summaryrefslogtreecommitdiff
path: root/misc/openlayers
diff options
context:
space:
mode:
Diffstat (limited to 'misc/openlayers')
-rw-r--r--misc/openlayers/.gitignore7
-rw-r--r--misc/openlayers/OpenLayers.debug.js85622
-rw-r--r--misc/openlayers/OpenLayers.js1443
-rw-r--r--misc/openlayers/OpenLayers.light.debug.js37230
-rw-r--r--misc/openlayers/OpenLayers.light.js592
-rw-r--r--misc/openlayers/OpenLayers.mobile.debug.js41831
-rw-r--r--misc/openlayers/OpenLayers.mobile.js681
-rw-r--r--misc/openlayers/apidoc_config/Languages.txt113
-rw-r--r--misc/openlayers/apidoc_config/Menu.txt520
-rw-r--r--misc/openlayers/apidoc_config/OL.css20
-rw-r--r--misc/openlayers/apidoc_config/Topics.txt105
-rw-r--r--misc/openlayers/art/arrows.svg127
-rw-r--r--misc/openlayers/art/layer-switcher-maximize.svg128
-rw-r--r--misc/openlayers/art/layer-switcher-minimize.svg142
-rw-r--r--misc/openlayers/art/marker.svg25
-rw-r--r--misc/openlayers/art/measuring-stick-off.svg36
-rw-r--r--misc/openlayers/art/measuring-stick-on.svg36
-rw-r--r--misc/openlayers/art/panning-hand-off.svg44
-rw-r--r--misc/openlayers/art/panning-hand-on.svg44
-rw-r--r--misc/openlayers/art/slider.svg71
-rw-r--r--misc/openlayers/art/zoom-world.svg193
-rw-r--r--misc/openlayers/art/zoombar.svg73
-rw-r--r--misc/openlayers/authors.txt56
-rw-r--r--misc/openlayers/build/README.txt46
-rwxr-xr-xmisc/openlayers/build/build.py158
-rwxr-xr-xmisc/openlayers/build/buildUncompressed.py25
-rw-r--r--misc/openlayers/build/closure-compiler/Externs.js50
-rw-r--r--misc/openlayers/build/full.cfg14
-rw-r--r--misc/openlayers/build/license.txt57
-rw-r--r--misc/openlayers/build/light.cfg32
-rw-r--r--misc/openlayers/build/lite.cfg17
-rw-r--r--misc/openlayers/build/mobile.cfg36
-rw-r--r--misc/openlayers/build/tests.cfg11
-rw-r--r--misc/openlayers/doc_config/Data/ClassHierarchy.ndbin0 -> 37981 bytes
-rw-r--r--misc/openlayers/doc_config/Data/ConfigFileInfo.ndbin0 -> 26 bytes
-rw-r--r--misc/openlayers/doc_config/Data/FileInfo.nd323
-rw-r--r--misc/openlayers/doc_config/Data/ImageFileInfo.ndbin0 -> 225 bytes
-rw-r--r--misc/openlayers/doc_config/Data/ImageReferenceTable.ndbin0 -> 449 bytes
-rw-r--r--misc/openlayers/doc_config/Data/IndexInfo.ndbin0 -> 267 bytes
-rw-r--r--misc/openlayers/doc_config/Data/PreviousMenuState.ndbin0 -> 27691 bytes
-rw-r--r--misc/openlayers/doc_config/Data/PreviousSettings.ndbin0 -> 121 bytes
-rw-r--r--misc/openlayers/doc_config/Data/SymbolTable.ndbin0 -> 1201468 bytes
-rw-r--r--misc/openlayers/doc_config/Languages.txt113
-rw-r--r--misc/openlayers/doc_config/Menu.txt520
-rw-r--r--misc/openlayers/doc_config/OL.css20
-rw-r--r--misc/openlayers/doc_config/Topics.txt102
-rw-r--r--misc/openlayers/examples/Jugl.js8
-rw-r--r--misc/openlayers/examples/KMLParser.html57
-rw-r--r--misc/openlayers/examples/SLDSelect.html202
-rw-r--r--misc/openlayers/examples/WMSDescribeLayerParser.html52
-rw-r--r--misc/openlayers/examples/accelerometer.html100
-rw-r--r--misc/openlayers/examples/accessible-click-control.html69
-rw-r--r--misc/openlayers/examples/accessible-click-control.js199
-rw-r--r--misc/openlayers/examples/accessible-panel.html130
-rw-r--r--misc/openlayers/examples/accessible-panel.js64
-rw-r--r--misc/openlayers/examples/accessible.html167
-rw-r--r--misc/openlayers/examples/all-overlays-google.html34
-rw-r--r--misc/openlayers/examples/all-overlays-google.js19
-rw-r--r--misc/openlayers/examples/all-overlays.html76
-rw-r--r--misc/openlayers/examples/anchor-permalink.html29
-rw-r--r--misc/openlayers/examples/anchor-permalink.js13
-rw-r--r--misc/openlayers/examples/animated_panning.html98
-rw-r--r--misc/openlayers/examples/animator.js670
-rw-r--r--misc/openlayers/examples/arcgis93rest.html69
-rw-r--r--misc/openlayers/examples/arcgiscache_ags.html221
-rw-r--r--misc/openlayers/examples/arcgiscache_direct.html108
-rw-r--r--misc/openlayers/examples/arcgiscache_jsonp.html106
-rw-r--r--misc/openlayers/examples/arcims-thematic.html82
-rw-r--r--misc/openlayers/examples/arcims.html57
-rw-r--r--misc/openlayers/examples/attribution.html60
-rw-r--r--misc/openlayers/examples/behavior-fixed-http-gml.html56
-rw-r--r--misc/openlayers/examples/bing-tiles-restrictedzoom.html43
-rw-r--r--misc/openlayers/examples/bing-tiles-restrictedzoom.js37
-rw-r--r--misc/openlayers/examples/bing-tiles.html39
-rw-r--r--misc/openlayers/examples/bing-tiles.js31
-rw-r--r--misc/openlayers/examples/bing.html64
-rw-r--r--misc/openlayers/examples/bootstrap.html81
-rw-r--r--misc/openlayers/examples/bootstrap.js31
-rw-r--r--misc/openlayers/examples/boxes-vector.html59
-rw-r--r--misc/openlayers/examples/boxes.html58
-rw-r--r--misc/openlayers/examples/browser.html152
-rw-r--r--misc/openlayers/examples/browser.js241
-rw-r--r--misc/openlayers/examples/buffer.html54
-rw-r--r--misc/openlayers/examples/cache-read.html36
-rw-r--r--misc/openlayers/examples/cache-read.js36
-rw-r--r--misc/openlayers/examples/cache-write.html37
-rw-r--r--misc/openlayers/examples/cache-write.js46
-rw-r--r--misc/openlayers/examples/canvas-hit-detection.html31
-rw-r--r--misc/openlayers/examples/canvas-hit-detection.js88
-rw-r--r--misc/openlayers/examples/canvas-inspector.html53
-rw-r--r--misc/openlayers/examples/canvas-inspector.js91
-rw-r--r--misc/openlayers/examples/canvas.html35
-rw-r--r--misc/openlayers/examples/canvas.js57
-rw-r--r--misc/openlayers/examples/cartodb-geojson.html71
-rw-r--r--misc/openlayers/examples/click-handler.html232
-rw-r--r--misc/openlayers/examples/click.html91
-rw-r--r--misc/openlayers/examples/clientzoom.html72
-rw-r--r--misc/openlayers/examples/clientzoom.js39
-rw-r--r--misc/openlayers/examples/controls.html86
-rw-r--r--misc/openlayers/examples/cql-format.html54
-rw-r--r--misc/openlayers/examples/cql-format.js61
-rw-r--r--misc/openlayers/examples/cross-origin-xml.html32
-rw-r--r--misc/openlayers/examples/cross-origin-xml.js25
-rw-r--r--misc/openlayers/examples/cross-origin.html36
-rw-r--r--misc/openlayers/examples/cross-origin.js39
-rw-r--r--misc/openlayers/examples/custom-control.html68
-rw-r--r--misc/openlayers/examples/custom-style.html66
-rw-r--r--misc/openlayers/examples/data/4_m_citylights_lg.gifbin0 -> 19000 bytes
-rw-r--r--misc/openlayers/examples/data/line.json10
-rw-r--r--misc/openlayers/examples/data/point.json8
-rw-r--r--misc/openlayers/examples/data/poly.json9
-rw-r--r--misc/openlayers/examples/data/roads.json349
-rw-r--r--misc/openlayers/examples/data/tazdem.tiffbin0 -> 58048 bytes
-rw-r--r--misc/openlayers/examples/debug.html77
-rw-r--r--misc/openlayers/examples/document-drag.html43
-rw-r--r--misc/openlayers/examples/donut.html62
-rw-r--r--misc/openlayers/examples/donut.js44
-rw-r--r--misc/openlayers/examples/drag-feature.html114
-rw-r--r--misc/openlayers/examples/draw-feature.html143
-rw-r--r--misc/openlayers/examples/draw-undo-redo.html38
-rw-r--r--misc/openlayers/examples/draw-undo-redo.js45
-rw-r--r--misc/openlayers/examples/dynamic-text-layer.html101
-rw-r--r--misc/openlayers/examples/editing-methods.html58
-rw-r--r--misc/openlayers/examples/editing-methods.js83
-rw-r--r--misc/openlayers/examples/editingtoolbar-outside.html56
-rw-r--r--misc/openlayers/examples/editingtoolbar.html55
-rw-r--r--misc/openlayers/examples/encoded-polyline.html47
-rw-r--r--misc/openlayers/examples/events.html155
-rw-r--r--misc/openlayers/examples/example-list.html302
-rw-r--r--misc/openlayers/examples/example.html25
-rw-r--r--misc/openlayers/examples/example.js23
-rw-r--r--misc/openlayers/examples/feature-events.html46
-rw-r--r--misc/openlayers/examples/feature-events.js67
-rw-r--r--misc/openlayers/examples/filter-strategy.html54
-rw-r--r--misc/openlayers/examples/filter-strategy.js84
-rw-r--r--misc/openlayers/examples/filter.html107
-rw-r--r--misc/openlayers/examples/fractional-zoom.html72
-rw-r--r--misc/openlayers/examples/fullScreen.html53
-rw-r--r--misc/openlayers/examples/fullScreen.js20
-rw-r--r--misc/openlayers/examples/fusiontables.html35
-rw-r--r--misc/openlayers/examples/fusiontables.js51
-rw-r--r--misc/openlayers/examples/game-accel-ball.html82
-rw-r--r--misc/openlayers/examples/geojson-reprojected.html46
-rw-r--r--misc/openlayers/examples/geojson-reprojected.js27
-rw-r--r--misc/openlayers/examples/geojson-reprojected.json1
-rw-r--r--misc/openlayers/examples/geojson.html77
-rw-r--r--misc/openlayers/examples/geolocation.html41
-rw-r--r--misc/openlayers/examples/geolocation.js112
-rw-r--r--misc/openlayers/examples/georss-flickr.html119
-rw-r--r--misc/openlayers/examples/georss-markers.html45
-rw-r--r--misc/openlayers/examples/georss.html63
-rw-r--r--misc/openlayers/examples/georss.xml378
-rw-r--r--misc/openlayers/examples/getfeature-wfs.html84
-rw-r--r--misc/openlayers/examples/getfeatureinfo-control.html221
-rw-r--r--misc/openlayers/examples/getfeatureinfo-popup.html96
-rw-r--r--misc/openlayers/examples/gml-layer.html48
-rw-r--r--misc/openlayers/examples/gml/line.xml42
-rw-r--r--misc/openlayers/examples/gml/multipoint.xml70
-rw-r--r--misc/openlayers/examples/gml/multipolygon.xml106
-rw-r--r--misc/openlayers/examples/gml/owls.xml156
-rw-r--r--misc/openlayers/examples/gml/point.xml42
-rw-r--r--misc/openlayers/examples/gml/polygon.xml89
-rw-r--r--misc/openlayers/examples/google-static.html39
-rw-r--r--misc/openlayers/examples/google-static.js61
-rw-r--r--misc/openlayers/examples/google-v3-alloverlays.html33
-rw-r--r--misc/openlayers/examples/google-v3-alloverlays.js35
-rw-r--r--misc/openlayers/examples/google-v3.html35
-rw-r--r--misc/openlayers/examples/google-v3.js39
-rw-r--r--misc/openlayers/examples/google.html69
-rw-r--r--misc/openlayers/examples/graphic-name.html42
-rw-r--r--misc/openlayers/examples/graphic-name.js67
-rw-r--r--misc/openlayers/examples/graticule.html116
-rw-r--r--misc/openlayers/examples/gutter.html55
-rw-r--r--misc/openlayers/examples/highlight-feature.html88
-rw-r--r--misc/openlayers/examples/hover-handler.html216
-rw-r--r--misc/openlayers/examples/image-layer.html76
-rw-r--r--misc/openlayers/examples/img/check-round-green.pngbin0 -> 895 bytes
-rw-r--r--misc/openlayers/examples/img/check-round-grey.pngbin0 -> 1057 bytes
-rw-r--r--misc/openlayers/examples/img/list.pngbin0 -> 995 bytes
-rw-r--r--misc/openlayers/examples/img/locate.pngbin0 -> 469 bytes
-rw-r--r--misc/openlayers/examples/img/marker_shadow.pngbin0 -> 374 bytes
-rw-r--r--misc/openlayers/examples/img/minus1.pngbin0 -> 261 bytes
-rw-r--r--misc/openlayers/examples/img/mobile-layers.pngbin0 -> 290 bytes
-rw-r--r--misc/openlayers/examples/img/mobile-loc.pngbin0 -> 353 bytes
-rw-r--r--misc/openlayers/examples/img/mobile-zoombar.pngbin0 -> 3760 bytes
-rw-r--r--misc/openlayers/examples/img/openlayers.pngbin0 -> 679 bytes
-rw-r--r--misc/openlayers/examples/img/popupMatrix.jpgbin0 -> 37469 bytes
-rw-r--r--misc/openlayers/examples/img/small.jpgbin0 -> 16415 bytes
-rw-r--r--misc/openlayers/examples/img/thinlong.jpgbin0 -> 60996 bytes
-rw-r--r--misc/openlayers/examples/img/widelong.jpgbin0 -> 214964 bytes
-rw-r--r--misc/openlayers/examples/img/wideshort.jpgbin0 -> 48477 bytes
-rw-r--r--misc/openlayers/examples/intersects.html193
-rw-r--r--misc/openlayers/examples/kamap.html45
-rw-r--r--misc/openlayers/examples/kamap.txt508
-rw-r--r--misc/openlayers/examples/kinetic.html44
-rw-r--r--misc/openlayers/examples/kinetic.js27
-rw-r--r--misc/openlayers/examples/kml-layer.html36
-rw-r--r--misc/openlayers/examples/kml-layer.js22
-rw-r--r--misc/openlayers/examples/kml-pointtrack.html40
-rw-r--r--misc/openlayers/examples/kml-pointtrack.js52
-rw-r--r--misc/openlayers/examples/kml-track.html42
-rw-r--r--misc/openlayers/examples/kml-track.js40
-rw-r--r--misc/openlayers/examples/kml-track.kml3359
-rw-r--r--misc/openlayers/examples/kml/lines.kml275
-rw-r--r--misc/openlayers/examples/kml/styles.kml21
-rw-r--r--misc/openlayers/examples/kml/sundials.kml2273
-rw-r--r--misc/openlayers/examples/label-scale.html34
-rw-r--r--misc/openlayers/examples/label-scale.js72
-rw-r--r--misc/openlayers/examples/late-render.html48
-rw-r--r--misc/openlayers/examples/layer-opacity.html95
-rw-r--r--misc/openlayers/examples/layerLoadMonitoring.html135
-rw-r--r--misc/openlayers/examples/layerswitcher.html60
-rw-r--r--misc/openlayers/examples/light-basic.html35
-rw-r--r--misc/openlayers/examples/light-basic.js67
-rw-r--r--misc/openlayers/examples/lite.html39
-rw-r--r--misc/openlayers/examples/mapbox.html30
-rw-r--r--misc/openlayers/examples/mapbox.js21
-rw-r--r--misc/openlayers/examples/mapguide.html155
-rw-r--r--misc/openlayers/examples/mapquest.html28
-rw-r--r--misc/openlayers/examples/mapquest.js36
-rw-r--r--misc/openlayers/examples/mapserver.html41
-rw-r--r--misc/openlayers/examples/mapserver_untiled.html43
-rw-r--r--misc/openlayers/examples/marker-shadow.html152
-rw-r--r--misc/openlayers/examples/markerResize.html60
-rw-r--r--misc/openlayers/examples/markers.html59
-rw-r--r--misc/openlayers/examples/markersTextLayer.html41
-rw-r--r--misc/openlayers/examples/measure.html203
-rw-r--r--misc/openlayers/examples/mobile-base.js167
-rw-r--r--misc/openlayers/examples/mobile-drawing.html52
-rw-r--r--misc/openlayers/examples/mobile-drawing.js71
-rw-r--r--misc/openlayers/examples/mobile-jq.html76
-rw-r--r--misc/openlayers/examples/mobile-jq.js159
-rw-r--r--misc/openlayers/examples/mobile-layers.html62
-rw-r--r--misc/openlayers/examples/mobile-layers.js71
-rw-r--r--misc/openlayers/examples/mobile-navigation.html52
-rw-r--r--misc/openlayers/examples/mobile-navigation.js24
-rw-r--r--misc/openlayers/examples/mobile-sencha.html184
-rw-r--r--misc/openlayers/examples/mobile-sencha.js198
-rw-r--r--misc/openlayers/examples/mobile-wmts-vienna.css205
-rw-r--r--misc/openlayers/examples/mobile-wmts-vienna.html27
-rw-r--r--misc/openlayers/examples/mobile-wmts-vienna.js281
-rw-r--r--misc/openlayers/examples/mobile.html56
-rw-r--r--misc/openlayers/examples/mobile.js39
-rw-r--r--misc/openlayers/examples/modify-feature.html193
-rw-r--r--misc/openlayers/examples/mouse-position.html67
-rw-r--r--misc/openlayers/examples/mousewheel-interval.html63
-rw-r--r--misc/openlayers/examples/multiserver.html52
-rw-r--r--misc/openlayers/examples/multitouch.html28
-rw-r--r--misc/openlayers/examples/mvs.html129
-rw-r--r--misc/openlayers/examples/navigation-control.html44
-rw-r--r--misc/openlayers/examples/navigation-history.html62
-rw-r--r--misc/openlayers/examples/navtoolbar-alwaysZoom.html85
-rw-r--r--misc/openlayers/examples/navtoolbar-outsidemap.html47
-rw-r--r--misc/openlayers/examples/navtoolbar.html46
-rw-r--r--misc/openlayers/examples/offline-storage.html44
-rw-r--r--misc/openlayers/examples/offline-storage.js199
-rw-r--r--misc/openlayers/examples/openls.html88
-rw-r--r--misc/openlayers/examples/ordering.html221
-rw-r--r--misc/openlayers/examples/osm-google.html32
-rw-r--r--misc/openlayers/examples/osm-google.js23
-rw-r--r--misc/openlayers/examples/osm-grayscale.html77
-rw-r--r--misc/openlayers/examples/osm-marker-popup.html32
-rw-r--r--misc/openlayers/examples/osm-marker-popup.js39
-rw-r--r--misc/openlayers/examples/osm.html41
-rw-r--r--misc/openlayers/examples/osm/sutton_coldfield.osm662
-rw-r--r--misc/openlayers/examples/overviewmap.html120
-rw-r--r--misc/openlayers/examples/pan-zoom-panels.html97
-rw-r--r--misc/openlayers/examples/panel.html99
-rw-r--r--misc/openlayers/examples/point-grid.html75
-rw-r--r--misc/openlayers/examples/point-grid.js33
-rw-r--r--misc/openlayers/examples/point-track-markers.html72
-rw-r--r--misc/openlayers/examples/polar-projections.html41
-rw-r--r--misc/openlayers/examples/polar-projections.js84
-rw-r--r--misc/openlayers/examples/popupMatrix.html652
-rwxr-xr-xmisc/openlayers/examples/proxy.cgi81
-rw-r--r--misc/openlayers/examples/regular-polygons.html177
-rw-r--r--misc/openlayers/examples/resize-features.html101
-rw-r--r--misc/openlayers/examples/restricted-extent.html77
-rw-r--r--misc/openlayers/examples/rotate-features.html113
-rw-r--r--misc/openlayers/examples/select-feature-multilayer.html129
-rw-r--r--misc/openlayers/examples/select-feature-openpopup.html106
-rw-r--r--misc/openlayers/examples/select-feature.html170
-rw-r--r--misc/openlayers/examples/setextent.html39
-rw-r--r--misc/openlayers/examples/simplify-linestring.html103
-rw-r--r--misc/openlayers/examples/simplify-linestring.js599
-rw-r--r--misc/openlayers/examples/single-tile.html33
-rw-r--r--misc/openlayers/examples/single-tile.js20
-rw-r--r--misc/openlayers/examples/sld-parser.html70
-rw-r--r--misc/openlayers/examples/sld.html31
-rw-r--r--misc/openlayers/examples/sld.js102
-rw-r--r--misc/openlayers/examples/snap-grid.html78
-rw-r--r--misc/openlayers/examples/snap-grid.js81
-rw-r--r--misc/openlayers/examples/snap-split.html281
-rw-r--r--misc/openlayers/examples/snapping.html324
-rw-r--r--misc/openlayers/examples/sos.html189
-rw-r--r--misc/openlayers/examples/spherical-mercator.html120
-rw-r--r--misc/openlayers/examples/split-feature.html116
-rw-r--r--misc/openlayers/examples/strategy-bbox.html106
-rw-r--r--misc/openlayers/examples/strategy-cluster-extended.html125
-rw-r--r--misc/openlayers/examples/strategy-cluster-extended.js247
-rw-r--r--misc/openlayers/examples/strategy-cluster-threshold.html149
-rw-r--r--misc/openlayers/examples/strategy-cluster.html238
-rw-r--r--misc/openlayers/examples/strategy-paging.html115
-rw-r--r--misc/openlayers/examples/style-rules.html49
-rw-r--r--misc/openlayers/examples/style-rules.js99
-rw-r--r--misc/openlayers/examples/style.css143
-rw-r--r--misc/openlayers/examples/style.mobile-jq.css62
-rw-r--r--misc/openlayers/examples/stylemap.html100
-rw-r--r--misc/openlayers/examples/styles-context.html117
-rw-r--r--misc/openlayers/examples/styles-rotation.html93
-rw-r--r--misc/openlayers/examples/styles-unique.html109
-rw-r--r--misc/openlayers/examples/sundials-spherical-mercator.html111
-rw-r--r--misc/openlayers/examples/sundials.html107
-rw-r--r--misc/openlayers/examples/symbolizers-fill-stroke-graphic.html141
-rw-r--r--misc/openlayers/examples/tasmania/TasmaniaCities.xml40
-rw-r--r--misc/openlayers/examples/tasmania/TasmaniaRoads.xml204
-rw-r--r--misc/openlayers/examples/tasmania/TasmaniaStateBoundaries.xml92
-rw-r--r--misc/openlayers/examples/tasmania/TasmaniaWaterBodies.xml162
-rw-r--r--misc/openlayers/examples/tasmania/sld-tasmania.xml594
-rw-r--r--misc/openlayers/examples/teleportation.html76
-rw-r--r--misc/openlayers/examples/textfile.txt4
-rw-r--r--misc/openlayers/examples/tile-origin.html38
-rw-r--r--misc/openlayers/examples/tile-origin.js16
-rw-r--r--misc/openlayers/examples/tilecache.html58
-rw-r--r--misc/openlayers/examples/tms.html62
-rw-r--r--misc/openlayers/examples/transform-feature.html123
-rw-r--r--misc/openlayers/examples/transition.html70
-rw-r--r--misc/openlayers/examples/using-proj4js.html109
-rw-r--r--misc/openlayers/examples/using-proj4js.js132
-rw-r--r--misc/openlayers/examples/utfgrid-geography-class.html51
-rw-r--r--misc/openlayers/examples/utfgrid-geography-class.js62
-rw-r--r--misc/openlayers/examples/utfgrid.html64
-rw-r--r--misc/openlayers/examples/utfgrid.js61
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/0.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/1.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/2.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/0.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/1.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/2.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/0.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/1.json1
-rw-r--r--misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/2.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/0/0/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/1/0/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/1/0/1.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/1/1/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/1/1/1.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/0/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/0/1.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/0/2.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/0/3.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/1/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/1/1.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/1/2.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/1/3.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/2/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/2/1.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/2/2.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/2/3.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/3/0.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/3/1.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/3/2.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/geography-class/2/3/3.grid.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/0/0.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/0/1.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/0/2.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/1/0.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/1/1.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/1/2.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/2/0.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/2/1.json1
-rw-r--r--misc/openlayers/examples/utfgrid/world_utfgrid/1/2/2.json1
-rw-r--r--misc/openlayers/examples/utfgrid_twogrids.html51
-rw-r--r--misc/openlayers/examples/utfgrid_twogrids.js70
-rw-r--r--misc/openlayers/examples/vector-features-with-text.html138
-rw-r--r--misc/openlayers/examples/vector-features.html149
-rw-r--r--misc/openlayers/examples/vector-formats.html240
-rw-r--r--misc/openlayers/examples/web-mercator.html50
-rw-r--r--misc/openlayers/examples/web-mercator.js37
-rw-r--r--misc/openlayers/examples/wfs-filter.html35
-rw-r--r--misc/openlayers/examples/wfs-filter.js48
-rw-r--r--misc/openlayers/examples/wfs-protocol-transactions.html104
-rw-r--r--misc/openlayers/examples/wfs-protocol-transactions.js106
-rw-r--r--misc/openlayers/examples/wfs-protocol.html55
-rw-r--r--misc/openlayers/examples/wfs-reprojection.html42
-rw-r--r--misc/openlayers/examples/wfs-reprojection.js60
-rw-r--r--misc/openlayers/examples/wfs-snap-split.html292
-rw-r--r--misc/openlayers/examples/wfs-spatial-filter.html37
-rw-r--r--misc/openlayers/examples/wfs-spatial-filter.js36
-rw-r--r--misc/openlayers/examples/wfs-states.html34
-rw-r--r--misc/openlayers/examples/wfs-states.js35
-rw-r--r--misc/openlayers/examples/wmc.html150
-rw-r--r--misc/openlayers/examples/wms-long-url.html44
-rw-r--r--misc/openlayers/examples/wms-long-url.js26
-rw-r--r--misc/openlayers/examples/wms-untiled.html48
-rw-r--r--misc/openlayers/examples/wms-v13.html57
-rw-r--r--misc/openlayers/examples/wms.html49
-rw-r--r--misc/openlayers/examples/wmst.html63
-rw-r--r--misc/openlayers/examples/wmts-capabilities.html43
-rw-r--r--misc/openlayers/examples/wmts-capabilities.js58
-rw-r--r--misc/openlayers/examples/wmts-getfeatureinfo.html74
-rw-r--r--misc/openlayers/examples/wmts-getfeatureinfo.js94
-rw-r--r--misc/openlayers/examples/wmts.html41
-rw-r--r--misc/openlayers/examples/wmts.js35
-rw-r--r--misc/openlayers/examples/wps-client.html31
-rw-r--r--misc/openlayers/examples/wps-client.js75
-rw-r--r--misc/openlayers/examples/wps.html89
-rw-r--r--misc/openlayers/examples/wps.js353
-rw-r--r--misc/openlayers/examples/wrapDateLine.html73
-rw-r--r--misc/openlayers/examples/xhtml.html42
-rw-r--r--misc/openlayers/examples/xml.html161
-rw-r--r--misc/openlayers/examples/xml/features.xml2
-rw-r--r--misc/openlayers/examples/xml/georss-flickr.xml730
-rw-r--r--misc/openlayers/examples/xml/track1.xml98
-rw-r--r--misc/openlayers/examples/xml/wmsdescribelayer.xml5
-rw-r--r--misc/openlayers/examples/xyz-esri.html45
-rw-r--r--misc/openlayers/examples/yelp-georss.xml147
-rw-r--r--misc/openlayers/examples/zoom.html68
-rw-r--r--misc/openlayers/examples/zoom.js34
-rw-r--r--misc/openlayers/examples/zoomLevels.html81
-rw-r--r--misc/openlayers/examples/zoomify.html70
-rw-r--r--misc/openlayers/img/blank.gifbin0 -> 42 bytes
-rwxr-xr-xmisc/openlayers/img/cloud-popup-relative.pngbin0 -> 4067 bytes
-rw-r--r--misc/openlayers/img/drag-rectangle-off.pngbin0 -> 1024 bytes
-rw-r--r--misc/openlayers/img/drag-rectangle-on.pngbin0 -> 1041 bytes
-rw-r--r--misc/openlayers/img/east-mini.pngbin0 -> 342 bytes
-rw-r--r--misc/openlayers/img/layer-switcher-maximize.pngbin0 -> 405 bytes
-rw-r--r--misc/openlayers/img/layer-switcher-minimize.pngbin0 -> 220 bytes
-rw-r--r--misc/openlayers/img/marker-blue.pngbin0 -> 758 bytes
-rw-r--r--misc/openlayers/img/marker-gold.pngbin0 -> 703 bytes
-rw-r--r--misc/openlayers/img/marker-green.pngbin0 -> 753 bytes
-rw-r--r--misc/openlayers/img/marker.pngbin0 -> 601 bytes
-rw-r--r--misc/openlayers/img/measuring-stick-off.pngbin0 -> 3028 bytes
-rw-r--r--misc/openlayers/img/measuring-stick-on.pngbin0 -> 3725 bytes
-rw-r--r--misc/openlayers/img/north-mini.pngbin0 -> 378 bytes
-rw-r--r--misc/openlayers/img/panning-hand-off.pngbin0 -> 3511 bytes
-rw-r--r--misc/openlayers/img/panning-hand-on.pngbin0 -> 3565 bytes
-rw-r--r--misc/openlayers/img/slider.pngbin0 -> 247 bytes
-rw-r--r--misc/openlayers/img/south-mini.pngbin0 -> 373 bytes
-rw-r--r--misc/openlayers/img/west-mini.pngbin0 -> 360 bytes
-rw-r--r--misc/openlayers/img/zoom-minus-mini.pngbin0 -> 291 bytes
-rw-r--r--misc/openlayers/img/zoom-plus-mini.pngbin0 -> 386 bytes
-rw-r--r--misc/openlayers/img/zoom-world-mini.pngbin0 -> 882 bytes
-rw-r--r--misc/openlayers/img/zoombar.pngbin0 -> 350 bytes
-rw-r--r--misc/openlayers/lib/Firebug/errorIcon.pngbin0 -> 457 bytes
-rw-r--r--misc/openlayers/lib/Firebug/firebug.css209
-rw-r--r--misc/openlayers/lib/Firebug/firebug.html23
-rw-r--r--misc/openlayers/lib/Firebug/firebug.js674
-rw-r--r--misc/openlayers/lib/Firebug/firebugx.js10
-rw-r--r--misc/openlayers/lib/Firebug/infoIcon.pngbin0 -> 524 bytes
-rw-r--r--misc/openlayers/lib/Firebug/license.txt30
-rw-r--r--misc/openlayers/lib/Firebug/readme.txt13
-rw-r--r--misc/openlayers/lib/Firebug/warningIcon.pngbin0 -> 516 bytes
-rw-r--r--misc/openlayers/lib/OpenLayers.js429
-rw-r--r--misc/openlayers/lib/OpenLayers/Animation.js102
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes.js463
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/Bounds.js837
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/Class.js121
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/Date.js123
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/Element.js189
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/LonLat.js215
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/Pixel.js143
-rw-r--r--misc/openlayers/lib/OpenLayers/BaseTypes/Size.js89
-rw-r--r--misc/openlayers/lib/OpenLayers/Console.js250
-rw-r--r--misc/openlayers/lib/OpenLayers/Control.js371
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ArgParser.js182
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Attribution.js104
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Button.js44
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/CacheRead.js156
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/CacheWrite.js257
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/DragFeature.js366
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/DragPan.js156
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/DrawFeature.js229
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/EditingToolbar.js81
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Geolocate.js192
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/GetFeature.js597
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Graticule.js377
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/KeyboardDefaults.js142
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/LayerSwitcher.js521
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Measure.js379
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ModifyFeature.js835
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/MousePosition.js227
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/NavToolbar.js57
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Navigation.js345
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/NavigationHistory.js423
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/OverviewMap.js750
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Pan.js95
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/PanPanel.js73
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/PanZoom.js233
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/PanZoomBar.js408
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Panel.js431
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Permalink.js257
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/PinchZoom.js157
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/SLDSelect.js567
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Scale.js100
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ScaleLine.js220
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/SelectFeature.js643
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Snapping.js560
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Split.js494
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/TouchNavigation.js182
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/TransformFeature.js624
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/UTFGrid.js240
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/WMSGetFeatureInfo.js532
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/WMTSGetFeatureInfo.js400
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/Zoom.js138
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ZoomBox.js129
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ZoomIn.js29
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ZoomOut.js29
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ZoomPanel.js54
-rw-r--r--misc/openlayers/lib/OpenLayers/Control/ZoomToMaxExtent.js35
-rw-r--r--misc/openlayers/lib/OpenLayers/Events.js1170
-rw-r--r--misc/openlayers/lib/OpenLayers/Events/buttonclick.js206
-rw-r--r--misc/openlayers/lib/OpenLayers/Events/featureclick.js321
-rw-r--r--misc/openlayers/lib/OpenLayers/Feature.js225
-rw-r--r--misc/openlayers/lib/OpenLayers/Feature/Vector.js510
-rw-r--r--misc/openlayers/lib/OpenLayers/Filter.js87
-rw-r--r--misc/openlayers/lib/OpenLayers/Filter/Comparison.js267
-rw-r--r--misc/openlayers/lib/OpenLayers/Filter/FeatureId.js87
-rw-r--r--misc/openlayers/lib/OpenLayers/Filter/Function.js49
-rw-r--r--misc/openlayers/lib/OpenLayers/Filter/Logical.js121
-rw-r--r--misc/openlayers/lib/OpenLayers/Filter/Spatial.js122
-rw-r--r--misc/openlayers/lib/OpenLayers/Format.js123
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/ArcXML.js1028
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/ArcXML/Features.js46
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Atom.js712
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/CQL.js452
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/CSWGetDomain.js34
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/CSWGetDomain/v2_0_2.js240
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/CSWGetRecords.js34
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/CSWGetRecords/v2_0_2.js457
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Context.js334
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/EncodedPolyline.js557
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Filter.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Filter/v1.js504
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Filter/v1_0_0.js184
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Filter/v1_1_0.js222
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GML.js923
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GML/Base.js645
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GML/v2.js193
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GML/v3.js477
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GPX.js385
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GeoJSON.js716
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/GeoRSS.js409
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/JSON.js398
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/KML.js1517
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OGCExceptionReport.js108
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OSM.js465
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OWSCommon.js78
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1.js318
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_0_0.js62
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_1_0.js116
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OWSContext.js86
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/OWSContext/v0_3_1.js595
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/QueryStringFilter.js183
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SLD.js81
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SLD/v1.js1309
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0.js46
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js149
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SOSCapabilities.js48
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SOSCapabilities/v1_0_0.js158
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SOSGetFeatureOfInterest.js190
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/SOSGetObservation.js302
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/Text.js151
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WCSCapabilities.js47
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1.js55
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_0_0.js170
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_1_0.js109
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WCSGetCoverage.js199
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFS.js223
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFSCapabilities.js47
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1.js129
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js115
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_1_0.js63
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFSDescribeFeatureType.js234
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFST.js34
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFST/v1.js446
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFST/v1_0_0.js174
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WFST/v1_1_0.js189
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WKT.js392
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMC.js182
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMC/v1.js1267
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMC/v1_0_0.js104
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMC/v1_1_0.js149
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities.js56
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1.js368
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1.js122
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_0.js57
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1.js60
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js85
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3.js128
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3_0.js30
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer/v1_1.js122
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMSGetFeatureInfo.js296
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities.js230
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js251
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WPSCapabilities.js48
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WPSCapabilities/v1_0_0.js119
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WPSDescribeProcess.js185
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/WPSExecute.js395
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/XLS.js68
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/XLS/v1.js304
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/XLS/v1_1_0.js48
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/XML.js897
-rw-r--r--misc/openlayers/lib/OpenLayers/Format/XML/VersionedOGC.js212
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry.js500
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/Collection.js563
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/Curve.js89
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/LineString.js646
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/LinearRing.js433
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/MultiLineString.js258
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/MultiPoint.js66
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/MultiPolygon.js42
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/Point.js283
-rw-r--r--misc/openlayers/lib/OpenLayers/Geometry/Polygon.js255
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler.js325
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Box.js244
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Click.js505
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Drag.js547
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Feature.js434
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Hover.js180
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Keyboard.js117
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js264
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Path.js543
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Pinch.js239
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Point.js556
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/Polygon.js305
-rw-r--r--misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js429
-rw-r--r--misc/openlayers/lib/OpenLayers/Icon.js243
-rw-r--r--misc/openlayers/lib/OpenLayers/Kinetic.js178
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang.js134
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ar.js32
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/be-tarask.js54
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/bg.js25
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/br.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ca.js89
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/cs-CZ.js45
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/da-DK.js80
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/de.js55
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/el.js19
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/en-CA.js21
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/en.js89
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/es.js90
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/fi.js32
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/fr.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/fur.js35
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/gl.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/gsw.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/hr.js37
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/hsb.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/hu.js54
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ia.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/id.js54
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/io.js19
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/is.js27
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/it.js80
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ja.js54
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/km.js23
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ksh.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/lt.js47
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/nb.js82
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/nds.js37
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/nl.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/nn.js19
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/oc.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/pl.js89
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/pt-BR.js54
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/pt.js55
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ro.js69
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/ru.js56
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/sk.js44
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/sv-SE.js45
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/te.js27
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/vi.js53
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/zh-CN.js80
-rw-r--r--misc/openlayers/lib/OpenLayers/Lang/zh-TW.js81
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer.js1377
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js225
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js480
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js425
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Bing.js333
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Boxes.js76
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/EventPane.js441
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js319
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js265
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Google.js809
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Google/v3.js351
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Grid.js1343
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js230
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Image.js259
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/KaMap.js192
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js143
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/MapGuide.js443
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/MapServer.js181
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Markers.js187
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/OSM.js123
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/PointGrid.js299
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/PointTrack.js125
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js146
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/TMS.js202
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Text.js267
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/TileCache.js140
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js184
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Vector.js1007
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js154
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/WMS.js267
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/WMTS.js510
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/WorldWind.js105
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/XYZ.js172
-rw-r--r--misc/openlayers/lib/OpenLayers/Layer/Zoomify.js260
-rw-r--r--misc/openlayers/lib/OpenLayers/Map.js2867
-rw-r--r--misc/openlayers/lib/OpenLayers/Marker.js241
-rw-r--r--misc/openlayers/lib/OpenLayers/Marker/Box.js120
-rw-r--r--misc/openlayers/lib/OpenLayers/Popup.js1065
-rw-r--r--misc/openlayers/lib/OpenLayers/Popup/Anchored.js195
-rw-r--r--misc/openlayers/lib/OpenLayers/Popup/Framed.js343
-rw-r--r--misc/openlayers/lib/OpenLayers/Popup/FramedCloud.js227
-rw-r--r--misc/openlayers/lib/OpenLayers/Projection.js322
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol.js291
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/CSW.js30
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/CSW/v2_0_2.js127
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/HTTP.js580
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/SOS.js33
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/SOS/v1_0_0.js133
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/Script.js377
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/WFS.js86
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/WFS/v1.js453
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_0_0.js44
-rw-r--r--misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_1_0.js68
-rw-r--r--misc/openlayers/lib/OpenLayers/Renderer.js432
-rw-r--r--misc/openlayers/lib/OpenLayers/Renderer/Canvas.js906
-rw-r--r--misc/openlayers/lib/OpenLayers/Renderer/Elements.js1053
-rw-r--r--misc/openlayers/lib/OpenLayers/Renderer/SVG.js1012
-rw-r--r--misc/openlayers/lib/OpenLayers/Renderer/VML.js985
-rw-r--r--misc/openlayers/lib/OpenLayers/Request.js429
-rw-r--r--misc/openlayers/lib/OpenLayers/Request/XMLHttpRequest.js458
-rw-r--r--misc/openlayers/lib/OpenLayers/Rule.js236
-rw-r--r--misc/openlayers/lib/OpenLayers/SingleFile.js78
-rw-r--r--misc/openlayers/lib/OpenLayers/Spherical.js67
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy.js121
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/BBOX.js290
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/Cluster.js283
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/Filter.js159
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/Fixed.js135
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/Paging.js233
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/Refresh.js141
-rw-r--r--misc/openlayers/lib/OpenLayers/Strategy/Save.js231
-rw-r--r--misc/openlayers/lib/OpenLayers/Style.js448
-rw-r--r--misc/openlayers/lib/OpenLayers/Style2.js112
-rw-r--r--misc/openlayers/lib/OpenLayers/StyleMap.js161
-rw-r--r--misc/openlayers/lib/OpenLayers/Symbolizer.js55
-rw-r--r--misc/openlayers/lib/OpenLayers/Symbolizer/Line.js74
-rw-r--r--misc/openlayers/lib/OpenLayers/Symbolizer/Point.js157
-rw-r--r--misc/openlayers/lib/OpenLayers/Symbolizer/Polygon.js88
-rw-r--r--misc/openlayers/lib/OpenLayers/Symbolizer/Raster.js34
-rw-r--r--misc/openlayers/lib/OpenLayers/Symbolizer/Text.js70
-rw-r--r--misc/openlayers/lib/OpenLayers/Tile.js292
-rw-r--r--misc/openlayers/lib/OpenLayers/Tile/Image.js510
-rw-r--r--misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js233
-rw-r--r--misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js252
-rw-r--r--misc/openlayers/lib/OpenLayers/TileManager.js462
-rw-r--r--misc/openlayers/lib/OpenLayers/Tween.js361
-rw-r--r--misc/openlayers/lib/OpenLayers/Util.js1773
-rw-r--r--misc/openlayers/lib/OpenLayers/Util/vendorPrefix.js131
-rw-r--r--misc/openlayers/lib/OpenLayers/WPSClient.js223
-rw-r--r--misc/openlayers/lib/OpenLayers/WPSProcess.js501
-rw-r--r--misc/openlayers/lib/Rico/Color.js253
-rw-r--r--misc/openlayers/lib/Rico/Corner.js339
-rw-r--r--misc/openlayers/lib/Rico/license.js19
-rw-r--r--misc/openlayers/lib/deprecated.js5842
-rw-r--r--misc/openlayers/license.txt27
-rw-r--r--misc/openlayers/licenses/APACHE-2.0.txt202
-rw-r--r--misc/openlayers/licenses/BSD-LICENSE.txt28
-rw-r--r--misc/openlayers/licenses/MIT-LICENSE.txt18
-rw-r--r--misc/openlayers/notes/2.12.md391
-rw-r--r--misc/openlayers/notes/2.13.md159
-rw-r--r--misc/openlayers/readme.md79
-rw-r--r--misc/openlayers/tests/Animation.html96
-rw-r--r--misc/openlayers/tests/BaseTypes.html387
-rw-r--r--misc/openlayers/tests/BaseTypes/Bounds.html738
-rw-r--r--misc/openlayers/tests/BaseTypes/Class.html350
-rw-r--r--misc/openlayers/tests/BaseTypes/Date.html191
-rw-r--r--misc/openlayers/tests/BaseTypes/Element.html195
-rw-r--r--misc/openlayers/tests/BaseTypes/LonLat.html241
-rw-r--r--misc/openlayers/tests/BaseTypes/Pixel.html123
-rw-r--r--misc/openlayers/tests/BaseTypes/Size.html67
-rw-r--r--misc/openlayers/tests/Console.html39
-rw-r--r--misc/openlayers/tests/Control.html107
-rw-r--r--misc/openlayers/tests/Control/ArgParser.html26
-rw-r--r--misc/openlayers/tests/Control/Attribution.html60
-rw-r--r--misc/openlayers/tests/Control/Button.html17
-rw-r--r--misc/openlayers/tests/Control/CacheRead.html108
-rw-r--r--misc/openlayers/tests/Control/CacheWrite.html90
-rw-r--r--misc/openlayers/tests/Control/DragFeature.html383
-rw-r--r--misc/openlayers/tests/Control/DragPan.html104
-rw-r--r--misc/openlayers/tests/Control/DrawFeature.html160
-rw-r--r--misc/openlayers/tests/Control/EditingToolbar.html33
-rw-r--r--misc/openlayers/tests/Control/Geolocate.html129
-rw-r--r--misc/openlayers/tests/Control/GetFeature.html177
-rw-r--r--misc/openlayers/tests/Control/Graticule.html66
-rw-r--r--misc/openlayers/tests/Control/KeyboardDefaults.html173
-rw-r--r--misc/openlayers/tests/Control/LayerSwitcher.html249
-rw-r--r--misc/openlayers/tests/Control/Measure.html386
-rw-r--r--misc/openlayers/tests/Control/ModifyFeature.html828
-rw-r--r--misc/openlayers/tests/Control/MousePosition.html109
-rw-r--r--misc/openlayers/tests/Control/NavToolbar.html45
-rw-r--r--misc/openlayers/tests/Control/Navigation.html200
-rw-r--r--misc/openlayers/tests/Control/NavigationHistory.html245
-rw-r--r--misc/openlayers/tests/Control/OverviewMap.html266
-rw-r--r--misc/openlayers/tests/Control/Pan.html201
-rw-r--r--misc/openlayers/tests/Control/PanPanel.html61
-rw-r--r--misc/openlayers/tests/Control/PanZoom.html244
-rw-r--r--misc/openlayers/tests/Control/PanZoomBar.html245
-rw-r--r--misc/openlayers/tests/Control/Panel.html382
-rw-r--r--misc/openlayers/tests/Control/Permalink.html453
-rw-r--r--misc/openlayers/tests/Control/PinchZoom.html134
-rw-r--r--misc/openlayers/tests/Control/SLDSelect.html239
-rw-r--r--misc/openlayers/tests/Control/Scale.html54
-rw-r--r--misc/openlayers/tests/Control/ScaleLine.html187
-rw-r--r--misc/openlayers/tests/Control/SelectFeature.html632
-rw-r--r--misc/openlayers/tests/Control/Snapping.html448
-rw-r--r--misc/openlayers/tests/Control/Split.html319
-rw-r--r--misc/openlayers/tests/Control/TouchNavigation.html155
-rw-r--r--misc/openlayers/tests/Control/TransformFeature.html129
-rw-r--r--misc/openlayers/tests/Control/UTFGrid.html120
-rw-r--r--misc/openlayers/tests/Control/WMSGetFeatureInfo.html644
-rw-r--r--misc/openlayers/tests/Control/WMTSGetFeatureInfo.html334
-rw-r--r--misc/openlayers/tests/Control/Zoom.html83
-rw-r--r--misc/openlayers/tests/Control/ZoomBox.html54
-rw-r--r--misc/openlayers/tests/Control/ZoomIn.html101
-rw-r--r--misc/openlayers/tests/Control/ZoomOut.html100
-rw-r--r--misc/openlayers/tests/Control/ZoomToMaxExtent.html102
-rw-r--r--misc/openlayers/tests/Events.html487
-rw-r--r--misc/openlayers/tests/Events/buttonclick.html214
-rw-r--r--misc/openlayers/tests/Events/featureclick.html91
-rw-r--r--misc/openlayers/tests/Extras.html21
-rw-r--r--misc/openlayers/tests/Feature.html205
-rw-r--r--misc/openlayers/tests/Feature/Vector.html170
-rw-r--r--misc/openlayers/tests/Filter.html31
-rw-r--r--misc/openlayers/tests/Filter/Comparison.html373
-rw-r--r--misc/openlayers/tests/Filter/FeatureId.html67
-rw-r--r--misc/openlayers/tests/Filter/Logical.html144
-rw-r--r--misc/openlayers/tests/Filter/Spatial.html112
-rw-r--r--misc/openlayers/tests/Format.html23
-rw-r--r--misc/openlayers/tests/Format/ArcXML.html277
-rw-r--r--misc/openlayers/tests/Format/ArcXML/Features.html69
-rw-r--r--misc/openlayers/tests/Format/Atom.html450
-rw-r--r--misc/openlayers/tests/Format/CQL.html364
-rw-r--r--misc/openlayers/tests/Format/CSWGetDomain.html23
-rw-r--r--misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.html56
-rw-r--r--misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.js18
-rw-r--r--misc/openlayers/tests/Format/CSWGetRecords.html23
-rw-r--r--misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.html88
-rw-r--r--misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.js50
-rw-r--r--misc/openlayers/tests/Format/EncodedPolyline.html372
-rw-r--r--misc/openlayers/tests/Format/Filter.html21
-rw-r--r--misc/openlayers/tests/Format/Filter/v1.html404
-rw-r--r--misc/openlayers/tests/Format/Filter/v1_0_0.html295
-rw-r--r--misc/openlayers/tests/Format/Filter/v1_1_0.html402
-rw-r--r--misc/openlayers/tests/Format/GML.html462
-rw-r--r--misc/openlayers/tests/Format/GML/cases.js232
-rw-r--r--misc/openlayers/tests/Format/GML/v2.html684
-rw-r--r--misc/openlayers/tests/Format/GML/v3.html828
-rw-r--r--misc/openlayers/tests/Format/GPX.html179
-rw-r--r--misc/openlayers/tests/Format/GeoJSON.html468
-rw-r--r--misc/openlayers/tests/Format/GeoRSS.html122
-rw-r--r--misc/openlayers/tests/Format/JSON.html53
-rw-r--r--misc/openlayers/tests/Format/KML.html1437
-rw-r--r--misc/openlayers/tests/Format/OGCExceptionReport.html100
-rw-r--r--misc/openlayers/tests/Format/OSM.html115
-rw-r--r--misc/openlayers/tests/Format/OWSCommon/v1_0_0.html34
-rw-r--r--misc/openlayers/tests/Format/OWSCommon/v1_1_0.html34
-rw-r--r--misc/openlayers/tests/Format/OWSContext/v0_3_1.html278
-rw-r--r--misc/openlayers/tests/Format/QueryStringFilter.html306
-rw-r--r--misc/openlayers/tests/Format/SLD.html36
-rw-r--r--misc/openlayers/tests/Format/SLD/v1_0_0.html1028
-rw-r--r--misc/openlayers/tests/Format/SLD/v1_0_0_GeoServer.html228
-rw-r--r--misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.html80
-rw-r--r--misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.js484
-rw-r--r--misc/openlayers/tests/Format/SOSGetFeatureOfInterest.html80
-rw-r--r--misc/openlayers/tests/Format/SOSGetObservation.html183
-rw-r--r--misc/openlayers/tests/Format/Text.html49
-rw-r--r--misc/openlayers/tests/Format/WCSCapabilities.html43
-rw-r--r--misc/openlayers/tests/Format/WCSCapabilities/v1.html87
-rw-r--r--misc/openlayers/tests/Format/WCSGetCoverage.html80
-rw-r--r--misc/openlayers/tests/Format/WFS.html81
-rw-r--r--misc/openlayers/tests/Format/WFSCapabilities.html43
-rw-r--r--misc/openlayers/tests/Format/WFSCapabilities/v1.html179
-rw-r--r--misc/openlayers/tests/Format/WFSDescribeFeatureType.html436
-rw-r--r--misc/openlayers/tests/Format/WFST.html23
-rw-r--r--misc/openlayers/tests/Format/WFST/v1.html455
-rw-r--r--misc/openlayers/tests/Format/WFST/v1_0_0.html135
-rw-r--r--misc/openlayers/tests/Format/WFST/v1_1_0.html236
-rw-r--r--misc/openlayers/tests/Format/WKT.html297
-rw-r--r--misc/openlayers/tests/Format/WMC.html315
-rw-r--r--misc/openlayers/tests/Format/WMC/v1.html266
-rw-r--r--misc/openlayers/tests/Format/WMC/v1_1_0.html86
-rw-r--r--misc/openlayers/tests/Format/WMSCapabilities.html20
-rw-r--r--misc/openlayers/tests/Format/WMSCapabilities/v1_1_1.html5209
-rw-r--r--misc/openlayers/tests/Format/WMSCapabilities/v1_1_1_WMSC.html348
-rw-r--r--misc/openlayers/tests/Format/WMSCapabilities/v1_3_0.html614
-rw-r--r--misc/openlayers/tests/Format/WMSDescribeLayer.html65
-rw-r--r--misc/openlayers/tests/Format/WMSGetFeatureInfo.html319
-rw-r--r--misc/openlayers/tests/Format/WMTSCapabilities.html20
-rw-r--r--misc/openlayers/tests/Format/WMTSCapabilities/v1_0_0.html1042
-rw-r--r--misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.html30
-rw-r--r--misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.js112
-rw-r--r--misc/openlayers/tests/Format/WPSDescribeProcess.html206
-rw-r--r--misc/openlayers/tests/Format/WPSExecute.html549
-rw-r--r--misc/openlayers/tests/Format/XLS/v1_1_0.html98
-rw-r--r--misc/openlayers/tests/Format/XML.html900
-rw-r--r--misc/openlayers/tests/Format/XML/VersionedOGC.html51
-rw-r--r--misc/openlayers/tests/Geometry.html356
-rw-r--r--misc/openlayers/tests/Geometry/Collection.html431
-rw-r--r--misc/openlayers/tests/Geometry/Curve.html157
-rw-r--r--misc/openlayers/tests/Geometry/LineString.html443
-rw-r--r--misc/openlayers/tests/Geometry/LinearRing.html362
-rw-r--r--misc/openlayers/tests/Geometry/MultiLineString.html267
-rw-r--r--misc/openlayers/tests/Geometry/MultiPoint.html130
-rw-r--r--misc/openlayers/tests/Geometry/MultiPolygon.html34
-rw-r--r--misc/openlayers/tests/Geometry/Point.html244
-rw-r--r--misc/openlayers/tests/Geometry/Polygon.html420
-rw-r--r--misc/openlayers/tests/Handler.html265
-rw-r--r--misc/openlayers/tests/Handler/Box.html106
-rw-r--r--misc/openlayers/tests/Handler/Click.html735
-rw-r--r--misc/openlayers/tests/Handler/Drag.html603
-rw-r--r--misc/openlayers/tests/Handler/Feature.html698
-rw-r--r--misc/openlayers/tests/Handler/Hover.html136
-rw-r--r--misc/openlayers/tests/Handler/Keyboard.html150
-rw-r--r--misc/openlayers/tests/Handler/MouseWheel.html182
-rw-r--r--misc/openlayers/tests/Handler/Path.html1464
-rw-r--r--misc/openlayers/tests/Handler/Pinch.html285
-rw-r--r--misc/openlayers/tests/Handler/Point.html600
-rw-r--r--misc/openlayers/tests/Handler/Polygon.html1161
-rw-r--r--misc/openlayers/tests/Handler/RegularPolygon.html235
-rw-r--r--misc/openlayers/tests/Icon.html68
-rw-r--r--misc/openlayers/tests/Kinetic.html132
-rw-r--r--misc/openlayers/tests/Lang.html106
-rw-r--r--misc/openlayers/tests/Layer.html910
-rw-r--r--misc/openlayers/tests/Layer/ArcGIS93Rest.html324
-rw-r--r--misc/openlayers/tests/Layer/ArcGISCache.html256
-rw-r--r--misc/openlayers/tests/Layer/ArcGISCache.json334
-rw-r--r--misc/openlayers/tests/Layer/ArcIMS.html123
-rw-r--r--misc/openlayers/tests/Layer/Bing.html200
-rw-r--r--misc/openlayers/tests/Layer/EventPane.html172
-rw-r--r--misc/openlayers/tests/Layer/FixedZoomLevels.html137
-rw-r--r--misc/openlayers/tests/Layer/GeoRSS.html210
-rw-r--r--misc/openlayers/tests/Layer/Google.html369
-rw-r--r--misc/openlayers/tests/Layer/Google/v3.html337
-rw-r--r--misc/openlayers/tests/Layer/Grid.html1593
-rw-r--r--misc/openlayers/tests/Layer/HTTPRequest.html229
-rw-r--r--misc/openlayers/tests/Layer/Image.html164
-rw-r--r--misc/openlayers/tests/Layer/KaMap.html287
-rw-r--r--misc/openlayers/tests/Layer/MapGuide.html177
-rw-r--r--misc/openlayers/tests/Layer/MapServer.html238
-rw-r--r--misc/openlayers/tests/Layer/Markers.html156
-rw-r--r--misc/openlayers/tests/Layer/OSM.html16
-rw-r--r--misc/openlayers/tests/Layer/PointGrid.html232
-rw-r--r--misc/openlayers/tests/Layer/PointTrack.html79
-rw-r--r--misc/openlayers/tests/Layer/SphericalMercator.html126
-rw-r--r--misc/openlayers/tests/Layer/TMS.html262
-rw-r--r--misc/openlayers/tests/Layer/Text.html211
-rw-r--r--misc/openlayers/tests/Layer/TileCache.html203
-rw-r--r--misc/openlayers/tests/Layer/UTFGrid.html131
-rw-r--r--misc/openlayers/tests/Layer/Vector.html879
-rw-r--r--misc/openlayers/tests/Layer/Vector/RootContainer.html63
-rw-r--r--misc/openlayers/tests/Layer/WMS.html583
-rw-r--r--misc/openlayers/tests/Layer/WMTS.html1491
-rw-r--r--misc/openlayers/tests/Layer/WrapDateLine.html188
-rw-r--r--misc/openlayers/tests/Layer/XYZ.html266
-rw-r--r--misc/openlayers/tests/Layer/atom-1.0.xml34
-rw-r--r--misc/openlayers/tests/Layer/data_Layer_Text_textfile.txt3
-rw-r--r--misc/openlayers/tests/Layer/data_Layer_Text_textfile_2.txt3
-rw-r--r--misc/openlayers/tests/Layer/data_Layer_Text_textfile_overflow.txt3
-rw-r--r--misc/openlayers/tests/Layer/georss.txt378
-rw-r--r--misc/openlayers/tests/Map.html2255
-rw-r--r--misc/openlayers/tests/Marker.html163
-rw-r--r--misc/openlayers/tests/Marker/Box.html183
-rw-r--r--misc/openlayers/tests/OLLoader.js26
-rw-r--r--misc/openlayers/tests/OpenLayers1.html18
-rw-r--r--misc/openlayers/tests/OpenLayers2.html19
-rw-r--r--misc/openlayers/tests/OpenLayers3.html19
-rw-r--r--misc/openlayers/tests/OpenLayers4.html18
-rw-r--r--misc/openlayers/tests/OpenLayersJsFiles.html27
-rw-r--r--misc/openlayers/tests/Popup.html219
-rw-r--r--misc/openlayers/tests/Popup/Anchored.html37
-rw-r--r--misc/openlayers/tests/Popup/FramedCloud.html18
-rw-r--r--misc/openlayers/tests/Projection.html87
-rw-r--r--misc/openlayers/tests/Protocol.html63
-rw-r--r--misc/openlayers/tests/Protocol/CSW.html90
-rw-r--r--misc/openlayers/tests/Protocol/HTTP.html842
-rw-r--r--misc/openlayers/tests/Protocol/SOS.html57
-rw-r--r--misc/openlayers/tests/Protocol/Script.html282
-rw-r--r--misc/openlayers/tests/Protocol/WFS.html471
-rw-r--r--misc/openlayers/tests/README.txt16
-rw-r--r--misc/openlayers/tests/Renderer.html96
-rw-r--r--misc/openlayers/tests/Renderer/Canvas.html501
-rw-r--r--misc/openlayers/tests/Renderer/Elements.html651
-rw-r--r--misc/openlayers/tests/Renderer/SVG.html441
-rw-r--r--misc/openlayers/tests/Renderer/VML.html454
-rw-r--r--misc/openlayers/tests/Request.html524
-rw-r--r--misc/openlayers/tests/Request/XMLHttpRequest.html59
-rw-r--r--misc/openlayers/tests/Rule.html123
-rw-r--r--misc/openlayers/tests/SingleFile1.html15
-rw-r--r--misc/openlayers/tests/SingleFile2.html15
-rw-r--r--misc/openlayers/tests/SingleFile3.html15
-rw-r--r--misc/openlayers/tests/Strategy.html94
-rw-r--r--misc/openlayers/tests/Strategy/BBOX.html361
-rw-r--r--misc/openlayers/tests/Strategy/Cluster.html148
-rw-r--r--misc/openlayers/tests/Strategy/Filter.html135
-rw-r--r--misc/openlayers/tests/Strategy/Fixed.html253
-rw-r--r--misc/openlayers/tests/Strategy/Paging.html113
-rw-r--r--misc/openlayers/tests/Strategy/Refresh.html177
-rw-r--r--misc/openlayers/tests/Strategy/Save.html127
-rw-r--r--misc/openlayers/tests/Style.html426
-rw-r--r--misc/openlayers/tests/Style2.html56
-rw-r--r--misc/openlayers/tests/StyleMap.html44
-rw-r--r--misc/openlayers/tests/Symbolizer.html31
-rw-r--r--misc/openlayers/tests/Symbolizer/Line.html42
-rw-r--r--misc/openlayers/tests/Symbolizer/Point.html52
-rw-r--r--misc/openlayers/tests/Symbolizer/Polygon.html44
-rw-r--r--misc/openlayers/tests/Symbolizer/Raster.html32
-rw-r--r--misc/openlayers/tests/Symbolizer/Text.html42
-rw-r--r--misc/openlayers/tests/Test.AnotherWay.baseadditions.js191
-rw-r--r--misc/openlayers/tests/Test.AnotherWay.css243
-rw-r--r--misc/openlayers/tests/Test.AnotherWay.geom_eq.js139
-rw-r--r--misc/openlayers/tests/Test.AnotherWay.js2498
-rw-r--r--misc/openlayers/tests/Test.AnotherWay.xml_eq.js311
-rw-r--r--misc/openlayers/tests/Tile.html130
-rw-r--r--misc/openlayers/tests/Tile/Image.html490
-rw-r--r--misc/openlayers/tests/Tile/Image/IFrame.html183
-rw-r--r--misc/openlayers/tests/Tile/UTFGrid.html306
-rw-r--r--misc/openlayers/tests/TileManager.html137
-rw-r--r--misc/openlayers/tests/Tween.html116
-rw-r--r--misc/openlayers/tests/Util.html1180
-rw-r--r--misc/openlayers/tests/Util/vendorPrefix.html117
-rw-r--r--misc/openlayers/tests/Util_common.js64
-rw-r--r--misc/openlayers/tests/Util_w3c.html35
-rw-r--r--misc/openlayers/tests/WPSClient.html108
-rw-r--r--misc/openlayers/tests/WPSProcess.html188
-rw-r--r--misc/openlayers/tests/atom-1.0.xml34
-rw-r--r--misc/openlayers/tests/auto-tests.html2447
-rw-r--r--misc/openlayers/tests/data/geos_wkt_intersects.js495
-rw-r--r--misc/openlayers/tests/data/osm.js14
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/0.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/2.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/0.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/2.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/0.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/2.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/demo-1.1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/0.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/2.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/0.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/2.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/0.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/1.json1
-rw-r--r--misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/2.json1
-rw-r--r--misc/openlayers/tests/data_Layer_Text_textfile.txt3
-rw-r--r--misc/openlayers/tests/data_Layer_Text_textfile_2.txt3
-rw-r--r--misc/openlayers/tests/data_Layer_Text_textfile_overflow.txt3
-rw-r--r--misc/openlayers/tests/deprecated/Ajax.html28
-rw-r--r--misc/openlayers/tests/deprecated/BaseTypes/Class.html142
-rw-r--r--misc/openlayers/tests/deprecated/BaseTypes/Element.html56
-rw-r--r--misc/openlayers/tests/deprecated/Control/MouseToolbar.html60
-rw-r--r--misc/openlayers/tests/deprecated/Geometry/Rectangle.html77
-rw-r--r--misc/openlayers/tests/deprecated/Layer/GML.html61
-rw-r--r--misc/openlayers/tests/deprecated/Layer/MapServer.html59
-rw-r--r--misc/openlayers/tests/deprecated/Layer/MapServer/Untiled.html158
-rw-r--r--misc/openlayers/tests/deprecated/Layer/WFS.html178
-rw-r--r--misc/openlayers/tests/deprecated/Layer/WMS.html60
-rw-r--r--misc/openlayers/tests/deprecated/Layer/WMS/Post.html89
-rwxr-xr-xmisc/openlayers/tests/deprecated/Layer/Yahoo.html121
-rw-r--r--misc/openlayers/tests/deprecated/Layer/mice.xml156
-rw-r--r--misc/openlayers/tests/deprecated/Layer/owls.xml156
-rw-r--r--misc/openlayers/tests/deprecated/Popup/AnchoredBubble.html61
-rw-r--r--misc/openlayers/tests/deprecated/Protocol/SQL.html24
-rw-r--r--misc/openlayers/tests/deprecated/Protocol/SQL/Gears.html474
-rw-r--r--misc/openlayers/tests/deprecated/Renderer/SVG2.html399
-rw-r--r--misc/openlayers/tests/deprecated/Tile/WFS.html215
-rw-r--r--misc/openlayers/tests/deprecated/Util.html20
-rw-r--r--misc/openlayers/tests/georss.txt378
-rw-r--r--misc/openlayers/tests/grid_inittiles.html30
-rw-r--r--misc/openlayers/tests/index.html6
-rw-r--r--misc/openlayers/tests/list-tests.html260
-rw-r--r--misc/openlayers/tests/manual/ajax.html49
-rw-r--r--misc/openlayers/tests/manual/ajax.txt1
-rw-r--r--misc/openlayers/tests/manual/alloverlays-mixed.html55
-rw-r--r--misc/openlayers/tests/manual/arcims-2117.html103
-rw-r--r--misc/openlayers/tests/manual/arkansas.rss9
-rw-r--r--misc/openlayers/tests/manual/big-georss.html33
-rw-r--r--misc/openlayers/tests/manual/box-quirks.html52
-rw-r--r--misc/openlayers/tests/manual/box-strict.html46
-rw-r--r--misc/openlayers/tests/manual/clip-features-svg.html128
-rw-r--r--misc/openlayers/tests/manual/dateline-sketch.html66
-rw-r--r--misc/openlayers/tests/manual/dateline-smallextent.html61
-rw-r--r--misc/openlayers/tests/manual/draw-feature.html73
-rw-r--r--misc/openlayers/tests/manual/feature-handler.html126
-rw-r--r--misc/openlayers/tests/manual/geodesic.html160
-rw-r--r--misc/openlayers/tests/manual/geojson-geomcoll-reprojection.html74
-rw-r--r--misc/openlayers/tests/manual/google-fullscreen-overlay.html54
-rw-r--r--misc/openlayers/tests/manual/google-panning.html122
-rw-r--r--misc/openlayers/tests/manual/google-resize.html55
-rw-r--r--misc/openlayers/tests/manual/google-tilt.html37
-rw-r--r--misc/openlayers/tests/manual/google-v3-resize.html54
-rw-r--r--misc/openlayers/tests/manual/loadend.html73
-rw-r--r--misc/openlayers/tests/manual/map-events.html38
-rw-r--r--misc/openlayers/tests/manual/memory/Marker-2258.html60
-rw-r--r--misc/openlayers/tests/manual/memory/PanZoom-2323.html41
-rw-r--r--misc/openlayers/tests/manual/memory/RemoveChild-2170.html56
-rw-r--r--misc/openlayers/tests/manual/memory/VML-2170.html49
-rw-r--r--misc/openlayers/tests/manual/multiple-google-layers.html135
-rw-r--r--misc/openlayers/tests/manual/overviewmap-projection.html70
-rw-r--r--misc/openlayers/tests/manual/page-position.html103
-rw-r--r--misc/openlayers/tests/manual/pan-redraw-svg.html58
-rw-r--r--misc/openlayers/tests/manual/popup-keepInMap.html100
-rw-r--r--misc/openlayers/tests/manual/reflow.html59
-rw-r--r--misc/openlayers/tests/manual/renderedDimensions.html113
-rw-r--r--misc/openlayers/tests/manual/select-feature-right-click.html86
-rw-r--r--misc/openlayers/tests/manual/select-feature.html75
-rw-r--r--misc/openlayers/tests/manual/tiles-loading.html122
-rw-r--r--misc/openlayers/tests/manual/tween.html82
-rw-r--r--misc/openlayers/tests/manual/vector-features-performance.html149
-rw-r--r--misc/openlayers/tests/manual/vector-layer-zindex.html143
-rw-r--r--misc/openlayers/tests/mice.xml156
-rw-r--r--misc/openlayers/tests/node.js/mockdom.js104
-rw-r--r--misc/openlayers/tests/node.js/node-tests.cfg12
-rw-r--r--misc/openlayers/tests/node.js/node.js1
-rw-r--r--misc/openlayers/tests/node.js/run-test.js26
-rwxr-xr-xmisc/openlayers/tests/node.js/run.sh10
-rw-r--r--misc/openlayers/tests/owls.xml156
-rw-r--r--misc/openlayers/tests/run-tests.html155
-rw-r--r--misc/openlayers/tests/selenium/remotecontrol/config.cfg48
-rw-r--r--misc/openlayers/tests/selenium/remotecontrol/selenium.py1846
-rw-r--r--misc/openlayers/tests/selenium/remotecontrol/setup.txt8
-rw-r--r--misc/openlayers/tests/selenium/remotecontrol/test_ol.py95
-rw-r--r--misc/openlayers/tests/speed/geometry.html43
-rw-r--r--misc/openlayers/tests/speed/string_format.html29
-rw-r--r--misc/openlayers/tests/speed/vector-renderers.html25
-rw-r--r--misc/openlayers/tests/speed/vector-renderers.js70
-rw-r--r--misc/openlayers/tests/speed/wmc_speed.html30
-rw-r--r--misc/openlayers/tests/speed/wmscaps.html52
-rw-r--r--misc/openlayers/tests/speed/wmscaps.js4956
-rw-r--r--misc/openlayers/tests/speed/wmscaps.xml4954
-rw-r--r--misc/openlayers/tests/throws.js82
-rw-r--r--misc/openlayers/theme/default/google.css9
-rw-r--r--misc/openlayers/theme/default/google.tidy.css1
-rw-r--r--misc/openlayers/theme/default/ie6-style.css10
-rw-r--r--misc/openlayers/theme/default/ie6-style.tidy.css1
-rw-r--r--misc/openlayers/theme/default/img/add_point_off.pngbin0 -> 1614 bytes
-rw-r--r--misc/openlayers/theme/default/img/add_point_on.pngbin0 -> 1464 bytes
-rw-r--r--misc/openlayers/theme/default/img/blank.gifbin0 -> 42 bytes
-rw-r--r--misc/openlayers/theme/default/img/close.gifbin0 -> 1078 bytes
-rw-r--r--misc/openlayers/theme/default/img/drag-rectangle-off.pngbin0 -> 1024 bytes
-rw-r--r--misc/openlayers/theme/default/img/drag-rectangle-on.pngbin0 -> 1041 bytes
-rw-r--r--misc/openlayers/theme/default/img/draw_line_off.pngbin0 -> 1565 bytes
-rw-r--r--misc/openlayers/theme/default/img/draw_line_on.pngbin0 -> 1396 bytes
-rw-r--r--misc/openlayers/theme/default/img/draw_point_off.pngbin0 -> 1610 bytes
-rw-r--r--misc/openlayers/theme/default/img/draw_point_on.pngbin0 -> 1458 bytes
-rw-r--r--misc/openlayers/theme/default/img/draw_polygon_off.pngbin0 -> 1544 bytes
-rw-r--r--misc/openlayers/theme/default/img/draw_polygon_on.pngbin0 -> 1405 bytes
-rw-r--r--misc/openlayers/theme/default/img/editing_tool_bar.pngbin0 -> 2222 bytes
-rw-r--r--misc/openlayers/theme/default/img/move_feature_off.pngbin0 -> 1541 bytes
-rw-r--r--misc/openlayers/theme/default/img/move_feature_on.pngbin0 -> 1377 bytes
-rw-r--r--misc/openlayers/theme/default/img/navigation_history.pngbin0 -> 6628 bytes
-rw-r--r--misc/openlayers/theme/default/img/overview_replacement.gifbin0 -> 79 bytes
-rw-r--r--misc/openlayers/theme/default/img/pan-panel-NOALPHA.pngbin0 -> 564 bytes
-rw-r--r--misc/openlayers/theme/default/img/pan-panel.pngbin0 -> 814 bytes
-rw-r--r--misc/openlayers/theme/default/img/pan_off.pngbin0 -> 1696 bytes
-rw-r--r--misc/openlayers/theme/default/img/pan_on.pngbin0 -> 1566 bytes
-rw-r--r--misc/openlayers/theme/default/img/panning-hand-off.pngbin0 -> 3511 bytes
-rw-r--r--misc/openlayers/theme/default/img/panning-hand-on.pngbin0 -> 3565 bytes
-rw-r--r--misc/openlayers/theme/default/img/remove_point_off.pngbin0 -> 1612 bytes
-rw-r--r--misc/openlayers/theme/default/img/remove_point_on.pngbin0 -> 1461 bytes
-rw-r--r--misc/openlayers/theme/default/img/ruler.pngbin0 -> 1211 bytes
-rw-r--r--misc/openlayers/theme/default/img/save_features_off.pngbin0 -> 354 bytes
-rw-r--r--misc/openlayers/theme/default/img/save_features_on.pngbin0 -> 361 bytes
-rw-r--r--misc/openlayers/theme/default/img/view_next_off.pngbin0 -> 1499 bytes
-rw-r--r--misc/openlayers/theme/default/img/view_next_on.pngbin0 -> 1686 bytes
-rw-r--r--misc/openlayers/theme/default/img/view_previous_off.pngbin0 -> 1476 bytes
-rw-r--r--misc/openlayers/theme/default/img/view_previous_on.pngbin0 -> 1592 bytes
-rw-r--r--misc/openlayers/theme/default/img/zoom-panel-NOALPHA.pngbin0 -> 1173 bytes
-rw-r--r--misc/openlayers/theme/default/img/zoom-panel.pngbin0 -> 1285 bytes
-rw-r--r--misc/openlayers/theme/default/style.css516
-rw-r--r--misc/openlayers/theme/default/style.mobile.css70
-rw-r--r--misc/openlayers/theme/default/style.mobile.tidy.css1
-rw-r--r--misc/openlayers/theme/default/style.tidy.css1
-rw-r--r--misc/openlayers/tools/BeautifulSoup.py1767
-rw-r--r--misc/openlayers/tools/README.txt14
-rw-r--r--misc/openlayers/tools/closure_library_jscompiler.py71
-rw-r--r--misc/openlayers/tools/closure_ws.py28
-rwxr-xr-xmisc/openlayers/tools/exampleparser.py251
-rw-r--r--misc/openlayers/tools/jsmin.c272
-rwxr-xr-xmisc/openlayers/tools/jsmin.py216
-rwxr-xr-xmisc/openlayers/tools/mergejs.py287
-rw-r--r--misc/openlayers/tools/minimize.py47
-rw-r--r--misc/openlayers/tools/oldot.py43
-rwxr-xr-xmisc/openlayers/tools/release.sh71
-rwxr-xr-xmisc/openlayers/tools/shrinksafe.py54
-rw-r--r--misc/openlayers/tools/toposort.py35
-rw-r--r--misc/openlayers/tools/uglify_js.py35
-rwxr-xr-xmisc/openlayers/tools/update_dev_dir.sh103
1207 files changed, 401671 insertions, 0 deletions
diff --git a/misc/openlayers/.gitignore b/misc/openlayers/.gitignore
new file mode 100644
index 0000000..42b6026
--- /dev/null
+++ b/misc/openlayers/.gitignore
@@ -0,0 +1,7 @@
+/build/OpenLayers.js
+/tools/closure-compiler.jar
+/tools/*.pyc
+/apidoc_config/Data/
+/doc/apidocs/
+/examples/example-list.js
+/examples/example-list.xml
diff --git a/misc/openlayers/OpenLayers.debug.js b/misc/openlayers/OpenLayers.debug.js
new file mode 100644
index 0000000..3a5882f
--- /dev/null
+++ b/misc/openlayers/OpenLayers.debug.js
@@ -0,0 +1,85622 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* ======================================================================
+ OpenLayers/SingleFile.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+var OpenLayers = {
+ /**
+ * Constant: VERSION_NUMBER
+ */
+ VERSION_NUMBER: "Release 2.13.1",
+
+ /**
+ * Constant: singleFile
+ * TODO: remove this in 3.0 when we stop supporting build profiles that
+ * include OpenLayers.js
+ */
+ singleFile: true,
+
+ /**
+ * Method: _getScriptLocation
+ * Return the path to this script. This is also implemented in
+ * OpenLayers.js
+ *
+ * Returns:
+ * {String} Path to this script
+ */
+ _getScriptLocation: (function() {
+ var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
+ s = document.getElementsByTagName('script'),
+ src, m, l = "";
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+ return (function() { return l; });
+ })(),
+
+ /**
+ * Property: ImgPath
+ * {String} Set this to the path where control images are stored, a path
+ * given here must end with a slash. If set to '' (which is the default)
+ * OpenLayers will use its script location + "img/".
+ *
+ * You will need to set this property when you have a singlefile build of
+ * OpenLayers that either is not named "OpenLayers.js" or if you move
+ * the file in a way such that the image directory cannot be derived from
+ * the script location.
+ *
+ * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+ * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+ * way of including OpenLayers in your HTML would be:
+ *
+ * (code)
+ * <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+ * <script type="text/javascript">
+ * // tell OpenLayers where the control images are
+ * // remember the trailing slash
+ * OpenLayers.ImgPath = "/resources/external/images/ol/";
+ * </script>
+ * (end code)
+ *
+ * Please remember that when your OpenLayers script is not named
+ * "OpenLayers.js" you will have to make sure that the default theme is
+ * loaded into the page by including an appropriate <link>-tag,
+ * e.g.:
+ *
+ * (code)
+ * <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
+ * (end code)
+ */
+ ImgPath : ''
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Class.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Constructor: OpenLayers.Class
+ * Base class used to construct all other classes. Includes support for
+ * multiple inheritance.
+ *
+ * This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old
+ * syntax for creating classes and dealing with inheritance
+ * will be removed.
+ *
+ * To create a new OpenLayers-style class, use the following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(prototype);
+ * (end)
+ *
+ * To create a new OpenLayers-style class with multiple inheritance, use the
+ * following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(Class1, Class2, prototype);
+ * (end)
+ *
+ * Note that instanceof reflection will only reveal Class1 as superclass.
+ *
+ */
+OpenLayers.Class = function() {
+ var len = arguments.length;
+ var P = arguments[0];
+ var F = arguments[len-1];
+
+ var C = typeof F.initialize == "function" ?
+ F.initialize :
+ function(){ P.prototype.initialize.apply(this, arguments); };
+
+ if (len > 1) {
+ var newArgs = [C, P].concat(
+ Array.prototype.slice.call(arguments).slice(1, len-1), F);
+ OpenLayers.inherit.apply(null, newArgs);
+ } else {
+ C.prototype = F;
+ }
+ return C;
+};
+
+/**
+ * Function: OpenLayers.inherit
+ *
+ * Parameters:
+ * C - {Object} the class that inherits
+ * P - {Object} the superclass to inherit from
+ *
+ * In addition to the mandatory C and P parameters, an arbitrary number of
+ * objects can be passed, which will extend C.
+ */
+OpenLayers.inherit = function(C, P) {
+ var F = function() {};
+ F.prototype = P.prototype;
+ C.prototype = new F;
+ var i, l, o;
+ for(i=2, l=arguments.length; i<l; i++) {
+ o = arguments[i];
+ if(typeof o === "function") {
+ o = o.prototype;
+ }
+ OpenLayers.Util.extend(C.prototype, o);
+ }
+};
+
+/**
+ * APIFunction: extend
+ * Copy all properties of a source object to a destination object. Modifies
+ * the passed in destination object. Any properties on the source object
+ * that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+OpenLayers.Util.extend = function(destination, source) {
+ destination = destination || {};
+ if (source) {
+ for (var property in source) {
+ var value = source[property];
+ if (value !== undefined) {
+ destination[property] = value;
+ }
+ }
+
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object
+ * is an instance of window.Event.
+ */
+
+ var sourceIsEvt = typeof window.Event == "function"
+ && source instanceof window.Event;
+
+ if (!sourceIsEvt
+ && source.hasOwnProperty && source.hasOwnProperty("toString")) {
+ destination.toString = source.toString;
+ }
+ }
+ return destination;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Header: OpenLayers Base Types
+ * OpenLayers custom string, number and function functions are described here.
+ */
+
+/**
+ * Namespace: OpenLayers.String
+ * Contains convenience functions for string manipulation.
+ */
+OpenLayers.String = {
+
+ /**
+ * APIFunction: startsWith
+ * Test whether a string starts with another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string starts with the second.
+ */
+ startsWith: function(str, sub) {
+ return (str.indexOf(sub) == 0);
+ },
+
+ /**
+ * APIFunction: contains
+ * Test whether a string contains another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string contains the second.
+ */
+ contains: function(str, sub) {
+ return (str.indexOf(sub) != -1);
+ },
+
+ /**
+ * APIFunction: trim
+ * Removes leading and trailing whitespace characters from a string.
+ *
+ * Parameters:
+ * str - {String} The (potentially) space padded string. This string is not
+ * modified.
+ *
+ * Returns:
+ * {String} A trimmed version of the string with all leading and
+ * trailing spaces removed.
+ */
+ trim: function(str) {
+ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ },
+
+ /**
+ * APIFunction: camelize
+ * Camel-case a hyphenated string.
+ * Ex. "chicken-head" becomes "chickenHead", and
+ * "-chicken-head" becomes "ChickenHead".
+ *
+ * Parameters:
+ * str - {String} The string to be camelized. The original is not modified.
+ *
+ * Returns:
+ * {String} The string, camelized
+ */
+ camelize: function(str) {
+ var oStringList = str.split('-');
+ var camelizedString = oStringList[0];
+ for (var i=1, len=oStringList.length; i<len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+ return camelizedString;
+ },
+
+ /**
+ * APIFunction: format
+ * Given a string with tokens in the form ${token}, return a string
+ * with tokens replaced with properties from the given context
+ * object. Represent a literal "${" by doubling it, e.g. "${${".
+ *
+ * Parameters:
+ * template - {String} A string with tokens to be replaced. A template
+ * has the form "literal ${token}" where the token will be replaced
+ * by the value of context["token"].
+ * context - {Object} An optional object with properties corresponding
+ * to the tokens in the format string. If no context is sent, the
+ * window object will be used.
+ * args - {Array} Optional arguments to pass to any functions found in
+ * the context. If a context property is a function, the token
+ * will be replaced by the return from the function called with
+ * these arguments.
+ *
+ * Returns:
+ * {String} A string with tokens replaced from the context object.
+ */
+ format: function(template, context, args) {
+ if(!context) {
+ context = window;
+ }
+
+ // Example matching:
+ // str = ${foo.bar}
+ // match = foo.bar
+ var replacer = function(str, match) {
+ var replacement;
+
+ // Loop through all subs. Example: ${a.b.c}
+ // 0 -> replacement = context[a];
+ // 1 -> replacement = context[a][b];
+ // 2 -> replacement = context[a][b][c];
+ var subs = match.split(/\.+/);
+ for (var i=0; i< subs.length; i++) {
+ if (i == 0) {
+ replacement = context;
+ }
+ if (replacement === undefined) {
+ break;
+ }
+ replacement = replacement[subs[i]];
+ }
+
+ if(typeof replacement == "function") {
+ replacement = args ?
+ replacement.apply(null, args) :
+ replacement();
+ }
+
+ // If replacement is undefined, return the string 'undefined'.
+ // This is a workaround for a bugs in browsers not properly
+ // dealing with non-participating groups in regular expressions:
+ // http://blog.stevenlevithan.com/archives/npcg-javascript
+ if (typeof replacement == 'undefined') {
+ return 'undefined';
+ } else {
+ return replacement;
+ }
+ };
+
+ return template.replace(OpenLayers.String.tokenRegEx, replacer);
+ },
+
+ /**
+ * Property: tokenRegEx
+ * Used to find tokens in a string.
+ * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
+ */
+ tokenRegEx: /\$\{([\w.]+?)\}/g,
+
+ /**
+ * Property: numberRegEx
+ * Used to test strings as numbers.
+ */
+ numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+
+ /**
+ * APIFunction: isNumeric
+ * Determine whether a string contains only a numeric value.
+ *
+ * Examples:
+ * (code)
+ * OpenLayers.String.isNumeric("6.02e23") // true
+ * OpenLayers.String.isNumeric("12 dozen") // false
+ * OpenLayers.String.isNumeric("4") // true
+ * OpenLayers.String.isNumeric(" 4 ") // false
+ * (end)
+ *
+ * Returns:
+ * {Boolean} String contains only a number.
+ */
+ isNumeric: function(value) {
+ return OpenLayers.String.numberRegEx.test(value);
+ },
+
+ /**
+ * APIFunction: numericIf
+ * Converts a string that appears to be a numeric value into a number.
+ *
+ * Parameters:
+ * value - {String}
+ * trimWhitespace - {Boolean}
+ *
+ * Returns:
+ * {Number|String} a Number if the passed value is a number, a String
+ * otherwise.
+ */
+ numericIf: function(value, trimWhitespace) {
+ var originalValue = value;
+ if (trimWhitespace === true && value != null && value.replace) {
+ value = value.replace(/^\s*|\s*$/g, "");
+ }
+ return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
+ }
+
+};
+
+/**
+ * Namespace: OpenLayers.Number
+ * Contains convenience functions for manipulating numbers.
+ */
+OpenLayers.Number = {
+
+ /**
+ * Property: decimalSeparator
+ * Decimal separator to use when formatting numbers.
+ */
+ decimalSeparator: ".",
+
+ /**
+ * Property: thousandsSeparator
+ * Thousands separator to use when formatting numbers.
+ */
+ thousandsSeparator: ",",
+
+ /**
+ * APIFunction: limitSigDigs
+ * Limit the number of significant digits on a float.
+ *
+ * Parameters:
+ * num - {Float}
+ * sig - {Integer}
+ *
+ * Returns:
+ * {Float} The number, rounded to the specified number of significant
+ * digits.
+ */
+ limitSigDigs: function(num, sig) {
+ var fig = 0;
+ if (sig > 0) {
+ fig = parseFloat(num.toPrecision(sig));
+ }
+ return fig;
+ },
+
+ /**
+ * APIFunction: format
+ * Formats a number for output.
+ *
+ * Parameters:
+ * num - {Float}
+ * dec - {Integer} Number of decimal places to round to.
+ * Defaults to 0. Set to null to leave decimal places unchanged.
+ * tsep - {String} Thousands separator.
+ * Default is ",".
+ * dsep - {String} Decimal separator.
+ * Default is ".".
+ *
+ * Returns:
+ * {String} A string representing the formatted number.
+ */
+ format: function(num, dec, tsep, dsep) {
+ dec = (typeof dec != "undefined") ? dec : 0;
+ tsep = (typeof tsep != "undefined") ? tsep :
+ OpenLayers.Number.thousandsSeparator;
+ dsep = (typeof dsep != "undefined") ? dsep :
+ OpenLayers.Number.decimalSeparator;
+
+ if (dec != null) {
+ num = parseFloat(num.toFixed(dec));
+ }
+
+ var parts = num.toString().split(".");
+ if (parts.length == 1 && dec == null) {
+ // integer where we do not want to touch the decimals
+ dec = 0;
+ }
+
+ var integer = parts[0];
+ if (tsep) {
+ var thousands = /(-?[0-9]+)([0-9]{3})/;
+ while(thousands.test(integer)) {
+ integer = integer.replace(thousands, "$1" + tsep + "$2");
+ }
+ }
+
+ var str;
+ if (dec == 0) {
+ str = integer;
+ } else {
+ var rem = parts.length > 1 ? parts[1] : "0";
+ if (dec != null) {
+ rem = rem + new Array(dec - rem.length + 1).join("0");
+ }
+ str = integer + dsep + rem;
+ }
+ return str;
+ },
+
+ /**
+ * Method: zeroPad
+ * Create a zero padded string optionally with a radix for casting numbers.
+ *
+ * Parameters:
+ * num - {Number} The number to be zero padded.
+ * len - {Number} The length of the string to be returned.
+ * radix - {Number} An integer between 2 and 36 specifying the base to use
+ * for representing numeric values.
+ */
+ zeroPad: function(num, len, radix) {
+ var str = num.toString(radix || 10);
+ while (str.length < len) {
+ str = "0" + str;
+ }
+ return str;
+ }
+};
+
+/**
+ * Namespace: OpenLayers.Function
+ * Contains convenience functions for function manipulation.
+ */
+OpenLayers.Function = {
+ /**
+ * APIFunction: bind
+ * Bind a function to an object. Method to easily create closures with
+ * 'this' altered.
+ *
+ * Parameters:
+ * func - {Function} Input function.
+ * object - {Object} The object to bind to the input function (as this).
+ *
+ * Returns:
+ * {Function} A closure with 'this' set to the passed in object.
+ */
+ bind: function(func, object) {
+ // create a reference to all arguments past the second one
+ var args = Array.prototype.slice.apply(arguments, [2]);
+ return function() {
+ // Push on any additional arguments from the actual function call.
+ // These will come after those sent to the bind call.
+ var newArgs = args.concat(
+ Array.prototype.slice.apply(arguments, [0])
+ );
+ return func.apply(object, newArgs);
+ };
+ },
+
+ /**
+ * APIFunction: bindAsEventListener
+ * Bind a function to an object, and configure it to receive the event
+ * object as first parameter when called.
+ *
+ * Parameters:
+ * func - {Function} Input function to serve as an event listener.
+ * object - {Object} A reference to this.
+ *
+ * Returns:
+ * {Function}
+ */
+ bindAsEventListener: function(func, object) {
+ return function(event) {
+ return func.call(object, event || window.event);
+ };
+ },
+
+ /**
+ * APIFunction: False
+ * A simple function to that just does "return false". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.False;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ False : function() {
+ return false;
+ },
+
+ /**
+ * APIFunction: True
+ * A simple function to that just does "return true". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.True;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ True : function() {
+ return true;
+ },
+
+ /**
+ * APIFunction: Void
+ * A reusable function that returns ``undefined``.
+ *
+ * Returns:
+ * {undefined}
+ */
+ Void: function() {}
+
+};
+
+/**
+ * Namespace: OpenLayers.Array
+ * Contains convenience functions for array manipulation.
+ */
+OpenLayers.Array = {
+
+ /**
+ * APIMethod: filter
+ * Filter an array. Provides the functionality of the
+ * Array.prototype.filter extension to the ECMA-262 standard. Where
+ * available, Array.prototype.filter will be used.
+ *
+ * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
+ *
+ * Parameters:
+ * array - {Array} The array to be filtered. This array is not mutated.
+ * Elements added to this array by the callback will not be visited.
+ * callback - {Function} A function that is called for each element in
+ * the array. If this function returns true, the element will be
+ * included in the return. The function will be called with three
+ * arguments: the element in the array, the index of that element, and
+ * the array itself. If the optional caller parameter is specified
+ * the callback will be called with this set to caller.
+ * caller - {Object} Optional object to be set as this when the callback
+ * is called.
+ *
+ * Returns:
+ * {Array} An array of elements from the passed in array for which the
+ * callback returns true.
+ */
+ filter: function(array, callback, caller) {
+ var selected = [];
+ if (Array.prototype.filter) {
+ selected = array.filter(callback, caller);
+ } else {
+ var len = array.length;
+ if (typeof callback != "function") {
+ throw new TypeError();
+ }
+ for(var i=0; i<len; i++) {
+ if (i in array) {
+ var val = array[i];
+ if (callback.call(caller, val, i, array)) {
+ selected.push(val);
+ }
+ }
+ }
+ }
+ return selected;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Bounds.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Bounds
+ * Instances of this class represent bounding boxes. Data stored as left,
+ * bottom, right, top floats. All values are initialized to null, however,
+ * you should make sure you set them before using the bounds for anything.
+ *
+ * Possible use case:
+ * (code)
+ * bounds = new OpenLayers.Bounds();
+ * bounds.extend(new OpenLayers.LonLat(4,5));
+ * bounds.extend(new OpenLayers.LonLat(5,6));
+ * bounds.toBBOX(); // returns 4,5,5,6
+ * (end)
+ */
+OpenLayers.Bounds = OpenLayers.Class({
+
+ /**
+ * Property: left
+ * {Number} Minimum horizontal coordinate.
+ */
+ left: null,
+
+ /**
+ * Property: bottom
+ * {Number} Minimum vertical coordinate.
+ */
+ bottom: null,
+
+ /**
+ * Property: right
+ * {Number} Maximum horizontal coordinate.
+ */
+ right: null,
+
+ /**
+ * Property: top
+ * {Number} Maximum vertical coordinate.
+ */
+ top: null,
+
+ /**
+ * Property: centerLonLat
+ * {<OpenLayers.LonLat>} A cached center location. This should not be
+ * accessed directly. Use <getCenterLonLat> instead.
+ */
+ centerLonLat: null,
+
+ /**
+ * Constructor: OpenLayers.Bounds
+ * Construct a new bounds object. Coordinates can either be passed as four
+ * arguments, or as a single argument.
+ *
+ * Parameters (four arguments):
+ * left - {Number} The left bounds of the box. Note that for width
+ * calculations, this is assumed to be less than the right value.
+ * bottom - {Number} The bottom bounds of the box. Note that for height
+ * calculations, this is assumed to be less than the top value.
+ * right - {Number} The right bounds.
+ * top - {Number} The top bounds.
+ *
+ * Parameters (single argument):
+ * bounds - {Array(Number)} [left, bottom, right, top]
+ */
+ initialize: function(left, bottom, right, top) {
+ if (OpenLayers.Util.isArray(left)) {
+ top = left[3];
+ right = left[2];
+ bottom = left[1];
+ left = left[0];
+ }
+ if (left != null) {
+ this.left = OpenLayers.Util.toFloat(left);
+ }
+ if (bottom != null) {
+ this.bottom = OpenLayers.Util.toFloat(bottom);
+ }
+ if (right != null) {
+ this.right = OpenLayers.Util.toFloat(right);
+ }
+ if (top != null) {
+ this.top = OpenLayers.Util.toFloat(top);
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a cloned instance of this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A fresh copy of the bounds
+ */
+ clone:function() {
+ return new OpenLayers.Bounds(this.left, this.bottom,
+ this.right, this.top);
+ },
+
+ /**
+ * Method: equals
+ * Test a two bounds for equivalence.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object has the same left,
+ * right, top, bottom components as this. Note that if bounds
+ * passed in is null, returns false.
+ */
+ equals:function(bounds) {
+ var equals = false;
+ if (bounds != null) {
+ equals = ((this.left == bounds.left) &&
+ (this.right == bounds.right) &&
+ (this.top == bounds.top) &&
+ (this.bottom == bounds.bottom));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: toString
+ * Returns a string representation of the bounds object.
+ *
+ * Returns:
+ * {String} String representation of bounds object.
+ */
+ toString:function() {
+ return [this.left, this.bottom, this.right, this.top].join(",");
+ },
+
+ /**
+ * APIMethod: toArray
+ * Returns an array representation of the bounds object.
+ *
+ * Returns an array of left, bottom, right, top properties, or -- when the
+ * optional parameter is true -- an array of the bottom, left, top,
+ * right properties.
+ *
+ * Parameters:
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {Array} array of left, bottom, right, top
+ */
+ toArray: function(reverseAxisOrder) {
+ if (reverseAxisOrder === true) {
+ return [this.bottom, this.left, this.top, this.right];
+ } else {
+ return [this.left, this.bottom, this.right, this.top];
+ }
+ },
+
+ /**
+ * APIMethod: toBBOX
+ * Returns a boundingbox-string representation of the bounds object.
+ *
+ * Parameters:
+ * decimal - {Integer} How many significant digits in the bbox coords?
+ * Default is 6
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {String} Simple String representation of bounds object.
+ * (e.g. "5,42,10,45")
+ */
+ toBBOX:function(decimal, reverseAxisOrder) {
+ if (decimal== null) {
+ decimal = 6;
+ }
+ var mult = Math.pow(10, decimal);
+ var xmin = Math.round(this.left * mult) / mult;
+ var ymin = Math.round(this.bottom * mult) / mult;
+ var xmax = Math.round(this.right * mult) / mult;
+ var ymax = Math.round(this.top * mult) / mult;
+ if (reverseAxisOrder === true) {
+ return ymin + "," + xmin + "," + ymax + "," + xmax;
+ } else {
+ return xmin + "," + ymin + "," + xmax + "," + ymax;
+ }
+ },
+
+ /**
+ * APIMethod: toGeometry
+ * Create a new polygon geometry based on this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
+ * of this bounds.
+ */
+ toGeometry: function() {
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(this.left, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.top),
+ new OpenLayers.Geometry.Point(this.left, this.top)
+ ])
+ ]);
+ },
+
+ /**
+ * APIMethod: getWidth
+ * Returns the width of the bounds.
+ *
+ * Returns:
+ * {Float} The width of the bounds (right minus left).
+ */
+ getWidth:function() {
+ return (this.right - this.left);
+ },
+
+ /**
+ * APIMethod: getHeight
+ * Returns the height of the bounds.
+ *
+ * Returns:
+ * {Float} The height of the bounds (top minus bottom).
+ */
+ getHeight:function() {
+ return (this.top - this.bottom);
+ },
+
+ /**
+ * APIMethod: getSize
+ * Returns an <OpenLayers.Size> object of the bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size of the bounds.
+ */
+ getSize:function() {
+ return new OpenLayers.Size(this.getWidth(), this.getHeight());
+ },
+
+ /**
+ * APIMethod: getCenterPixel
+ * Returns the <OpenLayers.Pixel> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
+ */
+ getCenterPixel:function() {
+ return new OpenLayers.Pixel( (this.left + this.right) / 2,
+ (this.bottom + this.top) / 2);
+ },
+
+ /**
+ * APIMethod: getCenterLonLat
+ * Returns the <OpenLayers.LonLat> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The center of the bounds in map space.
+ */
+ getCenterLonLat:function() {
+ if(!this.centerLonLat) {
+ this.centerLonLat = new OpenLayers.LonLat(
+ (this.left + this.right) / 2, (this.bottom + this.top) / 2
+ );
+ }
+ return this.centerLonLat;
+ },
+
+ /**
+ * APIMethod: scale
+ * Scales the bounds around a pixel or lonlat. Note that the new
+ * bounds may return non-integer properties, even if a pixel
+ * is passed.
+ *
+ * Parameters:
+ * ratio - {Float}
+ * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+ * Default is center.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
+ * from origin.
+ */
+ scale: function(ratio, origin){
+ if(origin == null){
+ origin = this.getCenterLonLat();
+ }
+
+ var origx,origy;
+
+ // get origin coordinates
+ if(origin.CLASS_NAME == "OpenLayers.LonLat"){
+ origx = origin.lon;
+ origy = origin.lat;
+ } else {
+ origx = origin.x;
+ origy = origin.y;
+ }
+
+ var left = (this.left - origx) * ratio + origx;
+ var bottom = (this.bottom - origy) * ratio + origy;
+ var right = (this.right - origx) * ratio + origx;
+ var top = (this.top - origy) * ratio + origy;
+
+ return new OpenLayers.Bounds(left, bottom, right, top);
+ },
+
+ /**
+ * APIMethod: add
+ * Shifts the coordinates of the bound by the given horizontal and vertical
+ * deltas.
+ *
+ * (start code)
+ * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+ * bounds.toString();
+ * // => "0,0,10,10"
+ *
+ * bounds.add(-1.5, 4).toString();
+ * // => "-1.5,4,8.5,14"
+ * (end)
+ *
+ * This method will throw a TypeError if it is passed null as an argument.
+ *
+ * Parameters:
+ * x - {Float} horizontal delta
+ * y - {Float} vertical delta
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
+ * this, but shifted by the passed-in x and y values.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Bounds.add cannot receive null values');
+ }
+ return new OpenLayers.Bounds(this.left + x, this.bottom + y,
+ this.right + x, this.top + y);
+ },
+
+ /**
+ * APIMethod: extend
+ * Extend the bounds to include the <OpenLayers.LonLat>,
+ * <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
+ *
+ * Please note that this function assumes that left < right and
+ * bottom < top.
+ *
+ * Parameters:
+ * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
+ * <OpenLayers.Bounds>} The object to be included in the new bounds
+ * object.
+ */
+ extend:function(object) {
+ if (object) {
+ switch(object.CLASS_NAME) {
+ case "OpenLayers.LonLat":
+ this.extendXY(object.lon, object.lat);
+ break;
+ case "OpenLayers.Geometry.Point":
+ this.extendXY(object.x, object.y);
+ break;
+
+ case "OpenLayers.Bounds":
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ( (this.left == null) || (object.left < this.left)) {
+ this.left = object.left;
+ }
+ if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
+ this.bottom = object.bottom;
+ }
+ if ( (this.right == null) || (object.right > this.right) ) {
+ this.right = object.right;
+ }
+ if ( (this.top == null) || (object.top > this.top) ) {
+ this.top = object.top;
+ }
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: extendXY
+ * Extend the bounds to include the XY coordinate specified.
+ *
+ * Parameters:
+ * x - {number} The X part of the the coordinate.
+ * y - {number} The Y part of the the coordinate.
+ */
+ extendXY:function(x, y) {
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ((this.left == null) || (x < this.left)) {
+ this.left = x;
+ }
+ if ((this.bottom == null) || (y < this.bottom)) {
+ this.bottom = y;
+ }
+ if ((this.right == null) || (x > this.right)) {
+ this.right = x;
+ }
+ if ((this.top == null) || (y > this.top)) {
+ this.top = y;
+ }
+ },
+
+ /**
+ * APIMethod: containsLonLat
+ * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * options - {Object} Optional parameters
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Whether or not to include the border.
+ * Default is true.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
+ * ll will be considered as contained if it exceeds the world bounds,
+ * but can be wrapped around the dateline so it is contained by this
+ * bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in lonlat is within this bounds.
+ */
+ containsLonLat: function(ll, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ var contains = this.contains(ll.lon, ll.lat, options.inclusive),
+ worldBounds = options.worldBounds;
+ if (worldBounds && !contains) {
+ var worldWidth = worldBounds.getWidth();
+ var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
+ var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
+ contains = this.containsLonLat({
+ lon: ll.lon - worldsAway * worldWidth,
+ lat: ll.lat
+ }, {inclusive: options.inclusive});
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: containsPixel
+ * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in pixel is within this bounds.
+ */
+ containsPixel:function(px, inclusive) {
+ return this.contains(px.x, px.y, inclusive);
+ },
+
+ /**
+ * APIMethod: contains
+ * Returns whether the bounds object contains the given x and y.
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the passed-in coordinates are within this
+ * bounds.
+ */
+ contains:function(x, y, inclusive) {
+ //set default
+ if (inclusive == null) {
+ inclusive = true;
+ }
+
+ if (x == null || y == null) {
+ return false;
+ }
+
+ x = OpenLayers.Util.toFloat(x);
+ y = OpenLayers.Util.toFloat(y);
+
+ var contains = false;
+ if (inclusive) {
+ contains = ((x >= this.left) && (x <= this.right) &&
+ (y >= this.bottom) && (y <= this.top));
+ } else {
+ contains = ((x > this.left) && (x < this.right) &&
+ (y > this.bottom) && (y < this.top));
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: intersectsBounds
+ * Determine whether the target bounds intersects this bounds. Bounds are
+ * considered intersecting if any of their edges intersect or if one
+ * bounds contains the other.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * options - {Object} Optional parameters.
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Treat coincident borders as intersecting. Default
+ * is true. If false, bounds that do not overlap but only touch at the
+ * border will not be considered as intersecting.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
+ * bounds will be considered as intersecting if they intersect when
+ * shifted to within the world bounds. This applies only to bounds that
+ * cross or are completely outside the world bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object intersects this bounds.
+ */
+ intersectsBounds:function(bounds, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ if (options.worldBounds) {
+ var self = this.wrapDateLine(options.worldBounds);
+ bounds = bounds.wrapDateLine(options.worldBounds);
+ } else {
+ self = this;
+ }
+ if (options.inclusive == null) {
+ options.inclusive = true;
+ }
+ var intersects = false;
+ var mightTouch = (
+ self.left == bounds.right ||
+ self.right == bounds.left ||
+ self.top == bounds.bottom ||
+ self.bottom == bounds.top
+ );
+
+ // if the two bounds only touch at an edge, and inclusive is false,
+ // then the bounds don't *really* intersect.
+ if (options.inclusive || !mightTouch) {
+ // otherwise, if one of the boundaries even partially contains another,
+ // inclusive of the edges, then they do intersect.
+ var inBottom = (
+ ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
+ ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
+ );
+ var inTop = (
+ ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
+ ((self.top > bounds.bottom) && (self.top < bounds.top))
+ );
+ var inLeft = (
+ ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
+ ((self.left >= bounds.left) && (self.left <= bounds.right))
+ );
+ var inRight = (
+ ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
+ ((self.right >= bounds.left) && (self.right <= bounds.right))
+ );
+ intersects = ((inBottom || inTop) && (inLeft || inRight));
+ }
+ // document me
+ if (options.worldBounds && !intersects) {
+ var world = options.worldBounds;
+ var width = world.getWidth();
+ var selfCrosses = !world.containsBounds(self);
+ var boundsCrosses = !world.containsBounds(bounds);
+ if (selfCrosses && !boundsCrosses) {
+ bounds = bounds.add(-width, 0);
+ intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
+ } else if (boundsCrosses && !selfCrosses) {
+ self = self.add(-width, 0);
+ intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});
+ }
+ }
+ return intersects;
+ },
+
+ /**
+ * APIMethod: containsBounds
+ * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
+ *
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * partial - {Boolean} If any of the target corners is within this bounds
+ * consider the bounds contained. Default is false. If false, the
+ * entire target bounds must be contained within this bounds.
+ * inclusive - {Boolean} Treat shared edges as contained. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object is contained within this bounds.
+ */
+ containsBounds:function(bounds, partial, inclusive) {
+ if (partial == null) {
+ partial = false;
+ }
+ if (inclusive == null) {
+ inclusive = true;
+ }
+ var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
+ var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
+ var topLeft = this.contains(bounds.left, bounds.top, inclusive);
+ var topRight = this.contains(bounds.right, bounds.top, inclusive);
+
+ return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
+ : (bottomLeft && bottomRight && topLeft && topRight);
+ },
+
+ /**
+ * APIMethod: determineQuadrant
+ * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
+ * <OpenLayers.LonLat> lies.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
+ * coordinate lies.
+ */
+ determineQuadrant: function(lonlat) {
+
+ var quadrant = "";
+ var center = this.getCenterLonLat();
+
+ quadrant += (lonlat.lat < center.lat) ? "b" : "t";
+ quadrant += (lonlat.lon < center.lon) ? "l" : "r";
+
+ return quadrant;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the Bounds object from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ // clear cached center location
+ this.centerLonLat = null;
+ var ll = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.bottom}, source, dest);
+ var lr = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.bottom}, source, dest);
+ var ul = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.top}, source, dest);
+ var ur = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.top}, source, dest);
+ this.left = Math.min(ll.x, ul.x);
+ this.bottom = Math.min(ll.y, lr.y);
+ this.right = Math.max(lr.x, ur.x);
+ this.top = Math.max(ul.y, ur.y);
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ * Wraps the bounds object around the dateline.
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ * options - {Object} Some possible options are:
+ *
+ * Allowed Options:
+ * leftTolerance - {float} Allow for a margin of error
+ * with the 'left' value of this
+ * bound.
+ * Default is 0.
+ * rightTolerance - {float} Allow for a margin of error
+ * with the 'right' value of
+ * this bound.
+ * Default is 0.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent). Note that this function only returns
+ * a different bounds value if this bounds is
+ * *entirely* outside of the maxExtent. If this
+ * bounds straddles the dateline (is part in/part
+ * out of maxExtent), the returned bounds will always
+ * cross the left edge of the given maxExtent.
+ *.
+ */
+ wrapDateLine: function(maxExtent, options) {
+ options = options || {};
+
+ var leftTolerance = options.leftTolerance || 0;
+ var rightTolerance = options.rightTolerance || 0;
+
+ var newBounds = this.clone();
+
+ if (maxExtent) {
+ var width = maxExtent.getWidth();
+
+ //shift right?
+ while (newBounds.left < maxExtent.left &&
+ newBounds.right - rightTolerance <= maxExtent.left ) {
+ newBounds = newBounds.add(width, 0);
+ }
+
+ //shift left?
+ while (newBounds.left + leftTolerance >= maxExtent.right &&
+ newBounds.right > maxExtent.right ) {
+ newBounds = newBounds.add(-width, 0);
+ }
+
+ // crosses right only? force left
+ var newLeft = newBounds.left + leftTolerance;
+ if (newLeft < maxExtent.right && newLeft > maxExtent.left &&
+ newBounds.right - rightTolerance > maxExtent.right) {
+ newBounds = newBounds.add(-width, 0);
+ }
+ }
+
+ return newBounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Bounds"
+});
+
+/**
+ * APIFunction: fromString
+ * Alternative constructor that builds a new OpenLayers.Bounds from a
+ * parameter string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromString("5,42,10,45");
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
+ * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the
+ * passed-in String.
+ */
+OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
+ var bounds = str.split(",");
+ return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
+};
+
+/**
+ * APIFunction: fromArray
+ * Alternative constructor that builds a new OpenLayers.Bounds from an array.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
+ * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
+ */
+OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
+ return reverseAxisOrder === true ?
+ new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
+ new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
+};
+
+/**
+ * APIFunction: fromSize
+ * Alternative constructor that builds a new OpenLayers.Bounds from a size.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(0, 20, 10, 0);
+ * (end)
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
+ * both 'w' and 'h' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
+ */
+OpenLayers.Bounds.fromSize = function(size) {
+ return new OpenLayers.Bounds(0,
+ size.h,
+ size.w,
+ 0);
+};
+
+/**
+ * Function: oppositeQuadrant
+ * Get the opposite quadrant for a given quadrant string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.oppositeQuadrant( "tl" );
+ * // => "br"
+ *
+ * OpenLayers.Bounds.oppositeQuadrant( "tr" );
+ * // => "bl"
+ * (end)
+ *
+ * Parameters:
+ * quadrant - {String} two character quadrant shortstring
+ *
+ * Returns:
+ * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if
+ * you pass in "bl" it returns "tr", if you pass in "br" it
+ * returns "tl", etc.
+ */
+OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
+ var opp = "";
+
+ opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
+ opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
+
+ return opp;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Element.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ */
+
+/**
+ * Namespace: OpenLayers.Element
+ */
+OpenLayers.Element = {
+
+ /**
+ * APIFunction: visible
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Boolean} Is the element visible?
+ */
+ visible: function(element) {
+ return OpenLayers.Util.getElement(element).style.display != 'none';
+ },
+
+ /**
+ * APIFunction: toggle
+ * Toggle the visibility of element(s) passed in
+ *
+ * Parameters:
+ * element - {DOMElement} Actually user can pass any number of elements
+ */
+ toggle: function() {
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ var display = OpenLayers.Element.visible(element) ? 'none'
+ : '';
+ element.style.display = display;
+ }
+ },
+
+ /**
+ * APIFunction: remove
+ * Remove the specified element from the DOM.
+ *
+ * Parameters:
+ * element - {DOMElement}
+ */
+ remove: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ element.parentNode.removeChild(element);
+ },
+
+ /**
+ * APIFunction: getHeight
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Integer} The offset height of the element passed in
+ */
+ getHeight: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ return element.offsetHeight;
+ },
+
+ /**
+ * Function: hasClass
+ * Tests if an element has the given CSS class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to search for.
+ *
+ * Returns:
+ * {Boolean} The element has the given class name.
+ */
+ hasClass: function(element, name) {
+ var names = element.className;
+ return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
+ },
+
+ /**
+ * Function: addClass
+ * Add a CSS class name to an element. Safe where element already has
+ * the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to add.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ addClass: function(element, name) {
+ if(!OpenLayers.Element.hasClass(element, name)) {
+ element.className += (element.className ? " " : "") + name;
+ }
+ return element;
+ },
+
+ /**
+ * Function: removeClass
+ * Remove a CSS class name from an element. Safe where element does not
+ * have the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to remove.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ removeClass: function(element, name) {
+ var names = element.className;
+ if(names) {
+ element.className = OpenLayers.String.trim(
+ names.replace(
+ new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
+ )
+ );
+ }
+ return element;
+ },
+
+ /**
+ * Function: toggleClass
+ * Remove a CSS class name from an element if it exists. Add the class name
+ * if it doesn't exist.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to toggle.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ toggleClass: function(element, name) {
+ if(OpenLayers.Element.hasClass(element, name)) {
+ OpenLayers.Element.removeClass(element, name);
+ } else {
+ OpenLayers.Element.addClass(element, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIFunction: getStyle
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * style - {?}
+ *
+ * Returns:
+ * {?}
+ */
+ getStyle: function(element, style) {
+ element = OpenLayers.Util.getElement(element);
+
+ var value = null;
+ if (element && element.style) {
+ value = element.style[OpenLayers.String.camelize(style)];
+ if (!value) {
+ if (document.defaultView &&
+ document.defaultView.getComputedStyle) {
+
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[OpenLayers.String.camelize(style)];
+ }
+ }
+
+ var positions = ['left', 'top', 'right', 'bottom'];
+ if (window.opera &&
+ (OpenLayers.Util.indexOf(positions,style) != -1) &&
+ (OpenLayers.Element.getStyle(element, 'position') == 'static')) {
+ value = 'auto';
+ }
+ }
+
+ return value == 'auto' ? null : value;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/LonLat.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.LonLat
+ * This class represents a longitude and latitude pair
+ */
+OpenLayers.LonLat = OpenLayers.Class({
+
+ /**
+ * APIProperty: lon
+ * {Float} The x-axis coodinate in map units
+ */
+ lon: 0.0,
+
+ /**
+ * APIProperty: lat
+ * {Float} The y-axis coordinate in map units
+ */
+ lat: 0.0,
+
+ /**
+ * Constructor: OpenLayers.LonLat
+ * Create a new map location. Coordinates can be passed either as two
+ * arguments, or as a single argument.
+ *
+ * Parameters (two arguments):
+ * lon - {Number} The x-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Longitude. Otherwise,
+ * it will be the x coordinate of the map location in your map units.
+ * lat - {Number} The y-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Latitude. Otherwise,
+ * it will be the y coordinate of the map location in your map units.
+ *
+ * Parameters (single argument):
+ * location - {Array(Float)} [lon, lat]
+ */
+ initialize: function(lon, lat) {
+ if (OpenLayers.Util.isArray(lon)) {
+ lat = lon[1];
+ lon = lon[0];
+ }
+ this.lon = OpenLayers.Util.toFloat(lon);
+ this.lat = OpenLayers.Util.toFloat(lat);
+ },
+
+ /**
+ * Method: toString
+ * Return a readable string version of the lonlat
+ *
+ * Returns:
+ * {String} String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"lon=5,lat=42"</i>)
+ */
+ toString:function() {
+ return ("lon=" + this.lon + ",lat=" + this.lat);
+ },
+
+ /**
+ * APIMethod: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"5, 42"</i>)
+ */
+ toShortString:function() {
+ return (this.lon + ", " + this.lat);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon
+ * and lat values
+ */
+ clone:function() {
+ return new OpenLayers.LonLat(this.lon, this.lat);
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and
+ * lat passed-in added to this's.
+ */
+ add:function(lon, lat) {
+ if ( (lon == null) || (lat == null) ) {
+ throw new TypeError('LonLat.add cannot receive null values');
+ }
+ return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon),
+ this.lat + OpenLayers.Util.toFloat(lat));
+ },
+
+ /**
+ * APIMethod: equals
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Boolean value indicating whether the passed-in
+ * <OpenLayers.LonLat> object has the same lon and lat
+ * components as this.
+ * Note: if ll passed in is null, returns false
+ */
+ equals:function(ll) {
+ var equals = false;
+ if (ll != null) {
+ equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
+ (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the LonLat object from source to dest. This transformation is
+ * *in place*: if you want a *new* lonlat, use .clone() first.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ var point = OpenLayers.Projection.transform(
+ {'x': this.lon, 'y': this.lat}, source, dest);
+ this.lon = point.x;
+ this.lat = point.y;
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent)
+ */
+ wrapDateLine: function(maxExtent) {
+
+ var newLonLat = this.clone();
+
+ if (maxExtent) {
+ //shift right?
+ while (newLonLat.lon < maxExtent.left) {
+ newLonLat.lon += maxExtent.getWidth();
+ }
+
+ //shift left?
+ while (newLonLat.lon > maxExtent.right) {
+ newLonLat.lon -= maxExtent.getWidth();
+ }
+ }
+
+ return newLonLat;
+ },
+
+ CLASS_NAME: "OpenLayers.LonLat"
+});
+
+/**
+ * Function: fromString
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from a
+ * parameter string
+ *
+ * Parameters:
+ * str - {String} Comma-separated Lon,Lat coordinate string.
+ * (e.g. <i>"5,40"</i>)
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in String.
+ */
+OpenLayers.LonLat.fromString = function(str) {
+ var pair = str.split(",");
+ return new OpenLayers.LonLat(pair[0], pair[1]);
+};
+
+/**
+ * Function: fromArray
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from an
+ * array of two numbers that represent lon- and lat-values.
+ *
+ * Parameters:
+ * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in array.
+ */
+OpenLayers.LonLat.fromArray = function(arr) {
+ var gotArr = OpenLayers.Util.isArray(arr),
+ lon = gotArr && arr[0],
+ lat = gotArr && arr[1];
+ return new OpenLayers.LonLat(lon, lat);
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Pixel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Pixel
+ * This class represents a screen coordinate, in x and y coordinates
+ */
+OpenLayers.Pixel = OpenLayers.Class({
+
+ /**
+ * APIProperty: x
+ * {Number} The x coordinate
+ */
+ x: 0.0,
+
+ /**
+ * APIProperty: y
+ * {Number} The y coordinate
+ */
+ y: 0.0,
+
+ /**
+ * Constructor: OpenLayers.Pixel
+ * Create a new OpenLayers.Pixel instance
+ *
+ * Parameters:
+ * x - {Number} The x coordinate
+ * y - {Number} The y coordinate
+ *
+ * Returns:
+ * An instance of OpenLayers.Pixel
+ */
+ initialize: function(x, y) {
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * Method: toString
+ * Cast this object into a string
+ *
+ * Returns:
+ * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
+ */
+ toString:function() {
+ return ("x=" + this.x + ",y=" + this.y);
+ },
+
+ /**
+ * APIMethod: clone
+ * Return a clone of this pixel object
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A clone pixel
+ */
+ clone:function() {
+ return new OpenLayers.Pixel(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether one pixel is equivalent to another
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {Boolean} The point passed in as parameter is equal to this. Note that
+ * if px passed in is null, returns false.
+ */
+ equals:function(px) {
+ var equals = false;
+ if (px != null) {
+ equals = ((this.x == px.x && this.y == px.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Returns the distance to the pixel point passed in as a parameter.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Float} The pixel point passed in as parameter to calculate the
+ * distance to.
+ */
+ distanceTo:function(px) {
+ return Math.sqrt(
+ Math.pow(this.x - px.x, 2) +
+ Math.pow(this.y - px.y, 2)
+ );
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * values passed in.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Pixel.add cannot receive null values');
+ }
+ return new OpenLayers.Pixel(this.x + x, this.y + y);
+ },
+
+ /**
+ * APIMethod: offset
+ *
+ * Parameters
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * x&y values of the pixel passed in.
+ */
+ offset:function(px) {
+ var newPx = this.clone();
+ if (px) {
+ newPx = this.add(px.x, px.y);
+ }
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Pixel"
+});
+/* ======================================================================
+ OpenLayers/BaseTypes/Size.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Size
+ * Instances of this class represent a width/height pair
+ */
+OpenLayers.Size = OpenLayers.Class({
+
+ /**
+ * APIProperty: w
+ * {Number} width
+ */
+ w: 0.0,
+
+ /**
+ * APIProperty: h
+ * {Number} height
+ */
+ h: 0.0,
+
+
+ /**
+ * Constructor: OpenLayers.Size
+ * Create an instance of OpenLayers.Size
+ *
+ * Parameters:
+ * w - {Number} width
+ * h - {Number} height
+ */
+ initialize: function(w, h) {
+ this.w = parseFloat(w);
+ this.h = parseFloat(h);
+ },
+
+ /**
+ * Method: toString
+ * Return the string representation of a size object
+ *
+ * Returns:
+ * {String} The string representation of OpenLayers.Size object.
+ * (e.g. <i>"w=55,h=66"</i>)
+ */
+ toString:function() {
+ return ("w=" + this.w + ",h=" + this.h);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this size object
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
+ * values
+ */
+ clone:function() {
+ return new OpenLayers.Size(this.w, this.h);
+ },
+
+ /**
+ *
+ * APIMethod: equals
+ * Determine where this size is equal to another
+ *
+ * Parameters:
+ * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ *
+ * Returns:
+ * {Boolean} The passed in size has the same h and w properties as this one.
+ * Note that if sz passed in is null, returns false.
+ */
+ equals:function(sz) {
+ var equals = false;
+ if (sz != null) {
+ equals = ((this.w == sz.w && this.h == sz.h) ||
+ (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
+ }
+ return equals;
+ },
+
+ CLASS_NAME: "OpenLayers.Size"
+});
+/* ======================================================================
+ OpenLayers/Console.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Namespace: OpenLayers.Console
+ * The OpenLayers.Console namespace is used for debugging and error logging.
+ * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
+ * calls to OpenLayers.Console methods will get redirected to window.console.
+ * This makes use of the Firebug extension where available and allows for
+ * cross-browser debugging Firebug style.
+ *
+ * Note:
+ * Note that behavior will differ with the Firebug extention and Firebug Lite.
+ * Most notably, the Firebug Lite console does not currently allow for
+ * hyperlinks to code or for clicking on object to explore their properties.
+ *
+ */
+OpenLayers.Console = {
+ /**
+ * Create empty functions for all console methods. The real value of these
+ * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
+ * included. We explicitly require the Firebug Lite script to trigger
+ * functionality of the OpenLayers.Console methods.
+ */
+
+ /**
+ * APIFunction: log
+ * Log an object in the console. The Firebug Lite console logs string
+ * representation of objects. Given multiple arguments, they will
+ * be cast to strings and logged with a space delimiter. If the first
+ * argument is a string with printf-like formatting, subsequent arguments
+ * will be used in string substitution. Any additional arguments (beyond
+ * the number substituted in a format string) will be appended in a space-
+ * delimited line.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ log: function() {},
+
+ /**
+ * APIFunction: debug
+ * Writes a message to the console, including a hyperlink to the line
+ * where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ debug: function() {},
+
+ /**
+ * APIFunction: info
+ * Writes a message to the console with the visual "info" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ info: function() {},
+
+ /**
+ * APIFunction: warn
+ * Writes a message to the console with the visual "warning" icon and
+ * color coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ warn: function() {},
+
+ /**
+ * APIFunction: error
+ * Writes a message to the console with the visual "error" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ error: function() {},
+
+ /**
+ * APIFunction: userError
+ * A single interface for showing error messages to the user. The default
+ * behavior is a Javascript alert, though this can be overridden by
+ * reassigning OpenLayers.Console.userError to a different function.
+ *
+ * Expects a single error message
+ *
+ * Parameters:
+ * error - {Object}
+ */
+ userError: function(error) {
+ alert(error);
+ },
+
+ /**
+ * APIFunction: assert
+ * Tests that an expression is true. If not, it will write a message to
+ * the console and throw an exception.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ assert: function() {},
+
+ /**
+ * APIFunction: dir
+ * Prints an interactive listing of all properties of the object. This
+ * looks identical to the view that you would see in the DOM tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dir: function() {},
+
+ /**
+ * APIFunction: dirxml
+ * Prints the XML source tree of an HTML or XML element. This looks
+ * identical to the view that you would see in the HTML tab. You can click
+ * on any node to inspect it in the HTML tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dirxml: function() {},
+
+ /**
+ * APIFunction: trace
+ * Prints an interactive stack trace of JavaScript execution at the point
+ * where it is called. The stack trace details the functions on the stack,
+ * as well as the values that were passed as arguments to each function.
+ * You can click each function to take you to its source in the Script tab,
+ * and click each argument value to inspect it in the DOM or HTML tabs.
+ *
+ */
+ trace: function() {},
+
+ /**
+ * APIFunction: group
+ * Writes a message to the console and opens a nested block to indent all
+ * future messages sent to the console. Call OpenLayers.Console.groupEnd()
+ * to close the block.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ group: function() {},
+
+ /**
+ * APIFunction: groupEnd
+ * Closes the most recently opened block created by a call to
+ * OpenLayers.Console.group
+ */
+ groupEnd: function() {},
+
+ /**
+ * APIFunction: time
+ * Creates a new timer under the given name. Call
+ * OpenLayers.Console.timeEnd(name)
+ * with the same name to stop the timer and print the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ time: function() {},
+
+ /**
+ * APIFunction: timeEnd
+ * Stops a timer created by a call to OpenLayers.Console.time(name) and
+ * writes the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ timeEnd: function() {},
+
+ /**
+ * APIFunction: profile
+ * Turns on the JavaScript profiler. The optional argument title would
+ * contain the text to be printed in the header of the profile report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title for the profiler
+ */
+ profile: function() {},
+
+ /**
+ * APIFunction: profileEnd
+ * Turns off the JavaScript profiler and prints its report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ */
+ profileEnd: function() {},
+
+ /**
+ * APIFunction: count
+ * Writes the number of times that the line of code where count was called
+ * was executed. The optional argument title will print a message in
+ * addition to the number of the count.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title to be printed with count
+ */
+ count: function() {},
+
+ CLASS_NAME: "OpenLayers.Console"
+};
+
+/**
+ * Execute an anonymous function to extend the OpenLayers.Console namespace
+ * if the firebug.js script is included. This closure is used so that the
+ * "scripts" and "i" variables don't pollute the global namespace.
+ */
+(function() {
+ /**
+ * If Firebug Lite is included (before this script), re-route all
+ * OpenLayers.Console calls to the console object.
+ */
+ var scripts = document.getElementsByTagName("script");
+ for(var i=0, len=scripts.length; i<len; ++i) {
+ if(scripts[i].src.indexOf("firebug.js") != -1) {
+ if(console) {
+ OpenLayers.Util.extend(OpenLayers.Console, console);
+ break;
+ }
+ }
+ }
+})();
+/* ======================================================================
+ OpenLayers/Lang.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang
+ * Internationalization namespace. Contains dictionaries in various languages
+ * and methods to set and get the current language.
+ */
+OpenLayers.Lang = {
+
+ /**
+ * Property: code
+ * {String} Current language code to use in OpenLayers. Use the
+ * <setCode> method to set this value and the <getCode> method to
+ * retrieve it.
+ */
+ code: null,
+
+ /**
+ * APIProperty: defaultCode
+ * {String} Default language to use when a specific language can't be
+ * found. Default is "en".
+ */
+ defaultCode: "en",
+
+ /**
+ * APIFunction: getCode
+ * Get the current language code.
+ *
+ * Returns:
+ * {String} The current language code.
+ */
+ getCode: function() {
+ if(!OpenLayers.Lang.code) {
+ OpenLayers.Lang.setCode();
+ }
+ return OpenLayers.Lang.code;
+ },
+
+ /**
+ * APIFunction: setCode
+ * Set the language code for string translation. This code is used by
+ * the <OpenLayers.Lang.translate> method.
+ *
+ * Parameters:
+ * code - {String} These codes follow the IETF recommendations at
+ * http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the
+ * browser's language setting will be tested. If no <OpenLayers.Lang>
+ * dictionary exists for the code, the <OpenLayers.String.defaultLang>
+ * will be used.
+ */
+ setCode: function(code) {
+ var lang;
+ if(!code) {
+ code = (OpenLayers.BROWSER_NAME == "msie") ?
+ navigator.userLanguage : navigator.language;
+ }
+ var parts = code.split('-');
+ parts[0] = parts[0].toLowerCase();
+ if(typeof OpenLayers.Lang[parts[0]] == "object") {
+ lang = parts[0];
+ }
+
+ // check for regional extensions
+ if(parts[1]) {
+ var testLang = parts[0] + '-' + parts[1].toUpperCase();
+ if(typeof OpenLayers.Lang[testLang] == "object") {
+ lang = testLang;
+ }
+ }
+ if(!lang) {
+ OpenLayers.Console.warn(
+ 'Failed to find OpenLayers.Lang.' + parts.join("-") +
+ ' dictionary, falling back to default language'
+ );
+ lang = OpenLayers.Lang.defaultCode;
+ }
+
+ OpenLayers.Lang.code = lang;
+ },
+
+ /**
+ * APIMethod: translate
+ * Looks up a key from a dictionary based on the current language string.
+ * The value of <getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+ translate: function(key, context) {
+ var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
+ var message = dictionary && dictionary[key];
+ if(!message) {
+ // Message not found, fall back to message key
+ message = key;
+ }
+ if(context) {
+ message = OpenLayers.String.format(message, context);
+ }
+ return message;
+ }
+
+};
+
+
+/**
+ * APIMethod: OpenLayers.i18n
+ * Alias for <OpenLayers.Lang.translate>. Looks up a key from a dictionary
+ * based on the current language string. The value of
+ * <OpenLayers.Lang.getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+OpenLayers.i18n = OpenLayers.Lang.translate;
+/* ======================================================================
+ OpenLayers/Util.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/BaseTypes/LonLat.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: Util
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+
+/**
+ * Function: getElement
+ * This is the old $() from prototype
+ *
+ * Parameters:
+ * e - {String or DOMElement or Window}
+ *
+ * Returns:
+ * {Array(DOMElement) or DOMElement}
+ */
+OpenLayers.Util.getElement = function() {
+ var elements = [];
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ if (arguments.length == 1) {
+ return element;
+ }
+ elements.push(element);
+ }
+ return elements;
+};
+
+/**
+ * Function: isElement
+ * A cross-browser implementation of "e instanceof Element".
+ *
+ * Parameters:
+ * o - {Object} The object to test.
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.isElement = function(o) {
+ return !!(o && o.nodeType === 1);
+};
+
+/**
+ * Function: isArray
+ * Tests that the provided object is an array.
+ * This test handles the cross-IFRAME case not caught
+ * by "a instanceof Array" and should be used instead.
+ *
+ * Parameters:
+ * a - {Object} the object test.
+ *
+ * Returns:
+ * {Boolean} true if the object is an array.
+ */
+OpenLayers.Util.isArray = function(a) {
+ return (Object.prototype.toString.call(a) === '[object Array]');
+};
+
+/**
+ * Function: removeItem
+ * Remove an object from an array. Iterates through the array
+ * to find the item, then removes it.
+ *
+ * Parameters:
+ * array - {Array}
+ * item - {Object}
+ *
+ * Returns:
+ * {Array} A reference to the array
+ */
+OpenLayers.Util.removeItem = function(array, item) {
+ for(var i = array.length - 1; i >= 0; i--) {
+ if(array[i] == item) {
+ array.splice(i,1);
+ //break;more than once??
+ }
+ }
+ return array;
+};
+
+/**
+ * Function: indexOf
+ * Seems to exist already in FF, but not in MOZ.
+ *
+ * Parameters:
+ * array - {Array}
+ * obj - {*}
+ *
+ * Returns:
+ * {Integer} The index at which the first object was found in the array.
+ * If not found, returns -1.
+ */
+OpenLayers.Util.indexOf = function(array, obj) {
+ // use the build-in function if available.
+ if (typeof array.indexOf == "function") {
+ return array.indexOf(obj);
+ } else {
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (array[i] == obj) {
+ return i;
+ }
+ }
+ return -1;
+ }
+};
+
+
+/**
+ * Property: dotless
+ * {RegExp}
+ * Compiled regular expression to match dots ("."). This is used for replacing
+ * dots in identifiers. Because object identifiers are frequently used for
+ * DOM element identifiers by the library, we avoid using dots to make for
+ * more sensible CSS selectors.
+ *
+ * TODO: Use a module pattern to avoid bloating the API with stuff like this.
+ */
+OpenLayers.Util.dotless = /\./g;
+
+/**
+ * Function: modifyDOMElement
+ *
+ * Modifies many properties of a DOM element all at once. Passing in
+ * null to an individual parameter will avoid setting the attribute.
+ *
+ * Parameters:
+ * element - {DOMElement} DOM element to modify.
+ * id - {String} The element id attribute to set. Note that dots (".") will be
+ * replaced with underscore ("_") in setting the element id.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * position - {String} The position attribute. eg: absolute,
+ * relative, etc.
+ * border - {String} The style.border attribute. eg:
+ * solid black 2px
+ * overflow - {String} The style.overview attribute.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position,
+ border, overflow, opacity) {
+
+ if (id) {
+ element.id = id.replace(OpenLayers.Util.dotless, "_");
+ }
+ if (px) {
+ element.style.left = px.x + "px";
+ element.style.top = px.y + "px";
+ }
+ if (sz) {
+ element.style.width = sz.w + "px";
+ element.style.height = sz.h + "px";
+ }
+ if (position) {
+ element.style.position = position;
+ }
+ if (border) {
+ element.style.border = border;
+ }
+ if (overflow) {
+ element.style.overflow = overflow;
+ }
+ if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
+ element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
+ element.style.opacity = opacity;
+ } else if (parseFloat(opacity) == 1.0) {
+ element.style.filter = '';
+ element.style.opacity = '';
+ }
+};
+
+/**
+ * Function: createDiv
+ * Creates a new div and optionally set some standard attributes.
+ * Null may be passed to each parameter if you do not wish to
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
+ *
+ * Parameters:
+ * id - {String} An identifier for this element. If no id is
+ * passed an identifier will be created
+ * automatically. Note that dots (".") will be replaced with
+ * underscore ("_") when generating ids.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} A url pointing to an image to use as a
+ * background image.
+ * position - {String} The style.position value. eg: absolute,
+ * relative etc.
+ * border - {String} The the style.border value.
+ * eg: 2px solid black
+ * overflow - {String} The style.overflow value. Eg. hidden
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with the specified attributes.
+ */
+OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position,
+ border, overflow, opacity) {
+
+ var dom = document.createElement('div');
+
+ if (imgURL) {
+ dom.style.backgroundImage = 'url(' + imgURL + ')';
+ }
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "absolute";
+ }
+ OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position,
+ border, overflow, opacity);
+
+ return dom;
+};
+
+/**
+ * Function: createImage
+ * Creates an img element with specific attribute values.
+ *
+ * Parameters:
+ * id - {String} The id field for the img. If none assigned one will be
+ * automatically generated.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} The url to use as the image source.
+ * position - {String} The style.position value.
+ * border - {String} The border to place around the image.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Image created with the specified attributes.
+ */
+OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
+ opacity, delayDisplay) {
+
+ var image = document.createElement("img");
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "relative";
+ }
+ OpenLayers.Util.modifyDOMElement(image, id, px, sz, position,
+ border, null, opacity);
+
+ if (delayDisplay) {
+ image.style.display = "none";
+ function display() {
+ image.style.display = "";
+ OpenLayers.Event.stopObservingElement(image);
+ }
+ OpenLayers.Event.observe(image, "load", display);
+ OpenLayers.Event.observe(image, "error", display);
+ }
+
+ //set special properties
+ image.style.alt = id;
+ image.galleryImg = "no";
+ if (imgURL) {
+ image.src = imgURL;
+ }
+
+ return image;
+};
+
+/**
+ * Property: IMAGE_RELOAD_ATTEMPTS
+ * {Integer} How many times should we try to reload an image before giving up?
+ * Default is 0
+ */
+OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
+
+/**
+ * Property: alphaHackNeeded
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHackNeeded = null;
+
+/**
+ * Function: alphaHack
+ * Checks whether it's necessary (and possible) to use the png alpha
+ * hack which allows alpha transparency for png images under Internet
+ * Explorer.
+ *
+ * Returns:
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHack = function() {
+ if (OpenLayers.Util.alphaHackNeeded == null) {
+ var arVersion = navigator.appVersion.split("MSIE");
+ var version = parseFloat(arVersion[1]);
+ var filter = false;
+
+ // IEs4Lin dies when trying to access document.body.filters, because
+ // the property is there, but requires a DLL that can't be provided. This
+ // means that we need to wrap this in a try/catch so that this can
+ // continue.
+
+ try {
+ filter = !!(document.body.filters);
+ } catch (e) {}
+
+ OpenLayers.Util.alphaHackNeeded = (filter &&
+ (version >= 5.5) && (version < 7));
+ }
+ return OpenLayers.Util.alphaHackNeeded;
+};
+
+/**
+ * Function: modifyAlphaImageDiv
+ *
+ * Parameters:
+ * div - {DOMElement} Div containing Alpha-adjusted Image
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL,
+ position, border, sizing,
+ opacity) {
+
+ OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+ null, null, opacity);
+
+ var img = div.childNodes[0];
+
+ if (imgURL) {
+ img.src = imgURL;
+ }
+ OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz,
+ "relative", border);
+
+ if (OpenLayers.Util.alphaHack()) {
+ if(div.style.display != "none") {
+ div.style.display = "inline-block";
+ }
+ if (sizing == null) {
+ sizing = "scale";
+ }
+
+ div.style.filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img.src + "', " +
+ "sizingMethod='" + sizing + "')";
+ if (parseFloat(div.style.opacity) >= 0.0 &&
+ parseFloat(div.style.opacity) < 1.0) {
+ div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
+ }
+
+ img.style.filter = "alpha(opacity=0)";
+ }
+};
+
+/**
+ * Function: createAlphaImageDiv
+ *
+ * Parameters:
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is
+ * needed for transparency in IE, it is added.
+ */
+OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL,
+ position, border, sizing,
+ opacity, delayDisplay) {
+
+ var div = OpenLayers.Util.createDiv();
+ var img = OpenLayers.Util.createImage(null, null, null, null, null, null,
+ null, delayDisplay);
+ img.className = "olAlphaImg";
+ div.appendChild(img);
+
+ OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position,
+ border, sizing, opacity);
+
+ return div;
+};
+
+
+/**
+ * Function: upperCaseObject
+ * Creates a new hashtable and copies over all the keys from the
+ * passed-in object, but storing them under an uppercased
+ * version of the key at which they were stored.
+ *
+ * Parameters:
+ * object - {Object}
+ *
+ * Returns:
+ * {Object} A new Object with all the same keys but uppercased
+ */
+OpenLayers.Util.upperCaseObject = function (object) {
+ var uObject = {};
+ for (var key in object) {
+ uObject[key.toUpperCase()] = object[key];
+ }
+ return uObject;
+};
+
+/**
+ * Function: applyDefaults
+ * Takes an object and copies any properties that don't exist from
+ * another properties, by analogy with OpenLayers.Util.extend() from
+ * Prototype.js.
+ *
+ * Parameters:
+ * to - {Object} The destination object.
+ * from - {Object} The source object. Any properties of this object that
+ * are undefined in the to object will be set on the to object.
+ *
+ * Returns:
+ * {Object} A reference to the to object. Note that the to argument is modified
+ * in place and returned by this function.
+ */
+OpenLayers.Util.applyDefaults = function (to, from) {
+ to = to || {};
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object is an
+ * instance of window.Event.
+ */
+ var fromIsEvt = typeof window.Event == "function"
+ && from instanceof window.Event;
+
+ for (var key in from) {
+ if (to[key] === undefined ||
+ (!fromIsEvt && from.hasOwnProperty
+ && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
+ to[key] = from[key];
+ }
+ }
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+ if(!fromIsEvt && from && from.hasOwnProperty
+ && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
+ to.toString = from.toString;
+ }
+
+ return to;
+};
+
+/**
+ * Function: getParameterString
+ *
+ * Parameters:
+ * params - {Object}
+ *
+ * Returns:
+ * {String} A concatenation of the properties of an object in
+ * http parameter notation.
+ * (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
+ * If a parameter is actually a list, that parameter will then
+ * be set to a comma-seperated list of values (foo,bar) instead
+ * of being URL escaped (foo%3Abar).
+ */
+OpenLayers.Util.getParameterString = function(params) {
+ var paramsArray = [];
+
+ for (var key in params) {
+ var value = params[key];
+ if ((value != null) && (typeof value != 'function')) {
+ var encodedValue;
+ if (typeof value == 'object' && value.constructor == Array) {
+ /* value is an array; encode items and separate with "," */
+ var encodedItemArray = [];
+ var item;
+ for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
+ item = value[itemIndex];
+ encodedItemArray.push(encodeURIComponent(
+ (item === null || item === undefined) ? "" : item)
+ );
+ }
+ encodedValue = encodedItemArray.join(",");
+ }
+ else {
+ /* value is a string; simply encode */
+ encodedValue = encodeURIComponent(value);
+ }
+ paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
+ }
+ }
+
+ return paramsArray.join("&");
+};
+
+/**
+ * Function: urlAppend
+ * Appends a parameter string to a url. This function includes the logic for
+ * using the appropriate character (none, & or ?) to append to the url before
+ * appending the param string.
+ *
+ * Parameters:
+ * url - {String} The url to append to
+ * paramStr - {String} The param string to append
+ *
+ * Returns:
+ * {String} The new url
+ */
+OpenLayers.Util.urlAppend = function(url, paramStr) {
+ var newUrl = url;
+ if(paramStr) {
+ var parts = (url + " ").split(/[?&]/);
+ newUrl += (parts.pop() === " " ?
+ paramStr :
+ parts.length ? "&" + paramStr : "?" + paramStr);
+ }
+ return newUrl;
+};
+
+/**
+ * Function: getImagesLocation
+ *
+ * Returns:
+ * {String} The fully formatted image location string
+ */
+OpenLayers.Util.getImagesLocation = function() {
+ return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
+};
+
+/**
+ * Function: getImageLocation
+ *
+ * Returns:
+ * {String} The fully formatted location string for a specified image
+ */
+OpenLayers.Util.getImageLocation = function(image) {
+ return OpenLayers.Util.getImagesLocation() + image;
+};
+
+
+/**
+ * Function: Try
+ * Execute functions until one of them doesn't throw an error.
+ * Capitalized because "try" is a reserved word in JavaScript.
+ * Taken directly from OpenLayers.Util.Try()
+ *
+ * Parameters:
+ * [*] - {Function} Any number of parameters may be passed to Try()
+ * It will attempt to execute each of them until one of them
+ * successfully executes.
+ * If none executes successfully, returns null.
+ *
+ * Returns:
+ * {*} The value returned by the first successfully executed function.
+ */
+OpenLayers.Util.Try = function() {
+ var returnValue = null;
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+};
+
+/**
+ * Function: getXmlNodeValue
+ *
+ * Parameters:
+ * node - {XMLNode}
+ *
+ * Returns:
+ * {String} The text value of the given node, without breaking in firefox or IE
+ */
+OpenLayers.Util.getXmlNodeValue = function(node) {
+ var val = null;
+ OpenLayers.Util.Try(
+ function() {
+ val = node.text;
+ if (!val) {
+ val = node.textContent;
+ }
+ if (!val) {
+ val = node.firstChild.nodeValue;
+ }
+ },
+ function() {
+ val = node.textContent;
+ });
+ return val;
+};
+
+/**
+ * Function: mouseLeft
+ *
+ * Parameters:
+ * evt - {Event}
+ * div - {HTMLDivElement}
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.mouseLeft = function (evt, div) {
+ // start with the element to which the mouse has moved
+ var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
+ // walk up the DOM tree.
+ while (target != div && target != null) {
+ target = target.parentNode;
+ }
+ // if the target we stop at isn't the div, then we've left the div.
+ return (target != div);
+};
+
+/**
+ * Property: precision
+ * {Number} The number of significant digits to retain to avoid
+ * floating point precision errors.
+ *
+ * We use 14 as a "safe" default because, although IEEE 754 double floats
+ * (standard on most modern operating systems) support up to about 16
+ * significant digits, 14 significant digits are sufficient to represent
+ * sub-millimeter accuracy in any coordinate system that anyone is likely to
+ * use with OpenLayers.
+ *
+ * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
+ * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
+ * with certain projections, e.g. spherical Mercator.
+ *
+ */
+OpenLayers.Util.DEFAULT_PRECISION = 14;
+
+/**
+ * Function: toFloat
+ * Convenience method to cast an object to a Number, rounded to the
+ * desired floating point precision.
+ *
+ * Parameters:
+ * number - {Number} The number to cast and round.
+ * precision - {Number} An integer suitable for use with
+ * Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
+ * If set to 0, no rounding is performed.
+ *
+ * Returns:
+ * {Number} The cast, rounded number.
+ */
+OpenLayers.Util.toFloat = function (number, precision) {
+ if (precision == null) {
+ precision = OpenLayers.Util.DEFAULT_PRECISION;
+ }
+ if (typeof number !== "number") {
+ number = parseFloat(number);
+ }
+ return precision === 0 ? number :
+ parseFloat(number.toPrecision(precision));
+};
+
+/**
+ * Function: rad
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
+
+/**
+ * Function: deg
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
+
+/**
+ * Property: VincentyConstants
+ * {Object} Constants for Vincenty functions.
+ */
+OpenLayers.Util.VincentyConstants = {
+ a: 6378137,
+ b: 6356752.3142,
+ f: 1/298.257223563
+};
+
+/**
+ * APIFunction: distVincenty
+ * Given two objects representing points with geographic coordinates, this
+ * calculates the distance between those points on the surface of an
+ * ellipsoid.
+ *
+ * Parameters:
+ * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ *
+ * Returns:
+ * {Float} The distance (in km) between the two input points as measured on an
+ * ellipsoid. Note that the input point objects must be in geographic
+ * coordinates (decimal degrees) and the return distance is in kilometers.
+ */
+OpenLayers.Util.distVincenty = function(p1, p2) {
+ var ct = OpenLayers.Util.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var L = OpenLayers.Util.rad(p2.lon - p1.lon);
+ var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
+ var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
+ var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
+ var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
+ var lambda = L, lambdaP = 2*Math.PI;
+ var iterLimit = 20;
+ while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
+ var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
+ var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
+ (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
+ if (sinSigma==0) {
+ return 0; // co-incident points
+ }
+ var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
+ var sigma = Math.atan2(sinSigma, cosSigma);
+ var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
+ var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
+ var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ lambdaP = lambda;
+ lambda = L + (1-C) * f * Math.sin(alpha) *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+ }
+ if (iterLimit==0) {
+ return NaN; // formula failed to converge
+ }
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ var s = b*A*(sigma-deltaSigma);
+ var d = s.toFixed(3)/1000; // round to 1mm precision
+ return d;
+};
+
+/**
+ * APIFunction: destinationVincenty
+ * Calculate destination point given start point lat/long (numeric degrees),
+ * bearing (numeric degrees) & distance (in m).
+ * Adapted from Chris Veness work, see
+ * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
+ * properties) The start point.
+ * brng - {Float} The bearing (degrees).
+ * dist - {Float} The ground distance (meters).
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The destination point.
+ */
+OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
+ var u = OpenLayers.Util;
+ var ct = u.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var lon1 = lonlat.lon;
+ var lat1 = lonlat.lat;
+
+ var s = dist;
+ var alpha1 = u.rad(brng);
+ var sinAlpha1 = Math.sin(alpha1);
+ var cosAlpha1 = Math.cos(alpha1);
+
+ var tanU1 = (1-f) * Math.tan(u.rad(lat1));
+ var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
+ var sigma1 = Math.atan2(tanU1, cosAlpha1);
+ var sinAlpha = cosU1 * sinAlpha1;
+ var cosSqAlpha = 1 - sinAlpha*sinAlpha;
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+
+ var sigma = s / (b*A), sigmaP = 2*Math.PI;
+ while (Math.abs(sigma-sigmaP) > 1e-12) {
+ var cos2SigmaM = Math.cos(2*sigma1 + sigma);
+ var sinSigma = Math.sin(sigma);
+ var cosSigma = Math.cos(sigma);
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ sigmaP = sigma;
+ sigma = s / (b*A) + deltaSigma;
+ }
+
+ var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
+ var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
+ (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+ var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ var L = lambda - (1-C) * f * sinAlpha *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+
+ var revAz = Math.atan2(sinAlpha, -tmp); // final bearing
+
+ return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
+};
+
+/**
+ * Function: getParameters
+ * Parse the parameters from a URL or from the current page itself into a
+ * JavaScript Object. Note that parameter values with commas are separated
+ * out into an Array.
+ *
+ * Parameters:
+ * url - {String} Optional url used to extract the query string.
+ * If url is null or is not supplied, query string is taken
+ * from the page location.
+ * options - {Object} Additional options. Optional.
+ *
+ * Valid options:
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object of key/value pairs from the query string.
+ */
+OpenLayers.Util.getParameters = function(url, options) {
+ options = options || {};
+ // if no url specified, take it from the location bar
+ url = (url === null || url === undefined) ? window.location.href : url;
+
+ //parse out parameters portion of url string
+ var paramsString = "";
+ if (OpenLayers.String.contains(url, '?')) {
+ var start = url.indexOf('?') + 1;
+ var end = OpenLayers.String.contains(url, "#") ?
+ url.indexOf('#') : url.length;
+ paramsString = url.substring(start, end);
+ }
+
+ var parameters = {};
+ var pairs = paramsString.split(/[&;]/);
+ for(var i=0, len=pairs.length; i<len; ++i) {
+ var keyValue = pairs[i].split('=');
+ if (keyValue[0]) {
+
+ var key = keyValue[0];
+ try {
+ key = decodeURIComponent(key);
+ } catch (err) {
+ key = unescape(key);
+ }
+
+ // being liberal by replacing "+" with " "
+ var value = (keyValue[1] || '').replace(/\+/g, " ");
+
+ try {
+ value = decodeURIComponent(value);
+ } catch (err) {
+ value = unescape(value);
+ }
+
+ // follow OGC convention of comma delimited values
+ if (options.splitArgs !== false) {
+ value = value.split(",");
+ }
+
+ //if there's only one value, do not return as array
+ if (value.length == 1) {
+ value = value[0];
+ }
+
+ parameters[key] = value;
+ }
+ }
+ return parameters;
+};
+
+/**
+ * Property: lastSeqID
+ * {Integer} The ever-incrementing count variable.
+ * Used for generating unique ids.
+ */
+OpenLayers.Util.lastSeqID = 0;
+
+/**
+ * Function: createUniqueID
+ * Create a unique identifier for this session. Each time this function
+ * is called, a counter is incremented. The return will be the optional
+ * prefix (defaults to "id_") appended with the counter value.
+ *
+ * Parameters:
+ * prefix - {String} Optional string to prefix unique id. Default is "id_".
+ * Note that dots (".") in the prefix will be replaced with underscore ("_").
+ *
+ * Returns:
+ * {String} A unique id string, built on the passed in prefix.
+ */
+OpenLayers.Util.createUniqueID = function(prefix) {
+ if (prefix == null) {
+ prefix = "id_";
+ } else {
+ prefix = prefix.replace(OpenLayers.Util.dotless, "_");
+ }
+ OpenLayers.Util.lastSeqID += 1;
+ return prefix + OpenLayers.Util.lastSeqID;
+};
+
+/**
+ * Constant: INCHES_PER_UNIT
+ * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
+ * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
+ * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
+ * and PROJ.4 (http://trac.osgeo.org/proj/)
+ * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
+ * The hardcoded table of PROJ.4 units are in pj_units.c.
+ */
+OpenLayers.INCHES_PER_UNIT = {
+ 'inches': 1.0,
+ 'ft': 12.0,
+ 'mi': 63360.0,
+ 'm': 39.37,
+ 'km': 39370,
+ 'dd': 4374754,
+ 'yd': 36
+};
+OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
+OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
+OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
+
+// Units from CS-Map
+OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "Inch": OpenLayers.INCHES_PER_UNIT.inches,
+ "Meter": 1.0 / OpenLayers.METERS_PER_INCH, //EPSG:9001
+ "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH, //EPSG:9003
+ "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9002
+ "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH, //EPSG:9005
+ "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH, //EPSG:9041
+ "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH, //EPSG:9094
+ "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
+ "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
+ "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
+ "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9036
+ "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
+ "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH, //EPSG:9040
+ "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH, //EPSG:9084
+ "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH, //EPSG:9085
+ "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH, //EPSG:9086
+ "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH, //EPSG:9087
+ "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH, //EPSG:9080
+ "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH, //EPSG:9081
+ "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH, //EPSG:9082
+ "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH, //EPSG:9083
+ "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
+ "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9096
+ "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9093
+ "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9030
+ "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
+ "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
+ "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH, //EPSG:9031
+ "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
+ "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9038
+ "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9033
+ "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9062
+ "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9042
+ "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9039
+ "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9034
+ "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9063
+ "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9043
+ "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH, //EPSG:9097
+ "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH, //EPSG:9098
+ "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
+ "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
+ "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
+ "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
+ "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
+ "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
+ "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
+ "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
+});
+
+//unit abbreviations supported by PROJ.4
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
+ "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
+ "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
+ "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
+ "kmi": OpenLayers.INCHES_PER_UNIT["nmi"], //International Nautical Mile
+ "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
+ "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"], //International Chain
+ "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
+ "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
+ "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
+ "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
+ "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
+ "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"], //U.S. Surveyor's Statute Mile
+ "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"], //Indian Yard
+ "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"], //Indian Foot
+ "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH //Indian Chain
+});
+
+/**
+ * Constant: DOTS_PER_INCH
+ * {Integer} 72 (A sensible default)
+ */
+OpenLayers.DOTS_PER_INCH = 72;
+
+/**
+ * Function: normalizeScale
+ *
+ * Parameters:
+ * scale - {float}
+ *
+ * Returns:
+ * {Float} A normalized scale value, in 1 / X format.
+ * This means that if a value less than one ( already 1/x) is passed
+ * in, it just returns scale directly. Otherwise, it returns
+ * 1 / scale
+ */
+OpenLayers.Util.normalizeScale = function (scale) {
+ var normScale = (scale > 1.0) ? (1.0 / scale)
+ : scale;
+ return normScale;
+};
+
+/**
+ * Function: getResolutionFromScale
+ *
+ * Parameters:
+ * scale - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding resolution given passed-in scale and unit
+ * parameters. If the given scale is falsey, the returned resolution will
+ * be undefined.
+ */
+OpenLayers.Util.getResolutionFromScale = function (scale, units) {
+ var resolution;
+ if (scale) {
+ if (units == null) {
+ units = "degrees";
+ }
+ var normScale = OpenLayers.Util.normalizeScale(scale);
+ resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
+ * OpenLayers.DOTS_PER_INCH);
+ }
+ return resolution;
+};
+
+/**
+ * Function: getScaleFromResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding scale given passed-in resolution and unit
+ * parameters.
+ */
+OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
+
+ if (units == null) {
+ units = "degrees";
+ }
+
+ var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
+ OpenLayers.DOTS_PER_INCH;
+ return scale;
+};
+
+/**
+ * Function: pagePosition
+ * Calculates the position of an element on the page (see
+ * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
+ *
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Parameters:
+ * forElement - {DOMElement}
+ *
+ * Returns:
+ * {Array} two item array, Left value then Top value.
+ */
+OpenLayers.Util.pagePosition = function(forElement) {
+ // NOTE: If element is hidden (display none or disconnected or any the
+ // ancestors are hidden) we get (0,0) by default but we still do the
+ // accumulation of scroll position.
+
+ var pos = [0, 0];
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ if (!forElement || forElement == window || forElement == viewportElement) {
+ // viewport is always at 0,0 as that defined the coordinate system for
+ // this function - this avoids special case checks in the code below
+ return pos;
+ }
+
+ // Gecko browsers normally use getBoxObjectFor to calculate the position.
+ // When invoked for an element with an implicit absolute position though it
+ // can be off by one. Therefore the recursive implementation is used in
+ // those (relatively rare) cases.
+ var BUGGY_GECKO_BOX_OBJECT =
+ OpenLayers.IS_GECKO && document.getBoxObjectFor &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
+ (forElement.style.top == '' || forElement.style.left == '');
+
+ var parent = null;
+ var box;
+
+ if (forElement.getBoundingClientRect) { // IE
+ box = forElement.getBoundingClientRect();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+
+ pos[0] = box.left + scrollLeft;
+ pos[1] = box.top + scrollTop;
+
+ } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
+ // Gecko ignores the scroll values for ancestors, up to 1.9. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
+
+ box = document.getBoxObjectFor(forElement);
+ var vpBox = document.getBoxObjectFor(viewportElement);
+ pos[0] = box.screenX - vpBox.screenX;
+ pos[1] = box.screenY - vpBox.screenY;
+
+ } else { // safari/opera
+ pos[0] = forElement.offsetLeft;
+ pos[1] = forElement.offsetTop;
+ parent = forElement.offsetParent;
+ if (parent != forElement) {
+ while (parent) {
+ pos[0] += parent.offsetLeft;
+ pos[1] += parent.offsetTop;
+ parent = parent.offsetParent;
+ }
+ }
+
+ var browser = OpenLayers.BROWSER_NAME;
+
+ // opera & (safari absolute) incorrectly account for body offsetTop
+ if (browser == "opera" || (browser == "safari" &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
+ pos[1] -= document.body.offsetTop;
+ }
+
+ // accumulate the scroll positions for everything but the body element
+ parent = forElement.offsetParent;
+ while (parent && parent != document.body) {
+ pos[0] -= parent.scrollLeft;
+ // see https://bugs.opera.com/show_bug.cgi?id=249965
+ if (browser != "opera" || parent.tagName != 'TR') {
+ pos[1] -= parent.scrollTop;
+ }
+ parent = parent.offsetParent;
+ }
+ }
+
+ return pos;
+};
+
+/**
+ * Function: getViewportElement
+ * Returns die viewport element of the document. The viewport element is
+ * usually document.documentElement, except in IE,where it is either
+ * document.body or document.documentElement, depending on the document's
+ * compatibility mode (see
+ * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+OpenLayers.Util.getViewportElement = function() {
+ var viewportElement = arguments.callee.viewportElement;
+ if (viewportElement == undefined) {
+ viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
+ document.compatMode != 'CSS1Compat') ? document.body :
+ document.documentElement;
+ arguments.callee.viewportElement = viewportElement;
+ }
+ return viewportElement;
+};
+
+/**
+ * Function: isEquivalentUrl
+ * Test two URLs for equivalence.
+ *
+ * Setting 'ignoreCase' allows for case-independent comparison.
+ *
+ * Comparison is based on:
+ * - Protocol
+ * - Host (evaluated without the port)
+ * - Port (set 'ignorePort80' to ignore "80" values)
+ * - Hash ( set 'ignoreHash' to disable)
+ * - Pathname (for relative <-> absolute comparison)
+ * - Arguments (so they can be out of order)
+ *
+ * Parameters:
+ * url1 - {String}
+ * url2 - {String}
+ * options - {Object} Allows for customization of comparison:
+ * 'ignoreCase' - Default is True
+ * 'ignorePort80' - Default is True
+ * 'ignoreHash' - Default is True
+ *
+ * Returns:
+ * {Boolean} Whether or not the two URLs are equivalent
+ */
+OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
+ options = options || {};
+
+ OpenLayers.Util.applyDefaults(options, {
+ ignoreCase: true,
+ ignorePort80: true,
+ ignoreHash: true,
+ splitArgs: false
+ });
+
+ var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
+ var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
+
+ //compare all keys except for "args" (treated below)
+ for(var key in urlObj1) {
+ if(key !== "args") {
+ if(urlObj1[key] != urlObj2[key]) {
+ return false;
+ }
+ }
+ }
+
+ // compare search args - irrespective of order
+ for(var key in urlObj1.args) {
+ if(urlObj1.args[key] != urlObj2.args[key]) {
+ return false;
+ }
+ delete urlObj2.args[key];
+ }
+ // urlObj2 shouldn't have any args left
+ for(var key in urlObj2.args) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Function: createUrlObject
+ *
+ * Parameters:
+ * url - {String}
+ * options - {Object} A hash of options.
+ *
+ * Valid options:
+ * ignoreCase - {Boolean} lowercase url,
+ * ignorePort80 - {Boolean} don't include explicit port if port is 80,
+ * ignoreHash - {Boolean} Don't include part of url after the hash (#).
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object with separate url, a, port, host, and args parsed out
+ * and ready for comparison
+ */
+OpenLayers.Util.createUrlObject = function(url, options) {
+ options = options || {};
+
+ // deal with relative urls first
+ if(!(/^\w+:\/\//).test(url)) {
+ var loc = window.location;
+ var port = loc.port ? ":" + loc.port : "";
+ var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
+ if(url.indexOf("/") === 0) {
+ // full pathname
+ url = fullUrl + url;
+ } else {
+ // relative to current path
+ var parts = loc.pathname.split("/");
+ parts.pop();
+ url = fullUrl + parts.join("/") + "/" + url;
+ }
+ }
+
+ if (options.ignoreCase) {
+ url = url.toLowerCase();
+ }
+
+ var a = document.createElement('a');
+ a.href = url;
+
+ var urlObject = {};
+
+ //host (without port)
+ urlObject.host = a.host.split(":").shift();
+
+ //protocol
+ urlObject.protocol = a.protocol;
+
+ //port (get uniform browser behavior with port 80 here)
+ if(options.ignorePort80) {
+ urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
+ } else {
+ urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
+ }
+
+ //hash
+ urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;
+
+ //args
+ var queryString = a.search;
+ if (!queryString) {
+ var qMark = url.indexOf("?");
+ queryString = (qMark != -1) ? url.substr(qMark) : "";
+ }
+ urlObject.args = OpenLayers.Util.getParameters(queryString,
+ {splitArgs: options.splitArgs});
+
+ // pathname
+ //
+ // This is a workaround for Internet Explorer where
+ // window.location.pathname has a leading "/", but
+ // a.pathname has no leading "/".
+ urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
+
+ return urlObject;
+};
+
+/**
+ * Function: removeTail
+ * Takes a url and removes everything after the ? and #
+ *
+ * Parameters:
+ * url - {String} The url to process
+ *
+ * Returns:
+ * {String} The string with all queryString and Hash removed
+ */
+OpenLayers.Util.removeTail = function(url) {
+ var head = null;
+
+ var qMark = url.indexOf("?");
+ var hashMark = url.indexOf("#");
+
+ if (qMark == -1) {
+ head = (hashMark != -1) ? url.substr(0,hashMark) : url;
+ } else {
+ head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark))
+ : url.substr(0, qMark);
+ }
+ return head;
+};
+
+/**
+ * Constant: IS_GECKO
+ * {Boolean} True if the userAgent reports the browser to use the Gecko engine
+ */
+OpenLayers.IS_GECKO = (function() {
+ var ua = navigator.userAgent.toLowerCase();
+ return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
+})();
+
+/**
+ * Constant: CANVAS_SUPPORTED
+ * {Boolean} True if canvas 2d is supported.
+ */
+OpenLayers.CANVAS_SUPPORTED = (function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+})();
+
+/**
+ * Constant: BROWSER_NAME
+ * {String}
+ * A substring of the navigator.userAgent property. Depending on the userAgent
+ * property, this will be the empty string or one of the following:
+ * * "opera" -- Opera
+ * * "msie" -- Internet Explorer
+ * * "safari" -- Safari
+ * * "firefox" -- Firefox
+ * * "mozilla" -- Mozilla
+ */
+OpenLayers.BROWSER_NAME = (function() {
+ var name = "";
+ var ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf("opera") != -1) {
+ name = "opera";
+ } else if (ua.indexOf("msie") != -1) {
+ name = "msie";
+ } else if (ua.indexOf("safari") != -1) {
+ name = "safari";
+ } else if (ua.indexOf("mozilla") != -1) {
+ if (ua.indexOf("firefox") != -1) {
+ name = "firefox";
+ } else {
+ name = "mozilla";
+ }
+ }
+ return name;
+})();
+
+/**
+ * Function: getBrowserName
+ *
+ * Returns:
+ * {String} A string which specifies which is the current
+ * browser in which we are running.
+ *
+ * Currently-supported browser detection and codes:
+ * * 'opera' -- Opera
+ * * 'msie' -- Internet Explorer
+ * * 'safari' -- Safari
+ * * 'firefox' -- Firefox
+ * * 'mozilla' -- Mozilla
+ *
+ * If we are unable to property identify the browser, we
+ * return an empty string.
+ */
+OpenLayers.Util.getBrowserName = function() {
+ return OpenLayers.BROWSER_NAME;
+};
+
+/**
+ * Method: getRenderedDimensions
+ * Renders the contentHTML offscreen to determine actual dimensions for
+ * popup sizing. As we need layout to determine dimensions the content
+ * is rendered -9999px to the left and absolute to ensure the
+ * scrollbars do not flicker
+ *
+ * Parameters:
+ * contentHTML
+ * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is
+ * specified, we fix that dimension of the div to be measured. This is
+ * useful in the case where we have a limit in one dimension and must
+ * therefore meaure the flow in the other dimension.
+ * options - {Object}
+ *
+ * Allowed Options:
+ * displayClass - {String} Optional parameter. A CSS class name(s) string
+ * to provide the CSS context of the rendered content.
+ * containerElement - {DOMElement} Optional parameter. Insert the HTML to
+ * this node instead of the body root when calculating dimensions.
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
+
+ var w, h;
+
+ // create temp container div with restricted size
+ var container = document.createElement("div");
+ container.style.visibility = "hidden";
+
+ var containerElement = (options && options.containerElement)
+ ? options.containerElement : document.body;
+
+ // Opera and IE7 can't handle a node with position:aboslute if it inherits
+ // position:absolute from a parent.
+ var parentHasPositionAbsolute = false;
+ var superContainer = null;
+ var parent = containerElement;
+ while (parent && parent.tagName.toLowerCase()!="body") {
+ var parentPosition = OpenLayers.Element.getStyle(parent, "position");
+ if(parentPosition == "absolute") {
+ parentHasPositionAbsolute = true;
+ break;
+ } else if (parentPosition && parentPosition != "static") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+ if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 ||
+ containerElement.clientWidth === 0) ){
+ superContainer = document.createElement("div");
+ superContainer.style.visibility = "hidden";
+ superContainer.style.position = "absolute";
+ superContainer.style.overflow = "visible";
+ superContainer.style.width = document.body.clientWidth + "px";
+ superContainer.style.height = document.body.clientHeight + "px";
+ superContainer.appendChild(container);
+ }
+ container.style.position = "absolute";
+
+ //fix a dimension, if specified.
+ if (size) {
+ if (size.w) {
+ w = size.w;
+ container.style.width = w + "px";
+ } else if (size.h) {
+ h = size.h;
+ container.style.height = h + "px";
+ }
+ }
+
+ //add css classes, if specified
+ if (options && options.displayClass) {
+ container.className = options.displayClass;
+ }
+
+ // create temp content div and assign content
+ var content = document.createElement("div");
+ content.innerHTML = contentHTML;
+
+ // we need overflow visible when calculating the size
+ content.style.overflow = "visible";
+ if (content.childNodes) {
+ for (var i=0, l=content.childNodes.length; i<l; i++) {
+ if (!content.childNodes[i].style) continue;
+ content.childNodes[i].style.overflow = "visible";
+ }
+ }
+
+ // add content to restricted container
+ container.appendChild(content);
+
+ // append container to body for rendering
+ if (superContainer) {
+ containerElement.appendChild(superContainer);
+ } else {
+ containerElement.appendChild(container);
+ }
+
+ // calculate scroll width of content and add corners and shadow width
+ if (!w) {
+ w = parseInt(content.scrollWidth);
+
+ // update container width to allow height to adjust
+ container.style.width = w + "px";
+ }
+ // capture height and add shadow and corner image widths
+ if (!h) {
+ h = parseInt(content.scrollHeight);
+ }
+
+ // remove elements
+ container.removeChild(content);
+ if (superContainer) {
+ superContainer.removeChild(container);
+ containerElement.removeChild(superContainer);
+ } else {
+ containerElement.removeChild(container);
+ }
+
+ return new OpenLayers.Size(w, h);
+};
+
+/**
+ * APIFunction: getScrollbarWidth
+ * This function has been modified by the OpenLayers from the original version,
+ * written by Matthew Eernisse and released under the Apache 2
+ * license here:
+ *
+ * http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
+ *
+ * It has been modified simply to cache its value, since it is physically
+ * impossible that this code could ever run in more than one browser at
+ * once.
+ *
+ * Returns:
+ * {Integer}
+ */
+OpenLayers.Util.getScrollbarWidth = function() {
+
+ var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+
+ if (scrollbarWidth == null) {
+ var scr = null;
+ var inn = null;
+ var wNoScroll = 0;
+ var wScroll = 0;
+
+ // Outer scrolling div
+ scr = document.createElement('div');
+ scr.style.position = 'absolute';
+ scr.style.top = '-1000px';
+ scr.style.left = '-1000px';
+ scr.style.width = '100px';
+ scr.style.height = '50px';
+ // Start with no scrollbar
+ scr.style.overflow = 'hidden';
+
+ // Inner content div
+ inn = document.createElement('div');
+ inn.style.width = '100%';
+ inn.style.height = '200px';
+
+ // Put the inner div in the scrolling div
+ scr.appendChild(inn);
+ // Append the scrolling div to the doc
+ document.body.appendChild(scr);
+
+ // Width of the inner div sans scrollbar
+ wNoScroll = inn.offsetWidth;
+
+ // Add the scrollbar
+ scr.style.overflow = 'scroll';
+ // Width of the inner div width scrollbar
+ wScroll = inn.offsetWidth;
+
+ // Remove the scrolling div from the doc
+ document.body.removeChild(document.body.lastChild);
+
+ // Pixel width of the scroller
+ OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
+ scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+ }
+
+ return scrollbarWidth;
+};
+
+/**
+ * APIFunction: getFormattedLonLat
+ * This function will return latitude or longitude value formatted as
+ *
+ * Parameters:
+ * coordinate - {Float} the coordinate value to be formatted
+ * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
+ * to be formatted (default = lat)
+ * dmsOption - {String} specify the precision of the output can be one of:
+ * 'dms' show degrees minutes and seconds
+ * 'dm' show only degrees and minutes
+ * 'd' show only degrees
+ *
+ * Returns:
+ * {String} the coordinate value formatted as a string
+ */
+OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
+ if (!dmsOption) {
+ dmsOption = 'dms'; //default to show degree, minutes, seconds
+ }
+
+ coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
+
+ var abscoordinate = Math.abs(coordinate);
+ var coordinatedegrees = Math.floor(abscoordinate);
+
+ var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
+ var tempcoordinateminutes = coordinateminutes;
+ coordinateminutes = Math.floor(coordinateminutes);
+ var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
+ coordinateseconds = Math.round(coordinateseconds*10);
+ coordinateseconds /= 10;
+
+ if( coordinateseconds >= 60) {
+ coordinateseconds -= 60;
+ coordinateminutes += 1;
+ if( coordinateminutes >= 60) {
+ coordinateminutes -= 60;
+ coordinatedegrees += 1;
+ }
+ }
+
+ if( coordinatedegrees < 10 ) {
+ coordinatedegrees = "0" + coordinatedegrees;
+ }
+ var str = coordinatedegrees + "\u00B0";
+
+ if (dmsOption.indexOf('dm') >= 0) {
+ if( coordinateminutes < 10 ) {
+ coordinateminutes = "0" + coordinateminutes;
+ }
+ str += coordinateminutes + "'";
+
+ if (dmsOption.indexOf('dms') >= 0) {
+ if( coordinateseconds < 10 ) {
+ coordinateseconds = "0" + coordinateseconds;
+ }
+ str += coordinateseconds + '"';
+ }
+ }
+
+ if (axis == "lon") {
+ str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
+ } else {
+ str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
+ }
+ return str;
+};
+
+/* ======================================================================
+ OpenLayers/Format.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats. Subclasses
+ * of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+
+ /**
+ * Property: options
+ * {Object} A reference to options passed to the constructor.
+ */
+ options: null,
+
+ /**
+ * APIProperty: externalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The externalProjection is the projection used by
+ * the content which is passed into read or which comes out of write.
+ * In order to reproject, a projection transformation function for the
+ * specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ externalProjection: null,
+
+ /**
+ * APIProperty: internalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The internalProjection is the projection used by
+ * the geometries which are returned by read or which are passed into
+ * write. In order to reproject, a projection transformation function
+ * for the specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ internalProjection: null,
+
+ /**
+ * APIProperty: data
+ * {Object} When <keepData> is true, this is the parsed string sent to
+ * <read>.
+ */
+ data: null,
+
+ /**
+ * APIProperty: keepData
+ * {Object} Maintain a reference (<data>) to the most recently read data.
+ * Default is false.
+ */
+ keepData: false,
+
+ /**
+ * Constructor: OpenLayers.Format
+ * Instances of this class are not useful. See one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * format
+ *
+ * Valid options:
+ * keepData - {Boolean} If true, upon <read>, the data property will be
+ * set to the parsed object (e.g. the json or xml object).
+ *
+ * Returns:
+ * An instance of OpenLayers.Format
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * Method: read
+ * Read data from a string, and return an object whose type depends on the
+ * subclass.
+ *
+ * Parameters:
+ * data - {string} Data to read/parse.
+ *
+ * Returns:
+ * Depends on the subclass
+ */
+ read: function(data) {
+ throw new Error('Read not implemented.');
+ },
+
+ /**
+ * Method: write
+ * Accept an object, and return a string.
+ *
+ * Parameters:
+ * object - {Object} Object to be serialized
+ *
+ * Returns:
+ * {String} A string representation of the object.
+ */
+ write: function(object) {
+ throw new Error('Write not implemented.');
+ },
+
+ CLASS_NAME: "OpenLayers.Format"
+});
+/* ======================================================================
+ OpenLayers/Format/CSWGetRecords.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetRecords
+ * Default version is 2.0.2.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A CSWGetRecords format of the given version.
+ */
+OpenLayers.Format.CSWGetRecords = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.CSWGetRecords.DEFAULTS
+ );
+ var cls = OpenLayers.Format.CSWGetRecords["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported CSWGetRecords version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: DEFAULTS
+ * {Object} Default properties for the CSWGetRecords format.
+ */
+OpenLayers.Format.CSWGetRecords.DEFAULTS = {
+ "version": "2.0.2"
+};
+/* ======================================================================
+ OpenLayers/Control.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Control
+ * Controls affect the display or behavior of the map. They allow everything
+ * from panning and zooming to displaying a scale indicator. Controls by
+ * default are added to the map they are contained within however it is
+ * possible to add a control to an external div by passing the div in the
+ * options parameter.
+ *
+ * Example:
+ * The following example shows how to add many of the common controls
+ * to a map.
+ *
+ * > var map = new OpenLayers.Map('map', { controls: [] });
+ * >
+ * > map.addControl(new OpenLayers.Control.PanZoomBar());
+ * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
+ * > map.addControl(new OpenLayers.Control.Permalink());
+ * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
+ * > map.addControl(new OpenLayers.Control.MousePosition());
+ * > map.addControl(new OpenLayers.Control.OverviewMap());
+ * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
+ *
+ * The next code fragment is a quick example of how to intercept
+ * shift-mouse click to display the extent of the bounding box
+ * dragged out by the user. Usually controls are not created
+ * in exactly this manner. See the source for a more complete
+ * example:
+ *
+ * > var control = new OpenLayers.Control();
+ * > OpenLayers.Util.extend(control, {
+ * > draw: function () {
+ * > // this Handler.Box will intercept the shift-mousedown
+ * > // before Control.MouseDefault gets to see it
+ * > this.box = new OpenLayers.Handler.Box( control,
+ * > {"done": this.notice},
+ * > {keyMask: OpenLayers.Handler.MOD_SHIFT});
+ * > this.box.activate();
+ * > },
+ * >
+ * > notice: function (bounds) {
+ * > OpenLayers.Console.userError(bounds);
+ * > }
+ * > });
+ * > map.addControl(control);
+ *
+ */
+OpenLayers.Control = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in the addControl() function in
+ * OpenLayers.Map
+ */
+ map: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement} The element that contains the control, if not present the
+ * control is placed inside the map.
+ */
+ div: null,
+
+ /**
+ * APIProperty: type
+ * {Number} Controls can have a 'type'. The type determines the type of
+ * interactions which are possible with them when they are placed in an
+ * <OpenLayers.Control.Panel>.
+ */
+ type: null,
+
+ /**
+ * Property: allowSelection
+ * {Boolean} By default, controls do not allow selection, because
+ * it may interfere with map dragging. If this is true, OpenLayers
+ * will not prevent selection of the control.
+ * Default is false.
+ */
+ allowSelection: false,
+
+ /**
+ * Property: displayClass
+ * {string} This property is used for CSS related to the drawing of the
+ * Control.
+ */
+ displayClass: "",
+
+ /**
+ * APIProperty: title
+ * {string} This property is used for showing a tooltip over the
+ * Control.
+ */
+ title: "",
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * false.
+ */
+ autoActivate: false,
+
+ /**
+ * APIProperty: active
+ * {Boolean} The control is active (read-only). Use <activate> and
+ * <deactivate> to change control state.
+ */
+ active: null,
+
+ /**
+ * Property: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+ handlerOptions: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler>} null
+ */
+ handler: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to control.events.object (a reference
+ * to the control).
+ * element - {DOMElement} A reference to control.events.element (which
+ * will be null unless documented otherwise).
+ *
+ * Supported map event types:
+ * activate - Triggered when activated.
+ * deactivate - Triggered when deactivated.
+ */
+ events: null,
+
+ /**
+ * Constructor: OpenLayers.Control
+ * Create an OpenLayers Control. The options passed as a parameter
+ * directly extend the control. For example passing the following:
+ *
+ * > var control = new OpenLayers.Control({div: myDiv});
+ *
+ * Overrides the default div attribute value of null.
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function (options) {
+ // We do this before the extend so that instances can override
+ // className in options.
+ this.displayClass =
+ this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ if (this.id == null) {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function () {
+ if(this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.events = null;
+ }
+ this.eventListeners = null;
+
+ // eliminate circular references
+ if (this.handler) {
+ this.handler.destroy();
+ this.handler = null;
+ }
+ if(this.handlers) {
+ for(var key in this.handlers) {
+ if(this.handlers.hasOwnProperty(key) &&
+ typeof this.handlers[key].destroy == "function") {
+ this.handlers[key].destroy();
+ }
+ }
+ this.handlers = null;
+ }
+ if (this.map) {
+ this.map.removeControl(this);
+ this.map = null;
+ }
+ this.div = null;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.map = map;
+ if (this.handler) {
+ this.handler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: draw
+ * The draw method is called when the control is ready to be displayed
+ * on the page. If a div has not been created one is created. Controls
+ * with a visual component will almost always want to override this method
+ * to customize the look of control.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The top-left pixel position of the control
+ * or null.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function (px) {
+ if (this.div == null) {
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.className = this.displayClass;
+ if (!this.allowSelection) {
+ this.div.className += " olControlNoSelect";
+ this.div.setAttribute("unselectable", "on", 0);
+ this.div.onselectstart = OpenLayers.Function.False;
+ }
+ if (this.title != "") {
+ this.div.title = this.title;
+ }
+ }
+ if (px != null) {
+ this.position = px.clone();
+ }
+ this.moveTo(this.position);
+ return this.div;
+ },
+
+ /**
+ * Method: moveTo
+ * Sets the left and top style attributes to the passed in pixel
+ * coordinates.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Explicitly activates a control and it's associated
+ * handler if one has been set. Controls can be
+ * deactivated by calling the deactivate() method.
+ *
+ * Returns:
+ * {Boolean} True if the control was successfully activated or
+ * false if the control was already active.
+ */
+ activate: function () {
+ if (this.active) {
+ return false;
+ }
+ if (this.handler) {
+ this.handler.activate();
+ }
+ this.active = true;
+ if(this.map) {
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("activate");
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates a control and it's associated handler if any. The exact
+ * effect of this depends on the control itself.
+ *
+ * Returns:
+ * {Boolean} True if the control was effectively deactivated or false
+ * if the control was already inactive.
+ */
+ deactivate: function () {
+ if (this.active) {
+ if (this.handler) {
+ this.handler.deactivate();
+ }
+ this.active = false;
+ if(this.map) {
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("deactivate");
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Control"
+});
+
+/**
+ * Constant: OpenLayers.Control.TYPE_BUTTON
+ */
+OpenLayers.Control.TYPE_BUTTON = 1;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOGGLE
+ */
+OpenLayers.Control.TYPE_TOGGLE = 2;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOOL
+ */
+OpenLayers.Control.TYPE_TOOL = 3;
+/* ======================================================================
+ OpenLayers/Events.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Event
+ * Utility functions for event handling.
+ */
+OpenLayers.Event = {
+
+ /**
+ * Property: observers
+ * {Object} A hashtable cache of the event observers. Keyed by
+ * element._eventCacheID
+ */
+ observers: false,
+
+ /**
+ * Constant: KEY_SPACE
+ * {int}
+ */
+ KEY_SPACE: 32,
+
+ /**
+ * Constant: KEY_BACKSPACE
+ * {int}
+ */
+ KEY_BACKSPACE: 8,
+
+ /**
+ * Constant: KEY_TAB
+ * {int}
+ */
+ KEY_TAB: 9,
+
+ /**
+ * Constant: KEY_RETURN
+ * {int}
+ */
+ KEY_RETURN: 13,
+
+ /**
+ * Constant: KEY_ESC
+ * {int}
+ */
+ KEY_ESC: 27,
+
+ /**
+ * Constant: KEY_LEFT
+ * {int}
+ */
+ KEY_LEFT: 37,
+
+ /**
+ * Constant: KEY_UP
+ * {int}
+ */
+ KEY_UP: 38,
+
+ /**
+ * Constant: KEY_RIGHT
+ * {int}
+ */
+ KEY_RIGHT: 39,
+
+ /**
+ * Constant: KEY_DOWN
+ * {int}
+ */
+ KEY_DOWN: 40,
+
+ /**
+ * Constant: KEY_DELETE
+ * {int}
+ */
+ KEY_DELETE: 46,
+
+
+ /**
+ * Method: element
+ * Cross browser event element detection.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {DOMElement} The element that caused the event
+ */
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ /**
+ * Method: isSingleTouch
+ * Determine whether event was caused by a single touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isSingleTouch: function(event) {
+ return event.touches && event.touches.length == 1;
+ },
+
+ /**
+ * Method: isMultiTouch
+ * Determine whether event was caused by a multi touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isMultiTouch: function(event) {
+ return event.touches && event.touches.length > 1;
+ },
+
+ /**
+ * Method: isLeftClick
+ * Determine whether event was caused by a left click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ /**
+ * Method: isRightClick
+ * Determine whether event was caused by a right mouse click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isRightClick: function(event) {
+ return (((event.which) && (event.which == 3)) ||
+ ((event.button) && (event.button == 2)));
+ },
+
+ /**
+ * Method: stop
+ * Stops an event from propagating.
+ *
+ * Parameters:
+ * event - {Event}
+ * allowDefault - {Boolean} If true, we stop the event chain but
+ * still allow the default browser behaviour (text selection,
+ * radio-button clicking, etc). Default is false.
+ */
+ stop: function(event, allowDefault) {
+
+ if (!allowDefault) {
+ OpenLayers.Event.preventDefault(event);
+ }
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * Method: preventDefault
+ * Cancels the event if it is cancelable, without stopping further
+ * propagation of the event.
+ *
+ * Parameters:
+ * event - {Event}
+ */
+ preventDefault: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
+ },
+
+ /**
+ * Method: findElement
+ *
+ * Parameters:
+ * event - {Event}
+ * tagName - {String}
+ *
+ * Returns:
+ * {DOMElement} The first node with the given tagName, starting from the
+ * node the event was triggered on and traversing the DOM upwards
+ */
+ findElement: function(event, tagName) {
+ var element = OpenLayers.Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase()))){
+ element = element.parentNode;
+ }
+ return element;
+ },
+
+ /**
+ * Method: observe
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ */
+ observe: function(elementParam, name, observer, useCapture) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent)) {
+ name = 'keydown';
+ }
+
+ //if observers cache has not yet been created, create it
+ if (!this.observers) {
+ this.observers = {};
+ }
+
+ //if not already assigned, make a new unique cache ID
+ if (!element._eventCacheID) {
+ var idPrefix = "eventCacheID_";
+ if (element.id) {
+ idPrefix = element.id + "_" + idPrefix;
+ }
+ element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
+ }
+
+ var cacheID = element._eventCacheID;
+
+ //if there is not yet a hash entry for this element, add one
+ if (!this.observers[cacheID]) {
+ this.observers[cacheID] = [];
+ }
+
+ //add a new observer to this element's list
+ this.observers[cacheID].push({
+ 'element': element,
+ 'name': name,
+ 'observer': observer,
+ 'useCapture': useCapture
+ });
+
+ //add the actual browser event listener
+ if (element.addEventListener) {
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ /**
+ * Method: stopObservingElement
+ * Given the id of an element to stop observing, cycle through the
+ * element's cached observers, calling stopObserving on each one,
+ * skipping those entries which can no longer be removed.
+ *
+ * parameters:
+ * elementParam - {DOMElement || String}
+ */
+ stopObservingElement: function(elementParam) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
+ },
+
+ /**
+ * Method: _removeElementObservers
+ *
+ * Parameters:
+ * elementObservers - {Array(Object)} Array of (element, name,
+ * observer, usecapture) objects,
+ * taken directly from hashtable
+ */
+ _removeElementObservers: function(elementObservers) {
+ if (elementObservers) {
+ for(var i = elementObservers.length-1; i >= 0; i--) {
+ var entry = elementObservers[i];
+ OpenLayers.Event.stopObserving.apply(this, [
+ entry.element, entry.name, entry.observer, entry.useCapture
+ ]);
+ }
+ }
+ },
+
+ /**
+ * Method: stopObserving
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ *
+ * Returns:
+ * {Boolean} Whether or not the event observer was removed
+ */
+ stopObserving: function(elementParam, name, observer, useCapture) {
+ useCapture = useCapture || false;
+
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ if (name == 'keypress') {
+ if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
+ element.detachEvent) {
+ name = 'keydown';
+ }
+ }
+
+ // find element's entry in this.observers cache and remove it
+ var foundEntry = false;
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ if (elementObservers) {
+
+ // find the specific event type in the element's list
+ var i=0;
+ while(!foundEntry && i < elementObservers.length) {
+ var cacheEntry = elementObservers[i];
+
+ if ((cacheEntry.name == name) &&
+ (cacheEntry.observer == observer) &&
+ (cacheEntry.useCapture == useCapture)) {
+
+ elementObservers.splice(i, 1);
+ if (elementObservers.length == 0) {
+ delete OpenLayers.Event.observers[cacheID];
+ }
+ foundEntry = true;
+ break;
+ }
+ i++;
+ }
+ }
+
+ //actually remove the event listener from browser
+ if (foundEntry) {
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element && element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+ return foundEntry;
+ },
+
+ /**
+ * Method: unloadCache
+ * Cycle through all the element entries in the events cache and call
+ * stopObservingElement on each.
+ */
+ unloadCache: function() {
+ // check for OpenLayers.Event before checking for observers, because
+ // OpenLayers.Event may be undefined in IE if no map instance was
+ // created
+ if (OpenLayers.Event && OpenLayers.Event.observers) {
+ for (var cacheID in OpenLayers.Event.observers) {
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ OpenLayers.Event._removeElementObservers.apply(this,
+ [elementObservers]);
+ }
+ OpenLayers.Event.observers = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Event"
+};
+
+/* prevent memory leaks in IE */
+OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
+
+/**
+ * Class: OpenLayers.Events
+ */
+OpenLayers.Events = OpenLayers.Class({
+
+ /**
+ * Constant: BROWSER_EVENTS
+ * {Array(String)} supported events
+ */
+ BROWSER_EVENTS: [
+ "mouseover", "mouseout",
+ "mousedown", "mouseup", "mousemove",
+ "click", "dblclick", "rightclick", "dblrightclick",
+ "resize", "focus", "blur",
+ "touchstart", "touchmove", "touchend",
+ "keydown"
+ ],
+
+ /**
+ * Property: listeners
+ * {Object} Hashtable of Array(Function): events listener functions
+ */
+ listeners: null,
+
+ /**
+ * Property: object
+ * {Object} the code object issuing application events
+ */
+ object: null,
+
+ /**
+ * Property: element
+ * {DOMElement} the DOM element receiving browser events
+ */
+ element: null,
+
+ /**
+ * Property: eventHandler
+ * {Function} bound event handler attached to elements
+ */
+ eventHandler: null,
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean}
+ */
+ fallThrough: null,
+
+ /**
+ * APIProperty: includeXY
+ * {Boolean} Should the .xy property automatically be created for browser
+ * mouse events? In general, this should be false. If it is true, then
+ * mouse events will automatically generate a '.xy' property on the
+ * event object that is passed. (Prior to OpenLayers 2.7, this was true
+ * by default.) Otherwise, you can call the getMousePosition on the
+ * relevant events handler on the object available via the 'evt.object'
+ * property of the evt object. So, for most events, you can call:
+ * function named(evt) {
+ * this.xy = this.object.events.getMousePosition(evt)
+ * }
+ *
+ * This option typically defaults to false for performance reasons:
+ * when creating an events object whose primary purpose is to manage
+ * relatively positioned mouse events within a div, it may make
+ * sense to set it to true.
+ *
+ * This option is also used to control whether the events object caches
+ * offsets. If this is false, it will not: the reason for this is that
+ * it is only expected to be called many times if the includeXY property
+ * is set to true. If you set this to true, you are expected to clear
+ * the offset cache manually (using this.clearMouseCache()) if:
+ * the border of the element changes
+ * the location of the element in the page changes
+ */
+ includeXY: false,
+
+ /**
+ * APIProperty: extensions
+ * {Object} Event extensions registered with this instance. Keys are
+ * event types, values are {OpenLayers.Events.*} extension instances or
+ * {Boolean} for events that an instantiated extension provides in
+ * addition to the one it was created for.
+ *
+ * Extensions create an event in addition to browser events, which usually
+ * fires when a sequence of browser events is completed. Extensions are
+ * automatically instantiated when a listener is registered for an event
+ * provided by an extension.
+ *
+ * Extensions are created in the <OpenLayers.Events> namespace using
+ * <OpenLayers.Class>, and named after the event they provide.
+ * The constructor receives the target <OpenLayers.Events> instance as
+ * argument. Extensions that need to capture browser events before they
+ * propagate can register their listeners events using <register>, with
+ * {extension: true} as 4th argument.
+ *
+ * If an extension creates more than one event, an alias for each event
+ * type should be created and reference the same class. The constructor
+ * should set a reference in the target's extensions registry to itself.
+ *
+ * Below is a minimal extension that provides the "foostart" and "fooend"
+ * event types, which replace the native "click" event type if clicked on
+ * an element with the css class "foo":
+ *
+ * (code)
+ * OpenLayers.Events.foostart = OpenLayers.Class({
+ * initialize: function(target) {
+ * this.target = target;
+ * this.target.register("click", this, this.doStuff, {extension: true});
+ * // only required if extension provides more than one event type
+ * this.target.extensions["foostart"] = true;
+ * this.target.extensions["fooend"] = true;
+ * },
+ * destroy: function() {
+ * var target = this.target;
+ * target.unregister("click", this, this.doStuff);
+ * delete this.target;
+ * // only required if extension provides more than one event type
+ * delete target.extensions["foostart"];
+ * delete target.extensions["fooend"];
+ * },
+ * doStuff: function(evt) {
+ * var propagate = true;
+ * if (OpenLayers.Event.element(evt).className === "foo") {
+ * propagate = false;
+ * var target = this.target;
+ * target.triggerEvent("foostart");
+ * window.setTimeout(function() {
+ * target.triggerEvent("fooend");
+ * }, 1000);
+ * }
+ * return propagate;
+ * }
+ * });
+ * // only required if extension provides more than one event type
+ * OpenLayers.Events.fooend = OpenLayers.Events.foostart;
+ * (end)
+ *
+ */
+ extensions: null,
+
+ /**
+ * Property: extensionCount
+ * {Object} Keys are event types (like in <listeners>), values are the
+ * number of extension listeners for each event type.
+ */
+ extensionCount: null,
+
+ /**
+ * Method: clearMouseListener
+ * A version of <clearMouseCache> that is bound to this instance so that
+ * it can be used with <OpenLayers.Event.observe> and
+ * <OpenLayers.Event.stopObserving>.
+ */
+ clearMouseListener: null,
+
+ /**
+ * Constructor: OpenLayers.Events
+ * Construct an OpenLayers.Events object.
+ *
+ * Parameters:
+ * object - {Object} The js object to which this Events object is being added
+ * element - {DOMElement} A dom element to respond to browser events
+ * eventTypes - {Array(String)} Deprecated. Array of custom application
+ * events. A listener may be registered for any named event, regardless
+ * of the values provided here.
+ * fallThrough - {Boolean} Allow events to fall through after these have
+ * been handled?
+ * options - {Object} Options for the events object.
+ */
+ initialize: function (object, element, eventTypes, fallThrough, options) {
+ OpenLayers.Util.extend(this, options);
+ this.object = object;
+ this.fallThrough = fallThrough;
+ this.listeners = {};
+ this.extensions = {};
+ this.extensionCount = {};
+ this._msTouches = [];
+
+ // if a dom element is specified, add a listeners list
+ // for browser events on the element and register them
+ if (element != null) {
+ this.attachToElement(element);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function () {
+ for (var e in this.extensions) {
+ if (typeof this.extensions[e] !== "boolean") {
+ this.extensions[e].destroy();
+ }
+ }
+ this.extensions = null;
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ if(this.element.hasScrollEvent) {
+ OpenLayers.Event.stopObserving(
+ window, "scroll", this.clearMouseListener
+ );
+ }
+ }
+ this.element = null;
+
+ this.listeners = null;
+ this.object = null;
+ this.fallThrough = null;
+ this.eventHandler = null;
+ },
+
+ /**
+ * APIMethod: addEventType
+ * Deprecated. Any event can be triggered without adding it first.
+ *
+ * Parameters:
+ * eventName - {String}
+ */
+ addEventType: function(eventName) {
+ },
+
+ /**
+ * Method: attachToElement
+ *
+ * Parameters:
+ * element - {HTMLDOMElement} a DOM element to attach browser events to
+ */
+ attachToElement: function (element) {
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ } else {
+ // keep a bound copy of handleBrowserEvent() so that we can
+ // pass the same function to both Event.observe() and .stopObserving()
+ this.eventHandler = OpenLayers.Function.bindAsEventListener(
+ this.handleBrowserEvent, this
+ );
+
+ // to be used with observe and stopObserving
+ this.clearMouseListener = OpenLayers.Function.bind(
+ this.clearMouseCache, this
+ );
+ }
+ this.element = element;
+ var msTouch = !!window.navigator.msMaxTouchPoints;
+ var type;
+ for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
+ type = this.BROWSER_EVENTS[i];
+ // register the event cross-browser
+ OpenLayers.Event.observe(element, type, this.eventHandler
+ );
+ if (msTouch && type.indexOf('touch') === 0) {
+ this.addMsTouchListener(element, type, this.eventHandler);
+ }
+ }
+ // disable dragstart in IE so that mousedown/move/up works normally
+ OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
+ },
+
+ /**
+ * APIMethod: on
+ * Convenience method for registering listeners with a common scope.
+ * Internally, this method calls <register> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // register a single listener for the "loadstart" event
+ * events.on({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", undefined, loadStartListener);
+ *
+ * // register multiple listeners to be called with the same `this` object
+ * events.on({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", object, loadStartListener);
+ * events.register("loadend", object, loadEndListener);
+ * (end)
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ on: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.register(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: register
+ * Register an event on the events object.
+ *
+ * When the event is triggered, the 'func' function will be called, in the
+ * context of 'obj'. Imagine we were to register an event, specifying an
+ * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
+ * context in the callback function will be our Bounds object. This means
+ * that within our callback function, we can access the properties and
+ * methods of the Bounds object through the "this" variable. So our
+ * callback could execute something like:
+ * : leftStr = "Left: " + this.left;
+ *
+ * or
+ *
+ * : centerStr = "Center: " + this.getCenterLonLat();
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ * priority - {Boolean|Object} If true, adds the new listener to the
+ * *front* of the events queue instead of to the end.
+ *
+ * Valid options for priority:
+ * extension - {Boolean} If true, then the event will be registered as
+ * extension event. Extension events are handled before all other
+ * events.
+ */
+ register: function (type, obj, func, priority) {
+ if (type in OpenLayers.Events && !this.extensions[type]) {
+ this.extensions[type] = new OpenLayers.Events[type](this);
+ }
+ if (func != null) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (!listeners) {
+ listeners = [];
+ this.listeners[type] = listeners;
+ this.extensionCount[type] = 0;
+ }
+ var listener = {obj: obj, func: func};
+ if (priority) {
+ listeners.splice(this.extensionCount[type], 0, listener);
+ if (typeof priority === "object" && priority.extension) {
+ this.extensionCount[type]++;
+ }
+ } else {
+ listeners.push(listener);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: registerPriority
+ * Same as register() but adds the new listener to the *front* of the
+ * events queue instead of to the end.
+ *
+ * TODO: get rid of this in 3.0 - Decide whether listeners should be
+ * called in the order they were registered or in reverse order.
+ *
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's
+ * 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ */
+ registerPriority: function (type, obj, func) {
+ this.register(type, obj, func, true);
+ },
+
+ /**
+ * APIMethod: un
+ * Convenience method for unregistering listeners with a common scope.
+ * Internally, this method calls <unregister> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // unregister a single listener for the "loadstart" event
+ * events.un({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", undefined, loadStartListener);
+ *
+ * // unregister multiple listeners with the same `this` object
+ * events.un({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", object, loadStartListener);
+ * events.unregister("loadend", object, loadEndListener);
+ * (end)
+ */
+ un: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.unregister(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: unregister
+ *
+ * Parameters:
+ * type - {String}
+ * obj - {Object} If none specified, defaults to this.object
+ * func - {Function}
+ */
+ unregister: function (type, obj, func) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (listeners != null) {
+ for (var i=0, len=listeners.length; i<len; i++) {
+ if (listeners[i].obj == obj && listeners[i].func == func) {
+ listeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: remove
+ * Remove all listeners for a given event type. If type is not registered,
+ * does nothing.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ remove: function(type) {
+ if (this.listeners[type] != null) {
+ this.listeners[type] = [];
+ }
+ },
+
+ /**
+ * APIMethod: triggerEvent
+ * Trigger a specified registered event.
+ *
+ * Parameters:
+ * type - {String}
+ * evt - {Event || Object} will be passed to the listeners.
+ *
+ * Returns:
+ * {Boolean} The last listener return. If a listener returns false, the
+ * chain of listeners will stop getting called.
+ */
+ triggerEvent: function (type, evt) {
+ var listeners = this.listeners[type];
+
+ // fast path
+ if(!listeners || listeners.length == 0) {
+ return undefined;
+ }
+
+ // prep evt object with object & div references
+ if (evt == null) {
+ evt = {};
+ }
+ evt.object = this.object;
+ evt.element = this.element;
+ if(!evt.type) {
+ evt.type = type;
+ }
+
+ // execute all callbacks registered for specified type
+ // get a clone of the listeners array to
+ // allow for splicing during callbacks
+ listeners = listeners.slice();
+ var continueChain;
+ for (var i=0, len=listeners.length; i<len; i++) {
+ var callback = listeners[i];
+ // bind the context to callback.obj
+ continueChain = callback.func.apply(callback.obj, [evt]);
+
+ if ((continueChain != undefined) && (continueChain == false)) {
+ // if callback returns false, execute no more callbacks.
+ break;
+ }
+ }
+ // don't fall through to other DOM elements
+ if (!this.fallThrough) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ return continueChain;
+ },
+
+ /**
+ * Method: handleBrowserEvent
+ * Basically just a wrapper to the triggerEvent() function, but takes
+ * care to set a property 'xy' on the event with the current mouse
+ * position.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ handleBrowserEvent: function (evt) {
+ var type = evt.type, listeners = this.listeners[type];
+ if(!listeners || listeners.length == 0) {
+ // noone's listening, bail out
+ return;
+ }
+ // add clientX & clientY to all events - corresponds to average x, y
+ var touches = evt.touches;
+ if (touches && touches[0]) {
+ var x = 0;
+ var y = 0;
+ var num = touches.length;
+ var touch;
+ for (var i=0; i<num; ++i) {
+ touch = this.getTouchClientXY(touches[i]);
+ x += touch.clientX;
+ y += touch.clientY;
+ }
+ evt.clientX = x / num;
+ evt.clientY = y / num;
+ }
+ if (this.includeXY) {
+ evt.xy = this.getMousePosition(evt);
+ }
+ this.triggerEvent(type, evt);
+ },
+
+ /**
+ * Method: getTouchClientXY
+ * WebKit has a few bugs for clientX/clientY. This method detects them
+ * and calculate the correct values.
+ *
+ * Parameters:
+ * evt - {Touch} a Touch object from a TouchEvent
+ *
+ * Returns:
+ * {Object} An object with only clientX and clientY properties with the
+ * calculated values.
+ */
+ getTouchClientXY: function (evt) {
+ // olMochWin is to override window, used for testing
+ var win = window.olMockWin || window,
+ winPageX = win.pageXOffset,
+ winPageY = win.pageYOffset,
+ x = evt.clientX,
+ y = evt.clientY;
+
+ if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
+ evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
+ // iOS4 include scroll offset in clientX/Y
+ x = x - winPageX;
+ y = y - winPageY;
+ } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
+ // Some Android browsers have totally bogus values for clientX/Y
+ // when scrolling/zooming a page
+ x = evt.pageX - winPageX;
+ y = evt.pageY - winPageY;
+ }
+
+ evt.olClientX = x;
+ evt.olClientY = y;
+
+ return {
+ clientX: x,
+ clientY: y
+ };
+ },
+
+ /**
+ * APIMethod: clearMouseCache
+ * Clear cached data about the mouse position. This should be called any
+ * time the element that events are registered on changes position
+ * within the page.
+ */
+ clearMouseCache: function() {
+ this.element.scrolls = null;
+ this.element.lefttop = null;
+ this.element.offsets = null;
+ },
+
+ /**
+ * Method: getMousePosition
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
+ * for offsets
+ */
+ getMousePosition: function (evt) {
+ if (!this.includeXY) {
+ this.clearMouseCache();
+ } else if (!this.element.hasScrollEvent) {
+ OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
+ this.element.hasScrollEvent = true;
+ }
+
+ if (!this.element.scrolls) {
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ this.element.scrolls = [
+ window.pageXOffset || viewportElement.scrollLeft,
+ window.pageYOffset || viewportElement.scrollTop
+ ];
+ }
+
+ if (!this.element.lefttop) {
+ this.element.lefttop = [
+ (document.documentElement.clientLeft || 0),
+ (document.documentElement.clientTop || 0)
+ ];
+ }
+
+ if (!this.element.offsets) {
+ this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+ }
+
+ return new OpenLayers.Pixel(
+ (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
+ - this.element.lefttop[0],
+ (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
+ - this.element.lefttop[1]
+ );
+ },
+
+ /**
+ * Method: addMsTouchListener
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListener: function (element, type, handler) {
+ var eventHandler = this.eventHandler;
+ var touches = this._msTouches;
+
+ function msHandler(evt) {
+ handler(OpenLayers.Util.applyDefaults({
+ stopPropagation: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].stopPropagation();
+ }
+ },
+ preventDefault: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].preventDefault();
+ }
+ },
+ type: type
+ }, evt));
+ }
+
+ switch (type) {
+ case 'touchstart':
+ return this.addMsTouchListenerStart(element, type, msHandler);
+ case 'touchend':
+ return this.addMsTouchListenerEnd(element, type, msHandler);
+ case 'touchmove':
+ return this.addMsTouchListenerMove(element, type, msHandler);
+ default:
+ throw 'Unknown touch event type';
+ }
+ },
+
+ /**
+ * Method: addMsTouchListenerStart
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerStart: function(element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ var alreadyInArray = false;
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ alreadyInArray = true;
+ break;
+ }
+ }
+ if (!alreadyInArray) {
+ touches.push(e);
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerDown', cb);
+
+ // Need to also listen for end events to keep the _msTouches list
+ // accurate
+ var internalCb = function(e) {
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+ };
+ OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
+ },
+
+ /**
+ * Method: addMsTouchListenerMove
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerMove: function (element, type, handler) {
+ var touches = this._msTouches;
+ var cb = function(e) {
+
+ //Don't fire touch moves when mouse isn't down
+ if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
+ return;
+ }
+
+ if (touches.length == 1 && touches[0].pageX == e.pageX &&
+ touches[0].pageY == e.pageY) {
+ // don't trigger event when pointer has not moved
+ return;
+ }
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches[i] = e;
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerMove', cb);
+ },
+
+ /**
+ * Method: addMsTouchListenerEnd
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerEnd: function (element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerUp', cb);
+ },
+
+ CLASS_NAME: "OpenLayers.Events"
+});
+/* ======================================================================
+ OpenLayers/Events/buttonclick.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.buttonclick
+ * Extension event type for handling buttons on top of a dom element. This
+ * event type fires "buttonclick" on its <target> when a button was
+ * clicked. Buttons are detected by the "olButton" class.
+ *
+ * This event type makes sure that button clicks do not interfere with other
+ * events that are registered on the same <element>.
+ *
+ * Event types provided by this extension:
+ * - *buttonclick* Triggered when a button is clicked. Listeners receive an
+ * object with a *buttonElement* property referencing the dom element of
+ * the clicked button, and an *buttonXY* property with the click position
+ * relative to the button.
+ */
+OpenLayers.Events.buttonclick = OpenLayers.Class({
+
+ /**
+ * Property: target
+ * {<OpenLayers.Events>} The events instance that the buttonclick event will
+ * be triggered on.
+ */
+ target: null,
+
+ /**
+ * Property: events
+ * {Array} Events to observe and conditionally stop from propagating when
+ * an element with the olButton class (or its olAlphaImg child) is
+ * clicked.
+ */
+ events: [
+ 'mousedown', 'mouseup', 'click', 'dblclick',
+ 'touchstart', 'touchmove', 'touchend', 'keydown'
+ ],
+
+ /**
+ * Property: startRegEx
+ * {RegExp} Regular expression to test Event.type for events that start
+ * a buttonclick sequence.
+ */
+ startRegEx: /^mousedown|touchstart$/,
+
+ /**
+ * Property: cancelRegEx
+ * {RegExp} Regular expression to test Event.type for events that cancel
+ * a buttonclick sequence.
+ */
+ cancelRegEx: /^touchmove$/,
+
+ /**
+ * Property: completeRegEx
+ * {RegExp} Regular expression to test Event.type for events that complete
+ * a buttonclick sequence.
+ */
+ completeRegEx: /^mouseup|touchend$/,
+
+ /**
+ * Property: startEvt
+ * {Event} The event that started the click sequence
+ */
+
+ /**
+ * Constructor: OpenLayers.Events.buttonclick
+ * Construct a buttonclick event type. Applications are not supposed to
+ * create instances of this class - they are created on demand by
+ * <OpenLayers.Events> instances.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance that the buttonclick
+ * event will be triggered on.
+ */
+ initialize: function(target) {
+ this.target = target;
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.register(this.events[i], this, this.buttonClick, {
+ extension: true
+ });
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.unregister(this.events[i], this, this.buttonClick);
+ }
+ delete this.target;
+ },
+
+ /**
+ * Method: getPressedButton
+ * Get the pressed button, if any. Returns undefined if no button
+ * was pressed.
+ *
+ * Arguments:
+ * element - {DOMElement} The event target.
+ *
+ * Returns:
+ * {DOMElement} The button element, or undefined.
+ */
+ getPressedButton: function(element) {
+ var depth = 3, // limit the search depth
+ button;
+ do {
+ if(OpenLayers.Element.hasClass(element, "olButton")) {
+ // hit!
+ button = element;
+ break;
+ }
+ element = element.parentNode;
+ } while(--depth > 0 && element);
+ return button;
+ },
+
+ /**
+ * Method: ignore
+ * Check for event target elements that should be ignored by OpenLayers.
+ *
+ * Parameters:
+ * element - {DOMElement} The event target.
+ */
+ ignore: function(element) {
+ var depth = 3,
+ ignore = false;
+ do {
+ if (element.nodeName.toLowerCase() === 'a') {
+ ignore = true;
+ break;
+ }
+ element = element.parentNode;
+ } while (--depth > 0 && element);
+ return ignore;
+ },
+
+ /**
+ * Method: buttonClick
+ * Check if a button was clicked, and fire the buttonclick event
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonClick: function(evt) {
+ var propagate = true,
+ element = OpenLayers.Event.element(evt);
+ if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
+ // was a button pressed?
+ var button = this.getPressedButton(element);
+ if (button) {
+ if (evt.type === "keydown") {
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_RETURN:
+ case OpenLayers.Event.KEY_SPACE:
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button
+ });
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ break;
+ }
+ } else if (this.startEvt) {
+ if (this.completeRegEx.test(evt.type)) {
+ var pos = OpenLayers.Util.pagePosition(button);
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+ pos[0] = pos[0] - scrollLeft;
+ pos[1] = pos[1] - scrollTop;
+
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button,
+ buttonXY: {
+ x: this.startEvt.clientX - pos[0],
+ y: this.startEvt.clientY - pos[1]
+ }
+ });
+ }
+ if (this.cancelRegEx.test(evt.type)) {
+ delete this.startEvt;
+ }
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ if (this.startRegEx.test(evt.type)) {
+ this.startEvt = evt;
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ } else {
+ propagate = !this.ignore(OpenLayers.Event.element(evt));
+ delete this.startEvt;
+ }
+ }
+ return propagate;
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Util/vendorPrefix.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+OpenLayers.Util = OpenLayers.Util || {};
+/**
+ * Namespace: OpenLayers.Util.vendorPrefix
+ * A collection of utility functions to detect vendor prefixed features
+ */
+OpenLayers.Util.vendorPrefix = (function() {
+ "use strict";
+
+ var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
+ divStyle = document.createElement("div").style,
+ cssCache = {},
+ jsCache = {};
+
+
+ /**
+ * Function: domToCss
+ * Converts a upper camel case DOM style property name to a CSS property
+ * i.e. transformOrigin -> transform-origin
+ * or WebkitTransformOrigin -> -webkit-transform-origin
+ *
+ * Parameters:
+ * prefixedDom - {String} The property to convert
+ *
+ * Returns:
+ * {String} The CSS property
+ */
+ function domToCss(prefixedDom) {
+ if (!prefixedDom) { return null; }
+ return prefixedDom.
+ replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
+ replace(/^ms-/, "-ms-");
+ }
+
+ /**
+ * APIMethod: css
+ * Detect which property is used for a CSS property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) CSS property name
+ *
+ * Returns:
+ * {String} The standard CSS property, prefixed property or null if not
+ * supported
+ */
+ function css(property) {
+ if (cssCache[property] === undefined) {
+ var domProperty = property.
+ replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
+ var prefixedDom = style(domProperty);
+ cssCache[property] = domToCss(prefixedDom);
+ }
+ return cssCache[property];
+ }
+
+ /**
+ * APIMethod: js
+ * Detect which property is used for a JS property/method
+ *
+ * Parameters:
+ * obj - {Object} The object to test on
+ * property - {String} The standard (unprefixed) JS property name
+ *
+ * Returns:
+ * {String} The standard JS property, prefixed property or null if not
+ * supported
+ */
+ function js(obj, property) {
+ if (jsCache[property] === undefined) {
+ var tmpProp,
+ i = 0,
+ l = VENDOR_PREFIXES.length,
+ prefix,
+ isStyleObj = (typeof obj.cssText !== "undefined");
+
+ jsCache[property] = null;
+ for(; i<l; i++) {
+ prefix = VENDOR_PREFIXES[i];
+ if(prefix) {
+ if (!isStyleObj) {
+ // js prefix should be lower-case, while style
+ // properties have upper case on first character
+ prefix = prefix.toLowerCase();
+ }
+ tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
+ } else {
+ tmpProp = property;
+ }
+
+ if(obj[tmpProp] !== undefined) {
+ jsCache[property] = tmpProp;
+ break;
+ }
+ }
+ }
+ return jsCache[property];
+ }
+
+ /**
+ * APIMethod: style
+ * Detect which property is used for a DOM style property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) style property name
+ *
+ * Returns:
+ * {String} The standard style property, prefixed property or null if not
+ * supported
+ */
+ function style(property) {
+ return js(divStyle, property);
+ }
+
+ return {
+ css: css,
+ js: js,
+ style: style,
+
+ // used for testing
+ cssCache: cssCache,
+ jsCache: jsCache
+ };
+}());
+/* ======================================================================
+ OpenLayers/Animation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ */
+
+/**
+ * Namespace: OpenLayers.Animation
+ * A collection of utility functions for executing methods that repaint a
+ * portion of the browser window. These methods take advantage of the
+ * browser's scheduled repaints where requestAnimationFrame is available.
+ */
+OpenLayers.Animation = (function(window) {
+
+ /**
+ * Property: isNative
+ * {Boolean} true if a native requestAnimationFrame function is available
+ */
+ var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
+ var isNative = !!(requestAnimationFrame);
+
+ /**
+ * Function: requestFrame
+ * Schedule a function to be called at the next available animation frame.
+ * Uses the native method where available. Where requestAnimationFrame is
+ * not available, setTimeout will be called with a 16ms delay.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ */
+ var requestFrame = (function() {
+ var request = window[requestAnimationFrame] ||
+ function(callback, element) {
+ window.setTimeout(callback, 16);
+ };
+ // bind to window to avoid illegal invocation of native function
+ return function(callback, element) {
+ request.apply(window, [callback, element]);
+ };
+ })();
+
+ // private variables for animation loops
+ var counter = 0;
+ var loops = {};
+
+ /**
+ * Function: start
+ * Executes a method with <requestFrame> in series for some
+ * duration.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * duration - {Number} Optional duration for the loop. If not provided, the
+ * animation loop will execute indefinitely.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ *
+ * Returns:
+ * {Number} Identifier for the animation loop. Used to stop animations with
+ * <stop>.
+ */
+ function start(callback, duration, element) {
+ duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
+ var id = ++counter;
+ var start = +new Date;
+ loops[id] = function() {
+ if (loops[id] && +new Date - start <= duration) {
+ callback();
+ if (loops[id]) {
+ requestFrame(loops[id], element);
+ }
+ } else {
+ delete loops[id];
+ }
+ };
+ requestFrame(loops[id], element);
+ return id;
+ }
+
+ /**
+ * Function: stop
+ * Terminates an animation loop started with <start>.
+ *
+ * Parameters:
+ * id - {Number} Identifier returned from <start>.
+ */
+ function stop(id) {
+ delete loops[id];
+ }
+
+ return {
+ isNative: isNative,
+ requestFrame: requestFrame,
+ start: start,
+ stop: stop
+ };
+
+})(window);
+/* ======================================================================
+ OpenLayers/Tween.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+/**
+ * Namespace: OpenLayers.Tween
+ */
+OpenLayers.Tween = OpenLayers.Class({
+
+ /**
+ * APIProperty: easing
+ * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
+ * Defaultly set to OpenLayers.Easing.Expo.easeOut
+ */
+ easing: null,
+
+ /**
+ * APIProperty: begin
+ * {Object} Values to start the animation with
+ */
+ begin: null,
+
+ /**
+ * APIProperty: finish
+ * {Object} Values to finish the animation with
+ */
+ finish: null,
+
+ /**
+ * APIProperty: duration
+ * {int} duration of the tween (number of steps)
+ */
+ duration: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} An object with start, eachStep and done properties whose values
+ * are functions to be call during the animation. They are passed the
+ * current computed value as argument.
+ */
+ callbacks: null,
+
+ /**
+ * Property: time
+ * {int} Step counter
+ */
+ time: null,
+
+ /**
+ * APIProperty: minFrameRate
+ * {Number} The minimum framerate for animations in frames per second. After
+ * each step, the time spent in the animation is compared to the calculated
+ * time at this frame rate. If the animation runs longer than the calculated
+ * time, the next step is skipped. Default is 30.
+ */
+ minFrameRate: null,
+
+ /**
+ * Property: startTime
+ * {Number} The timestamp of the first execution step. Used for skipping
+ * frames
+ */
+ startTime: null,
+
+ /**
+ * Property: animationId
+ * {int} Loop id returned by OpenLayers.Animation.start
+ */
+ animationId: null,
+
+ /**
+ * Property: playing
+ * {Boolean} Tells if the easing is currently playing
+ */
+ playing: false,
+
+ /**
+ * Constructor: OpenLayers.Tween
+ * Creates a Tween.
+ *
+ * Parameters:
+ * easing - {<OpenLayers.Easing>(Function)} easing function method to use
+ */
+ initialize: function(easing) {
+ this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
+ },
+
+ /**
+ * APIMethod: start
+ * Plays the Tween, and calls the callback method on each step
+ *
+ * Parameters:
+ * begin - {Object} values to start the animation with
+ * finish - {Object} values to finish the animation with
+ * duration - {int} duration of the tween (number of steps)
+ * options - {Object} hash of options (callbacks (start, eachStep, done),
+ * minFrameRate)
+ */
+ start: function(begin, finish, duration, options) {
+ this.playing = true;
+ this.begin = begin;
+ this.finish = finish;
+ this.duration = duration;
+ this.callbacks = options.callbacks;
+ this.minFrameRate = options.minFrameRate || 30;
+ this.time = 0;
+ this.startTime = new Date().getTime();
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ if (this.callbacks && this.callbacks.start) {
+ this.callbacks.start.call(this, this.begin);
+ }
+ this.animationId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(this.play, this)
+ );
+ },
+
+ /**
+ * APIMethod: stop
+ * Stops the Tween, and calls the done callback
+ * Doesn't do anything if animation is already finished
+ */
+ stop: function() {
+ if (!this.playing) {
+ return;
+ }
+
+ if (this.callbacks && this.callbacks.done) {
+ this.callbacks.done.call(this, this.finish);
+ }
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ this.playing = false;
+ },
+
+ /**
+ * Method: play
+ * Calls the appropriate easing method
+ */
+ play: function() {
+ var value = {};
+ for (var i in this.begin) {
+ var b = this.begin[i];
+ var f = this.finish[i];
+ if (b == null || f == null || isNaN(b) || isNaN(f)) {
+ throw new TypeError('invalid value for Tween');
+ }
+
+ var c = f - b;
+ value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
+ }
+ this.time++;
+
+ if (this.callbacks && this.callbacks.eachStep) {
+ // skip frames if frame rate drops below threshold
+ if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
+ this.callbacks.eachStep.call(this, value);
+ }
+ }
+
+ if (this.time > this.duration) {
+ this.stop();
+ }
+ },
+
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Tween"
+});
+
+/**
+ * Namespace: OpenLayers.Easing
+ *
+ * Credits:
+ * Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
+ */
+OpenLayers.Easing = {
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Easing"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Linear
+ */
+OpenLayers.Easing.Linear = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Linear"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Expo
+ */
+OpenLayers.Easing.Expo = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if (t==0) return b;
+ if (t==d) return b+c;
+ if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Expo"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Quad
+ */
+OpenLayers.Easing.Quad = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Quad"
+};
+/* ======================================================================
+ OpenLayers/Projection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Projection
+ * Methods for coordinate transforms between coordinate systems. By default,
+ * OpenLayers ships with the ability to transform coordinates between
+ * geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
+ * coordinate reference systems. See the <transform> method for details
+ * on usage.
+ *
+ * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
+ * library. If the proj4js library is included, the <transform> method
+ * will work between any two coordinate reference systems with proj4js
+ * definitions.
+ *
+ * If the proj4js library is not included, or if you wish to allow transforms
+ * between arbitrary coordinate reference systems, use the <addTransform>
+ * method to register a custom transform method.
+ */
+OpenLayers.Projection = OpenLayers.Class({
+
+ /**
+ * Property: proj
+ * {Object} Proj4js.Proj instance.
+ */
+ proj: null,
+
+ /**
+ * Property: projCode
+ * {String}
+ */
+ projCode: null,
+
+ /**
+ * Property: titleRegEx
+ * {RegExp} regular expression to strip the title from a proj4js definition
+ */
+ titleRegEx: /\+title=[^\+]*/,
+
+ /**
+ * Constructor: OpenLayers.Projection
+ * This class offers several methods for interacting with a wrapped
+ * pro4js projection object.
+ *
+ * Parameters:
+ * projCode - {String} A string identifying the Well Known Identifier for
+ * the projection.
+ * options - {Object} An optional object to set additional properties
+ * on the projection.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} A projection object.
+ */
+ initialize: function(projCode, options) {
+ OpenLayers.Util.extend(this, options);
+ this.projCode = projCode;
+ if (typeof Proj4js == "object") {
+ this.proj = new Proj4js.Proj(projCode);
+ }
+ },
+
+ /**
+ * APIMethod: getCode
+ * Get the string SRS code.
+ *
+ * Returns:
+ * {String} The SRS code.
+ */
+ getCode: function() {
+ return this.proj ? this.proj.srsCode : this.projCode;
+ },
+
+ /**
+ * APIMethod: getUnits
+ * Get the units string for the projection -- returns null if
+ * proj4js is not available.
+ *
+ * Returns:
+ * {String} The units abbreviation.
+ */
+ getUnits: function() {
+ return this.proj ? this.proj.units : null;
+ },
+
+ /**
+ * Method: toString
+ * Convert projection to string (getCode wrapper).
+ *
+ * Returns:
+ * {String} The projection code.
+ */
+ toString: function() {
+ return this.getCode();
+ },
+
+ /**
+ * Method: equals
+ * Test equality of two projection instances. Determines equality based
+ * soley on the projection code.
+ *
+ * Returns:
+ * {Boolean} The two projections are equivalent.
+ */
+ equals: function(projection) {
+ var p = projection, equals = false;
+ if (p) {
+ if (!(p instanceof OpenLayers.Projection)) {
+ p = new OpenLayers.Projection(p);
+ }
+ if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
+ equals = this.proj.defData.replace(this.titleRegEx, "") ==
+ p.proj.defData.replace(this.titleRegEx, "");
+ } else if (p.getCode) {
+ var source = this.getCode(), target = p.getCode();
+ equals = source == target ||
+ !!OpenLayers.Projection.transforms[source] &&
+ OpenLayers.Projection.transforms[source][target] ===
+ OpenLayers.Projection.nullTransform;
+ }
+ }
+ return equals;
+ },
+
+ /* Method: destroy
+ * Destroy projection object.
+ */
+ destroy: function() {
+ delete this.proj;
+ delete this.projCode;
+ },
+
+ CLASS_NAME: "OpenLayers.Projection"
+});
+
+/**
+ * Property: transforms
+ * {Object} Transforms is an object, with from properties, each of which may
+ * have a to property. This allows you to define projections without
+ * requiring support for proj4js to be included.
+ *
+ * This object has keys which correspond to a 'source' projection object. The
+ * keys should be strings, corresponding to the projection.getCode() value.
+ * Each source projection object should have a set of destination projection
+ * keys included in the object.
+ *
+ * Each value in the destination object should be a transformation function,
+ * where the function is expected to be passed an object with a .x and a .y
+ * property. The function should return the object, with the .x and .y
+ * transformed according to the transformation function.
+ *
+ * Note - Properties on this object should not be set directly. To add a
+ * transform method to this object, use the <addTransform> method. For an
+ * example of usage, see the OpenLayers.Layer.SphericalMercator file.
+ */
+OpenLayers.Projection.transforms = {};
+
+/**
+ * APIProperty: defaults
+ * {Object} Defaults for the SRS codes known to OpenLayers (currently
+ * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
+ * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units,
+ * maxExtent (the validity extent for the SRS) and yx (true if this SRS is
+ * known to have a reverse axis order).
+ */
+OpenLayers.Projection.defaults = {
+ "EPSG:4326": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90],
+ yx: true
+ },
+ "CRS:84": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90]
+ },
+ "EPSG:900913": {
+ units: "m",
+ maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
+ }
+};
+
+/**
+ * APIMethod: addTransform
+ * Set a custom transform method between two projections. Use this method in
+ * cases where the proj4js lib is not available or where custom projections
+ * need to be handled.
+ *
+ * Parameters:
+ * from - {String} The code for the source projection
+ * to - {String} the code for the destination projection
+ * method - {Function} A function that takes a point as an argument and
+ * transforms that point from the source to the destination projection
+ * in place. The original point should be modified.
+ */
+OpenLayers.Projection.addTransform = function(from, to, method) {
+ if (method === OpenLayers.Projection.nullTransform) {
+ var defaults = OpenLayers.Projection.defaults[from];
+ if (defaults && !OpenLayers.Projection.defaults[to]) {
+ OpenLayers.Projection.defaults[to] = defaults;
+ }
+ }
+ if(!OpenLayers.Projection.transforms[from]) {
+ OpenLayers.Projection.transforms[from] = {};
+ }
+ OpenLayers.Projection.transforms[from][to] = method;
+};
+
+/**
+ * APIMethod: transform
+ * Transform a point coordinate from one projection to another. Note that
+ * the input point is transformed in place.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
+ * properties representing coordinates in those dimensions.
+ * source - {OpenLayers.Projection} Source map coordinate system
+ * dest - {OpenLayers.Projection} Destination map coordinate system
+ *
+ * Returns:
+ * point - {object} A transformed coordinate. The original point is modified.
+ */
+OpenLayers.Projection.transform = function(point, source, dest) {
+ if (source && dest) {
+ if (!(source instanceof OpenLayers.Projection)) {
+ source = new OpenLayers.Projection(source);
+ }
+ if (!(dest instanceof OpenLayers.Projection)) {
+ dest = new OpenLayers.Projection(dest);
+ }
+ if (source.proj && dest.proj) {
+ point = Proj4js.transform(source.proj, dest.proj, point);
+ } else {
+ var sourceCode = source.getCode();
+ var destCode = dest.getCode();
+ var transforms = OpenLayers.Projection.transforms;
+ if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
+ transforms[sourceCode][destCode](point);
+ }
+ }
+ }
+ return point;
+};
+
+/**
+ * APIFunction: nullTransform
+ * A null transformation - useful for defining projection aliases when
+ * proj4js is not available:
+ *
+ * (code)
+ * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
+ * OpenLayers.Projection.nullTransform);
+ * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
+ * OpenLayers.Projection.nullTransform);
+ * (end)
+ */
+OpenLayers.Projection.nullTransform = function(point) {
+ return point;
+};
+
+/**
+ * Note: Transforms for web mercator <-> geographic
+ * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100.
+ * OpenLayers originally started referring to EPSG:900913 as web mercator.
+ * The EPSG has declared EPSG:3857 to be web mercator.
+ * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
+ * equivalent. See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
+ * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
+ * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
+ * order for EPSG:4326.
+ */
+(function() {
+
+ var pole = 20037508.34;
+
+ function inverseMercator(xy) {
+ xy.x = 180 * xy.x / pole;
+ xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
+ return xy;
+ }
+
+ function forwardMercator(xy) {
+ xy.x = xy.x * pole / 180;
+ var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
+ xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
+ return xy;
+ }
+
+ function map(base, codes) {
+ var add = OpenLayers.Projection.addTransform;
+ var same = OpenLayers.Projection.nullTransform;
+ var i, len, code, other, j;
+ for (i=0, len=codes.length; i<len; ++i) {
+ code = codes[i];
+ add(base, code, forwardMercator);
+ add(code, base, inverseMercator);
+ for (j=i+1; j<len; ++j) {
+ other = codes[j];
+ add(code, other, same);
+ add(other, code, same);
+ }
+ }
+ }
+
+ // list of equivalent codes for web mercator
+ var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
+ geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
+ i;
+ for (i=mercator.length-1; i>=0; --i) {
+ map(mercator[i], geographic);
+ }
+ for (i=geographic.length-1; i>=0; --i) {
+ map(geographic[i], mercator);
+ }
+
+})();
+/* ======================================================================
+ OpenLayers/Map.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Tween.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Map
+ * Instances of OpenLayers.Map are interactive maps embedded in a web page.
+ * Create a new map with the <OpenLayers.Map> constructor.
+ *
+ * On their own maps do not provide much functionality. To extend a map
+ * it's necessary to add controls (<OpenLayers.Control>) and
+ * layers (<OpenLayers.Layer>) to the map.
+ */
+OpenLayers.Map = OpenLayers.Class({
+
+ /**
+ * Constant: Z_INDEX_BASE
+ * {Object} Base z-indexes for different classes of thing
+ */
+ Z_INDEX_BASE: {
+ BaseLayer: 100,
+ Overlay: 325,
+ Feature: 725,
+ Popup: 750,
+ Control: 1000
+ },
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * map.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to map.events.object.
+ * element - {DOMElement} A reference to map.events.element.
+ *
+ * Browser events have the following additional properties:
+ * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
+ * to the the map viewport).
+ *
+ * Supported map event types:
+ * preaddlayer - triggered before a layer has been added. The event
+ * object will include a *layer* property that references the layer
+ * to be added. When a listener returns "false" the adding will be
+ * aborted.
+ * addlayer - triggered after a layer has been added. The event object
+ * will include a *layer* property that references the added layer.
+ * preremovelayer - triggered before a layer has been removed. The event
+ * object will include a *layer* property that references the layer
+ * to be removed. When a listener returns "false" the removal will be
+ * aborted.
+ * removelayer - triggered after a layer has been removed. The event
+ * object will include a *layer* property that references the removed
+ * layer.
+ * changelayer - triggered after a layer name change, order change,
+ * opacity change, params change, visibility change (actual visibility,
+ * not the layer's visibility property) or attribution change (due to
+ * extent change). Listeners will receive an event object with *layer*
+ * and *property* properties. The *layer* property will be a reference
+ * to the changed layer. The *property* property will be a key to the
+ * changed property (name, order, opacity, params, visibility or
+ * attribution).
+ * movestart - triggered after the start of a drag, pan, or zoom. The event
+ * object may include a *zoomChanged* property that tells whether the
+ * zoom has changed.
+ * move - triggered after each drag, pan, or zoom
+ * moveend - triggered after a drag, pan, or zoom completes
+ * zoomend - triggered after a zoom completes
+ * mouseover - triggered after mouseover the map
+ * mouseout - triggered after mouseout the map
+ * mousemove - triggered after mousemove the map
+ * changebaselayer - triggered after the base layer changes
+ * updatesize - triggered after the <updateSize> method was executed
+ */
+
+ /**
+ * Property: id
+ * {String} Unique identifier for the map
+ */
+ id: null,
+
+ /**
+ * Property: fractionalZoom
+ * {Boolean} For a base layer that supports it, allow the map resolution
+ * to be set to a value between one of the values in the resolutions
+ * array. Default is false.
+ *
+ * When fractionalZoom is set to true, it is possible to zoom to
+ * an arbitrary extent. This requires a base layer from a source
+ * that supports requests for arbitrary extents (i.e. not cached
+ * tiles on a regular lattice). This means that fractionalZoom
+ * will not work with commercial layers (Google, Yahoo, VE), layers
+ * using TileCache, or any other pre-cached data sources.
+ *
+ * If you are using fractionalZoom, then you should also use
+ * <getResolutionForZoom> instead of layer.resolutions[zoom] as the
+ * former works for non-integer zoom levels.
+ */
+ fractionalZoom: false,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the map
+ */
+ events: null,
+
+ /**
+ * APIProperty: allOverlays
+ * {Boolean} Allow the map to function with "overlays" only. Defaults to
+ * false. If true, the lowest layer in the draw order will act as
+ * the base layer. In addition, if set to true, all layers will
+ * have isBaseLayer set to false when they are added to the map.
+ *
+ * Note:
+ * If you set map.allOverlays to true, then you *cannot* use
+ * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true,
+ * the lowest layer in the draw layer is the base layer. So, to change
+ * the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
+ * index to 0.
+ */
+ allOverlays: false,
+
+ /**
+ * APIProperty: div
+ * {DOMElement|String} The element that contains the map (or an id for
+ * that element). If the <OpenLayers.Map> constructor is called
+ * with two arguments, this should be provided as the first argument.
+ * Alternatively, the map constructor can be called with the options
+ * object as the only argument. In this case (one argument), a
+ * div property may or may not be provided. If the div property
+ * is not provided, the map can be rendered to a container later
+ * using the <render> method.
+ *
+ * Note:
+ * If you are calling <render> after map construction, do not use
+ * <maxResolution> auto. Instead, divide your <maxExtent> by your
+ * maximum expected dimension.
+ */
+ div: null,
+
+ /**
+ * Property: dragging
+ * {Boolean} The map is currently being dragged.
+ */
+ dragging: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} Size of the main div (this.div)
+ */
+ size: null,
+
+ /**
+ * Property: viewPortDiv
+ * {HTMLDivElement} The element that represents the map viewport
+ */
+ viewPortDiv: null,
+
+ /**
+ * Property: layerContainerOrigin
+ * {<OpenLayers.LonLat>} The lonlat at which the later container was
+ * re-initialized (on-zoom)
+ */
+ layerContainerOrigin: null,
+
+ /**
+ * Property: layerContainerDiv
+ * {HTMLDivElement} The element that contains the layers.
+ */
+ layerContainerDiv: null,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
+ */
+ layers: null,
+
+ /**
+ * APIProperty: controls
+ * {Array(<OpenLayers.Control>)} List of controls associated with the map.
+ *
+ * If not provided in the map options at construction, the map will
+ * by default be given the following controls if present in the build:
+ * - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
+ * - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
+ * - <OpenLayers.Control.ArgParser>
+ * - <OpenLayers.Control.Attribution>
+ */
+ controls: null,
+
+ /**
+ * Property: popups
+ * {Array(<OpenLayers.Popup>)} List of popups associated with the map
+ */
+ popups: null,
+
+ /**
+ * APIProperty: baseLayer
+ * {<OpenLayers.Layer>} The currently selected base layer. This determines
+ * min/max zoom level, projection, etc.
+ */
+ baseLayer: null,
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>} The current center of the map
+ */
+ center: null,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution of the map.
+ */
+ resolution: null,
+
+ /**
+ * Property: zoom
+ * {Integer} The current zoom level of the map
+ */
+ zoom: 0,
+
+ /**
+ * Property: panRatio
+ * {Float} The ratio of the current extent within
+ * which panning will tween.
+ */
+ panRatio: 1.5,
+
+ /**
+ * APIProperty: options
+ * {Object} The options object passed to the class constructor. Read-only.
+ */
+ options: null,
+
+ // Options
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} Set in the map options to override the default tile
+ * size for this map.
+ */
+ tileSize: null,
+
+ /**
+ * APIProperty: projection
+ * {String} Set in the map options to specify the default projection
+ * for layers added to this map. When using a projection other than EPSG:4326
+ * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
+ * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326".
+ * Note that the projection of the map is usually determined
+ * by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
+ */
+ projection: "EPSG:4326",
+
+ /**
+ * APIProperty: units
+ * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm',
+ * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units
+ */
+ units: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array(Float)} A list of map resolutions (map units per pixel) in
+ * descending order. If this is not set in the layer constructor, it
+ * will be set based on other resolution related properties
+ * (maxExtent, maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Required if you are not displaying the whole world on a tile
+ * with the size specified in <tileSize>.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the map.
+ * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
+ * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
+ * else, defaults to null.
+ * To restrict user panning and zooming of the map, use <restrictedExtent> instead.
+ * The value for <maxExtent> will change calculations for tile URLs.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the map. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: restrictedExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * Limit map navigation to this extent where possible.
+ * If a non-null restrictedExtent is set, panning will be restricted
+ * to the given bounds. In addition, zooming to a resolution that
+ * displays more than the restricted extent will center the map
+ * on the restricted extent. If you wish to limit the zoom level
+ * or resolution, use maxResolution.
+ */
+ restrictedExtent: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer} Number of zoom levels for the map. Defaults to 16. Set a
+ * different value in the map options if needed.
+ */
+ numZoomLevels: 16,
+
+ /**
+ * APIProperty: theme
+ * {String} Relative path to a CSS file from which to load theme styles.
+ * Specify null in the map options (e.g. {theme: null}) if you
+ * want to get cascading style declarations - by putting links to
+ * stylesheets or style declarations directly in your page.
+ */
+ theme: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support for projections other
+ * than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
+ * several controls to display data to user. If this property is set,
+ * it will be set on any control which has a null displayProjection
+ * property at the time the control is added to the map.
+ */
+ displayProjection: null,
+
+ /**
+ * APIProperty: tileManager
+ * {<OpenLayers.TileManager>|Object} By default, and if the build contains
+ * TileManager.js, the map will use the TileManager to queue image requests
+ * and to cache tile image elements. To create a map without a TileManager
+ * configure the map with tileManager: null. To create a TileManager with
+ * non-default options, supply the options instead or alternatively supply
+ * an instance of {<OpenLayers.TileManager>}.
+ */
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean} Should OpenLayers allow events on the map to fall through to
+ * other elements on the page, or should it swallow them? (#457)
+ * Default is to swallow.
+ */
+ fallThrough: false,
+
+ /**
+ * APIProperty: autoUpdateSize
+ * {Boolean} Should OpenLayers automatically update the size of the map
+ * when the resize event is fired. Default is true.
+ */
+ autoUpdateSize: true,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * Property: panTween
+ * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
+ */
+ panTween: null,
+
+ /**
+ * APIProperty: panMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
+ * animated panning.
+ */
+ panMethod: OpenLayers.Easing.Expo.easeOut,
+
+ /**
+ * Property: panDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is
+ * panned.
+ * Default is 50.
+ */
+ panDuration: 50,
+
+ /**
+ * Property: zoomTween
+ * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
+ */
+ zoomTween: null,
+
+ /**
+ * APIProperty: zoomMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
+ * animated zooming.
+ */
+ zoomMethod: OpenLayers.Easing.Quad.easeOut,
+
+ /**
+ * Property: zoomDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is zoomed.
+ * Default is 20.
+ */
+ zoomDuration: 20,
+
+ /**
+ * Property: paddingForPopups
+ * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent
+ * the popup from getting too close to the map border.
+ */
+ paddingForPopups : null,
+
+ /**
+ * Property: layerContainerOriginPx
+ * {Object} Cached object representing the layer container origin (in pixels).
+ */
+ layerContainerOriginPx: null,
+
+ /**
+ * Property: minPx
+ * {Object} An object with a 'x' and 'y' values that is the lower
+ * left of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid. It is also used in the getLonLatFromViewPortPx function
+ * of Layer.
+ */
+ minPx: null,
+
+ /**
+ * Property: maxPx
+ * {Object} An object with a 'x' and 'y' values that is the top
+ * right of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid.
+ */
+ maxPx: null,
+
+ /**
+ * Constructor: OpenLayers.Map
+ * Constructor for a new OpenLayers.Map instance. There are two possible
+ * ways to call the map constructor. See the examples below.
+ *
+ * Parameters:
+ * div - {DOMElement|String} The element or id of an element in your page
+ * that will contain the map. May be omitted if the <div> option is
+ * provided or if you intend to call the <render> method later.
+ * options - {Object} Optional object with properties to tag onto the map.
+ *
+ * Valid options (in addition to the listed API properties):
+ * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * Only specify if <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains coordinates, center will be set
+ * by that, and this option will be ignored.
+ * zoom - {Number} The initial zoom level for the map. Only specify if
+ * <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains a zoom level, zoom will be set
+ * by that, and this option will be ignored.
+ * extent - {<OpenLayers.Bounds>|Array} The initial extent of the map.
+ * If provided as an array, the array should consist of
+ * four values (left, bottom, right, top).
+ * Only specify if <center> and <zoom> are not provided.
+ *
+ * Examples:
+ * (code)
+ * // create a map with default options in an element with the id "map1"
+ * var map = new OpenLayers.Map("map1");
+ *
+ * // create a map with non-default options in an element with id "map2"
+ * var options = {
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
+ * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
+ * };
+ * var map = new OpenLayers.Map("map2", options);
+ *
+ * // map with non-default options - same as above but with a single argument,
+ * // a restricted extent, and using arrays for bounds and center
+ * var map = new OpenLayers.Map({
+ * div: "map_id",
+ * projection: "EPSG:3857",
+ * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
+ * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
+ * center: [-12356463.476333, 5621521.4854095]
+ * });
+ *
+ * // create a map without a reference to a container - call render later
+ * var map = new OpenLayers.Map({
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
+ * });
+ * (end)
+ */
+ initialize: function (div, options) {
+
+ // If only one argument is provided, check if it is an object.
+ if(arguments.length === 1 && typeof div === "object") {
+ options = div;
+ div = options && options.div;
+ }
+
+ // Simple-type defaults are set in class definition.
+ // Now set complex-type defaults
+ this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+ OpenLayers.Map.TILE_HEIGHT);
+
+ this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
+
+ this.theme = OpenLayers._getScriptLocation() +
+ 'theme/default/style.css';
+
+ // backup original options
+ this.options = OpenLayers.Util.extend({}, options);
+
+ // now override default options
+ OpenLayers.Util.extend(this, options);
+
+ var projCode = this.projection instanceof OpenLayers.Projection ?
+ this.projection.projCode : this.projection;
+ OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
+
+ // allow extents and center to be arrays
+ if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
+ this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
+ }
+ if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
+ this.minExtent = new OpenLayers.Bounds(this.minExtent);
+ }
+ if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
+ this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
+ }
+ if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
+ this.center = new OpenLayers.LonLat(this.center);
+ }
+
+ // initialize layers array
+ this.layers = [];
+
+ this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
+
+ this.div = OpenLayers.Util.getElement(div);
+ if(!this.div) {
+ this.div = document.createElement("div");
+ this.div.style.height = "1px";
+ this.div.style.width = "1px";
+ }
+
+ OpenLayers.Element.addClass(this.div, 'olMap');
+
+ // the viewPortDiv is the outermost div we modify
+ var id = this.id + "_OpenLayers_ViewPort";
+ this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
+ "relative", null,
+ "hidden");
+ this.viewPortDiv.style.width = "100%";
+ this.viewPortDiv.style.height = "100%";
+ this.viewPortDiv.className = "olMapViewport";
+ this.div.appendChild(this.viewPortDiv);
+
+ this.events = new OpenLayers.Events(
+ this, this.viewPortDiv, null, this.fallThrough,
+ {includeXY: true}
+ );
+
+ if (OpenLayers.TileManager && this.tileManager !== null) {
+ if (!(this.tileManager instanceof OpenLayers.TileManager)) {
+ this.tileManager = new OpenLayers.TileManager(this.tileManager);
+ }
+ this.tileManager.addMap(this);
+ }
+
+ // the layerContainerDiv is the one that holds all the layers
+ id = this.id + "_OpenLayers_Container";
+ this.layerContainerDiv = OpenLayers.Util.createDiv(id);
+ this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
+ this.layerContainerOriginPx = {x: 0, y: 0};
+ this.applyTransform();
+
+ this.viewPortDiv.appendChild(this.layerContainerDiv);
+
+ this.updateSize();
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ if (this.autoUpdateSize === true) {
+ // updateSize on catching the window's resize
+ // Note that this is ok, as updateSize() does nothing if the
+ // map's size has not actually changed.
+ this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize,
+ this);
+ OpenLayers.Event.observe(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ // only append link stylesheet if the theme property is set
+ if(this.theme) {
+ // check existing links for equivalent url
+ var addNode = true;
+ var nodes = document.getElementsByTagName('link');
+ for(var i=0, len=nodes.length; i<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
+ this.theme)) {
+ addNode = false;
+ break;
+ }
+ }
+ // only add a new node if one with an equivalent url hasn't already
+ // been added
+ if(addNode) {
+ var cssNode = document.createElement('link');
+ cssNode.setAttribute('rel', 'stylesheet');
+ cssNode.setAttribute('type', 'text/css');
+ cssNode.setAttribute('href', this.theme);
+ document.getElementsByTagName('head')[0].appendChild(cssNode);
+ }
+ }
+
+ if (this.controls == null) { // default controls
+ this.controls = [];
+ if (OpenLayers.Control != null) { // running full or lite?
+ // Navigation or TouchNavigation depending on what is in build
+ if (OpenLayers.Control.Navigation) {
+ this.controls.push(new OpenLayers.Control.Navigation());
+ } else if (OpenLayers.Control.TouchNavigation) {
+ this.controls.push(new OpenLayers.Control.TouchNavigation());
+ }
+ if (OpenLayers.Control.Zoom) {
+ this.controls.push(new OpenLayers.Control.Zoom());
+ } else if (OpenLayers.Control.PanZoom) {
+ this.controls.push(new OpenLayers.Control.PanZoom());
+ }
+
+ if (OpenLayers.Control.ArgParser) {
+ this.controls.push(new OpenLayers.Control.ArgParser());
+ }
+ if (OpenLayers.Control.Attribution) {
+ this.controls.push(new OpenLayers.Control.Attribution());
+ }
+ }
+ }
+
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ this.addControlToMap(this.controls[i]);
+ }
+
+ this.popups = [];
+
+ this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
+
+
+ // always call map.destroy()
+ OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
+
+ // add any initial layers
+ if (options && options.layers) {
+ /**
+ * If you have set options.center, the map center property will be
+ * set at this point. However, since setCenter has not been called,
+ * addLayers gets confused. So we delete the map center in this
+ * case. Because the check below uses options.center, it will
+ * be properly set below.
+ */
+ delete this.center;
+ delete this.zoom;
+ this.addLayers(options.layers);
+ // set center (and optionally zoom)
+ if (options.center && !this.getCenter()) {
+ // zoom can be undefined here
+ this.setCenter(options.center, options.zoom);
+ }
+ }
+
+ if (this.panMethod) {
+ this.panTween = new OpenLayers.Tween(this.panMethod);
+ }
+ if (this.zoomMethod && this.applyTransform.transform) {
+ this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
+ }
+ },
+
+ /**
+ * APIMethod: getViewport
+ * Get the DOMElement representing the view port.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ getViewport: function() {
+ return this.viewPortDiv;
+ },
+
+ /**
+ * APIMethod: render
+ * Render the map to a specified container.
+ *
+ * Parameters:
+ * div - {String|DOMElement} The container that the map should be rendered
+ * to. If different than the current container, the map viewport
+ * will be moved from the current to the new container.
+ */
+ render: function(div) {
+ this.div = OpenLayers.Util.getElement(div);
+ OpenLayers.Element.addClass(this.div, 'olMap');
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ this.div.appendChild(this.viewPortDiv);
+ this.updateSize();
+ },
+
+ /**
+ * Method: unloadDestroy
+ * Function that is called to destroy the map on page unload. stored here
+ * so that if map is manually destroyed, we can unregister this.
+ */
+ unloadDestroy: null,
+
+ /**
+ * Method: updateSizeDestroy
+ * When the map is destroyed, we need to stop listening to updateSize
+ * events: this method stores the function we need to unregister in
+ * non-IE browsers.
+ */
+ updateSizeDestroy: null,
+
+ /**
+ * APIMethod: destroy
+ * Destroy this map.
+ * Note that if you are using an application which removes a container
+ * of the map from the DOM, you need to ensure that you destroy the
+ * map *before* this happens; otherwise, the page unload handler
+ * will fail because the DOM elements that map.destroy() wants
+ * to clean up will be gone. (See
+ * http://trac.osgeo.org/openlayers/ticket/2277 for more information).
+ * This will apply to GeoExt and also to other applications which
+ * modify the DOM of the container of the OpenLayers Map.
+ */
+ destroy:function() {
+ // if unloadDestroy is null, we've already been destroyed
+ if (!this.unloadDestroy) {
+ return false;
+ }
+
+ // make sure panning doesn't continue after destruction
+ if(this.panTween) {
+ this.panTween.stop();
+ this.panTween = null;
+ }
+ // make sure zooming doesn't continue after destruction
+ if(this.zoomTween) {
+ this.zoomTween.stop();
+ this.zoomTween = null;
+ }
+
+ // map has been destroyed. dont do it again!
+ OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
+ this.unloadDestroy = null;
+
+ if (this.updateSizeDestroy) {
+ OpenLayers.Event.stopObserving(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ this.paddingForPopups = null;
+
+ if (this.controls != null) {
+ for (var i = this.controls.length - 1; i>=0; --i) {
+ this.controls[i].destroy();
+ }
+ this.controls = null;
+ }
+ if (this.layers != null) {
+ for (var i = this.layers.length - 1; i>=0; --i) {
+ //pass 'false' to destroy so that map wont try to set a new
+ // baselayer after each baselayer is removed
+ this.layers[i].destroy(false);
+ }
+ this.layers = null;
+ }
+ if (this.viewPortDiv && this.viewPortDiv.parentNode) {
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ }
+ this.viewPortDiv = null;
+
+ if (this.tileManager) {
+ this.tileManager.removeMap(this);
+ this.tileManager = null;
+ }
+
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ this.eventListeners = null;
+ }
+ this.events.destroy();
+ this.events = null;
+
+ this.options = null;
+ },
+
+ /**
+ * APIMethod: setOptions
+ * Change the map options
+ *
+ * Parameters:
+ * options - {Object} Hashtable of options to tag to the map
+ */
+ setOptions: function(options) {
+ var updatePxExtent = this.minPx &&
+ options.restrictedExtent != this.restrictedExtent;
+ OpenLayers.Util.extend(this, options);
+ // force recalculation of minPx and maxPx
+ updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
+ forceZoomChange: true
+ });
+ },
+
+ /**
+ * APIMethod: getTileSize
+ * Get the tile size for the map
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+ getTileSize: function() {
+ return this.tileSize;
+ },
+
+
+ /**
+ * APIMethod: getBy
+ * Get a list of objects given a property and a match item.
+ *
+ * Parameters:
+ * array - {String} A property on the map whose value is an array.
+ * property - {String} A property on each item of the given array.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(map[array][i][property]) evaluates to true, the item will
+ * be included in the array returned. If no items are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array} An array of items where the given property matches the given
+ * criteria.
+ */
+ getBy: function(array, property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this[array], function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getLayersBy
+ * Get a list of layers with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A layer property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersBy: function(property, match) {
+ return this.getBy("layers", property, match);
+ },
+
+ /**
+ * APIMethod: getLayersByName
+ * Get a list of layers with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A layer name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(layer.name) evaluates to true, the layer will be included
+ * in the list of layers returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByName: function(match) {
+ return this.getLayersBy("name", match);
+ },
+
+ /**
+ * APIMethod: getLayersByClass
+ * Get a list of layers of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A layer class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(layer.CLASS_NAME) evaluates to true, the layer will
+ * be included in the list of layers returned. If no layers are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByClass: function(match) {
+ return this.getLayersBy("CLASS_NAME", match);
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given
+ * criteria. An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ return this.getBy("controls", property, match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Layers to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getLayer
+ * Get a layer based on its id
+ *
+ * Parameters:
+ * id - {String} A layer id
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
+ * layer collection, or null if not found.
+ */
+ getLayer: function(id) {
+ var foundLayer = null;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer.id == id) {
+ foundLayer = layer;
+ break;
+ }
+ }
+ return foundLayer;
+ },
+
+ /**
+ * Method: setLayerZIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * zIdx - {int}
+ */
+ setLayerZIndex: function (layer, zIdx) {
+ layer.setZIndex(
+ this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+ + zIdx * 5 );
+ },
+
+ /**
+ * Method: resetLayersZIndex
+ * Reset each layer's z-index based on layer's array index
+ */
+ resetLayersZIndex: function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ this.setLayerZIndex(layer, i);
+ }
+ },
+
+ /**
+ * APIMethod: addLayer
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Boolean} True if the layer has been added to the map.
+ */
+ addLayer: function (layer) {
+ for(var i = 0, len = this.layers.length; i < len; i++) {
+ if (this.layers[i] == layer) {
+ return false;
+ }
+ }
+ if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
+ return false;
+ }
+ if(this.allOverlays) {
+ layer.isBaseLayer = false;
+ }
+
+ layer.div.className = "olLayerDiv";
+ layer.div.style.overflow = "";
+ this.setLayerZIndex(layer, this.layers.length);
+
+ if (layer.isFixed) {
+ this.viewPortDiv.appendChild(layer.div);
+ } else {
+ this.layerContainerDiv.appendChild(layer.div);
+ }
+ this.layers.push(layer);
+ layer.setMap(this);
+
+ if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) {
+ if (this.baseLayer == null) {
+ // set the first baselaye we add as the baselayer
+ this.setBaseLayer(layer);
+ } else {
+ layer.setVisibility(false);
+ }
+ } else {
+ layer.redraw();
+ }
+
+ this.events.triggerEvent("addlayer", {layer: layer});
+ layer.events.triggerEvent("added", {map: this, layer: layer});
+ layer.afterAdd();
+
+ return true;
+ },
+
+ /**
+ * APIMethod: addLayers
+ *
+ * Parameters:
+ * layers - {Array(<OpenLayers.Layer>)}
+ */
+ addLayers: function (layers) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ /**
+ * APIMethod: removeLayer
+ * Removes a layer from the map by removing its visual element (the
+ * layer.div property), then removing it from the map's internal list
+ * of layers, setting the layer's map property to null.
+ *
+ * a "removelayer" event is triggered.
+ *
+ * very worthy of mention is that simply removing a layer from a map
+ * will not cause the removal of any popups which may have been created
+ * by the layer. this is due to the fact that it was decided at some
+ * point that popups would not belong to layers. thus there is no way
+ * for us to know here to which layer the popup belongs.
+ *
+ * A simple solution to this is simply to call destroy() on the layer.
+ * the default OpenLayers.Layer class's destroy() function
+ * automatically takes care to remove itself from whatever map it has
+ * been attached to.
+ *
+ * The correct solution is for the layer itself to register an
+ * event-handler on "removelayer" and when it is called, if it
+ * recognizes itself as the layer being removed, then it cycles through
+ * its own personal list of popups, removing them from the map.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * setNewBaseLayer - {Boolean} Default is true
+ */
+ removeLayer: function(layer, setNewBaseLayer) {
+ if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
+ return;
+ }
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+
+ if (layer.isFixed) {
+ this.viewPortDiv.removeChild(layer.div);
+ } else {
+ this.layerContainerDiv.removeChild(layer.div);
+ }
+ OpenLayers.Util.removeItem(this.layers, layer);
+ layer.removeMap(this);
+ layer.map = null;
+
+ // if we removed the base layer, need to set a new one
+ if(this.baseLayer == layer) {
+ this.baseLayer = null;
+ if(setNewBaseLayer) {
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ var iLayer = this.layers[i];
+ if (iLayer.isBaseLayer || this.allOverlays) {
+ this.setBaseLayer(iLayer);
+ break;
+ }
+ }
+ }
+ }
+
+ this.resetLayersZIndex();
+
+ this.events.triggerEvent("removelayer", {layer: layer});
+ layer.events.triggerEvent("removed", {map: this, layer: layer});
+ },
+
+ /**
+ * APIMethod: getNumLayers
+ *
+ * Returns:
+ * {Int} The number of layers attached to the map.
+ */
+ getNumLayers: function () {
+ return this.layers.length;
+ },
+
+ /**
+ * APIMethod: getLayerIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Integer} The current (zero-based) index of the given layer in the map's
+ * layer stack. Returns -1 if the layer isn't on the map.
+ */
+ getLayerIndex: function (layer) {
+ return OpenLayers.Util.indexOf(this.layers, layer);
+ },
+
+ /**
+ * APIMethod: setLayerIndex
+ * Move the given layer to the specified (zero-based) index in the layer
+ * list, changing its z-index in the map display. Use
+ * map.getLayerIndex() to find out the current index of a layer. Note
+ * that this cannot (or at least should not) be effectively used to
+ * raise base layers above overlays.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * idx - {int}
+ */
+ setLayerIndex: function (layer, idx) {
+ var base = this.getLayerIndex(layer);
+ if (idx < 0) {
+ idx = 0;
+ } else if (idx > this.layers.length) {
+ idx = this.layers.length;
+ }
+ if (base != idx) {
+ this.layers.splice(base, 1);
+ this.layers.splice(idx, 0, layer);
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ this.setLayerZIndex(this.layers[i], i);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "order"
+ });
+ if(this.allOverlays) {
+ if(idx === 0) {
+ this.setBaseLayer(layer);
+ } else if(this.baseLayer !== this.layers[0]) {
+ this.setBaseLayer(this.layers[0]);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: raiseLayer
+ * Change the index of the given layer by delta. If delta is positive,
+ * the layer is moved up the map's layer stack; if delta is negative,
+ * the layer is moved down. Again, note that this cannot (or at least
+ * should not) be effectively used to raise base layers above overlays.
+ *
+ * Paremeters:
+ * layer - {<OpenLayers.Layer>}
+ * delta - {int}
+ */
+ raiseLayer: function (layer, delta) {
+ var idx = this.getLayerIndex(layer) + delta;
+ this.setLayerIndex(layer, idx);
+ },
+
+ /**
+ * APIMethod: setBaseLayer
+ * Allows user to specify one of the currently-loaded layers as the Map's
+ * new base layer.
+ *
+ * Parameters:
+ * newBaseLayer - {<OpenLayers.Layer>}
+ */
+ setBaseLayer: function(newBaseLayer) {
+
+ if (newBaseLayer != this.baseLayer) {
+
+ // ensure newBaseLayer is already loaded
+ if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
+
+ // preserve center and scale when changing base layers
+ var center = this.getCachedCenter();
+ var newResolution = OpenLayers.Util.getResolutionFromScale(
+ this.getScale(), newBaseLayer.units
+ );
+
+ // make the old base layer invisible
+ if (this.baseLayer != null && !this.allOverlays) {
+ this.baseLayer.setVisibility(false);
+ }
+
+ // set new baselayer
+ this.baseLayer = newBaseLayer;
+
+ if(!this.allOverlays || this.baseLayer.visibility) {
+ this.baseLayer.setVisibility(true);
+ // Layer may previously have been visible but not in range.
+ // In this case we need to redraw it to make it visible.
+ if (this.baseLayer.inRange === false) {
+ this.baseLayer.redraw();
+ }
+ }
+
+ // recenter the map
+ if (center != null) {
+ // new zoom level derived from old scale
+ var newZoom = this.getZoomForResolution(
+ newResolution || this.resolution, true
+ );
+ // zoom and force zoom change
+ this.setCenter(center, newZoom, false, true);
+ }
+
+ this.events.triggerEvent("changebaselayer", {
+ layer: this.baseLayer
+ });
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Control Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Controls to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addControl
+ * Add the passed over control to the map. Optionally
+ * position the control at the given pixel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControl: function (control, px) {
+ this.controls.push(control);
+ this.addControlToMap(control, px);
+ },
+
+ /**
+ * APIMethod: addControls
+ * Add all of the passed over controls to the map.
+ * You can pass over an optional second array
+ * with pixel-objects to position the controls.
+ * The indices of the two arrays should match and
+ * you can add null as pixel for those controls
+ * you want to be autopositioned.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)}
+ * pixels - {Array(<OpenLayers.Pixel>)}
+ */
+ addControls: function (controls, pixels) {
+ var pxs = (arguments.length === 1) ? [] : pixels;
+ for (var i=0, len=controls.length; i<len; i++) {
+ var ctrl = controls[i];
+ var px = (pxs[i]) ? pxs[i] : null;
+ this.addControl( ctrl, px );
+ }
+ },
+
+ /**
+ * Method: addControlToMap
+ *
+ * Parameters:
+ *
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControlToMap: function (control, px) {
+ // If a control doesn't have a div at this point, it belongs in the
+ // viewport.
+ control.outsideViewport = (control.div != null);
+
+ // If the map has a displayProjection, and the control doesn't, set
+ // the display projection.
+ if (this.displayProjection && !control.displayProjection) {
+ control.displayProjection = this.displayProjection;
+ }
+
+ control.setMap(this);
+ var div = control.draw(px);
+ if (div) {
+ if(!control.outsideViewport) {
+ div.style.zIndex = this.Z_INDEX_BASE['Control'] +
+ this.controls.length;
+ this.viewPortDiv.appendChild( div );
+ }
+ }
+ if(control.autoActivate) {
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: getControl
+ *
+ * Parameters:
+ * id - {String} ID of the control to return.
+ *
+ * Returns:
+ * {<OpenLayers.Control>} The control from the map's list of controls
+ * which has a matching 'id'. If none found,
+ * returns null.
+ */
+ getControl: function (id) {
+ var returnControl = null;
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ var control = this.controls[i];
+ if (control.id == id) {
+ returnControl = control;
+ break;
+ }
+ }
+ return returnControl;
+ },
+
+ /**
+ * APIMethod: removeControl
+ * Remove a control from the map. Removes the control both from the map
+ * object's internal array of controls, as well as from the map's
+ * viewPort (assuming the control was not added outsideViewport)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to remove.
+ */
+ removeControl: function (control) {
+ //make sure control is non-null and actually part of our map
+ if ( (control) && (control == this.getControl(control.id)) ) {
+ if (control.div && (control.div.parentNode == this.viewPortDiv)) {
+ this.viewPortDiv.removeChild(control.div);
+ }
+ OpenLayers.Util.removeItem(this.controls, control);
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Popup Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Popups to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addPopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ * exclusive - {Boolean} If true, closes all other popups first
+ */
+ addPopup: function(popup, exclusive) {
+
+ if (exclusive) {
+ //remove all other popups from screen
+ for (var i = this.popups.length - 1; i >= 0; --i) {
+ this.removePopup(this.popups[i]);
+ }
+ }
+
+ popup.map = this;
+ this.popups.push(popup);
+ var popupDiv = popup.draw();
+ if (popupDiv) {
+ popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
+ this.popups.length;
+ this.layerContainerDiv.appendChild(popupDiv);
+ }
+ },
+
+ /**
+ * APIMethod: removePopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ */
+ removePopup: function(popup) {
+ OpenLayers.Util.removeItem(this.popups, popup);
+ if (popup.div) {
+ try { this.layerContainerDiv.removeChild(popup.div); }
+ catch (e) { } // Popups sometimes apparently get disconnected
+ // from the layerContainerDiv, and cause complaints.
+ }
+ popup.map = null;
+ },
+
+ /********************************************************/
+ /* */
+ /* Container Div Functions */
+ /* */
+ /* The following functions deal with the access to */
+ /* and maintenance of the size of the container div */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
+ * size, in pixels, of the div into which OpenLayers
+ * has been loaded.
+ * Note - A clone() of this locally cached variable is
+ * returned, so as not to allow users to modify it.
+ */
+ getSize: function () {
+ var size = null;
+ if (this.size != null) {
+ size = this.size.clone();
+ }
+ return size;
+ },
+
+ /**
+ * APIMethod: updateSize
+ * This function should be called by any external code which dynamically
+ * changes the size of the map div (because mozilla wont let us catch
+ * the "onresize" for an element)
+ */
+ updateSize: function() {
+ // the div might have moved on the page, also
+ var newSize = this.getCurrentSize();
+ if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
+ this.events.clearMouseCache();
+ var oldSize = this.getSize();
+ if (oldSize == null) {
+ this.size = oldSize = newSize;
+ }
+ if (!newSize.equals(oldSize)) {
+
+ // store the new size
+ this.size = newSize;
+
+ //notify layers of mapresize
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ this.layers[i].onMapResize();
+ }
+
+ var center = this.getCachedCenter();
+
+ if (this.baseLayer != null && center != null) {
+ var zoom = this.getZoom();
+ this.zoom = null;
+ this.setCenter(center, zoom);
+ }
+
+ }
+ }
+ this.events.triggerEvent("updatesize");
+ },
+
+ /**
+ * Method: getCurrentSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
+ * of the map div
+ */
+ getCurrentSize: function() {
+
+ var size = new OpenLayers.Size(this.div.clientWidth,
+ this.div.clientHeight);
+
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = this.div.offsetWidth;
+ size.h = this.div.offsetHeight;
+ }
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = parseInt(this.div.style.width);
+ size.h = parseInt(this.div.style.height);
+ }
+ return size;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * center - {<OpenLayers.LonLat>} Default is this.getCenter()
+ * resolution - {float} Default is this.getResolution()
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds based on resolution, center, and
+ * current mapsize.
+ */
+ calculateBounds: function(center, resolution) {
+
+ var extent = null;
+
+ if (center == null) {
+ center = this.getCachedCenter();
+ }
+ if (resolution == null) {
+ resolution = this.getResolution();
+ }
+
+ if ((center != null) && (resolution != null)) {
+ var halfWDeg = (this.size.w * resolution) / 2;
+ var halfHDeg = (this.size.h * resolution) / 2;
+
+ extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ }
+
+ return extent;
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Zoom, Center, Pan Functions */
+ /* */
+ /* The following functions handle the validation, */
+ /* getting and setting of the Zoom Level and Center */
+ /* as well as the panning of the Map */
+ /* */
+ /********************************************************/
+ /**
+ * APIMethod: getCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCenter: function () {
+ var center = null;
+ var cachedCenter = this.getCachedCenter();
+ if (cachedCenter) {
+ center = cachedCenter.clone();
+ }
+ return center;
+ },
+
+ /**
+ * Method: getCachedCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCachedCenter: function() {
+ if (!this.center && this.size) {
+ this.center = this.getLonLatFromViewPortPx({
+ x: this.size.w / 2,
+ y: this.size.h / 2
+ });
+ }
+ return this.center;
+ },
+
+ /**
+ * APIMethod: getZoom
+ *
+ * Returns:
+ * {Integer}
+ */
+ getZoom: function () {
+ return this.zoom;
+ },
+
+ /**
+ * APIMethod: pan
+ * Allows user to pan by a value of screen pixels
+ *
+ * Parameters:
+ * dx - {Integer}
+ * dy - {Integer}
+ * options - {Object} Options to configure panning:
+ * - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
+ * - *dragging* {Boolean} Call setCenter with dragging true. Default is
+ * false.
+ */
+ pan: function(dx, dy, options) {
+ options = OpenLayers.Util.applyDefaults(options, {
+ animate: true,
+ dragging: false
+ });
+ if (options.dragging) {
+ if (dx != 0 || dy != 0) {
+ this.moveByPx(dx, dy);
+ }
+ } else {
+ // getCenter
+ var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());
+
+ // adjust
+ var newCenterPx = centerPx.add(dx, dy);
+
+ if (this.dragging || !newCenterPx.equals(centerPx)) {
+ var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
+ if (options.animate) {
+ this.panTo(newCenterLonLat);
+ } else {
+ this.moveTo(newCenterLonLat);
+ if(this.dragging) {
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }
+ }
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: panTo
+ * Allows user to pan to a new lonlat
+ * If the new lonlat is in the current extent the map will slide smoothly
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ panTo: function(lonlat) {
+ if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
+ var center = this.getCachedCenter();
+
+ // center will not change, don't do nothing
+ if (lonlat.equals(center)) {
+ return;
+ }
+
+ var from = this.getPixelFromLonLat(center);
+ var to = this.getPixelFromLonLat(lonlat);
+ var vector = { x: to.x - from.x, y: to.y - from.y };
+ var last = { x: 0, y: 0 };
+
+ this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
+ callbacks: {
+ eachStep: OpenLayers.Function.bind(function(px) {
+ var x = px.x - last.x,
+ y = px.y - last.y;
+ this.moveByPx(x, y);
+ last.x = Math.round(px.x);
+ last.y = Math.round(px.y);
+ }, this),
+ done: OpenLayers.Function.bind(function(px) {
+ this.moveTo(lonlat);
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }, this)
+ }
+ });
+ } else {
+ this.setCenter(lonlat);
+ }
+ },
+
+ /**
+ * APIMethod: setCenter
+ * Set the map center (and optionally, the zoom level).
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * zoom - {Integer} Optional zoom level.
+ * dragging - {Boolean} Specifies whether or not to trigger
+ * movestart/end events
+ * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom
+ * change events (needed on baseLayer change)
+ *
+ * TBD: reconsider forceZoomChange in 3.0
+ */
+ setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
+ if (this.panTween) {
+ this.panTween.stop();
+ }
+ if (this.zoomTween) {
+ this.zoomTween.stop();
+ }
+ this.moveTo(lonlat, zoom, {
+ 'dragging': dragging,
+ 'forceZoomChange': forceZoomChange
+ });
+ },
+
+ /**
+ * Method: moveByPx
+ * Drag the map by pixels.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ var hw = this.size.w / 2;
+ var hh = this.size.h / 2;
+ var x = hw + dx;
+ var y = hh + dy;
+ var wrapDateLine = this.baseLayer.wrapDateLine;
+ var xRestriction = 0;
+ var yRestriction = 0;
+ if (this.restrictedExtent) {
+ xRestriction = hw;
+ yRestriction = hh;
+ // wrapping the date line makes no sense for restricted extents
+ wrapDateLine = false;
+ }
+ dx = wrapDateLine ||
+ x <= this.maxPx.x - xRestriction &&
+ x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
+ dy = y <= this.maxPx.y - yRestriction &&
+ y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
+ if (dx || dy) {
+ if (!this.dragging) {
+ this.dragging = true;
+ this.events.triggerEvent("movestart");
+ }
+ this.center = null;
+ if (dx) {
+ this.layerContainerOriginPx.x -= dx;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ }
+ if (dy) {
+ this.layerContainerOriginPx.y -= dy;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ this.applyTransform();
+ var layer, i, len;
+ for (i=0, len=this.layers.length; i<len; ++i) {
+ layer = this.layers[i];
+ if (layer.visibility &&
+ (layer === this.baseLayer || layer.inRange)) {
+ layer.moveByPx(dx, dy);
+ layer.events.triggerEvent("move");
+ }
+ }
+ this.events.triggerEvent("move");
+ }
+ },
+
+ /**
+ * Method: adjustZoom
+ *
+ * Parameters:
+ * zoom - {Number} The zoom level to adjust
+ *
+ * Returns:
+ * {Integer} Adjusted zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent.
+ */
+ adjustZoom: function(zoom) {
+ if (this.baseLayer && this.baseLayer.wrapDateLine) {
+ var resolution, resolutions = this.baseLayer.resolutions,
+ maxResolution = this.getMaxExtent().getWidth() / this.size.w;
+ if (this.getResolutionForZoom(zoom) > maxResolution) {
+ if (this.fractionalZoom) {
+ zoom = this.getZoomForResolution(maxResolution);
+ } else {
+ for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
+ if (resolutions[i] <= maxResolution) {
+ zoom = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getMinZoom
+ * Returns the minimum zoom level for the current map view. If the base
+ * layer is configured with <wrapDateLine> set to true, this will be the
+ * first zoom level that shows no more than one world width in the current
+ * map viewport. Components that rely on this value (e.g. zoom sliders)
+ * should also listen to the map's "updatesize" event and call this method
+ * in the "updatesize" listener.
+ *
+ * Returns:
+ * {Number} Minimum zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
+ * configured with <fractionalZoom> set to true.
+ */
+ getMinZoom: function() {
+ return this.adjustZoom(0);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ * zoom - {Integer}
+ * options - {Object}
+ */
+ moveTo: function(lonlat, zoom, options) {
+ if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
+ lonlat = new OpenLayers.LonLat(lonlat);
+ }
+ if (!options) {
+ options = {};
+ }
+ if (zoom != null) {
+ zoom = parseFloat(zoom);
+ if (!this.fractionalZoom) {
+ zoom = Math.round(zoom);
+ }
+ }
+ var requestedZoom = zoom;
+ zoom = this.adjustZoom(zoom);
+ if (zoom !== requestedZoom) {
+ // zoom was adjusted, so keep old lonlat to avoid panning
+ lonlat = this.getCenter();
+ }
+ // dragging is false by default
+ var dragging = options.dragging || this.dragging;
+ // forceZoomChange is false by default
+ var forceZoomChange = options.forceZoomChange;
+
+ if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
+ lonlat = this.maxExtent.getCenterLonLat();
+ this.center = lonlat.clone();
+ }
+
+ if(this.restrictedExtent != null) {
+ // In 3.0, decide if we want to change interpretation of maxExtent.
+ if(lonlat == null) {
+ lonlat = this.center;
+ }
+ if(zoom == null) {
+ zoom = this.getZoom();
+ }
+ var resolution = this.getResolutionForZoom(zoom);
+ var extent = this.calculateBounds(lonlat, resolution);
+ if(!this.restrictedExtent.containsBounds(extent)) {
+ var maxCenter = this.restrictedExtent.getCenterLonLat();
+ if(extent.getWidth() > this.restrictedExtent.getWidth()) {
+ lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);
+ } else if(extent.left < this.restrictedExtent.left) {
+ lonlat = lonlat.add(this.restrictedExtent.left -
+ extent.left, 0);
+ } else if(extent.right > this.restrictedExtent.right) {
+ lonlat = lonlat.add(this.restrictedExtent.right -
+ extent.right, 0);
+ }
+ if(extent.getHeight() > this.restrictedExtent.getHeight()) {
+ lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);
+ } else if(extent.bottom < this.restrictedExtent.bottom) {
+ lonlat = lonlat.add(0, this.restrictedExtent.bottom -
+ extent.bottom);
+ }
+ else if(extent.top > this.restrictedExtent.top) {
+ lonlat = lonlat.add(0, this.restrictedExtent.top -
+ extent.top);
+ }
+ }
+ }
+
+ var zoomChanged = forceZoomChange || (
+ (this.isValidZoomLevel(zoom)) &&
+ (zoom != this.getZoom()) );
+
+ var centerChanged = (this.isValidLonLat(lonlat)) &&
+ (!lonlat.equals(this.center));
+
+ // if neither center nor zoom will change, no need to do anything
+ if (zoomChanged || centerChanged || dragging) {
+ dragging || this.events.triggerEvent("movestart", {
+ zoomChanged: zoomChanged
+ });
+
+ if (centerChanged) {
+ if (!zoomChanged && this.center) {
+ // if zoom hasnt changed, just slide layerContainer
+ // (must be done before setting this.center to new value)
+ this.centerLayerContainer(lonlat);
+ }
+ this.center = lonlat.clone();
+ }
+
+ var res = zoomChanged ?
+ this.getResolutionForZoom(zoom) : this.getResolution();
+ // (re)set the layerContainerDiv's location
+ if (zoomChanged || this.layerContainerOrigin == null) {
+ this.layerContainerOrigin = this.getCachedCenter();
+ this.layerContainerOriginPx.x = 0;
+ this.layerContainerOriginPx.y = 0;
+ this.applyTransform();
+ var maxExtent = this.getMaxExtent({restricted: true});
+ var maxExtentCenter = maxExtent.getCenterLonLat();
+ var lonDelta = this.center.lon - maxExtentCenter.lon;
+ var latDelta = maxExtentCenter.lat - this.center.lat;
+ var extentWidth = Math.round(maxExtent.getWidth() / res);
+ var extentHeight = Math.round(maxExtent.getHeight() / res);
+ this.minPx = {
+ x: (this.size.w - extentWidth) / 2 - lonDelta / res,
+ y: (this.size.h - extentHeight) / 2 - latDelta / res
+ };
+ this.maxPx = {
+ x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
+ y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
+ };
+ }
+
+ if (zoomChanged) {
+ this.zoom = zoom;
+ this.resolution = res;
+ }
+
+ var bounds = this.getExtent();
+
+ //send the move call to the baselayer and all the overlays
+
+ if(this.baseLayer.visibility) {
+ this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || this.baseLayer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+
+ bounds = this.baseLayer.getExtent();
+
+ for (var i=this.layers.length-1; i>=0; --i) {
+ var layer = this.layers[i];
+ if (layer !== this.baseLayer && !layer.isBaseLayer) {
+ var inRange = layer.calculateInRange();
+ if (layer.inRange != inRange) {
+ // the inRange property has changed. If the layer is
+ // no longer in range, we turn it off right away. If
+ // the layer is no longer out of range, the moveTo
+ // call below will turn on the layer.
+ layer.inRange = inRange;
+ if (!inRange) {
+ layer.display(false);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "visibility"
+ });
+ }
+ if (inRange && layer.visibility) {
+ layer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || layer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+ }
+ }
+
+ this.events.triggerEvent("move");
+ dragging || this.events.triggerEvent("moveend");
+
+ if (zoomChanged) {
+ //redraw popups
+ for (var i=0, len=this.popups.length; i<len; i++) {
+ this.popups[i].updatePosition();
+ }
+ this.events.triggerEvent("zoomend");
+ }
+ }
+ },
+
+ /**
+ * Method: centerLayerContainer
+ * This function takes care to recenter the layerContainerDiv.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ centerLayerContainer: function (lonlat) {
+ var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
+ var newPx = this.getViewPortPxFromLonLat(lonlat);
+
+ if ((originPx != null) && (newPx != null)) {
+ var oldLeft = this.layerContainerOriginPx.x;
+ var oldTop = this.layerContainerOriginPx.y;
+ var newLeft = Math.round(originPx.x - newPx.x);
+ var newTop = Math.round(originPx.y - newPx.y);
+ this.applyTransform(
+ (this.layerContainerOriginPx.x = newLeft),
+ (this.layerContainerOriginPx.y = newTop));
+ var dx = oldLeft - newLeft;
+ var dy = oldTop - newTop;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ },
+
+ /**
+ * Method: isValidZoomLevel
+ *
+ * Parameters:
+ * zoomLevel - {Integer}
+ *
+ * Returns:
+ * {Boolean} Whether or not the zoom level passed in is non-null and
+ * within the min/max range of zoom levels.
+ */
+ isValidZoomLevel: function(zoomLevel) {
+ return ( (zoomLevel != null) &&
+ (zoomLevel >= 0) &&
+ (zoomLevel < this.getNumZoomLevels()) );
+ },
+
+ /**
+ * Method: isValidLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Whether or not the lonlat passed in is non-null and within
+ * the maxExtent bounds
+ */
+ isValidLonLat: function(lonlat) {
+ var valid = false;
+ if (lonlat != null) {
+ var maxExtent = this.getMaxExtent();
+ var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
+ valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
+ }
+ return valid;
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Options */
+ /* */
+ /* Accessor functions to Layer Options parameters */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getProjection
+ * This method returns a string representing the projection. In
+ * the case of projection support, this will be the srsCode which
+ * is loaded -- otherwise it will simply be the string value that
+ * was passed to the projection at startup.
+ *
+ * FIXME: In 3.0, we will remove getProjectionObject, and instead
+ * return a Projection object from this function.
+ *
+ * Returns:
+ * {String} The Projection string from the base layer or null.
+ */
+ getProjection: function() {
+ var projection = this.getProjectionObject();
+ return projection ? projection.getCode() : null;
+ },
+
+ /**
+ * APIMethod: getProjectionObject
+ * Returns the projection obect from the baselayer.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} The Projection of the base layer.
+ */
+ getProjectionObject: function() {
+ var projection = null;
+ if (this.baseLayer != null) {
+ projection = this.baseLayer.projection;
+ }
+ return projection;
+ },
+
+ /**
+ * APIMethod: getMaxResolution
+ *
+ * Returns:
+ * {String} The Map's Maximum Resolution
+ */
+ getMaxResolution: function() {
+ var maxResolution = null;
+ if (this.baseLayer != null) {
+ maxResolution = this.baseLayer.maxResolution;
+ }
+ return maxResolution;
+ },
+
+ /**
+ * APIMethod: getMaxExtent
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} If true, returns restricted extent (if it is
+ * available.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The maxExtent property as set on the current
+ * baselayer, unless the 'restricted' option is set, in which case
+ * the 'restrictedExtent' option from the map is returned (if it
+ * is set).
+ */
+ getMaxExtent: function (options) {
+ var maxExtent = null;
+ if(options && options.restricted && this.restrictedExtent){
+ maxExtent = this.restrictedExtent;
+ } else if (this.baseLayer != null) {
+ maxExtent = this.baseLayer.maxExtent;
+ }
+ return maxExtent;
+ },
+
+ /**
+ * APIMethod: getNumZoomLevels
+ *
+ * Returns:
+ * {Integer} The total number of zoom levels that can be displayed by the
+ * current baseLayer.
+ */
+ getNumZoomLevels: function() {
+ var numZoomLevels = null;
+ if (this.baseLayer != null) {
+ numZoomLevels = this.baseLayer.numZoomLevels;
+ }
+ return numZoomLevels;
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API?, are all merely wrappers to the */
+ /* the same calls on whatever layer is set as */
+ /* the current base layer */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ * If no baselayer is set, returns null.
+ */
+ getExtent: function () {
+ var extent = null;
+ if (this.baseLayer != null) {
+ extent = this.baseLayer.getExtent();
+ }
+ return extent;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The current resolution of the map.
+ * If no baselayer is set, returns null.
+ */
+ getResolution: function () {
+ var resolution = null;
+ if (this.baseLayer != null) {
+ resolution = this.baseLayer.getResolution();
+ } else if(this.allOverlays === true && this.layers.length > 0) {
+ // while adding the 1st layer to the map in allOverlays mode,
+ // this.baseLayer is not set yet when we need the resolution
+ // for calculateInRange.
+ resolution = this.layers[0].getResolution();
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getUnits
+ *
+ * Returns:
+ * {Float} The current units of the map.
+ * If no baselayer is set, returns null.
+ */
+ getUnits: function () {
+ var units = null;
+ if (this.baseLayer != null) {
+ units = this.baseLayer.units;
+ }
+ return units;
+ },
+
+ /**
+ * APIMethod: getScale
+ *
+ * Returns:
+ * {Float} The current scale denominator of the map.
+ * If no baselayer is set, returns null.
+ */
+ getScale: function () {
+ var scale = null;
+ if (this.baseLayer != null) {
+ var res = this.getResolution();
+ var units = this.baseLayer.units;
+ scale = OpenLayers.Util.getScaleFromResolution(res, units);
+ }
+ return scale;
+ },
+
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified bounds.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForExtent: function (bounds, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForExtent(bounds, closest);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom. If no baselayer
+ * is set, returns null.
+ */
+ getResolutionForZoom: function(zoom) {
+ var resolution = null;
+ if(this.baseLayer) {
+ resolution = this.baseLayer.getResolutionForZoom(zoom);
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForResolution(resolution, closest);
+ }
+ return zoom;
+ },
+
+ /********************************************************/
+ /* */
+ /* Zooming Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API, are all merely wrappers to the */
+ /* the setCenter() function */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: zoomTo
+ * Zoom to a specific zoom level. Zooming will be animated unless the map
+ * is configured with {zoomMethod: null}. To zoom without animation, use
+ * <setCenter> without a lonlat argument.
+ *
+ * Parameters:
+ * zoom - {Integer}
+ */
+ zoomTo: function(zoom, xy) {
+ // non-API arguments:
+ // xy - {<OpenLayers.Pixel>} optional zoom origin
+
+ var map = this;
+ if (map.isValidZoomLevel(zoom)) {
+ if (map.baseLayer.wrapDateLine) {
+ zoom = map.adjustZoom(zoom);
+ }
+ if (map.zoomTween) {
+ var currentRes = map.getResolution(),
+ targetRes = map.getResolutionForZoom(zoom),
+ start = {scale: 1},
+ end = {scale: currentRes / targetRes};
+ if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
+ // update the end scale, and reuse the running zoomTween
+ map.zoomTween.finish = {
+ scale: map.zoomTween.finish.scale * end.scale
+ };
+ } else {
+ if (!xy) {
+ var size = map.getSize();
+ xy = {x: size.w / 2, y: size.h / 2};
+ }
+ map.zoomTween.start(start, end, map.zoomDuration, {
+ minFrameRate: 50, // don't spend much time zooming
+ callbacks: {
+ eachStep: function(data) {
+ var containerOrigin = map.layerContainerOriginPx,
+ scale = data.scale,
+ dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
+ dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
+ map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
+ },
+ done: function(data) {
+ map.applyTransform();
+ var resolution = map.getResolution() / data.scale,
+ zoom = map.getZoomForResolution(resolution, true)
+ map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
+ }
+ }
+ });
+ }
+ } else {
+ var center = xy ?
+ map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
+ null;
+ map.setCenter(center, zoom);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: zoomIn
+ *
+ */
+ zoomIn: function() {
+ this.zoomTo(this.getZoom() + 1);
+ },
+
+ /**
+ * APIMethod: zoomOut
+ *
+ */
+ zoomOut: function() {
+ this.zoomTo(this.getZoom() - 1);
+ },
+
+ /**
+ * APIMethod: zoomToExtent
+ * Zoom to the passed in bounds, recenter
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToExtent: function(bounds, closest) {
+ if (!(bounds instanceof OpenLayers.Bounds)) {
+ bounds = new OpenLayers.Bounds(bounds);
+ }
+ var center = bounds.getCenterLonLat();
+ if (this.baseLayer.wrapDateLine) {
+ var maxExtent = this.getMaxExtent();
+
+ //fix straddling bounds (in the case of a bbox that straddles the
+ // dateline, it's left and right boundaries will appear backwards.
+ // we fix this by allowing a right value that is greater than the
+ // max value at the dateline -- this allows us to pass a valid
+ // bounds to calculate zoom)
+ //
+ bounds = bounds.clone();
+ while (bounds.right < bounds.left) {
+ bounds.right += maxExtent.getWidth();
+ }
+ //if the bounds was straddling (see above), then the center point
+ // we got from it was wrong. So we take our new bounds and ask it
+ // for the center.
+ //
+ center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
+ }
+ this.setCenter(center, this.getZoomForExtent(bounds, closest));
+ },
+
+ /**
+ * APIMethod: zoomToMaxExtent
+ * Zoom to the full extent and recenter.
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} True to zoom to restricted extent if it is
+ * set. Defaults to true.
+ */
+ zoomToMaxExtent: function(options) {
+ //restricted is true by default
+ var restricted = (options) ? options.restricted : true;
+
+ var maxExtent = this.getMaxExtent({
+ 'restricted': restricted
+ });
+ this.zoomToExtent(maxExtent);
+ },
+
+ /**
+ * APIMethod: zoomToScale
+ * Zoom to a specified scale
+ *
+ * Parameters:
+ * scale - {float}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified scale. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToScale: function(scale, closest) {
+ var res = OpenLayers.Util.getResolutionFromScale(scale,
+ this.baseLayer.units);
+
+ var halfWDeg = (this.size.w * res) / 2;
+ var halfHDeg = (this.size.h * res) / 2;
+ var center = this.getCachedCenter();
+
+ var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ this.zoomToExtent(extent, closest);
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate between */
+ /* LonLat, LayerPx, and ViewPortPx */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: LonLat <-> ViewPortPx
+ //
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port <OpenLayers.Pixel>, translated into lon/lat
+ * by the current base layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if (this.baseLayer != null) {
+ lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into view port
+ * pixels by the current base layer.
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var px = null;
+ if (this.baseLayer != null) {
+ px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
+ }
+ return px;
+ },
+
+ /**
+ * Method: getZoomTargetCenter
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
+ * resolution - {Float} The resolution we want to get the center for
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The location of the map center after the
+ * transformation described by the origin xy and the target resolution.
+ */
+ getZoomTargetCenter: function (xy, resolution) {
+ var lonlat = null,
+ size = this.getSize(),
+ deltaX = size.w/2 - xy.x,
+ deltaY = xy.y - size.h/2,
+ zoomPoint = this.getLonLatFromPixel(xy);
+ if (zoomPoint) {
+ lonlat = new OpenLayers.LonLat(
+ zoomPoint.lon + deltaX * resolution,
+ zoomPoint.lat + deltaY * resolution
+ );
+ }
+ return lonlat;
+ },
+
+ //
+ // CONVENIENCE TRANSLATION FUNCTIONS FOR API
+ //
+
+ /**
+ * APIMethod: getLonLatFromPixel
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
+ * OpenLayers.Pixel, translated into lon/lat by the
+ * current base layer
+ */
+ getLonLatFromPixel: function (px) {
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getPixelFromLonLat
+ * Returns a pixel location given a map location. The map location is
+ * translated to an integer pixel location (in viewport pixel
+ * coordinates) by the current base layer.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} A map location.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
+ * <OpenLayers.LonLat> translated into view port pixels by the current
+ * base layer.
+ */
+ getPixelFromLonLat: function (lonlat) {
+ var px = this.getViewPortPxFromLonLat(lonlat);
+ px.x = Math.round(px.x);
+ px.y = Math.round(px.y);
+ return px;
+ },
+
+ /**
+ * Method: getGeodesicPixelSize
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
+ * not provided, the center pixel of the map viewport will be used.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The geodesic size of the pixel in kilometers.
+ */
+ getGeodesicPixelSize: function(px) {
+ var lonlat = px ? this.getLonLatFromPixel(px) : (
+ this.getCachedCenter() || new OpenLayers.LonLat(0, 0));
+ var res = this.getResolution();
+ var left = lonlat.add(-res / 2, 0);
+ var right = lonlat.add(res / 2, 0);
+ var bottom = lonlat.add(0, -res / 2);
+ var top = lonlat.add(0, res / 2);
+ var dest = new OpenLayers.Projection("EPSG:4326");
+ var source = this.getProjectionObject() || dest;
+ if(!source.equals(dest)) {
+ left.transform(source, dest);
+ right.transform(source, dest);
+ bottom.transform(source, dest);
+ top.transform(source, dest);
+ }
+
+ return new OpenLayers.Size(
+ OpenLayers.Util.distVincenty(left, right),
+ OpenLayers.Util.distVincenty(bottom, top)
+ );
+ },
+
+
+
+ //
+ // TRANSLATION: ViewPortPx <-> LayerPx
+ //
+
+ /**
+ * APIMethod: getViewPortPxFromLayerPx
+ *
+ * Parameters:
+ * layerPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel
+ * coordinates
+ */
+ getViewPortPxFromLayerPx:function(layerPx) {
+ var viewPortPx = null;
+ if (layerPx != null) {
+ var dX = this.layerContainerOriginPx.x;
+ var dY = this.layerContainerOriginPx.y;
+ viewPortPx = layerPx.add(dX, dY);
+ }
+ return viewPortPx;
+ },
+
+ /**
+ * APIMethod: getLayerPxFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel
+ * coordinates
+ */
+ getLayerPxFromViewPortPx:function(viewPortPx) {
+ var layerPx = null;
+ if (viewPortPx != null) {
+ var dX = -this.layerContainerOriginPx.x;
+ var dY = -this.layerContainerOriginPx.y;
+ layerPx = viewPortPx.add(dX, dY);
+ if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
+ layerPx = null;
+ }
+ }
+ return layerPx;
+ },
+
+ //
+ // TRANSLATION: LonLat <-> LayerPx
+ //
+
+ /**
+ * Method: getLonLatFromLayerPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getLonLatFromLayerPx: function (px) {
+ //adjust for displacement of layerContainerDiv
+ px = this.getViewPortPxFromLayerPx(px);
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getLayerPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} lonlat
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into layer pixels
+ * by the current base layer
+ */
+ getLayerPxFromLonLat: function (lonlat) {
+ //adjust for displacement of layerContainerDiv
+ var px = this.getPixelFromLonLat(lonlat);
+ return this.getLayerPxFromViewPortPx(px);
+ },
+
+ /**
+ * Method: applyTransform
+ * Applies the given transform to the <layerContainerDiv>. This method has
+ * a 2-stage fallback from translate3d/scale3d via translate/scale to plain
+ * style.left/style.top, in which case no scaling is supported.
+ *
+ * Parameters:
+ * x - {Number} x parameter for the translation. Defaults to the x value of
+ * the map's <layerContainerOriginPx>
+ * y - {Number} y parameter for the translation. Defaults to the y value of
+ * the map's <layerContainerOriginPx>
+ * scale - {Number} scale. Defaults to 1 if not provided.
+ */
+ applyTransform: function(x, y, scale) {
+ scale = scale || 1;
+ var origin = this.layerContainerOriginPx,
+ needTransform = scale !== 1;
+ x = x || origin.x;
+ y = y || origin.y;
+
+ var style = this.layerContainerDiv.style,
+ transform = this.applyTransform.transform,
+ template = this.applyTransform.template;
+
+ if (transform === undefined) {
+ transform = OpenLayers.Util.vendorPrefix.style('transform');
+ this.applyTransform.transform = transform;
+ if (transform) {
+ // Try translate3d, but only if the viewPortDiv has a transform
+ // defined in a stylesheet
+ var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
+ OpenLayers.Util.vendorPrefix.css('transform'));
+ if (!computedStyle || computedStyle !== 'none') {
+ template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
+ style[transform] = [template[0], '0,0', template[1]].join('');
+ }
+ // If no transform is defined in the stylesheet or translate3d
+ // does not stick, use translate and scale
+ if (!template || !~style[transform].indexOf(template[0])) {
+ template = ['translate(', ') ', 'scale(', ')'];
+ }
+ this.applyTransform.template = template;
+ }
+ }
+
+ // If we do 3d transforms, we always want to use them. If we do 2d
+ // transforms, we only use them when we need to.
+ if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
+ // Our 2d transforms are combined with style.left and style.top, so
+ // adjust x and y values and set the origin as left and top
+ if (needTransform === true && template[0] === 'translate(') {
+ x -= origin.x;
+ y -= origin.y;
+ style.left = origin.x + 'px';
+ style.top = origin.y + 'px';
+ }
+ style[transform] = [
+ template[0], x, 'px,', y, 'px', template[1],
+ template[2], scale, ',', scale, template[3]
+ ].join('');
+ } else {
+ style.left = x + 'px';
+ style.top = y + 'px';
+ // We previously might have had needTransform, so remove transform
+ if (transform !== null) {
+ style[transform] = '';
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Map"
+});
+
+/**
+ * Constant: TILE_WIDTH
+ * {Integer} 256 Default tile width (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_WIDTH = 256;
+/**
+ * Constant: TILE_HEIGHT
+ * {Integer} 256 Default tile height (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_HEIGHT = 256;
+/* ======================================================================
+ OpenLayers/Handler.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Handler
+ * Base class to construct a higher-level handler for event sequences. All
+ * handlers have activate and deactivate methods. In addition, they have
+ * methods named like browser events. When a handler is activated, any
+ * additional methods named like a browser event is registered as a
+ * listener for the corresponding event. When a handler is deactivated,
+ * those same methods are unregistered as event listeners.
+ *
+ * Handlers also typically have a callbacks object with keys named like
+ * the abstracted events or event sequences that they are in charge of
+ * handling. The controls that wrap handlers define the methods that
+ * correspond to these abstract events - so instead of listening for
+ * individual browser events, they only listen for the abstract events
+ * defined by the handler.
+ *
+ * Handlers are created by controls, which ultimately have the responsibility
+ * of making changes to the the state of the application. Handlers
+ * themselves may make temporary changes, but in general are expected to
+ * return the application in the same state that they found it.
+ */
+OpenLayers.Handler = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: control
+ * {<OpenLayers.Control>}. The control that initialized this handler. The
+ * control is assumed to have a valid map property - that map is used
+ * in the handler's own setMap method.
+ */
+ control: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>}
+ */
+ map: null,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
+ * constants to construct a keyMask. The keyMask is used by
+ * <checkModifiers>. If the keyMask matches the combination of keys
+ * down on an event, checkModifiers returns true.
+ *
+ * Example:
+ * (code)
+ * // handler only responds if the Shift key is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
+ *
+ * // handler only responds if Ctrl-Shift is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
+ * OpenLayers.Handler.MOD_CTRL;
+ * (end)
+ */
+ keyMask: null,
+
+ /**
+ * Property: active
+ * {Boolean}
+ */
+ active: false,
+
+ /**
+ * Property: evt
+ * {Event} This property references the last event handled by the handler.
+ * Note that this property is not part of the stable API. Use of the
+ * evt property should be restricted to controls in the library
+ * or other applications that are willing to update with changes to
+ * the OpenLayers code.
+ */
+ evt: null,
+
+ /**
+ * Property: touch
+ * {Boolean} Indicates the support of touch events. When touch events are
+ * started touch will be true and all mouse related listeners will do
+ * nothing.
+ */
+ touch: false,
+
+ /**
+ * Constructor: OpenLayers.Handler
+ * Construct a handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that initialized this
+ * handler. The control is assumed to have a valid map property; that
+ * map is used in the handler's own setMap method. If a map property
+ * is present in the options argument it will be used instead.
+ * callbacks - {Object} An object whose properties correspond to abstracted
+ * events or sequences of browser events. The values for these
+ * properties are functions defined by the control that get called by
+ * the handler.
+ * options - {Object} An optional object whose properties will be set on
+ * the handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Util.extend(this, options);
+ this.control = control;
+ this.callbacks = callbacks;
+
+ var map = this.map || control.map;
+ if (map) {
+ this.setMap(map);
+ }
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ this.map = map;
+ },
+
+ /**
+ * Method: checkModifiers
+ * Check the keyMask on the handler. If no <keyMask> is set, this always
+ * returns true. If a <keyMask> is set and it matches the combination
+ * of keys down on an event, this returns true.
+ *
+ * Returns:
+ * {Boolean} The keyMask matches the keys down on an event.
+ */
+ checkModifiers: function (evt) {
+ if(this.keyMask == null) {
+ return true;
+ }
+ /* calculate the keyboard modifier mask for this event */
+ var keyModifiers =
+ (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
+ (evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) |
+ (evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) |
+ (evt.metaKey ? OpenLayers.Handler.MOD_META : 0);
+
+ /* if it differs from the handler object's key mask,
+ bail out of the event handler */
+ return (keyModifiers == this.keyMask);
+ },
+
+ /**
+ * APIMethod: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean} The handler was activated.
+ */
+ activate: function() {
+ if(this.active) {
+ return false;
+ }
+ // register for event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.register(events[i], this[events[i]]);
+ }
+ }
+ this.active = true;
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler. Returns false if the handler was already inactive.
+ *
+ * Returns:
+ * {Boolean} The handler was deactivated.
+ */
+ deactivate: function() {
+ if(!this.active) {
+ return false;
+ }
+ // unregister event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ this.touch = false;
+ this.active = false;
+ return true;
+ },
+
+ /**
+ * Method: startTouch
+ * Start touch events, this method must be called by subclasses in
+ * "touchstart" method. When touch events are started <touch> will be
+ * true and all mouse related listeners will do nothing.
+ */
+ startTouch: function() {
+ if (!this.touch) {
+ this.touch = true;
+ var events = [
+ "mousedown", "mouseup", "mousemove", "click", "dblclick",
+ "mouseout"
+ ];
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array(*)} An array of arguments (any type) with which to call
+ * the callback (defined by the control).
+ */
+ callback: function (name, args) {
+ if (name && this.callbacks[name]) {
+ this.callbacks[name].apply(this.control, args);
+ }
+ },
+
+ /**
+ * Method: register
+ * register an event on the map
+ */
+ register: function (name, method) {
+ // TODO: deal with registerPriority in 3.0
+ this.map.events.registerPriority(name, this, method);
+ this.map.events.registerPriority(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: unregister
+ * unregister an event from the map
+ */
+ unregister: function (name, method) {
+ this.map.events.unregister(name, this, method);
+ this.map.events.unregister(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: setEvent
+ * With each registered browser event, the handler sets its own evt
+ * property. This property can be accessed by controls if needed
+ * to get more information about the event that the handler is
+ * processing.
+ *
+ * This allows modifier keys on the event to be checked (alt, shift, ctrl,
+ * and meta cannot be checked with the keyboard handler). For a
+ * control to determine which modifier keys are associated with the
+ * event that a handler is currently processing, it should access
+ * (code)handler.evt.altKey || handler.evt.shiftKey ||
+ * handler.evt.ctrlKey || handler.evt.metaKey(end).
+ *
+ * Parameters:
+ * evt - {Event} The browser event.
+ */
+ setEvent: function(evt) {
+ this.evt = evt;
+ return true;
+ },
+
+ /**
+ * Method: destroy
+ * Deconstruct the handler.
+ */
+ destroy: function () {
+ // unregister event listeners
+ this.deactivate();
+ // eliminate circular references
+ this.control = this.map = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler"
+});
+
+/**
+ * Constant: OpenLayers.Handler.MOD_NONE
+ * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
+ */
+OpenLayers.Handler.MOD_NONE = 0;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_SHIFT
+ * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
+ */
+OpenLayers.Handler.MOD_SHIFT = 1;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_CTRL
+ * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
+ */
+OpenLayers.Handler.MOD_CTRL = 2;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_ALT
+ * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
+ */
+OpenLayers.Handler.MOD_ALT = 4;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_META
+ * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
+ */
+OpenLayers.Handler.MOD_META = 8;
+
+
+/* ======================================================================
+ OpenLayers/Handler/Click.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Click
+ * A handler for mouse clicks. The intention of this handler is to give
+ * controls more flexibility with handling clicks. Browsers trigger
+ * click events twice for a double-click. In addition, the mousedown,
+ * mousemove, mouseup sequence fires a click event. With this handler,
+ * controls can decide whether to ignore clicks associated with a double
+ * click. By setting a <pixelTolerance>, controls can also ignore clicks
+ * that include a drag. Create a new instance with the
+ * <OpenLayers.Handler.Click> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * APIProperty: delay
+ * {Number} Number of milliseconds between clicks before the event is
+ * considered a double-click.
+ */
+ delay: 300,
+
+ /**
+ * APIProperty: single
+ * {Boolean} Handle single clicks. Default is true. If false, clicks
+ * will not be reported. If true, single-clicks will be reported.
+ */
+ single: true,
+
+ /**
+ * APIProperty: double
+ * {Boolean} Handle double-clicks. Default is false.
+ */
+ 'double': false,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between mouseup and mousedown for an
+ * event to be considered a click. Default is 0. If set to an
+ * integer value, clicks with a drag greater than the value will be
+ * ignored. This property can only be set when the handler is
+ * constructed.
+ */
+ pixelTolerance: 0,
+
+ /**
+ * APIProperty: dblclickTolerance
+ * {Number} Maximum distance in pixels between clicks for a sequence of
+ * events to be considered a double click. Default is 13. If the
+ * distance between two clicks is greater than this value, a double-
+ * click will not be fired.
+ */
+ dblclickTolerance: 13,
+
+ /**
+ * APIProperty: stopSingle
+ * {Boolean} Stop other listeners from being notified of clicks. Default
+ * is false. If true, any listeners registered before this one for
+ * click or rightclick events will not be notified.
+ */
+ stopSingle: false,
+
+ /**
+ * APIProperty: stopDouble
+ * {Boolean} Stop other listeners from being notified of double-clicks.
+ * Default is false. If true, any click listeners registered before
+ * this one will not be notified of *any* double-click events.
+ *
+ * The one caveat with stopDouble is that given a map with two click
+ * handlers, one with stopDouble true and the other with stopSingle
+ * true, the stopSingle handler should be activated last to get
+ * uniform cross-browser performance. Since IE triggers one click
+ * with a dblclick and FF triggers two, if a stopSingle handler is
+ * activated first, all it gets in IE is a single click when the
+ * second handler stops propagation on the dblclick.
+ */
+ stopDouble: false,
+
+ /**
+ * Property: timerId
+ * {Number} The id of the timeout waiting to clear the <delayedCall>.
+ */
+ timerId: null,
+
+ /**
+ * Property: down
+ * {Object} Object that store relevant information about the last
+ * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ down: null,
+
+ /**
+ * Property: last
+ * {Object} Object that store relevant information about the last
+ * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ last: null,
+
+ /**
+ * Property: first
+ * {Object} When waiting for double clicks, this object will store
+ * information about the first click in a two click sequence.
+ */
+ first: null,
+
+ /**
+ * Property: rightclickTimerId
+ * {Number} The id of the right mouse timeout waiting to clear the
+ * <delayedEvent>.
+ */
+ rightclickTimerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Click
+ * Create a new click handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handler's setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object with keys corresponding to callbacks
+ * that will be called by the handler. The callbacks should
+ * expect to recieve a single argument, the click event.
+ * Callbacks for 'click' and 'dblclick' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchmove
+ * Store position of last move, because touchend event can have
+ * an empty "touches" property.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchmove: function(evt) {
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Correctly set event xy property, and add lastTouches to have
+ * touches property from last touchstart or touchmove
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchend: function(evt) {
+ // touchstart may not have been allowed to propagate
+ if (this.down) {
+ evt.xy = this.last.xy;
+ evt.lastTouches = this.last.touches;
+ this.handleSingle(evt);
+ this.down = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mousedown: function(evt) {
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup. Installed to support collection of right mouse events.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mouseup: function (evt) {
+ var propagate = true;
+
+ // Collect right mouse clicks from the mouseup
+ // IE - ignores the second right click in mousedown so using
+ // mouseup instead
+ if (this.checkModifiers(evt) && this.control.handleRightClicks &&
+ OpenLayers.Event.isRightClick(evt)) {
+ propagate = this.rightclick(evt);
+ }
+
+ return propagate;
+ },
+
+ /**
+ * Method: rightclick
+ * Handle rightclick. For a dblrightclick, we get two clicks so we need
+ * to always register for dblrightclick to properly handle single
+ * clicks.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ rightclick: function(evt) {
+ if(this.passesTolerance(evt)) {
+ if(this.rightclickTimerId != null) {
+ //Second click received before timeout this must be
+ // a double click
+ this.clearTimer();
+ this.callback('dblrightclick', [evt]);
+ return !this.stopDouble;
+ } else {
+ //Set the rightclickTimerId, send evt only if double is
+ // true else trigger single
+ var clickEvent = this['double'] ?
+ OpenLayers.Util.extend({}, evt) :
+ this.callback('rightclick', [evt]);
+
+ var delayedRightCall = OpenLayers.Function.bind(
+ this.delayedRightCall,
+ this,
+ clickEvent
+ );
+ this.rightclickTimerId = window.setTimeout(
+ delayedRightCall, this.delay
+ );
+ }
+ }
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: delayedRightCall
+ * Sets <rightclickTimerId> to null. And optionally triggers the
+ * rightclick callback if evt is set.
+ */
+ delayedRightCall: function(evt) {
+ this.rightclickTimerId = null;
+ if (evt) {
+ this.callback('rightclick', [evt]);
+ }
+ },
+
+ /**
+ * Method: click
+ * Handle click events from the browser. This is registered as a listener
+ * for click events and should not be called from other events in this
+ * handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ click: function(evt) {
+ if (!this.last) {
+ this.last = this.getEventInfo(evt);
+ }
+ this.handleSingle(evt);
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. For a dblclick, we get two clicks in some browsers
+ * (FF) and one in others (IE). So we need to always register for
+ * dblclick to properly handle single clicks. This method is registered
+ * as a listener for the dblclick browser event. It should *not* be
+ * called by other methods in this handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ dblclick: function(evt) {
+ this.handleDouble(evt);
+ return !this.stopDouble;
+ },
+
+ /**
+ * Method: handleDouble
+ * Handle double-click sequence.
+ */
+ handleDouble: function(evt) {
+ if (this.passesDblclickTolerance(evt)) {
+ if (this["double"]) {
+ this.callback("dblclick", [evt]);
+ }
+ // to prevent a dblclick from firing the click callback in IE
+ this.clearTimer();
+ }
+ },
+
+ /**
+ * Method: handleSingle
+ * Handle single click sequence.
+ */
+ handleSingle: function(evt) {
+ if (this.passesTolerance(evt)) {
+ if (this.timerId != null) {
+ // already received a click
+ if (this.last.touches && this.last.touches.length === 1) {
+ // touch device, no dblclick event - this may be a double
+ if (this["double"]) {
+ // on Android don't let the browser zoom on the page
+ OpenLayers.Event.preventDefault(evt);
+ }
+ this.handleDouble(evt);
+ }
+ // if we're not in a touch environment we clear the click timer
+ // if we've got a second touch, we'll get two touchend events
+ if (!this.last.touches || this.last.touches.length !== 2) {
+ this.clearTimer();
+ }
+ } else {
+ // remember the first click info so we can compare to the second
+ this.first = this.getEventInfo(evt);
+ // set the timer, send evt only if single is true
+ //use a clone of the event object because it will no longer
+ //be a valid event object in IE in the timer callback
+ var clickEvent = this.single ?
+ OpenLayers.Util.extend({}, evt) : null;
+ this.queuePotentialClick(clickEvent);
+ }
+ }
+ },
+
+ /**
+ * Method: queuePotentialClick
+ * This method is separated out largely to make testing easier (so we
+ * don't have to override window.setTimeout)
+ */
+ queuePotentialClick: function(evt) {
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance. Note
+ * that the pixel tolerance check only works if mousedown events get to
+ * the listeners registered here. If they are stopped by other elements,
+ * the <pixelTolerance> will have no effect here (this method will always
+ * return true).
+ *
+ * Returns:
+ * {Boolean} The click is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(evt) {
+ var passes = true;
+ if (this.pixelTolerance != null && this.down && this.down.xy) {
+ passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
+ // for touch environments, we also enforce that all touches
+ // start and end within the given tolerance to be considered a click
+ if (passes && this.touch &&
+ this.down.touches.length === this.last.touches.length) {
+ // the touchend event doesn't come with touches, so we check
+ // down and last
+ for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
+ if (this.getTouchDistance(
+ this.down.touches[i],
+ this.last.touches[i]
+ ) > this.pixelTolerance) {
+ passes = false;
+ break;
+ }
+ }
+ }
+ }
+ return passes;
+ },
+
+ /**
+ * Method: getTouchDistance
+ *
+ * Returns:
+ * {Boolean} The pixel displacement between two touches.
+ */
+ getTouchDistance: function(from, to) {
+ return Math.sqrt(
+ Math.pow(from.clientX - to.clientX, 2) +
+ Math.pow(from.clientY - to.clientY, 2)
+ );
+ },
+
+ /**
+ * Method: passesDblclickTolerance
+ * Determine whether the event is within the optional double-cick pixel
+ * tolerance.
+ *
+ * Returns:
+ * {Boolean} The click is within the double-click pixel tolerance.
+ */
+ passesDblclickTolerance: function(evt) {
+ var passes = true;
+ if (this.down && this.first) {
+ passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
+ }
+ return passes;
+ },
+
+ /**
+ * Method: clearTimer
+ * Clear the timer and set <timerId> to null.
+ */
+ clearTimer: function() {
+ if (this.timerId != null) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ if (this.rightclickTimerId != null) {
+ window.clearTimeout(this.rightclickTimerId);
+ this.rightclickTimerId = null;
+ }
+ },
+
+ /**
+ * Method: delayedCall
+ * Sets <timerId> to null. And optionally triggers the click callback if
+ * evt is set.
+ */
+ delayedCall: function(evt) {
+ this.timerId = null;
+ if (evt) {
+ this.callback("click", [evt]);
+ }
+ },
+
+ /**
+ * Method: getEventInfo
+ * This method allows us to store event information without storing the
+ * actual event. In touch devices (at least), the same event is
+ * modified between touchstart, touchmove, and touchend.
+ *
+ * Returns:
+ * {Object} An object with event related info.
+ */
+ getEventInfo: function(evt) {
+ var touches;
+ if (evt.touches) {
+ var len = evt.touches.length;
+ touches = new Array(len);
+ var touch;
+ for (var i=0; i<len; i++) {
+ touch = evt.touches[i];
+ touches[i] = {
+ clientX: touch.olClientX,
+ clientY: touch.olClientY
+ };
+ }
+ }
+ return {
+ xy: evt.xy,
+ touches: touches
+ };
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.clearTimer();
+ this.down = null;
+ this.first = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Click"
+});
+/* ======================================================================
+ OpenLayers/Handler/Drag.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Drag
+ * The drag handler is used to deal with sequences of browser events related
+ * to dragging. The handler is used by controls that want to know when
+ * a drag sequence begins, when a drag is happening, and when it has
+ * finished.
+ *
+ * Controls that use the drag handler typically construct it with callbacks
+ * for 'down', 'move', and 'done'. Callbacks for these keys are called
+ * when the drag begins, with each move, and when the drag is done. In
+ * addition, controls can have callbacks keyed to 'up' and 'out' if they
+ * care to differentiate between the types of events that correspond with
+ * the end of a drag sequence. If no drag actually occurs (no mouse move)
+ * the 'down' and 'up' callbacks will be called, but not the 'done'
+ * callback.
+ *
+ * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a mousedown or touchstart event is received, we want to
+ * record it, but not set 'dragging' until the mouse moves after starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of mousedown events from getting to listeners
+ * on the same element. Default is true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: dragging
+ * {Boolean}
+ */
+ dragging: false,
+
+ /**
+ * Property: last
+ * {<OpenLayers.Pixel>} The last pixel location of the drag.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {<OpenLayers.Pixel>} The first pixel location of the drag.
+ */
+ start: null,
+
+ /**
+ * Property: lastMoveEvt
+ * {Object} The last mousemove event that occurred. Used to
+ * position the map correctly when our "delay drag"
+ * timeout expired.
+ */
+ lastMoveEvt: null,
+
+ /**
+ * Property: oldOnselectstart
+ * {Function}
+ */
+ oldOnselectstart: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase performance, an interval (in
+ * milliseconds) can be set to reduce the number of drag events
+ * called. If set, a new drag event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: timeoutId
+ * {String} The id of the timeout used for the mousedown interval.
+ * This is "private", and should be left alone.
+ */
+ timeoutId: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, the handler will also handle mouse moves when
+ * the cursor has moved out of the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: documentEvents
+ * {Boolean} Are we currently observing document events?
+ */
+ documentEvents: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Drag
+ * Returns OpenLayers.Handler.Drag
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'move' and 'done' are supported. You can also speficy
+ * callbacks for 'down', 'up', and 'out' to respond to those events.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+
+ if (this.documentDrag === true) {
+ var me = this;
+ this._docMove = function(evt) {
+ me.mousemove({
+ xy: {x: evt.clientX, y: evt.clientY},
+ element: document
+ });
+ };
+ this._docUp = function(evt) {
+ me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
+ };
+ }
+ },
+
+
+ /**
+ * Method: dragstart
+ * This private method is factorized from mousedown and touchstart methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragstart: function (evt) {
+ var propagate = true;
+ this.dragging = false;
+ if (this.checkModifiers(evt) &&
+ (OpenLayers.Event.isLeftClick(evt) ||
+ OpenLayers.Event.isSingleTouch(evt))) {
+ this.started = true;
+ this.start = evt.xy;
+ this.last = evt.xy;
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.down(evt);
+ this.callback("down", [evt.xy]);
+
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart ?
+ document.onselectstart : OpenLayers.Function.True;
+ }
+ document.onselectstart = OpenLayers.Function.False;
+
+ propagate = !this.stopDown;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: dragmove
+ * This private method is factorized from mousemove and touchmove methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragmove: function (evt) {
+ this.lastMoveEvt = evt;
+ if (this.started && !this.timeoutId && (evt.xy.x != this.last.x ||
+ evt.xy.y != this.last.y)) {
+ if(this.documentDrag === true && this.documentEvents) {
+ if(evt.element === document) {
+ this.adjustXY(evt);
+ // do setEvent manually because the documentEvents are not
+ // registered with the map
+ this.setEvent(evt);
+ } else {
+ this.removeDocumentEvents();
+ }
+ }
+ if (this.interval > 0) {
+ this.timeoutId = setTimeout(
+ OpenLayers.Function.bind(this.removeTimeout, this),
+ this.interval);
+ }
+ this.dragging = true;
+
+ this.move(evt);
+ this.callback("move", [evt.xy]);
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart;
+ document.onselectstart = OpenLayers.Function.False;
+ }
+ this.last = evt.xy;
+ }
+ return true;
+ },
+
+ /**
+ * Method: dragend
+ * This private method is factorized from mouseup and touchend methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragend: function (evt) {
+ if (this.started) {
+ if(this.documentDrag === true && this.documentEvents) {
+ this.adjustXY(evt);
+ this.removeDocumentEvents();
+ }
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.up(evt);
+ this.callback("up", [evt.xy]);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ document.onselectstart = this.oldOnselectstart;
+ }
+ return true;
+ },
+
+ /**
+ * The four methods below (down, move, up, and out) are used by subclasses
+ * to do their own processing related to these mouse events.
+ */
+
+ /**
+ * Method: down
+ * This method is called during the handling of the mouse down event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse down event
+ */
+ down: function(evt) {
+ },
+
+ /**
+ * Method: move
+ * This method is called during the handling of the mouse move event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse move event
+ *
+ */
+ move: function(evt) {
+ },
+
+ /**
+ * Method: up
+ * This method is called during the handling of the mouse up event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ },
+
+ /**
+ * Method: out
+ * This method is called during the handling of the mouse out event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ },
+
+ /**
+ * The methods below are part of the magic of event handling. Because
+ * they are named like browser events, they are registered as listeners
+ * for the events they represent.
+ */
+
+ /**
+ * Method: mousedown
+ * Handle mousedown events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousedown: function(evt) {
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousemove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: removeTimeout
+ * Private. Called by mousemove() to remove the drag timeout.
+ */
+ removeTimeout: function() {
+ this.timeoutId = null;
+ // if timeout expires while we're still dragging (mouseup
+ // hasn't occurred) then call mousemove to move to the
+ // correct position
+ if(this.dragging) {
+ this.mousemove(this.lastMoveEvt);
+ }
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseup: function(evt) {
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ // override evt.xy with last position since touchend does not have
+ // any touch position
+ evt.xy = this.last;
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouseout events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseout: function (evt) {
+ if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if(this.documentDrag === true) {
+ this.addDocumentEvents();
+ } else {
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.out(evt);
+ this.callback("out", []);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ if(document.onselectstart) {
+ document.onselectstart = this.oldOnselectstart;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Method: click
+ * The drag handler captures the click event. If something else registers
+ * for clicks on the same element, its listener will not be called
+ * after a drag.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ click: function (evt) {
+ // let the click event propagate only if the mouse moved
+ return (this.start == this.last);
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragging = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.dragging = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: adjustXY
+ * Converts event coordinates that are relative to the document body to
+ * ones that are relative to the map viewport. The latter is the default in
+ * OpenLayers.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ adjustXY: function(evt) {
+ var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
+ evt.xy.x -= pos[0];
+ evt.xy.y -= pos[1];
+ },
+
+ /**
+ * Method: addDocumentEvents
+ * Start observing document events when documentDrag is true and the mouse
+ * cursor leaves the map viewport while dragging.
+ */
+ addDocumentEvents: function() {
+ OpenLayers.Element.addClass(document.body, "olDragDown");
+ this.documentEvents = true;
+ OpenLayers.Event.observe(document, "mousemove", this._docMove);
+ OpenLayers.Event.observe(document, "mouseup", this._docUp);
+ },
+
+ /**
+ * Method: removeDocumentEvents
+ * Stops observing document events when documentDrag is true and the mouse
+ * cursor re-enters the map viewport while dragging.
+ */
+ removeDocumentEvents: function() {
+ OpenLayers.Element.removeClass(document.body, "olDragDown");
+ this.documentEvents = false;
+ OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
+ OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Drag"
+});
+/* ======================================================================
+ OpenLayers/Control/OverviewMap.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Events/buttonclick.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.OverviewMap
+ * The OverMap control creates a small overview map, useful to display the
+ * extent of a zoomed map and your main map and provide additional
+ * navigation options to the User. By default the overview map is drawn in
+ * the lower right corner of the main map. Create a new overview map with the
+ * <OpenLayers.Control.OverviewMap> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.OverviewMap = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: element
+ * {DOMElement} The DOM element that contains the overview map
+ */
+ element: null,
+
+ /**
+ * APIProperty: ovmap
+ * {<OpenLayers.Map>} A reference to the overview map itself.
+ */
+ ovmap: null,
+
+ /**
+ * APIProperty: size
+ * {<OpenLayers.Size>} The overvew map size in pixels. Note that this is
+ * the size of the map itself - the element that contains the map (default
+ * class name olControlOverviewMapElement) may have padding or other style
+ * attributes added via CSS.
+ */
+ size: {w: 180, h: 90},
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the overview map.
+ * If none are sent at construction, the base layer for the main map is used.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: minRectSize
+ * {Integer} The minimum width or height (in pixels) of the extent
+ * rectangle on the overview map. When the extent rectangle reaches
+ * this size, it will be replaced depending on the value of the
+ * <minRectDisplayClass> property. Default is 15 pixels.
+ */
+ minRectSize: 15,
+
+ /**
+ * APIProperty: minRectDisplayClass
+ * {String} Replacement style class name for the extent rectangle when
+ * <minRectSize> is reached. This string will be suffixed on to the
+ * displayClass. Default is "RectReplacement".
+ *
+ * Example CSS declaration:
+ * (code)
+ * .olControlOverviewMapRectReplacement {
+ * overflow: hidden;
+ * cursor: move;
+ * background-image: url("img/overview_replacement.gif");
+ * background-repeat: no-repeat;
+ * background-position: center;
+ * }
+ * (end)
+ */
+ minRectDisplayClass: "RectReplacement",
+
+ /**
+ * APIProperty: minRatio
+ * {Float} The ratio of the overview map resolution to the main map
+ * resolution at which to zoom farther out on the overview map.
+ */
+ minRatio: 8,
+
+ /**
+ * APIProperty: maxRatio
+ * {Float} The ratio of the overview map resolution to the main map
+ * resolution at which to zoom farther in on the overview map.
+ */
+ maxRatio: 32,
+
+ /**
+ * APIProperty: mapOptions
+ * {Object} An object containing any non-default properties to be sent to
+ * the overview map's map constructor. These should include any
+ * non-default options that the main map was constructed with.
+ */
+ mapOptions: null,
+
+ /**
+ * APIProperty: autoPan
+ * {Boolean} Always pan the overview map, so the extent marker remains in
+ * the center. Default is false. If true, when you drag the extent
+ * marker, the overview map will update itself so the marker returns
+ * to the center.
+ */
+ autoPan: false,
+
+ /**
+ * Property: handlers
+ * {Object}
+ */
+ handlers: null,
+
+ /**
+ * Property: resolutionFactor
+ * {Object}
+ */
+ resolutionFactor: 1,
+
+ /**
+ * APIProperty: maximized
+ * {Boolean} Start as maximized (visible). Defaults to false.
+ */
+ maximized: false,
+
+ /**
+ * APIProperty: maximizeTitle
+ * {String} This property is used for showing a tooltip over the
+ * maximize div. Defaults to "" (no title).
+ */
+ maximizeTitle: "",
+
+ /**
+ * APIProperty: minimizeTitle
+ * {String} This property is used for showing a tooltip over the
+ * minimize div. Defaults to "" (no title).
+ */
+ minimizeTitle: "",
+
+ /**
+ * Constructor: OpenLayers.Control.OverviewMap
+ * Create a new overview map
+ *
+ * Parameters:
+ * options - {Object} Properties of this object will be set on the overview
+ * map object. Note, to set options on the map object contained in this
+ * control, set <mapOptions> as one of the options properties.
+ */
+ initialize: function(options) {
+ this.layers = [];
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct the control
+ */
+ destroy: function() {
+ if (!this.mapDiv) { // we've already been destroyed
+ return;
+ }
+ if (this.handlers.click) {
+ this.handlers.click.destroy();
+ }
+ if (this.handlers.drag) {
+ this.handlers.drag.destroy();
+ }
+
+ this.ovmap && this.ovmap.viewPortDiv.removeChild(this.extentRectangle);
+ this.extentRectangle = null;
+
+ if (this.rectEvents) {
+ this.rectEvents.destroy();
+ this.rectEvents = null;
+ }
+
+ if (this.ovmap) {
+ this.ovmap.destroy();
+ this.ovmap = null;
+ }
+
+ this.element.removeChild(this.mapDiv);
+ this.mapDiv = null;
+
+ this.div.removeChild(this.element);
+ this.element = null;
+
+ if (this.maximizeDiv) {
+ this.div.removeChild(this.maximizeDiv);
+ this.maximizeDiv = null;
+ }
+
+ if (this.minimizeDiv) {
+ this.div.removeChild(this.minimizeDiv);
+ this.minimizeDiv = null;
+ }
+
+ this.map.events.un({
+ buttonclick: this.onButtonClick,
+ moveend: this.update,
+ changebaselayer: this.baseLayerDraw,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Render the control in the browser.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.layers.length === 0) {
+ if (this.map.baseLayer) {
+ var layer = this.map.baseLayer.clone();
+ this.layers = [layer];
+ } else {
+ this.map.events.register("changebaselayer", this, this.baseLayerDraw);
+ return this.div;
+ }
+ }
+
+ // create overview map DOM elements
+ this.element = document.createElement('div');
+ this.element.className = this.displayClass + 'Element';
+ this.element.style.display = 'none';
+
+ this.mapDiv = document.createElement('div');
+ this.mapDiv.style.width = this.size.w + 'px';
+ this.mapDiv.style.height = this.size.h + 'px';
+ this.mapDiv.style.position = 'relative';
+ this.mapDiv.style.overflow = 'hidden';
+ this.mapDiv.id = OpenLayers.Util.createUniqueID('overviewMap');
+
+ this.extentRectangle = document.createElement('div');
+ this.extentRectangle.style.position = 'absolute';
+ this.extentRectangle.style.zIndex = 1000; //HACK
+ this.extentRectangle.className = this.displayClass+'ExtentRectangle';
+
+ this.element.appendChild(this.mapDiv);
+
+ this.div.appendChild(this.element);
+
+ // Optionally add min/max buttons if the control will go in the
+ // map viewport.
+ if(!this.outsideViewport) {
+ this.div.className += " " + this.displayClass + 'Container';
+ // maximize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
+ this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ this.displayClass + 'MaximizeButton',
+ null,
+ null,
+ img,
+ 'absolute');
+ this.maximizeDiv.style.display = 'none';
+ this.maximizeDiv.className = this.displayClass + 'MaximizeButton olButton';
+ if (this.maximizeTitle) {
+ this.maximizeDiv.title = this.maximizeTitle;
+ }
+ this.div.appendChild(this.maximizeDiv);
+
+ // minimize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
+ this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ 'OpenLayers_Control_minimizeDiv',
+ null,
+ null,
+ img,
+ 'absolute');
+ this.minimizeDiv.style.display = 'none';
+ this.minimizeDiv.className = this.displayClass + 'MinimizeButton olButton';
+ if (this.minimizeTitle) {
+ this.minimizeDiv.title = this.minimizeTitle;
+ }
+ this.div.appendChild(this.minimizeDiv);
+ this.minimizeControl();
+ } else {
+ // show the overview map
+ this.element.style.display = '';
+ }
+ if(this.map.getExtent()) {
+ this.update();
+ }
+
+ this.map.events.on({
+ buttonclick: this.onButtonClick,
+ moveend: this.update,
+ scope: this
+ });
+
+ if (this.maximized) {
+ this.maximizeControl();
+ }
+ return this.div;
+ },
+
+ /**
+ * Method: baseLayerDraw
+ * Draw the base layer - called if unable to complete in the initial draw
+ */
+ baseLayerDraw: function() {
+ this.draw();
+ this.map.events.unregister("changebaselayer", this, this.baseLayerDraw);
+ },
+
+ /**
+ * Method: rectDrag
+ * Handle extent rectangle drag
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel location of the drag.
+ */
+ rectDrag: function(px) {
+ var deltaX = this.handlers.drag.last.x - px.x;
+ var deltaY = this.handlers.drag.last.y - px.y;
+ if(deltaX != 0 || deltaY != 0) {
+ var rectTop = this.rectPxBounds.top;
+ var rectLeft = this.rectPxBounds.left;
+ var rectHeight = Math.abs(this.rectPxBounds.getHeight());
+ var rectWidth = this.rectPxBounds.getWidth();
+ // don't allow dragging off of parent element
+ var newTop = Math.max(0, (rectTop - deltaY));
+ newTop = Math.min(newTop,
+ this.ovmap.size.h - this.hComp - rectHeight);
+ var newLeft = Math.max(0, (rectLeft - deltaX));
+ newLeft = Math.min(newLeft,
+ this.ovmap.size.w - this.wComp - rectWidth);
+ this.setRectPxBounds(new OpenLayers.Bounds(newLeft,
+ newTop + rectHeight,
+ newLeft + rectWidth,
+ newTop));
+ }
+ },
+
+ /**
+ * Method: mapDivClick
+ * Handle browser events
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>} evt
+ */
+ mapDivClick: function(evt) {
+ var pxCenter = this.rectPxBounds.getCenterPixel();
+ var deltaX = evt.xy.x - pxCenter.x;
+ var deltaY = evt.xy.y - pxCenter.y;
+ var top = this.rectPxBounds.top;
+ var left = this.rectPxBounds.left;
+ var height = Math.abs(this.rectPxBounds.getHeight());
+ var width = this.rectPxBounds.getWidth();
+ var newTop = Math.max(0, (top + deltaY));
+ newTop = Math.min(newTop, this.ovmap.size.h - height);
+ var newLeft = Math.max(0, (left + deltaX));
+ newLeft = Math.min(newLeft, this.ovmap.size.w - width);
+ this.setRectPxBounds(new OpenLayers.Bounds(newLeft,
+ newTop + height,
+ newLeft + width,
+ newTop));
+ this.updateMapToRect();
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ if (evt.buttonElement === this.minimizeDiv) {
+ this.minimizeControl();
+ } else if (evt.buttonElement === this.maximizeDiv) {
+ this.maximizeControl();
+ }
+ },
+
+ /**
+ * Method: maximizeControl
+ * Unhide the control. Called when the control is in the map viewport.
+ *
+ * Parameters:
+ * e - {<OpenLayers.Event>}
+ */
+ maximizeControl: function(e) {
+ this.element.style.display = '';
+ this.showToggle(false);
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: minimizeControl
+ * Hide all the contents of the control, shrink the size,
+ * add the maximize icon
+ *
+ * Parameters:
+ * e - {<OpenLayers.Event>}
+ */
+ minimizeControl: function(e) {
+ this.element.style.display = 'none';
+ this.showToggle(true);
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: showToggle
+ * Hide/Show the toggle depending on whether the control is minimized
+ *
+ * Parameters:
+ * minimize - {Boolean}
+ */
+ showToggle: function(minimize) {
+ if (this.maximizeDiv) {
+ this.maximizeDiv.style.display = minimize ? '' : 'none';
+ }
+ if (this.minimizeDiv) {
+ this.minimizeDiv.style.display = minimize ? 'none' : '';
+ }
+ },
+
+ /**
+ * Method: update
+ * Update the overview map after layers move.
+ */
+ update: function() {
+ if(this.ovmap == null) {
+ this.createMap();
+ }
+
+ if(this.autoPan || !this.isSuitableOverview()) {
+ this.updateOverview();
+ }
+
+ // update extent rectangle
+ this.updateRectToMap();
+ },
+
+ /**
+ * Method: isSuitableOverview
+ * Determines if the overview map is suitable given the extent and
+ * resolution of the main map.
+ */
+ isSuitableOverview: function() {
+ var mapExtent = this.map.getExtent();
+ var maxExtent = this.map.getMaxExtent();
+ var testExtent = new OpenLayers.Bounds(
+ Math.max(mapExtent.left, maxExtent.left),
+ Math.max(mapExtent.bottom, maxExtent.bottom),
+ Math.min(mapExtent.right, maxExtent.right),
+ Math.min(mapExtent.top, maxExtent.top));
+
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ testExtent = testExtent.transform(
+ this.map.getProjectionObject(),
+ this.ovmap.getProjectionObject() );
+ }
+
+ var resRatio = this.ovmap.getResolution() / this.map.getResolution();
+ return ((resRatio > this.minRatio) &&
+ (resRatio <= this.maxRatio) &&
+ (this.ovmap.getExtent().containsBounds(testExtent)));
+ },
+
+ /**
+ * Method updateOverview
+ * Called by <update> if <isSuitableOverview> returns true
+ */
+ updateOverview: function() {
+ var mapRes = this.map.getResolution();
+ var targetRes = this.ovmap.getResolution();
+ var resRatio = targetRes / mapRes;
+ if(resRatio > this.maxRatio) {
+ // zoom in overview map
+ targetRes = this.minRatio * mapRes;
+ } else if(resRatio <= this.minRatio) {
+ // zoom out overview map
+ targetRes = this.maxRatio * mapRes;
+ }
+ var center;
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ center = this.map.center.clone();
+ center.transform(this.map.getProjectionObject(),
+ this.ovmap.getProjectionObject() );
+ } else {
+ center = this.map.center;
+ }
+ this.ovmap.setCenter(center, this.ovmap.getZoomForResolution(
+ targetRes * this.resolutionFactor));
+ this.updateRectToMap();
+ },
+
+ /**
+ * Method: createMap
+ * Construct the map that this control contains
+ */
+ createMap: function() {
+ // create the overview map
+ var options = OpenLayers.Util.extend(
+ {controls: [], maxResolution: 'auto',
+ fallThrough: false}, this.mapOptions);
+ this.ovmap = new OpenLayers.Map(this.mapDiv, options);
+ this.ovmap.viewPortDiv.appendChild(this.extentRectangle);
+
+ // prevent ovmap from being destroyed when the page unloads, because
+ // the OverviewMap control has to do this (and does it).
+ OpenLayers.Event.stopObserving(window, 'unload', this.ovmap.unloadDestroy);
+
+ this.ovmap.addLayers(this.layers);
+ this.ovmap.zoomToMaxExtent();
+ // check extent rectangle border width
+ this.wComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-left-width')) +
+ parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-right-width'));
+ this.wComp = (this.wComp) ? this.wComp : 2;
+ this.hComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-top-width')) +
+ parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-bottom-width'));
+ this.hComp = (this.hComp) ? this.hComp : 2;
+
+ this.handlers.drag = new OpenLayers.Handler.Drag(
+ this, {move: this.rectDrag, done: this.updateMapToRect},
+ {map: this.ovmap}
+ );
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, {
+ "click": this.mapDivClick
+ },{
+ "single": true, "double": false,
+ "stopSingle": true, "stopDouble": true,
+ "pixelTolerance": 1,
+ map: this.ovmap
+ }
+ );
+ this.handlers.click.activate();
+
+ this.rectEvents = new OpenLayers.Events(this, this.extentRectangle,
+ null, true);
+ this.rectEvents.register("mouseover", this, function(e) {
+ if(!this.handlers.drag.active && !this.map.dragging) {
+ this.handlers.drag.activate();
+ }
+ });
+ this.rectEvents.register("mouseout", this, function(e) {
+ if(!this.handlers.drag.dragging) {
+ this.handlers.drag.deactivate();
+ }
+ });
+
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ var sourceUnits = this.map.getProjectionObject().getUnits() ||
+ this.map.units || this.map.baseLayer.units;
+ var targetUnits = this.ovmap.getProjectionObject().getUnits() ||
+ this.ovmap.units || this.ovmap.baseLayer.units;
+ this.resolutionFactor = sourceUnits && targetUnits ?
+ OpenLayers.INCHES_PER_UNIT[sourceUnits] /
+ OpenLayers.INCHES_PER_UNIT[targetUnits] : 1;
+ }
+ },
+
+ /**
+ * Method: updateRectToMap
+ * Updates the extent rectangle position and size to match the map extent
+ */
+ updateRectToMap: function() {
+ // If the projections differ we need to reproject
+ var bounds;
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ bounds = this.map.getExtent().transform(
+ this.map.getProjectionObject(),
+ this.ovmap.getProjectionObject() );
+ } else {
+ bounds = this.map.getExtent();
+ }
+ var pxBounds = this.getRectBoundsFromMapBounds(bounds);
+ if (pxBounds) {
+ this.setRectPxBounds(pxBounds);
+ }
+ },
+
+ /**
+ * Method: updateMapToRect
+ * Updates the map extent to match the extent rectangle position and size
+ */
+ updateMapToRect: function() {
+ var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds);
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ lonLatBounds = lonLatBounds.transform(
+ this.ovmap.getProjectionObject(),
+ this.map.getProjectionObject() );
+ }
+ this.map.panTo(lonLatBounds.getCenterLonLat());
+ },
+
+ /**
+ * Method: setRectPxBounds
+ * Set extent rectangle pixel bounds.
+ *
+ * Parameters:
+ * pxBounds - {<OpenLayers.Bounds>}
+ */
+ setRectPxBounds: function(pxBounds) {
+ var top = Math.max(pxBounds.top, 0);
+ var left = Math.max(pxBounds.left, 0);
+ var bottom = Math.min(pxBounds.top + Math.abs(pxBounds.getHeight()),
+ this.ovmap.size.h - this.hComp);
+ var right = Math.min(pxBounds.left + pxBounds.getWidth(),
+ this.ovmap.size.w - this.wComp);
+ var width = Math.max(right - left, 0);
+ var height = Math.max(bottom - top, 0);
+ if(width < this.minRectSize || height < this.minRectSize) {
+ this.extentRectangle.className = this.displayClass +
+ this.minRectDisplayClass;
+ var rLeft = left + (width / 2) - (this.minRectSize / 2);
+ var rTop = top + (height / 2) - (this.minRectSize / 2);
+ this.extentRectangle.style.top = Math.round(rTop) + 'px';
+ this.extentRectangle.style.left = Math.round(rLeft) + 'px';
+ this.extentRectangle.style.height = this.minRectSize + 'px';
+ this.extentRectangle.style.width = this.minRectSize + 'px';
+ } else {
+ this.extentRectangle.className = this.displayClass +
+ 'ExtentRectangle';
+ this.extentRectangle.style.top = Math.round(top) + 'px';
+ this.extentRectangle.style.left = Math.round(left) + 'px';
+ this.extentRectangle.style.height = Math.round(height) + 'px';
+ this.extentRectangle.style.width = Math.round(width) + 'px';
+ }
+ this.rectPxBounds = new OpenLayers.Bounds(
+ Math.round(left), Math.round(bottom),
+ Math.round(right), Math.round(top)
+ );
+ },
+
+ /**
+ * Method: getRectBoundsFromMapBounds
+ * Get the rect bounds from the map bounds.
+ *
+ * Parameters:
+ * lonLatBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}A bounds which is the passed-in map lon/lat extent
+ * translated into pixel bounds for the overview map
+ */
+ getRectBoundsFromMapBounds: function(lonLatBounds) {
+ var leftBottomPx = this.getOverviewPxFromLonLat({
+ lon: lonLatBounds.left,
+ lat: lonLatBounds.bottom
+ });
+ var rightTopPx = this.getOverviewPxFromLonLat({
+ lon: lonLatBounds.right,
+ lat: lonLatBounds.top
+ });
+ var bounds = null;
+ if (leftBottomPx && rightTopPx) {
+ bounds = new OpenLayers.Bounds(leftBottomPx.x, leftBottomPx.y,
+ rightTopPx.x, rightTopPx.y);
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: getMapBoundsFromRectBounds
+ * Get the map bounds from the rect bounds.
+ *
+ * Parameters:
+ * pxBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds which is the passed-in overview rect bounds
+ * translated into lon/lat bounds for the overview map
+ */
+ getMapBoundsFromRectBounds: function(pxBounds) {
+ var leftBottomLonLat = this.getLonLatFromOverviewPx({
+ x: pxBounds.left,
+ y: pxBounds.bottom
+ });
+ var rightTopLonLat = this.getLonLatFromOverviewPx({
+ x: pxBounds.right,
+ y: pxBounds.top
+ });
+ return new OpenLayers.Bounds(leftBottomLonLat.lon, leftBottomLonLat.lat,
+ rightTopLonLat.lon, rightTopLonLat.lat);
+ },
+
+ /**
+ * Method: getLonLatFromOverviewPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * overviewMapPx - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or
+ * an object with a
+ * 'x' and 'y' properties.
+ *
+ * Returns:
+ * {Object} Location which is the passed-in overview map
+ * OpenLayers.Pixel, translated into lon/lat by the overview
+ * map. An object with a 'lon' and 'lat' properties.
+ */
+ getLonLatFromOverviewPx: function(overviewMapPx) {
+ var size = this.ovmap.size;
+ var res = this.ovmap.getResolution();
+ var center = this.ovmap.getExtent().getCenterLonLat();
+
+ var deltaX = overviewMapPx.x - (size.w / 2);
+ var deltaY = overviewMapPx.y - (size.h / 2);
+
+ return {
+ lon: center.lon + deltaX * res,
+ lat: center.lat - deltaY * res
+ };
+ },
+
+ /**
+ * Method: getOverviewPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ *
+ * Returns:
+ * {Object} Location which is the passed-in OpenLayers.LonLat,
+ * translated into overview map pixels
+ */
+ getOverviewPxFromLonLat: function(lonlat) {
+ var res = this.ovmap.getResolution();
+ var extent = this.ovmap.getExtent();
+ if (extent) {
+ return {
+ x: Math.round(1/res * (lonlat.lon - extent.left)),
+ y: Math.round(1/res * (extent.top - lonlat.lat))
+ };
+ }
+ },
+
+ CLASS_NAME: 'OpenLayers.Control.OverviewMap'
+});
+/* ======================================================================
+ OpenLayers/Layer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer
+ */
+OpenLayers.Layer = OpenLayers.Class({
+
+ /**
+ * APIProperty: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * APIProperty: opacity
+ * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
+ * is 1.
+ */
+ opacity: 1,
+
+ /**
+ * APIProperty: alwaysInRange
+ * {Boolean} If a layer's display should not be scale-based, this should
+ * be set to true. This will cause the layer, as an overlay, to always
+ * be 'active', by always returning true from the calculateInRange()
+ * function.
+ *
+ * If not explicitly specified for a layer, its value will be
+ * determined on startup in initResolutions() based on whether or not
+ * any scale-specific properties have been set as options on the
+ * layer. If no scale-specific options have been set on the layer, we
+ * assume that it should always be in range.
+ *
+ * See #987 for more info.
+ */
+ alwaysInRange: null,
+
+ /**
+ * Constant: RESOLUTION_PROPERTIES
+ * {Array} The properties that are used for calculating resolutions
+ * information.
+ */
+ RESOLUTION_PROPERTIES: [
+ 'scales', 'resolutions',
+ 'maxScale', 'minScale',
+ 'maxResolution', 'minResolution',
+ 'numZoomLevels', 'maxZoomLevel'
+ ],
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types:
+ * loadstart - Triggered when layer loading starts. When using a Vector
+ * layer with a Fixed or BBOX strategy, the event object includes
+ * a *filter* property holding the OpenLayers.Filter used when
+ * calling read on the protocol.
+ * loadend - Triggered when layer loading ends. When using a Vector layer
+ * with a Fixed or BBOX strategy, the event object includes a
+ * *response* property holding an OpenLayers.Protocol.Response object.
+ * visibilitychanged - Triggered when the layer's visibility property is
+ * changed, e.g. by turning the layer on or off in the layer switcher.
+ * Note that the actual visibility of the layer can also change if it
+ * gets out of range (see <calculateInRange>). If you also want to catch
+ * these cases, register for the map's 'changelayer' event instead.
+ * move - Triggered when layer moves (triggered with every mousemove
+ * during a drag).
+ * moveend - Triggered when layer is done moving, object passed as
+ * argument has a zoomChanged boolean property which tells that the
+ * zoom has changed.
+ * added - Triggered after the layer is added to a map. Listeners will
+ * receive an object with a *map* property referencing the map and a
+ * *layer* property referencing the layer.
+ * removed - Triggered after the layer is removed from the map. Listeners
+ * will receive an object with a *map* property referencing the map and
+ * a *layer* property referencing the layer.
+ */
+ events: null,
+
+ /**
+ * APIProperty: map
+ * {<OpenLayers.Map>} This variable is set when the layer is added to
+ * the map, via the accessor function setMap().
+ */
+ map: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Whether or not the layer is a base layer. This should be set
+ * individually by all subclasses. Default is false
+ */
+ isBaseLayer: false,
+
+ /**
+ * Property: alpha
+ * {Boolean} The layer's images have an alpha channel. Default is false.
+ */
+ alpha: false,
+
+ /**
+ * APIProperty: displayInLayerSwitcher
+ * {Boolean} Display the layer's name in the layer switcher. Default is
+ * true.
+ */
+ displayInLayerSwitcher: true,
+
+ /**
+ * APIProperty: visibility
+ * {Boolean} The layer should be displayed in the map. Default is true.
+ */
+ visibility: true,
+
+ /**
+ * APIProperty: attribution
+ * {String} Attribution string, displayed when an
+ * <OpenLayers.Control.Attribution> has been added to the map.
+ */
+ attribution: null,
+
+ /**
+ * Property: inRange
+ * {Boolean} The current map resolution is within the layer's min/max
+ * range. This is set in <OpenLayers.Map.setCenter> whenever the zoom
+ * changes.
+ */
+ inRange: false,
+
+ /**
+ * Propery: imageSize
+ * {<OpenLayers.Size>} For layers with a gutter, the image is larger than
+ * the tile by twice the gutter in each dimension.
+ */
+ imageSize: null,
+
+ // OPTIONS
+
+ /**
+ * Property: options
+ * {Object} An optional object whose properties will be set on the layer.
+ * Any of the layer properties can be set as a property of the options
+ * object and sent to the constructor when the layer is created.
+ */
+ options: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: gutter
+ * {Integer} Determines the width (in pixels) of the gutter around image
+ * tiles to ignore. By setting this property to a non-zero value,
+ * images will be requested that are wider and taller than the tile
+ * size by a value of 2 x gutter. This allows artifacts of rendering
+ * at tile edges to be ignored. Set a gutter value that is equal to
+ * half the size of the widest symbol that needs to be displayed.
+ * Defaults to zero. Non-tiled layers always have zero gutter.
+ */
+ gutter: 0,
+
+ /**
+ * APIProperty: projection
+ * {<OpenLayers.Projection>} or {<String>} Specifies the projection of the layer.
+ * Can be set in the layer options. If not specified in the layer options,
+ * it is set to the default projection specified in the map,
+ * when the layer is added to the map.
+ * Projection along with default maxExtent and resolutions
+ * are set automatically with commercial baselayers in EPSG:3857,
+ * such as Google, Bing and OpenStreetMap, and do not need to be specified.
+ * Otherwise, if specifying projection, also set maxExtent,
+ * maxResolution or resolutions as appropriate.
+ * When using vector layers with strategies, layer projection should be set
+ * to the projection of the source data if that is different from the map default.
+ *
+ * Can be either a string or an <OpenLayers.Projection> object;
+ * if a string is passed, will be converted to an object when
+ * the layer is added to the map.
+ *
+ */
+ projection: null,
+
+ /**
+ * APIProperty: units
+ * {String} The layer map units. Defaults to null. Possible values
+ * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
+ * Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units.
+ */
+ units: null,
+
+ /**
+ * APIProperty: scales
+ * {Array} An array of map scales in descending order. The values in the
+ * array correspond to the map scale denominator. Note that these
+ * values only make sense if the display (monitor) resolution of the
+ * client is correctly guessed by whomever is configuring the
+ * application. In addition, the units property must also be set.
+ * Use <resolutions> instead wherever possible.
+ */
+ scales: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array} A list of map resolutions (map units per pixel) in descending
+ * order. If this is not set in the layer constructor, it will be set
+ * based on other resolution related properties (maxExtent,
+ * maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the layer. Defaults to null.
+ *
+ * The center of these bounds will not stray outside
+ * of the viewport extent during panning. In addition, if
+ * <displayOutsideMaxExtent> is set to false, data will not be
+ * requested that falls completely outside of these bounds.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the layer. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Default max is 360 deg / 256 px, which corresponds to
+ * zoom level 0 on gmaps. Specify a different value in the layer
+ * options if you are not using the default <OpenLayers.Map.tileSize>
+ * and displaying the whole world.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer}
+ */
+ numZoomLevels: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: displayOutsideMaxExtent
+ * {Boolean} Request map tiles that are completely outside of the max
+ * extent for this layer. Defaults to false.
+ */
+ displayOutsideMaxExtent: false,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Wraps the world at the international dateline, so the map can
+ * be panned infinitely in longitudinal direction. Only use this on the
+ * base layer, and only if the layer's maxExtent equals the world bounds.
+ * #487 for more info.
+ */
+ wrapDateLine: false,
+
+ /**
+ * Property: metadata
+ * {Object} This object can be used to store additional information on a
+ * layer object.
+ */
+ metadata: null,
+
+ /**
+ * Constructor: OpenLayers.Layer
+ *
+ * Parameters:
+ * name - {String} The layer name
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+
+ this.metadata = {};
+
+ options = OpenLayers.Util.extend({}, options);
+ // make sure we respect alwaysInRange if set on the prototype
+ if (this.alwaysInRange != null) {
+ options.alwaysInRange = this.alwaysInRange;
+ }
+ this.addOptions(options);
+
+ this.name = name;
+
+ if (this.id == null) {
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.style.width = "100%";
+ this.div.style.height = "100%";
+ this.div.dir = "ltr";
+
+ this.events = new OpenLayers.Events(this, this.div);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Destroy is a destructor: this is to alleviate cyclic references which
+ * the Javascript garbage cleaner can not take care of on its own.
+ *
+ * Parameters:
+ * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
+ * been destroyed. Default is true.
+ */
+ destroy: function(setNewBaseLayer) {
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+ if (this.map != null) {
+ this.map.removeLayer(this, setNewBaseLayer);
+ }
+ this.projection = null;
+ this.map = null;
+ this.name = null;
+ this.div = null;
+ this.options = null;
+
+ if (this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ }
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: clone
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Layer>} The layer to be cloned
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer(this.name, this.getOptions());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ // a cloned layer should never have its map property set
+ // because it has not been added to a map yet.
+ obj.map = null;
+
+ return obj;
+ },
+
+ /**
+ * Method: getOptions
+ * Extracts an object from the layer with the properties that were set as
+ * options, but updates them with the values currently set on the
+ * instance.
+ *
+ * Returns:
+ * {Object} the <options> of the layer, representing the current state.
+ */
+ getOptions: function() {
+ var options = {};
+ for(var o in this.options) {
+ options[o] = this[o];
+ }
+ return options;
+ },
+
+ /**
+ * APIMethod: setName
+ * Sets the new layer name for this layer. Can trigger a changelayer event
+ * on the map.
+ *
+ * Parameters:
+ * newName - {String} The new name.
+ */
+ setName: function(newName) {
+ if (newName != this.name) {
+ this.name = newName;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "name"
+ });
+ }
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+
+ if (this.options == null) {
+ this.options = {};
+ }
+
+ if (newOptions) {
+ // make sure this.projection references a projection object
+ if(typeof newOptions.projection == "string") {
+ newOptions.projection = new OpenLayers.Projection(newOptions.projection);
+ }
+ if (newOptions.projection) {
+ // get maxResolution, units and maxExtent from projection defaults if
+ // they are not defined already
+ OpenLayers.Util.applyDefaults(newOptions,
+ OpenLayers.Projection.defaults[newOptions.projection.getCode()]);
+ }
+ // allow array for extents
+ if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) {
+ newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent);
+ }
+ if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) {
+ newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent);
+ }
+ }
+
+ // update our copy for clone
+ OpenLayers.Util.extend(this.options, newOptions);
+
+ // add new options to this
+ OpenLayers.Util.extend(this, newOptions);
+
+ // get the units from the projection, if we have a projection
+ // and it it has units
+ if(this.projection && this.projection.getUnits()) {
+ this.units = this.projection.getUnits();
+ }
+
+ // re-initialize resolutions if necessary, i.e. if any of the
+ // properties of the "properties" array defined below is set
+ // in the new options
+ if(this.map) {
+ // store current resolution so we can try to restore it later
+ var resolution = this.map.getResolution();
+ var properties = this.RESOLUTION_PROPERTIES.concat(
+ ["projection", "units", "minExtent", "maxExtent"]
+ );
+ for(var o in newOptions) {
+ if(newOptions.hasOwnProperty(o) &&
+ OpenLayers.Util.indexOf(properties, o) >= 0) {
+
+ this.initResolutions();
+ if (reinitialize && this.map.baseLayer === this) {
+ // update map position, and restore previous resolution
+ this.map.setCenter(this.map.getCenter(),
+ this.map.getZoomForResolution(resolution),
+ false, true
+ );
+ // trigger a changebaselayer event to make sure that
+ // all controls (especially
+ // OpenLayers.Control.PanZoomBar) get notified of the
+ // new options
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: onMapResize
+ * This function can be implemented by subclasses
+ */
+ onMapResize: function() {
+ //this function can be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function() {
+ var redrawn = false;
+ if (this.map) {
+
+ // min/max Range may have changed
+ this.inRange = this.calculateInRange();
+
+ // map's center might not yet be set
+ var extent = this.getExtent();
+
+ if (extent && this.inRange && this.visibility) {
+ var zoomChanged = true;
+ this.moveTo(extent, zoomChanged, false);
+ this.events.triggerEvent("moveend",
+ {"zoomChanged": zoomChanged});
+ redrawn = true;
+ }
+ }
+ return redrawn;
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ var display = this.visibility;
+ if (!this.isBaseLayer) {
+ display = display && this.inRange;
+ }
+ this.display(display);
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Here we take care to bring over any of the necessary default
+ * properties from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ if (this.map == null) {
+
+ this.map = map;
+
+ // grab some essential layer data from the map if it hasn't already
+ // been set
+ this.maxExtent = this.maxExtent || this.map.maxExtent;
+ this.minExtent = this.minExtent || this.map.minExtent;
+
+ this.projection = this.projection || this.map.projection;
+ if (typeof this.projection == "string") {
+ this.projection = new OpenLayers.Projection(this.projection);
+ }
+
+ // Check the projection to see if we can get units -- if not, refer
+ // to properties.
+ this.units = this.projection.getUnits() ||
+ this.units || this.map.units;
+
+ this.initResolutions();
+
+ if (!this.isBaseLayer) {
+ this.inRange = this.calculateInRange();
+ var show = ((this.visibility) && (this.inRange));
+ this.div.style.display = show ? "" : "none";
+ }
+
+ // deal with gutters
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. To be overridden by subclasses.
+ */
+ afterAdd: function() {
+ },
+
+ /**
+ * APIMethod: removeMap
+ * Just as setMap() allows each layer the possibility to take a
+ * personalized action on being added to the map, removeMap() allows
+ * each layer to take a personalized action on being removed from it.
+ * For now, this will be mostly unused, except for the EventPane layer,
+ * which needs this hook so that it can remove the special invisible
+ * pane.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * APIMethod: getImageSize
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
+ * by subclasses that have to deal with different tile sizes at the
+ * layer extent edges (e.g. Zoomify)
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size that the image should be, taking into
+ * account gutters.
+ */
+ getImageSize: function(bounds) {
+ return (this.imageSize || this.tileSize);
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Set the tile size based on the map size. This also sets layer.imageSize
+ * or use by Tile.Image.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ var tileSize = (size) ? size :
+ ((this.tileSize) ? this.tileSize :
+ this.map.getTileSize());
+ this.tileSize = tileSize;
+ if(this.gutter) {
+ // layers with gutters need non-null tile sizes
+ //if(tileSize == null) {
+ // OpenLayers.console.error("Error in layer.setMap() for " +
+ // this.name + ": layers with " +
+ // "gutters need non-null tile sizes");
+ //}
+ this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter),
+ tileSize.h + (2*this.gutter));
+ }
+ },
+
+ /**
+ * APIMethod: getVisibility
+ *
+ * Returns:
+ * {Boolean} The layer should be displayed (if in range).
+ */
+ getVisibility: function() {
+ return this.visibility;
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visibility - {Boolean} Whether or not to display the layer (if in range)
+ */
+ setVisibility: function(visibility) {
+ if (visibility != this.visibility) {
+ this.visibility = visibility;
+ this.display(visibility);
+ this.redraw();
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "visibility"
+ });
+ }
+ this.events.triggerEvent("visibilitychanged");
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer. This is designed to be used internally, and
+ * is not generally the way to enable or disable the layer. For that,
+ * use the setVisibility function instead..
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ if (display != (this.div.style.display != "none")) {
+ this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
+ }
+ },
+
+ /**
+ * APIMethod: calculateInRange
+ *
+ * Returns:
+ * {Boolean} The layer is displayable at the current map's current
+ * resolution. Note that if 'alwaysInRange' is true for the layer,
+ * this function will always return true.
+ */
+ calculateInRange: function() {
+ var inRange = false;
+
+ if (this.alwaysInRange) {
+ inRange = true;
+ } else {
+ if (this.map) {
+ var resolution = this.map.getResolution();
+ inRange = ( (resolution >= this.minResolution) &&
+ (resolution <= this.maxResolution) );
+ }
+ }
+ return inRange;
+ },
+
+ /**
+ * APIMethod: setIsBaseLayer
+ *
+ * Parameters:
+ * isBaseLayer - {Boolean}
+ */
+ setIsBaseLayer: function(isBaseLayer) {
+ if (isBaseLayer != this.isBaseLayer) {
+ this.isBaseLayer = isBaseLayer;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: initResolutions
+ * This method's responsibility is to set up the 'resolutions' array
+ * for the layer -- this array is what the layer will use to interface
+ * between the zoom levels of the map and the resolution display
+ * of the layer.
+ *
+ * The user has several options that determine how the array is set up.
+ *
+ * For a detailed explanation, see the following wiki from the
+ * openlayers.org homepage:
+ * http://trac.openlayers.org/wiki/SettingZoomLevels
+ */
+ initResolutions: function() {
+
+ // ok we want resolutions, here's our strategy:
+ //
+ // 1. if resolutions are defined in the layer config, use them
+ // 2. else, if scales are defined in the layer config then derive
+ // resolutions from these scales
+ // 3. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // layer config
+ // 4. if we still don't have resolutions, and if resolutions
+ // are defined in the same, use them
+ // 5. else, if scales are defined in the map then derive
+ // resolutions from these scales
+ // 6. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // map
+ // 7. hope for the best!
+
+ var i, len, p;
+ var props = {}, alwaysInRange = true;
+
+ // get resolution data from layer config
+ // (we also set alwaysInRange in the layer as appropriate)
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p];
+ if(alwaysInRange && this.options[p]) {
+ alwaysInRange = false;
+ }
+ }
+ if(this.options.alwaysInRange == null) {
+ this.alwaysInRange = alwaysInRange;
+ }
+
+ // if we don't have resolutions then attempt to derive them from scales
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+
+ // if we still don't have resolutions then attempt to calculate them
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+
+ // if we couldn't calculate resolutions then we look at we have
+ // in the map
+ if(props.resolutions == null) {
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p] != null ?
+ this.options[p] : this.map[p];
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+ }
+
+ // ok, we new need to set properties in the instance
+
+ // get maxResolution from the config if it's defined there
+ var maxResolution;
+ if(this.options.maxResolution &&
+ this.options.maxResolution !== "auto") {
+ maxResolution = this.options.maxResolution;
+ }
+ if(this.options.minScale) {
+ maxResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.minScale, this.units);
+ }
+
+ // get minResolution from the config if it's defined there
+ var minResolution;
+ if(this.options.minResolution &&
+ this.options.minResolution !== "auto") {
+ minResolution = this.options.minResolution;
+ }
+ if(this.options.maxScale) {
+ minResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.maxScale, this.units);
+ }
+
+ if(props.resolutions) {
+
+ //sort resolutions array descendingly
+ props.resolutions.sort(function(a, b) {
+ return (b - a);
+ });
+
+ // if we still don't have a maxResolution get it from the
+ // resolutions array
+ if(!maxResolution) {
+ maxResolution = props.resolutions[0];
+ }
+
+ // if we still don't have a minResolution get it from the
+ // resolutions array
+ if(!minResolution) {
+ var lastIdx = props.resolutions.length - 1;
+ minResolution = props.resolutions[lastIdx];
+ }
+ }
+
+ this.resolutions = props.resolutions;
+ if(this.resolutions) {
+ len = this.resolutions.length;
+ this.scales = new Array(len);
+ for(i=0; i<len; i++) {
+ this.scales[i] = OpenLayers.Util.getScaleFromResolution(
+ this.resolutions[i], this.units);
+ }
+ this.numZoomLevels = len;
+ }
+ this.minResolution = minResolution;
+ if(minResolution) {
+ this.maxScale = OpenLayers.Util.getScaleFromResolution(
+ minResolution, this.units);
+ }
+ this.maxResolution = maxResolution;
+ if(maxResolution) {
+ this.minScale = OpenLayers.Util.getScaleFromResolution(
+ maxResolution, this.units);
+ }
+ },
+
+ /**
+ * Method: resolutionsFromScales
+ * Derive resolutions from scales.
+ *
+ * Parameters:
+ * scales - {Array(Number)} Scales
+ *
+ * Returns
+ * {Array(Number)} Resolutions
+ */
+ resolutionsFromScales: function(scales) {
+ if(scales == null) {
+ return;
+ }
+ var resolutions, i, len;
+ len = scales.length;
+ resolutions = new Array(len);
+ for(i=0; i<len; i++) {
+ resolutions[i] = OpenLayers.Util.getResolutionFromScale(
+ scales[i], this.units);
+ }
+ return resolutions;
+ },
+
+ /**
+ * Method: calculateResolutions
+ * Calculate resolutions based on the provided properties.
+ *
+ * Parameters:
+ * props - {Object} Properties
+ *
+ * Returns:
+ * {Array({Number})} Array of resolutions.
+ */
+ calculateResolutions: function(props) {
+
+ var viewSize, wRes, hRes;
+
+ // determine maxResolution
+ var maxResolution = props.maxResolution;
+ if(props.minScale != null) {
+ maxResolution =
+ OpenLayers.Util.getResolutionFromScale(props.minScale,
+ this.units);
+ } else if(maxResolution == "auto" && this.maxExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.maxExtent.getWidth() / viewSize.w;
+ hRes = this.maxExtent.getHeight() / viewSize.h;
+ maxResolution = Math.max(wRes, hRes);
+ }
+
+ // determine minResolution
+ var minResolution = props.minResolution;
+ if(props.maxScale != null) {
+ minResolution =
+ OpenLayers.Util.getResolutionFromScale(props.maxScale,
+ this.units);
+ } else if(props.minResolution == "auto" && this.minExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.minExtent.getWidth() / viewSize.w;
+ hRes = this.minExtent.getHeight()/ viewSize.h;
+ minResolution = Math.max(wRes, hRes);
+ }
+
+ if(typeof maxResolution !== "number" &&
+ typeof minResolution !== "number" &&
+ this.maxExtent != null) {
+ // maxResolution for default grid sets assumes that at zoom
+ // level zero, the whole world fits on one tile.
+ var tileSize = this.map.getTileSize();
+ maxResolution = Math.max(
+ this.maxExtent.getWidth() / tileSize.w,
+ this.maxExtent.getHeight() / tileSize.h
+ );
+ }
+
+ // determine numZoomLevels
+ var maxZoomLevel = props.maxZoomLevel;
+ var numZoomLevels = props.numZoomLevels;
+ if(typeof minResolution === "number" &&
+ typeof maxResolution === "number" && numZoomLevels === undefined) {
+ var ratio = maxResolution / minResolution;
+ numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
+ } else if(numZoomLevels === undefined && maxZoomLevel != null) {
+ numZoomLevels = maxZoomLevel + 1;
+ }
+
+ // are we able to calculate resolutions?
+ if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
+ (typeof maxResolution !== "number" &&
+ typeof minResolution !== "number")) {
+ return;
+ }
+
+ // now we have numZoomLevels and at least one of maxResolution
+ // or minResolution, we can populate the resolutions array
+
+ var resolutions = new Array(numZoomLevels);
+ var base = 2;
+ if(typeof minResolution == "number" &&
+ typeof maxResolution == "number") {
+ // if maxResolution and minResolution are set, we calculate
+ // the base for exponential scaling that starts at
+ // maxResolution and ends at minResolution in numZoomLevels
+ // steps.
+ base = Math.pow(
+ (maxResolution / minResolution),
+ (1 / (numZoomLevels - 1))
+ );
+ }
+
+ var i;
+ if(typeof maxResolution === "number") {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[i] = maxResolution / Math.pow(base, i);
+ }
+ } else {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[numZoomLevels - 1 - i] =
+ minResolution * Math.pow(base, i);
+ }
+ }
+
+ return resolutions;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The currently selected resolution of the map, taken from the
+ * resolutions array, indexed by current zoom level.
+ */
+ getResolution: function() {
+ var zoom = this.map.getZoom();
+ return this.getResolutionForZoom(zoom);
+ },
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function() {
+ // just use stock map calculateBounds function -- passing no arguments
+ // means it will user map's current center & resolution
+ //
+ return this.map.calculateBounds();
+ },
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * for the passed-in extent. We do this by calculating the ideal
+ * resolution for the given extent (based on the map size) and then
+ * calling getZoomForResolution(), passing along the 'closest'
+ * parameter.
+ */
+ getZoomForExtent: function(extent, closest) {
+ var viewSize = this.map.getSize();
+ var idealResolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+
+ return this.getZoomForResolution(idealResolution, closest);
+ },
+
+ /**
+ * Method: getDataExtent
+ * Calculates the max extent which includes all of the data for the layer.
+ * This function is to be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ //to be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom.
+ */
+ getResolutionForZoom: function(zoom) {
+ zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
+ var resolution;
+ if(this.map.fractionalZoom) {
+ var low = Math.floor(zoom);
+ var high = Math.ceil(zoom);
+ resolution = this.resolutions[low] -
+ ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
+ } else {
+ resolution = this.resolutions[Math.round(zoom)];
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * that corresponds to the best fit resolution given the passed in
+ * value and the 'closest' specification.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom, i, len;
+ if(this.map.fractionalZoom) {
+ var lowZoom = 0;
+ var highZoom = this.resolutions.length - 1;
+ var highRes = this.resolutions[lowZoom];
+ var lowRes = this.resolutions[highZoom];
+ var res;
+ for(i=0, len=this.resolutions.length; i<len; ++i) {
+ res = this.resolutions[i];
+ if(res >= resolution) {
+ highRes = res;
+ lowZoom = i;
+ }
+ if(res <= resolution) {
+ lowRes = res;
+ highZoom = i;
+ break;
+ }
+ }
+ var dRes = highRes - lowRes;
+ if(dRes > 0) {
+ zoom = lowZoom + ((highRes - resolution) / dRes);
+ } else {
+ zoom = lowZoom;
+ }
+ } else {
+ var diff;
+ var minDiff = Number.POSITIVE_INFINITY;
+ for(i=0, len=this.resolutions.length; i<len; i++) {
+ if (closest) {
+ diff = Math.abs(this.resolutions[i] - resolution);
+ if (diff > minDiff) {
+ break;
+ }
+ minDiff = diff;
+ } else {
+ if (this.resolutions[i] < resolution) {
+ break;
+ }
+ }
+ }
+ zoom = Math.max(0, i-1);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in
+ * view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ var map = this.map;
+ if (viewPortPx != null && map.minPx) {
+ var res = map.getResolution();
+ var maxExtent = map.getMaxExtent({restricted: true});
+ var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
+ var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
+ lonlat = new OpenLayers.LonLat(lon, lat);
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ * Returns a pixel location given a map location. This method will return
+ * fractional pixel values.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
+ * an object with a 'lon'
+ * and 'lat' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in
+ * lonlat translated into view port pixels.
+ */
+ getViewPortPxFromLonLat: function (lonlat, resolution) {
+ var px = null;
+ if (lonlat != null) {
+ resolution = resolution || this.map.getResolution();
+ var extent = this.map.calculateBounds(null, resolution);
+ px = new OpenLayers.Pixel(
+ (1/resolution * (lonlat.lon - extent.left)),
+ (1/resolution * (extent.top - lonlat.lat))
+ );
+ }
+ return px;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity != this.opacity) {
+ this.opacity = opacity;
+ var childNodes = this.div.childNodes;
+ for(var i = 0, len = childNodes.length; i < len; ++i) {
+ var element = childNodes[i].firstChild || childNodes[i];
+ var lastChild = childNodes[i].lastChild;
+ //TODO de-uglify this
+ if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") {
+ element = lastChild.parentNode;
+ }
+ OpenLayers.Util.modifyDOMElement(element, null, null, null,
+ null, null, null, opacity);
+ }
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ }
+ },
+
+ /**
+ * Method: getZIndex
+ *
+ * Returns:
+ * {Integer} the z-index of this layer
+ */
+ getZIndex: function () {
+ return this.div.style.zIndex;
+ },
+
+ /**
+ * Method: setZIndex
+ *
+ * Parameters:
+ * zIndex - {Integer}
+ */
+ setZIndex: function (zIndex) {
+ this.div.style.zIndex = zIndex;
+ },
+
+ /**
+ * Method: adjustBounds
+ * This function will take a bounds, and if wrapDateLine option is set
+ * on the layer, it will return a bounds which is wrapped around the
+ * world. We do not wrap for bounds which *cross* the
+ * maxExtent.left/right, only bounds which are entirely to the left
+ * or entirely to the right.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ adjustBounds: function (bounds) {
+
+ if (this.gutter) {
+ // Adjust the extent of a bounds in map units by the
+ // layer's gutter in pixels.
+ var mapGutter = this.gutter * this.map.getResolution();
+ bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
+ bounds.bottom - mapGutter,
+ bounds.right + mapGutter,
+ bounds.top + mapGutter);
+ }
+
+ if (this.wrapDateLine) {
+ // wrap around the date line, within the limits of rounding error
+ var wrappingOptions = {
+ 'rightTolerance':this.getResolution(),
+ 'leftTolerance':this.getResolution()
+ };
+ bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
+
+ }
+ return bounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer"
+});
+/* ======================================================================
+ OpenLayers/Layer/SphericalMercator.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.SphericalMercator
+ * A mixin for layers that wraps up the pieces neccesary to have a coordinate
+ * conversion for working with commercial APIs which use a spherical
+ * mercator projection. Using this layer as a base layer, additional
+ * layers can be used as overlays if they are in the same projection.
+ *
+ * A layer is given properties of this object by setting the sphericalMercator
+ * property to true.
+ *
+ * More projection information:
+ * - http://spatialreference.org/ref/user/google-projection/
+ *
+ * Proj4 Text:
+ * +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
+ * +k=1.0 +units=m +nadgrids=@null +no_defs
+ *
+ * WKT:
+ * 900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
+ * DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]],
+ * PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295],
+ * AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
+ * PROJECTION["Mercator_1SP_Google"],
+ * PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0],
+ * PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0],
+ * PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
+ * AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]
+ */
+OpenLayers.Layer.SphericalMercator = {
+
+ /**
+ * Method: getExtent
+ * Get the map's extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The map extent.
+ */
+ getExtent: function() {
+ var extent = null;
+ if (this.sphericalMercator) {
+ extent = this.map.calculateBounds();
+ } else {
+ extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);
+ }
+ return extent;
+ },
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port OpenLayers.Pixel, translated into lon/lat by map lib
+ * If the map lib is not loaded or not centered, returns null
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments);
+ },
+
+ /**
+ * Method: getViewPortPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * OpenLayers.LonLat, translated into view port pixels by map lib
+ * If map lib is not loaded or not centered, returns null
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments);
+ },
+
+ /**
+ * Method: initMercatorParameters
+ * Set up the mercator parameters on the layer: resolutions,
+ * projection, units.
+ */
+ initMercatorParameters: function() {
+ // set up properties for Mercator - assume EPSG:900913
+ this.RESOLUTIONS = [];
+ var maxResolution = 156543.03390625;
+ for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
+ this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
+ }
+ this.units = "m";
+ this.projection = this.projection || "EPSG:900913";
+ },
+
+ /**
+ * APIMethod: forwardMercator
+ * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator.
+ *
+ * Parameters:
+ * lon - {float}
+ * lat - {float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The coordinates transformed to Mercator.
+ */
+ forwardMercator: (function() {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ return function(lon, lat) {
+ var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm);
+ return new OpenLayers.LonLat(point.x, point.y);
+ };
+ })(),
+
+ /**
+ * APIMethod: inverseMercator
+ * Given a x,y in Spherical Mercator, return a point in EPSG:4326.
+ *
+ * Parameters:
+ * x - {float} A map x in Spherical Mercator.
+ * y - {float} A map y in Spherical Mercator.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326.
+ */
+ inverseMercator: (function() {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ return function(x, y) {
+ var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg);
+ return new OpenLayers.LonLat(point.x, point.y);
+ };
+ })()
+
+};
+/* ======================================================================
+ OpenLayers/Layer/EventPane.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.EventPane
+ * Base class for 3rd party layers, providing a DOM element which isolates
+ * the 3rd-party layer from mouse events.
+ * Only used by Google layers.
+ *
+ * Automatically instantiated by the Google constructor, and not usually instantiated directly.
+ *
+ * Create a new event pane layer with the
+ * <OpenLayers.Layer.EventPane> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: smoothDragPan
+ * {Boolean} smoothDragPan determines whether non-public/internal API
+ * methods are used for better performance while dragging EventPane
+ * layers. When not in sphericalMercator mode, the smoother dragging
+ * doesn't actually move north/south directly with the number of
+ * pixels moved, resulting in a slight offset when you drag your mouse
+ * north south with this option on. If this visual disparity bothers
+ * you, you should turn this option off, or use spherical mercator.
+ * Default is on.
+ */
+ smoothDragPan: true,
+
+ /**
+ * Property: isBaseLayer
+ * {Boolean} EventPaned layers are always base layers, by necessity.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} EventPaned layers are fixed by default.
+ */
+ isFixed: true,
+
+ /**
+ * Property: pane
+ * {DOMElement} A reference to the element that controls the events.
+ */
+ pane: null,
+
+
+ /**
+ * Property: mapObject
+ * {Object} This is the object which will be used to load the 3rd party library
+ * in the case of the google layer, this will be of type GMap,
+ * in the case of the ve layer, this will be of type VEMap
+ */
+ mapObject: null,
+
+
+ /**
+ * Constructor: OpenLayers.Layer.EventPane
+ * Create a new event pane layer
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ if (this.pane == null) {
+ this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct this layer.
+ */
+ destroy: function() {
+ this.mapObject = null;
+ this.pane = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+ this.pane.style.display = this.div.style.display;
+ this.pane.style.width="100%";
+ this.pane.style.height="100%";
+ if (OpenLayers.BROWSER_NAME == "msie") {
+ this.pane.style.background =
+ "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")";
+ }
+
+ if (this.isFixed) {
+ this.map.viewPortDiv.appendChild(this.pane);
+ } else {
+ this.map.layerContainerDiv.appendChild(this.pane);
+ }
+
+ // once our layer has been added to the map, we can load it
+ this.loadMapObject();
+
+ // if map didn't load, display warning
+ if (this.mapObject == null) {
+ this.loadWarningMessage();
+ }
+ },
+
+ /**
+ * APIMethod: removeMap
+ * On being removed from the map, we'll like to remove the invisible 'pane'
+ * div that we added to it on creation.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this.pane && this.pane.parentNode) {
+ this.pane.parentNode.removeChild(this.pane);
+ }
+ OpenLayers.Layer.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: loadWarningMessage
+ * If we can't load the map lib, then display an error message to the
+ * user and tell them where to go for help.
+ *
+ * This function sets up the layout for the warning message. Each 3rd
+ * party layer must implement its own getWarningHTML() function to
+ * provide the actual warning message.
+ */
+ loadWarningMessage:function() {
+
+ this.div.style.backgroundColor = "darkblue";
+
+ var viewSize = this.map.getSize();
+
+ var msgW = Math.min(viewSize.w, 300);
+ var msgH = Math.min(viewSize.h, 200);
+ var size = new OpenLayers.Size(msgW, msgH);
+
+ var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2);
+
+ var topLeft = centerPx.add(-size.w/2, -size.h/2);
+
+ var div = OpenLayers.Util.createDiv(this.name + "_warning",
+ topLeft,
+ size,
+ null,
+ null,
+ null,
+ "auto");
+
+ div.style.padding = "7px";
+ div.style.backgroundColor = "yellow";
+
+ div.innerHTML = this.getWarningHTML();
+ this.div.appendChild(div);
+ },
+
+ /**
+ * Method: getWarningHTML
+ * To be implemented by subclasses.
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ //should be implemented by subclasses
+ return "";
+ },
+
+ /**
+ * Method: display
+ * Set the display on the pane
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ this.pane.style.display = this.div.style.display;
+ },
+
+ /**
+ * Method: setZIndex
+ * Set the z-index order for the pane.
+ *
+ * Parameters:
+ * zIndex - {int}
+ */
+ setZIndex: function (zIndex) {
+ OpenLayers.Layer.prototype.setZIndex.apply(this, arguments);
+ this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ OpenLayers.Layer.prototype.moveByPx.apply(this, arguments);
+
+ if (this.dragPanMapObject) {
+ this.dragPanMapObject(dx, -dy);
+ } else {
+ this.moveTo(this.map.getCachedCenter());
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * Handle calls to move the layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ if (this.mapObject != null) {
+
+ var newCenter = this.map.getCenter();
+ var newZoom = this.map.getZoom();
+
+ if (newCenter != null) {
+
+ var moOldCenter = this.getMapObjectCenter();
+ var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter);
+
+ var moOldZoom = this.getMapObjectZoom();
+ var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom);
+
+ if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) {
+
+ if (!zoomChanged && oldCenter && this.dragPanMapObject &&
+ this.smoothDragPan) {
+ var oldPx = this.map.getViewPortPxFromLonLat(oldCenter);
+ var newPx = this.map.getViewPortPxFromLonLat(newCenter);
+ this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y);
+ } else {
+ var center = this.getMapObjectLonLatFromOLLonLat(newCenter);
+ var zoom = this.getMapObjectZoomFromOLZoom(newZoom);
+ this.setMapObjectCenter(center, zoom, dragging);
+ }
+ }
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port OpenLayers.Pixel, translated into lon/lat by map lib
+ * If the map lib is not loaded or not centered, returns null
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if ( (this.mapObject != null) &&
+ (this.getMapObjectCenter() != null) ) {
+ var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx);
+ var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel);
+ lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat);
+ }
+ return lonlat;
+ },
+
+
+ /**
+ * Method: getViewPortPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * OpenLayers.LonLat, translated into view port pixels by map lib
+ * If map lib is not loaded or not centered, returns null
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var viewPortPx = null;
+ if ( (this.mapObject != null) &&
+ (this.getMapObjectCenter() != null) ) {
+
+ var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat);
+ var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat);
+
+ viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel);
+ }
+ return viewPortPx;
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate Map Object and */
+ /* OL formats for Pixel, LonLat */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat
+ //
+
+ /**
+ * Method: getOLLonLatFromMapObjectLonLat
+ * Get an OL style map location from a 3rd party style map location
+ *
+ * Parameters
+ * moLonLat - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in
+ * MapObject LonLat
+ * Returns null if null value is passed in
+ */
+ getOLLonLatFromMapObjectLonLat: function(moLonLat) {
+ var olLonLat = null;
+ if (moLonLat != null) {
+ var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+ var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+ olLonLat = new OpenLayers.LonLat(lon, lat);
+ }
+ return olLonLat;
+ },
+
+ /**
+ * Method: getMapObjectLonLatFromOLLonLat
+ * Get a 3rd party map location from an OL map location.
+ *
+ * Parameters:
+ * olLonLat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Object} A MapObject LonLat, translated from the passed in
+ * OpenLayers.LonLat
+ * Returns null if null value is passed in
+ */
+ getMapObjectLonLatFromOLLonLat: function(olLonLat) {
+ var moLatLng = null;
+ if (olLonLat != null) {
+ moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon,
+ olLonLat.lat);
+ }
+ return moLatLng;
+ },
+
+
+ //
+ // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel
+ //
+
+ /**
+ * Method: getOLPixelFromMapObjectPixel
+ * Get an OL pixel location from a 3rd party pixel location.
+ *
+ * Parameters:
+ * moPixel - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in
+ * MapObject Pixel
+ * Returns null if null value is passed in
+ */
+ getOLPixelFromMapObjectPixel: function(moPixel) {
+ var olPixel = null;
+ if (moPixel != null) {
+ var x = this.getXFromMapObjectPixel(moPixel);
+ var y = this.getYFromMapObjectPixel(moPixel);
+ olPixel = new OpenLayers.Pixel(x, y);
+ }
+ return olPixel;
+ },
+
+ /**
+ * Method: getMapObjectPixelFromOLPixel
+ * Get a 3rd party pixel location from an OL pixel location
+ *
+ * Parameters:
+ * olPixel - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Object} A MapObject Pixel, translated from the passed in
+ * OpenLayers.Pixel
+ * Returns null if null value is passed in
+ */
+ getMapObjectPixelFromOLPixel: function(olPixel) {
+ var moPixel = null;
+ if (olPixel != null) {
+ moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y);
+ }
+ return moPixel;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.EventPane"
+});
+/* ======================================================================
+ OpenLayers/Layer/FixedZoomLevels.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.FixedZoomLevels
+ * Some Layers will already have established zoom levels (like google
+ * or ve). Instead of trying to determine them and populate a resolutions[]
+ * Array with those values, we will hijack the resolution functionality
+ * here.
+ *
+ * When you subclass FixedZoomLevels:
+ *
+ * The initResolutions() call gets nullified, meaning no resolutions[] array
+ * is set up. Which would be a big problem getResolution() in Layer, since
+ * it merely takes map.zoom and indexes into resolutions[]... but....
+ *
+ * The getResolution() call is also overridden. Instead of using the
+ * resolutions[] array, we simply calculate the current resolution based
+ * on the current extent and the current map size. But how will we be able
+ * to calculate the current extent without knowing the resolution...?
+ *
+ * The getExtent() function is also overridden. Instead of calculating extent
+ * based on the center point and the current resolution, we instead
+ * calculate the extent by getting the lonlats at the top-left and
+ * bottom-right by using the getLonLatFromViewPortPx() translation function,
+ * taken from the pixel locations (0,0) and the size of the map. But how
+ * will we be able to do lonlat-px translation without resolution....?
+ *
+ * The getZoomForResolution() method is overridden. Instead of indexing into
+ * the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in
+ * the desired resolution. With this extent, we then call getZoomForExtent()
+ *
+ *
+ * Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels,
+ * it is your responsibility to provide the following three functions:
+ *
+ * - getLonLatFromViewPortPx
+ * - getViewPortPxFromLonLat
+ * - getZoomForExtent
+ *
+ * ...those three functions should generally be provided by any reasonable
+ * API that you might be working from.
+ *
+ */
+OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions must all be implemented */
+ /* by all base layers */
+ /* */
+ /********************************************************/
+
+ /**
+ * Constructor: OpenLayers.Layer.FixedZoomLevels
+ * Create a new fixed zoom levels layer.
+ */
+ initialize: function() {
+ //this class is only just to add the following functions...
+ // nothing to actually do here... but it is probably a good
+ // idea to have layers that use these functions call this
+ // inititalize() anyways, in case at some point we decide we
+ // do want to put some functionality or state in here.
+ },
+
+ /**
+ * Method: initResolutions
+ * Populate the resolutions array
+ */
+ initResolutions: function() {
+
+ var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels'];
+
+ for(var i=0, len=props.length; i<len; i++) {
+ var property = props[i];
+ this[property] = (this.options[property] != null)
+ ? this.options[property]
+ : this.map[property];
+ }
+
+ if ( (this.minZoomLevel == null) ||
+ (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){
+ this.minZoomLevel = this.MIN_ZOOM_LEVEL;
+ }
+
+ //
+ // At this point, we know what the minimum desired zoom level is, and
+ // we must calculate the total number of zoom levels.
+ //
+ // Because we allow for the setting of either the 'numZoomLevels'
+ // or the 'maxZoomLevel' properties... on either the layer or the
+ // map, we have to define some rules to see which we take into
+ // account first in this calculation.
+ //
+ // The following is the precedence list for these properties:
+ //
+ // (1) numZoomLevels set on layer
+ // (2) maxZoomLevel set on layer
+ // (3) numZoomLevels set on map
+ // (4) maxZoomLevel set on map*
+ // (5) none of the above*
+ //
+ // *Note that options (4) and (5) are only possible if the user
+ // _explicitly_ sets the 'numZoomLevels' property on the map to
+ // null, since it is set by default to 16.
+ //
+
+ //
+ // Note to future: In 3.0, I think we should remove the default
+ // value of 16 for map.numZoomLevels. Rather, I think that value
+ // should be set as a default on the Layer.WMS class. If someone
+ // creates a 3rd party layer and does not specify any 'minZoomLevel',
+ // 'maxZoomLevel', or 'numZoomLevels', and has not explicitly
+ // specified any of those on the map object either.. then I think
+ // it is fair to say that s/he wants all the zoom levels available.
+ //
+ // By making map.numZoomLevels *null* by default, that will be the
+ // case. As it is, I don't feel comfortable changing that right now
+ // as it would be a glaring API change and actually would probably
+ // break many peoples' codes.
+ //
+
+ //the number of zoom levels we'd like to have.
+ var desiredZoomLevels;
+
+ //this is the maximum number of zoom levels the layer will allow,
+ // given the specified starting minimum zoom level.
+ var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;
+
+ if ( ((this.options.numZoomLevels == null) &&
+ (this.options.maxZoomLevel != null)) // (2)
+ ||
+ ((this.numZoomLevels == null) &&
+ (this.maxZoomLevel != null)) // (4)
+ ) {
+ //calculate based on specified maxZoomLevel (on layer or map)
+ desiredZoomLevels = this.maxZoomLevel - this.minZoomLevel + 1;
+ } else {
+ //calculate based on specified numZoomLevels (on layer or map)
+ // this covers cases (1) and (3)
+ desiredZoomLevels = this.numZoomLevels;
+ }
+
+ if (desiredZoomLevels != null) {
+ //Now that we know what we would *like* the number of zoom levels
+ // to be, based on layer or map options, we have to make sure that
+ // it does not conflict with the actual limit, as specified by
+ // the constants on the layer itself (and calculated into the
+ // 'limitZoomLevels' variable).
+ this.numZoomLevels = Math.min(desiredZoomLevels, limitZoomLevels);
+ } else {
+ // case (5) -- neither 'numZoomLevels' not 'maxZoomLevel' was
+ // set on either the layer or the map. So we just use the
+ // maximum limit as calculated by the layer's constants.
+ this.numZoomLevels = limitZoomLevels;
+ }
+
+ //now that the 'numZoomLevels' is appropriately, safely set,
+ // we go back and re-calculate the 'maxZoomLevel'.
+ this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1;
+
+ if (this.RESOLUTIONS != null) {
+ var resolutionsIndex = 0;
+ this.resolutions = [];
+ for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) {
+ this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i];
+ }
+ this.maxResolution = this.resolutions[0];
+ this.minResolution = this.resolutions[this.resolutions.length - 1];
+ }
+ },
+
+ /**
+ * APIMethod: getResolution
+ * Get the current map resolution
+ *
+ * Returns:
+ * {Float} Map units per Pixel
+ */
+ getResolution: function() {
+
+ if (this.resolutions != null) {
+ return OpenLayers.Layer.prototype.getResolution.apply(this, arguments);
+ } else {
+ var resolution = null;
+
+ var viewSize = this.map.getSize();
+ var extent = this.getExtent();
+
+ if ((viewSize != null) && (extent != null)) {
+ resolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+ }
+ return resolution;
+ }
+ },
+
+ /**
+ * APIMethod: getExtent
+ * Calculates using px-> lonlat translation functions on tl and br
+ * corners of viewport
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function () {
+ var size = this.map.getSize();
+ var tl = this.getLonLatFromViewPortPx({
+ x: 0, y: 0
+ });
+ var br = this.getLonLatFromViewPortPx({
+ x: size.w, y: size.h
+ });
+
+ if ((tl != null) && (br != null)) {
+ return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat);
+ } else {
+ return null;
+ }
+ },
+
+ /**
+ * Method: getZoomForResolution
+ * Get the zoom level for a given resolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution) {
+
+ if (this.resolutions != null) {
+ return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments);
+ } else {
+ var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []);
+ return this.getZoomForExtent(extent);
+ }
+ },
+
+
+
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate GMaps and OL */
+ /* formats for Pixel, LonLat, Bounds, and Zoom */
+ /* */
+ /********************************************************/
+
+
+ //
+ // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+ //
+
+ /**
+ * Method: getOLZoomFromMapObjectZoom
+ * Get the OL zoom index from the map object zoom level
+ *
+ * Parameters:
+ * moZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} An OpenLayers Zoom level, translated from the passed in zoom
+ * Returns null if null value is passed in
+ */
+ getOLZoomFromMapObjectZoom: function(moZoom) {
+ var zoom = null;
+ if (moZoom != null) {
+ zoom = moZoom - this.minZoomLevel;
+ if (this.map.baseLayer !== this) {
+ zoom = this.map.baseLayer.getZoomForResolution(
+ this.getResolutionForZoom(zoom)
+ );
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * Method: getMapObjectZoomFromOLZoom
+ * Get the map object zoom level from the OL zoom level
+ *
+ * Parameters:
+ * olZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} A MapObject level, translated from the passed in olZoom
+ * Returns null if null value is passed in
+ */
+ getMapObjectZoomFromOLZoom: function(olZoom) {
+ var zoom = null;
+ if (olZoom != null) {
+ zoom = olZoom + this.minZoomLevel;
+ if (this.map.baseLayer !== this) {
+ zoom = this.getZoomForResolution(
+ this.map.baseLayer.getResolutionForZoom(zoom)
+ );
+ }
+ }
+ return zoom;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels"
+});
+
+/* ======================================================================
+ OpenLayers/Layer/Google.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/SphericalMercator.js
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Google
+ *
+ * Provides a wrapper for Google's Maps API
+ * Normally the Terms of Use for this API do not allow wrapping, but Google
+ * have provided written consent to OpenLayers for this - see email in
+ * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.SphericalMercator>
+ * - <OpenLayers.Layer.EventPane>
+ * - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Google = OpenLayers.Class(
+ OpenLayers.Layer.EventPane,
+ OpenLayers.Layer.FixedZoomLevels, {
+
+ /**
+ * Constant: MIN_ZOOM_LEVEL
+ * {Integer} 0
+ */
+ MIN_ZOOM_LEVEL: 0,
+
+ /**
+ * Constant: MAX_ZOOM_LEVEL
+ * {Integer} 21
+ */
+ MAX_ZOOM_LEVEL: 21,
+
+ /**
+ * Constant: RESOLUTIONS
+ * {Array(Float)} Hardcode these resolutions so that they are more closely
+ * tied with the standard wms projection
+ */
+ RESOLUTIONS: [
+ 1.40625,
+ 0.703125,
+ 0.3515625,
+ 0.17578125,
+ 0.087890625,
+ 0.0439453125,
+ 0.02197265625,
+ 0.010986328125,
+ 0.0054931640625,
+ 0.00274658203125,
+ 0.001373291015625,
+ 0.0006866455078125,
+ 0.00034332275390625,
+ 0.000171661376953125,
+ 0.0000858306884765625,
+ 0.00004291534423828125,
+ 0.00002145767211914062,
+ 0.00001072883605957031,
+ 0.00000536441802978515,
+ 0.00000268220901489257,
+ 0.0000013411045074462891,
+ 0.00000067055225372314453
+ ],
+
+ /**
+ * APIProperty: type
+ * {GMapType}
+ */
+ type: null,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Allow user to pan forever east/west. Default is true.
+ * Setting this to false only restricts panning if
+ * <sphericalMercator> is true.
+ */
+ wrapDateLine: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * {Boolean} Should the map act as a mercator-projected map? This will
+ * cause all interactions with the map to be in the actual map
+ * projection, which allows support for vector drawing, overlaying
+ * other maps, etc.
+ */
+ sphericalMercator: false,
+
+ /**
+ * Property: version
+ * {Number} The version of the Google Maps API
+ */
+ version: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Google
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * options - {Object} An optional object whose properties will be set
+ * on the layer.
+ */
+ initialize: function(name, options) {
+ options = options || {};
+ if(!options.version) {
+ options.version = typeof GMap2 === "function" ? "2" : "3";
+ }
+ var mixin = OpenLayers.Layer.Google["v" +
+ options.version.replace(/\./g, "_")];
+ if (mixin) {
+ OpenLayers.Util.applyDefaults(options, mixin);
+ } else {
+ throw "Unsupported Google Maps API version: " + options.version;
+ }
+
+ OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS);
+ if (options.maxExtent) {
+ options.maxExtent = options.maxExtent.clone();
+ }
+
+ OpenLayers.Layer.EventPane.prototype.initialize.apply(this,
+ [name, options]);
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+ [name, options]);
+
+ if (this.sphericalMercator) {
+ OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+ this.initMercatorParameters();
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Google>} An exact clone of this layer
+ */
+ clone: function() {
+ /**
+ * This method isn't intended to be called by a subclass and it
+ * doesn't call the same method on the superclass. We don't call
+ * the super's clone because we don't want properties that are set
+ * on this layer after initialize (i.e. this.mapObject etc.).
+ */
+ return new OpenLayers.Layer.Google(
+ this.name, this.getOptions()
+ );
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the layer (if in range)
+ */
+ setVisibility: function(visible) {
+ // sharing a map container, opacity has to be set per layer
+ var opacity = this.opacity == null ? 1 : this.opacity;
+ OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments);
+ this.setOpacity(opacity);
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * visible - {Boolean}
+ */
+ display: function(visible) {
+ if (!this._dragging) {
+ this.setGMapVisibility(visible);
+ }
+ OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ this._dragging = dragging;
+ OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments);
+ delete this._dragging;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity !== this.opacity) {
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ this.opacity = opacity;
+ }
+ // Though this layer's opacity may not change, we're sharing a container
+ // and need to update the opacity for the entire container.
+ if (this.getVisibility()) {
+ var container = this.getMapContainer();
+ OpenLayers.Util.modifyDOMElement(
+ container, null, null, null, null, null, null, opacity
+ );
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up this layer.
+ */
+ destroy: function() {
+ /**
+ * We have to override this method because the event pane destroy
+ * deletes the mapObject reference before removing this layer from
+ * the map.
+ */
+ if (this.map) {
+ this.setGMapVisibility(false);
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache && cache.count <= 1) {
+ this.removeGMapElements();
+ }
+ }
+ OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: removeGMapElements
+ * Remove all elements added to the dom. This should only be called if
+ * this is the last of the Google layers for the given map.
+ */
+ removeGMapElements: function() {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // remove shared elements from dom
+ var container = this.mapObject && this.getMapContainer();
+ if (container && container.parentNode) {
+ container.parentNode.removeChild(container);
+ }
+ var termsOfUse = cache.termsOfUse;
+ if (termsOfUse && termsOfUse.parentNode) {
+ termsOfUse.parentNode.removeChild(termsOfUse);
+ }
+ var poweredBy = cache.poweredBy;
+ if (poweredBy && poweredBy.parentNode) {
+ poweredBy.parentNode.removeChild(poweredBy);
+ }
+ if (this.mapObject && window.google && google.maps &&
+ google.maps.event && google.maps.event.clearListeners) {
+ google.maps.event.clearListeners(this.mapObject, 'tilesloaded');
+ }
+ }
+ },
+
+ /**
+ * APIMethod: removeMap
+ * On being removed from the map, also remove termsOfUse and poweredBy divs
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ // hide layer before removing
+ if (this.visibility && this.mapObject) {
+ this.setGMapVisibility(false);
+ }
+ // check to see if last Google layer in this map
+ var cache = OpenLayers.Layer.Google.cache[map.id];
+ if (cache) {
+ if (cache.count <= 1) {
+ this.removeGMapElements();
+ delete OpenLayers.Layer.Google.cache[map.id];
+ } else {
+ // decrement the layer count
+ --cache.count;
+ }
+ }
+ // remove references to gmap elements
+ delete this.termsOfUse;
+ delete this.poweredBy;
+ delete this.mapObject;
+ delete this.dragObject;
+ OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments);
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getOLBoundsFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the
+ * passed-in MapObject Bounds.
+ * Returns null if null value is passed in.
+ */
+ getOLBoundsFromMapObjectBounds: function(moBounds) {
+ var olBounds = null;
+ if (moBounds != null) {
+ var sw = moBounds.getSouthWest();
+ var ne = moBounds.getNorthEast();
+ if (this.sphericalMercator) {
+ sw = this.forwardMercator(sw.lng(), sw.lat());
+ ne = this.forwardMercator(ne.lng(), ne.lat());
+ } else {
+ sw = new OpenLayers.LonLat(sw.lng(), sw.lat());
+ ne = new OpenLayers.LonLat(ne.lng(), ne.lat());
+ }
+ olBounds = new OpenLayers.Bounds(sw.lon,
+ sw.lat,
+ ne.lon,
+ ne.lat );
+ }
+ return olBounds;
+ },
+
+ /**
+ * APIMethod: getWarningHTML
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ return OpenLayers.i18n("googleWarning");
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: getMapObjectCenter
+ *
+ * Returns:
+ * {Object} The mapObject's current center in Map Object format
+ */
+ getMapObjectCenter: function() {
+ return this.mapObject.getCenter();
+ },
+
+ /**
+ * APIMethod: getMapObjectZoom
+ *
+ * Returns:
+ * {Integer} The mapObject's current zoom, in Map Object format
+ */
+ getMapObjectZoom: function() {
+ return this.mapObject.getZoom();
+ },
+
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getLongitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Longitude of the given MapObject LonLat
+ */
+ getLongitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon :
+ moLonLat.lng();
+ },
+
+ /**
+ * APIMethod: getLatitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Latitude of the given MapObject LonLat
+ */
+ getLatitudeFromMapObjectLonLat: function(moLonLat) {
+ var lat = this.sphericalMercator ?
+ this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat :
+ moLonLat.lat();
+ return lat;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getXFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} X value of the MapObject Pixel
+ */
+ getXFromMapObjectPixel: function(moPixel) {
+ return moPixel.x;
+ },
+
+ /**
+ * APIMethod: getYFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} Y value of the MapObject Pixel
+ */
+ getYFromMapObjectPixel: function(moPixel) {
+ return moPixel.y;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Google"
+});
+
+/**
+ * Property: OpenLayers.Layer.Google.cache
+ * {Object} Cache for elements that should only be created once per map.
+ */
+OpenLayers.Layer.Google.cache = {};
+
+
+/**
+ * Constant: OpenLayers.Layer.Google.v2
+ *
+ * Mixin providing functionality specific to the Google Maps API v2.
+ *
+ * This API has been deprecated by Google.
+ * Developers are encouraged to migrate to v3 of the API; support for this
+ * is provided by <OpenLayers.Layer.Google.v3>
+ */
+OpenLayers.Layer.Google.v2 = {
+
+ /**
+ * Property: termsOfUse
+ * {DOMElement} Div for Google's copyright and terms of use link
+ */
+ termsOfUse: null,
+
+ /**
+ * Property: poweredBy
+ * {DOMElement} Div for Google's powered by logo and link
+ */
+ poweredBy: null,
+
+ /**
+ * Property: dragObject
+ * {GDraggableObject} Since 2.93, Google has exposed the ability to get
+ * the maps GDraggableObject. We can now use this for smooth panning
+ */
+ dragObject: null,
+
+ /**
+ * Method: loadMapObject
+ * Load the GMap and register appropriate event listeners. If we can't
+ * load GMap2, then display a warning message.
+ */
+ loadMapObject:function() {
+ if (!this.type) {
+ this.type = G_NORMAL_MAP;
+ }
+ var mapObject, termsOfUse, poweredBy;
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // there are already Google layers added to this map
+ mapObject = cache.mapObject;
+ termsOfUse = cache.termsOfUse;
+ poweredBy = cache.poweredBy;
+ // increment the layer count
+ ++cache.count;
+ } else {
+ // this is the first Google layer for this map
+
+ var container = this.map.viewPortDiv;
+ var div = document.createElement("div");
+ div.id = this.map.id + "_GMap2Container";
+ div.style.position = "absolute";
+ div.style.width = "100%";
+ div.style.height = "100%";
+ container.appendChild(div);
+
+ // create GMap and shuffle elements
+ try {
+ mapObject = new GMap2(div);
+
+ // move the ToS and branding stuff up to the container div
+ termsOfUse = div.lastChild;
+ container.appendChild(termsOfUse);
+ termsOfUse.style.zIndex = "1100";
+ termsOfUse.style.right = "";
+ termsOfUse.style.bottom = "";
+ termsOfUse.className = "olLayerGoogleCopyright";
+
+ poweredBy = div.lastChild;
+ container.appendChild(poweredBy);
+ poweredBy.style.zIndex = "1100";
+ poweredBy.style.right = "";
+ poweredBy.style.bottom = "";
+ poweredBy.className = "olLayerGooglePoweredBy gmnoprint";
+
+ } catch (e) {
+ throw(e);
+ }
+ // cache elements for use by any other google layers added to
+ // this same map
+ OpenLayers.Layer.Google.cache[this.map.id] = {
+ mapObject: mapObject,
+ termsOfUse: termsOfUse,
+ poweredBy: poweredBy,
+ count: 1
+ };
+ }
+
+ this.mapObject = mapObject;
+ this.termsOfUse = termsOfUse;
+ this.poweredBy = poweredBy;
+
+ // ensure this layer type is one of the mapObject types
+ if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),
+ this.type) === -1) {
+ this.mapObject.addMapType(this.type);
+ }
+
+ //since v 2.93 getDragObject is now available.
+ if(typeof mapObject.getDragObject == "function") {
+ this.dragObject = mapObject.getDragObject();
+ } else {
+ this.dragPanMapObject = null;
+ }
+
+ if(this.isBaseLayer === false) {
+ this.setGMapVisibility(this.div.style.display !== "none");
+ }
+
+ },
+
+ /**
+ * APIMethod: onMapResize
+ */
+ onMapResize: function() {
+ // workaround for resizing of invisible or not yet fully loaded layers
+ // where GMap2.checkResize() does not work. We need to load the GMap
+ // for the old div size, then checkResize(), and then call
+ // layer.moveTo() to trigger GMap.setCenter() (which will finish
+ // the GMap initialization).
+ if(this.visibility && this.mapObject.isLoaded()) {
+ this.mapObject.checkResize();
+ } else {
+ if(!this._resized) {
+ var layer = this;
+ var handle = GEvent.addListener(this.mapObject, "load", function() {
+ GEvent.removeListener(handle);
+ delete layer._resized;
+ layer.mapObject.checkResize();
+ layer.moveTo(layer.map.getCenter(), layer.map.getZoom());
+ });
+ }
+ this._resized = true;
+ }
+ },
+
+ /**
+ * Method: setGMapVisibility
+ * Display the GMap container and associated elements.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the GMap elements.
+ */
+ setGMapVisibility: function(visible) {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ var container = this.mapObject.getContainer();
+ if (visible === true) {
+ this.mapObject.setMapType(this.type);
+ container.style.display = "";
+ this.termsOfUse.style.left = "";
+ this.termsOfUse.style.display = "";
+ this.poweredBy.style.display = "";
+ cache.displayed = this.id;
+ } else {
+ if (cache.displayed === this.id) {
+ delete cache.displayed;
+ }
+ if (!cache.displayed) {
+ container.style.display = "none";
+ this.termsOfUse.style.display = "none";
+ // move ToU far to the left in addition to setting display
+ // to "none", because at the end of the GMap2 load
+ // sequence, display: none will be unset and ToU would be
+ // visible after loading a map with a google layer that is
+ // initially hidden.
+ this.termsOfUse.style.left = "-9999px";
+ this.poweredBy.style.display = "none";
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getMapContainer
+ *
+ * Returns:
+ * {DOMElement} the GMap container's div
+ */
+ getMapContainer: function() {
+ return this.mapObject.getContainer();
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getMapObjectBoundsFromOLBounds
+ *
+ * Parameters:
+ * olBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} A MapObject Bounds, translated from olBounds
+ * Returns null if null value is passed in
+ */
+ getMapObjectBoundsFromOLBounds: function(olBounds) {
+ var moBounds = null;
+ if (olBounds != null) {
+ var sw = this.sphericalMercator ?
+ this.inverseMercator(olBounds.bottom, olBounds.left) :
+ new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+ var ne = this.sphericalMercator ?
+ this.inverseMercator(olBounds.top, olBounds.right) :
+ new OpenLayers.LonLat(olBounds.top, olBounds.right);
+ moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon),
+ new GLatLng(ne.lat, ne.lon));
+ }
+ return moBounds;
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ this.mapObject.setCenter(center, zoom);
+ },
+
+ /**
+ * APIMethod: dragPanMapObject
+ *
+ * Parameters:
+ * dX - {Integer}
+ * dY - {Integer}
+ */
+ dragPanMapObject: function(dX, dY) {
+ this.dragObject.moveBy(new GSize(-dX, dY));
+ },
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ return this.mapObject.fromContainerPixelToLatLng(moPixel);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ return this.mapObject.fromLatLngToContainerPixel(moLonLat);
+ },
+
+
+ // Bounds
+
+ /**
+ * APIMethod: getMapObjectZoomFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object} MapObject Bounds format
+ *
+ * Returns:
+ * {Object} MapObject Zoom for specified MapObject Bounds
+ */
+ getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+ return this.mapObject.getBoundsZoomLevel(moBounds);
+ },
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var gLatLng;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ gLatLng = new GLatLng(lonlat.lat, lonlat.lon);
+ } else {
+ gLatLng = new GLatLng(lat, lon);
+ }
+ return gLatLng;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new GPoint(x, y);
+ }
+
+};
+/* ======================================================================
+ OpenLayers/Format/XML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML
+ * Read and write XML. For cross-browser XML generation, use methods on an
+ * instance of the XML format class instead of on <code>document<end>.
+ * The DOM creation and traversing methods exposed here all mimic the
+ * W3C XML DOM methods. Create a new parser with the
+ * <OpenLayers.Format.XML> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs. Properties
+ * of this object should not be set individually. Read-only. All
+ * XML subclasses should have their own namespaces object. Use
+ * <setNamespace> to add or set a namespace alias after construction.
+ */
+ namespaces: null,
+
+ /**
+ * Property: namespaceAlias
+ * {Object} Mapping of namespace URI to namespace alias. This object
+ * is read-only. Use <setNamespace> to add or set a namespace alias.
+ */
+ namespaceAlias: null,
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default namespace alias for creating element nodes.
+ */
+ defaultPrefix: null,
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {},
+
+ /**
+ * Property: writers
+ * As a compliment to the <readers> property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {},
+
+ /**
+ * Property: xmldom
+ * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
+ * object. It is not intended to be a browser sniffing property.
+ * Instead, the xmldom property is used instead of <code>document<end>
+ * where namespaced node creation methods are not supported. In all
+ * other browsers, this remains null.
+ */
+ xmldom: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XML
+ * Construct an XML parser. The parser is used to read and write XML.
+ * Reading XML from a string returns a DOM element. Writing XML from
+ * a DOM element returns a string.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on
+ * the object.
+ */
+ initialize: function(options) {
+ if(window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ // clone the namespace object and set all namespace aliases
+ this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
+ this.namespaceAlias = {};
+ for(var alias in this.namespaces) {
+ this.namespaceAlias[this.namespaces[alias]] = alias;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ this.xmldom = null;
+ OpenLayers.Format.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setNamespace
+ * Set a namespace alias and URI for the format.
+ *
+ * Parameters:
+ * alias - {String} The namespace alias (prefix).
+ * uri - {String} The namespace URI.
+ */
+ setNamespace: function(alias, uri) {
+ this.namespaces[alias] = uri;
+ this.namespaceAlias[uri] = alias;
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a XML string and return a DOM node.
+ *
+ * Parameters:
+ * text - {String} A XML string
+
+ * Returns:
+ * {DOMElement} A DOM node
+ */
+ read: function(text) {
+ var index = text.indexOf('<');
+ if(index > 0) {
+ text = text.substring(index);
+ }
+ var node = OpenLayers.Util.Try(
+ OpenLayers.Function.bind((
+ function() {
+ var xmldom;
+ /**
+ * Since we want to be able to call this method on the prototype
+ * itself, this.xmldom may not exist even if in IE.
+ */
+ if(window.ActiveXObject && !this.xmldom) {
+ xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ } else {
+ xmldom = this.xmldom;
+
+ }
+ xmldom.loadXML(text);
+ return xmldom;
+ }
+ ), this),
+ function() {
+ return new DOMParser().parseFromString(text, 'text/xml');
+ },
+ function() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "data:" + "text/xml" +
+ ";charset=utf-8," + encodeURIComponent(text), false);
+ if(req.overrideMimeType) {
+ req.overrideMimeType("text/xml");
+ }
+ req.send(null);
+ return req.responseXML;
+ }
+ );
+
+ if(this.keepData) {
+ this.data = node;
+ }
+
+ return node;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a DOM node into a XML string.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM node.
+ *
+ * Returns:
+ * {String} The XML string representation of the input node.
+ */
+ write: function(node) {
+ var data;
+ if(this.xmldom) {
+ data = node.xml;
+ } else {
+ var serializer = new XMLSerializer();
+ if (node.nodeType == 1) {
+ // Add nodes to a document before serializing. Everything else
+ // is serialized as is. This may need more work. See #1218 .
+ var doc = document.implementation.createDocument("", "", null);
+ if (doc.importNode) {
+ node = doc.importNode(node, true);
+ }
+ doc.appendChild(node);
+ data = serializer.serializeToString(doc);
+ } else {
+ data = serializer.serializeToString(node);
+ }
+ }
+ return data;
+ },
+
+ /**
+ * APIMethod: createElementNS
+ * Create a new element with namespace. This node can be appended to
+ * another node with the standard node.appendChild method. For
+ * cross-browser support, this method must be used instead of
+ * document.createElementNS.
+ *
+ * Parameters:
+ * uri - {String} Namespace URI for the element.
+ * name - {String} The qualified name of the element (prefix:localname).
+ *
+ * Returns:
+ * {Element} A DOM element with namespace.
+ */
+ createElementNS: function(uri, name) {
+ var element;
+ if(this.xmldom) {
+ if(typeof uri == "string") {
+ element = this.xmldom.createNode(1, name, uri);
+ } else {
+ element = this.xmldom.createNode(1, name, "");
+ }
+ } else {
+ element = document.createElementNS(uri, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIMethod: createDocumentFragment
+ * Create a document fragment node that can be appended to another node
+ * created by createElementNS. This will call
+ * document.createDocumentFragment outside of IE. In IE, the ActiveX
+ * object's createDocumentFragment method is used.
+ *
+ * Returns:
+ * {Element} A document fragment.
+ */
+ createDocumentFragment: function() {
+ var element;
+ if (this.xmldom) {
+ element = this.xmldom.createDocumentFragment();
+ } else {
+ element = document.createDocumentFragment();
+ }
+ return element;
+ },
+
+ /**
+ * APIMethod: createTextNode
+ * Create a text node. This node can be appended to another node with
+ * the standard node.appendChild method. For cross-browser support,
+ * this method must be used instead of document.createTextNode.
+ *
+ * Parameters:
+ * text - {String} The text of the node.
+ *
+ * Returns:
+ * {DOMElement} A DOM text node.
+ */
+ createTextNode: function(text) {
+ var node;
+ if (typeof text !== "string") {
+ text = String(text);
+ }
+ if(this.xmldom) {
+ node = this.xmldom.createTextNode(text);
+ } else {
+ node = document.createTextNode(text);
+ }
+ return node;
+ },
+
+ /**
+ * APIMethod: getElementsByTagNameNS
+ * Get a list of elements on a node given the namespace URI and local name.
+ * To return all nodes in a given namespace, use '*' for the name
+ * argument. To return all nodes of a given (local) name, regardless
+ * of namespace, use '*' for the uri argument.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for other nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the tag (without the prefix).
+ *
+ * Returns:
+ * {NodeList} A node list or array of elements.
+ */
+ getElementsByTagNameNS: function(node, uri, name) {
+ var elements = [];
+ if(node.getElementsByTagNameNS) {
+ elements = node.getElementsByTagNameNS(uri, name);
+ } else {
+ // brute force method
+ var allNodes = node.getElementsByTagName("*");
+ var potentialNode, fullName;
+ for(var i=0, len=allNodes.length; i<len; ++i) {
+ potentialNode = allNodes[i];
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if((name == "*") || (fullName == potentialNode.nodeName)) {
+ if((uri == "*") || (uri == potentialNode.namespaceURI)) {
+ elements.push(potentialNode);
+ }
+ }
+ }
+ }
+ return elements;
+ },
+
+ /**
+ * APIMethod: getAttributeNodeNS
+ * Get an attribute node given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for attribute nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {DOMElement} An attribute node or null if none found.
+ */
+ getAttributeNodeNS: function(node, uri, name) {
+ var attributeNode = null;
+ if(node.getAttributeNodeNS) {
+ attributeNode = node.getAttributeNodeNS(uri, name);
+ } else {
+ var attributes = node.attributes;
+ var potentialNode, fullName;
+ for(var i=0, len=attributes.length; i<len; ++i) {
+ potentialNode = attributes[i];
+ if(potentialNode.namespaceURI == uri) {
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if(fullName == potentialNode.nodeName) {
+ attributeNode = potentialNode;
+ break;
+ }
+ }
+ }
+ }
+ return attributeNode;
+ },
+
+ /**
+ * APIMethod: getAttributeNS
+ * Get an attribute value given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {String} An attribute value or and empty string if none found.
+ */
+ getAttributeNS: function(node, uri, name) {
+ var attributeValue = "";
+ if(node.getAttributeNS) {
+ attributeValue = node.getAttributeNS(uri, name) || "";
+ } else {
+ var attributeNode = this.getAttributeNodeNS(node, uri, name);
+ if(attributeNode) {
+ attributeValue = attributeNode.nodeValue;
+ }
+ }
+ return attributeValue;
+ },
+
+ /**
+ * APIMethod: getChildValue
+ * Get the textual value of the node if it exists, or return an
+ * optional default string. Returns an empty string if no first child
+ * exists and no default value is supplied.
+ *
+ * Parameters:
+ * node - {DOMElement} The element used to look for a first child value.
+ * def - {String} Optional string to return in the event that no
+ * first child value exists.
+ *
+ * Returns:
+ * {String} The value of the first child of the given node.
+ */
+ getChildValue: function(node, def) {
+ var value = def || "";
+ if(node) {
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ }
+ return value;
+ },
+
+ /**
+ * APIMethod: isSimpleContent
+ * Test if the given node has only simple content (i.e. no child element
+ * nodes).
+ *
+ * Parameters:
+ * node - {DOMElement} An element node.
+ *
+ * Returns:
+ * {Boolean} The node has no child element nodes (nodes of type 1).
+ */
+ isSimpleContent: function(node) {
+ var simple = true;
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ if(child.nodeType === 1) {
+ simple = false;
+ break;
+ }
+ }
+ return simple;
+ },
+
+ /**
+ * APIMethod: contentType
+ * Determine the content type for a given node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
+ * if the node has no, simple, complex, or mixed content.
+ */
+ contentType: function(node) {
+ var simple = false,
+ complex = false;
+
+ var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
+
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1: // element
+ complex = true;
+ break;
+ case 8: // comment
+ break;
+ default:
+ simple = true;
+ }
+ if(complex && simple) {
+ break;
+ }
+ }
+
+ if(complex && simple) {
+ type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
+ } else if(complex) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
+ } else if(simple) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
+ }
+ return type;
+ },
+
+ /**
+ * APIMethod: hasAttributeNS
+ * Determine whether a node has a particular attribute matching the given
+ * name and namespace.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {Boolean} The node has an attribute matching the name and namespace.
+ */
+ hasAttributeNS: function(node, uri, name) {
+ var found = false;
+ if(node.hasAttributeNS) {
+ found = node.hasAttributeNS(uri, name);
+ } else {
+ found = !!this.getAttributeNodeNS(node, uri, name);
+ }
+ return found;
+ },
+
+ /**
+ * APIMethod: setAttributeNS
+ * Adds a new attribute or changes the value of an attribute with the given
+ * namespace and name.
+ *
+ * Parameters:
+ * node - {Element} Element node on which to set the attribute.
+ * uri - {String} Namespace URI for the attribute.
+ * name - {String} Qualified name (prefix:localname) for the attribute.
+ * value - {String} Attribute value.
+ */
+ setAttributeNS: function(node, uri, name, value) {
+ if(node.setAttributeNS) {
+ node.setAttributeNS(uri, name, value);
+ } else {
+ if(this.xmldom) {
+ if(uri) {
+ var attribute = node.ownerDocument.createNode(
+ 2, name, uri
+ );
+ attribute.nodeValue = value;
+ node.setAttributeNode(attribute);
+ } else {
+ node.setAttribute(name, value);
+ }
+ } else {
+ throw "setAttributeNS not implemented";
+ }
+ }
+ },
+
+ /**
+ * Method: createElementNSPlus
+ * Shorthand for creating namespaced elements with optional attributes and
+ * child text nodes.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * options - {Object} Optional object for node configuration.
+ *
+ * Valid options:
+ * uri - {String} Optional namespace uri for the element - supply a prefix
+ * instead if the namespace uri is a property of the format's namespace
+ * object.
+ * attributes - {Object} Optional attributes to be set using the
+ * <setAttributes> method.
+ * value - {String} Optional text to be appended as a text node.
+ *
+ * Returns:
+ * {Element} An element node.
+ */
+ createElementNSPlus: function(name, options) {
+ options = options || {};
+ // order of prefix preference
+ // 1. in the uri option
+ // 2. in the prefix option
+ // 3. in the qualified name
+ // 4. from the defaultPrefix
+ var uri = options.uri || this.namespaces[options.prefix];
+ if(!uri) {
+ var loc = name.indexOf(":");
+ uri = this.namespaces[name.substring(0, loc)];
+ }
+ if(!uri) {
+ uri = this.namespaces[this.defaultPrefix];
+ }
+ var node = this.createElementNS(uri, name);
+ if(options.attributes) {
+ this.setAttributes(node, options.attributes);
+ }
+ var value = options.value;
+ if(value != null) {
+ node.appendChild(this.createTextNode(value));
+ }
+ return node;
+ },
+
+ /**
+ * Method: setAttributes
+ * Set multiple attributes given key value pairs from an object.
+ *
+ * Parameters:
+ * node - {Element} An element node.
+ * obj - {Object || Array} An object whose properties represent attribute
+ * names and values represent attribute values. If an attribute name
+ * is a qualified name ("prefix:local"), the prefix will be looked up
+ * in the parsers {namespaces} object. If the prefix is found,
+ * setAttributeNS will be used instead of setAttribute.
+ */
+ setAttributes: function(node, obj) {
+ var value, uri;
+ for(var name in obj) {
+ if(obj[name] != null && obj[name].toString) {
+ value = obj[name].toString();
+ // check for qualified attribute name ("prefix:local")
+ uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+ this.setAttributeNS(node, uri, name, value);
+ }
+ }
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj) {
+ if(!obj) {
+ obj = {};
+ }
+ var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
+ if(group) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var reader = group[local] || group["*"];
+ if(reader) {
+ reader.apply(this, [node, obj]);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: readChildNodes
+ * Shorthand for applying the named readers to all children of a node.
+ * For each child of type 1 (element), <readSelf> is called.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ *
+ * Returns:
+ * {Object} The input object, modified.
+ */
+ readChildNodes: function(node, obj) {
+ if(!obj) {
+ obj = {};
+ }
+ var children = node.childNodes;
+ var child;
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ this.readNode(child, obj);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: writeNode
+ * Shorthand for applying one of the named writers and appending the
+ * results to a node. If a qualified name is not provided for the
+ * second argument (and a local name is used instead), the namespace
+ * of the parent node will be assumed.
+ *
+ * Parameters:
+ * name - {String} The name of a node to generate. If a qualified name
+ * (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+ * in the <writers> group. If a local name is used (e.g. "Name") then
+ * the namespace of the parent is assumed. If a local name is used
+ * and no parent is supplied, then the default namespace is assumed.
+ * obj - {Object} Structure containing data for the writer.
+ * parent - {DOMElement} Result will be appended to this node. If no parent
+ * is supplied, the node will not be appended to anything.
+ *
+ * Returns:
+ * {DOMElement} The child node.
+ */
+ writeNode: function(name, obj, parent) {
+ var prefix, local;
+ var split = name.indexOf(":");
+ if(split > 0) {
+ prefix = name.substring(0, split);
+ local = name.substring(split + 1);
+ } else {
+ if(parent) {
+ prefix = this.namespaceAlias[parent.namespaceURI];
+ } else {
+ prefix = this.defaultPrefix;
+ }
+ local = name;
+ }
+ var child = this.writers[prefix][local].apply(this, [obj]);
+ if(parent) {
+ parent.appendChild(child);
+ }
+ return child;
+ },
+
+ /**
+ * APIMethod: getChildEl
+ * Get the first child element. Optionally only return the first child
+ * if it matches the given name and namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The parent node.
+ * name - {String} Optional node name (local) to search for.
+ * uri - {String} Optional namespace URI to search for.
+ *
+ * Returns:
+ * {DOMElement} The first child. Returns null if no element is found, if
+ * something significant besides an element is found, or if the element
+ * found does not match the optional name and uri.
+ */
+ getChildEl: function(node, name, uri) {
+ return node && this.getThisOrNextEl(node.firstChild, name, uri);
+ },
+
+ /**
+ * APIMethod: getNextEl
+ * Get the next sibling element. Optionally get the first sibling only
+ * if it matches the given local name and namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The node.
+ * name - {String} Optional local name of the sibling to search for.
+ * uri - {String} Optional namespace URI of the sibling to search for.
+ *
+ * Returns:
+ * {DOMElement} The next sibling element. Returns null if no element is
+ * found, something significant besides an element is found, or the
+ * found element does not match the optional name and uri.
+ */
+ getNextEl: function(node, name, uri) {
+ return node && this.getThisOrNextEl(node.nextSibling, name, uri);
+ },
+
+ /**
+ * Method: getThisOrNextEl
+ * Return this node or the next element node. Optionally get the first
+ * sibling with the given local name or namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The node.
+ * name - {String} Optional local name of the sibling to search for.
+ * uri - {String} Optional namespace URI of the sibling to search for.
+ *
+ * Returns:
+ * {DOMElement} The next sibling element. Returns null if no element is
+ * found, something significant besides an element is found, or the
+ * found element does not match the query.
+ */
+ getThisOrNextEl: function(node, name, uri) {
+ outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
+ switch(sibling.nodeType) {
+ case 1: // Element
+ if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
+ (!uri || uri === sibling.namespaceURI)) {
+ // matches
+ break outer;
+ }
+ sibling = null;
+ break outer;
+ case 3: // Text
+ if(/^\s*$/.test(sibling.nodeValue)) {
+ break;
+ }
+ case 4: // CDATA
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ sibling = null;
+ break outer;
+ } // ignore comments and processing instructions
+ }
+ return sibling || null;
+ },
+
+ /**
+ * APIMethod: lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+ lookupNamespaceURI: function(node, prefix) {
+ var uri = null;
+ if(node) {
+ if(node.lookupNamespaceURI) {
+ uri = node.lookupNamespaceURI(prefix);
+ } else {
+ outer: switch(node.nodeType) {
+ case 1: // ELEMENT_NODE
+ if(node.namespaceURI !== null && node.prefix === prefix) {
+ uri = node.namespaceURI;
+ break outer;
+ }
+ var len = node.attributes.length;
+ if(len) {
+ var attr;
+ for(var i=0; i<len; ++i) {
+ attr = node.attributes[i];
+ if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
+ uri = attr.value || null;
+ break outer;
+ } else if(attr.name === "xmlns" && prefix === null) {
+ uri = attr.value || null;
+ break outer;
+ }
+ }
+ }
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ case 2: // ATTRIBUTE_NODE
+ uri = this.lookupNamespaceURI(node.ownerElement, prefix);
+ break outer;
+ case 9: // DOCUMENT_NODE
+ uri = this.lookupNamespaceURI(node.documentElement, prefix);
+ break outer;
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ break outer;
+ default:
+ // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
+ // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ }
+ }
+ }
+ return uri;
+ },
+
+ /**
+ * Method: getXMLDoc
+ * Get an XML document for nodes that are not supported in HTML (e.g.
+ * createCDATASection). On IE, this will either return an existing or
+ * create a new <xmldom> on the instance. On other browsers, this will
+ * either return an existing or create a new shared document (see
+ * <OpenLayers.Format.XML.document>).
+ *
+ * Returns:
+ * {XMLDocument}
+ */
+ getXMLDoc: function() {
+ if (!OpenLayers.Format.XML.document && !this.xmldom) {
+ if (document.implementation && document.implementation.createDocument) {
+ OpenLayers.Format.XML.document =
+ document.implementation.createDocument("", "", null);
+ } else if (!this.xmldom && window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ }
+ return OpenLayers.Format.XML.document || this.xmldom;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML"
+
+});
+
+OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
+
+/**
+ * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
+ OpenLayers.Format.XML.prototype.lookupNamespaceURI,
+ OpenLayers.Format.XML.prototype
+);
+
+/**
+ * Property: OpenLayers.Format.XML.document
+ * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
+ * like document.createCDATASection.
+ */
+OpenLayers.Format.XML.document = null;
+/* ======================================================================
+ OpenLayers/Format/WFST.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Function: OpenLayers.Format.WFST
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A WFST format of the given version.
+ */
+OpenLayers.Format.WFST = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.WFST.DEFAULTS
+ );
+ var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFST version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Format.WFST.DEFAULTS
+ * {Object} Default properties for the WFST format.
+ */
+OpenLayers.Format.WFST.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ * class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>}
+ */
+ lonlat: null,
+
+ /**
+ * Property: data
+ * {Object}
+ */
+ data: null,
+
+ /**
+ * Property: marker
+ * {<OpenLayers.Marker>}
+ */
+ marker: null,
+
+ /**
+ * APIProperty: popupClass
+ * {<OpenLayers.Class>} The class which will be used to instantiate
+ * a new Popup. Default is <OpenLayers.Popup.Anchored>.
+ */
+ popupClass: null,
+
+ /**
+ * Property: popup
+ * {<OpenLayers.Popup>}
+ */
+ popup: null,
+
+ /**
+ * Constructor: OpenLayers.Feature
+ * Constructor for features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * lonlat - {<OpenLayers.LonLat>}
+ * data - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Feature>}
+ */
+ initialize: function(layer, lonlat, data) {
+ this.layer = layer;
+ this.lonlat = lonlat;
+ this.data = (data != null) ? data : {};
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ //remove the popup from the map
+ if ((this.layer != null) && (this.layer.map != null)) {
+ if (this.popup != null) {
+ this.layer.map.removePopup(this.popup);
+ }
+ }
+ // remove the marker from the layer
+ if (this.layer != null && this.marker != null) {
+ this.layer.removeMarker(this.marker);
+ }
+
+ this.layer = null;
+ this.id = null;
+ this.lonlat = null;
+ this.data = null;
+ if (this.marker != null) {
+ this.destroyMarker(this.marker);
+ this.marker = null;
+ }
+ if (this.popup != null) {
+ this.destroyPopup(this.popup);
+ this.popup = null;
+ }
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is currently visible on screen
+ * (based on its 'lonlat' property)
+ */
+ onScreen:function() {
+
+ var onScreen = false;
+ if ((this.layer != null) && (this.layer.map != null)) {
+ var screenBounds = this.layer.map.getExtent();
+ onScreen = screenBounds.containsLonLat(this.lonlat);
+ }
+ return onScreen;
+ },
+
+
+ /**
+ * Method: createMarker
+ * Based on the data associated with the Feature, create and return a marker object.
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
+ * set in this.data. If no 'lonlat' is set, returns null. If no
+ * 'icon' is set, OpenLayers.Marker() will load the default image.
+ *
+ * Note - this.marker is set to return value
+ *
+ */
+ createMarker: function() {
+
+ if (this.lonlat != null) {
+ this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
+ }
+ return this.marker;
+ },
+
+ /**
+ * Method: destroyMarker
+ * Destroys marker.
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ this.marker.destroy();
+ },
+
+ /**
+ * Method: createPopup
+ * Creates a popup object created from the 'lonlat', 'popupSize',
+ * and 'popupContentHTML' properties set in this.data. It uses
+ * this.marker.icon as default anchor.
+ *
+ * If no 'lonlat' is set, returns null.
+ * If no this.marker has been created, no anchor is sent.
+ *
+ * Note - the returned popup object is 'owned' by the feature, so you
+ * cannot use the popup's destroy method to discard the popup.
+ * Instead, you must use the feature's destroyPopup
+ *
+ * Note - this.popup is set to return value
+ *
+ * Parameters:
+ * closeBox - {Boolean} create popup with closebox or not
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} Returns the created popup, which is also set
+ * as 'popup' property of this feature. Will be of whatever type
+ * specified by this feature's 'popupClass' property, but must be
+ * of type <OpenLayers.Popup>.
+ *
+ */
+ createPopup: function(closeBox) {
+
+ if (this.lonlat != null) {
+ if (!this.popup) {
+ var anchor = (this.marker) ? this.marker.icon : null;
+ var popupClass = this.popupClass ?
+ this.popupClass : OpenLayers.Popup.Anchored;
+ this.popup = new popupClass(this.id + "_popup",
+ this.lonlat,
+ this.data.popupSize,
+ this.data.popupContentHTML,
+ anchor,
+ closeBox);
+ }
+ if (this.data.overflow != null) {
+ this.popup.contentDiv.style.overflow = this.data.overflow;
+ }
+
+ this.popup.feature = this;
+ }
+ return this.popup;
+ },
+
+
+ /**
+ * Method: destroyPopup
+ * Destroys the popup created via createPopup.
+ *
+ * As with the marker, if user overrides the createPopup() function, s/he
+ * should also be able to override the destruction
+ */
+ destroyPopup: function() {
+ if (this.popup) {
+ this.popup.feature = null;
+ this.popup.destroy();
+ this.popup = null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature"
+});
+/* ======================================================================
+ OpenLayers/Feature/Vector.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+// TRASH THIS
+OpenLayers.State = {
+ /** states */
+ UNKNOWN: 'Unknown',
+ INSERT: 'Insert',
+ UPDATE: 'Update',
+ DELETE: 'Delete'
+};
+
+/**
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.Vector
+ * Vector features use the OpenLayers.Geometry classes as geometry description.
+ * They have an 'attributes' property, which is the data object, and a 'style'
+ * property, the default values of which are defined in the
+ * <OpenLayers.Feature.Vector.style> objects.
+ *
+ * Inherits from:
+ * - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+ /**
+ * Property: fid
+ * {String}
+ */
+ fid: null,
+
+ /**
+ * APIProperty: geometry
+ * {<OpenLayers.Geometry>}
+ */
+ geometry: null,
+
+ /**
+ * APIProperty: attributes
+ * {Object} This object holds arbitrary, serializable properties that
+ * describe the feature.
+ */
+ attributes: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
+ * property can be set by an <OpenLayers.Format> object when
+ * deserializing the feature, so in most cases it represents an
+ * information set by the server.
+ */
+ bounds: null,
+
+ /**
+ * Property: state
+ * {String}
+ */
+ state: null,
+
+ /**
+ * APIProperty: style
+ * {Object}
+ */
+ style: null,
+
+ /**
+ * APIProperty: url
+ * {String} If this property is set it will be taken into account by
+ * {<OpenLayers.HTTP>} when upadting or deleting the feature.
+ */
+ url: null,
+
+ /**
+ * Property: renderIntent
+ * {String} rendering intent currently being used
+ */
+ renderIntent: "default",
+
+ /**
+ * APIProperty: modified
+ * {Object} An object with the originals of the geometry and attributes of
+ * the feature, if they were changed. Currently this property is only read
+ * by <OpenLayers.Format.WFST.v1>, and written by
+ * <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
+ * Applications can set the originals of modified attributes in the
+ * attributes property. Note that applications have to check if this
+ * object and the attributes property is already created before using it.
+ * After a change made with ModifyFeature, this object could look like
+ *
+ * (code)
+ * {
+ * geometry: >Object
+ * }
+ * (end)
+ *
+ * When an application has made changes to feature attributes, it could
+ * have set the attributes to something like this:
+ *
+ * (code)
+ * {
+ * attributes: {
+ * myAttribute: "original"
+ * }
+ * }
+ * (end)
+ *
+ * Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
+ * *modified.geometry* and the attribute names in *modified.attributes*,
+ * but it is recommended to set the original values (and not just true) as
+ * attribute value, so applications could use this information to undo
+ * changes.
+ */
+ modified: null,
+
+ /**
+ * Constructor: OpenLayers.Feature.Vector
+ * Create a vector feature.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry that this feature
+ * represents.
+ * attributes - {Object} An optional object that will be mapped to the
+ * <attributes> property.
+ * style - {Object} An optional style object.
+ */
+ initialize: function(geometry, attributes, style) {
+ OpenLayers.Feature.prototype.initialize.apply(this,
+ [null, null, attributes]);
+ this.lonlat = null;
+ this.geometry = geometry ? geometry : null;
+ this.state = null;
+ this.attributes = {};
+ if (attributes) {
+ this.attributes = OpenLayers.Util.extend(this.attributes,
+ attributes);
+ }
+ this.style = style ? style : null;
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.layer) {
+ this.layer.removeFeatures(this);
+ this.layer = null;
+ }
+
+ this.geometry = null;
+ this.modified = null;
+ OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this vector feature. Does not set any non-standard
+ * properties.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
+ */
+ clone: function () {
+ return new OpenLayers.Feature.Vector(
+ this.geometry ? this.geometry.clone() : null,
+ this.attributes,
+ this.style);
+ },
+
+ /**
+ * Method: onScreen
+ * Determine whether the feature is within the map viewport. This method
+ * tests for an intersection between the geometry and the viewport
+ * bounds. If a more effecient but less precise geometry bounds
+ * intersection is desired, call the method with the boundsOnly
+ * parameter true.
+ *
+ * Parameters:
+ * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
+ * the viewport bounds. Default is false. If false, the feature's
+ * geometry must intersect the viewport for onScreen to return true.
+ *
+ * Returns:
+ * {Boolean} The feature is currently visible on screen (optionally
+ * based on its bounds if boundsOnly is true).
+ */
+ onScreen:function(boundsOnly) {
+ var onScreen = false;
+ if(this.layer && this.layer.map) {
+ var screenBounds = this.layer.map.getExtent();
+ if(boundsOnly) {
+ var featureBounds = this.geometry.getBounds();
+ onScreen = screenBounds.intersectsBounds(featureBounds);
+ } else {
+ var screenPoly = screenBounds.toGeometry();
+ onScreen = screenPoly.intersects(this.geometry);
+ }
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: getVisibility
+ * Determine whether the feature is displayed or not. It may not displayed
+ * because:
+ * - its style display property is set to 'none',
+ * - it doesn't belong to any layer,
+ * - the styleMap creates a symbolizer with display property set to 'none'
+ * for it,
+ * - the layer which it belongs to is not visible.
+ *
+ * Returns:
+ * {Boolean} The feature is currently displayed.
+ */
+ getVisibility: function() {
+ return !(this.style && this.style.display == 'none' ||
+ !this.layer ||
+ this.layer && this.layer.styleMap &&
+ this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
+ this.layer && !this.layer.getVisibility());
+ },
+
+ /**
+ * Method: createMarker
+ * HACK - we need to decide if all vector features should be able to
+ * create markers
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} For now just returns null
+ */
+ createMarker: function() {
+ return null;
+ },
+
+ /**
+ * Method: destroyMarker
+ * HACK - we need to decide if all vector features should be able to
+ * delete markers
+ *
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ // pass
+ },
+
+ /**
+ * Method: createPopup
+ * HACK - we need to decide if all vector features should be able to
+ * create popups
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} For now just returns null
+ */
+ createPopup: function() {
+ return null;
+ },
+
+ /**
+ * Method: atPoint
+ * Determins whether the feature intersects with the specified location.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ if(this.geometry) {
+ atPoint = this.geometry.atPoint(lonlat, toleranceLon,
+ toleranceLat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: destroyPopup
+ * HACK - we need to decide if all vector features should be able to
+ * delete popups
+ */
+ destroyPopup: function() {
+ // pass
+ },
+
+ /**
+ * Method: move
+ * Moves the feature and redraws it at its new location
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} the
+ * location to which to move the feature.
+ */
+ move: function(location) {
+
+ if(!this.layer || !this.geometry.move){
+ //do nothing if no layer or immoveable geometry
+ return undefined;
+ }
+
+ var pixel;
+ if (location.CLASS_NAME == "OpenLayers.LonLat") {
+ pixel = this.layer.getViewPortPxFromLonLat(location);
+ } else {
+ pixel = location;
+ }
+
+ var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
+ var res = this.layer.map.getResolution();
+ this.geometry.move(res * (pixel.x - lastPixel.x),
+ res * (lastPixel.y - pixel.y));
+ this.layer.drawFeature(this);
+ return lastPixel;
+ },
+
+ /**
+ * Method: toState
+ * Sets the new state
+ *
+ * Parameters:
+ * state - {String}
+ */
+ toState: function(state) {
+ if (state == OpenLayers.State.UPDATE) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.DELETE:
+ this.state = state;
+ break;
+ case OpenLayers.State.UPDATE:
+ case OpenLayers.State.INSERT:
+ break;
+ }
+ } else if (state == OpenLayers.State.INSERT) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ break;
+ default:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.DELETE) {
+ switch (this.state) {
+ case OpenLayers.State.INSERT:
+ // the feature should be destroyed
+ break;
+ case OpenLayers.State.DELETE:
+ break;
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.UPDATE:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.UNKNOWN) {
+ this.state = state;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature.Vector"
+});
+
+
+/**
+ * Constant: OpenLayers.Feature.Vector.style
+ * OpenLayers features can have a number of style attributes. The 'default'
+ * style will typically be used if no other style is specified. These
+ * styles correspond for the most part, to the styling properties defined
+ * by the SVG standard.
+ * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
+ * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
+ *
+ * Symbolizer properties:
+ * fill - {Boolean} Set to false if no fill is desired.
+ * fillColor - {String} Hex fill color. Default is "#ee9900".
+ * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4
+ * stroke - {Boolean} Set to false if no stroke is desired.
+ * strokeColor - {String} Hex stroke color. Default is "#ee9900".
+ * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1.
+ * strokeWidth - {Number} Pixel stroke width. Default is 1.
+ * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square]
+ * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
+ * graphic - {Boolean} Set to false if no graphic is desired.
+ * pointRadius - {Number} Pixel point radius. Default is 6.
+ * pointerEvents - {String} Default is "visiblePainted".
+ * cursor - {String} Default is "".
+ * externalGraphic - {String} Url to an external graphic that will be used for rendering points.
+ * graphicWidth - {Number} Pixel width for sizing an external graphic.
+ * graphicHeight - {Number} Pixel height for sizing an external graphic.
+ * graphicOpacity - {Number} Opacity (0-1) for an external graphic.
+ * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
+ * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
+ * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset).
+ * graphicZIndex - {Number} The integer z-index value to use in rendering.
+ * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default),
+ * "square", "star", "x", "cross", "triangle".
+ * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
+ * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
+ * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
+ * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
+ * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
+ * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
+ * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used.
+ * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used.
+ * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
+ * fillText or mozDrawText to be available.
+ * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
+ * composed of two characters. The first character is for the horizontal alignment, the second for the vertical
+ * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
+ * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
+ * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
+ * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
+ * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
+ * Default is false.
+ * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
+ * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers.
+ * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
+ * fontColor - {String} The font color for the label, to be provided like CSS.
+ * fontOpacity - {Number} Opacity (0-1) for the label
+ * fontFamily - {String} The font family for the label, to be provided like in CSS.
+ * fontSize - {String} The font size for the label, to be provided like in CSS.
+ * fontStyle - {String} The font style for the label, to be provided like in CSS.
+ * fontWeight - {String} The font weight for the label, to be provided like in CSS.
+ * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect.
+ */
+OpenLayers.Feature.Vector.style = {
+ 'default': {
+ fillColor: "#ee9900",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#ee9900",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+ },
+ 'select': {
+ fillColor: "blue",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "blue",
+ strokeOpacity: 1,
+ strokeWidth: 2,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "pointer",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'temporary': {
+ fillColor: "#66cccc",
+ fillOpacity: 0.2,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#66cccc",
+ strokeOpacity: 1,
+ strokeLinecap: "round",
+ strokeWidth: 2,
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'delete': {
+ display: "none"
+ }
+};
+/* ======================================================================
+ OpenLayers/Style.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Style
+ * This class represents a UserStyle obtained
+ * from a SLD, containing styling rules.
+ */
+OpenLayers.Style = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this style (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this style (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * APIProperty: layerName
+ * {<String>} name of the layer that this style belongs to, usually
+ * according to the NamedLayer attribute of an SLD document.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: isDefault
+ * {Boolean}
+ */
+ isDefault: false,
+
+ /**
+ * Property: rules
+ * {Array(<OpenLayers.Rule>)}
+ */
+ rules: null,
+
+ /**
+ * APIProperty: context
+ * {Object} An optional object with properties that symbolizers' property
+ * values should be evaluated against. If no context is specified,
+ * feature.attributes will be used
+ */
+ context: null,
+
+ /**
+ * Property: defaultStyle
+ * {Object} hash of style properties to use as default for merging
+ * rule-based style symbolizers onto. If no rules are defined,
+ * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
+ * true, the defaultStyle will only be taken into account if there are
+ * rules defined.
+ */
+ defaultStyle: null,
+
+ /**
+ * Property: defaultsPerSymbolizer
+ * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
+ * of every rule. Properties of the <defaultStyle> will also be used to set
+ * missing symbolizer properties if the symbolizer has stroke, fill or
+ * graphic set to true. Default is false.
+ */
+ defaultsPerSymbolizer: false,
+
+ /**
+ * Property: propertyStyles
+ * {Hash of Boolean} cache of style properties that need to be parsed for
+ * propertyNames. Property names are keys, values won't be used.
+ */
+ propertyStyles: null,
+
+
+ /**
+ * Constructor: OpenLayers.Style
+ * Creates a UserStyle.
+ *
+ * Parameters:
+ * style - {Object} Optional hash of style properties that will be
+ * used as default style for this style object. This style
+ * applies if no rules are specified. Symbolizers defined in
+ * rules will extend this default style.
+ * options - {Object} An optional object with properties to set on the
+ * style.
+ *
+ * Valid options:
+ * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
+ * style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>}
+ */
+ initialize: function(style, options) {
+
+ OpenLayers.Util.extend(this, options);
+ this.rules = [];
+ if(options && options.rules) {
+ this.addRules(options.rules);
+ }
+
+ // use the default style from OpenLayers.Feature.Vector if no style
+ // was given in the constructor
+ this.setDefaultStyle(style ||
+ OpenLayers.Feature.Vector.style["default"]);
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i=0, len=this.rules.length; i<len; i++) {
+ this.rules[i].destroy();
+ this.rules[i] = null;
+ }
+ this.rules = null;
+ this.defaultStyle = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * creates a style by applying all feature-dependent rules to the base
+ * style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to evaluate rules for
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature) {
+ var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
+ OpenLayers.Util.extend({}, this.defaultStyle), feature);
+
+ var rules = this.rules;
+
+ var rule, context;
+ var elseRules = [];
+ var appliedRules = false;
+ for(var i=0, len=rules.length; i<len; i++) {
+ rule = rules[i];
+ // does the rule apply?
+ var applies = rule.evaluate(feature);
+
+ if(applies) {
+ if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+ elseRules.push(rule);
+ } else {
+ appliedRules = true;
+ this.applySymbolizer(rule, style, feature);
+ }
+ }
+ }
+
+ // if no other rules apply, apply the rules with else filters
+ if(appliedRules == false && elseRules.length > 0) {
+ appliedRules = true;
+ for(var i=0, len=elseRules.length; i<len; i++) {
+ this.applySymbolizer(elseRules[i], style, feature);
+ }
+ }
+
+ // don't display if there were rules but none applied
+ if(rules.length > 0 && appliedRules == false) {
+ style.display = "none";
+ }
+
+ if (style.label != null && typeof style.label !== "string") {
+ style.label = String(style.label);
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: applySymbolizer
+ *
+ * Parameters:
+ * rule - {<OpenLayers.Rule>}
+ * style - {Object}
+ * feature - {<OpenLayer.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} A style with new symbolizer applied.
+ */
+ applySymbolizer: function(rule, style, feature) {
+ var symbolizerPrefix = feature.geometry ?
+ this.getSymbolizerPrefix(feature.geometry) :
+ OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
+
+ var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+
+ if(this.defaultsPerSymbolizer === true) {
+ var defaults = this.defaultStyle;
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: defaults.pointRadius
+ });
+ if(symbolizer.stroke === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ strokeWidth: defaults.strokeWidth,
+ strokeColor: defaults.strokeColor,
+ strokeOpacity: defaults.strokeOpacity,
+ strokeDashstyle: defaults.strokeDashstyle,
+ strokeLinecap: defaults.strokeLinecap
+ });
+ }
+ if(symbolizer.fill === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ fillColor: defaults.fillColor,
+ fillOpacity: defaults.fillOpacity
+ });
+ }
+ if(symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: this.defaultStyle.pointRadius,
+ externalGraphic: this.defaultStyle.externalGraphic,
+ graphicName: this.defaultStyle.graphicName,
+ graphicOpacity: this.defaultStyle.graphicOpacity,
+ graphicWidth: this.defaultStyle.graphicWidth,
+ graphicHeight: this.defaultStyle.graphicHeight,
+ graphicXOffset: this.defaultStyle.graphicXOffset,
+ graphicYOffset: this.defaultStyle.graphicYOffset
+ });
+ }
+ }
+
+ // merge the style with the current style
+ return this.createLiterals(
+ OpenLayers.Util.extend(style, symbolizer), feature);
+ },
+
+ /**
+ * Method: createLiterals
+ * creates literals for all style properties that have an entry in
+ * <this.propertyStyles>.
+ *
+ * Parameters:
+ * style - {Object} style to create literals for. Will be modified
+ * inline.
+ * feature - {Object}
+ *
+ * Returns:
+ * {Object} the modified style
+ */
+ createLiterals: function(style, feature) {
+ var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
+ OpenLayers.Util.extend(context, this.context);
+
+ for (var i in this.propertyStyles) {
+ style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
+ }
+ return style;
+ },
+
+ /**
+ * Method: findPropertyStyles
+ * Looks into all rules for this style and the defaultStyle to collect
+ * all the style hash property names containing ${...} strings that have
+ * to be replaced using the createLiteral method before returning them.
+ *
+ * Returns:
+ * {Object} hash of property names that need createLiteral parsing. The
+ * name of the property is the key, and the value is true;
+ */
+ findPropertyStyles: function() {
+ var propertyStyles = {};
+
+ // check the default style
+ var style = this.defaultStyle;
+ this.addPropertyStyles(propertyStyles, style);
+
+ // walk through all rules to check for properties in their symbolizer
+ var rules = this.rules;
+ var symbolizer, value;
+ for (var i=0, len=rules.length; i<len; i++) {
+ symbolizer = rules[i].symbolizer;
+ for (var key in symbolizer) {
+ value = symbolizer[key];
+ if (typeof value == "object") {
+ // symbolizer key is "Point", "Line" or "Polygon"
+ this.addPropertyStyles(propertyStyles, value);
+ } else {
+ // symbolizer is a hash of style properties
+ this.addPropertyStyles(propertyStyles, symbolizer);
+ break;
+ }
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * Method: addPropertyStyles
+ *
+ * Parameters:
+ * propertyStyles - {Object} hash to add new property styles to. Will be
+ * modified inline
+ * symbolizer - {Object} search this symbolizer for property styles
+ *
+ * Returns:
+ * {Object} propertyStyles hash
+ */
+ addPropertyStyles: function(propertyStyles, symbolizer) {
+ var property;
+ for (var key in symbolizer) {
+ property = symbolizer[key];
+ if (typeof property == "string" &&
+ property.match(/\$\{\w+\}/)) {
+ propertyStyles[key] = true;
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * APIMethod: addRules
+ * Adds rules to this style.
+ *
+ * Parameters:
+ * rules - {Array(<OpenLayers.Rule>)}
+ */
+ addRules: function(rules) {
+ Array.prototype.push.apply(this.rules, rules);
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * APIMethod: setDefaultStyle
+ * Sets the default style for this style object.
+ *
+ * Parameters:
+ * style - {Object} Hash of style properties
+ */
+ setDefaultStyle: function(style) {
+ this.defaultStyle = style;
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * Method: getSymbolizerPrefix
+ * Returns the correct symbolizer prefix according to the
+ * geometry type of the passed geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {String} key of the according symbolizer
+ */
+ getSymbolizerPrefix: function(geometry) {
+ var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+ for (var i=0, len=prefixes.length; i<len; i++) {
+ if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+ return prefixes[i];
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>} Clone of this style.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ // clone rules
+ if(this.rules) {
+ options.rules = [];
+ for(var i=0, len=this.rules.length; i<len; ++i) {
+ options.rules.push(this.rules[i].clone());
+ }
+ }
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ //clone default style
+ var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
+ return new OpenLayers.Style(defaultStyle, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ *
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ * will be replaced by the value of the "bar" attribute of the passed
+ * feature.
+ * context - {Object} context to take attribute values from
+ * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
+ * <OpenLayers.String.format> for evaluating functions in the
+ * context.
+ * property - {String} optional, name of the property for which the literal is
+ * being created for evaluating functions in the context.
+ *
+ * Returns:
+ * {String} the parsed value. In the example of the value parameter above, the
+ * result would be "foo valueOfBar", assuming that the passed feature has an
+ * attribute named "bar" with the value "valueOfBar".
+ */
+OpenLayers.Style.createLiteral = function(value, context, feature, property) {
+ if (typeof value == "string" && value.indexOf("${") != -1) {
+ value = OpenLayers.String.format(value, context, [feature, property]);
+ value = (isNaN(value) || !value) ? value : parseFloat(value);
+ }
+ return value;
+};
+
+/**
+ * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
+ * {Array} prefixes of the sld symbolizers. These are the
+ * same as the main geometry types
+ */
+OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text',
+ 'Raster'];
+/* ======================================================================
+ OpenLayers/Filter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Filter
+ * This class represents an OGC Filter.
+ */
+OpenLayers.Filter = OpenLayers.Class({
+
+ /**
+ * Constructor: OpenLayers.Filter
+ * This class represents a generic filter.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to anything added.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context. Instances or subclasses
+ * are supposed to override this method.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ return true;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter. Should be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} Clone of this filter.
+ */
+ clone: function() {
+ return null;
+ },
+
+ /**
+ * APIMethod: toString
+ *
+ * Returns:
+ * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
+ * representation of the filter returned. Otherwise "[Object object]"
+ * will be returned.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.CQL) {
+ string = OpenLayers.Format.CQL.prototype.write(this);
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter"
+});
+/* ======================================================================
+ OpenLayers/Filter/Spatial.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Spatial
+ * This class represents a spatial filter.
+ * Currently implemented: BBOX, DWithin and Intersects
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} Type of spatial filter.
+ *
+ * The type should be one of:
+ * - OpenLayers.Filter.Spatial.BBOX
+ * - OpenLayers.Filter.Spatial.INTERSECTS
+ * - OpenLayers.Filter.Spatial.DWITHIN
+ * - OpenLayers.Filter.Spatial.WITHIN
+ * - OpenLayers.Filter.Spatial.CONTAINS
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String} Name of the context property to compare.
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
+ * to be used by the filter. Use bounds for BBOX filters and geometry
+ * for INTERSECTS or DWITHIN filters.
+ */
+ value: null,
+
+ /**
+ * APIProperty: distance
+ * {Number} The distance to use in a DWithin spatial filter.
+ */
+ distance: null,
+
+ /**
+ * APIProperty: distanceUnits
+ * {String} The units to use for the distance, e.g. 'm'.
+ */
+ distanceUnits: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Spatial
+ * Creates a spatial filter.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>}
+ */
+
+ /**
+ * Method: evaluate
+ * Evaluates this filter for a specific feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
+ *
+ * Returns:
+ * {Boolean} The feature meets filter criteria.
+ */
+ evaluate: function(feature) {
+ var intersect = false;
+ switch(this.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ case OpenLayers.Filter.Spatial.INTERSECTS:
+ if(feature.geometry) {
+ var geom = this.value;
+ if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
+ geom = this.value.toGeometry();
+ }
+ if(feature.geometry.intersects(geom)) {
+ intersect = true;
+ }
+ }
+ break;
+ default:
+ throw new Error('evaluate is not implemented for this filter type.');
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} Clone of this filter.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.applyDefaults({
+ value: this.value && this.value.clone && this.value.clone()
+ }, this);
+ return new OpenLayers.Filter.Spatial(options);
+ },
+ CLASS_NAME: "OpenLayers.Filter.Spatial"
+});
+
+OpenLayers.Filter.Spatial.BBOX = "BBOX";
+OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
+OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
+OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
+OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
+/* ======================================================================
+ OpenLayers/Filter/FeatureId.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.FeatureId
+ * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
+ * styling
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: fids
+ * {Array(String)} Feature Ids to evaluate this rule against.
+ * To be passed inside the params object.
+ */
+ fids: null,
+
+ /**
+ * Property: type
+ * {String} Type to identify this filter.
+ */
+ type: "FID",
+
+ /**
+ * Constructor: OpenLayers.Filter.FeatureId
+ * Creates an ogc:FeatureId rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>}
+ */
+ initialize: function(options) {
+ this.fids = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+ * For vector features, the check is run against the fid,
+ * for plain features against the id.
+ *
+ * Returns:
+ * {Boolean} true if the rule applies, false if it does not
+ */
+ evaluate: function(feature) {
+ for (var i=0, len=this.fids.length; i<len; i++) {
+ var fid = feature.fid || feature.id;
+ if (fid == this.fids[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>} Clone of this filter.
+ */
+ clone: function() {
+ var filter = new OpenLayers.Filter.FeatureId();
+ OpenLayers.Util.extend(filter, this);
+ filter.fids = this.fids.slice();
+ return filter;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.FeatureId"
+});
+/* ======================================================================
+ OpenLayers/Format/WFST/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/WFST.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1
+ * Superclass for WFST parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ wfs: "http://www.opengis.net/wfs",
+ gml: "http://www.opengis.net/gml",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wfs",
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocations: null,
+
+ /**
+ * APIProperty: srsName
+ * {String} URI for spatial reference system.
+ */
+ srsName: null,
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: stateName
+ * {Object} Maps feature states to node names.
+ */
+ stateName: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
+ * constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // set state name mapping
+ this.stateName = {};
+ this.stateName[OpenLayers.State.INSERT] = "wfs:Insert";
+ this.stateName[OpenLayers.State.UPDATE] = "wfs:Update";
+ this.stateName[OpenLayers.State.DELETE] = "wfs:Delete";
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: getSrsName
+ */
+ getSrsName: function(feature, options) {
+ var srsName = options && options.srsName;
+ if(!srsName) {
+ if(feature && feature.layer) {
+ srsName = feature.layer.projection.getCode();
+ } else {
+ srsName = this.srsName;
+ }
+ }
+ return srsName;
+ },
+
+ /**
+ * APIMethod: read
+ * Parse the response from a transaction. Because WFS is split into
+ * Transaction requests (create, update, and delete) and GetFeature
+ * requests (read), this method handles parsing of both types of
+ * responses.
+ *
+ * Parameters:
+ * data - {String | Document} The WFST document to read
+ * options - {Object} Options for the reader
+ *
+ * Valid options properties:
+ * output - {String} either "features" or "object". The default is
+ * "features", which means that the method will return an array of
+ * features. If set to "object", an object with a "features" property
+ * and other properties read by the parser will be returned.
+ *
+ * Returns:
+ * {Array | Object} Output depending on the output option.
+ */
+ read: function(data, options) {
+ options = options || {};
+ OpenLayers.Util.applyDefaults(options, {
+ output: "features"
+ });
+
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ if(data) {
+ this.readNode(data, obj, true);
+ }
+ if(obj.features && options.output === "features") {
+ obj = obj.features;
+ }
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ obj.features = [];
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ * Given an array of features, write a WFS transaction. This assumes
+ * the features have a state property that determines the operation
+ * type - insert, update, or delete.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} A list of features. See
+ * below for a more detailed description of the influence of the
+ * feature's *modified* property.
+ * options - {Object}
+ *
+ * feature.modified rules:
+ * If a feature has a modified property set, the following checks will be
+ * made before a feature's geometry or attribute is included in an Update
+ * transaction:
+ * - *modified* is not set at all: The geometry and all attributes will be
+ * included.
+ * - *modified.geometry* is set (null or a geometry): The geometry will be
+ * included. If *modified.attributes* is not set, all attributes will
+ * be included.
+ * - *modified.attributes* is set: Only the attributes set (i.e. to null or
+ * a value) in *modified.attributes* will be included.
+ * If *modified.geometry* is not set, the geometry will not be included.
+ *
+ * Valid options include:
+ * - *multi* {Boolean} If set to true, geometries will be casted to
+ * Multi geometries before writing.
+ *
+ * Returns:
+ * {String} A serialized WFS transaction.
+ */
+ write: function(features, options) {
+ var node = this.writeNode("wfs:Transaction", {
+ features:features,
+ options: options
+ });
+ var value = this.schemaLocationAttr();
+ if(value) {
+ this.setAttributeNS(
+ node, this.namespaces["xsi"], "xsi:schemaLocation", value
+ );
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": {
+ "GetFeature": function(options) {
+ var node = this.createElementNSPlus("wfs:GetFeature", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options && options.handle,
+ outputFormat: options && options.outputFormat,
+ maxFeatures: options && options.maxFeatures,
+ "xsi:schemaLocation": this.schemaLocationAttr(options)
+ }
+ });
+ if (typeof this.featureType == "string") {
+ this.writeNode("Query", options, node);
+ } else {
+ for (var i=0,len = this.featureType.length; i<len; i++) {
+ options.featureType = this.featureType[i];
+ this.writeNode("Query", options, node);
+ }
+ }
+ return node;
+ },
+ "Transaction": function(obj) {
+ obj = obj || {};
+ var options = obj.options || {};
+ var node = this.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options.handle
+ }
+ });
+ var i, len;
+ var features = obj.features;
+ if(features) {
+ // temporarily re-assigning geometry types
+ if (options.multi === true) {
+ OpenLayers.Util.extend(this.geometryTypes, {
+ "OpenLayers.Geometry.Point": "MultiPoint",
+ "OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
+ });
+ }
+ var name, feature;
+ for(i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ name = this.stateName[feature.state];
+ if(name) {
+ this.writeNode(name, {
+ feature: feature,
+ options: options
+ }, node);
+ }
+ }
+ // switch back to original geometry types assignment
+ if (options.multi === true) {
+ this.setGeometryTypes();
+ }
+ }
+ if (options.nativeElements) {
+ for (i=0, len=options.nativeElements.length; i<len; ++i) {
+ this.writeNode("wfs:Native",
+ options.nativeElements[i], node);
+ }
+ }
+ return node;
+ },
+ "Native": function(nativeElement) {
+ var node = this.createElementNSPlus("wfs:Native", {
+ attributes: {
+ vendorId: nativeElement.vendorId,
+ safeToIgnore: nativeElement.safeToIgnore
+ },
+ value: nativeElement.value
+ });
+ return node;
+ },
+ "Insert": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Insert", {
+ attributes: {
+ handle: options && options.handle
+ }
+ });
+ this.srsName = this.getSrsName(feature);
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "Update": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Update", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+
+ // add in geometry
+ var modified = feature.modified;
+ if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
+ this.srsName = this.getSrsName(feature);
+ this.writeNode(
+ "Property", {name: this.geometryName, value: feature.geometry}, node
+ );
+ }
+
+ // add in attributes
+ for(var key in feature.attributes) {
+ if(feature.attributes[key] !== undefined &&
+ (!modified || !modified.attributes ||
+ (modified.attributes && modified.attributes[key] !== undefined))) {
+ this.writeNode(
+ "Property", {name: key, value: feature.attributes[key]}, node
+ );
+ }
+ }
+
+ // add feature id filter
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+
+ return node;
+ },
+ "Property": function(obj) {
+ var node = this.createElementNSPlus("wfs:Property");
+ this.writeNode("Name", obj.name, node);
+ if(obj.value !== null) {
+ this.writeNode("Value", obj.value, node);
+ }
+ return node;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("wfs:Name", {value: name});
+ },
+ "Value": function(obj) {
+ var node;
+ if(obj instanceof OpenLayers.Geometry) {
+ node = this.createElementNSPlus("wfs:Value");
+ var geom = this.writeNode("feature:_geometry", obj).firstChild;
+ node.appendChild(geom);
+ } else {
+ node = this.createElementNSPlus("wfs:Value", {value: obj});
+ }
+ return node;
+ },
+ "Delete": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: schemaLocationAttr
+ * Generate the xsi:schemaLocation attribute value.
+ *
+ * Returns:
+ * {String} The xsi:schemaLocation attribute or undefined if none.
+ */
+ schemaLocationAttr: function(options) {
+ options = OpenLayers.Util.extend({
+ featurePrefix: this.featurePrefix,
+ schema: this.schema
+ }, options);
+ var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
+ if(options.schema) {
+ schemaLocations[options.featurePrefix] = options.schema;
+ }
+ var parts = [];
+ var uri;
+ for(var key in schemaLocations) {
+ uri = this.namespaces[key];
+ if(uri) {
+ parts.push(uri + " " + schemaLocations[key]);
+ }
+ }
+ var value = parts.join(" ") || undefined;
+ return value;
+ },
+
+ /**
+ * Method: setFilterProperty
+ * Set the property of each spatial filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ setFilterProperty: function(filter) {
+ if(filter.filters) {
+ for(var i=0, len=filter.filters.length; i<len; ++i) {
+ OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
+ }
+ } else {
+ if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
+ // got a spatial filter without property, so set it
+ filter.property = this.geometryName;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/OGCExceptionReport.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OGCExceptionReport
+ * Class to read exception reports for various OGC services and versions.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ogc: "http://www.opengis.net/ogc"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "ogc",
+
+ /**
+ * Constructor: OpenLayers.Format.OGCExceptionReport
+ * Create a new parser for OGC exception reports.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read OGC exception report data from a string, and return an object with
+ * information about the exceptions.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the exceptions that occurred.
+ */
+ read: function(data) {
+ var result;
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var exceptionInfo = {exceptionReport: null};
+ if (root) {
+ this.readChildNodes(data, exceptionInfo);
+ if (exceptionInfo.exceptionReport === null) {
+ // fall-back to OWSCommon since this is a common output format for exceptions
+ // we cannot easily use the ows readers directly since they differ for 1.0 and 1.1
+ exceptionInfo = new OpenLayers.Format.OWSCommon().read(data);
+ }
+ }
+ return exceptionInfo;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": {
+ "ServiceExceptionReport": function(node, obj) {
+ obj.exceptionReport = {exceptions: []};
+ this.readChildNodes(node, obj.exceptionReport);
+ },
+ "ServiceException": function(node, exceptionReport) {
+ var exception = {
+ code: node.getAttribute("code"),
+ locator: node.getAttribute("locator"),
+ text: this.getChildValue(node)
+ };
+ exceptionReport.exceptions.push(exception);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OGCExceptionReport"
+
+});
+/* ======================================================================
+ OpenLayers/Format/XML/VersionedOGC.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML.VersionedOGC
+ * Base class for versioned formats, i.e. a format which supports multiple
+ * versions.
+ *
+ * To enable checking if parsing succeeded, you will need to define a property
+ * called errorProperty on the parser you want to check. The parser will then
+ * check the returned object to see if that property is present. If it is, it
+ * assumes the parsing was successful. If it is not present (or is null), it will
+ * pass the document through an OGCExceptionReport parser.
+ *
+ * If errorProperty is undefined for the parser, this error checking mechanism
+ * will be disabled.
+ *
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found.
+ */
+ defaultVersion: null,
+
+ /**
+ * APIProperty: version
+ * {String} Specify a version string if one is known.
+ */
+ version: null,
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ */
+ profile: null,
+
+ /**
+ * APIProperty: allowFallback
+ * {Boolean} If a profiled parser cannot be found for the returned version,
+ * use a non-profiled parser as the fallback. Application code using this
+ * should take into account that the return object structure might be
+ * missing the specifics of the profile. Defaults to false.
+ */
+ allowFallback: false,
+
+ /**
+ * Property: name
+ * {String} The name of this parser, this is the part of the CLASS_NAME
+ * except for "OpenLayers.Format."
+ */
+ name: null,
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is false.
+ */
+ stringifyOutput: false,
+
+ /**
+ * Property: parser
+ * {Object} Instance of the versioned parser. Cached for multiple read and
+ * write calls of the same version.
+ */
+ parser: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XML.VersionedOGC.
+ * Constructor.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on
+ * the object.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ var className = this.CLASS_NAME;
+ this.name = className.substring(className.lastIndexOf(".")+1);
+ },
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version;
+ // read
+ if (root) {
+ version = this.version;
+ if(!version) {
+ version = root.getAttribute("version");
+ if(!version) {
+ version = this.defaultVersion;
+ }
+ }
+ } else { // write
+ version = (options && options.version) ||
+ this.version || this.defaultVersion;
+ }
+ return version;
+ },
+
+ /**
+ * Method: getParser
+ * Get an instance of the cached parser if available, otherwise create one.
+ *
+ * Parameters:
+ * version - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Format>}
+ */
+ getParser: function(version) {
+ version = version || this.defaultVersion;
+ var profile = this.profile ? "_" + this.profile : "";
+ if(!this.parser || this.parser.VERSION != version) {
+ var format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_") + profile
+ ];
+ if(!format) {
+ if (profile !== "" && this.allowFallback) {
+ // fallback to the non-profiled version of the parser
+ profile = "";
+ format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_")
+ ];
+ }
+ if (!format) {
+ throw "Can't find a " + this.name + " parser for version " +
+ version + profile;
+ }
+ }
+ this.parser = new format(this.options);
+ }
+ return this.parser;
+ },
+
+ /**
+ * APIMethod: write
+ * Write a document.
+ *
+ * Parameters:
+ * obj - {Object} An object representing the document.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The document as a string
+ */
+ write: function(obj, options) {
+ var version = this.getVersion(null, options);
+ this.parser = this.getParser(version);
+ var root = this.parser.write(obj, options);
+ if (this.stringifyOutput === false) {
+ return root;
+ } else {
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ }
+ },
+
+ /**
+ * APIMethod: read
+ * Read a doc and return an object representing the document.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the document.
+ */
+ read: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var version = this.getVersion(root);
+ this.parser = this.getParser(version); // Select the parser
+ var obj = this.parser.read(data, options); // Parse the data
+
+ var errorProperty = this.parser.errorProperty || null;
+ if (errorProperty !== null && obj[errorProperty] === undefined) {
+ // an error must have happened, so parse it and report back
+ var format = new OpenLayers.Format.OGCExceptionReport();
+ obj.error = format.read(data);
+ }
+ obj.version = version;
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
+});
+/* ======================================================================
+ OpenLayers/Filter/Logical.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: filters
+ * {Array(<OpenLayers.Filter>)} Child filters for this filter.
+ */
+ filters: null,
+
+ /**
+ * APIProperty: type
+ * {String} type of logical operator. Available types are:
+ * - OpenLayers.Filter.Logical.AND = "&&";
+ * - OpenLayers.Filter.Logical.OR = "||";
+ * - OpenLayers.Filter.Logical.NOT = "!";
+ */
+ type: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Logical
+ * Creates a logical filter (And, Or, Not).
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>}
+ */
+ initialize: function(options) {
+ this.filters = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to child filters.
+ */
+ destroy: function() {
+ this.filters = null;
+ OpenLayers.Filter.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. A vector
+ * feature may also be provided to evaluate feature attributes in
+ * comparison filters or geometries in spatial filters.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ var i, len;
+ switch(this.type) {
+ case OpenLayers.Filter.Logical.AND:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == false) {
+ return false;
+ }
+ }
+ return true;
+
+ case OpenLayers.Filter.Logical.OR:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == true) {
+ return true;
+ }
+ }
+ return false;
+
+ case OpenLayers.Filter.Logical.NOT:
+ return (!this.filters[0].evaluate(context));
+ }
+ return undefined;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>} Clone of this filter.
+ */
+ clone: function() {
+ var filters = [];
+ for(var i=0, len=this.filters.length; i<len; ++i) {
+ filters.push(this.filters[i].clone());
+ }
+ return new OpenLayers.Filter.Logical({
+ type: this.type,
+ filters: filters
+ });
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR = "||";
+OpenLayers.Filter.Logical.NOT = "!";
+/* ======================================================================
+ OpenLayers/Filter/Comparison.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Comparison
+ * This class represents a comparison filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} type: type of the comparison. This is one of
+ * - OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+ * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+ * - OpenLayers.Filter.Comparison.LESS_THAN = "<";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+ * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+ * - OpenLayers.Filter.Comparison.BETWEEN = "..";
+ * - OpenLayers.Filter.Comparison.LIKE = "~";
+ * - OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String}
+ * name of the context property to compare
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {Number} or {String}
+ * comparison value for binary comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ value: null,
+
+ /**
+ * Property: matchCase
+ * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
+ * comparisons. The Filter Encoding 1.1 specification added a matchCase
+ * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
+ * elements. This property will be serialized with those elements only
+ * if using the v1.1.0 filter format. However, when evaluating filters
+ * here, the matchCase property will always be respected (for EQUAL_TO
+ * and NOT_EQUAL_TO). Default is true.
+ */
+ matchCase: true,
+
+ /**
+ * APIProperty: lowerBoundary
+ * {Number} or {String}
+ * lower boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ lowerBoundary: null,
+
+ /**
+ * APIProperty: upperBoundary
+ * {Number} or {String}
+ * upper boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ upperBoundary: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Comparison
+ * Creates a comparison rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>}
+ */
+ initialize: function(options) {
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ // since matchCase on PropertyIsLike is not schema compliant, we only
+ // want to use this if explicitly asked for
+ if (this.type === OpenLayers.Filter.Comparison.LIKE
+ && options.matchCase === undefined) {
+ this.matchCase = null;
+ }
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ if (context instanceof OpenLayers.Feature.Vector) {
+ context = context.attributes;
+ }
+ var result = false;
+ var got = context[this.property];
+ var exp;
+ switch(this.type) {
+ case OpenLayers.Filter.Comparison.EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() == exp.toUpperCase());
+ } else {
+ result = (got == exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() != exp.toUpperCase());
+ } else {
+ result = (got != exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN:
+ result = got < this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN:
+ result = got > this.value;
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+ result = got <= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+ result = got >= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.BETWEEN:
+ result = (got >= this.lowerBoundary) &&
+ (got <= this.upperBoundary);
+ break;
+ case OpenLayers.Filter.Comparison.LIKE:
+ var regexp = new RegExp(this.value, "gi");
+ result = regexp.test(got);
+ break;
+ case OpenLayers.Filter.Comparison.IS_NULL:
+ result = (got === null);
+ break;
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: value2regex
+ * Converts the value of this rule into a regular expression string,
+ * according to the wildcard characters specified. This method has to
+ * be called after instantiation of this class, if the value is not a
+ * regular expression already.
+ *
+ * Parameters:
+ * wildCard - {Char} wildcard character in the above value, default
+ * is "*"
+ * singleChar - {Char} single-character wildcard in the above value
+ * default is "."
+ * escapeChar - {Char} escape character in the above value, default is
+ * "!"
+ *
+ * Returns:
+ * {String} regular expression string
+ */
+ value2regex: function(wildCard, singleChar, escapeChar) {
+ if (wildCard == ".") {
+ throw new Error("'.' is an unsupported wildCard character for " +
+ "OpenLayers.Filter.Comparison");
+ }
+
+
+ // set UMN MapServer defaults for unspecified parameters
+ wildCard = wildCard ? wildCard : "*";
+ singleChar = singleChar ? singleChar : ".";
+ escapeChar = escapeChar ? escapeChar : "!";
+
+ this.value = this.value.replace(
+ new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
+ this.value = this.value.replace(
+ new RegExp("\\"+singleChar, "g"), ".");
+ this.value = this.value.replace(
+ new RegExp("\\"+wildCard, "g"), ".*");
+ this.value = this.value.replace(
+ new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
+ this.value = this.value.replace(
+ new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+
+ return this.value;
+ },
+
+ /**
+ * Method: regex2value
+ * Convert the value of this rule from a regular expression string into an
+ * ogc literal string using a wildCard of *, a singleChar of ., and an
+ * escape of !. Leaves the <value> property unmodified.
+ *
+ * Returns:
+ * {String} A string value.
+ */
+ regex2value: function() {
+
+ var value = this.value;
+
+ // replace ! with !!
+ value = value.replace(/!/g, "!!");
+
+ // replace \. with !. (watching out for \\.)
+ value = value.replace(/(\\)?\\\./g, function($0, $1) {
+ return $1 ? $0 : "!.";
+ });
+
+ // replace \* with #* (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "!*";
+ });
+
+ // replace \\ with \
+ value = value.replace(/\\\\/g, "\\");
+
+ // convert .* to * (the sequence #.* is not allowed)
+ value = value.replace(/\.\*/g, "*");
+
+ return value;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>} Clone of this filter.
+ */
+ clone: function() {
+ return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Comparison"
+});
+
+
+OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+OpenLayers.Filter.Comparison.LESS_THAN = "<";
+OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+OpenLayers.Filter.Comparison.BETWEEN = "..";
+OpenLayers.Filter.Comparison.LIKE = "~";
+OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+/* ======================================================================
+ OpenLayers/Format/Filter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter
+ * Read/Write ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIMethod: write
+ * Write an ogc:Filter given a filter object.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} An filter.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {Elment} An ogc:Filter element node.
+ */
+
+ /**
+ * APIMethod: read
+ * Read and Filter doc and return an object representing the Filter.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.Filter"
+});
+/* ======================================================================
+ OpenLayers/Filter/Function.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Function
+ * This class represents a filter function.
+ * We are using this class for creation of complex
+ * filters that can contain filter functions as values.
+ * Nesting function as other functions parameter is supported.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: name
+ * {String} Name of the function.
+ */
+ name: null,
+
+ /**
+ * APIProperty: params
+ * {Array(<OpenLayers.Filter.Function> || String || Number)} Function parameters
+ * For now support only other Functions, String or Number
+ */
+ params: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Function
+ * Creates a filter function.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * function.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Function>}
+ */
+
+ CLASS_NAME: "OpenLayers.Filter.Function"
+});
+
+/* ======================================================================
+ OpenLayers/BaseTypes/Date.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Namespace: OpenLayers.Date
+ * Contains implementations of Date.parse and date.toISOString that match the
+ * ECMAScript 5 specification for parsing RFC 3339 dates.
+ * http://tools.ietf.org/html/rfc3339
+ */
+OpenLayers.Date = {
+
+ /**
+ * APIProperty: dateRegEx
+ * The regex to be used for validating dates. You can provide your own
+ * regex for instance for adding support for years before BC. Default
+ * value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/
+ */
+ dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,
+
+ /**
+ * APIMethod: toISOString
+ * Generates a string representing a date. The format of the string follows
+ * the profile of ISO 8601 for date and time on the Internet (see
+ * http://tools.ietf.org/html/rfc3339). If the toISOString method is
+ * available on the Date prototype, that is used. The toISOString
+ * method for Date instances is defined in ECMA-262.
+ *
+ * Parameters:
+ * date - {Date} A date object.
+ *
+ * Returns:
+ * {String} A string representing the date (e.g.
+ * "2010-08-07T16:58:23.123Z"). If the date does not have a valid time
+ * (i.e. isNaN(date.getTime())) this method returns the string "Invalid
+ * Date". The ECMA standard says the toISOString method should throw
+ * RangeError in this case, but Firefox returns a string instead. For
+ * best results, use isNaN(date.getTime()) to determine date validity
+ * before generating date strings.
+ */
+ toISOString: (function() {
+ if ("toISOString" in Date.prototype) {
+ return function(date) {
+ return date.toISOString();
+ };
+ } else {
+ return function(date) {
+ var str;
+ if (isNaN(date.getTime())) {
+ // ECMA-262 says throw RangeError, Firefox returns
+ // "Invalid Date"
+ str = "Invalid Date";
+ } else {
+ str =
+ date.getUTCFullYear() + "-" +
+ OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" +
+ OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" +
+ OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" +
+ OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" +
+ OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." +
+ OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z";
+ }
+ return str;
+ };
+ }
+
+ })(),
+
+ /**
+ * APIMethod: parse
+ * Generate a date object from a string. The format for the string follows
+ * the profile of ISO 8601 for date and time on the Internet (see
+ * http://tools.ietf.org/html/rfc3339). We don't call the native
+ * Date.parse because of inconsistency between implmentations. In
+ * Chrome, calling Date.parse with a string that doesn't contain any
+ * indication of the timezone (e.g. "2011"), the date is interpreted
+ * in local time. On Firefox, the assumption is UTC.
+ *
+ * Parameters:
+ * str - {String} A string representing the date (e.g.
+ * "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z",
+ * "2010-08-07T11:58:23.123-06").
+ *
+ * Returns:
+ * {Date} A date object. If the string could not be parsed, an invalid
+ * date is returned (i.e. isNaN(date.getTime())).
+ */
+ parse: function(str) {
+ var date;
+ var match = str.match(this.dateRegEx);
+ if (match && (match[1] || match[7])) { // must have at least year or time
+ var year = parseInt(match[1], 10) || 0;
+ var month = (parseInt(match[2], 10) - 1) || 0;
+ var day = parseInt(match[3], 10) || 1;
+ date = new Date(Date.UTC(year, month, day));
+ // optional time
+ var type = match[7];
+ if (type) {
+ var hours = parseInt(match[4], 10);
+ var minutes = parseInt(match[5], 10);
+ var secFrac = parseFloat(match[6]);
+ var seconds = secFrac | 0;
+ var milliseconds = Math.round(1000 * (secFrac - seconds));
+ date.setUTCHours(hours, minutes, seconds, milliseconds);
+ // check offset
+ if (type !== "Z") {
+ var hoursOffset = parseInt(type, 10);
+ var minutesOffset = parseInt(match[8], 10) || 0;
+ var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60);
+ date = new Date(date.getTime() + offset);
+ }
+ }
+ } else {
+ date = new Date("invalid");
+ }
+ return date;
+ }
+};
+/* ======================================================================
+ OpenLayers/Format/Filter/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+/**
+ * @requires OpenLayers/Format/Filter.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Filter/Function.js
+ * @requires OpenLayers/BaseTypes/Date.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1
+ * Superclass for Filter version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ogc: "http://www.opengis.net/ogc",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "ogc",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} A Filter document element.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+ read: function(data) {
+ var obj = {};
+ this.readers.ogc["Filter"].apply(this, [data, obj]);
+ return obj.filter;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": {
+ "_expression": function(node) {
+ // only the simplest of ogc:expression handled
+ // "some text and an <PropertyName>attribute</PropertyName>"}
+ var obj, value = "";
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1:
+ obj = this.readNode(child);
+ if (obj.property) {
+ value += "${" + obj.property + "}";
+ } else if (obj.value !== undefined) {
+ value += obj.value;
+ }
+ break;
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ return value;
+ },
+ "Filter": function(node, parent) {
+ // Filters correspond to subclasses of OpenLayers.Filter.
+ // Since they contain information we don't persist, we
+ // create a temporary object and then pass on the filter
+ // (ogc:Filter) to the parent obj.
+ var obj = {
+ fids: [],
+ filters: []
+ };
+ this.readChildNodes(node, obj);
+ if(obj.fids.length > 0) {
+ parent.filter = new OpenLayers.Filter.FeatureId({
+ fids: obj.fids
+ });
+ } else if(obj.filters.length > 0) {
+ parent.filter = obj.filters[0];
+ }
+ },
+ "FeatureId": function(node, obj) {
+ var fid = node.getAttribute("fid");
+ if(fid) {
+ obj.fids.push(fid);
+ }
+ },
+ "And": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Or": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Not": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLessThan": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsGreaterThan": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLessThanOrEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsBetween": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Literal": function(node, obj) {
+ obj.value = OpenLayers.String.numericIf(
+ this.getChildValue(node), true);
+ },
+ "PropertyName": function(node, filter) {
+ filter.property = this.getChildValue(node);
+ },
+ "LowerBoundary": function(node, filter) {
+ filter.lowerBoundary = OpenLayers.String.numericIf(
+ this.readers.ogc._expression.call(this, node), true);
+ },
+ "UpperBoundary": function(node, filter) {
+ filter.upperBoundary = OpenLayers.String.numericIf(
+ this.readers.ogc._expression.call(this, node), true);
+ },
+ "Intersects": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
+ },
+ "Within": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN);
+ },
+ "Contains": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
+ },
+ "DWithin": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
+ },
+ "Distance": function(node, obj) {
+ obj.distance = parseInt(this.getChildValue(node));
+ obj.distanceUnits = node.getAttribute("units");
+ },
+ "Function": function(node, obj) {
+ //TODO write decoder for it
+ return;
+ },
+ "PropertyIsNull": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ }
+ }
+ },
+
+ /**
+ * Method: readSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM element that contains an ogc:expression.
+ * obj - {Object} The target object.
+ * type - {String} One of the OpenLayers.Filter.Spatial.* constants.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} The created filter.
+ */
+ readSpatial: function(node, obj, type) {
+ var filter = new OpenLayers.Filter.Spatial({
+ type: type
+ });
+ this.readChildNodes(node, filter);
+ filter.value = filter.components[0];
+ delete filter.components;
+ obj.filters.push(filter);
+ },
+
+ /**
+ * APIMethod: encodeLiteral
+ * Generates the string representation of a value for use in <Literal>
+ * elements. The default encoder writes Date values as ISO 8601
+ * strings.
+ *
+ * Parameters:
+ * value - {Object} Literal value to encode
+ *
+ * Returns:
+ * {String} String representation of the provided value.
+ */
+ encodeLiteral: function(value) {
+ if (value instanceof Date) {
+ value = OpenLayers.Date.toISOString(value);
+ }
+ return value;
+ },
+
+ /**
+ * Method: writeOgcExpression
+ * Limited support for writing OGC expressions. Currently it supports
+ * (<OpenLayers.Filter.Function> || String || Number)
+ *
+ * Parameters:
+ * value - (<OpenLayers.Filter.Function> || String || Number)
+ * node - {DOMElement} A parent DOM element
+ *
+ * Returns:
+ * {DOMElement} Updated node element.
+ */
+ writeOgcExpression: function(value, node) {
+ if (value instanceof OpenLayers.Filter.Function){
+ this.writeNode("Function", value, node);
+ } else {
+ this.writeNode("Literal", value, node);
+ }
+ return node;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} A filter object.
+ *
+ * Returns:
+ * {DOMElement} An ogc:Filter element.
+ */
+ write: function(filter) {
+ return this.writers.ogc["Filter"].apply(this, [filter]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": {
+ "Filter": function(filter) {
+ var node = this.createElementNSPlus("ogc:Filter");
+ this.writeNode(this.getFilterType(filter), filter, node);
+ return node;
+ },
+ "_featureIds": function(filter) {
+ var node = this.createDocumentFragment();
+ for (var i=0, ii=filter.fids.length; i<ii; ++i) {
+ this.writeNode("ogc:FeatureId", filter.fids[i], node);
+ }
+ return node;
+ },
+ "FeatureId": function(fid) {
+ return this.createElementNSPlus("ogc:FeatureId", {
+ attributes: {fid: fid}
+ });
+ },
+ "And": function(filter) {
+ var node = this.createElementNSPlus("ogc:And");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Or": function(filter) {
+ var node = this.createElementNSPlus("ogc:Or");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Not": function(filter) {
+ var node = this.createElementNSPlus("ogc:Not");
+ var childFilter = filter.filters[0];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ return node;
+ },
+ "PropertyIsLessThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsGreaterThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLessThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsBetween": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsBetween");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ this.writeNode("LowerBoundary", filter, node);
+ this.writeNode("UpperBoundary", filter, node);
+ return node;
+ },
+ "PropertyName": function(filter) {
+ // no ogc:expression handling for now
+ return this.createElementNSPlus("ogc:PropertyName", {
+ value: filter.property
+ });
+ },
+ "Literal": function(value) {
+ var encode = this.encodeLiteral ||
+ OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
+ return this.createElementNSPlus("ogc:Literal", {
+ value: encode(value)
+ });
+ },
+ "LowerBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:LowerBoundary");
+ this.writeOgcExpression(filter.lowerBoundary, node);
+ return node;
+ },
+ "UpperBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:UpperBoundary");
+ this.writeNode("Literal", filter.upperBoundary, node);
+ return node;
+ },
+ "INTERSECTS": function(filter) {
+ return this.writeSpatial(filter, "Intersects");
+ },
+ "WITHIN": function(filter) {
+ return this.writeSpatial(filter, "Within");
+ },
+ "CONTAINS": function(filter) {
+ return this.writeSpatial(filter, "Contains");
+ },
+ "DWITHIN": function(filter) {
+ var node = this.writeSpatial(filter, "DWithin");
+ this.writeNode("Distance", filter, node);
+ return node;
+ },
+ "Distance": function(filter) {
+ return this.createElementNSPlus("ogc:Distance", {
+ attributes: {
+ units: filter.distanceUnits
+ },
+ value: filter.distance
+ });
+ },
+ "Function": function(filter) {
+ var node = this.createElementNSPlus("ogc:Function", {
+ attributes: {
+ name: filter.name
+ }
+ });
+ var params = filter.params;
+ for(var i=0, len=params.length; i<len; i++){
+ this.writeOgcExpression(params[i], node);
+ }
+ return node;
+ },
+ "PropertyIsNull": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNull");
+ this.writeNode("PropertyName", filter, node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: getFilterType
+ */
+ getFilterType: function(filter) {
+ var filterType = this.filterMap[filter.type];
+ if(!filterType) {
+ throw "Filter writing not supported for rule type: " + filter.type;
+ }
+ return filterType;
+ },
+
+ /**
+ * Property: filterMap
+ * {Object} Contains a member for each filter type. Values are node names
+ * for corresponding OGC Filter child elements.
+ */
+ filterMap: {
+ "&&": "And",
+ "||": "Or",
+ "!": "Not",
+ "==": "PropertyIsEqualTo",
+ "!=": "PropertyIsNotEqualTo",
+ "<": "PropertyIsLessThan",
+ ">": "PropertyIsGreaterThan",
+ "<=": "PropertyIsLessThanOrEqualTo",
+ ">=": "PropertyIsGreaterThanOrEqualTo",
+ "..": "PropertyIsBetween",
+ "~": "PropertyIsLike",
+ "NULL": "PropertyIsNull",
+ "BBOX": "BBOX",
+ "DWITHIN": "DWITHIN",
+ "WITHIN": "WITHIN",
+ "CONTAINS": "CONTAINS",
+ "INTERSECTS": "INTERSECTS",
+ "FID": "_featureIds"
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Geometry.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object. Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor. This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ *
+ * Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
+ * explicitly include the OpenLayers.Format.WKT in your build.
+ */
+OpenLayers.Geometry = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique identifier for this geometry.
+ */
+ id: null,
+
+ /**
+ * Property: parent
+ * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+ * of another geometry
+ */
+ parent: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The bounds of this geometry
+ */
+ bounds: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry
+ * Creates a geometry object.
+ */
+ initialize: function() {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
+ },
+
+ /**
+ * Method: destroy
+ * Destroy this geometry.
+ */
+ destroy: function() {
+ this.id = null;
+ this.bounds = null;
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this geometry. Does not set any non-standard
+ * properties of the cloned geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} An exact clone of this geometry.
+ */
+ clone: function() {
+ return new OpenLayers.Geometry();
+ },
+
+ /**
+ * Method: setBounds
+ * Set the bounds for this Geometry.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ if (bounds) {
+ this.bounds = bounds.clone();
+ }
+ },
+
+ /**
+ * Method: clearBounds
+ * Nullify this components bounds and that of its parent as well.
+ */
+ clearBounds: function() {
+ this.bounds = null;
+ if (this.parent) {
+ this.parent.clearBounds();
+ }
+ },
+
+ /**
+ * Method: extendBounds
+ * Extend the existing bounds to include the new bounds.
+ * If geometry's bounds is not yet set, then set a new Bounds.
+ *
+ * Parameters:
+ * newBounds - {<OpenLayers.Bounds>}
+ */
+ extendBounds: function(newBounds){
+ var bounds = this.getBounds();
+ if (!bounds) {
+ this.setBounds(newBounds);
+ } else {
+ this.bounds.extend(newBounds);
+ }
+ },
+
+ /**
+ * APIMethod: getBounds
+ * Get the bounds for this Geometry. If bounds is not set, it
+ * is calculated again, this makes queries faster.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getBounds: function() {
+ if (this.bounds == null) {
+ this.calculateBounds();
+ }
+ return this.bounds;
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds for the geometry.
+ */
+ calculateBounds: function() {
+ //
+ // This should be overridden by subclasses.
+ //
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options depend on the specific geometry type.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ },
+
+ /**
+ * Method: atPoint
+ * Note - This is only an approximation based on the bounds of the
+ * geometry.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the geometry is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ var bounds = this.getBounds();
+ if ((bounds != null) && (lonlat != null)) {
+
+ var dX = (toleranceLon != null) ? toleranceLon : 0;
+ var dY = (toleranceLat != null) ? toleranceLat : 0;
+
+ var toleranceBounds =
+ new OpenLayers.Bounds(this.bounds.left - dX,
+ this.bounds.bottom - dY,
+ this.bounds.right + dX,
+ this.bounds.top + dY);
+
+ atPoint = toleranceBounds.containsLonLat(lonlat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: getLength
+ * Calculate the length of this geometry. This method is defined in
+ * subclasses.
+ *
+ * Returns:
+ * {Float} The length of the collection by summing its parts
+ */
+ getLength: function() {
+ //to be overridden by geometries that actually have a length
+ //
+ return 0.0;
+ },
+
+ /**
+ * Method: getArea
+ * Calculate the area of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ //to be overridden by geometries that actually have an area
+ //
+ return 0.0;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ * Calculate the centroid of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return null;
+ },
+
+ /**
+ * Method: toString
+ * Returns a text representation of the geometry. If the WKT format is
+ * included in a build, this will be the Well-Known Text
+ * representation.
+ *
+ * Returns:
+ * {String} String representation of this geometry.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ string = OpenLayers.Format.WKT.prototype.write(
+ new OpenLayers.Feature.Vector(this)
+ );
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry"
+});
+
+/**
+ * Function: OpenLayers.Geometry.fromWKT
+ * Generate a geometry given a Well-Known Text string. For this method to
+ * work, you must include the OpenLayers.Format.WKT in your build
+ * explicitly.
+ *
+ * Parameters:
+ * wkt - {String} A string representing the geometry in Well-Known Text.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry of the appropriate class.
+ */
+OpenLayers.Geometry.fromWKT = function(wkt) {
+ var geom;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ var format = OpenLayers.Geometry.fromWKT.format;
+ if (!format) {
+ format = new OpenLayers.Format.WKT();
+ OpenLayers.Geometry.fromWKT.format = format;
+ }
+ var result = format.read(wkt);
+ if (result instanceof OpenLayers.Feature.Vector) {
+ geom = result.geometry;
+ } else if (OpenLayers.Util.isArray(result)) {
+ var len = result.length;
+ var components = new Array(len);
+ for (var i=0; i<len; ++i) {
+ components[i] = result[i].geometry;
+ }
+ geom = new OpenLayers.Geometry.Collection(components);
+ }
+ }
+ return geom;
+};
+
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect. Optionally calculates
+ * and returns the intersection point. This function is optimized for
+ * cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1. In those
+ * obvious cases where there is no intersection, the function should
+ * not be called.
+ *
+ * Parameters:
+ * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * options - {Object} Optional properties for calculating the intersection.
+ *
+ * Valid options:
+ * point - {Boolean} Return the intersection point. If false, the actual
+ * intersection point will not be calculated. If true and the segments
+ * intersect, the intersection point will be returned. If true and
+ * the segments do not intersect, false will be returned. If true and
+ * the segments are coincident, true will be returned.
+ * tolerance - {Number} If a non-null value is provided, if the segments are
+ * within the tolerance distance, this will be considered an intersection.
+ * In addition, if the point option is true and the calculated intersection
+ * is within the tolerance distance of an end point, the endpoint will be
+ * returned instead of the calculated intersection. Further, if the
+ * intersection is within the tolerance of endpoints on both segments, or
+ * if two segment endpoints are within the tolerance distance of eachother
+ * (but no intersection is otherwise calculated), an endpoint on the
+ * first segment provided will be returned.
+ *
+ * Returns:
+ * {Boolean | <OpenLayers.Geometry.Point>} The two segments intersect.
+ * If the point argument is true, the return will be the intersection
+ * point or false if none exists. If point is true and the segments
+ * are coincident, return will be true (and the instersection is equal
+ * to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
+ var point = options && options.point;
+ var tolerance = options && options.tolerance;
+ var intersection = false;
+ var x11_21 = seg1.x1 - seg2.x1;
+ var y11_21 = seg1.y1 - seg2.y1;
+ var x12_11 = seg1.x2 - seg1.x1;
+ var y12_11 = seg1.y2 - seg1.y1;
+ var y22_21 = seg2.y2 - seg2.y1;
+ var x22_21 = seg2.x2 - seg2.x1;
+ var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+ var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+ var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+ if(d == 0) {
+ // parallel
+ if(n1 == 0 && n2 == 0) {
+ // coincident
+ intersection = true;
+ }
+ } else {
+ var along1 = n1 / d;
+ var along2 = n2 / d;
+ if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+ // intersect
+ if(!point) {
+ intersection = true;
+ } else {
+ // calculate the intersection point
+ var x = seg1.x1 + (along1 * x12_11);
+ var y = seg1.y1 + (along1 * y12_11);
+ intersection = new OpenLayers.Geometry.Point(x, y);
+ }
+ }
+ }
+ if(tolerance) {
+ var dist;
+ if(intersection) {
+ if(point) {
+ var segs = [seg1, seg2];
+ var seg, x, y;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ seg = segs[i];
+ for(var j=1; j<3; ++j) {
+ x = seg["x" + j];
+ y = seg["y" + j];
+ dist = Math.sqrt(
+ Math.pow(x - intersection.x, 2) +
+ Math.pow(y - intersection.y, 2)
+ );
+ if(dist < tolerance) {
+ intersection.x = x;
+ intersection.y = y;
+ break outer;
+ }
+ }
+ }
+
+ }
+ } else {
+ // no calculated intersection, but segments could be within
+ // the tolerance of one another
+ var segs = [seg1, seg2];
+ var source, target, x, y, p, result;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ source = segs[i];
+ target = segs[(i+1)%2];
+ for(var j=1; j<3; ++j) {
+ p = {x: source["x"+j], y: source["y"+j]};
+ result = OpenLayers.Geometry.distanceToSegment(p, target);
+ if(result.distance < tolerance) {
+ if(point) {
+ intersection = new OpenLayers.Geometry.Point(p.x, p.y);
+ } else {
+ intersection = true;
+ }
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ return intersection;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceToSegment
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with distance, along, x, and y properties. The distance
+ * will be the shortest distance between the input point and segment.
+ * The x and y properties represent the coordinates along the segment
+ * where the shortest distance meets the segment. The along attribute
+ * describes how far between the two segment points the given point is.
+ */
+OpenLayers.Geometry.distanceToSegment = function(point, segment) {
+ var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
+ result.distance = Math.sqrt(result.distance);
+ return result;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceSquaredToSegment
+ *
+ * Usually the distanceToSegment function should be used. This variant however
+ * can be used for comparisons where the exact distance is not important.
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with squared distance, along, x, and y properties.
+ * The distance will be the shortest distance between the input point and
+ * segment. The x and y properties represent the coordinates along the
+ * segment where the shortest distance meets the segment. The along
+ * attribute describes how far between the two segment points the given
+ * point is.
+ */
+OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
+ var x0 = point.x;
+ var y0 = point.y;
+ var x1 = segment.x1;
+ var y1 = segment.y1;
+ var x2 = segment.x2;
+ var y2 = segment.y2;
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
+ (Math.pow(dx, 2) + Math.pow(dy, 2));
+ var x, y;
+ if(along <= 0.0) {
+ x = x1;
+ y = y1;
+ } else if(along >= 1.0) {
+ x = x2;
+ y = y2;
+ } else {
+ x = x1 + along * dx;
+ y = y1 + along * dy;
+ }
+ return {
+ distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
+ x: x, y: y,
+ along: along
+ };
+};
+/* ======================================================================
+ OpenLayers/Geometry/Point.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Point
+ * Point geometry class.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: x
+ * {float}
+ */
+ x: null,
+
+ /**
+ * APIProperty: y
+ * {float}
+ */
+ y: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Point
+ * Construct a point geometry.
+ *
+ * Parameters:
+ * x - {float}
+ * y - {float}
+ *
+ */
+ initialize: function(x, y) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Geometry.Point(this.x, this.y);
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ return obj;
+ },
+
+ /**
+ * Method: calculateBounds
+ * Create a new Bounds based on the lon/lat
+ */
+ calculateBounds: function () {
+ this.bounds = new OpenLayers.Bounds(this.x, this.y,
+ this.x, this.y);
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var distance, x0, y0, x1, y1, result;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ x0 = this.x;
+ y0 = this.y;
+ x1 = geometry.x;
+ y1 = geometry.y;
+ distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
+ result = !details ?
+ distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
+ } else {
+ result = geometry.distanceTo(this, options);
+ if(details) {
+ // switch coord order since this geom is target
+ result = {
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0,
+ distance: result.distance
+ };
+ }
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geom - {<OpenLayers.Geometry.Point>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geom) {
+ var equals = false;
+ if (geom != null) {
+ equals = ((this.x == geom.x && this.y == geom.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * Method: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of Point object.
+ * (ex. <i>"5, 42"</i>)
+ */
+ toShortString: function() {
+ return (this.x + ", " + this.y);
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ this.x = this.x + x;
+ this.y = this.y + y;
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a point around another.
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ angle *= Math.PI / 180;
+ var radius = this.distanceTo(origin);
+ var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+ this.x = origin.x + (radius * Math.cos(theta));
+ this.y = origin.y + (radius * Math.sin(theta));
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return new OpenLayers.Geometry.Point(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a point relative to some origin. For points, this has the effect
+ * of scaling a vector (from the origin to the point). This method is
+ * more useful on geometry collection subclasses.
+ *
+ * Parameters:
+ * scale - {Float} Ratio of the new distance from the origin to the old
+ * distance from the origin. A scale of 2 doubles the
+ * distance between the point and origin.
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ ratio = (ratio == undefined) ? 1 : ratio;
+ this.x = origin.x + (scale * ratio * (this.x - origin.x));
+ this.y = origin.y + (scale * (this.y - origin.y));
+ this.clearBounds();
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.equals(geometry);
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: transform
+ * Translate the x,y properties of the point from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if ((source && dest)) {
+ OpenLayers.Projection.transform(
+ this, source, dest);
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return [this];
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Point"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Collection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different
+ * Geometries. These are stored in the local parameter <components> (which
+ * can be passed as a parameter to the constructor).
+ *
+ * As new geometries are added to the collection, they are NOT cloned.
+ * When removing geometries, they need to be specified by reference (ie you
+ * have to pass in the *exact* geometry to be removed).
+ *
+ * The <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: components
+ * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
+ */
+ components: null,
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Collection
+ * Creates a Geometry Collection -- a list of geoms.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
+ *
+ */
+ initialize: function (components) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+ this.components = [];
+ if (components != null) {
+ this.addComponents(components);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this geometry.
+ */
+ destroy: function () {
+ this.components.length = 0;
+ this.components = null;
+ OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ * Clone this geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+ */
+ clone: function() {
+ var geometry = eval("new " + this.CLASS_NAME + "()");
+ for(var i=0, len=this.components.length; i<len; i++) {
+ geometry.addComponent(this.components[i].clone());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(geometry, this);
+
+ return geometry;
+ },
+
+ /**
+ * Method: getComponentsString
+ * Get a string representing the components for this collection
+ *
+ * Returns:
+ * {String} A string representation of the components of this geometry
+ */
+ getComponentsString: function(){
+ var strings = [];
+ for(var i=0, len=this.components.length; i<len; i++) {
+ strings.push(this.components[i].toShortString());
+ }
+ return strings.join(",");
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds by iterating through the components and
+ * calling calling extendBounds() on each item.
+ */
+ calculateBounds: function() {
+ this.bounds = null;
+ var bounds = new OpenLayers.Bounds();
+ var components = this.components;
+ if (components) {
+ for (var i=0, len=components.length; i<len; i++) {
+ bounds.extend(components[i].getBounds());
+ }
+ }
+ // to preserve old behavior, we only set bounds if non-null
+ // in the future, we could add bounds.isEmpty()
+ if (bounds.left != null && bounds.bottom != null &&
+ bounds.right != null && bounds.top != null) {
+ this.setBounds(bounds);
+ }
+ },
+
+ /**
+ * APIMethod: addComponents
+ * Add components to this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+ */
+ addComponents: function(components){
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=0, len=components.length; i<len; i++) {
+ this.addComponent(components[i]);
+ }
+ },
+
+ /**
+ * Method: addComponent
+ * Add a new component (geometry) to the collection. If this.componentTypes
+ * is set, then the component class name must be in the componentTypes array.
+ *
+ * The bounds cache is reset.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>} A geometry to add
+ * index - {int} Optional index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} The component geometry was successfully added
+ */
+ addComponent: function(component, index) {
+ var added = false;
+ if(component) {
+ if(this.componentTypes == null ||
+ (OpenLayers.Util.indexOf(this.componentTypes,
+ component.CLASS_NAME) > -1)) {
+
+ if(index != null && (index < this.components.length)) {
+ var components1 = this.components.slice(0, index);
+ var components2 = this.components.slice(index,
+ this.components.length);
+ components1.push(component);
+ this.components = components1.concat(components2);
+ } else {
+ this.components.push(component);
+ }
+ component.parent = this;
+ this.clearBounds();
+ added = true;
+ }
+ }
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponents
+ * Remove components from this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} The components to be removed
+ *
+ * Returns:
+ * {Boolean} A component was removed.
+ */
+ removeComponents: function(components) {
+ var removed = false;
+
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=components.length-1; i>=0; --i) {
+ removed = this.removeComponent(components[i]) || removed;
+ }
+ return removed;
+ },
+
+ /**
+ * Method: removeComponent
+ * Remove a component from this geometry.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(component) {
+
+ OpenLayers.Util.removeItem(this.components, component);
+
+ // clearBounds() so that it gets recalculated on the next call
+ // to this.getBounds();
+ this.clearBounds();
+ return true;
+ },
+
+ /**
+ * APIMethod: getLength
+ * Calculate the length of this geometry
+ *
+ * Returns:
+ * {Float} The length of the geometry
+ */
+ getLength: function() {
+ var length = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getLength();
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getArea
+ * Calculate the area of this geometry. Note how this function is overridden
+ * in <OpenLayers.Geometry.Polygon>.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ var area = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getArea();
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the geometry in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getGeodesicArea(projection);
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Compute the centroid for this geometry collection.
+ *
+ * Parameters:
+ * weighted - {Boolean} Perform the getCentroid computation recursively,
+ * returning an area weighted average of all geometries in this collection.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function(weighted) {
+ if (!weighted) {
+ return this.components.length && this.components[0].getCentroid();
+ }
+ var len = this.components.length;
+ if (!len) {
+ return false;
+ }
+
+ var areas = [];
+ var centroids = [];
+ var areaSum = 0;
+ var minArea = Number.MAX_VALUE;
+ var component;
+ for (var i=0; i<len; ++i) {
+ component = this.components[i];
+ var area = component.getArea();
+ var centroid = component.getCentroid(true);
+ if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
+ continue;
+ }
+ areas.push(area);
+ areaSum += area;
+ minArea = (area < minArea && area > 0) ? area : minArea;
+ centroids.push(centroid);
+ }
+ len = areas.length;
+ if (areaSum === 0) {
+ // all the components in this collection have 0 area
+ // probably a collection of points -- weight all the points the same
+ for (var i=0; i<len; ++i) {
+ areas[i] = 1;
+ }
+ areaSum = areas.length;
+ } else {
+ // normalize all the areas where the smallest area will get
+ // a value of 1
+ for (var i=0; i<len; ++i) {
+ areas[i] /= minArea;
+ }
+ areaSum /= minArea;
+ }
+
+ var xSum = 0, ySum = 0, centroid, area;
+ for (var i=0; i<len; ++i) {
+ centroid = centroids[i];
+ area = areas[i];
+ xSum += centroid.x * area;
+ ySum += centroid.y * area;
+ }
+
+ return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var length = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getGeodesicLength(projection);
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i=0, len=this.components.length; i<len; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0; i<this.components.length; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best, distance;
+ var min = Number.POSITIVE_INFINITY;
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ result = this.components[i].distanceTo(geometry, options);
+ distance = details ? result.distance : result;
+ if(distance < min) {
+ min = distance;
+ best = result;
+ if(min == 0) {
+ break;
+ }
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geometry) {
+ var equivalent = true;
+ if(!geometry || !geometry.CLASS_NAME ||
+ (this.CLASS_NAME != geometry.CLASS_NAME)) {
+ equivalent = false;
+ } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
+ (geometry.components.length != this.components.length)) {
+ equivalent = false;
+ } else {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ if(!this.components[i].equals(geometry.components[i])) {
+ equivalent = false;
+ break;
+ }
+ }
+ }
+ return equivalent;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ for(var i=0, len=this.components.length; i<len; ++ i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices = [];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ Array.prototype.push.apply(
+ vertices, this.components[i].getVertices(nodes)
+ );
+ }
+ return vertices;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
+/* ======================================================================
+ OpenLayers/Geometry/MultiPoint.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points. Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPoint
+ * Create a new MultiPoint Geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>}
+ */
+
+ /**
+ * APIMethod: addPoint
+ * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be added
+ * index - {Integer} Optional index
+ */
+ addPoint: function(point, index) {
+ this.addComponent(point, index);
+ },
+
+ /**
+ * APIMethod: removePoint
+ * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be removed
+ */
+ removePoint: function(point){
+ this.removeComponent(point);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Curve.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Curve
+ * A Curve is a MultiPoint, whose points are assumed to be connected. To
+ * this end, we provide a "getLength()" function, which iterates through
+ * the points, summing the distances between them.
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.MultiPoint>
+ */
+OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Curve
+ *
+ * Parameters:
+ * point - {Array(<OpenLayers.Geometry.Point>)}
+ */
+
+ /**
+ * APIMethod: getLength
+ *
+ * Returns:
+ * {Float} The length of the curve
+ */
+ getLength: function() {
+ var length = 0.0;
+ if ( this.components && (this.components.length > 1)) {
+ for(var i=1, len=this.components.length; i<len; i++) {
+ length += this.components[i-1].distanceTo(this.components[i]);
+ }
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var geom = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ geom = this.clone().transform(projection, gg);
+ }
+ }
+ var length = 0.0;
+ if(geom.components && (geom.components.length > 1)) {
+ var p1, p2;
+ for(var i=1, len=geom.components.length; i<len; i++) {
+ p1 = geom.components[i-1];
+ p2 = geom.components[i];
+ // this returns km and requires lon/lat properties
+ length += OpenLayers.Util.distVincenty(
+ {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
+ );
+ }
+ }
+ // convert to m
+ return length * 1000;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can
+ * never be less than two points long.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+ /**
+ * Constructor: OpenLayers.Geometry.LineString
+ * Create a new LineString geometry
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
+ * generate the linestring
+ *
+ */
+
+ /**
+ * APIMethod: removeComponent
+ * Only allows removal of a point if there are three or more points in
+ * the linestring. (otherwise the result would be just a single point)
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The point to be removed
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 2);
+ if (removed) {
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Test for instersection between two geometries. This is a cheapo
+ * implementation of the Bently-Ottmann algorigithm. It doesn't
+ * really keep track of a sweep line data structure. It is closer
+ * to the brute force method, except that segments are sorted and
+ * potential intersections are only calculated when bounding boxes
+ * intersect.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this geometry.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var type = geometry.CLASS_NAME;
+ if(type == "OpenLayers.Geometry.LineString" ||
+ type == "OpenLayers.Geometry.LinearRing" ||
+ type == "OpenLayers.Geometry.Point") {
+ var segs1 = this.getSortedSegments();
+ var segs2;
+ if(type == "OpenLayers.Geometry.Point") {
+ segs2 = [{
+ x1: geometry.x, y1: geometry.y,
+ x2: geometry.x, y2: geometry.y
+ }];
+ } else {
+ segs2 = geometry.getSortedSegments();
+ }
+ var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
+ seg2, seg2y1, seg2y2;
+ // sweep right
+ outer: for(var i=0, len=segs1.length; i<len; ++i) {
+ seg1 = segs1[i];
+ seg1x1 = seg1.x1;
+ seg1x2 = seg1.x2;
+ seg1y1 = seg1.y1;
+ seg1y2 = seg1.y2;
+ inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+ seg2 = segs2[j];
+ if(seg2.x1 > seg1x2) {
+ // seg1 still left of seg2
+ break;
+ }
+ if(seg2.x2 < seg1x1) {
+ // seg2 still left of seg1
+ continue;
+ }
+ seg2y1 = seg2.y1;
+ seg2y2 = seg2.y2;
+ if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
+ // seg2 above seg1
+ continue;
+ }
+ if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
+ // seg2 below seg1
+ continue;
+ }
+ if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
+ intersect = true;
+ break outer;
+ }
+ }
+ }
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * Method: getSortedSegments
+ *
+ * Returns:
+ * {Array} An array of segment objects. Segment objects have properties
+ * x1, y1, x2, and y2. The start point is represented by x1 and y1.
+ * The end point is represented by x2 and y2. Start and end are
+ * ordered so that x1 < x2.
+ */
+ getSortedSegments: function() {
+ var numSeg = this.components.length - 1;
+ var segments = new Array(numSeg), point1, point2;
+ for(var i=0; i<numSeg; ++i) {
+ point1 = this.components[i];
+ point2 = this.components[i + 1];
+ if(point1.x < point2.x) {
+ segments[i] = {
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y
+ };
+ } else {
+ segments[i] = {
+ x1: point2.x,
+ y1: point2.y,
+ x2: point1.x,
+ y2: point1.y
+ };
+ }
+ }
+ // more efficient to define this somewhere static
+ function byX1(seg1, seg2) {
+ return seg1.x1 - seg2.x1;
+ }
+ return segments.sort(byX1);
+ },
+
+ /**
+ * Method: splitWithSegment
+ * Split this geometry with the given segment.
+ *
+ * Parameters:
+ * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
+ * segment endpoint coordinates.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source segment must be within the
+ * tolerance distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of one of the source segment's
+ * endpoints will be assumed to occur at the endpoint.
+ *
+ * Returns:
+ * {Object} An object with *lines* and *points* properties. If the given
+ * segment intersects this linestring, the lines array will reference
+ * geometries that result from the split. The points array will contain
+ * all intersection points. Intersection points are sorted along the
+ * segment (in order from x1,y1 to x2,y2).
+ */
+ splitWithSegment: function(seg, options) {
+ var edge = !(options && options.edge === false);
+ var tolerance = options && options.tolerance;
+ var lines = [];
+ var verts = this.getVertices();
+ var points = [];
+ var intersections = [];
+ var split = false;
+ var vert1, vert2, point;
+ var node, vertex, target;
+ var interOptions = {point: true, tolerance: tolerance};
+ var result = null;
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ points.push(vert1.clone());
+ vert2 = verts[i+1];
+ target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
+ point = OpenLayers.Geometry.segmentsIntersect(
+ seg, target, interOptions
+ );
+ if(point instanceof OpenLayers.Geometry.Point) {
+ if((point.x === seg.x1 && point.y === seg.y1) ||
+ (point.x === seg.x2 && point.y === seg.y2) ||
+ point.equals(vert1) || point.equals(vert2)) {
+ vertex = true;
+ } else {
+ vertex = false;
+ }
+ if(vertex || edge) {
+ // push intersections different than the previous
+ if(!point.equals(intersections[intersections.length-1])) {
+ intersections.push(point.clone());
+ }
+ if(i === 0) {
+ if(point.equals(vert1)) {
+ continue;
+ }
+ }
+ if(point.equals(vert2)) {
+ continue;
+ }
+ split = true;
+ if(!point.equals(vert1)) {
+ points.push(point);
+ }
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ points = [point.clone()];
+ }
+ }
+ }
+ if(split) {
+ points.push(vert2.clone());
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ }
+ if(intersections.length > 0) {
+ // sort intersections along segment
+ var xDir = seg.x1 < seg.x2 ? 1 : -1;
+ var yDir = seg.y1 < seg.y2 ? 1 : -1;
+ result = {
+ lines: lines,
+ points: intersections.sort(function(p1, p2) {
+ return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
+ })
+ };
+ }
+ return result;
+ },
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(target, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var sourceSplit, targetSplit, sourceParts, targetParts;
+ if(target instanceof OpenLayers.Geometry.LineString) {
+ var verts = this.getVertices();
+ var vert1, vert2, seg, splits, lines, point;
+ var points = [];
+ sourceParts = [];
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ vert2 = verts[i+1];
+ seg = {
+ x1: vert1.x, y1: vert1.y,
+ x2: vert2.x, y2: vert2.y
+ };
+ targetParts = targetParts || [target];
+ if(mutual) {
+ points.push(vert1.clone());
+ }
+ for(var j=0; j<targetParts.length; ++j) {
+ splits = targetParts[j].splitWithSegment(seg, options);
+ if(splits) {
+ // splice in new features
+ lines = splits.lines;
+ if(lines.length > 0) {
+ lines.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, lines);
+ j += lines.length - 2;
+ }
+ if(mutual) {
+ for(var k=0, len=splits.points.length; k<len; ++k) {
+ point = splits.points[k];
+ if(!point.equals(vert1)) {
+ points.push(point);
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ if(point.equals(vert2)) {
+ points = [];
+ } else {
+ points = [point.clone()];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(mutual && sourceParts.length > 0 && points.length > 0) {
+ points.push(vert2.clone());
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ }
+ } else {
+ results = target.splitWith(this, options);
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetSplit || sourceSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ return geometry.split(this, options);
+
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices;
+ if(nodes === true) {
+ vertices = [
+ this.components[0],
+ this.components[this.components.length-1]
+ ];
+ } else if (nodes === false) {
+ vertices = this.components.slice(1, this.components.length-1);
+ } else {
+ vertices = this.components.slice();
+ }
+ return vertices;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best = {};
+ var min = Number.POSITIVE_INFINITY;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ var segs = this.getSortedSegments();
+ var x = geometry.x;
+ var y = geometry.y;
+ var seg;
+ for(var i=0, len=segs.length; i<len; ++i) {
+ seg = segs[i];
+ result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
+ if(result.distance < min) {
+ min = result.distance;
+ best = result;
+ if(min === 0) {
+ break;
+ }
+ } else {
+ // if distance increases and we cross y0 to the right of x0, no need to keep looking.
+ if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {
+ break;
+ }
+ }
+ }
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x, y0: best.y,
+ x1: x, y1: y
+ };
+ } else {
+ best = best.distance;
+ }
+ } else if(geometry instanceof OpenLayers.Geometry.LineString) {
+ var segs0 = this.getSortedSegments();
+ var segs1 = geometry.getSortedSegments();
+ var seg0, seg1, intersection, x0, y0;
+ var len1 = segs1.length;
+ var interOptions = {point: true};
+ outer: for(var i=0, len=segs0.length; i<len; ++i) {
+ seg0 = segs0[i];
+ x0 = seg0.x1;
+ y0 = seg0.y1;
+ for(var j=0; j<len1; ++j) {
+ seg1 = segs1[j];
+ intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
+ if(intersection) {
+ min = 0;
+ best = {
+ distance: 0,
+ x0: intersection.x, y0: intersection.y,
+ x1: intersection.x, y1: intersection.y
+ };
+ break outer;
+ } else {
+ result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
+ if(result.distance < min) {
+ min = result.distance;
+ best = {
+ distance: min,
+ x0: x0, y0: y0,
+ x1: result.x, y1: result.y
+ };
+ }
+ }
+ }
+ }
+ if(!details) {
+ best = best.distance;
+ }
+ if(min !== 0) {
+ // check the final vertex in this line's sorted segments
+ if(seg0) {
+ result = geometry.distanceTo(
+ new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
+ options
+ );
+ var dist = details ? result.distance : result;
+ if(dist < min) {
+ if(details) {
+ best = {
+ distance: min,
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0
+ };
+ } else {
+ best = dist;
+ }
+ }
+ }
+ }
+ } else {
+ best = geometry.distanceTo(this, options);
+ // swap since target comes from this line
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x1, y0: best.y1,
+ x1: best.x0, y1: best.y0
+ };
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: simplify
+ * This function will return a simplified LineString.
+ * Simplification is based on the Douglas-Peucker algorithm.
+ *
+ *
+ * Parameters:
+ * tolerance - {number} threshhold for simplification in map units
+ *
+ * Returns:
+ * {OpenLayers.Geometry.LineString} the simplified LineString
+ */
+ simplify: function(tolerance){
+ if (this && this !== null) {
+ var points = this.getVertices();
+ if (points.length < 3) {
+ return this;
+ }
+
+ var compareNumbers = function(a, b){
+ return (a-b);
+ };
+
+ /**
+ * Private function doing the Douglas-Peucker reduction
+ */
+ var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
+ var maxDistance = 0;
+ var indexFarthest = 0;
+
+ for (var index = firstPoint, distance; index < lastPoint; index++) {
+ distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ indexFarthest = index;
+ }
+ }
+
+ if (maxDistance > tolerance && indexFarthest != firstPoint) {
+ //Add the largest point that exceeds the tolerance
+ pointIndexsToKeep.push(indexFarthest);
+ douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
+ douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
+ }
+ };
+
+ /**
+ * Private function calculating the perpendicular distance
+ * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
+ */
+ var perpendicularDistance = function(point1, point2, point){
+ //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
+ //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
+ //Area = .5*Base*H *Solve for height
+ //Height = Area/.5/Base
+
+ var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
+ var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
+ var height = area / bottom * 2;
+
+ return height;
+ };
+
+ var firstPoint = 0;
+ var lastPoint = points.length - 1;
+ var pointIndexsToKeep = [];
+
+ //Add the first and last index to the keepers
+ pointIndexsToKeep.push(firstPoint);
+ pointIndexsToKeep.push(lastPoint);
+
+ //The first and the last point cannot be the same
+ while (points[firstPoint].equals(points[lastPoint])) {
+ lastPoint--;
+ //Addition: the first point not equal to first point in the LineString is kept as well
+ pointIndexsToKeep.push(lastPoint);
+ }
+
+ douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
+ var returnPoints = [];
+ pointIndexsToKeep.sort(compareNumbers);
+ for (var index = 0; index < pointIndexsToKeep.length; index++) {
+ returnPoints.push(points[pointIndexsToKeep[index]]);
+ }
+ return new OpenLayers.Geometry.LineString(returnPoints);
+
+ }
+ else {
+ return this;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
+/* ======================================================================
+ OpenLayers/Geometry/MultiLineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LineString"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiLineString
+ * Constructor for a MultiLineString Geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LineString>)}
+ *
+ */
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
+ var sourceParts = [];
+ var targetParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ sourceLine = this.components[i];
+ sourceSplit = false;
+ for(var j=0; j < targetParts.length; ++j) {
+ splits = sourceLine.split(targetParts[j], options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ for(var k=0, klen=sourceLines.length; k<klen; ++k) {
+ if(k===0 && sourceParts.length) {
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLines[k]
+ );
+ } else {
+ sourceParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ sourceLines[k]
+ ])
+ );
+ }
+ }
+ sourceSplit = true;
+ splits = splits[1];
+ }
+ if(splits.length) {
+ // splice in new target parts
+ splits.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, splits);
+ break;
+ }
+ }
+ }
+ if(!sourceSplit) {
+ // source line was not hit
+ if(sourceParts.length) {
+ // add line to existing multi
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLine.clone()
+ );
+ } else {
+ // create a fresh multi
+ sourceParts = [
+ new OpenLayers.Geometry.MultiLineString(
+ sourceLine.clone()
+ )
+ ];
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
+ if(geometry instanceof OpenLayers.Geometry.LineString) {
+ targetParts = [];
+ sourceParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ targetSplit = false;
+ targetLine = this.components[i];
+ for(var j=0; j<sourceParts.length; ++j) {
+ splits = sourceParts[j].split(targetLine, options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ if(sourceLines.length) {
+ // splice in new source parts
+ sourceLines.unshift(j, 1);
+ Array.prototype.splice.apply(sourceParts, sourceLines);
+ j += sourceLines.length - 2;
+ }
+ splits = splits[1];
+ if(splits.length === 0) {
+ splits = [targetLine.clone()];
+ }
+ }
+ for(var k=0, klen=splits.length; k<klen; ++k) {
+ if(k===0 && targetParts.length) {
+ targetParts[targetParts.length-1].addComponent(
+ splits[k]
+ );
+ } else {
+ targetParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ splits[k]
+ ])
+ );
+ }
+ }
+ targetSplit = true;
+ }
+ }
+ if(!targetSplit) {
+ // target component was not hit
+ if(targetParts.length) {
+ // add it to any existing multi-line
+ targetParts[targetParts.length-1].addComponent(
+ targetLine.clone()
+ );
+ } else {
+ // or start with a fresh multi-line
+ targetParts = [
+ new OpenLayers.Geometry.MultiLineString([
+ targetLine.clone()
+ ])
+ ];
+ }
+
+ }
+ }
+ } else {
+ results = geometry.split(this);
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LinearRing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ *
+ * A Linear Ring is a special LineString which is closed. It closes itself
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point.
+ *
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.LineString>
+ */
+OpenLayers.Geometry.LinearRing = OpenLayers.Class(
+ OpenLayers.Geometry.LineString, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.LinearRing
+ * Linear rings are constructed with an array of points. This array
+ * can represent a closed or open ring. If the ring is open (the last
+ * point does not equal the first point), the constructor will close
+ * the ring. If the ring is already closed (the last point does equal
+ * the first point), it will be left closed.
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} points
+ */
+
+ /**
+ * APIMethod: addComponent
+ * Adds a point to geometry components. If the point is to be added to
+ * the end of the components array and it is the same as the last point
+ * already in that array, the duplicate point is not added. This has
+ * the effect of closing the ring if it is not already closed, and
+ * doing the right thing if it is already closed. This behavior can
+ * be overridden by calling the method with a non-null index as the
+ * second argument.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * index - {Integer} Index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} Was the Point successfully added?
+ */
+ addComponent: function(point, index) {
+ var added = false;
+
+ //remove last point
+ var lastPoint = this.components.pop();
+
+ // given an index, add the point
+ // without an index only add non-duplicate points
+ if(index != null || !point.equals(lastPoint)) {
+ added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ arguments);
+ }
+
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponent
+ * Removes a point from geometry components.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 3);
+ if (removed) {
+ //remove last point
+ this.components.pop();
+
+ //remove our point
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i = 0, len=this.components.length; i<len - 1; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ if (this.components) {
+ var len = this.components.length;
+ if (len > 0 && len <= 2) {
+ return this.components[0].clone();
+ } else if (len > 2) {
+ var sumX = 0.0;
+ var sumY = 0.0;
+ var x0 = this.components[0].x;
+ var y0 = this.components[0].y;
+ var area = -1 * this.getArea();
+ if (area != 0) {
+ for (var i = 0; i < len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ }
+ var x = x0 + sumX / (6 * area);
+ var y = y0 + sumY / (6 * area);
+ } else {
+ for (var i = 0; i < len - 1; i++) {
+ sumX += this.components[i].x;
+ sumY += this.components[i].y;
+ }
+ var x = sumX / (len - 1);
+ var y = sumY / (len - 1);
+ }
+ return new OpenLayers.Geometry.Point(x, y);
+ } else {
+ return null;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getArea
+ * Note - The area is positive if the ring is oriented CW, otherwise
+ * it will be negative.
+ *
+ * Returns:
+ * {Float} The signed area for a ring.
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 2)) {
+ var sum = 0.0;
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sum += (b.x + c.x) * (c.y - b.y);
+ }
+ area = - sum / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth. Note that this area will be positive if ring is oriented
+ * clockwise, otherwise it will be negative.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate signed geodesic area of the polygon in square
+ * meters.
+ */
+ getGeodesicArea: function(projection) {
+ var ring = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ ring = this.clone().transform(projection, gg);
+ }
+ }
+ var area = 0.0;
+ var len = ring.components && ring.components.length;
+ if(len > 2) {
+ var p1, p2;
+ for(var i=0; i<len-1; i++) {
+ p1 = ring.components[i];
+ p2 = ring.components[i+1];
+ area += OpenLayers.Util.rad(p2.x - p1.x) *
+ (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
+ Math.sin(OpenLayers.Util.rad(p2.y)));
+ }
+ area = area * 6378137.0 * 6378137.0 / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a linear ring. For the case where a point
+ * is coincident with a linear ring edge, returns 1. Otherwise,
+ * returns boolean.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the linear ring. Returns 1 if
+ * the point is coincident with an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var approx = OpenLayers.Number.limitSigDigs;
+ var digs = 14;
+ var px = approx(point.x, digs);
+ var py = approx(point.y, digs);
+ function getX(y, x1, y1, x2, y2) {
+ return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
+ }
+ var numSeg = this.components.length - 1;
+ var start, end, x1, y1, x2, y2, cx, cy;
+ var crosses = 0;
+ for(var i=0; i<numSeg; ++i) {
+ start = this.components[i];
+ x1 = approx(start.x, digs);
+ y1 = approx(start.y, digs);
+ end = this.components[i + 1];
+ x2 = approx(end.x, digs);
+ y2 = approx(end.y, digs);
+
+ /**
+ * The following conditions enforce five edge-crossing rules:
+ * 1. points coincident with edges are considered contained;
+ * 2. an upward edge includes its starting endpoint, and
+ * excludes its final endpoint;
+ * 3. a downward edge excludes its starting endpoint, and
+ * includes its final endpoint;
+ * 4. horizontal edges are excluded; and
+ * 5. the edge-ray intersection point must be strictly right
+ * of the point P.
+ */
+ if(y1 == y2) {
+ // horizontal edge
+ if(py == y1) {
+ // point on horizontal line
+ if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
+ x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ // ignore other horizontal edges
+ continue;
+ }
+ cx = approx(getX(py, x1, y1, x2, y2), digs);
+ if(cx == px) {
+ // point on line
+ if(y1 < y2 && (py >= y1 && py <= y2) || // upward
+ y1 > y2 && (py <= y1 && py >= y2)) { // downward
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ if(cx <= px) {
+ // no crossing to the right
+ continue;
+ }
+ if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
+ // no crossing
+ continue;
+ }
+ if(y1 < y2 && (py >= y1 && py < y2) || // upward
+ y1 > y2 && (py < y1 && py >= y2)) { // downward
+ ++crosses;
+ }
+ }
+ var contained = (crosses == -1) ?
+ // on edge
+ 1 :
+ // even (out) or odd (in)
+ !!(crosses & 1);
+
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+ intersect = geometry.intersects(this);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
+ this, [geometry]
+ );
+ } else {
+ // check for component intersections
+ for(var i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = geometry.components[i].intersects(this);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Polygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon
+ * Polygon is a collection of Geometry.LinearRings.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Polygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LinearRing"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Polygon
+ * Constructor for a Polygon geometry.
+ * The first ring (this.component[0])is the outer bounds of the polygon and
+ * all subsequent rings (this.component[1-n]) are internal holes.
+ *
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LinearRing>)}
+ */
+
+ /**
+ * APIMethod: getArea
+ * Calculated by subtracting the areas of the internal holes from the
+ * area of the outer hole.
+ *
+ * Returns:
+ * {float} The area of the geometry
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getArea());
+ for (var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getArea());
+ }
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the polygon in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ if(this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getGeodesicArea(projection));
+ for(var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getGeodesicArea(projection));
+ }
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a polygon. Points on a polygon edge are
+ * considered inside.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the polygon. Returns 1 if the
+ * point is on an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var numRings = this.components.length;
+ var contained = false;
+ if(numRings > 0) {
+ // check exterior ring - 1 means on edge, boolean otherwise
+ contained = this.components[0].containsPoint(point);
+ if(contained !== 1) {
+ if(contained && numRings > 1) {
+ // check interior rings
+ var hole;
+ for(var i=1; i<numRings; ++i) {
+ hole = this.components[i].containsPoint(point);
+ if(hole) {
+ if(hole === 1) {
+ // on edge
+ contained = 1;
+ } else {
+ // in hole
+ contained = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var i, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+ geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ // check if rings/linestrings intersect
+ for(i=0, len=this.components.length; i<len; ++i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ if(!intersect) {
+ // check if this poly contains points of the ring/linestring
+ for(i=0, len=geometry.components.length; i<len; ++i) {
+ intersect = this.containsPoint(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ } else {
+ for(i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = this.intersects(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ // check case where this poly is wholly contained by another
+ if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ // exterior ring points will be contained in the other geometry
+ var ring = this.components[0];
+ for(i=0, len=ring.components.length; i<len; ++i) {
+ intersect = geometry.containsPoint(ring.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var result;
+ // this is the case where we might not be looking for distance to edge
+ if(!edge && this.intersects(geometry)) {
+ result = 0;
+ } else {
+ result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
+ this, [geometry, options]
+ );
+ }
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Polygon"
+});
+
+/**
+ * APIMethod: createRegularPolygon
+ * Create a regular polygon around a radius. Useful for creating circles
+ * and the like.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.Geometry.Point>} center of polygon.
+ * radius - {Float} distance to vertex, in map units.
+ * sides - {Integer} Number of sides. 20 approximates a circle.
+ * rotation - {Float} original angle of rotation, in degrees.
+ */
+OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {
+ var angle = Math.PI * ((1/sides) - (1/2));
+ if(rotation) {
+ angle += (rotation / 180) * Math.PI;
+ }
+ var rotatedAngle, x, y;
+ var points = [];
+ for(var i=0; i<sides; ++i) {
+ rotatedAngle = angle + (i * 2 * Math.PI / sides);
+ x = origin.x + (radius * Math.cos(rotatedAngle));
+ y = origin.y + (radius * Math.sin(rotatedAngle));
+ points.push(new OpenLayers.Geometry.Point(x, y));
+ }
+ var ring = new OpenLayers.Geometry.LinearRing(points);
+ return new OpenLayers.Geometry.Polygon([ring]);
+};
+/* ======================================================================
+ OpenLayers/Geometry/MultiPolygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components. Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ */
+OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Polygon"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPolygon
+ * Create a new MultiPolygon geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
+ * used to generate the MultiPolygon
+ *
+ */
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
+});
+/* ======================================================================
+ OpenLayers/Format/GML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML
+ * Read/Write GML. Create a new instance with the <OpenLayers.Format.GML>
+ * constructor. Supports the GML simple features profile.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: featureNS
+ * {String} Namespace used for feature attributes. Default is
+ * "http://mapserver.gis.umn.edu/mapserver".
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * APIProperty: featurePrefix
+ * {String} Namespace alias (or prefix) for feature nodes. Default is
+ * "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * APIProperty: featureName
+ * {String} Element name for features. Default is "featureMember".
+ */
+ featureName: "featureMember",
+
+ /**
+ * APIProperty: layerName
+ * {String} Name of data layer. Default is "features".
+ */
+ layerName: "features",
+
+ /**
+ * APIProperty: geometryName
+ * {String} Name of geometry element. Defaults to "geometry".
+ */
+ geometryName: "geometry",
+
+ /**
+ * APIProperty: collectionName
+ * {String} Name of featureCollection element.
+ */
+ collectionName: "FeatureCollection",
+
+ /**
+ * APIProperty: gmlns
+ * {String} GML Namespace.
+ */
+ gmlns: "http://www.opengis.net/gml",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Constructor: OpenLayers.Format.GML
+ * Create a new parser for GML.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // compile regular expressions once instead of every time they are used
+ this.regExes = {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ };
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var featureNodes = this.getElementsByTagNameNS(data.documentElement,
+ this.gmlns,
+ this.featureName);
+ var features = [];
+ for(var i=0; i<featureNodes.length; i++) {
+ var feature = this.parseFeature(featureNodes[i]);
+ if(feature) {
+ features.push(feature);
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the GML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML feature node.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiPolygon", "Polygon",
+ "MultiLineString", "LineString",
+ "MultiPoint", "Point", "Envelope"];
+ // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
+ // this code creates a geometry derived from the Envelope. This is not correct.
+ var type, nodeList, geometry, parser;
+ for(var i=0; i<order.length; ++i) {
+ type = order[i];
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ geometry = parser.apply(this, [nodeList[0]]);
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ } else {
+ throw new TypeError("Unsupported geometry type: " + type);
+ }
+ // stop looking for different geometry types
+ break;
+ }
+ }
+
+ var bounds;
+ var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box");
+ for(i=0; i<boxNodes.length; ++i) {
+ var boxNode = boxNodes[i];
+ var box = this.parseGeometry["box"].apply(this, [boxNode]);
+ var parentNode = boxNode.parentNode;
+ var parentName = parentNode.localName ||
+ parentNode.nodeName.split(":").pop();
+ if(parentName === "boundedBy") {
+ bounds = box;
+ } else {
+ geometry = box.toGeometry();
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ feature.bounds = bounds;
+
+ feature.gml = {
+ featureType: node.firstChild.nodeName.split(":")[1],
+ featureNS: node.firstChild.namespaceURI,
+ featureNSPrefix: node.firstChild.prefix
+ };
+
+ // assign fid - this can come from a "fid" or "id" attribute
+ var childNode = node.firstChild;
+ var fid;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ fid = childNode.getAttribute("fid") ||
+ childNode.getAttribute("id");
+ if(fid) {
+ break;
+ }
+ }
+ childNode = childNode.nextSibling;
+ }
+ feature.fid = fid;
+ return feature;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a GML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ /**
+ * Three coordinate variations to consider:
+ * 1) <gml:pos>x y z</gml:pos>
+ * 2) <gml:coordinates>x, y, z</gml:coordinates>
+ * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
+ */
+ var nodeList, coordString;
+ var coords = [];
+
+ // look for <gml:pos>
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
+ if(nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ // look for <gml:coordinates>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ if(nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace,
+ "");
+ coords = coordString.split(",");
+ }
+ }
+
+ // look for <gml:coord>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coord");
+ if(nodeList.length > 0) {
+ var xList = this.getElementsByTagNameNS(nodeList[0],
+ this.gmlns, "X");
+ var yList = this.getElementsByTagNameNS(nodeList[0],
+ this.gmlns, "Y");
+ if(xList.length > 0 && yList.length > 0) {
+ coords = [xList[0].firstChild.nodeValue,
+ yList[0].firstChild.nodeValue];
+ }
+ }
+ }
+
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+
+ if (this.xy) {
+ return new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ }
+ else{
+ return new OpenLayers.Geometry.Point(coords[1], coords[0],
+ coords[2]);
+ }
+ },
+
+ /**
+ * Method: parseGeometry.multipoint
+ * Given a GML node representing a multipoint geometry, create an
+ * OpenLayers multipoint geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+ */
+ multipoint: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "Point");
+ var components = [];
+ if(nodeList.length > 0) {
+ var point;
+ for(var i=0; i<nodeList.length; ++i) {
+ point = this.parseGeometry.point.apply(this, [nodeList[i]]);
+ if(point) {
+ components.push(point);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPoint(components);
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a GML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ /**
+ * Two coordinate variations to consider:
+ * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
+ * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
+ */
+ var nodeList, coordString;
+ var coords = [];
+ var points = [];
+
+ // look for <gml:posList>
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
+ if(nodeList.length > 0) {
+ coordString = this.getChildValue(nodeList[0]);
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ var dim = parseInt(nodeList[0].getAttribute("dimension"));
+ var j, x, y, z;
+ for(var i=0; i<coords.length/dim; ++i) {
+ j = i * dim;
+ x = coords[j];
+ y = coords[j+1];
+ z = (dim == 2) ? null : coords[j+2];
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(x, y, z));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(y, x, z));
+ }
+ }
+ }
+
+ // look for <gml:coordinates>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ if(nodeList.length > 0) {
+ coordString = this.getChildValue(nodeList[0]);
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ for(var i=0; i<pointList.length; ++i) {
+ coords = pointList[i].split(",");
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(coords[1],
+ coords[0],
+ coords[2]));
+ }
+ }
+ }
+ }
+
+ var line = null;
+ if(points.length != 0) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ }
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.multilinestring
+ * Given a GML node representing a multilinestring geometry, create an
+ * OpenLayers multilinestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
+ */
+ multilinestring: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "LineString");
+ var components = [];
+ if(nodeList.length > 0) {
+ var line;
+ for(var i=0; i<nodeList.length; ++i) {
+ line = this.parseGeometry.linestring.apply(this,
+ [nodeList[i]]);
+ if(line) {
+ components.push(line);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiLineString(components);
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a GML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "LinearRing");
+ var components = [];
+ if(nodeList.length > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0; i<nodeList.length; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components.push(ring);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multipolygon
+ * Given a GML node representing a multipolygon geometry, create an
+ * OpenLayers multipolygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
+ */
+ multipolygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "Polygon");
+ var components = [];
+ if(nodeList.length > 0) {
+ var polygon;
+ for(var i=0; i<nodeList.length; ++i) {
+ polygon = this.parseGeometry.polygon.apply(this,
+ [nodeList[i]]);
+ if(polygon) {
+ components.push(polygon);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPolygon(components);
+ },
+
+ envelope: function(node) {
+ var components = [];
+ var coordString;
+ var envelope;
+
+ var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
+ if (lpoint.length > 0) {
+ var coords = [];
+
+ if(lpoint.length > 0) {
+ coordString = lpoint[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+ } else {
+ var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+ }
+ }
+
+ var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
+ if (upoint.length > 0) {
+ var coords = [];
+
+ if(upoint.length > 0) {
+ coordString = upoint[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+ } else {
+ var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+ }
+ }
+
+ if (lowerPoint && upperPoint) {
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+ components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
+ components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+ envelope = new OpenLayers.Geometry.Polygon([ring]);
+ }
+ return envelope;
+ },
+
+ /**
+ * Method: parseGeometry.box
+ * Given a GML node representing a box geometry, create an
+ * OpenLayers.Bounds.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds representing the box.
+ */
+ box: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ var coordString;
+ var coords, beginPoint = null, endPoint = null;
+ if (nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coords = coordString.split(" ");
+ if (coords.length == 2) {
+ beginPoint = coords[0].split(",");
+ endPoint = coords[1].split(",");
+ }
+ }
+ if (beginPoint !== null && endPoint !== null) {
+ return new OpenLayers.Bounds(parseFloat(beginPoint[0]),
+ parseFloat(beginPoint[1]),
+ parseFloat(endPoint[0]),
+ parseFloat(endPoint[1]) );
+ }
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+ // assume attributes are children of the first type 1 child
+ var childNode = node.firstChild;
+ var children, i, child, grandchildren, grandchild, name, value;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ // attributes are type 1 children with one type 3 child
+ children = childNode.childNodes;
+ for(i=0; i<children.length; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length == 1) {
+ grandchild = grandchildren[0];
+ if(grandchild.nodeType == 3 ||
+ grandchild.nodeType == 4) {
+ name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ value = grandchild.nodeValue.replace(
+ this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ } else {
+ // If child has no childNodes (grandchildren),
+ // set an attribute with null value.
+ // e.g. <prefix:fieldname/> becomes
+ // {fieldname: null}
+ attributes[child.nodeName.split(":").pop()] = null;
+ }
+ }
+ }
+ break;
+ }
+ childNode = childNode.nextSibling;
+ }
+ return attributes;
+ },
+
+ /**
+ * APIMethod: write
+ * Generate a GML document string given a list of features.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
+ * serialize into a string.
+ *
+ * Returns:
+ * {String} A string representing the GML document.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var gml = this.createElementNS("http://www.opengis.net/wfs",
+ "wfs:" + this.collectionName);
+ for(var i=0; i<features.length; i++) {
+ gml.appendChild(this.createFeatureXML(features[i]));
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
+ *
+ * Returns:
+ * {DOMElement} A node reprensting the feature in GML.
+ */
+ createFeatureXML: function(feature) {
+ var geometry = feature.geometry;
+ var geometryNode = this.buildGeometryNode(geometry);
+ var geomContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ this.geometryName);
+ geomContainer.appendChild(geometryNode);
+ var featureNode = this.createElementNS(this.gmlns,
+ "gml:" + this.featureName);
+ var featureContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ this.layerName);
+ var fid = feature.fid || feature.id;
+ featureContainer.setAttribute("fid", fid);
+ featureContainer.appendChild(geomContainer);
+ for(var attr in feature.attributes) {
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr.substring(attr.lastIndexOf(":") + 1);
+ var attrContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ nodename);
+ attrContainer.appendChild(attrText);
+ featureContainer.appendChild(attrContainer);
+ }
+ featureNode.appendChild(featureContainer);
+ return featureNode;
+ },
+
+ /**
+ * APIMethod: buildGeometryNode
+ */
+ buildGeometryNode: function(geometry) {
+ if (this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ return builder.apply(this, [geometry]);
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD retrieve the srs from layer
+ // srsName is non-standard, so not including it until it's right.
+ // gml.setAttribute("srsName",
+ // "http://www.opengis.net/gml/srs/epsg.xml#4326");
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a GML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML point node.
+ */
+ point: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:Point");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a GML multipoint.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multipoint node.
+ */
+ multipoint: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
+ var points = geometry.components;
+ var pointMember, pointGeom;
+ for(var i=0; i<points.length; i++) {
+ pointMember = this.createElementNS(this.gmlns,
+ "gml:pointMember");
+ pointGeom = this.buildGeometry.point.apply(this,
+ [points[i]]);
+ pointMember.appendChild(pointGeom);
+ gml.appendChild(pointMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a GML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML linestring node.
+ */
+ linestring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:LineString");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a GML
+ * multilinestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
+ * geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multilinestring node.
+ */
+ multilinestring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
+ var lines = geometry.components;
+ var lineMember, lineGeom;
+ for(var i=0; i<lines.length; ++i) {
+ lineMember = this.createElementNS(this.gmlns,
+ "gml:lineStringMember");
+ lineGeom = this.buildGeometry.linestring.apply(this,
+ [lines[i]]);
+ lineMember.appendChild(lineGeom);
+ gml.appendChild(lineMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a GML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML linearring node.
+ */
+ linearring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a GML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML polygon node.
+ */
+ polygon: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0; i<rings.length; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.gmlns,
+ "gml:" + type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ gml.appendChild(ringMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
+ * geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multipolygon node.
+ */
+ multipolygon: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
+ var polys = geometry.components;
+ var polyMember, polyGeom;
+ for(var i=0; i<polys.length; ++i) {
+ polyMember = this.createElementNS(this.gmlns,
+ "gml:polygonMember");
+ polyGeom = this.buildGeometry.polygon.apply(this,
+ [polys[i]]);
+ polyMember.appendChild(polyGeom);
+ gml.appendChild(polyMember);
+ }
+ return gml;
+
+ },
+
+ /**
+ * Method: buildGeometry.bounds
+ * Given an OpenLayers bounds, create a GML box.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
+ *
+ * Returns:
+ * {DOMElement} A GML box node.
+ */
+ bounds: function(bounds) {
+ var gml = this.createElementNS(this.gmlns, "gml:Box");
+ gml.appendChild(this.buildCoordinatesNode(bounds));
+ return gml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinates
+ * builds the coordinates XmlNode
+ * (code)
+ * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
+ * (end)
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {XmlNode} created xmlNode
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.gmlns,
+ "gml:coordinates");
+ coordinatesNode.setAttribute("decimal", ".");
+ coordinatesNode.setAttribute("cs", ",");
+ coordinatesNode.setAttribute("ts", " ");
+
+ var parts = [];
+
+ if(geometry instanceof OpenLayers.Bounds){
+ parts.push(geometry.left + "," + geometry.bottom);
+ parts.push(geometry.right + "," + geometry.top);
+ } else {
+ var points = (geometry.components) ? geometry.components : [geometry];
+ for(var i=0; i<points.length; i++) {
+ parts.push(points[i].x + "," + points[i].y);
+ }
+ }
+
+ var txtNode = this.createTextNode(parts.join(" "));
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML"
+});
+/* ======================================================================
+ OpenLayers/Format/GML/Base.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML.js
+ */
+
+/**
+ * Though required in the full build, if the GML format is excluded, we set
+ * the namespace here.
+ */
+if(!OpenLayers.Format.GML) {
+ OpenLayers.Format.GML = {};
+}
+
+/**
+ * Class: OpenLayers.Format.GML.Base
+ * Superclass for GML parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "gml",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * APIProperty: featureType
+ * {Array(String) or String} The local (without prefix) feature typeName(s).
+ */
+ featureType: null,
+
+ /**
+ * APIProperty: featureNS
+ * {String} The feature namespace. Must be set in the options at
+ * construction.
+ */
+ featureNS: null,
+
+ /**
+ * APIProperty: geometry
+ * {String} Name of geometry element. Defaults to "geometry". If null, it
+ * will be set on <read> when the first geometry is parsed.
+ */
+ geometryName: "geometry",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: srsName
+ * {String} URI for spatial reference system. This is optional for
+ * single part geometries and mandatory for collections and multis.
+ * If set, the srsName attribute will be written for all geometries.
+ * Default is null.
+ */
+ srsName: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: geometryTypes
+ * {Object} Maps OpenLayers geometry class names to GML element names.
+ * Use <setGeometryTypes> before accessing this property.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: singleFeatureType
+ * {Boolean} True if there is only 1 featureType, and not an array
+ * of featuretypes.
+ */
+ singleFeatureType: null,
+
+ /**
+ * Property: autoConfig
+ * {Boolean} Indicates if the format was configured without a <featureNS>,
+ * but auto-configured <featureNS> and <featureType> during read.
+ * Subclasses making use of <featureType> auto-configuration should make
+ * the first call to the <readNode> method (usually in the read method)
+ * with true as 3rd argument, so the auto-configured featureType can be
+ * reset and the format can be reused for subsequent reads with data from
+ * different featureTypes. Set to false after read if you want to keep the
+ * auto-configured values.
+ */
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g),
+ featureMember: (/^(.*:)?featureMembers?$/)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.GML.Base
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> constructor
+ * instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {Array(String) or String} Local (without prefix) feature
+ * typeName(s) (required for write).
+ * featureNS - {String} Feature namespace (required for write).
+ * geometryName - {String} Geometry element name (required for write).
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.setGeometryTypes();
+ if(options && options.featureNS) {
+ this.setNamespace("feature", options.featureNS);
+ }
+ this.singleFeatureType = !options || (typeof options.featureType === "string");
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} A gml:featureMember element, a gml:featureMembers
+ * element, or an element containing either of the above at any level.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var features = [];
+ this.readNode(data, {features: features}, true);
+ if(features.length == 0) {
+ // look for gml:featureMember elements
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMember"
+ );
+ if(elements.length) {
+ for(var i=0, len=elements.length; i<len; ++i) {
+ this.readNode(elements[i], {features: features}, true);
+ }
+ } else {
+ // look for gml:featureMembers elements (this is v3, but does no harm here)
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMembers"
+ );
+ if(elements.length) {
+ // there can be only one
+ this.readNode(elements[0], {features: features}, true);
+ }
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // on subsequent calls of format.read(), we want to reset auto-
+ // configured properties and auto-configure again.
+ if (first === true && this.autoConfig === true) {
+ this.featureType = null;
+ delete this.namespaceAlias[this.featureNS];
+ delete this.namespaces["feature"];
+ this.featureNS = null;
+ }
+ // featureType auto-configuration
+ if (!this.featureNS && (!(node.prefix in this.namespaces) &&
+ node.parentNode.namespaceURI == this.namespaces["gml"] &&
+ this.regExes.featureMember.test(node.parentNode.nodeName))) {
+ this.featureType = node.nodeName.split(":").pop();
+ this.setNamespace("feature", node.namespaceURI);
+ this.featureNS = node.namespaceURI;
+ this.autoConfig = true;
+ }
+ return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, obj]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": {
+ "_inherit": function(node, obj, container) {
+ // To be implemented by version specific parsers
+ },
+ "featureMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "featureMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "boundedBy": function(node, obj) {
+ var container = {};
+ this.readChildNodes(node, container);
+ if(container.components && container.components.length > 0) {
+ obj.bounds = container.components[0];
+ }
+ },
+ "Point": function(node, container) {
+ var obj = {points: []};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(obj.points[0]);
+ },
+ "coordinates": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ var coords;
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ if (this.xy) {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[0], coords[1], coords[2]
+ );
+ } else {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[1], coords[0], coords[2]
+ );
+ }
+ }
+ obj.points = points;
+ },
+ "coord": function(node, obj) {
+ var coord = {};
+ this.readChildNodes(node, coord);
+ if(!obj.points) {
+ obj.points = [];
+ }
+ obj.points.push(new OpenLayers.Geometry.Point(
+ coord.x, coord.y, coord.z
+ ));
+ },
+ "X": function(node, coord) {
+ coord.x = this.getChildValue(node);
+ },
+ "Y": function(node, coord) {
+ coord.y = this.getChildValue(node);
+ },
+ "Z": function(node, coord) {
+ coord.z = this.getChildValue(node);
+ },
+ "MultiPoint": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPoint(obj.components)
+ ];
+ },
+ "pointMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LineString": function(node, container) {
+ var obj = {};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.LineString(obj.points)
+ );
+ },
+ "MultiLineString": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiLineString(obj.components)
+ ];
+ },
+ "lineStringMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Polygon": function(node, container) {
+ var obj = {outer: null, inner: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ obj.inner.unshift(obj.outer);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.Polygon(obj.inner)
+ );
+ },
+ "LinearRing": function(node, obj) {
+ var container = {};
+ this.readers.gml._inherit.apply(this, [node, container]);
+ this.readChildNodes(node, container);
+ obj.components = [new OpenLayers.Geometry.LinearRing(
+ container.points
+ )];
+ },
+ "MultiPolygon": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPolygon(obj.components)
+ ];
+ },
+ "polygonMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "GeometryCollection": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.Collection(obj.components)
+ ];
+ },
+ "geometryMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ },
+ "feature": {
+ "*": function(node, obj) {
+ // The node can either be named like the featureType, or it
+ // can be a child of the feature:featureType. Children can be
+ // geometry or attributes.
+ var name;
+ var local = node.localName || node.nodeName.split(":").pop();
+ // Since an attribute can have the same name as the feature type
+ // we only want to read the node as a feature if the parent
+ // node can have feature nodes as children. In this case, the
+ // obj.features property is set.
+ if (obj.features) {
+ if (!this.singleFeatureType &&
+ (OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
+ name = "_typeName";
+ } else if(local === this.featureType) {
+ name = "_typeName";
+ }
+ } else {
+ // Assume attribute elements have one child node and that the child
+ // is a text node. Otherwise assume it is a geometry node.
+ if(node.childNodes.length == 0 ||
+ (node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
+ if(this.extractAttributes) {
+ name = "_attribute";
+ }
+ } else {
+ name = "_geometry";
+ }
+ }
+ if(name) {
+ this.readers.feature[name].apply(this, [node, obj]);
+ }
+ },
+ "_typeName": function(node, obj) {
+ var container = {components: [], attributes: {}};
+ this.readChildNodes(node, container);
+ // look for common gml namespaced elements
+ if(container.name) {
+ container.attributes.name = container.name;
+ }
+ var feature = new OpenLayers.Feature.Vector(
+ container.components[0], container.attributes
+ );
+ if (!this.singleFeatureType) {
+ feature.type = node.nodeName.split(":").pop();
+ feature.namespace = node.namespaceURI;
+ }
+ var fid = node.getAttribute("fid") ||
+ this.getAttributeNS(node, this.namespaces["gml"], "id");
+ if(fid) {
+ feature.fid = fid;
+ }
+ if(this.internalProjection && this.externalProjection &&
+ feature.geometry) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if(container.bounds) {
+ feature.bounds = container.bounds;
+ }
+ obj.features.push(feature);
+ },
+ "_geometry": function(node, obj) {
+ if (!this.geometryName) {
+ this.geometryName = node.nodeName.split(":").pop();
+ }
+ this.readChildNodes(node, obj);
+ },
+ "_attribute": function(node, obj) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var value = this.getChildValue(node);
+ obj.attributes[local] = value;
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ name = "featureMembers";
+ } else {
+ name = "featureMember";
+ }
+ var root = this.writeNode("gml:" + name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": {
+ "featureMember": function(feature) {
+ var node = this.createElementNSPlus("gml:featureMember");
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "MultiPoint": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPoint");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("pointMember", components[i], node);
+ }
+ return node;
+ },
+ "pointMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:pointMember");
+ this.writeNode("Point", geometry, node);
+ return node;
+ },
+ "MultiLineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiLineString");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("lineStringMember", components[i], node);
+ }
+ return node;
+ },
+ "lineStringMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:lineStringMember");
+ this.writeNode("LineString", geometry, node);
+ return node;
+ },
+ "MultiPolygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPolygon");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode(
+ "polygonMember", components[i], node
+ );
+ }
+ return node;
+ },
+ "polygonMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:polygonMember");
+ this.writeNode("Polygon", geometry, node);
+ return node;
+ },
+ "GeometryCollection": function(geometry) {
+ var node = this.createElementNSPlus("gml:GeometryCollection");
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ this.writeNode("geometryMember", geometry.components[i], node);
+ }
+ return node;
+ },
+ "geometryMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:geometryMember");
+ var child = this.writeNode("feature:_geometry", geometry);
+ node.appendChild(child.firstChild);
+ return node;
+ }
+ },
+ "feature": {
+ "_typeName": function(feature) {
+ var node = this.createElementNSPlus("feature:" + this.featureType, {
+ attributes: {fid: feature.fid}
+ });
+ if(feature.geometry) {
+ this.writeNode("feature:_geometry", feature.geometry, node);
+ }
+ for(var name in feature.attributes) {
+ var value = feature.attributes[name];
+ if(value != null) {
+ this.writeNode(
+ "feature:_attribute",
+ {name: name, value: value}, node
+ );
+ }
+ }
+ return node;
+ },
+ "_geometry": function(geometry) {
+ if(this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone().transform(
+ this.internalProjection, this.externalProjection
+ );
+ }
+ var node = this.createElementNSPlus(
+ "feature:" + this.geometryName
+ );
+ var type = this.geometryTypes[geometry.CLASS_NAME];
+ var child = this.writeNode("gml:" + type, geometry, node);
+ if(this.srsName) {
+ child.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ },
+ "_attribute": function(obj) {
+ return this.createElementNSPlus("feature:" + obj.name, {
+ value: obj.value
+ });
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(features) {
+ /**
+ * This is only here because GML2 only describes abstract
+ * feature collections. Typically, you would not be using
+ * the GML format to write wfs elements. This just provides
+ * some way to write out lists of features. GML3 defines the
+ * featureMembers element, so that is used by default instead.
+ */
+ var node = this.createElementNSPlus("wfs:FeatureCollection");
+ for(var i=0, len=features.length; i<len; ++i) {
+ this.writeNode("gml:featureMember", features[i], node);
+ }
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: setGeometryTypes
+ * Sets the <geometryTypes> mapping.
+ */
+ setGeometryTypes: function() {
+ this.geometryTypes = {
+ "OpenLayers.Geometry.Point": "Point",
+ "OpenLayers.Geometry.MultiPoint": "MultiPoint",
+ "OpenLayers.Geometry.LineString": "LineString",
+ "OpenLayers.Geometry.MultiLineString": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": "Polygon",
+ "OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
+ "OpenLayers.Geometry.Collection": "GeometryCollection"
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.Base"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GML/v3.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/Base.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML.v3
+ * Parses GML version 3.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.Base>
+ */
+OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version. The writers
+ * conform with the Simple Features Profile for GML.
+ */
+ schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",
+
+ /**
+ * Property: curve
+ * {Boolean} Write gml:Curve instead of gml:LineString elements. This also
+ * affects the elements in multi-part geometries. Default is false.
+ * To write gml:Curve elements instead of gml:LineString, set curve
+ * to true in the options to the contstructor (cannot be changed after
+ * instantiation).
+ */
+ curve: false,
+
+ /**
+ * Property: multiCurve
+ * {Boolean} Write gml:MultiCurve instead of gml:MultiLineString. Since
+ * the latter is deprecated in GML 3, the default is true. To write
+ * gml:MultiLineString instead of gml:MultiCurve, set multiCurve to
+ * false in the options to the constructor (cannot be changed after
+ * instantiation).
+ */
+ multiCurve: true,
+
+ /**
+ * Property: surface
+ * {Boolean} Write gml:Surface instead of gml:Polygon elements. This also
+ * affects the elements in multi-part geometries. Default is false.
+ * To write gml:Surface elements instead of gml:Polygon, set surface
+ * to true in the options to the contstructor (cannot be changed after
+ * instantiation).
+ */
+ surface: false,
+
+ /**
+ * Property: multiSurface
+ * {Boolean} Write gml:multiSurface instead of gml:MultiPolygon. Since
+ * the latter is deprecated in GML 3, the default is true. To write
+ * gml:MultiPolygon instead of gml:multiSurface, set multiSurface to
+ * false in the options to the constructor (cannot be changed after
+ * instantiation).
+ */
+ multiSurface: true,
+
+ /**
+ * Constructor: OpenLayers.Format.GML.v3
+ * Create a parser for GML v3.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required).
+ * geometryName - {String} Geometry element name.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "_inherit": function(node, obj, container) {
+ // SRSReferenceGroup attributes
+ var dim = parseInt(node.getAttribute("srsDimension"), 10) ||
+ (container && container.srsDimension);
+ if (dim) {
+ obj.srsDimension = dim;
+ }
+ },
+ "featureMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Curve": function(node, container) {
+ var obj = {points: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.LineString(obj.points)
+ );
+ },
+ "segments": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LineStringSegment": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ if(obj.points) {
+ Array.prototype.push.apply(container.points, obj.points);
+ }
+ },
+ "pos": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ var coords = str.split(this.regExes.splitSpace);
+ var point;
+ if(this.xy) {
+ point = new OpenLayers.Geometry.Point(
+ coords[0], coords[1], coords[2]
+ );
+ } else {
+ point = new OpenLayers.Geometry.Point(
+ coords[1], coords[0], coords[2]
+ );
+ }
+ obj.points = [point];
+ },
+ "posList": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ var coords = str.split(this.regExes.splitSpace);
+ // The "dimension" attribute is from the GML 3.0.1 spec.
+ var dim = obj.srsDimension ||
+ parseInt(node.getAttribute("srsDimension") || node.getAttribute("dimension"), 10) || 2;
+ var j, x, y, z;
+ var numPoints = coords.length / dim;
+ var points = new Array(numPoints);
+ for(var i=0, len=coords.length; i<len; i += dim) {
+ x = coords[i];
+ y = coords[i+1];
+ z = (dim == 2) ? undefined : coords[i+2];
+ if (this.xy) {
+ points[i/dim] = new OpenLayers.Geometry.Point(x, y, z);
+ } else {
+ points[i/dim] = new OpenLayers.Geometry.Point(y, x, z);
+ }
+ }
+ obj.points = points;
+ },
+ "Surface": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "patches": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "PolygonPatch": function(node, obj) {
+ this.readers.gml.Polygon.apply(this, [node, obj]);
+ },
+ "exterior": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.outer = obj.components[0];
+ },
+ "interior": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.inner.push(obj.components[0]);
+ },
+ "MultiCurve": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(obj.components.length > 0) {
+ container.components = [
+ new OpenLayers.Geometry.MultiLineString(obj.components)
+ ];
+ }
+ },
+ "curveMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "MultiSurface": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(obj.components.length > 0) {
+ container.components = [
+ new OpenLayers.Geometry.MultiPolygon(obj.components)
+ ];
+ }
+ },
+ "surfaceMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "surfaceMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "pointMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "lineStringMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "polygonMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "geometryMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Envelope": function(node, container) {
+ var obj = {points: new Array(2)};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ var min = obj.points[0];
+ var max = obj.points[1];
+ container.components.push(
+ new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
+ );
+ },
+ "lowerCorner": function(node, container) {
+ var obj = {};
+ this.readers.gml.pos.apply(this, [node, obj]);
+ container.points[0] = obj.points[0];
+ },
+ "upperCorner": function(node, container) {
+ var obj = {};
+ this.readers.gml.pos.apply(this, [node, obj]);
+ container.points[1] = obj.points[0];
+ }
+ }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ name = "featureMembers";
+ } else {
+ name = "featureMember";
+ }
+ var root = this.writeNode("gml:" + name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "featureMembers": function(features) {
+ var node = this.createElementNSPlus("gml:featureMembers");
+ for(var i=0, len=features.length; i<len; ++i) {
+ this.writeNode("feature:_typeName", features[i], node);
+ }
+ return node;
+ },
+ "Point": function(geometry) {
+ var node = this.createElementNSPlus("gml:Point");
+ this.writeNode("pos", geometry, node);
+ return node;
+ },
+ "pos": function(point) {
+ // only 2d for simple features profile
+ var pos = (this.xy) ?
+ (point.x + " " + point.y) : (point.y + " " + point.x);
+ return this.createElementNSPlus("gml:pos", {
+ value: pos
+ });
+ },
+ "LineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineString");
+ this.writeNode("posList", geometry.components, node);
+ return node;
+ },
+ "Curve": function(geometry) {
+ var node = this.createElementNSPlus("gml:Curve");
+ this.writeNode("segments", geometry, node);
+ return node;
+ },
+ "segments": function(geometry) {
+ var node = this.createElementNSPlus("gml:segments");
+ this.writeNode("LineStringSegment", geometry, node);
+ return node;
+ },
+ "LineStringSegment": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineStringSegment");
+ this.writeNode("posList", geometry.components, node);
+ return node;
+ },
+ "posList": function(points) {
+ // only 2d for simple features profile
+ var len = points.length;
+ var parts = new Array(len);
+ var point;
+ for(var i=0; i<len; ++i) {
+ point = points[i];
+ if(this.xy) {
+ parts[i] = point.x + " " + point.y;
+ } else {
+ parts[i] = point.y + " " + point.x;
+ }
+ }
+ return this.createElementNSPlus("gml:posList", {
+ value: parts.join(" ")
+ });
+ },
+ "Surface": function(geometry) {
+ var node = this.createElementNSPlus("gml:Surface");
+ this.writeNode("patches", geometry, node);
+ return node;
+ },
+ "patches": function(geometry) {
+ var node = this.createElementNSPlus("gml:patches");
+ this.writeNode("PolygonPatch", geometry, node);
+ return node;
+ },
+ "PolygonPatch": function(geometry) {
+ var node = this.createElementNSPlus("gml:PolygonPatch", {
+ attributes: {interpolation: "planar"}
+ });
+ this.writeNode("exterior", geometry.components[0], node);
+ for(var i=1, len=geometry.components.length; i<len; ++i) {
+ this.writeNode(
+ "interior", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "Polygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:Polygon");
+ this.writeNode("exterior", geometry.components[0], node);
+ for(var i=1, len=geometry.components.length; i<len; ++i) {
+ this.writeNode(
+ "interior", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "exterior": function(ring) {
+ var node = this.createElementNSPlus("gml:exterior");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "interior": function(ring) {
+ var node = this.createElementNSPlus("gml:interior");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "LinearRing": function(ring) {
+ var node = this.createElementNSPlus("gml:LinearRing");
+ this.writeNode("posList", ring.components, node);
+ return node;
+ },
+ "MultiCurve": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiCurve");
+ var components = geometry.components || [geometry];
+ for(var i=0, len=components.length; i<len; ++i) {
+ this.writeNode("curveMember", components[i], node);
+ }
+ return node;
+ },
+ "curveMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:curveMember");
+ if(this.curve) {
+ this.writeNode("Curve", geometry, node);
+ } else {
+ this.writeNode("LineString", geometry, node);
+ }
+ return node;
+ },
+ "MultiSurface": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiSurface");
+ var components = geometry.components || [geometry];
+ for(var i=0, len=components.length; i<len; ++i) {
+ this.writeNode("surfaceMember", components[i], node);
+ }
+ return node;
+ },
+ "surfaceMember": function(polygon) {
+ var node = this.createElementNSPlus("gml:surfaceMember");
+ if(this.surface) {
+ this.writeNode("Surface", polygon, node);
+ } else {
+ this.writeNode("Polygon", polygon, node);
+ }
+ return node;
+ },
+ "Envelope": function(bounds) {
+ var node = this.createElementNSPlus("gml:Envelope");
+ this.writeNode("lowerCorner", bounds, node);
+ this.writeNode("upperCorner", bounds, node);
+ // srsName attribute is required for gml:Envelope
+ if(this.srsName) {
+ node.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ },
+ "lowerCorner": function(bounds) {
+ // only 2d for simple features profile
+ var pos = (this.xy) ?
+ (bounds.left + " " + bounds.bottom) :
+ (bounds.bottom + " " + bounds.left);
+ return this.createElementNSPlus("gml:lowerCorner", {
+ value: pos
+ });
+ },
+ "upperCorner": function(bounds) {
+ // only 2d for simple features profile
+ var pos = (this.xy) ?
+ (bounds.right + " " + bounds.top) :
+ (bounds.top + " " + bounds.right);
+ return this.createElementNSPlus("gml:upperCorner", {
+ value: pos
+ });
+ }
+ }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
+ },
+
+ /**
+ * Method: setGeometryTypes
+ * Sets the <geometryTypes> mapping.
+ */
+ setGeometryTypes: function() {
+ this.geometryTypes = {
+ "OpenLayers.Geometry.Point": "Point",
+ "OpenLayers.Geometry.MultiPoint": "MultiPoint",
+ "OpenLayers.Geometry.LineString": (this.curve === true) ? "Curve": "LineString",
+ "OpenLayers.Geometry.MultiLineString": (this.multiCurve === false) ? "MultiLineString" : "MultiCurve",
+ "OpenLayers.Geometry.Polygon": (this.surface === true) ? "Surface" : "Polygon",
+ "OpenLayers.Geometry.MultiPolygon": (this.multiSurface === false) ? "MultiPolygon" : "MultiSurface",
+ "OpenLayers.Geometry.Collection": "GeometryCollection"
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.v3"
+
+});
+/* ======================================================================
+ OpenLayers/Format/Filter/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Filter/v1.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_1_0
+ * Write ogc:Filter version 1.1.0.
+ *
+ * Differences from the v1.0.0 parser:
+ * - uses GML v3 instead of GML v2
+ * - reads matchCase attribute on ogc:PropertyIsEqual and
+ * ogc:PropertyIsNotEqual elements.
+ * - writes matchCase attribute from comparison filters of type EQUAL_TO,
+ * NOT_EQUAL_TO and LIKE.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.v3>
+ * - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.GML.v3, OpenLayers.Format.Filter.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1.0
+ */
+ VERSION: "1.1.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/ogc/filter/1.1.0/filter.xsd
+ */
+ schemaLocation: "http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1_1_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.v3.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(node, obj) {
+ var matchCase = node.getAttribute("matchCase");
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ matchCase: !(matchCase === "false" || matchCase === "0")
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsNotEqualTo": function(node, obj) {
+ var matchCase = node.getAttribute("matchCase");
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: !(matchCase === "false" || matchCase === "0")
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLike": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE
+ });
+ this.readChildNodes(node, filter);
+ var wildCard = node.getAttribute("wildCard");
+ var singleChar = node.getAttribute("singleChar");
+ var esc = node.getAttribute("escapeChar");
+ filter.value2regex(wildCard, singleChar, esc);
+ obj.filters.push(filter);
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsEqualTo", {
+ attributes: {matchCase: filter.matchCase}
+ });
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsNotEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo", {
+ attributes: {matchCase: filter.matchCase}
+ });
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLike": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+ attributes: {
+ matchCase: filter.matchCase,
+ wildCard: "*", singleChar: ".", escapeChar: "!"
+ }
+ });
+ // no ogc:expression handling for now
+ this.writeNode("PropertyName", filter, node);
+ // convert regex string to ogc string
+ this.writeNode("Literal", filter.regex2value(), node);
+ return node;
+ },
+ "BBOX": function(filter) {
+ var node = this.createElementNSPlus("ogc:BBOX");
+ // PropertyName is optional in 1.1.0
+ filter.property && this.writeNode("PropertyName", filter, node);
+ var box = this.writeNode("gml:Envelope", filter.value);
+ if(filter.projection) {
+ box.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(box);
+ return node;
+ },
+ "SortBy": function(sortProperties) {
+ var node = this.createElementNSPlus("ogc:SortBy");
+ for (var i=0,l=sortProperties.length;i<l;i++) {
+ this.writeNode(
+ "ogc:SortProperty",
+ sortProperties[i],
+ node
+ );
+ }
+ return node;
+ },
+ "SortProperty": function(sortProperty) {
+ var node = this.createElementNSPlus("ogc:SortProperty");
+ this.writeNode(
+ "ogc:PropertyName",
+ sortProperty,
+ node
+ );
+ this.writeNode(
+ "ogc:SortOrder",
+ (sortProperty.order == 'DESC') ? 'DESC' : 'ASC',
+ node
+ );
+ return node;
+ },
+ "SortOrder": function(value) {
+ var node = this.createElementNSPlus("ogc:SortOrder", {
+ value: value
+ });
+ return node;
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"]
+ },
+
+ /**
+ * Method: writeSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter.Spatial>} The filter.
+ * name - {String} Name of the generated XML element.
+ *
+ * Returns:
+ * {DOMElement} The created XML element.
+ */
+ writeSpatial: function(filter, name) {
+ var node = this.createElementNSPlus("ogc:"+name);
+ this.writeNode("PropertyName", filter, node);
+ if(filter.value instanceof OpenLayers.Filter.Function) {
+ this.writeNode("Function", filter.value, node);
+ } else {
+ var child;
+ if(filter.value instanceof OpenLayers.Geometry) {
+ child = this.writeNode("feature:_geometry", filter.value).firstChild;
+ } else {
+ child = this.writeNode("gml:Envelope", filter.value);
+ }
+ if(filter.projection) {
+ child.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(child);
+ }
+ return node;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1_1_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/OWSCommon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon
+ * Read OWSCommon. Create a new instance with the <OpenLayers.Format.OWSCommon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.OWSCommon = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Format.OWSCommon
+ * Create a new parser for OWSCommon.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version = this.version;
+ if(!version) {
+ // remember version does not correspond to the OWS version
+ // it corresponds to the WMS/WFS/WCS etc. request version
+ var uri = root.getAttribute("xmlns:ows");
+ // the above will fail if the namespace prefix is different than
+ // ows and if the namespace is declared on a different element
+ if (uri && uri.substring(uri.lastIndexOf("/")+1) === "1.1") {
+ version ="1.1.0";
+ }
+ if(!version) {
+ version = this.defaultVersion;
+ }
+ }
+ return version;
+ },
+
+ /**
+ * APIMethod: read
+ * Read an OWSCommon document and return an object.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the structure of the document.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon"
+});
+/* ======================================================================
+ OpenLayers/Format/OWSCommon/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/OWSCommon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon.v1
+ * Common readers and writers for OWSCommon v1.X formats
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OWSCommon.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An OWSCommon document element.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the OWSCommon document.
+ */
+ read: function(data, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var ows = {};
+ this.readChildNodes(data, ows);
+ return ows;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ows": {
+ "Exception": function(node, exceptionReport) {
+ var exception = {
+ code: node.getAttribute('exceptionCode'),
+ locator: node.getAttribute('locator'),
+ texts: []
+ };
+ exceptionReport.exceptions.push(exception);
+ this.readChildNodes(node, exception);
+ },
+ "ExceptionText": function(node, exception) {
+ var text = this.getChildValue(node);
+ exception.texts.push(text);
+ },
+ "ServiceIdentification": function(node, obj) {
+ obj.serviceIdentification = {};
+ this.readChildNodes(node, obj.serviceIdentification);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, serviceIdentification) {
+ serviceIdentification["abstract"] = this.getChildValue(node);
+ },
+ "Keywords": function(node, serviceIdentification) {
+ serviceIdentification.keywords = {};
+ this.readChildNodes(node, serviceIdentification.keywords);
+ },
+ "Keyword": function(node, keywords) {
+ keywords[this.getChildValue(node)] = true;
+ },
+ "ServiceType": function(node, serviceIdentification) {
+ serviceIdentification.serviceType = {
+ codeSpace: node.getAttribute('codeSpace'),
+ value: this.getChildValue(node)};
+ },
+ "ServiceTypeVersion": function(node, serviceIdentification) {
+ serviceIdentification.serviceTypeVersion = this.getChildValue(node);
+ },
+ "Fees": function(node, serviceIdentification) {
+ serviceIdentification.fees = this.getChildValue(node);
+ },
+ "AccessConstraints": function(node, serviceIdentification) {
+ serviceIdentification.accessConstraints =
+ this.getChildValue(node);
+ },
+ "ServiceProvider": function(node, obj) {
+ obj.serviceProvider = {};
+ this.readChildNodes(node, obj.serviceProvider);
+ },
+ "ProviderName": function(node, serviceProvider) {
+ serviceProvider.providerName = this.getChildValue(node);
+ },
+ "ProviderSite": function(node, serviceProvider) {
+ serviceProvider.providerSite = this.getAttributeNS(node,
+ this.namespaces.xlink, "href");
+ },
+ "ServiceContact": function(node, serviceProvider) {
+ serviceProvider.serviceContact = {};
+ this.readChildNodes(node, serviceProvider.serviceContact);
+ },
+ "IndividualName": function(node, serviceContact) {
+ serviceContact.individualName = this.getChildValue(node);
+ },
+ "PositionName": function(node, serviceContact) {
+ serviceContact.positionName = this.getChildValue(node);
+ },
+ "ContactInfo": function(node, serviceContact) {
+ serviceContact.contactInfo = {};
+ this.readChildNodes(node, serviceContact.contactInfo);
+ },
+ "Phone": function(node, contactInfo) {
+ contactInfo.phone = {};
+ this.readChildNodes(node, contactInfo.phone);
+ },
+ "Voice": function(node, phone) {
+ phone.voice = this.getChildValue(node);
+ },
+ "Address": function(node, contactInfo) {
+ contactInfo.address = {};
+ this.readChildNodes(node, contactInfo.address);
+ },
+ "DeliveryPoint": function(node, address) {
+ address.deliveryPoint = this.getChildValue(node);
+ },
+ "City": function(node, address) {
+ address.city = this.getChildValue(node);
+ },
+ "AdministrativeArea": function(node, address) {
+ address.administrativeArea = this.getChildValue(node);
+ },
+ "PostalCode": function(node, address) {
+ address.postalCode = this.getChildValue(node);
+ },
+ "Country": function(node, address) {
+ address.country = this.getChildValue(node);
+ },
+ "ElectronicMailAddress": function(node, address) {
+ address.electronicMailAddress = this.getChildValue(node);
+ },
+ "Role": function(node, serviceContact) {
+ serviceContact.role = this.getChildValue(node);
+ },
+ "OperationsMetadata": function(node, obj) {
+ obj.operationsMetadata = {};
+ this.readChildNodes(node, obj.operationsMetadata);
+ },
+ "Operation": function(node, operationsMetadata) {
+ var name = node.getAttribute("name");
+ operationsMetadata[name] = {};
+ this.readChildNodes(node, operationsMetadata[name]);
+ },
+ "DCP": function(node, operation) {
+ operation.dcp = {};
+ this.readChildNodes(node, operation.dcp);
+ },
+ "HTTP": function(node, dcp) {
+ dcp.http = {};
+ this.readChildNodes(node, dcp.http);
+ },
+ "Get": function(node, http) {
+ if (!http.get) {
+ http.get = [];
+ }
+ var obj = {
+ url: this.getAttributeNS(node, this.namespaces.xlink, "href")
+ };
+ this.readChildNodes(node, obj);
+ http.get.push(obj);
+ },
+ "Post": function(node, http) {
+ if (!http.post) {
+ http.post = [];
+ }
+ var obj = {
+ url: this.getAttributeNS(node, this.namespaces.xlink, "href")
+ };
+ this.readChildNodes(node, obj);
+ http.post.push(obj);
+ },
+ "Parameter": function(node, operation) {
+ if (!operation.parameters) {
+ operation.parameters = {};
+ }
+ var name = node.getAttribute("name");
+ operation.parameters[name] = {};
+ this.readChildNodes(node, operation.parameters[name]);
+ },
+ "Constraint": function(node, obj) {
+ if (!obj.constraints) {
+ obj.constraints = {};
+ }
+ var name = node.getAttribute("name");
+ obj.constraints[name] = {};
+ this.readChildNodes(node, obj.constraints[name]);
+ },
+ "Value": function(node, allowedValues) {
+ allowedValues[this.getChildValue(node)] = true;
+ },
+ "OutputFormat": function(node, obj) {
+ obj.formats.push({value: this.getChildValue(node)});
+ this.readChildNodes(node, obj);
+ },
+ "WGS84BoundingBox": function(node, obj) {
+ var boundingBox = {};
+ boundingBox.crs = node.getAttribute("crs");
+ if (obj.BoundingBox) {
+ obj.BoundingBox.push(boundingBox);
+ } else {
+ obj.projection = boundingBox.crs;
+ boundingBox = obj;
+ }
+ this.readChildNodes(node, boundingBox);
+ },
+ "BoundingBox": function(node, obj) {
+ // FIXME: We consider that BoundingBox is the same as WGS84BoundingBox
+ // LowerCorner = "min_x min_y"
+ // UpperCorner = "max_x max_y"
+ // It should normally depend on the projection
+ this.readers['ows']['WGS84BoundingBox'].apply(this, [node, obj]);
+ },
+ "LowerCorner": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, "");
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ obj.left = pointList[0];
+ obj.bottom = pointList[1];
+ },
+ "UpperCorner": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, "");
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ obj.right = pointList[0];
+ obj.top = pointList[1];
+ obj.bounds = new OpenLayers.Bounds(obj.left, obj.bottom,
+ obj.right, obj.top);
+ delete obj.left;
+ delete obj.bottom;
+ delete obj.right;
+ delete obj.top;
+ },
+ "Language": function(node, obj) {
+ obj.language = this.getChildValue(node);
+ }
+ }
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ows": {
+ "BoundingBox": function(options, nodeName) {
+ var node = this.createElementNSPlus(nodeName || "ows:BoundingBox", {
+ attributes: {
+ crs: options.projection
+ }
+ });
+ this.writeNode("ows:LowerCorner", options, node);
+ this.writeNode("ows:UpperCorner", options, node);
+ return node;
+ },
+ "LowerCorner": function(options) {
+ var node = this.createElementNSPlus("ows:LowerCorner", {
+ value: options.bounds.left + " " + options.bounds.bottom });
+ return node;
+ },
+ "UpperCorner": function(options) {
+ var node = this.createElementNSPlus("ows:UpperCorner", {
+ value: options.bounds.right + " " + options.bounds.top });
+ return node;
+ },
+ "Identifier": function(identifier) {
+ var node = this.createElementNSPlus("ows:Identifier", {
+ value: identifier });
+ return node;
+ },
+ "Title": function(title) {
+ var node = this.createElementNSPlus("ows:Title", {
+ value: title });
+ return node;
+ },
+ "Abstract": function(abstractValue) {
+ var node = this.createElementNSPlus("ows:Abstract", {
+ value: abstractValue });
+ return node;
+ },
+ "OutputFormat": function(format) {
+ var node = this.createElementNSPlus("ows:OutputFormat", {
+ value: format });
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/OWSCommon/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/OWSCommon/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon.v1_0_0
+ * Parser for OWS Common version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.OWSCommon.v1>
+ */
+OpenLayers.Format.OWSCommon.v1_0_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ows": OpenLayers.Util.applyDefaults({
+ "ExceptionReport": function(node, obj) {
+ obj.success = false;
+ obj.exceptionReport = {
+ version: node.getAttribute('version'),
+ language: node.getAttribute('language'),
+ exceptions: []
+ };
+ this.readChildNodes(node, obj.exceptionReport);
+ }
+ }, OpenLayers.Format.OWSCommon.v1.prototype.readers.ows)
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ows": OpenLayers.Format.OWSCommon.v1.prototype.writers.ows
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WFST/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFST/v1.js
+ * @requires OpenLayers/Format/Filter/v1_1_0.js
+ * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1_1_0
+ * A format for creating WFS v1.1.0 transactions. Create a new instance with the
+ * <OpenLayers.Format.WFST.v1_1_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_1_0>
+ * - <OpenLayers.Format.WFST.v1>
+ */
+OpenLayers.Format.WFST.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.Filter.v1_1_0, OpenLayers.Format.WFST.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.1.0",
+
+ /**
+ * Property: schemaLocations
+ * {Object} Properties are namespace aliases, values are schema locations.
+ */
+ schemaLocations: {
+ "wfs": "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1_1_0
+ * A class for parsing and generating WFS v1.1.0 transactions.
+ *
+ * To read additional information like hit count (numberOfFeatures) from
+ * the FeatureCollection, call the <OpenLayers.Format.WFST.v1.read> method
+ * with {output: "object"} as 2nd argument. Note that it is possible to
+ * just request the hit count from a WFS 1.1.0 server with the
+ * resultType="hits" request parameter.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // Not the superclass, only the mixin classes inherit from
+ // Format.GML.v3. We need this because we don't want to get readNode
+ // from the superclass's superclass, which is OpenLayers.Format.XML.
+ return OpenLayers.Format.GML.v3.prototype.readNode.apply(this, arguments);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "FeatureCollection": function(node, obj) {
+ obj.numberOfFeatures = parseInt(node.getAttribute(
+ "numberOfFeatures"));
+ OpenLayers.Format.WFST.v1.prototype.readers["wfs"]["FeatureCollection"].apply(
+ this, arguments);
+ },
+ "TransactionResponse": function(node, obj) {
+ obj.insertIds = [];
+ obj.success = false;
+ this.readChildNodes(node, obj);
+ },
+ "TransactionSummary": function(node, obj) {
+ // this is a limited test of success
+ obj.success = true;
+ },
+ "InsertResults": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Feature": function(node, container) {
+ var obj = {fids: []};
+ this.readChildNodes(node, obj);
+ container.insertIds.push(obj.fids[0]);
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.readers["ogc"],
+ "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "GetFeature": function(options) {
+ var node = OpenLayers.Format.WFST.v1.prototype.writers["wfs"]["GetFeature"].apply(this, arguments);
+ options && this.setAttributes(node, {
+ resultType: options.resultType,
+ startIndex: options.startIndex,
+ count: options.count
+ });
+ return node;
+ },
+ "Query": function(options) {
+ options = OpenLayers.Util.extend({
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ featureType: this.featureType,
+ srsName: this.srsName
+ }, options);
+ var prefix = options.featurePrefix;
+ var node = this.createElementNSPlus("wfs:Query", {
+ attributes: {
+ typeName: (prefix ? prefix + ":" : "") +
+ options.featureType,
+ srsName: options.srsName
+ }
+ });
+ if(options.featureNS) {
+ node.setAttribute("xmlns:" + prefix, options.featureNS);
+ }
+ if(options.propertyNames) {
+ for(var i=0,len = options.propertyNames.length; i<len; i++) {
+ this.writeNode(
+ "wfs:PropertyName",
+ {property: options.propertyNames[i]},
+ node
+ );
+ }
+ }
+ if(options.filter) {
+ OpenLayers.Format.WFST.v1_1_0.prototype.setFilterProperty.call(this, options.filter);
+ this.writeNode("ogc:Filter", options.filter, node);
+ }
+ return node;
+ },
+ "PropertyName": function(obj) {
+ return this.createElementNSPlus("wfs:PropertyName", {
+ value: obj.property
+ });
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1_1_0"
+});
+/* ======================================================================
+ OpenLayers/Protocol.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class. Not to be instantiated directly. Use
+ * one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} The format used by this protocol.
+ */
+ format: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the protocol can set autoDestroy to false
+ * to fully control when the protocol is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Property: defaultFilter
+ * {<OpenLayers.Filter>} Optional default filter to read requests
+ */
+ defaultFilter: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol
+ * Abstract class for vector protocols. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ options = options || {};
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * Method: mergeWithDefaultFilter
+ * Merge filter passed to the read method with the default one
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ mergeWithDefaultFilter: function(filter) {
+ var merged;
+ if (filter && this.defaultFilter) {
+ merged = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.defaultFilter, filter]
+ });
+ } else {
+ merged = filter || this.defaultFilter || undefined;
+ }
+ return merged;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.options = null;
+ this.format = null;
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ read: function(options) {
+ options = options || {};
+ options.filter = this.mergeWithDefaultFilter(options.filter);
+ },
+
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ create: function() {
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ update: function() {
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ "delete": function() {
+ },
+
+ /**
+ * APIMethod: commit
+ * Go over the features and for each take action
+ * based on the feature state. Possible actions are create,
+ * update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Object whose possible keys are "create", "update",
+ * "delete", "callback" and "scope", the values referenced by the
+ * first three are objects as passed to the "create", "update", and
+ * "delete" methods, the value referenced by the "callback" key is
+ * a function which is called when the commit operation is complete
+ * using the scope referenced by the "scope" key.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Protocol.Response>})} An array of
+ * <OpenLayers.Protocol.Response> objects.
+ */
+ commit: function() {
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ },
+
+ /**
+ * Method: createCallback
+ * Returns a function that applies the given public method with resp and
+ * options arguments.
+ *
+ * Parameters:
+ * method - {Function} The method to be applied by the callback.
+ * response - {<OpenLayers.Protocol.Response>} The protocol response object.
+ * options - {Object} Options sent to the protocol method
+ */
+ createCallback: function(method, response, options) {
+ return OpenLayers.Function.bind(function() {
+ method.apply(this, [response, options]);
+ }, this);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol"
+});
+
+/**
+ * Class: OpenLayers.Protocol.Response
+ * Protocols return Response objects to their users.
+ */
+OpenLayers.Protocol.Response = OpenLayers.Class({
+ /**
+ * Property: code
+ * {Number} - OpenLayers.Protocol.Response.SUCCESS or
+ * OpenLayers.Protocol.Response.FAILURE
+ */
+ code: null,
+
+ /**
+ * Property: requestType
+ * {String} The type of request this response corresponds to. Either
+ * "create", "read", "update" or "delete".
+ */
+ requestType: null,
+
+ /**
+ * Property: last
+ * {Boolean} - true if this is the last response expected in a commit,
+ * false otherwise, defaults to true.
+ */
+ last: true,
+
+ /**
+ * Property: features
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ features: null,
+
+ /**
+ * Property: data
+ * {Object}
+ * The data returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ data: null,
+
+ /**
+ * Property: reqFeatures
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features provided by the user and placed in the request by the
+ * protocol.
+ */
+ reqFeatures: null,
+
+ /**
+ * Property: priv
+ */
+ priv: null,
+
+ /**
+ * Property: error
+ * {Object} The error object in case a service exception was encountered.
+ */
+ error: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Response
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: success
+ *
+ * Returns:
+ * {Boolean} - true on success, false otherwise
+ */
+ success: function() {
+ return this.code > 0;
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
+/* ======================================================================
+ OpenLayers/Format/JSON.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * Note:
+ * This work draws heavily from the public domain JSON serializer/deserializer
+ * at http://www.json.org/json.js. Rewritten so that it doesn't modify
+ * basic data prototypes.
+ */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.JSON
+ * A parser to read/write JSON safely. Create a new instance with the
+ * <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: indent
+ * {String} For "pretty" printing, the indent string will be used once for
+ * each indentation level.
+ */
+ indent: " ",
+
+ /**
+ * APIProperty: space
+ * {String} For "pretty" printing, the space string will be used after
+ * the ":" separating a name/value pair.
+ */
+ space: " ",
+
+ /**
+ * APIProperty: newline
+ * {String} For "pretty" printing, the newline string will be used at the
+ * end of each name/value pair or array item.
+ */
+ newline: "\n",
+
+ /**
+ * Property: level
+ * {Integer} For "pretty" printing, this is incremented/decremented during
+ * serialization.
+ */
+ level: 0,
+
+ /**
+ * Property: pretty
+ * {Boolean} Serialize with extra whitespace for structure. This is set
+ * by the <write> method.
+ */
+ pretty: false,
+
+ /**
+ * Property: nativeJSON
+ * {Boolean} Does the browser support native json?
+ */
+ nativeJSON: (function() {
+ return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function");
+ })(),
+
+ /**
+ * Constructor: OpenLayers.Format.JSON
+ * Create a new parser for JSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a json string.
+ *
+ * Parameters:
+ * json - {String} A JSON string
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} An object, array, string, or number .
+ */
+ read: function(json, filter) {
+ var object;
+ if (this.nativeJSON) {
+ object = JSON.parse(json, filter);
+ } else try {
+ /**
+ * Parsing happens in three stages. In the first stage, we run the
+ * text against a regular expression which looks for non-JSON
+ * characters. We are especially concerned with '()' and 'new'
+ * because they can cause invocation, and '=' because it can
+ * cause mutation. But just to be safe, we will reject all
+ * unexpected characters.
+ */
+ if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+ /**
+ * In the second stage we use the eval function to compile the
+ * text into a JavaScript structure. The '{' operator is
+ * subject to a syntactic ambiguity in JavaScript - it can
+ * begin a block or an object literal. We wrap the text in
+ * parens to eliminate the ambiguity.
+ */
+ object = eval('(' + json + ')');
+
+ /**
+ * In the optional third stage, we recursively walk the new
+ * structure, passing each name/value pair to a filter
+ * function for possible transformation.
+ */
+ if(typeof filter === 'function') {
+ function walk(k, v) {
+ if(v && typeof v === 'object') {
+ for(var i in v) {
+ if(v.hasOwnProperty(i)) {
+ v[i] = walk(i, v[i]);
+ }
+ }
+ }
+ return filter(k, v);
+ }
+ object = walk('', object);
+ }
+ }
+ } catch(e) {
+ // Fall through if the regexp test fails.
+ }
+
+ if(this.keepData) {
+ this.data = object;
+ }
+
+ return object;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize an object into a JSON string.
+ *
+ * Parameters:
+ * value - {String} The object, array, string, number, boolean or date
+ * to be serialized.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The JSON string representation of the input value.
+ */
+ write: function(value, pretty) {
+ this.pretty = !!pretty;
+ var json = null;
+ var type = typeof value;
+ if(this.serialize[type]) {
+ try {
+ json = (!this.pretty && this.nativeJSON) ?
+ JSON.stringify(value) :
+ this.serialize[type].apply(this, [value]);
+ } catch(err) {
+ OpenLayers.Console.error("Trouble serializing: " + err);
+ }
+ }
+ return json;
+ },
+
+ /**
+ * Method: writeIndent
+ * Output an indentation string depending on the indentation level.
+ *
+ * Returns:
+ * {String} An appropriate indentation string.
+ */
+ writeIndent: function() {
+ var pieces = [];
+ if(this.pretty) {
+ for(var i=0; i<this.level; ++i) {
+ pieces.push(this.indent);
+ }
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: writeNewline
+ * Output a string representing a newline if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A string representing a new line.
+ */
+ writeNewline: function() {
+ return (this.pretty) ? this.newline : '';
+ },
+
+ /**
+ * Method: writeSpace
+ * Output a string representing a space if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A space.
+ */
+ writeSpace: function() {
+ return (this.pretty) ? this.space : '';
+ },
+
+ /**
+ * Property: serialize
+ * Object with properties corresponding to the serializable data types.
+ * Property values are functions that do the actual serializing.
+ */
+ serialize: {
+ /**
+ * Method: serialize.object
+ * Transform an object into a JSON string.
+ *
+ * Parameters:
+ * object - {Object} The object to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the object.
+ */
+ 'object': function(object) {
+ // three special objects that we want to treat differently
+ if(object == null) {
+ return "null";
+ }
+ if(object.constructor == Date) {
+ return this.serialize.date.apply(this, [object]);
+ }
+ if(object.constructor == Array) {
+ return this.serialize.array.apply(this, [object]);
+ }
+ var pieces = ['{'];
+ this.level += 1;
+ var key, keyJSON, valueJSON;
+
+ var addComma = false;
+ for(key in object) {
+ if(object.hasOwnProperty(key)) {
+ // recursive calls need to allow for sub-classing
+ keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [key, this.pretty]);
+ valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [object[key], this.pretty]);
+ if(keyJSON != null && valueJSON != null) {
+ if(addComma) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(),
+ keyJSON, ':', this.writeSpace(), valueJSON);
+ addComma = true;
+ }
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), '}');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.array
+ * Transform an array into a JSON string.
+ *
+ * Parameters:
+ * array - {Array} The array to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the array.
+ */
+ 'array': function(array) {
+ var json;
+ var pieces = ['['];
+ this.level += 1;
+
+ for(var i=0, len=array.length; i<len; ++i) {
+ // recursive calls need to allow for sub-classing
+ json = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [array[i], this.pretty]);
+ if(json != null) {
+ if(i > 0) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(), json);
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), ']');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.string
+ * Transform a string into a JSON string.
+ *
+ * Parameters:
+ * string - {String} The string to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the string.
+ */
+ 'string': function(string) {
+ // If the string contains no control characters, no quote characters, and no
+ // backslash characters, then we can simply slap some quotes around it.
+ // Otherwise we must also replace the offending characters with safe
+ // sequences.
+ var m = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+ if(/["\\\x00-\x1f]/.test(string)) {
+ return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c) {
+ return c;
+ }
+ c = b.charCodeAt();
+ return '\\u00' +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + string + '"';
+ },
+
+ /**
+ * Method: serialize.number
+ * Transform a number into a JSON string.
+ *
+ * Parameters:
+ * number - {Number} The number to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the number.
+ */
+ 'number': function(number) {
+ return isFinite(number) ? String(number) : "null";
+ },
+
+ /**
+ * Method: serialize.boolean
+ * Transform a boolean into a JSON string.
+ *
+ * Parameters:
+ * bool - {Boolean} The boolean to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the boolean.
+ */
+ 'boolean': function(bool) {
+ return String(bool);
+ },
+
+ /**
+ * Method: serialize.object
+ * Transform a date into a JSON string.
+ *
+ * Parameters:
+ * date - {Date} The date to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the date.
+ */
+ 'date': function(date) {
+ function format(number) {
+ // Format integers to have at least two digits.
+ return (number < 10) ? '0' + number : number;
+ }
+ return '"' + date.getFullYear() + '-' +
+ format(date.getMonth() + 1) + '-' +
+ format(date.getDate()) + 'T' +
+ format(date.getHours()) + ':' +
+ format(date.getMinutes()) + ':' +
+ format(date.getSeconds()) + '"';
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.JSON"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GeoJSON.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ * <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.JSON>
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+ /**
+ * APIProperty: ignoreExtraDims
+ * {Boolean} Ignore dimensions higher than 2 when reading geometry
+ * coordinates.
+ */
+ ignoreExtraDims: false,
+
+ /**
+ * Constructor: OpenLayers.Format.GeoJSON
+ * Create a new parser for GeoJSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a GeoJSON string.
+ *
+ * Parameters:
+ * json - {String} A GeoJSON string
+ * type - {String} Optional string that determines the structure of
+ * the output. Supported values are "Geometry", "Feature", and
+ * "FeatureCollection". If absent or null, a default of
+ * "FeatureCollection" is assumed.
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} The return depends on the value of the type argument. If type
+ * is "FeatureCollection" (the default), the return will be an array
+ * of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+ * must represent a single geometry, and the return will be an
+ * <OpenLayers.Geometry>. If type is "Feature", the input json must
+ * represent a single feature, and the return will be an
+ * <OpenLayers.Feature.Vector>.
+ */
+ read: function(json, type, filter) {
+ type = (type) ? type : "FeatureCollection";
+ var results = null;
+ var obj = null;
+ if (typeof json == "string") {
+ obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+ [json, filter]);
+ } else {
+ obj = json;
+ }
+ if(!obj) {
+ OpenLayers.Console.error("Bad JSON: " + json);
+ } else if(typeof(obj.type) != "string") {
+ OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+ } else if(this.isValidType(obj, type)) {
+ switch(type) {
+ case "Geometry":
+ try {
+ results = this.parseGeometry(obj);
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "Feature":
+ try {
+ results = this.parseFeature(obj);
+ results.type = "Feature";
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ // for type FeatureCollection, we allow input to be any type
+ results = [];
+ switch(obj.type) {
+ case "Feature":
+ try {
+ results.push(this.parseFeature(obj));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ for(var i=0, len=obj.features.length; i<len; ++i) {
+ try {
+ results.push(this.parseFeature(obj.features[i]));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ default:
+ try {
+ var geom = this.parseGeometry(obj);
+ results.push(new OpenLayers.Feature.Vector(geom));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: isValidType
+ * Check if a GeoJSON object is a valid representative of the given type.
+ *
+ * Returns:
+ * {Boolean} The object is valid GeoJSON object of the given type.
+ */
+ isValidType: function(obj, type) {
+ var valid = false;
+ switch(type) {
+ case "Geometry":
+ if(OpenLayers.Util.indexOf(
+ ["Point", "MultiPoint", "LineString", "MultiLineString",
+ "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+ obj.type) == -1) {
+ // unsupported geometry type
+ OpenLayers.Console.error("Unsupported geometry type: " +
+ obj.type);
+ } else {
+ valid = true;
+ }
+ break;
+ case "FeatureCollection":
+ // allow for any type to be converted to a feature collection
+ valid = true;
+ break;
+ default:
+ // for Feature types must match
+ if(obj.type == type) {
+ valid = true;
+ } else {
+ OpenLayers.Console.error("Cannot convert types from " +
+ obj.type + " to " + type);
+ }
+ }
+ return valid;
+ },
+
+ /**
+ * Method: parseFeature
+ * Convert a feature object from GeoJSON into an
+ * <OpenLayers.Feature.Vector>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature.
+ */
+ parseFeature: function(obj) {
+ var feature, geometry, attributes, bbox;
+ attributes = (obj.properties) ? obj.properties : {};
+ bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
+ try {
+ geometry = this.parseGeometry(obj.geometry);
+ } catch(err) {
+ // deal with bad geometries
+ throw err;
+ }
+ feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ if(bbox) {
+ feature.bounds = OpenLayers.Bounds.fromArray(bbox);
+ }
+ if(obj.id) {
+ feature.fid = obj.id;
+ }
+ return feature;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ parseGeometry: function(obj) {
+ if (obj == null) {
+ return null;
+ }
+ var geometry, collection = false;
+ if(obj.type == "GeometryCollection") {
+ if(!(OpenLayers.Util.isArray(obj.geometries))) {
+ throw "GeometryCollection must have geometries array: " + obj;
+ }
+ var numGeom = obj.geometries.length;
+ var components = new Array(numGeom);
+ for(var i=0; i<numGeom; ++i) {
+ components[i] = this.parseGeometry.apply(
+ this, [obj.geometries[i]]
+ );
+ }
+ geometry = new OpenLayers.Geometry.Collection(components);
+ collection = true;
+ } else {
+ if(!(OpenLayers.Util.isArray(obj.coordinates))) {
+ throw "Geometry must have coordinates array: " + obj;
+ }
+ if(!this.parseCoords[obj.type.toLowerCase()]) {
+ throw "Unsupported geometry type: " + obj.type;
+ }
+ try {
+ geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+ this, [obj.coordinates]
+ );
+ } catch(err) {
+ // deal with bad coordinates
+ throw err;
+ }
+ }
+ // We don't reproject collections because the children are reprojected
+ // for us when they are created.
+ if (this.internalProjection && this.externalProjection && !collection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ return geometry;
+ },
+
+ /**
+ * Property: parseCoords
+ * Object with properties corresponding to the GeoJSON geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parseCoords: {
+ /**
+ * Method: parseCoords.point
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "point": function(array) {
+ if (this.ignoreExtraDims == false &&
+ array.length != 2) {
+ throw "Only 2D points are supported: " + array;
+ }
+ return new OpenLayers.Geometry.Point(array[0], array[1]);
+ },
+
+ /**
+ * Method: parseCoords.multipoint
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipoint": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPoint(points);
+ },
+
+ /**
+ * Method: parseCoords.linestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "linestring": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.LineString(points);
+ },
+
+ /**
+ * Method: parseCoords.multilinestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multilinestring": function(array) {
+ var lines = [];
+ var l = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ lines.push(l);
+ }
+ return new OpenLayers.Geometry.MultiLineString(lines);
+ },
+
+ /**
+ * Method: parseCoords.polygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "polygon": function(array) {
+ var rings = [];
+ var r, l;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ r = new OpenLayers.Geometry.LinearRing(l.components);
+ rings.push(r);
+ }
+ return new OpenLayers.Geometry.Polygon(rings);
+ },
+
+ /**
+ * Method: parseCoords.multipolygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipolygon": function(array) {
+ var polys = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["polygon"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ polys.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPolygon(polys);
+ },
+
+ /**
+ * Method: parseCoords.box
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "box": function(array) {
+ if(array.length != 2) {
+ throw "GeoJSON box coordinates must have 2 elements";
+ }
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+ ])
+ ]);
+ }
+
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature, geometry, array of features into a GeoJSON string.
+ *
+ * Parameters:
+ * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+ * or an array of features.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The GeoJSON string representation of the input geometry,
+ * features, or array of features.
+ */
+ write: function(obj, pretty) {
+ var geojson = {
+ "type": null
+ };
+ if(OpenLayers.Util.isArray(obj)) {
+ geojson.type = "FeatureCollection";
+ var numFeatures = obj.length;
+ geojson.features = new Array(numFeatures);
+ for(var i=0; i<numFeatures; ++i) {
+ var element = obj[i];
+ if(!element instanceof OpenLayers.Feature.Vector) {
+ var msg = "FeatureCollection only supports collections " +
+ "of features: " + element;
+ throw msg;
+ }
+ geojson.features[i] = this.extract.feature.apply(
+ this, [element]
+ );
+ }
+ } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+ geojson = this.extract.geometry.apply(this, [obj]);
+ } else if (obj instanceof OpenLayers.Feature.Vector) {
+ geojson = this.extract.feature.apply(this, [obj]);
+ if(obj.layer && obj.layer.projection) {
+ geojson.crs = this.createCRSObject(obj);
+ }
+ }
+ return OpenLayers.Format.JSON.prototype.write.apply(this,
+ [geojson, pretty]);
+ },
+
+ /**
+ * Method: createCRSObject
+ * Create the CRS object for an object.
+ *
+ * Parameters:
+ * object - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object which can be assigned to the crs property
+ * of a GeoJSON object.
+ */
+ createCRSObject: function(object) {
+ var proj = object.layer.projection.toString();
+ var crs = {};
+ if (proj.match(/epsg:/i)) {
+ var code = parseInt(proj.substring(proj.indexOf(":") + 1));
+ if (code == 4326) {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
+ }
+ };
+ } else {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "EPSG:" + code
+ }
+ };
+ }
+ }
+ return crs;
+ },
+
+ /**
+ * Property: extract
+ * Object with properties corresponding to the GeoJSON types.
+ * Property values are functions that do the actual value extraction.
+ */
+ extract: {
+ /**
+ * Method: extract.feature
+ * Return a partial GeoJSON object representing a single feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object representing the point.
+ */
+ 'feature': function(feature) {
+ var geom = this.extract.geometry.apply(this, [feature.geometry]);
+ var json = {
+ "type": "Feature",
+ "properties": feature.attributes,
+ "geometry": geom
+ };
+ if (feature.fid != null) {
+ json.id = feature.fid;
+ }
+ return json;
+ },
+
+ /**
+ * Method: extract.geometry
+ * Return a GeoJSON object representing a single geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Object} An object representing the geometry.
+ */
+ 'geometry': function(geometry) {
+ if (geometry == null) {
+ return null;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var geometryType = geometry.CLASS_NAME.split('.')[2];
+ var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+ var json;
+ if(geometryType == "Collection") {
+ json = {
+ "type": "GeometryCollection",
+ "geometries": data
+ };
+ } else {
+ json = {
+ "type": geometryType,
+ "coordinates": data
+ };
+ }
+
+ return json;
+ },
+
+ /**
+ * Method: extract.point
+ * Return an array of coordinates from a point.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Array} An array of coordinates representing the point.
+ */
+ 'point': function(point) {
+ return [point.x, point.y];
+ },
+
+ /**
+ * Method: extract.multipoint
+ * Return an array of point coordinates from a multipoint.
+ *
+ * Parameters:
+ * multipoint - {<OpenLayers.Geometry.MultiPoint>}
+ *
+ * Returns:
+ * {Array} An array of point coordinate arrays representing
+ * the multipoint.
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0, len=multipoint.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.linestring
+ * Return an array of coordinate arrays from a linestring.
+ *
+ * Parameters:
+ * linestring - {<OpenLayers.Geometry.LineString>}
+ *
+ * Returns:
+ * {Array} An array of coordinate arrays representing
+ * the linestring.
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multilinestring
+ * Return an array of linestring arrays from a linestring.
+ *
+ * Parameters:
+ * multilinestring - {<OpenLayers.Geometry.MultiLineString>}
+ *
+ * Returns:
+ * {Array} An array of linestring arrays representing
+ * the multilinestring.
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.polygon
+ * Return an array of linear ring arrays from a polygon.
+ *
+ * Parameters:
+ * polygon - {<OpenLayers.Geometry.Polygon>}
+ *
+ * Returns:
+ * {Array} An array of linear ring arrays representing the polygon.
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0, len=polygon.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multipolygon
+ * Return an array of polygon arrays from a multipolygon.
+ *
+ * Parameters:
+ * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+ *
+ * Returns:
+ * {Array} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.collection
+ * Return an array of geometries from a geometry collection.
+ *
+ * Parameters:
+ * collection - {<OpenLayers.Geometry.Collection>}
+ *
+ * Returns:
+ * {Array} An array of geometry objects representing the geometry
+ * collection.
+ */
+ 'collection': function(collection) {
+ var len = collection.components.length;
+ var array = new Array(len);
+ for(var i=0; i<len; ++i) {
+ array[i] = this.extract.geometry.apply(
+ this, [collection.components[i]]
+ );
+ }
+ return array;
+ }
+
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoJSON"
+
+});
+/* ======================================================================
+ OpenLayers/Protocol/Script.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Format/GeoJSON.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.Script
+ * A basic Script protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.Script> constructor. A script protocol is used to
+ * get around the same origin policy. It works with services that return
+ * JSONP - that is, JSON wrapped in a client-specified callback. The
+ * protocol handles fetching and parsing of feature data and sends parsed
+ * features to the <callback> configured with the protocol. The protocol
+ * expects features serialized as GeoJSON by default, but can be configured
+ * to work with other formats by setting the <format> property.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * APIProperty: url
+ * {String} Service URL. The service is expected to return serialized
+ * features wrapped in a named callback (where the callback name is
+ * generated by this protocol).
+ * Read-only, set through the options passed to the constructor.
+ */
+ url: null,
+
+ /**
+ * APIProperty: params
+ * {Object} Query string parameters to be appended to the URL.
+ * Read-only, set through the options passed to the constructor.
+ * Example: {maxFeatures: 50}
+ */
+ params: null,
+
+ /**
+ * APIProperty: callback
+ * {Object} Function to be called when the <read> operation completes.
+ */
+ callback: null,
+
+ /**
+ * APIProperty: callbackTemplate
+ * {String} Template for creating a unique callback function name
+ * for the registry. Should include ${id}. The ${id} variable will be
+ * replaced with a string identifier prefixed with a "c" (e.g. c1, c2).
+ * Default is "OpenLayers.Protocol.Script.registry.${id}".
+ */
+ callbackTemplate: "OpenLayers.Protocol.Script.registry.${id}",
+
+ /**
+ * APIProperty: callbackKey
+ * {String} The name of the query string parameter that the service
+ * recognizes as the callback identifier. Default is "callback".
+ * This key is used to generate the URL for the script. For example
+ * setting <callbackKey> to "myCallback" would result in a URL like
+ * http://example.com/?myCallback=...
+ */
+ callbackKey: "callback",
+
+ /**
+ * APIProperty: callbackPrefix
+ * {String} Where a service requires that the callback query string
+ * parameter value is prefixed by some string, this value may be set.
+ * For example, setting <callbackPrefix> to "foo:" would result in a
+ * URL like http://example.com/?callback=foo:... Default is "".
+ */
+ callbackPrefix: "",
+
+ /**
+ * APIProperty: scope
+ * {Object} Optional ``this`` object for the callback. Read-only, set
+ * through the options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: format
+ * {<OpenLayers.Format>} Format for parsing features. Default is an
+ * <OpenLayers.Format.GeoJSON> format. If an alternative is provided,
+ * the format's read method must take an object and return an array
+ * of features.
+ */
+ format: null,
+
+ /**
+ * Property: pendingRequests
+ * {Object} References all pending requests. Property names are script
+ * identifiers and property values are script elements.
+ */
+ pendingRequests: null,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Setting this property has no effect if a custom filterToParams method
+ * is provided. Default is false. If true and the layer has a
+ * projection object set, any BBOX filter will be serialized with a
+ * fifth item identifying the projection.
+ * E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Script
+ * A class for giving layers generic Script protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * params - {Object}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.pendingRequests = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+ if (!this.format) {
+ this.format = new OpenLayers.Format.GeoJSON();
+ }
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Valid options:
+ * url - {String} Url for the request.
+ * params - {Object} Parameters to get serialized as a query string.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the injected script. This object is also passed to the
+ * callback function when the request completes, its "features" property
+ * is then populated with the features received from the server.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params
+ );
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+ var request = this.createRequest(
+ options.url,
+ options.params,
+ OpenLayers.Function.bind(function(data) {
+ response.data = data;
+ this.handleRead(response, options);
+ }, this)
+ );
+ response.priv = request;
+ return response;
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, any filter will not be serialized.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * Method: createRequest
+ * Issues a request for features by creating injecting a script in the
+ * document head.
+ *
+ * Parameters:
+ * url - {String} Service URL.
+ * params - {Object} Query string parameters.
+ * callback - {Function} Callback to be called with resulting data.
+ *
+ * Returns:
+ * {HTMLScriptElement} The script pending execution.
+ */
+ createRequest: function(url, params, callback) {
+ var id = OpenLayers.Protocol.Script.register(callback);
+ var name = OpenLayers.String.format(this.callbackTemplate, {id: id});
+ params = OpenLayers.Util.extend({}, params);
+ params[this.callbackKey] = this.callbackPrefix + name;
+ url = OpenLayers.Util.urlAppend(
+ url, OpenLayers.Util.getParameterString(params)
+ );
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.id = "OpenLayers_Protocol_Script_" + id;
+ this.pendingRequests[script.id] = script;
+ var head = document.getElementsByTagName("head")[0];
+ head.appendChild(script);
+ return script;
+ },
+
+ /**
+ * Method: destroyRequest
+ * Remove a script node associated with a response from the document. Also
+ * unregisters the callback and removes the script from the
+ * <pendingRequests> object.
+ *
+ * Parameters:
+ * script - {HTMLScriptElement}
+ */
+ destroyRequest: function(script) {
+ OpenLayers.Protocol.Script.unregister(script.id.split("_").pop());
+ delete this.pendingRequests[script.id];
+ if (script.parentNode) {
+ script.parentNode.removeChild(script);
+ }
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ this.handleResponse(response, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create, read, update,
+ * or delete call.
+ */
+ handleResponse: function(response, options) {
+ if (options.callback) {
+ if (response.data) {
+ response.features = this.parseFeatures(response.data);
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ this.destroyRequest(response.priv);
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read Script response body and return features.
+ *
+ * Parameters:
+ * data - {Object} The data sent to the callback function by the server.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(data) {
+ return this.format.read(data);
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request. If no response is provided, all pending
+ * requests will be aborted.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object returned
+ * from a <read> request.
+ */
+ abort: function(response) {
+ if (response) {
+ this.destroyRequest(response.priv);
+ } else {
+ for (var key in this.pendingRequests) {
+ this.destroyRequest(this.pendingRequests[key]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.abort();
+ delete this.params;
+ delete this.format;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Script"
+});
+
+(function() {
+ var o = OpenLayers.Protocol.Script;
+ var counter = 0;
+ o.registry = {};
+
+ /**
+ * Function: OpenLayers.Protocol.Script.register
+ * Register a callback for a newly created script.
+ *
+ * Parameters:
+ * callback - {Function} The callback to be executed when the newly added
+ * script loads. This callback will be called with a single argument
+ * that is the JSON returned by the service.
+ *
+ * Returns:
+ * {Number} An identifier for retrieving the registered callback.
+ */
+ o.register = function(callback) {
+ var id = "c"+(++counter);
+ o.registry[id] = function() {
+ callback.apply(this, arguments);
+ };
+ return id;
+ };
+
+ /**
+ * Function: OpenLayers.Protocol.Script.unregister
+ * Unregister a callback previously registered with the register function.
+ *
+ * Parameters:
+ * id - {Number} The identifer returned by the register function.
+ */
+ o.unregister = function(id) {
+ delete o.registry[id];
+ };
+})();
+/* ======================================================================
+ OpenLayers/Format/EncodedPolyline.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.EncodedPolyline
+ * Class for reading and writing encoded polylines. Create a new instance
+ * with the <OpenLayers.Format.EncodedPolyline> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: geometryType
+ * {String} Geometry type to output. One of: linestring (default),
+ * linearring, point, multipoint or polygon. If the geometryType is
+ * point, only the first point of the string is returned.
+ */
+ geometryType: "linestring",
+
+ /**
+ * Constructor: OpenLayers.Format.EncodedPolyline
+ * Create a new parser for encoded polylines
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance
+ *
+ * Returns:
+ * {<OpenLayers.Format.EncodedPolyline>} A new encoded polylines parser.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize an encoded polyline string and return a vector feature.
+ *
+ * Parameters:
+ * encoded - {String} An encoded polyline string
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A vector feature with a linestring.
+ */
+ read: function(encoded) {
+ var geomType;
+ if (this.geometryType == "linestring")
+ geomType = OpenLayers.Geometry.LineString;
+ else if (this.geometryType == "linearring")
+ geomType = OpenLayers.Geometry.LinearRing;
+ else if (this.geometryType == "multipoint")
+ geomType = OpenLayers.Geometry.MultiPoint;
+ else if (this.geometryType != "point" && this.geometryType != "polygon")
+ return null;
+
+ var flatPoints = this.decodeDeltas(encoded, 2);
+ var flatPointsLength = flatPoints.length;
+
+ var pointGeometries = [];
+ for (var i = 0; i + 1 < flatPointsLength;) {
+ var y = flatPoints[i++], x = flatPoints[i++];
+ pointGeometries.push(new OpenLayers.Geometry.Point(x, y));
+ }
+
+
+ if (this.geometryType == "point")
+ return new OpenLayers.Feature.Vector(
+ pointGeometries[0]
+ );
+
+ if (this.geometryType == "polygon")
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing(pointGeometries)
+ ])
+ );
+
+ return new OpenLayers.Feature.Vector(
+ new geomType(pointGeometries)
+ );
+ },
+
+ /**
+ * APIMethod: decode
+ * Deserialize an encoded string and return an array of n-dimensional
+ * points.
+ *
+ * Parameters:
+ * encoded - {String} An encoded string
+ * dims - {int} The dimension of the points that are returned
+ *
+ * Returns:
+ * {Array(Array(int))} An array containing n-dimensional arrays of
+ * coordinates.
+ */
+ decode: function(encoded, dims, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var flatPoints = this.decodeDeltas(encoded, dims, factor);
+ var flatPointsLength = flatPoints.length;
+
+ var points = [];
+ for (var i = 0; i + (dims - 1) < flatPointsLength;) {
+ var point = [];
+
+ for (var dim = 0; dim < dims; ++dim) {
+ point.push(flatPoints[i++])
+ }
+
+ points.push(point);
+ }
+
+ return points;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature or array of features into a WKT string.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>|Array} A feature or array of
+ * features
+ *
+ * Returns:
+ * {String} The WKT string representation of the input geometries
+ */
+ write: function(features) {
+ var feature;
+ if (features.constructor == Array)
+ feature = features[0];
+ else
+ feature = features;
+
+ var geometry = feature.geometry;
+ var type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
+
+ var pointGeometries;
+ if (type == "point")
+ pointGeometries = new Array(geometry);
+ else if (type == "linestring" ||
+ type == "linearring" ||
+ type == "multipoint")
+ pointGeometries = geometry.components;
+ else if (type == "polygon")
+ pointGeometries = geometry.components[0].components;
+ else
+ return null;
+
+ var flatPoints = [];
+
+ var pointGeometriesLength = pointGeometries.length;
+ for (var i = 0; i < pointGeometriesLength; ++i) {
+ var pointGeometry = pointGeometries[i];
+ flatPoints.push(pointGeometry.y);
+ flatPoints.push(pointGeometry.x);
+ }
+
+ return this.encodeDeltas(flatPoints, 2);
+ },
+
+ /**
+ * APIMethod: encode
+ * Serialize an array of n-dimensional points and return an encoded string
+ *
+ * Parameters:
+ * points - {Array(Array(int))} An array containing n-dimensional
+ * arrays of coordinates
+ * dims - {int} The dimension of the points that should be read
+ *
+ * Returns:
+ * {String} An encoded string
+ */
+ encode: function (points, dims, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var flatPoints = [];
+
+ var pointsLength = points.length;
+ for (var i = 0; i < pointsLength; ++i) {
+ var point = points[i];
+
+ for (var dim = 0; dim < dims; ++dim) {
+ flatPoints.push(point[dim]);
+ }
+ }
+
+ return this.encodeDeltas(flatPoints, dims, factor);
+ },
+
+ /**
+ * APIMethod: encodeDeltas
+ * Encode a list of n-dimensional points and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of n-dimensional points.
+ * dimension - {number} The dimension of the points in the list.
+ * opt_factor - {number=} The factor by which the numbers will be
+ * multiplied. The remaining decimal places will get rounded away.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeDeltas: function(numbers, dimension, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var d;
+
+ var lastNumbers = new Array(dimension);
+ for (d = 0; d < dimension; ++d) {
+ lastNumbers[d] = 0;
+ }
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength;) {
+ for (d = 0; d < dimension; ++d, ++i) {
+ var num = numbers[i];
+ var delta = num - lastNumbers[d];
+ lastNumbers[d] = num;
+
+ numbers[i] = delta;
+ }
+ }
+
+ return this.encodeFloats(numbers, factor);
+ },
+
+
+ /**
+ * APIMethod: decodeDeltas
+ * Decode a list of n-dimensional points from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ * dimension - {number} The dimension of the points in the encoded string.
+ * opt_factor - {number=} The factor by which the resulting numbers will
+ * be divided.
+ *
+ * Returns:
+ * {Array.<number>} A list of n-dimensional points.
+ */
+ decodeDeltas: function(encoded, dimension, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var d;
+
+ var lastNumbers = new Array(dimension);
+ for (d = 0; d < dimension; ++d) {
+ lastNumbers[d] = 0;
+ }
+
+ var numbers = this.decodeFloats(encoded, factor);
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength;) {
+ for (d = 0; d < dimension; ++d, ++i) {
+ lastNumbers[d] += numbers[i];
+
+ numbers[i] = lastNumbers[d];
+ }
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * APIMethod: encodeFloats
+ * Encode a list of floating point numbers and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of floating point numbers.
+ * opt_factor - {number=} The factor by which the numbers will be
+ * multiplied. The remaining decimal places will get rounded away.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeFloats: function(numbers, opt_factor) {
+ var factor = opt_factor || 1e5;
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ numbers[i] = Math.round(numbers[i] * factor);
+ }
+
+ return this.encodeSignedIntegers(numbers);
+ },
+
+
+ /**
+ * APIMethod: decodeFloats
+ * Decode a list of floating point numbers from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ * opt_factor - {number=} The factor by which the result will be divided.
+ *
+ * Returns:
+ * {Array.<number>} A list of floating point numbers.
+ */
+ decodeFloats: function(encoded, opt_factor) {
+ var factor = opt_factor || 1e5;
+
+ var numbers = this.decodeSignedIntegers(encoded);
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ numbers[i] /= factor;
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * APIMethod: encodeSignedIntegers
+ * Encode a list of signed integers and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of signed integers.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeSignedIntegers: function(numbers) {
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ var num = numbers[i];
+
+ var signedNum = num << 1;
+ if (num < 0) {
+ signedNum = ~(signedNum);
+ }
+
+ numbers[i] = signedNum;
+ }
+
+ return this.encodeUnsignedIntegers(numbers);
+ },
+
+
+ /**
+ * APIMethod: decodeSignedIntegers
+ * Decode a list of signed integers from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {Array.<number>} A list of signed integers.
+ */
+ decodeSignedIntegers: function(encoded) {
+ var numbers = this.decodeUnsignedIntegers(encoded);
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ var num = numbers[i];
+ numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * APIMethod: encodeUnsignedIntegers
+ * Encode a list of unsigned integers and return an encoded string
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of unsigned integers.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeUnsignedIntegers: function(numbers) {
+ var encoded = '';
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ encoded += this.encodeUnsignedInteger(numbers[i]);
+ }
+
+ return encoded;
+ },
+
+
+ /**
+ * APIMethod: decodeUnsignedIntegers
+ * Decode a list of unsigned integers from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {Array.<number>} A list of unsigned integers.
+ */
+ decodeUnsignedIntegers: function(encoded) {
+ var numbers = [];
+
+ var current = 0;
+ var shift = 0;
+
+ var encodedLength = encoded.length;
+ for (var i = 0; i < encodedLength; ++i) {
+ var b = encoded.charCodeAt(i) - 63;
+
+ current |= (b & 0x1f) << shift;
+
+ if (b < 0x20) {
+ numbers.push(current);
+ current = 0;
+ shift = 0;
+ } else {
+ shift += 5;
+ }
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * Method: encodeFloat
+ * Encode one single floating point number and return an encoded string
+ *
+ * Parameters:
+ * num - {number} Floating point number that should be encoded.
+ * opt_factor - {number=} The factor by which num will be multiplied.
+ * The remaining decimal places will get rounded away.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeFloat: function(num, opt_factor) {
+ num = Math.round(num * (opt_factor || 1e5));
+ return this.encodeSignedInteger(num);
+ },
+
+
+ /**
+ * Method: decodeFloat
+ * Decode one single floating point number from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ * opt_factor - {number=} The factor by which the result will be divided.
+ *
+ * Returns:
+ * {number} The decoded floating point number.
+ */
+ decodeFloat: function(encoded, opt_factor) {
+ var result = this.decodeSignedInteger(encoded);
+ return result / (opt_factor || 1e5);
+ },
+
+
+ /**
+ * Method: encodeSignedInteger
+ * Encode one single signed integer and return an encoded string
+ *
+ * Parameters:
+ * num - {number} Signed integer that should be encoded.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeSignedInteger: function(num) {
+ var signedNum = num << 1;
+ if (num < 0) {
+ signedNum = ~(signedNum);
+ }
+
+ return this.encodeUnsignedInteger(signedNum);
+ },
+
+
+ /**
+ * Method: decodeSignedInteger
+ * Decode one single signed integer from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {number} The decoded signed integer.
+ */
+ decodeSignedInteger: function(encoded) {
+ var result = this.decodeUnsignedInteger(encoded);
+ return ((result & 1) ? ~(result >> 1) : (result >> 1));
+ },
+
+
+ /**
+ * Method: encodeUnsignedInteger
+ * Encode one single unsigned integer and return an encoded string
+ *
+ * Parameters:
+ * num - {number} Unsigned integer that should be encoded.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeUnsignedInteger: function(num) {
+ var value, encoded = '';
+ while (num >= 0x20) {
+ value = (0x20 | (num & 0x1f)) + 63;
+ encoded += (String.fromCharCode(value));
+ num >>= 5;
+ }
+ value = num + 63;
+ encoded += (String.fromCharCode(value));
+ return encoded;
+ },
+
+
+ /**
+ * Method: decodeUnsignedInteger
+ * Decode one single unsigned integer from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {number} The decoded unsigned integer.
+ */
+ decodeUnsignedInteger: function(encoded) {
+ var result = 0;
+ var shift = 0;
+
+ var encodedLength = encoded.length;
+ for (var i = 0; i < encodedLength; ++i) {
+ var b = encoded.charCodeAt(i) - 63;
+
+ result |= (b & 0x1f) << shift;
+
+ if (b < 0x20)
+ break;
+
+ shift += 5;
+ }
+
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.EncodedPolyline"
+});
+/* ======================================================================
+ OpenLayers/Control/Panel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ * The Panel control is a container for other controls. With it toolbars
+ * may be composed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: controls
+ * {Array(<OpenLayers.Control>)}
+ */
+ controls: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: defaultControl
+ * {<OpenLayers.Control>} The control which is activated when the control is
+ * activated (turned on), which also happens at instantiation.
+ * If <saveState> is true, <defaultControl> will be nullified after the
+ * first activation of the panel.
+ */
+ defaultControl: null,
+
+ /**
+ * APIProperty: saveState
+ * {Boolean} If set to true, the active state of this panel's controls will
+ * be stored on panel deactivation, and restored on reactivation. Default
+ * is false.
+ */
+ saveState: false,
+
+ /**
+ * APIProperty: allowDepress
+ * {Boolean} If is true the <OpenLayers.Control.TYPE_TOOL> controls can
+ * be deactivated by clicking the icon that represents them. Default
+ * is false.
+ */
+ allowDepress: false,
+
+ /**
+ * Property: activeState
+ * {Object} stores the active state of this panel's controls.
+ */
+ activeState: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Panel
+ * Create a new control panel.
+ *
+ * Each control in the panel is represented by an icon. When clicking
+ * on an icon, the <activateControl> method is called.
+ *
+ * Specific properties for controls on a panel:
+ * type - {Number} One of <OpenLayers.Control.TYPE_TOOL>,
+ * <OpenLayers.Control.TYPE_TOGGLE>, <OpenLayers.Control.TYPE_BUTTON>.
+ * If not provided, <OpenLayers.Control.TYPE_TOOL> is assumed.
+ * title - {string} Text displayed when mouse is over the icon that
+ * represents the control.
+ *
+ * The <OpenLayers.Control.type> of a control determines the behavior when
+ * clicking its icon:
+ * <OpenLayers.Control.TYPE_TOOL> - The control is activated and other
+ * controls of this type in the same panel are deactivated. This is
+ * the default type.
+ * <OpenLayers.Control.TYPE_TOGGLE> - The active state of the control is
+ * toggled.
+ * <OpenLayers.Control.TYPE_BUTTON> - The
+ * <OpenLayers.Control.Button.trigger> method of the control is called,
+ * but its active state is not changed.
+ *
+ * If a control is <OpenLayers.Control.active>, it will be drawn with the
+ * olControl[Name]ItemActive class, otherwise with the
+ * olControl[Name]ItemInactive class.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.controls = [];
+ this.activeState = {};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onButtonClick);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ for (var ctl, i = this.controls.length - 1; i >= 0; i--) {
+ ctl = this.controls[i];
+ if (ctl.events) {
+ ctl.events.un({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ ctl.panel_div = null;
+ }
+ this.activeState = null;
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ if (control === this.defaultControl ||
+ (this.saveState && this.activeState[control.id])) {
+ control.activate();
+ }
+ }
+ if (this.saveState === true) {
+ this.defaultControl = null;
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ this.activeState[control.id] = control.deactivate();
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ this.addControlsToMap(this.controls);
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function() {
+ for (var l=this.div.childNodes.length, i=l-1; i>=0; i--) {
+ this.div.removeChild(this.div.childNodes[i]);
+ }
+ this.div.innerHTML = "";
+ if (this.active) {
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ this.div.appendChild(this.controls[i].panel_div);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: activateControl
+ * This method is called when the user click on the icon representing a
+ * control in the panel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ */
+ activateControl: function (control) {
+ if (!this.active) { return false; }
+ if (control.type == OpenLayers.Control.TYPE_BUTTON) {
+ control.trigger();
+ return;
+ }
+ if (control.type == OpenLayers.Control.TYPE_TOGGLE) {
+ if (control.active) {
+ control.deactivate();
+ } else {
+ control.activate();
+ }
+ return;
+ }
+ if (this.allowDepress && control.active) {
+ control.deactivate();
+ } else {
+ var c;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ c = this.controls[i];
+ if (c != control &&
+ (c.type === OpenLayers.Control.TYPE_TOOL || c.type == null)) {
+ c.deactivate();
+ }
+ }
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: addControls
+ * To build a toolbar, you add a set of controls to it. addControls
+ * lets you add a single control or a list of controls to the
+ * Control Panel.
+ *
+ * Parameters:
+ * controls - {<OpenLayers.Control>} Controls to add in the panel.
+ */
+ addControls: function(controls) {
+ if (!(OpenLayers.Util.isArray(controls))) {
+ controls = [controls];
+ }
+ this.controls = this.controls.concat(controls);
+
+ for (var i=0, len=controls.length; i<len; i++) {
+ var control = controls[i],
+ element = this.createControlMarkup(control);
+ OpenLayers.Element.addClass(element,
+ control.displayClass + "ItemInactive");
+ OpenLayers.Element.addClass(element, "olButton");
+ if (control.title != "" && !element.title) {
+ element.title = control.title;
+ }
+ control.panel_div = element;
+ }
+
+ if (this.map) { // map.addControl() has already been called on the panel
+ this.addControlsToMap(controls);
+ this.redraw();
+ }
+ },
+
+ /**
+ * APIMethod: createControlMarkup
+ * This function just creates a div for the control. If specific HTML
+ * markup is needed this function can be overridden in specific classes,
+ * or at panel instantiation time:
+ *
+ * Example:
+ * (code)
+ * var panel = new OpenLayers.Control.Panel({
+ * defaultControl: control,
+ * // ovverride createControlMarkup to create actual buttons
+ * // including texts wrapped into span elements.
+ * createControlMarkup: function(control) {
+ * var button = document.createElement('button'),
+ * span = document.createElement('span');
+ * if (control.text) {
+ * span.innerHTML = control.text;
+ * }
+ * return button;
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to create the HTML
+ * markup for.
+ *
+ * Returns:
+ * {DOMElement} The markup.
+ */
+ createControlMarkup: function(control) {
+ return document.createElement("div");
+ },
+
+ /**
+ * Method: addControlsToMap
+ * Only for internal use in draw() and addControls() methods.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)} Controls to add into map.
+ */
+ addControlsToMap: function (controls) {
+ var control;
+ for (var i=0, len=controls.length; i<len; i++) {
+ control = controls[i];
+ if (control.autoActivate === true) {
+ control.autoActivate = false;
+ this.map.addControl(control);
+ control.autoActivate = true;
+ } else {
+ this.map.addControl(control);
+ control.deactivate();
+ }
+ control.events.on({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ },
+
+ /**
+ * Method: iconOn
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOn: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Inactive\\b");
+ d.className = d.className.replace(re, "$1Active");
+ },
+
+ /**
+ * Method: iconOff
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOff: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Active\\b");
+ d.className = d.className.replace(re, "$1Inactive");
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function (evt) {
+ var controls = this.controls,
+ button = evt.buttonElement;
+ for (var i=controls.length-1; i>=0; --i) {
+ if (controls[i].panel_div === button) {
+ this.activateControl(controls[i]);
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(control[property]) evaluates to true, the control will be
+ * included in the array returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this.controls, function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getControlsByName
+ * Get a list of contorls with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A control name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(control.name) evaluates to true, the control will be included
+ * in the list of controls returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByName: function(match) {
+ return this.getControlsBy("name", match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given type (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The type can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given type.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
+/* ======================================================================
+ OpenLayers/Control/Button.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Button
+ * The Button control is a very simple push-button, for use with
+ * <OpenLayers.Control.Panel>.
+ * When clicked, the function trigger() is executed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ *
+ * Use:
+ * (code)
+ * var button = new OpenLayers.Control.Button({
+ * displayClass: "MyButton", trigger: myFunction
+ * });
+ * panel.addControls([button]);
+ * (end)
+ *
+ * Will create a button with CSS class MyButtonItemInactive, that
+ * will call the function MyFunction() when clicked.
+ */
+OpenLayers.Control.Button = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: type
+ * {Integer} OpenLayers.Control.TYPE_BUTTON.
+ */
+ type: OpenLayers.Control.TYPE_BUTTON,
+
+ /**
+ * Method: trigger
+ * Called by a control panel when the button is clicked.
+ */
+ trigger: function() {},
+
+ CLASS_NAME: "OpenLayers.Control.Button"
+});
+/* ======================================================================
+ OpenLayers/Control/ZoomIn.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomIn
+ * The ZoomIn control is a button to increase the zoom level of a map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomIn = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * Method: trigger
+ */
+ trigger: function(){
+ if (this.map) {
+ this.map.zoomIn();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomIn"
+});
+/* ======================================================================
+ OpenLayers/Control/ZoomOut.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomOut
+ * The ZoomOut control is a button to decrease the zoom level of a map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomOut = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * Method: trigger
+ */
+ trigger: function(){
+ if (this.map) {
+ this.map.zoomOut();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomOut"
+});
+/* ======================================================================
+ OpenLayers/Control/ZoomToMaxExtent.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomToMaxExtent
+ * The ZoomToMaxExtent control is a button that zooms out to the maximum
+ * extent of the map. It is designed to be used with a
+ * <OpenLayers.Control.Panel>.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomToMaxExtent = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * Method: trigger
+ *
+ * Called whenever this control is being rendered inside of a panel and a
+ * click occurs on this controls element. Actually zooms to the maximum
+ * extent of this controls map.
+ */
+ trigger: function() {
+ if (this.map) {
+ this.map.zoomToMaxExtent();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomToMaxExtent"
+});
+/* ======================================================================
+ OpenLayers/Control/ZoomPanel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/ZoomIn.js
+ * @requires OpenLayers/Control/ZoomOut.js
+ * @requires OpenLayers/Control/ZoomToMaxExtent.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomPanel
+ * The ZoomPanel control is a compact collecton of 3 zoom controls: a
+ * <OpenLayers.Control.ZoomIn>, a <OpenLayers.Control.ZoomToMaxExtent>, and a
+ * <OpenLayers.Control.ZoomOut>. By default it is drawn in the upper left
+ * corner of the map.
+ *
+ * Note:
+ * If you wish to use this class with the default images and you want
+ * it to look nice in ie6, you should add the following, conditionally
+ * added css stylesheet to your HTML file:
+ *
+ * (code)
+ * <!--[if lte IE 6]>
+ * <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ * <![endif]-->
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.ZoomPanel = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * Constructor: OpenLayers.Control.ZoomPanel
+ * Add the three zooming controls.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ this.addControls([
+ new OpenLayers.Control.ZoomIn(),
+ new OpenLayers.Control.ZoomToMaxExtent(),
+ new OpenLayers.Control.ZoomOut()
+ ]);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomPanel"
+});
+/* ======================================================================
+ OpenLayers/Layer/HTTPRequest.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.HTTPRequest
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * Constant: URL_HASH_FACTOR
+ * {Float} Used to hash URL param strings for multi-WMS server selection.
+ * Set to the Golden Ratio per Knuth's recommendation.
+ */
+ URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2,
+
+ /**
+ * Property: url
+ * {Array(String) or String} This is either an array of url strings or
+ * a single url string.
+ */
+ url: null,
+
+ /**
+ * Property: params
+ * {Object} Hashtable of key/value parameters
+ */
+ params: null,
+
+ /**
+ * APIProperty: reproject
+ * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html
+ * for information on the replacement for this functionality.
+ * {Boolean} Whether layer should reproject itself based on base layer
+ * locations. This allows reprojection onto commercial layers.
+ * Default is false: Most layers can't reproject, but layers
+ * which can create non-square geographic pixels can, like WMS.
+ *
+ */
+ reproject: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.HTTPRequest
+ *
+ * Parameters:
+ * name - {String}
+ * url - {Array(String) or String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+ this.url = url;
+ if (!this.params) {
+ this.params = OpenLayers.Util.extend({}, params);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.url = null;
+ this.params = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this
+ * <OpenLayers.Layer.HTTPRequest>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.HTTPRequest(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: setUrl
+ *
+ * Parameters:
+ * newUrl - {String}
+ */
+ setUrl: function(newUrl) {
+ this.url = newUrl;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+ mergeNewParams:function(newParams) {
+ this.params = OpenLayers.Util.extend(this.params, newParams);
+ var ret = this.redraw();
+ if(this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "params"
+ });
+ }
+ return ret;
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Parameters:
+ * force - {Boolean} Force redraw by adding random parameter.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function(force) {
+ if (force) {
+ return this.mergeNewParams({"_olSalt": Math.random()});
+ } else {
+ return OpenLayers.Layer.prototype.redraw.apply(this, []);
+ }
+ },
+
+ /**
+ * Method: selectUrl
+ * selectUrl() implements the standard floating-point multiplicative
+ * hash function described by Knuth, and hashes the contents of the
+ * given param string into a float between 0 and 1. This float is then
+ * scaled to the size of the provided urls array, and used to select
+ * a URL.
+ *
+ * Parameters:
+ * paramString - {String}
+ * urls - {Array(String)}
+ *
+ * Returns:
+ * {String} An entry from the urls array, deterministically selected based
+ * on the paramString.
+ */
+ selectUrl: function(paramString, urls) {
+ var product = 1;
+ for (var i=0, len=paramString.length; i<len; i++) {
+ product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR;
+ product -= Math.floor(product);
+ }
+ return urls[Math.floor(product * urls.length)];
+ },
+
+ /**
+ * Method: getFullRequestString
+ * Combine url with layer's params and these newParams.
+ *
+ * does checking on the serverPath variable, allowing for cases when it
+ * is supplied with trailing ? or &, as well as cases where not.
+ *
+ * return in formatted string like this:
+ * "server?key1=value1&key2=value2&key3=value3"
+ *
+ * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+
+ // if not altUrl passed in, use layer's url
+ var url = altUrl || this.url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ //
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams =
+ OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ return OpenLayers.Util.urlAppend(url, paramsString);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
+});
+/* ======================================================================
+ OpenLayers/Tile.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile
+ * This is a class designed to designate a single tile, however
+ * it is explicitly designed to do relatively little. Tiles store
+ * information about themselves -- such as the URL that they are related
+ * to, and their size - but do not add themselves to the layer div
+ * automatically, for example. Create a new tile with the
+ * <OpenLayers.Tile> constructor, or a subclass.
+ *
+ * TBD 3.0 - remove reference to url in above paragraph
+ *
+ */
+OpenLayers.Tile = OpenLayers.Class({
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types:
+ * beforedraw - Triggered before the tile is drawn. Used to defer
+ * drawing to an animation queue. To defer drawing, listeners need
+ * to return false, which will abort drawing. The queue handler needs
+ * to call <draw>(true) to actually draw the tile.
+ * loadstart - Triggered when tile loading starts.
+ * loadend - Triggered when tile loading ends.
+ * loaderror - Triggered before the loadend event (i.e. when the tile is
+ * still hidden) if the tile could not be loaded.
+ * reload - Triggered when an already loading tile is reloaded.
+ * unload - Triggered before a tile is unloaded.
+ */
+ events: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ *
+ * This options can be set in the ``tileOptions`` option from
+ * <OpenLayers.Layer.Grid>. For example, to be notified of the
+ * ``loadend`` event of each tiles:
+ * (code)
+ * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', {
+ * tileOptions: {
+ * eventListeners: {
+ * 'loadend': function(evt) {
+ * // do something on loadend
+ * }
+ * }
+ * }
+ * });
+ * (end)
+ */
+ eventListeners: null,
+
+ /**
+ * Property: id
+ * {String} null
+ */
+ id: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>} layer the tile is attached to
+ */
+ layer: null,
+
+ /**
+ * Property: url
+ * {String} url of the request.
+ *
+ * TBD 3.0
+ * Deprecated. The base tile class does not need an url. This should be
+ * handled in subclasses. Does not belong here.
+ */
+ url: null,
+
+ /**
+ * APIProperty: bounds
+ * {<OpenLayers.Bounds>} null
+ */
+ bounds: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} null
+ */
+ size: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>} Top Left pixel of the tile
+ */
+ position: null,
+
+ /**
+ * Property: isLoading
+ * {Boolean} Is the tile loading?
+ */
+ isLoading: false,
+
+ /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
+ * there is no need for the base tile class to have a url.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile
+ * Constructor for a new <OpenLayers.Tile> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>}
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ this.layer = layer;
+ this.position = position.clone();
+ this.setBounds(bounds);
+ this.url = url;
+ if (size) {
+ this.size = size.clone();
+ }
+
+ //give the tile a unique id based on its BBOX.
+ this.id = OpenLayers.Util.createUniqueID("Tile_");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if (this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ },
+
+ /**
+ * Method: unload
+ * Call immediately before destroying if you are listening to tile
+ * events, so that counters are properly handled if tile is still
+ * loading at destroy-time. Will only fire an event if the tile is
+ * still loading.
+ */
+ unload: function() {
+ if (this.isLoading) {
+ this.isLoading = false;
+ this.events.triggerEvent("unload");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Nullify references to prevent circular references and memory leaks.
+ */
+ destroy:function() {
+ this.layer = null;
+ this.bounds = null;
+ this.size = null;
+ this.position = null;
+
+ if (this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: draw
+ * Clear whatever is currently in the tile, then return whether or not
+ * it should actually be re-drawn. This is an example implementation
+ * that can be overridden by subclasses. The minimum thing to do here
+ * is to call <clear> and return the result from <shouldDraw>.
+ *
+ * Parameters:
+ * force - {Boolean} If true, the tile will not be cleared and no beforedraw
+ * event will be fired. This is used for drawing tiles asynchronously
+ * after drawing has been cancelled by returning false from a beforedraw
+ * listener.
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn. Returns null
+ * if a beforedraw listener returned false.
+ */
+ draw: function(force) {
+ if (!force) {
+ //clear tile's contents and mark as not drawn
+ this.clear();
+ }
+ var draw = this.shouldDraw();
+ if (draw && !force && this.events.triggerEvent("beforedraw") === false) {
+ draw = null;
+ }
+ return draw;
+ },
+
+ /**
+ * Method: shouldDraw
+ * Return whether or not the tile should actually be (re-)drawn. The only
+ * case where we *wouldn't* want to draw the tile is if the tile is outside
+ * its layer's maxExtent
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn.
+ */
+ shouldDraw: function() {
+ var withinMaxExtent = false,
+ maxExtent = this.layer.maxExtent;
+ if (maxExtent) {
+ var map = this.layer.map;
+ var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent();
+ if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) {
+ withinMaxExtent = true;
+ }
+ }
+
+ return withinMaxExtent || this.layer.displayOutsideMaxExtent;
+ },
+
+ /**
+ * Method: setBounds
+ * Sets the bounds on this instance
+ *
+ * Parameters:
+ * bounds {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ bounds = bounds.clone();
+ if (this.layer.map.baseLayer.wrapDateLine) {
+ var worldExtent = this.layer.map.getMaxExtent(),
+ tolerance = this.layer.map.getResolution();
+ bounds = bounds.wrapDateLine(worldExtent, {
+ leftTolerance: tolerance,
+ rightTolerance: tolerance
+ });
+ }
+ this.bounds = bounds;
+ },
+
+ /**
+ * Method: moveTo
+ * Reposition the tile.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ * redraw - {Boolean} Call draw method on tile after moving.
+ * Default is true
+ */
+ moveTo: function (bounds, position, redraw) {
+ if (redraw == null) {
+ redraw = true;
+ }
+
+ this.setBounds(bounds);
+ this.position = position.clone();
+ if (redraw) {
+ this.draw();
+ }
+ },
+
+ /**
+ * Method: clear
+ * Clear the tile of any bounds/position-related data so that it can
+ * be reused in a new location.
+ */
+ clear: function(draw) {
+ // to be extended by subclasses
+ },
+
+ CLASS_NAME: "OpenLayers.Tile"
+});
+/* ======================================================================
+ OpenLayers/Tile/Image.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Animation.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.Image
+ * Instances of OpenLayers.Tile.Image are used to manage the image tiles
+ * used by various layers. Create a new image tile with the
+ * <OpenLayers.Tile.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to the <OpenLayers.Tile> events):
+ * beforeload - Triggered before an image is prepared for loading, when the
+ * url for the image is known already. Listeners may call <setImage> on
+ * the tile instance. If they do so, that image will be used and no new
+ * one will be created.
+ */
+
+ /**
+ * APIProperty: url
+ * {String} The URL of the image being requested. No default. Filled in by
+ * layer.getURL() function. May be modified by loadstart listeners.
+ */
+ url: null,
+
+ /**
+ * Property: imgDiv
+ * {HTMLImageElement} The image for this tile.
+ */
+ imgDiv: null,
+
+ /**
+ * Property: frame
+ * {DOMElement} The image element is appended to the frame. Any gutter on
+ * the image will be hidden behind the frame. If no gutter is set,
+ * this will be null.
+ */
+ frame: null,
+
+ /**
+ * Property: imageReloadAttempts
+ * {Integer} Attempts to load the image.
+ */
+ imageReloadAttempts: null,
+
+ /**
+ * Property: layerAlphaHack
+ * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
+ */
+ layerAlphaHack: null,
+
+ /**
+ * Property: asyncRequestId
+ * {Integer} ID of an request to see if request is still valid. This is a
+ * number which increments by 1 for each asynchronous request.
+ */
+ asyncRequestId: null,
+
+ /**
+ * APIProperty: maxGetUrlLength
+ * {Number} If set, requests that would result in GET urls with more
+ * characters than the number provided will be made using form-encoded
+ * HTTP POST. It is good practice to avoid urls that are longer than 2048
+ * characters.
+ *
+ * Caution:
+ * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
+ * Opera versions do not fully support this option. On all browsers,
+ * transition effects are not supported if POST requests are used.
+ */
+ maxGetUrlLength: null,
+
+ /**
+ * Property: canvasContext
+ * {CanvasRenderingContext2D} A canvas context associated with
+ * the tile image.
+ */
+ canvasContext: null,
+
+ /**
+ * APIProperty: crossOriginKeyword
+ * The value of the crossorigin keyword to use when loading images. This is
+ * only relevant when using <getCanvasContext> for tiles from remote
+ * origins and should be set to either 'anonymous' or 'use-credentials'
+ * for servers that send Access-Control-Allow-Origin headers with their
+ * tiles.
+ */
+ crossOriginKeyword: null,
+
+ /** TBD 3.0 - reorder the parameters to the init function to remove
+ * URL. the getUrl() function on the layer gets called on
+ * each draw(), so no need to specify it here.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile.Image
+ * Constructor for a new <OpenLayers.Tile.Image> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+
+ this.url = url; //deprecated remove me
+
+ this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
+
+ if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
+ // only create frame if it's needed
+ this.frame = document.createElement("div");
+ this.frame.style.position = "absolute";
+ this.frame.style.overflow = "hidden";
+ }
+ if (this.maxGetUrlLength != null) {
+ OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.imgDiv) {
+ this.clear();
+ this.imgDiv = null;
+ this.frame = null;
+ }
+ // don't handle async requests any more
+ this.asyncRequestId = null;
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and draw it.
+ *
+ * Returns:
+ * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
+ * false.
+ */
+ draw: function() {
+ var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+ if (shouldDraw) {
+ // The layer's reproject option is deprecated.
+ if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
+ // getBoundsFromBaseLayer is defined in deprecated.js.
+ this.bounds = this.getBoundsFromBaseLayer(this.position);
+ }
+ if (this.isLoading) {
+ //if we're already loading, send 'reload' instead of 'loadstart'.
+ this._loadEvent = "reload";
+ } else {
+ this.isLoading = true;
+ this._loadEvent = "loadstart";
+ }
+ this.renderTile();
+ this.positionTile();
+ } else if (shouldDraw === false) {
+ this.unload();
+ }
+ return shouldDraw;
+ },
+
+ /**
+ * Method: renderTile
+ * Internal function to actually initialize the image tile,
+ * position it correctly, and set its url.
+ */
+ renderTile: function() {
+ if (this.layer.async) {
+ // Asynchronous image requests call the asynchronous getURL method
+ // on the layer to fetch an image that covers 'this.bounds'.
+ var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
+ this.layer.getURLasync(this.bounds, function(url) {
+ if (id == this.asyncRequestId) {
+ this.url = url;
+ this.initImage();
+ }
+ }, this);
+ } else {
+ // synchronous image requests get the url immediately.
+ this.url = this.layer.getURL(this.bounds);
+ this.initImage();
+ }
+ },
+
+ /**
+ * Method: positionTile
+ * Using the properties currenty set on the layer, position the tile correctly.
+ * This method is used both by the async and non-async versions of the Tile.Image
+ * code.
+ */
+ positionTile: function() {
+ var style = this.getTile().style,
+ size = this.frame ? this.size :
+ this.layer.getImageSize(this.bounds),
+ ratio = 1;
+ if (this.layer instanceof OpenLayers.Layer.Grid) {
+ ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
+ }
+ style.left = this.position.x + "px";
+ style.top = this.position.y + "px";
+ style.width = Math.round(ratio * size.w) + "px";
+ style.height = Math.round(ratio * size.h) + "px";
+ },
+
+ /**
+ * Method: clear
+ * Remove the tile from the DOM, clear it of any image related data so that
+ * it can be reused in a new location.
+ */
+ clear: function() {
+ OpenLayers.Tile.prototype.clear.apply(this, arguments);
+ var img = this.imgDiv;
+ if (img) {
+ var tile = this.getTile();
+ if (tile.parentNode === this.layer.div) {
+ this.layer.div.removeChild(tile);
+ }
+ this.setImgSrc();
+ if (this.layerAlphaHack === true) {
+ img.style.filter = "";
+ }
+ OpenLayers.Element.removeClass(img, "olImageLoadError");
+ }
+ this.canvasContext = null;
+ },
+
+ /**
+ * Method: getImage
+ * Returns or creates and returns the tile image.
+ */
+ getImage: function() {
+ if (!this.imgDiv) {
+ this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
+
+ var style = this.imgDiv.style;
+ if (this.frame) {
+ var left = 0, top = 0;
+ if (this.layer.gutter) {
+ left = this.layer.gutter / this.layer.tileSize.w * 100;
+ top = this.layer.gutter / this.layer.tileSize.h * 100;
+ }
+ style.left = -left + "%";
+ style.top = -top + "%";
+ style.width = (2 * left + 100) + "%";
+ style.height = (2 * top + 100) + "%";
+ }
+ style.visibility = "hidden";
+ style.opacity = 0;
+ if (this.layer.opacity < 1) {
+ style.filter = 'alpha(opacity=' +
+ (this.layer.opacity * 100) +
+ ')';
+ }
+ style.position = "absolute";
+ if (this.layerAlphaHack) {
+ // move the image out of sight
+ style.paddingTop = style.height;
+ style.height = "0";
+ style.width = "100%";
+ }
+ if (this.frame) {
+ this.frame.appendChild(this.imgDiv);
+ }
+ }
+
+ return this.imgDiv;
+ },
+
+ /**
+ * APIMethod: setImage
+ * Sets the image element for this tile. This method should only be called
+ * from beforeload listeners.
+ *
+ * Parameters
+ * img - {HTMLImageElement} The image to use for this tile.
+ */
+ setImage: function(img) {
+ this.imgDiv = img;
+ },
+
+ /**
+ * Method: initImage
+ * Creates the content for the frame on the tile.
+ */
+ initImage: function() {
+ if (!this.url && !this.imgDiv) {
+ // fast path out - if there is no tile url and no previous image
+ this.isLoading = false;
+ return;
+ }
+ this.events.triggerEvent('beforeload');
+ this.layer.div.appendChild(this.getTile());
+ this.events.triggerEvent(this._loadEvent);
+ var img = this.getImage();
+ var src = img.getAttribute('src') || '';
+ if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
+ this._loadTimeout = window.setTimeout(
+ OpenLayers.Function.bind(this.onImageLoad, this), 0
+ );
+ } else {
+ this.stopLoading();
+ if (this.crossOriginKeyword) {
+ img.removeAttribute("crossorigin");
+ }
+ OpenLayers.Event.observe(img, "load",
+ OpenLayers.Function.bind(this.onImageLoad, this)
+ );
+ OpenLayers.Event.observe(img, "error",
+ OpenLayers.Function.bind(this.onImageError, this)
+ );
+ this.imageReloadAttempts = 0;
+ this.setImgSrc(this.url);
+ }
+ },
+
+ /**
+ * Method: setImgSrc
+ * Sets the source for the tile image
+ *
+ * Parameters:
+ * url - {String} or undefined to hide the image
+ */
+ setImgSrc: function(url) {
+ var img = this.imgDiv;
+ if (url) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ // don't set crossOrigin if the url is a data URL
+ if (this.crossOriginKeyword) {
+ if (url.substr(0, 5) !== 'data:') {
+ img.setAttribute("crossorigin", this.crossOriginKeyword);
+ } else {
+ img.removeAttribute("crossorigin");
+ }
+ }
+ img.src = url;
+ } else {
+ // Remove reference to the image, and leave it to the browser's
+ // caching and garbage collection.
+ this.stopLoading();
+ this.imgDiv = null;
+ if (img.parentNode) {
+ img.parentNode.removeChild(img);
+ }
+ }
+ },
+
+ /**
+ * Method: getTile
+ * Get the tile's markup.
+ *
+ * Returns:
+ * {DOMElement} The tile's markup
+ */
+ getTile: function() {
+ return this.frame ? this.frame : this.getImage();
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
+ * of the tile's markup, because we want to avoid the reloading of the
+ * image. So we clone the frame, and steal the image from the tile.
+ *
+ * Returns:
+ * {DOMElement} The markup, or undefined if the tile has no image
+ * or if it's currently loading.
+ */
+ createBackBuffer: function() {
+ if (!this.imgDiv || this.isLoading) {
+ return;
+ }
+ var backBuffer;
+ if (this.frame) {
+ backBuffer = this.frame.cloneNode(false);
+ backBuffer.appendChild(this.imgDiv);
+ } else {
+ backBuffer = this.imgDiv;
+ }
+ this.imgDiv = null;
+ return backBuffer;
+ },
+
+ /**
+ * Method: onImageLoad
+ * Handler for the image onload event
+ */
+ onImageLoad: function() {
+ var img = this.imgDiv;
+ this.stopLoading();
+ img.style.visibility = 'inherit';
+ img.style.opacity = this.layer.opacity;
+ this.isLoading = false;
+ this.canvasContext = null;
+ this.events.triggerEvent("loadend");
+
+ if (this.layerAlphaHack === true) {
+ img.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
+ img.src + "', sizingMethod='scale')";
+ }
+ },
+
+ /**
+ * Method: onImageError
+ * Handler for the image onerror event
+ */
+ onImageError: function() {
+ var img = this.imgDiv;
+ if (img.src != null) {
+ this.imageReloadAttempts++;
+ if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
+ this.setImgSrc(this.layer.getURL(this.bounds));
+ } else {
+ OpenLayers.Element.addClass(img, "olImageLoadError");
+ this.events.triggerEvent("loaderror");
+ this.onImageLoad();
+ }
+ }
+ },
+
+ /**
+ * Method: stopLoading
+ * Stops a loading sequence so <onImageLoad> won't be executed.
+ */
+ stopLoading: function() {
+ OpenLayers.Event.stopObservingElement(this.imgDiv);
+ window.clearTimeout(this._loadTimeout);
+ delete this._loadTimeout;
+ },
+
+ /**
+ * APIMethod: getCanvasContext
+ * Returns a canvas context associated with the tile image (with
+ * the image drawn on it).
+ * Returns undefined if the browser does not support canvas, if
+ * the tile has no image or if it's currently loading.
+ *
+ * The function returns a canvas context instance but the
+ * underlying canvas is still available in the 'canvas' property:
+ * (code)
+ * var context = tile.getCanvasContext();
+ * if (context) {
+ * var data = context.canvas.toDataURL('image/jpeg');
+ * }
+ * (end)
+ *
+ * Returns:
+ * {Boolean}
+ */
+ getCanvasContext: function() {
+ if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
+ if (!this.canvasContext) {
+ var canvas = document.createElement("canvas");
+ canvas.width = this.size.w;
+ canvas.height = this.size.h;
+ this.canvasContext = canvas.getContext("2d");
+ this.canvasContext.drawImage(this.imgDiv, 0, 0);
+ }
+ return this.canvasContext;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.Image"
+
+});
+
+/**
+ * Constant: OpenLayers.Tile.Image.IMAGE
+ * {HTMLImageElement} The image for a tile.
+ */
+OpenLayers.Tile.Image.IMAGE = (function() {
+ var img = new Image();
+ img.className = "olTileImage";
+ // avoid image gallery menu in IE6
+ img.galleryImg = "no";
+ return img;
+}());
+
+/* ======================================================================
+ OpenLayers/Layer/Grid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/HTTPRequest.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Grid
+ * Base class for layers that use a lattice of tiles. Create a new grid
+ * layer with the <OpenLayers.Layer.Grid> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.HTTPRequest>
+ */
+OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>}
+ */
+ tileSize: null,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} If the <tileOrigin> property is not provided, the tile origin
+ * will be derived from the layer's <maxExtent>. The corner of the
+ * <maxExtent> used is determined by this property. Acceptable values
+ * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
+ * (bottom right). Default is "bl".
+ */
+ tileOriginCorner: "bl",
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
+ * If provided, requests for tiles at all resolutions will be aligned
+ * with this location (no tiles shall overlap this location). If
+ * not provided, the grid of tiles will be aligned with the layer's
+ * <maxExtent>. Default is ``null``.
+ */
+ tileOrigin: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer, if supported by the tile class.
+ */
+ tileOptions: null,
+
+ /**
+ * APIProperty: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is OpenLayers.Tile.Image.
+ */
+ tileClass: OpenLayers.Tile.Image,
+
+ /**
+ * Property: grid
+ * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is
+ * an array of tiles.
+ */
+ grid: null,
+
+ /**
+ * APIProperty: singleTile
+ * {Boolean} Moves the layer into single-tile mode, meaning that one tile
+ * will be loaded. The tile's size will be determined by the 'ratio'
+ * property. When the tile is dragged such that it does not cover the
+ * entire viewport, it is reloaded.
+ */
+ singleTile: false,
+
+ /** APIProperty: ratio
+ * {Float} Used only when in single-tile mode, this specifies the
+ * ratio of the size of the single tile to the size of the map.
+ * Default value is 1.5.
+ */
+ ratio: 1.5,
+
+ /**
+ * APIProperty: buffer
+ * {Integer} Used only when in gridded mode, this specifies the number of
+ * extra rows and colums of tiles on each side which will
+ * surround the minimum grid tiles to cover the map.
+ * For very slow loading layers, a larger value may increase
+ * performance somewhat when dragging, but will increase bandwidth
+ * use significantly.
+ */
+ buffer: 0,
+
+ /**
+ * APIProperty: transitionEffect
+ * {String} The transition effect to use when the map is zoomed.
+ * Two posible values:
+ *
+ * "resize" - Existing tiles are resized on zoom to provide a visual
+ * effect of the zoom having taken place immediately. As the
+ * new tiles become available, they are drawn on top of the
+ * resized tiles (this is the default setting).
+ * "map-resize" - Existing tiles are resized on zoom and placed below the
+ * base layer. New tiles for the base layer will cover existing tiles.
+ * This setting is recommended when having an overlay duplicated during
+ * the transition is undesirable (e.g. street labels or big transparent
+ * fills).
+ * null - No transition effect.
+ *
+ * Using "resize" on non-opaque layers can cause undesired visual
+ * effects. Set transitionEffect to null in this case.
+ */
+ transitionEffect: "resize",
+
+ /**
+ * APIProperty: numLoadingTiles
+ * {Integer} How many tiles are still loading?
+ */
+ numLoadingTiles: 0,
+
+ /**
+ * Property: serverResolutions
+ * {Array(Number}} This property is documented in subclasses as
+ * an API property.
+ */
+ serverResolutions: null,
+
+ /**
+ * Property: loading
+ * {Boolean} Indicates if tiles are being loaded.
+ */
+ loading: false,
+
+ /**
+ * Property: backBuffer
+ * {DOMElement} The back buffer.
+ */
+ backBuffer: null,
+
+ /**
+ * Property: gridResolution
+ * {Number} The resolution of the current grid. Used for backbuffer and
+ * client zoom. This property is updated every time the grid is
+ * initialized.
+ */
+ gridResolution: null,
+
+ /**
+ * Property: backBufferResolution
+ * {Number} The resolution of the current back buffer. This property is
+ * updated each time a back buffer is created.
+ */
+ backBufferResolution: null,
+
+ /**
+ * Property: backBufferLonLat
+ * {Object} The top-left corner of the current back buffer. Includes lon
+ * and lat properties. This object is updated each time a back buffer
+ * is created.
+ */
+ backBufferLonLat: null,
+
+ /**
+ * Property: backBufferTimerId
+ * {Number} The id of the back buffer timer. This timer is used to
+ * delay the removal of the back buffer, thereby preventing
+ * flash effects caused by tile animation.
+ */
+ backBufferTimerId: null,
+
+ /**
+ * APIProperty: removeBackBufferDelay
+ * {Number} Delay for removing the backbuffer when all tiles have finished
+ * loading. Can be set to 0 when no css opacity transitions for the
+ * olTileImage class are used. Default is 0 for <singleTile> layers,
+ * 2500 for tiled layers. See <className> for more information on
+ * tile animation.
+ */
+ removeBackBufferDelay: null,
+
+ /**
+ * APIProperty: className
+ * {String} Name of the class added to the layer div. If not set in the
+ * options passed to the constructor then className defaults to
+ * "olLayerGridSingleTile" for single tile layers (see <singleTile>),
+ * and "olLayerGrid" for non single tile layers.
+ *
+ * Note:
+ *
+ * The displaying of tiles is not animated by default for single tile
+ * layers - OpenLayers' default theme (style.css) includes this:
+ * (code)
+ * .olLayerGrid .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * To animate tile displaying for any grid layer the following
+ * CSS rule can be used:
+ * (code)
+ * .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * In that case, to avoid flash effects, <removeBackBufferDelay>
+ * should not be zero.
+ */
+ className: null,
+
+ /**
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported event types:
+ * addtile - Triggered when a tile is added to this layer. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that has been added.
+ * tileloadstart - Triggered when a tile starts loading. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that starts loading.
+ * tileloaded - Triggered when each new tile is
+ * loaded, as a means of progress update to listeners.
+ * listeners can access 'numLoadingTiles' if they wish to keep
+ * track of the loading progress. Listeners are called with an object
+ * with a 'tile' property as first argument, making the loaded tile
+ * available to the listener, and an 'aborted' property, which will be
+ * true when loading was aborted and no tile data is available.
+ * tileerror - Triggered before the tileloaded event (i.e. when the tile is
+ * still hidden) if a tile failed to load. Listeners receive an object
+ * as first argument, which has a tile property that references the
+ * tile that could not be loaded.
+ * retile - Triggered when the layer recreates its tile grid.
+ */
+
+ /**
+ * Property: gridLayout
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ gridLayout: null,
+
+ /**
+ * Property: rowSign
+ * {Number} 1 for grids starting at the top, -1 for grids starting at the
+ * bottom. This is used for several grid index and offset calculations.
+ */
+ rowSign: null,
+
+ /**
+ * Property: transitionendEvents
+ * {Array} Event names for transitionend
+ */
+ transitionendEvents: [
+ 'transitionend', 'webkitTransitionEnd', 'otransitionend',
+ 'oTransitionEnd'
+ ],
+
+ /**
+ * Constructor: OpenLayers.Layer.Grid
+ * Create a new grid layer
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
+ arguments);
+ this.grid = [];
+ this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);
+
+ this.initProperties();
+
+ this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
+ },
+
+ /**
+ * Method: initProperties
+ * Set any properties that depend on the value of singleTile.
+ * Currently sets removeBackBufferDelay and className
+ */
+ initProperties: function() {
+ if (this.options.removeBackBufferDelay === undefined) {
+ this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
+ }
+
+ if (this.options.className === undefined) {
+ this.className = this.singleTile ? 'olLayerGridSingleTile' :
+ 'olLayerGrid';
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
+ OpenLayers.Element.addClass(this.div, this.className);
+ },
+
+ /**
+ * Method: removeMap
+ * Called when the layer is removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ removeMap: function(map) {
+ this.removeBackBuffer();
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct the layer and clear the grid.
+ */
+ destroy: function() {
+ this.removeBackBuffer();
+ this.clearGrid();
+
+ this.grid = null;
+ this.tileSize = null;
+ OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Refetches tiles with new params merged, keeping a backbuffer. Each
+ * loading new tile will have a css class of '.olTileReplacing'. If a
+ * stylesheet applies a 'display: none' style to that class, any fade-in
+ * transition will not apply, and backbuffers for each tile will be removed
+ * as soon as the tile is loaded.
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+
+ /**
+ * Method: clearGrid
+ * Go through and remove all tiles from the grid, calling
+ * destroy() on each of them to kill circular references
+ */
+ clearGrid:function() {
+ if (this.grid) {
+ for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
+ var row = this.grid[iRow];
+ for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
+ var tile = row[iCol];
+ this.destroyTile(tile);
+ }
+ }
+ this.grid = [];
+ this.gridResolution = null;
+ this.gridLayout = null;
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+ var singleTileChanged = newOptions.singleTile !== undefined &&
+ newOptions.singleTile !== this.singleTile;
+ OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
+ if (this.map && singleTileChanged) {
+ this.initProperties();
+ this.clearGrid();
+ this.tileSize = this.options.tileSize;
+ this.setTileSize();
+ this.moveTo(null, true);
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Grid(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ if (this.tileSize != null) {
+ obj.tileSize = this.tileSize.clone();
+ }
+
+ // we do not want to copy reference to grid, so we make a new array
+ obj.grid = [];
+ obj.gridResolution = null;
+ // same for backbuffer
+ obj.backBuffer = null;
+ obj.backBufferTimerId = null;
+ obj.loading = false;
+ obj.numLoadingTiles = 0;
+
+ return obj;
+ },
+
+ /**
+ * Method: moveTo
+ * This function is called whenever the map is moved. All the moving
+ * of actual 'tiles' is done by the map, but moveTo's role is to accept
+ * a bounds and make sure the data that that bounds requires is pre-loaded.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+
+ OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
+
+ bounds = bounds || this.map.getExtent();
+
+ if (bounds != null) {
+
+ // if grid is empty or zoom has changed, we *must* re-tile
+ var forceReTile = !this.grid.length || zoomChanged;
+
+ // total bounds of the tiles
+ var tilesBounds = this.getTilesBounds();
+
+ // the new map resolution
+ var resolution = this.map.getResolution();
+
+ // the server-supported resolution for the new map resolution
+ var serverResolution = this.getServerResolution(resolution);
+
+ if (this.singleTile) {
+
+ // We want to redraw whenever even the slightest part of the
+ // current bounds is not contained by our tile.
+ // (thus, we do not specify partial -- its default is false)
+
+ if ( forceReTile ||
+ (!dragging && !tilesBounds.containsBounds(bounds))) {
+
+ // In single tile mode with no transition effect, we insert
+ // a non-scaled backbuffer when the layer is moved. But if
+ // a zoom occurs right after a move, i.e. before the new
+ // image is received, we need to remove the backbuffer, or
+ // an ill-positioned image will be visible during the zoom
+ // transition.
+
+ if(zoomChanged && this.transitionEffect !== 'resize') {
+ this.removeBackBuffer();
+ }
+
+ if(!zoomChanged || this.transitionEffect === 'resize') {
+ this.applyBackBuffer(resolution);
+ }
+
+ this.initSingleTile(bounds);
+ }
+ } else {
+
+ // if the bounds have changed such that they are not even
+ // *partially* contained by our tiles (e.g. when user has
+ // programmatically panned to the other side of the earth on
+ // zoom level 18), then moveGriddedTiles could potentially have
+ // to run through thousands of cycles, so we want to reTile
+ // instead (thus, partial true).
+ forceReTile = forceReTile ||
+ !tilesBounds.intersectsBounds(bounds, {
+ worldBounds: this.map.baseLayer.wrapDateLine &&
+ this.map.getMaxExtent()
+ });
+
+ if(forceReTile) {
+ if(zoomChanged && (this.transitionEffect === 'resize' ||
+ this.gridResolution === resolution)) {
+ this.applyBackBuffer(resolution);
+ }
+ this.initGriddedTiles(bounds);
+ } else {
+ this.moveGriddedTiles();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getTileData
+ * Given a map location, retrieve a tile and the pixel offset within that
+ * tile corresponding to the location. If there is not an existing
+ * tile in the grid that covers the given location, null will be
+ * returned.
+ *
+ * Parameters:
+ * loc - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
+ * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
+ * offset from top left).
+ */
+ getTileData: function(loc) {
+ var data = null,
+ x = loc.lon,
+ y = loc.lat,
+ numRows = this.grid.length;
+
+ if (this.map && numRows) {
+ var res = this.map.getResolution(),
+ tileWidth = this.tileSize.w,
+ tileHeight = this.tileSize.h,
+ bounds = this.grid[0][0].bounds,
+ left = bounds.left,
+ top = bounds.top;
+
+ if (x < left) {
+ // deal with multiple worlds
+ if (this.map.baseLayer.wrapDateLine) {
+ var worldWidth = this.map.getMaxExtent().getWidth();
+ var worldsAway = Math.ceil((left - x) / worldWidth);
+ x += worldWidth * worldsAway;
+ }
+ }
+ // tile distance to location (fractional number of tiles);
+ var dtx = (x - left) / (res * tileWidth);
+ var dty = (top - y) / (res * tileHeight);
+ // index of tile in grid
+ var col = Math.floor(dtx);
+ var row = Math.floor(dty);
+ if (row >= 0 && row < numRows) {
+ var tile = this.grid[row][col];
+ if (tile) {
+ data = {
+ tile: tile,
+ // pixel index within tile
+ i: Math.floor((dtx - col) * tileWidth),
+ j: Math.floor((dty - row) * tileHeight)
+ };
+ }
+ }
+ }
+ return data;
+ },
+
+ /**
+ * Method: destroyTile
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ destroyTile: function(tile) {
+ this.removeTileMonitoringHooks(tile);
+ tile.destroy();
+ },
+
+ /**
+ * Method: getServerResolution
+ * Return the closest server-supported resolution.
+ *
+ * Parameters:
+ * resolution - {Number} The base resolution. If undefined the
+ * map resolution is used.
+ *
+ * Returns:
+ * {Number} The closest server resolution value.
+ */
+ getServerResolution: function(resolution) {
+ var distance = Number.POSITIVE_INFINITY;
+ resolution = resolution || this.map.getResolution();
+ if(this.serverResolutions &&
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
+ var i, newDistance, newResolution, serverResolution;
+ for(i=this.serverResolutions.length-1; i>= 0; i--) {
+ newResolution = this.serverResolutions[i];
+ newDistance = Math.abs(newResolution - resolution);
+ if (newDistance > distance) {
+ break;
+ }
+ distance = newDistance;
+ serverResolution = newResolution;
+ }
+ resolution = serverResolution;
+ }
+ return resolution;
+ },
+
+ /**
+ * Method: getServerZoom
+ * Return the zoom value corresponding to the best matching server
+ * resolution, taking into account <serverResolutions> and <zoomOffset>.
+ *
+ * Returns:
+ * {Number} The closest server supported zoom. This is not the map zoom
+ * level, but an index of the server's resolutions array.
+ */
+ getServerZoom: function() {
+ var resolution = this.getServerResolution();
+ return this.serverResolutions ?
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
+ this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
+ },
+
+ /**
+ * Method: applyBackBuffer
+ * Create, insert, scale and position a back buffer for the layer.
+ *
+ * Parameters:
+ * resolution - {Number} The resolution to transition to.
+ */
+ applyBackBuffer: function(resolution) {
+ if(this.backBufferTimerId !== null) {
+ this.removeBackBuffer();
+ }
+ var backBuffer = this.backBuffer;
+ if(!backBuffer) {
+ backBuffer = this.createBackBuffer();
+ if(!backBuffer) {
+ return;
+ }
+ if (resolution === this.gridResolution) {
+ this.div.insertBefore(backBuffer, this.div.firstChild);
+ } else {
+ this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
+ }
+ this.backBuffer = backBuffer;
+
+ // set some information in the instance for subsequent
+ // calls to applyBackBuffer where the same back buffer
+ // is reused
+ var topLeftTileBounds = this.grid[0][0].bounds;
+ this.backBufferLonLat = {
+ lon: topLeftTileBounds.left,
+ lat: topLeftTileBounds.top
+ };
+ this.backBufferResolution = this.gridResolution;
+ }
+
+ var ratio = this.backBufferResolution / resolution;
+
+ // scale the tiles inside the back buffer
+ var tiles = backBuffer.childNodes, tile;
+ for (var i=tiles.length-1; i>=0; --i) {
+ tile = tiles[i];
+ tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px';
+ tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px';
+ tile.style.width = Math.round(ratio * tile._w) + 'px';
+ tile.style.height = Math.round(ratio * tile._h) + 'px';
+ }
+
+ // and position it (based on the grid's top-left corner)
+ var position = this.getViewPortPxFromLonLat(
+ this.backBufferLonLat, resolution);
+ var leftOffset = this.map.layerContainerOriginPx.x;
+ var topOffset = this.map.layerContainerOriginPx.y;
+ backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
+ backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a back buffer.
+ *
+ * Returns:
+ * {DOMElement} The DOM element for the back buffer, undefined if the
+ * grid isn't initialized yet.
+ */
+ createBackBuffer: function() {
+ var backBuffer;
+ if(this.grid.length > 0) {
+ backBuffer = document.createElement('div');
+ backBuffer.id = this.div.id + '_bb';
+ backBuffer.className = 'olBackBuffer';
+ backBuffer.style.position = 'absolute';
+ var map = this.map;
+ backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
+ this.getZIndex() - 1 :
+ // 'map-resize':
+ map.Z_INDEX_BASE.BaseLayer -
+ (map.getNumLayers() - map.getLayerIndex(this));
+ for(var i=0, lenI=this.grid.length; i<lenI; i++) {
+ for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
+ var tile = this.grid[i][j],
+ markup = this.grid[i][j].createBackBuffer();
+ if (markup) {
+ markup._i = i;
+ markup._j = j;
+ markup._w = tile.size.w;
+ markup._h = tile.size.h;
+ markup.id = tile.id + '_bb';
+ backBuffer.appendChild(markup);
+ }
+ }
+ }
+ }
+ return backBuffer;
+ },
+
+ /**
+ * Method: removeBackBuffer
+ * Remove back buffer from DOM.
+ */
+ removeBackBuffer: function() {
+ if (this._transitionElement) {
+ for (var i=this.transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.stopObserving(this._transitionElement,
+ this.transitionendEvents[i], this._removeBackBuffer);
+ }
+ delete this._transitionElement;
+ }
+ if(this.backBuffer) {
+ if (this.backBuffer.parentNode) {
+ this.backBuffer.parentNode.removeChild(this.backBuffer);
+ }
+ this.backBuffer = null;
+ this.backBufferResolution = null;
+ if(this.backBufferTimerId !== null) {
+ window.clearTimeout(this.backBufferTimerId);
+ this.backBufferTimerId = null;
+ }
+ }
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ if (!this.singleTile) {
+ this.moveGriddedTiles();
+ }
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Check if we are in singleTile mode and if so, set the size as a ratio
+ * of the map size (as specified by the layer's 'ratio' property).
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ if (this.singleTile) {
+ size = this.map.getSize();
+ size.h = parseInt(size.h * this.ratio, 10);
+ size.w = parseInt(size.w * this.ratio, 10);
+ }
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
+ },
+
+ /**
+ * APIMethod: getTilesBounds
+ * Return the bounds of the tile grid.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
+ * currently loaded tiles (including those partially or not at all seen
+ * onscreen).
+ */
+ getTilesBounds: function() {
+ var bounds = null;
+
+ var length = this.grid.length;
+ if (length) {
+ var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
+ width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
+ height = this.grid.length * bottomLeftTileBounds.getHeight();
+
+ bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left,
+ bottomLeftTileBounds.bottom,
+ bottomLeftTileBounds.left + width,
+ bottomLeftTileBounds.bottom + height);
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: initSingleTile
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initSingleTile: function(bounds) {
+ this.events.triggerEvent("retile");
+
+ //determine new tile bounds
+ var center = bounds.getCenterLonLat();
+ var tileWidth = bounds.getWidth() * this.ratio;
+ var tileHeight = bounds.getHeight() * this.ratio;
+
+ var tileBounds =
+ new OpenLayers.Bounds(center.lon - (tileWidth/2),
+ center.lat - (tileHeight/2),
+ center.lon + (tileWidth/2),
+ center.lat + (tileHeight/2));
+
+ var px = this.map.getLayerPxFromLonLat({
+ lon: tileBounds.left,
+ lat: tileBounds.top
+ });
+
+ if (!this.grid.length) {
+ this.grid[0] = [];
+ }
+
+ var tile = this.grid[0][0];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+
+ this.addTileMonitoringHooks(tile);
+ tile.draw();
+ this.grid[0][0] = tile;
+ } else {
+ tile.moveTo(tileBounds, px);
+ }
+
+ //remove all but our single tile
+ this.removeExcessTiles(1,1);
+
+ // store the resolution of the grid
+ this.gridResolution = this.getServerResolution();
+ },
+
+ /**
+ * Method: calculateGridLayout
+ * Generate parameters for the grid layout.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
+ * object with a 'left' and 'top' properties.
+ * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * resolution - {Number}
+ *
+ * Returns:
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ calculateGridLayout: function(bounds, origin, resolution) {
+ var tilelon = resolution * this.tileSize.w;
+ var tilelat = resolution * this.tileSize.h;
+
+ var offsetlon = bounds.left - origin.lon;
+ var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+
+ var rowSign = this.rowSign;
+
+ var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);
+ var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
+
+ return {
+ tilelon: tilelon, tilelat: tilelat,
+ startcol: tilecol, startrow: tilerow
+ };
+
+ },
+
+ /**
+ * Method: getTileOrigin
+ * Determine the origin for aligning the grid of tiles. If a <tileOrigin>
+ * property is supplied, that will be returned. Otherwise, the origin
+ * will be derived from the layer's <maxExtent> property. In this case,
+ * the tile origin will be the corner of the <maxExtent> given by the
+ * <tileOriginCorner> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The tile origin.
+ */
+ getTileOrigin: function() {
+ var origin = this.tileOrigin;
+ if (!origin) {
+ var extent = this.getMaxExtent();
+ var edges = ({
+ "tl": ["left", "top"],
+ "tr": ["right", "top"],
+ "bl": ["left", "bottom"],
+ "br": ["right", "bottom"]
+ })[this.tileOriginCorner];
+ origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
+ }
+ return origin;
+ },
+
+ /**
+ * Method: getTileBoundsForGridIndex
+ *
+ * Parameters:
+ * row - {Number} The row of the grid
+ * col - {Number} The column of the grid
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
+ */
+ getTileBoundsForGridIndex: function(row, col) {
+ var origin = this.getTileOrigin();
+ var tileLayout = this.gridLayout;
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+ var startcol = tileLayout.startcol;
+ var startrow = tileLayout.startrow;
+ var rowSign = this.rowSign;
+ return new OpenLayers.Bounds(
+ origin.lon + (startcol + col) * tilelon,
+ origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
+ origin.lon + (startcol + col + 1) * tilelon,
+ origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
+ );
+ },
+
+ /**
+ * Method: initGriddedTiles
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initGriddedTiles:function(bounds) {
+ this.events.triggerEvent("retile");
+
+ // work out mininum number of rows and columns; this is the number of
+ // tiles required to cover the viewport plus at least one for panning
+
+ var viewSize = this.map.getSize();
+
+ var origin = this.getTileOrigin();
+ var resolution = this.map.getResolution(),
+ serverResolution = this.getServerResolution(),
+ ratio = resolution / serverResolution,
+ tileSize = {
+ w: this.tileSize.w / ratio,
+ h: this.tileSize.h / ratio
+ };
+
+ var minRows = Math.ceil(viewSize.h/tileSize.h) +
+ 2 * this.buffer + 1;
+ var minCols = Math.ceil(viewSize.w/tileSize.w) +
+ 2 * this.buffer + 1;
+
+ var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
+ this.gridLayout = tileLayout;
+
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+
+ var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
+ var layerContainerDivTop = this.map.layerContainerOriginPx.y;
+
+ var tileBounds = this.getTileBoundsForGridIndex(0, 0);
+ var startPx = this.map.getViewPortPxFromLonLat(
+ new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
+ );
+ startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
+ startPx.y = Math.round(startPx.y) - layerContainerDivTop;
+
+ var tileData = [], center = this.map.getCenter();
+
+ var rowidx = 0;
+ do {
+ var row = this.grid[rowidx];
+ if (!row) {
+ row = [];
+ this.grid.push(row);
+ }
+
+ var colidx = 0;
+ do {
+ tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
+ var px = startPx.clone();
+ px.x = px.x + colidx * Math.round(tileSize.w);
+ px.y = px.y + rowidx * Math.round(tileSize.h);
+ var tile = row[colidx];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+ this.addTileMonitoringHooks(tile);
+ row.push(tile);
+ } else {
+ tile.moveTo(tileBounds, px, false);
+ }
+ var tileCenter = tileBounds.getCenterLonLat();
+ tileData.push({
+ tile: tile,
+ distance: Math.pow(tileCenter.lon - center.lon, 2) +
+ Math.pow(tileCenter.lat - center.lat, 2)
+ });
+
+ colidx += 1;
+ } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
+ || colidx < minCols);
+
+ rowidx += 1;
+ } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
+ || rowidx < minRows);
+
+ //shave off exceess rows and colums
+ this.removeExcessTiles(rowidx, colidx);
+
+ var resolution = this.getServerResolution();
+ // store the resolution of the grid
+ this.gridResolution = resolution;
+
+ //now actually draw the tiles
+ tileData.sort(function(a, b) {
+ return a.distance - b.distance;
+ });
+ for (var i=0, ii=tileData.length; i<ii; ++i) {
+ tileData[i].tile.draw();
+ }
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent. (Implemented as a getter for
+ * potential specific implementations in sub-classes.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ return this.maxExtent;
+ },
+
+ /**
+ * APIMethod: addTile
+ * Create a tile, initialize it, and add it to the layer div.
+ *
+ * Parameters
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Tile>} The added OpenLayers.Tile
+ */
+ addTile: function(bounds, position) {
+ var tile = new this.tileClass(
+ this, position, bounds, null, this.tileSize, this.tileOptions
+ );
+ this.events.triggerEvent("addtile", {tile: tile});
+ return tile;
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tiles.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+
+ var replacingCls = 'olTileReplacing';
+
+ tile.onLoadStart = function() {
+ //if that was first tile then trigger a 'loadstart' on the layer
+ if (this.loading === false) {
+ this.loading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.events.triggerEvent("tileloadstart", {tile: tile});
+ this.numLoadingTiles++;
+ if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ OpenLayers.Element.addClass(tile.getTile(), replacingCls);
+ }
+ };
+
+ tile.onLoadEnd = function(evt) {
+ this.numLoadingTiles--;
+ var aborted = evt.type === 'unload';
+ this.events.triggerEvent("tileloaded", {
+ tile: tile,
+ aborted: aborted
+ });
+ if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ var tileDiv = tile.getTile();
+ if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
+ var bufferTile = document.getElementById(tile.id + '_bb');
+ if (bufferTile) {
+ bufferTile.parentNode.removeChild(bufferTile);
+ }
+ }
+ OpenLayers.Element.removeClass(tileDiv, replacingCls);
+ }
+ //if that was the last tile, then trigger a 'loadend' on the layer
+ if (this.numLoadingTiles === 0) {
+ if (this.backBuffer) {
+ if (this.backBuffer.childNodes.length === 0) {
+ // no tiles transitioning, remove immediately
+ this.removeBackBuffer();
+ } else {
+ // wait until transition has ended or delay has passed
+ this._transitionElement = aborted ?
+ this.div.lastChild : tile.imgDiv;
+ var transitionendEvents = this.transitionendEvents;
+ for (var i=transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.observe(this._transitionElement,
+ transitionendEvents[i],
+ this._removeBackBuffer);
+ }
+ // the removal of the back buffer is delayed to prevent
+ // flash effects due to the animation of tile displaying
+ this.backBufferTimerId = window.setTimeout(
+ this._removeBackBuffer, this.removeBackBufferDelay
+ );
+ }
+ }
+ this.loading = false;
+ this.events.triggerEvent("loadend");
+ }
+ };
+
+ tile.onLoadError = function() {
+ this.events.triggerEvent("tileerror", {tile: tile});
+ };
+
+ tile.events.on({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in addTileMonitoringHooks()
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: moveGriddedTiles
+ */
+ moveGriddedTiles: function() {
+ var buffer = this.buffer + 1;
+ while(true) {
+ var tlTile = this.grid[0][0];
+ var tlViewPort = {
+ x: tlTile.position.x +
+ this.map.layerContainerOriginPx.x,
+ y: tlTile.position.y +
+ this.map.layerContainerOriginPx.y
+ };
+ var ratio = this.getServerResolution() / this.map.getResolution();
+ var tileSize = {
+ w: Math.round(this.tileSize.w * ratio),
+ h: Math.round(this.tileSize.h * ratio)
+ };
+ if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
+ this.shiftColumn(true, tileSize);
+ } else if (tlViewPort.x < -tileSize.w * buffer) {
+ this.shiftColumn(false, tileSize);
+ } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
+ this.shiftRow(true, tileSize);
+ } else if (tlViewPort.y < -tileSize.h * buffer) {
+ this.shiftRow(false, tileSize);
+ } else {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: shiftRow
+ * Shifty grid work
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftRow: function(prepend, tileSize) {
+ var grid = this.grid;
+ var rowIndex = prepend ? 0 : (grid.length - 1);
+ var sign = prepend ? -1 : 1;
+ var rowSign = this.rowSign;
+ var tileLayout = this.gridLayout;
+ tileLayout.startrow += sign * rowSign;
+
+ var modelRow = grid[rowIndex];
+ var row = grid[prepend ? 'pop' : 'shift']();
+ for (var i=0, len=row.length; i<len; i++) {
+ var tile = row[i];
+ var position = modelRow[i].position.clone();
+ position.y += tileSize.h * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
+ }
+ grid[prepend ? 'unshift' : 'push'](row);
+ },
+
+ /**
+ * Method: shiftColumn
+ * Shift grid work in the other dimension
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftColumn: function(prepend, tileSize) {
+ var grid = this.grid;
+ var colIndex = prepend ? 0 : (grid[0].length - 1);
+ var sign = prepend ? -1 : 1;
+ var tileLayout = this.gridLayout;
+ tileLayout.startcol += sign;
+
+ for (var i=0, len=grid.length; i<len; i++) {
+ var row = grid[i];
+ var position = row[colIndex].position.clone();
+ var tile = row[prepend ? 'pop' : 'shift']();
+ position.x += tileSize.w * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
+ row[prepend ? 'unshift' : 'push'](tile);
+ }
+ },
+
+ /**
+ * Method: removeExcessTiles
+ * When the size of the map or the buffer changes, we may need to
+ * remove some excess rows and columns.
+ *
+ * Parameters:
+ * rows - {Integer} Maximum number of rows we want our grid to have.
+ * columns - {Integer} Maximum number of columns we want our grid to have.
+ */
+ removeExcessTiles: function(rows, columns) {
+ var i, l;
+
+ // remove extra rows
+ while (this.grid.length > rows) {
+ var row = this.grid.pop();
+ for (i=0, l=row.length; i<l; i++) {
+ var tile = row[i];
+ this.destroyTile(tile);
+ }
+ }
+
+ // remove extra columns
+ for (i=0, l=this.grid.length; i<l; i++) {
+ while (this.grid[i].length > columns) {
+ var row = this.grid[i];
+ var tile = row.pop();
+ this.destroyTile(tile);
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * For singleTile layers, this will set a new tile size according to the
+ * dimensions of the map pane.
+ */
+ onMapResize: function() {
+ if (this.singleTile) {
+ this.clearGrid();
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * APIMethod: getTileBounds
+ * Returns The tile bounds for a layer given a pixel location.
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var maxExtent = this.maxExtent;
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = maxExtent.left + (tileMapWidth *
+ Math.floor((mapPoint.lon -
+ maxExtent.left) /
+ tileMapWidth));
+ var tileBottom = maxExtent.bottom + (tileMapHeight *
+ Math.floor((mapPoint.lat -
+ maxExtent.bottom) /
+ tileMapHeight));
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Grid"
+});
+/* ======================================================================
+ OpenLayers/Format/ArcXML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Format.ArcXML
+ * Read/Write ArcXML. Create a new instance with the <OpenLayers.Format.ArcXML>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.ArcXML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: fontStyleKeys
+ * {Array} List of keys used in font styling.
+ */
+ fontStyleKeys: [
+ 'antialiasing', 'blockout', 'font', 'fontcolor','fontsize', 'fontstyle',
+ 'glowing', 'interval', 'outline', 'printmode', 'shadow', 'transparency'
+ ],
+
+ /**
+ * Property: request
+ * A get_image request destined for an ArcIMS server.
+ */
+ request: null,
+
+ /**
+ * Property: response
+ * A parsed response from an ArcIMS server.
+ */
+ response: null,
+
+ /**
+ * Constructor: OpenLayers.Format.ArcXML
+ * Create a new parser/writer for ArcXML. Create an instance of this class
+ * to begin authoring a request to an ArcIMS service. This is used
+ * primarily by the ArcIMS layer, but could be used to do other wild
+ * stuff, like geocoding.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ this.request = new OpenLayers.Format.ArcXML.Request();
+ this.response = new OpenLayers.Format.ArcXML.Response();
+
+ if (options) {
+ if (options.requesttype == "feature") {
+ this.request.get_image = null;
+
+ var qry = this.request.get_feature.query;
+ this.addCoordSys(qry.featurecoordsys, options.featureCoordSys);
+ this.addCoordSys(qry.filtercoordsys, options.filterCoordSys);
+
+ if (options.polygon) {
+ qry.isspatial = true;
+ qry.spatialfilter.polygon = options.polygon;
+ } else if (options.envelope) {
+ qry.isspatial = true;
+ qry.spatialfilter.envelope = {minx:0, miny:0, maxx:0, maxy:0};
+ this.parseEnvelope(qry.spatialfilter.envelope, options.envelope);
+ }
+ } else if (options.requesttype == "image") {
+ this.request.get_feature = null;
+
+ var props = this.request.get_image.properties;
+ this.parseEnvelope(props.envelope, options.envelope);
+
+ this.addLayers(props.layerlist, options.layers);
+ this.addImageSize(props.imagesize, options.tileSize);
+ this.addCoordSys(props.featurecoordsys, options.featureCoordSys);
+ this.addCoordSys(props.filtercoordsys, options.filterCoordSys);
+ } else {
+ // if an arcxml object is being created with no request type, it is
+ // probably going to consume a response, so do not throw an error if
+ // the requesttype is not defined
+ this.request = null;
+ }
+ }
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: parseEnvelope
+ * Parse an array of coordinates into an ArcXML envelope structure.
+ *
+ * Parameters:
+ * env - {Object} An envelope object that will contain the parsed coordinates.
+ * arr - {Array(double)} An array of coordinates in the order: [ minx, miny, maxx, maxy ]
+ */
+ parseEnvelope: function(env, arr) {
+ if (arr && arr.length == 4) {
+ env.minx = arr[0];
+ env.miny = arr[1];
+ env.maxx = arr[2];
+ env.maxy = arr[3];
+ }
+ },
+
+ /**
+ * Method: addLayers
+ * Add a collection of layers to another collection of layers. Each layer in the list is tuple of
+ * { id, visible }. These layer collections represent the
+ * /ARCXML/REQUEST/get_image/PROPERTIES/LAYERLIST/LAYERDEF items in ArcXML
+ *
+ * TODO: Add support for dynamic layer rendering.
+ *
+ * Parameters:
+ * ll - {Array({id,visible})} A list of layer definitions.
+ * lyrs - {Array({id,visible})} A list of layer definitions.
+ */
+ addLayers: function(ll, lyrs) {
+ for(var lind = 0, len=lyrs.length; lind < len; lind++) {
+ ll.push(lyrs[lind]);
+ }
+ },
+
+ /**
+ * Method: addImageSize
+ * Set the size of the requested image.
+ *
+ * Parameters:
+ * imsize - {Object} An ArcXML imagesize object.
+ * olsize - {<OpenLayers.Size>} The image size to set.
+ */
+ addImageSize: function(imsize, olsize) {
+ if (olsize !== null) {
+ imsize.width = olsize.w;
+ imsize.height = olsize.h;
+ imsize.printwidth = olsize.w;
+ imsize.printheight = olsize.h;
+ }
+ },
+
+ /**
+ * Method: addCoordSys
+ * Add the coordinate system information to an object. The object may be
+ *
+ * Parameters:
+ * featOrFilt - {Object} A featurecoordsys or filtercoordsys ArcXML structure.
+ * fsys - {String} or {<OpenLayers.Projection>} or {filtercoordsys} or
+ * {featurecoordsys} A projection representation. If it's a {String},
+ * the value is assumed to be the SRID. If it's a {OpenLayers.Projection}
+ * AND Proj4js is available, the projection number and name are extracted
+ * from there. If it's a filter or feature ArcXML structure, it is copied.
+ */
+ addCoordSys: function(featOrFilt, fsys) {
+ if (typeof fsys == "string") {
+ featOrFilt.id = parseInt(fsys);
+ featOrFilt.string = fsys;
+ }
+ // is this a proj4js instance?
+ else if (typeof fsys == "object" && fsys.proj !== null){
+ featOrFilt.id = fsys.proj.srsProjNumber;
+ featOrFilt.string = fsys.proj.srsCode;
+ } else {
+ featOrFilt = fsys;
+ }
+ },
+
+ /**
+ * APIMethod: iserror
+ * Check to see if the response from the server was an error.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse. If nothing is supplied,
+ * the current response is examined.
+ *
+ * Returns:
+ * {Boolean} true if the response was an error.
+ */
+ iserror: function(data) {
+ var ret = null;
+
+ if (!data) {
+ ret = (this.response.error !== '');
+ } else {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ var errorNodes = data.documentElement.getElementsByTagName("ERROR");
+ ret = (errorNodes !== null && errorNodes.length > 0);
+ }
+
+ return ret;
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return an response.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {<OpenLayers.Format.ArcXML.Response>} An ArcXML response. Note that this response
+ * data may change in the future.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+
+ var arcNode = null;
+ if (data && data.documentElement) {
+ if(data.documentElement.nodeName == "ARCXML") {
+ arcNode = data.documentElement;
+ } else {
+ arcNode = data.documentElement.getElementsByTagName("ARCXML")[0];
+ }
+ }
+
+ // in Safari, arcNode will be there but will have a child named
+ // parsererror
+ if (!arcNode || arcNode.firstChild.nodeName === 'parsererror') {
+ var error, source;
+ try {
+ error = data.firstChild.nodeValue;
+ source = data.firstChild.childNodes[1].firstChild.nodeValue;
+ } catch (err) {
+ // pass
+ }
+ throw {
+ message: "Error parsing the ArcXML request",
+ error: error,
+ source: source
+ };
+ }
+
+ var response = this.parseResponse(arcNode);
+ return response;
+ },
+
+ /**
+ * APIMethod: write
+ * Generate an ArcXml document string for sending to an ArcIMS server.
+ *
+ * Returns:
+ * {String} A string representing the ArcXML document request.
+ */
+ write: function(request) {
+ if (!request) {
+ request = this.request;
+ }
+ var root = this.createElementNS("", "ARCXML");
+ root.setAttribute("version","1.1");
+
+ var reqElem = this.createElementNS("", "REQUEST");
+
+ if (request.get_image != null) {
+ var getElem = this.createElementNS("", "GET_IMAGE");
+ reqElem.appendChild(getElem);
+
+ var propElem = this.createElementNS("", "PROPERTIES");
+ getElem.appendChild(propElem);
+
+ var props = request.get_image.properties;
+ if (props.featurecoordsys != null) {
+ var feat = this.createElementNS("", "FEATURECOORDSYS");
+ propElem.appendChild(feat);
+
+ if (props.featurecoordsys.id === 0) {
+ feat.setAttribute("string", props.featurecoordsys['string']);
+ }
+ else {
+ feat.setAttribute("id", props.featurecoordsys.id);
+ }
+ }
+
+ if (props.filtercoordsys != null) {
+ var filt = this.createElementNS("", "FILTERCOORDSYS");
+ propElem.appendChild(filt);
+
+ if (props.filtercoordsys.id === 0) {
+ filt.setAttribute("string", props.filtercoordsys.string);
+ }
+ else {
+ filt.setAttribute("id", props.filtercoordsys.id);
+ }
+ }
+
+ if (props.envelope != null) {
+ var env = this.createElementNS("", "ENVELOPE");
+ propElem.appendChild(env);
+
+ env.setAttribute("minx", props.envelope.minx);
+ env.setAttribute("miny", props.envelope.miny);
+ env.setAttribute("maxx", props.envelope.maxx);
+ env.setAttribute("maxy", props.envelope.maxy);
+ }
+
+ var imagesz = this.createElementNS("", "IMAGESIZE");
+ propElem.appendChild(imagesz);
+
+ imagesz.setAttribute("height", props.imagesize.height);
+ imagesz.setAttribute("width", props.imagesize.width);
+
+ if (props.imagesize.height != props.imagesize.printheight ||
+ props.imagesize.width != props.imagesize.printwidth) {
+ imagesz.setAttribute("printheight", props.imagesize.printheight);
+ imagesz.setArrtibute("printwidth", props.imagesize.printwidth);
+ }
+
+ if (props.background != null) {
+ var backgrnd = this.createElementNS("", "BACKGROUND");
+ propElem.appendChild(backgrnd);
+
+ backgrnd.setAttribute("color",
+ props.background.color.r + "," +
+ props.background.color.g + "," +
+ props.background.color.b);
+
+ if (props.background.transcolor !== null) {
+ backgrnd.setAttribute("transcolor",
+ props.background.transcolor.r + "," +
+ props.background.transcolor.g + "," +
+ props.background.transcolor.b);
+ }
+ }
+
+ if (props.layerlist != null && props.layerlist.length > 0) {
+ var layerlst = this.createElementNS("", "LAYERLIST");
+ propElem.appendChild(layerlst);
+
+ for (var ld = 0; ld < props.layerlist.length; ld++) {
+ var ldef = this.createElementNS("", "LAYERDEF");
+ layerlst.appendChild(ldef);
+
+ ldef.setAttribute("id", props.layerlist[ld].id);
+ ldef.setAttribute("visible", props.layerlist[ld].visible);
+
+ if (typeof props.layerlist[ld].query == "object") {
+ var query = props.layerlist[ld].query;
+
+ if (query.where.length < 0) {
+ continue;
+ }
+
+ var queryElem = null;
+ if (typeof query.spatialfilter == "boolean" && query.spatialfilter) {
+ // handle spatial filter madness
+ queryElem = this.createElementNS("", "SPATIALQUERY");
+ }
+ else {
+ queryElem = this.createElementNS("", "QUERY");
+ }
+
+ queryElem.setAttribute("where", query.where);
+
+ if (typeof query.accuracy == "number" && query.accuracy > 0) {
+ queryElem.setAttribute("accuracy", query.accuracy);
+ }
+ if (typeof query.featurelimit == "number" && query.featurelimit < 2000) {
+ queryElem.setAttribute("featurelimit", query.featurelimit);
+ }
+ if (typeof query.subfields == "string" && query.subfields != "#ALL#") {
+ queryElem.setAttribute("subfields", query.subfields);
+ }
+ if (typeof query.joinexpression == "string" && query.joinexpression.length > 0) {
+ queryElem.setAttribute("joinexpression", query.joinexpression);
+ }
+ if (typeof query.jointables == "string" && query.jointables.length > 0) {
+ queryElem.setAttribute("jointables", query.jointables);
+ }
+
+ ldef.appendChild(queryElem);
+ }
+
+ if (typeof props.layerlist[ld].renderer == "object") {
+ this.addRenderer(ldef, props.layerlist[ld].renderer);
+ }
+ }
+ }
+ } else if (request.get_feature != null) {
+ var getElem = this.createElementNS("", "GET_FEATURES");
+ getElem.setAttribute("outputmode", "newxml");
+ getElem.setAttribute("checkesc", "true");
+
+ if (request.get_feature.geometry) {
+ getElem.setAttribute("geometry", request.get_feature.geometry);
+ }
+ else {
+ getElem.setAttribute("geometry", "false");
+ }
+
+ if (request.get_feature.compact) {
+ getElem.setAttribute("compact", request.get_feature.compact);
+ }
+
+ if (request.get_feature.featurelimit == "number") {
+ getElem.setAttribute("featurelimit", request.get_feature.featurelimit);
+ }
+
+ getElem.setAttribute("globalenvelope", "true");
+ reqElem.appendChild(getElem);
+
+ if (request.get_feature.layer != null && request.get_feature.layer.length > 0) {
+ var lyrElem = this.createElementNS("", "LAYER");
+ lyrElem.setAttribute("id", request.get_feature.layer);
+ getElem.appendChild(lyrElem);
+ }
+
+ var fquery = request.get_feature.query;
+ if (fquery != null) {
+ var qElem = null;
+ if (fquery.isspatial) {
+ qElem = this.createElementNS("", "SPATIALQUERY");
+ } else {
+ qElem = this.createElementNS("", "QUERY");
+ }
+ getElem.appendChild(qElem);
+
+ if (typeof fquery.accuracy == "number") {
+ qElem.setAttribute("accuracy", fquery.accuracy);
+ }
+ //qElem.setAttribute("featurelimit", "5");
+
+ if (fquery.featurecoordsys != null) {
+ var fcsElem1 = this.createElementNS("", "FEATURECOORDSYS");
+
+ if (fquery.featurecoordsys.id == 0) {
+ fcsElem1.setAttribute("string", fquery.featurecoordsys.string);
+ } else {
+ fcsElem1.setAttribute("id", fquery.featurecoordsys.id);
+ }
+ qElem.appendChild(fcsElem1);
+ }
+
+ if (fquery.filtercoordsys != null) {
+ var fcsElem2 = this.createElementNS("", "FILTERCOORDSYS");
+
+ if (fquery.filtercoordsys.id === 0) {
+ fcsElem2.setAttribute("string", fquery.filtercoordsys.string);
+ } else {
+ fcsElem2.setAttribute("id", fquery.filtercoordsys.id);
+ }
+ qElem.appendChild(fcsElem2);
+ }
+
+ if (fquery.buffer > 0) {
+ var bufElem = this.createElementNS("", "BUFFER");
+ bufElem.setAttribute("distance", fquery.buffer);
+ qElem.appendChild(bufElem);
+ }
+
+ if (fquery.isspatial) {
+ var spfElem = this.createElementNS("", "SPATIALFILTER");
+ spfElem.setAttribute("relation", fquery.spatialfilter.relation);
+ qElem.appendChild(spfElem);
+
+ if (fquery.spatialfilter.envelope) {
+ var envElem = this.createElementNS("", "ENVELOPE");
+ envElem.setAttribute("minx", fquery.spatialfilter.envelope.minx);
+ envElem.setAttribute("miny", fquery.spatialfilter.envelope.miny);
+ envElem.setAttribute("maxx", fquery.spatialfilter.envelope.maxx);
+ envElem.setAttribute("maxy", fquery.spatialfilter.envelope.maxy);
+ spfElem.appendChild(envElem);
+ } else if(typeof fquery.spatialfilter.polygon == "object") {
+ spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon));
+ }
+ }
+
+ if (fquery.where != null && fquery.where.length > 0) {
+ qElem.setAttribute("where", fquery.where);
+ }
+ }
+ }
+
+ root.appendChild(reqElem);
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+
+ addGroupRenderer: function(ldef, toprenderer) {
+ var topRelem = this.createElementNS("", "GROUPRENDERER");
+ ldef.appendChild(topRelem);
+
+ for (var rind = 0; rind < toprenderer.length; rind++) {
+ var renderer = toprenderer[rind];
+ this.addRenderer(topRelem, renderer);
+ }
+ },
+
+
+ addRenderer: function(topRelem, renderer) {
+ if (OpenLayers.Util.isArray(renderer)) {
+ this.addGroupRenderer(topRelem, renderer);
+ } else {
+ var renderElem = this.createElementNS("", renderer.type.toUpperCase() + "RENDERER");
+ topRelem.appendChild(renderElem);
+
+ if (renderElem.tagName == "VALUEMAPRENDERER") {
+ this.addValueMapRenderer(renderElem, renderer);
+ } else if (renderElem.tagName == "VALUEMAPLABELRENDERER") {
+ this.addValueMapLabelRenderer(renderElem, renderer);
+ } else if (renderElem.tagName == "SIMPLELABELRENDERER") {
+ this.addSimpleLabelRenderer(renderElem, renderer);
+ } else if (renderElem.tagName == "SCALEDEPENDENTRENDERER") {
+ this.addScaleDependentRenderer(renderElem, renderer);
+ }
+ }
+ },
+
+
+ addScaleDependentRenderer: function(renderElem, renderer) {
+ if (typeof renderer.lower == "string" || typeof renderer.lower == "number") {
+ renderElem.setAttribute("lower", renderer.lower);
+ }
+ if (typeof renderer.upper == "string" || typeof renderer.upper == "number") {
+ renderElem.setAttribute("upper", renderer.upper);
+ }
+
+ this.addRenderer(renderElem, renderer.renderer);
+ },
+
+
+ addValueMapLabelRenderer: function(renderElem, renderer) {
+ renderElem.setAttribute("lookupfield", renderer.lookupfield);
+ renderElem.setAttribute("labelfield", renderer.labelfield);
+
+ if (typeof renderer.exacts == "object") {
+ for (var ext=0, extlen=renderer.exacts.length; ext<extlen; ext++) {
+ var exact = renderer.exacts[ext];
+
+ var eelem = this.createElementNS("", "EXACT");
+
+ if (typeof exact.value == "string") {
+ eelem.setAttribute("value", exact.value);
+ }
+ if (typeof exact.label == "string") {
+ eelem.setAttribute("label", exact.label);
+ }
+ if (typeof exact.method == "string") {
+ eelem.setAttribute("method", exact.method);
+ }
+
+ renderElem.appendChild(eelem);
+
+ if (typeof exact.symbol == "object") {
+ var selem = null;
+
+ if (exact.symbol.type == "text") {
+ selem = this.createElementNS("", "TEXTSYMBOL");
+ }
+
+ if (selem != null) {
+ var keys = this.fontStyleKeys;
+ for (var i = 0, len = keys.length; i < len; i++) {
+ var key = keys[i];
+ if (exact.symbol[key]) {
+ selem.setAttribute(key, exact.symbol[key]);
+ }
+ }
+ eelem.appendChild(selem);
+ }
+ }
+ } // for each exact
+ }
+ },
+
+ addValueMapRenderer: function(renderElem, renderer) {
+ renderElem.setAttribute("lookupfield", renderer.lookupfield);
+
+ if (typeof renderer.ranges == "object") {
+ for(var rng=0, rnglen=renderer.ranges.length; rng<rnglen; rng++) {
+ var range = renderer.ranges[rng];
+
+ var relem = this.createElementNS("", "RANGE");
+ relem.setAttribute("lower", range.lower);
+ relem.setAttribute("upper", range.upper);
+
+ renderElem.appendChild(relem);
+
+ if (typeof range.symbol == "object") {
+ var selem = null;
+
+ if (range.symbol.type == "simplepolygon") {
+ selem = this.createElementNS("", "SIMPLEPOLYGONSYMBOL");
+ }
+
+ if (selem != null) {
+ if (typeof range.symbol.boundarycolor == "string") {
+ selem.setAttribute("boundarycolor", range.symbol.boundarycolor);
+ }
+ if (typeof range.symbol.fillcolor == "string") {
+ selem.setAttribute("fillcolor", range.symbol.fillcolor);
+ }
+ if (typeof range.symbol.filltransparency == "number") {
+ selem.setAttribute("filltransparency", range.symbol.filltransparency);
+ }
+ relem.appendChild(selem);
+ }
+ }
+ } // for each range
+ } else if (typeof renderer.exacts == "object") {
+ for (var ext=0, extlen=renderer.exacts.length; ext<extlen; ext++) {
+ var exact = renderer.exacts[ext];
+
+ var eelem = this.createElementNS("", "EXACT");
+ if (typeof exact.value == "string") {
+ eelem.setAttribute("value", exact.value);
+ }
+ if (typeof exact.label == "string") {
+ eelem.setAttribute("label", exact.label);
+ }
+ if (typeof exact.method == "string") {
+ eelem.setAttribute("method", exact.method);
+ }
+
+ renderElem.appendChild(eelem);
+
+ if (typeof exact.symbol == "object") {
+ var selem = null;
+
+ if (exact.symbol.type == "simplemarker") {
+ selem = this.createElementNS("", "SIMPLEMARKERSYMBOL");
+ }
+
+ if (selem != null) {
+ if (typeof exact.symbol.antialiasing == "string") {
+ selem.setAttribute("antialiasing", exact.symbol.antialiasing);
+ }
+ if (typeof exact.symbol.color == "string") {
+ selem.setAttribute("color", exact.symbol.color);
+ }
+ if (typeof exact.symbol.outline == "string") {
+ selem.setAttribute("outline", exact.symbol.outline);
+ }
+ if (typeof exact.symbol.overlap == "string") {
+ selem.setAttribute("overlap", exact.symbol.overlap);
+ }
+ if (typeof exact.symbol.shadow == "string") {
+ selem.setAttribute("shadow", exact.symbol.shadow);
+ }
+ if (typeof exact.symbol.transparency == "number") {
+ selem.setAttribute("transparency", exact.symbol.transparency);
+ }
+ //if (typeof exact.symbol.type == "string")
+ // selem.setAttribute("type", exact.symbol.type);
+ if (typeof exact.symbol.usecentroid == "string") {
+ selem.setAttribute("usecentroid", exact.symbol.usecentroid);
+ }
+ if (typeof exact.symbol.width == "number") {
+ selem.setAttribute("width", exact.symbol.width);
+ }
+
+ eelem.appendChild(selem);
+ }
+ }
+ } // for each exact
+ }
+ },
+
+
+ addSimpleLabelRenderer: function(renderElem, renderer) {
+ renderElem.setAttribute("field", renderer.field);
+ var keys = ['featureweight', 'howmanylabels', 'labelbufferratio',
+ 'labelpriorities', 'labelweight', 'linelabelposition',
+ 'rotationalangles'];
+ for (var i=0, len=keys.length; i<len; i++) {
+ var key = keys[i];
+ if (renderer[key]) {
+ renderElem.setAttribute(key, renderer[key]);
+ }
+ }
+
+ if (renderer.symbol.type == "text") {
+ var symbol = renderer.symbol;
+ var selem = this.createElementNS("", "TEXTSYMBOL");
+ renderElem.appendChild(selem);
+
+ var keys = this.fontStyleKeys;
+ for (var i=0, len=keys.length; i<len; i++) {
+ var key = keys[i];
+ if (symbol[key]) {
+ selem.setAttribute(key, renderer[key]);
+ }
+ }
+ }
+ },
+
+ writePolygonGeometry: function(polygon) {
+ if (!(polygon instanceof OpenLayers.Geometry.Polygon)) {
+ throw {
+ message:'Cannot write polygon geometry to ArcXML with an ' +
+ polygon.CLASS_NAME + ' object.',
+ geometry: polygon
+ };
+ }
+
+ var polyElem = this.createElementNS("", "POLYGON");
+
+ for (var ln=0, lnlen=polygon.components.length; ln<lnlen; ln++) {
+ var ring = polygon.components[ln];
+ var ringElem = this.createElementNS("", "RING");
+
+ for (var rn=0, rnlen=ring.components.length; rn<rnlen; rn++) {
+ var point = ring.components[rn];
+ var pointElem = this.createElementNS("", "POINT");
+
+ pointElem.setAttribute("x", point.x);
+ pointElem.setAttribute("y", point.y);
+
+ ringElem.appendChild(pointElem);
+ }
+
+ polyElem.appendChild(ringElem);
+ }
+
+ return polyElem;
+ },
+
+ /**
+ * Method: parseResponse
+ * Take an ArcXML response, and parse in into this object's internal properties.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} The ArcXML response, as either a string or the
+ * top level DOMElement of the response.
+ */
+ parseResponse: function(data) {
+ if(typeof data == "string") {
+ var newData = new OpenLayers.Format.XML();
+ data = newData.read(data);
+ }
+ var response = new OpenLayers.Format.ArcXML.Response();
+
+ var errorNode = data.getElementsByTagName("ERROR");
+
+ if (errorNode != null && errorNode.length > 0) {
+ response.error = this.getChildValue(errorNode, "Unknown error.");
+ } else {
+ var responseNode = data.getElementsByTagName("RESPONSE");
+
+ if (responseNode == null || responseNode.length == 0) {
+ response.error = "No RESPONSE tag found in ArcXML response.";
+ return response;
+ }
+
+ var rtype = responseNode[0].firstChild.nodeName;
+ if (rtype == "#text") {
+ rtype = responseNode[0].firstChild.nextSibling.nodeName;
+ }
+
+ if (rtype == "IMAGE") {
+ var envelopeNode = data.getElementsByTagName("ENVELOPE");
+ var outputNode = data.getElementsByTagName("OUTPUT");
+
+ if (envelopeNode == null || envelopeNode.length == 0) {
+ response.error = "No ENVELOPE tag found in ArcXML response.";
+ } else if (outputNode == null || outputNode.length == 0) {
+ response.error = "No OUTPUT tag found in ArcXML response.";
+ } else {
+ var envAttr = this.parseAttributes(envelopeNode[0]);
+ var outputAttr = this.parseAttributes(outputNode[0]);
+
+ if (typeof outputAttr.type == "string") {
+ response.image = {
+ envelope: envAttr,
+ output: {
+ type: outputAttr.type,
+ data: this.getChildValue(outputNode[0])
+ }
+ };
+ } else {
+ response.image = { envelope: envAttr, output: outputAttr };
+ }
+ }
+ } else if (rtype == "FEATURES") {
+ var features = responseNode[0].getElementsByTagName("FEATURES");
+
+ // get the feature count
+ var featureCount = features[0].getElementsByTagName("FEATURECOUNT");
+ response.features.featurecount = featureCount[0].getAttribute("count");
+
+ if (response.features.featurecount > 0) {
+ // get the feature envelope
+ var envelope = features[0].getElementsByTagName("ENVELOPE");
+ response.features.envelope = this.parseAttributes(envelope[0], typeof(0));
+
+ // get the field values per feature
+ var featureList = features[0].getElementsByTagName("FEATURE");
+ for (var fn = 0; fn < featureList.length; fn++) {
+ var feature = new OpenLayers.Feature.Vector();
+ var fields = featureList[fn].getElementsByTagName("FIELD");
+
+ for (var fdn = 0; fdn < fields.length; fdn++) {
+ var fieldName = fields[fdn].getAttribute("name");
+ var fieldValue = fields[fdn].getAttribute("value");
+ feature.attributes[ fieldName ] = fieldValue;
+ }
+
+ var geom = featureList[fn].getElementsByTagName("POLYGON");
+
+ if (geom.length > 0) {
+ // if there is a polygon, create an openlayers polygon, and assign
+ // it to the .geometry property of the feature
+ var ring = geom[0].getElementsByTagName("RING");
+
+ var polys = [];
+ for (var rn = 0; rn < ring.length; rn++) {
+ var linearRings = [];
+ linearRings.push(this.parsePointGeometry(ring[rn]));
+
+ var holes = ring[rn].getElementsByTagName("HOLE");
+ for (var hn = 0; hn < holes.length; hn++) {
+ linearRings.push(this.parsePointGeometry(holes[hn]));
+ }
+ holes = null;
+ polys.push(new OpenLayers.Geometry.Polygon(linearRings));
+ linearRings = null;
+ }
+ ring = null;
+
+ if (polys.length == 1) {
+ feature.geometry = polys[0];
+ } else
+ {
+ feature.geometry = new OpenLayers.Geometry.MultiPolygon(polys);
+ }
+ }
+
+ response.features.feature.push(feature);
+ }
+ }
+ } else {
+ response.error = "Unidentified response type.";
+ }
+ }
+ return response;
+ },
+
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {<DOMElement>} An element to parse attributes from.
+ *
+ * Returns:
+ * {Object} An attributes object, with properties set to attribute values.
+ */
+ parseAttributes: function(node,type) {
+ var attributes = {};
+ for(var attr = 0; attr < node.attributes.length; attr++) {
+ if (type == "number") {
+ attributes[node.attributes[attr].nodeName] = parseFloat(node.attributes[attr].nodeValue);
+ } else {
+ attributes[node.attributes[attr].nodeName] = node.attributes[attr].nodeValue;
+ }
+ }
+ return attributes;
+ },
+
+
+ /**
+ * Method: parsePointGeometry
+ *
+ * Parameters:
+ * node - {<DOMElement>} An element to parse <COORDS> or <POINT> arcxml data from.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LinearRing>} A linear ring represented by the node's points.
+ */
+ parsePointGeometry: function(node) {
+ var ringPoints = [];
+ var coords = node.getElementsByTagName("COORDS");
+
+ if (coords.length > 0) {
+ // if coords is present, it's the only coords item
+ var coordArr = this.getChildValue(coords[0]);
+ coordArr = coordArr.split(/;/);
+ for (var cn = 0; cn < coordArr.length; cn++) {
+ var coordItems = coordArr[cn].split(/ /);
+ ringPoints.push(new OpenLayers.Geometry.Point(coordItems[0], coordItems[1]));
+ }
+ coords = null;
+ } else {
+ var point = node.getElementsByTagName("POINT");
+ if (point.length > 0) {
+ for (var pn = 0; pn < point.length; pn++) {
+ ringPoints.push(
+ new OpenLayers.Geometry.Point(
+ parseFloat(point[pn].getAttribute("x")),
+ parseFloat(point[pn].getAttribute("y"))
+ )
+ );
+ }
+ }
+ point = null;
+ }
+
+ return new OpenLayers.Geometry.LinearRing(ringPoints);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.ArcXML"
+});
+
+OpenLayers.Format.ArcXML.Request = OpenLayers.Class({
+ initialize: function(params) {
+ var defaults = {
+ get_image: {
+ properties: {
+ background: null,
+ /*{
+ color: { r:255, g:255, b:255 },
+ transcolor: null
+ },*/
+ draw: true,
+ envelope: {
+ minx: 0,
+ miny: 0,
+ maxx: 0,
+ maxy: 0
+ },
+ featurecoordsys: {
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ filtercoordsys:{
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ imagesize:{
+ height:0,
+ width:0,
+ dpi:96,
+ printheight:0,
+ printwidth:0,
+ scalesymbols:false
+ },
+ layerlist:[],
+ /* no support for legends */
+ output:{
+ baseurl:"",
+ legendbaseurl:"",
+ legendname:"",
+ legendpath:"",
+ legendurl:"",
+ name:"",
+ path:"",
+ type:"jpg",
+ url:""
+ }
+ }
+ },
+
+ get_feature: {
+ layer: "",
+ query: {
+ isspatial: false,
+ featurecoordsys: {
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ filtercoordsys: {
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ buffer:0,
+ where:"",
+ spatialfilter: {
+ relation: "envelope_intersection",
+ envelope: null
+ }
+ }
+ },
+
+ environment: {
+ separators: {
+ cs:" ",
+ ts:";"
+ }
+ },
+
+ layer: [],
+ workspaces: []
+ };
+
+ return OpenLayers.Util.extend(this, defaults);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.ArcXML.Request"
+});
+
+OpenLayers.Format.ArcXML.Response = OpenLayers.Class({
+ initialize: function(params) {
+ var defaults = {
+ image: {
+ envelope:null,
+ output:''
+ },
+
+ features: {
+ featurecount: 0,
+ envelope: null,
+ feature: []
+ },
+
+ error:''
+ };
+
+ return OpenLayers.Util.extend(this, defaults);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.ArcXML.Response"
+});
+/* ======================================================================
+ OpenLayers/Request/XMLHttpRequest.js
+ ====================================================================== */
+
+// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+ // Save reference to earlier defined object implementation (if any)
+ var oXMLHttpRequest = window.XMLHttpRequest;
+
+ // Define on browser type
+ var bGecko = !!window.controllers,
+ bIE = window.document.all && !window.opera,
+ bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
+
+ // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
+ function fXMLHttpRequest() {
+ this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
+ this._listeners = [];
+ };
+
+ // Constructor
+ function cXMLHttpRequest() {
+ return new fXMLHttpRequest;
+ };
+ cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
+
+ // BUGFIX: Firefox with Firebug installed would break pages if not executed
+ if (bGecko && oXMLHttpRequest.wrapped)
+ cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
+
+ // Constants
+ cXMLHttpRequest.UNSENT = 0;
+ cXMLHttpRequest.OPENED = 1;
+ cXMLHttpRequest.HEADERS_RECEIVED = 2;
+ cXMLHttpRequest.LOADING = 3;
+ cXMLHttpRequest.DONE = 4;
+
+ // Public Properties
+ cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
+ cXMLHttpRequest.prototype.responseText = '';
+ cXMLHttpRequest.prototype.responseXML = null;
+ cXMLHttpRequest.prototype.status = 0;
+ cXMLHttpRequest.prototype.statusText = '';
+
+ // Priority proposal
+ cXMLHttpRequest.prototype.priority = "NORMAL";
+
+ // Instance-level Events Handlers
+ cXMLHttpRequest.prototype.onreadystatechange = null;
+
+ // Class-level Events Handlers
+ cXMLHttpRequest.onreadystatechange = null;
+ cXMLHttpRequest.onopen = null;
+ cXMLHttpRequest.onsend = null;
+ cXMLHttpRequest.onabort = null;
+
+ // Public Methods
+ cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+ // Delete headers, required when object is reused
+ delete this._headers;
+
+ // When bAsync parameter value is omitted, use true as default
+ if (arguments.length < 3)
+ bAsync = true;
+
+ // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+ this._async = bAsync;
+
+ // Set the onreadystatechange handler
+ var oRequest = this,
+ nState = this.readyState,
+ fOnUnload;
+
+ // BUGFIX: IE - memory leak on page unload (inter-page leak)
+ if (bIE && bAsync) {
+ fOnUnload = function() {
+ if (nState != cXMLHttpRequest.DONE) {
+ fCleanTransport(oRequest);
+ // Safe to abort here since onreadystatechange handler removed
+ oRequest.abort();
+ }
+ };
+ window.attachEvent("onunload", fOnUnload);
+ }
+
+ // Add method sniffer
+ if (cXMLHttpRequest.onopen)
+ cXMLHttpRequest.onopen.apply(this, arguments);
+
+ if (arguments.length > 4)
+ this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ if (arguments.length > 3)
+ this._object.open(sMethod, sUrl, bAsync, sUser);
+ else
+ this._object.open(sMethod, sUrl, bAsync);
+
+ this.readyState = cXMLHttpRequest.OPENED;
+ fReadyStateChange(this);
+
+ this._object.onreadystatechange = function() {
+ if (bGecko && !bAsync)
+ return;
+
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ // BUGFIX: Firefox fires unnecessary DONE when aborting
+ if (oRequest._aborted) {
+ // Reset readyState to UNSENT
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return now
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Free up queue
+ delete oRequest._data;
+/* if (bAsync)
+ fQueue_remove(oRequest);*/
+ //
+ fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+ // BUGFIX: IE - cache issue
+ if (!oRequest._object.getResponseHeader("Date")) {
+ // Save object to cache
+ oRequest._cached = oRequest._object;
+
+ // Instantiate a new transport object
+ cXMLHttpRequest.call(oRequest);
+
+ // Re-send request
+ if (sUser) {
+ if (sPassword)
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser);
+ }
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync);
+ oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+ // Copy headers set
+ if (oRequest._headers)
+ for (var sHeader in oRequest._headers)
+ if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions
+ oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+ oRequest._object.onreadystatechange = function() {
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ if (oRequest._aborted) {
+ //
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Clean Object
+ fCleanTransport(oRequest);
+
+ // get cached request
+ if (oRequest.status == 304)
+ oRequest._object = oRequest._cached;
+
+ //
+ delete oRequest._cached;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ //
+ fReadyStateChange(oRequest);
+
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+ };
+ oRequest._object.send(null);
+
+ // Return now - wait until re-sent request is finished
+ return;
+ };
+*/
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+
+ // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+ if (nState != oRequest.readyState)
+ fReadyStateChange(oRequest);
+
+ nState = oRequest.readyState;
+ }
+ };
+ function fXMLHttpRequest_send(oRequest) {
+ oRequest._object.send(oRequest._data);
+
+ // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+ if (bGecko && !oRequest._async) {
+ oRequest.readyState = cXMLHttpRequest.OPENED;
+
+ // Synchronize state
+ fSynchronizeValues(oRequest);
+
+ // Simulate missing states
+ while (oRequest.readyState < cXMLHttpRequest.DONE) {
+ oRequest.readyState++;
+ fReadyStateChange(oRequest);
+ // Check if we are aborted
+ if (oRequest._aborted)
+ return;
+ }
+ }
+ };
+ cXMLHttpRequest.prototype.send = function(vData) {
+ // Add method sniffer
+ if (cXMLHttpRequest.onsend)
+ cXMLHttpRequest.onsend.apply(this, arguments);
+
+ if (!arguments.length)
+ vData = null;
+
+ // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+ // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+ // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+ if (vData && vData.nodeType) {
+ vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+ if (!this._headers["Content-Type"])
+ this._object.setRequestHeader("Content-Type", "application/xml");
+ }
+
+ this._data = vData;
+/*
+ // Add to queue
+ if (this._async)
+ fQueue_add(this);
+ else*/
+ fXMLHttpRequest_send(this);
+ };
+ cXMLHttpRequest.prototype.abort = function() {
+ // Add method sniffer
+ if (cXMLHttpRequest.onabort)
+ cXMLHttpRequest.onabort.apply(this, arguments);
+
+ // BUGFIX: Gecko - unnecessary DONE when aborting
+ if (this.readyState > cXMLHttpRequest.UNSENT)
+ this._aborted = true;
+
+ this._object.abort();
+
+ // BUGFIX: IE - memory leak
+ fCleanTransport(this);
+
+ this.readyState = cXMLHttpRequest.UNSENT;
+
+ delete this._data;
+/* if (this._async)
+ fQueue_remove(this);*/
+ };
+ cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
+ return this._object.getAllResponseHeaders();
+ };
+ cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
+ return this._object.getResponseHeader(sName);
+ };
+ cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
+ // BUGFIX: IE - cache issue
+ if (!this._headers)
+ this._headers = {};
+ this._headers[sName] = sValue;
+
+ return this._object.setRequestHeader(sName, sValue);
+ };
+
+ // EventTarget interface implementation
+ cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ return;
+ // Add listener
+ this._listeners.push([sName, fHandler, bUseCapture]);
+ };
+
+ cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ break;
+ // Remove listener
+ if (oListener)
+ this._listeners.splice(nIndex, 1);
+ };
+
+ cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
+ var oEventPseudo = {
+ 'type': oEvent.type,
+ 'target': this,
+ 'currentTarget':this,
+ 'eventPhase': 2,
+ 'bubbles': oEvent.bubbles,
+ 'cancelable': oEvent.cancelable,
+ 'timeStamp': oEvent.timeStamp,
+ 'stopPropagation': function() {}, // There is no flow
+ 'preventDefault': function() {}, // There is no default action
+ 'initEvent': function() {} // Original event object should be initialized
+ };
+
+ // Execute onreadystatechange
+ if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
+ (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
+
+ // Execute listeners
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == oEventPseudo.type && !oListener[2])
+ (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
+ };
+
+ //
+ cXMLHttpRequest.prototype.toString = function() {
+ return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+ };
+
+ cXMLHttpRequest.toString = function() {
+ return '[' + "XMLHttpRequest" + ']';
+ };
+
+ // Helper function
+ function fReadyStateChange(oRequest) {
+ // Sniffing code
+ if (cXMLHttpRequest.onreadystatechange)
+ cXMLHttpRequest.onreadystatechange.apply(oRequest);
+
+ // Fake event
+ oRequest.dispatchEvent({
+ 'type': "readystatechange",
+ 'bubbles': false,
+ 'cancelable': false,
+ 'timeStamp': new Date + 0
+ });
+ };
+
+ function fGetDocument(oRequest) {
+ var oDocument = oRequest.responseXML,
+ sResponse = oRequest.responseText;
+ // Try parsing responseText
+ if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+ oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
+ oDocument.async = false;
+ oDocument.validateOnParse = false;
+ oDocument.loadXML(sResponse);
+ }
+ // Check if there is no error in document
+ if (oDocument)
+ if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+ return null;
+ return oDocument;
+ };
+
+ function fSynchronizeValues(oRequest) {
+ try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
+ try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
+ try { oRequest.status = oRequest._object.status; } catch (e) {}
+ try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
+ };
+
+ function fCleanTransport(oRequest) {
+ // BUGFIX: IE - memory leak (on-page leak)
+ oRequest._object.onreadystatechange = new window.Function;
+ };
+/*
+ // Queue manager
+ var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
+ aQueueRunning = [];
+ function fQueue_add(oRequest) {
+ oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_remove(oRequest) {
+ for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
+ if (bFound)
+ aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
+ else
+ if (aQueueRunning[nIndex] == oRequest)
+ bFound = true;
+ if (bFound)
+ aQueueRunning.length--;
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_process() {
+ if (aQueueRunning.length < 6) {
+ for (var sPriority in oQueuePending) {
+ if (oQueuePending[sPriority].length) {
+ var oRequest = oQueuePending[sPriority][0];
+ oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
+ //
+ aQueueRunning.push(oRequest);
+ // Send request
+ fXMLHttpRequest_send(oRequest);
+ break;
+ }
+ }
+ }
+ };
+*/
+ // Internet Explorer 5.0 (missing apply)
+ if (!window.Function.prototype.apply) {
+ window.Function.prototype.apply = function(oRequest, oArguments) {
+ if (!oArguments)
+ oArguments = [];
+ oRequest.__func = this;
+ oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+ delete oRequest.__func;
+ };
+ };
+
+ // Register new object with window
+ /**
+ * Class: OpenLayers.Request.XMLHttpRequest
+ * Standard-compliant (W3C) cross-browser implementation of the
+ * XMLHttpRequest object. From
+ * http://code.google.com/p/xmlhttprequest/.
+ */
+ if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+ }
+ OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
+/* ======================================================================
+ OpenLayers/Request.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * TODO: deprecate me
+ * Use OpenLayers.Request.proxy instead.
+ */
+OpenLayers.ProxyHost = "";
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ * with XMLHttpRequests. These methods work with a cross-browser
+ * W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
+ */
+if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request/XMLHttpRequest.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+}
+OpenLayers.Util.extend(OpenLayers.Request, {
+
+ /**
+ * Constant: DEFAULT_CONFIG
+ * {Object} Default configuration for all requests.
+ */
+ DEFAULT_CONFIG: {
+ method: "GET",
+ url: window.location.href,
+ async: true,
+ user: undefined,
+ password: undefined,
+ params: null,
+ proxy: OpenLayers.ProxyHost,
+ headers: {},
+ data: null,
+ callback: function() {},
+ success: null,
+ failure: null,
+ scope: null
+ },
+
+ /**
+ * Constant: URL_SPLIT_REGEX
+ */
+ URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the {<OpenLayers.Request>} object.
+ *
+ * All event listeners will receive an event object with three properties:
+ * request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
+ * config - {Object} The config object sent to the specific request method.
+ * requestUrl - {String} The request url.
+ *
+ * Supported event types:
+ * complete - Triggered when we have a response from the request, if a
+ * listener returns false, no further response processing will take
+ * place.
+ * success - Triggered when the HTTP response has a success code (200-299).
+ * failure - Triggered when the HTTP response does not have a success code.
+ */
+ events: new OpenLayers.Events(this),
+
+ /**
+ * Method: makeSameOrigin
+ * Using the specified proxy, returns a same origin url of the provided url.
+ *
+ * Parameters:
+ * url - {String} An arbitrary url
+ * proxy {String|Function} The proxy to use to make the provided url a
+ * same origin url.
+ *
+ * Returns
+ * {String} the same origin url. If no proxy is provided, the returned url
+ * will be the same as the provided url.
+ */
+ makeSameOrigin: function(url, proxy) {
+ var sameOrigin = url.indexOf("http") !== 0;
+ var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
+ if (urlParts) {
+ var location = window.location;
+ sameOrigin =
+ urlParts[1] == location.protocol &&
+ urlParts[3] == location.hostname;
+ var uPort = urlParts[4], lPort = location.port;
+ if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
+ sameOrigin = sameOrigin && uPort == lPort;
+ }
+ }
+ if (!sameOrigin) {
+ if (proxy) {
+ if (typeof proxy == "function") {
+ url = proxy(url);
+ } else {
+ url = proxy + encodeURIComponent(url);
+ }
+ }
+ }
+ return url;
+ },
+
+ /**
+ * APIMethod: issue
+ * Create a new XMLHttpRequest object, open it, set any headers, bind
+ * a callback to done state, and send any data. It is recommended that
+ * you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
+ * This method is only documented to provide detail on the configuration
+ * options available to all request methods.
+ *
+ * Parameters:
+ * config - {Object} Object containing properties for configuring the
+ * request. Allowed configuration properties are described below.
+ * This object is modified and should not be reused.
+ *
+ * Allowed config properties:
+ * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
+ * OPTIONS. Default is GET.
+ * url - {String} URL for the request.
+ * async - {Boolean} Open an asynchronous request. Default is true.
+ * user - {String} User for relevant authentication scheme. Set
+ * to null to clear current user.
+ * password - {String} Password for relevant authentication scheme.
+ * Set to null to clear current password.
+ * proxy - {String} Optional proxy. Defaults to
+ * <OpenLayers.ProxyHost>.
+ * params - {Object} Any key:value pairs to be appended to the
+ * url as a query string. Assumes url doesn't already include a query
+ * string or hash. Typically, this is only appropriate for <GET>
+ * requests where the query string will be appended to the url.
+ * Parameter values that are arrays will be
+ * concatenated with a comma (note that this goes against form-encoding)
+ * as is done with <OpenLayers.Util.getParameterString>.
+ * headers - {Object} Object with header:value pairs to be set on
+ * the request.
+ * data - {String | Document} Optional data to send with the request.
+ * Typically, this is only used with <POST> and <PUT> requests.
+ * Make sure to provide the appropriate "Content-Type" header for your
+ * data. For <POST> and <PUT> requests, the content type defaults to
+ * "application-xml". If your data is a different content type, or
+ * if you are using a different HTTP method, set the "Content-Type"
+ * header to match your data type.
+ * callback - {Function} Function to call when request is done.
+ * To determine if the request failed, check request.status (200
+ * indicates success).
+ * success - {Function} Optional function to call if request status is in
+ * the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * failure - {Function} Optional function to call if request status is not
+ * in the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * scope - {Object} If callback is a public method on some object,
+ * set the scope to that object.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object. To abort the request before a response
+ * is received, call abort() on the request object.
+ */
+ issue: function(config) {
+ // apply default config - proxy host may have changed
+ var defaultConfig = OpenLayers.Util.extend(
+ this.DEFAULT_CONFIG,
+ {proxy: OpenLayers.ProxyHost}
+ );
+ config = config || {};
+ config.headers = config.headers || {};
+ config = OpenLayers.Util.applyDefaults(config, defaultConfig);
+ config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
+ // Always set the "X-Requested-With" header to signal that this request
+ // was issued through the XHR-object. Since header keys are case
+ // insensitive and we want to allow overriding of the "X-Requested-With"
+ // header through the user we cannot use applyDefaults, but have to
+ // check manually whether we were called with a "X-Requested-With"
+ // header.
+ var customRequestedWithHeader = false,
+ headerKey;
+ for(headerKey in config.headers) {
+ if (config.headers.hasOwnProperty( headerKey )) {
+ if (headerKey.toLowerCase() === 'x-requested-with') {
+ customRequestedWithHeader = true;
+ }
+ }
+ }
+ if (customRequestedWithHeader === false) {
+ // we did not have a custom "X-Requested-With" header
+ config.headers['X-Requested-With'] = 'XMLHttpRequest';
+ }
+
+ // create request, open, and set headers
+ var request = new OpenLayers.Request.XMLHttpRequest();
+ var url = OpenLayers.Util.urlAppend(config.url,
+ OpenLayers.Util.getParameterString(config.params || {}));
+ url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
+ request.open(
+ config.method, url, config.async, config.user, config.password
+ );
+ for(var header in config.headers) {
+ request.setRequestHeader(header, config.headers[header]);
+ }
+
+ var events = this.events;
+
+ // we want to execute runCallbacks with "this" as the
+ // execution scope
+ var self = this;
+
+ request.onreadystatechange = function() {
+ if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
+ var proceed = events.triggerEvent(
+ "complete",
+ {request: request, config: config, requestUrl: url}
+ );
+ if(proceed !== false) {
+ self.runCallbacks(
+ {request: request, config: config, requestUrl: url}
+ );
+ }
+ }
+ };
+
+ // send request (optionally with data) and return
+ // call in a timeout for asynchronous requests so the return is
+ // available before readyState == 4 for cached docs
+ if(config.async === false) {
+ request.send(config.data);
+ } else {
+ window.setTimeout(function(){
+ if (request.readyState !== 0) { // W3C: 0-UNSENT
+ request.send(config.data);
+ }
+ }, 0);
+ }
+ return request;
+ },
+
+ /**
+ * Method: runCallbacks
+ * Calls the complete, success and failure callbacks. Application
+ * can listen to the "complete" event, have the listener
+ * display a confirm window and always return false, and
+ * execute OpenLayers.Request.runCallbacks if the user
+ * hits "yes" in the confirm window.
+ *
+ * Parameters:
+ * options - {Object} Hash containing request, config and requestUrl keys
+ */
+ runCallbacks: function(options) {
+ var request = options.request;
+ var config = options.config;
+
+ // bind callbacks to readyState 4 (done)
+ var complete = (config.scope) ?
+ OpenLayers.Function.bind(config.callback, config.scope) :
+ config.callback;
+
+ // optional success callback
+ var success;
+ if(config.success) {
+ success = (config.scope) ?
+ OpenLayers.Function.bind(config.success, config.scope) :
+ config.success;
+ }
+
+ // optional failure callback
+ var failure;
+ if(config.failure) {
+ failure = (config.scope) ?
+ OpenLayers.Function.bind(config.failure, config.scope) :
+ config.failure;
+ }
+
+ if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
+ request.responseText) {
+ request.status = 200;
+ }
+ complete(request);
+
+ if (!request.status || (request.status >= 200 && request.status < 300)) {
+ this.events.triggerEvent("success", options);
+ if(success) {
+ success(request);
+ }
+ }
+ if(request.status && (request.status < 200 || request.status >= 300)) {
+ this.events.triggerEvent("failure", options);
+ if(failure) {
+ failure(request);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: GET
+ * Send an HTTP GET request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to GET.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ GET: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "GET"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: POST
+ * Send a POST request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to POST and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ POST: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "POST"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: PUT
+ * Send an HTTP PUT request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to PUT and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ PUT: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "PUT"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: DELETE
+ * Send an HTTP DELETE request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to DELETE.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ DELETE: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "DELETE"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: HEAD
+ * Send an HTTP HEAD request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to HEAD.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ HEAD: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "HEAD"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: OPTIONS
+ * Send an HTTP OPTIONS request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to OPTIONS.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ OPTIONS: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+ return OpenLayers.Request.issue(config);
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Layer/ArcIMS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Format/ArcXML.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.ArcIMS
+ * Instances of OpenLayers.Layer.ArcIMS are used to display data from ESRI ArcIMS
+ * Mapping Services. Create a new ArcIMS layer with the <OpenLayers.Layer.ArcIMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.ArcIMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Default query string parameters.
+ */
+ DEFAULT_PARAMS: {
+ ClientVersion: "9.2",
+ ServiceName: ''
+ },
+
+ /**
+ * APIProperty: featureCoordSys
+ * {String} Code for feature coordinate system. Default is "4326".
+ */
+ featureCoordSys: "4326",
+
+ /**
+ * APIProperty: filterCoordSys
+ * {String} Code for filter coordinate system. Default is "4326".
+ */
+ filterCoordSys: "4326",
+
+ /**
+ * APIProperty: layers
+ * {Array} An array of objects with layer properties.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: async
+ * {Boolean} Request images asynchronously. Default is true.
+ */
+ async: true,
+
+ /**
+ * APIProperty: name
+ * {String} Layer name. Default is "ArcIMS".
+ */
+ name: "ArcIMS",
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is true.
+ */
+ isBaseLayer: true,
+
+ /**
+ * Constant: DEFAULT_OPTIONS
+ * {Object} Default layers properties.
+ */
+ DEFAULT_OPTIONS: {
+ tileSize: new OpenLayers.Size(512, 512),
+ featureCoordSys: "4326",
+ filterCoordSys: "4326",
+ layers: null,
+ isBaseLayer: true,
+ async: true,
+ name: "ArcIMS"
+ },
+
+ /**
+ * Constructor: OpenLayers.Layer.ArcIMS
+ * Create a new ArcIMS layer object.
+ *
+ * Example:
+ * (code)
+ * var arcims = new OpenLayers.Layer.ArcIMS(
+ * "Global Sample",
+ * "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap",
+ * {
+ * service: "OpenLayers_Sample",
+ * layers: [
+ * // layers to manipulate
+ * {id: "1", visible: true}
+ * ]
+ * }
+ * );
+ * (end)
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the ArcIMS server
+ * options - {Object} Optional object with properties to be set on the
+ * layer.
+ */
+ initialize: function(name, url, options) {
+
+ this.tileSize = new OpenLayers.Size(512, 512);
+
+ // parameters
+ this.params = OpenLayers.Util.applyDefaults(
+ {ServiceName: options.serviceName},
+ this.DEFAULT_PARAMS
+ );
+ this.options = OpenLayers.Util.applyDefaults(
+ options, this.DEFAULT_OPTIONS
+ );
+
+ OpenLayers.Layer.Grid.prototype.initialize.apply(
+ this, [name, url, this.params, options]
+ );
+
+ //layer is transparent
+ if (this.transparent) {
+
+ // unless explicitly set in options, make layer an overlay
+ if (!this.isBaseLayer) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.format == "image/jpeg") {
+ this.format = OpenLayers.Util.alphaHack() ? "image/gif" : "image/png";
+ }
+ }
+
+ // create an empty layer list if no layers specified in the options
+ if (this.options.layers === null) {
+ this.options.layers = [];
+ }
+ },
+
+ /**
+ * Method: getURL
+ * Return an image url this layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the map image's url.
+ */
+ getURL: function(bounds) {
+ var url = "";
+ bounds = this.adjustBounds(bounds);
+
+ // create an arcxml request to generate the image
+ var axlReq = new OpenLayers.Format.ArcXML(
+ OpenLayers.Util.extend(this.options, {
+ requesttype: "image",
+ envelope: bounds.toArray(),
+ tileSize: this.tileSize
+ })
+ );
+
+ // create a synchronous ajax request to get an arcims image
+ var req = new OpenLayers.Request.POST({
+ url: this.getFullRequestString(),
+ data: axlReq.write(),
+ async: false
+ });
+
+ // if the response exists
+ if (req != null) {
+ var doc = req.responseXML;
+
+ if (!doc || !doc.documentElement) {
+ doc = req.responseText;
+ }
+
+ // create a new arcxml format to read the response
+ var axlResp = new OpenLayers.Format.ArcXML();
+ var arcxml = axlResp.read(doc);
+ url = this.getUrlOrImage(arcxml.image.output);
+ }
+
+ return url;
+ },
+
+
+ /**
+ * Method: getURLasync
+ * Get an image url this layer asynchronously, and execute a callback
+ * when the image url is generated.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ * callback - {Function} Function to call when image url is retrieved.
+ * scope - {Object} The scope of the callback method.
+ */
+ getURLasync: function(bounds, callback, scope) {
+ bounds = this.adjustBounds(bounds);
+
+ // create an arcxml request to generate the image
+ var axlReq = new OpenLayers.Format.ArcXML(
+ OpenLayers.Util.extend(this.options, {
+ requesttype: "image",
+ envelope: bounds.toArray(),
+ tileSize: this.tileSize
+ })
+ );
+
+ // create an asynchronous ajax request to get an arcims image
+ OpenLayers.Request.POST({
+ url: this.getFullRequestString(),
+ async: true,
+ data: axlReq.write(),
+ callback: function(req) {
+ // process the response from ArcIMS, and call the callback function
+ // to set the image URL
+ var doc = req.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = req.responseText;
+ }
+
+ // create a new arcxml format to read the response
+ var axlResp = new OpenLayers.Format.ArcXML();
+ var arcxml = axlResp.read(doc);
+
+ callback.call(scope, this.getUrlOrImage(arcxml.image.output));
+ },
+ scope: this
+ });
+ },
+
+ /**
+ * Method: getUrlOrImage
+ * Extract a url or image from the ArcXML image output.
+ *
+ * Parameters:
+ * output - {Object} The image.output property of the object returned from
+ * the ArcXML format read method.
+ *
+ * Returns:
+ * {String} A URL for an image (potentially with the data protocol).
+ */
+ getUrlOrImage: function(output) {
+ var ret = "";
+ if(output.url) {
+ // If the image response output url is a string, then the image
+ // data is not inline.
+ ret = output.url;
+ } else if(output.data) {
+ // The image data is inline and base64 encoded, create a data
+ // url for the image. This will only work for small images,
+ // due to browser url length limits.
+ ret = "data:image/" + output.type +
+ ";base64," + output.data;
+ }
+ return ret;
+ },
+
+ /**
+ * Method: setLayerQuery
+ * Set the query definition on this layer. Query definitions are used to
+ * render parts of the spatial data in an image, and can be used to
+ * filter features or layers in the ArcIMS service.
+ *
+ * Parameters:
+ * id - {String} The ArcIMS layer ID.
+ * querydef - {Object} The query definition to apply to this layer.
+ */
+ setLayerQuery: function(id, querydef) {
+ // find the matching layer, if it exists
+ for (var lyr = 0; lyr < this.options.layers.length; lyr++) {
+ if (id == this.options.layers[lyr].id) {
+ // replace this layer definition
+ this.options.layers[lyr].query = querydef;
+ return;
+ }
+ }
+
+ // no layer found, create a new definition
+ this.options.layers.push({id: id, visible: true, query: querydef});
+ },
+
+ /**
+ * Method: getFeatureInfo
+ * Get feature information from ArcIMS. Using the applied geometry, apply
+ * the options to the query (buffer, area/envelope intersection), and
+ * query the ArcIMS service.
+ *
+ * A note about accuracy:
+ * ArcIMS interprets the accuracy attribute in feature requests to be
+ * something like the 'modulus' operator on feature coordinates,
+ * applied to the database geometry of the feature. It doesn't round,
+ * so your feature coordinates may be up to (1 x accuracy) offset from
+ * the actual feature coordinates. If the accuracy of the layer is not
+ * specified, the accuracy will be computed to be approximately 1
+ * feature coordinate per screen pixel.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.LonLat>} or {<OpenLayers.Geometry.Polygon>} The
+ * geometry to use when making the query. This should be a closed
+ * polygon for behavior approximating a free selection.
+ * layer - {Object} The ArcIMS layer definition. This is an anonymous object
+ * that looks like:
+ * (code)
+ * {
+ * id: "ArcXML layer ID", // the ArcXML layer ID
+ * query: {
+ * where: "STATE = 'PA'", // the where clause of the query
+ * accuracy: 100 // the accuracy of the returned feature
+ * }
+ * }
+ * (end)
+ * options - {Object} Object with non-default properties to set on the layer.
+ * Supported properties are buffer, callback, scope, and any other
+ * properties applicable to the ArcXML format. Set the 'callback' and
+ * 'scope' for an object and function to recieve the parsed features
+ * from ArcIMS.
+ */
+ getFeatureInfo: function(geometry, layer, options) {
+ // set the buffer to 1 unit (dd/m/ft?) by default
+ var buffer = options.buffer || 1;
+ // empty callback by default
+ var callback = options.callback || function() {};
+ // default scope is window (global)
+ var scope = options.scope || window;
+
+ // apply these option to the request options
+ var requestOptions = {};
+ OpenLayers.Util.extend(requestOptions, this.options);
+
+ // this is a feature request
+ requestOptions.requesttype = "feature";
+
+ if (geometry instanceof OpenLayers.LonLat) {
+ // create an envelope if the geometry is really a lon/lat
+ requestOptions.polygon = null;
+ requestOptions.envelope = [
+ geometry.lon - buffer,
+ geometry.lat - buffer,
+ geometry.lon + buffer,
+ geometry.lat + buffer
+ ];
+ } else if (geometry instanceof OpenLayers.Geometry.Polygon) {
+ // use the polygon assigned, and empty the envelope
+ requestOptions.envelope = null;
+ requestOptions.polygon = geometry;
+ }
+
+ // create an arcxml request to get feature requests
+ var arcxml = new OpenLayers.Format.ArcXML(requestOptions);
+
+ // apply any get feature options to the arcxml request
+ OpenLayers.Util.extend(arcxml.request.get_feature, options);
+
+ arcxml.request.get_feature.layer = layer.id;
+ if (typeof layer.query.accuracy == "number") {
+ // set the accuracy if it was specified
+ arcxml.request.get_feature.query.accuracy = layer.query.accuracy;
+ } else {
+ // guess that the accuracy is 1 per screen pixel
+ var mapCenter = this.map.getCenter();
+ var viewPx = this.map.getViewPortPxFromLonLat(mapCenter);
+ viewPx.x++;
+ var mapOffCenter = this.map.getLonLatFromPixel(viewPx);
+ arcxml.request.get_feature.query.accuracy = mapOffCenter.lon - mapCenter.lon;
+ }
+
+ // set the get_feature query to be the same as the layer passed in
+ arcxml.request.get_feature.query.where = layer.query.where;
+
+ // use area_intersection
+ arcxml.request.get_feature.query.spatialfilter.relation = "area_intersection";
+
+ // create a new asynchronous request to get the feature info
+ OpenLayers.Request.POST({
+ url: this.getFullRequestString({'CustomService': 'Query'}),
+ data: arcxml.write(),
+ callback: function(request) {
+ // parse the arcxml response
+ var response = arcxml.parseResponse(request.responseText);
+
+ if (!arcxml.iserror()) {
+ // if the arcxml is not an error, call the callback with the features parsed
+ callback.call(scope, response.features);
+ } else {
+ // if the arcxml is an error, return null features selected
+ callback.call(scope, null);
+ }
+ }
+ });
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.ArcIMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.ArcIMS(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.ArcIMS"
+});
+/* ======================================================================
+ OpenLayers/Control/PanZoom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanZoom
+ * The PanZoom is a visible control, composed of a
+ * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomPanel>. By
+ * default it is drawn in the upper left corner of the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.PanZoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: slideFactor
+ * {Integer} Number of pixels by which we'll pan the map in any direction
+ * on clicking the arrow buttons. If you want to pan by some ratio
+ * of the map dimensions, use <slideRatio> instead.
+ */
+ slideFactor: 50,
+
+ /**
+ * APIProperty: slideRatio
+ * {Number} The fraction of map width/height by which we'll pan the map
+ * on clicking the arrow buttons. Default is null. If set, will
+ * override <slideFactor>. E.g. if slideRatio is .5, then the Pan Up
+ * button will pan up half the map height.
+ */
+ slideRatio: null,
+
+ /**
+ * Property: buttons
+ * {Array(DOMElement)} Array of Button Divs
+ */
+ buttons: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>}
+ */
+ position: null,
+
+ /**
+ * Constructor: OpenLayers.Control.PanZoom
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ this.position = new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,
+ OpenLayers.Control.PanZoom.Y);
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onButtonClick);
+ }
+ this.removeButtons();
+ this.buttons = null;
+ this.position = null;
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Properties:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {DOMElement} A reference to the container div for the PanZoom control.
+ */
+ draw: function(px) {
+ // initialize our internal div
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ px = this.position;
+
+ // place the controls
+ this.buttons = [];
+
+ var sz = {w: 18, h: 18};
+ var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
+
+ this._addButton("panup", "north-mini.png", centered, sz);
+ px.y = centered.y+sz.h;
+ this._addButton("panleft", "west-mini.png", px, sz);
+ this._addButton("panright", "east-mini.png", px.add(sz.w, 0), sz);
+ this._addButton("pandown", "south-mini.png",
+ centered.add(0, sz.h*2), sz);
+ this._addButton("zoomin", "zoom-plus-mini.png",
+ centered.add(0, sz.h*3+5), sz);
+ this._addButton("zoomworld", "zoom-world-mini.png",
+ centered.add(0, sz.h*4+5), sz);
+ this._addButton("zoomout", "zoom-minus-mini.png",
+ centered.add(0, sz.h*5+5), sz);
+ return this.div;
+ },
+
+ /**
+ * Method: _addButton
+ *
+ * Parameters:
+ * id - {String}
+ * img - {String}
+ * xy - {<OpenLayers.Pixel>}
+ * sz - {<OpenLayers.Size>}
+ *
+ * Returns:
+ * {DOMElement} A Div (an alphaImageDiv, to be precise) that contains the
+ * image of the button, and has all the proper event handlers set.
+ */
+ _addButton:function(id, img, xy, sz) {
+ var imgLocation = OpenLayers.Util.getImageLocation(img);
+ var btn = OpenLayers.Util.createAlphaImageDiv(
+ this.id + "_" + id,
+ xy, sz, imgLocation, "absolute");
+ btn.style.cursor = "pointer";
+ //we want to add the outer div
+ this.div.appendChild(btn);
+ btn.action = id;
+ btn.className = "olButton";
+
+ //we want to remember/reference the outer div
+ this.buttons.push(btn);
+ return btn;
+ },
+
+ /**
+ * Method: _removeButton
+ *
+ * Parameters:
+ * btn - {Object}
+ */
+ _removeButton: function(btn) {
+ this.div.removeChild(btn);
+ OpenLayers.Util.removeItem(this.buttons, btn);
+ },
+
+ /**
+ * Method: removeButtons
+ */
+ removeButtons: function() {
+ for(var i=this.buttons.length-1; i>=0; --i) {
+ this._removeButton(this.buttons[i]);
+ }
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ var btn = evt.buttonElement;
+ switch (btn.action) {
+ case "panup":
+ this.map.pan(0, -this.getSlideFactor("h"));
+ break;
+ case "pandown":
+ this.map.pan(0, this.getSlideFactor("h"));
+ break;
+ case "panleft":
+ this.map.pan(-this.getSlideFactor("w"), 0);
+ break;
+ case "panright":
+ this.map.pan(this.getSlideFactor("w"), 0);
+ break;
+ case "zoomin":
+ this.map.zoomIn();
+ break;
+ case "zoomout":
+ this.map.zoomOut();
+ break;
+ case "zoomworld":
+ this.map.zoomToMaxExtent();
+ break;
+ }
+ },
+
+ /**
+ * Method: getSlideFactor
+ *
+ * Parameters:
+ * dim - {String} "w" or "h" (for width or height).
+ *
+ * Returns:
+ * {Number} The slide factor for panning in the requested direction.
+ */
+ getSlideFactor: function(dim) {
+ return this.slideRatio ?
+ this.map.getSize()[dim] * this.slideRatio :
+ this.slideFactor;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PanZoom"
+});
+
+/**
+ * Constant: X
+ * {Integer}
+ */
+OpenLayers.Control.PanZoom.X = 4;
+
+/**
+ * Constant: Y
+ * {Integer}
+ */
+OpenLayers.Control.PanZoom.Y = 4;
+/* ======================================================================
+ OpenLayers/Control/PanZoomBar.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control/PanZoom.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanZoomBar
+ * The PanZoomBar is a visible control composed of a
+ * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomBar>.
+ * By default it is displayed in the upper left corner of the map as 4
+ * directional arrows above a vertical slider.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.PanZoom>
+ */
+OpenLayers.Control.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoom, {
+
+ /**
+ * APIProperty: zoomStopWidth
+ */
+ zoomStopWidth: 18,
+
+ /**
+ * APIProperty: zoomStopHeight
+ */
+ zoomStopHeight: 11,
+
+ /**
+ * Property: slider
+ */
+ slider: null,
+
+ /**
+ * Property: sliderEvents
+ * {<OpenLayers.Events>}
+ */
+ sliderEvents: null,
+
+ /**
+ * Property: zoombarDiv
+ * {DOMElement}
+ */
+ zoombarDiv: null,
+
+ /**
+ * APIProperty: zoomWorldIcon
+ * {Boolean}
+ */
+ zoomWorldIcon: false,
+
+ /**
+ * APIProperty: panIcons
+ * {Boolean} Set this property to false not to display the pan icons. If
+ * false the zoom world icon is placed under the zoom bar. Defaults to
+ * true.
+ */
+ panIcons: true,
+
+ /**
+ * APIProperty: forceFixedZoomLevel
+ * {Boolean} Force a fixed zoom level even though the map has
+ * fractionalZoom
+ */
+ forceFixedZoomLevel: false,
+
+ /**
+ * Property: mouseDragStart
+ * {<OpenLayers.Pixel>}
+ */
+ mouseDragStart: null,
+
+ /**
+ * Property: deltaY
+ * {Number} The cumulative vertical pixel offset during a zoom bar drag.
+ */
+ deltaY: null,
+
+ /**
+ * Property: zoomStart
+ * {<OpenLayers.Pixel>}
+ */
+ zoomStart: null,
+
+ /**
+ * Constructor: OpenLayers.Control.PanZoomBar
+ */
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+
+ this._removeZoomBar();
+
+ this.map.events.un({
+ "changebaselayer": this.redraw,
+ "updatesize": this.redraw,
+ scope: this
+ });
+
+ OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments);
+
+ delete this.mouseDragStart;
+ delete this.zoomStart;
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments);
+ this.map.events.on({
+ "changebaselayer": this.redraw,
+ "updatesize": this.redraw,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: redraw
+ * clear the div and start over.
+ */
+ redraw: function() {
+ if (this.div != null) {
+ this.removeButtons();
+ this._removeZoomBar();
+ }
+ this.draw();
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ draw: function(px) {
+ // initialize our internal div
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ px = this.position.clone();
+
+ // place the controls
+ this.buttons = [];
+
+ var sz = {w: 18, h: 18};
+ if (this.panIcons) {
+ var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
+ var wposition = sz.w;
+
+ if (this.zoomWorldIcon) {
+ centered = new OpenLayers.Pixel(px.x+sz.w, px.y);
+ }
+
+ this._addButton("panup", "north-mini.png", centered, sz);
+ px.y = centered.y+sz.h;
+ this._addButton("panleft", "west-mini.png", px, sz);
+ if (this.zoomWorldIcon) {
+ this._addButton("zoomworld", "zoom-world-mini.png", px.add(sz.w, 0), sz);
+
+ wposition *= 2;
+ }
+ this._addButton("panright", "east-mini.png", px.add(wposition, 0), sz);
+ this._addButton("pandown", "south-mini.png", centered.add(0, sz.h*2), sz);
+ this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, sz.h*3+5), sz);
+ centered = this._addZoomBar(centered.add(0, sz.h*4 + 5));
+ this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
+ }
+ else {
+ this._addButton("zoomin", "zoom-plus-mini.png", px, sz);
+ centered = this._addZoomBar(px.add(0, sz.h));
+ this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
+ if (this.zoomWorldIcon) {
+ centered = centered.add(0, sz.h+3);
+ this._addButton("zoomworld", "zoom-world-mini.png", centered, sz);
+ }
+ }
+ return this.div;
+ },
+
+ /**
+ * Method: _addZoomBar
+ *
+ * Parameters:
+ * centered - {<OpenLayers.Pixel>} where zoombar drawing is to start.
+ */
+ _addZoomBar:function(centered) {
+ var imgLocation = OpenLayers.Util.getImageLocation("slider.png");
+ var id = this.id + "_" + this.map.id;
+ var minZoom = this.map.getMinZoom();
+ var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom();
+ var slider = OpenLayers.Util.createAlphaImageDiv(id,
+ centered.add(-1, zoomsToEnd * this.zoomStopHeight),
+ {w: 20, h: 9},
+ imgLocation,
+ "absolute");
+ slider.style.cursor = "move";
+ this.slider = slider;
+
+ this.sliderEvents = new OpenLayers.Events(this, slider, null, true,
+ {includeXY: true});
+ this.sliderEvents.on({
+ "touchstart": this.zoomBarDown,
+ "touchmove": this.zoomBarDrag,
+ "touchend": this.zoomBarUp,
+ "mousedown": this.zoomBarDown,
+ "mousemove": this.zoomBarDrag,
+ "mouseup": this.zoomBarUp
+ });
+
+ var sz = {
+ w: this.zoomStopWidth,
+ h: this.zoomStopHeight * (this.map.getNumZoomLevels() - minZoom)
+ };
+ var imgLocation = OpenLayers.Util.getImageLocation("zoombar.png");
+ var div = null;
+
+ if (OpenLayers.Util.alphaHack()) {
+ var id = this.id + "_" + this.map.id;
+ div = OpenLayers.Util.createAlphaImageDiv(id, centered,
+ {w: sz.w, h: this.zoomStopHeight},
+ imgLocation,
+ "absolute", null, "crop");
+ div.style.height = sz.h + "px";
+ } else {
+ div = OpenLayers.Util.createDiv(
+ 'OpenLayers_Control_PanZoomBar_Zoombar' + this.map.id,
+ centered,
+ sz,
+ imgLocation);
+ }
+ div.style.cursor = "pointer";
+ div.className = "olButton";
+ this.zoombarDiv = div;
+
+ this.div.appendChild(div);
+
+ this.startTop = parseInt(div.style.top);
+ this.div.appendChild(slider);
+
+ this.map.events.register("zoomend", this, this.moveZoomBar);
+
+ centered = centered.add(0,
+ this.zoomStopHeight * (this.map.getNumZoomLevels() - minZoom));
+ return centered;
+ },
+
+ /**
+ * Method: _removeZoomBar
+ */
+ _removeZoomBar: function() {
+ this.sliderEvents.un({
+ "touchstart": this.zoomBarDown,
+ "touchmove": this.zoomBarDrag,
+ "touchend": this.zoomBarUp,
+ "mousedown": this.zoomBarDown,
+ "mousemove": this.zoomBarDrag,
+ "mouseup": this.zoomBarUp
+ });
+ this.sliderEvents.destroy();
+
+ this.div.removeChild(this.zoombarDiv);
+ this.zoombarDiv = null;
+ this.div.removeChild(this.slider);
+ this.slider = null;
+
+ this.map.events.unregister("zoomend", this, this.moveZoomBar);
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ OpenLayers.Control.PanZoom.prototype.onButtonClick.apply(this, arguments);
+ if (evt.buttonElement === this.zoombarDiv) {
+ var levels = evt.buttonXY.y / this.zoomStopHeight;
+ if(this.forceFixedZoomLevel || !this.map.fractionalZoom) {
+ levels = Math.floor(levels);
+ }
+ var zoom = (this.map.getNumZoomLevels() - 1) - levels;
+ zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1);
+ this.map.zoomTo(zoom);
+ }
+ },
+
+ /**
+ * Method: passEventToSlider
+ * This function is used to pass events that happen on the div, or the map,
+ * through to the slider, which then does its moving thing.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ passEventToSlider:function(evt) {
+ this.sliderEvents.handleBrowserEvent(evt);
+ },
+
+ /*
+ * Method: zoomBarDown
+ * event listener for clicks on the slider
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ zoomBarDown:function(evt) {
+ if (!OpenLayers.Event.isLeftClick(evt) && !OpenLayers.Event.isSingleTouch(evt)) {
+ return;
+ }
+ this.map.events.on({
+ "touchmove": this.passEventToSlider,
+ "mousemove": this.passEventToSlider,
+ "mouseup": this.passEventToSlider,
+ scope: this
+ });
+ this.mouseDragStart = evt.xy.clone();
+ this.zoomStart = evt.xy.clone();
+ this.div.style.cursor = "move";
+ // reset the div offsets just in case the div moved
+ this.zoombarDiv.offsets = null;
+ OpenLayers.Event.stop(evt);
+ },
+
+ /*
+ * Method: zoomBarDrag
+ * This is what happens when a click has occurred, and the client is
+ * dragging. Here we must ensure that the slider doesn't go beyond the
+ * bottom/top of the zoombar div, as well as moving the slider to its new
+ * visual location
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ zoomBarDrag:function(evt) {
+ if (this.mouseDragStart != null) {
+ var deltaY = this.mouseDragStart.y - evt.xy.y;
+ var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv);
+ if ((evt.clientY - offsets[1]) > 0 &&
+ (evt.clientY - offsets[1]) < parseInt(this.zoombarDiv.style.height) - 2) {
+ var newTop = parseInt(this.slider.style.top) - deltaY;
+ this.slider.style.top = newTop+"px";
+ this.mouseDragStart = evt.xy.clone();
+ }
+ // set cumulative displacement
+ this.deltaY = this.zoomStart.y - evt.xy.y;
+ OpenLayers.Event.stop(evt);
+ }
+ },
+
+ /*
+ * Method: zoomBarUp
+ * Perform cleanup when a mouseup event is received -- discover new zoom
+ * level and switch to it.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ zoomBarUp:function(evt) {
+ if (!OpenLayers.Event.isLeftClick(evt) && evt.type !== "touchend") {
+ return;
+ }
+ if (this.mouseDragStart) {
+ this.div.style.cursor="";
+ this.map.events.un({
+ "touchmove": this.passEventToSlider,
+ "mouseup": this.passEventToSlider,
+ "mousemove": this.passEventToSlider,
+ scope: this
+ });
+ var zoomLevel = this.map.zoom;
+ if (!this.forceFixedZoomLevel && this.map.fractionalZoom) {
+ zoomLevel += this.deltaY/this.zoomStopHeight;
+ zoomLevel = Math.min(Math.max(zoomLevel, 0),
+ this.map.getNumZoomLevels() - 1);
+ } else {
+ zoomLevel += this.deltaY/this.zoomStopHeight;
+ zoomLevel = Math.max(Math.round(zoomLevel), 0);
+ }
+ this.map.zoomTo(zoomLevel);
+ this.mouseDragStart = null;
+ this.zoomStart = null;
+ this.deltaY = 0;
+ OpenLayers.Event.stop(evt);
+ }
+ },
+
+ /*
+ * Method: moveZoomBar
+ * Change the location of the slider to match the current zoom level.
+ */
+ moveZoomBar:function() {
+ var newTop =
+ ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) *
+ this.zoomStopHeight + this.startTop + 1;
+ this.slider.style.top = newTop + "px";
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PanZoomBar"
+});
+/* ======================================================================
+ OpenLayers/Format/WFSCapabilities.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities
+ * Read WFS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WFSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities
+ * Create a new parser for WFS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WFSCapabilities/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFSCapabilities.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities.v1
+ * Abstract class not to be instantiated directly.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wfs: "http://www.opengis.net/wfs",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ ows: "http://www.opengis.net/ows"
+ },
+
+
+ /**
+ * APIProperty: errorProperty
+ * {String} Which property of the returned object to check for in order to
+ * determine whether or not parsing has failed. In the case that the
+ * errorProperty is undefined on the returned object, the document will be
+ * run through an OGCExceptionReport parser.
+ */
+ errorProperty: "featureTypeList",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wfs",
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities.v1_1
+ * Create an instance of one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var raw = data;
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": {
+ "WFS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "FeatureTypeList": function(node, request) {
+ request.featureTypeList = {
+ featureTypes: []
+ };
+ this.readChildNodes(node, request.featureTypeList);
+ },
+ "FeatureType": function(node, featureTypeList) {
+ var featureType = {};
+ this.readChildNodes(node, featureType);
+ featureTypeList.featureTypes.push(featureType);
+ },
+ "Name": function(node, obj) {
+ var name = this.getChildValue(node);
+ if(name) {
+ var parts = name.split(":");
+ obj.name = parts.pop();
+ if(parts.length > 0) {
+ obj.featureNS = this.lookupNamespaceURI(node, parts[0]);
+ }
+ }
+ },
+ "Title": function(node, obj) {
+ var title = this.getChildValue(node);
+ if(title) {
+ obj.title = title;
+ }
+ },
+ "Abstract": function(node, obj) {
+ var abst = this.getChildValue(node);
+ if(abst) {
+ obj["abstract"] = abst;
+ }
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WFSCapabilities/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFSCapabilities/v1.js
+ * @requires OpenLayers/Format/OWSCommon/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities/v1_1_0
+ * Read WFS Capabilities version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WFSCapabilities>
+ */
+OpenLayers.Format.WFSCapabilities.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WFSCapabilities.v1, {
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities.v1_1_0
+ * Create a new parser for WFS capabilities version 1.1.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "DefaultSRS": function(node, obj) {
+ var defaultSRS = this.getChildValue(node);
+ if (defaultSRS) {
+ obj.srs = defaultSRS;
+ }
+ }
+ }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"]),
+ "ows": OpenLayers.Format.OWSCommon.v1.prototype.readers.ows
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_1_0"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/Image.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Image
+ * Instances of OpenLayers.Layer.Image are used to display data from a web
+ * accessible image as a map layer. Create a new image layer with the
+ * <OpenLayers.Layer.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * Property: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is true. Set this property
+ * in the layer options
+ */
+ isBaseLayer: true,
+
+ /**
+ * Property: url
+ * {String} URL of the image to use
+ */
+ url: null,
+
+ /**
+ * Property: extent
+ * {<OpenLayers.Bounds>} The image bounds in map units. This extent will
+ * also be used as the default maxExtent for the layer. If you wish
+ * to have a maxExtent that is different than the image extent, set the
+ * maxExtent property of the options argument (as with any other layer).
+ */
+ extent: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} The image size in pixels
+ */
+ size: null,
+
+ /**
+ * Property: tile
+ * {<OpenLayers.Tile.Image>}
+ */
+ tile: null,
+
+ /**
+ * Property: aspectRatio
+ * {Float} The ratio of height/width represented by a single pixel in the
+ * graphic
+ */
+ aspectRatio: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Image
+ * Create a new image layer
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * url - {String} Relative or absolute path to the image
+ * extent - {<OpenLayers.Bounds>} The extent represented by the image
+ * size - {<OpenLayers.Size>} The size (in pixels) of the image
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, extent, size, options) {
+ this.url = url;
+ this.extent = extent;
+ this.maxExtent = extent;
+ this.size = size;
+ OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+
+ this.aspectRatio = (this.extent.getHeight() / this.size.h) /
+ (this.extent.getWidth() / this.size.w);
+ },
+
+ /**
+ * Method: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.tile) {
+ this.removeTileMonitoringHooks(this.tile);
+ this.tile.destroy();
+ this.tile = null;
+ }
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Paramters:
+ * obj - {Object} An optional layer (is this ever used?)
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Image>} An exact copy of this layer
+ */
+ clone: function(obj) {
+
+ if(obj == null) {
+ obj = new OpenLayers.Layer.Image(this.name,
+ this.url,
+ this.extent,
+ this.size,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ /**
+ * If nothing to do with resolutions has been set, assume a single
+ * resolution determined by ratio*extent/size - if an image has a
+ * pixel aspect ratio different than one (as calculated above), the
+ * image will be stretched in one dimension only.
+ */
+ if( this.options.maxResolution == null ) {
+ this.options.maxResolution = this.aspectRatio *
+ this.extent.getWidth() /
+ this.size.w;
+ }
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ * Create the tile for the image or resize it for the new resolution
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ var firstRendering = (this.tile == null);
+
+ if(zoomChanged || firstRendering) {
+
+ //determine new tile size
+ this.setTileSize();
+
+ //determine new position (upper left corner of new bounds)
+ var ulPx = this.map.getLayerPxFromLonLat({
+ lon: this.extent.left,
+ lat: this.extent.top
+ });
+
+ if(firstRendering) {
+ //create the new tile
+ this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent,
+ null, this.tileSize);
+ this.addTileMonitoringHooks(this.tile);
+ } else {
+ //just resize the tile and set it's new position
+ this.tile.size = this.tileSize.clone();
+ this.tile.position = ulPx.clone();
+ }
+ this.tile.draw();
+ }
+ },
+
+ /**
+ * Set the tile size based on the map size.
+ */
+ setTileSize: function() {
+ var tileWidth = this.extent.getWidth() / this.map.getResolution();
+ var tileHeight = this.extent.getHeight() / this.map.getResolution();
+ this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tiles.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+ tile.onLoadStart = function() {
+ this.events.triggerEvent("loadstart");
+ };
+ tile.events.register("loadstart", this, tile.onLoadStart);
+
+ tile.onLoadEnd = function() {
+ this.events.triggerEvent("loadend");
+ };
+ tile.events.register("loadend", this, tile.onLoadEnd);
+ tile.events.register("unload", this, tile.onLoadEnd);
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in <addTileMonitoringHooks>.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ scope: this
+ });
+ },
+
+ /**
+ * APIMethod: setUrl
+ *
+ * Parameters:
+ * newUrl - {String}
+ */
+ setUrl: function(newUrl) {
+ this.url = newUrl;
+ this.tile.draw();
+ },
+
+ /**
+ * APIMethod: getURL
+ * The url we return is always the same (the image itself never changes)
+ * so we can ignore the bounds parameter (it will always be the same,
+ * anyways)
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getURL: function(bounds) {
+ return this.url;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Image"
+});
+/* ======================================================================
+ OpenLayers/Strategy.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class. Not to be instantiated directly. Use
+ * one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The layer this strategy belongs to.
+ */
+ layer: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: active
+ * {Boolean} The control is active.
+ */
+ active: null,
+
+ /**
+ * Property: autoActivate
+ * {Boolean} The creator of the strategy can set autoActivate to false
+ * to fully control when the protocol is activated and deactivated.
+ * Defaults to true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the strategy can set autoDestroy to false
+ * to fully control when the strategy is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Constructor: OpenLayers.Strategy
+ * Abstract class for vector strategies. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ // set the active property here, so that user cannot override it
+ this.active = false;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the strategy.
+ */
+ destroy: function() {
+ this.deactivate();
+ this.layer = null;
+ this.options = null;
+ },
+
+ /**
+ * Method: setLayer
+ * Called to set the <layer> property.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layer) {
+ this.layer = layer;
+ },
+
+ /**
+ * Method: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ if (!this.active) {
+ this.active = true;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ if (this.active) {
+ this.active = false;
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Save.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Save
+ * A strategy that commits newly created or modified features. By default
+ * the strategy waits for a call to <save> before persisting changes. By
+ * configuring the strategy with the <auto> option, changes can be saved
+ * automatically.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Save = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the strategy object.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * strategy.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types:
+ * start - Triggered before saving
+ * success - Triggered after a successful transaction
+ * fail - Triggered after a failed transaction
+ *
+ */
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} Events instance for triggering this protocol
+ * events.
+ */
+ events: null,
+
+ /**
+ * APIProperty: auto
+ * {Boolean | Number} Auto-save. Default is false. If true, features will be
+ * saved immediately after being added to the layer and with each
+ * modification or deletion. If auto is a number, features will be
+ * saved on an interval provided by the value (in seconds).
+ */
+ auto: false,
+
+ /**
+ * Property: timer
+ * {Number} The id of the timer.
+ */
+ timer: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Save
+ * Create a new Save strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
+ this.events = new OpenLayers.Events(this);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ if(this.auto) {
+ if(typeof this.auto === "number") {
+ this.timer = window.setInterval(
+ OpenLayers.Function.bind(this.save, this),
+ this.auto * 1000
+ );
+ } else {
+ this.layer.events.on({
+ "featureadded": this.triggerSave,
+ "afterfeaturemodified": this.triggerSave,
+ scope: this
+ });
+ }
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ if(this.auto) {
+ if(typeof this.auto === "number") {
+ window.clearInterval(this.timer);
+ } else {
+ this.layer.events.un({
+ "featureadded": this.triggerSave,
+ "afterfeaturemodified": this.triggerSave,
+ scope: this
+ });
+ }
+ }
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: triggerSave
+ * Registered as a listener. Calls save if a feature has insert, update,
+ * or delete state.
+ *
+ * Parameters:
+ * event - {Object} The event this function is listening for.
+ */
+ triggerSave: function(event) {
+ var feature = event.feature;
+ if(feature.state === OpenLayers.State.INSERT ||
+ feature.state === OpenLayers.State.UPDATE ||
+ feature.state === OpenLayers.State.DELETE) {
+ this.save([event.feature]);
+ }
+ },
+
+ /**
+ * APIMethod: save
+ * Tell the layer protocol to commit unsaved features. If the layer
+ * projection differs from the map projection, features will be
+ * transformed into the layer projection before being committed.
+ *
+ * Parameters:
+ * features - {Array} Features to be saved. If null, then default is all
+ * features in the layer. Features are assumed to be in the map
+ * projection.
+ */
+ save: function(features) {
+ if(!features) {
+ features = this.layer.features;
+ }
+ this.events.triggerEvent("start", {features:features});
+ var remote = this.layer.projection;
+ var local = this.layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var len = features.length;
+ var clones = new Array(len);
+ var orig, clone;
+ for(var i=0; i<len; ++i) {
+ orig = features[i];
+ clone = orig.clone();
+ clone.fid = orig.fid;
+ clone.state = orig.state;
+ if(orig.url) {
+ clone.url = orig.url;
+ }
+ clone._original = orig;
+ clone.geometry.transform(local, remote);
+ clones[i] = clone;
+ }
+ features = clones;
+ }
+ this.layer.protocol.commit(features, {
+ callback: this.onCommit,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: onCommit
+ * Called after protocol commit.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} A response object.
+ */
+ onCommit: function(response) {
+ var evt = {"response": response};
+ if(response.success()) {
+ var features = response.reqFeatures;
+ // deal with inserts, updates, and deletes
+ var state, feature;
+ var destroys = [];
+ var insertIds = response.insertIds || [];
+ var j = 0;
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ // if projection was different, we may be dealing with clones
+ feature = feature._original || feature;
+ state = feature.state;
+ if(state) {
+ if(state == OpenLayers.State.DELETE) {
+ destroys.push(feature);
+ } else if(state == OpenLayers.State.INSERT) {
+ feature.fid = insertIds[j];
+ ++j;
+ }
+ feature.state = null;
+ }
+ }
+
+ if(destroys.length > 0) {
+ this.layer.destroyFeatures(destroys);
+ }
+
+ this.events.triggerEvent("success", evt);
+
+ } else {
+ this.events.triggerEvent("fail", evt);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Save"
+});
+/* ======================================================================
+ OpenLayers/Events/featureclick.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.featureclick
+ *
+ * Extension event type for handling feature click events, including overlapping
+ * features.
+ *
+ * Event types provided by this extension:
+ * - featureclick
+ */
+OpenLayers.Events.featureclick = OpenLayers.Class({
+
+ /**
+ * Property: cache
+ * {Object} A cache of features under the mouse.
+ */
+ cache: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} The map to register browser events on.
+ */
+ map: null,
+
+ /**
+ * Property: provides
+ * {Array(String)} The event types provided by this extension.
+ */
+ provides: ["featureclick", "nofeatureclick", "featureover", "featureout"],
+
+ /**
+ * Constructor: OpenLayers.Events.featureclick
+ * Create a new featureclick event type.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance to create the events
+ * for.
+ */
+ initialize: function(target) {
+ this.target = target;
+ if (target.object instanceof OpenLayers.Map) {
+ this.setMap(target.object);
+ } else if (target.object instanceof OpenLayers.Layer.Vector) {
+ if (target.object.map) {
+ this.setMap(target.object.map);
+ } else {
+ target.object.events.register("added", this, function(evt) {
+ this.setMap(target.object.map);
+ });
+ }
+ } else {
+ throw("Listeners for '" + this.provides.join("', '") +
+ "' events can only be registered for OpenLayers.Layer.Vector " +
+ "or OpenLayers.Map instances");
+ }
+ for (var i=this.provides.length-1; i>=0; --i) {
+ target.extensions[this.provides[i]] = true;
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map to register browser events on.
+ */
+ setMap: function(map) {
+ this.map = map;
+ this.cache = {};
+ map.events.register("mousedown", this, this.start, {extension: true});
+ map.events.register("mouseup", this, this.onClick, {extension: true});
+ map.events.register("touchstart", this, this.start, {extension: true});
+ map.events.register("touchmove", this, this.cancel, {extension: true});
+ map.events.register("touchend", this, this.onClick, {extension: true});
+ map.events.register("mousemove", this, this.onMousemove, {extension: true});
+ },
+
+ /**
+ * Method: start
+ * Sets startEvt = evt.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ start: function(evt) {
+ this.startEvt = evt;
+ },
+
+ /**
+ * Method: cancel
+ * Deletes the start event.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ cancel: function(evt) {
+ delete this.startEvt;
+ },
+
+ /**
+ * Method: onClick
+ * Listener for the click event.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ onClick: function(evt) {
+ if (!this.startEvt || evt.type !== "touchend" &&
+ !OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ var features = this.getFeatures(this.startEvt);
+ delete this.startEvt;
+ // fire featureclick events
+ var feature, layer, more, clicked = {};
+ for (var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ layer = feature.layer;
+ clicked[layer.id] = true;
+ more = this.triggerEvent("featureclick", {feature: feature});
+ if (more === false) {
+ break;
+ }
+ }
+ // fire nofeatureclick events on all vector layers with no targets
+ for (i=0, len=this.map.layers.length; i<len; ++i) {
+ layer = this.map.layers[i];
+ if (layer instanceof OpenLayers.Layer.Vector && !clicked[layer.id]) {
+ this.triggerEvent("nofeatureclick", {layer: layer});
+ }
+ }
+ },
+
+ /**
+ * Method: onMousemove
+ * Listener for the mousemove event.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ onMousemove: function(evt) {
+ delete this.startEvt;
+ var features = this.getFeatures(evt);
+ var over = {}, newly = [], feature;
+ for (var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ over[feature.id] = feature;
+ if (!this.cache[feature.id]) {
+ newly.push(feature);
+ }
+ }
+ // check if already over features
+ var out = [];
+ for (var id in this.cache) {
+ feature = this.cache[id];
+ if (feature.layer && feature.layer.map) {
+ if (!over[feature.id]) {
+ out.push(feature);
+ }
+ } else {
+ // removed
+ delete this.cache[id];
+ }
+ }
+ // fire featureover events
+ var more;
+ for (i=0, len=newly.length; i<len; ++i) {
+ feature = newly[i];
+ this.cache[feature.id] = feature;
+ more = this.triggerEvent("featureover", {feature: feature});
+ if (more === false) {
+ break;
+ }
+ }
+ // fire featureout events
+ for (i=0, len=out.length; i<len; ++i) {
+ feature = out[i];
+ delete this.cache[feature.id];
+ more = this.triggerEvent("featureout", {feature: feature});
+ if (more === false) {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: triggerEvent
+ * Determines where to trigger the event and triggers it.
+ *
+ * Parameters:
+ * type - {String} The event type to trigger
+ * evt - {Object} The listener argument
+ *
+ * Returns:
+ * {Boolean} The last listener return.
+ */
+ triggerEvent: function(type, evt) {
+ var layer = evt.feature ? evt.feature.layer : evt.layer,
+ object = this.target.object;
+ if (object instanceof OpenLayers.Map || object === layer) {
+ return this.target.triggerEvent(type, evt);
+ }
+ },
+
+ /**
+ * Method: getFeatures
+ * Get all features at the given screen location.
+ *
+ * Parameters:
+ * evt - {Object} Event object.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features at the given point.
+ */
+ getFeatures: function(evt) {
+ var x = evt.clientX, y = evt.clientY,
+ features = [], targets = [], layers = [],
+ layer, target, feature, i, len;
+ // go through all layers looking for targets
+ for (i=this.map.layers.length-1; i>=0; --i) {
+ layer = this.map.layers[i];
+ if (layer.div.style.display !== "none") {
+ if (layer.renderer instanceof OpenLayers.Renderer.Elements) {
+ if (layer instanceof OpenLayers.Layer.Vector) {
+ target = document.elementFromPoint(x, y);
+ while (target && target._featureId) {
+ feature = layer.getFeatureById(target._featureId);
+ if (feature) {
+ features.push(feature);
+ target.style.display = "none";
+ targets.push(target);
+ target = document.elementFromPoint(x, y);
+ } else {
+ // sketch, all bets off
+ target = false;
+ }
+ }
+ }
+ layers.push(layer);
+ layer.div.style.display = "none";
+ } else if (layer.renderer instanceof OpenLayers.Renderer.Canvas) {
+ feature = layer.renderer.getFeatureIdFromEvent(evt);
+ if (feature) {
+ features.push(feature);
+ layers.push(layer);
+ }
+ }
+ }
+ }
+ // restore feature visibility
+ for (i=0, len=targets.length; i<len; ++i) {
+ targets[i].style.display = "";
+ }
+ // restore layer visibility
+ for (i=layers.length-1; i>=0; --i) {
+ layers[i].div.style.display = "block";
+ }
+ return features;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ for (var i=this.provides.length-1; i>=0; --i) {
+ delete this.target.extensions[this.provides[i]];
+ }
+ this.map.events.un({
+ mousemove: this.onMousemove,
+ mousedown: this.start,
+ mouseup: this.onClick,
+ touchstart: this.start,
+ touchmove: this.cancel,
+ touchend: this.onClick,
+ scope: this
+ });
+ delete this.cache;
+ delete this.map;
+ delete this.target;
+ }
+
+});
+
+/**
+ * Class: OpenLayers.Events.nofeatureclick
+ *
+ * Extension event type for handling click events that do not hit a feature.
+ *
+ * Event types provided by this extension:
+ * - nofeatureclick
+ */
+OpenLayers.Events.nofeatureclick = OpenLayers.Events.featureclick;
+
+/**
+ * Class: OpenLayers.Events.featureover
+ *
+ * Extension event type for handling hovering over a feature.
+ *
+ * Event types provided by this extension:
+ * - featureover
+ */
+OpenLayers.Events.featureover = OpenLayers.Events.featureclick;
+
+/**
+ * Class: OpenLayers.Events.featureout
+ *
+ * Extension event type for handling leaving a feature.
+ *
+ * Event types provided by this extension:
+ * - featureout
+ */
+OpenLayers.Events.featureout = OpenLayers.Events.featureclick;
+/* ======================================================================
+ OpenLayers/Format/GPX.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GPX
+ * Read/write GPX parser. Create a new instance with the
+ * <OpenLayers.Format.GPX> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GPX = OpenLayers.Class(OpenLayers.Format.XML, {
+
+
+ /**
+ * APIProperty: defaultDesc
+ * {String} Default description for the waypoints/tracks in the case
+ * where the feature has no "description" attribute.
+ * Default is "No description available".
+ */
+ defaultDesc: "No description available",
+
+ /**
+ * APIProperty: extractWaypoints
+ * {Boolean} Extract waypoints from GPX. (default: true)
+ */
+ extractWaypoints: true,
+
+ /**
+ * APIProperty: extractTracks
+ * {Boolean} Extract tracks from GPX. (default: true)
+ */
+ extractTracks: true,
+
+ /**
+ * APIProperty: extractRoutes
+ * {Boolean} Extract routes from GPX. (default: true)
+ */
+ extractRoutes: true,
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract feature attributes from GPX. (default: true)
+ * NOTE: Attributes as part of extensions to the GPX standard may not
+ * be extracted.
+ */
+ extractAttributes: true,
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ gpx: "http://www.topografix.com/GPX/1/1",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location. Defaults to
+ * "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
+ */
+ schemaLocation: "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd",
+
+ /**
+ * APIProperty: creator
+ * {String} The creator attribute to be added to the written GPX files.
+ * Defaults to "OpenLayers"
+ */
+ creator: "OpenLayers",
+
+ /**
+ * Constructor: OpenLayers.Format.GPX
+ * Create a new parser for GPX.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // GPX coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a GPX doc
+ *
+ * Parameters:
+ * doc - {Element}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+ var features = [];
+
+ if(this.extractTracks) {
+ var tracks = doc.getElementsByTagName("trk");
+ for (var i=0, len=tracks.length; i<len; i++) {
+ // Attributes are only in trk nodes, not trkseg nodes
+ var attrs = {};
+ if(this.extractAttributes) {
+ attrs = this.parseAttributes(tracks[i]);
+ }
+
+ var segs = this.getElementsByTagNameNS(tracks[i], tracks[i].namespaceURI, "trkseg");
+ for (var j = 0, seglen = segs.length; j < seglen; j++) {
+ // We don't yet support extraction of trkpt attributes
+ // All trksegs of a trk get that trk's attributes
+ var track = this.extractSegment(segs[j], "trkpt");
+ features.push(new OpenLayers.Feature.Vector(track, attrs));
+ }
+ }
+ }
+
+ if(this.extractRoutes) {
+ var routes = doc.getElementsByTagName("rte");
+ for (var k=0, klen=routes.length; k<klen; k++) {
+ var attrs = {};
+ if(this.extractAttributes) {
+ attrs = this.parseAttributes(routes[k]);
+ }
+ var route = this.extractSegment(routes[k], "rtept");
+ features.push(new OpenLayers.Feature.Vector(route, attrs));
+ }
+ }
+
+ if(this.extractWaypoints) {
+ var waypoints = doc.getElementsByTagName("wpt");
+ for (var l = 0, len = waypoints.length; l < len; l++) {
+ var attrs = {};
+ if(this.extractAttributes) {
+ attrs = this.parseAttributes(waypoints[l]);
+ }
+ var wpt = new OpenLayers.Geometry.Point(waypoints[l].getAttribute("lon"), waypoints[l].getAttribute("lat"));
+ features.push(new OpenLayers.Feature.Vector(wpt, attrs));
+ }
+ }
+
+ if (this.internalProjection && this.externalProjection) {
+ for (var g = 0, featLength = features.length; g < featLength; g++) {
+ features[g].geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ }
+
+ return features;
+ },
+
+ /**
+ * Method: extractSegment
+ *
+ * Parameters:
+ * segment - {DOMElement} a trkseg or rte node to parse
+ * segmentType - {String} nodeName of waypoints that form the line
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry
+ */
+ extractSegment: function(segment, segmentType) {
+ var points = this.getElementsByTagNameNS(segment, segment.namespaceURI, segmentType);
+ var point_features = [];
+ for (var i = 0, len = points.length; i < len; i++) {
+ point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"), points[i].getAttribute("lat")));
+ }
+ return new OpenLayers.Geometry.LineString(point_features);
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {<DOMElement>}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ // node is either a wpt, trk or rte
+ // attributes are children of the form <attr>value</attr>
+ var attributes = {};
+ var attrNode = node.firstChild, value, name;
+ while(attrNode) {
+ if(attrNode.nodeType == 1 && attrNode.firstChild) {
+ value = attrNode.firstChild;
+ if(value.nodeType == 3 || value.nodeType == 4) {
+ name = (attrNode.prefix) ?
+ attrNode.nodeName.split(":")[1] :
+ attrNode.nodeName;
+ if(name != "trkseg" && name != "rtept") {
+ attributes[name] = value.nodeValue;
+ }
+ }
+ }
+ attrNode = attrNode.nextSibling;
+ }
+ return attributes;
+ },
+
+ /**
+ * APIMethod: write
+ * Accepts Feature Collection, and returns a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to serialize into a string.
+ * metadata - {Object} A key/value pairs object to build a metadata node to
+ * add to the gpx. Supported keys are 'name', 'desc', 'author'.
+ */
+ write: function(features, metadata) {
+ features = OpenLayers.Util.isArray(features) ?
+ features : [features];
+ var gpx = this.createElementNS(this.namespaces.gpx, "gpx");
+ gpx.setAttribute("version", "1.1");
+ gpx.setAttribute("creator", this.creator);
+ this.setAttributes(gpx, {
+ "xsi:schemaLocation": this.schemaLocation
+ });
+
+ if (metadata && typeof metadata == 'object') {
+ gpx.appendChild(this.buildMetadataNode(metadata));
+ }
+ for(var i=0, len=features.length; i<len; i++) {
+ gpx.appendChild(this.buildFeatureNode(features[i]));
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [gpx]);
+ },
+
+ /**
+ * Method: buildMetadataNode
+ * Creates a "metadata" node.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildMetadataNode: function(metadata) {
+ var types = ['name', 'desc', 'author'],
+ node = this.createElementNS(this.namespaces.gpx, 'metadata');
+ for (var i=0; i < types.length; i++) {
+ var type = types[i];
+ if (metadata[type]) {
+ var n = this.createElementNS(this.namespaces.gpx, type);
+ n.appendChild(this.createTextNode(metadata[type]));
+ node.appendChild(n);
+ }
+ }
+ return node;
+ },
+
+ /**
+ * Method: buildFeatureNode
+ * Accepts an <OpenLayers.Feature.Vector>, and builds a node for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement} - The created node, either a 'wpt' or a 'trk'.
+ */
+ buildFeatureNode: function(feature) {
+ var geometry = feature.geometry;
+ geometry = geometry.clone();
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ var wpt = this.buildWptNode(geometry);
+ this.appendAttributesNode(wpt, feature);
+ return wpt;
+ } else {
+ var trkNode = this.createElementNS(this.namespaces.gpx, "trk");
+ this.appendAttributesNode(trkNode, feature);
+ var trkSegNodes = this.buildTrkSegNode(geometry);
+ trkSegNodes = OpenLayers.Util.isArray(trkSegNodes) ?
+ trkSegNodes : [trkSegNodes];
+ for (var i = 0, len = trkSegNodes.length; i < len; i++) {
+ trkNode.appendChild(trkSegNodes[i]);
+ }
+ return trkNode;
+ }
+ },
+
+ /**
+ * Method: buildTrkSegNode
+ * Builds trkseg node(s) given a geometry
+ *
+ * Parameters:
+ * trknode
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ buildTrkSegNode: function(geometry) {
+ var node,
+ i,
+ len,
+ point,
+ nodes;
+ if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+ geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ node = this.createElementNS(this.namespaces.gpx, "trkseg");
+ for (i = 0, len=geometry.components.length; i < len; i++) {
+ point = geometry.components[i];
+ node.appendChild(this.buildTrkPtNode(point));
+ }
+ return node;
+ } else {
+ nodes = [];
+ for (i = 0, len = geometry.components.length; i < len; i++) {
+ nodes.push(this.buildTrkSegNode(geometry.components[i]));
+ }
+ return nodes;
+ }
+ },
+
+ /**
+ * Method: buildTrkPtNode
+ * Builds a trkpt node given a point
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {DOMElement} A trkpt node
+ */
+ buildTrkPtNode: function(point) {
+ var node = this.createElementNS(this.namespaces.gpx, "trkpt");
+ node.setAttribute("lon", point.x);
+ node.setAttribute("lat", point.y);
+ return node;
+ },
+
+ /**
+ * Method: buildWptNode
+ * Builds a wpt node given a point
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {DOMElement} A wpt node
+ */
+ buildWptNode: function(geometry) {
+ var node = this.createElementNS(this.namespaces.gpx, "wpt");
+ node.setAttribute("lon", geometry.x);
+ node.setAttribute("lat", geometry.y);
+ return node;
+ },
+
+ /**
+ * Method: appendAttributesNode
+ * Adds some attributes node.
+ *
+ * Parameters:
+ * node - {DOMElement} the node to append the attribute nodes to.
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ appendAttributesNode: function(node, feature) {
+ var name = this.createElementNS(this.namespaces.gpx, 'name');
+ name.appendChild(this.createTextNode(
+ feature.attributes.name || feature.id));
+ node.appendChild(name);
+ var desc = this.createElementNS(this.namespaces.gpx, 'desc');
+ desc.appendChild(this.createTextNode(
+ feature.attributes.description || this.defaultDesc));
+ node.appendChild(desc);
+ // TBD - deal with remaining (non name/description) attributes.
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GPX"
+});
+/* ======================================================================
+ OpenLayers/Format/WMSDescribeLayer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSDescribeLayer
+ * Read SLD WMS DescribeLayer response
+ * DescribeLayer is meant to couple WMS to WFS and WCS
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WMSDescribeLayer = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.1".
+ */
+ defaultVersion: "1.1.1",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSDescribeLayer
+ * Create a new parser for WMS DescribeLayer responses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read DescribeLayer data from a string, and return the response.
+ * The OGC currently defines 2 formats which are allowed for output,
+ * so we need to parse these 2 types
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} Array of {<LayerDescription>} objects which have:
+ * - {String} owsType: WFS/WCS
+ * - {String} owsURL: the online resource
+ * - {String} typeName: the name of the typename on the service
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WMSDescribeLayer"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMSDescribeLayer/v1_1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSDescribeLayer.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSDescribeLayer.v1_1_1
+ * Read SLD WMS DescribeLayer response for WMS 1.1.X
+ * WMS 1.1.X is tightly coupled to SLD 1.0.0
+ *
+ * Example DescribeLayer request:
+ * http://demo.opengeo.org/geoserver/wms?request=DescribeLayer&version=1.1.1&layers=topp:states
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSDescribeLayer>
+ */
+OpenLayers.Format.WMSDescribeLayer.v1_1_1 = OpenLayers.Class(
+ OpenLayers.Format.WMSDescribeLayer, {
+
+ /**
+ * Constructor: OpenLayers.Format.WMSDescribeLayer
+ * Create a new parser for WMS DescribeLayer responses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.WMSDescribeLayer.prototype.initialize.apply(this,
+ [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read DescribeLayer data from a string, and return the response.
+ * The OGC defines 2 formats which are allowed for output,
+ * so we need to parse these 2 types for version 1.1.X
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Object with a layerDescriptions property, which holds an Array
+ * of {<LayerDescription>} objects which have:
+ * - {String} owsType: WFS/WCS
+ * - {String} owsURL: the online resource
+ * - {String} typeName: the name of the typename on the owsType service
+ * - {String} layerName: the name of the WMS layer we did a lookup for
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var children = root.childNodes;
+ var describelayer = {layerDescriptions: []};
+ var childNode, nodeName;
+ for(var i=0; i<children.length; ++i) {
+ childNode = children[i];
+ nodeName = childNode.nodeName;
+ if (nodeName == 'LayerDescription') {
+ var layerName = childNode.getAttribute('name');
+ var owsType = '';
+ var owsURL = '';
+ var typeName = '';
+ // check for owsType and owsURL attributes
+ if (childNode.getAttribute('owsType')) {
+ owsType = childNode.getAttribute('owsType');
+ owsURL = childNode.getAttribute('owsURL');
+ } else {
+ // look for wfs or wcs attribute
+ if (childNode.getAttribute('wfs') != '') {
+ owsType = 'WFS';
+ owsURL = childNode.getAttribute('wfs');
+ } else if (childNode.getAttribute('wcs') != '') {
+ owsType = 'WCS';
+ owsURL = childNode.getAttribute('wcs');
+ }
+ }
+ // look for Query child
+ var query = childNode.getElementsByTagName('Query');
+ if(query.length > 0) {
+ typeName = query[0].getAttribute('typeName');
+ if (!typeName) {
+ // because of Ionic bug
+ typeName = query[0].getAttribute('typename');
+ }
+ }
+ var layerDescription = {
+ layerName: layerName, owsType: owsType,
+ owsURL: owsURL, typeName: typeName
+ };
+ describelayer.layerDescriptions.push(layerDescription);
+
+ //TODO do this in deprecated.js instead:
+ // array style index for backwards compatibility
+ describelayer.length = describelayer.layerDescriptions.length;
+ describelayer[describelayer.length - 1] = layerDescription;
+
+ } else if (nodeName == 'ServiceException') {
+ // an exception must have occurred, so parse it
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ return {
+ error: parser.read(data)
+ };
+ }
+ }
+ return describelayer;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSDescribeLayer.v1_1_1"
+
+});
+
+// Version alias - workaround for http://trac.osgeo.org/mapserver/ticket/2257
+OpenLayers.Format.WMSDescribeLayer.v1_1_0 =
+ OpenLayers.Format.WMSDescribeLayer.v1_1_1;
+/* ======================================================================
+ OpenLayers/Layer/XYZ.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.XYZ
+ * The XYZ class is designed to make it easier for people who have tiles
+ * arranged by a standard XYZ grid.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * Default is true, as this is designed to be a base tile source.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * Whether the tile extents should be set to the defaults for
+ * spherical mercator. Useful for things like OpenStreetMap.
+ * Default is false, except for the OSM subclass.
+ */
+ sphericalMercator: false,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more zoom levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Using <zoomOffset> is an alternative to
+ * setting <serverResolutions> if you only want to expose a subset
+ * of the server resolutions.
+ */
+ zoomOffset: 0,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.XYZ
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, options) {
+ if (options && options.sphericalMercator || this.sphericalMercator) {
+ options = OpenLayers.Util.extend({
+ projection: "EPSG:900913",
+ numZoomLevels: 19
+ }, options);
+ }
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
+ name || this.name, url || this.url, {}, options
+ ]);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.XYZ(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ var xyz = this.getXYZ(bounds);
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ var s = '' + xyz.x + xyz.y + xyz.z;
+ url = this.selectUrl(s, url);
+ }
+
+ return OpenLayers.String.format(url, xyz);
+ },
+
+ /**
+ * Method: getXYZ
+ * Calculates x, y and z for the given bounds.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} - an object with x, y and z properties.
+ */
+ getXYZ: function(bounds) {
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.maxExtent.left) /
+ (res * this.tileSize.w));
+ var y = Math.round((this.maxExtent.top - bounds.top) /
+ (res * this.tileSize.h));
+ var z = this.getServerZoom();
+
+ if (this.wrapDateLine) {
+ var limit = Math.pow(2, z);
+ x = ((x % limit) + limit) % limit;
+ }
+
+ return {'x': x, 'y': y, 'z': z};
+ },
+
+ /* APIMethod: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left,
+ this.maxExtent.bottom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.XYZ"
+});
+/* ======================================================================
+ OpenLayers/Layer/OSM.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.OSM
+ * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap
+ * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use
+ * a different layer instead, you need to provide a different
+ * URL to the constructor. Here's an example for using OpenCycleMap:
+ *
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: name
+ * {String} The layer name. Defaults to "OpenStreetMap" if the first
+ * argument to the constructor is null or undefined.
+ */
+ name: "OpenStreetMap",
+
+ /**
+ * APIProperty: url
+ * {String} The tileset URL scheme. Defaults to
+ * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png
+ * (the official OSM tileset) if the second argument to the constructor
+ * is null or undefined. To use another tileset you can have something
+ * like this:
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ */
+ url: [
+ 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png'
+ ],
+
+ /**
+ * Property: attribution
+ * {String} The layer attribution.
+ */
+ attribution: "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
+
+ /**
+ * Property: sphericalMercator
+ * {Boolean}
+ */
+ sphericalMercator: true,
+
+ /**
+ * Property: wrapDateLine
+ * {Boolean}
+ */
+ wrapDateLine: true,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ *
+ * When using OSM tilesets other than the default ones, it may be
+ * necessary to set this to
+ *
+ * (code)
+ * {crossOriginKeyword: null}
+ * (end)
+ *
+ * if the server does not send Access-Control-Allow-Origin headers.
+ */
+ tileOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.OSM
+ *
+ * Parameters:
+ * name - {String} The layer name.
+ * url - {String} The tileset URL scheme.
+ * options - {Object} Configuration options for the layer. Any inherited
+ * layer option can be set in this object (e.g.
+ * <OpenLayers.Layer.Grid.buffer>).
+ */
+ initialize: function(name, url, options) {
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options && this.options.tileOptions);
+ },
+
+ /**
+ * Method: clone
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.OSM(
+ this.name, this.url, this.getOptions());
+ }
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM"
+});
+/* ======================================================================
+ OpenLayers/Renderer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer
+ * This is the base class for all renderers.
+ *
+ * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
+ * It is largely composed of virtual functions that are to be implemented
+ * in technology-specific subclasses, but there is some generic code too.
+ *
+ * The functions that *are* implemented here merely deal with the maintenance
+ * of the size and extent variables, as well as the cached 'resolution'
+ * value.
+ *
+ * A note to the user that all subclasses should use getResolution() instead
+ * of directly accessing this.resolution in order to correctly use the
+ * cacheing system.
+ *
+ */
+OpenLayers.Renderer = OpenLayers.Class({
+
+ /**
+ * Property: container
+ * {DOMElement}
+ */
+ container: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: extent
+ * {<OpenLayers.Bounds>}
+ */
+ extent: null,
+
+ /**
+ * Property: locked
+ * {Boolean} If the renderer is currently in a state where many things
+ * are changing, the 'locked' property is set to true. This means
+ * that renderers can expect at least one more drawFeature event to be
+ * called with the 'locked' property set to 'true': In some renderers,
+ * this might make sense to use as a 'only update local information'
+ * flag.
+ */
+ locked: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>}
+ */
+ size: null,
+
+ /**
+ * Property: resolution
+ * {Float} cache of current map resolution
+ */
+ resolution: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
+ */
+ map: null,
+
+ /**
+ * Property: featureDx
+ * {Number} Feature offset in x direction. Will be calculated for and
+ * applied to the current feature while rendering (see
+ * <calculateFeatureDx>).
+ */
+ featureDx: 0,
+
+ /**
+ * Constructor: OpenLayers.Renderer
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} options for this renderer. See sublcasses for
+ * supported options.
+ */
+ initialize: function(containerID, options) {
+ this.container = OpenLayers.Util.getElement(containerID);
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.container = null;
+ this.extent = null;
+ this.size = null;
+ this.resolution = null;
+ this.map = null;
+ },
+
+ /**
+ * APIMethod: supported
+ * This should be overridden by specific subclasses
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return false;
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ * We nullify the resolution cache (this.resolution) if resolutionChanged
+ * is set to true - this way it will be re-computed on the next
+ * getResolution() request.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ this.extent = extent.clone();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio);
+ this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
+ }
+ if (resolutionChanged) {
+ this.resolution = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ this.resolution = null;
+ },
+
+ /**
+ * Method: getResolution
+ * Uses cached copy of resolution if available to minimize computing
+ *
+ * Returns:
+ * {Float} The current map's resolution
+ */
+ getResolution: function() {
+ this.resolution = this.resolution || this.map.getResolution();
+ return this.resolution;
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. The optional style argument can be used
+ * to override the feature's own style. This method should only
+ * be called from layer.drawFeature().
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} true if the feature has been drawn completely, false if not,
+ * undefined if the feature had no geometry
+ */
+ drawFeature: function(feature, style) {
+ if(style == null) {
+ style = feature.style;
+ }
+ if (feature.geometry) {
+ var bounds = feature.geometry.getBounds();
+ if(bounds) {
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+ if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
+ style = {display: "none"};
+ } else {
+ this.calculateFeatureDx(bounds, worldBounds);
+ }
+ var rendered = this.drawGeometry(feature.geometry, style, feature.id);
+ if(style.display != "none" && style.label && rendered !== false) {
+
+ var location = feature.geometry.getCentroid();
+ if(style.labelXOffset || style.labelYOffset) {
+ var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
+ var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
+ var res = this.getResolution();
+ location.move(xOffset*res, yOffset*res);
+ }
+ this.drawText(feature.id, style, location);
+ } else {
+ this.removeText(feature.id);
+ }
+ return rendered;
+ }
+ }
+ },
+
+ /**
+ * Method: calculateFeatureDx
+ * {Number} Calculates the feature offset in x direction. Looking at the
+ * center of the feature bounds and the renderer extent, we calculate how
+ * many world widths the two are away from each other. This distance is
+ * used to shift the feature as close as possible to the center of the
+ * current enderer extent, which ensures that the feature is visible in the
+ * current viewport.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} Bounds of the feature
+ * worldBounds - {<OpenLayers.Bounds>} Bounds of the world
+ */
+ calculateFeatureDx: function(bounds, worldBounds) {
+ this.featureDx = 0;
+ if (worldBounds) {
+ var worldWidth = worldBounds.getWidth(),
+ rendererCenterX = (this.extent.left + this.extent.right) / 2,
+ featureCenterX = (bounds.left + bounds.right) / 2,
+ worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
+ this.featureDx = worldsAway * worldWidth;
+ }
+ },
+
+ /**
+ * Method: drawGeometry
+ *
+ * Draw a geometry. This should only be called from the renderer itself.
+ * Use layer.drawFeature() from outside the renderer.
+ * virtual function
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {<String>}
+ */
+ drawGeometry: function(geometry, style, featureId) {},
+
+ /**
+ * Method: drawText
+ * Function for drawing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {},
+
+ /**
+ * Method: removeText
+ * Function for removing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {},
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ * virtual function.
+ */
+ clear: function() {},
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ * How this happens is specific to the renderer. This should be
+ * called from layer.getFeatureFromEvent().
+ * Virtual function.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {},
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0, len=features.length; i<len; ++i) {
+ var feature = features[i];
+ this.eraseGeometry(feature.geometry, feature.id);
+ this.removeText(feature.id);
+ }
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Remove a geometry from the renderer (by id).
+ * virtual function.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a (different) renderer.
+ * To be implemented by subclasses that require a common renderer root for
+ * feature selection.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {},
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.container.id;
+ },
+
+ /**
+ * Method: applyDefaultSymbolizer
+ *
+ * Parameters:
+ * symbolizer - {Object}
+ *
+ * Returns:
+ * {Object}
+ */
+ applyDefaultSymbolizer: function(symbolizer) {
+ var result = OpenLayers.Util.extend({},
+ OpenLayers.Renderer.defaultSymbolizer);
+ if(symbolizer.stroke === false) {
+ delete result.strokeWidth;
+ delete result.strokeColor;
+ }
+ if(symbolizer.fill === false) {
+ delete result.fillColor;
+ }
+ OpenLayers.Util.extend(result, symbolizer);
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.defaultSymbolizer
+ * {Object} Properties from this symbolizer will be applied to symbolizers
+ * with missing properties. This can also be used to set a global
+ * symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
+ * following code before rendering any vector features:
+ * (code)
+ * OpenLayers.Renderer.defaultSymbolizer = {
+ * fillColor: "#808080",
+ * fillOpacity: 1,
+ * strokeColor: "#000000",
+ * strokeOpacity: 1,
+ * strokeWidth: 1,
+ * pointRadius: 3,
+ * graphicName: "square"
+ * };
+ * (end)
+ */
+OpenLayers.Renderer.defaultSymbolizer = {
+ fillColor: "#000000",
+ strokeColor: "#000000",
+ strokeWidth: 2,
+ fillOpacity: 1,
+ strokeOpacity: 1,
+ pointRadius: 0,
+ labelAlign: 'cm'
+};
+
+
+
+/**
+ * Constant: OpenLayers.Renderer.symbol
+ * Coordinate arrays for well known (named) symbols.
+ */
+OpenLayers.Renderer.symbol = {
+ "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
+ 303,215, 231,161, 321,161, 350,75],
+ "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
+ 4,0],
+ "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
+ "square": [0,0, 0,1, 1,1, 1,0, 0,0],
+ "triangle": [0,10, 10,10, 5,0, 0,10]
+};
+/* ======================================================================
+ OpenLayers/Renderer/Canvas.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.Canvas
+ * A renderer based on the 2D 'canvas' drawing element.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * APIProperty: hitDetection
+ * {Boolean} Allow for hit detection of features. Default is true.
+ */
+ hitDetection: true,
+
+ /**
+ * Property: hitOverflow
+ * {Number} The method for converting feature identifiers to color values
+ * supports 16777215 sequential values. Two features cannot be
+ * predictably detected if their identifiers differ by more than this
+ * value. The hitOverflow allows for bigger numbers (but the
+ * difference in values is still limited).
+ */
+ hitOverflow: 0,
+
+ /**
+ * Property: canvas
+ * {Canvas} The canvas context object.
+ */
+ canvas: null,
+
+ /**
+ * Property: features
+ * {Object} Internal object of feature/style pairs for use in redrawing the layer.
+ */
+ features: null,
+
+ /**
+ * Property: pendingRedraw
+ * {Boolean} The renderer needs a redraw call to render features added while
+ * the renderer was locked.
+ */
+ pendingRedraw: false,
+
+ /**
+ * Property: cachedSymbolBounds
+ * {Object} Internal cache of calculated symbol extents.
+ */
+ cachedSymbolBounds: {},
+
+ /**
+ * Constructor: OpenLayers.Renderer.Canvas
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} Optional properties to be set on the renderer.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+ this.root = document.createElement("canvas");
+ this.container.appendChild(this.root);
+ this.canvas = this.root.getContext("2d");
+ this.features = {};
+ if (this.hitDetection) {
+ this.hitCanvas = document.createElement("canvas");
+ this.hitContext = this.hitCanvas.getContext("2d");
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function() {
+ OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ // always redraw features
+ return false;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. Because the Canvas renderer has
+ * 'memory' of the features that it has drawn, we have to remove the
+ * feature so it doesn't redraw.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ this.eraseFeatures(this.features[featureId][0]);
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return OpenLayers.CANVAS_SUPPORTED;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Once the size is updated, redraw the canvas.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ var root = this.root;
+ root.style.width = size.w + "px";
+ root.style.height = size.h + "px";
+ root.width = size.w;
+ root.height = size.h;
+ this.resolution = null;
+ if (this.hitDetection) {
+ var hitCanvas = this.hitCanvas;
+ hitCanvas.style.width = size.w + "px";
+ hitCanvas.style.height = size.h + "px";
+ hitCanvas.width = size.w;
+ hitCanvas.height = size.h;
+ }
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. Stores the feature in the features list,
+ * then redraws the layer.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} The feature has been drawn completely. If the feature has no
+ * geometry, undefined will be returned. If the feature is not rendered
+ * for other reasons, false will be returned.
+ */
+ drawFeature: function(feature, style) {
+ var rendered;
+ if (feature.geometry) {
+ style = this.applyDefaultSymbolizer(style || feature.style);
+ // don't render if display none or feature outside extent
+ var bounds = feature.geometry.getBounds();
+
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+
+ var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});
+
+ rendered = (style.display !== "none") && !!bounds && intersects;
+ if (rendered) {
+ // keep track of what we have rendered for redraw
+ this.features[feature.id] = [feature, style];
+ }
+ else {
+ // remove from features tracked for redraw
+ delete(this.features[feature.id]);
+ }
+ this.pendingRedraw = true;
+ }
+ if (this.pendingRedraw && !this.locked) {
+ this.redraw();
+ this.pendingRedraw = false;
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: drawGeometry
+ * Used when looping (in redraw) over the features; draws
+ * the canvas.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0; i < geometry.components.length; i++) {
+ this.drawGeometry(geometry.components[i], style, featureId);
+ }
+ return;
+ }
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ this.drawPoint(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ this.drawLineString(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ this.drawLinearRing(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ this.drawPolygon(geometry, style, featureId);
+ break;
+ default:
+ break;
+ }
+ },
+
+ /**
+ * Method: drawExternalGraphic
+ * Called to draw External graphics.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawExternalGraphic: function(geometry, style, featureId) {
+ var img = new Image();
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ img.title = title;
+ }
+
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius * 2;
+ height = height ? height : style.pointRadius * 2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ var onLoad = function() {
+ if(!this.features[featureId]) {
+ return;
+ }
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+ if(!isNaN(p0) && !isNaN(p1)) {
+ var x = (p0 + xOffset) | 0;
+ var y = (p1 + yOffset) | 0;
+ var canvas = this.canvas;
+ canvas.globalAlpha = opacity;
+ var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
+ (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
+ /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
+ // 320 is the screen width of the G1 phone, for
+ // which drawImage works out of the box.
+ 320 / window.screen.width : 1
+ );
+ canvas.drawImage(
+ img, x*factor, y*factor, width*factor, height*factor
+ );
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId);
+ this.hitContext.fillRect(x, y, width, height);
+ }
+ }
+ };
+
+ img.onload = OpenLayers.Function.bind(onLoad, this);
+ img.src = style.externalGraphic;
+ },
+
+ /**
+ * Method: drawNamedSymbol
+ * Called to draw Well Known Graphic Symbol Name.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawNamedSymbol: function(geometry, style, featureId) {
+ var x, y, cx, cy, i, symbolBounds, scaling, angle;
+ var unscaledStrokeWidth;
+ var deg2rad = Math.PI / 180.0;
+
+ var symbol = OpenLayers.Renderer.symbol[style.graphicName];
+
+ if (!symbol) {
+ throw new Error(style.graphicName + ' is not a valid symbol name');
+ }
+
+ if (!symbol.length || symbol.length < 2) return;
+
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+
+ if (isNaN(p0) || isNaN(p1)) return;
+
+ // Use rounded line caps
+ this.canvas.lineCap = "round";
+ this.canvas.lineJoin = "round";
+
+ if (this.hitDetection) {
+ this.hitContext.lineCap = "round";
+ this.hitContext.lineJoin = "round";
+ }
+
+ // Scale and rotate symbols, using precalculated bounds whenever possible.
+ if (style.graphicName in this.cachedSymbolBounds) {
+ symbolBounds = this.cachedSymbolBounds[style.graphicName];
+ } else {
+ symbolBounds = new OpenLayers.Bounds();
+ for(i = 0; i < symbol.length; i+=2) {
+ symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
+ }
+ this.cachedSymbolBounds[style.graphicName] = symbolBounds;
+ }
+
+ // Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
+ // Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
+ this.canvas.save();
+ if (this.hitDetection) { this.hitContext.save(); }
+
+ // Step 3: place symbol at the desired location
+ this.canvas.translate(p0,p1);
+ if (this.hitDetection) { this.hitContext.translate(p0,p1); }
+
+ // Step 2a. rotate the symbol if necessary
+ angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
+ if (!isNaN(angle)) {
+ this.canvas.rotate(angle);
+ if (this.hitDetection) { this.hitContext.rotate(angle); }
+ }
+
+ // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
+ scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
+ this.canvas.scale(scaling,scaling);
+ if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
+
+ // Step 1: center the symbol at the origin
+ cx = symbolBounds.getCenterLonLat().lon;
+ cy = symbolBounds.getCenterLonLat().lat;
+ this.canvas.translate(-cx,-cy);
+ if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }
+
+ // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
+ // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
+ unscaledStrokeWidth = style.strokeWidth;
+ style.strokeWidth = unscaledStrokeWidth / scaling;
+
+ if (style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.fill();
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.fill();
+ }
+ }
+
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.stroke();
+
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style, scaling);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.hitContext.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.stroke();
+ }
+
+ }
+
+ style.strokeWidth = unscaledStrokeWidth;
+ this.canvas.restore();
+ if (this.hitDetection) { this.hitContext.restore(); }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: setCanvasStyle
+ * Prepare the canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * style - {Object} Symbolizer hash
+ */
+ setCanvasStyle: function(type, style) {
+ if (type === "fill") {
+ this.canvas.globalAlpha = style['fillOpacity'];
+ this.canvas.fillStyle = style['fillColor'];
+ } else if (type === "stroke") {
+ this.canvas.globalAlpha = style['strokeOpacity'];
+ this.canvas.strokeStyle = style['strokeColor'];
+ this.canvas.lineWidth = style['strokeWidth'];
+ } else {
+ this.canvas.globalAlpha = 0;
+ this.canvas.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: featureIdToHex
+ * Convert a feature ID string into an RGB hex string.
+ *
+ * Parameters:
+ * featureId - {String} Feature id
+ *
+ * Returns:
+ * {String} RGB hex string.
+ */
+ featureIdToHex: function(featureId) {
+ var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
+ if (id >= 16777216) {
+ this.hitOverflow = id - 16777215;
+ id = id % 16777216 + 1;
+ }
+ var hex = "000000" + id.toString(16);
+ var len = hex.length;
+ hex = "#" + hex.substring(len-6, len);
+ return hex;
+ },
+
+ /**
+ * Method: setHitContextStyle
+ * Prepare the hit canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * featureId - {String} The feature id.
+ * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
+ */
+ setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
+ var hex = this.featureIdToHex(featureId);
+ if (type == "fill") {
+ this.hitContext.globalAlpha = 1.0;
+ this.hitContext.fillStyle = hex;
+ } else if (type == "stroke") {
+ this.hitContext.globalAlpha = 1.0;
+ this.hitContext.strokeStyle = hex;
+ // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol
+ // on a transformed canvas, so the antialias width bump has to scale as well.
+ if (typeof strokeScaling === "undefined") {
+ this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
+ } else {
+ if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
+ }
+ } else {
+ this.hitContext.globalAlpha = 0;
+ this.hitContext.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawPoint: function(geometry, style, featureId) {
+ if(style.graphic !== false) {
+ if(style.externalGraphic) {
+ this.drawExternalGraphic(geometry, style, featureId);
+ } else if (style.graphicName && (style.graphicName != "circle")) {
+ this.drawNamedSymbol(geometry, style, featureId);
+ } else {
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+ if(!isNaN(p0) && !isNaN(p1)) {
+ var twoPi = Math.PI*2;
+ var radius = style.pointRadius;
+ if(style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.canvas.beginPath();
+ this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+ this.canvas.fill();
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+ this.hitContext.fill();
+ }
+ }
+
+ if(style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+ this.canvas.stroke();
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style);
+ this.hitContext.beginPath();
+ this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+ this.hitContext.stroke();
+ }
+ this.setCanvasStyle("reset");
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawLineString: function(geometry, style, featureId) {
+ style = OpenLayers.Util.applyDefaults({fill: false}, style);
+ this.drawLinearRing(geometry, style, featureId);
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawLinearRing: function(geometry, style, featureId) {
+ if (style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.renderPath(this.canvas, geometry, style, featureId, "fill");
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.renderPath(this.hitContext, geometry, style, featureId, "fill");
+ }
+ }
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.renderPath(this.canvas, geometry, style, featureId, "stroke");
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style);
+ this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: renderPath
+ * Render a path with stroke and optional fill.
+ */
+ renderPath: function(context, geometry, style, featureId, type) {
+ var components = geometry.components;
+ var len = components.length;
+ context.beginPath();
+ var start = this.getLocalXY(components[0]);
+ var x = start[0];
+ var y = start[1];
+ if (!isNaN(x) && !isNaN(y)) {
+ context.moveTo(start[0], start[1]);
+ for (var i=1; i<len; ++i) {
+ var pt = this.getLocalXY(components[i]);
+ context.lineTo(pt[0], pt[1]);
+ }
+ if (type === "fill") {
+ context.fill();
+ } else {
+ context.stroke();
+ }
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawPolygon: function(geometry, style, featureId) {
+ var components = geometry.components;
+ var len = components.length;
+ this.drawLinearRing(components[0], style, featureId);
+ // erase inner rings
+ for (var i=1; i<len; ++i) {
+ /**
+ * Note that this is overly agressive. Here we punch holes through
+ * all previously rendered features on the same canvas. A better
+ * solution for polygons with interior rings would be to draw the
+ * polygon on a sketch canvas first. We could erase all holes
+ * there and then copy the drawing to the layer canvas.
+ * TODO: http://trac.osgeo.org/openlayers/ticket/3130
+ */
+ this.canvas.globalCompositeOperation = "destination-out";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "destination-out";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
+ featureId
+ );
+ this.canvas.globalCompositeOperation = "source-over";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "source-over";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({fill: false}, style),
+ featureId
+ );
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * location - {<OpenLayers.Point>}
+ * style - {Object}
+ */
+ drawText: function(location, style) {
+ var pt = this.getLocalXY(location);
+
+ this.setCanvasStyle("reset");
+ this.canvas.fillStyle = style.fontColor;
+ this.canvas.globalAlpha = style.fontOpacity || 1.0;
+ var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
+ "normal", // "font-variant" not supported
+ style.fontWeight ? style.fontWeight : "normal",
+ style.fontSize ? style.fontSize : "1em",
+ style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ if (this.canvas.fillText) {
+ // HTML5
+ this.canvas.font = fontStyle;
+ this.canvas.textAlign =
+ OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
+ "center";
+ this.canvas.textBaseline =
+ OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
+ "middle";
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight =
+ this.canvas.measureText('Mg').height ||
+ this.canvas.measureText('xx').width;
+ pt[1] += lineHeight*vfactor*(numRows-1);
+ for (var i = 0; i < numRows; i++) {
+ if (style.labelOutlineWidth) {
+ this.canvas.save();
+ this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
+ this.canvas.strokeStyle = style.labelOutlineColor;
+ this.canvas.lineWidth = style.labelOutlineWidth;
+ this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
+ this.canvas.restore();
+ }
+ this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
+ }
+ } else if (this.canvas.mozDrawText) {
+ // Mozilla pre-Gecko1.9.1 (<FF3.1)
+ this.canvas.mozTextStyle = fontStyle;
+ // No built-in text alignment, so we measure and adjust the position
+ var hfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
+ if (hfactor == null) {
+ hfactor = -.5;
+ }
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight = this.canvas.mozMeasureText('xx');
+ pt[1] += lineHeight*(1 + (vfactor*numRows));
+ for (var i = 0; i < numRows; i++) {
+ var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
+ var y = pt[1] + (i*lineHeight);
+ this.canvas.translate(x, y);
+ this.canvas.mozDrawText(labelRows[i]);
+ this.canvas.translate(-x, -y);
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: getLocalXY
+ * transform geographic xy into pixel xy
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ */
+ getLocalXY: function(point) {
+ var resolution = this.getResolution();
+ var extent = this.extent;
+ var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution));
+ var y = ((extent.top / resolution) - point.y / resolution);
+ return [x, y];
+ },
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ */
+ clear: function() {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ this.features = {};
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a
+ * feature instead of a feature id to avoid an unnecessary lookup on the
+ * layer.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId, feature;
+
+ if (this.hitDetection && this.root.style.display !== "none") {
+ // this dragging check should go in the feature handler
+ if (!this.map.dragging) {
+ var xy = evt.xy;
+ var x = xy.x | 0;
+ var y = xy.y | 0;
+ var data = this.hitContext.getImageData(x, y, 1, 1).data;
+ if (data[3] === 255) { // antialiased
+ var id = data[2] + (256 * (data[1] + (256 * data[0])));
+ if (id) {
+ featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
+ try {
+ feature = this.features[featureId][0];
+ } catch(err) {
+ // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
+ // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
+ // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
+ }
+ }
+ }
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features; removes the feature from
+ * the list, then redraws the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0; i<features.length; ++i) {
+ delete this.features[features[i].id];
+ }
+ this.redraw();
+ },
+
+ /**
+ * Method: redraw
+ * The real 'meat' of the function: any time things have changed,
+ * redraw() can be called to loop over all the data and (you guessed
+ * it) redraw it. Unlike Elements-based Renderers, we can't interact
+ * with things once they're drawn, to remove them, for example, so
+ * instead we have to just clear everything and draw from scratch.
+ */
+ redraw: function() {
+ if (!this.locked) {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ var labelMap = [];
+ var feature, geometry, style;
+ var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
+ for (var id in this.features) {
+ if (!this.features.hasOwnProperty(id)) { continue; }
+ feature = this.features[id][0];
+ geometry = feature.geometry;
+ this.calculateFeatureDx(geometry.getBounds(), worldBounds);
+ style = this.features[id][1];
+ this.drawGeometry(geometry, style, feature.id);
+ if(style.label) {
+ labelMap.push([feature, style]);
+ }
+ }
+ var item;
+ for (var i=0, len=labelMap.length; i<len; ++i) {
+ item = labelMap[i];
+ this.drawText(item[0].geometry.getCentroid(), item[1]);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Canvas"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
+ "l": "left",
+ "r": "right",
+ "t": "top",
+ "b": "bottom"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
+ "l": 0,
+ "r": -1,
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
+ * {Number} Scale factor to apply to the canvas drawImage arguments. This
+ * is always 1 except for Android 2.1 devices, to work around
+ * http://code.google.com/p/android/issues/detail?id=5141.
+ */
+OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
+/* ======================================================================
+ OpenLayers/Format/OSM.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OSM
+ * OSM parser. Create a new instance with the
+ * <OpenLayers.Format.OSM> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: checkTags
+ * {Boolean} Should tags be checked to determine whether something
+ * should be treated as a seperate node. Will slow down parsing.
+ * Default is false.
+ */
+ checkTags: false,
+
+ /**
+ * Property: interestingTagsExclude
+ * {Array} List of tags to exclude from 'interesting' checks on nodes.
+ * Must be set when creating the format. Will only be used if checkTags
+ * is set.
+ */
+ interestingTagsExclude: null,
+
+ /**
+ * APIProperty: areaTags
+ * {Array} List of tags indicating that something is an area.
+ * Must be set when creating the format. Will only be used if
+ * checkTags is true.
+ */
+ areaTags: null,
+
+ /**
+ * Constructor: OpenLayers.Format.OSM
+ * Create a new parser for OSM.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ var layer_defaults = {
+ 'interestingTagsExclude': ['source', 'source_ref',
+ 'source:ref', 'history', 'attribution', 'created_by'],
+ 'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
+ 'historic', 'landuse', 'military', 'natural', 'sport']
+ };
+
+ layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
+
+ var interesting = {};
+ for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
+ interesting[layer_defaults.interestingTagsExclude[i]] = true;
+ }
+ layer_defaults.interestingTagsExclude = interesting;
+
+ var area = {};
+ for (var i = 0; i < layer_defaults.areaTags.length; i++) {
+ area[layer_defaults.areaTags[i]] = true;
+ }
+ layer_defaults.areaTags = area;
+
+ // OSM coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a OSM doc
+
+ * Parameters:
+ * doc - {Element}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+
+ var nodes = this.getNodes(doc);
+ var ways = this.getWays(doc);
+
+ // Geoms will contain at least ways.length entries.
+ var feat_list = new Array(ways.length);
+
+ for (var i = 0; i < ways.length; i++) {
+ // We know the minimal of this one ahead of time. (Could be -1
+ // due to areas/polygons)
+ var point_list = new Array(ways[i].nodes.length);
+
+ var poly = this.isWayArea(ways[i]) ? 1 : 0;
+ for (var j = 0; j < ways[i].nodes.length; j++) {
+ var node = nodes[ways[i].nodes[j]];
+
+ var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
+
+ // Since OSM is topological, we stash the node ID internally.
+ point.osm_id = parseInt(ways[i].nodes[j]);
+ point_list[j] = point;
+
+ // We don't display nodes if they're used inside other
+ // elements.
+ node.used = true;
+ }
+ var geometry = null;
+ if (poly) {
+ geometry = new OpenLayers.Geometry.Polygon(
+ new OpenLayers.Geometry.LinearRing(point_list));
+ } else {
+ geometry = new OpenLayers.Geometry.LineString(point_list);
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ var feat = new OpenLayers.Feature.Vector(geometry,
+ ways[i].tags);
+ feat.osm_id = parseInt(ways[i].id);
+ feat.fid = "way." + feat.osm_id;
+ feat_list[i] = feat;
+ }
+ for (var node_id in nodes) {
+ var node = nodes[node_id];
+ if (!node.used || this.checkTags) {
+ var tags = null;
+
+ if (this.checkTags) {
+ var result = this.getTags(node.node, true);
+ if (node.used && !result[1]) {
+ continue;
+ }
+ tags = result[0];
+ } else {
+ tags = this.getTags(node.node);
+ }
+
+ var feat = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(node['lon'], node['lat']),
+ tags);
+ if (this.internalProjection && this.externalProjection) {
+ feat.geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ feat.osm_id = parseInt(node_id);
+ feat.fid = "node." + feat.osm_id;
+ feat_list.push(feat);
+ }
+ // Memory cleanup
+ node.node = null;
+ }
+ return feat_list;
+ },
+
+ /**
+ * Method: getNodes
+ * Return the node items from a doc.
+ *
+ * Parameters:
+ * doc - {DOMElement} node to parse tags from
+ */
+ getNodes: function(doc) {
+ var node_list = doc.getElementsByTagName("node");
+ var nodes = {};
+ for (var i = 0; i < node_list.length; i++) {
+ var node = node_list[i];
+ var id = node.getAttribute("id");
+ nodes[id] = {
+ 'lat': node.getAttribute("lat"),
+ 'lon': node.getAttribute("lon"),
+ 'node': node
+ };
+ }
+ return nodes;
+ },
+
+ /**
+ * Method: getWays
+ * Return the way items from a doc.
+ *
+ * Parameters:
+ * doc - {DOMElement} node to parse tags from
+ */
+ getWays: function(doc) {
+ var way_list = doc.getElementsByTagName("way");
+ var return_ways = [];
+ for (var i = 0; i < way_list.length; i++) {
+ var way = way_list[i];
+ var way_object = {
+ id: way.getAttribute("id")
+ };
+
+ way_object.tags = this.getTags(way);
+
+ var node_list = way.getElementsByTagName("nd");
+
+ way_object.nodes = new Array(node_list.length);
+
+ for (var j = 0; j < node_list.length; j++) {
+ way_object.nodes[j] = node_list[j].getAttribute("ref");
+ }
+ return_ways.push(way_object);
+ }
+ return return_ways;
+
+ },
+
+ /**
+ * Method: getTags
+ * Return the tags list attached to a specific DOM element.
+ *
+ * Parameters:
+ * dom_node - {DOMElement} node to parse tags from
+ * interesting_tags - {Boolean} whether the return from this function should
+ * return a boolean indicating that it has 'interesting tags' --
+ * tags like attribution and source are ignored. (To change the list
+ * of tags, see interestingTagsExclude)
+ *
+ * Returns:
+ * tags - {Object} hash of tags
+ * interesting - {Boolean} if interesting_tags is passed, returns
+ * whether there are any interesting tags on this element.
+ */
+ getTags: function(dom_node, interesting_tags) {
+ var tag_list = dom_node.getElementsByTagName("tag");
+ var tags = {};
+ var interesting = false;
+ for (var j = 0; j < tag_list.length; j++) {
+ var key = tag_list[j].getAttribute("k");
+ tags[key] = tag_list[j].getAttribute("v");
+ if (interesting_tags) {
+ if (!this.interestingTagsExclude[key]) {
+ interesting = true;
+ }
+ }
+ }
+ return interesting_tags ? [tags, interesting] : tags;
+ },
+
+ /**
+ * Method: isWayArea
+ * Given a way object from getWays, check whether the tags and geometry
+ * indicate something is an area.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isWayArea: function(way) {
+ var poly_shaped = false;
+ var poly_tags = false;
+
+ if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
+ poly_shaped = true;
+ }
+ if (this.checkTags) {
+ for(var key in way.tags) {
+ if (this.areaTags[key]) {
+ poly_tags = true;
+ break;
+ }
+ }
+ }
+ return poly_shaped && (this.checkTags ? poly_tags : true);
+ },
+
+ /**
+ * APIMethod: write
+ * Takes a list of features, returns a serialized OSM format file for use
+ * in tools like JOSM.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ write: function(features) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ this.osm_id = 1;
+ this.created_nodes = {};
+ var root_node = this.createElementNS(null, "osm");
+ root_node.setAttribute("version", "0.5");
+ root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
+
+ // Loop backwards, because the deserializer puts nodes last, and
+ // we want them first if possible
+ for(var i = features.length - 1; i >= 0; i--) {
+ var nodes = this.createFeatureNodes(features[i]);
+ for (var j = 0; j < nodes.length; j++) {
+ root_node.appendChild(nodes[j]);
+ }
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
+ },
+
+ /**
+ * Method: createFeatureNodes
+ * Takes a feature, returns a list of nodes from size 0->n.
+ * Will include all pieces of the serialization that are required which
+ * have not already been created. Calls out to createXML based on geometry
+ * type.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ createFeatureNodes: function(feature) {
+ var nodes = [];
+ var className = feature.geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ type = type.toLowerCase();
+ var builder = this.createXML[type];
+ if (builder) {
+ nodes = builder.apply(this, [feature]);
+ }
+ return nodes;
+ },
+
+ /**
+ * Method: createXML
+ * Takes a feature, returns a list of nodes from size 0->n.
+ * Will include all pieces of the serialization that are required which
+ * have not already been created.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ createXML: {
+ 'point': function(point) {
+ var id = null;
+ var geometry = point.geometry ? point.geometry : point;
+
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+
+ var already_exists = false; // We don't return anything if the node
+ // has already been created
+ if (point.osm_id) {
+ id = point.osm_id;
+ if (this.created_nodes[id]) {
+ already_exists = true;
+ }
+ } else {
+ id = -this.osm_id;
+ this.osm_id++;
+ }
+ if (already_exists) {
+ node = this.created_nodes[id];
+ } else {
+ var node = this.createElementNS(null, "node");
+ }
+ this.created_nodes[id] = node;
+ node.setAttribute("id", id);
+ node.setAttribute("lon", geometry.x);
+ node.setAttribute("lat", geometry.y);
+ if (point.attributes) {
+ this.serializeTags(point, node);
+ }
+ this.setState(point, node);
+ return already_exists ? [] : [node];
+ },
+ linestring: function(feature) {
+ var id;
+ var nodes = [];
+ var geometry = feature.geometry;
+ if (feature.osm_id) {
+ id = feature.osm_id;
+ } else {
+ id = -this.osm_id;
+ this.osm_id++;
+ }
+ var way = this.createElementNS(null, "way");
+ way.setAttribute("id", id);
+ for (var i = 0; i < geometry.components.length; i++) {
+ var node = this.createXML['point'].apply(this, [geometry.components[i]]);
+ if (node.length) {
+ node = node[0];
+ var node_ref = node.getAttribute("id");
+ nodes.push(node);
+ } else {
+ node_ref = geometry.components[i].osm_id;
+ node = this.created_nodes[node_ref];
+ }
+ this.setState(feature, node);
+ var nd_dom = this.createElementNS(null, "nd");
+ nd_dom.setAttribute("ref", node_ref);
+ way.appendChild(nd_dom);
+ }
+ this.serializeTags(feature, way);
+ nodes.push(way);
+
+ return nodes;
+ },
+ polygon: function(feature) {
+ var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes);
+ var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs);
+ feat.osm_id = feature.osm_id;
+ return this.createXML['linestring'].apply(this, [feat]);
+ }
+ },
+
+ /**
+ * Method: serializeTags
+ * Given a feature, serialize the attributes onto the given node.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * node - {DOMNode}
+ */
+ serializeTags: function(feature, node) {
+ for (var key in feature.attributes) {
+ var tag = this.createElementNS(null, "tag");
+ tag.setAttribute("k", key);
+ tag.setAttribute("v", feature.attributes[key]);
+ node.appendChild(tag);
+ }
+ },
+
+ /**
+ * Method: setState
+ * OpenStreetMap has a convention that 'state' is stored for modification or deletion.
+ * This allows the file to be uploaded via JOSM or the bulk uploader tool.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * node - {DOMNode}
+ */
+ setState: function(feature, node) {
+ if (feature.state) {
+ var state = null;
+ switch(feature.state) {
+ case OpenLayers.State.UPDATE:
+ state = "modify";
+ case OpenLayers.State.DELETE:
+ state = "delete";
+ }
+ if (state) {
+ node.setAttribute("action", state);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OSM"
+});
+/* ======================================================================
+ OpenLayers/Handler/Keyboard.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.handler.Keyboard
+ * A handler for keyboard events. Create a new instance with the
+ * <OpenLayers.Handler.Keyboard> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, {
+
+ /* http://www.quirksmode.org/js/keys.html explains key x-browser
+ key handling quirks in pretty nice detail */
+
+ /**
+ * Constant: KEY_EVENTS
+ * keydown, keypress, keyup
+ */
+ KEY_EVENTS: ["keydown", "keyup"],
+
+ /**
+ * Property: eventListener
+ * {Function}
+ */
+ eventListener: null,
+
+ /**
+ * Property: observeElement
+ * {DOMElement|String} The DOM element on which we listen for
+ * key events. Default to the document.
+ */
+ observeElement: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Keyboard
+ * Returns a new keyboard handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'keydown', 'keypress', and 'keyup' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ // cache the bound event listener method so it can be unobserved later
+ this.eventListener = OpenLayers.Function.bindAsEventListener(
+ this.handleKeyEvent, this
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ this.eventListener = null;
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.observeElement = this.observeElement || document;
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.observe(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.stopObserving(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleKeyEvent
+ */
+ handleKeyEvent: function (evt) {
+ if (this.checkModifiers(evt)) {
+ this.callback(evt.type, [evt]);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Keyboard"
+});
+/* ======================================================================
+ OpenLayers/Control/ModifyFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ModifyFeature
+ * Control to modify features. When activated, a click renders the vertices
+ * of a feature - these vertices can then be dragged. By default, the
+ * delete key will delete the vertex under the mouse. New features are
+ * added by dragging "virtual vertices" between vertices. Create a new
+ * control with the <OpenLayers.Control.ModifyFeature> constructor.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, dragging vertices will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict modification to a limited set of geometry
+ * types, send a list of strings corresponding to the geometry class
+ * names.
+ */
+ geometryTypes: null,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click.
+ * Default is true.
+ */
+ toggle: true,
+
+ /**
+ * APIProperty: standalone
+ * {Boolean} Set to true to create a control without SelectFeature
+ * capabilities. Default is false. If standalone is true, to modify
+ * a feature, call the <selectFeature> method with the target feature.
+ * Note that you must call the <unselectFeature> method to finish
+ * feature modification in standalone mode (before starting to modify
+ * another feature).
+ */
+ standalone: false,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} Feature currently available for modification.
+ */
+ feature: null,
+
+ /**
+ * Property: vertex
+ * {<OpenLayers.Feature.Vector>} Vertex currently being modified.
+ */
+ vertex: null,
+
+ /**
+ * Property: vertices
+ * {Array(<OpenLayers.Feature.Vector>)} Verticies currently available
+ * for dragging.
+ */
+ vertices: null,
+
+ /**
+ * Property: virtualVertices
+ * {Array(<OpenLayers.Feature.Vector>)} Virtual vertices in the middle
+ * of each edge.
+ */
+ virtualVertices: null,
+
+ /**
+ * Property: handlers
+ * {Object}
+ */
+ handlers: null,
+
+ /**
+ * APIProperty: deleteCodes
+ * {Array(Integer)} Keycodes for deleting verticies. Set to null to disable
+ * vertex deltion by keypress. If non-null, keypresses with codes
+ * in this array will delete vertices under the mouse. Default
+ * is 46 and 68, the 'delete' and lowercase 'd' keys.
+ */
+ deleteCodes: null,
+
+ /**
+ * APIProperty: virtualStyle
+ * {Object} A symbolizer to be used for virtual vertices.
+ */
+ virtualStyle: null,
+
+ /**
+ * APIProperty: vertexRenderIntent
+ * {String} The renderIntent to use for vertices. If no <virtualStyle> is
+ * provided, this renderIntent will also be used for virtual vertices, with
+ * a fillOpacity and strokeOpacity of 0.3. Default is null, which means
+ * that the layer's default style will be used for vertices.
+ */
+ vertexRenderIntent: null,
+
+ /**
+ * APIProperty: mode
+ * {Integer} Bitfields specifying the modification mode. Defaults to
+ * OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a
+ * combination of options, use the | operator. For example, to allow
+ * the control to both resize and rotate features, use the following
+ * syntax
+ * (code)
+ * control.mode = OpenLayers.Control.ModifyFeature.RESIZE |
+ * OpenLayers.Control.ModifyFeature.ROTATE;
+ * (end)
+ */
+ mode: null,
+
+ /**
+ * APIProperty: createVertices
+ * {Boolean} Create new vertices by dragging the virtual vertices
+ * in the middle of each edge. Default is true.
+ */
+ createVertices: true,
+
+ /**
+ * Property: modified
+ * {Boolean} The currently selected feature has been modified.
+ */
+ modified: false,
+
+ /**
+ * Property: radiusHandle
+ * {<OpenLayers.Feature.Vector>} A handle for rotating/resizing a feature.
+ */
+ radiusHandle: null,
+
+ /**
+ * Property: dragHandle
+ * {<OpenLayers.Feature.Vector>} A handle for dragging a feature.
+ */
+ dragHandle: null,
+
+ /**
+ * APIProperty: onModificationStart
+ * {Function} *Deprecated*. Register for "beforefeaturemodified" instead.
+ * The "beforefeaturemodified" event is triggered on the layer before
+ * any modification begins.
+ *
+ * Optional function to be called when a feature is selected
+ * to be modified. The function should expect to be called with a
+ * feature. This could be used for example to allow to lock the
+ * feature on server-side.
+ */
+ onModificationStart: function() {},
+
+ /**
+ * APIProperty: onModification
+ * {Function} *Deprecated*. Register for "featuremodified" instead.
+ * The "featuremodified" event is triggered on the layer with each
+ * feature modification.
+ *
+ * Optional function to be called when a feature has been
+ * modified. The function should expect to be called with a feature.
+ */
+ onModification: function() {},
+
+ /**
+ * APIProperty: onModificationEnd
+ * {Function} *Deprecated*. Register for "afterfeaturemodified" instead.
+ * The "afterfeaturemodified" event is triggered on the layer after
+ * a feature has been modified.
+ *
+ * Optional function to be called when a feature is finished
+ * being modified. The function should expect to be called with a
+ * feature.
+ */
+ onModificationEnd: function() {},
+
+ /**
+ * Constructor: OpenLayers.Control.ModifyFeature
+ * Create a new modify feature control.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+ * will be modified.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ options = options || {};
+ this.layer = layer;
+ this.vertices = [];
+ this.virtualVertices = [];
+ this.virtualStyle = OpenLayers.Util.extend({},
+ this.layer.style ||
+ this.layer.styleMap.createSymbolizer(null, options.vertexRenderIntent)
+ );
+ this.virtualStyle.fillOpacity = 0.3;
+ this.virtualStyle.strokeOpacity = 0.3;
+ this.deleteCodes = [46, 68];
+ this.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ if(!(OpenLayers.Util.isArray(this.deleteCodes))) {
+ this.deleteCodes = [this.deleteCodes];
+ }
+
+ // configure the drag handler
+ var dragCallbacks = {
+ down: function(pixel) {
+ this.vertex = null;
+ var feature = this.layer.getFeatureFromEvent(
+ this.handlers.drag.evt);
+ if (feature) {
+ this.dragStart(feature);
+ } else if (this.clickout) {
+ this._unselect = this.feature;
+ }
+ },
+ move: function(pixel) {
+ delete this._unselect;
+ if (this.vertex) {
+ this.dragVertex(this.vertex, pixel);
+ }
+ },
+ up: function() {
+ this.handlers.drag.stopDown = false;
+ if (this._unselect) {
+ this.unselectFeature(this._unselect);
+ delete this._unselect;
+ }
+ },
+ done: function(pixel) {
+ if (this.vertex) {
+ this.dragComplete(this.vertex);
+ }
+ }
+ };
+ var dragOptions = {
+ documentDrag: this.documentDrag,
+ stopDown: false
+ };
+
+ // configure the keyboard handler
+ var keyboardOptions = {
+ keydown: this.handleKeypress
+ };
+ this.handlers = {
+ keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions),
+ drag: new OpenLayers.Handler.Drag(this, dragCallbacks, dragOptions)
+ };
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ }
+ this.layer = null;
+ OpenLayers.Control.prototype.destroy.apply(this, []);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the control.
+ */
+ activate: function() {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ return (this.handlers.keyboard.activate() &&
+ this.handlers.drag.activate() &&
+ OpenLayers.Control.prototype.activate.apply(this, arguments));
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control.
+ *
+ * Returns:
+ * {Boolean} Successfully deactivated the control.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ // the return from the controls is unimportant in this case
+ if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.layer.removeFeatures(this.virtualVertices, {silent: true});
+ this.vertices = [];
+ this.handlers.drag.deactivate();
+ this.handlers.keyboard.deactivate();
+ var feature = this.feature;
+ if (feature && feature.geometry && feature.layer) {
+ this.unselectFeature(feature);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: beforeSelectFeature
+ * Called before a feature is selected.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature about to be selected.
+ */
+ beforeSelectFeature: function(feature) {
+ return this.layer.events.triggerEvent(
+ "beforefeaturemodified", {feature: feature}
+ );
+ },
+
+ /**
+ * APIMethod: selectFeature
+ * Select a feature for modification in standalone mode. In non-standalone
+ * mode, this method is called when a feature is selected by clicking.
+ * Register a listener to the beforefeaturemodified event and return false
+ * to prevent feature modification.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} the selected feature.
+ */
+ selectFeature: function(feature) {
+ if (this.feature === feature ||
+ (this.geometryTypes && OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) == -1)) {
+ return;
+ }
+ if (this.beforeSelectFeature(feature) !== false) {
+ if (this.feature) {
+ this.unselectFeature(this.feature);
+ }
+ this.feature = feature;
+ this.layer.selectedFeatures.push(feature);
+ this.layer.drawFeature(feature, 'select');
+ this.modified = false;
+ this.resetVertices();
+ this.onModificationStart(this.feature);
+ }
+ // keep track of geometry modifications
+ var modified = feature.modified;
+ if (feature.geometry && !(modified && modified.geometry)) {
+ this._originalGeometry = feature.geometry.clone();
+ }
+ },
+
+ /**
+ * APIMethod: unselectFeature
+ * Called when the select feature control unselects a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The unselected feature.
+ */
+ unselectFeature: function(feature) {
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ if(this.dragHandle) {
+ this.layer.destroyFeatures([this.dragHandle], {silent: true});
+ delete this.dragHandle;
+ }
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ delete this.radiusHandle;
+ }
+ this.layer.drawFeature(this.feature, 'default');
+ this.feature = null;
+ OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature);
+ this.onModificationEnd(feature);
+ this.layer.events.triggerEvent("afterfeaturemodified", {
+ feature: feature,
+ modified: this.modified
+ });
+ this.modified = false;
+ },
+
+
+ /**
+ * Method: dragStart
+ * Called by the drag handler before a feature is dragged. This method is
+ * used to differentiate between points and vertices
+ * of higher order geometries.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
+ * dragged.
+ */
+ dragStart: function(feature) {
+ var isPoint = feature.geometry.CLASS_NAME ==
+ 'OpenLayers.Geometry.Point';
+ if (!this.standalone &&
+ ((!feature._sketch && isPoint) || !feature._sketch)) {
+ if (this.toggle && this.feature === feature) {
+ // mark feature for unselection
+ this._unselect = feature;
+ }
+ this.selectFeature(feature);
+ }
+ if (feature._sketch || isPoint) {
+ // feature is a drag or virtual handle or point
+ this.vertex = feature;
+ this.handlers.drag.stopDown = true;
+ }
+ },
+
+ /**
+ * Method: dragVertex
+ * Called by the drag handler with each drag move of a vertex.
+ *
+ * Parameters:
+ * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
+ */
+ dragVertex: function(vertex, pixel) {
+ var pos = this.map.getLonLatFromViewPortPx(pixel);
+ var geom = vertex.geometry;
+ geom.move(pos.lon - geom.x, pos.lat - geom.y);
+ this.modified = true;
+ /**
+ * Five cases:
+ * 1) dragging a simple point
+ * 2) dragging a virtual vertex
+ * 3) dragging a drag handle
+ * 4) dragging a real vertex
+ * 5) dragging a radius handle
+ */
+ if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ // dragging a simple point
+ this.layer.events.triggerEvent("vertexmodified", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: pixel
+ });
+ } else {
+ if(vertex._index) {
+ // dragging a virtual vertex
+ vertex.geometry.parent.addComponent(vertex.geometry,
+ vertex._index);
+ // move from virtual to real vertex
+ delete vertex._index;
+ OpenLayers.Util.removeItem(this.virtualVertices, vertex);
+ this.vertices.push(vertex);
+ } else if(vertex == this.dragHandle) {
+ // dragging a drag handle
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ this.radiusHandle = null;
+ }
+ } else if(vertex !== this.radiusHandle) {
+ // dragging a real vertex
+ this.layer.events.triggerEvent("vertexmodified", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: pixel
+ });
+ }
+ // dragging a radius handle - no special treatment
+ if(this.virtualVertices.length > 0) {
+ this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ }
+ this.layer.drawFeature(this.feature, this.standalone ? undefined :
+ 'select');
+ }
+ // keep the vertex on top so it gets the mouseout after dragging
+ // this should be removed in favor of an option to draw under or
+ // maintain node z-index
+ this.layer.drawFeature(vertex);
+ },
+
+ /**
+ * Method: dragComplete
+ * Called by the drag handler when the feature dragging is complete.
+ *
+ * Parameters:
+ * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ */
+ dragComplete: function(vertex) {
+ this.resetVertices();
+ this.setFeatureState();
+ this.onModification(this.feature);
+ this.layer.events.triggerEvent("featuremodified",
+ {feature: this.feature});
+ },
+
+ /**
+ * Method: setFeatureState
+ * Called when the feature is modified. If the current state is not
+ * INSERT or DELETE, the state is set to UPDATE.
+ */
+ setFeatureState: function() {
+ if(this.feature.state != OpenLayers.State.INSERT &&
+ this.feature.state != OpenLayers.State.DELETE) {
+ this.feature.state = OpenLayers.State.UPDATE;
+ if (this.modified && this._originalGeometry) {
+ var feature = this.feature;
+ feature.modified = OpenLayers.Util.extend(feature.modified, {
+ geometry: this._originalGeometry
+ });
+ delete this._originalGeometry;
+ }
+ }
+ },
+
+ /**
+ * Method: resetVertices
+ */
+ resetVertices: function() {
+ if(this.vertices.length > 0) {
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ }
+ if(this.virtualVertices.length > 0) {
+ this.layer.removeFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ }
+ if(this.dragHandle) {
+ this.layer.destroyFeatures([this.dragHandle], {silent: true});
+ this.dragHandle = null;
+ }
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ this.radiusHandle = null;
+ }
+ if(this.feature &&
+ this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
+ if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
+ this.collectDragHandle();
+ }
+ if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
+ OpenLayers.Control.ModifyFeature.RESIZE))) {
+ this.collectRadiusHandle();
+ }
+ if(this.mode & OpenLayers.Control.ModifyFeature.RESHAPE){
+ // Don't collect vertices when we're resizing
+ if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)){
+ this.collectVertices();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: handleKeypress
+ * Called by the feature handler on keypress. This is used to delete
+ * vertices. If the <deleteCode> property is set, vertices will
+ * be deleted when a feature is selected for modification and
+ * the mouse is over a vertex.
+ *
+ * Parameters:
+ * evt - {Event} Keypress event.
+ */
+ handleKeypress: function(evt) {
+ var code = evt.keyCode;
+
+ // check for delete key
+ if(this.feature &&
+ OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
+ var vertex = this.layer.getFeatureFromEvent(this.handlers.drag.evt);
+ if (vertex &&
+ OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
+ !this.handlers.drag.dragging && vertex.geometry.parent) {
+ // remove the vertex
+ vertex.geometry.parent.removeComponent(vertex.geometry);
+ this.layer.events.triggerEvent("vertexremoved", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: evt.xy
+ });
+ this.layer.drawFeature(this.feature, this.standalone ?
+ undefined : 'select');
+ this.modified = true;
+ this.resetVertices();
+ this.setFeatureState();
+ this.onModification(this.feature);
+ this.layer.events.triggerEvent("featuremodified",
+ {feature: this.feature});
+ }
+ }
+ },
+
+ /**
+ * Method: collectVertices
+ * Collect the vertices from the modifiable feature's geometry and push
+ * them on to the control's vertices array.
+ */
+ collectVertices: function() {
+ this.vertices = [];
+ this.virtualVertices = [];
+ var control = this;
+ function collectComponentVertices(geometry) {
+ var i, vertex, component, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(geometry);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ var numVert = geometry.components.length;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ numVert -= 1;
+ }
+ for(i=0; i<numVert; ++i) {
+ component = geometry.components[i];
+ if(component.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(component);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ collectComponentVertices(component);
+ }
+ }
+
+ // add virtual vertices in the middle of each edge
+ if (control.createVertices && geometry.CLASS_NAME != "OpenLayers.Geometry.MultiPoint") {
+ for(i=0, len=geometry.components.length; i<len-1; ++i) {
+ var prevVertex = geometry.components[i];
+ var nextVertex = geometry.components[i + 1];
+ if(prevVertex.CLASS_NAME == "OpenLayers.Geometry.Point" &&
+ nextVertex.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ var x = (prevVertex.x + nextVertex.x) / 2;
+ var y = (prevVertex.y + nextVertex.y) / 2;
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y),
+ null, control.virtualStyle
+ );
+ // set the virtual parent and intended index
+ point.geometry.parent = geometry;
+ point._index = i + 1;
+ point._sketch = true;
+ control.virtualVertices.push(point);
+ }
+ }
+ }
+ }
+ }
+ collectComponentVertices.call(this, this.feature.geometry);
+ this.layer.addFeatures(this.virtualVertices, {silent: true});
+ this.layer.addFeatures(this.vertices, {silent: true});
+ },
+
+ /**
+ * Method: collectDragHandle
+ * Collect the drag handle for the selected geometry.
+ */
+ collectDragHandle: function() {
+ var geometry = this.feature.geometry;
+ var center = geometry.getBounds().getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var origin = new OpenLayers.Feature.Vector(originGeometry);
+ originGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ geometry.move(x, y);
+ };
+ origin._sketch = true;
+ this.dragHandle = origin;
+ this.dragHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.dragHandle], {silent: true});
+ },
+
+ /**
+ * Method: collectRadiusHandle
+ * Collect the radius handle for the selected geometry.
+ */
+ collectRadiusHandle: function() {
+ var geometry = this.feature.geometry;
+ var bounds = geometry.getBounds();
+ var center = bounds.getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var radiusGeometry = new OpenLayers.Geometry.Point(
+ bounds.right, bounds.bottom
+ );
+ var radius = new OpenLayers.Feature.Vector(radiusGeometry);
+ var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
+ var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
+ var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
+
+ radiusGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ var dx1 = this.x - originGeometry.x;
+ var dy1 = this.y - originGeometry.y;
+ var dx0 = dx1 - x;
+ var dy0 = dy1 - y;
+ if(rotate) {
+ var a0 = Math.atan2(dy0, dx0);
+ var a1 = Math.atan2(dy1, dx1);
+ var angle = a1 - a0;
+ angle *= 180 / Math.PI;
+ geometry.rotate(angle, originGeometry);
+ }
+ if(resize) {
+ var scale, ratio;
+ // 'resize' together with 'reshape' implies that the aspect
+ // ratio of the geometry will not be preserved whilst resizing
+ if (reshape) {
+ scale = dy1 / dy0;
+ ratio = (dx1 / dx0) / scale;
+ } else {
+ var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+ var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+ scale = l1 / l0;
+ }
+ geometry.resize(scale, originGeometry, ratio);
+ }
+ };
+ radius._sketch = true;
+ this.radiusHandle = radius;
+ this.radiusHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.radiusHandle], {silent: true});
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and all handlers.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The control's map.
+ */
+ setMap: function(map) {
+ this.handlers.drag.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ModifyFeature"
+});
+
+/**
+ * Constant: RESHAPE
+ * {Integer} Constant used to make the control work in reshape mode
+ */
+OpenLayers.Control.ModifyFeature.RESHAPE = 1;
+/**
+ * Constant: RESIZE
+ * {Integer} Constant used to make the control work in resize mode
+ */
+OpenLayers.Control.ModifyFeature.RESIZE = 2;
+/**
+ * Constant: ROTATE
+ * {Integer} Constant used to make the control work in rotate mode
+ */
+OpenLayers.Control.ModifyFeature.ROTATE = 4;
+/**
+ * Constant: DRAG
+ * {Integer} Constant used to make the control work in drag mode
+ */
+OpenLayers.Control.ModifyFeature.DRAG = 8;
+/* ======================================================================
+ OpenLayers/Layer/Bing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Bing
+ * Bing layer using direct tile access as provided by Bing Maps REST Services.
+ * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more
+ * information. Note: Terms of Service compliant use requires the map to be
+ * configured with an <OpenLayers.Control.Attribution> control and the
+ * attribution placed on or near the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * Property: key
+ * {String} API key for Bing maps, get your own key
+ * at http://bingmapsportal.com/ .
+ */
+ key: null,
+
+ /**
+ * Property: serverResolutions
+ * {Array} the resolutions provided by the Bing servers.
+ */
+ serverResolutions: [
+ 156543.03390625, 78271.516953125, 39135.7584765625,
+ 19567.87923828125, 9783.939619140625, 4891.9698095703125,
+ 2445.9849047851562, 1222.9924523925781, 611.4962261962891,
+ 305.74811309814453, 152.87405654907226, 76.43702827453613,
+ 38.218514137268066, 19.109257068634033, 9.554628534317017,
+ 4.777314267158508, 2.388657133579254, 1.194328566789627,
+ 0.5971642833948135, 0.29858214169740677, 0.14929107084870338,
+ 0.07464553542435169
+ ],
+
+ /**
+ * Property: attributionTemplate
+ * {String}
+ */
+ attributionTemplate: '<span class="olBingAttribution ${type}">' +
+ '<div><a target="_blank" href="http://www.bing.com/maps/">' +
+ '<img src="${logo}" /></a></div>${copyrights}' +
+ '<a style="white-space: nowrap" target="_blank" '+
+ 'href="http://www.microsoft.com/maps/product/terms.html">' +
+ 'Terms of Use</a></span>',
+
+ /**
+ * Property: metadata
+ * {Object} Metadata for this layer, as returned by the callback script
+ */
+ metadata: null,
+
+ /**
+ * Property: protocolRegex
+ * {RegExp} Regular expression to match and replace http: in bing urls
+ */
+ protocolRegex: /^http:/i,
+
+ /**
+ * APIProperty: type
+ * {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used. Default is "Road".
+ */
+ type: "Road",
+
+ /**
+ * APIProperty: culture
+ * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx
+ * for the definition and the possible values. Default is "en-US".
+ */
+ culture: "en-US",
+
+ /**
+ * APIProperty: metadataParams
+ * {Object} Optional url parameters for the Get Imagery Metadata request
+ * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx
+ */
+ metadataParams: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ */
+ tileOptions: null,
+
+ /** APIProperty: protocol
+ * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo
+ * Can be 'http:' 'https:' or ''
+ *
+ * Warning: tiles may not be available under both HTTP and HTTPS protocols.
+ * Microsoft approved use of both HTTP and HTTPS urls for tiles. However
+ * this is undocumented and the Imagery Metadata API always returns HTTP
+ * urls.
+ *
+ * Default is '', unless when executed from a file:/// uri, in which case
+ * it is 'http:'.
+ */
+ protocol: ~window.location.href.indexOf('http') ? '' : 'http:',
+
+ /**
+ * Constructor: OpenLayers.Layer.Bing
+ * Create a new Bing layer.
+ *
+ * Example:
+ * (code)
+ * var road = new OpenLayers.Layer.Bing({
+ * name: "My Bing Aerial Layer",
+ * type: "Aerial",
+ * key: "my-api-key-here",
+ * });
+ * (end)
+ *
+ * Parameters:
+ * options - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * key - {String} Bing Maps API key for your application. Get one at
+ * http://bingmapsportal.com/.
+ * type - {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used.
+ *
+ * Any other documented layer properties can be provided in the config object.
+ */
+ initialize: function(options) {
+ options = OpenLayers.Util.applyDefaults({
+ sphericalMercator: true
+ }, options);
+ var name = options.name || "Bing " + (options.type || this.type);
+
+ var newArgs = [name, null, options];
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options.tileOptions);
+ this.loadMetadata();
+ },
+
+ /**
+ * Method: loadMetadata
+ */
+ loadMetadata: function() {
+ this._callbackId = "_callback_" + this.id.replace(/\./g, "_");
+ // link the processMetadata method to the global scope and bind it
+ // to this instance
+ window[this._callbackId] = OpenLayers.Function.bind(
+ OpenLayers.Layer.Bing.processMetadata, this
+ );
+ var params = OpenLayers.Util.applyDefaults({
+ key: this.key,
+ jsonp: this._callbackId,
+ include: "ImageryProviders"
+ }, this.metadataParams);
+ var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" +
+ this.type + "?" + OpenLayers.Util.getParameterString(params);
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.id = this._callbackId;
+ document.getElementsByTagName("head")[0].appendChild(script);
+ },
+
+ /**
+ * Method: initLayer
+ *
+ * Sets layer properties according to the metadata provided by the API
+ */
+ initLayer: function() {
+ var res = this.metadata.resourceSets[0].resources[0];
+ var url = res.imageUrl.replace("{quadkey}", "${quadkey}");
+ url = url.replace("{culture}", this.culture);
+ url = url.replace(this.protocolRegex, this.protocol);
+ this.url = [];
+ for (var i=0; i<res.imageUrlSubdomains.length; ++i) {
+ this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
+ }
+ this.addOptions({
+ maxResolution: Math.min(
+ this.serverResolutions[res.zoomMin],
+ this.maxResolution || Number.POSITIVE_INFINITY
+ ),
+ numZoomLevels: Math.min(
+ res.zoomMax + 1 - res.zoomMin, this.numZoomLevels
+ )
+ }, true);
+ if (!this.isBaseLayer) {
+ this.redraw();
+ }
+ this.updateAttribution();
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Paramters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getURL: function(bounds) {
+ if (!this.url) {
+ return;
+ }
+ var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z;
+ var quadDigits = [];
+ for (var i = z; i > 0; --i) {
+ var digit = '0';
+ var mask = 1 << (i - 1);
+ if ((x & mask) != 0) {
+ digit++;
+ }
+ if ((y & mask) != 0) {
+ digit++;
+ digit++;
+ }
+ quadDigits.push(digit);
+ }
+ var quadKey = quadDigits.join("");
+ var url = this.selectUrl('' + x + y + z, this.url);
+
+ return OpenLayers.String.format(url, {'quadkey': quadKey});
+ },
+
+ /**
+ * Method: updateAttribution
+ * Updates the attribution according to the requirements outlined in
+ * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html
+ */
+ updateAttribution: function() {
+ var metadata = this.metadata;
+ if (!metadata.resourceSets || !this.map || !this.map.center) {
+ return;
+ }
+ var res = metadata.resourceSets[0].resources[0];
+ var extent = this.map.getExtent().transform(
+ this.map.getProjectionObject(),
+ new OpenLayers.Projection("EPSG:4326")
+ );
+ var providers = res.imageryProviders || [],
+ zoom = OpenLayers.Util.indexOf(this.serverResolutions,
+ this.getServerResolution()),
+ copyrights = "", provider, i, ii, j, jj, bbox, coverage;
+ for (i=0,ii=providers.length; i<ii; ++i) {
+ provider = providers[i];
+ for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
+ coverage = provider.coverageAreas[j];
+ // axis order provided is Y,X
+ bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true);
+ if (extent.intersectsBounds(bbox) &&
+ zoom <= coverage.zoomMax && zoom >= coverage.zoomMin) {
+ copyrights += provider.attribution + " ";
+ }
+ }
+ }
+ var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol);
+ this.attribution = OpenLayers.String.format(this.attributionTemplate, {
+ type: this.type.toLowerCase(),
+ logo: logo,
+ copyrights: copyrights
+ });
+ this.map && this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "attribution"
+ });
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function() {
+ OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments);
+ this.map.events.register("moveend", this, this.updateAttribution);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Bing(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.map &&
+ this.map.events.unregister("moveend", this, this.updateAttribution);
+ OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Bing"
+});
+
+/**
+ * Function: OpenLayers.Layer.Bing.processMetadata
+ * This function will be bound to an instance, linked to the global scope with
+ * an id, and called by the JSONP script returned by the API.
+ *
+ * Parameters:
+ * metadata - {Object} metadata as returned by the API
+ */
+OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ this.metadata = metadata;
+ this.initLayer();
+ var script = document.getElementById(this._callbackId);
+ script.parentNode.removeChild(script);
+ window[this._callbackId] = undefined; // cannot delete from window in IE
+ delete this._callbackId;
+};
+/* ======================================================================
+ OpenLayers/StyleMap.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.StyleMap
+ */
+OpenLayers.StyleMap = OpenLayers.Class({
+
+ /**
+ * Property: styles
+ * {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
+ * rendering intents (e.g. "default", "temporary", "select", "delete").
+ */
+ styles: null,
+
+ /**
+ * Property: extendDefault
+ * {Boolean} if true, every render intent will extend the symbolizers
+ * specified for the "default" intent at rendering time. Otherwise, every
+ * rendering intent will be treated as a completely independent style.
+ */
+ extendDefault: true,
+
+ /**
+ * Constructor: OpenLayers.StyleMap
+ *
+ * Parameters:
+ * style - {Object} Optional. Either a style hash, or a style object, or
+ * a hash of style objects (style hashes) keyed by rendering
+ * intent. If just one style hash or style object is passed,
+ * this will be used for all known render intents (default,
+ * select, temporary)
+ * options - {Object} optional hash of additional options for this
+ * instance
+ */
+ initialize: function (style, options) {
+ this.styles = {
+ "default": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["default"]),
+ "select": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["select"]),
+ "temporary": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["temporary"]),
+ "delete": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["delete"])
+ };
+
+ // take whatever the user passed as style parameter and convert it
+ // into parts of stylemap.
+ if(style instanceof OpenLayers.Style) {
+ // user passed a style object
+ this.styles["default"] = style;
+ this.styles["select"] = style;
+ this.styles["temporary"] = style;
+ this.styles["delete"] = style;
+ } else if(typeof style == "object") {
+ for(var key in style) {
+ if(style[key] instanceof OpenLayers.Style) {
+ // user passed a hash of style objects
+ this.styles[key] = style[key];
+ } else if(typeof style[key] == "object") {
+ // user passsed a hash of style hashes
+ this.styles[key] = new OpenLayers.Style(style[key]);
+ } else {
+ // user passed a style hash (i.e. symbolizer)
+ this.styles["default"] = new OpenLayers.Style(style);
+ this.styles["select"] = new OpenLayers.Style(style);
+ this.styles["temporary"] = new OpenLayers.Style(style);
+ this.styles["delete"] = new OpenLayers.Style(style);
+ break;
+ }
+ }
+ }
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for(var key in this.styles) {
+ this.styles[key].destroy();
+ }
+ this.styles = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * Creates the symbolizer for a feature for a render intent.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
+ * of the intended style against.
+ * intent - {String} The intent determines the symbolizer that will be
+ * used to draw the feature. Well known intents are "default"
+ * (for just drawing the features), "select" (for selected
+ * features) and "temporary" (for drawing features).
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature, intent) {
+ if(!feature) {
+ feature = new OpenLayers.Feature.Vector();
+ }
+ if(!this.styles[intent]) {
+ intent = "default";
+ }
+ feature.renderIntent = intent;
+ var defaultSymbolizer = {};
+ if(this.extendDefault && intent != "default") {
+ defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
+ }
+ return OpenLayers.Util.extend(defaultSymbolizer,
+ this.styles[intent].createSymbolizer(feature));
+ },
+
+ /**
+ * Method: addUniqueValueRules
+ * Convenience method to create comparison rules for unique values of a
+ * property. The rules will be added to the style object for a specified
+ * rendering intent. This method is a shortcut for creating something like
+ * the "unique value legends" familiar from well known desktop GIS systems
+ *
+ * Parameters:
+ * renderIntent - {String} rendering intent to add the rules to
+ * property - {String} values of feature attributes to create the
+ * rules for
+ * symbolizers - {Object} Hash of symbolizers, keyed by the desired
+ * property values
+ * context - {Object} An optional object with properties that
+ * symbolizers' property values should be evaluated
+ * against. If no context is specified, feature.attributes
+ * will be used
+ */
+ addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
+ var rules = [];
+ for (var value in symbolizers) {
+ rules.push(new OpenLayers.Rule({
+ symbolizer: symbolizers[value],
+ context: context,
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: property,
+ value: value
+ })
+ }));
+ }
+ this.styles[renderIntent].addRules(rules);
+ },
+
+ CLASS_NAME: "OpenLayers.StyleMap"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Renderer.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector
+ * Instances of OpenLayers.Layer.Vector are used to render vector data from
+ * a variety of sources. Create a new vector layer with the
+ * <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types (in addition to those from <OpenLayers.Layer.events>):
+ * beforefeatureadded - Triggered before a feature is added. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be added. To stop the feature from being added, a
+ * listener should return false.
+ * beforefeaturesadded - Triggered before an array of features is added.
+ * Listeners will receive an object with a *features* property
+ * referencing the feature to be added. To stop the features from
+ * being added, a listener should return false.
+ * featureadded - Triggered after a feature is added. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the added feature.
+ * featuresadded - Triggered after features are added. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of added features.
+ * beforefeatureremoved - Triggered before a feature is removed. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be removed.
+ * beforefeaturesremoved - Triggered before multiple features are removed.
+ * Listeners will receive an object with a *features* property
+ * referencing the features to be removed.
+ * featureremoved - Triggerd after a feature is removed. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the removed feature.
+ * featuresremoved - Triggered after features are removed. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of removed features.
+ * beforefeatureselected - Triggered before a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be selected. To stop the feature from being selectd, a
+ * listener should return false.
+ * featureselected - Triggered after a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * selected feature.
+ * featureunselected - Triggered after a feature is unselected.
+ * Listeners will receive an object with a *feature* property
+ * referencing the unselected feature.
+ * beforefeaturemodified - Triggered when a feature is selected to
+ * be modified. Listeners will receive an object with a *feature*
+ * property referencing the selected feature.
+ * featuremodified - Triggered when a feature has been modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * afterfeaturemodified - Triggered when a feature is finished being modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * vertexmodified - Triggered when a vertex within any feature geometry
+ * has been modified. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * modification.
+ * vertexremoved - Triggered when a vertex within any feature geometry
+ * has been deleted. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * removal.
+ * sketchstarted - Triggered when a feature sketch bound for this layer
+ * is started. Listeners will receive an object with a *feature*
+ * property referencing the new sketch feature and a *vertex* property
+ * referencing the creation point.
+ * sketchmodified - Triggered when a feature sketch bound for this layer
+ * is modified. Listeners will receive an object with a *vertex*
+ * property referencing the modified vertex and a *feature* property
+ * referencing the sketch feature.
+ * sketchcomplete - Triggered when a feature sketch bound for this layer
+ * is complete. Listeners will receive an object with a *feature*
+ * property referencing the sketch feature. By returning false, a
+ * listener can stop the sketch feature from being added to the layer.
+ * refresh - Triggered when something wants a strategy to ask the protocol
+ * for a new set of features.
+ */
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is false. Set this property
+ * in the layer options.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} Whether the layer remains in one place while dragging the
+ * map. Note that setting this to true will move the layer to the bottom
+ * of the layer stack.
+ */
+ isFixed: false,
+
+ /**
+ * APIProperty: features
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ features: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} The filter set in this layer,
+ * a strategy launching read requests can combined
+ * this filter with its own filter.
+ */
+ filter: null,
+
+ /**
+ * Property: selectedFeatures
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ selectedFeatures: null,
+
+ /**
+ * Property: unrenderedFeatures
+ * {Object} hash of features, keyed by feature.id, that the renderer
+ * failed to draw
+ */
+ unrenderedFeatures: null,
+
+ /**
+ * APIProperty: reportError
+ * {Boolean} report friendly error message when loading of renderer
+ * fails.
+ */
+ reportError: true,
+
+ /**
+ * APIProperty: style
+ * {Object} Default style for the layer
+ */
+ style: null,
+
+ /**
+ * Property: styleMap
+ * {<OpenLayers.StyleMap>}
+ */
+ styleMap: null,
+
+ /**
+ * Property: strategies
+ * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+ */
+ strategies: null,
+
+ /**
+ * Property: protocol
+ * {<OpenLayers.Protocol>} Optional protocol for the layer.
+ */
+ protocol: null,
+
+ /**
+ * Property: renderers
+ * {Array(String)} List of supported Renderer classes. Add to this list to
+ * add support for additional renderers. This list is ordered:
+ * the first renderer which returns true for the 'supported()'
+ * method will be used, if not defined in the 'renderer' option.
+ */
+ renderers: ['SVG', 'VML', 'Canvas'],
+
+ /**
+ * Property: renderer
+ * {<OpenLayers.Renderer>}
+ */
+ renderer: null,
+
+ /**
+ * APIProperty: rendererOptions
+ * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
+ * supported options.
+ */
+ rendererOptions: null,
+
+ /**
+ * APIProperty: geometryType
+ * {String} geometryType allows you to limit the types of geometries this
+ * layer supports. This should be set to something like
+ * "OpenLayers.Geometry.Point" to limit types.
+ */
+ geometryType: null,
+
+ /**
+ * Property: drawn
+ * {Boolean} Whether the Vector Layer features have been drawn yet.
+ */
+ drawn: false,
+
+ /**
+ * APIProperty: ratio
+ * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map.
+ */
+ ratio: 1,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector
+ * Create a new vector layer
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} A new vector layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+
+ // allow user-set renderer, otherwise assign one
+ if (!this.renderer || !this.renderer.supported()) {
+ this.assignRenderer();
+ }
+
+ // if no valid renderer found, display error
+ if (!this.renderer || !this.renderer.supported()) {
+ this.renderer = null;
+ this.displayError();
+ }
+
+ if (!this.styleMap) {
+ this.styleMap = new OpenLayers.StyleMap();
+ }
+
+ this.features = [];
+ this.selectedFeatures = [];
+ this.unrenderedFeatures = {};
+
+ // Allow for custom layer behavior
+ if(this.strategies){
+ for(var i=0, len=this.strategies.length; i<len; i++) {
+ this.strategies[i].setLayer(this);
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoDestroy) {
+ strategy.destroy();
+ }
+ }
+ this.strategies = null;
+ }
+ if (this.protocol) {
+ if(this.protocol.autoDestroy) {
+ this.protocol.destroy();
+ }
+ this.protocol = null;
+ }
+ this.destroyFeatures();
+ this.features = null;
+ this.selectedFeatures = null;
+ this.unrenderedFeatures = null;
+ if (this.renderer) {
+ this.renderer.destroy();
+ }
+ this.renderer = null;
+ this.geometryType = null;
+ this.drawn = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer.
+ *
+ * Note: Features of the layer are also cloned.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Vector(this.name, this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ var features = this.features;
+ var len = features.length;
+ var clonedFeatures = new Array(len);
+ for(var i=0; i<len; ++i) {
+ clonedFeatures[i] = features[i].clone();
+ }
+ obj.features = clonedFeatures;
+
+ return obj;
+ },
+
+ /**
+ * Method: refresh
+ * Ask the layer to request features again and redraw them. Triggers
+ * the refresh event if the layer is in range and visible.
+ *
+ * Parameters:
+ * obj - {Object} Optional object with properties for any listener of
+ * the refresh event.
+ */
+ refresh: function(obj) {
+ if(this.calculateInRange() && this.visibility) {
+ this.events.triggerEvent("refresh", obj);
+ }
+ },
+
+ /**
+ * Method: assignRenderer
+ * Iterates through the available renderer implementations and selects
+ * and assigns the first one whose "supported()" function returns true.
+ */
+ assignRenderer: function() {
+ for (var i=0, len=this.renderers.length; i<len; i++) {
+ var rendererClass = this.renderers[i];
+ var renderer = (typeof rendererClass == "function") ?
+ rendererClass :
+ OpenLayers.Renderer[rendererClass];
+ if (renderer && renderer.prototype.supported()) {
+ this.renderer = new renderer(this.div, this.rendererOptions);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: displayError
+ * Let the user know their browser isn't supported.
+ */
+ displayError: function() {
+ if (this.reportError) {
+ OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
+ {renderers: this. renderers.join('\n')}));
+ }
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * If there is no renderer set, the layer can't be used. Remove it.
+ * Otherwise, give the renderer a reference to the map and set its size.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ if (!this.renderer) {
+ this.map.removeLayer(this);
+ } else {
+ this.renderer.map = this.map;
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. Any autoActivate strategies will be
+ * activated here.
+ */
+ afterAdd: function() {
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.activate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ this.drawn = false;
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.deactivate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * Notify the renderer of the change in size.
+ *
+ */
+ onMapResize: function() {
+ OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ },
+
+ /**
+ * Method: moveTo
+ * Reset the vector layer's div so that it once again is lined up with
+ * the map. Notify the renderer of the change of extent, and in the
+ * case of a change of zoom level (resolution), have the
+ * renderer redraw features.
+ *
+ * If the layer has not yet been drawn, cycle through the layer's
+ * features and draw each one.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ var coordSysUnchanged = true;
+ if (!dragging) {
+ this.renderer.root.style.visibility = 'hidden';
+
+ var viewSize = this.map.getSize(),
+ viewWidth = viewSize.w,
+ viewHeight = viewSize.h,
+ offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2,
+ offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2;
+ offsetLeft += this.map.layerContainerOriginPx.x;
+ offsetLeft = -Math.round(offsetLeft);
+ offsetTop += this.map.layerContainerOriginPx.y;
+ offsetTop = -Math.round(offsetTop);
+
+ this.div.style.left = offsetLeft + 'px';
+ this.div.style.top = offsetTop + 'px';
+
+ var extent = this.map.getExtent().scale(this.ratio);
+ coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
+
+ this.renderer.root.style.visibility = 'visible';
+
+ // Force a reflow on gecko based browsers to prevent jump/flicker.
+ // This seems to happen on only certain configurations; it was originally
+ // noticed in FF 2.0 and Linux.
+ if (OpenLayers.IS_GECKO === true) {
+ this.div.scrollLeft = this.div.scrollLeft;
+ }
+
+ if (!zoomChanged && coordSysUnchanged) {
+ for (var i in this.unrenderedFeatures) {
+ var feature = this.unrenderedFeatures[i];
+ this.drawFeature(feature);
+ }
+ }
+ }
+ if (!this.drawn || zoomChanged || !coordSysUnchanged) {
+ this.drawn = true;
+ var feature;
+ for(var i=0, len=this.features.length; i<len; i++) {
+ this.renderer.locked = (i !== (len - 1));
+ feature = this.features[i];
+ this.drawFeature(feature);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ // we need to set the display style of the root in case it is attached
+ // to a foreign layer
+ var currentDisplay = this.div.style.display;
+ if(currentDisplay != this.renderer.root.style.display) {
+ this.renderer.root.style.display = currentDisplay;
+ }
+ },
+
+ /**
+ * APIMethod: addFeatures
+ * Add Features to the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ */
+ addFeatures: function(features, options) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var notify = !options || !options.silent;
+ if(notify) {
+ var event = {features: features};
+ var ret = this.events.triggerEvent("beforefeaturesadded", event);
+ if(ret === false) {
+ return;
+ }
+ features = event.features;
+ }
+
+ // Track successfully added features for featuresadded event, since
+ // beforefeatureadded can veto single features.
+ var featuresAdded = [];
+ for (var i=0, len=features.length; i<len; i++) {
+ if (i != (features.length - 1)) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+ var feature = features[i];
+
+ if (this.geometryType &&
+ !(feature.geometry instanceof this.geometryType)) {
+ throw new TypeError('addFeatures: component should be an ' +
+ this.geometryType.prototype.CLASS_NAME);
+ }
+
+ //give feature reference to its layer
+ feature.layer = this;
+
+ if (!feature.style && this.style) {
+ feature.style = OpenLayers.Util.extend({}, this.style);
+ }
+
+ if (notify) {
+ if(this.events.triggerEvent("beforefeatureadded",
+ {feature: feature}) === false) {
+ continue;
+ }
+ this.preFeatureInsert(feature);
+ }
+
+ featuresAdded.push(feature);
+ this.features.push(feature);
+ this.drawFeature(feature);
+
+ if (notify) {
+ this.events.triggerEvent("featureadded", {
+ feature: feature
+ });
+ this.onFeatureInsert(feature);
+ }
+ }
+
+ if(notify) {
+ this.events.triggerEvent("featuresadded", {features: featuresAdded});
+ }
+ },
+
+
+ /**
+ * APIMethod: removeFeatures
+ * Remove features from the layer. This erases any drawn features and
+ * removes them from the layer's control. The beforefeatureremoved
+ * and featureremoved events will be triggered for each feature. The
+ * featuresremoved event will be triggered after all features have
+ * been removed. To supress event triggering, use the silent option.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
+ * removed.
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeFeatures: function(features, options) {
+ if(!features || features.length === 0) {
+ return;
+ }
+ if (features === this.features) {
+ return this.removeAllFeatures(options);
+ }
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ if (features === this.selectedFeatures) {
+ features = features.slice();
+ }
+
+ var notify = !options || !options.silent;
+
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+
+ for (var i = features.length - 1; i >= 0; i--) {
+ // We remain locked so long as we're not at 0
+ // and the 'next' feature has a geometry. We do the geometry check
+ // because if all the features after the current one are 'null', we
+ // won't call eraseGeometry, so we break the 'renderer functions
+ // will always be called with locked=false *last*' rule. The end result
+ // is a possible gratiutious unlocking to save a loop through the rest
+ // of the list checking the remaining features every time. So long as
+ // null geoms are rare, this is probably okay.
+ if (i != 0 && features[i-1].geometry) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+
+ var feature = features[i];
+ delete this.unrenderedFeatures[feature.id];
+
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+
+ this.features = OpenLayers.Util.removeItem(this.features, feature);
+ // feature has no layer at this point
+ feature.layer = null;
+
+ if (feature.geometry) {
+ this.renderer.eraseFeatures(feature);
+ }
+
+ //in the case that this feature is one of the selected features,
+ // remove it from that array as well.
+ if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
+ OpenLayers.Util.removeItem(this.selectedFeatures, feature);
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: removeAllFeatures
+ * Remove all features from the layer.
+ *
+ * Parameters:
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeAllFeatures: function(options) {
+ var notify = !options || !options.silent;
+ var features = this.features;
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+ var feature;
+ for (var i = features.length-1; i >= 0; i--) {
+ feature = features[i];
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+ feature.layer = null;
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+ this.renderer.clear();
+ this.features = [];
+ this.unrenderedFeatures = {};
+ this.selectedFeatures = [];
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: destroyFeatures
+ * Erase and destroy features on the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
+ * features to destroy. If not supplied, all features on the layer
+ * will be destroyed.
+ * options - {Object}
+ */
+ destroyFeatures: function(features, options) {
+ var all = (features == undefined); // evaluates to true if
+ // features is null
+ if(all) {
+ features = this.features;
+ }
+ if(features) {
+ this.removeFeatures(features, options);
+ for(var i=features.length-1; i>=0; i--) {
+ features[i].destroy();
+ }
+ }
+ },
+
+ /**
+ * APIMethod: drawFeature
+ * Draw (or redraw) a feature on the layer. If the optional style argument
+ * is included, this style will be used. If no style is included, the
+ * feature's style will be used. If the feature doesn't have a style,
+ * the layer's style will be used.
+ *
+ * This function is not designed to be used when adding features to
+ * the layer (use addFeatures instead). It is meant to be used when
+ * the style of a feature has changed, or in some other way needs to
+ * visually updated *after* it has already been added to a layer. You
+ * must add the feature to the layer for most layer-related events to
+ * happen.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {String | Object} Named render intent or full symbolizer object.
+ */
+ drawFeature: function(feature, style) {
+ // don't try to draw the feature with the renderer if the layer is not
+ // drawn itself
+ if (!this.drawn) {
+ return;
+ }
+ if (typeof style != "object") {
+ if(!style && feature.state === OpenLayers.State.DELETE) {
+ style = "delete";
+ }
+ var renderIntent = style || feature.renderIntent;
+ style = feature.style || this.style;
+ if (!style) {
+ style = this.styleMap.createSymbolizer(feature, renderIntent);
+ }
+ }
+
+ var drawn = this.renderer.drawFeature(feature, style);
+ //TODO remove the check for null when we get rid of Renderer.SVG
+ if (drawn === false || drawn === null) {
+ this.unrenderedFeatures[feature.id] = feature;
+ } else {
+ delete this.unrenderedFeatures[feature.id];
+ }
+ },
+
+ /**
+ * Method: eraseFeatures
+ * Erase features from the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ this.renderer.eraseFeatures(features);
+ },
+
+ /**
+ * Method: getFeatureFromEvent
+ * Given an event, return a feature if the event occurred over one.
+ * Otherwise, return null.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
+ */
+ getFeatureFromEvent: function(evt) {
+ if (!this.renderer) {
+ throw new Error('getFeatureFromEvent called on layer with no ' +
+ 'renderer. This usually means you destroyed a ' +
+ 'layer, but not some handler which is associated ' +
+ 'with it.');
+ }
+ var feature = null;
+ var featureId = this.renderer.getFeatureIdFromEvent(evt);
+ if (featureId) {
+ if (typeof featureId === "string") {
+ feature = this.getFeatureById(featureId);
+ } else {
+ feature = featureId;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureBy
+ * Given a property value, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * property - {String}
+ * value - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * property value or null if there is no such feature.
+ */
+ getFeatureBy: function(property, value) {
+ //TBD - would it be more efficient to use a hash for this.features?
+ var feature = null;
+ for(var i=0, len=this.features.length; i<len; ++i) {
+ if(this.features[i][property] == value) {
+ feature = this.features[i];
+ break;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureById
+ * Given a feature id, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureId - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureId or null if there is no such feature.
+ */
+ getFeatureById: function(featureId) {
+ return this.getFeatureBy('id', featureId);
+ },
+
+ /**
+ * APIMethod: getFeatureByFid
+ * Given a feature fid, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureFid - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureFid or null if there is no such feature.
+ */
+ getFeatureByFid: function(featureFid) {
+ return this.getFeatureBy('fid', featureFid);
+ },
+
+ /**
+ * APIMethod: getFeaturesByAttribute
+ * Returns an array of features that have the given attribute key set to the
+ * given value. Comparison of attribute values takes care of datatypes, e.g.
+ * the string '1234' is not equal to the number 1234.
+ *
+ * Parameters:
+ * attrName - {String}
+ * attrValue - {Mixed}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>}) An array of features that have the
+ * passed named attribute set to the given value.
+ */
+ getFeaturesByAttribute: function(attrName, attrValue) {
+ var i,
+ feature,
+ len = this.features.length,
+ foundFeatures = [];
+ for(i = 0; i < len; i++) {
+ feature = this.features[i];
+ if(feature && feature.attributes) {
+ if (feature.attributes[attrName] === attrValue) {
+ foundFeatures.push(feature);
+ }
+ }
+ }
+ return foundFeatures;
+ },
+
+ /**
+ * Unselect the selected features
+ * i.e. clears the featureSelection array
+ * change the style back
+ clearSelection: function() {
+
+ var vectorLayer = this.map.vectorLayer;
+ for (var i = 0; i < this.map.featureSelection.length; i++) {
+ var featureSelection = this.map.featureSelection[i];
+ vectorLayer.drawFeature(featureSelection, vectorLayer.style);
+ }
+ this.map.featureSelection = [];
+ },
+ */
+
+
+ /**
+ * APIMethod: onFeatureInsert
+ * method called after a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something on feature updates.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ onFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: preFeatureInsert
+ * method called before a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something when features are first added to the
+ * layer, but before they are drawn, such as adjust the style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ preFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the features.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} or null if the layer has no features with
+ * geometries.
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+ var features = this.features;
+ if(features && (features.length > 0)) {
+ var geometry = null;
+ for(var i=0, len=features.length; i<len; i++) {
+ geometry = features[i].geometry;
+ if (geometry) {
+ if (maxExtent === null) {
+ maxExtent = new OpenLayers.Bounds();
+ }
+ maxExtent.extend(geometry.getBounds());
+ }
+ }
+ }
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector"
+});
+/* ======================================================================
+ OpenLayers/Layer/PointGrid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.PointGrid
+ * A point grid layer dynamically generates a regularly spaced grid of point
+ * features. This is a specialty layer for cases where an application needs
+ * a regular grid of points. It can be used, for example, in an editing
+ * environment to snap to a grid.
+ *
+ * Create a new vector layer with the <OpenLayers.Layer.PointGrid> constructor.
+ * (code)
+ * // create a grid with points spaced at 10 map units
+ * var points = new OpenLayers.Layer.PointGrid({dx: 10, dy: 10});
+ *
+ * // create a grid with different x/y spacing rotated 15 degrees clockwise.
+ * var points = new OpenLayers.Layer.PointGrid({dx: 5, dy: 10, rotation: 15});
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.PointGrid = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * APIProperty: dx
+ * {Number} Point grid spacing in the x-axis direction (map units).
+ * Read-only. Use the <setSpacing> method to modify this value.
+ */
+ dx: null,
+
+ /**
+ * APIProperty: dy
+ * {Number} Point grid spacing in the y-axis direction (map units).
+ * Read-only. Use the <setSpacing> method to modify this value.
+ */
+ dy: null,
+
+ /**
+ * APIProperty: ratio
+ * {Number} Ratio of the desired grid size to the map viewport size.
+ * Default is 1.5. Larger ratios mean the grid is recalculated less often
+ * while panning. The <maxFeatures> setting has precedence when determining
+ * grid size. Read-only. Use the <setRatio> method to modify this value.
+ */
+ ratio: 1.5,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Number} The maximum number of points to generate in the grid. Default
+ * is 250. Read-only. Use the <setMaxFeatures> method to modify this value.
+ */
+ maxFeatures: 250,
+
+ /**
+ * APIProperty: rotation
+ * {Number} Grid rotation (in degrees clockwise from the positive x-axis).
+ * Default is 0. Read-only. Use the <setRotation> method to modify this
+ * value.
+ */
+ rotation: 0,
+
+ /**
+ * APIProperty: origin
+ * {<OpenLayers.LonLat>} Grid origin. The grid lattice will be aligned with
+ * the origin. If not set at construction, the center of the map's maximum
+ * extent is used. Read-only. Use the <setOrigin> method to modify this
+ * value.
+ */
+ origin: null,
+
+ /**
+ * Property: gridBounds
+ * {<OpenLayers.Bounds>} Internally cached grid bounds (with optional
+ * rotation applied).
+ */
+ gridBounds: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.PointGrid
+ * Creates a new point grid layer.
+ *
+ * Parameters:
+ * config - {Object} An object containing all configuration properties for
+ * the layer. The <dx> and <dy> properties are required to be set at
+ * construction. Any other layer properties may be set in this object.
+ */
+ initialize: function(config) {
+ config = config || {};
+ OpenLayers.Layer.Vector.prototype.initialize.apply(this, [config.name, config]);
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ map.events.register("moveend", this, this.onMoveEnd);
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("moveend", this, this.onMoveEnd);
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setRatio
+ * Set the grid <ratio> property and update the grid. Can only be called
+ * after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * ratio - {Number}
+ */
+ setRatio: function(ratio) {
+ this.ratio = ratio;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: setMaxFeatures
+ * Set the grid <maxFeatures> property and update the grid. Can only be
+ * called after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * maxFeatures - {Number}
+ */
+ setMaxFeatures: function(maxFeatures) {
+ this.maxFeatures = maxFeatures;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: setSpacing
+ * Set the grid <dx> and <dy> properties and update the grid. If only one
+ * argument is provided, it will be set as <dx> and <dy>. Can only be
+ * called after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ setSpacing: function(dx, dy) {
+ this.dx = dx;
+ this.dy = dy || dx;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: setOrigin
+ * Set the grid <origin> property and update the grid. Can only be called
+ * after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.LonLat>}
+ */
+ setOrigin: function(origin) {
+ this.origin = origin;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: getOrigin
+ * Get the grid <origin> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The grid origin.
+ */
+ getOrigin: function() {
+ if (!this.origin) {
+ this.origin = this.map.getExtent().getCenterLonLat();
+ }
+ return this.origin;
+ },
+
+ /**
+ * APIMethod: setRotation
+ * Set the grid <rotation> property and update the grid. Rotation values
+ * are in degrees clockwise from the positive x-axis (negative values
+ * for counter-clockwise rotation). Can only be called after the layer
+ * has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * rotation - {Number} Degrees clockwise from the positive x-axis.
+ */
+ setRotation: function(rotation) {
+ this.rotation = rotation;
+ this.updateGrid(true);
+ },
+
+ /**
+ * Method: onMoveEnd
+ * Listener for map "moveend" events.
+ */
+ onMoveEnd: function() {
+ this.updateGrid();
+ },
+
+ /**
+ * Method: getViewBounds
+ * Gets the (potentially rotated) view bounds for grid calculations.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getViewBounds: function() {
+ var bounds = this.map.getExtent();
+ if (this.rotation) {
+ var origin = this.getOrigin();
+ var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat);
+ var rect = bounds.toGeometry();
+ rect.rotate(-this.rotation, rotationOrigin);
+ bounds = rect.getBounds();
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: updateGrid
+ * Update the grid.
+ *
+ * Parameters:
+ * force - {Boolean} Update the grid even if the previous bounds are still
+ * valid.
+ */
+ updateGrid: function(force) {
+ if (force || this.invalidBounds()) {
+ var viewBounds = this.getViewBounds();
+ var origin = this.getOrigin();
+ var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat);
+ var viewBoundsWidth = viewBounds.getWidth();
+ var viewBoundsHeight = viewBounds.getHeight();
+ var aspectRatio = viewBoundsWidth / viewBoundsHeight;
+ var maxHeight = Math.sqrt(this.dx * this.dy * this.maxFeatures / aspectRatio);
+ var maxWidth = maxHeight * aspectRatio;
+ var gridWidth = Math.min(viewBoundsWidth * this.ratio, maxWidth);
+ var gridHeight = Math.min(viewBoundsHeight * this.ratio, maxHeight);
+ var center = viewBounds.getCenterLonLat();
+ this.gridBounds = new OpenLayers.Bounds(
+ center.lon - (gridWidth / 2),
+ center.lat - (gridHeight / 2),
+ center.lon + (gridWidth / 2),
+ center.lat + (gridHeight / 2)
+ );
+ var rows = Math.floor(gridHeight / this.dy);
+ var cols = Math.floor(gridWidth / this.dx);
+ var gridLeft = origin.lon + (this.dx * Math.ceil((this.gridBounds.left - origin.lon) / this.dx));
+ var gridBottom = origin.lat + (this.dy * Math.ceil((this.gridBounds.bottom - origin.lat) / this.dy));
+ var features = new Array(rows * cols);
+ var x, y, point;
+ for (var i=0; i<cols; ++i) {
+ x = gridLeft + (i * this.dx);
+ for (var j=0; j<rows; ++j) {
+ y = gridBottom + (j * this.dy);
+ point = new OpenLayers.Geometry.Point(x, y);
+ if (this.rotation) {
+ point.rotate(this.rotation, rotationOrigin);
+ }
+ features[(i*rows)+j] = new OpenLayers.Feature.Vector(point);
+ }
+ }
+ this.destroyFeatures(this.features, {silent: true});
+ this.addFeatures(features, {silent: true});
+ }
+ },
+
+ /**
+ * Method: invalidBounds
+ * Determine whether the previously generated point grid is invalid.
+ * This occurs when the map bounds extends beyond the previously
+ * generated grid bounds.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ invalidBounds: function() {
+ return !this.gridBounds || !this.gridBounds.containsBounds(this.getViewBounds());
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.PointGrid"
+
+});
+/* ======================================================================
+ OpenLayers/Handler/MouseWheel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.MouseWheel
+ * Handler for wheel up/down events.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * Property: wheelListener
+ * {function}
+ */
+ wheelListener: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase server performance, an interval (in
+ * milliseconds) can be set to reduce the number of up/down events
+ * called. If set, a new up/down event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: maxDelta
+ * {Integer} Maximum delta to collect before breaking from the current
+ * interval. In cumulative mode, this also limits the maximum delta
+ * returned from the handler. Default is Number.POSITIVE_INFINITY.
+ */
+ maxDelta: Number.POSITIVE_INFINITY,
+
+ /**
+ * Property: delta
+ * {Integer} When interval is set, delta collects the mousewheel z-deltas
+ * of the events that occur within the interval.
+ * See also the cumulative option
+ */
+ delta: 0,
+
+ /**
+ * Property: cumulative
+ * {Boolean} When interval is set: true to collect all the mousewheel
+ * z-deltas, false to only record the delta direction (positive or
+ * negative)
+ */
+ cumulative: true,
+
+ /**
+ * Constructor: OpenLayers.Handler.MouseWheel
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished.
+ * The callback should expect to recieve a single
+ * argument, the point geometry.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ this.wheelListener = OpenLayers.Function.bindAsEventListener(
+ this.onWheelEvent, this
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ this.wheelListener = null;
+ },
+
+ /**
+ * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
+ */
+
+ /**
+ * Method: onWheelEvent
+ * Catch the wheel event and handle it xbrowserly
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ onWheelEvent: function(e){
+
+ // make sure we have a map and check keyboard modifiers
+ if (!this.map || !this.checkModifiers(e)) {
+ return;
+ }
+
+ // Ride up the element's DOM hierarchy to determine if it or any of
+ // its ancestors was:
+ // * specifically marked as scrollable (CSS overflow property)
+ // * one of our layer divs or a div marked as scrollable
+ // ('olScrollable' CSS class)
+ // * the map div
+ //
+ var overScrollableDiv = false;
+ var allowScroll = false;
+ var overMapDiv = false;
+
+ var elem = OpenLayers.Event.element(e);
+ while((elem != null) && !overMapDiv && !overScrollableDiv) {
+
+ if (!overScrollableDiv) {
+ try {
+ var overflow;
+ if (elem.currentStyle) {
+ overflow = elem.currentStyle["overflow"];
+ } else {
+ var style =
+ document.defaultView.getComputedStyle(elem, null);
+ overflow = style.getPropertyValue("overflow");
+ }
+ overScrollableDiv = ( overflow &&
+ (overflow == "auto") || (overflow == "scroll") );
+ } catch(err) {
+ //sometimes when scrolling in a popup, this causes
+ // obscure browser error
+ }
+ }
+
+ if (!allowScroll) {
+ allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable');
+ if (!allowScroll) {
+ for (var i = 0, len = this.map.layers.length; i < len; i++) {
+ // Are we in the layer div? Note that we have two cases
+ // here: one is to catch EventPane layers, which have a
+ // pane above the layer (layer.pane)
+ var layer = this.map.layers[i];
+ if (elem == layer.div || elem == layer.pane) {
+ allowScroll = true;
+ break;
+ }
+ }
+ }
+ }
+ overMapDiv = (elem == this.map.div);
+
+ elem = elem.parentNode;
+ }
+
+ // Logic below is the following:
+ //
+ // If we are over a scrollable div or not over the map div:
+ // * do nothing (let the browser handle scrolling)
+ //
+ // otherwise
+ //
+ // If we are over the layer div or a 'olScrollable' div:
+ // * zoom/in out
+ // then
+ // * kill event (so as not to also scroll the page after zooming)
+ //
+ // otherwise
+ //
+ // Kill the event (dont scroll the page if we wheel over the
+ // layerswitcher or the pan/zoom control)
+ //
+ if (!overScrollableDiv && overMapDiv) {
+ if (allowScroll) {
+ var delta = 0;
+
+ if (e.wheelDelta) {
+ delta = e.wheelDelta;
+ if (delta % 160 === 0) {
+ // opera have steps of 160 instead of 120
+ delta = delta * 0.75;
+ }
+ delta = delta / 120;
+ } else if (e.detail) {
+ // detail in Firefox on OS X is 1/3 of Windows
+ // so force delta 1 / -1
+ delta = - (e.detail / Math.abs(e.detail));
+ }
+ this.delta += delta;
+
+ window.clearTimeout(this._timeoutId);
+ if(this.interval && Math.abs(this.delta) < this.maxDelta) {
+ // store e because window.event might change during delay
+ var evt = OpenLayers.Util.extend({}, e);
+ this._timeoutId = window.setTimeout(
+ OpenLayers.Function.bind(function(){
+ this.wheelZoom(evt);
+ }, this),
+ this.interval
+ );
+ } else {
+ this.wheelZoom(e);
+ }
+ }
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: wheelZoom
+ * Given the wheel event, we carry out the appropriate zooming in or out,
+ * based on the 'wheelDelta' or 'detail' property of the event.
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ wheelZoom: function(e) {
+ var delta = this.delta;
+ this.delta = 0;
+
+ if (delta) {
+ e.xy = this.map.events.getMousePosition(e);
+ if (delta < 0) {
+ this.callback("down",
+ [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]);
+ } else {
+ this.callback("up",
+ [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function (evt) {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ //register mousewheel events specifically on the window and document
+ var wheelListener = this.wheelListener;
+ OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener);
+ OpenLayers.Event.observe(window, "mousewheel", wheelListener);
+ OpenLayers.Event.observe(document, "mousewheel", wheelListener);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function (evt) {
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ // unregister mousewheel events specifically on the window and document
+ var wheelListener = this.wheelListener;
+ OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener);
+ OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener);
+ OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.MouseWheel"
+});
+/* ======================================================================
+ OpenLayers/Symbolizer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer
+ * Base class representing a symbolizer used for feature rendering.
+ */
+OpenLayers.Symbolizer = OpenLayers.Class({
+
+
+ /**
+ * APIProperty: zIndex
+ * {Number} The zIndex determines the rendering order for a symbolizer.
+ * Symbolizers with larger zIndex values are rendered over symbolizers
+ * with smaller zIndex values. Default is 0.
+ */
+ zIndex: 0,
+
+ /**
+ * Constructor: OpenLayers.Symbolizer
+ * Instances of this class are not useful. See one of the subclasses.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Util.extend(this, config);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a copy of this symbolizer.
+ *
+ * Returns a symbolizer of the same type with the same properties.
+ */
+ clone: function() {
+ var Type = eval(this.CLASS_NAME);
+ return new Type(OpenLayers.Util.extend({}, this));
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer"
+
+});
+
+/* ======================================================================
+ OpenLayers/Symbolizer/Raster.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Raster
+ * A symbolizer used to render raster images.
+ */
+OpenLayers.Symbolizer.Raster = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Raster
+ * Create a symbolizer for rendering rasters.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new raster symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Raster"
+
+});
+/* ======================================================================
+ OpenLayers/Rule.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Rule
+ * This class represents an SLD Rule, as being used for rule-based SLD styling.
+ */
+OpenLayers.Rule = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String} name of this rule
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this rule (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this rule (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * Property: context
+ * {Object} An optional object with properties that the rule should be
+ * evaluated against. If no context is specified, feature.attributes will
+ * be used.
+ */
+ context: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} Optional filter for the rule.
+ */
+ filter: null,
+
+ /**
+ * Property: elseFilter
+ * {Boolean} Determines whether this rule is only to be applied only if
+ * no other rules match (ElseFilter according to the SLD specification).
+ * Default is false. For instances of OpenLayers.Rule, if elseFilter is
+ * false, the rule will always apply. For subclasses, the else property is
+ * ignored.
+ */
+ elseFilter: false,
+
+ /**
+ * Property: symbolizer
+ * {Object} Symbolizer or hash of symbolizers for this rule. If hash of
+ * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The
+ * latter if useful if it is required to style e.g. vertices of a line
+ * with a point symbolizer. Note, however, that this is not implemented
+ * yet in OpenLayers, but it is the way how symbolizers are defined in
+ * SLD.
+ */
+ symbolizer: null,
+
+ /**
+ * Property: symbolizers
+ * {Array} Collection of symbolizers associated with this rule. If
+ * provided at construction, the symbolizers array has precedence
+ * over the deprecated symbolizer property. Note that multiple
+ * symbolizers are not currently supported by the vector renderers.
+ * Rules with multiple symbolizers are currently only useful for
+ * maintaining elements in an SLD document.
+ */
+ symbolizers: null,
+
+ /**
+ * APIProperty: minScaleDenominator
+ * {Number} or {String} minimum scale at which to draw the feature.
+ * In the case of a String, this can be a combination of text and
+ * propertyNames in the form "literal ${propertyName}"
+ */
+ minScaleDenominator: null,
+
+ /**
+ * APIProperty: maxScaleDenominator
+ * {Number} or {String} maximum scale at which to draw the feature.
+ * In the case of a String, this can be a combination of text and
+ * propertyNames in the form "literal ${propertyName}"
+ */
+ maxScaleDenominator: null,
+
+ /**
+ * Constructor: OpenLayers.Rule
+ * Creates a Rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Rule>}
+ */
+ initialize: function(options) {
+ this.symbolizer = {};
+ OpenLayers.Util.extend(this, options);
+ if (this.symbolizers) {
+ delete this.symbolizer;
+ }
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i in this.symbolizer) {
+ this.symbolizer[i] = null;
+ }
+ this.symbolizer = null;
+ delete this.symbolizers;
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+ *
+ * Returns:
+ * {Boolean} true if the rule applies, false if it does not.
+ * This rule is the default rule and always returns true.
+ */
+ evaluate: function(feature) {
+ var context = this.getContext(feature);
+ var applies = true;
+
+ if (this.minScaleDenominator || this.maxScaleDenominator) {
+ var scale = feature.layer.map.getScale();
+ }
+
+ // check if within minScale/maxScale bounds
+ if (this.minScaleDenominator) {
+ applies = scale >= OpenLayers.Style.createLiteral(
+ this.minScaleDenominator, context);
+ }
+ if (applies && this.maxScaleDenominator) {
+ applies = scale < OpenLayers.Style.createLiteral(
+ this.maxScaleDenominator, context);
+ }
+
+ // check if optional filter applies
+ if(applies && this.filter) {
+ // feature id filters get the feature, others get the context
+ if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
+ applies = this.filter.evaluate(feature);
+ } else {
+ applies = this.filter.evaluate(context);
+ }
+ }
+
+ return applies;
+ },
+
+ /**
+ * Method: getContext
+ * Gets the context for evaluating this rule
+ *
+ * Paramters:
+ * feature - {<OpenLayers.Feature>} feature to take the context from if
+ * none is specified.
+ */
+ getContext: function(feature) {
+ var context = this.context;
+ if (!context) {
+ context = feature.attributes || feature.data;
+ }
+ if (typeof this.context == "function") {
+ context = this.context(feature);
+ }
+ return context;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this rule.
+ *
+ * Returns:
+ * {<OpenLayers.Rule>} Clone of this rule.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ if (this.symbolizers) {
+ // clone symbolizers
+ var len = this.symbolizers.length;
+ options.symbolizers = new Array(len);
+ for (var i=0; i<len; ++i) {
+ options.symbolizers[i] = this.symbolizers[i].clone();
+ }
+ } else {
+ // clone symbolizer
+ options.symbolizer = {};
+ var value, type;
+ for(var key in this.symbolizer) {
+ value = this.symbolizer[key];
+ type = typeof value;
+ if(type === "object") {
+ options.symbolizer[key] = OpenLayers.Util.extend({}, value);
+ } else if(type === "string") {
+ options.symbolizer[key] = value;
+ }
+ }
+ }
+ // clone filter
+ options.filter = this.filter && this.filter.clone();
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ return new OpenLayers.Rule(options);
+ },
+
+ CLASS_NAME: "OpenLayers.Rule"
+});
+/* ======================================================================
+ OpenLayers/Format/SLD.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD
+ * Read/Write SLD. Create a new instance with the <OpenLayers.Format.SLD>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ *
+ * Currently supported profiles:
+ * - GeoServer - parses GeoServer vendor specific capabilities for SLD.
+ */
+ profile: null,
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is true.
+ */
+ stringifyOutput: true,
+
+ /**
+ * APIProperty: namedLayersAsArray
+ * {Boolean} Generate a namedLayers array. If false, the namedLayers
+ * property value will be an object keyed by layer name. Default is
+ * false.
+ */
+ namedLayersAsArray: false,
+
+ /**
+ * APIMethod: write
+ * Write a SLD document given a list of styles.
+ *
+ * Parameters:
+ * sld - {Object} An object representing the SLD.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} An SLD document string.
+ */
+
+ /**
+ * APIMethod: read
+ * Read and SLD doc and return an object representing the SLD.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the SLD.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.SLD"
+});
+/* ======================================================================
+ OpenLayers/Symbolizer/Polygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Polygon
+ * A symbolizer used to render line features.
+ */
+OpenLayers.Symbolizer.Polygon = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: strokeColor
+ * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000"
+ * for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeOpacity
+ * {Number} Stroke opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeWidth
+ * {Number} Pixel stroke width.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeLinecap
+ * {String} Stroke cap type ("butt", "round", or "square").
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: strokeDashstyle
+ * {String} Stroke dash style according to the SLD spec. Note that the
+ * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot",
+ * "longdash", "longdashdot", or "solid") will not work in SLD, but
+ * most SLD patterns will render correctly in OpenLayers.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillColor
+ * {String} RGB hex fill color (e.g. "#ff0000" for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillOpacity
+ * {Number} Fill opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Polygon
+ * Create a symbolizer for rendering polygons.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new polygon symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Polygon"
+
+});
+
+/* ======================================================================
+ OpenLayers/Format/GML/v2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/Base.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML.v2
+ * Parses GML version 2.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.Base>
+ */
+OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.GML.v2
+ * Create a parser for GML v2.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required).
+ * geometryName - {String} Geometry element name.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "outerBoundaryIs": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.outer = obj.components[0];
+ },
+ "innerBoundaryIs": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.inner.push(obj.components[0]);
+ },
+ "Box": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ var min = obj.points[0];
+ var max = obj.points[1];
+ container.components.push(
+ new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
+ );
+ }
+ }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ // GML2 only has abstract feature collections
+ // wfs provides a feature collection from a well-known schema
+ name = "wfs:FeatureCollection";
+ } else {
+ name = "gml:featureMember";
+ }
+ var root = this.writeNode(name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "Point": function(geometry) {
+ var node = this.createElementNSPlus("gml:Point");
+ this.writeNode("coordinates", [geometry], node);
+ return node;
+ },
+ "coordinates": function(points) {
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ var point;
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ if(this.xy) {
+ parts[i] = point.x + "," + point.y;
+ } else {
+ parts[i] = point.y + "," + point.x;
+ }
+ if(point.z != undefined) { // allow null or undefined
+ parts[i] += "," + point.z;
+ }
+ }
+ return this.createElementNSPlus("gml:coordinates", {
+ attributes: {
+ decimal: ".", cs: ",", ts: " "
+ },
+ value: (numPoints == 1) ? parts[0] : parts.join(" ")
+ });
+ },
+ "LineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineString");
+ this.writeNode("coordinates", geometry.components, node);
+ return node;
+ },
+ "Polygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:Polygon");
+ this.writeNode("outerBoundaryIs", geometry.components[0], node);
+ for(var i=1; i<geometry.components.length; ++i) {
+ this.writeNode(
+ "innerBoundaryIs", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "outerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:outerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "innerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:innerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "LinearRing": function(ring) {
+ var node = this.createElementNSPlus("gml:LinearRing");
+ this.writeNode("coordinates", ring.components, node);
+ return node;
+ },
+ "Box": function(bounds) {
+ var node = this.createElementNSPlus("gml:Box");
+ this.writeNode("coordinates", [
+ {x: bounds.left, y: bounds.bottom},
+ {x: bounds.right, y: bounds.top}
+ ], node);
+ // srsName attribute is optional for gml:Box
+ if(this.srsName) {
+ node.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.v2"
+
+});
+/* ======================================================================
+ OpenLayers/Format/Filter/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/v2.js
+ * @requires OpenLayers/Format/Filter/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_0_0
+ * Write ogc:Filter version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.v2>
+ * - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
+ */
+ schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.v2.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsNotEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLike": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE
+ });
+ this.readChildNodes(node, filter);
+ var wildCard = node.getAttribute("wildCard");
+ var singleChar = node.getAttribute("singleChar");
+ var esc = node.getAttribute("escape");
+ filter.value2regex(wildCard, singleChar, esc);
+ obj.filters.push(filter);
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsNotEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLike": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+ attributes: {
+ wildCard: "*", singleChar: ".", escape: "!"
+ }
+ });
+ // no ogc:expression handling for now
+ this.writeNode("PropertyName", filter, node);
+ // convert regex string to ogc string
+ this.writeNode("Literal", filter.regex2value(), node);
+ return node;
+ },
+ "BBOX": function(filter) {
+ var node = this.createElementNSPlus("ogc:BBOX");
+ // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
+ // accepts filters without it. When this is used with
+ // OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a
+ // missing filter.property to the geometryName that is
+ // configured with the protocol, which defaults to "the_geom".
+ // So the only way to omit this mandatory property is to not
+ // set the property on the filter and to set the geometryName
+ // on the WFS protocol to null. The latter also happens when
+ // the protocol is configured without a geometryName and a
+ // featureNS.
+ filter.property && this.writeNode("PropertyName", filter, node);
+ var box = this.writeNode("gml:Box", filter.value, node);
+ if(filter.projection) {
+ box.setAttribute("srsName", filter.projection);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
+ },
+
+ /**
+ * Method: writeSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter.Spatial>} The filter.
+ * name - {String} Name of the generated XML element.
+ *
+ * Returns:
+ * {DOMElement} The created XML element.
+ */
+ writeSpatial: function(filter, name) {
+ var node = this.createElementNSPlus("ogc:"+name);
+ this.writeNode("PropertyName", filter, node);
+ if(filter.value instanceof OpenLayers.Filter.Function) {
+ this.writeNode("Function", filter.value, node);
+ } else {
+ var child;
+ if(filter.value instanceof OpenLayers.Geometry) {
+ child = this.writeNode("feature:_geometry", filter.value).firstChild;
+ } else {
+ child = this.writeNode("gml:Box", filter.value);
+ }
+ if(filter.projection) {
+ child.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(child);
+ }
+ return node;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WFST/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFST/v1.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1_0_0
+ * A format for creating WFS v1.0.0 transactions. Create a new instance with the
+ * <OpenLayers.Format.WFST.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_0_0>
+ * - <OpenLayers.Format.WFST.v1>
+ */
+OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * APIProperty: srsNameInQuery
+ * {Boolean} If true the reference system is passed in Query requests
+ * via the "srsName" attribute to the "wfs:Query" element, this
+ * property defaults to false as it isn't WFS 1.0.0 compliant.
+ */
+ srsNameInQuery: false,
+
+ /**
+ * Property: schemaLocations
+ * {Object} Properties are namespace aliases, values are schema locations.
+ */
+ schemaLocations: {
+ "wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1_0_0
+ * A class for parsing and generating WFS v1.0.0 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // Not the superclass, only the mixin classes inherit from
+ // Format.GML.v2. We need this because we don't want to get readNode
+ // from the superclass's superclass, which is OpenLayers.Format.XML.
+ return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "WFS_TransactionResponse": function(node, obj) {
+ obj.insertIds = [];
+ obj.success = false;
+ this.readChildNodes(node, obj);
+ },
+ "InsertResult": function(node, container) {
+ var obj = {fids: []};
+ this.readChildNodes(node, obj);
+ container.insertIds = container.insertIds.concat(obj.fids);
+ },
+ "TransactionResult": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Status": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "SUCCESS": function(node, obj) {
+ obj.success = true;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "Query": function(options) {
+ options = OpenLayers.Util.extend({
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ featureType: this.featureType,
+ srsName: this.srsName,
+ srsNameInQuery: this.srsNameInQuery
+ }, options);
+ var prefix = options.featurePrefix;
+ var node = this.createElementNSPlus("wfs:Query", {
+ attributes: {
+ typeName: (prefix ? prefix + ":" : "") +
+ options.featureType
+ }
+ });
+ if(options.srsNameInQuery && options.srsName) {
+ node.setAttribute("srsName", options.srsName);
+ }
+ if(options.featureNS) {
+ node.setAttribute("xmlns:" + prefix, options.featureNS);
+ }
+ if(options.propertyNames) {
+ for(var i=0,len = options.propertyNames.length; i<len; i++) {
+ this.writeNode(
+ "ogc:PropertyName",
+ {property: options.propertyNames[i]},
+ node
+ );
+ }
+ }
+ if(options.filter) {
+ this.setFilterProperty(options.filter);
+ this.writeNode("ogc:Filter", options.filter, node);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/Renderer/Elements.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ * placed in the DOM based on given indexing methods.
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+
+ /**
+ * Property: maxZIndex
+ * {Integer} This is the largest-most z-index value for a node
+ * contained within the indexer.
+ */
+ maxZIndex: null,
+
+ /**
+ * Property: order
+ * {Array<String>} This is an array of node id's stored in the
+ * order that they should show up on screen. Id's higher up in the
+ * array (higher array index) represent nodes with higher z-indeces.
+ */
+ order: null,
+
+ /**
+ * Property: indices
+ * {Object} This is a hash that maps node ids to their z-index value
+ * stored in the indexer. This is done to make finding a nodes z-index
+ * value O(1).
+ */
+ indices: null,
+
+ /**
+ * Property: compare
+ * {Function} This is the function used to determine placement of
+ * of a new node within the indexer. If null, this defaults to to
+ * the Z_ORDER_DRAWING_ORDER comparison method.
+ */
+ compare: null,
+
+ /**
+ * APIMethod: initialize
+ * Create a new indexer with
+ *
+ * Parameters:
+ * yOrdering - {Boolean} Whether to use y-ordering.
+ */
+ initialize: function(yOrdering) {
+
+ this.compare = yOrdering ?
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
+
+ this.clear();
+ },
+
+ /**
+ * APIMethod: insert
+ * Insert a new node into the indexer. In order to find the correct
+ * positioning for the node to be inserted, this method uses a binary
+ * search. This makes inserting O(log(n)).
+ *
+ * Parameters:
+ * newNode - {DOMElement} The new node to be inserted.
+ *
+ * Returns
+ * {DOMElement} the node before which we should insert our newNode, or
+ * null if newNode can just be appended.
+ */
+ insert: function(newNode) {
+ // If the node is known to the indexer, remove it so we can
+ // recalculate where it should go.
+ if (this.exists(newNode)) {
+ this.remove(newNode);
+ }
+
+ var nodeId = newNode.id;
+
+ this.determineZIndex(newNode);
+
+ var leftIndex = -1;
+ var rightIndex = this.order.length;
+ var middle;
+
+ while (rightIndex - leftIndex > 1) {
+ middle = parseInt((leftIndex + rightIndex) / 2);
+
+ var placement = this.compare(this, newNode,
+ OpenLayers.Util.getElement(this.order[middle]));
+
+ if (placement > 0) {
+ leftIndex = middle;
+ } else {
+ rightIndex = middle;
+ }
+ }
+
+ this.order.splice(rightIndex, 0, nodeId);
+ this.indices[nodeId] = this.getZIndex(newNode);
+
+ // If the new node should be before another in the index
+ // order, return the node before which we have to insert the new one;
+ // else, return null to indicate that the new node can be appended.
+ return this.getNextElement(rightIndex);
+ },
+
+ /**
+ * APIMethod: remove
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be removed.
+ */
+ remove: function(node) {
+ var nodeId = node.id;
+ var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
+ if (arrayIndex >= 0) {
+ // Remove it from the order array, as well as deleting the node
+ // from the indeces hash.
+ this.order.splice(arrayIndex, 1);
+ delete this.indices[nodeId];
+
+ // Reset the maxium z-index based on the last item in the
+ // order array.
+ if (this.order.length > 0) {
+ var lastId = this.order[this.order.length - 1];
+ this.maxZIndex = this.indices[lastId];
+ } else {
+ this.maxZIndex = 0;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clear
+ */
+ clear: function() {
+ this.order = [];
+ this.indices = {};
+ this.maxZIndex = 0;
+ },
+
+ /**
+ * APIMethod: exists
+ *
+ * Parameters:
+ * node - {DOMElement} The node to test for existence.
+ *
+ * Returns:
+ * {Boolean} Whether or not the node exists in the indexer?
+ */
+ exists: function(node) {
+ return (this.indices[node.id] != null);
+ },
+
+ /**
+ * APIMethod: getZIndex
+ * Get the z-index value for the current node from the node data itself.
+ *
+ * Parameters:
+ * node - {DOMElement} The node whose z-index to get.
+ *
+ * Returns:
+ * {Integer} The z-index value for the specified node (from the node
+ * data itself).
+ */
+ getZIndex: function(node) {
+ return node._style.graphicZIndex;
+ },
+
+ /**
+ * Method: determineZIndex
+ * Determine the z-index for the current node if there isn't one,
+ * and set the maximum value if we've found a new maximum.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ determineZIndex: function(node) {
+ var zIndex = node._style.graphicZIndex;
+
+ // Everything must have a zIndex. If none is specified,
+ // this means the user *must* (hint: assumption) want this
+ // node to succomb to drawing order. To enforce drawing order
+ // over all indexing methods, we'll create a new z-index that's
+ // greater than any currently in the indexer.
+ if (zIndex == null) {
+ zIndex = this.maxZIndex;
+ node._style.graphicZIndex = zIndex;
+ } else if (zIndex > this.maxZIndex) {
+ this.maxZIndex = zIndex;
+ }
+ },
+
+ /**
+ * APIMethod: getNextElement
+ * Get the next element in the order stack.
+ *
+ * Parameters:
+ * index - {Integer} The index of the current node in this.order.
+ *
+ * Returns:
+ * {DOMElement} the node following the index passed in, or
+ * null.
+ */
+ getNextElement: function(index) {
+ var nextIndex = index + 1;
+ if (nextIndex < this.order.length) {
+ var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
+ if (nextElement == undefined) {
+ nextElement = this.getNextElement(nextIndex);
+ }
+ return nextElement;
+ } else {
+ return null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.ElementsIndexer"
+});
+
+/**
+ * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
+ * These are the compare methods for figuring out where a new node should be
+ * placed within the indexer. These methods are very similar to general
+ * sorting methods in that they return -1, 0, and 1 to specify the
+ * direction in which new nodes fall in the ordering.
+ */
+OpenLayers.ElementsIndexer.IndexingMethods = {
+
+ /**
+ * Method: Z_ORDER
+ * This compare method is used by other comparison methods.
+ * It can be used individually for ordering, but is not recommended,
+ * because it doesn't subscribe to drawing order.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER: function(indexer, newNode, nextNode) {
+ var newZIndex = indexer.getZIndex(newNode);
+
+ var returnVal = 0;
+ if (nextNode) {
+ var nextZIndex = indexer.getZIndex(nextNode);
+ returnVal = newZIndex - nextZIndex;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_DRAWING_ORDER
+ * This method orders nodes by their z-index, but does so in a way
+ * that, if there are other nodes with the same z-index, the newest
+ * drawn will be the front most within that z-index. This is the
+ * default indexing method.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ // Make Z_ORDER subscribe to drawing order by pushing it above
+ // all of the other nodes with the same z-index.
+ if (nextNode && returnVal == 0) {
+ returnVal = 1;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_Y_ORDER
+ * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
+ * best describes which ordering methods have precedence (though, the
+ * name would be too long). This method orders nodes by their z-index,
+ * but does so in a way that, if there are other nodes with the same
+ * z-index, the nodes with the lower y position will be "closer" than
+ * those with a higher y position. If two nodes have the exact same y
+ * position, however, then this method will revert to using drawing
+ * order to decide placement.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ if (nextNode && returnVal === 0) {
+ var result = nextNode._boundsBottom - newNode._boundsBottom;
+ returnVal = (result === 0) ? 1 : result;
+ }
+
+ return returnVal;
+ }
+};
+
+/**
+ * Class: OpenLayers.Renderer.Elements
+ * This is another virtual class in that it should never be instantiated by
+ * itself as a Renderer. It exists because there is *tons* of shared
+ * functionality between different vector libraries which use nodes/elements
+ * as a base for rendering vectors.
+ *
+ * The highlevel bits of code that are implemented here are the adding and
+ * removing of geometries, which is essentially the same for any
+ * element-based renderer. The details of creating each node and drawing the
+ * paths are of course different, but the machinery is the same.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * Property: rendererRoot
+ * {DOMElement}
+ */
+ rendererRoot: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: vectorRoot
+ * {DOMElement}
+ */
+ vectorRoot: null,
+
+ /**
+ * Property: textRoot
+ * {DOMElement}
+ */
+ textRoot: null,
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: null,
+
+ /**
+ * Property: xOffset
+ * {Number} Offset to apply to the renderer viewport translation in x
+ * direction. If the renderer extent's center is on the right of the
+ * dateline (i.e. exceeds the world bounds), we shift the viewport to the
+ * left by one world width. This avoids that features disappear from the
+ * map viewport. Because our dateline handling logic in other places
+ * ensures that extents crossing the dateline always have a center
+ * exceeding the world bounds on the left, we need this offset to make sure
+ * that the same is true for the renderer extent in pixel space as well.
+ */
+ xOffset: 0,
+
+ /**
+ * Property: rightOfDateLine
+ * {Boolean} Keeps track of the location of the map extent relative to the
+ * date line. The <setExtent> method compares this value (which is the one
+ * from the previous <setExtent> call) with the current position of the map
+ * extent relative to the date line and updates the xOffset when the extent
+ * has moved from one side of the date line to the other.
+ */
+
+ /**
+ * Property: Indexer
+ * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
+ * created upon initialization if the zIndexing or yOrdering options
+ * passed to this renderer's constructor are set to true.
+ */
+ indexer: null,
+
+ /**
+ * Constant: BACKGROUND_ID_SUFFIX
+ * {String}
+ */
+ BACKGROUND_ID_SUFFIX: "_background",
+
+ /**
+ * Constant: LABEL_ID_SUFFIX
+ * {String}
+ */
+ LABEL_ID_SUFFIX: "_label",
+
+ /**
+ * Constant: LABEL_OUTLINE_SUFFIX
+ * {String}
+ */
+ LABEL_OUTLINE_SUFFIX: "_outline",
+
+ /**
+ * Constructor: OpenLayers.Renderer.Elements
+ *
+ * Parameters:
+ * containerID - {String}
+ * options - {Object} options for this renderer.
+ *
+ * Supported options are:
+ * yOrdering - {Boolean} Whether to use y-ordering
+ * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+ * if yOrdering is set to true.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+
+ this.rendererRoot = this.createRenderRoot();
+ this.root = this.createRoot("_root");
+ this.vectorRoot = this.createRoot("_vroot");
+ this.textRoot = this.createRoot("_troot");
+
+ this.root.appendChild(this.vectorRoot);
+ this.root.appendChild(this.textRoot);
+
+ this.rendererRoot.appendChild(this.root);
+ this.container.appendChild(this.rendererRoot);
+
+ if(options && (options.zIndexing || options.yOrdering)) {
+ this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+
+ this.clear();
+
+ this.rendererRoot = null;
+ this.root = null;
+ this.xmlns = null;
+
+ OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clear
+ * Remove all the elements from the root
+ */
+ clear: function() {
+ var child;
+ var root = this.vectorRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ root = this.textRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ if (this.indexer) {
+ this.indexer.clear();
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var rightOfDateLine,
+ ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio),
+ world = this.map.getMaxExtent();
+ if (world.right > extent.left && world.right < extent.right) {
+ rightOfDateLine = true;
+ } else if (world.left > extent.left && world.left < extent.right) {
+ rightOfDateLine = false;
+ }
+ if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
+ coordSysUnchanged = false;
+ this.xOffset = rightOfDateLine === true ?
+ world.getWidth() / resolution : 0;
+ }
+ this.rightOfDateLine = rightOfDateLine;
+ }
+ return coordSysUnchanged;
+ },
+
+ /**
+ * Method: getNodeType
+ * This function is in charge of asking the specific renderer which type
+ * of node to create for the given geometry and style. All geometries
+ * in an Elements-based renderer consist of one node and some
+ * attributes. We have the nodeFactory() function which creates a node
+ * for us, but it takes a 'type' as input, and that is precisely what
+ * this function tells us.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) { },
+
+ /**
+ * Method: drawGeometry
+ * Draw the geometry, creating new nodes, setting paths, setting style,
+ * setting featureId on the node. This method should only be called
+ * by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the geometry has been drawn completely; null if
+ * incomplete; false otherwise
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ var rendered = true;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0, len=geometry.components.length; i<len; i++) {
+ rendered = this.drawGeometry(
+ geometry.components[i], style, featureId) && rendered;
+ }
+ return rendered;
+ }
+
+ rendered = false;
+ var removeBackground = false;
+ if (style.display != "none") {
+ if (style.backgroundGraphic) {
+ this.redrawBackgroundNode(geometry.id, geometry, style,
+ featureId);
+ } else {
+ removeBackground = true;
+ }
+ rendered = this.redrawNode(geometry.id, geometry, style,
+ featureId);
+ }
+ if (rendered == false) {
+ var node = document.getElementById(geometry.id);
+ if (node) {
+ if (node._style.backgroundGraphic) {
+ removeBackground = true;
+ }
+ node.parentNode.removeChild(node);
+ }
+ }
+ if (removeBackground) {
+ var node = document.getElementById(
+ geometry.id + this.BACKGROUND_ID_SUFFIX);
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: redrawNode
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawNode: function(id, geometry, style, featureId) {
+ style = this.applyDefaultSymbolizer(style);
+ // Get the node if it's already on the map.
+ var node = this.nodeFactory(id, this.getNodeType(geometry, style));
+
+ // Set the data for the node, then draw it.
+ node._featureId = featureId;
+ node._boundsBottom = geometry.getBounds().bottom;
+ node._geometryClass = geometry.CLASS_NAME;
+ node._style = style;
+
+ var drawResult = this.drawGeometryNode(node, geometry, style);
+ if(drawResult === false) {
+ return false;
+ }
+
+ node = drawResult.node;
+
+ // Insert the node into the indexer so it can show us where to
+ // place it. Note that this operation is O(log(n)). If there's a
+ // performance problem (when dragging, for instance) this is
+ // likely where it would be.
+ if (this.indexer) {
+ var insert = this.indexer.insert(node);
+ if (insert) {
+ this.vectorRoot.insertBefore(node, insert);
+ } else {
+ this.vectorRoot.appendChild(node);
+ }
+ } else {
+ // if there's no indexer, simply append the node to root,
+ // but only if the node is a new one
+ if (node.parentNode !== this.vectorRoot){
+ this.vectorRoot.appendChild(node);
+ }
+ }
+
+ this.postDraw(node);
+
+ return drawResult.complete;
+ },
+
+ /**
+ * Method: redrawBackgroundNode
+ * Redraws the node using special 'background' style properties. Basically
+ * just calls redrawNode(), but instead of directly using the
+ * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
+ * 'graphicZIndex' properties directly from the specified 'style'
+ * parameter, we create a new style object and set those properties
+ * from the corresponding 'background'-prefixed properties from
+ * specified 'style' parameter.
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawBackgroundNode: function(id, geometry, style, featureId) {
+ var backgroundStyle = OpenLayers.Util.extend({}, style);
+
+ // Set regular style attributes to apply to the background styles.
+ backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
+ backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
+ backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
+ backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
+ backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
+ backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
+
+ // Erase background styles.
+ backgroundStyle.backgroundGraphic = null;
+ backgroundStyle.backgroundXOffset = null;
+ backgroundStyle.backgroundYOffset = null;
+ backgroundStyle.backgroundGraphicZIndex = null;
+
+ return this.redrawNode(
+ id + this.BACKGROUND_ID_SUFFIX,
+ geometry,
+ backgroundStyle,
+ null
+ );
+ },
+
+ /**
+ * Method: drawGeometryNode
+ * Given a node, draw a geometry on the specified layer.
+ * node and geometry are required arguments, style is optional.
+ * This method is only called by the render itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {Object} a hash with properties "node" (the drawn node) and "complete"
+ * (null if parts of the geometry could not be drawn, false if nothing
+ * could be drawn)
+ */
+ drawGeometryNode: function(node, geometry, style) {
+ style = style || node._style;
+
+ var options = {
+ 'isFilled': style.fill === undefined ?
+ true :
+ style.fill,
+ 'isStroked': style.stroke === undefined ?
+ !!style.strokeWidth :
+ style.stroke
+ };
+ var drawn;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if(style.graphic === false) {
+ options.isFilled = false;
+ options.isStroked = false;
+ }
+ drawn = this.drawPoint(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ options.isFilled = false;
+ drawn = this.drawLineString(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ drawn = this.drawLinearRing(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ drawn = this.drawPolygon(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ drawn = this.drawRectangle(node, geometry);
+ break;
+ default:
+ break;
+ }
+
+ node._options = options;
+
+ //set style
+ //TBD simplify this
+ if (drawn != false) {
+ return {
+ node: this.setStyle(node, style, options, geometry),
+ complete: drawn
+ };
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: postDraw
+ * Things that have do be done after the geometry node is appended
+ * to its parent node. To be overridden by subclasses.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {},
+
+ /**
+ * Method: drawPoint
+ * Virtual function for drawing Point Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {},
+
+ /**
+ * Method: drawLineString
+ * Virtual function for drawing LineString Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {},
+
+ /**
+ * Method: drawLinearRing
+ * Virtual function for drawing LinearRing Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {},
+
+ /**
+ * Method: drawPolygon
+ * Virtual function for drawing Polygon Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {},
+
+ /**
+ * Method: drawRectangle
+ * Virtual function for drawing Rectangle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {},
+
+ /**
+ * Method: drawCircle
+ * Virtual function for drawing Circle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry) {},
+
+ /**
+ * Method: removeText
+ * Removes a label
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {
+ var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
+ if (label) {
+ this.textRoot.removeChild(label);
+ }
+ var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
+ if (outline) {
+ this.textRoot.removeChild(outline);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var target = evt.target;
+ var useElement = target && target.correspondingUseElement;
+ var node = useElement ? useElement : (target || evt.srcElement);
+ return node._featureId;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. In the case of a multi-geometry,
+ * we cycle through and recurse on ourselves. Otherwise, we look for a
+ * node with the geometry.id, destroy its geometry, and remove it from
+ * the DOM.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
+ for (var i=0, len=geometry.components.length; i<len; i++) {
+ this.eraseGeometry(geometry.components[i], featureId);
+ }
+ } else {
+ var element = OpenLayers.Util.getElement(geometry.id);
+ if (element && element.parentNode) {
+ if (element.geometry) {
+ element.geometry.destroy();
+ element.geometry = null;
+ }
+ element.parentNode.removeChild(element);
+
+ if (this.indexer) {
+ this.indexer.remove(element);
+ }
+
+ if (element._style.backgroundGraphic) {
+ var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+ var bElem = OpenLayers.Util.getElement(backgroundId);
+ if (bElem && bElem.parentNode) {
+ // No need to destroy the geometry since the element and the background
+ // node share the same geometry.
+ bElem.parentNode.removeChild(bElem);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: nodeFactory
+ * Create new node of the specified type, with the (optional) specified id.
+ *
+ * If node already exists with same ID and a different type, we remove it
+ * and then call ourselves again to recreate it.
+ *
+ * Parameters:
+ * id - {String}
+ * type - {String} type Kind of node to draw.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ */
+ nodeFactory: function(id, type) {
+ var node = OpenLayers.Util.getElement(id);
+ if (node) {
+ if (!this.nodeTypeCompare(node, type)) {
+ node.parentNode.removeChild(node);
+ node = this.nodeFactory(id, type);
+ }
+ } else {
+ node = this.createNode(type, id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ * This function must be overridden by subclasses.
+ */
+ nodeTypeCompare: function(node, type) {},
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw.
+ * id - {String} Id for node.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ * This function must be overridden by subclasses.
+ */
+ createNode: function(type, id) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {
+ var root = this.root;
+ if(renderer.root.parentNode == this.rendererRoot) {
+ root = renderer.root;
+ }
+ root.parentNode.removeChild(root);
+ renderer.rendererRoot.appendChild(root);
+ },
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.root.parentNode.parentNode.id;
+ },
+
+ /**
+ * Method: isComplexSymbol
+ * Determines if a symbol cannot be rendered using drawCircle
+ *
+ * Parameters:
+ * graphicName - {String}
+ *
+ * Returns
+ * {Boolean} true if the symbol is complex, false if not
+ */
+ isComplexSymbol: function(graphicName) {
+ return (graphicName != "circle") && !!graphicName;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
+/* ======================================================================
+ OpenLayers/Control/ArgParser.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ArgParser
+ * The ArgParser control adds location bar query string parsing functionality
+ * to an OpenLayers Map.
+ * When added to a Map control, on a page load/refresh, the Map will
+ * automatically take the href string and parse it for lon, lat, zoom, and
+ * layers information.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ArgParser = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>}
+ */
+ center: null,
+
+ /**
+ * Property: zoom
+ * {int}
+ */
+ zoom: null,
+
+ /**
+ * Property: layers
+ * {String} Each character represents the state of the corresponding layer
+ * on the map.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support.
+ * Projection used when reading the coordinates from the URL. This will
+ * reproject the map coordinates from the URL into the map's
+ * projection.
+ *
+ * If you are using this functionality, be aware that any permalink
+ * which is added to the map will determine the coordinate type which
+ * is read from the URL, which means you should not add permalinks with
+ * different displayProjections to the same map.
+ */
+ displayProjection: null,
+
+ /**
+ * Constructor: OpenLayers.Control.ArgParser
+ *
+ * Parameters:
+ * options - {Object}
+ */
+
+ /**
+ * Method: getParameters
+ */
+ getParameters: function(url) {
+ url = url || window.location.href;
+ var parameters = OpenLayers.Util.getParameters(url);
+
+ // If we have an anchor in the url use it to split the url
+ var index = url.indexOf('#');
+ if (index > 0) {
+ // create an url to parse on the getParameters
+ url = '?' + url.substring(index + 1, url.length);
+
+ OpenLayers.Util.extend(parameters,
+ OpenLayers.Util.getParameters(url));
+ }
+ return parameters;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ //make sure we dont already have an arg parser attached
+ for(var i=0, len=this.map.controls.length; i<len; i++) {
+ var control = this.map.controls[i];
+ if ( (control != this) &&
+ (control.CLASS_NAME == "OpenLayers.Control.ArgParser") ) {
+
+ // If a second argparser is added to the map, then we
+ // override the displayProjection to be the one added to the
+ // map.
+ if (control.displayProjection != this.displayProjection) {
+ this.displayProjection = control.displayProjection;
+ }
+
+ break;
+ }
+ }
+ if (i == this.map.controls.length) {
+
+ var args = this.getParameters();
+ // Be careful to set layer first, to not trigger unnecessary layer loads
+ if (args.layers) {
+ this.layers = args.layers;
+
+ // when we add a new layer, set its visibility
+ this.map.events.register('addlayer', this,
+ this.configureLayers);
+ this.configureLayers();
+ }
+ if (args.lat && args.lon) {
+ this.center = new OpenLayers.LonLat(parseFloat(args.lon),
+ parseFloat(args.lat));
+ if (args.zoom) {
+ this.zoom = parseFloat(args.zoom);
+ }
+
+ // when we add a new baselayer to see when we can set the center
+ this.map.events.register('changebaselayer', this,
+ this.setCenter);
+ this.setCenter();
+ }
+ }
+ },
+
+ /**
+ * Method: setCenter
+ * As soon as a baseLayer has been loaded, we center and zoom
+ * ...and remove the handler.
+ */
+ setCenter: function() {
+
+ if (this.map.baseLayer) {
+ //dont need to listen for this one anymore
+ this.map.events.unregister('changebaselayer', this,
+ this.setCenter);
+
+ if (this.displayProjection) {
+ this.center.transform(this.displayProjection,
+ this.map.getProjectionObject());
+ }
+
+ this.map.setCenter(this.center, this.zoom);
+ }
+ },
+
+ /**
+ * Method: configureLayers
+ * As soon as all the layers are loaded, cycle through them and
+ * hide or show them.
+ */
+ configureLayers: function() {
+
+ if (this.layers.length == this.map.layers.length) {
+ this.map.events.unregister('addlayer', this, this.configureLayers);
+
+ for(var i=0, len=this.layers.length; i<len; i++) {
+
+ var layer = this.map.layers[i];
+ var c = this.layers.charAt(i);
+
+ if (c == "B") {
+ this.map.setBaseLayer(layer);
+ } else if ( (c == "T") || (c == "F") ) {
+ layer.setVisibility(c == "T");
+ }
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ArgParser"
+});
+/* ======================================================================
+ OpenLayers/Control/Permalink.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/ArgParser.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Permalink
+ * The Permalink control is hyperlink that will return the user to the
+ * current map view. By default it is drawn in the lower right corner of the
+ * map. The href is updated as the map is zoomed, panned and whilst layers
+ * are switched.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: argParserClass
+ * {Class} The ArgParser control class (not instance) to use with this
+ * control.
+ */
+ argParserClass: OpenLayers.Control.ArgParser,
+
+ /**
+ * Property: element
+ * {DOMElement}
+ */
+ element: null,
+
+ /**
+ * APIProperty: anchor
+ * {Boolean} This option changes 3 things:
+ * the character '#' is used in place of the character '?',
+ * the window.href is updated if no element is provided.
+ * When this option is set to true it's not recommend to provide
+ * a base without provide an element.
+ */
+ anchor: false,
+
+ /**
+ * APIProperty: base
+ * {String}
+ */
+ base: '',
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support. Projection used
+ * when creating the coordinates in the link. This will reproject the
+ * map coordinates into display coordinates. If you are using this
+ * functionality, the permalink which is last added to the map will
+ * determine the coordinate type which is read from the URL, which
+ * means you should not add permalinks with different
+ * displayProjections to the same map.
+ */
+ displayProjection: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Permalink
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * base - {String}
+ * options - {Object} options to the control.
+ *
+ * Or for anchor:
+ * options - {Object} options to the control.
+ */
+ initialize: function(element, base, options) {
+ if (element !== null && typeof element == 'object' && !OpenLayers.Util.isElement(element)) {
+ options = element;
+ this.base = document.location.href;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ if (this.element != null) {
+ this.element = OpenLayers.Util.getElement(this.element);
+ }
+ }
+ else {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.element = OpenLayers.Util.getElement(element);
+ this.base = base || document.location.href;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.element && this.element.parentNode == this.div) {
+ this.div.removeChild(this.element);
+ this.element = null;
+ }
+ if (this.map) {
+ this.map.events.unregister('moveend', this, this.updateLink);
+ }
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ //make sure we have an arg parser attached
+ for(var i=0, len=this.map.controls.length; i<len; i++) {
+ var control = this.map.controls[i];
+ if (control.CLASS_NAME == this.argParserClass.CLASS_NAME) {
+
+ // If a permalink is added to the map, and an ArgParser already
+ // exists, we override the displayProjection to be the one
+ // on the permalink.
+ if (control.displayProjection != this.displayProjection) {
+ this.displayProjection = control.displayProjection;
+ }
+
+ break;
+ }
+ }
+ if (i == this.map.controls.length) {
+ this.map.addControl(new this.argParserClass(
+ { 'displayProjection': this.displayProjection }));
+ }
+
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ if (!this.element && !this.anchor) {
+ this.element = document.createElement("a");
+ this.element.innerHTML = OpenLayers.i18n("Permalink");
+ this.element.href="";
+ this.div.appendChild(this.element);
+ }
+ this.map.events.on({
+ 'moveend': this.updateLink,
+ 'changelayer': this.updateLink,
+ 'changebaselayer': this.updateLink,
+ scope: this
+ });
+
+ // Make it so there is at least a link even though the map may not have
+ // moved yet.
+ this.updateLink();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateLink
+ */
+ updateLink: function() {
+ var separator = this.anchor ? '#' : '?';
+ var href = this.base;
+ var anchor = null;
+ if (href.indexOf("#") != -1 && this.anchor == false) {
+ anchor = href.substring( href.indexOf("#"), href.length);
+ }
+ if (href.indexOf(separator) != -1) {
+ href = href.substring( 0, href.indexOf(separator) );
+ }
+ var splits = href.split("#");
+ href = splits[0] + separator+ OpenLayers.Util.getParameterString(this.createParams());
+ if (anchor) {
+ href += anchor;
+ }
+ if (this.anchor && !this.element) {
+ window.location.href = href;
+ }
+ else {
+ this.element.href = href;
+ }
+ },
+
+ /**
+ * APIMethod: createParams
+ * Creates the parameters that need to be encoded into the permalink url.
+ *
+ * Parameters:
+ * center - {<OpenLayers.LonLat>} center to encode in the permalink.
+ * Defaults to the current map center.
+ * zoom - {Integer} zoom level to encode in the permalink. Defaults to the
+ * current map zoom level.
+ * layers - {Array(<OpenLayers.Layer>)} layers to encode in the permalink.
+ * Defaults to the current map layers.
+ *
+ * Returns:
+ * {Object} Hash of parameters that will be url-encoded into the
+ * permalink.
+ */
+ createParams: function(center, zoom, layers) {
+ center = center || this.map.getCenter();
+
+ var params = OpenLayers.Util.getParameters(this.base);
+
+ // If there's still no center, map is not initialized yet.
+ // Break out of this function, and simply return the params from the
+ // base link.
+ if (center) {
+
+ //zoom
+ params.zoom = zoom || this.map.getZoom();
+
+ //lon,lat
+ var lat = center.lat;
+ var lon = center.lon;
+
+ if (this.displayProjection) {
+ var mapPosition = OpenLayers.Projection.transform(
+ { x: lon, y: lat },
+ this.map.getProjectionObject(),
+ this.displayProjection );
+ lon = mapPosition.x;
+ lat = mapPosition.y;
+ }
+ params.lat = Math.round(lat*100000)/100000;
+ params.lon = Math.round(lon*100000)/100000;
+
+ //layers
+ layers = layers || this.map.layers;
+ params.layers = '';
+ for (var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+
+ if (layer.isBaseLayer) {
+ params.layers += (layer == this.map.baseLayer) ? "B" : "0";
+ } else {
+ params.layers += (layer.getVisibility()) ? "T" : "F";
+ }
+ }
+ }
+
+ return params;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Permalink"
+});
+/* ======================================================================
+ OpenLayers/Layer/TMS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TMS
+ * Create a layer for accessing tiles from services that conform with the
+ * Tile Map Service Specification
+ * (http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification).
+ *
+ * Example:
+ * (code)
+ * var layer = new OpenLayers.Layer.TMS(
+ * "My Layer", // name for display in LayerSwitcher
+ * "http://tilecache.osgeo.org/wms-c/Basic.py/", // service endpoint
+ * {layername: "basic", type: "png"} // required properties
+ * );
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: serviceVersion
+ * {String} Service version for tile requests. Default is "1.0.0".
+ */
+ serviceVersion: "1.0.0",
+
+ /**
+ * APIProperty: layername
+ * {String} The identifier for the <TileMap> as advertised by the service.
+ * For example, if the service advertises a <TileMap> with
+ * 'href="http://tms.osgeo.org/1.0.0/vmap0"', the <layername> property
+ * would be set to "vmap0".
+ */
+ layername: null,
+
+ /**
+ * APIProperty: type
+ * {String} The format extension corresponding to the requested tile image
+ * type. This is advertised in a <TileFormat> element as the
+ * "extension" attribute. For example, if the service advertises a
+ * <TileMap> with <TileFormat width="256" height="256" mime-type="image/jpeg" extension="jpg" />,
+ * the <type> property would be set to "jpg".
+ */
+ type: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Make this layer a base layer. Default is true. Set false to
+ * use the layer as an overlay.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
+ * If provided, requests for tiles at all resolutions will be aligned
+ * with this location (no tiles shall overlap this location). If
+ * not provided, the grid of tiles will be aligned with the bottom-left
+ * corner of the map's <maxExtent>. Default is ``null``.
+ *
+ * Example:
+ * (code)
+ * var layer = new OpenLayers.Layer.TMS(
+ * "My Layer",
+ * "http://tilecache.osgeo.org/wms-c/Basic.py/",
+ * {
+ * layername: "basic",
+ * type: "png",
+ * // set if different than the bottom left of map.maxExtent
+ * tileOrigin: new OpenLayers.LonLat(-180, -90)
+ * }
+ * );
+ * (end)
+ */
+ tileOrigin: null,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more zoom levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Using <zoomOffset> is an alternative to
+ * setting <serverResolutions> if you only want to expose a subset
+ * of the server resolutions.
+ */
+ zoomOffset: 0,
+
+ /**
+ * Constructor: OpenLayers.Layer.TMS
+ *
+ * Parameters:
+ * name - {String} Title to be displayed in a <OpenLayers.Control.LayerSwitcher>
+ * url - {String} Service endpoint (without the version number). E.g.
+ * "http://tms.osgeo.org/".
+ * options - {Object} Additional properties to be set on the layer. The
+ * <layername> and <type> properties must be set here.
+ */
+ initialize: function(name, url, options) {
+ var newArguments = [];
+ newArguments.push(name, url, {}, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a complete copy of this layer.
+ *
+ * Parameters:
+ * obj - {Object} Should only be provided by subclasses that call this
+ * method.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.TMS>} An exact clone of this <OpenLayers.Layer.TMS>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.TMS(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+ var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
+ var z = this.getServerZoom();
+ var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type;
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(path, url);
+ }
+ return url + path;
+ },
+
+ /**
+ * Method: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+ this.map.maxExtent.bottom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.TMS"
+});
+/* ======================================================================
+ OpenLayers/Format/WCSCapabilities.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities
+ * Read WCS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WCSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WCSCapabilities
+ * Create a new parser for WCS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of coverages.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named coverages.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WCSCapabilities/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WCSCapabilities.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities.v1
+ * Abstract class not to be instantiated directly.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WCSCapabilities.v1 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ splitSpace: (/\s+/)
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wcs",
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of coverages.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named coverages.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var raw = data;
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WCSCapabilities/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WCSCapabilities/v1.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities/v1_0_0
+ * Read WCS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WCSCapabilities.v1>
+ */
+OpenLayers.Format.WCSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.WCSCapabilities.v1, {
+
+ /**
+ * Constructor: OpenLayers.Format.WCSCapabilities.v1_0_0
+ * Create a new parser for WCS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wcs: "http://www.opengis.net/wcs",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ ows: "http://www.opengis.net/ows"
+ },
+
+ /**
+ * Property: errorProperty
+ * {String} Which property of the returned object to check for in order to
+ * determine whether or not parsing has failed. In the case that the
+ * errorProperty is undefined on the returned object, the document will be
+ * run through an OGCExceptionReport parser.
+ */
+ errorProperty: "service",
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wcs": {
+ "WCS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Service": function(node, obj) {
+ obj.service = {};
+ this.readChildNodes(node, obj.service);
+ },
+ "name": function(node, service) {
+ service.name = this.getChildValue(node);
+ },
+ "label": function(node, service) {
+ service.label = this.getChildValue(node);
+ },
+ "keywords": function(node, service) {
+ service.keywords = [];
+ this.readChildNodes(node, service.keywords);
+ },
+ "keyword": function(node, keywords) {
+ // Append the keyword to the keywords list
+ keywords.push(this.getChildValue(node));
+ },
+ "responsibleParty": function(node, service) {
+ service.responsibleParty = {};
+ this.readChildNodes(node, service.responsibleParty);
+ },
+ "individualName": function(node, responsibleParty) {
+ responsibleParty.individualName = this.getChildValue(node);
+ },
+ "organisationName": function(node, responsibleParty) {
+ responsibleParty.organisationName = this.getChildValue(node);
+ },
+ "positionName": function(node, responsibleParty) {
+ responsibleParty.positionName = this.getChildValue(node);
+ },
+ "contactInfo": function(node, responsibleParty) {
+ responsibleParty.contactInfo = {};
+ this.readChildNodes(node, responsibleParty.contactInfo);
+ },
+ "phone": function(node, contactInfo) {
+ contactInfo.phone = {};
+ this.readChildNodes(node, contactInfo.phone);
+ },
+ "voice": function(node, phone) {
+ phone.voice = this.getChildValue(node);
+ },
+ "facsimile": function(node, phone) {
+ phone.facsimile = this.getChildValue(node);
+ },
+ "address": function(node, contactInfo) {
+ contactInfo.address = {};
+ this.readChildNodes(node, contactInfo.address);
+ },
+ "deliveryPoint": function(node, address) {
+ address.deliveryPoint = this.getChildValue(node);
+ },
+ "city": function(node, address) {
+ address.city = this.getChildValue(node);
+ },
+ "postalCode": function(node, address) {
+ address.postalCode = this.getChildValue(node);
+ },
+ "country": function(node, address) {
+ address.country = this.getChildValue(node);
+ },
+ "electronicMailAddress": function(node, address) {
+ address.electronicMailAddress = this.getChildValue(node);
+ },
+ "fees": function(node, service) {
+ service.fees = this.getChildValue(node);
+ },
+ "accessConstraints": function(node, service) {
+ service.accessConstraints = this.getChildValue(node);
+ },
+ "ContentMetadata": function(node, obj) {
+ obj.contentMetadata = [];
+ this.readChildNodes(node, obj.contentMetadata);
+ },
+ "CoverageOfferingBrief": function(node, contentMetadata) {
+ var coverageOfferingBrief = {};
+ this.readChildNodes(node, coverageOfferingBrief);
+ contentMetadata.push(coverageOfferingBrief);
+ },
+ "name": function(node, coverageOfferingBrief) {
+ coverageOfferingBrief.name = this.getChildValue(node);
+ },
+ "label": function(node, coverageOfferingBrief) {
+ coverageOfferingBrief.label = this.getChildValue(node);
+ },
+ "lonLatEnvelope": function(node, coverageOfferingBrief) {
+ var nodeList = this.getElementsByTagNameNS(node, "http://www.opengis.net/gml", "pos");
+
+ // We expect two nodes here, to create the corners of a bounding box
+ if(nodeList.length == 2) {
+ var min = {};
+ var max = {};
+
+ OpenLayers.Format.GML.v3.prototype.readers["gml"].pos.apply(this, [nodeList[0], min]);
+ OpenLayers.Format.GML.v3.prototype.readers["gml"].pos.apply(this, [nodeList[1], max]);
+
+ coverageOfferingBrief.lonLatEnvelope = {};
+ coverageOfferingBrief.lonLatEnvelope.srsName = node.getAttribute("srsName");
+ coverageOfferingBrief.lonLatEnvelope.min = min.points[0];
+ coverageOfferingBrief.lonLatEnvelope.max = max.points[0];
+ }
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Strategy/Fixed.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: preload
+ * {Boolean} Load data before layer made visible. Enabling this may result
+ * in considerable overhead if your application loads many data layers
+ * that are not visible by default. Default is false.
+ */
+ preload: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Fixed
+ * Create a new Fixed strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Activate the strategy: load data or add listener to load when visible
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
+ if(activated) {
+ this.layer.events.on({
+ "refresh": this.load,
+ scope: this
+ });
+ if(this.layer.visibility == true || this.preload) {
+ this.load();
+ } else {
+ this.layer.events.on({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Undo what is done in <activate>.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "refresh": this.load,
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: load
+ * Tells protocol to load data and unhooks the visibilitychanged event
+ *
+ * Parameters:
+ * options - {Object} options to pass to protocol read.
+ */
+ load: function(options) {
+ var layer = this.layer;
+ layer.events.triggerEvent("loadstart", {filter: layer.filter});
+ layer.protocol.read(OpenLayers.Util.applyDefaults({
+ callback: this.merge,
+ filter: layer.filter,
+ scope: this
+ }, options));
+ layer.events.un({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: merge
+ * Add all features to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ var layer = this.layer;
+ layer.destroyFeatures();
+ var features = resp.features;
+ if (features && features.length > 0) {
+ var remote = layer.projection;
+ var local = layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ layer.addFeatures(features);
+ }
+ layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
+/* ======================================================================
+ OpenLayers/Control/Zoom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Zoom
+ * The Zoom control is a pair of +/- links for zooming in and out.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: zoomInText
+ * {String}
+ * Text for zoom-in link. Default is "+".
+ */
+ zoomInText: "+",
+
+ /**
+ * APIProperty: zoomInId
+ * {String}
+ * Instead of having the control create a zoom in link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomInLink" will be searched for
+ * and used if it exists.
+ */
+ zoomInId: "olZoomInLink",
+
+ /**
+ * APIProperty: zoomOutText
+ * {String}
+ * Text for zoom-out link. Default is "\u2212".
+ */
+ zoomOutText: "\u2212",
+
+ /**
+ * APIProperty: zoomOutId
+ * {String}
+ * Instead of having the control create a zoom out link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomOutLink" will be searched for
+ * and used if it exists.
+ */
+ zoomOutId: "olZoomOutLink",
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DOMElement containing the zoom links.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.prototype.draw.apply(this),
+ links = this.getOrCreateLinks(div),
+ zoomIn = links.zoomIn,
+ zoomOut = links.zoomOut,
+ eventsInstance = this.map.events;
+
+ if (zoomOut.parentNode !== div) {
+ eventsInstance = this.events;
+ eventsInstance.attachToElement(zoomOut.parentNode);
+ }
+ eventsInstance.register("buttonclick", this, this.onZoomClick);
+
+ this.zoomInLink = zoomIn;
+ this.zoomOutLink = zoomOut;
+ return div;
+ },
+
+ /**
+ * Method: getOrCreateLinks
+ *
+ * Parameters:
+ * el - {DOMElement}
+ *
+ * Return:
+ * {Object} Object with zoomIn and zoomOut properties referencing links.
+ */
+ getOrCreateLinks: function(el) {
+ var zoomIn = document.getElementById(this.zoomInId),
+ zoomOut = document.getElementById(this.zoomOutId);
+ if (!zoomIn) {
+ zoomIn = document.createElement("a");
+ zoomIn.href = "#zoomIn";
+ zoomIn.appendChild(document.createTextNode(this.zoomInText));
+ zoomIn.className = "olControlZoomIn";
+ el.appendChild(zoomIn);
+ }
+ OpenLayers.Element.addClass(zoomIn, "olButton");
+ if (!zoomOut) {
+ zoomOut = document.createElement("a");
+ zoomOut.href = "#zoomOut";
+ zoomOut.appendChild(document.createTextNode(this.zoomOutText));
+ zoomOut.className = "olControlZoomOut";
+ el.appendChild(zoomOut);
+ }
+ OpenLayers.Element.addClass(zoomOut, "olButton");
+ return {
+ zoomIn: zoomIn, zoomOut: zoomOut
+ };
+ },
+
+ /**
+ * Method: onZoomClick
+ * Called when zoomin/out link is clicked.
+ */
+ onZoomClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.zoomInLink) {
+ this.map.zoomIn();
+ } else if (button === this.zoomOutLink) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onZoomClick);
+ }
+ delete this.zoomInLink;
+ delete this.zoomOutLink;
+ OpenLayers.Control.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Zoom"
+});
+/* ======================================================================
+ OpenLayers/Layer/PointTrack.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.PointTrack
+ * Vector layer to display ordered point features as a line, creating one
+ * LineString feature for each pair of two points.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.PointTrack = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * APIProperty: dataFrom
+ * {<OpenLayers.Layer.PointTrack.TARGET_NODE>} or
+ * {<OpenLayers.Layer.PointTrack.SOURCE_NODE>} optional. If the lines
+ * should get the data/attributes from one of the two points it is
+ * composed of, which one should it be?
+ */
+ dataFrom: null,
+
+ /**
+ * APIProperty: styleFrom
+ * {<OpenLayers.Layer.PointTrack.TARGET_NODE>} or
+ * {<OpenLayers.Layer.PointTrack.SOURCE_NODE>} optional. If the lines
+ * should get the style from one of the two points it is composed of,
+ * which one should it be?
+ */
+ styleFrom: null,
+
+ /**
+ * Constructor: OpenLayers.PointTrack
+ * Constructor for a new OpenLayers.PointTrack instance.
+ *
+ * Parameters:
+ * name - {String} name of the layer
+ * options - {Object} Optional object with properties to tag onto the
+ * instance.
+ */
+
+ /**
+ * APIMethod: addNodes
+ * Adds point features that will be used to create lines from, using point
+ * pairs. The first point of a pair will be the source node, the second
+ * will be the target node.
+ *
+ * Parameters:
+ * pointFeatures - {Array(<OpenLayers.Feature>)}
+ * options - {Object}
+ *
+ * Supported options:
+ * silent - {Boolean} true to suppress (before)feature(s)added events
+ */
+ addNodes: function(pointFeatures, options) {
+ if (pointFeatures.length < 2) {
+ throw new Error("At least two point features have to be added to " +
+ "create a line from");
+ }
+
+ var lines = new Array(pointFeatures.length-1);
+
+ var pointFeature, startPoint, endPoint;
+ for(var i=0, len=pointFeatures.length; i<len; i++) {
+ pointFeature = pointFeatures[i];
+ endPoint = pointFeature.geometry;
+
+ if (!endPoint) {
+ var lonlat = pointFeature.lonlat;
+ endPoint = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
+ } else if(endPoint.CLASS_NAME != "OpenLayers.Geometry.Point") {
+ throw new TypeError("Only features with point geometries are supported.");
+ }
+
+ if(i > 0) {
+ var attributes = (this.dataFrom != null) ?
+ (pointFeatures[i+this.dataFrom].data ||
+ pointFeatures[i+this.dataFrom].attributes) :
+ null;
+ var style = (this.styleFrom != null) ?
+ (pointFeatures[i+this.styleFrom].style) :
+ null;
+ var line = new OpenLayers.Geometry.LineString([startPoint,
+ endPoint]);
+
+ lines[i-1] = new OpenLayers.Feature.Vector(line, attributes,
+ style);
+ }
+
+ startPoint = endPoint;
+ }
+
+ this.addFeatures(lines, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.PointTrack"
+});
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.SOURCE_NODE
+ * {Number} value for <OpenLayers.Layer.PointTrack.dataFrom> and
+ * <OpenLayers.Layer.PointTrack.styleFrom>
+ */
+OpenLayers.Layer.PointTrack.SOURCE_NODE = -1;
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.TARGET_NODE
+ * {Number} value for <OpenLayers.Layer.PointTrack.dataFrom> and
+ * <OpenLayers.Layer.PointTrack.styleFrom>
+ */
+OpenLayers.Layer.PointTrack.TARGET_NODE = 0;
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.dataFrom
+ * {Object} with the following keys - *deprecated*
+ * - SOURCE_NODE: take data/attributes from the source node of the line
+ * - TARGET_NODE: take data/attributes from the target node of the line
+ */
+OpenLayers.Layer.PointTrack.dataFrom = {'SOURCE_NODE': -1, 'TARGET_NODE': 0};
+/* ======================================================================
+ OpenLayers/Protocol/WFS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol>} A WFS protocol of the given version.
+ *
+ * Example:
+ * (code)
+ * var protocol = new OpenLayers.Protocol.WFS({
+ * version: "1.1.0",
+ * url: "http://demo.opengeo.org/geoserver/wfs",
+ * featureType: "tasmania_roads",
+ * featureNS: "http://www.openplans.org/topp",
+ * geometryName: "the_geom"
+ * });
+ * (end)
+ *
+ * See the protocols for specific WFS versions for more detail.
+ */
+OpenLayers.Protocol.WFS = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.WFS.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFS version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Function: fromWMSLayer
+ * Convenience function to create a WFS protocol from a WMS layer. This makes
+ * the assumption that a WFS requests can be issued at the same URL as
+ * WMS requests and that a WFS featureType exists with the same name as the
+ * WMS layer.
+ *
+ * This function is designed to auto-configure <url>, <featureType>,
+ * <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
+ * srsName matching with the WMS layer will not work with WFS 1.0.0.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} WMS layer that has a matching WFS
+ * FeatureType at the same server url with the same typename.
+ * options - {Object} Default properties to be set on the protocol.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.WFS>}
+ */
+OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
+ var typeName, featurePrefix;
+ var param = layer.params["LAYERS"];
+ var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
+ if(parts.length > 1) {
+ featurePrefix = parts[0];
+ }
+ typeName = parts.pop();
+ var protocolOptions = {
+ url: layer.url,
+ featureType: typeName,
+ featurePrefix: featurePrefix,
+ srsName: layer.projection && layer.projection.getCode() ||
+ layer.map && layer.map.getProjectionObject().getCode(),
+ version: "1.1.0"
+ };
+ return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
+ options, protocolOptions
+ ));
+};
+
+/**
+ * Constant: OpenLayers.Protocol.WFS.DEFAULTS
+ */
+OpenLayers.Protocol.WFS.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Layer/Markers.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Markers
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Markers layer is never a base layer.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: markers
+ * {Array(<OpenLayers.Marker>)} internal marker list
+ */
+ markers: null,
+
+
+ /**
+ * Property: drawn
+ * {Boolean} internal state of drawing. This is a workaround for the fact
+ * that the map does not call moveTo with a zoomChanged when the map is
+ * first starting up. This lets us catch the case where we have *never*
+ * drawn the layer, and draw it even if the zoom hasn't changed.
+ */
+ drawn: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.Markers
+ * Create a Markers layer.
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ this.markers = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.clearMarkers();
+ this.markers = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for all the markers.
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity != this.opacity) {
+ this.opacity = opacity;
+ for (var i=0, len=this.markers.length; i<len; i++) {
+ this.markers[i].setOpacity(this.opacity);
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ if (zoomChanged || !this.drawn) {
+ for(var i=0, len=this.markers.length; i<len; i++) {
+ this.drawMarker(this.markers[i]);
+ }
+ this.drawn = true;
+ }
+ },
+
+ /**
+ * APIMethod: addMarker
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker>}
+ */
+ addMarker: function(marker) {
+ this.markers.push(marker);
+
+ if (this.opacity < 1) {
+ marker.setOpacity(this.opacity);
+ }
+
+ if (this.map && this.map.getExtent()) {
+ marker.map = this.map;
+ this.drawMarker(marker);
+ }
+ },
+
+ /**
+ * APIMethod: removeMarker
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker>}
+ */
+ removeMarker: function(marker) {
+ if (this.markers && this.markers.length) {
+ OpenLayers.Util.removeItem(this.markers, marker);
+ marker.erase();
+ }
+ },
+
+ /**
+ * Method: clearMarkers
+ * This method removes all markers from a layer. The markers are not
+ * destroyed by this function, but are removed from the list of markers.
+ */
+ clearMarkers: function() {
+ if (this.markers != null) {
+ while(this.markers.length > 0) {
+ this.removeMarker(this.markers[0]);
+ }
+ }
+ },
+
+ /**
+ * Method: drawMarker
+ * Calculate the pixel location for the marker, create it, and
+ * add it to the layer's div
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker>}
+ */
+ drawMarker: function(marker) {
+ var px = this.map.getLayerPxFromLonLat(marker.lonlat);
+ if (px == null) {
+ marker.display(false);
+ } else {
+ if (!marker.isDrawn()) {
+ var markerImg = marker.draw(px);
+ this.div.appendChild(markerImg);
+ } else if(marker.icon) {
+ marker.icon.moveTo(px);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the markers.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+
+ if ( this.markers && (this.markers.length > 0)) {
+ var maxExtent = new OpenLayers.Bounds();
+ for(var i=0, len=this.markers.length; i<len; i++) {
+ var marker = this.markers[i];
+ maxExtent.extend(marker.lonlat);
+ }
+ }
+
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Markers"
+});
+/* ======================================================================
+ OpenLayers/Control/Pan.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Pan
+ * The Pan control is a single button to pan the map in one direction. For
+ * a more complete control see <OpenLayers.Control.PanPanel>.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Pan = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * APIProperty: slideFactor
+ * {Integer} Number of pixels by which we'll pan the map in any direction
+ * on clicking the arrow buttons, defaults to 50. If you want to pan
+ * by some ratio of the map dimensions, use <slideRatio> instead.
+ */
+ slideFactor: 50,
+
+ /**
+ * APIProperty: slideRatio
+ * {Number} The fraction of map width/height by which we'll pan the map
+ * on clicking the arrow buttons. Default is null. If set, will
+ * override <slideFactor>. E.g. if slideRatio is .5, then Pan Up will
+ * pan up half the map height.
+ */
+ slideRatio: null,
+
+ /**
+ * Property: direction
+ * {String} in {'North', 'South', 'East', 'West'}
+ */
+ direction: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Pan
+ * Control which handles the panning (in any of the cardinal directions)
+ * of the map by a set px distance.
+ *
+ * Parameters:
+ * direction - {String} The direction this button should pan.
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(direction, options) {
+
+ this.direction = direction;
+ this.CLASS_NAME += this.direction;
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: trigger
+ */
+ trigger: function(){
+ if (this.map) {
+ var getSlideFactor = OpenLayers.Function.bind(function (dim) {
+ return this.slideRatio ?
+ this.map.getSize()[dim] * this.slideRatio :
+ this.slideFactor;
+ }, this);
+
+ switch (this.direction) {
+ case OpenLayers.Control.Pan.NORTH:
+ this.map.pan(0, -getSlideFactor("h"));
+ break;
+ case OpenLayers.Control.Pan.SOUTH:
+ this.map.pan(0, getSlideFactor("h"));
+ break;
+ case OpenLayers.Control.Pan.WEST:
+ this.map.pan(-getSlideFactor("w"), 0);
+ break;
+ case OpenLayers.Control.Pan.EAST:
+ this.map.pan(getSlideFactor("w"), 0);
+ break;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Pan"
+});
+
+OpenLayers.Control.Pan.NORTH = "North";
+OpenLayers.Control.Pan.SOUTH = "South";
+OpenLayers.Control.Pan.EAST = "East";
+OpenLayers.Control.Pan.WEST = "West";
+/* ======================================================================
+ OpenLayers/Format/CSWGetDomain.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetDomain
+ * Default version is 2.0.2.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A CSWGetDomain format of the given version.
+ */
+OpenLayers.Format.CSWGetDomain = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.CSWGetDomain.DEFAULTS
+ );
+ var cls = OpenLayers.Format.CSWGetDomain["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported CSWGetDomain version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: DEFAULTS
+ * {Object} Default properties for the CSWGetDomain format.
+ */
+OpenLayers.Format.CSWGetDomain.DEFAULTS = {
+ "version": "2.0.2"
+};
+/* ======================================================================
+ OpenLayers/Format/CSWGetDomain/v2_0_2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/CSWGetDomain.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetDomain.v2_0_2
+ * A format for creating CSWGetDomain v2.0.2 transactions.
+ * Create a new instance with the
+ * <OpenLayers.Format.CSWGetDomain.v2_0_2> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.CSWGetDomain.v2_0_2 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ csw: "http://www.opengis.net/cat/csw/2.0.2"
+ },
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default prefix (used by Format.XML).
+ */
+ defaultPrefix: "csw",
+
+ /**
+ * Property: version
+ * {String} CSW version number.
+ */
+ version: "2.0.2",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/cat/csw/2.0.2
+ * http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd
+ */
+ schemaLocation: "http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",
+
+ /**
+ * APIProperty: PropertyName
+ * {String} Value of the csw:PropertyName element, used when
+ * writing a GetDomain document.
+ */
+ PropertyName: null,
+
+ /**
+ * APIProperty: ParameterName
+ * {String} Value of the csw:ParameterName element, used when
+ * writing a GetDomain document.
+ */
+ ParameterName: null,
+
+ /**
+ * Constructor: OpenLayers.Format.CSWGetDomain.v2_0_2
+ * A class for parsing and generating CSWGetDomain v2.0.2 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * - PropertyName
+ * - ParameterName
+ */
+
+ /**
+ * APIMethod: read
+ * Parse the response from a GetDomain request.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ this.readNode(data, obj);
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "csw": {
+ "GetDomainResponse": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "DomainValues": function(node, obj) {
+ if (!(OpenLayers.Util.isArray(obj.DomainValues))) {
+ obj.DomainValues = [];
+ }
+ var attrs = node.attributes;
+ var domainValue = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ domainValue[attrs[i].name] = attrs[i].nodeValue;
+ }
+ this.readChildNodes(node, domainValue);
+ obj.DomainValues.push(domainValue);
+ },
+ "PropertyName": function(node, obj) {
+ obj.PropertyName = this.getChildValue(node);
+ },
+ "ParameterName": function(node, obj) {
+ obj.ParameterName = this.getChildValue(node);
+ },
+ "ListOfValues": function(node, obj) {
+ if (!(OpenLayers.Util.isArray(obj.ListOfValues))) {
+ obj.ListOfValues = [];
+ }
+ this.readChildNodes(node, obj.ListOfValues);
+ },
+ "Value": function(node, obj) {
+ var attrs = node.attributes;
+ var value = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ value[attrs[i].name] = attrs[i].nodeValue;
+ }
+ value.value = this.getChildValue(node);
+ obj.push({Value: value});
+ },
+ "ConceptualScheme": function(node, obj) {
+ obj.ConceptualScheme = {};
+ this.readChildNodes(node, obj.ConceptualScheme);
+ },
+ "Name": function(node, obj) {
+ obj.Name = this.getChildValue(node);
+ },
+ "Document": function(node, obj) {
+ obj.Document = this.getChildValue(node);
+ },
+ "Authority": function(node, obj) {
+ obj.Authority = this.getChildValue(node);
+ },
+ "RangeOfValues": function(node, obj) {
+ obj.RangeOfValues = {};
+ this.readChildNodes(node, obj.RangeOfValues);
+ },
+ "MinValue": function(node, obj) {
+ var attrs = node.attributes;
+ var value = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ value[attrs[i].name] = attrs[i].nodeValue;
+ }
+ value.value = this.getChildValue(node);
+ obj.MinValue = value;
+ },
+ "MaxValue": function(node, obj) {
+ var attrs = node.attributes;
+ var value = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ value[attrs[i].name] = attrs[i].nodeValue;
+ }
+ value.value = this.getChildValue(node);
+ obj.MaxValue = value;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: write
+ * Given an configuration js object, write a CSWGetDomain request.
+ *
+ * Parameters:
+ * options - {Object} A object mapping the request.
+ *
+ * Returns:
+ * {String} A serialized CSWGetDomain request.
+ */
+ write: function(options) {
+ var node = this.writeNode("csw:GetDomain", options);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "csw": {
+ "GetDomain": function(options) {
+ var node = this.createElementNSPlus("csw:GetDomain", {
+ attributes: {
+ service: "CSW",
+ version: this.version
+ }
+ });
+ if (options.PropertyName || this.PropertyName) {
+ this.writeNode(
+ "csw:PropertyName",
+ options.PropertyName || this.PropertyName,
+ node
+ );
+ } else if (options.ParameterName || this.ParameterName) {
+ this.writeNode(
+ "csw:ParameterName",
+ options.ParameterName || this.ParameterName,
+ node
+ );
+ }
+ this.readChildNodes(node, options);
+ return node;
+ },
+ "PropertyName": function(value) {
+ var node = this.createElementNSPlus("csw:PropertyName", {
+ value: value
+ });
+ return node;
+ },
+ "ParameterName": function(value) {
+ var node = this.createElementNSPlus("csw:ParameterName", {
+ value: value
+ });
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.CSWGetDomain.v2_0_2"
+});
+/* ======================================================================
+ OpenLayers/Format/ArcXML/Features.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/ArcXML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.ArcXML.Features
+ * Read/Write ArcXML features. Create a new instance with the
+ * <OpenLayers.Format.ArcXML.Features> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.ArcXML.Features = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Constructor: OpenLayers.Format.ArcXML.Features
+ * Create a new parser/writer for ArcXML Features. Create an instance of this class
+ * to get a set of features from an ArcXML response.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read data from a string of ArcXML, and return a set of OpenLayers features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} A collection of features.
+ */
+ read: function(data) {
+ var axl = new OpenLayers.Format.ArcXML();
+ var parsed = axl.read(data);
+
+ return parsed.features.feature;
+ }
+});
+/* ======================================================================
+ OpenLayers/Control/Snapping.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Snapping
+ * Acts as a snapping agent while editing vector features.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Snapping = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforesnap - Triggered before a snap occurs. Listeners receive an
+ * event object with *point*, *x*, *y*, *distance*, *layer*, and
+ * *snapType* properties. The point property will be original point
+ * geometry considered for snapping. The x and y properties represent
+ * coordinates the point will receive. The distance is the distance
+ * of the snap. The layer is the target layer. The snapType property
+ * will be one of "node", "vertex", or "edge". Return false to stop
+ * snapping from occurring.
+ * snap - Triggered when a snap occurs. Listeners receive an event with
+ * *point*, *snapType*, *layer*, and *distance* properties. The point
+ * will be the location snapped to. The snapType will be one of "node",
+ * "vertex", or "edge". The layer will be the target layer. The
+ * distance will be the distance of the snap in map units.
+ * unsnap - Triggered when a vertex is unsnapped. Listeners receive an
+ * event with a *point* property.
+ */
+
+ /**
+ * CONSTANT: DEFAULTS
+ * Default target properties.
+ */
+ DEFAULTS: {
+ tolerance: 10,
+ node: true,
+ edge: true,
+ vertex: true
+ },
+
+ /**
+ * Property: greedy
+ * {Boolean} Snap to closest feature in first layer with an eligible
+ * feature. Default is true.
+ */
+ greedy: true,
+
+ /**
+ * Property: precedence
+ * {Array} List representing precedence of different snapping types.
+ * Default is "node", "vertex", "edge".
+ */
+ precedence: ["node", "vertex", "edge"],
+
+ /**
+ * Property: resolution
+ * {Float} The map resolution for the previously considered snap.
+ */
+ resolution: null,
+
+ /**
+ * Property: geoToleranceCache
+ * {Object} A cache of geo-tolerances. Tolerance values (in map units) are
+ * calculated when the map resolution changes.
+ */
+ geoToleranceCache: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The current editable layer. Set at
+ * construction or after construction with <setLayer>.
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The current editable feature.
+ */
+ feature: null,
+
+ /**
+ * Property: point
+ * {<OpenLayers.Geometry.Point>} The currently snapped vertex.
+ */
+ point: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Snapping
+ * Creates a new snapping control. A control is constructed with an editable
+ * layer and a set of configuration objects for target layers. While the
+ * control is active, dragging vertices while drawing new features or
+ * modifying existing features on the editable layer will engage
+ * snapping to features on the target layers. Whether a vertex snaps to
+ * a feature on a target layer depends on the target layer configuration.
+ *
+ * Parameters:
+ * options - {Object} An object containing all configuration properties for
+ * the control.
+ *
+ * Valid options:
+ * layer - {<OpenLayers.Layer.Vector>} The editable layer. Features from this
+ * layer that are digitized or modified may have vertices snapped to
+ * features from any of the target layers.
+ * targets - {Array(Object | OpenLayers.Layer.Vector)} A list of objects for
+ * configuring target layers. See valid properties of the target
+ * objects below. If the items in the targets list are vector layers
+ * (instead of configuration objects), the defaults from the <defaults>
+ * property will apply. The editable layer itself may be a target
+ * layer, allowing newly created or edited features to be snapped to
+ * existing features from the same layer. If no targets are provided
+ * the layer given in the constructor (as <layer>) will become the
+ * initial target.
+ * defaults - {Object} An object with default properties to be applied
+ * to all target objects.
+ * greedy - {Boolean} Snap to closest feature in first target layer that
+ * applies. Default is true. If false, all features in all target
+ * layers will be checked and the closest feature in all target layers
+ * will be chosen. The greedy property determines if the order of the
+ * target layers is significant. By default, the order of the target
+ * layers is significant where layers earlier in the target layer list
+ * have precedence over layers later in the list. Within a single
+ * layer, the closest feature is always chosen for snapping. This
+ * property only determines whether the search for a closer feature
+ * continues after an eligible feature is found in a target layer.
+ *
+ * Valid target properties:
+ * layer - {<OpenLayers.Layer.Vector>} A target layer. Features from this
+ * layer will be eligible to act as snapping target for the editable
+ * layer.
+ * tolerance - {Float} The distance (in pixels) at which snapping may occur.
+ * Default is 10.
+ * node - {Boolean} Snap to nodes (first or last point in a geometry) in
+ * target layer. Default is true.
+ * nodeTolerance - {Float} Optional distance at which snapping may occur
+ * for nodes specifically. If none is provided, <tolerance> will be
+ * used.
+ * vertex - {Boolean} Snap to vertices in target layer. Default is true.
+ * vertexTolerance - {Float} Optional distance at which snapping may occur
+ * for vertices specifically. If none is provided, <tolerance> will be
+ * used.
+ * edge - {Boolean} Snap to edges in target layer. Default is true.
+ * edgeTolerance - {Float} Optional distance at which snapping may occur
+ * for edges specifically. If none is provided, <tolerance> will be
+ * used.
+ * filter - {<OpenLayers.Filter>} Optional filter to evaluate to determine if
+ * feature is eligible for snapping. If filter evaluates to true for a
+ * target feature a vertex may be snapped to the feature.
+ * minResolution - {Number} If a minResolution is provided, snapping to this
+ * target will only be considered if the map resolution is greater than
+ * or equal to this value (the minResolution is inclusive). Default is
+ * no minimum resolution limit.
+ * maxResolution - {Number} If a maxResolution is provided, snapping to this
+ * target will only be considered if the map resolution is strictly
+ * less than this value (the maxResolution is exclusive). Default is
+ * no maximum resolution limit.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.options = options || {}; // TODO: this could be done by the super
+
+ // set the editable layer if provided
+ if(this.options.layer) {
+ this.setLayer(this.options.layer);
+ }
+ // configure target layers
+ var defaults = OpenLayers.Util.extend({}, this.options.defaults);
+ this.defaults = OpenLayers.Util.applyDefaults(defaults, this.DEFAULTS);
+ this.setTargets(this.options.targets);
+ if(this.targets.length === 0 && this.layer) {
+ this.addTargetLayer(this.layer);
+ }
+
+ this.geoToleranceCache = {};
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Set the editable layer. Call the setLayer method if the editable layer
+ * changes and the same control should be used on a new editable layer.
+ * If the control is already active, it will be active after the new
+ * layer is set.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The new editable layer.
+ */
+ setLayer: function(layer) {
+ if(this.active) {
+ this.deactivate();
+ this.layer = layer;
+ this.activate();
+ } else {
+ this.layer = layer;
+ }
+ },
+
+ /**
+ * Method: setTargets
+ * Set the targets for the snapping agent.
+ *
+ * Parameters:
+ * targets - {Array} An array of target configs or target layers.
+ */
+ setTargets: function(targets) {
+ this.targets = [];
+ if(targets && targets.length) {
+ var target;
+ for(var i=0, len=targets.length; i<len; ++i) {
+ target = targets[i];
+ if(target instanceof OpenLayers.Layer.Vector) {
+ this.addTargetLayer(target);
+ } else {
+ this.addTarget(target);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: addTargetLayer
+ * Add a target layer with the default target config.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} A target layer.
+ */
+ addTargetLayer: function(layer) {
+ this.addTarget({layer: layer});
+ },
+
+ /**
+ * Method: addTarget
+ * Add a configured target layer.
+ *
+ * Parameters:
+ * target - {Object} A target config.
+ */
+ addTarget: function(target) {
+ target = OpenLayers.Util.applyDefaults(target, this.defaults);
+ target.nodeTolerance = target.nodeTolerance || target.tolerance;
+ target.vertexTolerance = target.vertexTolerance || target.tolerance;
+ target.edgeTolerance = target.edgeTolerance || target.tolerance;
+ this.targets.push(target);
+ },
+
+ /**
+ * Method: removeTargetLayer
+ * Remove a target layer.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The target layer to remove.
+ */
+ removeTargetLayer: function(layer) {
+ var target;
+ for(var i=this.targets.length-1; i>=0; --i) {
+ target = this.targets[i];
+ if(target.layer === layer) {
+ this.removeTarget(target);
+ }
+ }
+ },
+
+ /**
+ * Method: removeTarget
+ * Remove a target.
+ *
+ * Parameters:
+ * target - {Object} A target config.
+ *
+ * Returns:
+ * {Array} The targets array.
+ */
+ removeTarget: function(target) {
+ return OpenLayers.Util.removeItem(this.targets, target);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. Activating the control registers listeners for
+ * editing related events so that during feature creation and
+ * modification, moving vertices will trigger snapping.
+ */
+ activate: function() {
+ var activated = OpenLayers.Control.prototype.activate.call(this);
+ if(activated) {
+ if(this.layer && this.layer.events) {
+ this.layer.events.on({
+ sketchstarted: this.onSketchModified,
+ sketchmodified: this.onSketchModified,
+ vertexmodified: this.onVertexModified,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. Deactivating the control unregisters listeners
+ * so feature editing may proceed without engaging the snapping agent.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
+ if(deactivated) {
+ if(this.layer && this.layer.events) {
+ this.layer.events.un({
+ sketchstarted: this.onSketchModified,
+ sketchmodified: this.onSketchModified,
+ vertexmodified: this.onVertexModified,
+ scope: this
+ });
+ }
+ }
+ this.feature = null;
+ this.point = null;
+ return deactivated;
+ },
+
+ /**
+ * Method: onSketchModified
+ * Registered as a listener for the sketchmodified event on the editable
+ * layer.
+ *
+ * Parameters:
+ * event - {Object} The sketch modified event.
+ */
+ onSketchModified: function(event) {
+ this.feature = event.feature;
+ this.considerSnapping(event.vertex, event.vertex);
+ },
+
+ /**
+ * Method: onVertexModified
+ * Registered as a listener for the vertexmodified event on the editable
+ * layer.
+ *
+ * Parameters:
+ * event - {Object} The vertex modified event.
+ */
+ onVertexModified: function(event) {
+ this.feature = event.feature;
+ var loc = this.layer.map.getLonLatFromViewPortPx(event.pixel);
+ this.considerSnapping(
+ event.vertex, new OpenLayers.Geometry.Point(loc.lon, loc.lat)
+ );
+ },
+
+ /**
+ * Method: considerSnapping
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The vertex to be snapped (or
+ * unsnapped).
+ * loc - {<OpenLayers.Geometry.Point>} The location of the mouse in map
+ * coords.
+ */
+ considerSnapping: function(point, loc) {
+ var best = {
+ rank: Number.POSITIVE_INFINITY,
+ dist: Number.POSITIVE_INFINITY,
+ x: null, y: null
+ };
+ var snapped = false;
+ var result, target;
+ for(var i=0, len=this.targets.length; i<len; ++i) {
+ target = this.targets[i];
+ result = this.testTarget(target, loc);
+ if(result) {
+ if(this.greedy) {
+ best = result;
+ best.target = target;
+ snapped = true;
+ break;
+ } else {
+ if((result.rank < best.rank) ||
+ (result.rank === best.rank && result.dist < best.dist)) {
+ best = result;
+ best.target = target;
+ snapped = true;
+ }
+ }
+ }
+ }
+ if(snapped) {
+ var proceed = this.events.triggerEvent("beforesnap", {
+ point: point, x: best.x, y: best.y, distance: best.dist,
+ layer: best.target.layer, snapType: this.precedence[best.rank]
+ });
+ if(proceed !== false) {
+ point.x = best.x;
+ point.y = best.y;
+ this.point = point;
+ this.events.triggerEvent("snap", {
+ point: point,
+ snapType: this.precedence[best.rank],
+ layer: best.target.layer,
+ distance: best.dist
+ });
+ } else {
+ snapped = false;
+ }
+ }
+ if(this.point && !snapped) {
+ point.x = loc.x;
+ point.y = loc.y;
+ this.point = null;
+ this.events.triggerEvent("unsnap", {point: point});
+ }
+ },
+
+ /**
+ * Method: testTarget
+ *
+ * Parameters:
+ * target - {Object} Object with target layer configuration.
+ * loc - {<OpenLayers.Geometry.Point>} The location of the mouse in map
+ * coords.
+ *
+ * Returns:
+ * {Object} A result object with rank, dist, x, and y properties.
+ * Returns null if candidate is not eligible for snapping.
+ */
+ testTarget: function(target, loc) {
+ var resolution = this.layer.map.getResolution();
+ if ("minResolution" in target) {
+ if (resolution < target.minResolution) {
+ return null;
+ }
+ }
+ if ("maxResolution" in target) {
+ if (resolution >= target.maxResolution) {
+ return null;
+ }
+ }
+ var tolerance = {
+ node: this.getGeoTolerance(target.nodeTolerance, resolution),
+ vertex: this.getGeoTolerance(target.vertexTolerance, resolution),
+ edge: this.getGeoTolerance(target.edgeTolerance, resolution)
+ };
+ // this could be cached if we don't support setting tolerance values directly
+ var maxTolerance = Math.max(
+ tolerance.node, tolerance.vertex, tolerance.edge
+ );
+ var result = {
+ rank: Number.POSITIVE_INFINITY, dist: Number.POSITIVE_INFINITY
+ };
+ var eligible = false;
+ var features = target.layer.features;
+ var feature, type, vertices, vertex, closest, dist, found;
+ var numTypes = this.precedence.length;
+ var ll = new OpenLayers.LonLat(loc.x, loc.y);
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ if(feature !== this.feature && !feature._sketch &&
+ feature.state !== OpenLayers.State.DELETE &&
+ (!target.filter || target.filter.evaluate(feature))) {
+ if(feature.atPoint(ll, maxTolerance, maxTolerance)) {
+ for(var j=0, stop=Math.min(result.rank+1, numTypes); j<stop; ++j) {
+ type = this.precedence[j];
+ if(target[type]) {
+ if(type === "edge") {
+ closest = feature.geometry.distanceTo(loc, {details: true});
+ dist = closest.distance;
+ if(dist <= tolerance[type] && dist < result.dist) {
+ result = {
+ rank: j, dist: dist,
+ x: closest.x0, y: closest.y0 // closest coords on feature
+ };
+ eligible = true;
+ // don't look for lower precedence types for this feature
+ break;
+ }
+ } else {
+ // look for nodes or vertices
+ vertices = feature.geometry.getVertices(type === "node");
+ found = false;
+ for(var k=0, klen=vertices.length; k<klen; ++k) {
+ vertex = vertices[k];
+ dist = vertex.distanceTo(loc);
+ if(dist <= tolerance[type] &&
+ (j < result.rank || (j === result.rank && dist < result.dist))) {
+ result = {
+ rank: j, dist: dist,
+ x: vertex.x, y: vertex.y
+ };
+ eligible = true;
+ found = true;
+ }
+ }
+ if(found) {
+ // don't look for lower precedence types for this feature
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return eligible ? result : null;
+ },
+
+ /**
+ * Method: getGeoTolerance
+ * Calculate a tolerance in map units given a tolerance in pixels. This
+ * takes advantage of the <geoToleranceCache> when the map resolution
+ * has not changed.
+ *
+ * Parameters:
+ * tolerance - {Number} A tolerance value in pixels.
+ * resolution - {Number} Map resolution.
+ *
+ * Returns:
+ * {Number} A tolerance value in map units.
+ */
+ getGeoTolerance: function(tolerance, resolution) {
+ if(resolution !== this.resolution) {
+ this.resolution = resolution;
+ this.geoToleranceCache = {};
+ }
+ var geoTolerance = this.geoToleranceCache[tolerance];
+ if(geoTolerance === undefined) {
+ geoTolerance = tolerance * resolution;
+ this.geoToleranceCache[tolerance] = geoTolerance;
+ }
+ return geoTolerance;
+ },
+
+ /**
+ * Method: destroy
+ * Clean up the control.
+ */
+ destroy: function() {
+ if(this.active) {
+ this.deactivate(); // TODO: this should be handled by the super
+ }
+ delete this.layer;
+ delete this.targets;
+ OpenLayers.Control.prototype.destroy.call(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Snapping"
+});
+/* ======================================================================
+ OpenLayers/Format/OWSCommon/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/OWSCommon/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon.v1_1_0
+ * Parser for OWS Common version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.OWSCommon.v1>
+ */
+OpenLayers.Format.OWSCommon.v1_1_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ows": OpenLayers.Util.applyDefaults({
+ "ExceptionReport": function(node, obj) {
+ obj.exceptionReport = {
+ version: node.getAttribute('version'),
+ language: node.getAttribute('xml:lang'),
+ exceptions: []
+ };
+ this.readChildNodes(node, obj.exceptionReport);
+ },
+ "AllowedValues": function(node, parameter) {
+ parameter.allowedValues = {};
+ this.readChildNodes(node, parameter.allowedValues);
+ },
+ "AnyValue": function(node, parameter) {
+ parameter.anyValue = true;
+ },
+ "DataType": function(node, parameter) {
+ parameter.dataType = this.getChildValue(node);
+ },
+ "Range": function(node, allowedValues) {
+ allowedValues.range = {};
+ this.readChildNodes(node, allowedValues.range);
+ },
+ "MinimumValue": function(node, range) {
+ range.minValue = this.getChildValue(node);
+ },
+ "MaximumValue": function(node, range) {
+ range.maxValue = this.getChildValue(node);
+ },
+ "Identifier": function(node, obj) {
+ obj.identifier = this.getChildValue(node);
+ },
+ "SupportedCRS": function(node, obj) {
+ obj.supportedCRS = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"])
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ows": OpenLayers.Util.applyDefaults({
+ "Range": function(range) {
+ var node = this.createElementNSPlus("ows:Range", {
+ attributes: {
+ 'ows:rangeClosure': range.closure
+ }
+ });
+ this.writeNode("ows:MinimumValue", range.minValue, node);
+ this.writeNode("ows:MaximumValue", range.maxValue, node);
+ return node;
+ },
+ "MinimumValue": function(minValue) {
+ var node = this.createElementNSPlus("ows:MinimumValue", {
+ value: minValue
+ });
+ return node;
+ },
+ "MaximumValue": function(maxValue) {
+ var node = this.createElementNSPlus("ows:MaximumValue", {
+ value: maxValue
+ });
+ return node;
+ },
+ "Value": function(value) {
+ var node = this.createElementNSPlus("ows:Value", {
+ value: value
+ });
+ return node;
+ }
+ }, OpenLayers.Format.OWSCommon.v1.prototype.writers["ows"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_1_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WCSGetCoverage.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSGetCoverage version 1.1.0
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WCSGetCoverage = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ wcs: "http://www.opengis.net/wcs/1.1",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1.2
+ */
+ VERSION: "1.1.2",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/wcs/1.1 http://schemas.opengis.net/wcs/1.1/wcsGetCoverage.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.WCSGetCoverage
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} A WCS GetCoverage request XML string.
+ */
+ write: function(options) {
+ var node = this.writeNode("wcs:GetCoverage", options);
+ this.setAttributeNS(
+ node, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wcs": {
+ "GetCoverage": function(options) {
+ var node = this.createElementNSPlus("wcs:GetCoverage", {
+ attributes: {
+ version: options.version || this.VERSION,
+ service: 'WCS'
+ }
+ });
+ this.writeNode("ows:Identifier", options.identifier, node);
+ this.writeNode("wcs:DomainSubset", options.domainSubset, node);
+ this.writeNode("wcs:Output", options.output, node);
+ return node;
+ },
+ "DomainSubset": function(domainSubset) {
+ var node = this.createElementNSPlus("wcs:DomainSubset", {});
+ this.writeNode("ows:BoundingBox", domainSubset.boundingBox, node);
+ if (domainSubset.temporalSubset) {
+ this.writeNode("wcs:TemporalSubset", domainSubset.temporalSubset, node);
+ }
+ return node;
+ },
+ "TemporalSubset": function(temporalSubset) {
+ var node = this.createElementNSPlus("wcs:TemporalSubset", {});
+ for (var i=0, len=temporalSubset.timePeriods.length; i<len; ++i) {
+ this.writeNode("wcs:TimePeriod", temporalSubset.timePeriods[i], node);
+ }
+ return node;
+ },
+ "TimePeriod": function(timePeriod) {
+ var node = this.createElementNSPlus("wcs:TimePeriod", {});
+ this.writeNode("wcs:BeginPosition", timePeriod.begin, node);
+ this.writeNode("wcs:EndPosition", timePeriod.end, node);
+ if (timePeriod.resolution) {
+ this.writeNode("wcs:TimeResolution", timePeriod.resolution, node);
+ }
+ return node;
+ },
+ "BeginPosition": function(begin) {
+ var node = this.createElementNSPlus("wcs:BeginPosition", {
+ value: begin
+ });
+ return node;
+ },
+ "EndPosition": function(end) {
+ var node = this.createElementNSPlus("wcs:EndPosition", {
+ value: end
+ });
+ return node;
+ },
+ "TimeResolution": function(resolution) {
+ var node = this.createElementNSPlus("wcs:TimeResolution", {
+ value: resolution
+ });
+ return node;
+ },
+ "Output": function(output) {
+ var node = this.createElementNSPlus("wcs:Output", {
+ attributes: {
+ format: output.format,
+ store: output.store
+ }
+ });
+ if (output.gridCRS) {
+ this.writeNode("wcs:GridCRS", output.gridCRS, node);
+ }
+ return node;
+ },
+ "GridCRS": function(gridCRS) {
+ var node = this.createElementNSPlus("wcs:GridCRS", {});
+ this.writeNode("wcs:GridBaseCRS", gridCRS.baseCRS, node);
+ if (gridCRS.type) {
+ this.writeNode("wcs:GridType", gridCRS.type, node);
+ }
+ if (gridCRS.origin) {
+ this.writeNode("wcs:GridOrigin", gridCRS.origin, node);
+ }
+ this.writeNode("wcs:GridOffsets", gridCRS.offsets, node);
+ if (gridCRS.CS) {
+ this.writeNode("wcs:GridCS", gridCRS.CS, node);
+ }
+ return node;
+ },
+ "GridBaseCRS": function(baseCRS) {
+ return this.createElementNSPlus("wcs:GridBaseCRS", {
+ value: baseCRS
+ });
+ },
+ "GridOrigin": function(origin) {
+ return this.createElementNSPlus("wcs:GridOrigin", {
+ value: origin
+ });
+ },
+ "GridType": function(type) {
+ return this.createElementNSPlus("wcs:GridType", {
+ value: type
+ });
+ },
+ "GridOffsets": function(offsets) {
+ return this.createElementNSPlus("wcs:GridOffsets", {
+ value: offsets
+ });
+ },
+ "GridCS": function(CS) {
+ return this.createElementNSPlus("wcs:GridCS", {
+ value: CS
+ });
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSGetCoverage"
+
+});
+/* ======================================================================
+ OpenLayers/Format/KML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Date.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.KML
+ * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ kml: "http://www.opengis.net/kml/2.2",
+ gx: "http://www.google.com/kml/ext/2.2"
+ },
+
+ /**
+ * APIProperty: kmlns
+ * {String} KML Namespace to use. Defaults to 2.0 namespace.
+ */
+ kmlns: "http://earth.google.com/kml/2.0",
+
+ /**
+ * APIProperty: placemarksDesc
+ * {String} Name of the placemarks. Default is "No description available".
+ */
+ placemarksDesc: "No description available",
+
+ /**
+ * APIProperty: foldersName
+ * {String} Name of the folders. Default is "OpenLayers export".
+ * If set to null, no name element will be created.
+ */
+ foldersName: "OpenLayers export",
+
+ /**
+ * APIProperty: foldersDesc
+ * {String} Description of the folders. Default is "Exported on [date]."
+ * If set to null, no description element will be created.
+ */
+ foldersDesc: "Exported on " + new Date(),
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from KML. Default is true.
+ * Extracting styleUrls requires this to be set to true
+ * Note that currently only Data and SimpleData
+ * elements are handled.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: kvpAttributes
+ * {Boolean} Only used if extractAttributes is true.
+ * If set to true, attributes will be simple
+ * key-value pairs, compatible with other formats,
+ * Any displayName elements will be ignored.
+ * If set to false, attributes will be objects,
+ * retaining any displayName elements, but not
+ * compatible with other formats. Any CDATA in
+ * displayName will be read in as a string value.
+ * Default is false.
+ */
+ kvpAttributes: false,
+
+ /**
+ * Property: extractStyles
+ * {Boolean} Extract styles from KML. Default is false.
+ * Extracting styleUrls also requires extractAttributes to be
+ * set to true
+ */
+ extractStyles: false,
+
+ /**
+ * APIProperty: extractTracks
+ * {Boolean} Extract gx:Track elements from Placemark elements. Default
+ * is false. If true, features will be generated for all points in
+ * all gx:Track elements. Features will have a when (Date) attribute
+ * based on when elements in the track. If tracks include angle
+ * elements, features will have heading, tilt, and roll attributes.
+ * If track point coordinates have three values, features will have
+ * an altitude attribute with the third coordinate value.
+ */
+ extractTracks: false,
+
+ /**
+ * APIProperty: trackAttributes
+ * {Array} If <extractTracks> is true, points within gx:Track elements will
+ * be parsed as features with when, heading, tilt, and roll attributes.
+ * Any additional attribute names can be provided in <trackAttributes>.
+ */
+ trackAttributes: null,
+
+ /**
+ * Property: internalns
+ * {String} KML Namespace to use -- defaults to the namespace of the
+ * Placemark node being parsed, but falls back to kmlns.
+ */
+ internalns: null,
+
+ /**
+ * Property: features
+ * {Array} Array of features
+ *
+ */
+ features: null,
+
+ /**
+ * Property: styles
+ * {Object} Storage of style objects
+ *
+ */
+ styles: null,
+
+ /**
+ * Property: styleBaseUrl
+ * {String}
+ */
+ styleBaseUrl: "",
+
+ /**
+ * Property: fetched
+ * {Object} Storage of KML URLs that have been fetched before
+ * in order to prevent reloading them.
+ */
+ fetched: null,
+
+ /**
+ * APIProperty: maxDepth
+ * {Integer} Maximum depth for recursive loading external KML URLs
+ * Defaults to 0: do no external fetching
+ */
+ maxDepth: 0,
+
+ /**
+ * Constructor: OpenLayers.Format.KML
+ * Create a new parser for KML.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // compile regular expressions once instead of every time they are used
+ this.regExes = {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g),
+ kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
+ kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
+ straightBracket: (/\$\[(.*?)\]/g)
+ };
+ // KML coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features.
+ */
+ read: function(data) {
+ this.features = [];
+ this.styles = {};
+ this.fetched = {};
+
+ // Set default options
+ var options = {
+ depth: 0,
+ styleBaseUrl: this.styleBaseUrl
+ };
+
+ return this.parseData(data, options);
+ },
+
+ /**
+ * Method: parseData
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features.
+ */
+ parseData: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+
+ // Loop throught the following node types in this order and
+ // process the nodes found
+ var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
+ for(var i=0, len=types.length; i<len; ++i) {
+ var type = types[i];
+
+ var nodes = this.getElementsByTagNameNS(data, "*", type);
+
+ // skip to next type if no nodes are found
+ if(nodes.length == 0) {
+ continue;
+ }
+
+ switch (type.toLowerCase()) {
+
+ // Fetch external links
+ case "link":
+ case "networklink":
+ this.parseLinks(nodes, options);
+ break;
+
+ // parse style information
+ case "style":
+ if (this.extractStyles) {
+ this.parseStyles(nodes, options);
+ }
+ break;
+ case "stylemap":
+ if (this.extractStyles) {
+ this.parseStyleMaps(nodes, options);
+ }
+ break;
+
+ // parse features
+ case "placemark":
+ this.parseFeatures(nodes, options);
+ break;
+ }
+ }
+
+ return this.features;
+ },
+
+ /**
+ * Method: parseLinks
+ * Finds URLs of linked KML documents and fetches them
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseLinks: function(nodes, options) {
+
+ // Fetch external links <NetworkLink> and <Link>
+ // Don't do anything if we have reached our maximum depth for recursion
+ if (options.depth >= this.maxDepth) {
+ return false;
+ }
+
+ // increase depth
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var href = this.parseProperty(nodes[i], "*", "href");
+ if(href && !this.fetched[href]) {
+ this.fetched[href] = true; // prevent reloading the same urls
+ var data = this.fetchLink(href);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Method: fetchLink
+ * Fetches a URL and returns the result
+ *
+ * Parameters:
+ * href - {String} url to be fetched
+ *
+ */
+ fetchLink: function(href) {
+ var request = OpenLayers.Request.GET({url: href, async: false});
+ if (request) {
+ return request.responseText;
+ }
+ },
+
+ /**
+ * Method: parseStyles
+ * Parses <Style> nodes
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyles: function(nodes, options) {
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var style = this.parseStyle(nodes[i]);
+ if(style) {
+ var styleName = (options.styleBaseUrl || "") + "#" + style.id;
+
+ this.styles[styleName] = style;
+ }
+ }
+ },
+
+ /**
+ * Method: parseKmlColor
+ * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
+ * color and opacity or null if the color is invalid.
+ *
+ * Parameters:
+ * kmlColor - {String} a kml formated color
+ *
+ * Returns:
+ * {Object}
+ */
+ parseKmlColor: function(kmlColor) {
+ var color = null;
+ if (kmlColor) {
+ var matches = kmlColor.match(this.regExes.kmlColor);
+ if (matches) {
+ color = {
+ color: '#' + matches[4] + matches[3] + matches[2],
+ opacity: parseInt(matches[1], 16) / 255
+ };
+ }
+ }
+ return color;
+ },
+
+ /**
+ * Method: parseStyle
+ * Parses the children of a <Style> node and builds the style hash
+ * accordingly
+ *
+ * Parameters:
+ * node - {DOMElement} <Style> node
+ *
+ */
+ parseStyle: function(node) {
+ var style = {};
+
+ var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle",
+ "LabelStyle"];
+ var type, styleTypeNode, nodeList, geometry, parser;
+ for(var i=0, len=types.length; i<len; ++i) {
+ type = types[i];
+ styleTypeNode = this.getElementsByTagNameNS(node, "*", type)[0];
+ if(!styleTypeNode) {
+ continue;
+ }
+
+ // only deal with first geometry of this type
+ switch (type.toLowerCase()) {
+ case "linestyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["strokeColor"] = color.color;
+ style["strokeOpacity"] = color.opacity;
+ }
+
+ var width = this.parseProperty(styleTypeNode, "*", "width");
+ if (width) {
+ style["strokeWidth"] = width;
+ }
+ break;
+
+ case "polystyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fillOpacity"] = color.opacity;
+ style["fillColor"] = color.color;
+ }
+ // Check if fill is disabled
+ var fill = this.parseProperty(styleTypeNode, "*", "fill");
+ if (fill == "0") {
+ style["fillColor"] = "none";
+ }
+ // Check if outline is disabled
+ var outline = this.parseProperty(styleTypeNode, "*", "outline");
+ if (outline == "0") {
+ style["strokeWidth"] = "0";
+ }
+
+ break;
+
+ case "iconstyle":
+ // set scale
+ var scale = parseFloat(this.parseProperty(styleTypeNode,
+ "*", "scale") || 1);
+
+ // set default width and height of icon
+ var width = 32 * scale;
+ var height = 32 * scale;
+
+ var iconNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "Icon")[0];
+ if (iconNode) {
+ var href = this.parseProperty(iconNode, "*", "href");
+ if (href) {
+
+ var w = this.parseProperty(iconNode, "*", "w");
+ var h = this.parseProperty(iconNode, "*", "h");
+
+ // Settings for Google specific icons that are 64x64
+ // We set the width and height to 64 and halve the
+ // scale to prevent icons from being too big
+ var google = "http://maps.google.com/mapfiles/kml";
+ if (OpenLayers.String.startsWith(
+ href, google) && !w && !h) {
+ w = 64;
+ h = 64;
+ scale = scale / 2;
+ }
+
+ // if only dimension is defined, make sure the
+ // other one has the same value
+ w = w || h;
+ h = h || w;
+
+ if (w) {
+ width = parseInt(w) * scale;
+ }
+
+ if (h) {
+ height = parseInt(h) * scale;
+ }
+
+ // support for internal icons
+ // (/root://icons/palette-x.png)
+ // x and y tell the position on the palette:
+ // - in pixels
+ // - starting from the left bottom
+ // We translate that to a position in the list
+ // and request the appropriate icon from the
+ // google maps website
+ var matches = href.match(this.regExes.kmlIconPalette);
+ if (matches) {
+ var palette = matches[1];
+ var file_extension = matches[2];
+
+ var x = this.parseProperty(iconNode, "*", "x");
+ var y = this.parseProperty(iconNode, "*", "y");
+
+ var posX = x ? x/32 : 0;
+ var posY = y ? (7 - y/32) : 7;
+
+ var pos = posY * 8 + posX;
+ href = "http://maps.google.com/mapfiles/kml/pal"
+ + palette + "/icon" + pos + file_extension;
+ }
+
+ style["graphicOpacity"] = 1; // fully opaque
+ style["externalGraphic"] = href;
+ }
+
+ }
+
+
+ // hotSpots define the offset for an Icon
+ var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "hotSpot")[0];
+ if (hotSpotNode) {
+ var x = parseFloat(hotSpotNode.getAttribute("x"));
+ var y = parseFloat(hotSpotNode.getAttribute("y"));
+
+ var xUnits = hotSpotNode.getAttribute("xunits");
+ if (xUnits == "pixels") {
+ style["graphicXOffset"] = -x * scale;
+ }
+ else if (xUnits == "insetPixels") {
+ style["graphicXOffset"] = -width + (x * scale);
+ }
+ else if (xUnits == "fraction") {
+ style["graphicXOffset"] = -width * x;
+ }
+
+ var yUnits = hotSpotNode.getAttribute("yunits");
+ if (yUnits == "pixels") {
+ style["graphicYOffset"] = -height + (y * scale) + 1;
+ }
+ else if (yUnits == "insetPixels") {
+ style["graphicYOffset"] = -(y * scale) + 1;
+ }
+ else if (yUnits == "fraction") {
+ style["graphicYOffset"] = -height * (1 - y) + 1;
+ }
+ }
+
+ style["graphicWidth"] = width;
+ style["graphicHeight"] = height;
+ break;
+
+ case "balloonstyle":
+ var balloonStyle = OpenLayers.Util.getXmlNodeValue(
+ styleTypeNode);
+ if (balloonStyle) {
+ style["balloonStyle"] = balloonStyle.replace(
+ this.regExes.straightBracket, "${$1}");
+ }
+ break;
+ case "labelstyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fontColor"] = color.color;
+ style["fontOpacity"] = color.opacity;
+ }
+ break;
+
+ default:
+ }
+ }
+
+ // Some polygons have no line color, so we use the fillColor for that
+ if (!style["strokeColor"] && style["fillColor"]) {
+ style["strokeColor"] = style["fillColor"];
+ }
+
+ var id = node.getAttribute("id");
+ if (id && style) {
+ style.id = id;
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: parseStyleMaps
+ * Parses <StyleMap> nodes, but only uses the 'normal' key
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyleMaps: function(nodes, options) {
+ // Only the default or "normal" part of the StyleMap is processed now
+ // To do the select or "highlight" bit, we'd need to change lots more
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var node = nodes[i];
+ var pairs = this.getElementsByTagNameNS(node, "*",
+ "Pair");
+
+ var id = node.getAttribute("id");
+ for (var j=0, jlen=pairs.length; j<jlen; j++) {
+ var pair = pairs[j];
+ // Use the shortcut in the SLD format to quickly retrieve the
+ // value of a node. Maybe it's good to have a method in
+ // Format.XML to do this
+ var key = this.parseProperty(pair, "*", "key");
+ var styleUrl = this.parseProperty(pair, "*", "styleUrl");
+
+ if (styleUrl && key == "normal") {
+ this.styles[(options.styleBaseUrl || "") + "#" + id] =
+ this.styles[(options.styleBaseUrl || "") + styleUrl];
+ }
+
+ // TODO: implement the "select" part
+ //if (styleUrl && key == "highlight") {
+ //}
+
+ }
+ }
+
+ },
+
+
+ /**
+ * Method: parseFeatures
+ * Loop through all Placemark nodes and parse them.
+ * Will create a list of features
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseFeatures: function(nodes, options) {
+ var features = [];
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var featureNode = nodes[i];
+ var feature = this.parseFeature.apply(this,[featureNode]) ;
+ if(feature) {
+
+ // Create reference to styleUrl
+ if (this.extractStyles && feature.attributes &&
+ feature.attributes.styleUrl) {
+ feature.style = this.getStyle(feature.attributes.styleUrl, options);
+ }
+
+ if (this.extractStyles) {
+ // Make sure that <Style> nodes within a placemark are
+ // processed as well
+ var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
+ "*",
+ "Style")[0];
+ if (inlineStyleNode) {
+ var inlineStyle= this.parseStyle(inlineStyleNode);
+ if (inlineStyle) {
+ feature.style = OpenLayers.Util.extend(
+ feature.style, inlineStyle
+ );
+ }
+ }
+ }
+
+ // check if gx:Track elements should be parsed
+ if (this.extractTracks) {
+ var tracks = this.getElementsByTagNameNS(
+ featureNode, this.namespaces.gx, "Track"
+ );
+ if (tracks && tracks.length > 0) {
+ var track = tracks[0];
+ var container = {
+ features: [],
+ feature: feature
+ };
+ this.readNode(track, container);
+ if (container.features.length > 0) {
+ features.push.apply(features, container.features);
+ }
+ }
+ } else {
+ // add feature to list of features
+ features.push(feature);
+ }
+ } else {
+ throw "Bad Placemark: " + i;
+ }
+ }
+
+ // add new features to existing feature list
+ this.features = this.features.concat(features);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "kml": {
+ "when": function(node, container) {
+ container.whens.push(OpenLayers.Date.parse(
+ this.getChildValue(node)
+ ));
+ },
+ "_trackPointAttribute": function(node, container) {
+ var name = node.nodeName.split(":").pop();
+ container.attributes[name].push(this.getChildValue(node));
+ }
+ },
+ "gx": {
+ "Track": function(node, container) {
+ var obj = {
+ whens: [],
+ points: [],
+ angles: []
+ };
+ if (this.trackAttributes) {
+ var name;
+ obj.attributes = {};
+ for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) {
+ name = this.trackAttributes[i];
+ obj.attributes[name] = [];
+ if (!(name in this.readers.kml)) {
+ this.readers.kml[name] = this.readers.kml._trackPointAttribute;
+ }
+ }
+ }
+ this.readChildNodes(node, obj);
+ if (obj.whens.length !== obj.points.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:coord (" +
+ obj.points.length + ") elements.");
+ }
+ var hasAngles = obj.angles.length > 0;
+ if (hasAngles && obj.whens.length !== obj.angles.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:angles (" +
+ obj.angles.length + ") elements.");
+ }
+ var feature, point, angles;
+ for (var i=0, ii=obj.whens.length; i<ii; ++i) {
+ feature = container.feature.clone();
+ feature.fid = container.feature.fid || container.feature.id;
+ point = obj.points[i];
+ feature.geometry = point;
+ if ("z" in point) {
+ feature.attributes.altitude = point.z;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if (this.trackAttributes) {
+ for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) {
+ var name = this.trackAttributes[j];
+ feature.attributes[name] = obj.attributes[name][i];
+ }
+ }
+ feature.attributes.when = obj.whens[i];
+ feature.attributes.trackId = container.feature.id;
+ if (hasAngles) {
+ angles = obj.angles[i];
+ feature.attributes.heading = parseFloat(angles[0]);
+ feature.attributes.tilt = parseFloat(angles[1]);
+ feature.attributes.roll = parseFloat(angles[2]);
+ }
+ container.features.push(feature);
+ }
+ },
+ "coord": function(node, container) {
+ var str = this.getChildValue(node);
+ var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ var point = new OpenLayers.Geometry.Point(coords[0], coords[1]);
+ if (coords.length > 2) {
+ point.z = parseFloat(coords[2]);
+ }
+ container.points.push(point);
+ },
+ "angles": function(node, container) {
+ var str = this.getChildValue(node);
+ var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ container.angles.push(parts);
+ }
+ }
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the KML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A vector feature.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+ var type, nodeList, geometry, parser;
+ for(var i=0, len=order.length; i<len; ++i) {
+ type = order[i];
+ this.internalns = node.namespaceURI ?
+ node.namespaceURI : this.kmlns;
+ nodeList = this.getElementsByTagNameNS(node,
+ this.internalns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ geometry = parser.apply(this, [nodeList[0]]);
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ } else {
+ throw new TypeError("Unsupported geometry type: " + type);
+ }
+ // stop looking for different geometry types
+ break;
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+
+ var fid = node.getAttribute("id") || node.getAttribute("name");
+ if(fid != null) {
+ feature.fid = fid;
+ }
+
+ return feature;
+ },
+
+ /**
+ * Method: getStyle
+ * Retrieves a style from a style hash using styleUrl as the key
+ * If the styleUrl doesn't exist yet, we try to fetch it
+ * Internet
+ *
+ * Parameters:
+ * styleUrl - {String} URL of style
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Object} - (reference to) Style hash
+ */
+ getStyle: function(styleUrl, options) {
+
+ var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
+
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+ newOptions.styleBaseUrl = styleBaseUrl;
+
+ // Fetch remote Style URLs (if not fetched before)
+ if (!this.styles[styleUrl]
+ && !OpenLayers.String.startsWith(styleUrl, "#")
+ && newOptions.depth <= this.maxDepth
+ && !this.fetched[styleBaseUrl] ) {
+
+ var data = this.fetchLink(styleBaseUrl);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+
+ }
+
+ // return requested style
+ var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
+ return style;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a KML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Point node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var coords = [];
+ if(nodeList.length > 0) {
+ var coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace, "");
+ coords = coordString.split(",");
+ }
+
+ var point = null;
+ if(coords.length > 1) {
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ } else {
+ throw "Bad coordinate string: " + coordString;
+ }
+ return point;
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a KML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML LineString node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var line = null;
+ if(nodeList.length > 0) {
+ var coordString = this.getChildValue(nodeList[0]);
+
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ var coords, numCoords;
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ numCoords = coords.length;
+ if(numCoords > 1) {
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ points[i] = new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]);
+ } else {
+ throw "Bad LineString point coordinates: " +
+ pointList[i];
+ }
+ }
+ if(numPoints) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ } else {
+ throw "Bad LineString coordinates: " + coordString;
+ }
+ }
+
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a KML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Polygon node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "LinearRing");
+ var numRings = nodeList.length;
+ var components = new Array(numRings);
+ if(numRings > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0, len=nodeList.length; i<len; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components[i] = ring;
+ } else {
+ throw "Bad LinearRing geometry: " + i;
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multigeometry
+ * Given a KML node representing a multigeometry, create an
+ * OpenLayers geometry collection.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML MultiGeometry node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} A geometry collection.
+ */
+ multigeometry: function(node) {
+ var child, parser;
+ var parts = [];
+ var children = node.childNodes;
+ for(var i=0, len=children.length; i<len; ++i ) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ var type = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ parts.push(parser.apply(this, [child]));
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Collection(parts);
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+
+ // Extended Data is parsed first.
+ var edNodes = node.getElementsByTagName("ExtendedData");
+ if (edNodes.length) {
+ attributes = this.parseExtendedData(edNodes[0]);
+ }
+
+ // assume attribute nodes are type 1 children with a type 3 or 4 child
+ var child, grandchildren, grandchild;
+ var children = node.childNodes;
+
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length >= 1 && grandchildren.length <= 3) {
+ var grandchild;
+ switch (grandchildren.length) {
+ case 1:
+ grandchild = grandchildren[0];
+ break;
+ case 2:
+ var c1 = grandchildren[0];
+ var c2 = grandchildren[1];
+ grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ?
+ c1 : c2;
+ break;
+ case 3:
+ default:
+ grandchild = grandchildren[1];
+ break;
+ }
+ if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+ var name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var value = OpenLayers.Util.getXmlNodeValue(grandchild);
+ if (value) {
+ value = value.replace(this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ }
+ }
+ }
+ }
+ return attributes;
+ },
+
+ /**
+ * Method: parseExtendedData
+ * Parse ExtendedData from KML. Limited support for schemas/datatypes.
+ * See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
+ * for more information on extendeddata.
+ */
+ parseExtendedData: function(node) {
+ var attributes = {};
+ var i, len, data, key;
+ var dataNodes = node.getElementsByTagName("Data");
+ for (i = 0, len = dataNodes.length; i < len; i++) {
+ data = dataNodes[i];
+ key = data.getAttribute("name");
+ var ed = {};
+ var valueNode = data.getElementsByTagName("value");
+ if (valueNode.length) {
+ ed['value'] = this.getChildValue(valueNode[0]);
+ }
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ var nameNode = data.getElementsByTagName("displayName");
+ if (nameNode.length) {
+ ed['displayName'] = this.getChildValue(nameNode[0]);
+ }
+ attributes[key] = ed;
+ }
+ }
+ var simpleDataNodes = node.getElementsByTagName("SimpleData");
+ for (i = 0, len = simpleDataNodes.length; i < len; i++) {
+ var ed = {};
+ data = simpleDataNodes[i];
+ key = data.getAttribute("name");
+ ed['value'] = this.getChildValue(data);
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ ed['displayName'] = key;
+ attributes[key] = ed;
+ }
+ }
+
+ return attributes;
+ },
+
+ /**
+ * Method: parseProperty
+ * Convenience method to find a node and return its value
+ *
+ * Parameters:
+ * xmlNode - {<DOMElement>}
+ * namespace - {String} namespace of the node to find
+ * tagName - {String} name of the property to parse
+ *
+ * Returns:
+ * {String} The value for the requested property (defaults to null)
+ */
+ parseProperty: function(xmlNode, namespace, tagName) {
+ var value;
+ var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
+ try {
+ value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
+ } catch(e) {
+ value = null;
+ }
+
+ return value;
+ },
+
+ /**
+ * APIMethod: write
+ * Accept Feature Collection, and return a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ *
+ * Returns:
+ * {String} A KML string.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var kml = this.createElementNS(this.kmlns, "kml");
+ var folder = this.createFolderXML();
+ for(var i=0, len=features.length; i<len; ++i) {
+ folder.appendChild(this.createPlacemarkXML(features[i]));
+ }
+ kml.appendChild(folder);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+ },
+
+ /**
+ * Method: createFolderXML
+ * Creates and returns a KML folder node
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createFolderXML: function() {
+ // Folder
+ var folder = this.createElementNS(this.kmlns, "Folder");
+
+ // Folder name
+ if (this.foldersName) {
+ var folderName = this.createElementNS(this.kmlns, "name");
+ var folderNameText = this.createTextNode(this.foldersName);
+ folderName.appendChild(folderNameText);
+ folder.appendChild(folderName);
+ }
+
+ // Folder description
+ if (this.foldersDesc) {
+ var folderDesc = this.createElementNS(this.kmlns, "description");
+ var folderDescText = this.createTextNode(this.foldersDesc);
+ folderDesc.appendChild(folderDescText);
+ folder.appendChild(folderDesc);
+ }
+
+ return folder;
+ },
+
+ /**
+ * Method: createPlacemarkXML
+ * Creates and returns a KML placemark node representing the given feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createPlacemarkXML: function(feature) {
+ // Placemark name
+ var placemarkName = this.createElementNS(this.kmlns, "name");
+ var label = (feature.style && feature.style.label) ? feature.style.label : feature.id;
+ var name = feature.attributes.name || label;
+ placemarkName.appendChild(this.createTextNode(name));
+
+ // Placemark description
+ var placemarkDesc = this.createElementNS(this.kmlns, "description");
+ var desc = feature.attributes.description || this.placemarksDesc;
+ placemarkDesc.appendChild(this.createTextNode(desc));
+
+ // Placemark
+ var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+ if(feature.fid != null) {
+ placemarkNode.setAttribute("id", feature.fid);
+ }
+ placemarkNode.appendChild(placemarkName);
+ placemarkNode.appendChild(placemarkDesc);
+
+ // Geometry node (Point, LineString, etc. nodes)
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ placemarkNode.appendChild(geometryNode);
+
+ // output attributes as extendedData
+ if (feature.attributes) {
+ var edNode = this.buildExtendedData(feature.attributes);
+ if (edNode) {
+ placemarkNode.appendChild(edNode);
+ }
+ }
+
+ return placemarkNode;
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * Builds and returns a KML geometry node with the given geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildGeometryNode: function(geometry) {
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ var node = null;
+ if(builder) {
+ node = builder.apply(this, [geometry]);
+ }
+ return node;
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD: Anybody care about namespace aliases here (these nodes have
+ // no prefixes)?
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a KML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML point node.
+ */
+ point: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Point");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipoint: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a KML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linestring node.
+ */
+ linestring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LineString");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multilinestring: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a KML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linearring node.
+ */
+ linearring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LinearRing");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a KML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML polygon node.
+ */
+ polygon: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0, len=rings.length; i<len; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.kmlns, type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ kml.appendChild(ringMember);
+ }
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipolygon: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.collection
+ * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+ *
+ * Returns:
+ * {DOMElement} A KML MultiGeometry node.
+ */
+ collection: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+ var child;
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ child = this.buildGeometryNode.apply(this,
+ [geometry.components[i]]);
+ if(child) {
+ kml.appendChild(child);
+ }
+ }
+ return kml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinatesNode
+ * Builds and returns the KML coordinates node with the given geometry
+ * <coordinates>...</coordinates>
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+
+ var path;
+ var points = geometry.components;
+ if(points) {
+ // LineString or LinearRing
+ var point;
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ parts[i] = this.buildCoordinates(point);
+ }
+ path = parts.join(" ");
+ } else {
+ // Point
+ path = this.buildCoordinates(geometry);
+ }
+
+ var txtNode = this.createTextNode(path);
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ /**
+ * Method: buildCoordinates
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns
+ * {String} a coordinate pair
+ */
+ buildCoordinates: function(point) {
+ if (this.internalProjection && this.externalProjection) {
+ point = point.clone();
+ point.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ return point.x + "," + point.y;
+ },
+
+ /**
+ * Method: buildExtendedData
+ *
+ * Parameters:
+ * attributes - {Object}
+ *
+ * Returns
+ * {DOMElement} A KML ExtendedData node or {null} if no attributes.
+ */
+ buildExtendedData: function(attributes) {
+ var extendedData = this.createElementNS(this.kmlns, "ExtendedData");
+ for (var attributeName in attributes) {
+ // empty, name, description, styleUrl attributes ignored
+ if (attributes[attributeName] && attributeName != "name" && attributeName != "description" && attributeName != "styleUrl") {
+ var data = this.createElementNS(this.kmlns, "Data");
+ data.setAttribute("name", attributeName);
+ var value = this.createElementNS(this.kmlns, "value");
+ if (typeof attributes[attributeName] == "object") {
+ // cater for object attributes with 'value' properties
+ // other object properties will output an empty node
+ if (attributes[attributeName].value) {
+ value.appendChild(this.createTextNode(attributes[attributeName].value));
+ }
+ if (attributes[attributeName].displayName) {
+ var displayName = this.createElementNS(this.kmlns, "displayName");
+ // displayName always written as CDATA
+ displayName.appendChild(this.getXMLDoc().createCDATASection(attributes[attributeName].displayName));
+ data.appendChild(displayName);
+ }
+ } else {
+ value.appendChild(this.createTextNode(attributes[attributeName]));
+ }
+ data.appendChild(value);
+ extendedData.appendChild(data);
+ }
+ }
+ if (this.isSimpleContent(extendedData)) {
+ return null;
+ } else {
+ return extendedData;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.KML"
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities
+ * Read WMS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WMSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.1".
+ */
+ defaultVersion: "1.1.1",
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ *
+ * Currently supported profiles:
+ * - WMSC - parses vendor specific capabilities for WMS-C.
+ */
+ profile: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities
+ * Create a new parser for WMS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities.v1
+ * Abstract class not to be instantiated directly. Creates
+ * the common parts for both WMS 1.1.X and WMS 1.3.X.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMSCapabilities.v1 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wms: "http://www.opengis.net/wms",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wms",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1
+ * Create an instance of one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var raw = data;
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ if (capabilities.service === undefined) {
+ // an exception must have occurred, so parse it
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ capabilities.error = parser.read(raw);
+ }
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": {
+ "Service": function(node, obj) {
+ obj.service = {};
+ this.readChildNodes(node, obj.service);
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, obj) {
+ obj["abstract"] = this.getChildValue(node);
+ },
+ "BoundingBox": function(node, obj) {
+ var bbox = {};
+ bbox.bbox = [
+ parseFloat(node.getAttribute("minx")),
+ parseFloat(node.getAttribute("miny")),
+ parseFloat(node.getAttribute("maxx")),
+ parseFloat(node.getAttribute("maxy"))
+ ];
+ var res = {
+ x: parseFloat(node.getAttribute("resx")),
+ y: parseFloat(node.getAttribute("resy"))
+ };
+
+ if (! (isNaN(res.x) && isNaN(res.y))) {
+ bbox.res = res;
+ }
+ // return the bbox so that descendant classes can set the
+ // CRS and SRS and add it to the obj
+ return bbox;
+ },
+ "OnlineResource": function(node, obj) {
+ obj.href = this.getAttributeNS(node, this.namespaces.xlink,
+ "href");
+ },
+ "ContactInformation": function(node, obj) {
+ obj.contactInformation = {};
+ this.readChildNodes(node, obj.contactInformation);
+ },
+ "ContactPersonPrimary": function(node, obj) {
+ obj.personPrimary = {};
+ this.readChildNodes(node, obj.personPrimary);
+ },
+ "ContactPerson": function(node, obj) {
+ obj.person = this.getChildValue(node);
+ },
+ "ContactOrganization": function(node, obj) {
+ obj.organization = this.getChildValue(node);
+ },
+ "ContactPosition": function(node, obj) {
+ obj.position = this.getChildValue(node);
+ },
+ "ContactAddress": function(node, obj) {
+ obj.contactAddress = {};
+ this.readChildNodes(node, obj.contactAddress);
+ },
+ "AddressType": function(node, obj) {
+ obj.type = this.getChildValue(node);
+ },
+ "Address": function(node, obj) {
+ obj.address = this.getChildValue(node);
+ },
+ "City": function(node, obj) {
+ obj.city = this.getChildValue(node);
+ },
+ "StateOrProvince": function(node, obj) {
+ obj.stateOrProvince = this.getChildValue(node);
+ },
+ "PostCode": function(node, obj) {
+ obj.postcode = this.getChildValue(node);
+ },
+ "Country": function(node, obj) {
+ obj.country = this.getChildValue(node);
+ },
+ "ContactVoiceTelephone": function(node, obj) {
+ obj.phone = this.getChildValue(node);
+ },
+ "ContactFacsimileTelephone": function(node, obj) {
+ obj.fax = this.getChildValue(node);
+ },
+ "ContactElectronicMailAddress": function(node, obj) {
+ obj.email = this.getChildValue(node);
+ },
+ "Fees": function(node, obj) {
+ var fees = this.getChildValue(node);
+ if (fees && fees.toLowerCase() != "none") {
+ obj.fees = fees;
+ }
+ },
+ "AccessConstraints": function(node, obj) {
+ var constraints = this.getChildValue(node);
+ if (constraints && constraints.toLowerCase() != "none") {
+ obj.accessConstraints = constraints;
+ }
+ },
+ "Capability": function(node, obj) {
+ obj.capability = {
+ nestedLayers: [],
+ layers: []
+ };
+ this.readChildNodes(node, obj.capability);
+ },
+ "Request": function(node, obj) {
+ obj.request = {};
+ this.readChildNodes(node, obj.request);
+ },
+ "GetCapabilities": function(node, obj) {
+ obj.getcapabilities = {formats: []};
+ this.readChildNodes(node, obj.getcapabilities);
+ },
+ "Format": function(node, obj) {
+ if (OpenLayers.Util.isArray(obj.formats)) {
+ obj.formats.push(this.getChildValue(node));
+ } else {
+ obj.format = this.getChildValue(node);
+ }
+ },
+ "DCPType": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "HTTP": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Get": function(node, obj) {
+ obj.get = {};
+ this.readChildNodes(node, obj.get);
+ // backwards compatibility
+ if (!obj.href) {
+ obj.href = obj.get.href;
+ }
+ },
+ "Post": function(node, obj) {
+ obj.post = {};
+ this.readChildNodes(node, obj.post);
+ // backwards compatibility
+ if (!obj.href) {
+ obj.href = obj.get.href;
+ }
+ },
+ "GetMap": function(node, obj) {
+ obj.getmap = {formats: []};
+ this.readChildNodes(node, obj.getmap);
+ },
+ "GetFeatureInfo": function(node, obj) {
+ obj.getfeatureinfo = {formats: []};
+ this.readChildNodes(node, obj.getfeatureinfo);
+ },
+ "Exception": function(node, obj) {
+ obj.exception = {formats: []};
+ this.readChildNodes(node, obj.exception);
+ },
+ "Layer": function(node, obj) {
+ var parentLayer, capability;
+ if (obj.capability) {
+ capability = obj.capability;
+ parentLayer = obj;
+ } else {
+ capability = obj;
+ }
+ var attrNode = node.getAttributeNode("queryable");
+ var queryable = (attrNode && attrNode.specified) ?
+ node.getAttribute("queryable") : null;
+ attrNode = node.getAttributeNode("cascaded");
+ var cascaded = (attrNode && attrNode.specified) ?
+ node.getAttribute("cascaded") : null;
+ attrNode = node.getAttributeNode("opaque");
+ var opaque = (attrNode && attrNode.specified) ?
+ node.getAttribute('opaque') : null;
+ var noSubsets = node.getAttribute('noSubsets');
+ var fixedWidth = node.getAttribute('fixedWidth');
+ var fixedHeight = node.getAttribute('fixedHeight');
+ var parent = parentLayer || {},
+ extend = OpenLayers.Util.extend;
+ var layer = {
+ nestedLayers: [],
+ styles: parentLayer ? [].concat(parentLayer.styles) : [],
+ srs: parentLayer ? extend({}, parent.srs) : {},
+ metadataURLs: [],
+ bbox: parentLayer ? extend({}, parent.bbox) : {},
+ llbbox: parent.llbbox,
+ dimensions: parentLayer ? extend({}, parent.dimensions) : {},
+ authorityURLs: parentLayer ? extend({}, parent.authorityURLs) : {},
+ identifiers: {},
+ keywords: [],
+ queryable: (queryable && queryable !== "") ?
+ (queryable === "1" || queryable === "true" ) :
+ (parent.queryable || false),
+ cascaded: (cascaded !== null) ? parseInt(cascaded) :
+ (parent.cascaded || 0),
+ opaque: opaque ?
+ (opaque === "1" || opaque === "true" ) :
+ (parent.opaque || false),
+ noSubsets: (noSubsets !== null) ?
+ (noSubsets === "1" || noSubsets === "true" ) :
+ (parent.noSubsets || false),
+ fixedWidth: (fixedWidth != null) ?
+ parseInt(fixedWidth) : (parent.fixedWidth || 0),
+ fixedHeight: (fixedHeight != null) ?
+ parseInt(fixedHeight) : (parent.fixedHeight || 0),
+ minScale: parent.minScale,
+ maxScale: parent.maxScale,
+ attribution: parent.attribution
+ };
+ obj.nestedLayers.push(layer);
+ layer.capability = capability;
+ this.readChildNodes(node, layer);
+ delete layer.capability;
+ if(layer.name) {
+ var parts = layer.name.split(":"),
+ request = capability.request,
+ gfi = request.getfeatureinfo;
+ if(parts.length > 0) {
+ layer.prefix = parts[0];
+ }
+ capability.layers.push(layer);
+ if (layer.formats === undefined) {
+ layer.formats = request.getmap.formats;
+ }
+ if (layer.infoFormats === undefined && gfi) {
+ layer.infoFormats = gfi.formats;
+ }
+ }
+ },
+ "Attribution": function(node, obj) {
+ obj.attribution = {};
+ this.readChildNodes(node, obj.attribution);
+ },
+ "LogoURL": function(node, obj) {
+ obj.logo = {
+ width: node.getAttribute("width"),
+ height: node.getAttribute("height")
+ };
+ this.readChildNodes(node, obj.logo);
+ },
+ "Style": function(node, obj) {
+ var style = {};
+ obj.styles.push(style);
+ this.readChildNodes(node, style);
+ },
+ "LegendURL": function(node, obj) {
+ var legend = {
+ width: node.getAttribute("width"),
+ height: node.getAttribute("height")
+ };
+ obj.legend = legend;
+ this.readChildNodes(node, legend);
+ },
+ "MetadataURL": function(node, obj) {
+ var metadataURL = {type: node.getAttribute("type")};
+ obj.metadataURLs.push(metadataURL);
+ this.readChildNodes(node, metadataURL);
+ },
+ "DataURL": function(node, obj) {
+ obj.dataURL = {};
+ this.readChildNodes(node, obj.dataURL);
+ },
+ "FeatureListURL": function(node, obj) {
+ obj.featureListURL = {};
+ this.readChildNodes(node, obj.featureListURL);
+ },
+ "AuthorityURL": function(node, obj) {
+ var name = node.getAttribute("name");
+ var authority = {};
+ this.readChildNodes(node, authority);
+ obj.authorityURLs[name] = authority.href;
+ },
+ "Identifier": function(node, obj) {
+ var authority = node.getAttribute("authority");
+ obj.identifiers[authority] = this.getChildValue(node);
+ },
+ "KeywordList": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "SRS": function(node, obj) {
+ obj.srs[this.getChildValue(node)] = true;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1_1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities.v1_1
+ * Abstract class not to be instantiated directly.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1, {
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "WMT_MS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Keyword": function(node, obj) {
+ if (obj.keywords) {
+ obj.keywords.push(this.getChildValue(node));
+ }
+ },
+ "DescribeLayer": function(node, obj) {
+ obj.describelayer = {formats: []};
+ this.readChildNodes(node, obj.describelayer);
+ },
+ "GetLegendGraphic": function(node, obj) {
+ obj.getlegendgraphic = {formats: []};
+ this.readChildNodes(node, obj.getlegendgraphic);
+ },
+ "GetStyles": function(node, obj) {
+ obj.getstyles = {formats: []};
+ this.readChildNodes(node, obj.getstyles);
+ },
+ "PutStyles": function(node, obj) {
+ obj.putstyles = {formats: []};
+ this.readChildNodes(node, obj.putstyles);
+ },
+ "UserDefinedSymbolization": function(node, obj) {
+ var userSymbols = {
+ supportSLD: parseInt(node.getAttribute("SupportSLD")) == 1,
+ userLayer: parseInt(node.getAttribute("UserLayer")) == 1,
+ userStyle: parseInt(node.getAttribute("UserStyle")) == 1,
+ remoteWFS: parseInt(node.getAttribute("RemoteWFS")) == 1
+ };
+ obj.userSymbols = userSymbols;
+ },
+ "LatLonBoundingBox": function(node, obj) {
+ obj.llbbox = [
+ parseFloat(node.getAttribute("minx")),
+ parseFloat(node.getAttribute("miny")),
+ parseFloat(node.getAttribute("maxx")),
+ parseFloat(node.getAttribute("maxy"))
+ ];
+ },
+ "BoundingBox": function(node, obj) {
+ var bbox = OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"].BoundingBox.apply(this, [node, obj]);
+ bbox.srs = node.getAttribute("SRS");
+ obj.bbox[bbox.srs] = bbox;
+ },
+ "ScaleHint": function(node, obj) {
+ var min = node.getAttribute("min");
+ var max = node.getAttribute("max");
+ var rad2 = Math.pow(2, 0.5);
+ var ipm = OpenLayers.INCHES_PER_UNIT["m"];
+ if (min != 0) {
+ obj.maxScale = parseFloat(
+ ((min / rad2) * ipm *
+ OpenLayers.DOTS_PER_INCH).toPrecision(13)
+ );
+ }
+ if (max != Number.POSITIVE_INFINITY) {
+ obj.minScale = parseFloat(
+ ((max / rad2) * ipm *
+ OpenLayers.DOTS_PER_INCH).toPrecision(13)
+ );
+ }
+ },
+ "Dimension": function(node, obj) {
+ var name = node.getAttribute("name").toLowerCase();
+ var dim = {
+ name: name,
+ units: node.getAttribute("units"),
+ unitsymbol: node.getAttribute("unitSymbol")
+ };
+ obj.dimensions[dim.name] = dim;
+ },
+ "Extent": function(node, obj) {
+ var name = node.getAttribute("name").toLowerCase();
+ if (name in obj["dimensions"]) {
+ var extent = obj.dimensions[name];
+ extent.nearestVal =
+ node.getAttribute("nearestValue") === "1";
+ extent.multipleVal =
+ node.getAttribute("multipleValues") === "1";
+ extent.current = node.getAttribute("current") === "1";
+ extent["default"] = node.getAttribute("default") || "";
+ var values = this.getChildValue(node);
+ extent.values = values.split(",");
+ }
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_0
+ * Read WMS Capabilities version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_1, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_0
+ * Create a new parser for WMS capabilities version 1.1.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "SRS": function(node, obj) {
+ var srs = this.getChildValue(node);
+ var values = srs.split(/ +/);
+ for (var i=0, len=values.length; i<len; i++) {
+ obj.srs[values[i]] = true;
+ }
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_0"
+
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1
+ * Abstract class for for v1.0.0 and v1.1.0 protocol.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: srsName
+ * {String} Name of spatial reference system. Default is "EPSG:4326".
+ */
+ srsName: "EPSG:4326",
+
+ /**
+ * Property: featureType
+ * {String} Local feature typeName.
+ */
+ featureType: null,
+
+ /**
+ * Property: featureNS
+ * {String} Feature namespace.
+ */
+ featureNS: null,
+
+ /**
+ * Property: geometryName
+ * {String} Name of the geometry attribute for features. Default is
+ * "the_geom" for WFS <version> 1.0, and null for higher versions.
+ */
+ geometryName: "the_geom",
+
+ /**
+ * Property: maxFeatures
+ * {Integer} Optional maximum number of features to retrieve.
+ */
+
+ /**
+ * Property: schema
+ * {String} Optional schema location that will be included in the
+ * schemaLocation attribute value. Note that the feature type schema
+ * is required for a strict XML validator (on transactions with an
+ * insert for example), but is *not* required by the WFS specification
+ * (since the server is supposed to know about feature type schemas).
+ */
+ schema: null,
+
+ /**
+ * Property: featurePrefix
+ * {String} Namespace alias for feature type. Default is "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: readFormat
+ * {<OpenLayers.Format>} For WFS requests it is possible to get a
+ * different output format than GML. In that case, we cannot parse
+ * the response with the default format (WFST) and we need a different
+ * format for reading.
+ */
+ readFormat: null,
+
+ /**
+ * Property: readOptions
+ * {Object} Optional object to pass to format's read.
+ */
+ readOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS
+ * A class for giving layers WFS protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * url - {String} URL to send requests to (required).
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required, but can be autodetected
+ * during the first query if GML is used as readFormat and
+ * featurePrefix is provided and matches the prefix used by the server
+ * for this featureType).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * for writing if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. The default is
+ * 'the_geom' for WFS <version> 1.0, and null for higher versions. If
+ * null, it will be set to the name of the first geometry found in the
+ * first read operation.
+ * multi - {Boolean} If set to true, geometries will be casted to Multi
+ * geometries before they are written in a transaction. No casting will
+ * be done when reading features.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
+ version: this.version,
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ geometryName: this.geometryName,
+ srsName: this.srsName,
+ schema: this.schema
+ }, this.formatOptions));
+ }
+ if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
+ this.setGeometryName(null);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features. Since WFS splits the
+ * basic CRUD operations into GetFeature requests (for read) and
+ * Transactions (for all others), this method does not make use of the
+ * format's read method (that is only about reading transaction
+ * responses).
+ *
+ * Parameters:
+ * options - {Object} Options for the read operation, in addition to the
+ * options set on the instance (options set here will take precedence).
+ *
+ * To use a configured protocol to get e.g. a WFS hit count, applications
+ * could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * readOptions: {output: "object"},
+ * resultType: "hits",
+ * maxFeatures: null,
+ * callback: function(resp) {
+ * // process resp.numberOfFeatures here
+ * }
+ * });
+ * (end)
+ *
+ * To use a configured protocol to use WFS paging (if supported by the
+ * server), applications could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * startIndex: 0,
+ * count: 50
+ * });
+ * (end)
+ *
+ * To limit the attributes returned by the GetFeature request, applications
+ * can use the propertyNames option to specify the properties to include in
+ * the response:
+ *
+ * (code)
+ * protocol.read({
+ * propertyNames: ["DURATION", "INTENSITY"]
+ * });
+ * (end)
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [this.format.writeNode("wfs:GetFeature", options)]
+ );
+
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ params: options.params,
+ headers: options.headers,
+ data: data
+ });
+
+ return response;
+ },
+
+ /**
+ * APIMethod: setFeatureType
+ * Change the feature type on the fly.
+ *
+ * Parameters:
+ * featureType - {String} Local (without prefix) feature typeName.
+ */
+ setFeatureType: function(featureType) {
+ this.featureType = featureType;
+ this.format.featureType = featureType;
+ },
+
+ /**
+ * APIMethod: setGeometryName
+ * Sets the geometryName option after instantiation.
+ *
+ * Parameters:
+ * geometryName - {String} Name of geometry attribute.
+ */
+ setGeometryName: function(geometryName) {
+ this.geometryName = geometryName;
+ this.format.geometryName = geometryName;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ var result = this.parseResponse(request, options.readOptions);
+ if (result && result.success !== false) {
+ if (options.readOptions && options.readOptions.output == "object") {
+ OpenLayers.Util.extend(response, result);
+ } else {
+ response.features = result;
+ }
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure (service exception)
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = result;
+ }
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseResponse
+ * Read HTTP response body and return features
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ * options - {Object} Optional object to pass to format's read
+ *
+ * Returns:
+ * {Object} or {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * An object with a features property, an array of features or a single
+ * feature.
+ */
+ parseResponse: function(request, options) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ var result = (this.readFormat !== null) ? this.readFormat.read(doc) :
+ this.format.read(doc, options);
+ if (!this.featureNS) {
+ var format = this.readFormat || this.format;
+ this.featureNS = format.featureNS;
+ // no need to auto-configure again on subsequent reads
+ format.autoConfig = false;
+ if (!this.geometryName) {
+ this.setGeometryName(format.geometryName);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Method: commit
+ * Given a list of feature, assemble a batch request for update, create,
+ * and delete transactions. A commit call on the prototype amounts
+ * to writing a WFS transaction - so the write method on the format
+ * is used.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ *
+ * Valid options properties:
+ * nativeElements - {Array({Object})} Array of objects with information for writing
+ * out <Native> elements, these objects have vendorId, safeToIgnore and
+ * value properties. The <Native> element is intended to allow access to
+ * vendor specific capabilities of any particular web feature server or
+ * datastore.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object with a features
+ * property containing any insertIds and a priv property referencing
+ * the XMLHttpRequest object.
+ */
+ commit: function(features, options) {
+
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit",
+ reqFeatures: features
+ });
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ headers: options.headers,
+ data: this.format.write(features, options),
+ callback: this.createCallback(this.handleCommit, response, options)
+ });
+
+ return response;
+ },
+
+ /**
+ * Method: handleCommit
+ * Called when the commit request returns.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the commit call.
+ */
+ handleCommit: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+
+ // ensure that we have an xml doc
+ var data = request.responseXML;
+ if(!data || !data.documentElement) {
+ data = request.responseText;
+ }
+
+ var obj = this.format.read(data) || {};
+
+ response.insertIds = obj.insertIds || [];
+ if (obj.success) {
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = obj;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: filterDelete
+ * Send a request that deletes all features by their filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter
+ */
+ filterDelete: function(filter, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit"
+ });
+
+ var root = this.format.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version
+ }
+ });
+
+ var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
+ options.featureType
+ }
+ });
+
+ if(options.featureNS) {
+ deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
+ }
+ var filterNode = this.format.writeNode("ogc:Filter", filter);
+
+ deleteNode.appendChild(filterNode);
+
+ root.appendChild(deleteNode);
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [root]
+ );
+
+ return OpenLayers.Request.POST({
+ url: this.url,
+ callback : options.callback || function(){},
+ data: data
+ });
+
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this protocol (as a result
+ * of a read, or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1"
+});
+/* ======================================================================
+ OpenLayers/Handler/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature
+ * Handler to respond to mouse events related to a drawn feature. Callbacks
+ * with the following keys will be notified of the following events
+ * associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ * browser events target features that can be selected.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: EVENTMAP
+ * {Object} A object mapping the browser events to objects with callback
+ * keys for in and out.
+ */
+ EVENTMAP: {
+ 'click': {'in': 'click', 'out': 'clickout'},
+ 'mousemove': {'in': 'over', 'out': 'out'},
+ 'dblclick': {'in': 'dblclick', 'out': null},
+ 'mousedown': {'in': null, 'out': null},
+ 'mouseup': {'in': null, 'out': null},
+ 'touchstart': {'in': 'click', 'out': 'clickout'}
+ },
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+ */
+ feature: null,
+
+ /**
+ * Property: lastFeature
+ * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+ */
+ lastFeature: null,
+
+ /**
+ * Property: down
+ * {<OpenLayers.Pixel>} The location of the last mousedown.
+ */
+ down: null,
+
+ /**
+ * Property: up
+ * {<OpenLayers.Pixel>} The location of the last mouseup.
+ */
+ up: null,
+
+ /**
+ * Property: clickTolerance
+ * {Number} The number of pixels the mouse can move between mousedown
+ * and mouseup for the event to still be considered a click.
+ * Dragging the map should not trigger the click and clickout callbacks
+ * unless the map is moved by less than this tolerance. Defaults to 4.
+ */
+ clickTolerance: 4,
+
+ /**
+ * Property: geometryTypes
+ * To restrict dragging to a limited set of geometry types, send a list
+ * of strings corresponding to the geometry class names.
+ *
+ * @type Array(String)
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: stopClick
+ * {Boolean} If stopClick is set to true, handled clicks do not
+ * propagate to other click listeners. Otherwise, handled clicks
+ * do propagate. Unhandled clicks always propagate, whatever the
+ * value of stopClick. Defaults to true.
+ */
+ stopClick: true,
+
+ /**
+ * Property: stopDown
+ * {Boolean} If stopDown is set to true, handled mousedowns do not
+ * propagate to other mousedown listeners. Otherwise, handled
+ * mousedowns do propagate. Unhandled mousedowns always propagate,
+ * whatever the value of stopDown. Defaults to true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: stopUp
+ * {Boolean} If stopUp is set to true, handled mouseups do not
+ * propagate to other mouseup listeners. Otherwise, handled mouseups
+ * do propagate. Unhandled mouseups always propagate, whatever the
+ * value of stopUp. Defaults to false.
+ */
+ stopUp: false,
+
+ /**
+ * Constructor: OpenLayers.Handler.Feature
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * layer - {<OpenLayers.Layer.Vector>}
+ * callbacks - {Object} An object with a 'over' property whos value is
+ * a function to be called when the mouse is over a feature. The
+ * callback should expect to recieve a single argument, the feature.
+ * options - {Object}
+ */
+ initialize: function(control, layer, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+ this.layer = layer;
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return OpenLayers.Event.isMultiTouch(evt) ?
+ true : this.mousedown(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events. We just prevent the browser default behavior,
+ * for Android Webkit not to select text when moving the finger after
+ * selecting a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ touchmove: function(evt) {
+ OpenLayers.Event.preventDefault(evt);
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mouse down. Stop propagation if a feature is targeted by this
+ * event (stops map dragging during feature selection).
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mousedown: function(evt) {
+ // Feature selection is only done with a left click. Other handlers may stop the
+ // propagation of left-click mousedown events but not right-click mousedown events.
+ // This mismatch causes problems when comparing the location of the down and up
+ // events in the click function so it is important ignore right-clicks.
+ if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
+ this.down = evt.xy;
+ }
+ return this.handle(evt) ? !this.stopDown : true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouse up. Stop propagation if a feature is targeted by this
+ * event.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mouseup: function(evt) {
+ this.up = evt.xy;
+ return this.handle(evt) ? !this.stopUp : true;
+ },
+
+ /**
+ * Method: click
+ * Handle click. Call the "click" callback if click on a feature,
+ * or the "clickout" callback if click outside any feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ click: function(evt) {
+ return this.handle(evt) ? !this.stopClick : true;
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mouse moves. Call the "over" callback if moving in to a feature,
+ * or the "out" callback if moving out of a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ mousemove: function(evt) {
+ if (!this.callbacks['over'] && !this.callbacks['out']) {
+ return true;
+ }
+ this.handle(evt);
+ return true;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ dblclick: function(evt) {
+ return !this.handle(evt);
+ },
+
+ /**
+ * Method: geometryTypeMatches
+ * Return true if the geometry type of the passed feature matches
+ * one of the geometry types in the geometryTypes array.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ geometryTypeMatches: function(feature) {
+ return this.geometryTypes == null ||
+ OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) > -1;
+ },
+
+ /**
+ * Method: handle
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} The event occurred over a relevant feature.
+ */
+ handle: function(evt) {
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ var type = evt.type;
+ var handled = false;
+ var previouslyIn = !!(this.feature); // previously in a feature
+ var click = (type == "click" || type == "dblclick" || type == "touchstart");
+ this.feature = this.layer.getFeatureFromEvent(evt);
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ if(this.lastFeature && !this.lastFeature.layer) {
+ // last feature has been destroyed
+ this.lastFeature = null;
+ }
+ if(this.feature) {
+ if(type === "touchstart") {
+ // stop the event to prevent Android Webkit from
+ // "flashing" the map div
+ OpenLayers.Event.preventDefault(evt);
+ }
+ var inNew = (this.feature != this.lastFeature);
+ if(this.geometryTypeMatches(this.feature)) {
+ // in to a feature
+ if(previouslyIn && inNew) {
+ // out of last feature and in to another
+ if(this.lastFeature) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ this.triggerCallback(type, 'in', [this.feature]);
+ } else if(!previouslyIn || click) {
+ // in feature for the first time
+ this.triggerCallback(type, 'in', [this.feature]);
+ }
+ this.lastFeature = this.feature;
+ handled = true;
+ } else {
+ // not in to a feature
+ if(this.lastFeature && (previouslyIn && inNew || click)) {
+ // out of last feature for the first time
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ // next time the mouse goes in a feature whose geometry type
+ // doesn't match we don't want to call the 'out' callback
+ // again, so let's set this.feature to null so that
+ // previouslyIn will evaluate to false the next time
+ // we enter handle. Yes, a bit hackish...
+ this.feature = null;
+ }
+ } else if(this.lastFeature && (previouslyIn || click)) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ return handled;
+ },
+
+ /**
+ * Method: triggerCallback
+ * Call the callback keyed in the event map with the supplied arguments.
+ * For click and clickout, the <clickTolerance> is checked first.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ triggerCallback: function(type, mode, args) {
+ var key = this.EVENTMAP[type][mode];
+ if(key) {
+ if(type == 'click' && this.up && this.down) {
+ // for click/clickout, only trigger callback if tolerance is met
+ var dpx = Math.sqrt(
+ Math.pow(this.up.x - this.down.x, 2) +
+ Math.pow(this.up.y - this.down.y, 2)
+ );
+ if(dpx <= this.clickTolerance) {
+ this.callback(key, args);
+ }
+ // we're done with this set of events now: clear the cached
+ // positions so we can't trip over them later (this can occur
+ // if one of the up/down events gets eaten before it gets to us
+ // but we still get the click)
+ this.up = this.down = null;
+ } else {
+ this.callback(key, args);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Turn off the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.feature = null;
+ this.lastFeature = null;
+ this.down = null;
+ this.up = null;
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Feature"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector/RootContainer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector.RootContainer
+ * A special layer type to combine multiple vector layers inside a single
+ * renderer root container. This class is not supposed to be instantiated
+ * from user space, it is a helper class for controls that require event
+ * processing for multiple vector layers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * Property: displayInLayerSwitcher
+ * Set to false for this layer type
+ */
+ displayInLayerSwitcher: false,
+
+ /**
+ * APIProperty: layers
+ * Layers that are attached to this container. Required config option.
+ */
+ layers: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector.RootContainer
+ * Create a new root container for multiple vector layer. This constructor
+ * is not supposed to be used from user space, it is only to be used by
+ * controls that need feature selection across multiple vector layers.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Required options properties:
+ * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this
+ * container
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root
+ * container
+ */
+
+ /**
+ * Method: display
+ */
+ display: function() {},
+
+ /**
+ * Method: getFeatureFromEvent
+ * walk through the layers to find the feature returned by the event
+ *
+ * Parameters:
+ * evt - {Object} event object with a feature property
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getFeatureFromEvent: function(evt) {
+ var layers = this.layers;
+ var feature;
+ for(var i=0; i<layers.length; i++) {
+ feature = layers[i].getFeatureFromEvent(evt);
+ if(feature) {
+ return feature;
+ }
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ this.collectRoots();
+ map.events.register("changelayer", this, this.handleChangeLayer);
+ },
+
+ /**
+ * Method: removeMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("changelayer", this, this.handleChangeLayer);
+ this.resetRoots();
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: collectRoots
+ * Collects the root nodes of all layers this control is configured with
+ * and moveswien the nodes to this control's layer
+ */
+ collectRoots: function() {
+ var layer;
+ // walk through all map layers, because we want to keep the order
+ for(var i=0; i<this.map.layers.length; ++i) {
+ layer = this.map.layers[i];
+ if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ layer.renderer.moveRoot(this.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: resetRoots
+ * Resets the root nodes back into the layers they belong to.
+ */
+ resetRoots: function() {
+ var layer;
+ for(var i=0; i<this.layers.length; ++i) {
+ layer = this.layers[i];
+ if(this.renderer && layer.renderer.getRenderLayerId() == this.id) {
+ this.renderer.moveRoot(layer.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: handleChangeLayer
+ * Event handler for the map's changelayer event. We need to rebuild
+ * this container's layer dom if order of one of its layers changes.
+ * This handler is added with the setMap method, and removed with the
+ * removeMap method.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleChangeLayer: function(evt) {
+ var layer = evt.layer;
+ if(evt.property == "order" &&
+ OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ this.resetRoots();
+ this.collectRoots();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
+});
+/* ======================================================================
+ OpenLayers/Control/SelectFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ * @requires OpenLayers/Layer/Vector/RootContainer.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * The SelectFeature control selects vector features from a given layer on
+ * click or hover.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforefeaturehighlighted - Triggered before a feature is highlighted
+ * featurehighlighted - Triggered when a feature is highlighted
+ * featureunhighlighted - Triggered when a feature is unhighlighted
+ * boxselectionstart - Triggered before box selection starts
+ * boxselectionend - Triggered after box selection ends
+ */
+
+ /**
+ * Property: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * Property: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Default is false. Only
+ * has meaning if hover is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Select on mouse over and deselect on mouse out. If true, this
+ * ignores clicks and only listens to mouse moves.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: highlightOnly
+ * {Boolean} If true do not actually select features (that is place them in
+ * the layer's selected features array), just highlight them. This property
+ * has no effect if hover is false. Defaults to false.
+ */
+ highlightOnly: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box.
+ */
+ box: false,
+
+ /**
+ * Property: onBeforeSelect
+ * {Function} Optional function to be called before a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onBeforeSelect: function() {},
+
+ /**
+ * APIProperty: onSelect
+ * {Function} Optional function to be called when a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onSelect: function() {},
+
+ /**
+ * APIProperty: onUnselect
+ * {Function} Optional function to be called when a feature is unselected.
+ * The function should expect to be called with a feature.
+ */
+ onUnselect: function() {},
+
+ /**
+ * Property: scope
+ * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
+ * callbacks. If null the scope will be this control.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict selecting to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
+ * root for all layers this control is configured with (if an array of
+ * layers was passed to the constructor), or the vector layer the control
+ * was configured with (if a single layer was passed to the constructor).
+ */
+ layer: null,
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
+ * or null if the control was configured with a single layer
+ */
+ layers: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} The functions that are sent to the handlers.feature for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectStyle
+ * {Object} Hash of styles
+ */
+ selectStyle: null,
+
+ /**
+ * Property: renderIntent
+ * {String} key used to retrieve the select style from the layer's
+ * style map.
+ */
+ renderIntent: "select",
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Constructor: OpenLayers.Control.SelectFeature
+ * Create a new control for selecting features.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
+ * layer(s) this control will select features from.
+ * options - {Object}
+ */
+ initialize: function(layers, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(this.scope === null) {
+ this.scope = this;
+ }
+ this.initLayer(layers);
+ var callbacks = {
+ click: this.clickFeature,
+ clickout: this.clickoutFeature
+ };
+ if (this.hover) {
+ callbacks.over = this.overFeature;
+ callbacks.out = this.outFeature;
+ }
+
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+ this.handlers = {
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, this.callbacks,
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+
+ if (this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ {boxDivClassName: "olHandlerBoxSelectFeature"}
+ );
+ }
+ },
+
+ /**
+ * Method: initLayer
+ * Assign the layer property. If layers is an array, we need to use
+ * a RootContainer.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
+ */
+ initLayer: function(layers) {
+ if(OpenLayers.Util.isArray(layers)) {
+ this.layers = layers;
+ this.layer = new OpenLayers.Layer.Vector.RootContainer(
+ this.id + "_container", {
+ layers: layers
+ }
+ );
+ } else {
+ this.layer = layers;
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ if(this.active && this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if(this.layers) {
+ this.layer.destroy();
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ if(this.layers) {
+ this.map.addLayer(this.layer);
+ }
+ this.handlers.feature.activate();
+ if(this.box && this.handlers.box) {
+ this.handlers.box.activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ this.handlers.feature.deactivate();
+ if(this.handlers.box) {
+ this.handlers.box.deactivate();
+ }
+ if(this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features. To unselect all except for a single
+ * feature, set the options.except property to the feature.
+ *
+ * Parameters:
+ * options - {Object} Optional configuration object.
+ */
+ unselectAll: function(options) {
+ // we'll want an option to supress notification here
+ var layers = this.layers || [this.layer],
+ layer, feature, l, numExcept;
+ for(l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ numExcept = 0;
+ //layer.selectedFeatures is null when layer is destroyed and
+ //one of it's preremovelayer listener calls setLayer
+ //with another layer on this control
+ if(layer.selectedFeatures != null) {
+ while(layer.selectedFeatures.length > numExcept) {
+ feature = layer.selectedFeatures[numExcept];
+ if(!options || options.except != feature) {
+ this.unselect(feature);
+ } else {
+ ++numExcept;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clickFeature
+ * Called on click in a feature
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if(!this.hover) {
+ var selected = (OpenLayers.Util.indexOf(
+ feature.layer.selectedFeatures, feature) > -1);
+ if(selected) {
+ if(this.toggleSelect()) {
+ this.unselect(feature);
+ } else if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ } else {
+ if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: multipleSelect
+ * Allow for multiple selected features based on <multiple> property and
+ * <multipleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Allow for multiple selected features.
+ */
+ multipleSelect: function() {
+ return this.multiple || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.multipleKey]);
+ },
+
+ /**
+ * Method: toggleSelect
+ * Event should toggle the selected state of a feature based on <toggle>
+ * property and <toggleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Toggle the selected state of a feature.
+ */
+ toggleSelect: function() {
+ return this.toggle || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.toggleKey]);
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called on click outside a previously clicked (selected) feature.
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ */
+ clickoutFeature: function(feature) {
+ if(!this.hover && this.clickout) {
+ this.unselectAll();
+ }
+ },
+
+ /**
+ * Method: overFeature
+ * Called on over a feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ overFeature: function(feature) {
+ var layer = feature.layer;
+ if(this.hover) {
+ if(this.highlightOnly) {
+ this.highlight(feature);
+ } else if(OpenLayers.Util.indexOf(
+ layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: outFeature
+ * Called on out of a selected feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ outFeature: function(feature) {
+ if(this.hover) {
+ if(this.highlightOnly) {
+ // we do nothing if we're not the last highlighter of the
+ // feature
+ if(feature._lastHighlighter == this.id) {
+ // if another select control had highlighted the feature before
+ // we did it ourself then we use that control to highlight the
+ // feature as it was before we highlighted it, else we just
+ // unhighlight it
+ if(feature._prevHighlighter &&
+ feature._prevHighlighter != this.id) {
+ delete feature._lastHighlighter;
+ var control = this.map.getControl(
+ feature._prevHighlighter);
+ if(control) {
+ control.highlight(feature);
+ }
+ } else {
+ this.unhighlight(feature);
+ }
+ }
+ } else {
+ this.unselect(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: highlight
+ * Redraw feature with the select style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ highlight: function(feature) {
+ var layer = feature.layer;
+ var cont = this.events.triggerEvent("beforefeaturehighlighted", {
+ feature : feature
+ });
+ if(cont !== false) {
+ feature._prevHighlighter = feature._lastHighlighter;
+ feature._lastHighlighter = this.id;
+ var style = this.selectStyle || this.renderIntent;
+ layer.drawFeature(feature, style);
+ this.events.triggerEvent("featurehighlighted", {feature : feature});
+ }
+ },
+
+ /**
+ * Method: unhighlight
+ * Redraw feature with the "default" style
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unhighlight: function(feature) {
+ var layer = feature.layer;
+ // three cases:
+ // 1. there's no other highlighter, in that case _prev is undefined,
+ // and we just need to undef _last
+ // 2. another control highlighted the feature after we did it, in
+ // that case _last references this other control, and we just
+ // need to undef _prev
+ // 3. another control highlighted the feature before we did it, in
+ // that case _prev references this other control, and we need to
+ // set _last to _prev and undef _prev
+ if(feature._prevHighlighter == undefined) {
+ delete feature._lastHighlighter;
+ } else if(feature._prevHighlighter == this.id) {
+ delete feature._prevHighlighter;
+ } else {
+ feature._lastHighlighter = feature._prevHighlighter;
+ delete feature._prevHighlighter;
+ }
+ layer.drawFeature(feature, feature.style || feature.layer.style ||
+ "default");
+ this.events.triggerEvent("featureunhighlighted", {feature : feature});
+ },
+
+ /**
+ * Method: select
+ * Add feature to the layer's selectedFeature array, render the feature as
+ * selected, and call the onSelect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ select: function(feature) {
+ var cont = this.onBeforeSelect.call(this.scope, feature);
+ var layer = feature.layer;
+ if(cont !== false) {
+ cont = layer.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ layer.selectedFeatures.push(feature);
+ this.highlight(feature);
+ // if the feature handler isn't involved in the feature
+ // selection (because the box handler is used or the
+ // feature is selected programatically) we fake the
+ // feature handler to allow unselecting on click
+ if(!this.handlers.feature.lastFeature) {
+ this.handlers.feature.lastFeature = layer.selectedFeatures[0];
+ }
+ layer.events.triggerEvent("featureselected", {feature: feature});
+ this.onSelect.call(this.scope, feature);
+ }
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the layer's selectedFeature array, render the feature as
+ * normal, and call the onUnselect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ var layer = feature.layer;
+ // Store feature style for restoration later
+ this.unhighlight(feature);
+ OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
+ layer.events.triggerEvent("featureunselected", {feature: feature});
+ this.onUnselect.call(this.scope, feature);
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is true
+ * on.
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
+ */
+ selectBox: function(position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ var bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ // if multiple is false, first deselect currently selected features
+ if (!this.multipleSelect()) {
+ this.unselectAll();
+ }
+
+ // because we're using a box, we consider we want multiple selection
+ var prevMultiple = this.multiple;
+ this.multiple = true;
+ var layers = this.layers || [this.layer];
+ this.events.triggerEvent("boxselectionstart", {layers: layers});
+ var layer;
+ for(var l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ for(var i=0, len = layer.features.length; i<len; ++i) {
+ var feature = layer.features[i];
+ // check if the feature is displayed
+ if (!feature.getVisibility()) {
+ continue;
+ }
+
+ if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+ this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+ if (bounds.toGeometry().intersects(feature.geometry)) {
+ if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ }
+ }
+ }
+ this.multiple = prevMultiple;
+ this.events.triggerEvent("boxselectionend", {layers: layers});
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.handlers.feature.setMap(map);
+ if (this.box) {
+ this.handlers.box.setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Attach a new layer to the control, overriding any existing layers.
+ *
+ * Parameters:
+ * layers - Array of {<OpenLayers.Layer.Vector>} or a single
+ * {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layers) {
+ var isActive = this.active;
+ this.unselectAll();
+ this.deactivate();
+ if(this.layers) {
+ this.layer.destroy();
+ this.layers = null;
+ }
+ this.initLayer(layers);
+ this.handlers.feature.layer = this.layer;
+ if (isActive) {
+ this.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/Point.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Point
+ * Handler to draw a point on the map. Point is displayed on activation,
+ * moves on mouse move, and is finished on mouse up. The handler triggers
+ * callbacks for 'done', 'cancel', and 'modify'. The modify callback is
+ * called with each change in the sketch and will receive the latest point
+ * drawn. Create a new instance with the <OpenLayers.Handler.Point>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: point
+ * {<OpenLayers.Feature.Vector>} The currently drawn point
+ */
+ point: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+ */
+ layer: null,
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Property: mouseDown
+ * {Boolean} The mouse is down
+ */
+ mouseDown: false,
+
+ /**
+ * Property: stoppedDown
+ * {Boolean} Indicate whether the last mousedown stopped the event
+ * propagation.
+ */
+ stoppedDown: null,
+
+ /**
+ * Property: lastDown
+ * {<OpenLayers.Pixel>} Location of the last mouse down
+ */
+ lastDown: null,
+
+ /**
+ * Property: lastUp
+ * {<OpenLayers.Pixel>}
+ */
+ lastUp: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Leave the feature rendered until destroyFeature is called.
+ * Default is false. If set to true, the feature remains rendered until
+ * destroyFeature is called, typically by deactivating the handler or
+ * starting another drawing.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: stopDown
+ * {Boolean} Stop event propagation on mousedown. Must be false to
+ * allow "pan while drawing". Defaults to false.
+ */
+ stopDown: false,
+
+ /**
+ * APIPropery: stopUp
+ * {Boolean} Stop event propagation on mouse. Must be false to
+ * allow "pan while dragging". Defaults to fase.
+ */
+ stopUp: false,
+
+ /**
+ * Property: layerOptions
+ * {Object} Any optional properties to be set on the sketch layer.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between down and up (mousedown
+ * and mouseup, or touchstart and touchend) for the handler to
+ * add a new point. If set to an integer value, if the
+ * displacement between down and up is great to this value
+ * no point will be added. Default value is 5.
+ */
+ pixelTolerance: 5,
+
+ /**
+ * Property: lastTouchPx
+ * {<OpenLayers.Pixel>} The last pixel used to know the distance between
+ * two touches (for double touch).
+ */
+ lastTouchPx: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Point
+ * Create a new point handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the point geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+ initialize: function(control, callbacks, options) {
+ if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
+ this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+ }
+
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: activate
+ * turn on the handler
+ */
+ activate: function() {
+ if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ return false;
+ }
+ // create temporary vector layer for rendering geometry sketch
+ // TBD: this could be moved to initialize/destroy - setting visibility here
+ var options = OpenLayers.Util.extend({
+ displayInLayerSwitcher: false,
+ // indicate that the temp vector layer will never be out of range
+ // without this, resolution properties must be specified at the
+ // map-level for this temporary layer to init its resolutions
+ // correctly
+ calculateInRange: OpenLayers.Function.True,
+ wrapDateLine: this.citeCompliant
+ }, this.layerOptions);
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+ this.map.addLayer(this.layer);
+ return true;
+ },
+
+ /**
+ * Method: createFeature
+ * Add temporary features
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.callback("create", [this.point.geometry, this.point]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.point], {silent: true});
+ },
+
+ /**
+ * APIMethod: deactivate
+ * turn off the handler
+ */
+ deactivate: function() {
+ if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ return false;
+ }
+ this.cancel();
+ // If a layer's map property is set to null, it means that that layer
+ // isn't added to the map. Since we ourself added the layer to the map
+ // in activate(), we can assume that if this.layer.map is null it means
+ // that the layer has been destroyed (as a result of map.destroy() for
+ // example.
+ if (this.layer.map != null) {
+ this.destroyFeature(true);
+ this.layer.destroy(false);
+ }
+ this.layer = null;
+ return true;
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy the temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ if(this.layer && (force || !this.persist)) {
+ this.layer.destroyFeatures();
+ }
+ this.point = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 1) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: finalize
+ * Finish the geometry and call the "done" callback.
+ *
+ * Parameters:
+ * cancel - {Boolean} Call cancel instead of done callback. Default
+ * is false.
+ */
+ finalize: function(cancel) {
+ var key = cancel ? "cancel" : "done";
+ this.mouseDown = false;
+ this.lastDown = null;
+ this.lastUp = null;
+ this.lastTouchPx = null;
+ this.callback(key, [this.geometryClone()]);
+ this.destroyFeature(cancel);
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ this.finalize(true);
+ },
+
+ /**
+ * Method: click
+ * Handle clicks. Clicks are stopped from propagating to other listeners
+ * on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ click: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks. Double-clicks are stopped from propagating to other
+ * listeners on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given a pixel location.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ modifyFeature: function(pixel) {
+ if(!this.point) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.point, false]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render features on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>}
+ */
+ getGeometry: function() {
+ var geometry = this.point && this.point.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPoint([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * Method: geometryClone
+ * Return a clone of the relevant geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ geometryClone: function() {
+ var geom = this.getGeometry();
+ return geom && geom.clone();
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousedown: function(evt) {
+ return this.down(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.lastTouchPx = evt.xy;
+ return this.down(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousemove: function(evt) {
+ return this.move(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchmove: function(evt) {
+ this.lastTouchPx = evt.xy;
+ return this.move(evt);
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mouseup: function(evt) {
+ return this.up(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchend: function(evt) {
+ evt.xy = this.lastTouchPx;
+ return this.up(evt);
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ if(!this.touch) { // no point displayed until up on touch devices
+ this.modifyFeature(evt.xy);
+ }
+ this.stoppedDown = this.stopDown;
+ return !this.stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(!this.touch // no point displayed until up on touch devices
+ && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to the control.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ this.mouseDown = false;
+ this.stoppedDown = this.stopDown;
+
+ // check keyboard modifiers
+ if(!this.checkModifiers(evt)) {
+ return true;
+ }
+ // ignore double-clicks
+ if (this.lastUp && this.lastUp.equals(evt.xy)) {
+ return true;
+ }
+ if (this.lastDown && this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.lastUp = evt.xy;
+ this.finalize();
+ return !this.stopUp;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouse out. For better user experience reset mouseDown
+ * and stoppedDown when the mouse leaves the map viewport.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ */
+ mouseout: function(evt) {
+ if(OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ }
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance.
+ *
+ * Returns:
+ * {Boolean} The event is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(pixel1, pixel2, tolerance) {
+ var passes = true;
+
+ if (tolerance != null && pixel1 && pixel2) {
+ var dist = pixel1.distanceTo(pixel2);
+ if (dist > tolerance) {
+ passes = false;
+ }
+ }
+ return passes;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Point"
+});
+/* ======================================================================
+ OpenLayers/Handler/Path.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Path
+ * Handler to draw a path on the map. Path is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Point>
+ */
+OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
+
+ /**
+ * Property: line
+ * {<OpenLayers.Feature.Vector>}
+ */
+ line: null,
+
+ /**
+ * APIProperty: maxVertices
+ * {Number} The maximum number of vertices which can be drawn by this
+ * handler. When the number of vertices reaches maxVertices, the
+ * geometry is automatically finalized. Default is null.
+ */
+ maxVertices: null,
+
+ /**
+ * Property: doubleTouchTolerance
+ * {Number} Maximum number of pixels between two touches for
+ * the gesture to be considered a "finalize feature" action.
+ * Default is 20.
+ */
+ doubleTouchTolerance: 20,
+
+ /**
+ * Property: freehand
+ * {Boolean} In freehand mode, the handler starts the path on mouse down,
+ * adds a point for every mouse move, and finishes the path on mouse up.
+ * Outside of freehand mode, a point is added to the path on every mouse
+ * click and double-click finishes the path.
+ */
+ freehand: false,
+
+ /**
+ * Property: freehandToggle
+ * {String} If set, freehandToggle is checked on mouse events and will set
+ * the freehand mode to the opposite of this.freehand. To disallow
+ * toggling between freehand and non-freehand mode, set freehandToggle to
+ * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
+ */
+ freehandToggle: 'shiftKey',
+
+ /**
+ * Property: timerId
+ * {Integer} The timer used to test the double touch.
+ */
+ timerId: null,
+
+ /**
+ * Property: redoStack
+ * {Array} Stack containing points removed with <undo>.
+ */
+ redoStack: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Path
+ * Create a new path hander
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the linestring geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([this.point.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.line, this.point], {silent: true});
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Point.prototype.destroyFeature.call(
+ this, force);
+ this.line = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 2) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: removePoint
+ * Destroy the temporary point.
+ */
+ removePoint: function() {
+ if(this.point) {
+ this.layer.removeFeatures([this.point]);
+ }
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry. Send the point index to override
+ * the behavior of LinearRing that disregards adding duplicate points.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ this.layer.removeFeatures([this.point]);
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
+ );
+ this.line.geometry.addComponent(
+ this.point.geometry, this.line.geometry.components.length
+ );
+ this.layer.addFeatures([this.point]);
+ this.callback("point", [this.point.geometry, this.getGeometry()]);
+ this.callback("modify", [this.point.geometry, this.getSketch()]);
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertXY
+ * Insert a point in the current sketch given x & y coordinates. The new
+ * point is inserted immediately before the most recently drawn point.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ this.line.geometry.addComponent(
+ new OpenLayers.Geometry.Point(x, y),
+ this.getCurrentPointIndex()
+ );
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ var p0 = this.line.geometry.components[previousIndex];
+ if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) {
+ this.insertXY(p0.x + dx, p0.y + dy);
+ }
+ },
+
+ /**
+ * Method: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ direction *= Math.PI / 180;
+ var dx = length * Math.cos(direction);
+ var dy = length * Math.sin(direction);
+ this.insertDeltaXY(dx, dy);
+ },
+
+ /**
+ * Method: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ if (previousIndex > 0) {
+ var p1 = this.line.geometry.components[previousIndex];
+ var p0 = this.line.geometry.components[previousIndex-1];
+ var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x);
+ this.insertDirectionLength(
+ (theta * 180 / Math.PI) + deflection, length
+ );
+ }
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 1;
+ },
+
+
+ /**
+ * Method: undo
+ * Remove the most recently added point in the sketch geometry.
+ *
+ * Returns:
+ * {Boolean} A point was removed.
+ */
+ undo: function() {
+ var geometry = this.line.geometry;
+ var components = geometry.components;
+ var index = this.getCurrentPointIndex() - 1;
+ var target = components[index];
+ var undone = geometry.removeComponent(target);
+ if (undone) {
+ // On touch devices, set the current ("mouse location") point to
+ // match the last digitized point.
+ if (this.touch && index > 0) {
+ components = geometry.components; // safety
+ var lastpt = components[index - 1];
+ var curptidx = this.getCurrentPointIndex();
+ var curpt = components[curptidx];
+ curpt.x = lastpt.x;
+ curpt.y = lastpt.y;
+ }
+ if (!this.redoStack) {
+ this.redoStack = [];
+ }
+ this.redoStack.push(target);
+ this.drawFeature();
+ }
+ return undone;
+ },
+
+ /**
+ * Method: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} A point was added.
+ */
+ redo: function() {
+ var target = this.redoStack && this.redoStack.pop();
+ if (target) {
+ this.line.geometry.addComponent(target, this.getCurrentPointIndex());
+ this.drawFeature();
+ }
+ return !!target;
+ },
+
+ /**
+ * Method: freehandMode
+ * Determine whether to behave in freehand mode or not.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ freehandMode: function(evt) {
+ return (this.freehandToggle && evt[this.freehandToggle]) ?
+ !this.freehand : this.freehand;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given the new point
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest
+ * point.
+ * drawing - {Boolean} Indicate if we're currently drawing.
+ */
+ modifyFeature: function(pixel, drawing) {
+ if(!this.line) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.line, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.line;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>}
+ */
+ getGeometry: function() {
+ var geometry = this.line && this.line.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * method: touchstart
+ * handle touchstart.
+ *
+ * parameters:
+ * evt - {event} the browser event
+ *
+ * returns:
+ * {boolean} allow event propagation
+ */
+ touchstart: function(evt) {
+ if (this.timerId &&
+ this.passesTolerance(this.lastTouchPx, evt.xy,
+ this.doubleTouchTolerance)) {
+ // double-tap, finalize the geometry
+ this.finishGeometry();
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ return false;
+ } else {
+ if (this.timerId) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.timerId = null;
+ }, this), 300);
+ return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt);
+ }
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Add a new point to the geometry and
+ * render it. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ var stopDown = this.stopDown;
+ if(this.freehandMode(evt)) {
+ stopDown = true;
+ if (this.touch) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ OpenLayers.Event.stop(evt);
+ }
+ }
+ if (!this.touch && (!this.lastDown ||
+ !this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance))) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ this.stoppedDown = stopDown;
+ return !stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ if(this.maxVertices && this.line &&
+ this.line.geometry.components.length === this.maxVertices) {
+ this.removePoint();
+ this.finalize();
+ } else {
+ this.addPoint(evt.xy);
+ }
+ return false;
+ }
+ if (!this.touch && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to
+ * the control. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if (this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.removePoint();
+ this.finalize();
+ } else {
+ if (this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.lastUp == null && this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.addPoint(evt.xy);
+ this.lastUp = evt.xy;
+ if(this.line.geometry.components.length === this.maxVertices + 1) {
+ this.finishGeometry();
+ }
+ }
+ }
+ }
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ return !this.stopUp;
+ },
+
+ /**
+ * APIMethod: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 1;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ if(!this.freehandMode(evt)) {
+ this.finishGeometry();
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Path"
+});
+/* ======================================================================
+ OpenLayers/Spherical.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Namespace: Spherical
+ * The OpenLayers.Spherical namespace includes utility functions for
+ * calculations on the basis of a spherical earth (ignoring ellipsoidal
+ * effects), which is accurate enough for most purposes.
+ *
+ * Relevant links:
+ * * http://www.movable-type.co.uk/scripts/latlong.html
+ * * http://code.google.com/apis/maps/documentation/javascript/reference.html#spherical
+ */
+
+OpenLayers.Spherical = OpenLayers.Spherical || {};
+
+OpenLayers.Spherical.DEFAULT_RADIUS = 6378137;
+
+/**
+ * APIFunction: computeDistanceBetween
+ * Computes the distance between two LonLats.
+ *
+ * Parameters:
+ * from - {<OpenLayers.LonLat>} or {Object} Starting point. A LonLat or
+ * a JavaScript literal with lon lat properties.
+ * to - {<OpenLayers.LonLat>} or {Object} Ending point. A LonLat or a
+ * JavaScript literal with lon lat properties.
+ * radius - {Float} The radius. Optional. Defaults to 6378137 meters.
+ *
+ * Returns:
+ * {Float} The distance in meters.
+ */
+OpenLayers.Spherical.computeDistanceBetween = function(from, to, radius) {
+ var R = radius || OpenLayers.Spherical.DEFAULT_RADIUS;
+ var sinHalfDeltaLon = Math.sin(Math.PI * (to.lon - from.lon) / 360);
+ var sinHalfDeltaLat = Math.sin(Math.PI * (to.lat - from.lat) / 360);
+ var a = sinHalfDeltaLat * sinHalfDeltaLat +
+ sinHalfDeltaLon * sinHalfDeltaLon * Math.cos(Math.PI * from.lat / 180) * Math.cos(Math.PI * to.lat / 180);
+ return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+};
+
+
+/**
+ * APIFunction: computeHeading
+ * Computes the heading from one LonLat to another LonLat.
+ *
+ * Parameters:
+ * from - {<OpenLayers.LonLat>} or {Object} Starting point. A LonLat or
+ * a JavaScript literal with lon lat properties.
+ * to - {<OpenLayers.LonLat>} or {Object} Ending point. A LonLat or a
+ * JavaScript literal with lon lat properties.
+ *
+ * Returns:
+ * {Float} The heading in degrees.
+ */
+OpenLayers.Spherical.computeHeading = function(from, to) {
+ var y = Math.sin(Math.PI * (from.lon - to.lon) / 180) * Math.cos(Math.PI * to.lat / 180);
+ var x = Math.cos(Math.PI * from.lat / 180) * Math.sin(Math.PI * to.lat / 180) -
+ Math.sin(Math.PI * from.lat / 180) * Math.cos(Math.PI * to.lat / 180) * Math.cos(Math.PI * (from.lon - to.lon) / 180);
+ return 180 * Math.atan2(y, x) / Math.PI;
+};
+/* ======================================================================
+ OpenLayers/Control/CacheWrite.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Control.CacheWrite
+ * A control for caching image tiles in the browser's local storage. The
+ * <OpenLayers.Control.CacheRead> control is used to fetch and use the cached
+ * tile images.
+ *
+ * Note: Before using this control on any layer that is not your own, make sure
+ * that the terms of service of the tile provider allow local storage of tiles.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * To register events in the constructor, configure <eventListeners>.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * cachefull - Triggered when the cache is full. Listeners receive an
+ * object with a tile property as first argument. The tile references
+ * the tile that couldn't be cached.
+ */
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} Object with event listeners, keyed by event name. An optional
+ * scope property defines the scope that listeners will be executed in.
+ */
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, caching
+ * will be enabled for these layers only, otherwise for all cacheable
+ * layers.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: imageFormat
+ * {String} The image format used for caching. The default is "image/png".
+ * Supported formats depend on the user agent. If an unsupported
+ * <imageFormat> is provided, "image/png" will be used. For aerial
+ * imagery, "image/jpeg" is recommended.
+ */
+ imageFormat: "image/png",
+
+ /**
+ * Property: quotaRegEx
+ * {RegExp}
+ */
+ quotaRegEx: (/quota/i),
+
+ /**
+ * Constructor: OpenLayers.Control.CacheWrite
+ *
+ * Parameters:
+ * options - {Object} Object with API properties for this control.
+ */
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ var i, layers = this.layers || map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.addLayer({layer: layers[i]});
+ }
+ if (!this.layers) {
+ map.events.on({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Adds a layer to the control. Once added, tiles requested for this layer
+ * will be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ addLayer: function(evt) {
+ evt.layer.events.on({
+ tileloadstart: this.makeSameOrigin,
+ tileloaded: this.onTileLoaded,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeLayer
+ * Removes a layer from the control. Once removed, tiles requested for this
+ * layer will no longer be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ removeLayer: function(evt) {
+ evt.layer.events.un({
+ tileloadstart: this.makeSameOrigin,
+ tileloaded: this.onTileLoaded,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: makeSameOrigin
+ * If the tile does not have CORS image loading enabled and is from a
+ * different origin, use OpenLayers.ProxyHost to make it a same origin url.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ makeSameOrigin: function(evt) {
+ if (this.active) {
+ var tile = evt.tile;
+ if (tile instanceof OpenLayers.Tile.Image &&
+ !tile.crossOriginKeyword &&
+ tile.url.substr(0, 5) !== "data:") {
+ var sameOriginUrl = OpenLayers.Request.makeSameOrigin(
+ tile.url, OpenLayers.ProxyHost
+ );
+ OpenLayers.Control.CacheWrite.urlMap[sameOriginUrl] = tile.url;
+ tile.url = sameOriginUrl;
+ }
+ }
+ },
+
+ /**
+ * Method: onTileLoaded
+ * Decides whether a tile can be cached and calls the cache method.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onTileLoaded: function(evt) {
+ if (this.active && !evt.aborted &&
+ evt.tile instanceof OpenLayers.Tile.Image &&
+ evt.tile.url.substr(0, 5) !== 'data:') {
+ this.cache({tile: evt.tile});
+ delete OpenLayers.Control.CacheWrite.urlMap[evt.tile.url];
+ }
+ },
+
+ /**
+ * Method: cache
+ * Adds a tile to the cache. When the cache is full, the "cachefull" event
+ * is triggered.
+ *
+ * Parameters:
+ * obj - {Object} Object with a tile property, tile being the
+ * <OpenLayers.Tile.Image> with the data to add to the cache
+ */
+ cache: function(obj) {
+ if (window.localStorage) {
+ var tile = obj.tile;
+ try {
+ var canvasContext = tile.getCanvasContext();
+ if (canvasContext) {
+ var urlMap = OpenLayers.Control.CacheWrite.urlMap;
+ var url = urlMap[tile.url] || tile.url;
+ window.localStorage.setItem(
+ "olCache_" + url,
+ canvasContext.canvas.toDataURL(this.imageFormat)
+ );
+ }
+ } catch(e) {
+ // local storage full or CORS violation
+ var reason = e.name || e.message;
+ if (reason && this.quotaRegEx.test(reason)) {
+ this.events.triggerEvent("cachefull", {tile: tile});
+ } else {
+ OpenLayers.Console.error(e.toString());
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ if (this.layers || this.map) {
+ var i, layers = this.layers || this.map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.removeLayer({layer: layers[i]});
+ }
+ }
+ if (this.map) {
+ this.map.events.un({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.CacheWrite"
+});
+
+/**
+ * APIFunction: OpenLayers.Control.CacheWrite.clearCache
+ * Clears all tiles cached with <OpenLayers.Control.CacheWrite> from the cache.
+ */
+OpenLayers.Control.CacheWrite.clearCache = function() {
+ if (!window.localStorage) { return; }
+ var i, key;
+ for (i=window.localStorage.length-1; i>=0; --i) {
+ key = window.localStorage.key(i);
+ if (key.substr(0, 8) === "olCache_") {
+ window.localStorage.removeItem(key);
+ }
+ }
+};
+
+/**
+ * Property: OpenLayers.Control.CacheWrite.urlMap
+ * {Object} Mapping of same origin urls to cache url keys. Entries will be
+ * deleted as soon as a tile was cached.
+ */
+OpenLayers.Control.CacheWrite.urlMap = {};
+
+
+/* ======================================================================
+ OpenLayers/Format/Context.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Context
+ * Base class for both Format.WMC and Format.OWSContext
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.Context = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * Property: layerOptions
+ * {Object} Default options for layers created by the parser. These
+ * options are overridden by the options which are read from the
+ * capabilities document.
+ */
+ layerOptions: null,
+
+ /**
+ * Property: layerParams
+ * {Object} Default parameters for layers created by the parser. This
+ * can be used e.g. to override DEFAULT_PARAMS for
+ * OpenLayers.Layer.WMS.
+ */
+ layerParams: null,
+
+ /**
+ * Constructor: OpenLayers.Format.Context
+ * Create a new parser for Context documents.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read Context data from a string, and return an object with map
+ * properties and a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ * options - {Object} The options object must contain a map property. If
+ * the map property is a string, it must be the id of a dom element
+ * where the new map will be placed. If the map property is an
+ * <OpenLayers.Map>, the layers from the context document will be added
+ * to the map.
+ *
+ * Returns:
+ * {<OpenLayers.Map>} A map based on the context.
+ */
+ read: function(data, options) {
+ var context = OpenLayers.Format.XML.VersionedOGC.prototype.read.apply(this,
+ arguments);
+ var map;
+ if(options && options.map) {
+ this.context = context;
+ if(options.map instanceof OpenLayers.Map) {
+ map = this.mergeContextToMap(context, options.map);
+ } else {
+ var mapOptions = options.map;
+ if(OpenLayers.Util.isElement(mapOptions) ||
+ typeof mapOptions == "string") {
+ // we assume mapOptions references a div
+ // element
+ mapOptions = {div: mapOptions};
+ }
+ map = this.contextToMap(context, mapOptions);
+ }
+ } else {
+ // not documented as part of the API, provided as a non-API option
+ map = context;
+ }
+ return map;
+ },
+
+ /**
+ * Method: getLayerFromContext
+ * Create a WMS layer from a layerContext object.
+ *
+ * Parameters:
+ * layerContext - {Object} An object representing a WMS layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} A WMS layer.
+ */
+ getLayerFromContext: function(layerContext) {
+ var i, len;
+ // fill initial options object from layerContext
+ var options = {
+ queryable: layerContext.queryable, //keep queryable for api compatibility
+ visibility: layerContext.visibility,
+ maxExtent: layerContext.maxExtent,
+ metadata: OpenLayers.Util.applyDefaults(layerContext.metadata,
+ {styles: layerContext.styles,
+ formats: layerContext.formats,
+ "abstract": layerContext["abstract"],
+ dataURL: layerContext.dataURL
+ }),
+ numZoomLevels: layerContext.numZoomLevels,
+ units: layerContext.units,
+ isBaseLayer: layerContext.isBaseLayer,
+ opacity: layerContext.opacity,
+ displayInLayerSwitcher: layerContext.displayInLayerSwitcher,
+ singleTile: layerContext.singleTile,
+ tileSize: (layerContext.tileSize) ?
+ new OpenLayers.Size(
+ layerContext.tileSize.width,
+ layerContext.tileSize.height
+ ) : undefined,
+ minScale: layerContext.minScale || layerContext.maxScaleDenominator,
+ maxScale: layerContext.maxScale || layerContext.minScaleDenominator,
+ srs: layerContext.srs,
+ dimensions: layerContext.dimensions,
+ metadataURL: layerContext.metadataURL
+ };
+ if (this.layerOptions) {
+ OpenLayers.Util.applyDefaults(options, this.layerOptions);
+ }
+
+ var params = {
+ layers: layerContext.name,
+ transparent: layerContext.transparent,
+ version: layerContext.version
+ };
+ if (layerContext.formats && layerContext.formats.length>0) {
+ // set default value for params if current attribute is not positionned
+ params.format = layerContext.formats[0].value;
+ for (i=0, len=layerContext.formats.length; i<len; i++) {
+ var format = layerContext.formats[i];
+ if (format.current == true) {
+ params.format = format.value;
+ break;
+ }
+ }
+ }
+ if (layerContext.styles && layerContext.styles.length>0) {
+ for (i=0, len=layerContext.styles.length; i<len; i++) {
+ var style = layerContext.styles[i];
+ if (style.current == true) {
+ // three style types to consider
+ // 1) linked SLD
+ // 2) inline SLD
+ // 3) named style
+ if(style.href) {
+ params.sld = style.href;
+ } else if(style.body) {
+ params.sld_body = style.body;
+ } else {
+ params.styles = style.name;
+ }
+ break;
+ }
+ }
+ }
+ if (this.layerParams) {
+ OpenLayers.Util.applyDefaults(params, this.layerParams);
+ }
+
+ var layer = null;
+ var service = layerContext.service;
+ if (service == OpenLayers.Format.Context.serviceTypes.WFS) {
+ options.strategies = [new OpenLayers.Strategy.BBOX()];
+ options.protocol = new OpenLayers.Protocol.WFS({
+ url: layerContext.url,
+ // since we do not know featureNS, let the protocol
+ // determine it automagically using featurePrefix
+ featurePrefix: layerContext.name.split(":")[0],
+ featureType: layerContext.name.split(":").pop()
+ });
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ } else if (service == OpenLayers.Format.Context.serviceTypes.KML) {
+ // use a vector layer with an HTTP Protcol and a Fixed strategy
+ options.strategies = [new OpenLayers.Strategy.Fixed()];
+ options.protocol = new OpenLayers.Protocol.HTTP({
+ url: layerContext.url,
+ format: new OpenLayers.Format.KML()
+ });
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ } else if (service == OpenLayers.Format.Context.serviceTypes.GML) {
+ // use a vector layer with a HTTP Protocol and a Fixed strategy
+ options.strategies = [new OpenLayers.Strategy.Fixed()];
+ options.protocol = new OpenLayers.Protocol.HTTP({
+ url: layerContext.url,
+ format: new OpenLayers.Format.GML()
+ });
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ } else if (layerContext.features) {
+ // inline GML or KML features
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ layer.addFeatures(layerContext.features);
+ } else if (layerContext.categoryLayer !== true) {
+ layer = new OpenLayers.Layer.WMS(
+ layerContext.title || layerContext.name,
+ layerContext.url,
+ params,
+ options
+ );
+ }
+ return layer;
+ },
+
+ /**
+ * Method: getLayersFromContext
+ * Create an array of layers from an array of layerContext objects.
+ *
+ * Parameters:
+ * layersContext - {Array(Object)} An array of objects representing layers.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} An array of layers.
+ */
+ getLayersFromContext: function(layersContext) {
+ var layers = [];
+ for (var i=0, len=layersContext.length; i<len; i++) {
+ var layer = this.getLayerFromContext(layersContext[i]);
+ if (layer !== null) {
+ layers.push(layer);
+ }
+ }
+ return layers;
+ },
+
+ /**
+ * Method: contextToMap
+ * Create a map given a context object.
+ *
+ * Parameters:
+ * context - {Object} The context object.
+ * options - {Object} Default map options.
+ *
+ * Returns:
+ * {<OpenLayers.Map>} A map based on the context object.
+ */
+ contextToMap: function(context, options) {
+ options = OpenLayers.Util.applyDefaults({
+ maxExtent: context.maxExtent,
+ projection: context.projection,
+ units: context.units
+ }, options);
+
+ if (options.maxExtent) {
+ options.maxResolution =
+ options.maxExtent.getWidth() / OpenLayers.Map.TILE_WIDTH;
+ }
+
+ var metadata = {
+ contactInformation: context.contactInformation,
+ "abstract": context["abstract"],
+ keywords: context.keywords,
+ logo: context.logo,
+ descriptionURL: context.descriptionURL
+ };
+
+ options.metadata = metadata;
+
+ var map = new OpenLayers.Map(options);
+ map.addLayers(this.getLayersFromContext(context.layersContext));
+ map.setCenter(
+ context.bounds.getCenterLonLat(),
+ map.getZoomForExtent(context.bounds, true)
+ );
+ return map;
+ },
+
+ /**
+ * Method: mergeContextToMap
+ * Add layers from a context object to a map.
+ *
+ * Parameters:
+ * context - {Object} The context object.
+ * map - {<OpenLayers.Map>} The map.
+ *
+ * Returns:
+ * {<OpenLayers.Map>} The same map with layers added.
+ */
+ mergeContextToMap: function(context, map) {
+ map.addLayers(this.getLayersFromContext(context.layersContext));
+ return map;
+ },
+
+ /**
+ * APIMethod: write
+ * Write a context document given a map.
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Map> | Object} A map or context object.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} A context document string.
+ */
+ write: function(obj, options) {
+ obj = this.toContext(obj);
+ return OpenLayers.Format.XML.VersionedOGC.prototype.write.apply(this,
+ arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Context"
+});
+
+/**
+ * Constant: OpenLayers.Format.Context.serviceTypes
+ * Enumeration for service types
+ */
+OpenLayers.Format.Context.serviceTypes = {
+ "WMS": "urn:ogc:serviceType:WMS",
+ "WFS": "urn:ogc:serviceType:WFS",
+ "WCS": "urn:ogc:serviceType:WCS",
+ "GML": "urn:ogc:serviceType:GML",
+ "SLD": "urn:ogc:serviceType:SLD",
+ "FES": "urn:ogc:serviceType:FES",
+ "KML": "urn:ogc:serviceType:KML"
+};
+/* ======================================================================
+ OpenLayers/Format/WMC.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/Context.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC
+ * Read and write Web Map Context documents.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Context>
+ */
+OpenLayers.Format.WMC = OpenLayers.Class(OpenLayers.Format.Context, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC
+ * Create a new parser for Web Map Context documents.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: layerToContext
+ * Create a layer context object given a wms layer object.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} The layer.
+ *
+ * Returns:
+ * {Object} A layer context object.
+ */
+ layerToContext: function(layer) {
+ var parser = this.getParser();
+ var layerContext = {
+ queryable: layer.queryable,
+ visibility: layer.visibility,
+ name: layer.params["LAYERS"],
+ title: layer.name,
+ "abstract": layer.metadata["abstract"],
+ dataURL: layer.metadata.dataURL,
+ metadataURL: layer.metadataURL,
+ server: {
+ version: layer.params["VERSION"],
+ url: layer.url
+ },
+ maxExtent: layer.maxExtent,
+ transparent: layer.params["TRANSPARENT"],
+ numZoomLevels: layer.numZoomLevels,
+ units: layer.units,
+ isBaseLayer: layer.isBaseLayer,
+ opacity: layer.opacity == 1 ? undefined : layer.opacity,
+ displayInLayerSwitcher: layer.displayInLayerSwitcher,
+ singleTile: layer.singleTile,
+ tileSize: (layer.singleTile || !layer.tileSize) ?
+ undefined : {width: layer.tileSize.w, height: layer.tileSize.h},
+ minScale : (layer.options.resolutions ||
+ layer.options.scales ||
+ layer.options.maxResolution ||
+ layer.options.minScale) ?
+ layer.minScale : undefined,
+ maxScale : (layer.options.resolutions ||
+ layer.options.scales ||
+ layer.options.minResolution ||
+ layer.options.maxScale) ?
+ layer.maxScale : undefined,
+ formats: [],
+ styles: [],
+ srs: layer.srs,
+ dimensions: layer.dimensions
+ };
+
+
+ if (layer.metadata.servertitle) {
+ layerContext.server.title = layer.metadata.servertitle;
+ }
+
+ if (layer.metadata.formats && layer.metadata.formats.length > 0) {
+ for (var i=0, len=layer.metadata.formats.length; i<len; i++) {
+ var format = layer.metadata.formats[i];
+ layerContext.formats.push({
+ value: format.value,
+ current: (format.value == layer.params["FORMAT"])
+ });
+ }
+ } else {
+ layerContext.formats.push({
+ value: layer.params["FORMAT"],
+ current: true
+ });
+ }
+
+ if (layer.metadata.styles && layer.metadata.styles.length > 0) {
+ for (var i=0, len=layer.metadata.styles.length; i<len; i++) {
+ var style = layer.metadata.styles[i];
+ if ((style.href == layer.params["SLD"]) ||
+ (style.body == layer.params["SLD_BODY"]) ||
+ (style.name == layer.params["STYLES"])) {
+ style.current = true;
+ } else {
+ style.current = false;
+ }
+ layerContext.styles.push(style);
+ }
+ } else {
+ layerContext.styles.push({
+ href: layer.params["SLD"],
+ body: layer.params["SLD_BODY"],
+ name: layer.params["STYLES"] || parser.defaultStyleName,
+ title: parser.defaultStyleTitle,
+ current: true
+ });
+ }
+
+ return layerContext;
+ },
+
+ /**
+ * Method: toContext
+ * Create a context object free from layer given a map or a
+ * context object.
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Map> | Object} The map or context.
+ *
+ * Returns:
+ * {Object} A context object.
+ */
+ toContext: function(obj) {
+ var context = {};
+ var layers = obj.layers;
+ if (obj.CLASS_NAME == "OpenLayers.Map") {
+ var metadata = obj.metadata || {};
+ context.size = obj.getSize();
+ context.bounds = obj.getExtent();
+ context.projection = obj.projection;
+ context.title = obj.title;
+ context.keywords = metadata.keywords;
+ context["abstract"] = metadata["abstract"];
+ context.logo = metadata.logo;
+ context.descriptionURL = metadata.descriptionURL;
+ context.contactInformation = metadata.contactInformation;
+ context.maxExtent = obj.maxExtent;
+ } else {
+ // copy all obj properties except the "layers" property
+ OpenLayers.Util.applyDefaults(context, obj);
+ if (context.layers != undefined) {
+ delete(context.layers);
+ }
+ }
+
+ if (context.layersContext == undefined) {
+ context.layersContext = [];
+ }
+
+ // let's convert layers into layersContext object (if any)
+ if (layers != undefined && OpenLayers.Util.isArray(layers)) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ if (layer instanceof OpenLayers.Layer.WMS) {
+ context.layersContext.push(this.layerToContext(layer));
+ }
+ }
+ }
+ return context;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMC"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMC/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC.js
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1
+ * Superclass for WMC version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMC.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ol: "http://openlayers.org/context",
+ wmc: "http://www.opengis.net/context",
+ sld: "http://www.opengis.net/sld",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: "",
+
+ /**
+ * Method: getNamespacePrefix
+ * Get the namespace prefix for a given uri from the <namespaces> object.
+ *
+ * Returns:
+ * {String} A namespace prefix or null if none found.
+ */
+ getNamespacePrefix: function(uri) {
+ var prefix = null;
+ if(uri == null) {
+ prefix = this.namespaces[this.defaultPrefix];
+ } else {
+ for(prefix in this.namespaces) {
+ if(this.namespaces[prefix] == uri) {
+ break;
+ }
+ }
+ }
+ return prefix;
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wmc",
+
+ /**
+ * Property: rootPrefix
+ * {String} Prefix on the root node that maps to the context namespace URI.
+ */
+ rootPrefix: null,
+
+ /**
+ * Property: defaultStyleName
+ * {String} Style name used if layer has no style param. Default is "".
+ */
+ defaultStyleName: "",
+
+ /**
+ * Property: defaultStyleTitle
+ * {String} Default style title. Default is "Default".
+ */
+ defaultStyleTitle: "Default",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WMC> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ this.rootPrefix = root.prefix;
+ var context = {
+ version: root.getAttribute("version")
+ };
+ this.runChildNodes(context, root);
+ return context;
+ },
+
+ /**
+ * Method: runChildNodes
+ */
+ runChildNodes: function(obj, node) {
+ var children = node.childNodes;
+ var childNode, processor, prefix, local;
+ for(var i=0, len=children.length; i<len; ++i) {
+ childNode = children[i];
+ if(childNode.nodeType == 1) {
+ prefix = this.getNamespacePrefix(childNode.namespaceURI);
+ local = childNode.nodeName.split(":").pop();
+ processor = this["read_" + prefix + "_" + local];
+ if(processor) {
+ processor.apply(this, [obj, childNode]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: read_wmc_General
+ */
+ read_wmc_General: function(context, node) {
+ this.runChildNodes(context, node);
+ },
+
+ /**
+ * Method: read_wmc_BoundingBox
+ */
+ read_wmc_BoundingBox: function(context, node) {
+ context.projection = node.getAttribute("SRS");
+ context.bounds = new OpenLayers.Bounds(
+ node.getAttribute("minx"), node.getAttribute("miny"),
+ node.getAttribute("maxx"), node.getAttribute("maxy")
+ );
+ },
+
+ /**
+ * Method: read_wmc_LayerList
+ */
+ read_wmc_LayerList: function(context, node) {
+ // layersContext is an array containing info for each layer
+ context.layersContext = [];
+ this.runChildNodes(context, node);
+ },
+
+ /**
+ * Method: read_wmc_Layer
+ */
+ read_wmc_Layer: function(context, node) {
+ var layerContext = {
+ visibility: (node.getAttribute("hidden") != "1"),
+ queryable: (node.getAttribute("queryable") == "1"),
+ formats: [],
+ styles: [],
+ metadata: {}
+ };
+
+ this.runChildNodes(layerContext, node);
+ // set properties common to multiple objects on layer options/params
+ context.layersContext.push(layerContext);
+ },
+
+ /**
+ * Method: read_wmc_Extension
+ */
+ read_wmc_Extension: function(obj, node) {
+ this.runChildNodes(obj, node);
+ },
+
+ /**
+ * Method: read_ol_units
+ */
+ read_ol_units: function(layerContext, node) {
+ layerContext.units = this.getChildValue(node);
+ },
+
+ /**
+ * Method: read_ol_maxExtent
+ */
+ read_ol_maxExtent: function(obj, node) {
+ var bounds = new OpenLayers.Bounds(
+ node.getAttribute("minx"), node.getAttribute("miny"),
+ node.getAttribute("maxx"), node.getAttribute("maxy")
+ );
+ obj.maxExtent = bounds;
+ },
+
+ /**
+ * Method: read_ol_transparent
+ */
+ read_ol_transparent: function(layerContext, node) {
+ layerContext.transparent = this.getChildValue(node);
+ },
+
+ /**
+ * Method: read_ol_numZoomLevels
+ */
+ read_ol_numZoomLevels: function(layerContext, node) {
+ layerContext.numZoomLevels = parseInt(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_ol_opacity
+ */
+ read_ol_opacity: function(layerContext, node) {
+ layerContext.opacity = parseFloat(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_ol_singleTile
+ */
+ read_ol_singleTile: function(layerContext, node) {
+ layerContext.singleTile = (this.getChildValue(node) == "true");
+ },
+
+ /**
+ * Method: read_ol_tileSize
+ */
+ read_ol_tileSize: function(layerContext, node) {
+ var obj = {"width": node.getAttribute("width"), "height": node.getAttribute("height")};
+ layerContext.tileSize = obj;
+ },
+
+ /**
+ * Method: read_ol_isBaseLayer
+ */
+ read_ol_isBaseLayer: function(layerContext, node) {
+ layerContext.isBaseLayer = (this.getChildValue(node) == "true");
+ },
+
+ /**
+ * Method: read_ol_displayInLayerSwitcher
+ */
+ read_ol_displayInLayerSwitcher: function(layerContext, node) {
+ layerContext.displayInLayerSwitcher = (this.getChildValue(node) == "true");
+ },
+
+ /**
+ * Method: read_wmc_Server
+ */
+ read_wmc_Server: function(layerContext, node) {
+ layerContext.version = node.getAttribute("version");
+ layerContext.url = this.getOnlineResource_href(node);
+ layerContext.metadata.servertitle = node.getAttribute("title");
+ },
+
+ /**
+ * Method: read_wmc_FormatList
+ */
+ read_wmc_FormatList: function(layerContext, node) {
+ this.runChildNodes(layerContext, node);
+ },
+
+ /**
+ * Method: read_wmc_Format
+ */
+ read_wmc_Format: function(layerContext, node) {
+ var format = {
+ value: this.getChildValue(node)
+ };
+ if(node.getAttribute("current") == "1") {
+ format.current = true;
+ }
+ layerContext.formats.push(format);
+ },
+
+ /**
+ * Method: read_wmc_StyleList
+ */
+ read_wmc_StyleList: function(layerContext, node) {
+ this.runChildNodes(layerContext, node);
+ },
+
+ /**
+ * Method: read_wmc_Style
+ */
+ read_wmc_Style: function(layerContext, node) {
+ var style = {};
+ this.runChildNodes(style, node);
+ if(node.getAttribute("current") == "1") {
+ style.current = true;
+ }
+ layerContext.styles.push(style);
+ },
+
+ /**
+ * Method: read_wmc_SLD
+ */
+ read_wmc_SLD: function(style, node) {
+ this.runChildNodes(style, node);
+ // style either comes back with an href or a body property
+ },
+
+ /**
+ * Method: read_sld_StyledLayerDescriptor
+ */
+ read_sld_StyledLayerDescriptor: function(sld, node) {
+ var xml = OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ sld.body = xml;
+ },
+
+ /**
+ * Method: read_sld_FeatureTypeStyle
+ */
+ read_sld_FeatureTypeStyle: function(sld, node) {
+ var xml = OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ sld.body = xml;
+ },
+
+ /**
+ * Method: read_wmc_OnlineResource
+ */
+ read_wmc_OnlineResource: function(obj, node) {
+ obj.href = this.getAttributeNS(
+ node, this.namespaces.xlink, "href"
+ );
+ },
+
+ /**
+ * Method: read_wmc_Name
+ */
+ read_wmc_Name: function(obj, node) {
+ var name = this.getChildValue(node);
+ if(name) {
+ obj.name = name;
+ }
+ },
+
+ /**
+ * Method: read_wmc_Title
+ */
+ read_wmc_Title: function(obj, node) {
+ var title = this.getChildValue(node);
+ if(title) {
+ obj.title = title;
+ }
+ },
+
+ /**
+ * Method: read_wmc_MetadataURL
+ */
+ read_wmc_MetadataURL: function(layerContext, node) {
+ layerContext.metadataURL = this.getOnlineResource_href(node);
+ },
+
+ /**
+ * Method: read_wmc_KeywordList
+ */
+ read_wmc_KeywordList: function(context, node) {
+ context.keywords = [];
+ this.runChildNodes(context.keywords, node);
+ },
+
+ /**
+ * Method: read_wmc_Keyword
+ */
+ read_wmc_Keyword: function(keywords, node) {
+ keywords.push(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_wmc_Abstract
+ */
+ read_wmc_Abstract: function(obj, node) {
+ var abst = this.getChildValue(node);
+ if(abst) {
+ obj["abstract"] = abst;
+ }
+ },
+
+ /**
+ * Method: read_wmc_LogoURL
+ */
+ read_wmc_LogoURL: function(context, node) {
+ context.logo = {
+ width: node.getAttribute("width"),
+ height: node.getAttribute("height"),
+ format: node.getAttribute("format"),
+ href: this.getOnlineResource_href(node)
+ };
+ },
+
+ /**
+ * Method: read_wmc_DescriptionURL
+ */
+ read_wmc_DescriptionURL: function(context, node) {
+ context.descriptionURL = this.getOnlineResource_href(node);
+ },
+
+ /**
+ * Method: read_wmc_ContactInformation
+ */
+ read_wmc_ContactInformation: function(obj, node) {
+ var contact = {};
+ this.runChildNodes(contact, node);
+ obj.contactInformation = contact;
+ },
+
+ /**
+ * Method: read_wmc_ContactPersonPrimary
+ */
+ read_wmc_ContactPersonPrimary: function(contact, node) {
+ var personPrimary = {};
+ this.runChildNodes(personPrimary, node);
+ contact.personPrimary = personPrimary;
+ },
+
+ /**
+ * Method: read_wmc_ContactPerson
+ */
+ read_wmc_ContactPerson: function(primaryPerson, node) {
+ var person = this.getChildValue(node);
+ if (person) {
+ primaryPerson.person = person;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactOrganization
+ */
+ read_wmc_ContactOrganization: function(primaryPerson, node) {
+ var organization = this.getChildValue(node);
+ if (organization) {
+ primaryPerson.organization = organization;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactPosition
+ */
+ read_wmc_ContactPosition: function(contact, node) {
+ var position = this.getChildValue(node);
+ if (position) {
+ contact.position = position;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactAddress
+ */
+ read_wmc_ContactAddress: function(contact, node) {
+ var contactAddress = {};
+ this.runChildNodes(contactAddress, node);
+ contact.contactAddress = contactAddress;
+ },
+
+ /**
+ * Method: read_wmc_AddressType
+ */
+ read_wmc_AddressType: function(contactAddress, node) {
+ var type = this.getChildValue(node);
+ if (type) {
+ contactAddress.type = type;
+ }
+ },
+
+ /**
+ * Method: read_wmc_Address
+ */
+ read_wmc_Address: function(contactAddress, node) {
+ var address = this.getChildValue(node);
+ if (address) {
+ contactAddress.address = address;
+ }
+ },
+
+ /**
+ * Method: read_wmc_City
+ */
+ read_wmc_City: function(contactAddress, node) {
+ var city = this.getChildValue(node);
+ if (city) {
+ contactAddress.city = city;
+ }
+ },
+
+ /**
+ * Method: read_wmc_StateOrProvince
+ */
+ read_wmc_StateOrProvince: function(contactAddress, node) {
+ var stateOrProvince = this.getChildValue(node);
+ if (stateOrProvince) {
+ contactAddress.stateOrProvince = stateOrProvince;
+ }
+ },
+
+ /**
+ * Method: read_wmc_PostCode
+ */
+ read_wmc_PostCode: function(contactAddress, node) {
+ var postcode = this.getChildValue(node);
+ if (postcode) {
+ contactAddress.postcode = postcode;
+ }
+ },
+
+ /**
+ * Method: read_wmc_Country
+ */
+ read_wmc_Country: function(contactAddress, node) {
+ var country = this.getChildValue(node);
+ if (country) {
+ contactAddress.country = country;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactVoiceTelephone
+ */
+ read_wmc_ContactVoiceTelephone: function(contact, node) {
+ var phone = this.getChildValue(node);
+ if (phone) {
+ contact.phone = phone;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactFacsimileTelephone
+ */
+ read_wmc_ContactFacsimileTelephone: function(contact, node) {
+ var fax = this.getChildValue(node);
+ if (fax) {
+ contact.fax = fax;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactElectronicMailAddress
+ */
+ read_wmc_ContactElectronicMailAddress: function(contact, node) {
+ var email = this.getChildValue(node);
+ if (email) {
+ contact.email = email;
+ }
+ },
+
+ /**
+ * Method: read_wmc_DataURL
+ */
+ read_wmc_DataURL: function(layerContext, node) {
+ layerContext.dataURL = this.getOnlineResource_href(node);
+ },
+
+ /**
+ * Method: read_wmc_LegendURL
+ */
+ read_wmc_LegendURL: function(style, node) {
+ var legend = {
+ width: node.getAttribute('width'),
+ height: node.getAttribute('height'),
+ format: node.getAttribute('format'),
+ href: this.getOnlineResource_href(node)
+ };
+ style.legend = legend;
+ },
+
+ /**
+ * Method: read_wmc_DimensionList
+ */
+ read_wmc_DimensionList: function(layerContext, node) {
+ layerContext.dimensions = {};
+ this.runChildNodes(layerContext.dimensions, node);
+ },
+ /**
+ * Method: read_wmc_Dimension
+ */
+ read_wmc_Dimension: function(dimensions, node) {
+ var name = node.getAttribute("name").toLowerCase();
+
+ var dim = {
+ name: name,
+ units: node.getAttribute("units") || "",
+ unitSymbol: node.getAttribute("unitSymbol") || "",
+ userValue: node.getAttribute("userValue") || "",
+ nearestValue: node.getAttribute("nearestValue") === "1",
+ multipleValues: node.getAttribute("multipleValues") === "1",
+ current: node.getAttribute("current") === "1",
+ "default": node.getAttribute("default") || ""
+ };
+ var values = this.getChildValue(node);
+ dim.values = values.split(",");
+
+ dimensions[dim.name] = dim;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * context - {Object} An object representing the map context.
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} A WMC document string.
+ */
+ write: function(context, options) {
+ var root = this.createElementDefaultNS("ViewContext");
+ this.setAttributes(root, {
+ version: this.VERSION,
+ id: (options && typeof options.id == "string") ?
+ options.id :
+ OpenLayers.Util.createUniqueID("OpenLayers_Context_")
+ });
+
+ // add schemaLocation attribute
+ this.setAttributeNS(
+ root, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ // required General element
+ root.appendChild(this.write_wmc_General(context));
+
+ // required LayerList element
+ root.appendChild(this.write_wmc_LayerList(context));
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Method: createElementDefaultNS
+ * Shorthand for createElementNS with namespace from <defaultPrefix>.
+ * Can optionally be used to set attributes and a text child value.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * childValue - {String} Optional value for text child node.
+ * attributes - {Object} Optional object representing attributes.
+ *
+ * Returns:
+ * {Element} An element node.
+ */
+ createElementDefaultNS: function(name, childValue, attributes) {
+ var node = this.createElementNS(
+ this.namespaces[this.defaultPrefix],
+ name
+ );
+ if(childValue) {
+ node.appendChild(this.createTextNode(childValue));
+ }
+ if(attributes) {
+ this.setAttributes(node, attributes);
+ }
+ return node;
+ },
+
+ /**
+ * Method: setAttributes
+ * Set multiple attributes given key value pairs from an object.
+ *
+ * Parameters:
+ * node - {Element} An element node.
+ * obj - {Object} An object whose properties represent attribute names and
+ * values represent attribute values.
+ */
+ setAttributes: function(node, obj) {
+ var value;
+ for(var name in obj) {
+ value = obj[name].toString();
+ if(value.match(/[A-Z]/)) {
+ // safari lowercases attributes with setAttribute
+ this.setAttributeNS(node, null, name, value);
+ } else {
+ node.setAttribute(name, value);
+ }
+ }
+ },
+
+ /**
+ * Method: write_wmc_General
+ * Create a General node given an context object.
+ *
+ * Parameters:
+ * context - {Object} Context object.
+ *
+ * Returns:
+ * {Element} A WMC General element node.
+ */
+ write_wmc_General: function(context) {
+ var node = this.createElementDefaultNS("General");
+
+ // optional Window element
+ if(context.size) {
+ node.appendChild(this.createElementDefaultNS(
+ "Window", null,
+ {
+ width: context.size.w,
+ height: context.size.h
+ }
+ ));
+ }
+
+ // required BoundingBox element
+ var bounds = context.bounds;
+ node.appendChild(this.createElementDefaultNS(
+ "BoundingBox", null,
+ {
+ minx: bounds.left.toPrecision(18),
+ miny: bounds.bottom.toPrecision(18),
+ maxx: bounds.right.toPrecision(18),
+ maxy: bounds.top.toPrecision(18),
+ SRS: context.projection
+ }
+ ));
+
+ // required Title element
+ node.appendChild(this.createElementDefaultNS(
+ "Title", context.title
+ ));
+
+ // optional KeywordList element
+ if (context.keywords) {
+ node.appendChild(this.write_wmc_KeywordList(context.keywords));
+ }
+
+ // optional Abstract element
+ if (context["abstract"]) {
+ node.appendChild(this.createElementDefaultNS(
+ "Abstract", context["abstract"]
+ ));
+ }
+
+ // Optional LogoURL element
+ if (context.logo) {
+ node.appendChild(this.write_wmc_URLType("LogoURL", context.logo.href, context.logo));
+ }
+
+ // Optional DescriptionURL element
+ if (context.descriptionURL) {
+ node.appendChild(this.write_wmc_URLType("DescriptionURL", context.descriptionURL));
+ }
+
+ // Optional ContactInformation element
+ if (context.contactInformation) {
+ node.appendChild(this.write_wmc_ContactInformation(context.contactInformation));
+ }
+
+ // OpenLayers specific map properties
+ node.appendChild(this.write_ol_MapExtension(context));
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_KeywordList
+ */
+ write_wmc_KeywordList: function(keywords) {
+ var node = this.createElementDefaultNS("KeywordList");
+
+ for (var i=0, len=keywords.length; i<len; i++) {
+ node.appendChild(this.createElementDefaultNS(
+ "Keyword", keywords[i]
+ ));
+ }
+ return node;
+ },
+ /**
+ * Method: write_wmc_ContactInformation
+ */
+ write_wmc_ContactInformation: function(contact) {
+ var node = this.createElementDefaultNS("ContactInformation");
+
+ if (contact.personPrimary) {
+ node.appendChild(this.write_wmc_ContactPersonPrimary(contact.personPrimary));
+ }
+ if (contact.position) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactPosition", contact.position
+ ));
+ }
+ if (contact.contactAddress) {
+ node.appendChild(this.write_wmc_ContactAddress(contact.contactAddress));
+ }
+ if (contact.phone) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactVoiceTelephone", contact.phone
+ ));
+ }
+ if (contact.fax) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactFacsimileTelephone", contact.fax
+ ));
+ }
+ if (contact.email) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactElectronicMailAddress", contact.email
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_ContactPersonPrimary
+ */
+ write_wmc_ContactPersonPrimary: function(personPrimary) {
+ var node = this.createElementDefaultNS("ContactPersonPrimary");
+ if (personPrimary.person) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactPerson", personPrimary.person
+ ));
+ }
+ if (personPrimary.organization) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactOrganization", personPrimary.organization
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_ContactAddress
+ */
+ write_wmc_ContactAddress: function(contactAddress) {
+ var node = this.createElementDefaultNS("ContactAddress");
+ if (contactAddress.type) {
+ node.appendChild(this.createElementDefaultNS(
+ "AddressType", contactAddress.type
+ ));
+ }
+ if (contactAddress.address) {
+ node.appendChild(this.createElementDefaultNS(
+ "Address", contactAddress.address
+ ));
+ }
+ if (contactAddress.city) {
+ node.appendChild(this.createElementDefaultNS(
+ "City", contactAddress.city
+ ));
+ }
+ if (contactAddress.stateOrProvince) {
+ node.appendChild(this.createElementDefaultNS(
+ "StateOrProvince", contactAddress.stateOrProvince
+ ));
+ }
+ if (contactAddress.postcode) {
+ node.appendChild(this.createElementDefaultNS(
+ "PostCode", contactAddress.postcode
+ ));
+ }
+ if (contactAddress.country) {
+ node.appendChild(this.createElementDefaultNS(
+ "Country", contactAddress.country
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_ol_MapExtension
+ */
+ write_ol_MapExtension: function(context) {
+ var node = this.createElementDefaultNS("Extension");
+
+ var bounds = context.maxExtent;
+ if(bounds) {
+ var maxExtent = this.createElementNS(
+ this.namespaces.ol, "ol:maxExtent"
+ );
+ this.setAttributes(maxExtent, {
+ minx: bounds.left.toPrecision(18),
+ miny: bounds.bottom.toPrecision(18),
+ maxx: bounds.right.toPrecision(18),
+ maxy: bounds.top.toPrecision(18)
+ });
+ node.appendChild(maxExtent);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_LayerList
+ * Create a LayerList node given an context object.
+ *
+ * Parameters:
+ * context - {Object} Context object.
+ *
+ * Returns:
+ * {Element} A WMC LayerList element node.
+ */
+ write_wmc_LayerList: function(context) {
+ var list = this.createElementDefaultNS("LayerList");
+
+ for(var i=0, len=context.layersContext.length; i<len; ++i) {
+ list.appendChild(this.write_wmc_Layer(context.layersContext[i]));
+ }
+
+ return list;
+ },
+
+ /**
+ * Method: write_wmc_Layer
+ * Create a Layer node given a layer context object.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.}
+ *
+ * Returns:
+ * {Element} A WMC Layer element node.
+ */
+ write_wmc_Layer: function(context) {
+ var node = this.createElementDefaultNS(
+ "Layer", null, {
+ queryable: context.queryable ? "1" : "0",
+ hidden: context.visibility ? "0" : "1"
+ }
+ );
+
+ // required Server element
+ node.appendChild(this.write_wmc_Server(context));
+
+ // required Name element
+ node.appendChild(this.createElementDefaultNS(
+ "Name", context.name
+ ));
+
+ // required Title element
+ node.appendChild(this.createElementDefaultNS(
+ "Title", context.title
+ ));
+
+ // optional Abstract element
+ if (context["abstract"]) {
+ node.appendChild(this.createElementDefaultNS(
+ "Abstract", context["abstract"]
+ ));
+ }
+
+ // optional DataURL element
+ if (context.dataURL) {
+ node.appendChild(this.write_wmc_URLType("DataURL", context.dataURL));
+ }
+
+ // optional MetadataURL element
+ if (context.metadataURL) {
+ node.appendChild(this.write_wmc_URLType("MetadataURL", context.metadataURL));
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_LayerExtension
+ * Add OpenLayers specific layer parameters to an Extension element.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.
+ *
+ * Returns:
+ * {Element} A WMC Extension element (for a layer).
+ */
+ write_wmc_LayerExtension: function(context) {
+ var node = this.createElementDefaultNS("Extension");
+
+ var bounds = context.maxExtent;
+ var maxExtent = this.createElementNS(
+ this.namespaces.ol, "ol:maxExtent"
+ );
+ this.setAttributes(maxExtent, {
+ minx: bounds.left.toPrecision(18),
+ miny: bounds.bottom.toPrecision(18),
+ maxx: bounds.right.toPrecision(18),
+ maxy: bounds.top.toPrecision(18)
+ });
+ node.appendChild(maxExtent);
+
+ if (context.tileSize && !context.singleTile) {
+ var size = this.createElementNS(
+ this.namespaces.ol, "ol:tileSize"
+ );
+ this.setAttributes(size, context.tileSize);
+ node.appendChild(size);
+ }
+
+ var properties = [
+ "transparent", "numZoomLevels", "units", "isBaseLayer",
+ "opacity", "displayInLayerSwitcher", "singleTile"
+ ];
+ var child;
+ for(var i=0, len=properties.length; i<len; ++i) {
+ child = this.createOLPropertyNode(context, properties[i]);
+ if(child) {
+ node.appendChild(child);
+ }
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: createOLPropertyNode
+ * Create a node representing an OpenLayers property. If the property is
+ * null or undefined, null will be returned.
+ *
+ * Parameters:
+ * obj - {Object} An object.
+ * prop - {String} A property.
+ *
+ * Returns:
+ * {Element} A property node.
+ */
+ createOLPropertyNode: function(obj, prop) {
+ var node = null;
+ if(obj[prop] != null) {
+ node = this.createElementNS(this.namespaces.ol, "ol:" + prop);
+ node.appendChild(this.createTextNode(obj[prop].toString()));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_Server
+ * Create a Server node given a layer context object.
+ *
+ * Parameters:
+ * context - {Object} Layer context object.
+ *
+ * Returns:
+ * {Element} A WMC Server element node.
+ */
+ write_wmc_Server: function(context) {
+ var server = context.server;
+ var node = this.createElementDefaultNS("Server");
+ var attributes = {
+ service: "OGC:WMS",
+ version: server.version
+ };
+ if (server.title) {
+ attributes.title = server.title;
+ }
+ this.setAttributes(node, attributes);
+
+ // required OnlineResource element
+ node.appendChild(this.write_wmc_OnlineResource(server.url));
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_URLType
+ * Create a LogoURL/DescriptionURL/MetadataURL/DataURL/LegendURL node given a object and elementName.
+ *
+ * Parameters:
+ * elName - {String} Name of element (LogoURL/DescriptionURL/MetadataURL/LegendURL)
+ * url - {String} URL string value
+ * attr - {Object} Optional attributes (width, height, format)
+ *
+ * Returns:
+ * {Element} A WMC element node.
+ */
+ write_wmc_URLType: function(elName, url, attr) {
+ var node = this.createElementDefaultNS(elName);
+ node.appendChild(this.write_wmc_OnlineResource(url));
+ if (attr) {
+ var optionalAttributes = ["width", "height", "format"];
+ for (var i=0; i<optionalAttributes.length; i++) {
+ if (optionalAttributes[i] in attr) {
+ node.setAttribute(optionalAttributes[i], attr[optionalAttributes[i]]);
+ }
+ }
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_DimensionList
+ */
+ write_wmc_DimensionList: function(context) {
+ var node = this.createElementDefaultNS("DimensionList");
+ var required_attributes = {
+ name: true,
+ units: true,
+ unitSymbol: true,
+ userValue: true
+ };
+ for (var dim in context.dimensions) {
+ var attributes = {};
+ var dimension = context.dimensions[dim];
+ for (var name in dimension) {
+ if (typeof dimension[name] == "boolean") {
+ attributes[name] = Number(dimension[name]);
+ } else {
+ attributes[name] = dimension[name];
+ }
+ }
+ var values = "";
+ if (attributes.values) {
+ values = attributes.values.join(",");
+ delete attributes.values;
+ }
+
+ node.appendChild(this.createElementDefaultNS(
+ "Dimension", values, attributes
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_FormatList
+ * Create a FormatList node given a layer context.
+ *
+ * Parameters:
+ * context - {Object} Layer context object.
+ *
+ * Returns:
+ * {Element} A WMC FormatList element node.
+ */
+ write_wmc_FormatList: function(context) {
+ var node = this.createElementDefaultNS("FormatList");
+ for (var i=0, len=context.formats.length; i<len; i++) {
+ var format = context.formats[i];
+ node.appendChild(this.createElementDefaultNS(
+ "Format",
+ format.value,
+ (format.current && format.current == true) ?
+ {current: "1"} : null
+ ));
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_StyleList
+ * Create a StyleList node given a layer context.
+ *
+ * Parameters:
+ * layer - {Object} Layer context object.
+ *
+ * Returns:
+ * {Element} A WMC StyleList element node.
+ */
+ write_wmc_StyleList: function(layer) {
+ var node = this.createElementDefaultNS("StyleList");
+
+ var styles = layer.styles;
+ if (styles && OpenLayers.Util.isArray(styles)) {
+ var sld;
+ for (var i=0, len=styles.length; i<len; i++) {
+ var s = styles[i];
+ // three style types to consider
+ // [1] linked SLD
+ // [2] inline SLD
+ // [3] named style
+ // running child nodes always gets name, optionally gets href or body
+ var style = this.createElementDefaultNS(
+ "Style",
+ null,
+ (s.current && s.current == true) ?
+ {current: "1"} : null
+ );
+ if(s.href) { // [1]
+ sld = this.createElementDefaultNS("SLD");
+ // Name is optional.
+ if (s.name) {
+ sld.appendChild(this.createElementDefaultNS("Name", s.name));
+ }
+ // Title is optional.
+ if (s.title) {
+ sld.appendChild(this.createElementDefaultNS("Title", s.title));
+ }
+ // LegendURL is optional
+ if (s.legend) {
+ sld.appendChild(this.write_wmc_URLType("LegendURL", s.legend.href, s.legend));
+ }
+
+ var link = this.write_wmc_OnlineResource(s.href);
+ sld.appendChild(link);
+ style.appendChild(sld);
+ } else if(s.body) { // [2]
+ sld = this.createElementDefaultNS("SLD");
+ // Name is optional.
+ if (s.name) {
+ sld.appendChild(this.createElementDefaultNS("Name", s.name));
+ }
+ // Title is optional.
+ if (s.title) {
+ sld.appendChild(this.createElementDefaultNS("Title", s.title));
+ }
+ // LegendURL is optional
+ if (s.legend) {
+ sld.appendChild(this.write_wmc_URLType("LegendURL", s.legend.href, s.legend));
+ }
+
+ // read in body as xml doc - assume proper namespace declarations
+ var doc = OpenLayers.Format.XML.prototype.read.apply(this, [s.body]);
+ // append to StyledLayerDescriptor node
+ var imported = doc.documentElement;
+ if(sld.ownerDocument && sld.ownerDocument.importNode) {
+ imported = sld.ownerDocument.importNode(imported, true);
+ }
+ sld.appendChild(imported);
+ style.appendChild(sld);
+ } else { // [3]
+ // both Name and Title are required.
+ style.appendChild(this.createElementDefaultNS("Name", s.name));
+ style.appendChild(this.createElementDefaultNS("Title", s.title));
+ // Abstract is optional
+ if (s['abstract']) { // abstract is a js keyword
+ style.appendChild(this.createElementDefaultNS(
+ "Abstract", s['abstract']
+ ));
+ }
+ // LegendURL is optional
+ if (s.legend) {
+ style.appendChild(this.write_wmc_URLType("LegendURL", s.legend.href, s.legend));
+ }
+ }
+ node.appendChild(style);
+ }
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_OnlineResource
+ * Create an OnlineResource node given a URL.
+ *
+ * Parameters:
+ * href - {String} URL for the resource.
+ *
+ * Returns:
+ * {Element} A WMC OnlineResource element node.
+ */
+ write_wmc_OnlineResource: function(href) {
+ var node = this.createElementDefaultNS("OnlineResource");
+ this.setAttributeNS(node, this.namespaces.xlink, "xlink:type", "simple");
+ this.setAttributeNS(node, this.namespaces.xlink, "xlink:href", href);
+ return node;
+ },
+
+ /**
+ * Method: getOnlineResource_href
+ */
+ getOnlineResource_href: function(node) {
+ var object = {};
+ var links = node.getElementsByTagName("OnlineResource");
+ if(links.length > 0) {
+ this.read_wmc_OnlineResource(object, links[0]);
+ }
+ return object.href;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Format.WMC.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Control/PanPanel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Pan.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanPanel
+ * The PanPanel is visible control for panning the map North, South, East or
+ * West in small steps. By default it is drawn in the top left corner of the
+ * map.
+ *
+ * Note:
+ * If you wish to use this class with the default images and you want
+ * it to look nice in ie6, you should add the following, conditionally
+ * added css stylesheet to your HTML file:
+ *
+ * (code)
+ * <!--[if lte IE 6]>
+ * <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ * <![endif]-->
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.PanPanel = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * APIProperty: slideFactor
+ * {Integer} Number of pixels by which we'll pan the map in any direction
+ * on clicking the arrow buttons, defaults to 50. If you want to pan
+ * by some ratio of the map dimensions, use <slideRatio> instead.
+ */
+ slideFactor: 50,
+
+ /**
+ * APIProperty: slideRatio
+ * {Number} The fraction of map width/height by which we'll pan the map
+ * on clicking the arrow buttons. Default is null. If set, will
+ * override <slideFactor>. E.g. if slideRatio is .5, then Pan Up will
+ * pan up half the map height.
+ */
+ slideRatio: null,
+
+ /**
+ * Constructor: OpenLayers.Control.PanPanel
+ * Add the four directional pan buttons.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ var options = {
+ slideFactor: this.slideFactor,
+ slideRatio: this.slideRatio
+ };
+ this.addControls([
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH, options),
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH, options),
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST, options),
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST, options)
+ ]);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PanPanel"
+});
+/* ======================================================================
+ OpenLayers/Control/Attribution.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Attribution
+ * The attribution control adds attribution from layers to the map display.
+ * It uses 'attribution' property of each layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Attribution =
+ OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: separator
+ * {String} String used to separate layers.
+ */
+ separator: ", ",
+
+ /**
+ * APIProperty: template
+ * {String} Template for the attribution. This has to include the substring
+ * "${layers}", which will be replaced by the layer specific
+ * attributions, separated by <separator>. The default is "${layers}".
+ */
+ template: "${layers}",
+
+ /**
+ * Constructor: OpenLayers.Control.Attribution
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ * Destroy control.
+ */
+ destroy: function() {
+ this.map.events.un({
+ "removelayer": this.updateAttribution,
+ "addlayer": this.updateAttribution,
+ "changelayer": this.updateAttribution,
+ "changebaselayer": this.updateAttribution,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Initialize control.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ this.map.events.on({
+ 'changebaselayer': this.updateAttribution,
+ 'changelayer': this.updateAttribution,
+ 'addlayer': this.updateAttribution,
+ 'removelayer': this.updateAttribution,
+ scope: this
+ });
+ this.updateAttribution();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateAttribution
+ * Update attribution string.
+ */
+ updateAttribution: function() {
+ var attributions = [];
+ if (this.map && this.map.layers) {
+ for(var i=0, len=this.map.layers.length; i<len; i++) {
+ var layer = this.map.layers[i];
+ if (layer.attribution && layer.getVisibility()) {
+ // add attribution only if attribution text is unique
+ if (OpenLayers.Util.indexOf(
+ attributions, layer.attribution) === -1) {
+ attributions.push( layer.attribution );
+ }
+ }
+ }
+ this.div.innerHTML = OpenLayers.String.format(this.template, {
+ layers: attributions.join(this.separator)
+ });
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Attribution"
+});
+/* ======================================================================
+ OpenLayers/Kinetic.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+OpenLayers.Kinetic = OpenLayers.Class({
+
+ /**
+ * Property: threshold
+ * In most cases changing the threshold isn't needed.
+ * In px/ms, default to 0.
+ */
+ threshold: 0,
+
+ /**
+ * Property: deceleration
+ * {Float} the deseleration in px/ms², default to 0.0035.
+ */
+ deceleration: 0.0035,
+
+ /**
+ * Property: nbPoints
+ * {Integer} the number of points we use to calculate the kinetic
+ * initial values.
+ */
+ nbPoints: 100,
+
+ /**
+ * Property: delay
+ * {Float} time to consider to calculate the kinetic initial values.
+ * In ms, default to 200.
+ */
+ delay: 200,
+
+ /**
+ * Property: points
+ * List of points use to calculate the kinetic initial values.
+ */
+ points: undefined,
+
+ /**
+ * Property: timerId
+ * ID of the timer.
+ */
+ timerId: undefined,
+
+ /**
+ * Constructor: OpenLayers.Kinetic
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: begin
+ * Begins the dragging.
+ */
+ begin: function() {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = undefined;
+ this.points = [];
+ },
+
+ /**
+ * Method: update
+ * Updates during the dragging.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The new position.
+ */
+ update: function(xy) {
+ this.points.unshift({xy: xy, tick: new Date().getTime()});
+ if (this.points.length > this.nbPoints) {
+ this.points.pop();
+ }
+ },
+
+ /**
+ * Method: end
+ * Ends the dragging, start the kinetic.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The last position.
+ *
+ * Returns:
+ * {Object} An object with two properties: "speed", and "theta". The
+ * "speed" and "theta" values are to be passed to the move
+ * function when starting the animation.
+ */
+ end: function(xy) {
+ var last, now = new Date().getTime();
+ for (var i = 0, l = this.points.length, point; i < l; i++) {
+ point = this.points[i];
+ if (now - point.tick > this.delay) {
+ break;
+ }
+ last = point;
+ }
+ if (!last) {
+ return;
+ }
+ var time = new Date().getTime() - last.tick;
+ var dist = Math.sqrt(Math.pow(xy.x - last.xy.x, 2) +
+ Math.pow(xy.y - last.xy.y, 2));
+ var speed = dist / time;
+ if (speed == 0 || speed < this.threshold) {
+ return;
+ }
+ var theta = Math.asin((xy.y - last.xy.y) / dist);
+ if (last.xy.x <= xy.x) {
+ theta = Math.PI - theta;
+ }
+ return {speed: speed, theta: theta};
+ },
+
+ /**
+ * Method: move
+ * Launch the kinetic move pan.
+ *
+ * Parameters:
+ * info - {Object} An object with two properties, "speed", and "theta".
+ * These values are those returned from the "end" call.
+ * callback - {Function} Function called on every step of the animation,
+ * receives x, y (values to pan), end (is the last point).
+ */
+ move: function(info, callback) {
+ var v0 = info.speed;
+ var fx = Math.cos(info.theta);
+ var fy = -Math.sin(info.theta);
+
+ var initialTime = new Date().getTime();
+
+ var lastX = 0;
+ var lastY = 0;
+
+ var timerCallback = function() {
+ if (this.timerId == null) {
+ return;
+ }
+
+ var t = new Date().getTime() - initialTime;
+
+ var p = (-this.deceleration * Math.pow(t, 2)) / 2.0 + v0 * t;
+ var x = p * fx;
+ var y = p * fy;
+
+ var args = {};
+ args.end = false;
+ var v = -this.deceleration * t + v0;
+
+ if (v <= 0) {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = null;
+ args.end = true;
+ }
+
+ args.x = x - lastX;
+ args.y = y - lastY;
+ lastX = x;
+ lastY = y;
+ callback(args.x, args.y, args.end);
+ };
+
+ this.timerId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(timerCallback, this)
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Kinetic"
+});
+/* ======================================================================
+ OpenLayers/Format/WPSExecute.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ * @requires OpenLayers/Format/WCSGetCoverage.js
+ * @requires OpenLayers/Format/WFST/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSExecute version 1.0.0
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WPSExecute = OpenLayers.Class(OpenLayers.Format.XML,
+ OpenLayers.Format.Filter.v1_1_0, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ gml: "http://www.opengis.net/gml",
+ wps: "http://www.opengis.net/wps/1.0.0",
+ wfs: "http://www.opengis.net/wfs",
+ ogc: "http://www.opengis.net/ogc",
+ wcs: "http://www.opengis.net/wcs",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",
+
+ schemaLocationAttr: function(options) {
+ return undefined;
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WPSExecute
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} An WPS Execute request XML string.
+ */
+ write: function(options) {
+ var doc;
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject("Microsoft.XMLDOM");
+ this.xmldom = doc;
+ } else {
+ doc = document.implementation.createDocument("", "", null);
+ }
+ var node = this.writeNode("wps:Execute", options, doc);
+ this.setAttributeNS(
+ node, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * APIMethod: read
+ * Parse a WPS Execute and return an object with its information.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object}
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var info = {};
+ this.readNode(data, info);
+ return info;
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wps": {
+ "Execute": function(options) {
+ var node = this.createElementNSPlus("wps:Execute", {
+ attributes: {
+ version: this.VERSION,
+ service: 'WPS'
+ }
+ });
+ this.writeNode("ows:Identifier", options.identifier, node);
+ this.writeNode("wps:DataInputs", options.dataInputs, node);
+ this.writeNode("wps:ResponseForm", options.responseForm, node);
+ return node;
+ },
+ "ResponseForm": function(responseForm) {
+ var node = this.createElementNSPlus("wps:ResponseForm", {});
+ if (responseForm.rawDataOutput) {
+ this.writeNode("wps:RawDataOutput", responseForm.rawDataOutput, node);
+ }
+ if (responseForm.responseDocument) {
+ this.writeNode("wps:ResponseDocument", responseForm.responseDocument, node);
+ }
+ return node;
+ },
+ "ResponseDocument": function(responseDocument) {
+ var node = this.createElementNSPlus("wps:ResponseDocument", {
+ attributes: {
+ storeExecuteResponse: responseDocument.storeExecuteResponse,
+ lineage: responseDocument.lineage,
+ status: responseDocument.status
+ }
+ });
+ if (responseDocument.outputs) {
+ for (var i = 0, len = responseDocument.outputs.length; i < len; i++) {
+ this.writeNode("wps:Output", responseDocument.outputs[i], node);
+ }
+ }
+ return node;
+ },
+ "Output": function(output) {
+ var node = this.createElementNSPlus("wps:Output", {
+ attributes: {
+ asReference: output.asReference,
+ mimeType: output.mimeType,
+ encoding: output.encoding,
+ schema: output.schema
+ }
+ });
+ this.writeNode("ows:Identifier", output.identifier, node);
+ this.writeNode("ows:Title", output.title, node);
+ this.writeNode("ows:Abstract", output["abstract"], node);
+ return node;
+ },
+ "RawDataOutput": function(rawDataOutput) {
+ var node = this.createElementNSPlus("wps:RawDataOutput", {
+ attributes: {
+ mimeType: rawDataOutput.mimeType,
+ encoding: rawDataOutput.encoding,
+ schema: rawDataOutput.schema
+ }
+ });
+ this.writeNode("ows:Identifier", rawDataOutput.identifier, node);
+ return node;
+ },
+ "DataInputs": function(dataInputs) {
+ var node = this.createElementNSPlus("wps:DataInputs", {});
+ for (var i=0, ii=dataInputs.length; i<ii; ++i) {
+ this.writeNode("wps:Input", dataInputs[i], node);
+ }
+ return node;
+ },
+ "Input": function(input) {
+ var node = this.createElementNSPlus("wps:Input", {});
+ this.writeNode("ows:Identifier", input.identifier, node);
+ if (input.title) {
+ this.writeNode("ows:Title", input.title, node);
+ }
+ if (input.data) {
+ this.writeNode("wps:Data", input.data, node);
+ }
+ if (input.reference) {
+ this.writeNode("wps:Reference", input.reference, node);
+ }
+ if (input.boundingBoxData) {
+ this.writeNode("wps:BoundingBoxData", input.boundingBoxData, node);
+ }
+ return node;
+ },
+ "Data": function(data) {
+ var node = this.createElementNSPlus("wps:Data", {});
+ if (data.literalData) {
+ this.writeNode("wps:LiteralData", data.literalData, node);
+ } else if (data.complexData) {
+ this.writeNode("wps:ComplexData", data.complexData, node);
+ } else if (data.boundingBoxData) {
+ this.writeNode("ows:BoundingBox", data.boundingBoxData, node);
+ }
+ return node;
+ },
+ "LiteralData": function(literalData) {
+ var node = this.createElementNSPlus("wps:LiteralData", {
+ attributes: {
+ uom: literalData.uom
+ },
+ value: literalData.value
+ });
+ return node;
+ },
+ "ComplexData": function(complexData) {
+ var node = this.createElementNSPlus("wps:ComplexData", {
+ attributes: {
+ mimeType: complexData.mimeType,
+ encoding: complexData.encoding,
+ schema: complexData.schema
+ }
+ });
+ var data = complexData.value;
+ if (typeof data === "string") {
+ node.appendChild(
+ this.getXMLDoc().createCDATASection(complexData.value)
+ );
+ } else {
+ node.appendChild(data);
+ }
+ return node;
+ },
+ "Reference": function(reference) {
+ var node = this.createElementNSPlus("wps:Reference", {
+ attributes: {
+ mimeType: reference.mimeType,
+ "xlink:href": reference.href,
+ method: reference.method,
+ encoding: reference.encoding,
+ schema: reference.schema
+ }
+ });
+ if (reference.body) {
+ this.writeNode("wps:Body", reference.body, node);
+ }
+ return node;
+ },
+ "BoundingBoxData": function(node, obj) {
+ this.writers['ows']['BoundingBox'].apply(this, [node, obj, "wps:BoundingBoxData"]);
+ },
+ "Body": function(body) {
+ var node = this.createElementNSPlus("wps:Body", {});
+ if (body.wcs) {
+ this.writeNode("wcs:GetCoverage", body.wcs, node);
+ }
+ else if (body.wfs) {
+ // OpenLayers.Format.WFST expects these to be on the
+ // instance and not in the options
+ this.featureType = body.wfs.featureType;
+ this.version = body.wfs.version;
+ this.writeNode("wfs:GetFeature", body.wfs, node);
+ } else {
+ this.writeNode("wps:Execute", body, node);
+ }
+ return node;
+ }
+ },
+ "wcs": OpenLayers.Format.WCSGetCoverage.prototype.writers.wcs,
+ "wfs": OpenLayers.Format.WFST.v1_1_0.prototype.writers.wfs,
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc,
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wps": {
+ "ExecuteResponse": function(node, obj) {
+ obj.executeResponse = {
+ lang: node.getAttribute("lang"),
+ statusLocation: node.getAttribute("statusLocation"),
+ serviceInstance: node.getAttribute("serviceInstance"),
+ service: node.getAttribute("service")
+ };
+ this.readChildNodes(node, obj.executeResponse);
+ },
+ "Process":function(node,obj) {
+ obj.process = {};
+ this.readChildNodes(node, obj.process);
+ },
+ "Status":function(node,obj) {
+ obj.status = {
+ creationTime: node.getAttribute("creationTime")
+ };
+ this.readChildNodes(node, obj.status);
+ },
+ "ProcessSucceeded": function(node,obj) {
+ obj.processSucceeded = true;
+ },
+ "ProcessOutputs": function(node, processDescription) {
+ processDescription.processOutputs = [];
+ this.readChildNodes(node, processDescription.processOutputs);
+ },
+ "Output": function(node, processOutputs) {
+ var output = {};
+ this.readChildNodes(node, output);
+ processOutputs.push(output);
+ },
+ "Reference": function(node, output) {
+ output.reference = {
+ href: node.getAttribute("href"),
+ mimeType: node.getAttribute("mimeType"),
+ encoding: node.getAttribute("encoding"),
+ schema: node.getAttribute("schema")
+ };
+ },
+ "Data": function(node, output) {
+ output.data = {};
+ this.readChildNodes(node, output);
+ },
+ "LiteralData": function(node, output) {
+ output.literalData = {
+ dataType: node.getAttribute("dataType"),
+ uom: node.getAttribute("uom"),
+ value: this.getChildValue(node)
+ };
+ },
+ "ComplexData": function(node, output) {
+ output.complexData = {
+ mimeType: node.getAttribute("mimeType"),
+ schema: node.getAttribute("schema"),
+ encoding: node.getAttribute("encoding"),
+ value: ""
+ };
+
+ // try to get *some* value, ignore the empty text values
+ if (this.isSimpleContent(node)) {
+ var child;
+ for(child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 3: // text node
+ case 4: // cdata section
+ output.complexData.value += child.nodeValue;
+ }
+ }
+ }
+ else {
+ for(child=node.firstChild; child; child=child.nextSibling) {
+ if (child.nodeType == 1) {
+ output.complexData.value = child;
+ }
+ }
+ }
+
+ },
+ "BoundingBox": function(node, output) {
+ output.boundingBoxData = {
+ dimensions: node.getAttribute("dimensions"),
+ crs: node.getAttribute("crs")
+ };
+ this.readChildNodes(node, output.boundingBoxData);
+ }
+ },
+
+ // TODO: we should add Exception parsing here
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WPSExecute"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/GeoRSS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.GeoRSS
+ * Add GeoRSS Point features to your map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.GeoRSS = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+ /**
+ * Property: location
+ * {String} store url of text file
+ */
+ location: null,
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature>)}
+ */
+ features: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Hash of options which should be passed to the format when it is
+ * created. Must be passed in the constructor.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: selectedFeature
+ * {<OpenLayers.Feature>}
+ */
+ selectedFeature: null,
+
+ /**
+ * APIProperty: icon
+ * {<OpenLayers.Icon>}. This determines the Icon to be used on the map
+ * for this GeoRSS layer.
+ */
+ icon: null,
+
+ /**
+ * APIProperty: popupSize
+ * {<OpenLayers.Size>} This determines the size of GeoRSS popups. If
+ * not provided, defaults to 250px by 120px.
+ */
+ popupSize: null,
+
+ /**
+ * APIProperty: useFeedTitle
+ * {Boolean} Set layer.name to the first <title> element in the feed. Default is true.
+ */
+ useFeedTitle: true,
+
+ /**
+ * Constructor: OpenLayers.Layer.GeoRSS
+ * Create a GeoRSS Layer.
+ *
+ * Parameters:
+ * name - {String}
+ * location - {String}
+ * options - {Object}
+ */
+ initialize: function(name, location, options) {
+ OpenLayers.Layer.Markers.prototype.initialize.apply(this, [name, options]);
+ this.location = location;
+ this.features = [];
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ // Warning: Layer.Markers.destroy() must be called prior to calling
+ // clearFeatures() here, otherwise we leak memory. Indeed, if
+ // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+ // able to remove the marker image elements from the layer's div since
+ // the markers will have been destroyed by clearFeatures().
+ OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+ this.clearFeatures();
+ this.features = null;
+ },
+
+ /**
+ * Method: loadRSS
+ * Start the load of the RSS data. Don't do this when we first add the layer,
+ * since we may not be visible at any point, and it would therefore be a waste.
+ */
+ loadRSS: function() {
+ if (!this.loaded) {
+ this.events.triggerEvent("loadstart");
+ OpenLayers.Request.GET({
+ url: this.location,
+ success: this.parseData,
+ scope: this
+ });
+ this.loaded = true;
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * If layer is visible and RSS has not been loaded, load RSS.
+ *
+ * Parameters:
+ * bounds - {Object}
+ * zoomChanged - {Object}
+ * minor - {Object}
+ */
+ moveTo:function(bounds, zoomChanged, minor) {
+ OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+ if(this.visibility && !this.loaded){
+ this.loadRSS();
+ }
+ },
+
+ /**
+ * Method: parseData
+ * Parse the data returned from the Events call.
+ *
+ * Parameters:
+ * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>}
+ */
+ parseData: function(ajaxRequest) {
+ var doc = ajaxRequest.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText);
+ }
+
+ if (this.useFeedTitle) {
+ var name = null;
+ try {
+ name = doc.getElementsByTagNameNS('*', 'title')[0].firstChild.nodeValue;
+ }
+ catch (e) {
+ name = doc.getElementsByTagName('title')[0].firstChild.nodeValue;
+ }
+ if (name) {
+ this.setName(name);
+ }
+ }
+
+ var options = {};
+
+ OpenLayers.Util.extend(options, this.formatOptions);
+
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ var format = new OpenLayers.Format.GeoRSS(options);
+ var features = format.read(doc);
+
+ for (var i=0, len=features.length; i<len; i++) {
+ var data = {};
+ var feature = features[i];
+
+ // we don't support features with no geometry in the GeoRSS
+ // layer at this time.
+ if (!feature.geometry) {
+ continue;
+ }
+
+ var title = feature.attributes.title ?
+ feature.attributes.title : "Untitled";
+
+ var description = feature.attributes.description ?
+ feature.attributes.description : "No description.";
+
+ var link = feature.attributes.link ? feature.attributes.link : "";
+
+ var location = feature.geometry.getBounds().getCenterLonLat();
+
+
+ data.icon = this.icon == null ?
+ OpenLayers.Marker.defaultIcon() :
+ this.icon.clone();
+
+ data.popupSize = this.popupSize ?
+ this.popupSize.clone() :
+ new OpenLayers.Size(250, 120);
+
+ if (title || description) {
+ // we have supplemental data, store them.
+ data.title = title;
+ data.description = description;
+
+ var contentHTML = '<div class="olLayerGeoRSSClose">[x]</div>';
+ contentHTML += '<div class="olLayerGeoRSSTitle">';
+ if (link) {
+ contentHTML += '<a class="link" href="'+link+'" target="_blank">';
+ }
+ contentHTML += title;
+ if (link) {
+ contentHTML += '</a>';
+ }
+ contentHTML += '</div>';
+ contentHTML += '<div style="" class="olLayerGeoRSSDescription">';
+ contentHTML += description;
+ contentHTML += '</div>';
+ data['popupContentHTML'] = contentHTML;
+ }
+ var feature = new OpenLayers.Feature(this, location, data);
+ this.features.push(feature);
+ var marker = feature.createMarker();
+ marker.events.register('click', feature, this.markerClick);
+ this.addMarker(marker);
+ }
+ this.events.triggerEvent("loadend");
+ },
+
+ /**
+ * Method: markerClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ markerClick: function(evt) {
+ var sameMarkerClicked = (this == this.layer.selectedFeature);
+ this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+ for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+ this.layer.map.removePopup(this.layer.map.popups[i]);
+ }
+ if (!sameMarkerClicked) {
+ var popup = this.createPopup();
+ OpenLayers.Event.observe(popup.div, "click",
+ OpenLayers.Function.bind(function() {
+ for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+ this.layer.map.removePopup(this.layer.map.popups[i]);
+ }
+ }, this)
+ );
+ this.layer.map.addPopup(popup);
+ }
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: clearFeatures
+ * Destroy all features in this layer.
+ */
+ clearFeatures: function() {
+ if (this.features != null) {
+ while(this.features.length > 0) {
+ var feature = this.features[0];
+ OpenLayers.Util.removeItem(this.features, feature);
+ feature.destroy();
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.GeoRSS"
+});
+/* ======================================================================
+ OpenLayers/Symbolizer/Point.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Point
+ * A symbolizer used to render point features.
+ */
+OpenLayers.Symbolizer.Point = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: strokeColor
+ * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000"
+ * for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeOpacity
+ * {Number} Stroke opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeWidth
+ * {Number} Pixel stroke width.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeLinecap
+ * {String} Stroke cap type ("butt", "round", or "square").
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: strokeDashstyle
+ * {String} Stroke dash style according to the SLD spec. Note that the
+ * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot",
+ * "longdash", "longdashdot", or "solid") will not work in SLD, but
+ * most SLD patterns will render correctly in OpenLayers.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillColor
+ * {String} RGB hex fill color (e.g. "#ff0000" for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillOpacity
+ * {Number} Fill opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: pointRadius
+ * {Number} Pixel point radius.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: externalGraphic
+ * {String} Url to an external graphic that will be used for rendering
+ * points.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicWidth
+ * {Number} Pixel width for sizing an external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicHeight
+ * {Number} Pixel height for sizing an external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicOpacity
+ * {Number} Opacity (0-1) for an external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicXOffset
+ * {Number} Pixel offset along the positive x axis for displacing an
+ * external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicYOffset
+ * {Number} Pixel offset along the positive y axis for displacing an
+ * external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: rotation
+ * {Number} The rotation of a graphic in the clockwise direction about its
+ * center point (or any point off center as specified by
+ * <graphicXOffset> and <graphicYOffset>).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicName
+ * {String} Named graphic to use when rendering points. Supported values
+ * include "circle", "square", "star", "x", "cross", and "triangle".
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Point
+ * Create a symbolizer for rendering points.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new point symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Point"
+
+});
+
+/* ======================================================================
+ OpenLayers/Symbolizer/Line.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Line
+ * A symbolizer used to render line features.
+ */
+OpenLayers.Symbolizer.Line = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: strokeColor
+ * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000"
+ * for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeOpacity
+ * {Number} Stroke opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeWidth
+ * {Number} Pixel stroke width.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeLinecap
+ * {String} Stroke cap type ("butt", "round", or "square").
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: strokeDashstyle
+ * {String} Stroke dash style according to the SLD spec. Note that the
+ * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot",
+ * "longdash", "longdashdot", or "solid") will not work in SLD, but
+ * most SLD patterns will render correctly in OpenLayers.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Line
+ * Create a symbolizer for rendering lines.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new line symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Line"
+
+});
+
+/* ======================================================================
+ OpenLayers/Symbolizer/Text.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Text
+ * A symbolizer used to render text labels for features.
+ */
+OpenLayers.Symbolizer.Text = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: label
+ * {String} The text for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fontFamily
+ * {String} The font family for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fontSize
+ * {String} The font size for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fontWeight
+ * {String} The font weight for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: fontStyle
+ * {String} The font style for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Text
+ * Create a symbolizer for rendering text labels.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new text symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Text"
+
+});
+
+/* ======================================================================
+ OpenLayers/Format/SLD/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Format/SLD.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ * @requires OpenLayers/Symbolizer/Point.js
+ * @requires OpenLayers/Symbolizer/Line.js
+ * @requires OpenLayers/Symbolizer/Polygon.js
+ * @requires OpenLayers/Symbolizer/Text.js
+ * @requires OpenLayers/Symbolizer/Raster.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1
+ * Superclass for SLD version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_0_0>
+ */
+OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ sld: "http://www.opengis.net/sld",
+ ogc: "http://www.opengis.net/ogc",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sld",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * APIProperty: multipleSymbolizers
+ * {Boolean} Support multiple symbolizers per rule. Default is false. if
+ * true, an OpenLayers.Style2 instance will be created to represent
+ * user styles instead of an OpenLayers.Style instace. The
+ * OpenLayers.Style2 class allows collections of rules with multiple
+ * symbolizers, but is not currently useful for client side rendering.
+ * If multiple symbolizers is true, multiple FeatureTypeStyle elements
+ * are preserved in reading/writing by setting symbolizer zIndex values.
+ * In addition, the <defaultSymbolizer> property is ignored if
+ * multiple symbolizers are supported (defaults should be applied
+ * when rendering).
+ */
+ multipleSymbolizers: false,
+
+ /**
+ * Property: featureTypeCounter
+ * {Number} Private counter for multiple feature type styles.
+ */
+ featureTypeCounter: null,
+
+ /**
+ * APIProperty: defaultSymbolizer.
+ * {Object} A symbolizer with the SLD defaults.
+ */
+ defaultSymbolizer: {
+ fillColor: "#808080",
+ fillOpacity: 1,
+ strokeColor: "#000000",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeDashstyle: "solid",
+ pointRadius: 3,
+ graphicName: "square"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.SLD> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An SLD document element.
+ * options - {Object} Options for the reader.
+ *
+ * Valid options:
+ * namedLayersAsArray - {Boolean} Generate a namedLayers array. If false,
+ * the namedLayers property value will be an object keyed by layer name.
+ * Default is false.
+ *
+ * Returns:
+ * {Object} An object representing the SLD.
+ */
+ read: function(data, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var sld = {
+ namedLayers: options.namedLayersAsArray === true ? [] : {}
+ };
+ this.readChildNodes(data, sld);
+ return sld;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: OpenLayers.Util.applyDefaults({
+ "sld": {
+ "StyledLayerDescriptor": function(node, sld) {
+ sld.version = node.getAttribute("version");
+ this.readChildNodes(node, sld);
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, obj) {
+ obj.description = this.getChildValue(node);
+ },
+ "NamedLayer": function(node, sld) {
+ var layer = {
+ userStyles: [],
+ namedStyles: []
+ };
+ this.readChildNodes(node, layer);
+ // give each of the user styles this layer name
+ for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+ layer.userStyles[i].layerName = layer.name;
+ }
+ if(OpenLayers.Util.isArray(sld.namedLayers)) {
+ sld.namedLayers.push(layer);
+ } else {
+ sld.namedLayers[layer.name] = layer;
+ }
+ },
+ "NamedStyle": function(node, layer) {
+ layer.namedStyles.push(
+ this.getChildName(node.firstChild)
+ );
+ },
+ "UserStyle": function(node, layer) {
+ var obj = {defaultsPerSymbolizer: true, rules: []};
+ this.featureTypeCounter = -1;
+ this.readChildNodes(node, obj);
+ var style;
+ if (this.multipleSymbolizers) {
+ delete obj.defaultsPerSymbolizer;
+ style = new OpenLayers.Style2(obj);
+ } else {
+ style = new OpenLayers.Style(this.defaultSymbolizer, obj);
+ }
+ layer.userStyles.push(style);
+ },
+ "IsDefault": function(node, style) {
+ if(this.getChildValue(node) == "1") {
+ style.isDefault = true;
+ }
+ },
+ "FeatureTypeStyle": function(node, style) {
+ ++this.featureTypeCounter;
+ var obj = {
+ rules: this.multipleSymbolizers ? style.rules : []
+ };
+ this.readChildNodes(node, obj);
+ if (!this.multipleSymbolizers) {
+ style.rules = obj.rules;
+ }
+ },
+ "Rule": function(node, obj) {
+ var config;
+ if (this.multipleSymbolizers) {
+ config = {symbolizers: []};
+ }
+ var rule = new OpenLayers.Rule(config);
+ this.readChildNodes(node, rule);
+ obj.rules.push(rule);
+ },
+ "ElseFilter": function(node, rule) {
+ rule.elseFilter = true;
+ },
+ "MinScaleDenominator": function(node, rule) {
+ rule.minScaleDenominator = parseFloat(this.getChildValue(node));
+ },
+ "MaxScaleDenominator": function(node, rule) {
+ rule.maxScaleDenominator = parseFloat(this.getChildValue(node));
+ },
+ "TextSymbolizer": function(node, rule) {
+ var config = {};
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Text(config)
+ );
+ } else {
+ rule.symbolizer["Text"] = OpenLayers.Util.applyDefaults(
+ config, rule.symbolizer["Text"]
+ );
+ }
+ },
+ "LabelPlacement": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "PointPlacement": function(node, symbolizer) {
+ var config = {};
+ this.readChildNodes(node, config);
+ config.labelRotation = config.rotation;
+ delete config.rotation;
+ var labelAlign,
+ x = symbolizer.labelAnchorPointX,
+ y = symbolizer.labelAnchorPointY;
+ if (x <= 1/3) {
+ labelAlign = 'l';
+ } else if (x > 1/3 && x < 2/3) {
+ labelAlign = 'c';
+ } else if (x >= 2/3) {
+ labelAlign = 'r';
+ }
+ if (y <= 1/3) {
+ labelAlign += 'b';
+ } else if (y > 1/3 && y < 2/3) {
+ labelAlign += 'm';
+ } else if (y >= 2/3) {
+ labelAlign += 't';
+ }
+ config.labelAlign = labelAlign;
+ OpenLayers.Util.applyDefaults(symbolizer, config);
+ },
+ "AnchorPoint": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "AnchorPointX": function(node, symbolizer) {
+ var labelAnchorPointX = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelAnchorPointX) {
+ symbolizer.labelAnchorPointX = labelAnchorPointX;
+ }
+ },
+ "AnchorPointY": function(node, symbolizer) {
+ var labelAnchorPointY = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelAnchorPointY) {
+ symbolizer.labelAnchorPointY = labelAnchorPointY;
+ }
+ },
+ "Displacement": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "DisplacementX": function(node, symbolizer) {
+ var labelXOffset = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelXOffset) {
+ symbolizer.labelXOffset = labelXOffset;
+ }
+ },
+ "DisplacementY": function(node, symbolizer) {
+ var labelYOffset = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelYOffset) {
+ symbolizer.labelYOffset = labelYOffset;
+ }
+ },
+ "LinePlacement": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "PerpendicularOffset": function(node, symbolizer) {
+ var labelPerpendicularOffset = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelPerpendicularOffset) {
+ symbolizer.labelPerpendicularOffset = labelPerpendicularOffset;
+ }
+ },
+ "Label": function(node, symbolizer) {
+ var value = this.readers.ogc._expression.call(this, node);
+ if (value) {
+ symbolizer.label = value;
+ }
+ },
+ "Font": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "Halo": function(node, symbolizer) {
+ // halo has a fill, so send fresh object
+ var obj = {};
+ this.readChildNodes(node, obj);
+ symbolizer.haloRadius = obj.haloRadius;
+ symbolizer.haloColor = obj.fillColor;
+ symbolizer.haloOpacity = obj.fillOpacity;
+ },
+ "Radius": function(node, symbolizer) {
+ var radius = this.readers.ogc._expression.call(this, node);
+ if(radius != null) {
+ // radius is only used for halo
+ symbolizer.haloRadius = radius;
+ }
+ },
+ "RasterSymbolizer": function(node, rule) {
+ var config = {};
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Raster(config)
+ );
+ } else {
+ rule.symbolizer["Raster"] = OpenLayers.Util.applyDefaults(
+ config, rule.symbolizer["Raster"]
+ );
+ }
+ },
+ "Geometry": function(node, obj) {
+ obj.geometry = {};
+ this.readChildNodes(node, obj.geometry);
+ },
+ "ColorMap": function(node, symbolizer) {
+ symbolizer.colorMap = [];
+ this.readChildNodes(node, symbolizer.colorMap);
+ },
+ "ColorMapEntry": function(node, colorMap) {
+ var q = node.getAttribute("quantity");
+ var o = node.getAttribute("opacity");
+ colorMap.push({
+ color: node.getAttribute("color"),
+ quantity: q !== null ? parseFloat(q) : undefined,
+ label: node.getAttribute("label") || undefined,
+ opacity: o !== null ? parseFloat(o) : undefined
+ });
+ },
+ "LineSymbolizer": function(node, rule) {
+ var config = {};
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Line(config)
+ );
+ } else {
+ rule.symbolizer["Line"] = OpenLayers.Util.applyDefaults(
+ config, rule.symbolizer["Line"]
+ );
+ }
+ },
+ "PolygonSymbolizer": function(node, rule) {
+ var config = {
+ fill: false,
+ stroke: false
+ };
+ if (!this.multipleSymbolizers) {
+ config = rule.symbolizer["Polygon"] || config;
+ }
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Polygon(config)
+ );
+ } else {
+ rule.symbolizer["Polygon"] = config;
+ }
+ },
+ "PointSymbolizer": function(node, rule) {
+ var config = {
+ fill: false,
+ stroke: false,
+ graphic: false
+ };
+ if (!this.multipleSymbolizers) {
+ config = rule.symbolizer["Point"] || config;
+ }
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Point(config)
+ );
+ } else {
+ rule.symbolizer["Point"] = config;
+ }
+ },
+ "Stroke": function(node, symbolizer) {
+ symbolizer.stroke = true;
+ this.readChildNodes(node, symbolizer);
+ },
+ "Fill": function(node, symbolizer) {
+ symbolizer.fill = true;
+ this.readChildNodes(node, symbolizer);
+ },
+ "CssParameter": function(node, symbolizer) {
+ var cssProperty = node.getAttribute("name");
+ var symProperty = this.cssMap[cssProperty];
+ // for labels, fill should map to fontColor and fill-opacity
+ // to fontOpacity
+ if (symbolizer.label) {
+ if (cssProperty === 'fill') {
+ symProperty = "fontColor";
+ } else if (cssProperty === 'fill-opacity') {
+ symProperty = "fontOpacity";
+ }
+ }
+ if(symProperty) {
+ // Limited support for parsing of OGC expressions
+ var value = this.readers.ogc._expression.call(this, node);
+ // always string, could be an empty string
+ if(value) {
+ symbolizer[symProperty] = value;
+ }
+ }
+ },
+ "Graphic": function(node, symbolizer) {
+ symbolizer.graphic = true;
+ var graphic = {};
+ // painter's order not respected here, clobber previous with next
+ this.readChildNodes(node, graphic);
+ // directly properties with names that match symbolizer properties
+ var properties = [
+ "stroke", "strokeColor", "strokeWidth", "strokeOpacity",
+ "strokeLinecap", "fill", "fillColor", "fillOpacity",
+ "graphicName", "rotation", "graphicFormat"
+ ];
+ var prop, value;
+ for(var i=0, len=properties.length; i<len; ++i) {
+ prop = properties[i];
+ value = graphic[prop];
+ if(value != undefined) {
+ symbolizer[prop] = value;
+ }
+ }
+ // set other generic properties with specific graphic property names
+ if(graphic.opacity != undefined) {
+ symbolizer.graphicOpacity = graphic.opacity;
+ }
+ if(graphic.size != undefined) {
+ var pointRadius = graphic.size / 2;
+ if (isNaN(pointRadius)) {
+ // likely a property name
+ symbolizer.graphicWidth = graphic.size;
+ } else {
+ symbolizer.pointRadius = graphic.size / 2;
+ }
+ }
+ if(graphic.href != undefined) {
+ symbolizer.externalGraphic = graphic.href;
+ }
+ if(graphic.rotation != undefined) {
+ symbolizer.rotation = graphic.rotation;
+ }
+ },
+ "ExternalGraphic": function(node, graphic) {
+ this.readChildNodes(node, graphic);
+ },
+ "Mark": function(node, graphic) {
+ this.readChildNodes(node, graphic);
+ },
+ "WellKnownName": function(node, graphic) {
+ graphic.graphicName = this.getChildValue(node);
+ },
+ "Opacity": function(node, obj) {
+ var opacity = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(opacity) {
+ obj.opacity = opacity;
+ }
+ },
+ "Size": function(node, obj) {
+ var size = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(size) {
+ obj.size = size;
+ }
+ },
+ "Rotation": function(node, obj) {
+ var rotation = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(rotation) {
+ obj.rotation = rotation;
+ }
+ },
+ "OnlineResource": function(node, obj) {
+ obj.href = this.getAttributeNS(
+ node, this.namespaces.xlink, "href"
+ );
+ },
+ "Format": function(node, graphic) {
+ graphic.graphicFormat = this.getChildValue(node);
+ }
+ }
+ }, OpenLayers.Format.Filter.v1_0_0.prototype.readers),
+
+ /**
+ * Property: cssMap
+ * {Object} Object mapping supported css property names to OpenLayers
+ * symbolizer property names.
+ */
+ cssMap: {
+ "stroke": "strokeColor",
+ "stroke-opacity": "strokeOpacity",
+ "stroke-width": "strokeWidth",
+ "stroke-linecap": "strokeLinecap",
+ "stroke-dasharray": "strokeDashstyle",
+ "fill": "fillColor",
+ "fill-opacity": "fillOpacity",
+ "font-family": "fontFamily",
+ "font-size": "fontSize",
+ "font-weight": "fontWeight",
+ "font-style": "fontStyle"
+ },
+
+ /**
+ * Method: getCssProperty
+ * Given a symbolizer property, get the corresponding CSS property
+ * from the <cssMap>.
+ *
+ * Parameters:
+ * sym - {String} A symbolizer property name.
+ *
+ * Returns:
+ * {String} A CSS property name or null if none found.
+ */
+ getCssProperty: function(sym) {
+ var css = null;
+ for(var prop in this.cssMap) {
+ if(this.cssMap[prop] == sym) {
+ css = prop;
+ break;
+ }
+ }
+ return css;
+ },
+
+ /**
+ * Method: getGraphicFormat
+ * Given a href for an external graphic, try to determine the mime-type.
+ * This method doesn't try too hard, and will fall back to
+ * <defaultGraphicFormat> if one of the known <graphicFormats> is not
+ * the file extension of the provided href.
+ *
+ * Parameters:
+ * href - {String}
+ *
+ * Returns:
+ * {String} The graphic format.
+ */
+ getGraphicFormat: function(href) {
+ var format, regex;
+ for(var key in this.graphicFormats) {
+ if(this.graphicFormats[key].test(href)) {
+ format = key;
+ break;
+ }
+ }
+ return format || this.defaultGraphicFormat;
+ },
+
+ /**
+ * Property: defaultGraphicFormat
+ * {String} If none other can be determined from <getGraphicFormat>, this
+ * default will be returned.
+ */
+ defaultGraphicFormat: "image/png",
+
+ /**
+ * Property: graphicFormats
+ * {Object} Mapping of image mime-types to regular extensions matching
+ * well-known file extensions.
+ */
+ graphicFormats: {
+ "image/jpeg": /\.jpe?g$/i,
+ "image/gif": /\.gif$/i,
+ "image/png": /\.png$/i
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * sld - {Object} An object representing the SLD.
+ *
+ * Returns:
+ * {DOMElement} The root of an SLD document.
+ */
+ write: function(sld) {
+ return this.writers.sld.StyledLayerDescriptor.apply(this, [sld]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: OpenLayers.Util.applyDefaults({
+ "sld": {
+ "_OGCExpression": function(nodeName, value) {
+ // only the simplest of ogc:expression handled
+ // {label: "some text and a ${propertyName}"}
+ var node = this.createElementNSPlus(nodeName);
+ var tokens = typeof value == "string" ?
+ value.split("${") :
+ [value];
+ node.appendChild(this.createTextNode(tokens[0]));
+ var item, last;
+ for(var i=1, len=tokens.length; i<len; i++) {
+ item = tokens[i];
+ last = item.indexOf("}");
+ if(last > 0) {
+ this.writeNode(
+ "ogc:PropertyName",
+ {property: item.substring(0, last)},
+ node
+ );
+ node.appendChild(
+ this.createTextNode(item.substring(++last))
+ );
+ } else {
+ // no ending }, so this is a literal ${
+ node.appendChild(
+ this.createTextNode("${" + item)
+ );
+ }
+ }
+ return node;
+ },
+ "StyledLayerDescriptor": function(sld) {
+ var root = this.createElementNSPlus(
+ "sld:StyledLayerDescriptor",
+ {attributes: {
+ "version": this.VERSION,
+ "xsi:schemaLocation": this.schemaLocation
+ }}
+ );
+
+ // For ArcGIS Server it is necessary to define this
+ // at the root level (see ticket:2166).
+ root.setAttribute("xmlns:ogc", this.namespaces.ogc);
+ root.setAttribute("xmlns:gml", this.namespaces.gml);
+
+ // add in optional name
+ if(sld.name) {
+ this.writeNode("Name", sld.name, root);
+ }
+ // add in optional title
+ if(sld.title) {
+ this.writeNode("Title", sld.title, root);
+ }
+ // add in optional description
+ if(sld.description) {
+ this.writeNode("Abstract", sld.description, root);
+ }
+ // add in named layers
+ // allow namedLayers to be an array
+ if(OpenLayers.Util.isArray(sld.namedLayers)) {
+ for(var i=0, len=sld.namedLayers.length; i<len; ++i) {
+ this.writeNode("NamedLayer", sld.namedLayers[i], root);
+ }
+ } else {
+ for(var name in sld.namedLayers) {
+ this.writeNode("NamedLayer", sld.namedLayers[name], root);
+ }
+ }
+ return root;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("sld:Name", {value: name});
+ },
+ "Title": function(title) {
+ return this.createElementNSPlus("sld:Title", {value: title});
+ },
+ "Abstract": function(description) {
+ return this.createElementNSPlus(
+ "sld:Abstract", {value: description}
+ );
+ },
+ "NamedLayer": function(layer) {
+ var node = this.createElementNSPlus("sld:NamedLayer");
+
+ // add in required name
+ this.writeNode("Name", layer.name, node);
+
+ // optional sld:LayerFeatureConstraints here
+
+ // add in named styles
+ if(layer.namedStyles) {
+ for(var i=0, len=layer.namedStyles.length; i<len; ++i) {
+ this.writeNode(
+ "NamedStyle", layer.namedStyles[i], node
+ );
+ }
+ }
+
+ // add in user styles
+ if(layer.userStyles) {
+ for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+ this.writeNode(
+ "UserStyle", layer.userStyles[i], node
+ );
+ }
+ }
+
+ return node;
+ },
+ "NamedStyle": function(name) {
+ var node = this.createElementNSPlus("sld:NamedStyle");
+ this.writeNode("Name", name, node);
+ return node;
+ },
+ "UserStyle": function(style) {
+ var node = this.createElementNSPlus("sld:UserStyle");
+
+ // add in optional name
+ if(style.name) {
+ this.writeNode("Name", style.name, node);
+ }
+ // add in optional title
+ if(style.title) {
+ this.writeNode("Title", style.title, node);
+ }
+ // add in optional description
+ if(style.description) {
+ this.writeNode("Abstract", style.description, node);
+ }
+
+ // add isdefault
+ if(style.isDefault) {
+ this.writeNode("IsDefault", style.isDefault, node);
+ }
+
+ // add FeatureTypeStyles
+ if (this.multipleSymbolizers && style.rules) {
+ // group style objects by symbolizer zIndex
+ var rulesByZ = {
+ 0: []
+ };
+ var zValues = [0];
+ var rule, ruleMap, symbolizer, zIndex, clone;
+ for (var i=0, ii=style.rules.length; i<ii; ++i) {
+ rule = style.rules[i];
+ if (rule.symbolizers) {
+ ruleMap = {};
+ for (var j=0, jj=rule.symbolizers.length; j<jj; ++j) {
+ symbolizer = rule.symbolizers[j];
+ zIndex = symbolizer.zIndex;
+ if (!(zIndex in ruleMap)) {
+ clone = rule.clone();
+ clone.symbolizers = [];
+ ruleMap[zIndex] = clone;
+ }
+ ruleMap[zIndex].symbolizers.push(symbolizer.clone());
+ }
+ for (zIndex in ruleMap) {
+ if (!(zIndex in rulesByZ)) {
+ zValues.push(zIndex);
+ rulesByZ[zIndex] = [];
+ }
+ rulesByZ[zIndex].push(ruleMap[zIndex]);
+ }
+ } else {
+ // no symbolizers in rule
+ rulesByZ[0].push(rule.clone());
+ }
+ }
+ // write one FeatureTypeStyle per zIndex
+ zValues.sort();
+ var rules;
+ for (var i=0, ii=zValues.length; i<ii; ++i) {
+ rules = rulesByZ[zValues[i]];
+ if (rules.length > 0) {
+ clone = style.clone();
+ clone.rules = rulesByZ[zValues[i]];
+ this.writeNode("FeatureTypeStyle", clone, node);
+ }
+ }
+ } else {
+ this.writeNode("FeatureTypeStyle", style, node);
+ }
+
+ return node;
+ },
+ "IsDefault": function(bool) {
+ return this.createElementNSPlus(
+ "sld:IsDefault", {value: (bool) ? "1" : "0"}
+ );
+ },
+ "FeatureTypeStyle": function(style) {
+ var node = this.createElementNSPlus("sld:FeatureTypeStyle");
+
+ // OpenLayers currently stores no Name, Title, Abstract,
+ // FeatureTypeName, or SemanticTypeIdentifier information
+ // related to FeatureTypeStyle
+
+ // add in rules
+ for(var i=0, len=style.rules.length; i<len; ++i) {
+ this.writeNode("Rule", style.rules[i], node);
+ }
+
+ return node;
+ },
+ "Rule": function(rule) {
+ var node = this.createElementNSPlus("sld:Rule");
+
+ // add in optional name
+ if(rule.name) {
+ this.writeNode("Name", rule.name, node);
+ }
+ // add in optional title
+ if(rule.title) {
+ this.writeNode("Title", rule.title, node);
+ }
+ // add in optional description
+ if(rule.description) {
+ this.writeNode("Abstract", rule.description, node);
+ }
+
+ // add in LegendGraphic here
+
+ // add in optional filters
+ if(rule.elseFilter) {
+ this.writeNode("ElseFilter", null, node);
+ } else if(rule.filter) {
+ this.writeNode("ogc:Filter", rule.filter, node);
+ }
+
+ // add in scale limits
+ if(rule.minScaleDenominator != undefined) {
+ this.writeNode(
+ "MinScaleDenominator", rule.minScaleDenominator, node
+ );
+ }
+ if(rule.maxScaleDenominator != undefined) {
+ this.writeNode(
+ "MaxScaleDenominator", rule.maxScaleDenominator, node
+ );
+ }
+
+ var type, symbolizer;
+ if (this.multipleSymbolizers && rule.symbolizers) {
+ var symbolizer;
+ for (var i=0, ii=rule.symbolizers.length; i<ii; ++i) {
+ symbolizer = rule.symbolizers[i];
+ type = symbolizer.CLASS_NAME.split(".").pop();
+ this.writeNode(
+ type + "Symbolizer", symbolizer, node
+ );
+ }
+ } else {
+ // add in symbolizers (relies on geometry type keys)
+ var types = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+ for(var i=0, len=types.length; i<len; ++i) {
+ type = types[i];
+ symbolizer = rule.symbolizer[type];
+ if(symbolizer) {
+ this.writeNode(
+ type + "Symbolizer", symbolizer, node
+ );
+ }
+ }
+ }
+ return node;
+
+ },
+ "ElseFilter": function() {
+ return this.createElementNSPlus("sld:ElseFilter");
+ },
+ "MinScaleDenominator": function(scale) {
+ return this.createElementNSPlus(
+ "sld:MinScaleDenominator", {value: scale}
+ );
+ },
+ "MaxScaleDenominator": function(scale) {
+ return this.createElementNSPlus(
+ "sld:MaxScaleDenominator", {value: scale}
+ );
+ },
+ "LineSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:LineSymbolizer");
+ this.writeNode("Stroke", symbolizer, node);
+ return node;
+ },
+ "Stroke": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Stroke");
+
+ // GraphicFill here
+ // GraphicStroke here
+
+ // add in CssParameters
+ if(symbolizer.strokeColor != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeColor"},
+ node
+ );
+ }
+ if(symbolizer.strokeOpacity != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeOpacity"},
+ node
+ );
+ }
+ if(symbolizer.strokeWidth != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeWidth"},
+ node
+ );
+ }
+ if(symbolizer.strokeDashstyle != undefined && symbolizer.strokeDashstyle !== "solid") {
+ // assumes valid stroke-dasharray value
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeDashstyle"},
+ node
+ );
+ }
+ if(symbolizer.strokeLinecap != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeLinecap"},
+ node
+ );
+ }
+ return node;
+ },
+ "CssParameter": function(obj) {
+ // not handling ogc:expressions for now
+ return this.createElementNSPlus("sld:CssParameter", {
+ attributes: {name: this.getCssProperty(obj.key)},
+ value: obj.symbolizer[obj.key]
+ });
+ },
+ "TextSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:TextSymbolizer");
+ // add in optional Label
+ if(symbolizer.label != null) {
+ this.writeNode("Label", symbolizer.label, node);
+ }
+ // add in optional Font
+ if(symbolizer.fontFamily != null ||
+ symbolizer.fontSize != null ||
+ symbolizer.fontWeight != null ||
+ symbolizer.fontStyle != null) {
+ this.writeNode("Font", symbolizer, node);
+ }
+ // add in optional LabelPlacement
+ if (symbolizer.labelAnchorPointX != null ||
+ symbolizer.labelAnchorPointY != null ||
+ symbolizer.labelAlign != null ||
+ symbolizer.labelXOffset != null ||
+ symbolizer.labelYOffset != null ||
+ symbolizer.labelRotation != null ||
+ symbolizer.labelPerpendicularOffset != null) {
+ this.writeNode("LabelPlacement", symbolizer, node);
+ }
+ // add in optional Halo
+ if(symbolizer.haloRadius != null ||
+ symbolizer.haloColor != null ||
+ symbolizer.haloOpacity != null) {
+ this.writeNode("Halo", symbolizer, node);
+ }
+ // add in optional Fill
+ if(symbolizer.fontColor != null ||
+ symbolizer.fontOpacity != null) {
+ this.writeNode("Fill", {
+ fillColor: symbolizer.fontColor,
+ fillOpacity: symbolizer.fontOpacity
+ }, node);
+ }
+ return node;
+ },
+ "LabelPlacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:LabelPlacement");
+ // PointPlacement and LinePlacement are choices, so don't output both
+ if ((symbolizer.labelAnchorPointX != null ||
+ symbolizer.labelAnchorPointY != null ||
+ symbolizer.labelAlign != null ||
+ symbolizer.labelXOffset != null ||
+ symbolizer.labelYOffset != null ||
+ symbolizer.labelRotation != null) &&
+ symbolizer.labelPerpendicularOffset == null) {
+ this.writeNode("PointPlacement", symbolizer, node);
+ }
+ if (symbolizer.labelPerpendicularOffset != null) {
+ this.writeNode("LinePlacement", symbolizer, node);
+ }
+ return node;
+ },
+ "LinePlacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:LinePlacement");
+ this.writeNode("PerpendicularOffset", symbolizer.labelPerpendicularOffset, node);
+ return node;
+ },
+ "PerpendicularOffset": function(value) {
+ return this.createElementNSPlus("sld:PerpendicularOffset", {
+ value: value
+ });
+ },
+ "PointPlacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:PointPlacement");
+ if (symbolizer.labelAnchorPointX != null ||
+ symbolizer.labelAnchorPointY != null ||
+ symbolizer.labelAlign != null) {
+ this.writeNode("AnchorPoint", symbolizer, node);
+ }
+ if (symbolizer.labelXOffset != null ||
+ symbolizer.labelYOffset != null) {
+ this.writeNode("Displacement", symbolizer, node);
+ }
+ if (symbolizer.labelRotation != null) {
+ this.writeNode("Rotation", symbolizer.labelRotation, node);
+ }
+ return node;
+ },
+ "AnchorPoint": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:AnchorPoint");
+ var x = symbolizer.labelAnchorPointX,
+ y = symbolizer.labelAnchorPointY;
+ if (x != null) {
+ this.writeNode("AnchorPointX", x, node);
+ }
+ if (y != null) {
+ this.writeNode("AnchorPointY", y, node);
+ }
+ if (x == null && y == null) {
+ var xAlign = symbolizer.labelAlign.substr(0, 1),
+ yAlign = symbolizer.labelAlign.substr(1, 1);
+ if (xAlign === "l") {
+ x = 0;
+ } else if (xAlign === "c") {
+ x = 0.5;
+ } else if (xAlign === "r") {
+ x = 1;
+ }
+ if (yAlign === "b") {
+ y = 0;
+ } else if (yAlign === "m") {
+ y = 0.5;
+ } else if (yAlign === "t") {
+ y = 1;
+ }
+ this.writeNode("AnchorPointX", x, node);
+ this.writeNode("AnchorPointY", y, node);
+ }
+ return node;
+ },
+ "AnchorPointX": function(value) {
+ return this.createElementNSPlus("sld:AnchorPointX", {
+ value: value
+ });
+ },
+ "AnchorPointY": function(value) {
+ return this.createElementNSPlus("sld:AnchorPointY", {
+ value: value
+ });
+ },
+ "Displacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Displacement");
+ if (symbolizer.labelXOffset != null) {
+ this.writeNode("DisplacementX", symbolizer.labelXOffset, node);
+ }
+ if (symbolizer.labelYOffset != null) {
+ this.writeNode("DisplacementY", symbolizer.labelYOffset, node);
+ }
+ return node;
+ },
+ "DisplacementX": function(value) {
+ return this.createElementNSPlus("sld:DisplacementX", {
+ value: value
+ });
+ },
+ "DisplacementY": function(value) {
+ return this.createElementNSPlus("sld:DisplacementY", {
+ value: value
+ });
+ },
+ "Font": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Font");
+ // add in CssParameters
+ if(symbolizer.fontFamily) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontFamily"},
+ node
+ );
+ }
+ if(symbolizer.fontSize) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontSize"},
+ node
+ );
+ }
+ if(symbolizer.fontWeight) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontWeight"},
+ node
+ );
+ }
+ if(symbolizer.fontStyle) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontStyle"},
+ node
+ );
+ }
+ return node;
+ },
+ "Label": function(label) {
+ return this.writers.sld._OGCExpression.call(
+ this, "sld:Label", label
+ );
+ },
+ "Halo": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Halo");
+ if(symbolizer.haloRadius) {
+ this.writeNode("Radius", symbolizer.haloRadius, node);
+ }
+ if(symbolizer.haloColor || symbolizer.haloOpacity) {
+ this.writeNode("Fill", {
+ fillColor: symbolizer.haloColor,
+ fillOpacity: symbolizer.haloOpacity
+ }, node);
+ }
+ return node;
+ },
+ "Radius": function(value) {
+ return this.createElementNSPlus("sld:Radius", {
+ value: value
+ });
+ },
+ "RasterSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:RasterSymbolizer");
+ if (symbolizer.geometry) {
+ this.writeNode("Geometry", symbolizer.geometry, node);
+ }
+ if (symbolizer.opacity) {
+ this.writeNode("Opacity", symbolizer.opacity, node);
+ }
+ if (symbolizer.colorMap) {
+ this.writeNode("ColorMap", symbolizer.colorMap, node);
+ }
+ return node;
+ },
+ "Geometry": function(geometry) {
+ var node = this.createElementNSPlus("sld:Geometry");
+ if (geometry.property) {
+ this.writeNode("ogc:PropertyName", geometry, node);
+ }
+ return node;
+ },
+ "ColorMap": function(colorMap) {
+ var node = this.createElementNSPlus("sld:ColorMap");
+ for (var i=0, len=colorMap.length; i<len; ++i) {
+ this.writeNode("ColorMapEntry", colorMap[i], node);
+ }
+ return node;
+ },
+ "ColorMapEntry": function(colorMapEntry) {
+ var node = this.createElementNSPlus("sld:ColorMapEntry");
+ var a = colorMapEntry;
+ node.setAttribute("color", a.color);
+ a.opacity !== undefined && node.setAttribute("opacity",
+ parseFloat(a.opacity));
+ a.quantity !== undefined && node.setAttribute("quantity",
+ parseFloat(a.quantity));
+ a.label !== undefined && node.setAttribute("label", a.label);
+ return node;
+ },
+ "PolygonSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:PolygonSymbolizer");
+ if(symbolizer.fill !== false) {
+ this.writeNode("Fill", symbolizer, node);
+ }
+ if(symbolizer.stroke !== false) {
+ this.writeNode("Stroke", symbolizer, node);
+ }
+ return node;
+ },
+ "Fill": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Fill");
+
+ // GraphicFill here
+
+ // add in CssParameters
+ if(symbolizer.fillColor) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fillColor"},
+ node
+ );
+ }
+ if(symbolizer.fillOpacity != null) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fillOpacity"},
+ node
+ );
+ }
+ return node;
+ },
+ "PointSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:PointSymbolizer");
+ this.writeNode("Graphic", symbolizer, node);
+ return node;
+ },
+ "Graphic": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Graphic");
+ if(symbolizer.externalGraphic != undefined) {
+ this.writeNode("ExternalGraphic", symbolizer, node);
+ } else {
+ this.writeNode("Mark", symbolizer, node);
+ }
+
+ if(symbolizer.graphicOpacity != undefined) {
+ this.writeNode("Opacity", symbolizer.graphicOpacity, node);
+ }
+ if(symbolizer.pointRadius != undefined) {
+ this.writeNode("Size", symbolizer.pointRadius * 2, node);
+ } else if (symbolizer.graphicWidth != undefined) {
+ this.writeNode("Size", symbolizer.graphicWidth, node);
+ }
+ if(symbolizer.rotation != undefined) {
+ this.writeNode("Rotation", symbolizer.rotation, node);
+ }
+ return node;
+ },
+ "ExternalGraphic": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:ExternalGraphic");
+ this.writeNode(
+ "OnlineResource", symbolizer.externalGraphic, node
+ );
+ var format = symbolizer.graphicFormat ||
+ this.getGraphicFormat(symbolizer.externalGraphic);
+ this.writeNode("Format", format, node);
+ return node;
+ },
+ "Mark": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Mark");
+ if(symbolizer.graphicName) {
+ this.writeNode("WellKnownName", symbolizer.graphicName, node);
+ }
+ if (symbolizer.fill !== false) {
+ this.writeNode("Fill", symbolizer, node);
+ }
+ if (symbolizer.stroke !== false) {
+ this.writeNode("Stroke", symbolizer, node);
+ }
+ return node;
+ },
+ "WellKnownName": function(name) {
+ return this.createElementNSPlus("sld:WellKnownName", {
+ value: name
+ });
+ },
+ "Opacity": function(value) {
+ return this.createElementNSPlus("sld:Opacity", {
+ value: value
+ });
+ },
+ "Size": function(value) {
+ return this.writers.sld._OGCExpression.call(
+ this, "sld:Size", value
+ );
+ },
+ "Rotation": function(value) {
+ return this.createElementNSPlus("sld:Rotation", {
+ value: value
+ });
+ },
+ "OnlineResource": function(href) {
+ return this.createElementNSPlus("sld:OnlineResource", {
+ attributes: {
+ "xlink:type": "simple",
+ "xlink:href": href
+ }
+ });
+ },
+ "Format": function(format) {
+ return this.createElementNSPlus("sld:Format", {
+ value: format
+ });
+ }
+ }
+ }, OpenLayers.Format.Filter.v1_0_0.prototype.writers),
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/WMS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMS
+ * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web
+ * Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: { service: "WMS",
+ version: "1.1.1",
+ request: "GetMap",
+ styles: "",
+ format: "image/jpeg"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for WMS layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: encodeBBOX
+ * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
+ * but some services want it that way. Default false.
+ */
+ encodeBBOX: false,
+
+ /**
+ * APIProperty: noMagic
+ * {Boolean} If true, the image format will not be automagicaly switched
+ * from image/jpeg to image/png or image/gif when using
+ * TRANSPARENT=TRUE. Also isBaseLayer will not changed by the
+ * constructor. Default false.
+ */
+ noMagic: false,
+
+ /**
+ * Property: yx
+ * {Object} Keys in this object are EPSG codes for which the axis order
+ * is to be reversed (yx instead of xy, LatLon instead of LonLat), with
+ * true as value. This is only relevant for WMS versions >= 1.3.0, and
+ * only if yx is not set in <OpenLayers.Projection.defaults> for the
+ * used projection.
+ */
+ yx: {},
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS
+ * Create a new WMS layer object
+ *
+ * Examples:
+ *
+ * The code below creates a simple WMS layer using the image/jpeg format.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {layers: "modis,global_mosaic"});
+ * (end)
+ * Note the 3rd argument (params). Properties added to this object will be
+ * added to the WMS GetMap requests used for this layer's tiles. The only
+ * mandatory parameter is "layers". Other common WMS params include
+ * "transparent", "styles" and "format". Note that the "srs" param will
+ * always be ignored. Instead, it will be derived from the baseLayer's or
+ * map's projection.
+ *
+ * The code below creates a transparent WMS layer with additional options.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {
+ * layers: "modis,global_mosaic",
+ * transparent: true
+ * }, {
+ * opacity: 0.5,
+ * singleTile: true
+ * });
+ * (end)
+ * Note that by default, a WMS layer is configured as baseLayer. Setting
+ * the "transparent" param to true will apply some magic (see <noMagic>).
+ * The default image format changes from image/jpeg to image/png, and the
+ * layer is not configured as baseLayer.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the WMS
+ * (e.g. http://wms.jpl.nasa.gov/wms.cgi)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ * These options include all properties listed above, plus the ones
+ * inherited from superclasses.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) {
+ params.EXCEPTIONS = "INIMAGE";
+ }
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+
+ //layer is transparent
+ if (!this.noMagic && this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "image/jpeg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
+ : "image/png";
+ }
+ }
+
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMS(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: reverseAxisOrder
+ * Returns true if the axis order is reversed for the WMS version and
+ * projection of the layer.
+ *
+ * Returns:
+ * {Boolean} true if the axis order is reversed, false otherwise.
+ */
+ reverseAxisOrder: function() {
+ var projCode = this.projection.getCode();
+ return parseFloat(this.params.VERSION) >= 1.3 &&
+ !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] &&
+ OpenLayers.Projection.defaults[projCode].yx));
+ },
+
+ /**
+ * Method: getURL
+ * Return a GetMap query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+
+ var imageSize = this.getImageSize();
+ var newParams = {};
+ // WMS 1.3 introduced axis order
+ var reverseAxisOrder = this.reverseAxisOrder();
+ newParams.BBOX = this.encodeBBOX ?
+ bounds.toBBOX(null, reverseAxisOrder) :
+ bounds.toArray(reverseAxisOrder);
+ newParams.WIDTH = imageSize.w;
+ newParams.HEIGHT = imageSize.h;
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ /**
+ * APIMethod: getFullRequestString
+ * Combine the layer's url with its params and these newParams.
+ *
+ * Add the SRS parameter from projection -- this is probably
+ * more eloquently done via a setProjection() method, but this
+ * works for now and always.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ var mapProjection = this.map.getProjectionObject();
+ var projectionCode = this.projection && this.projection.equals(mapProjection) ?
+ this.projection.getCode() :
+ mapProjection.getCode();
+ var value = (projectionCode == "none") ? null : projectionCode;
+ if (parseFloat(this.params.VERSION) >= 1.3) {
+ this.params.CRS = value;
+ } else {
+ this.params.SRS = value;
+ }
+
+ if (typeof this.params.TRANSPARENT == "boolean") {
+ newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE";
+ }
+
+ return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+ this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMS"
+});
+/* ======================================================================
+ OpenLayers/Layer/KaMap.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.KaMap
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.KaMap = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} KaMap Layer is always a base layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} parameters set by default. The default parameters set
+ * the format via the 'i' parameter to 'jpeg'.
+ */
+ DEFAULT_PARAMS: {
+ i: 'jpeg',
+ map: ''
+ },
+
+ /**
+ * Constructor: OpenLayers.Layer.KaMap
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object} Parameters to be sent to the HTTP server in the
+ * query string for the tile. The format can be set via the 'i'
+ * parameter (defaults to jpg) , and the map should be set via
+ * the 'map' parameter. It has been reported that ka-Map may behave
+ * inconsistently if your format parameter does not match the format
+ * parameter configured in your config.php. (See ticket #327 for more
+ * information.)
+ * options - {Object} Additional options for the layer. Any of the
+ * APIProperties listed on this layer, and any layer types it
+ * extends, can be overridden through the options parameter.
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
+ this.params = OpenLayers.Util.applyDefaults(
+ this.params, this.DEFAULT_PARAMS
+ );
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var mapRes = this.map.getResolution();
+ var scale = Math.round((this.map.getScale() * 10000)) / 10000;
+ var pX = Math.round(bounds.left / mapRes);
+ var pY = -Math.round(bounds.top / mapRes);
+ return this.getFullRequestString(
+ { t: pY,
+ l: pX,
+ s: scale
+ });
+ },
+
+ /**
+ * Method: calculateGridLayout
+ * ka-Map uses the center point of the map as an origin for
+ * its tiles. Override calculateGridLayout to center tiles
+ * correctly for this case.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bound>}
+ * origin - {<OpenLayers.LonLat>}
+ * resolution - {Number}
+ *
+ * Returns:
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ calculateGridLayout: function(bounds, origin, resolution) {
+ var tilelon = resolution*this.tileSize.w;
+ var tilelat = resolution*this.tileSize.h;
+
+ var offsetlon = bounds.left;
+ var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+
+ var offsetlat = bounds.top;
+ var tilerow = Math.floor(offsetlat/tilelat) + this.buffer;
+
+ return {
+ tilelon: tilelon, tilelat: tilelat,
+ startcol: tilecol, startrow: tilerow
+ };
+ },
+
+ /**
+ * Method: getTileBoundsForGridIndex
+ *
+ * Parameters:
+ * row - {Number} The row of the grid
+ * col - {Number} The column of the grid
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
+ */
+ getTileBoundsForGridIndex: function(row, col) {
+ var origin = this.getTileOrigin();
+ var tileLayout = this.gridLayout;
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+ var minX = (tileLayout.startcol + col) * tilelon;
+ var minY = (tileLayout.startrow - row) * tilelat;
+ return new OpenLayers.Bounds(
+ minX, minY,
+ minX + tilelon, minY + tilelat
+ );
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Kamap>} An exact clone of this OpenLayers.Layer.KaMap
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.KaMap(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ if (this.tileSize != null) {
+ obj.tileSize = this.tileSize.clone();
+ }
+
+ // we do not want to copy reference to grid, so we make a new array
+ obj.grid = [];
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: getTileBounds
+ * Returns The tile bounds for a layer given a pixel location.
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = tileMapWidth * Math.floor(mapPoint.lon / tileMapWidth);
+ var tileBottom = tileMapHeight * Math.floor(mapPoint.lat / tileMapHeight);
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.KaMap"
+});
+/* ======================================================================
+ OpenLayers/Format/WMC/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_1_0
+ * Read and write WMC version 1.1.0.
+ *
+ * Differences between 1.1.0 and 1.0.0:
+ * - 1.1.0 Layers have optional sld:MinScaleDenominator and
+ * sld:MaxScaleDenominator
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WMC.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1.0
+ */
+ VERSION: "1.1.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/context
+ * http://schemas.opengis.net/context/1.1.0/context.xsd
+ */
+ schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC.v1_1_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WMC> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Method: read_sld_MinScaleDenominator
+ * Read a sld:MinScaleDenominator node.
+ *
+ * Parameters:
+ * layerContext - {Object} An object representing a layer.
+ * node - {Element} An element node.
+ */
+ read_sld_MinScaleDenominator: function(layerContext, node) {
+ var minScaleDenominator = parseFloat(this.getChildValue(node));
+ if (minScaleDenominator > 0) {
+ layerContext.maxScale = minScaleDenominator;
+ }
+ },
+
+ /**
+ * Method: read_sld_MaxScaleDenominator
+ * Read a sld:MaxScaleDenominator node.
+ *
+ * Parameters:
+ * layerContext - {Object} An object representing a layer.
+ * node - {Element} An element node.
+ */
+ read_sld_MaxScaleDenominator: function(layerContext, node) {
+ layerContext.minScale = parseFloat(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_wmc_SRS
+ */
+ read_wmc_SRS: function(layerContext, node) {
+ if (! ("srs" in layerContext)) {
+ layerContext.srs = {};
+ }
+ layerContext.srs[this.getChildValue(node)] = true;
+ },
+
+ /**
+ * Method: write_wmc_Layer
+ * Create a Layer node given a layer context object. This method adds
+ * elements specific to version 1.1.0.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.}
+ *
+ * Returns:
+ * {Element} A WMC Layer element node.
+ */
+ write_wmc_Layer: function(context) {
+ var node = OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(
+ this, [context]
+ );
+
+ // min/max scale denominator elements go before the 4th element in v1
+ if(context.maxScale) {
+ var minSD = this.createElementNS(
+ this.namespaces.sld, "sld:MinScaleDenominator"
+ );
+ minSD.appendChild(this.createTextNode(context.maxScale.toPrecision(16)));
+ node.appendChild(minSD);
+ }
+
+ if(context.minScale) {
+ var maxSD = this.createElementNS(
+ this.namespaces.sld, "sld:MaxScaleDenominator"
+ );
+ maxSD.appendChild(this.createTextNode(context.minScale.toPrecision(16)));
+ node.appendChild(maxSD);
+ }
+
+ // optional SRS element(s)
+ if (context.srs) {
+ for(var name in context.srs) {
+ node.appendChild(this.createElementDefaultNS("SRS", name));
+ }
+ }
+
+ // optional FormatList element
+ node.appendChild(this.write_wmc_FormatList(context));
+
+ // optional StyleList element
+ node.appendChild(this.write_wmc_StyleList(context));
+
+ // optional DimensionList element
+ if (context.dimensions) {
+ node.appendChild(this.write_wmc_DimensionList(context));
+ }
+
+ // OpenLayers specific properties go in an Extension element
+ node.appendChild(this.write_wmc_LayerExtension(context));
+
+ return node;
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMC.v1_1_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/XLS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XLS
+ * Read/Write XLS (OpenLS). Create a new instance with the <OpenLayers.Format.XLS>
+ * constructor. Currently only implemented for Location Utility Services, more
+ * specifically only for Geocoding. No support for Reverse Geocoding as yet.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.XLS = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is true.
+ */
+ stringifyOutput: true,
+
+ /**
+ * Constructor: OpenLayers.Format.XLS
+ * Create a new parser for XLS.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: write
+ * Write out an XLS request.
+ *
+ * Parameters:
+ * request - {Object} An object representing the LUS request.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} An XLS document string.
+ */
+
+ /**
+ * APIMethod: read
+ * Read an XLS doc and return an object representing the result.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the GeocodeResponse.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.XLS"
+});
+/* ======================================================================
+ OpenLayers/Format/XLS/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XLS.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XLS.v1
+ * Superclass for XLS version 1 parsers. Only supports GeocodeRequest for now.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.XLS.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xls: "http://www.opengis.net/xls",
+ gml: "http://www.opengis.net/gml",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "xls",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XLS.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.XLS> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An XLS document element.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the XLSResponse.
+ */
+ read: function(data, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var xls = {};
+ this.readChildNodes(data, xls);
+ return xls;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "xls": {
+ "XLS": function(node, xls) {
+ xls.version = node.getAttribute("version");
+ this.readChildNodes(node, xls);
+ },
+ "Response": function(node, xls) {
+ this.readChildNodes(node, xls);
+ },
+ "GeocodeResponse": function(node, xls) {
+ xls.responseLists = [];
+ this.readChildNodes(node, xls);
+ },
+ "GeocodeResponseList": function(node, xls) {
+ var responseList = {
+ features: [],
+ numberOfGeocodedAddresses:
+ parseInt(node.getAttribute("numberOfGeocodedAddresses"))
+ };
+ xls.responseLists.push(responseList);
+ this.readChildNodes(node, responseList);
+ },
+ "GeocodedAddress": function(node, responseList) {
+ var feature = new OpenLayers.Feature.Vector();
+ responseList.features.push(feature);
+ this.readChildNodes(node, feature);
+ // post-process geometry
+ feature.geometry = feature.components[0];
+ },
+ "GeocodeMatchCode": function(node, feature) {
+ feature.attributes.matchCode = {
+ accuracy: parseFloat(node.getAttribute("accuracy")),
+ matchType: node.getAttribute("matchType")
+ };
+ },
+ "Address": function(node, feature) {
+ var address = {
+ countryCode: node.getAttribute("countryCode"),
+ addressee: node.getAttribute("addressee"),
+ street: [],
+ place: []
+ };
+ feature.attributes.address = address;
+ this.readChildNodes(node, address);
+ },
+ "freeFormAddress": function(node, address) {
+ address.freeFormAddress = this.getChildValue(node);
+ },
+ "StreetAddress": function(node, address) {
+ this.readChildNodes(node, address);
+ },
+ "Building": function(node, address) {
+ address.building = {
+ 'number': node.getAttribute("number"),
+ subdivision: node.getAttribute("subdivision"),
+ buildingName: node.getAttribute("buildingName")
+ };
+ },
+ "Street": function(node, address) {
+ // only support the built-in primitive type for now
+ address.street.push(this.getChildValue(node));
+ },
+ "Place": function(node, address) {
+ // type is one of CountrySubdivision,
+ // CountrySecondarySubdivision, Municipality or
+ // MunicipalitySubdivision
+ address.place[node.getAttribute("type")] =
+ this.getChildValue(node);
+ },
+ "PostalCode": function(node, address) {
+ address.postalCode = this.getChildValue(node);
+ }
+ },
+ "gml": OpenLayers.Format.GML.v3.prototype.readers.gml
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * request - {Object} An object representing the geocode request.
+ *
+ * Returns:
+ * {DOMElement} The root of an XLS document.
+ */
+ write: function(request) {
+ return this.writers.xls.XLS.apply(this, [request]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "xls": {
+ "XLS": function(request) {
+ var root = this.createElementNSPlus(
+ "xls:XLS",
+ {attributes: {
+ "version": this.VERSION,
+ "xsi:schemaLocation": this.schemaLocation
+ }}
+ );
+ this.writeNode("RequestHeader", request.header, root);
+ this.writeNode("Request", request, root);
+ return root;
+ },
+ "RequestHeader": function(header) {
+ return this.createElementNSPlus("xls:RequestHeader");
+ },
+ "Request": function(request) {
+ var node = this.createElementNSPlus("xls:Request", {
+ attributes: {
+ methodName: "GeocodeRequest",
+ requestID: request.requestID || "",
+ version: this.VERSION
+ }
+ });
+ this.writeNode("GeocodeRequest", request.addresses, node);
+ return node;
+ },
+ "GeocodeRequest": function(addresses) {
+ var node = this.createElementNSPlus("xls:GeocodeRequest");
+ for (var i=0, len=addresses.length; i<len; i++) {
+ this.writeNode("Address", addresses[i], node);
+ }
+ return node;
+ },
+ "Address": function(address) {
+ var node = this.createElementNSPlus("xls:Address", {
+ attributes: {
+ countryCode: address.countryCode
+ }
+ });
+ if (address.freeFormAddress) {
+ this.writeNode("freeFormAddress", address.freeFormAddress, node);
+ } else {
+ if (address.street) {
+ this.writeNode("StreetAddress", address, node);
+ }
+ if (address.municipality) {
+ this.writeNode("Municipality", address.municipality, node);
+ }
+ if (address.countrySubdivision) {
+ this.writeNode("CountrySubdivision", address.countrySubdivision, node);
+ }
+ if (address.postalCode) {
+ this.writeNode("PostalCode", address.postalCode, node);
+ }
+ }
+ return node;
+ },
+ "freeFormAddress": function(freeFormAddress) {
+ return this.createElementNSPlus("freeFormAddress",
+ {value: freeFormAddress});
+ },
+ "StreetAddress": function(address) {
+ var node = this.createElementNSPlus("xls:StreetAddress");
+ if (address.building) {
+ this.writeNode(node, "Building", address.building);
+ }
+ var street = address.street;
+ if (!(OpenLayers.Util.isArray(street))) {
+ street = [street];
+ }
+ for (var i=0, len=street.length; i < len; i++) {
+ this.writeNode("Street", street[i], node);
+ }
+ return node;
+ },
+ "Building": function(building) {
+ return this.createElementNSPlus("xls:Building", {
+ attributes: {
+ "number": building["number"],
+ "subdivision": building.subdivision,
+ "buildingName": building.buildingName
+ }
+ });
+ },
+ "Street": function(street) {
+ return this.createElementNSPlus("xls:Street", {value: street});
+ },
+ "Municipality": function(municipality) {
+ return this.createElementNSPlus("xls:Place", {
+ attributes: {
+ type: "Municipality"
+ },
+ value: municipality
+ });
+ },
+ "CountrySubdivision": function(countrySubdivision) {
+ return this.createElementNSPlus("xls:Place", {
+ attributes: {
+ type: "CountrySubdivision"
+ },
+ value: countrySubdivision
+ });
+ },
+ "PostalCode": function(postalCode) {
+ return this.createElementNSPlus("xls:PostalCode", {
+ value: postalCode
+ });
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XLS.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/XLS/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XLS/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XLS.v1_1_0
+ * Read / write XLS version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XLS.v1>
+ */
+OpenLayers.Format.XLS.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.XLS.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1
+ */
+ VERSION: "1.1",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/xls
+ * http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd
+ */
+ schemaLocation: "http://www.opengis.net/xls http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.XLS.v1_1_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.XLS> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.XLS.v1_1_0"
+
+});
+
+// Support non standard implementation
+OpenLayers.Format.XLS.v1_1 = OpenLayers.Format.XLS.v1_1_0;
+/* ======================================================================
+ OpenLayers/Renderer/SVG.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: "http://www.w3.org/2000/svg",
+
+ /**
+ * Property: xlinkns
+ * {String}
+ */
+ xlinkns: "http://www.w3.org/1999/xlink",
+
+ /**
+ * Constant: MAX_PIXEL
+ * {Integer} Firefox has a limitation where values larger or smaller than
+ * about 15000 in an SVG document lock the browser up. This
+ * works around it.
+ */
+ MAX_PIXEL: 15000,
+
+ /**
+ * Property: translationParameters
+ * {Object} Hash with "x" and "y" properties
+ */
+ translationParameters: null,
+
+ /**
+ * Property: symbolMetrics
+ * {Object} Cache for symbol metrics according to their svg coordinate
+ * space. This is an object keyed by the symbol's id, and values are
+ * an array of [width, centerX, centerY].
+ */
+ symbolMetrics: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.SVG
+ *
+ * Parameters:
+ * containerID - {String}
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ this.translationParameters = {x: 0, y: 0};
+
+ this.symbolMetrics = {};
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SVG renderer
+ */
+ supported: function() {
+ var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+ return (document.implementation &&
+ (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
+ document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
+ document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+ },
+
+ /**
+ * Method: inValidRange
+ * See #669 for more information
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ * xyOnly - {Boolean} whether or not to just check for x and y, which means
+ * to not take the current translation parameters into account if true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the 'x' and 'y' coordinates are in the
+ * valid range.
+ */
+ inValidRange: function(x, y, xyOnly) {
+ var left = x + (xyOnly ? 0 : this.translationParameters.x);
+ var top = y + (xyOnly ? 0 : this.translationParameters.y);
+ return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+ top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+ },
+
+ /**
+ * Method: setExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+
+ var resolution = this.getResolution(),
+ left = -extent.left / resolution,
+ top = extent.top / resolution;
+
+ // If the resolution has changed, start over changing the corner, because
+ // the features will redraw.
+ if (resolutionChanged) {
+ this.left = left;
+ this.top = top;
+ // Set the viewbox
+ var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+ this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+ this.translate(this.xOffset, 0);
+ return true;
+ } else {
+ var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
+ if (!inRange) {
+ // recenter the coordinate system
+ this.setExtent(extent, true);
+ }
+ return coordSysUnchanged && inRange;
+ }
+ },
+
+ /**
+ * Method: translate
+ * Transforms the SVG coordinate system
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ *
+ * Returns:
+ * {Boolean} true if the translation parameters are in the valid coordinates
+ * range, false otherwise.
+ */
+ translate: function(x, y) {
+ if (!this.inValidRange(x, y, true)) {
+ return false;
+ } else {
+ var transformString = "";
+ if (x || y) {
+ transformString = "translate(" + x + "," + y + ")";
+ }
+ this.root.setAttributeNS(null, "transform", transformString);
+ this.translationParameters = {x: x, y: y};
+ return true;
+ }
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+ this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+ },
+
+ /**
+ * Method: getNodeType
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "image";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "svg";
+ } else {
+ nodeType = "circle";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ nodeType = "polyline";
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ nodeType = "polygon";
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "path";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a SVG node.
+ *
+ * Takes care to adjust stroke width and point radius to be
+ * resolution-relative
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ */
+ setStyle: function(node, style, options) {
+ style = style || node._style;
+ options = options || node._options;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.setAttributeNS(null, "title", title);
+ //Standards-conformant SVG
+ // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
+ var titleNode = node.getElementsByTagName("title");
+ if (titleNode.length > 0) {
+ titleNode[0].firstChild.textContent = title;
+ } else {
+ var label = this.nodeFactory(null, "title");
+ label.textContent = title;
+ node.appendChild(label);
+ }
+ }
+
+ var r = parseFloat(node.getAttributeNS(null, "r"));
+ var widthFactor = 1;
+ var pos;
+ if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+ node.style.visibility = "";
+ if (style.graphic === false) {
+ node.style.visibility = "hidden";
+ } else if (style.externalGraphic) {
+ pos = this.getPosition(node);
+ if (style.graphicWidth && style.graphicHeight) {
+ node.setAttributeNS(null, "preserveAspectRatio", "none");
+ }
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+ node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+ node.setAttributeNS(null, "width", width);
+ node.setAttributeNS(null, "height", height);
+ node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
+ node.setAttributeNS(null, "style", "opacity: "+opacity);
+ node.onclick = OpenLayers.Event.preventDefault;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ // the symbol viewBox is three times as large as the symbol
+ var offset = style.pointRadius * 3;
+ var size = offset * 2;
+ var src = this.importSymbol(style.graphicName);
+ pos = this.getPosition(node);
+ widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
+
+ // remove the node from the dom before we modify it. This
+ // prevents various rendering issues in Safari and FF
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+ if(parent) {
+ parent.removeChild(node);
+ }
+
+ // The more appropriate way to implement this would be use/defs,
+ // but due to various issues in several browsers, it is safer to
+ // copy the symbols instead of referencing them.
+ // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
+ // and this email thread
+ // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
+ node.firstChild && node.removeChild(node.firstChild);
+ node.appendChild(src.firstChild.cloneNode(true));
+ node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
+
+ node.setAttributeNS(null, "width", size);
+ node.setAttributeNS(null, "height", size);
+ node.setAttributeNS(null, "x", pos.x - offset);
+ node.setAttributeNS(null, "y", pos.y - offset);
+
+ // now that the node has all its new properties, insert it
+ // back into the dom where it was
+ if(nextSibling) {
+ parent.insertBefore(node, nextSibling);
+ } else if(parent) {
+ parent.appendChild(node);
+ }
+ } else {
+ node.setAttributeNS(null, "r", style.pointRadius);
+ }
+
+ var rotation = style.rotation;
+
+ if ((rotation !== undefined || node._rotation !== undefined) && pos) {
+ node._rotation = rotation;
+ rotation |= 0;
+ if (node.nodeName !== "svg") {
+ node.setAttributeNS(null, "transform",
+ "rotate(" + rotation + " " + pos.x + " " +
+ pos.y + ")");
+ } else {
+ var metrics = this.symbolMetrics[src.id];
+ node.firstChild.setAttributeNS(null, "transform", "rotate("
+ + rotation + " "
+ + metrics[1] + " "
+ + metrics[2] + ")");
+ }
+ }
+ }
+
+ if (options.isFilled) {
+ node.setAttributeNS(null, "fill", style.fillColor);
+ node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+ } else {
+ node.setAttributeNS(null, "fill", "none");
+ }
+
+ if (options.isStroked) {
+ node.setAttributeNS(null, "stroke", style.strokeColor);
+ node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+ node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+ node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
+ // Hard-coded linejoin for now, to make it look the same as in VML.
+ // There is no strokeLinejoin property yet for symbolizers.
+ node.setAttributeNS(null, "stroke-linejoin", "round");
+ style.strokeDashstyle && node.setAttributeNS(null,
+ "stroke-dasharray", this.dashStyle(style, widthFactor));
+ } else {
+ node.setAttributeNS(null, "stroke", "none");
+ }
+
+ if (style.pointerEvents) {
+ node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+ }
+
+ if (style.cursor != null) {
+ node.setAttributeNS(null, "cursor", style.cursor);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ * widthFactor - {Number}
+ *
+ * Returns:
+ * {String} A SVG compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style, widthFactor) {
+ var w = style.strokeWidth * widthFactor;
+ var str = style.strokeDashstyle;
+ switch (str) {
+ case 'solid':
+ return 'none';
+ case 'dot':
+ return [1, 4 * w].join();
+ case 'dash':
+ return [4 * w, 4 * w].join();
+ case 'dashdot':
+ return [4 * w, 4 * w, 1, 4 * w].join();
+ case 'longdash':
+ return [8 * w, 4 * w].join();
+ case 'longdashdot':
+ return [8 * w, 4 * w, 1, 4 * w].join();
+ default:
+ return OpenLayers.String.trim(str).replace(/\s+/g, ",");
+ }
+ },
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElementNS(this.xmlns, type);
+ if (id) {
+ node.setAttributeNS(null, "id", id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+ return (type == node.nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
+ svg.style.display = "block";
+ return svg;
+ },
+
+ /**
+ * Method: createRoot
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "g");
+ },
+
+ /**
+ * Method: createDefs
+ *
+ * Returns:
+ * {DOMElement} The element to which we'll add the symbol definitions
+ */
+ createDefs: function() {
+ var defs = this.nodeFactory(this.container.id + "_defs", "defs");
+ this.rendererRoot.appendChild(defs);
+ return defs;
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {Float}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry, radius) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "cx", x);
+ node.setAttributeNS(null, "cy", y);
+ node.setAttributeNS(null, "r", radius);
+ return node;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {
+ var d = "";
+ var draw = true;
+ var complete = true;
+ var linearRingResult, path;
+ for (var j=0, len=geometry.components.length; j<len; j++) {
+ d += " M";
+ linearRingResult = this.getComponentsString(
+ geometry.components[j].components, " ");
+ path = linearRingResult.path;
+ if (path) {
+ d += " " + path;
+ complete = linearRingResult.complete && complete;
+ } else {
+ draw = false;
+ }
+ }
+ d += " z";
+ if (draw) {
+ node.setAttributeNS(null, "d", d);
+ node.setAttributeNS(null, "fill-rule", "evenodd");
+ return complete ? node : null;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawRectangle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "x", x);
+ node.setAttributeNS(null, "y", y);
+ node.setAttributeNS(null, "width", geometry.width / resolution);
+ node.setAttributeNS(null, "height", geometry.height / resolution);
+ return node;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var drawOutline = (!!style.labelOutlineWidth);
+ // First draw text in halo color and size and overlay the
+ // normal text afterwards
+ if (drawOutline) {
+ var outlineStyle = OpenLayers.Util.extend({}, style);
+ outlineStyle.fontColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
+ if (style.labelOutlineOpacity) {
+ outlineStyle.fontOpacity = style.labelOutlineOpacity;
+ }
+ delete outlineStyle.labelOutlineWidth;
+ this.drawText(featureId, outlineStyle, location);
+ }
+
+ var resolution = this.getResolution();
+
+ var x = ((location.x - this.featureDx) / resolution + this.left);
+ var y = (location.y / resolution - this.top);
+
+ var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
+ var label = this.nodeFactory(featureId + suffix, "text");
+
+ label.setAttributeNS(null, "x", x);
+ label.setAttributeNS(null, "y", -y);
+
+ if (style.fontColor) {
+ label.setAttributeNS(null, "fill", style.fontColor);
+ }
+ if (style.fontStrokeColor) {
+ label.setAttributeNS(null, "stroke", style.fontStrokeColor);
+ }
+ if (style.fontStrokeWidth) {
+ label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
+ }
+ if (style.fontOpacity) {
+ label.setAttributeNS(null, "opacity", style.fontOpacity);
+ }
+ if (style.fontFamily) {
+ label.setAttributeNS(null, "font-family", style.fontFamily);
+ }
+ if (style.fontSize) {
+ label.setAttributeNS(null, "font-size", style.fontSize);
+ }
+ if (style.fontWeight) {
+ label.setAttributeNS(null, "font-weight", style.fontWeight);
+ }
+ if (style.fontStyle) {
+ label.setAttributeNS(null, "font-style", style.fontStyle);
+ }
+ if (style.labelSelect === true) {
+ label.setAttributeNS(null, "pointer-events", "visible");
+ label._featureId = featureId;
+ } else {
+ label.setAttributeNS(null, "pointer-events", "none");
+ }
+ var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
+ label.setAttributeNS(null, "text-anchor",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
+
+ if (OpenLayers.IS_GECKO === true) {
+ label.setAttributeNS(null, "dominant-baseline",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
+ }
+
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ while (label.childNodes.length > numRows) {
+ label.removeChild(label.lastChild);
+ }
+ for (var i = 0; i < numRows; i++) {
+ var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
+ if (style.labelSelect === true) {
+ tspan._featureId = featureId;
+ tspan._geometry = location;
+ tspan._geometryClass = location.CLASS_NAME;
+ }
+ if (OpenLayers.IS_GECKO === false) {
+ tspan.setAttributeNS(null, "baseline-shift",
+ OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
+ }
+ tspan.setAttribute("x", x);
+ if (i == 0) {
+ var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
+ } else {
+ tspan.setAttribute("dy", "1em");
+ }
+ tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
+ if (!tspan.parentNode) {
+ label.appendChild(tspan);
+ }
+ }
+
+ if (!label.parentNode) {
+ this.textRoot.appendChild(label);
+ }
+ },
+
+ /**
+ * Method: getComponentString
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+ * separator - {String} character between coordinate pairs. Defaults to ","
+ *
+ * Returns:
+ * {Object} hash with properties "path" (the string created from the
+ * components and "complete" (false if the renderer was unable to
+ * draw all components)
+ */
+ getComponentsString: function(components, separator) {
+ var renderCmp = [];
+ var complete = true;
+ var len = components.length;
+ var strings = [];
+ var str, component;
+ for(var i=0; i<len; i++) {
+ component = components[i];
+ renderCmp.push(component);
+ str = this.getShortString(component);
+ if (str) {
+ strings.push(str);
+ } else {
+ // The current component is outside the valid range. Let's
+ // see if the previous or next component is inside the range.
+ // If so, add the coordinate of the intersection with the
+ // valid range bounds.
+ if (i > 0) {
+ if (this.getShortString(components[i - 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i-1]));
+ }
+ }
+ if (i < len - 1) {
+ if (this.getShortString(components[i + 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i+1]));
+ }
+ }
+ complete = false;
+ }
+ }
+
+ return {
+ path: strings.join(separator || ","),
+ complete: complete
+ };
+ },
+
+ /**
+ * Method: clipLine
+ * Given two points (one inside the valid range, and one outside),
+ * clips the line betweeen the two points so that the new points are both
+ * inside the valid range.
+ *
+ * Parameters:
+ * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * invalid point
+ * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * valid point
+ * Returns
+ * {String} the SVG coordinate pair of the clipped point (like
+ * getShortString), or an empty string if both passed componets are at
+ * the same point.
+ */
+ clipLine: function(badComponent, goodComponent) {
+ if (goodComponent.equals(badComponent)) {
+ return "";
+ }
+ var resolution = this.getResolution();
+ var maxX = this.MAX_PIXEL - this.translationParameters.x;
+ var maxY = this.MAX_PIXEL - this.translationParameters.y;
+ var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
+ var y1 = this.top - goodComponent.y / resolution;
+ var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
+ var y2 = this.top - badComponent.y / resolution;
+ var k;
+ if (x2 < -maxX || x2 > maxX) {
+ k = (y2 - y1) / (x2 - x1);
+ x2 = x2 < 0 ? -maxX : maxX;
+ y2 = y1 + (x2 - x1) * k;
+ }
+ if (y2 < -maxY || y2 > maxY) {
+ k = (x2 - x1) / (y2 - y1);
+ y2 = y2 < 0 ? -maxY : maxY;
+ x2 = x1 + (y2 - y1) * k;
+ }
+ return x2 + "," + y2;
+ },
+
+ /**
+ * Method: getShortString
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {String} or false if point is outside the valid range
+ */
+ getShortString: function(point) {
+ var resolution = this.getResolution();
+ var x = ((point.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - point.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ return x + "," + y;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getPosition
+ * Finds the position of an svg node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} hash with x and y properties, representing the coordinates
+ * within the svg coordinate system
+ */
+ getPosition: function(node) {
+ return({
+ x: parseFloat(node.getAttributeNS(null, "cx")),
+ y: parseFloat(node.getAttributeNS(null, "cy"))
+ });
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {DOMElement} - the imported symbol
+ */
+ importSymbol: function (graphicName) {
+ if (!this.defs) {
+ // create svg defs tag
+ this.defs = this.createDefs();
+ }
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the defs
+ var existing = document.getElementById(id);
+ if (existing != null) {
+ return existing;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolNode = this.nodeFactory(id, "symbol");
+ var node = this.nodeFactory(null, "polygon");
+ symbolNode.appendChild(node);
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var points = [];
+ var x,y;
+ for (var i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+ points.push(x, ",", y);
+ }
+
+ node.setAttributeNS(null, "points", points.join(" "));
+
+ var width = symbolExtent.getWidth();
+ var height = symbolExtent.getHeight();
+ // create a viewBox three times as large as the symbol itself,
+ // to allow for strokeWidth being displayed correctly at the corners.
+ var viewBox = [symbolExtent.left - width,
+ symbolExtent.bottom - height, width * 3, height * 3];
+ symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+ this.symbolMetrics[id] = [
+ Math.max(width, height),
+ symbolExtent.getCenterLonLat().lon,
+ symbolExtent.getCenterLonLat().lat
+ ];
+
+ this.defs.appendChild(symbolNode);
+ return symbolNode;
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
+ if(!featureId) {
+ var target = evt.target;
+ featureId = target.parentNode && target != this.rendererRoot ?
+ target.parentNode._featureId : undefined;
+ }
+ return featureId;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_ALIGN = {
+ "l": "start",
+ "r": "end",
+ "b": "bottom",
+ "t": "hanging"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
+ // according to
+ // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
+ // a baseline-shift of -70% shifts the text exactly from the
+ // bottom to the top of the baseline, so -35% moves the text to
+ // the center of the baseline.
+ "t": "-70%",
+ "b": "0"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Function: OpenLayers.Renderer.SVG.preventDefault
+ * *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead.
+ * Used to prevent default events (especially opening images in a new tab on
+ * ctrl-click) from being executed for externalGraphic symbols
+ */
+OpenLayers.Renderer.SVG.preventDefault = function(e) {
+ OpenLayers.Event.preventDefault(e);
+};
+/* ======================================================================
+ OpenLayers/Format/SLD/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1_0_0
+ * Write SLD version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.SLD.v1>
+ */
+OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.SLD.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/sld
+ * http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd
+ */
+ schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.SLD> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/OWSContext.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Context.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSContext
+ * Read and write OWS Context documents. OWS Context documents are a
+ * preliminary OGC (Open Geospatial Consortium) standard for storing the
+ * state of a web mapping application. In a way it is the successor to
+ * Web Map Context (WMC), since it is more generic and more types of layers
+ * can be stored. Also, nesting of layers is supported since version 0.3.1.
+ * For more information see: http://www.ogcnetwork.net/context
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Context>
+ */
+OpenLayers.Format.OWSContext = OpenLayers.Class(OpenLayers.Format.Context,{
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "0.3.1".
+ */
+ defaultVersion: "0.3.1",
+
+ /**
+ * Constructor: OpenLayers.Format.OWSContext
+ * Create a new parser for OWS Context documents.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version = OpenLayers.Format.XML.VersionedOGC.prototype.getVersion.apply(
+ this, arguments);
+ // 0.3.1 is backwards compatible with 0.3.0
+ if (version === "0.3.0") {
+ version = this.defaultVersion;
+ }
+ return version;
+ },
+
+ /**
+ * Method: toContext
+ * Create a context object free from layer given a map or a
+ * context object.
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Map> | Object} The map or context.
+ *
+ * Returns:
+ * {Object} A context object.
+ */
+ toContext: function(obj) {
+ var context = {};
+ if(obj.CLASS_NAME == "OpenLayers.Map") {
+ context.bounds = obj.getExtent();
+ context.maxExtent = obj.maxExtent;
+ context.projection = obj.projection;
+ context.size = obj.getSize();
+ context.layers = obj.layers;
+ }
+ return context;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSContext"
+
+});
+/* ======================================================================
+ OpenLayers/Format/OWSContext/v0_3_1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/KML.js
+ * @requires OpenLayers/Format/GML.js
+ * @requires OpenLayers/Format/GML/v2.js
+ * @requires OpenLayers/Format/SLD/v1_0_0.js
+ * @requires OpenLayers/Format/OWSContext.js
+ * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSContext.v0_3_1
+ * Read and write OWSContext version 0.3.1.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OWSContext.v0_3_1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ owc: "http://www.opengis.net/ows-context",
+ gml: "http://www.opengis.net/gml",
+ kml: "http://www.opengis.net/kml/2.2",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows",
+ sld: "http://www.opengis.net/sld",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 0.3.1
+ */
+ VERSION: "0.3.1",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd",
+
+ /**
+ * Property: defaultPrefix
+ * {String} Default namespace prefix to use.
+ */
+ defaultPrefix: "owc",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: featureNS
+ * {String} The namespace uri to use for writing InlineGeometry
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * Property: featureType
+ * {String} The name to use as the feature type when writing out
+ * InlineGeometry
+ */
+ featureType: 'vector',
+
+ /**
+ * Property: geometryName
+ * {String} The name to use for the geometry attribute when writing out
+ * InlineGeometry
+ */
+ geometryName: 'geometry',
+
+ /**
+ * Property: nestingLayerLookup
+ * {Object} Hashtable lookup for nesting layer nodes. Used while writing
+ * the OWS context document. It is necessary to keep track of the
+ * nestingPaths for which nesting layer nodes have already been
+ * created, so (nesting) layer nodes are added to those nodes.
+ *
+ * For example:
+ *
+ * If there are three layers with nestingPaths:
+ * layer1.metadata.nestingPath = "a/b/"
+ * layer2.metadata.nestingPath = "a/b/"
+ * layer2.metadata.nestingPath = "a/c"
+ *
+ * then a nesting layer node "a" should be created once and added
+ * to the resource list, a nesting layer node "b" should be created
+ * once and added under "a", and a nesting layer node "c" should be
+ * created and added under "a". The lookup paths for these nodes
+ * will be "a", "a/b", and "a/c" respectively.
+ */
+ nestingLayerLookup: null,
+
+ /**
+ * Constructor: OpenLayers.Format.OWSContext.v0_3_1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.OWSContext> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.GML.v2.prototype.setGeometryTypes.call(this);
+ },
+
+ /**
+ * Method: setNestingPath
+ * Set the nestingPath property of the layer depending on the position
+ * of the layer in hierarchy of layers.
+ *
+ * Parameters:
+ * l - {Object} An object that may have a layersContext array property.
+ *
+ */
+ setNestingPath : function(l){
+ if(l.layersContext){
+ for (var i = 0, len = l.layersContext.length; i < len; i++) {
+ var layerContext = l.layersContext[i];
+ var nPath = [];
+ var nTitle = l.title || "";
+ if(l.metadata && l.metadata.nestingPath){
+ nPath = l.metadata.nestingPath.slice();
+ }
+ if (nTitle != "") {
+ nPath.push(nTitle);
+ }
+ layerContext.metadata.nestingPath = nPath;
+ if(layerContext.layersContext){
+ this.setNestingPath(layerContext);
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: decomposeNestingPath
+ * Takes a nestingPath like "a/b/c" and decomposes it into subpaths:
+ * "a", "a/b", "a/b/c"
+ *
+ * Parameters:
+ * nPath - {Array} the nesting path
+ *
+ * Returns:
+ * Array({String}) Array with subpaths, or empty array if there is nothing
+ * to decompose
+ */
+ decomposeNestingPath: function(nPath){
+ var a = [];
+ if (OpenLayers.Util.isArray(nPath)) {
+ var path = nPath.slice();
+ while (path.length > 0) {
+ a.push(path.slice());
+ path.pop();
+ }
+ a.reverse();
+ }
+ return a;
+ },
+
+ /**
+ * APIMethod: read
+ * Read OWS context data from a string or DOMElement, and return a list
+ * of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} The context object with a flat layer list as a property named
+ * layersContext.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var context = {};
+ this.readNode(data, context);
+ // since an OWSContext can be nested we need to go through this
+ // structure recursively
+ this.setNestingPath({layersContext : context.layersContext});
+ // after nesting path has been set, create a flat list of layers
+ var layers = [];
+ this.processLayer(layers, context);
+ delete context.layersContext;
+ context.layersContext = layers;
+ return context;
+ },
+
+ /**
+ * Method: processLayer
+ * Recursive function to get back a flat list of layers from the hierarchic
+ * layer structure.
+ *
+ * Parameters:
+ * layerArray - {Array({Object})} Array of layerContext objects
+ * layer - {Object} layerContext object
+ */
+ processLayer: function(layerArray, layer) {
+ if (layer.layersContext) {
+ for (var i=0, len = layer.layersContext.length; i<len; i++) {
+ var l = layer.layersContext[i];
+ layerArray.push(l);
+ if (l.layersContext) {
+ this.processLayer(layerArray, l);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: write
+ *
+ * Parameters:
+ * context - {Object} An object representing the map context.
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} An OWS Context document string.
+ */
+ write: function(context, options) {
+ var name = "OWSContext";
+ this.nestingLayerLookup = {}; //start with empty lookup
+ options = options || {};
+ OpenLayers.Util.applyDefaults(options, context);
+ var root = this.writeNode(name, options);
+ this.nestingLayerLookup = null; //clear lookup
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "kml": {
+ "Document": function(node, obj) {
+ obj.features = new OpenLayers.Format.KML(
+ {kmlns: this.namespaces.kml,
+ extractStyles: true}).read(node);
+ }
+ },
+ "owc": {
+ "OWSContext": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "General": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "ResourceList": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Layer": function(node, obj) {
+ var layerContext = {
+ metadata: {},
+ visibility: (node.getAttribute("hidden") != "1"),
+ queryable: (node.getAttribute("queryable") == "1"),
+ opacity: ((node.getAttribute("opacity") != null) ?
+ parseFloat(node.getAttribute("opacity")) : null),
+ name: node.getAttribute("name"),
+ /* A category layer is a dummy layer meant for creating
+ hierarchies. It is not a physical layer in the
+ OpenLayers sense. The assumption we make here is that
+ category layers do not have a name attribute */
+ categoryLayer: (node.getAttribute("name") == null),
+ formats: [],
+ styles: []
+ };
+ if (!obj.layersContext) {
+ obj.layersContext = [];
+ }
+ obj.layersContext.push(layerContext);
+ this.readChildNodes(node, layerContext);
+ },
+ "InlineGeometry": function(node, obj) {
+ obj.features = [];
+ var elements = this.getElementsByTagNameNS(node,
+ this.namespaces.gml, "featureMember");
+ var el;
+ if (elements.length >= 1) {
+ el = elements[0];
+ }
+ if (el && el.firstChild) {
+ var featurenode = (el.firstChild.nextSibling) ?
+ el.firstChild.nextSibling : el.firstChild;
+ this.setNamespace("feature", featurenode.namespaceURI);
+ this.featureType = featurenode.localName ||
+ featurenode.nodeName.split(":").pop();
+ this.readChildNodes(node, obj);
+ }
+ },
+ "Server": function(node, obj) {
+ // when having multiple Server types, we prefer WMS
+ if ((!obj.service && !obj.version) ||
+ (obj.service !=
+ OpenLayers.Format.Context.serviceTypes.WMS)) {
+ obj.service = node.getAttribute("service");
+ obj.version = node.getAttribute("version");
+ this.readChildNodes(node, obj);
+ }
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ this.readChildNodes(node, obj);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ this.readChildNodes(node, obj);
+ },
+ "StyleList": function(node, obj) {
+ this.readChildNodes(node, obj.styles);
+ },
+ "Style": function(node, obj) {
+ var style = {};
+ obj.push(style);
+ this.readChildNodes(node, style);
+ },
+ "LegendURL": function(node, obj) {
+ var legend = {};
+ obj.legend = legend;
+ this.readChildNodes(node, legend);
+ },
+ "OnlineResource": function(node, obj) {
+ obj.url = this.getAttributeNS(node, this.namespaces.xlink,
+ "href");
+ this.readChildNodes(node, obj);
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows,
+ "gml": OpenLayers.Format.GML.v2.prototype.readers.gml,
+ "sld": OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld,
+ "feature": OpenLayers.Format.GML.v2.prototype.readers.feature
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "owc": {
+ "OWSContext": function(options) {
+ var node = this.createElementNSPlus("OWSContext", {
+ attributes: {
+ version: this.VERSION,
+ id: options.id || OpenLayers.Util.createUniqueID("OpenLayers_OWSContext_")
+ }
+ });
+ this.writeNode("General", options, node);
+ this.writeNode("ResourceList", options, node);
+ return node;
+ },
+ "General": function(options) {
+ var node = this.createElementNSPlus("General");
+ this.writeNode("ows:BoundingBox", options, node);
+ this.writeNode("ows:Title", options.title || 'OpenLayers OWSContext', node);
+ return node;
+ },
+ "ResourceList": function(options) {
+ var node = this.createElementNSPlus("ResourceList");
+ for (var i=0, len=options.layers.length; i<len; i++) {
+ var layer = options.layers[i];
+ var decomposedPath = this.decomposeNestingPath(layer.metadata.nestingPath);
+ this.writeNode("_Layer", {layer: layer, subPaths: decomposedPath}, node);
+ }
+ return node;
+ },
+ "Server": function(options) {
+ var node = this.createElementNSPlus("Server", {attributes: {
+ version: options.version,
+ service: options.service }
+ });
+ this.writeNode("OnlineResource", options, node);
+ return node;
+ },
+ "OnlineResource": function(options) {
+ var node = this.createElementNSPlus("OnlineResource", {attributes: {
+ "xlink:href": options.url }
+ });
+ return node;
+ },
+ "InlineGeometry": function(layer) {
+ var node = this.createElementNSPlus("InlineGeometry"),
+ dataExtent = layer.getDataExtent();
+ if (dataExtent !== null) {
+ this.writeNode("gml:boundedBy", dataExtent, node);
+ }
+ for (var i=0, len=layer.features.length; i<len; i++) {
+ this.writeNode("gml:featureMember", layer.features[i], node);
+ }
+ return node;
+ },
+ "StyleList": function(styles) {
+ var node = this.createElementNSPlus("StyleList");
+ for (var i=0, len=styles.length; i<len; i++) {
+ this.writeNode("Style", styles[i], node);
+ }
+ return node;
+ },
+ "Style": function(style) {
+ var node = this.createElementNSPlus("Style");
+ this.writeNode("Name", style, node);
+ this.writeNode("Title", style, node);
+ if (style.legend) {
+ this.writeNode("LegendURL", style, node);
+ }
+ return node;
+ },
+ "Name": function(obj) {
+ var node = this.createElementNSPlus("Name", {
+ value: obj.name });
+ return node;
+ },
+ "Title": function(obj) {
+ var node = this.createElementNSPlus("Title", {
+ value: obj.title });
+ return node;
+ },
+ "LegendURL": function(style) {
+ var node = this.createElementNSPlus("LegendURL");
+ this.writeNode("OnlineResource", style.legend, node);
+ return node;
+ },
+ "_WMS": function(layer) {
+ var node = this.createElementNSPlus("Layer", {attributes: {
+ name: layer.params.LAYERS,
+ queryable: layer.queryable ? "1" : "0",
+ hidden: layer.visibility ? "0" : "1",
+ opacity: layer.hasOwnProperty("opacity") ? layer.opacity : null}
+ });
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("ows:OutputFormat", layer.params.FORMAT, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.WMS,
+ version: layer.params.VERSION, url: layer.url}, node);
+ if (layer.metadata.styles && layer.metadata.styles.length > 0) {
+ this.writeNode("StyleList", layer.metadata.styles, node);
+ }
+ return node;
+ },
+ "_Layer": function(options) {
+ var layer, subPaths, node, title;
+ layer = options.layer;
+ subPaths = options.subPaths;
+ node = null;
+ title = null;
+ // subPaths is an array of an array
+ // recursively calling _Layer writer eats up subPaths, until a
+ // real writer is called and nodes are returned.
+ if(subPaths.length > 0){
+ var path = subPaths[0].join("/");
+ var index = path.lastIndexOf("/");
+ node = this.nestingLayerLookup[path];
+ title = (index > 0)?path.substring(index + 1, path.length):path;
+ if(!node){
+ // category layer
+ node = this.createElementNSPlus("Layer");
+ this.writeNode("ows:Title", title, node);
+ this.nestingLayerLookup[path] = node;
+ }
+ options.subPaths.shift();//remove a path after each call
+ this.writeNode("_Layer", options, node);
+ return node;
+ } else {
+ // write out the actual layer
+ if (layer instanceof OpenLayers.Layer.WMS) {
+ node = this.writeNode("_WMS", layer);
+ } else if (layer instanceof OpenLayers.Layer.Vector) {
+ if (layer.protocol instanceof OpenLayers.Protocol.WFS.v1) {
+ node = this.writeNode("_WFS", layer);
+ } else if (layer.protocol instanceof OpenLayers.Protocol.HTTP) {
+ if (layer.protocol.format instanceof OpenLayers.Format.GML) {
+ layer.protocol.format.version = "2.1.2";
+ node = this.writeNode("_GML", layer);
+ } else if (layer.protocol.format instanceof OpenLayers.Format.KML) {
+ layer.protocol.format.version = "2.2";
+ node = this.writeNode("_KML", layer);
+ }
+ } else {
+ // write out as inline GML since we have no idea
+ // about the original Format
+ this.setNamespace("feature", this.featureNS);
+ node = this.writeNode("_InlineGeometry", layer);
+ }
+ }
+ if (layer.options.maxScale) {
+ this.writeNode("sld:MinScaleDenominator",
+ layer.options.maxScale, node);
+ }
+ if (layer.options.minScale) {
+ this.writeNode("sld:MaxScaleDenominator",
+ layer.options.minScale, node);
+ }
+ this.nestingLayerLookup[layer.name] = node;
+ return node;
+ }
+ },
+ "_WFS": function(layer) {
+ var node = this.createElementNSPlus("Layer", {attributes: {
+ name: layer.protocol.featurePrefix + ":" + layer.protocol.featureType,
+ hidden: layer.visibility ? "0" : "1" }
+ });
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.WFS,
+ version: layer.protocol.version,
+ url: layer.protocol.url}, node);
+ return node;
+ },
+ "_InlineGeometry": function(layer) {
+ var node = this.createElementNSPlus("Layer", {attributes: {
+ name: this.featureType,
+ hidden: layer.visibility ? "0" : "1" }
+ });
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("InlineGeometry", layer, node);
+ return node;
+ },
+ "_GML": function(layer) {
+ var node = this.createElementNSPlus("Layer");
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.GML,
+ url: layer.protocol.url, version:
+ layer.protocol.format.version}, node);
+ return node;
+ },
+ "_KML": function(layer) {
+ var node = this.createElementNSPlus("Layer");
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.KML,
+ version: layer.protocol.format.version, url:
+ layer.protocol.url}, node);
+ return node;
+ }
+ },
+ "gml": OpenLayers.Util.applyDefaults({
+ "boundedBy": function(bounds) {
+ var node = this.createElementNSPlus("gml:boundedBy");
+ this.writeNode("gml:Box", bounds, node);
+ return node;
+ }
+ }, OpenLayers.Format.GML.v2.prototype.writers.gml),
+ "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.writers.ows,
+ "sld": OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld,
+ "feature": OpenLayers.Format.GML.v2.prototype.writers.feature
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSContext.v0_3_1"
+
+});
+/* ======================================================================
+ OpenLayers/Popup.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+
+/**
+ * Class: OpenLayers.Popup
+ * A popup is a small div that can opened and closed on the map.
+ * Typically opened in response to clicking on a marker.
+ * See <OpenLayers.Marker>. Popup's don't require their own
+ * layer and are added the the map using the <OpenLayers.Map.addPopup>
+ * method.
+ *
+ * Example:
+ * (code)
+ * popup = new OpenLayers.Popup("chicken",
+ * new OpenLayers.LonLat(5,40),
+ * new OpenLayers.Size(200,200),
+ * "example popup",
+ * true);
+ *
+ * map.addPopup(popup);
+ * (end)
+ */
+OpenLayers.Popup = OpenLayers.Class({
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} custom event manager
+ */
+ events: null,
+
+ /** Property: id
+ * {String} the unique identifier assigned to this popup.
+ */
+ id: "",
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>} the position of this popup on the map
+ */
+ lonlat: null,
+
+ /**
+ * Property: div
+ * {DOMElement} the div that contains this popup.
+ */
+ div: null,
+
+ /**
+ * Property: contentSize
+ * {<OpenLayers.Size>} the width and height of the content.
+ */
+ contentSize: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} the width and height of the popup.
+ */
+ size: null,
+
+ /**
+ * Property: contentHTML
+ * {String} An HTML string for this popup to display.
+ */
+ contentHTML: null,
+
+ /**
+ * Property: backgroundColor
+ * {String} the background color used by the popup.
+ */
+ backgroundColor: "",
+
+ /**
+ * Property: opacity
+ * {float} the opacity of this popup (between 0.0 and 1.0)
+ */
+ opacity: "",
+
+ /**
+ * Property: border
+ * {String} the border size of the popup. (eg 2px)
+ */
+ border: "",
+
+ /**
+ * Property: contentDiv
+ * {DOMElement} a reference to the element that holds the content of
+ * the div.
+ */
+ contentDiv: null,
+
+ /**
+ * Property: groupDiv
+ * {DOMElement} First and only child of 'div'. The group Div contains the
+ * 'contentDiv' and the 'closeDiv'.
+ */
+ groupDiv: null,
+
+ /**
+ * Property: closeDiv
+ * {DOMElement} the optional closer image
+ */
+ closeDiv: null,
+
+ /**
+ * APIProperty: autoSize
+ * {Boolean} Resize the popup to auto-fit the contents.
+ * Default is false.
+ */
+ autoSize: false,
+
+ /**
+ * APIProperty: minSize
+ * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
+ */
+ minSize: null,
+
+ /**
+ * APIProperty: maxSize
+ * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
+ */
+ maxSize: null,
+
+ /**
+ * Property: displayClass
+ * {String} The CSS class of the popup.
+ */
+ displayClass: "olPopup",
+
+ /**
+ * Property: contentDisplayClass
+ * {String} The CSS class of the popup content div.
+ */
+ contentDisplayClass: "olPopupContent",
+
+ /**
+ * Property: padding
+ * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal
+ * padding of the content div inside the popup. This was originally
+ * confused with the css padding as specified in style.css's
+ * 'olPopupContent' class. We would like to get rid of this altogether,
+ * except that it does come in handy for the framed and anchoredbubble
+ * popups, who need to maintain yet another barrier between their
+ * content and the outer border of the popup itself.
+ *
+ * Note that in order to not break API, we must continue to support
+ * this property being set as an integer. Really, though, we'd like to
+ * have this specified as a Bounds object so that user can specify
+ * distinct left, top, right, bottom paddings. With the 3.0 release
+ * we can make this only a bounds.
+ */
+ padding: 0,
+
+ /**
+ * Property: disableFirefoxOverflowHack
+ * {Boolean} The hack for overflow in Firefox causes all elements
+ * to be re-drawn, which causes Flash elements to be
+ * re-initialized, which is troublesome.
+ * With this property the hack can be disabled.
+ */
+ disableFirefoxOverflowHack: false,
+
+ /**
+ * Method: fixPadding
+ * To be removed in 3.0, this function merely helps us to deal with the
+ * case where the user may have set an integer value for padding,
+ * instead of an <OpenLayers.Bounds> object.
+ */
+ fixPadding: function() {
+ if (typeof this.padding == "number") {
+ this.padding = new OpenLayers.Bounds(
+ this.padding, this.padding, this.padding, this.padding
+ );
+ }
+ },
+
+ /**
+ * APIProperty: panMapIfOutOfView
+ * {Boolean} When drawn, pan map such that the entire popup is visible in
+ * the current viewport (if necessary).
+ * Default is false.
+ */
+ panMapIfOutOfView: false,
+
+ /**
+ * APIProperty: keepInMap
+ * {Boolean} If panMapIfOutOfView is false, and this property is true,
+ * contrain the popup such that it always fits in the available map
+ * space. By default, this is not set on the base class. If you are
+ * creating popups that are near map edges and not allowing pannning,
+ * and especially if you have a popup which has a
+ * fixedRelativePosition, setting this to false may be a smart thing to
+ * do. Subclasses may want to override this setting.
+ *
+ * Default is false.
+ */
+ keepInMap: false,
+
+ /**
+ * APIProperty: closeOnMove
+ * {Boolean} When map pans, close the popup.
+ * Default is false.
+ */
+ closeOnMove: false,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
+ */
+ map: null,
+
+ /**
+ * Constructor: OpenLayers.Popup
+ * Create a popup.
+ *
+ * Parameters:
+ * id - {String} a unqiue identifier for this popup. If null is passed
+ * an identifier will be automatically generated.
+ * lonlat - {<OpenLayers.LonLat>} The position on the map the popup will
+ * be shown.
+ * contentSize - {<OpenLayers.Size>} The size of the content.
+ * contentHTML - {String} An HTML string to display inside the
+ * popup.
+ * closeBox - {Boolean} Whether to display a close box inside
+ * the popup.
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
+ if (id == null) {
+ id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+
+ this.id = id;
+ this.lonlat = lonlat;
+
+ this.contentSize = (contentSize != null) ? contentSize
+ : new OpenLayers.Size(
+ OpenLayers.Popup.WIDTH,
+ OpenLayers.Popup.HEIGHT);
+ if (contentHTML != null) {
+ this.contentHTML = contentHTML;
+ }
+ this.backgroundColor = OpenLayers.Popup.COLOR;
+ this.opacity = OpenLayers.Popup.OPACITY;
+ this.border = OpenLayers.Popup.BORDER;
+
+ this.div = OpenLayers.Util.createDiv(this.id, null, null,
+ null, null, null, "hidden");
+ this.div.className = this.displayClass;
+
+ var groupDivId = this.id + "_GroupDiv";
+ this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null,
+ null, "relative", null,
+ "hidden");
+
+ var id = this.div.id + "_contentDiv";
+ this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(),
+ null, "relative");
+ this.contentDiv.className = this.contentDisplayClass;
+ this.groupDiv.appendChild(this.contentDiv);
+ this.div.appendChild(this.groupDiv);
+
+ if (closeBox) {
+ this.addCloseBox(closeBoxCallback);
+ }
+
+ this.registerEvents();
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ this.id = null;
+ this.lonlat = null;
+ this.size = null;
+ this.contentHTML = null;
+
+ this.backgroundColor = null;
+ this.opacity = null;
+ this.border = null;
+
+ if (this.closeOnMove && this.map) {
+ this.map.events.unregister("movestart", this, this.hide);
+ }
+
+ this.events.destroy();
+ this.events = null;
+
+ if (this.closeDiv) {
+ OpenLayers.Event.stopObservingElement(this.closeDiv);
+ this.groupDiv.removeChild(this.closeDiv);
+ }
+ this.closeDiv = null;
+
+ this.div.removeChild(this.groupDiv);
+ this.groupDiv = null;
+
+ if (this.map != null) {
+ this.map.removePopup(this);
+ }
+ this.map = null;
+ this.div = null;
+
+ this.autoSize = null;
+ this.minSize = null;
+ this.maxSize = null;
+ this.padding = null;
+ this.panMapIfOutOfView = null;
+ },
+
+ /**
+ * Method: draw
+ * Constructs the elements that make up the popup.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} the position the popup in pixels.
+ *
+ * Returns:
+ * {DOMElement} Reference to a div that contains the drawn popup
+ */
+ draw: function(px) {
+ if (px == null) {
+ if ((this.lonlat != null) && (this.map != null)) {
+ px = this.map.getLayerPxFromLonLat(this.lonlat);
+ }
+ }
+
+ // this assumes that this.map already exists, which is okay because
+ // this.draw is only called once the popup has been added to the map.
+ if (this.closeOnMove) {
+ this.map.events.register("movestart", this, this.hide);
+ }
+
+ //listen to movestart, moveend to disable overflow (FF bug)
+ if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == 'firefox') {
+ this.map.events.register("movestart", this, function() {
+ var style = document.defaultView.getComputedStyle(
+ this.contentDiv, null
+ );
+ var currentOverflow = style.getPropertyValue("overflow");
+ if (currentOverflow != "hidden") {
+ this.contentDiv._oldOverflow = currentOverflow;
+ this.contentDiv.style.overflow = "hidden";
+ }
+ });
+ this.map.events.register("moveend", this, function() {
+ var oldOverflow = this.contentDiv._oldOverflow;
+ if (oldOverflow) {
+ this.contentDiv.style.overflow = oldOverflow;
+ this.contentDiv._oldOverflow = null;
+ }
+ });
+ }
+
+ this.moveTo(px);
+ if (!this.autoSize && !this.size) {
+ this.setSize(this.contentSize);
+ }
+ this.setBackgroundColor();
+ this.setOpacity();
+ this.setBorder();
+ this.setContentHTML();
+
+ if (this.panMapIfOutOfView) {
+ this.panIntoView();
+ }
+
+ return this.div;
+ },
+
+ /**
+ * Method: updatePosition
+ * if the popup has a lonlat and its map members set,
+ * then have it move itself to its proper position
+ */
+ updatePosition: function() {
+ if ((this.lonlat) && (this.map)) {
+ var px = this.map.getLayerPxFromLonLat(this.lonlat);
+ if (px) {
+ this.moveTo(px);
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} the top and left position of the popup div.
+ */
+ moveTo: function(px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * Method: visible
+ *
+ * Returns:
+ * {Boolean} Boolean indicating whether or not the popup is visible
+ */
+ visible: function() {
+ return OpenLayers.Element.visible(this.div);
+ },
+
+ /**
+ * Method: toggle
+ * Toggles visibility of the popup.
+ */
+ toggle: function() {
+ if (this.visible()) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ },
+
+ /**
+ * Method: show
+ * Makes the popup visible.
+ */
+ show: function() {
+ this.div.style.display = '';
+
+ if (this.panMapIfOutOfView) {
+ this.panIntoView();
+ }
+ },
+
+ /**
+ * Method: hide
+ * Makes the popup invisible.
+ */
+ hide: function() {
+ this.div.style.display = 'none';
+ },
+
+ /**
+ * Method: setSize
+ * Used to adjust the size of the popup.
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ this.size = contentSize.clone();
+
+ // if our contentDiv has a css 'padding' set on it by a stylesheet, we
+ // must add that to the desired "size".
+ var contentDivPadding = this.getContentDivPadding();
+ var wPadding = contentDivPadding.left + contentDivPadding.right;
+ var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+ // take into account the popup's 'padding' property
+ this.fixPadding();
+ wPadding += this.padding.left + this.padding.right;
+ hPadding += this.padding.top + this.padding.bottom;
+
+ // make extra space for the close div
+ if (this.closeDiv) {
+ var closeDivWidth = parseInt(this.closeDiv.style.width);
+ wPadding += closeDivWidth + contentDivPadding.right;
+ }
+
+ //increase size of the main popup div to take into account the
+ // users's desired padding and close div.
+ this.size.w += wPadding;
+ this.size.h += hPadding;
+
+ //now if our browser is IE, we need to actually make the contents
+ // div itself bigger to take its own padding into effect. this makes
+ // me want to shoot someone, but so it goes.
+ if (OpenLayers.BROWSER_NAME == "msie") {
+ this.contentSize.w +=
+ contentDivPadding.left + contentDivPadding.right;
+ this.contentSize.h +=
+ contentDivPadding.bottom + contentDivPadding.top;
+ }
+
+ if (this.div != null) {
+ this.div.style.width = this.size.w + "px";
+ this.div.style.height = this.size.h + "px";
+ }
+ if (this.contentDiv != null){
+ this.contentDiv.style.width = contentSize.w + "px";
+ this.contentDiv.style.height = contentSize.h + "px";
+ }
+ },
+
+ /**
+ * APIMethod: updateSize
+ * Auto size the popup so that it precisely fits its contents (as
+ * determined by this.contentDiv.innerHTML). Popup size will, of
+ * course, be limited by the available space on the current map
+ */
+ updateSize: function() {
+
+ // determine actual render dimensions of the contents by putting its
+ // contents into a fake contentDiv (for the CSS) and then measuring it
+ var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" +
+ this.contentDiv.innerHTML +
+ "</div>";
+
+ var containerElement = (this.map) ? this.map.div : document.body;
+ var realSize = OpenLayers.Util.getRenderedDimensions(
+ preparedHTML, null, {
+ displayClass: this.displayClass,
+ containerElement: containerElement
+ }
+ );
+
+ // is the "real" size of the div is safe to display in our map?
+ var safeSize = this.getSafeContentSize(realSize);
+
+ var newSize = null;
+ if (safeSize.equals(realSize)) {
+ //real size of content is small enough to fit on the map,
+ // so we use real size.
+ newSize = realSize;
+
+ } else {
+
+ // make a new 'size' object with the clipped dimensions
+ // set or null if not clipped.
+ var fixedSize = {
+ w: (safeSize.w < realSize.w) ? safeSize.w : null,
+ h: (safeSize.h < realSize.h) ? safeSize.h : null
+ };
+
+ if (fixedSize.w && fixedSize.h) {
+ //content is too big in both directions, so we will use
+ // max popup size (safeSize), knowing well that it will
+ // overflow both ways.
+ newSize = safeSize;
+ } else {
+ //content is clipped in only one direction, so we need to
+ // run getRenderedDimensions() again with a fixed dimension
+ var clippedSize = OpenLayers.Util.getRenderedDimensions(
+ preparedHTML, fixedSize, {
+ displayClass: this.contentDisplayClass,
+ containerElement: containerElement
+ }
+ );
+
+ //if the clipped size is still the same as the safeSize,
+ // that means that our content must be fixed in the
+ // offending direction. If overflow is 'auto', this means
+ // we are going to have a scrollbar for sure, so we must
+ // adjust for that.
+ //
+ var currentOverflow = OpenLayers.Element.getStyle(
+ this.contentDiv, "overflow"
+ );
+ if ( (currentOverflow != "hidden") &&
+ (clippedSize.equals(safeSize)) ) {
+ var scrollBar = OpenLayers.Util.getScrollbarWidth();
+ if (fixedSize.w) {
+ clippedSize.h += scrollBar;
+ } else {
+ clippedSize.w += scrollBar;
+ }
+ }
+
+ newSize = this.getSafeContentSize(clippedSize);
+ }
+ }
+ this.setSize(newSize);
+ },
+
+ /**
+ * Method: setBackgroundColor
+ * Sets the background color of the popup.
+ *
+ * Parameters:
+ * color - {String} the background color. eg "#FFBBBB"
+ */
+ setBackgroundColor:function(color) {
+ if (color != undefined) {
+ this.backgroundColor = color;
+ }
+
+ if (this.div != null) {
+ this.div.style.backgroundColor = this.backgroundColor;
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Sets the opacity of the popup.
+ *
+ * Parameters:
+ * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
+ */
+ setOpacity:function(opacity) {
+ if (opacity != undefined) {
+ this.opacity = opacity;
+ }
+
+ if (this.div != null) {
+ // for Mozilla and Safari
+ this.div.style.opacity = this.opacity;
+
+ // for IE
+ this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
+ }
+ },
+
+ /**
+ * Method: setBorder
+ * Sets the border style of the popup.
+ *
+ * Parameters:
+ * border - {String} The border style value. eg 2px
+ */
+ setBorder:function(border) {
+ if (border != undefined) {
+ this.border = border;
+ }
+
+ if (this.div != null) {
+ this.div.style.border = this.border;
+ }
+ },
+
+ /**
+ * Method: setContentHTML
+ * Allows the user to set the HTML content of the popup.
+ *
+ * Parameters:
+ * contentHTML - {String} HTML for the div.
+ */
+ setContentHTML:function(contentHTML) {
+
+ if (contentHTML != null) {
+ this.contentHTML = contentHTML;
+ }
+
+ if ((this.contentDiv != null) &&
+ (this.contentHTML != null) &&
+ (this.contentHTML != this.contentDiv.innerHTML)) {
+
+ this.contentDiv.innerHTML = this.contentHTML;
+
+ if (this.autoSize) {
+
+ //if popup has images, listen for when they finish
+ // loading and resize accordingly
+ this.registerImageListeners();
+
+ //auto size the popup to its current contents
+ this.updateSize();
+ }
+ }
+
+ },
+
+ /**
+ * Method: registerImageListeners
+ * Called when an image contained by the popup loaded. this function
+ * updates the popup size, then unregisters the image load listener.
+ */
+ registerImageListeners: function() {
+
+ // As the images load, this function will call updateSize() to
+ // resize the popup to fit the content div (which presumably is now
+ // bigger than when the image was not loaded).
+ //
+ // If the 'panMapIfOutOfView' property is set, we will pan the newly
+ // resized popup back into view.
+ //
+ // Note that this function, when called, will have 'popup' and
+ // 'img' properties in the context.
+ //
+ var onImgLoad = function() {
+ if (this.popup.id === null) { // this.popup has been destroyed!
+ return;
+ }
+ this.popup.updateSize();
+
+ if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
+ this.popup.panIntoView();
+ }
+
+ OpenLayers.Event.stopObserving(
+ this.img, "load", this.img._onImgLoad
+ );
+
+ };
+
+ //cycle through the images and if their size is 0x0, that means that
+ // they haven't been loaded yet, so we attach the listener, which
+ // will fire when the images finish loading and will resize the
+ // popup accordingly to its new size.
+ var images = this.contentDiv.getElementsByTagName("img");
+ for (var i = 0, len = images.length; i < len; i++) {
+ var img = images[i];
+ if (img.width == 0 || img.height == 0) {
+
+ var context = {
+ 'popup': this,
+ 'img': img
+ };
+
+ //expando this function to the image itself before registering
+ // it. This way we can easily and properly unregister it.
+ img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
+
+ OpenLayers.Event.observe(img, 'load', img._onImgLoad);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getSafeContentSize
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} Desired size to make the popup.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A size to make the popup which is neither smaller
+ * than the specified minimum size, nor bigger than the maximum
+ * size (which is calculated relative to the size of the viewport).
+ */
+ getSafeContentSize: function(size) {
+
+ var safeContentSize = size.clone();
+
+ // if our contentDiv has a css 'padding' set on it by a stylesheet, we
+ // must add that to the desired "size".
+ var contentDivPadding = this.getContentDivPadding();
+ var wPadding = contentDivPadding.left + contentDivPadding.right;
+ var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+ // take into account the popup's 'padding' property
+ this.fixPadding();
+ wPadding += this.padding.left + this.padding.right;
+ hPadding += this.padding.top + this.padding.bottom;
+
+ if (this.closeDiv) {
+ var closeDivWidth = parseInt(this.closeDiv.style.width);
+ wPadding += closeDivWidth + contentDivPadding.right;
+ }
+
+ // prevent the popup from being smaller than a specified minimal size
+ if (this.minSize) {
+ safeContentSize.w = Math.max(safeContentSize.w,
+ (this.minSize.w - wPadding));
+ safeContentSize.h = Math.max(safeContentSize.h,
+ (this.minSize.h - hPadding));
+ }
+
+ // prevent the popup from being bigger than a specified maximum size
+ if (this.maxSize) {
+ safeContentSize.w = Math.min(safeContentSize.w,
+ (this.maxSize.w - wPadding));
+ safeContentSize.h = Math.min(safeContentSize.h,
+ (this.maxSize.h - hPadding));
+ }
+
+ //make sure the desired size to set doesn't result in a popup that
+ // is bigger than the map's viewport.
+ //
+ if (this.map && this.map.size) {
+
+ var extraX = 0, extraY = 0;
+ if (this.keepInMap && !this.panMapIfOutOfView) {
+ var px = this.map.getPixelFromLonLat(this.lonlat);
+ switch (this.relativePosition) {
+ case "tr":
+ extraX = px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ case "tl":
+ extraX = this.map.size.w - px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ case "bl":
+ extraX = this.map.size.w - px.x;
+ extraY = px.y;
+ break;
+ case "br":
+ extraX = px.x;
+ extraY = px.y;
+ break;
+ default:
+ extraX = px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ }
+ }
+
+ var maxY = this.map.size.h -
+ this.map.paddingForPopups.top -
+ this.map.paddingForPopups.bottom -
+ hPadding - extraY;
+
+ var maxX = this.map.size.w -
+ this.map.paddingForPopups.left -
+ this.map.paddingForPopups.right -
+ wPadding - extraX;
+
+ safeContentSize.w = Math.min(safeContentSize.w, maxX);
+ safeContentSize.h = Math.min(safeContentSize.h, maxY);
+ }
+
+ return safeContentSize;
+ },
+
+ /**
+ * Method: getContentDivPadding
+ * Glorious, oh glorious hack in order to determine the css 'padding' of
+ * the contentDiv. IE/Opera return null here unless we actually add the
+ * popup's main 'div' element (which contains contentDiv) to the DOM.
+ * So we make it invisible and then add it to the document temporarily.
+ *
+ * Once we've taken the padding readings we need, we then remove it
+ * from the DOM (it will actually get added to the DOM in
+ * Map.js's addPopup)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getContentDivPadding: function() {
+
+ //use cached value if we have it
+ var contentDivPadding = this._contentDivPadding;
+ if (!contentDivPadding) {
+
+ if (this.div.parentNode == null) {
+ //make the div invisible and add it to the page
+ this.div.style.display = "none";
+ document.body.appendChild(this.div);
+ }
+
+ //read the padding settings from css, put them in an OL.Bounds
+ contentDivPadding = new OpenLayers.Bounds(
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
+ );
+
+ //cache the value
+ this._contentDivPadding = contentDivPadding;
+
+ if (this.div.parentNode == document.body) {
+ //remove the div from the page and make it visible again
+ document.body.removeChild(this.div);
+ this.div.style.display = "";
+ }
+ }
+ return contentDivPadding;
+ },
+
+ /**
+ * Method: addCloseBox
+ *
+ * Parameters:
+ * callback - {Function} The callback to be called when the close button
+ * is clicked.
+ */
+ addCloseBox: function(callback) {
+
+ this.closeDiv = OpenLayers.Util.createDiv(
+ this.id + "_close", null, {w: 17, h: 17}
+ );
+ this.closeDiv.className = "olPopupCloseBox";
+
+ // use the content div's css padding to determine if we should
+ // padd the close div
+ var contentDivPadding = this.getContentDivPadding();
+
+ this.closeDiv.style.right = contentDivPadding.right + "px";
+ this.closeDiv.style.top = contentDivPadding.top + "px";
+ this.groupDiv.appendChild(this.closeDiv);
+
+ var closePopup = callback || function(e) {
+ this.hide();
+ OpenLayers.Event.stop(e);
+ };
+ OpenLayers.Event.observe(this.closeDiv, "touchend",
+ OpenLayers.Function.bindAsEventListener(closePopup, this));
+ OpenLayers.Event.observe(this.closeDiv, "click",
+ OpenLayers.Function.bindAsEventListener(closePopup, this));
+ },
+
+ /**
+ * Method: panIntoView
+ * Pans the map such that the popup is totaly viewable (if necessary)
+ */
+ panIntoView: function() {
+
+ var mapSize = this.map.getSize();
+
+ //start with the top left corner of the popup, in px,
+ // relative to the viewport
+ var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
+ parseInt(this.div.style.left),
+ parseInt(this.div.style.top)
+ ));
+ var newTL = origTL.clone();
+
+ //new left (compare to margins, using this.size to calculate right)
+ if (origTL.x < this.map.paddingForPopups.left) {
+ newTL.x = this.map.paddingForPopups.left;
+ } else
+ if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
+ newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
+ }
+
+ //new top (compare to margins, using this.size to calculate bottom)
+ if (origTL.y < this.map.paddingForPopups.top) {
+ newTL.y = this.map.paddingForPopups.top;
+ } else
+ if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
+ newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
+ }
+
+ var dx = origTL.x - newTL.x;
+ var dy = origTL.y - newTL.y;
+
+ this.map.pan(dx, dy);
+ },
+
+ /**
+ * Method: registerEvents
+ * Registers events on the popup.
+ *
+ * Do this in a separate function so that subclasses can
+ * choose to override it if they wish to deal differently
+ * with mouse events
+ *
+ * Note in the following handler functions that some special
+ * care is needed to deal correctly with mousing and popups.
+ *
+ * Because the user might select the zoom-rectangle option and
+ * then drag it over a popup, we need a safe way to allow the
+ * mousemove and mouseup events to pass through the popup when
+ * they are initiated from outside. The same procedure is needed for
+ * touchmove and touchend events.
+ *
+ * Otherwise, we want to essentially kill the event propagation
+ * for all other events, though we have to do so carefully,
+ * without disabling basic html functionality, like clicking on
+ * hyperlinks or drag-selecting text.
+ */
+ registerEvents:function() {
+ this.events = new OpenLayers.Events(this, this.div, null, true);
+
+ function onTouchstart(evt) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ this.events.on({
+ "mousedown": this.onmousedown,
+ "mousemove": this.onmousemove,
+ "mouseup": this.onmouseup,
+ "click": this.onclick,
+ "mouseout": this.onmouseout,
+ "dblclick": this.ondblclick,
+ "touchstart": onTouchstart,
+ scope: this
+ });
+
+ },
+
+ /**
+ * Method: onmousedown
+ * When mouse goes down within the popup, make a note of
+ * it locally, and then do not propagate the mousedown
+ * (but do so safely so that user can select text inside)
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmousedown: function (evt) {
+ this.mousedown = true;
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ /**
+ * Method: onmousemove
+ * If the drag was started within the popup, then
+ * do not propagate the mousemove (but do so safely
+ * so that user can select text inside)
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmousemove: function (evt) {
+ if (this.mousedown) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ },
+
+ /**
+ * Method: onmouseup
+ * When mouse comes up within the popup, after going down
+ * in it, reset the flag, and then (once again) do not
+ * propagate the event, but do so safely so that user can
+ * select text inside
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmouseup: function (evt) {
+ if (this.mousedown) {
+ this.mousedown = false;
+ OpenLayers.Event.stop(evt, true);
+ }
+ },
+
+ /**
+ * Method: onclick
+ * Ignore clicks, but allowing default browser handling
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onclick: function (evt) {
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ /**
+ * Method: onmouseout
+ * When mouse goes out of the popup set the flag to false so that
+ * if they let go and then drag back in, we won't be confused.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmouseout: function (evt) {
+ this.mousedown = false;
+ },
+
+ /**
+ * Method: ondblclick
+ * Ignore double-clicks, but allowing default browser handling
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ ondblclick: function (evt) {
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ CLASS_NAME: "OpenLayers.Popup"
+});
+
+OpenLayers.Popup.WIDTH = 200;
+OpenLayers.Popup.HEIGHT = 200;
+OpenLayers.Popup.COLOR = "white";
+OpenLayers.Popup.OPACITY = 1;
+OpenLayers.Popup.BORDER = "0px";
+/* ======================================================================
+ OpenLayers/Control/ScaleLine.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ScaleLine
+ * The ScaleLine displays a small line indicator representing the current
+ * map scale on the map. By default it is drawn in the lower left corner of
+ * the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ *
+ * Is a very close copy of:
+ * - <OpenLayers.Control.Scale>
+ */
+OpenLayers.Control.ScaleLine = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: maxWidth
+ * {Integer} Maximum width of the scale line in pixels. Default is 100.
+ */
+ maxWidth: 100,
+
+ /**
+ * Property: topOutUnits
+ * {String} Units for zoomed out on top bar. Default is km.
+ */
+ topOutUnits: "km",
+
+ /**
+ * Property: topInUnits
+ * {String} Units for zoomed in on top bar. Default is m.
+ */
+ topInUnits: "m",
+
+ /**
+ * Property: bottomOutUnits
+ * {String} Units for zoomed out on bottom bar. Default is mi.
+ */
+ bottomOutUnits: "mi",
+
+ /**
+ * Property: bottomInUnits
+ * {String} Units for zoomed in on bottom bar. Default is ft.
+ */
+ bottomInUnits: "ft",
+
+ /**
+ * Property: eTop
+ * {DOMElement}
+ */
+ eTop: null,
+
+ /**
+ * Property: eBottom
+ * {DOMElement}
+ */
+ eBottom:null,
+
+ /**
+ * APIProperty: geodesic
+ * {Boolean} Use geodesic measurement. Default is false. The recommended
+ * setting for maps in EPSG:4326 is false, and true EPSG:900913. If set to
+ * true, the scale will be calculated based on the horizontal size of the
+ * pixel in the center of the map viewport.
+ */
+ geodesic: false,
+
+ /**
+ * Constructor: OpenLayers.Control.ScaleLine
+ * Create a new scale line control.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (!this.eTop) {
+ // stick in the top bar
+ this.eTop = document.createElement("div");
+ this.eTop.className = this.displayClass + "Top";
+ var theLen = this.topInUnits.length;
+ this.div.appendChild(this.eTop);
+ if((this.topOutUnits == "") || (this.topInUnits == "")) {
+ this.eTop.style.visibility = "hidden";
+ } else {
+ this.eTop.style.visibility = "visible";
+ }
+
+ // and the bottom bar
+ this.eBottom = document.createElement("div");
+ this.eBottom.className = this.displayClass + "Bottom";
+ this.div.appendChild(this.eBottom);
+ if((this.bottomOutUnits == "") || (this.bottomInUnits == "")) {
+ this.eBottom.style.visibility = "hidden";
+ } else {
+ this.eBottom.style.visibility = "visible";
+ }
+ }
+ this.map.events.register('moveend', this, this.update);
+ this.update();
+ return this.div;
+ },
+
+ /**
+ * Method: getBarLen
+ * Given a number, round it down to the nearest 1,2,5 times a power of 10.
+ * That seems a fairly useful set of number groups to use.
+ *
+ * Parameters:
+ * maxLen - {float} the number we're rounding down from
+ *
+ * Returns:
+ * {Float} the rounded number (less than or equal to maxLen)
+ */
+ getBarLen: function(maxLen) {
+ // nearest power of 10 lower than maxLen
+ var digits = parseInt(Math.log(maxLen) / Math.log(10));
+ var pow10 = Math.pow(10, digits);
+
+ // ok, find first character
+ var firstChar = parseInt(maxLen / pow10);
+
+ // right, put it into the correct bracket
+ var barLen;
+ if(firstChar > 5) {
+ barLen = 5;
+ } else if(firstChar > 2) {
+ barLen = 2;
+ } else {
+ barLen = 1;
+ }
+
+ // scale it up the correct power of 10
+ return barLen * pow10;
+ },
+
+ /**
+ * Method: update
+ * Update the size of the bars, and the labels they contain.
+ */
+ update: function() {
+ var res = this.map.getResolution();
+ if (!res) {
+ return;
+ }
+
+ var curMapUnits = this.map.getUnits();
+ var inches = OpenLayers.INCHES_PER_UNIT;
+
+ // convert maxWidth to map units
+ var maxSizeData = this.maxWidth * res * inches[curMapUnits];
+ var geodesicRatio = 1;
+ if(this.geodesic === true) {
+ var maxSizeGeodesic = (this.map.getGeodesicPixelSize().w ||
+ 0.000001) * this.maxWidth;
+ var maxSizeKilometers = maxSizeData / inches["km"];
+ geodesicRatio = maxSizeGeodesic / maxSizeKilometers;
+ maxSizeData *= geodesicRatio;
+ }
+
+ // decide whether to use large or small scale units
+ var topUnits;
+ var bottomUnits;
+ if(maxSizeData > 100000) {
+ topUnits = this.topOutUnits;
+ bottomUnits = this.bottomOutUnits;
+ } else {
+ topUnits = this.topInUnits;
+ bottomUnits = this.bottomInUnits;
+ }
+
+ // and to map units units
+ var topMax = maxSizeData / inches[topUnits];
+ var bottomMax = maxSizeData / inches[bottomUnits];
+
+ // now trim this down to useful block length
+ var topRounded = this.getBarLen(topMax);
+ var bottomRounded = this.getBarLen(bottomMax);
+
+ // and back to display units
+ topMax = topRounded / inches[curMapUnits] * inches[topUnits];
+ bottomMax = bottomRounded / inches[curMapUnits] * inches[bottomUnits];
+
+ // and to pixel units
+ var topPx = topMax / res / geodesicRatio;
+ var bottomPx = bottomMax / res / geodesicRatio;
+
+ // now set the pixel widths
+ // and the values inside them
+
+ if (this.eBottom.style.visibility == "visible"){
+ this.eBottom.style.width = Math.round(bottomPx) + "px";
+ this.eBottom.innerHTML = bottomRounded + " " + bottomUnits ;
+ }
+
+ if (this.eTop.style.visibility == "visible"){
+ this.eTop.style.width = Math.round(topPx) + "px";
+ this.eTop.innerHTML = topRounded + " " + topUnits;
+ }
+
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ScaleLine"
+});
+
+/* ======================================================================
+ OpenLayers/Icon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Icon
+ *
+ * The icon represents a graphical icon on the screen. Typically used in
+ * conjunction with a <OpenLayers.Marker> to represent markers on a screen.
+ *
+ * An icon has a url, size and position. It also contains an offset which
+ * allows the center point to be represented correctly. This can be
+ * provided either as a fixed offset or a function provided to calculate
+ * the desired offset.
+ *
+ */
+OpenLayers.Icon = OpenLayers.Class({
+
+ /**
+ * Property: url
+ * {String} image url
+ */
+ url: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>|Object} An OpenLayers.Size or
+ * an object with a 'w' and 'h' properties.
+ */
+ size: null,
+
+ /**
+ * Property: offset
+ * {<OpenLayers.Pixel>|Object} distance in pixels to offset the
+ * image when being rendered. An OpenLayers.Pixel or an object
+ * with a 'x' and 'y' properties.
+ */
+ offset: null,
+
+ /**
+ * Property: calculateOffset
+ * {Function} Function to calculate the offset (based on the size)
+ */
+ calculateOffset: null,
+
+ /**
+ * Property: imageDiv
+ * {DOMElement}
+ */
+ imageDiv: null,
+
+ /**
+ * Property: px
+ * {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object
+ * with a 'x' and 'y' properties.
+ */
+ px: null,
+
+ /**
+ * Constructor: OpenLayers.Icon
+ * Creates an icon, which is an image tag in a div.
+ *
+ * url - {String}
+ * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or an
+ * object with a 'w' and 'h'
+ * properties.
+ * offset - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
+ * object with a 'x' and 'y'
+ * properties.
+ * calculateOffset - {Function}
+ */
+ initialize: function(url, size, offset, calculateOffset) {
+ this.url = url;
+ this.size = size || {w: 20, h: 20};
+ this.offset = offset || {x: -(this.size.w/2), y: -(this.size.h/2)};
+ this.calculateOffset = calculateOffset;
+
+ var id = OpenLayers.Util.createUniqueID("OL_Icon_");
+ this.imageDiv = OpenLayers.Util.createAlphaImageDiv(id);
+ },
+
+ /**
+ * Method: destroy
+ * Nullify references and remove event listeners to prevent circular
+ * references and memory leaks
+ */
+ destroy: function() {
+ // erase any drawn elements
+ this.erase();
+
+ OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);
+ this.imageDiv.innerHTML = "";
+ this.imageDiv = null;
+ },
+
+ /**
+ * Method: clone
+ *
+ * Returns:
+ * {<OpenLayers.Icon>} A fresh copy of the icon.
+ */
+ clone: function() {
+ return new OpenLayers.Icon(this.url,
+ this.size,
+ this.offset,
+ this.calculateOffset);
+ },
+
+ /**
+ * Method: setSize
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or
+ * an object with a 'w' and 'h' properties.
+ */
+ setSize: function(size) {
+ if (size != null) {
+ this.size = size;
+ }
+ this.draw();
+ },
+
+ /**
+ * Method: setUrl
+ *
+ * Parameters:
+ * url - {String}
+ */
+ setUrl: function(url) {
+ if (url != null) {
+ this.url = url;
+ }
+ this.draw();
+ },
+
+ /**
+ * Method: draw
+ * Move the div to the given pixel.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
+ * object with a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {DOMElement} A new DOM Image of this icon set at the location passed-in
+ */
+ draw: function(px) {
+ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,
+ null,
+ null,
+ this.size,
+ this.url,
+ "absolute");
+ this.moveTo(px);
+ return this.imageDiv;
+ },
+
+ /**
+ * Method: erase
+ * Erase the underlying image element.
+ */
+ erase: function() {
+ if (this.imageDiv != null && this.imageDiv.parentNode != null) {
+ OpenLayers.Element.remove(this.imageDiv);
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Change the icon's opacity
+ *
+ * Parameters:
+ * opacity - {float}
+ */
+ setOpacity: function(opacity) {
+ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null,
+ null, null, null, null, opacity);
+
+ },
+
+ /**
+ * Method: moveTo
+ * move icon to passed in px.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
+ * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
+ */
+ moveTo: function (px) {
+ //if no px passed in, use stored location
+ if (px != null) {
+ this.px = px;
+ }
+
+ if (this.imageDiv != null) {
+ if (this.px == null) {
+ this.display(false);
+ } else {
+ if (this.calculateOffset) {
+ this.offset = this.calculateOffset(this.size);
+ }
+ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {
+ x: this.px.x + this.offset.x,
+ y: this.px.y + this.offset.y
+ });
+ }
+ }
+ },
+
+ /**
+ * Method: display
+ * Hide or show the icon
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ this.imageDiv.style.display = (display) ? "" : "none";
+ },
+
+
+ /**
+ * APIMethod: isDrawn
+ *
+ * Returns:
+ * {Boolean} Whether or not the icon is drawn.
+ */
+ isDrawn: function() {
+ // nodeType 11 for ie, whose nodes *always* have a parentNode
+ // (of type document fragment)
+ var isDrawn = (this.imageDiv && this.imageDiv.parentNode &&
+ (this.imageDiv.parentNode.nodeType != 11));
+
+ return isDrawn;
+ },
+
+ CLASS_NAME: "OpenLayers.Icon"
+});
+/* ======================================================================
+ OpenLayers/Marker.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Icon.js
+ */
+
+/**
+ * Class: OpenLayers.Marker
+ * Instances of OpenLayers.Marker are a combination of a
+ * <OpenLayers.LonLat> and an <OpenLayers.Icon>.
+ *
+ * Markers are generally added to a special layer called
+ * <OpenLayers.Layer.Markers>.
+ *
+ * Example:
+ * (code)
+ * var markers = new OpenLayers.Layer.Markers( "Markers" );
+ * map.addLayer(markers);
+ *
+ * var size = new OpenLayers.Size(21,25);
+ * var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+ * var icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png', size, offset);
+ * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
+ * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon.clone()));
+ *
+ * (end)
+ *
+ * Note that if you pass an icon into the Marker constructor, it will take
+ * that icon and use it. This means that you should not share icons between
+ * markers -- you use them once, but you should clone() for any additional
+ * markers using that same icon.
+ */
+OpenLayers.Marker = OpenLayers.Class({
+
+ /**
+ * Property: icon
+ * {<OpenLayers.Icon>} The icon used by this marker.
+ */
+ icon: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>} location of object
+ */
+ lonlat: null,
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} the event handler.
+ */
+ events: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} the map this marker is attached to
+ */
+ map: null,
+
+ /**
+ * Constructor: OpenLayers.Marker
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} the position of this marker
+ * icon - {<OpenLayers.Icon>} the icon for this marker
+ */
+ initialize: function(lonlat, icon) {
+ this.lonlat = lonlat;
+
+ var newIcon = (icon) ? icon : OpenLayers.Marker.defaultIcon();
+ if (this.icon == null) {
+ this.icon = newIcon;
+ } else {
+ this.icon.url = newIcon.url;
+ this.icon.size = newIcon.size;
+ this.icon.offset = newIcon.offset;
+ this.icon.calculateOffset = newIcon.calculateOffset;
+ }
+ this.events = new OpenLayers.Events(this, this.icon.imageDiv);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy the marker. You must first remove the marker from any
+ * layer which it has been added to, or you will get buggy behavior.
+ * (This can not be done within the marker since the marker does not
+ * know which layer it is attached to.)
+ */
+ destroy: function() {
+ // erase any drawn features
+ this.erase();
+
+ this.map = null;
+
+ this.events.destroy();
+ this.events = null;
+
+ if (this.icon != null) {
+ this.icon.destroy();
+ this.icon = null;
+ }
+ },
+
+ /**
+ * Method: draw
+ * Calls draw on the icon, and returns that output.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {DOMElement} A new DOM Image with this marker's icon set at the
+ * location passed-in
+ */
+ draw: function(px) {
+ return this.icon.draw(px);
+ },
+
+ /**
+ * Method: erase
+ * Erases any drawn elements for this marker.
+ */
+ erase: function() {
+ if (this.icon != null) {
+ this.icon.erase();
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * Move the marker to the new location.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
+ * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.icon != null)) {
+ this.icon.moveTo(px);
+ }
+ this.lonlat = this.map.getLonLatFromLayerPx(px);
+ },
+
+ /**
+ * APIMethod: isDrawn
+ *
+ * Returns:
+ * {Boolean} Whether or not the marker is drawn.
+ */
+ isDrawn: function() {
+ var isDrawn = (this.icon && this.icon.isDrawn());
+ return isDrawn;
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Returns:
+ * {Boolean} Whether or not the marker is currently visible on screen.
+ */
+ onScreen:function() {
+
+ var onScreen = false;
+ if (this.map) {
+ var screenBounds = this.map.getExtent();
+ onScreen = screenBounds.containsLonLat(this.lonlat);
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: inflate
+ * Englarges the markers icon by the specified ratio.
+ *
+ * Parameters:
+ * inflate - {float} the ratio to enlarge the marker by (passing 2
+ * will double the size).
+ */
+ inflate: function(inflate) {
+ if (this.icon) {
+ this.icon.setSize({
+ w: this.icon.size.w * inflate,
+ h: this.icon.size.h * inflate
+ });
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Change the opacity of the marker by changin the opacity of
+ * its icon
+ *
+ * Parameters:
+ * opacity - {float} Specified as fraction (0.4, etc)
+ */
+ setOpacity: function(opacity) {
+ this.icon.setOpacity(opacity);
+ },
+
+ /**
+ * Method: setUrl
+ * Change URL of the Icon Image.
+ *
+ * url - {String}
+ */
+ setUrl: function(url) {
+ this.icon.setUrl(url);
+ },
+
+ /**
+ * Method: display
+ * Hide or show the icon
+ *
+ * display - {Boolean}
+ */
+ display: function(display) {
+ this.icon.display(display);
+ },
+
+ CLASS_NAME: "OpenLayers.Marker"
+});
+
+
+/**
+ * Function: defaultIcon
+ * Creates a default <OpenLayers.Icon>.
+ *
+ * Returns:
+ * {<OpenLayers.Icon>} A default OpenLayers.Icon to use for a marker
+ */
+OpenLayers.Marker.defaultIcon = function() {
+ return new OpenLayers.Icon(OpenLayers.Util.getImageLocation("marker.png"),
+ {w: 21, h: 25}, {x: -10.5, y: -25});
+};
+
+
+/* ======================================================================
+ OpenLayers/Layer/TileCache.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TileCache
+ * A read only TileCache layer. Used to requests tiles cached by TileCache in
+ * a web accessible cache. This means that you have to pre-populate your
+ * cache before this layer can be used. It is meant only to read tiles
+ * created by TileCache, and not to make calls to TileCache for tile
+ * creation. Create a new instance with the
+ * <OpenLayers.Layer.TileCache> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Treat this layer as a base layer. Default is true.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: format
+ * {String} Mime type of the images returned. Default is image/png.
+ */
+ format: 'image/png',
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer. (b) The map can work with resolutions
+ * that aren't supported by the server, i.e. that aren't in
+ * <serverResolutions>. When the map is displayed in such a resolution
+ * data for the closest server-supported resolution is loaded and the
+ * layer div is stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.TileCache
+ * Create a new read only TileCache layer.
+ *
+ * Parameters:
+ * name - {String} Name of the layer displayed in the interface
+ * url - {String} Location of the web accessible cache (not the location of
+ * your tilecache script!)
+ * layername - {String} Layer name as defined in the TileCache
+ * configuration
+ * options - {Object} Optional object with properties to be set on the
+ * layer. Note that you should speficy your resolutions to match
+ * your TileCache configuration. This can be done by setting
+ * the resolutions array directly (here or on the map), by setting
+ * maxResolution and numZoomLevels, or by using scale based properties.
+ */
+ initialize: function(name, url, layername, options) {
+ this.layername = layername;
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this,
+ [name, url, {}, options]);
+ this.extension = this.format.split('/')[1].toLowerCase();
+ this.extension = (this.extension == 'jpg') ? 'jpeg' : this.extension;
+ },
+
+ /**
+ * APIMethod: clone
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.TileCache>} An exact clone of this
+ * <OpenLayers.Layer.TileCache>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.TileCache(this.name,
+ this.url,
+ this.layername,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as parameters.
+ */
+ getURL: function(bounds) {
+ var res = this.getServerResolution();
+ var bbox = this.maxExtent;
+ var size = this.tileSize;
+ var tileX = Math.round((bounds.left - bbox.left) / (res * size.w));
+ var tileY = Math.round((bounds.bottom - bbox.bottom) / (res * size.h));
+ var tileZ = this.serverResolutions != null ?
+ OpenLayers.Util.indexOf(this.serverResolutions, res) :
+ this.map.getZoom();
+
+ var components = [
+ this.layername,
+ OpenLayers.Number.zeroPad(tileZ, 2),
+ OpenLayers.Number.zeroPad(parseInt(tileX / 1000000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileX / 1000) % 1000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileX) % 1000), 3),
+ OpenLayers.Number.zeroPad(parseInt(tileY / 1000000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileY / 1000) % 1000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileY) % 1000), 3) + '.' + this.extension
+ ];
+ var path = components.join('/');
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(path, url);
+ }
+ url = (url.charAt(url.length - 1) == '/') ? url : url + '/';
+ return url + path;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.TileCache"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Paging.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Paging
+ * Strategy for vector feature paging
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Paging = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature.Vector>)} Cached features.
+ */
+ features: null,
+
+ /**
+ * Property: length
+ * {Integer} Number of features per page. Default is 10.
+ */
+ length: 10,
+
+ /**
+ * Property: num
+ * {Integer} The currently displayed page number.
+ */
+ num: null,
+
+ /**
+ * Property: paging
+ * {Boolean} The strategy is currently changing pages.
+ */
+ paging: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Paging
+ * Create a new paging strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "beforefeaturesadded": this.cacheFeatures,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.clearCache();
+ this.layer.events.un({
+ "beforefeaturesadded": this.cacheFeatures,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: cacheFeatures
+ * Cache features before they are added to the layer.
+ *
+ * Parameters:
+ * event - {Object} The event that this was listening for. This will come
+ * with a batch of features to be paged.
+ */
+ cacheFeatures: function(event) {
+ if(!this.paging) {
+ this.clearCache();
+ this.features = event.features;
+ this.pageNext(event);
+ }
+ },
+
+ /**
+ * Method: clearCache
+ * Clear out the cached features. This destroys features, assuming
+ * nothing else has a reference.
+ */
+ clearCache: function() {
+ if(this.features) {
+ for(var i=0; i<this.features.length; ++i) {
+ this.features[i].destroy();
+ }
+ }
+ this.features = null;
+ this.num = null;
+ },
+
+ /**
+ * APIMethod: pageCount
+ * Get the total count of pages given the current cache of features.
+ *
+ * Returns:
+ * {Integer} The page count.
+ */
+ pageCount: function() {
+ var numFeatures = this.features ? this.features.length : 0;
+ return Math.ceil(numFeatures / this.length);
+ },
+
+ /**
+ * APIMethod: pageNum
+ * Get the zero based page number.
+ *
+ * Returns:
+ * {Integer} The current page number being displayed.
+ */
+ pageNum: function() {
+ return this.num;
+ },
+
+ /**
+ * APIMethod: pageLength
+ * Gets or sets page length.
+ *
+ * Parameters:
+ * newLength - {Integer} Optional length to be set.
+ *
+ * Returns:
+ * {Integer} The length of a page (number of features per page).
+ */
+ pageLength: function(newLength) {
+ if(newLength && newLength > 0) {
+ this.length = newLength;
+ }
+ return this.length;
+ },
+
+ /**
+ * APIMethod: pageNext
+ * Display the next page of features.
+ *
+ * Returns:
+ * {Boolean} A new page was displayed.
+ */
+ pageNext: function(event) {
+ var changed = false;
+ if(this.features) {
+ if(this.num === null) {
+ this.num = -1;
+ }
+ var start = (this.num + 1) * this.length;
+ changed = this.page(start, event);
+ }
+ return changed;
+ },
+
+ /**
+ * APIMethod: pagePrevious
+ * Display the previous page of features.
+ *
+ * Returns:
+ * {Boolean} A new page was displayed.
+ */
+ pagePrevious: function() {
+ var changed = false;
+ if(this.features) {
+ if(this.num === null) {
+ this.num = this.pageCount();
+ }
+ var start = (this.num - 1) * this.length;
+ changed = this.page(start);
+ }
+ return changed;
+ },
+
+ /**
+ * Method: page
+ * Display the page starting at the given index from the cache.
+ *
+ * Returns:
+ * {Boolean} A new page was displayed.
+ */
+ page: function(start, event) {
+ var changed = false;
+ if(this.features) {
+ if(start >= 0 && start < this.features.length) {
+ var num = Math.floor(start / this.length);
+ if(num != this.num) {
+ this.paging = true;
+ var features = this.features.slice(start, start + this.length);
+ this.layer.removeFeatures(this.layer.features);
+ this.num = num;
+ // modify the event if any
+ if(event && event.features) {
+ // this.was called by an event listener
+ event.features = features;
+ } else {
+ // this was called directly on the strategy
+ this.layer.addFeatures(features);
+ }
+ this.paging = false;
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Paging"
+});
+/* ======================================================================
+ OpenLayers/Control/DragFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragFeature
+ * The DragFeature control moves a feature with a drag of the mouse. Create a
+ * new control with the <OpenLayers.Control.DragFeature> constructor.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict dragging to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * APIProperty: onStart
+ * {Function} Define this function if you want to know when a drag starts.
+ * The function should expect to receive two arguments: the feature
+ * that is about to be dragged and the pixel location of the mouse.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that is about to be
+ * dragged.
+ * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+ */
+ onStart: function(feature, pixel) {},
+
+ /**
+ * APIProperty: onDrag
+ * {Function} Define this function if you want to know about each move of a
+ * feature. The function should expect to receive two arguments: the
+ * feature that is being dragged and the pixel location of the mouse.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+ * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+ */
+ onDrag: function(feature, pixel) {},
+
+ /**
+ * APIProperty: onComplete
+ * {Function} Define this function if you want to know when a feature is
+ * done dragging. The function should expect to receive two arguments:
+ * the feature that is being dragged and the pixel location of the
+ * mouse.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+ * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+ */
+ onComplete: function(feature, pixel) {},
+
+ /**
+ * APIProperty: onEnter
+ * {Function} Define this function if you want to know when the mouse
+ * goes over a feature and thereby makes this feature a candidate
+ * for dragging.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that is ready
+ * to be dragged.
+ */
+ onEnter: function(feature) {},
+
+ /**
+ * APIProperty: onLeave
+ * {Function} Define this function if you want to know when the mouse
+ * goes out of the feature that was dragged.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+ */
+ onLeave: function(feature) {},
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>}
+ */
+ feature: null,
+
+ /**
+ * Property: dragCallbacks
+ * {Object} The functions that are sent to the drag handler for callback.
+ */
+ dragCallbacks: {},
+
+ /**
+ * Property: featureCallbacks
+ * {Object} The functions that are sent to the feature handler for callback.
+ */
+ featureCallbacks: {},
+
+ /**
+ * Property: lastPixel
+ * {<OpenLayers.Pixel>}
+ */
+ lastPixel: null,
+
+ /**
+ * Constructor: OpenLayers.Control.DragFeature
+ * Create a new control to drag features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The layer containing features to be
+ * dragged.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.layer = layer;
+ this.handlers = {
+ drag: new OpenLayers.Handler.Drag(
+ this, OpenLayers.Util.extend({
+ down: this.downFeature,
+ move: this.moveFeature,
+ up: this.upFeature,
+ out: this.cancel,
+ done: this.doneDragging
+ }, this.dragCallbacks), {
+ documentDrag: this.documentDrag
+ }
+ ),
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, OpenLayers.Util.extend({
+ // 'click' and 'clickout' callback are for the mobile
+ // support: no 'over' or 'out' in touch based browsers.
+ click: this.clickFeature,
+ clickout: this.clickoutFeature,
+ over: this.overFeature,
+ out: this.outFeature
+ }, this.featureCallbacks),
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+ },
+
+ /**
+ * Method: clickFeature
+ * Called when the feature handler detects a click-in on a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if (this.handlers.feature.touch && !this.over && this.overFeature(feature)) {
+ this.handlers.drag.dragstart(this.handlers.feature.evt);
+ // to let the events propagate to the feature handler (click callback)
+ this.handlers.drag.stopDown = false;
+ }
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called when the feature handler detects a click-out on a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickoutFeature: function(feature) {
+ if (this.handlers.feature.touch && this.over) {
+ this.outFeature(feature);
+ this.handlers.drag.stopDown = true;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass
+ */
+ destroy: function() {
+ this.layer = null;
+ OpenLayers.Control.prototype.destroy.apply(this, []);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control and the feature handler.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the control and feature handler.
+ */
+ activate: function() {
+ return (this.handlers.feature.activate() &&
+ OpenLayers.Control.prototype.activate.apply(this, arguments));
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control and all handlers.
+ *
+ * Returns:
+ * {Boolean} Successfully deactivated the control.
+ */
+ deactivate: function() {
+ // the return from the handlers is unimportant in this case
+ this.handlers.drag.deactivate();
+ this.handlers.feature.deactivate();
+ this.feature = null;
+ this.dragging = false;
+ this.lastPixel = null;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, this.displayClass + "Over"
+ );
+ return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
+ },
+
+ /**
+ * Method: overFeature
+ * Called when the feature handler detects a mouse-over on a feature.
+ * This activates the drag handler.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The selected feature.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the drag handler.
+ */
+ overFeature: function(feature) {
+ var activated = false;
+ if(!this.handlers.drag.dragging) {
+ this.feature = feature;
+ this.handlers.drag.activate();
+ activated = true;
+ this.over = true;
+ OpenLayers.Element.addClass(this.map.viewPortDiv, this.displayClass + "Over");
+ this.onEnter(feature);
+ } else {
+ if(this.feature.id == feature.id) {
+ this.over = true;
+ } else {
+ this.over = false;
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: downFeature
+ * Called when the drag handler detects a mouse-down.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+ */
+ downFeature: function(pixel) {
+ this.lastPixel = pixel;
+ this.onStart(this.feature, pixel);
+ },
+
+ /**
+ * Method: moveFeature
+ * Called when the drag handler detects a mouse-move. Also calls the
+ * optional onDrag method.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+ */
+ moveFeature: function(pixel) {
+ var res = this.map.getResolution();
+ this.feature.geometry.move(res * (pixel.x - this.lastPixel.x),
+ res * (this.lastPixel.y - pixel.y));
+ this.layer.drawFeature(this.feature);
+ this.lastPixel = pixel;
+ this.onDrag(this.feature, pixel);
+ },
+
+ /**
+ * Method: upFeature
+ * Called when the drag handler detects a mouse-up.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+ */
+ upFeature: function(pixel) {
+ if(!this.over) {
+ this.handlers.drag.deactivate();
+ }
+ },
+
+ /**
+ * Method: doneDragging
+ * Called when the drag handler is done dragging.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The last event pixel location. If this event
+ * came from a mouseout, this may not be in the map viewport.
+ */
+ doneDragging: function(pixel) {
+ this.onComplete(this.feature, pixel);
+ },
+
+ /**
+ * Method: outFeature
+ * Called when the feature handler detects a mouse-out on a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that the mouse left.
+ */
+ outFeature: function(feature) {
+ if(!this.handlers.drag.dragging) {
+ this.over = false;
+ this.handlers.drag.deactivate();
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, this.displayClass + "Over"
+ );
+ this.onLeave(feature);
+ this.feature = null;
+ } else {
+ if(this.feature.id == feature.id) {
+ this.over = false;
+ }
+ }
+ },
+
+ /**
+ * Method: cancel
+ * Called when the drag handler detects a mouse-out (from the map viewport).
+ */
+ cancel: function() {
+ this.handlers.drag.deactivate();
+ this.over = false;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and all handlers.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The control's map.
+ */
+ setMap: function(map) {
+ this.handlers.drag.setMap(map);
+ this.handlers.feature.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragFeature"
+});
+/* ======================================================================
+ OpenLayers/Control/TransformFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/DragFeature.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Control.TransformFeature
+ * Control to transform features with a standard transformation box.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.TransformFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforesetfeature - Triggered before a feature is set for
+ * tranformation. The feature will not be set if a listener returns
+ * false. Listeners receive a *feature* property, with the feature
+ * that will be set for transformation. Listeners are allowed to
+ * set the control's *scale*, *ratio* and *rotation* properties,
+ * which will set the initial scale, ratio and rotation of the
+ * feature, like the <setFeature> method's initialParams argument.
+ * setfeature - Triggered when a feature is set for tranformation.
+ * Listeners receive a *feature* property, with the feature that
+ * is now set for transformation.
+ * beforetransform - Triggered while dragging, before a feature is
+ * transformed. The feature will not be transformed if a listener
+ * returns false (but the box still will). Listeners receive one or
+ * more of *center*, *scale*, *ratio* and *rotation*. The *center*
+ * property is an <OpenLayers.Geometry.Point> object with the new
+ * center of the transformed feature, the others are Floats with the
+ * scale, ratio or rotation change since the last transformation.
+ * transform - Triggered while dragging, when a feature is transformed.
+ * Listeners receive an event object with one or more of *center*,
+ * scale*, *ratio* and *rotation*. The *center* property is an
+ * <OpenLayers.Geometry.Point> object with the new center of the
+ * transformed feature, the others are Floats with the scale, ratio
+ * or rotation change of the feature since the last transformation.
+ * transformcomplete - Triggered after dragging. Listeners receive
+ * an event object with the transformed *feature*.
+ */
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict transformation to a limited set of geometry
+ * types, send a list of strings corresponding to the geometry class
+ * names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * APIProperty: preserveAspectRatio
+ * {Boolean} set to true to not change the feature's aspect ratio.
+ */
+ preserveAspectRatio: false,
+
+ /**
+ * APIProperty: rotate
+ * {Boolean} set to false if rotation should be disabled. Default is true.
+ * To be passed with the constructor or set when the control is not
+ * active.
+ */
+ rotate: true,
+
+ /**
+ * APIProperty: feature
+ * {<OpenLayers.Feature.Vector>} Feature currently available for
+ * transformation. Read-only, use <setFeature> to set it manually.
+ */
+ feature: null,
+
+ /**
+ * APIProperty: renderIntent
+ * {String|Object} Render intent for the transformation box and
+ * handles. A symbolizer object can also be provided here.
+ */
+ renderIntent: "temporary",
+
+ /**
+ * APIProperty: rotationHandleSymbolizer
+ * {Object|String} Optional. A custom symbolizer for the rotation handles.
+ * A render intent can also be provided here. Defaults to
+ * (code)
+ * {
+ * stroke: false,
+ * pointRadius: 10,
+ * fillOpacity: 0,
+ * cursor: "pointer"
+ * }
+ * (end)
+ */
+ rotationHandleSymbolizer: null,
+
+ /**
+ * APIProperty: box
+ * {<OpenLayers.Feature.Vector>} The transformation box rectangle.
+ * Read-only.
+ */
+ box: null,
+
+ /**
+ * APIProperty: center
+ * {<OpenLayers.Geometry.Point>} The center of the feature bounds.
+ * Read-only.
+ */
+ center: null,
+
+ /**
+ * APIProperty: scale
+ * {Float} The scale of the feature, relative to the scale the time the
+ * feature was set. Read-only, except for *beforesetfeature*
+ * listeners.
+ */
+ scale: 1,
+
+ /**
+ * APIProperty: ratio
+ * {Float} The ratio of the feature relative to the ratio the time the
+ * feature was set. Read-only, except for *beforesetfeature*
+ * listeners.
+ */
+ ratio: 1,
+
+ /**
+ * Property: rotation
+ * {Integer} the current rotation angle of the box. Read-only, except for
+ * *beforesetfeature* listeners.
+ */
+ rotation: 0,
+
+ /**
+ * APIProperty: handles
+ * {Array(<OpenLayers.Feature.Vector>)} The 8 handles currently available
+ * for scaling/resizing. Numbered counterclockwise, starting from the
+ * southwest corner. Read-only.
+ */
+ handles: null,
+
+ /**
+ * APIProperty: rotationHandles
+ * {Array(<OpenLayers.Feature.Vector>)} The 4 rotation handles currently
+ * available for rotating. Numbered counterclockwise, starting from
+ * the southwest corner. Read-only.
+ */
+ rotationHandles: null,
+
+ /**
+ * Property: dragControl
+ * {<OpenLayers.Control.DragFeature>}
+ */
+ dragControl: null,
+
+ /**
+ * APIProperty: irregular
+ * {Boolean} Make scaling/resizing work irregularly. If true then
+ * dragging a handle causes the feature to resize in the direction
+ * of movement. If false then the feature resizes symetrically
+ * about it's center.
+ */
+ irregular: false,
+
+ /**
+ * Constructor: OpenLayers.Control.TransformFeature
+ * Create a new transform feature control.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+ * will be transformed.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.layer = layer;
+
+ if(!this.rotationHandleSymbolizer) {
+ this.rotationHandleSymbolizer = {
+ stroke: false,
+ pointRadius: 10,
+ fillOpacity: 0,
+ cursor: "pointer"
+ };
+ }
+
+ this.createBox();
+ this.createControl();
+ },
+
+ /**
+ * APIMethod: activate
+ * Activates the control.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ this.dragControl.activate();
+ this.layer.addFeatures([this.box]);
+ this.rotate && this.layer.addFeatures(this.rotationHandles);
+ this.layer.addFeatures(this.handles);
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates the control.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.layer.removeFeatures(this.handles);
+ this.rotate && this.layer.removeFeatures(this.rotationHandles);
+ this.layer.removeFeatures([this.box]);
+ this.dragControl.deactivate();
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.dragControl.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setFeature
+ * Place the transformation box on a feature and start transforming it.
+ * If the control is not active, it will be activated.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * initialParams - {Object} Initial values for rotation, scale or ratio.
+ * Setting a rotation value here will cause the transformation box to
+ * start rotated. Setting a scale or ratio will not affect the
+ * transormation box, but applications may use this to keep track of
+ * scale and ratio of a feature across multiple transforms.
+ */
+ setFeature: function(feature, initialParams) {
+ initialParams = OpenLayers.Util.applyDefaults(initialParams, {
+ rotation: 0,
+ scale: 1,
+ ratio: 1
+ });
+
+ var oldRotation = this.rotation;
+ var oldCenter = this.center;
+ OpenLayers.Util.extend(this, initialParams);
+
+ var cont = this.events.triggerEvent("beforesetfeature",
+ {feature: feature}
+ );
+ if (cont === false) {
+ return;
+ }
+
+ this.feature = feature;
+ this.activate();
+
+ this._setfeature = true;
+
+ var featureBounds = this.feature.geometry.getBounds();
+ this.box.move(featureBounds.getCenterLonLat());
+ this.box.geometry.rotate(-oldRotation, oldCenter);
+ this._angle = 0;
+
+ var ll;
+ if(this.rotation) {
+ var geom = feature.geometry.clone();
+ geom.rotate(-this.rotation, this.center);
+ var box = new OpenLayers.Feature.Vector(
+ geom.getBounds().toGeometry());
+ box.geometry.rotate(this.rotation, this.center);
+ this.box.geometry.rotate(this.rotation, this.center);
+ this.box.move(box.geometry.getBounds().getCenterLonLat());
+ var llGeom = box.geometry.components[0].components[0];
+ ll = llGeom.getBounds().getCenterLonLat();
+ } else {
+ ll = new OpenLayers.LonLat(featureBounds.left, featureBounds.bottom);
+ }
+ this.handles[0].move(ll);
+
+ delete this._setfeature;
+
+ this.events.triggerEvent("setfeature", {feature: feature});
+ },
+
+ /**
+ * APIMethod: unsetFeature
+ * Remove the transformation box off any feature.
+ * If the control is active, it will be deactivated first.
+ */
+ unsetFeature: function() {
+ if (this.active) {
+ this.deactivate();
+ } else {
+ this.feature = null;
+ this.rotation = 0;
+ this.scale = 1;
+ this.ratio = 1;
+ }
+ },
+
+ /**
+ * Method: createBox
+ * Creates the box with all handles and transformation handles.
+ */
+ createBox: function() {
+ var control = this;
+
+ this.center = new OpenLayers.Geometry.Point(0, 0);
+ this.box = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-1, -1),
+ new OpenLayers.Geometry.Point(0, -1),
+ new OpenLayers.Geometry.Point(1, -1),
+ new OpenLayers.Geometry.Point(1, 0),
+ new OpenLayers.Geometry.Point(1, 1),
+ new OpenLayers.Geometry.Point(0, 1),
+ new OpenLayers.Geometry.Point(-1, 1),
+ new OpenLayers.Geometry.Point(-1, 0),
+ new OpenLayers.Geometry.Point(-1, -1)
+ ]), null,
+ typeof this.renderIntent == "string" ? null : this.renderIntent
+ );
+
+ // Override for box move - make sure that the center gets updated
+ this.box.geometry.move = function(x, y) {
+ control._moving = true;
+ OpenLayers.Geometry.LineString.prototype.move.apply(this, arguments);
+ control.center.move(x, y);
+ delete control._moving;
+ };
+
+ // Overrides for vertex move, resize and rotate - make sure that
+ // handle and rotationHandle geometries are also moved, resized and
+ // rotated.
+ var vertexMoveFn = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.apply(this, arguments);
+ this._rotationHandle && this._rotationHandle.geometry.move(x, y);
+ this._handle.geometry.move(x, y);
+ };
+ var vertexResizeFn = function(scale, center, ratio) {
+ OpenLayers.Geometry.Point.prototype.resize.apply(this, arguments);
+ this._rotationHandle && this._rotationHandle.geometry.resize(
+ scale, center, ratio);
+ this._handle.geometry.resize(scale, center, ratio);
+ };
+ var vertexRotateFn = function(angle, center) {
+ OpenLayers.Geometry.Point.prototype.rotate.apply(this, arguments);
+ this._rotationHandle && this._rotationHandle.geometry.rotate(
+ angle, center);
+ this._handle.geometry.rotate(angle, center);
+ };
+
+ // Override for handle move - make sure that the box and other handles
+ // are updated, and finally transform the feature.
+ var handleMoveFn = function(x, y) {
+ var oldX = this.x, oldY = this.y;
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ if(control._moving) {
+ return;
+ }
+ var evt = control.dragControl.handlers.drag.evt;
+ var preserveAspectRatio = !control._setfeature &&
+ control.preserveAspectRatio;
+ var reshape = !preserveAspectRatio && !(evt && evt.shiftKey);
+ var oldGeom = new OpenLayers.Geometry.Point(oldX, oldY);
+ var centerGeometry = control.center;
+ this.rotate(-control.rotation, centerGeometry);
+ oldGeom.rotate(-control.rotation, centerGeometry);
+ var dx1 = this.x - centerGeometry.x;
+ var dy1 = this.y - centerGeometry.y;
+ var dx0 = dx1 - (this.x - oldGeom.x);
+ var dy0 = dy1 - (this.y - oldGeom.y);
+ if (control.irregular && !control._setfeature) {
+ dx1 -= (this.x - oldGeom.x) / 2;
+ dy1 -= (this.y - oldGeom.y) / 2;
+ }
+ this.x = oldX;
+ this.y = oldY;
+ var scale, ratio = 1;
+ if (reshape) {
+ scale = Math.abs(dy0) < 0.00001 ? 1 : dy1 / dy0;
+ ratio = (Math.abs(dx0) < 0.00001 ? 1 : (dx1 / dx0)) / scale;
+ } else {
+ var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+ var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+ scale = l1 / l0;
+ }
+
+ // rotate the box to 0 before resizing - saves us some
+ // calculations and is inexpensive because we don't drawFeature.
+ control._moving = true;
+ control.box.geometry.rotate(-control.rotation, centerGeometry);
+ delete control._moving;
+
+ control.box.geometry.resize(scale, centerGeometry, ratio);
+ control.box.geometry.rotate(control.rotation, centerGeometry);
+ control.transformFeature({scale: scale, ratio: ratio});
+ if (control.irregular && !control._setfeature) {
+ var newCenter = centerGeometry.clone();
+ newCenter.x += Math.abs(oldX - centerGeometry.x) < 0.00001 ? 0 : (this.x - oldX);
+ newCenter.y += Math.abs(oldY - centerGeometry.y) < 0.00001 ? 0 : (this.y - oldY);
+ control.box.geometry.move(this.x - oldX, this.y - oldY);
+ control.transformFeature({center: newCenter});
+ }
+ };
+
+ // Override for rotation handle move - make sure that the box and
+ // other handles are updated, and finally transform the feature.
+ var rotationHandleMoveFn = function(x, y){
+ var oldX = this.x, oldY = this.y;
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ if(control._moving) {
+ return;
+ }
+ var evt = control.dragControl.handlers.drag.evt;
+ var constrain = (evt && evt.shiftKey) ? 45 : 1;
+ var centerGeometry = control.center;
+ var dx1 = this.x - centerGeometry.x;
+ var dy1 = this.y - centerGeometry.y;
+ var dx0 = dx1 - x;
+ var dy0 = dy1 - y;
+ this.x = oldX;
+ this.y = oldY;
+ var a0 = Math.atan2(dy0, dx0);
+ var a1 = Math.atan2(dy1, dx1);
+ var angle = a1 - a0;
+ angle *= 180 / Math.PI;
+ control._angle = (control._angle + angle) % 360;
+ var diff = control.rotation % constrain;
+ if(Math.abs(control._angle) >= constrain || diff !== 0) {
+ angle = Math.round(control._angle / constrain) * constrain -
+ diff;
+ control._angle = 0;
+ control.box.geometry.rotate(angle, centerGeometry);
+ control.transformFeature({rotation: angle});
+ }
+ };
+
+ var handles = new Array(8);
+ var rotationHandles = new Array(4);
+ var geom, handle, rotationHandle;
+ var positions = ["sw", "s", "se", "e", "ne", "n", "nw", "w"];
+ for(var i=0; i<8; ++i) {
+ geom = this.box.geometry.components[i];
+ handle = new OpenLayers.Feature.Vector(geom.clone(), {
+ role: positions[i] + "-resize"
+ }, typeof this.renderIntent == "string" ? null :
+ this.renderIntent);
+ if(i % 2 == 0) {
+ rotationHandle = new OpenLayers.Feature.Vector(geom.clone(), {
+ role: positions[i] + "-rotate"
+ }, typeof this.rotationHandleSymbolizer == "string" ?
+ null : this.rotationHandleSymbolizer);
+ rotationHandle.geometry.move = rotationHandleMoveFn;
+ geom._rotationHandle = rotationHandle;
+ rotationHandles[i/2] = rotationHandle;
+ }
+ geom.move = vertexMoveFn;
+ geom.resize = vertexResizeFn;
+ geom.rotate = vertexRotateFn;
+ handle.geometry.move = handleMoveFn;
+ geom._handle = handle;
+ handles[i] = handle;
+ }
+
+ this.rotationHandles = rotationHandles;
+ this.handles = handles;
+ },
+
+ /**
+ * Method: createControl
+ * Creates a DragFeature control for this control.
+ */
+ createControl: function() {
+ var control = this;
+ this.dragControl = new OpenLayers.Control.DragFeature(this.layer, {
+ documentDrag: true,
+ // avoid moving the feature itself - move the box instead
+ moveFeature: function(pixel) {
+ if(this.feature === control.feature) {
+ this.feature = control.box;
+ }
+ OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this,
+ arguments);
+ },
+ // transform while dragging
+ onDrag: function(feature, pixel) {
+ if(feature === control.box) {
+ control.transformFeature({center: control.center});
+ }
+ },
+ // set a new feature
+ onStart: function(feature, pixel) {
+ var eligible = !control.geometryTypes ||
+ OpenLayers.Util.indexOf(control.geometryTypes,
+ feature.geometry.CLASS_NAME) !== -1;
+ var i = OpenLayers.Util.indexOf(control.handles, feature);
+ i += OpenLayers.Util.indexOf(control.rotationHandles,
+ feature);
+ if(feature !== control.feature && feature !== control.box &&
+ i == -2 && eligible) {
+ control.setFeature(feature);
+ }
+ },
+ onComplete: function(feature, pixel) {
+ control.events.triggerEvent("transformcomplete",
+ {feature: control.feature});
+ }
+ });
+ },
+
+ /**
+ * Method: drawHandles
+ * Draws the handles to match the box.
+ */
+ drawHandles: function() {
+ var layer = this.layer;
+ for(var i=0; i<8; ++i) {
+ if(this.rotate && i % 2 === 0) {
+ layer.drawFeature(this.rotationHandles[i/2],
+ this.rotationHandleSymbolizer);
+ }
+ layer.drawFeature(this.handles[i], this.renderIntent);
+ }
+ },
+
+ /**
+ * Method: transformFeature
+ * Transforms the feature.
+ *
+ * Parameters:
+ * mods - {Object} An object with optional scale, ratio, rotation and
+ * center properties.
+ */
+ transformFeature: function(mods) {
+ if(!this._setfeature) {
+ this.scale *= (mods.scale || 1);
+ this.ratio *= (mods.ratio || 1);
+ var oldRotation = this.rotation;
+ this.rotation = (this.rotation + (mods.rotation || 0)) % 360;
+
+ if(this.events.triggerEvent("beforetransform", mods) !== false) {
+ var feature = this.feature;
+ var geom = feature.geometry;
+ var center = this.center;
+ geom.rotate(-oldRotation, center);
+ if(mods.scale || mods.ratio) {
+ geom.resize(mods.scale, center, mods.ratio);
+ } else if(mods.center) {
+ feature.move(mods.center.getBounds().getCenterLonLat());
+ }
+ geom.rotate(this.rotation, center);
+ this.layer.drawFeature(feature);
+ feature.toState(OpenLayers.State.UPDATE);
+ this.events.triggerEvent("transform", mods);
+ }
+ }
+ this.layer.drawFeature(this.box, this.renderIntent);
+ this.drawHandles();
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ var geom;
+ for(var i=0; i<8; ++i) {
+ geom = this.box.geometry.components[i];
+ geom._handle.destroy();
+ geom._handle = null;
+ geom._rotationHandle && geom._rotationHandle.destroy();
+ geom._rotationHandle = null;
+ }
+ this.center = null;
+ this.feature = null;
+ this.handles = null;
+ this.rotationHandleSymbolizer = null;
+ this.rotationHandles = null;
+ this.box.destroy();
+ this.box = null;
+ this.layer = null;
+ this.dragControl.destroy();
+ this.dragControl = null;
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.TransformFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/Box.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Box
+ * Handler for dragging a rectangle across the map. Box is displayed
+ * on mouse down, moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: dragHandler
+ * {<OpenLayers.Handler.Drag>}
+ */
+ dragHandler: null,
+
+ /**
+ * APIProperty: boxDivClassName
+ * {String} The CSS class to use for drawing the box. Default is
+ * olHandlerBoxZoomBox
+ */
+ boxDivClassName: 'olHandlerBoxZoomBox',
+
+ /**
+ * Property: boxOffsets
+ * {Object} Caches box offsets from css. This is used by the getBoxOffsets
+ * method.
+ */
+ boxOffsets: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Box
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object}
+ *
+ * Named callbacks:
+ * start - Called when the box drag operation starts.
+ * done - Called when the box drag operation is finished.
+ * The callback should expect to receive a single argument, the box
+ * bounds or a pixel. If the box dragging didn't span more than a 5
+ * pixel distance, a pixel will be returned instead of a bounds object.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ this.dragHandler = new OpenLayers.Handler.Drag(
+ this,
+ {
+ down: this.startBox,
+ move: this.moveBox,
+ out: this.removeBox,
+ up: this.endBox
+ },
+ {keyMask: this.keyMask}
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ if (this.dragHandler) {
+ this.dragHandler.destroy();
+ this.dragHandler = null;
+ }
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ OpenLayers.Handler.prototype.setMap.apply(this, arguments);
+ if (this.dragHandler) {
+ this.dragHandler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: startBox
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>}
+ */
+ startBox: function (xy) {
+ this.callback("start", []);
+ this.zoomBox = OpenLayers.Util.createDiv('zoomBox', {
+ x: -9999, y: -9999
+ });
+ this.zoomBox.className = this.boxDivClassName;
+ this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+
+ this.map.viewPortDiv.appendChild(this.zoomBox);
+
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDrawBox"
+ );
+ },
+
+ /**
+ * Method: moveBox
+ */
+ moveBox: function (xy) {
+ var startX = this.dragHandler.start.x;
+ var startY = this.dragHandler.start.y;
+ var deltaX = Math.abs(startX - xy.x);
+ var deltaY = Math.abs(startY - xy.y);
+
+ var offset = this.getBoxOffsets();
+ this.zoomBox.style.width = (deltaX + offset.width + 1) + "px";
+ this.zoomBox.style.height = (deltaY + offset.height + 1) + "px";
+ this.zoomBox.style.left = (xy.x < startX ?
+ startX - deltaX - offset.left : startX - offset.left) + "px";
+ this.zoomBox.style.top = (xy.y < startY ?
+ startY - deltaY - offset.top : startY - offset.top) + "px";
+ },
+
+ /**
+ * Method: endBox
+ */
+ endBox: function(end) {
+ var result;
+ if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||
+ Math.abs(this.dragHandler.start.y - end.y) > 5) {
+ var start = this.dragHandler.start;
+ var top = Math.min(start.y, end.y);
+ var bottom = Math.max(start.y, end.y);
+ var left = Math.min(start.x, end.x);
+ var right = Math.max(start.x, end.x);
+ result = new OpenLayers.Bounds(left, bottom, right, top);
+ } else {
+ result = this.dragHandler.start.clone(); // i.e. OL.Pixel
+ }
+ this.removeBox();
+
+ this.callback("done", [result]);
+ },
+
+ /**
+ * Method: removeBox
+ * Remove the zoombox from the screen and nullify our reference to it.
+ */
+ removeBox: function() {
+ this.map.viewPortDiv.removeChild(this.zoomBox);
+ this.zoomBox = null;
+ this.boxOffsets = null;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDrawBox"
+ );
+
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function () {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragHandler.activate();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function () {
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ if (this.dragHandler.deactivate()) {
+ if (this.zoomBox) {
+ this.removeBox();
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getBoxOffsets
+ * Determines border offsets for a box, according to the box model.
+ *
+ * Returns:
+ * {Object} an object with the following offsets:
+ * - left
+ * - right
+ * - top
+ * - bottom
+ * - width
+ * - height
+ */
+ getBoxOffsets: function() {
+ if (!this.boxOffsets) {
+ // Determine the box model. If the testDiv's clientWidth is 3, then
+ // the borders are outside and we are dealing with the w3c box
+ // model. Otherwise, the browser uses the traditional box model and
+ // the borders are inside the box bounds, leaving us with a
+ // clientWidth of 1.
+ var testDiv = document.createElement("div");
+ //testDiv.style.visibility = "hidden";
+ testDiv.style.position = "absolute";
+ testDiv.style.border = "1px solid black";
+ testDiv.style.width = "3px";
+ document.body.appendChild(testDiv);
+ var w3cBoxModel = testDiv.clientWidth == 3;
+ document.body.removeChild(testDiv);
+
+ var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+ "border-left-width"));
+ var right = parseInt(OpenLayers.Element.getStyle(
+ this.zoomBox, "border-right-width"));
+ var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+ "border-top-width"));
+ var bottom = parseInt(OpenLayers.Element.getStyle(
+ this.zoomBox, "border-bottom-width"));
+ this.boxOffsets = {
+ left: left,
+ right: right,
+ top: top,
+ bottom: bottom,
+ width: w3cBoxModel === false ? left + right : 0,
+ height: w3cBoxModel === false ? top + bottom : 0
+ };
+ }
+ return this.boxOffsets;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Box"
+});
+/* ======================================================================
+ OpenLayers/Control/ZoomBox.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Box.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomBox
+ * The ZoomBox control enables zooming directly to a given extent, by drawing
+ * a box on the map. The box is drawn by holding down shift, whilst dragging
+ * the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPE}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: out
+ * {Boolean} Should the control be used for zooming out?
+ */
+ out: false,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Zoom only occurs if the keyMask matches the combination of
+ * keys down. Use bitwise operators and one or more of the
+ * <OpenLayers.Handler> constants to construct a keyMask. Leave null if
+ * not used mask. Default is null.
+ */
+ keyMask: null,
+
+ /**
+ * APIProperty: alwaysZoom
+ * {Boolean} Always zoom in/out when box drawn, even if the zoom level does
+ * not change.
+ */
+ alwaysZoom: false,
+
+ /**
+ * APIProperty: zoomOnClick
+ * {Boolean} Should we zoom when no box was dragged, i.e. the user only
+ * clicked? Default is true.
+ */
+ zoomOnClick: true,
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ this.handler = new OpenLayers.Handler.Box( this,
+ {done: this.zoomBox}, {keyMask: this.keyMask} );
+ },
+
+ /**
+ * Method: zoomBox
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
+ */
+ zoomBox: function (position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var bounds,
+ targetCenterPx = position.getCenterPixel();
+ if (!this.out) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat,
+ maxXY.lon, maxXY.lat);
+ } else {
+ var pixWidth = position.right - position.left;
+ var pixHeight = position.bottom - position.top;
+ var zoomFactor = Math.min((this.map.size.h / pixHeight),
+ (this.map.size.w / pixWidth));
+ var extent = this.map.getExtent();
+ var center = this.map.getLonLatFromPixel(targetCenterPx);
+ var xmin = center.lon - (extent.getWidth()/2)*zoomFactor;
+ var xmax = center.lon + (extent.getWidth()/2)*zoomFactor;
+ var ymin = center.lat - (extent.getHeight()/2)*zoomFactor;
+ var ymax = center.lat + (extent.getHeight()/2)*zoomFactor;
+ bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax);
+ }
+ // always zoom in/out
+ var lastZoom = this.map.getZoom(),
+ size = this.map.getSize(),
+ centerPx = {x: size.w / 2, y: size.h / 2},
+ zoom = this.map.getZoomForExtent(bounds),
+ oldRes = this.map.getResolution(),
+ newRes = this.map.getResolutionForZoom(zoom);
+ if (oldRes == newRes) {
+ this.map.setCenter(this.map.getLonLatFromPixel(targetCenterPx));
+ } else {
+ var zoomOriginPx = {
+ x: (oldRes * targetCenterPx.x - newRes * centerPx.x) /
+ (oldRes - newRes),
+ y: (oldRes * targetCenterPx.y - newRes * centerPx.y) /
+ (oldRes - newRes)
+ };
+ this.map.zoomTo(zoom, zoomOriginPx);
+ }
+ if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){
+ this.map.zoomTo(lastZoom + (this.out ? -1 : 1));
+ }
+ } else if (this.zoomOnClick) { // it's a pixel
+ if (!this.out) {
+ this.map.zoomTo(this.map.getZoom() + 1, position);
+ } else {
+ this.map.zoomTo(this.map.getZoom() - 1, position);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomBox"
+});
+/* ======================================================================
+ OpenLayers/Control/DragPan.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragPan
+ * The DragPan control pans the map with a drag of the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: panned
+ * {Boolean} The map moved.
+ */
+ panned: false,
+
+ /**
+ * Property: interval
+ * {Integer} The number of milliseconds that should ellapse before
+ * panning the map again. Defaults to 0 milliseconds, which means that
+ * no separate cycle is used for panning. In most cases you won't want
+ * to change this value. For slow machines/devices larger values can be
+ * tried out.
+ */
+ interval: 0,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: kinetic
+ * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
+ */
+ kinetic: null,
+
+ /**
+ * APIProperty: enableKinetic
+ * {Boolean} Set this option to enable "kinetic dragging". Can be
+ * set to true or to an object. If set to an object this
+ * object will be passed to the {<OpenLayers.Kinetic>}
+ * constructor. Defaults to true.
+ * To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
+ * included in your build config.
+ */
+ enableKinetic: true,
+
+ /**
+ * APIProperty: kineticInterval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * scrolling". Applies only if enableKinetic is set. Defaults
+ * to 10 milliseconds.
+ */
+ kineticInterval: 10,
+
+
+ /**
+ * Method: draw
+ * Creates a Drag handler, using <panMap> and
+ * <panMapDone> as callbacks.
+ */
+ draw: function() {
+ if (this.enableKinetic && OpenLayers.Kinetic) {
+ var config = {interval: this.kineticInterval};
+ if(typeof this.enableKinetic === "object") {
+ config = OpenLayers.Util.extend(config, this.enableKinetic);
+ }
+ this.kinetic = new OpenLayers.Kinetic(config);
+ }
+ this.handler = new OpenLayers.Handler.Drag(this, {
+ "move": this.panMap,
+ "done": this.panMapDone,
+ "down": this.panMapStart
+ }, {
+ interval: this.interval,
+ documentDrag: this.documentDrag
+ }
+ );
+ },
+
+ /**
+ * Method: panMapStart
+ */
+ panMapStart: function() {
+ if(this.kinetic) {
+ this.kinetic.begin();
+ }
+ },
+
+ /**
+ * Method: panMap
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMap: function(xy) {
+ if(this.kinetic) {
+ this.kinetic.update(xy);
+ }
+ this.panned = true;
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: true, animate: false}
+ );
+ },
+
+ /**
+ * Method: panMapDone
+ * Finish the panning operation. Only call setCenter (through <panMap>)
+ * if the map has actually been moved.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMapDone: function(xy) {
+ if(this.panned) {
+ var res = null;
+ if (this.kinetic) {
+ res = this.kinetic.end(xy);
+ }
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: !!res, animate: false}
+ );
+ if (res) {
+ var self = this;
+ this.kinetic.move(res, function(x, y, end) {
+ self.map.pan(x, y, {dragging: !end, animate: false});
+ });
+ }
+ this.panned = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragPan"
+});
+/* ======================================================================
+ OpenLayers/Control/Navigation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/ZoomBox.js
+ * @requires OpenLayers/Control/DragPan.js
+ * @requires OpenLayers/Handler/MouseWheel.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Navigation
+ * The navigation control handles map browsing with mouse events (dragging,
+ * double-clicking, and scrolling the wheel). Create a new navigation
+ * control with the <OpenLayers.Control.Navigation> control.
+ *
+ * Note that this control is added to the map by default (if no controls
+ * array is sent in the options object to the <OpenLayers.Map>
+ * constructor).
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: zoomBox
+ * {<OpenLayers.Control.ZoomBox>}
+ */
+ zoomBox: null,
+
+ /**
+ * APIProperty: zoomBoxEnabled
+ * {Boolean} Whether the user can draw a box to zoom
+ */
+ zoomBoxEnabled: true,
+
+ /**
+ * APIProperty: zoomWheelEnabled
+ * {Boolean} Whether the mousewheel should zoom the map
+ */
+ zoomWheelEnabled: true,
+
+ /**
+ * Property: mouseWheelOptions
+ * {Object} Options passed to the MouseWheel control (only useful if
+ * <zoomWheelEnabled> is set to true). Default is no options for maps
+ * with fractionalZoom set to true, otherwise
+ * {cumulative: false, interval: 50, maxDelta: 6}
+ */
+ mouseWheelOptions: null,
+
+ /**
+ * APIProperty: handleRightClicks
+ * {Boolean} Whether or not to handle right clicks. Default is false.
+ */
+ handleRightClicks: false,
+
+ /**
+ * APIProperty: zoomBoxKeyMask
+ * {Integer} <OpenLayers.Handler> key code of the key, which has to be
+ * pressed, while drawing the zoom box with the mouse on the screen.
+ * You should probably set handleRightClicks to true if you use this
+ * with MOD_CTRL, to disable the context menu for machines which use
+ * CTRL-Click as a right click.
+ * Default: <OpenLayers.Handler.MOD_SHIFT>
+ */
+ zoomBoxKeyMask: OpenLayers.Handler.MOD_SHIFT,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.Navigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ this.deactivate();
+
+ if (this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+
+ if (this.zoomBox) {
+ this.zoomBox.destroy();
+ }
+ this.zoomBox = null;
+
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ }
+ this.pinchZoom = null;
+
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ this.dragPan.activate();
+ if (this.zoomWheelEnabled) {
+ this.handlers.wheel.activate();
+ }
+ this.handlers.click.activate();
+ if (this.zoomBoxEnabled) {
+ this.zoomBox.activate();
+ }
+ if (this.pinchZoom) {
+ this.pinchZoom.activate();
+ }
+ return OpenLayers.Control.prototype.activate.apply(this,arguments);
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if (this.pinchZoom) {
+ this.pinchZoom.deactivate();
+ }
+ this.zoomBox.deactivate();
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.handlers.wheel.deactivate();
+ return OpenLayers.Control.prototype.deactivate.apply(this,arguments);
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ // disable right mouse context menu for support of right click events
+ if (this.handleRightClicks) {
+ this.map.viewPortDiv.oncontextmenu = OpenLayers.Function.False;
+ }
+
+ var clickCallbacks = {
+ 'click': this.defaultClick,
+ 'dblclick': this.defaultDblClick,
+ 'dblrightclick': this.defaultDblRightClick
+ };
+ var clickOptions = {
+ 'double': true,
+ 'stopDouble': true
+ };
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.zoomBox = new OpenLayers.Control.ZoomBox(
+ {map: this.map, keyMask: this.zoomBoxKeyMask});
+ this.dragPan.draw();
+ this.zoomBox.draw();
+ var wheelOptions = this.map.fractionalZoom ? {} : {
+ cumulative: false,
+ interval: 50,
+ maxDelta: 6
+ };
+ this.handlers.wheel = new OpenLayers.Handler.MouseWheel(
+ this, {up : this.wheelUp, down: this.wheelDown},
+ OpenLayers.Util.extend(wheelOptions, this.mouseWheelOptions)
+ );
+ if (OpenLayers.Control.PinchZoom) {
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend(
+ {map: this.map}, this.pinchZoomOptions));
+ }
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if (evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ /**
+ * Method: defaultDblRightClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblRightClick: function (evt) {
+ this.map.zoomTo(this.map.zoom - 1, evt.xy);
+ },
+
+ /**
+ * Method: wheelChange
+ *
+ * Parameters:
+ * evt - {Event}
+ * deltaZ - {Integer}
+ */
+ wheelChange: function(evt, deltaZ) {
+ if (!this.map.fractionalZoom) {
+ deltaZ = Math.round(deltaZ);
+ }
+ var currentZoom = this.map.getZoom(),
+ newZoom = currentZoom + deltaZ;
+ newZoom = Math.max(newZoom, 0);
+ newZoom = Math.min(newZoom, this.map.getNumZoomLevels());
+ if (newZoom === currentZoom) {
+ return;
+ }
+ this.map.zoomTo(newZoom, evt.xy);
+ },
+
+ /**
+ * Method: wheelUp
+ * User spun scroll wheel up
+ *
+ * Parameters:
+ * evt - {Event}
+ * delta - {Integer}
+ */
+ wheelUp: function(evt, delta) {
+ this.wheelChange(evt, delta || 1);
+ },
+
+ /**
+ * Method: wheelDown
+ * User spun scroll wheel down
+ *
+ * Parameters:
+ * evt - {Event}
+ * delta - {Integer}
+ */
+ wheelDown: function(evt, delta) {
+ this.wheelChange(evt, delta || -1);
+ },
+
+ /**
+ * Method: disableZoomBox
+ */
+ disableZoomBox : function() {
+ this.zoomBoxEnabled = false;
+ this.zoomBox.deactivate();
+ },
+
+ /**
+ * Method: enableZoomBox
+ */
+ enableZoomBox : function() {
+ this.zoomBoxEnabled = true;
+ if (this.active) {
+ this.zoomBox.activate();
+ }
+ },
+
+ /**
+ * Method: disableZoomWheel
+ */
+
+ disableZoomWheel : function() {
+ this.zoomWheelEnabled = false;
+ this.handlers.wheel.deactivate();
+ },
+
+ /**
+ * Method: enableZoomWheel
+ */
+
+ enableZoomWheel : function() {
+ this.zoomWheelEnabled = true;
+ if (this.active) {
+ this.handlers.wheel.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Navigation"
+});
+/* ======================================================================
+ OpenLayers/Control/DrawFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DrawFeature
+ * The DrawFeature control draws point, line or polygon features on a vector
+ * layer when active.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * featureadded - Triggered when a feature is added
+ */
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: featureAdded
+ * {Function} Called after each feature is added
+ */
+ featureAdded: function() {},
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.DrawFeature
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ * handler - {<OpenLayers.Handler>}
+ * options - {Object}
+ */
+ initialize: function(layer, handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.callbacks = OpenLayers.Util.extend(
+ {
+ done: this.drawFeature,
+ modify: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchmodified", {vertex: vertex, feature: feature}
+ );
+ },
+ create: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchstarted", {vertex: vertex, feature: feature}
+ );
+ }
+ },
+ this.callbacks
+ );
+ this.layer = layer;
+ this.handlerOptions = this.handlerOptions || {};
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions, {
+ renderers: layer.renderers, rendererOptions: layer.rendererOptions
+ }
+ );
+ if (!("multi" in this.handlerOptions)) {
+ this.handlerOptions.multi = this.multi;
+ }
+ var sketchStyle = this.layer.styleMap && this.layer.styleMap.styles.temporary;
+ if(sketchStyle) {
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions,
+ {styleMap: new OpenLayers.StyleMap({"default": sketchStyle})}
+ );
+ }
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * Method: drawFeature
+ */
+ drawFeature: function(geometry) {
+ var feature = new OpenLayers.Feature.Vector(geometry);
+ var proceed = this.layer.events.triggerEvent(
+ "sketchcomplete", {feature: feature}
+ );
+ if(proceed !== false) {
+ feature.state = OpenLayers.State.INSERT;
+ this.layer.addFeatures([feature]);
+ this.featureAdded(feature);
+ this.events.triggerEvent("featureadded",{feature : feature});
+ }
+ },
+
+ /**
+ * APIMethod: insertXY
+ * Insert a point in the current sketch given x & y coordinates.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertXY(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeltaXY(dx, dy);
+ }
+ },
+
+ /**
+ * APIMethod: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDirectionLength(direction, length);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeflectionLength(deflection, length);
+ }
+ },
+
+ /**
+ * APIMethod: undo
+ * Remove the most recently added point in the current sketch geometry.
+ *
+ * Returns:
+ * {Boolean} An edit was undone.
+ */
+ undo: function() {
+ return this.handler.undo && this.handler.undo();
+ },
+
+ /**
+ * APIMethod: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} An edit was redone.
+ */
+ redo: function() {
+ return this.handler.redo && this.handler.redo();
+ },
+
+ /**
+ * APIMethod: finishSketch
+ * Finishes the sketch without including the currently drawn point.
+ * This method can be called to terminate drawing programmatically
+ * instead of waiting for the user to end the sketch.
+ */
+ finishSketch: function() {
+ this.handler.finishGeometry();
+ },
+
+ /**
+ * APIMethod: cancel
+ * Cancel the current sketch. This removes the current sketch and keeps
+ * the drawing control active.
+ */
+ cancel: function() {
+ this.handler.cancel();
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DrawFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/Polygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Polygon
+ * Handler to draw a polygon on the map. Polygon is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Path>
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
+
+ /**
+ * APIProperty: holeModifier
+ * {String} Key modifier to trigger hole digitizing. Acceptable values are
+ * "altKey", "shiftKey", or "ctrlKey". If not set, no hole digitizing
+ * will take place. Default is null.
+ */
+ holeModifier: null,
+
+ /**
+ * Property: drawingHole
+ * {Boolean} Currently drawing an interior ring.
+ */
+ drawingHole: false,
+
+ /**
+ * Property: polygon
+ * {<OpenLayers.Feature.Vector>}
+ */
+ polygon: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Polygon
+ * Create a Polygon Handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the polygon geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing([this.point.geometry])
+ );
+ this.polygon = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([this.line.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.polygon, this.point], {silent: true});
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ if(!this.drawingHole && this.holeModifier &&
+ this.evt && this.evt[this.holeModifier]) {
+ var geometry = this.point.geometry;
+ var features = this.control.layer.features;
+ var candidate, polygon;
+ // look for intersections, last drawn gets priority
+ for (var i=features.length-1; i>=0; --i) {
+ candidate = features[i].geometry;
+ if ((candidate instanceof OpenLayers.Geometry.Polygon ||
+ candidate instanceof OpenLayers.Geometry.MultiPolygon) &&
+ candidate.intersects(geometry)) {
+ polygon = features[i];
+ this.control.layer.removeFeatures([polygon], {silent: true});
+ this.control.layer.events.registerPriority(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.registerPriority(
+ "sketchmodified", this, this.enforceTopology
+ );
+ polygon.geometry.addComponent(this.line.geometry);
+ this.polygon = polygon;
+ this.drawingHole = true;
+ break;
+ }
+ }
+ }
+ OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments);
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 2;
+ },
+
+ /**
+ * Method: enforceTopology
+ * Simple topology enforcement for drawing interior rings. Ensures vertices
+ * of interior rings are contained by exterior ring. Other topology
+ * rules are enforced in <finalizeInteriorRing> to allow drawing of
+ * rings that intersect only during the sketch (e.g. a "C" shaped ring
+ * that nearly encloses another ring).
+ */
+ enforceTopology: function(event) {
+ var point = event.vertex;
+ var components = this.line.geometry.components;
+ // ensure that vertices of interior ring are contained by exterior ring
+ if (!this.polygon.geometry.intersects(point)) {
+ var last = components[components.length-3];
+ point.x = last.x;
+ point.y = last.y;
+ }
+ },
+
+ /**
+ * Method: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 2;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: finalizeInteriorRing
+ * Enforces that new ring has some area and doesn't contain vertices of any
+ * other rings.
+ */
+ finalizeInteriorRing: function() {
+ var ring = this.line.geometry;
+ // ensure that ring has some area
+ var modified = (ring.getArea() !== 0);
+ if (modified) {
+ // ensure that new ring doesn't intersect any other rings
+ var rings = this.polygon.geometry.components;
+ for (var i=rings.length-2; i>=0; --i) {
+ if (ring.intersects(rings[i])) {
+ modified = false;
+ break;
+ }
+ }
+ if (modified) {
+ // ensure that new ring doesn't contain any other rings
+ var target;
+ outer: for (var i=rings.length-2; i>0; --i) {
+ var points = rings[i].components;
+ for (var j=0, jj=points.length; j<jj; ++j) {
+ if (ring.containsPoint(points[j])) {
+ modified = false;
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ if (modified) {
+ if (this.polygon.state !== OpenLayers.State.INSERT) {
+ this.polygon.state = OpenLayers.State.UPDATE;
+ }
+ } else {
+ this.polygon.geometry.removeComponent(ring);
+ }
+ this.restoreFeature();
+ return false;
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ if (this.drawingHole) {
+ this.polygon.geometry.removeComponent(this.line.geometry);
+ this.restoreFeature(true);
+ }
+ return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
+ },
+
+ /**
+ * Method: restoreFeature
+ * Move the feature from the sketch layer to the target layer.
+ *
+ * Properties:
+ * cancel - {Boolean} Cancel drawing. If falsey, the "sketchcomplete" event
+ * will be fired.
+ */
+ restoreFeature: function(cancel) {
+ this.control.layer.events.unregister(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.unregister(
+ "sketchmodified", this, this.enforceTopology
+ );
+ this.layer.removeFeatures([this.polygon], {silent: true});
+ this.control.layer.addFeatures([this.polygon], {silent: true});
+ this.drawingHole = false;
+ if (!cancel) {
+ // Re-trigger "sketchcomplete" so other listeners can do their
+ // business. While this is somewhat sloppy (if a listener is
+ // registered with registerPriority - not common - between the start
+ // and end of a single ring drawing - very uncommon - it will be
+ // called twice).
+ // TODO: In 3.0, collapse sketch handlers into geometry specific
+ // drawing controls.
+ this.control.layer.events.triggerEvent(
+ "sketchcomplete", {feature : this.polygon}
+ );
+ }
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Path.prototype.destroyFeature.call(
+ this, force);
+ this.polygon = null;
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.polygon, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.polygon;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>}
+ */
+ getGeometry: function() {
+ var geometry = this.polygon && this.polygon.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPolygon([geometry]);
+ }
+ return geometry;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Polygon"
+});
+/* ======================================================================
+ OpenLayers/Control/EditingToolbar.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/DrawFeature.js
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Handler/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Control.EditingToolbar
+ * The EditingToolbar is a panel of 4 controls to draw polygons, lines,
+ * points, or to navigate the map by panning. By default it appears in the
+ * upper right corner of the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.EditingToolbar = OpenLayers.Class(
+ OpenLayers.Control.Panel, {
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Constructor: OpenLayers.Control.EditingToolbar
+ * Create an editing toolbar for a given layer.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ * options - {Object}
+ */
+ initialize: function(layer, options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+
+ this.addControls(
+ [ new OpenLayers.Control.Navigation() ]
+ );
+ var controls = [
+ new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {
+ displayClass: 'olControlDrawFeaturePoint',
+ handlerOptions: {citeCompliant: this.citeCompliant}
+ }),
+ new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {
+ displayClass: 'olControlDrawFeaturePath',
+ handlerOptions: {citeCompliant: this.citeCompliant}
+ }),
+ new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {
+ displayClass: 'olControlDrawFeaturePolygon',
+ handlerOptions: {citeCompliant: this.citeCompliant}
+ })
+ ];
+ this.addControls(controls);
+ },
+
+ /**
+ * Method: draw
+ * calls the default draw, and then activates mouse defaults.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+ if (this.defaultControl === null) {
+ this.defaultControl = this.controls[0];
+ }
+ return div;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.EditingToolbar"
+});
+/* ======================================================================
+ OpenLayers/Strategy/BBOX.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.BBOX
+ * A simple strategy that reads new features when the viewport invalidates
+ * some bounds.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The current data bounds (in the same projection
+ * as the layer - not always the same projection as the map).
+ */
+ bounds: null,
+
+ /**
+ * Property: resolution
+ * {Float} The current data resolution.
+ */
+ resolution: null,
+
+ /**
+ * APIProperty: ratio
+ * {Float} The ratio of the data bounds to the viewport bounds (in each
+ * dimension). Default is 2.
+ */
+ ratio: 2,
+
+ /**
+ * Property: resFactor
+ * {Float} Optional factor used to determine when previously requested
+ * features are invalid. If set, the resFactor will be compared to the
+ * resolution of the previous request to the current map resolution.
+ * If resFactor > (old / new) and 1/resFactor < (old / new). If you
+ * set a resFactor of 1, data will be requested every time the
+ * resolution changes. If you set a resFactor of 3, data will be
+ * requested if the old resolution is 3 times the new, or if the new is
+ * 3 times the old. If the old bounds do not contain the new bounds
+ * new data will always be requested (with or without considering
+ * resFactor).
+ */
+ resFactor: null,
+
+ /**
+ * Property: response
+ * {<OpenLayers.Protocol.Response>} The protocol response object returned
+ * by the layer protocol.
+ */
+ response: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.BBOX
+ * Create a new BBOX strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Set up strategy with regard to reading new batches of remote data.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "moveend": this.update,
+ "refresh": this.update,
+ "visibilitychanged": this.update,
+ scope: this
+ });
+ this.update();
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Tear down strategy with regard to reading new batches of remote data.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "moveend": this.update,
+ "refresh": this.update,
+ "visibilitychanged": this.update,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: update
+ * Callback function called on "moveend" or "refresh" layer events.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will determine
+ * the behaviour of this Strategy
+ *
+ * Valid options include:
+ * force - {Boolean} if true, new data must be unconditionally read.
+ * noAbort - {Boolean} if true, do not abort previous requests.
+ */
+ update: function(options) {
+ var mapBounds = this.getMapBounds();
+ if (mapBounds !== null && ((options && options.force) ||
+ (this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
+ this.calculateBounds(mapBounds);
+ this.resolution = this.layer.map.getResolution();
+ this.triggerRead(options);
+ }
+ },
+
+ /**
+ * Method: getMapBounds
+ * Get the map bounds expressed in the same projection as this layer.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
+ */
+ getMapBounds: function() {
+ if (this.layer.map === null) {
+ return null;
+ }
+ var bounds = this.layer.map.getExtent();
+ if(bounds && !this.layer.projection.equals(
+ this.layer.map.getProjectionObject())) {
+ bounds = bounds.clone().transform(
+ this.layer.map.getProjectionObject(), this.layer.projection
+ );
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: invalidBounds
+ * Determine whether the previously requested set of features is invalid.
+ * This occurs when the new map bounds do not contain the previously
+ * requested bounds. In addition, if <resFactor> is set, it will be
+ * considered.
+ *
+ * Parameters:
+ * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+ * retrieved from the map object if not provided
+ *
+ * Returns:
+ * {Boolean}
+ */
+ invalidBounds: function(mapBounds) {
+ if(!mapBounds) {
+ mapBounds = this.getMapBounds();
+ }
+ var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
+ if(!invalid && this.resFactor) {
+ var ratio = this.resolution / this.layer.map.getResolution();
+ invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
+ }
+ return invalid;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+ * retrieved from the map object if not provided
+ */
+ calculateBounds: function(mapBounds) {
+ if(!mapBounds) {
+ mapBounds = this.getMapBounds();
+ }
+ var center = mapBounds.getCenterLonLat();
+ var dataWidth = mapBounds.getWidth() * this.ratio;
+ var dataHeight = mapBounds.getHeight() * this.ratio;
+ this.bounds = new OpenLayers.Bounds(
+ center.lon - (dataWidth / 2),
+ center.lat - (dataHeight / 2),
+ center.lon + (dataWidth / 2),
+ center.lat + (dataHeight / 2)
+ );
+ },
+
+ /**
+ * Method: triggerRead
+ *
+ * Parameters:
+ * options - {Object} Additional options for the protocol's read method
+ * (optional)
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} The protocol response object
+ * returned by the layer protocol.
+ */
+ triggerRead: function(options) {
+ if (this.response && !(options && options.noAbort === true)) {
+ this.layer.protocol.abort(this.response);
+ this.layer.events.triggerEvent("loadend");
+ }
+ var evt = {filter: this.createFilter()};
+ this.layer.events.triggerEvent("loadstart", evt);
+ this.response = this.layer.protocol.read(
+ OpenLayers.Util.applyDefaults({
+ filter: evt.filter,
+ callback: this.merge,
+ scope: this
+ }, options));
+ },
+
+ /**
+ * Method: createFilter
+ * Creates a spatial BBOX filter. If the layer that this strategy belongs
+ * to has a filter property, this filter will be combined with the BBOX
+ * filter.
+ *
+ * Returns
+ * {<OpenLayers.Filter>} The filter object.
+ */
+ createFilter: function() {
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: this.bounds,
+ projection: this.layer.projection
+ });
+ if (this.layer.filter) {
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.layer.filter, filter]
+ });
+ }
+ return filter;
+ },
+
+ /**
+ * Method: merge
+ * Given a list of features, determine which ones to add to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ this.layer.destroyFeatures();
+ if (resp.success()) {
+ var features = resp.features;
+ if(features && features.length > 0) {
+ var remote = this.layer.projection;
+ var local = this.layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ this.layer.addFeatures(features);
+ }
+ } else {
+ this.bounds = null;
+ }
+ this.response = null;
+ this.layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.BBOX"
+});
+/* ======================================================================
+ OpenLayers/Layer/WorldWind.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WorldWind
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WorldWind = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ DEFAULT_PARAMS: {
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} WorldWind layer is a base layer by default.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: lzd
+ * {Float} LevelZeroTileSizeDegrees
+ */
+ lzd: null,
+
+ /**
+ * APIProperty: zoomLevels
+ * {Integer} Number of zoom levels.
+ */
+ zoomLevels: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.WorldWind
+ *
+ * Parameters:
+ * name - {String} Name of Layer
+ * url - {String} Base URL
+ * lzd - {Float} Level zero tile size degrees
+ * zoomLevels - {Integer} number of zoom levels
+ * params - {Object} additional parameters
+ * options - {Object} additional options
+ */
+ initialize: function(name, url, lzd, zoomLevels, params, options) {
+ this.lzd = lzd;
+ this.zoomLevels = zoomLevels;
+ var newArguments = [];
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ this.params = OpenLayers.Util.applyDefaults(
+ this.params, this.DEFAULT_PARAMS
+ );
+ },
+
+ /**
+ * Method: getZoom
+ * Convert map zoom to WW zoom.
+ */
+ getZoom: function () {
+ var zoom = this.map.getZoom();
+ var extent = this.map.getMaxExtent();
+ zoom = zoom - Math.log(this.maxResolution / (this.lzd/512))/Math.log(2);
+ return zoom;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var zoom = this.getZoom();
+ var extent = this.map.getMaxExtent();
+ var deg = this.lzd/Math.pow(2,this.getZoom());
+ var x = Math.floor((bounds.left - extent.left)/deg);
+ var y = Math.floor((bounds.bottom - extent.bottom)/deg);
+ if (this.map.getResolution() <= (this.lzd/512)
+ && this.getZoom() <= this.zoomLevels) {
+ return this.getFullRequestString(
+ { L: zoom,
+ X: x,
+ Y: y
+ });
+ } else {
+ return OpenLayers.Util.getImageLocation("blank.gif");
+ }
+
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WorldWind"
+});
+/* ======================================================================
+ OpenLayers/Protocol/CSW.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.CSW
+ * Used to create a versioned CSW protocol. Default version is 2.0.2.
+ */
+OpenLayers.Protocol.CSW = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.CSW.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.CSW["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported CSW version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Protocol.CSW.DEFAULTS
+ */
+OpenLayers.Protocol.CSW.DEFAULTS = {
+ "version": "2.0.2"
+};
+/* ======================================================================
+ OpenLayers/Format/WMTSCapabilities.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMTSCapabilities
+ * Read WMTS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIProperty: yx
+ * {Object} Members in the yx object are used to determine if a CRS URN
+ * corresponds to a CRS with y,x axis order. Member names are CRS URNs
+ * and values are boolean. By default, the following CRS URN are
+ * assumed to correspond to a CRS with y,x axis order:
+ *
+ * * urn:ogc:def:crs:EPSG::4326
+ */
+ yx: {
+ "urn:ogc:def:crs:EPSG::4326": true
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WMTSCapabilities
+ * Create a new parser for WMTS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return information about
+ * the service (offering and observedProperty mostly).
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Info about the WMTS Capabilities
+ */
+
+ /**
+ * APIMethod: createLayer
+ * Create a WMTS layer given a capabilities object.
+ *
+ * Parameters:
+ * capabilities - {Object} The object returned from a <read> call to this
+ * format.
+ * config - {Object} Configuration properties for the layer. Defaults for
+ * the layer will apply if not provided.
+ *
+ * Required config properties:
+ * layer - {String} The layer identifier.
+ *
+ * Optional config properties:
+ * matrixSet - {String} The matrix set identifier, required if there is
+ * more than one matrix set in the layer capabilities.
+ * style - {String} The name of the style
+ * format - {String} Image format for the layer. Default is the first
+ * format returned in the GetCapabilities response.
+ * param - {Object} The dimensions values eg: {"Year": "2012"}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMTS>} A properly configured WMTS layer. Throws an
+ * error if an incomplete config is provided. Returns undefined if no
+ * layer could be created with the provided config.
+ */
+ createLayer: function(capabilities, config) {
+ var layer;
+
+ // confirm required properties are supplied in config
+ if (!('layer' in config)) {
+ throw new Error("Missing property 'layer' in configuration.");
+ }
+
+ var contents = capabilities.contents;
+
+ // find the layer definition with the given identifier
+ var layers = contents.layers;
+ var layerDef;
+ for (var i=0, ii=contents.layers.length; i<ii; ++i) {
+ if (contents.layers[i].identifier === config.layer) {
+ layerDef = contents.layers[i];
+ break;
+ }
+ }
+ if (!layerDef) {
+ throw new Error("Layer not found");
+ }
+
+ var format = config.format;
+ if (!format && layerDef.formats && layerDef.formats.length) {
+ format = layerDef.formats[0];
+ }
+
+ // find the matrixSet definition
+ var matrixSet;
+ if (config.matrixSet) {
+ matrixSet = contents.tileMatrixSets[config.matrixSet];
+ } else if (layerDef.tileMatrixSetLinks.length >= 1) {
+ matrixSet = contents.tileMatrixSets[
+ layerDef.tileMatrixSetLinks[0].tileMatrixSet];
+ }
+ if (!matrixSet) {
+ throw new Error("matrixSet not found");
+ }
+
+ // get the default style for the layer
+ var style;
+ for (var i=0, ii=layerDef.styles.length; i<ii; ++i) {
+ style = layerDef.styles[i];
+ if (style.isDefault) {
+ break;
+ }
+ }
+
+ var requestEncoding = config.requestEncoding;
+ if (!requestEncoding) {
+ requestEncoding = "KVP";
+ if (capabilities.operationsMetadata.GetTile.dcp.http) {
+ var http = capabilities.operationsMetadata.GetTile.dcp.http;
+ // Get first get method
+ if (http.get[0].constraints) {
+ var constraints = http.get[0].constraints;
+ var allowedValues = constraints.GetEncoding.allowedValues;
+
+ // The OGC documentation is not clear if we should use
+ // REST or RESTful, ArcGis use RESTful,
+ // and OpenLayers use REST.
+ if (!allowedValues.KVP &&
+ (allowedValues.REST || allowedValues.RESTful)) {
+ requestEncoding = "REST";
+ }
+ }
+ }
+ }
+
+ var dimensions = [];
+ var params = config.params || {};
+ // to don't overwrite the changes in the applyDefaults
+ delete config.params;
+ for (var id = 0, ld = layerDef.dimensions.length ; id < ld ; id++) {
+ var dimension = layerDef.dimensions[id];
+ dimensions.push(dimension.identifier);
+ if (!params.hasOwnProperty(dimension.identifier)) {
+ params[dimension.identifier] = dimension['default'];
+ }
+ }
+
+ var projection = config.projection || matrixSet.supportedCRS.replace(
+ /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, "$1:$3");
+ var units = config.units ||
+ (projection === "EPSG:4326" ? "degrees" : "m");
+
+ var resolutions = [];
+ for (var mid in matrixSet.matrixIds) {
+ if (matrixSet.matrixIds.hasOwnProperty(mid)) {
+ resolutions.push(
+ matrixSet.matrixIds[mid].scaleDenominator * 0.28E-3 /
+ OpenLayers.METERS_PER_INCH /
+ OpenLayers.INCHES_PER_UNIT[units]);
+ }
+ }
+
+ var url;
+ if (requestEncoding === "REST" && layerDef.resourceUrls) {
+ url = [];
+ var resourceUrls = layerDef.resourceUrls,
+ resourceUrl;
+ for (var t = 0, tt = layerDef.resourceUrls.length; t < tt; ++t) {
+ resourceUrl = layerDef.resourceUrls[t];
+ if (resourceUrl.format === format && resourceUrl.resourceType === "tile") {
+ url.push(resourceUrl.template);
+ }
+ }
+ }
+ else {
+ var httpGet = capabilities.operationsMetadata.GetTile.dcp.http.get;
+ url = [];
+ var constraint;
+ for (var i = 0, ii = httpGet.length; i < ii; i++) {
+ constraint = httpGet[i].constraints;
+ if (!constraint || (constraint && constraint.
+ GetEncoding.allowedValues[requestEncoding])) {
+ url.push(httpGet[i].url);
+ }
+ }
+ }
+
+ return new OpenLayers.Layer.WMTS(
+ OpenLayers.Util.applyDefaults(config, {
+ url: url,
+ requestEncoding: requestEncoding,
+ name: layerDef.title,
+ style: style.identifier,
+ format: format,
+ matrixIds: matrixSet.matrixIds,
+ matrixSet: matrixSet.identifier,
+ projection: projection,
+ units: units,
+ resolutions: config.isBaseLayer === false ? undefined :
+ resolutions,
+ serverResolutions: resolutions,
+ tileFullExtent: matrixSet.bounds,
+ dimensions: dimensions,
+ params: params
+ })
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMTSCapabilities"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/Google/v3.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Google.js
+ */
+
+/**
+ * Constant: OpenLayers.Layer.Google.v3
+ *
+ * Mixin providing functionality specific to the Google Maps API v3.
+ *
+ * To use this layer, you must include the GMaps v3 API in your html.
+ *
+ * Note that this layer configures the google.maps.map object with the
+ * "disableDefaultUI" option set to true. Using UI controls that the Google
+ * Maps API provides is not supported by the OpenLayers API.
+ */
+OpenLayers.Layer.Google.v3 = {
+
+ /**
+ * Constant: DEFAULTS
+ * {Object} It is not recommended to change the properties set here. Note
+ * that Google.v3 layers only work when sphericalMercator is set to true.
+ *
+ * (code)
+ * {
+ * sphericalMercator: true,
+ * projection: "EPSG:900913"
+ * }
+ * (end)
+ */
+ DEFAULTS: {
+ sphericalMercator: true,
+ projection: "EPSG:900913"
+ },
+
+ /**
+ * APIProperty: animationEnabled
+ * {Boolean} If set to true, the transition between zoom levels will be
+ * animated (if supported by the GMaps API for the device used). Set to
+ * false to match the zooming experience of other layer types. Default
+ * is true. Note that the GMaps API does not give us control over zoom
+ * animation, so if set to false, when zooming, this will make the
+ * layer temporarily invisible, wait until GMaps reports the map being
+ * idle, and make it visible again. The result will be a blank layer
+ * for a few moments while zooming.
+ */
+ animationEnabled: true,
+
+ /**
+ * Method: loadMapObject
+ * Load the GMap and register appropriate event listeners.
+ */
+ loadMapObject: function() {
+ if (!this.type) {
+ this.type = google.maps.MapTypeId.ROADMAP;
+ }
+ var mapObject;
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // there are already Google layers added to this map
+ mapObject = cache.mapObject;
+ // increment the layer count
+ ++cache.count;
+ } else {
+ // this is the first Google layer for this map
+ // create GMap
+ var center = this.map.getCenter();
+ var container = document.createElement('div');
+ container.className = "olForeignContainer";
+ container.style.width = '100%';
+ container.style.height = '100%';
+ mapObject = new google.maps.Map(container, {
+ center: center ?
+ new google.maps.LatLng(center.lat, center.lon) :
+ new google.maps.LatLng(0, 0),
+ zoom: this.map.getZoom() || 0,
+ mapTypeId: this.type,
+ disableDefaultUI: true,
+ keyboardShortcuts: false,
+ draggable: false,
+ disableDoubleClickZoom: true,
+ scrollwheel: false,
+ streetViewControl: false
+ });
+ var googleControl = document.createElement('div');
+ googleControl.style.width = '100%';
+ googleControl.style.height = '100%';
+ mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(googleControl);
+
+ // cache elements for use by any other google layers added to
+ // this same map
+ cache = {
+ googleControl: googleControl,
+ mapObject: mapObject,
+ count: 1
+ };
+ OpenLayers.Layer.Google.cache[this.map.id] = cache;
+ }
+ this.mapObject = mapObject;
+ this.setGMapVisibility(this.visibility);
+ },
+
+ /**
+ * APIMethod: onMapResize
+ */
+ onMapResize: function() {
+ if (this.visibility) {
+ google.maps.event.trigger(this.mapObject, "resize");
+ }
+ },
+
+ /**
+ * Method: setGMapVisibility
+ * Display the GMap container and associated elements.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the GMap elements.
+ */
+ setGMapVisibility: function(visible) {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ var map = this.map;
+ if (cache) {
+ var type = this.type;
+ var layers = map.layers;
+ var layer;
+ for (var i=layers.length-1; i>=0; --i) {
+ layer = layers[i];
+ if (layer instanceof OpenLayers.Layer.Google &&
+ layer.visibility === true && layer.inRange === true) {
+ type = layer.type;
+ visible = true;
+ break;
+ }
+ }
+ var container = this.mapObject.getDiv();
+ if (visible === true) {
+ if (container.parentNode !== map.div) {
+ if (!cache.rendered) {
+ var me = this;
+ google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() {
+ cache.rendered = true;
+ me.setGMapVisibility(me.getVisibility());
+ me.moveTo(me.map.getCenter());
+ });
+ } else {
+ map.div.appendChild(container);
+ cache.googleControl.appendChild(map.viewPortDiv);
+ google.maps.event.trigger(this.mapObject, 'resize');
+ }
+ }
+ this.mapObject.setMapTypeId(type);
+ } else if (cache.googleControl.hasChildNodes()) {
+ map.div.appendChild(map.viewPortDiv);
+ map.div.removeChild(container);
+ }
+ }
+ },
+
+ /**
+ * Method: getMapContainer
+ *
+ * Returns:
+ * {DOMElement} the GMap container's div
+ */
+ getMapContainer: function() {
+ return this.mapObject.getDiv();
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getMapObjectBoundsFromOLBounds
+ *
+ * Parameters:
+ * olBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} A MapObject Bounds, translated from olBounds
+ * Returns null if null value is passed in
+ */
+ getMapObjectBoundsFromOLBounds: function(olBounds) {
+ var moBounds = null;
+ if (olBounds != null) {
+ var sw = this.sphericalMercator ?
+ this.inverseMercator(olBounds.bottom, olBounds.left) :
+ new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+ var ne = this.sphericalMercator ?
+ this.inverseMercator(olBounds.top, olBounds.right) :
+ new OpenLayers.LonLat(olBounds.top, olBounds.right);
+ moBounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(sw.lat, sw.lon),
+ new google.maps.LatLng(ne.lat, ne.lon)
+ );
+ }
+ return moBounds;
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ var size = this.map.getSize();
+ var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center);
+ var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center);
+ var res = this.map.getResolution();
+
+ var delta_x = moPixel.x - (size.w / 2);
+ var delta_y = moPixel.y - (size.h / 2);
+
+ var lonlat = new OpenLayers.LonLat(
+ lon + delta_x * res,
+ lat - delta_y * res
+ );
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+ var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+ var res = this.map.getResolution();
+ var extent = this.map.getExtent();
+ return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)),
+ (1/res * (extent.top - lat)));
+ },
+
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ if (this.animationEnabled === false && zoom != this.mapObject.zoom) {
+ var mapContainer = this.getMapContainer();
+ google.maps.event.addListenerOnce(
+ this.mapObject,
+ "idle",
+ function() {
+ mapContainer.style.visibility = "";
+ }
+ );
+ mapContainer.style.visibility = "hidden";
+ }
+ this.mapObject.setOptions({
+ center: center,
+ zoom: zoom
+ });
+ },
+
+
+ // Bounds
+
+ /**
+ * APIMethod: getMapObjectZoomFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object} MapObject Bounds format
+ *
+ * Returns:
+ * {Object} MapObject Zoom for specified MapObject Bounds
+ */
+ getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+ return this.mapObject.getBoundsZoomLevel(moBounds);
+ },
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var gLatLng;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ gLatLng = new google.maps.LatLng(lonlat.lat, lonlat.lon);
+ } else {
+ gLatLng = new google.maps.LatLng(lat, lon);
+ }
+ return gLatLng;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new google.maps.Point(x, y);
+ }
+
+};
+/* ======================================================================
+ OpenLayers/Format/WPSDescribeProcess.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSDescribeProcess
+ * Read WPS DescribeProcess responses.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WPSDescribeProcess = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wps: "http://www.opengis.net/wps/1.0.0",
+ ows: "http://www.opengis.net/ows/1.1",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wps",
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WPSDescribeProcess
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Parse a WPS DescribeProcess and return an object with its information.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object}
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var info = {};
+ this.readNode(data, info);
+ return info;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wps": {
+ "ProcessDescriptions": function(node, obj) {
+ obj.processDescriptions = {};
+ this.readChildNodes(node, obj.processDescriptions);
+ },
+ "ProcessDescription": function(node, processDescriptions) {
+ var processVersion = this.getAttributeNS(node, this.namespaces.wps, "processVersion");
+ var processDescription = {
+ processVersion: processVersion,
+ statusSupported: (node.getAttribute("statusSupported") === "true"),
+ storeSupported: (node.getAttribute("storeSupported") === "true")
+ };
+ this.readChildNodes(node, processDescription);
+ processDescriptions[processDescription.identifier] = processDescription;
+ },
+ "DataInputs": function(node, processDescription) {
+ processDescription.dataInputs = [];
+ this.readChildNodes(node, processDescription.dataInputs);
+ },
+ "ProcessOutputs": function(node, processDescription) {
+ processDescription.processOutputs = [];
+ this.readChildNodes(node, processDescription.processOutputs);
+ },
+ "Output": function(node, processOutputs) {
+ var output = {};
+ this.readChildNodes(node, output);
+ processOutputs.push(output);
+ },
+ "ComplexOutput": function(node, output) {
+ output.complexOutput = {};
+ this.readChildNodes(node, output.complexOutput);
+ },
+ "LiteralOutput": function(node, output) {
+ output.literalOutput = {};
+ this.readChildNodes(node, output.literalOutput);
+ },
+ "Input": function(node, dataInputs) {
+ var input = {
+ maxOccurs: parseInt(node.getAttribute("maxOccurs")),
+ minOccurs: parseInt(node.getAttribute("minOccurs"))
+ };
+ this.readChildNodes(node, input);
+ dataInputs.push(input);
+ },
+ "BoundingBoxData": function(node, input) {
+ input.boundingBoxData = {};
+ this.readChildNodes(node, input.boundingBoxData);
+ },
+ "CRS": function(node, obj) {
+ if (!obj.CRSs) {
+ obj.CRSs = {};
+ }
+ obj.CRSs[this.getChildValue(node)] = true;
+ },
+ "LiteralData": function(node, input) {
+ input.literalData = {};
+ this.readChildNodes(node, input.literalData);
+ },
+ "ComplexData": function(node, input) {
+ input.complexData = {};
+ this.readChildNodes(node, input.complexData);
+ },
+ "Default": function(node, complexData) {
+ complexData["default"] = {};
+ this.readChildNodes(node, complexData["default"]);
+ },
+ "Supported": function(node, complexData) {
+ complexData["supported"] = {};
+ this.readChildNodes(node, complexData["supported"]);
+ },
+ "Format": function(node, obj) {
+ var format = {};
+ this.readChildNodes(node, format);
+ if (!obj.formats) {
+ obj.formats = {};
+ }
+ obj.formats[format.mimeType] = true;
+ },
+ "MimeType": function(node, format) {
+ format.mimeType = this.getChildValue(node);
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WPSDescribeProcess"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WKT.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WKT
+ * Class for reading and writing Well-Known Text. Create a new instance
+ * with the <OpenLayers.Format.WKT> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.WKT = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Constructor: OpenLayers.Format.WKT
+ * Create a new parser for WKT
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance
+ *
+ * Returns:
+ * {<OpenLayers.Format.WKT>} A new WKT parser.
+ */
+ initialize: function(options) {
+ this.regExes = {
+ 'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
+ 'spaces': /\s+/,
+ 'parenComma': /\)\s*,\s*\(/,
+ 'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/, // can't use {2} here
+ 'trimParens': /^\s*\(?(.*?)\)?\s*$/
+ };
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a WKT string and return a vector feature or an
+ * array of vector features. Supports WKT for POINT, MULTIPOINT,
+ * LINESTRING, MULTILINESTRING, POLYGON, MULTIPOLYGON, and
+ * GEOMETRYCOLLECTION.
+ *
+ * Parameters:
+ * wkt - {String} A WKT string
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>|Array} A feature or array of features for
+ * GEOMETRYCOLLECTION WKT.
+ */
+ read: function(wkt) {
+ var features, type, str;
+ wkt = wkt.replace(/[\n\r]/g, " ");
+ var matches = this.regExes.typeStr.exec(wkt);
+ if(matches) {
+ type = matches[1].toLowerCase();
+ str = matches[2];
+ if(this.parse[type]) {
+ features = this.parse[type].apply(this, [str]);
+ }
+ if (this.internalProjection && this.externalProjection) {
+ if (features &&
+ features.CLASS_NAME == "OpenLayers.Feature.Vector") {
+ features.geometry.transform(this.externalProjection,
+ this.internalProjection);
+ } else if (features &&
+ type != "geometrycollection" &&
+ typeof features == "object") {
+ for (var i=0, len=features.length; i<len; i++) {
+ var component = features[i];
+ component.geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ }
+ }
+ }
+ return features;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature or array of features into a WKT string.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>|Array} A feature or array of
+ * features
+ *
+ * Returns:
+ * {String} The WKT string representation of the input geometries
+ */
+ write: function(features) {
+ var collection, geometry, isCollection;
+ if (features.constructor == Array) {
+ collection = features;
+ isCollection = true;
+ } else {
+ collection = [features];
+ isCollection = false;
+ }
+ var pieces = [];
+ if (isCollection) {
+ pieces.push('GEOMETRYCOLLECTION(');
+ }
+ for (var i=0, len=collection.length; i<len; ++i) {
+ if (isCollection && i>0) {
+ pieces.push(',');
+ }
+ geometry = collection[i].geometry;
+ pieces.push(this.extractGeometry(geometry));
+ }
+ if (isCollection) {
+ pieces.push(')');
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: extractGeometry
+ * Entry point to construct the WKT for a single Geometry object.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Geometry>}
+ *
+ * Returns:
+ * {String} A WKT string of representing the geometry
+ */
+ extractGeometry: function(geometry) {
+ var type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
+ if (!this.extract[type]) {
+ return null;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection, this.externalProjection);
+ }
+ var wktType = type == 'collection' ? 'GEOMETRYCOLLECTION' : type.toUpperCase();
+ var data = wktType + '(' + this.extract[type].apply(this, [geometry]) + ')';
+ return data;
+ },
+
+ /**
+ * Object with properties corresponding to the geometry types.
+ * Property values are functions that do the actual data extraction.
+ */
+ extract: {
+ /**
+ * Return a space delimited string of point coordinates.
+ * @param {OpenLayers.Geometry.Point} point
+ * @returns {String} A string of coordinates representing the point
+ */
+ 'point': function(point) {
+ return point.x + ' ' + point.y;
+ },
+
+ /**
+ * Return a comma delimited string of point coordinates from a multipoint.
+ * @param {OpenLayers.Geometry.MultiPoint} multipoint
+ * @returns {String} A string of point coordinate strings representing
+ * the multipoint
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0, len=multipoint.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.point.apply(this, [multipoint.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return a comma delimited string of point coordinates from a line.
+ * @param {OpenLayers.Geometry.LineString} linestring
+ * @returns {String} A string of point coordinate strings representing
+ * the linestring
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return a comma delimited string of linestring strings from a multilinestring.
+ * @param {OpenLayers.Geometry.MultiLineString} multilinestring
+ * @returns {String} A string of of linestring strings representing
+ * the multilinestring
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.linestring.apply(this, [multilinestring.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return a comma delimited string of linear ring arrays from a polygon.
+ * @param {OpenLayers.Geometry.Polygon} polygon
+ * @returns {String} An array of linear ring arrays representing the polygon
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0, len=polygon.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.linestring.apply(this, [polygon.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return an array of polygon arrays from a multipolygon.
+ * @param {OpenLayers.Geometry.MultiPolygon} multipolygon
+ * @returns {String} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.polygon.apply(this, [multipolygon.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return the WKT portion between 'GEOMETRYCOLLECTION(' and ')' for an <OpenLayers.Geometry.Collection>
+ * @param {OpenLayers.Geometry.Collection} collection
+ * @returns {String} internal WKT representation of the collection
+ */
+ 'collection': function(collection) {
+ var array = [];
+ for(var i=0, len=collection.components.length; i<len; ++i) {
+ array.push(this.extractGeometry.apply(this, [collection.components[i]]));
+ }
+ return array.join(',');
+ }
+
+ },
+
+ /**
+ * Object with properties corresponding to the geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parse: {
+ /**
+ * Return point feature given a point WKT fragment.
+ * @param {String} str A WKT fragment representing the point
+ * @returns {OpenLayers.Feature.Vector} A point feature
+ * @private
+ */
+ 'point': function(str) {
+ var coords = OpenLayers.String.trim(str).split(this.regExes.spaces);
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(coords[0], coords[1])
+ );
+ },
+
+ /**
+ * Return a multipoint feature given a multipoint WKT fragment.
+ * @param {String} str A WKT fragment representing the multipoint
+ * @returns {OpenLayers.Feature.Vector} A multipoint feature
+ * @private
+ */
+ 'multipoint': function(str) {
+ var point;
+ var points = OpenLayers.String.trim(str).split(',');
+ var components = [];
+ for(var i=0, len=points.length; i<len; ++i) {
+ point = points[i].replace(this.regExes.trimParens, '$1');
+ components.push(this.parse.point.apply(this, [point]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPoint(components)
+ );
+ },
+
+ /**
+ * Return a linestring feature given a linestring WKT fragment.
+ * @param {String} str A WKT fragment representing the linestring
+ * @returns {OpenLayers.Feature.Vector} A linestring feature
+ * @private
+ */
+ 'linestring': function(str) {
+ var points = OpenLayers.String.trim(str).split(',');
+ var components = [];
+ for(var i=0, len=points.length; i<len; ++i) {
+ components.push(this.parse.point.apply(this, [points[i]]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(components)
+ );
+ },
+
+ /**
+ * Return a multilinestring feature given a multilinestring WKT fragment.
+ * @param {String} str A WKT fragment representing the multilinestring
+ * @returns {OpenLayers.Feature.Vector} A multilinestring feature
+ * @private
+ */
+ 'multilinestring': function(str) {
+ var line;
+ var lines = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+ var components = [];
+ for(var i=0, len=lines.length; i<len; ++i) {
+ line = lines[i].replace(this.regExes.trimParens, '$1');
+ components.push(this.parse.linestring.apply(this, [line]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiLineString(components)
+ );
+ },
+
+ /**
+ * Return a polygon feature given a polygon WKT fragment.
+ * @param {String} str A WKT fragment representing the polygon
+ * @returns {OpenLayers.Feature.Vector} A polygon feature
+ * @private
+ */
+ 'polygon': function(str) {
+ var ring, linestring, linearring;
+ var rings = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+ var components = [];
+ for(var i=0, len=rings.length; i<len; ++i) {
+ ring = rings[i].replace(this.regExes.trimParens, '$1');
+ linestring = this.parse.linestring.apply(this, [ring]).geometry;
+ linearring = new OpenLayers.Geometry.LinearRing(linestring.components);
+ components.push(linearring);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon(components)
+ );
+ },
+
+ /**
+ * Return a multipolygon feature given a multipolygon WKT fragment.
+ * @param {String} str A WKT fragment representing the multipolygon
+ * @returns {OpenLayers.Feature.Vector} A multipolygon feature
+ * @private
+ */
+ 'multipolygon': function(str) {
+ var polygon;
+ var polygons = OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);
+ var components = [];
+ for(var i=0, len=polygons.length; i<len; ++i) {
+ polygon = polygons[i].replace(this.regExes.trimParens, '$1');
+ components.push(this.parse.polygon.apply(this, [polygon]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPolygon(components)
+ );
+ },
+
+ /**
+ * Return an array of features given a geometrycollection WKT fragment.
+ * @param {String} str A WKT fragment representing the geometrycollection
+ * @returns {Array} An array of OpenLayers.Feature.Vector
+ * @private
+ */
+ 'geometrycollection': function(str) {
+ // separate components of the collection with |
+ str = str.replace(/,\s*([A-Za-z])/g, '|$1');
+ var wktArray = OpenLayers.String.trim(str).split('|');
+ var components = [];
+ for(var i=0, len=wktArray.length; i<len; ++i) {
+ components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
+ }
+ return components;
+ }
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WKT"
+});
+/* ======================================================================
+ OpenLayers/WPSProcess.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Format/GeoJSON.js
+ * @requires OpenLayers/Format/WPSExecute.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.WPSProcess
+ * Representation of a WPS process. Usually instances of
+ * <OpenLayers.WPSProcess> are created by calling 'getProcess' on an
+ * <OpenLayers.WPSClient> instance.
+ *
+ * Currently <OpenLayers.WPSProcess> supports processes that have geometries
+ * or features as output, using WKT or GeoJSON as output format. It also
+ * supports chaining of processes by using the <output> method to create a
+ * handle that is used as process input instead of a static value.
+ */
+OpenLayers.WPSProcess = OpenLayers.Class({
+
+ /**
+ * Property: client
+ * {<OpenLayers.WPSClient>} The client that manages this process.
+ */
+ client: null,
+
+ /**
+ * Property: server
+ * {String} Local client identifier for this process's server.
+ */
+ server: null,
+
+ /**
+ * Property: identifier
+ * {String} Process identifier known to the server.
+ */
+ identifier: null,
+
+ /**
+ * Property: description
+ * {Object} DescribeProcess response for this process.
+ */
+ description: null,
+
+ /**
+ * APIProperty: localWPS
+ * {String} Service endpoint for locally chained WPS processes. Default is
+ * 'http://geoserver/wps'.
+ */
+ localWPS: 'http://geoserver/wps',
+
+ /**
+ * Property: formats
+ * {Object} OpenLayers.Format instances keyed by mimetype.
+ */
+ formats: null,
+
+ /**
+ * Property: chained
+ * {Integer} Number of chained processes for pending execute requests that
+ * don't have a full configuration yet.
+ */
+ chained: 0,
+
+ /**
+ * Property: executeCallbacks
+ * {Array} Callbacks waiting to be executed until all chained processes
+ * are configured;
+ */
+ executeCallbacks: null,
+
+ /**
+ * Constructor: OpenLayers.WPSProcess
+ *
+ * Parameters:
+ * options - {Object} Object whose properties will be set on the instance.
+ *
+ * Avaliable options:
+ * client - {<OpenLayers.WPSClient>} Mandatory. Client that manages this
+ * process.
+ * server - {String} Mandatory. Local client identifier of this process's
+ * server.
+ * identifier - {String} Mandatory. Process identifier known to the server.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.executeCallbacks = [];
+ this.formats = {
+ 'application/wkt': new OpenLayers.Format.WKT(),
+ 'application/json': new OpenLayers.Format.GeoJSON()
+ };
+ },
+
+ /**
+ * Method: describe
+ * Makes the client issue a DescribeProcess request asynchronously.
+ *
+ * Parameters:
+ * options - {Object} Configuration for the method call
+ *
+ * Available options:
+ * callback - {Function} Callback to execute when the description is
+ * available. Will be called with the parsed description as argument.
+ * Optional.
+ * scope - {Object} The scope in which the callback will be executed.
+ * Default is the global object.
+ */
+ describe: function(options) {
+ options = options || {};
+ if (!this.description) {
+ this.client.describeProcess(this.server, this.identifier, function(description) {
+ if (!this.description) {
+ this.parseDescription(description);
+ }
+ if (options.callback) {
+ options.callback.call(options.scope, this.description);
+ }
+ }, this);
+ } else if (options.callback) {
+ var description = this.description;
+ window.setTimeout(function() {
+ options.callback.call(options.scope, description);
+ }, 0);
+ }
+ },
+
+ /**
+ * APIMethod: configure
+ * Configure the process, but do not execute it. Use this for processes
+ * that are chained as input of a different process by means of the
+ * <output> method.
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.WPSProcess>} this process.
+ *
+ * Available options:
+ * inputs - {Object} The inputs for the process, keyed by input identifier.
+ * For spatial data inputs, the value of an input is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ * callback - {Function} Callback to call when the configuration is
+ * complete. Optional.
+ * scope - {Object} Optional scope for the callback.
+ */
+ configure: function(options) {
+ this.describe({
+ callback: function() {
+ var description = this.description,
+ inputs = options.inputs,
+ input, i, ii;
+ for (i=0, ii=description.dataInputs.length; i<ii; ++i) {
+ input = description.dataInputs[i];
+ this.setInputData(input, inputs[input.identifier]);
+ }
+ if (options.callback) {
+ options.callback.call(options.scope);
+ }
+ },
+ scope: this
+ });
+ return this;
+ },
+
+ /**
+ * APIMethod: execute
+ * Configures and executes the process
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Available options:
+ * inputs - {Object} The inputs for the process, keyed by input identifier.
+ * For spatial data inputs, the value of an input is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ * output - {String} The identifier of the output to request and parse.
+ * Optional. If not provided, the first output will be requested.
+ * success - {Function} Callback to call when the process is complete.
+ * This function is called with an outputs object as argument, which
+ * will have a property with the identifier of the requested output
+ * (or 'result' if output was not configured). For processes that
+ * generate spatial output, the value will be an array of
+ * <OpenLayers.Feature.Vector> instances.
+ * scope - {Object} Optional scope for the success callback.
+ */
+ execute: function(options) {
+ this.configure({
+ inputs: options.inputs,
+ callback: function() {
+ var me = this;
+ //TODO For now we only deal with a single output
+ var outputIndex = this.getOutputIndex(
+ me.description.processOutputs, options.output
+ );
+ me.setResponseForm({outputIndex: outputIndex});
+ (function callback() {
+ OpenLayers.Util.removeItem(me.executeCallbacks, callback);
+ if (me.chained !== 0) {
+ // need to wait until chained processes have a
+ // description and configuration - see chainProcess
+ me.executeCallbacks.push(callback);
+ return;
+ }
+ // all chained processes are added as references now, so
+ // let's proceed.
+ OpenLayers.Request.POST({
+ url: me.client.servers[me.server].url,
+ data: new OpenLayers.Format.WPSExecute().write(me.description),
+ success: function(response) {
+ var output = me.description.processOutputs[outputIndex];
+ var mimeType = me.findMimeType(
+ output.complexOutput.supported.formats
+ );
+ //TODO For now we assume a spatial output
+ var features = me.formats[mimeType].read(response.responseText);
+ if (features instanceof OpenLayers.Feature.Vector) {
+ features = [features];
+ }
+ if (options.success) {
+ var outputs = {};
+ outputs[options.output || 'result'] = features;
+ options.success.call(options.scope, outputs);
+ }
+ },
+ scope: me
+ });
+ })();
+ },
+ scope: this
+ });
+ },
+
+ /**
+ * APIMethod: output
+ * Chain an output of a configured process (see <configure>) as input to
+ * another process.
+ *
+ * (code)
+ * intersect = client.getProcess('opengeo', 'JTS:intersection');
+ * intersect.configure({
+ * // ...
+ * });
+ * buffer = client.getProcess('opengeo', 'JTS:buffer');
+ * buffer.execute({
+ * inputs: {
+ * geom: intersect.output('result'), // <-- here we're chaining
+ * distance: 1
+ * },
+ * // ...
+ * });
+ * (end)
+ *
+ * Parameters:
+ * identifier - {String} Identifier of the output that we're chaining. If
+ * not provided, the first output will be used.
+ */
+ output: function(identifier) {
+ return new OpenLayers.WPSProcess.ChainLink({
+ process: this,
+ output: identifier
+ });
+ },
+
+ /**
+ * Method: parseDescription
+ * Parses the DescribeProcess response
+ *
+ * Parameters:
+ * description - {Object}
+ */
+ parseDescription: function(description) {
+ var server = this.client.servers[this.server];
+ this.description = new OpenLayers.Format.WPSDescribeProcess()
+ .read(server.processDescription[this.identifier])
+ .processDescriptions[this.identifier];
+ },
+
+ /**
+ * Method: setInputData
+ * Sets the data for a single input
+ *
+ * Parameters:
+ * input - {Object} An entry from the dataInputs array of the process
+ * description.
+ * data - {Mixed} For spatial data inputs, this is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ */
+ setInputData: function(input, data) {
+ // clear any previous data
+ delete input.data;
+ delete input.reference;
+ if (data instanceof OpenLayers.WPSProcess.ChainLink) {
+ ++this.chained;
+ input.reference = {
+ method: 'POST',
+ href: data.process.server === this.server ?
+ this.localWPS : this.client.servers[data.process.server].url
+ };
+ data.process.describe({
+ callback: function() {
+ --this.chained;
+ this.chainProcess(input, data);
+ },
+ scope: this
+ });
+ } else {
+ input.data = {};
+ var complexData = input.complexData;
+ if (complexData) {
+ var format = this.findMimeType(complexData.supported.formats);
+ input.data.complexData = {
+ mimeType: format,
+ value: this.formats[format].write(this.toFeatures(data))
+ };
+ } else {
+ input.data.literalData = {
+ value: data
+ };
+ }
+ }
+ },
+
+ /**
+ * Method: setResponseForm
+ * Sets the responseForm property of the <execute> payload.
+ *
+ * Parameters:
+ * options - {Object} See below.
+ *
+ * Available options:
+ * outputIndex - {Integer} The index of the output to use. Optional.
+ * supportedFormats - {Object} Object with supported mime types as key,
+ * and true as value for supported types. Optional.
+ */
+ setResponseForm: function(options) {
+ options = options || {};
+ var output = this.description.processOutputs[options.outputIndex || 0];
+ this.description.responseForm = {
+ rawDataOutput: {
+ identifier: output.identifier,
+ mimeType: this.findMimeType(output.complexOutput.supported.formats, options.supportedFormats)
+ }
+ };
+ },
+
+ /**
+ * Method: getOutputIndex
+ * Gets the index of a processOutput by its identifier
+ *
+ * Parameters:
+ * outputs - {Array} The processOutputs array to look at
+ * identifier - {String} The identifier of the output
+ *
+ * Returns
+ * {Integer} The index of the processOutput with the provided identifier
+ * in the outputs array.
+ */
+ getOutputIndex: function(outputs, identifier) {
+ var output;
+ if (identifier) {
+ for (var i=outputs.length-1; i>=0; --i) {
+ if (outputs[i].identifier === identifier) {
+ output = i;
+ break;
+ }
+ }
+ } else {
+ output = 0;
+ }
+ return output;
+ },
+
+ /**
+ * Method: chainProcess
+ * Sets a fully configured chained process as input for this process.
+ *
+ * Parameters:
+ * input - {Object} The dataInput that the chained process provides.
+ * chainLink - {<OpenLayers.WPSProcess.ChainLink>} The process to chain.
+ */
+ chainProcess: function(input, chainLink) {
+ var output = this.getOutputIndex(
+ chainLink.process.description.processOutputs, chainLink.output
+ );
+ input.reference.mimeType = this.findMimeType(
+ input.complexData.supported.formats,
+ chainLink.process.description.processOutputs[output].complexOutput.supported.formats
+ );
+ var formats = {};
+ formats[input.reference.mimeType] = true;
+ chainLink.process.setResponseForm({
+ outputIndex: output,
+ supportedFormats: formats
+ });
+ input.reference.body = chainLink.process.description;
+ while (this.executeCallbacks.length > 0) {
+ this.executeCallbacks[0]();
+ }
+ },
+
+ /**
+ * Method: toFeatures
+ * Converts spatial input into features so it can be processed by
+ * <OpenLayers.Format> instances.
+ *
+ * Parameters:
+ * source - {Mixed} An <OpenLayers.Geometry>, an
+ * <OpenLayers.Feature.Vector>, or an array of geometries or features
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ toFeatures: function(source) {
+ var isArray = OpenLayers.Util.isArray(source);
+ if (!isArray) {
+ source = [source];
+ }
+ var target = new Array(source.length),
+ current;
+ for (var i=0, ii=source.length; i<ii; ++i) {
+ current = source[i];
+ target[i] = current instanceof OpenLayers.Feature.Vector ?
+ current : new OpenLayers.Feature.Vector(current);
+ }
+ return isArray ? target : target[0];
+ },
+
+ /**
+ * Method: findMimeType
+ * Finds a supported mime type.
+ *
+ * Parameters:
+ * sourceFormats - {Object} An object literal with mime types as key and
+ * true as value for supported formats.
+ * targetFormats - {Object} Like <sourceFormats>, but optional to check for
+ * supported mime types on a different target than this process.
+ * Default is to check against this process's supported formats.
+ *
+ * Returns:
+ * {String} A supported mime type.
+ */
+ findMimeType: function(sourceFormats, targetFormats) {
+ targetFormats = targetFormats || this.formats;
+ for (var f in sourceFormats) {
+ if (f in targetFormats) {
+ return f;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.WPSProcess"
+
+});
+
+/**
+ * Class: OpenLayers.WPSProcess.ChainLink
+ * Type for chaining processes.
+ */
+OpenLayers.WPSProcess.ChainLink = OpenLayers.Class({
+
+ /**
+ * Property: process
+ * {<OpenLayers.WPSProcess>} The process to chain
+ */
+ process: null,
+
+ /**
+ * Property: output
+ * {String} The output identifier of the output we are going to use as
+ * input for another process.
+ */
+ output: null,
+
+ /**
+ * Constructor: OpenLayers.WPSProcess.ChainLink
+ *
+ * Parameters:
+ * options - {Object} Properties to set on the instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ CLASS_NAME: "OpenLayers.WPSProcess.ChainLink"
+
+});
+/* ======================================================================
+ OpenLayers/WPSClient.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/WPSProcess.js
+ * @requires OpenLayers/Format/WPSDescribeProcess.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.WPSClient
+ * High level API for interaction with Web Processing Services (WPS).
+ * An <OpenLayers.WPSClient> instance is used to create <OpenLayers.WPSProcess>
+ * instances for servers known to the WPSClient. The WPSClient also caches
+ * DescribeProcess responses to reduce the number of requests sent to servers
+ * when processes are created.
+ */
+OpenLayers.WPSClient = OpenLayers.Class({
+
+ /**
+ * Property: servers
+ * {Object} Service metadata, keyed by a local identifier.
+ *
+ * Properties:
+ * url - {String} the url of the server
+ * version - {String} WPS version of the server
+ * processDescription - {Object} Cache of raw DescribeProcess
+ * responses, keyed by process identifier.
+ */
+ servers: null,
+
+ /**
+ * Property: version
+ * {String} The default WPS version to use if none is configured. Default
+ * is '1.0.0'.
+ */
+ version: '1.0.0',
+
+ /**
+ * Property: lazy
+ * {Boolean} Should the DescribeProcess be deferred until a process is
+ * fully configured? Default is false.
+ */
+ lazy: false,
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>}
+ *
+ * Supported event types:
+ * describeprocess - Fires when the process description is available.
+ * Listeners receive an object with a 'raw' property holding the raw
+ * DescribeProcess response, and an 'identifier' property holding the
+ * process identifier of the described process.
+ */
+ events: null,
+
+ /**
+ * Constructor: OpenLayers.WPSClient
+ *
+ * Parameters:
+ * options - {Object} Object whose properties will be set on the instance.
+ *
+ * Avaliable options:
+ * servers - {Object} Mandatory. Service metadata, keyed by a local
+ * identifier. Can either be a string with the service url or an
+ * object literal with additional metadata:
+ *
+ * (code)
+ * servers: {
+ * local: '/geoserver/wps'
+ * }, {
+ * opengeo: {
+ * url: 'http://demo.opengeo.org/geoserver/wps',
+ * version: '1.0.0'
+ * }
+ * }
+ * (end)
+ *
+ * lazy - {Boolean} Optional. Set to true if DescribeProcess should not be
+ * requested until a process is fully configured. Default is false.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.events = new OpenLayers.Events(this);
+ this.servers = {};
+ for (var s in options.servers) {
+ this.servers[s] = typeof options.servers[s] == 'string' ? {
+ url: options.servers[s],
+ version: this.version,
+ processDescription: {}
+ } : options.servers[s];
+ }
+ },
+
+ /**
+ * APIMethod: execute
+ * Shortcut to execute a process with a single function call. This is
+ * equivalent to using <getProcess> and then calling execute on the
+ * process.
+ *
+ * Parameters:
+ * options - {Object} Options for the execute operation.
+ *
+ * Available options:
+ * server - {String} Mandatory. One of the local identifiers of the
+ * configured servers.
+ * process - {String} Mandatory. A process identifier known to the
+ * server.
+ * inputs - {Object} The inputs for the process, keyed by input identifier.
+ * For spatial data inputs, the value of an input is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ * output - {String} The identifier of an output to parse. Optional. If not
+ * provided, the first output will be parsed.
+ * success - {Function} Callback to call when the process is complete.
+ * This function is called with an outputs object as argument, which
+ * will have a property with the identifier of the requested output
+ * (e.g. 'result'). For processes that generate spatial output, the
+ * value will either be a single <OpenLayers.Feature.Vector> or an
+ * array of features.
+ * scope - {Object} Optional scope for the success callback.
+ */
+ execute: function(options) {
+ var process = this.getProcess(options.server, options.process);
+ process.execute({
+ inputs: options.inputs,
+ success: options.success,
+ scope: options.scope
+ });
+ },
+
+ /**
+ * APIMethod: getProcess
+ * Creates an <OpenLayers.WPSProcess>.
+ *
+ * Parameters:
+ * serverID - {String} Local identifier from the servers that this instance
+ * was constructed with.
+ * processID - {String} Process identifier known to the server.
+ *
+ * Returns:
+ * {<OpenLayers.WPSProcess>}
+ */
+ getProcess: function(serverID, processID) {
+ var process = new OpenLayers.WPSProcess({
+ client: this,
+ server: serverID,
+ identifier: processID
+ });
+ if (!this.lazy) {
+ process.describe();
+ }
+ return process;
+ },
+
+ /**
+ * Method: describeProcess
+ *
+ * Parameters:
+ * serverID - {String} Identifier of the server
+ * processID - {String} Identifier of the requested process
+ * callback - {Function} Callback to call when the description is available
+ * scope - {Object} Optional execution scope for the callback function
+ */
+ describeProcess: function(serverID, processID, callback, scope) {
+ var server = this.servers[serverID];
+ if (!server.processDescription[processID]) {
+ if (!(processID in server.processDescription)) {
+ // set to null so we know a describeFeature request is pending
+ server.processDescription[processID] = null;
+ OpenLayers.Request.GET({
+ url: server.url,
+ params: {
+ SERVICE: 'WPS',
+ VERSION: server.version,
+ REQUEST: 'DescribeProcess',
+ IDENTIFIER: processID
+ },
+ success: function(response) {
+ server.processDescription[processID] = response.responseText;
+ this.events.triggerEvent('describeprocess', {
+ identifier: processID,
+ raw: response.responseText
+ });
+ },
+ scope: this
+ });
+ } else {
+ // pending request
+ this.events.register('describeprocess', this, function describe(evt) {
+ if (evt.identifier === processID) {
+ this.events.unregister('describeprocess', this, describe);
+ callback.call(scope, evt.raw);
+ }
+ });
+ }
+ } else {
+ window.setTimeout(function() {
+ callback.call(scope, server.processDescription[processID]);
+ }, 0);
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.events.destroy();
+ this.events = null;
+ this.servers = null;
+ },
+
+ CLASS_NAME: 'OpenLayers.WPSClient'
+
+});
+/* ======================================================================
+ OpenLayers/Format/CSWGetRecords/v2_0_2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/CSWGetRecords.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ * @requires OpenLayers/Format/Filter/v1_1_0.js
+ * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetRecords.v2_0_2
+ * A format for creating CSWGetRecords v2.0.2 transactions.
+ * Create a new instance with the
+ * <OpenLayers.Format.CSWGetRecords.v2_0_2> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.CSWGetRecords.v2_0_2 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ csw: "http://www.opengis.net/cat/csw/2.0.2",
+ dc: "http://purl.org/dc/elements/1.1/",
+ dct: "http://purl.org/dc/terms/",
+ gmd: "http://www.isotc211.org/2005/gmd",
+ geonet: "http://www.fao.org/geonetwork",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default prefix (used by Format.XML).
+ */
+ defaultPrefix: "csw",
+
+ /**
+ * Property: version
+ * {String} CSW version number.
+ */
+ version: "2.0.2",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/cat/csw/2.0.2
+ * http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd
+ */
+ schemaLocation: "http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",
+
+ /**
+ * APIProperty: requestId
+ * {String} Value of the requestId attribute of the GetRecords element.
+ */
+ requestId: null,
+
+ /**
+ * APIProperty: resultType
+ * {String} Value of the resultType attribute of the GetRecords element,
+ * specifies the result type in the GetRecords response, "hits" is
+ * the default.
+ */
+ resultType: null,
+
+ /**
+ * APIProperty: outputFormat
+ * {String} Value of the outputFormat attribute of the GetRecords element,
+ * specifies the format of the GetRecords response,
+ * "application/xml" is the default.
+ */
+ outputFormat: null,
+
+ /**
+ * APIProperty: outputSchema
+ * {String} Value of the outputSchema attribute of the GetRecords element,
+ * specifies the schema of the GetRecords response.
+ */
+ outputSchema: null,
+
+ /**
+ * APIProperty: startPosition
+ * {String} Value of the startPosition attribute of the GetRecords element,
+ * specifies the start position (offset+1) for the GetRecords response,
+ * 1 is the default.
+ */
+ startPosition: null,
+
+ /**
+ * APIProperty: maxRecords
+ * {String} Value of the maxRecords attribute of the GetRecords element,
+ * specifies the maximum number of records in the GetRecords response,
+ * 10 is the default.
+ */
+ maxRecords: null,
+
+ /**
+ * APIProperty: DistributedSearch
+ * {String} Value of the csw:DistributedSearch element, used when writing
+ * a csw:GetRecords document.
+ */
+ DistributedSearch: null,
+
+ /**
+ * APIProperty: ResponseHandler
+ * {Array({String})} Values of the csw:ResponseHandler elements, used when
+ * writting a csw:GetRecords document.
+ */
+ ResponseHandler: null,
+
+ /**
+ * APIProperty: Query
+ * {String} Value of the csw:Query element, used when writing a csw:GetRecords
+ * document.
+ */
+ Query: null,
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.CSWGetRecords.v2_0_2
+ * A class for parsing and generating CSWGetRecords v2.0.2 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties (documented as class properties):
+ * - requestId
+ * - resultType
+ * - outputFormat
+ * - outputSchema
+ * - startPosition
+ * - maxRecords
+ * - DistributedSearch
+ * - ResponseHandler
+ * - Query
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Parse the response from a GetRecords request.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ this.readNode(data, obj);
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "csw": {
+ "GetRecordsResponse": function(node, obj) {
+ obj.records = [];
+ this.readChildNodes(node, obj);
+ var version = this.getAttributeNS(node, "", 'version');
+ if (version != "") {
+ obj.version = version;
+ }
+ },
+ "RequestId": function(node, obj) {
+ obj.RequestId = this.getChildValue(node);
+ },
+ "SearchStatus": function(node, obj) {
+ obj.SearchStatus = {};
+ var timestamp = this.getAttributeNS(node, "", 'timestamp');
+ if (timestamp != "") {
+ obj.SearchStatus.timestamp = timestamp;
+ }
+ },
+ "SearchResults": function(node, obj) {
+ this.readChildNodes(node, obj);
+ var attrs = node.attributes;
+ var SearchResults = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ if ((attrs[i].name == "numberOfRecordsMatched") ||
+ (attrs[i].name == "numberOfRecordsReturned") ||
+ (attrs[i].name == "nextRecord")) {
+ SearchResults[attrs[i].name] = parseInt(attrs[i].nodeValue);
+ } else {
+ SearchResults[attrs[i].name] = attrs[i].nodeValue;
+ }
+ }
+ obj.SearchResults = SearchResults;
+ },
+ "SummaryRecord": function(node, obj) {
+ var record = {type: "SummaryRecord"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "BriefRecord": function(node, obj) {
+ var record = {type: "BriefRecord"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "DCMIRecord": function(node, obj) {
+ var record = {type: "DCMIRecord"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "Record": function(node, obj) {
+ var record = {type: "Record"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "*": function(node, obj) {
+ var name = node.localName || node.nodeName.split(":").pop();
+ obj[name] = this.getChildValue(node);
+ }
+ },
+ "geonet": {
+ "info": function(node, obj) {
+ var gninfo = {};
+ this.readChildNodes(node, gninfo);
+ obj.gninfo = gninfo;
+ }
+ },
+ "dc": {
+ // audience, contributor, coverage, creator, date, description, format,
+ // identifier, language, provenance, publisher, relation, rights,
+ // rightsHolder, source, subject, title, type, URI
+ "*": function(node, obj) {
+ var name = node.localName || node.nodeName.split(":").pop();
+ if (!(OpenLayers.Util.isArray(obj[name]))) {
+ obj[name] = [];
+ }
+ var dc_element = {};
+ var attrs = node.attributes;
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ dc_element[attrs[i].name] = attrs[i].nodeValue;
+ }
+ dc_element.value = this.getChildValue(node);
+ if (dc_element.value != "") {
+ obj[name].push(dc_element);
+ }
+ }
+ },
+ "dct": {
+ // abstract, modified, spatial
+ "*": function(node, obj) {
+ var name = node.localName || node.nodeName.split(":").pop();
+ if (!(OpenLayers.Util.isArray(obj[name]))) {
+ obj[name] = [];
+ }
+ obj[name].push(this.getChildValue(node));
+ }
+ },
+ "ows": OpenLayers.Util.applyDefaults({
+ "BoundingBox": function(node, obj) {
+ if (obj.bounds) {
+ obj.BoundingBox = [{crs: obj.projection, value:
+ [
+ obj.bounds.left,
+ obj.bounds.bottom,
+ obj.bounds.right,
+ obj.bounds.top
+ ]
+ }];
+ delete obj.projection;
+ delete obj.bounds;
+ }
+ OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"]["BoundingBox"].apply(
+ this, arguments);
+ }
+ }, OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"])
+ },
+
+ /**
+ * Method: write
+ * Given an configuration js object, write a CSWGetRecords request.
+ *
+ * Parameters:
+ * options - {Object} A object mapping the request.
+ *
+ * Returns:
+ * {String} A serialized CSWGetRecords request.
+ */
+ write: function(options) {
+ var node = this.writeNode("csw:GetRecords", options);
+ node.setAttribute("xmlns:gmd", this.namespaces.gmd);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "csw": {
+ "GetRecords": function(options) {
+ if (!options) {
+ options = {};
+ }
+ var node = this.createElementNSPlus("csw:GetRecords", {
+ attributes: {
+ service: "CSW",
+ version: this.version,
+ requestId: options.requestId || this.requestId,
+ resultType: options.resultType || this.resultType,
+ outputFormat: options.outputFormat || this.outputFormat,
+ outputSchema: options.outputSchema || this.outputSchema,
+ startPosition: options.startPosition || this.startPosition,
+ maxRecords: options.maxRecords || this.maxRecords
+ }
+ });
+ if (options.DistributedSearch || this.DistributedSearch) {
+ this.writeNode(
+ "csw:DistributedSearch",
+ options.DistributedSearch || this.DistributedSearch,
+ node
+ );
+ }
+ var ResponseHandler = options.ResponseHandler || this.ResponseHandler;
+ if (OpenLayers.Util.isArray(ResponseHandler) && ResponseHandler.length > 0) {
+ // ResponseHandler must be a non-empty array
+ for(var i=0, len=ResponseHandler.length; i<len; i++) {
+ this.writeNode(
+ "csw:ResponseHandler",
+ ResponseHandler[i],
+ node
+ );
+ }
+ }
+ this.writeNode("Query", options.Query || this.Query, node);
+ return node;
+ },
+ "DistributedSearch": function(options) {
+ var node = this.createElementNSPlus("csw:DistributedSearch", {
+ attributes: {
+ hopCount: options.hopCount
+ }
+ });
+ return node;
+ },
+ "ResponseHandler": function(options) {
+ var node = this.createElementNSPlus("csw:ResponseHandler", {
+ value: options.value
+ });
+ return node;
+ },
+ "Query": function(options) {
+ if (!options) {
+ options = {};
+ }
+ var node = this.createElementNSPlus("csw:Query", {
+ attributes: {
+ typeNames: options.typeNames || "csw:Record"
+ }
+ });
+ var ElementName = options.ElementName;
+ if (OpenLayers.Util.isArray(ElementName) && ElementName.length > 0) {
+ // ElementName must be a non-empty array
+ for(var i=0, len=ElementName.length; i<len; i++) {
+ this.writeNode(
+ "csw:ElementName",
+ ElementName[i],
+ node
+ );
+ }
+ } else {
+ this.writeNode(
+ "csw:ElementSetName",
+ options.ElementSetName || {value: 'summary'},
+ node
+ );
+ }
+ if (options.Constraint) {
+ this.writeNode(
+ "csw:Constraint",
+ options.Constraint,
+ node
+ );
+ }
+ if (options.SortBy) {
+ this.writeNode(
+ "ogc:SortBy",
+ options.SortBy,
+ node
+ );
+ }
+ return node;
+ },
+ "ElementName": function(options) {
+ var node = this.createElementNSPlus("csw:ElementName", {
+ value: options.value
+ });
+ return node;
+ },
+ "ElementSetName": function(options) {
+ var node = this.createElementNSPlus("csw:ElementSetName", {
+ attributes: {
+ typeNames: options.typeNames
+ },
+ value: options.value
+ });
+ return node;
+ },
+ "Constraint": function(options) {
+ var node = this.createElementNSPlus("csw:Constraint", {
+ attributes: {
+ version: options.version
+ }
+ });
+ if (options.Filter) {
+ var format = new OpenLayers.Format.Filter({
+ version: options.version
+ });
+ node.appendChild(format.write(options.Filter));
+ } else if (options.CqlText) {
+ var child = this.createElementNSPlus("CqlText", {
+ value: options.CqlText.value
+ });
+ node.appendChild(child);
+ }
+ return node;
+ }
+ },
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.CSWGetRecords.v2_0_2"
+});
+/* ======================================================================
+ Rico/license.js
+ ====================================================================== */
+
+/**
+ * @license Apache 2
+ *
+ * Contains portions of Rico <http://openrico.org/>
+ *
+ * Copyright 2005 Sabre Airline Solutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you
+ * may not use this file except in compliance with the License. You
+ * may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+/* ======================================================================
+ OpenLayers/Marker/Box.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Marker.js
+ */
+
+/**
+ * Class: OpenLayers.Marker.Box
+ *
+ * Inherits from:
+ * - <OpenLayers.Marker>
+ */
+OpenLayers.Marker.Box = OpenLayers.Class(OpenLayers.Marker, {
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>}
+ */
+ bounds: null,
+
+ /**
+ * Property: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * Constructor: OpenLayers.Marker.Box
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * borderColor - {String}
+ * borderWidth - {int}
+ */
+ initialize: function(bounds, borderColor, borderWidth) {
+ this.bounds = bounds;
+ this.div = OpenLayers.Util.createDiv();
+ this.div.style.overflow = 'hidden';
+ this.events = new OpenLayers.Events(this, this.div);
+ this.setBorder(borderColor, borderWidth);
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+
+ this.bounds = null;
+ this.div = null;
+
+ OpenLayers.Marker.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setBorder
+ * Allow the user to change the box's color and border width
+ *
+ * Parameters:
+ * color - {String} Default is "red"
+ * width - {int} Default is 2
+ */
+ setBorder: function (color, width) {
+ if (!color) {
+ color = "red";
+ }
+ if (!width) {
+ width = 2;
+ }
+ this.div.style.border = width + "px solid " + color;
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * sz - {<OpenLayers.Size>}
+ *
+ * Returns:
+ * {DOMElement} A new DOM Image with this marker's icon set at the
+ * location passed-in
+ */
+ draw: function(px, sz) {
+ OpenLayers.Util.modifyDOMElement(this.div, null, px, sz);
+ return this.div;
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Rreturn:
+ * {Boolean} Whether or not the marker is currently visible on screen.
+ */
+ onScreen:function() {
+ var onScreen = false;
+ if (this.map) {
+ var screenBounds = this.map.getExtent();
+ onScreen = screenBounds.containsBounds(this.bounds, true, true);
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: display
+ * Hide or show the icon
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ this.div.style.display = (display) ? "" : "none";
+ },
+
+ CLASS_NAME: "OpenLayers.Marker.Box"
+});
+
+/* ======================================================================
+ OpenLayers/Format/Text.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Text
+ * Read Text format. Create a new instance with the <OpenLayers.Format.Text>
+ * constructor. This reads text which is formatted like CSV text, using
+ * tabs as the seperator by default. It provides parsing of data originally
+ * used in the MapViewerService, described on the wiki. This Format is used
+ * by the <OpenLayers.Layer.Text> class.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.Text = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: defaultStyle
+ * defaultStyle allows one to control the default styling of the features.
+ * It should be a symbolizer hash. By default, this is set to match the
+ * Layer.Text behavior, which is to use the default OpenLayers Icon.
+ */
+ defaultStyle: null,
+
+ /**
+ * APIProperty: extractStyles
+ * set to true to extract styles from the TSV files, using information
+ * from the image or icon, iconSize and iconOffset fields. This will result
+ * in features with a symbolizer (style) property set, using the
+ * default symbolizer specified in <defaultStyle>. Set to false if you
+ * wish to use a styleMap or OpenLayers.Style options to style your
+ * layer instead.
+ */
+ extractStyles: true,
+
+ /**
+ * Constructor: OpenLayers.Format.Text
+ * Create a new parser for TSV Text.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ options = options || {};
+
+ if(options.extractStyles !== false) {
+ options.defaultStyle = {
+ 'externalGraphic': OpenLayers.Util.getImageLocation("marker.png"),
+ 'graphicWidth': 21,
+ 'graphicHeight': 25,
+ 'graphicXOffset': -10.5,
+ 'graphicYOffset': -12.5
+ };
+ }
+
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a Tab Seperated Values text string.
+ *
+ * Parameters:
+ * text - {String}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(text) {
+ var lines = text.split('\n');
+ var columns;
+ var features = [];
+ // length - 1 to allow for trailing new line
+ for (var lcv = 0; lcv < (lines.length - 1); lcv++) {
+ var currLine = lines[lcv].replace(/^\s*/,'').replace(/\s*$/,'');
+
+ if (currLine.charAt(0) != '#') { /* not a comment */
+
+ if (!columns) {
+ //First line is columns
+ columns = currLine.split('\t');
+ } else {
+ var vals = currLine.split('\t');
+ var geometry = new OpenLayers.Geometry.Point(0,0);
+ var attributes = {};
+ var style = this.defaultStyle ?
+ OpenLayers.Util.applyDefaults({}, this.defaultStyle) :
+ null;
+ var icon, iconSize, iconOffset, overflow;
+ var set = false;
+ for (var valIndex = 0; valIndex < vals.length; valIndex++) {
+ if (vals[valIndex]) {
+ if (columns[valIndex] == 'point') {
+ var coords = vals[valIndex].split(',');
+ geometry.y = parseFloat(coords[0]);
+ geometry.x = parseFloat(coords[1]);
+ set = true;
+ } else if (columns[valIndex] == 'lat') {
+ geometry.y = parseFloat(vals[valIndex]);
+ set = true;
+ } else if (columns[valIndex] == 'lon') {
+ geometry.x = parseFloat(vals[valIndex]);
+ set = true;
+ } else if (columns[valIndex] == 'title')
+ attributes['title'] = vals[valIndex];
+ else if (columns[valIndex] == 'image' ||
+ columns[valIndex] == 'icon' && style) {
+ style['externalGraphic'] = vals[valIndex];
+ } else if (columns[valIndex] == 'iconSize' && style) {
+ var size = vals[valIndex].split(',');
+ style['graphicWidth'] = parseFloat(size[0]);
+ style['graphicHeight'] = parseFloat(size[1]);
+ } else if (columns[valIndex] == 'iconOffset' && style) {
+ var offset = vals[valIndex].split(',');
+ style['graphicXOffset'] = parseFloat(offset[0]);
+ style['graphicYOffset'] = parseFloat(offset[1]);
+ } else if (columns[valIndex] == 'description') {
+ attributes['description'] = vals[valIndex];
+ } else if (columns[valIndex] == 'overflow') {
+ attributes['overflow'] = vals[valIndex];
+ } else {
+ // For StyleMap filtering, allow additional
+ // columns to be stored as attributes.
+ attributes[columns[valIndex]] = vals[valIndex];
+ }
+ }
+ }
+ if (set) {
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes, style);
+ features.push(feature);
+ }
+ }
+ }
+ }
+ return features;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Text"
+});
+/* ======================================================================
+ OpenLayers/Layer/Text.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Format/Text.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Text
+ * This layer creates markers given data in a text file. The <location>
+ * property of the layer (specified as a property of the options argument
+ * in the <OpenLayers.Layer.Text> constructor) points to a tab delimited
+ * file with data used to create markers.
+ *
+ * The first row of the data file should be a header line with the column names
+ * of the data. Each column should be delimited by a tab space. The
+ * possible columns are:
+ * - *point* lat,lon of the point where a marker is to be placed
+ * - *lat* Latitude of the point where a marker is to be placed
+ * - *lon* Longitude of the point where a marker is to be placed
+ * - *icon* or *image* URL of marker icon to use.
+ * - *iconSize* Size of Icon to use.
+ * - *iconOffset* Where the top-left corner of the icon is to be placed
+ * relative to the latitude and longitude of the point.
+ * - *title* The text of the 'title' is placed inside an 'h2' marker
+ * inside a popup, which opens when the marker is clicked.
+ * - *description* The text of the 'description' is placed below the h2
+ * in the popup. this can be plain text or HTML.
+ *
+ * Example text file:
+ * (code)
+ * lat lon title description iconSize iconOffset icon
+ * 10 20 title description 21,25 -10,-25 http://www.openlayers.org/dev/img/marker.png
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Text = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+ /**
+ * APIProperty: location
+ * {String} URL of text file. Must be specified in the "options" argument
+ * of the constructor. Can not be changed once passed in.
+ */
+ location:null,
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature>)}
+ */
+ features: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Hash of options which should be passed to the format when it is
+ * created. Must be passed in the constructor.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: selectedFeature
+ * {<OpenLayers.Feature>}
+ */
+ selectedFeature: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Text
+ * Create a text layer.
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Object with properties to be set on the layer.
+ * Must include <location> property.
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
+ this.features = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ // Warning: Layer.Markers.destroy() must be called prior to calling
+ // clearFeatures() here, otherwise we leak memory. Indeed, if
+ // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+ // able to remove the marker image elements from the layer's div since
+ // the markers will have been destroyed by clearFeatures().
+ OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+ this.clearFeatures();
+ this.features = null;
+ },
+
+ /**
+ * Method: loadText
+ * Start the load of the Text data. Don't do this when we first add the layer,
+ * since we may not be visible at any point, and it would therefore be a waste.
+ */
+ loadText: function() {
+ if (!this.loaded) {
+ if (this.location != null) {
+
+ var onFail = function(e) {
+ this.events.triggerEvent("loadend");
+ };
+
+ this.events.triggerEvent("loadstart");
+ OpenLayers.Request.GET({
+ url: this.location,
+ success: this.parseData,
+ failure: onFail,
+ scope: this
+ });
+ this.loaded = true;
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * If layer is visible and Text has not been loaded, load Text.
+ *
+ * Parameters:
+ * bounds - {Object}
+ * zoomChanged - {Object}
+ * minor - {Object}
+ */
+ moveTo:function(bounds, zoomChanged, minor) {
+ OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+ if(this.visibility && !this.loaded){
+ this.loadText();
+ }
+ },
+
+ /**
+ * Method: parseData
+ *
+ * Parameters:
+ * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>}
+ */
+ parseData: function(ajaxRequest) {
+ var text = ajaxRequest.responseText;
+
+ var options = {};
+
+ OpenLayers.Util.extend(options, this.formatOptions);
+
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ var parser = new OpenLayers.Format.Text(options);
+ var features = parser.read(text);
+ for (var i=0, len=features.length; i<len; i++) {
+ var data = {};
+ var feature = features[i];
+ var location;
+ var iconSize, iconOffset;
+
+ location = new OpenLayers.LonLat(feature.geometry.x,
+ feature.geometry.y);
+
+ if (feature.style.graphicWidth
+ && feature.style.graphicHeight) {
+ iconSize = new OpenLayers.Size(
+ feature.style.graphicWidth,
+ feature.style.graphicHeight);
+ }
+
+ // FIXME: At the moment, we only use this if we have an
+ // externalGraphic, because icon has no setOffset API Method.
+ /**
+ * FIXME FIRST!!
+ * The Text format does all sorts of parseFloating
+ * The result of a parseFloat for a bogus string is NaN. That
+ * means the three possible values here are undefined, NaN, or a
+ * number. The previous check was an identity check for null. This
+ * means it was failing for all undefined or NaN. A slightly better
+ * check is for undefined. An even better check is to see if the
+ * value is a number (see #1441).
+ */
+ if (feature.style.graphicXOffset !== undefined
+ && feature.style.graphicYOffset !== undefined) {
+ iconOffset = new OpenLayers.Pixel(
+ feature.style.graphicXOffset,
+ feature.style.graphicYOffset);
+ }
+
+ if (feature.style.externalGraphic != null) {
+ data.icon = new OpenLayers.Icon(feature.style.externalGraphic,
+ iconSize,
+ iconOffset);
+ } else {
+ data.icon = OpenLayers.Marker.defaultIcon();
+
+ //allows for the case where the image url is not
+ // specified but the size is. use a default icon
+ // but change the size
+ if (iconSize != null) {
+ data.icon.setSize(iconSize);
+ }
+ }
+
+ if ((feature.attributes.title != null)
+ && (feature.attributes.description != null)) {
+ data['popupContentHTML'] =
+ '<h2>'+feature.attributes.title+'</h2>' +
+ '<p>'+feature.attributes.description+'</p>';
+ }
+
+ data['overflow'] = feature.attributes.overflow || "auto";
+
+ var markerFeature = new OpenLayers.Feature(this, location, data);
+ this.features.push(markerFeature);
+ var marker = markerFeature.createMarker();
+ if ((feature.attributes.title != null)
+ && (feature.attributes.description != null)) {
+ marker.events.register('click', markerFeature, this.markerClick);
+ }
+ this.addMarker(marker);
+ }
+ this.events.triggerEvent("loadend");
+ },
+
+ /**
+ * Property: markerClick
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Context:
+ * - {<OpenLayers.Feature>}
+ */
+ markerClick: function(evt) {
+ var sameMarkerClicked = (this == this.layer.selectedFeature);
+ this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+ for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+ this.layer.map.removePopup(this.layer.map.popups[i]);
+ }
+ if (!sameMarkerClicked) {
+ this.layer.map.addPopup(this.createPopup());
+ }
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: clearFeatures
+ */
+ clearFeatures: function() {
+ if (this.features != null) {
+ while(this.features.length > 0) {
+ var feature = this.features[0];
+ OpenLayers.Util.removeItem(this.features, feature);
+ feature.destroy();
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Text"
+});
+/* ======================================================================
+ OpenLayers/Handler/RegularPolygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.RegularPolygon
+ * Handler to draw a regular polygon on the map. Polygon is displayed on mouse
+ * down, moves or is modified on mouse move, and is finished on mouse up.
+ * The handler triggers callbacks for 'done' and 'cancel'. Create a new
+ * instance with the <OpenLayers.Handler.RegularPolygon> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Drag>
+ */
+OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
+
+ /**
+ * APIProperty: sides
+ * {Integer} Number of sides for the regular polygon. Needs to be greater
+ * than 2. Defaults to 4.
+ */
+ sides: 4,
+
+ /**
+ * APIProperty: radius
+ * {Float} Optional radius in map units of the regular polygon. If this is
+ * set to some non-zero value, a polygon with a fixed radius will be
+ * drawn and dragged with mose movements. If this property is not
+ * set, dragging changes the radius of the polygon. Set to null by
+ * default.
+ */
+ radius: null,
+
+ /**
+ * APIProperty: snapAngle
+ * {Float} If set to a non-zero value, the handler will snap the polygon
+ * rotation to multiples of the snapAngle. Value is an angle measured
+ * in degrees counterclockwise from the positive x-axis.
+ */
+ snapAngle: null,
+
+ /**
+ * APIProperty: snapToggle
+ * {String} If set, snapToggle is checked on mouse events and will set
+ * the snap mode to the opposite of what it currently is. To disallow
+ * toggling between snap and non-snap mode, set freehandToggle to
+ * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and
+ * 'altKey'. Snap mode is only possible if this.snapAngle is set to a
+ * non-zero value.
+ */
+ snapToggle: 'shiftKey',
+
+ /**
+ * Property: layerOptions
+ * {Object} Any optional properties to be set on the sketch layer.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Leave the feature rendered until clear is called. Default
+ * is false. If set to true, the feature remains rendered until
+ * clear is called, typically by deactivating the handler or starting
+ * another drawing.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: irregular
+ * {Boolean} Draw an irregular polygon instead of a regular polygon.
+ * Default is false. If true, the initial mouse down will represent
+ * one corner of the polygon bounds and with each mouse movement, the
+ * polygon will be stretched so the opposite corner of its bounds
+ * follows the mouse position. This property takes precedence over
+ * the radius property. If set to true, the radius property will
+ * be ignored.
+ */
+ irregular: false,
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Property: angle
+ * {Float} The angle from the origin (mouse down) to the current mouse
+ * position, in radians. This is measured counterclockwise from the
+ * positive x-axis.
+ */
+ angle: null,
+
+ /**
+ * Property: fixedRadius
+ * {Boolean} The polygon has a fixed radius. True if a radius is set before
+ * drawing begins. False otherwise.
+ */
+ fixedRadius: false,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
+ */
+ feature: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+ */
+ layer: null,
+
+ /**
+ * Property: origin
+ * {<OpenLayers.Geometry.Point>} Location of the first mouse down
+ */
+ origin: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.RegularPolygon
+ * Create a new regular polygon handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An object with properties to be set on the handler.
+ * If the options.sides property is not specified, the number of sides
+ * will default to 4.
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * done - Called when the sketch drawing is finished. The callback will
+ * recieve a single argument, the sketch geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+ initialize: function(control, callbacks, options) {
+ if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
+ this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+ }
+
+ OpenLayers.Handler.Drag.prototype.initialize.apply(this,
+ [control, callbacks, options]);
+ this.options = (options) ? options : {};
+ },
+
+ /**
+ * APIMethod: setOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ */
+ setOptions: function (newOptions) {
+ OpenLayers.Util.extend(this.options, newOptions);
+ OpenLayers.Util.extend(this, newOptions);
+ },
+
+ /**
+ * APIMethod: activate
+ * Turn on the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.Drag.prototype.activate.apply(this, arguments)) {
+ // create temporary vector layer for rendering geometry sketch
+ var options = OpenLayers.Util.extend({
+ displayInLayerSwitcher: false,
+ // indicate that the temp vector layer will never be out of range
+ // without this, resolution properties must be specified at the
+ // map-level for this temporary layer to init its resolutions
+ // correctly
+ calculateInRange: OpenLayers.Function.True,
+ wrapDateLine: this.citeCompliant
+ }, this.layerOptions);
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+ this.map.addLayer(this.layer);
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
+ // call the cancel callback if mid-drawing
+ if(this.dragging) {
+ this.cancel();
+ }
+ // If a layer's map property is set to null, it means that that
+ // layer isn't added to the map. Since we ourself added the layer
+ // to the map in activate(), we can assume that if this.layer.map
+ // is null it means that the layer has been destroyed (as a result
+ // of map.destroy() for example.
+ if (this.layer.map != null) {
+ this.layer.destroy(false);
+ if (this.feature) {
+ this.feature.destroy();
+ }
+ }
+ this.layer = null;
+ this.feature = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: down
+ * Start drawing a new feature
+ *
+ * Parameters:
+ * evt - {Event} The drag start event
+ */
+ down: function(evt) {
+ this.fixedRadius = !!(this.radius);
+ var maploc = this.layer.getLonLatFromViewPortPx(evt.xy);
+ this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+ // create the new polygon
+ if(!this.fixedRadius || this.irregular) {
+ // smallest radius should not be less one pixel in map units
+ // VML doesn't behave well with smaller
+ this.radius = this.map.getResolution();
+ }
+ if(this.persist) {
+ this.clear();
+ }
+ this.feature = new OpenLayers.Feature.Vector();
+ this.createGeometry();
+ this.callback("create", [this.origin, this.feature]);
+ this.layer.addFeatures([this.feature], {silent: true});
+ this.layer.drawFeature(this.feature, this.style);
+ },
+
+ /**
+ * Method: move
+ * Respond to drag move events
+ *
+ * Parameters:
+ * evt - {Evt} The move event
+ */
+ move: function(evt) {
+ var maploc = this.layer.getLonLatFromViewPortPx(evt.xy);
+ var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+ if(this.irregular) {
+ var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
+ this.radius = Math.max(this.map.getResolution() / 2, ry);
+ } else if(this.fixedRadius) {
+ this.origin = point;
+ } else {
+ this.calculateAngle(point, evt);
+ this.radius = Math.max(this.map.getResolution() / 2,
+ point.distanceTo(this.origin));
+ }
+ this.modifyGeometry();
+ if(this.irregular) {
+ var dx = point.x - this.origin.x;
+ var dy = point.y - this.origin.y;
+ var ratio;
+ if(dy == 0) {
+ ratio = dx / (this.radius * Math.sqrt(2));
+ } else {
+ ratio = dx / dy;
+ }
+ this.feature.geometry.resize(1, this.origin, ratio);
+ this.feature.geometry.move(dx / 2, dy / 2);
+ }
+ this.layer.drawFeature(this.feature, this.style);
+ },
+
+ /**
+ * Method: up
+ * Finish drawing the feature
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ this.finalize();
+ // the mouseup method of superclass doesn't call the
+ // "done" callback if there's been no move between
+ // down and up
+ if (this.start == this.last) {
+ this.callback("done", [evt.xy]);
+ }
+ },
+
+ /**
+ * Method: out
+ * Finish drawing the feature.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ this.finalize();
+ },
+
+ /**
+ * Method: createGeometry
+ * Create the new polygon geometry. This is called at the start of the
+ * drag and at any point during the drag if the number of sides
+ * changes.
+ */
+ createGeometry: function() {
+ this.angle = Math.PI * ((1/this.sides) - (1/2));
+ if(this.snapAngle) {
+ this.angle += this.snapAngle * (Math.PI / 180);
+ }
+ this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+ this.origin, this.radius, this.sides, this.snapAngle
+ );
+ },
+
+ /**
+ * Method: modifyGeometry
+ * Modify the polygon geometry in place.
+ */
+ modifyGeometry: function() {
+ var angle, point;
+ var ring = this.feature.geometry.components[0];
+ // if the number of sides ever changes, create a new geometry
+ if(ring.components.length != (this.sides + 1)) {
+ this.createGeometry();
+ ring = this.feature.geometry.components[0];
+ }
+ for(var i=0; i<this.sides; ++i) {
+ point = ring.components[i];
+ angle = this.angle + (i * 2 * Math.PI / this.sides);
+ point.x = this.origin.x + (this.radius * Math.cos(angle));
+ point.y = this.origin.y + (this.radius * Math.sin(angle));
+ point.clearBounds();
+ }
+ },
+
+ /**
+ * Method: calculateAngle
+ * Calculate the angle based on settings.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * evt - {Event}
+ */
+ calculateAngle: function(point, evt) {
+ var alpha = Math.atan2(point.y - this.origin.y,
+ point.x - this.origin.x);
+ if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
+ var snapAngleRad = (Math.PI / 180) * this.snapAngle;
+ this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
+ } else {
+ this.angle = alpha;
+ }
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ // the polygon geometry gets cloned in the callback method
+ this.callback("cancel", null);
+ this.finalize();
+ },
+
+ /**
+ * Method: finalize
+ * Finish the geometry and call the "done" callback.
+ */
+ finalize: function() {
+ this.origin = null;
+ this.radius = this.options.radius;
+ },
+
+ /**
+ * APIMethod: clear
+ * Clear any rendered features on the temporary layer. This is called
+ * when the handler is deactivated, canceled, or done (unless persist
+ * is true).
+ */
+ clear: function() {
+ if (this.layer) {
+ this.layer.renderer.clear();
+ this.layer.destroyFeatures();
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array} An array of arguments with which to call the callback
+ * (defined by the control).
+ */
+ callback: function (name, args) {
+ // override the callback method to always send the polygon geometry
+ if (this.callbacks[name]) {
+ this.callbacks[name].apply(this.control,
+ [this.feature.geometry.clone()]);
+ }
+ // since sketch features are added to the temporary layer
+ // they must be cleared here if done or cancel
+ if(!this.persist && (name == "done" || name == "cancel")) {
+ this.clear();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.RegularPolygon"
+});
+/* ======================================================================
+ OpenLayers/Control/SLDSelect.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Layer/WMS.js
+ * @requires OpenLayers/Handler/RegularPolygon.js
+ * @requires OpenLayers/Handler/Polygon.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Format/SLD/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SLDSelect
+ * Perform selections on WMS layers using Styled Layer Descriptor (SLD)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SLDSelect = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * selected - Triggered when a selection occurs. Listeners receive an
+ * event with *filters* and *layer* properties. Filters will be an
+ * array of OpenLayers.Filter objects created in order to perform
+ * the particular selection.
+ */
+
+ /**
+ * APIProperty: clearOnDeactivate
+ * {Boolean} Should the selection be cleared when the control is
+ * deactivated. Default value is false.
+ */
+ clearOnDeactivate: false,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.WMS>)} The WMS layers this control will work
+ * on.
+ */
+ layers: null,
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectionSymbolizer
+ * {Object} Determines the styling of the selected objects. Default is
+ * a selection in red.
+ */
+ selectionSymbolizer: {
+ 'Polygon': {fillColor: '#FF0000', stroke: false},
+ 'Line': {strokeColor: '#FF0000', strokeWidth: 2},
+ 'Point': {graphicName: 'square', fillColor: '#FF0000', pointRadius: 5}
+ },
+
+ /**
+ * APIProperty: layerOptions
+ * {Object} The options to apply to the selection layer, by default the
+ * selection layer will be kept out of the layer switcher.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * APIProperty: sketchStyle
+ * {<OpenLayers.Style>|Object} Style or symbolizer to use for the sketch
+ * handler. The recommended way of styling the sketch layer, however, is
+ * to configure an <OpenLayers.StyleMap> in the layerOptions of the
+ * <handlerOptions>:
+ *
+ * (code)
+ * new OpenLayers.Control.SLDSelect(OpenLayers.Handler.Path, {
+ * handlerOptions: {
+ * layerOptions: {
+ * styleMap: new OpenLayers.StyleMap({
+ * "default": {strokeColor: "yellow"}
+ * })
+ * }
+ * }
+ * });
+ * (end)
+ */
+ sketchStyle: null,
+
+ /**
+ * APIProperty: wfsCache
+ * {Object} Cache to use for storing parsed results from
+ * <OpenLayers.Format.WFSDescribeFeatureType.read>. If not provided,
+ * these will be cached on the prototype.
+ */
+ wfsCache: {},
+
+ /**
+ * APIProperty: layerCache
+ * {Object} Cache to use for storing references to the selection layers.
+ * Normally each source layer will have exactly 1 selection layer of
+ * type OpenLayers.Layer.WMS. If not provided, layers will
+ * be cached on the prototype. Note that if <clearOnDeactivate> is
+ * true, the layer will no longer be cached after deactivating the
+ * control.
+ */
+ layerCache: {},
+
+ /**
+ * Constructor: OpenLayers.Control.SLDSelect
+ * Create a new control for selecting features in WMS layers using
+ * Styled Layer Descriptor (SLD).
+ *
+ * Parameters:
+ * handler - {<OpenLayers.Class>} A sketch handler class. This determines
+ * the type of selection, e.g. box (<OpenLayers.Handler.Box>), point
+ * (<OpenLayers.Handler.Point>), path (<OpenLayers.Handler.Path>) or
+ * polygon (<OpenLayers.Handler.Polygon>) selection. To use circle
+ * type selection, use <OpenLayers.Handler.RegularPolygon> and pass
+ * the number of desired sides (e.g. 40) as "sides" property to the
+ * <handlerOptions>.
+ * options - {Object} An object containing all configuration properties for
+ * the control.
+ *
+ * Valid options:
+ * layers - Array({<OpenLayers.Layer.WMS>}) The layers to perform the
+ * selection on.
+ */
+ initialize: function(handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.callbacks = OpenLayers.Util.extend({done: this.select,
+ click: this.select}, this.callbacks);
+ this.handlerOptions = this.handlerOptions || {};
+ this.layerOptions = OpenLayers.Util.applyDefaults(this.layerOptions, {
+ displayInLayerSwitcher: false,
+ tileOptions: {maxGetUrlLength: 2048}
+ });
+ if (this.sketchStyle) {
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions,
+ {styleMap: new OpenLayers.StyleMap({"default": this.sketchStyle})}
+ );
+ }
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ for (var key in this.layerCache) {
+ delete this.layerCache[key];
+ }
+ for (var key in this.wfsCache) {
+ delete this.wfsCache[key];
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: coupleLayerVisiblity
+ * Couple the selection layer and the source layer with respect to
+ * layer visibility. So if the source layer is turned off, the
+ * selection layer is also turned off.
+ *
+ * Context:
+ * - {<OpenLayers.Layer>}
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ coupleLayerVisiblity: function(evt) {
+ this.setVisibility(evt.object.getVisibility());
+ },
+
+ /**
+ * Method: createSelectionLayer
+ * Creates a "clone" from the source layer in which the selection can
+ * be drawn. This ensures both the source layer and the selection are
+ * visible and not only the selection.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Layer.WMS>} The source layer on which the selection
+ * is performed.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} A WMS layer with maxGetUrlLength configured to 2048
+ * since SLD selections can easily get quite long.
+ */
+ createSelectionLayer: function(source) {
+ // check if we already have a selection layer for the source layer
+ var selectionLayer;
+ if (!this.layerCache[source.id]) {
+ selectionLayer = new OpenLayers.Layer.WMS(source.name,
+ source.url, source.params,
+ OpenLayers.Util.applyDefaults(
+ this.layerOptions,
+ source.getOptions())
+ );
+ this.layerCache[source.id] = selectionLayer;
+ // make sure the layers are coupled wrt visibility, but only
+ // if they are not displayed in the layer switcher, because in
+ // that case the user cannot control visibility.
+ if (this.layerOptions.displayInLayerSwitcher === false) {
+ source.events.on({
+ "visibilitychanged": this.coupleLayerVisiblity,
+ scope: selectionLayer});
+ }
+ this.map.addLayer(selectionLayer);
+ } else {
+ selectionLayer = this.layerCache[source.id];
+ }
+ return selectionLayer;
+ },
+
+ /**
+ * Method: createSLD
+ * Create the SLD document for the layer using the supplied filters.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>}
+ * filters - Array({<OpenLayers.Filter>}) The filters to be applied.
+ * geometryAttributes - Array({Object}) The geometry attributes of the
+ * layer.
+ *
+ * Returns:
+ * {String} The SLD document generated as a string.
+ */
+ createSLD: function(layer, filters, geometryAttributes) {
+ var sld = {version: "1.0.0", namedLayers: {}};
+ var layerNames = [layer.params.LAYERS].join(",").split(",");
+ for (var i=0, len=layerNames.length; i<len; i++) {
+ var name = layerNames[i];
+ sld.namedLayers[name] = {name: name, userStyles: []};
+ var symbolizer = this.selectionSymbolizer;
+ var geometryAttribute = geometryAttributes[i];
+ if (geometryAttribute.type.indexOf('Polygon') >= 0) {
+ symbolizer = {Polygon: this.selectionSymbolizer['Polygon']};
+ } else if (geometryAttribute.type.indexOf('LineString') >= 0) {
+ symbolizer = {Line: this.selectionSymbolizer['Line']};
+ } else if (geometryAttribute.type.indexOf('Point') >= 0) {
+ symbolizer = {Point: this.selectionSymbolizer['Point']};
+ }
+ var filter = filters[i];
+ sld.namedLayers[name].userStyles.push({name: 'default', rules: [
+ new OpenLayers.Rule({symbolizer: symbolizer,
+ filter: filter,
+ maxScaleDenominator: layer.options.minScale})
+ ]});
+ }
+ return new OpenLayers.Format.SLD({srsName: this.map.getProjection()}).write(sld);
+ },
+
+ /**
+ * Method: parseDescribeLayer
+ * Parse the SLD WMS DescribeLayer response and issue the corresponding
+ * WFS DescribeFeatureType request
+ *
+ * request - {XMLHttpRequest} The request object.
+ */
+ parseDescribeLayer: function(request) {
+ var format = new OpenLayers.Format.WMSDescribeLayer();
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var describeLayer = format.read(doc);
+ var typeNames = [];
+ var url = null;
+ for (var i=0, len=describeLayer.length; i<len; i++) {
+ // perform a WFS DescribeFeatureType request
+ if (describeLayer[i].owsType == "WFS") {
+ typeNames.push(describeLayer[i].typeName);
+ url = describeLayer[i].owsURL;
+ }
+ }
+ var options = {
+ url: url,
+ params: {
+ SERVICE: "WFS",
+ TYPENAME: typeNames.toString(),
+ REQUEST: "DescribeFeatureType",
+ VERSION: "1.0.0"
+ },
+ callback: function(request) {
+ var format = new OpenLayers.Format.WFSDescribeFeatureType();
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var describeFeatureType = format.read(doc);
+ this.control.wfsCache[this.layer.id] = describeFeatureType;
+ this.control._queue && this.control.applySelection();
+ },
+ scope: this
+ };
+ OpenLayers.Request.GET(options);
+ },
+
+ /**
+ * Method: getGeometryAttributes
+ * Look up the geometry attributes from the WFS DescribeFeatureType response
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} The layer for which to look up the
+ * geometry attributes.
+ *
+ * Returns:
+ * Array({Object}) Array of geometry attributes
+ */
+ getGeometryAttributes: function(layer) {
+ var result = [];
+ var cache = this.wfsCache[layer.id];
+ for (var i=0, len=cache.featureTypes.length; i<len; i++) {
+ var typeName = cache.featureTypes[i];
+ var properties = typeName.properties;
+ for (var j=0, lenj=properties.length; j < lenj; j++) {
+ var property = properties[j];
+ var type = property.type;
+ if ((type.indexOf('LineString') >= 0) ||
+ (type.indexOf('GeometryAssociationType') >=0) ||
+ (type.indexOf('GeometryPropertyType') >= 0) ||
+ (type.indexOf('Point') >= 0) ||
+ (type.indexOf('Polygon') >= 0) ) {
+ result.push(property);
+ }
+ }
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. Activating the control will perform a SLD WMS
+ * DescribeLayer request followed by a WFS DescribeFeatureType request
+ * so that the proper symbolizers can be chosen based on the geometry
+ * type.
+ */
+ activate: function() {
+ var activated = OpenLayers.Control.prototype.activate.call(this);
+ if(activated) {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer && !this.wfsCache[layer.id]) {
+ var options = {
+ url: layer.url,
+ params: {
+ SERVICE: "WMS",
+ VERSION: layer.params.VERSION,
+ LAYERS: layer.params.LAYERS,
+ REQUEST: "DescribeLayer"
+ },
+ callback: this.parseDescribeLayer,
+ scope: {layer: layer, control: this}
+ };
+ OpenLayers.Request.GET(options);
+ }
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. If clearOnDeactivate is true, remove the
+ * selection layer(s).
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
+ if(deactivated) {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer && this.clearOnDeactivate === true) {
+ var layerCache = this.layerCache;
+ var selectionLayer = layerCache[layer.id];
+ if (selectionLayer) {
+ layer.events.un({
+ "visibilitychanged": this.coupleLayerVisiblity,
+ scope: selectionLayer});
+ selectionLayer.destroy();
+ delete layerCache[layer.id];
+ }
+ }
+ }
+ }
+ return deactivated;
+ },
+
+ /**
+ * APIMethod: setLayers
+ * Set the layers on which the selection should be performed. Call the
+ * setLayers method if the layer(s) to be used change and the same
+ * control should be used on a new set of layers.
+ * If the control is already active, it will be active after the new
+ * set of layers is set.
+ *
+ * Parameters:
+ * layers - {Array(<OpenLayers.Layer.WMS>)} The new set of layers on which
+ * the selection should be performed.
+ */
+ setLayers: function(layers) {
+ if(this.active) {
+ this.deactivate();
+ this.layers = layers;
+ this.activate();
+ } else {
+ this.layers = layers;
+ }
+ },
+
+ /**
+ * Function: createFilter
+ * Create the filter to be used in the SLD.
+ *
+ * Parameters:
+ * geometryAttribute - {Object} Used to get the name of the geometry
+ * attribute which is needed for constructing the spatial filter.
+ * geometry - {<OpenLayers.Geometry>} The geometry to use.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} The spatial filter created.
+ */
+ createFilter: function(geometryAttribute, geometry) {
+ var filter = null;
+ if (this.handler instanceof OpenLayers.Handler.RegularPolygon) {
+ // box
+ if (this.handler.irregular === true) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ property: geometryAttribute.name,
+ value: geometry.getBounds()}
+ );
+ } else {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ }
+ } else if (this.handler instanceof OpenLayers.Handler.Polygon) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ } else if (this.handler instanceof OpenLayers.Handler.Path) {
+ // if source layer is point based, use DWITHIN instead
+ if (geometryAttribute.type.indexOf('Point') >= 0) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ property: geometryAttribute.name,
+ distance: this.map.getExtent().getWidth()*0.01 ,
+ distanceUnits: this.map.getUnits(),
+ value: geometry}
+ );
+ } else {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ }
+ } else if (this.handler instanceof OpenLayers.Handler.Click) {
+ if (geometryAttribute.type.indexOf('Polygon') >= 0) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ } else {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ property: geometryAttribute.name,
+ distance: this.map.getExtent().getWidth()*0.01 ,
+ distanceUnits: this.map.getUnits(),
+ value: geometry}
+ );
+ }
+ }
+ return filter;
+ },
+
+ /**
+ * Method: select
+ * When the handler is done, use SLD_BODY on the selection layer to
+ * display the selection in the map.
+ *
+ * Parameters:
+ * geometry - {Object} or {<OpenLayers.Geometry>}
+ */
+ select: function(geometry) {
+ this._queue = function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ var geometryAttributes = this.getGeometryAttributes(layer);
+ var filters = [];
+ for (var j=0, lenj=geometryAttributes.length; j<lenj; j++) {
+ var geometryAttribute = geometryAttributes[j];
+ if (geometryAttribute !== null) {
+ // from the click handler we will not get an actual
+ // geometry so transform
+ if (!(geometry instanceof OpenLayers.Geometry)) {
+ var point = this.map.getLonLatFromPixel(
+ geometry.xy);
+ geometry = new OpenLayers.Geometry.Point(
+ point.lon, point.lat);
+ }
+ var filter = this.createFilter(geometryAttribute,
+ geometry);
+ if (filter !== null) {
+ filters.push(filter);
+ }
+ }
+ }
+
+ var selectionLayer = this.createSelectionLayer(layer);
+
+ this.events.triggerEvent("selected", {
+ layer: layer,
+ filters: filters
+ });
+
+ var sld = this.createSLD(layer, filters, geometryAttributes);
+
+ selectionLayer.mergeNewParams({SLD_BODY: sld});
+ delete this._queue;
+ }
+ };
+ this.applySelection();
+ },
+
+ /**
+ * Method: applySelection
+ * Checks if all required wfs data is cached, and applies the selection
+ */
+ applySelection: function() {
+ var canApply = true;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ if(!this.wfsCache[this.layers[i].id]) {
+ canApply = false;
+ break;
+ }
+ }
+ canApply && this._queue.call(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SLDSelect"
+});
+/* ======================================================================
+ OpenLayers/Control/Scale.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Scale
+ * The Scale control displays the current map scale as a ratio (e.g. Scale =
+ * 1:1M). By default it is displayed in the lower right corner of the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Scale = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: element
+ * {DOMElement}
+ */
+ element: null,
+
+ /**
+ * APIProperty: geodesic
+ * {Boolean} Use geodesic measurement. Default is false. The recommended
+ * setting for maps in EPSG:4326 is false, and true EPSG:900913. If set to
+ * true, the scale will be calculated based on the horizontal size of the
+ * pixel in the center of the map viewport.
+ */
+ geodesic: false,
+
+ /**
+ * Constructor: OpenLayers.Control.Scale
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * options - {Object}
+ */
+ initialize: function(element, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.element = OpenLayers.Util.getElement(element);
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (!this.element) {
+ this.element = document.createElement("div");
+ this.div.appendChild(this.element);
+ }
+ this.map.events.register( 'moveend', this, this.updateScale);
+ this.updateScale();
+ return this.div;
+ },
+
+ /**
+ * Method: updateScale
+ */
+ updateScale: function() {
+ var scale;
+ if(this.geodesic === true) {
+ var units = this.map.getUnits();
+ if(!units) {
+ return;
+ }
+ var inches = OpenLayers.INCHES_PER_UNIT;
+ scale = (this.map.getGeodesicPixelSize().w || 0.000001) *
+ inches["km"] * OpenLayers.DOTS_PER_INCH;
+ } else {
+ scale = this.map.getScale();
+ }
+
+ if (!scale) {
+ return;
+ }
+
+ if (scale >= 9500 && scale <= 950000) {
+ scale = Math.round(scale / 1000) + "K";
+ } else if (scale >= 950000) {
+ scale = Math.round(scale / 1000000) + "M";
+ } else {
+ scale = Math.round(scale);
+ }
+
+ this.element.innerHTML = OpenLayers.i18n("Scale = 1 : ${scaleDenom}", {'scaleDenom':scale});
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Scale"
+});
+
+/* ======================================================================
+ OpenLayers/Layer/MapGuide.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MapGuide
+ * Instances of OpenLayers.Layer.MapGuide are used to display
+ * data from a MapGuide OS instance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.MapGuide = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Treat this layer as a base layer. Default is true.
+ **/
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: useHttpTile
+ * {Boolean} use a tile cache exposed directly via a webserver rather than the
+ * via mapguide server. This does require extra configuration on the Mapguide Server,
+ * and will only work when singleTile is false. The url for the layer must be set to the
+ * webserver path rather than the Mapguide mapagent.
+ * See http://trac.osgeo.org/mapguide/wiki/CodeSamples/Tiles/ServingTilesViaHttp
+ **/
+ useHttpTile: false,
+
+ /**
+ * APIProperty: singleTile
+ * {Boolean} use tile server or request single tile image.
+ **/
+ singleTile: false,
+
+ /**
+ * APIProperty: useOverlay
+ * {Boolean} flag to indicate if the layer should be retrieved using
+ * GETMAPIMAGE (default) or using GETDYNAMICOVERLAY requests.
+ **/
+ useOverlay: false,
+
+ /**
+ * APIProperty: useAsyncOverlay
+ * {Boolean} indicates if the MapGuide site supports the asynchronous
+ * GETDYNAMICOVERLAY requests which is available in MapGuide Enterprise 2010
+ * and MapGuide Open Source v2.0.3 or higher. The newer versions of MG
+ * is called asynchronously, allows selections to be drawn separately from
+ * the map and offers styling options.
+ *
+ * With older versions of MapGuide, set useAsyncOverlay=false. Note that in
+ * this case a synchronous AJAX call is issued and the mapname and session
+ * parameters must be used to initialize the layer, not the mapdefinition
+ * parameter. Also note that this will issue a synchronous AJAX request
+ * before the image request can be issued so the users browser may lock
+ * up if the MG Web tier does not respond in a timely fashion.
+ **/
+ useAsyncOverlay: true,
+
+ /**
+ * Constant: TILE_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs for tiled layer
+ */
+ TILE_PARAMS: {
+ operation: 'GETTILEIMAGE',
+ version: '1.2.0'
+ },
+
+ /**
+ * Constant: SINGLE_TILE_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs for untiled layer
+ */
+ SINGLE_TILE_PARAMS: {
+ operation: 'GETMAPIMAGE',
+ format: 'PNG',
+ locale: 'en',
+ clip: '1',
+ version: '1.0.0'
+ },
+
+ /**
+ * Constant: OVERLAY_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs for untiled layer
+ */
+ OVERLAY_PARAMS: {
+ operation: 'GETDYNAMICMAPOVERLAYIMAGE',
+ format: 'PNG',
+ locale: 'en',
+ clip: '1',
+ version: '2.0.0'
+ },
+
+ /**
+ * Constant: FOLDER_PARAMS
+ * {Object} Hashtable of parameter key/value pairs which describe
+ * the folder structure for tiles as configured in the mapguide
+ * serverconfig.ini section [TileServiceProperties]
+ */
+ FOLDER_PARAMS: {
+ tileColumnsPerFolder: 30,
+ tileRowsPerFolder: 30,
+ format: 'png',
+ querystring: null
+ },
+
+ /**
+ * Property: defaultSize
+ * {<OpenLayers.Size>} Tile size as produced by MapGuide server
+ **/
+ defaultSize: new OpenLayers.Size(300,300),
+
+ /**
+ * Property: tileOriginCorner
+ * {String} MapGuide tile server uses top-left as tile origin
+ **/
+ tileOriginCorner: "tl",
+
+ /**
+ * Constructor: OpenLayers.Layer.MapGuide
+ * Create a new Mapguide layer, either tiled or untiled.
+ *
+ * For tiled layers, the 'groupName' and 'mapDefinition' values
+ * must be specified as parameters in the constructor.
+ *
+ * For untiled base layers, specify either combination of 'mapName' and
+ * 'session', or 'mapDefinition' and 'locale'.
+ *
+ * For older versions of MapGuide and overlay layers, set useAsyncOverlay
+ * to false and in this case mapName and session are required parameters
+ * for the constructor.
+ *
+ * NOTE: MapGuide OS uses a DPI value and degrees to meters conversion
+ * factor that are different than the defaults used in OpenLayers,
+ * so these must be adjusted accordingly in your application.
+ * See the MapGuide example for how to set these values for MGOS.
+ *
+ * Parameters:
+ * name - {String} Name of the layer displayed in the interface
+ * url - {String} Location of the MapGuide mapagent executable
+ * (e.g. http://localhost:8008/mapguide/mapagent/mapagent.fcgi)
+ * params - {Object} hashtable of additional parameters to use. Some
+ * parameters may require additional code on the server. The ones that
+ * you may want to use are:
+ * - mapDefinition - {String} The MapGuide resource definition
+ * (e.g. Library://Samples/Gmap/Maps/gmapTiled.MapDefinition)
+ * - locale - Locale setting
+ * (for untiled overlays layers only)
+ * - mapName - {String} Name of the map as stored in the MapGuide session.
+ * (for untiled layers with a session parameter only)
+ * - session - { String} MapGuide session ID
+ * (for untiled overlays layers only)
+ * - basemaplayergroupname - {String} GroupName for tiled MapGuide layers only
+ * - format - Image format to be returned (for untiled overlay layers only)
+ * - showLayers - {String} A comma separated list of GUID's for the
+ * layers to display eg: 'cvc-xcv34,453-345-345sdf'.
+ * - hideLayers - {String} A comma separated list of GUID's for the
+ * layers to hide eg: 'cvc-xcv34,453-345-345sdf'.
+ * - showGroups - {String} A comma separated list of GUID's for the
+ * groups to display eg: 'cvc-xcv34,453-345-345sdf'.
+ * - hideGroups - {String} A comma separated list of GUID's for the
+ * groups to hide eg: 'cvc-xcv34,453-345-345sdf'
+ * - selectionXml - {String} A selection xml string Some server plumbing
+ * is required to read such a value.
+ * options - {Object} Hashtable of extra options to tag onto the layer;
+ * will vary depending if tiled or untiled maps are being requested
+ */
+ initialize: function(name, url, params, options) {
+
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
+
+ // unless explicitly set in options, if the layer is transparent,
+ // it will be an overlay
+ if (options == null || options.isBaseLayer == null) {
+ this.isBaseLayer = ((this.transparent != "true") &&
+ (this.transparent != true));
+ }
+
+ if (options && options.useOverlay!=null) {
+ this.useOverlay = options.useOverlay;
+ }
+
+ //initialize for untiled layers
+ if (this.singleTile) {
+ if (this.useOverlay) {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.OVERLAY_PARAMS
+ );
+ if (!this.useAsyncOverlay) {
+ this.params.version = "1.0.0";
+ }
+ } else {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.SINGLE_TILE_PARAMS
+ );
+ }
+ } else {
+ //initialize for tiled layers
+ if (this.useHttpTile) {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.FOLDER_PARAMS
+ );
+ } else {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.TILE_PARAMS
+ );
+ }
+ this.setTileSize(this.defaultSize);
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.MapGuide>} An exact clone of this layer
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.MapGuide(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ * Return a query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox
+ * for the request
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also
+ * the passed-in bounds and appropriate tile size specified
+ * as parameters.
+ */
+ getURL: function (bounds) {
+ var url;
+ var center = bounds.getCenterLonLat();
+ var mapSize = this.map.getSize();
+
+ if (this.singleTile) {
+ //set up the call for GETMAPIMAGE or GETDYNAMICMAPOVERLAY with
+ //dynamic map parameters
+ var params = {
+ setdisplaydpi: OpenLayers.DOTS_PER_INCH,
+ setdisplayheight: mapSize.h*this.ratio,
+ setdisplaywidth: mapSize.w*this.ratio,
+ setviewcenterx: center.lon,
+ setviewcentery: center.lat,
+ setviewscale: this.map.getScale()
+ };
+
+ if (this.useOverlay && !this.useAsyncOverlay) {
+ //first we need to call GETVISIBLEMAPEXTENT to set the extent
+ var getVisParams = {};
+ getVisParams = OpenLayers.Util.extend(getVisParams, params);
+ getVisParams.operation = "GETVISIBLEMAPEXTENT";
+ getVisParams.version = "1.0.0";
+ getVisParams.session = this.params.session;
+ getVisParams.mapName = this.params.mapName;
+ getVisParams.format = 'text/xml';
+ url = this.getFullRequestString( getVisParams );
+
+ OpenLayers.Request.GET({url: url, async: false});
+ }
+ //construct the full URL
+ url = this.getFullRequestString( params );
+ } else {
+
+ //tiled version
+ var currentRes = this.map.getResolution();
+ var colidx = Math.floor((bounds.left-this.maxExtent.left)/currentRes);
+ colidx = Math.round(colidx/this.tileSize.w);
+ var rowidx = Math.floor((this.maxExtent.top-bounds.top)/currentRes);
+ rowidx = Math.round(rowidx/this.tileSize.h);
+
+ if (this.useHttpTile){
+ url = this.getImageFilePath(
+ {
+ tilecol: colidx,
+ tilerow: rowidx,
+ scaleindex: this.resolutions.length - this.map.zoom - 1
+ });
+
+ } else {
+ url = this.getFullRequestString(
+ {
+ tilecol: colidx,
+ tilerow: rowidx,
+ scaleindex: this.resolutions.length - this.map.zoom - 1
+ });
+ }
+ }
+ return url;
+ },
+
+ /**
+ * Method: getFullRequestString
+ * getFullRequestString on MapGuide layers is special, because we
+ * do a regular expression replace on ',' in parameters to '+'.
+ * This is why it is subclassed here.
+ *
+ * Parameters:
+ * altUrl - {String} Alternative base URL to use.
+ *
+ * Returns:
+ * {String} A string with the layer's url appropriately encoded for MapGuide
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ // use layer's url unless altUrl passed in
+ var url = (altUrl == null) ? this.url : altUrl;
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will randomly select one of them in order
+ // to evenly distribute requests to different urls.
+ if (typeof url == "object") {
+ url = url[Math.floor(Math.random()*url.length)];
+ }
+ // requestString always starts with url
+ var requestString = url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ // ignore parameters that are already in the url search string
+ var urlParams = OpenLayers.Util.upperCaseObject(
+ OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ /* MapGuide needs '+' seperating things like bounds/height/width.
+ Since typically this is URL encoded, we use a slight hack: we
+ depend on the list-like functionality of getParameterString to
+ leave ',' only in the case of list items (since otherwise it is
+ encoded) then do a regular expression replace on the , characters
+ to '+' */
+ paramsString = paramsString.replace(/,/g, "+");
+
+ if (paramsString != "") {
+ var lastServerChar = url.charAt(url.length - 1);
+ if ((lastServerChar == "&") || (lastServerChar == "?")) {
+ requestString += paramsString;
+ } else {
+ if (url.indexOf('?') == -1) {
+ //serverPath has no ? -- add one
+ requestString += '?' + paramsString;
+ } else {
+ //serverPath contains ?, so must already have paramsString at the end
+ requestString += '&' + paramsString;
+ }
+ }
+ }
+ return requestString;
+ },
+
+ /**
+ * Method: getImageFilePath
+ * special handler to request mapguide tiles from an http exposed tilecache
+ *
+ * Parameters:
+ * altUrl - {String} Alternative base URL to use.
+ *
+ * Returns:
+ * {String} A string with the url for the tile image
+ */
+ getImageFilePath:function(newParams, altUrl) {
+ // use layer's url unless altUrl passed in
+ var url = (altUrl == null) ? this.url : altUrl;
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will randomly select one of them in order
+ // to evenly distribute requests to different urls.
+ if (typeof url == "object") {
+ url = url[Math.floor(Math.random()*url.length)];
+ }
+ // requestString always starts with url
+ var requestString = url;
+
+ var tileRowGroup = "";
+ var tileColGroup = "";
+
+ if (newParams.tilerow < 0) {
+ tileRowGroup = '-';
+ }
+
+ if (newParams.tilerow == 0 ) {
+ tileRowGroup += '0';
+ } else {
+ tileRowGroup += Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder)) * this.params.tileRowsPerFolder;
+ }
+
+ if (newParams.tilecol < 0) {
+ tileColGroup = '-';
+ }
+
+ if (newParams.tilecol == 0) {
+ tileColGroup += '0';
+ } else {
+ tileColGroup += Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder)) * this.params.tileColumnsPerFolder;
+ }
+
+ var tilePath = '/S' + Math.floor(newParams.scaleindex)
+ + '/' + this.params.basemaplayergroupname
+ + '/R' + tileRowGroup
+ + '/C' + tileColGroup
+ + '/' + (newParams.tilerow % this.params.tileRowsPerFolder)
+ + '_' + (newParams.tilecol % this.params.tileColumnsPerFolder)
+ + '.' + this.params.format;
+
+ if (this.params.querystring) {
+ tilePath += "?" + this.params.querystring;
+ }
+
+ requestString += tilePath;
+ return requestString;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.MapGuide"
+});
+/* ======================================================================
+ OpenLayers/Control/Measure.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Measure
+ * Allows for drawing of features for measurements.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Measure = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * measure - Triggered when a measurement sketch is complete. Listeners
+ * will receive an event with measure, units, order, and geometry
+ * properties.
+ * measurepartial - Triggered when a new point is added to the
+ * measurement sketch or if the <immediate> property is true and the
+ * measurement sketch is modified. Listeners receive an event with measure,
+ * units, order, and geometry.
+ */
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: displaySystem
+ * {String} Display system for output measurements. Supported values
+ * are 'english', 'metric', and 'geographic'. Default is 'metric'.
+ */
+ displaySystem: 'metric',
+
+ /**
+ * APIProperty: geodesic
+ * {Boolean} Calculate geodesic metrics instead of planar metrics. This
+ * requires that geometries can be transformed into Geographic/WGS84
+ * (if that is not already the map projection). Default is false.
+ */
+ geodesic: false,
+
+ /**
+ * Property: displaySystemUnits
+ * {Object} Units for various measurement systems. Values are arrays
+ * of unit abbreviations (from OpenLayers.INCHES_PER_UNIT) in decreasing
+ * order of length.
+ */
+ displaySystemUnits: {
+ geographic: ['dd'],
+ english: ['mi', 'ft', 'in'],
+ metric: ['km', 'm']
+ },
+
+ /**
+ * Property: delay
+ * {Number} Number of milliseconds between clicks before the event is
+ * considered a double-click. The "measurepartial" event will not
+ * be triggered if the sketch is completed within this time. This
+ * is required for IE where creating a browser reflow (if a listener
+ * is modifying the DOM by displaying the measurement values) messes
+ * with the dblclick listener in the sketch handler.
+ */
+ partialDelay: 300,
+
+ /**
+ * Property: delayedTrigger
+ * {Number} Timeout id of trigger for measurepartial.
+ */
+ delayedTrigger: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Keep the temporary measurement sketch drawn after the
+ * measurement is complete. The geometry will persist until a new
+ * measurement is started, the control is deactivated, or <cancel> is
+ * called.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: immediate
+ * {Boolean} Activates the immediate measurement so that the "measurepartial"
+ * event is also fired once the measurement sketch is modified.
+ * Default is false.
+ */
+ immediate : false,
+
+ /**
+ * Constructor: OpenLayers.Control.Measure
+ *
+ * Parameters:
+ * handler - {<OpenLayers.Handler>}
+ * options - {Object}
+ */
+ initialize: function(handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ var callbacks = {done: this.measureComplete,
+ point: this.measurePartial};
+ if (this.immediate){
+ callbacks.modify = this.measureImmediate;
+ }
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+
+ // let the handler options override, so old code that passes 'persist'
+ // directly to the handler does not need an update
+ this.handlerOptions = OpenLayers.Util.extend(
+ {persist: this.persist}, this.handlerOptions
+ );
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ this.cancelDelay();
+ return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: cancel
+ * Stop the control from measuring. If <persist> is true, the temporary
+ * sketch will be erased.
+ */
+ cancel: function() {
+ this.cancelDelay();
+ this.handler.cancel();
+ },
+
+ /**
+ * APIMethod: setImmediate
+ * Sets the <immediate> property. Changes the activity of immediate
+ * measurement.
+ */
+ setImmediate: function(immediate) {
+ this.immediate = immediate;
+ if (this.immediate){
+ this.callbacks.modify = this.measureImmediate;
+ } else {
+ delete this.callbacks.modify;
+ }
+ },
+
+ /**
+ * Method: updateHandler
+ *
+ * Parameters:
+ * handler - {Function} One of the sketch handler constructors.
+ * options - {Object} Options for the handler.
+ */
+ updateHandler: function(handler, options) {
+ var active = this.active;
+ if(active) {
+ this.deactivate();
+ }
+ this.handler = new handler(this, this.callbacks, options);
+ if(active) {
+ this.activate();
+ }
+ },
+
+ /**
+ * Method: measureComplete
+ * Called when the measurement sketch is done.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ measureComplete: function(geometry) {
+ this.cancelDelay();
+ this.measure(geometry, "measure");
+ },
+
+ /**
+ * Method: measurePartial
+ * Called each time a new point is added to the measurement sketch.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The last point added.
+ * geometry - {<OpenLayers.Geometry>} The sketch geometry.
+ */
+ measurePartial: function(point, geometry) {
+ this.cancelDelay();
+ geometry = geometry.clone();
+ // when we're wating for a dblclick, we have to trigger measurepartial
+ // after some delay to deal with reflow issues in IE
+ if (this.handler.freehandMode(this.handler.evt)) {
+ // no dblclick in freehand mode
+ this.measure(geometry, "measurepartial");
+ } else {
+ this.delayedTrigger = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.delayedTrigger = null;
+ this.measure(geometry, "measurepartial");
+ }, this),
+ this.partialDelay
+ );
+ }
+ },
+
+ /**
+ * Method: measureImmediate
+ * Called each time the measurement sketch is modified.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The point at the mouse position.
+ * feature - {<OpenLayers.Feature.Vector>} The sketch feature.
+ * drawing - {Boolean} Indicates whether we're currently drawing.
+ */
+ measureImmediate : function(point, feature, drawing) {
+ if (drawing && !this.handler.freehandMode(this.handler.evt)) {
+ this.cancelDelay();
+ this.measure(feature.geometry, "measurepartial");
+ }
+ },
+
+ /**
+ * Method: cancelDelay
+ * Cancels the delay measurement that measurePartial began.
+ */
+ cancelDelay: function() {
+ if (this.delayedTrigger !== null) {
+ window.clearTimeout(this.delayedTrigger);
+ this.delayedTrigger = null;
+ }
+ },
+
+ /**
+ * Method: measure
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * eventType - {String}
+ */
+ measure: function(geometry, eventType) {
+ var stat, order;
+ if(geometry.CLASS_NAME.indexOf('LineString') > -1) {
+ stat = this.getBestLength(geometry);
+ order = 1;
+ } else {
+ stat = this.getBestArea(geometry);
+ order = 2;
+ }
+ this.events.triggerEvent(eventType, {
+ measure: stat[0],
+ units: stat[1],
+ order: order,
+ geometry: geometry
+ });
+ },
+
+ /**
+ * Method: getBestArea
+ * Based on the <displaySystem> returns the area of a geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Array([Float, String])} Returns a two item array containing the
+ * area and the units abbreviation.
+ */
+ getBestArea: function(geometry) {
+ var units = this.displaySystemUnits[this.displaySystem];
+ var unit, area;
+ for(var i=0, len=units.length; i<len; ++i) {
+ unit = units[i];
+ area = this.getArea(geometry, unit);
+ if(area > 1) {
+ break;
+ }
+ }
+ return [area, unit];
+ },
+
+ /**
+ * Method: getArea
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * units - {String} Unit abbreviation
+ *
+ * Returns:
+ * {Float} The geometry area in the given units.
+ */
+ getArea: function(geometry, units) {
+ var area, geomUnits;
+ if(this.geodesic) {
+ area = geometry.getGeodesicArea(this.map.getProjectionObject());
+ geomUnits = "m";
+ } else {
+ area = geometry.getArea();
+ geomUnits = this.map.getUnits();
+ }
+ var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
+ if(inPerDisplayUnit) {
+ var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
+ area *= Math.pow((inPerMapUnit / inPerDisplayUnit), 2);
+ }
+ return area;
+ },
+
+ /**
+ * Method: getBestLength
+ * Based on the <displaySystem> returns the length of a geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Array([Float, String])} Returns a two item array containing the
+ * length and the units abbreviation.
+ */
+ getBestLength: function(geometry) {
+ var units = this.displaySystemUnits[this.displaySystem];
+ var unit, length;
+ for(var i=0, len=units.length; i<len; ++i) {
+ unit = units[i];
+ length = this.getLength(geometry, unit);
+ if(length > 1) {
+ break;
+ }
+ }
+ return [length, unit];
+ },
+
+ /**
+ * Method: getLength
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * units - {String} Unit abbreviation
+ *
+ * Returns:
+ * {Float} The geometry length in the given units.
+ */
+ getLength: function(geometry, units) {
+ var length, geomUnits;
+ if(this.geodesic) {
+ length = geometry.getGeodesicLength(this.map.getProjectionObject());
+ geomUnits = "m";
+ } else {
+ length = geometry.getLength();
+ geomUnits = this.map.getUnits();
+ }
+ var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
+ if(inPerDisplayUnit) {
+ var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
+ length *= (inPerMapUnit / inPerDisplayUnit);
+ }
+ return length;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Measure"
+});
+/* ======================================================================
+ OpenLayers/Format/WMC/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_0_0
+ * Read and write WMC version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.WMC.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/context
+ * http://schemas.opengis.net/context/1.0.0/context.xsd
+ */
+ schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WMC> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Method: read_wmc_SRS
+ */
+ read_wmc_SRS: function(layerContext, node) {
+ var srs = this.getChildValue(node);
+ if (typeof layerContext.projections != "object") {
+ layerContext.projections = {};
+ }
+ var values = srs.split(/ +/);
+ for (var i=0, len=values.length; i<len; i++) {
+ layerContext.projections[values[i]] = true;
+ }
+ },
+
+ /**
+ * Method: write_wmc_Layer
+ * Create a Layer node given a layer context object. This method adds
+ * elements specific to version 1.0.0.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.}
+ *
+ * Returns:
+ * {Element} A WMC Layer element node.
+ */
+ write_wmc_Layer: function(context) {
+ var node = OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(
+ this, [context]
+ );
+
+ // optional SRS element(s)
+ if (context.srs) {
+ var projections = [];
+ for(var name in context.srs) {
+ projections.push(name);
+ }
+ node.appendChild(this.createElementDefaultNS("SRS", projections.join(" ")));
+ }
+
+ // optional FormatList element
+ node.appendChild(this.write_wmc_FormatList(context));
+
+ // optional StyleList element
+ node.appendChild(this.write_wmc_StyleList(context));
+
+ // optional DimensionList element
+ if (context.dimensions) {
+ node.appendChild(this.write_wmc_DimensionList(context));
+ }
+
+ // OpenLayers specific properties go in an Extension element
+ node.appendChild(this.write_wmc_LayerExtension(context));
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMC.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Popup/Anchored.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Anchored
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup>
+ */
+OpenLayers.Popup.Anchored =
+ OpenLayers.Class(OpenLayers.Popup, {
+
+ /**
+ * Property: relativePosition
+ * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
+ */
+ relativePosition: null,
+
+ /**
+ * APIProperty: keepInMap
+ * {Boolean} If panMapIfOutOfView is false, and this property is true,
+ * contrain the popup such that it always fits in the available map
+ * space. By default, this is set. If you are creating popups that are
+ * near map edges and not allowing pannning, and especially if you have
+ * a popup which has a fixedRelativePosition, setting this to false may
+ * be a smart thing to do.
+ *
+ * For anchored popups, default is true, since subclasses will
+ * usually want this functionality.
+ */
+ keepInMap: true,
+
+ /**
+ * Property: anchor
+ * {Object} Object to which we'll anchor the popup. Must expose a
+ * 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
+ */
+ anchor: null,
+
+ /**
+ * Constructor: OpenLayers.Popup.Anchored
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object which must expose a 'size' <OpenLayers.Size>
+ * and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+ var newArguments = [
+ id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback
+ ];
+ OpenLayers.Popup.prototype.initialize.apply(this, newArguments);
+
+ this.anchor = (anchor != null) ? anchor
+ : { size: new OpenLayers.Size(0,0),
+ offset: new OpenLayers.Pixel(0,0)};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.anchor = null;
+ this.relativePosition = null;
+
+ OpenLayers.Popup.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: show
+ * Overridden from Popup since user might hide popup and then show() it
+ * in a new location (meaning we might want to update the relative
+ * position on the show)
+ */
+ show: function() {
+ this.updatePosition();
+ OpenLayers.Popup.prototype.show.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ * Since the popup is moving to a new px, it might need also to be moved
+ * relative to where the marker is. We first calculate the new
+ * relativePosition, and then we calculate the new px where we will
+ * put the popup, based on the new relative position.
+ *
+ * If the relativePosition has changed, we must also call
+ * updateRelativePosition() to make any visual changes to the popup
+ * which are associated with putting it in a new relativePosition.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function(px) {
+ var oldRelativePosition = this.relativePosition;
+ this.relativePosition = this.calculateRelativePosition(px);
+
+ OpenLayers.Popup.prototype.moveTo.call(this, this.calculateNewPx(px));
+
+ //if this move has caused the popup to change its relative position,
+ // we need to make the appropriate cosmetic changes.
+ if (this.relativePosition != oldRelativePosition) {
+ this.updateRelativePosition();
+ }
+ },
+
+ /**
+ * APIMethod: setSize
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.prototype.setSize.apply(this, arguments);
+
+ if ((this.lonlat) && (this.map)) {
+ var px = this.map.getLayerPxFromLonLat(this.lonlat);
+ this.moveTo(px);
+ }
+ },
+
+ /**
+ * Method: calculateRelativePosition
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {String} The relative position ("br" "tr" "tl" "bl") at which the popup
+ * should be placed.
+ */
+ calculateRelativePosition:function(px) {
+ var lonlat = this.map.getLonLatFromLayerPx(px);
+
+ var extent = this.map.getExtent();
+ var quadrant = extent.determineQuadrant(lonlat);
+
+ return OpenLayers.Bounds.oppositeQuadrant(quadrant);
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * The popup has been moved to a new relative location, so we may want to
+ * make some cosmetic adjustments to it.
+ *
+ * Note that in the classic Anchored popup, there is nothing to do
+ * here, since the popup looks exactly the same in all four positions.
+ * Subclasses such as Framed, however, will want to do something
+ * special here.
+ */
+ updateRelativePosition: function() {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * Method: calculateNewPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+ * relative to the passed-in px.
+ */
+ calculateNewPx:function(px) {
+ var newPx = px.offset(this.anchor.offset);
+
+ //use contentSize if size is not already set
+ var size = this.size || this.contentSize;
+
+ var top = (this.relativePosition.charAt(0) == 't');
+ newPx.y += (top) ? -size.h : this.anchor.size.h;
+
+ var left = (this.relativePosition.charAt(1) == 'l');
+ newPx.x += (left) ? -size.w : this.anchor.size.w;
+
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.Anchored"
+});
+/* ======================================================================
+ OpenLayers/Popup/Framed.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Framed
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.Framed =
+ OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+ /**
+ * Property: imageSrc
+ * {String} location of the image to be used as the popup frame
+ */
+ imageSrc: null,
+
+ /**
+ * Property: imageSize
+ * {<OpenLayers.Size>} Size (measured in pixels) of the image located
+ * by the 'imageSrc' property.
+ */
+ imageSize: null,
+
+ /**
+ * APIProperty: isAlphaImage
+ * {Boolean} The image has some alpha and thus needs to use the alpha
+ * image hack. Note that setting this to true will have no noticeable
+ * effect in FF or IE7 browsers, but will all but crush the ie6
+ * browser.
+ * Default is false.
+ */
+ isAlphaImage: false,
+
+ /**
+ * Property: positionBlocks
+ * {Object} Hash of different position blocks (Object/Hashs). Each block
+ * will be keyed by a two-character 'relativePosition'
+ * code string (ie "tl", "tr", "bl", "br"). Block properties are
+ * 'offset', 'padding' (self-explanatory), and finally the 'blocks'
+ * parameter, which is an array of the block objects.
+ *
+ * Each block object must have 'size', 'anchor', and 'position'
+ * properties.
+ *
+ * Note that positionBlocks should never be modified at runtime.
+ */
+ positionBlocks: null,
+
+ /**
+ * Property: blocks
+ * {Array[Object]} Array of objects, each of which is one "block" of the
+ * popup. Each block has a 'div' and an 'image' property, both of
+ * which are DOMElements, and the latter of which is appended to the
+ * former. These are reused as the popup goes changing positions for
+ * great economy and elegance.
+ */
+ blocks: null,
+
+ /**
+ * APIProperty: fixedRelativePosition
+ * {Boolean} We want the framed popup to work dynamically placed relative
+ * to its anchor but also in just one fixed position. A well designed
+ * framed popup will have the pixels and logic to display itself in
+ * any of the four relative positions, but (understandably), this will
+ * not be the case for all of them. By setting this property to 'true',
+ * framed popup will not recalculate for the best placement each time
+ * it's open, but will always open the same way.
+ * Note that if this is set to true, it is generally advisable to also
+ * set the 'panIntoView' property to true so that the popup can be
+ * scrolled into view (since it will often be offscreen on open)
+ * Default is false.
+ */
+ fixedRelativePosition: false,
+
+ /**
+ * Constructor: OpenLayers.Popup.Framed
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+
+ if (this.fixedRelativePosition) {
+ //based on our decided relativePostion, set the current padding
+ // this keeps us from getting into trouble
+ this.updateRelativePosition();
+
+ //make calculateRelativePosition always return the specified
+ // fixed position.
+ this.calculateRelativePosition = function(px) {
+ return this.relativePosition;
+ };
+ }
+
+ this.contentDiv.style.position = "absolute";
+ this.contentDiv.style.zIndex = 1;
+
+ if (closeBox) {
+ this.closeDiv.style.zIndex = 1;
+ }
+
+ this.groupDiv.style.position = "absolute";
+ this.groupDiv.style.top = "0px";
+ this.groupDiv.style.left = "0px";
+ this.groupDiv.style.height = "100%";
+ this.groupDiv.style.width = "100%";
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.imageSrc = null;
+ this.imageSize = null;
+ this.isAlphaImage = null;
+
+ this.fixedRelativePosition = false;
+ this.positionBlocks = null;
+
+ //remove our blocks
+ for(var i = 0; i < this.blocks.length; i++) {
+ var block = this.blocks[i];
+
+ if (block.image) {
+ block.div.removeChild(block.image);
+ }
+ block.image = null;
+
+ if (block.div) {
+ this.groupDiv.removeChild(block.div);
+ }
+ block.div = null;
+ }
+ this.blocks = null;
+
+ OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setBackgroundColor
+ */
+ setBackgroundColor:function(color) {
+ //does nothing since the framed popup's entire scheme is based on a
+ // an image -- changing the background color makes no sense.
+ },
+
+ /**
+ * APIMethod: setBorder
+ */
+ setBorder:function() {
+ //does nothing since the framed popup's entire scheme is based on a
+ // an image -- changing the popup's border makes no sense.
+ },
+
+ /**
+ * Method: setOpacity
+ * Sets the opacity of the popup.
+ *
+ * Parameters:
+ * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
+ */
+ setOpacity:function(opacity) {
+ //does nothing since we suppose that we'll never apply an opacity
+ // to a framed popup
+ },
+
+ /**
+ * APIMethod: setSize
+ * Overridden here, because we need to update the blocks whenever the size
+ * of the popup has changed.
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+ this.updateBlocks();
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * When the relative position changes, we need to set the new padding
+ * BBOX on the popup, reposition the close div, and update the blocks.
+ */
+ updateRelativePosition: function() {
+
+ //update the padding
+ this.padding = this.positionBlocks[this.relativePosition].padding;
+
+ //update the position of our close box to new padding
+ if (this.closeDiv) {
+ // use the content div's css padding to determine if we should
+ // padd the close div
+ var contentDivPadding = this.getContentDivPadding();
+
+ this.closeDiv.style.right = contentDivPadding.right +
+ this.padding.right + "px";
+ this.closeDiv.style.top = contentDivPadding.top +
+ this.padding.top + "px";
+ }
+
+ this.updateBlocks();
+ },
+
+ /**
+ * Method: calculateNewPx
+ * Besides the standard offset as determined by the Anchored class, our
+ * Framed popups have a special 'offset' property for each of their
+ * positions, which is used to offset the popup relative to its anchor.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+ * relative to the passed-in px.
+ */
+ calculateNewPx:function(px) {
+ var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(
+ this, arguments
+ );
+
+ newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset);
+
+ return newPx;
+ },
+
+ /**
+ * Method: createBlocks
+ */
+ createBlocks: function() {
+ this.blocks = [];
+
+ //since all positions contain the same number of blocks, we can
+ // just pick the first position and use its blocks array to create
+ // our blocks array
+ var firstPosition = null;
+ for(var key in this.positionBlocks) {
+ firstPosition = key;
+ break;
+ }
+
+ var position = this.positionBlocks[firstPosition];
+ for (var i = 0; i < position.blocks.length; i++) {
+
+ var block = {};
+ this.blocks.push(block);
+
+ var divId = this.id + '_FrameDecorationDiv_' + i;
+ block.div = OpenLayers.Util.createDiv(divId,
+ null, null, null, "absolute", null, "hidden", null
+ );
+
+ var imgId = this.id + '_FrameDecorationImg_' + i;
+ var imageCreator =
+ (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv
+ : OpenLayers.Util.createImage;
+
+ block.image = imageCreator(imgId,
+ null, this.imageSize, this.imageSrc,
+ "absolute", null, null, null
+ );
+
+ block.div.appendChild(block.image);
+ this.groupDiv.appendChild(block.div);
+ }
+ },
+
+ /**
+ * Method: updateBlocks
+ * Internal method, called on initialize and when the popup's relative
+ * position has changed. This function takes care of re-positioning
+ * the popup's blocks in their appropropriate places.
+ */
+ updateBlocks: function() {
+ if (!this.blocks) {
+ this.createBlocks();
+ }
+
+ if (this.size && this.relativePosition) {
+ var position = this.positionBlocks[this.relativePosition];
+ for (var i = 0; i < position.blocks.length; i++) {
+
+ var positionBlock = position.blocks[i];
+ var block = this.blocks[i];
+
+ // adjust sizes
+ var l = positionBlock.anchor.left;
+ var b = positionBlock.anchor.bottom;
+ var r = positionBlock.anchor.right;
+ var t = positionBlock.anchor.top;
+
+ //note that we use the isNaN() test here because if the
+ // size object is initialized with a "auto" parameter, the
+ // size constructor calls parseFloat() on the string,
+ // which will turn it into NaN
+ //
+ var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l)
+ : positionBlock.size.w;
+
+ var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t)
+ : positionBlock.size.h;
+
+ block.div.style.width = (w < 0 ? 0 : w) + 'px';
+ block.div.style.height = (h < 0 ? 0 : h) + 'px';
+
+ block.div.style.left = (l != null) ? l + 'px' : '';
+ block.div.style.bottom = (b != null) ? b + 'px' : '';
+ block.div.style.right = (r != null) ? r + 'px' : '';
+ block.div.style.top = (t != null) ? t + 'px' : '';
+
+ block.image.style.left = positionBlock.position.x + 'px';
+ block.image.style.top = positionBlock.position.y + 'px';
+ }
+
+ this.contentDiv.style.left = this.padding.left + "px";
+ this.contentDiv.style.top = this.padding.top + "px";
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.Framed"
+});
+/* ======================================================================
+ OpenLayers/Popup/FramedCloud.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Framed.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.FramedCloud
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Framed>
+ */
+OpenLayers.Popup.FramedCloud =
+ OpenLayers.Class(OpenLayers.Popup.Framed, {
+
+ /**
+ * Property: contentDisplayClass
+ * {String} The CSS class of the popup content div.
+ */
+ contentDisplayClass: "olFramedCloudPopupContent",
+
+ /**
+ * APIProperty: autoSize
+ * {Boolean} Framed Cloud is autosizing by default.
+ */
+ autoSize: true,
+
+ /**
+ * APIProperty: panMapIfOutOfView
+ * {Boolean} Framed Cloud does pan into view by default.
+ */
+ panMapIfOutOfView: true,
+
+ /**
+ * APIProperty: imageSize
+ * {<OpenLayers.Size>}
+ */
+ imageSize: new OpenLayers.Size(1276, 736),
+
+ /**
+ * APIProperty: isAlphaImage
+ * {Boolean} The FramedCloud does not use an alpha image (in honor of the
+ * good ie6 folk out there)
+ */
+ isAlphaImage: false,
+
+ /**
+ * APIProperty: fixedRelativePosition
+ * {Boolean} The Framed Cloud popup works in just one fixed position.
+ */
+ fixedRelativePosition: false,
+
+ /**
+ * Property: positionBlocks
+ * {Object} Hash of differen position blocks, keyed by relativePosition
+ * two-character code string (ie "tl", "tr", "bl", "br")
+ */
+ positionBlocks: {
+ "tl": {
+ 'offset': new OpenLayers.Pixel(44, 0),
+ 'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 19),
+ anchor: new OpenLayers.Bounds(0, 32, 22, null),
+ position: new OpenLayers.Pixel(0, -631)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 18),
+ anchor: new OpenLayers.Bounds(null, 32, 0, null),
+ position: new OpenLayers.Pixel(-1238, -632)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 35),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(0, -688)
+ }
+ ]
+ },
+ "tr": {
+ 'offset': new OpenLayers.Pixel(-45, 0),
+ 'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 19),
+ anchor: new OpenLayers.Bounds(0, 32, 22, null),
+ position: new OpenLayers.Pixel(0, -631)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 19),
+ anchor: new OpenLayers.Bounds(null, 32, 0, null),
+ position: new OpenLayers.Pixel(-1238, -631)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 35),
+ anchor: new OpenLayers.Bounds(0, 0, null, null),
+ position: new OpenLayers.Pixel(-215, -687)
+ }
+ ]
+ },
+ "bl": {
+ 'offset': new OpenLayers.Pixel(45, 0),
+ 'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 21),
+ anchor: new OpenLayers.Bounds(0, 0, 22, null),
+ position: new OpenLayers.Pixel(0, -629)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 21),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(-1238, -629)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 33),
+ anchor: new OpenLayers.Bounds(null, null, 0, 0),
+ position: new OpenLayers.Pixel(-101, -674)
+ }
+ ]
+ },
+ "br": {
+ 'offset': new OpenLayers.Pixel(-44, 0),
+ 'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 21),
+ anchor: new OpenLayers.Bounds(0, 0, 22, null),
+ position: new OpenLayers.Pixel(0, -629)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 21),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(-1238, -629)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 33),
+ anchor: new OpenLayers.Bounds(0, null, null, 0),
+ position: new OpenLayers.Pixel(-311, -674)
+ }
+ ]
+ }
+ },
+
+ /**
+ * APIProperty: minSize
+ * {<OpenLayers.Size>}
+ */
+ minSize: new OpenLayers.Size(105, 10),
+
+ /**
+ * APIProperty: maxSize
+ * {<OpenLayers.Size>}
+ */
+ maxSize: new OpenLayers.Size(1200, 660),
+
+ /**
+ * Constructor: OpenLayers.Popup.FramedCloud
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ this.imageSrc = OpenLayers.Util.getImageLocation('cloud-popup-relative.png');
+ OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments);
+ this.contentDiv.className = this.contentDisplayClass;
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.FramedCloud"
+});
+/* ======================================================================
+ OpenLayers/Tile/Image/IFrame.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Constant: OpenLayers.Tile.Image.IFrame
+ * Mixin for tiles that use form-encoded POST requests to get images from
+ * remote services. Images will be loaded using HTTP-POST into an IFrame.
+ *
+ * This mixin will be applied to <OpenLayers.Tile.Image> instances
+ * configured with <OpenLayers.Tile.Image.maxGetUrlLength> set.
+ */
+OpenLayers.Tile.Image.IFrame = {
+
+ /**
+ * Property: useIFrame
+ * {Boolean} true if we are currently using an IFrame to render POST
+ * responses, false if we are using an img element to render GET responses.
+ */
+ useIFrame: null,
+
+ /**
+ * Property: blankImageUrl
+ * {String} Using a data scheme url is not supported by all browsers, but
+ * we don't care because we either set it as css backgroundImage, or the
+ * image's display style is set to "none" when we use it.
+ */
+ blankImageUrl: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7",
+
+ /**
+ * Method: draw
+ * Set useIFrame in the instance, and operate the image/iframe switch.
+ * Then call Tile.Image.draw.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ draw: function() {
+ var draw = OpenLayers.Tile.Image.prototype.shouldDraw.call(this);
+ if(draw) {
+
+ // this.url isn't set to the currect value yet, so we call getURL
+ // on the layer and store the result in a local variable
+ var url = this.layer.getURL(this.bounds);
+
+ var usedIFrame = this.useIFrame;
+ this.useIFrame = this.maxGetUrlLength !== null &&
+ !this.layer.async &&
+ url.length > this.maxGetUrlLength;
+
+ var fromIFrame = usedIFrame && !this.useIFrame;
+ var toIFrame = !usedIFrame && this.useIFrame;
+
+ if(fromIFrame || toIFrame) {
+
+ // Switching between GET (image) and POST (iframe).
+
+ // We remove the imgDiv (really either an image or an iframe)
+ // from the frame and set it to null to make sure initImage
+ // will call getImage.
+
+ if(this.imgDiv && this.imgDiv.parentNode === this.frame) {
+ this.frame.removeChild(this.imgDiv);
+ }
+ this.imgDiv = null;
+
+ // And if we had an iframe we also remove the event pane.
+
+ if(fromIFrame) {
+ this.frame.removeChild(this.frame.firstChild);
+ }
+ }
+ }
+ return OpenLayers.Tile.Image.prototype.draw.apply(this, arguments);
+ },
+
+ /**
+ * Method: getImage
+ * Creates the content for the frame on the tile.
+ */
+ getImage: function() {
+ if (this.useIFrame === true) {
+ if (!this.frame.childNodes.length) {
+ var eventPane = document.createElement("div"),
+ style = eventPane.style;
+ style.position = "absolute";
+ style.width = "100%";
+ style.height = "100%";
+ style.zIndex = 1;
+ style.backgroundImage = "url(" + this.blankImageUrl + ")";
+ this.frame.appendChild(eventPane);
+ }
+
+ var id = this.id + '_iFrame', iframe;
+ if (parseFloat(navigator.appVersion.split("MSIE")[1]) < 9) {
+ // Older IE versions do not set the name attribute of an iFrame
+ // properly via DOM manipulation, so we need to do it on our own with
+ // this hack.
+ iframe = document.createElement('<iframe name="'+id+'">');
+
+ // IFrames in older IE versions are not transparent, if you set
+ // the backgroundColor transparent. This is a workaround to get
+ // transparent iframes.
+ iframe.style.backgroundColor = '#FFFFFF';
+ iframe.style.filter = 'chroma(color=#FFFFFF)';
+ }
+ else {
+ iframe = document.createElement('iframe');
+ iframe.style.backgroundColor = 'transparent';
+
+ // iframe.name needs to be an unique id, otherwise it
+ // could happen that other iframes are overwritten.
+ iframe.name = id;
+ }
+
+ // some special properties to avoid scaling the images and scrollbars
+ // in the iframe
+ iframe.scrolling = 'no';
+ iframe.marginWidth = '0px';
+ iframe.marginHeight = '0px';
+ iframe.frameBorder = '0';
+
+ iframe.style.position = "absolute";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+
+ if (this.layer.opacity < 1) {
+ OpenLayers.Util.modifyDOMElement(iframe, null, null, null,
+ null, null, null, this.layer.opacity);
+ }
+ this.frame.appendChild(iframe);
+ this.imgDiv = iframe;
+ return iframe;
+ } else {
+ return OpenLayers.Tile.Image.prototype.getImage.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Method: createRequestForm
+ * Create the html <form> element with width, height, bbox and all
+ * parameters specified in the layer params.
+ *
+ * Returns:
+ * {DOMElement} The form element which sends the HTTP-POST request to the
+ * WMS.
+ */
+ createRequestForm: function() {
+ // creation of the form element
+ var form = document.createElement('form');
+ form.method = 'POST';
+ var cacheId = this.layer.params["_OLSALT"];
+ cacheId = (cacheId ? cacheId + "_" : "") + this.bounds.toBBOX();
+ form.action = OpenLayers.Util.urlAppend(this.layer.url, cacheId);
+ form.target = this.id + '_iFrame';
+
+ // adding all parameters in layer params as hidden fields to the html
+ // form element
+ var imageSize = this.layer.getImageSize(),
+ params = OpenLayers.Util.getParameters(this.url),
+ field;
+
+ for(var par in params) {
+ field = document.createElement('input');
+ field.type = 'hidden';
+ field.name = par;
+ field.value = params[par];
+ form.appendChild(field);
+ }
+
+ return form;
+ },
+
+ /**
+ * Method: setImgSrc
+ * Sets the source for the tile image
+ *
+ * Parameters:
+ * url - {String}
+ */
+ setImgSrc: function(url) {
+ if (this.useIFrame === true) {
+ if (url) {
+ var form = this.createRequestForm();
+ this.frame.appendChild(form);
+ form.submit();
+ this.frame.removeChild(form);
+ } else if (this.imgDiv.parentNode === this.frame) {
+ // we don't reuse iframes to avoid caching issues
+ this.frame.removeChild(this.imgDiv);
+ this.imgDiv = null;
+ }
+ } else {
+ OpenLayers.Tile.Image.prototype.setImgSrc.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Method: onImageLoad
+ * Handler for the image onload event
+ */
+ onImageLoad: function() {
+ //TODO de-uglify opacity handling
+ OpenLayers.Tile.Image.prototype.onImageLoad.apply(this, arguments);
+ if (this.useIFrame === true) {
+ this.imgDiv.style.opacity = 1;
+ this.frame.style.opacity = this.layer.opacity;
+ }
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Override createBackBuffer to do nothing when we use an iframe. Moving an
+ * iframe from one element to another makes it necessary to reload the iframe
+ * because its content is lost. So we just give up.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createBackBuffer: function() {
+ var backBuffer;
+ if(this.useIFrame === false) {
+ backBuffer = OpenLayers.Tile.Image.prototype.createBackBuffer.call(this);
+ }
+ return backBuffer;
+ }
+};
+/* ======================================================================
+ OpenLayers/Format/SOSCapabilities.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSCapabilities
+ * Read SOS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.SOSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Format.SOSCapabilities
+ * Create a new parser for SOS Capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return information about
+ * the service (offering and observedProperty mostly).
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Info about the SOS
+ */
+
+ CLASS_NAME: "OpenLayers.Format.SOSCapabilities"
+
+});
+/* ======================================================================
+ OpenLayers/Format/SOSCapabilities/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SOSCapabilities.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSCapabilities.v1_0_0
+ * Read SOS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.SOSCapabilities>
+ */
+OpenLayers.Format.SOSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.SOSCapabilities, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ sos: "http://www.opengis.net/sos/1.0",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SOSCapabilities.v1_0_0
+ * Create a new parser for SOS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return info about the SOS.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the SOS service.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "TimePeriod": function(node, obj) {
+ obj.timePeriod = {};
+ this.readChildNodes(node, obj.timePeriod);
+ },
+ "beginPosition": function(node, timePeriod) {
+ timePeriod.beginPosition = this.getChildValue(node);
+ },
+ "endPosition": function(node, timePeriod) {
+ timePeriod.endPosition = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.GML.v3.prototype.readers["gml"]),
+ "sos": {
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Contents": function(node, obj) {
+ obj.contents = {};
+ this.readChildNodes(node, obj.contents);
+ },
+ "ObservationOfferingList": function(node, contents) {
+ contents.offeringList = {};
+ this.readChildNodes(node, contents.offeringList);
+ },
+ "ObservationOffering": function(node, offeringList) {
+ var id = this.getAttributeNS(node, this.namespaces.gml, "id");
+ offeringList[id] = {
+ procedures: [],
+ observedProperties: [],
+ featureOfInterestIds: [],
+ responseFormats: [],
+ resultModels: [],
+ responseModes: []
+ };
+ this.readChildNodes(node, offeringList[id]);
+ },
+ "time": function(node, offering) {
+ offering.time = {};
+ this.readChildNodes(node, offering.time);
+ },
+ "procedure": function(node, offering) {
+ offering.procedures.push(this.getAttributeNS(node,
+ this.namespaces.xlink, "href"));
+ },
+ "observedProperty": function(node, offering) {
+ offering.observedProperties.push(this.getAttributeNS(node,
+ this.namespaces.xlink, "href"));
+ },
+ "featureOfInterest": function(node, offering) {
+ offering.featureOfInterestIds.push(this.getAttributeNS(node,
+ this.namespaces.xlink, "href"));
+ },
+ "responseFormat": function(node, offering) {
+ offering.responseFormats.push(this.getChildValue(node));
+ },
+ "resultModel": function(node, offering) {
+ offering.resultModels.push(this.getChildValue(node));
+ },
+ "responseMode": function(node, offering) {
+ offering.responseModes.push(this.getChildValue(node));
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SOSCapabilities.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Handler/Pinch.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Pinch
+ * The pinch handler is used to deal with sequences of browser events related
+ * to pinch gestures. The handler is used by controls that want to know
+ * when a pinch sequence begins, when a pinch is happening, and when it has
+ * finished.
+ *
+ * Controls that use the pinch handler typically construct it with callbacks
+ * for 'start', 'move', and 'done'. Callbacks for these keys are
+ * called when the pinch begins, with each change, and when the pinch is
+ * done.
+ *
+ * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a touchstart event is received, we want to record it,
+ * but not set 'pinching' until the touchmove get started after
+ * starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of touchstart events from getting to
+ * listeners on the same element. Default is false.
+ */
+ stopDown: false,
+
+ /**
+ * Property: pinching
+ * {Boolean}
+ */
+ pinching: false,
+
+ /**
+ * Property: last
+ * {Object} Object that store informations related to pinch last touch.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {Object} Object that store informations related to pinch touchstart.
+ */
+ start: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Pinch
+ * Returns OpenLayers.Handler.Pinch
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing functions to be called when
+ * the pinch operation start, change, or is finished. The callbacks
+ * should expect to receive an object argument, which contains
+ * information about scale, distance, and position of touch points.
+ * options - {Object}
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ var propagate = true;
+ this.pinching = false;
+ if (OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = true;
+ this.last = this.start = {
+ distance: this.getDistance(evt.touches),
+ delta: 0,
+ scale: 1
+ };
+ this.callback("start", [evt, this.start]);
+ propagate = !this.stopDown;
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+ return propagate;
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
+ this.pinching = true;
+ var current = this.getPinchData(evt);
+ this.callback("move", [evt, current]);
+ this.last = current;
+ // prevent document dragging
+ OpenLayers.Event.stop(evt);
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ if (this.started && !OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = false;
+ this.pinching = false;
+ this.callback("done", [evt, this.start, this.last]);
+ this.start = null;
+ this.last = null;
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.pinching = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.pinching = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: getDistance
+ * Get the distance in pixels between two touches.
+ *
+ * Parameters:
+ * touches - {Array(Object)}
+ *
+ * Returns:
+ * {Number} The distance in pixels.
+ */
+ getDistance: function(touches) {
+ var t0 = touches[0];
+ var t1 = touches[1];
+ return Math.sqrt(
+ Math.pow(t0.olClientX - t1.olClientX, 2) +
+ Math.pow(t0.olClientY - t1.olClientY, 2)
+ );
+ },
+
+
+ /**
+ * Method: getPinchData
+ * Get informations about the pinch event.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Object} Object that contains data about the current pinch.
+ */
+ getPinchData: function(evt) {
+ var distance = this.getDistance(evt.touches);
+ var scale = distance / this.start.distance;
+ return {
+ distance: distance,
+ delta: this.last.distance - distance,
+ scale: scale
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Pinch"
+});
+
+/* ======================================================================
+ OpenLayers/Control/NavToolbar.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/ZoomBox.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavToolbar
+ * This Toolbar is an alternative to the Navigation control that displays
+ * the state of the control, and provides a UI for changing state to
+ * use the zoomBox via a Panel control.
+ *
+ * If you wish to change the properties of the Navigation control used
+ * in the NavToolbar, see:
+ * http://trac.openlayers.org/wiki/Toolbars#SubclassingNavToolbar
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.NavToolbar = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * Constructor: OpenLayers.Control.NavToolbar
+ * Add our two mousedefaults controls.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ this.addControls([
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.ZoomBox()
+ ]);
+ },
+
+ /**
+ * Method: draw
+ * calls the default draw, and then activates mouse defaults.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+ if (this.defaultControl === null) {
+ this.defaultControl = this.controls[0];
+ }
+ return div;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.NavToolbar"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Refresh.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Refresh
+ * A strategy that refreshes the layer. By default the strategy waits for a
+ * call to <refresh> before refreshing. By configuring the strategy with
+ * the <interval> option, refreshing can take place automatically.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Refresh = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: force
+ * {Boolean} Force a refresh on the layer. Default is false.
+ */
+ force: false,
+
+ /**
+ * Property: interval
+ * {Number} Auto-refresh. Default is 0. If > 0, layer will be refreshed
+ * every N milliseconds.
+ */
+ interval: 0,
+
+ /**
+ * Property: timer
+ * {Number} The id of the timer.
+ */
+ timer: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Refresh
+ * Create a new Refresh strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ if(this.layer.visibility === true) {
+ this.start();
+ }
+ this.layer.events.on({
+ "visibilitychanged": this.reset,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.stop();
+ this.layer.events.un({
+ "visibilitychanged": this.reset,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: reset
+ * Start or cancel the refresh interval depending on the visibility of
+ * the layer.
+ */
+ reset: function() {
+ if(this.layer.visibility === true) {
+ this.start();
+ } else {
+ this.stop();
+ }
+ },
+
+ /**
+ * Method: start
+ * Start the refresh interval.
+ */
+ start: function() {
+ if(this.interval && typeof this.interval === "number" &&
+ this.interval > 0) {
+
+ this.timer = window.setInterval(
+ OpenLayers.Function.bind(this.refresh, this),
+ this.interval);
+ }
+ },
+
+ /**
+ * APIMethod: refresh
+ * Tell the strategy to refresh which will refresh the layer.
+ */
+ refresh: function() {
+ if (this.layer && this.layer.refresh &&
+ typeof this.layer.refresh == "function") {
+
+ this.layer.refresh({force: this.force});
+ }
+ },
+
+ /**
+ * Method: stop
+ * Cancels the refresh interval.
+ */
+ stop: function() {
+ if(this.timer !== null) {
+ window.clearInterval(this.timer);
+ this.timer = null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Refresh"
+});
+/* ======================================================================
+ OpenLayers/Layer/ArcGIS93Rest.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.ArcGIS93Rest
+ * Instances of OpenLayers.Layer.ArcGIS93Rest are used to display data from
+ * ESRI ArcGIS Server 9.3 (and up?) Mapping Services using the REST API.
+ * Create a new ArcGIS93Rest layer with the <OpenLayers.Layer.ArcGIS93Rest>
+ * constructor. More detail on the REST API is available at
+ * http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/index.html ;
+ * specifically, the URL provided to this layer should be an export service
+ * URL: http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/export.html
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.ArcGIS93Rest = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: {
+ format: "png"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for ArcGIS93Rest layer
+ */
+ isBaseLayer: true,
+
+
+ /**
+ * Constructor: OpenLayers.Layer.ArcGIS93Rest
+ * Create a new ArcGIS93Rest layer object.
+ *
+ * Example:
+ * (code)
+ * var arcims = new OpenLayers.Layer.ArcGIS93Rest("MyName",
+ * "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer/export",
+ * {
+ * layers: "0,1,2"
+ * });
+ * (end)
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the ArcGIS server REST service
+ * options - {Object} An object with key/value pairs representing the
+ * options and option values.
+ *
+ * Valid Options:
+ * format - {String} MIME type of desired image type.
+ * layers - {String} Comma-separated list of layers to display.
+ * srs - {String} Projection ID.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+ //layer is transparent
+ if (this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "jpg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "gif"
+ : "png";
+ }
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.ArcGIS93Rest>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.ArcGIS93Rest(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+
+ /**
+ * Method: getURL
+ * Return an image url this layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the map image's url.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+
+ // ArcGIS Server only wants the numeric portion of the projection ID.
+ var projWords = this.projection.getCode().split(":");
+ var srid = projWords[projWords.length - 1];
+
+ var imageSize = this.getImageSize();
+ var newParams = {
+ 'BBOX': bounds.toBBOX(),
+ 'SIZE': imageSize.w + "," + imageSize.h,
+ // We always want image, the other options were json, image with a whole lotta html around it, etc.
+ 'F': "image",
+ 'BBOXSR': srid,
+ 'IMAGESR': srid
+ };
+
+ // Now add the filter parameters.
+ if (this.layerDefs) {
+ var layerDefStrList = [];
+ var layerID;
+ for(layerID in this.layerDefs) {
+ if (this.layerDefs.hasOwnProperty(layerID)) {
+ if (this.layerDefs[layerID]) {
+ layerDefStrList.push(layerID);
+ layerDefStrList.push(":");
+ layerDefStrList.push(this.layerDefs[layerID]);
+ layerDefStrList.push(";");
+ }
+ }
+ }
+ if (layerDefStrList.length > 0) {
+ newParams['LAYERDEFS'] = layerDefStrList.join("");
+ }
+ }
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * Method: setLayerFilter
+ * addTile creates a tile, initializes it, and adds it to the layer div.
+ *
+ * Parameters:
+ * id - {String} The id of the layer to which the filter applies.
+ * queryDef - {String} A sql-ish query filter, for more detail see the ESRI
+ * documentation at http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/export.html
+ */
+ setLayerFilter: function ( id, queryDef ) {
+ if (!this.layerDefs) {
+ this.layerDefs = {};
+ }
+ if (queryDef) {
+ this.layerDefs[id] = queryDef;
+ } else {
+ delete this.layerDefs[id];
+ }
+ },
+
+ /**
+ * Method: clearLayerFilter
+ * Clears layer filters, either from a specific layer,
+ * or all of them.
+ *
+ * Parameters:
+ * id - {String} The id of the layer from which to remove any
+ * filter. If unspecified/blank, all filters
+ * will be removed.
+ */
+ clearLayerFilter: function ( id ) {
+ if (id) {
+ delete this.layerDefs[id];
+ } else {
+ delete this.layerDefs;
+ }
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.ArcGIS93Rest"
+});
+/* ======================================================================
+ OpenLayers/Handler/Hover.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Hover
+ * The hover handler is to be used to emulate mouseovers on objects
+ * on the map that aren't DOM elements. For example one can use
+ * this handler to send WMS/GetFeatureInfo requests as the user
+ * moves the mouve over the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Hover = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * APIProperty: delay
+ * {Integer} - Number of milliseconds between mousemoves before
+ * the event is considered a hover. Default is 500.
+ */
+ delay: 500,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Integer} - Maximum number of pixels between mousemoves for
+ * an event to be considered a hover. Default is null.
+ */
+ pixelTolerance: null,
+
+ /**
+ * APIProperty: stopMove
+ * {Boolean} - Stop other listeners from being notified on mousemoves.
+ * Default is false.
+ */
+ stopMove: false,
+
+ /**
+ * Property: px
+ * {<OpenLayers.Pixel>} - The location of the last mousemove, expressed
+ * in pixels.
+ */
+ px: null,
+
+ /**
+ * Property: timerId
+ * {Number} - The id of the timer.
+ */
+ timerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Hover
+ * Construct a hover handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that initialized this
+ * handler. The control is assumed to have a valid map property; that
+ * map is used in the handler's own setMap method.
+ * callbacks - {Object} An object with keys corresponding to callbacks
+ * that will be called by the handler. The callbacks should
+ * expect to receive a single argument, the event. Callbacks for
+ * 'move', the mouse is moving, and 'pause', the mouse is pausing,
+ * are supported.
+ * options - {Object} An optional object whose properties will be set on
+ * the handler.
+ */
+
+ /**
+ * Method: mousemove
+ * Called when the mouse moves on the map.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mousemove: function(evt) {
+ if(this.passesTolerance(evt.xy)) {
+ this.clearTimer();
+ this.callback('move', [evt]);
+ this.px = evt.xy;
+ // clone the evt so original properties can be accessed even
+ // if the browser deletes them during the delay
+ evt = OpenLayers.Util.extend({}, evt);
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ }
+ return !this.stopMove;
+ },
+
+ /**
+ * Method: mouseout
+ * Called when the mouse goes out of the map.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mouseout: function(evt) {
+ if (OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ this.clearTimer();
+ this.callback('move', [evt]);
+ }
+ return true;
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the mouse move is within the optional pixel tolerance.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Boolean} The mouse move is within the pixel tolerance.
+ */
+ passesTolerance: function(px) {
+ var passes = true;
+ if(this.pixelTolerance && this.px) {
+ var dpx = Math.sqrt(
+ Math.pow(this.px.x - px.x, 2) +
+ Math.pow(this.px.y - px.y, 2)
+ );
+ if(dpx < this.pixelTolerance) {
+ passes = false;
+ }
+ }
+ return passes;
+ },
+
+ /**
+ * Method: clearTimer
+ * Clear the timer and set <timerId> to null.
+ */
+ clearTimer: function() {
+ if(this.timerId != null) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ },
+
+ /**
+ * Method: delayedCall
+ * Triggers pause callback.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ delayedCall: function(evt) {
+ this.callback('pause', [evt]);
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.clearTimer();
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Hover"
+});
+/* ======================================================================
+ OpenLayers/Control/GetFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Box.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Control.GetFeature
+ * Gets vector features for locations underneath the mouse cursor. Can be
+ * configured to act on click, hover or dragged boxes. Uses an
+ * <OpenLayers.Protocol> that supports spatial filters to retrieve
+ * features from a server and fires events that notify applications of the
+ * selected features.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.GetFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: protocol
+ * {<OpenLayers.Protocol>} Required. The protocol used for fetching
+ * features.
+ */
+ protocol: null,
+
+ /**
+ * APIProperty: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * APIProperty: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * Property: modifiers
+ * {Object} The event modifiers to use, according to the current event
+ * being handled by this control's handlers
+ */
+ modifiers: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: click
+ * {Boolean} Use a click handler for selecting/unselecting features. If
+ * both <click> and <box> are set to true, the click handler takes
+ * precedence over the box handler if a box with zero extent was
+ * selected. Default is true.
+ */
+ click: true,
+
+ /**
+ * APIProperty: single
+ * {Boolean} Tells whether select by click should select a single
+ * feature. If set to false, all matching features are selected.
+ * If set to true, only the best matching feature is selected.
+ * This option has an effect only of the <click> option is set
+ * to true. Default is true.
+ */
+ single: true,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Applies only if <click> is true. Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Applies only if
+ * <click> is true. Default is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: clickTolerance
+ * {Integer} Tolerance for the filter query in pixels. This has the
+ * same effect as the tolerance parameter on WMS GetFeatureInfo
+ * requests. Will be ignored for box selections. Applies only if
+ * <click> or <hover> is true. Default is 5. Note that this not
+ * only affects requests on click, but also on hover.
+ */
+ clickTolerance: 5,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send feature requests on mouse moves. Default is false.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box. If set to
+ * true set <click> to false to disable the click handler and
+ * rely on the box handler only, even for "zero extent" boxes.
+ * See the description of the <click> option for additional
+ * information. Default is false.
+ */
+ box: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a query in single mode
+ * if supported by the <protocol>. This set of features is then used to
+ * determine the best match client-side. Default is 10.
+ */
+ maxFeatures: 10,
+
+ /**
+ * Property: features
+ * {Object} Hash of {<OpenLayers.Feature.Vector>}, keyed by fid, holding
+ * the currently selected features
+ */
+ features: null,
+
+ /**
+ * Proeprty: hoverFeature
+ * {<OpenLayers.Feature.Vector>} The feature currently selected by the
+ * hover handler
+ */
+ hoverFeature: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control. This
+ * is a hash with the keys "click", "box" and "hover".
+ */
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Property: hoverResponse
+ * {<OpenLayers.Protocol.Response>} The response object associated with
+ * the currently running hover request (if any).
+ */
+ hoverResponse: null,
+
+ /**
+ * Property: filterType
+ * {<String>} The type of filter to use when sending off a request.
+ * Possible values:
+ * OpenLayers.Filter.Spatial.<BBOX|INTERSECTS|WITHIN|CONTAINS>
+ * Defaults to: OpenLayers.Filter.Spatial.BBOX
+ */
+ filterType: OpenLayers.Filter.Spatial.BBOX,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforefeatureselected - Triggered when <click> is true before a
+ * feature is selected. The event object has a feature property with
+ * the feature about to select
+ * featureselected - Triggered when <click> is true and a feature is
+ * selected. The event object has a feature property with the
+ * selected feature
+ * beforefeaturesselected - Triggered when <click> is true before a
+ * set of features is selected. The event object is an array of
+ * feature properties with the features about to be selected.
+ * Return false after receiving this event to discontinue processing
+ * of all featureselected events and the featuresselected event.
+ * featuresselected - Triggered when <click> is true and a set of
+ * features is selected. The event object is an array of feature
+ * properties of the selected features
+ * featureunselected - Triggered when <click> is true and a feature is
+ * unselected. The event object has a feature property with the
+ * unselected feature
+ * clickout - Triggered when when <click> is true and no feature was
+ * selected.
+ * hoverfeature - Triggered when <hover> is true and the mouse has
+ * stopped over a feature
+ * outfeature - Triggered when <hover> is true and the mouse moves
+ * moved away from a hover-selected feature
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.GetFeature
+ * Create a new control for fetching remote features.
+ *
+ * Parameters:
+ * options - {Object} A configuration object which at least has to contain
+ * a <protocol> property (if not, it has to be set before a request is
+ * made)
+ */
+ initialize: function(options) {
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.features = {};
+
+ this.handlers = {};
+
+ if(this.click) {
+ this.handlers.click = new OpenLayers.Handler.Click(this,
+ {click: this.selectClick}, this.handlerOptions.click || {});
+ }
+
+ if(this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ OpenLayers.Util.extend(this.handlerOptions.box, {
+ boxDivClassName: "olHandlerBoxSelectFeature"
+ })
+ );
+ }
+
+ if(this.hover) {
+ this.handlers.hover = new OpenLayers.Handler.Hover(
+ this, {'move': this.cancelHover, 'pause': this.selectHover},
+ OpenLayers.Util.extend(this.handlerOptions.hover, {
+ 'delay': 250,
+ 'pixelTolerance': 2
+ })
+ );
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ for(var i in this.handlers) {
+ this.handlers[i].activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ for(var i in this.handlers) {
+ this.handlers[i].deactivate();
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: selectClick
+ * Called on click
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ selectClick: function(evt) {
+ var bounds = this.pixelToBounds(evt.xy);
+
+ this.setModifiers(evt);
+ this.request(bounds, {single: this.single});
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is on
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds>|Object} An OpenLayers.Bounds or
+ * an object with a 'left', 'bottom', 'right' and 'top' properties.
+ */
+ selectBox: function(position) {
+ var bounds;
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ } else {
+ if(this.click) {
+ // box without extent - let the click handler take care of it
+ return;
+ }
+ bounds = this.pixelToBounds(position);
+ }
+ this.setModifiers(this.handlers.box.dragHandler.evt);
+ this.request(bounds);
+ },
+
+ /**
+ * Method: selectHover
+ * Callback from the handlers.hover set up when <hover> selection is on
+ *
+ * Parameters:
+ * evt - {Object} event object with an xy property
+ */
+ selectHover: function(evt) {
+ var bounds = this.pixelToBounds(evt.xy);
+ this.request(bounds, {single: true, hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Callback from the handlers.hover set up when <hover> selection is on
+ */
+ cancelHover: function() {
+ if (this.hoverResponse) {
+ this.protocol.abort(this.hoverResponse);
+ this.hoverResponse = null;
+
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ }
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeature request to the WFS
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} bounds for the request's BBOX filter
+ * options - {Object} additional options for this method.
+ *
+ * Supported options include:
+ * single - {Boolean} A single feature should be returned.
+ * Note that this will be ignored if the protocol does not
+ * return the geometries of the features.
+ * hover - {Boolean} Do the request for the hover handler.
+ */
+ request: function(bounds, options) {
+ options = options || {};
+ var filter = new OpenLayers.Filter.Spatial({
+ type: this.filterType,
+ value: bounds
+ });
+
+ // Set the cursor to "wait" to tell the user we're working.
+ OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
+
+ var response = this.protocol.read({
+ maxFeatures: options.single == true ? this.maxFeatures : undefined,
+ filter: filter,
+ callback: function(result) {
+ if(result.success()) {
+ if(result.features.length) {
+ if(options.single == true) {
+ this.selectBestFeature(result.features,
+ bounds.getCenterLonLat(), options);
+ } else {
+ this.select(result.features);
+ }
+ } else if(options.hover) {
+ this.hoverSelect();
+ } else {
+ this.events.triggerEvent("clickout");
+ if(this.clickout) {
+ this.unselectAll();
+ }
+ }
+ }
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ },
+ scope: this
+ });
+ if(options.hover == true) {
+ this.hoverResponse = response;
+ }
+ },
+
+ /**
+ * Method: selectBestFeature
+ * Selects the feature from an array of features that is the best match
+ * for the click position.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * clickPosition - {<OpenLayers.LonLat>}
+ * options - {Object} additional options for this method
+ *
+ * Supported options include:
+ * hover - {Boolean} Do the selection for the hover handler.
+ */
+ selectBestFeature: function(features, clickPosition, options) {
+ options = options || {};
+ if(features.length) {
+ var point = new OpenLayers.Geometry.Point(clickPosition.lon,
+ clickPosition.lat);
+ var feature, resultFeature, dist;
+ var minDist = Number.MAX_VALUE;
+ for(var i=0; i<features.length; ++i) {
+ feature = features[i];
+ if(feature.geometry) {
+ dist = point.distanceTo(feature.geometry, {edge: false});
+ if(dist < minDist) {
+ minDist = dist;
+ resultFeature = feature;
+ if(minDist == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ if(options.hover == true) {
+ this.hoverSelect(resultFeature);
+ } else {
+ this.select(resultFeature || features);
+ }
+ }
+ },
+
+ /**
+ * Method: setModifiers
+ * Sets the multiple and toggle modifiers according to the current event
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ setModifiers: function(evt) {
+ this.modifiers = {
+ multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]),
+ toggle: this.toggle || (this.toggleKey && evt[this.toggleKey])
+ };
+ },
+
+ /**
+ * Method: select
+ * Add feature to the hash of selected features and trigger the
+ * featureselected and featuresselected events.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>} or an array of features
+ */
+ select: function(features) {
+ if(!this.modifiers.multiple && !this.modifiers.toggle) {
+ this.unselectAll();
+ }
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var cont = this.events.triggerEvent("beforefeaturesselected", {
+ features: features
+ });
+ if(cont !== false) {
+ var selectedFeatures = [];
+ var feature;
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ if(this.features[feature.fid || feature.id]) {
+ if(this.modifiers.toggle) {
+ this.unselect(this.features[feature.fid || feature.id]);
+ }
+ } else {
+ cont = this.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ this.features[feature.fid || feature.id] = feature;
+ selectedFeatures.push(feature);
+
+ this.events.triggerEvent("featureselected",
+ {feature: feature});
+ }
+ }
+ }
+ this.events.triggerEvent("featuresselected", {
+ features: selectedFeatures
+ });
+ }
+ },
+
+ /**
+ * Method: hoverSelect
+ * Sets/unsets the <hoverFeature>
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} the feature to hover-select.
+ * If none is provided, the current <hoverFeature> will be nulled and
+ * the outfeature event will be triggered.
+ */
+ hoverSelect: function(feature) {
+ var fid = feature ? feature.fid || feature.id : null;
+ var hfid = this.hoverFeature ?
+ this.hoverFeature.fid || this.hoverFeature.id : null;
+
+ if(hfid && hfid != fid) {
+ this.events.triggerEvent("outfeature",
+ {feature: this.hoverFeature});
+ this.hoverFeature = null;
+ }
+ if(fid && fid != hfid) {
+ this.events.triggerEvent("hoverfeature", {feature: feature});
+ this.hoverFeature = feature;
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the hash of selected features and trigger the
+ * featureunselected event.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ delete this.features[feature.fid || feature.id];
+ this.events.triggerEvent("featureunselected", {feature: feature});
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features.
+ */
+ unselectAll: function() {
+ // we'll want an option to supress notification here
+ for(var fid in this.features) {
+ this.unselect(this.features[fid]);
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ for(var i in this.handlers) {
+ this.handlers[i].setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: pixelToBounds
+ * Takes a pixel as argument and creates bounds after adding the
+ * <clickTolerance>.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>}
+ */
+ pixelToBounds: function(pixel) {
+ var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2);
+ var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2);
+ var ll = this.map.getLonLatFromPixel(llPx);
+ var ur = this.map.getLonLatFromPixel(urPx);
+ return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.GetFeature"
+});
+/* ======================================================================
+ OpenLayers/Format/QueryStringFilter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Filter/Logical.js
+ */
+
+/**
+ * Class: OpenLayers.Format.QueryStringFilter
+ * Parser for reading a query string and creating a simple filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.QueryStringFilter = (function() {
+
+ /**
+ * Map the OpenLayers.Filter.Comparison types to the operation strings of
+ * the protocol.
+ */
+ var cmpToStr = {};
+ cmpToStr[OpenLayers.Filter.Comparison.EQUAL_TO] = "eq";
+ cmpToStr[OpenLayers.Filter.Comparison.NOT_EQUAL_TO] = "ne";
+ cmpToStr[OpenLayers.Filter.Comparison.LESS_THAN] = "lt";
+ cmpToStr[OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO] = "lte";
+ cmpToStr[OpenLayers.Filter.Comparison.GREATER_THAN] = "gt";
+ cmpToStr[OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO] = "gte";
+ cmpToStr[OpenLayers.Filter.Comparison.LIKE] = "ilike";
+
+ /**
+ * Function: regex2value
+ * Convert the value from a regular expression string to a LIKE/ILIKE
+ * string known to the web service.
+ *
+ * Parameters:
+ * value - {String} The regex string.
+ *
+ * Returns:
+ * {String} The converted string.
+ */
+ function regex2value(value) {
+
+ // highly sensitive!! Do not change this without running the
+ // Protocol/HTTP.html unit tests
+
+ // convert % to \%
+ value = value.replace(/%/g, "\\%");
+
+ // convert \\. to \\_ (\\.* occurences converted later)
+ value = value.replace(/\\\\\.(\*)?/g, function($0, $1) {
+ return $1 ? $0 : "\\\\_";
+ });
+
+ // convert \\.* to \\%
+ value = value.replace(/\\\\\.\*/g, "\\\\%");
+
+ // convert . to _ (\. and .* occurences converted later)
+ value = value.replace(/(\\)?\.(\*)?/g, function($0, $1, $2) {
+ return $1 || $2 ? $0 : "_";
+ });
+
+ // convert .* to % (\.* occurnces converted later)
+ value = value.replace(/(\\)?\.\*/g, function($0, $1) {
+ return $1 ? $0 : "%";
+ });
+
+ // convert \. to .
+ value = value.replace(/\\\./g, ".");
+
+ // replace \* with * (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "*";
+ });
+
+ return value;
+ }
+
+ return OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Default is false. If true and the layer has a projection object set,
+ * any BBOX filter will be serialized with a fifth item identifying the
+ * projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * APIMethod: write
+ * Serialize an <OpenLayers.Filter> objects using the "simple" filter syntax for
+ * query string parameters. This function must be called as a method of
+ * a protocol instance.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+ write: function(filter, params) {
+ params = params || {};
+ var className = filter.CLASS_NAME;
+ var filterType = className.substring(className.lastIndexOf(".") + 1);
+ switch (filterType) {
+ case "Spatial":
+ switch (filter.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ params.bbox = filter.value.toArray();
+ if (this.srsInBBOX && filter.projection) {
+ params.bbox.push(filter.projection.getCode());
+ }
+ break;
+ case OpenLayers.Filter.Spatial.DWITHIN:
+ params.tolerance = filter.distance;
+ // no break here
+ case OpenLayers.Filter.Spatial.WITHIN:
+ params.lon = filter.value.x;
+ params.lat = filter.value.y;
+ break;
+ default:
+ OpenLayers.Console.warn(
+ "Unknown spatial filter type " + filter.type);
+ }
+ break;
+ case "Comparison":
+ var op = cmpToStr[filter.type];
+ if (op !== undefined) {
+ var value = filter.value;
+ if (filter.type == OpenLayers.Filter.Comparison.LIKE) {
+ value = regex2value(value);
+ if (this.wildcarded) {
+ value = "%" + value + "%";
+ }
+ }
+ params[filter.property + "__" + op] = value;
+ params.queryable = params.queryable || [];
+ params.queryable.push(filter.property);
+ } else {
+ OpenLayers.Console.warn(
+ "Unknown comparison filter type " + filter.type);
+ }
+ break;
+ case "Logical":
+ if (filter.type === OpenLayers.Filter.Logical.AND) {
+ for (var i=0,len=filter.filters.length; i<len; i++) {
+ params = this.write(filter.filters[i], params);
+ }
+ } else {
+ OpenLayers.Console.warn(
+ "Unsupported logical filter type " + filter.type);
+ }
+ break;
+ default:
+ OpenLayers.Console.warn("Unknown filter type " + filterType);
+ }
+ return params;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.QueryStringFilter"
+
+ });
+
+
+})();
+/* ======================================================================
+ OpenLayers/Control/MousePosition.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.MousePosition
+ * The MousePosition control displays geographic coordinates of the mouse
+ * pointer, as it is moved about the map.
+ *
+ * You can use the <prefix>- or <suffix>-properties to provide more information
+ * about the displayed coordinates to the user:
+ *
+ * (code)
+ * var mousePositionCtrl = new OpenLayers.Control.MousePosition({
+ * prefix: '<a target="_blank" ' +
+ * 'href="http://spatialreference.org/ref/epsg/4326/">' +
+ * 'EPSG:4326</a> coordinates: '
+ * }
+ * );
+ * (end code)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.MousePosition = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: element
+ * {DOMElement}
+ */
+ element: null,
+
+ /**
+ * APIProperty: prefix
+ * {String} A string to be prepended to the current pointers coordinates
+ * when it is rendered. Defaults to the empty string ''.
+ */
+ prefix: '',
+
+ /**
+ * APIProperty: separator
+ * {String} A string to be used to seperate the two coordinates from each
+ * other. Defaults to the string ', ', which will result in a
+ * rendered coordinate of e.g. '42.12, 21.22'.
+ */
+ separator: ', ',
+
+ /**
+ * APIProperty: suffix
+ * {String} A string to be appended to the current pointers coordinates
+ * when it is rendered. Defaults to the empty string ''.
+ */
+ suffix: '',
+
+ /**
+ * APIProperty: numDigits
+ * {Integer} The number of digits each coordinate shall have when being
+ * rendered, Defaults to 5.
+ */
+ numDigits: 5,
+
+ /**
+ * APIProperty: granularity
+ * {Integer}
+ */
+ granularity: 10,
+
+ /**
+ * APIProperty: emptyString
+ * {String} Set this to some value to set when the mouse is outside the
+ * map.
+ */
+ emptyString: null,
+
+ /**
+ * Property: lastXy
+ * {<OpenLayers.Pixel>}
+ */
+ lastXy: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} The projection in which the mouse position is
+ * displayed.
+ */
+ displayProjection: null,
+
+ /**
+ * Constructor: OpenLayers.Control.MousePosition
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ this.map.events.register('mousemove', this, this.redraw);
+ this.map.events.register('mouseout', this, this.reset);
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.map.events.unregister('mousemove', this, this.redraw);
+ this.map.events.unregister('mouseout', this, this.reset);
+ this.element.innerHTML = "";
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ if (!this.element) {
+ this.div.left = "";
+ this.div.top = "";
+ this.element = this.div;
+ }
+
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function(evt) {
+
+ var lonLat;
+
+ if (evt == null) {
+ this.reset();
+ return;
+ } else {
+ if (this.lastXy == null ||
+ Math.abs(evt.xy.x - this.lastXy.x) > this.granularity ||
+ Math.abs(evt.xy.y - this.lastXy.y) > this.granularity)
+ {
+ this.lastXy = evt.xy;
+ return;
+ }
+
+ lonLat = this.map.getLonLatFromPixel(evt.xy);
+ if (!lonLat) {
+ // map has not yet been properly initialized
+ return;
+ }
+ if (this.displayProjection) {
+ lonLat.transform(this.map.getProjectionObject(),
+ this.displayProjection );
+ }
+ this.lastXy = evt.xy;
+
+ }
+
+ var newHtml = this.formatOutput(lonLat);
+
+ if (newHtml != this.element.innerHTML) {
+ this.element.innerHTML = newHtml;
+ }
+ },
+
+ /**
+ * Method: reset
+ */
+ reset: function(evt) {
+ if (this.emptyString != null) {
+ this.element.innerHTML = this.emptyString;
+ }
+ },
+
+ /**
+ * Method: formatOutput
+ * Override to provide custom display output
+ *
+ * Parameters:
+ * lonLat - {<OpenLayers.LonLat>} Location to display
+ */
+ formatOutput: function(lonLat) {
+ var digits = parseInt(this.numDigits);
+ var newHtml =
+ this.prefix +
+ lonLat.lon.toFixed(digits) +
+ this.separator +
+ lonLat.lat.toFixed(digits) +
+ this.suffix;
+ return newHtml;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.MousePosition"
+});
+/* ======================================================================
+ OpenLayers/Control/Geolocate.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Geolocate
+ * The Geolocate control wraps w3c geolocation API into control that can be
+ * bound to a map, and generate events on location update
+ *
+ * To use this control requires to load the proj4js library if the projection
+ * of the map is not EPSG:4326 or EPSG:900913.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Geolocate = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * locationupdated - Triggered when browser return a new position. Listeners will
+ * receive an object with a 'position' property which is the browser.geolocation.position
+ * native object, as well as a 'point' property which is the location transformed in the
+ * current map projection.
+ * locationfailed - Triggered when geolocation has failed
+ * locationuncapable - Triggered when control is activated on a browser
+ * which doesn't support geolocation
+ */
+
+ /**
+ * Property: geolocation
+ * {Object} The geolocation engine, as a property to be possibly mocked.
+ * This is set lazily to avoid a memory leak in IE9.
+ */
+ geolocation: null,
+
+ /**
+ * Property: available
+ * {Boolean} The navigator.geolocation object is available.
+ */
+ available: ('geolocation' in navigator),
+
+ /**
+ * APIProperty: bind
+ * {Boolean} If true, map center will be set on location update.
+ */
+ bind: true,
+
+ /**
+ * APIProperty: watch
+ * {Boolean} If true, position will be update regularly.
+ */
+ watch: false,
+
+ /**
+ * APIProperty: geolocationOptions
+ * {Object} Options to pass to the navigator's geolocation API. See
+ * <http://dev.w3.org/geo/api/spec-source.html>. No specific
+ * option is passed to the geolocation API by default.
+ */
+ geolocationOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Geolocate
+ * Create a new control to deal with browser geolocation API
+ *
+ */
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (this.available && !this.geolocation) {
+ // set lazily to avoid IE9 memory leak
+ this.geolocation = navigator.geolocation;
+ }
+ if (!this.geolocation) {
+ this.events.triggerEvent("locationuncapable");
+ return false;
+ }
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ if (this.watch) {
+ this.watchId = this.geolocation.watchPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ } else {
+ this.getCurrentLocation();
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active && this.watchId !== null) {
+ this.geolocation.clearWatch(this.watchId);
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: geolocate
+ * Activates the control.
+ *
+ */
+ geolocate: function (position) {
+ var center = new OpenLayers.LonLat(
+ position.coords.longitude,
+ position.coords.latitude
+ ).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ this.map.getProjectionObject()
+ );
+ if (this.bind) {
+ this.map.setCenter(center);
+ }
+ this.events.triggerEvent("locationupdated", {
+ position: position,
+ point: new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ )
+ });
+ },
+
+ /**
+ * APIMethod: getCurrentLocation
+ *
+ * Returns:
+ * {Boolean} Returns true if a event will be fired (successfull
+ * registration)
+ */
+ getCurrentLocation: function() {
+ if (!this.active || this.watch) {
+ return false;
+ }
+ this.geolocation.getCurrentPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ return true;
+ },
+
+ /**
+ * Method: failure
+ * method called on browser's geolocation failure
+ *
+ */
+ failure: function (error) {
+ this.events.triggerEvent("locationfailed", {error: error});
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Geolocate"
+});
+/* ======================================================================
+ OpenLayers/Tile/UTFGrid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.UTFGrid
+ * Instances of OpenLayers.Tile.UTFGrid are used to manage
+ * UTFGrids. This is an unusual tile type in that it doesn't have a
+ * rendered image; only a 'hit grid' that can be used to
+ * look up feature attributes.
+ *
+ * See the <OpenLayers.Tile.UTFGrid> constructor for details on constructing a
+ * new instance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.UTFGrid = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * Property: url
+ * {String}
+ * The URL of the UTFGrid file being requested. Provided by the <getURL>
+ * method.
+ */
+ url: null,
+
+ /**
+ * Property: utfgridResolution
+ * {Number}
+ * Ratio of the pixel width to the width of a UTFGrid data point. If an
+ * entry in the grid represents a 4x4 block of pixels, the
+ * utfgridResolution would be 4. Default is 2.
+ */
+ utfgridResolution: 2,
+
+ /**
+ * Property: json
+ * {Object}
+ * Stores the parsed JSON tile data structure.
+ */
+ json: null,
+
+ /**
+ * Property: format
+ * {OpenLayers.Format.JSON}
+ * Parser instance used to parse JSON for cross browser support. The native
+ * JSON.parse method will be used where available (all except IE<8).
+ */
+ format: null,
+
+ /**
+ * Constructor: OpenLayers.Tile.UTFGrid
+ * Constructor for a new <OpenLayers.Tile.UTFGrid> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ this.clear();
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and draw it.
+ * In the case of UTFGrids, "drawing" it means fetching and
+ * parsing the json.
+ *
+ * Returns:
+ * {Boolean} Was a tile drawn?
+ */
+ draw: function() {
+ var drawn = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+ if (drawn) {
+ if (this.isLoading) {
+ this.abortLoading();
+ //if we're already loading, send 'reload' instead of 'loadstart'.
+ this.events.triggerEvent("reload");
+ } else {
+ this.isLoading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.url = this.layer.getURL(this.bounds);
+
+ if (this.layer.useJSONP) {
+ // Use JSONP method to avoid xbrowser policy
+ var ols = new OpenLayers.Protocol.Script({
+ url: this.url,
+ callback: function(response) {
+ this.isLoading = false;
+ this.events.triggerEvent("loadend");
+ this.json = response.data;
+ },
+ scope: this
+ });
+ ols.read();
+ this.request = ols;
+ } else {
+ // Use standard XHR
+ this.request = OpenLayers.Request.GET({
+ url: this.url,
+ callback: function(response) {
+ this.isLoading = false;
+ this.events.triggerEvent("loadend");
+ if (response.status === 200) {
+ this.parseData(response.responseText);
+ }
+ },
+ scope: this
+ });
+ }
+ } else {
+ this.unload();
+ }
+ return drawn;
+ },
+
+ /**
+ * Method: abortLoading
+ * Cancel a pending request.
+ */
+ abortLoading: function() {
+ if (this.request) {
+ this.request.abort();
+ delete this.request;
+ }
+ this.isLoading = false;
+ },
+
+ /**
+ * Method: getFeatureInfo
+ * Get feature information associated with a pixel offset. If the pixel
+ * offset corresponds to a feature, the returned object will have id
+ * and data properties. Otherwise, null will be returned.
+ *
+ *
+ * Parameters:
+ * i - {Number} X-axis pixel offset (from top left of tile)
+ * j - {Number} Y-axis pixel offset (from top left of tile)
+ *
+ * Returns:
+ * {Object} Object with feature id and data properties corresponding to the
+ * given pixel offset.
+ */
+ getFeatureInfo: function(i, j) {
+ var info = null;
+ if (this.json) {
+ var id = this.getFeatureId(i, j);
+ if (id !== null) {
+ info = {id: id, data: this.json.data[id]};
+ }
+ }
+ return info;
+ },
+
+ /**
+ * Method: getFeatureId
+ * Get the identifier for the feature associated with a pixel offset.
+ *
+ * Parameters:
+ * i - {Number} X-axis pixel offset (from top left of tile)
+ * j - {Number} Y-axis pixel offset (from top left of tile)
+ *
+ * Returns:
+ * {Object} The feature identifier corresponding to the given pixel offset.
+ * Returns null if pixel doesn't correspond to a feature.
+ */
+ getFeatureId: function(i, j) {
+ var id = null;
+ if (this.json) {
+ var resolution = this.utfgridResolution;
+ var row = Math.floor(j / resolution);
+ var col = Math.floor(i / resolution);
+ var charCode = this.json.grid[row].charCodeAt(col);
+ var index = this.indexFromCharCode(charCode);
+ var keys = this.json.keys;
+ if (!isNaN(index) && (index in keys)) {
+ id = keys[index];
+ }
+ }
+ return id;
+ },
+
+ /**
+ * Method: indexFromCharCode
+ * Given a character code for one of the UTFGrid "grid" characters,
+ * resolve the integer index for the feature id in the UTFGrid "keys"
+ * array.
+ *
+ * Parameters:
+ * charCode - {Integer}
+ *
+ * Returns:
+ * {Integer} Index for the feature id from the keys array.
+ */
+ indexFromCharCode: function(charCode) {
+ if (charCode >= 93) {
+ charCode--;
+ }
+ if (charCode >= 35) {
+ charCode --;
+ }
+ return charCode - 32;
+ },
+
+ /**
+ * Method: parseData
+ * Parse the JSON from a request
+ *
+ * Parameters:
+ * str - {String} UTFGrid as a JSON string.
+ *
+ * Returns:
+ * {Object} parsed javascript data
+ */
+ parseData: function(str) {
+ if (!this.format) {
+ this.format = new OpenLayers.Format.JSON();
+ }
+ this.json = this.format.read(str);
+ },
+
+ /**
+ * Method: clear
+ * Delete data stored with this tile.
+ */
+ clear: function() {
+ this.json = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.UTFGrid"
+
+});
+/* ======================================================================
+ OpenLayers/Protocol/HTTP.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.HTTP
+ * A basic HTTP protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.HTTP> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: url
+ * {String} Service URL, read-only, set through the options
+ * passed to constructor.
+ */
+ url: null,
+
+ /**
+ * Property: headers
+ * {Object} HTTP request headers, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'Content-Type': 'plain/text'}
+ */
+ headers: null,
+
+ /**
+ * Property: params
+ * {Object} Parameters of GET requests, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'bbox': '5,5,5,5'}
+ */
+ params: null,
+
+ /**
+ * Property: callback
+ * {Object} Function to be called when the <read>, <create>,
+ * <update>, <delete> or <commit> operation completes, read-only,
+ * set through the options passed to the constructor.
+ */
+ callback: null,
+
+ /**
+ * Property: scope
+ * {Object} Callback execution scope, read-only, set through the
+ * options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: readWithPOST
+ * {Boolean} true if read operations are done with POST requests
+ * instead of GET, defaults to false.
+ */
+ readWithPOST: false,
+
+ /**
+ * APIProperty: updateWithPOST
+ * {Boolean} true if update operations are done with POST requests
+ * defaults to false.
+ */
+ updateWithPOST: false,
+
+ /**
+ * APIProperty: deleteWithPOST
+ * {Boolean} true if delete operations are done with POST requests
+ * defaults to false.
+ * if true, POST data is set to output of format.write().
+ */
+ deleteWithPOST: false,
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Default is false. If true and the layer has a projection object set,
+ * any BBOX filter will be serialized with a fifth item identifying the
+ * projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * Constructor: OpenLayers.Protocol.HTTP
+ * A class for giving layers generic HTTP protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * headers - {Object}
+ * params - {Object} URL parameters for GET requests
+ * format - {<OpenLayers.Format>}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.headers = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ wildcarded: this.wildcarded,
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.params = null;
+ this.headers = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, the filter will be serialized using the
+ * <OpenLayers.Format.QueryStringFilter> class.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Valid options:
+ * url - {String} Url for the request.
+ * params - {Object} Parameters to get serialized as a query string.
+ * headers - {Object} Headers to be set on the request.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ * readWithPOST - {Boolean} If the request should be done with POST.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the HTTP request, this object is also passed to the
+ * callback function when the request completes, its "features" property
+ * is then populated with the features received from the server.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = options || {};
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var readWithPOST = (options.readWithPOST !== undefined) ?
+ options.readWithPOST : this.readWithPOST;
+ var resp = new OpenLayers.Protocol.Response({requestType: "read"});
+ if(readWithPOST) {
+ var headers = options.headers || {};
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ data: OpenLayers.Util.getParameterString(options.params),
+ headers: headers
+ });
+ } else {
+ resp.priv = OpenLayers.Request.GET({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ params: options.params,
+ headers: options.headers
+ });
+ }
+ return resp;
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the features received from the server.
+ */
+ create: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: features,
+ requestType: "create"
+ });
+
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleCreate, resp, options),
+ headers: options.headers,
+ data: this.format.write(features)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleCreate
+ * Called the the request issued by <create> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create call.
+ */
+ handleCreate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the feature received from the server.
+ */
+ update: function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "update"
+ });
+
+ var method = this.updateWithPOST ? "POST" : "PUT";
+ resp.priv = OpenLayers.Request[method]({
+ url: url,
+ callback: this.createCallback(this.handleUpdate, resp, options),
+ headers: options.headers,
+ data: this.format.write(feature)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleUpdate
+ * Called the the request issued by <update> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the update call.
+ */
+ handleUpdate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes.
+ */
+ "delete": function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "delete"
+ });
+
+ var method = this.deleteWithPOST ? "POST" : "DELETE";
+ var requestOptions = {
+ url: url,
+ callback: this.createCallback(this.handleDelete, resp, options),
+ headers: options.headers
+ };
+ if (this.deleteWithPOST) {
+ requestOptions.data = this.format.write(feature);
+ }
+ resp.priv = OpenLayers.Request[method](requestOptions);
+
+ return resp;
+ },
+
+ /**
+ * Method: handleDelete
+ * Called the the request issued by <delete> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the delete call.
+ */
+ handleDelete: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create, read, update,
+ * or delete call.
+ */
+ handleResponse: function(resp, options) {
+ var request = resp.priv;
+ if(options.callback) {
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ if(resp.requestType != "delete") {
+ resp.features = this.parseFeatures(request);
+ }
+ resp.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ resp.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, resp);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features.
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if (!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ /**
+ * APIMethod: commit
+ * Iterate over each feature and take action based on the feature state.
+ * Possible actions are create, update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Optional object for setting up intermediate commit
+ * callbacks.
+ *
+ * Valid options:
+ * create - {Object} Optional object to be passed to the <create> method.
+ * update - {Object} Optional object to be passed to the <update> method.
+ * delete - {Object} Optional object to be passed to the <delete> method.
+ * callback - {Function} Optional function to be called when the commit
+ * is complete.
+ * scope - {Object} Optional object to be set as the scope of the callback.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
+ * one per request made to the server, each object's "priv" property
+ * references the corresponding HTTP request.
+ */
+ commit: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var resp = [], nResponses = 0;
+
+ // Divide up features before issuing any requests. This properly
+ // counts requests in the event that any responses come in before
+ // all requests have been issued.
+ var types = {};
+ types[OpenLayers.State.INSERT] = [];
+ types[OpenLayers.State.UPDATE] = [];
+ types[OpenLayers.State.DELETE] = [];
+ var feature, list, requestFeatures = [];
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ list = types[feature.state];
+ if(list) {
+ list.push(feature);
+ requestFeatures.push(feature);
+ }
+ }
+ // tally up number of requests
+ var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
+ types[OpenLayers.State.UPDATE].length +
+ types[OpenLayers.State.DELETE].length;
+
+ // This response will be sent to the final callback after all the others
+ // have been fired.
+ var success = true;
+ var finalResponse = new OpenLayers.Protocol.Response({
+ reqFeatures: requestFeatures
+ });
+
+ function insertCallback(response) {
+ var len = response.features ? response.features.length : 0;
+ var fids = new Array(len);
+ for(var i=0; i<len; ++i) {
+ fids[i] = response.features[i].fid;
+ }
+ finalResponse.insertIds = fids;
+ callback.apply(this, [response]);
+ }
+
+ function callback(response) {
+ this.callUserCallback(response, options);
+ success = success && response.success();
+ nResponses++;
+ if (nResponses >= nRequests) {
+ if (options.callback) {
+ finalResponse.code = success ?
+ OpenLayers.Protocol.Response.SUCCESS :
+ OpenLayers.Protocol.Response.FAILURE;
+ options.callback.apply(options.scope, [finalResponse]);
+ }
+ }
+ }
+
+ // start issuing requests
+ var queue = types[OpenLayers.State.INSERT];
+ if(queue.length > 0) {
+ resp.push(this.create(
+ queue, OpenLayers.Util.applyDefaults(
+ {callback: insertCallback, scope: this}, options.create
+ )
+ ));
+ }
+ queue = types[OpenLayers.State.UPDATE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this.update(
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options.update
+ ))
+ );
+ }
+ queue = types[OpenLayers.State.DELETE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this["delete"](
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options["delete"]
+ ))
+ );
+ }
+ return resp;
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this HTTP protocol (as a result
+ * of a create, read, update, delete or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ /**
+ * Method: callUserCallback
+ * This method is used from within the commit method each time an
+ * an HTTP response is received from the server, it is responsible
+ * for calling the user-supplied callbacks.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>}
+ * options - {Object} The map of options passed to the commit call.
+ */
+ callUserCallback: function(resp, options) {
+ var opt = options[resp.requestType];
+ if(opt && opt.callback) {
+ opt.callback.call(opt.scope, resp);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.HTTP"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Cluster.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Cluster
+ * Strategy for vector feature clustering.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: distance
+ * {Integer} Pixel distance between features that should be considered a
+ * single cluster. Default is 20 pixels.
+ */
+ distance: 20,
+
+ /**
+ * APIProperty: threshold
+ * {Integer} Optional threshold below which original features will be
+ * added to the layer instead of clusters. For example, a threshold
+ * of 3 would mean that any time there are 2 or fewer features in
+ * a cluster, those features will be added directly to the layer instead
+ * of a cluster representing those features. Default is null (which is
+ * equivalent to 1 - meaning that clusters may contain just one feature).
+ */
+ threshold: null,
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature.Vector>)} Cached features.
+ */
+ features: null,
+
+ /**
+ * Property: clusters
+ * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters.
+ */
+ clusters: null,
+
+ /**
+ * Property: clustering
+ * {Boolean} The strategy is currently clustering features.
+ */
+ clustering: false,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution (map units per pixel) of the current cluster set.
+ */
+ resolution: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Cluster
+ * Create a new clustering strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "beforefeaturesadded": this.cacheFeatures,
+ "featuresremoved": this.clearCache,
+ "moveend": this.cluster,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.clearCache();
+ this.layer.events.un({
+ "beforefeaturesadded": this.cacheFeatures,
+ "featuresremoved": this.clearCache,
+ "moveend": this.cluster,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: cacheFeatures
+ * Cache features before they are added to the layer.
+ *
+ * Parameters:
+ * event - {Object} The event that this was listening for. This will come
+ * with a batch of features to be clustered.
+ *
+ * Returns:
+ * {Boolean} False to stop features from being added to the layer.
+ */
+ cacheFeatures: function(event) {
+ var propagate = true;
+ if(!this.clustering) {
+ this.clearCache();
+ this.features = event.features;
+ this.cluster();
+ propagate = false;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: clearCache
+ * Clear out the cached features.
+ */
+ clearCache: function() {
+ if(!this.clustering) {
+ this.features = null;
+ }
+ },
+
+ /**
+ * Method: cluster
+ * Cluster features based on some threshold distance.
+ *
+ * Parameters:
+ * event - {Object} The event received when cluster is called as a
+ * result of a moveend event.
+ */
+ cluster: function(event) {
+ if((!event || event.zoomChanged) && this.features) {
+ var resolution = this.layer.map.getResolution();
+ if(resolution != this.resolution || !this.clustersExist()) {
+ this.resolution = resolution;
+ var clusters = [];
+ var feature, clustered, cluster;
+ for(var i=0; i<this.features.length; ++i) {
+ feature = this.features[i];
+ if(feature.geometry) {
+ clustered = false;
+ for(var j=clusters.length-1; j>=0; --j) {
+ cluster = clusters[j];
+ if(this.shouldCluster(cluster, feature)) {
+ this.addToCluster(cluster, feature);
+ clustered = true;
+ break;
+ }
+ }
+ if(!clustered) {
+ clusters.push(this.createCluster(this.features[i]));
+ }
+ }
+ }
+ this.clustering = true;
+ this.layer.removeAllFeatures();
+ this.clustering = false;
+ if(clusters.length > 0) {
+ if(this.threshold > 1) {
+ var clone = clusters.slice();
+ clusters = [];
+ var candidate;
+ for(var i=0, len=clone.length; i<len; ++i) {
+ candidate = clone[i];
+ if(candidate.attributes.count < this.threshold) {
+ Array.prototype.push.apply(clusters, candidate.cluster);
+ } else {
+ clusters.push(candidate);
+ }
+ }
+ }
+ this.clustering = true;
+ // A legitimate feature addition could occur during this
+ // addFeatures call. For clustering to behave well, features
+ // should be removed from a layer before requesting a new batch.
+ this.layer.addFeatures(clusters);
+ this.clustering = false;
+ }
+ this.clusters = clusters;
+ }
+ }
+ },
+
+ /**
+ * Method: clustersExist
+ * Determine whether calculated clusters are already on the layer.
+ *
+ * Returns:
+ * {Boolean} The calculated clusters are already on the layer.
+ */
+ clustersExist: function() {
+ var exist = false;
+ if(this.clusters && this.clusters.length > 0 &&
+ this.clusters.length == this.layer.features.length) {
+ exist = true;
+ for(var i=0; i<this.clusters.length; ++i) {
+ if(this.clusters[i] != this.layer.features[i]) {
+ exist = false;
+ break;
+ }
+ }
+ }
+ return exist;
+ },
+
+ /**
+ * Method: shouldCluster
+ * Determine whether to include a feature in a given cluster.
+ *
+ * Parameters:
+ * cluster - {<OpenLayers.Feature.Vector>} A cluster.
+ * feature - {<OpenLayers.Feature.Vector>} A feature.
+ *
+ * Returns:
+ * {Boolean} The feature should be included in the cluster.
+ */
+ shouldCluster: function(cluster, feature) {
+ var cc = cluster.geometry.getBounds().getCenterLonLat();
+ var fc = feature.geometry.getBounds().getCenterLonLat();
+ var distance = (
+ Math.sqrt(
+ Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2)
+ ) / this.resolution
+ );
+ return (distance <= this.distance);
+ },
+
+ /**
+ * Method: addToCluster
+ * Add a feature to a cluster.
+ *
+ * Parameters:
+ * cluster - {<OpenLayers.Feature.Vector>} A cluster.
+ * feature - {<OpenLayers.Feature.Vector>} A feature.
+ */
+ addToCluster: function(cluster, feature) {
+ cluster.cluster.push(feature);
+ cluster.attributes.count += 1;
+ },
+
+ /**
+ * Method: createCluster
+ * Given a feature, create a cluster.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A cluster.
+ */
+ createCluster: function(feature) {
+ var center = feature.geometry.getBounds().getCenterLonLat();
+ var cluster = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(center.lon, center.lat),
+ {count: 1}
+ );
+ cluster.cluster = [feature];
+ return cluster;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Cluster"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Filter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Filter
+ * Strategy for limiting features that get added to a layer by
+ * evaluating a filter. The strategy maintains a cache of
+ * all features until removeFeatures is called on the layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Filter = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: filter
+ * {<OpenLayers.Filter>} Filter for limiting features sent to the layer.
+ * Use the <setFilter> method to update this filter after construction.
+ */
+ filter: null,
+
+ /**
+ * Property: cache
+ * {Array(<OpenLayers.Feature.Vector>)} List of currently cached
+ * features.
+ */
+ cache: null,
+
+ /**
+ * Property: caching
+ * {Boolean} The filter is currently caching features.
+ */
+ caching: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Filter
+ * Create a new filter strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ * By default, this strategy automatically activates itself when a layer
+ * is added to a map.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
+ if (activated) {
+ this.cache = [];
+ this.layer.events.on({
+ "beforefeaturesadded": this.handleAdd,
+ "beforefeaturesremoved": this.handleRemove,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Clear the feature cache.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ this.cache = null;
+ if (this.layer && this.layer.events) {
+ this.layer.events.un({
+ "beforefeaturesadded": this.handleAdd,
+ "beforefeaturesremoved": this.handleRemove,
+ scope: this
+ });
+ }
+ return OpenLayers.Strategy.prototype.deactivate.apply(this, arguments);
+ },
+
+ /**
+ * Method: handleAdd
+ */
+ handleAdd: function(event) {
+ if (!this.caching && this.filter) {
+ var features = event.features;
+ event.features = [];
+ var feature;
+ for (var i=0, ii=features.length; i<ii; ++i) {
+ feature = features[i];
+ if (this.filter.evaluate(feature)) {
+ event.features.push(feature);
+ } else {
+ this.cache.push(feature);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: handleRemove
+ */
+ handleRemove: function(event) {
+ if (!this.caching) {
+ this.cache = [];
+ }
+ },
+
+ /**
+ * APIMethod: setFilter
+ * Update the filter for this strategy. This will re-evaluate
+ * any features on the layer and in the cache. Only features
+ * for which filter.evalute(feature) returns true will be
+ * added to the layer. Others will be cached by the strategy.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} A filter for evaluating features.
+ */
+ setFilter: function(filter) {
+ this.filter = filter;
+ var previousCache = this.cache;
+ this.cache = [];
+ // look through layer for features to remove from layer
+ this.handleAdd({features: this.layer.features});
+ // cache now contains features to remove from layer
+ if (this.cache.length > 0) {
+ this.caching = true;
+ this.layer.removeFeatures(this.cache.slice());
+ this.caching = false;
+ }
+ // now look through previous cache for features to add to layer
+ if (previousCache.length > 0) {
+ var event = {features: previousCache};
+ this.handleAdd(event);
+ if (event.features.length > 0) {
+ // event has features to add to layer
+ this.caching = true;
+ this.layer.addFeatures(event.features);
+ this.caching = false;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Filter"
+
+});
+/* ======================================================================
+ OpenLayers/Protocol/SOS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Function: OpenLayers.Protocol.SOS
+ * Used to create a versioned SOS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol>} An SOS protocol for the given version.
+ */
+OpenLayers.Protocol.SOS = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.SOS.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.SOS["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported SOS version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Protocol.SOS.DEFAULTS
+ */
+OpenLayers.Protocol.SOS.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Format/WFSDescribeFeatureType.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSDescribeFeatureType
+ * Read WFS DescribeFeatureType response
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFSDescribeFeatureType = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g)
+ },
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xsd: "http://www.w3.org/2001/XMLSchema"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFSDescribeFeatureType
+ * Create a new parser for WFS DescribeFeatureType responses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "xsd": {
+ "schema": function(node, obj) {
+ var complexTypes = [];
+ var customTypes = {};
+ var schema = {
+ complexTypes: complexTypes,
+ customTypes: customTypes
+ };
+ var i, len;
+
+ this.readChildNodes(node, schema);
+
+ var attributes = node.attributes;
+ var attr, name;
+ for(i=0, len=attributes.length; i<len; ++i) {
+ attr = attributes[i];
+ name = attr.name;
+ if(name.indexOf("xmlns") === 0) {
+ this.setNamespace(name.split(":")[1] || "", attr.value);
+ } else {
+ obj[name] = attr.value;
+ }
+ }
+ obj.featureTypes = complexTypes;
+ obj.targetPrefix = this.namespaceAlias[obj.targetNamespace];
+
+ // map complexTypes to names of customTypes
+ var complexType, customType;
+ for(i=0, len=complexTypes.length; i<len; ++i) {
+ complexType = complexTypes[i];
+ customType = customTypes[complexType.typeName];
+ if(customTypes[complexType.typeName]) {
+ complexType.typeName = customType.name;
+ }
+ }
+ },
+ "complexType": function(node, obj) {
+ var complexType = {
+ // this is a temporary typeName, it will be overwritten by
+ // the schema reader with the metadata found in the
+ // customTypes hash
+ "typeName": node.getAttribute("name")
+ };
+ this.readChildNodes(node, complexType);
+ obj.complexTypes.push(complexType);
+ },
+ "complexContent": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "extension": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "sequence": function(node, obj) {
+ var sequence = {
+ elements: []
+ };
+ this.readChildNodes(node, sequence);
+ obj.properties = sequence.elements;
+ },
+ "element": function(node, obj) {
+ var type;
+ if(obj.elements) {
+ var element = {};
+ var attributes = node.attributes;
+ var attr;
+ for(var i=0, len=attributes.length; i<len; ++i) {
+ attr = attributes[i];
+ element[attr.name] = attr.value;
+ }
+
+ type = element.type;
+ if(!type) {
+ type = {};
+ this.readChildNodes(node, type);
+ element.restriction = type;
+ element.type = type.base;
+ }
+ var fullType = type.base || type;
+ element.localType = fullType.split(":").pop();
+ obj.elements.push(element);
+ this.readChildNodes(node, element);
+ }
+
+ if(obj.complexTypes) {
+ type = node.getAttribute("type");
+ var localType = type.split(":").pop();
+ obj.customTypes[localType] = {
+ "name": node.getAttribute("name"),
+ "type": type
+ };
+ }
+ },
+ "annotation": function(node, obj) {
+ obj.annotation = {};
+ this.readChildNodes(node, obj.annotation);
+ },
+ "appinfo": function(node, obj) {
+ if (!obj.appinfo) {
+ obj.appinfo = [];
+ }
+ obj.appinfo.push(this.getChildValue(node));
+ },
+ "documentation": function(node, obj) {
+ if (!obj.documentation) {
+ obj.documentation = [];
+ }
+ var value = this.getChildValue(node);
+ obj.documentation.push({
+ lang: node.getAttribute("xml:lang"),
+ textContent: value.replace(this.regExes.trimSpace, "")
+ });
+ },
+ "simpleType": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "restriction": function(node, obj) {
+ obj.base = node.getAttribute("base");
+ this.readRestriction(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: readRestriction
+ * Reads restriction defined in the child nodes of a restriction element
+ *
+ * Parameters:
+ * node - {DOMElement} the node to parse
+ * obj - {Object} the object that receives the read result
+ */
+ readRestriction: function(node, obj) {
+ var children = node.childNodes;
+ var child, nodeName, value;
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ nodeName = child.nodeName.split(":").pop();
+ value = child.getAttribute("value");
+ if(!obj[nodeName]) {
+ obj[nodeName] = value;
+ } else {
+ if(typeof obj[nodeName] == "string") {
+ obj[nodeName] = [obj[nodeName]];
+ }
+ obj[nodeName].push(value);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement|String} A WFS DescribeFeatureType document.
+ *
+ * Returns:
+ * {Object} An object representing the WFS DescribeFeatureType response.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var schema = {};
+ if (data.nodeName.split(":").pop() === 'ExceptionReport') {
+ // an exception must have occurred, so parse it
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ schema.error = parser.read(data);
+ } else {
+ this.readNode(data, schema);
+ }
+ return schema;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSDescribeFeatureType"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GeoRSS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoRSS
+ * Read/write GeoRSS parser. Create a new instance with the
+ * <OpenLayers.Format.GeoRSS> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GeoRSS = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: rssns
+ * {String} RSS namespace to use. Defaults to
+ * "http://backend.userland.com/rss2"
+ */
+ rssns: "http://backend.userland.com/rss2",
+
+ /**
+ * APIProperty: featurens
+ * {String} Feature Attributes namespace. Defaults to
+ * "http://mapserver.gis.umn.edu/mapserver"
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * APIProperty: georssns
+ * {String} GeoRSS namespace to use. Defaults to
+ * "http://www.georss.org/georss"
+ */
+ georssns: "http://www.georss.org/georss",
+
+ /**
+ * APIProperty: geons
+ * {String} W3C Geo namespace to use. Defaults to
+ * "http://www.w3.org/2003/01/geo/wgs84_pos#"
+ */
+ geons: "http://www.w3.org/2003/01/geo/wgs84_pos#",
+
+ /**
+ * APIProperty: featureTitle
+ * {String} Default title for features. Defaults to "Untitled"
+ */
+ featureTitle: "Untitled",
+
+ /**
+ * APIProperty: featureDescription
+ * {String} Default description for features. Defaults to "No Description"
+ */
+ featureDescription: "No Description",
+
+ /**
+ * Property: gmlParse
+ * {Object} GML Format object for parsing features
+ * Non-API and only created if necessary
+ */
+ gmlParser: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+ * For GeoRSS the default is (y,x), therefore: false
+ */
+ xy: false,
+
+ /**
+ * Constructor: OpenLayers.Format.GeoRSS
+ * Create a new parser for GeoRSS.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: createGeometryFromItem
+ * Return a geometry from a GeoRSS Item.
+ *
+ * Parameters:
+ * item - {DOMElement} A GeoRSS item node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry representing the node.
+ */
+ createGeometryFromItem: function(item) {
+ var point = this.getElementsByTagNameNS(item, this.georssns, "point");
+ var lat = this.getElementsByTagNameNS(item, this.geons, 'lat');
+ var lon = this.getElementsByTagNameNS(item, this.geons, 'long');
+
+ var line = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "line");
+ var polygon = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "polygon");
+ var where = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "where");
+ var box = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "box");
+
+ if (point.length > 0 || (lat.length > 0 && lon.length > 0)) {
+ var location;
+ if (point.length > 0) {
+ location = OpenLayers.String.trim(
+ point[0].firstChild.nodeValue).split(/\s+/);
+ if (location.length !=2) {
+ location = OpenLayers.String.trim(
+ point[0].firstChild.nodeValue).split(/\s*,\s*/);
+ }
+ } else {
+ location = [parseFloat(lat[0].firstChild.nodeValue),
+ parseFloat(lon[0].firstChild.nodeValue)];
+ }
+
+ var geometry = new OpenLayers.Geometry.Point(location[1], location[0]);
+
+ } else if (line.length > 0) {
+ var coords = OpenLayers.String.trim(this.getChildValue(line[0])).split(/\s+/);
+ var components = [];
+ var point;
+ for (var i=0, len=coords.length; i<len; i+=2) {
+ point = new OpenLayers.Geometry.Point(coords[i+1], coords[i]);
+ components.push(point);
+ }
+ geometry = new OpenLayers.Geometry.LineString(components);
+ } else if (polygon.length > 0) {
+ var coords = OpenLayers.String.trim(this.getChildValue(polygon[0])).split(/\s+/);
+ var components = [];
+ var point;
+ for (var i=0, len=coords.length; i<len; i+=2) {
+ point = new OpenLayers.Geometry.Point(coords[i+1], coords[i]);
+ components.push(point);
+ }
+ geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);
+ } else if (where.length > 0) {
+ if (!this.gmlParser) {
+ this.gmlParser = new OpenLayers.Format.GML({'xy': this.xy});
+ }
+ var feature = this.gmlParser.parseFeature(where[0]);
+ geometry = feature.geometry;
+ } else if (box.length > 0) {
+ var coords = OpenLayers.String.trim(box[0].firstChild.nodeValue).split(/\s+/);
+ var components = [];
+ var point;
+ if (coords.length > 3) {
+ point = new OpenLayers.Geometry.Point(coords[1], coords[0]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[1], coords[2]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[3], coords[2]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[3], coords[0]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[1], coords[0]);
+ components.push(point);
+ }
+ geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);
+ }
+
+ if (geometry && this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+
+ return geometry;
+ },
+
+ /**
+ * Method: createFeatureFromItem
+ * Return a feature from a GeoRSS Item.
+ *
+ * Parameters:
+ * item - {DOMElement} A GeoRSS item node.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature representing the item.
+ */
+ createFeatureFromItem: function(item) {
+ var geometry = this.createGeometryFromItem(item);
+
+ /* Provide defaults for title and description */
+ var title = this._getChildValue(item, "*", "title", this.featureTitle);
+
+ /* First try RSS descriptions, then Atom summaries */
+ var description = this._getChildValue(
+ item, "*", "description",
+ this._getChildValue(item, "*", "content",
+ this._getChildValue(item, "*", "summary", this.featureDescription)));
+
+ /* If no link URL is found in the first child node, try the
+ href attribute */
+ var link = this._getChildValue(item, "*", "link");
+ if(!link) {
+ try {
+ link = this.getElementsByTagNameNS(item, "*", "link")[0].getAttribute("href");
+ } catch(e) {
+ link = null;
+ }
+ }
+
+ var id = this._getChildValue(item, "*", "id", null);
+
+ var data = {
+ "title": title,
+ "description": description,
+ "link": link
+ };
+ var feature = new OpenLayers.Feature.Vector(geometry, data);
+ feature.fid = id;
+ return feature;
+ },
+
+ /**
+ * Method: _getChildValue
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * nsuri - {String} Child node namespace uri ("*" for any).
+ * name - {String} Child node name.
+ * def - {String} Optional string default to return if no child found.
+ *
+ * Returns:
+ * {String} The value of the first child with the given tag name. Returns
+ * default value or empty string if none found.
+ */
+ _getChildValue: function(node, nsuri, name, def) {
+ var value;
+ var eles = this.getElementsByTagNameNS(node, nsuri, name);
+ if(eles && eles[0] && eles[0].firstChild
+ && eles[0].firstChild.nodeValue) {
+ value = this.getChildValue(eles[0]);
+ } else {
+ value = (def == undefined) ? "" : def;
+ }
+ return value;
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a GeoRSS doc
+ *
+ * Parameters:
+ * doc - {Element}
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+
+ /* Try RSS items first, then Atom entries */
+ var itemlist = null;
+ itemlist = this.getElementsByTagNameNS(doc, '*', 'item');
+ if (itemlist.length == 0) {
+ itemlist = this.getElementsByTagNameNS(doc, '*', 'entry');
+ }
+
+ var numItems = itemlist.length;
+ var features = new Array(numItems);
+ for(var i=0; i<numItems; i++) {
+ features[i] = this.createFeatureFromItem(itemlist[i]);
+ }
+ return features;
+ },
+
+
+ /**
+ * APIMethod: write
+ * Accept Feature Collection, and return a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to serialize into a string.
+ */
+ write: function(features) {
+ var georss;
+ if(OpenLayers.Util.isArray(features)) {
+ georss = this.createElementNS(this.rssns, "rss");
+ for(var i=0, len=features.length; i<len; i++) {
+ georss.appendChild(this.createFeatureXML(features[i]));
+ }
+ } else {
+ georss = this.createFeatureXML(features);
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [georss]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ * Accept an <OpenLayers.Feature.Vector>, and build a geometry for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createFeatureXML: function(feature) {
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ var featureNode = this.createElementNS(this.rssns, "item");
+ var titleNode = this.createElementNS(this.rssns, "title");
+ titleNode.appendChild(this.createTextNode(feature.attributes.title ? feature.attributes.title : ""));
+ var descNode = this.createElementNS(this.rssns, "description");
+ descNode.appendChild(this.createTextNode(feature.attributes.description ? feature.attributes.description : ""));
+ featureNode.appendChild(titleNode);
+ featureNode.appendChild(descNode);
+ if (feature.attributes.link) {
+ var linkNode = this.createElementNS(this.rssns, "link");
+ linkNode.appendChild(this.createTextNode(feature.attributes.link));
+ featureNode.appendChild(linkNode);
+ }
+ for(var attr in feature.attributes) {
+ if (attr == "link" || attr == "title" || attr == "description") { continue; }
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr;
+ if (attr.search(":") != -1) {
+ nodename = attr.split(":")[1];
+ }
+ var attrContainer = this.createElementNS(this.featureNS, "feature:"+nodename);
+ attrContainer.appendChild(attrText);
+ featureNode.appendChild(attrContainer);
+ }
+ featureNode.appendChild(geometryNode);
+ return featureNode;
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * builds a GeoRSS node with a given geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} A gml node.
+ */
+ buildGeometryNode: function(geometry) {
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var node;
+ // match Polygon
+ if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ node = this.createElementNS(this.georssns, 'georss:polygon');
+
+ node.appendChild(this.buildCoordinatesNode(geometry.components[0]));
+ }
+ // match LineString
+ else if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+ node = this.createElementNS(this.georssns, 'georss:line');
+
+ node.appendChild(this.buildCoordinatesNode(geometry));
+ }
+ // match Point
+ else if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ node = this.createElementNS(this.georssns, 'georss:point');
+ node.appendChild(this.buildCoordinatesNode(geometry));
+ } else {
+ throw "Couldn't parse " + geometry.CLASS_NAME;
+ }
+ return node;
+ },
+
+ /**
+ * Method: buildCoordinatesNode
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ buildCoordinatesNode: function(geometry) {
+ var points = null;
+
+ if (geometry.components) {
+ points = geometry.components;
+ }
+
+ var path;
+ if (points) {
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ for (var i = 0; i < numPoints; i++) {
+ parts[i] = points[i].y + " " + points[i].x;
+ }
+ path = parts.join(" ");
+ } else {
+ path = geometry.y + " " + geometry.x;
+ }
+ return this.createTextNode(path);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoRSS"
+});
+/* ======================================================================
+ OpenLayers/Format/WPSCapabilities.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSCapabilities
+ * Read WPS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WPSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WPSCapabilities
+ * Create a new parser for WPS Capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return information about
+ * the service.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Info about the WPS
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WPSCapabilities"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WPSCapabilities/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WPSCapabilities.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSCapabilities.v1_0_0
+ * Read WPS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WPSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ wps: "http://www.opengis.net/wps/1.0.0",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WPSCapabilities.v1_0_0
+ * Create a new parser for WPS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return info about the WPS.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the WPS service.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wps": {
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "ProcessOfferings": function(node, obj) {
+ obj.processOfferings = {};
+ this.readChildNodes(node, obj.processOfferings);
+ },
+ "Process": function(node, processOfferings) {
+ var processVersion = this.getAttributeNS(node, this.namespaces.wps, "processVersion");
+ var process = {processVersion: processVersion};
+ this.readChildNodes(node, process);
+ processOfferings[process.identifier] = process;
+ },
+ "Languages": function(node, obj) {
+ obj.languages = [];
+ this.readChildNodes(node, obj.languages);
+ },
+ "Default": function(node, languages) {
+ var language = {isDefault: true};
+ this.readChildNodes(node, language);
+ languages.push(language);
+ },
+ "Supported": function(node, languages) {
+ var language = {};
+ this.readChildNodes(node, language);
+ languages.push(language);
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WPSCapabilities.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Control/PinchZoom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler/Pinch.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PinchZoom
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: pinchOrigin
+ * {Object} Cached object representing the pinch start (in pixels).
+ */
+ pinchOrigin: null,
+
+ /**
+ * Property: currentCenter
+ * {Object} Cached object representing the latest pinch center (in pixels).
+ */
+ currentCenter: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: preserveCenter
+ * {Boolean} Set this to true if you don't want the map center to change
+ * while pinching. For example you may want to set preserveCenter to
+ * true when the user location is being watched and you want to preserve
+ * the user location at the center of the map even if he zooms in or
+ * out using pinch. This property's value can be changed any time on an
+ * existing instance. Default is false.
+ */
+ preserveCenter: false,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the pinch handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.PinchZoom
+ * Create a control for zooming with pinch gestures. This works on devices
+ * with multi-touch support.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.handler = new OpenLayers.Handler.Pinch(this, {
+ start: this.pinchStart,
+ move: this.pinchMove,
+ done: this.pinchDone
+ }, this.handlerOptions);
+ },
+
+ /**
+ * Method: pinchStart
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchStart: function(evt, pinchData) {
+ var xy = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+ this.pinchOrigin = xy;
+ this.currentCenter = xy;
+ },
+
+ /**
+ * Method: pinchMove
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchMove: function(evt, pinchData) {
+ var scale = pinchData.scale;
+ var containerOrigin = this.map.layerContainerOriginPx;
+ var pinchOrigin = this.pinchOrigin;
+ var current = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+
+ var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
+ var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
+
+ this.map.applyTransform(dx, dy, scale);
+ this.currentCenter = current;
+ },
+
+ /**
+ * Method: pinchDone
+ *
+ * Parameters:
+ * evt - {Event}
+ * start - {Object} pinch data object related to the touchstart event that
+ * started the pinch gesture.
+ * last - {Object} pinch data object related to the last touchmove event
+ * of the pinch gesture. This give us the final scale of the pinch.
+ */
+ pinchDone: function(evt, start, last) {
+ this.map.applyTransform();
+ var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
+ if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
+ var resolution = this.map.getResolutionForZoom(zoom);
+
+ var location = this.map.getLonLatFromPixel(this.pinchOrigin);
+ var zoomPixel = this.currentCenter;
+ var size = this.map.getSize();
+
+ location.lon += resolution * ((size.w / 2) - zoomPixel.x);
+ location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
+
+ // Force a reflow before calling setCenter. This is to work
+ // around an issue occuring in iOS.
+ //
+ // See https://github.com/openlayers/openlayers/pull/351.
+ //
+ // Without a reflow setting the layer container div's top left
+ // style properties to "0px" - as done in Map.moveTo when zoom
+ // is changed - won't actually correctly reposition the layer
+ // container div.
+ //
+ // Also, we need to use a statement that the Google Closure
+ // compiler won't optimize away.
+ this.map.div.clientWidth = this.map.div.clientWidth;
+
+ this.map.setCenter(location, zoom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PinchZoom"
+
+});
+/* ======================================================================
+ OpenLayers/Control/TouchNavigation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/DragPan.js
+ * @requires OpenLayers/Control/PinchZoom.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.TouchNavigation
+ * The navigation control handles map browsing with touch events (dragging,
+ * double-tapping, tap with two fingers, and pinch zoom). Create a new
+ * control with the <OpenLayers.Control.TouchNavigation> constructor.
+ *
+ * If you’re only targeting touch enabled devices with your mapping application,
+ * you can create a map with only a TouchNavigation control. The
+ * <OpenLayers.Control.Navigation> control is mobile ready by default, but
+ * you can generate a smaller build of the library by only including this
+ * touch navigation control if you aren't concerned about mouse interaction.
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: clickHandlerOptions
+ * {Object} Options passed to the Click handler.
+ */
+ clickHandlerOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.TouchNavigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ this.deactivate();
+ if(this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ delete this.pinchZoom;
+ }
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
+ this.dragPan.activate();
+ this.handlers.click.activate();
+ this.pinchZoom.activate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.pinchZoom.deactivate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ var clickCallbacks = {
+ click: this.defaultClick,
+ dblclick: this.defaultDblClick
+ };
+ var clickOptions = OpenLayers.Util.extend({
+ "double": true,
+ stopDouble: true,
+ pixelTolerance: 2
+ }, this.clickHandlerOptions);
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.dragPan.draw();
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
+ );
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if(evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.TouchNavigation"
+});
+/* ======================================================================
+ Rico/Color.js
+ ====================================================================== */
+
+/**
+ * @requires Rico/license.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ */
+
+
+/*
+ * This file has been edited substantially from the Rico-released version by
+ * the OpenLayers development team.
+ */
+
+OpenLayers.Console.warn("OpenLayers.Rico is deprecated");
+
+OpenLayers.Rico = OpenLayers.Rico || {};
+OpenLayers.Rico.Color = OpenLayers.Class({
+
+ initialize: function(red, green, blue) {
+ this.rgb = { r: red, g : green, b : blue };
+ },
+
+ setRed: function(r) {
+ this.rgb.r = r;
+ },
+
+ setGreen: function(g) {
+ this.rgb.g = g;
+ },
+
+ setBlue: function(b) {
+ this.rgb.b = b;
+ },
+
+ setHue: function(h) {
+
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.h = h;
+
+ // convert back to RGB...
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setSaturation: function(s) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.s = s;
+
+ // convert back to RGB and set values...
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setBrightness: function(b) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.b = b;
+
+ // convert back to RGB and set values...
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+ },
+
+ darken: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+ },
+
+ brighten: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+ },
+
+ blend: function(other) {
+ this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+ this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+ this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+ },
+
+ isBright: function() {
+ var hsb = this.asHSB();
+ return this.asHSB().b > 0.5;
+ },
+
+ isDark: function() {
+ return ! this.isBright();
+ },
+
+ asRGB: function() {
+ return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+ },
+
+ asHex: function() {
+ return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+ },
+
+ asHSB: function() {
+ return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+ },
+
+ toString: function() {
+ return this.asHex();
+ }
+
+});
+
+OpenLayers.Rico.Color.createFromHex = function(hexCode) {
+ if(hexCode.length==4) {
+ var shortHexCode = hexCode;
+ var hexCode = '#';
+ for(var i=1;i<4;i++) {
+ hexCode += (shortHexCode.charAt(i) +
+shortHexCode.charAt(i));
+ }
+ }
+ if ( hexCode.indexOf('#') == 0 ) {
+ hexCode = hexCode.substring(1);
+ }
+ var red = hexCode.substring(0,2);
+ var green = hexCode.substring(2,4);
+ var blue = hexCode.substring(4,6);
+ return new OpenLayers.Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+};
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+OpenLayers.Rico.Color.createColorFromBackground = function(elem) {
+
+ var actualColor =
+ OpenLayers.Element.getStyle(OpenLayers.Util.getElement(elem),
+ "backgroundColor");
+
+ if ( actualColor == "transparent" && elem.parentNode ) {
+ return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);
+ }
+ if ( actualColor == null ) {
+ return new OpenLayers.Rico.Color(255,255,255);
+ }
+ if ( actualColor.indexOf("rgb(") == 0 ) {
+ var colors = actualColor.substring(4, actualColor.length - 1 );
+ var colorArray = colors.split(",");
+ return new OpenLayers.Rico.Color( parseInt( colorArray[0] ),
+ parseInt( colorArray[1] ),
+ parseInt( colorArray[2] ) );
+
+ }
+ else if ( actualColor.indexOf("#") == 0 ) {
+ return OpenLayers.Rico.Color.createFromHex(actualColor);
+ }
+ else {
+ return new OpenLayers.Rico.Color(255,255,255);
+ }
+};
+
+OpenLayers.Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+ var red = 0;
+ var green = 0;
+ var blue = 0;
+
+ if (saturation == 0) {
+ red = parseInt(brightness * 255.0 + 0.5);
+ green = red;
+ blue = red;
+ }
+ else {
+ var h = (hue - Math.floor(hue)) * 6.0;
+ var f = h - Math.floor(h);
+ var p = brightness * (1.0 - saturation);
+ var q = brightness * (1.0 - saturation * f);
+ var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+ switch (parseInt(h)) {
+ case 0:
+ red = (brightness * 255.0 + 0.5);
+ green = (t * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 1:
+ red = (q * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 2:
+ red = (p * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (t * 255.0 + 0.5);
+ break;
+ case 3:
+ red = (p * 255.0 + 0.5);
+ green = (q * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 4:
+ red = (t * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 5:
+ red = (brightness * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (q * 255.0 + 0.5);
+ break;
+ }
+ }
+
+ return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+};
+
+OpenLayers.Rico.Color.RGBtoHSB = function(r, g, b) {
+
+ var hue;
+ var saturation;
+ var brightness;
+
+ var cmax = (r > g) ? r : g;
+ if (b > cmax) {
+ cmax = b;
+ }
+ var cmin = (r < g) ? r : g;
+ if (b < cmin) {
+ cmin = b;
+ }
+ brightness = cmax / 255.0;
+ if (cmax != 0) {
+ saturation = (cmax - cmin)/cmax;
+ } else {
+ saturation = 0;
+ }
+ if (saturation == 0) {
+ hue = 0;
+ } else {
+ var redc = (cmax - r)/(cmax - cmin);
+ var greenc = (cmax - g)/(cmax - cmin);
+ var bluec = (cmax - b)/(cmax - cmin);
+
+ if (r == cmax) {
+ hue = bluec - greenc;
+ } else if (g == cmax) {
+ hue = 2.0 + redc - bluec;
+ } else {
+ hue = 4.0 + greenc - redc;
+ }
+ hue = hue / 6.0;
+ if (hue < 0) {
+ hue = hue + 1.0;
+ }
+ }
+
+ return { h : hue, s : saturation, b : brightness };
+};
+
+/* ======================================================================
+ OpenLayers/Style2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Symbolizer/Point.js
+ * @requires OpenLayers/Symbolizer/Line.js
+ * @requires OpenLayers/Symbolizer/Polygon.js
+ * @requires OpenLayers/Symbolizer/Text.js
+ * @requires OpenLayers/Symbolizer/Raster.js
+ */
+
+/**
+ * Class: OpenLayers.Style2
+ * This class represents a collection of rules for rendering features.
+ */
+OpenLayers.Style2 = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String} Style identifier.
+ */
+ name: null,
+
+ /**
+ * APIProperty: title
+ * {String} Title of this style.
+ */
+ title: null,
+
+ /**
+ * APIProperty: description
+ * {String} Description of this style.
+ */
+ description: null,
+
+ /**
+ * APIProperty: layerName
+ * {<String>} Name of the layer that this style belongs to, usually
+ * according to the NamedLayer attribute of an SLD document.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: isDefault
+ * {Boolean}
+ */
+ isDefault: false,
+
+ /**
+ * APIProperty: rules
+ * {Array(<OpenLayers.Rule>)} Collection of rendering rules.
+ */
+ rules: null,
+
+ /**
+ * Constructor: OpenLayers.Style2
+ * Creates a style representing a collection of rendering rules.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * style. Any documented properties may be set at construction.
+ *
+ * Returns:
+ * {<OpenLayers.Style2>} A new style object.
+ */
+ initialize: function(config) {
+ OpenLayers.Util.extend(this, config);
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i=0, len=this.rules.length; i<len; i++) {
+ this.rules[i].destroy();
+ }
+ delete this.rules;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style2>} Clone of this style.
+ */
+ clone: function() {
+ var config = OpenLayers.Util.extend({}, this);
+ // clone rules
+ if (this.rules) {
+ config.rules = [];
+ for (var i=0, len=this.rules.length; i<len; ++i) {
+ config.rules.push(this.rules[i].clone());
+ }
+ }
+ return new OpenLayers.Style2(config);
+ },
+
+ CLASS_NAME: "OpenLayers.Style2"
+});
+/* ======================================================================
+ OpenLayers/Format/WFS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFS
+ * Read/Write WFS.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML>
+ */
+OpenLayers.Format.WFS = OpenLayers.Class(OpenLayers.Format.GML, {
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * APIProperty: wfsns
+ * {String}
+ */
+ wfsns: "http://www.opengis.net/wfs",
+
+ /**
+ * Property: ogcns
+ * {String}
+ */
+ ogcns: "http://www.opengis.net/ogc",
+
+ /**
+ * Constructor: OpenLayers.Format.WFS
+ * Create a WFS-T formatter. This requires a layer: that layer should
+ * have two properties: geometry_column and typename. The parser
+ * for this format is subclassed entirely from GML: There is a writer
+ * only, which uses most of the code from the GML layer, and wraps
+ * it in transactional elements.
+ *
+ * Parameters:
+ * options - {Object}
+ * layer - {<OpenLayers.Layer>}
+ */
+ initialize: function(options, layer) {
+ OpenLayers.Format.GML.prototype.initialize.apply(this, [options]);
+ this.layer = layer;
+ if (this.layer.featureNS) {
+ this.featureNS = this.layer.featureNS;
+ }
+ if (this.layer.options.geometry_column) {
+ this.geometryName = this.layer.options.geometry_column;
+ }
+ if (this.layer.options.typename) {
+ this.featureName = this.layer.options.typename;
+ }
+ },
+
+ /**
+ * Method: write
+ * Takes a feature list, and generates a WFS-T Transaction
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ write: function(features) {
+
+ var transaction = this.createElementNS(this.wfsns, 'wfs:Transaction');
+ transaction.setAttribute("version","1.0.0");
+ transaction.setAttribute("service","WFS");
+ for (var i=0; i < features.length; i++) {
+ switch (features[i].state) {
+ case OpenLayers.State.INSERT:
+ transaction.appendChild(this.insert(features[i]));
+ break;
+ case OpenLayers.State.UPDATE:
+ transaction.appendChild(this.update(features[i]));
+ break;
+ case OpenLayers.State.DELETE:
+ transaction.appendChild(this.remove(features[i]));
+ break;
+ }
+ }
+
+ return OpenLayers.Format.XML.prototype.write.apply(this,[transaction]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ createFeatureXML: function(feature) {
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ var geomContainer = this.createElementNS(this.featureNS, "feature:" + this.geometryName);
+ geomContainer.appendChild(geometryNode);
+ var featureContainer = this.createElementNS(this.featureNS, "feature:" + this.featureName);
+ featureContainer.appendChild(geomContainer);
+ for(var attr in feature.attributes) {
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr;
+ if (attr.search(":") != -1) {
+ nodename = attr.split(":")[1];
+ }
+ var attrContainer = this.createElementNS(this.featureNS, "feature:" + nodename);
+ attrContainer.appendChild(attrText);
+ featureContainer.appendChild(attrContainer);
+ }
+ return featureContainer;
+ },
+
+ /**
+ * Method: insert
+ * Takes a feature, and generates a WFS-T Transaction "Insert"
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ insert: function(feature) {
+ var insertNode = this.createElementNS(this.wfsns, 'wfs:Insert');
+ insertNode.appendChild(this.createFeatureXML(feature));
+ return insertNode;
+ },
+
+ /**
+ * Method: update
+ * Takes a feature, and generates a WFS-T Transaction "Update"
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ update: function(feature) {
+ if (!feature.fid) { OpenLayers.Console.userError(OpenLayers.i18n("noFID")); }
+ var updateNode = this.createElementNS(this.wfsns, 'wfs:Update');
+ updateNode.setAttribute("typeName", this.featurePrefix + ':' + this.featureName);
+ updateNode.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+
+ var propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+ var nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+
+ var txtNode = this.createTextNode(this.geometryName);
+ nameNode.appendChild(txtNode);
+ propertyNode.appendChild(nameNode);
+
+ var valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+
+ if(feature.layer){
+ geometryNode.setAttribute(
+ "srsName", feature.layer.projection.getCode()
+ );
+ }
+
+ valueNode.appendChild(geometryNode);
+
+ propertyNode.appendChild(valueNode);
+ updateNode.appendChild(propertyNode);
+
+ // add in attributes
+ for(var propName in feature.attributes) {
+ propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+ nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+ nameNode.appendChild(this.createTextNode(propName));
+ propertyNode.appendChild(nameNode);
+ valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+ valueNode.appendChild(this.createTextNode(feature.attributes[propName]));
+ propertyNode.appendChild(valueNode);
+ updateNode.appendChild(propertyNode);
+ }
+
+
+ var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+ var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+ filterIdNode.setAttribute("fid", feature.fid);
+ filterNode.appendChild(filterIdNode);
+ updateNode.appendChild(filterNode);
+
+ return updateNode;
+ },
+
+ /**
+ * Method: remove
+ * Takes a feature, and generates a WFS-T Transaction "Delete"
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ remove: function(feature) {
+ if (!feature.fid) {
+ OpenLayers.Console.userError(OpenLayers.i18n("noFID"));
+ return false;
+ }
+ var deleteNode = this.createElementNS(this.wfsns, 'wfs:Delete');
+ deleteNode.setAttribute("typeName", this.featurePrefix + ':' + this.featureName);
+ deleteNode.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+
+ var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+ var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+ filterIdNode.setAttribute("fid", feature.fid);
+ filterNode.appendChild(filterIdNode);
+ deleteNode.appendChild(filterNode);
+
+ return deleteNode;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove ciruclar ref to layer
+ */
+ destroy: function() {
+ this.layer = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFS"
+});
+/* ======================================================================
+ OpenLayers/Format/SLD/v1_0_0_GeoServer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD/v1_0_0_GeoServer
+ * Read and write SLD version 1.0.0 with GeoServer-specific enhanced options.
+ * See http://svn.osgeo.org/geotools/trunk/modules/extension/xsd/xsd-sld/src/main/resources/org/geotools/sld/bindings/StyledLayerDescriptor.xsd
+ * for more information.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.SLD.v1_0_0>
+ */
+OpenLayers.Format.SLD.v1_0_0_GeoServer = OpenLayers.Class(
+ OpenLayers.Format.SLD.v1_0_0, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.0.0",
+
+ /**
+ * Property: profile
+ * {String} The specific profile
+ */
+ profile: "GeoServer",
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1_0_0_GeoServer
+ * Create a new parser for GeoServer-enhanced SLD version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: OpenLayers.Util.applyDefaults({
+ "sld": OpenLayers.Util.applyDefaults({
+ "Priority": function(node, obj) {
+ var value = this.readers.ogc._expression.call(this, node);
+ if (value) {
+ obj.priority = value;
+ }
+ },
+ "VendorOption": function(node, obj) {
+ if (!obj.vendorOptions) {
+ obj.vendorOptions = {};
+ }
+ obj.vendorOptions[node.getAttribute("name")] = this.getChildValue(node);
+ },
+ "TextSymbolizer": function(node, rule) {
+ OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld.TextSymbolizer.apply(this, arguments);
+ var symbolizer = this.multipleSymbolizers ? rule.symbolizers[rule.symbolizers.length-1] : rule.symbolizer["Text"];
+ if (symbolizer.graphic === undefined) {
+ symbolizer.graphic = false;
+ }
+ }
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.readers["sld"])
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.readers),
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: OpenLayers.Util.applyDefaults({
+ "sld": OpenLayers.Util.applyDefaults({
+ "Priority": function(priority) {
+ return this.writers.sld._OGCExpression.call(
+ this, "sld:Priority", priority
+ );
+ },
+ "VendorOption": function(option) {
+ return this.createElementNSPlus("sld:VendorOption", {
+ attributes: {name: option.name},
+ value: option.value
+ });
+ },
+ "TextSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["TextSymbolizer"].apply(this, arguments);
+ if (symbolizer.graphic !== false && (symbolizer.externalGraphic || symbolizer.graphicName)) {
+ this.writeNode("Graphic", symbolizer, node);
+ }
+ if ("priority" in symbolizer) {
+ this.writeNode("Priority", symbolizer.priority, node);
+ }
+ return this.addVendorOptions(node, symbolizer);
+ },
+ "PointSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["PointSymbolizer"].apply(this, arguments);
+ return this.addVendorOptions(node, symbolizer);
+ },
+ "LineSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["LineSymbolizer"].apply(this, arguments);
+ return this.addVendorOptions(node, symbolizer);
+ },
+ "PolygonSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["PolygonSymbolizer"].apply(this, arguments);
+ return this.addVendorOptions(node, symbolizer);
+ }
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.writers["sld"])
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.writers),
+
+ /**
+ * Method: addVendorOptions
+ * Add in the VendorOption tags and return the node again.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM node.
+ * symbolizer - {Object}
+ *
+ * Returns:
+ * {DOMElement} A DOM node.
+ */
+ addVendorOptions: function(node, symbolizer) {
+ var options = symbolizer.vendorOptions;
+ if (options) {
+ for (var key in symbolizer.vendorOptions) {
+ this.writeNode("VendorOption", {
+ name: key,
+ value: symbolizer.vendorOptions[key]
+ }, node);
+ }
+ }
+ return node;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0_GeoServer"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/Boxes.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Layer/Markers.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Boxes
+ * Draw divs as 'boxes' on the layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Boxes = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+ /**
+ * Constructor: OpenLayers.Layer.Boxes
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+
+ /**
+ * Method: drawMarker
+ * Calculate the pixel location for the marker, create it, and
+ * add it to the layer's div
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker.Box>}
+ */
+ drawMarker: function(marker) {
+ var topleft = this.map.getLayerPxFromLonLat({
+ lon: marker.bounds.left,
+ lat: marker.bounds.top
+ });
+ var botright = this.map.getLayerPxFromLonLat({
+ lon: marker.bounds.right,
+ lat: marker.bounds.bottom
+ });
+ if (botright == null || topleft == null) {
+ marker.display(false);
+ } else {
+ var markerDiv = marker.draw(topleft, {
+ w: Math.max(1, botright.x - topleft.x),
+ h: Math.max(1, botright.y - topleft.y)
+ });
+ if (!marker.drawn) {
+ this.div.appendChild(markerDiv);
+ marker.drawn = true;
+ }
+ }
+ },
+
+
+ /**
+ * APIMethod: removeMarker
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker.Box>}
+ */
+ removeMarker: function(marker) {
+ OpenLayers.Util.removeItem(this.markers, marker);
+ if ((marker.div != null) &&
+ (marker.div.parentNode == this.div) ) {
+ this.div.removeChild(marker.div);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Boxes"
+});
+/* ======================================================================
+ OpenLayers/Format/WFSCapabilities/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFSCapabilities/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities/v1_0_0
+ * Read WFS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WFSCapabilities.v1>
+ */
+OpenLayers.Format.WFSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.WFSCapabilities.v1, {
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities.v1_0_0
+ * Create a new parser for WFS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "Service": function(node, capabilities) {
+ capabilities.service = {};
+ this.readChildNodes(node, capabilities.service);
+ },
+ "Fees": function(node, service) {
+ var fees = this.getChildValue(node);
+ if (fees && fees.toLowerCase() != "none") {
+ service.fees = fees;
+ }
+ },
+ "AccessConstraints": function(node, service) {
+ var constraints = this.getChildValue(node);
+ if (constraints && constraints.toLowerCase() != "none") {
+ service.accessConstraints = constraints;
+ }
+ },
+ "OnlineResource": function(node, service) {
+ var onlineResource = this.getChildValue(node);
+ if (onlineResource && onlineResource.toLowerCase() != "none") {
+ service.onlineResource = onlineResource;
+ }
+ },
+ "Keywords": function(node, service) {
+ var keywords = this.getChildValue(node);
+ if (keywords && keywords.toLowerCase() != "none") {
+ service.keywords = keywords.split(', ');
+ }
+ },
+ "Capability": function(node, capabilities) {
+ capabilities.capability = {};
+ this.readChildNodes(node, capabilities.capability);
+ },
+ "Request": function(node, obj) {
+ obj.request = {};
+ this.readChildNodes(node, obj.request);
+ },
+ "GetFeature": function(node, request) {
+ request.getfeature = {
+ href: {}, // DCPType
+ formats: [] // ResultFormat
+ };
+ this.readChildNodes(node, request.getfeature);
+ },
+ "ResultFormat": function(node, obj) {
+ var children = node.childNodes;
+ var childNode;
+ for(var i=0; i<children.length; i++) {
+ childNode = children[i];
+ if(childNode.nodeType == 1) {
+ obj.formats.push(childNode.nodeName);
+ }
+ }
+ },
+ "DCPType": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "HTTP": function(node, obj) {
+ this.readChildNodes(node, obj.href);
+ },
+ "Get": function(node, obj) {
+ obj.get = node.getAttribute("onlineResource");
+ },
+ "Post": function(node, obj) {
+ obj.post = node.getAttribute("onlineResource");
+ },
+ "SRS": function(node, obj) {
+ var srs = this.getChildValue(node);
+ if (srs) {
+ obj.srs = srs;
+ }
+ }
+ }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1_3.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_3
+ * Abstract base class for WMS Capabilities version 1.3.X.
+ * SLD 1.1.0 adds in the extra operations DescribeLayer and GetLegendGraphic,
+ * see: http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_3 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1, {
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "WMS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LayerLimit": function(node, obj) {
+ obj.layerLimit = parseInt(this.getChildValue(node));
+ },
+ "MaxWidth": function(node, obj) {
+ obj.maxWidth = parseInt(this.getChildValue(node));
+ },
+ "MaxHeight": function(node, obj) {
+ obj.maxHeight = parseInt(this.getChildValue(node));
+ },
+ "BoundingBox": function(node, obj) {
+ var bbox = OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"].BoundingBox.apply(this, [node, obj]);
+ bbox.srs = node.getAttribute("CRS");
+ obj.bbox[bbox.srs] = bbox;
+ },
+ "CRS": function(node, obj) {
+ // CRS is the synonym of SRS
+ this.readers.wms.SRS.apply(this, [node, obj]);
+ },
+ "EX_GeographicBoundingBox": function(node, obj) {
+ // replacement of LatLonBoundingBox
+ obj.llbbox = [];
+ this.readChildNodes(node, obj.llbbox);
+
+ },
+ "westBoundLongitude": function(node, obj) {
+ obj[0] = this.getChildValue(node);
+ },
+ "eastBoundLongitude": function(node, obj) {
+ obj[2] = this.getChildValue(node);
+ },
+ "southBoundLatitude": function(node, obj) {
+ obj[1] = this.getChildValue(node);
+ },
+ "northBoundLatitude": function(node, obj) {
+ obj[3] = this.getChildValue(node);
+ },
+ "MinScaleDenominator": function(node, obj) {
+ obj.maxScale = parseFloat(this.getChildValue(node)).toPrecision(16);
+ },
+ "MaxScaleDenominator": function(node, obj) {
+ obj.minScale = parseFloat(this.getChildValue(node)).toPrecision(16);
+ },
+ "Dimension": function(node, obj) {
+ // dimension has extra attributes: default, multipleValues,
+ // nearestValue, current which used to be part of Extent. It now
+ // also contains the values.
+ var name = node.getAttribute("name").toLowerCase();
+ var dim = {
+ name: name,
+ units: node.getAttribute("units"),
+ unitsymbol: node.getAttribute("unitSymbol"),
+ nearestVal: node.getAttribute("nearestValue") === "1",
+ multipleVal: node.getAttribute("multipleValues") === "1",
+ "default": node.getAttribute("default") || "",
+ current: node.getAttribute("current") === "1",
+ values: this.getChildValue(node).split(",")
+
+ };
+ // Theoretically there can be more dimensions with the same
+ // name, but with a different unit. Until we meet such a case,
+ // let's just keep the same structure as the WMS 1.1
+ // GetCapabilities parser uses. We will store the last
+ // one encountered.
+ obj.dimensions[dim.name] = dim;
+ },
+ "Keyword": function(node, obj) {
+ // TODO: should we change the structure of keyword in v1.js?
+ // Make it an object with a value instead of a string?
+ var keyword = {value: this.getChildValue(node),
+ vocabulary: node.getAttribute("vocabulary")};
+ if (obj.keywords) {
+ obj.keywords.push(keyword);
+ }
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"]),
+ "sld": {
+ "UserDefinedSymbolization": function(node, obj) {
+ this.readers.wms.UserDefinedSymbolization.apply(this, [node, obj]);
+ // add the two extra attributes
+ obj.userSymbols.inlineFeature = parseInt(node.getAttribute("InlineFeature")) == 1;
+ obj.userSymbols.remoteWCS = parseInt(node.getAttribute("RemoteWCS")) == 1;
+ },
+ "DescribeLayer": function(node, obj) {
+ this.readers.wms.DescribeLayer.apply(this, [node, obj]);
+ },
+ "GetLegendGraphic": function(node, obj) {
+ this.readers.wms.GetLegendGraphic.apply(this, [node, obj]);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_3"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/Zoomify.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/*
+ * Development supported by a R&D grant DC08P02OUK006 - Old Maps Online
+ * (www.oldmapsonline.org) from Ministry of Culture of the Czech Republic.
+ */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Zoomify
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.Zoomify = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} The Zoomify image size in pixels.
+ */
+ size: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean}
+ */
+ isBaseLayer: true,
+
+ /**
+ * Property: standardTileSize
+ * {Integer} The size of a standard (non-border) square tile in pixels.
+ */
+ standardTileSize: 256,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} This layer uses top-left as tile origin
+ **/
+ tileOriginCorner: "tl",
+
+ /**
+ * Property: numberOfTiers
+ * {Integer} Depth of the Zoomify pyramid, number of tiers (zoom levels)
+ * - filled during Zoomify pyramid initialization.
+ */
+ numberOfTiers: 0,
+
+ /**
+ * Property: tileCountUpToTier
+ * {Array(Integer)} Number of tiles up to the given tier of pyramid.
+ * - filled during Zoomify pyramid initialization.
+ */
+ tileCountUpToTier: null,
+
+ /**
+ * Property: tierSizeInTiles
+ * {Array(<OpenLayers.Size>)} Size (in tiles) for each tier of pyramid.
+ * - filled during Zoomify pyramid initialization.
+ */
+ tierSizeInTiles: null,
+
+ /**
+ * Property: tierImageSize
+ * {Array(<OpenLayers.Size>)} Image size in pixels for each pyramid tier.
+ * - filled during Zoomify pyramid initialization.
+ */
+ tierImageSize: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Zoomify
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * url - {String} - Relative or absolute path to the image or more
+ * precisly to the TileGroup[X] directories root.
+ * Flash plugin use the variable name "zoomifyImagePath" for this.
+ * size - {<OpenLayers.Size>} The size (in pixels) of the image.
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, size, options) {
+
+ // initilize the Zoomify pyramid for given size
+ this.initializeZoomify(size);
+
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
+ name, url, size, {}, options
+ ]);
+ },
+
+ /**
+ * Method: initializeZoomify
+ * It generates constants for all tiers of the Zoomify pyramid
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the image in pixels
+ *
+ */
+ initializeZoomify: function( size ) {
+
+ var imageSize = size.clone();
+ this.size = size.clone();
+ var tiles = new OpenLayers.Size(
+ Math.ceil( imageSize.w / this.standardTileSize ),
+ Math.ceil( imageSize.h / this.standardTileSize )
+ );
+
+ this.tierSizeInTiles = [tiles];
+ this.tierImageSize = [imageSize];
+
+ while (imageSize.w > this.standardTileSize ||
+ imageSize.h > this.standardTileSize ) {
+
+ imageSize = new OpenLayers.Size(
+ Math.floor( imageSize.w / 2 ),
+ Math.floor( imageSize.h / 2 )
+ );
+ tiles = new OpenLayers.Size(
+ Math.ceil( imageSize.w / this.standardTileSize ),
+ Math.ceil( imageSize.h / this.standardTileSize )
+ );
+ this.tierSizeInTiles.push( tiles );
+ this.tierImageSize.push( imageSize );
+ }
+
+ this.tierSizeInTiles.reverse();
+ this.tierImageSize.reverse();
+
+ this.numberOfTiers = this.tierSizeInTiles.length;
+ var resolutions = [1];
+ this.tileCountUpToTier = [0];
+ for (var i = 1; i < this.numberOfTiers; i++) {
+ resolutions.unshift(Math.pow(2, i));
+ this.tileCountUpToTier.push(
+ this.tierSizeInTiles[i-1].w * this.tierSizeInTiles[i-1].h +
+ this.tileCountUpToTier[i-1]
+ );
+ }
+ if (!this.serverResolutions) {
+ this.serverResolutions = resolutions;
+ }
+ },
+
+ /**
+ * APIMethod:destroy
+ */
+ destroy: function() {
+ // for now, nothing special to do here.
+ OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);
+
+ // Remove from memory the Zoomify pyramid - is that enough?
+ this.tileCountUpToTier.length = 0;
+ this.tierSizeInTiles.length = 0;
+ this.tierImageSize.length = 0;
+
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Zoomify>} An exact clone of this <OpenLayers.Layer.Zoomify>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Zoomify(this.name,
+ this.url,
+ this.size,
+ this.options);
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+ var y = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h));
+ var z = this.getZoomForResolution( res );
+
+ var tileIndex = x + y * this.tierSizeInTiles[z].w + this.tileCountUpToTier[z];
+ var path = "TileGroup" + Math.floor( (tileIndex) / 256 ) +
+ "/" + z + "-" + x + "-" + y + ".jpg";
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(path, url);
+ }
+ return url + path;
+ },
+
+ /**
+ * Method: getImageSize
+ * getImageSize returns size for a particular tile. If bounds are given as
+ * first argument, size is calculated (bottom-right tiles are non square).
+ *
+ */
+ getImageSize: function() {
+ if (arguments.length > 0) {
+ var bounds = this.adjustBounds(arguments[0]);
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+ var y = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h));
+ var z = this.getZoomForResolution( res );
+ var w = this.standardTileSize;
+ var h = this.standardTileSize;
+ if (x == this.tierSizeInTiles[z].w -1 ) {
+ var w = this.tierImageSize[z].w % this.standardTileSize;
+ }
+ if (y == this.tierSizeInTiles[z].h -1 ) {
+ var h = this.tierImageSize[z].h % this.standardTileSize;
+ }
+ return (new OpenLayers.Size(w, h));
+ } else {
+ return this.tileSize;
+ }
+ },
+
+ /**
+ * APIMethod: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+ this.map.maxExtent.top);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Zoomify"
+});
+/* ======================================================================
+ OpenLayers/Layer/MapServer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MapServer
+ * Instances of OpenLayers.Layer.MapServer are used to display
+ * data from a MapServer CGI instance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.MapServer = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: {
+ mode: "map",
+ map_imagetype: "png"
+ },
+
+ /**
+ * Constructor: OpenLayers.Layer.MapServer
+ * Create a new MapServer layer object
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the MapServer CGI
+ * (e.g. http://www2.dmsolutions.ca/cgi-bin/mapserv)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
+
+ this.params = OpenLayers.Util.applyDefaults(
+ this.params, this.DEFAULT_PARAMS
+ );
+
+ // unless explicitly set in options, if the layer is transparent,
+ // it will be an overlay
+ if (options == null || options.isBaseLayer == null) {
+ this.isBaseLayer = ((this.params.transparent != "true") &&
+ (this.params.transparent != true));
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.MapServer>} An exact clone of this layer
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.MapServer(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ * Return a query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox
+ * for the request
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also
+ * the passed-in bounds and appropriate tile size specified
+ * as parameters.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ // Make a list, so that getFullRequestString uses literal ","
+ var extent = [bounds.left, bounds. bottom, bounds.right, bounds.top];
+
+ var imageSize = this.getImageSize();
+
+ // make lists, so that literal ','s are used
+ var url = this.getFullRequestString(
+ {mapext: extent,
+ imgext: extent,
+ map_size: [imageSize.w, imageSize.h],
+ imgx: imageSize.w / 2,
+ imgy: imageSize.h / 2,
+ imgxy: [imageSize.w, imageSize.h]
+ });
+
+ return url;
+ },
+
+ /**
+ * Method: getFullRequestString
+ * combine the layer's url with its params and these newParams.
+ *
+ * Parameters:
+ * newParams - {Object} New parameters that should be added to the
+ * request string.
+ * altUrl - {String} (optional) Replace the URL in the full request
+ * string with the provided URL.
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters embedded in it.
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ // use layer's url unless altUrl passed in
+ var url = (altUrl == null) ? this.url : altUrl;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams = OpenLayers.Util.upperCaseObject(
+ OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // requestString always starts with url
+ var requestString = url;
+
+ // MapServer needs '+' seperating things like bounds/height/width.
+ // Since typically this is URL encoded, we use a slight hack: we
+ // depend on the list-like functionality of getParameterString to
+ // leave ',' only in the case of list items (since otherwise it is
+ // encoded) then do a regular expression replace on the , characters
+ // to '+'
+ //
+ paramsString = paramsString.replace(/,/g, "+");
+
+ if (paramsString != "") {
+ var lastServerChar = url.charAt(url.length - 1);
+ if ((lastServerChar == "&") || (lastServerChar == "?")) {
+ requestString += paramsString;
+ } else {
+ if (url.indexOf('?') == -1) {
+ //serverPath has no ? -- add one
+ requestString += '?' + paramsString;
+ } else {
+ //serverPath contains ?, so must already have paramsString at the end
+ requestString += '&' + paramsString;
+ }
+ }
+ }
+ return requestString;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.MapServer"
+});
+/* ======================================================================
+ OpenLayers/Renderer/VML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.VML
+ * Render vector features in browsers with VML capability. Construct a new
+ * VML renderer with the <OpenLayers.Renderer.VML> constructor.
+ *
+ * Note that for all calculations in this class, we use (num | 0) to truncate a
+ * float value to an integer. This is done because it seems that VML doesn't
+ * support float values.
+ *
+ * Inherits from:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String} XML Namespace URN
+ */
+ xmlns: "urn:schemas-microsoft-com:vml",
+
+ /**
+ * Property: symbolCache
+ * {DOMElement} node holding symbols. This hash is keyed by symbol name,
+ * and each value is a hash with a "path" and an "extent" property.
+ */
+ symbolCache: {},
+
+ /**
+ * Property: offset
+ * {Object} Hash with "x" and "y" properties
+ */
+ offset: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.VML
+ * Create a new VML renderer.
+ *
+ * Parameters:
+ * containerID - {String} The id for the element that contains the renderer
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ if (!document.namespaces.olv) {
+ document.namespaces.add("olv", this.xmlns);
+ var style = document.createStyleSheet();
+ var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox'];
+ for (var i = 0, len = shapes.length; i < len; i++) {
+
+ style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
+ "position: absolute; display: inline-block;");
+ }
+ }
+
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ },
+
+ /**
+ * APIMethod: supported
+ * Determine whether a browser supports this renderer.
+ *
+ * Returns:
+ * {Boolean} The browser supports the VML renderer
+ */
+ supported: function() {
+ return !!(document.namespaces);
+ },
+
+ /**
+ * Method: setExtent
+ * Set the renderer's extent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+
+ var left = (extent.left/resolution) | 0;
+ var top = (extent.top/resolution - this.size.h) | 0;
+ if (resolutionChanged || !this.offset) {
+ this.offset = {x: left, y: top};
+ left = 0;
+ top = 0;
+ } else {
+ left = left - this.offset.x;
+ top = top - this.offset.y;
+ }
+
+
+ var org = (left - this.xOffset) + " " + top;
+ this.root.coordorigin = org;
+ var roots = [this.root, this.vectorRoot, this.textRoot];
+ var root;
+ for(var i=0, len=roots.length; i<len; ++i) {
+ root = roots[i];
+
+ var size = this.size.w + " " + this.size.h;
+ root.coordsize = size;
+
+ }
+ // flip the VML display Y axis upside down so it
+ // matches the display Y axis of the map
+ this.root.style.flip = "y";
+
+ return coordSysUnchanged;
+ },
+
+
+ /**
+ * Method: setSize
+ * Set the size of the drawing surface
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} the size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ // setting width and height on all roots to avoid flicker which we
+ // would get with 100% width and height on child roots
+ var roots = [
+ this.rendererRoot,
+ this.root,
+ this.vectorRoot,
+ this.textRoot
+ ];
+ var w = this.size.w + "px";
+ var h = this.size.h + "px";
+ var root;
+ for(var i=0, len=roots.length; i<len; ++i) {
+ root = roots[i];
+ root.style.width = w;
+ root.style.height = h;
+ }
+ },
+
+ /**
+ * Method: getNodeType
+ * Get the node type for a geometry and style
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "olv:rect";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "olv:shape";
+ } else {
+ nodeType = "olv:oval";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "olv:rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ case "OpenLayers.Geometry.LinearRing":
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "olv:shape";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a VML node.
+ *
+ * Parameters:
+ * node - {DOMElement} An VML element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ setStyle: function(node, style, options, geometry) {
+ style = style || node._style;
+ options = options || node._options;
+ var fillColor = style.fillColor;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.title = title;
+ }
+
+ if (node._geometryClass === "OpenLayers.Geometry.Point") {
+ if (style.externalGraphic) {
+ options.isFilled = true;
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+
+ var resolution = this.getResolution();
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ node.style.left = ((((geometry.x - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px";
+ node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px";
+ node.style.width = width + "px";
+ node.style.height = height + "px";
+ node.style.flip = "y";
+
+ // modify fillColor and options for stroke styling below
+ fillColor = "none";
+ options.isStroked = false;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ var cache = this.importSymbol(style.graphicName);
+ node.path = cache.path;
+ node.coordorigin = cache.left + "," + cache.bottom;
+ var size = cache.size;
+ node.coordsize = size + "," + size;
+ this.drawCircle(node, geometry, style.pointRadius);
+ node.style.flip = "y";
+ } else {
+ this.drawCircle(node, geometry, style.pointRadius);
+ }
+ }
+
+ // fill
+ if (options.isFilled) {
+ node.fillcolor = fillColor;
+ } else {
+ node.filled = "false";
+ }
+ var fills = node.getElementsByTagName("fill");
+ var fill = (fills.length == 0) ? null : fills[0];
+ if (!options.isFilled) {
+ if (fill) {
+ node.removeChild(fill);
+ }
+ } else {
+ if (!fill) {
+ fill = this.createNode('olv:fill', node.id + "_fill");
+ }
+ fill.opacity = style.fillOpacity;
+
+ if (node._geometryClass === "OpenLayers.Geometry.Point" &&
+ style.externalGraphic) {
+
+ // override fillOpacity
+ if (style.graphicOpacity) {
+ fill.opacity = style.graphicOpacity;
+ }
+
+ fill.src = style.externalGraphic;
+ fill.type = "frame";
+
+ if (!(style.graphicWidth && style.graphicHeight)) {
+ fill.aspect = "atmost";
+ }
+ }
+ if (fill.parentNode != node) {
+ node.appendChild(fill);
+ }
+ }
+
+ // additional rendering for rotated graphics or symbols
+ var rotation = style.rotation;
+ if ((rotation !== undefined || node._rotation !== undefined)) {
+ node._rotation = rotation;
+ if (style.externalGraphic) {
+ this.graphicRotate(node, xOffset, yOffset, style);
+ // make the fill fully transparent, because we now have
+ // the graphic as imagedata element. We cannot just remove
+ // the fill, because this is part of the hack described
+ // in graphicRotate
+ fill.opacity = 0;
+ } else if(node._geometryClass === "OpenLayers.Geometry.Point") {
+ node.style.rotation = rotation || 0;
+ }
+ }
+
+ // stroke
+ var strokes = node.getElementsByTagName("stroke");
+ var stroke = (strokes.length == 0) ? null : strokes[0];
+ if (!options.isStroked) {
+ node.stroked = false;
+ if (stroke) {
+ stroke.on = false;
+ }
+ } else {
+ if (!stroke) {
+ stroke = this.createNode('olv:stroke', node.id + "_stroke");
+ node.appendChild(stroke);
+ }
+ stroke.on = true;
+ stroke.color = style.strokeColor;
+ stroke.weight = style.strokeWidth + "px";
+ stroke.opacity = style.strokeOpacity;
+ stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' :
+ (style.strokeLinecap || 'round');
+ if (style.strokeDashstyle) {
+ stroke.dashstyle = this.dashStyle(style);
+ }
+ }
+
+ if (style.cursor != "inherit" && style.cursor != null) {
+ node.style.cursor = style.cursor;
+ }
+ return node;
+ },
+
+ /**
+ * Method: graphicRotate
+ * If a point is to be styled with externalGraphic and rotation, VML fills
+ * cannot be used to display the graphic, because rotation of graphic
+ * fills is not supported by the VML implementation of Internet Explorer.
+ * This method creates a olv:imagedata element inside the VML node,
+ * DXImageTransform.Matrix and BasicImage filters for rotation and
+ * opacity, and a 3-step hack to remove rendering artefacts from the
+ * graphic and preserve the ability of graphics to trigger events.
+ * Finally, OpenLayers methods are used to determine the correct
+ * insertion point of the rotated image, because DXImageTransform.Matrix
+ * does the rotation without the ability to specify a rotation center
+ * point.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * xOffset - {Number} rotation center relative to image, x coordinate
+ * yOffset - {Number} rotation center relative to image, y coordinate
+ * style - {Object}
+ */
+ graphicRotate: function(node, xOffset, yOffset, style) {
+ var style = style || node._style;
+ var rotation = style.rotation || 0;
+
+ var aspectRatio, size;
+ if (!(style.graphicWidth && style.graphicHeight)) {
+ // load the image to determine its size
+ var img = new Image();
+ img.onreadystatechange = OpenLayers.Function.bind(function() {
+ if(img.readyState == "complete" ||
+ img.readyState == "interactive") {
+ aspectRatio = img.width / img.height;
+ size = Math.max(style.pointRadius * 2,
+ style.graphicWidth || 0,
+ style.graphicHeight || 0);
+ xOffset = xOffset * aspectRatio;
+ style.graphicWidth = size * aspectRatio;
+ style.graphicHeight = size;
+ this.graphicRotate(node, xOffset, yOffset, style);
+ }
+ }, this);
+ img.src = style.externalGraphic;
+
+ // will be called again by the onreadystate handler
+ return;
+ } else {
+ size = Math.max(style.graphicWidth, style.graphicHeight);
+ aspectRatio = style.graphicWidth / style.graphicHeight;
+ }
+
+ var width = Math.round(style.graphicWidth || size * aspectRatio);
+ var height = Math.round(style.graphicHeight || size);
+ node.style.width = width + "px";
+ node.style.height = height + "px";
+
+ // Three steps are required to remove artefacts for images with
+ // transparent backgrounds (resulting from using DXImageTransform
+ // filters on svg objects), while preserving awareness for browser
+ // events on images:
+ // - Use the fill as usual (like for unrotated images) to handle
+ // events
+ // - specify an imagedata element with the same src as the fill
+ // - style the imagedata element with an AlphaImageLoader filter
+ // with empty src
+ var image = document.getElementById(node.id + "_image");
+ if (!image) {
+ image = this.createNode("olv:imagedata", node.id + "_image");
+ node.appendChild(image);
+ }
+ image.style.width = width + "px";
+ image.style.height = height + "px";
+ image.src = style.externalGraphic;
+ image.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(" +
+ "src='', sizingMethod='scale')";
+
+ var rot = rotation * Math.PI / 180;
+ var sintheta = Math.sin(rot);
+ var costheta = Math.cos(rot);
+
+ // do the rotation on the image
+ var filter =
+ "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
+ ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
+ ",SizingMethod='auto expand')\n";
+
+ // set the opacity (needed for the imagedata)
+ var opacity = style.graphicOpacity || style.fillOpacity;
+ if (opacity && opacity != 1) {
+ filter +=
+ "progid:DXImageTransform.Microsoft.BasicImage(opacity=" +
+ opacity+")\n";
+ }
+ node.style.filter = filter;
+
+ // do the rotation again on a box, so we know the insertion point
+ var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
+ var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
+ imgBox.rotate(style.rotation, centerPoint);
+ var imgBounds = imgBox.getBounds();
+
+ node.style.left = Math.round(
+ parseInt(node.style.left) + imgBounds.left) + "px";
+ node.style.top = Math.round(
+ parseInt(node.style.top) - imgBounds.bottom) + "px";
+ },
+
+ /**
+ * Method: postDraw
+ * Does some node postprocessing to work around browser issues:
+ * - Some versions of Internet Explorer seem to be unable to set fillcolor
+ * and strokecolor to "none" correctly before the fill node is appended
+ * to a visible vml node. This method takes care of that and sets
+ * fillcolor and strokecolor again if needed.
+ * - In some cases, a node won't become visible after being drawn. Setting
+ * style.visibility to "visible" works around that.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {
+ node.style.visibility = "visible";
+ var fillColor = node._style.fillColor;
+ var strokeColor = node._style.strokeColor;
+ if (fillColor == "none" &&
+ node.fillcolor != fillColor) {
+ node.fillcolor = fillColor;
+ }
+ if (strokeColor == "none" &&
+ node.strokecolor != strokeColor) {
+ node.strokecolor = strokeColor;
+ }
+ },
+
+
+ /**
+ * Method: setNodeDimension
+ * Get the geometry's bounds, convert it to our vml coordinate system,
+ * then set the node's position, size, and local coordinate system.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ setNodeDimension: function(node, geometry) {
+
+ var bbox = geometry.getBounds();
+ if(bbox) {
+ var resolution = this.getResolution();
+
+ var scaledBox =
+ new OpenLayers.Bounds(((bbox.left - this.featureDx)/resolution - this.offset.x) | 0,
+ (bbox.bottom/resolution - this.offset.y) | 0,
+ ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0,
+ (bbox.top/resolution - this.offset.y) | 0);
+
+ // Set the internal coordinate system to draw the path
+ node.style.left = scaledBox.left + "px";
+ node.style.top = scaledBox.top + "px";
+ node.style.width = scaledBox.getWidth() + "px";
+ node.style.height = scaledBox.getHeight() + "px";
+
+ node.coordorigin = scaledBox.left + " " + scaledBox.top;
+ node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
+ }
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ *
+ * Returns:
+ * {String} A VML compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style) {
+ var dash = style.strokeDashstyle;
+ switch (dash) {
+ case 'solid':
+ case 'dot':
+ case 'dash':
+ case 'dashdot':
+ case 'longdash':
+ case 'longdashdot':
+ return dash;
+ default:
+ // very basic guessing of dash style patterns
+ var parts = dash.split(/[ ,]/);
+ if (parts.length == 2) {
+ if (1*parts[0] >= 2*parts[1]) {
+ return "longdash";
+ }
+ return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
+ } else if (parts.length == 4) {
+ return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
+ "dashdot";
+ }
+ return "solid";
+ }
+ },
+
+ /**
+ * Method: createNode
+ * Create a new node
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElement(type);
+ if (id) {
+ node.id = id;
+ }
+
+ // IE hack to make elements unselectable, to prevent 'blue flash'
+ // while dragging vectors; #1410
+ node.unselectable = 'on';
+ node.onselectstart = OpenLayers.Function.False;
+
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ * Determine whether a node is of a given type
+ *
+ * Parameters:
+ * node - {DOMElement} An VML element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+
+ //split type
+ var subType = type;
+ var splitIndex = subType.indexOf(":");
+ if (splitIndex != -1) {
+ subType = subType.substr(splitIndex+1);
+ }
+
+ //split nodeName
+ var nodeName = node.nodeName;
+ splitIndex = nodeName.indexOf(":");
+ if (splitIndex != -1) {
+ nodeName = nodeName.substr(splitIndex+1);
+ }
+
+ return (subType == nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ * Create the renderer root
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ return this.nodeFactory(this.container.id + "_vmlRoot", "div");
+ },
+
+ /**
+ * Method: createRoot
+ * Create the main root element
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "olv:group");
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * Render a point
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the point could not be drawn
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * Render a circle.
+ * Size and Center a circle given geometry (x,y center) and radius
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {float}
+ *
+ * Returns:
+ * {DOMElement} or false if the circle could not ne drawn
+ */
+ drawCircle: function(node, geometry, radius) {
+ if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
+ var resolution = this.getResolution();
+
+ node.style.left = ((((geometry.x - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px";
+ node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - radius) + "px";
+
+ var diameter = radius * 2;
+
+ node.style.width = diameter + "px";
+ node.style.height = diameter + "px";
+ return node;
+ }
+ return false;
+ },
+
+
+ /**
+ * Method: drawLineString
+ * Render a linestring.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLineString: function(node, geometry) {
+ return this.drawLine(node, geometry, false);
+ },
+
+ /**
+ * Method: drawLinearRing
+ * Render a linearring
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLinearRing: function(node, geometry) {
+ return this.drawLine(node, geometry, true);
+ },
+
+ /**
+ * Method: DrawLine
+ * Render a line.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * closeLine - {Boolean} Close the line? (make it a ring?)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLine: function(node, geometry, closeLine) {
+
+ this.setNodeDimension(node, geometry);
+
+ var resolution = this.getResolution();
+ var numComponents = geometry.components.length;
+ var parts = new Array(numComponents);
+
+ var comp, x, y;
+ for (var i = 0; i < numComponents; i++) {
+ comp = geometry.components[i];
+ x = ((comp.x - this.featureDx)/resolution - this.offset.x) | 0;
+ y = (comp.y/resolution - this.offset.y) | 0;
+ parts[i] = " " + x + "," + y + " l ";
+ }
+ var end = (closeLine) ? " x e" : " e";
+ node.path = "m" + parts.join("") + end;
+ return node;
+ },
+
+ /**
+ * Method: drawPolygon
+ * Render a polygon
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawPolygon: function(node, geometry) {
+ this.setNodeDimension(node, geometry);
+
+ var resolution = this.getResolution();
+
+ var path = [];
+ var j, jj, points, area, first, second, i, ii, comp, pathComp, x, y;
+ for (j=0, jj=geometry.components.length; j<jj; j++) {
+ path.push("m");
+ points = geometry.components[j].components;
+ // we only close paths of interior rings with area
+ area = (j === 0);
+ first = null;
+ second = null;
+ for (i=0, ii=points.length; i<ii; i++) {
+ comp = points[i];
+ x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
+ y = (comp.y / resolution - this.offset.y) | 0;
+ pathComp = " " + x + "," + y;
+ path.push(pathComp);
+ if (i==0) {
+ path.push(" l");
+ }
+ if (!area) {
+ // IE improperly renders sub-paths that have no area.
+ // Instead of checking the area of every ring, we confirm
+ // the ring has at least three distinct points. This does
+ // not catch all non-zero area cases, but it greatly improves
+ // interior ring digitizing and is a minor performance hit
+ // when rendering rings with many points.
+ if (!first) {
+ first = pathComp;
+ } else if (first != pathComp) {
+ if (!second) {
+ second = pathComp;
+ } else if (second != pathComp) {
+ // stop looking
+ area = true;
+ }
+ }
+ }
+ }
+ path.push(area ? " x " : " ");
+ }
+ path.push("e");
+ node.path = path.join("");
+ return node;
+ },
+
+ /**
+ * Method: drawRectangle
+ * Render a rectangle
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+
+ node.style.left = (((geometry.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
+ node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px";
+ node.style.width = ((geometry.width/resolution) | 0) + "px";
+ node.style.height = ((geometry.height/resolution) | 0) + "px";
+
+ return node;
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
+ var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
+
+ var resolution = this.getResolution();
+ label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
+ label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px";
+ label.style.flip = "y";
+
+ textbox.innerText = style.label;
+
+ if (style.cursor != "inherit" && style.cursor != null) {
+ textbox.style.cursor = style.cursor;
+ }
+ if (style.fontColor) {
+ textbox.style.color = style.fontColor;
+ }
+ if (style.fontOpacity) {
+ textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')';
+ }
+ if (style.fontFamily) {
+ textbox.style.fontFamily = style.fontFamily;
+ }
+ if (style.fontSize) {
+ textbox.style.fontSize = style.fontSize;
+ }
+ if (style.fontWeight) {
+ textbox.style.fontWeight = style.fontWeight;
+ }
+ if (style.fontStyle) {
+ textbox.style.fontStyle = style.fontStyle;
+ }
+ if(style.labelSelect === true) {
+ label._featureId = featureId;
+ textbox._featureId = featureId;
+ textbox._geometry = location;
+ textbox._geometryClass = location.CLASS_NAME;
+ }
+ textbox.style.whiteSpace = "nowrap";
+ // fun with IE: IE7 in standards compliant mode does not display any
+ // text with a left inset of 0. So we set this to 1px and subtract one
+ // pixel later when we set label.style.left
+ textbox.inset = "1px,0px,0px,0px";
+
+ if(!label.parentNode) {
+ label.appendChild(textbox);
+ this.textRoot.appendChild(label);
+ }
+
+ var align = style.labelAlign || "cm";
+ if (align.length == 1) {
+ align += "m";
+ }
+ var xshift = textbox.clientWidth *
+ (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
+ var yshift = textbox.clientHeight *
+ (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
+ label.style.left = parseInt(label.style.left)-xshift-1+"px";
+ label.style.top = parseInt(label.style.top)+yshift+"px";
+
+ },
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ * root - {DOMElement} optional root node. To be used when this renderer
+ * holds roots from multiple layers to tell this method which one to
+ * detach
+ *
+ * Returns:
+ * {Boolean} true if successful, false otherwise
+ */
+ moveRoot: function(renderer) {
+ var layer = this.map.getLayer(renderer.container.id);
+ if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
+ layer = this.map.getLayer(this.container.id);
+ }
+ layer && layer.renderer.clear();
+ OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
+ layer && layer.redraw();
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
+ */
+ importSymbol: function (graphicName) {
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the cache
+ var cache = this.symbolCache[id];
+ if (cache) {
+ return cache;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var pathitems = ["m"];
+ for (var i=0; i<symbol.length; i=i+2) {
+ var x = symbol[i];
+ var y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+
+ pathitems.push(x);
+ pathitems.push(y);
+ if (i == 0) {
+ pathitems.push("l");
+ }
+ }
+ pathitems.push("x e");
+ var path = pathitems.join(" ");
+
+ var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
+ if(diff > 0) {
+ symbolExtent.bottom = symbolExtent.bottom - diff;
+ symbolExtent.top = symbolExtent.top + diff;
+ } else {
+ symbolExtent.left = symbolExtent.left + diff;
+ symbolExtent.right = symbolExtent.right - diff;
+ }
+
+ cache = {
+ path: path,
+ size: symbolExtent.getWidth(), // equals getHeight() now
+ left: symbolExtent.left,
+ bottom: symbolExtent.bottom
+ };
+ this.symbolCache[id] = cache;
+
+ return cache;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.VML"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.VML.LABEL_SHIFT = {
+ "l": 0,
+ "c": .5,
+ "r": 1,
+ "t": 0,
+ "m": .5,
+ "b": 1
+};
+/* ======================================================================
+ OpenLayers/Control/CacheRead.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.CacheRead
+ * A control for using image tiles cached with <OpenLayers.Control.CacheWrite>
+ * from the browser's local storage.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.CacheRead = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: fetchEvent
+ * {String} The layer event to listen to for replacing remote resource tile
+ * URLs with cached data URIs. Supported values are "tileerror" (try
+ * remote first, fall back to cached) and "tileloadstart" (try cache
+ * first, fall back to remote). Default is "tileloadstart".
+ *
+ * Note that "tileerror" will not work for CORS enabled images (see
+ * https://developer.mozilla.org/en/CORS_Enabled_Image), i.e. layers
+ * configured with a <OpenLayers.Tile.Image.crossOriginKeyword> in
+ * <OpenLayers.Layer.Grid.tileOptions>.
+ */
+ fetchEvent: "tileloadstart",
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, only these
+ * layers will receive tiles from the cache.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.CacheRead
+ *
+ * Parameters:
+ * options - {Object} Object with API properties for this control
+ */
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ var i, layers = this.layers || map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.addLayer({layer: layers[i]});
+ }
+ if (!this.layers) {
+ map.events.on({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Adds a layer to the control. Once added, tiles requested for this layer
+ * will be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ addLayer: function(evt) {
+ evt.layer.events.register(this.fetchEvent, this, this.fetch);
+ },
+
+ /**
+ * Method: removeLayer
+ * Removes a layer from the control. Once removed, tiles requested for this
+ * layer will no longer be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ removeLayer: function(evt) {
+ evt.layer.events.unregister(this.fetchEvent, this, this.fetch);
+ },
+
+ /**
+ * Method: fetch
+ * Listener to the <fetchEvent> event. Replaces a tile's url with a data
+ * URI from the cache.
+ *
+ * Parameters:
+ * evt - {Object} Event object with a tile property.
+ */
+ fetch: function(evt) {
+ if (this.active && window.localStorage &&
+ evt.tile instanceof OpenLayers.Tile.Image) {
+ var tile = evt.tile,
+ url = tile.url;
+ // deal with modified tile urls when both CacheWrite and CacheRead
+ // are active
+ if (!tile.layer.crossOriginKeyword && OpenLayers.ProxyHost &&
+ url.indexOf(OpenLayers.ProxyHost) === 0) {
+ url = OpenLayers.Control.CacheWrite.urlMap[url];
+ }
+ var dataURI = window.localStorage.getItem("olCache_" + url);
+ if (dataURI) {
+ tile.url = dataURI;
+ if (evt.type === "tileerror") {
+ tile.setImgSrc(dataURI);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ if (this.layers || this.map) {
+ var i, layers = this.layers || this.map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.removeLayer({layer: layers[i]});
+ }
+ }
+ if (this.map) {
+ this.map.events.un({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.CacheRead"
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS/v1.js
+ * @requires OpenLayers/Format/WFST/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1_0_0
+ * A WFS v1.0.0 protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.WFS.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.WFS.v1>
+ */
+OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS.v1_0_0
+ * A class for giving layers WFS v1.0.0 protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/Format/WMSGetFeatureInfo.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSGetFeatureInfo
+ * Class to read GetFeatureInfo responses from Web Mapping Services
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: layerIdentifier
+ * {String} All xml nodes containing this search criteria will populate an
+ * internal array of layer nodes.
+ */
+ layerIdentifier: '_layer',
+
+ /**
+ * APIProperty: featureIdentifier
+ * {String} All xml nodes containing this search criteria will populate an
+ * internal array of feature nodes for each layer node found.
+ */
+ featureIdentifier: '_feature',
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: gmlFormat
+ * {<OpenLayers.Format.GML>} internal GML format for parsing geometries
+ * in msGMLOutput
+ */
+ gmlFormat: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WMSGetFeatureInfo
+ * Create a new parser for WMS GetFeatureInfo responses
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read WMS GetFeatureInfo data from a string, and return an array of features
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ var result;
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ if(root) {
+ var scope = this;
+ var read = this["read_" + root.nodeName];
+ if(read) {
+ result = read.call(this, root);
+ } else {
+ // fall-back to GML since this is a common output format for WMS
+ // GetFeatureInfo responses
+ result = new OpenLayers.Format.GML((this.options ? this.options : {})).read(data);
+ }
+ } else {
+ result = data;
+ }
+ return result;
+ },
+
+
+ /**
+ * Method: read_msGMLOutput
+ * Parse msGMLOutput nodes.
+ *
+ * Parameters:
+ * data - {DOMElement}
+ *
+ * Returns:
+ * {Array}
+ */
+ read_msGMLOutput: function(data) {
+ var response = [];
+ var layerNodes = this.getSiblingNodesByTagCriteria(data,
+ this.layerIdentifier);
+ if (layerNodes) {
+ for (var i=0, len=layerNodes.length; i<len; ++i) {
+ var node = layerNodes[i];
+ var layerName = node.nodeName;
+ if (node.prefix) {
+ layerName = layerName.split(':')[1];
+ }
+ var layerName = layerName.replace(this.layerIdentifier, '');
+ var featureNodes = this.getSiblingNodesByTagCriteria(node,
+ this.featureIdentifier);
+ if (featureNodes) {
+ for (var j = 0; j < featureNodes.length; j++) {
+ var featureNode = featureNodes[j];
+ var geomInfo = this.parseGeometry(featureNode);
+ var attributes = this.parseAttributes(featureNode);
+ var feature = new OpenLayers.Feature.Vector(geomInfo.geometry,
+ attributes, null);
+ feature.bounds = geomInfo.bounds;
+ feature.type = layerName;
+ response.push(feature);
+ }
+ }
+ }
+ }
+ return response;
+ },
+
+ /**
+ * Method: read_FeatureInfoResponse
+ * Parse FeatureInfoResponse nodes.
+ *
+ * Parameters:
+ * data - {DOMElement}
+ *
+ * Returns:
+ * {Array}
+ */
+ read_FeatureInfoResponse: function(data) {
+ var response = [];
+ var featureNodes = this.getElementsByTagNameNS(data, '*',
+ 'FIELDS');
+
+ for(var i=0, len=featureNodes.length;i<len;i++) {
+ var featureNode = featureNodes[i];
+ var geom = null;
+
+ // attributes can be actual attributes on the FIELDS tag,
+ // or FIELD children
+ var attributes = {};
+ var j;
+ var jlen = featureNode.attributes.length;
+ if (jlen > 0) {
+ for(j=0; j<jlen; j++) {
+ var attribute = featureNode.attributes[j];
+ attributes[attribute.nodeName] = attribute.nodeValue;
+ }
+ } else {
+ var nodes = featureNode.childNodes;
+ for (j=0, jlen=nodes.length; j<jlen; ++j) {
+ var node = nodes[j];
+ if (node.nodeType != 3) {
+ attributes[node.getAttribute("name")] =
+ node.getAttribute("value");
+ }
+ }
+ }
+
+ response.push(
+ new OpenLayers.Feature.Vector(geom, attributes, null)
+ );
+ }
+ return response;
+ },
+
+ /**
+ * Method: getSiblingNodesByTagCriteria
+ * Recursively searches passed xml node and all it's descendant levels for
+ * nodes whose tagName contains the passed search string. This returns an
+ * array of all sibling nodes which match the criteria from the highest
+ * hierarchial level from which a match is found.
+ *
+ * Parameters:
+ * node - {DOMElement} An xml node
+ * criteria - {String} Search string which will match some part of a tagName
+ *
+ * Returns:
+ * Array({DOMElement}) An array of sibling xml nodes
+ */
+ getSiblingNodesByTagCriteria: function(node, criteria){
+ var nodes = [];
+ var children, tagName, n, matchNodes, child;
+ if (node && node.hasChildNodes()) {
+ children = node.childNodes;
+ n = children.length;
+
+ for(var k=0; k<n; k++){
+ child = children[k];
+ while (child && child.nodeType != 1) {
+ child = child.nextSibling;
+ k++;
+ }
+ tagName = (child ? child.nodeName : '');
+ if (tagName.length > 0 && tagName.indexOf(criteria) > -1) {
+ nodes.push(child);
+ } else {
+ matchNodes = this.getSiblingNodesByTagCriteria(
+ child, criteria);
+
+ if(matchNodes.length > 0){
+ (nodes.length == 0) ?
+ nodes = matchNodes : nodes.push(matchNodes);
+ }
+ }
+ }
+
+ }
+ return nodes;
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {<DOMElement>}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ *
+ * Notes:
+ * Assumes that attributes are direct child xml nodes of the passed node
+ * and contain only a single text node.
+ */
+ parseAttributes: function(node){
+ var attributes = {};
+ if (node.nodeType == 1) {
+ var children = node.childNodes;
+ var n = children.length;
+ for (var i = 0; i < n; ++i) {
+ var child = children[i];
+ if (child.nodeType == 1) {
+ var grandchildren = child.childNodes;
+ var name = (child.prefix) ?
+ child.nodeName.split(":")[1] : child.nodeName;
+ if (grandchildren.length == 0) {
+ attributes[name] = null;
+ } else if (grandchildren.length == 1) {
+ var grandchild = grandchildren[0];
+ if (grandchild.nodeType == 3 ||
+ grandchild.nodeType == 4) {
+ var value = grandchild.nodeValue.replace(
+ this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ }
+ }
+ }
+ }
+ return attributes;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Parse the geometry and the feature bounds out of the node using
+ * Format.GML
+ *
+ * Parameters:
+ * node - {<DOMElement>}
+ *
+ * Returns:
+ * {Object} An object containing the geometry and the feature bounds
+ */
+ parseGeometry: function(node) {
+ // we need to use the old Format.GML parser since we do not know the
+ // geometry name
+ if (!this.gmlFormat) {
+ this.gmlFormat = new OpenLayers.Format.GML();
+ }
+ var feature = this.gmlFormat.parseFeature(node);
+ var geometry, bounds = null;
+ if (feature) {
+ geometry = feature.geometry && feature.geometry.clone();
+ bounds = feature.bounds && feature.bounds.clone();
+ feature.destroy();
+ }
+ return {geometry: geometry, bounds: bounds};
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSGetFeatureInfo"
+
+});
+/* ======================================================================
+ OpenLayers/Control/WMTSGetFeatureInfo.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Format/WMSGetFeatureInfo.js
+ */
+
+/**
+ * Class: OpenLayers.Control.WMTSGetFeatureInfo
+ * The WMTSGetFeatureInfo control uses a WMTS query to get information about a
+ * point on the map. The information may be in a display-friendly format
+ * such as HTML, or a machine-friendly format such as GML, depending on the
+ * server's capabilities and the client's configuration. This control
+ * handles click or hover events, attempts to parse the results using an
+ * OpenLayers.Format, and fires a 'getfeatureinfo' event for each layer
+ * queried.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.WMTSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send GetFeatureInfo requests when mouse stops moving.
+ * Default is false.
+ */
+ hover: false,
+
+ /**
+ * Property: requestEncoding
+ * {String} One of "KVP" or "REST". Only KVP encoding is supported at this
+ * time.
+ */
+ requestEncoding: "KVP",
+
+ /**
+ * APIProperty: drillDown
+ * {Boolean} Drill down over all WMTS layers in the map. When
+ * using drillDown mode, hover is not possible. A getfeatureinfo event
+ * will be fired for each layer queried.
+ */
+ drillDown: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a WMTS query. This
+ * sets the feature_count parameter on WMTS GetFeatureInfo
+ * requests.
+ */
+ maxFeatures: 10,
+
+ /** APIProperty: clickCallback
+ * {String} The click callback to register in the
+ * {<OpenLayers.Handler.Click>} object created when the hover
+ * option is set to false. Default is "click".
+ */
+ clickCallback: "click",
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.WMTS>)} The layers to query for feature info.
+ * If omitted, all map WMTS layers will be considered.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: queryVisible
+ * {Boolean} Filter out hidden layers when searching the map for layers to
+ * query. Default is true.
+ */
+ queryVisible: true,
+
+ /**
+ * Property: infoFormat
+ * {String} The mimetype to request from the server
+ */
+ infoFormat: 'text/html',
+
+ /**
+ * Property: vendorParams
+ * {Object} Additional parameters that will be added to the request, for
+ * WMTS implementations that support them. This could e.g. look like
+ * (start code)
+ * {
+ * radius: 5
+ * }
+ * (end)
+ */
+ vendorParams: {},
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} A format for parsing GetFeatureInfo responses.
+ * Default is <OpenLayers.Format.WMSGetFeatureInfo>.
+ */
+ format: null,
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional properties to set on the format (if one is not provided
+ * in the <format> property.
+ */
+ formatOptions: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control, e.g.
+ * (start code)
+ * {
+ * "click": {delay: 100},
+ * "hover": {delay: 300}
+ * }
+ * (end)
+ */
+
+ /**
+ * Property: handler
+ * {Object} Reference to the <OpenLayers.Handler> for this control
+ */
+ handler: null,
+
+ /**
+ * Property: hoverRequest
+ * {<OpenLayers.Request>} contains the currently running hover request
+ * (if any).
+ */
+ hoverRequest: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforegetfeatureinfo - Triggered before each request is sent.
+ * The event object has an *xy* property with the position of the
+ * mouse click or hover event that triggers the request and a *layer*
+ * property referencing the layer about to be queried. If a listener
+ * returns false, the request will not be issued.
+ * getfeatureinfo - Triggered when a GetFeatureInfo response is received.
+ * The event object has a *text* property with the body of the
+ * response (String), a *features* property with an array of the
+ * parsed features, an *xy* property with the position of the mouse
+ * click or hover event that triggered the request, a *layer* property
+ * referencing the layer queried and a *request* property with the
+ * request itself. If drillDown is set to true, one event will be fired
+ * for each layer queried.
+ * exception - Triggered when a GetFeatureInfo request fails (with a
+ * status other than 200) or whenparsing fails. Listeners will receive
+ * an event with *request*, *xy*, and *layer* properties. In the case
+ * of a parsing error, the event will also contain an *error* property.
+ */
+
+ /**
+ * Property: pending
+ * {Number} The number of pending requests.
+ */
+ pending: 0,
+
+ /**
+ * Constructor: <OpenLayers.Control.WMTSGetFeatureInfo>
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if (!this.format) {
+ this.format = new OpenLayers.Format.WMSGetFeatureInfo(
+ options.formatOptions
+ );
+ }
+
+ if (this.drillDown === true) {
+ this.hover = false;
+ }
+
+ if (this.hover) {
+ this.handler = new OpenLayers.Handler.Hover(
+ this, {
+ move: this.cancelHover,
+ pause: this.getInfoForHover
+ },
+ OpenLayers.Util.extend(
+ this.handlerOptions.hover || {}, {delay: 250}
+ )
+ );
+ } else {
+ var callbacks = {};
+ callbacks[this.clickCallback] = this.getInfoForClick;
+ this.handler = new OpenLayers.Handler.Click(
+ this, callbacks, this.handlerOptions.click || {}
+ );
+ }
+ },
+
+ /**
+ * Method: getInfoForClick
+ * Called on click
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ getInfoForClick: function(evt) {
+ this.request(evt.xy, {});
+ },
+
+ /**
+ * Method: getInfoForHover
+ * Pause callback for the hover handler
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ getInfoForHover: function(evt) {
+ this.request(evt.xy, {hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Cancel callback for the hover handler
+ */
+ cancelHover: function() {
+ if (this.hoverRequest) {
+ --this.pending;
+ if (this.pending <= 0) {
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ this.pending = 0;
+ }
+ this.hoverRequest.abort();
+ this.hoverRequest = null;
+ }
+ },
+
+ /**
+ * Method: findLayers
+ * Internal method to get the layers, independent of whether we are
+ * inspecting the map or using a client-provided array
+ */
+ findLayers: function() {
+ var candidates = this.layers || this.map.layers;
+ var layers = [];
+ var layer;
+ for (var i=candidates.length-1; i>=0; --i) {
+ layer = candidates[i];
+ if (layer instanceof OpenLayers.Layer.WMTS &&
+ layer.requestEncoding === this.requestEncoding &&
+ (!this.queryVisible || layer.getVisibility())) {
+ layers.push(layer);
+ if (!this.drillDown || this.hover) {
+ break;
+ }
+ }
+ }
+ return layers;
+ },
+
+ /**
+ * Method: buildRequestOptions
+ * Build an object with the relevant options for the GetFeatureInfo request.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMTS>} A WMTS layer.
+ * xy - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ */
+ buildRequestOptions: function(layer, xy) {
+ var loc = this.map.getLonLatFromPixel(xy);
+ var getTileUrl = layer.getURL(
+ new OpenLayers.Bounds(loc.lon, loc.lat, loc.lon, loc.lat)
+ );
+ var params = OpenLayers.Util.getParameters(getTileUrl);
+ var tileInfo = layer.getTileInfo(loc);
+ OpenLayers.Util.extend(params, {
+ service: "WMTS",
+ version: layer.version,
+ request: "GetFeatureInfo",
+ infoFormat: this.infoFormat,
+ i: tileInfo.i,
+ j: tileInfo.j
+ });
+ OpenLayers.Util.applyDefaults(params, this.vendorParams);
+ return {
+ url: OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url,
+ params: OpenLayers.Util.upperCaseObject(params),
+ callback: function(request) {
+ this.handleResponse(xy, request, layer);
+ },
+ scope: this
+ };
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeatureInfo request to the WMTS
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The position on the map where the mouse event
+ * occurred.
+ * options - {Object} additional options for this method.
+ *
+ * Valid options:
+ * - *hover* {Boolean} true if we do the request for the hover handler
+ */
+ request: function(xy, options) {
+ options = options || {};
+ var layers = this.findLayers();
+ if (layers.length > 0) {
+ var issue, layer;
+ for (var i=0, len=layers.length; i<len; i++) {
+ layer = layers[i];
+ issue = this.events.triggerEvent("beforegetfeatureinfo", {
+ xy: xy,
+ layer: layer
+ });
+ if (issue !== false) {
+ ++this.pending;
+ var requestOptions = this.buildRequestOptions(layer, xy);
+ var request = OpenLayers.Request.GET(requestOptions);
+ if (options.hover === true) {
+ this.hoverRequest = request;
+ }
+ }
+ }
+ if (this.pending > 0) {
+ OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
+ }
+ }
+ },
+
+ /**
+ * Method: handleResponse
+ * Handler for the GetFeatureInfo response.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The position on the map where the mouse event
+ * occurred.
+ * request - {XMLHttpRequest} The request object.
+ * layer - {<OpenLayers.Layer.WMTS>} The queried layer.
+ */
+ handleResponse: function(xy, request, layer) {
+ --this.pending;
+ if (this.pending <= 0) {
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ this.pending = 0;
+ }
+ if (request.status && (request.status < 200 || request.status >= 300)) {
+ this.events.triggerEvent("exception", {
+ xy: xy,
+ request: request,
+ layer: layer
+ });
+ } else {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var features, except;
+ try {
+ features = this.format.read(doc);
+ } catch (error) {
+ except = true;
+ this.events.triggerEvent("exception", {
+ xy: xy,
+ request: request,
+ error: error,
+ layer: layer
+ });
+ }
+ if (!except) {
+ this.events.triggerEvent("getfeatureinfo", {
+ text: request.responseText,
+ features: features,
+ request: request,
+ xy: xy,
+ layer: layer
+ });
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.WMTSGetFeatureInfo"
+});
+/* ======================================================================
+ OpenLayers/Protocol/CSW/v2_0_2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/CSW.js
+ * @requires OpenLayers/Format/CSWGetRecords/v2_0_2.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.CSW.v2_0_2
+ * CS-W (Catalogue services for the Web) version 2.0.2 protocol.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.CSW.v2_0_2 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.CSW.v2_0_2
+ * A class for CSW version 2.0.2 protocol management.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = new OpenLayers.Format.CSWGetRecords.v2_0_2(OpenLayers.Util.extend({
+ }, this.formatOptions));
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * Method: read
+ * Construct a request for reading new records from the Catalogue.
+ */
+ read: function(options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+
+ var data = this.format.write(options.params || options);
+
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ params: options.params,
+ headers: options.headers,
+ data: data
+ });
+
+ return response;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * This response is given a code property, and optionally a data property.
+ * The latter represents the CSW records as returned by the call to
+ * the CSW format read method.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ response.data = this.parseData(request);
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseData
+ * Read HTTP response body and return records
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Object} The CSW records as returned by the call to the format read method.
+ */
+ parseData: function(request) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.CSW.v2_0_2"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WCSCapabilities/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WCSCapabilities/v1.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities/v1_1_0
+ * Read WCS Capabilities version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WCSCapabilities.v1>
+ */
+OpenLayers.Format.WCSCapabilities.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WCSCapabilities.v1, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wcs: "http://www.opengis.net/wcs/1.1",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ ows: "http://www.opengis.net/ows/1.1"
+ },
+
+ /**
+ * APIProperty: errorProperty
+ * {String} Which property of the returned object to check for in order to
+ * determine whether or not parsing has failed. In the case that the
+ * errorProperty is undefined on the returned object, the document will be
+ * run through an OGCExceptionReport parser.
+ */
+ errorProperty: "operationsMetadata",
+
+ /**
+ * Constructor: OpenLayers.Format.WCSCapabilities.v1_1_0
+ * Create a new parser for WCS capabilities version 1.1.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wcs": OpenLayers.Util.applyDefaults({
+ // In 1.0.0, this was WCS_Capabilties, in 1.1.0, it's Capabilities
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Contents": function(node, request) {
+ request.contentMetadata = [];
+ this.readChildNodes(node, request.contentMetadata);
+ },
+ "CoverageSummary": function(node, contentMetadata) {
+ var coverageSummary = {};
+ // Read the summary:
+ this.readChildNodes(node, coverageSummary);
+
+ // Add it to the contentMetadata array:
+ contentMetadata.push(coverageSummary);
+ },
+ "Identifier": function(node, coverageSummary) {
+ coverageSummary.identifier = this.getChildValue(node);
+ },
+ "Title": function(node, coverageSummary) {
+ coverageSummary.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, coverageSummary) {
+ coverageSummary["abstract"] = this.getChildValue(node);
+ },
+ "SupportedCRS": function(node, coverageSummary) {
+ var crs = this.getChildValue(node);
+ if(crs) {
+ if(!coverageSummary.supportedCRS) {
+ coverageSummary.supportedCRS = [];
+ }
+ coverageSummary.supportedCRS.push(crs);
+ }
+ },
+ "SupportedFormat": function(node, coverageSummary) {
+ var format = this.getChildValue(node);
+ if(format) {
+ if(!coverageSummary.supportedFormat) {
+ coverageSummary.supportedFormat = [];
+ }
+ coverageSummary.supportedFormat.push(format);
+ }
+ }
+ }, OpenLayers.Format.WCSCapabilities.v1.prototype.readers["wcs"]),
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1_1_0"
+
+});
+/* ======================================================================
+ OpenLayers/Control/Graticule.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Graticule
+ * The Graticule displays a grid of latitude/longitude lines reprojected on
+ * the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ *
+ */
+OpenLayers.Control.Graticule = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: intervals
+ * {Array(Float)} A list of possible graticule widths in degrees.
+ */
+ intervals: [ 45, 30, 20, 10, 5, 2, 1,
+ 0.5, 0.2, 0.1, 0.05, 0.01,
+ 0.005, 0.002, 0.001 ],
+
+ /**
+ * APIProperty: displayInLayerSwitcher
+ * {Boolean} Allows the Graticule control to be switched on and off by
+ * LayerSwitcher control. Defaults is true.
+ */
+ displayInLayerSwitcher: true,
+
+ /**
+ * APIProperty: visible
+ * {Boolean} should the graticule be initially visible (default=true)
+ */
+ visible: true,
+
+ /**
+ * APIProperty: numPoints
+ * {Integer} The number of points to use in each graticule line. Higher
+ * numbers result in a smoother curve for projected maps
+ */
+ numPoints: 50,
+
+ /**
+ * APIProperty: targetSize
+ * {Integer} The maximum size of the grid in pixels on the map
+ */
+ targetSize: 200,
+
+ /**
+ * APIProperty: layerName
+ * {String} The name to be displayed in the layer switcher, default is set
+ * by {<OpenLayers.Lang>}.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: labelled
+ * {Boolean} Should the graticule lines be labelled?. default=true
+ */
+ labelled: true,
+
+ /**
+ * APIProperty: labelFormat
+ * {String} the format of the labels, default = 'dm'. See
+ * <OpenLayers.Util.getFormattedLonLat> for other options.
+ */
+ labelFormat: 'dm',
+
+ /**
+ * APIProperty: lineSymbolizer
+ * {symbolizer} the symbolizer used to render lines
+ */
+ lineSymbolizer: {
+ strokeColor: "#333",
+ strokeWidth: 1,
+ strokeOpacity: 0.5
+ },
+
+ /**
+ * APIProperty: labelSymbolizer
+ * {symbolizer} the symbolizer used to render labels
+ */
+ labelSymbolizer: {},
+
+ /**
+ * Property: gratLayer
+ * {<OpenLayers.Layer.Vector>} vector layer used to draw the graticule on
+ */
+ gratLayer: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Graticule
+ * Create a new graticule control to display a grid of latitude longitude
+ * lines.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.layerName = options.layerName || OpenLayers.i18n("Graticule");
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.labelSymbolizer.stroke = false;
+ this.labelSymbolizer.fill = false;
+ this.labelSymbolizer.label = "${label}";
+ this.labelSymbolizer.labelAlign = "${labelAlign}";
+ this.labelSymbolizer.labelXOffset = "${xOffset}";
+ this.labelSymbolizer.labelYOffset = "${yOffset}";
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if (this.gratLayer) {
+ this.gratLayer.destroy();
+ this.gratLayer = null;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * initializes the graticule layer and does the initial update
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (!this.gratLayer) {
+ var gratStyle = new OpenLayers.Style({},{
+ rules: [new OpenLayers.Rule({'symbolizer':
+ {"Point":this.labelSymbolizer,
+ "Line":this.lineSymbolizer}
+ })]
+ });
+ this.gratLayer = new OpenLayers.Layer.Vector(this.layerName, {
+ styleMap: new OpenLayers.StyleMap({'default':gratStyle}),
+ visibility: this.visible,
+ displayInLayerSwitcher: this.displayInLayerSwitcher
+ });
+ }
+ return this.div;
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ this.map.addLayer(this.gratLayer);
+ this.map.events.register('moveend', this, this.update);
+ this.update();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.map.events.unregister('moveend', this, this.update);
+ this.map.removeLayer(this.gratLayer);
+ return true;
+ } else {
+ return false;
+ }
+ },
+ /**
+ * Method: update
+ *
+ * calculates the grid to be displayed and actually draws it
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ update: function() {
+ //wait for the map to be initialized before proceeding
+ var mapBounds = this.map.getExtent();
+ if (!mapBounds) {
+ return;
+ }
+
+ //clear out the old grid
+ this.gratLayer.destroyFeatures();
+
+ //get the projection objects required
+ var llProj = new OpenLayers.Projection("EPSG:4326");
+ var mapProj = this.map.getProjectionObject();
+ var mapRes = this.map.getResolution();
+
+ //if the map is in lon/lat, then the lines are straight and only one
+ //point is required
+ if (mapProj.proj && mapProj.proj.projName == "longlat") {
+ this.numPoints = 1;
+ }
+
+ //get the map center in EPSG:4326
+ var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y
+ var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat);
+ OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj);
+
+ /* This block of code determines the lon/lat interval to use for the
+ * grid by calculating the diagonal size of one grid cell at the map
+ * center. Iterates through the intervals array until the diagonal
+ * length is less than the targetSize option.
+ */
+ //find lat/lon interval that results in a grid of less than the target size
+ var testSq = this.targetSize*mapRes;
+ testSq *= testSq; //compare squares rather than doing a square root to save time
+ var llInterval;
+ for (var i=0; i<this.intervals.length; ++i) {
+ llInterval = this.intervals[i]; //could do this for both x and y??
+ var delta = llInterval/2;
+ var p1 = mapCenterLL.offset({x: -delta, y: -delta}); //test coords in EPSG:4326 space
+ var p2 = mapCenterLL.offset({x: delta, y: delta});
+ OpenLayers.Projection.transform(p1, llProj, mapProj); // convert them back to map projection
+ OpenLayers.Projection.transform(p2, llProj, mapProj);
+ var distSq = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y);
+ if (distSq <= testSq) {
+ break;
+ }
+ }
+ //alert(llInterval);
+
+ //round the LL center to an even number based on the interval
+ mapCenterLL.x = Math.floor(mapCenterLL.x/llInterval)*llInterval;
+ mapCenterLL.y = Math.floor(mapCenterLL.y/llInterval)*llInterval;
+ //TODO adjust for minutses/seconds?
+
+ /* The following 2 blocks calculate the nodes of the grid along a
+ * line of constant longitude (then latitiude) running through the
+ * center of the map until it reaches the map edge. The calculation
+ * goes from the center in both directions to the edge.
+ */
+ //get the central longitude line, increment the latitude
+ var iter = 0;
+ var centerLonPoints = [mapCenterLL.clone()];
+ var newPoint = mapCenterLL.clone();
+ var mapXY;
+ do {
+ newPoint = newPoint.offset({x: 0, y: llInterval});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLonPoints.unshift(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+ newPoint = mapCenterLL.clone();
+ do {
+ newPoint = newPoint.offset({x: 0, y: -llInterval});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLonPoints.push(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+
+ //get the central latitude line, increment the longitude
+ iter = 0;
+ var centerLatPoints = [mapCenterLL.clone()];
+ newPoint = mapCenterLL.clone();
+ do {
+ newPoint = newPoint.offset({x: -llInterval, y: 0});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLatPoints.unshift(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+ newPoint = mapCenterLL.clone();
+ do {
+ newPoint = newPoint.offset({x: llInterval, y: 0});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLatPoints.push(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+
+ //now generate a line for each node in the central lat and lon lines
+ //first loop over constant longitude
+ var lines = [];
+ for(var i=0; i < centerLatPoints.length; ++i) {
+ var lon = centerLatPoints[i].x;
+ var pointList = [];
+ var labelPoint = null;
+ var latEnd = Math.min(centerLonPoints[0].y, 90);
+ var latStart = Math.max(centerLonPoints[centerLonPoints.length - 1].y, -90);
+ var latDelta = (latEnd - latStart)/this.numPoints;
+ var lat = latStart;
+ for(var j=0; j<= this.numPoints; ++j) {
+ var gridPoint = new OpenLayers.Geometry.Point(lon,lat);
+ gridPoint.transform(llProj, mapProj);
+ pointList.push(gridPoint);
+ lat += latDelta;
+ if (gridPoint.y >= mapBounds.bottom && !labelPoint) {
+ labelPoint = gridPoint;
+ }
+ }
+ if (this.labelled) {
+ //keep track of when this grid line crosses the map bounds to set
+ //the label position
+ //labels along the bottom, add 10 pixel offset up into the map
+ //TODO add option for labels on top
+ var labelPos = new OpenLayers.Geometry.Point(labelPoint.x,mapBounds.bottom);
+ var labelAttrs = {
+ value: lon,
+ label: this.labelled?OpenLayers.Util.getFormattedLonLat(lon, "lon", this.labelFormat):"",
+ labelAlign: "cb",
+ xOffset: 0,
+ yOffset: 2
+ };
+ this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));
+ }
+ var geom = new OpenLayers.Geometry.LineString(pointList);
+ lines.push(new OpenLayers.Feature.Vector(geom));
+ }
+
+ //now draw the lines of constant latitude
+ for (var j=0; j < centerLonPoints.length; ++j) {
+ lat = centerLonPoints[j].y;
+ if (lat<-90 || lat>90) { //latitudes only valid between -90 and 90
+ continue;
+ }
+ var pointList = [];
+ var lonStart = centerLatPoints[0].x;
+ var lonEnd = centerLatPoints[centerLatPoints.length - 1].x;
+ var lonDelta = (lonEnd - lonStart)/this.numPoints;
+ var lon = lonStart;
+ var labelPoint = null;
+ for(var i=0; i <= this.numPoints ; ++i) {
+ var gridPoint = new OpenLayers.Geometry.Point(lon,lat);
+ gridPoint.transform(llProj, mapProj);
+ pointList.push(gridPoint);
+ lon += lonDelta;
+ if (gridPoint.x < mapBounds.right) {
+ labelPoint = gridPoint;
+ }
+ }
+ if (this.labelled) {
+ //keep track of when this grid line crosses the map bounds to set
+ //the label position
+ //labels along the right, 30 pixel offset left into the map
+ //TODO add option for labels on left
+ var labelPos = new OpenLayers.Geometry.Point(mapBounds.right, labelPoint.y);
+ var labelAttrs = {
+ value: lat,
+ label: this.labelled?OpenLayers.Util.getFormattedLonLat(lat, "lat", this.labelFormat):"",
+ labelAlign: "rb",
+ xOffset: -2,
+ yOffset: 2
+ };
+ this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));
+ }
+ var geom = new OpenLayers.Geometry.LineString(pointList);
+ lines.push(new OpenLayers.Feature.Vector(geom));
+ }
+ this.gratLayer.addFeatures(lines);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Graticule"
+});
+
+/* ======================================================================
+ Rico/Corner.js
+ ====================================================================== */
+
+/**
+ * @requires OpenLayers/Console.js
+ * @requires Rico/Color.js
+ */
+
+
+/*
+ * This file has been edited substantially from the Rico-released
+ * version by the OpenLayers development team.
+ *
+ * Copyright 2005 Sabre Airline Solutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the * License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or
+ * implied. See the License for the specific language governing
+ * permissions * and limitations under the License.
+ *
+ */
+
+OpenLayers.Console.warn("OpenLayers.Rico is deprecated");
+
+OpenLayers.Rico = OpenLayers.Rico || {};
+OpenLayers.Rico.Corner = {
+
+ round: function(e, options) {
+ e = OpenLayers.Util.getElement(e);
+ this._setOptions(options);
+
+ var color = this.options.color;
+ if ( this.options.color == "fromElement" ) {
+ color = this._background(e);
+ }
+ var bgColor = this.options.bgColor;
+ if ( this.options.bgColor == "fromParent" ) {
+ bgColor = this._background(e.offsetParent);
+ }
+ this._roundCornersImpl(e, color, bgColor);
+ },
+
+ /** This is a helper function to change the background
+ * color of <div> that has had Rico rounded corners added.
+ *
+ * It seems we cannot just set the background color for the
+ * outer <div> so each <span> element used to create the
+ * corners must have its background color set individually.
+ *
+ * @param {DOM} theDiv - A child of the outer <div> that was
+ * supplied to the `round` method.
+ *
+ * @param {String} newColor - The new background color to use.
+ */
+ changeColor: function(theDiv, newColor) {
+
+ theDiv.style.backgroundColor = newColor;
+
+ var spanElements = theDiv.parentNode.getElementsByTagName("span");
+
+ for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+ spanElements[currIdx].style.backgroundColor = newColor;
+ }
+ },
+
+
+ /** This is a helper function to change the background
+ * opacity of <div> that has had Rico rounded corners added.
+ *
+ * See changeColor (above) for algorithm explanation
+ *
+ * @param {DOM} theDiv A child of the outer <div> that was
+ * supplied to the `round` method.
+ *
+ * @param {int} newOpacity The new opacity to use (0-1).
+ */
+ changeOpacity: function(theDiv, newOpacity) {
+
+ var mozillaOpacity = newOpacity;
+ var ieOpacity = 'alpha(opacity=' + newOpacity * 100 + ')';
+
+ theDiv.style.opacity = mozillaOpacity;
+ theDiv.style.filter = ieOpacity;
+
+ var spanElements = theDiv.parentNode.getElementsByTagName("span");
+
+ for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+ spanElements[currIdx].style.opacity = mozillaOpacity;
+ spanElements[currIdx].style.filter = ieOpacity;
+ }
+
+ },
+
+ /** this function takes care of redoing the rico cornering
+ *
+ * you can't just call updateRicoCorners() again and pass it a
+ * new options string. you have to first remove the divs that
+ * rico puts on top and below the content div.
+ *
+ * @param {DOM} theDiv - A child of the outer <div> that was
+ * supplied to the `round` method.
+ *
+ * @param {Object} options - list of options
+ */
+ reRound: function(theDiv, options) {
+
+ var topRico = theDiv.parentNode.childNodes[0];
+ //theDiv would be theDiv.parentNode.childNodes[1]
+ var bottomRico = theDiv.parentNode.childNodes[2];
+
+ theDiv.parentNode.removeChild(topRico);
+ theDiv.parentNode.removeChild(bottomRico);
+
+ this.round(theDiv.parentNode, options);
+ },
+
+ _roundCornersImpl: function(e, color, bgColor) {
+ if(this.options.border) {
+ this._renderBorder(e,bgColor);
+ }
+ if(this._isTopRounded()) {
+ this._roundTopCorners(e,color,bgColor);
+ }
+ if(this._isBottomRounded()) {
+ this._roundBottomCorners(e,color,bgColor);
+ }
+ },
+
+ _renderBorder: function(el,bgColor) {
+ var borderValue = "1px solid " + this._borderColor(bgColor);
+ var borderL = "border-left: " + borderValue;
+ var borderR = "border-right: " + borderValue;
+ var style = "style='" + borderL + ";" + borderR + "'";
+ el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>";
+ },
+
+ _roundTopCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=0 ; i < this.options.numSlices ; i++ ) {
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+ }
+ el.style.paddingTop = 0;
+ el.insertBefore(corner,el.firstChild);
+ },
+
+ _roundBottomCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- ) {
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+ }
+ el.style.paddingBottom = 0;
+ el.appendChild(corner);
+ },
+
+ _createCorner: function(bgColor) {
+ var corner = document.createElement("div");
+ corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+ return corner;
+ },
+
+ _createCornerSlice: function(color,bgColor, n, position) {
+ var slice = document.createElement("span");
+
+ var inStyle = slice.style;
+ inStyle.backgroundColor = color;
+ inStyle.display = "block";
+ inStyle.height = "1px";
+ inStyle.overflow = "hidden";
+ inStyle.fontSize = "1px";
+
+ var borderColor = this._borderColor(color,bgColor);
+ if ( this.options.border && n == 0 ) {
+ inStyle.borderTopStyle = "solid";
+ inStyle.borderTopWidth = "1px";
+ inStyle.borderLeftWidth = "0px";
+ inStyle.borderRightWidth = "0px";
+ inStyle.borderBottomWidth = "0px";
+ inStyle.height = "0px"; // assumes css compliant box model
+ inStyle.borderColor = borderColor;
+ }
+ else if(borderColor) {
+ inStyle.borderColor = borderColor;
+ inStyle.borderStyle = "solid";
+ inStyle.borderWidth = "0px 1px";
+ }
+
+ if ( !this.options.compact && (n == (this.options.numSlices-1)) ) {
+ inStyle.height = "2px";
+ }
+ this._setMargin(slice, n, position);
+ this._setBorder(slice, n, position);
+ return slice;
+ },
+
+ _setOptions: function(options) {
+ this.options = {
+ corners : "all",
+ color : "fromElement",
+ bgColor : "fromParent",
+ blend : true,
+ border : false,
+ compact : false
+ };
+ OpenLayers.Util.extend(this.options, options || {});
+
+ this.options.numSlices = this.options.compact ? 2 : 4;
+ if ( this._isTransparent() ) {
+ this.options.blend = false;
+ }
+ },
+
+ _whichSideTop: function() {
+ if ( this._hasString(this.options.corners, "all", "top") ) {
+ return "";
+ }
+ if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 ) {
+ return "";
+ }
+ if (this.options.corners.indexOf("tl") >= 0) {
+ return "left";
+ } else if (this.options.corners.indexOf("tr") >= 0) {
+ return "right";
+ }
+ return "";
+ },
+
+ _whichSideBottom: function() {
+ if ( this._hasString(this.options.corners, "all", "bottom") ) {
+ return "";
+ }
+ if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 ) {
+ return "";
+ }
+
+ if(this.options.corners.indexOf("bl") >=0) {
+ return "left";
+ } else if(this.options.corners.indexOf("br")>=0) {
+ return "right";
+ }
+ return "";
+ },
+
+ _borderColor : function(color,bgColor) {
+ if ( color == "transparent" ) {
+ return bgColor;
+ } else if ( this.options.border ) {
+ return this.options.border;
+ } else if ( this.options.blend ) {
+ return this._blend( bgColor, color );
+ } else {
+ return "";
+ }
+ },
+
+
+ _setMargin: function(el, n, corners) {
+ var marginSize = this._marginSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+ if ( whichSide == "left" ) {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px";
+ }
+ else {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+ }
+ },
+
+ _setBorder: function(el,n,corners) {
+ var borderSize = this._borderSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+ if ( whichSide == "left" ) {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px";
+ }
+ else {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ }
+ if (this.options.border != false) {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ }
+ },
+
+ _marginSize: function(n) {
+ if ( this._isTransparent() ) {
+ return 0;
+ }
+ var marginSizes = [ 5, 3, 2, 1 ];
+ var blendedMarginSizes = [ 3, 2, 1, 0 ];
+ var compactMarginSizes = [ 2, 1 ];
+ var smBlendedMarginSizes = [ 1, 0 ];
+
+ if ( this.options.compact && this.options.blend ) {
+ return smBlendedMarginSizes[n];
+ } else if ( this.options.compact ) {
+ return compactMarginSizes[n];
+ } else if ( this.options.blend ) {
+ return blendedMarginSizes[n];
+ } else {
+ return marginSizes[n];
+ }
+ },
+
+ _borderSize: function(n) {
+ var transparentBorderSizes = [ 5, 3, 2, 1 ];
+ var blendedBorderSizes = [ 2, 1, 1, 1 ];
+ var compactBorderSizes = [ 1, 0 ];
+ var actualBorderSizes = [ 0, 2, 0, 0 ];
+
+ if ( this.options.compact && (this.options.blend || this._isTransparent()) ) {
+ return 1;
+ } else if ( this.options.compact ) {
+ return compactBorderSizes[n];
+ } else if ( this.options.blend ) {
+ return blendedBorderSizes[n];
+ } else if ( this.options.border ) {
+ return actualBorderSizes[n];
+ } else if ( this._isTransparent() ) {
+ return transparentBorderSizes[n];
+ }
+ return 0;
+ },
+
+ _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) { return true; } return false; },
+ _blend: function(c1, c2) { var cc1 = OpenLayers.Rico.Color.createFromHex(c1); cc1.blend(OpenLayers.Rico.Color.createFromHex(c2)); return cc1; },
+ _background: function(el) { try { return OpenLayers.Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+ _isTransparent: function() { return this.options.color == "transparent"; },
+ _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+ _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+ _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
+};
+/* ======================================================================
+ OpenLayers/Control/NavigationHistory.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavigationHistory
+ * A navigation history control. This is a meta-control, that creates two
+ * dependent controls: <previous> and <next>. Call the trigger method
+ * on the <previous> and <next> controls to restore previous and next
+ * history states. The previous and next controls will become active
+ * when there are available states to restore and will become deactive
+ * when there are no states to restore.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.NavigationHistory = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {String} Note that this control is not intended to be added directly
+ * to a control panel. Instead, add the sub-controls previous and
+ * next. These sub-controls are button type controls that activate
+ * and deactivate themselves. If this parent control is added to
+ * a panel, it will act as a toggle.
+ */
+ type: OpenLayers.Control.TYPE_TOGGLE,
+
+ /**
+ * APIProperty: previous
+ * {<OpenLayers.Control>} A button type control whose trigger method restores
+ * the previous state managed by this control.
+ */
+ previous: null,
+
+ /**
+ * APIProperty: previousOptions
+ * {Object} Set this property on the options argument of the constructor
+ * to set optional properties on the <previous> control.
+ */
+ previousOptions: null,
+
+ /**
+ * APIProperty: next
+ * {<OpenLayers.Control>} A button type control whose trigger method restores
+ * the next state managed by this control.
+ */
+ next: null,
+
+ /**
+ * APIProperty: nextOptions
+ * {Object} Set this property on the options argument of the constructor
+ * to set optional properties on the <next> control.
+ */
+ nextOptions: null,
+
+ /**
+ * APIProperty: limit
+ * {Integer} Optional limit on the number of history items to retain. If
+ * null, there is no limit. Default is 50.
+ */
+ limit: 50,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: clearOnDeactivate
+ * {Boolean} Clear the history when the control is deactivated. Default
+ * is false.
+ */
+ clearOnDeactivate: false,
+
+ /**
+ * Property: registry
+ * {Object} An object with keys corresponding to event types. Values
+ * are functions that return an object representing the current state.
+ */
+ registry: null,
+
+ /**
+ * Property: nextStack
+ * {Array} Array of items in the history.
+ */
+ nextStack: null,
+
+ /**
+ * Property: previousStack
+ * {Array} List of items in the history. First item represents the current
+ * state.
+ */
+ previousStack: null,
+
+ /**
+ * Property: listeners
+ * {Object} An object containing properties corresponding to event types.
+ * This object is used to configure the control and is modified on
+ * construction.
+ */
+ listeners: null,
+
+ /**
+ * Property: restoring
+ * {Boolean} Currently restoring a history state. This is set to true
+ * before calling restore and set to false after restore returns.
+ */
+ restoring: false,
+
+ /**
+ * Constructor: OpenLayers.Control.NavigationHistory
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.registry = OpenLayers.Util.extend({
+ "moveend": this.getState
+ }, this.registry);
+
+ var previousOptions = {
+ trigger: OpenLayers.Function.bind(this.previousTrigger, this),
+ displayClass: this.displayClass + " " + this.displayClass + "Previous"
+ };
+ OpenLayers.Util.extend(previousOptions, this.previousOptions);
+ this.previous = new OpenLayers.Control.Button(previousOptions);
+
+ var nextOptions = {
+ trigger: OpenLayers.Function.bind(this.nextTrigger, this),
+ displayClass: this.displayClass + " " + this.displayClass + "Next"
+ };
+ OpenLayers.Util.extend(nextOptions, this.nextOptions);
+ this.next = new OpenLayers.Control.Button(nextOptions);
+
+ this.clear();
+ },
+
+ /**
+ * Method: onPreviousChange
+ * Called when the previous history stack changes.
+ *
+ * Parameters:
+ * state - {Object} An object representing the state to be restored
+ * if previous is triggered again or null if no previous states remain.
+ * length - {Integer} The number of remaining previous states that can
+ * be restored.
+ */
+ onPreviousChange: function(state, length) {
+ if(state && !this.previous.active) {
+ this.previous.activate();
+ } else if(!state && this.previous.active) {
+ this.previous.deactivate();
+ }
+ },
+
+ /**
+ * Method: onNextChange
+ * Called when the next history stack changes.
+ *
+ * Parameters:
+ * state - {Object} An object representing the state to be restored
+ * if next is triggered again or null if no next states remain.
+ * length - {Integer} The number of remaining next states that can
+ * be restored.
+ */
+ onNextChange: function(state, length) {
+ if(state && !this.next.active) {
+ this.next.activate();
+ } else if(!state && this.next.active) {
+ this.next.deactivate();
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy the control.
+ */
+ destroy: function() {
+ OpenLayers.Control.prototype.destroy.apply(this);
+ this.previous.destroy();
+ this.next.destroy();
+ this.deactivate();
+ for(var prop in this) {
+ this[prop] = null;
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and <previous> and <next> child
+ * controls.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.map = map;
+ this.next.setMap(map);
+ this.previous.setMap(map);
+ },
+
+ /**
+ * Method: draw
+ * Called when the control is added to the map.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ this.next.draw();
+ this.previous.draw();
+ },
+
+ /**
+ * Method: previousTrigger
+ * Restore the previous state. If no items are in the previous history
+ * stack, this has no effect.
+ *
+ * Returns:
+ * {Object} Item representing state that was restored. Undefined if no
+ * items are in the previous history stack.
+ */
+ previousTrigger: function() {
+ var current = this.previousStack.shift();
+ var state = this.previousStack.shift();
+ if(state != undefined) {
+ this.nextStack.unshift(current);
+ this.previousStack.unshift(state);
+ this.restoring = true;
+ this.restore(state);
+ this.restoring = false;
+ this.onNextChange(this.nextStack[0], this.nextStack.length);
+ this.onPreviousChange(
+ this.previousStack[1], this.previousStack.length - 1
+ );
+ } else {
+ this.previousStack.unshift(current);
+ }
+ return state;
+ },
+
+ /**
+ * APIMethod: nextTrigger
+ * Restore the next state. If no items are in the next history
+ * stack, this has no effect. The next history stack is populated
+ * as states are restored from the previous history stack.
+ *
+ * Returns:
+ * {Object} Item representing state that was restored. Undefined if no
+ * items are in the next history stack.
+ */
+ nextTrigger: function() {
+ var state = this.nextStack.shift();
+ if(state != undefined) {
+ this.previousStack.unshift(state);
+ this.restoring = true;
+ this.restore(state);
+ this.restoring = false;
+ this.onNextChange(this.nextStack[0], this.nextStack.length);
+ this.onPreviousChange(
+ this.previousStack[1], this.previousStack.length - 1
+ );
+ }
+ return state;
+ },
+
+ /**
+ * APIMethod: clear
+ * Clear history.
+ */
+ clear: function() {
+ this.previousStack = [];
+ this.previous.deactivate();
+ this.nextStack = [];
+ this.next.deactivate();
+ },
+
+ /**
+ * Method: getState
+ * Get the current state and return it.
+ *
+ * Returns:
+ * {Object} An object representing the current state.
+ */
+ getState: function() {
+ return {
+ center: this.map.getCenter(),
+ resolution: this.map.getResolution(),
+ projection: this.map.getProjectionObject(),
+ units: this.map.getProjectionObject().getUnits() ||
+ this.map.units || this.map.baseLayer.units
+ };
+ },
+
+ /**
+ * Method: restore
+ * Update the state with the given object.
+ *
+ * Parameters:
+ * state - {Object} An object representing the state to restore.
+ */
+ restore: function(state) {
+ var center, zoom;
+ if (this.map.getProjectionObject() == state.projection) {
+ zoom = this.map.getZoomForResolution(state.resolution);
+ center = state.center;
+ } else {
+ center = state.center.clone();
+ center.transform(state.projection, this.map.getProjectionObject());
+ var sourceUnits = state.units;
+ var targetUnits = this.map.getProjectionObject().getUnits() ||
+ this.map.units || this.map.baseLayer.units;
+ var resolutionFactor = sourceUnits && targetUnits ?
+ OpenLayers.INCHES_PER_UNIT[sourceUnits] / OpenLayers.INCHES_PER_UNIT[targetUnits] : 1;
+ zoom = this.map.getZoomForResolution(resolutionFactor*state.resolution);
+ }
+ this.map.setCenter(center, zoom);
+ },
+
+ /**
+ * Method: setListeners
+ * Sets functions to be registered in the listeners object.
+ */
+ setListeners: function() {
+ this.listeners = {};
+ for(var type in this.registry) {
+ this.listeners[type] = OpenLayers.Function.bind(function() {
+ if(!this.restoring) {
+ var state = this.registry[type].apply(this, arguments);
+ this.previousStack.unshift(state);
+ if(this.previousStack.length > 1) {
+ this.onPreviousChange(
+ this.previousStack[1], this.previousStack.length - 1
+ );
+ }
+ if(this.previousStack.length > (this.limit + 1)) {
+ this.previousStack.pop();
+ }
+ if(this.nextStack.length > 0) {
+ this.nextStack = [];
+ this.onNextChange(null, 0);
+ }
+ }
+ return true;
+ }, this);
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. This registers any listeners.
+ *
+ * Returns:
+ * {Boolean} Control successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(this.map) {
+ if(OpenLayers.Control.prototype.activate.apply(this)) {
+ if(this.listeners == null) {
+ this.setListeners();
+ }
+ for(var type in this.listeners) {
+ this.map.events.register(type, this, this.listeners[type]);
+ }
+ activated = true;
+ if(this.previousStack.length == 0) {
+ this.initStack();
+ }
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: initStack
+ * Called after the control is activated if the previous history stack is
+ * empty.
+ */
+ initStack: function() {
+ if(this.map.getCenter()) {
+ this.listeners.moveend();
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. This unregisters any listeners.
+ *
+ * Returns:
+ * {Boolean} Control successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(this.map) {
+ if(OpenLayers.Control.prototype.deactivate.apply(this)) {
+ for(var type in this.listeners) {
+ this.map.events.unregister(
+ type, this, this.listeners[type]
+ );
+ }
+ if(this.clearOnDeactivate) {
+ this.clear();
+ }
+ deactivated = true;
+ }
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.NavigationHistory"
+});
+
+/* ======================================================================
+ OpenLayers/Layer/UTFGrid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ * @requires OpenLayers/Tile/UTFGrid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.UTFGrid
+ * This Layer reads from UTFGrid tiled data sources. Since UTFGrids are
+ * essentially JSON-based ASCII art with attached attributes, they are not
+ * visibly rendered. In order to use them in the map, you must add a
+ * <OpenLayers.Control.UTFGrid> control as well.
+ *
+ * Example:
+ *
+ * (start code)
+ * var world_utfgrid = new OpenLayers.Layer.UTFGrid({
+ * url: "/tiles/world_utfgrid/${z}/${x}/${y}.json",
+ * utfgridResolution: 4,
+ * displayInLayerSwitcher: false
+ * );
+ * map.addLayer(world_utfgrid);
+ *
+ * var control = new OpenLayers.Control.UTFGrid({
+ * layers: [world_utfgrid],
+ * handlerMode: 'move',
+ * callback: function(dataLookup) {
+ * // do something with returned data
+ * }
+ * })
+ * (end code)
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.UTFGrid = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * Default is false, as UTFGrids are designed to be a transparent overlay layer.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: projection
+ * {<OpenLayers.Projection>}
+ * Source projection for the UTFGrids. Default is "EPSG:900913".
+ */
+ projection: new OpenLayers.Projection("EPSG:900913"),
+
+ /**
+ * Property: useJSONP
+ * {Boolean}
+ * Should we use a JSONP script approach instead of a standard AJAX call?
+ *
+ * Set to true for using utfgrids from another server.
+ * Avoids same-domain policy restrictions.
+ * Note that this only works if the server accepts
+ * the callback GET parameter and dynamically
+ * wraps the returned json in a function call.
+ *
+ * Default is false
+ */
+ useJSONP: false,
+
+ /**
+ * APIProperty: url
+ * {String}
+ * URL tempate for UTFGrid tiles. Include x, y, and z parameters.
+ * E.g. "/tiles/${z}/${x}/${y}.json"
+ */
+
+ /**
+ * APIProperty: utfgridResolution
+ * {Number}
+ * Ratio of the pixel width to the width of a UTFGrid data point. If an
+ * entry in the grid represents a 4x4 block of pixels, the
+ * utfgridResolution would be 4. Default is 2 (specified in
+ * <OpenLayers.Tile.UTFGrid>).
+ */
+
+ /**
+ * Property: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is <OpenLayers.Tile.UTFGrid>.
+ */
+ tileClass: OpenLayers.Tile.UTFGrid,
+
+ /**
+ * Constructor: OpenLayers.Layer.UTFGrid
+ * Create a new UTFGrid layer.
+ *
+ * Parameters:
+ * config - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * url - {String} The url template for UTFGrid tiles. See the <url> property.
+ */
+ initialize: function(options) {
+ OpenLayers.Layer.Grid.prototype.initialize.apply(
+ this, [options.name, options.url, {}, options]
+ );
+ this.tileOptions = OpenLayers.Util.extend({
+ utfgridResolution: this.utfgridResolution
+ }, this.tileOptions);
+ },
+
+ /**
+ * Method: createBackBuffer
+ * The UTFGrid cannot create a back buffer, so this method is overriden.
+ */
+ createBackBuffer: function() {},
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Only used by a subclass of this layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.UTFGrid>} An exact clone of this OpenLayers.Layer.UTFGrid
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.UTFGrid(this.getOptions());
+ }
+
+ // get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * APIProperty: getFeatureInfo
+ * Get details about a feature associated with a map location. The object
+ * returned will have id and data properties. If the given location
+ * doesn't correspond to a feature, null will be returned.
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object representing the feature id and UTFGrid data
+ * corresponding to the given map location. Returns null if the given
+ * location doesn't hit a feature.
+ */
+ getFeatureInfo: function(location) {
+ var info = null;
+ var tileInfo = this.getTileData(location);
+ if (tileInfo && tileInfo.tile) {
+ info = tileInfo.tile.getFeatureInfo(tileInfo.i, tileInfo.j);
+ }
+ return info;
+ },
+
+ /**
+ * APIMethod: getFeatureId
+ * Get the identifier for the feature associated with a map location.
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {String} The feature identifier corresponding to the given map location.
+ * Returns null if the location doesn't hit a feature.
+ */
+ getFeatureId: function(location) {
+ var id = null;
+ var info = this.getTileData(location);
+ if (info.tile) {
+ id = info.tile.getFeatureId(info.i, info.j);
+ }
+ return id;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.UTFGrid"
+});
+/* ======================================================================
+ OpenLayers/TileManager.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.TileManager
+ * Provides queueing of image requests and caching of image elements.
+ *
+ * Queueing avoids unnecessary image requests while changing zoom levels
+ * quickly, and helps improve dragging performance on mobile devices that show
+ * a lag in dragging when loading of new images starts. <zoomDelay> and
+ * <moveDelay> are the configuration options to control this behavior.
+ *
+ * Caching avoids setting the src on image elements for images that have already
+ * been used. Several maps can share a TileManager instance, in which case each
+ * map gets its own tile queue, but all maps share the same tile cache.
+ */
+OpenLayers.TileManager = OpenLayers.Class({
+
+ /**
+ * APIProperty: cacheSize
+ * {Number} Number of image elements to keep referenced in this instance's
+ * cache for fast reuse. Default is 256.
+ */
+ cacheSize: 256,
+
+ /**
+ * APIProperty: tilesPerFrame
+ * {Number} Number of queued tiles to load per frame (see <frameDelay>).
+ * Default is 2.
+ */
+ tilesPerFrame: 2,
+
+ /**
+ * APIProperty: frameDelay
+ * {Number} Delay between tile loading frames (see <tilesPerFrame>) in
+ * milliseconds. Default is 16.
+ */
+ frameDelay: 16,
+
+ /**
+ * APIProperty: moveDelay
+ * {Number} Delay in milliseconds after a map's move event before loading
+ * tiles. Default is 100.
+ */
+ moveDelay: 100,
+
+ /**
+ * APIProperty: zoomDelay
+ * {Number} Delay in milliseconds after a map's zoomend event before loading
+ * tiles. Default is 200.
+ */
+ zoomDelay: 200,
+
+ /**
+ * Property: maps
+ * {Array(<OpenLayers.Map>)} The maps to manage tiles on.
+ */
+ maps: null,
+
+ /**
+ * Property: tileQueueId
+ * {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
+ */
+ tileQueueId: null,
+
+ /**
+ * Property: tileQueue
+ * {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
+ * map id.
+ */
+ tileQueue: null,
+
+ /**
+ * Property: tileCache
+ * {Object} Cached image elements, keyed by URL.
+ */
+ tileCache: null,
+
+ /**
+ * Property: tileCacheIndex
+ * {Array(String)} URLs of cached tiles. First entry is the least recently
+ * used.
+ */
+ tileCacheIndex: null,
+
+ /**
+ * Constructor: OpenLayers.TileManager
+ * Constructor for a new <OpenLayers.TileManager> instance.
+ *
+ * Parameters:
+ * options - {Object} Configuration for this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.maps = [];
+ this.tileQueueId = {};
+ this.tileQueue = {};
+ this.tileCache = {};
+ this.tileCacheIndex = [];
+ },
+
+ /**
+ * Method: addMap
+ * Binds this instance to a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ addMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ this.maps.push(map);
+ this.tileQueue[map.id] = [];
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.addLayer({layer: map.layers[i]});
+ }
+ map.events.on({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeMap
+ * Unbinds this instance from a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ window.clearTimeout(this.tileQueueId[map.id]);
+ if (map.layers) {
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.removeLayer({layer: map.layers[i]});
+ }
+ }
+ if (map.events) {
+ map.events.un({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ }
+ delete this.tileQueue[map.id];
+ delete this.tileQueueId[map.id];
+ OpenLayers.Util.removeItem(this.maps, map);
+ },
+
+ /**
+ * Method: move
+ * Handles the map's move event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ move: function(evt) {
+ this.updateTimeout(evt.object, this.moveDelay, true);
+ },
+
+ /**
+ * Method: zoomEnd
+ * Handles the map's zoomEnd event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ zoomEnd: function(evt) {
+ this.updateTimeout(evt.object, this.zoomDelay);
+ },
+
+ /**
+ * Method: changeLayer
+ * Handles the map's changeLayer event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ changeLayer: function(evt) {
+ if (evt.property === 'visibility' || evt.property === 'params') {
+ this.updateTimeout(evt.object, 0);
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Handles the map's addlayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ layer.events.on({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.addTile({tile: tile});
+ if (tile.url && !tile.imgDiv) {
+ this.manageTileCache({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeLayer
+ * Handles the map's preremovelayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ removeLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ this.clearTileQueue({object: layer});
+ if (layer.events) {
+ layer.events.un({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ }
+ if (layer.grid) {
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.unloadTile({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: updateTimeout
+ * Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop,
+ * and schedules more queue processing after <frameDelay> if there are still
+ * tiles in the queue.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map to update the timeout for
+ * delay - {Number} The delay to apply
+ * nice - {Boolean} If true, the timeout function will only be created if
+ * the tilequeue is not empty. This is used by the move handler to
+ * avoid impacts on dragging performance. For other events, the tile
+ * queue may not be populated yet, so we need to set the timer
+ * regardless of the queue size.
+ */
+ updateTimeout: function(map, delay, nice) {
+ window.clearTimeout(this.tileQueueId[map.id]);
+ var tileQueue = this.tileQueue[map.id];
+ if (!nice || tileQueue.length) {
+ this.tileQueueId[map.id] = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.drawTilesFromQueue(map);
+ if (tileQueue.length) {
+ this.updateTimeout(map, this.frameDelay);
+ }
+ }, this), delay
+ );
+ }
+ },
+
+ /**
+ * Method: addTile
+ * Listener for the layer's addtile event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addTile: function(evt) {
+ if (evt.tile instanceof OpenLayers.Tile.Image) {
+ evt.tile.events.on({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ } else {
+ // Layer has the wrong tile type, so don't handle it any longer
+ this.removeLayer({layer: evt.tile.layer});
+ }
+ },
+
+ /**
+ * Method: unloadTile
+ * Listener for the tile's unload event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ unloadTile: function(evt) {
+ var tile = evt.object;
+ tile.events.un({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile);
+ },
+
+ /**
+ * Method: queueTileDraw
+ * Adds a tile to the queue that will draw it.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforedraw event
+ */
+ queueTileDraw: function(evt) {
+ var tile = evt.object;
+ var queued = false;
+ var layer = tile.layer;
+ var url = layer.getURL(tile.bounds);
+ var img = this.tileCache[url];
+ if (img && img.className !== 'olTileImage') {
+ // cached image no longer valid, e.g. because we're olTileReplacing
+ delete this.tileCache[url];
+ OpenLayers.Util.removeItem(this.tileCacheIndex, url);
+ img = null;
+ }
+ // queue only if image with same url not cached already
+ if (layer.url && (layer.async || !img)) {
+ // add to queue only if not in queue already
+ var tileQueue = this.tileQueue[layer.map.id];
+ if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
+ tileQueue.push(tile);
+ }
+ queued = true;
+ }
+ return !queued;
+ },
+
+ /**
+ * Method: drawTilesFromQueue
+ * Draws tiles from the tileQueue, and unqueues the tiles
+ */
+ drawTilesFromQueue: function(map) {
+ var tileQueue = this.tileQueue[map.id];
+ var limit = this.tilesPerFrame;
+ var animating = map.zoomTween && map.zoomTween.playing;
+ while (!animating && tileQueue.length && limit) {
+ tileQueue.shift().draw(true);
+ --limit;
+ }
+ },
+
+ /**
+ * Method: manageTileCache
+ * Adds, updates, removes and fetches cache entries.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforeload event
+ */
+ manageTileCache: function(evt) {
+ var tile = evt.object;
+ var img = this.tileCache[tile.url];
+ if (img) {
+ // if image is on its layer's backbuffer, remove it from backbuffer
+ if (img.parentNode &&
+ OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer')) {
+ img.parentNode.removeChild(img);
+ img.id = null;
+ }
+ // only use image from cache if it is not on a layer already
+ if (!img.parentNode) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ tile.setImage(img);
+ // LRU - move tile to the end of the array to mark it as the most
+ // recently used
+ OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url);
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: addToCache
+ *
+ * Parameters:
+ * evt - {Object} Listener argument for the tile's loadend event
+ */
+ addToCache: function(evt) {
+ var tile = evt.object;
+ if (!this.tileCache[tile.url]) {
+ if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) {
+ if (this.tileCacheIndex.length >= this.cacheSize) {
+ delete this.tileCache[this.tileCacheIndex[0]];
+ this.tileCacheIndex.shift();
+ }
+ this.tileCache[tile.url] = tile.imgDiv;
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: clearTileQueue
+ * Clears the tile queue from tiles of a specific layer
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the layer's retile event
+ */
+ clearTileQueue: function(evt) {
+ var layer = evt.object;
+ var tileQueue = this.tileQueue[layer.map.id];
+ for (var i=tileQueue.length-1; i>=0; --i) {
+ if (tileQueue[i].layer === layer) {
+ tileQueue.splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.maps.length-1; i>=0; --i) {
+ this.removeMap(this.maps[i]);
+ }
+ this.maps = null;
+ this.tileQueue = null;
+ this.tileQueueId = null;
+ this.tileCache = null;
+ this.tileCacheIndex = null;
+ this._destroyed = true;
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Layer/ArcGISCache.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.ArcGISCache
+ * Layer for accessing cached map tiles from an ArcGIS Server style mapcache.
+ * Tile must already be cached for this layer to access it. This does not require
+ * ArcGIS Server itself.
+ *
+ * A few attempts have been made at this kind of layer before. See
+ * http://trac.osgeo.org/openlayers/ticket/1967
+ * and
+ * http://trac.osgeo.org/openlayers/browser/sandbox/tschaub/arcgiscache/lib/OpenLayers/Layer/ArcGISCache.js
+ *
+ * Typically the problem encountered is that the tiles seem to "jump around".
+ * This is due to the fact that the actual max extent for the tiles on AGS layers
+ * changes at each zoom level due to the way these caches are constructed.
+ * We have attempted to use the resolutions, tile size, and tile origin
+ * from the cache meta data to make the appropriate changes to the max extent
+ * of the tile to compensate for this behavior. This must be done as zoom levels change
+ * and before tiles are requested, which is why methods from base classes are overridden.
+ *
+ * For reference, you can access mapcache meta data in two ways. For accessing a
+ * mapcache through ArcGIS Server, you can simply go to the landing page for the
+ * layer. (ie. http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer)
+ * For accessing it directly through HTTP, there should always be a conf.xml file
+ * in the root directory.
+ * (ie. http://serverx.esri.com/arcgiscache/DG_County_roads_yesA_backgroundDark/Layers/conf.xml)
+ *
+ *Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.ArcGISCache = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: url
+ * {String | Array} The base URL for the layer cache. You can also
+ * provide a list of URL strings for the layer if your cache is
+ * available from multiple origins. This must be set before the layer
+ * is drawn.
+ */
+ url: null,
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} The location of the tile origin for the cache.
+ * An ArcGIS cache has it's origin at the upper-left (lowest x value
+ * and highest y value of the coordinate system). The units for the
+ * tile origin should be the same as the units for the cached data.
+ */
+ tileOrigin: null,
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} This size of each tile. Defaults to 256 by 256 pixels.
+ */
+ tileSize: new OpenLayers.Size(256, 256),
+
+ /**
+ * APIProperty: useAGS
+ * {Boolean} Indicates if we are going to be accessing the ArcGIS Server (AGS)
+ * cache via an AGS MapServer or directly through HTTP. When accessing via
+ * AGS the path structure uses a standard z/y/x structure. But AGS actually
+ * stores the tile images on disk using a hex based folder structure that looks
+ * like "http://example.com/mylayer/L00/R00000000/C00000000.png". Learn more
+ * about this here:
+ * http://blogs.esri.com/Support/blogs/mappingcenter/archive/2010/08/20/Checking-Your-Local-Cache-Folders.aspx
+ * Defaults to true;
+ */
+ useArcGISServer: true,
+
+ /**
+ * APIProperty: type
+ * {String} Image type for the layer. This becomes the filename extension
+ * in tile requests. Default is "png" (generating a url like
+ * "http://example.com/mylayer/L00/R00000000/C00000000.png").
+ */
+ type: 'png',
+
+ /**
+ * APIProperty: useScales
+ * {Boolean} Optional override to indicate that the layer should use 'scale' information
+ * returned from the server capabilities object instead of 'resolution' information.
+ * This can be important if your tile server uses an unusual DPI for the tiles.
+ */
+ useScales: false,
+
+ /**
+ * APIProperty: overrideDPI
+ * {Boolean} Optional override to change the OpenLayers.DOTS_PER_INCH setting based
+ * on the tile information in the server capabilities object. This can be useful
+ * if your server has a non-standard DPI setting on its tiles, and you're only using
+ * tiles with that DPI. This value is used while OpenLayers is calculating resolution
+ * using scales, and is not necessary if you have resolution information. (This is
+ * typically the case) Regardless, this setting can be useful, but is dangerous
+ * because it will impact other layers while calculating resolution. Only use this
+ * if you know what you are doing. (See OpenLayers.Util.getResolutionFromScale)
+ */
+ overrideDPI: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.ArcGISCache
+ * Creates a new instance of this class
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * options - {Object} extra layer options
+ */
+ initialize: function(name, url, options) {
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
+
+ if (this.resolutions) {
+ this.serverResolutions = this.resolutions;
+ this.maxExtent = this.getMaxExtentForResolution(this.resolutions[0]);
+ }
+
+ // this block steps through translating the values from the server layer JSON
+ // capabilities object into values that we can use. This is also a helpful
+ // reference when configuring this layer directly.
+ if (this.layerInfo) {
+ // alias the object
+ var info = this.layerInfo;
+
+ // build our extents
+ var startingTileExtent = new OpenLayers.Bounds(
+ info.fullExtent.xmin,
+ info.fullExtent.ymin,
+ info.fullExtent.xmax,
+ info.fullExtent.ymax
+ );
+
+ // set our projection based on the given spatial reference.
+ // esri uses slightly different IDs, so this may not be comprehensive
+ this.projection = 'EPSG:' + info.spatialReference.wkid;
+ this.sphericalMercator = (info.spatialReference.wkid == 102100);
+
+ // convert esri units into openlayers units (basic feet or meters only)
+ this.units = (info.units == "esriFeet") ? 'ft' : 'm';
+
+ // optional extended section based on whether or not the server returned
+ // specific tile information
+ if (!!info.tileInfo) {
+ // either set the tiles based on rows/columns, or specific width/height
+ this.tileSize = new OpenLayers.Size(
+ info.tileInfo.width || info.tileInfo.cols,
+ info.tileInfo.height || info.tileInfo.rows
+ );
+
+ // this must be set when manually configuring this layer
+ this.tileOrigin = new OpenLayers.LonLat(
+ info.tileInfo.origin.x,
+ info.tileInfo.origin.y
+ );
+
+ var upperLeft = new OpenLayers.Geometry.Point(
+ startingTileExtent.left,
+ startingTileExtent.top
+ );
+
+ var bottomRight = new OpenLayers.Geometry.Point(
+ startingTileExtent.right,
+ startingTileExtent.bottom
+ );
+
+ if (this.useScales) {
+ this.scales = [];
+ } else {
+ this.resolutions = [];
+ }
+
+ this.lods = [];
+ for(var key in info.tileInfo.lods) {
+ if (info.tileInfo.lods.hasOwnProperty(key)) {
+ var lod = info.tileInfo.lods[key];
+ if (this.useScales) {
+ this.scales.push(lod.scale);
+ } else {
+ this.resolutions.push(lod.resolution);
+ }
+
+ var start = this.getContainingTileCoords(upperLeft, lod.resolution);
+ lod.startTileCol = start.x;
+ lod.startTileRow = start.y;
+
+ var end = this.getContainingTileCoords(bottomRight, lod.resolution);
+ lod.endTileCol = end.x;
+ lod.endTileRow = end.y;
+ this.lods.push(lod);
+ }
+ }
+
+ this.maxExtent = this.calculateMaxExtentWithLOD(this.lods[0]);
+ this.serverResolutions = this.resolutions;
+ if (this.overrideDPI && info.tileInfo.dpi) {
+ // see comment above for 'overrideDPI'
+ OpenLayers.DOTS_PER_INCH = info.tileInfo.dpi;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getContainingTileCoords
+ * Calculates the x/y pixel corresponding to the position of the tile
+ * that contains the given point and for the for the given resolution.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position
+ * of the upper left tile for the given resolution.
+ */
+ getContainingTileCoords: function(point, res) {
+ return new OpenLayers.Pixel(
+ Math.max(Math.floor((point.x - this.tileOrigin.lon) / (this.tileSize.w * res)),0),
+ Math.max(Math.floor((this.tileOrigin.lat - point.y) / (this.tileSize.h * res)),0)
+ );
+ },
+
+ /**
+ * Method: calculateMaxExtentWithLOD
+ * Given a Level of Detail object from the server, this function
+ * calculates the actual max extent
+ *
+ * Parameters:
+ * lod - {Object} a Level of Detail Object from the server capabilities object
+ representing a particular zoom level
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The actual extent of the tiles for the given zoom level
+ */
+ calculateMaxExtentWithLOD: function(lod) {
+ // the max extent we're provided with just overlaps some tiles
+ // our real extent is the bounds of all the tiles we touch
+
+ var numTileCols = (lod.endTileCol - lod.startTileCol) + 1;
+ var numTileRows = (lod.endTileRow - lod.startTileRow) + 1;
+
+ var minX = this.tileOrigin.lon + (lod.startTileCol * this.tileSize.w * lod.resolution);
+ var maxX = minX + (numTileCols * this.tileSize.w * lod.resolution);
+
+ var maxY = this.tileOrigin.lat - (lod.startTileRow * this.tileSize.h * lod.resolution);
+ var minY = maxY - (numTileRows * this.tileSize.h * lod.resolution);
+ return new OpenLayers.Bounds(minX, minY, maxX, maxY);
+ },
+
+ /**
+ * Method: calculateMaxExtentWithExtent
+ * Given a 'suggested' max extent from the server, this function uses
+ * information about the actual tile sizes to determine the actual
+ * extent of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>} The 'suggested' extent for the layer
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The actual extent of the tiles for the given zoom level
+ */
+ calculateMaxExtentWithExtent: function(extent, res) {
+ var upperLeft = new OpenLayers.Geometry.Point(extent.left, extent.top);
+ var bottomRight = new OpenLayers.Geometry.Point(extent.right, extent.bottom);
+ var start = this.getContainingTileCoords(upperLeft, res);
+ var end = this.getContainingTileCoords(bottomRight, res);
+ var lod = {
+ resolution: res,
+ startTileCol: start.x,
+ startTileRow: start.y,
+ endTileCol: end.x,
+ endTileRow: end.y
+ };
+ return this.calculateMaxExtentWithLOD(lod);
+ },
+
+ /**
+ * Method: getUpperLeftTileCoord
+ * Calculates the x/y pixel corresponding to the position
+ * of the upper left tile for the given resolution.
+ *
+ * Parameters:
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position
+ * of the upper left tile for the given resolution.
+ */
+ getUpperLeftTileCoord: function(res) {
+ var upperLeft = new OpenLayers.Geometry.Point(
+ this.maxExtent.left,
+ this.maxExtent.top);
+ return this.getContainingTileCoords(upperLeft, res);
+ },
+
+ /**
+ * Method: getLowerRightTileCoord
+ * Calculates the x/y pixel corresponding to the position
+ * of the lower right tile for the given resolution.
+ *
+ * Parameters:
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position
+ * of the lower right tile for the given resolution.
+ */
+ getLowerRightTileCoord: function(res) {
+ var bottomRight = new OpenLayers.Geometry.Point(
+ this.maxExtent.right,
+ this.maxExtent.bottom);
+ return this.getContainingTileCoords(bottomRight, res);
+ },
+
+ /**
+ * Method: getMaxExtentForResolution
+ * Since the max extent of a set of tiles can change from zoom level
+ * to zoom level, we need to be able to calculate that max extent
+ * for a given resolution.
+ *
+ * Parameters:
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The extent for this resolution
+ */
+ getMaxExtentForResolution: function(res) {
+ var start = this.getUpperLeftTileCoord(res);
+ var end = this.getLowerRightTileCoord(res);
+
+ var numTileCols = (end.x - start.x) + 1;
+ var numTileRows = (end.y - start.y) + 1;
+
+ var minX = this.tileOrigin.lon + (start.x * this.tileSize.w * res);
+ var maxX = minX + (numTileCols * this.tileSize.w * res);
+
+ var maxY = this.tileOrigin.lat - (start.y * this.tileSize.h * res);
+ var minY = maxY - (numTileRows * this.tileSize.h * res);
+ return new OpenLayers.Bounds(minX, minY, maxX, maxY);
+ },
+
+ /**
+ * APIMethod: clone
+ * Returns an exact clone of this OpenLayers.Layer.ArcGISCache
+ *
+ * Parameters:
+ * [obj] - {Object} optional object to assign the cloned instance to.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.ArcGISCache>} clone of this instance
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.ArcGISCache(this.name, this.url, this.options);
+ }
+ return OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ },
+
+ /**
+ * Method: initGriddedTiles
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initGriddedTiles: function(bounds) {
+ delete this._tileOrigin;
+ OpenLayers.Layer.XYZ.prototype.initGriddedTiles.apply(this, arguments);
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ var resolution = this.map.getResolution();
+ return this.maxExtent = this.getMaxExtentForResolution(resolution);
+ },
+
+ /**
+ * Method: getTileOrigin
+ * Determine the origin for aligning the grid of tiles.
+ * The origin will be derived from the layer's <maxExtent> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The tile origin.
+ */
+ getTileOrigin: function() {
+ if (!this._tileOrigin) {
+ var extent = this.getMaxExtent();
+ this._tileOrigin = new OpenLayers.LonLat(extent.left, extent.bottom);
+ }
+ return this._tileOrigin;
+ },
+
+ /**
+ * Method: getURL
+ * Determine the URL for a tile given the tile bounds. This is should support
+ * urls that access tiles through an ArcGIS Server MapServer or directly through
+ * the hex folder structure using HTTP. Just be sure to set the useArcGISServer
+ * property appropriately! This is basically the same as
+ * 'OpenLayers.Layer.TMS.getURL', but with the addition of hex addressing,
+ * and tile rounding.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} The URL for a tile based on given bounds.
+ */
+ getURL: function (bounds) {
+ var res = this.getResolution();
+
+ // tile center
+ var originTileX = (this.tileOrigin.lon + (res * this.tileSize.w/2));
+ var originTileY = (this.tileOrigin.lat - (res * this.tileSize.h/2));
+
+ var center = bounds.getCenterLonLat();
+ var point = { x: center.lon, y: center.lat };
+ var x = (Math.round(Math.abs((center.lon - originTileX) / (res * this.tileSize.w))));
+ var y = (Math.round(Math.abs((originTileY - center.lat) / (res * this.tileSize.h))));
+ var z = this.map.getZoom();
+
+ // this prevents us from getting pink tiles (non-existant tiles)
+ if (this.lods) {
+ var lod = this.lods[this.map.getZoom()];
+ if ((x < lod.startTileCol || x > lod.endTileCol)
+ || (y < lod.startTileRow || y > lod.endTileRow)) {
+ return null;
+ }
+ }
+ else {
+ var start = this.getUpperLeftTileCoord(res);
+ var end = this.getLowerRightTileCoord(res);
+ if ((x < start.x || x >= end.x)
+ || (y < start.y || y >= end.y)) {
+ return null;
+ }
+ }
+
+ // Construct the url string
+ var url = this.url;
+ var s = '' + x + y + z;
+
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(s, url);
+ }
+
+ // Accessing tiles through ArcGIS Server uses a different path
+ // structure than direct access via the folder structure.
+ if (this.useArcGISServer) {
+ // AGS MapServers have pretty url access to tiles
+ url = url + '/tile/${z}/${y}/${x}';
+ } else {
+ // The tile images are stored using hex values on disk.
+ x = 'C' + OpenLayers.Number.zeroPad(x, 8, 16);
+ y = 'R' + OpenLayers.Number.zeroPad(y, 8, 16);
+ z = 'L' + OpenLayers.Number.zeroPad(z, 2, 10);
+ url = url + '/${z}/${y}/${x}.' + this.type;
+ }
+
+ // Write the values into our formatted url
+ url = OpenLayers.String.format(url, {'x': x, 'y': y, 'z': z});
+
+ return OpenLayers.Util.urlAppend(
+ url, OpenLayers.Util.getParameterString(this.params)
+ );
+ },
+
+ CLASS_NAME: 'OpenLayers.Layer.ArcGISCache'
+});
+/* ======================================================================
+ OpenLayers/Control/WMSGetFeatureInfo.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Format/WMSGetFeatureInfo.js
+ */
+
+/**
+ * Class: OpenLayers.Control.WMSGetFeatureInfo
+ * The WMSGetFeatureInfo control uses a WMS query to get information about a point on the map. The
+ * information may be in a display-friendly format such as HTML, or a machine-friendly format such
+ * as GML, depending on the server's capabilities and the client's configuration. This control
+ * handles click or hover events, attempts to parse the results using an OpenLayers.Format, and
+ * fires a 'getfeatureinfo' event with the click position, the raw body of the response, and an
+ * array of features if it successfully read the response.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send GetFeatureInfo requests when mouse stops moving.
+ * Default is false.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: drillDown
+ * {Boolean} Drill down over all WMS layers in the map. When
+ * using drillDown mode, hover is not possible, and an infoFormat that
+ * returns parseable features is required. Default is false.
+ */
+ drillDown: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a WMS query. This
+ * sets the feature_count parameter on WMS GetFeatureInfo
+ * requests.
+ */
+ maxFeatures: 10,
+
+ /**
+ * APIProperty: clickCallback
+ * {String} The click callback to register in the
+ * {<OpenLayers.Handler.Click>} object created when the hover
+ * option is set to false. Default is "click".
+ */
+ clickCallback: "click",
+
+ /**
+ * APIProperty: output
+ * {String} Either "features" or "object". When triggering a getfeatureinfo
+ * request should we pass on an array of features or an object with with
+ * a "features" property and other properties (such as the url of the
+ * WMS). Default is "features".
+ */
+ output: "features",
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.WMS>)} The layers to query for feature info.
+ * If omitted, all map WMS layers with a url that matches this <url> or
+ * <layerUrls> will be considered.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: queryVisible
+ * {Boolean} If true, filter out hidden layers when searching the map for
+ * layers to query. Default is false.
+ */
+ queryVisible: false,
+
+ /**
+ * APIProperty: url
+ * {String} The URL of the WMS service to use. If not provided, the url
+ * of the first eligible layer will be used.
+ */
+ url: null,
+
+ /**
+ * APIProperty: layerUrls
+ * {Array(String)} Optional list of urls for layers that should be queried.
+ * This can be used when the layer url differs from the url used for
+ * making GetFeatureInfo requests (in the case of a layer using cached
+ * tiles).
+ */
+ layerUrls: null,
+
+ /**
+ * APIProperty: infoFormat
+ * {String} The mimetype to request from the server. If you are using
+ * drillDown mode and have multiple servers that do not share a common
+ * infoFormat, you can override the control's infoFormat by providing an
+ * INFO_FORMAT parameter in your <OpenLayers.Layer.WMS> instance(s).
+ */
+ infoFormat: 'text/html',
+
+ /**
+ * APIProperty: vendorParams
+ * {Object} Additional parameters that will be added to the request, for
+ * WMS implementations that support them. This could e.g. look like
+ * (start code)
+ * {
+ * radius: 5
+ * }
+ * (end)
+ */
+ vendorParams: {},
+
+ /**
+ * APIProperty: format
+ * {<OpenLayers.Format>} A format for parsing GetFeatureInfo responses.
+ * Default is <OpenLayers.Format.WMSGetFeatureInfo>.
+ */
+ format: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Optional properties to set on the format (if one is not provided
+ * in the <format> property.
+ */
+ formatOptions: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control, e.g.
+ * (start code)
+ * {
+ * "click": {delay: 100},
+ * "hover": {delay: 300}
+ * }
+ * (end)
+ */
+
+ /**
+ * Property: handler
+ * {Object} Reference to the <OpenLayers.Handler> for this control
+ */
+ handler: null,
+
+ /**
+ * Property: hoverRequest
+ * {<OpenLayers.Request>} contains the currently running hover request
+ * (if any).
+ */
+ hoverRequest: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforegetfeatureinfo - Triggered before the request is sent.
+ * The event object has an *xy* property with the position of the
+ * mouse click or hover event that triggers the request.
+ * nogetfeatureinfo - no queryable layers were found.
+ * getfeatureinfo - Triggered when a GetFeatureInfo response is received.
+ * The event object has a *text* property with the body of the
+ * response (String), a *features* property with an array of the
+ * parsed features, an *xy* property with the position of the mouse
+ * click or hover event that triggered the request, and a *request*
+ * property with the request itself. If drillDown is set to true and
+ * multiple requests were issued to collect feature info from all
+ * layers, *text* and *request* will only contain the response body
+ * and request object of the last request.
+ */
+
+ /**
+ * Constructor: <OpenLayers.Control.WMSGetFeatureInfo>
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(!this.format) {
+ this.format = new OpenLayers.Format.WMSGetFeatureInfo(
+ options.formatOptions
+ );
+ }
+
+ if(this.drillDown === true) {
+ this.hover = false;
+ }
+
+ if(this.hover) {
+ this.handler = new OpenLayers.Handler.Hover(
+ this, {
+ 'move': this.cancelHover,
+ 'pause': this.getInfoForHover
+ },
+ OpenLayers.Util.extend(this.handlerOptions.hover || {}, {
+ 'delay': 250
+ }));
+ } else {
+ var callbacks = {};
+ callbacks[this.clickCallback] = this.getInfoForClick;
+ this.handler = new OpenLayers.Handler.Click(
+ this, callbacks, this.handlerOptions.click || {});
+ }
+ },
+
+ /**
+ * Method: getInfoForClick
+ * Called on click
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ getInfoForClick: function(evt) {
+ this.events.triggerEvent("beforegetfeatureinfo", {xy: evt.xy});
+ // Set the cursor to "wait" to tell the user we're working on their
+ // click.
+ OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
+ this.request(evt.xy, {});
+ },
+
+ /**
+ * Method: getInfoForHover
+ * Pause callback for the hover handler
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ getInfoForHover: function(evt) {
+ this.events.triggerEvent("beforegetfeatureinfo", {xy: evt.xy});
+ this.request(evt.xy, {hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Cancel callback for the hover handler
+ */
+ cancelHover: function() {
+ if (this.hoverRequest) {
+ this.hoverRequest.abort();
+ this.hoverRequest = null;
+ }
+ },
+
+ /**
+ * Method: findLayers
+ * Internal method to get the layers, independent of whether we are
+ * inspecting the map or using a client-provided array
+ */
+ findLayers: function() {
+
+ var candidates = this.layers || this.map.layers;
+ var layers = [];
+ var layer, url;
+ for(var i = candidates.length - 1; i >= 0; --i) {
+ layer = candidates[i];
+ if(layer instanceof OpenLayers.Layer.WMS &&
+ (!this.queryVisible || layer.getVisibility())) {
+ url = OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url;
+ // if the control was not configured with a url, set it
+ // to the first layer url
+ if(this.drillDown === false && !this.url) {
+ this.url = url;
+ }
+ if(this.drillDown === true || this.urlMatches(url)) {
+ layers.push(layer);
+ }
+ }
+ }
+ return layers;
+ },
+
+ /**
+ * Method: urlMatches
+ * Test to see if the provided url matches either the control <url> or one
+ * of the <layerUrls>.
+ *
+ * Parameters:
+ * url - {String} The url to test.
+ *
+ * Returns:
+ * {Boolean} The provided url matches the control <url> or one of the
+ * <layerUrls>.
+ */
+ urlMatches: function(url) {
+ var matches = OpenLayers.Util.isEquivalentUrl(this.url, url);
+ if(!matches && this.layerUrls) {
+ for(var i=0, len=this.layerUrls.length; i<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[i], url)) {
+ matches = true;
+ break;
+ }
+ }
+ }
+ return matches;
+ },
+
+ /**
+ * Method: buildWMSOptions
+ * Build an object with the relevant WMS options for the GetFeatureInfo request
+ *
+ * Parameters:
+ * url - {String} The url to be used for sending the request
+ * layers - {Array(<OpenLayers.Layer.WMS)} An array of layers
+ * clickPosition - {<OpenLayers.Pixel>} The position on the map where the mouse
+ * event occurred.
+ * format - {String} The format from the corresponding GetMap request
+ */
+ buildWMSOptions: function(url, layers, clickPosition, format) {
+ var layerNames = [], styleNames = [];
+ for (var i = 0, len = layers.length; i < len; i++) {
+ if (layers[i].params.LAYERS != null) {
+ layerNames = layerNames.concat(layers[i].params.LAYERS);
+ styleNames = styleNames.concat(this.getStyleNames(layers[i]));
+ }
+ }
+ var firstLayer = layers[0];
+ // use the firstLayer's projection if it matches the map projection -
+ // this assumes that all layers will be available in this projection
+ var projection = this.map.getProjection();
+ var layerProj = firstLayer.projection;
+ if (layerProj && layerProj.equals(this.map.getProjectionObject())) {
+ projection = layerProj.getCode();
+ }
+ var params = OpenLayers.Util.extend({
+ service: "WMS",
+ version: firstLayer.params.VERSION,
+ request: "GetFeatureInfo",
+ exceptions: firstLayer.params.EXCEPTIONS,
+ bbox: this.map.getExtent().toBBOX(null,
+ firstLayer.reverseAxisOrder()),
+ feature_count: this.maxFeatures,
+ height: this.map.getSize().h,
+ width: this.map.getSize().w,
+ format: format,
+ info_format: firstLayer.params.INFO_FORMAT || this.infoFormat
+ }, (parseFloat(firstLayer.params.VERSION) >= 1.3) ?
+ {
+ crs: projection,
+ i: parseInt(clickPosition.x),
+ j: parseInt(clickPosition.y)
+ } :
+ {
+ srs: projection,
+ x: parseInt(clickPosition.x),
+ y: parseInt(clickPosition.y)
+ }
+ );
+ if (layerNames.length != 0) {
+ params = OpenLayers.Util.extend({
+ layers: layerNames,
+ query_layers: layerNames,
+ styles: styleNames
+ }, params);
+ }
+ OpenLayers.Util.applyDefaults(params, this.vendorParams);
+ return {
+ url: url,
+ params: OpenLayers.Util.upperCaseObject(params),
+ callback: function(request) {
+ this.handleResponse(clickPosition, request, url);
+ },
+ scope: this
+ };
+ },
+
+ /**
+ * Method: getStyleNames
+ * Gets the STYLES parameter for the layer. Make sure the STYLES parameter
+ * matches the LAYERS parameter
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>}
+ *
+ * Returns:
+ * {Array(String)} The STYLES parameter
+ */
+ getStyleNames: function(layer) {
+ // in the event of a WMS layer bundling multiple layers but not
+ // specifying styles,we need the same number of commas to specify
+ // the default style for each of the layers. We can't just leave it
+ // blank as we may be including other layers that do specify styles.
+ var styleNames;
+ if (layer.params.STYLES) {
+ styleNames = layer.params.STYLES;
+ } else {
+ if (OpenLayers.Util.isArray(layer.params.LAYERS)) {
+ styleNames = new Array(layer.params.LAYERS.length);
+ } else { // Assume it's a String
+ styleNames = layer.params.LAYERS.replace(/[^,]/g, "");
+ }
+ }
+ return styleNames;
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeatureInfo request to the WMS
+ *
+ * Parameters:
+ * clickPosition - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ * options - {Object} additional options for this method.
+ *
+ * Valid options:
+ * - *hover* {Boolean} true if we do the request for the hover handler
+ */
+ request: function(clickPosition, options) {
+ var layers = this.findLayers();
+ if(layers.length == 0) {
+ this.events.triggerEvent("nogetfeatureinfo");
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ return;
+ }
+
+ options = options || {};
+ if(this.drillDown === false) {
+ var wmsOptions = this.buildWMSOptions(this.url, layers,
+ clickPosition, layers[0].params.FORMAT);
+ var request = OpenLayers.Request.GET(wmsOptions);
+
+ if (options.hover === true) {
+ this.hoverRequest = request;
+ }
+ } else {
+ this._requestCount = 0;
+ this._numRequests = 0;
+ this.features = [];
+ // group according to service url to combine requests
+ var services = {}, url;
+ for(var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ var service, found = false;
+ url = OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url;
+ if(url in services) {
+ services[url].push(layer);
+ } else {
+ this._numRequests++;
+ services[url] = [layer];
+ }
+ }
+ var layers;
+ for (var url in services) {
+ layers = services[url];
+ var wmsOptions = this.buildWMSOptions(url, layers,
+ clickPosition, layers[0].params.FORMAT);
+ OpenLayers.Request.GET(wmsOptions);
+ }
+ }
+ },
+
+ /**
+ * Method: triggerGetFeatureInfo
+ * Trigger the getfeatureinfo event when all is done
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ * xy - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ * features - {Array(<OpenLayers.Feature.Vector>)} or
+ * {Array({Object}) when output is "object". The object has a url and a
+ * features property which contains an array of features.
+ */
+ triggerGetFeatureInfo: function(request, xy, features) {
+ this.events.triggerEvent("getfeatureinfo", {
+ text: request.responseText,
+ features: features,
+ request: request,
+ xy: xy
+ });
+
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ },
+
+ /**
+ * Method: handleResponse
+ * Handler for the GetFeatureInfo response.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ * request - {XMLHttpRequest} The request object.
+ * url - {String} The url which was used for this request.
+ */
+ handleResponse: function(xy, request, url) {
+
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var features = this.format.read(doc);
+ if (this.drillDown === false) {
+ this.triggerGetFeatureInfo(request, xy, features);
+ } else {
+ this._requestCount++;
+ if (this.output === "object") {
+ this._features = (this._features || []).concat(
+ {url: url, features: features}
+ );
+ } else {
+ this._features = (this._features || []).concat(features);
+ }
+ if (this._requestCount === this._numRequests) {
+ this.triggerGetFeatureInfo(request, xy, this._features.concat());
+ delete this._features;
+ delete this._requestCount;
+ delete this._numRequests;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.WMSGetFeatureInfo"
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1_3_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_3_0
+ * Read WMS Capabilities version 1.3.0.
+ * SLD 1.1.0 adds in the extra operations DescribeLayer and GetLegendGraphic,
+ * see: http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_3>
+ */
+OpenLayers.Format.WMSCapabilities.v1_3_0 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_3, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.3.0",
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_3_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/SOSGetFeatureOfInterest.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSGetFeatureOfInterest
+ * Read and write SOS GetFeatureOfInterest. This is used to get to
+ * the location of the features (stations). The stations can have 1 or more
+ * sensors.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SOSGetFeatureOfInterest = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ sos: "http://www.opengis.net/sos/1.0",
+ gml: "http://www.opengis.net/gml",
+ sa: "http://www.opengis.net/sampling/1.0",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sos",
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SOSGetFeatureOfInterest
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Parse a GetFeatureOfInterest response and return an array of features
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+
+ var info = {features: []};
+ this.readNode(data, info);
+
+ var features = [];
+ for (var i=0, len=info.features.length; i<len; i++) {
+ var container = info.features[i];
+ // reproject features if needed
+ if(this.internalProjection && this.externalProjection &&
+ container.components[0]) {
+ container.components[0].transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ var feature = new OpenLayers.Feature.Vector(
+ container.components[0], container.attributes);
+ features.push(feature);
+ }
+ return features;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "sa": {
+ "SamplingPoint": function(node, obj) {
+ // sampling point can also be without a featureMember if
+ // there is only 1
+ if (!obj.attributes) {
+ var feature = {attributes: {}};
+ obj.features.push(feature);
+ obj = feature;
+ }
+ obj.attributes.id = this.getAttributeNS(node,
+ this.namespaces.gml, "id");
+ this.readChildNodes(node, obj);
+ },
+ "position": function (node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ },
+ "gml": OpenLayers.Util.applyDefaults({
+ "FeatureCollection": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "featureMember": function(node, obj) {
+ var feature = {attributes: {}};
+ obj.features.push(feature);
+ this.readChildNodes(node, feature);
+ },
+ "name": function(node, obj) {
+ obj.attributes.name = this.getChildValue(node);
+ },
+ "pos": function(node, obj) {
+ // we need to parse the srsName to get to the
+ // externalProjection, that's why we cannot use
+ // GML v3 for this
+ if (!this.externalProjection) {
+ this.externalProjection = new OpenLayers.Projection(
+ node.getAttribute("srsName"));
+ }
+ OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(
+ this, [node, obj]);
+ }
+ }, OpenLayers.Format.GML.v3.prototype.readers.gml)
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "sos": {
+ "GetFeatureOfInterest": function(options) {
+ var node = this.createElementNSPlus("GetFeatureOfInterest", {
+ attributes: {
+ version: this.VERSION,
+ service: 'SOS',
+ "xsi:schemaLocation": this.schemaLocation
+ }
+ });
+ for (var i=0, len=options.fois.length; i<len; i++) {
+ this.writeNode("FeatureOfInterestId", {foi: options.fois[i]}, node);
+ }
+ return node;
+ },
+ "FeatureOfInterestId": function(options) {
+ var node = this.createElementNSPlus("FeatureOfInterestId", {value: options.foi});
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SOSGetFeatureOfInterest"
+
+});
+/* ======================================================================
+ OpenLayers/Format/SOSGetObservation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/SOSGetFeatureOfInterest.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSGetObservation
+ * Read and write SOS GetObersation (to get the actual values from a sensor)
+ * version 1.0.0
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SOSGetObservation = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows",
+ gml: "http://www.opengis.net/gml",
+ sos: "http://www.opengis.net/sos/1.0",
+ ogc: "http://www.opengis.net/ogc",
+ om: "http://www.opengis.net/om/1.0",
+ sa: "http://www.opengis.net/sampling/1.0",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sos",
+
+ /**
+ * Constructor: OpenLayers.Format.SOSGetObservation
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} An object containing the measurements
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var info = {measurements: [], observations: []};
+ this.readNode(data, info);
+ return info;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} An SOS GetObservation request XML string.
+ */
+ write: function(options) {
+ var node = this.writeNode("sos:GetObservation", options);
+ node.setAttribute("xmlns:om", this.namespaces.om);
+ node.setAttribute("xmlns:ogc", this.namespaces.ogc);
+ this.setAttributeNS(
+ node, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "om": {
+ "ObservationCollection": function(node, obj) {
+ obj.id = this.getAttributeNS(node, this.namespaces.gml, "id");
+ this.readChildNodes(node, obj);
+ },
+ "member": function(node, observationCollection) {
+ this.readChildNodes(node, observationCollection);
+ },
+ "Measurement": function(node, observationCollection) {
+ var measurement = {};
+ observationCollection.measurements.push(measurement);
+ this.readChildNodes(node, measurement);
+ },
+ "Observation": function(node, observationCollection) {
+ var observation = {};
+ observationCollection.observations.push(observation);
+ this.readChildNodes(node, observation);
+ },
+ "samplingTime": function(node, measurement) {
+ var samplingTime = {};
+ measurement.samplingTime = samplingTime;
+ this.readChildNodes(node, samplingTime);
+ },
+ "observedProperty": function(node, measurement) {
+ measurement.observedProperty =
+ this.getAttributeNS(node, this.namespaces.xlink, "href");
+ this.readChildNodes(node, measurement);
+ },
+ "procedure": function(node, measurement) {
+ measurement.procedure =
+ this.getAttributeNS(node, this.namespaces.xlink, "href");
+ this.readChildNodes(node, measurement);
+ },
+ "featureOfInterest": function(node, observation) {
+ var foi = {features: []};
+ observation.fois = [];
+ observation.fois.push(foi);
+ this.readChildNodes(node, foi);
+ // postprocessing to get actual features
+ var features = [];
+ for (var i=0, len=foi.features.length; i<len; i++) {
+ var feature = foi.features[i];
+ features.push(new OpenLayers.Feature.Vector(
+ feature.components[0], feature.attributes));
+ }
+ foi.features = features;
+ },
+ "result": function(node, measurement) {
+ var result = {};
+ measurement.result = result;
+ if (this.getChildValue(node) !== '') {
+ result.value = this.getChildValue(node);
+ result.uom = node.getAttribute("uom");
+ } else {
+ this.readChildNodes(node, result);
+ }
+ }
+ },
+ "sa": OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.sa,
+ "gml": OpenLayers.Util.applyDefaults({
+ "TimeInstant": function(node, samplingTime) {
+ var timeInstant = {};
+ samplingTime.timeInstant = timeInstant;
+ this.readChildNodes(node, timeInstant);
+ },
+ "timePosition": function(node, timeInstant) {
+ timeInstant.timePosition = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.gml)
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "sos": {
+ "GetObservation": function(options) {
+ var node = this.createElementNSPlus("GetObservation", {
+ attributes: {
+ version: this.VERSION,
+ service: 'SOS'
+ }
+ });
+ this.writeNode("offering", options, node);
+ if (options.eventTime) {
+ this.writeNode("eventTime", options, node);
+ }
+ for (var procedure in options.procedures) {
+ this.writeNode("procedure", options.procedures[procedure], node);
+ }
+ for (var observedProperty in options.observedProperties) {
+ this.writeNode("observedProperty", options.observedProperties[observedProperty], node);
+ }
+ if (options.foi) {
+ this.writeNode("featureOfInterest", options.foi, node);
+ }
+ this.writeNode("responseFormat", options, node);
+ if (options.resultModel) {
+ this.writeNode("resultModel", options, node);
+ }
+ if (options.responseMode) {
+ this.writeNode("responseMode", options, node);
+ }
+ return node;
+ },
+ "featureOfInterest": function(foi) {
+ var node = this.createElementNSPlus("featureOfInterest");
+ this.writeNode("ObjectID", foi.objectId, node);
+ return node;
+ },
+ "ObjectID": function(options) {
+ return this.createElementNSPlus("ObjectID",
+ {value: options});
+ },
+ "responseFormat": function(options) {
+ return this.createElementNSPlus("responseFormat",
+ {value: options.responseFormat});
+ },
+ "procedure": function(procedure) {
+ return this.createElementNSPlus("procedure",
+ {value: procedure});
+ },
+ "offering": function(options) {
+ return this.createElementNSPlus("offering", {value:
+ options.offering});
+ },
+ "observedProperty": function(observedProperty) {
+ return this.createElementNSPlus("observedProperty",
+ {value: observedProperty});
+ },
+ "eventTime": function(options) {
+ var node = this.createElementNSPlus("eventTime");
+ if (options.eventTime === 'latest') {
+ this.writeNode("ogc:TM_Equals", options, node);
+ }
+ return node;
+ },
+ "resultModel": function(options) {
+ return this.createElementNSPlus("resultModel", {value:
+ options.resultModel});
+ },
+ "responseMode": function(options) {
+ return this.createElementNSPlus("responseMode", {value:
+ options.responseMode});
+ }
+ },
+ "ogc": {
+ "TM_Equals": function(options) {
+ var node = this.createElementNSPlus("ogc:TM_Equals");
+ this.writeNode("ogc:PropertyName", {property:
+ "urn:ogc:data:time:iso8601"}, node);
+ if (options.eventTime === 'latest') {
+ this.writeNode("gml:TimeInstant", {value: 'latest'}, node);
+ }
+ return node;
+ },
+ "PropertyName": function(options) {
+ return this.createElementNSPlus("ogc:PropertyName",
+ {value: options.property});
+ }
+ },
+ "gml": {
+ "TimeInstant": function(options) {
+ var node = this.createElementNSPlus("gml:TimeInstant");
+ this.writeNode("gml:timePosition", options, node);
+ return node;
+ },
+ "timePosition": function(options) {
+ var node = this.createElementNSPlus("gml:timePosition",
+ {value: options.value});
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SOSGetObservation"
+
+});
+/* ======================================================================
+ OpenLayers/Control/UTFGrid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.UTFGrid
+ *
+ * This Control provides behavior associated with UTFGrid Layers.
+ * These 'hit grids' provide underlying feature attributes without
+ * calling the server (again). This control allows Mousemove, Hovering
+ * and Click events to trigger callbacks that use the attributes in
+ * whatever way you need.
+ *
+ * The most common example may be a UTFGrid layer containing feature
+ * attributes that are displayed in a div as you mouseover.
+ *
+ * Example Code:
+ *
+ * (start code)
+ * var world_utfgrid = new OpenLayers.Layer.UTFGrid(
+ * 'UTFGrid Layer',
+ * "http://tiles/world_utfgrid/${z}/${x}/${y}.json"
+ * );
+ * map.addLayer(world_utfgrid);
+ *
+ * var control = new OpenLayers.Control.UTFGrid({
+ * layers: [world_utfgrid],
+ * handlerMode: 'move',
+ * callback: function(infoLookup) {
+ * // do something with returned data
+ *
+ * }
+ * })
+ * (end code)
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.UTFGrid = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: Layers
+ * List of layers to consider. Must be Layer.UTFGrids
+ * `null` is the default indicating all UTFGrid Layers are queried.
+ * {Array} <OpenLayers.Layer.UTFGrid>
+ */
+ layers: null,
+
+ /* Property: defaultHandlerOptions
+ * The default opts passed to the handler constructors
+ */
+ defaultHandlerOptions: {
+ 'delay': 300,
+ 'pixelTolerance': 4,
+ 'stopMove': false,
+ 'single': true,
+ 'double': false,
+ 'stopSingle': false,
+ 'stopDouble': false
+ },
+
+ /* APIProperty: handlerMode
+ * Defaults to 'click'. Can be 'hover' or 'move'.
+ */
+ handlerMode: 'click',
+
+ /**
+ * APIMethod: setHandler
+ * sets this.handlerMode and calls resetHandler()
+ *
+ * Parameters:
+ * hm - {String} Handler Mode string; 'click', 'hover' or 'move'.
+ */
+ setHandler: function(hm) {
+ this.handlerMode = hm;
+ this.resetHandler();
+ },
+
+ /**
+ * Method: resetHandler
+ * Deactivates the old hanlder and creates a new
+ * <OpenLayers.Handler> based on the mode specified in
+ * this.handlerMode
+ *
+ */
+ resetHandler: function() {
+ if (this.handler) {
+ this.handler.deactivate();
+ this.handler.destroy();
+ this.handler = null;
+ }
+
+ if (this.handlerMode == 'hover') {
+ // Handle this event on hover
+ this.handler = new OpenLayers.Handler.Hover(
+ this,
+ {'pause': this.handleEvent, 'move': this.reset},
+ this.handlerOptions
+ );
+ } else if (this.handlerMode == 'click') {
+ // Handle this event on click
+ this.handler = new OpenLayers.Handler.Click(
+ this, {
+ 'click': this.handleEvent
+ }, this.handlerOptions
+ );
+ } else if (this.handlerMode == 'move') {
+ this.handler = new OpenLayers.Handler.Hover(
+ this,
+ // Handle this event while hovering OR moving
+ {'pause': this.handleEvent, 'move': this.handleEvent},
+ this.handlerOptions
+ );
+ }
+ if (this.handler) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Constructor: <OpenLayers.Control.UTFGrid>
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.handlerOptions = options.handlerOptions || this.defaultHandlerOptions;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.resetHandler();
+ },
+
+ /**
+ * Method: handleEvent
+ * Internal method called when specified event is triggered.
+ *
+ * This method does several things:
+ *
+ * Gets the lonLat of the event.
+ *
+ * Loops through the appropriate hit grid layers and gathers the attributes.
+ *
+ * Passes the attributes to the callback
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ handleEvent: function(evt) {
+ if (evt == null) {
+ this.reset();
+ return;
+ }
+
+ var lonLat = this.map.getLonLatFromPixel(evt.xy);
+ if (!lonLat) {
+ return;
+ }
+
+ var layers = this.findLayers();
+ if (layers.length > 0) {
+ var infoLookup = {};
+ var layer, idx;
+ for (var i=0, len=layers.length; i<len; i++) {
+ layer = layers[i];
+ idx = OpenLayers.Util.indexOf(this.map.layers, layer);
+ infoLookup[idx] = layer.getFeatureInfo(lonLat);
+ }
+ this.callback(infoLookup, lonLat, evt.xy);
+ }
+ },
+
+ /**
+ * APIMethod: callback
+ * Function to be called when a mouse event corresponds with a location that
+ * includes data in one of the configured UTFGrid layers.
+ *
+ * Parameters:
+ * infoLookup - {Object} Keys of this object are layer indexes and can be
+ * used to resolve a layer in the map.layers array. The structure of
+ * the property values depend on the data included in the underlying
+ * UTFGrid and may be any valid JSON type.
+ */
+ callback: function(infoLookup) {
+ // to be provided in the constructor
+ },
+
+ /**
+ * Method: reset
+ * Calls the callback with null.
+ */
+ reset: function(evt) {
+ this.callback(null);
+ },
+
+ /**
+ * Method: findLayers
+ * Internal method to get the layers, independent of whether we are
+ * inspecting the map or using a client-provided array
+ *
+ * The default value of this.layers is null; this causes the
+ * findLayers method to return ALL UTFGrid layers encountered.
+ *
+ * Parameters:
+ * None
+ *
+ * Returns:
+ * {Array} Layers to handle on each event
+ */
+ findLayers: function() {
+ var candidates = this.layers || this.map.layers;
+ var layers = [];
+ var layer;
+ for (var i=candidates.length-1; i>=0; --i) {
+ layer = candidates[i];
+ if (layer instanceof OpenLayers.Layer.UTFGrid ) {
+ layers.push(layer);
+ }
+ }
+ return layers;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.UTFGrid"
+});
+/* ======================================================================
+ OpenLayers/Format/CQL.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CQL
+ * Read CQL strings to get <OpenLayers.Filter> objects. Write
+ * <OpenLayers.Filter> objects to get CQL strings. Create a new parser with
+ * the <OpenLayers.Format.CQL> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.CQL = (function() {
+
+ var tokens = [
+ "PROPERTY", "COMPARISON", "VALUE", "LOGICAL"
+ ],
+
+ patterns = {
+ PROPERTY: /^[_a-zA-Z]\w*/,
+ COMPARISON: /^(=|<>|<=|<|>=|>|LIKE)/i,
+ IS_NULL: /^IS NULL/i,
+ COMMA: /^,/,
+ LOGICAL: /^(AND|OR)/i,
+ VALUE: /^('([^']|'')*'|\d+(\.\d*)?|\.\d+)/,
+ LPAREN: /^\(/,
+ RPAREN: /^\)/,
+ SPATIAL: /^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i,
+ NOT: /^NOT/i,
+ BETWEEN: /^BETWEEN/i,
+ GEOMETRY: function(text) {
+ var type = /^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(text);
+ if (type) {
+ var len = text.length;
+ var idx = text.indexOf("(", type[0].length);
+ if (idx > -1) {
+ var depth = 1;
+ while (idx < len && depth > 0) {
+ idx++;
+ switch(text.charAt(idx)) {
+ case '(':
+ depth++;
+ break;
+ case ')':
+ depth--;
+ break;
+ default:
+ // in default case, do nothing
+ }
+ }
+ }
+ return [text.substr(0, idx+1)];
+ }
+ },
+ END: /^$/
+ },
+
+ follows = {
+ LPAREN: ['GEOMETRY', 'SPATIAL', 'PROPERTY', 'VALUE', 'LPAREN'],
+ RPAREN: ['NOT', 'LOGICAL', 'END', 'RPAREN'],
+ PROPERTY: ['COMPARISON', 'BETWEEN', 'COMMA', 'IS_NULL'],
+ BETWEEN: ['VALUE'],
+ IS_NULL: ['END'],
+ COMPARISON: ['VALUE'],
+ COMMA: ['GEOMETRY', 'VALUE', 'PROPERTY'],
+ VALUE: ['LOGICAL', 'COMMA', 'RPAREN', 'END'],
+ SPATIAL: ['LPAREN'],
+ LOGICAL: ['NOT', 'VALUE', 'SPATIAL', 'PROPERTY', 'LPAREN'],
+ NOT: ['PROPERTY', 'LPAREN'],
+ GEOMETRY: ['COMMA', 'RPAREN']
+ },
+
+ operators = {
+ '=': OpenLayers.Filter.Comparison.EQUAL_TO,
+ '<>': OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ '<': OpenLayers.Filter.Comparison.LESS_THAN,
+ '<=': OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
+ '>': OpenLayers.Filter.Comparison.GREATER_THAN,
+ '>=': OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+ 'LIKE': OpenLayers.Filter.Comparison.LIKE,
+ 'BETWEEN': OpenLayers.Filter.Comparison.BETWEEN,
+ 'IS NULL': OpenLayers.Filter.Comparison.IS_NULL
+ },
+
+ operatorReverse = {},
+
+ logicals = {
+ 'AND': OpenLayers.Filter.Logical.AND,
+ 'OR': OpenLayers.Filter.Logical.OR
+ },
+
+ logicalReverse = {},
+
+ precedence = {
+ 'RPAREN': 3,
+ 'LOGICAL': 2,
+ 'COMPARISON': 1
+ };
+
+ var i;
+ for (i in operators) {
+ if (operators.hasOwnProperty(i)) {
+ operatorReverse[operators[i]] = i;
+ }
+ }
+
+ for (i in logicals) {
+ if (logicals.hasOwnProperty(i)) {
+ logicalReverse[logicals[i]] = i;
+ }
+ }
+
+ function tryToken(text, pattern) {
+ if (pattern instanceof RegExp) {
+ return pattern.exec(text);
+ } else {
+ return pattern(text);
+ }
+ }
+
+ function nextToken(text, tokens) {
+ var i, token, len = tokens.length;
+ for (i=0; i<len; i++) {
+ token = tokens[i];
+ var pat = patterns[token];
+ var matches = tryToken(text, pat);
+ if (matches) {
+ var match = matches[0];
+ var remainder = text.substr(match.length).replace(/^\s*/, "");
+ return {
+ type: token,
+ text: match,
+ remainder: remainder
+ };
+ }
+ }
+
+ var msg = "ERROR: In parsing: [" + text + "], expected one of: ";
+ for (i=0; i<len; i++) {
+ token = tokens[i];
+ msg += "\n " + token + ": " + patterns[token];
+ }
+
+ throw new Error(msg);
+ }
+
+ function tokenize(text) {
+ var results = [];
+ var token, expect = ["NOT", "GEOMETRY", "SPATIAL", "PROPERTY", "LPAREN"];
+
+ do {
+ token = nextToken(text, expect);
+ text = token.remainder;
+ expect = follows[token.type];
+ if (token.type != "END" && !expect) {
+ throw new Error("No follows list for " + token.type);
+ }
+ results.push(token);
+ } while (token.type != "END");
+
+ return results;
+ }
+
+ function buildAst(tokens) {
+ var operatorStack = [],
+ postfix = [];
+
+ while (tokens.length) {
+ var tok = tokens.shift();
+ switch (tok.type) {
+ case "PROPERTY":
+ case "GEOMETRY":
+ case "VALUE":
+ postfix.push(tok);
+ break;
+ case "COMPARISON":
+ case "BETWEEN":
+ case "IS_NULL":
+ case "LOGICAL":
+ var p = precedence[tok.type];
+
+ while (operatorStack.length > 0 &&
+ (precedence[operatorStack[operatorStack.length - 1].type] <= p)
+ ) {
+ postfix.push(operatorStack.pop());
+ }
+
+ operatorStack.push(tok);
+ break;
+ case "SPATIAL":
+ case "NOT":
+ case "LPAREN":
+ operatorStack.push(tok);
+ break;
+ case "RPAREN":
+ while (operatorStack.length > 0 &&
+ (operatorStack[operatorStack.length - 1].type != "LPAREN")
+ ) {
+ postfix.push(operatorStack.pop());
+ }
+ operatorStack.pop(); // toss out the LPAREN
+
+ if (operatorStack.length > 0 &&
+ operatorStack[operatorStack.length-1].type == "SPATIAL") {
+ postfix.push(operatorStack.pop());
+ }
+ case "COMMA":
+ case "END":
+ break;
+ default:
+ throw new Error("Unknown token type " + tok.type);
+ }
+ }
+
+ while (operatorStack.length > 0) {
+ postfix.push(operatorStack.pop());
+ }
+
+ function buildTree() {
+ var tok = postfix.pop();
+ switch (tok.type) {
+ case "LOGICAL":
+ var rhs = buildTree(),
+ lhs = buildTree();
+ return new OpenLayers.Filter.Logical({
+ filters: [lhs, rhs],
+ type: logicals[tok.text.toUpperCase()]
+ });
+ case "NOT":
+ var operand = buildTree();
+ return new OpenLayers.Filter.Logical({
+ filters: [operand],
+ type: OpenLayers.Filter.Logical.NOT
+ });
+ case "BETWEEN":
+ var min, max, property;
+ postfix.pop(); // unneeded AND token here
+ max = buildTree();
+ min = buildTree();
+ property = buildTree();
+ return new OpenLayers.Filter.Comparison({
+ property: property,
+ lowerBoundary: min,
+ upperBoundary: max,
+ type: OpenLayers.Filter.Comparison.BETWEEN
+ });
+ case "COMPARISON":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Comparison({
+ property: property,
+ value: value,
+ type: operators[tok.text.toUpperCase()]
+ });
+ case "IS_NULL":
+ var property = buildTree();
+ return new OpenLayers.Filter.Comparison({
+ property: property,
+ type: operators[tok.text.toUpperCase()]
+ });
+ case "VALUE":
+ var match = tok.text.match(/^'(.*)'$/);
+ if (match) {
+ return match[1].replace(/''/g, "'");
+ } else {
+ return Number(tok.text);
+ }
+ case "SPATIAL":
+ switch(tok.text.toUpperCase()) {
+ case "BBOX":
+ var maxy = buildTree(),
+ maxx = buildTree(),
+ miny = buildTree(),
+ minx = buildTree(),
+ prop = buildTree();
+
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ property: prop,
+ value: OpenLayers.Bounds.fromArray(
+ [minx, miny, maxx, maxy]
+ )
+ });
+ case "INTERSECTS":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: property,
+ value: value
+ });
+ case "WITHIN":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.WITHIN,
+ property: property,
+ value: value
+ });
+ case "CONTAINS":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.CONTAINS,
+ property: property,
+ value: value
+ });
+ case "DWITHIN":
+ var distance = buildTree(),
+ value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ value: value,
+ property: property,
+ distance: Number(distance)
+ });
+ }
+ case "GEOMETRY":
+ return OpenLayers.Geometry.fromWKT(tok.text);
+ default:
+ return tok.text;
+ }
+ }
+
+ var result = buildTree();
+ if (postfix.length > 0) {
+ var msg = "Remaining tokens after building AST: \n";
+ for (var i = postfix.length - 1; i >= 0; i--) {
+ msg += postfix[i].type + ": " + postfix[i].text + "\n";
+ }
+ throw new Error(msg);
+ }
+
+ return result;
+ }
+
+ return OpenLayers.Class(OpenLayers.Format, {
+ /**
+ * APIMethod: read
+ * Generate a filter from a CQL string.
+
+ * Parameters:
+ * text - {String} The CQL text.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter based on the CQL text.
+ */
+ read: function(text) {
+ var result = buildAst(tokenize(text));
+ if (this.keepData) {
+ this.data = result;
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: write
+ * Convert a filter into a CQL string.
+
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} The filter.
+ *
+ * Returns:
+ * {String} A CQL string based on the filter.
+ */
+ write: function(filter) {
+ if (filter instanceof OpenLayers.Geometry) {
+ return filter.toString();
+ }
+ switch (filter.CLASS_NAME) {
+ case "OpenLayers.Filter.Spatial":
+ switch(filter.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ return "BBOX(" +
+ filter.property + "," +
+ filter.value.toBBOX() +
+ ")";
+ case OpenLayers.Filter.Spatial.DWITHIN:
+ return "DWITHIN(" +
+ filter.property + ", " +
+ this.write(filter.value) + ", " +
+ filter.distance + ")";
+ case OpenLayers.Filter.Spatial.WITHIN:
+ return "WITHIN(" +
+ filter.property + ", " +
+ this.write(filter.value) + ")";
+ case OpenLayers.Filter.Spatial.INTERSECTS:
+ return "INTERSECTS(" +
+ filter.property + ", " +
+ this.write(filter.value) + ")";
+ case OpenLayers.Filter.Spatial.CONTAINS:
+ return "CONTAINS(" +
+ filter.property + ", " +
+ this.write(filter.value) + ")";
+ default:
+ throw new Error("Unknown spatial filter type: " + filter.type);
+ }
+ case "OpenLayers.Filter.Logical":
+ if (filter.type == OpenLayers.Filter.Logical.NOT) {
+ // TODO: deal with precedence of logical operators to
+ // avoid extra parentheses (not urgent)
+ return "NOT (" + this.write(filter.filters[0]) + ")";
+ } else {
+ var res = "(";
+ var first = true;
+ for (var i = 0; i < filter.filters.length; i++) {
+ if (first) {
+ first = false;
+ } else {
+ res += ") " + logicalReverse[filter.type] + " (";
+ }
+ res += this.write(filter.filters[i]);
+ }
+ return res + ")";
+ }
+ case "OpenLayers.Filter.Comparison":
+ if (filter.type == OpenLayers.Filter.Comparison.BETWEEN) {
+ return filter.property + " BETWEEN " +
+ this.write(filter.lowerBoundary) + " AND " +
+ this.write(filter.upperBoundary);
+ } else {
+ return (filter.value !== null) ? filter.property +
+ " " + operatorReverse[filter.type] + " " +
+ this.write(filter.value) : filter.property +
+ " " + operatorReverse[filter.type];
+ }
+ case undefined:
+ if (typeof filter === "string") {
+ return "'" + filter.replace(/'/g, "''") + "'";
+ } else if (typeof filter === "number") {
+ return String(filter);
+ }
+ default:
+ throw new Error("Can't encode: " + filter.CLASS_NAME + " " + filter);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.CQL"
+
+ });
+})();
+
+/* ======================================================================
+ OpenLayers/Control/Split.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Split
+ * Acts as a split feature agent while editing vector features.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforesplit - Triggered before a split occurs. Listeners receive an
+ * event object with *source* and *target* properties.
+ * split - Triggered when a split occurs. Listeners receive an event with
+ * an *original* property and a *features* property. The original
+ * is a reference to the target feature that the sketch or modified
+ * feature intersects. The features property is a list of all features
+ * that result from this single split. This event is triggered before
+ * the resulting features are added to the layer (while the layer still
+ * has a reference to the original).
+ * aftersplit - Triggered after all splits resulting from a single sketch
+ * or feature modification have occurred. The original features
+ * have been destroyed and features that result from the split
+ * have already been added to the layer. Listeners receive an event
+ * with a *source* and *features* property. The source references the
+ * sketch or modified feature used as a splitter. The features
+ * property is a list of all resulting features.
+ */
+
+ /**
+ * APIProperty: layer
+ * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
+ * Set at construction or after construction with <setLayer>.
+ */
+ layer: null,
+
+ /**
+ * Property: source
+ * {<OpenLayers.Layer.Vector>} Optional source layer. Any newly created
+ * or modified features from this layer will be used to split features
+ * on the target layer. If not provided, a temporary sketch layer will
+ * be created.
+ */
+ source: null,
+
+ /**
+ * Property: sourceOptions
+ * {Options} If a temporary sketch layer is created, these layer options
+ * will be applied.
+ */
+ sourceOptions: null,
+
+ /**
+ * APIProperty: tolerance
+ * {Number} Distance between the calculated intersection and a vertex on
+ * the source geometry below which the existing vertex will be used
+ * for the split. Default is null.
+ */
+ tolerance: null,
+
+ /**
+ * APIProperty: edge
+ * {Boolean} Allow splits given intersection of edges only. Default is
+ * true. If false, a vertex on the source must be within the
+ * <tolerance> distance of the calculated intersection for a split
+ * to occur.
+ */
+ edge: true,
+
+ /**
+ * APIProperty: deferDelete
+ * {Boolean} Instead of removing features from the layer, set feature
+ * states of split features to DELETE. This assumes a save strategy
+ * or other component is in charge of removing features from the
+ * layer. Default is false. If false, split features will be
+ * immediately deleted from the layer.
+ */
+ deferDelete: false,
+
+ /**
+ * APIProperty: mutual
+ * {Boolean} If source and target layers are the same, split source
+ * features and target features where they intersect. Default is
+ * true. If false, only target features will be split.
+ */
+ mutual: true,
+
+ /**
+ * APIProperty: targetFilter
+ * {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the target layer is eligible for
+ * splitting.
+ */
+ targetFilter: null,
+
+ /**
+ * APIProperty: sourceFilter
+ * {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the source layer is eligible for
+ * splitting.
+ */
+ sourceFilter: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
+ * no source layer is provided.
+ */
+ handler: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Split
+ * Creates a new split control. A control is constructed with a target
+ * layer and an optional source layer. While the control is active,
+ * creating new features or modifying existing features on the source
+ * layer will result in splitting any eligible features on the target
+ * layer. If no source layer is provided, a temporary sketch layer will
+ * be created to create lines for splitting features on the target.
+ *
+ * Parameters:
+ * options - {Object} An object containing all configuration properties for
+ * the control.
+ *
+ * Valid options:
+ * layer - {<OpenLayers.Layer.Vector>} The target layer. Features from this
+ * layer will be split by new or modified features on the source layer
+ * or temporary sketch layer.
+ * source - {<OpenLayers.Layer.Vector>} Optional source layer. If provided
+ * newly created features or modified features will be used to split
+ * features on the target layer. If not provided, a temporary sketch
+ * layer will be created for drawing lines.
+ * tolerance - {Number} Optional value for the distance between a source
+ * vertex and the calculated intersection below which the split will
+ * occur at the vertex.
+ * edge - {Boolean} Allow splits given intersection of edges only. Default
+ * is true. If false, a vertex on the source must be within the
+ * <tolerance> distance of the calculated intersection for a split
+ * to occur.
+ * mutual - {Boolean} If source and target are the same, split source
+ * features and target features where they intersect. Default is
+ * true. If false, only target features will be split.
+ * targetFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the target layer is eligible for
+ * splitting.
+ * sourceFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the target layer is eligible for
+ * splitting.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.options = options || {}; // TODO: this could be done by the super
+
+ // set the source layer if provided
+ if(this.options.source) {
+ this.setSource(this.options.source);
+ }
+ },
+
+ /**
+ * APIMethod: setSource
+ * Set the source layer for edits layer.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The new source layer layer. If
+ * null, a temporary sketch layer will be created.
+ */
+ setSource: function(layer) {
+ if(this.active) {
+ this.deactivate();
+ if(this.handler) {
+ this.handler.destroy();
+ delete this.handler;
+ }
+ this.source = layer;
+ this.activate();
+ } else {
+ this.source = layer;
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. Activating the control registers listeners for
+ * editing related events so that during feature creation and
+ * modification, features in the target will be considered for
+ * splitting.
+ */
+ activate: function() {
+ var activated = OpenLayers.Control.prototype.activate.call(this);
+ if(activated) {
+ if(!this.source) {
+ if(!this.handler) {
+ this.handler = new OpenLayers.Handler.Path(this,
+ {done: function(geometry) {
+ this.onSketchComplete({
+ feature: new OpenLayers.Feature.Vector(geometry)
+ });
+ }},
+ {layerOptions: this.sourceOptions}
+ );
+ }
+ this.handler.activate();
+ } else if(this.source.events) {
+ this.source.events.on({
+ sketchcomplete: this.onSketchComplete,
+ afterfeaturemodified: this.afterFeatureModified,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. Deactivating the control unregisters listeners
+ * so feature editing may proceed without engaging the split agent.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
+ if(deactivated) {
+ if(this.source && this.source.events) {
+ this.source.events.un({
+ sketchcomplete: this.onSketchComplete,
+ afterfeaturemodified: this.afterFeatureModified,
+ scope: this
+ });
+ }
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: onSketchComplete
+ * Registered as a listener for the sketchcomplete event on the editable
+ * layer.
+ *
+ * Parameters:
+ * event - {Object} The sketch complete event.
+ *
+ * Returns:
+ * {Boolean} Stop the sketch from being added to the layer (it has been
+ * split).
+ */
+ onSketchComplete: function(event) {
+ this.feature = null;
+ return !this.considerSplit(event.feature);
+ },
+
+ /**
+ * Method: afterFeatureModified
+ * Registered as a listener for the afterfeaturemodified event on the
+ * editable layer.
+ *
+ * Parameters:
+ * event - {Object} The after feature modified event.
+ */
+ afterFeatureModified: function(event) {
+ if(event.modified) {
+ var feature = event.feature;
+ if (typeof feature.geometry.split === "function") {
+ this.feature = event.feature;
+ this.considerSplit(event.feature);
+ }
+ }
+ },
+
+ /**
+ * Method: removeByGeometry
+ * Remove a feature from a list based on the given geometry.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} A list of features.
+ * geometry - {<OpenLayers.Geometry>} A geometry.
+ */
+ removeByGeometry: function(features, geometry) {
+ for(var i=0, len=features.length; i<len; ++i) {
+ if(features[i].geometry === geometry) {
+ features.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: isEligible
+ * Test if a target feature is eligible for splitting.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Feature.Vector>} The target feature.
+ *
+ * Returns:
+ * {Boolean} The target is eligible for splitting.
+ */
+ isEligible: function(target) {
+ if (!target.geometry) {
+ return false;
+ } else {
+ return (
+ target.state !== OpenLayers.State.DELETE
+ ) && (
+ typeof target.geometry.split === "function"
+ ) && (
+ this.feature !== target
+ ) && (
+ !this.targetFilter ||
+ this.targetFilter.evaluate(target.attributes)
+ );
+ }
+ },
+
+ /**
+ * Method: considerSplit
+ * Decide whether or not to split target features with the supplied
+ * feature. If <mutual> is true, both the source and target features
+ * will be split if eligible.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The newly created or modified
+ * feature.
+ *
+ * Returns:
+ * {Boolean} The supplied feature was split (and destroyed).
+ */
+ considerSplit: function(feature) {
+ var sourceSplit = false;
+ var targetSplit = false;
+ if(!this.sourceFilter ||
+ this.sourceFilter.evaluate(feature.attributes)) {
+ var features = this.layer && this.layer.features || [];
+ var target, results, proceed;
+ var additions = [], removals = [];
+ var mutual = (this.layer === this.source) && this.mutual;
+ var options = {
+ edge: this.edge,
+ tolerance: this.tolerance,
+ mutual: mutual
+ };
+ var sourceParts = [feature.geometry];
+ var targetFeature, targetParts;
+ var source, parts;
+ for(var i=0, len=features.length; i<len; ++i) {
+ targetFeature = features[i];
+ if(this.isEligible(targetFeature)) {
+ targetParts = [targetFeature.geometry];
+ // work through source geoms - this array may change
+ for(var j=0; j<sourceParts.length; ++j) {
+ source = sourceParts[j];
+ // work through target parts - this array may change
+ for(var k=0; k<targetParts.length; ++k) {
+ target = targetParts[k];
+ if(source.getBounds().intersectsBounds(target.getBounds())) {
+ results = source.split(target, options);
+ if(results) {
+ proceed = this.events.triggerEvent(
+ "beforesplit", {source: feature, target: targetFeature}
+ );
+ if(proceed !== false) {
+ if(mutual) {
+ parts = results[0];
+ // handle parts that result from source splitting
+ if(parts.length > 1) {
+ // splice in new source parts
+ parts.unshift(j, 1); // add args for splice below
+ Array.prototype.splice.apply(sourceParts, parts);
+ j += parts.length - 3;
+ }
+ results = results[1];
+ }
+ // handle parts that result from target splitting
+ if(results.length > 1) {
+ // splice in new target parts
+ results.unshift(k, 1); // add args for splice below
+ Array.prototype.splice.apply(targetParts, results);
+ k += results.length - 3;
+ }
+ }
+ }
+ }
+ }
+ }
+ if(targetParts && targetParts.length > 1) {
+ this.geomsToFeatures(targetFeature, targetParts);
+ this.events.triggerEvent("split", {
+ original: targetFeature,
+ features: targetParts
+ });
+ Array.prototype.push.apply(additions, targetParts);
+ removals.push(targetFeature);
+ targetSplit = true;
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ this.geomsToFeatures(feature, sourceParts);
+ this.events.triggerEvent("split", {
+ original: feature,
+ features: sourceParts
+ });
+ Array.prototype.push.apply(additions, sourceParts);
+ removals.push(feature);
+ sourceSplit = true;
+ }
+ if(sourceSplit || targetSplit) {
+ // remove and add feature events are suppressed
+ // listen for split event on this control instead
+ if(this.deferDelete) {
+ // Set state instead of removing. Take care to avoid
+ // setting delete for features that have not yet been
+ // inserted - those should be destroyed immediately.
+ var feat, destroys = [];
+ for(var i=0, len=removals.length; i<len; ++i) {
+ feat = removals[i];
+ if(feat.state === OpenLayers.State.INSERT) {
+ destroys.push(feat);
+ } else {
+ feat.state = OpenLayers.State.DELETE;
+ this.layer.drawFeature(feat);
+ }
+ }
+ this.layer.destroyFeatures(destroys, {silent: true});
+ for(var i=0, len=additions.length; i<len; ++i) {
+ additions[i].state = OpenLayers.State.INSERT;
+ }
+ } else {
+ this.layer.destroyFeatures(removals, {silent: true});
+ }
+ this.layer.addFeatures(additions, {silent: true});
+ this.events.triggerEvent("aftersplit", {
+ source: feature,
+ features: additions
+ });
+ }
+ }
+ return sourceSplit;
+ },
+
+ /**
+ * Method: geomsToFeatures
+ * Create new features given a template feature and a list of geometries.
+ * The list of geometries is modified in place. The result will be
+ * a list of new features.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned.
+ * geoms - {Array(<OpenLayers.Geometry>)} List of goemetries. This will
+ * become a list of new features.
+ */
+ geomsToFeatures: function(feature, geoms) {
+ var clone = feature.clone();
+ delete clone.geometry;
+ var newFeature;
+ for(var i=0, len=geoms.length; i<len; ++i) {
+ // turn results list from geoms to features
+ newFeature = clone.clone();
+ newFeature.geometry = geoms[i];
+ newFeature.state = OpenLayers.State.INSERT;
+ geoms[i] = newFeature;
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up the control.
+ */
+ destroy: function() {
+ if(this.active) {
+ this.deactivate(); // TODO: this should be handled by the super
+ }
+ OpenLayers.Control.prototype.destroy.call(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Split"
+});
+/* ======================================================================
+ OpenLayers/Layer/WMTS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMTS
+ * Instances of the WMTS class allow viewing of tiles from a service that
+ * implements the OGC WMTS specification version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer will be considered a base layer. Default is true.
+ */
+ isBaseLayer: true,
+
+ /**
+ * Property: version
+ * {String} WMTS version. Default is "1.0.0".
+ */
+ version: "1.0.0",
+
+ /**
+ * APIProperty: requestEncoding
+ * {String} Request encoding. Can be "REST" or "KVP". Default is "KVP".
+ */
+ requestEncoding: "KVP",
+
+ /**
+ * APIProperty: url
+ * {String|Array(String)} The base URL or request URL template for the WMTS
+ * service. Must be provided. Array is only supported for base URLs, not
+ * for request URL templates. URL templates are only supported for
+ * REST <requestEncoding>.
+ */
+ url: null,
+
+ /**
+ * APIProperty: layer
+ * {String} The layer identifier advertised by the WMTS service. Must be
+ * provided.
+ */
+ layer: null,
+
+ /**
+ * APIProperty: matrixSet
+ * {String} One of the advertised matrix set identifiers. Must be provided.
+ */
+ matrixSet: null,
+
+ /**
+ * APIProperty: style
+ * {String} One of the advertised layer styles. Must be provided.
+ */
+ style: null,
+
+ /**
+ * APIProperty: format
+ * {String} The image MIME type. Default is "image/jpeg".
+ */
+ format: "image/jpeg",
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} The top-left corner of the tile matrix in map
+ * units. If the tile origin for each matrix in a set is different,
+ * the <matrixIds> should include a topLeftCorner property. If
+ * not provided, the tile origin will default to the top left corner
+ * of the layer <maxExtent>.
+ */
+ tileOrigin: null,
+
+ /**
+ * APIProperty: tileFullExtent
+ * {<OpenLayers.Bounds>} The full extent of the tile set. If not supplied,
+ * the layer's <maxExtent> property will be used.
+ */
+ tileFullExtent: null,
+
+ /**
+ * APIProperty: formatSuffix
+ * {String} For REST request encoding, an image format suffix must be
+ * included in the request. If not provided, the suffix will be derived
+ * from the <format> property.
+ */
+ formatSuffix: null,
+
+ /**
+ * APIProperty: matrixIds
+ * {Array} A list of tile matrix identifiers. If not provided, the matrix
+ * identifiers will be assumed to be integers corresponding to the
+ * map zoom level. If a list of strings is provided, each item should
+ * be the matrix identifier that corresponds to the map zoom level.
+ * Additionally, a list of objects can be provided. Each object should
+ * describe the matrix as presented in the WMTS capabilities. These
+ * objects should have the propertes shown below.
+ *
+ * Matrix properties:
+ * identifier - {String} The matrix identifier (required).
+ * scaleDenominator - {Number} The matrix scale denominator.
+ * topLeftCorner - {<OpenLayers.LonLat>} The top left corner of the
+ * matrix. Must be provided if different than the layer <tileOrigin>.
+ * tileWidth - {Number} The tile width for the matrix. Must be provided
+ * if different than the width given in the layer <tileSize>.
+ * tileHeight - {Number} The tile height for the matrix. Must be provided
+ * if different than the height given in the layer <tileSize>.
+ */
+ matrixIds: null,
+
+ /**
+ * APIProperty: dimensions
+ * {Array} For RESTful request encoding, extra dimensions may be specified.
+ * Items in this list should be property names in the <params> object.
+ * Values of extra dimensions will be determined from the corresponding
+ * values in the <params> object.
+ */
+ dimensions: null,
+
+ /**
+ * APIProperty: params
+ * {Object} Extra parameters to include in tile requests. For KVP
+ * <requestEncoding>, these properties will be encoded in the request
+ * query string. For REST <requestEncoding>, these properties will
+ * become part of the request path, with order determined by the
+ * <dimensions> list.
+ */
+ params: null,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Additionally, if this layer is to be used
+ * as an overlay and the cache has fewer zoom levels than the base
+ * layer, you can supply a negative zoomOffset. For example, if a
+ * map zoom level of 1 corresponds to your cache level zero, you would
+ * supply a -1 zoomOffset (and set the maxResolution of the layer
+ * appropriately). The zoomOffset value has no effect if complete
+ * matrix definitions (including scaleDenominator) are supplied in
+ * the <matrixIds> property. Defaults to 0 (no zoom offset).
+ */
+ zoomOffset: 0,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Property: formatSuffixMap
+ * {Object} a map between WMTS 'format' request parameter and tile image file suffix
+ */
+ formatSuffixMap: {
+ "image/png": "png",
+ "image/png8": "png",
+ "image/png24": "png",
+ "image/png32": "png",
+ "png": "png",
+ "image/jpeg": "jpg",
+ "image/jpg": "jpg",
+ "jpeg": "jpg",
+ "jpg": "jpg"
+ },
+
+ /**
+ * Property: matrix
+ * {Object} Matrix definition for the current map resolution. Updated by
+ * the <updateMatrixProperties> method.
+ */
+ matrix: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.WMTS
+ * Create a new WMTS layer.
+ *
+ * Example:
+ * (code)
+ * var wmts = new OpenLayers.Layer.WMTS({
+ * name: "My WMTS Layer",
+ * url: "http://example.com/wmts",
+ * layer: "layer_id",
+ * style: "default",
+ * matrixSet: "matrix_id"
+ * });
+ * (end)
+ *
+ * Parameters:
+ * config - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * url - {String} The base url for the service. See the <url> property.
+ * layer - {String} The layer identifier. See the <layer> property.
+ * style - {String} The layer style identifier. See the <style> property.
+ * matrixSet - {String} The tile matrix set identifier. See the <matrixSet>
+ * property.
+ *
+ * Any other documented layer properties can be provided in the config object.
+ */
+ initialize: function(config) {
+
+ // confirm required properties are supplied
+ var required = {
+ url: true,
+ layer: true,
+ style: true,
+ matrixSet: true
+ };
+ for (var prop in required) {
+ if (!(prop in config)) {
+ throw new Error("Missing property '" + prop + "' in layer configuration.");
+ }
+ }
+
+ config.params = OpenLayers.Util.upperCaseObject(config.params);
+ var args = [config.name, config.url, config.params, config];
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, args);
+
+
+ // determine format suffix (for REST)
+ if (!this.formatSuffix) {
+ this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop();
+ }
+
+ // expand matrixIds (may be array of string or array of object)
+ if (this.matrixIds) {
+ var len = this.matrixIds.length;
+ if (len && typeof this.matrixIds[0] === "string") {
+ var ids = this.matrixIds;
+ this.matrixIds = new Array(len);
+ for (var i=0; i<len; ++i) {
+ this.matrixIds[i] = {identifier: ids[i]};
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function() {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: updateMatrixProperties
+ * Called when map resolution changes to update matrix related properties.
+ */
+ updateMatrixProperties: function() {
+ this.matrix = this.getMatrix();
+ if (this.matrix) {
+ if (this.matrix.topLeftCorner) {
+ this.tileOrigin = this.matrix.topLeftCorner;
+ }
+ if (this.matrix.tileWidth && this.matrix.tileHeight) {
+ this.tileSize = new OpenLayers.Size(
+ this.matrix.tileWidth, this.matrix.tileHeight
+ );
+ }
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(
+ this.maxExtent.left, this.maxExtent.top
+ );
+ }
+ if (!this.tileFullExtent) {
+ this.tileFullExtent = this.maxExtent;
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ if (zoomChanged || !this.matrix) {
+ this.updateMatrixProperties();
+ }
+ return OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMTS>} An exact clone of this <OpenLayers.Layer.WMTS>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMTS(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: getIdentifier
+ * Get the current index in the matrixIds array.
+ */
+ getIdentifier: function() {
+ return this.getServerZoom();
+ },
+
+ /**
+ * Method: getMatrix
+ * Get the appropriate matrix definition for the current map resolution.
+ */
+ getMatrix: function() {
+ var matrix;
+ if (!this.matrixIds || this.matrixIds.length === 0) {
+ matrix = {identifier: this.getIdentifier()};
+ } else {
+ // get appropriate matrix given the map scale if possible
+ if ("scaleDenominator" in this.matrixIds[0]) {
+ // scale denominator calculation based on WMTS spec
+ var denom =
+ OpenLayers.METERS_PER_INCH *
+ OpenLayers.INCHES_PER_UNIT[this.units] *
+ this.getServerResolution() / 0.28E-3;
+ var diff = Number.POSITIVE_INFINITY;
+ var delta;
+ for (var i=0, ii=this.matrixIds.length; i<ii; ++i) {
+ delta = Math.abs(1 - (this.matrixIds[i].scaleDenominator / denom));
+ if (delta < diff) {
+ diff = delta;
+ matrix = this.matrixIds[i];
+ }
+ }
+ } else {
+ // fall back on zoom as index
+ matrix = this.matrixIds[this.getIdentifier()];
+ }
+ }
+ return matrix;
+ },
+
+ /**
+ * Method: getTileInfo
+ * Get tile information for a given location at the current map resolution.
+ *
+ * Parameters:
+ * loc - {<OpenLayers.LonLat} A location in map coordinates.
+ *
+ * Returns:
+ * {Object} An object with "col", "row", "i", and "j" properties. The col
+ * and row values are zero based tile indexes from the top left. The
+ * i and j values are the number of pixels to the left and top
+ * (respectively) of the given location within the target tile.
+ */
+ getTileInfo: function(loc) {
+ var res = this.getServerResolution();
+
+ var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w);
+ var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h);
+
+ var col = Math.floor(fx);
+ var row = Math.floor(fy);
+
+ return {
+ col: col,
+ row: row,
+ i: Math.floor((fx - col) * this.tileSize.w),
+ j: Math.floor((fy - row) * this.tileSize.h)
+ };
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A URL for the tile corresponding to the given bounds.
+ */
+ getURL: function(bounds) {
+ bounds = this.adjustBounds(bounds);
+ var url = "";
+ if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) {
+
+ var center = bounds.getCenterLonLat();
+ var info = this.getTileInfo(center);
+ var matrixId = this.matrix.identifier;
+ var dimensions = this.dimensions, params;
+
+ if (OpenLayers.Util.isArray(this.url)) {
+ url = this.selectUrl([
+ this.version, this.style, this.matrixSet,
+ this.matrix.identifier, info.row, info.col
+ ].join(","), this.url);
+ } else {
+ url = this.url;
+ }
+
+ if (this.requestEncoding.toUpperCase() === "REST") {
+ params = this.params;
+ if (url.indexOf("{") !== -1) {
+ var template = url.replace(/\{/g, "${");
+ var context = {
+ // spec does not make clear if capital S or not
+ style: this.style, Style: this.style,
+ TileMatrixSet: this.matrixSet,
+ TileMatrix: this.matrix.identifier,
+ TileRow: info.row,
+ TileCol: info.col
+ };
+ if (dimensions) {
+ var dimension, i;
+ for (i=dimensions.length-1; i>=0; --i) {
+ dimension = dimensions[i];
+ context[dimension] = params[dimension.toUpperCase()];
+ }
+ }
+ url = OpenLayers.String.format(template, context);
+ } else {
+ // include 'version', 'layer' and 'style' in tile resource url
+ var path = this.version + "/" + this.layer + "/" + this.style + "/";
+
+ // append optional dimension path elements
+ if (dimensions) {
+ for (var i=0; i<dimensions.length; i++) {
+ if (params[dimensions[i]]) {
+ path = path + params[dimensions[i]] + "/";
+ }
+ }
+ }
+
+ // append other required path elements
+ path = path + this.matrixSet + "/" + this.matrix.identifier +
+ "/" + info.row + "/" + info.col + "." + this.formatSuffix;
+
+ if (!url.match(/\/$/)) {
+ url = url + "/";
+ }
+ url = url + path;
+ }
+ } else if (this.requestEncoding.toUpperCase() === "KVP") {
+
+ // assemble all required parameters
+ params = {
+ SERVICE: "WMTS",
+ REQUEST: "GetTile",
+ VERSION: this.version,
+ LAYER: this.layer,
+ STYLE: this.style,
+ TILEMATRIXSET: this.matrixSet,
+ TILEMATRIX: this.matrix.identifier,
+ TILEROW: info.row,
+ TILECOL: info.col,
+ FORMAT: this.format
+ };
+ url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]);
+
+ }
+ }
+ return url;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Extend the existing layer <params> with new properties. Tiles will be
+ * reloaded with updated params in the request.
+ *
+ * Parameters:
+ * newParams - {Object} Properties to extend to existing <params>.
+ */
+ mergeNewParams: function(newParams) {
+ if (this.requestEncoding.toUpperCase() === "KVP") {
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(
+ this, [OpenLayers.Util.upperCaseObject(newParams)]
+ );
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMTS"
+});
+/* ======================================================================
+ OpenLayers/Protocol/SOS/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/SOS.js
+ * @requires OpenLayers/Format/SOSGetFeatureOfInterest.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.SOS.v1_0_0
+ * An SOS v1.0.0 Protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.SOS.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+ OpenLayers.Protocol.SOS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * APIProperty: fois
+ * {Array(String)} Array of features of interest (foi)
+ */
+ fois: null,
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.SOS
+ * A class for giving layers an SOS protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * url - {String} URL to send requests to (required).
+ * fois - {Array} The features of interest (required).
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = new OpenLayers.Format.SOSGetFeatureOfInterest(
+ this.formatOptions);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new sensor positions. This is done by
+ * issuing one GetFeatureOfInterest request.
+ */
+ read: function(options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+ var format = this.format;
+ var data = OpenLayers.Format.XML.prototype.write.apply(format,
+ [format.writeNode("sos:GetFeatureOfInterest", {fois: this.fois})]
+ );
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ data: data
+ });
+ return response;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ response.features = this.parseFeatures(request);
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} Array of features
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.SOS.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/Layer/KaMapCache.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Layer/KaMap.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.KaMapCache
+ *
+ * This class is designed to talk directly to a web-accessible ka-Map
+ * cache generated by the precache2.php script.
+ *
+ * To create a a new KaMapCache layer, you must indicate also the "i" parameter
+ * (that will be used to calculate the file extension), and another special
+ * parameter, object names "metaTileSize", with "h" (height) and "w" (width)
+ * properties.
+ *
+ * // Create a new kaMapCache layer.
+ * var kamap_base = new OpenLayers.Layer.KaMapCache(
+ * "Satellite",
+ * "http://www.example.org/web/acessible/cache",
+ * {g: "satellite", map: "world", i: 'png24', metaTileSize: {w: 5, h: 5} }
+ * );
+ *
+ * // Create an kaMapCache overlay layer (using "isBaseLayer: false").
+ * // Forces the output to be a "gif", using the "i" parameter.
+ * var kamap_overlay = new OpenLayers.Layer.KaMapCache(
+ * "Streets",
+ * "http://www.example.org/web/acessible/cache",
+ * {g: "streets", map: "world", i: "gif", metaTileSize: {w: 5, h: 5} },
+ * {isBaseLayer: false}
+ * );
+ *
+ * The cache URLs must look like:
+ * var/cache/World/50000/Group_Name/def/t-440320/l20480
+ *
+ * This means that the cache generated via tile.php will *not* work with
+ * this class, and should instead use the KaMap layer.
+ *
+ * More information is available in Ticket #1518.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.KaMap>
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.KaMapCache = OpenLayers.Class(OpenLayers.Layer.KaMap, {
+
+ /**
+ * Constant: IMAGE_EXTENSIONS
+ * {Object} Simple hash map to convert format to extension.
+ */
+ IMAGE_EXTENSIONS: {
+ 'jpeg': 'jpg',
+ 'gif' : 'gif',
+ 'png' : 'png',
+ 'png8' : 'png',
+ 'png24' : 'png',
+ 'dithered' : 'png'
+ },
+
+ /**
+ * Constant: DEFAULT_FORMAT
+ * {Object} Simple hash map to convert format to extension.
+ */
+ DEFAULT_FORMAT: 'jpeg',
+
+ /**
+ * Constructor: OpenLayers.Layer.KaMapCache
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object} Parameters to be sent to the HTTP server in the
+ * query string for the tile. The format can be set via the 'i'
+ * parameter (defaults to jpg) , and the map should be set via
+ * the 'map' parameter. It has been reported that ka-Map may behave
+ * inconsistently if your format parameter does not match the format
+ * parameter configured in your config.php. (See ticket #327 for more
+ * information.)
+ * options - {Object} Additional options for the layer. Any of the
+ * APIProperties listed on this layer, and any layer types it
+ * extends, can be overridden through the options parameter.
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.KaMap.prototype.initialize.apply(this, arguments);
+ this.extension = this.IMAGE_EXTENSIONS[this.params.i.toLowerCase() || this.DEFAULT_FORMAT];
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var mapRes = this.map.getResolution();
+ var scale = Math.round((this.map.getScale() * 10000)) / 10000;
+ var pX = Math.round(bounds.left / mapRes);
+ var pY = -Math.round(bounds.top / mapRes);
+
+ var metaX = Math.floor(pX / this.tileSize.w / this.params.metaTileSize.w) * this.tileSize.w * this.params.metaTileSize.w;
+ var metaY = Math.floor(pY / this.tileSize.h / this.params.metaTileSize.h) * this.tileSize.h * this.params.metaTileSize.h;
+
+ var components = [
+ "/",
+ this.params.map,
+ "/",
+ scale,
+ "/",
+ this.params.g.replace(/\s/g, '_'),
+ "/def/t",
+ metaY,
+ "/l",
+ metaX,
+ "/t",
+ pY,
+ "l",
+ pX,
+ ".",
+ this.extension
+ ];
+
+ var url = this.url;
+
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(components.join(''), url);
+ }
+ return url + components.join("");
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.KaMapCache"
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/v1_1_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS/v1.js
+ * @requires OpenLayers/Format/WFST/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1_1_0
+ * A WFS v1.1.0 protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.WFS.v1_1_0> constructor.
+ *
+ * Differences from the v1.0.0 protocol:
+ * - uses Filter Encoding 1.1.0 instead of 1.0.0
+ * - uses GML 3 instead of 2 if no format is provided
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.WFS.v1>
+ */
+OpenLayers.Protocol.WFS.v1_1_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS.v1_1_0
+ * A class for giving layers WFS v1.1.0 protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ * outputFormat - {String} Optional output format to use for WFS GetFeature
+ * requests. This can be any format advertized by the WFS's
+ * GetCapabilities response. If set, an appropriate readFormat also
+ * has to be provided, unless outputFormat is GML3, GML2 or JSON.
+ * readFormat - {<OpenLayers.Format>} An appropriate format parser if
+ * outputFormat is none of GML3, GML2 or JSON.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.WFS.v1.prototype.initialize.apply(this, arguments);
+ if (this.outputFormat && !this.readFormat) {
+ if (this.outputFormat.toLowerCase() == "gml2") {
+ this.readFormat = new OpenLayers.Format.GML.v2({
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ geometryName: this.geometryName
+ });
+ } else if (this.outputFormat.toLowerCase() == "json") {
+ this.readFormat = new OpenLayers.Format.GeoJSON();
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1_1_0"
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1_1_1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_1
+ * Read WMS Capabilities version 1.1.1.
+ *
+ * Note on <ScaleHint> parsing: If the 'min' attribute is set to "0", no
+ * maxScale will be set on the layer object. If the 'max' attribute is set to
+ * "Infinity", no minScale will be set. This makes it easy to create proper
+ * {<OpenLayers.Layer.WMS>} configurations directly from the layer object
+ * literals returned by this format, because no minScale/maxScale modifications
+ * need to be made.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_1 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_1, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.1.1",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_1
+ * Create a new parser for WMS capabilities version 1.1.1.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "SRS": function(node, obj) {
+ obj.srs[this.getChildValue(node)] = true;
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_1_WMSC
+ * Read WMS-C Capabilities version 1.1.1.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_1_1, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.1.1",
+
+ /**
+ * Property: profile
+ * {String} The specific profile
+ */
+ profile: "WMSC",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_1
+ * Create a new parser for WMS-C capabilities version 1.1.1.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "VendorSpecificCapabilities": function(node, obj) {
+ obj.vendorSpecific = {tileSets: []};
+ this.readChildNodes(node, obj.vendorSpecific);
+ },
+ "TileSet": function(node, vendorSpecific) {
+ var tileset = {srs: {}, bbox: {}, resolutions: []};
+ this.readChildNodes(node, tileset);
+ vendorSpecific.tileSets.push(tileset);
+ },
+ "Resolutions": function(node, tileset) {
+ var res = this.getChildValue(node).split(" ");
+ for (var i=0, len=res.length; i<len; i++) {
+ if (res[i] != "") {
+ tileset.resolutions.push(parseFloat(res[i]));
+ }
+ }
+ },
+ "Width": function(node, tileset) {
+ tileset.width = parseInt(this.getChildValue(node));
+ },
+ "Height": function(node, tileset) {
+ tileset.height = parseInt(this.getChildValue(node));
+ },
+ "Layers": function(node, tileset) {
+ tileset.layers = this.getChildValue(node);
+ },
+ "Styles": function(node, tileset) {
+ tileset.styles = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1_1_1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC"
+
+});
+/* ======================================================================
+ OpenLayers/Control/LayerSwitcher.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.LayerSwitcher
+ * The LayerSwitcher control displays a table of contents for the map. This
+ * allows the user interface to switch between BaseLasyers and to show or hide
+ * Overlays. By default the switcher is shown minimized on the right edge of
+ * the map, the user may expand it by clicking on the handle.
+ *
+ * To create the LayerSwitcher outside of the map, pass the Id of a html div
+ * as the first argument to the constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.LayerSwitcher = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layerStates
+ * {Array(Object)} Basically a copy of the "state" of the map's layers
+ * the last time the control was drawn. We have this in order to avoid
+ * unnecessarily redrawing the control.
+ */
+ layerStates: null,
+
+ // DOM Elements
+
+ /**
+ * Property: layersDiv
+ * {DOMElement}
+ */
+ layersDiv: null,
+
+ /**
+ * Property: baseLayersDiv
+ * {DOMElement}
+ */
+ baseLayersDiv: null,
+
+ /**
+ * Property: baseLayers
+ * {Array(Object)}
+ */
+ baseLayers: null,
+
+
+ /**
+ * Property: dataLbl
+ * {DOMElement}
+ */
+ dataLbl: null,
+
+ /**
+ * Property: dataLayersDiv
+ * {DOMElement}
+ */
+ dataLayersDiv: null,
+
+ /**
+ * Property: dataLayers
+ * {Array(Object)}
+ */
+ dataLayers: null,
+
+
+ /**
+ * Property: minimizeDiv
+ * {DOMElement}
+ */
+ minimizeDiv: null,
+
+ /**
+ * Property: maximizeDiv
+ * {DOMElement}
+ */
+ maximizeDiv: null,
+
+ /**
+ * APIProperty: ascending
+ * {Boolean}
+ */
+ ascending: true,
+
+ /**
+ * Constructor: OpenLayers.Control.LayerSwitcher
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.layerStates = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+
+ //clear out layers info and unregister their events
+ this.clearLayersArray("base");
+ this.clearLayersArray("data");
+
+ this.map.events.un({
+ buttonclick: this.onButtonClick,
+ addlayer: this.redraw,
+ changelayer: this.redraw,
+ removelayer: this.redraw,
+ changebaselayer: this.redraw,
+ scope: this
+ });
+ this.events.unregister("buttonclick", this, this.onButtonClick);
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Properties:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ this.map.events.on({
+ addlayer: this.redraw,
+ changelayer: this.redraw,
+ removelayer: this.redraw,
+ changebaselayer: this.redraw,
+ scope: this
+ });
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the
+ * switcher tabs.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this);
+
+ // create layout divs
+ this.loadContents();
+
+ // set mode to minimize
+ if(!this.outsideViewport) {
+ this.minimizeControl();
+ }
+
+ // populate div with current info
+ this.redraw();
+
+ return this.div;
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.minimizeDiv) {
+ this.minimizeControl();
+ } else if (button === this.maximizeDiv) {
+ this.maximizeControl();
+ } else if (button._layerSwitcher === this.id) {
+ if (button["for"]) {
+ button = document.getElementById(button["for"]);
+ }
+ if (!button.disabled) {
+ if (button.type == "radio") {
+ button.checked = true;
+ this.map.setBaseLayer(this.map.getLayer(button._layer));
+ } else {
+ button.checked = !button.checked;
+ this.updateMap();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clearLayersArray
+ * User specifies either "base" or "data". we then clear all the
+ * corresponding listeners, the div, and reinitialize a new array.
+ *
+ * Parameters:
+ * layersType - {String}
+ */
+ clearLayersArray: function(layersType) {
+ this[layersType + "LayersDiv"].innerHTML = "";
+ this[layersType + "Layers"] = [];
+ },
+
+
+ /**
+ * Method: checkRedraw
+ * Checks if the layer state has changed since the last redraw() call.
+ *
+ * Returns:
+ * {Boolean} The layer state changed since the last redraw() call.
+ */
+ checkRedraw: function() {
+ if ( !this.layerStates.length ||
+ (this.map.layers.length != this.layerStates.length) ) {
+ return true;
+ }
+
+ for (var i = 0, len = this.layerStates.length; i < len; i++) {
+ var layerState = this.layerStates[i];
+ var layer = this.map.layers[i];
+ if ( (layerState.name != layer.name) ||
+ (layerState.inRange != layer.inRange) ||
+ (layerState.id != layer.id) ||
+ (layerState.visibility != layer.visibility) ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Method: redraw
+ * Goes through and takes the current state of the Map and rebuilds the
+ * control to display that state. Groups base layers into a
+ * radio-button group and lists each data layer with a checkbox.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ redraw: function() {
+ //if the state hasn't changed since last redraw, no need
+ // to do anything. Just return the existing div.
+ if (!this.checkRedraw()) {
+ return this.div;
+ }
+
+ //clear out previous layers
+ this.clearLayersArray("base");
+ this.clearLayersArray("data");
+
+ var containsOverlays = false;
+ var containsBaseLayers = false;
+
+ // Save state -- for checking layer if the map state changed.
+ // We save this before redrawing, because in the process of redrawing
+ // we will trigger more visibility changes, and we want to not redraw
+ // and enter an infinite loop.
+ var len = this.map.layers.length;
+ this.layerStates = new Array(len);
+ for (var i=0; i <len; i++) {
+ var layer = this.map.layers[i];
+ this.layerStates[i] = {
+ 'name': layer.name,
+ 'visibility': layer.visibility,
+ 'inRange': layer.inRange,
+ 'id': layer.id
+ };
+ }
+
+ var layers = this.map.layers.slice();
+ if (!this.ascending) { layers.reverse(); }
+ for(var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ var baseLayer = layer.isBaseLayer;
+
+ if (layer.displayInLayerSwitcher) {
+
+ if (baseLayer) {
+ containsBaseLayers = true;
+ } else {
+ containsOverlays = true;
+ }
+
+ // only check a baselayer if it is *the* baselayer, check data
+ // layers if they are visible
+ var checked = (baseLayer) ? (layer == this.map.baseLayer)
+ : layer.getVisibility();
+
+ // create input element
+ var inputElem = document.createElement("input"),
+ // The input shall have an id attribute so we can use
+ // labels to interact with them.
+ inputId = OpenLayers.Util.createUniqueID(
+ this.id + "_input_"
+ );
+
+ inputElem.id = inputId;
+ inputElem.name = (baseLayer) ? this.id + "_baseLayers" : layer.name;
+ inputElem.type = (baseLayer) ? "radio" : "checkbox";
+ inputElem.value = layer.name;
+ inputElem.checked = checked;
+ inputElem.defaultChecked = checked;
+ inputElem.className = "olButton";
+ inputElem._layer = layer.id;
+ inputElem._layerSwitcher = this.id;
+
+ if (!baseLayer && !layer.inRange) {
+ inputElem.disabled = true;
+ }
+
+ // create span
+ var labelSpan = document.createElement("label");
+ // this isn't the DOM attribute 'for', but an arbitrary name we
+ // use to find the appropriate input element in <onButtonClick>
+ labelSpan["for"] = inputElem.id;
+ OpenLayers.Element.addClass(labelSpan, "labelSpan olButton");
+ labelSpan._layer = layer.id;
+ labelSpan._layerSwitcher = this.id;
+ if (!baseLayer && !layer.inRange) {
+ labelSpan.style.color = "gray";
+ }
+ labelSpan.innerHTML = layer.name;
+ labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
+ : "baseline";
+ // create line break
+ var br = document.createElement("br");
+
+
+ var groupArray = (baseLayer) ? this.baseLayers
+ : this.dataLayers;
+ groupArray.push({
+ 'layer': layer,
+ 'inputElem': inputElem,
+ 'labelSpan': labelSpan
+ });
+
+
+ var groupDiv = (baseLayer) ? this.baseLayersDiv
+ : this.dataLayersDiv;
+ groupDiv.appendChild(inputElem);
+ groupDiv.appendChild(labelSpan);
+ groupDiv.appendChild(br);
+ }
+ }
+
+ // if no overlays, dont display the overlay label
+ this.dataLbl.style.display = (containsOverlays) ? "" : "none";
+
+ // if no baselayers, dont display the baselayer label
+ this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateMap
+ * Cycles through the loaded data and base layer input arrays and makes
+ * the necessary calls to the Map object such that that the map's
+ * visual state corresponds to what the user has selected in
+ * the control.
+ */
+ updateMap: function() {
+
+ // set the newly selected base layer
+ for(var i=0, len=this.baseLayers.length; i<len; i++) {
+ var layerEntry = this.baseLayers[i];
+ if (layerEntry.inputElem.checked) {
+ this.map.setBaseLayer(layerEntry.layer, false);
+ }
+ }
+
+ // set the correct visibilities for the overlays
+ for(var i=0, len=this.dataLayers.length; i<len; i++) {
+ var layerEntry = this.dataLayers[i];
+ layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
+ }
+
+ },
+
+ /**
+ * Method: maximizeControl
+ * Set up the labels and divs for the control
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ maximizeControl: function(e) {
+
+ // set the div's width and height to empty values, so
+ // the div dimensions can be controlled by CSS
+ this.div.style.width = "";
+ this.div.style.height = "";
+
+ this.showControls(false);
+
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: minimizeControl
+ * Hide all the contents of the control, shrink the size,
+ * add the maximize icon
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ minimizeControl: function(e) {
+
+ // to minimize the control we set its div's width
+ // and height to 0px, we cannot just set "display"
+ // to "none" because it would hide the maximize
+ // div
+ this.div.style.width = "0px";
+ this.div.style.height = "0px";
+
+ this.showControls(true);
+
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: showControls
+ * Hide/Show all LayerSwitcher controls depending on whether we are
+ * minimized or not
+ *
+ * Parameters:
+ * minimize - {Boolean}
+ */
+ showControls: function(minimize) {
+
+ this.maximizeDiv.style.display = minimize ? "" : "none";
+ this.minimizeDiv.style.display = minimize ? "none" : "";
+
+ this.layersDiv.style.display = minimize ? "none" : "";
+ },
+
+ /**
+ * Method: loadContents
+ * Set up the labels and divs for the control
+ */
+ loadContents: function() {
+
+ // layers list div
+ this.layersDiv = document.createElement("div");
+ this.layersDiv.id = this.id + "_layersDiv";
+ OpenLayers.Element.addClass(this.layersDiv, "layersDiv");
+
+ this.baseLbl = document.createElement("div");
+ this.baseLbl.innerHTML = OpenLayers.i18n("Base Layer");
+ OpenLayers.Element.addClass(this.baseLbl, "baseLbl");
+
+ this.baseLayersDiv = document.createElement("div");
+ OpenLayers.Element.addClass(this.baseLayersDiv, "baseLayersDiv");
+
+ this.dataLbl = document.createElement("div");
+ this.dataLbl.innerHTML = OpenLayers.i18n("Overlays");
+ OpenLayers.Element.addClass(this.dataLbl, "dataLbl");
+
+ this.dataLayersDiv = document.createElement("div");
+ OpenLayers.Element.addClass(this.dataLayersDiv, "dataLayersDiv");
+
+ if (this.ascending) {
+ this.layersDiv.appendChild(this.baseLbl);
+ this.layersDiv.appendChild(this.baseLayersDiv);
+ this.layersDiv.appendChild(this.dataLbl);
+ this.layersDiv.appendChild(this.dataLayersDiv);
+ } else {
+ this.layersDiv.appendChild(this.dataLbl);
+ this.layersDiv.appendChild(this.dataLayersDiv);
+ this.layersDiv.appendChild(this.baseLbl);
+ this.layersDiv.appendChild(this.baseLayersDiv);
+ }
+
+ this.div.appendChild(this.layersDiv);
+
+ // maximize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
+ this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MaximizeDiv",
+ null,
+ null,
+ img,
+ "absolute");
+ OpenLayers.Element.addClass(this.maximizeDiv, "maximizeDiv olButton");
+ this.maximizeDiv.style.display = "none";
+
+ this.div.appendChild(this.maximizeDiv);
+
+ // minimize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
+ this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MinimizeDiv",
+ null,
+ null,
+ img,
+ "absolute");
+ OpenLayers.Element.addClass(this.minimizeDiv, "minimizeDiv olButton");
+ this.minimizeDiv.style.display = "none";
+
+ this.div.appendChild(this.minimizeDiv);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
+});
+/* ======================================================================
+ OpenLayers/Format/Atom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML/v3.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Atom
+ * Read/write Atom feeds. Create a new instance with the
+ * <OpenLayers.Format.AtomFeed> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Atom = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs. Properties
+ * of this object should not be set individually. Read-only. All
+ * XML subclasses should have their own namespaces object. Use
+ * <setNamespace> to add or set a namespace alias after construction.
+ */
+ namespaces: {
+ atom: "http://www.w3.org/2005/Atom",
+ georss: "http://www.georss.org/georss"
+ },
+
+ /**
+ * APIProperty: feedTitle
+ * {String} Atom feed elements require a title. Default is "untitled".
+ */
+ feedTitle: "untitled",
+
+ /**
+ * APIProperty: defaultEntryTitle
+ * {String} Atom entry elements require a title. In cases where one is
+ * not provided in the feature attributes, this will be used. Default
+ * is "untitled".
+ */
+ defaultEntryTitle: "untitled",
+
+ /**
+ * Property: gmlParse
+ * {Object} GML Format object for parsing features
+ * Non-API and only created if necessary
+ */
+ gmlParser: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+ * For GeoRSS the default is (y,x), therefore: false
+ */
+ xy: false,
+
+ /**
+ * Constructor: OpenLayers.Format.AtomEntry
+ * Create a new parser for Atom.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Return a list of features from an Atom feed or entry document.
+
+ * Parameters:
+ * doc - {Element} or {String}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+ return this.parseFeatures(doc);
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize or more feature nodes to Atom documents.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>} or Array({<OpenLayers.Feature.Vector>})
+ *
+ * Returns:
+ * {String} an Atom entry document if passed one feature node, or a feed
+ * document if passed an array of feature nodes.
+ */
+ write: function(features) {
+ var doc;
+ if (OpenLayers.Util.isArray(features)) {
+ doc = this.createElementNSPlus("atom:feed");
+ doc.appendChild(
+ this.createElementNSPlus("atom:title", {
+ value: this.feedTitle
+ })
+ );
+ for (var i=0, ii=features.length; i<ii; i++) {
+ doc.appendChild(this.buildEntryNode(features[i]));
+ }
+ }
+ else {
+ doc = this.buildEntryNode(features);
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [doc]);
+ },
+
+ /**
+ * Method: buildContentNode
+ *
+ * Parameters:
+ * content - {Object}
+ *
+ * Returns:
+ * {DOMElement} an Atom content node.
+ *
+ * TODO: types other than text.
+ */
+ buildContentNode: function(content) {
+ var node = this.createElementNSPlus("atom:content", {
+ attributes: {
+ type: content.type || null
+ }
+ });
+ if (content.src) {
+ node.setAttribute("src", content.src);
+ } else {
+ if (content.type == "text" || content.type == null) {
+ node.appendChild(
+ this.createTextNode(content.value)
+ );
+ } else if (content.type == "html") {
+ if (typeof content.value != "string") {
+ throw "HTML content must be in form of an escaped string";
+ }
+ node.appendChild(
+ this.createTextNode(content.value)
+ );
+ } else if (content.type == "xhtml") {
+ node.appendChild(content.value);
+ } else if (content.type == "xhtml" ||
+ content.type.match(/(\+|\/)xml$/)) {
+ node.appendChild(content.value);
+ }
+ else { // MUST be a valid Base64 encoding
+ node.appendChild(
+ this.createTextNode(content.value)
+ );
+ }
+ }
+ return node;
+ },
+
+ /**
+ * Method: buildEntryNode
+ * Build an Atom entry node from a feature object.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement} an Atom entry node.
+ *
+ * These entries are geared for publication using AtomPub.
+ *
+ * TODO: support extension elements
+ */
+ buildEntryNode: function(feature) {
+ var attrib = feature.attributes;
+ var atomAttrib = attrib.atom || {};
+ var entryNode = this.createElementNSPlus("atom:entry");
+
+ // atom:author
+ if (atomAttrib.authors) {
+ var authors = OpenLayers.Util.isArray(atomAttrib.authors) ?
+ atomAttrib.authors : [atomAttrib.authors];
+ for (var i=0, ii=authors.length; i<ii; i++) {
+ entryNode.appendChild(
+ this.buildPersonConstructNode(
+ "author", authors[i]
+ )
+ );
+ }
+ }
+
+ // atom:category
+ if (atomAttrib.categories) {
+ var categories = OpenLayers.Util.isArray(atomAttrib.categories) ?
+ atomAttrib.categories : [atomAttrib.categories];
+ var category;
+ for (var i=0, ii=categories.length; i<ii; i++) {
+ category = categories[i];
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:category", {
+ attributes: {
+ term: category.term,
+ scheme: category.scheme || null,
+ label: category.label || null
+ }
+ })
+ );
+ }
+ }
+
+ // atom:content
+ if (atomAttrib.content) {
+ entryNode.appendChild(this.buildContentNode(atomAttrib.content));
+ }
+
+ // atom:contributor
+ if (atomAttrib.contributors) {
+ var contributors = OpenLayers.Util.isArray(atomAttrib.contributors) ?
+ atomAttrib.contributors : [atomAttrib.contributors];
+ for (var i=0, ii=contributors.length; i<ii; i++) {
+ entryNode.appendChild(
+ this.buildPersonConstructNode(
+ "contributor",
+ contributors[i]
+ )
+ );
+ }
+ }
+
+ // atom:id
+ if (feature.fid) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:id", {
+ value: feature.fid
+ })
+ );
+ }
+
+ // atom:link
+ if (atomAttrib.links) {
+ var links = OpenLayers.Util.isArray(atomAttrib.links) ?
+ atomAttrib.links : [atomAttrib.links];
+ var link;
+ for (var i=0, ii=links.length; i<ii; i++) {
+ link = links[i];
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:link", {
+ attributes: {
+ href: link.href,
+ rel: link.rel || null,
+ type: link.type || null,
+ hreflang: link.hreflang || null,
+ title: link.title || null,
+ length: link.length || null
+ }
+ })
+ );
+ }
+ }
+
+ // atom:published
+ if (atomAttrib.published) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:published", {
+ value: atomAttrib.published
+ })
+ );
+ }
+
+ // atom:rights
+ if (atomAttrib.rights) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:rights", {
+ value: atomAttrib.rights
+ })
+ );
+ }
+
+ // atom:source not implemented
+
+ // atom:summary
+ if (atomAttrib.summary || attrib.description) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:summary", {
+ value: atomAttrib.summary || attrib.description
+ })
+ );
+ }
+
+ // atom:title
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:title", {
+ value: atomAttrib.title || attrib.title || this.defaultEntryTitle
+ })
+ );
+
+ // atom:updated
+ if (atomAttrib.updated) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:updated", {
+ value: atomAttrib.updated
+ })
+ );
+ }
+
+ // georss:where
+ if (feature.geometry) {
+ var whereNode = this.createElementNSPlus("georss:where");
+ whereNode.appendChild(
+ this.buildGeometryNode(feature.geometry)
+ );
+ entryNode.appendChild(whereNode);
+ }
+
+ return entryNode;
+ },
+
+ /**
+ * Method: initGmlParser
+ * Creates a GML parser.
+ */
+ initGmlParser: function() {
+ this.gmlParser = new OpenLayers.Format.GML.v3({
+ xy: this.xy,
+ featureNS: "http://example.com#feature",
+ internalProjection: this.internalProjection,
+ externalProjection: this.externalProjection
+ });
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * builds a GeoRSS node with a given geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} A gml node.
+ */
+ buildGeometryNode: function(geometry) {
+ if (!this.gmlParser) {
+ this.initGmlParser();
+ }
+ var node = this.gmlParser.writeNode("feature:_geometry", geometry);
+ return node.firstChild;
+ },
+
+ /**
+ * Method: buildPersonConstructNode
+ *
+ * Parameters:
+ * name - {String}
+ * value - {Object}
+ *
+ * Returns:
+ * {DOMElement} an Atom person construct node.
+ *
+ * Example:
+ * >>> buildPersonConstructNode("author", {name: "John Smith"})
+ * {<author><name>John Smith</name></author>}
+ *
+ * TODO: how to specify extension elements? Add to the oNames array?
+ */
+ buildPersonConstructNode: function(name, value) {
+ var oNames = ["uri", "email"];
+ var personNode = this.createElementNSPlus("atom:" + name);
+ personNode.appendChild(
+ this.createElementNSPlus("atom:name", {
+ value: value.name
+ })
+ );
+ for (var i=0, ii=oNames.length; i<ii; i++) {
+ if (value[oNames[i]]) {
+ personNode.appendChild(
+ this.createElementNSPlus("atom:" + oNames[i], {
+ value: value[oNames[i]]
+ })
+ );
+ }
+ }
+ return personNode;
+ },
+
+ /**
+ * Method: getFirstChildValue
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * nsuri - {String} Child node namespace uri ("*" for any).
+ * name - {String} Child node name.
+ * def - {String} Optional string default to return if no child found.
+ *
+ * Returns:
+ * {String} The value of the first child with the given tag name. Returns
+ * default value or empty string if none found.
+ */
+ getFirstChildValue: function(node, nsuri, name, def) {
+ var value;
+ var nodes = this.getElementsByTagNameNS(node, nsuri, name);
+ if (nodes && nodes.length > 0) {
+ value = this.getChildValue(nodes[0], def);
+ } else {
+ value = def;
+ }
+ return value;
+ },
+
+ /**
+ * Method: parseFeature
+ * Parse feature from an Atom entry node..
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ parseFeature: function(node) {
+ var atomAttrib = {};
+ var value = null;
+ var nodes = null;
+ var attval = null;
+ var atomns = this.namespaces.atom;
+
+ // atomAuthor*
+ this.parsePersonConstructs(node, "author", atomAttrib);
+
+ // atomCategory*
+ nodes = this.getElementsByTagNameNS(node, atomns, "category");
+ if (nodes.length > 0) {
+ atomAttrib.categories = [];
+ }
+ for (var i=0, ii=nodes.length; i<ii; i++) {
+ value = {};
+ value.term = nodes[i].getAttribute("term");
+ attval = nodes[i].getAttribute("scheme");
+ if (attval) { value.scheme = attval; }
+ attval = nodes[i].getAttribute("label");
+ if (attval) { value.label = attval; }
+ atomAttrib.categories.push(value);
+ }
+
+ // atomContent?
+ nodes = this.getElementsByTagNameNS(node, atomns, "content");
+ if (nodes.length > 0) {
+ value = {};
+ attval = nodes[0].getAttribute("type");
+ if (attval) {
+ value.type = attval;
+ }
+ attval = nodes[0].getAttribute("src");
+ if (attval) {
+ value.src = attval;
+ } else {
+ if (value.type == "text" ||
+ value.type == "html" ||
+ value.type == null ) {
+ value.value = this.getFirstChildValue(
+ node,
+ atomns,
+ "content",
+ null
+ );
+ } else if (value.type == "xhtml" ||
+ value.type.match(/(\+|\/)xml$/)) {
+ value.value = this.getChildEl(nodes[0]);
+ } else { // MUST be base64 encoded
+ value.value = this.getFirstChildValue(
+ node,
+ atomns,
+ "content",
+ null
+ );
+ }
+ atomAttrib.content = value;
+ }
+ }
+
+ // atomContributor*
+ this.parsePersonConstructs(node, "contributor", atomAttrib);
+
+ // atomId
+ atomAttrib.id = this.getFirstChildValue(node, atomns, "id", null);
+
+ // atomLink*
+ nodes = this.getElementsByTagNameNS(node, atomns, "link");
+ if (nodes.length > 0) {
+ atomAttrib.links = new Array(nodes.length);
+ }
+ var oAtts = ["rel", "type", "hreflang", "title", "length"];
+ for (var i=0, ii=nodes.length; i<ii; i++) {
+ value = {};
+ value.href = nodes[i].getAttribute("href");
+ for (var j=0, jj=oAtts.length; j<jj; j++) {
+ attval = nodes[i].getAttribute(oAtts[j]);
+ if (attval) {
+ value[oAtts[j]] = attval;
+ }
+ }
+ atomAttrib.links[i] = value;
+ }
+
+ // atomPublished?
+ value = this.getFirstChildValue(node, atomns, "published", null);
+ if (value) {
+ atomAttrib.published = value;
+ }
+
+ // atomRights?
+ value = this.getFirstChildValue(node, atomns, "rights", null);
+ if (value) {
+ atomAttrib.rights = value;
+ }
+
+ // atomSource? -- not implemented
+
+ // atomSummary?
+ value = this.getFirstChildValue(node, atomns, "summary", null);
+ if (value) {
+ atomAttrib.summary = value;
+ }
+
+ // atomTitle
+ atomAttrib.title = this.getFirstChildValue(
+ node, atomns, "title", null
+ );
+
+ // atomUpdated
+ atomAttrib.updated = this.getFirstChildValue(
+ node, atomns, "updated", null
+ );
+
+ var featureAttrib = {
+ title: atomAttrib.title,
+ description: atomAttrib.summary,
+ atom: atomAttrib
+ };
+ var geometry = this.parseLocations(node)[0];
+ var feature = new OpenLayers.Feature.Vector(geometry, featureAttrib);
+ feature.fid = atomAttrib.id;
+ return feature;
+ },
+
+ /**
+ * Method: parseFeatures
+ * Return features from an Atom entry or feed.
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ parseFeatures: function(node) {
+ var features = [];
+ var entries = this.getElementsByTagNameNS(
+ node, this.namespaces.atom, "entry"
+ );
+ if (entries.length == 0) {
+ entries = [node];
+ }
+ for (var i=0, ii=entries.length; i<ii; i++) {
+ features.push(this.parseFeature(entries[i]));
+ }
+ return features;
+ },
+
+ /**
+ * Method: parseLocations
+ * Parse the locations from an Atom entry or feed.
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ *
+ * Returns:
+ * Array({<OpenLayers.Geometry>})
+ */
+ parseLocations: function(node) {
+ var georssns = this.namespaces.georss;
+
+ var locations = {components: []};
+ var where = this.getElementsByTagNameNS(node, georssns, "where");
+ if (where && where.length > 0) {
+ if (!this.gmlParser) {
+ this.initGmlParser();
+ }
+ for (var i=0, ii=where.length; i<ii; i++) {
+ this.gmlParser.readChildNodes(where[i], locations);
+ }
+ }
+
+ var components = locations.components;
+ var point = this.getElementsByTagNameNS(node, georssns, "point");
+ if (point && point.length > 0) {
+ for (var i=0, ii=point.length; i<ii; i++) {
+ var xy = OpenLayers.String.trim(
+ point[i].firstChild.nodeValue
+ ).split(/\s+/);
+ if (xy.length !=2) {
+ xy = OpenLayers.String.trim(
+ point[i].firstChild.nodeValue
+ ).split(/\s*,\s*/);
+ }
+ components.push(new OpenLayers.Geometry.Point(xy[1], xy[0]));
+ }
+ }
+
+ var line = this.getElementsByTagNameNS(node, georssns, "line");
+ if (line && line.length > 0) {
+ var coords;
+ var p;
+ var points;
+ for (var i=0, ii=line.length; i<ii; i++) {
+ coords = OpenLayers.String.trim(
+ line[i].firstChild.nodeValue
+ ).split(/\s+/);
+ points = [];
+ for (var j=0, jj=coords.length; j<jj; j+=2) {
+ p = new OpenLayers.Geometry.Point(coords[j+1], coords[j]);
+ points.push(p);
+ }
+ components.push(
+ new OpenLayers.Geometry.LineString(points)
+ );
+ }
+ }
+
+ var polygon = this.getElementsByTagNameNS(node, georssns, "polygon");
+ if (polygon && polygon.length > 0) {
+ var coords;
+ var p;
+ var points;
+ for (var i=0, ii=polygon.length; i<ii; i++) {
+ coords = OpenLayers.String.trim(
+ polygon[i].firstChild.nodeValue
+ ).split(/\s+/);
+ points = [];
+ for (var j=0, jj=coords.length; j<jj; j+=2) {
+ p = new OpenLayers.Geometry.Point(coords[j+1], coords[j]);
+ points.push(p);
+ }
+ components.push(
+ new OpenLayers.Geometry.Polygon(
+ [new OpenLayers.Geometry.LinearRing(points)]
+ )
+ );
+ }
+ }
+
+ if (this.internalProjection && this.externalProjection) {
+ for (var i=0, ii=components.length; i<ii; i++) {
+ if (components[i]) {
+ components[i].transform(
+ this.externalProjection,
+ this.internalProjection
+ );
+ }
+ }
+ }
+
+ return components;
+ },
+
+ /**
+ * Method: parsePersonConstruct
+ * Parse Atom person constructs from an Atom entry node.
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ * name - {String} Construcy name ("author" or "contributor")
+ * data = {Object} Object in which to put parsed persons.
+ *
+ * Returns:
+ * An {Object}.
+ */
+ parsePersonConstructs: function(node, name, data) {
+ var persons = [];
+ var atomns = this.namespaces.atom;
+ var nodes = this.getElementsByTagNameNS(node, atomns, name);
+ var oAtts = ["uri", "email"];
+ for (var i=0, ii=nodes.length; i<ii; i++) {
+ var value = {};
+ value.name = this.getFirstChildValue(
+ nodes[i],
+ atomns,
+ "name",
+ null
+ );
+ for (var j=0, jj=oAtts.length; j<jj; j++) {
+ var attval = this.getFirstChildValue(
+ nodes[i],
+ atomns,
+ oAtts[j],
+ null);
+ if (attval) {
+ value[oAtts[j]] = attval;
+ }
+ }
+ persons.push(value);
+ }
+ if (persons.length > 0) {
+ data[name + "s"] = persons;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Atom"
+});
+/* ======================================================================
+ OpenLayers/Control/KeyboardDefaults.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Control.KeyboardDefaults
+ * The KeyboardDefaults control adds panning and zooming functions, controlled
+ * with the keyboard. By default arrow keys pan, +/- keys zoom & Page Up/Page
+ * Down/Home/End scroll by three quarters of a page.
+ *
+ * This control has no visible appearance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: slideFactor
+ * Pixels to slide by.
+ */
+ slideFactor: 75,
+
+ /**
+ * APIProperty: observeElement
+ * {DOMelement|String} The DOM element to handle keys for. You
+ * can use the map div here, to have the navigation keys
+ * work when the map div has the focus. If undefined the
+ * document is used.
+ */
+ observeElement: null,
+
+ /**
+ * Constructor: OpenLayers.Control.KeyboardDefaults
+ */
+
+ /**
+ * Method: draw
+ * Create handler.
+ */
+ draw: function() {
+ var observeElement = this.observeElement || document;
+ this.handler = new OpenLayers.Handler.Keyboard( this,
+ {"keydown": this.defaultKeyPress},
+ {observeElement: observeElement}
+ );
+ },
+
+ /**
+ * Method: defaultKeyPress
+ * When handling the key event, we only use evt.keyCode. This holds
+ * some drawbacks, though we get around them below. When interpretting
+ * the keycodes below (including the comments associated with them),
+ * consult the URL below. For instance, the Safari browser returns
+ * "IE keycodes", and so is supported by any keycode labeled "IE".
+ *
+ * Very informative URL:
+ * http://unixpapa.com/js/key.html
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultKeyPress: function (evt) {
+ var size, handled = true;
+
+ var target = OpenLayers.Event.element(evt);
+ if (target &&
+ (target.tagName == 'INPUT' ||
+ target.tagName == 'TEXTAREA' ||
+ target.tagName == 'SELECT')) {
+ return;
+ }
+
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_LEFT:
+ this.map.pan(-this.slideFactor, 0);
+ break;
+ case OpenLayers.Event.KEY_RIGHT:
+ this.map.pan(this.slideFactor, 0);
+ break;
+ case OpenLayers.Event.KEY_UP:
+ this.map.pan(0, -this.slideFactor);
+ break;
+ case OpenLayers.Event.KEY_DOWN:
+ this.map.pan(0, this.slideFactor);
+ break;
+
+ case 33: // Page Up. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(0, -0.75*size.h);
+ break;
+ case 34: // Page Down. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(0, 0.75*size.h);
+ break;
+ case 35: // End. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(0.75*size.w, 0);
+ break;
+ case 36: // Home. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(-0.75*size.w, 0);
+ break;
+
+ case 43: // +/= (ASCII), keypad + (ASCII, Opera)
+ case 61: // +/= (Mozilla, Opera, some ASCII)
+ case 187: // +/= (IE)
+ case 107: // keypad + (IE, Mozilla)
+ this.map.zoomIn();
+ break;
+ case 45: // -/_ (ASCII, Opera), keypad - (ASCII, Opera)
+ case 109: // -/_ (Mozilla), keypad - (Mozilla, IE)
+ case 189: // -/_ (IE)
+ case 95: // -/_ (some ASCII)
+ this.map.zoomOut();
+ break;
+ default:
+ handled = false;
+ }
+ if (handled) {
+ // prevent browser default not to move the page
+ // when moving the page with the keyboard
+ OpenLayers.Event.stop(evt);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.KeyboardDefaults"
+});
+/* ======================================================================
+ OpenLayers/Format/WMTSCapabilities/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMTSCapabilities.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMTSCapabilities.v1_0_0
+ * Read WMTS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMTSCapabilities>
+ */
+OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.OWSCommon.v1_1_0, {
+
+ /**
+ * Property: version
+ * {String} The parser version ("1.0.0").
+ */
+ version: "1.0.0",
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ wmts: "http://www.opengis.net/wmts/1.0",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: yx
+ * {Object} Members in the yx object are used to determine if a CRS URN
+ * corresponds to a CRS with y,x axis order. Member names are CRS URNs
+ * and values are boolean. Defaults come from the
+ * <OpenLayers.Format.WMTSCapabilities> prototype.
+ */
+ yx: null,
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default namespace alias for creating element nodes.
+ */
+ defaultPrefix: "wmts",
+
+ /**
+ * Constructor: OpenLayers.Format.WMTSCapabilities.v1_0_0
+ * Create a new parser for WMTS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.options = options;
+ var yx = OpenLayers.Util.extend(
+ {}, OpenLayers.Format.WMTSCapabilities.prototype.yx
+ );
+ this.yx = OpenLayers.Util.extend(yx, this.yx);
+ },
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return info about the WMTS.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the SOS service.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ capabilities.version = this.version;
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wmts": {
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Contents": function(node, obj) {
+ obj.contents = {};
+ obj.contents.layers = [];
+ obj.contents.tileMatrixSets = {};
+ this.readChildNodes(node, obj.contents);
+ },
+ "Layer": function(node, obj) {
+ var layer = {
+ styles: [],
+ formats: [],
+ dimensions: [],
+ tileMatrixSetLinks: []
+ };
+ layer.layers = [];
+ this.readChildNodes(node, layer);
+ obj.layers.push(layer);
+ },
+ "Style": function(node, obj) {
+ var style = {};
+ style.isDefault = (node.getAttribute("isDefault") === "true");
+ this.readChildNodes(node, style);
+ obj.styles.push(style);
+ },
+ "Format": function(node, obj) {
+ obj.formats.push(this.getChildValue(node));
+ },
+ "TileMatrixSetLink": function(node, obj) {
+ var tileMatrixSetLink = {};
+ this.readChildNodes(node, tileMatrixSetLink);
+ obj.tileMatrixSetLinks.push(tileMatrixSetLink);
+ },
+ "TileMatrixSet": function(node, obj) {
+ // node could be child of wmts:Contents or wmts:TileMatrixSetLink
+ // duck type wmts:Contents by looking for layers
+ if (obj.layers) {
+ // TileMatrixSet as object type in schema
+ var tileMatrixSet = {
+ matrixIds: []
+ };
+ this.readChildNodes(node, tileMatrixSet);
+ obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet;
+ } else {
+ // TileMatrixSet as string type in schema
+ obj.tileMatrixSet = this.getChildValue(node);
+ }
+ },
+ "TileMatrix": function(node, obj) {
+ var tileMatrix = {
+ supportedCRS: obj.supportedCRS
+ };
+ this.readChildNodes(node, tileMatrix);
+ obj.matrixIds.push(tileMatrix);
+ },
+ "ScaleDenominator": function(node, obj) {
+ obj.scaleDenominator = parseFloat(this.getChildValue(node));
+ },
+ "TopLeftCorner": function(node, obj) {
+ var topLeftCorner = this.getChildValue(node);
+ var coords = topLeftCorner.split(" ");
+ // decide on axis order for the given CRS
+ var yx;
+ if (obj.supportedCRS) {
+ // extract out version from URN
+ var crs = obj.supportedCRS.replace(
+ /urn:ogc:def:crs:(\w+):.+:(\w+)$/,
+ "urn:ogc:def:crs:$1::$2"
+ );
+ yx = !!this.yx[crs];
+ }
+ if (yx) {
+ obj.topLeftCorner = new OpenLayers.LonLat(
+ coords[1], coords[0]
+ );
+ } else {
+ obj.topLeftCorner = new OpenLayers.LonLat(
+ coords[0], coords[1]
+ );
+ }
+ },
+ "TileWidth": function(node, obj) {
+ obj.tileWidth = parseInt(this.getChildValue(node));
+ },
+ "TileHeight": function(node, obj) {
+ obj.tileHeight = parseInt(this.getChildValue(node));
+ },
+ "MatrixWidth": function(node, obj) {
+ obj.matrixWidth = parseInt(this.getChildValue(node));
+ },
+ "MatrixHeight": function(node, obj) {
+ obj.matrixHeight = parseInt(this.getChildValue(node));
+ },
+ "ResourceURL": function(node, obj) {
+ obj.resourceUrl = obj.resourceUrl || {};
+ var resourceType = node.getAttribute("resourceType");
+ if (!obj.resourceUrls) {
+ obj.resourceUrls = [];
+ }
+ var resourceUrl = obj.resourceUrl[resourceType] = {
+ format: node.getAttribute("format"),
+ template: node.getAttribute("template"),
+ resourceType: resourceType
+ };
+ obj.resourceUrls.push(resourceUrl);
+ },
+ // not used for now, can be added in the future though
+ /*"Themes": function(node, obj) {
+ obj.themes = [];
+ this.readChildNodes(node, obj.themes);
+ },
+ "Theme": function(node, obj) {
+ var theme = {};
+ this.readChildNodes(node, theme);
+ obj.push(theme);
+ },*/
+ "WSDL": function(node, obj) {
+ obj.wsdl = {};
+ obj.wsdl.href = node.getAttribute("xlink:href");
+ // TODO: other attributes of <WSDL> element
+ },
+ "ServiceMetadataURL": function(node, obj) {
+ obj.serviceMetadataUrl = {};
+ obj.serviceMetadataUrl.href = node.getAttribute("xlink:href");
+ // TODO: other attributes of <ServiceMetadataURL> element
+ },
+ "LegendURL": function(node, obj) {
+ obj.legend = {};
+ obj.legend.href = node.getAttribute("xlink:href");
+ obj.legend.format = node.getAttribute("format");
+ },
+ "Dimension": function(node, obj) {
+ var dimension = {values: []};
+ this.readChildNodes(node, dimension);
+ obj.dimensions.push(dimension);
+ },
+ "Default": function(node, obj) {
+ obj["default"] = this.getChildValue(node);
+ },
+ "Value": function(node, obj) {
+ obj.values.push(this.getChildValue(node));
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMTSCapabilities.v1_0_0"
+
+});
diff --git a/misc/openlayers/OpenLayers.js b/misc/openlayers/OpenLayers.js
new file mode 100644
index 0000000..669778d
--- /dev/null
+++ b/misc/openlayers/OpenLayers.js
@@ -0,0 +1,1443 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+var OpenLayers={VERSION_NUMBER:"Release 2.13.1",singleFile:!0,_getScriptLocation:function(){for(var a=/(^|(.*?\/))(OpenLayers[^\/]*?\.js)(\?|$)/,b=document.getElementsByTagName("script"),c,d="",e=0,f=b.length;e<f;e++)if(c=b[e].getAttribute("src"))if(c=c.match(a)){d=c[1];break}return function(){return d}}(),ImgPath:""};OpenLayers.Class=function(){var a=arguments.length,b=arguments[0],c=arguments[a-1],d="function"==typeof c.initialize?c.initialize:function(){b.prototype.initialize.apply(this,arguments)};1<a?(a=[d,b].concat(Array.prototype.slice.call(arguments).slice(1,a-1),c),OpenLayers.inherit.apply(null,a)):d.prototype=c;return d};
+OpenLayers.inherit=function(a,b){var c=function(){};c.prototype=b.prototype;a.prototype=new c;var d,e,c=2;for(d=arguments.length;c<d;c++)e=arguments[c],"function"===typeof e&&(e=e.prototype),OpenLayers.Util.extend(a.prototype,e)};OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.extend=function(a,b){a=a||{};if(b){for(var c in b){var d=b[c];void 0!==d&&(a[c]=d)}"function"==typeof window.Event&&b instanceof window.Event||(!b.hasOwnProperty||!b.hasOwnProperty("toString"))||(a.toString=b.toString)}return a};OpenLayers.String={startsWith:function(a,b){return 0==a.indexOf(b)},contains:function(a,b){return-1!=a.indexOf(b)},trim:function(a){return a.replace(/^\s\s*/,"").replace(/\s\s*$/,"")},camelize:function(a){a=a.split("-");for(var b=a[0],c=1,d=a.length;c<d;c++)var e=a[c],b=b+(e.charAt(0).toUpperCase()+e.substring(1));return b},format:function(a,b,c){b||(b=window);return a.replace(OpenLayers.String.tokenRegEx,function(a,e){for(var f,g=e.split(/\.+/),h=0;h<g.length;h++){0==h&&(f=b);if(void 0===f)break;
+f=f[g[h]]}"function"==typeof f&&(f=c?f.apply(null,c):f());return"undefined"==typeof f?"undefined":f})},tokenRegEx:/\$\{([\w.]+?)\}/g,numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(a){return OpenLayers.String.numberRegEx.test(a)},numericIf:function(a,b){var c=a;!0===b&&(null!=a&&a.replace)&&(a=a.replace(/^\s*|\s*$/g,""));return OpenLayers.String.isNumeric(a)?parseFloat(a):c}};
+OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(a,b){var c=0;0<b&&(c=parseFloat(a.toPrecision(b)));return c},format:function(a,b,c,d){b="undefined"!=typeof b?b:0;c="undefined"!=typeof c?c:OpenLayers.Number.thousandsSeparator;d="undefined"!=typeof d?d:OpenLayers.Number.decimalSeparator;null!=b&&(a=parseFloat(a.toFixed(b)));var e=a.toString().split(".");1==e.length&&null==b&&(b=0);a=e[0];if(c)for(var f=/(-?[0-9]+)([0-9]{3})/;f.test(a);)a=a.replace(f,"$1"+c+"$2");
+0==b?b=a:(c=1<e.length?e[1]:"0",null!=b&&(c+=Array(b-c.length+1).join("0")),b=a+d+c);return b},zeroPad:function(a,b,c){for(a=a.toString(c||10);a.length<b;)a="0"+a;return a}};
+OpenLayers.Function={bind:function(a,b){var c=Array.prototype.slice.apply(arguments,[2]);return function(){var d=c.concat(Array.prototype.slice.apply(arguments,[0]));return a.apply(b,d)}},bindAsEventListener:function(a,b){return function(c){return a.call(b,c||window.event)}},False:function(){return!1},True:function(){return!0},Void:function(){}};
+OpenLayers.Array={filter:function(a,b,c){var d=[];if(Array.prototype.filter)d=a.filter(b,c);else{var e=a.length;if("function"!=typeof b)throw new TypeError;for(var f=0;f<e;f++)if(f in a){var g=a[f];b.call(c,g,f,a)&&d.push(g)}}return d}};OpenLayers.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,centerLonLat:null,initialize:function(a,b,c,d){OpenLayers.Util.isArray(a)&&(d=a[3],c=a[2],b=a[1],a=a[0]);null!=a&&(this.left=OpenLayers.Util.toFloat(a));null!=b&&(this.bottom=OpenLayers.Util.toFloat(b));null!=c&&(this.right=OpenLayers.Util.toFloat(c));null!=d&&(this.top=OpenLayers.Util.toFloat(d))},clone:function(){return new OpenLayers.Bounds(this.left,this.bottom,this.right,this.top)},equals:function(a){var b=!1;null!=
+a&&(b=this.left==a.left&&this.right==a.right&&this.top==a.top&&this.bottom==a.bottom);return b},toString:function(){return[this.left,this.bottom,this.right,this.top].join()},toArray:function(a){return!0===a?[this.bottom,this.left,this.top,this.right]:[this.left,this.bottom,this.right,this.top]},toBBOX:function(a,b){null==a&&(a=6);var c=Math.pow(10,a),d=Math.round(this.left*c)/c,e=Math.round(this.bottom*c)/c,f=Math.round(this.right*c)/c,c=Math.round(this.top*c)/c;return!0===b?e+","+d+","+c+","+f:d+
+","+e+","+f+","+c},toGeometry:function(){return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(this.left,this.bottom),new OpenLayers.Geometry.Point(this.right,this.bottom),new OpenLayers.Geometry.Point(this.right,this.top),new OpenLayers.Geometry.Point(this.left,this.top)])])},getWidth:function(){return this.right-this.left},getHeight:function(){return this.top-this.bottom},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight())},
+getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2)},getCenterLonLat:function(){this.centerLonLat||(this.centerLonLat=new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2));return this.centerLonLat},scale:function(a,b){null==b&&(b=this.getCenterLonLat());var c,d;"OpenLayers.LonLat"==b.CLASS_NAME?(c=b.lon,d=b.lat):(c=b.x,d=b.y);return new OpenLayers.Bounds((this.left-c)*a+c,(this.bottom-d)*a+d,(this.right-c)*a+c,(this.top-d)*a+
+d)},add:function(a,b){if(null==a||null==b)throw new TypeError("Bounds.add cannot receive null values");return new OpenLayers.Bounds(this.left+a,this.bottom+b,this.right+a,this.top+b)},extend:function(a){if(a)switch(a.CLASS_NAME){case "OpenLayers.LonLat":this.extendXY(a.lon,a.lat);break;case "OpenLayers.Geometry.Point":this.extendXY(a.x,a.y);break;case "OpenLayers.Bounds":this.centerLonLat=null;if(null==this.left||a.left<this.left)this.left=a.left;if(null==this.bottom||a.bottom<this.bottom)this.bottom=
+a.bottom;if(null==this.right||a.right>this.right)this.right=a.right;if(null==this.top||a.top>this.top)this.top=a.top}},extendXY:function(a,b){this.centerLonLat=null;if(null==this.left||a<this.left)this.left=a;if(null==this.bottom||b<this.bottom)this.bottom=b;if(null==this.right||a>this.right)this.right=a;if(null==this.top||b>this.top)this.top=b},containsLonLat:function(a,b){"boolean"===typeof b&&(b={inclusive:b});b=b||{};var c=this.contains(a.lon,a.lat,b.inclusive),d=b.worldBounds;d&&!c&&(c=d.getWidth(),
+d=Math.round((a.lon-(d.left+d.right)/2)/c),c=this.containsLonLat({lon:a.lon-d*c,lat:a.lat},{inclusive:b.inclusive}));return c},containsPixel:function(a,b){return this.contains(a.x,a.y,b)},contains:function(a,b,c){null==c&&(c=!0);if(null==a||null==b)return!1;a=OpenLayers.Util.toFloat(a);b=OpenLayers.Util.toFloat(b);var d=!1;return d=c?a>=this.left&&a<=this.right&&b>=this.bottom&&b<=this.top:a>this.left&&a<this.right&&b>this.bottom&&b<this.top},intersectsBounds:function(a,b){"boolean"===typeof b&&(b=
+{inclusive:b});b=b||{};if(b.worldBounds){var c=this.wrapDateLine(b.worldBounds);a=a.wrapDateLine(b.worldBounds)}else c=this;null==b.inclusive&&(b.inclusive=!0);var d=!1,e=c.left==a.right||c.right==a.left||c.top==a.bottom||c.bottom==a.top;if(b.inclusive||!e)var d=a.top>=c.bottom&&a.top<=c.top||c.top>a.bottom&&c.top<a.top,e=a.left>=c.left&&a.left<=c.right||c.left>=a.left&&c.left<=a.right,f=a.right>=c.left&&a.right<=c.right||c.right>=a.left&&c.right<=a.right,d=(a.bottom>=c.bottom&&a.bottom<=c.top||c.bottom>=
+a.bottom&&c.bottom<=a.top||d)&&(e||f);if(b.worldBounds&&!d){var g=b.worldBounds,e=g.getWidth(),f=!g.containsBounds(c),g=!g.containsBounds(a);f&&!g?(a=a.add(-e,0),d=c.intersectsBounds(a,{inclusive:b.inclusive})):g&&!f&&(c=c.add(-e,0),d=a.intersectsBounds(c,{inclusive:b.inclusive}))}return d},containsBounds:function(a,b,c){null==b&&(b=!1);null==c&&(c=!0);var d=this.contains(a.left,a.bottom,c),e=this.contains(a.right,a.bottom,c),f=this.contains(a.left,a.top,c);a=this.contains(a.right,a.top,c);return b?
+d||e||f||a:d&&e&&f&&a},determineQuadrant:function(a){var b="",c=this.getCenterLonLat(),b=b+(a.lat<c.lat?"b":"t");return b+=a.lon<c.lon?"l":"r"},transform:function(a,b){this.centerLonLat=null;var c=OpenLayers.Projection.transform({x:this.left,y:this.bottom},a,b),d=OpenLayers.Projection.transform({x:this.right,y:this.bottom},a,b),e=OpenLayers.Projection.transform({x:this.left,y:this.top},a,b),f=OpenLayers.Projection.transform({x:this.right,y:this.top},a,b);this.left=Math.min(c.x,e.x);this.bottom=Math.min(c.y,
+d.y);this.right=Math.max(d.x,f.x);this.top=Math.max(e.y,f.y);return this},wrapDateLine:function(a,b){b=b||{};var c=b.leftTolerance||0,d=b.rightTolerance||0,e=this.clone();if(a){for(var f=a.getWidth();e.left<a.left&&e.right-d<=a.left;)e=e.add(f,0);for(;e.left+c>=a.right&&e.right>a.right;)e=e.add(-f,0);c=e.left+c;c<a.right&&(c>a.left&&e.right-d>a.right)&&(e=e.add(-f,0))}return e},CLASS_NAME:"OpenLayers.Bounds"});
+OpenLayers.Bounds.fromString=function(a,b){var c=a.split(",");return OpenLayers.Bounds.fromArray(c,b)};OpenLayers.Bounds.fromArray=function(a,b){return!0===b?new OpenLayers.Bounds(a[1],a[0],a[3],a[2]):new OpenLayers.Bounds(a[0],a[1],a[2],a[3])};OpenLayers.Bounds.fromSize=function(a){return new OpenLayers.Bounds(0,a.h,a.w,0)};OpenLayers.Bounds.oppositeQuadrant=function(a){var b;b=""+("t"==a.charAt(0)?"b":"t");return b+="l"==a.charAt(1)?"r":"l"};OpenLayers.Element={visible:function(a){return"none"!=OpenLayers.Util.getElement(a).style.display},toggle:function(){for(var a=0,b=arguments.length;a<b;a++){var c=OpenLayers.Util.getElement(arguments[a]),d=OpenLayers.Element.visible(c)?"none":"";c.style.display=d}},remove:function(a){a=OpenLayers.Util.getElement(a);a.parentNode.removeChild(a)},getHeight:function(a){a=OpenLayers.Util.getElement(a);return a.offsetHeight},hasClass:function(a,b){var c=a.className;return!!c&&RegExp("(^|\\s)"+b+"(\\s|$)").test(c)},
+addClass:function(a,b){OpenLayers.Element.hasClass(a,b)||(a.className+=(a.className?" ":"")+b);return a},removeClass:function(a,b){var c=a.className;c&&(a.className=OpenLayers.String.trim(c.replace(RegExp("(^|\\s+)"+b+"(\\s+|$)")," ")));return a},toggleClass:function(a,b){OpenLayers.Element.hasClass(a,b)?OpenLayers.Element.removeClass(a,b):OpenLayers.Element.addClass(a,b);return a},getStyle:function(a,b){a=OpenLayers.Util.getElement(a);var c=null;if(a&&a.style){c=a.style[OpenLayers.String.camelize(b)];
+c||(document.defaultView&&document.defaultView.getComputedStyle?c=(c=document.defaultView.getComputedStyle(a,null))?c.getPropertyValue(b):null:a.currentStyle&&(c=a.currentStyle[OpenLayers.String.camelize(b)]));var d=["left","top","right","bottom"];window.opera&&(-1!=OpenLayers.Util.indexOf(d,b)&&"static"==OpenLayers.Element.getStyle(a,"position"))&&(c="auto")}return"auto"==c?null:c}};OpenLayers.LonLat=OpenLayers.Class({lon:0,lat:0,initialize:function(a,b){OpenLayers.Util.isArray(a)&&(b=a[1],a=a[0]);this.lon=OpenLayers.Util.toFloat(a);this.lat=OpenLayers.Util.toFloat(b)},toString:function(){return"lon="+this.lon+",lat="+this.lat},toShortString:function(){return this.lon+", "+this.lat},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat)},add:function(a,b){if(null==a||null==b)throw new TypeError("LonLat.add cannot receive null values");return new OpenLayers.LonLat(this.lon+
+OpenLayers.Util.toFloat(a),this.lat+OpenLayers.Util.toFloat(b))},equals:function(a){var b=!1;null!=a&&(b=this.lon==a.lon&&this.lat==a.lat||isNaN(this.lon)&&isNaN(this.lat)&&isNaN(a.lon)&&isNaN(a.lat));return b},transform:function(a,b){var c=OpenLayers.Projection.transform({x:this.lon,y:this.lat},a,b);this.lon=c.x;this.lat=c.y;return this},wrapDateLine:function(a){var b=this.clone();if(a){for(;b.lon<a.left;)b.lon+=a.getWidth();for(;b.lon>a.right;)b.lon-=a.getWidth()}return b},CLASS_NAME:"OpenLayers.LonLat"});
+OpenLayers.LonLat.fromString=function(a){a=a.split(",");return new OpenLayers.LonLat(a[0],a[1])};OpenLayers.LonLat.fromArray=function(a){var b=OpenLayers.Util.isArray(a);return new OpenLayers.LonLat(b&&a[0],b&&a[1])};OpenLayers.Pixel=OpenLayers.Class({x:0,y:0,initialize:function(a,b){this.x=parseFloat(a);this.y=parseFloat(b)},toString:function(){return"x="+this.x+",y="+this.y},clone:function(){return new OpenLayers.Pixel(this.x,this.y)},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},distanceTo:function(a){return Math.sqrt(Math.pow(this.x-a.x,2)+Math.pow(this.y-a.y,2))},add:function(a,b){if(null==a||null==b)throw new TypeError("Pixel.add cannot receive null values");
+return new OpenLayers.Pixel(this.x+a,this.y+b)},offset:function(a){var b=this.clone();a&&(b=this.add(a.x,a.y));return b},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Size=OpenLayers.Class({w:0,h:0,initialize:function(a,b){this.w=parseFloat(a);this.h=parseFloat(b)},toString:function(){return"w="+this.w+",h="+this.h},clone:function(){return new OpenLayers.Size(this.w,this.h)},equals:function(a){var b=!1;null!=a&&(b=this.w==a.w&&this.h==a.h||isNaN(this.w)&&isNaN(this.h)&&isNaN(a.w)&&isNaN(a.h));return b},CLASS_NAME:"OpenLayers.Size"});OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},userError:function(a){alert(a)},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};
+(function(){for(var a=document.getElementsByTagName("script"),b=0,c=a.length;b<c;++b)if(-1!=a[b].src.indexOf("firebug.js")&&console){OpenLayers.Util.extend(OpenLayers.Console,console);break}})();OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){OpenLayers.Lang.code||OpenLayers.Lang.setCode();return OpenLayers.Lang.code},setCode:function(a){var b;a||(a="msie"==OpenLayers.BROWSER_NAME?navigator.userLanguage:navigator.language);a=a.split("-");a[0]=a[0].toLowerCase();"object"==typeof OpenLayers.Lang[a[0]]&&(b=a[0]);if(a[1]){var c=a[0]+"-"+a[1].toUpperCase();"object"==typeof OpenLayers.Lang[c]&&(b=c)}b||(OpenLayers.Console.warn("Failed to find OpenLayers.Lang."+a.join("-")+" dictionary, falling back to default language"),
+b=OpenLayers.Lang.defaultCode);OpenLayers.Lang.code=b},translate:function(a,b){var c=OpenLayers.Lang[OpenLayers.Lang.getCode()];(c=c&&c[a])||(c=a);b&&(c=OpenLayers.String.format(c,b));return c}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.getElement=function(){for(var a=[],b=0,c=arguments.length;b<c;b++){var d=arguments[b];"string"==typeof d&&(d=document.getElementById(d));if(1==arguments.length)return d;a.push(d)}return a};OpenLayers.Util.isElement=function(a){return!(!a||1!==a.nodeType)};OpenLayers.Util.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)};OpenLayers.Util.removeItem=function(a,b){for(var c=a.length-1;0<=c;c--)a[c]==b&&a.splice(c,1);return a};
+OpenLayers.Util.indexOf=function(a,b){if("function"==typeof a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;c<d;c++)if(a[c]==b)return c;return-1};OpenLayers.Util.dotless=/\./g;
+OpenLayers.Util.modifyDOMElement=function(a,b,c,d,e,f,g,h){b&&(a.id=b.replace(OpenLayers.Util.dotless,"_"));c&&(a.style.left=c.x+"px",a.style.top=c.y+"px");d&&(a.style.width=d.w+"px",a.style.height=d.h+"px");e&&(a.style.position=e);f&&(a.style.border=f);g&&(a.style.overflow=g);0<=parseFloat(h)&&1>parseFloat(h)?(a.style.filter="alpha(opacity="+100*h+")",a.style.opacity=h):1==parseFloat(h)&&(a.style.filter="",a.style.opacity="")};
+OpenLayers.Util.createDiv=function(a,b,c,d,e,f,g,h){var k=document.createElement("div");d&&(k.style.backgroundImage="url("+d+")");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="absolute");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,g,h);return k};
+OpenLayers.Util.createImage=function(a,b,c,d,e,f,g,h){var k=document.createElement("img");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="relative");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,null,g);h&&(k.style.display="none",b=function(){k.style.display="";OpenLayers.Event.stopObservingElement(k)},OpenLayers.Event.observe(k,"load",b),OpenLayers.Event.observe(k,"error",b));k.style.alt=a;k.galleryImg="no";d&&(k.src=d);return k};OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;
+OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(null==OpenLayers.Util.alphaHackNeeded){var a=navigator.appVersion.split("MSIE"),a=parseFloat(a[1]),b=!1;try{b=!!document.body.filters}catch(c){}OpenLayers.Util.alphaHackNeeded=b&&5.5<=a&&7>a}return OpenLayers.Util.alphaHackNeeded};
+OpenLayers.Util.modifyAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){OpenLayers.Util.modifyDOMElement(a,b,c,d,f,null,null,k);b=a.childNodes[0];e&&(b.src=e);OpenLayers.Util.modifyDOMElement(b,a.id+"_innerImage",null,d,"relative",g);OpenLayers.Util.alphaHack()&&("none"!=a.style.display&&(a.style.display="inline-block"),null==h&&(h="scale"),a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+b.src+"', sizingMethod='"+h+"')",0<=parseFloat(a.style.opacity)&&1>parseFloat(a.style.opacity)&&
+(a.style.filter+=" alpha(opacity="+100*a.style.opacity+")"),b.style.filter="alpha(opacity=0)")};OpenLayers.Util.createAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){var l=OpenLayers.Util.createDiv();k=OpenLayers.Util.createImage(null,null,null,null,null,null,null,k);k.className="olAlphaImg";l.appendChild(k);OpenLayers.Util.modifyAlphaImageDiv(l,a,b,c,d,e,f,g,h);return l};OpenLayers.Util.upperCaseObject=function(a){var b={},c;for(c in a)b[c.toUpperCase()]=a[c];return b};
+OpenLayers.Util.applyDefaults=function(a,b){a=a||{};var c="function"==typeof window.Event&&b instanceof window.Event,d;for(d in b)if(void 0===a[d]||!c&&b.hasOwnProperty&&b.hasOwnProperty(d)&&!a.hasOwnProperty(d))a[d]=b[d];!c&&(b&&b.hasOwnProperty&&b.hasOwnProperty("toString")&&!a.hasOwnProperty("toString"))&&(a.toString=b.toString);return a};
+OpenLayers.Util.getParameterString=function(a){var b=[],c;for(c in a){var d=a[c];if(null!=d&&"function"!=typeof d){if("object"==typeof d&&d.constructor==Array){for(var e=[],f,g=0,h=d.length;g<h;g++)f=d[g],e.push(encodeURIComponent(null===f||void 0===f?"":f));d=e.join(",")}else d=encodeURIComponent(d);b.push(encodeURIComponent(c)+"="+d)}}return b.join("&")};OpenLayers.Util.urlAppend=function(a,b){var c=a;if(b)var d=(a+" ").split(/[?&]/),c=c+(" "===d.pop()?b:d.length?"&"+b:"?"+b);return c};
+OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||OpenLayers._getScriptLocation()+"img/"};OpenLayers.Util.getImageLocation=function(a){return OpenLayers.Util.getImagesLocation()+a};OpenLayers.Util.Try=function(){for(var a=null,b=0,c=arguments.length;b<c;b++){var d=arguments[b];try{a=d();break}catch(e){}}return a};
+OpenLayers.Util.getXmlNodeValue=function(a){var b=null;OpenLayers.Util.Try(function(){b=a.text;b||(b=a.textContent);b||(b=a.firstChild.nodeValue)},function(){b=a.textContent});return b};OpenLayers.Util.mouseLeft=function(a,b){for(var c=a.relatedTarget?a.relatedTarget:a.toElement;c!=b&&null!=c;)c=c.parentNode;return c!=b};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(a,b){null==b&&(b=OpenLayers.Util.DEFAULT_PRECISION);"number"!==typeof a&&(a=parseFloat(a));return 0===b?a:parseFloat(a.toPrecision(b))};
+OpenLayers.Util.rad=function(a){return a*Math.PI/180};OpenLayers.Util.deg=function(a){return 180*a/Math.PI};OpenLayers.Util.VincentyConstants={a:6378137,b:6356752.3142,f:1/298.257223563};
+OpenLayers.Util.distVincenty=function(a,b){for(var c=OpenLayers.Util.VincentyConstants,d=c.a,e=c.b,c=c.f,f=OpenLayers.Util.rad(b.lon-a.lon),g=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(a.lat))),h=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(b.lat))),k=Math.sin(g),g=Math.cos(g),l=Math.sin(h),h=Math.cos(h),m=f,n=2*Math.PI,p=20;1E-12<Math.abs(m-n)&&0<--p;){var q=Math.sin(m),r=Math.cos(m),s=Math.sqrt(h*q*h*q+(g*l-k*h*r)*(g*l-k*h*r));if(0==s)return 0;var r=k*l+g*h*r,t=Math.atan2(s,r),u=Math.asin(g*h*
+q/s),v=Math.cos(u)*Math.cos(u),q=r-2*k*l/v,w=c/16*v*(4+c*(4-3*v)),n=m,m=f+(1-w)*c*Math.sin(u)*(t+w*s*(q+w*r*(-1+2*q*q)))}if(0==p)return NaN;d=v*(d*d-e*e)/(e*e);c=d/1024*(256+d*(-128+d*(74-47*d)));return(e*(1+d/16384*(4096+d*(-768+d*(320-175*d))))*(t-c*s*(q+c/4*(r*(-1+2*q*q)-c/6*q*(-3+4*s*s)*(-3+4*q*q))))).toFixed(3)/1E3};
+OpenLayers.Util.destinationVincenty=function(a,b,c){var d=OpenLayers.Util,e=d.VincentyConstants,f=e.a,g=e.b,h=e.f,e=a.lon;a=a.lat;var k=d.rad(b);b=Math.sin(k);k=Math.cos(k);a=(1-h)*Math.tan(d.rad(a));var l=1/Math.sqrt(1+a*a),m=a*l,n=Math.atan2(a,k);a=l*b;for(var p=1-a*a,f=p*(f*f-g*g)/(g*g),q=1+f/16384*(4096+f*(-768+f*(320-175*f))),r=f/1024*(256+f*(-128+f*(74-47*f))),f=c/(g*q),s=2*Math.PI;1E-12<Math.abs(f-s);)var t=Math.cos(2*n+f),u=Math.sin(f),v=Math.cos(f),w=r*u*(t+r/4*(v*(-1+2*t*t)-r/6*t*(-3+4*
+u*u)*(-3+4*t*t))),s=f,f=c/(g*q)+w;c=m*u-l*v*k;g=Math.atan2(m*v+l*u*k,(1-h)*Math.sqrt(a*a+c*c));b=Math.atan2(u*b,l*v-m*u*k);k=h/16*p*(4+h*(4-3*p));t=b-(1-k)*h*a*(f+k*u*(t+k*v*(-1+2*t*t)));Math.atan2(a,-c);return new OpenLayers.LonLat(e+d.deg(t),d.deg(g))};
+OpenLayers.Util.getParameters=function(a,b){b=b||{};a=null===a||void 0===a?window.location.href:a;var c="";if(OpenLayers.String.contains(a,"?"))var d=a.indexOf("?")+1,c=OpenLayers.String.contains(a,"#")?a.indexOf("#"):a.length,c=a.substring(d,c);for(var d={},c=c.split(/[&;]/),e=0,f=c.length;e<f;++e){var g=c[e].split("=");if(g[0]){var h=g[0];try{h=decodeURIComponent(h)}catch(k){h=unescape(h)}g=(g[1]||"").replace(/\+/g," ");try{g=decodeURIComponent(g)}catch(l){g=unescape(g)}!1!==b.splitArgs&&(g=g.split(","));
+1==g.length&&(g=g[0]);d[h]=g}}return d};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(a){a=null==a?"id_":a.replace(OpenLayers.Util.dotless,"_");OpenLayers.Util.lastSeqID+=1;return a+OpenLayers.Util.lastSeqID};OpenLayers.INCHES_PER_UNIT={inches:1,ft:12,mi:63360,m:39.37,km:39370,dd:4374754,yd:36};OpenLayers.INCHES_PER_UNIT["in"]=OpenLayers.INCHES_PER_UNIT.inches;OpenLayers.INCHES_PER_UNIT.degrees=OpenLayers.INCHES_PER_UNIT.dd;OpenLayers.INCHES_PER_UNIT.nmi=1852*OpenLayers.INCHES_PER_UNIT.m;
+OpenLayers.METERS_PER_INCH=0.0254000508001016;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{Inch:OpenLayers.INCHES_PER_UNIT.inches,Meter:1/OpenLayers.METERS_PER_INCH,Foot:0.3048006096012192/OpenLayers.METERS_PER_INCH,IFoot:0.3048/OpenLayers.METERS_PER_INCH,ClarkeFoot:0.3047972651151/OpenLayers.METERS_PER_INCH,SearsFoot:0.30479947153867626/OpenLayers.METERS_PER_INCH,GoldCoastFoot:0.3047997101815088/OpenLayers.METERS_PER_INCH,IInch:0.0254/OpenLayers.METERS_PER_INCH,MicroInch:2.54E-5/OpenLayers.METERS_PER_INCH,Mil:2.54E-8/OpenLayers.METERS_PER_INCH,
+Centimeter:0.01/OpenLayers.METERS_PER_INCH,Kilometer:1E3/OpenLayers.METERS_PER_INCH,Yard:0.9144018288036576/OpenLayers.METERS_PER_INCH,SearsYard:0.914398414616029/OpenLayers.METERS_PER_INCH,IndianYard:0.9143985307444408/OpenLayers.METERS_PER_INCH,IndianYd37:0.91439523/OpenLayers.METERS_PER_INCH,IndianYd62:0.9143988/OpenLayers.METERS_PER_INCH,IndianYd75:0.9143985/OpenLayers.METERS_PER_INCH,IndianFoot:0.30479951/OpenLayers.METERS_PER_INCH,IndianFt37:0.30479841/OpenLayers.METERS_PER_INCH,IndianFt62:0.3047996/
+OpenLayers.METERS_PER_INCH,IndianFt75:0.3047995/OpenLayers.METERS_PER_INCH,Mile:1609.3472186944373/OpenLayers.METERS_PER_INCH,IYard:0.9144/OpenLayers.METERS_PER_INCH,IMile:1609.344/OpenLayers.METERS_PER_INCH,NautM:1852/OpenLayers.METERS_PER_INCH,"Lat-66":110943.31648893273/OpenLayers.METERS_PER_INCH,"Lat-83":110946.25736872235/OpenLayers.METERS_PER_INCH,Decimeter:0.1/OpenLayers.METERS_PER_INCH,Millimeter:0.001/OpenLayers.METERS_PER_INCH,Dekameter:10/OpenLayers.METERS_PER_INCH,Decameter:10/OpenLayers.METERS_PER_INCH,
+Hectometer:100/OpenLayers.METERS_PER_INCH,GermanMeter:1.0000135965/OpenLayers.METERS_PER_INCH,CaGrid:0.999738/OpenLayers.METERS_PER_INCH,ClarkeChain:20.1166194976/OpenLayers.METERS_PER_INCH,GunterChain:20.11684023368047/OpenLayers.METERS_PER_INCH,BenoitChain:20.116782494375872/OpenLayers.METERS_PER_INCH,SearsChain:20.11676512155/OpenLayers.METERS_PER_INCH,ClarkeLink:0.201166194976/OpenLayers.METERS_PER_INCH,GunterLink:0.2011684023368047/OpenLayers.METERS_PER_INCH,BenoitLink:0.20116782494375873/OpenLayers.METERS_PER_INCH,
+SearsLink:0.2011676512155/OpenLayers.METERS_PER_INCH,Rod:5.02921005842012/OpenLayers.METERS_PER_INCH,IntnlChain:20.1168/OpenLayers.METERS_PER_INCH,IntnlLink:0.201168/OpenLayers.METERS_PER_INCH,Perch:5.02921005842012/OpenLayers.METERS_PER_INCH,Pole:5.02921005842012/OpenLayers.METERS_PER_INCH,Furlong:201.1684023368046/OpenLayers.METERS_PER_INCH,Rood:3.778266898/OpenLayers.METERS_PER_INCH,CapeFoot:0.3047972615/OpenLayers.METERS_PER_INCH,Brealey:375/OpenLayers.METERS_PER_INCH,ModAmFt:0.304812252984506/
+OpenLayers.METERS_PER_INCH,Fathom:1.8288/OpenLayers.METERS_PER_INCH,"NautM-UK":1853.184/OpenLayers.METERS_PER_INCH,"50kilometers":5E4/OpenLayers.METERS_PER_INCH,"150kilometers":15E4/OpenLayers.METERS_PER_INCH});
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{mm:OpenLayers.INCHES_PER_UNIT.Meter/1E3,cm:OpenLayers.INCHES_PER_UNIT.Meter/100,dm:100*OpenLayers.INCHES_PER_UNIT.Meter,km:1E3*OpenLayers.INCHES_PER_UNIT.Meter,kmi:OpenLayers.INCHES_PER_UNIT.nmi,fath:OpenLayers.INCHES_PER_UNIT.Fathom,ch:OpenLayers.INCHES_PER_UNIT.IntnlChain,link:OpenLayers.INCHES_PER_UNIT.IntnlLink,"us-in":OpenLayers.INCHES_PER_UNIT.inches,"us-ft":OpenLayers.INCHES_PER_UNIT.Foot,"us-yd":OpenLayers.INCHES_PER_UNIT.Yard,"us-ch":OpenLayers.INCHES_PER_UNIT.GunterChain,
+"us-mi":OpenLayers.INCHES_PER_UNIT.Mile,"ind-yd":OpenLayers.INCHES_PER_UNIT.IndianYd37,"ind-ft":OpenLayers.INCHES_PER_UNIT.IndianFt37,"ind-ch":20.11669506/OpenLayers.METERS_PER_INCH});OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(a){return 1<a?1/a:a};OpenLayers.Util.getResolutionFromScale=function(a,b){var c;a&&(null==b&&(b="degrees"),c=1/(OpenLayers.Util.normalizeScale(a)*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH));return c};
+OpenLayers.Util.getScaleFromResolution=function(a,b){null==b&&(b="degrees");return a*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH};
+OpenLayers.Util.pagePosition=function(a){var b=[0,0],c=OpenLayers.Util.getViewportElement();if(!a||a==window||a==c)return b;var d=OpenLayers.IS_GECKO&&document.getBoxObjectFor&&"absolute"==OpenLayers.Element.getStyle(a,"position")&&(""==a.style.top||""==a.style.left),e=null;if(a.getBoundingClientRect)a=a.getBoundingClientRect(),e=window.pageYOffset||c.scrollTop,b[0]=a.left+(window.pageXOffset||c.scrollLeft),b[1]=a.top+e;else if(document.getBoxObjectFor&&!d)a=document.getBoxObjectFor(a),c=document.getBoxObjectFor(c),
+b[0]=a.screenX-c.screenX,b[1]=a.screenY-c.screenY;else{b[0]=a.offsetLeft;b[1]=a.offsetTop;e=a.offsetParent;if(e!=a)for(;e;)b[0]+=e.offsetLeft,b[1]+=e.offsetTop,e=e.offsetParent;c=OpenLayers.BROWSER_NAME;if("opera"==c||"safari"==c&&"absolute"==OpenLayers.Element.getStyle(a,"position"))b[1]-=document.body.offsetTop;for(e=a.offsetParent;e&&e!=document.body;){b[0]-=e.scrollLeft;if("opera"!=c||"TR"!=e.tagName)b[1]-=e.scrollTop;e=e.offsetParent}}return b};
+OpenLayers.Util.getViewportElement=function(){var a=arguments.callee.viewportElement;void 0==a&&(a="msie"==OpenLayers.BROWSER_NAME&&"CSS1Compat"!=document.compatMode?document.body:document.documentElement,arguments.callee.viewportElement=a);return a};
+OpenLayers.Util.isEquivalentUrl=function(a,b,c){c=c||{};OpenLayers.Util.applyDefaults(c,{ignoreCase:!0,ignorePort80:!0,ignoreHash:!0,splitArgs:!1});a=OpenLayers.Util.createUrlObject(a,c);b=OpenLayers.Util.createUrlObject(b,c);for(var d in a)if("args"!==d&&a[d]!=b[d])return!1;for(d in a.args){if(a.args[d]!=b.args[d])return!1;delete b.args[d]}for(d in b.args)return!1;return!0};
+OpenLayers.Util.createUrlObject=function(a,b){b=b||{};if(!/^\w+:\/\//.test(a)){var c=window.location,d=c.port?":"+c.port:"",d=c.protocol+"//"+c.host.split(":").shift()+d;0===a.indexOf("/")?a=d+a:(c=c.pathname.split("/"),c.pop(),a=d+c.join("/")+"/"+a)}b.ignoreCase&&(a=a.toLowerCase());c=document.createElement("a");c.href=a;d={};d.host=c.host.split(":").shift();d.protocol=c.protocol;d.port=b.ignorePort80?"80"==c.port||"0"==c.port?"":c.port:""==c.port||"0"==c.port?"80":c.port;d.hash=b.ignoreHash||"#"===
+c.hash?"":c.hash;var e=c.search;e||(e=a.indexOf("?"),e=-1!=e?a.substr(e):"");d.args=OpenLayers.Util.getParameters(e,{splitArgs:b.splitArgs});d.pathname="/"==c.pathname.charAt(0)?c.pathname:"/"+c.pathname;return d};OpenLayers.Util.removeTail=function(a){var b=null,b=a.indexOf("?"),c=a.indexOf("#");return b=-1==b?-1!=c?a.substr(0,c):a:-1!=c?a.substr(0,Math.min(b,c)):a.substr(0,b)};OpenLayers.IS_GECKO=function(){var a=navigator.userAgent.toLowerCase();return-1==a.indexOf("webkit")&&-1!=a.indexOf("gecko")}();
+OpenLayers.CANVAS_SUPPORTED=function(){var a=document.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))}();OpenLayers.BROWSER_NAME=function(){var a="",b=navigator.userAgent.toLowerCase();-1!=b.indexOf("opera")?a="opera":-1!=b.indexOf("msie")?a="msie":-1!=b.indexOf("safari")?a="safari":-1!=b.indexOf("mozilla")&&(a=-1!=b.indexOf("firefox")?"firefox":"mozilla");return a}();OpenLayers.Util.getBrowserName=function(){return OpenLayers.BROWSER_NAME};
+OpenLayers.Util.getRenderedDimensions=function(a,b,c){var d,e,f=document.createElement("div");f.style.visibility="hidden";for(var g=c&&c.containerElement?c.containerElement:document.body,h=!1,k=null,l=g;l&&"body"!=l.tagName.toLowerCase();){var m=OpenLayers.Element.getStyle(l,"position");if("absolute"==m){h=!0;break}else if(m&&"static"!=m)break;l=l.parentNode}!h||0!==g.clientHeight&&0!==g.clientWidth||(k=document.createElement("div"),k.style.visibility="hidden",k.style.position="absolute",k.style.overflow=
+"visible",k.style.width=document.body.clientWidth+"px",k.style.height=document.body.clientHeight+"px",k.appendChild(f));f.style.position="absolute";b&&(b.w?(d=b.w,f.style.width=d+"px"):b.h&&(e=b.h,f.style.height=e+"px"));c&&c.displayClass&&(f.className=c.displayClass);b=document.createElement("div");b.innerHTML=a;b.style.overflow="visible";if(b.childNodes)for(a=0,c=b.childNodes.length;a<c;a++)b.childNodes[a].style&&(b.childNodes[a].style.overflow="visible");f.appendChild(b);k?g.appendChild(k):g.appendChild(f);
+d||(d=parseInt(b.scrollWidth),f.style.width=d+"px");e||(e=parseInt(b.scrollHeight));f.removeChild(b);k?(k.removeChild(f),g.removeChild(k)):g.removeChild(f);return new OpenLayers.Size(d,e)};
+OpenLayers.Util.getScrollbarWidth=function(){var a=OpenLayers.Util._scrollbarWidth;if(null==a){var b=null,c=null,b=a=0,b=document.createElement("div");b.style.position="absolute";b.style.top="-1000px";b.style.left="-1000px";b.style.width="100px";b.style.height="50px";b.style.overflow="hidden";c=document.createElement("div");c.style.width="100%";c.style.height="200px";b.appendChild(c);document.body.appendChild(b);a=c.offsetWidth;b.style.overflow="scroll";b=c.offsetWidth;document.body.removeChild(document.body.lastChild);
+OpenLayers.Util._scrollbarWidth=a-b;a=OpenLayers.Util._scrollbarWidth}return a};
+OpenLayers.Util.getFormattedLonLat=function(a,b,c){c||(c="dms");a=(a+540)%360-180;var d=Math.abs(a),e=Math.floor(d),f=d=(d-e)/(1/60),d=Math.floor(d),f=Math.round(10*((f-d)/(1/60))),f=f/10;60<=f&&(f-=60,d+=1,60<=d&&(d-=60,e+=1));10>e&&(e="0"+e);e+="\u00b0";0<=c.indexOf("dm")&&(10>d&&(d="0"+d),e+=d+"'",0<=c.indexOf("dms")&&(10>f&&(f="0"+f),e+=f+'"'));return e="lon"==b?e+(0>a?OpenLayers.i18n("W"):OpenLayers.i18n("E")):e+(0>a?OpenLayers.i18n("S"):OpenLayers.i18n("N"))};OpenLayers.Format=OpenLayers.Class({options:null,externalProjection:null,internalProjection:null,data:null,keepData:!1,initialize:function(a){OpenLayers.Util.extend(this,a);this.options=a},destroy:function(){},read:function(a){throw Error("Read not implemented.");},write:function(a){throw Error("Write not implemented.");},CLASS_NAME:"OpenLayers.Format"});OpenLayers.Format.CSWGetRecords=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Format.CSWGetRecords.DEFAULTS);var b=OpenLayers.Format.CSWGetRecords["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported CSWGetRecords version: "+a.version;return new b(a)};OpenLayers.Format.CSWGetRecords.DEFAULTS={version:"2.0.2"};OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:!1,displayClass:"",title:"",autoActivate:!1,active:null,handlerOptions:null,handler:null,eventListeners:null,events:null,initialize:function(a){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,a);this.events=new OpenLayers.Events(this);if(this.eventListeners instanceof Object)this.events.on(this.eventListeners);null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+
+"_"))},destroy:function(){this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy(),this.events=null);this.eventListeners=null;this.handler&&(this.handler.destroy(),this.handler=null);if(this.handlers){for(var a in this.handlers)this.handlers.hasOwnProperty(a)&&"function"==typeof this.handlers[a].destroy&&this.handlers[a].destroy();this.handlers=null}this.map&&(this.map.removeControl(this),this.map=null);this.div=null},setMap:function(a){this.map=a;this.handler&&
+this.handler.setMap(a)},draw:function(a){null==this.div&&(this.div=OpenLayers.Util.createDiv(this.id),this.div.className=this.displayClass,this.allowSelection||(this.div.className+=" olControlNoSelect",this.div.setAttribute("unselectable","on",0),this.div.onselectstart=OpenLayers.Function.False),""!=this.title&&(this.div.title=this.title));null!=a&&(this.position=a.clone());this.moveTo(this.position);return this.div},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"px",this.div.style.top=
+a.y+"px")},activate:function(){if(this.active)return!1;this.handler&&this.handler.activate();this.active=!0;this.map&&OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");this.events.triggerEvent("activate");return!0},deactivate:function(){return this.active?(this.handler&&this.handler.deactivate(),this.active=!1,this.map&&OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active"),this.events.triggerEvent("deactivate"),
+!0):!1},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Event={observers:!1,KEY_SPACE:32,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(a){return a.target||a.srcElement},isSingleTouch:function(a){return a.touches&&1==a.touches.length},isMultiTouch:function(a){return a.touches&&1<a.touches.length},isLeftClick:function(a){return a.which&&1==a.which||a.button&&1==a.button},isRightClick:function(a){return a.which&&3==a.which||a.button&&2==a.button},stop:function(a,
+b){b||OpenLayers.Event.preventDefault(a);a.stopPropagation?a.stopPropagation():a.cancelBubble=!0},preventDefault:function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},findElement:function(a,b){for(var c=OpenLayers.Event.element(a);c.parentNode&&(!c.tagName||c.tagName.toUpperCase()!=b.toUpperCase());)c=c.parentNode;return c},observe:function(a,b,c,d){a=OpenLayers.Util.getElement(a);d=d||!1;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.attachEvent)&&(b="keydown");
+this.observers||(this.observers={});if(!a._eventCacheID){var e="eventCacheID_";a.id&&(e=a.id+"_"+e);a._eventCacheID=OpenLayers.Util.createUniqueID(e)}e=a._eventCacheID;this.observers[e]||(this.observers[e]=[]);this.observers[e].push({element:a,name:b,observer:c,useCapture:d});a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},stopObservingElement:function(a){a=OpenLayers.Util.getElement(a)._eventCacheID;this._removeElementObservers(OpenLayers.Event.observers[a])},
+_removeElementObservers:function(a){if(a)for(var b=a.length-1;0<=b;b--){var c=a[b];OpenLayers.Event.stopObserving.apply(this,[c.element,c.name,c.observer,c.useCapture])}},stopObserving:function(a,b,c,d){d=d||!1;a=OpenLayers.Util.getElement(a);var e=a._eventCacheID;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.detachEvent)&&(b="keydown");var f=!1,g=OpenLayers.Event.observers[e];if(g)for(var h=0;!f&&h<g.length;){var k=g[h];if(k.name==b&&k.observer==c&&k.useCapture==d){g.splice(h,
+1);0==g.length&&delete OpenLayers.Event.observers[e];f=!0;break}h++}f&&(a.removeEventListener?a.removeEventListener(b,c,d):a&&a.detachEvent&&a.detachEvent("on"+b,c));return f},unloadCache:function(){if(OpenLayers.Event&&OpenLayers.Event.observers){for(var a in OpenLayers.Event.observers)OpenLayers.Event._removeElementObservers.apply(this,[OpenLayers.Event.observers[a]]);OpenLayers.Event.observers=!1}},CLASS_NAME:"OpenLayers.Event"};
+OpenLayers.Event.observe(window,"unload",OpenLayers.Event.unloadCache,!1);
+OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:"mouseover mouseout mousedown mouseup mousemove click dblclick rightclick dblrightclick resize focus blur touchstart touchmove touchend keydown".split(" "),listeners:null,object:null,element:null,eventHandler:null,fallThrough:null,includeXY:!1,extensions:null,extensionCount:null,clearMouseListener:null,initialize:function(a,b,c,d,e){OpenLayers.Util.extend(this,e);this.object=a;this.fallThrough=d;this.listeners={};this.extensions={};this.extensionCount=
+{};this._msTouches=[];null!=b&&this.attachToElement(b)},destroy:function(){for(var a in this.extensions)"boolean"!==typeof this.extensions[a]&&this.extensions[a].destroy();this.extensions=null;this.element&&(OpenLayers.Event.stopObservingElement(this.element),this.element.hasScrollEvent&&OpenLayers.Event.stopObserving(window,"scroll",this.clearMouseListener));this.eventHandler=this.fallThrough=this.object=this.listeners=this.element=null},addEventType:function(a){},attachToElement:function(a){this.element?
+OpenLayers.Event.stopObservingElement(this.element):(this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this),this.clearMouseListener=OpenLayers.Function.bind(this.clearMouseCache,this));this.element=a;for(var b=!!window.navigator.msMaxTouchPoints,c,d=0,e=this.BROWSER_EVENTS.length;d<e;d++)c=this.BROWSER_EVENTS[d],OpenLayers.Event.observe(a,c,this.eventHandler),b&&0===c.indexOf("touch")&&this.addMsTouchListener(a,c,this.eventHandler);OpenLayers.Event.observe(a,"dragstart",
+OpenLayers.Event.stop)},on:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.register(b,a.scope,a[b])},register:function(a,b,c,d){a in OpenLayers.Events&&!this.extensions[a]&&(this.extensions[a]=new OpenLayers.Events[a](this));if(null!=c){null==b&&(b=this.object);var e=this.listeners[a];e||(e=[],this.listeners[a]=e,this.extensionCount[a]=0);b={obj:b,func:c};d?(e.splice(this.extensionCount[a],0,b),"object"===typeof d&&d.extension&&this.extensionCount[a]++):e.push(b)}},registerPriority:function(a,
+b,c){this.register(a,b,c,!0)},un:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.unregister(b,a.scope,a[b])},unregister:function(a,b,c){null==b&&(b=this.object);a=this.listeners[a];if(null!=a)for(var d=0,e=a.length;d<e;d++)if(a[d].obj==b&&a[d].func==c){a.splice(d,1);break}},remove:function(a){null!=this.listeners[a]&&(this.listeners[a]=[])},triggerEvent:function(a,b){var c=this.listeners[a];if(c&&0!=c.length){null==b&&(b={});b.object=this.object;b.element=this.element;b.type||(b.type=
+a);for(var c=c.slice(),d,e=0,f=c.length;e<f&&(d=c[e],d=d.func.apply(d.obj,[b]),void 0==d||!1!=d);e++);this.fallThrough||OpenLayers.Event.stop(b,!0);return d}},handleBrowserEvent:function(a){var b=a.type,c=this.listeners[b];if(c&&0!=c.length){if((c=a.touches)&&c[0]){for(var d=0,e=0,f=c.length,g,h=0;h<f;++h)g=this.getTouchClientXY(c[h]),d+=g.clientX,e+=g.clientY;a.clientX=d/f;a.clientY=e/f}this.includeXY&&(a.xy=this.getMousePosition(a));this.triggerEvent(b,a)}},getTouchClientXY:function(a){var b=window.olMockWin||
+window,c=b.pageXOffset,b=b.pageYOffset,d=a.clientX,e=a.clientY;if(0===a.pageY&&Math.floor(e)>Math.floor(a.pageY)||0===a.pageX&&Math.floor(d)>Math.floor(a.pageX))d-=c,e-=b;else if(e<a.pageY-b||d<a.pageX-c)d=a.pageX-c,e=a.pageY-b;a.olClientX=d;a.olClientY=e;return{clientX:d,clientY:e}},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null},getMousePosition:function(a){this.includeXY?this.element.hasScrollEvent||(OpenLayers.Event.observe(window,"scroll",
+this.clearMouseListener),this.element.hasScrollEvent=!0):this.clearMouseCache();if(!this.element.scrolls){var b=OpenLayers.Util.getViewportElement();this.element.scrolls=[window.pageXOffset||b.scrollLeft,window.pageYOffset||b.scrollTop]}this.element.lefttop||(this.element.lefttop=[document.documentElement.clientLeft||0,document.documentElement.clientTop||0]);this.element.offsets||(this.element.offsets=OpenLayers.Util.pagePosition(this.element));return new OpenLayers.Pixel(a.clientX+this.element.scrolls[0]-
+this.element.offsets[0]-this.element.lefttop[0],a.clientY+this.element.scrolls[1]-this.element.offsets[1]-this.element.lefttop[1])},addMsTouchListener:function(a,b,c){function d(a){c(OpenLayers.Util.applyDefaults({stopPropagation:function(){for(var a=e.length-1;0<=a;--a)e[a].stopPropagation()},preventDefault:function(){for(var a=e.length-1;0<=a;--a)e[a].preventDefault()},type:b},a))}var e=this._msTouches;switch(b){case "touchstart":return this.addMsTouchListenerStart(a,b,d);case "touchend":return this.addMsTouchListenerEnd(a,
+b,d);case "touchmove":return this.addMsTouchListenerMove(a,b,d);default:throw"Unknown touch event type";}},addMsTouchListenerStart:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerDown",function(a){for(var b=!1,g=0,h=d.length;g<h;++g)if(d[g].pointerId==a.pointerId){b=!0;break}b||d.push(a);a.touches=d.slice();c(a)});OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,c=d.length;b<c;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}})},addMsTouchListenerMove:function(a,
+b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerMove",function(a){if(a.pointerType!=a.MSPOINTER_TYPE_MOUSE||0!=a.buttons)if(1!=d.length||d[0].pageX!=a.pageX||d[0].pageY!=a.pageY){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d[b]=a;break}a.touches=d.slice();c(a)}})},addMsTouchListenerEnd:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}a.touches=
+d.slice();c(a)})},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Events.buttonclick=OpenLayers.Class({target:null,events:"mousedown mouseup click dblclick touchstart touchmove touchend keydown".split(" "),startRegEx:/^mousedown|touchstart$/,cancelRegEx:/^touchmove$/,completeRegEx:/^mouseup|touchend$/,initialize:function(a){this.target=a;for(a=this.events.length-1;0<=a;--a)this.target.register(this.events[a],this,this.buttonClick,{extension:!0})},destroy:function(){for(var a=this.events.length-1;0<=a;--a)this.target.unregister(this.events[a],this,this.buttonClick);
+delete this.target},getPressedButton:function(a){var b=3,c;do{if(OpenLayers.Element.hasClass(a,"olButton")){c=a;break}a=a.parentNode}while(0<--b&&a);return c},ignore:function(a){var b=3,c=!1;do{if("a"===a.nodeName.toLowerCase()){c=!0;break}a=a.parentNode}while(0<--b&&a);return c},buttonClick:function(a){var b=!0,c=OpenLayers.Event.element(a);if(c&&(OpenLayers.Event.isLeftClick(a)||!~a.type.indexOf("mouse")))if(c=this.getPressedButton(c)){if("keydown"===a.type)switch(a.keyCode){case OpenLayers.Event.KEY_RETURN:case OpenLayers.Event.KEY_SPACE:this.target.triggerEvent("buttonclick",
+{buttonElement:c}),OpenLayers.Event.stop(a),b=!1}else if(this.startEvt){if(this.completeRegEx.test(a.type)){var b=OpenLayers.Util.pagePosition(c),d=OpenLayers.Util.getViewportElement(),e=window.pageYOffset||d.scrollTop;b[0]-=window.pageXOffset||d.scrollLeft;b[1]-=e;this.target.triggerEvent("buttonclick",{buttonElement:c,buttonXY:{x:this.startEvt.clientX-b[0],y:this.startEvt.clientY-b[1]}})}this.cancelRegEx.test(a.type)&&delete this.startEvt;OpenLayers.Event.stop(a);b=!1}this.startRegEx.test(a.type)&&
+(this.startEvt=a,OpenLayers.Event.stop(a),b=!1)}else b=!this.ignore(OpenLayers.Event.element(a)),delete this.startEvt;return b}});OpenLayers.Util=OpenLayers.Util||{};
+OpenLayers.Util.vendorPrefix=function(){function a(a){return a?a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()}).replace(/^ms-/,"-ms-"):null}function b(a,b){if(void 0===g[b]){var c,e=0,f=d.length,p="undefined"!==typeof a.cssText;for(g[b]=null;e<f;e++)if((c=d[e])?(p||(c=c.toLowerCase()),c=c+b.charAt(0).toUpperCase()+b.slice(1)):c=b,void 0!==a[c]){g[b]=c;break}}return g[b]}function c(a){return b(e,a)}var d=["","O","ms","Moz","Webkit"],e=document.createElement("div").style,f={},g={};return{css:function(b){if(void 0===
+f[b]){var d=b.replace(/(-[\s\S])/g,function(a){return a.charAt(1).toUpperCase()}),d=c(d);f[b]=a(d)}return f[b]},js:b,style:c,cssCache:f,jsCache:g}}();OpenLayers.Animation=function(a){var b=OpenLayers.Util.vendorPrefix.js(a,"requestAnimationFrame"),c=!!b,d=function(){var c=a[b]||function(b,c){a.setTimeout(b,16)};return function(b,d){c.apply(a,[b,d])}}(),e=0,f={};return{isNative:c,requestFrame:d,start:function(a,b,c){b=0<b?b:Number.POSITIVE_INFINITY;var l=++e,m=+new Date;f[l]=function(){f[l]&&+new Date-m<=b?(a(),f[l]&&d(f[l],c)):delete f[l]};d(f[l],c);return l},stop:function(a){delete f[a]}}}(window);OpenLayers.Tween=OpenLayers.Class({easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,minFrameRate:null,startTime:null,animationId:null,playing:!1,initialize:function(a){this.easing=a?a:OpenLayers.Easing.Expo.easeOut},start:function(a,b,c,d){this.playing=!0;this.begin=a;this.finish=b;this.duration=c;this.callbacks=d.callbacks;this.minFrameRate=d.minFrameRate||30;this.time=0;this.startTime=(new Date).getTime();OpenLayers.Animation.stop(this.animationId);this.animationId=null;
+this.callbacks&&this.callbacks.start&&this.callbacks.start.call(this,this.begin);this.animationId=OpenLayers.Animation.start(OpenLayers.Function.bind(this.play,this))},stop:function(){this.playing&&(this.callbacks&&this.callbacks.done&&this.callbacks.done.call(this,this.finish),OpenLayers.Animation.stop(this.animationId),this.animationId=null,this.playing=!1)},play:function(){var a={},b;for(b in this.begin){var c=this.begin[b],d=this.finish[b];if(null==c||null==d||isNaN(c)||isNaN(d))throw new TypeError("invalid value for Tween");
+a[b]=this.easing.apply(this,[this.time,c,d-c,this.duration])}this.time++;this.callbacks&&this.callbacks.eachStep&&((new Date).getTime()-this.startTime)/this.time<=1E3/this.minFrameRate&&this.callbacks.eachStep.call(this,a);this.time>this.duration&&this.stop()},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(a,b,c,d){return c*a/d+b},easeOut:function(a,b,c,d){return c*a/d+b},easeInOut:function(a,b,c,d){return c*a/d+b},CLASS_NAME:"OpenLayers.Easing.Linear"};
+OpenLayers.Easing.Expo={easeIn:function(a,b,c,d){return 0==a?b:c*Math.pow(2,10*(a/d-1))+b},easeOut:function(a,b,c,d){return a==d?b+c:c*(-Math.pow(2,-10*a/d)+1)+b},easeInOut:function(a,b,c,d){return 0==a?b:a==d?b+c:1>(a/=d/2)?c/2*Math.pow(2,10*(a-1))+b:c/2*(-Math.pow(2,-10*--a)+2)+b},CLASS_NAME:"OpenLayers.Easing.Expo"};
+OpenLayers.Easing.Quad={easeIn:function(a,b,c,d){return c*(a/=d)*a+b},easeOut:function(a,b,c,d){return-c*(a/=d)*(a-2)+b},easeInOut:function(a,b,c,d){return 1>(a/=d/2)?c/2*a*a+b:-c/2*(--a*(a-2)-1)+b},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,titleRegEx:/\+title=[^\+]*/,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.projCode=a;"object"==typeof Proj4js&&(this.proj=new Proj4js.Proj(a))},getCode:function(){return this.proj?this.proj.srsCode:this.projCode},getUnits:function(){return this.proj?this.proj.units:null},toString:function(){return this.getCode()},equals:function(a){var b=!1;a&&(a instanceof OpenLayers.Projection||(a=new OpenLayers.Projection(a)),"object"==
+typeof Proj4js&&this.proj.defData&&a.proj.defData?b=this.proj.defData.replace(this.titleRegEx,"")==a.proj.defData.replace(this.titleRegEx,""):a.getCode&&(b=this.getCode(),a=a.getCode(),b=b==a||!!OpenLayers.Projection.transforms[b]&&OpenLayers.Projection.transforms[b][a]===OpenLayers.Projection.nullTransform));return b},destroy:function(){delete this.proj;delete this.projCode},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};
+OpenLayers.Projection.defaults={"EPSG:4326":{units:"degrees",maxExtent:[-180,-90,180,90],yx:!0},"CRS:84":{units:"degrees",maxExtent:[-180,-90,180,90]},"EPSG:900913":{units:"m",maxExtent:[-2.003750834E7,-2.003750834E7,2.003750834E7,2.003750834E7]}};
+OpenLayers.Projection.addTransform=function(a,b,c){if(c===OpenLayers.Projection.nullTransform){var d=OpenLayers.Projection.defaults[a];d&&!OpenLayers.Projection.defaults[b]&&(OpenLayers.Projection.defaults[b]=d)}OpenLayers.Projection.transforms[a]||(OpenLayers.Projection.transforms[a]={});OpenLayers.Projection.transforms[a][b]=c};
+OpenLayers.Projection.transform=function(a,b,c){if(b&&c)if(b instanceof OpenLayers.Projection||(b=new OpenLayers.Projection(b)),c instanceof OpenLayers.Projection||(c=new OpenLayers.Projection(c)),b.proj&&c.proj)a=Proj4js.transform(b.proj,c.proj,a);else{b=b.getCode();c=c.getCode();var d=OpenLayers.Projection.transforms;if(d[b]&&d[b][c])d[b][c](a)}return a};OpenLayers.Projection.nullTransform=function(a){return a};
+(function(){function a(a){a.x=180*a.x/d;a.y=180/Math.PI*(2*Math.atan(Math.exp(a.y/d*Math.PI))-Math.PI/2);return a}function b(a){a.x=a.x*d/180;var b=Math.log(Math.tan((90+a.y)*Math.PI/360))/Math.PI*d;a.y=Math.max(-2.003750834E7,Math.min(b,2.003750834E7));return a}function c(c,d){var e=OpenLayers.Projection.addTransform,f=OpenLayers.Projection.nullTransform,g,p,q,r,s;g=0;for(p=d.length;g<p;++g)for(q=d[g],e(c,q,b),e(q,c,a),s=g+1;s<p;++s)r=d[s],e(q,r,f),e(r,q,f)}var d=2.003750834E7,e=["EPSG:900913","EPSG:3857",
+"EPSG:102113","EPSG:102100"],f=["CRS:84","urn:ogc:def:crs:EPSG:6.6:4326","EPSG:4326"],g;for(g=e.length-1;0<=g;--g)c(e[g],f);for(g=f.length-1;0<=g;--g)c(f[g],e)})();OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1E3},id:null,fractionalZoom:!1,events:null,allOverlays:!1,div:null,dragging:!1,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,panRatio:1.5,options:null,tileSize:null,projection:"EPSG:4326",units:null,resolutions:null,maxResolution:null,minResolution:null,maxScale:null,minScale:null,
+maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:!1,autoUpdateSize:!0,eventListeners:null,panTween:null,panMethod:OpenLayers.Easing.Expo.easeOut,panDuration:50,zoomTween:null,zoomMethod:OpenLayers.Easing.Quad.easeOut,zoomDuration:20,paddingForPopups:null,layerContainerOriginPx:null,minPx:null,maxPx:null,initialize:function(a,b){1===arguments.length&&"object"===typeof a&&(a=(b=a)&&b.div);this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+OpenLayers.Map.TILE_HEIGHT);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+"theme/default/style.css";this.options=OpenLayers.Util.extend({},b);OpenLayers.Util.extend(this,b);OpenLayers.Util.applyDefaults(this,OpenLayers.Projection.defaults[this.projection instanceof OpenLayers.Projection?this.projection.projCode:this.projection]);!this.maxExtent||this.maxExtent instanceof OpenLayers.Bounds||(this.maxExtent=new OpenLayers.Bounds(this.maxExtent));
+!this.minExtent||this.minExtent instanceof OpenLayers.Bounds||(this.minExtent=new OpenLayers.Bounds(this.minExtent));!this.restrictedExtent||this.restrictedExtent instanceof OpenLayers.Bounds||(this.restrictedExtent=new OpenLayers.Bounds(this.restrictedExtent));!this.center||this.center instanceof OpenLayers.LonLat||(this.center=new OpenLayers.LonLat(this.center));this.layers=[];this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(a);this.div||(this.div=document.createElement("div"),
+this.div.style.height="1px",this.div.style.width="1px");OpenLayers.Element.addClass(this.div,"olMap");var c=this.id+"_OpenLayers_ViewPort";this.viewPortDiv=OpenLayers.Util.createDiv(c,null,null,null,"relative",null,"hidden");this.viewPortDiv.style.width="100%";this.viewPortDiv.style.height="100%";this.viewPortDiv.className="olMapViewport";this.div.appendChild(this.viewPortDiv);this.events=new OpenLayers.Events(this,this.viewPortDiv,null,this.fallThrough,{includeXY:!0});OpenLayers.TileManager&&null!==
+this.tileManager&&(this.tileManager instanceof OpenLayers.TileManager||(this.tileManager=new OpenLayers.TileManager(this.tileManager)),this.tileManager.addMap(this));c=this.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(c);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE.Popup-1;this.layerContainerOriginPx={x:0,y:0};this.applyTransform();this.viewPortDiv.appendChild(this.layerContainerDiv);this.updateSize();if(this.eventListeners instanceof Object)this.events.on(this.eventListeners);
+!0===this.autoUpdateSize&&(this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this),OpenLayers.Event.observe(window,"resize",this.updateSizeDestroy));if(this.theme){for(var c=!0,d=document.getElementsByTagName("link"),e=0,f=d.length;e<f;++e)if(OpenLayers.Util.isEquivalentUrl(d.item(e).href,this.theme)){c=!1;break}c&&(c=document.createElement("link"),c.setAttribute("rel","stylesheet"),c.setAttribute("type","text/css"),c.setAttribute("href",this.theme),document.getElementsByTagName("head")[0].appendChild(c))}null==
+this.controls&&(this.controls=[],null!=OpenLayers.Control&&(OpenLayers.Control.Navigation?this.controls.push(new OpenLayers.Control.Navigation):OpenLayers.Control.TouchNavigation&&this.controls.push(new OpenLayers.Control.TouchNavigation),OpenLayers.Control.Zoom?this.controls.push(new OpenLayers.Control.Zoom):OpenLayers.Control.PanZoom&&this.controls.push(new OpenLayers.Control.PanZoom),OpenLayers.Control.ArgParser&&this.controls.push(new OpenLayers.Control.ArgParser),OpenLayers.Control.Attribution&&
+this.controls.push(new OpenLayers.Control.Attribution)));e=0;for(f=this.controls.length;e<f;e++)this.addControlToMap(this.controls[e]);this.popups=[];this.unloadDestroy=OpenLayers.Function.bind(this.destroy,this);OpenLayers.Event.observe(window,"unload",this.unloadDestroy);b&&b.layers&&(delete this.center,delete this.zoom,this.addLayers(b.layers),b.center&&!this.getCenter()&&this.setCenter(b.center,b.zoom));this.panMethod&&(this.panTween=new OpenLayers.Tween(this.panMethod));this.zoomMethod&&this.applyTransform.transform&&
+(this.zoomTween=new OpenLayers.Tween(this.zoomMethod))},getViewport:function(){return this.viewPortDiv},render:function(a){this.div=OpenLayers.Util.getElement(a);OpenLayers.Element.addClass(this.div,"olMap");this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);this.div.appendChild(this.viewPortDiv);this.updateSize()},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy)return!1;this.panTween&&(this.panTween.stop(),this.panTween=null);this.zoomTween&&(this.zoomTween.stop(),
+this.zoomTween=null);OpenLayers.Event.stopObserving(window,"unload",this.unloadDestroy);this.unloadDestroy=null;this.updateSizeDestroy&&OpenLayers.Event.stopObserving(window,"resize",this.updateSizeDestroy);this.paddingForPopups=null;if(null!=this.controls){for(var a=this.controls.length-1;0<=a;--a)this.controls[a].destroy();this.controls=null}if(null!=this.layers){for(a=this.layers.length-1;0<=a;--a)this.layers[a].destroy(!1);this.layers=null}this.viewPortDiv&&this.viewPortDiv.parentNode&&this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+this.viewPortDiv=null;this.tileManager&&(this.tileManager.removeMap(this),this.tileManager=null);this.eventListeners&&(this.events.un(this.eventListeners),this.eventListeners=null);this.events.destroy();this.options=this.events=null},setOptions:function(a){var b=this.minPx&&a.restrictedExtent!=this.restrictedExtent;OpenLayers.Util.extend(this,a);b&&this.moveTo(this.getCachedCenter(),this.zoom,{forceZoomChange:!0})},getTileSize:function(){return this.tileSize},getBy:function(a,b,c){var d="function"==
+typeof c.test;return OpenLayers.Array.filter(this[a],function(a){return a[b]==c||d&&c.test(a[b])})},getLayersBy:function(a,b){return this.getBy("layers",a,b)},getLayersByName:function(a){return this.getLayersBy("name",a)},getLayersByClass:function(a){return this.getLayersBy("CLASS_NAME",a)},getControlsBy:function(a,b){return this.getBy("controls",a,b)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},getLayer:function(a){for(var b=null,c=0,d=this.layers.length;c<d;c++){var e=
+this.layers[c];if(e.id==a){b=e;break}}return b},setLayerZIndex:function(a,b){a.setZIndex(this.Z_INDEX_BASE[a.isBaseLayer?"BaseLayer":"Overlay"]+5*b)},resetLayersZIndex:function(){for(var a=0,b=this.layers.length;a<b;a++)this.setLayerZIndex(this.layers[a],a)},addLayer:function(a){for(var b=0,c=this.layers.length;b<c;b++)if(this.layers[b]==a)return!1;if(!1===this.events.triggerEvent("preaddlayer",{layer:a}))return!1;this.allOverlays&&(a.isBaseLayer=!1);a.div.className="olLayerDiv";a.div.style.overflow=
+"";this.setLayerZIndex(a,this.layers.length);a.isFixed?this.viewPortDiv.appendChild(a.div):this.layerContainerDiv.appendChild(a.div);this.layers.push(a);a.setMap(this);a.isBaseLayer||this.allOverlays&&!this.baseLayer?null==this.baseLayer?this.setBaseLayer(a):a.setVisibility(!1):a.redraw();this.events.triggerEvent("addlayer",{layer:a});a.events.triggerEvent("added",{map:this,layer:a});a.afterAdd();return!0},addLayers:function(a){for(var b=0,c=a.length;b<c;b++)this.addLayer(a[b])},removeLayer:function(a,
+b){if(!1!==this.events.triggerEvent("preremovelayer",{layer:a})){null==b&&(b=!0);a.isFixed?this.viewPortDiv.removeChild(a.div):this.layerContainerDiv.removeChild(a.div);OpenLayers.Util.removeItem(this.layers,a);a.removeMap(this);a.map=null;if(this.baseLayer==a&&(this.baseLayer=null,b))for(var c=0,d=this.layers.length;c<d;c++){var e=this.layers[c];if(e.isBaseLayer||this.allOverlays){this.setBaseLayer(e);break}}this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:a});a.events.triggerEvent("removed",
+{map:this,layer:a})}},getNumLayers:function(){return this.layers.length},getLayerIndex:function(a){return OpenLayers.Util.indexOf(this.layers,a)},setLayerIndex:function(a,b){var c=this.getLayerIndex(a);0>b?b=0:b>this.layers.length&&(b=this.layers.length);if(c!=b){this.layers.splice(c,1);this.layers.splice(b,0,a);for(var c=0,d=this.layers.length;c<d;c++)this.setLayerZIndex(this.layers[c],c);this.events.triggerEvent("changelayer",{layer:a,property:"order"});this.allOverlays&&(0===b?this.setBaseLayer(a):
+this.baseLayer!==this.layers[0]&&this.setBaseLayer(this.layers[0]))}},raiseLayer:function(a,b){var c=this.getLayerIndex(a)+b;this.setLayerIndex(a,c)},setBaseLayer:function(a){if(a!=this.baseLayer&&-1!=OpenLayers.Util.indexOf(this.layers,a)){var b=this.getCachedCenter(),c=OpenLayers.Util.getResolutionFromScale(this.getScale(),a.units);null==this.baseLayer||this.allOverlays||this.baseLayer.setVisibility(!1);this.baseLayer=a;if(!this.allOverlays||this.baseLayer.visibility)this.baseLayer.setVisibility(!0),
+!1===this.baseLayer.inRange&&this.baseLayer.redraw();null!=b&&(a=this.getZoomForResolution(c||this.resolution,!0),this.setCenter(b,a,!1,!0));this.events.triggerEvent("changebaselayer",{layer:this.baseLayer})}},addControl:function(a,b){this.controls.push(a);this.addControlToMap(a,b)},addControls:function(a,b){for(var c=1===arguments.length?[]:b,d=0,e=a.length;d<e;d++)this.addControl(a[d],c[d]?c[d]:null)},addControlToMap:function(a,b){a.outsideViewport=null!=a.div;this.displayProjection&&!a.displayProjection&&
+(a.displayProjection=this.displayProjection);a.setMap(this);var c=a.draw(b);c&&!a.outsideViewport&&(c.style.zIndex=this.Z_INDEX_BASE.Control+this.controls.length,this.viewPortDiv.appendChild(c));a.autoActivate&&a.activate()},getControl:function(a){for(var b=null,c=0,d=this.controls.length;c<d;c++){var e=this.controls[c];if(e.id==a){b=e;break}}return b},removeControl:function(a){a&&a==this.getControl(a.id)&&(a.div&&a.div.parentNode==this.viewPortDiv&&this.viewPortDiv.removeChild(a.div),OpenLayers.Util.removeItem(this.controls,
+a))},addPopup:function(a,b){if(b)for(var c=this.popups.length-1;0<=c;--c)this.removePopup(this.popups[c]);a.map=this;this.popups.push(a);if(c=a.draw())c.style.zIndex=this.Z_INDEX_BASE.Popup+this.popups.length,this.layerContainerDiv.appendChild(c)},removePopup:function(a){OpenLayers.Util.removeItem(this.popups,a);if(a.div)try{this.layerContainerDiv.removeChild(a.div)}catch(b){}a.map=null},getSize:function(){var a=null;null!=this.size&&(a=this.size.clone());return a},updateSize:function(){var a=this.getCurrentSize();
+if(a&&!isNaN(a.h)&&!isNaN(a.w)){this.events.clearMouseCache();var b=this.getSize();null==b&&(this.size=b=a);if(!a.equals(b)){this.size=a;a=0;for(b=this.layers.length;a<b;a++)this.layers[a].onMapResize();a=this.getCachedCenter();null!=this.baseLayer&&null!=a&&(b=this.getZoom(),this.zoom=null,this.setCenter(a,b))}}this.events.triggerEvent("updatesize")},getCurrentSize:function(){var a=new OpenLayers.Size(this.div.clientWidth,this.div.clientHeight);if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=this.div.offsetWidth,
+a.h=this.div.offsetHeight;if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=parseInt(this.div.style.width),a.h=parseInt(this.div.style.height);return a},calculateBounds:function(a,b){var c=null;null==a&&(a=this.getCachedCenter());null==b&&(b=this.getResolution());if(null!=a&&null!=b)var c=this.size.w*b/2,d=this.size.h*b/2,c=new OpenLayers.Bounds(a.lon-c,a.lat-d,a.lon+c,a.lat+d);return c},getCenter:function(){var a=null,b=this.getCachedCenter();b&&(a=b.clone());return a},getCachedCenter:function(){!this.center&&
+this.size&&(this.center=this.getLonLatFromViewPortPx({x:this.size.w/2,y:this.size.h/2}));return this.center},getZoom:function(){return this.zoom},pan:function(a,b,c){c=OpenLayers.Util.applyDefaults(c,{animate:!0,dragging:!1});if(c.dragging)0==a&&0==b||this.moveByPx(a,b);else{var d=this.getViewPortPxFromLonLat(this.getCachedCenter());a=d.add(a,b);if(this.dragging||!a.equals(d))d=this.getLonLatFromViewPortPx(a),c.animate?this.panTo(d):(this.moveTo(d),this.dragging&&(this.dragging=!1,this.events.triggerEvent("moveend")))}},
+panTo:function(a){if(this.panTween&&this.getExtent().scale(this.panRatio).containsLonLat(a)){var b=this.getCachedCenter();if(!a.equals(b)){var b=this.getPixelFromLonLat(b),c=this.getPixelFromLonLat(a),d=0,e=0;this.panTween.start({x:0,y:0},{x:c.x-b.x,y:c.y-b.y},this.panDuration,{callbacks:{eachStep:OpenLayers.Function.bind(function(a){this.moveByPx(a.x-d,a.y-e);d=Math.round(a.x);e=Math.round(a.y)},this),done:OpenLayers.Function.bind(function(b){this.moveTo(a);this.dragging=!1;this.events.triggerEvent("moveend")},
+this)}})}}else this.setCenter(a)},setCenter:function(a,b,c,d){this.panTween&&this.panTween.stop();this.zoomTween&&this.zoomTween.stop();this.moveTo(a,b,{dragging:c,forceZoomChange:d})},moveByPx:function(a,b){var c=this.size.w/2,d=this.size.h/2,e=c+a,f=d+b,g=this.baseLayer.wrapDateLine,h=0,k=0;this.restrictedExtent&&(h=c,k=d,g=!1);a=g||e<=this.maxPx.x-h&&e>=this.minPx.x+h?Math.round(a):0;b=f<=this.maxPx.y-k&&f>=this.minPx.y+k?Math.round(b):0;if(a||b){this.dragging||(this.dragging=!0,this.events.triggerEvent("movestart"));
+this.center=null;a&&(this.layerContainerOriginPx.x-=a,this.minPx.x-=a,this.maxPx.x-=a);b&&(this.layerContainerOriginPx.y-=b,this.minPx.y-=b,this.maxPx.y-=b);this.applyTransform();d=0;for(e=this.layers.length;d<e;++d)c=this.layers[d],c.visibility&&(c===this.baseLayer||c.inRange)&&(c.moveByPx(a,b),c.events.triggerEvent("move"));this.events.triggerEvent("move")}},adjustZoom:function(a){if(this.baseLayer&&this.baseLayer.wrapDateLine){var b=this.baseLayer.resolutions,c=this.getMaxExtent().getWidth()/this.size.w;
+if(this.getResolutionForZoom(a)>c)if(this.fractionalZoom)a=this.getZoomForResolution(c);else for(var d=a|0,e=b.length;d<e;++d)if(b[d]<=c){a=d;break}}return a},getMinZoom:function(){return this.adjustZoom(0)},moveTo:function(a,b,c){null==a||a instanceof OpenLayers.LonLat||(a=new OpenLayers.LonLat(a));c||(c={});null!=b&&(b=parseFloat(b),this.fractionalZoom||(b=Math.round(b)));var d=b;b=this.adjustZoom(b);b!==d&&(a=this.getCenter());var d=c.dragging||this.dragging,e=c.forceZoomChange;this.getCachedCenter()||
+this.isValidLonLat(a)||(a=this.maxExtent.getCenterLonLat(),this.center=a.clone());if(null!=this.restrictedExtent){null==a&&(a=this.center);null==b&&(b=this.getZoom());var f=this.getResolutionForZoom(b),f=this.calculateBounds(a,f);if(!this.restrictedExtent.containsBounds(f)){var g=this.restrictedExtent.getCenterLonLat();f.getWidth()>this.restrictedExtent.getWidth()?a=new OpenLayers.LonLat(g.lon,a.lat):f.left<this.restrictedExtent.left?a=a.add(this.restrictedExtent.left-f.left,0):f.right>this.restrictedExtent.right&&
+(a=a.add(this.restrictedExtent.right-f.right,0));f.getHeight()>this.restrictedExtent.getHeight()?a=new OpenLayers.LonLat(a.lon,g.lat):f.bottom<this.restrictedExtent.bottom?a=a.add(0,this.restrictedExtent.bottom-f.bottom):f.top>this.restrictedExtent.top&&(a=a.add(0,this.restrictedExtent.top-f.top))}}e=e||this.isValidZoomLevel(b)&&b!=this.getZoom();f=this.isValidLonLat(a)&&!a.equals(this.center);if(e||f||d){d||this.events.triggerEvent("movestart",{zoomChanged:e});f&&(!e&&this.center&&this.centerLayerContainer(a),
+this.center=a.clone());a=e?this.getResolutionForZoom(b):this.getResolution();if(e||null==this.layerContainerOrigin){this.layerContainerOrigin=this.getCachedCenter();this.layerContainerOriginPx.x=0;this.layerContainerOriginPx.y=0;this.applyTransform();var f=this.getMaxExtent({restricted:!0}),h=f.getCenterLonLat(),g=this.center.lon-h.lon,h=h.lat-this.center.lat,k=Math.round(f.getWidth()/a),l=Math.round(f.getHeight()/a);this.minPx={x:(this.size.w-k)/2-g/a,y:(this.size.h-l)/2-h/a};this.maxPx={x:this.minPx.x+
+Math.round(f.getWidth()/a),y:this.minPx.y+Math.round(f.getHeight()/a)}}e&&(this.zoom=b,this.resolution=a);a=this.getExtent();this.baseLayer.visibility&&(this.baseLayer.moveTo(a,e,c.dragging),c.dragging||this.baseLayer.events.triggerEvent("moveend",{zoomChanged:e}));a=this.baseLayer.getExtent();for(b=this.layers.length-1;0<=b;--b)f=this.layers[b],f===this.baseLayer||f.isBaseLayer||(g=f.calculateInRange(),f.inRange!=g&&((f.inRange=g)||f.display(!1),this.events.triggerEvent("changelayer",{layer:f,property:"visibility"})),
+g&&f.visibility&&(f.moveTo(a,e,c.dragging),c.dragging||f.events.triggerEvent("moveend",{zoomChanged:e})));this.events.triggerEvent("move");d||this.events.triggerEvent("moveend");if(e){b=0;for(c=this.popups.length;b<c;b++)this.popups[b].updatePosition();this.events.triggerEvent("zoomend")}}},centerLayerContainer:function(a){var b=this.getViewPortPxFromLonLat(this.layerContainerOrigin),c=this.getViewPortPxFromLonLat(a);if(null!=b&&null!=c){var d=this.layerContainerOriginPx.x;a=this.layerContainerOriginPx.y;
+var e=Math.round(b.x-c.x),b=Math.round(b.y-c.y);this.applyTransform(this.layerContainerOriginPx.x=e,this.layerContainerOriginPx.y=b);d-=e;a-=b;this.minPx.x-=d;this.maxPx.x-=d;this.minPx.y-=a;this.maxPx.y-=a}},isValidZoomLevel:function(a){return null!=a&&0<=a&&a<this.getNumZoomLevels()},isValidLonLat:function(a){var b=!1;null!=a&&(b=this.getMaxExtent(),b=b.containsLonLat(a,{worldBounds:this.baseLayer.wrapDateLine&&b}));return b},getProjection:function(){var a=this.getProjectionObject();return a?a.getCode():
+null},getProjectionObject:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.projection);return a},getMaxResolution:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.maxResolution);return a},getMaxExtent:function(a){var b=null;a&&a.restricted&&this.restrictedExtent?b=this.restrictedExtent:null!=this.baseLayer&&(b=this.baseLayer.maxExtent);return b},getNumZoomLevels:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.numZoomLevels);return a},getExtent:function(){var a=
+null;null!=this.baseLayer&&(a=this.baseLayer.getExtent());return a},getResolution:function(){var a=null;null!=this.baseLayer?a=this.baseLayer.getResolution():!0===this.allOverlays&&0<this.layers.length&&(a=this.layers[0].getResolution());return a},getUnits:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.units);return a},getScale:function(){var a=null;null!=this.baseLayer&&(a=this.getResolution(),a=OpenLayers.Util.getScaleFromResolution(a,this.baseLayer.units));return a},getZoomForExtent:function(a,
+b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForExtent(a,b));return c},getResolutionForZoom:function(a){var b=null;this.baseLayer&&(b=this.baseLayer.getResolutionForZoom(a));return b},getZoomForResolution:function(a,b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForResolution(a,b));return c},zoomTo:function(a,b){var c=this;if(c.isValidZoomLevel(a))if(c.baseLayer.wrapDateLine&&(a=c.adjustZoom(a)),c.zoomTween){var d=c.getResolution(),e=c.getResolutionForZoom(a),f={scale:1},
+d={scale:d/e};c.zoomTween.playing&&c.zoomTween.duration<3*c.zoomDuration?c.zoomTween.finish={scale:c.zoomTween.finish.scale*d.scale}:(b||(e=c.getSize(),b={x:e.w/2,y:e.h/2}),c.zoomTween.start(f,d,c.zoomDuration,{minFrameRate:50,callbacks:{eachStep:function(a){var d=c.layerContainerOriginPx;a=a.scale;c.applyTransform(d.x+((a-1)*(d.x-b.x)|0),d.y+((a-1)*(d.y-b.y)|0),a)},done:function(a){c.applyTransform();a=c.getResolution()/a.scale;var d=c.getZoomForResolution(a,!0);c.moveTo(c.getZoomTargetCenter(b,
+a),d,!0)}}}))}else f=b?c.getZoomTargetCenter(b,c.getResolutionForZoom(a)):null,c.setCenter(f,a)},zoomIn:function(){this.zoomTo(this.getZoom()+1)},zoomOut:function(){this.zoomTo(this.getZoom()-1)},zoomToExtent:function(a,b){a instanceof OpenLayers.Bounds||(a=new OpenLayers.Bounds(a));var c=a.getCenterLonLat();if(this.baseLayer.wrapDateLine){c=this.getMaxExtent();for(a=a.clone();a.right<a.left;)a.right+=c.getWidth();c=a.getCenterLonLat().wrapDateLine(c)}this.setCenter(c,this.getZoomForExtent(a,b))},
+zoomToMaxExtent:function(a){a=this.getMaxExtent({restricted:a?a.restricted:!0});this.zoomToExtent(a)},zoomToScale:function(a,b){var c=OpenLayers.Util.getResolutionFromScale(a,this.baseLayer.units),d=this.size.w*c/2,c=this.size.h*c/2,e=this.getCachedCenter(),d=new OpenLayers.Bounds(e.lon-d,e.lat-c,e.lon+d,e.lat+c);this.zoomToExtent(d,b)},getLonLatFromViewPortPx:function(a){var b=null;null!=this.baseLayer&&(b=this.baseLayer.getLonLatFromViewPortPx(a));return b},getViewPortPxFromLonLat:function(a){var b=
+null;null!=this.baseLayer&&(b=this.baseLayer.getViewPortPxFromLonLat(a));return b},getZoomTargetCenter:function(a,b){var c=null,d=this.getSize(),e=d.w/2-a.x,d=a.y-d.h/2,f=this.getLonLatFromPixel(a);f&&(c=new OpenLayers.LonLat(f.lon+e*b,f.lat+d*b));return c},getLonLatFromPixel:function(a){return this.getLonLatFromViewPortPx(a)},getPixelFromLonLat:function(a){a=this.getViewPortPxFromLonLat(a);a.x=Math.round(a.x);a.y=Math.round(a.y);return a},getGeodesicPixelSize:function(a){var b=a?this.getLonLatFromPixel(a):
+this.getCachedCenter()||new OpenLayers.LonLat(0,0),c=this.getResolution();a=b.add(-c/2,0);var d=b.add(c/2,0),e=b.add(0,-c/2),b=b.add(0,c/2),c=new OpenLayers.Projection("EPSG:4326"),f=this.getProjectionObject()||c;f.equals(c)||(a.transform(f,c),d.transform(f,c),e.transform(f,c),b.transform(f,c));return new OpenLayers.Size(OpenLayers.Util.distVincenty(a,d),OpenLayers.Util.distVincenty(e,b))},getViewPortPxFromLayerPx:function(a){var b=null;null!=a&&(b=a.add(this.layerContainerOriginPx.x,this.layerContainerOriginPx.y));
+return b},getLayerPxFromViewPortPx:function(a){var b=null;null!=a&&(b=a.add(-this.layerContainerOriginPx.x,-this.layerContainerOriginPx.y),isNaN(b.x)||isNaN(b.y))&&(b=null);return b},getLonLatFromLayerPx:function(a){a=this.getViewPortPxFromLayerPx(a);return this.getLonLatFromViewPortPx(a)},getLayerPxFromLonLat:function(a){a=this.getPixelFromLonLat(a);return this.getLayerPxFromViewPortPx(a)},applyTransform:function(a,b,c){c=c||1;var d=this.layerContainerOriginPx,e=1!==c;a=a||d.x;b=b||d.y;var f=this.layerContainerDiv.style,
+g=this.applyTransform.transform,h=this.applyTransform.template;if(void 0===g&&(g=OpenLayers.Util.vendorPrefix.style("transform"),this.applyTransform.transform=g)){var k=OpenLayers.Element.getStyle(this.viewPortDiv,OpenLayers.Util.vendorPrefix.css("transform"));k&&"none"===k||(h=["translate3d(",",0) ","scale3d(",",1)"],f[g]=[h[0],"0,0",h[1]].join(""));h&&~f[g].indexOf(h[0])||(h=["translate(",") ","scale(",")"]);this.applyTransform.template=h}null===g||"translate3d("!==h[0]&&!0!==e?(f.left=a+"px",f.top=
+b+"px",null!==g&&(f[g]="")):(!0===e&&"translate("===h[0]&&(a-=d.x,b-=d.y,f.left=d.x+"px",f.top=d.y+"px"),f[g]=[h[0],a,"px,",b,"px",h[1],h[2],c,",",c,h[3]].join(""))},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:!1,evt:null,touch:!1,initialize:function(a,b,c){OpenLayers.Util.extend(this,c);this.control=a;this.callbacks=b;(a=this.map||a.map)&&this.setMap(a);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},setMap:function(a){this.map=a},checkModifiers:function(a){return null==this.keyMask?!0:((a.shiftKey?OpenLayers.Handler.MOD_SHIFT:0)|(a.ctrlKey?OpenLayers.Handler.MOD_CTRL:0)|(a.altKey?OpenLayers.Handler.MOD_ALT:
+0)|(a.metaKey?OpenLayers.Handler.MOD_META:0))==this.keyMask},activate:function(){if(this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.register(a[b],this[a[b]]);return this.active=!0},deactivate:function(){if(!this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]]);this.active=this.touch=!1;return!0},startTouch:function(){if(!this.touch){this.touch=!0;
+for(var a="mousedown mouseup mousemove click dblclick mouseout".split(" "),b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]])}},callback:function(a,b){a&&this.callbacks[a]&&this.callbacks[a].apply(this.control,b)},register:function(a,b){this.map.events.registerPriority(a,this,b);this.map.events.registerPriority(a,this,this.setEvent)},unregister:function(a,b){this.map.events.unregister(a,this,b);this.map.events.unregister(a,this,this.setEvent)},setEvent:function(a){this.evt=a;return!0},
+destroy:function(){this.deactivate();this.control=this.map=null},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Handler.MOD_META=8;OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:!0,"double":!1,pixelTolerance:0,dblclickTolerance:13,stopSingle:!1,stopDouble:!1,timerId:null,down:null,last:null,first:null,rightclickTimerId:null,touchstart:function(a){this.startTouch();this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},touchmove:function(a){this.last=this.getEventInfo(a);return!0},touchend:function(a){this.down&&(a.xy=this.last.xy,a.lastTouches=this.last.touches,this.handleSingle(a),
+this.down=null);return!0},mousedown:function(a){this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},mouseup:function(a){var b=!0;this.checkModifiers(a)&&(this.control.handleRightClicks&&OpenLayers.Event.isRightClick(a))&&(b=this.rightclick(a));return b},rightclick:function(a){if(this.passesTolerance(a)){if(null!=this.rightclickTimerId)return this.clearTimer(),this.callback("dblrightclick",[a]),!this.stopDouble;a=this["double"]?OpenLayers.Util.extend({},a):this.callback("rightclick",
+[a]);a=OpenLayers.Function.bind(this.delayedRightCall,this,a);this.rightclickTimerId=window.setTimeout(a,this.delay)}return!this.stopSingle},delayedRightCall:function(a){this.rightclickTimerId=null;a&&this.callback("rightclick",[a])},click:function(a){this.last||(this.last=this.getEventInfo(a));this.handleSingle(a);return!this.stopSingle},dblclick:function(a){this.handleDouble(a);return!this.stopDouble},handleDouble:function(a){this.passesDblclickTolerance(a)&&(this["double"]&&this.callback("dblclick",
+[a]),this.clearTimer())},handleSingle:function(a){this.passesTolerance(a)&&(null!=this.timerId?(this.last.touches&&1===this.last.touches.length&&(this["double"]&&OpenLayers.Event.preventDefault(a),this.handleDouble(a)),this.last.touches&&2===this.last.touches.length||this.clearTimer()):(this.first=this.getEventInfo(a),a=this.single?OpenLayers.Util.extend({},a):null,this.queuePotentialClick(a)))},queuePotentialClick:function(a){this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,
+this,a),this.delay)},passesTolerance:function(a){var b=!0;if(null!=this.pixelTolerance&&this.down&&this.down.xy&&(b=this.pixelTolerance>=this.down.xy.distanceTo(a.xy))&&this.touch&&this.down.touches.length===this.last.touches.length){a=0;for(var c=this.down.touches.length;a<c;++a)if(this.getTouchDistance(this.down.touches[a],this.last.touches[a])>this.pixelTolerance){b=!1;break}}return b},getTouchDistance:function(a,b){return Math.sqrt(Math.pow(a.clientX-b.clientX,2)+Math.pow(a.clientY-b.clientY,
+2))},passesDblclickTolerance:function(a){a=!0;this.down&&this.first&&(a=this.down.xy.distanceTo(this.first.xy)<=this.dblclickTolerance);return a},clearTimer:function(){null!=this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);null!=this.rightclickTimerId&&(window.clearTimeout(this.rightclickTimerId),this.rightclickTimerId=null)},delayedCall:function(a){this.timerId=null;a&&this.callback("click",[a])},getEventInfo:function(a){var b;if(a.touches){var c=a.touches.length;b=Array(c);for(var d,
+e=0;e<c;e++)d=a.touches[e],b[e]={clientX:d.olClientX,clientY:d.olClientY}}return{xy:a.xy,touches:b}},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.clearTimer(),this.last=this.first=this.down=null,a=!0);return a},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!0,dragging:!1,last:null,start:null,lastMoveEvt:null,oldOnselectstart:null,interval:0,timeoutId:null,documentDrag:!1,documentEvents:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(!0===this.documentDrag){var d=this;this._docMove=function(a){d.mousemove({xy:{x:a.clientX,y:a.clientY},element:document})};this._docUp=function(a){d.mouseup({xy:{x:a.clientX,y:a.clientY}})}}},
+dragstart:function(a){var b=!0;this.dragging=!1;this.checkModifiers(a)&&(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))?(this.started=!0,this.last=this.start=a.xy,OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown"),this.down(a),this.callback("down",[a.xy]),OpenLayers.Event.preventDefault(a),this.oldOnselectstart||(this.oldOnselectstart=document.onselectstart?document.onselectstart:OpenLayers.Function.True),document.onselectstart=OpenLayers.Function.False,b=!this.stopDown):
+(this.started=!1,this.last=this.start=null);return b},dragmove:function(a){this.lastMoveEvt=a;!this.started||(this.timeoutId||a.xy.x==this.last.x&&a.xy.y==this.last.y)||(!0===this.documentDrag&&this.documentEvents&&(a.element===document?(this.adjustXY(a),this.setEvent(a)):this.removeDocumentEvents()),0<this.interval&&(this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval)),this.dragging=!0,this.move(a),this.callback("move",[a.xy]),this.oldOnselectstart||(this.oldOnselectstart=
+document.onselectstart,document.onselectstart=OpenLayers.Function.False),this.last=a.xy);return!0},dragend:function(a){if(this.started){!0===this.documentDrag&&this.documentEvents&&(this.adjustXY(a),this.removeDocumentEvents());var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(a);this.callback("up",[a.xy]);b&&this.callback("done",[a.xy]);document.onselectstart=this.oldOnselectstart}return!0},down:function(a){},move:function(a){},
+up:function(a){},out:function(a){},mousedown:function(a){return this.dragstart(a)},touchstart:function(a){this.startTouch();return this.dragstart(a)},mousemove:function(a){return this.dragmove(a)},touchmove:function(a){return this.dragmove(a)},removeTimeout:function(){this.timeoutId=null;this.dragging&&this.mousemove(this.lastMoveEvt)},mouseup:function(a){return this.dragend(a)},touchend:function(a){a.xy=this.last;return this.dragend(a)},mouseout:function(a){if(this.started&&OpenLayers.Util.mouseLeft(a,
+this.map.viewPortDiv))if(!0===this.documentDrag)this.addDocumentEvents();else{var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(a);this.callback("out",[]);b&&this.callback("done",[a.xy]);document.onselectstart&&(document.onselectstart=this.oldOnselectstart)}return!0},click:function(a){return this.start==this.last},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.dragging=
+!1,a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.dragging=this.started=!1,this.last=this.start=null,a=!0,OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown"));return a},adjustXY:function(a){var b=OpenLayers.Util.pagePosition(this.map.viewPortDiv);a.xy.x-=b[0];a.xy.y-=b[1]},addDocumentEvents:function(){OpenLayers.Element.addClass(document.body,"olDragDown");this.documentEvents=!0;OpenLayers.Event.observe(document,"mousemove",
+this._docMove);OpenLayers.Event.observe(document,"mouseup",this._docUp)},removeDocumentEvents:function(){OpenLayers.Element.removeClass(document.body,"olDragDown");this.documentEvents=!1;OpenLayers.Event.stopObserving(document,"mousemove",this._docMove);OpenLayers.Event.stopObserving(document,"mouseup",this._docUp)},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:{w:180,h:90},layers:null,minRectSize:15,minRectDisplayClass:"RectReplacement",minRatio:8,maxRatio:32,mapOptions:null,autoPan:!1,handlers:null,resolutionFactor:1,maximized:!1,maximizeTitle:"",minimizeTitle:"",initialize:function(a){this.layers=[];this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,[a])},destroy:function(){this.mapDiv&&(this.handlers.click&&this.handlers.click.destroy(),
+this.handlers.drag&&this.handlers.drag.destroy(),this.ovmap&&this.ovmap.viewPortDiv.removeChild(this.extentRectangle),this.extentRectangle=null,this.rectEvents&&(this.rectEvents.destroy(),this.rectEvents=null),this.ovmap&&(this.ovmap.destroy(),this.ovmap=null),this.element.removeChild(this.mapDiv),this.mapDiv=null,this.div.removeChild(this.element),this.element=null,this.maximizeDiv&&(this.div.removeChild(this.maximizeDiv),this.maximizeDiv=null),this.minimizeDiv&&(this.div.removeChild(this.minimizeDiv),
+this.minimizeDiv=null),this.map.events.un({buttonclick:this.onButtonClick,moveend:this.update,changebaselayer:this.baseLayerDraw,scope:this}),OpenLayers.Control.prototype.destroy.apply(this,arguments))},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(0===this.layers.length)if(this.map.baseLayer)this.layers=[this.map.baseLayer.clone()];else return this.map.events.register("changebaselayer",this,this.baseLayerDraw),this.div;this.element=document.createElement("div");this.element.className=
+this.displayClass+"Element";this.element.style.display="none";this.mapDiv=document.createElement("div");this.mapDiv.style.width=this.size.w+"px";this.mapDiv.style.height=this.size.h+"px";this.mapDiv.style.position="relative";this.mapDiv.style.overflow="hidden";this.mapDiv.id=OpenLayers.Util.createUniqueID("overviewMap");this.extentRectangle=document.createElement("div");this.extentRectangle.style.position="absolute";this.extentRectangle.style.zIndex=1E3;this.extentRectangle.className=this.displayClass+
+"ExtentRectangle";this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(this.outsideViewport)this.element.style.display="";else{this.div.className+=" "+this.displayClass+"Container";var a=OpenLayers.Util.getImageLocation("layer-switcher-maximize.png");this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+"MaximizeButton",null,null,a,"absolute");this.maximizeDiv.style.display="none";this.maximizeDiv.className=this.displayClass+"MaximizeButton olButton";this.maximizeTitle&&
+(this.maximizeDiv.title=this.maximizeTitle);this.div.appendChild(this.maximizeDiv);a=OpenLayers.Util.getImageLocation("layer-switcher-minimize.png");this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_minimizeDiv",null,null,a,"absolute");this.minimizeDiv.style.display="none";this.minimizeDiv.className=this.displayClass+"MinimizeButton olButton";this.minimizeTitle&&(this.minimizeDiv.title=this.minimizeTitle);this.div.appendChild(this.minimizeDiv);this.minimizeControl()}this.map.getExtent()&&
+this.update();this.map.events.on({buttonclick:this.onButtonClick,moveend:this.update,scope:this});this.maximized&&this.maximizeControl();return this.div},baseLayerDraw:function(){this.draw();this.map.events.unregister("changebaselayer",this,this.baseLayerDraw)},rectDrag:function(a){var b=this.handlers.drag.last.x-a.x,c=this.handlers.drag.last.y-a.y;if(0!=b||0!=c){var d=this.rectPxBounds.top,e=this.rectPxBounds.left;a=Math.abs(this.rectPxBounds.getHeight());var f=this.rectPxBounds.getWidth(),c=Math.max(0,
+d-c),c=Math.min(c,this.ovmap.size.h-this.hComp-a),b=Math.max(0,e-b),b=Math.min(b,this.ovmap.size.w-this.wComp-f);this.setRectPxBounds(new OpenLayers.Bounds(b,c+a,b+f,c))}},mapDivClick:function(a){var b=this.rectPxBounds.getCenterPixel(),c=a.xy.x-b.x,d=a.xy.y-b.y,e=this.rectPxBounds.top,f=this.rectPxBounds.left;a=Math.abs(this.rectPxBounds.getHeight());b=this.rectPxBounds.getWidth();d=Math.max(0,e+d);d=Math.min(d,this.ovmap.size.h-a);c=Math.max(0,f+c);c=Math.min(c,this.ovmap.size.w-b);this.setRectPxBounds(new OpenLayers.Bounds(c,
+d+a,c+b,d));this.updateMapToRect()},onButtonClick:function(a){a.buttonElement===this.minimizeDiv?this.minimizeControl():a.buttonElement===this.maximizeDiv&&this.maximizeControl()},maximizeControl:function(a){this.element.style.display="";this.showToggle(!1);null!=a&&OpenLayers.Event.stop(a)},minimizeControl:function(a){this.element.style.display="none";this.showToggle(!0);null!=a&&OpenLayers.Event.stop(a)},showToggle:function(a){this.maximizeDiv&&(this.maximizeDiv.style.display=a?"":"none");this.minimizeDiv&&
+(this.minimizeDiv.style.display=a?"none":"")},update:function(){null==this.ovmap&&this.createMap();!this.autoPan&&this.isSuitableOverview()||this.updateOverview();this.updateRectToMap()},isSuitableOverview:function(){var a=this.map.getExtent(),b=this.map.getMaxExtent(),a=new OpenLayers.Bounds(Math.max(a.left,b.left),Math.max(a.bottom,b.bottom),Math.min(a.right,b.right),Math.min(a.top,b.top));this.ovmap.getProjection()!=this.map.getProjection()&&(a=a.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject()));
+b=this.ovmap.getResolution()/this.map.getResolution();return b>this.minRatio&&b<=this.maxRatio&&this.ovmap.getExtent().containsBounds(a)},updateOverview:function(){var a=this.map.getResolution(),b=this.ovmap.getResolution(),c=b/a;c>this.maxRatio?b=this.minRatio*a:c<=this.minRatio&&(b=this.maxRatio*a);this.ovmap.getProjection()!=this.map.getProjection()?(a=this.map.center.clone(),a.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject())):a=this.map.center;this.ovmap.setCenter(a,
+this.ovmap.getZoomForResolution(b*this.resolutionFactor));this.updateRectToMap()},createMap:function(){var a=OpenLayers.Util.extend({controls:[],maxResolution:"auto",fallThrough:!1},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,a);this.ovmap.viewPortDiv.appendChild(this.extentRectangle);OpenLayers.Event.stopObserving(window,"unload",this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=(this.wComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+"border-left-width"))+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,"border-right-width")))?this.wComp:2;this.hComp=(this.hComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,"border-top-width"))+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,"border-bottom-width")))?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{click:this.mapDivClick},
+{single:!0,"double":!1,stopSingle:!0,stopDouble:!0,pixelTolerance:1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,!0);this.rectEvents.register("mouseover",this,function(a){this.handlers.drag.active||this.map.dragging||this.handlers.drag.activate()});this.rectEvents.register("mouseout",this,function(a){this.handlers.drag.dragging||this.handlers.drag.deactivate()});if(this.ovmap.getProjection()!=this.map.getProjection()){var a=this.map.getProjectionObject().getUnits()||
+this.map.units||this.map.baseLayer.units,b=this.ovmap.getProjectionObject().getUnits()||this.ovmap.units||this.ovmap.baseLayer.units;this.resolutionFactor=a&&b?OpenLayers.INCHES_PER_UNIT[a]/OpenLayers.INCHES_PER_UNIT[b]:1}},updateRectToMap:function(){var a;a=this.ovmap.getProjection()!=this.map.getProjection()?this.map.getExtent().transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject()):this.map.getExtent();(a=this.getRectBoundsFromMapBounds(a))&&this.setRectPxBounds(a)},updateMapToRect:function(){var a=
+this.getMapBoundsFromRectBounds(this.rectPxBounds);this.ovmap.getProjection()!=this.map.getProjection()&&(a=a.transform(this.ovmap.getProjectionObject(),this.map.getProjectionObject()));this.map.panTo(a.getCenterLonLat())},setRectPxBounds:function(a){var b=Math.max(a.top,0),c=Math.max(a.left,0),d=Math.min(a.top+Math.abs(a.getHeight()),this.ovmap.size.h-this.hComp);a=Math.min(a.left+a.getWidth(),this.ovmap.size.w-this.wComp);var e=Math.max(a-c,0),f=Math.max(d-b,0);e<this.minRectSize||f<this.minRectSize?
+(this.extentRectangle.className=this.displayClass+this.minRectDisplayClass,e=c+e/2-this.minRectSize/2,this.extentRectangle.style.top=Math.round(b+f/2-this.minRectSize/2)+"px",this.extentRectangle.style.left=Math.round(e)+"px",this.extentRectangle.style.height=this.minRectSize+"px",this.extentRectangle.style.width=this.minRectSize+"px"):(this.extentRectangle.className=this.displayClass+"ExtentRectangle",this.extentRectangle.style.top=Math.round(b)+"px",this.extentRectangle.style.left=Math.round(c)+
+"px",this.extentRectangle.style.height=Math.round(f)+"px",this.extentRectangle.style.width=Math.round(e)+"px");this.rectPxBounds=new OpenLayers.Bounds(Math.round(c),Math.round(d),Math.round(a),Math.round(b))},getRectBoundsFromMapBounds:function(a){var b=this.getOverviewPxFromLonLat({lon:a.left,lat:a.bottom});a=this.getOverviewPxFromLonLat({lon:a.right,lat:a.top});var c=null;b&&a&&(c=new OpenLayers.Bounds(b.x,b.y,a.x,a.y));return c},getMapBoundsFromRectBounds:function(a){var b=this.getLonLatFromOverviewPx({x:a.left,
+y:a.bottom});a=this.getLonLatFromOverviewPx({x:a.right,y:a.top});return new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat)},getLonLatFromOverviewPx:function(a){var b=this.ovmap.size,c=this.ovmap.getResolution(),d=this.ovmap.getExtent().getCenterLonLat();return{lon:d.lon+(a.x-b.w/2)*c,lat:d.lat-(a.y-b.h/2)*c}},getOverviewPxFromLonLat:function(a){var b=this.ovmap.getResolution(),c=this.ovmap.getExtent();if(c)return{x:Math.round(1/b*(a.lon-c.left)),y:Math.round(1/b*(c.top-a.lat))}},CLASS_NAME:"OpenLayers.Control.OverviewMap"});OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:1,alwaysInRange:null,RESOLUTION_PROPERTIES:"scales resolutions maxScale minScale maxResolution minResolution numZoomLevels maxZoomLevel".split(" "),events:null,map:null,isBaseLayer:!1,alpha:!1,displayInLayerSwitcher:!0,visibility:!0,attribution:null,inRange:!1,imageSize:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,
+numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:!1,wrapDateLine:!1,metadata:null,initialize:function(a,b){this.metadata={};b=OpenLayers.Util.extend({},b);null!=this.alwaysInRange&&(b.alwaysInRange=this.alwaysInRange);this.addOptions(b);this.name=a;if(null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"),this.div=OpenLayers.Util.createDiv(this.id),this.div.style.width="100%",this.div.style.height="100%",this.div.dir="ltr",this.events=new OpenLayers.Events(this,
+this.div),this.eventListeners instanceof Object))this.events.on(this.eventListeners)},destroy:function(a){null==a&&(a=!0);null!=this.map&&this.map.removeLayer(this,a);this.options=this.div=this.name=this.map=this.projection=null;this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy());this.events=this.eventListeners=null},clone:function(a){null==a&&(a=new OpenLayers.Layer(this.name,this.getOptions()));OpenLayers.Util.applyDefaults(a,this);a.map=null;return a},
+getOptions:function(){var a={},b;for(b in this.options)a[b]=this[b];return a},setName:function(a){a!=this.name&&(this.name=a,null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"name"}))},addOptions:function(a,b){null==this.options&&(this.options={});a&&("string"==typeof a.projection&&(a.projection=new OpenLayers.Projection(a.projection)),a.projection&&OpenLayers.Util.applyDefaults(a,OpenLayers.Projection.defaults[a.projection.getCode()]),!a.maxExtent||a.maxExtent instanceof
+OpenLayers.Bounds||(a.maxExtent=new OpenLayers.Bounds(a.maxExtent)),!a.minExtent||a.minExtent instanceof OpenLayers.Bounds||(a.minExtent=new OpenLayers.Bounds(a.minExtent)));OpenLayers.Util.extend(this.options,a);OpenLayers.Util.extend(this,a);this.projection&&this.projection.getUnits()&&(this.units=this.projection.getUnits());if(this.map){var c=this.map.getResolution(),d=this.RESOLUTION_PROPERTIES.concat(["projection","units","minExtent","maxExtent"]),e;for(e in a)if(a.hasOwnProperty(e)&&0<=OpenLayers.Util.indexOf(d,
+e)){this.initResolutions();b&&this.map.baseLayer===this&&(this.map.setCenter(this.map.getCenter(),this.map.getZoomForResolution(c),!1,!0),this.map.events.triggerEvent("changebaselayer",{layer:this}));break}}},onMapResize:function(){},redraw:function(){var a=!1;if(this.map){this.inRange=this.calculateInRange();var b=this.getExtent();b&&(this.inRange&&this.visibility)&&(this.moveTo(b,!0,!1),this.events.triggerEvent("moveend",{zoomChanged:!0}),a=!0)}return a},moveTo:function(a,b,c){a=this.visibility;
+this.isBaseLayer||(a=a&&this.inRange);this.display(a)},moveByPx:function(a,b){},setMap:function(a){null==this.map&&(this.map=a,this.maxExtent=this.maxExtent||this.map.maxExtent,this.minExtent=this.minExtent||this.map.minExtent,this.projection=this.projection||this.map.projection,"string"==typeof this.projection&&(this.projection=new OpenLayers.Projection(this.projection)),this.units=this.projection.getUnits()||this.units||this.map.units,this.initResolutions(),this.isBaseLayer||(this.inRange=this.calculateInRange(),
+this.div.style.display=this.visibility&&this.inRange?"":"none"),this.setTileSize())},afterAdd:function(){},removeMap:function(a){},getImageSize:function(a){return this.imageSize||this.tileSize},setTileSize:function(a){this.tileSize=a=a?a:this.tileSize?this.tileSize:this.map.getTileSize();this.gutter&&(this.imageSize=new OpenLayers.Size(a.w+2*this.gutter,a.h+2*this.gutter))},getVisibility:function(){return this.visibility},setVisibility:function(a){a!=this.visibility&&(this.visibility=a,this.display(a),
+this.redraw(),null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"}),this.events.triggerEvent("visibilitychanged"))},display:function(a){a!=("none"!=this.div.style.display)&&(this.div.style.display=a&&this.calculateInRange()?"block":"none")},calculateInRange:function(){var a=!1;this.alwaysInRange?a=!0:this.map&&(a=this.map.getResolution(),a=a>=this.minResolution&&a<=this.maxResolution);return a},setIsBaseLayer:function(a){a!=this.isBaseLayer&&(this.isBaseLayer=
+a,null!=this.map&&this.map.events.triggerEvent("changebaselayer",{layer:this}))},initResolutions:function(){var a,b,c,d={},e=!0;a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=this.options[c],e&&this.options[c]&&(e=!1);null==this.options.alwaysInRange&&(this.alwaysInRange=e);null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d));if(null==d.resolutions){a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<
+b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=null!=this.options[c]?this.options[c]:this.map[c];null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d))}var f;this.options.maxResolution&&"auto"!==this.options.maxResolution&&(f=this.options.maxResolution);this.options.minScale&&(f=OpenLayers.Util.getResolutionFromScale(this.options.minScale,this.units));var g;this.options.minResolution&&"auto"!==this.options.minResolution&&
+(g=this.options.minResolution);this.options.maxScale&&(g=OpenLayers.Util.getResolutionFromScale(this.options.maxScale,this.units));d.resolutions&&(d.resolutions.sort(function(a,b){return b-a}),f||(f=d.resolutions[0]),g||(g=d.resolutions[d.resolutions.length-1]));if(this.resolutions=d.resolutions){b=this.resolutions.length;this.scales=Array(b);for(a=0;a<b;a++)this.scales[a]=OpenLayers.Util.getScaleFromResolution(this.resolutions[a],this.units);this.numZoomLevels=b}if(this.minResolution=g)this.maxScale=
+OpenLayers.Util.getScaleFromResolution(g,this.units);if(this.maxResolution=f)this.minScale=OpenLayers.Util.getScaleFromResolution(f,this.units)},resolutionsFromScales:function(a){if(null!=a){var b,c,d;d=a.length;b=Array(d);for(c=0;c<d;c++)b[c]=OpenLayers.Util.getResolutionFromScale(a[c],this.units);return b}},calculateResolutions:function(a){var b,c,d=a.maxResolution;null!=a.minScale?d=OpenLayers.Util.getResolutionFromScale(a.minScale,this.units):"auto"==d&&null!=this.maxExtent&&(b=this.map.getSize(),
+c=this.maxExtent.getWidth()/b.w,b=this.maxExtent.getHeight()/b.h,d=Math.max(c,b));c=a.minResolution;null!=a.maxScale?c=OpenLayers.Util.getResolutionFromScale(a.maxScale,this.units):"auto"==a.minResolution&&null!=this.minExtent&&(b=this.map.getSize(),c=this.minExtent.getWidth()/b.w,b=this.minExtent.getHeight()/b.h,c=Math.max(c,b));"number"!==typeof d&&("number"!==typeof c&&null!=this.maxExtent)&&(d=this.map.getTileSize(),d=Math.max(this.maxExtent.getWidth()/d.w,this.maxExtent.getHeight()/d.h));b=a.maxZoomLevel;
+a=a.numZoomLevels;"number"===typeof c&&"number"===typeof d&&void 0===a?a=Math.floor(Math.log(d/c)/Math.log(2))+1:void 0===a&&null!=b&&(a=b+1);if(!("number"!==typeof a||0>=a||"number"!==typeof d&&"number"!==typeof c)){b=Array(a);var e=2;"number"==typeof c&&"number"==typeof d&&(e=Math.pow(d/c,1/(a-1)));var f;if("number"===typeof d)for(f=0;f<a;f++)b[f]=d/Math.pow(e,f);else for(f=0;f<a;f++)b[a-1-f]=c*Math.pow(e,f);return b}},getResolution:function(){var a=this.map.getZoom();return this.getResolutionForZoom(a)},
+getExtent:function(){return this.map.calculateBounds()},getZoomForExtent:function(a,b){var c=this.map.getSize(),c=Math.max(a.getWidth()/c.w,a.getHeight()/c.h);return this.getZoomForResolution(c,b)},getDataExtent:function(){},getResolutionForZoom:function(a){a=Math.max(0,Math.min(a,this.resolutions.length-1));if(this.map.fractionalZoom){var b=Math.floor(a),c=Math.ceil(a);a=this.resolutions[b]-(a-b)*(this.resolutions[b]-this.resolutions[c])}else a=this.resolutions[Math.round(a)];return a},getZoomForResolution:function(a,
+b){var c,d;if(this.map.fractionalZoom){var e=0,f=this.resolutions[e],g=this.resolutions[this.resolutions.length-1],h;c=0;for(d=this.resolutions.length;c<d;++c)if(h=this.resolutions[c],h>=a&&(f=h,e=c),h<=a){g=h;break}c=f-g;c=0<c?e+(f-a)/c:e}else{f=Number.POSITIVE_INFINITY;c=0;for(d=this.resolutions.length;c<d;c++)if(b){e=Math.abs(this.resolutions[c]-a);if(e>f)break;f=e}else if(this.resolutions[c]<a)break;c=Math.max(0,c-1)}return c},getLonLatFromViewPortPx:function(a){var b=null,c=this.map;if(null!=
+a&&c.minPx){var b=c.getResolution(),d=c.getMaxExtent({restricted:!0}),b=new OpenLayers.LonLat((a.x-c.minPx.x)*b+d.left,(c.minPx.y-a.y)*b+d.top);this.wrapDateLine&&(b=b.wrapDateLine(this.maxExtent))}return b},getViewPortPxFromLonLat:function(a,b){var c=null;null!=a&&(b=b||this.map.getResolution(),c=this.map.calculateBounds(null,b),c=new OpenLayers.Pixel(1/b*(a.lon-c.left),1/b*(c.top-a.lat)));return c},setOpacity:function(a){if(a!=this.opacity){this.opacity=a;for(var b=this.div.childNodes,c=0,d=b.length;c<
+d;++c){var e=b[c].firstChild||b[c],f=b[c].lastChild;f&&"iframe"===f.nodeName.toLowerCase()&&(e=f.parentNode);OpenLayers.Util.modifyDOMElement(e,null,null,null,null,null,null,a)}null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"})}},getZIndex:function(){return this.div.style.zIndex},setZIndex:function(a){this.div.style.zIndex=a},adjustBounds:function(a){if(this.gutter){var b=this.gutter*this.map.getResolution();a=new OpenLayers.Bounds(a.left-b,a.bottom-b,a.right+
+b,a.top+b)}this.wrapDateLine&&(b={rightTolerance:this.getResolution(),leftTolerance:this.getResolution()},a=a.wrapDateLine(this.maxExtent,b));return a},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Layer.SphericalMercator={getExtent:function(){var a=null;return a=this.sphericalMercator?this.map.calculateBounds():OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this)},getLonLatFromViewPortPx:function(a){return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this,arguments)},getViewPortPxFromLonLat:function(a){return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this,arguments)},initMercatorParameters:function(){this.RESOLUTIONS=[];for(var a=0;a<=this.MAX_ZOOM_LEVEL;++a)this.RESOLUTIONS[a]=
+156543.03390625/Math.pow(2,a);this.units="m";this.projection=this.projection||"EPSG:900913"},forwardMercator:function(){var a=new OpenLayers.Projection("EPSG:4326"),b=new OpenLayers.Projection("EPSG:900913");return function(c,d){var e=OpenLayers.Projection.transform({x:c,y:d},a,b);return new OpenLayers.LonLat(e.x,e.y)}}(),inverseMercator:function(){var a=new OpenLayers.Projection("EPSG:4326"),b=new OpenLayers.Projection("EPSG:900913");return function(c,d){var e=OpenLayers.Projection.transform({x:c,
+y:d},b,a);return new OpenLayers.LonLat(e.x,e.y)}}()};OpenLayers.Layer.EventPane=OpenLayers.Class(OpenLayers.Layer,{smoothDragPan:!0,isBaseLayer:!0,isFixed:!0,pane:null,mapObject:null,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);null==this.pane&&(this.pane=OpenLayers.Util.createDiv(this.div.id+"_EventPane"))},destroy:function(){this.pane=this.mapObject=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Layer.prototype.setMap.apply(this,arguments);this.pane.style.zIndex=
+parseInt(this.div.style.zIndex)+1;this.pane.style.display=this.div.style.display;this.pane.style.width="100%";this.pane.style.height="100%";"msie"==OpenLayers.BROWSER_NAME&&(this.pane.style.background="url("+OpenLayers.Util.getImageLocation("blank.gif")+")");this.isFixed?this.map.viewPortDiv.appendChild(this.pane):this.map.layerContainerDiv.appendChild(this.pane);this.loadMapObject();null==this.mapObject&&this.loadWarningMessage()},removeMap:function(a){this.pane&&this.pane.parentNode&&this.pane.parentNode.removeChild(this.pane);
+OpenLayers.Layer.prototype.removeMap.apply(this,arguments)},loadWarningMessage:function(){this.div.style.backgroundColor="darkblue";var a=this.map.getSize(),b=Math.min(a.w,300),c=Math.min(a.h,200),b=new OpenLayers.Size(b,c),a=(new OpenLayers.Pixel(a.w/2,a.h/2)).add(-b.w/2,-b.h/2),a=OpenLayers.Util.createDiv(this.name+"_warning",a,b,null,null,null,"auto");a.style.padding="7px";a.style.backgroundColor="yellow";a.innerHTML=this.getWarningHTML();this.div.appendChild(a)},getWarningHTML:function(){return""},
+display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);this.pane.style.display=this.div.style.display},setZIndex:function(a){OpenLayers.Layer.prototype.setZIndex.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1},moveByPx:function(a,b){OpenLayers.Layer.prototype.moveByPx.apply(this,arguments);this.dragPanMapObject?this.dragPanMapObject(a,-b):this.moveTo(this.map.getCachedCenter())},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,
+arguments);if(null!=this.mapObject){var d=this.map.getCenter(),e=this.map.getZoom();if(null!=d){var f=this.getMapObjectCenter(),f=this.getOLLonLatFromMapObjectLonLat(f),g=this.getMapObjectZoom(),g=this.getOLZoomFromMapObjectZoom(g);d.equals(f)&&e==g||(!b&&f&&this.dragPanMapObject&&this.smoothDragPan?(e=this.map.getViewPortPxFromLonLat(f),d=this.map.getViewPortPxFromLonLat(d),this.dragPanMapObject(d.x-e.x,e.y-d.y)):(d=this.getMapObjectLonLatFromOLLonLat(d),e=this.getMapObjectZoomFromOLZoom(e),this.setMapObjectCenter(d,
+e,c)))}}},getLonLatFromViewPortPx:function(a){var b=null;null!=this.mapObject&&null!=this.getMapObjectCenter()&&(a=this.getMapObjectPixelFromOLPixel(a),a=this.getMapObjectLonLatFromMapObjectPixel(a),b=this.getOLLonLatFromMapObjectLonLat(a));return b},getViewPortPxFromLonLat:function(a){var b=null;null!=this.mapObject&&null!=this.getMapObjectCenter()&&(a=this.getMapObjectLonLatFromOLLonLat(a),a=this.getMapObjectPixelFromMapObjectLonLat(a),b=this.getOLPixelFromMapObjectPixel(a));return b},getOLLonLatFromMapObjectLonLat:function(a){var b=
+null;null!=a&&(b=this.getLongitudeFromMapObjectLonLat(a),a=this.getLatitudeFromMapObjectLonLat(a),b=new OpenLayers.LonLat(b,a));return b},getMapObjectLonLatFromOLLonLat:function(a){var b=null;null!=a&&(b=this.getMapObjectLonLatFromLonLat(a.lon,a.lat));return b},getOLPixelFromMapObjectPixel:function(a){var b=null;null!=a&&(b=this.getXFromMapObjectPixel(a),a=this.getYFromMapObjectPixel(a),b=new OpenLayers.Pixel(b,a));return b},getMapObjectPixelFromOLPixel:function(a){var b=null;null!=a&&(b=this.getMapObjectPixelFromXY(a.x,
+a.y));return b},CLASS_NAME:"OpenLayers.Layer.EventPane"});OpenLayers.Layer.FixedZoomLevels=OpenLayers.Class({initialize:function(){},initResolutions:function(){for(var a=["minZoomLevel","maxZoomLevel","numZoomLevels"],b=0,c=a.length;b<c;b++){var d=a[b];this[d]=null!=this.options[d]?this.options[d]:this.map[d]}if(null==this.minZoomLevel||this.minZoomLevel<this.MIN_ZOOM_LEVEL)this.minZoomLevel=this.MIN_ZOOM_LEVEL;a=this.MAX_ZOOM_LEVEL-this.minZoomLevel+1;b=null==this.options.numZoomLevels&&null!=this.options.maxZoomLevel||null==this.numZoomLevels&&null!=this.maxZoomLevel?
+this.maxZoomLevel-this.minZoomLevel+1:this.numZoomLevels;this.numZoomLevels=null!=b?Math.min(b,a):a;this.maxZoomLevel=this.minZoomLevel+this.numZoomLevels-1;if(null!=this.RESOLUTIONS){a=0;this.resolutions=[];for(b=this.minZoomLevel;b<=this.maxZoomLevel;b++)this.resolutions[a++]=this.RESOLUTIONS[b];this.maxResolution=this.resolutions[0];this.minResolution=this.resolutions[this.resolutions.length-1]}},getResolution:function(){if(null!=this.resolutions)return OpenLayers.Layer.prototype.getResolution.apply(this,
+arguments);var a=null,b=this.map.getSize(),c=this.getExtent();null!=b&&null!=c&&(a=Math.max(c.getWidth()/b.w,c.getHeight()/b.h));return a},getExtent:function(){var a=this.map.getSize(),b=this.getLonLatFromViewPortPx({x:0,y:0}),a=this.getLonLatFromViewPortPx({x:a.w,y:a.h});return null!=b&&null!=a?new OpenLayers.Bounds(b.lon,a.lat,a.lon,b.lat):null},getZoomForResolution:function(a){if(null!=this.resolutions)return OpenLayers.Layer.prototype.getZoomForResolution.apply(this,arguments);var b=OpenLayers.Layer.prototype.getExtent.apply(this,
+[]);return this.getZoomForExtent(b)},getOLZoomFromMapObjectZoom:function(a){var b=null;null!=a&&(b=a-this.minZoomLevel,this.map.baseLayer!==this&&(b=this.map.baseLayer.getZoomForResolution(this.getResolutionForZoom(b))));return b},getMapObjectZoomFromOLZoom:function(a){var b=null;null!=a&&(b=a+this.minZoomLevel,this.map.baseLayer!==this&&(b=this.getZoomForResolution(this.map.baseLayer.getResolutionForZoom(b))));return b},CLASS_NAME:"OpenLayers.Layer.FixedZoomLevels"});OpenLayers.Layer.Google=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:21,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,6.866455078125E-4,3.4332275390625E-4,1.71661376953125E-4,8.58306884765625E-5,4.291534423828125E-5,2.145767211914062E-5,1.072883605957031E-5,5.36441802978515E-6,2.68220901489257E-6,1.341104507446289E-6,6.705522537231445E-7],
+type:null,wrapDateLine:!0,sphericalMercator:!1,version:null,initialize:function(a,b){b=b||{};b.version||(b.version="function"===typeof GMap2?"2":"3");var c=OpenLayers.Layer.Google["v"+b.version.replace(/\./g,"_")];if(c)OpenLayers.Util.applyDefaults(b,c);else throw"Unsupported Google Maps API version: "+b.version;OpenLayers.Util.applyDefaults(b,c.DEFAULTS);b.maxExtent&&(b.maxExtent=b.maxExtent.clone());OpenLayers.Layer.EventPane.prototype.initialize.apply(this,[a,b]);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+[a,b]);this.sphericalMercator&&(OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator),this.initMercatorParameters())},clone:function(){return new OpenLayers.Layer.Google(this.name,this.getOptions())},setVisibility:function(a){var b=null==this.opacity?1:this.opacity;OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this,arguments);this.setOpacity(b)},display:function(a){this._dragging||this.setGMapVisibility(a);OpenLayers.Layer.EventPane.prototype.display.apply(this,arguments)},moveTo:function(a,
+b,c){this._dragging=c;OpenLayers.Layer.EventPane.prototype.moveTo.apply(this,arguments);delete this._dragging},setOpacity:function(a){a!==this.opacity&&(null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"}),this.opacity=a);if(this.getVisibility()){var b=this.getMapContainer();OpenLayers.Util.modifyDOMElement(b,null,null,null,null,null,null,a)}},destroy:function(){if(this.map){this.setGMapVisibility(!1);var a=OpenLayers.Layer.Google.cache[this.map.id];a&&1>=a.count&&
+this.removeGMapElements()}OpenLayers.Layer.EventPane.prototype.destroy.apply(this,arguments)},removeGMapElements:function(){var a=OpenLayers.Layer.Google.cache[this.map.id];if(a){var b=this.mapObject&&this.getMapContainer();b&&b.parentNode&&b.parentNode.removeChild(b);(b=a.termsOfUse)&&b.parentNode&&b.parentNode.removeChild(b);(a=a.poweredBy)&&a.parentNode&&a.parentNode.removeChild(a);this.mapObject&&(window.google&&google.maps&&google.maps.event&&google.maps.event.clearListeners)&&google.maps.event.clearListeners(this.mapObject,
+"tilesloaded")}},removeMap:function(a){this.visibility&&this.mapObject&&this.setGMapVisibility(!1);var b=OpenLayers.Layer.Google.cache[a.id];b&&(1>=b.count?(this.removeGMapElements(),delete OpenLayers.Layer.Google.cache[a.id]):--b.count);delete this.termsOfUse;delete this.poweredBy;delete this.mapObject;delete this.dragObject;OpenLayers.Layer.EventPane.prototype.removeMap.apply(this,arguments)},getOLBoundsFromMapObjectBounds:function(a){var b=null;null!=a&&(b=a.getSouthWest(),a=a.getNorthEast(),this.sphericalMercator?
+(b=this.forwardMercator(b.lng(),b.lat()),a=this.forwardMercator(a.lng(),a.lat())):(b=new OpenLayers.LonLat(b.lng(),b.lat()),a=new OpenLayers.LonLat(a.lng(),a.lat())),b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat));return b},getWarningHTML:function(){return OpenLayers.i18n("googleWarning")},getMapObjectCenter:function(){return this.mapObject.getCenter()},getMapObjectZoom:function(){return this.mapObject.getZoom()},getLongitudeFromMapObjectLonLat:function(a){return this.sphericalMercator?this.forwardMercator(a.lng(),
+a.lat()).lon:a.lng()},getLatitudeFromMapObjectLonLat:function(a){return this.sphericalMercator?this.forwardMercator(a.lng(),a.lat()).lat:a.lat()},getXFromMapObjectPixel:function(a){return a.x},getYFromMapObjectPixel:function(a){return a.y},CLASS_NAME:"OpenLayers.Layer.Google"});OpenLayers.Layer.Google.cache={};
+OpenLayers.Layer.Google.v2={termsOfUse:null,poweredBy:null,dragObject:null,loadMapObject:function(){this.type||(this.type=G_NORMAL_MAP);var a,b,c,d=OpenLayers.Layer.Google.cache[this.map.id];if(d)a=d.mapObject,b=d.termsOfUse,c=d.poweredBy,++d.count;else{var d=this.map.viewPortDiv,e=document.createElement("div");e.id=this.map.id+"_GMap2Container";e.style.position="absolute";e.style.width="100%";e.style.height="100%";d.appendChild(e);try{a=new GMap2(e),b=e.lastChild,d.appendChild(b),b.style.zIndex=
+"1100",b.style.right="",b.style.bottom="",b.className="olLayerGoogleCopyright",c=e.lastChild,d.appendChild(c),c.style.zIndex="1100",c.style.right="",c.style.bottom="",c.className="olLayerGooglePoweredBy gmnoprint"}catch(f){throw f;}OpenLayers.Layer.Google.cache[this.map.id]={mapObject:a,termsOfUse:b,poweredBy:c,count:1}}this.mapObject=a;this.termsOfUse=b;this.poweredBy=c;-1===OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),this.type)&&this.mapObject.addMapType(this.type);"function"==typeof a.getDragObject?
+this.dragObject=a.getDragObject():this.dragPanMapObject=null;!1===this.isBaseLayer&&this.setGMapVisibility("none"!==this.div.style.display)},onMapResize:function(){if(this.visibility&&this.mapObject.isLoaded())this.mapObject.checkResize();else{if(!this._resized)var a=this,b=GEvent.addListener(this.mapObject,"load",function(){GEvent.removeListener(b);delete a._resized;a.mapObject.checkResize();a.moveTo(a.map.getCenter(),a.map.getZoom())});this._resized=!0}},setGMapVisibility:function(a){var b=OpenLayers.Layer.Google.cache[this.map.id];
+if(b){var c=this.mapObject.getContainer();!0===a?(this.mapObject.setMapType(this.type),c.style.display="",this.termsOfUse.style.left="",this.termsOfUse.style.display="",this.poweredBy.style.display="",b.displayed=this.id):(b.displayed===this.id&&delete b.displayed,b.displayed||(c.style.display="none",this.termsOfUse.style.display="none",this.termsOfUse.style.left="-9999px",this.poweredBy.style.display="none"))}},getMapContainer:function(){return this.mapObject.getContainer()},getMapObjectBoundsFromOLBounds:function(a){var b=
+null;null!=a&&(b=this.sphericalMercator?this.inverseMercator(a.bottom,a.left):new OpenLayers.LonLat(a.bottom,a.left),a=this.sphericalMercator?this.inverseMercator(a.top,a.right):new OpenLayers.LonLat(a.top,a.right),b=new GLatLngBounds(new GLatLng(b.lat,b.lon),new GLatLng(a.lat,a.lon)));return b},setMapObjectCenter:function(a,b){this.mapObject.setCenter(a,b)},dragPanMapObject:function(a,b){this.dragObject.moveBy(new GSize(-a,b))},getMapObjectLonLatFromMapObjectPixel:function(a){return this.mapObject.fromContainerPixelToLatLng(a)},
+getMapObjectPixelFromMapObjectLonLat:function(a){return this.mapObject.fromLatLngToContainerPixel(a)},getMapObjectZoomFromMapObjectBounds:function(a){return this.mapObject.getBoundsZoomLevel(a)},getMapObjectLonLatFromLonLat:function(a,b){var c;this.sphericalMercator?(c=this.inverseMercator(a,b),c=new GLatLng(c.lat,c.lon)):c=new GLatLng(b,a);return c},getMapObjectPixelFromXY:function(a,b){return new GPoint(a,b)}};OpenLayers.Format.XML=OpenLayers.Class(OpenLayers.Format,{namespaces:null,namespaceAlias:null,defaultPrefix:null,readers:{},writers:{},xmldom:null,initialize:function(a){window.ActiveXObject&&(this.xmldom=new ActiveXObject("Microsoft.XMLDOM"));OpenLayers.Format.prototype.initialize.apply(this,[a]);this.namespaces=OpenLayers.Util.extend({},this.namespaces);this.namespaceAlias={};for(var b in this.namespaces)this.namespaceAlias[this.namespaces[b]]=b},destroy:function(){this.xmldom=null;OpenLayers.Format.prototype.destroy.apply(this,
+arguments)},setNamespace:function(a,b){this.namespaces[a]=b;this.namespaceAlias[b]=a},read:function(a){var b=a.indexOf("<");0<b&&(a=a.substring(b));b=OpenLayers.Util.Try(OpenLayers.Function.bind(function(){var b;b=window.ActiveXObject&&!this.xmldom?new ActiveXObject("Microsoft.XMLDOM"):this.xmldom;b.loadXML(a);return b},this),function(){return(new DOMParser).parseFromString(a,"text/xml")},function(){var b=new XMLHttpRequest;b.open("GET","data:text/xml;charset=utf-8,"+encodeURIComponent(a),!1);b.overrideMimeType&&
+b.overrideMimeType("text/xml");b.send(null);return b.responseXML});this.keepData&&(this.data=b);return b},write:function(a){if(this.xmldom)a=a.xml;else{var b=new XMLSerializer;if(1==a.nodeType){var c=document.implementation.createDocument("","",null);c.importNode&&(a=c.importNode(a,!0));c.appendChild(a);a=b.serializeToString(c)}else a=b.serializeToString(a)}return a},createElementNS:function(a,b){return this.xmldom?"string"==typeof a?this.xmldom.createNode(1,b,a):this.xmldom.createNode(1,b,""):document.createElementNS(a,
+b)},createDocumentFragment:function(){return this.xmldom?this.xmldom.createDocumentFragment():document.createDocumentFragment()},createTextNode:function(a){"string"!==typeof a&&(a=String(a));return this.xmldom?this.xmldom.createTextNode(a):document.createTextNode(a)},getElementsByTagNameNS:function(a,b,c){var d=[];if(a.getElementsByTagNameNS)d=a.getElementsByTagNameNS(b,c);else{a=a.getElementsByTagName("*");for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],f=e.prefix?e.prefix+":"+c:c,"*"==c||f==e.nodeName)"*"!=
+b&&b!=e.namespaceURI||d.push(e)}return d},getAttributeNodeNS:function(a,b,c){var d=null;if(a.getAttributeNodeNS)d=a.getAttributeNodeNS(b,c);else{a=a.attributes;for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],e.namespaceURI==b&&(f=e.prefix?e.prefix+":"+c:c,f==e.nodeName)){d=e;break}}return d},getAttributeNS:function(a,b,c){var d="";if(a.getAttributeNS)d=a.getAttributeNS(b,c)||"";else if(a=this.getAttributeNodeNS(a,b,c))d=a.nodeValue;return d},getChildValue:function(a,b){var c=b||"";if(a)for(var d=a.firstChild;d;d=
+d.nextSibling)switch(d.nodeType){case 3:case 4:c+=d.nodeValue}return c},isSimpleContent:function(a){var b=!0;for(a=a.firstChild;a;a=a.nextSibling)if(1===a.nodeType){b=!1;break}return b},contentType:function(a){var b=!1,c=!1,d=OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;for(a=a.firstChild;a;a=a.nextSibling){switch(a.nodeType){case 1:c=!0;break;case 8:break;default:b=!0}if(c&&b)break}if(c&&b)d=OpenLayers.Format.XML.CONTENT_TYPE.MIXED;else{if(c)return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;if(b)return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE}return d},
+hasAttributeNS:function(a,b,c){var d=!1;return d=a.hasAttributeNS?a.hasAttributeNS(b,c):!!this.getAttributeNodeNS(a,b,c)},setAttributeNS:function(a,b,c,d){if(a.setAttributeNS)a.setAttributeNS(b,c,d);else if(this.xmldom)b?(b=a.ownerDocument.createNode(2,c,b),b.nodeValue=d,a.setAttributeNode(b)):a.setAttribute(c,d);else throw"setAttributeNS not implemented";},createElementNSPlus:function(a,b){b=b||{};var c=b.uri||this.namespaces[b.prefix];c||(c=a.indexOf(":"),c=this.namespaces[a.substring(0,c)]);c||
+(c=this.namespaces[this.defaultPrefix]);c=this.createElementNS(c,a);b.attributes&&this.setAttributes(c,b.attributes);var d=b.value;null!=d&&c.appendChild(this.createTextNode(d));return c},setAttributes:function(a,b){var c,d,e;for(e in b)null!=b[e]&&b[e].toString&&(c=b[e].toString(),d=this.namespaces[e.substring(0,e.indexOf(":"))]||null,this.setAttributeNS(a,d,e,c))},readNode:function(a,b){b||(b={});var c=this.readers[a.namespaceURI?this.namespaceAlias[a.namespaceURI]:this.defaultPrefix];if(c){var d=
+a.localName||a.nodeName.split(":").pop();(c=c[d]||c["*"])&&c.apply(this,[a,b])}return b},readChildNodes:function(a,b){b||(b={});for(var c=a.childNodes,d,e=0,f=c.length;e<f;++e)d=c[e],1==d.nodeType&&this.readNode(d,b);return b},writeNode:function(a,b,c){var d,e=a.indexOf(":");0<e?(d=a.substring(0,e),a=a.substring(e+1)):d=c?this.namespaceAlias[c.namespaceURI]:this.defaultPrefix;b=this.writers[d][a].apply(this,[b]);c&&c.appendChild(b);return b},getChildEl:function(a,b,c){return a&&this.getThisOrNextEl(a.firstChild,
+b,c)},getNextEl:function(a,b,c){return a&&this.getThisOrNextEl(a.nextSibling,b,c)},getThisOrNextEl:function(a,b,c){a:for(;a;a=a.nextSibling)switch(a.nodeType){case 1:if(!(b&&b!==(a.localName||a.nodeName.split(":").pop())||c&&c!==a.namespaceURI))break a;a=null;break a;case 3:if(/^\s*$/.test(a.nodeValue))break;case 4:case 6:case 12:case 10:case 11:a=null;break a}return a||null},lookupNamespaceURI:function(a,b){var c=null;if(a)if(a.lookupNamespaceURI)c=a.lookupNamespaceURI(b);else a:switch(a.nodeType){case 1:if(null!==
+a.namespaceURI&&a.prefix===b){c=a.namespaceURI;break a}if(c=a.attributes.length)for(var d,e=0;e<c;++e)if(d=a.attributes[e],"xmlns"===d.prefix&&d.name==="xmlns:"+b){c=d.value||null;break a}else if("xmlns"===d.name&&null===b){c=d.value||null;break a}c=this.lookupNamespaceURI(a.parentNode,b);break a;case 2:c=this.lookupNamespaceURI(a.ownerElement,b);break a;case 9:c=this.lookupNamespaceURI(a.documentElement,b);break a;case 6:case 12:case 10:case 11:break a;default:c=this.lookupNamespaceURI(a.parentNode,
+b)}return c},getXMLDoc:function(){OpenLayers.Format.XML.document||this.xmldom||(document.implementation&&document.implementation.createDocument?OpenLayers.Format.XML.document=document.implementation.createDocument("","",null):!this.xmldom&&window.ActiveXObject&&(this.xmldom=new ActiveXObject("Microsoft.XMLDOM")));return OpenLayers.Format.XML.document||this.xmldom},CLASS_NAME:"OpenLayers.Format.XML"});OpenLayers.Format.XML.CONTENT_TYPE={EMPTY:0,SIMPLE:1,COMPLEX:2,MIXED:3};
+OpenLayers.Format.XML.lookupNamespaceURI=OpenLayers.Function.bind(OpenLayers.Format.XML.prototype.lookupNamespaceURI,OpenLayers.Format.XML.prototype);OpenLayers.Format.XML.document=null;OpenLayers.Format.WFST=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Format.WFST.DEFAULTS);var b=OpenLayers.Format.WFST["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported WFST version: "+a.version;return new b(a)};OpenLayers.Format.WFST.DEFAULTS={version:"1.0.0"};OpenLayers.Feature=OpenLayers.Class({layer:null,id:null,lonlat:null,data:null,marker:null,popupClass:null,popup:null,initialize:function(a,b,c){this.layer=a;this.lonlat=b;this.data=null!=c?c:{};this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){null!=this.layer&&null!=this.layer.map&&null!=this.popup&&this.layer.map.removePopup(this.popup);null!=this.layer&&null!=this.marker&&this.layer.removeMarker(this.marker);this.data=this.lonlat=this.id=this.layer=null;null!=this.marker&&
+(this.destroyMarker(this.marker),this.marker=null);null!=this.popup&&(this.destroyPopup(this.popup),this.popup=null)},onScreen:function(){var a=!1;null!=this.layer&&null!=this.layer.map&&(a=this.layer.map.getExtent().containsLonLat(this.lonlat));return a},createMarker:function(){null!=this.lonlat&&(this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon));return this.marker},destroyMarker:function(){this.marker.destroy()},createPopup:function(a){null!=this.lonlat&&(this.popup||(this.popup=new (this.popupClass?
+this.popupClass:OpenLayers.Popup.Anchored)(this.id+"_popup",this.lonlat,this.data.popupSize,this.data.popupContentHTML,this.marker?this.marker.icon:null,a)),null!=this.data.overflow&&(this.popup.contentDiv.style.overflow=this.data.overflow),this.popup.feature=this);return this.popup},destroyPopup:function(){this.popup&&(this.popup.feature=null,this.popup.destroy(),this.popup=null)},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.State={UNKNOWN:"Unknown",INSERT:"Insert",UPDATE:"Update",DELETE:"Delete"};
+OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,bounds:null,state:null,style:null,url:null,renderIntent:"default",modified:null,initialize:function(a,b,c){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,b]);this.lonlat=null;this.geometry=a?a:null;this.state=null;this.attributes={};b&&(this.attributes=OpenLayers.Util.extend(this.attributes,b));this.style=c?c:null},destroy:function(){this.layer&&(this.layer.removeFeatures(this),this.layer=
+null);this.modified=this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments)},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style)},onScreen:function(a){var b=!1;this.layer&&this.layer.map&&(b=this.layer.map.getExtent(),a?(a=this.geometry.getBounds(),b=b.intersectsBounds(a)):b=b.toGeometry().intersects(this.geometry));return b},getVisibility:function(){return!(this.style&&"none"==this.style.display||!this.layer||
+this.layer&&this.layer.styleMap&&"none"==this.layer.styleMap.createSymbolizer(this,this.renderIntent).display||this.layer&&!this.layer.getVisibility())},createMarker:function(){return null},destroyMarker:function(){},createPopup:function(){return null},atPoint:function(a,b,c){var d=!1;this.geometry&&(d=this.geometry.atPoint(a,b,c));return d},destroyPopup:function(){},move:function(a){if(this.layer&&this.geometry.move){a="OpenLayers.LonLat"==a.CLASS_NAME?this.layer.getViewPortPxFromLonLat(a):a;var b=
+this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()),c=this.layer.map.getResolution();this.geometry.move(c*(a.x-b.x),c*(b.y-a.y));this.layer.drawFeature(this);return b}},toState:function(a){if(a==OpenLayers.State.UPDATE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=a}else if(a==OpenLayers.State.INSERT)switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=a}else if(a==OpenLayers.State.DELETE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.UPDATE:this.state=
+a}else a==OpenLayers.State.UNKNOWN&&(this.state=a)},CLASS_NAME:"OpenLayers.Feature.Vector"});
+OpenLayers.Feature.Vector.style={"default":{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},select:{fillColor:"blue",fillOpacity:0.4,
+hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},temporary:{fillColor:"#66cccc",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#66cccc",strokeOpacity:1,
+strokeLinecap:"round",strokeWidth:2,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},"delete":{display:"none"}};OpenLayers.Style=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:!1,rules:null,context:null,defaultStyle:null,defaultsPerSymbolizer:!1,propertyStyles:null,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.rules=[];b&&b.rules&&this.addRules(b.rules);this.setDefaultStyle(a||OpenLayers.Feature.Vector.style["default"]);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){for(var a=0,b=this.rules.length;a<b;a++)this.rules[a].destroy(),
+this.rules[a]=null;this.defaultStyle=this.rules=null},createSymbolizer:function(a){for(var b=this.defaultsPerSymbolizer?{}:this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),a),c=this.rules,d,e=[],f=!1,g=0,h=c.length;g<h;g++)d=c[g],d.evaluate(a)&&(d instanceof OpenLayers.Rule&&d.elseFilter?e.push(d):(f=!0,this.applySymbolizer(d,b,a)));if(!1==f&&0<e.length)for(f=!0,g=0,h=e.length;g<h;g++)this.applySymbolizer(e[g],b,a);0<c.length&&!1==f&&(b.display="none");null!=b.label&&"string"!==typeof b.label&&
+(b.label=String(b.label));return b},applySymbolizer:function(a,b,c){var d=c.geometry?this.getSymbolizerPrefix(c.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];a=a.symbolizer[d]||a.symbolizer;!0===this.defaultsPerSymbolizer&&(d=this.defaultStyle,OpenLayers.Util.applyDefaults(a,{pointRadius:d.pointRadius}),!0!==a.stroke&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{strokeWidth:d.strokeWidth,strokeColor:d.strokeColor,strokeOpacity:d.strokeOpacity,strokeDashstyle:d.strokeDashstyle,strokeLinecap:d.strokeLinecap}),
+!0!==a.fill&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{fillColor:d.fillColor,fillOpacity:d.fillOpacity}),!0===a.graphic&&OpenLayers.Util.applyDefaults(a,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOffset:this.defaultStyle.graphicYOffset}));
+return this.createLiterals(OpenLayers.Util.extend(b,a),c)},createLiterals:function(a,b){var c=OpenLayers.Util.extend({},b.attributes||b.data);OpenLayers.Util.extend(c,this.context);for(var d in this.propertyStyles)a[d]=OpenLayers.Style.createLiteral(a[d],c,b,d);return a},findPropertyStyles:function(){var a={};this.addPropertyStyles(a,this.defaultStyle);for(var b=this.rules,c,d,e=0,f=b.length;e<f;e++){c=b[e].symbolizer;for(var g in c)if(d=c[g],"object"==typeof d)this.addPropertyStyles(a,d);else{this.addPropertyStyles(a,
+c);break}}return a},addPropertyStyles:function(a,b){var c,d;for(d in b)c=b[d],"string"==typeof c&&c.match(/\$\{\w+\}/)&&(a[d]=!0);return a},addRules:function(a){Array.prototype.push.apply(this.rules,a);this.propertyStyles=this.findPropertyStyles()},setDefaultStyle:function(a){this.defaultStyle=a;this.propertyStyles=this.findPropertyStyles()},getSymbolizerPrefix:function(a){for(var b=OpenLayers.Style.SYMBOLIZER_PREFIXES,c=0,d=b.length;c<d;c++)if(-1!=a.CLASS_NAME.indexOf(b[c]))return b[c]},clone:function(){var a=
+OpenLayers.Util.extend({},this);if(this.rules){a.rules=[];for(var b=0,c=this.rules.length;b<c;++b)a.rules.push(this.rules[b].clone())}a.context=this.context&&OpenLayers.Util.extend({},this.context);b=OpenLayers.Util.extend({},this.defaultStyle);return new OpenLayers.Style(b,a)},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(a,b,c,d){"string"==typeof a&&-1!=a.indexOf("${")&&(a=OpenLayers.String.format(a,b,[c,d]),a=isNaN(a)||!a?a:parseFloat(a));return a};
+OpenLayers.Style.SYMBOLIZER_PREFIXES=["Point","Line","Polygon","Text","Raster"];OpenLayers.Filter=OpenLayers.Class({initialize:function(a){OpenLayers.Util.extend(this,a)},destroy:function(){},evaluate:function(a){return!0},clone:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.CQL?OpenLayers.Format.CQL.prototype.write(this):Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Filter"});OpenLayers.Filter.Spatial=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,distance:null,distanceUnits:null,evaluate:function(a){var b=!1;switch(this.type){case OpenLayers.Filter.Spatial.BBOX:case OpenLayers.Filter.Spatial.INTERSECTS:if(a.geometry){var c=this.value;"OpenLayers.Bounds"==this.value.CLASS_NAME&&(c=this.value.toGeometry());a.geometry.intersects(c)&&(b=!0)}break;default:throw Error("evaluate is not implemented for this filter type.");}return b},clone:function(){var a=
+OpenLayers.Util.applyDefaults({value:this.value&&this.value.clone&&this.value.clone()},this);return new OpenLayers.Filter.Spatial(a)},CLASS_NAME:"OpenLayers.Filter.Spatial"});OpenLayers.Filter.Spatial.BBOX="BBOX";OpenLayers.Filter.Spatial.INTERSECTS="INTERSECTS";OpenLayers.Filter.Spatial.DWITHIN="DWITHIN";OpenLayers.Filter.Spatial.WITHIN="WITHIN";OpenLayers.Filter.Spatial.CONTAINS="CONTAINS";OpenLayers.Filter.FeatureId=OpenLayers.Class(OpenLayers.Filter,{fids:null,type:"FID",initialize:function(a){this.fids=[];OpenLayers.Filter.prototype.initialize.apply(this,[a])},evaluate:function(a){for(var b=0,c=this.fids.length;b<c;b++)if((a.fid||a.id)==this.fids[b])return!0;return!1},clone:function(){var a=new OpenLayers.Filter.FeatureId;OpenLayers.Util.extend(a,this);a.fids=this.fids.slice();return a},CLASS_NAME:"OpenLayers.Filter.FeatureId"});OpenLayers.Format.WFST.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs",gml:"http://www.opengis.net/gml",ogc:"http://www.opengis.net/ogc",ows:"http://www.opengis.net/ows"},defaultPrefix:"wfs",version:null,schemaLocations:null,srsName:null,extractAttributes:!0,xy:!0,stateName:null,initialize:function(a){this.stateName={};this.stateName[OpenLayers.State.INSERT]="wfs:Insert";this.stateName[OpenLayers.State.UPDATE]=
+"wfs:Update";this.stateName[OpenLayers.State.DELETE]="wfs:Delete";OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},getSrsName:function(a,b){var c=b&&b.srsName;c||(c=a&&a.layer?a.layer.projection.getCode():this.srsName);return c},read:function(a,b){b=b||{};OpenLayers.Util.applyDefaults(b,{output:"features"});"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var c={};a&&this.readNode(a,c,!0);c.features&&"features"===b.output&&
+(c=c.features);return c},readers:{wfs:{FeatureCollection:function(a,b){b.features=[];this.readChildNodes(a,b)}}},write:function(a,b){var c=this.writeNode("wfs:Transaction",{features:a,options:b}),d=this.schemaLocationAttr();d&&this.setAttributeNS(c,this.namespaces.xsi,"xsi:schemaLocation",d);return OpenLayers.Format.XML.prototype.write.apply(this,[c])},writers:{wfs:{GetFeature:function(a){var b=this.createElementNSPlus("wfs:GetFeature",{attributes:{service:"WFS",version:this.version,handle:a&&a.handle,
+outputFormat:a&&a.outputFormat,maxFeatures:a&&a.maxFeatures,"xsi:schemaLocation":this.schemaLocationAttr(a)}});if("string"==typeof this.featureType)this.writeNode("Query",a,b);else for(var c=0,d=this.featureType.length;c<d;c++)a.featureType=this.featureType[c],this.writeNode("Query",a,b);return b},Transaction:function(a){a=a||{};var b=a.options||{},c=this.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version,handle:b.handle}}),d,e=a.features;if(e){!0===b.multi&&OpenLayers.Util.extend(this.geometryTypes,
+{"OpenLayers.Geometry.Point":"MultiPoint","OpenLayers.Geometry.LineString":!0===this.multiCurve?"MultiCurve":"MultiLineString","OpenLayers.Geometry.Polygon":!0===this.multiSurface?"MultiSurface":"MultiPolygon"});var f,g;a=0;for(d=e.length;a<d;++a)g=e[a],(f=this.stateName[g.state])&&this.writeNode(f,{feature:g,options:b},c);!0===b.multi&&this.setGeometryTypes()}if(b.nativeElements)for(a=0,d=b.nativeElements.length;a<d;++a)this.writeNode("wfs:Native",b.nativeElements[a],c);return c},Native:function(a){return this.createElementNSPlus("wfs:Native",
+{attributes:{vendorId:a.vendorId,safeToIgnore:a.safeToIgnore},value:a.value})},Insert:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Insert",{attributes:{handle:a&&a.handle}});this.srsName=this.getSrsName(b);this.writeNode("feature:_typeName",b,a);return a},Update:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Update",{attributes:{handle:a&&a.handle,typeName:(this.featureNS?this.featurePrefix+":":"")+this.featureType}});this.featureNS&&a.setAttribute("xmlns:"+
+this.featurePrefix,this.featureNS);var c=b.modified;null===this.geometryName||c&&void 0===c.geometry||(this.srsName=this.getSrsName(b),this.writeNode("Property",{name:this.geometryName,value:b.geometry},a));for(var d in b.attributes)void 0===b.attributes[d]||c&&c.attributes&&(!c.attributes||void 0===c.attributes[d])||this.writeNode("Property",{name:d,value:b.attributes[d]},a);this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[b.fid]}),a);return a},Property:function(a){var b=this.createElementNSPlus("wfs:Property");
+this.writeNode("Name",a.name,b);null!==a.value&&this.writeNode("Value",a.value,b);return b},Name:function(a){return this.createElementNSPlus("wfs:Name",{value:a})},Value:function(a){var b;a instanceof OpenLayers.Geometry?(b=this.createElementNSPlus("wfs:Value"),a=this.writeNode("feature:_geometry",a).firstChild,b.appendChild(a)):b=this.createElementNSPlus("wfs:Value",{value:a});return b},Delete:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Delete",{attributes:{handle:a&&
+a.handle,typeName:(this.featureNS?this.featurePrefix+":":"")+this.featureType}});this.featureNS&&a.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[b.fid]}),a);return a}}},schemaLocationAttr:function(a){a=OpenLayers.Util.extend({featurePrefix:this.featurePrefix,schema:this.schema},a);var b=OpenLayers.Util.extend({},this.schemaLocations);a.schema&&(b[a.featurePrefix]=a.schema);a=[];var c,d;for(d in b)(c=this.namespaces[d])&&
+a.push(c+" "+b[d]);return a.join(" ")||void 0},setFilterProperty:function(a){if(a.filters)for(var b=0,c=a.filters.length;b<c;++b)OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this,a.filters[b]);else a instanceof OpenLayers.Filter.Spatial&&!a.property&&(a.property=this.geometryName)},CLASS_NAME:"OpenLayers.Format.WFST.v1"});OpenLayers.Format.OGCExceptionReport=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},defaultPrefix:"ogc",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b={exceptionReport:null};a.documentElement&&(this.readChildNodes(a,b),null===b.exceptionReport&&(b=(new OpenLayers.Format.OWSCommon).read(a)));return b},readers:{ogc:{ServiceExceptionReport:function(a,
+b){b.exceptionReport={exceptions:[]};this.readChildNodes(a,b.exceptionReport)},ServiceException:function(a,b){var c={code:a.getAttribute("code"),locator:a.getAttribute("locator"),text:this.getChildValue(a)};b.exceptions.push(c)}}},CLASS_NAME:"OpenLayers.Format.OGCExceptionReport"});OpenLayers.Format.XML.VersionedOGC=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:null,version:null,profile:null,allowFallback:!1,name:null,stringifyOutput:!1,parser:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);a=this.CLASS_NAME;this.name=a.substring(a.lastIndexOf(".")+1)},getVersion:function(a,b){var c;a?(c=this.version,c||(c=a.getAttribute("version"),c||(c=this.defaultVersion))):c=b&&b.version||this.version||this.defaultVersion;return c},getParser:function(a){a=
+a||this.defaultVersion;var b=this.profile?"_"+this.profile:"";if(!this.parser||this.parser.VERSION!=a){var c=OpenLayers.Format[this.name]["v"+a.replace(/\./g,"_")+b];if(!c&&(""!==b&&this.allowFallback&&(b="",c=OpenLayers.Format[this.name]["v"+a.replace(/\./g,"_")]),!c))throw"Can't find a "+this.name+" parser for version "+a+b;this.parser=new c(this.options)}return this.parser},write:function(a,b){var c=this.getVersion(null,b);this.parser=this.getParser(c);c=this.parser.write(a,b);return!1===this.stringifyOutput?
+c:OpenLayers.Format.XML.prototype.write.apply(this,[c])},read:function(a,b){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var c=this.getVersion(a.documentElement);this.parser=this.getParser(c);var d=this.parser.read(a,b),e=this.parser.errorProperty||null;null!==e&&void 0===d[e]&&(e=new OpenLayers.Format.OGCExceptionReport,d.error=e.read(a));d.version=c;return d},CLASS_NAME:"OpenLayers.Format.XML.VersionedOGC"});OpenLayers.Filter.Logical=OpenLayers.Class(OpenLayers.Filter,{filters:null,type:null,initialize:function(a){this.filters=[];OpenLayers.Filter.prototype.initialize.apply(this,[a])},destroy:function(){this.filters=null;OpenLayers.Filter.prototype.destroy.apply(this)},evaluate:function(a){var b,c;switch(this.type){case OpenLayers.Filter.Logical.AND:b=0;for(c=this.filters.length;b<c;b++)if(!1==this.filters[b].evaluate(a))return!1;return!0;case OpenLayers.Filter.Logical.OR:b=0;for(c=this.filters.length;b<
+c;b++)if(!0==this.filters[b].evaluate(a))return!0;return!1;case OpenLayers.Filter.Logical.NOT:return!this.filters[0].evaluate(a)}},clone:function(){for(var a=[],b=0,c=this.filters.length;b<c;++b)a.push(this.filters[b].clone());return new OpenLayers.Filter.Logical({type:this.type,filters:a})},CLASS_NAME:"OpenLayers.Filter.Logical"});OpenLayers.Filter.Logical.AND="&&";OpenLayers.Filter.Logical.OR="||";OpenLayers.Filter.Logical.NOT="!";OpenLayers.Filter.Comparison=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,matchCase:!0,lowerBoundary:null,upperBoundary:null,initialize:function(a){OpenLayers.Filter.prototype.initialize.apply(this,[a]);this.type===OpenLayers.Filter.Comparison.LIKE&&void 0===a.matchCase&&(this.matchCase=null)},evaluate:function(a){a instanceof OpenLayers.Feature.Vector&&(a=a.attributes);var b=!1;a=a[this.property];switch(this.type){case OpenLayers.Filter.Comparison.EQUAL_TO:b=this.value;
+b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a==b:a.toUpperCase()==b.toUpperCase();break;case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:b=this.value;b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a!=b:a.toUpperCase()!=b.toUpperCase();break;case OpenLayers.Filter.Comparison.LESS_THAN:b=a<this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN:b=a>this.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:b=a<=this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:b=
+a>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:b=a>=this.lowerBoundary&&a<=this.upperBoundary;break;case OpenLayers.Filter.Comparison.LIKE:b=RegExp(this.value,"gi").test(a);break;case OpenLayers.Filter.Comparison.IS_NULL:b=null===a}return b},value2regex:function(a,b,c){if("."==a)throw Error("'.' is an unsupported wildCard character for OpenLayers.Filter.Comparison");a=a?a:"*";b=b?b:".";this.value=this.value.replace(RegExp("\\"+(c?c:"!")+"(.|$)","g"),"\\$1");this.value=this.value.replace(RegExp("\\"+
+b,"g"),".");this.value=this.value.replace(RegExp("\\"+a,"g"),".*");this.value=this.value.replace(RegExp("\\\\.\\*","g"),"\\"+a);return this.value=this.value.replace(RegExp("\\\\\\.","g"),"\\"+b)},regex2value:function(){var a=this.value,a=a.replace(/!/g,"!!"),a=a.replace(/(\\)?\\\./g,function(a,c){return c?a:"!."}),a=a.replace(/(\\)?\\\*/g,function(a,c){return c?a:"!*"}),a=a.replace(/\\\\/g,"\\");return a=a.replace(/\.\*/g,"*")},clone:function(){return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison,
+this)},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.Comparison.IS_NULL="NULL";OpenLayers.Format.Filter=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",CLASS_NAME:"OpenLayers.Format.Filter"});OpenLayers.Filter.Function=OpenLayers.Class(OpenLayers.Filter,{name:null,params:null,CLASS_NAME:"OpenLayers.Filter.Function"});OpenLayers.Date={dateRegEx:/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,toISOString:function(){return"toISOString"in Date.prototype?function(a){return a.toISOString()}:function(a){return isNaN(a.getTime())?"Invalid Date":a.getUTCFullYear()+"-"+OpenLayers.Number.zeroPad(a.getUTCMonth()+1,2)+"-"+OpenLayers.Number.zeroPad(a.getUTCDate(),2)+"T"+OpenLayers.Number.zeroPad(a.getUTCHours(),2)+":"+OpenLayers.Number.zeroPad(a.getUTCMinutes(),
+2)+":"+OpenLayers.Number.zeroPad(a.getUTCSeconds(),2)+"."+OpenLayers.Number.zeroPad(a.getUTCMilliseconds(),3)+"Z"}}(),parse:function(a){var b;if((a=a.match(this.dateRegEx))&&(a[1]||a[7])){b=parseInt(a[1],10)||0;var c=parseInt(a[2],10)-1||0,d=parseInt(a[3],10)||1;b=new Date(Date.UTC(b,c,d));if(c=a[7]){var d=parseInt(a[4],10),e=parseInt(a[5],10),f=parseFloat(a[6]),g=f|0,f=Math.round(1E3*(f-g));b.setUTCHours(d,e,g,f);"Z"!==c&&(c=parseInt(c,10),a=parseInt(a[8],10)||0,a=-1E3*(60*60*c+60*a),b=new Date(b.getTime()+
+a))}}else b=new Date("invalid");return b}};OpenLayers.Format.Filter.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"ogc",schemaLocation:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){var b={};this.readers.ogc.Filter.apply(this,[a,b]);return b.filter},readers:{ogc:{_expression:function(a){for(var b="",c=a.firstChild;c;c=
+c.nextSibling)switch(c.nodeType){case 1:a=this.readNode(c);a.property?b+="${"+a.property+"}":void 0!==a.value&&(b+=a.value);break;case 3:case 4:b+=c.nodeValue}return b},Filter:function(a,b){var c={fids:[],filters:[]};this.readChildNodes(a,c);0<c.fids.length?b.filter=new OpenLayers.Filter.FeatureId({fids:c.fids}):0<c.filters.length&&(b.filter=c.filters[0])},FeatureId:function(a,b){var c=a.getAttribute("fid");c&&b.fids.push(c)},And:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND});
+this.readChildNodes(a,c);b.filters.push(c)},Or:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR});this.readChildNodes(a,c);b.filters.push(c)},Not:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.NOT});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLessThan:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsGreaterThan:function(a,
+b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLessThanOrEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsGreaterThanOrEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},
+PropertyIsBetween:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.BETWEEN});this.readChildNodes(a,c);b.filters.push(c)},Literal:function(a,b){b.value=OpenLayers.String.numericIf(this.getChildValue(a),!0)},PropertyName:function(a,b){b.property=this.getChildValue(a)},LowerBoundary:function(a,b){b.lowerBoundary=OpenLayers.String.numericIf(this.readers.ogc._expression.call(this,a),!0)},UpperBoundary:function(a,b){b.upperBoundary=OpenLayers.String.numericIf(this.readers.ogc._expression.call(this,
+a),!0)},Intersects:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.INTERSECTS)},Within:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.WITHIN)},Contains:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.CONTAINS)},DWithin:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.DWITHIN)},Distance:function(a,b){b.distance=parseInt(this.getChildValue(a));b.distanceUnits=a.getAttribute("units")},Function:function(a,b){},PropertyIsNull:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.IS_NULL});
+this.readChildNodes(a,c);b.filters.push(c)}}},readSpatial:function(a,b,c){c=new OpenLayers.Filter.Spatial({type:c});this.readChildNodes(a,c);c.value=c.components[0];delete c.components;b.filters.push(c)},encodeLiteral:function(a){a instanceof Date&&(a=OpenLayers.Date.toISOString(a));return a},writeOgcExpression:function(a,b){a instanceof OpenLayers.Filter.Function?this.writeNode("Function",a,b):this.writeNode("Literal",a,b);return b},write:function(a){return this.writers.ogc.Filter.apply(this,[a])},
+writers:{ogc:{Filter:function(a){var b=this.createElementNSPlus("ogc:Filter");this.writeNode(this.getFilterType(a),a,b);return b},_featureIds:function(a){for(var b=this.createDocumentFragment(),c=0,d=a.fids.length;c<d;++c)this.writeNode("ogc:FeatureId",a.fids[c],b);return b},FeatureId:function(a){return this.createElementNSPlus("ogc:FeatureId",{attributes:{fid:a}})},And:function(a){for(var b=this.createElementNSPlus("ogc:And"),c,d=0,e=a.filters.length;d<e;++d)c=a.filters[d],this.writeNode(this.getFilterType(c),
+c,b);return b},Or:function(a){for(var b=this.createElementNSPlus("ogc:Or"),c,d=0,e=a.filters.length;d<e;++d)c=a.filters[d],this.writeNode(this.getFilterType(c),c,b);return b},Not:function(a){var b=this.createElementNSPlus("ogc:Not");a=a.filters[0];this.writeNode(this.getFilterType(a),a,b);return b},PropertyIsLessThan:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLessThan");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsGreaterThan:function(a){var b=
+this.createElementNSPlus("ogc:PropertyIsGreaterThan");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLessThanOrEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsGreaterThanOrEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);
+return b},PropertyIsBetween:function(a){var b=this.createElementNSPlus("ogc:PropertyIsBetween");this.writeNode("PropertyName",a,b);this.writeNode("LowerBoundary",a,b);this.writeNode("UpperBoundary",a,b);return b},PropertyName:function(a){return this.createElementNSPlus("ogc:PropertyName",{value:a.property})},Literal:function(a){return this.createElementNSPlus("ogc:Literal",{value:(this.encodeLiteral||OpenLayers.Format.Filter.v1.prototype.encodeLiteral)(a)})},LowerBoundary:function(a){var b=this.createElementNSPlus("ogc:LowerBoundary");
+this.writeOgcExpression(a.lowerBoundary,b);return b},UpperBoundary:function(a){var b=this.createElementNSPlus("ogc:UpperBoundary");this.writeNode("Literal",a.upperBoundary,b);return b},INTERSECTS:function(a){return this.writeSpatial(a,"Intersects")},WITHIN:function(a){return this.writeSpatial(a,"Within")},CONTAINS:function(a){return this.writeSpatial(a,"Contains")},DWITHIN:function(a){var b=this.writeSpatial(a,"DWithin");this.writeNode("Distance",a,b);return b},Distance:function(a){return this.createElementNSPlus("ogc:Distance",
+{attributes:{units:a.distanceUnits},value:a.distance})},Function:function(a){var b=this.createElementNSPlus("ogc:Function",{attributes:{name:a.name}});a=a.params;for(var c=0,d=a.length;c<d;c++)this.writeOgcExpression(a[c],b);return b},PropertyIsNull:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNull");this.writeNode("PropertyName",a,b);return b}}},getFilterType:function(a){var b=this.filterMap[a.type];if(!b)throw"Filter writing not supported for rule type: "+a.type;return b},filterMap:{"&&":"And",
+"||":"Or","!":"Not","==":"PropertyIsEqualTo","!=":"PropertyIsNotEqualTo","<":"PropertyIsLessThan",">":"PropertyIsGreaterThan","<=":"PropertyIsLessThanOrEqualTo",">=":"PropertyIsGreaterThanOrEqualTo","..":"PropertyIsBetween","~":"PropertyIsLike",NULL:"PropertyIsNull",BBOX:"BBOX",DWITHIN:"DWITHIN",WITHIN:"WITHIN",CONTAINS:"CONTAINS",INTERSECTS:"INTERSECTS",FID:"_featureIds"},CLASS_NAME:"OpenLayers.Format.Filter.v1"});OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,initialize:function(){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){this.bounds=this.id=null},clone:function(){return new OpenLayers.Geometry},setBounds:function(a){a&&(this.bounds=a.clone())},clearBounds:function(){this.bounds=null;this.parent&&this.parent.clearBounds()},extendBounds:function(a){this.getBounds()?this.bounds.extend(a):this.setBounds(a)},getBounds:function(){null==this.bounds&&this.calculateBounds();
+return this.bounds},calculateBounds:function(){},distanceTo:function(a,b){},getVertices:function(a){},atPoint:function(a,b,c){var d=!1;null!=this.getBounds()&&null!=a&&(b=null!=b?b:0,c=null!=c?c:0,d=(new OpenLayers.Bounds(this.bounds.left-b,this.bounds.bottom-c,this.bounds.right+b,this.bounds.top+c)).containsLonLat(a));return d},getLength:function(){return 0},getArea:function(){return 0},getCentroid:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.WKT?OpenLayers.Format.WKT.prototype.write(new OpenLayers.Feature.Vector(this)):
+Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.fromWKT=function(a){var b;if(OpenLayers.Format&&OpenLayers.Format.WKT){var c=OpenLayers.Geometry.fromWKT.format;c||(c=new OpenLayers.Format.WKT,OpenLayers.Geometry.fromWKT.format=c);a=c.read(a);if(a instanceof OpenLayers.Feature.Vector)b=a.geometry;else if(OpenLayers.Util.isArray(a)){b=a.length;for(var c=Array(b),d=0;d<b;++d)c[d]=a[d].geometry;b=new OpenLayers.Geometry.Collection(c)}}return b};
+OpenLayers.Geometry.segmentsIntersect=function(a,b,c){var d=c&&c.point;c=c&&c.tolerance;var e=!1,f=a.x1-b.x1,g=a.y1-b.y1,h=a.x2-a.x1,k=a.y2-a.y1,l=b.y2-b.y1,m=b.x2-b.x1,n=l*h-m*k,l=m*g-l*f,g=h*g-k*f;0==n?0==l&&0==g&&(e=!0):(f=l/n,n=g/n,0<=f&&(1>=f&&0<=n&&1>=n)&&(d?(h=a.x1+f*h,n=a.y1+f*k,e=new OpenLayers.Geometry.Point(h,n)):e=!0));if(c)if(e){if(d)a:for(a=[a,b],b=0;2>b;++b)for(f=a[b],k=1;3>k;++k)if(h=f["x"+k],n=f["y"+k],d=Math.sqrt(Math.pow(h-e.x,2)+Math.pow(n-e.y,2)),d<c){e.x=h;e.y=n;break a}}else a:for(a=
+[a,b],b=0;2>b;++b)for(h=a[b],n=a[(b+1)%2],k=1;3>k;++k)if(f={x:h["x"+k],y:h["y"+k]},g=OpenLayers.Geometry.distanceToSegment(f,n),g.distance<c){e=d?new OpenLayers.Geometry.Point(f.x,f.y):!0;break a}return e};OpenLayers.Geometry.distanceToSegment=function(a,b){var c=OpenLayers.Geometry.distanceSquaredToSegment(a,b);c.distance=Math.sqrt(c.distance);return c};
+OpenLayers.Geometry.distanceSquaredToSegment=function(a,b){var c=a.x,d=a.y,e=b.x1,f=b.y1,g=b.x2,h=b.y2,k=g-e,l=h-f,m=(k*(c-e)+l*(d-f))/(Math.pow(k,2)+Math.pow(l,2));0>=m||(1<=m?(e=g,f=h):(e+=m*k,f+=m*l));return{distance:Math.pow(e-c,2)+Math.pow(f-d,2),x:e,y:f,along:m}};OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(a,b){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(a);this.y=parseFloat(b)},clone:function(a){null==a&&(a=new OpenLayers.Geometry.Point(this.x,this.y));OpenLayers.Util.applyDefaults(a,this);return a},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x,this.y)},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g,h;a instanceof
+OpenLayers.Geometry.Point?(e=this.x,f=this.y,g=a.x,h=a.y,d=Math.sqrt(Math.pow(e-g,2)+Math.pow(f-h,2)),d=c?{x0:e,y0:f,x1:g,y1:h,distance:d}:d):(d=a.distanceTo(this,b),c&&(d={x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0,distance:d.distance}));return d},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},toShortString:function(){return this.x+", "+this.y},move:function(a,b){this.x+=a;this.y+=b;this.clearBounds()},rotate:function(a,b){a*=
+Math.PI/180;var c=this.distanceTo(b),d=a+Math.atan2(this.y-b.y,this.x-b.x);this.x=b.x+c*Math.cos(d);this.y=b.y+c*Math.sin(d);this.clearBounds()},getCentroid:function(){return new OpenLayers.Geometry.Point(this.x,this.y)},resize:function(a,b,c){this.x=b.x+a*(void 0==c?1:c)*(this.x-b.x);this.y=b.y+a*(this.y-b.y);this.clearBounds();return this},intersects:function(a){var b=!1;return b="OpenLayers.Geometry.Point"==a.CLASS_NAME?this.equals(a):a.intersects(this)},transform:function(a,b){a&&b&&(OpenLayers.Projection.transform(this,
+a,b),this.bounds=null);return this},getVertices:function(a){return[this]},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(a){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];null!=a&&this.addComponents(a)},destroy:function(){this.components.length=0;this.components=null;OpenLayers.Geometry.prototype.destroy.apply(this,arguments)},clone:function(){for(var a=eval("new "+this.CLASS_NAME+"()"),b=0,c=this.components.length;b<c;b++)a.addComponent(this.components[b].clone());
+OpenLayers.Util.applyDefaults(a,this);return a},getComponentsString:function(){for(var a=[],b=0,c=this.components.length;b<c;b++)a.push(this.components[b].toShortString());return a.join(",")},calculateBounds:function(){this.bounds=null;var a=new OpenLayers.Bounds,b=this.components;if(b)for(var c=0,d=b.length;c<d;c++)a.extend(b[c].getBounds());null!=a.left&&(null!=a.bottom&&null!=a.right&&null!=a.top)&&this.setBounds(a)},addComponents:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<
+c;b++)this.addComponent(a[b])},addComponent:function(a,b){var c=!1;if(a&&(null==this.componentTypes||-1<OpenLayers.Util.indexOf(this.componentTypes,a.CLASS_NAME))){if(null!=b&&b<this.components.length){var c=this.components.slice(0,b),d=this.components.slice(b,this.components.length);c.push(a);this.components=c.concat(d)}else this.components.push(a);a.parent=this;this.clearBounds();c=!0}return c},removeComponents:function(a){var b=!1;OpenLayers.Util.isArray(a)||(a=[a]);for(var c=a.length-1;0<=c;--c)b=
+this.removeComponent(a[c])||b;return b},removeComponent:function(a){OpenLayers.Util.removeItem(this.components,a);this.clearBounds();return!0},getLength:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getLength();return a},getArea:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getArea();return a},getGeodesicArea:function(a){for(var b=0,c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicArea(a);return b},getCentroid:function(a){if(!a)return this.components.length&&
+this.components[0].getCentroid();a=this.components.length;if(!a)return!1;for(var b=[],c=[],d=0,e=Number.MAX_VALUE,f,g=0;g<a;++g){f=this.components[g];var h=f.getArea();f=f.getCentroid(!0);isNaN(h)||(isNaN(f.x)||isNaN(f.y))||(b.push(h),d+=h,e=h<e&&0<h?h:e,c.push(f))}a=b.length;if(0===d){for(g=0;g<a;++g)b[g]=1;d=b.length}else{for(g=0;g<a;++g)b[g]/=e;d/=e}for(var k=e=0,g=0;g<a;++g)f=c[g],h=b[g],e+=f.x*h,k+=f.y*h;return new OpenLayers.Geometry.Point(e/d,k/d)},getGeodesicLength:function(a){for(var b=0,
+c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicLength(a);return b},move:function(a,b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0;d<this.components.length;++d)this.components[d].resize(a,b,c);return this},distanceTo:function(a,b){for(var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g=Number.POSITIVE_INFINITY,h=0,k=this.components.length;h<
+k&&!(d=this.components[h].distanceTo(a,b),f=c?d.distance:d,f<g&&(g=f,e=d,0==g));++h);return e},equals:function(a){var b=!0;if(a&&a.CLASS_NAME&&this.CLASS_NAME==a.CLASS_NAME)if(OpenLayers.Util.isArray(a.components)&&a.components.length==this.components.length)for(var c=0,d=this.components.length;c<d;++c){if(!this.components[c].equals(a.components[c])){b=!1;break}}else b=!1;else b=!1;return b},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].transform(a,
+b);this.bounds=null}return this},intersects:function(a){for(var b=!1,c=0,d=this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);return b},getVertices:function(a){for(var b=[],c=0,d=this.components.length;c<d;++c)Array.prototype.push.apply(b,this.components[c].getVertices(a));return b},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],addPoint:function(a,b){this.addComponent(a,b)},removePoint:function(a){this.removeComponent(a)},CLASS_NAME:"OpenLayers.Geometry.MultiPoint"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],getLength:function(){var a=0;if(this.components&&1<this.components.length)for(var b=1,c=this.components.length;b<c;b++)a+=this.components[b-1].distanceTo(this.components[b]);return a},getGeodesicLength:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;if(b.components&&1<b.components.length)for(var d,e=1,f=b.components.length;e<
+f;e++)c=b.components[e-1],d=b.components[e],a+=OpenLayers.Util.distVincenty({lon:c.x,lat:c.y},{lon:d.x,lat:d.y});return 1E3*a},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{removeComponent:function(a){var b=this.components&&2<this.components.length;b&&OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);return b},intersects:function(a){var b=!1,c=a.CLASS_NAME;if("OpenLayers.Geometry.LineString"==c||"OpenLayers.Geometry.LinearRing"==c||"OpenLayers.Geometry.Point"==c){var d=this.getSortedSegments();a="OpenLayers.Geometry.Point"==c?[{x1:a.x,y1:a.y,x2:a.x,y2:a.y}]:a.getSortedSegments();
+var e,f,g,h,k,l,m,n=0,p=d.length;a:for(;n<p;++n){c=d[n];e=c.x1;f=c.x2;g=c.y1;h=c.y2;var q=0,r=a.length;for(;q<r;++q){k=a[q];if(k.x1>f)break;if(!(k.x2<e||(l=k.y1,m=k.y2,Math.min(l,m)>Math.max(g,h)||Math.max(l,m)<Math.min(g,h)||!OpenLayers.Geometry.segmentsIntersect(c,k)))){b=!0;break a}}}}else b=a.intersects(this);return b},getSortedSegments:function(){for(var a=this.components.length-1,b=Array(a),c,d,e=0;e<a;++e)c=this.components[e],d=this.components[e+1],b[e]=c.x<d.x?{x1:c.x,y1:c.y,x2:d.x,y2:d.y}:
+{x1:d.x,y1:d.y,x2:c.x,y2:c.y};return b.sort(function(a,b){return a.x1-b.x1})},splitWithSegment:function(a,b){for(var c=!(b&&!1===b.edge),d=b&&b.tolerance,e=[],f=this.getVertices(),g=[],h=[],k=!1,l,m,n,p={point:!0,tolerance:d},q=null,r=0,s=f.length-2;r<=s;++r)if(d=f[r],g.push(d.clone()),l=f[r+1],m={x1:d.x,y1:d.y,x2:l.x,y2:l.y},m=OpenLayers.Geometry.segmentsIntersect(a,m,p),m instanceof OpenLayers.Geometry.Point&&((n=m.x===a.x1&&m.y===a.y1||m.x===a.x2&&m.y===a.y2||m.equals(d)||m.equals(l)?!0:!1)||c))m.equals(h[h.length-
+1])||h.push(m.clone()),0===r&&m.equals(d)||m.equals(l)||(k=!0,m.equals(d)||g.push(m),e.push(new OpenLayers.Geometry.LineString(g)),g=[m.clone()]);k&&(g.push(l.clone()),e.push(new OpenLayers.Geometry.LineString(g)));if(0<h.length)var t=a.x1<a.x2?1:-1,u=a.y1<a.y2?1:-1,q={lines:e,points:h.sort(function(a,b){return t*a.x-t*b.x||u*a.y-u*b.y})};return q},split:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h;if(a instanceof OpenLayers.Geometry.LineString){var k=this.getVertices(),l,m,n,p,q,r=[];g=[];for(var s=
+0,t=k.length-2;s<=t;++s){l=k[s];m=k[s+1];n={x1:l.x,y1:l.y,x2:m.x,y2:m.y};h=h||[a];d&&r.push(l.clone());for(var u=0;u<h.length;++u)if(p=h[u].splitWithSegment(n,b))if(q=p.lines,0<q.length&&(q.unshift(u,1),Array.prototype.splice.apply(h,q),u+=q.length-2),d)for(var v=0,w=p.points.length;v<w;++v)q=p.points[v],q.equals(l)||(r.push(q),g.push(new OpenLayers.Geometry.LineString(r)),r=q.equals(m)?[]:[q.clone()])}d&&(0<g.length&&0<r.length)&&(r.push(m.clone()),g.push(new OpenLayers.Geometry.LineString(r)))}else c=
+a.splitWith(this,b);h&&1<h.length?f=!0:h=[];g&&1<g.length?e=!0:g=[];if(f||e)c=d?[g,h]:h;return c},splitWith:function(a,b){return a.split(this,b)},getVertices:function(a){return!0===a?[this.components[0],this.components[this.components.length-1]]:!1===a?this.components.slice(1,this.components.length-1):this.components.slice()},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e={},f=Number.POSITIVE_INFINITY;if(a instanceof OpenLayers.Geometry.Point){for(var g=this.getSortedSegments(),
+h=a.x,k=a.y,l,m=0,n=g.length;m<n;++m)if(l=g[m],d=OpenLayers.Geometry.distanceToSegment(a,l),d.distance<f){if(f=d.distance,e=d,0===f)break}else if(l.x2>h&&(k>l.y1&&k<l.y2||k<l.y1&&k>l.y2))break;e=c?{distance:e.distance,x0:e.x,y0:e.y,x1:h,y1:k}:e.distance}else if(a instanceof OpenLayers.Geometry.LineString){var g=this.getSortedSegments(),h=a.getSortedSegments(),p,q,r=h.length,s={point:!0},m=0,n=g.length;a:for(;m<n;++m){k=g[m];l=k.x1;q=k.y1;for(var t=0;t<r;++t)if(d=h[t],p=OpenLayers.Geometry.segmentsIntersect(k,
+d,s)){f=0;e={distance:0,x0:p.x,y0:p.y,x1:p.x,y1:p.y};break a}else d=OpenLayers.Geometry.distanceToSegment({x:l,y:q},d),d.distance<f&&(f=d.distance,e={distance:f,x0:l,y0:q,x1:d.x,y1:d.y})}c||(e=e.distance);0!==f&&k&&(d=a.distanceTo(new OpenLayers.Geometry.Point(k.x2,k.y2),b),m=c?d.distance:d,m<f&&(e=c?{distance:f,x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0}:m))}else e=a.distanceTo(this,b),c&&(e={distance:e.distance,x0:e.x1,y0:e.y1,x1:e.x0,y1:e.y0});return e},simplify:function(a){if(this&&null!==this){var b=this.getVertices();
+if(3>b.length)return this;var c=function(a,b,d,k){for(var l=0,m=0,n=b,p;n<d;n++){p=a[b];var q=a[d],r=a[n],r=Math.abs(0.5*(p.x*q.y+q.x*r.y+r.x*p.y-q.x*p.y-r.x*q.y-p.x*r.y));p=Math.sqrt(Math.pow(p.x-q.x,2)+Math.pow(p.y-q.y,2));p=2*(r/p);p>l&&(l=p,m=n)}l>k&&m!=b&&(e.push(m),c(a,b,m,k),c(a,m,d,k))},d=b.length-1,e=[];e.push(0);for(e.push(d);b[0].equals(b[d]);)d--,e.push(d);c(b,0,d,a);a=[];e.sort(function(a,b){return a-b});for(d=0;d<e.length;d++)a.push(b[e[d]]);return new OpenLayers.Geometry.LineString(a)}return this},
+CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Geometry.MultiLineString=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LineString"],split:function(a,b){for(var c=null,d=b&&b.mutual,e,f,g,h,k=[],l=[a],m=0,n=this.components.length;m<n;++m){f=this.components[m];g=!1;for(var p=0;p<l.length;++p)if(e=f.split(l[p],b)){if(d){g=e[0];for(var q=0,r=g.length;q<r;++q)0===q&&k.length?k[k.length-1].addComponent(g[q]):k.push(new OpenLayers.Geometry.MultiLineString([g[q]]));g=!0;e=e[1]}if(e.length){e.unshift(p,
+1);Array.prototype.splice.apply(l,e);break}}g||(k.length?k[k.length-1].addComponent(f.clone()):k=[new OpenLayers.Geometry.MultiLineString(f.clone())])}k&&1<k.length?g=!0:k=[];l&&1<l.length?h=!0:l=[];if(g||h)c=d?[k,l]:l;return c},splitWith:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h,k,l;if(a instanceof OpenLayers.Geometry.LineString){l=[];k=[a];for(var m=0,n=this.components.length;m<n;++m){g=!1;f=this.components[m];for(var p=0;p<k.length;++p)if(e=k[p].split(f,b)){d&&(g=e[0],g.length&&(g.unshift(p,
+1),Array.prototype.splice.apply(k,g),p+=g.length-2),e=e[1],0===e.length&&(e=[f.clone()]));g=0;for(var q=e.length;g<q;++g)0===g&&l.length?l[l.length-1].addComponent(e[g]):l.push(new OpenLayers.Geometry.MultiLineString([e[g]]));g=!0}g||(l.length?l[l.length-1].addComponent(f.clone()):l=[new OpenLayers.Geometry.MultiLineString([f.clone()])])}}else c=a.split(this);k&&1<k.length?h=!0:k=[];l&&1<l.length?g=!0:l=[];if(h||g)c=d?[k,l]:l;return c},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});OpenLayers.Geometry.LinearRing=OpenLayers.Class(OpenLayers.Geometry.LineString,{componentTypes:["OpenLayers.Geometry.Point"],addComponent:function(a,b){var c=!1,d=this.components.pop();null==b&&a.equals(d)||(c=OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,arguments));OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]);return c},removeComponent:function(a){var b=this.components&&3<this.components.length;b&&(this.components.pop(),OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+arguments),OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]));return b},move:function(a,b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d-1;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0,e=this.components.length;d<e-1;++d)this.components[d].resize(a,b,c);return this},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].transform(a,
+b);this.bounds=null}return this},getCentroid:function(){if(this.components){var a=this.components.length;if(0<a&&2>=a)return this.components[0].clone();if(2<a){var b=0,c=0,d=this.components[0].x,e=this.components[0].y,f=-1*this.getArea();if(0!=f){for(var g=0;g<a-1;g++)var h=this.components[g],k=this.components[g+1],b=b+(h.x+k.x-2*d)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e)),c=c+(h.y+k.y-2*e)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e));b=d+b/(6*f);a=e+c/(6*f)}else{for(g=0;g<a-1;g++)b+=this.components[g].x,c+=this.components[g].y;
+b/=a-1;a=c/(a-1)}return new OpenLayers.Geometry.Point(b,a)}return null}},getArea:function(){var a=0;if(this.components&&2<this.components.length){for(var b=a=0,c=this.components.length;b<c-1;b++)var d=this.components[b],e=this.components[b+1],a=a+(d.x+e.x)*(e.y-d.y);a=-a/2}return a},getGeodesicArea:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;c=b.components&&b.components.length;if(2<c){for(var d,e,f=0;f<c-1;f++)d=b.components[f],
+e=b.components[f+1],a+=OpenLayers.Util.rad(e.x-d.x)*(2+Math.sin(OpenLayers.Util.rad(d.y))+Math.sin(OpenLayers.Util.rad(e.y)));a=40680631590769*a/2}return a},containsPoint:function(a){var b=OpenLayers.Number.limitSigDigs,c=b(a.x,14);a=b(a.y,14);for(var d=this.components.length-1,e,f,g,h,k,l=0,m=0;m<d;++m)if(e=this.components[m],g=b(e.x,14),e=b(e.y,14),f=this.components[m+1],h=b(f.x,14),f=b(f.y,14),e==f){if(a==e&&(g<=h&&c>=g&&c<=h||g>=h&&c<=g&&c>=h)){l=-1;break}}else{k=b((a-f)*((h-g)/(f-e))+h,14);if(k==
+c&&(e<f&&a>=e&&a<=f||e>f&&a<=e&&a>=f)){l=-1;break}k<=c||g!=h&&(k<Math.min(g,h)||k>Math.max(g,h))||(e<f&&a>=e&&a<f||e>f&&a<e&&a>=f)&&++l}return-1==l?1:!!(l&1)},intersects:function(a){var b=!1;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME)b=a.intersects(this);else if("OpenLayers.Geometry.LinearRing"==a.CLASS_NAME)b=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[a]);else for(var c=0,d=a.components.length;c<
+d&&!(b=a.components[c].intersects(this));++c);return b},getVertices:function(a){return!0===a?[]:this.components.slice(0,this.components.length-1)},CLASS_NAME:"OpenLayers.Geometry.LinearRing"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],getArea:function(){var a=0;if(this.components&&0<this.components.length)for(var a=a+Math.abs(this.components[0].getArea()),b=1,c=this.components.length;b<c;b++)a-=Math.abs(this.components[b].getArea());return a},getGeodesicArea:function(a){var b=0;if(this.components&&0<this.components.length)for(var b=b+Math.abs(this.components[0].getGeodesicArea(a)),c=1,d=this.components.length;c<
+d;c++)b-=Math.abs(this.components[c].getGeodesicArea(a));return b},containsPoint:function(a){var b=this.components.length,c=!1;if(0<b&&(c=this.components[0].containsPoint(a),1!==c&&c&&1<b))for(var d,e=1;e<b;++e)if(d=this.components[e].containsPoint(a)){c=1===d?1:!1;break}return c},intersects:function(a){var b=!1,c,d;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME||"OpenLayers.Geometry.LinearRing"==a.CLASS_NAME){c=0;for(d=
+this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);if(!b)for(c=0,d=a.components.length;c<d&&!(b=this.containsPoint(a.components[c]));++c);}else for(c=0,d=a.components.length;c<d&&!(b=this.intersects(a.components[c]));++c);if(!b&&"OpenLayers.Geometry.Polygon"==a.CLASS_NAME){var e=this.components[0];c=0;for(d=e.components.length;c<d&&!(b=a.containsPoint(e.components[c]));++c);}return b},distanceTo:function(a,b){return b&&!1===b.edge&&this.intersects(a)?0:OpenLayers.Geometry.Collection.prototype.distanceTo.apply(this,
+[a,b])},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(a,b,c,d){var e=Math.PI*(1/c-0.5);d&&(e+=d/180*Math.PI);for(var f,g=[],h=0;h<c;++h)f=e+2*h*Math.PI/c,d=a.x+b*Math.cos(f),f=a.y+b*Math.sin(f),g.push(new OpenLayers.Geometry.Point(d,f));a=new OpenLayers.Geometry.LinearRing(g);return new OpenLayers.Geometry.Polygon([a])};OpenLayers.Geometry.MultiPolygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Polygon"],CLASS_NAME:"OpenLayers.Geometry.MultiPolygon"});OpenLayers.Format.GML=OpenLayers.Class(OpenLayers.Format.XML,{featureNS:"http://mapserver.gis.umn.edu/mapserver",featurePrefix:"feature",featureName:"featureMember",layerName:"features",geometryName:"geometry",collectionName:"FeatureCollection",gmlns:"http://www.opengis.net/gml",extractAttributes:!0,xy:!0,initialize:function(a){this.regExes={trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g};OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a=this.getElementsByTagNameNS(a.documentElement,this.gmlns,this.featureName);for(var b=[],c=0;c<a.length;c++){var d=this.parseFeature(a[c]);d&&b.push(d)}return b},parseFeature:function(a){for(var b="MultiPolygon Polygon MultiLineString LineString MultiPoint Point Envelope".split(" "),c,d,e,f=0;f<b.length;++f)if(c=b[f],d=this.getElementsByTagNameNS(a,this.gmlns,c),0<d.length){if(e=this.parseGeometry[c.toLowerCase()])e=e.apply(this,
+[d[0]]),this.internalProjection&&this.externalProjection&&e.transform(this.externalProjection,this.internalProjection);else throw new TypeError("Unsupported geometry type: "+c);break}var g;c=this.getElementsByTagNameNS(a,this.gmlns,"Box");for(f=0;f<c.length;++f)b=c[f],d=this.parseGeometry.box.apply(this,[b]),b=b.parentNode,"boundedBy"===(b.localName||b.nodeName.split(":").pop())?g=d:e=d.toGeometry();var h;this.extractAttributes&&(h=this.parseAttributes(a));h=new OpenLayers.Feature.Vector(e,h);h.bounds=
+g;h.gml={featureType:a.firstChild.nodeName.split(":")[1],featureNS:a.firstChild.namespaceURI,featureNSPrefix:a.firstChild.prefix};a=a.firstChild;for(var k;a&&(1!=a.nodeType||!(k=a.getAttribute("fid")||a.getAttribute("id")));)a=a.nextSibling;h.fid=k;return h},parseGeometry:{point:function(a){var b,c;c=[];b=this.getElementsByTagNameNS(a,this.gmlns,"pos");0<b.length&&(c=b[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));0==c.length&&(b=this.getElementsByTagNameNS(a,
+this.gmlns,"coordinates"),0<b.length&&(c=b[0].firstChild.nodeValue,c=c.replace(this.regExes.removeSpace,""),c=c.split(",")));0==c.length&&(b=this.getElementsByTagNameNS(a,this.gmlns,"coord"),0<b.length&&(a=this.getElementsByTagNameNS(b[0],this.gmlns,"X"),b=this.getElementsByTagNameNS(b[0],this.gmlns,"Y"),0<a.length&&0<b.length&&(c=[a[0].firstChild.nodeValue,b[0].firstChild.nodeValue])));2==c.length&&(c[2]=null);return this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],
+c[0],c[2])},multipoint:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"Point");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.point.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a,b){var c,d;d=[];var e=[];c=this.getElementsByTagNameNS(a,this.gmlns,"posList");if(0<c.length){d=this.getChildValue(c[0]);d=d.replace(this.regExes.trimSpace,"");d=d.split(this.regExes.splitSpace);var f=parseInt(c[0].getAttribute("dimension")),
+g,h,k;for(c=0;c<d.length/f;++c)g=c*f,h=d[g],k=d[g+1],g=2==f?null:d[g+2],this.xy?e.push(new OpenLayers.Geometry.Point(h,k,g)):e.push(new OpenLayers.Geometry.Point(k,h,g))}if(0==d.length&&(c=this.getElementsByTagNameNS(a,this.gmlns,"coordinates"),0<c.length))for(d=this.getChildValue(c[0]),d=d.replace(this.regExes.trimSpace,""),d=d.replace(this.regExes.trimComma,","),f=d.split(this.regExes.splitSpace),c=0;c<f.length;++c)d=f[c].split(","),2==d.length&&(d[2]=null),this.xy?e.push(new OpenLayers.Geometry.Point(d[0],
+d[1],d[2])):e.push(new OpenLayers.Geometry.Point(d[1],d[0],d[2]));d=null;0!=e.length&&(d=b?new OpenLayers.Geometry.LinearRing(e):new OpenLayers.Geometry.LineString(e));return d},multilinestring:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"LineString");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.linestring.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"LinearRing");
+var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.linestring.apply(this,[a[d],!0]))&&b.push(c);return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"Polygon");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.polygon.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiPolygon(b)},envelope:function(a){var b=[],c,d,e=this.getElementsByTagNameNS(a,this.gmlns,"lowerCorner");if(0<e.length){c=
+[];0<e.length&&(c=e[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));2==c.length&&(c[2]=null);var f=this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2])}a=this.getElementsByTagNameNS(a,this.gmlns,"upperCorner");if(0<a.length){c=[];0<a.length&&(c=a[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));2==c.length&&(c[2]=null);var g=this.xy?new OpenLayers.Geometry.Point(c[0],
+c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2])}f&&g&&(b.push(new OpenLayers.Geometry.Point(f.x,f.y)),b.push(new OpenLayers.Geometry.Point(g.x,f.y)),b.push(new OpenLayers.Geometry.Point(g.x,g.y)),b.push(new OpenLayers.Geometry.Point(f.x,g.y)),b.push(new OpenLayers.Geometry.Point(f.x,f.y)),b=new OpenLayers.Geometry.LinearRing(b),d=new OpenLayers.Geometry.Polygon([b]));return d},box:function(a){var b=this.getElementsByTagNameNS(a,this.gmlns,"coordinates"),c=a=null;0<b.length&&(b=b[0].firstChild.nodeValue,
+b=b.split(" "),2==b.length&&(a=b[0].split(","),c=b[1].split(",")));if(null!==a&&null!==c)return new OpenLayers.Bounds(parseFloat(a[0]),parseFloat(a[1]),parseFloat(c[0]),parseFloat(c[1]))}},parseAttributes:function(a){var b={};a=a.firstChild;for(var c,d,e;a;){if(1==a.nodeType){a=a.childNodes;for(c=0;c<a.length;++c)if(d=a[c],1==d.nodeType)if(e=d.childNodes,1==e.length){if(e=e[0],3==e.nodeType||4==e.nodeType)d=d.prefix?d.nodeName.split(":")[1]:d.nodeName,e=e.nodeValue.replace(this.regExes.trimSpace,
+""),b[d]=e}else b[d.nodeName.split(":").pop()]=null;break}a=a.nextSibling}return b},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=this.createElementNS("http://www.opengis.net/wfs","wfs:"+this.collectionName),c=0;c<a.length;c++)b.appendChild(this.createFeatureXML(a[c]));return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.featureNS,this.featurePrefix+":"+this.geometryName);c.appendChild(b);
+var b=this.createElementNS(this.gmlns,"gml:"+this.featureName),d=this.createElementNS(this.featureNS,this.featurePrefix+":"+this.layerName);d.setAttribute("fid",a.fid||a.id);d.appendChild(c);for(var e in a.attributes){var c=this.createTextNode(a.attributes[e]),f=e.substring(e.lastIndexOf(":")+1),f=this.createElementNS(this.featureNS,this.featurePrefix+":"+f);f.appendChild(c);d.appendChild(f)}b.appendChild(d);return b},buildGeometryNode:function(a){this.externalProjection&&this.internalProjection&&
+(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME,b=b.substring(b.lastIndexOf(".")+1);return this.buildGeometry[b.toLowerCase()].apply(this,[a])},buildGeometry:{point:function(a){var b=this.createElementNS(this.gmlns,"gml:Point");b.appendChild(this.buildCoordinatesNode(a));return b},multipoint:function(a){var b=this.createElementNS(this.gmlns,"gml:MultiPoint");a=a.components;for(var c,d,e=0;e<a.length;e++)c=this.createElementNS(this.gmlns,"gml:pointMember"),
+d=this.buildGeometry.point.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},linestring:function(a){var b=this.createElementNS(this.gmlns,"gml:LineString");b.appendChild(this.buildCoordinatesNode(a));return b},multilinestring:function(a){var b=this.createElementNS(this.gmlns,"gml:MultiLineString");a=a.components;for(var c,d,e=0;e<a.length;++e)c=this.createElementNS(this.gmlns,"gml:lineStringMember"),d=this.buildGeometry.linestring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);
+return b},linearring:function(a){var b=this.createElementNS(this.gmlns,"gml:LinearRing");b.appendChild(this.buildCoordinatesNode(a));return b},polygon:function(a){var b=this.createElementNS(this.gmlns,"gml:Polygon");a=a.components;for(var c,d,e=0;e<a.length;++e)c=0==e?"outerBoundaryIs":"innerBoundaryIs",c=this.createElementNS(this.gmlns,"gml:"+c),d=this.buildGeometry.linearring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},multipolygon:function(a){var b=this.createElementNS(this.gmlns,
+"gml:MultiPolygon");a=a.components;for(var c,d,e=0;e<a.length;++e)c=this.createElementNS(this.gmlns,"gml:polygonMember"),d=this.buildGeometry.polygon.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},bounds:function(a){var b=this.createElementNS(this.gmlns,"gml:Box");b.appendChild(this.buildCoordinatesNode(a));return b}},buildCoordinatesNode:function(a){var b=this.createElementNS(this.gmlns,"gml:coordinates");b.setAttribute("decimal",".");b.setAttribute("cs",",");b.setAttribute("ts",
+" ");var c=[];if(a instanceof OpenLayers.Bounds)c.push(a.left+","+a.bottom),c.push(a.right+","+a.top);else{a=a.components?a.components:[a];for(var d=0;d<a.length;d++)c.push(a[d].x+","+a[d].y)}c=this.createTextNode(c.join(" "));b.appendChild(c);return b},CLASS_NAME:"OpenLayers.Format.GML"});OpenLayers.Format.GML||(OpenLayers.Format.GML={});
+OpenLayers.Format.GML.Base=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs"},defaultPrefix:"gml",schemaLocation:null,featureType:null,featureNS:null,geometryName:"geometry",extractAttributes:!0,srsName:null,xy:!0,geometryTypes:null,singleFeatureType:null,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g,featureMember:/^(.*:)?featureMembers?$/},
+initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);this.setGeometryTypes();a&&a.featureNS&&this.setNamespace("feature",a.featureNS);this.singleFeatureType=!a||"string"===typeof a.featureType},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b=[];this.readNode(a,{features:b},!0);if(0==b.length){var c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMember");if(c.length){a=
+0;for(var d=c.length;a<d;++a)this.readNode(c[a],{features:b},!0)}else c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMembers"),c.length&&this.readNode(c[0],{features:b},!0)}return b},readNode:function(a,b,c){!0===c&&!0===this.autoConfig&&(this.featureType=null,delete this.namespaceAlias[this.featureNS],delete this.namespaces.feature,this.featureNS=null);this.featureNS||(a.prefix in this.namespaces||a.parentNode.namespaceURI!=this.namespaces.gml||!this.regExes.featureMember.test(a.parentNode.nodeName))||
+(this.featureType=a.nodeName.split(":").pop(),this.setNamespace("feature",a.namespaceURI),this.featureNS=a.namespaceURI,this.autoConfig=!0);return OpenLayers.Format.XML.prototype.readNode.apply(this,[a,b])},readers:{gml:{_inherit:function(a,b,c){},featureMember:function(a,b){this.readChildNodes(a,b)},featureMembers:function(a,b){this.readChildNodes(a,b)},name:function(a,b){b.name=this.getChildValue(a)},boundedBy:function(a,b){var c={};this.readChildNodes(a,c);c.components&&0<c.components.length&&
+(b.bounds=c.components[0])},Point:function(a,b){var c={points:[]};this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(c.points[0])},coordinates:function(a,b){for(var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace),d,e=c.length,f=Array(e),g=0;g<e;++g)d=c[g].split(","),f[g]=this.xy?new OpenLayers.Geometry.Point(d[0],d[1],d[2]):new OpenLayers.Geometry.Point(d[1],d[0],d[2]);b.points=f},coord:function(a,
+b){var c={};this.readChildNodes(a,c);b.points||(b.points=[]);b.points.push(new OpenLayers.Geometry.Point(c.x,c.y,c.z))},X:function(a,b){b.x=this.getChildValue(a)},Y:function(a,b){b.y=this.getChildValue(a)},Z:function(a,b){b.z=this.getChildValue(a)},MultiPoint:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiPoint(c.components)]},pointMember:function(a,b){this.readChildNodes(a,b)},LineString:function(a,
+b){var c={};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.LineString(c.points))},MultiLineString:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiLineString(c.components)]},lineStringMember:function(a,b){this.readChildNodes(a,b)},Polygon:function(a,b){var c={outer:null,inner:[]};this.readers.gml._inherit.apply(this,
+[a,c,b]);this.readChildNodes(a,c);c.inner.unshift(c.outer);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.Polygon(c.inner))},LinearRing:function(a,b){var c={};this.readers.gml._inherit.apply(this,[a,c]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.LinearRing(c.points)]},MultiPolygon:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiPolygon(c.components)]},
+polygonMember:function(a,b){this.readChildNodes(a,b)},GeometryCollection:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.Collection(c.components)]},geometryMember:function(a,b){this.readChildNodes(a,b)}},feature:{"*":function(a,b){var c,d=a.localName||a.nodeName.split(":").pop();b.features?this.singleFeatureType||-1===OpenLayers.Util.indexOf(this.featureType,d)?d===this.featureType&&(c="_typeName"):c=
+"_typeName":0==a.childNodes.length||1==a.childNodes.length&&3==a.firstChild.nodeType?this.extractAttributes&&(c="_attribute"):c="_geometry";c&&this.readers.feature[c].apply(this,[a,b])},_typeName:function(a,b){var c={components:[],attributes:{}};this.readChildNodes(a,c);c.name&&(c.attributes.name=c.name);var d=new OpenLayers.Feature.Vector(c.components[0],c.attributes);this.singleFeatureType||(d.type=a.nodeName.split(":").pop(),d.namespace=a.namespaceURI);var e=a.getAttribute("fid")||this.getAttributeNS(a,
+this.namespaces.gml,"id");e&&(d.fid=e);this.internalProjection&&(this.externalProjection&&d.geometry)&&d.geometry.transform(this.externalProjection,this.internalProjection);c.bounds&&(d.bounds=c.bounds);b.features.push(d)},_geometry:function(a,b){this.geometryName||(this.geometryName=a.nodeName.split(":").pop());this.readChildNodes(a,b)},_attribute:function(a,b){var c=a.localName||a.nodeName.split(":").pop(),d=this.getChildValue(a);b.attributes[c]=d}},wfs:{FeatureCollection:function(a,b){this.readChildNodes(a,
+b)}}},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"featureMembers":"featureMember";a=this.writeNode("gml:"+b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:{featureMember:function(a){var b=this.createElementNSPlus("gml:featureMember");this.writeNode("feature:_typeName",a,b);return b},MultiPoint:function(a){var b=this.createElementNSPlus("gml:MultiPoint");a=a.components||[a];
+for(var c=0,d=a.length;c<d;++c)this.writeNode("pointMember",a[c],b);return b},pointMember:function(a){var b=this.createElementNSPlus("gml:pointMember");this.writeNode("Point",a,b);return b},MultiLineString:function(a){var b=this.createElementNSPlus("gml:MultiLineString");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("lineStringMember",a[c],b);return b},lineStringMember:function(a){var b=this.createElementNSPlus("gml:lineStringMember");this.writeNode("LineString",a,b);return b},
+MultiPolygon:function(a){var b=this.createElementNSPlus("gml:MultiPolygon");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("polygonMember",a[c],b);return b},polygonMember:function(a){var b=this.createElementNSPlus("gml:polygonMember");this.writeNode("Polygon",a,b);return b},GeometryCollection:function(a){for(var b=this.createElementNSPlus("gml:GeometryCollection"),c=0,d=a.components.length;c<d;++c)this.writeNode("geometryMember",a.components[c],b);return b},geometryMember:function(a){var b=
+this.createElementNSPlus("gml:geometryMember");a=this.writeNode("feature:_geometry",a);b.appendChild(a.firstChild);return b}},feature:{_typeName:function(a){var b=this.createElementNSPlus("feature:"+this.featureType,{attributes:{fid:a.fid}});a.geometry&&this.writeNode("feature:_geometry",a.geometry,b);for(var c in a.attributes){var d=a.attributes[c];null!=d&&this.writeNode("feature:_attribute",{name:c,value:d},b)}return b},_geometry:function(a){this.externalProjection&&this.internalProjection&&(a=
+a.clone().transform(this.internalProjection,this.externalProjection));var b=this.createElementNSPlus("feature:"+this.geometryName);a=this.writeNode("gml:"+this.geometryTypes[a.CLASS_NAME],a,b);this.srsName&&a.setAttribute("srsName",this.srsName);return b},_attribute:function(a){return this.createElementNSPlus("feature:"+a.name,{value:a.value})}},wfs:{FeatureCollection:function(a){for(var b=this.createElementNSPlus("wfs:FeatureCollection"),c=0,d=a.length;c<d;++c)this.writeNode("gml:featureMember",
+a[c],b);return b}}},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":"LineString","OpenLayers.Geometry.MultiLineString":"MultiLineString","OpenLayers.Geometry.Polygon":"Polygon","OpenLayers.Geometry.MultiPolygon":"MultiPolygon","OpenLayers.Geometry.Collection":"GeometryCollection"}},CLASS_NAME:"OpenLayers.Format.GML.Base"});OpenLayers.Format.GML.v3=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",curve:!1,multiCurve:!0,surface:!1,multiSurface:!0,initialize:function(a){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[a])},readers:{gml:OpenLayers.Util.applyDefaults({_inherit:function(a,b,c){if(a=parseInt(a.getAttribute("srsDimension"),10)||c&&c.srsDimension)b.srsDimension=a},featureMembers:function(a,
+b){this.readChildNodes(a,b)},Curve:function(a,b){var c={points:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.LineString(c.points))},segments:function(a,b){this.readChildNodes(a,b)},LineStringSegment:function(a,b){var c={};this.readChildNodes(a,c);c.points&&Array.prototype.push.apply(b.points,c.points)},pos:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(this.regExes.splitSpace),
+c=this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2]);b.points=[c]},posList:function(a,b){for(var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(this.regExes.splitSpace),d=b.srsDimension||parseInt(a.getAttribute("srsDimension")||a.getAttribute("dimension"),10)||2,e,f,g,h=Array(c.length/d),k=0,l=c.length;k<l;k+=d)e=c[k],f=c[k+1],g=2==d?void 0:c[k+2],h[k/d]=this.xy?new OpenLayers.Geometry.Point(e,f,g):new OpenLayers.Geometry.Point(f,
+e,g);b.points=h},Surface:function(a,b){this.readChildNodes(a,b)},patches:function(a,b){this.readChildNodes(a,b)},PolygonPatch:function(a,b){this.readers.gml.Polygon.apply(this,[a,b])},exterior:function(a,b){var c={};this.readChildNodes(a,c);b.outer=c.components[0]},interior:function(a,b){var c={};this.readChildNodes(a,c);b.inner.push(c.components[0])},MultiCurve:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);0<c.components.length&&(b.components=
+[new OpenLayers.Geometry.MultiLineString(c.components)])},curveMember:function(a,b){this.readChildNodes(a,b)},MultiSurface:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);0<c.components.length&&(b.components=[new OpenLayers.Geometry.MultiPolygon(c.components)])},surfaceMember:function(a,b){this.readChildNodes(a,b)},surfaceMembers:function(a,b){this.readChildNodes(a,b)},pointMembers:function(a,b){this.readChildNodes(a,b)},lineStringMembers:function(a,
+b){this.readChildNodes(a,b)},polygonMembers:function(a,b){this.readChildNodes(a,b)},geometryMembers:function(a,b){this.readChildNodes(a,b)},Envelope:function(a,b){var c={points:Array(2)};this.readChildNodes(a,c);b.components||(b.components=[]);var d=c.points[0],c=c.points[1];b.components.push(new OpenLayers.Bounds(d.x,d.y,c.x,c.y))},lowerCorner:function(a,b){var c={};this.readers.gml.pos.apply(this,[a,c]);b.points[0]=c.points[0]},upperCorner:function(a,b){var c={};this.readers.gml.pos.apply(this,
+[a,c]);b.points[1]=c.points[0]}},OpenLayers.Format.GML.Base.prototype.readers.gml),feature:OpenLayers.Format.GML.Base.prototype.readers.feature,wfs:OpenLayers.Format.GML.Base.prototype.readers.wfs},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"featureMembers":"featureMember";a=this.writeNode("gml:"+b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:OpenLayers.Util.applyDefaults({featureMembers:function(a){for(var b=
+this.createElementNSPlus("gml:featureMembers"),c=0,d=a.length;c<d;++c)this.writeNode("feature:_typeName",a[c],b);return b},Point:function(a){var b=this.createElementNSPlus("gml:Point");this.writeNode("pos",a,b);return b},pos:function(a){return this.createElementNSPlus("gml:pos",{value:this.xy?a.x+" "+a.y:a.y+" "+a.x})},LineString:function(a){var b=this.createElementNSPlus("gml:LineString");this.writeNode("posList",a.components,b);return b},Curve:function(a){var b=this.createElementNSPlus("gml:Curve");
+this.writeNode("segments",a,b);return b},segments:function(a){var b=this.createElementNSPlus("gml:segments");this.writeNode("LineStringSegment",a,b);return b},LineStringSegment:function(a){var b=this.createElementNSPlus("gml:LineStringSegment");this.writeNode("posList",a.components,b);return b},posList:function(a){for(var b=a.length,c=Array(b),d,e=0;e<b;++e)d=a[e],c[e]=this.xy?d.x+" "+d.y:d.y+" "+d.x;return this.createElementNSPlus("gml:posList",{value:c.join(" ")})},Surface:function(a){var b=this.createElementNSPlus("gml:Surface");
+this.writeNode("patches",a,b);return b},patches:function(a){var b=this.createElementNSPlus("gml:patches");this.writeNode("PolygonPatch",a,b);return b},PolygonPatch:function(a){var b=this.createElementNSPlus("gml:PolygonPatch",{attributes:{interpolation:"planar"}});this.writeNode("exterior",a.components[0],b);for(var c=1,d=a.components.length;c<d;++c)this.writeNode("interior",a.components[c],b);return b},Polygon:function(a){var b=this.createElementNSPlus("gml:Polygon");this.writeNode("exterior",a.components[0],
+b);for(var c=1,d=a.components.length;c<d;++c)this.writeNode("interior",a.components[c],b);return b},exterior:function(a){var b=this.createElementNSPlus("gml:exterior");this.writeNode("LinearRing",a,b);return b},interior:function(a){var b=this.createElementNSPlus("gml:interior");this.writeNode("LinearRing",a,b);return b},LinearRing:function(a){var b=this.createElementNSPlus("gml:LinearRing");this.writeNode("posList",a.components,b);return b},MultiCurve:function(a){var b=this.createElementNSPlus("gml:MultiCurve");
+a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("curveMember",a[c],b);return b},curveMember:function(a){var b=this.createElementNSPlus("gml:curveMember");this.curve?this.writeNode("Curve",a,b):this.writeNode("LineString",a,b);return b},MultiSurface:function(a){var b=this.createElementNSPlus("gml:MultiSurface");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("surfaceMember",a[c],b);return b},surfaceMember:function(a){var b=this.createElementNSPlus("gml:surfaceMember");
+this.surface?this.writeNode("Surface",a,b):this.writeNode("Polygon",a,b);return b},Envelope:function(a){var b=this.createElementNSPlus("gml:Envelope");this.writeNode("lowerCorner",a,b);this.writeNode("upperCorner",a,b);this.srsName&&b.setAttribute("srsName",this.srsName);return b},lowerCorner:function(a){return this.createElementNSPlus("gml:lowerCorner",{value:this.xy?a.left+" "+a.bottom:a.bottom+" "+a.left})},upperCorner:function(a){return this.createElementNSPlus("gml:upperCorner",{value:this.xy?
+a.right+" "+a.top:a.top+" "+a.right})}},OpenLayers.Format.GML.Base.prototype.writers.gml),feature:OpenLayers.Format.GML.Base.prototype.writers.feature,wfs:OpenLayers.Format.GML.Base.prototype.writers.wfs},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":!0===this.curve?"Curve":"LineString","OpenLayers.Geometry.MultiLineString":!1===this.multiCurve?"MultiLineString":"MultiCurve","OpenLayers.Geometry.Polygon":!0===
+this.surface?"Surface":"Polygon","OpenLayers.Geometry.MultiPolygon":!1===this.multiSurface?"MultiPolygon":"MultiSurface","OpenLayers.Geometry.Collection":"GeometryCollection"}},CLASS_NAME:"OpenLayers.Format.GML.v3"});OpenLayers.Format.Filter.v1_1_0=OpenLayers.Class(OpenLayers.Format.GML.v3,OpenLayers.Format.Filter.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",initialize:function(a){OpenLayers.Format.GML.v3.prototype.initialize.apply(this,[a])},readers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a,b){var c=a.getAttribute("matchCase"),c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,matchCase:!("false"===c||"0"===c)});this.readChildNodes(a,
+c);b.filters.push(c)},PropertyIsNotEqualTo:function(a,b){var c=a.getAttribute("matchCase"),c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO,matchCase:!("false"===c||"0"===c)});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLike:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(a,c);var d=a.getAttribute("wildCard"),e=a.getAttribute("singleChar"),f=a.getAttribute("escapeChar");c.value2regex(d,e,
+f);b.filters.push(c)}},OpenLayers.Format.Filter.v1.prototype.readers.ogc),gml:OpenLayers.Format.GML.v3.prototype.readers.gml,feature:OpenLayers.Format.GML.v3.prototype.readers.feature},writers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsEqualTo",{attributes:{matchCase:a.matchCase}});this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsNotEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNotEqualTo",
+{attributes:{matchCase:a.matchCase}});this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLike:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLike",{attributes:{matchCase:a.matchCase,wildCard:"*",singleChar:".",escapeChar:"!"}});this.writeNode("PropertyName",a,b);this.writeNode("Literal",a.regex2value(),b);return b},BBOX:function(a){var b=this.createElementNSPlus("ogc:BBOX");a.property&&this.writeNode("PropertyName",a,b);var c=this.writeNode("gml:Envelope",
+a.value);a.projection&&c.setAttribute("srsName",a.projection);b.appendChild(c);return b},SortBy:function(a){for(var b=this.createElementNSPlus("ogc:SortBy"),c=0,d=a.length;c<d;c++)this.writeNode("ogc:SortProperty",a[c],b);return b},SortProperty:function(a){var b=this.createElementNSPlus("ogc:SortProperty");this.writeNode("ogc:PropertyName",a,b);this.writeNode("ogc:SortOrder","DESC"==a.order?"DESC":"ASC",b);return b},SortOrder:function(a){return this.createElementNSPlus("ogc:SortOrder",{value:a})}},
+OpenLayers.Format.Filter.v1.prototype.writers.ogc),gml:OpenLayers.Format.GML.v3.prototype.writers.gml,feature:OpenLayers.Format.GML.v3.prototype.writers.feature},writeSpatial:function(a,b){var c=this.createElementNSPlus("ogc:"+b);this.writeNode("PropertyName",a,c);if(a.value instanceof OpenLayers.Filter.Function)this.writeNode("Function",a.value,c);else{var d;d=a.value instanceof OpenLayers.Geometry?this.writeNode("feature:_geometry",a.value).firstChild:this.writeNode("gml:Envelope",a.value);a.projection&&
+d.setAttribute("srsName",a.projection);c.appendChild(d)}return c},CLASS_NAME:"OpenLayers.Format.Filter.v1_1_0"});OpenLayers.Format.OWSCommon=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",getVersion:function(a,b){var c=this.version;if(!c){var d=a.getAttribute("xmlns:ows");d&&"1.1"===d.substring(d.lastIndexOf("/")+1)&&(c="1.1.0");c||(c=this.defaultVersion)}return c},CLASS_NAME:"OpenLayers.Format.OWSCommon"});OpenLayers.Format.OWSCommon.v1=OpenLayers.Class(OpenLayers.Format.XML,{regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},read:function(a,b){OpenLayers.Util.applyDefaults(b,this.options);var c={};this.readChildNodes(a,c);return c},readers:{ows:{Exception:function(a,b){var c={code:a.getAttribute("exceptionCode"),locator:a.getAttribute("locator"),texts:[]};b.exceptions.push(c);this.readChildNodes(a,c)},ExceptionText:function(a,b){var c=this.getChildValue(a);b.texts.push(c)},
+ServiceIdentification:function(a,b){b.serviceIdentification={};this.readChildNodes(a,b.serviceIdentification)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b["abstract"]=this.getChildValue(a)},Keywords:function(a,b){b.keywords={};this.readChildNodes(a,b.keywords)},Keyword:function(a,b){b[this.getChildValue(a)]=!0},ServiceType:function(a,b){b.serviceType={codeSpace:a.getAttribute("codeSpace"),value:this.getChildValue(a)}},ServiceTypeVersion:function(a,b){b.serviceTypeVersion=
+this.getChildValue(a)},Fees:function(a,b){b.fees=this.getChildValue(a)},AccessConstraints:function(a,b){b.accessConstraints=this.getChildValue(a)},ServiceProvider:function(a,b){b.serviceProvider={};this.readChildNodes(a,b.serviceProvider)},ProviderName:function(a,b){b.providerName=this.getChildValue(a)},ProviderSite:function(a,b){b.providerSite=this.getAttributeNS(a,this.namespaces.xlink,"href")},ServiceContact:function(a,b){b.serviceContact={};this.readChildNodes(a,b.serviceContact)},IndividualName:function(a,
+b){b.individualName=this.getChildValue(a)},PositionName:function(a,b){b.positionName=this.getChildValue(a)},ContactInfo:function(a,b){b.contactInfo={};this.readChildNodes(a,b.contactInfo)},Phone:function(a,b){b.phone={};this.readChildNodes(a,b.phone)},Voice:function(a,b){b.voice=this.getChildValue(a)},Address:function(a,b){b.address={};this.readChildNodes(a,b.address)},DeliveryPoint:function(a,b){b.deliveryPoint=this.getChildValue(a)},City:function(a,b){b.city=this.getChildValue(a)},AdministrativeArea:function(a,
+b){b.administrativeArea=this.getChildValue(a)},PostalCode:function(a,b){b.postalCode=this.getChildValue(a)},Country:function(a,b){b.country=this.getChildValue(a)},ElectronicMailAddress:function(a,b){b.electronicMailAddress=this.getChildValue(a)},Role:function(a,b){b.role=this.getChildValue(a)},OperationsMetadata:function(a,b){b.operationsMetadata={};this.readChildNodes(a,b.operationsMetadata)},Operation:function(a,b){var c=a.getAttribute("name");b[c]={};this.readChildNodes(a,b[c])},DCP:function(a,
+b){b.dcp={};this.readChildNodes(a,b.dcp)},HTTP:function(a,b){b.http={};this.readChildNodes(a,b.http)},Get:function(a,b){b.get||(b.get=[]);var c={url:this.getAttributeNS(a,this.namespaces.xlink,"href")};this.readChildNodes(a,c);b.get.push(c)},Post:function(a,b){b.post||(b.post=[]);var c={url:this.getAttributeNS(a,this.namespaces.xlink,"href")};this.readChildNodes(a,c);b.post.push(c)},Parameter:function(a,b){b.parameters||(b.parameters={});var c=a.getAttribute("name");b.parameters[c]={};this.readChildNodes(a,
+b.parameters[c])},Constraint:function(a,b){b.constraints||(b.constraints={});var c=a.getAttribute("name");b.constraints[c]={};this.readChildNodes(a,b.constraints[c])},Value:function(a,b){b[this.getChildValue(a)]=!0},OutputFormat:function(a,b){b.formats.push({value:this.getChildValue(a)});this.readChildNodes(a,b)},WGS84BoundingBox:function(a,b){var c={};c.crs=a.getAttribute("crs");b.BoundingBox?b.BoundingBox.push(c):(b.projection=c.crs,c=b);this.readChildNodes(a,c)},BoundingBox:function(a,b){this.readers.ows.WGS84BoundingBox.apply(this,
+[a,b])},LowerCorner:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace);b.left=c[0];b.bottom=c[1]},UpperCorner:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace);b.right=c[0];b.top=c[1];b.bounds=new OpenLayers.Bounds(b.left,b.bottom,b.right,b.top);delete b.left;delete b.bottom;delete b.right;delete b.top},
+Language:function(a,b){b.language=this.getChildValue(a)}}},writers:{ows:{BoundingBox:function(a,b){var c=this.createElementNSPlus(b||"ows:BoundingBox",{attributes:{crs:a.projection}});this.writeNode("ows:LowerCorner",a,c);this.writeNode("ows:UpperCorner",a,c);return c},LowerCorner:function(a){return this.createElementNSPlus("ows:LowerCorner",{value:a.bounds.left+" "+a.bounds.bottom})},UpperCorner:function(a){return this.createElementNSPlus("ows:UpperCorner",{value:a.bounds.right+" "+a.bounds.top})},
+Identifier:function(a){return this.createElementNSPlus("ows:Identifier",{value:a})},Title:function(a){return this.createElementNSPlus("ows:Title",{value:a})},Abstract:function(a){return this.createElementNSPlus("ows:Abstract",{value:a})},OutputFormat:function(a){return this.createElementNSPlus("ows:OutputFormat",{value:a})}}},CLASS_NAME:"OpenLayers.Format.OWSCommon.v1"});OpenLayers.Format.OWSCommon.v1_0_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1,{namespaces:{ows:"http://www.opengis.net/ows",xlink:"http://www.w3.org/1999/xlink"},readers:{ows:OpenLayers.Util.applyDefaults({ExceptionReport:function(a,b){b.success=!1;b.exceptionReport={version:a.getAttribute("version"),language:a.getAttribute("language"),exceptions:[]};this.readChildNodes(a,b.exceptionReport)}},OpenLayers.Format.OWSCommon.v1.prototype.readers.ows)},writers:{ows:OpenLayers.Format.OWSCommon.v1.prototype.writers.ows},
+CLASS_NAME:"OpenLayers.Format.OWSCommon.v1_0_0"});OpenLayers.Format.WFST.v1_1_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_1_0,OpenLayers.Format.WFST.v1,{version:"1.1.0",schemaLocations:{wfs:"http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"},initialize:function(a){OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this,[a]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[a])},readNode:function(a,b,c){return OpenLayers.Format.GML.v3.prototype.readNode.apply(this,arguments)},readers:{wfs:OpenLayers.Util.applyDefaults({FeatureCollection:function(a,
+b){b.numberOfFeatures=parseInt(a.getAttribute("numberOfFeatures"));OpenLayers.Format.WFST.v1.prototype.readers.wfs.FeatureCollection.apply(this,arguments)},TransactionResponse:function(a,b){b.insertIds=[];b.success=!1;this.readChildNodes(a,b)},TransactionSummary:function(a,b){b.success=!0},InsertResults:function(a,b){this.readChildNodes(a,b)},Feature:function(a,b){var c={fids:[]};this.readChildNodes(a,c);b.insertIds.push(c.fids[0])}},OpenLayers.Format.WFST.v1.prototype.readers.wfs),gml:OpenLayers.Format.GML.v3.prototype.readers.gml,
+feature:OpenLayers.Format.GML.v3.prototype.readers.feature,ogc:OpenLayers.Format.Filter.v1_1_0.prototype.readers.ogc,ows:OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows},writers:{wfs:OpenLayers.Util.applyDefaults({GetFeature:function(a){var b=OpenLayers.Format.WFST.v1.prototype.writers.wfs.GetFeature.apply(this,arguments);a&&this.setAttributes(b,{resultType:a.resultType,startIndex:a.startIndex,count:a.count});return b},Query:function(a){a=OpenLayers.Util.extend({featureNS:this.featureNS,
+featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},a);var b=a.featurePrefix,c=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(b?b+":":"")+a.featureType,srsName:a.srsName}});a.featureNS&&c.setAttribute("xmlns:"+b,a.featureNS);if(a.propertyNames)for(var b=0,d=a.propertyNames.length;b<d;b++)this.writeNode("wfs:PropertyName",{property:a.propertyNames[b]},c);a.filter&&(OpenLayers.Format.WFST.v1_1_0.prototype.setFilterProperty.call(this,a.filter),this.writeNode("ogc:Filter",
+a.filter,c));return c},PropertyName:function(a){return this.createElementNSPlus("wfs:PropertyName",{value:a.property})}},OpenLayers.Format.WFST.v1.prototype.writers.wfs),gml:OpenLayers.Format.GML.v3.prototype.writers.gml,feature:OpenLayers.Format.GML.v3.prototype.writers.feature,ogc:OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc},CLASS_NAME:"OpenLayers.Format.WFST.v1_1_0"});OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:!0,defaultFilter:null,initialize:function(a){a=a||{};OpenLayers.Util.extend(this,a);this.options=a},mergeWithDefaultFilter:function(a){return a&&this.defaultFilter?new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.defaultFilter,a]}):a||this.defaultFilter||void 0},destroy:function(){this.format=this.options=null},read:function(a){a=a||{};a.filter=this.mergeWithDefaultFilter(a.filter)},create:function(){},
+update:function(){},"delete":function(){},commit:function(){},abort:function(a){},createCallback:function(a,b,c){return OpenLayers.Function.bind(function(){a.apply(this,[b,c])},this)},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:!0,features:null,data:null,reqFeatures:null,priv:null,error:null,initialize:function(a){OpenLayers.Util.extend(this,a)},success:function(){return 0<this.code},CLASS_NAME:"OpenLayers.Protocol.Response"});
+OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Format.JSON=OpenLayers.Class(OpenLayers.Format,{indent:" ",space:" ",newline:"\n",level:0,pretty:!1,nativeJSON:function(){return!(!window.JSON||"function"!=typeof JSON.parse||"function"!=typeof JSON.stringify)}(),read:function(a,b){var c;if(this.nativeJSON)c=JSON.parse(a,b);else try{if(/^[\],:{}\s]*$/.test(a.replace(/\\["\\\/bfnrtu]/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))&&(c=eval("("+a+")"),"function"===
+typeof b)){var d=function(a,c){if(c&&"object"===typeof c)for(var e in c)c.hasOwnProperty(e)&&(c[e]=d(e,c[e]));return b(a,c)};c=d("",c)}}catch(e){}this.keepData&&(this.data=c);return c},write:function(a,b){this.pretty=!!b;var c=null,d=typeof a;if(this.serialize[d])try{c=!this.pretty&&this.nativeJSON?JSON.stringify(a):this.serialize[d].apply(this,[a])}catch(e){OpenLayers.Console.error("Trouble serializing: "+e)}return c},writeIndent:function(){var a=[];if(this.pretty)for(var b=0;b<this.level;++b)a.push(this.indent);
+return a.join("")},writeNewline:function(){return this.pretty?this.newline:""},writeSpace:function(){return this.pretty?this.space:""},serialize:{object:function(a){if(null==a)return"null";if(a.constructor==Date)return this.serialize.date.apply(this,[a]);if(a.constructor==Array)return this.serialize.array.apply(this,[a]);var b=["{"];this.level+=1;var c,d,e,f=!1;for(c in a)a.hasOwnProperty(c)&&(d=OpenLayers.Format.JSON.prototype.write.apply(this,[c,this.pretty]),e=OpenLayers.Format.JSON.prototype.write.apply(this,
+[a[c],this.pretty]),null!=d&&null!=e&&(f&&b.push(","),b.push(this.writeNewline(),this.writeIndent(),d,":",this.writeSpace(),e),f=!0));this.level-=1;b.push(this.writeNewline(),this.writeIndent(),"}");return b.join("")},array:function(a){var b,c=["["];this.level+=1;for(var d=0,e=a.length;d<e;++d)b=OpenLayers.Format.JSON.prototype.write.apply(this,[a[d],this.pretty]),null!=b&&(0<d&&c.push(","),c.push(this.writeNewline(),this.writeIndent(),b));this.level-=1;c.push(this.writeNewline(),this.writeIndent(),
+"]");return c.join("")},string:function(a){var b={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};return/["\\\x00-\x1f]/.test(a)?'"'+a.replace(/([\x00-\x1f\\"])/g,function(a,d){var e=b[d];if(e)return e;e=d.charCodeAt();return"\\u00"+Math.floor(e/16).toString(16)+(e%16).toString(16)})+'"':'"'+a+'"'},number:function(a){return isFinite(a)?String(a):"null"},"boolean":function(a){return String(a)},date:function(a){function b(a){return 10>a?"0"+a:a}return'"'+a.getFullYear()+
+"-"+b(a.getMonth()+1)+"-"+b(a.getDate())+"T"+b(a.getHours())+":"+b(a.getMinutes())+":"+b(a.getSeconds())+'"'}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.GeoJSON=OpenLayers.Class(OpenLayers.Format.JSON,{ignoreExtraDims:!1,read:function(a,b,c){b=b?b:"FeatureCollection";var d=null,e=null,e="string"==typeof a?OpenLayers.Format.JSON.prototype.read.apply(this,[a,c]):a;if(!e)OpenLayers.Console.error("Bad JSON: "+a);else if("string"!=typeof e.type)OpenLayers.Console.error("Bad GeoJSON - no type: "+a);else if(this.isValidType(e,b))switch(b){case "Geometry":try{d=this.parseGeometry(e)}catch(f){OpenLayers.Console.error(f)}break;case "Feature":try{d=
+this.parseFeature(e),d.type="Feature"}catch(g){OpenLayers.Console.error(g)}break;case "FeatureCollection":switch(d=[],e.type){case "Feature":try{d.push(this.parseFeature(e))}catch(h){d=null,OpenLayers.Console.error(h)}break;case "FeatureCollection":a=0;for(b=e.features.length;a<b;++a)try{d.push(this.parseFeature(e.features[a]))}catch(k){d=null,OpenLayers.Console.error(k)}break;default:try{var l=this.parseGeometry(e);d.push(new OpenLayers.Feature.Vector(l))}catch(m){d=null,OpenLayers.Console.error(m)}}}return d},
+isValidType:function(a,b){var c=!1;switch(b){case "Geometry":-1==OpenLayers.Util.indexOf("Point MultiPoint LineString MultiLineString Polygon MultiPolygon Box GeometryCollection".split(" "),a.type)?OpenLayers.Console.error("Unsupported geometry type: "+a.type):c=!0;break;case "FeatureCollection":c=!0;break;default:a.type==b?c=!0:OpenLayers.Console.error("Cannot convert types from "+a.type+" to "+b)}return c},parseFeature:function(a){var b,c,d;c=a.properties?a.properties:{};d=a.geometry&&a.geometry.bbox||
+a.bbox;try{b=this.parseGeometry(a.geometry)}catch(e){throw e;}b=new OpenLayers.Feature.Vector(b,c);d&&(b.bounds=OpenLayers.Bounds.fromArray(d));a.id&&(b.fid=a.id);return b},parseGeometry:function(a){if(null==a)return null;var b,c=!1;if("GeometryCollection"==a.type){if(!OpenLayers.Util.isArray(a.geometries))throw"GeometryCollection must have geometries array: "+a;b=a.geometries.length;for(var c=Array(b),d=0;d<b;++d)c[d]=this.parseGeometry.apply(this,[a.geometries[d]]);b=new OpenLayers.Geometry.Collection(c);
+c=!0}else{if(!OpenLayers.Util.isArray(a.coordinates))throw"Geometry must have coordinates array: "+a;if(!this.parseCoords[a.type.toLowerCase()])throw"Unsupported geometry type: "+a.type;try{b=this.parseCoords[a.type.toLowerCase()].apply(this,[a.coordinates])}catch(e){throw e;}}this.internalProjection&&(this.externalProjection&&!c)&&b.transform(this.externalProjection,this.internalProjection);return b},parseCoords:{point:function(a){if(!1==this.ignoreExtraDims&&2!=a.length)throw"Only 2D points are supported: "+
+a;return new OpenLayers.Geometry.Point(a[0],a[1])},multipoint:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.LineString(b)},multilinestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=
+this.parseCoords.linestring.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){for(var b=[],c,d,e=0,f=a.length;e<f;++e){try{d=this.parseCoords.linestring.apply(this,[a[e]])}catch(g){throw g;}c=new OpenLayers.Geometry.LinearRing(d.components);b.push(c)}return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.polygon.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPolygon(b)},
+box:function(a){if(2!=a.length)throw"GeoJSON box coordinates must have 2 elements";return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(a[0][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[0][1])])])}},write:function(a,b){var c={type:null};if(OpenLayers.Util.isArray(a)){c.type="FeatureCollection";var d=
+a.length;c.features=Array(d);for(var e=0;e<d;++e){var f=a[e];if(!f instanceof OpenLayers.Feature.Vector)throw"FeatureCollection only supports collections of features: "+f;c.features[e]=this.extract.feature.apply(this,[f])}}else 0==a.CLASS_NAME.indexOf("OpenLayers.Geometry")?c=this.extract.geometry.apply(this,[a]):a instanceof OpenLayers.Feature.Vector&&(c=this.extract.feature.apply(this,[a]),a.layer&&a.layer.projection&&(c.crs=this.createCRSObject(a)));return OpenLayers.Format.JSON.prototype.write.apply(this,
+[c,b])},createCRSObject:function(a){a=a.layer.projection.toString();var b={};a.match(/epsg:/i)&&(a=parseInt(a.substring(a.indexOf(":")+1)),b=4326==a?{type:"name",properties:{name:"urn:ogc:def:crs:OGC:1.3:CRS84"}}:{type:"name",properties:{name:"EPSG:"+a}});return b},extract:{feature:function(a){var b=this.extract.geometry.apply(this,[a.geometry]),b={type:"Feature",properties:a.attributes,geometry:b};null!=a.fid&&(b.id=a.fid);return b},geometry:function(a){if(null==a)return null;this.internalProjection&&
+this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME.split(".")[2];a=this.extract[b.toLowerCase()].apply(this,[a]);return"Collection"==b?{type:"GeometryCollection",geometries:a}:{type:b,coordinates:a}},point:function(a){return[a.x,a.y]},multipoint:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},linestring:function(a){for(var b=[],c=0,d=a.components.length;c<
+d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},multilinestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},polygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},multipolygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.polygon.apply(this,[a.components[c]]));return b},collection:function(a){for(var b=
+a.components.length,c=Array(b),d=0;d<b;++d)c[d]=this.extract.geometry.apply(this,[a.components[d]]);return c}},CLASS_NAME:"OpenLayers.Format.GeoJSON"});OpenLayers.Protocol.Script=OpenLayers.Class(OpenLayers.Protocol,{url:null,params:null,callback:null,callbackTemplate:"OpenLayers.Protocol.Script.registry.${id}",callbackKey:"callback",callbackPrefix:"",scope:null,format:null,pendingRequests:null,srsInBBOX:!1,initialize:function(a){a=a||{};this.params={};this.pendingRequests={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);this.format||(this.format=new OpenLayers.Format.GeoJSON);if(!this.filterToParams&&OpenLayers.Format.QueryStringFilter){var b=
+new OpenLayers.Format.QueryStringFilter({srsInBBOX:this.srsInBBOX});this.filterToParams=function(a,d){return b.write(a,d)}}},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=OpenLayers.Util.applyDefaults(a,this.options);a.params=OpenLayers.Util.applyDefaults(a.params,this.options.params);a.filter&&this.filterToParams&&(a.params=this.filterToParams(a.filter,a.params));var b=new OpenLayers.Protocol.Response({requestType:"read"}),c=this.createRequest(a.url,a.params,OpenLayers.Function.bind(function(c){b.data=
+c;this.handleRead(b,a)},this));b.priv=c;return b},createRequest:function(a,b,c){c=OpenLayers.Protocol.Script.register(c);var d=OpenLayers.String.format(this.callbackTemplate,{id:c});b=OpenLayers.Util.extend({},b);b[this.callbackKey]=this.callbackPrefix+d;a=OpenLayers.Util.urlAppend(a,OpenLayers.Util.getParameterString(b));b=document.createElement("script");b.type="text/javascript";b.src=a;b.id="OpenLayers_Protocol_Script_"+c;this.pendingRequests[b.id]=b;document.getElementsByTagName("head")[0].appendChild(b);
+return b},destroyRequest:function(a){OpenLayers.Protocol.Script.unregister(a.id.split("_").pop());delete this.pendingRequests[a.id];a.parentNode&&a.parentNode.removeChild(a)},handleRead:function(a,b){this.handleResponse(a,b)},handleResponse:function(a,b){b.callback&&(a.data?(a.features=this.parseFeatures(a.data),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE,this.destroyRequest(a.priv),b.callback.call(b.scope,a))},parseFeatures:function(a){return this.format.read(a)},
+abort:function(a){if(a)this.destroyRequest(a.priv);else for(var b in this.pendingRequests)this.destroyRequest(this.pendingRequests[b])},destroy:function(){this.abort();delete this.params;delete this.format;OpenLayers.Protocol.prototype.destroy.apply(this)},CLASS_NAME:"OpenLayers.Protocol.Script"});(function(){var a=OpenLayers.Protocol.Script,b=0;a.registry={};a.register=function(c){var d="c"+ ++b;a.registry[d]=function(){c.apply(this,arguments)};return d};a.unregister=function(b){delete a.registry[b]}})();OpenLayers.Format.EncodedPolyline=OpenLayers.Class(OpenLayers.Format,{geometryType:"linestring",initialize:function(a){OpenLayers.Format.prototype.initialize.apply(this,[a])},read:function(a){var b;if("linestring"==this.geometryType)b=OpenLayers.Geometry.LineString;else if("linearring"==this.geometryType)b=OpenLayers.Geometry.LinearRing;else if("multipoint"==this.geometryType)b=OpenLayers.Geometry.MultiPoint;else if("point"!=this.geometryType&&"polygon"!=this.geometryType)return null;a=this.decodeDeltas(a,
+2);for(var c=a.length,d=[],e=0;e+1<c;){var f=a[e++],g=a[e++];d.push(new OpenLayers.Geometry.Point(g,f))}return"point"==this.geometryType?new OpenLayers.Feature.Vector(d[0]):"polygon"==this.geometryType?new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(d)])):new OpenLayers.Feature.Vector(new b(d))},decode:function(a,b,c){a=this.decodeDeltas(a,b,c||1E5);c=a.length;for(var d=[],e=0;e+(b-1)<c;){for(var f=[],g=0;g<b;++g)f.push(a[e++]);d.push(f)}return d},
+write:function(a){a=(a.constructor==Array?a[0]:a).geometry;var b=a.CLASS_NAME.split(".")[2].toLowerCase();if("point"==b)a=Array(a);else if("linestring"==b||"linearring"==b||"multipoint"==b)a=a.components;else if("polygon"==b)a=a.components[0].components;else return null;for(var b=[],c=a.length,d=0;d<c;++d){var e=a[d];b.push(e.y);b.push(e.x)}return this.encodeDeltas(b,2)},encode:function(a,b,c){c=c||1E5;for(var d=[],e=a.length,f=0;f<e;++f)for(var g=a[f],h=0;h<b;++h)d.push(g[h]);return this.encodeDeltas(d,
+b,c)},encodeDeltas:function(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;for(var f=a.length,g=0;g<f;)for(d=0;d<b;++d,++g){var h=a[g],k=h-e[d];e[d]=h;a[g]=k}return this.encodeFloats(a,c||1E5)},decodeDeltas:function(a,b,c){var d,e=Array(b);for(d=0;d<b;++d)e[d]=0;a=this.decodeFloats(a,c||1E5);c=a.length;for(var f=0;f<c;)for(d=0;d<b;++d,++f)e[d]+=a[f],a[f]=e[d];return a},encodeFloats:function(a,b){for(var c=b||1E5,d=a.length,e=0;e<d;++e)a[e]=Math.round(a[e]*c);return this.encodeSignedIntegers(a)},decodeFloats:function(a,
+b){for(var c=b||1E5,d=this.decodeSignedIntegers(a),e=d.length,f=0;f<e;++f)d[f]/=c;return d},encodeSignedIntegers:function(a){for(var b=a.length,c=0;c<b;++c){var d=a[c],e=d<<1;0>d&&(e=~e);a[c]=e}return this.encodeUnsignedIntegers(a)},decodeSignedIntegers:function(a){a=this.decodeUnsignedIntegers(a);for(var b=a.length,c=0;c<b;++c){var d=a[c];a[c]=d&1?~(d>>1):d>>1}return a},encodeUnsignedIntegers:function(a){for(var b="",c=a.length,d=0;d<c;++d)b+=this.encodeUnsignedInteger(a[d]);return b},decodeUnsignedIntegers:function(a){for(var b=
+[],c=0,d=0,e=a.length,f=0;f<e;++f){var g=a.charCodeAt(f)-63,c=c|(g&31)<<d;32>g?(b.push(c),d=c=0):d+=5}return b},encodeFloat:function(a,b){a=Math.round(a*(b||1E5));return this.encodeSignedInteger(a)},decodeFloat:function(a,b){return this.decodeSignedInteger(a)/(b||1E5)},encodeSignedInteger:function(a){var b=a<<1;0>a&&(b=~b);return this.encodeUnsignedInteger(b)},decodeSignedInteger:function(a){a=this.decodeUnsignedInteger(a);return a&1?~(a>>1):a>>1},encodeUnsignedInteger:function(a){for(var b,c="";32<=
+a;)b=(32|a&31)+63,c+=String.fromCharCode(b),a>>=5;return c+=String.fromCharCode(a+63)},decodeUnsignedInteger:function(a){for(var b=0,c=0,d=a.length,e=0;e<d;++e){var f=a.charCodeAt(e)-63,b=b|(f&31)<<c;if(32>f)break;c+=5}return b},CLASS_NAME:"OpenLayers.Format.EncodedPolyline"});OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,autoActivate:!0,defaultControl:null,saveState:!1,allowDepress:!1,activeState:null,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,[a]);this.controls=[];this.activeState={}},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onButtonClick);OpenLayers.Control.prototype.destroy.apply(this,arguments);for(var a,b=this.controls.length-1;0<=b;b--)a=this.controls[b],a.events&&
+a.events.un({activate:this.iconOn,deactivate:this.iconOff}),a.panel_div=null;this.activeState=null},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){for(var a,b=0,c=this.controls.length;b<c;b++)a=this.controls[b],(a===this.defaultControl||this.saveState&&this.activeState[a.id])&&a.activate();!0===this.saveState&&(this.defaultControl=null);this.redraw();return!0}return!1},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){for(var a,
+b=0,c=this.controls.length;b<c;b++)a=this.controls[b],this.activeState[a.id]=a.deactivate();this.redraw();return!0}return!1},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",this,this.onButtonClick)):this.map.events.register("buttonclick",this,this.onButtonClick);this.addControlsToMap(this.controls);return this.div},redraw:function(){for(var a=this.div.childNodes.length-1;0<=a;a--)this.div.removeChild(this.div.childNodes[a]);
+this.div.innerHTML="";if(this.active)for(var a=0,b=this.controls.length;a<b;a++)this.div.appendChild(this.controls[a].panel_div)},activateControl:function(a){if(!this.active)return!1;if(a.type==OpenLayers.Control.TYPE_BUTTON)a.trigger();else if(a.type==OpenLayers.Control.TYPE_TOGGLE)a.active?a.deactivate():a.activate();else if(this.allowDepress&&a.active)a.deactivate();else{for(var b,c=0,d=this.controls.length;c<d;c++)b=this.controls[c],b==a||b.type!==OpenLayers.Control.TYPE_TOOL&&null!=b.type||b.deactivate();
+a.activate()}},addControls:function(a){OpenLayers.Util.isArray(a)||(a=[a]);this.controls=this.controls.concat(a);for(var b=0,c=a.length;b<c;b++){var d=a[b],e=this.createControlMarkup(d);OpenLayers.Element.addClass(e,d.displayClass+"ItemInactive");OpenLayers.Element.addClass(e,"olButton");""==d.title||e.title||(e.title=d.title);d.panel_div=e}this.map&&(this.addControlsToMap(a),this.redraw())},createControlMarkup:function(a){return document.createElement("div")},addControlsToMap:function(a){for(var b,
+c=0,d=a.length;c<d;c++)b=a[c],!0===b.autoActivate?(b.autoActivate=!1,this.map.addControl(b),b.autoActivate=!0):(this.map.addControl(b),b.deactivate()),b.events.on({activate:this.iconOn,deactivate:this.iconOff})},iconOn:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Inactive\\b"),"$1Active")},iconOff:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Active\\b"),"$1Inactive")},onButtonClick:function(a){var b=
+this.controls;a=a.buttonElement;for(var c=b.length-1;0<=c;--c)if(b[c].panel_div===a){this.activateControl(b[c]);break}},getControlsBy:function(a,b){var c="function"==typeof b.test;return OpenLayers.Array.filter(this.controls,function(d){return d[a]==b||c&&b.test(d[a])})},getControlsByName:function(a){return this.getControlsBy("name",a)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},CLASS_NAME:"OpenLayers.Control.Panel"});OpenLayers.Control.Button=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){},CLASS_NAME:"OpenLayers.Control.Button"});OpenLayers.Control.ZoomIn=OpenLayers.Class(OpenLayers.Control.Button,{trigger:function(){this.map&&this.map.zoomIn()},CLASS_NAME:"OpenLayers.Control.ZoomIn"});OpenLayers.Control.ZoomOut=OpenLayers.Class(OpenLayers.Control.Button,{trigger:function(){this.map&&this.map.zoomOut()},CLASS_NAME:"OpenLayers.Control.ZoomOut"});OpenLayers.Control.ZoomToMaxExtent=OpenLayers.Class(OpenLayers.Control.Button,{trigger:function(){this.map&&this.map.zoomToMaxExtent()},CLASS_NAME:"OpenLayers.Control.ZoomToMaxExtent"});OpenLayers.Control.ZoomPanel=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(a){OpenLayers.Control.Panel.prototype.initialize.apply(this,[a]);this.addControls([new OpenLayers.Control.ZoomIn,new OpenLayers.Control.ZoomToMaxExtent,new OpenLayers.Control.ZoomOut])},CLASS_NAME:"OpenLayers.Control.ZoomPanel"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:!1,initialize:function(a,b,c,d){OpenLayers.Layer.prototype.initialize.apply(this,[a,d]);this.url=b;this.params||(this.params=OpenLayers.Util.extend({},c))},destroy:function(){this.params=this.url=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.getOptions()));
+return a=OpenLayers.Layer.prototype.clone.apply(this,[a])},setUrl:function(a){this.url=a},mergeNewParams:function(a){this.params=OpenLayers.Util.extend(this.params,a);a=this.redraw();null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"params"});return a},redraw:function(a){return a?this.mergeNewParams({_olSalt:Math.random()}):OpenLayers.Layer.prototype.redraw.apply(this,[])},selectUrl:function(a,b){for(var c=1,d=0,e=a.length;d<e;d++)c*=a.charCodeAt(d)*this.URL_HASH_FACTOR,
+c-=Math.floor(c);return b[Math.floor(c*b.length)]},getFullRequestString:function(a,b){var c=b||this.url,d=OpenLayers.Util.extend({},this.params),d=OpenLayers.Util.extend(d,a),e=OpenLayers.Util.getParameterString(d);OpenLayers.Util.isArray(c)&&(c=this.selectUrl(e,c));var e=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),f;for(f in d)f.toUpperCase()in e&&delete d[f];e=OpenLayers.Util.getParameterString(d);return OpenLayers.Util.urlAppend(c,e)},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Tile=OpenLayers.Class({events:null,eventListeners:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:!1,initialize:function(a,b,c,d,e,f){this.layer=a;this.position=b.clone();this.setBounds(c);this.url=d;e&&(this.size=e.clone());this.id=OpenLayers.Util.createUniqueID("Tile_");OpenLayers.Util.extend(this,f);this.events=new OpenLayers.Events(this);if(this.eventListeners instanceof Object)this.events.on(this.eventListeners)},unload:function(){this.isLoading&&(this.isLoading=
+!1,this.events.triggerEvent("unload"))},destroy:function(){this.position=this.size=this.bounds=this.layer=null;this.eventListeners&&this.events.un(this.eventListeners);this.events.destroy();this.events=this.eventListeners=null},draw:function(a){a||this.clear();var b=this.shouldDraw();b&&(!a&&!1===this.events.triggerEvent("beforedraw"))&&(b=null);return b},shouldDraw:function(){var a=!1,b=this.layer.maxExtent;if(b){var c=this.layer.map,c=c.baseLayer.wrapDateLine&&c.getMaxExtent();this.bounds.intersectsBounds(b,
+{inclusive:!1,worldBounds:c})&&(a=!0)}return a||this.layer.displayOutsideMaxExtent},setBounds:function(a){a=a.clone();if(this.layer.map.baseLayer.wrapDateLine){var b=this.layer.map.getMaxExtent(),c=this.layer.map.getResolution();a=a.wrapDateLine(b,{leftTolerance:c,rightTolerance:c})}this.bounds=a},moveTo:function(a,b,c){null==c&&(c=!0);this.setBounds(a);this.position=b.clone();c&&this.draw()},clear:function(a){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,imageReloadAttempts:null,layerAlphaHack:null,asyncRequestId:null,maxGetUrlLength:null,canvasContext:null,crossOriginKeyword:null,initialize:function(a,b,c,d,e,f){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=d;this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();if(null!=this.maxGetUrlLength||this.layer.gutter||this.layerAlphaHack)this.frame=document.createElement("div"),this.frame.style.position=
+"absolute",this.frame.style.overflow="hidden";null!=this.maxGetUrlLength&&OpenLayers.Util.extend(this,OpenLayers.Tile.Image.IFrame)},destroy:function(){this.imgDiv&&(this.clear(),this.frame=this.imgDiv=null);this.asyncRequestId=null;OpenLayers.Tile.prototype.destroy.apply(this,arguments)},draw:function(){var a=OpenLayers.Tile.prototype.draw.apply(this,arguments);a?(this.layer!=this.layer.map.baseLayer&&this.layer.reproject&&(this.bounds=this.getBoundsFromBaseLayer(this.position)),this.isLoading?this._loadEvent=
+"reload":(this.isLoading=!0,this._loadEvent="loadstart"),this.renderTile(),this.positionTile()):!1===a&&this.unload();return a},renderTile:function(){if(this.layer.async){var a=this.asyncRequestId=(this.asyncRequestId||0)+1;this.layer.getURLasync(this.bounds,function(b){a==this.asyncRequestId&&(this.url=b,this.initImage())},this)}else this.url=this.layer.getURL(this.bounds),this.initImage()},positionTile:function(){var a=this.getTile().style,b=this.frame?this.size:this.layer.getImageSize(this.bounds),
+c=1;this.layer instanceof OpenLayers.Layer.Grid&&(c=this.layer.getServerResolution()/this.layer.map.getResolution());a.left=this.position.x+"px";a.top=this.position.y+"px";a.width=Math.round(c*b.w)+"px";a.height=Math.round(c*b.h)+"px"},clear:function(){OpenLayers.Tile.prototype.clear.apply(this,arguments);var a=this.imgDiv;if(a){var b=this.getTile();b.parentNode===this.layer.div&&this.layer.div.removeChild(b);this.setImgSrc();!0===this.layerAlphaHack&&(a.style.filter="");OpenLayers.Element.removeClass(a,
+"olImageLoadError")}this.canvasContext=null},getImage:function(){if(!this.imgDiv){this.imgDiv=OpenLayers.Tile.Image.IMAGE.cloneNode(!1);var a=this.imgDiv.style;if(this.frame){var b=0,c=0;this.layer.gutter&&(b=100*(this.layer.gutter/this.layer.tileSize.w),c=100*(this.layer.gutter/this.layer.tileSize.h));a.left=-b+"%";a.top=-c+"%";a.width=2*b+100+"%";a.height=2*c+100+"%"}a.visibility="hidden";a.opacity=0;1>this.layer.opacity&&(a.filter="alpha(opacity="+100*this.layer.opacity+")");a.position="absolute";
+this.layerAlphaHack&&(a.paddingTop=a.height,a.height="0",a.width="100%");this.frame&&this.frame.appendChild(this.imgDiv)}return this.imgDiv},setImage:function(a){this.imgDiv=a},initImage:function(){if(this.url||this.imgDiv){this.events.triggerEvent("beforeload");this.layer.div.appendChild(this.getTile());this.events.triggerEvent(this._loadEvent);var a=this.getImage(),b=a.getAttribute("src")||"";this.url&&OpenLayers.Util.isEquivalentUrl(b,this.url)?this._loadTimeout=window.setTimeout(OpenLayers.Function.bind(this.onImageLoad,
+this),0):(this.stopLoading(),this.crossOriginKeyword&&a.removeAttribute("crossorigin"),OpenLayers.Event.observe(a,"load",OpenLayers.Function.bind(this.onImageLoad,this)),OpenLayers.Event.observe(a,"error",OpenLayers.Function.bind(this.onImageError,this)),this.imageReloadAttempts=0,this.setImgSrc(this.url))}else this.isLoading=!1},setImgSrc:function(a){var b=this.imgDiv;a?(b.style.visibility="hidden",b.style.opacity=0,this.crossOriginKeyword&&("data:"!==a.substr(0,5)?b.setAttribute("crossorigin",this.crossOriginKeyword):
+b.removeAttribute("crossorigin")),b.src=a):(this.stopLoading(),this.imgDiv=null,b.parentNode&&b.parentNode.removeChild(b))},getTile:function(){return this.frame?this.frame:this.getImage()},createBackBuffer:function(){if(this.imgDiv&&!this.isLoading){var a;this.frame?(a=this.frame.cloneNode(!1),a.appendChild(this.imgDiv)):a=this.imgDiv;this.imgDiv=null;return a}},onImageLoad:function(){var a=this.imgDiv;this.stopLoading();a.style.visibility="inherit";a.style.opacity=this.layer.opacity;this.isLoading=
+!1;this.canvasContext=null;this.events.triggerEvent("loadend");!0===this.layerAlphaHack&&(a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+a.src+"', sizingMethod='scale')")},onImageError:function(){var a=this.imgDiv;null!=a.src&&(this.imageReloadAttempts++,this.imageReloadAttempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS?this.setImgSrc(this.layer.getURL(this.bounds)):(OpenLayers.Element.addClass(a,"olImageLoadError"),this.events.triggerEvent("loaderror"),this.onImageLoad()))},stopLoading:function(){OpenLayers.Event.stopObservingElement(this.imgDiv);
+window.clearTimeout(this._loadTimeout);delete this._loadTimeout},getCanvasContext:function(){if(OpenLayers.CANVAS_SUPPORTED&&this.imgDiv&&!this.isLoading){if(!this.canvasContext){var a=document.createElement("canvas");a.width=this.size.w;a.height=this.size.h;this.canvasContext=a.getContext("2d");this.canvasContext.drawImage(this.imgDiv,0,0)}return this.canvasContext}},CLASS_NAME:"OpenLayers.Tile.Image"});
+OpenLayers.Tile.Image.IMAGE=function(){var a=new Image;a.className="olTileImage";a.galleryImg="no";return a}();OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,tileOriginCorner:"bl",tileOrigin:null,tileOptions:null,tileClass:OpenLayers.Tile.Image,grid:null,singleTile:!1,ratio:1.5,buffer:0,transitionEffect:"resize",numLoadingTiles:0,serverResolutions:null,loading:!1,backBuffer:null,gridResolution:null,backBufferResolution:null,backBufferLonLat:null,backBufferTimerId:null,removeBackBufferDelay:null,className:null,gridLayout:null,rowSign:null,transitionendEvents:["transitionend",
+"webkitTransitionEnd","otransitionend","oTransitionEnd"],initialize:function(a,b,c,d){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.grid=[];this._removeBackBuffer=OpenLayers.Function.bind(this.removeBackBuffer,this);this.initProperties();this.rowSign="t"===this.tileOriginCorner.substr(0,1)?1:-1},initProperties:function(){void 0===this.options.removeBackBufferDelay&&(this.removeBackBufferDelay=this.singleTile?0:2500);void 0===this.options.className&&(this.className=this.singleTile?
+"olLayerGridSingleTile":"olLayerGrid")},setMap:function(a){OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this,a);OpenLayers.Element.addClass(this.div,this.className)},removeMap:function(a){this.removeBackBuffer()},destroy:function(){this.removeBackBuffer();this.clearGrid();this.tileSize=this.grid=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments)},clearGrid:function(){if(this.grid){for(var a=0,b=this.grid.length;a<b;a++)for(var c=this.grid[a],d=0,e=c.length;d<e;d++)this.destroyTile(c[d]);
+this.grid=[];this.gridLayout=this.gridResolution=null}},addOptions:function(a,b){var c=void 0!==a.singleTile&&a.singleTile!==this.singleTile;OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this,arguments);this.map&&c&&(this.initProperties(),this.clearGrid(),this.tileSize=this.options.tileSize,this.setTileSize(),this.moveTo(null,!0))},clone:function(a){null==a&&(a=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.getOptions()));a=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,
+[a]);null!=this.tileSize&&(a.tileSize=this.tileSize.clone());a.grid=[];a.gridResolution=null;a.backBuffer=null;a.backBufferTimerId=null;a.loading=!1;a.numLoadingTiles=0;return a},moveTo:function(a,b,c){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);a=a||this.map.getExtent();if(null!=a){var d=!this.grid.length||b,e=this.getTilesBounds(),f=this.map.getResolution();this.getServerResolution(f);if(this.singleTile){if(d||!c&&!e.containsBounds(a))b&&"resize"!==this.transitionEffect&&
+this.removeBackBuffer(),b&&"resize"!==this.transitionEffect||this.applyBackBuffer(f),this.initSingleTile(a)}else(d=d||!e.intersectsBounds(a,{worldBounds:this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent()}))?(!b||"resize"!==this.transitionEffect&&this.gridResolution!==f||this.applyBackBuffer(f),this.initGriddedTiles(a)):this.moveGriddedTiles()}},getTileData:function(a){var b=null,c=a.lon,d=a.lat,e=this.grid.length;if(this.map&&e){var f=this.map.getResolution();a=this.tileSize.w;var g=this.tileSize.h,
+h=this.grid[0][0].bounds,k=h.left,h=h.top;if(c<k&&this.map.baseLayer.wrapDateLine)var l=this.map.getMaxExtent().getWidth(),m=Math.ceil((k-c)/l),c=c+l*m;c=(c-k)/(f*a);d=(h-d)/(f*g);f=Math.floor(c);k=Math.floor(d);0<=k&&k<e&&(e=this.grid[k][f])&&(b={tile:e,i:Math.floor((c-f)*a),j:Math.floor((d-k)*g)})}return b},destroyTile:function(a){this.removeTileMonitoringHooks(a);a.destroy()},getServerResolution:function(a){var b=Number.POSITIVE_INFINITY;a=a||this.map.getResolution();if(this.serverResolutions&&
+-1===OpenLayers.Util.indexOf(this.serverResolutions,a)){var c,d,e,f;for(c=this.serverResolutions.length-1;0<=c;c--){e=this.serverResolutions[c];d=Math.abs(e-a);if(d>b)break;b=d;f=e}a=f}return a},getServerZoom:function(){var a=this.getServerResolution();return this.serverResolutions?OpenLayers.Util.indexOf(this.serverResolutions,a):this.map.getZoomForResolution(a)+(this.zoomOffset||0)},applyBackBuffer:function(a){null!==this.backBufferTimerId&&this.removeBackBuffer();var b=this.backBuffer;if(!b){b=
+this.createBackBuffer();if(!b)return;a===this.gridResolution?this.div.insertBefore(b,this.div.firstChild):this.map.baseLayer.div.parentNode.insertBefore(b,this.map.baseLayer.div);this.backBuffer=b;var c=this.grid[0][0].bounds;this.backBufferLonLat={lon:c.left,lat:c.top};this.backBufferResolution=this.gridResolution}for(var c=this.backBufferResolution/a,d=b.childNodes,e,f=d.length-1;0<=f;--f)e=d[f],e.style.top=(c*e._i*e._h|0)+"px",e.style.left=(c*e._j*e._w|0)+"px",e.style.width=Math.round(c*e._w)+
+"px",e.style.height=Math.round(c*e._h)+"px";a=this.getViewPortPxFromLonLat(this.backBufferLonLat,a);c=this.map.layerContainerOriginPx.y;b.style.left=Math.round(a.x-this.map.layerContainerOriginPx.x)+"px";b.style.top=Math.round(a.y-c)+"px"},createBackBuffer:function(){var a;if(0<this.grid.length){a=document.createElement("div");a.id=this.div.id+"_bb";a.className="olBackBuffer";a.style.position="absolute";var b=this.map;a.style.zIndex="resize"===this.transitionEffect?this.getZIndex()-1:b.Z_INDEX_BASE.BaseLayer-
+(b.getNumLayers()-b.getLayerIndex(this));for(var b=0,c=this.grid.length;b<c;b++)for(var d=0,e=this.grid[b].length;d<e;d++){var f=this.grid[b][d],g=this.grid[b][d].createBackBuffer();g&&(g._i=b,g._j=d,g._w=f.size.w,g._h=f.size.h,g.id=f.id+"_bb",a.appendChild(g))}}return a},removeBackBuffer:function(){if(this._transitionElement){for(var a=this.transitionendEvents.length-1;0<=a;--a)OpenLayers.Event.stopObserving(this._transitionElement,this.transitionendEvents[a],this._removeBackBuffer);delete this._transitionElement}this.backBuffer&&
+(this.backBuffer.parentNode&&this.backBuffer.parentNode.removeChild(this.backBuffer),this.backBufferResolution=this.backBuffer=null,null!==this.backBufferTimerId&&(window.clearTimeout(this.backBufferTimerId),this.backBufferTimerId=null))},moveByPx:function(a,b){this.singleTile||this.moveGriddedTiles()},setTileSize:function(a){this.singleTile&&(a=this.map.getSize(),a.h=parseInt(a.h*this.ratio,10),a.w=parseInt(a.w*this.ratio,10));OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this,[a])},getTilesBounds:function(){var a=
+null,b=this.grid.length;if(b)var a=this.grid[b-1][0].bounds,b=this.grid[0].length*a.getWidth(),c=this.grid.length*a.getHeight(),a=new OpenLayers.Bounds(a.left,a.bottom,a.left+b,a.bottom+c);return a},initSingleTile:function(a){this.events.triggerEvent("retile");var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;b=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2);c=this.map.getLayerPxFromLonLat({lon:b.left,lat:b.top});this.grid.length||(this.grid[0]=[]);(a=this.grid[0][0])?
+a.moveTo(b,c):(a=this.addTile(b,c),this.addTileMonitoringHooks(a),a.draw(),this.grid[0][0]=a);this.removeExcessTiles(1,1);this.gridResolution=this.getServerResolution()},calculateGridLayout:function(a,b,c){var d=c*this.tileSize.w;c*=this.tileSize.h;var e=Math.floor((a.left-b.lon)/d)-this.buffer,f=this.rowSign;a=Math[~f?"floor":"ceil"](f*(b.lat-a.top+c)/c)-this.buffer*f;return{tilelon:d,tilelat:c,startcol:e,startrow:a}},getTileOrigin:function(){var a=this.tileOrigin;if(!a)var a=this.getMaxExtent(),
+b={tl:["left","top"],tr:["right","top"],bl:["left","bottom"],br:["right","bottom"]}[this.tileOriginCorner],a=new OpenLayers.LonLat(a[b[0]],a[b[1]]);return a},getTileBoundsForGridIndex:function(a,b){var c=this.getTileOrigin(),d=this.gridLayout,e=d.tilelon,f=d.tilelat,g=d.startcol,d=d.startrow,h=this.rowSign;return new OpenLayers.Bounds(c.lon+(g+b)*e,c.lat-(d+a*h)*f*h,c.lon+(g+b+1)*e,c.lat-(d+(a-1)*h)*f*h)},initGriddedTiles:function(a){this.events.triggerEvent("retile");var b=this.map.getSize(),c=this.getTileOrigin(),
+d=this.map.getResolution(),e=this.getServerResolution(),f=d/e,d=this.tileSize.w/f,f=this.tileSize.h/f,g=Math.ceil(b.h/f)+2*this.buffer+1,b=Math.ceil(b.w/d)+2*this.buffer+1;this.gridLayout=e=this.calculateGridLayout(a,c,e);var c=e.tilelon,h=e.tilelat,e=this.map.layerContainerOriginPx.x,k=this.map.layerContainerOriginPx.y,l=this.getTileBoundsForGridIndex(0,0),m=this.map.getViewPortPxFromLonLat(new OpenLayers.LonLat(l.left,l.top));m.x=Math.round(m.x)-e;m.y=Math.round(m.y)-k;var e=[],k=this.map.getCenter(),
+n=0;do{var p=this.grid[n];p||(p=[],this.grid.push(p));var q=0;do{var l=this.getTileBoundsForGridIndex(n,q),r=m.clone();r.x+=q*Math.round(d);r.y+=n*Math.round(f);var s=p[q];s?s.moveTo(l,r,!1):(s=this.addTile(l,r),this.addTileMonitoringHooks(s),p.push(s));r=l.getCenterLonLat();e.push({tile:s,distance:Math.pow(r.lon-k.lon,2)+Math.pow(r.lat-k.lat,2)});q+=1}while(l.right<=a.right+c*this.buffer||q<b);n+=1}while(l.bottom>=a.bottom-h*this.buffer||n<g);this.removeExcessTiles(n,q);this.gridResolution=d=this.getServerResolution();
+e.sort(function(a,b){return a.distance-b.distance});a=0;for(d=e.length;a<d;++a)e[a].tile.draw()},getMaxExtent:function(){return this.maxExtent},addTile:function(a,b){var c=new this.tileClass(this,b,a,null,this.tileSize,this.tileOptions);this.events.triggerEvent("addtile",{tile:c});return c},addTileMonitoringHooks:function(a){a.onLoadStart=function(){!1===this.loading&&(this.loading=!0,this.events.triggerEvent("loadstart"));this.events.triggerEvent("tileloadstart",{tile:a});this.numLoadingTiles++;
+!this.singleTile&&(this.backBuffer&&this.gridResolution===this.backBufferResolution)&&OpenLayers.Element.addClass(a.getTile(),"olTileReplacing")};a.onLoadEnd=function(b){this.numLoadingTiles--;b="unload"===b.type;this.events.triggerEvent("tileloaded",{tile:a,aborted:b});if(!this.singleTile&&!b&&this.backBuffer&&this.gridResolution===this.backBufferResolution){var c=a.getTile();if("none"===OpenLayers.Element.getStyle(c,"display")){var d=document.getElementById(a.id+"_bb");d&&d.parentNode.removeChild(d)}OpenLayers.Element.removeClass(c,
+"olTileReplacing")}if(0===this.numLoadingTiles){if(this.backBuffer)if(0===this.backBuffer.childNodes.length)this.removeBackBuffer();else{this._transitionElement=b?this.div.lastChild:a.imgDiv;b=this.transitionendEvents;for(c=b.length-1;0<=c;--c)OpenLayers.Event.observe(this._transitionElement,b[c],this._removeBackBuffer);this.backBufferTimerId=window.setTimeout(this._removeBackBuffer,this.removeBackBufferDelay)}this.loading=!1;this.events.triggerEvent("loadend")}};a.onLoadError=function(){this.events.triggerEvent("tileerror",
+{tile:a})};a.events.on({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},removeTileMonitoringHooks:function(a){a.unload();a.events.un({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},moveGriddedTiles:function(){for(var a=this.buffer+1;;){var b=this.grid[0][0],c=b.position.x+this.map.layerContainerOriginPx.x,b=b.position.y+this.map.layerContainerOriginPx.y,d=this.getServerResolution()/this.map.getResolution(),
+d={w:Math.round(this.tileSize.w*d),h:Math.round(this.tileSize.h*d)};if(c>-d.w*(a-1))this.shiftColumn(!0,d);else if(c<-d.w*a)this.shiftColumn(!1,d);else if(b>-d.h*(a-1))this.shiftRow(!0,d);else if(b<-d.h*a)this.shiftRow(!1,d);else break}},shiftRow:function(a,b){var c=this.grid,d=a?0:c.length-1,e=a?-1:1;this.gridLayout.startrow+=e*this.rowSign;for(var f=c[d],g=c[a?"pop":"shift"](),h=0,k=g.length;h<k;h++){var l=g[h],m=f[h].position.clone();m.y+=b.h*e;l.moveTo(this.getTileBoundsForGridIndex(d,h),m)}c[a?
+"unshift":"push"](g)},shiftColumn:function(a,b){var c=this.grid,d=a?0:c[0].length-1,e=a?-1:1;this.gridLayout.startcol+=e;for(var f=0,g=c.length;f<g;f++){var h=c[f],k=h[d].position.clone(),l=h[a?"pop":"shift"]();k.x+=b.w*e;l.moveTo(this.getTileBoundsForGridIndex(f,d),k);h[a?"unshift":"push"](l)}},removeExcessTiles:function(a,b){for(var c,d;this.grid.length>a;){var e=this.grid.pop();c=0;for(d=e.length;c<d;c++){var f=e[c];this.destroyTile(f)}}c=0;for(d=this.grid.length;c<d;c++)for(;this.grid[c].length>
+b;)e=this.grid[c],f=e.pop(),this.destroyTile(f)},onMapResize:function(){this.singleTile&&(this.clearGrid(),this.setTileSize())},getTileBounds:function(a){var b=this.maxExtent,c=this.getResolution(),d=c*this.tileSize.w,c=c*this.tileSize.h,e=this.getLonLatFromViewPortPx(a);a=b.left+d*Math.floor((e.lon-b.left)/d);b=b.bottom+c*Math.floor((e.lat-b.bottom)/c);return new OpenLayers.Bounds(a,b,a+d,b+c)},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Format.ArcXML=OpenLayers.Class(OpenLayers.Format.XML,{fontStyleKeys:"antialiasing blockout font fontcolor fontsize fontstyle glowing interval outline printmode shadow transparency".split(" "),request:null,response:null,initialize:function(a){this.request=new OpenLayers.Format.ArcXML.Request;this.response=new OpenLayers.Format.ArcXML.Response;if(a)if("feature"==a.requesttype){this.request.get_image=null;var b=this.request.get_feature.query;this.addCoordSys(b.featurecoordsys,a.featureCoordSys);
+this.addCoordSys(b.filtercoordsys,a.filterCoordSys);a.polygon?(b.isspatial=!0,b.spatialfilter.polygon=a.polygon):a.envelope&&(b.isspatial=!0,b.spatialfilter.envelope={minx:0,miny:0,maxx:0,maxy:0},this.parseEnvelope(b.spatialfilter.envelope,a.envelope))}else"image"==a.requesttype?(this.request.get_feature=null,b=this.request.get_image.properties,this.parseEnvelope(b.envelope,a.envelope),this.addLayers(b.layerlist,a.layers),this.addImageSize(b.imagesize,a.tileSize),this.addCoordSys(b.featurecoordsys,
+a.featureCoordSys),this.addCoordSys(b.filtercoordsys,a.filterCoordSys)):this.request=null;OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},parseEnvelope:function(a,b){b&&4==b.length&&(a.minx=b[0],a.miny=b[1],a.maxx=b[2],a.maxy=b[3])},addLayers:function(a,b){for(var c=0,d=b.length;c<d;c++)a.push(b[c])},addImageSize:function(a,b){null!==b&&(a.width=b.w,a.height=b.h,a.printwidth=b.w,a.printheight=b.h)},addCoordSys:function(a,b){"string"==typeof b?(a.id=parseInt(b),a.string=b):"object"==typeof b&&
+null!==b.proj&&(a.id=b.proj.srsProjNumber,a.string=b.proj.srsCode)},iserror:function(a){var b=null;a?(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]),a=a.documentElement.getElementsByTagName("ERROR"),b=null!==a&&0<a.length):b=""!==this.response.error;return b},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=null;a&&a.documentElement&&(b="ARCXML"==a.documentElement.nodeName?a.documentElement:a.documentElement.getElementsByTagName("ARCXML")[0]);
+if(!b||"parsererror"===b.firstChild.nodeName){var c,d;try{c=a.firstChild.nodeValue,d=a.firstChild.childNodes[1].firstChild.nodeValue}catch(e){}throw{message:"Error parsing the ArcXML request",error:c,source:d};}return this.parseResponse(b)},write:function(a){a||(a=this.request);var b=this.createElementNS("","ARCXML");b.setAttribute("version","1.1");var c=this.createElementNS("","REQUEST");if(null!=a.get_image){var d=this.createElementNS("","GET_IMAGE");c.appendChild(d);var e=this.createElementNS("",
+"PROPERTIES");d.appendChild(e);a=a.get_image.properties;null!=a.featurecoordsys&&(d=this.createElementNS("","FEATURECOORDSYS"),e.appendChild(d),0===a.featurecoordsys.id?d.setAttribute("string",a.featurecoordsys.string):d.setAttribute("id",a.featurecoordsys.id));null!=a.filtercoordsys&&(d=this.createElementNS("","FILTERCOORDSYS"),e.appendChild(d),0===a.filtercoordsys.id?d.setAttribute("string",a.filtercoordsys.string):d.setAttribute("id",a.filtercoordsys.id));null!=a.envelope&&(d=this.createElementNS("",
+"ENVELOPE"),e.appendChild(d),d.setAttribute("minx",a.envelope.minx),d.setAttribute("miny",a.envelope.miny),d.setAttribute("maxx",a.envelope.maxx),d.setAttribute("maxy",a.envelope.maxy));d=this.createElementNS("","IMAGESIZE");e.appendChild(d);d.setAttribute("height",a.imagesize.height);d.setAttribute("width",a.imagesize.width);if(a.imagesize.height!=a.imagesize.printheight||a.imagesize.width!=a.imagesize.printwidth)d.setAttribute("printheight",a.imagesize.printheight),d.setArrtibute("printwidth",a.imagesize.printwidth);
+null!=a.background&&(d=this.createElementNS("","BACKGROUND"),e.appendChild(d),d.setAttribute("color",a.background.color.r+","+a.background.color.g+","+a.background.color.b),null!==a.background.transcolor&&d.setAttribute("transcolor",a.background.transcolor.r+","+a.background.transcolor.g+","+a.background.transcolor.b));if(null!=a.layerlist&&0<a.layerlist.length)for(d=this.createElementNS("","LAYERLIST"),e.appendChild(d),e=0;e<a.layerlist.length;e++){var f=this.createElementNS("","LAYERDEF");d.appendChild(f);
+f.setAttribute("id",a.layerlist[e].id);f.setAttribute("visible",a.layerlist[e].visible);if("object"==typeof a.layerlist[e].query){var g=a.layerlist[e].query;if(0>g.where.length)continue;var h=null,h="boolean"==typeof g.spatialfilter&&g.spatialfilter?this.createElementNS("","SPATIALQUERY"):this.createElementNS("","QUERY");h.setAttribute("where",g.where);"number"==typeof g.accuracy&&0<g.accuracy&&h.setAttribute("accuracy",g.accuracy);"number"==typeof g.featurelimit&&2E3>g.featurelimit&&h.setAttribute("featurelimit",
+g.featurelimit);"string"==typeof g.subfields&&"#ALL#"!=g.subfields&&h.setAttribute("subfields",g.subfields);"string"==typeof g.joinexpression&&0<g.joinexpression.length&&h.setAttribute("joinexpression",g.joinexpression);"string"==typeof g.jointables&&0<g.jointables.length&&h.setAttribute("jointables",g.jointables);f.appendChild(h)}"object"==typeof a.layerlist[e].renderer&&this.addRenderer(f,a.layerlist[e].renderer)}}else null!=a.get_feature&&(d=this.createElementNS("","GET_FEATURES"),d.setAttribute("outputmode",
+"newxml"),d.setAttribute("checkesc","true"),a.get_feature.geometry?d.setAttribute("geometry",a.get_feature.geometry):d.setAttribute("geometry","false"),a.get_feature.compact&&d.setAttribute("compact",a.get_feature.compact),"number"==a.get_feature.featurelimit&&d.setAttribute("featurelimit",a.get_feature.featurelimit),d.setAttribute("globalenvelope","true"),c.appendChild(d),null!=a.get_feature.layer&&0<a.get_feature.layer.length&&(e=this.createElementNS("","LAYER"),e.setAttribute("id",a.get_feature.layer),
+d.appendChild(e)),a=a.get_feature.query,null!=a&&(e=null,e=a.isspatial?this.createElementNS("","SPATIALQUERY"):this.createElementNS("","QUERY"),d.appendChild(e),"number"==typeof a.accuracy&&e.setAttribute("accuracy",a.accuracy),null!=a.featurecoordsys&&(d=this.createElementNS("","FEATURECOORDSYS"),0==a.featurecoordsys.id?d.setAttribute("string",a.featurecoordsys.string):d.setAttribute("id",a.featurecoordsys.id),e.appendChild(d)),null!=a.filtercoordsys&&(d=this.createElementNS("","FILTERCOORDSYS"),
+0===a.filtercoordsys.id?d.setAttribute("string",a.filtercoordsys.string):d.setAttribute("id",a.filtercoordsys.id),e.appendChild(d)),0<a.buffer&&(d=this.createElementNS("","BUFFER"),d.setAttribute("distance",a.buffer),e.appendChild(d)),a.isspatial&&(d=this.createElementNS("","SPATIALFILTER"),d.setAttribute("relation",a.spatialfilter.relation),e.appendChild(d),a.spatialfilter.envelope?(f=this.createElementNS("","ENVELOPE"),f.setAttribute("minx",a.spatialfilter.envelope.minx),f.setAttribute("miny",a.spatialfilter.envelope.miny),
+f.setAttribute("maxx",a.spatialfilter.envelope.maxx),f.setAttribute("maxy",a.spatialfilter.envelope.maxy),d.appendChild(f)):"object"==typeof a.spatialfilter.polygon&&d.appendChild(this.writePolygonGeometry(a.spatialfilter.polygon))),null!=a.where&&0<a.where.length&&e.setAttribute("where",a.where)));b.appendChild(c);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},addGroupRenderer:function(a,b){var c=this.createElementNS("","GROUPRENDERER");a.appendChild(c);for(var d=0;d<b.length;d++)this.addRenderer(c,
+b[d])},addRenderer:function(a,b){if(OpenLayers.Util.isArray(b))this.addGroupRenderer(a,b);else{var c=this.createElementNS("",b.type.toUpperCase()+"RENDERER");a.appendChild(c);"VALUEMAPRENDERER"==c.tagName?this.addValueMapRenderer(c,b):"VALUEMAPLABELRENDERER"==c.tagName?this.addValueMapLabelRenderer(c,b):"SIMPLELABELRENDERER"==c.tagName?this.addSimpleLabelRenderer(c,b):"SCALEDEPENDENTRENDERER"==c.tagName&&this.addScaleDependentRenderer(c,b)}},addScaleDependentRenderer:function(a,b){"string"!=typeof b.lower&&
+"number"!=typeof b.lower||a.setAttribute("lower",b.lower);"string"!=typeof b.upper&&"number"!=typeof b.upper||a.setAttribute("upper",b.upper);this.addRenderer(a,b.renderer)},addValueMapLabelRenderer:function(a,b){a.setAttribute("lookupfield",b.lookupfield);a.setAttribute("labelfield",b.labelfield);if("object"==typeof b.exacts)for(var c=0,d=b.exacts.length;c<d;c++){var e=b.exacts[c],f=this.createElementNS("","EXACT");"string"==typeof e.value&&f.setAttribute("value",e.value);"string"==typeof e.label&&
+f.setAttribute("label",e.label);"string"==typeof e.method&&f.setAttribute("method",e.method);a.appendChild(f);if("object"==typeof e.symbol){var g=null;"text"==e.symbol.type&&(g=this.createElementNS("","TEXTSYMBOL"));if(null!=g){for(var h=this.fontStyleKeys,k=0,l=h.length;k<l;k++){var m=h[k];e.symbol[m]&&g.setAttribute(m,e.symbol[m])}f.appendChild(g)}}}},addValueMapRenderer:function(a,b){a.setAttribute("lookupfield",b.lookupfield);if("object"==typeof b.ranges)for(var c=0,d=b.ranges.length;c<d;c++){var e=
+b.ranges[c],f=this.createElementNS("","RANGE");f.setAttribute("lower",e.lower);f.setAttribute("upper",e.upper);a.appendChild(f);if("object"==typeof e.symbol){var g=null;"simplepolygon"==e.symbol.type&&(g=this.createElementNS("","SIMPLEPOLYGONSYMBOL"));null!=g&&("string"==typeof e.symbol.boundarycolor&&g.setAttribute("boundarycolor",e.symbol.boundarycolor),"string"==typeof e.symbol.fillcolor&&g.setAttribute("fillcolor",e.symbol.fillcolor),"number"==typeof e.symbol.filltransparency&&g.setAttribute("filltransparency",
+e.symbol.filltransparency),f.appendChild(g))}}else if("object"==typeof b.exacts)for(c=0,d=b.exacts.length;c<d;c++)e=b.exacts[c],f=this.createElementNS("","EXACT"),"string"==typeof e.value&&f.setAttribute("value",e.value),"string"==typeof e.label&&f.setAttribute("label",e.label),"string"==typeof e.method&&f.setAttribute("method",e.method),a.appendChild(f),"object"==typeof e.symbol&&(g=null,"simplemarker"==e.symbol.type&&(g=this.createElementNS("","SIMPLEMARKERSYMBOL")),null!=g&&("string"==typeof e.symbol.antialiasing&&
+g.setAttribute("antialiasing",e.symbol.antialiasing),"string"==typeof e.symbol.color&&g.setAttribute("color",e.symbol.color),"string"==typeof e.symbol.outline&&g.setAttribute("outline",e.symbol.outline),"string"==typeof e.symbol.overlap&&g.setAttribute("overlap",e.symbol.overlap),"string"==typeof e.symbol.shadow&&g.setAttribute("shadow",e.symbol.shadow),"number"==typeof e.symbol.transparency&&g.setAttribute("transparency",e.symbol.transparency),"string"==typeof e.symbol.usecentroid&&g.setAttribute("usecentroid",
+e.symbol.usecentroid),"number"==typeof e.symbol.width&&g.setAttribute("width",e.symbol.width),f.appendChild(g)))},addSimpleLabelRenderer:function(a,b){a.setAttribute("field",b.field);for(var c="featureweight howmanylabels labelbufferratio labelpriorities labelweight linelabelposition rotationalangles".split(" "),d=0,e=c.length;d<e;d++){var f=c[d];b[f]&&a.setAttribute(f,b[f])}if("text"==b.symbol.type){var g=b.symbol,h=this.createElementNS("","TEXTSYMBOL");a.appendChild(h);c=this.fontStyleKeys;d=0;
+for(e=c.length;d<e;d++)f=c[d],g[f]&&h.setAttribute(f,b[f])}},writePolygonGeometry:function(a){if(!(a instanceof OpenLayers.Geometry.Polygon))throw{message:"Cannot write polygon geometry to ArcXML with an "+a.CLASS_NAME+" object.",geometry:a};for(var b=this.createElementNS("","POLYGON"),c=0,d=a.components.length;c<d;c++){for(var e=a.components[c],f=this.createElementNS("","RING"),g=0,h=e.components.length;g<h;g++){var k=e.components[g],l=this.createElementNS("","POINT");l.setAttribute("x",k.x);l.setAttribute("y",
+k.y);f.appendChild(l)}b.appendChild(f)}return b},parseResponse:function(a){"string"==typeof a&&(a=(new OpenLayers.Format.XML).read(a));var b=new OpenLayers.Format.ArcXML.Response,c=a.getElementsByTagName("ERROR");if(null!=c&&0<c.length)b.error=this.getChildValue(c,"Unknown error.");else{c=a.getElementsByTagName("RESPONSE");if(null==c||0==c.length)return b.error="No RESPONSE tag found in ArcXML response.",b;var d=c[0].firstChild.nodeName;"#text"==d&&(d=c[0].firstChild.nextSibling.nodeName);if("IMAGE"==
+d)c=a.getElementsByTagName("ENVELOPE"),a=a.getElementsByTagName("OUTPUT"),null==c||0==c.length?b.error="No ENVELOPE tag found in ArcXML response.":null==a||0==a.length?b.error="No OUTPUT tag found in ArcXML response.":(c=this.parseAttributes(c[0]),d=this.parseAttributes(a[0]),b.image="string"==typeof d.type?{envelope:c,output:{type:d.type,data:this.getChildValue(a[0])}}:{envelope:c,output:d});else if("FEATURES"==d){if(a=c[0].getElementsByTagName("FEATURES"),c=a[0].getElementsByTagName("FEATURECOUNT"),
+b.features.featurecount=c[0].getAttribute("count"),0<b.features.featurecount)for(c=a[0].getElementsByTagName("ENVELOPE"),b.features.envelope=this.parseAttributes(c[0],"number"),a=a[0].getElementsByTagName("FEATURE"),c=0;c<a.length;c++){for(var d=new OpenLayers.Feature.Vector,e=a[c].getElementsByTagName("FIELD"),f=0;f<e.length;f++){var g=e[f].getAttribute("name"),h=e[f].getAttribute("value");d.attributes[g]=h}e=a[c].getElementsByTagName("POLYGON");if(0<e.length){e=e[0].getElementsByTagName("RING");
+f=[];for(g=0;g<e.length;g++){h=[];h.push(this.parsePointGeometry(e[g]));for(var k=e[g].getElementsByTagName("HOLE"),l=0;l<k.length;l++)h.push(this.parsePointGeometry(k[l]));f.push(new OpenLayers.Geometry.Polygon(h))}d.geometry=1==f.length?f[0]:new OpenLayers.Geometry.MultiPolygon(f)}b.features.feature.push(d)}}else b.error="Unidentified response type."}return b},parseAttributes:function(a,b){for(var c={},d=0;d<a.attributes.length;d++)c[a.attributes[d].nodeName]="number"==b?parseFloat(a.attributes[d].nodeValue):
+a.attributes[d].nodeValue;return c},parsePointGeometry:function(a){var b=[],c=a.getElementsByTagName("COORDS");if(0<c.length)for(a=this.getChildValue(c[0]),a=a.split(/;/),c=0;c<a.length;c++){var d=a[c].split(/ /);b.push(new OpenLayers.Geometry.Point(d[0],d[1]))}else if(a=a.getElementsByTagName("POINT"),0<a.length)for(c=0;c<a.length;c++)b.push(new OpenLayers.Geometry.Point(parseFloat(a[c].getAttribute("x")),parseFloat(a[c].getAttribute("y"))));return new OpenLayers.Geometry.LinearRing(b)},CLASS_NAME:"OpenLayers.Format.ArcXML"});
+OpenLayers.Format.ArcXML.Request=OpenLayers.Class({initialize:function(a){return OpenLayers.Util.extend(this,{get_image:{properties:{background:null,draw:!0,envelope:{minx:0,miny:0,maxx:0,maxy:0},featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},imagesize:{height:0,width:0,dpi:96,printheight:0,printwidth:0,scalesymbols:!1},layerlist:[],output:{baseurl:"",legendbaseurl:"",legendname:"",legendpath:"",
+legendurl:"",name:"",path:"",type:"jpg",url:""}}},get_feature:{layer:"",query:{isspatial:!1,featurecoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},filtercoordsys:{id:0,string:"",datumtransformid:0,datumtransformstring:""},buffer:0,where:"",spatialfilter:{relation:"envelope_intersection",envelope:null}}},environment:{separators:{cs:" ",ts:";"}},layer:[],workspaces:[]})},CLASS_NAME:"OpenLayers.Format.ArcXML.Request"});
+OpenLayers.Format.ArcXML.Response=OpenLayers.Class({initialize:function(a){return OpenLayers.Util.extend(this,{image:{envelope:null,output:""},features:{featurecount:0,envelope:null,feature:[]},error:""})},CLASS_NAME:"OpenLayers.Format.ArcXML.Response"});(function(){function a(){this._object=f&&!k?new f:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}function b(){return new a}function c(a){b.onreadystatechange&&b.onreadystatechange.apply(a);a.dispatchEvent({type:"readystatechange",bubbles:!1,cancelable:!1,timeStamp:new Date+0})}function d(a){try{a.responseText=a._object.responseText}catch(b){}try{var c;var d=a._object,e=d.responseXML,f=d.responseText;h&&(f&&e&&!e.documentElement&&d.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/))&&
+(e=new window.ActiveXObject("Microsoft.XMLDOM"),e.async=!1,e.validateOnParse=!1,e.loadXML(f));c=e&&(h&&0!=e.parseError||!e.documentElement||e.documentElement&&"parsererror"==e.documentElement.tagName)?null:e;a.responseXML=c}catch(g){}try{a.status=a._object.status}catch(k){}try{a.statusText=a._object.statusText}catch(u){}}function e(a){a._object.onreadystatechange=new window.Function}var f=window.XMLHttpRequest,g=!!window.controllers,h=window.document.all&&!window.opera,k=h&&window.navigator.userAgent.match(/MSIE 7.0/);
+b.prototype=a.prototype;g&&f.wrapped&&(b.wrapped=f.wrapped);b.UNSENT=0;b.OPENED=1;b.HEADERS_RECEIVED=2;b.LOADING=3;b.DONE=4;b.prototype.readyState=b.UNSENT;b.prototype.responseText="";b.prototype.responseXML=null;b.prototype.status=0;b.prototype.statusText="";b.prototype.priority="NORMAL";b.prototype.onreadystatechange=null;b.onreadystatechange=null;b.onopen=null;b.onsend=null;b.onabort=null;b.prototype.open=function(a,f,k,p,q){delete this._headers;3>arguments.length&&(k=!0);this._async=k;var r=this,
+s=this.readyState,t;h&&k&&(t=function(){s!=b.DONE&&(e(r),r.abort())},window.attachEvent("onunload",t));b.onopen&&b.onopen.apply(this,arguments);4<arguments.length?this._object.open(a,f,k,p,q):3<arguments.length?this._object.open(a,f,k,p):this._object.open(a,f,k);this.readyState=b.OPENED;c(this);this._object.onreadystatechange=function(){if(!g||k)r.readyState=r._object.readyState,d(r),r._aborted?r.readyState=b.UNSENT:(r.readyState==b.DONE&&(delete r._data,e(r),h&&k&&window.detachEvent("onunload",t)),
+s!=r.readyState&&c(r),s=r.readyState)}};b.prototype.send=function(a){b.onsend&&b.onsend.apply(this,arguments);arguments.length||(a=null);a&&a.nodeType&&(a=window.XMLSerializer?(new window.XMLSerializer).serializeToString(a):a.xml,this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml"));this._data=a;a:if(this._object.send(this._data),g&&!this._async)for(this.readyState=b.OPENED,d(this);this.readyState<b.DONE;)if(this.readyState++,c(this),this._aborted)break a};
+b.prototype.abort=function(){b.onabort&&b.onabort.apply(this,arguments);this.readyState>b.UNSENT&&(this._aborted=!0);this._object.abort();e(this);this.readyState=b.UNSENT;delete this._data};b.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};b.prototype.getResponseHeader=function(a){return this._object.getResponseHeader(a)};b.prototype.setRequestHeader=function(a,b){this._headers||(this._headers={});this._headers[a]=b;return this._object.setRequestHeader(a,b)};
+b.prototype.addEventListener=function(a,b,c){for(var d=0,e;e=this._listeners[d];d++)if(e[0]==a&&e[1]==b&&e[2]==c)return;this._listeners.push([a,b,c])};b.prototype.removeEventListener=function(a,b,c){for(var d=0,e;(e=this._listeners[d])&&(e[0]!=a||e[1]!=b||e[2]!=c);d++);e&&this._listeners.splice(d,1)};b.prototype.dispatchEvent=function(a){a={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){},preventDefault:function(){},
+initEvent:function(){}};"readystatechange"==a.type&&this.onreadystatechange&&(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[a]);for(var b=0,c;c=this._listeners[b];b++)c[0]!=a.type||c[2]||(c[1].handleEvent||c[1]).apply(this,[a])};b.prototype.toString=function(){return"[object XMLHttpRequest]"};b.toString=function(){return"[XMLHttpRequest]"};window.Function.prototype.apply||(window.Function.prototype.apply=function(a,b){b||(b=[]);a.__func=this;a.__func(b[0],b[1],b[2],b[3],
+b[4]);delete a.__func});OpenLayers.Request||(OpenLayers.Request={});OpenLayers.Request.XMLHttpRequest=b})();OpenLayers.ProxyHost="";OpenLayers.Request||(OpenLayers.Request={});
+OpenLayers.Util.extend(OpenLayers.Request,{DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:!0,user:void 0,password:void 0,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},URL_SPLIT_REGEX:/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,events:new OpenLayers.Events(this),makeSameOrigin:function(a,b){var c=0!==a.indexOf("http"),d=!c&&a.match(this.URL_SPLIT_REGEX);if(d){var e=window.location,c=d[1]==e.protocol&&d[3]==
+e.hostname,d=d[4],e=e.port;if(80!=d&&""!=d||"80"!=e&&""!=e)c=c&&d==e}c||b&&(a="function"==typeof b?b(a):b+encodeURIComponent(a));return a},issue:function(a){var b=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});a=a||{};a.headers=a.headers||{};a=OpenLayers.Util.applyDefaults(a,b);a.headers=OpenLayers.Util.applyDefaults(a.headers,b.headers);var b=!1,c;for(c in a.headers)a.headers.hasOwnProperty(c)&&"x-requested-with"===c.toLowerCase()&&(b=!0);!1===b&&(a.headers["X-Requested-With"]=
+"XMLHttpRequest");var d=new OpenLayers.Request.XMLHttpRequest,e=OpenLayers.Util.urlAppend(a.url,OpenLayers.Util.getParameterString(a.params||{})),e=OpenLayers.Request.makeSameOrigin(e,a.proxy);d.open(a.method,e,a.async,a.user,a.password);for(var f in a.headers)d.setRequestHeader(f,a.headers[f]);var g=this.events,h=this;d.onreadystatechange=function(){d.readyState==OpenLayers.Request.XMLHttpRequest.DONE&&!1!==g.triggerEvent("complete",{request:d,config:a,requestUrl:e})&&h.runCallbacks({request:d,config:a,
+requestUrl:e})};!1===a.async?d.send(a.data):window.setTimeout(function(){0!==d.readyState&&d.send(a.data)},0);return d},runCallbacks:function(a){var b=a.request,c=a.config,d=c.scope?OpenLayers.Function.bind(c.callback,c.scope):c.callback,e;c.success&&(e=c.scope?OpenLayers.Function.bind(c.success,c.scope):c.success);var f;c.failure&&(f=c.scope?OpenLayers.Function.bind(c.failure,c.scope):c.failure);"file:"==OpenLayers.Util.createUrlObject(c.url).protocol&&b.responseText&&(b.status=200);d(b);if(!b.status||
+200<=b.status&&300>b.status)this.events.triggerEvent("success",a),e&&e(b);b.status&&(200>b.status||300<=b.status)&&(this.events.triggerEvent("failure",a),f&&f(b))},GET:function(a){a=OpenLayers.Util.extend(a,{method:"GET"});return OpenLayers.Request.issue(a)},POST:function(a){a=OpenLayers.Util.extend(a,{method:"POST"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},PUT:function(a){a=
+OpenLayers.Util.extend(a,{method:"PUT"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},DELETE:function(a){a=OpenLayers.Util.extend(a,{method:"DELETE"});return OpenLayers.Request.issue(a)},HEAD:function(a){a=OpenLayers.Util.extend(a,{method:"HEAD"});return OpenLayers.Request.issue(a)},OPTIONS:function(a){a=OpenLayers.Util.extend(a,{method:"OPTIONS"});return OpenLayers.Request.issue(a)}});OpenLayers.Layer.ArcIMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{ClientVersion:"9.2",ServiceName:""},featureCoordSys:"4326",filterCoordSys:"4326",layers:null,async:!0,name:"ArcIMS",isBaseLayer:!0,DEFAULT_OPTIONS:{tileSize:new OpenLayers.Size(512,512),featureCoordSys:"4326",filterCoordSys:"4326",layers:null,isBaseLayer:!0,async:!0,name:"ArcIMS"},initialize:function(a,b,c){this.tileSize=new OpenLayers.Size(512,512);this.params=OpenLayers.Util.applyDefaults({ServiceName:c.serviceName},
+this.DEFAULT_PARAMS);this.options=OpenLayers.Util.applyDefaults(c,this.DEFAULT_OPTIONS);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a,b,this.params,c]);this.transparent&&(this.isBaseLayer||(this.isBaseLayer=!1),"image/jpeg"==this.format&&(this.format=OpenLayers.Util.alphaHack()?"image/gif":"image/png"));null===this.options.layers&&(this.options.layers=[])},getURL:function(a){var b="";a=this.adjustBounds(a);a=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",
+envelope:a.toArray(),tileSize:this.tileSize}));a=new OpenLayers.Request.POST({url:this.getFullRequestString(),data:a.write(),async:!1});null!=a&&(b=a.responseXML,b&&b.documentElement||(b=a.responseText),b=(new OpenLayers.Format.ArcXML).read(b),b=this.getUrlOrImage(b.image.output));return b},getURLasync:function(a,b,c){a=this.adjustBounds(a);a=new OpenLayers.Format.ArcXML(OpenLayers.Util.extend(this.options,{requesttype:"image",envelope:a.toArray(),tileSize:this.tileSize}));OpenLayers.Request.POST({url:this.getFullRequestString(),
+async:!0,data:a.write(),callback:function(a){var e=a.responseXML;e&&e.documentElement||(e=a.responseText);a=(new OpenLayers.Format.ArcXML).read(e);b.call(c,this.getUrlOrImage(a.image.output))},scope:this})},getUrlOrImage:function(a){var b="";a.url?b=a.url:a.data&&(b="data:image/"+a.type+";base64,"+a.data);return b},setLayerQuery:function(a,b){for(var c=0;c<this.options.layers.length;c++)if(a==this.options.layers[c].id){this.options.layers[c].query=b;return}this.options.layers.push({id:a,visible:!0,
+query:b})},getFeatureInfo:function(a,b,c){var d=c.buffer||1,e=c.callback||function(){},f=c.scope||window,g={};OpenLayers.Util.extend(g,this.options);g.requesttype="feature";a instanceof OpenLayers.LonLat?(g.polygon=null,g.envelope=[a.lon-d,a.lat-d,a.lon+d,a.lat+d]):a instanceof OpenLayers.Geometry.Polygon&&(g.envelope=null,g.polygon=a);var h=new OpenLayers.Format.ArcXML(g);OpenLayers.Util.extend(h.request.get_feature,c);h.request.get_feature.layer=b.id;"number"==typeof b.query.accuracy?h.request.get_feature.query.accuracy=
+b.query.accuracy:(a=this.map.getCenter(),c=this.map.getViewPortPxFromLonLat(a),c.x++,c=this.map.getLonLatFromPixel(c),h.request.get_feature.query.accuracy=c.lon-a.lon);h.request.get_feature.query.where=b.query.where;h.request.get_feature.query.spatialfilter.relation="area_intersection";OpenLayers.Request.POST({url:this.getFullRequestString({CustomService:"Query"}),data:h.write(),callback:function(a){a=h.parseResponse(a.responseText);h.iserror()?e.call(f,null):e.call(f,a.features)}})},clone:function(a){null==
+a&&(a=new OpenLayers.Layer.ArcIMS(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},CLASS_NAME:"OpenLayers.Layer.ArcIMS"});OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,slideRatio:null,buttons:null,position:null,initialize:function(a){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments)},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onButtonClick);this.removeButtons();this.position=this.buttons=null;OpenLayers.Control.prototype.destroy.apply(this,arguments)},
+setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.register("buttonclick",this,this.onButtonClick)},draw:function(a){OpenLayers.Control.prototype.draw.apply(this,arguments);a=this.position;this.buttons=[];var b={w:18,h:18},c=new OpenLayers.Pixel(a.x+b.w/2,a.y);this._addButton("panup","north-mini.png",c,b);a.y=c.y+b.h;this._addButton("panleft","west-mini.png",a,b);this._addButton("panright","east-mini.png",a.add(b.w,0),b);this._addButton("pandown","south-mini.png",
+c.add(0,2*b.h),b);this._addButton("zoomin","zoom-plus-mini.png",c.add(0,3*b.h+5),b);this._addButton("zoomworld","zoom-world-mini.png",c.add(0,4*b.h+5),b);this._addButton("zoomout","zoom-minus-mini.png",c.add(0,5*b.h+5),b);return this.div},_addButton:function(a,b,c,d){b=OpenLayers.Util.getImageLocation(b);c=OpenLayers.Util.createAlphaImageDiv(this.id+"_"+a,c,d,b,"absolute");c.style.cursor="pointer";this.div.appendChild(c);c.action=a;c.className="olButton";this.buttons.push(c);return c},_removeButton:function(a){this.div.removeChild(a);
+OpenLayers.Util.removeItem(this.buttons,a)},removeButtons:function(){for(var a=this.buttons.length-1;0<=a;--a)this._removeButton(this.buttons[a])},onButtonClick:function(a){switch(a.buttonElement.action){case "panup":this.map.pan(0,-this.getSlideFactor("h"));break;case "pandown":this.map.pan(0,this.getSlideFactor("h"));break;case "panleft":this.map.pan(-this.getSlideFactor("w"),0);break;case "panright":this.map.pan(this.getSlideFactor("w"),0);break;case "zoomin":this.map.zoomIn();break;case "zoomout":this.map.zoomOut();
+break;case "zoomworld":this.map.zoomToMaxExtent()}},getSlideFactor:function(a){return this.slideRatio?this.map.getSize()[a]*this.slideRatio:this.slideFactor},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Control.PanZoomBar=OpenLayers.Class(OpenLayers.Control.PanZoom,{zoomStopWidth:18,zoomStopHeight:11,slider:null,sliderEvents:null,zoombarDiv:null,zoomWorldIcon:!1,panIcons:!0,forceFixedZoomLevel:!1,mouseDragStart:null,deltaY:null,zoomStart:null,destroy:function(){this._removeZoomBar();this.map.events.un({changebaselayer:this.redraw,updatesize:this.redraw,scope:this});OpenLayers.Control.PanZoom.prototype.destroy.apply(this,arguments);delete this.mouseDragStart;delete this.zoomStart},setMap:function(a){OpenLayers.Control.PanZoom.prototype.setMap.apply(this,
+arguments);this.map.events.on({changebaselayer:this.redraw,updatesize:this.redraw,scope:this})},redraw:function(){null!=this.div&&(this.removeButtons(),this._removeZoomBar());this.draw()},draw:function(a){OpenLayers.Control.prototype.draw.apply(this,arguments);a=this.position.clone();this.buttons=[];var b={w:18,h:18};if(this.panIcons){var c=new OpenLayers.Pixel(a.x+b.w/2,a.y),d=b.w;this.zoomWorldIcon&&(c=new OpenLayers.Pixel(a.x+b.w,a.y));this._addButton("panup","north-mini.png",c,b);a.y=c.y+b.h;
+this._addButton("panleft","west-mini.png",a,b);this.zoomWorldIcon&&(this._addButton("zoomworld","zoom-world-mini.png",a.add(b.w,0),b),d*=2);this._addButton("panright","east-mini.png",a.add(d,0),b);this._addButton("pandown","south-mini.png",c.add(0,2*b.h),b);this._addButton("zoomin","zoom-plus-mini.png",c.add(0,3*b.h+5),b);c=this._addZoomBar(c.add(0,4*b.h+5));this._addButton("zoomout","zoom-minus-mini.png",c,b)}else this._addButton("zoomin","zoom-plus-mini.png",a,b),c=this._addZoomBar(a.add(0,b.h)),
+this._addButton("zoomout","zoom-minus-mini.png",c,b),this.zoomWorldIcon&&(c=c.add(0,b.h+3),this._addButton("zoomworld","zoom-world-mini.png",c,b));return this.div},_addZoomBar:function(a){var b=OpenLayers.Util.getImageLocation("slider.png"),c=this.id+"_"+this.map.id,d=this.map.getMinZoom(),e=this.map.getNumZoomLevels()-1-this.map.getZoom(),e=OpenLayers.Util.createAlphaImageDiv(c,a.add(-1,e*this.zoomStopHeight),{w:20,h:9},b,"absolute");e.style.cursor="move";this.slider=e;this.sliderEvents=new OpenLayers.Events(this,
+e,null,!0,{includeXY:!0});this.sliderEvents.on({touchstart:this.zoomBarDown,touchmove:this.zoomBarDrag,touchend:this.zoomBarUp,mousedown:this.zoomBarDown,mousemove:this.zoomBarDrag,mouseup:this.zoomBarUp});var f={w:this.zoomStopWidth,h:this.zoomStopHeight*(this.map.getNumZoomLevels()-d)},b=OpenLayers.Util.getImageLocation("zoombar.png"),c=null;OpenLayers.Util.alphaHack()?(c=this.id+"_"+this.map.id,c=OpenLayers.Util.createAlphaImageDiv(c,a,{w:f.w,h:this.zoomStopHeight},b,"absolute",null,"crop"),c.style.height=
+f.h+"px"):c=OpenLayers.Util.createDiv("OpenLayers_Control_PanZoomBar_Zoombar"+this.map.id,a,f,b);c.style.cursor="pointer";c.className="olButton";this.zoombarDiv=c;this.div.appendChild(c);this.startTop=parseInt(c.style.top);this.div.appendChild(e);this.map.events.register("zoomend",this,this.moveZoomBar);return a=a.add(0,this.zoomStopHeight*(this.map.getNumZoomLevels()-d))},_removeZoomBar:function(){this.sliderEvents.un({touchstart:this.zoomBarDown,touchmove:this.zoomBarDrag,touchend:this.zoomBarUp,
+mousedown:this.zoomBarDown,mousemove:this.zoomBarDrag,mouseup:this.zoomBarUp});this.sliderEvents.destroy();this.div.removeChild(this.zoombarDiv);this.zoombarDiv=null;this.div.removeChild(this.slider);this.slider=null;this.map.events.unregister("zoomend",this,this.moveZoomBar)},onButtonClick:function(a){OpenLayers.Control.PanZoom.prototype.onButtonClick.apply(this,arguments);if(a.buttonElement===this.zoombarDiv){var b=a.buttonXY.y/this.zoomStopHeight;if(this.forceFixedZoomLevel||!this.map.fractionalZoom)b=
+Math.floor(b);b=this.map.getNumZoomLevels()-1-b;b=Math.min(Math.max(b,0),this.map.getNumZoomLevels()-1);this.map.zoomTo(b)}},passEventToSlider:function(a){this.sliderEvents.handleBrowserEvent(a)},zoomBarDown:function(a){if(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))this.map.events.on({touchmove:this.passEventToSlider,mousemove:this.passEventToSlider,mouseup:this.passEventToSlider,scope:this}),this.mouseDragStart=a.xy.clone(),this.zoomStart=a.xy.clone(),this.div.style.cursor=
+"move",this.zoombarDiv.offsets=null,OpenLayers.Event.stop(a)},zoomBarDrag:function(a){if(null!=this.mouseDragStart){var b=this.mouseDragStart.y-a.xy.y,c=OpenLayers.Util.pagePosition(this.zoombarDiv);0<a.clientY-c[1]&&a.clientY-c[1]<parseInt(this.zoombarDiv.style.height)-2&&(b=parseInt(this.slider.style.top)-b,this.slider.style.top=b+"px",this.mouseDragStart=a.xy.clone());this.deltaY=this.zoomStart.y-a.xy.y;OpenLayers.Event.stop(a)}},zoomBarUp:function(a){if((OpenLayers.Event.isLeftClick(a)||"touchend"===
+a.type)&&this.mouseDragStart){this.div.style.cursor="";this.map.events.un({touchmove:this.passEventToSlider,mouseup:this.passEventToSlider,mousemove:this.passEventToSlider,scope:this});var b=this.map.zoom;!this.forceFixedZoomLevel&&this.map.fractionalZoom?(b+=this.deltaY/this.zoomStopHeight,b=Math.min(Math.max(b,0),this.map.getNumZoomLevels()-1)):(b+=this.deltaY/this.zoomStopHeight,b=Math.max(Math.round(b),0));this.map.zoomTo(b);this.zoomStart=this.mouseDragStart=null;this.deltaY=0;OpenLayers.Event.stop(a)}},
+moveZoomBar:function(){var a=(this.map.getNumZoomLevels()-1-this.map.getZoom())*this.zoomStopHeight+this.startTop+1;this.slider.style.top=a+"px"},CLASS_NAME:"OpenLayers.Control.PanZoomBar"});OpenLayers.Format.WFSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.0",CLASS_NAME:"OpenLayers.Format.WFSCapabilities"});OpenLayers.Format.WFSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{wfs:"http://www.opengis.net/wfs",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",ows:"http://www.opengis.net/ows"},errorProperty:"featureTypeList",defaultPrefix:"wfs",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{wfs:{WFS_Capabilities:function(a,
+b){this.readChildNodes(a,b)},FeatureTypeList:function(a,b){b.featureTypeList={featureTypes:[]};this.readChildNodes(a,b.featureTypeList)},FeatureType:function(a,b){var c={};this.readChildNodes(a,c);b.featureTypes.push(c)},Name:function(a,b){var c=this.getChildValue(a);c&&(c=c.split(":"),b.name=c.pop(),0<c.length&&(b.featureNS=this.lookupNamespaceURI(a,c[0])))},Title:function(a,b){var c=this.getChildValue(a);c&&(b.title=c)},Abstract:function(a,b){var c=this.getChildValue(a);c&&(b["abstract"]=c)}}},
+CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1"});OpenLayers.Format.WFSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},readers:{wfs:OpenLayers.Util.applyDefaults({DefaultSRS:function(a,b){var c=this.getChildValue(a);c&&(b.srs=c)}},OpenLayers.Format.WFSCapabilities.v1.prototype.readers.wfs),ows:OpenLayers.Format.OWSCommon.v1.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_1_0"});OpenLayers.Layer.Image=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:!0,url:null,extent:null,size:null,tile:null,aspectRatio:null,initialize:function(a,b,c,d,e){this.url=b;this.maxExtent=this.extent=c;this.size=d;OpenLayers.Layer.prototype.initialize.apply(this,[a,e]);this.aspectRatio=this.extent.getHeight()/this.size.h/(this.extent.getWidth()/this.size.w)},destroy:function(){this.tile&&(this.removeTileMonitoringHooks(this.tile),this.tile.destroy(),this.tile=null);OpenLayers.Layer.prototype.destroy.apply(this,
+arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Image(this.name,this.url,this.extent,this.size,this.getOptions()));return a=OpenLayers.Layer.prototype.clone.apply(this,[a])},setMap:function(a){null==this.options.maxResolution&&(this.options.maxResolution=this.aspectRatio*this.extent.getWidth()/this.size.w);OpenLayers.Layer.prototype.setMap.apply(this,arguments)},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var d=null==this.tile;if(b||d){this.setTileSize();
+var e=this.map.getLayerPxFromLonLat({lon:this.extent.left,lat:this.extent.top});d?(this.tile=new OpenLayers.Tile.Image(this,e,this.extent,null,this.tileSize),this.addTileMonitoringHooks(this.tile)):(this.tile.size=this.tileSize.clone(),this.tile.position=e.clone());this.tile.draw()}},setTileSize:function(){var a=this.extent.getWidth()/this.map.getResolution(),b=this.extent.getHeight()/this.map.getResolution();this.tileSize=new OpenLayers.Size(a,b)},addTileMonitoringHooks:function(a){a.onLoadStart=
+function(){this.events.triggerEvent("loadstart")};a.events.register("loadstart",this,a.onLoadStart);a.onLoadEnd=function(){this.events.triggerEvent("loadend")};a.events.register("loadend",this,a.onLoadEnd);a.events.register("unload",this,a.onLoadEnd)},removeTileMonitoringHooks:function(a){a.unload();a.events.un({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,scope:this})},setUrl:function(a){this.url=a;this.tile.draw()},getURL:function(a){return this.url},CLASS_NAME:"OpenLayers.Layer.Image"});OpenLayers.Strategy=OpenLayers.Class({layer:null,options:null,active:null,autoActivate:!0,autoDestroy:!0,initialize:function(a){OpenLayers.Util.extend(this,a);this.options=a;this.active=!1},destroy:function(){this.deactivate();this.options=this.layer=null},setLayer:function(a){this.layer=a},activate:function(){return this.active?!1:this.active=!0},deactivate:function(){return this.active?(this.active=!1,!0):!1},CLASS_NAME:"OpenLayers.Strategy"});OpenLayers.Strategy.Save=OpenLayers.Class(OpenLayers.Strategy,{events:null,auto:!1,timer:null,initialize:function(a){OpenLayers.Strategy.prototype.initialize.apply(this,[a]);this.events=new OpenLayers.Events(this)},activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);if(a&&this.auto)if("number"===typeof this.auto)this.timer=window.setInterval(OpenLayers.Function.bind(this.save,this),1E3*this.auto);else this.layer.events.on({featureadded:this.triggerSave,afterfeaturemodified:this.triggerSave,
+scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.auto&&("number"===typeof this.auto?window.clearInterval(this.timer):this.layer.events.un({featureadded:this.triggerSave,afterfeaturemodified:this.triggerSave,scope:this}));return a},triggerSave:function(a){var b=a.feature;b.state!==OpenLayers.State.INSERT&&b.state!==OpenLayers.State.UPDATE&&b.state!==OpenLayers.State.DELETE||this.save([a.feature])},save:function(a){a||(a=this.layer.features);
+this.events.triggerEvent("start",{features:a});var b=this.layer.projection,c=this.layer.map.getProjectionObject();if(!c.equals(b)){for(var d=a.length,e=Array(d),f,g,h=0;h<d;++h)f=a[h],g=f.clone(),g.fid=f.fid,g.state=f.state,f.url&&(g.url=f.url),g._original=f,g.geometry.transform(c,b),e[h]=g;a=e}this.layer.protocol.commit(a,{callback:this.onCommit,scope:this})},onCommit:function(a){var b={response:a};if(a.success()){for(var c=a.reqFeatures,d,e=[],f=a.insertIds||[],g=0,h=0,k=c.length;h<k;++h)if(d=c[h],
+d=d._original||d,a=d.state)a==OpenLayers.State.DELETE?e.push(d):a==OpenLayers.State.INSERT&&(d.fid=f[g],++g),d.state=null;0<e.length&&this.layer.destroyFeatures(e);this.events.triggerEvent("success",b)}else this.events.triggerEvent("fail",b)},CLASS_NAME:"OpenLayers.Strategy.Save"});OpenLayers.Events.featureclick=OpenLayers.Class({cache:null,map:null,provides:["featureclick","nofeatureclick","featureover","featureout"],initialize:function(a){this.target=a;if(a.object instanceof OpenLayers.Map)this.setMap(a.object);else if(a.object instanceof OpenLayers.Layer.Vector)a.object.map?this.setMap(a.object.map):a.object.events.register("added",this,function(b){this.setMap(a.object.map)});else throw"Listeners for '"+this.provides.join("', '")+"' events can only be registered for OpenLayers.Layer.Vector or OpenLayers.Map instances";
+for(var b=this.provides.length-1;0<=b;--b)a.extensions[this.provides[b]]=!0},setMap:function(a){this.map=a;this.cache={};a.events.register("mousedown",this,this.start,{extension:!0});a.events.register("mouseup",this,this.onClick,{extension:!0});a.events.register("touchstart",this,this.start,{extension:!0});a.events.register("touchmove",this,this.cancel,{extension:!0});a.events.register("touchend",this,this.onClick,{extension:!0});a.events.register("mousemove",this,this.onMousemove,{extension:!0})},
+start:function(a){this.startEvt=a},cancel:function(a){delete this.startEvt},onClick:function(a){if(this.startEvt&&("touchend"===a.type||OpenLayers.Event.isLeftClick(a))){a=this.getFeatures(this.startEvt);delete this.startEvt;for(var b,c,d={},e=0,f=a.length;e<f&&(b=a[e],c=b.layer,d[c.id]=!0,b=this.triggerEvent("featureclick",{feature:b}),!1!==b);++e);e=0;for(f=this.map.layers.length;e<f;++e)c=this.map.layers[e],c instanceof OpenLayers.Layer.Vector&&!d[c.id]&&this.triggerEvent("nofeatureclick",{layer:c})}},
+onMousemove:function(a){delete this.startEvt;var b=this.getFeatures(a),c={};a=[];for(var d,e=0,f=b.length;e<f;++e)d=b[e],c[d.id]=d,this.cache[d.id]||a.push(d);var b=[],g;for(g in this.cache)d=this.cache[g],d.layer&&d.layer.map?c[d.id]||b.push(d):delete this.cache[g];e=0;for(f=a.length;e<f&&(d=a[e],this.cache[d.id]=d,g=this.triggerEvent("featureover",{feature:d}),!1!==g);++e);e=0;for(f=b.length;e<f&&(d=b[e],delete this.cache[d.id],g=this.triggerEvent("featureout",{feature:d}),!1!==g);++e);},triggerEvent:function(a,
+b){var c=b.feature?b.feature.layer:b.layer,d=this.target.object;if(d instanceof OpenLayers.Map||d===c)return this.target.triggerEvent(a,b)},getFeatures:function(a){var b=a.clientX,c=a.clientY,d=[],e=[],f=[],g,h,k,l;for(l=this.map.layers.length-1;0<=l;--l)if(g=this.map.layers[l],"none"!==g.div.style.display)if(g.renderer instanceof OpenLayers.Renderer.Elements){if(g instanceof OpenLayers.Layer.Vector)for(h=document.elementFromPoint(b,c);h&&h._featureId;)(k=g.getFeatureById(h._featureId))?(d.push(k),
+h.style.display="none",e.push(h),h=document.elementFromPoint(b,c)):h=!1;f.push(g);g.div.style.display="none"}else g.renderer instanceof OpenLayers.Renderer.Canvas&&(k=g.renderer.getFeatureIdFromEvent(a))&&(d.push(k),f.push(g));l=0;for(a=e.length;l<a;++l)e[l].style.display="";for(l=f.length-1;0<=l;--l)f[l].div.style.display="block";return d},destroy:function(){for(var a=this.provides.length-1;0<=a;--a)delete this.target.extensions[this.provides[a]];this.map.events.un({mousemove:this.onMousemove,mousedown:this.start,
+mouseup:this.onClick,touchstart:this.start,touchmove:this.cancel,touchend:this.onClick,scope:this});delete this.cache;delete this.map;delete this.target}});OpenLayers.Events.nofeatureclick=OpenLayers.Events.featureclick;OpenLayers.Events.featureover=OpenLayers.Events.featureclick;OpenLayers.Events.featureout=OpenLayers.Events.featureclick;OpenLayers.Format.GPX=OpenLayers.Class(OpenLayers.Format.XML,{defaultDesc:"No description available",extractWaypoints:!0,extractTracks:!0,extractRoutes:!0,extractAttributes:!0,namespaces:{gpx:"http://www.topografix.com/GPX/1/1",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd",creator:"OpenLayers",initialize:function(a){this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,
+[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=[];if(this.extractTracks)for(var c=a.getElementsByTagName("trk"),d=0,e=c.length;d<e;d++){var f={};this.extractAttributes&&(f=this.parseAttributes(c[d]));for(var g=this.getElementsByTagNameNS(c[d],c[d].namespaceURI,"trkseg"),h=0,k=g.length;h<k;h++){var l=this.extractSegment(g[h],"trkpt");b.push(new OpenLayers.Feature.Vector(l,f))}}if(this.extractRoutes)for(e=a.getElementsByTagName("rte"),c=0,d=
+e.length;c<d;c++)f={},this.extractAttributes&&(f=this.parseAttributes(e[c])),g=this.extractSegment(e[c],"rtept"),b.push(new OpenLayers.Feature.Vector(g,f));if(this.extractWaypoints)for(a=a.getElementsByTagName("wpt"),c=0,e=a.length;c<e;c++)f={},this.extractAttributes&&(f=this.parseAttributes(a[c])),d=new OpenLayers.Geometry.Point(a[c].getAttribute("lon"),a[c].getAttribute("lat")),b.push(new OpenLayers.Feature.Vector(d,f));if(this.internalProjection&&this.externalProjection)for(f=0,a=b.length;f<a;f++)b[f].geometry.transform(this.externalProjection,
+this.internalProjection);return b},extractSegment:function(a,b){for(var c=this.getElementsByTagNameNS(a,a.namespaceURI,b),d=[],e=0,f=c.length;e<f;e++)d.push(new OpenLayers.Geometry.Point(c[e].getAttribute("lon"),c[e].getAttribute("lat")));return new OpenLayers.Geometry.LineString(d)},parseAttributes:function(a){var b={};a=a.firstChild;for(var c,d;a;)1==a.nodeType&&a.firstChild&&(c=a.firstChild,3==c.nodeType||4==c.nodeType)&&(d=a.prefix?a.nodeName.split(":")[1]:a.nodeName,"trkseg"!=d&&"rtept"!=d&&
+(b[d]=c.nodeValue)),a=a.nextSibling;return b},write:function(a,b){a=OpenLayers.Util.isArray(a)?a:[a];var c=this.createElementNS(this.namespaces.gpx,"gpx");c.setAttribute("version","1.1");c.setAttribute("creator",this.creator);this.setAttributes(c,{"xsi:schemaLocation":this.schemaLocation});b&&"object"==typeof b&&c.appendChild(this.buildMetadataNode(b));for(var d=0,e=a.length;d<e;d++)c.appendChild(this.buildFeatureNode(a[d]));return OpenLayers.Format.XML.prototype.write.apply(this,[c])},buildMetadataNode:function(a){for(var b=
+["name","desc","author"],c=this.createElementNS(this.namespaces.gpx,"metadata"),d=0;d<b.length;d++){var e=b[d];if(a[e]){var f=this.createElementNS(this.namespaces.gpx,e);f.appendChild(this.createTextNode(a[e]));c.appendChild(f)}}return c},buildFeatureNode:function(a){var b=a.geometry,b=b.clone();this.internalProjection&&this.externalProjection&&b.transform(this.internalProjection,this.externalProjection);if("OpenLayers.Geometry.Point"==b.CLASS_NAME){var c=this.buildWptNode(b);this.appendAttributesNode(c,
+a);return c}c=this.createElementNS(this.namespaces.gpx,"trk");this.appendAttributesNode(c,a);a=this.buildTrkSegNode(b);a=OpenLayers.Util.isArray(a)?a:[a];for(var b=0,d=a.length;b<d;b++)c.appendChild(a[b]);return c},buildTrkSegNode:function(a){var b,c,d,e;if("OpenLayers.Geometry.LineString"==a.CLASS_NAME||"OpenLayers.Geometry.LinearRing"==a.CLASS_NAME){b=this.createElementNS(this.namespaces.gpx,"trkseg");c=0;for(d=a.components.length;c<d;c++)e=a.components[c],b.appendChild(this.buildTrkPtNode(e));
+return b}b=[];c=0;for(d=a.components.length;c<d;c++)b.push(this.buildTrkSegNode(a.components[c]));return b},buildTrkPtNode:function(a){var b=this.createElementNS(this.namespaces.gpx,"trkpt");b.setAttribute("lon",a.x);b.setAttribute("lat",a.y);return b},buildWptNode:function(a){var b=this.createElementNS(this.namespaces.gpx,"wpt");b.setAttribute("lon",a.x);b.setAttribute("lat",a.y);return b},appendAttributesNode:function(a,b){var c=this.createElementNS(this.namespaces.gpx,"name");c.appendChild(this.createTextNode(b.attributes.name||
+b.id));a.appendChild(c);c=this.createElementNS(this.namespaces.gpx,"desc");c.appendChild(this.createTextNode(b.attributes.description||this.defaultDesc));a.appendChild(c)},CLASS_NAME:"OpenLayers.Format.GPX"});OpenLayers.Format.WMSDescribeLayer=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.1",CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer"});OpenLayers.Format.WMSDescribeLayer.v1_1_1=OpenLayers.Class(OpenLayers.Format.WMSDescribeLayer,{initialize:function(a){OpenLayers.Format.WMSDescribeLayer.prototype.initialize.apply(this,[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));for(var b=a.documentElement.childNodes,c={layerDescriptions:[]},d,e,f=0;f<b.length;++f)if(d=b[f],e=d.nodeName,"LayerDescription"==e){e=d.getAttribute("name");var g="",h="",k="";d.getAttribute("owsType")?(g=d.getAttribute("owsType"),
+h=d.getAttribute("owsURL")):""!=d.getAttribute("wfs")?(g="WFS",h=d.getAttribute("wfs")):""!=d.getAttribute("wcs")&&(g="WCS",h=d.getAttribute("wcs"));d=d.getElementsByTagName("Query");0<d.length&&((k=d[0].getAttribute("typeName"))||(k=d[0].getAttribute("typename")));d={layerName:e,owsType:g,owsURL:h,typeName:k};c.layerDescriptions.push(d);c.length=c.layerDescriptions.length;c[c.length-1]=d}else if("ServiceException"==e)return{error:(new OpenLayers.Format.OGCExceptionReport).read(a)};return c},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer.v1_1_1"});
+OpenLayers.Format.WMSDescribeLayer.v1_1_0=OpenLayers.Format.WMSDescribeLayer.v1_1_1;OpenLayers.Layer.XYZ=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,sphericalMercator:!1,zoomOffset:0,serverResolutions:null,initialize:function(a,b,c){if(c&&c.sphericalMercator||this.sphericalMercator)c=OpenLayers.Util.extend({projection:"EPSG:900913",numZoomLevels:19},c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a||this.name,b||this.url,{},c])},clone:function(a){null==a&&(a=new OpenLayers.Layer.XYZ(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,
+[a])},getURL:function(a){a=this.getXYZ(a);var b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(""+a.x+a.y+a.z,b));return OpenLayers.String.format(b,a)},getXYZ:function(a){var b=this.getServerResolution(),c=Math.round((a.left-this.maxExtent.left)/(b*this.tileSize.w));a=Math.round((this.maxExtent.top-a.top)/(b*this.tileSize.h));b=this.getServerZoom();if(this.wrapDateLine)var d=Math.pow(2,b),c=(c%d+d)%d;return{x:c,y:a,z:b}},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,
+arguments);this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.bottom))},CLASS_NAME:"OpenLayers.Layer.XYZ"});OpenLayers.Layer.OSM=OpenLayers.Class(OpenLayers.Layer.XYZ,{name:"OpenStreetMap",url:["http://a.tile.openstreetmap.org/${z}/${x}/${y}.png","http://b.tile.openstreetmap.org/${z}/${x}/${y}.png","http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"],attribution:"&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",sphericalMercator:!0,wrapDateLine:!0,tileOptions:null,initialize:function(a,b,c){OpenLayers.Layer.XYZ.prototype.initialize.apply(this,arguments);this.tileOptions=
+OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options&&this.options.tileOptions)},clone:function(a){null==a&&(a=new OpenLayers.Layer.OSM(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:!1,size:null,resolution:null,map:null,featureDx:0,initialize:function(a,b){this.container=OpenLayers.Util.getElement(a);OpenLayers.Util.extend(this,b)},destroy:function(){this.map=this.resolution=this.size=this.extent=this.container=null},supported:function(){return!1},setExtent:function(a,b){this.extent=a.clone();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var c=a.getWidth()/this.map.getExtent().getWidth();
+a=a.scale(1/c);this.extent=a.wrapDateLine(this.map.getMaxExtent()).scale(c)}b&&(this.resolution=null);return!0},setSize:function(a){this.size=a.clone();this.resolution=null},getResolution:function(){return this.resolution=this.resolution||this.map.getResolution()},drawFeature:function(a,b){null==b&&(b=a.style);if(a.geometry){var c=a.geometry.getBounds();if(c){var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());c.intersectsBounds(this.extent,{worldBounds:d})?this.calculateFeatureDx(c,
+d):b={display:"none"};c=this.drawGeometry(a.geometry,b,a.id);if("none"!=b.display&&b.label&&!1!==c){d=a.geometry.getCentroid();if(b.labelXOffset||b.labelYOffset){var e=isNaN(b.labelXOffset)?0:b.labelXOffset,f=isNaN(b.labelYOffset)?0:b.labelYOffset,g=this.getResolution();d.move(e*g,f*g)}this.drawText(a.id,b,d)}else this.removeText(a.id);return c}}},calculateFeatureDx:function(a,b){this.featureDx=0;if(b){var c=b.getWidth();this.featureDx=Math.round(((a.left+a.right)/2-(this.extent.left+this.extent.right)/
+2)/c)*c}},drawGeometry:function(a,b,c){},drawText:function(a,b,c){},removeText:function(a){},clear:function(){},getFeatureIdFromEvent:function(a){},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<c;++b){var d=a[b];this.eraseGeometry(d.geometry,d.id);this.removeText(d.id)}},eraseGeometry:function(a,b){},moveRoot:function(a){},getRenderLayerId:function(){return this.container.id},applyDefaultSymbolizer:function(a){var b=OpenLayers.Util.extend({},OpenLayers.Renderer.defaultSymbolizer);
+!1===a.stroke&&(delete b.strokeWidth,delete b.strokeColor);!1===a.fill&&delete b.fillColor;OpenLayers.Util.extend(b,a);return b},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Renderer.defaultSymbolizer={fillColor:"#000000",strokeColor:"#000000",strokeWidth:2,fillOpacity:1,strokeOpacity:1,pointRadius:0,labelAlign:"cm"};
+OpenLayers.Renderer.symbol={star:[350,75,379,161,469,161,397,215,423,301,350,250,277,301,303,215,231,161,321,161,350,75],cross:[4,0,6,0,6,4,10,4,10,6,6,6,6,10,4,10,4,6,0,6,0,4,4,4,4,0],x:[0,0,25,0,50,35,75,0,100,0,65,50,100,100,75,100,50,65,25,100,0,100,35,50,0,0],square:[0,0,0,1,1,1,1,0,0,0],triangle:[0,10,10,10,5,0,0,10]};OpenLayers.Renderer.Canvas=OpenLayers.Class(OpenLayers.Renderer,{hitDetection:!0,hitOverflow:0,canvas:null,features:null,pendingRedraw:!1,cachedSymbolBounds:{},initialize:function(a,b){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.root=document.createElement("canvas");this.container.appendChild(this.root);this.canvas=this.root.getContext("2d");this.features={};this.hitDetection&&(this.hitCanvas=document.createElement("canvas"),this.hitContext=this.hitCanvas.getContext("2d"))},
+setExtent:function(){OpenLayers.Renderer.prototype.setExtent.apply(this,arguments);return!1},eraseGeometry:function(a,b){this.eraseFeatures(this.features[b][0])},supported:function(){return OpenLayers.CANVAS_SUPPORTED},setSize:function(a){this.size=a.clone();var b=this.root;b.style.width=a.w+"px";b.style.height=a.h+"px";b.width=a.w;b.height=a.h;this.resolution=null;this.hitDetection&&(b=this.hitCanvas,b.style.width=a.w+"px",b.style.height=a.h+"px",b.width=a.w,b.height=a.h)},drawFeature:function(a,
+b){var c;if(a.geometry){b=this.applyDefaultSymbolizer(b||a.style);c=a.geometry.getBounds();var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());d=c&&c.intersectsBounds(this.extent,{worldBounds:d});(c="none"!==b.display&&!!c&&d)?this.features[a.id]=[a,b]:delete this.features[a.id];this.pendingRedraw=!0}this.pendingRedraw&&!this.locked&&(this.redraw(),this.pendingRedraw=!1);return c},drawGeometry:function(a,b,c){var d=a.CLASS_NAME;if("OpenLayers.Geometry.Collection"==
+d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d)for(d=0;d<a.components.length;d++)this.drawGeometry(a.components[d],b,c);else switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":this.drawPoint(a,b,c);break;case "OpenLayers.Geometry.LineString":this.drawLineString(a,b,c);break;case "OpenLayers.Geometry.LinearRing":this.drawLinearRing(a,b,c);break;case "OpenLayers.Geometry.Polygon":this.drawPolygon(a,b,c)}},drawExternalGraphic:function(a,
+b,c){var d=new Image,e=b.title||b.graphicTitle;e&&(d.title=e);var f=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,f=f?f:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*f),k=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),l=b.graphicOpacity||b.fillOpacity;d.onload=OpenLayers.Function.bind(function(){if(this.features[c]){var b=this.getLocalXY(a),e=b[0],b=b[1];if(!isNaN(e)&&!isNaN(b)){var e=e+h|0,b=b+k|0,p=this.canvas;p.globalAlpha=l;var q=
+OpenLayers.Renderer.Canvas.drawImageScaleFactor||(OpenLayers.Renderer.Canvas.drawImageScaleFactor=/android 2.1/.test(navigator.userAgent.toLowerCase())?320/window.screen.width:1);p.drawImage(d,e*q,b*q,f*q,g*q);this.hitDetection&&(this.setHitContextStyle("fill",c),this.hitContext.fillRect(e,b,f,g))}}},this);d.src=b.externalGraphic},drawNamedSymbol:function(a,b,c){var d,e,f,g;f=Math.PI/180;var h=OpenLayers.Renderer.symbol[b.graphicName];if(!h)throw Error(b.graphicName+" is not a valid symbol name");
+if(!(!h.length||2>h.length||(a=this.getLocalXY(a),e=a[0],g=a[1],isNaN(e)||isNaN(g)))){this.canvas.lineCap="round";this.canvas.lineJoin="round";this.hitDetection&&(this.hitContext.lineCap="round",this.hitContext.lineJoin="round");if(b.graphicName in this.cachedSymbolBounds)d=this.cachedSymbolBounds[b.graphicName];else{d=new OpenLayers.Bounds;for(a=0;a<h.length;a+=2)d.extend(new OpenLayers.LonLat(h[a],h[a+1]));this.cachedSymbolBounds[b.graphicName]=d}this.canvas.save();this.hitDetection&&this.hitContext.save();
+this.canvas.translate(e,g);this.hitDetection&&this.hitContext.translate(e,g);a=f*b.rotation;isNaN(a)||(this.canvas.rotate(a),this.hitDetection&&this.hitContext.rotate(a));f=2*b.pointRadius/Math.max(d.getWidth(),d.getHeight());this.canvas.scale(f,f);this.hitDetection&&this.hitContext.scale(f,f);a=d.getCenterLonLat().lon;d=d.getCenterLonLat().lat;this.canvas.translate(-a,-d);this.hitDetection&&this.hitContext.translate(-a,-d);g=b.strokeWidth;b.strokeWidth=g/f;if(!1!==b.fill){this.setCanvasStyle("fill",
+b);this.canvas.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.canvas.lineTo(d,e);this.canvas.closePath();this.canvas.fill();if(this.hitDetection){this.setHitContextStyle("fill",c,b);this.hitContext.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.hitContext.lineTo(d,e);this.hitContext.closePath();this.hitContext.fill()}}if(!1!==b.stroke){this.setCanvasStyle("stroke",b);this.canvas.beginPath();for(a=0;a<h.length;a+=2)d=h[a],
+e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.canvas.lineTo(d,e);this.canvas.closePath();this.canvas.stroke();if(this.hitDetection){this.setHitContextStyle("stroke",c,b,f);this.hitContext.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.hitContext.moveTo(d,e),this.hitContext.lineTo(d,e);this.hitContext.closePath();this.hitContext.stroke()}}b.strokeWidth=g;this.canvas.restore();this.hitDetection&&this.hitContext.restore();this.setCanvasStyle("reset")}},setCanvasStyle:function(a,b){"fill"===
+a?(this.canvas.globalAlpha=b.fillOpacity,this.canvas.fillStyle=b.fillColor):"stroke"===a?(this.canvas.globalAlpha=b.strokeOpacity,this.canvas.strokeStyle=b.strokeColor,this.canvas.lineWidth=b.strokeWidth):(this.canvas.globalAlpha=0,this.canvas.lineWidth=1)},featureIdToHex:function(a){a=Number(a.split("_").pop())+1;16777216<=a&&(this.hitOverflow=a-16777215,a=a%16777216+1);a="000000"+a.toString(16);var b=a.length;return a="#"+a.substring(b-6,b)},setHitContextStyle:function(a,b,c,d){b=this.featureIdToHex(b);
+"fill"==a?(this.hitContext.globalAlpha=1,this.hitContext.fillStyle=b):"stroke"==a?(this.hitContext.globalAlpha=1,this.hitContext.strokeStyle=b,"undefined"===typeof d?this.hitContext.lineWidth=c.strokeWidth+2:isNaN(d)||(this.hitContext.lineWidth=c.strokeWidth+2/d)):(this.hitContext.globalAlpha=0,this.hitContext.lineWidth=1)},drawPoint:function(a,b,c){if(!1!==b.graphic)if(b.externalGraphic)this.drawExternalGraphic(a,b,c);else if(b.graphicName&&"circle"!=b.graphicName)this.drawNamedSymbol(a,b,c);else{var d=
+this.getLocalXY(a);a=d[0];d=d[1];if(!isNaN(a)&&!isNaN(d)){var e=2*Math.PI,f=b.pointRadius;!1!==b.fill&&(this.setCanvasStyle("fill",b),this.canvas.beginPath(),this.canvas.arc(a,d,f,0,e,!0),this.canvas.fill(),this.hitDetection&&(this.setHitContextStyle("fill",c,b),this.hitContext.beginPath(),this.hitContext.arc(a,d,f,0,e,!0),this.hitContext.fill()));!1!==b.stroke&&(this.setCanvasStyle("stroke",b),this.canvas.beginPath(),this.canvas.arc(a,d,f,0,e,!0),this.canvas.stroke(),this.hitDetection&&(this.setHitContextStyle("stroke",
+c,b),this.hitContext.beginPath(),this.hitContext.arc(a,d,f,0,e,!0),this.hitContext.stroke()),this.setCanvasStyle("reset"))}}},drawLineString:function(a,b,c){b=OpenLayers.Util.applyDefaults({fill:!1},b);this.drawLinearRing(a,b,c)},drawLinearRing:function(a,b,c){!1!==b.fill&&(this.setCanvasStyle("fill",b),this.renderPath(this.canvas,a,b,c,"fill"),this.hitDetection&&(this.setHitContextStyle("fill",c,b),this.renderPath(this.hitContext,a,b,c,"fill")));!1!==b.stroke&&(this.setCanvasStyle("stroke",b),this.renderPath(this.canvas,
+a,b,c,"stroke"),this.hitDetection&&(this.setHitContextStyle("stroke",c,b),this.renderPath(this.hitContext,a,b,c,"stroke")));this.setCanvasStyle("reset")},renderPath:function(a,b,c,d,e){b=b.components;c=b.length;a.beginPath();d=this.getLocalXY(b[0]);var f=d[1];if(!isNaN(d[0])&&!isNaN(f)){a.moveTo(d[0],d[1]);for(d=1;d<c;++d)f=this.getLocalXY(b[d]),a.lineTo(f[0],f[1]);"fill"===e?a.fill():a.stroke()}},drawPolygon:function(a,b,c){a=a.components;var d=a.length;this.drawLinearRing(a[0],b,c);for(var e=1;e<
+d;++e)this.canvas.globalCompositeOperation="destination-out",this.hitDetection&&(this.hitContext.globalCompositeOperation="destination-out"),this.drawLinearRing(a[e],OpenLayers.Util.applyDefaults({stroke:!1,fillOpacity:1},b),c),this.canvas.globalCompositeOperation="source-over",this.hitDetection&&(this.hitContext.globalCompositeOperation="source-over"),this.drawLinearRing(a[e],OpenLayers.Util.applyDefaults({fill:!1},b),c)},drawText:function(a,b){var c=this.getLocalXY(a);this.setCanvasStyle("reset");
+this.canvas.fillStyle=b.fontColor;this.canvas.globalAlpha=b.fontOpacity||1;var d=[b.fontStyle?b.fontStyle:"normal","normal",b.fontWeight?b.fontWeight:"normal",b.fontSize?b.fontSize:"1em",b.fontFamily?b.fontFamily:"sans-serif"].join(" "),e=b.label.split("\n"),f=e.length;if(this.canvas.fillText){this.canvas.font=d;this.canvas.textAlign=OpenLayers.Renderer.Canvas.LABEL_ALIGN[b.labelAlign[0]]||"center";this.canvas.textBaseline=OpenLayers.Renderer.Canvas.LABEL_ALIGN[b.labelAlign[1]]||"middle";var g=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[1]];
+null==g&&(g=-0.5);d=this.canvas.measureText("Mg").height||this.canvas.measureText("xx").width;c[1]+=d*g*(f-1);for(g=0;g<f;g++)b.labelOutlineWidth&&(this.canvas.save(),this.canvas.globalAlpha=b.labelOutlineOpacity||b.fontOpacity||1,this.canvas.strokeStyle=b.labelOutlineColor,this.canvas.lineWidth=b.labelOutlineWidth,this.canvas.strokeText(e[g],c[0],c[1]+d*g+1),this.canvas.restore()),this.canvas.fillText(e[g],c[0],c[1]+d*g)}else if(this.canvas.mozDrawText){this.canvas.mozTextStyle=d;var h=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[0]];
+null==h&&(h=-0.5);g=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[1]];null==g&&(g=-0.5);d=this.canvas.mozMeasureText("xx");c[1]+=d*(1+g*f);for(g=0;g<f;g++){var k=c[0]+h*this.canvas.mozMeasureText(e[g]),l=c[1]+g*d;this.canvas.translate(k,l);this.canvas.mozDrawText(e[g]);this.canvas.translate(-k,-l)}}this.setCanvasStyle("reset")},getLocalXY:function(a){var b=this.getResolution(),c=this.extent;return[(a.x-this.featureDx)/b+-c.left/b,c.top/b-a.y/b]},clear:function(){var a=this.root.height,b=this.root.width;
+this.canvas.clearRect(0,0,b,a);this.features={};this.hitDetection&&this.hitContext.clearRect(0,0,b,a)},getFeatureIdFromEvent:function(a){var b;if(this.hitDetection&&"none"!==this.root.style.display&&!this.map.dragging&&(a=a.xy,a=this.hitContext.getImageData(a.x|0,a.y|0,1,1).data,255===a[3]&&(a=a[2]+256*(a[1]+256*a[0])))){a="OpenLayers_Feature_Vector_"+(a-1+this.hitOverflow);try{b=this.features[a][0]}catch(c){}}return b},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0;b<a.length;++b)delete this.features[a[b].id];
+this.redraw()},redraw:function(){if(!this.locked){var a=this.root.height,b=this.root.width;this.canvas.clearRect(0,0,b,a);this.hitDetection&&this.hitContext.clearRect(0,0,b,a);var a=[],c,d,e=this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent(),f;for(f in this.features)this.features.hasOwnProperty(f)&&(b=this.features[f][0],c=b.geometry,this.calculateFeatureDx(c.getBounds(),e),d=this.features[f][1],this.drawGeometry(c,d,b.id),d.label&&a.push([b,d]));b=0;for(c=a.length;b<c;++b)f=
+a[b],this.drawText(f[0].geometry.getCentroid(),f[1])}},CLASS_NAME:"OpenLayers.Renderer.Canvas"});OpenLayers.Renderer.Canvas.LABEL_ALIGN={l:"left",r:"right",t:"top",b:"bottom"};OpenLayers.Renderer.Canvas.LABEL_FACTOR={l:0,r:-1,t:0,b:-1};OpenLayers.Renderer.Canvas.drawImageScaleFactor=null;OpenLayers.Format.OSM=OpenLayers.Class(OpenLayers.Format.XML,{checkTags:!1,interestingTagsExclude:null,areaTags:null,initialize:function(a){var b={interestingTagsExclude:"source source_ref source:ref history attribution created_by".split(" "),areaTags:"area building leisure tourism ruins historic landuse military natural sport".split(" ")},b=OpenLayers.Util.extend(b,a),c={};for(a=0;a<b.interestingTagsExclude.length;a++)c[b.interestingTagsExclude[a]]=!0;b.interestingTagsExclude=c;c={};for(a=0;a<b.areaTags.length;a++)c[b.areaTags[a]]=
+!0;b.areaTags=c;this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[b])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=this.getNodes(a),c=this.getWays(a);a=Array(c.length);for(var d=0;d<c.length;d++){for(var e=Array(c[d].nodes.length),f=this.isWayArea(c[d])?1:0,g=0;g<c[d].nodes.length;g++){var h=b[c[d].nodes[g]],k=new OpenLayers.Geometry.Point(h.lon,h.lat);k.osm_id=parseInt(c[d].nodes[g]);
+e[g]=k;h.used=!0}h=null;h=f?new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(e)):new OpenLayers.Geometry.LineString(e);this.internalProjection&&this.externalProjection&&h.transform(this.externalProjection,this.internalProjection);e=new OpenLayers.Feature.Vector(h,c[d].tags);e.osm_id=parseInt(c[d].id);e.fid="way."+e.osm_id;a[d]=e}for(var l in b){h=b[l];if(!h.used||this.checkTags){c=null;if(this.checkTags){c=this.getTags(h.node,!0);if(h.used&&!c[1])continue;c=c[0]}else c=this.getTags(h.node);
+e=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(h.lon,h.lat),c);this.internalProjection&&this.externalProjection&&e.geometry.transform(this.externalProjection,this.internalProjection);e.osm_id=parseInt(l);e.fid="node."+e.osm_id;a.push(e)}h.node=null}return a},getNodes:function(a){a=a.getElementsByTagName("node");for(var b={},c=0;c<a.length;c++){var d=a[c],e=d.getAttribute("id");b[e]={lat:d.getAttribute("lat"),lon:d.getAttribute("lon"),node:d}}return b},getWays:function(a){a=a.getElementsByTagName("way");
+for(var b=[],c=0;c<a.length;c++){var d=a[c],e={id:d.getAttribute("id")};e.tags=this.getTags(d);d=d.getElementsByTagName("nd");e.nodes=Array(d.length);for(var f=0;f<d.length;f++)e.nodes[f]=d[f].getAttribute("ref");b.push(e)}return b},getTags:function(a,b){for(var c=a.getElementsByTagName("tag"),d={},e=!1,f=0;f<c.length;f++){var g=c[f].getAttribute("k");d[g]=c[f].getAttribute("v");b&&(this.interestingTagsExclude[g]||(e=!0))}return b?[d,e]:d},isWayArea:function(a){var b=!1,c=!1;a.nodes[0]==a.nodes[a.nodes.length-
+1]&&(b=!0);if(this.checkTags)for(var d in a.tags)if(this.areaTags[d]){c=!0;break}return b&&(this.checkTags?c:!0)},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);this.osm_id=1;this.created_nodes={};var b=this.createElementNS(null,"osm");b.setAttribute("version","0.5");b.setAttribute("generator","OpenLayers "+OpenLayers.VERSION_NUMBER);for(var c=a.length-1;0<=c;c--)for(var d=this.createFeatureNodes(a[c]),e=0;e<d.length;e++)b.appendChild(d[e]);return OpenLayers.Format.XML.prototype.write.apply(this,
+[b])},createFeatureNodes:function(a){var b=[],c=a.geometry.CLASS_NAME,c=c.substring(c.lastIndexOf(".")+1),c=c.toLowerCase();(c=this.createXML[c])&&(b=c.apply(this,[a]));return b},createXML:{point:function(a){var b=null,c=a.geometry?a.geometry:a;this.internalProjection&&this.externalProjection&&(c=c.clone(),c.transform(this.internalProjection,this.externalProjection));var d=!1;a.osm_id?(b=a.osm_id,this.created_nodes[b]&&(d=!0)):(b=-this.osm_id,this.osm_id++);var e=d?this.created_nodes[b]:this.createElementNS(null,
+"node");this.created_nodes[b]=e;e.setAttribute("id",b);e.setAttribute("lon",c.x);e.setAttribute("lat",c.y);a.attributes&&this.serializeTags(a,e);this.setState(a,e);return d?[]:[e]},linestring:function(a){var b,c=[],d=a.geometry;a.osm_id?b=a.osm_id:(b=-this.osm_id,this.osm_id++);var e=this.createElementNS(null,"way");e.setAttribute("id",b);for(b=0;b<d.components.length;b++){var f=this.createXML.point.apply(this,[d.components[b]]);if(f.length){var f=f[0],g=f.getAttribute("id");c.push(f)}else g=d.components[b].osm_id,
+f=this.created_nodes[g];this.setState(a,f);f=this.createElementNS(null,"nd");f.setAttribute("ref",g);e.appendChild(f)}this.serializeTags(a,e);c.push(e);return c},polygon:function(a){var b=OpenLayers.Util.extend({area:"yes"},a.attributes),b=new OpenLayers.Feature.Vector(a.geometry.components[0],b);b.osm_id=a.osm_id;return this.createXML.linestring.apply(this,[b])}},serializeTags:function(a,b){for(var c in a.attributes){var d=this.createElementNS(null,"tag");d.setAttribute("k",c);d.setAttribute("v",
+a.attributes[c]);b.appendChild(d)}},setState:function(a,b){if(a.state){var c=null;switch(a.state){case OpenLayers.State.UPDATE:case OpenLayers.State.DELETE:c="delete"}c&&b.setAttribute("action",c)}},CLASS_NAME:"OpenLayers.Format.OSM"});OpenLayers.Handler.Keyboard=OpenLayers.Class(OpenLayers.Handler,{KEY_EVENTS:["keydown","keyup"],eventListener:null,observeElement:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.eventListener=OpenLayers.Function.bindAsEventListener(this.handleKeyEvent,this)},destroy:function(){this.deactivate();this.eventListener=null;OpenLayers.Handler.prototype.destroy.apply(this,arguments)},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,
+arguments)){this.observeElement=this.observeElement||document;for(var a=0,b=this.KEY_EVENTS.length;a<b;a++)OpenLayers.Event.observe(this.observeElement,this.KEY_EVENTS[a],this.eventListener);return!0}return!1},deactivate:function(){var a=!1;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){for(var a=0,b=this.KEY_EVENTS.length;a<b;a++)OpenLayers.Event.stopObserving(this.observeElement,this.KEY_EVENTS[a],this.eventListener);a=!0}return a},handleKeyEvent:function(a){this.checkModifiers(a)&&
+this.callback(a.type,[a])},CLASS_NAME:"OpenLayers.Handler.Keyboard"});OpenLayers.Control.ModifyFeature=OpenLayers.Class(OpenLayers.Control,{documentDrag:!1,geometryTypes:null,clickout:!0,toggle:!0,standalone:!1,layer:null,feature:null,vertex:null,vertices:null,virtualVertices:null,handlers:null,deleteCodes:null,virtualStyle:null,vertexRenderIntent:null,mode:null,createVertices:!0,modified:!1,radiusHandle:null,dragHandle:null,onModificationStart:function(){},onModification:function(){},onModificationEnd:function(){},initialize:function(a,b){b=b||{};this.layer=a;this.vertices=
+[];this.virtualVertices=[];this.virtualStyle=OpenLayers.Util.extend({},this.layer.style||this.layer.styleMap.createSymbolizer(null,b.vertexRenderIntent));this.virtualStyle.fillOpacity=0.3;this.virtualStyle.strokeOpacity=0.3;this.deleteCodes=[46,68];this.mode=OpenLayers.Control.ModifyFeature.RESHAPE;OpenLayers.Control.prototype.initialize.apply(this,[b]);OpenLayers.Util.isArray(this.deleteCodes)||(this.deleteCodes=[this.deleteCodes]);var c={documentDrag:this.documentDrag,stopDown:!1};this.handlers=
+{keyboard:new OpenLayers.Handler.Keyboard(this,{keydown:this.handleKeypress}),drag:new OpenLayers.Handler.Drag(this,{down:function(a){this.vertex=null;(a=this.layer.getFeatureFromEvent(this.handlers.drag.evt))?this.dragStart(a):this.clickout&&(this._unselect=this.feature)},move:function(a){delete this._unselect;this.vertex&&this.dragVertex(this.vertex,a)},up:function(){this.handlers.drag.stopDown=!1;this._unselect&&(this.unselectFeature(this._unselect),delete this._unselect)},done:function(a){this.vertex&&
+this.dragComplete(this.vertex)}},c)}},destroy:function(){this.map&&this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this});this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[])},activate:function(){this.moveLayerToTop();this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this});return this.handlers.keyboard.activate()&&this.handlers.drag.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments)},
+deactivate:function(){var a=!1;OpenLayers.Control.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),this.layer.removeFeatures(this.vertices,{silent:!0}),this.layer.removeFeatures(this.virtualVertices,{silent:!0}),this.vertices=[],this.handlers.drag.deactivate(),this.handlers.keyboard.deactivate(),(a=this.feature)&&(a.geometry&&a.layer)&&this.unselectFeature(a),a=!0);return a},beforeSelectFeature:function(a){return this.layer.events.triggerEvent("beforefeaturemodified",
+{feature:a})},selectFeature:function(a){if(!(this.feature===a||this.geometryTypes&&-1==OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME))){!1!==this.beforeSelectFeature(a)&&(this.feature&&this.unselectFeature(this.feature),this.feature=a,this.layer.selectedFeatures.push(a),this.layer.drawFeature(a,"select"),this.modified=!1,this.resetVertices(),this.onModificationStart(this.feature));var b=a.modified;!a.geometry||b&&b.geometry||(this._originalGeometry=a.geometry.clone())}},unselectFeature:function(a){this.layer.removeFeatures(this.vertices,
+{silent:!0});this.vertices=[];this.layer.destroyFeatures(this.virtualVertices,{silent:!0});this.virtualVertices=[];this.dragHandle&&(this.layer.destroyFeatures([this.dragHandle],{silent:!0}),delete this.dragHandle);this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),delete this.radiusHandle);this.layer.drawFeature(this.feature,"default");this.feature=null;OpenLayers.Util.removeItem(this.layer.selectedFeatures,a);this.onModificationEnd(a);this.layer.events.triggerEvent("afterfeaturemodified",
+{feature:a,modified:this.modified});this.modified=!1},dragStart:function(a){var b="OpenLayers.Geometry.Point"==a.geometry.CLASS_NAME;this.standalone||(a._sketch||!b)&&a._sketch||(this.toggle&&this.feature===a&&(this._unselect=a),this.selectFeature(a));if(a._sketch||b)this.vertex=a,this.handlers.drag.stopDown=!0},dragVertex:function(a,b){var c=this.map.getLonLatFromViewPortPx(b),d=a.geometry;d.move(c.lon-d.x,c.lat-d.y);this.modified=!0;"OpenLayers.Geometry.Point"==this.feature.geometry.CLASS_NAME?
+this.layer.events.triggerEvent("vertexmodified",{vertex:a.geometry,feature:this.feature,pixel:b}):(a._index?(a.geometry.parent.addComponent(a.geometry,a._index),delete a._index,OpenLayers.Util.removeItem(this.virtualVertices,a),this.vertices.push(a)):a==this.dragHandle?(this.layer.removeFeatures(this.vertices,{silent:!0}),this.vertices=[],this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),this.radiusHandle=null)):a!==this.radiusHandle&&this.layer.events.triggerEvent("vertexmodified",
+{vertex:a.geometry,feature:this.feature,pixel:b}),0<this.virtualVertices.length&&(this.layer.destroyFeatures(this.virtualVertices,{silent:!0}),this.virtualVertices=[]),this.layer.drawFeature(this.feature,this.standalone?void 0:"select"));this.layer.drawFeature(a)},dragComplete:function(a){this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature})},setFeatureState:function(){if(this.feature.state!=OpenLayers.State.INSERT&&
+this.feature.state!=OpenLayers.State.DELETE&&(this.feature.state=OpenLayers.State.UPDATE,this.modified&&this._originalGeometry)){var a=this.feature;a.modified=OpenLayers.Util.extend(a.modified,{geometry:this._originalGeometry});delete this._originalGeometry}},resetVertices:function(){0<this.vertices.length&&(this.layer.removeFeatures(this.vertices,{silent:!0}),this.vertices=[]);0<this.virtualVertices.length&&(this.layer.removeFeatures(this.virtualVertices,{silent:!0}),this.virtualVertices=[]);this.dragHandle&&
+(this.layer.destroyFeatures([this.dragHandle],{silent:!0}),this.dragHandle=null);this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),this.radiusHandle=null);this.feature&&"OpenLayers.Geometry.Point"!=this.feature.geometry.CLASS_NAME&&(this.mode&OpenLayers.Control.ModifyFeature.DRAG&&this.collectDragHandle(),this.mode&(OpenLayers.Control.ModifyFeature.ROTATE|OpenLayers.Control.ModifyFeature.RESIZE)&&this.collectRadiusHandle(),this.mode&OpenLayers.Control.ModifyFeature.RESHAPE&&
+(this.mode&OpenLayers.Control.ModifyFeature.RESIZE||this.collectVertices()))},handleKeypress:function(a){var b=a.keyCode;this.feature&&-1!=OpenLayers.Util.indexOf(this.deleteCodes,b)&&(b=this.layer.getFeatureFromEvent(this.handlers.drag.evt))&&(-1!=OpenLayers.Util.indexOf(this.vertices,b)&&!this.handlers.drag.dragging&&b.geometry.parent)&&(b.geometry.parent.removeComponent(b.geometry),this.layer.events.triggerEvent("vertexremoved",{vertex:b.geometry,feature:this.feature,pixel:a.xy}),this.layer.drawFeature(this.feature,
+this.standalone?void 0:"select"),this.modified=!0,this.resetVertices(),this.setFeatureState(),this.onModification(this.feature),this.layer.events.triggerEvent("featuremodified",{feature:this.feature}))},collectVertices:function(){function a(c){var d,e,f;if("OpenLayers.Geometry.Point"==c.CLASS_NAME)e=new OpenLayers.Feature.Vector(c),e._sketch=!0,e.renderIntent=b.vertexRenderIntent,b.vertices.push(e);else{f=c.components.length;"OpenLayers.Geometry.LinearRing"==c.CLASS_NAME&&(f-=1);for(d=0;d<f;++d)e=
+c.components[d],"OpenLayers.Geometry.Point"==e.CLASS_NAME?(e=new OpenLayers.Feature.Vector(e),e._sketch=!0,e.renderIntent=b.vertexRenderIntent,b.vertices.push(e)):a(e);if(b.createVertices&&"OpenLayers.Geometry.MultiPoint"!=c.CLASS_NAME)for(d=0,f=c.components.length;d<f-1;++d){e=c.components[d];var g=c.components[d+1];"OpenLayers.Geometry.Point"==e.CLASS_NAME&&"OpenLayers.Geometry.Point"==g.CLASS_NAME&&(e=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point((e.x+g.x)/2,(e.y+g.y)/2),null,b.virtualStyle),
+e.geometry.parent=c,e._index=d+1,e._sketch=!0,b.virtualVertices.push(e))}}}this.vertices=[];this.virtualVertices=[];var b=this;a.call(this,this.feature.geometry);this.layer.addFeatures(this.virtualVertices,{silent:!0});this.layer.addFeatures(this.vertices,{silent:!0})},collectDragHandle:function(){var a=this.feature.geometry,b=a.getBounds().getCenterLonLat(),b=new OpenLayers.Geometry.Point(b.lon,b.lat),c=new OpenLayers.Feature.Vector(b);b.move=function(b,c){OpenLayers.Geometry.Point.prototype.move.call(this,
+b,c);a.move(b,c)};c._sketch=!0;this.dragHandle=c;this.dragHandle.renderIntent=this.vertexRenderIntent;this.layer.addFeatures([this.dragHandle],{silent:!0})},collectRadiusHandle:function(){var a=this.feature.geometry,b=a.getBounds(),c=b.getCenterLonLat(),d=new OpenLayers.Geometry.Point(c.lon,c.lat),b=new OpenLayers.Geometry.Point(b.right,b.bottom),c=new OpenLayers.Feature.Vector(b),e=this.mode&OpenLayers.Control.ModifyFeature.RESIZE,f=this.mode&OpenLayers.Control.ModifyFeature.RESHAPE,g=this.mode&
+OpenLayers.Control.ModifyFeature.ROTATE;b.move=function(b,c){OpenLayers.Geometry.Point.prototype.move.call(this,b,c);var l=this.x-d.x,m=this.y-d.y,n=l-b,p=m-c;if(g){var q=Math.atan2(p,n),q=Math.atan2(m,l)-q,q=q*(180/Math.PI);a.rotate(q,d)}if(e){var r;f?(m/=p,r=l/n/m):(n=Math.sqrt(n*n+p*p),m=Math.sqrt(l*l+m*m)/n);a.resize(m,d,r)}};c._sketch=!0;this.radiusHandle=c;this.radiusHandle.renderIntent=this.vertexRenderIntent;this.layer.addFeatures([this.radiusHandle],{silent:!0})},setMap:function(a){this.handlers.drag.setMap(a);
+OpenLayers.Control.prototype.setMap.apply(this,arguments)},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this.layer.setZIndex(a)},moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Control.ModifyFeature"});
+OpenLayers.Control.ModifyFeature.RESHAPE=1;OpenLayers.Control.ModifyFeature.RESIZE=2;OpenLayers.Control.ModifyFeature.ROTATE=4;OpenLayers.Control.ModifyFeature.DRAG=8;OpenLayers.Layer.Bing=OpenLayers.Class(OpenLayers.Layer.XYZ,{key:null,serverResolutions:[156543.03390625,78271.516953125,39135.7584765625,19567.87923828125,9783.939619140625,4891.9698095703125,2445.9849047851562,1222.9924523925781,611.4962261962891,305.74811309814453,152.87405654907226,76.43702827453613,38.218514137268066,19.109257068634033,9.554628534317017,4.777314267158508,2.388657133579254,1.194328566789627,0.5971642833948135,0.29858214169740677,0.14929107084870338,0.07464553542435169],attributionTemplate:'<span class="olBingAttribution ${type}"><div><a target="_blank" href="http://www.bing.com/maps/"><img src="${logo}" /></a></div>${copyrights}<a style="white-space: nowrap" target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a></span>',
+metadata:null,protocolRegex:/^http:/i,type:"Road",culture:"en-US",metadataParams:null,tileOptions:null,protocol:~window.location.href.indexOf("http")?"":"http:",initialize:function(a){a=OpenLayers.Util.applyDefaults({sphericalMercator:!0},a);OpenLayers.Layer.XYZ.prototype.initialize.apply(this,[a.name||"Bing "+(a.type||this.type),null,a]);this.tileOptions=OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options.tileOptions);this.loadMetadata()},loadMetadata:function(){this._callbackId=
+"_callback_"+this.id.replace(/\./g,"_");window[this._callbackId]=OpenLayers.Function.bind(OpenLayers.Layer.Bing.processMetadata,this);var a=OpenLayers.Util.applyDefaults({key:this.key,jsonp:this._callbackId,include:"ImageryProviders"},this.metadataParams),a=this.protocol+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+this.type+"?"+OpenLayers.Util.getParameterString(a),b=document.createElement("script");b.type="text/javascript";b.src=a;b.id=this._callbackId;document.getElementsByTagName("head")[0].appendChild(b)},
+initLayer:function(){var a=this.metadata.resourceSets[0].resources[0],b=a.imageUrl.replace("{quadkey}","${quadkey}"),b=b.replace("{culture}",this.culture),b=b.replace(this.protocolRegex,this.protocol);this.url=[];for(var c=0;c<a.imageUrlSubdomains.length;++c)this.url.push(b.replace("{subdomain}",a.imageUrlSubdomains[c]));this.addOptions({maxResolution:Math.min(this.serverResolutions[a.zoomMin],this.maxResolution||Number.POSITIVE_INFINITY),numZoomLevels:Math.min(a.zoomMax+1-a.zoomMin,this.numZoomLevels)},
+!0);this.isBaseLayer||this.redraw();this.updateAttribution()},getURL:function(a){if(this.url){var b=this.getXYZ(a);a=b.x;for(var c=b.y,b=b.z,d=[],e=b;0<e;--e){var f="0",g=1<<e-1;0!=(a&g)&&f++;0!=(c&g)&&(f++,f++);d.push(f)}d=d.join("");a=this.selectUrl(""+a+c+b,this.url);return OpenLayers.String.format(a,{quadkey:d})}},updateAttribution:function(){var a=this.metadata;if(a.resourceSets&&this.map&&this.map.center){var b=a.resourceSets[0].resources[0],c=this.map.getExtent().transform(this.map.getProjectionObject(),
+new OpenLayers.Projection("EPSG:4326")),d=b.imageryProviders||[],e=OpenLayers.Util.indexOf(this.serverResolutions,this.getServerResolution()),b="",f,g,h,k,l,m,n;g=0;for(h=d.length;g<h;++g)for(f=d[g],k=0,l=f.coverageAreas.length;k<l;++k)n=f.coverageAreas[k],m=OpenLayers.Bounds.fromArray(n.bbox,!0),c.intersectsBounds(m)&&(e<=n.zoomMax&&e>=n.zoomMin)&&(b+=f.attribution+" ");a=a.brandLogoUri.replace(this.protocolRegex,this.protocol);this.attribution=OpenLayers.String.format(this.attributionTemplate,{type:this.type.toLowerCase(),
+logo:a,copyrights:b});this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"attribution"})}},setMap:function(){OpenLayers.Layer.XYZ.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.updateAttribution)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Bing(this.options));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},destroy:function(){this.map&&this.map.events.unregister("moveend",this,this.updateAttribution);OpenLayers.Layer.XYZ.prototype.destroy.apply(this,
+arguments)},CLASS_NAME:"OpenLayers.Layer.Bing"});OpenLayers.Layer.Bing.processMetadata=function(a){this.metadata=a;this.initLayer();a=document.getElementById(this._callbackId);a.parentNode.removeChild(a);window[this._callbackId]=void 0;delete this._callbackId};OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:!0,initialize:function(a,b){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),select:new OpenLayers.Style(OpenLayers.Feature.Vector.style.select),temporary:new OpenLayers.Style(OpenLayers.Feature.Vector.style.temporary),"delete":new OpenLayers.Style(OpenLayers.Feature.Vector.style["delete"])};if(a instanceof OpenLayers.Style)this.styles["default"]=a,this.styles.select=a,this.styles.temporary=a,this.styles["delete"]=
+a;else if("object"==typeof a)for(var c in a)if(a[c]instanceof OpenLayers.Style)this.styles[c]=a[c];else if("object"==typeof a[c])this.styles[c]=new OpenLayers.Style(a[c]);else{this.styles["default"]=new OpenLayers.Style(a);this.styles.select=new OpenLayers.Style(a);this.styles.temporary=new OpenLayers.Style(a);this.styles["delete"]=new OpenLayers.Style(a);break}OpenLayers.Util.extend(this,b)},destroy:function(){for(var a in this.styles)this.styles[a].destroy();this.styles=null},createSymbolizer:function(a,
+b){a||(a=new OpenLayers.Feature.Vector);this.styles[b]||(b="default");a.renderIntent=b;var c={};this.extendDefault&&"default"!=b&&(c=this.styles["default"].createSymbolizer(a));return OpenLayers.Util.extend(c,this.styles[b].createSymbolizer(a))},addUniqueValueRules:function(a,b,c,d){var e=[],f;for(f in c)e.push(new OpenLayers.Rule({symbolizer:c[f],context:d,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:b,value:f})}));this.styles[a].addRules(e)},CLASS_NAME:"OpenLayers.StyleMap"});OpenLayers.Layer.Vector=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:!1,isFixed:!1,features:null,filter:null,selectedFeatures:null,unrenderedFeatures:null,reportError:!0,style:null,styleMap:null,strategies:null,protocol:null,renderers:["SVG","VML","Canvas"],renderer:null,rendererOptions:null,geometryType:null,drawn:!1,ratio:1,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);this.renderer&&this.renderer.supported()||this.assignRenderer();this.renderer&&this.renderer.supported()||
+(this.renderer=null,this.displayError());this.styleMap||(this.styleMap=new OpenLayers.StyleMap);this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies)for(var c=0,d=this.strategies.length;c<d;c++)this.strategies[c].setLayer(this)},destroy:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoDestroy&&a.destroy();this.strategies=null}this.protocol&&(this.protocol.autoDestroy&&this.protocol.destroy(),this.protocol=
+null);this.destroyFeatures();this.unrenderedFeatures=this.selectedFeatures=this.features=null;this.renderer&&this.renderer.destroy();this.drawn=this.geometryType=this.renderer=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Vector(this.name,this.getOptions()));a=OpenLayers.Layer.prototype.clone.apply(this,[a]);for(var b=this.features,c=b.length,d=Array(c),e=0;e<c;++e)d[e]=b[e].clone();a.features=d;return a},refresh:function(a){this.calculateInRange()&&
+this.visibility&&this.events.triggerEvent("refresh",a)},assignRenderer:function(){for(var a=0,b=this.renderers.length;a<b;a++){var c=this.renderers[a];if((c="function"==typeof c?c:OpenLayers.Renderer[c])&&c.prototype.supported()){this.renderer=new c(this.div,this.rendererOptions);break}}},displayError:function(){this.reportError&&OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{renderers:this.renderers.join("\n")}))},setMap:function(a){OpenLayers.Layer.prototype.setMap.apply(this,
+arguments);if(this.renderer){this.renderer.map=this.map;var b=this.map.getSize();b.w*=this.ratio;b.h*=this.ratio;this.renderer.setSize(b)}else this.map.removeLayer(this)},afterAdd:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.activate()}},removeMap:function(a){this.drawn=!1;if(this.strategies){var b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.deactivate()}},onMapResize:function(){OpenLayers.Layer.prototype.onMapResize.apply(this,
+arguments);var a=this.map.getSize();a.w*=this.ratio;a.h*=this.ratio;this.renderer.setSize(a)},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var d=!0;if(!c){this.renderer.root.style.visibility="hidden";var d=this.map.getSize(),e=d.w,d=d.h,e=e/2*this.ratio-e/2,d=d/2*this.ratio-d/2,e=e+this.map.layerContainerOriginPx.x,e=-Math.round(e),d=d+this.map.layerContainerOriginPx.y,d=-Math.round(d);this.div.style.left=e+"px";this.div.style.top=d+"px";e=this.map.getExtent().scale(this.ratio);
+d=this.renderer.setExtent(e,b);this.renderer.root.style.visibility="visible";!0===OpenLayers.IS_GECKO&&(this.div.scrollLeft=this.div.scrollLeft);if(!b&&d)for(var f in this.unrenderedFeatures)e=this.unrenderedFeatures[f],this.drawFeature(e)}if(!this.drawn||b||!d)for(this.drawn=!0,f=0,d=this.features.length;f<d;f++)this.renderer.locked=f!==d-1,e=this.features[f],this.drawFeature(e)},display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);var b=this.div.style.display;b!=this.renderer.root.style.display&&
+(this.renderer.root.style.display=b)},addFeatures:function(a,b){OpenLayers.Util.isArray(a)||(a=[a]);var c=!b||!b.silent;if(c){var d={features:a};if(!1===this.events.triggerEvent("beforefeaturesadded",d))return;a=d.features}for(var d=[],e=0,f=a.length;e<f;e++){this.renderer.locked=e!=a.length-1?!0:!1;var g=a[e];if(this.geometryType&&!(g.geometry instanceof this.geometryType))throw new TypeError("addFeatures: component should be an "+this.geometryType.prototype.CLASS_NAME);g.layer=this;!g.style&&this.style&&
+(g.style=OpenLayers.Util.extend({},this.style));if(c){if(!1===this.events.triggerEvent("beforefeatureadded",{feature:g}))continue;this.preFeatureInsert(g)}d.push(g);this.features.push(g);this.drawFeature(g);c&&(this.events.triggerEvent("featureadded",{feature:g}),this.onFeatureInsert(g))}c&&this.events.triggerEvent("featuresadded",{features:d})},removeFeatures:function(a,b){if(a&&0!==a.length){if(a===this.features)return this.removeAllFeatures(b);OpenLayers.Util.isArray(a)||(a=[a]);a===this.selectedFeatures&&
+(a=a.slice());var c=!b||!b.silent;c&&this.events.triggerEvent("beforefeaturesremoved",{features:a});for(var d=a.length-1;0<=d;d--){this.renderer.locked=0!=d&&a[d-1].geometry?!0:!1;var e=a[d];delete this.unrenderedFeatures[e.id];c&&this.events.triggerEvent("beforefeatureremoved",{feature:e});this.features=OpenLayers.Util.removeItem(this.features,e);e.layer=null;e.geometry&&this.renderer.eraseFeatures(e);-1!=OpenLayers.Util.indexOf(this.selectedFeatures,e)&&OpenLayers.Util.removeItem(this.selectedFeatures,
+e);c&&this.events.triggerEvent("featureremoved",{feature:e})}c&&this.events.triggerEvent("featuresremoved",{features:a})}},removeAllFeatures:function(a){a=!a||!a.silent;var b=this.features;a&&this.events.triggerEvent("beforefeaturesremoved",{features:b});for(var c,d=b.length-1;0<=d;d--)c=b[d],a&&this.events.triggerEvent("beforefeatureremoved",{feature:c}),c.layer=null,a&&this.events.triggerEvent("featureremoved",{feature:c});this.renderer.clear();this.features=[];this.unrenderedFeatures={};this.selectedFeatures=
+[];a&&this.events.triggerEvent("featuresremoved",{features:b})},destroyFeatures:function(a,b){void 0==a&&(a=this.features);if(a){this.removeFeatures(a,b);for(var c=a.length-1;0<=c;c--)a[c].destroy()}},drawFeature:function(a,b){if(this.drawn){if("object"!=typeof b){b||a.state!==OpenLayers.State.DELETE||(b="delete");var c=b||a.renderIntent;(b=a.style||this.style)||(b=this.styleMap.createSymbolizer(a,c))}c=this.renderer.drawFeature(a,b);!1===c||null===c?this.unrenderedFeatures[a.id]=a:delete this.unrenderedFeatures[a.id]}},
+eraseFeatures:function(a){this.renderer.eraseFeatures(a)},getFeatureFromEvent:function(a){if(!this.renderer)throw Error("getFeatureFromEvent called on layer with no renderer. This usually means you destroyed a layer, but not some handler which is associated with it.");var b=null;(a=this.renderer.getFeatureIdFromEvent(a))&&(b="string"===typeof a?this.getFeatureById(a):a);return b},getFeatureBy:function(a,b){for(var c=null,d=0,e=this.features.length;d<e;++d)if(this.features[d][a]==b){c=this.features[d];
+break}return c},getFeatureById:function(a){return this.getFeatureBy("id",a)},getFeatureByFid:function(a){return this.getFeatureBy("fid",a)},getFeaturesByAttribute:function(a,b){var c,d,e=this.features.length,f=[];for(c=0;c<e;c++)(d=this.features[c])&&d.attributes&&d.attributes[a]===b&&f.push(d);return f},onFeatureInsert:function(a){},preFeatureInsert:function(a){},getDataExtent:function(){var a=null,b=this.features;if(b&&0<b.length)for(var c=null,d=0,e=b.length;d<e;d++)if(c=b[d].geometry)null===a&&
+(a=new OpenLayers.Bounds),a.extend(c.getBounds());return a},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Layer.PointGrid=OpenLayers.Class(OpenLayers.Layer.Vector,{dx:null,dy:null,ratio:1.5,maxFeatures:250,rotation:0,origin:null,gridBounds:null,initialize:function(a){a=a||{};OpenLayers.Layer.Vector.prototype.initialize.apply(this,[a.name,a])},setMap:function(a){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);a.events.register("moveend",this,this.onMoveEnd)},removeMap:function(a){a.events.unregister("moveend",this,this.onMoveEnd);OpenLayers.Layer.Vector.prototype.removeMap.apply(this,
+arguments)},setRatio:function(a){this.ratio=a;this.updateGrid(!0)},setMaxFeatures:function(a){this.maxFeatures=a;this.updateGrid(!0)},setSpacing:function(a,b){this.dx=a;this.dy=b||a;this.updateGrid(!0)},setOrigin:function(a){this.origin=a;this.updateGrid(!0)},getOrigin:function(){this.origin||(this.origin=this.map.getExtent().getCenterLonLat());return this.origin},setRotation:function(a){this.rotation=a;this.updateGrid(!0)},onMoveEnd:function(){this.updateGrid()},getViewBounds:function(){var a=this.map.getExtent();
+if(this.rotation){var b=this.getOrigin(),b=new OpenLayers.Geometry.Point(b.lon,b.lat),a=a.toGeometry();a.rotate(-this.rotation,b);a=a.getBounds()}return a},updateGrid:function(a){if(a||this.invalidBounds()){var b=this.getViewBounds(),c=this.getOrigin();a=new OpenLayers.Geometry.Point(c.lon,c.lat);var d=b.getWidth(),e=b.getHeight(),f=d/e,g=Math.sqrt(this.dx*this.dy*this.maxFeatures/f),d=Math.min(d*this.ratio,g*f),e=Math.min(e*this.ratio,g),b=b.getCenterLonLat();this.gridBounds=new OpenLayers.Bounds(b.lon-
+d/2,b.lat-e/2,b.lon+d/2,b.lat+e/2);for(var b=Math.floor(e/this.dy),d=Math.floor(d/this.dx),e=c.lon+this.dx*Math.ceil((this.gridBounds.left-c.lon)/this.dx),c=c.lat+this.dy*Math.ceil((this.gridBounds.bottom-c.lat)/this.dy),g=Array(b*d),h,k=0;k<d;++k)for(var f=e+k*this.dx,l=0;l<b;++l)h=c+l*this.dy,h=new OpenLayers.Geometry.Point(f,h),this.rotation&&h.rotate(this.rotation,a),g[k*b+l]=new OpenLayers.Feature.Vector(h);this.destroyFeatures(this.features,{silent:!0});this.addFeatures(g,{silent:!0})}},invalidBounds:function(){return!this.gridBounds||
+!this.gridBounds.containsBounds(this.getViewBounds())},CLASS_NAME:"OpenLayers.Layer.PointGrid"});OpenLayers.Handler.MouseWheel=OpenLayers.Class(OpenLayers.Handler,{wheelListener:null,interval:0,maxDelta:Number.POSITIVE_INFINITY,delta:0,cumulative:!0,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.wheelListener=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this)},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.wheelListener=null},onWheelEvent:function(a){if(this.map&&this.checkModifiers(a)){for(var b=
+!1,c=!1,d=!1,e=OpenLayers.Event.element(a);null!=e&&!d&&!b;){if(!b)try{var f,b=(f=e.currentStyle?e.currentStyle.overflow:document.defaultView.getComputedStyle(e,null).getPropertyValue("overflow"))&&"auto"==f||"scroll"==f}catch(g){}if(!c&&(c=OpenLayers.Element.hasClass(e,"olScrollable"),!c))for(var d=0,h=this.map.layers.length;d<h;d++){var k=this.map.layers[d];if(e==k.div||e==k.pane){c=!0;break}}d=e==this.map.div;e=e.parentNode}if(!b&&d){if(c)if(b=0,a.wheelDelta?(b=a.wheelDelta,0===b%160&&(b*=0.75),
+b/=120):a.detail&&(b=-(a.detail/Math.abs(a.detail))),this.delta+=b,window.clearTimeout(this._timeoutId),this.interval&&Math.abs(this.delta)<this.maxDelta){var l=OpenLayers.Util.extend({},a);this._timeoutId=window.setTimeout(OpenLayers.Function.bind(function(){this.wheelZoom(l)},this),this.interval)}else this.wheelZoom(a);OpenLayers.Event.stop(a)}}},wheelZoom:function(a){var b=this.delta;this.delta=0;b&&(a.xy=this.map.events.getMousePosition(a),0>b?this.callback("down",[a,this.cumulative?Math.max(-this.maxDelta,
+b):-1]):this.callback("up",[a,this.cumulative?Math.min(this.maxDelta,b):1]))},activate:function(a){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var b=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",b);OpenLayers.Event.observe(window,"mousewheel",b);OpenLayers.Event.observe(document,"mousewheel",b);return!0}return!1},deactivate:function(a){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var b=this.wheelListener;OpenLayers.Event.stopObserving(window,
+"DOMMouseScroll",b);OpenLayers.Event.stopObserving(window,"mousewheel",b);OpenLayers.Event.stopObserving(document,"mousewheel",b);return!0}return!1},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Symbolizer=OpenLayers.Class({zIndex:0,initialize:function(a){OpenLayers.Util.extend(this,a)},clone:function(){return new (eval(this.CLASS_NAME))(OpenLayers.Util.extend({},this))},CLASS_NAME:"OpenLayers.Symbolizer"});OpenLayers.Symbolizer.Raster=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Raster"});OpenLayers.Rule=OpenLayers.Class({id:null,name:null,title:null,description:null,context:null,filter:null,elseFilter:!1,symbolizer:null,symbolizers:null,minScaleDenominator:null,maxScaleDenominator:null,initialize:function(a){this.symbolizer={};OpenLayers.Util.extend(this,a);this.symbolizers&&delete this.symbolizer;this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){for(var a in this.symbolizer)this.symbolizer[a]=null;this.symbolizer=null;delete this.symbolizers},evaluate:function(a){var b=
+this.getContext(a),c=!0;if(this.minScaleDenominator||this.maxScaleDenominator)var d=a.layer.map.getScale();this.minScaleDenominator&&(c=d>=OpenLayers.Style.createLiteral(this.minScaleDenominator,b));c&&this.maxScaleDenominator&&(c=d<OpenLayers.Style.createLiteral(this.maxScaleDenominator,b));c&&this.filter&&(c="OpenLayers.Filter.FeatureId"==this.filter.CLASS_NAME?this.filter.evaluate(a):this.filter.evaluate(b));return c},getContext:function(a){var b=this.context;b||(b=a.attributes||a.data);"function"==
+typeof this.context&&(b=this.context(a));return b},clone:function(){var a=OpenLayers.Util.extend({},this);if(this.symbolizers){var b=this.symbolizers.length;a.symbolizers=Array(b);for(var c=0;c<b;++c)a.symbolizers[c]=this.symbolizers[c].clone()}else{a.symbolizer={};for(var d in this.symbolizer)b=this.symbolizer[d],c=typeof b,"object"===c?a.symbolizer[d]=OpenLayers.Util.extend({},b):"string"===c&&(a.symbolizer[d]=b)}a.filter=this.filter&&this.filter.clone();a.context=this.context&&OpenLayers.Util.extend({},
+this.context);return new OpenLayers.Rule(a)},CLASS_NAME:"OpenLayers.Rule"});OpenLayers.Format.SLD=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{profile:null,defaultVersion:"1.0.0",stringifyOutput:!0,namedLayersAsArray:!1,CLASS_NAME:"OpenLayers.Format.SLD"});OpenLayers.Symbolizer.Polygon=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Polygon"});OpenLayers.Format.GML.v2=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",initialize:function(a){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[a])},readers:{gml:OpenLayers.Util.applyDefaults({outerBoundaryIs:function(a,b){var c={};this.readChildNodes(a,c);b.outer=c.components[0]},innerBoundaryIs:function(a,b){var c={};this.readChildNodes(a,c);b.inner.push(c.components[0])},Box:function(a,b){var c=
+{};this.readChildNodes(a,c);b.components||(b.components=[]);var d=c.points[0],c=c.points[1];b.components.push(new OpenLayers.Bounds(d.x,d.y,c.x,c.y))}},OpenLayers.Format.GML.Base.prototype.readers.gml),feature:OpenLayers.Format.GML.Base.prototype.readers.feature,wfs:OpenLayers.Format.GML.Base.prototype.readers.wfs},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"wfs:FeatureCollection":"gml:featureMember";a=this.writeNode(b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);
+return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:OpenLayers.Util.applyDefaults({Point:function(a){var b=this.createElementNSPlus("gml:Point");this.writeNode("coordinates",[a],b);return b},coordinates:function(a){for(var b=a.length,c=Array(b),d,e=0;e<b;++e)d=a[e],c[e]=this.xy?d.x+","+d.y:d.y+","+d.x,void 0!=d.z&&(c[e]+=","+d.z);return this.createElementNSPlus("gml:coordinates",{attributes:{decimal:".",cs:",",ts:" "},value:1==b?c[0]:c.join(" ")})},LineString:function(a){var b=
+this.createElementNSPlus("gml:LineString");this.writeNode("coordinates",a.components,b);return b},Polygon:function(a){var b=this.createElementNSPlus("gml:Polygon");this.writeNode("outerBoundaryIs",a.components[0],b);for(var c=1;c<a.components.length;++c)this.writeNode("innerBoundaryIs",a.components[c],b);return b},outerBoundaryIs:function(a){var b=this.createElementNSPlus("gml:outerBoundaryIs");this.writeNode("LinearRing",a,b);return b},innerBoundaryIs:function(a){var b=this.createElementNSPlus("gml:innerBoundaryIs");
+this.writeNode("LinearRing",a,b);return b},LinearRing:function(a){var b=this.createElementNSPlus("gml:LinearRing");this.writeNode("coordinates",a.components,b);return b},Box:function(a){var b=this.createElementNSPlus("gml:Box");this.writeNode("coordinates",[{x:a.left,y:a.bottom},{x:a.right,y:a.top}],b);this.srsName&&b.setAttribute("srsName",this.srsName);return b}},OpenLayers.Format.GML.Base.prototype.writers.gml),feature:OpenLayers.Format.GML.Base.prototype.writers.feature,wfs:OpenLayers.Format.GML.Base.prototype.writers.wfs},
+CLASS_NAME:"OpenLayers.Format.GML.v2"});OpenLayers.Format.Filter.v1_0_0=OpenLayers.Class(OpenLayers.Format.GML.v2,OpenLayers.Format.Filter.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",initialize:function(a){OpenLayers.Format.GML.v2.prototype.initialize.apply(this,[a])},readers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsNotEqualTo:function(a,
+b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLike:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(a,c);var d=a.getAttribute("wildCard"),e=a.getAttribute("singleChar"),f=a.getAttribute("escape");c.value2regex(d,e,f);b.filters.push(c)}},OpenLayers.Format.Filter.v1.prototype.readers.ogc),gml:OpenLayers.Format.GML.v2.prototype.readers.gml,
+feature:OpenLayers.Format.GML.v2.prototype.readers.feature},writers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsNotEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNotEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLike:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLike",
+{attributes:{wildCard:"*",singleChar:".",escape:"!"}});this.writeNode("PropertyName",a,b);this.writeNode("Literal",a.regex2value(),b);return b},BBOX:function(a){var b=this.createElementNSPlus("ogc:BBOX");a.property&&this.writeNode("PropertyName",a,b);var c=this.writeNode("gml:Box",a.value,b);a.projection&&c.setAttribute("srsName",a.projection);return b}},OpenLayers.Format.Filter.v1.prototype.writers.ogc),gml:OpenLayers.Format.GML.v2.prototype.writers.gml,feature:OpenLayers.Format.GML.v2.prototype.writers.feature},
+writeSpatial:function(a,b){var c=this.createElementNSPlus("ogc:"+b);this.writeNode("PropertyName",a,c);if(a.value instanceof OpenLayers.Filter.Function)this.writeNode("Function",a.value,c);else{var d;d=a.value instanceof OpenLayers.Geometry?this.writeNode("feature:_geometry",a.value).firstChild:this.writeNode("gml:Box",a.value);a.projection&&d.setAttribute("srsName",a.projection);c.appendChild(d)}return c},CLASS_NAME:"OpenLayers.Format.Filter.v1_0_0"});OpenLayers.Format.WFST.v1_0_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,OpenLayers.Format.WFST.v1,{version:"1.0.0",srsNameInQuery:!1,schemaLocations:{wfs:"http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"},initialize:function(a){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[a]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[a])},readNode:function(a,b,c){return OpenLayers.Format.GML.v2.prototype.readNode.apply(this,arguments)},readers:{wfs:OpenLayers.Util.applyDefaults({WFS_TransactionResponse:function(a,
+b){b.insertIds=[];b.success=!1;this.readChildNodes(a,b)},InsertResult:function(a,b){var c={fids:[]};this.readChildNodes(a,c);b.insertIds=b.insertIds.concat(c.fids)},TransactionResult:function(a,b){this.readChildNodes(a,b)},Status:function(a,b){this.readChildNodes(a,b)},SUCCESS:function(a,b){b.success=!0}},OpenLayers.Format.WFST.v1.prototype.readers.wfs),gml:OpenLayers.Format.GML.v2.prototype.readers.gml,feature:OpenLayers.Format.GML.v2.prototype.readers.feature,ogc:OpenLayers.Format.Filter.v1_0_0.prototype.readers.ogc},
+writers:{wfs:OpenLayers.Util.applyDefaults({Query:function(a){a=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName,srsNameInQuery:this.srsNameInQuery},a);var b=a.featurePrefix,c=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(b?b+":":"")+a.featureType}});a.srsNameInQuery&&a.srsName&&c.setAttribute("srsName",a.srsName);a.featureNS&&c.setAttribute("xmlns:"+b,a.featureNS);if(a.propertyNames)for(var b=0,d=a.propertyNames.length;b<
+d;b++)this.writeNode("ogc:PropertyName",{property:a.propertyNames[b]},c);a.filter&&(this.setFilterProperty(a.filter),this.writeNode("ogc:Filter",a.filter,c));return c}},OpenLayers.Format.WFST.v1.prototype.writers.wfs),gml:OpenLayers.Format.GML.v2.prototype.writers.gml,feature:OpenLayers.Format.GML.v2.prototype.writers.feature,ogc:OpenLayers.Format.Filter.v1_0_0.prototype.writers.ogc},CLASS_NAME:"OpenLayers.Format.WFST.v1_0_0"});OpenLayers.ElementsIndexer=OpenLayers.Class({maxZIndex:null,order:null,indices:null,compare:null,initialize:function(a){this.compare=a?OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER:OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;this.clear()},insert:function(a){this.exists(a)&&this.remove(a);var b=a.id;this.determineZIndex(a);for(var c=-1,d=this.order.length,e;1<d-c;)e=parseInt((c+d)/2),0<this.compare(this,a,OpenLayers.Util.getElement(this.order[e]))?c=e:d=e;this.order.splice(d,
+0,b);this.indices[b]=this.getZIndex(a);return this.getNextElement(d)},remove:function(a){a=a.id;var b=OpenLayers.Util.indexOf(this.order,a);0<=b&&(this.order.splice(b,1),delete this.indices[a],this.maxZIndex=0<this.order.length?this.indices[this.order[this.order.length-1]]:0)},clear:function(){this.order=[];this.indices={};this.maxZIndex=0},exists:function(a){return null!=this.indices[a.id]},getZIndex:function(a){return a._style.graphicZIndex},determineZIndex:function(a){var b=a._style.graphicZIndex;
+null==b?(b=this.maxZIndex,a._style.graphicZIndex=b):b>this.maxZIndex&&(this.maxZIndex=b)},getNextElement:function(a){a+=1;if(a<this.order.length){var b=OpenLayers.Util.getElement(this.order[a]);void 0==b&&(b=this.getNextElement(a));return b}return null},CLASS_NAME:"OpenLayers.ElementsIndexer"});
+OpenLayers.ElementsIndexer.IndexingMethods={Z_ORDER:function(a,b,c){b=a.getZIndex(b);var d=0;c&&(a=a.getZIndex(c),d=b-a);return d},Z_ORDER_DRAWING_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0==a&&(a=1);return a},Z_ORDER_Y_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0===a&&(b=c._boundsBottom-b._boundsBottom,a=0===b?1:b);return a}};
+OpenLayers.Renderer.Elements=OpenLayers.Class(OpenLayers.Renderer,{rendererRoot:null,root:null,vectorRoot:null,textRoot:null,xmlns:null,xOffset:0,indexer:null,BACKGROUND_ID_SUFFIX:"_background",LABEL_ID_SUFFIX:"_label",LABEL_OUTLINE_SUFFIX:"_outline",initialize:function(a,b){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.rendererRoot=this.createRenderRoot();this.root=this.createRoot("_root");this.vectorRoot=this.createRoot("_vroot");this.textRoot=this.createRoot("_troot");this.root.appendChild(this.vectorRoot);
+this.root.appendChild(this.textRoot);this.rendererRoot.appendChild(this.root);this.container.appendChild(this.rendererRoot);b&&(b.zIndexing||b.yOrdering)&&(this.indexer=new OpenLayers.ElementsIndexer(b.yOrdering))},destroy:function(){this.clear();this.xmlns=this.root=this.rendererRoot=null;OpenLayers.Renderer.prototype.destroy.apply(this,arguments)},clear:function(){var a,b=this.vectorRoot;if(b)for(;a=b.firstChild;)b.removeChild(a);if(b=this.textRoot)for(;a=b.firstChild;)b.removeChild(a);this.indexer&&
+this.indexer.clear()},setExtent:function(a,b){var c=OpenLayers.Renderer.prototype.setExtent.apply(this,arguments),d=this.getResolution();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var e,f=a.getWidth()/this.map.getExtent().getWidth();a=a.scale(1/f);f=this.map.getMaxExtent();f.right>a.left&&f.right<a.right?e=!0:f.left>a.left&&f.left<a.right&&(e=!1);if(e!==this.rightOfDateLine||b)c=!1,this.xOffset=!0===e?f.getWidth()/d:0;this.rightOfDateLine=e}return c},getNodeType:function(a,b){},drawGeometry:function(a,
+b,c){var d=a.CLASS_NAME,e=!0;if("OpenLayers.Geometry.Collection"==d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d){for(var d=0,f=a.components.length;d<f;d++)e=this.drawGeometry(a.components[d],b,c)&&e;return e}d=e=!1;"none"!=b.display&&(b.backgroundGraphic?this.redrawBackgroundNode(a.id,a,b,c):d=!0,e=this.redrawNode(a.id,a,b,c));!1==e&&(b=document.getElementById(a.id))&&(b._style.backgroundGraphic&&(d=!0),b.parentNode.removeChild(b));
+d&&(b=document.getElementById(a.id+this.BACKGROUND_ID_SUFFIX))&&b.parentNode.removeChild(b);return e},redrawNode:function(a,b,c,d){c=this.applyDefaultSymbolizer(c);a=this.nodeFactory(a,this.getNodeType(b,c));a._featureId=d;a._boundsBottom=b.getBounds().bottom;a._geometryClass=b.CLASS_NAME;a._style=c;b=this.drawGeometryNode(a,b,c);if(!1===b)return!1;a=b.node;this.indexer?(c=this.indexer.insert(a))?this.vectorRoot.insertBefore(a,c):this.vectorRoot.appendChild(a):a.parentNode!==this.vectorRoot&&this.vectorRoot.appendChild(a);
+this.postDraw(a);return b.complete},redrawBackgroundNode:function(a,b,c,d){c=OpenLayers.Util.extend({},c);c.externalGraphic=c.backgroundGraphic;c.graphicXOffset=c.backgroundXOffset;c.graphicYOffset=c.backgroundYOffset;c.graphicZIndex=c.backgroundGraphicZIndex;c.graphicWidth=c.backgroundWidth||c.graphicWidth;c.graphicHeight=c.backgroundHeight||c.graphicHeight;c.backgroundGraphic=null;c.backgroundXOffset=null;c.backgroundYOffset=null;c.backgroundGraphicZIndex=null;return this.redrawNode(a+this.BACKGROUND_ID_SUFFIX,
+b,c,null)},drawGeometryNode:function(a,b,c){c=c||a._style;var d={isFilled:void 0===c.fill?!0:c.fill,isStroked:void 0===c.stroke?!!c.strokeWidth:c.stroke},e;switch(b.CLASS_NAME){case "OpenLayers.Geometry.Point":!1===c.graphic&&(d.isFilled=!1,d.isStroked=!1);e=this.drawPoint(a,b);break;case "OpenLayers.Geometry.LineString":d.isFilled=!1;e=this.drawLineString(a,b);break;case "OpenLayers.Geometry.LinearRing":e=this.drawLinearRing(a,b);break;case "OpenLayers.Geometry.Polygon":e=this.drawPolygon(a,b);break;
+case "OpenLayers.Geometry.Rectangle":e=this.drawRectangle(a,b)}a._options=d;return!1!=e?{node:this.setStyle(a,c,d,b),complete:e}:!1},postDraw:function(a){},drawPoint:function(a,b){},drawLineString:function(a,b){},drawLinearRing:function(a,b){},drawPolygon:function(a,b){},drawRectangle:function(a,b){},drawCircle:function(a,b){},removeText:function(a){var b=document.getElementById(a+this.LABEL_ID_SUFFIX);b&&this.textRoot.removeChild(b);(a=document.getElementById(a+this.LABEL_OUTLINE_SUFFIX))&&this.textRoot.removeChild(a)},
+getFeatureIdFromEvent:function(a){var b=a.target,c=b&&b.correspondingUseElement;return(c?c:b||a.srcElement)._featureId},eraseGeometry:function(a,b){if("OpenLayers.Geometry.MultiPoint"==a.CLASS_NAME||"OpenLayers.Geometry.MultiLineString"==a.CLASS_NAME||"OpenLayers.Geometry.MultiPolygon"==a.CLASS_NAME||"OpenLayers.Geometry.Collection"==a.CLASS_NAME)for(var c=0,d=a.components.length;c<d;c++)this.eraseGeometry(a.components[c],b);else(c=OpenLayers.Util.getElement(a.id))&&c.parentNode&&(c.geometry&&(c.geometry.destroy(),
+c.geometry=null),c.parentNode.removeChild(c),this.indexer&&this.indexer.remove(c),c._style.backgroundGraphic&&(c=OpenLayers.Util.getElement(a.id+this.BACKGROUND_ID_SUFFIX))&&c.parentNode&&c.parentNode.removeChild(c))},nodeFactory:function(a,b){var c=OpenLayers.Util.getElement(a);c?this.nodeTypeCompare(c,b)||(c.parentNode.removeChild(c),c=this.nodeFactory(a,b)):c=this.createNode(b,a);return c},nodeTypeCompare:function(a,b){},createNode:function(a,b){},moveRoot:function(a){var b=this.root;a.root.parentNode==
+this.rendererRoot&&(b=a.root);b.parentNode.removeChild(b);a.rendererRoot.appendChild(b)},getRenderLayerId:function(){return this.root.parentNode.parentNode.id},isComplexSymbol:function(a){return"circle"!=a&&!!a},CLASS_NAME:"OpenLayers.Renderer.Elements"});OpenLayers.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,getParameters:function(a){a=a||window.location.href;var b=OpenLayers.Util.getParameters(a),c=a.indexOf("#");0<c&&(a="?"+a.substring(c+1,a.length),OpenLayers.Util.extend(b,OpenLayers.Util.getParameters(a)));return b},setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var b=0,c=this.map.controls.length;b<c;b++){var d=this.map.controls[b];if(d!=this&&
+"OpenLayers.Control.ArgParser"==d.CLASS_NAME){d.displayProjection!=this.displayProjection&&(this.displayProjection=d.displayProjection);break}}b==this.map.controls.length&&(b=this.getParameters(),b.layers&&(this.layers=b.layers,this.map.events.register("addlayer",this,this.configureLayers),this.configureLayers()),b.lat&&b.lon&&(this.center=new OpenLayers.LonLat(parseFloat(b.lon),parseFloat(b.lat)),b.zoom&&(this.zoom=parseFloat(b.zoom)),this.map.events.register("changebaselayer",this,this.setCenter),
+this.setCenter()))},setCenter:function(){this.map.baseLayer&&(this.map.events.unregister("changebaselayer",this,this.setCenter),this.displayProjection&&this.center.transform(this.displayProjection,this.map.getProjectionObject()),this.map.setCenter(this.center,this.zoom))},configureLayers:function(){if(this.layers.length==this.map.layers.length){this.map.events.unregister("addlayer",this,this.configureLayers);for(var a=0,b=this.layers.length;a<b;a++){var c=this.map.layers[a],d=this.layers.charAt(a);
+"B"==d?this.map.setBaseLayer(c):"T"!=d&&"F"!=d||c.setVisibility("T"==d)}}},CLASS_NAME:"OpenLayers.Control.ArgParser"});OpenLayers.Control.Permalink=OpenLayers.Class(OpenLayers.Control,{argParserClass:OpenLayers.Control.ArgParser,element:null,anchor:!1,base:"",displayProjection:null,initialize:function(a,b,c){null===a||"object"!=typeof a||OpenLayers.Util.isElement(a)?(OpenLayers.Control.prototype.initialize.apply(this,[c]),this.element=OpenLayers.Util.getElement(a),this.base=b||document.location.href):(this.base=document.location.href,OpenLayers.Control.prototype.initialize.apply(this,[a]),null!=this.element&&(this.element=
+OpenLayers.Util.getElement(this.element)))},destroy:function(){this.element&&this.element.parentNode==this.div&&(this.div.removeChild(this.element),this.element=null);this.map&&this.map.events.unregister("moveend",this,this.updateLink);OpenLayers.Control.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var b=0,c=this.map.controls.length;b<c;b++){var d=this.map.controls[b];if(d.CLASS_NAME==this.argParserClass.CLASS_NAME){d.displayProjection!=
+this.displayProjection&&(this.displayProjection=d.displayProjection);break}}b==this.map.controls.length&&this.map.addControl(new this.argParserClass({displayProjection:this.displayProjection}))},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.element||this.anchor||(this.element=document.createElement("a"),this.element.innerHTML=OpenLayers.i18n("Permalink"),this.element.href="",this.div.appendChild(this.element));this.map.events.on({moveend:this.updateLink,changelayer:this.updateLink,
+changebaselayer:this.updateLink,scope:this});this.updateLink();return this.div},updateLink:function(){var a=this.anchor?"#":"?",b=this.base,c=null;-1!=b.indexOf("#")&&!1==this.anchor&&(c=b.substring(b.indexOf("#"),b.length));-1!=b.indexOf(a)&&(b=b.substring(0,b.indexOf(a)));b=b.split("#")[0]+a+OpenLayers.Util.getParameterString(this.createParams());c&&(b+=c);this.anchor&&!this.element?window.location.href=b:this.element.href=b},createParams:function(a,b,c){a=a||this.map.getCenter();var d=OpenLayers.Util.getParameters(this.base);
+if(a)for(d.zoom=b||this.map.getZoom(),b=a.lat,a=a.lon,this.displayProjection&&(b=OpenLayers.Projection.transform({x:a,y:b},this.map.getProjectionObject(),this.displayProjection),a=b.x,b=b.y),d.lat=Math.round(1E5*b)/1E5,d.lon=Math.round(1E5*a)/1E5,c=c||this.map.layers,d.layers="",a=0,b=c.length;a<b;a++){var e=c[a];d.layers=e.isBaseLayer?d.layers+(e==this.map.baseLayer?"B":"0"):d.layers+(e.getVisibility()?"T":"F")}return d},CLASS_NAME:"OpenLayers.Control.Permalink"});OpenLayers.Layer.TMS=OpenLayers.Class(OpenLayers.Layer.Grid,{serviceVersion:"1.0.0",layername:null,type:null,isBaseLayer:!0,tileOrigin:null,serverResolutions:null,zoomOffset:0,initialize:function(a,b,c){var d=[];d.push(a,b,{},c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,d)},clone:function(a){null==a&&(a=new OpenLayers.Layer.TMS(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds(a);var b=this.getServerResolution(),
+c=Math.round((a.left-this.tileOrigin.lon)/(b*this.tileSize.w));a=Math.round((a.bottom-this.tileOrigin.lat)/(b*this.tileSize.h));b=this.getServerZoom();c=this.serviceVersion+"/"+this.layername+"/"+b+"/"+c+"/"+a+"."+this.type;a=this.url;OpenLayers.Util.isArray(a)&&(a=this.selectUrl(c,a));return a+c},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,this.map.maxExtent.bottom))},CLASS_NAME:"OpenLayers.Layer.TMS"});OpenLayers.Format.WCSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.0",CLASS_NAME:"OpenLayers.Format.WCSCapabilities"});OpenLayers.Format.WCSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.XML,{regExes:{trimSpace:/^\s*|\s*$/g,splitSpace:/\s+/},defaultPrefix:"wcs",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},CLASS_NAME:"OpenLayers.Format.WCSCapabilities.v1"});OpenLayers.Format.WCSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.WCSCapabilities.v1,{namespaces:{wcs:"http://www.opengis.net/wcs",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",ows:"http://www.opengis.net/ows"},errorProperty:"service",readers:{wcs:{WCS_Capabilities:function(a,b){this.readChildNodes(a,b)},Service:function(a,b){b.service={};this.readChildNodes(a,b.service)},name:function(a,b){b.name=this.getChildValue(a)},label:function(a,b){b.label=
+this.getChildValue(a)},keywords:function(a,b){b.keywords=[];this.readChildNodes(a,b.keywords)},keyword:function(a,b){b.push(this.getChildValue(a))},responsibleParty:function(a,b){b.responsibleParty={};this.readChildNodes(a,b.responsibleParty)},individualName:function(a,b){b.individualName=this.getChildValue(a)},organisationName:function(a,b){b.organisationName=this.getChildValue(a)},positionName:function(a,b){b.positionName=this.getChildValue(a)},contactInfo:function(a,b){b.contactInfo={};this.readChildNodes(a,
+b.contactInfo)},phone:function(a,b){b.phone={};this.readChildNodes(a,b.phone)},voice:function(a,b){b.voice=this.getChildValue(a)},facsimile:function(a,b){b.facsimile=this.getChildValue(a)},address:function(a,b){b.address={};this.readChildNodes(a,b.address)},deliveryPoint:function(a,b){b.deliveryPoint=this.getChildValue(a)},city:function(a,b){b.city=this.getChildValue(a)},postalCode:function(a,b){b.postalCode=this.getChildValue(a)},country:function(a,b){b.country=this.getChildValue(a)},electronicMailAddress:function(a,
+b){b.electronicMailAddress=this.getChildValue(a)},fees:function(a,b){b.fees=this.getChildValue(a)},accessConstraints:function(a,b){b.accessConstraints=this.getChildValue(a)},ContentMetadata:function(a,b){b.contentMetadata=[];this.readChildNodes(a,b.contentMetadata)},CoverageOfferingBrief:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)},name:function(a,b){b.name=this.getChildValue(a)},label:function(a,b){b.label=this.getChildValue(a)},lonLatEnvelope:function(a,b){var c=this.getElementsByTagNameNS(a,
+"http://www.opengis.net/gml","pos");if(2==c.length){var d={},e={};OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[c[0],d]);OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[c[1],e]);b.lonLatEnvelope={};b.lonLatEnvelope.srsName=a.getAttribute("srsName");b.lonLatEnvelope.min=d.points[0];b.lonLatEnvelope.max=e.points[0]}}}},CLASS_NAME:"OpenLayers.Format.WCSCapabilities.v1_0_0"});OpenLayers.Strategy.Fixed=OpenLayers.Class(OpenLayers.Strategy,{preload:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.apply(this,arguments);if(a)if(this.layer.events.on({refresh:this.load,scope:this}),!0==this.layer.visibility||this.preload)this.load();else this.layer.events.on({visibilitychanged:this.load,scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.layer.events.un({refresh:this.load,visibilitychanged:this.load,
+scope:this});return a},load:function(a){var b=this.layer;b.events.triggerEvent("loadstart",{filter:b.filter});b.protocol.read(OpenLayers.Util.applyDefaults({callback:this.merge,filter:b.filter,scope:this},a));b.events.un({visibilitychanged:this.load,scope:this})},merge:function(a){var b=this.layer;b.destroyFeatures();var c=a.features;if(c&&0<c.length){var d=b.projection,e=b.map.getProjectionObject();if(!e.equals(d))for(var f,g=0,h=c.length;g<h;++g)(f=c[g].geometry)&&f.transform(d,e);b.addFeatures(c)}b.events.triggerEvent("loadend",
+{response:a})},CLASS_NAME:"OpenLayers.Strategy.Fixed"});OpenLayers.Control.Zoom=OpenLayers.Class(OpenLayers.Control,{zoomInText:"+",zoomInId:"olZoomInLink",zoomOutText:"\u2212",zoomOutId:"olZoomOutLink",draw:function(){var a=OpenLayers.Control.prototype.draw.apply(this),b=this.getOrCreateLinks(a),c=b.zoomIn,b=b.zoomOut,d=this.map.events;b.parentNode!==a&&(d=this.events,d.attachToElement(b.parentNode));d.register("buttonclick",this,this.onZoomClick);this.zoomInLink=c;this.zoomOutLink=b;return a},getOrCreateLinks:function(a){var b=document.getElementById(this.zoomInId),
+c=document.getElementById(this.zoomOutId);b||(b=document.createElement("a"),b.href="#zoomIn",b.appendChild(document.createTextNode(this.zoomInText)),b.className="olControlZoomIn",a.appendChild(b));OpenLayers.Element.addClass(b,"olButton");c||(c=document.createElement("a"),c.href="#zoomOut",c.appendChild(document.createTextNode(this.zoomOutText)),c.className="olControlZoomOut",a.appendChild(c));OpenLayers.Element.addClass(c,"olButton");return{zoomIn:b,zoomOut:c}},onZoomClick:function(a){a=a.buttonElement;
+a===this.zoomInLink?this.map.zoomIn():a===this.zoomOutLink&&this.map.zoomOut()},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onZoomClick);delete this.zoomInLink;delete this.zoomOutLink;OpenLayers.Control.prototype.destroy.apply(this)},CLASS_NAME:"OpenLayers.Control.Zoom"});OpenLayers.Layer.PointTrack=OpenLayers.Class(OpenLayers.Layer.Vector,{dataFrom:null,styleFrom:null,addNodes:function(a,b){if(2>a.length)throw Error("At least two point features have to be added to create a line from");for(var c=Array(a.length-1),d,e,f,g=0,h=a.length;g<h;g++){d=a[g];f=d.geometry;if(!f)f=d.lonlat,f=new OpenLayers.Geometry.Point(f.lon,f.lat);else if("OpenLayers.Geometry.Point"!=f.CLASS_NAME)throw new TypeError("Only features with point geometries are supported.");if(0<g){d=null!=this.dataFrom?
+a[g+this.dataFrom].data||a[g+this.dataFrom].attributes:null;var k=null!=this.styleFrom?a[g+this.styleFrom].style:null;e=new OpenLayers.Geometry.LineString([e,f]);c[g-1]=new OpenLayers.Feature.Vector(e,d,k)}e=f}this.addFeatures(c,b)},CLASS_NAME:"OpenLayers.Layer.PointTrack"});OpenLayers.Layer.PointTrack.SOURCE_NODE=-1;OpenLayers.Layer.PointTrack.TARGET_NODE=0;OpenLayers.Layer.PointTrack.dataFrom={SOURCE_NODE:-1,TARGET_NODE:0};OpenLayers.Protocol.WFS=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Protocol.WFS.DEFAULTS);var b=OpenLayers.Protocol.WFS["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported WFS version: "+a.version;return new b(a)};
+OpenLayers.Protocol.WFS.fromWMSLayer=function(a,b){var c,d;c=a.params.LAYERS;c=(OpenLayers.Util.isArray(c)?c[0]:c).split(":");1<c.length&&(d=c[0]);c=c.pop();d={url:a.url,featureType:c,featurePrefix:d,srsName:a.projection&&a.projection.getCode()||a.map&&a.map.getProjectionObject().getCode(),version:"1.1.0"};return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(b,d))};OpenLayers.Protocol.WFS.DEFAULTS={version:"1.0.0"};OpenLayers.Layer.Markers=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:!1,markers:null,drawn:!1,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);this.markers=[]},destroy:function(){this.clearMarkers();this.markers=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},setOpacity:function(a){if(a!=this.opacity){this.opacity=a;a=0;for(var b=this.markers.length;a<b;a++)this.markers[a].setOpacity(this.opacity)}},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,
+arguments);if(b||!this.drawn){for(var d=0,e=this.markers.length;d<e;d++)this.drawMarker(this.markers[d]);this.drawn=!0}},addMarker:function(a){this.markers.push(a);1>this.opacity&&a.setOpacity(this.opacity);this.map&&this.map.getExtent()&&(a.map=this.map,this.drawMarker(a))},removeMarker:function(a){this.markers&&this.markers.length&&(OpenLayers.Util.removeItem(this.markers,a),a.erase())},clearMarkers:function(){if(null!=this.markers)for(;0<this.markers.length;)this.removeMarker(this.markers[0])},
+drawMarker:function(a){var b=this.map.getLayerPxFromLonLat(a.lonlat);null==b?a.display(!1):a.isDrawn()?a.icon&&a.icon.moveTo(b):(a=a.draw(b),this.div.appendChild(a))},getDataExtent:function(){var a=null;if(this.markers&&0<this.markers.length)for(var a=new OpenLayers.Bounds,b=0,c=this.markers.length;b<c;b++)a.extend(this.markers[b].lonlat);return a},CLASS_NAME:"OpenLayers.Layer.Markers"});OpenLayers.Control.Pan=OpenLayers.Class(OpenLayers.Control.Button,{slideFactor:50,slideRatio:null,direction:null,initialize:function(a,b){this.direction=a;this.CLASS_NAME+=this.direction;OpenLayers.Control.prototype.initialize.apply(this,[b])},trigger:function(){if(this.map){var a=OpenLayers.Function.bind(function(a){return this.slideRatio?this.map.getSize()[a]*this.slideRatio:this.slideFactor},this);switch(this.direction){case OpenLayers.Control.Pan.NORTH:this.map.pan(0,-a("h"));break;case OpenLayers.Control.Pan.SOUTH:this.map.pan(0,
+a("h"));break;case OpenLayers.Control.Pan.WEST:this.map.pan(-a("w"),0);break;case OpenLayers.Control.Pan.EAST:this.map.pan(a("w"),0)}}},CLASS_NAME:"OpenLayers.Control.Pan"});OpenLayers.Control.Pan.NORTH="North";OpenLayers.Control.Pan.SOUTH="South";OpenLayers.Control.Pan.EAST="East";OpenLayers.Control.Pan.WEST="West";OpenLayers.Format.CSWGetDomain=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Format.CSWGetDomain.DEFAULTS);var b=OpenLayers.Format.CSWGetDomain["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported CSWGetDomain version: "+a.version;return new b(a)};OpenLayers.Format.CSWGetDomain.DEFAULTS={version:"2.0.2"};OpenLayers.Format.CSWGetDomain.v2_0_2=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",csw:"http://www.opengis.net/cat/csw/2.0.2"},defaultPrefix:"csw",version:"2.0.2",schemaLocation:"http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",PropertyName:null,ParameterName:null,read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==
+a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{csw:{GetDomainResponse:function(a,b){this.readChildNodes(a,b)},DomainValues:function(a,b){OpenLayers.Util.isArray(b.DomainValues)||(b.DomainValues=[]);for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;this.readChildNodes(a,d);b.DomainValues.push(d)},PropertyName:function(a,b){b.PropertyName=this.getChildValue(a)},ParameterName:function(a,b){b.ParameterName=this.getChildValue(a)},ListOfValues:function(a,
+b){OpenLayers.Util.isArray(b.ListOfValues)||(b.ListOfValues=[]);this.readChildNodes(a,b.ListOfValues)},Value:function(a,b){for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;d.value=this.getChildValue(a);b.push({Value:d})},ConceptualScheme:function(a,b){b.ConceptualScheme={};this.readChildNodes(a,b.ConceptualScheme)},Name:function(a,b){b.Name=this.getChildValue(a)},Document:function(a,b){b.Document=this.getChildValue(a)},Authority:function(a,b){b.Authority=this.getChildValue(a)},
+RangeOfValues:function(a,b){b.RangeOfValues={};this.readChildNodes(a,b.RangeOfValues)},MinValue:function(a,b){for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;d.value=this.getChildValue(a);b.MinValue=d},MaxValue:function(a,b){for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]=c[e].nodeValue;d.value=this.getChildValue(a);b.MaxValue=d}}},write:function(a){a=this.writeNode("csw:GetDomain",a);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{csw:{GetDomain:function(a){var b=
+this.createElementNSPlus("csw:GetDomain",{attributes:{service:"CSW",version:this.version}});a.PropertyName||this.PropertyName?this.writeNode("csw:PropertyName",a.PropertyName||this.PropertyName,b):(a.ParameterName||this.ParameterName)&&this.writeNode("csw:ParameterName",a.ParameterName||this.ParameterName,b);this.readChildNodes(b,a);return b},PropertyName:function(a){return this.createElementNSPlus("csw:PropertyName",{value:a})},ParameterName:function(a){return this.createElementNSPlus("csw:ParameterName",
+{value:a})}}},CLASS_NAME:"OpenLayers.Format.CSWGetDomain.v2_0_2"});OpenLayers.Format.ArcXML.Features=OpenLayers.Class(OpenLayers.Format.XML,{read:function(a){return(new OpenLayers.Format.ArcXML).read(a).features.feature}});OpenLayers.Control.Snapping=OpenLayers.Class(OpenLayers.Control,{DEFAULTS:{tolerance:10,node:!0,edge:!0,vertex:!0},greedy:!0,precedence:["node","vertex","edge"],resolution:null,geoToleranceCache:null,layer:null,feature:null,point:null,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,[a]);this.options=a||{};this.options.layer&&this.setLayer(this.options.layer);a=OpenLayers.Util.extend({},this.options.defaults);this.defaults=OpenLayers.Util.applyDefaults(a,this.DEFAULTS);this.setTargets(this.options.targets);
+0===this.targets.length&&this.layer&&this.addTargetLayer(this.layer);this.geoToleranceCache={}},setLayer:function(a){this.active?(this.deactivate(),this.layer=a,this.activate()):this.layer=a},setTargets:function(a){this.targets=[];if(a&&a.length)for(var b,c=0,d=a.length;c<d;++c)b=a[c],b instanceof OpenLayers.Layer.Vector?this.addTargetLayer(b):this.addTarget(b)},addTargetLayer:function(a){this.addTarget({layer:a})},addTarget:function(a){a=OpenLayers.Util.applyDefaults(a,this.defaults);a.nodeTolerance=
+a.nodeTolerance||a.tolerance;a.vertexTolerance=a.vertexTolerance||a.tolerance;a.edgeTolerance=a.edgeTolerance||a.tolerance;this.targets.push(a)},removeTargetLayer:function(a){for(var b,c=this.targets.length-1;0<=c;--c)b=this.targets[c],b.layer===a&&this.removeTarget(b)},removeTarget:function(a){return OpenLayers.Util.removeItem(this.targets,a)},activate:function(){var a=OpenLayers.Control.prototype.activate.call(this);if(a&&this.layer&&this.layer.events)this.layer.events.on({sketchstarted:this.onSketchModified,
+sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});return a},deactivate:function(){var a=OpenLayers.Control.prototype.deactivate.call(this);a&&this.layer&&this.layer.events&&this.layer.events.un({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});this.point=this.feature=null;return a},onSketchModified:function(a){this.feature=a.feature;this.considerSnapping(a.vertex,a.vertex)},onVertexModified:function(a){this.feature=
+a.feature;var b=this.layer.map.getLonLatFromViewPortPx(a.pixel);this.considerSnapping(a.vertex,new OpenLayers.Geometry.Point(b.lon,b.lat))},considerSnapping:function(a,b){for(var c={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY,x:null,y:null},d=!1,e,f,g=0,h=this.targets.length;g<h;++g)if(f=this.targets[g],e=this.testTarget(f,b))if(this.greedy){c=e;c.target=f;d=!0;break}else if(e.rank<c.rank||e.rank===c.rank&&e.dist<c.dist)c=e,c.target=f,d=!0;d&&(!1!==this.events.triggerEvent("beforesnap",
+{point:a,x:c.x,y:c.y,distance:c.dist,layer:c.target.layer,snapType:this.precedence[c.rank]})?(a.x=c.x,a.y=c.y,this.point=a,this.events.triggerEvent("snap",{point:a,snapType:this.precedence[c.rank],layer:c.target.layer,distance:c.dist})):d=!1);this.point&&!d&&(a.x=b.x,a.y=b.y,this.point=null,this.events.triggerEvent("unsnap",{point:a}))},testTarget:function(a,b){var c=this.layer.map.getResolution();if("minResolution"in a&&c<a.minResolution||"maxResolution"in a&&c>=a.maxResolution)return null;for(var c=
+{node:this.getGeoTolerance(a.nodeTolerance,c),vertex:this.getGeoTolerance(a.vertexTolerance,c),edge:this.getGeoTolerance(a.edgeTolerance,c)},d=Math.max(c.node,c.vertex,c.edge),e={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY},f=!1,g=a.layer.features,h,k,l,m,n,p,q=this.precedence.length,r=new OpenLayers.LonLat(b.x,b.y),s=0,t=g.length;s<t;++s)if(h=g[s],h!==this.feature&&(!h._sketch&&h.state!==OpenLayers.State.DELETE&&(!a.filter||a.filter.evaluate(h)))&&h.atPoint(r,d,d))for(var u=0,v=Math.min(e.rank+
+1,q);u<v;++u)if(k=this.precedence[u],a[k])if("edge"===k){if(l=h.geometry.distanceTo(b,{details:!0}),n=l.distance,n<=c[k]&&n<e.dist){e={rank:u,dist:n,x:l.x0,y:l.y0};f=!0;break}}else{l=h.geometry.getVertices("node"===k);p=!1;for(var w=0,x=l.length;w<x;++w)m=l[w],n=m.distanceTo(b),n<=c[k]&&(u<e.rank||u===e.rank&&n<e.dist)&&(e={rank:u,dist:n,x:m.x,y:m.y},p=f=!0);if(p)break}return f?e:null},getGeoTolerance:function(a,b){b!==this.resolution&&(this.resolution=b,this.geoToleranceCache={});var c=this.geoToleranceCache[a];
+void 0===c&&(c=a*b,this.geoToleranceCache[a]=c);return c},destroy:function(){this.active&&this.deactivate();delete this.layer;delete this.targets;OpenLayers.Control.prototype.destroy.call(this)},CLASS_NAME:"OpenLayers.Control.Snapping"});OpenLayers.Format.OWSCommon.v1_1_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1,{namespaces:{ows:"http://www.opengis.net/ows/1.1",xlink:"http://www.w3.org/1999/xlink"},readers:{ows:OpenLayers.Util.applyDefaults({ExceptionReport:function(a,b){b.exceptionReport={version:a.getAttribute("version"),language:a.getAttribute("xml:lang"),exceptions:[]};this.readChildNodes(a,b.exceptionReport)},AllowedValues:function(a,b){b.allowedValues={};this.readChildNodes(a,b.allowedValues)},AnyValue:function(a,b){b.anyValue=
+!0},DataType:function(a,b){b.dataType=this.getChildValue(a)},Range:function(a,b){b.range={};this.readChildNodes(a,b.range)},MinimumValue:function(a,b){b.minValue=this.getChildValue(a)},MaximumValue:function(a,b){b.maxValue=this.getChildValue(a)},Identifier:function(a,b){b.identifier=this.getChildValue(a)},SupportedCRS:function(a,b){b.supportedCRS=this.getChildValue(a)}},OpenLayers.Format.OWSCommon.v1.prototype.readers.ows)},writers:{ows:OpenLayers.Util.applyDefaults({Range:function(a){var b=this.createElementNSPlus("ows:Range",
+{attributes:{"ows:rangeClosure":a.closure}});this.writeNode("ows:MinimumValue",a.minValue,b);this.writeNode("ows:MaximumValue",a.maxValue,b);return b},MinimumValue:function(a){return this.createElementNSPlus("ows:MinimumValue",{value:a})},MaximumValue:function(a){return this.createElementNSPlus("ows:MaximumValue",{value:a})},Value:function(a){return this.createElementNSPlus("ows:Value",{value:a})}},OpenLayers.Format.OWSCommon.v1.prototype.writers.ows)},CLASS_NAME:"OpenLayers.Format.OWSCommon.v1_1_0"});OpenLayers.Format.WCSGetCoverage=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ows:"http://www.opengis.net/ows/1.1",wcs:"http://www.opengis.net/wcs/1.1",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},VERSION:"1.1.2",schemaLocation:"http://www.opengis.net/wcs/1.1 http://schemas.opengis.net/wcs/1.1/wcsGetCoverage.xsd",write:function(a){a=this.writeNode("wcs:GetCoverage",
+a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{wcs:{GetCoverage:function(a){var b=this.createElementNSPlus("wcs:GetCoverage",{attributes:{version:a.version||this.VERSION,service:"WCS"}});this.writeNode("ows:Identifier",a.identifier,b);this.writeNode("wcs:DomainSubset",a.domainSubset,b);this.writeNode("wcs:Output",a.output,b);return b},DomainSubset:function(a){var b=this.createElementNSPlus("wcs:DomainSubset",
+{});this.writeNode("ows:BoundingBox",a.boundingBox,b);a.temporalSubset&&this.writeNode("wcs:TemporalSubset",a.temporalSubset,b);return b},TemporalSubset:function(a){for(var b=this.createElementNSPlus("wcs:TemporalSubset",{}),c=0,d=a.timePeriods.length;c<d;++c)this.writeNode("wcs:TimePeriod",a.timePeriods[c],b);return b},TimePeriod:function(a){var b=this.createElementNSPlus("wcs:TimePeriod",{});this.writeNode("wcs:BeginPosition",a.begin,b);this.writeNode("wcs:EndPosition",a.end,b);a.resolution&&this.writeNode("wcs:TimeResolution",
+a.resolution,b);return b},BeginPosition:function(a){return this.createElementNSPlus("wcs:BeginPosition",{value:a})},EndPosition:function(a){return this.createElementNSPlus("wcs:EndPosition",{value:a})},TimeResolution:function(a){return this.createElementNSPlus("wcs:TimeResolution",{value:a})},Output:function(a){var b=this.createElementNSPlus("wcs:Output",{attributes:{format:a.format,store:a.store}});a.gridCRS&&this.writeNode("wcs:GridCRS",a.gridCRS,b);return b},GridCRS:function(a){var b=this.createElementNSPlus("wcs:GridCRS",
+{});this.writeNode("wcs:GridBaseCRS",a.baseCRS,b);a.type&&this.writeNode("wcs:GridType",a.type,b);a.origin&&this.writeNode("wcs:GridOrigin",a.origin,b);this.writeNode("wcs:GridOffsets",a.offsets,b);a.CS&&this.writeNode("wcs:GridCS",a.CS,b);return b},GridBaseCRS:function(a){return this.createElementNSPlus("wcs:GridBaseCRS",{value:a})},GridOrigin:function(a){return this.createElementNSPlus("wcs:GridOrigin",{value:a})},GridType:function(a){return this.createElementNSPlus("wcs:GridType",{value:a})},GridOffsets:function(a){return this.createElementNSPlus("wcs:GridOffsets",
+{value:a})},GridCS:function(a){return this.createElementNSPlus("wcs:GridCS",{value:a})}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows},CLASS_NAME:"OpenLayers.Format.WCSGetCoverage"});OpenLayers.Format.KML=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{kml:"http://www.opengis.net/kml/2.2",gx:"http://www.google.com/kml/ext/2.2"},kmlns:"http://earth.google.com/kml/2.0",placemarksDesc:"No description available",foldersName:"OpenLayers export",foldersDesc:"Exported on "+new Date,extractAttributes:!0,kvpAttributes:!1,extractStyles:!1,extractTracks:!1,trackAttributes:null,internalns:null,features:null,styles:null,styleBaseUrl:"",fetched:null,maxDepth:0,initialize:function(a){this.regExes=
+{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g,kmlColor:/(\w{2})(\w{2})(\w{2})(\w{2})/,kmlIconPalette:/root:\/\/icons\/palette-(\d+)(\.\w+)/,straightBracket:/\$\[(.*?)\]/g};this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){this.features=[];this.styles={};this.fetched={};return this.parseData(a,{depth:0,styleBaseUrl:this.styleBaseUrl})},parseData:function(a,b){"string"==typeof a&&
+(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));for(var c=["Link","NetworkLink","Style","StyleMap","Placemark"],d=0,e=c.length;d<e;++d){var f=c[d],g=this.getElementsByTagNameNS(a,"*",f);if(0!=g.length)switch(f.toLowerCase()){case "link":case "networklink":this.parseLinks(g,b);break;case "style":this.extractStyles&&this.parseStyles(g,b);break;case "stylemap":this.extractStyles&&this.parseStyleMaps(g,b);break;case "placemark":this.parseFeatures(g,b)}}return this.features},parseLinks:function(a,
+b){if(b.depth>=this.maxDepth)return!1;var c=OpenLayers.Util.extend({},b);c.depth++;for(var d=0,e=a.length;d<e;d++){var f=this.parseProperty(a[d],"*","href");f&&!this.fetched[f]&&(this.fetched[f]=!0,(f=this.fetchLink(f))&&this.parseData(f,c))}},fetchLink:function(a){if(a=OpenLayers.Request.GET({url:a,async:!1}))return a.responseText},parseStyles:function(a,b){for(var c=0,d=a.length;c<d;c++){var e=this.parseStyle(a[c]);e&&(this.styles[(b.styleBaseUrl||"")+"#"+e.id]=e)}},parseKmlColor:function(a){var b=
+null;a&&(a=a.match(this.regExes.kmlColor))&&(b={color:"#"+a[4]+a[3]+a[2],opacity:parseInt(a[1],16)/255});return b},parseStyle:function(a){for(var b={},c=["LineStyle","PolyStyle","IconStyle","BalloonStyle","LabelStyle"],d,e,f=0,g=c.length;f<g;++f)if(d=c[f],e=this.getElementsByTagNameNS(a,"*",d)[0])switch(d.toLowerCase()){case "linestyle":d=this.parseProperty(e,"*","color");if(d=this.parseKmlColor(d))b.strokeColor=d.color,b.strokeOpacity=d.opacity;(d=this.parseProperty(e,"*","width"))&&(b.strokeWidth=
+d);break;case "polystyle":d=this.parseProperty(e,"*","color");if(d=this.parseKmlColor(d))b.fillOpacity=d.opacity,b.fillColor=d.color;"0"==this.parseProperty(e,"*","fill")&&(b.fillColor="none");"0"==this.parseProperty(e,"*","outline")&&(b.strokeWidth="0");break;case "iconstyle":var h=parseFloat(this.parseProperty(e,"*","scale")||1);d=32*h;var k=32*h,l=this.getElementsByTagNameNS(e,"*","Icon")[0];if(l){var m=this.parseProperty(l,"*","href");if(m){var n=this.parseProperty(l,"*","w"),p=this.parseProperty(l,
+"*","h");!OpenLayers.String.startsWith(m,"http://maps.google.com/mapfiles/kml")||(n||p)||(p=n=64,h/=2);n=n||p;p=p||n;n&&(d=parseInt(n)*h);p&&(k=parseInt(p)*h);if(p=m.match(this.regExes.kmlIconPalette))n=p[1],p=p[2],m=this.parseProperty(l,"*","x"),l=this.parseProperty(l,"*","y"),m="http://maps.google.com/mapfiles/kml/pal"+n+"/icon"+(8*(l?7-l/32:7)+(m?m/32:0))+p;b.graphicOpacity=1;b.externalGraphic=m}}if(e=this.getElementsByTagNameNS(e,"*","hotSpot")[0])m=parseFloat(e.getAttribute("x")),l=parseFloat(e.getAttribute("y")),
+n=e.getAttribute("xunits"),"pixels"==n?b.graphicXOffset=-m*h:"insetPixels"==n?b.graphicXOffset=-d+m*h:"fraction"==n&&(b.graphicXOffset=-d*m),e=e.getAttribute("yunits"),"pixels"==e?b.graphicYOffset=-k+l*h+1:"insetPixels"==e?b.graphicYOffset=-(l*h)+1:"fraction"==e&&(b.graphicYOffset=-k*(1-l)+1);b.graphicWidth=d;b.graphicHeight=k;break;case "balloonstyle":(e=OpenLayers.Util.getXmlNodeValue(e))&&(b.balloonStyle=e.replace(this.regExes.straightBracket,"${$1}"));break;case "labelstyle":if(d=this.parseProperty(e,
+"*","color"),d=this.parseKmlColor(d))b.fontColor=d.color,b.fontOpacity=d.opacity}!b.strokeColor&&b.fillColor&&(b.strokeColor=b.fillColor);(a=a.getAttribute("id"))&&b&&(b.id=a);return b},parseStyleMaps:function(a,b){for(var c=0,d=a.length;c<d;c++)for(var e=a[c],f=this.getElementsByTagNameNS(e,"*","Pair"),e=e.getAttribute("id"),g=0,h=f.length;g<h;g++){var k=f[g],l=this.parseProperty(k,"*","key");(k=this.parseProperty(k,"*","styleUrl"))&&"normal"==l&&(this.styles[(b.styleBaseUrl||"")+"#"+e]=this.styles[(b.styleBaseUrl||
+"")+k])}},parseFeatures:function(a,b){for(var c=[],d=0,e=a.length;d<e;d++){var f=a[d],g=this.parseFeature.apply(this,[f]);if(g){this.extractStyles&&(g.attributes&&g.attributes.styleUrl)&&(g.style=this.getStyle(g.attributes.styleUrl,b));if(this.extractStyles){var h=this.getElementsByTagNameNS(f,"*","Style")[0];h&&(h=this.parseStyle(h))&&(g.style=OpenLayers.Util.extend(g.style,h))}this.extractTracks?(f=this.getElementsByTagNameNS(f,this.namespaces.gx,"Track"))&&0<f.length&&(g={features:[],feature:g},
+this.readNode(f[0],g),0<g.features.length&&c.push.apply(c,g.features)):c.push(g)}else throw"Bad Placemark: "+d;}this.features=this.features.concat(c)},readers:{kml:{when:function(a,b){b.whens.push(OpenLayers.Date.parse(this.getChildValue(a)))},_trackPointAttribute:function(a,b){var c=a.nodeName.split(":").pop();b.attributes[c].push(this.getChildValue(a))}},gx:{Track:function(a,b){var c={whens:[],points:[],angles:[]};if(this.trackAttributes){var d;c.attributes={};for(var e=0,f=this.trackAttributes.length;e<
+f;++e)d=this.trackAttributes[e],c.attributes[d]=[],d in this.readers.kml||(this.readers.kml[d]=this.readers.kml._trackPointAttribute)}this.readChildNodes(a,c);if(c.whens.length!==c.points.length)throw Error("gx:Track with unequal number of when ("+c.whens.length+") and gx:coord ("+c.points.length+") elements.");var g=0<c.angles.length;if(g&&c.whens.length!==c.angles.length)throw Error("gx:Track with unequal number of when ("+c.whens.length+") and gx:angles ("+c.angles.length+") elements.");for(var h,
+e=0,f=c.whens.length;e<f;++e){h=b.feature.clone();h.fid=b.feature.fid||b.feature.id;d=c.points[e];h.geometry=d;"z"in d&&(h.attributes.altitude=d.z);this.internalProjection&&this.externalProjection&&h.geometry.transform(this.externalProjection,this.internalProjection);if(this.trackAttributes)for(var k=0,l=this.trackAttributes.length;k<l;++k)d=this.trackAttributes[k],h.attributes[d]=c.attributes[d][e];h.attributes.when=c.whens[e];h.attributes.trackId=b.feature.id;g&&(d=c.angles[e],h.attributes.heading=
+parseFloat(d[0]),h.attributes.tilt=parseFloat(d[1]),h.attributes.roll=parseFloat(d[2]));b.features.push(h)}},coord:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(/\s+/),d=new OpenLayers.Geometry.Point(c[0],c[1]);2<c.length&&(d.z=parseFloat(c[2]));b.points.push(d)},angles:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(/\s+/);b.angles.push(c)}}},parseFeature:function(a){for(var b=["MultiGeometry","Polygon","LineString","Point"],
+c,d,e,f=0,g=b.length;f<g;++f)if(c=b[f],this.internalns=a.namespaceURI?a.namespaceURI:this.kmlns,d=this.getElementsByTagNameNS(a,this.internalns,c),0<d.length){if(b=this.parseGeometry[c.toLowerCase()])e=b.apply(this,[d[0]]),this.internalProjection&&this.externalProjection&&e.transform(this.externalProjection,this.internalProjection);else throw new TypeError("Unsupported geometry type: "+c);break}var h;this.extractAttributes&&(h=this.parseAttributes(a));c=new OpenLayers.Feature.Vector(e,h);a=a.getAttribute("id")||
+a.getAttribute("name");null!=a&&(c.fid=a);return c},getStyle:function(a,b){var c=OpenLayers.Util.removeTail(a),d=OpenLayers.Util.extend({},b);d.depth++;d.styleBaseUrl=c;!this.styles[a]&&!OpenLayers.String.startsWith(a,"#")&&d.depth<=this.maxDepth&&!this.fetched[c]&&(c=this.fetchLink(c))&&this.parseData(c,d);return OpenLayers.Util.extend({},this.styles[a])},parseGeometry:{point:function(a){var b=this.getElementsByTagNameNS(a,this.internalns,"coordinates");a=[];if(0<b.length){var c=b[0].firstChild.nodeValue,
+c=c.replace(this.regExes.removeSpace,"");a=c.split(",")}b=null;if(1<a.length)2==a.length&&(a[2]=null),b=new OpenLayers.Geometry.Point(a[0],a[1],a[2]);else throw"Bad coordinate string: "+c;return b},linestring:function(a,b){var c=this.getElementsByTagNameNS(a,this.internalns,"coordinates"),d=null;if(0<c.length){for(var c=this.getChildValue(c[0]),c=c.replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),d=c.split(this.regExes.splitSpace),e=d.length,f=Array(e),g,h,k=0;k<e;++k)if(g=
+d[k].split(","),h=g.length,1<h)2==g.length&&(g[2]=null),f[k]=new OpenLayers.Geometry.Point(g[0],g[1],g[2]);else throw"Bad LineString point coordinates: "+d[k];if(e)d=b?new OpenLayers.Geometry.LinearRing(f):new OpenLayers.Geometry.LineString(f);else throw"Bad LineString coordinates: "+c;}return d},polygon:function(a){a=this.getElementsByTagNameNS(a,this.internalns,"LinearRing");var b=a.length,c=Array(b);if(0<b)for(var d=0,e=a.length;d<e;++d)if(b=this.parseGeometry.linestring.apply(this,[a[d],!0]))c[d]=
+b;else throw"Bad LinearRing geometry: "+d;return new OpenLayers.Geometry.Polygon(c)},multigeometry:function(a){for(var b,c=[],d=a.childNodes,e=0,f=d.length;e<f;++e)a=d[e],1==a.nodeType&&(b=a.prefix?a.nodeName.split(":")[1]:a.nodeName,(b=this.parseGeometry[b.toLowerCase()])&&c.push(b.apply(this,[a])));return new OpenLayers.Geometry.Collection(c)}},parseAttributes:function(a){var b={},c=a.getElementsByTagName("ExtendedData");c.length&&(b=this.parseExtendedData(c[0]));var d,e,f;a=a.childNodes;for(var c=
+0,g=a.length;c<g;++c)if(d=a[c],1==d.nodeType&&(e=d.childNodes,1<=e.length&&3>=e.length)){switch(e.length){case 1:f=e[0];break;case 2:f=e[0];e=e[1];f=3==f.nodeType||4==f.nodeType?f:e;break;default:f=e[1]}if(3==f.nodeType||4==f.nodeType)if(d=d.prefix?d.nodeName.split(":")[1]:d.nodeName,f=OpenLayers.Util.getXmlNodeValue(f))f=f.replace(this.regExes.trimSpace,""),b[d]=f}return b},parseExtendedData:function(a){var b={},c,d,e,f,g=a.getElementsByTagName("Data");c=0;for(d=g.length;c<d;c++){e=g[c];f=e.getAttribute("name");
+var h={},k=e.getElementsByTagName("value");k.length&&(h.value=this.getChildValue(k[0]));this.kvpAttributes?b[f]=h.value:(e=e.getElementsByTagName("displayName"),e.length&&(h.displayName=this.getChildValue(e[0])),b[f]=h)}a=a.getElementsByTagName("SimpleData");c=0;for(d=a.length;c<d;c++)h={},e=a[c],f=e.getAttribute("name"),h.value=this.getChildValue(e),this.kvpAttributes?b[f]=h.value:(h.displayName=f,b[f]=h);return b},parseProperty:function(a,b,c){var d;a=this.getElementsByTagNameNS(a,b,c);try{d=OpenLayers.Util.getXmlNodeValue(a[0])}catch(e){d=
+null}return d},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=this.createElementNS(this.kmlns,"kml"),c=this.createFolderXML(),d=0,e=a.length;d<e;++d)c.appendChild(this.createPlacemarkXML(a[d]));b.appendChild(c);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFolderXML:function(){var a=this.createElementNS(this.kmlns,"Folder");if(this.foldersName){var b=this.createElementNS(this.kmlns,"name"),c=this.createTextNode(this.foldersName);b.appendChild(c);a.appendChild(b)}this.foldersDesc&&
+(b=this.createElementNS(this.kmlns,"description"),c=this.createTextNode(this.foldersDesc),b.appendChild(c),a.appendChild(b));return a},createPlacemarkXML:function(a){var b=this.createElementNS(this.kmlns,"name"),c=a.style&&a.style.label?a.style.label:a.id;b.appendChild(this.createTextNode(a.attributes.name||c));var d=this.createElementNS(this.kmlns,"description");d.appendChild(this.createTextNode(a.attributes.description||this.placemarksDesc));c=this.createElementNS(this.kmlns,"Placemark");null!=
+a.fid&&c.setAttribute("id",a.fid);c.appendChild(b);c.appendChild(d);b=this.buildGeometryNode(a.geometry);c.appendChild(b);a.attributes&&(a=this.buildExtendedData(a.attributes))&&c.appendChild(a);return c},buildGeometryNode:function(a){var b=a.CLASS_NAME,b=b.substring(b.lastIndexOf(".")+1),b=this.buildGeometry[b.toLowerCase()],c=null;b&&(c=b.apply(this,[a]));return c},buildGeometry:{point:function(a){var b=this.createElementNS(this.kmlns,"Point");b.appendChild(this.buildCoordinatesNode(a));return b},
+multipoint:function(a){return this.buildGeometry.collection.apply(this,[a])},linestring:function(a){var b=this.createElementNS(this.kmlns,"LineString");b.appendChild(this.buildCoordinatesNode(a));return b},multilinestring:function(a){return this.buildGeometry.collection.apply(this,[a])},linearring:function(a){var b=this.createElementNS(this.kmlns,"LinearRing");b.appendChild(this.buildCoordinatesNode(a));return b},polygon:function(a){var b=this.createElementNS(this.kmlns,"Polygon");a=a.components;
+for(var c,d,e=0,f=a.length;e<f;++e)c=0==e?"outerBoundaryIs":"innerBoundaryIs",c=this.createElementNS(this.kmlns,c),d=this.buildGeometry.linearring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},multipolygon:function(a){return this.buildGeometry.collection.apply(this,[a])},collection:function(a){for(var b=this.createElementNS(this.kmlns,"MultiGeometry"),c,d=0,e=a.components.length;d<e;++d)(c=this.buildGeometryNode.apply(this,[a.components[d]]))&&b.appendChild(c);return b}},buildCoordinatesNode:function(a){var b=
+this.createElementNS(this.kmlns,"coordinates"),c;if(c=a.components){for(var d=c.length,e=Array(d),f=0;f<d;++f)a=c[f],e[f]=this.buildCoordinates(a);c=e.join(" ")}else c=this.buildCoordinates(a);c=this.createTextNode(c);b.appendChild(c);return b},buildCoordinates:function(a){this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));return a.x+","+a.y},buildExtendedData:function(a){var b=this.createElementNS(this.kmlns,"ExtendedData"),
+c;for(c in a)if(a[c]&&"name"!=c&&"description"!=c&&"styleUrl"!=c){var d=this.createElementNS(this.kmlns,"Data");d.setAttribute("name",c);var e=this.createElementNS(this.kmlns,"value");if("object"==typeof a[c]){if(a[c].value&&e.appendChild(this.createTextNode(a[c].value)),a[c].displayName){var f=this.createElementNS(this.kmlns,"displayName");f.appendChild(this.getXMLDoc().createCDATASection(a[c].displayName));d.appendChild(f)}}else e.appendChild(this.createTextNode(a[c]));d.appendChild(e);b.appendChild(d)}return this.isSimpleContent(b)?
+null:b},CLASS_NAME:"OpenLayers.Format.KML"});OpenLayers.Format.WMSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.1",profile:null,CLASS_NAME:"OpenLayers.Format.WMSCapabilities"});OpenLayers.Format.WMSCapabilities.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{wms:"http://www.opengis.net/wms",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"wms",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=a;a&&9==a.nodeType&&(a=a.documentElement);var c={};this.readNode(a,c);void 0===c.service&&(a=new OpenLayers.Format.OGCExceptionReport,c.error=a.read(b));return c},readers:{wms:{Service:function(a,
+b){b.service={};this.readChildNodes(a,b.service)},Name:function(a,b){b.name=this.getChildValue(a)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b["abstract"]=this.getChildValue(a)},BoundingBox:function(a,b){var c={};c.bbox=[parseFloat(a.getAttribute("minx")),parseFloat(a.getAttribute("miny")),parseFloat(a.getAttribute("maxx")),parseFloat(a.getAttribute("maxy"))];var d={x:parseFloat(a.getAttribute("resx")),y:parseFloat(a.getAttribute("resy"))};isNaN(d.x)&&isNaN(d.y)||(c.res=
+d);return c},OnlineResource:function(a,b){b.href=this.getAttributeNS(a,this.namespaces.xlink,"href")},ContactInformation:function(a,b){b.contactInformation={};this.readChildNodes(a,b.contactInformation)},ContactPersonPrimary:function(a,b){b.personPrimary={};this.readChildNodes(a,b.personPrimary)},ContactPerson:function(a,b){b.person=this.getChildValue(a)},ContactOrganization:function(a,b){b.organization=this.getChildValue(a)},ContactPosition:function(a,b){b.position=this.getChildValue(a)},ContactAddress:function(a,
+b){b.contactAddress={};this.readChildNodes(a,b.contactAddress)},AddressType:function(a,b){b.type=this.getChildValue(a)},Address:function(a,b){b.address=this.getChildValue(a)},City:function(a,b){b.city=this.getChildValue(a)},StateOrProvince:function(a,b){b.stateOrProvince=this.getChildValue(a)},PostCode:function(a,b){b.postcode=this.getChildValue(a)},Country:function(a,b){b.country=this.getChildValue(a)},ContactVoiceTelephone:function(a,b){b.phone=this.getChildValue(a)},ContactFacsimileTelephone:function(a,
+b){b.fax=this.getChildValue(a)},ContactElectronicMailAddress:function(a,b){b.email=this.getChildValue(a)},Fees:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.fees=c)},AccessConstraints:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.accessConstraints=c)},Capability:function(a,b){b.capability={nestedLayers:[],layers:[]};this.readChildNodes(a,b.capability)},Request:function(a,b){b.request={};this.readChildNodes(a,b.request)},GetCapabilities:function(a,
+b){b.getcapabilities={formats:[]};this.readChildNodes(a,b.getcapabilities)},Format:function(a,b){OpenLayers.Util.isArray(b.formats)?b.formats.push(this.getChildValue(a)):b.format=this.getChildValue(a)},DCPType:function(a,b){this.readChildNodes(a,b)},HTTP:function(a,b){this.readChildNodes(a,b)},Get:function(a,b){b.get={};this.readChildNodes(a,b.get);b.href||(b.href=b.get.href)},Post:function(a,b){b.post={};this.readChildNodes(a,b.post);b.href||(b.href=b.get.href)},GetMap:function(a,b){b.getmap={formats:[]};
+this.readChildNodes(a,b.getmap)},GetFeatureInfo:function(a,b){b.getfeatureinfo={formats:[]};this.readChildNodes(a,b.getfeatureinfo)},Exception:function(a,b){b.exception={formats:[]};this.readChildNodes(a,b.exception)},Layer:function(a,b){var c,d;b.capability?(d=b.capability,c=b):d=b;var e=a.getAttributeNode("queryable"),f=e&&e.specified?a.getAttribute("queryable"):null,g=(e=a.getAttributeNode("cascaded"))&&e.specified?a.getAttribute("cascaded"):null,e=(e=a.getAttributeNode("opaque"))&&e.specified?
+a.getAttribute("opaque"):null,h=a.getAttribute("noSubsets"),k=a.getAttribute("fixedWidth"),l=a.getAttribute("fixedHeight"),m=c||{},n=OpenLayers.Util.extend;c={nestedLayers:[],styles:c?[].concat(c.styles):[],srs:c?n({},m.srs):{},metadataURLs:[],bbox:c?n({},m.bbox):{},llbbox:m.llbbox,dimensions:c?n({},m.dimensions):{},authorityURLs:c?n({},m.authorityURLs):{},identifiers:{},keywords:[],queryable:f&&""!==f?"1"===f||"true"===f:m.queryable||!1,cascaded:null!==g?parseInt(g):m.cascaded||0,opaque:e?"1"===
+e||"true"===e:m.opaque||!1,noSubsets:null!==h?"1"===h||"true"===h:m.noSubsets||!1,fixedWidth:null!=k?parseInt(k):m.fixedWidth||0,fixedHeight:null!=l?parseInt(l):m.fixedHeight||0,minScale:m.minScale,maxScale:m.maxScale,attribution:m.attribution};b.nestedLayers.push(c);c.capability=d;this.readChildNodes(a,c);delete c.capability;c.name&&(f=c.name.split(":"),g=d.request,e=g.getfeatureinfo,0<f.length&&(c.prefix=f[0]),d.layers.push(c),void 0===c.formats&&(c.formats=g.getmap.formats),void 0===c.infoFormats&&
+e&&(c.infoFormats=e.formats))},Attribution:function(a,b){b.attribution={};this.readChildNodes(a,b.attribution)},LogoURL:function(a,b){b.logo={width:a.getAttribute("width"),height:a.getAttribute("height")};this.readChildNodes(a,b.logo)},Style:function(a,b){var c={};b.styles.push(c);this.readChildNodes(a,c)},LegendURL:function(a,b){var c={width:a.getAttribute("width"),height:a.getAttribute("height")};b.legend=c;this.readChildNodes(a,c)},MetadataURL:function(a,b){var c={type:a.getAttribute("type")};
+b.metadataURLs.push(c);this.readChildNodes(a,c)},DataURL:function(a,b){b.dataURL={};this.readChildNodes(a,b.dataURL)},FeatureListURL:function(a,b){b.featureListURL={};this.readChildNodes(a,b.featureListURL)},AuthorityURL:function(a,b){var c=a.getAttribute("name"),d={};this.readChildNodes(a,d);b.authorityURLs[c]=d.href},Identifier:function(a,b){var c=a.getAttribute("authority");b.identifiers[c]=this.getChildValue(a)},KeywordList:function(a,b){this.readChildNodes(a,b)},SRS:function(a,b){b.srs[this.getChildValue(a)]=
+!0}}},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1"});OpenLayers.Format.WMSCapabilities.v1_1=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1,{readers:{wms:OpenLayers.Util.applyDefaults({WMT_MS_Capabilities:function(a,b){this.readChildNodes(a,b)},Keyword:function(a,b){b.keywords&&b.keywords.push(this.getChildValue(a))},DescribeLayer:function(a,b){b.describelayer={formats:[]};this.readChildNodes(a,b.describelayer)},GetLegendGraphic:function(a,b){b.getlegendgraphic={formats:[]};this.readChildNodes(a,b.getlegendgraphic)},GetStyles:function(a,b){b.getstyles=
+{formats:[]};this.readChildNodes(a,b.getstyles)},PutStyles:function(a,b){b.putstyles={formats:[]};this.readChildNodes(a,b.putstyles)},UserDefinedSymbolization:function(a,b){var c={supportSLD:1==parseInt(a.getAttribute("SupportSLD")),userLayer:1==parseInt(a.getAttribute("UserLayer")),userStyle:1==parseInt(a.getAttribute("UserStyle")),remoteWFS:1==parseInt(a.getAttribute("RemoteWFS"))};b.userSymbols=c},LatLonBoundingBox:function(a,b){b.llbbox=[parseFloat(a.getAttribute("minx")),parseFloat(a.getAttribute("miny")),
+parseFloat(a.getAttribute("maxx")),parseFloat(a.getAttribute("maxy"))]},BoundingBox:function(a,b){var c=OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms.BoundingBox.apply(this,[a,b]);c.srs=a.getAttribute("SRS");b.bbox[c.srs]=c},ScaleHint:function(a,b){var c=a.getAttribute("min"),d=a.getAttribute("max"),e=Math.pow(2,0.5),f=OpenLayers.INCHES_PER_UNIT.m;0!=c&&(b.maxScale=parseFloat((c/e*f*OpenLayers.DOTS_PER_INCH).toPrecision(13)));d!=Number.POSITIVE_INFINITY&&(b.minScale=parseFloat((d/e*f*
+OpenLayers.DOTS_PER_INCH).toPrecision(13)))},Dimension:function(a,b){var c={name:a.getAttribute("name").toLowerCase(),units:a.getAttribute("units"),unitsymbol:a.getAttribute("unitSymbol")};b.dimensions[c.name]=c},Extent:function(a,b){var c=a.getAttribute("name").toLowerCase();if(c in b.dimensions){c=b.dimensions[c];c.nearestVal="1"===a.getAttribute("nearestValue");c.multipleVal="1"===a.getAttribute("multipleValues");c.current="1"===a.getAttribute("current");c["default"]=a.getAttribute("default")||
+"";var d=this.getChildValue(a);c.values=d.split(",")}}},OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms)},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1"});OpenLayers.Format.WMSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.0",readers:{wms:OpenLayers.Util.applyDefaults({SRS:function(a,b){for(var c=this.getChildValue(a).split(/ +/),d=0,e=c.length;d<e;d++)b.srs[c[d]]=!0}},OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers.wms)},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_0"});OpenLayers.Protocol.WFS.v1=OpenLayers.Class(OpenLayers.Protocol,{version:null,srsName:"EPSG:4326",featureType:null,featureNS:null,geometryName:"the_geom",schema:null,featurePrefix:"feature",formatOptions:null,readFormat:null,readOptions:null,initialize:function(a){OpenLayers.Protocol.prototype.initialize.apply(this,[a]);a.format||(this.format=OpenLayers.Format.WFST(OpenLayers.Util.extend({version:this.version,featureType:this.featureType,featureNS:this.featureNS,featurePrefix:this.featurePrefix,geometryName:this.geometryName,
+srsName:this.srsName,schema:this.schema},this.formatOptions)));!a.geometryName&&1<parseFloat(this.format.version)&&this.setGeometryName(null)},destroy:function(){this.options&&!this.options.format&&this.format.destroy();this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=OpenLayers.Util.extend({},a);OpenLayers.Util.applyDefaults(a,this.options||{});var b=new OpenLayers.Protocol.Response({requestType:"read"}),
+c=OpenLayers.Format.XML.prototype.write.apply(this.format,[this.format.writeNode("wfs:GetFeature",a)]);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),params:a.params,headers:a.headers,data:c});return b},setFeatureType:function(a){this.featureType=a;this.format.featureType=a},setGeometryName:function(a){this.geometryName=a;this.format.geometryName=a},handleRead:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);if(b.callback){var c=
+a.priv;200<=c.status&&300>c.status?(c=this.parseResponse(c,b.readOptions))&&!1!==c.success?(b.readOptions&&"object"==b.readOptions.output?OpenLayers.Util.extend(a,c):a.features=c,a.code=OpenLayers.Protocol.Response.SUCCESS):(a.code=OpenLayers.Protocol.Response.FAILURE,a.error=c):a.code=OpenLayers.Protocol.Response.FAILURE;b.callback.call(b.scope,a)}},parseResponse:function(a,b){var c=a.responseXML;c&&c.documentElement||(c=a.responseText);if(!c||0>=c.length)return null;c=null!==this.readFormat?this.readFormat.read(c):
+this.format.read(c,b);if(!this.featureNS){var d=this.readFormat||this.format;this.featureNS=d.featureNS;d.autoConfig=!1;this.geometryName||this.setGeometryName(d.geometryName)}return c},commit:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);var c=new OpenLayers.Protocol.Response({requestType:"commit",reqFeatures:a});c.priv=OpenLayers.Request.POST({url:b.url,headers:b.headers,data:this.format.write(a,b),callback:this.createCallback(this.handleCommit,c,b)});
+return c},handleCommit:function(a,b){if(b.callback){var c=a.priv,d=c.responseXML;d&&d.documentElement||(d=c.responseText);c=this.format.read(d)||{};a.insertIds=c.insertIds||[];c.success?a.code=OpenLayers.Protocol.Response.SUCCESS:(a.code=OpenLayers.Protocol.Response.FAILURE,a.error=c);b.callback.call(b.scope,a)}},filterDelete:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);new OpenLayers.Protocol.Response({requestType:"commit"});var c=this.format.createElementNSPlus("wfs:Transaction",
+{attributes:{service:"WFS",version:this.version}}),d=this.format.createElementNSPlus("wfs:Delete",{attributes:{typeName:(b.featureNS?this.featurePrefix+":":"")+b.featureType}});b.featureNS&&d.setAttribute("xmlns:"+this.featurePrefix,b.featureNS);var e=this.format.writeNode("ogc:Filter",a);d.appendChild(e);c.appendChild(d);c=OpenLayers.Format.XML.prototype.write.apply(this.format,[c]);return OpenLayers.Request.POST({url:this.url,callback:b.callback||function(){},data:c})},abort:function(a){a&&a.priv.abort()},
+CLASS_NAME:"OpenLayers.Protocol.WFS.v1"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{click:{"in":"click",out:"clickout"},mousemove:{"in":"over",out:"out"},dblclick:{"in":"dblclick",out:null},mousedown:{"in":null,out:null},mouseup:{"in":null,out:null},touchstart:{"in":"click",out:"clickout"}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:!0,stopDown:!0,stopUp:!1,initialize:function(a,b,c,d){OpenLayers.Handler.prototype.initialize.apply(this,[a,c,d]);this.layer=
+b},touchstart:function(a){this.startTouch();return OpenLayers.Event.isMultiTouch(a)?!0:this.mousedown(a)},touchmove:function(a){OpenLayers.Event.preventDefault(a)},mousedown:function(a){if(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))this.down=a.xy;return this.handle(a)?!this.stopDown:!0},mouseup:function(a){this.up=a.xy;return this.handle(a)?!this.stopUp:!0},click:function(a){return this.handle(a)?!this.stopClick:!0},mousemove:function(a){if(!this.callbacks.over&&!this.callbacks.out)return!0;
+this.handle(a);return!0},dblclick:function(a){return!this.handle(a)},geometryTypeMatches:function(a){return null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME)},handle:function(a){this.feature&&!this.feature.layer&&(this.feature=null);var b=a.type,c=!1,d=!!this.feature,e="click"==b||"dblclick"==b||"touchstart"==b;(this.feature=this.layer.getFeatureFromEvent(a))&&!this.feature.layer&&(this.feature=null);this.lastFeature&&!this.lastFeature.layer&&(this.lastFeature=
+null);this.feature?("touchstart"===b&&OpenLayers.Event.preventDefault(a),a=this.feature!=this.lastFeature,this.geometryTypeMatches(this.feature)?(d&&a?(this.lastFeature&&this.triggerCallback(b,"out",[this.lastFeature]),this.triggerCallback(b,"in",[this.feature])):d&&!e||this.triggerCallback(b,"in",[this.feature]),this.lastFeature=this.feature,c=!0):(this.lastFeature&&(d&&a||e)&&this.triggerCallback(b,"out",[this.lastFeature]),this.feature=null)):this.lastFeature&&(d||e)&&this.triggerCallback(b,"out",
+[this.lastFeature]);return c},triggerCallback:function(a,b,c){if(b=this.EVENTMAP[a][b])"click"==a&&this.up&&this.down?(Math.sqrt(Math.pow(this.up.x-this.down.x,2)+Math.pow(this.up.y-this.down.y,2))<=this.clickTolerance&&this.callback(b,c),this.up=this.down=null):this.callback(b,c)},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.moveLayerToTop(),this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);
+return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.up=this.down=this.lastFeature=this.feature=null,this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);return a},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this.layer.setZIndex(a)},
+moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:!1,layers:null,display:function(){},getFeatureFromEvent:function(a){for(var b=this.layers,c,d=0;d<b.length;d++)if(c=b[d].getFeatureFromEvent(a))return c},setMap:function(a){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);this.collectRoots();a.events.register("changelayer",this,this.handleChangeLayer)},removeMap:function(a){a.events.unregister("changelayer",this,this.handleChangeLayer);
+this.resetRoots();OpenLayers.Layer.Vector.prototype.removeMap.apply(this,arguments)},collectRoots:function(){for(var a,b=0;b<this.map.layers.length;++b)a=this.map.layers[b],-1!=OpenLayers.Util.indexOf(this.layers,a)&&a.renderer.moveRoot(this.renderer)},resetRoots:function(){for(var a,b=0;b<this.layers.length;++b)a=this.layers[b],this.renderer&&a.renderer.getRenderLayerId()==this.id&&this.renderer.moveRoot(a.renderer)},handleChangeLayer:function(a){var b=a.layer;"order"==a.property&&-1!=OpenLayers.Util.indexOf(this.layers,
+b)&&(this.resetRoots(),this.collectRoots())},CLASS_NAME:"OpenLayers.Layer.Vector.RootContainer"});OpenLayers.Control.SelectFeature=OpenLayers.Class(OpenLayers.Control,{multipleKey:null,toggleKey:null,multiple:!1,clickout:!0,toggle:!1,hover:!1,highlightOnly:!1,box:!1,onBeforeSelect:function(){},onSelect:function(){},onUnselect:function(){},scope:null,geometryTypes:null,layer:null,layers:null,callbacks:null,selectStyle:null,renderIntent:"select",handlers:null,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);null===this.scope&&(this.scope=this);this.initLayer(a);var c=
+{click:this.clickFeature,clickout:this.clickoutFeature};this.hover&&(c.over=this.overFeature,c.out=this.outFeature);this.callbacks=OpenLayers.Util.extend(c,this.callbacks);this.handlers={feature:new OpenLayers.Handler.Feature(this,this.layer,this.callbacks,{geometryTypes:this.geometryTypes})};this.box&&(this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},{boxDivClassName:"olHandlerBoxSelectFeature"}))},initLayer:function(a){OpenLayers.Util.isArray(a)?(this.layers=a,this.layer=
+new OpenLayers.Layer.Vector.RootContainer(this.id+"_container",{layers:a})):this.layer=a},destroy:function(){this.active&&this.layers&&this.map.removeLayer(this.layer);OpenLayers.Control.prototype.destroy.apply(this,arguments);this.layers&&this.layer.destroy()},activate:function(){this.active||(this.layers&&this.map.addLayer(this.layer),this.handlers.feature.activate(),this.box&&this.handlers.box&&this.handlers.box.activate());return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.active&&
+(this.handlers.feature.deactivate(),this.handlers.box&&this.handlers.box.deactivate(),this.layers&&this.map.removeLayer(this.layer));return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},unselectAll:function(a){var b=this.layers||[this.layer],c,d,e,f;for(e=0;e<b.length;++e)if(c=b[e],f=0,null!=c.selectedFeatures)for(;c.selectedFeatures.length>f;)d=c.selectedFeatures[f],a&&a.except==d?++f:this.unselect(d)},clickFeature:function(a){this.hover||(-1<OpenLayers.Util.indexOf(a.layer.selectedFeatures,
+a)?this.toggleSelect()?this.unselect(a):this.multipleSelect()||this.unselectAll({except:a}):(this.multipleSelect()||this.unselectAll({except:a}),this.select(a)))},multipleSelect:function(){return this.multiple||this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]},toggleSelect:function(){return this.toggle||this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]},clickoutFeature:function(a){!this.hover&&this.clickout&&this.unselectAll()},overFeature:function(a){var b=
+a.layer;this.hover&&(this.highlightOnly?this.highlight(a):-1==OpenLayers.Util.indexOf(b.selectedFeatures,a)&&this.select(a))},outFeature:function(a){if(this.hover)if(this.highlightOnly){if(a._lastHighlighter==this.id)if(a._prevHighlighter&&a._prevHighlighter!=this.id){delete a._lastHighlighter;var b=this.map.getControl(a._prevHighlighter);b&&b.highlight(a)}else this.unhighlight(a)}else this.unselect(a)},highlight:function(a){var b=a.layer;!1!==this.events.triggerEvent("beforefeaturehighlighted",{feature:a})&&
+(a._prevHighlighter=a._lastHighlighter,a._lastHighlighter=this.id,b.drawFeature(a,this.selectStyle||this.renderIntent),this.events.triggerEvent("featurehighlighted",{feature:a}))},unhighlight:function(a){var b=a.layer;void 0==a._prevHighlighter?delete a._lastHighlighter:(a._prevHighlighter!=this.id&&(a._lastHighlighter=a._prevHighlighter),delete a._prevHighlighter);b.drawFeature(a,a.style||a.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:a})},select:function(a){var b=
+this.onBeforeSelect.call(this.scope,a),c=a.layer;!1!==b&&(b=c.events.triggerEvent("beforefeatureselected",{feature:a}),!1!==b&&(c.selectedFeatures.push(a),this.highlight(a),this.handlers.feature.lastFeature||(this.handlers.feature.lastFeature=c.selectedFeatures[0]),c.events.triggerEvent("featureselected",{feature:a}),this.onSelect.call(this.scope,a)))},unselect:function(a){var b=a.layer;this.unhighlight(a);OpenLayers.Util.removeItem(b.selectedFeatures,a);b.events.triggerEvent("featureunselected",
+{feature:a});this.onUnselect.call(this.scope,a)},selectBox:function(a){if(a instanceof OpenLayers.Bounds){var b=this.map.getLonLatFromPixel({x:a.left,y:a.bottom});a=this.map.getLonLatFromPixel({x:a.right,y:a.top});b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat);this.multipleSelect()||this.unselectAll();a=this.multiple;this.multiple=!0;var c=this.layers||[this.layer];this.events.triggerEvent("boxselectionstart",{layers:c});for(var d,e=0;e<c.length;++e){d=c[e];for(var f=0,g=d.features.length;f<g;++f){var h=
+d.features[f];h.getVisibility()&&(null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,h.geometry.CLASS_NAME))&&b.toGeometry().intersects(h.geometry)&&-1==OpenLayers.Util.indexOf(d.selectedFeatures,h)&&this.select(h)}}this.multiple=a;this.events.triggerEvent("boxselectionend",{layers:c})}},setMap:function(a){this.handlers.feature.setMap(a);this.box&&this.handlers.box.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},setLayer:function(a){var b=this.active;this.unselectAll();
+this.deactivate();this.layers&&(this.layer.destroy(),this.layers=null);this.initLayer(a);this.handlers.feature.layer=this.layer;b&&this.activate()},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Handler.Point=OpenLayers.Class(OpenLayers.Handler,{point:null,layer:null,multi:!1,citeCompliant:!1,mouseDown:!1,stoppedDown:null,lastDown:null,lastUp:null,persist:!1,stopDown:!1,stopUp:!1,layerOptions:null,pixelTolerance:5,lastTouchPx:null,initialize:function(a,b,c){c&&c.layerOptions&&c.layerOptions.styleMap||(this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style["default"],{}));OpenLayers.Handler.prototype.initialize.apply(this,arguments)},activate:function(){if(!OpenLayers.Handler.prototype.activate.apply(this,
+arguments))return!1;var a=OpenLayers.Util.extend({displayInLayerSwitcher:!1,calculateInRange:OpenLayers.Function.True,wrapDateLine:this.citeCompliant},this.layerOptions);this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,a);this.map.addLayer(this.layer);return!0},createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.callback("create",[this.point.geometry,this.point]);this.point.geometry.clearBounds();
+this.layer.addFeatures([this.point],{silent:!0})},deactivate:function(){if(!OpenLayers.Handler.prototype.deactivate.apply(this,arguments))return!1;this.cancel();null!=this.layer.map&&(this.destroyFeature(!0),this.layer.destroy(!1));this.layer=null;return!0},destroyFeature:function(a){!this.layer||!a&&this.persist||this.layer.destroyFeatures();this.point=null},destroyPersistedFeature:function(){var a=this.layer;a&&1<a.features.length&&this.layer.features[0].destroy()},finalize:function(a){this.mouseDown=
+!1;this.lastTouchPx=this.lastUp=this.lastDown=null;this.callback(a?"cancel":"done",[this.geometryClone()]);this.destroyFeature(a)},cancel:function(){this.finalize(!0)},click:function(a){OpenLayers.Event.stop(a);return!1},dblclick:function(a){OpenLayers.Event.stop(a);return!1},modifyFeature:function(a){this.point||this.createFeature(a);a=this.layer.getLonLatFromViewPortPx(a);this.point.geometry.x=a.lon;this.point.geometry.y=a.lat;this.callback("modify",[this.point.geometry,this.point,!1]);this.point.geometry.clearBounds();
+this.drawFeature()},drawFeature:function(){this.layer.drawFeature(this.point,this.style)},getGeometry:function(){var a=this.point&&this.point.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiPoint([a]));return a},geometryClone:function(){var a=this.getGeometry();return a&&a.clone()},mousedown:function(a){return this.down(a)},touchstart:function(a){this.startTouch();this.lastTouchPx=a.xy;return this.down(a)},mousemove:function(a){return this.move(a)},touchmove:function(a){this.lastTouchPx=a.xy;
+return this.move(a)},mouseup:function(a){return this.up(a)},touchend:function(a){a.xy=this.lastTouchPx;return this.up(a)},down:function(a){this.mouseDown=!0;this.lastDown=a.xy;this.touch||this.modifyFeature(a.xy);this.stoppedDown=this.stopDown;return!this.stopDown},move:function(a){this.touch||this.mouseDown&&!this.stoppedDown||this.modifyFeature(a.xy);return!0},up:function(a){this.mouseDown=!1;this.stoppedDown=this.stopDown;if(!this.checkModifiers(a)||this.lastUp&&this.lastUp.equals(a.xy)||!this.lastDown||
+!this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance))return!0;this.touch&&this.modifyFeature(a.xy);this.persist&&this.destroyPersistedFeature();this.lastUp=a.xy;this.finalize();return!this.stopUp},mouseout:function(a){OpenLayers.Util.mouseLeft(a,this.map.viewPortDiv)&&(this.stoppedDown=this.stopDown,this.mouseDown=!1)},passesTolerance:function(a,b,c){var d=!0;null!=c&&a&&b&&a.distanceTo(b)>c&&(d=!1);return d},CLASS_NAME:"OpenLayers.Handler.Point"});OpenLayers.Handler.Path=OpenLayers.Class(OpenLayers.Handler.Point,{line:null,maxVertices:null,doubleTouchTolerance:20,freehand:!1,freehandToggle:"shiftKey",timerId:null,redoStack:null,createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([this.point.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);
+this.point.geometry.clearBounds();this.layer.addFeatures([this.line,this.point],{silent:!0})},destroyFeature:function(a){OpenLayers.Handler.Point.prototype.destroyFeature.call(this,a);this.line=null},destroyPersistedFeature:function(){var a=this.layer;a&&2<a.features.length&&this.layer.features[0].destroy()},removePoint:function(){this.point&&this.layer.removeFeatures([this.point])},addPoint:function(a){this.layer.removeFeatures([this.point]);a=this.layer.getLonLatFromViewPortPx(a);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(a.lon,
+a.lat));this.line.geometry.addComponent(this.point.geometry,this.line.geometry.components.length);this.layer.addFeatures([this.point]);this.callback("point",[this.point.geometry,this.getGeometry()]);this.callback("modify",[this.point.geometry,this.getSketch()]);this.drawFeature();delete this.redoStack},insertXY:function(a,b){this.line.geometry.addComponent(new OpenLayers.Geometry.Point(a,b),this.getCurrentPointIndex());this.drawFeature();delete this.redoStack},insertDeltaXY:function(a,b){var c=this.getCurrentPointIndex()-
+1,c=this.line.geometry.components[c];!c||(isNaN(c.x)||isNaN(c.y))||this.insertXY(c.x+a,c.y+b)},insertDirectionLength:function(a,b){a*=Math.PI/180;var c=b*Math.cos(a),d=b*Math.sin(a);this.insertDeltaXY(c,d)},insertDeflectionLength:function(a,b){var c=this.getCurrentPointIndex()-1;if(0<c){var d=this.line.geometry.components[c],c=this.line.geometry.components[c-1],d=Math.atan2(d.y-c.y,d.x-c.x);this.insertDirectionLength(180*d/Math.PI+a,b)}},getCurrentPointIndex:function(){return this.line.geometry.components.length-
+1},undo:function(){var a=this.line.geometry,b=a.components,c=this.getCurrentPointIndex()-1,d=b[c],e=a.removeComponent(d);e&&(this.touch&&0<c&&(b=a.components,a=b[c-1],c=this.getCurrentPointIndex(),b=b[c],b.x=a.x,b.y=a.y),this.redoStack||(this.redoStack=[]),this.redoStack.push(d),this.drawFeature());return e},redo:function(){var a=this.redoStack&&this.redoStack.pop();a&&(this.line.geometry.addComponent(a,this.getCurrentPointIndex()),this.drawFeature());return!!a},freehandMode:function(a){return this.freehandToggle&&
+a[this.freehandToggle]?!this.freehand:this.freehand},modifyFeature:function(a,b){this.line||this.createFeature(a);var c=this.layer.getLonLatFromViewPortPx(a);this.point.geometry.x=c.lon;this.point.geometry.y=c.lat;this.callback("modify",[this.point.geometry,this.getSketch(),b]);this.point.geometry.clearBounds();this.drawFeature()},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style)},getSketch:function(){return this.line},getGeometry:function(){var a=
+this.line&&this.line.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiLineString([a]));return a},touchstart:function(a){if(this.timerId&&this.passesTolerance(this.lastTouchPx,a.xy,this.doubleTouchTolerance))return this.finishGeometry(),window.clearTimeout(this.timerId),this.timerId=null,!1;this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);this.timerId=window.setTimeout(OpenLayers.Function.bind(function(){this.timerId=null},this),300);return OpenLayers.Handler.Point.prototype.touchstart.call(this,
+a)},down:function(a){var b=this.stopDown;this.freehandMode(a)&&(b=!0,this.touch&&(this.modifyFeature(a.xy,!!this.lastUp),OpenLayers.Event.stop(a)));this.touch||this.lastDown&&this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance)||this.modifyFeature(a.xy,!!this.lastUp);this.mouseDown=!0;this.lastDown=a.xy;this.stoppedDown=b;return!b},move:function(a){if(this.stoppedDown&&this.freehandMode(a))return this.persist&&this.destroyPersistedFeature(),this.maxVertices&&this.line&&this.line.geometry.components.length===
+this.maxVertices?(this.removePoint(),this.finalize()):this.addPoint(a.xy),!1;this.touch||this.mouseDown&&!this.stoppedDown||this.modifyFeature(a.xy,!!this.lastUp);return!0},up:function(a){!this.mouseDown||this.lastUp&&this.lastUp.equals(a.xy)||(this.stoppedDown&&this.freehandMode(a)?(this.persist&&this.destroyPersistedFeature(),this.removePoint(),this.finalize()):this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance)&&(this.touch&&this.modifyFeature(a.xy),null==this.lastUp&&this.persist&&this.destroyPersistedFeature(),
+this.addPoint(a.xy),this.lastUp=a.xy,this.line.geometry.components.length===this.maxVertices+1&&this.finishGeometry()));this.stoppedDown=this.stopDown;this.mouseDown=!1;return!this.stopUp},finishGeometry:function(){this.line.geometry.removeComponent(this.line.geometry.components[this.line.geometry.components.length-1]);this.removePoint();this.finalize()},dblclick:function(a){this.freehandMode(a)||this.finishGeometry();return!1},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Spherical=OpenLayers.Spherical||{};OpenLayers.Spherical.DEFAULT_RADIUS=6378137;OpenLayers.Spherical.computeDistanceBetween=function(a,b,c){c=c||OpenLayers.Spherical.DEFAULT_RADIUS;var d=Math.sin(Math.PI*(b.lon-a.lon)/360),e=Math.sin(Math.PI*(b.lat-a.lat)/360);a=e*e+d*d*Math.cos(Math.PI*a.lat/180)*Math.cos(Math.PI*b.lat/180);return 2*c*Math.atan2(Math.sqrt(a),Math.sqrt(1-a))};
+OpenLayers.Spherical.computeHeading=function(a,b){var c=Math.sin(Math.PI*(a.lon-b.lon)/180)*Math.cos(Math.PI*b.lat/180),d=Math.cos(Math.PI*a.lat/180)*Math.sin(Math.PI*b.lat/180)-Math.sin(Math.PI*a.lat/180)*Math.cos(Math.PI*b.lat/180)*Math.cos(Math.PI*(a.lon-b.lon)/180);return 180*Math.atan2(c,d)/Math.PI};OpenLayers.Control.CacheWrite=OpenLayers.Class(OpenLayers.Control,{layers:null,imageFormat:"image/png",quotaRegEx:/quota/i,setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);var b,c=this.layers||a.layers;for(b=c.length-1;0<=b;--b)this.addLayer({layer:c[b]});if(!this.layers)a.events.on({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this})},addLayer:function(a){a.layer.events.on({tileloadstart:this.makeSameOrigin,tileloaded:this.onTileLoaded,scope:this})},removeLayer:function(a){a.layer.events.un({tileloadstart:this.makeSameOrigin,
+tileloaded:this.onTileLoaded,scope:this})},makeSameOrigin:function(a){if(this.active&&(a=a.tile,a instanceof OpenLayers.Tile.Image&&!a.crossOriginKeyword&&"data:"!==a.url.substr(0,5))){var b=OpenLayers.Request.makeSameOrigin(a.url,OpenLayers.ProxyHost);OpenLayers.Control.CacheWrite.urlMap[b]=a.url;a.url=b}},onTileLoaded:function(a){this.active&&(!a.aborted&&a.tile instanceof OpenLayers.Tile.Image&&"data:"!==a.tile.url.substr(0,5))&&(this.cache({tile:a.tile}),delete OpenLayers.Control.CacheWrite.urlMap[a.tile.url])},
+cache:function(a){if(window.localStorage){a=a.tile;try{var b=a.getCanvasContext();b&&window.localStorage.setItem("olCache_"+(OpenLayers.Control.CacheWrite.urlMap[a.url]||a.url),b.canvas.toDataURL(this.imageFormat))}catch(c){(b=c.name||c.message)&&this.quotaRegEx.test(b)?this.events.triggerEvent("cachefull",{tile:a}):OpenLayers.Console.error(c.toString())}}},destroy:function(){if(this.layers||this.map){var a,b=this.layers||this.map.layers;for(a=b.length-1;0<=a;--a)this.removeLayer({layer:b[a]})}this.map&&
+this.map.events.un({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},CLASS_NAME:"OpenLayers.Control.CacheWrite"});OpenLayers.Control.CacheWrite.clearCache=function(){if(window.localStorage){var a,b;for(a=window.localStorage.length-1;0<=a;--a)b=window.localStorage.key(a),"olCache_"===b.substr(0,8)&&window.localStorage.removeItem(b)}};OpenLayers.Control.CacheWrite.urlMap={};OpenLayers.Format.Context=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{layerOptions:null,layerParams:null,read:function(a,b){var c=OpenLayers.Format.XML.VersionedOGC.prototype.read.apply(this,arguments);if(b&&b.map)if(this.context=c,b.map instanceof OpenLayers.Map)c=this.mergeContextToMap(c,b.map);else{var d=b.map;if(OpenLayers.Util.isElement(d)||"string"==typeof d)d={div:d};c=this.contextToMap(c,d)}return c},getLayerFromContext:function(a){var b,c,d={queryable:a.queryable,visibility:a.visibility,
+maxExtent:a.maxExtent,metadata:OpenLayers.Util.applyDefaults(a.metadata,{styles:a.styles,formats:a.formats,"abstract":a["abstract"],dataURL:a.dataURL}),numZoomLevels:a.numZoomLevels,units:a.units,isBaseLayer:a.isBaseLayer,opacity:a.opacity,displayInLayerSwitcher:a.displayInLayerSwitcher,singleTile:a.singleTile,tileSize:a.tileSize?new OpenLayers.Size(a.tileSize.width,a.tileSize.height):void 0,minScale:a.minScale||a.maxScaleDenominator,maxScale:a.maxScale||a.minScaleDenominator,srs:a.srs,dimensions:a.dimensions,
+metadataURL:a.metadataURL};this.layerOptions&&OpenLayers.Util.applyDefaults(d,this.layerOptions);var e={layers:a.name,transparent:a.transparent,version:a.version};if(a.formats&&0<a.formats.length)for(e.format=a.formats[0].value,b=0,c=a.formats.length;b<c;b++){var f=a.formats[b];if(!0==f.current){e.format=f.value;break}}if(a.styles&&0<a.styles.length)for(b=0,c=a.styles.length;b<c;b++)if(f=a.styles[b],!0==f.current){f.href?e.sld=f.href:f.body?e.sld_body=f.body:e.styles=f.name;break}this.layerParams&&
+OpenLayers.Util.applyDefaults(e,this.layerParams);b=null;c=a.service;c==OpenLayers.Format.Context.serviceTypes.WFS?(d.strategies=[new OpenLayers.Strategy.BBOX],d.protocol=new OpenLayers.Protocol.WFS({url:a.url,featurePrefix:a.name.split(":")[0],featureType:a.name.split(":").pop()}),b=new OpenLayers.Layer.Vector(a.title||a.name,d)):c==OpenLayers.Format.Context.serviceTypes.KML?(d.strategies=[new OpenLayers.Strategy.Fixed],d.protocol=new OpenLayers.Protocol.HTTP({url:a.url,format:new OpenLayers.Format.KML}),
+b=new OpenLayers.Layer.Vector(a.title||a.name,d)):c==OpenLayers.Format.Context.serviceTypes.GML?(d.strategies=[new OpenLayers.Strategy.Fixed],d.protocol=new OpenLayers.Protocol.HTTP({url:a.url,format:new OpenLayers.Format.GML}),b=new OpenLayers.Layer.Vector(a.title||a.name,d)):a.features?(b=new OpenLayers.Layer.Vector(a.title||a.name,d),b.addFeatures(a.features)):!0!==a.categoryLayer&&(b=new OpenLayers.Layer.WMS(a.title||a.name,a.url,e,d));return b},getLayersFromContext:function(a){for(var b=[],c=
+0,d=a.length;c<d;c++){var e=this.getLayerFromContext(a[c]);null!==e&&b.push(e)}return b},contextToMap:function(a,b){b=OpenLayers.Util.applyDefaults({maxExtent:a.maxExtent,projection:a.projection,units:a.units},b);b.maxExtent&&(b.maxResolution=b.maxExtent.getWidth()/OpenLayers.Map.TILE_WIDTH);b.metadata={contactInformation:a.contactInformation,"abstract":a["abstract"],keywords:a.keywords,logo:a.logo,descriptionURL:a.descriptionURL};var c=new OpenLayers.Map(b);c.addLayers(this.getLayersFromContext(a.layersContext));
+c.setCenter(a.bounds.getCenterLonLat(),c.getZoomForExtent(a.bounds,!0));return c},mergeContextToMap:function(a,b){b.addLayers(this.getLayersFromContext(a.layersContext));return b},write:function(a,b){a=this.toContext(a);return OpenLayers.Format.XML.VersionedOGC.prototype.write.apply(this,arguments)},CLASS_NAME:"OpenLayers.Format.Context"});
+OpenLayers.Format.Context.serviceTypes={WMS:"urn:ogc:serviceType:WMS",WFS:"urn:ogc:serviceType:WFS",WCS:"urn:ogc:serviceType:WCS",GML:"urn:ogc:serviceType:GML",SLD:"urn:ogc:serviceType:SLD",FES:"urn:ogc:serviceType:FES",KML:"urn:ogc:serviceType:KML"};OpenLayers.Format.WMC=OpenLayers.Class(OpenLayers.Format.Context,{defaultVersion:"1.1.0",layerToContext:function(a){var b=this.getParser(),c={queryable:a.queryable,visibility:a.visibility,name:a.params.LAYERS,title:a.name,"abstract":a.metadata["abstract"],dataURL:a.metadata.dataURL,metadataURL:a.metadataURL,server:{version:a.params.VERSION,url:a.url},maxExtent:a.maxExtent,transparent:a.params.TRANSPARENT,numZoomLevels:a.numZoomLevels,units:a.units,isBaseLayer:a.isBaseLayer,opacity:1==a.opacity?void 0:
+a.opacity,displayInLayerSwitcher:a.displayInLayerSwitcher,singleTile:a.singleTile,tileSize:a.singleTile||!a.tileSize?void 0:{width:a.tileSize.w,height:a.tileSize.h},minScale:a.options.resolutions||a.options.scales||a.options.maxResolution||a.options.minScale?a.minScale:void 0,maxScale:a.options.resolutions||a.options.scales||a.options.minResolution||a.options.maxScale?a.maxScale:void 0,formats:[],styles:[],srs:a.srs,dimensions:a.dimensions};a.metadata.servertitle&&(c.server.title=a.metadata.servertitle);
+if(a.metadata.formats&&0<a.metadata.formats.length)for(var d=0,e=a.metadata.formats.length;d<e;d++){var f=a.metadata.formats[d];c.formats.push({value:f.value,current:f.value==a.params.FORMAT})}else c.formats.push({value:a.params.FORMAT,current:!0});if(a.metadata.styles&&0<a.metadata.styles.length)for(d=0,e=a.metadata.styles.length;d<e;d++)b=a.metadata.styles[d],b.current=b.href==a.params.SLD||b.body==a.params.SLD_BODY||b.name==a.params.STYLES?!0:!1,c.styles.push(b);else c.styles.push({href:a.params.SLD,
+body:a.params.SLD_BODY,name:a.params.STYLES||b.defaultStyleName,title:b.defaultStyleTitle,current:!0});return c},toContext:function(a){var b={},c=a.layers;if("OpenLayers.Map"==a.CLASS_NAME){var d=a.metadata||{};b.size=a.getSize();b.bounds=a.getExtent();b.projection=a.projection;b.title=a.title;b.keywords=d.keywords;b["abstract"]=d["abstract"];b.logo=d.logo;b.descriptionURL=d.descriptionURL;b.contactInformation=d.contactInformation;b.maxExtent=a.maxExtent}else OpenLayers.Util.applyDefaults(b,a),void 0!=
+b.layers&&delete b.layers;void 0==b.layersContext&&(b.layersContext=[]);if(void 0!=c&&OpenLayers.Util.isArray(c))for(a=0,d=c.length;a<d;a++){var e=c[a];e instanceof OpenLayers.Layer.WMS&&b.layersContext.push(this.layerToContext(e))}return b},CLASS_NAME:"OpenLayers.Format.WMC"});OpenLayers.Format.WMC.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ol:"http://openlayers.org/context",wmc:"http://www.opengis.net/context",sld:"http://www.opengis.net/sld",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"",getNamespacePrefix:function(a){var b=null;if(null==a)b=this.namespaces[this.defaultPrefix];else for(b in this.namespaces)if(this.namespaces[b]==a)break;return b},defaultPrefix:"wmc",rootPrefix:null,defaultStyleName:"",
+defaultStyleTitle:"Default",initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a=a.documentElement;this.rootPrefix=a.prefix;var b={version:a.getAttribute("version")};this.runChildNodes(b,a);return b},runChildNodes:function(a,b){for(var c=b.childNodes,d,e,f,g=0,h=c.length;g<h;++g)d=c[g],1==d.nodeType&&(e=this.getNamespacePrefix(d.namespaceURI),f=d.nodeName.split(":").pop(),
+(e=this["read_"+e+"_"+f])&&e.apply(this,[a,d]))},read_wmc_General:function(a,b){this.runChildNodes(a,b)},read_wmc_BoundingBox:function(a,b){a.projection=b.getAttribute("SRS");a.bounds=new OpenLayers.Bounds(b.getAttribute("minx"),b.getAttribute("miny"),b.getAttribute("maxx"),b.getAttribute("maxy"))},read_wmc_LayerList:function(a,b){a.layersContext=[];this.runChildNodes(a,b)},read_wmc_Layer:function(a,b){var c={visibility:"1"!=b.getAttribute("hidden"),queryable:"1"==b.getAttribute("queryable"),formats:[],
+styles:[],metadata:{}};this.runChildNodes(c,b);a.layersContext.push(c)},read_wmc_Extension:function(a,b){this.runChildNodes(a,b)},read_ol_units:function(a,b){a.units=this.getChildValue(b)},read_ol_maxExtent:function(a,b){var c=new OpenLayers.Bounds(b.getAttribute("minx"),b.getAttribute("miny"),b.getAttribute("maxx"),b.getAttribute("maxy"));a.maxExtent=c},read_ol_transparent:function(a,b){a.transparent=this.getChildValue(b)},read_ol_numZoomLevels:function(a,b){a.numZoomLevels=parseInt(this.getChildValue(b))},
+read_ol_opacity:function(a,b){a.opacity=parseFloat(this.getChildValue(b))},read_ol_singleTile:function(a,b){a.singleTile="true"==this.getChildValue(b)},read_ol_tileSize:function(a,b){var c={width:b.getAttribute("width"),height:b.getAttribute("height")};a.tileSize=c},read_ol_isBaseLayer:function(a,b){a.isBaseLayer="true"==this.getChildValue(b)},read_ol_displayInLayerSwitcher:function(a,b){a.displayInLayerSwitcher="true"==this.getChildValue(b)},read_wmc_Server:function(a,b){a.version=b.getAttribute("version");
+a.url=this.getOnlineResource_href(b);a.metadata.servertitle=b.getAttribute("title")},read_wmc_FormatList:function(a,b){this.runChildNodes(a,b)},read_wmc_Format:function(a,b){var c={value:this.getChildValue(b)};"1"==b.getAttribute("current")&&(c.current=!0);a.formats.push(c)},read_wmc_StyleList:function(a,b){this.runChildNodes(a,b)},read_wmc_Style:function(a,b){var c={};this.runChildNodes(c,b);"1"==b.getAttribute("current")&&(c.current=!0);a.styles.push(c)},read_wmc_SLD:function(a,b){this.runChildNodes(a,
+b)},read_sld_StyledLayerDescriptor:function(a,b){var c=OpenLayers.Format.XML.prototype.write.apply(this,[b]);a.body=c},read_sld_FeatureTypeStyle:function(a,b){var c=OpenLayers.Format.XML.prototype.write.apply(this,[b]);a.body=c},read_wmc_OnlineResource:function(a,b){a.href=this.getAttributeNS(b,this.namespaces.xlink,"href")},read_wmc_Name:function(a,b){var c=this.getChildValue(b);c&&(a.name=c)},read_wmc_Title:function(a,b){var c=this.getChildValue(b);c&&(a.title=c)},read_wmc_MetadataURL:function(a,
+b){a.metadataURL=this.getOnlineResource_href(b)},read_wmc_KeywordList:function(a,b){a.keywords=[];this.runChildNodes(a.keywords,b)},read_wmc_Keyword:function(a,b){a.push(this.getChildValue(b))},read_wmc_Abstract:function(a,b){var c=this.getChildValue(b);c&&(a["abstract"]=c)},read_wmc_LogoURL:function(a,b){a.logo={width:b.getAttribute("width"),height:b.getAttribute("height"),format:b.getAttribute("format"),href:this.getOnlineResource_href(b)}},read_wmc_DescriptionURL:function(a,b){a.descriptionURL=
+this.getOnlineResource_href(b)},read_wmc_ContactInformation:function(a,b){var c={};this.runChildNodes(c,b);a.contactInformation=c},read_wmc_ContactPersonPrimary:function(a,b){var c={};this.runChildNodes(c,b);a.personPrimary=c},read_wmc_ContactPerson:function(a,b){var c=this.getChildValue(b);c&&(a.person=c)},read_wmc_ContactOrganization:function(a,b){var c=this.getChildValue(b);c&&(a.organization=c)},read_wmc_ContactPosition:function(a,b){var c=this.getChildValue(b);c&&(a.position=c)},read_wmc_ContactAddress:function(a,
+b){var c={};this.runChildNodes(c,b);a.contactAddress=c},read_wmc_AddressType:function(a,b){var c=this.getChildValue(b);c&&(a.type=c)},read_wmc_Address:function(a,b){var c=this.getChildValue(b);c&&(a.address=c)},read_wmc_City:function(a,b){var c=this.getChildValue(b);c&&(a.city=c)},read_wmc_StateOrProvince:function(a,b){var c=this.getChildValue(b);c&&(a.stateOrProvince=c)},read_wmc_PostCode:function(a,b){var c=this.getChildValue(b);c&&(a.postcode=c)},read_wmc_Country:function(a,b){var c=this.getChildValue(b);
+c&&(a.country=c)},read_wmc_ContactVoiceTelephone:function(a,b){var c=this.getChildValue(b);c&&(a.phone=c)},read_wmc_ContactFacsimileTelephone:function(a,b){var c=this.getChildValue(b);c&&(a.fax=c)},read_wmc_ContactElectronicMailAddress:function(a,b){var c=this.getChildValue(b);c&&(a.email=c)},read_wmc_DataURL:function(a,b){a.dataURL=this.getOnlineResource_href(b)},read_wmc_LegendURL:function(a,b){var c={width:b.getAttribute("width"),height:b.getAttribute("height"),format:b.getAttribute("format"),
+href:this.getOnlineResource_href(b)};a.legend=c},read_wmc_DimensionList:function(a,b){a.dimensions={};this.runChildNodes(a.dimensions,b)},read_wmc_Dimension:function(a,b){var c={name:b.getAttribute("name").toLowerCase(),units:b.getAttribute("units")||"",unitSymbol:b.getAttribute("unitSymbol")||"",userValue:b.getAttribute("userValue")||"",nearestValue:"1"===b.getAttribute("nearestValue"),multipleValues:"1"===b.getAttribute("multipleValues"),current:"1"===b.getAttribute("current"),"default":b.getAttribute("default")||
+""},d=this.getChildValue(b);c.values=d.split(",");a[c.name]=c},write:function(a,b){var c=this.createElementDefaultNS("ViewContext");this.setAttributes(c,{version:this.VERSION,id:b&&"string"==typeof b.id?b.id:OpenLayers.Util.createUniqueID("OpenLayers_Context_")});this.setAttributeNS(c,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);c.appendChild(this.write_wmc_General(a));c.appendChild(this.write_wmc_LayerList(a));return OpenLayers.Format.XML.prototype.write.apply(this,[c])},createElementDefaultNS:function(a,
+b,c){a=this.createElementNS(this.namespaces[this.defaultPrefix],a);b&&a.appendChild(this.createTextNode(b));c&&this.setAttributes(a,c);return a},setAttributes:function(a,b){var c,d;for(d in b)c=b[d].toString(),c.match(/[A-Z]/)?this.setAttributeNS(a,null,d,c):a.setAttribute(d,c)},write_wmc_General:function(a){var b=this.createElementDefaultNS("General");a.size&&b.appendChild(this.createElementDefaultNS("Window",null,{width:a.size.w,height:a.size.h}));var c=a.bounds;b.appendChild(this.createElementDefaultNS("BoundingBox",
+null,{minx:c.left.toPrecision(18),miny:c.bottom.toPrecision(18),maxx:c.right.toPrecision(18),maxy:c.top.toPrecision(18),SRS:a.projection}));b.appendChild(this.createElementDefaultNS("Title",a.title));a.keywords&&b.appendChild(this.write_wmc_KeywordList(a.keywords));a["abstract"]&&b.appendChild(this.createElementDefaultNS("Abstract",a["abstract"]));a.logo&&b.appendChild(this.write_wmc_URLType("LogoURL",a.logo.href,a.logo));a.descriptionURL&&b.appendChild(this.write_wmc_URLType("DescriptionURL",a.descriptionURL));
+a.contactInformation&&b.appendChild(this.write_wmc_ContactInformation(a.contactInformation));b.appendChild(this.write_ol_MapExtension(a));return b},write_wmc_KeywordList:function(a){for(var b=this.createElementDefaultNS("KeywordList"),c=0,d=a.length;c<d;c++)b.appendChild(this.createElementDefaultNS("Keyword",a[c]));return b},write_wmc_ContactInformation:function(a){var b=this.createElementDefaultNS("ContactInformation");a.personPrimary&&b.appendChild(this.write_wmc_ContactPersonPrimary(a.personPrimary));
+a.position&&b.appendChild(this.createElementDefaultNS("ContactPosition",a.position));a.contactAddress&&b.appendChild(this.write_wmc_ContactAddress(a.contactAddress));a.phone&&b.appendChild(this.createElementDefaultNS("ContactVoiceTelephone",a.phone));a.fax&&b.appendChild(this.createElementDefaultNS("ContactFacsimileTelephone",a.fax));a.email&&b.appendChild(this.createElementDefaultNS("ContactElectronicMailAddress",a.email));return b},write_wmc_ContactPersonPrimary:function(a){var b=this.createElementDefaultNS("ContactPersonPrimary");
+a.person&&b.appendChild(this.createElementDefaultNS("ContactPerson",a.person));a.organization&&b.appendChild(this.createElementDefaultNS("ContactOrganization",a.organization));return b},write_wmc_ContactAddress:function(a){var b=this.createElementDefaultNS("ContactAddress");a.type&&b.appendChild(this.createElementDefaultNS("AddressType",a.type));a.address&&b.appendChild(this.createElementDefaultNS("Address",a.address));a.city&&b.appendChild(this.createElementDefaultNS("City",a.city));a.stateOrProvince&&
+b.appendChild(this.createElementDefaultNS("StateOrProvince",a.stateOrProvince));a.postcode&&b.appendChild(this.createElementDefaultNS("PostCode",a.postcode));a.country&&b.appendChild(this.createElementDefaultNS("Country",a.country));return b},write_ol_MapExtension:function(a){var b=this.createElementDefaultNS("Extension");if(a=a.maxExtent){var c=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(c,{minx:a.left.toPrecision(18),miny:a.bottom.toPrecision(18),maxx:a.right.toPrecision(18),
+maxy:a.top.toPrecision(18)});b.appendChild(c)}return b},write_wmc_LayerList:function(a){for(var b=this.createElementDefaultNS("LayerList"),c=0,d=a.layersContext.length;c<d;++c)b.appendChild(this.write_wmc_Layer(a.layersContext[c]));return b},write_wmc_Layer:function(a){var b=this.createElementDefaultNS("Layer",null,{queryable:a.queryable?"1":"0",hidden:a.visibility?"0":"1"});b.appendChild(this.write_wmc_Server(a));b.appendChild(this.createElementDefaultNS("Name",a.name));b.appendChild(this.createElementDefaultNS("Title",
+a.title));a["abstract"]&&b.appendChild(this.createElementDefaultNS("Abstract",a["abstract"]));a.dataURL&&b.appendChild(this.write_wmc_URLType("DataURL",a.dataURL));a.metadataURL&&b.appendChild(this.write_wmc_URLType("MetadataURL",a.metadataURL));return b},write_wmc_LayerExtension:function(a){var b=this.createElementDefaultNS("Extension"),c=a.maxExtent,d=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(d,{minx:c.left.toPrecision(18),miny:c.bottom.toPrecision(18),maxx:c.right.toPrecision(18),
+maxy:c.top.toPrecision(18)});b.appendChild(d);a.tileSize&&!a.singleTile&&(c=this.createElementNS(this.namespaces.ol,"ol:tileSize"),this.setAttributes(c,a.tileSize),b.appendChild(c));for(var c="transparent numZoomLevels units isBaseLayer opacity displayInLayerSwitcher singleTile".split(" "),e=0,f=c.length;e<f;++e)(d=this.createOLPropertyNode(a,c[e]))&&b.appendChild(d);return b},createOLPropertyNode:function(a,b){var c=null;null!=a[b]&&(c=this.createElementNS(this.namespaces.ol,"ol:"+b),c.appendChild(this.createTextNode(a[b].toString())));
+return c},write_wmc_Server:function(a){a=a.server;var b=this.createElementDefaultNS("Server"),c={service:"OGC:WMS",version:a.version};a.title&&(c.title=a.title);this.setAttributes(b,c);b.appendChild(this.write_wmc_OnlineResource(a.url));return b},write_wmc_URLType:function(a,b,c){a=this.createElementDefaultNS(a);a.appendChild(this.write_wmc_OnlineResource(b));if(c){b=["width","height","format"];for(var d=0;d<b.length;d++)b[d]in c&&a.setAttribute(b[d],c[b[d]])}return a},write_wmc_DimensionList:function(a){var b=
+this.createElementDefaultNS("DimensionList"),c;for(c in a.dimensions){var d={},e=a.dimensions[c],f;for(f in e)d[f]="boolean"==typeof e[f]?Number(e[f]):e[f];e="";d.values&&(e=d.values.join(","),delete d.values);b.appendChild(this.createElementDefaultNS("Dimension",e,d))}return b},write_wmc_FormatList:function(a){for(var b=this.createElementDefaultNS("FormatList"),c=0,d=a.formats.length;c<d;c++){var e=a.formats[c];b.appendChild(this.createElementDefaultNS("Format",e.value,e.current&&!0==e.current?{current:"1"}:
+null))}return b},write_wmc_StyleList:function(a){var b=this.createElementDefaultNS("StyleList");if((a=a.styles)&&OpenLayers.Util.isArray(a))for(var c,d=0,e=a.length;d<e;d++){var f=a[d],g=this.createElementDefaultNS("Style",null,f.current&&!0==f.current?{current:"1"}:null);f.href?(c=this.createElementDefaultNS("SLD"),f.name&&c.appendChild(this.createElementDefaultNS("Name",f.name)),f.title&&c.appendChild(this.createElementDefaultNS("Title",f.title)),f.legend&&c.appendChild(this.write_wmc_URLType("LegendURL",
+f.legend.href,f.legend)),f=this.write_wmc_OnlineResource(f.href),c.appendChild(f),g.appendChild(c)):f.body?(c=this.createElementDefaultNS("SLD"),f.name&&c.appendChild(this.createElementDefaultNS("Name",f.name)),f.title&&c.appendChild(this.createElementDefaultNS("Title",f.title)),f.legend&&c.appendChild(this.write_wmc_URLType("LegendURL",f.legend.href,f.legend)),f=OpenLayers.Format.XML.prototype.read.apply(this,[f.body]).documentElement,c.ownerDocument&&c.ownerDocument.importNode&&(f=c.ownerDocument.importNode(f,
+!0)),c.appendChild(f),g.appendChild(c)):(g.appendChild(this.createElementDefaultNS("Name",f.name)),g.appendChild(this.createElementDefaultNS("Title",f.title)),f["abstract"]&&g.appendChild(this.createElementDefaultNS("Abstract",f["abstract"])),f.legend&&g.appendChild(this.write_wmc_URLType("LegendURL",f.legend.href,f.legend)));b.appendChild(g)}return b},write_wmc_OnlineResource:function(a){var b=this.createElementDefaultNS("OnlineResource");this.setAttributeNS(b,this.namespaces.xlink,"xlink:type",
+"simple");this.setAttributeNS(b,this.namespaces.xlink,"xlink:href",a);return b},getOnlineResource_href:function(a){var b={};a=a.getElementsByTagName("OnlineResource");0<a.length&&this.read_wmc_OnlineResource(b,a[0]);return b.href},CLASS_NAME:"OpenLayers.Format.WMC.v1"});OpenLayers.Control.PanPanel=OpenLayers.Class(OpenLayers.Control.Panel,{slideFactor:50,slideRatio:null,initialize:function(a){OpenLayers.Control.Panel.prototype.initialize.apply(this,[a]);a={slideFactor:this.slideFactor,slideRatio:this.slideRatio};this.addControls([new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH,a),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH,a),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST,a),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST,a)])},
+CLASS_NAME:"OpenLayers.Control.PanPanel"});OpenLayers.Control.Attribution=OpenLayers.Class(OpenLayers.Control,{separator:", ",template:"${layers}",destroy:function(){this.map.events.un({removelayer:this.updateAttribution,addlayer:this.updateAttribution,changelayer:this.updateAttribution,changebaselayer:this.updateAttribution,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.map.events.on({changebaselayer:this.updateAttribution,changelayer:this.updateAttribution,
+addlayer:this.updateAttribution,removelayer:this.updateAttribution,scope:this});this.updateAttribution();return this.div},updateAttribution:function(){var a=[];if(this.map&&this.map.layers){for(var b=0,c=this.map.layers.length;b<c;b++){var d=this.map.layers[b];d.attribution&&d.getVisibility()&&-1===OpenLayers.Util.indexOf(a,d.attribution)&&a.push(d.attribution)}this.div.innerHTML=OpenLayers.String.format(this.template,{layers:a.join(this.separator)})}},CLASS_NAME:"OpenLayers.Control.Attribution"});OpenLayers.Kinetic=OpenLayers.Class({threshold:0,deceleration:0.0035,nbPoints:100,delay:200,points:void 0,timerId:void 0,initialize:function(a){OpenLayers.Util.extend(this,a)},begin:function(){OpenLayers.Animation.stop(this.timerId);this.timerId=void 0;this.points=[]},update:function(a){this.points.unshift({xy:a,tick:(new Date).getTime()});this.points.length>this.nbPoints&&this.points.pop()},end:function(a){for(var b,c=(new Date).getTime(),d=0,e=this.points.length,f;d<e;d++){f=this.points[d];if(c-
+f.tick>this.delay)break;b=f}if(b&&(d=(new Date).getTime()-b.tick,c=Math.sqrt(Math.pow(a.x-b.xy.x,2)+Math.pow(a.y-b.xy.y,2)),d=c/d,!(0==d||d<this.threshold)))return c=Math.asin((a.y-b.xy.y)/c),b.xy.x<=a.x&&(c=Math.PI-c),{speed:d,theta:c}},move:function(a,b){var c=a.speed,d=Math.cos(a.theta),e=-Math.sin(a.theta),f=(new Date).getTime(),g=0,h=0;this.timerId=OpenLayers.Animation.start(OpenLayers.Function.bind(function(){if(null!=this.timerId){var a=(new Date).getTime()-f,l=-this.deceleration*Math.pow(a,
+2)/2+c*a,m=l*d,l=l*e,n,p;n=!1;0>=-this.deceleration*a+c&&(OpenLayers.Animation.stop(this.timerId),this.timerId=null,n=!0);a=m-g;p=l-h;g=m;h=l;b(a,p,n)}},this))},CLASS_NAME:"OpenLayers.Kinetic"});OpenLayers.Format.WPSExecute=OpenLayers.Class(OpenLayers.Format.XML,OpenLayers.Format.Filter.v1_1_0,{namespaces:{ows:"http://www.opengis.net/ows/1.1",gml:"http://www.opengis.net/gml",wps:"http://www.opengis.net/wps/1.0.0",wfs:"http://www.opengis.net/wfs",ogc:"http://www.opengis.net/ogc",wcs:"http://www.opengis.net/wcs",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},VERSION:"1.0.0",
+schemaLocation:"http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",schemaLocationAttr:function(a){},write:function(a){var b;window.ActiveXObject?this.xmldom=b=new ActiveXObject("Microsoft.XMLDOM"):b=document.implementation.createDocument("","",null);a=this.writeNode("wps:Execute",a,b);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},read:function(a){"string"==typeof a&&(a=
+OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},writers:{wps:{Execute:function(a){var b=this.createElementNSPlus("wps:Execute",{attributes:{version:this.VERSION,service:"WPS"}});this.writeNode("ows:Identifier",a.identifier,b);this.writeNode("wps:DataInputs",a.dataInputs,b);this.writeNode("wps:ResponseForm",a.responseForm,b);return b},ResponseForm:function(a){var b=this.createElementNSPlus("wps:ResponseForm",{});a.rawDataOutput&&
+this.writeNode("wps:RawDataOutput",a.rawDataOutput,b);a.responseDocument&&this.writeNode("wps:ResponseDocument",a.responseDocument,b);return b},ResponseDocument:function(a){var b=this.createElementNSPlus("wps:ResponseDocument",{attributes:{storeExecuteResponse:a.storeExecuteResponse,lineage:a.lineage,status:a.status}});if(a.outputs)for(var c=0,d=a.outputs.length;c<d;c++)this.writeNode("wps:Output",a.outputs[c],b);return b},Output:function(a){var b=this.createElementNSPlus("wps:Output",{attributes:{asReference:a.asReference,
+mimeType:a.mimeType,encoding:a.encoding,schema:a.schema}});this.writeNode("ows:Identifier",a.identifier,b);this.writeNode("ows:Title",a.title,b);this.writeNode("ows:Abstract",a["abstract"],b);return b},RawDataOutput:function(a){var b=this.createElementNSPlus("wps:RawDataOutput",{attributes:{mimeType:a.mimeType,encoding:a.encoding,schema:a.schema}});this.writeNode("ows:Identifier",a.identifier,b);return b},DataInputs:function(a){for(var b=this.createElementNSPlus("wps:DataInputs",{}),c=0,d=a.length;c<
+d;++c)this.writeNode("wps:Input",a[c],b);return b},Input:function(a){var b=this.createElementNSPlus("wps:Input",{});this.writeNode("ows:Identifier",a.identifier,b);a.title&&this.writeNode("ows:Title",a.title,b);a.data&&this.writeNode("wps:Data",a.data,b);a.reference&&this.writeNode("wps:Reference",a.reference,b);a.boundingBoxData&&this.writeNode("wps:BoundingBoxData",a.boundingBoxData,b);return b},Data:function(a){var b=this.createElementNSPlus("wps:Data",{});a.literalData?this.writeNode("wps:LiteralData",
+a.literalData,b):a.complexData?this.writeNode("wps:ComplexData",a.complexData,b):a.boundingBoxData&&this.writeNode("ows:BoundingBox",a.boundingBoxData,b);return b},LiteralData:function(a){return this.createElementNSPlus("wps:LiteralData",{attributes:{uom:a.uom},value:a.value})},ComplexData:function(a){var b=this.createElementNSPlus("wps:ComplexData",{attributes:{mimeType:a.mimeType,encoding:a.encoding,schema:a.schema}}),c=a.value;"string"===typeof c?b.appendChild(this.getXMLDoc().createCDATASection(a.value)):
+b.appendChild(c);return b},Reference:function(a){var b=this.createElementNSPlus("wps:Reference",{attributes:{mimeType:a.mimeType,"xlink:href":a.href,method:a.method,encoding:a.encoding,schema:a.schema}});a.body&&this.writeNode("wps:Body",a.body,b);return b},BoundingBoxData:function(a,b){this.writers.ows.BoundingBox.apply(this,[a,b,"wps:BoundingBoxData"])},Body:function(a){var b=this.createElementNSPlus("wps:Body",{});a.wcs?this.writeNode("wcs:GetCoverage",a.wcs,b):a.wfs?(this.featureType=a.wfs.featureType,
+this.version=a.wfs.version,this.writeNode("wfs:GetFeature",a.wfs,b)):this.writeNode("wps:Execute",a,b);return b}},wcs:OpenLayers.Format.WCSGetCoverage.prototype.writers.wcs,wfs:OpenLayers.Format.WFST.v1_1_0.prototype.writers.wfs,ogc:OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc,ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows},readers:{wps:{ExecuteResponse:function(a,b){b.executeResponse={lang:a.getAttribute("lang"),statusLocation:a.getAttribute("statusLocation"),serviceInstance:a.getAttribute("serviceInstance"),
+service:a.getAttribute("service")};this.readChildNodes(a,b.executeResponse)},Process:function(a,b){b.process={};this.readChildNodes(a,b.process)},Status:function(a,b){b.status={creationTime:a.getAttribute("creationTime")};this.readChildNodes(a,b.status)},ProcessSucceeded:function(a,b){b.processSucceeded=!0},ProcessOutputs:function(a,b){b.processOutputs=[];this.readChildNodes(a,b.processOutputs)},Output:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)},Reference:function(a,b){b.reference=
+{href:a.getAttribute("href"),mimeType:a.getAttribute("mimeType"),encoding:a.getAttribute("encoding"),schema:a.getAttribute("schema")}},Data:function(a,b){b.data={};this.readChildNodes(a,b)},LiteralData:function(a,b){b.literalData={dataType:a.getAttribute("dataType"),uom:a.getAttribute("uom"),value:this.getChildValue(a)}},ComplexData:function(a,b){b.complexData={mimeType:a.getAttribute("mimeType"),schema:a.getAttribute("schema"),encoding:a.getAttribute("encoding"),value:""};if(this.isSimpleContent(a)){var c;
+for(c=a.firstChild;c;c=c.nextSibling)switch(c.nodeType){case 3:case 4:b.complexData.value+=c.nodeValue}}else for(c=a.firstChild;c;c=c.nextSibling)1==c.nodeType&&(b.complexData.value=c)},BoundingBox:function(a,b){b.boundingBoxData={dimensions:a.getAttribute("dimensions"),crs:a.getAttribute("crs")};this.readChildNodes(a,b.boundingBoxData)}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WPSExecute"});OpenLayers.Layer.GeoRSS=OpenLayers.Class(OpenLayers.Layer.Markers,{location:null,features:null,formatOptions:null,selectedFeature:null,icon:null,popupSize:null,useFeedTitle:!0,initialize:function(a,b,c){OpenLayers.Layer.Markers.prototype.initialize.apply(this,[a,c]);this.location=b;this.features=[]},destroy:function(){OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);this.clearFeatures();this.features=null},loadRSS:function(){this.loaded||(this.events.triggerEvent("loadstart"),OpenLayers.Request.GET({url:this.location,
+success:this.parseData,scope:this}),this.loaded=!0)},moveTo:function(a,b,c){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);this.visibility&&!this.loaded&&this.loadRSS()},parseData:function(a){var b=a.responseXML;b&&b.documentElement||(b=OpenLayers.Format.XML.prototype.read(a.responseText));if(this.useFeedTitle){a=null;try{a=b.getElementsByTagNameNS("*","title")[0].firstChild.nodeValue}catch(c){a=b.getElementsByTagName("title")[0].firstChild.nodeValue}a&&this.setName(a)}a={};OpenLayers.Util.extend(a,
+this.formatOptions);this.map&&!this.projection.equals(this.map.getProjectionObject())&&(a.externalProjection=this.projection,a.internalProjection=this.map.getProjectionObject());b=(new OpenLayers.Format.GeoRSS(a)).read(b);a=0;for(var d=b.length;a<d;a++){var e={},f=b[a];if(f.geometry){var g=f.attributes.title?f.attributes.title:"Untitled",h=f.attributes.description?f.attributes.description:"No description.",k=f.attributes.link?f.attributes.link:"",f=f.geometry.getBounds().getCenterLonLat();e.icon=
+null==this.icon?OpenLayers.Marker.defaultIcon():this.icon.clone();e.popupSize=this.popupSize?this.popupSize.clone():new OpenLayers.Size(250,120);if(g||h){e.title=g;e.description=h;var l='<div class="olLayerGeoRSSClose">[x]</div>',l=l+'<div class="olLayerGeoRSSTitle">';k&&(l+='<a class="link" href="'+k+'" target="_blank">');l+=g;k&&(l+="</a>");l+="</div>";l+='<div style="" class="olLayerGeoRSSDescription">';l+=h;l+="</div>";e.popupContentHTML=l}f=new OpenLayers.Feature(this,f,e);this.features.push(f);
+e=f.createMarker();e.events.register("click",f,this.markerClick);this.addMarker(e)}}this.events.triggerEvent("loadend")},markerClick:function(a){var b=this==this.layer.selectedFeature;this.layer.selectedFeature=b?null:this;for(var c=0,d=this.layer.map.popups.length;c<d;c++)this.layer.map.removePopup(this.layer.map.popups[c]);b||(b=this.createPopup(),OpenLayers.Event.observe(b.div,"click",OpenLayers.Function.bind(function(){for(var a=0,b=this.layer.map.popups.length;a<b;a++)this.layer.map.removePopup(this.layer.map.popups[a])},
+this)),this.layer.map.addPopup(b));OpenLayers.Event.stop(a)},clearFeatures:function(){if(null!=this.features)for(;0<this.features.length;){var a=this.features[0];OpenLayers.Util.removeItem(this.features,a);a.destroy()}},CLASS_NAME:"OpenLayers.Layer.GeoRSS"});OpenLayers.Symbolizer.Point=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Point"});OpenLayers.Symbolizer.Line=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Line"});OpenLayers.Symbolizer.Text=OpenLayers.Class(OpenLayers.Symbolizer,{initialize:function(a){OpenLayers.Symbolizer.prototype.initialize.apply(this,arguments)},CLASS_NAME:"OpenLayers.Symbolizer.Text"});OpenLayers.Format.SLD.v1=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,{namespaces:{sld:"http://www.opengis.net/sld",ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"sld",schemaLocation:null,multipleSymbolizers:!1,featureTypeCounter:null,defaultSymbolizer:{fillColor:"#808080",fillOpacity:1,strokeColor:"#000000",strokeOpacity:1,strokeWidth:1,strokeDashstyle:"solid",pointRadius:3,
+graphicName:"square"},read:function(a,b){b=OpenLayers.Util.applyDefaults(b,this.options);var c={namedLayers:!0===b.namedLayersAsArray?[]:{}};this.readChildNodes(a,c);return c},readers:OpenLayers.Util.applyDefaults({sld:{StyledLayerDescriptor:function(a,b){b.version=a.getAttribute("version");this.readChildNodes(a,b)},Name:function(a,b){b.name=this.getChildValue(a)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b.description=this.getChildValue(a)},NamedLayer:function(a,b){var c=
+{userStyles:[],namedStyles:[]};this.readChildNodes(a,c);for(var d=0,e=c.userStyles.length;d<e;++d)c.userStyles[d].layerName=c.name;OpenLayers.Util.isArray(b.namedLayers)?b.namedLayers.push(c):b.namedLayers[c.name]=c},NamedStyle:function(a,b){b.namedStyles.push(this.getChildName(a.firstChild))},UserStyle:function(a,b){var c={defaultsPerSymbolizer:!0,rules:[]};this.featureTypeCounter=-1;this.readChildNodes(a,c);this.multipleSymbolizers?(delete c.defaultsPerSymbolizer,c=new OpenLayers.Style2(c)):c=new OpenLayers.Style(this.defaultSymbolizer,
+c);b.userStyles.push(c)},IsDefault:function(a,b){"1"==this.getChildValue(a)&&(b.isDefault=!0)},FeatureTypeStyle:function(a,b){++this.featureTypeCounter;var c={rules:this.multipleSymbolizers?b.rules:[]};this.readChildNodes(a,c);this.multipleSymbolizers||(b.rules=c.rules)},Rule:function(a,b){var c;this.multipleSymbolizers&&(c={symbolizers:[]});c=new OpenLayers.Rule(c);this.readChildNodes(a,c);b.rules.push(c)},ElseFilter:function(a,b){b.elseFilter=!0},MinScaleDenominator:function(a,b){b.minScaleDenominator=
+parseFloat(this.getChildValue(a))},MaxScaleDenominator:function(a,b){b.maxScaleDenominator=parseFloat(this.getChildValue(a))},TextSymbolizer:function(a,b){var c={};this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Text(c))):b.symbolizer.Text=OpenLayers.Util.applyDefaults(c,b.symbolizer.Text)},LabelPlacement:function(a,b){this.readChildNodes(a,b)},PointPlacement:function(a,b){var c={};this.readChildNodes(a,c);c.labelRotation=
+c.rotation;delete c.rotation;var d,e=b.labelAnchorPointX,f=b.labelAnchorPointY;e<=1/3?d="l":e>1/3&&e<2/3?d="c":e>=2/3&&(d="r");f<=1/3?d+="b":f>1/3&&f<2/3?d+="m":f>=2/3&&(d+="t");c.labelAlign=d;OpenLayers.Util.applyDefaults(b,c)},AnchorPoint:function(a,b){this.readChildNodes(a,b)},AnchorPointX:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelAnchorPointX=c)},AnchorPointY:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelAnchorPointY=c)},Displacement:function(a,
+b){this.readChildNodes(a,b)},DisplacementX:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelXOffset=c)},DisplacementY:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelYOffset=c)},LinePlacement:function(a,b){this.readChildNodes(a,b)},PerpendicularOffset:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.labelPerpendicularOffset=c)},Label:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.label=c)},Font:function(a,b){this.readChildNodes(a,
+b)},Halo:function(a,b){var c={};this.readChildNodes(a,c);b.haloRadius=c.haloRadius;b.haloColor=c.fillColor;b.haloOpacity=c.fillOpacity},Radius:function(a,b){var c=this.readers.ogc._expression.call(this,a);null!=c&&(b.haloRadius=c)},RasterSymbolizer:function(a,b){var c={};this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Raster(c))):b.symbolizer.Raster=OpenLayers.Util.applyDefaults(c,b.symbolizer.Raster)},Geometry:function(a,
+b){b.geometry={};this.readChildNodes(a,b.geometry)},ColorMap:function(a,b){b.colorMap=[];this.readChildNodes(a,b.colorMap)},ColorMapEntry:function(a,b){var c=a.getAttribute("quantity"),d=a.getAttribute("opacity");b.push({color:a.getAttribute("color"),quantity:null!==c?parseFloat(c):void 0,label:a.getAttribute("label")||void 0,opacity:null!==d?parseFloat(d):void 0})},LineSymbolizer:function(a,b){var c={};this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Line(c))):
+b.symbolizer.Line=OpenLayers.Util.applyDefaults(c,b.symbolizer.Line)},PolygonSymbolizer:function(a,b){var c={fill:!1,stroke:!1};this.multipleSymbolizers||(c=b.symbolizer.Polygon||c);this.readChildNodes(a,c);this.multipleSymbolizers?(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Polygon(c))):b.symbolizer.Polygon=c},PointSymbolizer:function(a,b){var c={fill:!1,stroke:!1,graphic:!1};this.multipleSymbolizers||(c=b.symbolizer.Point||c);this.readChildNodes(a,c);this.multipleSymbolizers?
+(c.zIndex=this.featureTypeCounter,b.symbolizers.push(new OpenLayers.Symbolizer.Point(c))):b.symbolizer.Point=c},Stroke:function(a,b){b.stroke=!0;this.readChildNodes(a,b)},Fill:function(a,b){b.fill=!0;this.readChildNodes(a,b)},CssParameter:function(a,b){var c=a.getAttribute("name"),d=this.cssMap[c];b.label&&("fill"===c?d="fontColor":"fill-opacity"===c&&(d="fontOpacity"));d&&(c=this.readers.ogc._expression.call(this,a))&&(b[d]=c)},Graphic:function(a,b){b.graphic=!0;var c={};this.readChildNodes(a,c);
+for(var d="stroke strokeColor strokeWidth strokeOpacity strokeLinecap fill fillColor fillOpacity graphicName rotation graphicFormat".split(" "),e,f,g=0,h=d.length;g<h;++g)e=d[g],f=c[e],void 0!=f&&(b[e]=f);void 0!=c.opacity&&(b.graphicOpacity=c.opacity);void 0!=c.size&&(isNaN(c.size/2)?b.graphicWidth=c.size:b.pointRadius=c.size/2);void 0!=c.href&&(b.externalGraphic=c.href);void 0!=c.rotation&&(b.rotation=c.rotation)},ExternalGraphic:function(a,b){this.readChildNodes(a,b)},Mark:function(a,b){this.readChildNodes(a,
+b)},WellKnownName:function(a,b){b.graphicName=this.getChildValue(a)},Opacity:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.opacity=c)},Size:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.size=c)},Rotation:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.rotation=c)},OnlineResource:function(a,b){b.href=this.getAttributeNS(a,this.namespaces.xlink,"href")},Format:function(a,b){b.graphicFormat=this.getChildValue(a)}}},OpenLayers.Format.Filter.v1_0_0.prototype.readers),
+cssMap:{stroke:"strokeColor","stroke-opacity":"strokeOpacity","stroke-width":"strokeWidth","stroke-linecap":"strokeLinecap","stroke-dasharray":"strokeDashstyle",fill:"fillColor","fill-opacity":"fillOpacity","font-family":"fontFamily","font-size":"fontSize","font-weight":"fontWeight","font-style":"fontStyle"},getCssProperty:function(a){var b=null,c;for(c in this.cssMap)if(this.cssMap[c]==a){b=c;break}return b},getGraphicFormat:function(a){var b,c;for(c in this.graphicFormats)if(this.graphicFormats[c].test(a)){b=
+c;break}return b||this.defaultGraphicFormat},defaultGraphicFormat:"image/png",graphicFormats:{"image/jpeg":/\.jpe?g$/i,"image/gif":/\.gif$/i,"image/png":/\.png$/i},write:function(a){return this.writers.sld.StyledLayerDescriptor.apply(this,[a])},writers:OpenLayers.Util.applyDefaults({sld:{_OGCExpression:function(a,b){var c=this.createElementNSPlus(a),d="string"==typeof b?b.split("${"):[b];c.appendChild(this.createTextNode(d[0]));for(var e,f,g=1,h=d.length;g<h;g++)e=d[g],f=e.indexOf("}"),0<f?(this.writeNode("ogc:PropertyName",
+{property:e.substring(0,f)},c),c.appendChild(this.createTextNode(e.substring(++f)))):c.appendChild(this.createTextNode("${"+e));return c},StyledLayerDescriptor:function(a){var b=this.createElementNSPlus("sld:StyledLayerDescriptor",{attributes:{version:this.VERSION,"xsi:schemaLocation":this.schemaLocation}});b.setAttribute("xmlns:ogc",this.namespaces.ogc);b.setAttribute("xmlns:gml",this.namespaces.gml);a.name&&this.writeNode("Name",a.name,b);a.title&&this.writeNode("Title",a.title,b);a.description&&
+this.writeNode("Abstract",a.description,b);if(OpenLayers.Util.isArray(a.namedLayers))for(var c=0,d=a.namedLayers.length;c<d;++c)this.writeNode("NamedLayer",a.namedLayers[c],b);else for(c in a.namedLayers)this.writeNode("NamedLayer",a.namedLayers[c],b);return b},Name:function(a){return this.createElementNSPlus("sld:Name",{value:a})},Title:function(a){return this.createElementNSPlus("sld:Title",{value:a})},Abstract:function(a){return this.createElementNSPlus("sld:Abstract",{value:a})},NamedLayer:function(a){var b=
+this.createElementNSPlus("sld:NamedLayer");this.writeNode("Name",a.name,b);if(a.namedStyles)for(var c=0,d=a.namedStyles.length;c<d;++c)this.writeNode("NamedStyle",a.namedStyles[c],b);if(a.userStyles)for(c=0,d=a.userStyles.length;c<d;++c)this.writeNode("UserStyle",a.userStyles[c],b);return b},NamedStyle:function(a){var b=this.createElementNSPlus("sld:NamedStyle");this.writeNode("Name",a,b);return b},UserStyle:function(a){var b=this.createElementNSPlus("sld:UserStyle");a.name&&this.writeNode("Name",
+a.name,b);a.title&&this.writeNode("Title",a.title,b);a.description&&this.writeNode("Abstract",a.description,b);a.isDefault&&this.writeNode("IsDefault",a.isDefault,b);if(this.multipleSymbolizers&&a.rules){for(var c={0:[]},d=[0],e,f,g,h,k,l=0,m=a.rules.length;l<m;++l)if(e=a.rules[l],e.symbolizers){f={};for(var n=0,p=e.symbolizers.length;n<p;++n)g=e.symbolizers[n],h=g.zIndex,h in f||(k=e.clone(),k.symbolizers=[],f[h]=k),f[h].symbolizers.push(g.clone());for(h in f)h in c||(d.push(h),c[h]=[]),c[h].push(f[h])}else c[0].push(e.clone());
+d.sort();l=0;for(m=d.length;l<m;++l)e=c[d[l]],0<e.length&&(k=a.clone(),k.rules=c[d[l]],this.writeNode("FeatureTypeStyle",k,b))}else this.writeNode("FeatureTypeStyle",a,b);return b},IsDefault:function(a){return this.createElementNSPlus("sld:IsDefault",{value:a?"1":"0"})},FeatureTypeStyle:function(a){for(var b=this.createElementNSPlus("sld:FeatureTypeStyle"),c=0,d=a.rules.length;c<d;++c)this.writeNode("Rule",a.rules[c],b);return b},Rule:function(a){var b=this.createElementNSPlus("sld:Rule");a.name&&
+this.writeNode("Name",a.name,b);a.title&&this.writeNode("Title",a.title,b);a.description&&this.writeNode("Abstract",a.description,b);a.elseFilter?this.writeNode("ElseFilter",null,b):a.filter&&this.writeNode("ogc:Filter",a.filter,b);void 0!=a.minScaleDenominator&&this.writeNode("MinScaleDenominator",a.minScaleDenominator,b);void 0!=a.maxScaleDenominator&&this.writeNode("MaxScaleDenominator",a.maxScaleDenominator,b);var c,d;if(this.multipleSymbolizers&&a.symbolizers)for(var e=0,f=a.symbolizers.length;e<
+f;++e)d=a.symbolizers[e],c=d.CLASS_NAME.split(".").pop(),this.writeNode(c+"Symbolizer",d,b);else for(var f=OpenLayers.Style.SYMBOLIZER_PREFIXES,e=0,g=f.length;e<g;++e)c=f[e],(d=a.symbolizer[c])&&this.writeNode(c+"Symbolizer",d,b);return b},ElseFilter:function(){return this.createElementNSPlus("sld:ElseFilter")},MinScaleDenominator:function(a){return this.createElementNSPlus("sld:MinScaleDenominator",{value:a})},MaxScaleDenominator:function(a){return this.createElementNSPlus("sld:MaxScaleDenominator",
+{value:a})},LineSymbolizer:function(a){var b=this.createElementNSPlus("sld:LineSymbolizer");this.writeNode("Stroke",a,b);return b},Stroke:function(a){var b=this.createElementNSPlus("sld:Stroke");void 0!=a.strokeColor&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeColor"},b);void 0!=a.strokeOpacity&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeOpacity"},b);void 0!=a.strokeWidth&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeWidth"},b);void 0!=a.strokeDashstyle&&"solid"!==
+a.strokeDashstyle&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeDashstyle"},b);void 0!=a.strokeLinecap&&this.writeNode("CssParameter",{symbolizer:a,key:"strokeLinecap"},b);return b},CssParameter:function(a){return this.createElementNSPlus("sld:CssParameter",{attributes:{name:this.getCssProperty(a.key)},value:a.symbolizer[a.key]})},TextSymbolizer:function(a){var b=this.createElementNSPlus("sld:TextSymbolizer");null!=a.label&&this.writeNode("Label",a.label,b);null==a.fontFamily&&null==a.fontSize&&
+null==a.fontWeight&&null==a.fontStyle||this.writeNode("Font",a,b);null==a.labelAnchorPointX&&null==a.labelAnchorPointY&&null==a.labelAlign&&null==a.labelXOffset&&null==a.labelYOffset&&null==a.labelRotation&&null==a.labelPerpendicularOffset||this.writeNode("LabelPlacement",a,b);null==a.haloRadius&&null==a.haloColor&&null==a.haloOpacity||this.writeNode("Halo",a,b);null==a.fontColor&&null==a.fontOpacity||this.writeNode("Fill",{fillColor:a.fontColor,fillOpacity:a.fontOpacity},b);return b},LabelPlacement:function(a){var b=
+this.createElementNSPlus("sld:LabelPlacement");null==a.labelAnchorPointX&&null==a.labelAnchorPointY&&null==a.labelAlign&&null==a.labelXOffset&&null==a.labelYOffset&&null==a.labelRotation||null!=a.labelPerpendicularOffset||this.writeNode("PointPlacement",a,b);null!=a.labelPerpendicularOffset&&this.writeNode("LinePlacement",a,b);return b},LinePlacement:function(a){var b=this.createElementNSPlus("sld:LinePlacement");this.writeNode("PerpendicularOffset",a.labelPerpendicularOffset,b);return b},PerpendicularOffset:function(a){return this.createElementNSPlus("sld:PerpendicularOffset",
+{value:a})},PointPlacement:function(a){var b=this.createElementNSPlus("sld:PointPlacement");null==a.labelAnchorPointX&&null==a.labelAnchorPointY&&null==a.labelAlign||this.writeNode("AnchorPoint",a,b);null==a.labelXOffset&&null==a.labelYOffset||this.writeNode("Displacement",a,b);null!=a.labelRotation&&this.writeNode("Rotation",a.labelRotation,b);return b},AnchorPoint:function(a){var b=this.createElementNSPlus("sld:AnchorPoint"),c=a.labelAnchorPointX,d=a.labelAnchorPointY;null!=c&&this.writeNode("AnchorPointX",
+c,b);null!=d&&this.writeNode("AnchorPointY",d,b);if(null==c&&null==d){var e=a.labelAlign.substr(0,1);a=a.labelAlign.substr(1,1);"l"===e?c=0:"c"===e?c=0.5:"r"===e&&(c=1);"b"===a?d=0:"m"===a?d=0.5:"t"===a&&(d=1);this.writeNode("AnchorPointX",c,b);this.writeNode("AnchorPointY",d,b)}return b},AnchorPointX:function(a){return this.createElementNSPlus("sld:AnchorPointX",{value:a})},AnchorPointY:function(a){return this.createElementNSPlus("sld:AnchorPointY",{value:a})},Displacement:function(a){var b=this.createElementNSPlus("sld:Displacement");
+null!=a.labelXOffset&&this.writeNode("DisplacementX",a.labelXOffset,b);null!=a.labelYOffset&&this.writeNode("DisplacementY",a.labelYOffset,b);return b},DisplacementX:function(a){return this.createElementNSPlus("sld:DisplacementX",{value:a})},DisplacementY:function(a){return this.createElementNSPlus("sld:DisplacementY",{value:a})},Font:function(a){var b=this.createElementNSPlus("sld:Font");a.fontFamily&&this.writeNode("CssParameter",{symbolizer:a,key:"fontFamily"},b);a.fontSize&&this.writeNode("CssParameter",
+{symbolizer:a,key:"fontSize"},b);a.fontWeight&&this.writeNode("CssParameter",{symbolizer:a,key:"fontWeight"},b);a.fontStyle&&this.writeNode("CssParameter",{symbolizer:a,key:"fontStyle"},b);return b},Label:function(a){return this.writers.sld._OGCExpression.call(this,"sld:Label",a)},Halo:function(a){var b=this.createElementNSPlus("sld:Halo");a.haloRadius&&this.writeNode("Radius",a.haloRadius,b);(a.haloColor||a.haloOpacity)&&this.writeNode("Fill",{fillColor:a.haloColor,fillOpacity:a.haloOpacity},b);
+return b},Radius:function(a){return this.createElementNSPlus("sld:Radius",{value:a})},RasterSymbolizer:function(a){var b=this.createElementNSPlus("sld:RasterSymbolizer");a.geometry&&this.writeNode("Geometry",a.geometry,b);a.opacity&&this.writeNode("Opacity",a.opacity,b);a.colorMap&&this.writeNode("ColorMap",a.colorMap,b);return b},Geometry:function(a){var b=this.createElementNSPlus("sld:Geometry");a.property&&this.writeNode("ogc:PropertyName",a,b);return b},ColorMap:function(a){for(var b=this.createElementNSPlus("sld:ColorMap"),
+c=0,d=a.length;c<d;++c)this.writeNode("ColorMapEntry",a[c],b);return b},ColorMapEntry:function(a){var b=this.createElementNSPlus("sld:ColorMapEntry");b.setAttribute("color",a.color);void 0!==a.opacity&&b.setAttribute("opacity",parseFloat(a.opacity));void 0!==a.quantity&&b.setAttribute("quantity",parseFloat(a.quantity));void 0!==a.label&&b.setAttribute("label",a.label);return b},PolygonSymbolizer:function(a){var b=this.createElementNSPlus("sld:PolygonSymbolizer");!1!==a.fill&&this.writeNode("Fill",
+a,b);!1!==a.stroke&&this.writeNode("Stroke",a,b);return b},Fill:function(a){var b=this.createElementNSPlus("sld:Fill");a.fillColor&&this.writeNode("CssParameter",{symbolizer:a,key:"fillColor"},b);null!=a.fillOpacity&&this.writeNode("CssParameter",{symbolizer:a,key:"fillOpacity"},b);return b},PointSymbolizer:function(a){var b=this.createElementNSPlus("sld:PointSymbolizer");this.writeNode("Graphic",a,b);return b},Graphic:function(a){var b=this.createElementNSPlus("sld:Graphic");void 0!=a.externalGraphic?
+this.writeNode("ExternalGraphic",a,b):this.writeNode("Mark",a,b);void 0!=a.graphicOpacity&&this.writeNode("Opacity",a.graphicOpacity,b);void 0!=a.pointRadius?this.writeNode("Size",2*a.pointRadius,b):void 0!=a.graphicWidth&&this.writeNode("Size",a.graphicWidth,b);void 0!=a.rotation&&this.writeNode("Rotation",a.rotation,b);return b},ExternalGraphic:function(a){var b=this.createElementNSPlus("sld:ExternalGraphic");this.writeNode("OnlineResource",a.externalGraphic,b);a=a.graphicFormat||this.getGraphicFormat(a.externalGraphic);
+this.writeNode("Format",a,b);return b},Mark:function(a){var b=this.createElementNSPlus("sld:Mark");a.graphicName&&this.writeNode("WellKnownName",a.graphicName,b);!1!==a.fill&&this.writeNode("Fill",a,b);!1!==a.stroke&&this.writeNode("Stroke",a,b);return b},WellKnownName:function(a){return this.createElementNSPlus("sld:WellKnownName",{value:a})},Opacity:function(a){return this.createElementNSPlus("sld:Opacity",{value:a})},Size:function(a){return this.writers.sld._OGCExpression.call(this,"sld:Size",
+a)},Rotation:function(a){return this.createElementNSPlus("sld:Rotation",{value:a})},OnlineResource:function(a){return this.createElementNSPlus("sld:OnlineResource",{attributes:{"xlink:type":"simple","xlink:href":a}})},Format:function(a){return this.createElementNSPlus("sld:Format",{value:a})}}},OpenLayers.Format.Filter.v1_0_0.prototype.writers),CLASS_NAME:"OpenLayers.Format.SLD.v1"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1",request:"GetMap",styles:"",format:"image/jpeg"},isBaseLayer:!0,encodeBBOX:!1,noMagic:!1,yx:{},initialize:function(a,b,c,d){var e=[];c=OpenLayers.Util.upperCaseObject(c);1.3<=parseFloat(c.VERSION)&&!c.EXCEPTIONS&&(c.EXCEPTIONS="INIMAGE");e.push(a,b,c,d);OpenLayers.Layer.Grid.prototype.initialize.apply(this,e);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));
+!this.noMagic&&(this.params.TRANSPARENT&&"true"==this.params.TRANSPARENT.toString().toLowerCase())&&(null!=d&&d.isBaseLayer||(this.isBaseLayer=!1),"image/jpeg"==this.params.FORMAT&&(this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png"))},clone:function(a){null==a&&(a=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},reverseAxisOrder:function(){var a=this.projection.getCode();return 1.3<=parseFloat(this.params.VERSION)&&
+!!(this.yx[a]||OpenLayers.Projection.defaults[a]&&OpenLayers.Projection.defaults[a].yx)},getURL:function(a){a=this.adjustBounds(a);var b=this.getImageSize(),c={},d=this.reverseAxisOrder();c.BBOX=this.encodeBBOX?a.toBBOX(null,d):a.toArray(d);c.WIDTH=b.w;c.HEIGHT=b.h;return this.getFullRequestString(c)},mergeNewParams:function(a){a=[OpenLayers.Util.upperCaseObject(a)];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,a)},getFullRequestString:function(a,b){var c=this.map.getProjectionObject(),
+c=this.projection&&this.projection.equals(c)?this.projection.getCode():c.getCode(),c="none"==c?null:c;1.3<=parseFloat(this.params.VERSION)?this.params.CRS=c:this.params.SRS=c;"boolean"==typeof this.params.TRANSPARENT&&(a.TRANSPARENT=this.params.TRANSPARENT?"TRUE":"FALSE");return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments)},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.Layer.KaMap=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,DEFAULT_PARAMS:{i:"jpeg",map:""},initialize:function(a,b,c,d){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS)},getURL:function(a){a=this.adjustBounds(a);var b=this.map.getResolution(),c=Math.round(1E4*this.map.getScale())/1E4,d=Math.round(a.left/b);a=-Math.round(a.top/b);return this.getFullRequestString({t:a,l:d,s:c})},calculateGridLayout:function(a,
+b,c){b=c*this.tileSize.w;c*=this.tileSize.h;return{tilelon:b,tilelat:c,startcol:Math.floor(a.left/b)-this.buffer,startrow:Math.floor(a.top/c)+this.buffer}},getTileBoundsForGridIndex:function(a,b){this.getTileOrigin();var c=this.gridLayout,d=c.tilelon,e=c.tilelat,f=(c.startcol+b)*d,c=(c.startrow-a)*e;return new OpenLayers.Bounds(f,c,f+d,c+e)},clone:function(a){null==a&&(a=new OpenLayers.Layer.KaMap(this.name,this.url,this.params,this.getOptions()));a=OpenLayers.Layer.Grid.prototype.clone.apply(this,
+[a]);null!=this.tileSize&&(a.tileSize=this.tileSize.clone());a.grid=[];return a},getTileBounds:function(a){var b=this.getResolution(),c=b*this.tileSize.w,b=b*this.tileSize.h,d=this.getLonLatFromViewPortPx(a);a=c*Math.floor(d.lon/c);d=b*Math.floor(d.lat/b);return new OpenLayers.Bounds(a,d,a+c,d+b)},CLASS_NAME:"OpenLayers.Layer.KaMap"});OpenLayers.Format.WMC.v1_1_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.1.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",initialize:function(a){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[a])},read_sld_MinScaleDenominator:function(a,b){var c=parseFloat(this.getChildValue(b));0<c&&(a.maxScale=c)},read_sld_MaxScaleDenominator:function(a,b){a.minScale=parseFloat(this.getChildValue(b))},read_wmc_SRS:function(a,b){"srs"in
+a||(a.srs={});a.srs[this.getChildValue(b)]=!0},write_wmc_Layer:function(a){var b=OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(this,[a]);if(a.maxScale){var c=this.createElementNS(this.namespaces.sld,"sld:MinScaleDenominator");c.appendChild(this.createTextNode(a.maxScale.toPrecision(16)));b.appendChild(c)}a.minScale&&(c=this.createElementNS(this.namespaces.sld,"sld:MaxScaleDenominator"),c.appendChild(this.createTextNode(a.minScale.toPrecision(16))),b.appendChild(c));if(a.srs)for(var d in a.srs)b.appendChild(this.createElementDefaultNS("SRS",
+d));b.appendChild(this.write_wmc_FormatList(a));b.appendChild(this.write_wmc_StyleList(a));a.dimensions&&b.appendChild(this.write_wmc_DimensionList(a));b.appendChild(this.write_wmc_LayerExtension(a));return b},CLASS_NAME:"OpenLayers.Format.WMC.v1_1_0"});OpenLayers.Format.XLS=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.1.0",stringifyOutput:!0,CLASS_NAME:"OpenLayers.Format.XLS"});OpenLayers.Format.XLS.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xls:"http://www.opengis.net/xls",gml:"http://www.opengis.net/gml",xsi:"http://www.w3.org/2001/XMLSchema-instance"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},xy:!0,defaultPrefix:"xls",schemaLocation:null,read:function(a,b){OpenLayers.Util.applyDefaults(b,this.options);var c={};this.readChildNodes(a,c);return c},readers:{xls:{XLS:function(a,b){b.version=a.getAttribute("version");
+this.readChildNodes(a,b)},Response:function(a,b){this.readChildNodes(a,b)},GeocodeResponse:function(a,b){b.responseLists=[];this.readChildNodes(a,b)},GeocodeResponseList:function(a,b){var c={features:[],numberOfGeocodedAddresses:parseInt(a.getAttribute("numberOfGeocodedAddresses"))};b.responseLists.push(c);this.readChildNodes(a,c)},GeocodedAddress:function(a,b){var c=new OpenLayers.Feature.Vector;b.features.push(c);this.readChildNodes(a,c);c.geometry=c.components[0]},GeocodeMatchCode:function(a,b){b.attributes.matchCode=
+{accuracy:parseFloat(a.getAttribute("accuracy")),matchType:a.getAttribute("matchType")}},Address:function(a,b){var c={countryCode:a.getAttribute("countryCode"),addressee:a.getAttribute("addressee"),street:[],place:[]};b.attributes.address=c;this.readChildNodes(a,c)},freeFormAddress:function(a,b){b.freeFormAddress=this.getChildValue(a)},StreetAddress:function(a,b){this.readChildNodes(a,b)},Building:function(a,b){b.building={number:a.getAttribute("number"),subdivision:a.getAttribute("subdivision"),
+buildingName:a.getAttribute("buildingName")}},Street:function(a,b){b.street.push(this.getChildValue(a))},Place:function(a,b){b.place[a.getAttribute("type")]=this.getChildValue(a)},PostalCode:function(a,b){b.postalCode=this.getChildValue(a)}},gml:OpenLayers.Format.GML.v3.prototype.readers.gml},write:function(a){return this.writers.xls.XLS.apply(this,[a])},writers:{xls:{XLS:function(a){var b=this.createElementNSPlus("xls:XLS",{attributes:{version:this.VERSION,"xsi:schemaLocation":this.schemaLocation}});
+this.writeNode("RequestHeader",a.header,b);this.writeNode("Request",a,b);return b},RequestHeader:function(a){return this.createElementNSPlus("xls:RequestHeader")},Request:function(a){var b=this.createElementNSPlus("xls:Request",{attributes:{methodName:"GeocodeRequest",requestID:a.requestID||"",version:this.VERSION}});this.writeNode("GeocodeRequest",a.addresses,b);return b},GeocodeRequest:function(a){for(var b=this.createElementNSPlus("xls:GeocodeRequest"),c=0,d=a.length;c<d;c++)this.writeNode("Address",
+a[c],b);return b},Address:function(a){var b=this.createElementNSPlus("xls:Address",{attributes:{countryCode:a.countryCode}});a.freeFormAddress?this.writeNode("freeFormAddress",a.freeFormAddress,b):(a.street&&this.writeNode("StreetAddress",a,b),a.municipality&&this.writeNode("Municipality",a.municipality,b),a.countrySubdivision&&this.writeNode("CountrySubdivision",a.countrySubdivision,b),a.postalCode&&this.writeNode("PostalCode",a.postalCode,b));return b},freeFormAddress:function(a){return this.createElementNSPlus("freeFormAddress",
+{value:a})},StreetAddress:function(a){var b=this.createElementNSPlus("xls:StreetAddress");a.building&&this.writeNode(b,"Building",a.building);a=a.street;OpenLayers.Util.isArray(a)||(a=[a]);for(var c=0,d=a.length;c<d;c++)this.writeNode("Street",a[c],b);return b},Building:function(a){return this.createElementNSPlus("xls:Building",{attributes:{number:a.number,subdivision:a.subdivision,buildingName:a.buildingName}})},Street:function(a){return this.createElementNSPlus("xls:Street",{value:a})},Municipality:function(a){return this.createElementNSPlus("xls:Place",
+{attributes:{type:"Municipality"},value:a})},CountrySubdivision:function(a){return this.createElementNSPlus("xls:Place",{attributes:{type:"CountrySubdivision"},value:a})},PostalCode:function(a){return this.createElementNSPlus("xls:PostalCode",{value:a})}}},CLASS_NAME:"OpenLayers.Format.XLS.v1"});OpenLayers.Format.XLS.v1_1_0=OpenLayers.Class(OpenLayers.Format.XLS.v1,{VERSION:"1.1",schemaLocation:"http://www.opengis.net/xls http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd",CLASS_NAME:"OpenLayers.Format.XLS.v1_1_0"});OpenLayers.Format.XLS.v1_1=OpenLayers.Format.XLS.v1_1_0;OpenLayers.Renderer.SVG=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"http://www.w3.org/2000/svg",xlinkns:"http://www.w3.org/1999/xlink",MAX_PIXEL:15E3,translationParameters:null,symbolMetrics:null,initialize:function(a){this.supported()&&(OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments),this.translationParameters={x:0,y:0},this.symbolMetrics={})},supported:function(){return document.implementation&&(document.implementation.hasFeature("org.w3c.svg","1.0")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG",
+"1.1")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"))},inValidRange:function(a,b,c){a+=c?0:this.translationParameters.x;b+=c?0:this.translationParameters.y;return a>=-this.MAX_PIXEL&&a<=this.MAX_PIXEL&&b>=-this.MAX_PIXEL&&b<=this.MAX_PIXEL},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=-a.left/d,d=a.top/d;if(b)return this.left=e,this.top=d,this.rendererRoot.setAttributeNS(null,
+"viewBox","0 0 "+this.size.w+" "+this.size.h),this.translate(this.xOffset,0),!0;(e=this.translate(e-this.left+this.xOffset,d-this.top))||this.setExtent(a,!0);return c&&e},translate:function(a,b){if(this.inValidRange(a,b,!0)){var c="";if(a||b)c="translate("+a+","+b+")";this.root.setAttributeNS(null,"transform",c);this.translationParameters={x:a,y:b};return!0}return!1},setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"width",this.size.w);
+this.rendererRoot.setAttributeNS(null,"height",this.size.h)},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"image":this.isComplexSymbol(b.graphicName)?"svg":"circle";break;case "OpenLayers.Geometry.Rectangle":c="rect";break;case "OpenLayers.Geometry.LineString":c="polyline";break;case "OpenLayers.Geometry.LinearRing":c="polygon";break;case "OpenLayers.Geometry.Polygon":case "OpenLayers.Geometry.Curve":c="path"}return c},setStyle:function(a,
+b,c){b=b||a._style;c=c||a._options;var d=b.title||b.graphicTitle;if(d){a.setAttributeNS(null,"title",d);var e=a.getElementsByTagName("title");0<e.length?e[0].firstChild.textContent=d:(e=this.nodeFactory(null,"title"),e.textContent=d,a.appendChild(e))}var e=parseFloat(a.getAttributeNS(null,"r")),d=1,f;if("OpenLayers.Geometry.Point"==a._geometryClass&&e){a.style.visibility="";if(!1===b.graphic)a.style.visibility="hidden";else if(b.externalGraphic){f=this.getPosition(a);b.graphicWidth&&b.graphicHeight&&
+a.setAttributeNS(null,"preserveAspectRatio","none");var e=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),k=b.graphicOpacity||b.fillOpacity;a.setAttributeNS(null,"x",(f.x+(void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e))).toFixed());a.setAttributeNS(null,"y",(f.y+h).toFixed());a.setAttributeNS(null,"width",e);a.setAttributeNS(null,"height",g);a.setAttributeNS(this.xlinkns,"xlink:href",
+b.externalGraphic);a.setAttributeNS(null,"style","opacity: "+k);a.onclick=OpenLayers.Event.preventDefault}else if(this.isComplexSymbol(b.graphicName)){var e=3*b.pointRadius,g=2*e,l=this.importSymbol(b.graphicName);f=this.getPosition(a);d=3*this.symbolMetrics[l.id][0]/g;h=a.parentNode;k=a.nextSibling;h&&h.removeChild(a);a.firstChild&&a.removeChild(a.firstChild);a.appendChild(l.firstChild.cloneNode(!0));a.setAttributeNS(null,"viewBox",l.getAttributeNS(null,"viewBox"));a.setAttributeNS(null,"width",
+g);a.setAttributeNS(null,"height",g);a.setAttributeNS(null,"x",f.x-e);a.setAttributeNS(null,"y",f.y-e);k?h.insertBefore(a,k):h&&h.appendChild(a)}else a.setAttributeNS(null,"r",b.pointRadius);e=b.rotation;void 0===e&&void 0===a._rotation||!f||(a._rotation=e,e|=0,"svg"!==a.nodeName?a.setAttributeNS(null,"transform","rotate("+e+" "+f.x+" "+f.y+")"):(f=this.symbolMetrics[l.id],a.firstChild.setAttributeNS(null,"transform","rotate("+e+" "+f[1]+" "+f[2]+")")))}c.isFilled?(a.setAttributeNS(null,"fill",b.fillColor),
+a.setAttributeNS(null,"fill-opacity",b.fillOpacity)):a.setAttributeNS(null,"fill","none");c.isStroked?(a.setAttributeNS(null,"stroke",b.strokeColor),a.setAttributeNS(null,"stroke-opacity",b.strokeOpacity),a.setAttributeNS(null,"stroke-width",b.strokeWidth*d),a.setAttributeNS(null,"stroke-linecap",b.strokeLinecap||"round"),a.setAttributeNS(null,"stroke-linejoin","round"),b.strokeDashstyle&&a.setAttributeNS(null,"stroke-dasharray",this.dashStyle(b,d))):a.setAttributeNS(null,"stroke","none");b.pointerEvents&&
+a.setAttributeNS(null,"pointer-events",b.pointerEvents);null!=b.cursor&&a.setAttributeNS(null,"cursor",b.cursor);return a},dashStyle:function(a,b){var c=a.strokeWidth*b,d=a.strokeDashstyle;switch(d){case "solid":return"none";case "dot":return[1,4*c].join();case "dash":return[4*c,4*c].join();case "dashdot":return[4*c,4*c,1,4*c].join();case "longdash":return[8*c,4*c].join();case "longdashdot":return[8*c,4*c,1,4*c].join();default:return OpenLayers.String.trim(d).replace(/\s+/g,",")}},createNode:function(a,
+b){var c=document.createElementNS(this.xmlns,a);b&&c.setAttributeNS(null,"id",b);return c},nodeTypeCompare:function(a,b){return b==a.nodeName},createRenderRoot:function(){var a=this.nodeFactory(this.container.id+"_svgRoot","svg");a.style.display="block";return a},createRoot:function(a){return this.nodeFactory(this.container.id+a,"g")},createDefs:function(){var a=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(a);return a},drawPoint:function(a,b){return this.drawCircle(a,
+b,1)},drawCircle:function(a,b,c){var d=this.getResolution(),e=(b.x-this.featureDx)/d+this.left;b=this.top-b.y/d;return this.inValidRange(e,b)?(a.setAttributeNS(null,"cx",e),a.setAttributeNS(null,"cy",b),a.setAttributeNS(null,"r",c),a):!1},drawLineString:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,"points",c.path),c.complete?a:null):!1},drawLinearRing:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,
+"points",c.path),c.complete?a:null):!1},drawPolygon:function(a,b){for(var c="",d=!0,e=!0,f,g,h=0,k=b.components.length;h<k;h++)c+=" M",f=this.getComponentsString(b.components[h].components," "),(g=f.path)?(c+=" "+g,e=f.complete&&e):d=!1;return d?(a.setAttributeNS(null,"d",c+" z"),a.setAttributeNS(null,"fill-rule","evenodd"),e?a:null):!1},drawRectangle:function(a,b){var c=this.getResolution(),d=(b.x-this.featureDx)/c+this.left,e=this.top-b.y/c;return this.inValidRange(d,e)?(a.setAttributeNS(null,"x",
+d),a.setAttributeNS(null,"y",e),a.setAttributeNS(null,"width",b.width/c),a.setAttributeNS(null,"height",b.height/c),a):!1},drawText:function(a,b,c){var d=!!b.labelOutlineWidth;if(d){var e=OpenLayers.Util.extend({},b);e.fontColor=e.labelOutlineColor;e.fontStrokeColor=e.labelOutlineColor;e.fontStrokeWidth=b.labelOutlineWidth;b.labelOutlineOpacity&&(e.fontOpacity=b.labelOutlineOpacity);delete e.labelOutlineWidth;this.drawText(a,e,c)}var f=this.getResolution(),e=(c.x-this.featureDx)/f+this.left,g=c.y/
+f-this.top,d=d?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX,f=this.nodeFactory(a+d,"text");f.setAttributeNS(null,"x",e);f.setAttributeNS(null,"y",-g);b.fontColor&&f.setAttributeNS(null,"fill",b.fontColor);b.fontStrokeColor&&f.setAttributeNS(null,"stroke",b.fontStrokeColor);b.fontStrokeWidth&&f.setAttributeNS(null,"stroke-width",b.fontStrokeWidth);b.fontOpacity&&f.setAttributeNS(null,"opacity",b.fontOpacity);b.fontFamily&&f.setAttributeNS(null,"font-family",b.fontFamily);b.fontSize&&f.setAttributeNS(null,
+"font-size",b.fontSize);b.fontWeight&&f.setAttributeNS(null,"font-weight",b.fontWeight);b.fontStyle&&f.setAttributeNS(null,"font-style",b.fontStyle);!0===b.labelSelect?(f.setAttributeNS(null,"pointer-events","visible"),f._featureId=a):f.setAttributeNS(null,"pointer-events","none");g=b.labelAlign||OpenLayers.Renderer.defaultSymbolizer.labelAlign;f.setAttributeNS(null,"text-anchor",OpenLayers.Renderer.SVG.LABEL_ALIGN[g[0]]||"middle");!0===OpenLayers.IS_GECKO&&f.setAttributeNS(null,"dominant-baseline",
+OpenLayers.Renderer.SVG.LABEL_ALIGN[g[1]]||"central");for(var h=b.label.split("\n"),k=h.length;f.childNodes.length>k;)f.removeChild(f.lastChild);for(var l=0;l<k;l++){var m=this.nodeFactory(a+d+"_tspan_"+l,"tspan");!0===b.labelSelect&&(m._featureId=a,m._geometry=c,m._geometryClass=c.CLASS_NAME);!1===OpenLayers.IS_GECKO&&m.setAttributeNS(null,"baseline-shift",OpenLayers.Renderer.SVG.LABEL_VSHIFT[g[1]]||"-35%");m.setAttribute("x",e);if(0==l){var n=OpenLayers.Renderer.SVG.LABEL_VFACTOR[g[1]];null==n&&
+(n=-0.5);m.setAttribute("dy",n*(k-1)+"em")}else m.setAttribute("dy","1em");m.textContent=""===h[l]?" ":h[l];m.parentNode||f.appendChild(m)}f.parentNode||this.textRoot.appendChild(f)},getComponentsString:function(a,b){for(var c=[],d=!0,e=a.length,f=[],g,h=0;h<e;h++)g=a[h],c.push(g),(g=this.getShortString(g))?f.push(g):(0<h&&this.getShortString(a[h-1])&&f.push(this.clipLine(a[h],a[h-1])),h<e-1&&this.getShortString(a[h+1])&&f.push(this.clipLine(a[h],a[h+1])),d=!1);return{path:f.join(b||","),complete:d}},
+clipLine:function(a,b){if(b.equals(a))return"";var c=this.getResolution(),d=this.MAX_PIXEL-this.translationParameters.x,e=this.MAX_PIXEL-this.translationParameters.y,f=(b.x-this.featureDx)/c+this.left,g=this.top-b.y/c,h=(a.x-this.featureDx)/c+this.left,c=this.top-a.y/c,k;if(h<-d||h>d)k=(c-g)/(h-f),h=0>h?-d:d,c=g+(h-f)*k;if(c<-e||c>e)k=(h-f)/(c-g),c=0>c?-e:e,h=f+(c-g)*k;return h+","+c},getShortString:function(a){var b=this.getResolution(),c=(a.x-this.featureDx)/b+this.left;a=this.top-a.y/b;return this.inValidRange(c,
+a)?c+","+a:!1},getPosition:function(a){return{x:parseFloat(a.getAttributeNS(null,"cx")),y:parseFloat(a.getAttributeNS(null,"cy"))}},importSymbol:function(a){this.defs||(this.defs=this.createDefs());var b=this.container.id+"-"+a,c=document.getElementById(b);if(null!=c)return c;var d=OpenLayers.Renderer.symbol[a];if(!d)throw Error(a+" is not a valid symbol name");a=this.nodeFactory(b,"symbol");var e=this.nodeFactory(null,"polygon");a.appendChild(e);for(var c=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,
+0,0),f=[],g,h,k=0;k<d.length;k+=2)g=d[k],h=d[k+1],c.left=Math.min(c.left,g),c.bottom=Math.min(c.bottom,h),c.right=Math.max(c.right,g),c.top=Math.max(c.top,h),f.push(g,",",h);e.setAttributeNS(null,"points",f.join(" "));d=c.getWidth();e=c.getHeight();a.setAttributeNS(null,"viewBox",[c.left-d,c.bottom-e,3*d,3*e].join(" "));this.symbolMetrics[b]=[Math.max(d,e),c.getCenterLonLat().lon,c.getCenterLonLat().lat];this.defs.appendChild(a);return a},getFeatureIdFromEvent:function(a){var b=OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this,
+arguments);b||(b=a.target,b=b.parentNode&&b!=this.rendererRoot?b.parentNode._featureId:void 0);return b},CLASS_NAME:"OpenLayers.Renderer.SVG"});OpenLayers.Renderer.SVG.LABEL_ALIGN={l:"start",r:"end",b:"bottom",t:"hanging"};OpenLayers.Renderer.SVG.LABEL_VSHIFT={t:"-70%",b:"0"};OpenLayers.Renderer.SVG.LABEL_VFACTOR={t:0,b:-1};OpenLayers.Renderer.SVG.preventDefault=function(a){OpenLayers.Event.preventDefault(a)};OpenLayers.Format.SLD.v1_0_0=OpenLayers.Class(OpenLayers.Format.SLD.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",CLASS_NAME:"OpenLayers.Format.SLD.v1_0_0"});OpenLayers.Format.OWSContext=OpenLayers.Class(OpenLayers.Format.Context,{defaultVersion:"0.3.1",getVersion:function(a,b){var c=OpenLayers.Format.XML.VersionedOGC.prototype.getVersion.apply(this,arguments);"0.3.0"===c&&(c=this.defaultVersion);return c},toContext:function(a){var b={};"OpenLayers.Map"==a.CLASS_NAME&&(b.bounds=a.getExtent(),b.maxExtent=a.maxExtent,b.projection=a.projection,b.size=a.getSize(),b.layers=a.layers);return b},CLASS_NAME:"OpenLayers.Format.OWSContext"});OpenLayers.Format.OWSContext.v0_3_1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{owc:"http://www.opengis.net/ows-context",gml:"http://www.opengis.net/gml",kml:"http://www.opengis.net/kml/2.2",ogc:"http://www.opengis.net/ogc",ows:"http://www.opengis.net/ows",sld:"http://www.opengis.net/sld",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},VERSION:"0.3.1",schemaLocation:"http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd",
+defaultPrefix:"owc",extractAttributes:!0,xy:!0,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},featureNS:"http://mapserver.gis.umn.edu/mapserver",featureType:"vector",geometryName:"geometry",nestingLayerLookup:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);OpenLayers.Format.GML.v2.prototype.setGeometryTypes.call(this)},setNestingPath:function(a){if(a.layersContext)for(var b=0,c=a.layersContext.length;b<c;b++){var d=
+a.layersContext[b],e=[],f=a.title||"";a.metadata&&a.metadata.nestingPath&&(e=a.metadata.nestingPath.slice());""!=f&&e.push(f);d.metadata.nestingPath=e;d.layersContext&&this.setNestingPath(d)}},decomposeNestingPath:function(a){var b=[];if(OpenLayers.Util.isArray(a)){for(a=a.slice();0<a.length;)b.push(a.slice()),a.pop();b.reverse()}return b},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,
+b);this.setNestingPath({layersContext:b.layersContext});a=[];this.processLayer(a,b);delete b.layersContext;b.layersContext=a;return b},processLayer:function(a,b){if(b.layersContext)for(var c=0,d=b.layersContext.length;c<d;c++){var e=b.layersContext[c];a.push(e);e.layersContext&&this.processLayer(a,e)}},write:function(a,b){this.nestingLayerLookup={};b=b||{};OpenLayers.Util.applyDefaults(b,a);var c=this.writeNode("OWSContext",b);this.nestingLayerLookup=null;this.setAttributeNS(c,this.namespaces.xsi,
+"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[c])},readers:{kml:{Document:function(a,b){b.features=(new OpenLayers.Format.KML({kmlns:this.namespaces.kml,extractStyles:!0})).read(a)}},owc:{OWSContext:function(a,b){this.readChildNodes(a,b)},General:function(a,b){this.readChildNodes(a,b)},ResourceList:function(a,b){this.readChildNodes(a,b)},Layer:function(a,b){var c={metadata:{},visibility:"1"!=a.getAttribute("hidden"),queryable:"1"==a.getAttribute("queryable"),
+opacity:null!=a.getAttribute("opacity")?parseFloat(a.getAttribute("opacity")):null,name:a.getAttribute("name"),categoryLayer:null==a.getAttribute("name"),formats:[],styles:[]};b.layersContext||(b.layersContext=[]);b.layersContext.push(c);this.readChildNodes(a,c)},InlineGeometry:function(a,b){b.features=[];var c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMember"),d;1<=c.length&&(d=c[0]);d&&d.firstChild&&(c=d.firstChild.nextSibling?d.firstChild.nextSibling:d.firstChild,this.setNamespace("feature",
+c.namespaceURI),this.featureType=c.localName||c.nodeName.split(":").pop(),this.readChildNodes(a,b))},Server:function(a,b){if(!b.service&&!b.version||b.service!=OpenLayers.Format.Context.serviceTypes.WMS)b.service=a.getAttribute("service"),b.version=a.getAttribute("version"),this.readChildNodes(a,b)},Name:function(a,b){b.name=this.getChildValue(a);this.readChildNodes(a,b)},Title:function(a,b){b.title=this.getChildValue(a);this.readChildNodes(a,b)},StyleList:function(a,b){this.readChildNodes(a,b.styles)},
+Style:function(a,b){var c={};b.push(c);this.readChildNodes(a,c)},LegendURL:function(a,b){var c={};b.legend=c;this.readChildNodes(a,c)},OnlineResource:function(a,b){b.url=this.getAttributeNS(a,this.namespaces.xlink,"href");this.readChildNodes(a,b)}},ows:OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows,gml:OpenLayers.Format.GML.v2.prototype.readers.gml,sld:OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld,feature:OpenLayers.Format.GML.v2.prototype.readers.feature},writers:{owc:{OWSContext:function(a){var b=
+this.createElementNSPlus("OWSContext",{attributes:{version:this.VERSION,id:a.id||OpenLayers.Util.createUniqueID("OpenLayers_OWSContext_")}});this.writeNode("General",a,b);this.writeNode("ResourceList",a,b);return b},General:function(a){var b=this.createElementNSPlus("General");this.writeNode("ows:BoundingBox",a,b);this.writeNode("ows:Title",a.title||"OpenLayers OWSContext",b);return b},ResourceList:function(a){for(var b=this.createElementNSPlus("ResourceList"),c=0,d=a.layers.length;c<d;c++){var e=
+a.layers[c],f=this.decomposeNestingPath(e.metadata.nestingPath);this.writeNode("_Layer",{layer:e,subPaths:f},b)}return b},Server:function(a){var b=this.createElementNSPlus("Server",{attributes:{version:a.version,service:a.service}});this.writeNode("OnlineResource",a,b);return b},OnlineResource:function(a){return this.createElementNSPlus("OnlineResource",{attributes:{"xlink:href":a.url}})},InlineGeometry:function(a){var b=this.createElementNSPlus("InlineGeometry"),c=a.getDataExtent();null!==c&&this.writeNode("gml:boundedBy",
+c,b);for(var c=0,d=a.features.length;c<d;c++)this.writeNode("gml:featureMember",a.features[c],b);return b},StyleList:function(a){for(var b=this.createElementNSPlus("StyleList"),c=0,d=a.length;c<d;c++)this.writeNode("Style",a[c],b);return b},Style:function(a){var b=this.createElementNSPlus("Style");this.writeNode("Name",a,b);this.writeNode("Title",a,b);a.legend&&this.writeNode("LegendURL",a,b);return b},Name:function(a){return this.createElementNSPlus("Name",{value:a.name})},Title:function(a){return this.createElementNSPlus("Title",
+{value:a.title})},LegendURL:function(a){var b=this.createElementNSPlus("LegendURL");this.writeNode("OnlineResource",a.legend,b);return b},_WMS:function(a){var b=this.createElementNSPlus("Layer",{attributes:{name:a.params.LAYERS,queryable:a.queryable?"1":"0",hidden:a.visibility?"0":"1",opacity:a.hasOwnProperty("opacity")?a.opacity:null}});this.writeNode("ows:Title",a.name,b);this.writeNode("ows:OutputFormat",a.params.FORMAT,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.WMS,
+version:a.params.VERSION,url:a.url},b);a.metadata.styles&&0<a.metadata.styles.length&&this.writeNode("StyleList",a.metadata.styles,b);return b},_Layer:function(a){var b,c,d;b=a.layer;c=a.subPaths;d=null;0<c.length?(b=c[0].join("/"),c=b.lastIndexOf("/"),d=this.nestingLayerLookup[b],c=0<c?b.substring(c+1,b.length):b,d||(d=this.createElementNSPlus("Layer"),this.writeNode("ows:Title",c,d),this.nestingLayerLookup[b]=d),a.subPaths.shift(),this.writeNode("_Layer",a,d)):(b instanceof OpenLayers.Layer.WMS?
+d=this.writeNode("_WMS",b):b instanceof OpenLayers.Layer.Vector&&(b.protocol instanceof OpenLayers.Protocol.WFS.v1?d=this.writeNode("_WFS",b):b.protocol instanceof OpenLayers.Protocol.HTTP?b.protocol.format instanceof OpenLayers.Format.GML?(b.protocol.format.version="2.1.2",d=this.writeNode("_GML",b)):b.protocol.format instanceof OpenLayers.Format.KML&&(b.protocol.format.version="2.2",d=this.writeNode("_KML",b)):(this.setNamespace("feature",this.featureNS),d=this.writeNode("_InlineGeometry",b))),
+b.options.maxScale&&this.writeNode("sld:MinScaleDenominator",b.options.maxScale,d),b.options.minScale&&this.writeNode("sld:MaxScaleDenominator",b.options.minScale,d),this.nestingLayerLookup[b.name]=d);return d},_WFS:function(a){var b=this.createElementNSPlus("Layer",{attributes:{name:a.protocol.featurePrefix+":"+a.protocol.featureType,hidden:a.visibility?"0":"1"}});this.writeNode("ows:Title",a.name,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.WFS,version:a.protocol.version,
+url:a.protocol.url},b);return b},_InlineGeometry:function(a){var b=this.createElementNSPlus("Layer",{attributes:{name:this.featureType,hidden:a.visibility?"0":"1"}});this.writeNode("ows:Title",a.name,b);this.writeNode("InlineGeometry",a,b);return b},_GML:function(a){var b=this.createElementNSPlus("Layer");this.writeNode("ows:Title",a.name,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.GML,url:a.protocol.url,version:a.protocol.format.version},b);return b},_KML:function(a){var b=
+this.createElementNSPlus("Layer");this.writeNode("ows:Title",a.name,b);this.writeNode("Server",{service:OpenLayers.Format.Context.serviceTypes.KML,version:a.protocol.format.version,url:a.protocol.url},b);return b}},gml:OpenLayers.Util.applyDefaults({boundedBy:function(a){var b=this.createElementNSPlus("gml:boundedBy");this.writeNode("gml:Box",a,b);return b}},OpenLayers.Format.GML.v2.prototype.writers.gml),ows:OpenLayers.Format.OWSCommon.v1_0_0.prototype.writers.ows,sld:OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld,
+feature:OpenLayers.Format.GML.v2.prototype.writers.feature},CLASS_NAME:"OpenLayers.Format.OWSContext.v0_3_1"});OpenLayers.Popup=OpenLayers.Class({events:null,id:"",lonlat:null,div:null,contentSize:null,size:null,contentHTML:null,backgroundColor:"",opacity:"",border:"",contentDiv:null,groupDiv:null,closeDiv:null,autoSize:!1,minSize:null,maxSize:null,displayClass:"olPopup",contentDisplayClass:"olPopupContent",padding:0,disableFirefoxOverflowHack:!1,fixPadding:function(){"number"==typeof this.padding&&(this.padding=new OpenLayers.Bounds(this.padding,this.padding,this.padding,this.padding))},panMapIfOutOfView:!1,
+keepInMap:!1,closeOnMove:!1,map:null,initialize:function(a,b,c,d,e,f){null==a&&(a=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"));this.id=a;this.lonlat=b;this.contentSize=null!=c?c:new OpenLayers.Size(OpenLayers.Popup.WIDTH,OpenLayers.Popup.HEIGHT);null!=d&&(this.contentHTML=d);this.backgroundColor=OpenLayers.Popup.COLOR;this.opacity=OpenLayers.Popup.OPACITY;this.border=OpenLayers.Popup.BORDER;this.div=OpenLayers.Util.createDiv(this.id,null,null,null,null,null,"hidden");this.div.className=this.displayClass;
+this.groupDiv=OpenLayers.Util.createDiv(this.id+"_GroupDiv",null,null,null,"relative",null,"hidden");a=this.div.id+"_contentDiv";this.contentDiv=OpenLayers.Util.createDiv(a,null,this.contentSize.clone(),null,"relative");this.contentDiv.className=this.contentDisplayClass;this.groupDiv.appendChild(this.contentDiv);this.div.appendChild(this.groupDiv);e&&this.addCloseBox(f);this.registerEvents()},destroy:function(){this.border=this.opacity=this.backgroundColor=this.contentHTML=this.size=this.lonlat=this.id=
+null;this.closeOnMove&&this.map&&this.map.events.unregister("movestart",this,this.hide);this.events.destroy();this.events=null;this.closeDiv&&(OpenLayers.Event.stopObservingElement(this.closeDiv),this.groupDiv.removeChild(this.closeDiv));this.closeDiv=null;this.div.removeChild(this.groupDiv);this.groupDiv=null;null!=this.map&&this.map.removePopup(this);this.panMapIfOutOfView=this.padding=this.maxSize=this.minSize=this.autoSize=this.div=this.map=null},draw:function(a){null==a&&null!=this.lonlat&&null!=
+this.map&&(a=this.map.getLayerPxFromLonLat(this.lonlat));this.closeOnMove&&this.map.events.register("movestart",this,this.hide);this.disableFirefoxOverflowHack||"firefox"!=OpenLayers.BROWSER_NAME||(this.map.events.register("movestart",this,function(){var a=document.defaultView.getComputedStyle(this.contentDiv,null).getPropertyValue("overflow");"hidden"!=a&&(this.contentDiv._oldOverflow=a,this.contentDiv.style.overflow="hidden")}),this.map.events.register("moveend",this,function(){var a=this.contentDiv._oldOverflow;
+a&&(this.contentDiv.style.overflow=a,this.contentDiv._oldOverflow=null)}));this.moveTo(a);this.autoSize||this.size||this.setSize(this.contentSize);this.setBackgroundColor();this.setOpacity();this.setBorder();this.setContentHTML();this.panMapIfOutOfView&&this.panIntoView();return this.div},updatePosition:function(){if(this.lonlat&&this.map){var a=this.map.getLayerPxFromLonLat(this.lonlat);a&&this.moveTo(a)}},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"px",this.div.style.top=
+a.y+"px")},visible:function(){return OpenLayers.Element.visible(this.div)},toggle:function(){this.visible()?this.hide():this.show()},show:function(){this.div.style.display="";this.panMapIfOutOfView&&this.panIntoView()},hide:function(){this.div.style.display="none"},setSize:function(a){this.size=a.clone();var b=this.getContentDivPadding(),c=b.left+b.right,d=b.top+b.bottom;this.fixPadding();c+=this.padding.left+this.padding.right;d+=this.padding.top+this.padding.bottom;if(this.closeDiv)var e=parseInt(this.closeDiv.style.width),
+c=c+(e+b.right);this.size.w+=c;this.size.h+=d;"msie"==OpenLayers.BROWSER_NAME&&(this.contentSize.w+=b.left+b.right,this.contentSize.h+=b.bottom+b.top);null!=this.div&&(this.div.style.width=this.size.w+"px",this.div.style.height=this.size.h+"px");null!=this.contentDiv&&(this.contentDiv.style.width=a.w+"px",this.contentDiv.style.height=a.h+"px")},updateSize:function(){var a="<div class='"+this.contentDisplayClass+"'>"+this.contentDiv.innerHTML+"</div>",b=this.map?this.map.div:document.body,c=OpenLayers.Util.getRenderedDimensions(a,
+null,{displayClass:this.displayClass,containerElement:b}),d=this.getSafeContentSize(c),e=null;d.equals(c)?e=c:(c={w:d.w<c.w?d.w:null,h:d.h<c.h?d.h:null},c.w&&c.h?e=d:(a=OpenLayers.Util.getRenderedDimensions(a,c,{displayClass:this.contentDisplayClass,containerElement:b}),"hidden"!=OpenLayers.Element.getStyle(this.contentDiv,"overflow")&&a.equals(d)&&(d=OpenLayers.Util.getScrollbarWidth(),c.w?a.h+=d:a.w+=d),e=this.getSafeContentSize(a)));this.setSize(e)},setBackgroundColor:function(a){void 0!=a&&(this.backgroundColor=
+a);null!=this.div&&(this.div.style.backgroundColor=this.backgroundColor)},setOpacity:function(a){void 0!=a&&(this.opacity=a);null!=this.div&&(this.div.style.opacity=this.opacity,this.div.style.filter="alpha(opacity="+100*this.opacity+")")},setBorder:function(a){void 0!=a&&(this.border=a);null!=this.div&&(this.div.style.border=this.border)},setContentHTML:function(a){null!=a&&(this.contentHTML=a);null!=this.contentDiv&&(null!=this.contentHTML&&this.contentHTML!=this.contentDiv.innerHTML)&&(this.contentDiv.innerHTML=
+this.contentHTML,this.autoSize&&(this.registerImageListeners(),this.updateSize()))},registerImageListeners:function(){for(var a=function(){null!==this.popup.id&&(this.popup.updateSize(),this.popup.visible()&&this.popup.panMapIfOutOfView&&this.popup.panIntoView(),OpenLayers.Event.stopObserving(this.img,"load",this.img._onImgLoad))},b=this.contentDiv.getElementsByTagName("img"),c=0,d=b.length;c<d;c++){var e=b[c];if(0==e.width||0==e.height)e._onImgLoad=OpenLayers.Function.bind(a,{popup:this,img:e}),
+OpenLayers.Event.observe(e,"load",e._onImgLoad)}},getSafeContentSize:function(a){a=a.clone();var b=this.getContentDivPadding(),c=b.left+b.right,d=b.top+b.bottom;this.fixPadding();c+=this.padding.left+this.padding.right;d+=this.padding.top+this.padding.bottom;if(this.closeDiv)var e=parseInt(this.closeDiv.style.width),c=c+(e+b.right);this.minSize&&(a.w=Math.max(a.w,this.minSize.w-c),a.h=Math.max(a.h,this.minSize.h-d));this.maxSize&&(a.w=Math.min(a.w,this.maxSize.w-c),a.h=Math.min(a.h,this.maxSize.h-
+d));if(this.map&&this.map.size){e=b=0;if(this.keepInMap&&!this.panMapIfOutOfView)switch(e=this.map.getPixelFromLonLat(this.lonlat),this.relativePosition){case "tr":b=e.x;e=this.map.size.h-e.y;break;case "tl":b=this.map.size.w-e.x;e=this.map.size.h-e.y;break;case "bl":b=this.map.size.w-e.x;e=e.y;break;case "br":b=e.x;e=e.y;break;default:b=e.x,e=this.map.size.h-e.y}d=this.map.size.h-this.map.paddingForPopups.top-this.map.paddingForPopups.bottom-d-e;a.w=Math.min(a.w,this.map.size.w-this.map.paddingForPopups.left-
+this.map.paddingForPopups.right-c-b);a.h=Math.min(a.h,d)}return a},getContentDivPadding:function(){var a=this._contentDivPadding;a||(null==this.div.parentNode&&(this.div.style.display="none",document.body.appendChild(this.div)),this._contentDivPadding=a=new OpenLayers.Bounds(OpenLayers.Element.getStyle(this.contentDiv,"padding-left"),OpenLayers.Element.getStyle(this.contentDiv,"padding-bottom"),OpenLayers.Element.getStyle(this.contentDiv,"padding-right"),OpenLayers.Element.getStyle(this.contentDiv,
+"padding-top")),this.div.parentNode==document.body&&(document.body.removeChild(this.div),this.div.style.display=""));return a},addCloseBox:function(a){this.closeDiv=OpenLayers.Util.createDiv(this.id+"_close",null,{w:17,h:17});this.closeDiv.className="olPopupCloseBox";var b=this.getContentDivPadding();this.closeDiv.style.right=b.right+"px";this.closeDiv.style.top=b.top+"px";this.groupDiv.appendChild(this.closeDiv);a=a||function(a){this.hide();OpenLayers.Event.stop(a)};OpenLayers.Event.observe(this.closeDiv,
+"touchend",OpenLayers.Function.bindAsEventListener(a,this));OpenLayers.Event.observe(this.closeDiv,"click",OpenLayers.Function.bindAsEventListener(a,this))},panIntoView:function(){var a=this.map.getSize(),b=this.map.getViewPortPxFromLayerPx(new OpenLayers.Pixel(parseInt(this.div.style.left),parseInt(this.div.style.top))),c=b.clone();b.x<this.map.paddingForPopups.left?c.x=this.map.paddingForPopups.left:b.x+this.size.w>a.w-this.map.paddingForPopups.right&&(c.x=a.w-this.map.paddingForPopups.right-this.size.w);
+b.y<this.map.paddingForPopups.top?c.y=this.map.paddingForPopups.top:b.y+this.size.h>a.h-this.map.paddingForPopups.bottom&&(c.y=a.h-this.map.paddingForPopups.bottom-this.size.h);this.map.pan(b.x-c.x,b.y-c.y)},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,!0);this.events.on({mousedown:this.onmousedown,mousemove:this.onmousemove,mouseup:this.onmouseup,click:this.onclick,mouseout:this.onmouseout,dblclick:this.ondblclick,touchstart:function(a){OpenLayers.Event.stop(a,!0)},
+scope:this})},onmousedown:function(a){this.mousedown=!0;OpenLayers.Event.stop(a,!0)},onmousemove:function(a){this.mousedown&&OpenLayers.Event.stop(a,!0)},onmouseup:function(a){this.mousedown&&(this.mousedown=!1,OpenLayers.Event.stop(a,!0))},onclick:function(a){OpenLayers.Event.stop(a,!0)},onmouseout:function(a){this.mousedown=!1},ondblclick:function(a){OpenLayers.Event.stop(a,!0)},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLOR="white";
+OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Control.ScaleLine=OpenLayers.Class(OpenLayers.Control,{maxWidth:100,topOutUnits:"km",topInUnits:"m",bottomOutUnits:"mi",bottomInUnits:"ft",eTop:null,eBottom:null,geodesic:!1,draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.eTop||(this.eTop=document.createElement("div"),this.eTop.className=this.displayClass+"Top",this.div.appendChild(this.eTop),this.eTop.style.visibility=""==this.topOutUnits||""==this.topInUnits?"hidden":"visible",this.eBottom=document.createElement("div"),
+this.eBottom.className=this.displayClass+"Bottom",this.div.appendChild(this.eBottom),this.eBottom.style.visibility=""==this.bottomOutUnits||""==this.bottomInUnits?"hidden":"visible");this.map.events.register("moveend",this,this.update);this.update();return this.div},getBarLen:function(a){var b=parseInt(Math.log(a)/Math.log(10)),b=Math.pow(10,b);a=parseInt(a/b);return(5<a?5:2<a?2:1)*b},update:function(){var a=this.map.getResolution();if(a){var b=this.map.getUnits(),c=OpenLayers.INCHES_PER_UNIT,d=this.maxWidth*
+a*c[b],e=1;!0===this.geodesic&&(e=(this.map.getGeodesicPixelSize().w||1E-6)*this.maxWidth/(d/c.km),d*=e);var f,g;1E5<d?(f=this.topOutUnits,g=this.bottomOutUnits):(f=this.topInUnits,g=this.bottomInUnits);var h=d/c[f],k=d/c[g],d=this.getBarLen(h),l=this.getBarLen(k),h=d/c[b]*c[f],k=l/c[b]*c[g],b=h/a/e,a=k/a/e;"visible"==this.eBottom.style.visibility&&(this.eBottom.style.width=Math.round(a)+"px",this.eBottom.innerHTML=l+" "+g);"visible"==this.eTop.style.visibility&&(this.eTop.style.width=Math.round(b)+
+"px",this.eTop.innerHTML=d+" "+f)}},CLASS_NAME:"OpenLayers.Control.ScaleLine"});OpenLayers.Icon=OpenLayers.Class({url:null,size:null,offset:null,calculateOffset:null,imageDiv:null,px:null,initialize:function(a,b,c,d){this.url=a;this.size=b||{w:20,h:20};this.offset=c||{x:-(this.size.w/2),y:-(this.size.h/2)};this.calculateOffset=d;a=OpenLayers.Util.createUniqueID("OL_Icon_");this.imageDiv=OpenLayers.Util.createAlphaImageDiv(a)},destroy:function(){this.erase();OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);this.imageDiv.innerHTML="";this.imageDiv=null},clone:function(){return new OpenLayers.Icon(this.url,
+this.size,this.offset,this.calculateOffset)},setSize:function(a){null!=a&&(this.size=a);this.draw()},setUrl:function(a){null!=a&&(this.url=a);this.draw()},draw:function(a){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,this.size,this.url,"absolute");this.moveTo(a);return this.imageDiv},erase:function(){null!=this.imageDiv&&null!=this.imageDiv.parentNode&&OpenLayers.Element.remove(this.imageDiv)},setOpacity:function(a){OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,null,null,null,
+null,null,null,a)},moveTo:function(a){null!=a&&(this.px=a);null!=this.imageDiv&&(null==this.px?this.display(!1):(this.calculateOffset&&(this.offset=this.calculateOffset(this.size)),OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,null,{x:this.px.x+this.offset.x,y:this.px.y+this.offset.y})))},display:function(a){this.imageDiv.style.display=a?"":"none"},isDrawn:function(){return this.imageDiv&&this.imageDiv.parentNode&&11!=this.imageDiv.parentNode.nodeType},CLASS_NAME:"OpenLayers.Icon"});OpenLayers.Marker=OpenLayers.Class({icon:null,lonlat:null,events:null,map:null,initialize:function(a,b){this.lonlat=a;var c=b?b:OpenLayers.Marker.defaultIcon();null==this.icon?this.icon=c:(this.icon.url=c.url,this.icon.size=c.size,this.icon.offset=c.offset,this.icon.calculateOffset=c.calculateOffset);this.events=new OpenLayers.Events(this,this.icon.imageDiv)},destroy:function(){this.erase();this.map=null;this.events.destroy();this.events=null;null!=this.icon&&(this.icon.destroy(),this.icon=null)},
+draw:function(a){return this.icon.draw(a)},erase:function(){null!=this.icon&&this.icon.erase()},moveTo:function(a){null!=a&&null!=this.icon&&this.icon.moveTo(a);this.lonlat=this.map.getLonLatFromLayerPx(a)},isDrawn:function(){return this.icon&&this.icon.isDrawn()},onScreen:function(){var a=!1;this.map&&(a=this.map.getExtent().containsLonLat(this.lonlat));return a},inflate:function(a){this.icon&&this.icon.setSize({w:this.icon.size.w*a,h:this.icon.size.h*a})},setOpacity:function(a){this.icon.setOpacity(a)},
+setUrl:function(a){this.icon.setUrl(a)},display:function(a){this.icon.display(a)},CLASS_NAME:"OpenLayers.Marker"});OpenLayers.Marker.defaultIcon=function(){return new OpenLayers.Icon(OpenLayers.Util.getImageLocation("marker.png"),{w:21,h:25},{x:-10.5,y:-25})};OpenLayers.Layer.TileCache=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,format:"image/png",serverResolutions:null,initialize:function(a,b,c,d){this.layername=c;OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a,b,{},d]);this.extension=this.format.split("/")[1].toLowerCase();this.extension="jpg"==this.extension?"jpeg":this.extension},clone:function(a){null==a&&(a=new OpenLayers.Layer.TileCache(this.name,this.url,this.layername,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,
+[a])},getURL:function(a){var b=this.getServerResolution(),c=this.maxExtent,d=this.tileSize,e=Math.round((a.left-c.left)/(b*d.w));a=Math.round((a.bottom-c.bottom)/(b*d.h));b=null!=this.serverResolutions?OpenLayers.Util.indexOf(this.serverResolutions,b):this.map.getZoom();e=[this.layername,OpenLayers.Number.zeroPad(b,2),OpenLayers.Number.zeroPad(parseInt(e/1E6),3),OpenLayers.Number.zeroPad(parseInt(e/1E3)%1E3,3),OpenLayers.Number.zeroPad(parseInt(e)%1E3,3),OpenLayers.Number.zeroPad(parseInt(a/1E6),
+3),OpenLayers.Number.zeroPad(parseInt(a/1E3)%1E3,3),OpenLayers.Number.zeroPad(parseInt(a)%1E3,3)+"."+this.extension].join("/");b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(e,b));b="/"==b.charAt(b.length-1)?b:b+"/";return b+e},CLASS_NAME:"OpenLayers.Layer.TileCache"});OpenLayers.Strategy.Paging=OpenLayers.Class(OpenLayers.Strategy,{features:null,length:10,num:null,paging:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);if(a)this.layer.events.on({beforefeaturesadded:this.cacheFeatures,scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&(this.clearCache(),this.layer.events.un({beforefeaturesadded:this.cacheFeatures,scope:this}));return a},cacheFeatures:function(a){this.paging||(this.clearCache(),
+this.features=a.features,this.pageNext(a))},clearCache:function(){if(this.features)for(var a=0;a<this.features.length;++a)this.features[a].destroy();this.num=this.features=null},pageCount:function(){return Math.ceil((this.features?this.features.length:0)/this.length)},pageNum:function(){return this.num},pageLength:function(a){a&&0<a&&(this.length=a);return this.length},pageNext:function(a){var b=!1;this.features&&(null===this.num&&(this.num=-1),b=this.page((this.num+1)*this.length,a));return b},pagePrevious:function(){var a=
+!1;this.features&&(null===this.num&&(this.num=this.pageCount()),a=this.page((this.num-1)*this.length));return a},page:function(a,b){var c=!1;if(this.features&&0<=a&&a<this.features.length){var d=Math.floor(a/this.length);d!=this.num&&(this.paging=!0,c=this.features.slice(a,a+this.length),this.layer.removeFeatures(this.layer.features),this.num=d,b&&b.features?b.features=c:this.layer.addFeatures(c),this.paging=!1,c=!0)}return c},CLASS_NAME:"OpenLayers.Strategy.Paging"});OpenLayers.Control.DragFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,onStart:function(a,b){},onDrag:function(a,b){},onComplete:function(a,b){},onEnter:function(a){},onLeave:function(a){},documentDrag:!1,layer:null,feature:null,dragCallbacks:{},featureCallbacks:{},lastPixel:null,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);this.layer=a;this.handlers={drag:new OpenLayers.Handler.Drag(this,OpenLayers.Util.extend({down:this.downFeature,move:this.moveFeature,
+up:this.upFeature,out:this.cancel,done:this.doneDragging},this.dragCallbacks),{documentDrag:this.documentDrag}),feature:new OpenLayers.Handler.Feature(this,this.layer,OpenLayers.Util.extend({click:this.clickFeature,clickout:this.clickoutFeature,over:this.overFeature,out:this.outFeature},this.featureCallbacks),{geometryTypes:this.geometryTypes})}},clickFeature:function(a){this.handlers.feature.touch&&(!this.over&&this.overFeature(a))&&(this.handlers.drag.dragstart(this.handlers.feature.evt),this.handlers.drag.stopDown=
+!1)},clickoutFeature:function(a){this.handlers.feature.touch&&this.over&&(this.outFeature(a),this.handlers.drag.stopDown=!0)},destroy:function(){this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[])},activate:function(){return this.handlers.feature.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.handlers.drag.deactivate();this.handlers.feature.deactivate();this.feature=null;this.dragging=!1;this.lastPixel=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,
+this.displayClass+"Over");return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},overFeature:function(a){var b=!1;this.handlers.drag.dragging?this.over=this.feature.id==a.id?!0:!1:(this.feature=a,this.handlers.drag.activate(),this.over=b=!0,OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass+"Over"),this.onEnter(a));return b},downFeature:function(a){this.lastPixel=a;this.onStart(this.feature,a)},moveFeature:function(a){var b=this.map.getResolution();this.feature.geometry.move(b*
+(a.x-this.lastPixel.x),b*(this.lastPixel.y-a.y));this.layer.drawFeature(this.feature);this.lastPixel=a;this.onDrag(this.feature,a)},upFeature:function(a){this.over||this.handlers.drag.deactivate()},doneDragging:function(a){this.onComplete(this.feature,a)},outFeature:function(a){this.handlers.drag.dragging?this.feature.id==a.id&&(this.over=!1):(this.over=!1,this.handlers.drag.deactivate(),OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over"),this.onLeave(a),this.feature=null)},
+cancel:function(){this.handlers.drag.deactivate();this.over=!1},setMap:function(a){this.handlers.drag.setMap(a);this.handlers.feature.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},CLASS_NAME:"OpenLayers.Control.DragFeature"});OpenLayers.Control.TransformFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,layer:null,preserveAspectRatio:!1,rotate:!0,feature:null,renderIntent:"temporary",rotationHandleSymbolizer:null,box:null,center:null,scale:1,ratio:1,rotation:0,handles:null,rotationHandles:null,dragControl:null,irregular:!1,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);this.layer=a;this.rotationHandleSymbolizer||(this.rotationHandleSymbolizer={stroke:!1,pointRadius:10,fillOpacity:0,
+cursor:"pointer"});this.createBox();this.createControl()},activate:function(){var a=!1;OpenLayers.Control.prototype.activate.apply(this,arguments)&&(this.dragControl.activate(),this.layer.addFeatures([this.box]),this.rotate&&this.layer.addFeatures(this.rotationHandles),this.layer.addFeatures(this.handles),a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Control.prototype.deactivate.apply(this,arguments)&&(this.layer.removeFeatures(this.handles),this.rotate&&this.layer.removeFeatures(this.rotationHandles),
+this.layer.removeFeatures([this.box]),this.dragControl.deactivate(),a=!0);return a},setMap:function(a){this.dragControl.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},setFeature:function(a,b){b=OpenLayers.Util.applyDefaults(b,{rotation:0,scale:1,ratio:1});var c=this.rotation,d=this.center;OpenLayers.Util.extend(this,b);if(!1!==this.events.triggerEvent("beforesetfeature",{feature:a})){this.feature=a;this.activate();this._setfeature=!0;var e=this.feature.geometry.getBounds();this.box.move(e.getCenterLonLat());
+this.box.geometry.rotate(-c,d);this._angle=0;this.rotation?(c=a.geometry.clone(),c.rotate(-this.rotation,this.center),c=new OpenLayers.Feature.Vector(c.getBounds().toGeometry()),c.geometry.rotate(this.rotation,this.center),this.box.geometry.rotate(this.rotation,this.center),this.box.move(c.geometry.getBounds().getCenterLonLat()),c=c.geometry.components[0].components[0].getBounds().getCenterLonLat()):c=new OpenLayers.LonLat(e.left,e.bottom);this.handles[0].move(c);delete this._setfeature;this.events.triggerEvent("setfeature",
+{feature:a})}},unsetFeature:function(){this.active?this.deactivate():(this.feature=null,this.rotation=0,this.ratio=this.scale=1)},createBox:function(){var a=this;this.center=new OpenLayers.Geometry.Point(0,0);this.box=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([new OpenLayers.Geometry.Point(-1,-1),new OpenLayers.Geometry.Point(0,-1),new OpenLayers.Geometry.Point(1,-1),new OpenLayers.Geometry.Point(1,0),new OpenLayers.Geometry.Point(1,1),new OpenLayers.Geometry.Point(0,1),new OpenLayers.Geometry.Point(-1,
+1),new OpenLayers.Geometry.Point(-1,0),new OpenLayers.Geometry.Point(-1,-1)]),null,"string"==typeof this.renderIntent?null:this.renderIntent);this.box.geometry.move=function(b,c){a._moving=!0;OpenLayers.Geometry.LineString.prototype.move.apply(this,arguments);a.center.move(b,c);delete a._moving};for(var b=function(a,b){OpenLayers.Geometry.Point.prototype.move.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.move(a,b);this._handle.geometry.move(a,b)},c=function(a,b,c){OpenLayers.Geometry.Point.prototype.resize.apply(this,
+arguments);this._rotationHandle&&this._rotationHandle.geometry.resize(a,b,c);this._handle.geometry.resize(a,b,c)},d=function(a,b){OpenLayers.Geometry.Point.prototype.rotate.apply(this,arguments);this._rotationHandle&&this._rotationHandle.geometry.rotate(a,b);this._handle.geometry.rotate(a,b)},e=function(b,c){var d=this.x,e=this.y;OpenLayers.Geometry.Point.prototype.move.call(this,b,c);if(!a._moving){var f=a.dragControl.handlers.drag.evt,g=!(!a._setfeature&&a.preserveAspectRatio)&&!(f&&f.shiftKey),
+h=new OpenLayers.Geometry.Point(d,e),f=a.center;this.rotate(-a.rotation,f);h.rotate(-a.rotation,f);var k=this.x-f.x,l=this.y-f.y,m=k-(this.x-h.x),n=l-(this.y-h.y);a.irregular&&!a._setfeature&&(k-=(this.x-h.x)/2,l-=(this.y-h.y)/2);this.x=d;this.y=e;h=1;g?(l=1E-5>Math.abs(n)?1:l/n,h=(1E-5>Math.abs(m)?1:k/m)/l):(m=Math.sqrt(m*m+n*n),l=Math.sqrt(k*k+l*l)/m);a._moving=!0;a.box.geometry.rotate(-a.rotation,f);delete a._moving;a.box.geometry.resize(l,f,h);a.box.geometry.rotate(a.rotation,f);a.transformFeature({scale:l,
+ratio:h});a.irregular&&!a._setfeature&&(k=f.clone(),k.x+=1E-5>Math.abs(d-f.x)?0:this.x-d,k.y+=1E-5>Math.abs(e-f.y)?0:this.y-e,a.box.geometry.move(this.x-d,this.y-e),a.transformFeature({center:k}))}},f=function(b,c){var d=this.x,e=this.y;OpenLayers.Geometry.Point.prototype.move.call(this,b,c);if(!a._moving){var f=a.dragControl.handlers.drag.evt,f=f&&f.shiftKey?45:1,g=a.center,h=this.x-g.x,k=this.y-g.y;this.x=d;this.y=e;d=Math.atan2(k-c,h-b);d=Math.atan2(k,h)-d;d*=180/Math.PI;a._angle=(a._angle+d)%
+360;d=a.rotation%f;if(Math.abs(a._angle)>=f||0!==d)d=Math.round(a._angle/f)*f-d,a._angle=0,a.box.geometry.rotate(d,g),a.transformFeature({rotation:d})}},g=Array(8),h=Array(4),k,l,m,n="sw s se e ne n nw w".split(" "),p=0;8>p;++p)k=this.box.geometry.components[p],l=new OpenLayers.Feature.Vector(k.clone(),{role:n[p]+"-resize"},"string"==typeof this.renderIntent?null:this.renderIntent),0==p%2&&(m=new OpenLayers.Feature.Vector(k.clone(),{role:n[p]+"-rotate"},"string"==typeof this.rotationHandleSymbolizer?
+null:this.rotationHandleSymbolizer),m.geometry.move=f,k._rotationHandle=m,h[p/2]=m),k.move=b,k.resize=c,k.rotate=d,l.geometry.move=e,k._handle=l,g[p]=l;this.rotationHandles=h;this.handles=g},createControl:function(){var a=this;this.dragControl=new OpenLayers.Control.DragFeature(this.layer,{documentDrag:!0,moveFeature:function(b){this.feature===a.feature&&(this.feature=a.box);OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this,arguments)},onDrag:function(b,c){b===a.box&&a.transformFeature({center:a.center})},
+onStart:function(b,c){var d=!a.geometryTypes||-1!==OpenLayers.Util.indexOf(a.geometryTypes,b.geometry.CLASS_NAME),e=OpenLayers.Util.indexOf(a.handles,b),e=e+OpenLayers.Util.indexOf(a.rotationHandles,b);b!==a.feature&&(b!==a.box&&-2==e&&d)&&a.setFeature(b)},onComplete:function(b,c){a.events.triggerEvent("transformcomplete",{feature:a.feature})}})},drawHandles:function(){for(var a=this.layer,b=0;8>b;++b)this.rotate&&0===b%2&&a.drawFeature(this.rotationHandles[b/2],this.rotationHandleSymbolizer),a.drawFeature(this.handles[b],
+this.renderIntent)},transformFeature:function(a){if(!this._setfeature){this.scale*=a.scale||1;this.ratio*=a.ratio||1;var b=this.rotation;this.rotation=(this.rotation+(a.rotation||0))%360;if(!1!==this.events.triggerEvent("beforetransform",a)){var c=this.feature,d=c.geometry,e=this.center;d.rotate(-b,e);a.scale||a.ratio?d.resize(a.scale,e,a.ratio):a.center&&c.move(a.center.getBounds().getCenterLonLat());d.rotate(this.rotation,e);this.layer.drawFeature(c);c.toState(OpenLayers.State.UPDATE);this.events.triggerEvent("transform",
+a)}}this.layer.drawFeature(this.box,this.renderIntent);this.drawHandles()},destroy:function(){for(var a,b=0;8>b;++b)a=this.box.geometry.components[b],a._handle.destroy(),a._handle=null,a._rotationHandle&&a._rotationHandle.destroy(),a._rotationHandle=null;this.rotationHandles=this.rotationHandleSymbolizer=this.handles=this.feature=this.center=null;this.box.destroy();this.layer=this.box=null;this.dragControl.destroy();this.dragControl=null;OpenLayers.Control.prototype.destroy.apply(this,arguments)},
+CLASS_NAME:"OpenLayers.Control.TransformFeature"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:"olHandlerBoxZoomBox",boxOffsets:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.dragHandler=new OpenLayers.Handler.Drag(this,{down:this.startBox,move:this.moveBox,out:this.removeBox,up:this.endBox},{keyMask:this.keyMask})},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.dragHandler&&(this.dragHandler.destroy(),this.dragHandler=
+null)},setMap:function(a){OpenLayers.Handler.prototype.setMap.apply(this,arguments);this.dragHandler&&this.dragHandler.setMap(a)},startBox:function(a){this.callback("start",[]);this.zoomBox=OpenLayers.Util.createDiv("zoomBox",{x:-9999,y:-9999});this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE.Popup-1;this.map.viewPortDiv.appendChild(this.zoomBox);OpenLayers.Element.addClass(this.map.viewPortDiv,"olDrawBox")},moveBox:function(a){var b=this.dragHandler.start.x,
+c=this.dragHandler.start.y,d=Math.abs(b-a.x),e=Math.abs(c-a.y),f=this.getBoxOffsets();this.zoomBox.style.width=d+f.width+1+"px";this.zoomBox.style.height=e+f.height+1+"px";this.zoomBox.style.left=(a.x<b?b-d-f.left:b-f.left)+"px";this.zoomBox.style.top=(a.y<c?c-e-f.top:c-f.top)+"px"},endBox:function(a){var b;if(5<Math.abs(this.dragHandler.start.x-a.x)||5<Math.abs(this.dragHandler.start.y-a.y)){var c=this.dragHandler.start;b=Math.min(c.y,a.y);var d=Math.max(c.y,a.y),e=Math.min(c.x,a.x);a=Math.max(c.x,
+a.x);b=new OpenLayers.Bounds(e,d,a,b)}else b=this.dragHandler.start.clone();this.removeBox();this.callback("done",[b])},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.boxOffsets=this.zoomBox=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDrawBox")},activate:function(){return OpenLayers.Handler.prototype.activate.apply(this,arguments)?(this.dragHandler.activate(),!0):!1},deactivate:function(){return OpenLayers.Handler.prototype.deactivate.apply(this,arguments)?
+(this.dragHandler.deactivate()&&this.zoomBox&&this.removeBox(),!0):!1},getBoxOffsets:function(){if(!this.boxOffsets){var a=document.createElement("div");a.style.position="absolute";a.style.border="1px solid black";a.style.width="3px";document.body.appendChild(a);var b=3==a.clientWidth;document.body.removeChild(a);var a=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width")),c=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width")),d=parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+"border-top-width")),e=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"));this.boxOffsets={left:a,right:c,top:d,bottom:e,width:!1===b?a+c:0,height:!1===b?d+e:0}}return this.boxOffsets},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:!1,keyMask:null,alwaysZoom:!1,zoomOnClick:!0,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask})},zoomBox:function(a){if(a instanceof OpenLayers.Bounds){var b,c=a.getCenterPixel();if(this.out){b=Math.min(this.map.size.h/(a.bottom-a.top),this.map.size.w/(a.right-a.left));var d=this.map.getExtent(),e=this.map.getLonLatFromPixel(c),f=e.lon-d.getWidth()/
+2*b;a=e.lon+d.getWidth()/2*b;var g=e.lat-d.getHeight()/2*b;b=e.lat+d.getHeight()/2*b;b=new OpenLayers.Bounds(f,g,a,b)}else f=this.map.getLonLatFromPixel({x:a.left,y:a.bottom}),a=this.map.getLonLatFromPixel({x:a.right,y:a.top}),b=new OpenLayers.Bounds(f.lon,f.lat,a.lon,a.lat);f=this.map.getZoom();g=this.map.getSize();a=g.w/2;g=g.h/2;b=this.map.getZoomForExtent(b);d=this.map.getResolution();e=this.map.getResolutionForZoom(b);d==e?this.map.setCenter(this.map.getLonLatFromPixel(c)):this.map.zoomTo(b,
+{x:(d*c.x-e*a)/(d-e),y:(d*c.y-e*g)/(d-e)});f==this.map.getZoom()&&!0==this.alwaysZoom&&this.map.zoomTo(f+(this.out?-1:1))}else this.zoomOnClick&&(this.out?this.map.zoomTo(this.map.getZoom()-1,a):this.map.zoomTo(this.map.getZoom()+1,a))},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:!1,interval:0,documentDrag:!1,kinetic:null,enableKinetic:!0,kineticInterval:10,draw:function(){if(this.enableKinetic&&OpenLayers.Kinetic){var a={interval:this.kineticInterval};"object"===typeof this.enableKinetic&&(a=OpenLayers.Util.extend(a,this.enableKinetic));this.kinetic=new OpenLayers.Kinetic(a)}this.handler=new OpenLayers.Handler.Drag(this,{move:this.panMap,done:this.panMapDone,down:this.panMapStart},
+{interval:this.interval,documentDrag:this.documentDrag})},panMapStart:function(){this.kinetic&&this.kinetic.begin()},panMap:function(a){this.kinetic&&this.kinetic.update(a);this.panned=!0;this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!0,animate:!1})},panMapDone:function(a){if(this.panned){var b=null;this.kinetic&&(b=this.kinetic.end(a));this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!!b,animate:!1});if(b){var c=this;this.kinetic.move(b,function(a,b,
+f){c.map.pan(a,b,{dragging:!f,animate:!1})})}this.panned=!1}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,pinchZoom:null,pinchZoomOptions:null,documentDrag:!1,zoomBox:null,zoomBoxEnabled:!0,zoomWheelEnabled:!0,mouseWheelOptions:null,handleRightClicks:!1,zoomBoxKeyMask:OpenLayers.Handler.MOD_SHIFT,autoActivate:!0,initialize:function(a){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments)},destroy:function(){this.deactivate();this.dragPan&&this.dragPan.destroy();this.dragPan=null;
+this.zoomBox&&this.zoomBox.destroy();this.zoomBox=null;this.pinchZoom&&this.pinchZoom.destroy();this.pinchZoom=null;OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){this.dragPan.activate();this.zoomWheelEnabled&&this.handlers.wheel.activate();this.handlers.click.activate();this.zoomBoxEnabled&&this.zoomBox.activate();this.pinchZoom&&this.pinchZoom.activate();return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.pinchZoom&&this.pinchZoom.deactivate();
+this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},draw:function(){this.handleRightClicks&&(this.map.viewPortDiv.oncontextmenu=OpenLayers.Function.False);this.handlers.click=new OpenLayers.Handler.Click(this,{click:this.defaultClick,dblclick:this.defaultDblClick,dblrightclick:this.defaultDblRightClick},{"double":!0,stopDouble:!0});this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map,
+documentDrag:this.documentDrag},this.dragPanOptions));this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:this.zoomBoxKeyMask});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{up:this.wheelUp,down:this.wheelDown},OpenLayers.Util.extend(this.map.fractionalZoom?{}:{cumulative:!1,interval:50,maxDelta:6},this.mouseWheelOptions));OpenLayers.Control.PinchZoom&&(this.pinchZoom=new OpenLayers.Control.PinchZoom(OpenLayers.Util.extend({map:this.map},
+this.pinchZoomOptions)))},defaultClick:function(a){a.lastTouches&&2==a.lastTouches.length&&this.map.zoomOut()},defaultDblClick:function(a){this.map.zoomTo(this.map.zoom+1,a.xy)},defaultDblRightClick:function(a){this.map.zoomTo(this.map.zoom-1,a.xy)},wheelChange:function(a,b){this.map.fractionalZoom||(b=Math.round(b));var c=this.map.getZoom(),d;d=Math.max(c+b,0);d=Math.min(d,this.map.getNumZoomLevels());d!==c&&this.map.zoomTo(d,a.xy)},wheelUp:function(a,b){this.wheelChange(a,b||1)},wheelDown:function(a,
+b){this.wheelChange(a,b||-1)},disableZoomBox:function(){this.zoomBoxEnabled=!1;this.zoomBox.deactivate()},enableZoomBox:function(){this.zoomBoxEnabled=!0;this.active&&this.zoomBox.activate()},disableZoomWheel:function(){this.zoomWheelEnabled=!1;this.handlers.wheel.deactivate()},enableZoomWheel:function(){this.zoomWheelEnabled=!0;this.active&&this.handlers.wheel.activate()},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{layer:null,callbacks:null,multi:!1,featureAdded:function(){},initialize:function(a,b,c){OpenLayers.Control.prototype.initialize.apply(this,[c]);this.callbacks=OpenLayers.Util.extend({done:this.drawFeature,modify:function(a,b){this.layer.events.triggerEvent("sketchmodified",{vertex:a,feature:b})},create:function(a,b){this.layer.events.triggerEvent("sketchstarted",{vertex:a,feature:b})}},this.callbacks);this.layer=a;this.handlerOptions=
+this.handlerOptions||{};this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{renderers:a.renderers,rendererOptions:a.rendererOptions});"multi"in this.handlerOptions||(this.handlerOptions.multi=this.multi);if(a=this.layer.styleMap&&this.layer.styleMap.styles.temporary)this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":a})});this.handler=new b(this,this.callbacks,this.handlerOptions)},
+drawFeature:function(a){a=new OpenLayers.Feature.Vector(a);!1!==this.layer.events.triggerEvent("sketchcomplete",{feature:a})&&(a.state=OpenLayers.State.INSERT,this.layer.addFeatures([a]),this.featureAdded(a),this.events.triggerEvent("featureadded",{feature:a}))},insertXY:function(a,b){this.handler&&this.handler.line&&this.handler.insertXY(a,b)},insertDeltaXY:function(a,b){this.handler&&this.handler.line&&this.handler.insertDeltaXY(a,b)},insertDirectionLength:function(a,b){this.handler&&this.handler.line&&
+this.handler.insertDirectionLength(a,b)},insertDeflectionLength:function(a,b){this.handler&&this.handler.line&&this.handler.insertDeflectionLength(a,b)},undo:function(){return this.handler.undo&&this.handler.undo()},redo:function(){return this.handler.redo&&this.handler.redo()},finishSketch:function(){this.handler.finishGeometry()},cancel:function(){this.handler.cancel()},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Handler.Polygon=OpenLayers.Class(OpenLayers.Handler.Path,{holeModifier:null,drawingHole:!1,polygon:null,createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LinearRing([this.point.geometry]));this.polygon=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([this.line.geometry]));this.callback("create",[this.point.geometry,
+this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.polygon,this.point],{silent:!0})},addPoint:function(a){if(!this.drawingHole&&this.holeModifier&&this.evt&&this.evt[this.holeModifier])for(var b=this.point.geometry,c=this.control.layer.features,d,e=c.length-1;0<=e;--e)if(d=c[e].geometry,(d instanceof OpenLayers.Geometry.Polygon||d instanceof OpenLayers.Geometry.MultiPolygon)&&d.intersects(b)){b=c[e];this.control.layer.removeFeatures([b],{silent:!0});this.control.layer.events.registerPriority("sketchcomplete",
+this,this.finalizeInteriorRing);this.control.layer.events.registerPriority("sketchmodified",this,this.enforceTopology);b.geometry.addComponent(this.line.geometry);this.polygon=b;this.drawingHole=!0;break}OpenLayers.Handler.Path.prototype.addPoint.apply(this,arguments)},getCurrentPointIndex:function(){return this.line.geometry.components.length-2},enforceTopology:function(a){a=a.vertex;var b=this.line.geometry.components;this.polygon.geometry.intersects(a)||(b=b[b.length-3],a.x=b.x,a.y=b.y)},finishGeometry:function(){this.line.geometry.removeComponent(this.line.geometry.components[this.line.geometry.components.length-
+2]);this.removePoint();this.finalize()},finalizeInteriorRing:function(){var a=this.line.geometry,b=0!==a.getArea();if(b){for(var c=this.polygon.geometry.components,d=c.length-2;0<=d;--d)if(a.intersects(c[d])){b=!1;break}if(b)a:for(d=c.length-2;0<d;--d)for(var e=c[d].components,f=0,g=e.length;f<g;++f)if(a.containsPoint(e[f])){b=!1;break a}}b?this.polygon.state!==OpenLayers.State.INSERT&&(this.polygon.state=OpenLayers.State.UPDATE):this.polygon.geometry.removeComponent(a);this.restoreFeature();return!1},
+cancel:function(){this.drawingHole&&(this.polygon.geometry.removeComponent(this.line.geometry),this.restoreFeature(!0));return OpenLayers.Handler.Path.prototype.cancel.apply(this,arguments)},restoreFeature:function(a){this.control.layer.events.unregister("sketchcomplete",this,this.finalizeInteriorRing);this.control.layer.events.unregister("sketchmodified",this,this.enforceTopology);this.layer.removeFeatures([this.polygon],{silent:!0});this.control.layer.addFeatures([this.polygon],{silent:!0});this.drawingHole=
+!1;a||this.control.layer.events.triggerEvent("sketchcomplete",{feature:this.polygon})},destroyFeature:function(a){OpenLayers.Handler.Path.prototype.destroyFeature.call(this,a);this.polygon=null},drawFeature:function(){this.layer.drawFeature(this.polygon,this.style);this.layer.drawFeature(this.point,this.style)},getSketch:function(){return this.polygon},getGeometry:function(){var a=this.polygon&&this.polygon.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiPolygon([a]));return a},CLASS_NAME:"OpenLayers.Handler.Polygon"});OpenLayers.Control.EditingToolbar=OpenLayers.Class(OpenLayers.Control.Panel,{citeCompliant:!1,initialize:function(a,b){OpenLayers.Control.Panel.prototype.initialize.apply(this,[b]);this.addControls([new OpenLayers.Control.Navigation]);var c=[new OpenLayers.Control.DrawFeature(a,OpenLayers.Handler.Point,{displayClass:"olControlDrawFeaturePoint",handlerOptions:{citeCompliant:this.citeCompliant}}),new OpenLayers.Control.DrawFeature(a,OpenLayers.Handler.Path,{displayClass:"olControlDrawFeaturePath",handlerOptions:{citeCompliant:this.citeCompliant}}),
+new OpenLayers.Control.DrawFeature(a,OpenLayers.Handler.Polygon,{displayClass:"olControlDrawFeaturePolygon",handlerOptions:{citeCompliant:this.citeCompliant}})];this.addControls(c)},draw:function(){var a=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);null===this.defaultControl&&(this.defaultControl=this.controls[0]);return a},CLASS_NAME:"OpenLayers.Control.EditingToolbar"});OpenLayers.Strategy.BBOX=OpenLayers.Class(OpenLayers.Strategy,{bounds:null,resolution:null,ratio:2,resFactor:null,response:null,activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);a&&(this.layer.events.on({moveend:this.update,refresh:this.update,visibilitychanged:this.update,scope:this}),this.update());return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.layer.events.un({moveend:this.update,refresh:this.update,visibilitychanged:this.update,
+scope:this});return a},update:function(a){var b=this.getMapBounds();null!==b&&(a&&a.force||this.layer.visibility&&this.layer.calculateInRange()&&this.invalidBounds(b))&&(this.calculateBounds(b),this.resolution=this.layer.map.getResolution(),this.triggerRead(a))},getMapBounds:function(){if(null===this.layer.map)return null;var a=this.layer.map.getExtent();a&&!this.layer.projection.equals(this.layer.map.getProjectionObject())&&(a=a.clone().transform(this.layer.map.getProjectionObject(),this.layer.projection));
+return a},invalidBounds:function(a){a||(a=this.getMapBounds());a=!this.bounds||!this.bounds.containsBounds(a);!a&&this.resFactor&&(a=this.resolution/this.layer.map.getResolution(),a=a>=this.resFactor||a<=1/this.resFactor);return a},calculateBounds:function(a){a||(a=this.getMapBounds());var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;this.bounds=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2)},triggerRead:function(a){!this.response||a&&!0===a.noAbort||
+(this.layer.protocol.abort(this.response),this.layer.events.triggerEvent("loadend"));var b={filter:this.createFilter()};this.layer.events.triggerEvent("loadstart",b);this.response=this.layer.protocol.read(OpenLayers.Util.applyDefaults({filter:b.filter,callback:this.merge,scope:this},a))},createFilter:function(){var a=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:this.bounds,projection:this.layer.projection});this.layer.filter&&(a=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,
+filters:[this.layer.filter,a]}));return a},merge:function(a){this.layer.destroyFeatures();if(a.success()){var b=a.features;if(b&&0<b.length){var c=this.layer.projection,d=this.layer.map.getProjectionObject();if(!d.equals(c))for(var e,f=0,g=b.length;f<g;++f)(e=b[f].geometry)&&e.transform(c,d);this.layer.addFeatures(b)}}else this.bounds=null;this.response=null;this.layer.events.triggerEvent("loadend",{response:a})},CLASS_NAME:"OpenLayers.Strategy.BBOX"});OpenLayers.Layer.WorldWind=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{},isBaseLayer:!0,lzd:null,zoomLevels:null,initialize:function(a,b,c,d,e,f){this.lzd=c;this.zoomLevels=d;c=[];c.push(a,b,e,f);OpenLayers.Layer.Grid.prototype.initialize.apply(this,c);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS)},getZoom:function(){var a=this.map.getZoom();this.map.getMaxExtent();return a-=Math.log(this.maxResolution/(this.lzd/512))/Math.log(2)},getURL:function(a){a=this.adjustBounds(a);
+var b=this.getZoom(),c=this.map.getMaxExtent(),d=this.lzd/Math.pow(2,this.getZoom()),e=Math.floor((a.left-c.left)/d);a=Math.floor((a.bottom-c.bottom)/d);return this.map.getResolution()<=this.lzd/512&&this.getZoom()<=this.zoomLevels?this.getFullRequestString({L:b,X:e,Y:a}):OpenLayers.Util.getImageLocation("blank.gif")},CLASS_NAME:"OpenLayers.Layer.WorldWind"});OpenLayers.Protocol.CSW=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Protocol.CSW.DEFAULTS);var b=OpenLayers.Protocol.CSW["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported CSW version: "+a.version;return new b(a)};OpenLayers.Protocol.CSW.DEFAULTS={version:"2.0.2"};OpenLayers.Format.WMTSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",yx:{"urn:ogc:def:crs:EPSG::4326":!0},createLayer:function(a,b){if(!("layer"in b))throw Error("Missing property 'layer' in configuration.");for(var c=a.contents,d,e=0,f=c.layers.length;e<f;++e)if(c.layers[e].identifier===b.layer){d=c.layers[e];break}if(!d)throw Error("Layer not found");var g=b.format;!g&&(d.formats&&d.formats.length)&&(g=d.formats[0]);var h;b.matrixSet?h=c.tileMatrixSets[b.matrixSet]:
+1<=d.tileMatrixSetLinks.length&&(h=c.tileMatrixSets[d.tileMatrixSetLinks[0].tileMatrixSet]);if(!h)throw Error("matrixSet not found");for(var k,e=0,f=d.styles.length;e<f&&(k=d.styles[e],!k.isDefault);++e);c=b.requestEncoding;if(!c&&(c="KVP",a.operationsMetadata.GetTile.dcp.http)){var l=a.operationsMetadata.GetTile.dcp.http;l.get[0].constraints&&(l=l.get[0].constraints.GetEncoding.allowedValues,l.KVP||!l.REST&&!l.RESTful||(c="REST"))}var l=[],m=b.params||{};delete b.params;for(var n=0,p=d.dimensions.length;n<
+p;n++){var q=d.dimensions[n];l.push(q.identifier);m.hasOwnProperty(q.identifier)||(m[q.identifier]=q["default"])}var n=b.projection||h.supportedCRS.replace(/urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/,"$1:$3"),p=b.units||("EPSG:4326"===n?"degrees":"m"),q=[],r;for(r in h.matrixIds)h.matrixIds.hasOwnProperty(r)&&q.push(2.8E-4*h.matrixIds[r].scaleDenominator/OpenLayers.METERS_PER_INCH/OpenLayers.INCHES_PER_UNIT[p]);if("REST"===c&&d.resourceUrls){r=[];for(var f=0,s=d.resourceUrls.length;f<s;++f)e=d.resourceUrls[f],
+e.format===g&&"tile"===e.resourceType&&r.push(e.template)}else{s=a.operationsMetadata.GetTile.dcp.http.get;r=[];for(var t,e=0,f=s.length;e<f;e++)t=s[e].constraints,(!t||t&&t.GetEncoding.allowedValues[c])&&r.push(s[e].url)}return new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults(b,{url:r,requestEncoding:c,name:d.title,style:k.identifier,format:g,matrixIds:h.matrixIds,matrixSet:h.identifier,projection:n,units:p,resolutions:!1===b.isBaseLayer?void 0:q,serverResolutions:q,tileFullExtent:h.bounds,
+dimensions:l,params:m}))},CLASS_NAME:"OpenLayers.Format.WMTSCapabilities"});OpenLayers.Layer.Google.v3={DEFAULTS:{sphericalMercator:!0,projection:"EPSG:900913"},animationEnabled:!0,loadMapObject:function(){this.type||(this.type=google.maps.MapTypeId.ROADMAP);var a,b=OpenLayers.Layer.Google.cache[this.map.id];b?(a=b.mapObject,++b.count):(a=this.map.getCenter(),b=document.createElement("div"),b.className="olForeignContainer",b.style.width="100%",b.style.height="100%",a=new google.maps.Map(b,{center:a?new google.maps.LatLng(a.lat,a.lon):new google.maps.LatLng(0,0),zoom:this.map.getZoom()||
+0,mapTypeId:this.type,disableDefaultUI:!0,keyboardShortcuts:!1,draggable:!1,disableDoubleClickZoom:!0,scrollwheel:!1,streetViewControl:!1}),b=document.createElement("div"),b.style.width="100%",b.style.height="100%",a.controls[google.maps.ControlPosition.TOP_LEFT].push(b),b={googleControl:b,mapObject:a,count:1},OpenLayers.Layer.Google.cache[this.map.id]=b);this.mapObject=a;this.setGMapVisibility(this.visibility)},onMapResize:function(){this.visibility&&google.maps.event.trigger(this.mapObject,"resize")},
+setGMapVisibility:function(a){var b=OpenLayers.Layer.Google.cache[this.map.id],c=this.map;if(b){for(var d=this.type,e=c.layers,f,g=e.length-1;0<=g;--g)if(f=e[g],f instanceof OpenLayers.Layer.Google&&!0===f.visibility&&!0===f.inRange){d=f.type;a=!0;break}e=this.mapObject.getDiv();if(!0===a){if(e.parentNode!==c.div)if(b.rendered)c.div.appendChild(e),b.googleControl.appendChild(c.viewPortDiv),google.maps.event.trigger(this.mapObject,"resize");else{var h=this;google.maps.event.addListenerOnce(this.mapObject,
+"tilesloaded",function(){b.rendered=!0;h.setGMapVisibility(h.getVisibility());h.moveTo(h.map.getCenter())})}this.mapObject.setMapTypeId(d)}else b.googleControl.hasChildNodes()&&(c.div.appendChild(c.viewPortDiv),c.div.removeChild(e))}},getMapContainer:function(){return this.mapObject.getDiv()},getMapObjectBoundsFromOLBounds:function(a){var b=null;null!=a&&(b=this.sphericalMercator?this.inverseMercator(a.bottom,a.left):new OpenLayers.LonLat(a.bottom,a.left),a=this.sphericalMercator?this.inverseMercator(a.top,
+a.right):new OpenLayers.LonLat(a.top,a.right),b=new google.maps.LatLngBounds(new google.maps.LatLng(b.lat,b.lon),new google.maps.LatLng(a.lat,a.lon)));return b},getMapObjectLonLatFromMapObjectPixel:function(a){var b=this.map.getSize(),c=this.getLongitudeFromMapObjectLonLat(this.mapObject.center),d=this.getLatitudeFromMapObjectLonLat(this.mapObject.center),e=this.map.getResolution();a=new OpenLayers.LonLat(c+(a.x-b.w/2)*e,d-(a.y-b.h/2)*e);this.wrapDateLine&&(a=a.wrapDateLine(this.maxExtent));return this.getMapObjectLonLatFromLonLat(a.lon,
+a.lat)},getMapObjectPixelFromMapObjectLonLat:function(a){var b=this.getLongitudeFromMapObjectLonLat(a);a=this.getLatitudeFromMapObjectLonLat(a);var c=this.map.getResolution(),d=this.map.getExtent();return this.getMapObjectPixelFromXY(1/c*(b-d.left),1/c*(d.top-a))},setMapObjectCenter:function(a,b){if(!1===this.animationEnabled&&b!=this.mapObject.zoom){var c=this.getMapContainer();google.maps.event.addListenerOnce(this.mapObject,"idle",function(){c.style.visibility=""});c.style.visibility="hidden"}this.mapObject.setOptions({center:a,
+zoom:b})},getMapObjectZoomFromMapObjectBounds:function(a){return this.mapObject.getBoundsZoomLevel(a)},getMapObjectLonLatFromLonLat:function(a,b){var c;this.sphericalMercator?(c=this.inverseMercator(a,b),c=new google.maps.LatLng(c.lat,c.lon)):c=new google.maps.LatLng(b,a);return c},getMapObjectPixelFromXY:function(a,b){return new google.maps.Point(a,b)}};OpenLayers.Format.WPSDescribeProcess=OpenLayers.Class(OpenLayers.Format.XML,{VERSION:"1.0.0",namespaces:{wps:"http://www.opengis.net/wps/1.0.0",ows:"http://www.opengis.net/ows/1.1",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",defaultPrefix:"wps",regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,
+[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{wps:{ProcessDescriptions:function(a,b){b.processDescriptions={};this.readChildNodes(a,b.processDescriptions)},ProcessDescription:function(a,b){var c={processVersion:this.getAttributeNS(a,this.namespaces.wps,"processVersion"),statusSupported:"true"===a.getAttribute("statusSupported"),storeSupported:"true"===a.getAttribute("storeSupported")};this.readChildNodes(a,c);b[c.identifier]=c},DataInputs:function(a,
+b){b.dataInputs=[];this.readChildNodes(a,b.dataInputs)},ProcessOutputs:function(a,b){b.processOutputs=[];this.readChildNodes(a,b.processOutputs)},Output:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)},ComplexOutput:function(a,b){b.complexOutput={};this.readChildNodes(a,b.complexOutput)},LiteralOutput:function(a,b){b.literalOutput={};this.readChildNodes(a,b.literalOutput)},Input:function(a,b){var c={maxOccurs:parseInt(a.getAttribute("maxOccurs")),minOccurs:parseInt(a.getAttribute("minOccurs"))};
+this.readChildNodes(a,c);b.push(c)},BoundingBoxData:function(a,b){b.boundingBoxData={};this.readChildNodes(a,b.boundingBoxData)},CRS:function(a,b){b.CRSs||(b.CRSs={});b.CRSs[this.getChildValue(a)]=!0},LiteralData:function(a,b){b.literalData={};this.readChildNodes(a,b.literalData)},ComplexData:function(a,b){b.complexData={};this.readChildNodes(a,b.complexData)},Default:function(a,b){b["default"]={};this.readChildNodes(a,b["default"])},Supported:function(a,b){b.supported={};this.readChildNodes(a,b.supported)},
+Format:function(a,b){var c={};this.readChildNodes(a,c);b.formats||(b.formats={});b.formats[c.mimeType]=!0},MimeType:function(a,b){b.mimeType=this.getChildValue(a)}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WPSDescribeProcess"});OpenLayers.Format.WKT=OpenLayers.Class(OpenLayers.Format,{initialize:function(a){this.regExes={typeStr:/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,spaces:/\s+/,parenComma:/\)\s*,\s*\(/,doubleParenComma:/\)\s*\)\s*,\s*\(\s*\(/,trimParens:/^\s*\(?(.*?)\)?\s*$/};OpenLayers.Format.prototype.initialize.apply(this,[a])},read:function(a){var b,c;a=a.replace(/[\n\r]/g," ");if(c=this.regExes.typeStr.exec(a))if(a=c[1].toLowerCase(),c=c[2],this.parse[a]&&(b=this.parse[a].apply(this,[c])),this.internalProjection&&this.externalProjection)if(b&&
+"OpenLayers.Feature.Vector"==b.CLASS_NAME)b.geometry.transform(this.externalProjection,this.internalProjection);else if(b&&"geometrycollection"!=a&&"object"==typeof b)for(a=0,c=b.length;a<c;a++)b[a].geometry.transform(this.externalProjection,this.internalProjection);return b},write:function(a){var b,c;a.constructor==Array?c=!0:(a=[a],c=!1);var d=[];c&&d.push("GEOMETRYCOLLECTION(");for(var e=0,f=a.length;e<f;++e)c&&0<e&&d.push(","),b=a[e].geometry,d.push(this.extractGeometry(b));c&&d.push(")");return d.join("")},
+extractGeometry:function(a){var b=a.CLASS_NAME.split(".")[2].toLowerCase();if(!this.extract[b])return null;this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));return("collection"==b?"GEOMETRYCOLLECTION":b.toUpperCase())+"("+this.extract[b].apply(this,[a])+")"},extract:{point:function(a){return a.x+" "+a.y},multipoint:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.point.apply(this,[a.components[c]])+
+")");return b.join(",")},linestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b.join(",")},multilinestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.linestring.apply(this,[a.components[c]])+")");return b.join(",")},polygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.linestring.apply(this,[a.components[c]])+")");return b.join(",")},multipolygon:function(a){for(var b=
+[],c=0,d=a.components.length;c<d;++c)b.push("("+this.extract.polygon.apply(this,[a.components[c]])+")");return b.join(",")},collection:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extractGeometry.apply(this,[a.components[c]]));return b.join(",")}},parse:{point:function(a){a=OpenLayers.String.trim(a).split(this.regExes.spaces);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(a[0],a[1]))},multipoint:function(a){for(var b=OpenLayers.String.trim(a).split(","),
+c=[],d=0,e=b.length;d<e;++d)a=b[d].replace(this.regExes.trimParens,"$1"),c.push(this.parse.point.apply(this,[a]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint(c))},linestring:function(a){a=OpenLayers.String.trim(a).split(",");for(var b=[],c=0,d=a.length;c<d;++c)b.push(this.parse.point.apply(this,[a[c]]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(b))},multilinestring:function(a){for(var b=OpenLayers.String.trim(a).split(this.regExes.parenComma),
+c=[],d=0,e=b.length;d<e;++d)a=b[d].replace(this.regExes.trimParens,"$1"),c.push(this.parse.linestring.apply(this,[a]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiLineString(c))},polygon:function(a){var b;a=OpenLayers.String.trim(a).split(this.regExes.parenComma);for(var c=[],d=0,e=a.length;d<e;++d)b=a[d].replace(this.regExes.trimParens,"$1"),b=this.parse.linestring.apply(this,[b]).geometry,b=new OpenLayers.Geometry.LinearRing(b.components),c.push(b);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon(c))},
+multipolygon:function(a){for(var b=OpenLayers.String.trim(a).split(this.regExes.doubleParenComma),c=[],d=0,e=b.length;d<e;++d)a=b[d].replace(this.regExes.trimParens,"$1"),c.push(this.parse.polygon.apply(this,[a]).geometry);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPolygon(c))},geometrycollection:function(a){a=a.replace(/,\s*([A-Za-z])/g,"|$1");a=OpenLayers.String.trim(a).split("|");for(var b=[],c=0,d=a.length;c<d;++c)b.push(OpenLayers.Format.WKT.prototype.read.apply(this,[a[c]]));
+return b}},CLASS_NAME:"OpenLayers.Format.WKT"});OpenLayers.WPSProcess=OpenLayers.Class({client:null,server:null,identifier:null,description:null,localWPS:"http://geoserver/wps",formats:null,chained:0,executeCallbacks:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.executeCallbacks=[];this.formats={"application/wkt":new OpenLayers.Format.WKT,"application/json":new OpenLayers.Format.GeoJSON}},describe:function(a){a=a||{};if(!this.description)this.client.describeProcess(this.server,this.identifier,function(b){this.description||this.parseDescription(b);
+a.callback&&a.callback.call(a.scope,this.description)},this);else if(a.callback){var b=this.description;window.setTimeout(function(){a.callback.call(a.scope,b)},0)}},configure:function(a){this.describe({callback:function(){var b=this.description,c=a.inputs,d,e,f;e=0;for(f=b.dataInputs.length;e<f;++e)d=b.dataInputs[e],this.setInputData(d,c[d.identifier]);a.callback&&a.callback.call(a.scope)},scope:this});return this},execute:function(a){this.configure({inputs:a.inputs,callback:function(){var b=this,
+c=this.getOutputIndex(b.description.processOutputs,a.output);b.setResponseForm({outputIndex:c});(function e(){OpenLayers.Util.removeItem(b.executeCallbacks,e);0!==b.chained?b.executeCallbacks.push(e):OpenLayers.Request.POST({url:b.client.servers[b.server].url,data:(new OpenLayers.Format.WPSExecute).write(b.description),success:function(e){var g=b.findMimeType(b.description.processOutputs[c].complexOutput.supported.formats);e=b.formats[g].read(e.responseText);e instanceof OpenLayers.Feature.Vector&&
+(e=[e]);a.success&&(g={},g[a.output||"result"]=e,a.success.call(a.scope,g))},scope:b})})()},scope:this})},output:function(a){return new OpenLayers.WPSProcess.ChainLink({process:this,output:a})},parseDescription:function(a){a=this.client.servers[this.server];this.description=(new OpenLayers.Format.WPSDescribeProcess).read(a.processDescription[this.identifier]).processDescriptions[this.identifier]},setInputData:function(a,b){delete a.data;delete a.reference;if(b instanceof OpenLayers.WPSProcess.ChainLink)++this.chained,
+a.reference={method:"POST",href:b.process.server===this.server?this.localWPS:this.client.servers[b.process.server].url},b.process.describe({callback:function(){--this.chained;this.chainProcess(a,b)},scope:this});else{a.data={};var c=a.complexData;c?(c=this.findMimeType(c.supported.formats),a.data.complexData={mimeType:c,value:this.formats[c].write(this.toFeatures(b))}):a.data.literalData={value:b}}},setResponseForm:function(a){a=a||{};var b=this.description.processOutputs[a.outputIndex||0];this.description.responseForm=
+{rawDataOutput:{identifier:b.identifier,mimeType:this.findMimeType(b.complexOutput.supported.formats,a.supportedFormats)}}},getOutputIndex:function(a,b){var c;if(b)for(var d=a.length-1;0<=d;--d){if(a[d].identifier===b){c=d;break}}else c=0;return c},chainProcess:function(a,b){var c=this.getOutputIndex(b.process.description.processOutputs,b.output);a.reference.mimeType=this.findMimeType(a.complexData.supported.formats,b.process.description.processOutputs[c].complexOutput.supported.formats);var d={};
+d[a.reference.mimeType]=!0;b.process.setResponseForm({outputIndex:c,supportedFormats:d});for(a.reference.body=b.process.description;0<this.executeCallbacks.length;)this.executeCallbacks[0]()},toFeatures:function(a){var b=OpenLayers.Util.isArray(a);b||(a=[a]);for(var c=Array(a.length),d,e=0,f=a.length;e<f;++e)d=a[e],c[e]=d instanceof OpenLayers.Feature.Vector?d:new OpenLayers.Feature.Vector(d);return b?c:c[0]},findMimeType:function(a,b){b=b||this.formats;for(var c in a)if(c in b)return c},CLASS_NAME:"OpenLayers.WPSProcess"});
+OpenLayers.WPSProcess.ChainLink=OpenLayers.Class({process:null,output:null,initialize:function(a){OpenLayers.Util.extend(this,a)},CLASS_NAME:"OpenLayers.WPSProcess.ChainLink"});OpenLayers.WPSClient=OpenLayers.Class({servers:null,version:"1.0.0",lazy:!1,events:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.events=new OpenLayers.Events(this);this.servers={};for(var b in a.servers)this.servers[b]="string"==typeof a.servers[b]?{url:a.servers[b],version:this.version,processDescription:{}}:a.servers[b]},execute:function(a){this.getProcess(a.server,a.process).execute({inputs:a.inputs,success:a.success,scope:a.scope})},getProcess:function(a,b){var c=new OpenLayers.WPSProcess({client:this,
+server:a,identifier:b});this.lazy||c.describe();return c},describeProcess:function(a,b,c,d){var e=this.servers[a];e.processDescription[b]?window.setTimeout(function(){c.call(d,e.processDescription[b])},0):b in e.processDescription?this.events.register("describeprocess",this,function g(a){a.identifier===b&&(this.events.unregister("describeprocess",this,g),c.call(d,a.raw))}):(e.processDescription[b]=null,OpenLayers.Request.GET({url:e.url,params:{SERVICE:"WPS",VERSION:e.version,REQUEST:"DescribeProcess",
+IDENTIFIER:b},success:function(a){e.processDescription[b]=a.responseText;this.events.triggerEvent("describeprocess",{identifier:b,raw:a.responseText})},scope:this}))},destroy:function(){this.events.destroy();this.servers=this.events=null},CLASS_NAME:"OpenLayers.WPSClient"});OpenLayers.Format.CSWGetRecords.v2_0_2=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{csw:"http://www.opengis.net/cat/csw/2.0.2",dc:"http://purl.org/dc/elements/1.1/",dct:"http://purl.org/dc/terms/",gmd:"http://www.isotc211.org/2005/gmd",geonet:"http://www.fao.org/geonetwork",ogc:"http://www.opengis.net/ogc",ows:"http://www.opengis.net/ows",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"csw",version:"2.0.2",schemaLocation:"http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",
+requestId:null,resultType:null,outputFormat:null,outputSchema:null,startPosition:null,maxRecords:null,DistributedSearch:null,ResponseHandler:null,Query:null,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},
+readers:{csw:{GetRecordsResponse:function(a,b){b.records=[];this.readChildNodes(a,b);var c=this.getAttributeNS(a,"","version");""!=c&&(b.version=c)},RequestId:function(a,b){b.RequestId=this.getChildValue(a)},SearchStatus:function(a,b){b.SearchStatus={};var c=this.getAttributeNS(a,"","timestamp");""!=c&&(b.SearchStatus.timestamp=c)},SearchResults:function(a,b){this.readChildNodes(a,b);for(var c=a.attributes,d={},e=0,f=c.length;e<f;++e)d[c[e].name]="numberOfRecordsMatched"==c[e].name||"numberOfRecordsReturned"==
+c[e].name||"nextRecord"==c[e].name?parseInt(c[e].nodeValue):c[e].nodeValue;b.SearchResults=d},SummaryRecord:function(a,b){var c={type:"SummaryRecord"};this.readChildNodes(a,c);b.records.push(c)},BriefRecord:function(a,b){var c={type:"BriefRecord"};this.readChildNodes(a,c);b.records.push(c)},DCMIRecord:function(a,b){var c={type:"DCMIRecord"};this.readChildNodes(a,c);b.records.push(c)},Record:function(a,b){var c={type:"Record"};this.readChildNodes(a,c);b.records.push(c)},"*":function(a,b){var c=a.localName||
+a.nodeName.split(":").pop();b[c]=this.getChildValue(a)}},geonet:{info:function(a,b){var c={};this.readChildNodes(a,c);b.gninfo=c}},dc:{"*":function(a,b){var c=a.localName||a.nodeName.split(":").pop();OpenLayers.Util.isArray(b[c])||(b[c]=[]);for(var d={},e=a.attributes,f=0,g=e.length;f<g;++f)d[e[f].name]=e[f].nodeValue;d.value=this.getChildValue(a);""!=d.value&&b[c].push(d)}},dct:{"*":function(a,b){var c=a.localName||a.nodeName.split(":").pop();OpenLayers.Util.isArray(b[c])||(b[c]=[]);b[c].push(this.getChildValue(a))}},
+ows:OpenLayers.Util.applyDefaults({BoundingBox:function(a,b){b.bounds&&(b.BoundingBox=[{crs:b.projection,value:[b.bounds.left,b.bounds.bottom,b.bounds.right,b.bounds.top]}],delete b.projection,delete b.bounds);OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows.BoundingBox.apply(this,arguments)}},OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows)},write:function(a){a=this.writeNode("csw:GetRecords",a);a.setAttribute("xmlns:gmd",this.namespaces.gmd);return OpenLayers.Format.XML.prototype.write.apply(this,
+[a])},writers:{csw:{GetRecords:function(a){a||(a={});var b=this.createElementNSPlus("csw:GetRecords",{attributes:{service:"CSW",version:this.version,requestId:a.requestId||this.requestId,resultType:a.resultType||this.resultType,outputFormat:a.outputFormat||this.outputFormat,outputSchema:a.outputSchema||this.outputSchema,startPosition:a.startPosition||this.startPosition,maxRecords:a.maxRecords||this.maxRecords}});(a.DistributedSearch||this.DistributedSearch)&&this.writeNode("csw:DistributedSearch",
+a.DistributedSearch||this.DistributedSearch,b);var c=a.ResponseHandler||this.ResponseHandler;if(OpenLayers.Util.isArray(c)&&0<c.length)for(var d=0,e=c.length;d<e;d++)this.writeNode("csw:ResponseHandler",c[d],b);this.writeNode("Query",a.Query||this.Query,b);return b},DistributedSearch:function(a){return this.createElementNSPlus("csw:DistributedSearch",{attributes:{hopCount:a.hopCount}})},ResponseHandler:function(a){return this.createElementNSPlus("csw:ResponseHandler",{value:a.value})},Query:function(a){a||
+(a={});var b=this.createElementNSPlus("csw:Query",{attributes:{typeNames:a.typeNames||"csw:Record"}}),c=a.ElementName;if(OpenLayers.Util.isArray(c)&&0<c.length)for(var d=0,e=c.length;d<e;d++)this.writeNode("csw:ElementName",c[d],b);else this.writeNode("csw:ElementSetName",a.ElementSetName||{value:"summary"},b);a.Constraint&&this.writeNode("csw:Constraint",a.Constraint,b);a.SortBy&&this.writeNode("ogc:SortBy",a.SortBy,b);return b},ElementName:function(a){return this.createElementNSPlus("csw:ElementName",
+{value:a.value})},ElementSetName:function(a){return this.createElementNSPlus("csw:ElementSetName",{attributes:{typeNames:a.typeNames},value:a.value})},Constraint:function(a){var b=this.createElementNSPlus("csw:Constraint",{attributes:{version:a.version}});if(a.Filter){var c=new OpenLayers.Format.Filter({version:a.version});b.appendChild(c.write(a.Filter))}else a.CqlText&&(a=this.createElementNSPlus("CqlText",{value:a.CqlText.value}),b.appendChild(a));return b}},ogc:OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc},
+CLASS_NAME:"OpenLayers.Format.CSWGetRecords.v2_0_2"});/*
+ Apache 2
+
+ Contains portions of Rico <http://openrico.org/>
+
+ Copyright 2005 Sabre Airline Solutions
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you
+ may not use this file except in compliance with the License. You
+ may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied. See the License for the specific language governing
+ permissions and limitations under the License.
+*/
+OpenLayers.Marker.Box=OpenLayers.Class(OpenLayers.Marker,{bounds:null,div:null,initialize:function(a,b,c){this.bounds=a;this.div=OpenLayers.Util.createDiv();this.div.style.overflow="hidden";this.events=new OpenLayers.Events(this,this.div);this.setBorder(b,c)},destroy:function(){this.div=this.bounds=null;OpenLayers.Marker.prototype.destroy.apply(this,arguments)},setBorder:function(a,b){a||(a="red");b||(b=2);this.div.style.border=b+"px solid "+a},draw:function(a,b){OpenLayers.Util.modifyDOMElement(this.div,
+null,a,b);return this.div},onScreen:function(){var a=!1;this.map&&(a=this.map.getExtent().containsBounds(this.bounds,!0,!0));return a},display:function(a){this.div.style.display=a?"":"none"},CLASS_NAME:"OpenLayers.Marker.Box"});OpenLayers.Format.Text=OpenLayers.Class(OpenLayers.Format,{defaultStyle:null,extractStyles:!0,initialize:function(a){a=a||{};!1!==a.extractStyles&&(a.defaultStyle={externalGraphic:OpenLayers.Util.getImageLocation("marker.png"),graphicWidth:21,graphicHeight:25,graphicXOffset:-10.5,graphicYOffset:-12.5});OpenLayers.Format.prototype.initialize.apply(this,[a])},read:function(a){a=a.split("\n");for(var b,c=[],d=0;d<a.length-1;d++){var e=a[d].replace(/^\s*/,"").replace(/\s*$/,"");if("#"!=e.charAt(0))if(b){for(var e=
+e.split("\t"),f=new OpenLayers.Geometry.Point(0,0),g={},h=this.defaultStyle?OpenLayers.Util.applyDefaults({},this.defaultStyle):null,k=!1,l=0;l<e.length;l++)if(e[l])if("point"==b[l])k=e[l].split(","),f.y=parseFloat(k[0]),f.x=parseFloat(k[1]),k=!0;else if("lat"==b[l])f.y=parseFloat(e[l]),k=!0;else if("lon"==b[l])f.x=parseFloat(e[l]),k=!0;else if("title"==b[l])g.title=e[l];else if("image"==b[l]||"icon"==b[l]&&h)h.externalGraphic=e[l];else if("iconSize"==b[l]&&h){var m=e[l].split(",");h.graphicWidth=
+parseFloat(m[0]);h.graphicHeight=parseFloat(m[1])}else"iconOffset"==b[l]&&h?(m=e[l].split(","),h.graphicXOffset=parseFloat(m[0]),h.graphicYOffset=parseFloat(m[1])):"description"==b[l]?g.description=e[l]:"overflow"==b[l]?g.overflow=e[l]:g[b[l]]=e[l];k&&(this.internalProjection&&this.externalProjection&&f.transform(this.externalProjection,this.internalProjection),e=new OpenLayers.Feature.Vector(f,g,h),c.push(e))}else b=e.split("\t")}return c},CLASS_NAME:"OpenLayers.Format.Text"});OpenLayers.Layer.Text=OpenLayers.Class(OpenLayers.Layer.Markers,{location:null,features:null,formatOptions:null,selectedFeature:null,initialize:function(a,b){OpenLayers.Layer.Markers.prototype.initialize.apply(this,arguments);this.features=[]},destroy:function(){OpenLayers.Layer.Markers.prototype.destroy.apply(this,arguments);this.clearFeatures();this.features=null},loadText:function(){this.loaded||null==this.location||(this.events.triggerEvent("loadstart"),OpenLayers.Request.GET({url:this.location,
+success:this.parseData,failure:function(a){this.events.triggerEvent("loadend")},scope:this}),this.loaded=!0)},moveTo:function(a,b,c){OpenLayers.Layer.Markers.prototype.moveTo.apply(this,arguments);this.visibility&&!this.loaded&&this.loadText()},parseData:function(a){a=a.responseText;var b={};OpenLayers.Util.extend(b,this.formatOptions);this.map&&!this.projection.equals(this.map.getProjectionObject())&&(b.externalProjection=this.projection,b.internalProjection=this.map.getProjectionObject());a=(new OpenLayers.Format.Text(b)).read(a);
+for(var b=0,c=a.length;b<c;b++){var d={},e=a[b],f,g,h;f=new OpenLayers.LonLat(e.geometry.x,e.geometry.y);e.style.graphicWidth&&e.style.graphicHeight&&(g=new OpenLayers.Size(e.style.graphicWidth,e.style.graphicHeight));void 0!==e.style.graphicXOffset&&void 0!==e.style.graphicYOffset&&(h=new OpenLayers.Pixel(e.style.graphicXOffset,e.style.graphicYOffset));null!=e.style.externalGraphic?d.icon=new OpenLayers.Icon(e.style.externalGraphic,g,h):(d.icon=OpenLayers.Marker.defaultIcon(),null!=g&&d.icon.setSize(g));
+null!=e.attributes.title&&null!=e.attributes.description&&(d.popupContentHTML="<h2>"+e.attributes.title+"</h2><p>"+e.attributes.description+"</p>");d.overflow=e.attributes.overflow||"auto";d=new OpenLayers.Feature(this,f,d);this.features.push(d);f=d.createMarker();null!=e.attributes.title&&null!=e.attributes.description&&f.events.register("click",d,this.markerClick);this.addMarker(f)}this.events.triggerEvent("loadend")},markerClick:function(a){var b=this==this.layer.selectedFeature;this.layer.selectedFeature=
+b?null:this;for(var c=0,d=this.layer.map.popups.length;c<d;c++)this.layer.map.removePopup(this.layer.map.popups[c]);b||this.layer.map.addPopup(this.createPopup());OpenLayers.Event.stop(a)},clearFeatures:function(){if(null!=this.features)for(;0<this.features.length;){var a=this.features[0];OpenLayers.Util.removeItem(this.features,a);a.destroy()}},CLASS_NAME:"OpenLayers.Layer.Text"});OpenLayers.Handler.RegularPolygon=OpenLayers.Class(OpenLayers.Handler.Drag,{sides:4,radius:null,snapAngle:null,snapToggle:"shiftKey",layerOptions:null,persist:!1,irregular:!1,citeCompliant:!1,angle:null,fixedRadius:!1,feature:null,layer:null,origin:null,initialize:function(a,b,c){c&&c.layerOptions&&c.layerOptions.styleMap||(this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style["default"],{}));OpenLayers.Handler.Drag.prototype.initialize.apply(this,[a,b,c]);this.options=c?c:{}},setOptions:function(a){OpenLayers.Util.extend(this.options,
+a);OpenLayers.Util.extend(this,a)},activate:function(){var a=!1;OpenLayers.Handler.Drag.prototype.activate.apply(this,arguments)&&(a=OpenLayers.Util.extend({displayInLayerSwitcher:!1,calculateInRange:OpenLayers.Function.True,wrapDateLine:this.citeCompliant},this.layerOptions),this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,a),this.map.addLayer(this.layer),a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Handler.Drag.prototype.deactivate.apply(this,arguments)&&(this.dragging&&this.cancel(),
+null!=this.layer.map&&(this.layer.destroy(!1),this.feature&&this.feature.destroy()),this.feature=this.layer=null,a=!0);return a},down:function(a){this.fixedRadius=!!this.radius;a=this.layer.getLonLatFromViewPortPx(a.xy);this.origin=new OpenLayers.Geometry.Point(a.lon,a.lat);if(!this.fixedRadius||this.irregular)this.radius=this.map.getResolution();this.persist&&this.clear();this.feature=new OpenLayers.Feature.Vector;this.createGeometry();this.callback("create",[this.origin,this.feature]);this.layer.addFeatures([this.feature],
+{silent:!0});this.layer.drawFeature(this.feature,this.style)},move:function(a){var b=this.layer.getLonLatFromViewPortPx(a.xy),b=new OpenLayers.Geometry.Point(b.lon,b.lat);this.irregular?(a=Math.sqrt(2)*Math.abs(b.y-this.origin.y)/2,this.radius=Math.max(this.map.getResolution()/2,a)):this.fixedRadius?this.origin=b:(this.calculateAngle(b,a),this.radius=Math.max(this.map.getResolution()/2,b.distanceTo(this.origin)));this.modifyGeometry();if(this.irregular){a=b.x-this.origin.x;var b=b.y-this.origin.y,
+c;c=0==b?a/(this.radius*Math.sqrt(2)):a/b;this.feature.geometry.resize(1,this.origin,c);this.feature.geometry.move(a/2,b/2)}this.layer.drawFeature(this.feature,this.style)},up:function(a){this.finalize();this.start==this.last&&this.callback("done",[a.xy])},out:function(a){this.finalize()},createGeometry:function(){this.angle=Math.PI*(1/this.sides-0.5);this.snapAngle&&(this.angle+=this.snapAngle*(Math.PI/180));this.feature.geometry=OpenLayers.Geometry.Polygon.createRegularPolygon(this.origin,this.radius,
+this.sides,this.snapAngle)},modifyGeometry:function(){var a,b,c=this.feature.geometry.components[0];c.components.length!=this.sides+1&&(this.createGeometry(),c=this.feature.geometry.components[0]);for(var d=0;d<this.sides;++d)b=c.components[d],a=this.angle+2*d*Math.PI/this.sides,b.x=this.origin.x+this.radius*Math.cos(a),b.y=this.origin.y+this.radius*Math.sin(a),b.clearBounds()},calculateAngle:function(a,b){var c=Math.atan2(a.y-this.origin.y,a.x-this.origin.x);if(this.snapAngle&&this.snapToggle&&!b[this.snapToggle]){var d=
+Math.PI/180*this.snapAngle;this.angle=Math.round(c/d)*d}else this.angle=c},cancel:function(){this.callback("cancel",null);this.finalize()},finalize:function(){this.origin=null;this.radius=this.options.radius},clear:function(){this.layer&&(this.layer.renderer.clear(),this.layer.destroyFeatures())},callback:function(a,b){this.callbacks[a]&&this.callbacks[a].apply(this.control,[this.feature.geometry.clone()]);this.persist||"done"!=a&&"cancel"!=a||this.clear()},CLASS_NAME:"OpenLayers.Handler.RegularPolygon"});OpenLayers.Control.SLDSelect=OpenLayers.Class(OpenLayers.Control,{clearOnDeactivate:!1,layers:null,callbacks:null,selectionSymbolizer:{Polygon:{fillColor:"#FF0000",stroke:!1},Line:{strokeColor:"#FF0000",strokeWidth:2},Point:{graphicName:"square",fillColor:"#FF0000",pointRadius:5}},layerOptions:null,sketchStyle:null,wfsCache:{},layerCache:{},initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);this.callbacks=OpenLayers.Util.extend({done:this.select,click:this.select},this.callbacks);
+this.handlerOptions=this.handlerOptions||{};this.layerOptions=OpenLayers.Util.applyDefaults(this.layerOptions,{displayInLayerSwitcher:!1,tileOptions:{maxGetUrlLength:2048}});this.sketchStyle&&(this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":this.sketchStyle})}));this.handler=new a(this,this.callbacks,this.handlerOptions)},destroy:function(){for(var a in this.layerCache)delete this.layerCache[a];for(a in this.wfsCache)delete this.wfsCache[a];
+OpenLayers.Control.prototype.destroy.apply(this,arguments)},coupleLayerVisiblity:function(a){this.setVisibility(a.object.getVisibility())},createSelectionLayer:function(a){var b;if(this.layerCache[a.id])b=this.layerCache[a.id];else{b=new OpenLayers.Layer.WMS(a.name,a.url,a.params,OpenLayers.Util.applyDefaults(this.layerOptions,a.getOptions()));this.layerCache[a.id]=b;if(!1===this.layerOptions.displayInLayerSwitcher)a.events.on({visibilitychanged:this.coupleLayerVisiblity,scope:b});this.map.addLayer(b)}return b},
+createSLD:function(a,b,c){for(var d={version:"1.0.0",namedLayers:{}},e=(""+a.params.LAYERS).split(","),f=0,g=e.length;f<g;f++){var h=e[f];d.namedLayers[h]={name:h,userStyles:[]};var k=this.selectionSymbolizer,l=c[f];0<=l.type.indexOf("Polygon")?k={Polygon:this.selectionSymbolizer.Polygon}:0<=l.type.indexOf("LineString")?k={Line:this.selectionSymbolizer.Line}:0<=l.type.indexOf("Point")&&(k={Point:this.selectionSymbolizer.Point});d.namedLayers[h].userStyles.push({name:"default",rules:[new OpenLayers.Rule({symbolizer:k,
+filter:b[f],maxScaleDenominator:a.options.minScale})]})}return(new OpenLayers.Format.SLD({srsName:this.map.getProjection()})).write(d)},parseDescribeLayer:function(a){var b=new OpenLayers.Format.WMSDescribeLayer,c=a.responseXML;c&&c.documentElement||(c=a.responseText);a=b.read(c);for(var b=[],c=null,d=0,e=a.length;d<e;d++)"WFS"==a[d].owsType&&(b.push(a[d].typeName),c=a[d].owsURL);OpenLayers.Request.GET({url:c,params:{SERVICE:"WFS",TYPENAME:b.toString(),REQUEST:"DescribeFeatureType",VERSION:"1.0.0"},
+callback:function(a){var b=new OpenLayers.Format.WFSDescribeFeatureType,c=a.responseXML;c&&c.documentElement||(c=a.responseText);a=b.read(c);this.control.wfsCache[this.layer.id]=a;this.control._queue&&this.control.applySelection()},scope:this})},getGeometryAttributes:function(a){var b=[];a=this.wfsCache[a.id];for(var c=0,d=a.featureTypes.length;c<d;c++)for(var e=a.featureTypes[c].properties,f=0,g=e.length;f<g;f++){var h=e[f],k=h.type;(0<=k.indexOf("LineString")||0<=k.indexOf("GeometryAssociationType")||
+0<=k.indexOf("GeometryPropertyType")||0<=k.indexOf("Point")||0<=k.indexOf("Polygon"))&&b.push(h)}return b},activate:function(){var a=OpenLayers.Control.prototype.activate.call(this);if(a)for(var b=0,c=this.layers.length;b<c;b++){var d=this.layers[b];d&&!this.wfsCache[d.id]&&OpenLayers.Request.GET({url:d.url,params:{SERVICE:"WMS",VERSION:d.params.VERSION,LAYERS:d.params.LAYERS,REQUEST:"DescribeLayer"},callback:this.parseDescribeLayer,scope:{layer:d,control:this}})}return a},deactivate:function(){var a=
+OpenLayers.Control.prototype.deactivate.call(this);if(a)for(var b=0,c=this.layers.length;b<c;b++){var d=this.layers[b];if(d&&!0===this.clearOnDeactivate){var e=this.layerCache,f=e[d.id];f&&(d.events.un({visibilitychanged:this.coupleLayerVisiblity,scope:f}),f.destroy(),delete e[d.id])}}return a},setLayers:function(a){this.active?(this.deactivate(),this.layers=a,this.activate()):this.layers=a},createFilter:function(a,b){var c=null;this.handler instanceof OpenLayers.Handler.RegularPolygon?c=!0===this.handler.irregular?
+new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,property:a.name,value:b.getBounds()}):new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):this.handler instanceof OpenLayers.Handler.Polygon?c=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):this.handler instanceof OpenLayers.Handler.Path?c=0<=a.type.indexOf("Point")?new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWITHIN,
+property:a.name,distance:0.01*this.map.getExtent().getWidth(),distanceUnits:this.map.getUnits(),value:b}):new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):this.handler instanceof OpenLayers.Handler.Click&&(c=0<=a.type.indexOf("Polygon")?new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:a.name,value:b}):new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWITHIN,property:a.name,distance:0.01*this.map.getExtent().getWidth(),
+distanceUnits:this.map.getUnits(),value:b}));return c},select:function(a){this._queue=function(){for(var b=0,c=this.layers.length;b<c;b++){for(var d=this.layers[b],e=this.getGeometryAttributes(d),f=[],g=0,h=e.length;g<h;g++){var k=e[g];if(null!==k){if(!(a instanceof OpenLayers.Geometry)){var l=this.map.getLonLatFromPixel(a.xy);a=new OpenLayers.Geometry.Point(l.lon,l.lat)}k=this.createFilter(k,a);null!==k&&f.push(k)}}g=this.createSelectionLayer(d);this.events.triggerEvent("selected",{layer:d,filters:f});
+d=this.createSLD(d,f,e);g.mergeNewParams({SLD_BODY:d});delete this._queue}};this.applySelection()},applySelection:function(){for(var a=!0,b=0,c=this.layers.length;b<c;b++)if(!this.wfsCache[this.layers[b].id]){a=!1;break}a&&this._queue.call(this)},CLASS_NAME:"OpenLayers.Control.SLDSelect"});OpenLayers.Control.Scale=OpenLayers.Class(OpenLayers.Control,{element:null,geodesic:!1,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);this.element=OpenLayers.Util.getElement(a)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.element||(this.element=document.createElement("div"),this.div.appendChild(this.element));this.map.events.register("moveend",this,this.updateScale);this.updateScale();return this.div},updateScale:function(){var a;
+if(!0===this.geodesic){if(!this.map.getUnits())return;a=OpenLayers.INCHES_PER_UNIT;a=(this.map.getGeodesicPixelSize().w||1E-6)*a.km*OpenLayers.DOTS_PER_INCH}else a=this.map.getScale();a&&(a=9500<=a&&95E4>=a?Math.round(a/1E3)+"K":95E4<=a?Math.round(a/1E6)+"M":Math.round(a),this.element.innerHTML=OpenLayers.i18n("Scale = 1 : ${scaleDenom}",{scaleDenom:a}))},CLASS_NAME:"OpenLayers.Control.Scale"});OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,useHttpTile:!1,singleTile:!1,useOverlay:!1,useAsyncOverlay:!0,TILE_PARAMS:{operation:"GETTILEIMAGE",version:"1.2.0"},SINGLE_TILE_PARAMS:{operation:"GETMAPIMAGE",format:"PNG",locale:"en",clip:"1",version:"1.0.0"},OVERLAY_PARAMS:{operation:"GETDYNAMICMAPOVERLAYIMAGE",format:"PNG",locale:"en",clip:"1",version:"2.0.0"},FOLDER_PARAMS:{tileColumnsPerFolder:30,tileRowsPerFolder:30,format:"png",querystring:null},defaultSize:new OpenLayers.Size(300,
+300),tileOriginCorner:"tl",initialize:function(a,b,c,d){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(null==d||null==d.isBaseLayer)this.isBaseLayer="true"!=this.transparent&&!0!=this.transparent;d&&null!=d.useOverlay&&(this.useOverlay=d.useOverlay);this.singleTile?this.useOverlay?(OpenLayers.Util.applyDefaults(this.params,this.OVERLAY_PARAMS),this.useAsyncOverlay||(this.params.version="1.0.0")):OpenLayers.Util.applyDefaults(this.params,this.SINGLE_TILE_PARAMS):(this.useHttpTile?
+OpenLayers.Util.applyDefaults(this.params,this.FOLDER_PARAMS):OpenLayers.Util.applyDefaults(this.params,this.TILE_PARAMS),this.setTileSize(this.defaultSize))},clone:function(a){null==a&&(a=new OpenLayers.Layer.MapGuide(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){var b;b=a.getCenterLonLat();var c=this.map.getSize();this.singleTile?(a={setdisplaydpi:OpenLayers.DOTS_PER_INCH,setdisplayheight:c.h*this.ratio,setdisplaywidth:c.w*
+this.ratio,setviewcenterx:b.lon,setviewcentery:b.lat,setviewscale:this.map.getScale()},this.useOverlay&&!this.useAsyncOverlay&&(b={},b=OpenLayers.Util.extend(b,a),b.operation="GETVISIBLEMAPEXTENT",b.version="1.0.0",b.session=this.params.session,b.mapName=this.params.mapName,b.format="text/xml",b=this.getFullRequestString(b),OpenLayers.Request.GET({url:b,async:!1})),b=this.getFullRequestString(a)):(c=this.map.getResolution(),b=Math.floor((a.left-this.maxExtent.left)/c),b=Math.round(b/this.tileSize.w),
+a=Math.floor((this.maxExtent.top-a.top)/c),a=Math.round(a/this.tileSize.h),b=this.useHttpTile?this.getImageFilePath({tilecol:b,tilerow:a,scaleindex:this.resolutions.length-this.map.zoom-1}):this.getFullRequestString({tilecol:b,tilerow:a,scaleindex:this.resolutions.length-this.map.zoom-1}));return b},getFullRequestString:function(a,b){var c=null==b?this.url:b;"object"==typeof c&&(c=c[Math.floor(Math.random()*c.length)]);var d=c,e=OpenLayers.Util.extend({},this.params),e=OpenLayers.Util.extend(e,a),
+f=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),g;for(g in e)g.toUpperCase()in f&&delete e[g];e=OpenLayers.Util.getParameterString(e);e=e.replace(/,/g,"+");""!=e&&(f=c.charAt(c.length-1),d="&"==f||"?"==f?d+e:-1==c.indexOf("?")?d+("?"+e):d+("&"+e));return d},getImageFilePath:function(a,b){var c=null==b?this.url:b;"object"==typeof c&&(c=c[Math.floor(Math.random()*c.length)]);var d="",e="";0>a.tilerow&&(d="-");d=0==a.tilerow?d+"0":d+Math.floor(Math.abs(a.tilerow/this.params.tileRowsPerFolder))*
+this.params.tileRowsPerFolder;0>a.tilecol&&(e="-");e=0==a.tilecol?e+"0":e+Math.floor(Math.abs(a.tilecol/this.params.tileColumnsPerFolder))*this.params.tileColumnsPerFolder;d="/S"+Math.floor(a.scaleindex)+"/"+this.params.basemaplayergroupname+"/R"+d+"/C"+e+"/"+a.tilerow%this.params.tileRowsPerFolder+"_"+a.tilecol%this.params.tileColumnsPerFolder+"."+this.params.format;this.params.querystring&&(d+="?"+this.params.querystring);return c+d},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers.Control.Measure=OpenLayers.Class(OpenLayers.Control,{callbacks:null,displaySystem:"metric",geodesic:!1,displaySystemUnits:{geographic:["dd"],english:["mi","ft","in"],metric:["km","m"]},partialDelay:300,delayedTrigger:null,persist:!1,immediate:!1,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);var c={done:this.measureComplete,point:this.measurePartial};this.immediate&&(c.modify=this.measureImmediate);this.callbacks=OpenLayers.Util.extend(c,this.callbacks);
+this.handlerOptions=OpenLayers.Util.extend({persist:this.persist},this.handlerOptions);this.handler=new a(this,this.callbacks,this.handlerOptions)},deactivate:function(){this.cancelDelay();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},cancel:function(){this.cancelDelay();this.handler.cancel()},setImmediate:function(a){(this.immediate=a)?this.callbacks.modify=this.measureImmediate:delete this.callbacks.modify},updateHandler:function(a,b){var c=this.active;c&&this.deactivate();
+this.handler=new a(this,this.callbacks,b);c&&this.activate()},measureComplete:function(a){this.cancelDelay();this.measure(a,"measure")},measurePartial:function(a,b){this.cancelDelay();b=b.clone();this.handler.freehandMode(this.handler.evt)?this.measure(b,"measurepartial"):this.delayedTrigger=window.setTimeout(OpenLayers.Function.bind(function(){this.delayedTrigger=null;this.measure(b,"measurepartial")},this),this.partialDelay)},measureImmediate:function(a,b,c){c&&!this.handler.freehandMode(this.handler.evt)&&
+(this.cancelDelay(),this.measure(b.geometry,"measurepartial"))},cancelDelay:function(){null!==this.delayedTrigger&&(window.clearTimeout(this.delayedTrigger),this.delayedTrigger=null)},measure:function(a,b){var c,d;-1<a.CLASS_NAME.indexOf("LineString")?(c=this.getBestLength(a),d=1):(c=this.getBestArea(a),d=2);this.events.triggerEvent(b,{measure:c[0],units:c[1],order:d,geometry:a})},getBestArea:function(a){for(var b=this.displaySystemUnits[this.displaySystem],c,d,e=0,f=b.length;e<f&&!(c=b[e],d=this.getArea(a,
+c),1<d);++e);return[d,c]},getArea:function(a,b){var c,d;this.geodesic?(c=a.getGeodesicArea(this.map.getProjectionObject()),d="m"):(c=a.getArea(),d=this.map.getUnits());var e=OpenLayers.INCHES_PER_UNIT[b];e&&(c*=Math.pow(OpenLayers.INCHES_PER_UNIT[d]/e,2));return c},getBestLength:function(a){for(var b=this.displaySystemUnits[this.displaySystem],c,d,e=0,f=b.length;e<f&&!(c=b[e],d=this.getLength(a,c),1<d);++e);return[d,c]},getLength:function(a,b){var c,d;this.geodesic?(c=a.getGeodesicLength(this.map.getProjectionObject()),
+d="m"):(c=a.getLength(),d=this.map.getUnits());var e=OpenLayers.INCHES_PER_UNIT[b];e&&(c*=OpenLayers.INCHES_PER_UNIT[d]/e);return c},CLASS_NAME:"OpenLayers.Control.Measure"});OpenLayers.Format.WMC.v1_0_0=OpenLayers.Class(OpenLayers.Format.WMC.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",initialize:function(a){OpenLayers.Format.WMC.v1.prototype.initialize.apply(this,[a])},read_wmc_SRS:function(a,b){var c=this.getChildValue(b);"object"!=typeof a.projections&&(a.projections={});for(var c=c.split(/ +/),d=0,e=c.length;d<e;d++)a.projections[c[d]]=!0},write_wmc_Layer:function(a){var b=OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(this,
+[a]);if(a.srs){var c=[],d;for(d in a.srs)c.push(d);b.appendChild(this.createElementDefaultNS("SRS",c.join(" ")))}b.appendChild(this.write_wmc_FormatList(a));b.appendChild(this.write_wmc_StyleList(a));a.dimensions&&b.appendChild(this.write_wmc_DimensionList(a));b.appendChild(this.write_wmc_LayerExtension(a))},CLASS_NAME:"OpenLayers.Format.WMC.v1_0_0"});OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,keepInMap:!0,anchor:null,initialize:function(a,b,c,d,e,f,g){OpenLayers.Popup.prototype.initialize.apply(this,[a,b,c,d,f,g]);this.anchor=null!=e?e:{size:new OpenLayers.Size(0,0),offset:new OpenLayers.Pixel(0,0)}},destroy:function(){this.relativePosition=this.anchor=null;OpenLayers.Popup.prototype.destroy.apply(this,arguments)},show:function(){this.updatePosition();OpenLayers.Popup.prototype.show.apply(this,arguments)},
+moveTo:function(a){var b=this.relativePosition;this.relativePosition=this.calculateRelativePosition(a);OpenLayers.Popup.prototype.moveTo.call(this,this.calculateNewPx(a));this.relativePosition!=b&&this.updateRelativePosition()},setSize:function(a){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if(this.lonlat&&this.map){var b=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(b)}},calculateRelativePosition:function(a){a=this.map.getLonLatFromLayerPx(a);a=this.map.getExtent().determineQuadrant(a);
+return OpenLayers.Bounds.oppositeQuadrant(a)},updateRelativePosition:function(){},calculateNewPx:function(a){a=a.offset(this.anchor.offset);var b=this.size||this.contentSize,c="t"==this.relativePosition.charAt(0);a.y+=c?-b.h:this.anchor.size.h;c="l"==this.relativePosition.charAt(1);a.x+=c?-b.w:this.anchor.size.w;return a},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Popup.Framed=OpenLayers.Class(OpenLayers.Popup.Anchored,{imageSrc:null,imageSize:null,isAlphaImage:!1,positionBlocks:null,blocks:null,fixedRelativePosition:!1,initialize:function(a,b,c,d,e,f,g){OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);this.fixedRelativePosition&&(this.updateRelativePosition(),this.calculateRelativePosition=function(a){return this.relativePosition});this.contentDiv.style.position="absolute";this.contentDiv.style.zIndex=1;f&&(this.closeDiv.style.zIndex=
+1);this.groupDiv.style.position="absolute";this.groupDiv.style.top="0px";this.groupDiv.style.left="0px";this.groupDiv.style.height="100%";this.groupDiv.style.width="100%"},destroy:function(){this.isAlphaImage=this.imageSize=this.imageSrc=null;this.fixedRelativePosition=!1;this.positionBlocks=null;for(var a=0;a<this.blocks.length;a++){var b=this.blocks[a];b.image&&b.div.removeChild(b.image);b.image=null;b.div&&this.groupDiv.removeChild(b.div);b.div=null}this.blocks=null;OpenLayers.Popup.Anchored.prototype.destroy.apply(this,
+arguments)},setBackgroundColor:function(a){},setBorder:function(){},setOpacity:function(a){},setSize:function(a){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.updateBlocks()},updateRelativePosition:function(){this.padding=this.positionBlocks[this.relativePosition].padding;if(this.closeDiv){var a=this.getContentDivPadding();this.closeDiv.style.right=a.right+this.padding.right+"px";this.closeDiv.style.top=a.top+this.padding.top+"px"}this.updateBlocks()},calculateNewPx:function(a){var b=
+OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(this,arguments);return b=b.offset(this.positionBlocks[this.relativePosition].offset)},createBlocks:function(){this.blocks=[];var a=null,b;for(b in this.positionBlocks){a=b;break}a=this.positionBlocks[a];for(b=0;b<a.blocks.length;b++){var c={};this.blocks.push(c);c.div=OpenLayers.Util.createDiv(this.id+"_FrameDecorationDiv_"+b,null,null,null,"absolute",null,"hidden",null);c.image=(this.isAlphaImage?OpenLayers.Util.createAlphaImageDiv:OpenLayers.Util.createImage)(this.id+
+"_FrameDecorationImg_"+b,null,this.imageSize,this.imageSrc,"absolute",null,null,null);c.div.appendChild(c.image);this.groupDiv.appendChild(c.div)}},updateBlocks:function(){this.blocks||this.createBlocks();if(this.size&&this.relativePosition){for(var a=this.positionBlocks[this.relativePosition],b=0;b<a.blocks.length;b++){var c=a.blocks[b],d=this.blocks[b],e=c.anchor.left,f=c.anchor.bottom,g=c.anchor.right,h=c.anchor.top,k=isNaN(c.size.w)?this.size.w-(g+e):c.size.w,l=isNaN(c.size.h)?this.size.h-(f+
+h):c.size.h;d.div.style.width=(0>k?0:k)+"px";d.div.style.height=(0>l?0:l)+"px";d.div.style.left=null!=e?e+"px":"";d.div.style.bottom=null!=f?f+"px":"";d.div.style.right=null!=g?g+"px":"";d.div.style.top=null!=h?h+"px":"";d.image.style.left=c.position.x+"px";d.image.style.top=c.position.y+"px"}this.contentDiv.style.left=this.padding.left+"px";this.contentDiv.style.top=this.padding.top+"px"}},CLASS_NAME:"OpenLayers.Popup.Framed"});OpenLayers.Popup.FramedCloud=OpenLayers.Class(OpenLayers.Popup.Framed,{contentDisplayClass:"olFramedCloudPopupContent",autoSize:!0,panMapIfOutOfView:!0,imageSize:new OpenLayers.Size(1276,736),isAlphaImage:!1,fixedRelativePosition:!1,positionBlocks:{tl:{offset:new OpenLayers.Pixel(44,0),padding:new OpenLayers.Bounds(8,40,8,9),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,
+50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,18),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-632)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(0,-688)}]},tr:{offset:new OpenLayers.Pixel(-45,0),padding:new OpenLayers.Bounds(8,40,8,9),blocks:[{size:new OpenLayers.Size("auto",
+"auto"),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,19),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-631)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(0,
+0,null,null),position:new OpenLayers.Pixel(-215,-687)}]},bl:{offset:new OpenLayers.Pixel(45,0),padding:new OpenLayers.Bounds(8,9,8,40),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,
+21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(null,null,0,0),position:new OpenLayers.Pixel(-101,-674)}]},br:{offset:new OpenLayers.Pixel(-44,0),padding:new OpenLayers.Bounds(8,9,8,40),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,
+0)},{size:new OpenLayers.Size("auto",21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(0,null,null,0),position:new OpenLayers.Pixel(-311,-674)}]}},minSize:new OpenLayers.Size(105,10),maxSize:new OpenLayers.Size(1200,660),initialize:function(a,b,c,d,e,f,g){this.imageSrc=OpenLayers.Util.getImageLocation("cloud-popup-relative.png");
+OpenLayers.Popup.Framed.prototype.initialize.apply(this,arguments);this.contentDiv.className=this.contentDisplayClass},CLASS_NAME:"OpenLayers.Popup.FramedCloud"});OpenLayers.Tile.Image.IFrame={useIFrame:null,blankImageUrl:"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7",draw:function(){if(OpenLayers.Tile.Image.prototype.shouldDraw.call(this)){var a=this.layer.getURL(this.bounds),b=this.useIFrame;this.useIFrame=null!==this.maxGetUrlLength&&!this.layer.async&&a.length>this.maxGetUrlLength;a=b&&!this.useIFrame;b=!b&&this.useIFrame;if(a||b)this.imgDiv&&this.imgDiv.parentNode===this.frame&&this.frame.removeChild(this.imgDiv),this.imgDiv=
+null,a&&this.frame.removeChild(this.frame.firstChild)}return OpenLayers.Tile.Image.prototype.draw.apply(this,arguments)},getImage:function(){if(!0===this.useIFrame){if(!this.frame.childNodes.length){var a=document.createElement("div"),b=a.style;b.position="absolute";b.width="100%";b.height="100%";b.zIndex=1;b.backgroundImage="url("+this.blankImageUrl+")";this.frame.appendChild(a)}a=this.id+"_iFrame";9>parseFloat(navigator.appVersion.split("MSIE")[1])?(b=document.createElement('<iframe name="'+a+'">'),
+b.style.backgroundColor="#FFFFFF",b.style.filter="chroma(color=#FFFFFF)"):(b=document.createElement("iframe"),b.style.backgroundColor="transparent",b.name=a);b.scrolling="no";b.marginWidth="0px";b.marginHeight="0px";b.frameBorder="0";b.style.position="absolute";b.style.width="100%";b.style.height="100%";1>this.layer.opacity&&OpenLayers.Util.modifyDOMElement(b,null,null,null,null,null,null,this.layer.opacity);this.frame.appendChild(b);return this.imgDiv=b}return OpenLayers.Tile.Image.prototype.getImage.apply(this,
+arguments)},createRequestForm:function(){var a=document.createElement("form");a.method="POST";var b=this.layer.params._OLSALT,b=(b?b+"_":"")+this.bounds.toBBOX();a.action=OpenLayers.Util.urlAppend(this.layer.url,b);a.target=this.id+"_iFrame";this.layer.getImageSize();var b=OpenLayers.Util.getParameters(this.url),c,d;for(d in b)c=document.createElement("input"),c.type="hidden",c.name=d,c.value=b[d],a.appendChild(c);return a},setImgSrc:function(a){if(!0===this.useIFrame)if(a){var b=this.createRequestForm();
+this.frame.appendChild(b);b.submit();this.frame.removeChild(b)}else this.imgDiv.parentNode===this.frame&&(this.frame.removeChild(this.imgDiv),this.imgDiv=null);else OpenLayers.Tile.Image.prototype.setImgSrc.apply(this,arguments)},onImageLoad:function(){OpenLayers.Tile.Image.prototype.onImageLoad.apply(this,arguments);!0===this.useIFrame&&(this.imgDiv.style.opacity=1,this.frame.style.opacity=this.layer.opacity)},createBackBuffer:function(){var a;!1===this.useIFrame&&(a=OpenLayers.Tile.Image.prototype.createBackBuffer.call(this));
+return a}};OpenLayers.Format.SOSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",CLASS_NAME:"OpenLayers.Format.SOSCapabilities"});OpenLayers.Format.SOSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.SOSCapabilities,{namespaces:{ows:"http://www.opengis.net/ows/1.1",sos:"http://www.opengis.net/sos/1.0",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);this.options=a},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,
+[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);return b},readers:{gml:OpenLayers.Util.applyDefaults({name:function(a,b){b.name=this.getChildValue(a)},TimePeriod:function(a,b){b.timePeriod={};this.readChildNodes(a,b.timePeriod)},beginPosition:function(a,b){b.beginPosition=this.getChildValue(a)},endPosition:function(a,b){b.endPosition=this.getChildValue(a)}},OpenLayers.Format.GML.v3.prototype.readers.gml),sos:{Capabilities:function(a,b){this.readChildNodes(a,b)},Contents:function(a,
+b){b.contents={};this.readChildNodes(a,b.contents)},ObservationOfferingList:function(a,b){b.offeringList={};this.readChildNodes(a,b.offeringList)},ObservationOffering:function(a,b){var c=this.getAttributeNS(a,this.namespaces.gml,"id");b[c]={procedures:[],observedProperties:[],featureOfInterestIds:[],responseFormats:[],resultModels:[],responseModes:[]};this.readChildNodes(a,b[c])},time:function(a,b){b.time={};this.readChildNodes(a,b.time)},procedure:function(a,b){b.procedures.push(this.getAttributeNS(a,
+this.namespaces.xlink,"href"))},observedProperty:function(a,b){b.observedProperties.push(this.getAttributeNS(a,this.namespaces.xlink,"href"))},featureOfInterest:function(a,b){b.featureOfInterestIds.push(this.getAttributeNS(a,this.namespaces.xlink,"href"))},responseFormat:function(a,b){b.responseFormats.push(this.getChildValue(a))},resultModel:function(a,b){b.resultModels.push(this.getChildValue(a))},responseMode:function(a,b){b.responseModes.push(this.getChildValue(a))}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},
+CLASS_NAME:"OpenLayers.Format.SOSCapabilities.v1_0_0"});OpenLayers.Handler.Pinch=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!1,pinching:!1,last:null,start:null,touchstart:function(a){var b=!0;this.pinching=!1;if(OpenLayers.Event.isMultiTouch(a))this.started=!0,this.last=this.start={distance:this.getDistance(a.touches),delta:0,scale:1},this.callback("start",[a,this.start]),b=!this.stopDown;else{if(this.started)return!1;this.started=!1;this.last=this.start=null}OpenLayers.Event.preventDefault(a);return b},touchmove:function(a){if(this.started&&
+OpenLayers.Event.isMultiTouch(a)){this.pinching=!0;var b=this.getPinchData(a);this.callback("move",[a,b]);this.last=b;OpenLayers.Event.stop(a)}else if(this.started)return!1;return!0},touchend:function(a){return this.started&&!OpenLayers.Event.isMultiTouch(a)?(this.pinching=this.started=!1,this.callback("done",[a,this.start,this.last]),this.last=this.start=null,!1):!0},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.pinching=!1,a=!0);return a},deactivate:function(){var a=
+!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.pinching=this.started=!1,this.last=this.start=null,a=!0);return a},getDistance:function(a){var b=a[0];a=a[1];return Math.sqrt(Math.pow(b.olClientX-a.olClientX,2)+Math.pow(b.olClientY-a.olClientY,2))},getPinchData:function(a){a=this.getDistance(a.touches);return{distance:a,delta:this.last.distance-a,scale:a/this.start.distance}},CLASS_NAME:"OpenLayers.Handler.Pinch"});OpenLayers.Control.NavToolbar=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(a){OpenLayers.Control.Panel.prototype.initialize.apply(this,[a]);this.addControls([new OpenLayers.Control.Navigation,new OpenLayers.Control.ZoomBox])},draw:function(){var a=OpenLayers.Control.Panel.prototype.draw.apply(this,arguments);null===this.defaultControl&&(this.defaultControl=this.controls[0]);return a},CLASS_NAME:"OpenLayers.Control.NavToolbar"});OpenLayers.Strategy.Refresh=OpenLayers.Class(OpenLayers.Strategy,{force:!1,interval:0,timer:null,activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);a&&(!0===this.layer.visibility&&this.start(),this.layer.events.on({visibilitychanged:this.reset,scope:this}));return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&(this.stop(),this.layer.events.un({visibilitychanged:this.reset,scope:this}));return a},reset:function(){!0===this.layer.visibility?
+this.start():this.stop()},start:function(){this.interval&&("number"===typeof this.interval&&0<this.interval)&&(this.timer=window.setInterval(OpenLayers.Function.bind(this.refresh,this),this.interval))},refresh:function(){this.layer&&(this.layer.refresh&&"function"==typeof this.layer.refresh)&&this.layer.refresh({force:this.force})},stop:function(){null!==this.timer&&(window.clearInterval(this.timer),this.timer=null)},CLASS_NAME:"OpenLayers.Strategy.Refresh"});OpenLayers.Layer.ArcGIS93Rest=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{format:"png"},isBaseLayer:!0,initialize:function(a,b,c,d){var e=[];c=OpenLayers.Util.upperCaseObject(c);e.push(a,b,c,d);OpenLayers.Layer.Grid.prototype.initialize.apply(this,e);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));this.params.TRANSPARENT&&"true"==this.params.TRANSPARENT.toString().toLowerCase()&&(null!=d&&d.isBaseLayer||(this.isBaseLayer=!1),"jpg"==this.params.FORMAT&&
+(this.params.FORMAT=OpenLayers.Util.alphaHack()?"gif":"png"))},clone:function(a){null==a&&(a=new OpenLayers.Layer.ArcGIS93Rest(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds(a);var b=this.projection.getCode().split(":"),b=b[b.length-1],c=this.getImageSize();a={BBOX:a.toBBOX(),SIZE:c.w+","+c.h,F:"image",BBOXSR:b,IMAGESR:b};if(this.layerDefs){var b=[],d;for(d in this.layerDefs)this.layerDefs.hasOwnProperty(d)&&
+this.layerDefs[d]&&(b.push(d),b.push(":"),b.push(this.layerDefs[d]),b.push(";"));0<b.length&&(a.LAYERDEFS=b.join(""))}return this.getFullRequestString(a)},setLayerFilter:function(a,b){this.layerDefs||(this.layerDefs={});b?this.layerDefs[a]=b:delete this.layerDefs[a]},clearLayerFilter:function(a){a?delete this.layerDefs[a]:delete this.layerDefs},mergeNewParams:function(a){a=[OpenLayers.Util.upperCaseObject(a)];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,a)},CLASS_NAME:"OpenLayers.Layer.ArcGIS93Rest"});OpenLayers.Handler.Hover=OpenLayers.Class(OpenLayers.Handler,{delay:500,pixelTolerance:null,stopMove:!1,px:null,timerId:null,mousemove:function(a){this.passesTolerance(a.xy)&&(this.clearTimer(),this.callback("move",[a]),this.px=a.xy,a=OpenLayers.Util.extend({},a),this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,a),this.delay));return!this.stopMove},mouseout:function(a){OpenLayers.Util.mouseLeft(a,this.map.viewPortDiv)&&(this.clearTimer(),this.callback("move",[a]));return!0},
+passesTolerance:function(a){var b=!0;this.pixelTolerance&&this.px&&Math.sqrt(Math.pow(this.px.x-a.x,2)+Math.pow(this.px.y-a.y,2))<this.pixelTolerance&&(b=!1);return b},clearTimer:function(){null!=this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null)},delayedCall:function(a){this.callback("pause",[a])},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.clearTimer(),a=!0);return a},CLASS_NAME:"OpenLayers.Handler.Hover"});OpenLayers.Control.GetFeature=OpenLayers.Class(OpenLayers.Control,{protocol:null,multipleKey:null,toggleKey:null,modifiers:null,multiple:!1,click:!0,single:!0,clickout:!0,toggle:!1,clickTolerance:5,hover:!1,box:!1,maxFeatures:10,features:null,hoverFeature:null,handlers:null,hoverResponse:null,filterType:OpenLayers.Filter.Spatial.BBOX,initialize:function(a){a.handlerOptions=a.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[a]);this.features={};this.handlers={};this.click&&(this.handlers.click=
+new OpenLayers.Handler.Click(this,{click:this.selectClick},this.handlerOptions.click||{}));this.box&&(this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},OpenLayers.Util.extend(this.handlerOptions.box,{boxDivClassName:"olHandlerBoxSelectFeature"})));this.hover&&(this.handlers.hover=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.selectHover},OpenLayers.Util.extend(this.handlerOptions.hover,{delay:250,pixelTolerance:2})))},activate:function(){if(!this.active)for(var a in this.handlers)this.handlers[a].activate();
+return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){if(this.active)for(var a in this.handlers)this.handlers[a].deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},selectClick:function(a){var b=this.pixelToBounds(a.xy);this.setModifiers(a);this.request(b,{single:this.single})},selectBox:function(a){var b;if(a instanceof OpenLayers.Bounds)b=this.map.getLonLatFromPixel({x:a.left,y:a.bottom}),a=this.map.getLonLatFromPixel({x:a.right,
+y:a.top}),b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat);else{if(this.click)return;b=this.pixelToBounds(a)}this.setModifiers(this.handlers.box.dragHandler.evt);this.request(b)},selectHover:function(a){a=this.pixelToBounds(a.xy);this.request(a,{single:!0,hover:!0})},cancelHover:function(){this.hoverResponse&&(this.protocol.abort(this.hoverResponse),this.hoverResponse=null,OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait"))},request:function(a,b){b=b||{};var c=new OpenLayers.Filter.Spatial({type:this.filterType,
+value:a});OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");c=this.protocol.read({maxFeatures:!0==b.single?this.maxFeatures:void 0,filter:c,callback:function(c){c.success()&&(c.features.length?!0==b.single?this.selectBestFeature(c.features,a.getCenterLonLat(),b):this.select(c.features):b.hover?this.hoverSelect():(this.events.triggerEvent("clickout"),this.clickout&&this.unselectAll()));OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait")},scope:this});!0==b.hover&&(this.hoverResponse=
+c)},selectBestFeature:function(a,b,c){c=c||{};if(a.length){b=new OpenLayers.Geometry.Point(b.lon,b.lat);for(var d,e,f,g=Number.MAX_VALUE,h=0;h<a.length&&!(d=a[h],d.geometry&&(f=b.distanceTo(d.geometry,{edge:!1}),f<g&&(g=f,e=d,0==g)));++h);!0==c.hover?this.hoverSelect(e):this.select(e||a)}},setModifiers:function(a){this.modifiers={multiple:this.multiple||this.multipleKey&&a[this.multipleKey],toggle:this.toggle||this.toggleKey&&a[this.toggleKey]}},select:function(a){this.modifiers.multiple||this.modifiers.toggle||
+this.unselectAll();OpenLayers.Util.isArray(a)||(a=[a]);var b=this.events.triggerEvent("beforefeaturesselected",{features:a});if(!1!==b){for(var c=[],d,e=0,f=a.length;e<f;++e)d=a[e],this.features[d.fid||d.id]?this.modifiers.toggle&&this.unselect(this.features[d.fid||d.id]):(b=this.events.triggerEvent("beforefeatureselected",{feature:d}),!1!==b&&(this.features[d.fid||d.id]=d,c.push(d),this.events.triggerEvent("featureselected",{feature:d})));this.events.triggerEvent("featuresselected",{features:c})}},
+hoverSelect:function(a){var b=a?a.fid||a.id:null,c=this.hoverFeature?this.hoverFeature.fid||this.hoverFeature.id:null;c&&c!=b&&(this.events.triggerEvent("outfeature",{feature:this.hoverFeature}),this.hoverFeature=null);b&&b!=c&&(this.events.triggerEvent("hoverfeature",{feature:a}),this.hoverFeature=a)},unselect:function(a){delete this.features[a.fid||a.id];this.events.triggerEvent("featureunselected",{feature:a})},unselectAll:function(){for(var a in this.features)this.unselect(this.features[a])},
+setMap:function(a){for(var b in this.handlers)this.handlers[b].setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},pixelToBounds:function(a){var b=a.add(-this.clickTolerance/2,this.clickTolerance/2);a=a.add(this.clickTolerance/2,-this.clickTolerance/2);b=this.map.getLonLatFromPixel(b);a=this.map.getLonLatFromPixel(a);return new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat)},CLASS_NAME:"OpenLayers.Control.GetFeature"});OpenLayers.Format.QueryStringFilter=function(){function a(a){a=a.replace(/%/g,"\\%");a=a.replace(/\\\\\.(\*)?/g,function(a,b){return b?a:"\\\\_"});a=a.replace(/\\\\\.\*/g,"\\\\%");a=a.replace(/(\\)?\.(\*)?/g,function(a,b,c){return b||c?a:"_"});a=a.replace(/(\\)?\.\*/g,function(a,b){return b?a:"%"});a=a.replace(/\\\./g,".");return a=a.replace(/(\\)?\\\*/g,function(a,b){return b?a:"*"})}var b={};b[OpenLayers.Filter.Comparison.EQUAL_TO]="eq";b[OpenLayers.Filter.Comparison.NOT_EQUAL_TO]="ne";b[OpenLayers.Filter.Comparison.LESS_THAN]=
+"lt";b[OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO]="lte";b[OpenLayers.Filter.Comparison.GREATER_THAN]="gt";b[OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO]="gte";b[OpenLayers.Filter.Comparison.LIKE]="ilike";return OpenLayers.Class(OpenLayers.Format,{wildcarded:!1,srsInBBOX:!1,write:function(c,d){d=d||{};var e=c.CLASS_NAME,e=e.substring(e.lastIndexOf(".")+1);switch(e){case "Spatial":switch(c.type){case OpenLayers.Filter.Spatial.BBOX:d.bbox=c.value.toArray();this.srsInBBOX&&c.projection&&
+d.bbox.push(c.projection.getCode());break;case OpenLayers.Filter.Spatial.DWITHIN:d.tolerance=c.distance;case OpenLayers.Filter.Spatial.WITHIN:d.lon=c.value.x;d.lat=c.value.y;break;default:OpenLayers.Console.warn("Unknown spatial filter type "+c.type)}break;case "Comparison":e=b[c.type];if(void 0!==e){var f=c.value;c.type==OpenLayers.Filter.Comparison.LIKE&&(f=a(f),this.wildcarded&&(f="%"+f+"%"));d[c.property+"__"+e]=f;d.queryable=d.queryable||[];d.queryable.push(c.property)}else OpenLayers.Console.warn("Unknown comparison filter type "+
+c.type);break;case "Logical":if(c.type===OpenLayers.Filter.Logical.AND)for(e=0,f=c.filters.length;e<f;e++)d=this.write(c.filters[e],d);else OpenLayers.Console.warn("Unsupported logical filter type "+c.type);break;default:OpenLayers.Console.warn("Unknown filter type "+e)}return d},CLASS_NAME:"OpenLayers.Format.QueryStringFilter"})}();OpenLayers.Control.MousePosition=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,element:null,prefix:"",separator:", ",suffix:"",numDigits:5,granularity:10,emptyString:null,lastXy:null,displayProjection:null,destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){return OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.map.events.register("mousemove",this,this.redraw),this.map.events.register("mouseout",this,this.reset),
+this.redraw(),!0):!1},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments)?(this.map.events.unregister("mousemove",this,this.redraw),this.map.events.unregister("mouseout",this,this.reset),this.element.innerHTML="",!0):!1},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.element||(this.div.left="",this.div.top="",this.element=this.div);return this.div},redraw:function(a){var b;if(null==a)this.reset();else if(null==this.lastXy||Math.abs(a.xy.x-
+this.lastXy.x)>this.granularity||Math.abs(a.xy.y-this.lastXy.y)>this.granularity)this.lastXy=a.xy;else if(b=this.map.getLonLatFromPixel(a.xy))this.displayProjection&&b.transform(this.map.getProjectionObject(),this.displayProjection),this.lastXy=a.xy,a=this.formatOutput(b),a!=this.element.innerHTML&&(this.element.innerHTML=a)},reset:function(a){null!=this.emptyString&&(this.element.innerHTML=this.emptyString)},formatOutput:function(a){var b=parseInt(this.numDigits);return this.prefix+a.lon.toFixed(b)+
+this.separator+a.lat.toFixed(b)+this.suffix},CLASS_NAME:"OpenLayers.Control.MousePosition"});OpenLayers.Control.Geolocate=OpenLayers.Class(OpenLayers.Control,{geolocation:null,available:"geolocation"in navigator,bind:!0,watch:!1,geolocationOptions:null,destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){this.available&&!this.geolocation&&(this.geolocation=navigator.geolocation);return this.geolocation?OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.watch?this.watchId=this.geolocation.watchPosition(OpenLayers.Function.bind(this.geolocate,
+this),OpenLayers.Function.bind(this.failure,this),this.geolocationOptions):this.getCurrentLocation(),!0):!1:(this.events.triggerEvent("locationuncapable"),!1)},deactivate:function(){this.active&&null!==this.watchId&&this.geolocation.clearWatch(this.watchId);return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},geolocate:function(a){var b=(new OpenLayers.LonLat(a.coords.longitude,a.coords.latitude)).transform(new OpenLayers.Projection("EPSG:4326"),this.map.getProjectionObject());this.bind&&
+this.map.setCenter(b);this.events.triggerEvent("locationupdated",{position:a,point:new OpenLayers.Geometry.Point(b.lon,b.lat)})},getCurrentLocation:function(){if(!this.active||this.watch)return!1;this.geolocation.getCurrentPosition(OpenLayers.Function.bind(this.geolocate,this),OpenLayers.Function.bind(this.failure,this),this.geolocationOptions);return!0},failure:function(a){this.events.triggerEvent("locationfailed",{error:a})},CLASS_NAME:"OpenLayers.Control.Geolocate"});OpenLayers.Tile.UTFGrid=OpenLayers.Class(OpenLayers.Tile,{url:null,utfgridResolution:2,json:null,format:null,destroy:function(){this.clear();OpenLayers.Tile.prototype.destroy.apply(this,arguments)},draw:function(){var a=OpenLayers.Tile.prototype.draw.apply(this,arguments);if(a)if(this.isLoading?(this.abortLoading(),this.events.triggerEvent("reload")):(this.isLoading=!0,this.events.triggerEvent("loadstart")),this.url=this.layer.getURL(this.bounds),this.layer.useJSONP){var b=new OpenLayers.Protocol.Script({url:this.url,
+callback:function(a){this.isLoading=!1;this.events.triggerEvent("loadend");this.json=a.data},scope:this});b.read();this.request=b}else this.request=OpenLayers.Request.GET({url:this.url,callback:function(a){this.isLoading=!1;this.events.triggerEvent("loadend");200===a.status&&this.parseData(a.responseText)},scope:this});else this.unload();return a},abortLoading:function(){this.request&&(this.request.abort(),delete this.request);this.isLoading=!1},getFeatureInfo:function(a,b){var c=null;if(this.json){var d=
+this.getFeatureId(a,b);null!==d&&(c={id:d,data:this.json.data[d]})}return c},getFeatureId:function(a,b){var c=null;if(this.json){var d=this.utfgridResolution,d=this.json.grid[Math.floor(b/d)].charCodeAt(Math.floor(a/d)),d=this.indexFromCharCode(d),e=this.json.keys;!isNaN(d)&&d in e&&(c=e[d])}return c},indexFromCharCode:function(a){93<=a&&a--;35<=a&&a--;return a-32},parseData:function(a){this.format||(this.format=new OpenLayers.Format.JSON);this.json=this.format.read(a)},clear:function(){this.json=
+null},CLASS_NAME:"OpenLayers.Tile.UTFGrid"});OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:!1,updateWithPOST:!1,deleteWithPOST:!1,wildcarded:!1,srsInBBOX:!1,initialize:function(a){a=a||{};this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);if(!this.filterToParams&&OpenLayers.Format.QueryStringFilter){var b=new OpenLayers.Format.QueryStringFilter({wildcarded:this.wildcarded,srsInBBOX:this.srsInBBOX});this.filterToParams=
+function(a,d){return b.write(a,d)}}},destroy:function(){this.headers=this.params=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=a||{};a.params=OpenLayers.Util.applyDefaults(a.params,this.options.params);a=OpenLayers.Util.applyDefaults(a,this.options);a.filter&&this.filterToParams&&(a.params=this.filterToParams(a.filter,a.params));var b=void 0!==a.readWithPOST?a.readWithPOST:this.readWithPOST,c=new OpenLayers.Protocol.Response({requestType:"read"});
+b?(b=a.headers||{},b["Content-Type"]="application/x-www-form-urlencoded",c.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,c,a),data:OpenLayers.Util.getParameterString(a.params),headers:b})):c.priv=OpenLayers.Request.GET({url:a.url,callback:this.createCallback(this.handleRead,c,a),params:a.params,headers:a.headers});return c},handleRead:function(a,b){this.handleResponse(a,b)},create:function(a,b){b=OpenLayers.Util.applyDefaults(b,this.options);var c=new OpenLayers.Protocol.Response({reqFeatures:a,
+requestType:"create"});c.priv=OpenLayers.Request.POST({url:b.url,callback:this.createCallback(this.handleCreate,c,b),headers:b.headers,data:this.format.write(a)});return c},handleCreate:function(a,b){this.handleResponse(a,b)},update:function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"update"});d.priv=OpenLayers.Request[this.updateWithPOST?"POST":"PUT"]({url:c,callback:this.createCallback(this.handleUpdate,
+d,b),headers:b.headers,data:this.format.write(a)});return d},handleUpdate:function(a,b){this.handleResponse(a,b)},"delete":function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"delete"}),e=this.deleteWithPOST?"POST":"DELETE",c={url:c,callback:this.createCallback(this.handleDelete,d,b),headers:b.headers};this.deleteWithPOST&&(c.data=this.format.write(a));d.priv=OpenLayers.Request[e](c);
+return d},handleDelete:function(a,b){this.handleResponse(a,b)},handleResponse:function(a,b){var c=a.priv;b.callback&&(200<=c.status&&300>c.status?("delete"!=a.requestType&&(a.features=this.parseFeatures(c)),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE,b.callback.call(b.scope,a))},parseFeatures:function(a){var b=a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},commit:function(a,b){function c(a){for(var b=
+a.features?a.features.length:0,c=Array(b),e=0;e<b;++e)c[e]=a.features[e].fid;r.insertIds=c;d.apply(this,[a])}function d(a){this.callUserCallback(a,b);q=q&&a.success();f++;f>=p&&b.callback&&(r.code=q?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE,b.callback.apply(b.scope,[r]))}b=OpenLayers.Util.applyDefaults(b,this.options);var e=[],f=0,g={};g[OpenLayers.State.INSERT]=[];g[OpenLayers.State.UPDATE]=[];g[OpenLayers.State.DELETE]=[];for(var h,k,l=[],m=0,n=a.length;m<n;++m)if(h=
+a[m],k=g[h.state])k.push(h),l.push(h);var p=(0<g[OpenLayers.State.INSERT].length?1:0)+g[OpenLayers.State.UPDATE].length+g[OpenLayers.State.DELETE].length,q=!0,r=new OpenLayers.Protocol.Response({reqFeatures:l});h=g[OpenLayers.State.INSERT];0<h.length&&e.push(this.create(h,OpenLayers.Util.applyDefaults({callback:c,scope:this},b.create)));h=g[OpenLayers.State.UPDATE];for(m=h.length-1;0<=m;--m)e.push(this.update(h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b.update)));h=g[OpenLayers.State.DELETE];
+for(m=h.length-1;0<=m;--m)e.push(this["delete"](h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b["delete"])));return e},abort:function(a){a&&a.priv.abort()},callUserCallback:function(a,b){var c=b[a.requestType];c&&c.callback&&c.callback.call(c.scope,a)},CLASS_NAME:"OpenLayers.Protocol.HTTP"});OpenLayers.Strategy.Cluster=OpenLayers.Class(OpenLayers.Strategy,{distance:20,threshold:null,features:null,clusters:null,clustering:!1,resolution:null,activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);if(a)this.layer.events.on({beforefeaturesadded:this.cacheFeatures,featuresremoved:this.clearCache,moveend:this.cluster,scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&(this.clearCache(),this.layer.events.un({beforefeaturesadded:this.cacheFeatures,
+featuresremoved:this.clearCache,moveend:this.cluster,scope:this}));return a},cacheFeatures:function(a){var b=!0;this.clustering||(this.clearCache(),this.features=a.features,this.cluster(),b=!1);return b},clearCache:function(){this.clustering||(this.features=null)},cluster:function(a){if((!a||a.zoomChanged)&&this.features&&(a=this.layer.map.getResolution(),a!=this.resolution||!this.clustersExist())){this.resolution=a;a=[];for(var b,c,d,e=0;e<this.features.length;++e)if(b=this.features[e],b.geometry){c=
+!1;for(var f=a.length-1;0<=f;--f)if(d=a[f],this.shouldCluster(d,b)){this.addToCluster(d,b);c=!0;break}c||a.push(this.createCluster(this.features[e]))}this.clustering=!0;this.layer.removeAllFeatures();this.clustering=!1;if(0<a.length){if(1<this.threshold)for(b=a.slice(),a=[],e=0,d=b.length;e<d;++e)c=b[e],c.attributes.count<this.threshold?Array.prototype.push.apply(a,c.cluster):a.push(c);this.clustering=!0;this.layer.addFeatures(a);this.clustering=!1}this.clusters=a}},clustersExist:function(){var a=
+!1;if(this.clusters&&0<this.clusters.length&&this.clusters.length==this.layer.features.length)for(var a=!0,b=0;b<this.clusters.length;++b)if(this.clusters[b]!=this.layer.features[b]){a=!1;break}return a},shouldCluster:function(a,b){var c=a.geometry.getBounds().getCenterLonLat(),d=b.geometry.getBounds().getCenterLonLat();return Math.sqrt(Math.pow(c.lon-d.lon,2)+Math.pow(c.lat-d.lat,2))/this.resolution<=this.distance},addToCluster:function(a,b){a.cluster.push(b);a.attributes.count+=1},createCluster:function(a){var b=
+a.geometry.getBounds().getCenterLonLat(),b=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(b.lon,b.lat),{count:1});b.cluster=[a];return b},CLASS_NAME:"OpenLayers.Strategy.Cluster"});OpenLayers.Strategy.Filter=OpenLayers.Class(OpenLayers.Strategy,{filter:null,cache:null,caching:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.apply(this,arguments);a&&(this.cache=[],this.layer.events.on({beforefeaturesadded:this.handleAdd,beforefeaturesremoved:this.handleRemove,scope:this}));return a},deactivate:function(){this.cache=null;this.layer&&this.layer.events&&this.layer.events.un({beforefeaturesadded:this.handleAdd,beforefeaturesremoved:this.handleRemove,scope:this});
+return OpenLayers.Strategy.prototype.deactivate.apply(this,arguments)},handleAdd:function(a){if(!this.caching&&this.filter){var b=a.features;a.features=[];for(var c,d=0,e=b.length;d<e;++d)c=b[d],this.filter.evaluate(c)?a.features.push(c):this.cache.push(c)}},handleRemove:function(a){this.caching||(this.cache=[])},setFilter:function(a){this.filter=a;a=this.cache;this.cache=[];this.handleAdd({features:this.layer.features});0<this.cache.length&&(this.caching=!0,this.layer.removeFeatures(this.cache.slice()),
+this.caching=!1);0<a.length&&(a={features:a},this.handleAdd(a),0<a.features.length&&(this.caching=!0,this.layer.addFeatures(a.features),this.caching=!1))},CLASS_NAME:"OpenLayers.Strategy.Filter"});OpenLayers.Protocol.SOS=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Protocol.SOS.DEFAULTS);var b=OpenLayers.Protocol.SOS["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported SOS version: "+a.version;return new b(a)};OpenLayers.Protocol.SOS.DEFAULTS={version:"1.0.0"};OpenLayers.Format.WFSDescribeFeatureType=OpenLayers.Class(OpenLayers.Format.XML,{regExes:{trimSpace:/^\s*|\s*$/g},namespaces:{xsd:"http://www.w3.org/2001/XMLSchema"},readers:{xsd:{schema:function(a,b){var c=[],d={},e,f;this.readChildNodes(a,{complexTypes:c,customTypes:d});var g=a.attributes,h,k;e=0;for(f=g.length;e<f;++e)h=g[e],k=h.name,0===k.indexOf("xmlns")?this.setNamespace(k.split(":")[1]||"",h.value):b[k]=h.value;b.featureTypes=c;b.targetPrefix=this.namespaceAlias[b.targetNamespace];e=0;for(f=
+c.length;e<f;++e)g=c[e],h=d[g.typeName],d[g.typeName]&&(g.typeName=h.name)},complexType:function(a,b){var c={typeName:a.getAttribute("name")};this.readChildNodes(a,c);b.complexTypes.push(c)},complexContent:function(a,b){this.readChildNodes(a,b)},extension:function(a,b){this.readChildNodes(a,b)},sequence:function(a,b){var c={elements:[]};this.readChildNodes(a,c);b.properties=c.elements},element:function(a,b){var c;if(b.elements){var d={};c=a.attributes;for(var e,f=0,g=c.length;f<g;++f)e=c[f],d[e.name]=
+e.value;c=d.type;c||(c={},this.readChildNodes(a,c),d.restriction=c,d.type=c.base);d.localType=(c.base||c).split(":").pop();b.elements.push(d);this.readChildNodes(a,d)}b.complexTypes&&(c=a.getAttribute("type"),d=c.split(":").pop(),b.customTypes[d]={name:a.getAttribute("name"),type:c})},annotation:function(a,b){b.annotation={};this.readChildNodes(a,b.annotation)},appinfo:function(a,b){b.appinfo||(b.appinfo=[]);b.appinfo.push(this.getChildValue(a))},documentation:function(a,b){b.documentation||(b.documentation=
+[]);var c=this.getChildValue(a);b.documentation.push({lang:a.getAttribute("xml:lang"),textContent:c.replace(this.regExes.trimSpace,"")})},simpleType:function(a,b){this.readChildNodes(a,b)},restriction:function(a,b){b.base=a.getAttribute("base");this.readRestriction(a,b)}}},readRestriction:function(a,b){for(var c=a.childNodes,d,e,f=0,g=c.length;f<g;++f)d=c[f],1==d.nodeType&&(e=d.nodeName.split(":").pop(),d=d.getAttribute("value"),b[e]?("string"==typeof b[e]&&(b[e]=[b[e]]),b[e].push(d)):b[e]=d)},read:function(a){"string"==
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};if("ExceptionReport"===a.nodeName.split(":").pop()){var c=new OpenLayers.Format.OGCExceptionReport;b.error=c.read(a)}else this.readNode(a,b);return b},CLASS_NAME:"OpenLayers.Format.WFSDescribeFeatureType"});OpenLayers.Format.GeoRSS=OpenLayers.Class(OpenLayers.Format.XML,{rssns:"http://backend.userland.com/rss2",featureNS:"http://mapserver.gis.umn.edu/mapserver",georssns:"http://www.georss.org/georss",geons:"http://www.w3.org/2003/01/geo/wgs84_pos#",featureTitle:"Untitled",featureDescription:"No Description",gmlParser:null,xy:!1,createGeometryFromItem:function(a){var b=this.getElementsByTagNameNS(a,this.georssns,"point"),c=this.getElementsByTagNameNS(a,this.geons,"lat"),d=this.getElementsByTagNameNS(a,
+this.geons,"long"),e=this.getElementsByTagNameNS(a,this.georssns,"line"),f=this.getElementsByTagNameNS(a,this.georssns,"polygon"),g=this.getElementsByTagNameNS(a,this.georssns,"where");a=this.getElementsByTagNameNS(a,this.georssns,"box");if(0<b.length||0<c.length&&0<d.length){0<b.length?(c=OpenLayers.String.trim(b[0].firstChild.nodeValue).split(/\s+/),2!=c.length&&(c=OpenLayers.String.trim(b[0].firstChild.nodeValue).split(/\s*,\s*/))):c=[parseFloat(c[0].firstChild.nodeValue),parseFloat(d[0].firstChild.nodeValue)];
+var h=new OpenLayers.Geometry.Point(c[1],c[0])}else if(0<e.length){c=OpenLayers.String.trim(this.getChildValue(e[0])).split(/\s+/);d=[];e=0;for(f=c.length;e<f;e+=2)b=new OpenLayers.Geometry.Point(c[e+1],c[e]),d.push(b);h=new OpenLayers.Geometry.LineString(d)}else if(0<f.length){c=OpenLayers.String.trim(this.getChildValue(f[0])).split(/\s+/);d=[];e=0;for(f=c.length;e<f;e+=2)b=new OpenLayers.Geometry.Point(c[e+1],c[e]),d.push(b);h=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(d)])}else 0<
+g.length?(this.gmlParser||(this.gmlParser=new OpenLayers.Format.GML({xy:this.xy})),h=this.gmlParser.parseFeature(g[0]).geometry):0<a.length&&(c=OpenLayers.String.trim(a[0].firstChild.nodeValue).split(/\s+/),d=[],3<c.length&&(b=new OpenLayers.Geometry.Point(c[1],c[0]),d.push(b),b=new OpenLayers.Geometry.Point(c[1],c[2]),d.push(b),b=new OpenLayers.Geometry.Point(c[3],c[2]),d.push(b),b=new OpenLayers.Geometry.Point(c[3],c[0]),d.push(b),b=new OpenLayers.Geometry.Point(c[1],c[0]),d.push(b)),h=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(d)]));
+h&&(this.internalProjection&&this.externalProjection)&&h.transform(this.externalProjection,this.internalProjection);return h},createFeatureFromItem:function(a){var b=this.createGeometryFromItem(a),c=this._getChildValue(a,"*","title",this.featureTitle),d=this._getChildValue(a,"*","description",this._getChildValue(a,"*","content",this._getChildValue(a,"*","summary",this.featureDescription))),e=this._getChildValue(a,"*","link");if(!e)try{e=this.getElementsByTagNameNS(a,"*","link")[0].getAttribute("href")}catch(f){e=
+null}a=this._getChildValue(a,"*","id",null);b=new OpenLayers.Feature.Vector(b,{title:c,description:d,link:e});b.fid=a;return b},_getChildValue:function(a,b,c,d){return(a=this.getElementsByTagNameNS(a,b,c))&&a[0]&&a[0].firstChild&&a[0].firstChild.nodeValue?this.getChildValue(a[0]):void 0==d?"":d},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=null,b=this.getElementsByTagNameNS(a,"*","item");0==b.length&&(b=this.getElementsByTagNameNS(a,"*","entry"));
+a=b.length;for(var c=Array(a),d=0;d<a;d++)c[d]=this.createFeatureFromItem(b[d]);return c},write:function(a){var b;if(OpenLayers.Util.isArray(a)){b=this.createElementNS(this.rssns,"rss");for(var c=0,d=a.length;c<d;c++)b.appendChild(this.createFeatureXML(a[c]))}else b=this.createFeatureXML(a);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.rssns,"item"),d=this.createElementNS(this.rssns,"title");
+d.appendChild(this.createTextNode(a.attributes.title?a.attributes.title:""));var e=this.createElementNS(this.rssns,"description");e.appendChild(this.createTextNode(a.attributes.description?a.attributes.description:""));c.appendChild(d);c.appendChild(e);a.attributes.link&&(d=this.createElementNS(this.rssns,"link"),d.appendChild(this.createTextNode(a.attributes.link)),c.appendChild(d));for(var f in a.attributes)"link"!=f&&("title"!=f&&"description"!=f)&&(d=this.createTextNode(a.attributes[f]),e=f,-1!=
+f.search(":")&&(e=f.split(":")[1]),e=this.createElementNS(this.featureNS,"feature:"+e),e.appendChild(d),c.appendChild(e));c.appendChild(b);return c},buildGeometryNode:function(a){this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b;if("OpenLayers.Geometry.Polygon"==a.CLASS_NAME)b=this.createElementNS(this.georssns,"georss:polygon"),b.appendChild(this.buildCoordinatesNode(a.components[0]));else if("OpenLayers.Geometry.LineString"==
+a.CLASS_NAME)b=this.createElementNS(this.georssns,"georss:line"),b.appendChild(this.buildCoordinatesNode(a));else if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.createElementNS(this.georssns,"georss:point"),b.appendChild(this.buildCoordinatesNode(a));else throw"Couldn't parse "+a.CLASS_NAME;return b},buildCoordinatesNode:function(a){var b=null;a.components&&(b=a.components);if(b){a=b.length;for(var c=Array(a),d=0;d<a;d++)c[d]=b[d].y+" "+b[d].x;b=c.join(" ")}else b=a.y+" "+a.x;return this.createTextNode(b)},
+CLASS_NAME:"OpenLayers.Format.GeoRSS"});OpenLayers.Format.WPSCapabilities=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",CLASS_NAME:"OpenLayers.Format.WPSCapabilities"});OpenLayers.Format.WPSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ows:"http://www.opengis.net/ows/1.1",wps:"http://www.opengis.net/wps/1.0.0",xlink:"http://www.w3.org/1999/xlink"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);
+var b={};this.readNode(a,b);return b},readers:{wps:{Capabilities:function(a,b){this.readChildNodes(a,b)},ProcessOfferings:function(a,b){b.processOfferings={};this.readChildNodes(a,b.processOfferings)},Process:function(a,b){var c={processVersion:this.getAttributeNS(a,this.namespaces.wps,"processVersion")};this.readChildNodes(a,c);b[c.identifier]=c},Languages:function(a,b){b.languages=[];this.readChildNodes(a,b.languages)},Default:function(a,b){var c={isDefault:!0};this.readChildNodes(a,c);b.push(c)},
+Supported:function(a,b){var c={};this.readChildNodes(a,c);b.push(c)}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WPSCapabilities.v1_0_0"});OpenLayers.Control.PinchZoom=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,pinchOrigin:null,currentCenter:null,autoActivate:!0,preserveCenter:!1,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.handler=new OpenLayers.Handler.Pinch(this,{start:this.pinchStart,move:this.pinchMove,done:this.pinchDone},this.handlerOptions)},pinchStart:function(a,b){var c=this.preserveCenter?this.map.getPixelFromLonLat(this.map.getCenter()):a.xy;this.currentCenter=
+this.pinchOrigin=c},pinchMove:function(a,b){var c=b.scale,d=this.map.layerContainerOriginPx,e=this.pinchOrigin,f=this.preserveCenter?this.map.getPixelFromLonLat(this.map.getCenter()):a.xy,g=Math.round(d.x+f.x-e.x+(c-1)*(d.x-e.x)),d=Math.round(d.y+f.y-e.y+(c-1)*(d.y-e.y));this.map.applyTransform(g,d,c);this.currentCenter=f},pinchDone:function(a,b,c){this.map.applyTransform();a=this.map.getZoomForResolution(this.map.getResolution()/c.scale,!0);if(a!==this.map.getZoom()||!this.currentCenter.equals(this.pinchOrigin)){b=
+this.map.getResolutionForZoom(a);c=this.map.getLonLatFromPixel(this.pinchOrigin);var d=this.currentCenter,e=this.map.getSize();c.lon+=b*(e.w/2-d.x);c.lat-=b*(e.h/2-d.y);this.map.div.clientWidth=this.map.div.clientWidth;this.map.setCenter(c,a)}},CLASS_NAME:"OpenLayers.Control.PinchZoom"});OpenLayers.Control.TouchNavigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,pinchZoom:null,pinchZoomOptions:null,clickHandlerOptions:null,documentDrag:!1,autoActivate:!0,initialize:function(a){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments)},destroy:function(){this.deactivate();this.dragPan&&this.dragPan.destroy();this.dragPan=null;this.pinchZoom&&(this.pinchZoom.destroy(),delete this.pinchZoom);OpenLayers.Control.prototype.destroy.apply(this,
+arguments)},activate:function(){return OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.dragPan.activate(),this.handlers.click.activate(),this.pinchZoom.activate(),!0):!1},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments)?(this.dragPan.deactivate(),this.handlers.click.deactivate(),this.pinchZoom.deactivate(),!0):!1},draw:function(){var a={click:this.defaultClick,dblclick:this.defaultDblClick},b=OpenLayers.Util.extend({"double":!0,stopDouble:!0,
+pixelTolerance:2},this.clickHandlerOptions);this.handlers.click=new OpenLayers.Handler.Click(this,a,b);this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map,documentDrag:this.documentDrag},this.dragPanOptions));this.dragPan.draw();this.pinchZoom=new OpenLayers.Control.PinchZoom(OpenLayers.Util.extend({map:this.map},this.pinchZoomOptions))},defaultClick:function(a){a.lastTouches&&2==a.lastTouches.length&&this.map.zoomOut()},defaultDblClick:function(a){this.map.zoomTo(this.map.zoom+
+1,a.xy)},CLASS_NAME:"OpenLayers.Control.TouchNavigation"});OpenLayers.Console.warn("OpenLayers.Rico is deprecated");OpenLayers.Rico=OpenLayers.Rico||{};
+OpenLayers.Rico.Color=OpenLayers.Class({initialize:function(a,b,c){this.rgb={r:a,g:b,b:c}},setRed:function(a){this.rgb.r=a},setGreen:function(a){this.rgb.g=a},setBlue:function(a){this.rgb.b=a},setHue:function(a){var b=this.asHSB();b.h=a;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,b.b)},setSaturation:function(a){var b=this.asHSB();b.s=a;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,b.b)},setBrightness:function(a){var b=this.asHSB();b.b=a;this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,b.b)},
+darken:function(a){var b=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,Math.max(b.b-a,0))},brighten:function(a){var b=this.asHSB();this.rgb=OpenLayers.Rico.Color.HSBtoRGB(b.h,b.s,Math.min(b.b+a,1))},blend:function(a){this.rgb.r=Math.floor((this.rgb.r+a.rgb.r)/2);this.rgb.g=Math.floor((this.rgb.g+a.rgb.g)/2);this.rgb.b=Math.floor((this.rgb.b+a.rgb.b)/2)},isBright:function(){this.asHSB();return 0.5<this.asHSB().b},isDark:function(){return!this.isBright()},asRGB:function(){return"rgb("+
+this.rgb.r+","+this.rgb.g+","+this.rgb.b+")"},asHex:function(){return"#"+this.rgb.r.toColorPart()+this.rgb.g.toColorPart()+this.rgb.b.toColorPart()},asHSB:function(){return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r,this.rgb.g,this.rgb.b)},toString:function(){return this.asHex()}});
+OpenLayers.Rico.Color.createFromHex=function(a){if(4==a.length){var b=a;a="#";for(var c=1;4>c;c++)a+=b.charAt(c)+b.charAt(c)}0==a.indexOf("#")&&(a=a.substring(1));b=a.substring(0,2);c=a.substring(2,4);a=a.substring(4,6);return new OpenLayers.Rico.Color(parseInt(b,16),parseInt(c,16),parseInt(a,16))};
+OpenLayers.Rico.Color.createColorFromBackground=function(a){var b=OpenLayers.Element.getStyle(OpenLayers.Util.getElement(a),"backgroundColor");return"transparent"==b&&a.parentNode?OpenLayers.Rico.Color.createColorFromBackground(a.parentNode):null==b?new OpenLayers.Rico.Color(255,255,255):0==b.indexOf("rgb(")?(a=b.substring(4,b.length-1).split(","),new OpenLayers.Rico.Color(parseInt(a[0]),parseInt(a[1]),parseInt(a[2]))):0==b.indexOf("#")?OpenLayers.Rico.Color.createFromHex(b):new OpenLayers.Rico.Color(255,
+255,255)};
+OpenLayers.Rico.Color.HSBtoRGB=function(a,b,c){var d=0,e=0,f=0;if(0==b)f=e=d=parseInt(255*c+0.5);else{a=6*(a-Math.floor(a));var g=a-Math.floor(a),h=c*(1-b),k=c*(1-b*g);b=c*(1-b*(1-g));switch(parseInt(a)){case 0:d=255*c+0.5;e=255*b+0.5;f=255*h+0.5;break;case 1:d=255*k+0.5;e=255*c+0.5;f=255*h+0.5;break;case 2:d=255*h+0.5;e=255*c+0.5;f=255*b+0.5;break;case 3:d=255*h+0.5;e=255*k+0.5;f=255*c+0.5;break;case 4:d=255*b+0.5;e=255*h+0.5;f=255*c+0.5;break;case 5:d=255*c+0.5,e=255*h+0.5,f=255*k+0.5}}return{r:parseInt(d),g:parseInt(e),
+b:parseInt(f)}};OpenLayers.Rico.Color.RGBtoHSB=function(a,b,c){var d,e=a>b?a:b;c>e&&(e=c);var f=a<b?a:b;c<f&&(f=c);d=0!=e?(e-f)/e:0;if(0==d)a=0;else{var g=(e-a)/(e-f),h=(e-b)/(e-f);c=(e-c)/(e-f);a=(a==e?c-h:b==e?2+g-c:4+h-g)/6;0>a&&(a+=1)}return{h:a,s:d,b:e/255}};OpenLayers.Style2=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:!1,rules:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){for(var a=0,b=this.rules.length;a<b;a++)this.rules[a].destroy();delete this.rules},clone:function(){var a=OpenLayers.Util.extend({},this);if(this.rules){a.rules=[];for(var b=0,c=this.rules.length;b<c;++b)a.rules.push(this.rules[b].clone())}return new OpenLayers.Style2(a)},
+CLASS_NAME:"OpenLayers.Style2"});OpenLayers.Format.WFS=OpenLayers.Class(OpenLayers.Format.GML,{layer:null,wfsns:"http://www.opengis.net/wfs",ogcns:"http://www.opengis.net/ogc",initialize:function(a,b){OpenLayers.Format.GML.prototype.initialize.apply(this,[a]);this.layer=b;this.layer.featureNS&&(this.featureNS=this.layer.featureNS);this.layer.options.geometry_column&&(this.geometryName=this.layer.options.geometry_column);this.layer.options.typename&&(this.featureName=this.layer.options.typename)},write:function(a){var b=this.createElementNS(this.wfsns,
+"wfs:Transaction");b.setAttribute("version","1.0.0");b.setAttribute("service","WFS");for(var c=0;c<a.length;c++)switch(a[c].state){case OpenLayers.State.INSERT:b.appendChild(this.insert(a[c]));break;case OpenLayers.State.UPDATE:b.appendChild(this.update(a[c]));break;case OpenLayers.State.DELETE:b.appendChild(this.remove(a[c]))}return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.featureNS,"feature:"+
+this.geometryName);c.appendChild(b);b=this.createElementNS(this.featureNS,"feature:"+this.featureName);b.appendChild(c);for(var d in a.attributes){var c=this.createTextNode(a.attributes[d]),e=d;-1!=d.search(":")&&(e=d.split(":")[1]);e=this.createElementNS(this.featureNS,"feature:"+e);e.appendChild(c);b.appendChild(e)}return b},insert:function(a){var b=this.createElementNS(this.wfsns,"wfs:Insert");b.appendChild(this.createFeatureXML(a));return b},update:function(a){a.fid||OpenLayers.Console.userError(OpenLayers.i18n("noFID"));
+var b=this.createElementNS(this.wfsns,"wfs:Update");b.setAttribute("typeName",this.featurePrefix+":"+this.featureName);b.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var c=this.createElementNS(this.wfsns,"wfs:Property"),d=this.createElementNS(this.wfsns,"wfs:Name"),e=this.createTextNode(this.geometryName);d.appendChild(e);c.appendChild(d);d=this.createElementNS(this.wfsns,"wfs:Value");e=this.buildGeometryNode(a.geometry);a.layer&&e.setAttribute("srsName",a.layer.projection.getCode());
+d.appendChild(e);c.appendChild(d);b.appendChild(c);for(var f in a.attributes)c=this.createElementNS(this.wfsns,"wfs:Property"),d=this.createElementNS(this.wfsns,"wfs:Name"),d.appendChild(this.createTextNode(f)),c.appendChild(d),d=this.createElementNS(this.wfsns,"wfs:Value"),d.appendChild(this.createTextNode(a.attributes[f])),c.appendChild(d),b.appendChild(c);c=this.createElementNS(this.ogcns,"ogc:Filter");f=this.createElementNS(this.ogcns,"ogc:FeatureId");f.setAttribute("fid",a.fid);c.appendChild(f);
+b.appendChild(c);return b},remove:function(a){if(!a.fid)return OpenLayers.Console.userError(OpenLayers.i18n("noFID")),!1;var b=this.createElementNS(this.wfsns,"wfs:Delete");b.setAttribute("typeName",this.featurePrefix+":"+this.featureName);b.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);var c=this.createElementNS(this.ogcns,"ogc:Filter"),d=this.createElementNS(this.ogcns,"ogc:FeatureId");d.setAttribute("fid",a.fid);c.appendChild(d);b.appendChild(c);return b},destroy:function(){this.layer=
+null},CLASS_NAME:"OpenLayers.Format.WFS"});OpenLayers.Format.SLD.v1_0_0_GeoServer=OpenLayers.Class(OpenLayers.Format.SLD.v1_0_0,{version:"1.0.0",profile:"GeoServer",readers:OpenLayers.Util.applyDefaults({sld:OpenLayers.Util.applyDefaults({Priority:function(a,b){var c=this.readers.ogc._expression.call(this,a);c&&(b.priority=c)},VendorOption:function(a,b){b.vendorOptions||(b.vendorOptions={});b.vendorOptions[a.getAttribute("name")]=this.getChildValue(a)},TextSymbolizer:function(a,b){OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld.TextSymbolizer.apply(this,
+arguments);var c=this.multipleSymbolizers?b.symbolizers[b.symbolizers.length-1]:b.symbolizer.Text;void 0===c.graphic&&(c.graphic=!1)}},OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld)},OpenLayers.Format.SLD.v1_0_0.prototype.readers),writers:OpenLayers.Util.applyDefaults({sld:OpenLayers.Util.applyDefaults({Priority:function(a){return this.writers.sld._OGCExpression.call(this,"sld:Priority",a)},VendorOption:function(a){return this.createElementNSPlus("sld:VendorOption",{attributes:{name:a.name},
+value:a.value})},TextSymbolizer:function(a){var b=OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.TextSymbolizer.apply(this,arguments);!1!==a.graphic&&(a.externalGraphic||a.graphicName)&&this.writeNode("Graphic",a,b);"priority"in a&&this.writeNode("Priority",a.priority,b);return this.addVendorOptions(b,a)},PointSymbolizer:function(a){var b=OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.PointSymbolizer.apply(this,arguments);return this.addVendorOptions(b,a)},LineSymbolizer:function(a){var b=
+OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.LineSymbolizer.apply(this,arguments);return this.addVendorOptions(b,a)},PolygonSymbolizer:function(a){var b=OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld.PolygonSymbolizer.apply(this,arguments);return this.addVendorOptions(b,a)}},OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld)},OpenLayers.Format.SLD.v1_0_0.prototype.writers),addVendorOptions:function(a,b){if(b.vendorOptions)for(var c in b.vendorOptions)this.writeNode("VendorOption",{name:c,
+value:b.vendorOptions[c]},a);return a},CLASS_NAME:"OpenLayers.Format.SLD.v1_0_0_GeoServer"});OpenLayers.Layer.Boxes=OpenLayers.Class(OpenLayers.Layer.Markers,{drawMarker:function(a){var b=this.map.getLayerPxFromLonLat({lon:a.bounds.left,lat:a.bounds.top}),c=this.map.getLayerPxFromLonLat({lon:a.bounds.right,lat:a.bounds.bottom});null==c||null==b?a.display(!1):(b=a.draw(b,{w:Math.max(1,c.x-b.x),h:Math.max(1,c.y-b.y)}),a.drawn||(this.div.appendChild(b),a.drawn=!0))},removeMarker:function(a){OpenLayers.Util.removeItem(this.markers,a);null!=a.div&&a.div.parentNode==this.div&&this.div.removeChild(a.div)},
+CLASS_NAME:"OpenLayers.Layer.Boxes"});OpenLayers.Format.WFSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{readers:{wfs:OpenLayers.Util.applyDefaults({Service:function(a,b){b.service={};this.readChildNodes(a,b.service)},Fees:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.fees=c)},AccessConstraints:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.accessConstraints=c)},OnlineResource:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.onlineResource=
+c)},Keywords:function(a,b){var c=this.getChildValue(a);c&&"none"!=c.toLowerCase()&&(b.keywords=c.split(", "))},Capability:function(a,b){b.capability={};this.readChildNodes(a,b.capability)},Request:function(a,b){b.request={};this.readChildNodes(a,b.request)},GetFeature:function(a,b){b.getfeature={href:{},formats:[]};this.readChildNodes(a,b.getfeature)},ResultFormat:function(a,b){for(var c=a.childNodes,d,e=0;e<c.length;e++)d=c[e],1==d.nodeType&&b.formats.push(d.nodeName)},DCPType:function(a,b){this.readChildNodes(a,
+b)},HTTP:function(a,b){this.readChildNodes(a,b.href)},Get:function(a,b){b.get=a.getAttribute("onlineResource")},Post:function(a,b){b.post=a.getAttribute("onlineResource")},SRS:function(a,b){var c=this.getChildValue(a);c&&(b.srs=c)}},OpenLayers.Format.WFSCapabilities.v1.prototype.readers.wfs)},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_0_0"});OpenLayers.Format.WMSCapabilities.v1_3=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1,{readers:{wms:OpenLayers.Util.applyDefaults({WMS_Capabilities:function(a,b){this.readChildNodes(a,b)},LayerLimit:function(a,b){b.layerLimit=parseInt(this.getChildValue(a))},MaxWidth:function(a,b){b.maxWidth=parseInt(this.getChildValue(a))},MaxHeight:function(a,b){b.maxHeight=parseInt(this.getChildValue(a))},BoundingBox:function(a,b){var c=OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms.BoundingBox.apply(this,
+[a,b]);c.srs=a.getAttribute("CRS");b.bbox[c.srs]=c},CRS:function(a,b){this.readers.wms.SRS.apply(this,[a,b])},EX_GeographicBoundingBox:function(a,b){b.llbbox=[];this.readChildNodes(a,b.llbbox)},westBoundLongitude:function(a,b){b[0]=this.getChildValue(a)},eastBoundLongitude:function(a,b){b[2]=this.getChildValue(a)},southBoundLatitude:function(a,b){b[1]=this.getChildValue(a)},northBoundLatitude:function(a,b){b[3]=this.getChildValue(a)},MinScaleDenominator:function(a,b){b.maxScale=parseFloat(this.getChildValue(a)).toPrecision(16)},
+MaxScaleDenominator:function(a,b){b.minScale=parseFloat(this.getChildValue(a)).toPrecision(16)},Dimension:function(a,b){var c={name:a.getAttribute("name").toLowerCase(),units:a.getAttribute("units"),unitsymbol:a.getAttribute("unitSymbol"),nearestVal:"1"===a.getAttribute("nearestValue"),multipleVal:"1"===a.getAttribute("multipleValues"),"default":a.getAttribute("default")||"",current:"1"===a.getAttribute("current"),values:this.getChildValue(a).split(",")};b.dimensions[c.name]=c},Keyword:function(a,
+b){var c={value:this.getChildValue(a),vocabulary:a.getAttribute("vocabulary")};b.keywords&&b.keywords.push(c)}},OpenLayers.Format.WMSCapabilities.v1.prototype.readers.wms),sld:{UserDefinedSymbolization:function(a,b){this.readers.wms.UserDefinedSymbolization.apply(this,[a,b]);b.userSymbols.inlineFeature=1==parseInt(a.getAttribute("InlineFeature"));b.userSymbols.remoteWCS=1==parseInt(a.getAttribute("RemoteWCS"))},DescribeLayer:function(a,b){this.readers.wms.DescribeLayer.apply(this,[a,b])},GetLegendGraphic:function(a,
+b){this.readers.wms.GetLegendGraphic.apply(this,[a,b])}}},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_3"});OpenLayers.Layer.Zoomify=OpenLayers.Class(OpenLayers.Layer.Grid,{size:null,isBaseLayer:!0,standardTileSize:256,tileOriginCorner:"tl",numberOfTiers:0,tileCountUpToTier:null,tierSizeInTiles:null,tierImageSize:null,initialize:function(a,b,c,d){this.initializeZoomify(c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a,b,c,{},d])},initializeZoomify:function(a){var b=a.clone();this.size=a.clone();a=new OpenLayers.Size(Math.ceil(b.w/this.standardTileSize),Math.ceil(b.h/this.standardTileSize));this.tierSizeInTiles=
+[a];for(this.tierImageSize=[b];b.w>this.standardTileSize||b.h>this.standardTileSize;)b=new OpenLayers.Size(Math.floor(b.w/2),Math.floor(b.h/2)),a=new OpenLayers.Size(Math.ceil(b.w/this.standardTileSize),Math.ceil(b.h/this.standardTileSize)),this.tierSizeInTiles.push(a),this.tierImageSize.push(b);this.tierSizeInTiles.reverse();this.tierImageSize.reverse();this.numberOfTiers=this.tierSizeInTiles.length;b=[1];this.tileCountUpToTier=[0];for(a=1;a<this.numberOfTiers;a++)b.unshift(Math.pow(2,a)),this.tileCountUpToTier.push(this.tierSizeInTiles[a-
+1].w*this.tierSizeInTiles[a-1].h+this.tileCountUpToTier[a-1]);this.serverResolutions||(this.serverResolutions=b)},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);this.tileCountUpToTier.length=0;this.tierSizeInTiles.length=0;this.tierImageSize.length=0},clone:function(a){null==a&&(a=new OpenLayers.Layer.Zoomify(this.name,this.url,this.size,this.options));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds(a);var b=
+this.getServerResolution(),c=Math.round((a.left-this.tileOrigin.lon)/(b*this.tileSize.w));a=Math.round((this.tileOrigin.lat-a.top)/(b*this.tileSize.h));b=this.getZoomForResolution(b);c="TileGroup"+Math.floor((c+a*this.tierSizeInTiles[b].w+this.tileCountUpToTier[b])/256)+"/"+b+"-"+c+"-"+a+".jpg";b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(c,b));return b+c},getImageSize:function(){if(0<arguments.length){var a=this.adjustBounds(arguments[0]),b=this.getServerResolution(),c=Math.round((a.left-
+this.tileOrigin.lon)/(b*this.tileSize.w)),a=Math.round((this.tileOrigin.lat-a.top)/(b*this.tileSize.h)),b=this.getZoomForResolution(b),d=this.standardTileSize,e=this.standardTileSize;c==this.tierSizeInTiles[b].w-1&&(d=this.tierImageSize[b].w%this.standardTileSize);a==this.tierSizeInTiles[b].h-1&&(e=this.tierImageSize[b].h%this.standardTileSize);return new OpenLayers.Size(d,e)}return this.tileSize},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,
+this.map.maxExtent.top)},CLASS_NAME:"OpenLayers.Layer.Zoomify"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(a,b,c,d){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);if(null==d||null==d.isBaseLayer)this.isBaseLayer="true"!=this.params.transparent&&!0!=this.params.transparent},clone:function(a){null==a&&(a=new OpenLayers.Layer.MapServer(this.name,this.url,this.params,this.getOptions()));
+return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getURL:function(a){a=this.adjustBounds(a);a=[a.left,a.bottom,a.right,a.top];var b=this.getImageSize();return this.getFullRequestString({mapext:a,imgext:a,map_size:[b.w,b.h],imgx:b.w/2,imgy:b.h/2,imgxy:[b.w,b.h]})},getFullRequestString:function(a,b){var c=null==b?this.url:b,d=OpenLayers.Util.extend({},this.params),d=OpenLayers.Util.extend(d,a),e=OpenLayers.Util.getParameterString(d);OpenLayers.Util.isArray(c)&&(c=this.selectUrl(e,c));
+var e=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),f;for(f in d)f.toUpperCase()in e&&delete d[f];e=OpenLayers.Util.getParameterString(d);d=c;e=e.replace(/,/g,"+");""!=e&&(f=c.charAt(c.length-1),d="&"==f||"?"==f?d+e:-1==c.indexOf("?")?d+("?"+e):d+("&"+e));return d},CLASS_NAME:"OpenLayers.Layer.MapServer"});OpenLayers.Renderer.VML=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"urn:schemas-microsoft-com:vml",symbolCache:{},offset:null,initialize:function(a){if(this.supported()){if(!document.namespaces.olv){document.namespaces.add("olv",this.xmlns);for(var b=document.createStyleSheet(),c="shape rect oval fill stroke imagedata group textbox".split(" "),d=0,e=c.length;d<e;d++)b.addRule("olv\\:"+c[d],"behavior: url(#default#VML); position: absolute; display: inline-block;")}OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+arguments)}},supported:function(){return!!document.namespaces},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=a.left/d|0,d=a.top/d-this.size.h|0;b||!this.offset?(this.offset={x:e,y:d},d=e=0):(e-=this.offset.x,d-=this.offset.y);this.root.coordorigin=e-this.xOffset+" "+d;for(var e=[this.root,this.vectorRoot,this.textRoot],f=0,g=e.length;f<g;++f)d=e[f],d.coordsize=this.size.w+" "+this.size.h;this.root.style.flip="y";return c},
+setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);for(var b=[this.rendererRoot,this.root,this.vectorRoot,this.textRoot],c=this.size.w+"px",d=this.size.h+"px",e,f=0,g=b.length;f<g;++f)e=b[f],e.style.width=c,e.style.height=d},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"olv:rect":this.isComplexSymbol(b.graphicName)?"olv:shape":"olv:oval";break;case "OpenLayers.Geometry.Rectangle":c="olv:rect";break;case "OpenLayers.Geometry.LineString":case "OpenLayers.Geometry.LinearRing":case "OpenLayers.Geometry.Polygon":case "OpenLayers.Geometry.Curve":c=
+"olv:shape"}return c},setStyle:function(a,b,c,d){b=b||a._style;c=c||a._options;var e=b.fillColor,f=b.title||b.graphicTitle;f&&(a.title=f);if("OpenLayers.Geometry.Point"===a._geometryClass)if(b.externalGraphic){c.isFilled=!0;var e=b.graphicWidth||b.graphicHeight,f=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,f=f?f:2*b.pointRadius,g=this.getResolution(),h=void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e),k=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*f);a.style.left=((d.x-this.featureDx)/
+g-this.offset.x+h|0)+"px";a.style.top=(d.y/g-this.offset.y-(k+f)|0)+"px";a.style.width=e+"px";a.style.height=f+"px";a.style.flip="y";e="none";c.isStroked=!1}else this.isComplexSymbol(b.graphicName)?(f=this.importSymbol(b.graphicName),a.path=f.path,a.coordorigin=f.left+","+f.bottom,f=f.size,a.coordsize=f+","+f,this.drawCircle(a,d,b.pointRadius),a.style.flip="y"):this.drawCircle(a,d,b.pointRadius);c.isFilled?a.fillcolor=e:a.filled="false";d=a.getElementsByTagName("fill");d=0==d.length?null:d[0];c.isFilled?
+(d||(d=this.createNode("olv:fill",a.id+"_fill")),d.opacity=b.fillOpacity,"OpenLayers.Geometry.Point"===a._geometryClass&&b.externalGraphic&&(b.graphicOpacity&&(d.opacity=b.graphicOpacity),d.src=b.externalGraphic,d.type="frame",b.graphicWidth&&b.graphicHeight||(d.aspect="atmost")),d.parentNode!=a&&a.appendChild(d)):d&&a.removeChild(d);e=b.rotation;if(void 0!==e||void 0!==a._rotation)a._rotation=e,b.externalGraphic?(this.graphicRotate(a,h,k,b),d.opacity=0):"OpenLayers.Geometry.Point"===a._geometryClass&&
+(a.style.rotation=e||0);h=a.getElementsByTagName("stroke");h=0==h.length?null:h[0];c.isStroked?(h||(h=this.createNode("olv:stroke",a.id+"_stroke"),a.appendChild(h)),h.on=!0,h.color=b.strokeColor,h.weight=b.strokeWidth+"px",h.opacity=b.strokeOpacity,h.endcap="butt"==b.strokeLinecap?"flat":b.strokeLinecap||"round",b.strokeDashstyle&&(h.dashstyle=this.dashStyle(b))):(a.stroked=!1,h&&(h.on=!1));"inherit"!=b.cursor&&null!=b.cursor&&(a.style.cursor=b.cursor);return a},graphicRotate:function(a,b,c,d){d=
+d||a._style;var e=d.rotation||0,f,g;if(d.graphicWidth&&d.graphicHeight){g=Math.max(d.graphicWidth,d.graphicHeight);f=d.graphicWidth/d.graphicHeight;var h=Math.round(d.graphicWidth||g*f),k=Math.round(d.graphicHeight||g);a.style.width=h+"px";a.style.height=k+"px";var l=document.getElementById(a.id+"_image");l||(l=this.createNode("olv:imagedata",a.id+"_image"),a.appendChild(l));l.style.width=h+"px";l.style.height=k+"px";l.src=d.externalGraphic;l.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='', sizingMethod='scale')";
+l=e*Math.PI/180;e=Math.sin(l);l=Math.cos(l);e="progid:DXImageTransform.Microsoft.Matrix(M11="+l+",M12="+-e+",M21="+e+",M22="+l+",SizingMethod='auto expand')\n";(l=d.graphicOpacity||d.fillOpacity)&&1!=l&&(e+="progid:DXImageTransform.Microsoft.BasicImage(opacity="+l+")\n");a.style.filter=e;e=new OpenLayers.Geometry.Point(-b,-c);h=(new OpenLayers.Bounds(0,0,h,k)).toGeometry();h.rotate(d.rotation,e);h=h.getBounds();a.style.left=Math.round(parseInt(a.style.left)+h.left)+"px";a.style.top=Math.round(parseInt(a.style.top)-
+h.bottom)+"px"}else{var m=new Image;m.onreadystatechange=OpenLayers.Function.bind(function(){if("complete"==m.readyState||"interactive"==m.readyState)f=m.width/m.height,g=Math.max(2*d.pointRadius,d.graphicWidth||0,d.graphicHeight||0),b*=f,d.graphicWidth=g*f,d.graphicHeight=g,this.graphicRotate(a,b,c,d)},this);m.src=d.externalGraphic}},postDraw:function(a){a.style.visibility="visible";var b=a._style.fillColor,c=a._style.strokeColor;"none"==b&&a.fillcolor!=b&&(a.fillcolor=b);"none"==c&&a.strokecolor!=
+c&&(a.strokecolor=c)},setNodeDimension:function(a,b){var c=b.getBounds();if(c){var d=this.getResolution(),c=new OpenLayers.Bounds((c.left-this.featureDx)/d-this.offset.x|0,c.bottom/d-this.offset.y|0,(c.right-this.featureDx)/d-this.offset.x|0,c.top/d-this.offset.y|0);a.style.left=c.left+"px";a.style.top=c.top+"px";a.style.width=c.getWidth()+"px";a.style.height=c.getHeight()+"px";a.coordorigin=c.left+" "+c.top;a.coordsize=c.getWidth()+" "+c.getHeight()}},dashStyle:function(a){a=a.strokeDashstyle;switch(a){case "solid":case "dot":case "dash":case "dashdot":case "longdash":case "longdashdot":return a;
+default:return a=a.split(/[ ,]/),2==a.length?1*a[0]>=2*a[1]?"longdash":1==a[0]||1==a[1]?"dot":"dash":4==a.length?1*a[0]>=2*a[1]?"longdashdot":"dashdot":"solid"}},createNode:function(a,b){var c=document.createElement(a);b&&(c.id=b);c.unselectable="on";c.onselectstart=OpenLayers.Function.False;return c},nodeTypeCompare:function(a,b){var c=b,d=c.indexOf(":");-1!=d&&(c=c.substr(d+1));var e=a.nodeName,d=e.indexOf(":");-1!=d&&(e=e.substr(d+1));return c==e},createRenderRoot:function(){return this.nodeFactory(this.container.id+
+"_vmlRoot","div")},createRoot:function(a){return this.nodeFactory(this.container.id+a,"olv:group")},drawPoint:function(a,b){return this.drawCircle(a,b,1)},drawCircle:function(a,b,c){if(!isNaN(b.x)&&!isNaN(b.y)){var d=this.getResolution();a.style.left=((b.x-this.featureDx)/d-this.offset.x|0)-c+"px";a.style.top=(b.y/d-this.offset.y|0)-c+"px";b=2*c;a.style.width=b+"px";a.style.height=b+"px";return a}return!1},drawLineString:function(a,b){return this.drawLine(a,b,!1)},drawLinearRing:function(a,b){return this.drawLine(a,
+b,!0)},drawLine:function(a,b,c){this.setNodeDimension(a,b);for(var d=this.getResolution(),e=b.components.length,f=Array(e),g,h,k=0;k<e;k++)g=b.components[k],h=(g.x-this.featureDx)/d-this.offset.x|0,g=g.y/d-this.offset.y|0,f[k]=" "+h+","+g+" l ";b=c?" x e":" e";a.path="m"+f.join("")+b;return a},drawPolygon:function(a,b){this.setNodeDimension(a,b);var c=this.getResolution(),d=[],e,f,g,h,k,l,m,n,p,q;e=0;for(f=b.components.length;e<f;e++){d.push("m");g=b.components[e].components;h=0===e;l=k=null;m=0;
+for(n=g.length;m<n;m++)p=g[m],q=(p.x-this.featureDx)/c-this.offset.x|0,p=p.y/c-this.offset.y|0,q=" "+q+","+p,d.push(q),0==m&&d.push(" l"),h||(k?k!=q&&(l?l!=q&&(h=!0):l=q):k=q);d.push(h?" x ":" ")}d.push("e");a.path=d.join("");return a},drawRectangle:function(a,b){var c=this.getResolution();a.style.left=((b.x-this.featureDx)/c-this.offset.x|0)+"px";a.style.top=(b.y/c-this.offset.y|0)+"px";a.style.width=(b.width/c|0)+"px";a.style.height=(b.height/c|0)+"px";return a},drawText:function(a,b,c){var d=this.nodeFactory(a+
+this.LABEL_ID_SUFFIX,"olv:rect"),e=this.nodeFactory(a+this.LABEL_ID_SUFFIX+"_textbox","olv:textbox"),f=this.getResolution();d.style.left=((c.x-this.featureDx)/f-this.offset.x|0)+"px";d.style.top=(c.y/f-this.offset.y|0)+"px";d.style.flip="y";e.innerText=b.label;"inherit"!=b.cursor&&null!=b.cursor&&(e.style.cursor=b.cursor);b.fontColor&&(e.style.color=b.fontColor);b.fontOpacity&&(e.style.filter="alpha(opacity="+100*b.fontOpacity+")");b.fontFamily&&(e.style.fontFamily=b.fontFamily);b.fontSize&&(e.style.fontSize=
+b.fontSize);b.fontWeight&&(e.style.fontWeight=b.fontWeight);b.fontStyle&&(e.style.fontStyle=b.fontStyle);!0===b.labelSelect&&(d._featureId=a,e._featureId=a,e._geometry=c,e._geometryClass=c.CLASS_NAME);e.style.whiteSpace="nowrap";e.inset="1px,0px,0px,0px";d.parentNode||(d.appendChild(e),this.textRoot.appendChild(d));b=b.labelAlign||"cm";1==b.length&&(b+="m");a=e.clientWidth*OpenLayers.Renderer.VML.LABEL_SHIFT[b.substr(0,1)];e=e.clientHeight*OpenLayers.Renderer.VML.LABEL_SHIFT[b.substr(1,1)];d.style.left=
+parseInt(d.style.left)-a-1+"px";d.style.top=parseInt(d.style.top)+e+"px"},moveRoot:function(a){var b=this.map.getLayer(a.container.id);b instanceof OpenLayers.Layer.Vector.RootContainer&&(b=this.map.getLayer(this.container.id));b&&b.renderer.clear();OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this,arguments);b&&b.redraw()},importSymbol:function(a){var b=this.container.id+"-"+a,c=this.symbolCache[b];if(c)return c;c=OpenLayers.Renderer.symbol[a];if(!c)throw Error(a+" is not a valid symbol name");
+a=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);for(var d=["m"],e=0;e<c.length;e+=2){var f=c[e],g=c[e+1];a.left=Math.min(a.left,f);a.bottom=Math.min(a.bottom,g);a.right=Math.max(a.right,f);a.top=Math.max(a.top,g);d.push(f);d.push(g);0==e&&d.push("l")}d.push("x e");c=d.join(" ");d=(a.getWidth()-a.getHeight())/2;0<d?(a.bottom-=d,a.top+=d):(a.left+=d,a.right-=d);c={path:c,size:a.getWidth(),left:a.left,bottom:a.bottom};return this.symbolCache[b]=c},CLASS_NAME:"OpenLayers.Renderer.VML"});
+OpenLayers.Renderer.VML.LABEL_SHIFT={l:0,c:0.5,r:1,t:0,m:0.5,b:1};OpenLayers.Control.CacheRead=OpenLayers.Class(OpenLayers.Control,{fetchEvent:"tileloadstart",layers:null,autoActivate:!0,setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);var b,c=this.layers||a.layers;for(b=c.length-1;0<=b;--b)this.addLayer({layer:c[b]});if(!this.layers)a.events.on({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this})},addLayer:function(a){a.layer.events.register(this.fetchEvent,this,this.fetch)},removeLayer:function(a){a.layer.events.unregister(this.fetchEvent,
+this,this.fetch)},fetch:function(a){if(this.active&&window.localStorage&&a.tile instanceof OpenLayers.Tile.Image){var b=a.tile,c=b.url;!b.layer.crossOriginKeyword&&(OpenLayers.ProxyHost&&0===c.indexOf(OpenLayers.ProxyHost))&&(c=OpenLayers.Control.CacheWrite.urlMap[c]);if(c=window.localStorage.getItem("olCache_"+c))b.url=c,"tileerror"===a.type&&b.setImgSrc(c)}},destroy:function(){if(this.layers||this.map){var a,b=this.layers||this.map.layers;for(a=b.length-1;0<=a;--a)this.removeLayer({layer:b[a]})}this.map&&
+this.map.events.un({addlayer:this.addLayer,removeLayer:this.removeLayer,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},CLASS_NAME:"OpenLayers.Control.CacheRead"});OpenLayers.Protocol.WFS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.0.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_0_0"});OpenLayers.Format.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Format.XML,{layerIdentifier:"_layer",featureIdentifier:"_feature",regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},gmlFormat:null,read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b=a.documentElement;if(b){var c=this["read_"+b.nodeName];a=c?c.call(this,b):(new OpenLayers.Format.GML(this.options?this.options:{})).read(a)}return a},read_msGMLOutput:function(a){var b=
+[];if(a=this.getSiblingNodesByTagCriteria(a,this.layerIdentifier))for(var c=0,d=a.length;c<d;++c){var e=a[c],f=e.nodeName;e.prefix&&(f=f.split(":")[1]);f=f.replace(this.layerIdentifier,"");if(e=this.getSiblingNodesByTagCriteria(e,this.featureIdentifier))for(var g=0;g<e.length;g++){var h=e[g],k=this.parseGeometry(h),h=this.parseAttributes(h),h=new OpenLayers.Feature.Vector(k.geometry,h,null);h.bounds=k.bounds;h.type=f;b.push(h)}}return b},read_FeatureInfoResponse:function(a){var b=[];a=this.getElementsByTagNameNS(a,
+"*","FIELDS");for(var c=0,d=a.length;c<d;c++){var e=a[c],f={},g,h=e.attributes.length;if(0<h)for(g=0;g<h;g++){var k=e.attributes[g];f[k.nodeName]=k.nodeValue}else for(e=e.childNodes,g=0,h=e.length;g<h;++g)k=e[g],3!=k.nodeType&&(f[k.getAttribute("name")]=k.getAttribute("value"));b.push(new OpenLayers.Feature.Vector(null,f,null))}return b},getSiblingNodesByTagCriteria:function(a,b){var c=[],d,e,f,g;if(a&&a.hasChildNodes()){d=a.childNodes;f=d.length;for(var h=0;h<f;h++){for(g=d[h];g&&1!=g.nodeType;)g=
+g.nextSibling,h++;e=g?g.nodeName:"";0<e.length&&-1<e.indexOf(b)?c.push(g):(e=this.getSiblingNodesByTagCriteria(g,b),0<e.length&&(0==c.length?c=e:c.push(e)))}}return c},parseAttributes:function(a){var b={};if(1==a.nodeType){a=a.childNodes;for(var c=a.length,d=0;d<c;++d){var e=a[d];if(1==e.nodeType){var f=e.childNodes,e=e.prefix?e.nodeName.split(":")[1]:e.nodeName;0==f.length?b[e]=null:1==f.length&&(f=f[0],3==f.nodeType||4==f.nodeType)&&(f=f.nodeValue.replace(this.regExes.trimSpace,""),b[e]=f)}}}return b},
+parseGeometry:function(a){this.gmlFormat||(this.gmlFormat=new OpenLayers.Format.GML);a=this.gmlFormat.parseFeature(a);var b,c=null;a&&(b=a.geometry&&a.geometry.clone(),c=a.bounds&&a.bounds.clone(),a.destroy());return{geometry:b,bounds:c}},CLASS_NAME:"OpenLayers.Format.WMSGetFeatureInfo"});OpenLayers.Control.WMTSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:!1,requestEncoding:"KVP",drillDown:!1,maxFeatures:10,clickCallback:"click",layers:null,queryVisible:!0,infoFormat:"text/html",vendorParams:{},format:null,formatOptions:null,handler:null,hoverRequest:null,pending:0,initialize:function(a){a=a||{};a.handlerOptions=a.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[a]);this.format||(this.format=new OpenLayers.Format.WMSGetFeatureInfo(a.formatOptions));
+!0===this.drillDown&&(this.hover=!1);this.hover?this.handler=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{delay:250})):(a={},a[this.clickCallback]=this.getInfoForClick,this.handler=new OpenLayers.Handler.Click(this,a,this.handlerOptions.click||{}))},getInfoForClick:function(a){this.request(a.xy,{})},getInfoForHover:function(a){this.request(a.xy,{hover:!0})},cancelHover:function(){this.hoverRequest&&(--this.pending,
+0>=this.pending&&(OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait"),this.pending=0),this.hoverRequest.abort(),this.hoverRequest=null)},findLayers:function(){for(var a=this.layers||this.map.layers,b=[],c,d=a.length-1;0<=d&&(c=a[d],!(c instanceof OpenLayers.Layer.WMTS)||(c.requestEncoding!==this.requestEncoding||this.queryVisible&&!c.getVisibility())||(b.push(c),this.drillDown&&!this.hover));--d);return b},buildRequestOptions:function(a,b){var c=this.map.getLonLatFromPixel(b),d=a.getURL(new OpenLayers.Bounds(c.lon,
+c.lat,c.lon,c.lat)),d=OpenLayers.Util.getParameters(d),c=a.getTileInfo(c);OpenLayers.Util.extend(d,{service:"WMTS",version:a.version,request:"GetFeatureInfo",infoFormat:this.infoFormat,i:c.i,j:c.j});OpenLayers.Util.applyDefaults(d,this.vendorParams);return{url:OpenLayers.Util.isArray(a.url)?a.url[0]:a.url,params:OpenLayers.Util.upperCaseObject(d),callback:function(c){this.handleResponse(b,c,a)},scope:this}},request:function(a,b){b=b||{};var c=this.findLayers();if(0<c.length){for(var d,e,f=0,g=c.length;f<
+g;f++)e=c[f],d=this.events.triggerEvent("beforegetfeatureinfo",{xy:a,layer:e}),!1!==d&&(++this.pending,d=this.buildRequestOptions(e,a),d=OpenLayers.Request.GET(d),!0===b.hover&&(this.hoverRequest=d));0<this.pending&&OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait")}},handleResponse:function(a,b,c){--this.pending;0>=this.pending&&(OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait"),this.pending=0);if(b.status&&(200>b.status||300<=b.status))this.events.triggerEvent("exception",
+{xy:a,request:b,layer:c});else{var d=b.responseXML;d&&d.documentElement||(d=b.responseText);var e,f;try{e=this.format.read(d)}catch(g){f=!0,this.events.triggerEvent("exception",{xy:a,request:b,error:g,layer:c})}f||this.events.triggerEvent("getfeatureinfo",{text:b.responseText,features:e,request:b,xy:a,layer:c})}},CLASS_NAME:"OpenLayers.Control.WMTSGetFeatureInfo"});OpenLayers.Protocol.CSW.v2_0_2=OpenLayers.Class(OpenLayers.Protocol,{formatOptions:null,initialize:function(a){OpenLayers.Protocol.prototype.initialize.apply(this,[a]);a.format||(this.format=new OpenLayers.Format.CSWGetRecords.v2_0_2(OpenLayers.Util.extend({},this.formatOptions)))},destroy:function(){this.options&&!this.options.format&&this.format.destroy();this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){a=OpenLayers.Util.extend({},a);OpenLayers.Util.applyDefaults(a,
+this.options||{});var b=new OpenLayers.Protocol.Response({requestType:"read"}),c=this.format.write(a.params||a);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),params:a.params,headers:a.headers,data:c});return b},handleRead:function(a,b){if(b.callback){var c=a.priv;200<=c.status&&300>c.status?(a.data=this.parseData(c),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE;b.callback.call(b.scope,a)}},parseData:function(a){var b=
+a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},CLASS_NAME:"OpenLayers.Protocol.CSW.v2_0_2"});OpenLayers.Format.WCSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WCSCapabilities.v1,{namespaces:{wcs:"http://www.opengis.net/wcs/1.1",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",ows:"http://www.opengis.net/ows/1.1"},errorProperty:"operationsMetadata",readers:{wcs:OpenLayers.Util.applyDefaults({Capabilities:function(a,b){this.readChildNodes(a,b)},Contents:function(a,b){b.contentMetadata=[];this.readChildNodes(a,b.contentMetadata)},CoverageSummary:function(a,
+b){var c={};this.readChildNodes(a,c);b.push(c)},Identifier:function(a,b){b.identifier=this.getChildValue(a)},Title:function(a,b){b.title=this.getChildValue(a)},Abstract:function(a,b){b["abstract"]=this.getChildValue(a)},SupportedCRS:function(a,b){var c=this.getChildValue(a);c&&(b.supportedCRS||(b.supportedCRS=[]),b.supportedCRS.push(c))},SupportedFormat:function(a,b){var c=this.getChildValue(a);c&&(b.supportedFormat||(b.supportedFormat=[]),b.supportedFormat.push(c))}},OpenLayers.Format.WCSCapabilities.v1.prototype.readers.wcs),
+ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WCSCapabilities.v1_1_0"});OpenLayers.Control.Graticule=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,intervals:[45,30,20,10,5,2,1,0.5,0.2,0.1,0.05,0.01,0.005,0.002,0.001],displayInLayerSwitcher:!0,visible:!0,numPoints:50,targetSize:200,layerName:null,labelled:!0,labelFormat:"dm",lineSymbolizer:{strokeColor:"#333",strokeWidth:1,strokeOpacity:0.5},labelSymbolizer:{},gratLayer:null,initialize:function(a){a=a||{};a.layerName=a.layerName||OpenLayers.i18n("Graticule");OpenLayers.Control.prototype.initialize.apply(this,[a]);
+this.labelSymbolizer.stroke=!1;this.labelSymbolizer.fill=!1;this.labelSymbolizer.label="${label}";this.labelSymbolizer.labelAlign="${labelAlign}";this.labelSymbolizer.labelXOffset="${xOffset}";this.labelSymbolizer.labelYOffset="${yOffset}"},destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments);this.gratLayer&&(this.gratLayer.destroy(),this.gratLayer=null)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.gratLayer){var a=new OpenLayers.Style({},
+{rules:[new OpenLayers.Rule({symbolizer:{Point:this.labelSymbolizer,Line:this.lineSymbolizer}})]});this.gratLayer=new OpenLayers.Layer.Vector(this.layerName,{styleMap:new OpenLayers.StyleMap({"default":a}),visibility:this.visible,displayInLayerSwitcher:this.displayInLayerSwitcher})}return this.div},activate:function(){return OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.map.addLayer(this.gratLayer),this.map.events.register("moveend",this,this.update),this.update(),!0):!1},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,
+arguments)?(this.map.events.unregister("moveend",this,this.update),this.map.removeLayer(this.gratLayer),!0):!1},update:function(){var a=this.map.getExtent();if(a){this.gratLayer.destroyFeatures();var b=new OpenLayers.Projection("EPSG:4326"),c=this.map.getProjectionObject(),d=this.map.getResolution();c.proj&&"longlat"==c.proj.projName&&(this.numPoints=1);var e=this.map.getCenter(),f=new OpenLayers.Pixel(e.lon,e.lat);OpenLayers.Projection.transform(f,c,b);for(var e=this.targetSize*d,e=e*e,g,d=0;d<this.intervals.length;++d){g=
+this.intervals[d];var h=g/2,k=f.offset({x:-h,y:-h}),h=f.offset({x:h,y:h});OpenLayers.Projection.transform(k,b,c);OpenLayers.Projection.transform(h,b,c);if((k.x-h.x)*(k.x-h.x)+(k.y-h.y)*(k.y-h.y)<=e)break}f.x=Math.floor(f.x/g)*g;f.y=Math.floor(f.y/g)*g;var d=0,e=[f.clone()],h=f.clone(),l;do h=h.offset({x:0,y:g}),l=OpenLayers.Projection.transform(h.clone(),b,c),e.unshift(h);while(a.containsPixel(l)&&1E3>++d);h=f.clone();do h=h.offset({x:0,y:-g}),l=OpenLayers.Projection.transform(h.clone(),b,c),e.push(h);
+while(a.containsPixel(l)&&1E3>++d);d=0;k=[f.clone()];h=f.clone();do h=h.offset({x:-g,y:0}),l=OpenLayers.Projection.transform(h.clone(),b,c),k.unshift(h);while(a.containsPixel(l)&&1E3>++d);h=f.clone();do h=h.offset({x:g,y:0}),l=OpenLayers.Projection.transform(h.clone(),b,c),k.push(h);while(a.containsPixel(l)&&1E3>++d);g=[];for(d=0;d<k.length;++d){l=k[d].x;for(var f=[],m=null,n=Math.min(e[0].y,90),h=Math.max(e[e.length-1].y,-90),p=(n-h)/this.numPoints,n=h,h=0;h<=this.numPoints;++h){var q=new OpenLayers.Geometry.Point(l,
+n);q.transform(b,c);f.push(q);n+=p;q.y>=a.bottom&&!m&&(m=q)}this.labelled&&(m=new OpenLayers.Geometry.Point(m.x,a.bottom),l={value:l,label:this.labelled?OpenLayers.Util.getFormattedLonLat(l,"lon",this.labelFormat):"",labelAlign:"cb",xOffset:0,yOffset:2},this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(m,l)));f=new OpenLayers.Geometry.LineString(f);g.push(new OpenLayers.Feature.Vector(f))}for(h=0;h<e.length;++h)if(n=e[h].y,!(-90>n||90<n)){f=[];d=k[0].x;p=(k[k.length-1].x-d)/this.numPoints;
+l=d;m=null;for(d=0;d<=this.numPoints;++d)q=new OpenLayers.Geometry.Point(l,n),q.transform(b,c),f.push(q),l+=p,q.x<a.right&&(m=q);this.labelled&&(m=new OpenLayers.Geometry.Point(a.right,m.y),l={value:n,label:this.labelled?OpenLayers.Util.getFormattedLonLat(n,"lat",this.labelFormat):"",labelAlign:"rb",xOffset:-2,yOffset:2},this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(m,l)));f=new OpenLayers.Geometry.LineString(f);g.push(new OpenLayers.Feature.Vector(f))}this.gratLayer.addFeatures(g)}},CLASS_NAME:"OpenLayers.Control.Graticule"});OpenLayers.Console.warn("OpenLayers.Rico is deprecated");OpenLayers.Rico=OpenLayers.Rico||{};
+OpenLayers.Rico.Corner={round:function(a,b){a=OpenLayers.Util.getElement(a);this._setOptions(b);var c=this.options.color;"fromElement"==this.options.color&&(c=this._background(a));var d=this.options.bgColor;"fromParent"==this.options.bgColor&&(d=this._background(a.offsetParent));this._roundCornersImpl(a,c,d)},changeColor:function(a,b){a.style.backgroundColor=b;for(var c=a.parentNode.getElementsByTagName("span"),d=0;d<c.length;d++)c[d].style.backgroundColor=b},changeOpacity:function(a,b){var c="alpha(opacity="+
+100*b+")";a.style.opacity=b;a.style.filter=c;for(var d=a.parentNode.getElementsByTagName("span"),e=0;e<d.length;e++)d[e].style.opacity=b,d[e].style.filter=c},reRound:function(a,b){var c=a.parentNode.childNodes[2];a.parentNode.removeChild(a.parentNode.childNodes[0]);a.parentNode.removeChild(c);this.round(a.parentNode,b)},_roundCornersImpl:function(a,b,c){this.options.border&&this._renderBorder(a,c);this._isTopRounded()&&this._roundTopCorners(a,b,c);this._isBottomRounded()&&this._roundBottomCorners(a,
+b,c)},_renderBorder:function(a,b){var c="1px solid "+this._borderColor(b);a.innerHTML="<div "+("style='border-left: "+c+";"+("border-right: "+c)+"'")+">"+a.innerHTML+"</div>"},_roundTopCorners:function(a,b,c){for(var d=this._createCorner(c),e=0;e<this.options.numSlices;e++)d.appendChild(this._createCornerSlice(b,c,e,"top"));a.style.paddingTop=0;a.insertBefore(d,a.firstChild)},_roundBottomCorners:function(a,b,c){for(var d=this._createCorner(c),e=this.options.numSlices-1;0<=e;e--)d.appendChild(this._createCornerSlice(b,
+c,e,"bottom"));a.style.paddingBottom=0;a.appendChild(d)},_createCorner:function(a){var b=document.createElement("div");b.style.backgroundColor=this._isTransparent()?"transparent":a;return b},_createCornerSlice:function(a,b,c,d){var e=document.createElement("span"),f=e.style;f.backgroundColor=a;f.display="block";f.height="1px";f.overflow="hidden";f.fontSize="1px";a=this._borderColor(a,b);this.options.border&&0==c?(f.borderTopStyle="solid",f.borderTopWidth="1px",f.borderLeftWidth="0px",f.borderRightWidth=
+"0px",f.borderBottomWidth="0px",f.height="0px",f.borderColor=a):a&&(f.borderColor=a,f.borderStyle="solid",f.borderWidth="0px 1px");this.options.compact||c!=this.options.numSlices-1||(f.height="2px");this._setMargin(e,c,d);this._setBorder(e,c,d);return e},_setOptions:function(a){this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:!0,border:!1,compact:!1};OpenLayers.Util.extend(this.options,a||{});this.options.numSlices=this.options.compact?2:4;this._isTransparent()&&(this.options.blend=
+!1)},_whichSideTop:function(){return this._hasString(this.options.corners,"all","top")||0<=this.options.corners.indexOf("tl")&&0<=this.options.corners.indexOf("tr")?"":0<=this.options.corners.indexOf("tl")?"left":0<=this.options.corners.indexOf("tr")?"right":""},_whichSideBottom:function(){return this._hasString(this.options.corners,"all","bottom")||0<=this.options.corners.indexOf("bl")&&0<=this.options.corners.indexOf("br")?"":0<=this.options.corners.indexOf("bl")?"left":0<=this.options.corners.indexOf("br")?
+"right":""},_borderColor:function(a,b){return"transparent"==a?b:this.options.border?this.options.border:this.options.blend?this._blend(b,a):""},_setMargin:function(a,b,c){b=this._marginSize(b);c="top"==c?this._whichSideTop():this._whichSideBottom();"left"==c?(a.style.marginLeft=b+"px",a.style.marginRight="0px"):"right"==c?(a.style.marginRight=b+"px",a.style.marginLeft="0px"):(a.style.marginLeft=b+"px",a.style.marginRight=b+"px")},_setBorder:function(a,b,c){b=this._borderSize(b);c="top"==c?this._whichSideTop():
+this._whichSideBottom();"left"==c?(a.style.borderLeftWidth=b+"px",a.style.borderRightWidth="0px"):"right"==c?(a.style.borderRightWidth=b+"px",a.style.borderLeftWidth="0px"):(a.style.borderLeftWidth=b+"px",a.style.borderRightWidth=b+"px");!1!=this.options.border&&(a.style.borderLeftWidth=b+"px",a.style.borderRightWidth=b+"px")},_marginSize:function(a){if(this._isTransparent())return 0;var b=[5,3,2,1],c=[3,2,1,0],d=[2,1],e=[1,0];return this.options.compact&&this.options.blend?e[a]:this.options.compact?
+d[a]:this.options.blend?c[a]:b[a]},_borderSize:function(a){var b=[5,3,2,1],c=[2,1,1,1],d=[1,0],e=[0,2,0,0];return this.options.compact&&(this.options.blend||this._isTransparent())?1:this.options.compact?d[a]:this.options.blend?c[a]:this.options.border?e[a]:this._isTransparent()?b[a]:0},_hasString:function(a){for(var b=1;b<arguments.length;b++)if(0<=a.indexOf(arguments[b]))return!0;return!1},_blend:function(a,b){var c=OpenLayers.Rico.Color.createFromHex(a);c.blend(OpenLayers.Rico.Color.createFromHex(b));
+return c},_background:function(a){try{return OpenLayers.Rico.Color.createColorFromBackground(a).asHex()}catch(b){return"#ffffff"}},_isTransparent:function(){return"transparent"==this.options.color},_isTopRounded:function(){return this._hasString(this.options.corners,"all","top","tl","tr")},_isBottomRounded:function(){return this._hasString(this.options.corners,"all","bottom","bl","br")},_hasSingleTextChild:function(a){return 1==a.childNodes.length&&3==a.childNodes[0].nodeType}};OpenLayers.Control.NavigationHistory=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOGGLE,previous:null,previousOptions:null,next:null,nextOptions:null,limit:50,autoActivate:!0,clearOnDeactivate:!1,registry:null,nextStack:null,previousStack:null,listeners:null,restoring:!1,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,[a]);this.registry=OpenLayers.Util.extend({moveend:this.getState},this.registry);a={trigger:OpenLayers.Function.bind(this.previousTrigger,
+this),displayClass:this.displayClass+" "+this.displayClass+"Previous"};OpenLayers.Util.extend(a,this.previousOptions);this.previous=new OpenLayers.Control.Button(a);a={trigger:OpenLayers.Function.bind(this.nextTrigger,this),displayClass:this.displayClass+" "+this.displayClass+"Next"};OpenLayers.Util.extend(a,this.nextOptions);this.next=new OpenLayers.Control.Button(a);this.clear()},onPreviousChange:function(a,b){a&&!this.previous.active?this.previous.activate():!a&&this.previous.active&&this.previous.deactivate()},
+onNextChange:function(a,b){a&&!this.next.active?this.next.activate():!a&&this.next.active&&this.next.deactivate()},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this);this.previous.destroy();this.next.destroy();this.deactivate();for(var a in this)this[a]=null},setMap:function(a){this.map=a;this.next.setMap(a);this.previous.setMap(a)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.next.draw();this.previous.draw()},previousTrigger:function(){var a=this.previousStack.shift(),
+b=this.previousStack.shift();void 0!=b?(this.nextStack.unshift(a),this.previousStack.unshift(b),this.restoring=!0,this.restore(b),this.restoring=!1,this.onNextChange(this.nextStack[0],this.nextStack.length),this.onPreviousChange(this.previousStack[1],this.previousStack.length-1)):this.previousStack.unshift(a);return b},nextTrigger:function(){var a=this.nextStack.shift();void 0!=a&&(this.previousStack.unshift(a),this.restoring=!0,this.restore(a),this.restoring=!1,this.onNextChange(this.nextStack[0],
+this.nextStack.length),this.onPreviousChange(this.previousStack[1],this.previousStack.length-1));return a},clear:function(){this.previousStack=[];this.previous.deactivate();this.nextStack=[];this.next.deactivate()},getState:function(){return{center:this.map.getCenter(),resolution:this.map.getResolution(),projection:this.map.getProjectionObject(),units:this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units}},restore:function(a){var b,c;if(this.map.getProjectionObject()==
+a.projection)c=this.map.getZoomForResolution(a.resolution),b=a.center;else{b=a.center.clone();b.transform(a.projection,this.map.getProjectionObject());c=a.units;var d=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;c=this.map.getZoomForResolution((c&&d?OpenLayers.INCHES_PER_UNIT[c]/OpenLayers.INCHES_PER_UNIT[d]:1)*a.resolution)}this.map.setCenter(b,c)},setListeners:function(){this.listeners={};for(var a in this.registry)this.listeners[a]=OpenLayers.Function.bind(function(){if(!this.restoring){var b=
+this.registry[a].apply(this,arguments);this.previousStack.unshift(b);if(1<this.previousStack.length)this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);this.previousStack.length>this.limit+1&&this.previousStack.pop();0<this.nextStack.length&&(this.nextStack=[],this.onNextChange(null,0))}return!0},this)},activate:function(){var a=!1;if(this.map&&OpenLayers.Control.prototype.activate.apply(this)){null==this.listeners&&this.setListeners();for(var b in this.listeners)this.map.events.register(b,
+this,this.listeners[b]);a=!0;0==this.previousStack.length&&this.initStack()}return a},initStack:function(){this.map.getCenter()&&this.listeners.moveend()},deactivate:function(){var a=!1;if(this.map&&OpenLayers.Control.prototype.deactivate.apply(this)){for(var b in this.listeners)this.map.events.unregister(b,this,this.listeners[b]);this.clearOnDeactivate&&this.clear();a=!0}return a},CLASS_NAME:"OpenLayers.Control.NavigationHistory"});OpenLayers.Layer.UTFGrid=OpenLayers.Class(OpenLayers.Layer.XYZ,{isBaseLayer:!1,projection:new OpenLayers.Projection("EPSG:900913"),useJSONP:!1,tileClass:OpenLayers.Tile.UTFGrid,initialize:function(a){OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a.name,a.url,{},a]);this.tileOptions=OpenLayers.Util.extend({utfgridResolution:this.utfgridResolution},this.tileOptions)},createBackBuffer:function(){},clone:function(a){null==a&&(a=new OpenLayers.Layer.UTFGrid(this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,
+[a])},getFeatureInfo:function(a){var b=null;(a=this.getTileData(a))&&a.tile&&(b=a.tile.getFeatureInfo(a.i,a.j));return b},getFeatureId:function(a){var b=null;a=this.getTileData(a);a.tile&&(b=a.tile.getFeatureId(a.i,a.j));return b},CLASS_NAME:"OpenLayers.Layer.UTFGrid"});OpenLayers.TileManager=OpenLayers.Class({cacheSize:256,tilesPerFrame:2,frameDelay:16,moveDelay:100,zoomDelay:200,maps:null,tileQueueId:null,tileQueue:null,tileCache:null,tileCacheIndex:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.maps=[];this.tileQueueId={};this.tileQueue={};this.tileCache={};this.tileCacheIndex=[]},addMap:function(a){if(!this._destroyed&&OpenLayers.Layer.Grid){this.maps.push(a);this.tileQueue[a.id]=[];for(var b=0,c=a.layers.length;b<c;++b)this.addLayer({layer:a.layers[b]});
+a.events.on({move:this.move,zoomend:this.zoomEnd,changelayer:this.changeLayer,addlayer:this.addLayer,preremovelayer:this.removeLayer,scope:this})}},removeMap:function(a){if(!this._destroyed&&OpenLayers.Layer.Grid){window.clearTimeout(this.tileQueueId[a.id]);if(a.layers)for(var b=0,c=a.layers.length;b<c;++b)this.removeLayer({layer:a.layers[b]});a.events&&a.events.un({move:this.move,zoomend:this.zoomEnd,changelayer:this.changeLayer,addlayer:this.addLayer,preremovelayer:this.removeLayer,scope:this});
+delete this.tileQueue[a.id];delete this.tileQueueId[a.id];OpenLayers.Util.removeItem(this.maps,a)}},move:function(a){this.updateTimeout(a.object,this.moveDelay,!0)},zoomEnd:function(a){this.updateTimeout(a.object,this.zoomDelay)},changeLayer:function(a){"visibility"!==a.property&&"params"!==a.property||this.updateTimeout(a.object,0)},addLayer:function(a){a=a.layer;if(a instanceof OpenLayers.Layer.Grid){a.events.on({addtile:this.addTile,retile:this.clearTileQueue,scope:this});var b,c,d;for(b=a.grid.length-
+1;0<=b;--b)for(c=a.grid[b].length-1;0<=c;--c)d=a.grid[b][c],this.addTile({tile:d}),d.url&&!d.imgDiv&&this.manageTileCache({object:d})}},removeLayer:function(a){a=a.layer;if(a instanceof OpenLayers.Layer.Grid&&(this.clearTileQueue({object:a}),a.events&&a.events.un({addtile:this.addTile,retile:this.clearTileQueue,scope:this}),a.grid)){var b,c,d;for(b=a.grid.length-1;0<=b;--b)for(c=a.grid[b].length-1;0<=c;--c)d=a.grid[b][c],this.unloadTile({object:d})}},updateTimeout:function(a,b,c){window.clearTimeout(this.tileQueueId[a.id]);
+var d=this.tileQueue[a.id];if(!c||d.length)this.tileQueueId[a.id]=window.setTimeout(OpenLayers.Function.bind(function(){this.drawTilesFromQueue(a);d.length&&this.updateTimeout(a,this.frameDelay)},this),b)},addTile:function(a){if(a.tile instanceof OpenLayers.Tile.Image)a.tile.events.on({beforedraw:this.queueTileDraw,beforeload:this.manageTileCache,loadend:this.addToCache,unload:this.unloadTile,scope:this});else this.removeLayer({layer:a.tile.layer})},unloadTile:function(a){a=a.object;a.events.un({beforedraw:this.queueTileDraw,
+beforeload:this.manageTileCache,loadend:this.addToCache,unload:this.unloadTile,scope:this});OpenLayers.Util.removeItem(this.tileQueue[a.layer.map.id],a)},queueTileDraw:function(a){a=a.object;var b=!1,c=a.layer,d=c.getURL(a.bounds),e=this.tileCache[d];e&&"olTileImage"!==e.className&&(delete this.tileCache[d],OpenLayers.Util.removeItem(this.tileCacheIndex,d),e=null);!c.url||!c.async&&e||(b=this.tileQueue[c.map.id],~OpenLayers.Util.indexOf(b,a)||b.push(a),b=!0);return!b},drawTilesFromQueue:function(a){var b=
+this.tileQueue[a.id],c=this.tilesPerFrame;for(a=a.zoomTween&&a.zoomTween.playing;!a&&b.length&&c;)b.shift().draw(!0),--c},manageTileCache:function(a){a=a.object;var b=this.tileCache[a.url];b&&(b.parentNode&&OpenLayers.Element.hasClass(b.parentNode,"olBackBuffer")&&(b.parentNode.removeChild(b),b.id=null),b.parentNode||(b.style.visibility="hidden",b.style.opacity=0,a.setImage(b),OpenLayers.Util.removeItem(this.tileCacheIndex,a.url),this.tileCacheIndex.push(a.url)))},addToCache:function(a){a=a.object;
+this.tileCache[a.url]||OpenLayers.Element.hasClass(a.imgDiv,"olImageLoadError")||(this.tileCacheIndex.length>=this.cacheSize&&(delete this.tileCache[this.tileCacheIndex[0]],this.tileCacheIndex.shift()),this.tileCache[a.url]=a.imgDiv,this.tileCacheIndex.push(a.url))},clearTileQueue:function(a){a=a.object;for(var b=this.tileQueue[a.map.id],c=b.length-1;0<=c;--c)b[c].layer===a&&b.splice(c,1)},destroy:function(){for(var a=this.maps.length-1;0<=a;--a)this.removeMap(this.maps[a]);this.tileCacheIndex=this.tileCache=
+this.tileQueueId=this.tileQueue=this.maps=null;this._destroyed=!0}});OpenLayers.Layer.ArcGISCache=OpenLayers.Class(OpenLayers.Layer.XYZ,{url:null,tileOrigin:null,tileSize:new OpenLayers.Size(256,256),useArcGISServer:!0,type:"png",useScales:!1,overrideDPI:!1,initialize:function(a,b,c){OpenLayers.Layer.XYZ.prototype.initialize.apply(this,arguments);this.resolutions&&(this.serverResolutions=this.resolutions,this.maxExtent=this.getMaxExtentForResolution(this.resolutions[0]));if(this.layerInfo){var d=this.layerInfo,e=new OpenLayers.Bounds(d.fullExtent.xmin,d.fullExtent.ymin,
+d.fullExtent.xmax,d.fullExtent.ymax);this.projection="EPSG:"+d.spatialReference.wkid;this.sphericalMercator=102100==d.spatialReference.wkid;this.units="esriFeet"==d.units?"ft":"m";if(d.tileInfo){this.tileSize=new OpenLayers.Size(d.tileInfo.width||d.tileInfo.cols,d.tileInfo.height||d.tileInfo.rows);this.tileOrigin=new OpenLayers.LonLat(d.tileInfo.origin.x,d.tileInfo.origin.y);var f=new OpenLayers.Geometry.Point(e.left,e.top),e=new OpenLayers.Geometry.Point(e.right,e.bottom);this.useScales?this.scales=
+[]:this.resolutions=[];this.lods=[];for(var g in d.tileInfo.lods)if(d.tileInfo.lods.hasOwnProperty(g)){var h=d.tileInfo.lods[g];this.useScales?this.scales.push(h.scale):this.resolutions.push(h.resolution);var k=this.getContainingTileCoords(f,h.resolution);h.startTileCol=k.x;h.startTileRow=k.y;k=this.getContainingTileCoords(e,h.resolution);h.endTileCol=k.x;h.endTileRow=k.y;this.lods.push(h)}this.maxExtent=this.calculateMaxExtentWithLOD(this.lods[0]);this.serverResolutions=this.resolutions;this.overrideDPI&&
+d.tileInfo.dpi&&(OpenLayers.DOTS_PER_INCH=d.tileInfo.dpi)}}},getContainingTileCoords:function(a,b){return new OpenLayers.Pixel(Math.max(Math.floor((a.x-this.tileOrigin.lon)/(this.tileSize.w*b)),0),Math.max(Math.floor((this.tileOrigin.lat-a.y)/(this.tileSize.h*b)),0))},calculateMaxExtentWithLOD:function(a){var b=this.tileOrigin.lon+a.startTileCol*this.tileSize.w*a.resolution,c=this.tileOrigin.lat-a.startTileRow*this.tileSize.h*a.resolution;return new OpenLayers.Bounds(b,c-(a.endTileRow-a.startTileRow+
+1)*this.tileSize.h*a.resolution,b+(a.endTileCol-a.startTileCol+1)*this.tileSize.w*a.resolution,c)},calculateMaxExtentWithExtent:function(a,b){var c=new OpenLayers.Geometry.Point(a.left,a.top),d=new OpenLayers.Geometry.Point(a.right,a.bottom),c=this.getContainingTileCoords(c,b),d=this.getContainingTileCoords(d,b);return this.calculateMaxExtentWithLOD({resolution:b,startTileCol:c.x,startTileRow:c.y,endTileCol:d.x,endTileRow:d.y})},getUpperLeftTileCoord:function(a){var b=new OpenLayers.Geometry.Point(this.maxExtent.left,
+this.maxExtent.top);return this.getContainingTileCoords(b,a)},getLowerRightTileCoord:function(a){var b=new OpenLayers.Geometry.Point(this.maxExtent.right,this.maxExtent.bottom);return this.getContainingTileCoords(b,a)},getMaxExtentForResolution:function(a){var b=this.getUpperLeftTileCoord(a),c=this.getLowerRightTileCoord(a),d=this.tileOrigin.lon+b.x*this.tileSize.w*a,e=this.tileOrigin.lat-b.y*this.tileSize.h*a;return new OpenLayers.Bounds(d,e-(c.y-b.y+1)*this.tileSize.h*a,d+(c.x-b.x+1)*this.tileSize.w*
+a,e)},clone:function(a){null==a&&(a=new OpenLayers.Layer.ArcGISCache(this.name,this.url,this.options));return OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},initGriddedTiles:function(a){delete this._tileOrigin;OpenLayers.Layer.XYZ.prototype.initGriddedTiles.apply(this,arguments)},getMaxExtent:function(){var a=this.map.getResolution();return this.maxExtent=this.getMaxExtentForResolution(a)},getTileOrigin:function(){if(!this._tileOrigin){var a=this.getMaxExtent();this._tileOrigin=new OpenLayers.LonLat(a.left,
+a.bottom)}return this._tileOrigin},getURL:function(a){var b=this.getResolution(),c=this.tileOrigin.lon+b*this.tileSize.w/2,d=this.tileOrigin.lat-b*this.tileSize.h/2;a=a.getCenterLonLat();c=Math.round(Math.abs((a.lon-c)/(b*this.tileSize.w)));d=Math.round(Math.abs((d-a.lat)/(b*this.tileSize.h)));a=this.map.getZoom();if(this.lods){if(b=this.lods[this.map.getZoom()],c<b.startTileCol||c>b.endTileCol||d<b.startTileRow||d>b.endTileRow)return null}else{var e=this.getUpperLeftTileCoord(b),b=this.getLowerRightTileCoord(b);
+if(c<e.x||c>=b.x||d<e.y||d>=b.y)return null}b=this.url;e=""+c+d+a;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(e,b));this.useArcGISServer?b+="/tile/${z}/${y}/${x}":(c="C"+OpenLayers.Number.zeroPad(c,8,16),d="R"+OpenLayers.Number.zeroPad(d,8,16),a="L"+OpenLayers.Number.zeroPad(a,2,10),b=b+"/${z}/${y}/${x}."+this.type);b=OpenLayers.String.format(b,{x:c,y:d,z:a});return OpenLayers.Util.urlAppend(b,OpenLayers.Util.getParameterString(this.params))},CLASS_NAME:"OpenLayers.Layer.ArcGISCache"});OpenLayers.Control.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:!1,drillDown:!1,maxFeatures:10,clickCallback:"click",output:"features",layers:null,queryVisible:!1,url:null,layerUrls:null,infoFormat:"text/html",vendorParams:{},format:null,formatOptions:null,handler:null,hoverRequest:null,initialize:function(a){a=a||{};a.handlerOptions=a.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[a]);this.format||(this.format=new OpenLayers.Format.WMSGetFeatureInfo(a.formatOptions));
+!0===this.drillDown&&(this.hover=!1);this.hover?this.handler=new OpenLayers.Handler.Hover(this,{move:this.cancelHover,pause:this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{delay:250})):(a={},a[this.clickCallback]=this.getInfoForClick,this.handler=new OpenLayers.Handler.Click(this,a,this.handlerOptions.click||{}))},getInfoForClick:function(a){this.events.triggerEvent("beforegetfeatureinfo",{xy:a.xy});OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");this.request(a.xy,
+{})},getInfoForHover:function(a){this.events.triggerEvent("beforegetfeatureinfo",{xy:a.xy});this.request(a.xy,{hover:!0})},cancelHover:function(){this.hoverRequest&&(this.hoverRequest.abort(),this.hoverRequest=null)},findLayers:function(){for(var a=this.layers||this.map.layers,b=[],c,d,e=a.length-1;0<=e;--e)c=a[e],c instanceof OpenLayers.Layer.WMS&&(!this.queryVisible||c.getVisibility())&&(d=OpenLayers.Util.isArray(c.url)?c.url[0]:c.url,!1!==this.drillDown||this.url||(this.url=d),(!0===this.drillDown||
+this.urlMatches(d))&&b.push(c));return b},urlMatches:function(a){var b=OpenLayers.Util.isEquivalentUrl(this.url,a);if(!b&&this.layerUrls)for(var c=0,d=this.layerUrls.length;c<d;++c)if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[c],a)){b=!0;break}return b},buildWMSOptions:function(a,b,c,d){for(var e=[],f=[],g=0,h=b.length;g<h;g++)null!=b[g].params.LAYERS&&(e=e.concat(b[g].params.LAYERS),f=f.concat(this.getStyleNames(b[g])));b=b[0];g=this.map.getProjection();(h=b.projection)&&h.equals(this.map.getProjectionObject())&&
+(g=h.getCode());d=OpenLayers.Util.extend({service:"WMS",version:b.params.VERSION,request:"GetFeatureInfo",exceptions:b.params.EXCEPTIONS,bbox:this.map.getExtent().toBBOX(null,b.reverseAxisOrder()),feature_count:this.maxFeatures,height:this.map.getSize().h,width:this.map.getSize().w,format:d,info_format:b.params.INFO_FORMAT||this.infoFormat},1.3<=parseFloat(b.params.VERSION)?{crs:g,i:parseInt(c.x),j:parseInt(c.y)}:{srs:g,x:parseInt(c.x),y:parseInt(c.y)});0!=e.length&&(d=OpenLayers.Util.extend({layers:e,
+query_layers:e,styles:f},d));OpenLayers.Util.applyDefaults(d,this.vendorParams);return{url:a,params:OpenLayers.Util.upperCaseObject(d),callback:function(b){this.handleResponse(c,b,a)},scope:this}},getStyleNames:function(a){return a.params.STYLES?a.params.STYLES:OpenLayers.Util.isArray(a.params.LAYERS)?Array(a.params.LAYERS.length):a.params.LAYERS.replace(/[^,]/g,"")},request:function(a,b){var c=this.findLayers();if(0==c.length)this.events.triggerEvent("nogetfeatureinfo"),OpenLayers.Element.removeClass(this.map.viewPortDiv,
+"olCursorWait");else if(b=b||{},!1===this.drillDown){var c=this.buildWMSOptions(this.url,c,a,c[0].params.FORMAT),d=OpenLayers.Request.GET(c);!0===b.hover&&(this.hoverRequest=d)}else{this._numRequests=this._requestCount=0;this.features=[];for(var d={},e,f=0,g=c.length;f<g;f++){var h=c[f];e=OpenLayers.Util.isArray(h.url)?h.url[0]:h.url;e in d?d[e].push(h):(this._numRequests++,d[e]=[h])}for(e in d)c=d[e],c=this.buildWMSOptions(e,c,a,c[0].params.FORMAT),OpenLayers.Request.GET(c)}},triggerGetFeatureInfo:function(a,
+b,c){this.events.triggerEvent("getfeatureinfo",{text:a.responseText,features:c,request:a,xy:b});OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait")},handleResponse:function(a,b,c){var d=b.responseXML;d&&d.documentElement||(d=b.responseText);d=this.format.read(d);!1===this.drillDown?this.triggerGetFeatureInfo(b,a,d):(this._requestCount++,this._features="object"===this.output?(this._features||[]).concat({url:c,features:d}):(this._features||[]).concat(d),this._requestCount===this._numRequests&&
+(this.triggerGetFeatureInfo(b,a,this._features.concat()),delete this._features,delete this._requestCount,delete this._numRequests))},CLASS_NAME:"OpenLayers.Control.WMSGetFeatureInfo"});OpenLayers.Format.WMSCapabilities.v1_3_0=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_3,{version:"1.3.0",CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_3_0"});OpenLayers.Format.SOSGetFeatureOfInterest=OpenLayers.Class(OpenLayers.Format.XML,{VERSION:"1.0.0",namespaces:{sos:"http://www.opengis.net/sos/1.0",gml:"http://www.opengis.net/gml",sa:"http://www.opengis.net/sampling/1.0",xsi:"http://www.w3.org/2001/XMLSchema-instance"},schemaLocation:"http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd",defaultPrefix:"sos",regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},read:function(a){"string"==
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={features:[]};this.readNode(a,b);a=[];for(var c=0,d=b.features.length;c<d;c++){var e=b.features[c];this.internalProjection&&(this.externalProjection&&e.components[0])&&e.components[0].transform(this.externalProjection,this.internalProjection);e=new OpenLayers.Feature.Vector(e.components[0],e.attributes);a.push(e)}return a},readers:{sa:{SamplingPoint:function(a,b){if(!b.attributes){var c=
+{attributes:{}};b.features.push(c);b=c}b.attributes.id=this.getAttributeNS(a,this.namespaces.gml,"id");this.readChildNodes(a,b)},position:function(a,b){this.readChildNodes(a,b)}},gml:OpenLayers.Util.applyDefaults({FeatureCollection:function(a,b){this.readChildNodes(a,b)},featureMember:function(a,b){var c={attributes:{}};b.features.push(c);this.readChildNodes(a,c)},name:function(a,b){b.attributes.name=this.getChildValue(a)},pos:function(a,b){this.externalProjection||(this.externalProjection=new OpenLayers.Projection(a.getAttribute("srsName")));
+OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(this,[a,b])}},OpenLayers.Format.GML.v3.prototype.readers.gml)},writers:{sos:{GetFeatureOfInterest:function(a){for(var b=this.createElementNSPlus("GetFeatureOfInterest",{attributes:{version:this.VERSION,service:"SOS","xsi:schemaLocation":this.schemaLocation}}),c=0,d=a.fois.length;c<d;c++)this.writeNode("FeatureOfInterestId",{foi:a.fois[c]},b);return b},FeatureOfInterestId:function(a){return this.createElementNSPlus("FeatureOfInterestId",{value:a.foi})}}},
+CLASS_NAME:"OpenLayers.Format.SOSGetFeatureOfInterest"});OpenLayers.Format.SOSGetObservation=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ows:"http://www.opengis.net/ows",gml:"http://www.opengis.net/gml",sos:"http://www.opengis.net/sos/1.0",ogc:"http://www.opengis.net/ogc",om:"http://www.opengis.net/om/1.0",sa:"http://www.opengis.net/sampling/1.0",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd",
+defaultPrefix:"sos",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={measurements:[],observations:[]};this.readNode(a,b);return b},write:function(a){a=this.writeNode("sos:GetObservation",a);a.setAttribute("xmlns:om",this.namespaces.om);a.setAttribute("xmlns:ogc",this.namespaces.ogc);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,
+[a])},readers:{om:{ObservationCollection:function(a,b){b.id=this.getAttributeNS(a,this.namespaces.gml,"id");this.readChildNodes(a,b)},member:function(a,b){this.readChildNodes(a,b)},Measurement:function(a,b){var c={};b.measurements.push(c);this.readChildNodes(a,c)},Observation:function(a,b){var c={};b.observations.push(c);this.readChildNodes(a,c)},samplingTime:function(a,b){var c={};b.samplingTime=c;this.readChildNodes(a,c)},observedProperty:function(a,b){b.observedProperty=this.getAttributeNS(a,this.namespaces.xlink,
+"href");this.readChildNodes(a,b)},procedure:function(a,b){b.procedure=this.getAttributeNS(a,this.namespaces.xlink,"href");this.readChildNodes(a,b)},featureOfInterest:function(a,b){var c={features:[]};b.fois=[];b.fois.push(c);this.readChildNodes(a,c);for(var d=[],e=0,f=c.features.length;e<f;e++){var g=c.features[e];d.push(new OpenLayers.Feature.Vector(g.components[0],g.attributes))}c.features=d},result:function(a,b){var c={};b.result=c;""!==this.getChildValue(a)?(c.value=this.getChildValue(a),c.uom=
+a.getAttribute("uom")):this.readChildNodes(a,c)}},sa:OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.sa,gml:OpenLayers.Util.applyDefaults({TimeInstant:function(a,b){var c={};b.timeInstant=c;this.readChildNodes(a,c)},timePosition:function(a,b){b.timePosition=this.getChildValue(a)}},OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.gml)},writers:{sos:{GetObservation:function(a){var b=this.createElementNSPlus("GetObservation",{attributes:{version:this.VERSION,service:"SOS"}});this.writeNode("offering",
+a,b);a.eventTime&&this.writeNode("eventTime",a,b);for(var c in a.procedures)this.writeNode("procedure",a.procedures[c],b);for(var d in a.observedProperties)this.writeNode("observedProperty",a.observedProperties[d],b);a.foi&&this.writeNode("featureOfInterest",a.foi,b);this.writeNode("responseFormat",a,b);a.resultModel&&this.writeNode("resultModel",a,b);a.responseMode&&this.writeNode("responseMode",a,b);return b},featureOfInterest:function(a){var b=this.createElementNSPlus("featureOfInterest");this.writeNode("ObjectID",
+a.objectId,b);return b},ObjectID:function(a){return this.createElementNSPlus("ObjectID",{value:a})},responseFormat:function(a){return this.createElementNSPlus("responseFormat",{value:a.responseFormat})},procedure:function(a){return this.createElementNSPlus("procedure",{value:a})},offering:function(a){return this.createElementNSPlus("offering",{value:a.offering})},observedProperty:function(a){return this.createElementNSPlus("observedProperty",{value:a})},eventTime:function(a){var b=this.createElementNSPlus("eventTime");
+"latest"===a.eventTime&&this.writeNode("ogc:TM_Equals",a,b);return b},resultModel:function(a){return this.createElementNSPlus("resultModel",{value:a.resultModel})},responseMode:function(a){return this.createElementNSPlus("responseMode",{value:a.responseMode})}},ogc:{TM_Equals:function(a){var b=this.createElementNSPlus("ogc:TM_Equals");this.writeNode("ogc:PropertyName",{property:"urn:ogc:data:time:iso8601"},b);"latest"===a.eventTime&&this.writeNode("gml:TimeInstant",{value:"latest"},b);return b},PropertyName:function(a){return this.createElementNSPlus("ogc:PropertyName",
+{value:a.property})}},gml:{TimeInstant:function(a){var b=this.createElementNSPlus("gml:TimeInstant");this.writeNode("gml:timePosition",a,b);return b},timePosition:function(a){return this.createElementNSPlus("gml:timePosition",{value:a.value})}}},CLASS_NAME:"OpenLayers.Format.SOSGetObservation"});OpenLayers.Control.UTFGrid=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,layers:null,defaultHandlerOptions:{delay:300,pixelTolerance:4,stopMove:!1,single:!0,"double":!1,stopSingle:!1,stopDouble:!1},handlerMode:"click",setHandler:function(a){this.handlerMode=a;this.resetHandler()},resetHandler:function(){this.handler&&(this.handler.deactivate(),this.handler.destroy(),this.handler=null);"hover"==this.handlerMode?this.handler=new OpenLayers.Handler.Hover(this,{pause:this.handleEvent,move:this.reset},
+this.handlerOptions):"click"==this.handlerMode?this.handler=new OpenLayers.Handler.Click(this,{click:this.handleEvent},this.handlerOptions):"move"==this.handlerMode&&(this.handler=new OpenLayers.Handler.Hover(this,{pause:this.handleEvent,move:this.handleEvent},this.handlerOptions));return this.handler?!0:!1},initialize:function(a){a=a||{};a.handlerOptions=a.handlerOptions||this.defaultHandlerOptions;OpenLayers.Control.prototype.initialize.apply(this,[a]);this.resetHandler()},handleEvent:function(a){if(null==
+a)this.reset();else{var b=this.map.getLonLatFromPixel(a.xy);if(b){var c=this.findLayers();if(0<c.length){for(var d={},e,f,g=0,h=c.length;g<h;g++)e=c[g],f=OpenLayers.Util.indexOf(this.map.layers,e),d[f]=e.getFeatureInfo(b);this.callback(d,b,a.xy)}}}},callback:function(a){},reset:function(a){this.callback(null)},findLayers:function(){for(var a=this.layers||this.map.layers,b=[],c,d=a.length-1;0<=d;--d)c=a[d],c instanceof OpenLayers.Layer.UTFGrid&&b.push(c);return b},CLASS_NAME:"OpenLayers.Control.UTFGrid"});OpenLayers.Format.CQL=function(){function a(a){function b(){var a=e.pop();switch(a.type){case "LOGICAL":var c=b(),g=b();return new OpenLayers.Filter.Logical({filters:[g,c],type:f[a.text.toUpperCase()]});case "NOT":return a=b(),new OpenLayers.Filter.Logical({filters:[a],type:OpenLayers.Filter.Logical.NOT});case "BETWEEN":return e.pop(),g=b(),a=b(),c=b(),new OpenLayers.Filter.Comparison({property:c,lowerBoundary:a,upperBoundary:g,type:OpenLayers.Filter.Comparison.BETWEEN});case "COMPARISON":return g=
+b(),c=b(),new OpenLayers.Filter.Comparison({property:c,value:g,type:d[a.text.toUpperCase()]});case "IS_NULL":return c=b(),new OpenLayers.Filter.Comparison({property:c,type:d[a.text.toUpperCase()]});case "VALUE":return(c=a.text.match(/^'(.*)'$/))?c[1].replace(/''/g,"'"):Number(a.text);case "SPATIAL":switch(a.text.toUpperCase()){case "BBOX":var a=b(),c=b(),g=b(),h=b(),k=b();return new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,property:k,value:OpenLayers.Bounds.fromArray([h,g,c,
+a])});case "INTERSECTS":return g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.INTERSECTS,property:c,value:g});case "WITHIN":return g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.WITHIN,property:c,value:g});case "CONTAINS":return g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.CONTAINS,property:c,value:g});case "DWITHIN":return a=b(),g=b(),c=b(),new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.DWITHIN,value:g,
+property:c,distance:Number(a)})}case "GEOMETRY":return OpenLayers.Geometry.fromWKT(a.text);default:return a.text}}for(var c=[],e=[];a.length;){var g=a.shift();switch(g.type){case "PROPERTY":case "GEOMETRY":case "VALUE":e.push(g);break;case "COMPARISON":case "BETWEEN":case "IS_NULL":case "LOGICAL":for(var k=h[g.type];0<c.length&&h[c[c.length-1].type]<=k;)e.push(c.pop());c.push(g);break;case "SPATIAL":case "NOT":case "LPAREN":c.push(g);break;case "RPAREN":for(;0<c.length&&"LPAREN"!=c[c.length-1].type;)e.push(c.pop());
+c.pop();0<c.length&&"SPATIAL"==c[c.length-1].type&&e.push(c.pop());case "COMMA":case "END":break;default:throw Error("Unknown token type "+g.type);}}for(;0<c.length;)e.push(c.pop());a=b();if(0<e.length){a="Remaining tokens after building AST: \n";for(c=e.length-1;0<=c;c--)a+=e[c].type+": "+e[c].text+"\n";throw Error(a);}return a}var b={PROPERTY:/^[_a-zA-Z]\w*/,COMPARISON:/^(=|<>|<=|<|>=|>|LIKE)/i,IS_NULL:/^IS NULL/i,COMMA:/^,/,LOGICAL:/^(AND|OR)/i,VALUE:/^('([^']|'')*'|\d+(\.\d*)?|\.\d+)/,LPAREN:/^\(/,
+RPAREN:/^\)/,SPATIAL:/^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i,NOT:/^NOT/i,BETWEEN:/^BETWEEN/i,GEOMETRY:function(a){var b=/^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(a);if(b){var c=a.length,b=a.indexOf("(",b[0].length);if(-1<b)for(var d=1;b<c&&0<d;)switch(b++,a.charAt(b)){case "(":d++;break;case ")":d--}return[a.substr(0,b+1)]}},END:/^$/},c={LPAREN:["GEOMETRY","SPATIAL","PROPERTY","VALUE","LPAREN"],RPAREN:["NOT","LOGICAL","END","RPAREN"],PROPERTY:["COMPARISON",
+"BETWEEN","COMMA","IS_NULL"],BETWEEN:["VALUE"],IS_NULL:["END"],COMPARISON:["VALUE"],COMMA:["GEOMETRY","VALUE","PROPERTY"],VALUE:["LOGICAL","COMMA","RPAREN","END"],SPATIAL:["LPAREN"],LOGICAL:["NOT","VALUE","SPATIAL","PROPERTY","LPAREN"],NOT:["PROPERTY","LPAREN"],GEOMETRY:["COMMA","RPAREN"]},d={"=":OpenLayers.Filter.Comparison.EQUAL_TO,"<>":OpenLayers.Filter.Comparison.NOT_EQUAL_TO,"<":OpenLayers.Filter.Comparison.LESS_THAN,"<=":OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,">":OpenLayers.Filter.Comparison.GREATER_THAN,
+">=":OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,LIKE:OpenLayers.Filter.Comparison.LIKE,BETWEEN:OpenLayers.Filter.Comparison.BETWEEN,"IS NULL":OpenLayers.Filter.Comparison.IS_NULL},e={},f={AND:OpenLayers.Filter.Logical.AND,OR:OpenLayers.Filter.Logical.OR},g={},h={RPAREN:3,LOGICAL:2,COMPARISON:1},k;for(k in d)d.hasOwnProperty(k)&&(e[d[k]]=k);for(k in f)f.hasOwnProperty(k)&&(g[f[k]]=k);return OpenLayers.Class(OpenLayers.Format,{read:function(d){var e=d;d=[];var f,g=["NOT","GEOMETRY","SPATIAL",
+"PROPERTY","LPAREN"];do{a:{f=g;for(var h=void 0,g=void 0,k=f.length,h=0;h<k;h++){var g=f[h],s=b[g]instanceof RegExp?b[g].exec(e):(0,b[g])(e);if(s){f=s[0];e=e.substr(f.length).replace(/^\s*/,"");f={type:g,text:f,remainder:e};break a}}d="ERROR: In parsing: ["+e+"], expected one of: ";for(h=0;h<k;h++)g=f[h],d+="\n "+g+": "+b[g];throw Error(d);}e=f.remainder;g=c[f.type];if("END"!=f.type&&!g)throw Error("No follows list for "+f.type);d.push(f)}while("END"!=f.type);d=a(d);this.keepData&&(this.data=d);
+return d},write:function(a){if(a instanceof OpenLayers.Geometry)return a.toString();switch(a.CLASS_NAME){case "OpenLayers.Filter.Spatial":switch(a.type){case OpenLayers.Filter.Spatial.BBOX:return"BBOX("+a.property+","+a.value.toBBOX()+")";case OpenLayers.Filter.Spatial.DWITHIN:return"DWITHIN("+a.property+", "+this.write(a.value)+", "+a.distance+")";case OpenLayers.Filter.Spatial.WITHIN:return"WITHIN("+a.property+", "+this.write(a.value)+")";case OpenLayers.Filter.Spatial.INTERSECTS:return"INTERSECTS("+
+a.property+", "+this.write(a.value)+")";case OpenLayers.Filter.Spatial.CONTAINS:return"CONTAINS("+a.property+", "+this.write(a.value)+")";default:throw Error("Unknown spatial filter type: "+a.type);}case "OpenLayers.Filter.Logical":if(a.type==OpenLayers.Filter.Logical.NOT)return"NOT ("+this.write(a.filters[0])+")";for(var b="(",c=!0,d=0;d<a.filters.length;d++)c?c=!1:b+=") "+g[a.type]+" (",b+=this.write(a.filters[d]);return b+")";case "OpenLayers.Filter.Comparison":return a.type==OpenLayers.Filter.Comparison.BETWEEN?
+a.property+" BETWEEN "+this.write(a.lowerBoundary)+" AND "+this.write(a.upperBoundary):null!==a.value?a.property+" "+e[a.type]+" "+this.write(a.value):a.property+" "+e[a.type];case void 0:if("string"===typeof a)return"'"+a.replace(/'/g,"''")+"'";if("number"===typeof a)return String(a);default:throw Error("Can't encode: "+a.CLASS_NAME+" "+a);}},CLASS_NAME:"OpenLayers.Format.CQL"})}();OpenLayers.Control.Split=OpenLayers.Class(OpenLayers.Control,{layer:null,source:null,sourceOptions:null,tolerance:null,edge:!0,deferDelete:!1,mutual:!0,targetFilter:null,sourceFilter:null,handler:null,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,[a]);this.options=a||{};this.options.source&&this.setSource(this.options.source)},setSource:function(a){this.active?(this.deactivate(),this.handler&&(this.handler.destroy(),delete this.handler),this.source=a,this.activate()):this.source=
+a},activate:function(){var a=OpenLayers.Control.prototype.activate.call(this);if(a)if(!this.source)this.handler||(this.handler=new OpenLayers.Handler.Path(this,{done:function(a){this.onSketchComplete({feature:new OpenLayers.Feature.Vector(a)})}},{layerOptions:this.sourceOptions})),this.handler.activate();else if(this.source.events)this.source.events.on({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});return a},deactivate:function(){var a=OpenLayers.Control.prototype.deactivate.call(this);
+a&&this.source&&this.source.events&&this.source.events.un({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});return a},onSketchComplete:function(a){this.feature=null;return!this.considerSplit(a.feature)},afterFeatureModified:function(a){a.modified&&"function"===typeof a.feature.geometry.split&&(this.feature=a.feature,this.considerSplit(a.feature))},removeByGeometry:function(a,b){for(var c=0,d=a.length;c<d;++c)if(a[c].geometry===b){a.splice(c,1);break}},
+isEligible:function(a){return a.geometry?a.state!==OpenLayers.State.DELETE&&"function"===typeof a.geometry.split&&this.feature!==a&&(!this.targetFilter||this.targetFilter.evaluate(a.attributes)):!1},considerSplit:function(a){var b=!1,c=!1;if(!this.sourceFilter||this.sourceFilter.evaluate(a.attributes)){for(var d=this.layer&&this.layer.features||[],e,f,g=[],h=[],k=this.layer===this.source&&this.mutual,l={edge:this.edge,tolerance:this.tolerance,mutual:k},m=[a.geometry],n,p,q,r=0,s=d.length;r<s;++r)if(n=
+d[r],this.isEligible(n)){p=[n.geometry];for(var t=0;t<m.length;++t){q=m[t];for(var u=0;u<p.length;++u)if(e=p[u],q.getBounds().intersectsBounds(e.getBounds())&&(e=q.split(e,l)))f=this.events.triggerEvent("beforesplit",{source:a,target:n}),!1!==f&&(k&&(f=e[0],1<f.length&&(f.unshift(t,1),Array.prototype.splice.apply(m,f),t+=f.length-3),e=e[1]),1<e.length&&(e.unshift(u,1),Array.prototype.splice.apply(p,e),u+=e.length-3))}p&&1<p.length&&(this.geomsToFeatures(n,p),this.events.triggerEvent("split",{original:n,
+features:p}),Array.prototype.push.apply(g,p),h.push(n),c=!0)}m&&1<m.length&&(this.geomsToFeatures(a,m),this.events.triggerEvent("split",{original:a,features:m}),Array.prototype.push.apply(g,m),h.push(a),b=!0);if(b||c){if(this.deferDelete){d=[];r=0;for(s=h.length;r<s;++r)c=h[r],c.state===OpenLayers.State.INSERT?d.push(c):(c.state=OpenLayers.State.DELETE,this.layer.drawFeature(c));this.layer.destroyFeatures(d,{silent:!0});r=0;for(s=g.length;r<s;++r)g[r].state=OpenLayers.State.INSERT}else this.layer.destroyFeatures(h,
+{silent:!0});this.layer.addFeatures(g,{silent:!0});this.events.triggerEvent("aftersplit",{source:a,features:g})}}return b},geomsToFeatures:function(a,b){var c=a.clone();delete c.geometry;for(var d,e=0,f=b.length;e<f;++e)d=c.clone(),d.geometry=b[e],d.state=OpenLayers.State.INSERT,b[e]=d},destroy:function(){this.active&&this.deactivate();OpenLayers.Control.prototype.destroy.call(this)},CLASS_NAME:"OpenLayers.Control.Split"});OpenLayers.Layer.WMTS=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,version:"1.0.0",requestEncoding:"KVP",url:null,layer:null,matrixSet:null,style:null,format:"image/jpeg",tileOrigin:null,tileFullExtent:null,formatSuffix:null,matrixIds:null,dimensions:null,params:null,zoomOffset:0,serverResolutions:null,formatSuffixMap:{"image/png":"png","image/png8":"png","image/png24":"png","image/png32":"png",png:"png","image/jpeg":"jpg","image/jpg":"jpg",jpeg:"jpg",jpg:"jpg"},matrix:null,initialize:function(a){var b=
+{url:!0,layer:!0,style:!0,matrixSet:!0},c;for(c in b)if(!(c in a))throw Error("Missing property '"+c+"' in layer configuration.");a.params=OpenLayers.Util.upperCaseObject(a.params);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a.name,a.url,a.params,a]);this.formatSuffix||(this.formatSuffix=this.formatSuffixMap[this.format]||this.format.split("/").pop());if(this.matrixIds&&(a=this.matrixIds.length)&&"string"===typeof this.matrixIds[0])for(b=this.matrixIds,this.matrixIds=Array(a),c=0;c<a;++c)this.matrixIds[c]=
+{identifier:b[c]}},setMap:function(){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments)},updateMatrixProperties:function(){if(this.matrix=this.getMatrix())this.matrix.topLeftCorner&&(this.tileOrigin=this.matrix.topLeftCorner),this.matrix.tileWidth&&this.matrix.tileHeight&&(this.tileSize=new OpenLayers.Size(this.matrix.tileWidth,this.matrix.tileHeight)),this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.top)),this.tileFullExtent||(this.tileFullExtent=
+this.maxExtent)},moveTo:function(a,b,c){!b&&this.matrix||this.updateMatrixProperties();return OpenLayers.Layer.Grid.prototype.moveTo.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.WMTS(this.options));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},getIdentifier:function(){return this.getServerZoom()},getMatrix:function(){var a;if(this.matrixIds&&0!==this.matrixIds.length)if("scaleDenominator"in this.matrixIds[0])for(var b=OpenLayers.METERS_PER_INCH*OpenLayers.INCHES_PER_UNIT[this.units]*
+this.getServerResolution()/2.8E-4,c=Number.POSITIVE_INFINITY,d,e=0,f=this.matrixIds.length;e<f;++e)d=Math.abs(1-this.matrixIds[e].scaleDenominator/b),d<c&&(c=d,a=this.matrixIds[e]);else a=this.matrixIds[this.getIdentifier()];else a={identifier:this.getIdentifier()};return a},getTileInfo:function(a){var b=this.getServerResolution(),c=(a.lon-this.tileOrigin.lon)/(b*this.tileSize.w);a=(this.tileOrigin.lat-a.lat)/(b*this.tileSize.h);var b=Math.floor(c),d=Math.floor(a);return{col:b,row:d,i:Math.floor((c-
+b)*this.tileSize.w),j:Math.floor((a-d)*this.tileSize.h)}},getURL:function(a){a=this.adjustBounds(a);var b="";if(!this.tileFullExtent||this.tileFullExtent.intersectsBounds(a)){a=a.getCenterLonLat();var c=this.getTileInfo(a);a=this.dimensions;var d,b=OpenLayers.Util.isArray(this.url)?this.selectUrl([this.version,this.style,this.matrixSet,this.matrix.identifier,c.row,c.col].join(),this.url):this.url;if("REST"===this.requestEncoding.toUpperCase())if(d=this.params,-1!==b.indexOf("{")){b=b.replace(/\{/g,
+"${");c={style:this.style,Style:this.style,TileMatrixSet:this.matrixSet,TileMatrix:this.matrix.identifier,TileRow:c.row,TileCol:c.col};if(a){var e,f;for(f=a.length-1;0<=f;--f)e=a[f],c[e]=d[e.toUpperCase()]}b=OpenLayers.String.format(b,c)}else{e=this.version+"/"+this.layer+"/"+this.style+"/";if(a)for(f=0;f<a.length;f++)d[a[f]]&&(e=e+d[a[f]]+"/");e=e+this.matrixSet+"/"+this.matrix.identifier+"/"+c.row+"/"+c.col+"."+this.formatSuffix;b.match(/\/$/)||(b+="/");b+=e}else"KVP"===this.requestEncoding.toUpperCase()&&
+(d={SERVICE:"WMTS",REQUEST:"GetTile",VERSION:this.version,LAYER:this.layer,STYLE:this.style,TILEMATRIXSET:this.matrixSet,TILEMATRIX:this.matrix.identifier,TILEROW:c.row,TILECOL:c.col,FORMAT:this.format},b=OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,[d]))}return b},mergeNewParams:function(a){if("KVP"===this.requestEncoding.toUpperCase())return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,[OpenLayers.Util.upperCaseObject(a)])},CLASS_NAME:"OpenLayers.Layer.WMTS"});OpenLayers.Protocol.SOS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol,{fois:null,formatOptions:null,initialize:function(a){OpenLayers.Protocol.prototype.initialize.apply(this,[a]);a.format||(this.format=new OpenLayers.Format.SOSGetFeatureOfInterest(this.formatOptions))},destroy:function(){this.options&&!this.options.format&&this.format.destroy();this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){a=OpenLayers.Util.extend({},a);OpenLayers.Util.applyDefaults(a,this.options||
+{});var b=new OpenLayers.Protocol.Response({requestType:"read"}),c=this.format,c=OpenLayers.Format.XML.prototype.write.apply(c,[c.writeNode("sos:GetFeatureOfInterest",{fois:this.fois})]);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),data:c});return b},handleRead:function(a,b){if(b.callback){var c=a.priv;200<=c.status&&300>c.status?(a.features=this.parseFeatures(c),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE;
+b.callback.call(b.scope,a)}},parseFeatures:function(a){var b=a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},CLASS_NAME:"OpenLayers.Protocol.SOS.v1_0_0"});OpenLayers.Layer.KaMapCache=OpenLayers.Class(OpenLayers.Layer.KaMap,{IMAGE_EXTENSIONS:{jpeg:"jpg",gif:"gif",png:"png",png8:"png",png24:"png",dithered:"png"},DEFAULT_FORMAT:"jpeg",initialize:function(a,b,c,d){OpenLayers.Layer.KaMap.prototype.initialize.apply(this,arguments);this.extension=this.IMAGE_EXTENSIONS[this.params.i.toLowerCase()||this.DEFAULT_FORMAT]},getURL:function(a){a=this.adjustBounds(a);var b=this.map.getResolution(),c=Math.round(1E4*this.map.getScale())/1E4,d=Math.round(a.left/b);a=
+-Math.round(a.top/b);var b=Math.floor(d/this.tileSize.w/this.params.metaTileSize.w)*this.tileSize.w*this.params.metaTileSize.w,e=Math.floor(a/this.tileSize.h/this.params.metaTileSize.h)*this.tileSize.h*this.params.metaTileSize.h,c=["/",this.params.map,"/",c,"/",this.params.g.replace(/\s/g,"_"),"/def/t",e,"/l",b,"/t",a,"l",d,".",this.extension],d=this.url;OpenLayers.Util.isArray(d)&&(d=this.selectUrl(c.join(""),d));return d+c.join("")},CLASS_NAME:"OpenLayers.Layer.KaMapCache"});OpenLayers.Protocol.WFS.v1_1_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.1.0",initialize:function(a){OpenLayers.Protocol.WFS.v1.prototype.initialize.apply(this,arguments);this.outputFormat&&!this.readFormat&&("gml2"==this.outputFormat.toLowerCase()?this.readFormat=new OpenLayers.Format.GML.v2({featureType:this.featureType,featureNS:this.featureNS,geometryName:this.geometryName}):"json"==this.outputFormat.toLowerCase()&&(this.readFormat=new OpenLayers.Format.GeoJSON))},CLASS_NAME:"OpenLayers.Protocol.WFS.v1_1_0"});OpenLayers.Format.WMSCapabilities.v1_1_1=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1,{version:"1.1.1",readers:{wms:OpenLayers.Util.applyDefaults({SRS:function(a,b){b.srs[this.getChildValue(a)]=!0}},OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers.wms)},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_1"});OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC=OpenLayers.Class(OpenLayers.Format.WMSCapabilities.v1_1_1,{version:"1.1.1",profile:"WMSC",readers:{wms:OpenLayers.Util.applyDefaults({VendorSpecificCapabilities:function(a,b){b.vendorSpecific={tileSets:[]};this.readChildNodes(a,b.vendorSpecific)},TileSet:function(a,b){var c={srs:{},bbox:{},resolutions:[]};this.readChildNodes(a,c);b.tileSets.push(c)},Resolutions:function(a,b){for(var c=this.getChildValue(a).split(" "),d=0,e=c.length;d<e;d++)""!=c[d]&&b.resolutions.push(parseFloat(c[d]))},
+Width:function(a,b){b.width=parseInt(this.getChildValue(a))},Height:function(a,b){b.height=parseInt(this.getChildValue(a))},Layers:function(a,b){b.layers=this.getChildValue(a)},Styles:function(a,b){b.styles=this.getChildValue(a)}},OpenLayers.Format.WMSCapabilities.v1_1_1.prototype.readers.wms)},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC"});OpenLayers.Control.LayerSwitcher=OpenLayers.Class(OpenLayers.Control,{layerStates:null,layersDiv:null,baseLayersDiv:null,baseLayers:null,dataLbl:null,dataLayersDiv:null,dataLayers:null,minimizeDiv:null,maximizeDiv:null,ascending:!0,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.layerStates=[]},destroy:function(){this.clearLayersArray("base");this.clearLayersArray("data");this.map.events.un({buttonclick:this.onButtonClick,addlayer:this.redraw,changelayer:this.redraw,
+removelayer:this.redraw,changebaselayer:this.redraw,scope:this});this.events.unregister("buttonclick",this,this.onButtonClick);OpenLayers.Control.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.on({addlayer:this.redraw,changelayer:this.redraw,removelayer:this.redraw,changebaselayer:this.redraw,scope:this});this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",this,this.onButtonClick)):
+this.map.events.register("buttonclick",this,this.onButtonClick)},draw:function(){OpenLayers.Control.prototype.draw.apply(this);this.loadContents();this.outsideViewport||this.minimizeControl();this.redraw();return this.div},onButtonClick:function(a){a=a.buttonElement;a===this.minimizeDiv?this.minimizeControl():a===this.maximizeDiv?this.maximizeControl():a._layerSwitcher===this.id&&(a["for"]&&(a=document.getElementById(a["for"])),a.disabled||("radio"==a.type?(a.checked=!0,this.map.setBaseLayer(this.map.getLayer(a._layer))):
+(a.checked=!a.checked,this.updateMap())))},clearLayersArray:function(a){this[a+"LayersDiv"].innerHTML="";this[a+"Layers"]=[]},checkRedraw:function(){if(!this.layerStates.length||this.map.layers.length!=this.layerStates.length)return!0;for(var a=0,b=this.layerStates.length;a<b;a++){var c=this.layerStates[a],d=this.map.layers[a];if(c.name!=d.name||c.inRange!=d.inRange||c.id!=d.id||c.visibility!=d.visibility)return!0}return!1},redraw:function(){if(!this.checkRedraw())return this.div;this.clearLayersArray("base");
+this.clearLayersArray("data");var a=!1,b=!1,c=this.map.layers.length;this.layerStates=Array(c);for(var d=0;d<c;d++){var e=this.map.layers[d];this.layerStates[d]={name:e.name,visibility:e.visibility,inRange:e.inRange,id:e.id}}var f=this.map.layers.slice();this.ascending||f.reverse();d=0;for(c=f.length;d<c;d++){var e=f[d],g=e.isBaseLayer;if(e.displayInLayerSwitcher){g?b=!0:a=!0;var h=g?e==this.map.baseLayer:e.getVisibility(),k=document.createElement("input"),l=OpenLayers.Util.createUniqueID(this.id+
+"_input_");k.id=l;k.name=g?this.id+"_baseLayers":e.name;k.type=g?"radio":"checkbox";k.value=e.name;k.checked=h;k.defaultChecked=h;k.className="olButton";k._layer=e.id;k._layerSwitcher=this.id;g||e.inRange||(k.disabled=!0);h=document.createElement("label");h["for"]=k.id;OpenLayers.Element.addClass(h,"labelSpan olButton");h._layer=e.id;h._layerSwitcher=this.id;g||e.inRange||(h.style.color="gray");h.innerHTML=e.name;h.style.verticalAlign=g?"bottom":"baseline";l=document.createElement("br");(g?this.baseLayers:
+this.dataLayers).push({layer:e,inputElem:k,labelSpan:h});e=g?this.baseLayersDiv:this.dataLayersDiv;e.appendChild(k);e.appendChild(h);e.appendChild(l)}}this.dataLbl.style.display=a?"":"none";this.baseLbl.style.display=b?"":"none";return this.div},updateMap:function(){for(var a=0,b=this.baseLayers.length;a<b;a++){var c=this.baseLayers[a];c.inputElem.checked&&this.map.setBaseLayer(c.layer,!1)}a=0;for(b=this.dataLayers.length;a<b;a++)c=this.dataLayers[a],c.layer.setVisibility(c.inputElem.checked)},maximizeControl:function(a){this.div.style.width=
+"";this.div.style.height="";this.showControls(!1);null!=a&&OpenLayers.Event.stop(a)},minimizeControl:function(a){this.div.style.width="0px";this.div.style.height="0px";this.showControls(!0);null!=a&&OpenLayers.Event.stop(a)},showControls:function(a){this.maximizeDiv.style.display=a?"":"none";this.minimizeDiv.style.display=a?"none":"";this.layersDiv.style.display=a?"none":""},loadContents:function(){this.layersDiv=document.createElement("div");this.layersDiv.id=this.id+"_layersDiv";OpenLayers.Element.addClass(this.layersDiv,
+"layersDiv");this.baseLbl=document.createElement("div");this.baseLbl.innerHTML=OpenLayers.i18n("Base Layer");OpenLayers.Element.addClass(this.baseLbl,"baseLbl");this.baseLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.baseLayersDiv,"baseLayersDiv");this.dataLbl=document.createElement("div");this.dataLbl.innerHTML=OpenLayers.i18n("Overlays");OpenLayers.Element.addClass(this.dataLbl,"dataLbl");this.dataLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.dataLayersDiv,
+"dataLayersDiv");this.ascending?(this.layersDiv.appendChild(this.baseLbl),this.layersDiv.appendChild(this.baseLayersDiv),this.layersDiv.appendChild(this.dataLbl),this.layersDiv.appendChild(this.dataLayersDiv)):(this.layersDiv.appendChild(this.dataLbl),this.layersDiv.appendChild(this.dataLayersDiv),this.layersDiv.appendChild(this.baseLbl),this.layersDiv.appendChild(this.baseLayersDiv));this.div.appendChild(this.layersDiv);var a=OpenLayers.Util.getImageLocation("layer-switcher-maximize.png");this.maximizeDiv=
+OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MaximizeDiv",null,null,a,"absolute");OpenLayers.Element.addClass(this.maximizeDiv,"maximizeDiv olButton");this.maximizeDiv.style.display="none";this.div.appendChild(this.maximizeDiv);a=OpenLayers.Util.getImageLocation("layer-switcher-minimize.png");this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MinimizeDiv",null,null,a,"absolute");OpenLayers.Element.addClass(this.minimizeDiv,"minimizeDiv olButton");this.minimizeDiv.style.display=
+"none";this.div.appendChild(this.minimizeDiv)},CLASS_NAME:"OpenLayers.Control.LayerSwitcher"});OpenLayers.Format.Atom=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{atom:"http://www.w3.org/2005/Atom",georss:"http://www.georss.org/georss"},feedTitle:"untitled",defaultEntryTitle:"untitled",gmlParser:null,xy:!1,read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));return this.parseFeatures(a)},write:function(a){var b;if(OpenLayers.Util.isArray(a)){b=this.createElementNSPlus("atom:feed");b.appendChild(this.createElementNSPlus("atom:title",{value:this.feedTitle}));
+for(var c=0,d=a.length;c<d;c++)b.appendChild(this.buildEntryNode(a[c]))}else b=this.buildEntryNode(a);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},buildContentNode:function(a){var b=this.createElementNSPlus("atom:content",{attributes:{type:a.type||null}});if(a.src)b.setAttribute("src",a.src);else if("text"==a.type||null==a.type)b.appendChild(this.createTextNode(a.value));else if("html"==a.type){if("string"!=typeof a.value)throw"HTML content must be in form of an escaped string";b.appendChild(this.createTextNode(a.value))}else"xhtml"==
+a.type?b.appendChild(a.value):"xhtml"==a.type||a.type.match(/(\+|\/)xml$/)?b.appendChild(a.value):b.appendChild(this.createTextNode(a.value));return b},buildEntryNode:function(a){var b=a.attributes,c=b.atom||{},d=this.createElementNSPlus("atom:entry");if(c.authors)for(var e=OpenLayers.Util.isArray(c.authors)?c.authors:[c.authors],f=0,g=e.length;f<g;f++)d.appendChild(this.buildPersonConstructNode("author",e[f]));if(c.categories)for(var e=OpenLayers.Util.isArray(c.categories)?c.categories:[c.categories],
+h,f=0,g=e.length;f<g;f++)h=e[f],d.appendChild(this.createElementNSPlus("atom:category",{attributes:{term:h.term,scheme:h.scheme||null,label:h.label||null}}));c.content&&d.appendChild(this.buildContentNode(c.content));if(c.contributors)for(e=OpenLayers.Util.isArray(c.contributors)?c.contributors:[c.contributors],f=0,g=e.length;f<g;f++)d.appendChild(this.buildPersonConstructNode("contributor",e[f]));a.fid&&d.appendChild(this.createElementNSPlus("atom:id",{value:a.fid}));if(c.links)for(e=OpenLayers.Util.isArray(c.links)?
+c.links:[c.links],f=0,g=e.length;f<g;f++)h=e[f],d.appendChild(this.createElementNSPlus("atom:link",{attributes:{href:h.href,rel:h.rel||null,type:h.type||null,hreflang:h.hreflang||null,title:h.title||null,length:h.length||null}}));c.published&&d.appendChild(this.createElementNSPlus("atom:published",{value:c.published}));c.rights&&d.appendChild(this.createElementNSPlus("atom:rights",{value:c.rights}));(c.summary||b.description)&&d.appendChild(this.createElementNSPlus("atom:summary",{value:c.summary||
+b.description}));d.appendChild(this.createElementNSPlus("atom:title",{value:c.title||b.title||this.defaultEntryTitle}));c.updated&&d.appendChild(this.createElementNSPlus("atom:updated",{value:c.updated}));a.geometry&&(b=this.createElementNSPlus("georss:where"),b.appendChild(this.buildGeometryNode(a.geometry)),d.appendChild(b));return d},initGmlParser:function(){this.gmlParser=new OpenLayers.Format.GML.v3({xy:this.xy,featureNS:"http://example.com#feature",internalProjection:this.internalProjection,
+externalProjection:this.externalProjection})},buildGeometryNode:function(a){this.gmlParser||this.initGmlParser();return this.gmlParser.writeNode("feature:_geometry",a).firstChild},buildPersonConstructNode:function(a,b){var c=["uri","email"],d=this.createElementNSPlus("atom:"+a);d.appendChild(this.createElementNSPlus("atom:name",{value:b.name}));for(var e=0,f=c.length;e<f;e++)b[c[e]]&&d.appendChild(this.createElementNSPlus("atom:"+c[e],{value:b[c[e]]}));return d},getFirstChildValue:function(a,b,c,
+d){return(a=this.getElementsByTagNameNS(a,b,c))&&0<a.length?this.getChildValue(a[0],d):d},parseFeature:function(a){var b={},c=null,d=null,e=null,f=this.namespaces.atom;this.parsePersonConstructs(a,"author",b);d=this.getElementsByTagNameNS(a,f,"category");0<d.length&&(b.categories=[]);for(var g=0,h=d.length;g<h;g++){c={};c.term=d[g].getAttribute("term");if(e=d[g].getAttribute("scheme"))c.scheme=e;if(e=d[g].getAttribute("label"))c.label=e;b.categories.push(c)}d=this.getElementsByTagNameNS(a,f,"content");
+if(0<d.length){c={};if(e=d[0].getAttribute("type"))c.type=e;(e=d[0].getAttribute("src"))?c.src=e:("text"==c.type||"html"==c.type||null==c.type?c.value=this.getFirstChildValue(a,f,"content",null):"xhtml"==c.type||c.type.match(/(\+|\/)xml$/)?c.value=this.getChildEl(d[0]):c.value=this.getFirstChildValue(a,f,"content",null),b.content=c)}this.parsePersonConstructs(a,"contributor",b);b.id=this.getFirstChildValue(a,f,"id",null);d=this.getElementsByTagNameNS(a,f,"link");0<d.length&&(b.links=Array(d.length));
+for(var k=["rel","type","hreflang","title","length"],g=0,h=d.length;g<h;g++){c={};c.href=d[g].getAttribute("href");for(var l=0,m=k.length;l<m;l++)(e=d[g].getAttribute(k[l]))&&(c[k[l]]=e);b.links[g]=c}if(c=this.getFirstChildValue(a,f,"published",null))b.published=c;if(c=this.getFirstChildValue(a,f,"rights",null))b.rights=c;if(c=this.getFirstChildValue(a,f,"summary",null))b.summary=c;b.title=this.getFirstChildValue(a,f,"title",null);b.updated=this.getFirstChildValue(a,f,"updated",null);c={title:b.title,
+description:b.summary,atom:b};a=this.parseLocations(a)[0];a=new OpenLayers.Feature.Vector(a,c);a.fid=b.id;return a},parseFeatures:function(a){var b=[],c=this.getElementsByTagNameNS(a,this.namespaces.atom,"entry");0==c.length&&(c=[a]);a=0;for(var d=c.length;a<d;a++)b.push(this.parseFeature(c[a]));return b},parseLocations:function(a){var b=this.namespaces.georss,c={components:[]},d=this.getElementsByTagNameNS(a,b,"where");if(d&&0<d.length){this.gmlParser||this.initGmlParser();for(var e=0,f=d.length;e<
+f;e++)this.gmlParser.readChildNodes(d[e],c)}c=c.components;if((d=this.getElementsByTagNameNS(a,b,"point"))&&0<d.length)for(e=0,f=d.length;e<f;e++){var g=OpenLayers.String.trim(d[e].firstChild.nodeValue).split(/\s+/);2!=g.length&&(g=OpenLayers.String.trim(d[e].firstChild.nodeValue).split(/\s*,\s*/));c.push(new OpenLayers.Geometry.Point(g[1],g[0]))}var h=this.getElementsByTagNameNS(a,b,"line");if(h&&0<h.length)for(var k,e=0,f=h.length;e<f;e++){d=OpenLayers.String.trim(h[e].firstChild.nodeValue).split(/\s+/);
+k=[];for(var l=0,m=d.length;l<m;l+=2)g=new OpenLayers.Geometry.Point(d[l+1],d[l]),k.push(g);c.push(new OpenLayers.Geometry.LineString(k))}if((a=this.getElementsByTagNameNS(a,b,"polygon"))&&0<a.length)for(e=0,f=a.length;e<f;e++){d=OpenLayers.String.trim(a[e].firstChild.nodeValue).split(/\s+/);k=[];l=0;for(m=d.length;l<m;l+=2)g=new OpenLayers.Geometry.Point(d[l+1],d[l]),k.push(g);c.push(new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(k)]))}if(this.internalProjection&&this.externalProjection)for(e=
+0,f=c.length;e<f;e++)c[e]&&c[e].transform(this.externalProjection,this.internalProjection);return c},parsePersonConstructs:function(a,b,c){var d=[],e=this.namespaces.atom;a=this.getElementsByTagNameNS(a,e,b);for(var f=["uri","email"],g=0,h=a.length;g<h;g++){var k={};k.name=this.getFirstChildValue(a[g],e,"name",null);for(var l=0,m=f.length;l<m;l++){var n=this.getFirstChildValue(a[g],e,f[l],null);n&&(k[f[l]]=n)}d.push(k)}0<d.length&&(c[b+"s"]=d)},CLASS_NAME:"OpenLayers.Format.Atom"});OpenLayers.Control.KeyboardDefaults=OpenLayers.Class(OpenLayers.Control,{autoActivate:!0,slideFactor:75,observeElement:null,draw:function(){this.handler=new OpenLayers.Handler.Keyboard(this,{keydown:this.defaultKeyPress},{observeElement:this.observeElement||document})},defaultKeyPress:function(a){var b,c=!0;b=OpenLayers.Event.element(a);if(!b||"INPUT"!=b.tagName&&"TEXTAREA"!=b.tagName&&"SELECT"!=b.tagName){switch(a.keyCode){case OpenLayers.Event.KEY_LEFT:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Event.KEY_RIGHT:this.map.pan(this.slideFactor,
+0);break;case OpenLayers.Event.KEY_UP:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Event.KEY_DOWN:this.map.pan(0,this.slideFactor);break;case 33:b=this.map.getSize();this.map.pan(0,-0.75*b.h);break;case 34:b=this.map.getSize();this.map.pan(0,0.75*b.h);break;case 35:b=this.map.getSize();this.map.pan(0.75*b.w,0);break;case 36:b=this.map.getSize();this.map.pan(-0.75*b.w,0);break;case 43:case 61:case 187:case 107:this.map.zoomIn();break;case 45:case 109:case 189:case 95:this.map.zoomOut();
+break;default:c=!1}c&&OpenLayers.Event.stop(a)}},CLASS_NAME:"OpenLayers.Control.KeyboardDefaults"});OpenLayers.Format.WMTSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.OWSCommon.v1_1_0,{version:"1.0.0",namespaces:{ows:"http://www.opengis.net/ows/1.1",wmts:"http://www.opengis.net/wmts/1.0",xlink:"http://www.w3.org/1999/xlink"},yx:null,defaultPrefix:"wmts",initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);this.options=a;a=OpenLayers.Util.extend({},OpenLayers.Format.WMTSCapabilities.prototype.yx);this.yx=OpenLayers.Util.extend(a,this.yx)},read:function(a){"string"==
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b={};this.readNode(a,b);b.version=this.version;return b},readers:{wmts:{Capabilities:function(a,b){this.readChildNodes(a,b)},Contents:function(a,b){b.contents={};b.contents.layers=[];b.contents.tileMatrixSets={};this.readChildNodes(a,b.contents)},Layer:function(a,b){var c={styles:[],formats:[],dimensions:[],tileMatrixSetLinks:[],layers:[]};this.readChildNodes(a,c);b.layers.push(c)},Style:function(a,
+b){var c={};c.isDefault="true"===a.getAttribute("isDefault");this.readChildNodes(a,c);b.styles.push(c)},Format:function(a,b){b.formats.push(this.getChildValue(a))},TileMatrixSetLink:function(a,b){var c={};this.readChildNodes(a,c);b.tileMatrixSetLinks.push(c)},TileMatrixSet:function(a,b){if(b.layers){var c={matrixIds:[]};this.readChildNodes(a,c);b.tileMatrixSets[c.identifier]=c}else b.tileMatrixSet=this.getChildValue(a)},TileMatrix:function(a,b){var c={supportedCRS:b.supportedCRS};this.readChildNodes(a,
+c);b.matrixIds.push(c)},ScaleDenominator:function(a,b){b.scaleDenominator=parseFloat(this.getChildValue(a))},TopLeftCorner:function(a,b){var c=this.getChildValue(a).split(" "),d;b.supportedCRS&&(d=b.supportedCRS.replace(/urn:ogc:def:crs:(\w+):.+:(\w+)$/,"urn:ogc:def:crs:$1::$2"),d=!!this.yx[d]);b.topLeftCorner=d?new OpenLayers.LonLat(c[1],c[0]):new OpenLayers.LonLat(c[0],c[1])},TileWidth:function(a,b){b.tileWidth=parseInt(this.getChildValue(a))},TileHeight:function(a,b){b.tileHeight=parseInt(this.getChildValue(a))},
+MatrixWidth:function(a,b){b.matrixWidth=parseInt(this.getChildValue(a))},MatrixHeight:function(a,b){b.matrixHeight=parseInt(this.getChildValue(a))},ResourceURL:function(a,b){b.resourceUrl=b.resourceUrl||{};var c=a.getAttribute("resourceType");b.resourceUrls||(b.resourceUrls=[]);c=b.resourceUrl[c]={format:a.getAttribute("format"),template:a.getAttribute("template"),resourceType:c};b.resourceUrls.push(c)},WSDL:function(a,b){b.wsdl={};b.wsdl.href=a.getAttribute("xlink:href")},ServiceMetadataURL:function(a,
+b){b.serviceMetadataUrl={};b.serviceMetadataUrl.href=a.getAttribute("xlink:href")},LegendURL:function(a,b){b.legend={};b.legend.href=a.getAttribute("xlink:href");b.legend.format=a.getAttribute("format")},Dimension:function(a,b){var c={values:[]};this.readChildNodes(a,c);b.dimensions.push(c)},Default:function(a,b){b["default"]=this.getChildValue(a)},Value:function(a,b){b.values.push(this.getChildValue(a))}},ows:OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers.ows},CLASS_NAME:"OpenLayers.Format.WMTSCapabilities.v1_0_0"});
diff --git a/misc/openlayers/OpenLayers.light.debug.js b/misc/openlayers/OpenLayers.light.debug.js
new file mode 100644
index 0000000..536a033
--- /dev/null
+++ b/misc/openlayers/OpenLayers.light.debug.js
@@ -0,0 +1,37230 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* ======================================================================
+ OpenLayers/SingleFile.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+var OpenLayers = {
+ /**
+ * Constant: VERSION_NUMBER
+ */
+ VERSION_NUMBER: "Release 2.13.1",
+
+ /**
+ * Constant: singleFile
+ * TODO: remove this in 3.0 when we stop supporting build profiles that
+ * include OpenLayers.js
+ */
+ singleFile: true,
+
+ /**
+ * Method: _getScriptLocation
+ * Return the path to this script. This is also implemented in
+ * OpenLayers.js
+ *
+ * Returns:
+ * {String} Path to this script
+ */
+ _getScriptLocation: (function() {
+ var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
+ s = document.getElementsByTagName('script'),
+ src, m, l = "";
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+ return (function() { return l; });
+ })(),
+
+ /**
+ * Property: ImgPath
+ * {String} Set this to the path where control images are stored, a path
+ * given here must end with a slash. If set to '' (which is the default)
+ * OpenLayers will use its script location + "img/".
+ *
+ * You will need to set this property when you have a singlefile build of
+ * OpenLayers that either is not named "OpenLayers.js" or if you move
+ * the file in a way such that the image directory cannot be derived from
+ * the script location.
+ *
+ * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+ * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+ * way of including OpenLayers in your HTML would be:
+ *
+ * (code)
+ * <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+ * <script type="text/javascript">
+ * // tell OpenLayers where the control images are
+ * // remember the trailing slash
+ * OpenLayers.ImgPath = "/resources/external/images/ol/";
+ * </script>
+ * (end code)
+ *
+ * Please remember that when your OpenLayers script is not named
+ * "OpenLayers.js" you will have to make sure that the default theme is
+ * loaded into the page by including an appropriate <link>-tag,
+ * e.g.:
+ *
+ * (code)
+ * <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
+ * (end code)
+ */
+ ImgPath : ''
+};
+/* ======================================================================
+ OpenLayers/BaseTypes.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Header: OpenLayers Base Types
+ * OpenLayers custom string, number and function functions are described here.
+ */
+
+/**
+ * Namespace: OpenLayers.String
+ * Contains convenience functions for string manipulation.
+ */
+OpenLayers.String = {
+
+ /**
+ * APIFunction: startsWith
+ * Test whether a string starts with another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string starts with the second.
+ */
+ startsWith: function(str, sub) {
+ return (str.indexOf(sub) == 0);
+ },
+
+ /**
+ * APIFunction: contains
+ * Test whether a string contains another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string contains the second.
+ */
+ contains: function(str, sub) {
+ return (str.indexOf(sub) != -1);
+ },
+
+ /**
+ * APIFunction: trim
+ * Removes leading and trailing whitespace characters from a string.
+ *
+ * Parameters:
+ * str - {String} The (potentially) space padded string. This string is not
+ * modified.
+ *
+ * Returns:
+ * {String} A trimmed version of the string with all leading and
+ * trailing spaces removed.
+ */
+ trim: function(str) {
+ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ },
+
+ /**
+ * APIFunction: camelize
+ * Camel-case a hyphenated string.
+ * Ex. "chicken-head" becomes "chickenHead", and
+ * "-chicken-head" becomes "ChickenHead".
+ *
+ * Parameters:
+ * str - {String} The string to be camelized. The original is not modified.
+ *
+ * Returns:
+ * {String} The string, camelized
+ */
+ camelize: function(str) {
+ var oStringList = str.split('-');
+ var camelizedString = oStringList[0];
+ for (var i=1, len=oStringList.length; i<len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+ return camelizedString;
+ },
+
+ /**
+ * APIFunction: format
+ * Given a string with tokens in the form ${token}, return a string
+ * with tokens replaced with properties from the given context
+ * object. Represent a literal "${" by doubling it, e.g. "${${".
+ *
+ * Parameters:
+ * template - {String} A string with tokens to be replaced. A template
+ * has the form "literal ${token}" where the token will be replaced
+ * by the value of context["token"].
+ * context - {Object} An optional object with properties corresponding
+ * to the tokens in the format string. If no context is sent, the
+ * window object will be used.
+ * args - {Array} Optional arguments to pass to any functions found in
+ * the context. If a context property is a function, the token
+ * will be replaced by the return from the function called with
+ * these arguments.
+ *
+ * Returns:
+ * {String} A string with tokens replaced from the context object.
+ */
+ format: function(template, context, args) {
+ if(!context) {
+ context = window;
+ }
+
+ // Example matching:
+ // str = ${foo.bar}
+ // match = foo.bar
+ var replacer = function(str, match) {
+ var replacement;
+
+ // Loop through all subs. Example: ${a.b.c}
+ // 0 -> replacement = context[a];
+ // 1 -> replacement = context[a][b];
+ // 2 -> replacement = context[a][b][c];
+ var subs = match.split(/\.+/);
+ for (var i=0; i< subs.length; i++) {
+ if (i == 0) {
+ replacement = context;
+ }
+ if (replacement === undefined) {
+ break;
+ }
+ replacement = replacement[subs[i]];
+ }
+
+ if(typeof replacement == "function") {
+ replacement = args ?
+ replacement.apply(null, args) :
+ replacement();
+ }
+
+ // If replacement is undefined, return the string 'undefined'.
+ // This is a workaround for a bugs in browsers not properly
+ // dealing with non-participating groups in regular expressions:
+ // http://blog.stevenlevithan.com/archives/npcg-javascript
+ if (typeof replacement == 'undefined') {
+ return 'undefined';
+ } else {
+ return replacement;
+ }
+ };
+
+ return template.replace(OpenLayers.String.tokenRegEx, replacer);
+ },
+
+ /**
+ * Property: tokenRegEx
+ * Used to find tokens in a string.
+ * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
+ */
+ tokenRegEx: /\$\{([\w.]+?)\}/g,
+
+ /**
+ * Property: numberRegEx
+ * Used to test strings as numbers.
+ */
+ numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+
+ /**
+ * APIFunction: isNumeric
+ * Determine whether a string contains only a numeric value.
+ *
+ * Examples:
+ * (code)
+ * OpenLayers.String.isNumeric("6.02e23") // true
+ * OpenLayers.String.isNumeric("12 dozen") // false
+ * OpenLayers.String.isNumeric("4") // true
+ * OpenLayers.String.isNumeric(" 4 ") // false
+ * (end)
+ *
+ * Returns:
+ * {Boolean} String contains only a number.
+ */
+ isNumeric: function(value) {
+ return OpenLayers.String.numberRegEx.test(value);
+ },
+
+ /**
+ * APIFunction: numericIf
+ * Converts a string that appears to be a numeric value into a number.
+ *
+ * Parameters:
+ * value - {String}
+ * trimWhitespace - {Boolean}
+ *
+ * Returns:
+ * {Number|String} a Number if the passed value is a number, a String
+ * otherwise.
+ */
+ numericIf: function(value, trimWhitespace) {
+ var originalValue = value;
+ if (trimWhitespace === true && value != null && value.replace) {
+ value = value.replace(/^\s*|\s*$/g, "");
+ }
+ return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
+ }
+
+};
+
+/**
+ * Namespace: OpenLayers.Number
+ * Contains convenience functions for manipulating numbers.
+ */
+OpenLayers.Number = {
+
+ /**
+ * Property: decimalSeparator
+ * Decimal separator to use when formatting numbers.
+ */
+ decimalSeparator: ".",
+
+ /**
+ * Property: thousandsSeparator
+ * Thousands separator to use when formatting numbers.
+ */
+ thousandsSeparator: ",",
+
+ /**
+ * APIFunction: limitSigDigs
+ * Limit the number of significant digits on a float.
+ *
+ * Parameters:
+ * num - {Float}
+ * sig - {Integer}
+ *
+ * Returns:
+ * {Float} The number, rounded to the specified number of significant
+ * digits.
+ */
+ limitSigDigs: function(num, sig) {
+ var fig = 0;
+ if (sig > 0) {
+ fig = parseFloat(num.toPrecision(sig));
+ }
+ return fig;
+ },
+
+ /**
+ * APIFunction: format
+ * Formats a number for output.
+ *
+ * Parameters:
+ * num - {Float}
+ * dec - {Integer} Number of decimal places to round to.
+ * Defaults to 0. Set to null to leave decimal places unchanged.
+ * tsep - {String} Thousands separator.
+ * Default is ",".
+ * dsep - {String} Decimal separator.
+ * Default is ".".
+ *
+ * Returns:
+ * {String} A string representing the formatted number.
+ */
+ format: function(num, dec, tsep, dsep) {
+ dec = (typeof dec != "undefined") ? dec : 0;
+ tsep = (typeof tsep != "undefined") ? tsep :
+ OpenLayers.Number.thousandsSeparator;
+ dsep = (typeof dsep != "undefined") ? dsep :
+ OpenLayers.Number.decimalSeparator;
+
+ if (dec != null) {
+ num = parseFloat(num.toFixed(dec));
+ }
+
+ var parts = num.toString().split(".");
+ if (parts.length == 1 && dec == null) {
+ // integer where we do not want to touch the decimals
+ dec = 0;
+ }
+
+ var integer = parts[0];
+ if (tsep) {
+ var thousands = /(-?[0-9]+)([0-9]{3})/;
+ while(thousands.test(integer)) {
+ integer = integer.replace(thousands, "$1" + tsep + "$2");
+ }
+ }
+
+ var str;
+ if (dec == 0) {
+ str = integer;
+ } else {
+ var rem = parts.length > 1 ? parts[1] : "0";
+ if (dec != null) {
+ rem = rem + new Array(dec - rem.length + 1).join("0");
+ }
+ str = integer + dsep + rem;
+ }
+ return str;
+ },
+
+ /**
+ * Method: zeroPad
+ * Create a zero padded string optionally with a radix for casting numbers.
+ *
+ * Parameters:
+ * num - {Number} The number to be zero padded.
+ * len - {Number} The length of the string to be returned.
+ * radix - {Number} An integer between 2 and 36 specifying the base to use
+ * for representing numeric values.
+ */
+ zeroPad: function(num, len, radix) {
+ var str = num.toString(radix || 10);
+ while (str.length < len) {
+ str = "0" + str;
+ }
+ return str;
+ }
+};
+
+/**
+ * Namespace: OpenLayers.Function
+ * Contains convenience functions for function manipulation.
+ */
+OpenLayers.Function = {
+ /**
+ * APIFunction: bind
+ * Bind a function to an object. Method to easily create closures with
+ * 'this' altered.
+ *
+ * Parameters:
+ * func - {Function} Input function.
+ * object - {Object} The object to bind to the input function (as this).
+ *
+ * Returns:
+ * {Function} A closure with 'this' set to the passed in object.
+ */
+ bind: function(func, object) {
+ // create a reference to all arguments past the second one
+ var args = Array.prototype.slice.apply(arguments, [2]);
+ return function() {
+ // Push on any additional arguments from the actual function call.
+ // These will come after those sent to the bind call.
+ var newArgs = args.concat(
+ Array.prototype.slice.apply(arguments, [0])
+ );
+ return func.apply(object, newArgs);
+ };
+ },
+
+ /**
+ * APIFunction: bindAsEventListener
+ * Bind a function to an object, and configure it to receive the event
+ * object as first parameter when called.
+ *
+ * Parameters:
+ * func - {Function} Input function to serve as an event listener.
+ * object - {Object} A reference to this.
+ *
+ * Returns:
+ * {Function}
+ */
+ bindAsEventListener: function(func, object) {
+ return function(event) {
+ return func.call(object, event || window.event);
+ };
+ },
+
+ /**
+ * APIFunction: False
+ * A simple function to that just does "return false". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.False;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ False : function() {
+ return false;
+ },
+
+ /**
+ * APIFunction: True
+ * A simple function to that just does "return true". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.True;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ True : function() {
+ return true;
+ },
+
+ /**
+ * APIFunction: Void
+ * A reusable function that returns ``undefined``.
+ *
+ * Returns:
+ * {undefined}
+ */
+ Void: function() {}
+
+};
+
+/**
+ * Namespace: OpenLayers.Array
+ * Contains convenience functions for array manipulation.
+ */
+OpenLayers.Array = {
+
+ /**
+ * APIMethod: filter
+ * Filter an array. Provides the functionality of the
+ * Array.prototype.filter extension to the ECMA-262 standard. Where
+ * available, Array.prototype.filter will be used.
+ *
+ * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
+ *
+ * Parameters:
+ * array - {Array} The array to be filtered. This array is not mutated.
+ * Elements added to this array by the callback will not be visited.
+ * callback - {Function} A function that is called for each element in
+ * the array. If this function returns true, the element will be
+ * included in the return. The function will be called with three
+ * arguments: the element in the array, the index of that element, and
+ * the array itself. If the optional caller parameter is specified
+ * the callback will be called with this set to caller.
+ * caller - {Object} Optional object to be set as this when the callback
+ * is called.
+ *
+ * Returns:
+ * {Array} An array of elements from the passed in array for which the
+ * callback returns true.
+ */
+ filter: function(array, callback, caller) {
+ var selected = [];
+ if (Array.prototype.filter) {
+ selected = array.filter(callback, caller);
+ } else {
+ var len = array.length;
+ if (typeof callback != "function") {
+ throw new TypeError();
+ }
+ for(var i=0; i<len; i++) {
+ if (i in array) {
+ var val = array[i];
+ if (callback.call(caller, val, i, array)) {
+ selected.push(val);
+ }
+ }
+ }
+ }
+ return selected;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Class.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Constructor: OpenLayers.Class
+ * Base class used to construct all other classes. Includes support for
+ * multiple inheritance.
+ *
+ * This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old
+ * syntax for creating classes and dealing with inheritance
+ * will be removed.
+ *
+ * To create a new OpenLayers-style class, use the following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(prototype);
+ * (end)
+ *
+ * To create a new OpenLayers-style class with multiple inheritance, use the
+ * following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(Class1, Class2, prototype);
+ * (end)
+ *
+ * Note that instanceof reflection will only reveal Class1 as superclass.
+ *
+ */
+OpenLayers.Class = function() {
+ var len = arguments.length;
+ var P = arguments[0];
+ var F = arguments[len-1];
+
+ var C = typeof F.initialize == "function" ?
+ F.initialize :
+ function(){ P.prototype.initialize.apply(this, arguments); };
+
+ if (len > 1) {
+ var newArgs = [C, P].concat(
+ Array.prototype.slice.call(arguments).slice(1, len-1), F);
+ OpenLayers.inherit.apply(null, newArgs);
+ } else {
+ C.prototype = F;
+ }
+ return C;
+};
+
+/**
+ * Function: OpenLayers.inherit
+ *
+ * Parameters:
+ * C - {Object} the class that inherits
+ * P - {Object} the superclass to inherit from
+ *
+ * In addition to the mandatory C and P parameters, an arbitrary number of
+ * objects can be passed, which will extend C.
+ */
+OpenLayers.inherit = function(C, P) {
+ var F = function() {};
+ F.prototype = P.prototype;
+ C.prototype = new F;
+ var i, l, o;
+ for(i=2, l=arguments.length; i<l; i++) {
+ o = arguments[i];
+ if(typeof o === "function") {
+ o = o.prototype;
+ }
+ OpenLayers.Util.extend(C.prototype, o);
+ }
+};
+
+/**
+ * APIFunction: extend
+ * Copy all properties of a source object to a destination object. Modifies
+ * the passed in destination object. Any properties on the source object
+ * that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+OpenLayers.Util.extend = function(destination, source) {
+ destination = destination || {};
+ if (source) {
+ for (var property in source) {
+ var value = source[property];
+ if (value !== undefined) {
+ destination[property] = value;
+ }
+ }
+
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object
+ * is an instance of window.Event.
+ */
+
+ var sourceIsEvt = typeof window.Event == "function"
+ && source instanceof window.Event;
+
+ if (!sourceIsEvt
+ && source.hasOwnProperty && source.hasOwnProperty("toString")) {
+ destination.toString = source.toString;
+ }
+ }
+ return destination;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Bounds.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Bounds
+ * Instances of this class represent bounding boxes. Data stored as left,
+ * bottom, right, top floats. All values are initialized to null, however,
+ * you should make sure you set them before using the bounds for anything.
+ *
+ * Possible use case:
+ * (code)
+ * bounds = new OpenLayers.Bounds();
+ * bounds.extend(new OpenLayers.LonLat(4,5));
+ * bounds.extend(new OpenLayers.LonLat(5,6));
+ * bounds.toBBOX(); // returns 4,5,5,6
+ * (end)
+ */
+OpenLayers.Bounds = OpenLayers.Class({
+
+ /**
+ * Property: left
+ * {Number} Minimum horizontal coordinate.
+ */
+ left: null,
+
+ /**
+ * Property: bottom
+ * {Number} Minimum vertical coordinate.
+ */
+ bottom: null,
+
+ /**
+ * Property: right
+ * {Number} Maximum horizontal coordinate.
+ */
+ right: null,
+
+ /**
+ * Property: top
+ * {Number} Maximum vertical coordinate.
+ */
+ top: null,
+
+ /**
+ * Property: centerLonLat
+ * {<OpenLayers.LonLat>} A cached center location. This should not be
+ * accessed directly. Use <getCenterLonLat> instead.
+ */
+ centerLonLat: null,
+
+ /**
+ * Constructor: OpenLayers.Bounds
+ * Construct a new bounds object. Coordinates can either be passed as four
+ * arguments, or as a single argument.
+ *
+ * Parameters (four arguments):
+ * left - {Number} The left bounds of the box. Note that for width
+ * calculations, this is assumed to be less than the right value.
+ * bottom - {Number} The bottom bounds of the box. Note that for height
+ * calculations, this is assumed to be less than the top value.
+ * right - {Number} The right bounds.
+ * top - {Number} The top bounds.
+ *
+ * Parameters (single argument):
+ * bounds - {Array(Number)} [left, bottom, right, top]
+ */
+ initialize: function(left, bottom, right, top) {
+ if (OpenLayers.Util.isArray(left)) {
+ top = left[3];
+ right = left[2];
+ bottom = left[1];
+ left = left[0];
+ }
+ if (left != null) {
+ this.left = OpenLayers.Util.toFloat(left);
+ }
+ if (bottom != null) {
+ this.bottom = OpenLayers.Util.toFloat(bottom);
+ }
+ if (right != null) {
+ this.right = OpenLayers.Util.toFloat(right);
+ }
+ if (top != null) {
+ this.top = OpenLayers.Util.toFloat(top);
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a cloned instance of this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A fresh copy of the bounds
+ */
+ clone:function() {
+ return new OpenLayers.Bounds(this.left, this.bottom,
+ this.right, this.top);
+ },
+
+ /**
+ * Method: equals
+ * Test a two bounds for equivalence.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object has the same left,
+ * right, top, bottom components as this. Note that if bounds
+ * passed in is null, returns false.
+ */
+ equals:function(bounds) {
+ var equals = false;
+ if (bounds != null) {
+ equals = ((this.left == bounds.left) &&
+ (this.right == bounds.right) &&
+ (this.top == bounds.top) &&
+ (this.bottom == bounds.bottom));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: toString
+ * Returns a string representation of the bounds object.
+ *
+ * Returns:
+ * {String} String representation of bounds object.
+ */
+ toString:function() {
+ return [this.left, this.bottom, this.right, this.top].join(",");
+ },
+
+ /**
+ * APIMethod: toArray
+ * Returns an array representation of the bounds object.
+ *
+ * Returns an array of left, bottom, right, top properties, or -- when the
+ * optional parameter is true -- an array of the bottom, left, top,
+ * right properties.
+ *
+ * Parameters:
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {Array} array of left, bottom, right, top
+ */
+ toArray: function(reverseAxisOrder) {
+ if (reverseAxisOrder === true) {
+ return [this.bottom, this.left, this.top, this.right];
+ } else {
+ return [this.left, this.bottom, this.right, this.top];
+ }
+ },
+
+ /**
+ * APIMethod: toBBOX
+ * Returns a boundingbox-string representation of the bounds object.
+ *
+ * Parameters:
+ * decimal - {Integer} How many significant digits in the bbox coords?
+ * Default is 6
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {String} Simple String representation of bounds object.
+ * (e.g. "5,42,10,45")
+ */
+ toBBOX:function(decimal, reverseAxisOrder) {
+ if (decimal== null) {
+ decimal = 6;
+ }
+ var mult = Math.pow(10, decimal);
+ var xmin = Math.round(this.left * mult) / mult;
+ var ymin = Math.round(this.bottom * mult) / mult;
+ var xmax = Math.round(this.right * mult) / mult;
+ var ymax = Math.round(this.top * mult) / mult;
+ if (reverseAxisOrder === true) {
+ return ymin + "," + xmin + "," + ymax + "," + xmax;
+ } else {
+ return xmin + "," + ymin + "," + xmax + "," + ymax;
+ }
+ },
+
+ /**
+ * APIMethod: toGeometry
+ * Create a new polygon geometry based on this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
+ * of this bounds.
+ */
+ toGeometry: function() {
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(this.left, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.top),
+ new OpenLayers.Geometry.Point(this.left, this.top)
+ ])
+ ]);
+ },
+
+ /**
+ * APIMethod: getWidth
+ * Returns the width of the bounds.
+ *
+ * Returns:
+ * {Float} The width of the bounds (right minus left).
+ */
+ getWidth:function() {
+ return (this.right - this.left);
+ },
+
+ /**
+ * APIMethod: getHeight
+ * Returns the height of the bounds.
+ *
+ * Returns:
+ * {Float} The height of the bounds (top minus bottom).
+ */
+ getHeight:function() {
+ return (this.top - this.bottom);
+ },
+
+ /**
+ * APIMethod: getSize
+ * Returns an <OpenLayers.Size> object of the bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size of the bounds.
+ */
+ getSize:function() {
+ return new OpenLayers.Size(this.getWidth(), this.getHeight());
+ },
+
+ /**
+ * APIMethod: getCenterPixel
+ * Returns the <OpenLayers.Pixel> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
+ */
+ getCenterPixel:function() {
+ return new OpenLayers.Pixel( (this.left + this.right) / 2,
+ (this.bottom + this.top) / 2);
+ },
+
+ /**
+ * APIMethod: getCenterLonLat
+ * Returns the <OpenLayers.LonLat> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The center of the bounds in map space.
+ */
+ getCenterLonLat:function() {
+ if(!this.centerLonLat) {
+ this.centerLonLat = new OpenLayers.LonLat(
+ (this.left + this.right) / 2, (this.bottom + this.top) / 2
+ );
+ }
+ return this.centerLonLat;
+ },
+
+ /**
+ * APIMethod: scale
+ * Scales the bounds around a pixel or lonlat. Note that the new
+ * bounds may return non-integer properties, even if a pixel
+ * is passed.
+ *
+ * Parameters:
+ * ratio - {Float}
+ * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+ * Default is center.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
+ * from origin.
+ */
+ scale: function(ratio, origin){
+ if(origin == null){
+ origin = this.getCenterLonLat();
+ }
+
+ var origx,origy;
+
+ // get origin coordinates
+ if(origin.CLASS_NAME == "OpenLayers.LonLat"){
+ origx = origin.lon;
+ origy = origin.lat;
+ } else {
+ origx = origin.x;
+ origy = origin.y;
+ }
+
+ var left = (this.left - origx) * ratio + origx;
+ var bottom = (this.bottom - origy) * ratio + origy;
+ var right = (this.right - origx) * ratio + origx;
+ var top = (this.top - origy) * ratio + origy;
+
+ return new OpenLayers.Bounds(left, bottom, right, top);
+ },
+
+ /**
+ * APIMethod: add
+ * Shifts the coordinates of the bound by the given horizontal and vertical
+ * deltas.
+ *
+ * (start code)
+ * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+ * bounds.toString();
+ * // => "0,0,10,10"
+ *
+ * bounds.add(-1.5, 4).toString();
+ * // => "-1.5,4,8.5,14"
+ * (end)
+ *
+ * This method will throw a TypeError if it is passed null as an argument.
+ *
+ * Parameters:
+ * x - {Float} horizontal delta
+ * y - {Float} vertical delta
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
+ * this, but shifted by the passed-in x and y values.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Bounds.add cannot receive null values');
+ }
+ return new OpenLayers.Bounds(this.left + x, this.bottom + y,
+ this.right + x, this.top + y);
+ },
+
+ /**
+ * APIMethod: extend
+ * Extend the bounds to include the <OpenLayers.LonLat>,
+ * <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
+ *
+ * Please note that this function assumes that left < right and
+ * bottom < top.
+ *
+ * Parameters:
+ * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
+ * <OpenLayers.Bounds>} The object to be included in the new bounds
+ * object.
+ */
+ extend:function(object) {
+ if (object) {
+ switch(object.CLASS_NAME) {
+ case "OpenLayers.LonLat":
+ this.extendXY(object.lon, object.lat);
+ break;
+ case "OpenLayers.Geometry.Point":
+ this.extendXY(object.x, object.y);
+ break;
+
+ case "OpenLayers.Bounds":
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ( (this.left == null) || (object.left < this.left)) {
+ this.left = object.left;
+ }
+ if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
+ this.bottom = object.bottom;
+ }
+ if ( (this.right == null) || (object.right > this.right) ) {
+ this.right = object.right;
+ }
+ if ( (this.top == null) || (object.top > this.top) ) {
+ this.top = object.top;
+ }
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: extendXY
+ * Extend the bounds to include the XY coordinate specified.
+ *
+ * Parameters:
+ * x - {number} The X part of the the coordinate.
+ * y - {number} The Y part of the the coordinate.
+ */
+ extendXY:function(x, y) {
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ((this.left == null) || (x < this.left)) {
+ this.left = x;
+ }
+ if ((this.bottom == null) || (y < this.bottom)) {
+ this.bottom = y;
+ }
+ if ((this.right == null) || (x > this.right)) {
+ this.right = x;
+ }
+ if ((this.top == null) || (y > this.top)) {
+ this.top = y;
+ }
+ },
+
+ /**
+ * APIMethod: containsLonLat
+ * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * options - {Object} Optional parameters
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Whether or not to include the border.
+ * Default is true.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
+ * ll will be considered as contained if it exceeds the world bounds,
+ * but can be wrapped around the dateline so it is contained by this
+ * bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in lonlat is within this bounds.
+ */
+ containsLonLat: function(ll, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ var contains = this.contains(ll.lon, ll.lat, options.inclusive),
+ worldBounds = options.worldBounds;
+ if (worldBounds && !contains) {
+ var worldWidth = worldBounds.getWidth();
+ var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
+ var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
+ contains = this.containsLonLat({
+ lon: ll.lon - worldsAway * worldWidth,
+ lat: ll.lat
+ }, {inclusive: options.inclusive});
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: containsPixel
+ * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in pixel is within this bounds.
+ */
+ containsPixel:function(px, inclusive) {
+ return this.contains(px.x, px.y, inclusive);
+ },
+
+ /**
+ * APIMethod: contains
+ * Returns whether the bounds object contains the given x and y.
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the passed-in coordinates are within this
+ * bounds.
+ */
+ contains:function(x, y, inclusive) {
+ //set default
+ if (inclusive == null) {
+ inclusive = true;
+ }
+
+ if (x == null || y == null) {
+ return false;
+ }
+
+ x = OpenLayers.Util.toFloat(x);
+ y = OpenLayers.Util.toFloat(y);
+
+ var contains = false;
+ if (inclusive) {
+ contains = ((x >= this.left) && (x <= this.right) &&
+ (y >= this.bottom) && (y <= this.top));
+ } else {
+ contains = ((x > this.left) && (x < this.right) &&
+ (y > this.bottom) && (y < this.top));
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: intersectsBounds
+ * Determine whether the target bounds intersects this bounds. Bounds are
+ * considered intersecting if any of their edges intersect or if one
+ * bounds contains the other.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * options - {Object} Optional parameters.
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Treat coincident borders as intersecting. Default
+ * is true. If false, bounds that do not overlap but only touch at the
+ * border will not be considered as intersecting.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
+ * bounds will be considered as intersecting if they intersect when
+ * shifted to within the world bounds. This applies only to bounds that
+ * cross or are completely outside the world bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object intersects this bounds.
+ */
+ intersectsBounds:function(bounds, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ if (options.worldBounds) {
+ var self = this.wrapDateLine(options.worldBounds);
+ bounds = bounds.wrapDateLine(options.worldBounds);
+ } else {
+ self = this;
+ }
+ if (options.inclusive == null) {
+ options.inclusive = true;
+ }
+ var intersects = false;
+ var mightTouch = (
+ self.left == bounds.right ||
+ self.right == bounds.left ||
+ self.top == bounds.bottom ||
+ self.bottom == bounds.top
+ );
+
+ // if the two bounds only touch at an edge, and inclusive is false,
+ // then the bounds don't *really* intersect.
+ if (options.inclusive || !mightTouch) {
+ // otherwise, if one of the boundaries even partially contains another,
+ // inclusive of the edges, then they do intersect.
+ var inBottom = (
+ ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
+ ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
+ );
+ var inTop = (
+ ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
+ ((self.top > bounds.bottom) && (self.top < bounds.top))
+ );
+ var inLeft = (
+ ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
+ ((self.left >= bounds.left) && (self.left <= bounds.right))
+ );
+ var inRight = (
+ ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
+ ((self.right >= bounds.left) && (self.right <= bounds.right))
+ );
+ intersects = ((inBottom || inTop) && (inLeft || inRight));
+ }
+ // document me
+ if (options.worldBounds && !intersects) {
+ var world = options.worldBounds;
+ var width = world.getWidth();
+ var selfCrosses = !world.containsBounds(self);
+ var boundsCrosses = !world.containsBounds(bounds);
+ if (selfCrosses && !boundsCrosses) {
+ bounds = bounds.add(-width, 0);
+ intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
+ } else if (boundsCrosses && !selfCrosses) {
+ self = self.add(-width, 0);
+ intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});
+ }
+ }
+ return intersects;
+ },
+
+ /**
+ * APIMethod: containsBounds
+ * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
+ *
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * partial - {Boolean} If any of the target corners is within this bounds
+ * consider the bounds contained. Default is false. If false, the
+ * entire target bounds must be contained within this bounds.
+ * inclusive - {Boolean} Treat shared edges as contained. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object is contained within this bounds.
+ */
+ containsBounds:function(bounds, partial, inclusive) {
+ if (partial == null) {
+ partial = false;
+ }
+ if (inclusive == null) {
+ inclusive = true;
+ }
+ var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
+ var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
+ var topLeft = this.contains(bounds.left, bounds.top, inclusive);
+ var topRight = this.contains(bounds.right, bounds.top, inclusive);
+
+ return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
+ : (bottomLeft && bottomRight && topLeft && topRight);
+ },
+
+ /**
+ * APIMethod: determineQuadrant
+ * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
+ * <OpenLayers.LonLat> lies.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
+ * coordinate lies.
+ */
+ determineQuadrant: function(lonlat) {
+
+ var quadrant = "";
+ var center = this.getCenterLonLat();
+
+ quadrant += (lonlat.lat < center.lat) ? "b" : "t";
+ quadrant += (lonlat.lon < center.lon) ? "l" : "r";
+
+ return quadrant;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the Bounds object from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ // clear cached center location
+ this.centerLonLat = null;
+ var ll = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.bottom}, source, dest);
+ var lr = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.bottom}, source, dest);
+ var ul = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.top}, source, dest);
+ var ur = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.top}, source, dest);
+ this.left = Math.min(ll.x, ul.x);
+ this.bottom = Math.min(ll.y, lr.y);
+ this.right = Math.max(lr.x, ur.x);
+ this.top = Math.max(ul.y, ur.y);
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ * Wraps the bounds object around the dateline.
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ * options - {Object} Some possible options are:
+ *
+ * Allowed Options:
+ * leftTolerance - {float} Allow for a margin of error
+ * with the 'left' value of this
+ * bound.
+ * Default is 0.
+ * rightTolerance - {float} Allow for a margin of error
+ * with the 'right' value of
+ * this bound.
+ * Default is 0.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent). Note that this function only returns
+ * a different bounds value if this bounds is
+ * *entirely* outside of the maxExtent. If this
+ * bounds straddles the dateline (is part in/part
+ * out of maxExtent), the returned bounds will always
+ * cross the left edge of the given maxExtent.
+ *.
+ */
+ wrapDateLine: function(maxExtent, options) {
+ options = options || {};
+
+ var leftTolerance = options.leftTolerance || 0;
+ var rightTolerance = options.rightTolerance || 0;
+
+ var newBounds = this.clone();
+
+ if (maxExtent) {
+ var width = maxExtent.getWidth();
+
+ //shift right?
+ while (newBounds.left < maxExtent.left &&
+ newBounds.right - rightTolerance <= maxExtent.left ) {
+ newBounds = newBounds.add(width, 0);
+ }
+
+ //shift left?
+ while (newBounds.left + leftTolerance >= maxExtent.right &&
+ newBounds.right > maxExtent.right ) {
+ newBounds = newBounds.add(-width, 0);
+ }
+
+ // crosses right only? force left
+ var newLeft = newBounds.left + leftTolerance;
+ if (newLeft < maxExtent.right && newLeft > maxExtent.left &&
+ newBounds.right - rightTolerance > maxExtent.right) {
+ newBounds = newBounds.add(-width, 0);
+ }
+ }
+
+ return newBounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Bounds"
+});
+
+/**
+ * APIFunction: fromString
+ * Alternative constructor that builds a new OpenLayers.Bounds from a
+ * parameter string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromString("5,42,10,45");
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
+ * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the
+ * passed-in String.
+ */
+OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
+ var bounds = str.split(",");
+ return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
+};
+
+/**
+ * APIFunction: fromArray
+ * Alternative constructor that builds a new OpenLayers.Bounds from an array.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
+ * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
+ */
+OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
+ return reverseAxisOrder === true ?
+ new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
+ new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
+};
+
+/**
+ * APIFunction: fromSize
+ * Alternative constructor that builds a new OpenLayers.Bounds from a size.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(0, 20, 10, 0);
+ * (end)
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
+ * both 'w' and 'h' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
+ */
+OpenLayers.Bounds.fromSize = function(size) {
+ return new OpenLayers.Bounds(0,
+ size.h,
+ size.w,
+ 0);
+};
+
+/**
+ * Function: oppositeQuadrant
+ * Get the opposite quadrant for a given quadrant string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.oppositeQuadrant( "tl" );
+ * // => "br"
+ *
+ * OpenLayers.Bounds.oppositeQuadrant( "tr" );
+ * // => "bl"
+ * (end)
+ *
+ * Parameters:
+ * quadrant - {String} two character quadrant shortstring
+ *
+ * Returns:
+ * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if
+ * you pass in "bl" it returns "tr", if you pass in "br" it
+ * returns "tl", etc.
+ */
+OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
+ var opp = "";
+
+ opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
+ opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
+
+ return opp;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Element.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ */
+
+/**
+ * Namespace: OpenLayers.Element
+ */
+OpenLayers.Element = {
+
+ /**
+ * APIFunction: visible
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Boolean} Is the element visible?
+ */
+ visible: function(element) {
+ return OpenLayers.Util.getElement(element).style.display != 'none';
+ },
+
+ /**
+ * APIFunction: toggle
+ * Toggle the visibility of element(s) passed in
+ *
+ * Parameters:
+ * element - {DOMElement} Actually user can pass any number of elements
+ */
+ toggle: function() {
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ var display = OpenLayers.Element.visible(element) ? 'none'
+ : '';
+ element.style.display = display;
+ }
+ },
+
+ /**
+ * APIFunction: remove
+ * Remove the specified element from the DOM.
+ *
+ * Parameters:
+ * element - {DOMElement}
+ */
+ remove: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ element.parentNode.removeChild(element);
+ },
+
+ /**
+ * APIFunction: getHeight
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Integer} The offset height of the element passed in
+ */
+ getHeight: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ return element.offsetHeight;
+ },
+
+ /**
+ * Function: hasClass
+ * Tests if an element has the given CSS class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to search for.
+ *
+ * Returns:
+ * {Boolean} The element has the given class name.
+ */
+ hasClass: function(element, name) {
+ var names = element.className;
+ return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
+ },
+
+ /**
+ * Function: addClass
+ * Add a CSS class name to an element. Safe where element already has
+ * the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to add.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ addClass: function(element, name) {
+ if(!OpenLayers.Element.hasClass(element, name)) {
+ element.className += (element.className ? " " : "") + name;
+ }
+ return element;
+ },
+
+ /**
+ * Function: removeClass
+ * Remove a CSS class name from an element. Safe where element does not
+ * have the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to remove.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ removeClass: function(element, name) {
+ var names = element.className;
+ if(names) {
+ element.className = OpenLayers.String.trim(
+ names.replace(
+ new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
+ )
+ );
+ }
+ return element;
+ },
+
+ /**
+ * Function: toggleClass
+ * Remove a CSS class name from an element if it exists. Add the class name
+ * if it doesn't exist.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to toggle.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ toggleClass: function(element, name) {
+ if(OpenLayers.Element.hasClass(element, name)) {
+ OpenLayers.Element.removeClass(element, name);
+ } else {
+ OpenLayers.Element.addClass(element, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIFunction: getStyle
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * style - {?}
+ *
+ * Returns:
+ * {?}
+ */
+ getStyle: function(element, style) {
+ element = OpenLayers.Util.getElement(element);
+
+ var value = null;
+ if (element && element.style) {
+ value = element.style[OpenLayers.String.camelize(style)];
+ if (!value) {
+ if (document.defaultView &&
+ document.defaultView.getComputedStyle) {
+
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[OpenLayers.String.camelize(style)];
+ }
+ }
+
+ var positions = ['left', 'top', 'right', 'bottom'];
+ if (window.opera &&
+ (OpenLayers.Util.indexOf(positions,style) != -1) &&
+ (OpenLayers.Element.getStyle(element, 'position') == 'static')) {
+ value = 'auto';
+ }
+ }
+
+ return value == 'auto' ? null : value;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/LonLat.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.LonLat
+ * This class represents a longitude and latitude pair
+ */
+OpenLayers.LonLat = OpenLayers.Class({
+
+ /**
+ * APIProperty: lon
+ * {Float} The x-axis coodinate in map units
+ */
+ lon: 0.0,
+
+ /**
+ * APIProperty: lat
+ * {Float} The y-axis coordinate in map units
+ */
+ lat: 0.0,
+
+ /**
+ * Constructor: OpenLayers.LonLat
+ * Create a new map location. Coordinates can be passed either as two
+ * arguments, or as a single argument.
+ *
+ * Parameters (two arguments):
+ * lon - {Number} The x-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Longitude. Otherwise,
+ * it will be the x coordinate of the map location in your map units.
+ * lat - {Number} The y-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Latitude. Otherwise,
+ * it will be the y coordinate of the map location in your map units.
+ *
+ * Parameters (single argument):
+ * location - {Array(Float)} [lon, lat]
+ */
+ initialize: function(lon, lat) {
+ if (OpenLayers.Util.isArray(lon)) {
+ lat = lon[1];
+ lon = lon[0];
+ }
+ this.lon = OpenLayers.Util.toFloat(lon);
+ this.lat = OpenLayers.Util.toFloat(lat);
+ },
+
+ /**
+ * Method: toString
+ * Return a readable string version of the lonlat
+ *
+ * Returns:
+ * {String} String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"lon=5,lat=42"</i>)
+ */
+ toString:function() {
+ return ("lon=" + this.lon + ",lat=" + this.lat);
+ },
+
+ /**
+ * APIMethod: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"5, 42"</i>)
+ */
+ toShortString:function() {
+ return (this.lon + ", " + this.lat);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon
+ * and lat values
+ */
+ clone:function() {
+ return new OpenLayers.LonLat(this.lon, this.lat);
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and
+ * lat passed-in added to this's.
+ */
+ add:function(lon, lat) {
+ if ( (lon == null) || (lat == null) ) {
+ throw new TypeError('LonLat.add cannot receive null values');
+ }
+ return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon),
+ this.lat + OpenLayers.Util.toFloat(lat));
+ },
+
+ /**
+ * APIMethod: equals
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Boolean value indicating whether the passed-in
+ * <OpenLayers.LonLat> object has the same lon and lat
+ * components as this.
+ * Note: if ll passed in is null, returns false
+ */
+ equals:function(ll) {
+ var equals = false;
+ if (ll != null) {
+ equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
+ (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the LonLat object from source to dest. This transformation is
+ * *in place*: if you want a *new* lonlat, use .clone() first.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ var point = OpenLayers.Projection.transform(
+ {'x': this.lon, 'y': this.lat}, source, dest);
+ this.lon = point.x;
+ this.lat = point.y;
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent)
+ */
+ wrapDateLine: function(maxExtent) {
+
+ var newLonLat = this.clone();
+
+ if (maxExtent) {
+ //shift right?
+ while (newLonLat.lon < maxExtent.left) {
+ newLonLat.lon += maxExtent.getWidth();
+ }
+
+ //shift left?
+ while (newLonLat.lon > maxExtent.right) {
+ newLonLat.lon -= maxExtent.getWidth();
+ }
+ }
+
+ return newLonLat;
+ },
+
+ CLASS_NAME: "OpenLayers.LonLat"
+});
+
+/**
+ * Function: fromString
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from a
+ * parameter string
+ *
+ * Parameters:
+ * str - {String} Comma-separated Lon,Lat coordinate string.
+ * (e.g. <i>"5,40"</i>)
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in String.
+ */
+OpenLayers.LonLat.fromString = function(str) {
+ var pair = str.split(",");
+ return new OpenLayers.LonLat(pair[0], pair[1]);
+};
+
+/**
+ * Function: fromArray
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from an
+ * array of two numbers that represent lon- and lat-values.
+ *
+ * Parameters:
+ * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in array.
+ */
+OpenLayers.LonLat.fromArray = function(arr) {
+ var gotArr = OpenLayers.Util.isArray(arr),
+ lon = gotArr && arr[0],
+ lat = gotArr && arr[1];
+ return new OpenLayers.LonLat(lon, lat);
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Pixel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Pixel
+ * This class represents a screen coordinate, in x and y coordinates
+ */
+OpenLayers.Pixel = OpenLayers.Class({
+
+ /**
+ * APIProperty: x
+ * {Number} The x coordinate
+ */
+ x: 0.0,
+
+ /**
+ * APIProperty: y
+ * {Number} The y coordinate
+ */
+ y: 0.0,
+
+ /**
+ * Constructor: OpenLayers.Pixel
+ * Create a new OpenLayers.Pixel instance
+ *
+ * Parameters:
+ * x - {Number} The x coordinate
+ * y - {Number} The y coordinate
+ *
+ * Returns:
+ * An instance of OpenLayers.Pixel
+ */
+ initialize: function(x, y) {
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * Method: toString
+ * Cast this object into a string
+ *
+ * Returns:
+ * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
+ */
+ toString:function() {
+ return ("x=" + this.x + ",y=" + this.y);
+ },
+
+ /**
+ * APIMethod: clone
+ * Return a clone of this pixel object
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A clone pixel
+ */
+ clone:function() {
+ return new OpenLayers.Pixel(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether one pixel is equivalent to another
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {Boolean} The point passed in as parameter is equal to this. Note that
+ * if px passed in is null, returns false.
+ */
+ equals:function(px) {
+ var equals = false;
+ if (px != null) {
+ equals = ((this.x == px.x && this.y == px.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Returns the distance to the pixel point passed in as a parameter.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Float} The pixel point passed in as parameter to calculate the
+ * distance to.
+ */
+ distanceTo:function(px) {
+ return Math.sqrt(
+ Math.pow(this.x - px.x, 2) +
+ Math.pow(this.y - px.y, 2)
+ );
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * values passed in.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Pixel.add cannot receive null values');
+ }
+ return new OpenLayers.Pixel(this.x + x, this.y + y);
+ },
+
+ /**
+ * APIMethod: offset
+ *
+ * Parameters
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * x&y values of the pixel passed in.
+ */
+ offset:function(px) {
+ var newPx = this.clone();
+ if (px) {
+ newPx = this.add(px.x, px.y);
+ }
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Pixel"
+});
+/* ======================================================================
+ OpenLayers/BaseTypes/Size.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Size
+ * Instances of this class represent a width/height pair
+ */
+OpenLayers.Size = OpenLayers.Class({
+
+ /**
+ * APIProperty: w
+ * {Number} width
+ */
+ w: 0.0,
+
+ /**
+ * APIProperty: h
+ * {Number} height
+ */
+ h: 0.0,
+
+
+ /**
+ * Constructor: OpenLayers.Size
+ * Create an instance of OpenLayers.Size
+ *
+ * Parameters:
+ * w - {Number} width
+ * h - {Number} height
+ */
+ initialize: function(w, h) {
+ this.w = parseFloat(w);
+ this.h = parseFloat(h);
+ },
+
+ /**
+ * Method: toString
+ * Return the string representation of a size object
+ *
+ * Returns:
+ * {String} The string representation of OpenLayers.Size object.
+ * (e.g. <i>"w=55,h=66"</i>)
+ */
+ toString:function() {
+ return ("w=" + this.w + ",h=" + this.h);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this size object
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
+ * values
+ */
+ clone:function() {
+ return new OpenLayers.Size(this.w, this.h);
+ },
+
+ /**
+ *
+ * APIMethod: equals
+ * Determine where this size is equal to another
+ *
+ * Parameters:
+ * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ *
+ * Returns:
+ * {Boolean} The passed in size has the same h and w properties as this one.
+ * Note that if sz passed in is null, returns false.
+ */
+ equals:function(sz) {
+ var equals = false;
+ if (sz != null) {
+ equals = ((this.w == sz.w && this.h == sz.h) ||
+ (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
+ }
+ return equals;
+ },
+
+ CLASS_NAME: "OpenLayers.Size"
+});
+/* ======================================================================
+ OpenLayers/Console.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Namespace: OpenLayers.Console
+ * The OpenLayers.Console namespace is used for debugging and error logging.
+ * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
+ * calls to OpenLayers.Console methods will get redirected to window.console.
+ * This makes use of the Firebug extension where available and allows for
+ * cross-browser debugging Firebug style.
+ *
+ * Note:
+ * Note that behavior will differ with the Firebug extention and Firebug Lite.
+ * Most notably, the Firebug Lite console does not currently allow for
+ * hyperlinks to code or for clicking on object to explore their properties.
+ *
+ */
+OpenLayers.Console = {
+ /**
+ * Create empty functions for all console methods. The real value of these
+ * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
+ * included. We explicitly require the Firebug Lite script to trigger
+ * functionality of the OpenLayers.Console methods.
+ */
+
+ /**
+ * APIFunction: log
+ * Log an object in the console. The Firebug Lite console logs string
+ * representation of objects. Given multiple arguments, they will
+ * be cast to strings and logged with a space delimiter. If the first
+ * argument is a string with printf-like formatting, subsequent arguments
+ * will be used in string substitution. Any additional arguments (beyond
+ * the number substituted in a format string) will be appended in a space-
+ * delimited line.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ log: function() {},
+
+ /**
+ * APIFunction: debug
+ * Writes a message to the console, including a hyperlink to the line
+ * where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ debug: function() {},
+
+ /**
+ * APIFunction: info
+ * Writes a message to the console with the visual "info" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ info: function() {},
+
+ /**
+ * APIFunction: warn
+ * Writes a message to the console with the visual "warning" icon and
+ * color coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ warn: function() {},
+
+ /**
+ * APIFunction: error
+ * Writes a message to the console with the visual "error" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ error: function() {},
+
+ /**
+ * APIFunction: userError
+ * A single interface for showing error messages to the user. The default
+ * behavior is a Javascript alert, though this can be overridden by
+ * reassigning OpenLayers.Console.userError to a different function.
+ *
+ * Expects a single error message
+ *
+ * Parameters:
+ * error - {Object}
+ */
+ userError: function(error) {
+ alert(error);
+ },
+
+ /**
+ * APIFunction: assert
+ * Tests that an expression is true. If not, it will write a message to
+ * the console and throw an exception.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ assert: function() {},
+
+ /**
+ * APIFunction: dir
+ * Prints an interactive listing of all properties of the object. This
+ * looks identical to the view that you would see in the DOM tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dir: function() {},
+
+ /**
+ * APIFunction: dirxml
+ * Prints the XML source tree of an HTML or XML element. This looks
+ * identical to the view that you would see in the HTML tab. You can click
+ * on any node to inspect it in the HTML tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dirxml: function() {},
+
+ /**
+ * APIFunction: trace
+ * Prints an interactive stack trace of JavaScript execution at the point
+ * where it is called. The stack trace details the functions on the stack,
+ * as well as the values that were passed as arguments to each function.
+ * You can click each function to take you to its source in the Script tab,
+ * and click each argument value to inspect it in the DOM or HTML tabs.
+ *
+ */
+ trace: function() {},
+
+ /**
+ * APIFunction: group
+ * Writes a message to the console and opens a nested block to indent all
+ * future messages sent to the console. Call OpenLayers.Console.groupEnd()
+ * to close the block.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ group: function() {},
+
+ /**
+ * APIFunction: groupEnd
+ * Closes the most recently opened block created by a call to
+ * OpenLayers.Console.group
+ */
+ groupEnd: function() {},
+
+ /**
+ * APIFunction: time
+ * Creates a new timer under the given name. Call
+ * OpenLayers.Console.timeEnd(name)
+ * with the same name to stop the timer and print the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ time: function() {},
+
+ /**
+ * APIFunction: timeEnd
+ * Stops a timer created by a call to OpenLayers.Console.time(name) and
+ * writes the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ timeEnd: function() {},
+
+ /**
+ * APIFunction: profile
+ * Turns on the JavaScript profiler. The optional argument title would
+ * contain the text to be printed in the header of the profile report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title for the profiler
+ */
+ profile: function() {},
+
+ /**
+ * APIFunction: profileEnd
+ * Turns off the JavaScript profiler and prints its report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ */
+ profileEnd: function() {},
+
+ /**
+ * APIFunction: count
+ * Writes the number of times that the line of code where count was called
+ * was executed. The optional argument title will print a message in
+ * addition to the number of the count.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title to be printed with count
+ */
+ count: function() {},
+
+ CLASS_NAME: "OpenLayers.Console"
+};
+
+/**
+ * Execute an anonymous function to extend the OpenLayers.Console namespace
+ * if the firebug.js script is included. This closure is used so that the
+ * "scripts" and "i" variables don't pollute the global namespace.
+ */
+(function() {
+ /**
+ * If Firebug Lite is included (before this script), re-route all
+ * OpenLayers.Console calls to the console object.
+ */
+ var scripts = document.getElementsByTagName("script");
+ for(var i=0, len=scripts.length; i<len; ++i) {
+ if(scripts[i].src.indexOf("firebug.js") != -1) {
+ if(console) {
+ OpenLayers.Util.extend(OpenLayers.Console, console);
+ break;
+ }
+ }
+ }
+})();
+/* ======================================================================
+ OpenLayers/Lang.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang
+ * Internationalization namespace. Contains dictionaries in various languages
+ * and methods to set and get the current language.
+ */
+OpenLayers.Lang = {
+
+ /**
+ * Property: code
+ * {String} Current language code to use in OpenLayers. Use the
+ * <setCode> method to set this value and the <getCode> method to
+ * retrieve it.
+ */
+ code: null,
+
+ /**
+ * APIProperty: defaultCode
+ * {String} Default language to use when a specific language can't be
+ * found. Default is "en".
+ */
+ defaultCode: "en",
+
+ /**
+ * APIFunction: getCode
+ * Get the current language code.
+ *
+ * Returns:
+ * {String} The current language code.
+ */
+ getCode: function() {
+ if(!OpenLayers.Lang.code) {
+ OpenLayers.Lang.setCode();
+ }
+ return OpenLayers.Lang.code;
+ },
+
+ /**
+ * APIFunction: setCode
+ * Set the language code for string translation. This code is used by
+ * the <OpenLayers.Lang.translate> method.
+ *
+ * Parameters:
+ * code - {String} These codes follow the IETF recommendations at
+ * http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the
+ * browser's language setting will be tested. If no <OpenLayers.Lang>
+ * dictionary exists for the code, the <OpenLayers.String.defaultLang>
+ * will be used.
+ */
+ setCode: function(code) {
+ var lang;
+ if(!code) {
+ code = (OpenLayers.BROWSER_NAME == "msie") ?
+ navigator.userLanguage : navigator.language;
+ }
+ var parts = code.split('-');
+ parts[0] = parts[0].toLowerCase();
+ if(typeof OpenLayers.Lang[parts[0]] == "object") {
+ lang = parts[0];
+ }
+
+ // check for regional extensions
+ if(parts[1]) {
+ var testLang = parts[0] + '-' + parts[1].toUpperCase();
+ if(typeof OpenLayers.Lang[testLang] == "object") {
+ lang = testLang;
+ }
+ }
+ if(!lang) {
+ OpenLayers.Console.warn(
+ 'Failed to find OpenLayers.Lang.' + parts.join("-") +
+ ' dictionary, falling back to default language'
+ );
+ lang = OpenLayers.Lang.defaultCode;
+ }
+
+ OpenLayers.Lang.code = lang;
+ },
+
+ /**
+ * APIMethod: translate
+ * Looks up a key from a dictionary based on the current language string.
+ * The value of <getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+ translate: function(key, context) {
+ var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
+ var message = dictionary && dictionary[key];
+ if(!message) {
+ // Message not found, fall back to message key
+ message = key;
+ }
+ if(context) {
+ message = OpenLayers.String.format(message, context);
+ }
+ return message;
+ }
+
+};
+
+
+/**
+ * APIMethod: OpenLayers.i18n
+ * Alias for <OpenLayers.Lang.translate>. Looks up a key from a dictionary
+ * based on the current language string. The value of
+ * <OpenLayers.Lang.getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+OpenLayers.i18n = OpenLayers.Lang.translate;
+/* ======================================================================
+ OpenLayers/Util.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/BaseTypes/LonLat.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: Util
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+
+/**
+ * Function: getElement
+ * This is the old $() from prototype
+ *
+ * Parameters:
+ * e - {String or DOMElement or Window}
+ *
+ * Returns:
+ * {Array(DOMElement) or DOMElement}
+ */
+OpenLayers.Util.getElement = function() {
+ var elements = [];
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ if (arguments.length == 1) {
+ return element;
+ }
+ elements.push(element);
+ }
+ return elements;
+};
+
+/**
+ * Function: isElement
+ * A cross-browser implementation of "e instanceof Element".
+ *
+ * Parameters:
+ * o - {Object} The object to test.
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.isElement = function(o) {
+ return !!(o && o.nodeType === 1);
+};
+
+/**
+ * Function: isArray
+ * Tests that the provided object is an array.
+ * This test handles the cross-IFRAME case not caught
+ * by "a instanceof Array" and should be used instead.
+ *
+ * Parameters:
+ * a - {Object} the object test.
+ *
+ * Returns:
+ * {Boolean} true if the object is an array.
+ */
+OpenLayers.Util.isArray = function(a) {
+ return (Object.prototype.toString.call(a) === '[object Array]');
+};
+
+/**
+ * Function: removeItem
+ * Remove an object from an array. Iterates through the array
+ * to find the item, then removes it.
+ *
+ * Parameters:
+ * array - {Array}
+ * item - {Object}
+ *
+ * Returns:
+ * {Array} A reference to the array
+ */
+OpenLayers.Util.removeItem = function(array, item) {
+ for(var i = array.length - 1; i >= 0; i--) {
+ if(array[i] == item) {
+ array.splice(i,1);
+ //break;more than once??
+ }
+ }
+ return array;
+};
+
+/**
+ * Function: indexOf
+ * Seems to exist already in FF, but not in MOZ.
+ *
+ * Parameters:
+ * array - {Array}
+ * obj - {*}
+ *
+ * Returns:
+ * {Integer} The index at which the first object was found in the array.
+ * If not found, returns -1.
+ */
+OpenLayers.Util.indexOf = function(array, obj) {
+ // use the build-in function if available.
+ if (typeof array.indexOf == "function") {
+ return array.indexOf(obj);
+ } else {
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (array[i] == obj) {
+ return i;
+ }
+ }
+ return -1;
+ }
+};
+
+
+/**
+ * Property: dotless
+ * {RegExp}
+ * Compiled regular expression to match dots ("."). This is used for replacing
+ * dots in identifiers. Because object identifiers are frequently used for
+ * DOM element identifiers by the library, we avoid using dots to make for
+ * more sensible CSS selectors.
+ *
+ * TODO: Use a module pattern to avoid bloating the API with stuff like this.
+ */
+OpenLayers.Util.dotless = /\./g;
+
+/**
+ * Function: modifyDOMElement
+ *
+ * Modifies many properties of a DOM element all at once. Passing in
+ * null to an individual parameter will avoid setting the attribute.
+ *
+ * Parameters:
+ * element - {DOMElement} DOM element to modify.
+ * id - {String} The element id attribute to set. Note that dots (".") will be
+ * replaced with underscore ("_") in setting the element id.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * position - {String} The position attribute. eg: absolute,
+ * relative, etc.
+ * border - {String} The style.border attribute. eg:
+ * solid black 2px
+ * overflow - {String} The style.overview attribute.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position,
+ border, overflow, opacity) {
+
+ if (id) {
+ element.id = id.replace(OpenLayers.Util.dotless, "_");
+ }
+ if (px) {
+ element.style.left = px.x + "px";
+ element.style.top = px.y + "px";
+ }
+ if (sz) {
+ element.style.width = sz.w + "px";
+ element.style.height = sz.h + "px";
+ }
+ if (position) {
+ element.style.position = position;
+ }
+ if (border) {
+ element.style.border = border;
+ }
+ if (overflow) {
+ element.style.overflow = overflow;
+ }
+ if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
+ element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
+ element.style.opacity = opacity;
+ } else if (parseFloat(opacity) == 1.0) {
+ element.style.filter = '';
+ element.style.opacity = '';
+ }
+};
+
+/**
+ * Function: createDiv
+ * Creates a new div and optionally set some standard attributes.
+ * Null may be passed to each parameter if you do not wish to
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
+ *
+ * Parameters:
+ * id - {String} An identifier for this element. If no id is
+ * passed an identifier will be created
+ * automatically. Note that dots (".") will be replaced with
+ * underscore ("_") when generating ids.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} A url pointing to an image to use as a
+ * background image.
+ * position - {String} The style.position value. eg: absolute,
+ * relative etc.
+ * border - {String} The the style.border value.
+ * eg: 2px solid black
+ * overflow - {String} The style.overflow value. Eg. hidden
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with the specified attributes.
+ */
+OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position,
+ border, overflow, opacity) {
+
+ var dom = document.createElement('div');
+
+ if (imgURL) {
+ dom.style.backgroundImage = 'url(' + imgURL + ')';
+ }
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "absolute";
+ }
+ OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position,
+ border, overflow, opacity);
+
+ return dom;
+};
+
+/**
+ * Function: createImage
+ * Creates an img element with specific attribute values.
+ *
+ * Parameters:
+ * id - {String} The id field for the img. If none assigned one will be
+ * automatically generated.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} The url to use as the image source.
+ * position - {String} The style.position value.
+ * border - {String} The border to place around the image.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Image created with the specified attributes.
+ */
+OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
+ opacity, delayDisplay) {
+
+ var image = document.createElement("img");
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "relative";
+ }
+ OpenLayers.Util.modifyDOMElement(image, id, px, sz, position,
+ border, null, opacity);
+
+ if (delayDisplay) {
+ image.style.display = "none";
+ function display() {
+ image.style.display = "";
+ OpenLayers.Event.stopObservingElement(image);
+ }
+ OpenLayers.Event.observe(image, "load", display);
+ OpenLayers.Event.observe(image, "error", display);
+ }
+
+ //set special properties
+ image.style.alt = id;
+ image.galleryImg = "no";
+ if (imgURL) {
+ image.src = imgURL;
+ }
+
+ return image;
+};
+
+/**
+ * Property: IMAGE_RELOAD_ATTEMPTS
+ * {Integer} How many times should we try to reload an image before giving up?
+ * Default is 0
+ */
+OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
+
+/**
+ * Property: alphaHackNeeded
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHackNeeded = null;
+
+/**
+ * Function: alphaHack
+ * Checks whether it's necessary (and possible) to use the png alpha
+ * hack which allows alpha transparency for png images under Internet
+ * Explorer.
+ *
+ * Returns:
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHack = function() {
+ if (OpenLayers.Util.alphaHackNeeded == null) {
+ var arVersion = navigator.appVersion.split("MSIE");
+ var version = parseFloat(arVersion[1]);
+ var filter = false;
+
+ // IEs4Lin dies when trying to access document.body.filters, because
+ // the property is there, but requires a DLL that can't be provided. This
+ // means that we need to wrap this in a try/catch so that this can
+ // continue.
+
+ try {
+ filter = !!(document.body.filters);
+ } catch (e) {}
+
+ OpenLayers.Util.alphaHackNeeded = (filter &&
+ (version >= 5.5) && (version < 7));
+ }
+ return OpenLayers.Util.alphaHackNeeded;
+};
+
+/**
+ * Function: modifyAlphaImageDiv
+ *
+ * Parameters:
+ * div - {DOMElement} Div containing Alpha-adjusted Image
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL,
+ position, border, sizing,
+ opacity) {
+
+ OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+ null, null, opacity);
+
+ var img = div.childNodes[0];
+
+ if (imgURL) {
+ img.src = imgURL;
+ }
+ OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz,
+ "relative", border);
+
+ if (OpenLayers.Util.alphaHack()) {
+ if(div.style.display != "none") {
+ div.style.display = "inline-block";
+ }
+ if (sizing == null) {
+ sizing = "scale";
+ }
+
+ div.style.filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img.src + "', " +
+ "sizingMethod='" + sizing + "')";
+ if (parseFloat(div.style.opacity) >= 0.0 &&
+ parseFloat(div.style.opacity) < 1.0) {
+ div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
+ }
+
+ img.style.filter = "alpha(opacity=0)";
+ }
+};
+
+/**
+ * Function: createAlphaImageDiv
+ *
+ * Parameters:
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is
+ * needed for transparency in IE, it is added.
+ */
+OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL,
+ position, border, sizing,
+ opacity, delayDisplay) {
+
+ var div = OpenLayers.Util.createDiv();
+ var img = OpenLayers.Util.createImage(null, null, null, null, null, null,
+ null, delayDisplay);
+ img.className = "olAlphaImg";
+ div.appendChild(img);
+
+ OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position,
+ border, sizing, opacity);
+
+ return div;
+};
+
+
+/**
+ * Function: upperCaseObject
+ * Creates a new hashtable and copies over all the keys from the
+ * passed-in object, but storing them under an uppercased
+ * version of the key at which they were stored.
+ *
+ * Parameters:
+ * object - {Object}
+ *
+ * Returns:
+ * {Object} A new Object with all the same keys but uppercased
+ */
+OpenLayers.Util.upperCaseObject = function (object) {
+ var uObject = {};
+ for (var key in object) {
+ uObject[key.toUpperCase()] = object[key];
+ }
+ return uObject;
+};
+
+/**
+ * Function: applyDefaults
+ * Takes an object and copies any properties that don't exist from
+ * another properties, by analogy with OpenLayers.Util.extend() from
+ * Prototype.js.
+ *
+ * Parameters:
+ * to - {Object} The destination object.
+ * from - {Object} The source object. Any properties of this object that
+ * are undefined in the to object will be set on the to object.
+ *
+ * Returns:
+ * {Object} A reference to the to object. Note that the to argument is modified
+ * in place and returned by this function.
+ */
+OpenLayers.Util.applyDefaults = function (to, from) {
+ to = to || {};
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object is an
+ * instance of window.Event.
+ */
+ var fromIsEvt = typeof window.Event == "function"
+ && from instanceof window.Event;
+
+ for (var key in from) {
+ if (to[key] === undefined ||
+ (!fromIsEvt && from.hasOwnProperty
+ && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
+ to[key] = from[key];
+ }
+ }
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+ if(!fromIsEvt && from && from.hasOwnProperty
+ && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
+ to.toString = from.toString;
+ }
+
+ return to;
+};
+
+/**
+ * Function: getParameterString
+ *
+ * Parameters:
+ * params - {Object}
+ *
+ * Returns:
+ * {String} A concatenation of the properties of an object in
+ * http parameter notation.
+ * (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
+ * If a parameter is actually a list, that parameter will then
+ * be set to a comma-seperated list of values (foo,bar) instead
+ * of being URL escaped (foo%3Abar).
+ */
+OpenLayers.Util.getParameterString = function(params) {
+ var paramsArray = [];
+
+ for (var key in params) {
+ var value = params[key];
+ if ((value != null) && (typeof value != 'function')) {
+ var encodedValue;
+ if (typeof value == 'object' && value.constructor == Array) {
+ /* value is an array; encode items and separate with "," */
+ var encodedItemArray = [];
+ var item;
+ for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
+ item = value[itemIndex];
+ encodedItemArray.push(encodeURIComponent(
+ (item === null || item === undefined) ? "" : item)
+ );
+ }
+ encodedValue = encodedItemArray.join(",");
+ }
+ else {
+ /* value is a string; simply encode */
+ encodedValue = encodeURIComponent(value);
+ }
+ paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
+ }
+ }
+
+ return paramsArray.join("&");
+};
+
+/**
+ * Function: urlAppend
+ * Appends a parameter string to a url. This function includes the logic for
+ * using the appropriate character (none, & or ?) to append to the url before
+ * appending the param string.
+ *
+ * Parameters:
+ * url - {String} The url to append to
+ * paramStr - {String} The param string to append
+ *
+ * Returns:
+ * {String} The new url
+ */
+OpenLayers.Util.urlAppend = function(url, paramStr) {
+ var newUrl = url;
+ if(paramStr) {
+ var parts = (url + " ").split(/[?&]/);
+ newUrl += (parts.pop() === " " ?
+ paramStr :
+ parts.length ? "&" + paramStr : "?" + paramStr);
+ }
+ return newUrl;
+};
+
+/**
+ * Function: getImagesLocation
+ *
+ * Returns:
+ * {String} The fully formatted image location string
+ */
+OpenLayers.Util.getImagesLocation = function() {
+ return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
+};
+
+/**
+ * Function: getImageLocation
+ *
+ * Returns:
+ * {String} The fully formatted location string for a specified image
+ */
+OpenLayers.Util.getImageLocation = function(image) {
+ return OpenLayers.Util.getImagesLocation() + image;
+};
+
+
+/**
+ * Function: Try
+ * Execute functions until one of them doesn't throw an error.
+ * Capitalized because "try" is a reserved word in JavaScript.
+ * Taken directly from OpenLayers.Util.Try()
+ *
+ * Parameters:
+ * [*] - {Function} Any number of parameters may be passed to Try()
+ * It will attempt to execute each of them until one of them
+ * successfully executes.
+ * If none executes successfully, returns null.
+ *
+ * Returns:
+ * {*} The value returned by the first successfully executed function.
+ */
+OpenLayers.Util.Try = function() {
+ var returnValue = null;
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+};
+
+/**
+ * Function: getXmlNodeValue
+ *
+ * Parameters:
+ * node - {XMLNode}
+ *
+ * Returns:
+ * {String} The text value of the given node, without breaking in firefox or IE
+ */
+OpenLayers.Util.getXmlNodeValue = function(node) {
+ var val = null;
+ OpenLayers.Util.Try(
+ function() {
+ val = node.text;
+ if (!val) {
+ val = node.textContent;
+ }
+ if (!val) {
+ val = node.firstChild.nodeValue;
+ }
+ },
+ function() {
+ val = node.textContent;
+ });
+ return val;
+};
+
+/**
+ * Function: mouseLeft
+ *
+ * Parameters:
+ * evt - {Event}
+ * div - {HTMLDivElement}
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.mouseLeft = function (evt, div) {
+ // start with the element to which the mouse has moved
+ var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
+ // walk up the DOM tree.
+ while (target != div && target != null) {
+ target = target.parentNode;
+ }
+ // if the target we stop at isn't the div, then we've left the div.
+ return (target != div);
+};
+
+/**
+ * Property: precision
+ * {Number} The number of significant digits to retain to avoid
+ * floating point precision errors.
+ *
+ * We use 14 as a "safe" default because, although IEEE 754 double floats
+ * (standard on most modern operating systems) support up to about 16
+ * significant digits, 14 significant digits are sufficient to represent
+ * sub-millimeter accuracy in any coordinate system that anyone is likely to
+ * use with OpenLayers.
+ *
+ * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
+ * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
+ * with certain projections, e.g. spherical Mercator.
+ *
+ */
+OpenLayers.Util.DEFAULT_PRECISION = 14;
+
+/**
+ * Function: toFloat
+ * Convenience method to cast an object to a Number, rounded to the
+ * desired floating point precision.
+ *
+ * Parameters:
+ * number - {Number} The number to cast and round.
+ * precision - {Number} An integer suitable for use with
+ * Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
+ * If set to 0, no rounding is performed.
+ *
+ * Returns:
+ * {Number} The cast, rounded number.
+ */
+OpenLayers.Util.toFloat = function (number, precision) {
+ if (precision == null) {
+ precision = OpenLayers.Util.DEFAULT_PRECISION;
+ }
+ if (typeof number !== "number") {
+ number = parseFloat(number);
+ }
+ return precision === 0 ? number :
+ parseFloat(number.toPrecision(precision));
+};
+
+/**
+ * Function: rad
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
+
+/**
+ * Function: deg
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
+
+/**
+ * Property: VincentyConstants
+ * {Object} Constants for Vincenty functions.
+ */
+OpenLayers.Util.VincentyConstants = {
+ a: 6378137,
+ b: 6356752.3142,
+ f: 1/298.257223563
+};
+
+/**
+ * APIFunction: distVincenty
+ * Given two objects representing points with geographic coordinates, this
+ * calculates the distance between those points on the surface of an
+ * ellipsoid.
+ *
+ * Parameters:
+ * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ *
+ * Returns:
+ * {Float} The distance (in km) between the two input points as measured on an
+ * ellipsoid. Note that the input point objects must be in geographic
+ * coordinates (decimal degrees) and the return distance is in kilometers.
+ */
+OpenLayers.Util.distVincenty = function(p1, p2) {
+ var ct = OpenLayers.Util.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var L = OpenLayers.Util.rad(p2.lon - p1.lon);
+ var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
+ var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
+ var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
+ var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
+ var lambda = L, lambdaP = 2*Math.PI;
+ var iterLimit = 20;
+ while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
+ var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
+ var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
+ (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
+ if (sinSigma==0) {
+ return 0; // co-incident points
+ }
+ var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
+ var sigma = Math.atan2(sinSigma, cosSigma);
+ var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
+ var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
+ var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ lambdaP = lambda;
+ lambda = L + (1-C) * f * Math.sin(alpha) *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+ }
+ if (iterLimit==0) {
+ return NaN; // formula failed to converge
+ }
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ var s = b*A*(sigma-deltaSigma);
+ var d = s.toFixed(3)/1000; // round to 1mm precision
+ return d;
+};
+
+/**
+ * APIFunction: destinationVincenty
+ * Calculate destination point given start point lat/long (numeric degrees),
+ * bearing (numeric degrees) & distance (in m).
+ * Adapted from Chris Veness work, see
+ * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
+ * properties) The start point.
+ * brng - {Float} The bearing (degrees).
+ * dist - {Float} The ground distance (meters).
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The destination point.
+ */
+OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
+ var u = OpenLayers.Util;
+ var ct = u.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var lon1 = lonlat.lon;
+ var lat1 = lonlat.lat;
+
+ var s = dist;
+ var alpha1 = u.rad(brng);
+ var sinAlpha1 = Math.sin(alpha1);
+ var cosAlpha1 = Math.cos(alpha1);
+
+ var tanU1 = (1-f) * Math.tan(u.rad(lat1));
+ var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
+ var sigma1 = Math.atan2(tanU1, cosAlpha1);
+ var sinAlpha = cosU1 * sinAlpha1;
+ var cosSqAlpha = 1 - sinAlpha*sinAlpha;
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+
+ var sigma = s / (b*A), sigmaP = 2*Math.PI;
+ while (Math.abs(sigma-sigmaP) > 1e-12) {
+ var cos2SigmaM = Math.cos(2*sigma1 + sigma);
+ var sinSigma = Math.sin(sigma);
+ var cosSigma = Math.cos(sigma);
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ sigmaP = sigma;
+ sigma = s / (b*A) + deltaSigma;
+ }
+
+ var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
+ var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
+ (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+ var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ var L = lambda - (1-C) * f * sinAlpha *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+
+ var revAz = Math.atan2(sinAlpha, -tmp); // final bearing
+
+ return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
+};
+
+/**
+ * Function: getParameters
+ * Parse the parameters from a URL or from the current page itself into a
+ * JavaScript Object. Note that parameter values with commas are separated
+ * out into an Array.
+ *
+ * Parameters:
+ * url - {String} Optional url used to extract the query string.
+ * If url is null or is not supplied, query string is taken
+ * from the page location.
+ * options - {Object} Additional options. Optional.
+ *
+ * Valid options:
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object of key/value pairs from the query string.
+ */
+OpenLayers.Util.getParameters = function(url, options) {
+ options = options || {};
+ // if no url specified, take it from the location bar
+ url = (url === null || url === undefined) ? window.location.href : url;
+
+ //parse out parameters portion of url string
+ var paramsString = "";
+ if (OpenLayers.String.contains(url, '?')) {
+ var start = url.indexOf('?') + 1;
+ var end = OpenLayers.String.contains(url, "#") ?
+ url.indexOf('#') : url.length;
+ paramsString = url.substring(start, end);
+ }
+
+ var parameters = {};
+ var pairs = paramsString.split(/[&;]/);
+ for(var i=0, len=pairs.length; i<len; ++i) {
+ var keyValue = pairs[i].split('=');
+ if (keyValue[0]) {
+
+ var key = keyValue[0];
+ try {
+ key = decodeURIComponent(key);
+ } catch (err) {
+ key = unescape(key);
+ }
+
+ // being liberal by replacing "+" with " "
+ var value = (keyValue[1] || '').replace(/\+/g, " ");
+
+ try {
+ value = decodeURIComponent(value);
+ } catch (err) {
+ value = unescape(value);
+ }
+
+ // follow OGC convention of comma delimited values
+ if (options.splitArgs !== false) {
+ value = value.split(",");
+ }
+
+ //if there's only one value, do not return as array
+ if (value.length == 1) {
+ value = value[0];
+ }
+
+ parameters[key] = value;
+ }
+ }
+ return parameters;
+};
+
+/**
+ * Property: lastSeqID
+ * {Integer} The ever-incrementing count variable.
+ * Used for generating unique ids.
+ */
+OpenLayers.Util.lastSeqID = 0;
+
+/**
+ * Function: createUniqueID
+ * Create a unique identifier for this session. Each time this function
+ * is called, a counter is incremented. The return will be the optional
+ * prefix (defaults to "id_") appended with the counter value.
+ *
+ * Parameters:
+ * prefix - {String} Optional string to prefix unique id. Default is "id_".
+ * Note that dots (".") in the prefix will be replaced with underscore ("_").
+ *
+ * Returns:
+ * {String} A unique id string, built on the passed in prefix.
+ */
+OpenLayers.Util.createUniqueID = function(prefix) {
+ if (prefix == null) {
+ prefix = "id_";
+ } else {
+ prefix = prefix.replace(OpenLayers.Util.dotless, "_");
+ }
+ OpenLayers.Util.lastSeqID += 1;
+ return prefix + OpenLayers.Util.lastSeqID;
+};
+
+/**
+ * Constant: INCHES_PER_UNIT
+ * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
+ * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
+ * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
+ * and PROJ.4 (http://trac.osgeo.org/proj/)
+ * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
+ * The hardcoded table of PROJ.4 units are in pj_units.c.
+ */
+OpenLayers.INCHES_PER_UNIT = {
+ 'inches': 1.0,
+ 'ft': 12.0,
+ 'mi': 63360.0,
+ 'm': 39.37,
+ 'km': 39370,
+ 'dd': 4374754,
+ 'yd': 36
+};
+OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
+OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
+OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
+
+// Units from CS-Map
+OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "Inch": OpenLayers.INCHES_PER_UNIT.inches,
+ "Meter": 1.0 / OpenLayers.METERS_PER_INCH, //EPSG:9001
+ "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH, //EPSG:9003
+ "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9002
+ "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH, //EPSG:9005
+ "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH, //EPSG:9041
+ "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH, //EPSG:9094
+ "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
+ "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
+ "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
+ "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9036
+ "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
+ "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH, //EPSG:9040
+ "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH, //EPSG:9084
+ "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH, //EPSG:9085
+ "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH, //EPSG:9086
+ "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH, //EPSG:9087
+ "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH, //EPSG:9080
+ "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH, //EPSG:9081
+ "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH, //EPSG:9082
+ "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH, //EPSG:9083
+ "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
+ "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9096
+ "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9093
+ "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9030
+ "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
+ "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
+ "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH, //EPSG:9031
+ "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
+ "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9038
+ "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9033
+ "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9062
+ "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9042
+ "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9039
+ "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9034
+ "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9063
+ "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9043
+ "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH, //EPSG:9097
+ "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH, //EPSG:9098
+ "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
+ "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
+ "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
+ "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
+ "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
+ "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
+ "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
+ "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
+});
+
+//unit abbreviations supported by PROJ.4
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
+ "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
+ "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
+ "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
+ "kmi": OpenLayers.INCHES_PER_UNIT["nmi"], //International Nautical Mile
+ "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
+ "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"], //International Chain
+ "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
+ "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
+ "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
+ "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
+ "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
+ "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"], //U.S. Surveyor's Statute Mile
+ "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"], //Indian Yard
+ "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"], //Indian Foot
+ "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH //Indian Chain
+});
+
+/**
+ * Constant: DOTS_PER_INCH
+ * {Integer} 72 (A sensible default)
+ */
+OpenLayers.DOTS_PER_INCH = 72;
+
+/**
+ * Function: normalizeScale
+ *
+ * Parameters:
+ * scale - {float}
+ *
+ * Returns:
+ * {Float} A normalized scale value, in 1 / X format.
+ * This means that if a value less than one ( already 1/x) is passed
+ * in, it just returns scale directly. Otherwise, it returns
+ * 1 / scale
+ */
+OpenLayers.Util.normalizeScale = function (scale) {
+ var normScale = (scale > 1.0) ? (1.0 / scale)
+ : scale;
+ return normScale;
+};
+
+/**
+ * Function: getResolutionFromScale
+ *
+ * Parameters:
+ * scale - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding resolution given passed-in scale and unit
+ * parameters. If the given scale is falsey, the returned resolution will
+ * be undefined.
+ */
+OpenLayers.Util.getResolutionFromScale = function (scale, units) {
+ var resolution;
+ if (scale) {
+ if (units == null) {
+ units = "degrees";
+ }
+ var normScale = OpenLayers.Util.normalizeScale(scale);
+ resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
+ * OpenLayers.DOTS_PER_INCH);
+ }
+ return resolution;
+};
+
+/**
+ * Function: getScaleFromResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding scale given passed-in resolution and unit
+ * parameters.
+ */
+OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
+
+ if (units == null) {
+ units = "degrees";
+ }
+
+ var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
+ OpenLayers.DOTS_PER_INCH;
+ return scale;
+};
+
+/**
+ * Function: pagePosition
+ * Calculates the position of an element on the page (see
+ * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
+ *
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Parameters:
+ * forElement - {DOMElement}
+ *
+ * Returns:
+ * {Array} two item array, Left value then Top value.
+ */
+OpenLayers.Util.pagePosition = function(forElement) {
+ // NOTE: If element is hidden (display none or disconnected or any the
+ // ancestors are hidden) we get (0,0) by default but we still do the
+ // accumulation of scroll position.
+
+ var pos = [0, 0];
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ if (!forElement || forElement == window || forElement == viewportElement) {
+ // viewport is always at 0,0 as that defined the coordinate system for
+ // this function - this avoids special case checks in the code below
+ return pos;
+ }
+
+ // Gecko browsers normally use getBoxObjectFor to calculate the position.
+ // When invoked for an element with an implicit absolute position though it
+ // can be off by one. Therefore the recursive implementation is used in
+ // those (relatively rare) cases.
+ var BUGGY_GECKO_BOX_OBJECT =
+ OpenLayers.IS_GECKO && document.getBoxObjectFor &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
+ (forElement.style.top == '' || forElement.style.left == '');
+
+ var parent = null;
+ var box;
+
+ if (forElement.getBoundingClientRect) { // IE
+ box = forElement.getBoundingClientRect();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+
+ pos[0] = box.left + scrollLeft;
+ pos[1] = box.top + scrollTop;
+
+ } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
+ // Gecko ignores the scroll values for ancestors, up to 1.9. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
+
+ box = document.getBoxObjectFor(forElement);
+ var vpBox = document.getBoxObjectFor(viewportElement);
+ pos[0] = box.screenX - vpBox.screenX;
+ pos[1] = box.screenY - vpBox.screenY;
+
+ } else { // safari/opera
+ pos[0] = forElement.offsetLeft;
+ pos[1] = forElement.offsetTop;
+ parent = forElement.offsetParent;
+ if (parent != forElement) {
+ while (parent) {
+ pos[0] += parent.offsetLeft;
+ pos[1] += parent.offsetTop;
+ parent = parent.offsetParent;
+ }
+ }
+
+ var browser = OpenLayers.BROWSER_NAME;
+
+ // opera & (safari absolute) incorrectly account for body offsetTop
+ if (browser == "opera" || (browser == "safari" &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
+ pos[1] -= document.body.offsetTop;
+ }
+
+ // accumulate the scroll positions for everything but the body element
+ parent = forElement.offsetParent;
+ while (parent && parent != document.body) {
+ pos[0] -= parent.scrollLeft;
+ // see https://bugs.opera.com/show_bug.cgi?id=249965
+ if (browser != "opera" || parent.tagName != 'TR') {
+ pos[1] -= parent.scrollTop;
+ }
+ parent = parent.offsetParent;
+ }
+ }
+
+ return pos;
+};
+
+/**
+ * Function: getViewportElement
+ * Returns die viewport element of the document. The viewport element is
+ * usually document.documentElement, except in IE,where it is either
+ * document.body or document.documentElement, depending on the document's
+ * compatibility mode (see
+ * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+OpenLayers.Util.getViewportElement = function() {
+ var viewportElement = arguments.callee.viewportElement;
+ if (viewportElement == undefined) {
+ viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
+ document.compatMode != 'CSS1Compat') ? document.body :
+ document.documentElement;
+ arguments.callee.viewportElement = viewportElement;
+ }
+ return viewportElement;
+};
+
+/**
+ * Function: isEquivalentUrl
+ * Test two URLs for equivalence.
+ *
+ * Setting 'ignoreCase' allows for case-independent comparison.
+ *
+ * Comparison is based on:
+ * - Protocol
+ * - Host (evaluated without the port)
+ * - Port (set 'ignorePort80' to ignore "80" values)
+ * - Hash ( set 'ignoreHash' to disable)
+ * - Pathname (for relative <-> absolute comparison)
+ * - Arguments (so they can be out of order)
+ *
+ * Parameters:
+ * url1 - {String}
+ * url2 - {String}
+ * options - {Object} Allows for customization of comparison:
+ * 'ignoreCase' - Default is True
+ * 'ignorePort80' - Default is True
+ * 'ignoreHash' - Default is True
+ *
+ * Returns:
+ * {Boolean} Whether or not the two URLs are equivalent
+ */
+OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
+ options = options || {};
+
+ OpenLayers.Util.applyDefaults(options, {
+ ignoreCase: true,
+ ignorePort80: true,
+ ignoreHash: true,
+ splitArgs: false
+ });
+
+ var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
+ var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
+
+ //compare all keys except for "args" (treated below)
+ for(var key in urlObj1) {
+ if(key !== "args") {
+ if(urlObj1[key] != urlObj2[key]) {
+ return false;
+ }
+ }
+ }
+
+ // compare search args - irrespective of order
+ for(var key in urlObj1.args) {
+ if(urlObj1.args[key] != urlObj2.args[key]) {
+ return false;
+ }
+ delete urlObj2.args[key];
+ }
+ // urlObj2 shouldn't have any args left
+ for(var key in urlObj2.args) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Function: createUrlObject
+ *
+ * Parameters:
+ * url - {String}
+ * options - {Object} A hash of options.
+ *
+ * Valid options:
+ * ignoreCase - {Boolean} lowercase url,
+ * ignorePort80 - {Boolean} don't include explicit port if port is 80,
+ * ignoreHash - {Boolean} Don't include part of url after the hash (#).
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object with separate url, a, port, host, and args parsed out
+ * and ready for comparison
+ */
+OpenLayers.Util.createUrlObject = function(url, options) {
+ options = options || {};
+
+ // deal with relative urls first
+ if(!(/^\w+:\/\//).test(url)) {
+ var loc = window.location;
+ var port = loc.port ? ":" + loc.port : "";
+ var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
+ if(url.indexOf("/") === 0) {
+ // full pathname
+ url = fullUrl + url;
+ } else {
+ // relative to current path
+ var parts = loc.pathname.split("/");
+ parts.pop();
+ url = fullUrl + parts.join("/") + "/" + url;
+ }
+ }
+
+ if (options.ignoreCase) {
+ url = url.toLowerCase();
+ }
+
+ var a = document.createElement('a');
+ a.href = url;
+
+ var urlObject = {};
+
+ //host (without port)
+ urlObject.host = a.host.split(":").shift();
+
+ //protocol
+ urlObject.protocol = a.protocol;
+
+ //port (get uniform browser behavior with port 80 here)
+ if(options.ignorePort80) {
+ urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
+ } else {
+ urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
+ }
+
+ //hash
+ urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;
+
+ //args
+ var queryString = a.search;
+ if (!queryString) {
+ var qMark = url.indexOf("?");
+ queryString = (qMark != -1) ? url.substr(qMark) : "";
+ }
+ urlObject.args = OpenLayers.Util.getParameters(queryString,
+ {splitArgs: options.splitArgs});
+
+ // pathname
+ //
+ // This is a workaround for Internet Explorer where
+ // window.location.pathname has a leading "/", but
+ // a.pathname has no leading "/".
+ urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
+
+ return urlObject;
+};
+
+/**
+ * Function: removeTail
+ * Takes a url and removes everything after the ? and #
+ *
+ * Parameters:
+ * url - {String} The url to process
+ *
+ * Returns:
+ * {String} The string with all queryString and Hash removed
+ */
+OpenLayers.Util.removeTail = function(url) {
+ var head = null;
+
+ var qMark = url.indexOf("?");
+ var hashMark = url.indexOf("#");
+
+ if (qMark == -1) {
+ head = (hashMark != -1) ? url.substr(0,hashMark) : url;
+ } else {
+ head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark))
+ : url.substr(0, qMark);
+ }
+ return head;
+};
+
+/**
+ * Constant: IS_GECKO
+ * {Boolean} True if the userAgent reports the browser to use the Gecko engine
+ */
+OpenLayers.IS_GECKO = (function() {
+ var ua = navigator.userAgent.toLowerCase();
+ return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
+})();
+
+/**
+ * Constant: CANVAS_SUPPORTED
+ * {Boolean} True if canvas 2d is supported.
+ */
+OpenLayers.CANVAS_SUPPORTED = (function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+})();
+
+/**
+ * Constant: BROWSER_NAME
+ * {String}
+ * A substring of the navigator.userAgent property. Depending on the userAgent
+ * property, this will be the empty string or one of the following:
+ * * "opera" -- Opera
+ * * "msie" -- Internet Explorer
+ * * "safari" -- Safari
+ * * "firefox" -- Firefox
+ * * "mozilla" -- Mozilla
+ */
+OpenLayers.BROWSER_NAME = (function() {
+ var name = "";
+ var ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf("opera") != -1) {
+ name = "opera";
+ } else if (ua.indexOf("msie") != -1) {
+ name = "msie";
+ } else if (ua.indexOf("safari") != -1) {
+ name = "safari";
+ } else if (ua.indexOf("mozilla") != -1) {
+ if (ua.indexOf("firefox") != -1) {
+ name = "firefox";
+ } else {
+ name = "mozilla";
+ }
+ }
+ return name;
+})();
+
+/**
+ * Function: getBrowserName
+ *
+ * Returns:
+ * {String} A string which specifies which is the current
+ * browser in which we are running.
+ *
+ * Currently-supported browser detection and codes:
+ * * 'opera' -- Opera
+ * * 'msie' -- Internet Explorer
+ * * 'safari' -- Safari
+ * * 'firefox' -- Firefox
+ * * 'mozilla' -- Mozilla
+ *
+ * If we are unable to property identify the browser, we
+ * return an empty string.
+ */
+OpenLayers.Util.getBrowserName = function() {
+ return OpenLayers.BROWSER_NAME;
+};
+
+/**
+ * Method: getRenderedDimensions
+ * Renders the contentHTML offscreen to determine actual dimensions for
+ * popup sizing. As we need layout to determine dimensions the content
+ * is rendered -9999px to the left and absolute to ensure the
+ * scrollbars do not flicker
+ *
+ * Parameters:
+ * contentHTML
+ * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is
+ * specified, we fix that dimension of the div to be measured. This is
+ * useful in the case where we have a limit in one dimension and must
+ * therefore meaure the flow in the other dimension.
+ * options - {Object}
+ *
+ * Allowed Options:
+ * displayClass - {String} Optional parameter. A CSS class name(s) string
+ * to provide the CSS context of the rendered content.
+ * containerElement - {DOMElement} Optional parameter. Insert the HTML to
+ * this node instead of the body root when calculating dimensions.
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
+
+ var w, h;
+
+ // create temp container div with restricted size
+ var container = document.createElement("div");
+ container.style.visibility = "hidden";
+
+ var containerElement = (options && options.containerElement)
+ ? options.containerElement : document.body;
+
+ // Opera and IE7 can't handle a node with position:aboslute if it inherits
+ // position:absolute from a parent.
+ var parentHasPositionAbsolute = false;
+ var superContainer = null;
+ var parent = containerElement;
+ while (parent && parent.tagName.toLowerCase()!="body") {
+ var parentPosition = OpenLayers.Element.getStyle(parent, "position");
+ if(parentPosition == "absolute") {
+ parentHasPositionAbsolute = true;
+ break;
+ } else if (parentPosition && parentPosition != "static") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+ if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 ||
+ containerElement.clientWidth === 0) ){
+ superContainer = document.createElement("div");
+ superContainer.style.visibility = "hidden";
+ superContainer.style.position = "absolute";
+ superContainer.style.overflow = "visible";
+ superContainer.style.width = document.body.clientWidth + "px";
+ superContainer.style.height = document.body.clientHeight + "px";
+ superContainer.appendChild(container);
+ }
+ container.style.position = "absolute";
+
+ //fix a dimension, if specified.
+ if (size) {
+ if (size.w) {
+ w = size.w;
+ container.style.width = w + "px";
+ } else if (size.h) {
+ h = size.h;
+ container.style.height = h + "px";
+ }
+ }
+
+ //add css classes, if specified
+ if (options && options.displayClass) {
+ container.className = options.displayClass;
+ }
+
+ // create temp content div and assign content
+ var content = document.createElement("div");
+ content.innerHTML = contentHTML;
+
+ // we need overflow visible when calculating the size
+ content.style.overflow = "visible";
+ if (content.childNodes) {
+ for (var i=0, l=content.childNodes.length; i<l; i++) {
+ if (!content.childNodes[i].style) continue;
+ content.childNodes[i].style.overflow = "visible";
+ }
+ }
+
+ // add content to restricted container
+ container.appendChild(content);
+
+ // append container to body for rendering
+ if (superContainer) {
+ containerElement.appendChild(superContainer);
+ } else {
+ containerElement.appendChild(container);
+ }
+
+ // calculate scroll width of content and add corners and shadow width
+ if (!w) {
+ w = parseInt(content.scrollWidth);
+
+ // update container width to allow height to adjust
+ container.style.width = w + "px";
+ }
+ // capture height and add shadow and corner image widths
+ if (!h) {
+ h = parseInt(content.scrollHeight);
+ }
+
+ // remove elements
+ container.removeChild(content);
+ if (superContainer) {
+ superContainer.removeChild(container);
+ containerElement.removeChild(superContainer);
+ } else {
+ containerElement.removeChild(container);
+ }
+
+ return new OpenLayers.Size(w, h);
+};
+
+/**
+ * APIFunction: getScrollbarWidth
+ * This function has been modified by the OpenLayers from the original version,
+ * written by Matthew Eernisse and released under the Apache 2
+ * license here:
+ *
+ * http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
+ *
+ * It has been modified simply to cache its value, since it is physically
+ * impossible that this code could ever run in more than one browser at
+ * once.
+ *
+ * Returns:
+ * {Integer}
+ */
+OpenLayers.Util.getScrollbarWidth = function() {
+
+ var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+
+ if (scrollbarWidth == null) {
+ var scr = null;
+ var inn = null;
+ var wNoScroll = 0;
+ var wScroll = 0;
+
+ // Outer scrolling div
+ scr = document.createElement('div');
+ scr.style.position = 'absolute';
+ scr.style.top = '-1000px';
+ scr.style.left = '-1000px';
+ scr.style.width = '100px';
+ scr.style.height = '50px';
+ // Start with no scrollbar
+ scr.style.overflow = 'hidden';
+
+ // Inner content div
+ inn = document.createElement('div');
+ inn.style.width = '100%';
+ inn.style.height = '200px';
+
+ // Put the inner div in the scrolling div
+ scr.appendChild(inn);
+ // Append the scrolling div to the doc
+ document.body.appendChild(scr);
+
+ // Width of the inner div sans scrollbar
+ wNoScroll = inn.offsetWidth;
+
+ // Add the scrollbar
+ scr.style.overflow = 'scroll';
+ // Width of the inner div width scrollbar
+ wScroll = inn.offsetWidth;
+
+ // Remove the scrolling div from the doc
+ document.body.removeChild(document.body.lastChild);
+
+ // Pixel width of the scroller
+ OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
+ scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+ }
+
+ return scrollbarWidth;
+};
+
+/**
+ * APIFunction: getFormattedLonLat
+ * This function will return latitude or longitude value formatted as
+ *
+ * Parameters:
+ * coordinate - {Float} the coordinate value to be formatted
+ * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
+ * to be formatted (default = lat)
+ * dmsOption - {String} specify the precision of the output can be one of:
+ * 'dms' show degrees minutes and seconds
+ * 'dm' show only degrees and minutes
+ * 'd' show only degrees
+ *
+ * Returns:
+ * {String} the coordinate value formatted as a string
+ */
+OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
+ if (!dmsOption) {
+ dmsOption = 'dms'; //default to show degree, minutes, seconds
+ }
+
+ coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
+
+ var abscoordinate = Math.abs(coordinate);
+ var coordinatedegrees = Math.floor(abscoordinate);
+
+ var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
+ var tempcoordinateminutes = coordinateminutes;
+ coordinateminutes = Math.floor(coordinateminutes);
+ var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
+ coordinateseconds = Math.round(coordinateseconds*10);
+ coordinateseconds /= 10;
+
+ if( coordinateseconds >= 60) {
+ coordinateseconds -= 60;
+ coordinateminutes += 1;
+ if( coordinateminutes >= 60) {
+ coordinateminutes -= 60;
+ coordinatedegrees += 1;
+ }
+ }
+
+ if( coordinatedegrees < 10 ) {
+ coordinatedegrees = "0" + coordinatedegrees;
+ }
+ var str = coordinatedegrees + "\u00B0";
+
+ if (dmsOption.indexOf('dm') >= 0) {
+ if( coordinateminutes < 10 ) {
+ coordinateminutes = "0" + coordinateminutes;
+ }
+ str += coordinateminutes + "'";
+
+ if (dmsOption.indexOf('dms') >= 0) {
+ if( coordinateseconds < 10 ) {
+ coordinateseconds = "0" + coordinateseconds;
+ }
+ str += coordinateseconds + '"';
+ }
+ }
+
+ if (axis == "lon") {
+ str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
+ } else {
+ str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
+ }
+ return str;
+};
+
+/* ======================================================================
+ OpenLayers/Events.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Event
+ * Utility functions for event handling.
+ */
+OpenLayers.Event = {
+
+ /**
+ * Property: observers
+ * {Object} A hashtable cache of the event observers. Keyed by
+ * element._eventCacheID
+ */
+ observers: false,
+
+ /**
+ * Constant: KEY_SPACE
+ * {int}
+ */
+ KEY_SPACE: 32,
+
+ /**
+ * Constant: KEY_BACKSPACE
+ * {int}
+ */
+ KEY_BACKSPACE: 8,
+
+ /**
+ * Constant: KEY_TAB
+ * {int}
+ */
+ KEY_TAB: 9,
+
+ /**
+ * Constant: KEY_RETURN
+ * {int}
+ */
+ KEY_RETURN: 13,
+
+ /**
+ * Constant: KEY_ESC
+ * {int}
+ */
+ KEY_ESC: 27,
+
+ /**
+ * Constant: KEY_LEFT
+ * {int}
+ */
+ KEY_LEFT: 37,
+
+ /**
+ * Constant: KEY_UP
+ * {int}
+ */
+ KEY_UP: 38,
+
+ /**
+ * Constant: KEY_RIGHT
+ * {int}
+ */
+ KEY_RIGHT: 39,
+
+ /**
+ * Constant: KEY_DOWN
+ * {int}
+ */
+ KEY_DOWN: 40,
+
+ /**
+ * Constant: KEY_DELETE
+ * {int}
+ */
+ KEY_DELETE: 46,
+
+
+ /**
+ * Method: element
+ * Cross browser event element detection.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {DOMElement} The element that caused the event
+ */
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ /**
+ * Method: isSingleTouch
+ * Determine whether event was caused by a single touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isSingleTouch: function(event) {
+ return event.touches && event.touches.length == 1;
+ },
+
+ /**
+ * Method: isMultiTouch
+ * Determine whether event was caused by a multi touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isMultiTouch: function(event) {
+ return event.touches && event.touches.length > 1;
+ },
+
+ /**
+ * Method: isLeftClick
+ * Determine whether event was caused by a left click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ /**
+ * Method: isRightClick
+ * Determine whether event was caused by a right mouse click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isRightClick: function(event) {
+ return (((event.which) && (event.which == 3)) ||
+ ((event.button) && (event.button == 2)));
+ },
+
+ /**
+ * Method: stop
+ * Stops an event from propagating.
+ *
+ * Parameters:
+ * event - {Event}
+ * allowDefault - {Boolean} If true, we stop the event chain but
+ * still allow the default browser behaviour (text selection,
+ * radio-button clicking, etc). Default is false.
+ */
+ stop: function(event, allowDefault) {
+
+ if (!allowDefault) {
+ OpenLayers.Event.preventDefault(event);
+ }
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * Method: preventDefault
+ * Cancels the event if it is cancelable, without stopping further
+ * propagation of the event.
+ *
+ * Parameters:
+ * event - {Event}
+ */
+ preventDefault: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
+ },
+
+ /**
+ * Method: findElement
+ *
+ * Parameters:
+ * event - {Event}
+ * tagName - {String}
+ *
+ * Returns:
+ * {DOMElement} The first node with the given tagName, starting from the
+ * node the event was triggered on and traversing the DOM upwards
+ */
+ findElement: function(event, tagName) {
+ var element = OpenLayers.Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase()))){
+ element = element.parentNode;
+ }
+ return element;
+ },
+
+ /**
+ * Method: observe
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ */
+ observe: function(elementParam, name, observer, useCapture) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent)) {
+ name = 'keydown';
+ }
+
+ //if observers cache has not yet been created, create it
+ if (!this.observers) {
+ this.observers = {};
+ }
+
+ //if not already assigned, make a new unique cache ID
+ if (!element._eventCacheID) {
+ var idPrefix = "eventCacheID_";
+ if (element.id) {
+ idPrefix = element.id + "_" + idPrefix;
+ }
+ element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
+ }
+
+ var cacheID = element._eventCacheID;
+
+ //if there is not yet a hash entry for this element, add one
+ if (!this.observers[cacheID]) {
+ this.observers[cacheID] = [];
+ }
+
+ //add a new observer to this element's list
+ this.observers[cacheID].push({
+ 'element': element,
+ 'name': name,
+ 'observer': observer,
+ 'useCapture': useCapture
+ });
+
+ //add the actual browser event listener
+ if (element.addEventListener) {
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ /**
+ * Method: stopObservingElement
+ * Given the id of an element to stop observing, cycle through the
+ * element's cached observers, calling stopObserving on each one,
+ * skipping those entries which can no longer be removed.
+ *
+ * parameters:
+ * elementParam - {DOMElement || String}
+ */
+ stopObservingElement: function(elementParam) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
+ },
+
+ /**
+ * Method: _removeElementObservers
+ *
+ * Parameters:
+ * elementObservers - {Array(Object)} Array of (element, name,
+ * observer, usecapture) objects,
+ * taken directly from hashtable
+ */
+ _removeElementObservers: function(elementObservers) {
+ if (elementObservers) {
+ for(var i = elementObservers.length-1; i >= 0; i--) {
+ var entry = elementObservers[i];
+ OpenLayers.Event.stopObserving.apply(this, [
+ entry.element, entry.name, entry.observer, entry.useCapture
+ ]);
+ }
+ }
+ },
+
+ /**
+ * Method: stopObserving
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ *
+ * Returns:
+ * {Boolean} Whether or not the event observer was removed
+ */
+ stopObserving: function(elementParam, name, observer, useCapture) {
+ useCapture = useCapture || false;
+
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ if (name == 'keypress') {
+ if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
+ element.detachEvent) {
+ name = 'keydown';
+ }
+ }
+
+ // find element's entry in this.observers cache and remove it
+ var foundEntry = false;
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ if (elementObservers) {
+
+ // find the specific event type in the element's list
+ var i=0;
+ while(!foundEntry && i < elementObservers.length) {
+ var cacheEntry = elementObservers[i];
+
+ if ((cacheEntry.name == name) &&
+ (cacheEntry.observer == observer) &&
+ (cacheEntry.useCapture == useCapture)) {
+
+ elementObservers.splice(i, 1);
+ if (elementObservers.length == 0) {
+ delete OpenLayers.Event.observers[cacheID];
+ }
+ foundEntry = true;
+ break;
+ }
+ i++;
+ }
+ }
+
+ //actually remove the event listener from browser
+ if (foundEntry) {
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element && element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+ return foundEntry;
+ },
+
+ /**
+ * Method: unloadCache
+ * Cycle through all the element entries in the events cache and call
+ * stopObservingElement on each.
+ */
+ unloadCache: function() {
+ // check for OpenLayers.Event before checking for observers, because
+ // OpenLayers.Event may be undefined in IE if no map instance was
+ // created
+ if (OpenLayers.Event && OpenLayers.Event.observers) {
+ for (var cacheID in OpenLayers.Event.observers) {
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ OpenLayers.Event._removeElementObservers.apply(this,
+ [elementObservers]);
+ }
+ OpenLayers.Event.observers = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Event"
+};
+
+/* prevent memory leaks in IE */
+OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
+
+/**
+ * Class: OpenLayers.Events
+ */
+OpenLayers.Events = OpenLayers.Class({
+
+ /**
+ * Constant: BROWSER_EVENTS
+ * {Array(String)} supported events
+ */
+ BROWSER_EVENTS: [
+ "mouseover", "mouseout",
+ "mousedown", "mouseup", "mousemove",
+ "click", "dblclick", "rightclick", "dblrightclick",
+ "resize", "focus", "blur",
+ "touchstart", "touchmove", "touchend",
+ "keydown"
+ ],
+
+ /**
+ * Property: listeners
+ * {Object} Hashtable of Array(Function): events listener functions
+ */
+ listeners: null,
+
+ /**
+ * Property: object
+ * {Object} the code object issuing application events
+ */
+ object: null,
+
+ /**
+ * Property: element
+ * {DOMElement} the DOM element receiving browser events
+ */
+ element: null,
+
+ /**
+ * Property: eventHandler
+ * {Function} bound event handler attached to elements
+ */
+ eventHandler: null,
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean}
+ */
+ fallThrough: null,
+
+ /**
+ * APIProperty: includeXY
+ * {Boolean} Should the .xy property automatically be created for browser
+ * mouse events? In general, this should be false. If it is true, then
+ * mouse events will automatically generate a '.xy' property on the
+ * event object that is passed. (Prior to OpenLayers 2.7, this was true
+ * by default.) Otherwise, you can call the getMousePosition on the
+ * relevant events handler on the object available via the 'evt.object'
+ * property of the evt object. So, for most events, you can call:
+ * function named(evt) {
+ * this.xy = this.object.events.getMousePosition(evt)
+ * }
+ *
+ * This option typically defaults to false for performance reasons:
+ * when creating an events object whose primary purpose is to manage
+ * relatively positioned mouse events within a div, it may make
+ * sense to set it to true.
+ *
+ * This option is also used to control whether the events object caches
+ * offsets. If this is false, it will not: the reason for this is that
+ * it is only expected to be called many times if the includeXY property
+ * is set to true. If you set this to true, you are expected to clear
+ * the offset cache manually (using this.clearMouseCache()) if:
+ * the border of the element changes
+ * the location of the element in the page changes
+ */
+ includeXY: false,
+
+ /**
+ * APIProperty: extensions
+ * {Object} Event extensions registered with this instance. Keys are
+ * event types, values are {OpenLayers.Events.*} extension instances or
+ * {Boolean} for events that an instantiated extension provides in
+ * addition to the one it was created for.
+ *
+ * Extensions create an event in addition to browser events, which usually
+ * fires when a sequence of browser events is completed. Extensions are
+ * automatically instantiated when a listener is registered for an event
+ * provided by an extension.
+ *
+ * Extensions are created in the <OpenLayers.Events> namespace using
+ * <OpenLayers.Class>, and named after the event they provide.
+ * The constructor receives the target <OpenLayers.Events> instance as
+ * argument. Extensions that need to capture browser events before they
+ * propagate can register their listeners events using <register>, with
+ * {extension: true} as 4th argument.
+ *
+ * If an extension creates more than one event, an alias for each event
+ * type should be created and reference the same class. The constructor
+ * should set a reference in the target's extensions registry to itself.
+ *
+ * Below is a minimal extension that provides the "foostart" and "fooend"
+ * event types, which replace the native "click" event type if clicked on
+ * an element with the css class "foo":
+ *
+ * (code)
+ * OpenLayers.Events.foostart = OpenLayers.Class({
+ * initialize: function(target) {
+ * this.target = target;
+ * this.target.register("click", this, this.doStuff, {extension: true});
+ * // only required if extension provides more than one event type
+ * this.target.extensions["foostart"] = true;
+ * this.target.extensions["fooend"] = true;
+ * },
+ * destroy: function() {
+ * var target = this.target;
+ * target.unregister("click", this, this.doStuff);
+ * delete this.target;
+ * // only required if extension provides more than one event type
+ * delete target.extensions["foostart"];
+ * delete target.extensions["fooend"];
+ * },
+ * doStuff: function(evt) {
+ * var propagate = true;
+ * if (OpenLayers.Event.element(evt).className === "foo") {
+ * propagate = false;
+ * var target = this.target;
+ * target.triggerEvent("foostart");
+ * window.setTimeout(function() {
+ * target.triggerEvent("fooend");
+ * }, 1000);
+ * }
+ * return propagate;
+ * }
+ * });
+ * // only required if extension provides more than one event type
+ * OpenLayers.Events.fooend = OpenLayers.Events.foostart;
+ * (end)
+ *
+ */
+ extensions: null,
+
+ /**
+ * Property: extensionCount
+ * {Object} Keys are event types (like in <listeners>), values are the
+ * number of extension listeners for each event type.
+ */
+ extensionCount: null,
+
+ /**
+ * Method: clearMouseListener
+ * A version of <clearMouseCache> that is bound to this instance so that
+ * it can be used with <OpenLayers.Event.observe> and
+ * <OpenLayers.Event.stopObserving>.
+ */
+ clearMouseListener: null,
+
+ /**
+ * Constructor: OpenLayers.Events
+ * Construct an OpenLayers.Events object.
+ *
+ * Parameters:
+ * object - {Object} The js object to which this Events object is being added
+ * element - {DOMElement} A dom element to respond to browser events
+ * eventTypes - {Array(String)} Deprecated. Array of custom application
+ * events. A listener may be registered for any named event, regardless
+ * of the values provided here.
+ * fallThrough - {Boolean} Allow events to fall through after these have
+ * been handled?
+ * options - {Object} Options for the events object.
+ */
+ initialize: function (object, element, eventTypes, fallThrough, options) {
+ OpenLayers.Util.extend(this, options);
+ this.object = object;
+ this.fallThrough = fallThrough;
+ this.listeners = {};
+ this.extensions = {};
+ this.extensionCount = {};
+ this._msTouches = [];
+
+ // if a dom element is specified, add a listeners list
+ // for browser events on the element and register them
+ if (element != null) {
+ this.attachToElement(element);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function () {
+ for (var e in this.extensions) {
+ if (typeof this.extensions[e] !== "boolean") {
+ this.extensions[e].destroy();
+ }
+ }
+ this.extensions = null;
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ if(this.element.hasScrollEvent) {
+ OpenLayers.Event.stopObserving(
+ window, "scroll", this.clearMouseListener
+ );
+ }
+ }
+ this.element = null;
+
+ this.listeners = null;
+ this.object = null;
+ this.fallThrough = null;
+ this.eventHandler = null;
+ },
+
+ /**
+ * APIMethod: addEventType
+ * Deprecated. Any event can be triggered without adding it first.
+ *
+ * Parameters:
+ * eventName - {String}
+ */
+ addEventType: function(eventName) {
+ },
+
+ /**
+ * Method: attachToElement
+ *
+ * Parameters:
+ * element - {HTMLDOMElement} a DOM element to attach browser events to
+ */
+ attachToElement: function (element) {
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ } else {
+ // keep a bound copy of handleBrowserEvent() so that we can
+ // pass the same function to both Event.observe() and .stopObserving()
+ this.eventHandler = OpenLayers.Function.bindAsEventListener(
+ this.handleBrowserEvent, this
+ );
+
+ // to be used with observe and stopObserving
+ this.clearMouseListener = OpenLayers.Function.bind(
+ this.clearMouseCache, this
+ );
+ }
+ this.element = element;
+ var msTouch = !!window.navigator.msMaxTouchPoints;
+ var type;
+ for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
+ type = this.BROWSER_EVENTS[i];
+ // register the event cross-browser
+ OpenLayers.Event.observe(element, type, this.eventHandler
+ );
+ if (msTouch && type.indexOf('touch') === 0) {
+ this.addMsTouchListener(element, type, this.eventHandler);
+ }
+ }
+ // disable dragstart in IE so that mousedown/move/up works normally
+ OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
+ },
+
+ /**
+ * APIMethod: on
+ * Convenience method for registering listeners with a common scope.
+ * Internally, this method calls <register> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // register a single listener for the "loadstart" event
+ * events.on({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", undefined, loadStartListener);
+ *
+ * // register multiple listeners to be called with the same `this` object
+ * events.on({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", object, loadStartListener);
+ * events.register("loadend", object, loadEndListener);
+ * (end)
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ on: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.register(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: register
+ * Register an event on the events object.
+ *
+ * When the event is triggered, the 'func' function will be called, in the
+ * context of 'obj'. Imagine we were to register an event, specifying an
+ * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
+ * context in the callback function will be our Bounds object. This means
+ * that within our callback function, we can access the properties and
+ * methods of the Bounds object through the "this" variable. So our
+ * callback could execute something like:
+ * : leftStr = "Left: " + this.left;
+ *
+ * or
+ *
+ * : centerStr = "Center: " + this.getCenterLonLat();
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ * priority - {Boolean|Object} If true, adds the new listener to the
+ * *front* of the events queue instead of to the end.
+ *
+ * Valid options for priority:
+ * extension - {Boolean} If true, then the event will be registered as
+ * extension event. Extension events are handled before all other
+ * events.
+ */
+ register: function (type, obj, func, priority) {
+ if (type in OpenLayers.Events && !this.extensions[type]) {
+ this.extensions[type] = new OpenLayers.Events[type](this);
+ }
+ if (func != null) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (!listeners) {
+ listeners = [];
+ this.listeners[type] = listeners;
+ this.extensionCount[type] = 0;
+ }
+ var listener = {obj: obj, func: func};
+ if (priority) {
+ listeners.splice(this.extensionCount[type], 0, listener);
+ if (typeof priority === "object" && priority.extension) {
+ this.extensionCount[type]++;
+ }
+ } else {
+ listeners.push(listener);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: registerPriority
+ * Same as register() but adds the new listener to the *front* of the
+ * events queue instead of to the end.
+ *
+ * TODO: get rid of this in 3.0 - Decide whether listeners should be
+ * called in the order they were registered or in reverse order.
+ *
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's
+ * 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ */
+ registerPriority: function (type, obj, func) {
+ this.register(type, obj, func, true);
+ },
+
+ /**
+ * APIMethod: un
+ * Convenience method for unregistering listeners with a common scope.
+ * Internally, this method calls <unregister> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // unregister a single listener for the "loadstart" event
+ * events.un({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", undefined, loadStartListener);
+ *
+ * // unregister multiple listeners with the same `this` object
+ * events.un({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", object, loadStartListener);
+ * events.unregister("loadend", object, loadEndListener);
+ * (end)
+ */
+ un: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.unregister(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: unregister
+ *
+ * Parameters:
+ * type - {String}
+ * obj - {Object} If none specified, defaults to this.object
+ * func - {Function}
+ */
+ unregister: function (type, obj, func) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (listeners != null) {
+ for (var i=0, len=listeners.length; i<len; i++) {
+ if (listeners[i].obj == obj && listeners[i].func == func) {
+ listeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: remove
+ * Remove all listeners for a given event type. If type is not registered,
+ * does nothing.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ remove: function(type) {
+ if (this.listeners[type] != null) {
+ this.listeners[type] = [];
+ }
+ },
+
+ /**
+ * APIMethod: triggerEvent
+ * Trigger a specified registered event.
+ *
+ * Parameters:
+ * type - {String}
+ * evt - {Event || Object} will be passed to the listeners.
+ *
+ * Returns:
+ * {Boolean} The last listener return. If a listener returns false, the
+ * chain of listeners will stop getting called.
+ */
+ triggerEvent: function (type, evt) {
+ var listeners = this.listeners[type];
+
+ // fast path
+ if(!listeners || listeners.length == 0) {
+ return undefined;
+ }
+
+ // prep evt object with object & div references
+ if (evt == null) {
+ evt = {};
+ }
+ evt.object = this.object;
+ evt.element = this.element;
+ if(!evt.type) {
+ evt.type = type;
+ }
+
+ // execute all callbacks registered for specified type
+ // get a clone of the listeners array to
+ // allow for splicing during callbacks
+ listeners = listeners.slice();
+ var continueChain;
+ for (var i=0, len=listeners.length; i<len; i++) {
+ var callback = listeners[i];
+ // bind the context to callback.obj
+ continueChain = callback.func.apply(callback.obj, [evt]);
+
+ if ((continueChain != undefined) && (continueChain == false)) {
+ // if callback returns false, execute no more callbacks.
+ break;
+ }
+ }
+ // don't fall through to other DOM elements
+ if (!this.fallThrough) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ return continueChain;
+ },
+
+ /**
+ * Method: handleBrowserEvent
+ * Basically just a wrapper to the triggerEvent() function, but takes
+ * care to set a property 'xy' on the event with the current mouse
+ * position.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ handleBrowserEvent: function (evt) {
+ var type = evt.type, listeners = this.listeners[type];
+ if(!listeners || listeners.length == 0) {
+ // noone's listening, bail out
+ return;
+ }
+ // add clientX & clientY to all events - corresponds to average x, y
+ var touches = evt.touches;
+ if (touches && touches[0]) {
+ var x = 0;
+ var y = 0;
+ var num = touches.length;
+ var touch;
+ for (var i=0; i<num; ++i) {
+ touch = this.getTouchClientXY(touches[i]);
+ x += touch.clientX;
+ y += touch.clientY;
+ }
+ evt.clientX = x / num;
+ evt.clientY = y / num;
+ }
+ if (this.includeXY) {
+ evt.xy = this.getMousePosition(evt);
+ }
+ this.triggerEvent(type, evt);
+ },
+
+ /**
+ * Method: getTouchClientXY
+ * WebKit has a few bugs for clientX/clientY. This method detects them
+ * and calculate the correct values.
+ *
+ * Parameters:
+ * evt - {Touch} a Touch object from a TouchEvent
+ *
+ * Returns:
+ * {Object} An object with only clientX and clientY properties with the
+ * calculated values.
+ */
+ getTouchClientXY: function (evt) {
+ // olMochWin is to override window, used for testing
+ var win = window.olMockWin || window,
+ winPageX = win.pageXOffset,
+ winPageY = win.pageYOffset,
+ x = evt.clientX,
+ y = evt.clientY;
+
+ if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
+ evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
+ // iOS4 include scroll offset in clientX/Y
+ x = x - winPageX;
+ y = y - winPageY;
+ } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
+ // Some Android browsers have totally bogus values for clientX/Y
+ // when scrolling/zooming a page
+ x = evt.pageX - winPageX;
+ y = evt.pageY - winPageY;
+ }
+
+ evt.olClientX = x;
+ evt.olClientY = y;
+
+ return {
+ clientX: x,
+ clientY: y
+ };
+ },
+
+ /**
+ * APIMethod: clearMouseCache
+ * Clear cached data about the mouse position. This should be called any
+ * time the element that events are registered on changes position
+ * within the page.
+ */
+ clearMouseCache: function() {
+ this.element.scrolls = null;
+ this.element.lefttop = null;
+ this.element.offsets = null;
+ },
+
+ /**
+ * Method: getMousePosition
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
+ * for offsets
+ */
+ getMousePosition: function (evt) {
+ if (!this.includeXY) {
+ this.clearMouseCache();
+ } else if (!this.element.hasScrollEvent) {
+ OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
+ this.element.hasScrollEvent = true;
+ }
+
+ if (!this.element.scrolls) {
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ this.element.scrolls = [
+ window.pageXOffset || viewportElement.scrollLeft,
+ window.pageYOffset || viewportElement.scrollTop
+ ];
+ }
+
+ if (!this.element.lefttop) {
+ this.element.lefttop = [
+ (document.documentElement.clientLeft || 0),
+ (document.documentElement.clientTop || 0)
+ ];
+ }
+
+ if (!this.element.offsets) {
+ this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+ }
+
+ return new OpenLayers.Pixel(
+ (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
+ - this.element.lefttop[0],
+ (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
+ - this.element.lefttop[1]
+ );
+ },
+
+ /**
+ * Method: addMsTouchListener
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListener: function (element, type, handler) {
+ var eventHandler = this.eventHandler;
+ var touches = this._msTouches;
+
+ function msHandler(evt) {
+ handler(OpenLayers.Util.applyDefaults({
+ stopPropagation: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].stopPropagation();
+ }
+ },
+ preventDefault: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].preventDefault();
+ }
+ },
+ type: type
+ }, evt));
+ }
+
+ switch (type) {
+ case 'touchstart':
+ return this.addMsTouchListenerStart(element, type, msHandler);
+ case 'touchend':
+ return this.addMsTouchListenerEnd(element, type, msHandler);
+ case 'touchmove':
+ return this.addMsTouchListenerMove(element, type, msHandler);
+ default:
+ throw 'Unknown touch event type';
+ }
+ },
+
+ /**
+ * Method: addMsTouchListenerStart
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerStart: function(element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ var alreadyInArray = false;
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ alreadyInArray = true;
+ break;
+ }
+ }
+ if (!alreadyInArray) {
+ touches.push(e);
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerDown', cb);
+
+ // Need to also listen for end events to keep the _msTouches list
+ // accurate
+ var internalCb = function(e) {
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+ };
+ OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
+ },
+
+ /**
+ * Method: addMsTouchListenerMove
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerMove: function (element, type, handler) {
+ var touches = this._msTouches;
+ var cb = function(e) {
+
+ //Don't fire touch moves when mouse isn't down
+ if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
+ return;
+ }
+
+ if (touches.length == 1 && touches[0].pageX == e.pageX &&
+ touches[0].pageY == e.pageY) {
+ // don't trigger event when pointer has not moved
+ return;
+ }
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches[i] = e;
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerMove', cb);
+ },
+
+ /**
+ * Method: addMsTouchListenerEnd
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerEnd: function (element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerUp', cb);
+ },
+
+ CLASS_NAME: "OpenLayers.Events"
+});
+/* ======================================================================
+ OpenLayers/Events/buttonclick.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.buttonclick
+ * Extension event type for handling buttons on top of a dom element. This
+ * event type fires "buttonclick" on its <target> when a button was
+ * clicked. Buttons are detected by the "olButton" class.
+ *
+ * This event type makes sure that button clicks do not interfere with other
+ * events that are registered on the same <element>.
+ *
+ * Event types provided by this extension:
+ * - *buttonclick* Triggered when a button is clicked. Listeners receive an
+ * object with a *buttonElement* property referencing the dom element of
+ * the clicked button, and an *buttonXY* property with the click position
+ * relative to the button.
+ */
+OpenLayers.Events.buttonclick = OpenLayers.Class({
+
+ /**
+ * Property: target
+ * {<OpenLayers.Events>} The events instance that the buttonclick event will
+ * be triggered on.
+ */
+ target: null,
+
+ /**
+ * Property: events
+ * {Array} Events to observe and conditionally stop from propagating when
+ * an element with the olButton class (or its olAlphaImg child) is
+ * clicked.
+ */
+ events: [
+ 'mousedown', 'mouseup', 'click', 'dblclick',
+ 'touchstart', 'touchmove', 'touchend', 'keydown'
+ ],
+
+ /**
+ * Property: startRegEx
+ * {RegExp} Regular expression to test Event.type for events that start
+ * a buttonclick sequence.
+ */
+ startRegEx: /^mousedown|touchstart$/,
+
+ /**
+ * Property: cancelRegEx
+ * {RegExp} Regular expression to test Event.type for events that cancel
+ * a buttonclick sequence.
+ */
+ cancelRegEx: /^touchmove$/,
+
+ /**
+ * Property: completeRegEx
+ * {RegExp} Regular expression to test Event.type for events that complete
+ * a buttonclick sequence.
+ */
+ completeRegEx: /^mouseup|touchend$/,
+
+ /**
+ * Property: startEvt
+ * {Event} The event that started the click sequence
+ */
+
+ /**
+ * Constructor: OpenLayers.Events.buttonclick
+ * Construct a buttonclick event type. Applications are not supposed to
+ * create instances of this class - they are created on demand by
+ * <OpenLayers.Events> instances.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance that the buttonclick
+ * event will be triggered on.
+ */
+ initialize: function(target) {
+ this.target = target;
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.register(this.events[i], this, this.buttonClick, {
+ extension: true
+ });
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.unregister(this.events[i], this, this.buttonClick);
+ }
+ delete this.target;
+ },
+
+ /**
+ * Method: getPressedButton
+ * Get the pressed button, if any. Returns undefined if no button
+ * was pressed.
+ *
+ * Arguments:
+ * element - {DOMElement} The event target.
+ *
+ * Returns:
+ * {DOMElement} The button element, or undefined.
+ */
+ getPressedButton: function(element) {
+ var depth = 3, // limit the search depth
+ button;
+ do {
+ if(OpenLayers.Element.hasClass(element, "olButton")) {
+ // hit!
+ button = element;
+ break;
+ }
+ element = element.parentNode;
+ } while(--depth > 0 && element);
+ return button;
+ },
+
+ /**
+ * Method: ignore
+ * Check for event target elements that should be ignored by OpenLayers.
+ *
+ * Parameters:
+ * element - {DOMElement} The event target.
+ */
+ ignore: function(element) {
+ var depth = 3,
+ ignore = false;
+ do {
+ if (element.nodeName.toLowerCase() === 'a') {
+ ignore = true;
+ break;
+ }
+ element = element.parentNode;
+ } while (--depth > 0 && element);
+ return ignore;
+ },
+
+ /**
+ * Method: buttonClick
+ * Check if a button was clicked, and fire the buttonclick event
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonClick: function(evt) {
+ var propagate = true,
+ element = OpenLayers.Event.element(evt);
+ if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
+ // was a button pressed?
+ var button = this.getPressedButton(element);
+ if (button) {
+ if (evt.type === "keydown") {
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_RETURN:
+ case OpenLayers.Event.KEY_SPACE:
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button
+ });
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ break;
+ }
+ } else if (this.startEvt) {
+ if (this.completeRegEx.test(evt.type)) {
+ var pos = OpenLayers.Util.pagePosition(button);
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+ pos[0] = pos[0] - scrollLeft;
+ pos[1] = pos[1] - scrollTop;
+
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button,
+ buttonXY: {
+ x: this.startEvt.clientX - pos[0],
+ y: this.startEvt.clientY - pos[1]
+ }
+ });
+ }
+ if (this.cancelRegEx.test(evt.type)) {
+ delete this.startEvt;
+ }
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ if (this.startRegEx.test(evt.type)) {
+ this.startEvt = evt;
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ } else {
+ propagate = !this.ignore(OpenLayers.Event.element(evt));
+ delete this.startEvt;
+ }
+ }
+ return propagate;
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Util/vendorPrefix.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+OpenLayers.Util = OpenLayers.Util || {};
+/**
+ * Namespace: OpenLayers.Util.vendorPrefix
+ * A collection of utility functions to detect vendor prefixed features
+ */
+OpenLayers.Util.vendorPrefix = (function() {
+ "use strict";
+
+ var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
+ divStyle = document.createElement("div").style,
+ cssCache = {},
+ jsCache = {};
+
+
+ /**
+ * Function: domToCss
+ * Converts a upper camel case DOM style property name to a CSS property
+ * i.e. transformOrigin -> transform-origin
+ * or WebkitTransformOrigin -> -webkit-transform-origin
+ *
+ * Parameters:
+ * prefixedDom - {String} The property to convert
+ *
+ * Returns:
+ * {String} The CSS property
+ */
+ function domToCss(prefixedDom) {
+ if (!prefixedDom) { return null; }
+ return prefixedDom.
+ replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
+ replace(/^ms-/, "-ms-");
+ }
+
+ /**
+ * APIMethod: css
+ * Detect which property is used for a CSS property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) CSS property name
+ *
+ * Returns:
+ * {String} The standard CSS property, prefixed property or null if not
+ * supported
+ */
+ function css(property) {
+ if (cssCache[property] === undefined) {
+ var domProperty = property.
+ replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
+ var prefixedDom = style(domProperty);
+ cssCache[property] = domToCss(prefixedDom);
+ }
+ return cssCache[property];
+ }
+
+ /**
+ * APIMethod: js
+ * Detect which property is used for a JS property/method
+ *
+ * Parameters:
+ * obj - {Object} The object to test on
+ * property - {String} The standard (unprefixed) JS property name
+ *
+ * Returns:
+ * {String} The standard JS property, prefixed property or null if not
+ * supported
+ */
+ function js(obj, property) {
+ if (jsCache[property] === undefined) {
+ var tmpProp,
+ i = 0,
+ l = VENDOR_PREFIXES.length,
+ prefix,
+ isStyleObj = (typeof obj.cssText !== "undefined");
+
+ jsCache[property] = null;
+ for(; i<l; i++) {
+ prefix = VENDOR_PREFIXES[i];
+ if(prefix) {
+ if (!isStyleObj) {
+ // js prefix should be lower-case, while style
+ // properties have upper case on first character
+ prefix = prefix.toLowerCase();
+ }
+ tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
+ } else {
+ tmpProp = property;
+ }
+
+ if(obj[tmpProp] !== undefined) {
+ jsCache[property] = tmpProp;
+ break;
+ }
+ }
+ }
+ return jsCache[property];
+ }
+
+ /**
+ * APIMethod: style
+ * Detect which property is used for a DOM style property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) style property name
+ *
+ * Returns:
+ * {String} The standard style property, prefixed property or null if not
+ * supported
+ */
+ function style(property) {
+ return js(divStyle, property);
+ }
+
+ return {
+ css: css,
+ js: js,
+ style: style,
+
+ // used for testing
+ cssCache: cssCache,
+ jsCache: jsCache
+ };
+}());
+/* ======================================================================
+ OpenLayers/Animation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ */
+
+/**
+ * Namespace: OpenLayers.Animation
+ * A collection of utility functions for executing methods that repaint a
+ * portion of the browser window. These methods take advantage of the
+ * browser's scheduled repaints where requestAnimationFrame is available.
+ */
+OpenLayers.Animation = (function(window) {
+
+ /**
+ * Property: isNative
+ * {Boolean} true if a native requestAnimationFrame function is available
+ */
+ var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
+ var isNative = !!(requestAnimationFrame);
+
+ /**
+ * Function: requestFrame
+ * Schedule a function to be called at the next available animation frame.
+ * Uses the native method where available. Where requestAnimationFrame is
+ * not available, setTimeout will be called with a 16ms delay.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ */
+ var requestFrame = (function() {
+ var request = window[requestAnimationFrame] ||
+ function(callback, element) {
+ window.setTimeout(callback, 16);
+ };
+ // bind to window to avoid illegal invocation of native function
+ return function(callback, element) {
+ request.apply(window, [callback, element]);
+ };
+ })();
+
+ // private variables for animation loops
+ var counter = 0;
+ var loops = {};
+
+ /**
+ * Function: start
+ * Executes a method with <requestFrame> in series for some
+ * duration.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * duration - {Number} Optional duration for the loop. If not provided, the
+ * animation loop will execute indefinitely.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ *
+ * Returns:
+ * {Number} Identifier for the animation loop. Used to stop animations with
+ * <stop>.
+ */
+ function start(callback, duration, element) {
+ duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
+ var id = ++counter;
+ var start = +new Date;
+ loops[id] = function() {
+ if (loops[id] && +new Date - start <= duration) {
+ callback();
+ if (loops[id]) {
+ requestFrame(loops[id], element);
+ }
+ } else {
+ delete loops[id];
+ }
+ };
+ requestFrame(loops[id], element);
+ return id;
+ }
+
+ /**
+ * Function: stop
+ * Terminates an animation loop started with <start>.
+ *
+ * Parameters:
+ * id - {Number} Identifier returned from <start>.
+ */
+ function stop(id) {
+ delete loops[id];
+ }
+
+ return {
+ isNative: isNative,
+ requestFrame: requestFrame,
+ start: start,
+ stop: stop
+ };
+
+})(window);
+/* ======================================================================
+ OpenLayers/Tween.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+/**
+ * Namespace: OpenLayers.Tween
+ */
+OpenLayers.Tween = OpenLayers.Class({
+
+ /**
+ * APIProperty: easing
+ * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
+ * Defaultly set to OpenLayers.Easing.Expo.easeOut
+ */
+ easing: null,
+
+ /**
+ * APIProperty: begin
+ * {Object} Values to start the animation with
+ */
+ begin: null,
+
+ /**
+ * APIProperty: finish
+ * {Object} Values to finish the animation with
+ */
+ finish: null,
+
+ /**
+ * APIProperty: duration
+ * {int} duration of the tween (number of steps)
+ */
+ duration: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} An object with start, eachStep and done properties whose values
+ * are functions to be call during the animation. They are passed the
+ * current computed value as argument.
+ */
+ callbacks: null,
+
+ /**
+ * Property: time
+ * {int} Step counter
+ */
+ time: null,
+
+ /**
+ * APIProperty: minFrameRate
+ * {Number} The minimum framerate for animations in frames per second. After
+ * each step, the time spent in the animation is compared to the calculated
+ * time at this frame rate. If the animation runs longer than the calculated
+ * time, the next step is skipped. Default is 30.
+ */
+ minFrameRate: null,
+
+ /**
+ * Property: startTime
+ * {Number} The timestamp of the first execution step. Used for skipping
+ * frames
+ */
+ startTime: null,
+
+ /**
+ * Property: animationId
+ * {int} Loop id returned by OpenLayers.Animation.start
+ */
+ animationId: null,
+
+ /**
+ * Property: playing
+ * {Boolean} Tells if the easing is currently playing
+ */
+ playing: false,
+
+ /**
+ * Constructor: OpenLayers.Tween
+ * Creates a Tween.
+ *
+ * Parameters:
+ * easing - {<OpenLayers.Easing>(Function)} easing function method to use
+ */
+ initialize: function(easing) {
+ this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
+ },
+
+ /**
+ * APIMethod: start
+ * Plays the Tween, and calls the callback method on each step
+ *
+ * Parameters:
+ * begin - {Object} values to start the animation with
+ * finish - {Object} values to finish the animation with
+ * duration - {int} duration of the tween (number of steps)
+ * options - {Object} hash of options (callbacks (start, eachStep, done),
+ * minFrameRate)
+ */
+ start: function(begin, finish, duration, options) {
+ this.playing = true;
+ this.begin = begin;
+ this.finish = finish;
+ this.duration = duration;
+ this.callbacks = options.callbacks;
+ this.minFrameRate = options.minFrameRate || 30;
+ this.time = 0;
+ this.startTime = new Date().getTime();
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ if (this.callbacks && this.callbacks.start) {
+ this.callbacks.start.call(this, this.begin);
+ }
+ this.animationId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(this.play, this)
+ );
+ },
+
+ /**
+ * APIMethod: stop
+ * Stops the Tween, and calls the done callback
+ * Doesn't do anything if animation is already finished
+ */
+ stop: function() {
+ if (!this.playing) {
+ return;
+ }
+
+ if (this.callbacks && this.callbacks.done) {
+ this.callbacks.done.call(this, this.finish);
+ }
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ this.playing = false;
+ },
+
+ /**
+ * Method: play
+ * Calls the appropriate easing method
+ */
+ play: function() {
+ var value = {};
+ for (var i in this.begin) {
+ var b = this.begin[i];
+ var f = this.finish[i];
+ if (b == null || f == null || isNaN(b) || isNaN(f)) {
+ throw new TypeError('invalid value for Tween');
+ }
+
+ var c = f - b;
+ value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
+ }
+ this.time++;
+
+ if (this.callbacks && this.callbacks.eachStep) {
+ // skip frames if frame rate drops below threshold
+ if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
+ this.callbacks.eachStep.call(this, value);
+ }
+ }
+
+ if (this.time > this.duration) {
+ this.stop();
+ }
+ },
+
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Tween"
+});
+
+/**
+ * Namespace: OpenLayers.Easing
+ *
+ * Credits:
+ * Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
+ */
+OpenLayers.Easing = {
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Easing"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Linear
+ */
+OpenLayers.Easing.Linear = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Linear"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Expo
+ */
+OpenLayers.Easing.Expo = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if (t==0) return b;
+ if (t==d) return b+c;
+ if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Expo"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Quad
+ */
+OpenLayers.Easing.Quad = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Quad"
+};
+/* ======================================================================
+ OpenLayers/Projection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Projection
+ * Methods for coordinate transforms between coordinate systems. By default,
+ * OpenLayers ships with the ability to transform coordinates between
+ * geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
+ * coordinate reference systems. See the <transform> method for details
+ * on usage.
+ *
+ * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
+ * library. If the proj4js library is included, the <transform> method
+ * will work between any two coordinate reference systems with proj4js
+ * definitions.
+ *
+ * If the proj4js library is not included, or if you wish to allow transforms
+ * between arbitrary coordinate reference systems, use the <addTransform>
+ * method to register a custom transform method.
+ */
+OpenLayers.Projection = OpenLayers.Class({
+
+ /**
+ * Property: proj
+ * {Object} Proj4js.Proj instance.
+ */
+ proj: null,
+
+ /**
+ * Property: projCode
+ * {String}
+ */
+ projCode: null,
+
+ /**
+ * Property: titleRegEx
+ * {RegExp} regular expression to strip the title from a proj4js definition
+ */
+ titleRegEx: /\+title=[^\+]*/,
+
+ /**
+ * Constructor: OpenLayers.Projection
+ * This class offers several methods for interacting with a wrapped
+ * pro4js projection object.
+ *
+ * Parameters:
+ * projCode - {String} A string identifying the Well Known Identifier for
+ * the projection.
+ * options - {Object} An optional object to set additional properties
+ * on the projection.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} A projection object.
+ */
+ initialize: function(projCode, options) {
+ OpenLayers.Util.extend(this, options);
+ this.projCode = projCode;
+ if (typeof Proj4js == "object") {
+ this.proj = new Proj4js.Proj(projCode);
+ }
+ },
+
+ /**
+ * APIMethod: getCode
+ * Get the string SRS code.
+ *
+ * Returns:
+ * {String} The SRS code.
+ */
+ getCode: function() {
+ return this.proj ? this.proj.srsCode : this.projCode;
+ },
+
+ /**
+ * APIMethod: getUnits
+ * Get the units string for the projection -- returns null if
+ * proj4js is not available.
+ *
+ * Returns:
+ * {String} The units abbreviation.
+ */
+ getUnits: function() {
+ return this.proj ? this.proj.units : null;
+ },
+
+ /**
+ * Method: toString
+ * Convert projection to string (getCode wrapper).
+ *
+ * Returns:
+ * {String} The projection code.
+ */
+ toString: function() {
+ return this.getCode();
+ },
+
+ /**
+ * Method: equals
+ * Test equality of two projection instances. Determines equality based
+ * soley on the projection code.
+ *
+ * Returns:
+ * {Boolean} The two projections are equivalent.
+ */
+ equals: function(projection) {
+ var p = projection, equals = false;
+ if (p) {
+ if (!(p instanceof OpenLayers.Projection)) {
+ p = new OpenLayers.Projection(p);
+ }
+ if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
+ equals = this.proj.defData.replace(this.titleRegEx, "") ==
+ p.proj.defData.replace(this.titleRegEx, "");
+ } else if (p.getCode) {
+ var source = this.getCode(), target = p.getCode();
+ equals = source == target ||
+ !!OpenLayers.Projection.transforms[source] &&
+ OpenLayers.Projection.transforms[source][target] ===
+ OpenLayers.Projection.nullTransform;
+ }
+ }
+ return equals;
+ },
+
+ /* Method: destroy
+ * Destroy projection object.
+ */
+ destroy: function() {
+ delete this.proj;
+ delete this.projCode;
+ },
+
+ CLASS_NAME: "OpenLayers.Projection"
+});
+
+/**
+ * Property: transforms
+ * {Object} Transforms is an object, with from properties, each of which may
+ * have a to property. This allows you to define projections without
+ * requiring support for proj4js to be included.
+ *
+ * This object has keys which correspond to a 'source' projection object. The
+ * keys should be strings, corresponding to the projection.getCode() value.
+ * Each source projection object should have a set of destination projection
+ * keys included in the object.
+ *
+ * Each value in the destination object should be a transformation function,
+ * where the function is expected to be passed an object with a .x and a .y
+ * property. The function should return the object, with the .x and .y
+ * transformed according to the transformation function.
+ *
+ * Note - Properties on this object should not be set directly. To add a
+ * transform method to this object, use the <addTransform> method. For an
+ * example of usage, see the OpenLayers.Layer.SphericalMercator file.
+ */
+OpenLayers.Projection.transforms = {};
+
+/**
+ * APIProperty: defaults
+ * {Object} Defaults for the SRS codes known to OpenLayers (currently
+ * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
+ * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units,
+ * maxExtent (the validity extent for the SRS) and yx (true if this SRS is
+ * known to have a reverse axis order).
+ */
+OpenLayers.Projection.defaults = {
+ "EPSG:4326": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90],
+ yx: true
+ },
+ "CRS:84": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90]
+ },
+ "EPSG:900913": {
+ units: "m",
+ maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
+ }
+};
+
+/**
+ * APIMethod: addTransform
+ * Set a custom transform method between two projections. Use this method in
+ * cases where the proj4js lib is not available or where custom projections
+ * need to be handled.
+ *
+ * Parameters:
+ * from - {String} The code for the source projection
+ * to - {String} the code for the destination projection
+ * method - {Function} A function that takes a point as an argument and
+ * transforms that point from the source to the destination projection
+ * in place. The original point should be modified.
+ */
+OpenLayers.Projection.addTransform = function(from, to, method) {
+ if (method === OpenLayers.Projection.nullTransform) {
+ var defaults = OpenLayers.Projection.defaults[from];
+ if (defaults && !OpenLayers.Projection.defaults[to]) {
+ OpenLayers.Projection.defaults[to] = defaults;
+ }
+ }
+ if(!OpenLayers.Projection.transforms[from]) {
+ OpenLayers.Projection.transforms[from] = {};
+ }
+ OpenLayers.Projection.transforms[from][to] = method;
+};
+
+/**
+ * APIMethod: transform
+ * Transform a point coordinate from one projection to another. Note that
+ * the input point is transformed in place.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
+ * properties representing coordinates in those dimensions.
+ * source - {OpenLayers.Projection} Source map coordinate system
+ * dest - {OpenLayers.Projection} Destination map coordinate system
+ *
+ * Returns:
+ * point - {object} A transformed coordinate. The original point is modified.
+ */
+OpenLayers.Projection.transform = function(point, source, dest) {
+ if (source && dest) {
+ if (!(source instanceof OpenLayers.Projection)) {
+ source = new OpenLayers.Projection(source);
+ }
+ if (!(dest instanceof OpenLayers.Projection)) {
+ dest = new OpenLayers.Projection(dest);
+ }
+ if (source.proj && dest.proj) {
+ point = Proj4js.transform(source.proj, dest.proj, point);
+ } else {
+ var sourceCode = source.getCode();
+ var destCode = dest.getCode();
+ var transforms = OpenLayers.Projection.transforms;
+ if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
+ transforms[sourceCode][destCode](point);
+ }
+ }
+ }
+ return point;
+};
+
+/**
+ * APIFunction: nullTransform
+ * A null transformation - useful for defining projection aliases when
+ * proj4js is not available:
+ *
+ * (code)
+ * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
+ * OpenLayers.Projection.nullTransform);
+ * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
+ * OpenLayers.Projection.nullTransform);
+ * (end)
+ */
+OpenLayers.Projection.nullTransform = function(point) {
+ return point;
+};
+
+/**
+ * Note: Transforms for web mercator <-> geographic
+ * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100.
+ * OpenLayers originally started referring to EPSG:900913 as web mercator.
+ * The EPSG has declared EPSG:3857 to be web mercator.
+ * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
+ * equivalent. See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
+ * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
+ * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
+ * order for EPSG:4326.
+ */
+(function() {
+
+ var pole = 20037508.34;
+
+ function inverseMercator(xy) {
+ xy.x = 180 * xy.x / pole;
+ xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
+ return xy;
+ }
+
+ function forwardMercator(xy) {
+ xy.x = xy.x * pole / 180;
+ var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
+ xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
+ return xy;
+ }
+
+ function map(base, codes) {
+ var add = OpenLayers.Projection.addTransform;
+ var same = OpenLayers.Projection.nullTransform;
+ var i, len, code, other, j;
+ for (i=0, len=codes.length; i<len; ++i) {
+ code = codes[i];
+ add(base, code, forwardMercator);
+ add(code, base, inverseMercator);
+ for (j=i+1; j<len; ++j) {
+ other = codes[j];
+ add(code, other, same);
+ add(other, code, same);
+ }
+ }
+ }
+
+ // list of equivalent codes for web mercator
+ var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
+ geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
+ i;
+ for (i=mercator.length-1; i>=0; --i) {
+ map(mercator[i], geographic);
+ }
+ for (i=geographic.length-1; i>=0; --i) {
+ map(geographic[i], mercator);
+ }
+
+})();
+/* ======================================================================
+ OpenLayers/Map.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Tween.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Map
+ * Instances of OpenLayers.Map are interactive maps embedded in a web page.
+ * Create a new map with the <OpenLayers.Map> constructor.
+ *
+ * On their own maps do not provide much functionality. To extend a map
+ * it's necessary to add controls (<OpenLayers.Control>) and
+ * layers (<OpenLayers.Layer>) to the map.
+ */
+OpenLayers.Map = OpenLayers.Class({
+
+ /**
+ * Constant: Z_INDEX_BASE
+ * {Object} Base z-indexes for different classes of thing
+ */
+ Z_INDEX_BASE: {
+ BaseLayer: 100,
+ Overlay: 325,
+ Feature: 725,
+ Popup: 750,
+ Control: 1000
+ },
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * map.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to map.events.object.
+ * element - {DOMElement} A reference to map.events.element.
+ *
+ * Browser events have the following additional properties:
+ * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
+ * to the the map viewport).
+ *
+ * Supported map event types:
+ * preaddlayer - triggered before a layer has been added. The event
+ * object will include a *layer* property that references the layer
+ * to be added. When a listener returns "false" the adding will be
+ * aborted.
+ * addlayer - triggered after a layer has been added. The event object
+ * will include a *layer* property that references the added layer.
+ * preremovelayer - triggered before a layer has been removed. The event
+ * object will include a *layer* property that references the layer
+ * to be removed. When a listener returns "false" the removal will be
+ * aborted.
+ * removelayer - triggered after a layer has been removed. The event
+ * object will include a *layer* property that references the removed
+ * layer.
+ * changelayer - triggered after a layer name change, order change,
+ * opacity change, params change, visibility change (actual visibility,
+ * not the layer's visibility property) or attribution change (due to
+ * extent change). Listeners will receive an event object with *layer*
+ * and *property* properties. The *layer* property will be a reference
+ * to the changed layer. The *property* property will be a key to the
+ * changed property (name, order, opacity, params, visibility or
+ * attribution).
+ * movestart - triggered after the start of a drag, pan, or zoom. The event
+ * object may include a *zoomChanged* property that tells whether the
+ * zoom has changed.
+ * move - triggered after each drag, pan, or zoom
+ * moveend - triggered after a drag, pan, or zoom completes
+ * zoomend - triggered after a zoom completes
+ * mouseover - triggered after mouseover the map
+ * mouseout - triggered after mouseout the map
+ * mousemove - triggered after mousemove the map
+ * changebaselayer - triggered after the base layer changes
+ * updatesize - triggered after the <updateSize> method was executed
+ */
+
+ /**
+ * Property: id
+ * {String} Unique identifier for the map
+ */
+ id: null,
+
+ /**
+ * Property: fractionalZoom
+ * {Boolean} For a base layer that supports it, allow the map resolution
+ * to be set to a value between one of the values in the resolutions
+ * array. Default is false.
+ *
+ * When fractionalZoom is set to true, it is possible to zoom to
+ * an arbitrary extent. This requires a base layer from a source
+ * that supports requests for arbitrary extents (i.e. not cached
+ * tiles on a regular lattice). This means that fractionalZoom
+ * will not work with commercial layers (Google, Yahoo, VE), layers
+ * using TileCache, or any other pre-cached data sources.
+ *
+ * If you are using fractionalZoom, then you should also use
+ * <getResolutionForZoom> instead of layer.resolutions[zoom] as the
+ * former works for non-integer zoom levels.
+ */
+ fractionalZoom: false,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the map
+ */
+ events: null,
+
+ /**
+ * APIProperty: allOverlays
+ * {Boolean} Allow the map to function with "overlays" only. Defaults to
+ * false. If true, the lowest layer in the draw order will act as
+ * the base layer. In addition, if set to true, all layers will
+ * have isBaseLayer set to false when they are added to the map.
+ *
+ * Note:
+ * If you set map.allOverlays to true, then you *cannot* use
+ * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true,
+ * the lowest layer in the draw layer is the base layer. So, to change
+ * the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
+ * index to 0.
+ */
+ allOverlays: false,
+
+ /**
+ * APIProperty: div
+ * {DOMElement|String} The element that contains the map (or an id for
+ * that element). If the <OpenLayers.Map> constructor is called
+ * with two arguments, this should be provided as the first argument.
+ * Alternatively, the map constructor can be called with the options
+ * object as the only argument. In this case (one argument), a
+ * div property may or may not be provided. If the div property
+ * is not provided, the map can be rendered to a container later
+ * using the <render> method.
+ *
+ * Note:
+ * If you are calling <render> after map construction, do not use
+ * <maxResolution> auto. Instead, divide your <maxExtent> by your
+ * maximum expected dimension.
+ */
+ div: null,
+
+ /**
+ * Property: dragging
+ * {Boolean} The map is currently being dragged.
+ */
+ dragging: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} Size of the main div (this.div)
+ */
+ size: null,
+
+ /**
+ * Property: viewPortDiv
+ * {HTMLDivElement} The element that represents the map viewport
+ */
+ viewPortDiv: null,
+
+ /**
+ * Property: layerContainerOrigin
+ * {<OpenLayers.LonLat>} The lonlat at which the later container was
+ * re-initialized (on-zoom)
+ */
+ layerContainerOrigin: null,
+
+ /**
+ * Property: layerContainerDiv
+ * {HTMLDivElement} The element that contains the layers.
+ */
+ layerContainerDiv: null,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
+ */
+ layers: null,
+
+ /**
+ * APIProperty: controls
+ * {Array(<OpenLayers.Control>)} List of controls associated with the map.
+ *
+ * If not provided in the map options at construction, the map will
+ * by default be given the following controls if present in the build:
+ * - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
+ * - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
+ * - <OpenLayers.Control.ArgParser>
+ * - <OpenLayers.Control.Attribution>
+ */
+ controls: null,
+
+ /**
+ * Property: popups
+ * {Array(<OpenLayers.Popup>)} List of popups associated with the map
+ */
+ popups: null,
+
+ /**
+ * APIProperty: baseLayer
+ * {<OpenLayers.Layer>} The currently selected base layer. This determines
+ * min/max zoom level, projection, etc.
+ */
+ baseLayer: null,
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>} The current center of the map
+ */
+ center: null,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution of the map.
+ */
+ resolution: null,
+
+ /**
+ * Property: zoom
+ * {Integer} The current zoom level of the map
+ */
+ zoom: 0,
+
+ /**
+ * Property: panRatio
+ * {Float} The ratio of the current extent within
+ * which panning will tween.
+ */
+ panRatio: 1.5,
+
+ /**
+ * APIProperty: options
+ * {Object} The options object passed to the class constructor. Read-only.
+ */
+ options: null,
+
+ // Options
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} Set in the map options to override the default tile
+ * size for this map.
+ */
+ tileSize: null,
+
+ /**
+ * APIProperty: projection
+ * {String} Set in the map options to specify the default projection
+ * for layers added to this map. When using a projection other than EPSG:4326
+ * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
+ * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326".
+ * Note that the projection of the map is usually determined
+ * by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
+ */
+ projection: "EPSG:4326",
+
+ /**
+ * APIProperty: units
+ * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm',
+ * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units
+ */
+ units: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array(Float)} A list of map resolutions (map units per pixel) in
+ * descending order. If this is not set in the layer constructor, it
+ * will be set based on other resolution related properties
+ * (maxExtent, maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Required if you are not displaying the whole world on a tile
+ * with the size specified in <tileSize>.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the map.
+ * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
+ * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
+ * else, defaults to null.
+ * To restrict user panning and zooming of the map, use <restrictedExtent> instead.
+ * The value for <maxExtent> will change calculations for tile URLs.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the map. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: restrictedExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * Limit map navigation to this extent where possible.
+ * If a non-null restrictedExtent is set, panning will be restricted
+ * to the given bounds. In addition, zooming to a resolution that
+ * displays more than the restricted extent will center the map
+ * on the restricted extent. If you wish to limit the zoom level
+ * or resolution, use maxResolution.
+ */
+ restrictedExtent: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer} Number of zoom levels for the map. Defaults to 16. Set a
+ * different value in the map options if needed.
+ */
+ numZoomLevels: 16,
+
+ /**
+ * APIProperty: theme
+ * {String} Relative path to a CSS file from which to load theme styles.
+ * Specify null in the map options (e.g. {theme: null}) if you
+ * want to get cascading style declarations - by putting links to
+ * stylesheets or style declarations directly in your page.
+ */
+ theme: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support for projections other
+ * than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
+ * several controls to display data to user. If this property is set,
+ * it will be set on any control which has a null displayProjection
+ * property at the time the control is added to the map.
+ */
+ displayProjection: null,
+
+ /**
+ * APIProperty: tileManager
+ * {<OpenLayers.TileManager>|Object} By default, and if the build contains
+ * TileManager.js, the map will use the TileManager to queue image requests
+ * and to cache tile image elements. To create a map without a TileManager
+ * configure the map with tileManager: null. To create a TileManager with
+ * non-default options, supply the options instead or alternatively supply
+ * an instance of {<OpenLayers.TileManager>}.
+ */
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean} Should OpenLayers allow events on the map to fall through to
+ * other elements on the page, or should it swallow them? (#457)
+ * Default is to swallow.
+ */
+ fallThrough: false,
+
+ /**
+ * APIProperty: autoUpdateSize
+ * {Boolean} Should OpenLayers automatically update the size of the map
+ * when the resize event is fired. Default is true.
+ */
+ autoUpdateSize: true,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * Property: panTween
+ * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
+ */
+ panTween: null,
+
+ /**
+ * APIProperty: panMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
+ * animated panning.
+ */
+ panMethod: OpenLayers.Easing.Expo.easeOut,
+
+ /**
+ * Property: panDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is
+ * panned.
+ * Default is 50.
+ */
+ panDuration: 50,
+
+ /**
+ * Property: zoomTween
+ * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
+ */
+ zoomTween: null,
+
+ /**
+ * APIProperty: zoomMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
+ * animated zooming.
+ */
+ zoomMethod: OpenLayers.Easing.Quad.easeOut,
+
+ /**
+ * Property: zoomDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is zoomed.
+ * Default is 20.
+ */
+ zoomDuration: 20,
+
+ /**
+ * Property: paddingForPopups
+ * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent
+ * the popup from getting too close to the map border.
+ */
+ paddingForPopups : null,
+
+ /**
+ * Property: layerContainerOriginPx
+ * {Object} Cached object representing the layer container origin (in pixels).
+ */
+ layerContainerOriginPx: null,
+
+ /**
+ * Property: minPx
+ * {Object} An object with a 'x' and 'y' values that is the lower
+ * left of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid. It is also used in the getLonLatFromViewPortPx function
+ * of Layer.
+ */
+ minPx: null,
+
+ /**
+ * Property: maxPx
+ * {Object} An object with a 'x' and 'y' values that is the top
+ * right of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid.
+ */
+ maxPx: null,
+
+ /**
+ * Constructor: OpenLayers.Map
+ * Constructor for a new OpenLayers.Map instance. There are two possible
+ * ways to call the map constructor. See the examples below.
+ *
+ * Parameters:
+ * div - {DOMElement|String} The element or id of an element in your page
+ * that will contain the map. May be omitted if the <div> option is
+ * provided or if you intend to call the <render> method later.
+ * options - {Object} Optional object with properties to tag onto the map.
+ *
+ * Valid options (in addition to the listed API properties):
+ * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * Only specify if <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains coordinates, center will be set
+ * by that, and this option will be ignored.
+ * zoom - {Number} The initial zoom level for the map. Only specify if
+ * <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains a zoom level, zoom will be set
+ * by that, and this option will be ignored.
+ * extent - {<OpenLayers.Bounds>|Array} The initial extent of the map.
+ * If provided as an array, the array should consist of
+ * four values (left, bottom, right, top).
+ * Only specify if <center> and <zoom> are not provided.
+ *
+ * Examples:
+ * (code)
+ * // create a map with default options in an element with the id "map1"
+ * var map = new OpenLayers.Map("map1");
+ *
+ * // create a map with non-default options in an element with id "map2"
+ * var options = {
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
+ * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
+ * };
+ * var map = new OpenLayers.Map("map2", options);
+ *
+ * // map with non-default options - same as above but with a single argument,
+ * // a restricted extent, and using arrays for bounds and center
+ * var map = new OpenLayers.Map({
+ * div: "map_id",
+ * projection: "EPSG:3857",
+ * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
+ * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
+ * center: [-12356463.476333, 5621521.4854095]
+ * });
+ *
+ * // create a map without a reference to a container - call render later
+ * var map = new OpenLayers.Map({
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
+ * });
+ * (end)
+ */
+ initialize: function (div, options) {
+
+ // If only one argument is provided, check if it is an object.
+ if(arguments.length === 1 && typeof div === "object") {
+ options = div;
+ div = options && options.div;
+ }
+
+ // Simple-type defaults are set in class definition.
+ // Now set complex-type defaults
+ this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+ OpenLayers.Map.TILE_HEIGHT);
+
+ this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
+
+ this.theme = OpenLayers._getScriptLocation() +
+ 'theme/default/style.css';
+
+ // backup original options
+ this.options = OpenLayers.Util.extend({}, options);
+
+ // now override default options
+ OpenLayers.Util.extend(this, options);
+
+ var projCode = this.projection instanceof OpenLayers.Projection ?
+ this.projection.projCode : this.projection;
+ OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
+
+ // allow extents and center to be arrays
+ if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
+ this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
+ }
+ if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
+ this.minExtent = new OpenLayers.Bounds(this.minExtent);
+ }
+ if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
+ this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
+ }
+ if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
+ this.center = new OpenLayers.LonLat(this.center);
+ }
+
+ // initialize layers array
+ this.layers = [];
+
+ this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
+
+ this.div = OpenLayers.Util.getElement(div);
+ if(!this.div) {
+ this.div = document.createElement("div");
+ this.div.style.height = "1px";
+ this.div.style.width = "1px";
+ }
+
+ OpenLayers.Element.addClass(this.div, 'olMap');
+
+ // the viewPortDiv is the outermost div we modify
+ var id = this.id + "_OpenLayers_ViewPort";
+ this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
+ "relative", null,
+ "hidden");
+ this.viewPortDiv.style.width = "100%";
+ this.viewPortDiv.style.height = "100%";
+ this.viewPortDiv.className = "olMapViewport";
+ this.div.appendChild(this.viewPortDiv);
+
+ this.events = new OpenLayers.Events(
+ this, this.viewPortDiv, null, this.fallThrough,
+ {includeXY: true}
+ );
+
+ if (OpenLayers.TileManager && this.tileManager !== null) {
+ if (!(this.tileManager instanceof OpenLayers.TileManager)) {
+ this.tileManager = new OpenLayers.TileManager(this.tileManager);
+ }
+ this.tileManager.addMap(this);
+ }
+
+ // the layerContainerDiv is the one that holds all the layers
+ id = this.id + "_OpenLayers_Container";
+ this.layerContainerDiv = OpenLayers.Util.createDiv(id);
+ this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
+ this.layerContainerOriginPx = {x: 0, y: 0};
+ this.applyTransform();
+
+ this.viewPortDiv.appendChild(this.layerContainerDiv);
+
+ this.updateSize();
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ if (this.autoUpdateSize === true) {
+ // updateSize on catching the window's resize
+ // Note that this is ok, as updateSize() does nothing if the
+ // map's size has not actually changed.
+ this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize,
+ this);
+ OpenLayers.Event.observe(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ // only append link stylesheet if the theme property is set
+ if(this.theme) {
+ // check existing links for equivalent url
+ var addNode = true;
+ var nodes = document.getElementsByTagName('link');
+ for(var i=0, len=nodes.length; i<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
+ this.theme)) {
+ addNode = false;
+ break;
+ }
+ }
+ // only add a new node if one with an equivalent url hasn't already
+ // been added
+ if(addNode) {
+ var cssNode = document.createElement('link');
+ cssNode.setAttribute('rel', 'stylesheet');
+ cssNode.setAttribute('type', 'text/css');
+ cssNode.setAttribute('href', this.theme);
+ document.getElementsByTagName('head')[0].appendChild(cssNode);
+ }
+ }
+
+ if (this.controls == null) { // default controls
+ this.controls = [];
+ if (OpenLayers.Control != null) { // running full or lite?
+ // Navigation or TouchNavigation depending on what is in build
+ if (OpenLayers.Control.Navigation) {
+ this.controls.push(new OpenLayers.Control.Navigation());
+ } else if (OpenLayers.Control.TouchNavigation) {
+ this.controls.push(new OpenLayers.Control.TouchNavigation());
+ }
+ if (OpenLayers.Control.Zoom) {
+ this.controls.push(new OpenLayers.Control.Zoom());
+ } else if (OpenLayers.Control.PanZoom) {
+ this.controls.push(new OpenLayers.Control.PanZoom());
+ }
+
+ if (OpenLayers.Control.ArgParser) {
+ this.controls.push(new OpenLayers.Control.ArgParser());
+ }
+ if (OpenLayers.Control.Attribution) {
+ this.controls.push(new OpenLayers.Control.Attribution());
+ }
+ }
+ }
+
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ this.addControlToMap(this.controls[i]);
+ }
+
+ this.popups = [];
+
+ this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
+
+
+ // always call map.destroy()
+ OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
+
+ // add any initial layers
+ if (options && options.layers) {
+ /**
+ * If you have set options.center, the map center property will be
+ * set at this point. However, since setCenter has not been called,
+ * addLayers gets confused. So we delete the map center in this
+ * case. Because the check below uses options.center, it will
+ * be properly set below.
+ */
+ delete this.center;
+ delete this.zoom;
+ this.addLayers(options.layers);
+ // set center (and optionally zoom)
+ if (options.center && !this.getCenter()) {
+ // zoom can be undefined here
+ this.setCenter(options.center, options.zoom);
+ }
+ }
+
+ if (this.panMethod) {
+ this.panTween = new OpenLayers.Tween(this.panMethod);
+ }
+ if (this.zoomMethod && this.applyTransform.transform) {
+ this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
+ }
+ },
+
+ /**
+ * APIMethod: getViewport
+ * Get the DOMElement representing the view port.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ getViewport: function() {
+ return this.viewPortDiv;
+ },
+
+ /**
+ * APIMethod: render
+ * Render the map to a specified container.
+ *
+ * Parameters:
+ * div - {String|DOMElement} The container that the map should be rendered
+ * to. If different than the current container, the map viewport
+ * will be moved from the current to the new container.
+ */
+ render: function(div) {
+ this.div = OpenLayers.Util.getElement(div);
+ OpenLayers.Element.addClass(this.div, 'olMap');
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ this.div.appendChild(this.viewPortDiv);
+ this.updateSize();
+ },
+
+ /**
+ * Method: unloadDestroy
+ * Function that is called to destroy the map on page unload. stored here
+ * so that if map is manually destroyed, we can unregister this.
+ */
+ unloadDestroy: null,
+
+ /**
+ * Method: updateSizeDestroy
+ * When the map is destroyed, we need to stop listening to updateSize
+ * events: this method stores the function we need to unregister in
+ * non-IE browsers.
+ */
+ updateSizeDestroy: null,
+
+ /**
+ * APIMethod: destroy
+ * Destroy this map.
+ * Note that if you are using an application which removes a container
+ * of the map from the DOM, you need to ensure that you destroy the
+ * map *before* this happens; otherwise, the page unload handler
+ * will fail because the DOM elements that map.destroy() wants
+ * to clean up will be gone. (See
+ * http://trac.osgeo.org/openlayers/ticket/2277 for more information).
+ * This will apply to GeoExt and also to other applications which
+ * modify the DOM of the container of the OpenLayers Map.
+ */
+ destroy:function() {
+ // if unloadDestroy is null, we've already been destroyed
+ if (!this.unloadDestroy) {
+ return false;
+ }
+
+ // make sure panning doesn't continue after destruction
+ if(this.panTween) {
+ this.panTween.stop();
+ this.panTween = null;
+ }
+ // make sure zooming doesn't continue after destruction
+ if(this.zoomTween) {
+ this.zoomTween.stop();
+ this.zoomTween = null;
+ }
+
+ // map has been destroyed. dont do it again!
+ OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
+ this.unloadDestroy = null;
+
+ if (this.updateSizeDestroy) {
+ OpenLayers.Event.stopObserving(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ this.paddingForPopups = null;
+
+ if (this.controls != null) {
+ for (var i = this.controls.length - 1; i>=0; --i) {
+ this.controls[i].destroy();
+ }
+ this.controls = null;
+ }
+ if (this.layers != null) {
+ for (var i = this.layers.length - 1; i>=0; --i) {
+ //pass 'false' to destroy so that map wont try to set a new
+ // baselayer after each baselayer is removed
+ this.layers[i].destroy(false);
+ }
+ this.layers = null;
+ }
+ if (this.viewPortDiv && this.viewPortDiv.parentNode) {
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ }
+ this.viewPortDiv = null;
+
+ if (this.tileManager) {
+ this.tileManager.removeMap(this);
+ this.tileManager = null;
+ }
+
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ this.eventListeners = null;
+ }
+ this.events.destroy();
+ this.events = null;
+
+ this.options = null;
+ },
+
+ /**
+ * APIMethod: setOptions
+ * Change the map options
+ *
+ * Parameters:
+ * options - {Object} Hashtable of options to tag to the map
+ */
+ setOptions: function(options) {
+ var updatePxExtent = this.minPx &&
+ options.restrictedExtent != this.restrictedExtent;
+ OpenLayers.Util.extend(this, options);
+ // force recalculation of minPx and maxPx
+ updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
+ forceZoomChange: true
+ });
+ },
+
+ /**
+ * APIMethod: getTileSize
+ * Get the tile size for the map
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+ getTileSize: function() {
+ return this.tileSize;
+ },
+
+
+ /**
+ * APIMethod: getBy
+ * Get a list of objects given a property and a match item.
+ *
+ * Parameters:
+ * array - {String} A property on the map whose value is an array.
+ * property - {String} A property on each item of the given array.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(map[array][i][property]) evaluates to true, the item will
+ * be included in the array returned. If no items are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array} An array of items where the given property matches the given
+ * criteria.
+ */
+ getBy: function(array, property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this[array], function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getLayersBy
+ * Get a list of layers with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A layer property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersBy: function(property, match) {
+ return this.getBy("layers", property, match);
+ },
+
+ /**
+ * APIMethod: getLayersByName
+ * Get a list of layers with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A layer name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(layer.name) evaluates to true, the layer will be included
+ * in the list of layers returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByName: function(match) {
+ return this.getLayersBy("name", match);
+ },
+
+ /**
+ * APIMethod: getLayersByClass
+ * Get a list of layers of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A layer class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(layer.CLASS_NAME) evaluates to true, the layer will
+ * be included in the list of layers returned. If no layers are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByClass: function(match) {
+ return this.getLayersBy("CLASS_NAME", match);
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given
+ * criteria. An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ return this.getBy("controls", property, match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Layers to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getLayer
+ * Get a layer based on its id
+ *
+ * Parameters:
+ * id - {String} A layer id
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
+ * layer collection, or null if not found.
+ */
+ getLayer: function(id) {
+ var foundLayer = null;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer.id == id) {
+ foundLayer = layer;
+ break;
+ }
+ }
+ return foundLayer;
+ },
+
+ /**
+ * Method: setLayerZIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * zIdx - {int}
+ */
+ setLayerZIndex: function (layer, zIdx) {
+ layer.setZIndex(
+ this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+ + zIdx * 5 );
+ },
+
+ /**
+ * Method: resetLayersZIndex
+ * Reset each layer's z-index based on layer's array index
+ */
+ resetLayersZIndex: function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ this.setLayerZIndex(layer, i);
+ }
+ },
+
+ /**
+ * APIMethod: addLayer
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Boolean} True if the layer has been added to the map.
+ */
+ addLayer: function (layer) {
+ for(var i = 0, len = this.layers.length; i < len; i++) {
+ if (this.layers[i] == layer) {
+ return false;
+ }
+ }
+ if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
+ return false;
+ }
+ if(this.allOverlays) {
+ layer.isBaseLayer = false;
+ }
+
+ layer.div.className = "olLayerDiv";
+ layer.div.style.overflow = "";
+ this.setLayerZIndex(layer, this.layers.length);
+
+ if (layer.isFixed) {
+ this.viewPortDiv.appendChild(layer.div);
+ } else {
+ this.layerContainerDiv.appendChild(layer.div);
+ }
+ this.layers.push(layer);
+ layer.setMap(this);
+
+ if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) {
+ if (this.baseLayer == null) {
+ // set the first baselaye we add as the baselayer
+ this.setBaseLayer(layer);
+ } else {
+ layer.setVisibility(false);
+ }
+ } else {
+ layer.redraw();
+ }
+
+ this.events.triggerEvent("addlayer", {layer: layer});
+ layer.events.triggerEvent("added", {map: this, layer: layer});
+ layer.afterAdd();
+
+ return true;
+ },
+
+ /**
+ * APIMethod: addLayers
+ *
+ * Parameters:
+ * layers - {Array(<OpenLayers.Layer>)}
+ */
+ addLayers: function (layers) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ /**
+ * APIMethod: removeLayer
+ * Removes a layer from the map by removing its visual element (the
+ * layer.div property), then removing it from the map's internal list
+ * of layers, setting the layer's map property to null.
+ *
+ * a "removelayer" event is triggered.
+ *
+ * very worthy of mention is that simply removing a layer from a map
+ * will not cause the removal of any popups which may have been created
+ * by the layer. this is due to the fact that it was decided at some
+ * point that popups would not belong to layers. thus there is no way
+ * for us to know here to which layer the popup belongs.
+ *
+ * A simple solution to this is simply to call destroy() on the layer.
+ * the default OpenLayers.Layer class's destroy() function
+ * automatically takes care to remove itself from whatever map it has
+ * been attached to.
+ *
+ * The correct solution is for the layer itself to register an
+ * event-handler on "removelayer" and when it is called, if it
+ * recognizes itself as the layer being removed, then it cycles through
+ * its own personal list of popups, removing them from the map.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * setNewBaseLayer - {Boolean} Default is true
+ */
+ removeLayer: function(layer, setNewBaseLayer) {
+ if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
+ return;
+ }
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+
+ if (layer.isFixed) {
+ this.viewPortDiv.removeChild(layer.div);
+ } else {
+ this.layerContainerDiv.removeChild(layer.div);
+ }
+ OpenLayers.Util.removeItem(this.layers, layer);
+ layer.removeMap(this);
+ layer.map = null;
+
+ // if we removed the base layer, need to set a new one
+ if(this.baseLayer == layer) {
+ this.baseLayer = null;
+ if(setNewBaseLayer) {
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ var iLayer = this.layers[i];
+ if (iLayer.isBaseLayer || this.allOverlays) {
+ this.setBaseLayer(iLayer);
+ break;
+ }
+ }
+ }
+ }
+
+ this.resetLayersZIndex();
+
+ this.events.triggerEvent("removelayer", {layer: layer});
+ layer.events.triggerEvent("removed", {map: this, layer: layer});
+ },
+
+ /**
+ * APIMethod: getNumLayers
+ *
+ * Returns:
+ * {Int} The number of layers attached to the map.
+ */
+ getNumLayers: function () {
+ return this.layers.length;
+ },
+
+ /**
+ * APIMethod: getLayerIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Integer} The current (zero-based) index of the given layer in the map's
+ * layer stack. Returns -1 if the layer isn't on the map.
+ */
+ getLayerIndex: function (layer) {
+ return OpenLayers.Util.indexOf(this.layers, layer);
+ },
+
+ /**
+ * APIMethod: setLayerIndex
+ * Move the given layer to the specified (zero-based) index in the layer
+ * list, changing its z-index in the map display. Use
+ * map.getLayerIndex() to find out the current index of a layer. Note
+ * that this cannot (or at least should not) be effectively used to
+ * raise base layers above overlays.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * idx - {int}
+ */
+ setLayerIndex: function (layer, idx) {
+ var base = this.getLayerIndex(layer);
+ if (idx < 0) {
+ idx = 0;
+ } else if (idx > this.layers.length) {
+ idx = this.layers.length;
+ }
+ if (base != idx) {
+ this.layers.splice(base, 1);
+ this.layers.splice(idx, 0, layer);
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ this.setLayerZIndex(this.layers[i], i);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "order"
+ });
+ if(this.allOverlays) {
+ if(idx === 0) {
+ this.setBaseLayer(layer);
+ } else if(this.baseLayer !== this.layers[0]) {
+ this.setBaseLayer(this.layers[0]);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: raiseLayer
+ * Change the index of the given layer by delta. If delta is positive,
+ * the layer is moved up the map's layer stack; if delta is negative,
+ * the layer is moved down. Again, note that this cannot (or at least
+ * should not) be effectively used to raise base layers above overlays.
+ *
+ * Paremeters:
+ * layer - {<OpenLayers.Layer>}
+ * delta - {int}
+ */
+ raiseLayer: function (layer, delta) {
+ var idx = this.getLayerIndex(layer) + delta;
+ this.setLayerIndex(layer, idx);
+ },
+
+ /**
+ * APIMethod: setBaseLayer
+ * Allows user to specify one of the currently-loaded layers as the Map's
+ * new base layer.
+ *
+ * Parameters:
+ * newBaseLayer - {<OpenLayers.Layer>}
+ */
+ setBaseLayer: function(newBaseLayer) {
+
+ if (newBaseLayer != this.baseLayer) {
+
+ // ensure newBaseLayer is already loaded
+ if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
+
+ // preserve center and scale when changing base layers
+ var center = this.getCachedCenter();
+ var newResolution = OpenLayers.Util.getResolutionFromScale(
+ this.getScale(), newBaseLayer.units
+ );
+
+ // make the old base layer invisible
+ if (this.baseLayer != null && !this.allOverlays) {
+ this.baseLayer.setVisibility(false);
+ }
+
+ // set new baselayer
+ this.baseLayer = newBaseLayer;
+
+ if(!this.allOverlays || this.baseLayer.visibility) {
+ this.baseLayer.setVisibility(true);
+ // Layer may previously have been visible but not in range.
+ // In this case we need to redraw it to make it visible.
+ if (this.baseLayer.inRange === false) {
+ this.baseLayer.redraw();
+ }
+ }
+
+ // recenter the map
+ if (center != null) {
+ // new zoom level derived from old scale
+ var newZoom = this.getZoomForResolution(
+ newResolution || this.resolution, true
+ );
+ // zoom and force zoom change
+ this.setCenter(center, newZoom, false, true);
+ }
+
+ this.events.triggerEvent("changebaselayer", {
+ layer: this.baseLayer
+ });
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Control Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Controls to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addControl
+ * Add the passed over control to the map. Optionally
+ * position the control at the given pixel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControl: function (control, px) {
+ this.controls.push(control);
+ this.addControlToMap(control, px);
+ },
+
+ /**
+ * APIMethod: addControls
+ * Add all of the passed over controls to the map.
+ * You can pass over an optional second array
+ * with pixel-objects to position the controls.
+ * The indices of the two arrays should match and
+ * you can add null as pixel for those controls
+ * you want to be autopositioned.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)}
+ * pixels - {Array(<OpenLayers.Pixel>)}
+ */
+ addControls: function (controls, pixels) {
+ var pxs = (arguments.length === 1) ? [] : pixels;
+ for (var i=0, len=controls.length; i<len; i++) {
+ var ctrl = controls[i];
+ var px = (pxs[i]) ? pxs[i] : null;
+ this.addControl( ctrl, px );
+ }
+ },
+
+ /**
+ * Method: addControlToMap
+ *
+ * Parameters:
+ *
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControlToMap: function (control, px) {
+ // If a control doesn't have a div at this point, it belongs in the
+ // viewport.
+ control.outsideViewport = (control.div != null);
+
+ // If the map has a displayProjection, and the control doesn't, set
+ // the display projection.
+ if (this.displayProjection && !control.displayProjection) {
+ control.displayProjection = this.displayProjection;
+ }
+
+ control.setMap(this);
+ var div = control.draw(px);
+ if (div) {
+ if(!control.outsideViewport) {
+ div.style.zIndex = this.Z_INDEX_BASE['Control'] +
+ this.controls.length;
+ this.viewPortDiv.appendChild( div );
+ }
+ }
+ if(control.autoActivate) {
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: getControl
+ *
+ * Parameters:
+ * id - {String} ID of the control to return.
+ *
+ * Returns:
+ * {<OpenLayers.Control>} The control from the map's list of controls
+ * which has a matching 'id'. If none found,
+ * returns null.
+ */
+ getControl: function (id) {
+ var returnControl = null;
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ var control = this.controls[i];
+ if (control.id == id) {
+ returnControl = control;
+ break;
+ }
+ }
+ return returnControl;
+ },
+
+ /**
+ * APIMethod: removeControl
+ * Remove a control from the map. Removes the control both from the map
+ * object's internal array of controls, as well as from the map's
+ * viewPort (assuming the control was not added outsideViewport)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to remove.
+ */
+ removeControl: function (control) {
+ //make sure control is non-null and actually part of our map
+ if ( (control) && (control == this.getControl(control.id)) ) {
+ if (control.div && (control.div.parentNode == this.viewPortDiv)) {
+ this.viewPortDiv.removeChild(control.div);
+ }
+ OpenLayers.Util.removeItem(this.controls, control);
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Popup Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Popups to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addPopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ * exclusive - {Boolean} If true, closes all other popups first
+ */
+ addPopup: function(popup, exclusive) {
+
+ if (exclusive) {
+ //remove all other popups from screen
+ for (var i = this.popups.length - 1; i >= 0; --i) {
+ this.removePopup(this.popups[i]);
+ }
+ }
+
+ popup.map = this;
+ this.popups.push(popup);
+ var popupDiv = popup.draw();
+ if (popupDiv) {
+ popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
+ this.popups.length;
+ this.layerContainerDiv.appendChild(popupDiv);
+ }
+ },
+
+ /**
+ * APIMethod: removePopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ */
+ removePopup: function(popup) {
+ OpenLayers.Util.removeItem(this.popups, popup);
+ if (popup.div) {
+ try { this.layerContainerDiv.removeChild(popup.div); }
+ catch (e) { } // Popups sometimes apparently get disconnected
+ // from the layerContainerDiv, and cause complaints.
+ }
+ popup.map = null;
+ },
+
+ /********************************************************/
+ /* */
+ /* Container Div Functions */
+ /* */
+ /* The following functions deal with the access to */
+ /* and maintenance of the size of the container div */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
+ * size, in pixels, of the div into which OpenLayers
+ * has been loaded.
+ * Note - A clone() of this locally cached variable is
+ * returned, so as not to allow users to modify it.
+ */
+ getSize: function () {
+ var size = null;
+ if (this.size != null) {
+ size = this.size.clone();
+ }
+ return size;
+ },
+
+ /**
+ * APIMethod: updateSize
+ * This function should be called by any external code which dynamically
+ * changes the size of the map div (because mozilla wont let us catch
+ * the "onresize" for an element)
+ */
+ updateSize: function() {
+ // the div might have moved on the page, also
+ var newSize = this.getCurrentSize();
+ if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
+ this.events.clearMouseCache();
+ var oldSize = this.getSize();
+ if (oldSize == null) {
+ this.size = oldSize = newSize;
+ }
+ if (!newSize.equals(oldSize)) {
+
+ // store the new size
+ this.size = newSize;
+
+ //notify layers of mapresize
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ this.layers[i].onMapResize();
+ }
+
+ var center = this.getCachedCenter();
+
+ if (this.baseLayer != null && center != null) {
+ var zoom = this.getZoom();
+ this.zoom = null;
+ this.setCenter(center, zoom);
+ }
+
+ }
+ }
+ this.events.triggerEvent("updatesize");
+ },
+
+ /**
+ * Method: getCurrentSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
+ * of the map div
+ */
+ getCurrentSize: function() {
+
+ var size = new OpenLayers.Size(this.div.clientWidth,
+ this.div.clientHeight);
+
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = this.div.offsetWidth;
+ size.h = this.div.offsetHeight;
+ }
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = parseInt(this.div.style.width);
+ size.h = parseInt(this.div.style.height);
+ }
+ return size;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * center - {<OpenLayers.LonLat>} Default is this.getCenter()
+ * resolution - {float} Default is this.getResolution()
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds based on resolution, center, and
+ * current mapsize.
+ */
+ calculateBounds: function(center, resolution) {
+
+ var extent = null;
+
+ if (center == null) {
+ center = this.getCachedCenter();
+ }
+ if (resolution == null) {
+ resolution = this.getResolution();
+ }
+
+ if ((center != null) && (resolution != null)) {
+ var halfWDeg = (this.size.w * resolution) / 2;
+ var halfHDeg = (this.size.h * resolution) / 2;
+
+ extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ }
+
+ return extent;
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Zoom, Center, Pan Functions */
+ /* */
+ /* The following functions handle the validation, */
+ /* getting and setting of the Zoom Level and Center */
+ /* as well as the panning of the Map */
+ /* */
+ /********************************************************/
+ /**
+ * APIMethod: getCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCenter: function () {
+ var center = null;
+ var cachedCenter = this.getCachedCenter();
+ if (cachedCenter) {
+ center = cachedCenter.clone();
+ }
+ return center;
+ },
+
+ /**
+ * Method: getCachedCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCachedCenter: function() {
+ if (!this.center && this.size) {
+ this.center = this.getLonLatFromViewPortPx({
+ x: this.size.w / 2,
+ y: this.size.h / 2
+ });
+ }
+ return this.center;
+ },
+
+ /**
+ * APIMethod: getZoom
+ *
+ * Returns:
+ * {Integer}
+ */
+ getZoom: function () {
+ return this.zoom;
+ },
+
+ /**
+ * APIMethod: pan
+ * Allows user to pan by a value of screen pixels
+ *
+ * Parameters:
+ * dx - {Integer}
+ * dy - {Integer}
+ * options - {Object} Options to configure panning:
+ * - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
+ * - *dragging* {Boolean} Call setCenter with dragging true. Default is
+ * false.
+ */
+ pan: function(dx, dy, options) {
+ options = OpenLayers.Util.applyDefaults(options, {
+ animate: true,
+ dragging: false
+ });
+ if (options.dragging) {
+ if (dx != 0 || dy != 0) {
+ this.moveByPx(dx, dy);
+ }
+ } else {
+ // getCenter
+ var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());
+
+ // adjust
+ var newCenterPx = centerPx.add(dx, dy);
+
+ if (this.dragging || !newCenterPx.equals(centerPx)) {
+ var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
+ if (options.animate) {
+ this.panTo(newCenterLonLat);
+ } else {
+ this.moveTo(newCenterLonLat);
+ if(this.dragging) {
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }
+ }
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: panTo
+ * Allows user to pan to a new lonlat
+ * If the new lonlat is in the current extent the map will slide smoothly
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ panTo: function(lonlat) {
+ if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
+ var center = this.getCachedCenter();
+
+ // center will not change, don't do nothing
+ if (lonlat.equals(center)) {
+ return;
+ }
+
+ var from = this.getPixelFromLonLat(center);
+ var to = this.getPixelFromLonLat(lonlat);
+ var vector = { x: to.x - from.x, y: to.y - from.y };
+ var last = { x: 0, y: 0 };
+
+ this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
+ callbacks: {
+ eachStep: OpenLayers.Function.bind(function(px) {
+ var x = px.x - last.x,
+ y = px.y - last.y;
+ this.moveByPx(x, y);
+ last.x = Math.round(px.x);
+ last.y = Math.round(px.y);
+ }, this),
+ done: OpenLayers.Function.bind(function(px) {
+ this.moveTo(lonlat);
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }, this)
+ }
+ });
+ } else {
+ this.setCenter(lonlat);
+ }
+ },
+
+ /**
+ * APIMethod: setCenter
+ * Set the map center (and optionally, the zoom level).
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * zoom - {Integer} Optional zoom level.
+ * dragging - {Boolean} Specifies whether or not to trigger
+ * movestart/end events
+ * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom
+ * change events (needed on baseLayer change)
+ *
+ * TBD: reconsider forceZoomChange in 3.0
+ */
+ setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
+ if (this.panTween) {
+ this.panTween.stop();
+ }
+ if (this.zoomTween) {
+ this.zoomTween.stop();
+ }
+ this.moveTo(lonlat, zoom, {
+ 'dragging': dragging,
+ 'forceZoomChange': forceZoomChange
+ });
+ },
+
+ /**
+ * Method: moveByPx
+ * Drag the map by pixels.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ var hw = this.size.w / 2;
+ var hh = this.size.h / 2;
+ var x = hw + dx;
+ var y = hh + dy;
+ var wrapDateLine = this.baseLayer.wrapDateLine;
+ var xRestriction = 0;
+ var yRestriction = 0;
+ if (this.restrictedExtent) {
+ xRestriction = hw;
+ yRestriction = hh;
+ // wrapping the date line makes no sense for restricted extents
+ wrapDateLine = false;
+ }
+ dx = wrapDateLine ||
+ x <= this.maxPx.x - xRestriction &&
+ x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
+ dy = y <= this.maxPx.y - yRestriction &&
+ y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
+ if (dx || dy) {
+ if (!this.dragging) {
+ this.dragging = true;
+ this.events.triggerEvent("movestart");
+ }
+ this.center = null;
+ if (dx) {
+ this.layerContainerOriginPx.x -= dx;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ }
+ if (dy) {
+ this.layerContainerOriginPx.y -= dy;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ this.applyTransform();
+ var layer, i, len;
+ for (i=0, len=this.layers.length; i<len; ++i) {
+ layer = this.layers[i];
+ if (layer.visibility &&
+ (layer === this.baseLayer || layer.inRange)) {
+ layer.moveByPx(dx, dy);
+ layer.events.triggerEvent("move");
+ }
+ }
+ this.events.triggerEvent("move");
+ }
+ },
+
+ /**
+ * Method: adjustZoom
+ *
+ * Parameters:
+ * zoom - {Number} The zoom level to adjust
+ *
+ * Returns:
+ * {Integer} Adjusted zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent.
+ */
+ adjustZoom: function(zoom) {
+ if (this.baseLayer && this.baseLayer.wrapDateLine) {
+ var resolution, resolutions = this.baseLayer.resolutions,
+ maxResolution = this.getMaxExtent().getWidth() / this.size.w;
+ if (this.getResolutionForZoom(zoom) > maxResolution) {
+ if (this.fractionalZoom) {
+ zoom = this.getZoomForResolution(maxResolution);
+ } else {
+ for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
+ if (resolutions[i] <= maxResolution) {
+ zoom = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getMinZoom
+ * Returns the minimum zoom level for the current map view. If the base
+ * layer is configured with <wrapDateLine> set to true, this will be the
+ * first zoom level that shows no more than one world width in the current
+ * map viewport. Components that rely on this value (e.g. zoom sliders)
+ * should also listen to the map's "updatesize" event and call this method
+ * in the "updatesize" listener.
+ *
+ * Returns:
+ * {Number} Minimum zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
+ * configured with <fractionalZoom> set to true.
+ */
+ getMinZoom: function() {
+ return this.adjustZoom(0);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ * zoom - {Integer}
+ * options - {Object}
+ */
+ moveTo: function(lonlat, zoom, options) {
+ if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
+ lonlat = new OpenLayers.LonLat(lonlat);
+ }
+ if (!options) {
+ options = {};
+ }
+ if (zoom != null) {
+ zoom = parseFloat(zoom);
+ if (!this.fractionalZoom) {
+ zoom = Math.round(zoom);
+ }
+ }
+ var requestedZoom = zoom;
+ zoom = this.adjustZoom(zoom);
+ if (zoom !== requestedZoom) {
+ // zoom was adjusted, so keep old lonlat to avoid panning
+ lonlat = this.getCenter();
+ }
+ // dragging is false by default
+ var dragging = options.dragging || this.dragging;
+ // forceZoomChange is false by default
+ var forceZoomChange = options.forceZoomChange;
+
+ if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
+ lonlat = this.maxExtent.getCenterLonLat();
+ this.center = lonlat.clone();
+ }
+
+ if(this.restrictedExtent != null) {
+ // In 3.0, decide if we want to change interpretation of maxExtent.
+ if(lonlat == null) {
+ lonlat = this.center;
+ }
+ if(zoom == null) {
+ zoom = this.getZoom();
+ }
+ var resolution = this.getResolutionForZoom(zoom);
+ var extent = this.calculateBounds(lonlat, resolution);
+ if(!this.restrictedExtent.containsBounds(extent)) {
+ var maxCenter = this.restrictedExtent.getCenterLonLat();
+ if(extent.getWidth() > this.restrictedExtent.getWidth()) {
+ lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);
+ } else if(extent.left < this.restrictedExtent.left) {
+ lonlat = lonlat.add(this.restrictedExtent.left -
+ extent.left, 0);
+ } else if(extent.right > this.restrictedExtent.right) {
+ lonlat = lonlat.add(this.restrictedExtent.right -
+ extent.right, 0);
+ }
+ if(extent.getHeight() > this.restrictedExtent.getHeight()) {
+ lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);
+ } else if(extent.bottom < this.restrictedExtent.bottom) {
+ lonlat = lonlat.add(0, this.restrictedExtent.bottom -
+ extent.bottom);
+ }
+ else if(extent.top > this.restrictedExtent.top) {
+ lonlat = lonlat.add(0, this.restrictedExtent.top -
+ extent.top);
+ }
+ }
+ }
+
+ var zoomChanged = forceZoomChange || (
+ (this.isValidZoomLevel(zoom)) &&
+ (zoom != this.getZoom()) );
+
+ var centerChanged = (this.isValidLonLat(lonlat)) &&
+ (!lonlat.equals(this.center));
+
+ // if neither center nor zoom will change, no need to do anything
+ if (zoomChanged || centerChanged || dragging) {
+ dragging || this.events.triggerEvent("movestart", {
+ zoomChanged: zoomChanged
+ });
+
+ if (centerChanged) {
+ if (!zoomChanged && this.center) {
+ // if zoom hasnt changed, just slide layerContainer
+ // (must be done before setting this.center to new value)
+ this.centerLayerContainer(lonlat);
+ }
+ this.center = lonlat.clone();
+ }
+
+ var res = zoomChanged ?
+ this.getResolutionForZoom(zoom) : this.getResolution();
+ // (re)set the layerContainerDiv's location
+ if (zoomChanged || this.layerContainerOrigin == null) {
+ this.layerContainerOrigin = this.getCachedCenter();
+ this.layerContainerOriginPx.x = 0;
+ this.layerContainerOriginPx.y = 0;
+ this.applyTransform();
+ var maxExtent = this.getMaxExtent({restricted: true});
+ var maxExtentCenter = maxExtent.getCenterLonLat();
+ var lonDelta = this.center.lon - maxExtentCenter.lon;
+ var latDelta = maxExtentCenter.lat - this.center.lat;
+ var extentWidth = Math.round(maxExtent.getWidth() / res);
+ var extentHeight = Math.round(maxExtent.getHeight() / res);
+ this.minPx = {
+ x: (this.size.w - extentWidth) / 2 - lonDelta / res,
+ y: (this.size.h - extentHeight) / 2 - latDelta / res
+ };
+ this.maxPx = {
+ x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
+ y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
+ };
+ }
+
+ if (zoomChanged) {
+ this.zoom = zoom;
+ this.resolution = res;
+ }
+
+ var bounds = this.getExtent();
+
+ //send the move call to the baselayer and all the overlays
+
+ if(this.baseLayer.visibility) {
+ this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || this.baseLayer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+
+ bounds = this.baseLayer.getExtent();
+
+ for (var i=this.layers.length-1; i>=0; --i) {
+ var layer = this.layers[i];
+ if (layer !== this.baseLayer && !layer.isBaseLayer) {
+ var inRange = layer.calculateInRange();
+ if (layer.inRange != inRange) {
+ // the inRange property has changed. If the layer is
+ // no longer in range, we turn it off right away. If
+ // the layer is no longer out of range, the moveTo
+ // call below will turn on the layer.
+ layer.inRange = inRange;
+ if (!inRange) {
+ layer.display(false);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "visibility"
+ });
+ }
+ if (inRange && layer.visibility) {
+ layer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || layer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+ }
+ }
+
+ this.events.triggerEvent("move");
+ dragging || this.events.triggerEvent("moveend");
+
+ if (zoomChanged) {
+ //redraw popups
+ for (var i=0, len=this.popups.length; i<len; i++) {
+ this.popups[i].updatePosition();
+ }
+ this.events.triggerEvent("zoomend");
+ }
+ }
+ },
+
+ /**
+ * Method: centerLayerContainer
+ * This function takes care to recenter the layerContainerDiv.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ centerLayerContainer: function (lonlat) {
+ var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
+ var newPx = this.getViewPortPxFromLonLat(lonlat);
+
+ if ((originPx != null) && (newPx != null)) {
+ var oldLeft = this.layerContainerOriginPx.x;
+ var oldTop = this.layerContainerOriginPx.y;
+ var newLeft = Math.round(originPx.x - newPx.x);
+ var newTop = Math.round(originPx.y - newPx.y);
+ this.applyTransform(
+ (this.layerContainerOriginPx.x = newLeft),
+ (this.layerContainerOriginPx.y = newTop));
+ var dx = oldLeft - newLeft;
+ var dy = oldTop - newTop;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ },
+
+ /**
+ * Method: isValidZoomLevel
+ *
+ * Parameters:
+ * zoomLevel - {Integer}
+ *
+ * Returns:
+ * {Boolean} Whether or not the zoom level passed in is non-null and
+ * within the min/max range of zoom levels.
+ */
+ isValidZoomLevel: function(zoomLevel) {
+ return ( (zoomLevel != null) &&
+ (zoomLevel >= 0) &&
+ (zoomLevel < this.getNumZoomLevels()) );
+ },
+
+ /**
+ * Method: isValidLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Whether or not the lonlat passed in is non-null and within
+ * the maxExtent bounds
+ */
+ isValidLonLat: function(lonlat) {
+ var valid = false;
+ if (lonlat != null) {
+ var maxExtent = this.getMaxExtent();
+ var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
+ valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
+ }
+ return valid;
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Options */
+ /* */
+ /* Accessor functions to Layer Options parameters */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getProjection
+ * This method returns a string representing the projection. In
+ * the case of projection support, this will be the srsCode which
+ * is loaded -- otherwise it will simply be the string value that
+ * was passed to the projection at startup.
+ *
+ * FIXME: In 3.0, we will remove getProjectionObject, and instead
+ * return a Projection object from this function.
+ *
+ * Returns:
+ * {String} The Projection string from the base layer or null.
+ */
+ getProjection: function() {
+ var projection = this.getProjectionObject();
+ return projection ? projection.getCode() : null;
+ },
+
+ /**
+ * APIMethod: getProjectionObject
+ * Returns the projection obect from the baselayer.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} The Projection of the base layer.
+ */
+ getProjectionObject: function() {
+ var projection = null;
+ if (this.baseLayer != null) {
+ projection = this.baseLayer.projection;
+ }
+ return projection;
+ },
+
+ /**
+ * APIMethod: getMaxResolution
+ *
+ * Returns:
+ * {String} The Map's Maximum Resolution
+ */
+ getMaxResolution: function() {
+ var maxResolution = null;
+ if (this.baseLayer != null) {
+ maxResolution = this.baseLayer.maxResolution;
+ }
+ return maxResolution;
+ },
+
+ /**
+ * APIMethod: getMaxExtent
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} If true, returns restricted extent (if it is
+ * available.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The maxExtent property as set on the current
+ * baselayer, unless the 'restricted' option is set, in which case
+ * the 'restrictedExtent' option from the map is returned (if it
+ * is set).
+ */
+ getMaxExtent: function (options) {
+ var maxExtent = null;
+ if(options && options.restricted && this.restrictedExtent){
+ maxExtent = this.restrictedExtent;
+ } else if (this.baseLayer != null) {
+ maxExtent = this.baseLayer.maxExtent;
+ }
+ return maxExtent;
+ },
+
+ /**
+ * APIMethod: getNumZoomLevels
+ *
+ * Returns:
+ * {Integer} The total number of zoom levels that can be displayed by the
+ * current baseLayer.
+ */
+ getNumZoomLevels: function() {
+ var numZoomLevels = null;
+ if (this.baseLayer != null) {
+ numZoomLevels = this.baseLayer.numZoomLevels;
+ }
+ return numZoomLevels;
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API?, are all merely wrappers to the */
+ /* the same calls on whatever layer is set as */
+ /* the current base layer */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ * If no baselayer is set, returns null.
+ */
+ getExtent: function () {
+ var extent = null;
+ if (this.baseLayer != null) {
+ extent = this.baseLayer.getExtent();
+ }
+ return extent;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The current resolution of the map.
+ * If no baselayer is set, returns null.
+ */
+ getResolution: function () {
+ var resolution = null;
+ if (this.baseLayer != null) {
+ resolution = this.baseLayer.getResolution();
+ } else if(this.allOverlays === true && this.layers.length > 0) {
+ // while adding the 1st layer to the map in allOverlays mode,
+ // this.baseLayer is not set yet when we need the resolution
+ // for calculateInRange.
+ resolution = this.layers[0].getResolution();
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getUnits
+ *
+ * Returns:
+ * {Float} The current units of the map.
+ * If no baselayer is set, returns null.
+ */
+ getUnits: function () {
+ var units = null;
+ if (this.baseLayer != null) {
+ units = this.baseLayer.units;
+ }
+ return units;
+ },
+
+ /**
+ * APIMethod: getScale
+ *
+ * Returns:
+ * {Float} The current scale denominator of the map.
+ * If no baselayer is set, returns null.
+ */
+ getScale: function () {
+ var scale = null;
+ if (this.baseLayer != null) {
+ var res = this.getResolution();
+ var units = this.baseLayer.units;
+ scale = OpenLayers.Util.getScaleFromResolution(res, units);
+ }
+ return scale;
+ },
+
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified bounds.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForExtent: function (bounds, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForExtent(bounds, closest);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom. If no baselayer
+ * is set, returns null.
+ */
+ getResolutionForZoom: function(zoom) {
+ var resolution = null;
+ if(this.baseLayer) {
+ resolution = this.baseLayer.getResolutionForZoom(zoom);
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForResolution(resolution, closest);
+ }
+ return zoom;
+ },
+
+ /********************************************************/
+ /* */
+ /* Zooming Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API, are all merely wrappers to the */
+ /* the setCenter() function */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: zoomTo
+ * Zoom to a specific zoom level. Zooming will be animated unless the map
+ * is configured with {zoomMethod: null}. To zoom without animation, use
+ * <setCenter> without a lonlat argument.
+ *
+ * Parameters:
+ * zoom - {Integer}
+ */
+ zoomTo: function(zoom, xy) {
+ // non-API arguments:
+ // xy - {<OpenLayers.Pixel>} optional zoom origin
+
+ var map = this;
+ if (map.isValidZoomLevel(zoom)) {
+ if (map.baseLayer.wrapDateLine) {
+ zoom = map.adjustZoom(zoom);
+ }
+ if (map.zoomTween) {
+ var currentRes = map.getResolution(),
+ targetRes = map.getResolutionForZoom(zoom),
+ start = {scale: 1},
+ end = {scale: currentRes / targetRes};
+ if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
+ // update the end scale, and reuse the running zoomTween
+ map.zoomTween.finish = {
+ scale: map.zoomTween.finish.scale * end.scale
+ };
+ } else {
+ if (!xy) {
+ var size = map.getSize();
+ xy = {x: size.w / 2, y: size.h / 2};
+ }
+ map.zoomTween.start(start, end, map.zoomDuration, {
+ minFrameRate: 50, // don't spend much time zooming
+ callbacks: {
+ eachStep: function(data) {
+ var containerOrigin = map.layerContainerOriginPx,
+ scale = data.scale,
+ dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
+ dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
+ map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
+ },
+ done: function(data) {
+ map.applyTransform();
+ var resolution = map.getResolution() / data.scale,
+ zoom = map.getZoomForResolution(resolution, true)
+ map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
+ }
+ }
+ });
+ }
+ } else {
+ var center = xy ?
+ map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
+ null;
+ map.setCenter(center, zoom);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: zoomIn
+ *
+ */
+ zoomIn: function() {
+ this.zoomTo(this.getZoom() + 1);
+ },
+
+ /**
+ * APIMethod: zoomOut
+ *
+ */
+ zoomOut: function() {
+ this.zoomTo(this.getZoom() - 1);
+ },
+
+ /**
+ * APIMethod: zoomToExtent
+ * Zoom to the passed in bounds, recenter
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToExtent: function(bounds, closest) {
+ if (!(bounds instanceof OpenLayers.Bounds)) {
+ bounds = new OpenLayers.Bounds(bounds);
+ }
+ var center = bounds.getCenterLonLat();
+ if (this.baseLayer.wrapDateLine) {
+ var maxExtent = this.getMaxExtent();
+
+ //fix straddling bounds (in the case of a bbox that straddles the
+ // dateline, it's left and right boundaries will appear backwards.
+ // we fix this by allowing a right value that is greater than the
+ // max value at the dateline -- this allows us to pass a valid
+ // bounds to calculate zoom)
+ //
+ bounds = bounds.clone();
+ while (bounds.right < bounds.left) {
+ bounds.right += maxExtent.getWidth();
+ }
+ //if the bounds was straddling (see above), then the center point
+ // we got from it was wrong. So we take our new bounds and ask it
+ // for the center.
+ //
+ center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
+ }
+ this.setCenter(center, this.getZoomForExtent(bounds, closest));
+ },
+
+ /**
+ * APIMethod: zoomToMaxExtent
+ * Zoom to the full extent and recenter.
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} True to zoom to restricted extent if it is
+ * set. Defaults to true.
+ */
+ zoomToMaxExtent: function(options) {
+ //restricted is true by default
+ var restricted = (options) ? options.restricted : true;
+
+ var maxExtent = this.getMaxExtent({
+ 'restricted': restricted
+ });
+ this.zoomToExtent(maxExtent);
+ },
+
+ /**
+ * APIMethod: zoomToScale
+ * Zoom to a specified scale
+ *
+ * Parameters:
+ * scale - {float}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified scale. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToScale: function(scale, closest) {
+ var res = OpenLayers.Util.getResolutionFromScale(scale,
+ this.baseLayer.units);
+
+ var halfWDeg = (this.size.w * res) / 2;
+ var halfHDeg = (this.size.h * res) / 2;
+ var center = this.getCachedCenter();
+
+ var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ this.zoomToExtent(extent, closest);
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate between */
+ /* LonLat, LayerPx, and ViewPortPx */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: LonLat <-> ViewPortPx
+ //
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port <OpenLayers.Pixel>, translated into lon/lat
+ * by the current base layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if (this.baseLayer != null) {
+ lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into view port
+ * pixels by the current base layer.
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var px = null;
+ if (this.baseLayer != null) {
+ px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
+ }
+ return px;
+ },
+
+ /**
+ * Method: getZoomTargetCenter
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
+ * resolution - {Float} The resolution we want to get the center for
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The location of the map center after the
+ * transformation described by the origin xy and the target resolution.
+ */
+ getZoomTargetCenter: function (xy, resolution) {
+ var lonlat = null,
+ size = this.getSize(),
+ deltaX = size.w/2 - xy.x,
+ deltaY = xy.y - size.h/2,
+ zoomPoint = this.getLonLatFromPixel(xy);
+ if (zoomPoint) {
+ lonlat = new OpenLayers.LonLat(
+ zoomPoint.lon + deltaX * resolution,
+ zoomPoint.lat + deltaY * resolution
+ );
+ }
+ return lonlat;
+ },
+
+ //
+ // CONVENIENCE TRANSLATION FUNCTIONS FOR API
+ //
+
+ /**
+ * APIMethod: getLonLatFromPixel
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
+ * OpenLayers.Pixel, translated into lon/lat by the
+ * current base layer
+ */
+ getLonLatFromPixel: function (px) {
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getPixelFromLonLat
+ * Returns a pixel location given a map location. The map location is
+ * translated to an integer pixel location (in viewport pixel
+ * coordinates) by the current base layer.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} A map location.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
+ * <OpenLayers.LonLat> translated into view port pixels by the current
+ * base layer.
+ */
+ getPixelFromLonLat: function (lonlat) {
+ var px = this.getViewPortPxFromLonLat(lonlat);
+ px.x = Math.round(px.x);
+ px.y = Math.round(px.y);
+ return px;
+ },
+
+ /**
+ * Method: getGeodesicPixelSize
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
+ * not provided, the center pixel of the map viewport will be used.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The geodesic size of the pixel in kilometers.
+ */
+ getGeodesicPixelSize: function(px) {
+ var lonlat = px ? this.getLonLatFromPixel(px) : (
+ this.getCachedCenter() || new OpenLayers.LonLat(0, 0));
+ var res = this.getResolution();
+ var left = lonlat.add(-res / 2, 0);
+ var right = lonlat.add(res / 2, 0);
+ var bottom = lonlat.add(0, -res / 2);
+ var top = lonlat.add(0, res / 2);
+ var dest = new OpenLayers.Projection("EPSG:4326");
+ var source = this.getProjectionObject() || dest;
+ if(!source.equals(dest)) {
+ left.transform(source, dest);
+ right.transform(source, dest);
+ bottom.transform(source, dest);
+ top.transform(source, dest);
+ }
+
+ return new OpenLayers.Size(
+ OpenLayers.Util.distVincenty(left, right),
+ OpenLayers.Util.distVincenty(bottom, top)
+ );
+ },
+
+
+
+ //
+ // TRANSLATION: ViewPortPx <-> LayerPx
+ //
+
+ /**
+ * APIMethod: getViewPortPxFromLayerPx
+ *
+ * Parameters:
+ * layerPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel
+ * coordinates
+ */
+ getViewPortPxFromLayerPx:function(layerPx) {
+ var viewPortPx = null;
+ if (layerPx != null) {
+ var dX = this.layerContainerOriginPx.x;
+ var dY = this.layerContainerOriginPx.y;
+ viewPortPx = layerPx.add(dX, dY);
+ }
+ return viewPortPx;
+ },
+
+ /**
+ * APIMethod: getLayerPxFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel
+ * coordinates
+ */
+ getLayerPxFromViewPortPx:function(viewPortPx) {
+ var layerPx = null;
+ if (viewPortPx != null) {
+ var dX = -this.layerContainerOriginPx.x;
+ var dY = -this.layerContainerOriginPx.y;
+ layerPx = viewPortPx.add(dX, dY);
+ if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
+ layerPx = null;
+ }
+ }
+ return layerPx;
+ },
+
+ //
+ // TRANSLATION: LonLat <-> LayerPx
+ //
+
+ /**
+ * Method: getLonLatFromLayerPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getLonLatFromLayerPx: function (px) {
+ //adjust for displacement of layerContainerDiv
+ px = this.getViewPortPxFromLayerPx(px);
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getLayerPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} lonlat
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into layer pixels
+ * by the current base layer
+ */
+ getLayerPxFromLonLat: function (lonlat) {
+ //adjust for displacement of layerContainerDiv
+ var px = this.getPixelFromLonLat(lonlat);
+ return this.getLayerPxFromViewPortPx(px);
+ },
+
+ /**
+ * Method: applyTransform
+ * Applies the given transform to the <layerContainerDiv>. This method has
+ * a 2-stage fallback from translate3d/scale3d via translate/scale to plain
+ * style.left/style.top, in which case no scaling is supported.
+ *
+ * Parameters:
+ * x - {Number} x parameter for the translation. Defaults to the x value of
+ * the map's <layerContainerOriginPx>
+ * y - {Number} y parameter for the translation. Defaults to the y value of
+ * the map's <layerContainerOriginPx>
+ * scale - {Number} scale. Defaults to 1 if not provided.
+ */
+ applyTransform: function(x, y, scale) {
+ scale = scale || 1;
+ var origin = this.layerContainerOriginPx,
+ needTransform = scale !== 1;
+ x = x || origin.x;
+ y = y || origin.y;
+
+ var style = this.layerContainerDiv.style,
+ transform = this.applyTransform.transform,
+ template = this.applyTransform.template;
+
+ if (transform === undefined) {
+ transform = OpenLayers.Util.vendorPrefix.style('transform');
+ this.applyTransform.transform = transform;
+ if (transform) {
+ // Try translate3d, but only if the viewPortDiv has a transform
+ // defined in a stylesheet
+ var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
+ OpenLayers.Util.vendorPrefix.css('transform'));
+ if (!computedStyle || computedStyle !== 'none') {
+ template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
+ style[transform] = [template[0], '0,0', template[1]].join('');
+ }
+ // If no transform is defined in the stylesheet or translate3d
+ // does not stick, use translate and scale
+ if (!template || !~style[transform].indexOf(template[0])) {
+ template = ['translate(', ') ', 'scale(', ')'];
+ }
+ this.applyTransform.template = template;
+ }
+ }
+
+ // If we do 3d transforms, we always want to use them. If we do 2d
+ // transforms, we only use them when we need to.
+ if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
+ // Our 2d transforms are combined with style.left and style.top, so
+ // adjust x and y values and set the origin as left and top
+ if (needTransform === true && template[0] === 'translate(') {
+ x -= origin.x;
+ y -= origin.y;
+ style.left = origin.x + 'px';
+ style.top = origin.y + 'px';
+ }
+ style[transform] = [
+ template[0], x, 'px,', y, 'px', template[1],
+ template[2], scale, ',', scale, template[3]
+ ].join('');
+ } else {
+ style.left = x + 'px';
+ style.top = y + 'px';
+ // We previously might have had needTransform, so remove transform
+ if (transform !== null) {
+ style[transform] = '';
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Map"
+});
+
+/**
+ * Constant: TILE_WIDTH
+ * {Integer} 256 Default tile width (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_WIDTH = 256;
+/**
+ * Constant: TILE_HEIGHT
+ * {Integer} 256 Default tile height (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_HEIGHT = 256;
+/* ======================================================================
+ OpenLayers/Layer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer
+ */
+OpenLayers.Layer = OpenLayers.Class({
+
+ /**
+ * APIProperty: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * APIProperty: opacity
+ * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
+ * is 1.
+ */
+ opacity: 1,
+
+ /**
+ * APIProperty: alwaysInRange
+ * {Boolean} If a layer's display should not be scale-based, this should
+ * be set to true. This will cause the layer, as an overlay, to always
+ * be 'active', by always returning true from the calculateInRange()
+ * function.
+ *
+ * If not explicitly specified for a layer, its value will be
+ * determined on startup in initResolutions() based on whether or not
+ * any scale-specific properties have been set as options on the
+ * layer. If no scale-specific options have been set on the layer, we
+ * assume that it should always be in range.
+ *
+ * See #987 for more info.
+ */
+ alwaysInRange: null,
+
+ /**
+ * Constant: RESOLUTION_PROPERTIES
+ * {Array} The properties that are used for calculating resolutions
+ * information.
+ */
+ RESOLUTION_PROPERTIES: [
+ 'scales', 'resolutions',
+ 'maxScale', 'minScale',
+ 'maxResolution', 'minResolution',
+ 'numZoomLevels', 'maxZoomLevel'
+ ],
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types:
+ * loadstart - Triggered when layer loading starts. When using a Vector
+ * layer with a Fixed or BBOX strategy, the event object includes
+ * a *filter* property holding the OpenLayers.Filter used when
+ * calling read on the protocol.
+ * loadend - Triggered when layer loading ends. When using a Vector layer
+ * with a Fixed or BBOX strategy, the event object includes a
+ * *response* property holding an OpenLayers.Protocol.Response object.
+ * visibilitychanged - Triggered when the layer's visibility property is
+ * changed, e.g. by turning the layer on or off in the layer switcher.
+ * Note that the actual visibility of the layer can also change if it
+ * gets out of range (see <calculateInRange>). If you also want to catch
+ * these cases, register for the map's 'changelayer' event instead.
+ * move - Triggered when layer moves (triggered with every mousemove
+ * during a drag).
+ * moveend - Triggered when layer is done moving, object passed as
+ * argument has a zoomChanged boolean property which tells that the
+ * zoom has changed.
+ * added - Triggered after the layer is added to a map. Listeners will
+ * receive an object with a *map* property referencing the map and a
+ * *layer* property referencing the layer.
+ * removed - Triggered after the layer is removed from the map. Listeners
+ * will receive an object with a *map* property referencing the map and
+ * a *layer* property referencing the layer.
+ */
+ events: null,
+
+ /**
+ * APIProperty: map
+ * {<OpenLayers.Map>} This variable is set when the layer is added to
+ * the map, via the accessor function setMap().
+ */
+ map: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Whether or not the layer is a base layer. This should be set
+ * individually by all subclasses. Default is false
+ */
+ isBaseLayer: false,
+
+ /**
+ * Property: alpha
+ * {Boolean} The layer's images have an alpha channel. Default is false.
+ */
+ alpha: false,
+
+ /**
+ * APIProperty: displayInLayerSwitcher
+ * {Boolean} Display the layer's name in the layer switcher. Default is
+ * true.
+ */
+ displayInLayerSwitcher: true,
+
+ /**
+ * APIProperty: visibility
+ * {Boolean} The layer should be displayed in the map. Default is true.
+ */
+ visibility: true,
+
+ /**
+ * APIProperty: attribution
+ * {String} Attribution string, displayed when an
+ * <OpenLayers.Control.Attribution> has been added to the map.
+ */
+ attribution: null,
+
+ /**
+ * Property: inRange
+ * {Boolean} The current map resolution is within the layer's min/max
+ * range. This is set in <OpenLayers.Map.setCenter> whenever the zoom
+ * changes.
+ */
+ inRange: false,
+
+ /**
+ * Propery: imageSize
+ * {<OpenLayers.Size>} For layers with a gutter, the image is larger than
+ * the tile by twice the gutter in each dimension.
+ */
+ imageSize: null,
+
+ // OPTIONS
+
+ /**
+ * Property: options
+ * {Object} An optional object whose properties will be set on the layer.
+ * Any of the layer properties can be set as a property of the options
+ * object and sent to the constructor when the layer is created.
+ */
+ options: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: gutter
+ * {Integer} Determines the width (in pixels) of the gutter around image
+ * tiles to ignore. By setting this property to a non-zero value,
+ * images will be requested that are wider and taller than the tile
+ * size by a value of 2 x gutter. This allows artifacts of rendering
+ * at tile edges to be ignored. Set a gutter value that is equal to
+ * half the size of the widest symbol that needs to be displayed.
+ * Defaults to zero. Non-tiled layers always have zero gutter.
+ */
+ gutter: 0,
+
+ /**
+ * APIProperty: projection
+ * {<OpenLayers.Projection>} or {<String>} Specifies the projection of the layer.
+ * Can be set in the layer options. If not specified in the layer options,
+ * it is set to the default projection specified in the map,
+ * when the layer is added to the map.
+ * Projection along with default maxExtent and resolutions
+ * are set automatically with commercial baselayers in EPSG:3857,
+ * such as Google, Bing and OpenStreetMap, and do not need to be specified.
+ * Otherwise, if specifying projection, also set maxExtent,
+ * maxResolution or resolutions as appropriate.
+ * When using vector layers with strategies, layer projection should be set
+ * to the projection of the source data if that is different from the map default.
+ *
+ * Can be either a string or an <OpenLayers.Projection> object;
+ * if a string is passed, will be converted to an object when
+ * the layer is added to the map.
+ *
+ */
+ projection: null,
+
+ /**
+ * APIProperty: units
+ * {String} The layer map units. Defaults to null. Possible values
+ * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
+ * Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units.
+ */
+ units: null,
+
+ /**
+ * APIProperty: scales
+ * {Array} An array of map scales in descending order. The values in the
+ * array correspond to the map scale denominator. Note that these
+ * values only make sense if the display (monitor) resolution of the
+ * client is correctly guessed by whomever is configuring the
+ * application. In addition, the units property must also be set.
+ * Use <resolutions> instead wherever possible.
+ */
+ scales: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array} A list of map resolutions (map units per pixel) in descending
+ * order. If this is not set in the layer constructor, it will be set
+ * based on other resolution related properties (maxExtent,
+ * maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the layer. Defaults to null.
+ *
+ * The center of these bounds will not stray outside
+ * of the viewport extent during panning. In addition, if
+ * <displayOutsideMaxExtent> is set to false, data will not be
+ * requested that falls completely outside of these bounds.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the layer. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Default max is 360 deg / 256 px, which corresponds to
+ * zoom level 0 on gmaps. Specify a different value in the layer
+ * options if you are not using the default <OpenLayers.Map.tileSize>
+ * and displaying the whole world.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer}
+ */
+ numZoomLevels: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: displayOutsideMaxExtent
+ * {Boolean} Request map tiles that are completely outside of the max
+ * extent for this layer. Defaults to false.
+ */
+ displayOutsideMaxExtent: false,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Wraps the world at the international dateline, so the map can
+ * be panned infinitely in longitudinal direction. Only use this on the
+ * base layer, and only if the layer's maxExtent equals the world bounds.
+ * #487 for more info.
+ */
+ wrapDateLine: false,
+
+ /**
+ * Property: metadata
+ * {Object} This object can be used to store additional information on a
+ * layer object.
+ */
+ metadata: null,
+
+ /**
+ * Constructor: OpenLayers.Layer
+ *
+ * Parameters:
+ * name - {String} The layer name
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+
+ this.metadata = {};
+
+ options = OpenLayers.Util.extend({}, options);
+ // make sure we respect alwaysInRange if set on the prototype
+ if (this.alwaysInRange != null) {
+ options.alwaysInRange = this.alwaysInRange;
+ }
+ this.addOptions(options);
+
+ this.name = name;
+
+ if (this.id == null) {
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.style.width = "100%";
+ this.div.style.height = "100%";
+ this.div.dir = "ltr";
+
+ this.events = new OpenLayers.Events(this, this.div);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Destroy is a destructor: this is to alleviate cyclic references which
+ * the Javascript garbage cleaner can not take care of on its own.
+ *
+ * Parameters:
+ * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
+ * been destroyed. Default is true.
+ */
+ destroy: function(setNewBaseLayer) {
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+ if (this.map != null) {
+ this.map.removeLayer(this, setNewBaseLayer);
+ }
+ this.projection = null;
+ this.map = null;
+ this.name = null;
+ this.div = null;
+ this.options = null;
+
+ if (this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ }
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: clone
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Layer>} The layer to be cloned
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer(this.name, this.getOptions());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ // a cloned layer should never have its map property set
+ // because it has not been added to a map yet.
+ obj.map = null;
+
+ return obj;
+ },
+
+ /**
+ * Method: getOptions
+ * Extracts an object from the layer with the properties that were set as
+ * options, but updates them with the values currently set on the
+ * instance.
+ *
+ * Returns:
+ * {Object} the <options> of the layer, representing the current state.
+ */
+ getOptions: function() {
+ var options = {};
+ for(var o in this.options) {
+ options[o] = this[o];
+ }
+ return options;
+ },
+
+ /**
+ * APIMethod: setName
+ * Sets the new layer name for this layer. Can trigger a changelayer event
+ * on the map.
+ *
+ * Parameters:
+ * newName - {String} The new name.
+ */
+ setName: function(newName) {
+ if (newName != this.name) {
+ this.name = newName;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "name"
+ });
+ }
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+
+ if (this.options == null) {
+ this.options = {};
+ }
+
+ if (newOptions) {
+ // make sure this.projection references a projection object
+ if(typeof newOptions.projection == "string") {
+ newOptions.projection = new OpenLayers.Projection(newOptions.projection);
+ }
+ if (newOptions.projection) {
+ // get maxResolution, units and maxExtent from projection defaults if
+ // they are not defined already
+ OpenLayers.Util.applyDefaults(newOptions,
+ OpenLayers.Projection.defaults[newOptions.projection.getCode()]);
+ }
+ // allow array for extents
+ if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) {
+ newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent);
+ }
+ if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) {
+ newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent);
+ }
+ }
+
+ // update our copy for clone
+ OpenLayers.Util.extend(this.options, newOptions);
+
+ // add new options to this
+ OpenLayers.Util.extend(this, newOptions);
+
+ // get the units from the projection, if we have a projection
+ // and it it has units
+ if(this.projection && this.projection.getUnits()) {
+ this.units = this.projection.getUnits();
+ }
+
+ // re-initialize resolutions if necessary, i.e. if any of the
+ // properties of the "properties" array defined below is set
+ // in the new options
+ if(this.map) {
+ // store current resolution so we can try to restore it later
+ var resolution = this.map.getResolution();
+ var properties = this.RESOLUTION_PROPERTIES.concat(
+ ["projection", "units", "minExtent", "maxExtent"]
+ );
+ for(var o in newOptions) {
+ if(newOptions.hasOwnProperty(o) &&
+ OpenLayers.Util.indexOf(properties, o) >= 0) {
+
+ this.initResolutions();
+ if (reinitialize && this.map.baseLayer === this) {
+ // update map position, and restore previous resolution
+ this.map.setCenter(this.map.getCenter(),
+ this.map.getZoomForResolution(resolution),
+ false, true
+ );
+ // trigger a changebaselayer event to make sure that
+ // all controls (especially
+ // OpenLayers.Control.PanZoomBar) get notified of the
+ // new options
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: onMapResize
+ * This function can be implemented by subclasses
+ */
+ onMapResize: function() {
+ //this function can be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function() {
+ var redrawn = false;
+ if (this.map) {
+
+ // min/max Range may have changed
+ this.inRange = this.calculateInRange();
+
+ // map's center might not yet be set
+ var extent = this.getExtent();
+
+ if (extent && this.inRange && this.visibility) {
+ var zoomChanged = true;
+ this.moveTo(extent, zoomChanged, false);
+ this.events.triggerEvent("moveend",
+ {"zoomChanged": zoomChanged});
+ redrawn = true;
+ }
+ }
+ return redrawn;
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ var display = this.visibility;
+ if (!this.isBaseLayer) {
+ display = display && this.inRange;
+ }
+ this.display(display);
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Here we take care to bring over any of the necessary default
+ * properties from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ if (this.map == null) {
+
+ this.map = map;
+
+ // grab some essential layer data from the map if it hasn't already
+ // been set
+ this.maxExtent = this.maxExtent || this.map.maxExtent;
+ this.minExtent = this.minExtent || this.map.minExtent;
+
+ this.projection = this.projection || this.map.projection;
+ if (typeof this.projection == "string") {
+ this.projection = new OpenLayers.Projection(this.projection);
+ }
+
+ // Check the projection to see if we can get units -- if not, refer
+ // to properties.
+ this.units = this.projection.getUnits() ||
+ this.units || this.map.units;
+
+ this.initResolutions();
+
+ if (!this.isBaseLayer) {
+ this.inRange = this.calculateInRange();
+ var show = ((this.visibility) && (this.inRange));
+ this.div.style.display = show ? "" : "none";
+ }
+
+ // deal with gutters
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. To be overridden by subclasses.
+ */
+ afterAdd: function() {
+ },
+
+ /**
+ * APIMethod: removeMap
+ * Just as setMap() allows each layer the possibility to take a
+ * personalized action on being added to the map, removeMap() allows
+ * each layer to take a personalized action on being removed from it.
+ * For now, this will be mostly unused, except for the EventPane layer,
+ * which needs this hook so that it can remove the special invisible
+ * pane.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * APIMethod: getImageSize
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
+ * by subclasses that have to deal with different tile sizes at the
+ * layer extent edges (e.g. Zoomify)
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size that the image should be, taking into
+ * account gutters.
+ */
+ getImageSize: function(bounds) {
+ return (this.imageSize || this.tileSize);
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Set the tile size based on the map size. This also sets layer.imageSize
+ * or use by Tile.Image.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ var tileSize = (size) ? size :
+ ((this.tileSize) ? this.tileSize :
+ this.map.getTileSize());
+ this.tileSize = tileSize;
+ if(this.gutter) {
+ // layers with gutters need non-null tile sizes
+ //if(tileSize == null) {
+ // OpenLayers.console.error("Error in layer.setMap() for " +
+ // this.name + ": layers with " +
+ // "gutters need non-null tile sizes");
+ //}
+ this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter),
+ tileSize.h + (2*this.gutter));
+ }
+ },
+
+ /**
+ * APIMethod: getVisibility
+ *
+ * Returns:
+ * {Boolean} The layer should be displayed (if in range).
+ */
+ getVisibility: function() {
+ return this.visibility;
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visibility - {Boolean} Whether or not to display the layer (if in range)
+ */
+ setVisibility: function(visibility) {
+ if (visibility != this.visibility) {
+ this.visibility = visibility;
+ this.display(visibility);
+ this.redraw();
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "visibility"
+ });
+ }
+ this.events.triggerEvent("visibilitychanged");
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer. This is designed to be used internally, and
+ * is not generally the way to enable or disable the layer. For that,
+ * use the setVisibility function instead..
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ if (display != (this.div.style.display != "none")) {
+ this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
+ }
+ },
+
+ /**
+ * APIMethod: calculateInRange
+ *
+ * Returns:
+ * {Boolean} The layer is displayable at the current map's current
+ * resolution. Note that if 'alwaysInRange' is true for the layer,
+ * this function will always return true.
+ */
+ calculateInRange: function() {
+ var inRange = false;
+
+ if (this.alwaysInRange) {
+ inRange = true;
+ } else {
+ if (this.map) {
+ var resolution = this.map.getResolution();
+ inRange = ( (resolution >= this.minResolution) &&
+ (resolution <= this.maxResolution) );
+ }
+ }
+ return inRange;
+ },
+
+ /**
+ * APIMethod: setIsBaseLayer
+ *
+ * Parameters:
+ * isBaseLayer - {Boolean}
+ */
+ setIsBaseLayer: function(isBaseLayer) {
+ if (isBaseLayer != this.isBaseLayer) {
+ this.isBaseLayer = isBaseLayer;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: initResolutions
+ * This method's responsibility is to set up the 'resolutions' array
+ * for the layer -- this array is what the layer will use to interface
+ * between the zoom levels of the map and the resolution display
+ * of the layer.
+ *
+ * The user has several options that determine how the array is set up.
+ *
+ * For a detailed explanation, see the following wiki from the
+ * openlayers.org homepage:
+ * http://trac.openlayers.org/wiki/SettingZoomLevels
+ */
+ initResolutions: function() {
+
+ // ok we want resolutions, here's our strategy:
+ //
+ // 1. if resolutions are defined in the layer config, use them
+ // 2. else, if scales are defined in the layer config then derive
+ // resolutions from these scales
+ // 3. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // layer config
+ // 4. if we still don't have resolutions, and if resolutions
+ // are defined in the same, use them
+ // 5. else, if scales are defined in the map then derive
+ // resolutions from these scales
+ // 6. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // map
+ // 7. hope for the best!
+
+ var i, len, p;
+ var props = {}, alwaysInRange = true;
+
+ // get resolution data from layer config
+ // (we also set alwaysInRange in the layer as appropriate)
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p];
+ if(alwaysInRange && this.options[p]) {
+ alwaysInRange = false;
+ }
+ }
+ if(this.options.alwaysInRange == null) {
+ this.alwaysInRange = alwaysInRange;
+ }
+
+ // if we don't have resolutions then attempt to derive them from scales
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+
+ // if we still don't have resolutions then attempt to calculate them
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+
+ // if we couldn't calculate resolutions then we look at we have
+ // in the map
+ if(props.resolutions == null) {
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p] != null ?
+ this.options[p] : this.map[p];
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+ }
+
+ // ok, we new need to set properties in the instance
+
+ // get maxResolution from the config if it's defined there
+ var maxResolution;
+ if(this.options.maxResolution &&
+ this.options.maxResolution !== "auto") {
+ maxResolution = this.options.maxResolution;
+ }
+ if(this.options.minScale) {
+ maxResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.minScale, this.units);
+ }
+
+ // get minResolution from the config if it's defined there
+ var minResolution;
+ if(this.options.minResolution &&
+ this.options.minResolution !== "auto") {
+ minResolution = this.options.minResolution;
+ }
+ if(this.options.maxScale) {
+ minResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.maxScale, this.units);
+ }
+
+ if(props.resolutions) {
+
+ //sort resolutions array descendingly
+ props.resolutions.sort(function(a, b) {
+ return (b - a);
+ });
+
+ // if we still don't have a maxResolution get it from the
+ // resolutions array
+ if(!maxResolution) {
+ maxResolution = props.resolutions[0];
+ }
+
+ // if we still don't have a minResolution get it from the
+ // resolutions array
+ if(!minResolution) {
+ var lastIdx = props.resolutions.length - 1;
+ minResolution = props.resolutions[lastIdx];
+ }
+ }
+
+ this.resolutions = props.resolutions;
+ if(this.resolutions) {
+ len = this.resolutions.length;
+ this.scales = new Array(len);
+ for(i=0; i<len; i++) {
+ this.scales[i] = OpenLayers.Util.getScaleFromResolution(
+ this.resolutions[i], this.units);
+ }
+ this.numZoomLevels = len;
+ }
+ this.minResolution = minResolution;
+ if(minResolution) {
+ this.maxScale = OpenLayers.Util.getScaleFromResolution(
+ minResolution, this.units);
+ }
+ this.maxResolution = maxResolution;
+ if(maxResolution) {
+ this.minScale = OpenLayers.Util.getScaleFromResolution(
+ maxResolution, this.units);
+ }
+ },
+
+ /**
+ * Method: resolutionsFromScales
+ * Derive resolutions from scales.
+ *
+ * Parameters:
+ * scales - {Array(Number)} Scales
+ *
+ * Returns
+ * {Array(Number)} Resolutions
+ */
+ resolutionsFromScales: function(scales) {
+ if(scales == null) {
+ return;
+ }
+ var resolutions, i, len;
+ len = scales.length;
+ resolutions = new Array(len);
+ for(i=0; i<len; i++) {
+ resolutions[i] = OpenLayers.Util.getResolutionFromScale(
+ scales[i], this.units);
+ }
+ return resolutions;
+ },
+
+ /**
+ * Method: calculateResolutions
+ * Calculate resolutions based on the provided properties.
+ *
+ * Parameters:
+ * props - {Object} Properties
+ *
+ * Returns:
+ * {Array({Number})} Array of resolutions.
+ */
+ calculateResolutions: function(props) {
+
+ var viewSize, wRes, hRes;
+
+ // determine maxResolution
+ var maxResolution = props.maxResolution;
+ if(props.minScale != null) {
+ maxResolution =
+ OpenLayers.Util.getResolutionFromScale(props.minScale,
+ this.units);
+ } else if(maxResolution == "auto" && this.maxExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.maxExtent.getWidth() / viewSize.w;
+ hRes = this.maxExtent.getHeight() / viewSize.h;
+ maxResolution = Math.max(wRes, hRes);
+ }
+
+ // determine minResolution
+ var minResolution = props.minResolution;
+ if(props.maxScale != null) {
+ minResolution =
+ OpenLayers.Util.getResolutionFromScale(props.maxScale,
+ this.units);
+ } else if(props.minResolution == "auto" && this.minExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.minExtent.getWidth() / viewSize.w;
+ hRes = this.minExtent.getHeight()/ viewSize.h;
+ minResolution = Math.max(wRes, hRes);
+ }
+
+ if(typeof maxResolution !== "number" &&
+ typeof minResolution !== "number" &&
+ this.maxExtent != null) {
+ // maxResolution for default grid sets assumes that at zoom
+ // level zero, the whole world fits on one tile.
+ var tileSize = this.map.getTileSize();
+ maxResolution = Math.max(
+ this.maxExtent.getWidth() / tileSize.w,
+ this.maxExtent.getHeight() / tileSize.h
+ );
+ }
+
+ // determine numZoomLevels
+ var maxZoomLevel = props.maxZoomLevel;
+ var numZoomLevels = props.numZoomLevels;
+ if(typeof minResolution === "number" &&
+ typeof maxResolution === "number" && numZoomLevels === undefined) {
+ var ratio = maxResolution / minResolution;
+ numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
+ } else if(numZoomLevels === undefined && maxZoomLevel != null) {
+ numZoomLevels = maxZoomLevel + 1;
+ }
+
+ // are we able to calculate resolutions?
+ if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
+ (typeof maxResolution !== "number" &&
+ typeof minResolution !== "number")) {
+ return;
+ }
+
+ // now we have numZoomLevels and at least one of maxResolution
+ // or minResolution, we can populate the resolutions array
+
+ var resolutions = new Array(numZoomLevels);
+ var base = 2;
+ if(typeof minResolution == "number" &&
+ typeof maxResolution == "number") {
+ // if maxResolution and minResolution are set, we calculate
+ // the base for exponential scaling that starts at
+ // maxResolution and ends at minResolution in numZoomLevels
+ // steps.
+ base = Math.pow(
+ (maxResolution / minResolution),
+ (1 / (numZoomLevels - 1))
+ );
+ }
+
+ var i;
+ if(typeof maxResolution === "number") {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[i] = maxResolution / Math.pow(base, i);
+ }
+ } else {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[numZoomLevels - 1 - i] =
+ minResolution * Math.pow(base, i);
+ }
+ }
+
+ return resolutions;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The currently selected resolution of the map, taken from the
+ * resolutions array, indexed by current zoom level.
+ */
+ getResolution: function() {
+ var zoom = this.map.getZoom();
+ return this.getResolutionForZoom(zoom);
+ },
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function() {
+ // just use stock map calculateBounds function -- passing no arguments
+ // means it will user map's current center & resolution
+ //
+ return this.map.calculateBounds();
+ },
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * for the passed-in extent. We do this by calculating the ideal
+ * resolution for the given extent (based on the map size) and then
+ * calling getZoomForResolution(), passing along the 'closest'
+ * parameter.
+ */
+ getZoomForExtent: function(extent, closest) {
+ var viewSize = this.map.getSize();
+ var idealResolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+
+ return this.getZoomForResolution(idealResolution, closest);
+ },
+
+ /**
+ * Method: getDataExtent
+ * Calculates the max extent which includes all of the data for the layer.
+ * This function is to be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ //to be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom.
+ */
+ getResolutionForZoom: function(zoom) {
+ zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
+ var resolution;
+ if(this.map.fractionalZoom) {
+ var low = Math.floor(zoom);
+ var high = Math.ceil(zoom);
+ resolution = this.resolutions[low] -
+ ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
+ } else {
+ resolution = this.resolutions[Math.round(zoom)];
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * that corresponds to the best fit resolution given the passed in
+ * value and the 'closest' specification.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom, i, len;
+ if(this.map.fractionalZoom) {
+ var lowZoom = 0;
+ var highZoom = this.resolutions.length - 1;
+ var highRes = this.resolutions[lowZoom];
+ var lowRes = this.resolutions[highZoom];
+ var res;
+ for(i=0, len=this.resolutions.length; i<len; ++i) {
+ res = this.resolutions[i];
+ if(res >= resolution) {
+ highRes = res;
+ lowZoom = i;
+ }
+ if(res <= resolution) {
+ lowRes = res;
+ highZoom = i;
+ break;
+ }
+ }
+ var dRes = highRes - lowRes;
+ if(dRes > 0) {
+ zoom = lowZoom + ((highRes - resolution) / dRes);
+ } else {
+ zoom = lowZoom;
+ }
+ } else {
+ var diff;
+ var minDiff = Number.POSITIVE_INFINITY;
+ for(i=0, len=this.resolutions.length; i<len; i++) {
+ if (closest) {
+ diff = Math.abs(this.resolutions[i] - resolution);
+ if (diff > minDiff) {
+ break;
+ }
+ minDiff = diff;
+ } else {
+ if (this.resolutions[i] < resolution) {
+ break;
+ }
+ }
+ }
+ zoom = Math.max(0, i-1);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in
+ * view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ var map = this.map;
+ if (viewPortPx != null && map.minPx) {
+ var res = map.getResolution();
+ var maxExtent = map.getMaxExtent({restricted: true});
+ var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
+ var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
+ lonlat = new OpenLayers.LonLat(lon, lat);
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ * Returns a pixel location given a map location. This method will return
+ * fractional pixel values.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
+ * an object with a 'lon'
+ * and 'lat' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in
+ * lonlat translated into view port pixels.
+ */
+ getViewPortPxFromLonLat: function (lonlat, resolution) {
+ var px = null;
+ if (lonlat != null) {
+ resolution = resolution || this.map.getResolution();
+ var extent = this.map.calculateBounds(null, resolution);
+ px = new OpenLayers.Pixel(
+ (1/resolution * (lonlat.lon - extent.left)),
+ (1/resolution * (extent.top - lonlat.lat))
+ );
+ }
+ return px;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity != this.opacity) {
+ this.opacity = opacity;
+ var childNodes = this.div.childNodes;
+ for(var i = 0, len = childNodes.length; i < len; ++i) {
+ var element = childNodes[i].firstChild || childNodes[i];
+ var lastChild = childNodes[i].lastChild;
+ //TODO de-uglify this
+ if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") {
+ element = lastChild.parentNode;
+ }
+ OpenLayers.Util.modifyDOMElement(element, null, null, null,
+ null, null, null, opacity);
+ }
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ }
+ },
+
+ /**
+ * Method: getZIndex
+ *
+ * Returns:
+ * {Integer} the z-index of this layer
+ */
+ getZIndex: function () {
+ return this.div.style.zIndex;
+ },
+
+ /**
+ * Method: setZIndex
+ *
+ * Parameters:
+ * zIndex - {Integer}
+ */
+ setZIndex: function (zIndex) {
+ this.div.style.zIndex = zIndex;
+ },
+
+ /**
+ * Method: adjustBounds
+ * This function will take a bounds, and if wrapDateLine option is set
+ * on the layer, it will return a bounds which is wrapped around the
+ * world. We do not wrap for bounds which *cross* the
+ * maxExtent.left/right, only bounds which are entirely to the left
+ * or entirely to the right.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ adjustBounds: function (bounds) {
+
+ if (this.gutter) {
+ // Adjust the extent of a bounds in map units by the
+ // layer's gutter in pixels.
+ var mapGutter = this.gutter * this.map.getResolution();
+ bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
+ bounds.bottom - mapGutter,
+ bounds.right + mapGutter,
+ bounds.top + mapGutter);
+ }
+
+ if (this.wrapDateLine) {
+ // wrap around the date line, within the limits of rounding error
+ var wrappingOptions = {
+ 'rightTolerance':this.getResolution(),
+ 'leftTolerance':this.getResolution()
+ };
+ bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
+
+ }
+ return bounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer"
+});
+/* ======================================================================
+ OpenLayers/Layer/SphericalMercator.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.SphericalMercator
+ * A mixin for layers that wraps up the pieces neccesary to have a coordinate
+ * conversion for working with commercial APIs which use a spherical
+ * mercator projection. Using this layer as a base layer, additional
+ * layers can be used as overlays if they are in the same projection.
+ *
+ * A layer is given properties of this object by setting the sphericalMercator
+ * property to true.
+ *
+ * More projection information:
+ * - http://spatialreference.org/ref/user/google-projection/
+ *
+ * Proj4 Text:
+ * +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
+ * +k=1.0 +units=m +nadgrids=@null +no_defs
+ *
+ * WKT:
+ * 900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
+ * DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]],
+ * PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295],
+ * AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
+ * PROJECTION["Mercator_1SP_Google"],
+ * PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0],
+ * PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0],
+ * PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
+ * AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]
+ */
+OpenLayers.Layer.SphericalMercator = {
+
+ /**
+ * Method: getExtent
+ * Get the map's extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The map extent.
+ */
+ getExtent: function() {
+ var extent = null;
+ if (this.sphericalMercator) {
+ extent = this.map.calculateBounds();
+ } else {
+ extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);
+ }
+ return extent;
+ },
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port OpenLayers.Pixel, translated into lon/lat by map lib
+ * If the map lib is not loaded or not centered, returns null
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments);
+ },
+
+ /**
+ * Method: getViewPortPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * OpenLayers.LonLat, translated into view port pixels by map lib
+ * If map lib is not loaded or not centered, returns null
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments);
+ },
+
+ /**
+ * Method: initMercatorParameters
+ * Set up the mercator parameters on the layer: resolutions,
+ * projection, units.
+ */
+ initMercatorParameters: function() {
+ // set up properties for Mercator - assume EPSG:900913
+ this.RESOLUTIONS = [];
+ var maxResolution = 156543.03390625;
+ for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
+ this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
+ }
+ this.units = "m";
+ this.projection = this.projection || "EPSG:900913";
+ },
+
+ /**
+ * APIMethod: forwardMercator
+ * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator.
+ *
+ * Parameters:
+ * lon - {float}
+ * lat - {float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The coordinates transformed to Mercator.
+ */
+ forwardMercator: (function() {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ return function(lon, lat) {
+ var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm);
+ return new OpenLayers.LonLat(point.x, point.y);
+ };
+ })(),
+
+ /**
+ * APIMethod: inverseMercator
+ * Given a x,y in Spherical Mercator, return a point in EPSG:4326.
+ *
+ * Parameters:
+ * x - {float} A map x in Spherical Mercator.
+ * y - {float} A map y in Spherical Mercator.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326.
+ */
+ inverseMercator: (function() {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ return function(x, y) {
+ var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg);
+ return new OpenLayers.LonLat(point.x, point.y);
+ };
+ })()
+
+};
+/* ======================================================================
+ OpenLayers/Layer/EventPane.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.EventPane
+ * Base class for 3rd party layers, providing a DOM element which isolates
+ * the 3rd-party layer from mouse events.
+ * Only used by Google layers.
+ *
+ * Automatically instantiated by the Google constructor, and not usually instantiated directly.
+ *
+ * Create a new event pane layer with the
+ * <OpenLayers.Layer.EventPane> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: smoothDragPan
+ * {Boolean} smoothDragPan determines whether non-public/internal API
+ * methods are used for better performance while dragging EventPane
+ * layers. When not in sphericalMercator mode, the smoother dragging
+ * doesn't actually move north/south directly with the number of
+ * pixels moved, resulting in a slight offset when you drag your mouse
+ * north south with this option on. If this visual disparity bothers
+ * you, you should turn this option off, or use spherical mercator.
+ * Default is on.
+ */
+ smoothDragPan: true,
+
+ /**
+ * Property: isBaseLayer
+ * {Boolean} EventPaned layers are always base layers, by necessity.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} EventPaned layers are fixed by default.
+ */
+ isFixed: true,
+
+ /**
+ * Property: pane
+ * {DOMElement} A reference to the element that controls the events.
+ */
+ pane: null,
+
+
+ /**
+ * Property: mapObject
+ * {Object} This is the object which will be used to load the 3rd party library
+ * in the case of the google layer, this will be of type GMap,
+ * in the case of the ve layer, this will be of type VEMap
+ */
+ mapObject: null,
+
+
+ /**
+ * Constructor: OpenLayers.Layer.EventPane
+ * Create a new event pane layer
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ if (this.pane == null) {
+ this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct this layer.
+ */
+ destroy: function() {
+ this.mapObject = null;
+ this.pane = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+ this.pane.style.display = this.div.style.display;
+ this.pane.style.width="100%";
+ this.pane.style.height="100%";
+ if (OpenLayers.BROWSER_NAME == "msie") {
+ this.pane.style.background =
+ "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")";
+ }
+
+ if (this.isFixed) {
+ this.map.viewPortDiv.appendChild(this.pane);
+ } else {
+ this.map.layerContainerDiv.appendChild(this.pane);
+ }
+
+ // once our layer has been added to the map, we can load it
+ this.loadMapObject();
+
+ // if map didn't load, display warning
+ if (this.mapObject == null) {
+ this.loadWarningMessage();
+ }
+ },
+
+ /**
+ * APIMethod: removeMap
+ * On being removed from the map, we'll like to remove the invisible 'pane'
+ * div that we added to it on creation.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this.pane && this.pane.parentNode) {
+ this.pane.parentNode.removeChild(this.pane);
+ }
+ OpenLayers.Layer.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: loadWarningMessage
+ * If we can't load the map lib, then display an error message to the
+ * user and tell them where to go for help.
+ *
+ * This function sets up the layout for the warning message. Each 3rd
+ * party layer must implement its own getWarningHTML() function to
+ * provide the actual warning message.
+ */
+ loadWarningMessage:function() {
+
+ this.div.style.backgroundColor = "darkblue";
+
+ var viewSize = this.map.getSize();
+
+ var msgW = Math.min(viewSize.w, 300);
+ var msgH = Math.min(viewSize.h, 200);
+ var size = new OpenLayers.Size(msgW, msgH);
+
+ var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2);
+
+ var topLeft = centerPx.add(-size.w/2, -size.h/2);
+
+ var div = OpenLayers.Util.createDiv(this.name + "_warning",
+ topLeft,
+ size,
+ null,
+ null,
+ null,
+ "auto");
+
+ div.style.padding = "7px";
+ div.style.backgroundColor = "yellow";
+
+ div.innerHTML = this.getWarningHTML();
+ this.div.appendChild(div);
+ },
+
+ /**
+ * Method: getWarningHTML
+ * To be implemented by subclasses.
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ //should be implemented by subclasses
+ return "";
+ },
+
+ /**
+ * Method: display
+ * Set the display on the pane
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ this.pane.style.display = this.div.style.display;
+ },
+
+ /**
+ * Method: setZIndex
+ * Set the z-index order for the pane.
+ *
+ * Parameters:
+ * zIndex - {int}
+ */
+ setZIndex: function (zIndex) {
+ OpenLayers.Layer.prototype.setZIndex.apply(this, arguments);
+ this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ OpenLayers.Layer.prototype.moveByPx.apply(this, arguments);
+
+ if (this.dragPanMapObject) {
+ this.dragPanMapObject(dx, -dy);
+ } else {
+ this.moveTo(this.map.getCachedCenter());
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * Handle calls to move the layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ if (this.mapObject != null) {
+
+ var newCenter = this.map.getCenter();
+ var newZoom = this.map.getZoom();
+
+ if (newCenter != null) {
+
+ var moOldCenter = this.getMapObjectCenter();
+ var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter);
+
+ var moOldZoom = this.getMapObjectZoom();
+ var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom);
+
+ if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) {
+
+ if (!zoomChanged && oldCenter && this.dragPanMapObject &&
+ this.smoothDragPan) {
+ var oldPx = this.map.getViewPortPxFromLonLat(oldCenter);
+ var newPx = this.map.getViewPortPxFromLonLat(newCenter);
+ this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y);
+ } else {
+ var center = this.getMapObjectLonLatFromOLLonLat(newCenter);
+ var zoom = this.getMapObjectZoomFromOLZoom(newZoom);
+ this.setMapObjectCenter(center, zoom, dragging);
+ }
+ }
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port OpenLayers.Pixel, translated into lon/lat by map lib
+ * If the map lib is not loaded or not centered, returns null
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if ( (this.mapObject != null) &&
+ (this.getMapObjectCenter() != null) ) {
+ var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx);
+ var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel);
+ lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat);
+ }
+ return lonlat;
+ },
+
+
+ /**
+ * Method: getViewPortPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * OpenLayers.LonLat, translated into view port pixels by map lib
+ * If map lib is not loaded or not centered, returns null
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var viewPortPx = null;
+ if ( (this.mapObject != null) &&
+ (this.getMapObjectCenter() != null) ) {
+
+ var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat);
+ var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat);
+
+ viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel);
+ }
+ return viewPortPx;
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate Map Object and */
+ /* OL formats for Pixel, LonLat */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat
+ //
+
+ /**
+ * Method: getOLLonLatFromMapObjectLonLat
+ * Get an OL style map location from a 3rd party style map location
+ *
+ * Parameters
+ * moLonLat - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in
+ * MapObject LonLat
+ * Returns null if null value is passed in
+ */
+ getOLLonLatFromMapObjectLonLat: function(moLonLat) {
+ var olLonLat = null;
+ if (moLonLat != null) {
+ var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+ var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+ olLonLat = new OpenLayers.LonLat(lon, lat);
+ }
+ return olLonLat;
+ },
+
+ /**
+ * Method: getMapObjectLonLatFromOLLonLat
+ * Get a 3rd party map location from an OL map location.
+ *
+ * Parameters:
+ * olLonLat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Object} A MapObject LonLat, translated from the passed in
+ * OpenLayers.LonLat
+ * Returns null if null value is passed in
+ */
+ getMapObjectLonLatFromOLLonLat: function(olLonLat) {
+ var moLatLng = null;
+ if (olLonLat != null) {
+ moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon,
+ olLonLat.lat);
+ }
+ return moLatLng;
+ },
+
+
+ //
+ // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel
+ //
+
+ /**
+ * Method: getOLPixelFromMapObjectPixel
+ * Get an OL pixel location from a 3rd party pixel location.
+ *
+ * Parameters:
+ * moPixel - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in
+ * MapObject Pixel
+ * Returns null if null value is passed in
+ */
+ getOLPixelFromMapObjectPixel: function(moPixel) {
+ var olPixel = null;
+ if (moPixel != null) {
+ var x = this.getXFromMapObjectPixel(moPixel);
+ var y = this.getYFromMapObjectPixel(moPixel);
+ olPixel = new OpenLayers.Pixel(x, y);
+ }
+ return olPixel;
+ },
+
+ /**
+ * Method: getMapObjectPixelFromOLPixel
+ * Get a 3rd party pixel location from an OL pixel location
+ *
+ * Parameters:
+ * olPixel - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Object} A MapObject Pixel, translated from the passed in
+ * OpenLayers.Pixel
+ * Returns null if null value is passed in
+ */
+ getMapObjectPixelFromOLPixel: function(olPixel) {
+ var moPixel = null;
+ if (olPixel != null) {
+ moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y);
+ }
+ return moPixel;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.EventPane"
+});
+/* ======================================================================
+ OpenLayers/Layer/FixedZoomLevels.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.FixedZoomLevels
+ * Some Layers will already have established zoom levels (like google
+ * or ve). Instead of trying to determine them and populate a resolutions[]
+ * Array with those values, we will hijack the resolution functionality
+ * here.
+ *
+ * When you subclass FixedZoomLevels:
+ *
+ * The initResolutions() call gets nullified, meaning no resolutions[] array
+ * is set up. Which would be a big problem getResolution() in Layer, since
+ * it merely takes map.zoom and indexes into resolutions[]... but....
+ *
+ * The getResolution() call is also overridden. Instead of using the
+ * resolutions[] array, we simply calculate the current resolution based
+ * on the current extent and the current map size. But how will we be able
+ * to calculate the current extent without knowing the resolution...?
+ *
+ * The getExtent() function is also overridden. Instead of calculating extent
+ * based on the center point and the current resolution, we instead
+ * calculate the extent by getting the lonlats at the top-left and
+ * bottom-right by using the getLonLatFromViewPortPx() translation function,
+ * taken from the pixel locations (0,0) and the size of the map. But how
+ * will we be able to do lonlat-px translation without resolution....?
+ *
+ * The getZoomForResolution() method is overridden. Instead of indexing into
+ * the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in
+ * the desired resolution. With this extent, we then call getZoomForExtent()
+ *
+ *
+ * Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels,
+ * it is your responsibility to provide the following three functions:
+ *
+ * - getLonLatFromViewPortPx
+ * - getViewPortPxFromLonLat
+ * - getZoomForExtent
+ *
+ * ...those three functions should generally be provided by any reasonable
+ * API that you might be working from.
+ *
+ */
+OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions must all be implemented */
+ /* by all base layers */
+ /* */
+ /********************************************************/
+
+ /**
+ * Constructor: OpenLayers.Layer.FixedZoomLevels
+ * Create a new fixed zoom levels layer.
+ */
+ initialize: function() {
+ //this class is only just to add the following functions...
+ // nothing to actually do here... but it is probably a good
+ // idea to have layers that use these functions call this
+ // inititalize() anyways, in case at some point we decide we
+ // do want to put some functionality or state in here.
+ },
+
+ /**
+ * Method: initResolutions
+ * Populate the resolutions array
+ */
+ initResolutions: function() {
+
+ var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels'];
+
+ for(var i=0, len=props.length; i<len; i++) {
+ var property = props[i];
+ this[property] = (this.options[property] != null)
+ ? this.options[property]
+ : this.map[property];
+ }
+
+ if ( (this.minZoomLevel == null) ||
+ (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){
+ this.minZoomLevel = this.MIN_ZOOM_LEVEL;
+ }
+
+ //
+ // At this point, we know what the minimum desired zoom level is, and
+ // we must calculate the total number of zoom levels.
+ //
+ // Because we allow for the setting of either the 'numZoomLevels'
+ // or the 'maxZoomLevel' properties... on either the layer or the
+ // map, we have to define some rules to see which we take into
+ // account first in this calculation.
+ //
+ // The following is the precedence list for these properties:
+ //
+ // (1) numZoomLevels set on layer
+ // (2) maxZoomLevel set on layer
+ // (3) numZoomLevels set on map
+ // (4) maxZoomLevel set on map*
+ // (5) none of the above*
+ //
+ // *Note that options (4) and (5) are only possible if the user
+ // _explicitly_ sets the 'numZoomLevels' property on the map to
+ // null, since it is set by default to 16.
+ //
+
+ //
+ // Note to future: In 3.0, I think we should remove the default
+ // value of 16 for map.numZoomLevels. Rather, I think that value
+ // should be set as a default on the Layer.WMS class. If someone
+ // creates a 3rd party layer and does not specify any 'minZoomLevel',
+ // 'maxZoomLevel', or 'numZoomLevels', and has not explicitly
+ // specified any of those on the map object either.. then I think
+ // it is fair to say that s/he wants all the zoom levels available.
+ //
+ // By making map.numZoomLevels *null* by default, that will be the
+ // case. As it is, I don't feel comfortable changing that right now
+ // as it would be a glaring API change and actually would probably
+ // break many peoples' codes.
+ //
+
+ //the number of zoom levels we'd like to have.
+ var desiredZoomLevels;
+
+ //this is the maximum number of zoom levels the layer will allow,
+ // given the specified starting minimum zoom level.
+ var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;
+
+ if ( ((this.options.numZoomLevels == null) &&
+ (this.options.maxZoomLevel != null)) // (2)
+ ||
+ ((this.numZoomLevels == null) &&
+ (this.maxZoomLevel != null)) // (4)
+ ) {
+ //calculate based on specified maxZoomLevel (on layer or map)
+ desiredZoomLevels = this.maxZoomLevel - this.minZoomLevel + 1;
+ } else {
+ //calculate based on specified numZoomLevels (on layer or map)
+ // this covers cases (1) and (3)
+ desiredZoomLevels = this.numZoomLevels;
+ }
+
+ if (desiredZoomLevels != null) {
+ //Now that we know what we would *like* the number of zoom levels
+ // to be, based on layer or map options, we have to make sure that
+ // it does not conflict with the actual limit, as specified by
+ // the constants on the layer itself (and calculated into the
+ // 'limitZoomLevels' variable).
+ this.numZoomLevels = Math.min(desiredZoomLevels, limitZoomLevels);
+ } else {
+ // case (5) -- neither 'numZoomLevels' not 'maxZoomLevel' was
+ // set on either the layer or the map. So we just use the
+ // maximum limit as calculated by the layer's constants.
+ this.numZoomLevels = limitZoomLevels;
+ }
+
+ //now that the 'numZoomLevels' is appropriately, safely set,
+ // we go back and re-calculate the 'maxZoomLevel'.
+ this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1;
+
+ if (this.RESOLUTIONS != null) {
+ var resolutionsIndex = 0;
+ this.resolutions = [];
+ for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) {
+ this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i];
+ }
+ this.maxResolution = this.resolutions[0];
+ this.minResolution = this.resolutions[this.resolutions.length - 1];
+ }
+ },
+
+ /**
+ * APIMethod: getResolution
+ * Get the current map resolution
+ *
+ * Returns:
+ * {Float} Map units per Pixel
+ */
+ getResolution: function() {
+
+ if (this.resolutions != null) {
+ return OpenLayers.Layer.prototype.getResolution.apply(this, arguments);
+ } else {
+ var resolution = null;
+
+ var viewSize = this.map.getSize();
+ var extent = this.getExtent();
+
+ if ((viewSize != null) && (extent != null)) {
+ resolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+ }
+ return resolution;
+ }
+ },
+
+ /**
+ * APIMethod: getExtent
+ * Calculates using px-> lonlat translation functions on tl and br
+ * corners of viewport
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function () {
+ var size = this.map.getSize();
+ var tl = this.getLonLatFromViewPortPx({
+ x: 0, y: 0
+ });
+ var br = this.getLonLatFromViewPortPx({
+ x: size.w, y: size.h
+ });
+
+ if ((tl != null) && (br != null)) {
+ return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat);
+ } else {
+ return null;
+ }
+ },
+
+ /**
+ * Method: getZoomForResolution
+ * Get the zoom level for a given resolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution) {
+
+ if (this.resolutions != null) {
+ return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments);
+ } else {
+ var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []);
+ return this.getZoomForExtent(extent);
+ }
+ },
+
+
+
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate GMaps and OL */
+ /* formats for Pixel, LonLat, Bounds, and Zoom */
+ /* */
+ /********************************************************/
+
+
+ //
+ // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+ //
+
+ /**
+ * Method: getOLZoomFromMapObjectZoom
+ * Get the OL zoom index from the map object zoom level
+ *
+ * Parameters:
+ * moZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} An OpenLayers Zoom level, translated from the passed in zoom
+ * Returns null if null value is passed in
+ */
+ getOLZoomFromMapObjectZoom: function(moZoom) {
+ var zoom = null;
+ if (moZoom != null) {
+ zoom = moZoom - this.minZoomLevel;
+ if (this.map.baseLayer !== this) {
+ zoom = this.map.baseLayer.getZoomForResolution(
+ this.getResolutionForZoom(zoom)
+ );
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * Method: getMapObjectZoomFromOLZoom
+ * Get the map object zoom level from the OL zoom level
+ *
+ * Parameters:
+ * olZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} A MapObject level, translated from the passed in olZoom
+ * Returns null if null value is passed in
+ */
+ getMapObjectZoomFromOLZoom: function(olZoom) {
+ var zoom = null;
+ if (olZoom != null) {
+ zoom = olZoom + this.minZoomLevel;
+ if (this.map.baseLayer !== this) {
+ zoom = this.getZoomForResolution(
+ this.map.baseLayer.getResolutionForZoom(zoom)
+ );
+ }
+ }
+ return zoom;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels"
+});
+
+/* ======================================================================
+ OpenLayers/Layer/Google.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/SphericalMercator.js
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Google
+ *
+ * Provides a wrapper for Google's Maps API
+ * Normally the Terms of Use for this API do not allow wrapping, but Google
+ * have provided written consent to OpenLayers for this - see email in
+ * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.SphericalMercator>
+ * - <OpenLayers.Layer.EventPane>
+ * - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Google = OpenLayers.Class(
+ OpenLayers.Layer.EventPane,
+ OpenLayers.Layer.FixedZoomLevels, {
+
+ /**
+ * Constant: MIN_ZOOM_LEVEL
+ * {Integer} 0
+ */
+ MIN_ZOOM_LEVEL: 0,
+
+ /**
+ * Constant: MAX_ZOOM_LEVEL
+ * {Integer} 21
+ */
+ MAX_ZOOM_LEVEL: 21,
+
+ /**
+ * Constant: RESOLUTIONS
+ * {Array(Float)} Hardcode these resolutions so that they are more closely
+ * tied with the standard wms projection
+ */
+ RESOLUTIONS: [
+ 1.40625,
+ 0.703125,
+ 0.3515625,
+ 0.17578125,
+ 0.087890625,
+ 0.0439453125,
+ 0.02197265625,
+ 0.010986328125,
+ 0.0054931640625,
+ 0.00274658203125,
+ 0.001373291015625,
+ 0.0006866455078125,
+ 0.00034332275390625,
+ 0.000171661376953125,
+ 0.0000858306884765625,
+ 0.00004291534423828125,
+ 0.00002145767211914062,
+ 0.00001072883605957031,
+ 0.00000536441802978515,
+ 0.00000268220901489257,
+ 0.0000013411045074462891,
+ 0.00000067055225372314453
+ ],
+
+ /**
+ * APIProperty: type
+ * {GMapType}
+ */
+ type: null,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Allow user to pan forever east/west. Default is true.
+ * Setting this to false only restricts panning if
+ * <sphericalMercator> is true.
+ */
+ wrapDateLine: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * {Boolean} Should the map act as a mercator-projected map? This will
+ * cause all interactions with the map to be in the actual map
+ * projection, which allows support for vector drawing, overlaying
+ * other maps, etc.
+ */
+ sphericalMercator: false,
+
+ /**
+ * Property: version
+ * {Number} The version of the Google Maps API
+ */
+ version: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Google
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * options - {Object} An optional object whose properties will be set
+ * on the layer.
+ */
+ initialize: function(name, options) {
+ options = options || {};
+ if(!options.version) {
+ options.version = typeof GMap2 === "function" ? "2" : "3";
+ }
+ var mixin = OpenLayers.Layer.Google["v" +
+ options.version.replace(/\./g, "_")];
+ if (mixin) {
+ OpenLayers.Util.applyDefaults(options, mixin);
+ } else {
+ throw "Unsupported Google Maps API version: " + options.version;
+ }
+
+ OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS);
+ if (options.maxExtent) {
+ options.maxExtent = options.maxExtent.clone();
+ }
+
+ OpenLayers.Layer.EventPane.prototype.initialize.apply(this,
+ [name, options]);
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+ [name, options]);
+
+ if (this.sphericalMercator) {
+ OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+ this.initMercatorParameters();
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Google>} An exact clone of this layer
+ */
+ clone: function() {
+ /**
+ * This method isn't intended to be called by a subclass and it
+ * doesn't call the same method on the superclass. We don't call
+ * the super's clone because we don't want properties that are set
+ * on this layer after initialize (i.e. this.mapObject etc.).
+ */
+ return new OpenLayers.Layer.Google(
+ this.name, this.getOptions()
+ );
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the layer (if in range)
+ */
+ setVisibility: function(visible) {
+ // sharing a map container, opacity has to be set per layer
+ var opacity = this.opacity == null ? 1 : this.opacity;
+ OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments);
+ this.setOpacity(opacity);
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * visible - {Boolean}
+ */
+ display: function(visible) {
+ if (!this._dragging) {
+ this.setGMapVisibility(visible);
+ }
+ OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ this._dragging = dragging;
+ OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments);
+ delete this._dragging;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity !== this.opacity) {
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ this.opacity = opacity;
+ }
+ // Though this layer's opacity may not change, we're sharing a container
+ // and need to update the opacity for the entire container.
+ if (this.getVisibility()) {
+ var container = this.getMapContainer();
+ OpenLayers.Util.modifyDOMElement(
+ container, null, null, null, null, null, null, opacity
+ );
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up this layer.
+ */
+ destroy: function() {
+ /**
+ * We have to override this method because the event pane destroy
+ * deletes the mapObject reference before removing this layer from
+ * the map.
+ */
+ if (this.map) {
+ this.setGMapVisibility(false);
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache && cache.count <= 1) {
+ this.removeGMapElements();
+ }
+ }
+ OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: removeGMapElements
+ * Remove all elements added to the dom. This should only be called if
+ * this is the last of the Google layers for the given map.
+ */
+ removeGMapElements: function() {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // remove shared elements from dom
+ var container = this.mapObject && this.getMapContainer();
+ if (container && container.parentNode) {
+ container.parentNode.removeChild(container);
+ }
+ var termsOfUse = cache.termsOfUse;
+ if (termsOfUse && termsOfUse.parentNode) {
+ termsOfUse.parentNode.removeChild(termsOfUse);
+ }
+ var poweredBy = cache.poweredBy;
+ if (poweredBy && poweredBy.parentNode) {
+ poweredBy.parentNode.removeChild(poweredBy);
+ }
+ if (this.mapObject && window.google && google.maps &&
+ google.maps.event && google.maps.event.clearListeners) {
+ google.maps.event.clearListeners(this.mapObject, 'tilesloaded');
+ }
+ }
+ },
+
+ /**
+ * APIMethod: removeMap
+ * On being removed from the map, also remove termsOfUse and poweredBy divs
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ // hide layer before removing
+ if (this.visibility && this.mapObject) {
+ this.setGMapVisibility(false);
+ }
+ // check to see if last Google layer in this map
+ var cache = OpenLayers.Layer.Google.cache[map.id];
+ if (cache) {
+ if (cache.count <= 1) {
+ this.removeGMapElements();
+ delete OpenLayers.Layer.Google.cache[map.id];
+ } else {
+ // decrement the layer count
+ --cache.count;
+ }
+ }
+ // remove references to gmap elements
+ delete this.termsOfUse;
+ delete this.poweredBy;
+ delete this.mapObject;
+ delete this.dragObject;
+ OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments);
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getOLBoundsFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the
+ * passed-in MapObject Bounds.
+ * Returns null if null value is passed in.
+ */
+ getOLBoundsFromMapObjectBounds: function(moBounds) {
+ var olBounds = null;
+ if (moBounds != null) {
+ var sw = moBounds.getSouthWest();
+ var ne = moBounds.getNorthEast();
+ if (this.sphericalMercator) {
+ sw = this.forwardMercator(sw.lng(), sw.lat());
+ ne = this.forwardMercator(ne.lng(), ne.lat());
+ } else {
+ sw = new OpenLayers.LonLat(sw.lng(), sw.lat());
+ ne = new OpenLayers.LonLat(ne.lng(), ne.lat());
+ }
+ olBounds = new OpenLayers.Bounds(sw.lon,
+ sw.lat,
+ ne.lon,
+ ne.lat );
+ }
+ return olBounds;
+ },
+
+ /**
+ * APIMethod: getWarningHTML
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ return OpenLayers.i18n("googleWarning");
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: getMapObjectCenter
+ *
+ * Returns:
+ * {Object} The mapObject's current center in Map Object format
+ */
+ getMapObjectCenter: function() {
+ return this.mapObject.getCenter();
+ },
+
+ /**
+ * APIMethod: getMapObjectZoom
+ *
+ * Returns:
+ * {Integer} The mapObject's current zoom, in Map Object format
+ */
+ getMapObjectZoom: function() {
+ return this.mapObject.getZoom();
+ },
+
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getLongitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Longitude of the given MapObject LonLat
+ */
+ getLongitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon :
+ moLonLat.lng();
+ },
+
+ /**
+ * APIMethod: getLatitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Latitude of the given MapObject LonLat
+ */
+ getLatitudeFromMapObjectLonLat: function(moLonLat) {
+ var lat = this.sphericalMercator ?
+ this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat :
+ moLonLat.lat();
+ return lat;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getXFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} X value of the MapObject Pixel
+ */
+ getXFromMapObjectPixel: function(moPixel) {
+ return moPixel.x;
+ },
+
+ /**
+ * APIMethod: getYFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} Y value of the MapObject Pixel
+ */
+ getYFromMapObjectPixel: function(moPixel) {
+ return moPixel.y;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Google"
+});
+
+/**
+ * Property: OpenLayers.Layer.Google.cache
+ * {Object} Cache for elements that should only be created once per map.
+ */
+OpenLayers.Layer.Google.cache = {};
+
+
+/**
+ * Constant: OpenLayers.Layer.Google.v2
+ *
+ * Mixin providing functionality specific to the Google Maps API v2.
+ *
+ * This API has been deprecated by Google.
+ * Developers are encouraged to migrate to v3 of the API; support for this
+ * is provided by <OpenLayers.Layer.Google.v3>
+ */
+OpenLayers.Layer.Google.v2 = {
+
+ /**
+ * Property: termsOfUse
+ * {DOMElement} Div for Google's copyright and terms of use link
+ */
+ termsOfUse: null,
+
+ /**
+ * Property: poweredBy
+ * {DOMElement} Div for Google's powered by logo and link
+ */
+ poweredBy: null,
+
+ /**
+ * Property: dragObject
+ * {GDraggableObject} Since 2.93, Google has exposed the ability to get
+ * the maps GDraggableObject. We can now use this for smooth panning
+ */
+ dragObject: null,
+
+ /**
+ * Method: loadMapObject
+ * Load the GMap and register appropriate event listeners. If we can't
+ * load GMap2, then display a warning message.
+ */
+ loadMapObject:function() {
+ if (!this.type) {
+ this.type = G_NORMAL_MAP;
+ }
+ var mapObject, termsOfUse, poweredBy;
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // there are already Google layers added to this map
+ mapObject = cache.mapObject;
+ termsOfUse = cache.termsOfUse;
+ poweredBy = cache.poweredBy;
+ // increment the layer count
+ ++cache.count;
+ } else {
+ // this is the first Google layer for this map
+
+ var container = this.map.viewPortDiv;
+ var div = document.createElement("div");
+ div.id = this.map.id + "_GMap2Container";
+ div.style.position = "absolute";
+ div.style.width = "100%";
+ div.style.height = "100%";
+ container.appendChild(div);
+
+ // create GMap and shuffle elements
+ try {
+ mapObject = new GMap2(div);
+
+ // move the ToS and branding stuff up to the container div
+ termsOfUse = div.lastChild;
+ container.appendChild(termsOfUse);
+ termsOfUse.style.zIndex = "1100";
+ termsOfUse.style.right = "";
+ termsOfUse.style.bottom = "";
+ termsOfUse.className = "olLayerGoogleCopyright";
+
+ poweredBy = div.lastChild;
+ container.appendChild(poweredBy);
+ poweredBy.style.zIndex = "1100";
+ poweredBy.style.right = "";
+ poweredBy.style.bottom = "";
+ poweredBy.className = "olLayerGooglePoweredBy gmnoprint";
+
+ } catch (e) {
+ throw(e);
+ }
+ // cache elements for use by any other google layers added to
+ // this same map
+ OpenLayers.Layer.Google.cache[this.map.id] = {
+ mapObject: mapObject,
+ termsOfUse: termsOfUse,
+ poweredBy: poweredBy,
+ count: 1
+ };
+ }
+
+ this.mapObject = mapObject;
+ this.termsOfUse = termsOfUse;
+ this.poweredBy = poweredBy;
+
+ // ensure this layer type is one of the mapObject types
+ if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),
+ this.type) === -1) {
+ this.mapObject.addMapType(this.type);
+ }
+
+ //since v 2.93 getDragObject is now available.
+ if(typeof mapObject.getDragObject == "function") {
+ this.dragObject = mapObject.getDragObject();
+ } else {
+ this.dragPanMapObject = null;
+ }
+
+ if(this.isBaseLayer === false) {
+ this.setGMapVisibility(this.div.style.display !== "none");
+ }
+
+ },
+
+ /**
+ * APIMethod: onMapResize
+ */
+ onMapResize: function() {
+ // workaround for resizing of invisible or not yet fully loaded layers
+ // where GMap2.checkResize() does not work. We need to load the GMap
+ // for the old div size, then checkResize(), and then call
+ // layer.moveTo() to trigger GMap.setCenter() (which will finish
+ // the GMap initialization).
+ if(this.visibility && this.mapObject.isLoaded()) {
+ this.mapObject.checkResize();
+ } else {
+ if(!this._resized) {
+ var layer = this;
+ var handle = GEvent.addListener(this.mapObject, "load", function() {
+ GEvent.removeListener(handle);
+ delete layer._resized;
+ layer.mapObject.checkResize();
+ layer.moveTo(layer.map.getCenter(), layer.map.getZoom());
+ });
+ }
+ this._resized = true;
+ }
+ },
+
+ /**
+ * Method: setGMapVisibility
+ * Display the GMap container and associated elements.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the GMap elements.
+ */
+ setGMapVisibility: function(visible) {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ var container = this.mapObject.getContainer();
+ if (visible === true) {
+ this.mapObject.setMapType(this.type);
+ container.style.display = "";
+ this.termsOfUse.style.left = "";
+ this.termsOfUse.style.display = "";
+ this.poweredBy.style.display = "";
+ cache.displayed = this.id;
+ } else {
+ if (cache.displayed === this.id) {
+ delete cache.displayed;
+ }
+ if (!cache.displayed) {
+ container.style.display = "none";
+ this.termsOfUse.style.display = "none";
+ // move ToU far to the left in addition to setting display
+ // to "none", because at the end of the GMap2 load
+ // sequence, display: none will be unset and ToU would be
+ // visible after loading a map with a google layer that is
+ // initially hidden.
+ this.termsOfUse.style.left = "-9999px";
+ this.poweredBy.style.display = "none";
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getMapContainer
+ *
+ * Returns:
+ * {DOMElement} the GMap container's div
+ */
+ getMapContainer: function() {
+ return this.mapObject.getContainer();
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getMapObjectBoundsFromOLBounds
+ *
+ * Parameters:
+ * olBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} A MapObject Bounds, translated from olBounds
+ * Returns null if null value is passed in
+ */
+ getMapObjectBoundsFromOLBounds: function(olBounds) {
+ var moBounds = null;
+ if (olBounds != null) {
+ var sw = this.sphericalMercator ?
+ this.inverseMercator(olBounds.bottom, olBounds.left) :
+ new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+ var ne = this.sphericalMercator ?
+ this.inverseMercator(olBounds.top, olBounds.right) :
+ new OpenLayers.LonLat(olBounds.top, olBounds.right);
+ moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon),
+ new GLatLng(ne.lat, ne.lon));
+ }
+ return moBounds;
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ this.mapObject.setCenter(center, zoom);
+ },
+
+ /**
+ * APIMethod: dragPanMapObject
+ *
+ * Parameters:
+ * dX - {Integer}
+ * dY - {Integer}
+ */
+ dragPanMapObject: function(dX, dY) {
+ this.dragObject.moveBy(new GSize(-dX, dY));
+ },
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ return this.mapObject.fromContainerPixelToLatLng(moPixel);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ return this.mapObject.fromLatLngToContainerPixel(moLonLat);
+ },
+
+
+ // Bounds
+
+ /**
+ * APIMethod: getMapObjectZoomFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object} MapObject Bounds format
+ *
+ * Returns:
+ * {Object} MapObject Zoom for specified MapObject Bounds
+ */
+ getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+ return this.mapObject.getBoundsZoomLevel(moBounds);
+ },
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var gLatLng;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ gLatLng = new GLatLng(lonlat.lat, lonlat.lon);
+ } else {
+ gLatLng = new GLatLng(lat, lon);
+ }
+ return gLatLng;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new GPoint(x, y);
+ }
+
+};
+/* ======================================================================
+ OpenLayers/Geometry.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object. Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor. This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ *
+ * Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
+ * explicitly include the OpenLayers.Format.WKT in your build.
+ */
+OpenLayers.Geometry = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique identifier for this geometry.
+ */
+ id: null,
+
+ /**
+ * Property: parent
+ * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+ * of another geometry
+ */
+ parent: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The bounds of this geometry
+ */
+ bounds: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry
+ * Creates a geometry object.
+ */
+ initialize: function() {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
+ },
+
+ /**
+ * Method: destroy
+ * Destroy this geometry.
+ */
+ destroy: function() {
+ this.id = null;
+ this.bounds = null;
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this geometry. Does not set any non-standard
+ * properties of the cloned geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} An exact clone of this geometry.
+ */
+ clone: function() {
+ return new OpenLayers.Geometry();
+ },
+
+ /**
+ * Method: setBounds
+ * Set the bounds for this Geometry.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ if (bounds) {
+ this.bounds = bounds.clone();
+ }
+ },
+
+ /**
+ * Method: clearBounds
+ * Nullify this components bounds and that of its parent as well.
+ */
+ clearBounds: function() {
+ this.bounds = null;
+ if (this.parent) {
+ this.parent.clearBounds();
+ }
+ },
+
+ /**
+ * Method: extendBounds
+ * Extend the existing bounds to include the new bounds.
+ * If geometry's bounds is not yet set, then set a new Bounds.
+ *
+ * Parameters:
+ * newBounds - {<OpenLayers.Bounds>}
+ */
+ extendBounds: function(newBounds){
+ var bounds = this.getBounds();
+ if (!bounds) {
+ this.setBounds(newBounds);
+ } else {
+ this.bounds.extend(newBounds);
+ }
+ },
+
+ /**
+ * APIMethod: getBounds
+ * Get the bounds for this Geometry. If bounds is not set, it
+ * is calculated again, this makes queries faster.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getBounds: function() {
+ if (this.bounds == null) {
+ this.calculateBounds();
+ }
+ return this.bounds;
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds for the geometry.
+ */
+ calculateBounds: function() {
+ //
+ // This should be overridden by subclasses.
+ //
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options depend on the specific geometry type.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ },
+
+ /**
+ * Method: atPoint
+ * Note - This is only an approximation based on the bounds of the
+ * geometry.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the geometry is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ var bounds = this.getBounds();
+ if ((bounds != null) && (lonlat != null)) {
+
+ var dX = (toleranceLon != null) ? toleranceLon : 0;
+ var dY = (toleranceLat != null) ? toleranceLat : 0;
+
+ var toleranceBounds =
+ new OpenLayers.Bounds(this.bounds.left - dX,
+ this.bounds.bottom - dY,
+ this.bounds.right + dX,
+ this.bounds.top + dY);
+
+ atPoint = toleranceBounds.containsLonLat(lonlat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: getLength
+ * Calculate the length of this geometry. This method is defined in
+ * subclasses.
+ *
+ * Returns:
+ * {Float} The length of the collection by summing its parts
+ */
+ getLength: function() {
+ //to be overridden by geometries that actually have a length
+ //
+ return 0.0;
+ },
+
+ /**
+ * Method: getArea
+ * Calculate the area of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ //to be overridden by geometries that actually have an area
+ //
+ return 0.0;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ * Calculate the centroid of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return null;
+ },
+
+ /**
+ * Method: toString
+ * Returns a text representation of the geometry. If the WKT format is
+ * included in a build, this will be the Well-Known Text
+ * representation.
+ *
+ * Returns:
+ * {String} String representation of this geometry.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ string = OpenLayers.Format.WKT.prototype.write(
+ new OpenLayers.Feature.Vector(this)
+ );
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry"
+});
+
+/**
+ * Function: OpenLayers.Geometry.fromWKT
+ * Generate a geometry given a Well-Known Text string. For this method to
+ * work, you must include the OpenLayers.Format.WKT in your build
+ * explicitly.
+ *
+ * Parameters:
+ * wkt - {String} A string representing the geometry in Well-Known Text.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry of the appropriate class.
+ */
+OpenLayers.Geometry.fromWKT = function(wkt) {
+ var geom;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ var format = OpenLayers.Geometry.fromWKT.format;
+ if (!format) {
+ format = new OpenLayers.Format.WKT();
+ OpenLayers.Geometry.fromWKT.format = format;
+ }
+ var result = format.read(wkt);
+ if (result instanceof OpenLayers.Feature.Vector) {
+ geom = result.geometry;
+ } else if (OpenLayers.Util.isArray(result)) {
+ var len = result.length;
+ var components = new Array(len);
+ for (var i=0; i<len; ++i) {
+ components[i] = result[i].geometry;
+ }
+ geom = new OpenLayers.Geometry.Collection(components);
+ }
+ }
+ return geom;
+};
+
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect. Optionally calculates
+ * and returns the intersection point. This function is optimized for
+ * cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1. In those
+ * obvious cases where there is no intersection, the function should
+ * not be called.
+ *
+ * Parameters:
+ * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * options - {Object} Optional properties for calculating the intersection.
+ *
+ * Valid options:
+ * point - {Boolean} Return the intersection point. If false, the actual
+ * intersection point will not be calculated. If true and the segments
+ * intersect, the intersection point will be returned. If true and
+ * the segments do not intersect, false will be returned. If true and
+ * the segments are coincident, true will be returned.
+ * tolerance - {Number} If a non-null value is provided, if the segments are
+ * within the tolerance distance, this will be considered an intersection.
+ * In addition, if the point option is true and the calculated intersection
+ * is within the tolerance distance of an end point, the endpoint will be
+ * returned instead of the calculated intersection. Further, if the
+ * intersection is within the tolerance of endpoints on both segments, or
+ * if two segment endpoints are within the tolerance distance of eachother
+ * (but no intersection is otherwise calculated), an endpoint on the
+ * first segment provided will be returned.
+ *
+ * Returns:
+ * {Boolean | <OpenLayers.Geometry.Point>} The two segments intersect.
+ * If the point argument is true, the return will be the intersection
+ * point or false if none exists. If point is true and the segments
+ * are coincident, return will be true (and the instersection is equal
+ * to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
+ var point = options && options.point;
+ var tolerance = options && options.tolerance;
+ var intersection = false;
+ var x11_21 = seg1.x1 - seg2.x1;
+ var y11_21 = seg1.y1 - seg2.y1;
+ var x12_11 = seg1.x2 - seg1.x1;
+ var y12_11 = seg1.y2 - seg1.y1;
+ var y22_21 = seg2.y2 - seg2.y1;
+ var x22_21 = seg2.x2 - seg2.x1;
+ var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+ var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+ var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+ if(d == 0) {
+ // parallel
+ if(n1 == 0 && n2 == 0) {
+ // coincident
+ intersection = true;
+ }
+ } else {
+ var along1 = n1 / d;
+ var along2 = n2 / d;
+ if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+ // intersect
+ if(!point) {
+ intersection = true;
+ } else {
+ // calculate the intersection point
+ var x = seg1.x1 + (along1 * x12_11);
+ var y = seg1.y1 + (along1 * y12_11);
+ intersection = new OpenLayers.Geometry.Point(x, y);
+ }
+ }
+ }
+ if(tolerance) {
+ var dist;
+ if(intersection) {
+ if(point) {
+ var segs = [seg1, seg2];
+ var seg, x, y;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ seg = segs[i];
+ for(var j=1; j<3; ++j) {
+ x = seg["x" + j];
+ y = seg["y" + j];
+ dist = Math.sqrt(
+ Math.pow(x - intersection.x, 2) +
+ Math.pow(y - intersection.y, 2)
+ );
+ if(dist < tolerance) {
+ intersection.x = x;
+ intersection.y = y;
+ break outer;
+ }
+ }
+ }
+
+ }
+ } else {
+ // no calculated intersection, but segments could be within
+ // the tolerance of one another
+ var segs = [seg1, seg2];
+ var source, target, x, y, p, result;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ source = segs[i];
+ target = segs[(i+1)%2];
+ for(var j=1; j<3; ++j) {
+ p = {x: source["x"+j], y: source["y"+j]};
+ result = OpenLayers.Geometry.distanceToSegment(p, target);
+ if(result.distance < tolerance) {
+ if(point) {
+ intersection = new OpenLayers.Geometry.Point(p.x, p.y);
+ } else {
+ intersection = true;
+ }
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ return intersection;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceToSegment
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with distance, along, x, and y properties. The distance
+ * will be the shortest distance between the input point and segment.
+ * The x and y properties represent the coordinates along the segment
+ * where the shortest distance meets the segment. The along attribute
+ * describes how far between the two segment points the given point is.
+ */
+OpenLayers.Geometry.distanceToSegment = function(point, segment) {
+ var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
+ result.distance = Math.sqrt(result.distance);
+ return result;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceSquaredToSegment
+ *
+ * Usually the distanceToSegment function should be used. This variant however
+ * can be used for comparisons where the exact distance is not important.
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with squared distance, along, x, and y properties.
+ * The distance will be the shortest distance between the input point and
+ * segment. The x and y properties represent the coordinates along the
+ * segment where the shortest distance meets the segment. The along
+ * attribute describes how far between the two segment points the given
+ * point is.
+ */
+OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
+ var x0 = point.x;
+ var y0 = point.y;
+ var x1 = segment.x1;
+ var y1 = segment.y1;
+ var x2 = segment.x2;
+ var y2 = segment.y2;
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
+ (Math.pow(dx, 2) + Math.pow(dy, 2));
+ var x, y;
+ if(along <= 0.0) {
+ x = x1;
+ y = y1;
+ } else if(along >= 1.0) {
+ x = x2;
+ y = y2;
+ } else {
+ x = x1 + along * dx;
+ y = y1 + along * dy;
+ }
+ return {
+ distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
+ x: x, y: y,
+ along: along
+ };
+};
+/* ======================================================================
+ OpenLayers/Geometry/Collection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different
+ * Geometries. These are stored in the local parameter <components> (which
+ * can be passed as a parameter to the constructor).
+ *
+ * As new geometries are added to the collection, they are NOT cloned.
+ * When removing geometries, they need to be specified by reference (ie you
+ * have to pass in the *exact* geometry to be removed).
+ *
+ * The <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: components
+ * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
+ */
+ components: null,
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Collection
+ * Creates a Geometry Collection -- a list of geoms.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
+ *
+ */
+ initialize: function (components) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+ this.components = [];
+ if (components != null) {
+ this.addComponents(components);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this geometry.
+ */
+ destroy: function () {
+ this.components.length = 0;
+ this.components = null;
+ OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ * Clone this geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+ */
+ clone: function() {
+ var geometry = eval("new " + this.CLASS_NAME + "()");
+ for(var i=0, len=this.components.length; i<len; i++) {
+ geometry.addComponent(this.components[i].clone());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(geometry, this);
+
+ return geometry;
+ },
+
+ /**
+ * Method: getComponentsString
+ * Get a string representing the components for this collection
+ *
+ * Returns:
+ * {String} A string representation of the components of this geometry
+ */
+ getComponentsString: function(){
+ var strings = [];
+ for(var i=0, len=this.components.length; i<len; i++) {
+ strings.push(this.components[i].toShortString());
+ }
+ return strings.join(",");
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds by iterating through the components and
+ * calling calling extendBounds() on each item.
+ */
+ calculateBounds: function() {
+ this.bounds = null;
+ var bounds = new OpenLayers.Bounds();
+ var components = this.components;
+ if (components) {
+ for (var i=0, len=components.length; i<len; i++) {
+ bounds.extend(components[i].getBounds());
+ }
+ }
+ // to preserve old behavior, we only set bounds if non-null
+ // in the future, we could add bounds.isEmpty()
+ if (bounds.left != null && bounds.bottom != null &&
+ bounds.right != null && bounds.top != null) {
+ this.setBounds(bounds);
+ }
+ },
+
+ /**
+ * APIMethod: addComponents
+ * Add components to this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+ */
+ addComponents: function(components){
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=0, len=components.length; i<len; i++) {
+ this.addComponent(components[i]);
+ }
+ },
+
+ /**
+ * Method: addComponent
+ * Add a new component (geometry) to the collection. If this.componentTypes
+ * is set, then the component class name must be in the componentTypes array.
+ *
+ * The bounds cache is reset.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>} A geometry to add
+ * index - {int} Optional index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} The component geometry was successfully added
+ */
+ addComponent: function(component, index) {
+ var added = false;
+ if(component) {
+ if(this.componentTypes == null ||
+ (OpenLayers.Util.indexOf(this.componentTypes,
+ component.CLASS_NAME) > -1)) {
+
+ if(index != null && (index < this.components.length)) {
+ var components1 = this.components.slice(0, index);
+ var components2 = this.components.slice(index,
+ this.components.length);
+ components1.push(component);
+ this.components = components1.concat(components2);
+ } else {
+ this.components.push(component);
+ }
+ component.parent = this;
+ this.clearBounds();
+ added = true;
+ }
+ }
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponents
+ * Remove components from this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} The components to be removed
+ *
+ * Returns:
+ * {Boolean} A component was removed.
+ */
+ removeComponents: function(components) {
+ var removed = false;
+
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=components.length-1; i>=0; --i) {
+ removed = this.removeComponent(components[i]) || removed;
+ }
+ return removed;
+ },
+
+ /**
+ * Method: removeComponent
+ * Remove a component from this geometry.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(component) {
+
+ OpenLayers.Util.removeItem(this.components, component);
+
+ // clearBounds() so that it gets recalculated on the next call
+ // to this.getBounds();
+ this.clearBounds();
+ return true;
+ },
+
+ /**
+ * APIMethod: getLength
+ * Calculate the length of this geometry
+ *
+ * Returns:
+ * {Float} The length of the geometry
+ */
+ getLength: function() {
+ var length = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getLength();
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getArea
+ * Calculate the area of this geometry. Note how this function is overridden
+ * in <OpenLayers.Geometry.Polygon>.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ var area = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getArea();
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the geometry in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getGeodesicArea(projection);
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Compute the centroid for this geometry collection.
+ *
+ * Parameters:
+ * weighted - {Boolean} Perform the getCentroid computation recursively,
+ * returning an area weighted average of all geometries in this collection.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function(weighted) {
+ if (!weighted) {
+ return this.components.length && this.components[0].getCentroid();
+ }
+ var len = this.components.length;
+ if (!len) {
+ return false;
+ }
+
+ var areas = [];
+ var centroids = [];
+ var areaSum = 0;
+ var minArea = Number.MAX_VALUE;
+ var component;
+ for (var i=0; i<len; ++i) {
+ component = this.components[i];
+ var area = component.getArea();
+ var centroid = component.getCentroid(true);
+ if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
+ continue;
+ }
+ areas.push(area);
+ areaSum += area;
+ minArea = (area < minArea && area > 0) ? area : minArea;
+ centroids.push(centroid);
+ }
+ len = areas.length;
+ if (areaSum === 0) {
+ // all the components in this collection have 0 area
+ // probably a collection of points -- weight all the points the same
+ for (var i=0; i<len; ++i) {
+ areas[i] = 1;
+ }
+ areaSum = areas.length;
+ } else {
+ // normalize all the areas where the smallest area will get
+ // a value of 1
+ for (var i=0; i<len; ++i) {
+ areas[i] /= minArea;
+ }
+ areaSum /= minArea;
+ }
+
+ var xSum = 0, ySum = 0, centroid, area;
+ for (var i=0; i<len; ++i) {
+ centroid = centroids[i];
+ area = areas[i];
+ xSum += centroid.x * area;
+ ySum += centroid.y * area;
+ }
+
+ return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var length = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getGeodesicLength(projection);
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i=0, len=this.components.length; i<len; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0; i<this.components.length; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best, distance;
+ var min = Number.POSITIVE_INFINITY;
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ result = this.components[i].distanceTo(geometry, options);
+ distance = details ? result.distance : result;
+ if(distance < min) {
+ min = distance;
+ best = result;
+ if(min == 0) {
+ break;
+ }
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geometry) {
+ var equivalent = true;
+ if(!geometry || !geometry.CLASS_NAME ||
+ (this.CLASS_NAME != geometry.CLASS_NAME)) {
+ equivalent = false;
+ } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
+ (geometry.components.length != this.components.length)) {
+ equivalent = false;
+ } else {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ if(!this.components[i].equals(geometry.components[i])) {
+ equivalent = false;
+ break;
+ }
+ }
+ }
+ return equivalent;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ for(var i=0, len=this.components.length; i<len; ++ i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices = [];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ Array.prototype.push.apply(
+ vertices, this.components[i].getVertices(nodes)
+ );
+ }
+ return vertices;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Point.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Point
+ * Point geometry class.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: x
+ * {float}
+ */
+ x: null,
+
+ /**
+ * APIProperty: y
+ * {float}
+ */
+ y: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Point
+ * Construct a point geometry.
+ *
+ * Parameters:
+ * x - {float}
+ * y - {float}
+ *
+ */
+ initialize: function(x, y) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Geometry.Point(this.x, this.y);
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ return obj;
+ },
+
+ /**
+ * Method: calculateBounds
+ * Create a new Bounds based on the lon/lat
+ */
+ calculateBounds: function () {
+ this.bounds = new OpenLayers.Bounds(this.x, this.y,
+ this.x, this.y);
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var distance, x0, y0, x1, y1, result;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ x0 = this.x;
+ y0 = this.y;
+ x1 = geometry.x;
+ y1 = geometry.y;
+ distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
+ result = !details ?
+ distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
+ } else {
+ result = geometry.distanceTo(this, options);
+ if(details) {
+ // switch coord order since this geom is target
+ result = {
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0,
+ distance: result.distance
+ };
+ }
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geom - {<OpenLayers.Geometry.Point>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geom) {
+ var equals = false;
+ if (geom != null) {
+ equals = ((this.x == geom.x && this.y == geom.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * Method: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of Point object.
+ * (ex. <i>"5, 42"</i>)
+ */
+ toShortString: function() {
+ return (this.x + ", " + this.y);
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ this.x = this.x + x;
+ this.y = this.y + y;
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a point around another.
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ angle *= Math.PI / 180;
+ var radius = this.distanceTo(origin);
+ var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+ this.x = origin.x + (radius * Math.cos(theta));
+ this.y = origin.y + (radius * Math.sin(theta));
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return new OpenLayers.Geometry.Point(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a point relative to some origin. For points, this has the effect
+ * of scaling a vector (from the origin to the point). This method is
+ * more useful on geometry collection subclasses.
+ *
+ * Parameters:
+ * scale - {Float} Ratio of the new distance from the origin to the old
+ * distance from the origin. A scale of 2 doubles the
+ * distance between the point and origin.
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ ratio = (ratio == undefined) ? 1 : ratio;
+ this.x = origin.x + (scale * ratio * (this.x - origin.x));
+ this.y = origin.y + (scale * (this.y - origin.y));
+ this.clearBounds();
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.equals(geometry);
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: transform
+ * Translate the x,y properties of the point from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if ((source && dest)) {
+ OpenLayers.Projection.transform(
+ this, source, dest);
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return [this];
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Point"
+});
+/* ======================================================================
+ OpenLayers/Geometry/MultiPoint.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points. Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPoint
+ * Create a new MultiPoint Geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>}
+ */
+
+ /**
+ * APIMethod: addPoint
+ * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be added
+ * index - {Integer} Optional index
+ */
+ addPoint: function(point, index) {
+ this.addComponent(point, index);
+ },
+
+ /**
+ * APIMethod: removePoint
+ * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be removed
+ */
+ removePoint: function(point){
+ this.removeComponent(point);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Curve.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Curve
+ * A Curve is a MultiPoint, whose points are assumed to be connected. To
+ * this end, we provide a "getLength()" function, which iterates through
+ * the points, summing the distances between them.
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.MultiPoint>
+ */
+OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Curve
+ *
+ * Parameters:
+ * point - {Array(<OpenLayers.Geometry.Point>)}
+ */
+
+ /**
+ * APIMethod: getLength
+ *
+ * Returns:
+ * {Float} The length of the curve
+ */
+ getLength: function() {
+ var length = 0.0;
+ if ( this.components && (this.components.length > 1)) {
+ for(var i=1, len=this.components.length; i<len; i++) {
+ length += this.components[i-1].distanceTo(this.components[i]);
+ }
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var geom = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ geom = this.clone().transform(projection, gg);
+ }
+ }
+ var length = 0.0;
+ if(geom.components && (geom.components.length > 1)) {
+ var p1, p2;
+ for(var i=1, len=geom.components.length; i<len; i++) {
+ p1 = geom.components[i-1];
+ p2 = geom.components[i];
+ // this returns km and requires lon/lat properties
+ length += OpenLayers.Util.distVincenty(
+ {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
+ );
+ }
+ }
+ // convert to m
+ return length * 1000;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can
+ * never be less than two points long.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+ /**
+ * Constructor: OpenLayers.Geometry.LineString
+ * Create a new LineString geometry
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
+ * generate the linestring
+ *
+ */
+
+ /**
+ * APIMethod: removeComponent
+ * Only allows removal of a point if there are three or more points in
+ * the linestring. (otherwise the result would be just a single point)
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The point to be removed
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 2);
+ if (removed) {
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Test for instersection between two geometries. This is a cheapo
+ * implementation of the Bently-Ottmann algorigithm. It doesn't
+ * really keep track of a sweep line data structure. It is closer
+ * to the brute force method, except that segments are sorted and
+ * potential intersections are only calculated when bounding boxes
+ * intersect.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this geometry.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var type = geometry.CLASS_NAME;
+ if(type == "OpenLayers.Geometry.LineString" ||
+ type == "OpenLayers.Geometry.LinearRing" ||
+ type == "OpenLayers.Geometry.Point") {
+ var segs1 = this.getSortedSegments();
+ var segs2;
+ if(type == "OpenLayers.Geometry.Point") {
+ segs2 = [{
+ x1: geometry.x, y1: geometry.y,
+ x2: geometry.x, y2: geometry.y
+ }];
+ } else {
+ segs2 = geometry.getSortedSegments();
+ }
+ var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
+ seg2, seg2y1, seg2y2;
+ // sweep right
+ outer: for(var i=0, len=segs1.length; i<len; ++i) {
+ seg1 = segs1[i];
+ seg1x1 = seg1.x1;
+ seg1x2 = seg1.x2;
+ seg1y1 = seg1.y1;
+ seg1y2 = seg1.y2;
+ inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+ seg2 = segs2[j];
+ if(seg2.x1 > seg1x2) {
+ // seg1 still left of seg2
+ break;
+ }
+ if(seg2.x2 < seg1x1) {
+ // seg2 still left of seg1
+ continue;
+ }
+ seg2y1 = seg2.y1;
+ seg2y2 = seg2.y2;
+ if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
+ // seg2 above seg1
+ continue;
+ }
+ if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
+ // seg2 below seg1
+ continue;
+ }
+ if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
+ intersect = true;
+ break outer;
+ }
+ }
+ }
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * Method: getSortedSegments
+ *
+ * Returns:
+ * {Array} An array of segment objects. Segment objects have properties
+ * x1, y1, x2, and y2. The start point is represented by x1 and y1.
+ * The end point is represented by x2 and y2. Start and end are
+ * ordered so that x1 < x2.
+ */
+ getSortedSegments: function() {
+ var numSeg = this.components.length - 1;
+ var segments = new Array(numSeg), point1, point2;
+ for(var i=0; i<numSeg; ++i) {
+ point1 = this.components[i];
+ point2 = this.components[i + 1];
+ if(point1.x < point2.x) {
+ segments[i] = {
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y
+ };
+ } else {
+ segments[i] = {
+ x1: point2.x,
+ y1: point2.y,
+ x2: point1.x,
+ y2: point1.y
+ };
+ }
+ }
+ // more efficient to define this somewhere static
+ function byX1(seg1, seg2) {
+ return seg1.x1 - seg2.x1;
+ }
+ return segments.sort(byX1);
+ },
+
+ /**
+ * Method: splitWithSegment
+ * Split this geometry with the given segment.
+ *
+ * Parameters:
+ * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
+ * segment endpoint coordinates.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source segment must be within the
+ * tolerance distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of one of the source segment's
+ * endpoints will be assumed to occur at the endpoint.
+ *
+ * Returns:
+ * {Object} An object with *lines* and *points* properties. If the given
+ * segment intersects this linestring, the lines array will reference
+ * geometries that result from the split. The points array will contain
+ * all intersection points. Intersection points are sorted along the
+ * segment (in order from x1,y1 to x2,y2).
+ */
+ splitWithSegment: function(seg, options) {
+ var edge = !(options && options.edge === false);
+ var tolerance = options && options.tolerance;
+ var lines = [];
+ var verts = this.getVertices();
+ var points = [];
+ var intersections = [];
+ var split = false;
+ var vert1, vert2, point;
+ var node, vertex, target;
+ var interOptions = {point: true, tolerance: tolerance};
+ var result = null;
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ points.push(vert1.clone());
+ vert2 = verts[i+1];
+ target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
+ point = OpenLayers.Geometry.segmentsIntersect(
+ seg, target, interOptions
+ );
+ if(point instanceof OpenLayers.Geometry.Point) {
+ if((point.x === seg.x1 && point.y === seg.y1) ||
+ (point.x === seg.x2 && point.y === seg.y2) ||
+ point.equals(vert1) || point.equals(vert2)) {
+ vertex = true;
+ } else {
+ vertex = false;
+ }
+ if(vertex || edge) {
+ // push intersections different than the previous
+ if(!point.equals(intersections[intersections.length-1])) {
+ intersections.push(point.clone());
+ }
+ if(i === 0) {
+ if(point.equals(vert1)) {
+ continue;
+ }
+ }
+ if(point.equals(vert2)) {
+ continue;
+ }
+ split = true;
+ if(!point.equals(vert1)) {
+ points.push(point);
+ }
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ points = [point.clone()];
+ }
+ }
+ }
+ if(split) {
+ points.push(vert2.clone());
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ }
+ if(intersections.length > 0) {
+ // sort intersections along segment
+ var xDir = seg.x1 < seg.x2 ? 1 : -1;
+ var yDir = seg.y1 < seg.y2 ? 1 : -1;
+ result = {
+ lines: lines,
+ points: intersections.sort(function(p1, p2) {
+ return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
+ })
+ };
+ }
+ return result;
+ },
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(target, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var sourceSplit, targetSplit, sourceParts, targetParts;
+ if(target instanceof OpenLayers.Geometry.LineString) {
+ var verts = this.getVertices();
+ var vert1, vert2, seg, splits, lines, point;
+ var points = [];
+ sourceParts = [];
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ vert2 = verts[i+1];
+ seg = {
+ x1: vert1.x, y1: vert1.y,
+ x2: vert2.x, y2: vert2.y
+ };
+ targetParts = targetParts || [target];
+ if(mutual) {
+ points.push(vert1.clone());
+ }
+ for(var j=0; j<targetParts.length; ++j) {
+ splits = targetParts[j].splitWithSegment(seg, options);
+ if(splits) {
+ // splice in new features
+ lines = splits.lines;
+ if(lines.length > 0) {
+ lines.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, lines);
+ j += lines.length - 2;
+ }
+ if(mutual) {
+ for(var k=0, len=splits.points.length; k<len; ++k) {
+ point = splits.points[k];
+ if(!point.equals(vert1)) {
+ points.push(point);
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ if(point.equals(vert2)) {
+ points = [];
+ } else {
+ points = [point.clone()];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(mutual && sourceParts.length > 0 && points.length > 0) {
+ points.push(vert2.clone());
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ }
+ } else {
+ results = target.splitWith(this, options);
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetSplit || sourceSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ return geometry.split(this, options);
+
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices;
+ if(nodes === true) {
+ vertices = [
+ this.components[0],
+ this.components[this.components.length-1]
+ ];
+ } else if (nodes === false) {
+ vertices = this.components.slice(1, this.components.length-1);
+ } else {
+ vertices = this.components.slice();
+ }
+ return vertices;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best = {};
+ var min = Number.POSITIVE_INFINITY;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ var segs = this.getSortedSegments();
+ var x = geometry.x;
+ var y = geometry.y;
+ var seg;
+ for(var i=0, len=segs.length; i<len; ++i) {
+ seg = segs[i];
+ result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
+ if(result.distance < min) {
+ min = result.distance;
+ best = result;
+ if(min === 0) {
+ break;
+ }
+ } else {
+ // if distance increases and we cross y0 to the right of x0, no need to keep looking.
+ if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {
+ break;
+ }
+ }
+ }
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x, y0: best.y,
+ x1: x, y1: y
+ };
+ } else {
+ best = best.distance;
+ }
+ } else if(geometry instanceof OpenLayers.Geometry.LineString) {
+ var segs0 = this.getSortedSegments();
+ var segs1 = geometry.getSortedSegments();
+ var seg0, seg1, intersection, x0, y0;
+ var len1 = segs1.length;
+ var interOptions = {point: true};
+ outer: for(var i=0, len=segs0.length; i<len; ++i) {
+ seg0 = segs0[i];
+ x0 = seg0.x1;
+ y0 = seg0.y1;
+ for(var j=0; j<len1; ++j) {
+ seg1 = segs1[j];
+ intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
+ if(intersection) {
+ min = 0;
+ best = {
+ distance: 0,
+ x0: intersection.x, y0: intersection.y,
+ x1: intersection.x, y1: intersection.y
+ };
+ break outer;
+ } else {
+ result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
+ if(result.distance < min) {
+ min = result.distance;
+ best = {
+ distance: min,
+ x0: x0, y0: y0,
+ x1: result.x, y1: result.y
+ };
+ }
+ }
+ }
+ }
+ if(!details) {
+ best = best.distance;
+ }
+ if(min !== 0) {
+ // check the final vertex in this line's sorted segments
+ if(seg0) {
+ result = geometry.distanceTo(
+ new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
+ options
+ );
+ var dist = details ? result.distance : result;
+ if(dist < min) {
+ if(details) {
+ best = {
+ distance: min,
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0
+ };
+ } else {
+ best = dist;
+ }
+ }
+ }
+ }
+ } else {
+ best = geometry.distanceTo(this, options);
+ // swap since target comes from this line
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x1, y0: best.y1,
+ x1: best.x0, y1: best.y0
+ };
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: simplify
+ * This function will return a simplified LineString.
+ * Simplification is based on the Douglas-Peucker algorithm.
+ *
+ *
+ * Parameters:
+ * tolerance - {number} threshhold for simplification in map units
+ *
+ * Returns:
+ * {OpenLayers.Geometry.LineString} the simplified LineString
+ */
+ simplify: function(tolerance){
+ if (this && this !== null) {
+ var points = this.getVertices();
+ if (points.length < 3) {
+ return this;
+ }
+
+ var compareNumbers = function(a, b){
+ return (a-b);
+ };
+
+ /**
+ * Private function doing the Douglas-Peucker reduction
+ */
+ var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
+ var maxDistance = 0;
+ var indexFarthest = 0;
+
+ for (var index = firstPoint, distance; index < lastPoint; index++) {
+ distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ indexFarthest = index;
+ }
+ }
+
+ if (maxDistance > tolerance && indexFarthest != firstPoint) {
+ //Add the largest point that exceeds the tolerance
+ pointIndexsToKeep.push(indexFarthest);
+ douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
+ douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
+ }
+ };
+
+ /**
+ * Private function calculating the perpendicular distance
+ * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
+ */
+ var perpendicularDistance = function(point1, point2, point){
+ //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
+ //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
+ //Area = .5*Base*H *Solve for height
+ //Height = Area/.5/Base
+
+ var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
+ var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
+ var height = area / bottom * 2;
+
+ return height;
+ };
+
+ var firstPoint = 0;
+ var lastPoint = points.length - 1;
+ var pointIndexsToKeep = [];
+
+ //Add the first and last index to the keepers
+ pointIndexsToKeep.push(firstPoint);
+ pointIndexsToKeep.push(lastPoint);
+
+ //The first and the last point cannot be the same
+ while (points[firstPoint].equals(points[lastPoint])) {
+ lastPoint--;
+ //Addition: the first point not equal to first point in the LineString is kept as well
+ pointIndexsToKeep.push(lastPoint);
+ }
+
+ douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
+ var returnPoints = [];
+ pointIndexsToKeep.sort(compareNumbers);
+ for (var index = 0; index < pointIndexsToKeep.length; index++) {
+ returnPoints.push(points[pointIndexsToKeep[index]]);
+ }
+ return new OpenLayers.Geometry.LineString(returnPoints);
+
+ }
+ else {
+ return this;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LinearRing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ *
+ * A Linear Ring is a special LineString which is closed. It closes itself
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point.
+ *
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.LineString>
+ */
+OpenLayers.Geometry.LinearRing = OpenLayers.Class(
+ OpenLayers.Geometry.LineString, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.LinearRing
+ * Linear rings are constructed with an array of points. This array
+ * can represent a closed or open ring. If the ring is open (the last
+ * point does not equal the first point), the constructor will close
+ * the ring. If the ring is already closed (the last point does equal
+ * the first point), it will be left closed.
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} points
+ */
+
+ /**
+ * APIMethod: addComponent
+ * Adds a point to geometry components. If the point is to be added to
+ * the end of the components array and it is the same as the last point
+ * already in that array, the duplicate point is not added. This has
+ * the effect of closing the ring if it is not already closed, and
+ * doing the right thing if it is already closed. This behavior can
+ * be overridden by calling the method with a non-null index as the
+ * second argument.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * index - {Integer} Index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} Was the Point successfully added?
+ */
+ addComponent: function(point, index) {
+ var added = false;
+
+ //remove last point
+ var lastPoint = this.components.pop();
+
+ // given an index, add the point
+ // without an index only add non-duplicate points
+ if(index != null || !point.equals(lastPoint)) {
+ added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ arguments);
+ }
+
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponent
+ * Removes a point from geometry components.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 3);
+ if (removed) {
+ //remove last point
+ this.components.pop();
+
+ //remove our point
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i = 0, len=this.components.length; i<len - 1; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ if (this.components) {
+ var len = this.components.length;
+ if (len > 0 && len <= 2) {
+ return this.components[0].clone();
+ } else if (len > 2) {
+ var sumX = 0.0;
+ var sumY = 0.0;
+ var x0 = this.components[0].x;
+ var y0 = this.components[0].y;
+ var area = -1 * this.getArea();
+ if (area != 0) {
+ for (var i = 0; i < len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ }
+ var x = x0 + sumX / (6 * area);
+ var y = y0 + sumY / (6 * area);
+ } else {
+ for (var i = 0; i < len - 1; i++) {
+ sumX += this.components[i].x;
+ sumY += this.components[i].y;
+ }
+ var x = sumX / (len - 1);
+ var y = sumY / (len - 1);
+ }
+ return new OpenLayers.Geometry.Point(x, y);
+ } else {
+ return null;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getArea
+ * Note - The area is positive if the ring is oriented CW, otherwise
+ * it will be negative.
+ *
+ * Returns:
+ * {Float} The signed area for a ring.
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 2)) {
+ var sum = 0.0;
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sum += (b.x + c.x) * (c.y - b.y);
+ }
+ area = - sum / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth. Note that this area will be positive if ring is oriented
+ * clockwise, otherwise it will be negative.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate signed geodesic area of the polygon in square
+ * meters.
+ */
+ getGeodesicArea: function(projection) {
+ var ring = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ ring = this.clone().transform(projection, gg);
+ }
+ }
+ var area = 0.0;
+ var len = ring.components && ring.components.length;
+ if(len > 2) {
+ var p1, p2;
+ for(var i=0; i<len-1; i++) {
+ p1 = ring.components[i];
+ p2 = ring.components[i+1];
+ area += OpenLayers.Util.rad(p2.x - p1.x) *
+ (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
+ Math.sin(OpenLayers.Util.rad(p2.y)));
+ }
+ area = area * 6378137.0 * 6378137.0 / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a linear ring. For the case where a point
+ * is coincident with a linear ring edge, returns 1. Otherwise,
+ * returns boolean.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the linear ring. Returns 1 if
+ * the point is coincident with an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var approx = OpenLayers.Number.limitSigDigs;
+ var digs = 14;
+ var px = approx(point.x, digs);
+ var py = approx(point.y, digs);
+ function getX(y, x1, y1, x2, y2) {
+ return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
+ }
+ var numSeg = this.components.length - 1;
+ var start, end, x1, y1, x2, y2, cx, cy;
+ var crosses = 0;
+ for(var i=0; i<numSeg; ++i) {
+ start = this.components[i];
+ x1 = approx(start.x, digs);
+ y1 = approx(start.y, digs);
+ end = this.components[i + 1];
+ x2 = approx(end.x, digs);
+ y2 = approx(end.y, digs);
+
+ /**
+ * The following conditions enforce five edge-crossing rules:
+ * 1. points coincident with edges are considered contained;
+ * 2. an upward edge includes its starting endpoint, and
+ * excludes its final endpoint;
+ * 3. a downward edge excludes its starting endpoint, and
+ * includes its final endpoint;
+ * 4. horizontal edges are excluded; and
+ * 5. the edge-ray intersection point must be strictly right
+ * of the point P.
+ */
+ if(y1 == y2) {
+ // horizontal edge
+ if(py == y1) {
+ // point on horizontal line
+ if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
+ x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ // ignore other horizontal edges
+ continue;
+ }
+ cx = approx(getX(py, x1, y1, x2, y2), digs);
+ if(cx == px) {
+ // point on line
+ if(y1 < y2 && (py >= y1 && py <= y2) || // upward
+ y1 > y2 && (py <= y1 && py >= y2)) { // downward
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ if(cx <= px) {
+ // no crossing to the right
+ continue;
+ }
+ if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
+ // no crossing
+ continue;
+ }
+ if(y1 < y2 && (py >= y1 && py < y2) || // upward
+ y1 > y2 && (py < y1 && py >= y2)) { // downward
+ ++crosses;
+ }
+ }
+ var contained = (crosses == -1) ?
+ // on edge
+ 1 :
+ // even (out) or odd (in)
+ !!(crosses & 1);
+
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+ intersect = geometry.intersects(this);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
+ this, [geometry]
+ );
+ } else {
+ // check for component intersections
+ for(var i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = geometry.components[i].intersects(this);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
+/* ======================================================================
+ OpenLayers/Layer/HTTPRequest.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.HTTPRequest
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * Constant: URL_HASH_FACTOR
+ * {Float} Used to hash URL param strings for multi-WMS server selection.
+ * Set to the Golden Ratio per Knuth's recommendation.
+ */
+ URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2,
+
+ /**
+ * Property: url
+ * {Array(String) or String} This is either an array of url strings or
+ * a single url string.
+ */
+ url: null,
+
+ /**
+ * Property: params
+ * {Object} Hashtable of key/value parameters
+ */
+ params: null,
+
+ /**
+ * APIProperty: reproject
+ * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html
+ * for information on the replacement for this functionality.
+ * {Boolean} Whether layer should reproject itself based on base layer
+ * locations. This allows reprojection onto commercial layers.
+ * Default is false: Most layers can't reproject, but layers
+ * which can create non-square geographic pixels can, like WMS.
+ *
+ */
+ reproject: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.HTTPRequest
+ *
+ * Parameters:
+ * name - {String}
+ * url - {Array(String) or String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+ this.url = url;
+ if (!this.params) {
+ this.params = OpenLayers.Util.extend({}, params);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.url = null;
+ this.params = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this
+ * <OpenLayers.Layer.HTTPRequest>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.HTTPRequest(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: setUrl
+ *
+ * Parameters:
+ * newUrl - {String}
+ */
+ setUrl: function(newUrl) {
+ this.url = newUrl;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+ mergeNewParams:function(newParams) {
+ this.params = OpenLayers.Util.extend(this.params, newParams);
+ var ret = this.redraw();
+ if(this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "params"
+ });
+ }
+ return ret;
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Parameters:
+ * force - {Boolean} Force redraw by adding random parameter.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function(force) {
+ if (force) {
+ return this.mergeNewParams({"_olSalt": Math.random()});
+ } else {
+ return OpenLayers.Layer.prototype.redraw.apply(this, []);
+ }
+ },
+
+ /**
+ * Method: selectUrl
+ * selectUrl() implements the standard floating-point multiplicative
+ * hash function described by Knuth, and hashes the contents of the
+ * given param string into a float between 0 and 1. This float is then
+ * scaled to the size of the provided urls array, and used to select
+ * a URL.
+ *
+ * Parameters:
+ * paramString - {String}
+ * urls - {Array(String)}
+ *
+ * Returns:
+ * {String} An entry from the urls array, deterministically selected based
+ * on the paramString.
+ */
+ selectUrl: function(paramString, urls) {
+ var product = 1;
+ for (var i=0, len=paramString.length; i<len; i++) {
+ product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR;
+ product -= Math.floor(product);
+ }
+ return urls[Math.floor(product * urls.length)];
+ },
+
+ /**
+ * Method: getFullRequestString
+ * Combine url with layer's params and these newParams.
+ *
+ * does checking on the serverPath variable, allowing for cases when it
+ * is supplied with trailing ? or &, as well as cases where not.
+ *
+ * return in formatted string like this:
+ * "server?key1=value1&key2=value2&key3=value3"
+ *
+ * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+
+ // if not altUrl passed in, use layer's url
+ var url = altUrl || this.url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ //
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams =
+ OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ return OpenLayers.Util.urlAppend(url, paramsString);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
+});
+/* ======================================================================
+ OpenLayers/Tile.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile
+ * This is a class designed to designate a single tile, however
+ * it is explicitly designed to do relatively little. Tiles store
+ * information about themselves -- such as the URL that they are related
+ * to, and their size - but do not add themselves to the layer div
+ * automatically, for example. Create a new tile with the
+ * <OpenLayers.Tile> constructor, or a subclass.
+ *
+ * TBD 3.0 - remove reference to url in above paragraph
+ *
+ */
+OpenLayers.Tile = OpenLayers.Class({
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types:
+ * beforedraw - Triggered before the tile is drawn. Used to defer
+ * drawing to an animation queue. To defer drawing, listeners need
+ * to return false, which will abort drawing. The queue handler needs
+ * to call <draw>(true) to actually draw the tile.
+ * loadstart - Triggered when tile loading starts.
+ * loadend - Triggered when tile loading ends.
+ * loaderror - Triggered before the loadend event (i.e. when the tile is
+ * still hidden) if the tile could not be loaded.
+ * reload - Triggered when an already loading tile is reloaded.
+ * unload - Triggered before a tile is unloaded.
+ */
+ events: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ *
+ * This options can be set in the ``tileOptions`` option from
+ * <OpenLayers.Layer.Grid>. For example, to be notified of the
+ * ``loadend`` event of each tiles:
+ * (code)
+ * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', {
+ * tileOptions: {
+ * eventListeners: {
+ * 'loadend': function(evt) {
+ * // do something on loadend
+ * }
+ * }
+ * }
+ * });
+ * (end)
+ */
+ eventListeners: null,
+
+ /**
+ * Property: id
+ * {String} null
+ */
+ id: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>} layer the tile is attached to
+ */
+ layer: null,
+
+ /**
+ * Property: url
+ * {String} url of the request.
+ *
+ * TBD 3.0
+ * Deprecated. The base tile class does not need an url. This should be
+ * handled in subclasses. Does not belong here.
+ */
+ url: null,
+
+ /**
+ * APIProperty: bounds
+ * {<OpenLayers.Bounds>} null
+ */
+ bounds: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} null
+ */
+ size: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>} Top Left pixel of the tile
+ */
+ position: null,
+
+ /**
+ * Property: isLoading
+ * {Boolean} Is the tile loading?
+ */
+ isLoading: false,
+
+ /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
+ * there is no need for the base tile class to have a url.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile
+ * Constructor for a new <OpenLayers.Tile> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>}
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ this.layer = layer;
+ this.position = position.clone();
+ this.setBounds(bounds);
+ this.url = url;
+ if (size) {
+ this.size = size.clone();
+ }
+
+ //give the tile a unique id based on its BBOX.
+ this.id = OpenLayers.Util.createUniqueID("Tile_");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if (this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ },
+
+ /**
+ * Method: unload
+ * Call immediately before destroying if you are listening to tile
+ * events, so that counters are properly handled if tile is still
+ * loading at destroy-time. Will only fire an event if the tile is
+ * still loading.
+ */
+ unload: function() {
+ if (this.isLoading) {
+ this.isLoading = false;
+ this.events.triggerEvent("unload");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Nullify references to prevent circular references and memory leaks.
+ */
+ destroy:function() {
+ this.layer = null;
+ this.bounds = null;
+ this.size = null;
+ this.position = null;
+
+ if (this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: draw
+ * Clear whatever is currently in the tile, then return whether or not
+ * it should actually be re-drawn. This is an example implementation
+ * that can be overridden by subclasses. The minimum thing to do here
+ * is to call <clear> and return the result from <shouldDraw>.
+ *
+ * Parameters:
+ * force - {Boolean} If true, the tile will not be cleared and no beforedraw
+ * event will be fired. This is used for drawing tiles asynchronously
+ * after drawing has been cancelled by returning false from a beforedraw
+ * listener.
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn. Returns null
+ * if a beforedraw listener returned false.
+ */
+ draw: function(force) {
+ if (!force) {
+ //clear tile's contents and mark as not drawn
+ this.clear();
+ }
+ var draw = this.shouldDraw();
+ if (draw && !force && this.events.triggerEvent("beforedraw") === false) {
+ draw = null;
+ }
+ return draw;
+ },
+
+ /**
+ * Method: shouldDraw
+ * Return whether or not the tile should actually be (re-)drawn. The only
+ * case where we *wouldn't* want to draw the tile is if the tile is outside
+ * its layer's maxExtent
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn.
+ */
+ shouldDraw: function() {
+ var withinMaxExtent = false,
+ maxExtent = this.layer.maxExtent;
+ if (maxExtent) {
+ var map = this.layer.map;
+ var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent();
+ if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) {
+ withinMaxExtent = true;
+ }
+ }
+
+ return withinMaxExtent || this.layer.displayOutsideMaxExtent;
+ },
+
+ /**
+ * Method: setBounds
+ * Sets the bounds on this instance
+ *
+ * Parameters:
+ * bounds {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ bounds = bounds.clone();
+ if (this.layer.map.baseLayer.wrapDateLine) {
+ var worldExtent = this.layer.map.getMaxExtent(),
+ tolerance = this.layer.map.getResolution();
+ bounds = bounds.wrapDateLine(worldExtent, {
+ leftTolerance: tolerance,
+ rightTolerance: tolerance
+ });
+ }
+ this.bounds = bounds;
+ },
+
+ /**
+ * Method: moveTo
+ * Reposition the tile.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ * redraw - {Boolean} Call draw method on tile after moving.
+ * Default is true
+ */
+ moveTo: function (bounds, position, redraw) {
+ if (redraw == null) {
+ redraw = true;
+ }
+
+ this.setBounds(bounds);
+ this.position = position.clone();
+ if (redraw) {
+ this.draw();
+ }
+ },
+
+ /**
+ * Method: clear
+ * Clear the tile of any bounds/position-related data so that it can
+ * be reused in a new location.
+ */
+ clear: function(draw) {
+ // to be extended by subclasses
+ },
+
+ CLASS_NAME: "OpenLayers.Tile"
+});
+/* ======================================================================
+ OpenLayers/Tile/Image.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Animation.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.Image
+ * Instances of OpenLayers.Tile.Image are used to manage the image tiles
+ * used by various layers. Create a new image tile with the
+ * <OpenLayers.Tile.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to the <OpenLayers.Tile> events):
+ * beforeload - Triggered before an image is prepared for loading, when the
+ * url for the image is known already. Listeners may call <setImage> on
+ * the tile instance. If they do so, that image will be used and no new
+ * one will be created.
+ */
+
+ /**
+ * APIProperty: url
+ * {String} The URL of the image being requested. No default. Filled in by
+ * layer.getURL() function. May be modified by loadstart listeners.
+ */
+ url: null,
+
+ /**
+ * Property: imgDiv
+ * {HTMLImageElement} The image for this tile.
+ */
+ imgDiv: null,
+
+ /**
+ * Property: frame
+ * {DOMElement} The image element is appended to the frame. Any gutter on
+ * the image will be hidden behind the frame. If no gutter is set,
+ * this will be null.
+ */
+ frame: null,
+
+ /**
+ * Property: imageReloadAttempts
+ * {Integer} Attempts to load the image.
+ */
+ imageReloadAttempts: null,
+
+ /**
+ * Property: layerAlphaHack
+ * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
+ */
+ layerAlphaHack: null,
+
+ /**
+ * Property: asyncRequestId
+ * {Integer} ID of an request to see if request is still valid. This is a
+ * number which increments by 1 for each asynchronous request.
+ */
+ asyncRequestId: null,
+
+ /**
+ * APIProperty: maxGetUrlLength
+ * {Number} If set, requests that would result in GET urls with more
+ * characters than the number provided will be made using form-encoded
+ * HTTP POST. It is good practice to avoid urls that are longer than 2048
+ * characters.
+ *
+ * Caution:
+ * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
+ * Opera versions do not fully support this option. On all browsers,
+ * transition effects are not supported if POST requests are used.
+ */
+ maxGetUrlLength: null,
+
+ /**
+ * Property: canvasContext
+ * {CanvasRenderingContext2D} A canvas context associated with
+ * the tile image.
+ */
+ canvasContext: null,
+
+ /**
+ * APIProperty: crossOriginKeyword
+ * The value of the crossorigin keyword to use when loading images. This is
+ * only relevant when using <getCanvasContext> for tiles from remote
+ * origins and should be set to either 'anonymous' or 'use-credentials'
+ * for servers that send Access-Control-Allow-Origin headers with their
+ * tiles.
+ */
+ crossOriginKeyword: null,
+
+ /** TBD 3.0 - reorder the parameters to the init function to remove
+ * URL. the getUrl() function on the layer gets called on
+ * each draw(), so no need to specify it here.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile.Image
+ * Constructor for a new <OpenLayers.Tile.Image> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+
+ this.url = url; //deprecated remove me
+
+ this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
+
+ if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
+ // only create frame if it's needed
+ this.frame = document.createElement("div");
+ this.frame.style.position = "absolute";
+ this.frame.style.overflow = "hidden";
+ }
+ if (this.maxGetUrlLength != null) {
+ OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.imgDiv) {
+ this.clear();
+ this.imgDiv = null;
+ this.frame = null;
+ }
+ // don't handle async requests any more
+ this.asyncRequestId = null;
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and draw it.
+ *
+ * Returns:
+ * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
+ * false.
+ */
+ draw: function() {
+ var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+ if (shouldDraw) {
+ // The layer's reproject option is deprecated.
+ if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
+ // getBoundsFromBaseLayer is defined in deprecated.js.
+ this.bounds = this.getBoundsFromBaseLayer(this.position);
+ }
+ if (this.isLoading) {
+ //if we're already loading, send 'reload' instead of 'loadstart'.
+ this._loadEvent = "reload";
+ } else {
+ this.isLoading = true;
+ this._loadEvent = "loadstart";
+ }
+ this.renderTile();
+ this.positionTile();
+ } else if (shouldDraw === false) {
+ this.unload();
+ }
+ return shouldDraw;
+ },
+
+ /**
+ * Method: renderTile
+ * Internal function to actually initialize the image tile,
+ * position it correctly, and set its url.
+ */
+ renderTile: function() {
+ if (this.layer.async) {
+ // Asynchronous image requests call the asynchronous getURL method
+ // on the layer to fetch an image that covers 'this.bounds'.
+ var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
+ this.layer.getURLasync(this.bounds, function(url) {
+ if (id == this.asyncRequestId) {
+ this.url = url;
+ this.initImage();
+ }
+ }, this);
+ } else {
+ // synchronous image requests get the url immediately.
+ this.url = this.layer.getURL(this.bounds);
+ this.initImage();
+ }
+ },
+
+ /**
+ * Method: positionTile
+ * Using the properties currenty set on the layer, position the tile correctly.
+ * This method is used both by the async and non-async versions of the Tile.Image
+ * code.
+ */
+ positionTile: function() {
+ var style = this.getTile().style,
+ size = this.frame ? this.size :
+ this.layer.getImageSize(this.bounds),
+ ratio = 1;
+ if (this.layer instanceof OpenLayers.Layer.Grid) {
+ ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
+ }
+ style.left = this.position.x + "px";
+ style.top = this.position.y + "px";
+ style.width = Math.round(ratio * size.w) + "px";
+ style.height = Math.round(ratio * size.h) + "px";
+ },
+
+ /**
+ * Method: clear
+ * Remove the tile from the DOM, clear it of any image related data so that
+ * it can be reused in a new location.
+ */
+ clear: function() {
+ OpenLayers.Tile.prototype.clear.apply(this, arguments);
+ var img = this.imgDiv;
+ if (img) {
+ var tile = this.getTile();
+ if (tile.parentNode === this.layer.div) {
+ this.layer.div.removeChild(tile);
+ }
+ this.setImgSrc();
+ if (this.layerAlphaHack === true) {
+ img.style.filter = "";
+ }
+ OpenLayers.Element.removeClass(img, "olImageLoadError");
+ }
+ this.canvasContext = null;
+ },
+
+ /**
+ * Method: getImage
+ * Returns or creates and returns the tile image.
+ */
+ getImage: function() {
+ if (!this.imgDiv) {
+ this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
+
+ var style = this.imgDiv.style;
+ if (this.frame) {
+ var left = 0, top = 0;
+ if (this.layer.gutter) {
+ left = this.layer.gutter / this.layer.tileSize.w * 100;
+ top = this.layer.gutter / this.layer.tileSize.h * 100;
+ }
+ style.left = -left + "%";
+ style.top = -top + "%";
+ style.width = (2 * left + 100) + "%";
+ style.height = (2 * top + 100) + "%";
+ }
+ style.visibility = "hidden";
+ style.opacity = 0;
+ if (this.layer.opacity < 1) {
+ style.filter = 'alpha(opacity=' +
+ (this.layer.opacity * 100) +
+ ')';
+ }
+ style.position = "absolute";
+ if (this.layerAlphaHack) {
+ // move the image out of sight
+ style.paddingTop = style.height;
+ style.height = "0";
+ style.width = "100%";
+ }
+ if (this.frame) {
+ this.frame.appendChild(this.imgDiv);
+ }
+ }
+
+ return this.imgDiv;
+ },
+
+ /**
+ * APIMethod: setImage
+ * Sets the image element for this tile. This method should only be called
+ * from beforeload listeners.
+ *
+ * Parameters
+ * img - {HTMLImageElement} The image to use for this tile.
+ */
+ setImage: function(img) {
+ this.imgDiv = img;
+ },
+
+ /**
+ * Method: initImage
+ * Creates the content for the frame on the tile.
+ */
+ initImage: function() {
+ if (!this.url && !this.imgDiv) {
+ // fast path out - if there is no tile url and no previous image
+ this.isLoading = false;
+ return;
+ }
+ this.events.triggerEvent('beforeload');
+ this.layer.div.appendChild(this.getTile());
+ this.events.triggerEvent(this._loadEvent);
+ var img = this.getImage();
+ var src = img.getAttribute('src') || '';
+ if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
+ this._loadTimeout = window.setTimeout(
+ OpenLayers.Function.bind(this.onImageLoad, this), 0
+ );
+ } else {
+ this.stopLoading();
+ if (this.crossOriginKeyword) {
+ img.removeAttribute("crossorigin");
+ }
+ OpenLayers.Event.observe(img, "load",
+ OpenLayers.Function.bind(this.onImageLoad, this)
+ );
+ OpenLayers.Event.observe(img, "error",
+ OpenLayers.Function.bind(this.onImageError, this)
+ );
+ this.imageReloadAttempts = 0;
+ this.setImgSrc(this.url);
+ }
+ },
+
+ /**
+ * Method: setImgSrc
+ * Sets the source for the tile image
+ *
+ * Parameters:
+ * url - {String} or undefined to hide the image
+ */
+ setImgSrc: function(url) {
+ var img = this.imgDiv;
+ if (url) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ // don't set crossOrigin if the url is a data URL
+ if (this.crossOriginKeyword) {
+ if (url.substr(0, 5) !== 'data:') {
+ img.setAttribute("crossorigin", this.crossOriginKeyword);
+ } else {
+ img.removeAttribute("crossorigin");
+ }
+ }
+ img.src = url;
+ } else {
+ // Remove reference to the image, and leave it to the browser's
+ // caching and garbage collection.
+ this.stopLoading();
+ this.imgDiv = null;
+ if (img.parentNode) {
+ img.parentNode.removeChild(img);
+ }
+ }
+ },
+
+ /**
+ * Method: getTile
+ * Get the tile's markup.
+ *
+ * Returns:
+ * {DOMElement} The tile's markup
+ */
+ getTile: function() {
+ return this.frame ? this.frame : this.getImage();
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
+ * of the tile's markup, because we want to avoid the reloading of the
+ * image. So we clone the frame, and steal the image from the tile.
+ *
+ * Returns:
+ * {DOMElement} The markup, or undefined if the tile has no image
+ * or if it's currently loading.
+ */
+ createBackBuffer: function() {
+ if (!this.imgDiv || this.isLoading) {
+ return;
+ }
+ var backBuffer;
+ if (this.frame) {
+ backBuffer = this.frame.cloneNode(false);
+ backBuffer.appendChild(this.imgDiv);
+ } else {
+ backBuffer = this.imgDiv;
+ }
+ this.imgDiv = null;
+ return backBuffer;
+ },
+
+ /**
+ * Method: onImageLoad
+ * Handler for the image onload event
+ */
+ onImageLoad: function() {
+ var img = this.imgDiv;
+ this.stopLoading();
+ img.style.visibility = 'inherit';
+ img.style.opacity = this.layer.opacity;
+ this.isLoading = false;
+ this.canvasContext = null;
+ this.events.triggerEvent("loadend");
+
+ if (this.layerAlphaHack === true) {
+ img.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
+ img.src + "', sizingMethod='scale')";
+ }
+ },
+
+ /**
+ * Method: onImageError
+ * Handler for the image onerror event
+ */
+ onImageError: function() {
+ var img = this.imgDiv;
+ if (img.src != null) {
+ this.imageReloadAttempts++;
+ if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
+ this.setImgSrc(this.layer.getURL(this.bounds));
+ } else {
+ OpenLayers.Element.addClass(img, "olImageLoadError");
+ this.events.triggerEvent("loaderror");
+ this.onImageLoad();
+ }
+ }
+ },
+
+ /**
+ * Method: stopLoading
+ * Stops a loading sequence so <onImageLoad> won't be executed.
+ */
+ stopLoading: function() {
+ OpenLayers.Event.stopObservingElement(this.imgDiv);
+ window.clearTimeout(this._loadTimeout);
+ delete this._loadTimeout;
+ },
+
+ /**
+ * APIMethod: getCanvasContext
+ * Returns a canvas context associated with the tile image (with
+ * the image drawn on it).
+ * Returns undefined if the browser does not support canvas, if
+ * the tile has no image or if it's currently loading.
+ *
+ * The function returns a canvas context instance but the
+ * underlying canvas is still available in the 'canvas' property:
+ * (code)
+ * var context = tile.getCanvasContext();
+ * if (context) {
+ * var data = context.canvas.toDataURL('image/jpeg');
+ * }
+ * (end)
+ *
+ * Returns:
+ * {Boolean}
+ */
+ getCanvasContext: function() {
+ if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
+ if (!this.canvasContext) {
+ var canvas = document.createElement("canvas");
+ canvas.width = this.size.w;
+ canvas.height = this.size.h;
+ this.canvasContext = canvas.getContext("2d");
+ this.canvasContext.drawImage(this.imgDiv, 0, 0);
+ }
+ return this.canvasContext;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.Image"
+
+});
+
+/**
+ * Constant: OpenLayers.Tile.Image.IMAGE
+ * {HTMLImageElement} The image for a tile.
+ */
+OpenLayers.Tile.Image.IMAGE = (function() {
+ var img = new Image();
+ img.className = "olTileImage";
+ // avoid image gallery menu in IE6
+ img.galleryImg = "no";
+ return img;
+}());
+
+/* ======================================================================
+ OpenLayers/Layer/Grid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/HTTPRequest.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Grid
+ * Base class for layers that use a lattice of tiles. Create a new grid
+ * layer with the <OpenLayers.Layer.Grid> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.HTTPRequest>
+ */
+OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>}
+ */
+ tileSize: null,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} If the <tileOrigin> property is not provided, the tile origin
+ * will be derived from the layer's <maxExtent>. The corner of the
+ * <maxExtent> used is determined by this property. Acceptable values
+ * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
+ * (bottom right). Default is "bl".
+ */
+ tileOriginCorner: "bl",
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
+ * If provided, requests for tiles at all resolutions will be aligned
+ * with this location (no tiles shall overlap this location). If
+ * not provided, the grid of tiles will be aligned with the layer's
+ * <maxExtent>. Default is ``null``.
+ */
+ tileOrigin: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer, if supported by the tile class.
+ */
+ tileOptions: null,
+
+ /**
+ * APIProperty: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is OpenLayers.Tile.Image.
+ */
+ tileClass: OpenLayers.Tile.Image,
+
+ /**
+ * Property: grid
+ * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is
+ * an array of tiles.
+ */
+ grid: null,
+
+ /**
+ * APIProperty: singleTile
+ * {Boolean} Moves the layer into single-tile mode, meaning that one tile
+ * will be loaded. The tile's size will be determined by the 'ratio'
+ * property. When the tile is dragged such that it does not cover the
+ * entire viewport, it is reloaded.
+ */
+ singleTile: false,
+
+ /** APIProperty: ratio
+ * {Float} Used only when in single-tile mode, this specifies the
+ * ratio of the size of the single tile to the size of the map.
+ * Default value is 1.5.
+ */
+ ratio: 1.5,
+
+ /**
+ * APIProperty: buffer
+ * {Integer} Used only when in gridded mode, this specifies the number of
+ * extra rows and colums of tiles on each side which will
+ * surround the minimum grid tiles to cover the map.
+ * For very slow loading layers, a larger value may increase
+ * performance somewhat when dragging, but will increase bandwidth
+ * use significantly.
+ */
+ buffer: 0,
+
+ /**
+ * APIProperty: transitionEffect
+ * {String} The transition effect to use when the map is zoomed.
+ * Two posible values:
+ *
+ * "resize" - Existing tiles are resized on zoom to provide a visual
+ * effect of the zoom having taken place immediately. As the
+ * new tiles become available, they are drawn on top of the
+ * resized tiles (this is the default setting).
+ * "map-resize" - Existing tiles are resized on zoom and placed below the
+ * base layer. New tiles for the base layer will cover existing tiles.
+ * This setting is recommended when having an overlay duplicated during
+ * the transition is undesirable (e.g. street labels or big transparent
+ * fills).
+ * null - No transition effect.
+ *
+ * Using "resize" on non-opaque layers can cause undesired visual
+ * effects. Set transitionEffect to null in this case.
+ */
+ transitionEffect: "resize",
+
+ /**
+ * APIProperty: numLoadingTiles
+ * {Integer} How many tiles are still loading?
+ */
+ numLoadingTiles: 0,
+
+ /**
+ * Property: serverResolutions
+ * {Array(Number}} This property is documented in subclasses as
+ * an API property.
+ */
+ serverResolutions: null,
+
+ /**
+ * Property: loading
+ * {Boolean} Indicates if tiles are being loaded.
+ */
+ loading: false,
+
+ /**
+ * Property: backBuffer
+ * {DOMElement} The back buffer.
+ */
+ backBuffer: null,
+
+ /**
+ * Property: gridResolution
+ * {Number} The resolution of the current grid. Used for backbuffer and
+ * client zoom. This property is updated every time the grid is
+ * initialized.
+ */
+ gridResolution: null,
+
+ /**
+ * Property: backBufferResolution
+ * {Number} The resolution of the current back buffer. This property is
+ * updated each time a back buffer is created.
+ */
+ backBufferResolution: null,
+
+ /**
+ * Property: backBufferLonLat
+ * {Object} The top-left corner of the current back buffer. Includes lon
+ * and lat properties. This object is updated each time a back buffer
+ * is created.
+ */
+ backBufferLonLat: null,
+
+ /**
+ * Property: backBufferTimerId
+ * {Number} The id of the back buffer timer. This timer is used to
+ * delay the removal of the back buffer, thereby preventing
+ * flash effects caused by tile animation.
+ */
+ backBufferTimerId: null,
+
+ /**
+ * APIProperty: removeBackBufferDelay
+ * {Number} Delay for removing the backbuffer when all tiles have finished
+ * loading. Can be set to 0 when no css opacity transitions for the
+ * olTileImage class are used. Default is 0 for <singleTile> layers,
+ * 2500 for tiled layers. See <className> for more information on
+ * tile animation.
+ */
+ removeBackBufferDelay: null,
+
+ /**
+ * APIProperty: className
+ * {String} Name of the class added to the layer div. If not set in the
+ * options passed to the constructor then className defaults to
+ * "olLayerGridSingleTile" for single tile layers (see <singleTile>),
+ * and "olLayerGrid" for non single tile layers.
+ *
+ * Note:
+ *
+ * The displaying of tiles is not animated by default for single tile
+ * layers - OpenLayers' default theme (style.css) includes this:
+ * (code)
+ * .olLayerGrid .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * To animate tile displaying for any grid layer the following
+ * CSS rule can be used:
+ * (code)
+ * .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * In that case, to avoid flash effects, <removeBackBufferDelay>
+ * should not be zero.
+ */
+ className: null,
+
+ /**
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported event types:
+ * addtile - Triggered when a tile is added to this layer. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that has been added.
+ * tileloadstart - Triggered when a tile starts loading. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that starts loading.
+ * tileloaded - Triggered when each new tile is
+ * loaded, as a means of progress update to listeners.
+ * listeners can access 'numLoadingTiles' if they wish to keep
+ * track of the loading progress. Listeners are called with an object
+ * with a 'tile' property as first argument, making the loaded tile
+ * available to the listener, and an 'aborted' property, which will be
+ * true when loading was aborted and no tile data is available.
+ * tileerror - Triggered before the tileloaded event (i.e. when the tile is
+ * still hidden) if a tile failed to load. Listeners receive an object
+ * as first argument, which has a tile property that references the
+ * tile that could not be loaded.
+ * retile - Triggered when the layer recreates its tile grid.
+ */
+
+ /**
+ * Property: gridLayout
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ gridLayout: null,
+
+ /**
+ * Property: rowSign
+ * {Number} 1 for grids starting at the top, -1 for grids starting at the
+ * bottom. This is used for several grid index and offset calculations.
+ */
+ rowSign: null,
+
+ /**
+ * Property: transitionendEvents
+ * {Array} Event names for transitionend
+ */
+ transitionendEvents: [
+ 'transitionend', 'webkitTransitionEnd', 'otransitionend',
+ 'oTransitionEnd'
+ ],
+
+ /**
+ * Constructor: OpenLayers.Layer.Grid
+ * Create a new grid layer
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
+ arguments);
+ this.grid = [];
+ this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);
+
+ this.initProperties();
+
+ this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
+ },
+
+ /**
+ * Method: initProperties
+ * Set any properties that depend on the value of singleTile.
+ * Currently sets removeBackBufferDelay and className
+ */
+ initProperties: function() {
+ if (this.options.removeBackBufferDelay === undefined) {
+ this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
+ }
+
+ if (this.options.className === undefined) {
+ this.className = this.singleTile ? 'olLayerGridSingleTile' :
+ 'olLayerGrid';
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
+ OpenLayers.Element.addClass(this.div, this.className);
+ },
+
+ /**
+ * Method: removeMap
+ * Called when the layer is removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ removeMap: function(map) {
+ this.removeBackBuffer();
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct the layer and clear the grid.
+ */
+ destroy: function() {
+ this.removeBackBuffer();
+ this.clearGrid();
+
+ this.grid = null;
+ this.tileSize = null;
+ OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Refetches tiles with new params merged, keeping a backbuffer. Each
+ * loading new tile will have a css class of '.olTileReplacing'. If a
+ * stylesheet applies a 'display: none' style to that class, any fade-in
+ * transition will not apply, and backbuffers for each tile will be removed
+ * as soon as the tile is loaded.
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+
+ /**
+ * Method: clearGrid
+ * Go through and remove all tiles from the grid, calling
+ * destroy() on each of them to kill circular references
+ */
+ clearGrid:function() {
+ if (this.grid) {
+ for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
+ var row = this.grid[iRow];
+ for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
+ var tile = row[iCol];
+ this.destroyTile(tile);
+ }
+ }
+ this.grid = [];
+ this.gridResolution = null;
+ this.gridLayout = null;
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+ var singleTileChanged = newOptions.singleTile !== undefined &&
+ newOptions.singleTile !== this.singleTile;
+ OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
+ if (this.map && singleTileChanged) {
+ this.initProperties();
+ this.clearGrid();
+ this.tileSize = this.options.tileSize;
+ this.setTileSize();
+ this.moveTo(null, true);
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Grid(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ if (this.tileSize != null) {
+ obj.tileSize = this.tileSize.clone();
+ }
+
+ // we do not want to copy reference to grid, so we make a new array
+ obj.grid = [];
+ obj.gridResolution = null;
+ // same for backbuffer
+ obj.backBuffer = null;
+ obj.backBufferTimerId = null;
+ obj.loading = false;
+ obj.numLoadingTiles = 0;
+
+ return obj;
+ },
+
+ /**
+ * Method: moveTo
+ * This function is called whenever the map is moved. All the moving
+ * of actual 'tiles' is done by the map, but moveTo's role is to accept
+ * a bounds and make sure the data that that bounds requires is pre-loaded.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+
+ OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
+
+ bounds = bounds || this.map.getExtent();
+
+ if (bounds != null) {
+
+ // if grid is empty or zoom has changed, we *must* re-tile
+ var forceReTile = !this.grid.length || zoomChanged;
+
+ // total bounds of the tiles
+ var tilesBounds = this.getTilesBounds();
+
+ // the new map resolution
+ var resolution = this.map.getResolution();
+
+ // the server-supported resolution for the new map resolution
+ var serverResolution = this.getServerResolution(resolution);
+
+ if (this.singleTile) {
+
+ // We want to redraw whenever even the slightest part of the
+ // current bounds is not contained by our tile.
+ // (thus, we do not specify partial -- its default is false)
+
+ if ( forceReTile ||
+ (!dragging && !tilesBounds.containsBounds(bounds))) {
+
+ // In single tile mode with no transition effect, we insert
+ // a non-scaled backbuffer when the layer is moved. But if
+ // a zoom occurs right after a move, i.e. before the new
+ // image is received, we need to remove the backbuffer, or
+ // an ill-positioned image will be visible during the zoom
+ // transition.
+
+ if(zoomChanged && this.transitionEffect !== 'resize') {
+ this.removeBackBuffer();
+ }
+
+ if(!zoomChanged || this.transitionEffect === 'resize') {
+ this.applyBackBuffer(resolution);
+ }
+
+ this.initSingleTile(bounds);
+ }
+ } else {
+
+ // if the bounds have changed such that they are not even
+ // *partially* contained by our tiles (e.g. when user has
+ // programmatically panned to the other side of the earth on
+ // zoom level 18), then moveGriddedTiles could potentially have
+ // to run through thousands of cycles, so we want to reTile
+ // instead (thus, partial true).
+ forceReTile = forceReTile ||
+ !tilesBounds.intersectsBounds(bounds, {
+ worldBounds: this.map.baseLayer.wrapDateLine &&
+ this.map.getMaxExtent()
+ });
+
+ if(forceReTile) {
+ if(zoomChanged && (this.transitionEffect === 'resize' ||
+ this.gridResolution === resolution)) {
+ this.applyBackBuffer(resolution);
+ }
+ this.initGriddedTiles(bounds);
+ } else {
+ this.moveGriddedTiles();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getTileData
+ * Given a map location, retrieve a tile and the pixel offset within that
+ * tile corresponding to the location. If there is not an existing
+ * tile in the grid that covers the given location, null will be
+ * returned.
+ *
+ * Parameters:
+ * loc - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
+ * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
+ * offset from top left).
+ */
+ getTileData: function(loc) {
+ var data = null,
+ x = loc.lon,
+ y = loc.lat,
+ numRows = this.grid.length;
+
+ if (this.map && numRows) {
+ var res = this.map.getResolution(),
+ tileWidth = this.tileSize.w,
+ tileHeight = this.tileSize.h,
+ bounds = this.grid[0][0].bounds,
+ left = bounds.left,
+ top = bounds.top;
+
+ if (x < left) {
+ // deal with multiple worlds
+ if (this.map.baseLayer.wrapDateLine) {
+ var worldWidth = this.map.getMaxExtent().getWidth();
+ var worldsAway = Math.ceil((left - x) / worldWidth);
+ x += worldWidth * worldsAway;
+ }
+ }
+ // tile distance to location (fractional number of tiles);
+ var dtx = (x - left) / (res * tileWidth);
+ var dty = (top - y) / (res * tileHeight);
+ // index of tile in grid
+ var col = Math.floor(dtx);
+ var row = Math.floor(dty);
+ if (row >= 0 && row < numRows) {
+ var tile = this.grid[row][col];
+ if (tile) {
+ data = {
+ tile: tile,
+ // pixel index within tile
+ i: Math.floor((dtx - col) * tileWidth),
+ j: Math.floor((dty - row) * tileHeight)
+ };
+ }
+ }
+ }
+ return data;
+ },
+
+ /**
+ * Method: destroyTile
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ destroyTile: function(tile) {
+ this.removeTileMonitoringHooks(tile);
+ tile.destroy();
+ },
+
+ /**
+ * Method: getServerResolution
+ * Return the closest server-supported resolution.
+ *
+ * Parameters:
+ * resolution - {Number} The base resolution. If undefined the
+ * map resolution is used.
+ *
+ * Returns:
+ * {Number} The closest server resolution value.
+ */
+ getServerResolution: function(resolution) {
+ var distance = Number.POSITIVE_INFINITY;
+ resolution = resolution || this.map.getResolution();
+ if(this.serverResolutions &&
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
+ var i, newDistance, newResolution, serverResolution;
+ for(i=this.serverResolutions.length-1; i>= 0; i--) {
+ newResolution = this.serverResolutions[i];
+ newDistance = Math.abs(newResolution - resolution);
+ if (newDistance > distance) {
+ break;
+ }
+ distance = newDistance;
+ serverResolution = newResolution;
+ }
+ resolution = serverResolution;
+ }
+ return resolution;
+ },
+
+ /**
+ * Method: getServerZoom
+ * Return the zoom value corresponding to the best matching server
+ * resolution, taking into account <serverResolutions> and <zoomOffset>.
+ *
+ * Returns:
+ * {Number} The closest server supported zoom. This is not the map zoom
+ * level, but an index of the server's resolutions array.
+ */
+ getServerZoom: function() {
+ var resolution = this.getServerResolution();
+ return this.serverResolutions ?
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
+ this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
+ },
+
+ /**
+ * Method: applyBackBuffer
+ * Create, insert, scale and position a back buffer for the layer.
+ *
+ * Parameters:
+ * resolution - {Number} The resolution to transition to.
+ */
+ applyBackBuffer: function(resolution) {
+ if(this.backBufferTimerId !== null) {
+ this.removeBackBuffer();
+ }
+ var backBuffer = this.backBuffer;
+ if(!backBuffer) {
+ backBuffer = this.createBackBuffer();
+ if(!backBuffer) {
+ return;
+ }
+ if (resolution === this.gridResolution) {
+ this.div.insertBefore(backBuffer, this.div.firstChild);
+ } else {
+ this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
+ }
+ this.backBuffer = backBuffer;
+
+ // set some information in the instance for subsequent
+ // calls to applyBackBuffer where the same back buffer
+ // is reused
+ var topLeftTileBounds = this.grid[0][0].bounds;
+ this.backBufferLonLat = {
+ lon: topLeftTileBounds.left,
+ lat: topLeftTileBounds.top
+ };
+ this.backBufferResolution = this.gridResolution;
+ }
+
+ var ratio = this.backBufferResolution / resolution;
+
+ // scale the tiles inside the back buffer
+ var tiles = backBuffer.childNodes, tile;
+ for (var i=tiles.length-1; i>=0; --i) {
+ tile = tiles[i];
+ tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px';
+ tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px';
+ tile.style.width = Math.round(ratio * tile._w) + 'px';
+ tile.style.height = Math.round(ratio * tile._h) + 'px';
+ }
+
+ // and position it (based on the grid's top-left corner)
+ var position = this.getViewPortPxFromLonLat(
+ this.backBufferLonLat, resolution);
+ var leftOffset = this.map.layerContainerOriginPx.x;
+ var topOffset = this.map.layerContainerOriginPx.y;
+ backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
+ backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a back buffer.
+ *
+ * Returns:
+ * {DOMElement} The DOM element for the back buffer, undefined if the
+ * grid isn't initialized yet.
+ */
+ createBackBuffer: function() {
+ var backBuffer;
+ if(this.grid.length > 0) {
+ backBuffer = document.createElement('div');
+ backBuffer.id = this.div.id + '_bb';
+ backBuffer.className = 'olBackBuffer';
+ backBuffer.style.position = 'absolute';
+ var map = this.map;
+ backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
+ this.getZIndex() - 1 :
+ // 'map-resize':
+ map.Z_INDEX_BASE.BaseLayer -
+ (map.getNumLayers() - map.getLayerIndex(this));
+ for(var i=0, lenI=this.grid.length; i<lenI; i++) {
+ for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
+ var tile = this.grid[i][j],
+ markup = this.grid[i][j].createBackBuffer();
+ if (markup) {
+ markup._i = i;
+ markup._j = j;
+ markup._w = tile.size.w;
+ markup._h = tile.size.h;
+ markup.id = tile.id + '_bb';
+ backBuffer.appendChild(markup);
+ }
+ }
+ }
+ }
+ return backBuffer;
+ },
+
+ /**
+ * Method: removeBackBuffer
+ * Remove back buffer from DOM.
+ */
+ removeBackBuffer: function() {
+ if (this._transitionElement) {
+ for (var i=this.transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.stopObserving(this._transitionElement,
+ this.transitionendEvents[i], this._removeBackBuffer);
+ }
+ delete this._transitionElement;
+ }
+ if(this.backBuffer) {
+ if (this.backBuffer.parentNode) {
+ this.backBuffer.parentNode.removeChild(this.backBuffer);
+ }
+ this.backBuffer = null;
+ this.backBufferResolution = null;
+ if(this.backBufferTimerId !== null) {
+ window.clearTimeout(this.backBufferTimerId);
+ this.backBufferTimerId = null;
+ }
+ }
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ if (!this.singleTile) {
+ this.moveGriddedTiles();
+ }
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Check if we are in singleTile mode and if so, set the size as a ratio
+ * of the map size (as specified by the layer's 'ratio' property).
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ if (this.singleTile) {
+ size = this.map.getSize();
+ size.h = parseInt(size.h * this.ratio, 10);
+ size.w = parseInt(size.w * this.ratio, 10);
+ }
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
+ },
+
+ /**
+ * APIMethod: getTilesBounds
+ * Return the bounds of the tile grid.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
+ * currently loaded tiles (including those partially or not at all seen
+ * onscreen).
+ */
+ getTilesBounds: function() {
+ var bounds = null;
+
+ var length = this.grid.length;
+ if (length) {
+ var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
+ width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
+ height = this.grid.length * bottomLeftTileBounds.getHeight();
+
+ bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left,
+ bottomLeftTileBounds.bottom,
+ bottomLeftTileBounds.left + width,
+ bottomLeftTileBounds.bottom + height);
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: initSingleTile
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initSingleTile: function(bounds) {
+ this.events.triggerEvent("retile");
+
+ //determine new tile bounds
+ var center = bounds.getCenterLonLat();
+ var tileWidth = bounds.getWidth() * this.ratio;
+ var tileHeight = bounds.getHeight() * this.ratio;
+
+ var tileBounds =
+ new OpenLayers.Bounds(center.lon - (tileWidth/2),
+ center.lat - (tileHeight/2),
+ center.lon + (tileWidth/2),
+ center.lat + (tileHeight/2));
+
+ var px = this.map.getLayerPxFromLonLat({
+ lon: tileBounds.left,
+ lat: tileBounds.top
+ });
+
+ if (!this.grid.length) {
+ this.grid[0] = [];
+ }
+
+ var tile = this.grid[0][0];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+
+ this.addTileMonitoringHooks(tile);
+ tile.draw();
+ this.grid[0][0] = tile;
+ } else {
+ tile.moveTo(tileBounds, px);
+ }
+
+ //remove all but our single tile
+ this.removeExcessTiles(1,1);
+
+ // store the resolution of the grid
+ this.gridResolution = this.getServerResolution();
+ },
+
+ /**
+ * Method: calculateGridLayout
+ * Generate parameters for the grid layout.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
+ * object with a 'left' and 'top' properties.
+ * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * resolution - {Number}
+ *
+ * Returns:
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ calculateGridLayout: function(bounds, origin, resolution) {
+ var tilelon = resolution * this.tileSize.w;
+ var tilelat = resolution * this.tileSize.h;
+
+ var offsetlon = bounds.left - origin.lon;
+ var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+
+ var rowSign = this.rowSign;
+
+ var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);
+ var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
+
+ return {
+ tilelon: tilelon, tilelat: tilelat,
+ startcol: tilecol, startrow: tilerow
+ };
+
+ },
+
+ /**
+ * Method: getTileOrigin
+ * Determine the origin for aligning the grid of tiles. If a <tileOrigin>
+ * property is supplied, that will be returned. Otherwise, the origin
+ * will be derived from the layer's <maxExtent> property. In this case,
+ * the tile origin will be the corner of the <maxExtent> given by the
+ * <tileOriginCorner> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The tile origin.
+ */
+ getTileOrigin: function() {
+ var origin = this.tileOrigin;
+ if (!origin) {
+ var extent = this.getMaxExtent();
+ var edges = ({
+ "tl": ["left", "top"],
+ "tr": ["right", "top"],
+ "bl": ["left", "bottom"],
+ "br": ["right", "bottom"]
+ })[this.tileOriginCorner];
+ origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
+ }
+ return origin;
+ },
+
+ /**
+ * Method: getTileBoundsForGridIndex
+ *
+ * Parameters:
+ * row - {Number} The row of the grid
+ * col - {Number} The column of the grid
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
+ */
+ getTileBoundsForGridIndex: function(row, col) {
+ var origin = this.getTileOrigin();
+ var tileLayout = this.gridLayout;
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+ var startcol = tileLayout.startcol;
+ var startrow = tileLayout.startrow;
+ var rowSign = this.rowSign;
+ return new OpenLayers.Bounds(
+ origin.lon + (startcol + col) * tilelon,
+ origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
+ origin.lon + (startcol + col + 1) * tilelon,
+ origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
+ );
+ },
+
+ /**
+ * Method: initGriddedTiles
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initGriddedTiles:function(bounds) {
+ this.events.triggerEvent("retile");
+
+ // work out mininum number of rows and columns; this is the number of
+ // tiles required to cover the viewport plus at least one for panning
+
+ var viewSize = this.map.getSize();
+
+ var origin = this.getTileOrigin();
+ var resolution = this.map.getResolution(),
+ serverResolution = this.getServerResolution(),
+ ratio = resolution / serverResolution,
+ tileSize = {
+ w: this.tileSize.w / ratio,
+ h: this.tileSize.h / ratio
+ };
+
+ var minRows = Math.ceil(viewSize.h/tileSize.h) +
+ 2 * this.buffer + 1;
+ var minCols = Math.ceil(viewSize.w/tileSize.w) +
+ 2 * this.buffer + 1;
+
+ var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
+ this.gridLayout = tileLayout;
+
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+
+ var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
+ var layerContainerDivTop = this.map.layerContainerOriginPx.y;
+
+ var tileBounds = this.getTileBoundsForGridIndex(0, 0);
+ var startPx = this.map.getViewPortPxFromLonLat(
+ new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
+ );
+ startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
+ startPx.y = Math.round(startPx.y) - layerContainerDivTop;
+
+ var tileData = [], center = this.map.getCenter();
+
+ var rowidx = 0;
+ do {
+ var row = this.grid[rowidx];
+ if (!row) {
+ row = [];
+ this.grid.push(row);
+ }
+
+ var colidx = 0;
+ do {
+ tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
+ var px = startPx.clone();
+ px.x = px.x + colidx * Math.round(tileSize.w);
+ px.y = px.y + rowidx * Math.round(tileSize.h);
+ var tile = row[colidx];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+ this.addTileMonitoringHooks(tile);
+ row.push(tile);
+ } else {
+ tile.moveTo(tileBounds, px, false);
+ }
+ var tileCenter = tileBounds.getCenterLonLat();
+ tileData.push({
+ tile: tile,
+ distance: Math.pow(tileCenter.lon - center.lon, 2) +
+ Math.pow(tileCenter.lat - center.lat, 2)
+ });
+
+ colidx += 1;
+ } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
+ || colidx < minCols);
+
+ rowidx += 1;
+ } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
+ || rowidx < minRows);
+
+ //shave off exceess rows and colums
+ this.removeExcessTiles(rowidx, colidx);
+
+ var resolution = this.getServerResolution();
+ // store the resolution of the grid
+ this.gridResolution = resolution;
+
+ //now actually draw the tiles
+ tileData.sort(function(a, b) {
+ return a.distance - b.distance;
+ });
+ for (var i=0, ii=tileData.length; i<ii; ++i) {
+ tileData[i].tile.draw();
+ }
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent. (Implemented as a getter for
+ * potential specific implementations in sub-classes.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ return this.maxExtent;
+ },
+
+ /**
+ * APIMethod: addTile
+ * Create a tile, initialize it, and add it to the layer div.
+ *
+ * Parameters
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Tile>} The added OpenLayers.Tile
+ */
+ addTile: function(bounds, position) {
+ var tile = new this.tileClass(
+ this, position, bounds, null, this.tileSize, this.tileOptions
+ );
+ this.events.triggerEvent("addtile", {tile: tile});
+ return tile;
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tiles.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+
+ var replacingCls = 'olTileReplacing';
+
+ tile.onLoadStart = function() {
+ //if that was first tile then trigger a 'loadstart' on the layer
+ if (this.loading === false) {
+ this.loading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.events.triggerEvent("tileloadstart", {tile: tile});
+ this.numLoadingTiles++;
+ if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ OpenLayers.Element.addClass(tile.getTile(), replacingCls);
+ }
+ };
+
+ tile.onLoadEnd = function(evt) {
+ this.numLoadingTiles--;
+ var aborted = evt.type === 'unload';
+ this.events.triggerEvent("tileloaded", {
+ tile: tile,
+ aborted: aborted
+ });
+ if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ var tileDiv = tile.getTile();
+ if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
+ var bufferTile = document.getElementById(tile.id + '_bb');
+ if (bufferTile) {
+ bufferTile.parentNode.removeChild(bufferTile);
+ }
+ }
+ OpenLayers.Element.removeClass(tileDiv, replacingCls);
+ }
+ //if that was the last tile, then trigger a 'loadend' on the layer
+ if (this.numLoadingTiles === 0) {
+ if (this.backBuffer) {
+ if (this.backBuffer.childNodes.length === 0) {
+ // no tiles transitioning, remove immediately
+ this.removeBackBuffer();
+ } else {
+ // wait until transition has ended or delay has passed
+ this._transitionElement = aborted ?
+ this.div.lastChild : tile.imgDiv;
+ var transitionendEvents = this.transitionendEvents;
+ for (var i=transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.observe(this._transitionElement,
+ transitionendEvents[i],
+ this._removeBackBuffer);
+ }
+ // the removal of the back buffer is delayed to prevent
+ // flash effects due to the animation of tile displaying
+ this.backBufferTimerId = window.setTimeout(
+ this._removeBackBuffer, this.removeBackBufferDelay
+ );
+ }
+ }
+ this.loading = false;
+ this.events.triggerEvent("loadend");
+ }
+ };
+
+ tile.onLoadError = function() {
+ this.events.triggerEvent("tileerror", {tile: tile});
+ };
+
+ tile.events.on({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in addTileMonitoringHooks()
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: moveGriddedTiles
+ */
+ moveGriddedTiles: function() {
+ var buffer = this.buffer + 1;
+ while(true) {
+ var tlTile = this.grid[0][0];
+ var tlViewPort = {
+ x: tlTile.position.x +
+ this.map.layerContainerOriginPx.x,
+ y: tlTile.position.y +
+ this.map.layerContainerOriginPx.y
+ };
+ var ratio = this.getServerResolution() / this.map.getResolution();
+ var tileSize = {
+ w: Math.round(this.tileSize.w * ratio),
+ h: Math.round(this.tileSize.h * ratio)
+ };
+ if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
+ this.shiftColumn(true, tileSize);
+ } else if (tlViewPort.x < -tileSize.w * buffer) {
+ this.shiftColumn(false, tileSize);
+ } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
+ this.shiftRow(true, tileSize);
+ } else if (tlViewPort.y < -tileSize.h * buffer) {
+ this.shiftRow(false, tileSize);
+ } else {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: shiftRow
+ * Shifty grid work
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftRow: function(prepend, tileSize) {
+ var grid = this.grid;
+ var rowIndex = prepend ? 0 : (grid.length - 1);
+ var sign = prepend ? -1 : 1;
+ var rowSign = this.rowSign;
+ var tileLayout = this.gridLayout;
+ tileLayout.startrow += sign * rowSign;
+
+ var modelRow = grid[rowIndex];
+ var row = grid[prepend ? 'pop' : 'shift']();
+ for (var i=0, len=row.length; i<len; i++) {
+ var tile = row[i];
+ var position = modelRow[i].position.clone();
+ position.y += tileSize.h * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
+ }
+ grid[prepend ? 'unshift' : 'push'](row);
+ },
+
+ /**
+ * Method: shiftColumn
+ * Shift grid work in the other dimension
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftColumn: function(prepend, tileSize) {
+ var grid = this.grid;
+ var colIndex = prepend ? 0 : (grid[0].length - 1);
+ var sign = prepend ? -1 : 1;
+ var tileLayout = this.gridLayout;
+ tileLayout.startcol += sign;
+
+ for (var i=0, len=grid.length; i<len; i++) {
+ var row = grid[i];
+ var position = row[colIndex].position.clone();
+ var tile = row[prepend ? 'pop' : 'shift']();
+ position.x += tileSize.w * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
+ row[prepend ? 'unshift' : 'push'](tile);
+ }
+ },
+
+ /**
+ * Method: removeExcessTiles
+ * When the size of the map or the buffer changes, we may need to
+ * remove some excess rows and columns.
+ *
+ * Parameters:
+ * rows - {Integer} Maximum number of rows we want our grid to have.
+ * columns - {Integer} Maximum number of columns we want our grid to have.
+ */
+ removeExcessTiles: function(rows, columns) {
+ var i, l;
+
+ // remove extra rows
+ while (this.grid.length > rows) {
+ var row = this.grid.pop();
+ for (i=0, l=row.length; i<l; i++) {
+ var tile = row[i];
+ this.destroyTile(tile);
+ }
+ }
+
+ // remove extra columns
+ for (i=0, l=this.grid.length; i<l; i++) {
+ while (this.grid[i].length > columns) {
+ var row = this.grid[i];
+ var tile = row.pop();
+ this.destroyTile(tile);
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * For singleTile layers, this will set a new tile size according to the
+ * dimensions of the map pane.
+ */
+ onMapResize: function() {
+ if (this.singleTile) {
+ this.clearGrid();
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * APIMethod: getTileBounds
+ * Returns The tile bounds for a layer given a pixel location.
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var maxExtent = this.maxExtent;
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = maxExtent.left + (tileMapWidth *
+ Math.floor((mapPoint.lon -
+ maxExtent.left) /
+ tileMapWidth));
+ var tileBottom = maxExtent.bottom + (tileMapHeight *
+ Math.floor((mapPoint.lat -
+ maxExtent.bottom) /
+ tileMapHeight));
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Grid"
+});
+/* ======================================================================
+ OpenLayers/Layer/XYZ.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.XYZ
+ * The XYZ class is designed to make it easier for people who have tiles
+ * arranged by a standard XYZ grid.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * Default is true, as this is designed to be a base tile source.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * Whether the tile extents should be set to the defaults for
+ * spherical mercator. Useful for things like OpenStreetMap.
+ * Default is false, except for the OSM subclass.
+ */
+ sphericalMercator: false,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more zoom levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Using <zoomOffset> is an alternative to
+ * setting <serverResolutions> if you only want to expose a subset
+ * of the server resolutions.
+ */
+ zoomOffset: 0,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.XYZ
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, options) {
+ if (options && options.sphericalMercator || this.sphericalMercator) {
+ options = OpenLayers.Util.extend({
+ projection: "EPSG:900913",
+ numZoomLevels: 19
+ }, options);
+ }
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
+ name || this.name, url || this.url, {}, options
+ ]);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.XYZ(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ var xyz = this.getXYZ(bounds);
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ var s = '' + xyz.x + xyz.y + xyz.z;
+ url = this.selectUrl(s, url);
+ }
+
+ return OpenLayers.String.format(url, xyz);
+ },
+
+ /**
+ * Method: getXYZ
+ * Calculates x, y and z for the given bounds.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} - an object with x, y and z properties.
+ */
+ getXYZ: function(bounds) {
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.maxExtent.left) /
+ (res * this.tileSize.w));
+ var y = Math.round((this.maxExtent.top - bounds.top) /
+ (res * this.tileSize.h));
+ var z = this.getServerZoom();
+
+ if (this.wrapDateLine) {
+ var limit = Math.pow(2, z);
+ x = ((x % limit) + limit) % limit;
+ }
+
+ return {'x': x, 'y': y, 'z': z};
+ },
+
+ /* APIMethod: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left,
+ this.maxExtent.bottom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.XYZ"
+});
+/* ======================================================================
+ OpenLayers/Layer/OSM.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.OSM
+ * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap
+ * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use
+ * a different layer instead, you need to provide a different
+ * URL to the constructor. Here's an example for using OpenCycleMap:
+ *
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: name
+ * {String} The layer name. Defaults to "OpenStreetMap" if the first
+ * argument to the constructor is null or undefined.
+ */
+ name: "OpenStreetMap",
+
+ /**
+ * APIProperty: url
+ * {String} The tileset URL scheme. Defaults to
+ * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png
+ * (the official OSM tileset) if the second argument to the constructor
+ * is null or undefined. To use another tileset you can have something
+ * like this:
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ */
+ url: [
+ 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png'
+ ],
+
+ /**
+ * Property: attribution
+ * {String} The layer attribution.
+ */
+ attribution: "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
+
+ /**
+ * Property: sphericalMercator
+ * {Boolean}
+ */
+ sphericalMercator: true,
+
+ /**
+ * Property: wrapDateLine
+ * {Boolean}
+ */
+ wrapDateLine: true,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ *
+ * When using OSM tilesets other than the default ones, it may be
+ * necessary to set this to
+ *
+ * (code)
+ * {crossOriginKeyword: null}
+ * (end)
+ *
+ * if the server does not send Access-Control-Allow-Origin headers.
+ */
+ tileOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.OSM
+ *
+ * Parameters:
+ * name - {String} The layer name.
+ * url - {String} The tileset URL scheme.
+ * options - {Object} Configuration options for the layer. Any inherited
+ * layer option can be set in this object (e.g.
+ * <OpenLayers.Layer.Grid.buffer>).
+ */
+ initialize: function(name, url, options) {
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options && this.options.tileOptions);
+ },
+
+ /**
+ * Method: clone
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.OSM(
+ this.name, this.url, this.getOptions());
+ }
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM"
+});
+/* ======================================================================
+ OpenLayers/Layer/Bing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Bing
+ * Bing layer using direct tile access as provided by Bing Maps REST Services.
+ * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more
+ * information. Note: Terms of Service compliant use requires the map to be
+ * configured with an <OpenLayers.Control.Attribution> control and the
+ * attribution placed on or near the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * Property: key
+ * {String} API key for Bing maps, get your own key
+ * at http://bingmapsportal.com/ .
+ */
+ key: null,
+
+ /**
+ * Property: serverResolutions
+ * {Array} the resolutions provided by the Bing servers.
+ */
+ serverResolutions: [
+ 156543.03390625, 78271.516953125, 39135.7584765625,
+ 19567.87923828125, 9783.939619140625, 4891.9698095703125,
+ 2445.9849047851562, 1222.9924523925781, 611.4962261962891,
+ 305.74811309814453, 152.87405654907226, 76.43702827453613,
+ 38.218514137268066, 19.109257068634033, 9.554628534317017,
+ 4.777314267158508, 2.388657133579254, 1.194328566789627,
+ 0.5971642833948135, 0.29858214169740677, 0.14929107084870338,
+ 0.07464553542435169
+ ],
+
+ /**
+ * Property: attributionTemplate
+ * {String}
+ */
+ attributionTemplate: '<span class="olBingAttribution ${type}">' +
+ '<div><a target="_blank" href="http://www.bing.com/maps/">' +
+ '<img src="${logo}" /></a></div>${copyrights}' +
+ '<a style="white-space: nowrap" target="_blank" '+
+ 'href="http://www.microsoft.com/maps/product/terms.html">' +
+ 'Terms of Use</a></span>',
+
+ /**
+ * Property: metadata
+ * {Object} Metadata for this layer, as returned by the callback script
+ */
+ metadata: null,
+
+ /**
+ * Property: protocolRegex
+ * {RegExp} Regular expression to match and replace http: in bing urls
+ */
+ protocolRegex: /^http:/i,
+
+ /**
+ * APIProperty: type
+ * {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used. Default is "Road".
+ */
+ type: "Road",
+
+ /**
+ * APIProperty: culture
+ * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx
+ * for the definition and the possible values. Default is "en-US".
+ */
+ culture: "en-US",
+
+ /**
+ * APIProperty: metadataParams
+ * {Object} Optional url parameters for the Get Imagery Metadata request
+ * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx
+ */
+ metadataParams: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ */
+ tileOptions: null,
+
+ /** APIProperty: protocol
+ * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo
+ * Can be 'http:' 'https:' or ''
+ *
+ * Warning: tiles may not be available under both HTTP and HTTPS protocols.
+ * Microsoft approved use of both HTTP and HTTPS urls for tiles. However
+ * this is undocumented and the Imagery Metadata API always returns HTTP
+ * urls.
+ *
+ * Default is '', unless when executed from a file:/// uri, in which case
+ * it is 'http:'.
+ */
+ protocol: ~window.location.href.indexOf('http') ? '' : 'http:',
+
+ /**
+ * Constructor: OpenLayers.Layer.Bing
+ * Create a new Bing layer.
+ *
+ * Example:
+ * (code)
+ * var road = new OpenLayers.Layer.Bing({
+ * name: "My Bing Aerial Layer",
+ * type: "Aerial",
+ * key: "my-api-key-here",
+ * });
+ * (end)
+ *
+ * Parameters:
+ * options - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * key - {String} Bing Maps API key for your application. Get one at
+ * http://bingmapsportal.com/.
+ * type - {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used.
+ *
+ * Any other documented layer properties can be provided in the config object.
+ */
+ initialize: function(options) {
+ options = OpenLayers.Util.applyDefaults({
+ sphericalMercator: true
+ }, options);
+ var name = options.name || "Bing " + (options.type || this.type);
+
+ var newArgs = [name, null, options];
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options.tileOptions);
+ this.loadMetadata();
+ },
+
+ /**
+ * Method: loadMetadata
+ */
+ loadMetadata: function() {
+ this._callbackId = "_callback_" + this.id.replace(/\./g, "_");
+ // link the processMetadata method to the global scope and bind it
+ // to this instance
+ window[this._callbackId] = OpenLayers.Function.bind(
+ OpenLayers.Layer.Bing.processMetadata, this
+ );
+ var params = OpenLayers.Util.applyDefaults({
+ key: this.key,
+ jsonp: this._callbackId,
+ include: "ImageryProviders"
+ }, this.metadataParams);
+ var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" +
+ this.type + "?" + OpenLayers.Util.getParameterString(params);
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.id = this._callbackId;
+ document.getElementsByTagName("head")[0].appendChild(script);
+ },
+
+ /**
+ * Method: initLayer
+ *
+ * Sets layer properties according to the metadata provided by the API
+ */
+ initLayer: function() {
+ var res = this.metadata.resourceSets[0].resources[0];
+ var url = res.imageUrl.replace("{quadkey}", "${quadkey}");
+ url = url.replace("{culture}", this.culture);
+ url = url.replace(this.protocolRegex, this.protocol);
+ this.url = [];
+ for (var i=0; i<res.imageUrlSubdomains.length; ++i) {
+ this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
+ }
+ this.addOptions({
+ maxResolution: Math.min(
+ this.serverResolutions[res.zoomMin],
+ this.maxResolution || Number.POSITIVE_INFINITY
+ ),
+ numZoomLevels: Math.min(
+ res.zoomMax + 1 - res.zoomMin, this.numZoomLevels
+ )
+ }, true);
+ if (!this.isBaseLayer) {
+ this.redraw();
+ }
+ this.updateAttribution();
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Paramters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getURL: function(bounds) {
+ if (!this.url) {
+ return;
+ }
+ var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z;
+ var quadDigits = [];
+ for (var i = z; i > 0; --i) {
+ var digit = '0';
+ var mask = 1 << (i - 1);
+ if ((x & mask) != 0) {
+ digit++;
+ }
+ if ((y & mask) != 0) {
+ digit++;
+ digit++;
+ }
+ quadDigits.push(digit);
+ }
+ var quadKey = quadDigits.join("");
+ var url = this.selectUrl('' + x + y + z, this.url);
+
+ return OpenLayers.String.format(url, {'quadkey': quadKey});
+ },
+
+ /**
+ * Method: updateAttribution
+ * Updates the attribution according to the requirements outlined in
+ * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html
+ */
+ updateAttribution: function() {
+ var metadata = this.metadata;
+ if (!metadata.resourceSets || !this.map || !this.map.center) {
+ return;
+ }
+ var res = metadata.resourceSets[0].resources[0];
+ var extent = this.map.getExtent().transform(
+ this.map.getProjectionObject(),
+ new OpenLayers.Projection("EPSG:4326")
+ );
+ var providers = res.imageryProviders || [],
+ zoom = OpenLayers.Util.indexOf(this.serverResolutions,
+ this.getServerResolution()),
+ copyrights = "", provider, i, ii, j, jj, bbox, coverage;
+ for (i=0,ii=providers.length; i<ii; ++i) {
+ provider = providers[i];
+ for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
+ coverage = provider.coverageAreas[j];
+ // axis order provided is Y,X
+ bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true);
+ if (extent.intersectsBounds(bbox) &&
+ zoom <= coverage.zoomMax && zoom >= coverage.zoomMin) {
+ copyrights += provider.attribution + " ";
+ }
+ }
+ }
+ var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol);
+ this.attribution = OpenLayers.String.format(this.attributionTemplate, {
+ type: this.type.toLowerCase(),
+ logo: logo,
+ copyrights: copyrights
+ });
+ this.map && this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "attribution"
+ });
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function() {
+ OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments);
+ this.map.events.register("moveend", this, this.updateAttribution);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Bing(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.map &&
+ this.map.events.unregister("moveend", this, this.updateAttribution);
+ OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Bing"
+});
+
+/**
+ * Function: OpenLayers.Layer.Bing.processMetadata
+ * This function will be bound to an instance, linked to the global scope with
+ * an id, and called by the JSONP script returned by the API.
+ *
+ * Parameters:
+ * metadata - {Object} metadata as returned by the API
+ */
+OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ this.metadata = metadata;
+ this.initLayer();
+ var script = document.getElementById(this._callbackId);
+ script.parentNode.removeChild(script);
+ window[this._callbackId] = undefined; // cannot delete from window in IE
+ delete this._callbackId;
+};
+/* ======================================================================
+ OpenLayers/Handler.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Handler
+ * Base class to construct a higher-level handler for event sequences. All
+ * handlers have activate and deactivate methods. In addition, they have
+ * methods named like browser events. When a handler is activated, any
+ * additional methods named like a browser event is registered as a
+ * listener for the corresponding event. When a handler is deactivated,
+ * those same methods are unregistered as event listeners.
+ *
+ * Handlers also typically have a callbacks object with keys named like
+ * the abstracted events or event sequences that they are in charge of
+ * handling. The controls that wrap handlers define the methods that
+ * correspond to these abstract events - so instead of listening for
+ * individual browser events, they only listen for the abstract events
+ * defined by the handler.
+ *
+ * Handlers are created by controls, which ultimately have the responsibility
+ * of making changes to the the state of the application. Handlers
+ * themselves may make temporary changes, but in general are expected to
+ * return the application in the same state that they found it.
+ */
+OpenLayers.Handler = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: control
+ * {<OpenLayers.Control>}. The control that initialized this handler. The
+ * control is assumed to have a valid map property - that map is used
+ * in the handler's own setMap method.
+ */
+ control: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>}
+ */
+ map: null,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
+ * constants to construct a keyMask. The keyMask is used by
+ * <checkModifiers>. If the keyMask matches the combination of keys
+ * down on an event, checkModifiers returns true.
+ *
+ * Example:
+ * (code)
+ * // handler only responds if the Shift key is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
+ *
+ * // handler only responds if Ctrl-Shift is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
+ * OpenLayers.Handler.MOD_CTRL;
+ * (end)
+ */
+ keyMask: null,
+
+ /**
+ * Property: active
+ * {Boolean}
+ */
+ active: false,
+
+ /**
+ * Property: evt
+ * {Event} This property references the last event handled by the handler.
+ * Note that this property is not part of the stable API. Use of the
+ * evt property should be restricted to controls in the library
+ * or other applications that are willing to update with changes to
+ * the OpenLayers code.
+ */
+ evt: null,
+
+ /**
+ * Property: touch
+ * {Boolean} Indicates the support of touch events. When touch events are
+ * started touch will be true and all mouse related listeners will do
+ * nothing.
+ */
+ touch: false,
+
+ /**
+ * Constructor: OpenLayers.Handler
+ * Construct a handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that initialized this
+ * handler. The control is assumed to have a valid map property; that
+ * map is used in the handler's own setMap method. If a map property
+ * is present in the options argument it will be used instead.
+ * callbacks - {Object} An object whose properties correspond to abstracted
+ * events or sequences of browser events. The values for these
+ * properties are functions defined by the control that get called by
+ * the handler.
+ * options - {Object} An optional object whose properties will be set on
+ * the handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Util.extend(this, options);
+ this.control = control;
+ this.callbacks = callbacks;
+
+ var map = this.map || control.map;
+ if (map) {
+ this.setMap(map);
+ }
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ this.map = map;
+ },
+
+ /**
+ * Method: checkModifiers
+ * Check the keyMask on the handler. If no <keyMask> is set, this always
+ * returns true. If a <keyMask> is set and it matches the combination
+ * of keys down on an event, this returns true.
+ *
+ * Returns:
+ * {Boolean} The keyMask matches the keys down on an event.
+ */
+ checkModifiers: function (evt) {
+ if(this.keyMask == null) {
+ return true;
+ }
+ /* calculate the keyboard modifier mask for this event */
+ var keyModifiers =
+ (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
+ (evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) |
+ (evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) |
+ (evt.metaKey ? OpenLayers.Handler.MOD_META : 0);
+
+ /* if it differs from the handler object's key mask,
+ bail out of the event handler */
+ return (keyModifiers == this.keyMask);
+ },
+
+ /**
+ * APIMethod: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean} The handler was activated.
+ */
+ activate: function() {
+ if(this.active) {
+ return false;
+ }
+ // register for event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.register(events[i], this[events[i]]);
+ }
+ }
+ this.active = true;
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler. Returns false if the handler was already inactive.
+ *
+ * Returns:
+ * {Boolean} The handler was deactivated.
+ */
+ deactivate: function() {
+ if(!this.active) {
+ return false;
+ }
+ // unregister event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ this.touch = false;
+ this.active = false;
+ return true;
+ },
+
+ /**
+ * Method: startTouch
+ * Start touch events, this method must be called by subclasses in
+ * "touchstart" method. When touch events are started <touch> will be
+ * true and all mouse related listeners will do nothing.
+ */
+ startTouch: function() {
+ if (!this.touch) {
+ this.touch = true;
+ var events = [
+ "mousedown", "mouseup", "mousemove", "click", "dblclick",
+ "mouseout"
+ ];
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array(*)} An array of arguments (any type) with which to call
+ * the callback (defined by the control).
+ */
+ callback: function (name, args) {
+ if (name && this.callbacks[name]) {
+ this.callbacks[name].apply(this.control, args);
+ }
+ },
+
+ /**
+ * Method: register
+ * register an event on the map
+ */
+ register: function (name, method) {
+ // TODO: deal with registerPriority in 3.0
+ this.map.events.registerPriority(name, this, method);
+ this.map.events.registerPriority(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: unregister
+ * unregister an event from the map
+ */
+ unregister: function (name, method) {
+ this.map.events.unregister(name, this, method);
+ this.map.events.unregister(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: setEvent
+ * With each registered browser event, the handler sets its own evt
+ * property. This property can be accessed by controls if needed
+ * to get more information about the event that the handler is
+ * processing.
+ *
+ * This allows modifier keys on the event to be checked (alt, shift, ctrl,
+ * and meta cannot be checked with the keyboard handler). For a
+ * control to determine which modifier keys are associated with the
+ * event that a handler is currently processing, it should access
+ * (code)handler.evt.altKey || handler.evt.shiftKey ||
+ * handler.evt.ctrlKey || handler.evt.metaKey(end).
+ *
+ * Parameters:
+ * evt - {Event} The browser event.
+ */
+ setEvent: function(evt) {
+ this.evt = evt;
+ return true;
+ },
+
+ /**
+ * Method: destroy
+ * Deconstruct the handler.
+ */
+ destroy: function () {
+ // unregister event listeners
+ this.deactivate();
+ // eliminate circular references
+ this.control = this.map = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler"
+});
+
+/**
+ * Constant: OpenLayers.Handler.MOD_NONE
+ * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
+ */
+OpenLayers.Handler.MOD_NONE = 0;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_SHIFT
+ * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
+ */
+OpenLayers.Handler.MOD_SHIFT = 1;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_CTRL
+ * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
+ */
+OpenLayers.Handler.MOD_CTRL = 2;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_ALT
+ * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
+ */
+OpenLayers.Handler.MOD_ALT = 4;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_META
+ * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
+ */
+OpenLayers.Handler.MOD_META = 8;
+
+
+/* ======================================================================
+ OpenLayers/Handler/MouseWheel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.MouseWheel
+ * Handler for wheel up/down events.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * Property: wheelListener
+ * {function}
+ */
+ wheelListener: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase server performance, an interval (in
+ * milliseconds) can be set to reduce the number of up/down events
+ * called. If set, a new up/down event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: maxDelta
+ * {Integer} Maximum delta to collect before breaking from the current
+ * interval. In cumulative mode, this also limits the maximum delta
+ * returned from the handler. Default is Number.POSITIVE_INFINITY.
+ */
+ maxDelta: Number.POSITIVE_INFINITY,
+
+ /**
+ * Property: delta
+ * {Integer} When interval is set, delta collects the mousewheel z-deltas
+ * of the events that occur within the interval.
+ * See also the cumulative option
+ */
+ delta: 0,
+
+ /**
+ * Property: cumulative
+ * {Boolean} When interval is set: true to collect all the mousewheel
+ * z-deltas, false to only record the delta direction (positive or
+ * negative)
+ */
+ cumulative: true,
+
+ /**
+ * Constructor: OpenLayers.Handler.MouseWheel
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished.
+ * The callback should expect to recieve a single
+ * argument, the point geometry.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ this.wheelListener = OpenLayers.Function.bindAsEventListener(
+ this.onWheelEvent, this
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ this.wheelListener = null;
+ },
+
+ /**
+ * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
+ */
+
+ /**
+ * Method: onWheelEvent
+ * Catch the wheel event and handle it xbrowserly
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ onWheelEvent: function(e){
+
+ // make sure we have a map and check keyboard modifiers
+ if (!this.map || !this.checkModifiers(e)) {
+ return;
+ }
+
+ // Ride up the element's DOM hierarchy to determine if it or any of
+ // its ancestors was:
+ // * specifically marked as scrollable (CSS overflow property)
+ // * one of our layer divs or a div marked as scrollable
+ // ('olScrollable' CSS class)
+ // * the map div
+ //
+ var overScrollableDiv = false;
+ var allowScroll = false;
+ var overMapDiv = false;
+
+ var elem = OpenLayers.Event.element(e);
+ while((elem != null) && !overMapDiv && !overScrollableDiv) {
+
+ if (!overScrollableDiv) {
+ try {
+ var overflow;
+ if (elem.currentStyle) {
+ overflow = elem.currentStyle["overflow"];
+ } else {
+ var style =
+ document.defaultView.getComputedStyle(elem, null);
+ overflow = style.getPropertyValue("overflow");
+ }
+ overScrollableDiv = ( overflow &&
+ (overflow == "auto") || (overflow == "scroll") );
+ } catch(err) {
+ //sometimes when scrolling in a popup, this causes
+ // obscure browser error
+ }
+ }
+
+ if (!allowScroll) {
+ allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable');
+ if (!allowScroll) {
+ for (var i = 0, len = this.map.layers.length; i < len; i++) {
+ // Are we in the layer div? Note that we have two cases
+ // here: one is to catch EventPane layers, which have a
+ // pane above the layer (layer.pane)
+ var layer = this.map.layers[i];
+ if (elem == layer.div || elem == layer.pane) {
+ allowScroll = true;
+ break;
+ }
+ }
+ }
+ }
+ overMapDiv = (elem == this.map.div);
+
+ elem = elem.parentNode;
+ }
+
+ // Logic below is the following:
+ //
+ // If we are over a scrollable div or not over the map div:
+ // * do nothing (let the browser handle scrolling)
+ //
+ // otherwise
+ //
+ // If we are over the layer div or a 'olScrollable' div:
+ // * zoom/in out
+ // then
+ // * kill event (so as not to also scroll the page after zooming)
+ //
+ // otherwise
+ //
+ // Kill the event (dont scroll the page if we wheel over the
+ // layerswitcher or the pan/zoom control)
+ //
+ if (!overScrollableDiv && overMapDiv) {
+ if (allowScroll) {
+ var delta = 0;
+
+ if (e.wheelDelta) {
+ delta = e.wheelDelta;
+ if (delta % 160 === 0) {
+ // opera have steps of 160 instead of 120
+ delta = delta * 0.75;
+ }
+ delta = delta / 120;
+ } else if (e.detail) {
+ // detail in Firefox on OS X is 1/3 of Windows
+ // so force delta 1 / -1
+ delta = - (e.detail / Math.abs(e.detail));
+ }
+ this.delta += delta;
+
+ window.clearTimeout(this._timeoutId);
+ if(this.interval && Math.abs(this.delta) < this.maxDelta) {
+ // store e because window.event might change during delay
+ var evt = OpenLayers.Util.extend({}, e);
+ this._timeoutId = window.setTimeout(
+ OpenLayers.Function.bind(function(){
+ this.wheelZoom(evt);
+ }, this),
+ this.interval
+ );
+ } else {
+ this.wheelZoom(e);
+ }
+ }
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: wheelZoom
+ * Given the wheel event, we carry out the appropriate zooming in or out,
+ * based on the 'wheelDelta' or 'detail' property of the event.
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ wheelZoom: function(e) {
+ var delta = this.delta;
+ this.delta = 0;
+
+ if (delta) {
+ e.xy = this.map.events.getMousePosition(e);
+ if (delta < 0) {
+ this.callback("down",
+ [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]);
+ } else {
+ this.callback("up",
+ [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function (evt) {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ //register mousewheel events specifically on the window and document
+ var wheelListener = this.wheelListener;
+ OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener);
+ OpenLayers.Event.observe(window, "mousewheel", wheelListener);
+ OpenLayers.Event.observe(document, "mousewheel", wheelListener);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function (evt) {
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ // unregister mousewheel events specifically on the window and document
+ var wheelListener = this.wheelListener;
+ OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener);
+ OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener);
+ OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.MouseWheel"
+});
+/* ======================================================================
+ OpenLayers/Geometry/MultiLineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LineString"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiLineString
+ * Constructor for a MultiLineString Geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LineString>)}
+ *
+ */
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
+ var sourceParts = [];
+ var targetParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ sourceLine = this.components[i];
+ sourceSplit = false;
+ for(var j=0; j < targetParts.length; ++j) {
+ splits = sourceLine.split(targetParts[j], options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ for(var k=0, klen=sourceLines.length; k<klen; ++k) {
+ if(k===0 && sourceParts.length) {
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLines[k]
+ );
+ } else {
+ sourceParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ sourceLines[k]
+ ])
+ );
+ }
+ }
+ sourceSplit = true;
+ splits = splits[1];
+ }
+ if(splits.length) {
+ // splice in new target parts
+ splits.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, splits);
+ break;
+ }
+ }
+ }
+ if(!sourceSplit) {
+ // source line was not hit
+ if(sourceParts.length) {
+ // add line to existing multi
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLine.clone()
+ );
+ } else {
+ // create a fresh multi
+ sourceParts = [
+ new OpenLayers.Geometry.MultiLineString(
+ sourceLine.clone()
+ )
+ ];
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
+ if(geometry instanceof OpenLayers.Geometry.LineString) {
+ targetParts = [];
+ sourceParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ targetSplit = false;
+ targetLine = this.components[i];
+ for(var j=0; j<sourceParts.length; ++j) {
+ splits = sourceParts[j].split(targetLine, options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ if(sourceLines.length) {
+ // splice in new source parts
+ sourceLines.unshift(j, 1);
+ Array.prototype.splice.apply(sourceParts, sourceLines);
+ j += sourceLines.length - 2;
+ }
+ splits = splits[1];
+ if(splits.length === 0) {
+ splits = [targetLine.clone()];
+ }
+ }
+ for(var k=0, klen=splits.length; k<klen; ++k) {
+ if(k===0 && targetParts.length) {
+ targetParts[targetParts.length-1].addComponent(
+ splits[k]
+ );
+ } else {
+ targetParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ splits[k]
+ ])
+ );
+ }
+ }
+ targetSplit = true;
+ }
+ }
+ if(!targetSplit) {
+ // target component was not hit
+ if(targetParts.length) {
+ // add it to any existing multi-line
+ targetParts[targetParts.length-1].addComponent(
+ targetLine.clone()
+ );
+ } else {
+ // or start with a fresh multi-line
+ targetParts = [
+ new OpenLayers.Geometry.MultiLineString([
+ targetLine.clone()
+ ])
+ ];
+ }
+
+ }
+ }
+ } else {
+ results = geometry.split(this);
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
+/* ======================================================================
+ OpenLayers/Renderer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer
+ * This is the base class for all renderers.
+ *
+ * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
+ * It is largely composed of virtual functions that are to be implemented
+ * in technology-specific subclasses, but there is some generic code too.
+ *
+ * The functions that *are* implemented here merely deal with the maintenance
+ * of the size and extent variables, as well as the cached 'resolution'
+ * value.
+ *
+ * A note to the user that all subclasses should use getResolution() instead
+ * of directly accessing this.resolution in order to correctly use the
+ * cacheing system.
+ *
+ */
+OpenLayers.Renderer = OpenLayers.Class({
+
+ /**
+ * Property: container
+ * {DOMElement}
+ */
+ container: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: extent
+ * {<OpenLayers.Bounds>}
+ */
+ extent: null,
+
+ /**
+ * Property: locked
+ * {Boolean} If the renderer is currently in a state where many things
+ * are changing, the 'locked' property is set to true. This means
+ * that renderers can expect at least one more drawFeature event to be
+ * called with the 'locked' property set to 'true': In some renderers,
+ * this might make sense to use as a 'only update local information'
+ * flag.
+ */
+ locked: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>}
+ */
+ size: null,
+
+ /**
+ * Property: resolution
+ * {Float} cache of current map resolution
+ */
+ resolution: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
+ */
+ map: null,
+
+ /**
+ * Property: featureDx
+ * {Number} Feature offset in x direction. Will be calculated for and
+ * applied to the current feature while rendering (see
+ * <calculateFeatureDx>).
+ */
+ featureDx: 0,
+
+ /**
+ * Constructor: OpenLayers.Renderer
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} options for this renderer. See sublcasses for
+ * supported options.
+ */
+ initialize: function(containerID, options) {
+ this.container = OpenLayers.Util.getElement(containerID);
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.container = null;
+ this.extent = null;
+ this.size = null;
+ this.resolution = null;
+ this.map = null;
+ },
+
+ /**
+ * APIMethod: supported
+ * This should be overridden by specific subclasses
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return false;
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ * We nullify the resolution cache (this.resolution) if resolutionChanged
+ * is set to true - this way it will be re-computed on the next
+ * getResolution() request.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ this.extent = extent.clone();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio);
+ this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
+ }
+ if (resolutionChanged) {
+ this.resolution = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ this.resolution = null;
+ },
+
+ /**
+ * Method: getResolution
+ * Uses cached copy of resolution if available to minimize computing
+ *
+ * Returns:
+ * {Float} The current map's resolution
+ */
+ getResolution: function() {
+ this.resolution = this.resolution || this.map.getResolution();
+ return this.resolution;
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. The optional style argument can be used
+ * to override the feature's own style. This method should only
+ * be called from layer.drawFeature().
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} true if the feature has been drawn completely, false if not,
+ * undefined if the feature had no geometry
+ */
+ drawFeature: function(feature, style) {
+ if(style == null) {
+ style = feature.style;
+ }
+ if (feature.geometry) {
+ var bounds = feature.geometry.getBounds();
+ if(bounds) {
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+ if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
+ style = {display: "none"};
+ } else {
+ this.calculateFeatureDx(bounds, worldBounds);
+ }
+ var rendered = this.drawGeometry(feature.geometry, style, feature.id);
+ if(style.display != "none" && style.label && rendered !== false) {
+
+ var location = feature.geometry.getCentroid();
+ if(style.labelXOffset || style.labelYOffset) {
+ var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
+ var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
+ var res = this.getResolution();
+ location.move(xOffset*res, yOffset*res);
+ }
+ this.drawText(feature.id, style, location);
+ } else {
+ this.removeText(feature.id);
+ }
+ return rendered;
+ }
+ }
+ },
+
+ /**
+ * Method: calculateFeatureDx
+ * {Number} Calculates the feature offset in x direction. Looking at the
+ * center of the feature bounds and the renderer extent, we calculate how
+ * many world widths the two are away from each other. This distance is
+ * used to shift the feature as close as possible to the center of the
+ * current enderer extent, which ensures that the feature is visible in the
+ * current viewport.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} Bounds of the feature
+ * worldBounds - {<OpenLayers.Bounds>} Bounds of the world
+ */
+ calculateFeatureDx: function(bounds, worldBounds) {
+ this.featureDx = 0;
+ if (worldBounds) {
+ var worldWidth = worldBounds.getWidth(),
+ rendererCenterX = (this.extent.left + this.extent.right) / 2,
+ featureCenterX = (bounds.left + bounds.right) / 2,
+ worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
+ this.featureDx = worldsAway * worldWidth;
+ }
+ },
+
+ /**
+ * Method: drawGeometry
+ *
+ * Draw a geometry. This should only be called from the renderer itself.
+ * Use layer.drawFeature() from outside the renderer.
+ * virtual function
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {<String>}
+ */
+ drawGeometry: function(geometry, style, featureId) {},
+
+ /**
+ * Method: drawText
+ * Function for drawing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {},
+
+ /**
+ * Method: removeText
+ * Function for removing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {},
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ * virtual function.
+ */
+ clear: function() {},
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ * How this happens is specific to the renderer. This should be
+ * called from layer.getFeatureFromEvent().
+ * Virtual function.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {},
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0, len=features.length; i<len; ++i) {
+ var feature = features[i];
+ this.eraseGeometry(feature.geometry, feature.id);
+ this.removeText(feature.id);
+ }
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Remove a geometry from the renderer (by id).
+ * virtual function.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a (different) renderer.
+ * To be implemented by subclasses that require a common renderer root for
+ * feature selection.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {},
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.container.id;
+ },
+
+ /**
+ * Method: applyDefaultSymbolizer
+ *
+ * Parameters:
+ * symbolizer - {Object}
+ *
+ * Returns:
+ * {Object}
+ */
+ applyDefaultSymbolizer: function(symbolizer) {
+ var result = OpenLayers.Util.extend({},
+ OpenLayers.Renderer.defaultSymbolizer);
+ if(symbolizer.stroke === false) {
+ delete result.strokeWidth;
+ delete result.strokeColor;
+ }
+ if(symbolizer.fill === false) {
+ delete result.fillColor;
+ }
+ OpenLayers.Util.extend(result, symbolizer);
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.defaultSymbolizer
+ * {Object} Properties from this symbolizer will be applied to symbolizers
+ * with missing properties. This can also be used to set a global
+ * symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
+ * following code before rendering any vector features:
+ * (code)
+ * OpenLayers.Renderer.defaultSymbolizer = {
+ * fillColor: "#808080",
+ * fillOpacity: 1,
+ * strokeColor: "#000000",
+ * strokeOpacity: 1,
+ * strokeWidth: 1,
+ * pointRadius: 3,
+ * graphicName: "square"
+ * };
+ * (end)
+ */
+OpenLayers.Renderer.defaultSymbolizer = {
+ fillColor: "#000000",
+ strokeColor: "#000000",
+ strokeWidth: 2,
+ fillOpacity: 1,
+ strokeOpacity: 1,
+ pointRadius: 0,
+ labelAlign: 'cm'
+};
+
+
+
+/**
+ * Constant: OpenLayers.Renderer.symbol
+ * Coordinate arrays for well known (named) symbols.
+ */
+OpenLayers.Renderer.symbol = {
+ "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
+ 303,215, 231,161, 321,161, 350,75],
+ "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
+ 4,0],
+ "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
+ "square": [0,0, 0,1, 1,1, 1,0, 0,0],
+ "triangle": [0,10, 10,10, 5,0, 0,10]
+};
+/* ======================================================================
+ OpenLayers/Renderer/Elements.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ * placed in the DOM based on given indexing methods.
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+
+ /**
+ * Property: maxZIndex
+ * {Integer} This is the largest-most z-index value for a node
+ * contained within the indexer.
+ */
+ maxZIndex: null,
+
+ /**
+ * Property: order
+ * {Array<String>} This is an array of node id's stored in the
+ * order that they should show up on screen. Id's higher up in the
+ * array (higher array index) represent nodes with higher z-indeces.
+ */
+ order: null,
+
+ /**
+ * Property: indices
+ * {Object} This is a hash that maps node ids to their z-index value
+ * stored in the indexer. This is done to make finding a nodes z-index
+ * value O(1).
+ */
+ indices: null,
+
+ /**
+ * Property: compare
+ * {Function} This is the function used to determine placement of
+ * of a new node within the indexer. If null, this defaults to to
+ * the Z_ORDER_DRAWING_ORDER comparison method.
+ */
+ compare: null,
+
+ /**
+ * APIMethod: initialize
+ * Create a new indexer with
+ *
+ * Parameters:
+ * yOrdering - {Boolean} Whether to use y-ordering.
+ */
+ initialize: function(yOrdering) {
+
+ this.compare = yOrdering ?
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
+
+ this.clear();
+ },
+
+ /**
+ * APIMethod: insert
+ * Insert a new node into the indexer. In order to find the correct
+ * positioning for the node to be inserted, this method uses a binary
+ * search. This makes inserting O(log(n)).
+ *
+ * Parameters:
+ * newNode - {DOMElement} The new node to be inserted.
+ *
+ * Returns
+ * {DOMElement} the node before which we should insert our newNode, or
+ * null if newNode can just be appended.
+ */
+ insert: function(newNode) {
+ // If the node is known to the indexer, remove it so we can
+ // recalculate where it should go.
+ if (this.exists(newNode)) {
+ this.remove(newNode);
+ }
+
+ var nodeId = newNode.id;
+
+ this.determineZIndex(newNode);
+
+ var leftIndex = -1;
+ var rightIndex = this.order.length;
+ var middle;
+
+ while (rightIndex - leftIndex > 1) {
+ middle = parseInt((leftIndex + rightIndex) / 2);
+
+ var placement = this.compare(this, newNode,
+ OpenLayers.Util.getElement(this.order[middle]));
+
+ if (placement > 0) {
+ leftIndex = middle;
+ } else {
+ rightIndex = middle;
+ }
+ }
+
+ this.order.splice(rightIndex, 0, nodeId);
+ this.indices[nodeId] = this.getZIndex(newNode);
+
+ // If the new node should be before another in the index
+ // order, return the node before which we have to insert the new one;
+ // else, return null to indicate that the new node can be appended.
+ return this.getNextElement(rightIndex);
+ },
+
+ /**
+ * APIMethod: remove
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be removed.
+ */
+ remove: function(node) {
+ var nodeId = node.id;
+ var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
+ if (arrayIndex >= 0) {
+ // Remove it from the order array, as well as deleting the node
+ // from the indeces hash.
+ this.order.splice(arrayIndex, 1);
+ delete this.indices[nodeId];
+
+ // Reset the maxium z-index based on the last item in the
+ // order array.
+ if (this.order.length > 0) {
+ var lastId = this.order[this.order.length - 1];
+ this.maxZIndex = this.indices[lastId];
+ } else {
+ this.maxZIndex = 0;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clear
+ */
+ clear: function() {
+ this.order = [];
+ this.indices = {};
+ this.maxZIndex = 0;
+ },
+
+ /**
+ * APIMethod: exists
+ *
+ * Parameters:
+ * node - {DOMElement} The node to test for existence.
+ *
+ * Returns:
+ * {Boolean} Whether or not the node exists in the indexer?
+ */
+ exists: function(node) {
+ return (this.indices[node.id] != null);
+ },
+
+ /**
+ * APIMethod: getZIndex
+ * Get the z-index value for the current node from the node data itself.
+ *
+ * Parameters:
+ * node - {DOMElement} The node whose z-index to get.
+ *
+ * Returns:
+ * {Integer} The z-index value for the specified node (from the node
+ * data itself).
+ */
+ getZIndex: function(node) {
+ return node._style.graphicZIndex;
+ },
+
+ /**
+ * Method: determineZIndex
+ * Determine the z-index for the current node if there isn't one,
+ * and set the maximum value if we've found a new maximum.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ determineZIndex: function(node) {
+ var zIndex = node._style.graphicZIndex;
+
+ // Everything must have a zIndex. If none is specified,
+ // this means the user *must* (hint: assumption) want this
+ // node to succomb to drawing order. To enforce drawing order
+ // over all indexing methods, we'll create a new z-index that's
+ // greater than any currently in the indexer.
+ if (zIndex == null) {
+ zIndex = this.maxZIndex;
+ node._style.graphicZIndex = zIndex;
+ } else if (zIndex > this.maxZIndex) {
+ this.maxZIndex = zIndex;
+ }
+ },
+
+ /**
+ * APIMethod: getNextElement
+ * Get the next element in the order stack.
+ *
+ * Parameters:
+ * index - {Integer} The index of the current node in this.order.
+ *
+ * Returns:
+ * {DOMElement} the node following the index passed in, or
+ * null.
+ */
+ getNextElement: function(index) {
+ var nextIndex = index + 1;
+ if (nextIndex < this.order.length) {
+ var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
+ if (nextElement == undefined) {
+ nextElement = this.getNextElement(nextIndex);
+ }
+ return nextElement;
+ } else {
+ return null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.ElementsIndexer"
+});
+
+/**
+ * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
+ * These are the compare methods for figuring out where a new node should be
+ * placed within the indexer. These methods are very similar to general
+ * sorting methods in that they return -1, 0, and 1 to specify the
+ * direction in which new nodes fall in the ordering.
+ */
+OpenLayers.ElementsIndexer.IndexingMethods = {
+
+ /**
+ * Method: Z_ORDER
+ * This compare method is used by other comparison methods.
+ * It can be used individually for ordering, but is not recommended,
+ * because it doesn't subscribe to drawing order.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER: function(indexer, newNode, nextNode) {
+ var newZIndex = indexer.getZIndex(newNode);
+
+ var returnVal = 0;
+ if (nextNode) {
+ var nextZIndex = indexer.getZIndex(nextNode);
+ returnVal = newZIndex - nextZIndex;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_DRAWING_ORDER
+ * This method orders nodes by their z-index, but does so in a way
+ * that, if there are other nodes with the same z-index, the newest
+ * drawn will be the front most within that z-index. This is the
+ * default indexing method.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ // Make Z_ORDER subscribe to drawing order by pushing it above
+ // all of the other nodes with the same z-index.
+ if (nextNode && returnVal == 0) {
+ returnVal = 1;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_Y_ORDER
+ * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
+ * best describes which ordering methods have precedence (though, the
+ * name would be too long). This method orders nodes by their z-index,
+ * but does so in a way that, if there are other nodes with the same
+ * z-index, the nodes with the lower y position will be "closer" than
+ * those with a higher y position. If two nodes have the exact same y
+ * position, however, then this method will revert to using drawing
+ * order to decide placement.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ if (nextNode && returnVal === 0) {
+ var result = nextNode._boundsBottom - newNode._boundsBottom;
+ returnVal = (result === 0) ? 1 : result;
+ }
+
+ return returnVal;
+ }
+};
+
+/**
+ * Class: OpenLayers.Renderer.Elements
+ * This is another virtual class in that it should never be instantiated by
+ * itself as a Renderer. It exists because there is *tons* of shared
+ * functionality between different vector libraries which use nodes/elements
+ * as a base for rendering vectors.
+ *
+ * The highlevel bits of code that are implemented here are the adding and
+ * removing of geometries, which is essentially the same for any
+ * element-based renderer. The details of creating each node and drawing the
+ * paths are of course different, but the machinery is the same.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * Property: rendererRoot
+ * {DOMElement}
+ */
+ rendererRoot: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: vectorRoot
+ * {DOMElement}
+ */
+ vectorRoot: null,
+
+ /**
+ * Property: textRoot
+ * {DOMElement}
+ */
+ textRoot: null,
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: null,
+
+ /**
+ * Property: xOffset
+ * {Number} Offset to apply to the renderer viewport translation in x
+ * direction. If the renderer extent's center is on the right of the
+ * dateline (i.e. exceeds the world bounds), we shift the viewport to the
+ * left by one world width. This avoids that features disappear from the
+ * map viewport. Because our dateline handling logic in other places
+ * ensures that extents crossing the dateline always have a center
+ * exceeding the world bounds on the left, we need this offset to make sure
+ * that the same is true for the renderer extent in pixel space as well.
+ */
+ xOffset: 0,
+
+ /**
+ * Property: rightOfDateLine
+ * {Boolean} Keeps track of the location of the map extent relative to the
+ * date line. The <setExtent> method compares this value (which is the one
+ * from the previous <setExtent> call) with the current position of the map
+ * extent relative to the date line and updates the xOffset when the extent
+ * has moved from one side of the date line to the other.
+ */
+
+ /**
+ * Property: Indexer
+ * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
+ * created upon initialization if the zIndexing or yOrdering options
+ * passed to this renderer's constructor are set to true.
+ */
+ indexer: null,
+
+ /**
+ * Constant: BACKGROUND_ID_SUFFIX
+ * {String}
+ */
+ BACKGROUND_ID_SUFFIX: "_background",
+
+ /**
+ * Constant: LABEL_ID_SUFFIX
+ * {String}
+ */
+ LABEL_ID_SUFFIX: "_label",
+
+ /**
+ * Constant: LABEL_OUTLINE_SUFFIX
+ * {String}
+ */
+ LABEL_OUTLINE_SUFFIX: "_outline",
+
+ /**
+ * Constructor: OpenLayers.Renderer.Elements
+ *
+ * Parameters:
+ * containerID - {String}
+ * options - {Object} options for this renderer.
+ *
+ * Supported options are:
+ * yOrdering - {Boolean} Whether to use y-ordering
+ * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+ * if yOrdering is set to true.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+
+ this.rendererRoot = this.createRenderRoot();
+ this.root = this.createRoot("_root");
+ this.vectorRoot = this.createRoot("_vroot");
+ this.textRoot = this.createRoot("_troot");
+
+ this.root.appendChild(this.vectorRoot);
+ this.root.appendChild(this.textRoot);
+
+ this.rendererRoot.appendChild(this.root);
+ this.container.appendChild(this.rendererRoot);
+
+ if(options && (options.zIndexing || options.yOrdering)) {
+ this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+
+ this.clear();
+
+ this.rendererRoot = null;
+ this.root = null;
+ this.xmlns = null;
+
+ OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clear
+ * Remove all the elements from the root
+ */
+ clear: function() {
+ var child;
+ var root = this.vectorRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ root = this.textRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ if (this.indexer) {
+ this.indexer.clear();
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var rightOfDateLine,
+ ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio),
+ world = this.map.getMaxExtent();
+ if (world.right > extent.left && world.right < extent.right) {
+ rightOfDateLine = true;
+ } else if (world.left > extent.left && world.left < extent.right) {
+ rightOfDateLine = false;
+ }
+ if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
+ coordSysUnchanged = false;
+ this.xOffset = rightOfDateLine === true ?
+ world.getWidth() / resolution : 0;
+ }
+ this.rightOfDateLine = rightOfDateLine;
+ }
+ return coordSysUnchanged;
+ },
+
+ /**
+ * Method: getNodeType
+ * This function is in charge of asking the specific renderer which type
+ * of node to create for the given geometry and style. All geometries
+ * in an Elements-based renderer consist of one node and some
+ * attributes. We have the nodeFactory() function which creates a node
+ * for us, but it takes a 'type' as input, and that is precisely what
+ * this function tells us.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) { },
+
+ /**
+ * Method: drawGeometry
+ * Draw the geometry, creating new nodes, setting paths, setting style,
+ * setting featureId on the node. This method should only be called
+ * by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the geometry has been drawn completely; null if
+ * incomplete; false otherwise
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ var rendered = true;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0, len=geometry.components.length; i<len; i++) {
+ rendered = this.drawGeometry(
+ geometry.components[i], style, featureId) && rendered;
+ }
+ return rendered;
+ }
+
+ rendered = false;
+ var removeBackground = false;
+ if (style.display != "none") {
+ if (style.backgroundGraphic) {
+ this.redrawBackgroundNode(geometry.id, geometry, style,
+ featureId);
+ } else {
+ removeBackground = true;
+ }
+ rendered = this.redrawNode(geometry.id, geometry, style,
+ featureId);
+ }
+ if (rendered == false) {
+ var node = document.getElementById(geometry.id);
+ if (node) {
+ if (node._style.backgroundGraphic) {
+ removeBackground = true;
+ }
+ node.parentNode.removeChild(node);
+ }
+ }
+ if (removeBackground) {
+ var node = document.getElementById(
+ geometry.id + this.BACKGROUND_ID_SUFFIX);
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: redrawNode
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawNode: function(id, geometry, style, featureId) {
+ style = this.applyDefaultSymbolizer(style);
+ // Get the node if it's already on the map.
+ var node = this.nodeFactory(id, this.getNodeType(geometry, style));
+
+ // Set the data for the node, then draw it.
+ node._featureId = featureId;
+ node._boundsBottom = geometry.getBounds().bottom;
+ node._geometryClass = geometry.CLASS_NAME;
+ node._style = style;
+
+ var drawResult = this.drawGeometryNode(node, geometry, style);
+ if(drawResult === false) {
+ return false;
+ }
+
+ node = drawResult.node;
+
+ // Insert the node into the indexer so it can show us where to
+ // place it. Note that this operation is O(log(n)). If there's a
+ // performance problem (when dragging, for instance) this is
+ // likely where it would be.
+ if (this.indexer) {
+ var insert = this.indexer.insert(node);
+ if (insert) {
+ this.vectorRoot.insertBefore(node, insert);
+ } else {
+ this.vectorRoot.appendChild(node);
+ }
+ } else {
+ // if there's no indexer, simply append the node to root,
+ // but only if the node is a new one
+ if (node.parentNode !== this.vectorRoot){
+ this.vectorRoot.appendChild(node);
+ }
+ }
+
+ this.postDraw(node);
+
+ return drawResult.complete;
+ },
+
+ /**
+ * Method: redrawBackgroundNode
+ * Redraws the node using special 'background' style properties. Basically
+ * just calls redrawNode(), but instead of directly using the
+ * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
+ * 'graphicZIndex' properties directly from the specified 'style'
+ * parameter, we create a new style object and set those properties
+ * from the corresponding 'background'-prefixed properties from
+ * specified 'style' parameter.
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawBackgroundNode: function(id, geometry, style, featureId) {
+ var backgroundStyle = OpenLayers.Util.extend({}, style);
+
+ // Set regular style attributes to apply to the background styles.
+ backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
+ backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
+ backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
+ backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
+ backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
+ backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
+
+ // Erase background styles.
+ backgroundStyle.backgroundGraphic = null;
+ backgroundStyle.backgroundXOffset = null;
+ backgroundStyle.backgroundYOffset = null;
+ backgroundStyle.backgroundGraphicZIndex = null;
+
+ return this.redrawNode(
+ id + this.BACKGROUND_ID_SUFFIX,
+ geometry,
+ backgroundStyle,
+ null
+ );
+ },
+
+ /**
+ * Method: drawGeometryNode
+ * Given a node, draw a geometry on the specified layer.
+ * node and geometry are required arguments, style is optional.
+ * This method is only called by the render itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {Object} a hash with properties "node" (the drawn node) and "complete"
+ * (null if parts of the geometry could not be drawn, false if nothing
+ * could be drawn)
+ */
+ drawGeometryNode: function(node, geometry, style) {
+ style = style || node._style;
+
+ var options = {
+ 'isFilled': style.fill === undefined ?
+ true :
+ style.fill,
+ 'isStroked': style.stroke === undefined ?
+ !!style.strokeWidth :
+ style.stroke
+ };
+ var drawn;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if(style.graphic === false) {
+ options.isFilled = false;
+ options.isStroked = false;
+ }
+ drawn = this.drawPoint(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ options.isFilled = false;
+ drawn = this.drawLineString(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ drawn = this.drawLinearRing(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ drawn = this.drawPolygon(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ drawn = this.drawRectangle(node, geometry);
+ break;
+ default:
+ break;
+ }
+
+ node._options = options;
+
+ //set style
+ //TBD simplify this
+ if (drawn != false) {
+ return {
+ node: this.setStyle(node, style, options, geometry),
+ complete: drawn
+ };
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: postDraw
+ * Things that have do be done after the geometry node is appended
+ * to its parent node. To be overridden by subclasses.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {},
+
+ /**
+ * Method: drawPoint
+ * Virtual function for drawing Point Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {},
+
+ /**
+ * Method: drawLineString
+ * Virtual function for drawing LineString Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {},
+
+ /**
+ * Method: drawLinearRing
+ * Virtual function for drawing LinearRing Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {},
+
+ /**
+ * Method: drawPolygon
+ * Virtual function for drawing Polygon Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {},
+
+ /**
+ * Method: drawRectangle
+ * Virtual function for drawing Rectangle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {},
+
+ /**
+ * Method: drawCircle
+ * Virtual function for drawing Circle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry) {},
+
+ /**
+ * Method: removeText
+ * Removes a label
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {
+ var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
+ if (label) {
+ this.textRoot.removeChild(label);
+ }
+ var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
+ if (outline) {
+ this.textRoot.removeChild(outline);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var target = evt.target;
+ var useElement = target && target.correspondingUseElement;
+ var node = useElement ? useElement : (target || evt.srcElement);
+ return node._featureId;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. In the case of a multi-geometry,
+ * we cycle through and recurse on ourselves. Otherwise, we look for a
+ * node with the geometry.id, destroy its geometry, and remove it from
+ * the DOM.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
+ for (var i=0, len=geometry.components.length; i<len; i++) {
+ this.eraseGeometry(geometry.components[i], featureId);
+ }
+ } else {
+ var element = OpenLayers.Util.getElement(geometry.id);
+ if (element && element.parentNode) {
+ if (element.geometry) {
+ element.geometry.destroy();
+ element.geometry = null;
+ }
+ element.parentNode.removeChild(element);
+
+ if (this.indexer) {
+ this.indexer.remove(element);
+ }
+
+ if (element._style.backgroundGraphic) {
+ var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+ var bElem = OpenLayers.Util.getElement(backgroundId);
+ if (bElem && bElem.parentNode) {
+ // No need to destroy the geometry since the element and the background
+ // node share the same geometry.
+ bElem.parentNode.removeChild(bElem);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: nodeFactory
+ * Create new node of the specified type, with the (optional) specified id.
+ *
+ * If node already exists with same ID and a different type, we remove it
+ * and then call ourselves again to recreate it.
+ *
+ * Parameters:
+ * id - {String}
+ * type - {String} type Kind of node to draw.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ */
+ nodeFactory: function(id, type) {
+ var node = OpenLayers.Util.getElement(id);
+ if (node) {
+ if (!this.nodeTypeCompare(node, type)) {
+ node.parentNode.removeChild(node);
+ node = this.nodeFactory(id, type);
+ }
+ } else {
+ node = this.createNode(type, id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ * This function must be overridden by subclasses.
+ */
+ nodeTypeCompare: function(node, type) {},
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw.
+ * id - {String} Id for node.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ * This function must be overridden by subclasses.
+ */
+ createNode: function(type, id) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {
+ var root = this.root;
+ if(renderer.root.parentNode == this.rendererRoot) {
+ root = renderer.root;
+ }
+ root.parentNode.removeChild(root);
+ renderer.rendererRoot.appendChild(root);
+ },
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.root.parentNode.parentNode.id;
+ },
+
+ /**
+ * Method: isComplexSymbol
+ * Determines if a symbol cannot be rendered using drawCircle
+ *
+ * Parameters:
+ * graphicName - {String}
+ *
+ * Returns
+ * {Boolean} true if the symbol is complex, false if not
+ */
+ isComplexSymbol: function(graphicName) {
+ return (graphicName != "circle") && !!graphicName;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
+/* ======================================================================
+ OpenLayers/Control.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Control
+ * Controls affect the display or behavior of the map. They allow everything
+ * from panning and zooming to displaying a scale indicator. Controls by
+ * default are added to the map they are contained within however it is
+ * possible to add a control to an external div by passing the div in the
+ * options parameter.
+ *
+ * Example:
+ * The following example shows how to add many of the common controls
+ * to a map.
+ *
+ * > var map = new OpenLayers.Map('map', { controls: [] });
+ * >
+ * > map.addControl(new OpenLayers.Control.PanZoomBar());
+ * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
+ * > map.addControl(new OpenLayers.Control.Permalink());
+ * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
+ * > map.addControl(new OpenLayers.Control.MousePosition());
+ * > map.addControl(new OpenLayers.Control.OverviewMap());
+ * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
+ *
+ * The next code fragment is a quick example of how to intercept
+ * shift-mouse click to display the extent of the bounding box
+ * dragged out by the user. Usually controls are not created
+ * in exactly this manner. See the source for a more complete
+ * example:
+ *
+ * > var control = new OpenLayers.Control();
+ * > OpenLayers.Util.extend(control, {
+ * > draw: function () {
+ * > // this Handler.Box will intercept the shift-mousedown
+ * > // before Control.MouseDefault gets to see it
+ * > this.box = new OpenLayers.Handler.Box( control,
+ * > {"done": this.notice},
+ * > {keyMask: OpenLayers.Handler.MOD_SHIFT});
+ * > this.box.activate();
+ * > },
+ * >
+ * > notice: function (bounds) {
+ * > OpenLayers.Console.userError(bounds);
+ * > }
+ * > });
+ * > map.addControl(control);
+ *
+ */
+OpenLayers.Control = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in the addControl() function in
+ * OpenLayers.Map
+ */
+ map: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement} The element that contains the control, if not present the
+ * control is placed inside the map.
+ */
+ div: null,
+
+ /**
+ * APIProperty: type
+ * {Number} Controls can have a 'type'. The type determines the type of
+ * interactions which are possible with them when they are placed in an
+ * <OpenLayers.Control.Panel>.
+ */
+ type: null,
+
+ /**
+ * Property: allowSelection
+ * {Boolean} By default, controls do not allow selection, because
+ * it may interfere with map dragging. If this is true, OpenLayers
+ * will not prevent selection of the control.
+ * Default is false.
+ */
+ allowSelection: false,
+
+ /**
+ * Property: displayClass
+ * {string} This property is used for CSS related to the drawing of the
+ * Control.
+ */
+ displayClass: "",
+
+ /**
+ * APIProperty: title
+ * {string} This property is used for showing a tooltip over the
+ * Control.
+ */
+ title: "",
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * false.
+ */
+ autoActivate: false,
+
+ /**
+ * APIProperty: active
+ * {Boolean} The control is active (read-only). Use <activate> and
+ * <deactivate> to change control state.
+ */
+ active: null,
+
+ /**
+ * Property: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+ handlerOptions: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler>} null
+ */
+ handler: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to control.events.object (a reference
+ * to the control).
+ * element - {DOMElement} A reference to control.events.element (which
+ * will be null unless documented otherwise).
+ *
+ * Supported map event types:
+ * activate - Triggered when activated.
+ * deactivate - Triggered when deactivated.
+ */
+ events: null,
+
+ /**
+ * Constructor: OpenLayers.Control
+ * Create an OpenLayers Control. The options passed as a parameter
+ * directly extend the control. For example passing the following:
+ *
+ * > var control = new OpenLayers.Control({div: myDiv});
+ *
+ * Overrides the default div attribute value of null.
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function (options) {
+ // We do this before the extend so that instances can override
+ // className in options.
+ this.displayClass =
+ this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ if (this.id == null) {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function () {
+ if(this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.events = null;
+ }
+ this.eventListeners = null;
+
+ // eliminate circular references
+ if (this.handler) {
+ this.handler.destroy();
+ this.handler = null;
+ }
+ if(this.handlers) {
+ for(var key in this.handlers) {
+ if(this.handlers.hasOwnProperty(key) &&
+ typeof this.handlers[key].destroy == "function") {
+ this.handlers[key].destroy();
+ }
+ }
+ this.handlers = null;
+ }
+ if (this.map) {
+ this.map.removeControl(this);
+ this.map = null;
+ }
+ this.div = null;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.map = map;
+ if (this.handler) {
+ this.handler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: draw
+ * The draw method is called when the control is ready to be displayed
+ * on the page. If a div has not been created one is created. Controls
+ * with a visual component will almost always want to override this method
+ * to customize the look of control.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The top-left pixel position of the control
+ * or null.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function (px) {
+ if (this.div == null) {
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.className = this.displayClass;
+ if (!this.allowSelection) {
+ this.div.className += " olControlNoSelect";
+ this.div.setAttribute("unselectable", "on", 0);
+ this.div.onselectstart = OpenLayers.Function.False;
+ }
+ if (this.title != "") {
+ this.div.title = this.title;
+ }
+ }
+ if (px != null) {
+ this.position = px.clone();
+ }
+ this.moveTo(this.position);
+ return this.div;
+ },
+
+ /**
+ * Method: moveTo
+ * Sets the left and top style attributes to the passed in pixel
+ * coordinates.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Explicitly activates a control and it's associated
+ * handler if one has been set. Controls can be
+ * deactivated by calling the deactivate() method.
+ *
+ * Returns:
+ * {Boolean} True if the control was successfully activated or
+ * false if the control was already active.
+ */
+ activate: function () {
+ if (this.active) {
+ return false;
+ }
+ if (this.handler) {
+ this.handler.activate();
+ }
+ this.active = true;
+ if(this.map) {
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("activate");
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates a control and it's associated handler if any. The exact
+ * effect of this depends on the control itself.
+ *
+ * Returns:
+ * {Boolean} True if the control was effectively deactivated or false
+ * if the control was already inactive.
+ */
+ deactivate: function () {
+ if (this.active) {
+ if (this.handler) {
+ this.handler.deactivate();
+ }
+ this.active = false;
+ if(this.map) {
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("deactivate");
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Control"
+});
+
+/**
+ * Constant: OpenLayers.Control.TYPE_BUTTON
+ */
+OpenLayers.Control.TYPE_BUTTON = 1;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOGGLE
+ */
+OpenLayers.Control.TYPE_TOGGLE = 2;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOOL
+ */
+OpenLayers.Control.TYPE_TOOL = 3;
+/* ======================================================================
+ OpenLayers/Control/Panel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ * The Panel control is a container for other controls. With it toolbars
+ * may be composed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: controls
+ * {Array(<OpenLayers.Control>)}
+ */
+ controls: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: defaultControl
+ * {<OpenLayers.Control>} The control which is activated when the control is
+ * activated (turned on), which also happens at instantiation.
+ * If <saveState> is true, <defaultControl> will be nullified after the
+ * first activation of the panel.
+ */
+ defaultControl: null,
+
+ /**
+ * APIProperty: saveState
+ * {Boolean} If set to true, the active state of this panel's controls will
+ * be stored on panel deactivation, and restored on reactivation. Default
+ * is false.
+ */
+ saveState: false,
+
+ /**
+ * APIProperty: allowDepress
+ * {Boolean} If is true the <OpenLayers.Control.TYPE_TOOL> controls can
+ * be deactivated by clicking the icon that represents them. Default
+ * is false.
+ */
+ allowDepress: false,
+
+ /**
+ * Property: activeState
+ * {Object} stores the active state of this panel's controls.
+ */
+ activeState: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Panel
+ * Create a new control panel.
+ *
+ * Each control in the panel is represented by an icon. When clicking
+ * on an icon, the <activateControl> method is called.
+ *
+ * Specific properties for controls on a panel:
+ * type - {Number} One of <OpenLayers.Control.TYPE_TOOL>,
+ * <OpenLayers.Control.TYPE_TOGGLE>, <OpenLayers.Control.TYPE_BUTTON>.
+ * If not provided, <OpenLayers.Control.TYPE_TOOL> is assumed.
+ * title - {string} Text displayed when mouse is over the icon that
+ * represents the control.
+ *
+ * The <OpenLayers.Control.type> of a control determines the behavior when
+ * clicking its icon:
+ * <OpenLayers.Control.TYPE_TOOL> - The control is activated and other
+ * controls of this type in the same panel are deactivated. This is
+ * the default type.
+ * <OpenLayers.Control.TYPE_TOGGLE> - The active state of the control is
+ * toggled.
+ * <OpenLayers.Control.TYPE_BUTTON> - The
+ * <OpenLayers.Control.Button.trigger> method of the control is called,
+ * but its active state is not changed.
+ *
+ * If a control is <OpenLayers.Control.active>, it will be drawn with the
+ * olControl[Name]ItemActive class, otherwise with the
+ * olControl[Name]ItemInactive class.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.controls = [];
+ this.activeState = {};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onButtonClick);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ for (var ctl, i = this.controls.length - 1; i >= 0; i--) {
+ ctl = this.controls[i];
+ if (ctl.events) {
+ ctl.events.un({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ ctl.panel_div = null;
+ }
+ this.activeState = null;
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ if (control === this.defaultControl ||
+ (this.saveState && this.activeState[control.id])) {
+ control.activate();
+ }
+ }
+ if (this.saveState === true) {
+ this.defaultControl = null;
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ this.activeState[control.id] = control.deactivate();
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ this.addControlsToMap(this.controls);
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function() {
+ for (var l=this.div.childNodes.length, i=l-1; i>=0; i--) {
+ this.div.removeChild(this.div.childNodes[i]);
+ }
+ this.div.innerHTML = "";
+ if (this.active) {
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ this.div.appendChild(this.controls[i].panel_div);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: activateControl
+ * This method is called when the user click on the icon representing a
+ * control in the panel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ */
+ activateControl: function (control) {
+ if (!this.active) { return false; }
+ if (control.type == OpenLayers.Control.TYPE_BUTTON) {
+ control.trigger();
+ return;
+ }
+ if (control.type == OpenLayers.Control.TYPE_TOGGLE) {
+ if (control.active) {
+ control.deactivate();
+ } else {
+ control.activate();
+ }
+ return;
+ }
+ if (this.allowDepress && control.active) {
+ control.deactivate();
+ } else {
+ var c;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ c = this.controls[i];
+ if (c != control &&
+ (c.type === OpenLayers.Control.TYPE_TOOL || c.type == null)) {
+ c.deactivate();
+ }
+ }
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: addControls
+ * To build a toolbar, you add a set of controls to it. addControls
+ * lets you add a single control or a list of controls to the
+ * Control Panel.
+ *
+ * Parameters:
+ * controls - {<OpenLayers.Control>} Controls to add in the panel.
+ */
+ addControls: function(controls) {
+ if (!(OpenLayers.Util.isArray(controls))) {
+ controls = [controls];
+ }
+ this.controls = this.controls.concat(controls);
+
+ for (var i=0, len=controls.length; i<len; i++) {
+ var control = controls[i],
+ element = this.createControlMarkup(control);
+ OpenLayers.Element.addClass(element,
+ control.displayClass + "ItemInactive");
+ OpenLayers.Element.addClass(element, "olButton");
+ if (control.title != "" && !element.title) {
+ element.title = control.title;
+ }
+ control.panel_div = element;
+ }
+
+ if (this.map) { // map.addControl() has already been called on the panel
+ this.addControlsToMap(controls);
+ this.redraw();
+ }
+ },
+
+ /**
+ * APIMethod: createControlMarkup
+ * This function just creates a div for the control. If specific HTML
+ * markup is needed this function can be overridden in specific classes,
+ * or at panel instantiation time:
+ *
+ * Example:
+ * (code)
+ * var panel = new OpenLayers.Control.Panel({
+ * defaultControl: control,
+ * // ovverride createControlMarkup to create actual buttons
+ * // including texts wrapped into span elements.
+ * createControlMarkup: function(control) {
+ * var button = document.createElement('button'),
+ * span = document.createElement('span');
+ * if (control.text) {
+ * span.innerHTML = control.text;
+ * }
+ * return button;
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to create the HTML
+ * markup for.
+ *
+ * Returns:
+ * {DOMElement} The markup.
+ */
+ createControlMarkup: function(control) {
+ return document.createElement("div");
+ },
+
+ /**
+ * Method: addControlsToMap
+ * Only for internal use in draw() and addControls() methods.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)} Controls to add into map.
+ */
+ addControlsToMap: function (controls) {
+ var control;
+ for (var i=0, len=controls.length; i<len; i++) {
+ control = controls[i];
+ if (control.autoActivate === true) {
+ control.autoActivate = false;
+ this.map.addControl(control);
+ control.autoActivate = true;
+ } else {
+ this.map.addControl(control);
+ control.deactivate();
+ }
+ control.events.on({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ },
+
+ /**
+ * Method: iconOn
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOn: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Inactive\\b");
+ d.className = d.className.replace(re, "$1Active");
+ },
+
+ /**
+ * Method: iconOff
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOff: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Active\\b");
+ d.className = d.className.replace(re, "$1Inactive");
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function (evt) {
+ var controls = this.controls,
+ button = evt.buttonElement;
+ for (var i=controls.length-1; i>=0; --i) {
+ if (controls[i].panel_div === button) {
+ this.activateControl(controls[i]);
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(control[property]) evaluates to true, the control will be
+ * included in the array returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this.controls, function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getControlsByName
+ * Get a list of contorls with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A control name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(control.name) evaluates to true, the control will be included
+ * in the list of controls returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByName: function(match) {
+ return this.getControlsBy("name", match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given type (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The type can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given type.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
+/* ======================================================================
+ OpenLayers/Strategy.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class. Not to be instantiated directly. Use
+ * one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The layer this strategy belongs to.
+ */
+ layer: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: active
+ * {Boolean} The control is active.
+ */
+ active: null,
+
+ /**
+ * Property: autoActivate
+ * {Boolean} The creator of the strategy can set autoActivate to false
+ * to fully control when the protocol is activated and deactivated.
+ * Defaults to true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the strategy can set autoDestroy to false
+ * to fully control when the strategy is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Constructor: OpenLayers.Strategy
+ * Abstract class for vector strategies. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ // set the active property here, so that user cannot override it
+ this.active = false;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the strategy.
+ */
+ destroy: function() {
+ this.deactivate();
+ this.layer = null;
+ this.options = null;
+ },
+
+ /**
+ * Method: setLayer
+ * Called to set the <layer> property.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layer) {
+ this.layer = layer;
+ },
+
+ /**
+ * Method: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ if (!this.active) {
+ this.active = true;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ if (this.active) {
+ this.active = false;
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Fixed.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: preload
+ * {Boolean} Load data before layer made visible. Enabling this may result
+ * in considerable overhead if your application loads many data layers
+ * that are not visible by default. Default is false.
+ */
+ preload: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Fixed
+ * Create a new Fixed strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Activate the strategy: load data or add listener to load when visible
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
+ if(activated) {
+ this.layer.events.on({
+ "refresh": this.load,
+ scope: this
+ });
+ if(this.layer.visibility == true || this.preload) {
+ this.load();
+ } else {
+ this.layer.events.on({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Undo what is done in <activate>.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "refresh": this.load,
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: load
+ * Tells protocol to load data and unhooks the visibilitychanged event
+ *
+ * Parameters:
+ * options - {Object} options to pass to protocol read.
+ */
+ load: function(options) {
+ var layer = this.layer;
+ layer.events.triggerEvent("loadstart", {filter: layer.filter});
+ layer.protocol.read(OpenLayers.Util.applyDefaults({
+ callback: this.merge,
+ filter: layer.filter,
+ scope: this
+ }, options));
+ layer.events.un({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: merge
+ * Add all features to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ var layer = this.layer;
+ layer.destroyFeatures();
+ var features = resp.features;
+ if (features && features.length > 0) {
+ var remote = layer.projection;
+ var local = layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ layer.addFeatures(features);
+ }
+ layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
+/* ======================================================================
+ OpenLayers/Control/Zoom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Zoom
+ * The Zoom control is a pair of +/- links for zooming in and out.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: zoomInText
+ * {String}
+ * Text for zoom-in link. Default is "+".
+ */
+ zoomInText: "+",
+
+ /**
+ * APIProperty: zoomInId
+ * {String}
+ * Instead of having the control create a zoom in link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomInLink" will be searched for
+ * and used if it exists.
+ */
+ zoomInId: "olZoomInLink",
+
+ /**
+ * APIProperty: zoomOutText
+ * {String}
+ * Text for zoom-out link. Default is "\u2212".
+ */
+ zoomOutText: "\u2212",
+
+ /**
+ * APIProperty: zoomOutId
+ * {String}
+ * Instead of having the control create a zoom out link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomOutLink" will be searched for
+ * and used if it exists.
+ */
+ zoomOutId: "olZoomOutLink",
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DOMElement containing the zoom links.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.prototype.draw.apply(this),
+ links = this.getOrCreateLinks(div),
+ zoomIn = links.zoomIn,
+ zoomOut = links.zoomOut,
+ eventsInstance = this.map.events;
+
+ if (zoomOut.parentNode !== div) {
+ eventsInstance = this.events;
+ eventsInstance.attachToElement(zoomOut.parentNode);
+ }
+ eventsInstance.register("buttonclick", this, this.onZoomClick);
+
+ this.zoomInLink = zoomIn;
+ this.zoomOutLink = zoomOut;
+ return div;
+ },
+
+ /**
+ * Method: getOrCreateLinks
+ *
+ * Parameters:
+ * el - {DOMElement}
+ *
+ * Return:
+ * {Object} Object with zoomIn and zoomOut properties referencing links.
+ */
+ getOrCreateLinks: function(el) {
+ var zoomIn = document.getElementById(this.zoomInId),
+ zoomOut = document.getElementById(this.zoomOutId);
+ if (!zoomIn) {
+ zoomIn = document.createElement("a");
+ zoomIn.href = "#zoomIn";
+ zoomIn.appendChild(document.createTextNode(this.zoomInText));
+ zoomIn.className = "olControlZoomIn";
+ el.appendChild(zoomIn);
+ }
+ OpenLayers.Element.addClass(zoomIn, "olButton");
+ if (!zoomOut) {
+ zoomOut = document.createElement("a");
+ zoomOut.href = "#zoomOut";
+ zoomOut.appendChild(document.createTextNode(this.zoomOutText));
+ zoomOut.className = "olControlZoomOut";
+ el.appendChild(zoomOut);
+ }
+ OpenLayers.Element.addClass(zoomOut, "olButton");
+ return {
+ zoomIn: zoomIn, zoomOut: zoomOut
+ };
+ },
+
+ /**
+ * Method: onZoomClick
+ * Called when zoomin/out link is clicked.
+ */
+ onZoomClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.zoomInLink) {
+ this.map.zoomIn();
+ } else if (button === this.zoomOutLink) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onZoomClick);
+ }
+ delete this.zoomInLink;
+ delete this.zoomOutLink;
+ OpenLayers.Control.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Zoom"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Polygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon
+ * Polygon is a collection of Geometry.LinearRings.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Polygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LinearRing"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Polygon
+ * Constructor for a Polygon geometry.
+ * The first ring (this.component[0])is the outer bounds of the polygon and
+ * all subsequent rings (this.component[1-n]) are internal holes.
+ *
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LinearRing>)}
+ */
+
+ /**
+ * APIMethod: getArea
+ * Calculated by subtracting the areas of the internal holes from the
+ * area of the outer hole.
+ *
+ * Returns:
+ * {float} The area of the geometry
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getArea());
+ for (var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getArea());
+ }
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the polygon in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ if(this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getGeodesicArea(projection));
+ for(var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getGeodesicArea(projection));
+ }
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a polygon. Points on a polygon edge are
+ * considered inside.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the polygon. Returns 1 if the
+ * point is on an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var numRings = this.components.length;
+ var contained = false;
+ if(numRings > 0) {
+ // check exterior ring - 1 means on edge, boolean otherwise
+ contained = this.components[0].containsPoint(point);
+ if(contained !== 1) {
+ if(contained && numRings > 1) {
+ // check interior rings
+ var hole;
+ for(var i=1; i<numRings; ++i) {
+ hole = this.components[i].containsPoint(point);
+ if(hole) {
+ if(hole === 1) {
+ // on edge
+ contained = 1;
+ } else {
+ // in hole
+ contained = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var i, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+ geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ // check if rings/linestrings intersect
+ for(i=0, len=this.components.length; i<len; ++i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ if(!intersect) {
+ // check if this poly contains points of the ring/linestring
+ for(i=0, len=geometry.components.length; i<len; ++i) {
+ intersect = this.containsPoint(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ } else {
+ for(i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = this.intersects(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ // check case where this poly is wholly contained by another
+ if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ // exterior ring points will be contained in the other geometry
+ var ring = this.components[0];
+ for(i=0, len=ring.components.length; i<len; ++i) {
+ intersect = geometry.containsPoint(ring.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var result;
+ // this is the case where we might not be looking for distance to edge
+ if(!edge && this.intersects(geometry)) {
+ result = 0;
+ } else {
+ result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
+ this, [geometry, options]
+ );
+ }
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Polygon"
+});
+
+/**
+ * APIMethod: createRegularPolygon
+ * Create a regular polygon around a radius. Useful for creating circles
+ * and the like.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.Geometry.Point>} center of polygon.
+ * radius - {Float} distance to vertex, in map units.
+ * sides - {Integer} Number of sides. 20 approximates a circle.
+ * rotation - {Float} original angle of rotation, in degrees.
+ */
+OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {
+ var angle = Math.PI * ((1/sides) - (1/2));
+ if(rotation) {
+ angle += (rotation / 180) * Math.PI;
+ }
+ var rotatedAngle, x, y;
+ var points = [];
+ for(var i=0; i<sides; ++i) {
+ rotatedAngle = angle + (i * 2 * Math.PI / sides);
+ x = origin.x + (radius * Math.cos(rotatedAngle));
+ y = origin.y + (radius * Math.sin(rotatedAngle));
+ points.push(new OpenLayers.Geometry.Point(x, y));
+ }
+ var ring = new OpenLayers.Geometry.LinearRing(points);
+ return new OpenLayers.Geometry.Polygon([ring]);
+};
+/* ======================================================================
+ OpenLayers/Geometry/MultiPolygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components. Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ */
+OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Polygon"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPolygon
+ * Create a new MultiPolygon geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
+ * used to generate the MultiPolygon
+ *
+ */
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
+});
+/* ======================================================================
+ OpenLayers/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ * class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>}
+ */
+ lonlat: null,
+
+ /**
+ * Property: data
+ * {Object}
+ */
+ data: null,
+
+ /**
+ * Property: marker
+ * {<OpenLayers.Marker>}
+ */
+ marker: null,
+
+ /**
+ * APIProperty: popupClass
+ * {<OpenLayers.Class>} The class which will be used to instantiate
+ * a new Popup. Default is <OpenLayers.Popup.Anchored>.
+ */
+ popupClass: null,
+
+ /**
+ * Property: popup
+ * {<OpenLayers.Popup>}
+ */
+ popup: null,
+
+ /**
+ * Constructor: OpenLayers.Feature
+ * Constructor for features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * lonlat - {<OpenLayers.LonLat>}
+ * data - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Feature>}
+ */
+ initialize: function(layer, lonlat, data) {
+ this.layer = layer;
+ this.lonlat = lonlat;
+ this.data = (data != null) ? data : {};
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ //remove the popup from the map
+ if ((this.layer != null) && (this.layer.map != null)) {
+ if (this.popup != null) {
+ this.layer.map.removePopup(this.popup);
+ }
+ }
+ // remove the marker from the layer
+ if (this.layer != null && this.marker != null) {
+ this.layer.removeMarker(this.marker);
+ }
+
+ this.layer = null;
+ this.id = null;
+ this.lonlat = null;
+ this.data = null;
+ if (this.marker != null) {
+ this.destroyMarker(this.marker);
+ this.marker = null;
+ }
+ if (this.popup != null) {
+ this.destroyPopup(this.popup);
+ this.popup = null;
+ }
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is currently visible on screen
+ * (based on its 'lonlat' property)
+ */
+ onScreen:function() {
+
+ var onScreen = false;
+ if ((this.layer != null) && (this.layer.map != null)) {
+ var screenBounds = this.layer.map.getExtent();
+ onScreen = screenBounds.containsLonLat(this.lonlat);
+ }
+ return onScreen;
+ },
+
+
+ /**
+ * Method: createMarker
+ * Based on the data associated with the Feature, create and return a marker object.
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
+ * set in this.data. If no 'lonlat' is set, returns null. If no
+ * 'icon' is set, OpenLayers.Marker() will load the default image.
+ *
+ * Note - this.marker is set to return value
+ *
+ */
+ createMarker: function() {
+
+ if (this.lonlat != null) {
+ this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
+ }
+ return this.marker;
+ },
+
+ /**
+ * Method: destroyMarker
+ * Destroys marker.
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ this.marker.destroy();
+ },
+
+ /**
+ * Method: createPopup
+ * Creates a popup object created from the 'lonlat', 'popupSize',
+ * and 'popupContentHTML' properties set in this.data. It uses
+ * this.marker.icon as default anchor.
+ *
+ * If no 'lonlat' is set, returns null.
+ * If no this.marker has been created, no anchor is sent.
+ *
+ * Note - the returned popup object is 'owned' by the feature, so you
+ * cannot use the popup's destroy method to discard the popup.
+ * Instead, you must use the feature's destroyPopup
+ *
+ * Note - this.popup is set to return value
+ *
+ * Parameters:
+ * closeBox - {Boolean} create popup with closebox or not
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} Returns the created popup, which is also set
+ * as 'popup' property of this feature. Will be of whatever type
+ * specified by this feature's 'popupClass' property, but must be
+ * of type <OpenLayers.Popup>.
+ *
+ */
+ createPopup: function(closeBox) {
+
+ if (this.lonlat != null) {
+ if (!this.popup) {
+ var anchor = (this.marker) ? this.marker.icon : null;
+ var popupClass = this.popupClass ?
+ this.popupClass : OpenLayers.Popup.Anchored;
+ this.popup = new popupClass(this.id + "_popup",
+ this.lonlat,
+ this.data.popupSize,
+ this.data.popupContentHTML,
+ anchor,
+ closeBox);
+ }
+ if (this.data.overflow != null) {
+ this.popup.contentDiv.style.overflow = this.data.overflow;
+ }
+
+ this.popup.feature = this;
+ }
+ return this.popup;
+ },
+
+
+ /**
+ * Method: destroyPopup
+ * Destroys the popup created via createPopup.
+ *
+ * As with the marker, if user overrides the createPopup() function, s/he
+ * should also be able to override the destruction
+ */
+ destroyPopup: function() {
+ if (this.popup) {
+ this.popup.feature = null;
+ this.popup.destroy();
+ this.popup = null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature"
+});
+/* ======================================================================
+ OpenLayers/Feature/Vector.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+// TRASH THIS
+OpenLayers.State = {
+ /** states */
+ UNKNOWN: 'Unknown',
+ INSERT: 'Insert',
+ UPDATE: 'Update',
+ DELETE: 'Delete'
+};
+
+/**
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.Vector
+ * Vector features use the OpenLayers.Geometry classes as geometry description.
+ * They have an 'attributes' property, which is the data object, and a 'style'
+ * property, the default values of which are defined in the
+ * <OpenLayers.Feature.Vector.style> objects.
+ *
+ * Inherits from:
+ * - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+ /**
+ * Property: fid
+ * {String}
+ */
+ fid: null,
+
+ /**
+ * APIProperty: geometry
+ * {<OpenLayers.Geometry>}
+ */
+ geometry: null,
+
+ /**
+ * APIProperty: attributes
+ * {Object} This object holds arbitrary, serializable properties that
+ * describe the feature.
+ */
+ attributes: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
+ * property can be set by an <OpenLayers.Format> object when
+ * deserializing the feature, so in most cases it represents an
+ * information set by the server.
+ */
+ bounds: null,
+
+ /**
+ * Property: state
+ * {String}
+ */
+ state: null,
+
+ /**
+ * APIProperty: style
+ * {Object}
+ */
+ style: null,
+
+ /**
+ * APIProperty: url
+ * {String} If this property is set it will be taken into account by
+ * {<OpenLayers.HTTP>} when upadting or deleting the feature.
+ */
+ url: null,
+
+ /**
+ * Property: renderIntent
+ * {String} rendering intent currently being used
+ */
+ renderIntent: "default",
+
+ /**
+ * APIProperty: modified
+ * {Object} An object with the originals of the geometry and attributes of
+ * the feature, if they were changed. Currently this property is only read
+ * by <OpenLayers.Format.WFST.v1>, and written by
+ * <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
+ * Applications can set the originals of modified attributes in the
+ * attributes property. Note that applications have to check if this
+ * object and the attributes property is already created before using it.
+ * After a change made with ModifyFeature, this object could look like
+ *
+ * (code)
+ * {
+ * geometry: >Object
+ * }
+ * (end)
+ *
+ * When an application has made changes to feature attributes, it could
+ * have set the attributes to something like this:
+ *
+ * (code)
+ * {
+ * attributes: {
+ * myAttribute: "original"
+ * }
+ * }
+ * (end)
+ *
+ * Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
+ * *modified.geometry* and the attribute names in *modified.attributes*,
+ * but it is recommended to set the original values (and not just true) as
+ * attribute value, so applications could use this information to undo
+ * changes.
+ */
+ modified: null,
+
+ /**
+ * Constructor: OpenLayers.Feature.Vector
+ * Create a vector feature.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry that this feature
+ * represents.
+ * attributes - {Object} An optional object that will be mapped to the
+ * <attributes> property.
+ * style - {Object} An optional style object.
+ */
+ initialize: function(geometry, attributes, style) {
+ OpenLayers.Feature.prototype.initialize.apply(this,
+ [null, null, attributes]);
+ this.lonlat = null;
+ this.geometry = geometry ? geometry : null;
+ this.state = null;
+ this.attributes = {};
+ if (attributes) {
+ this.attributes = OpenLayers.Util.extend(this.attributes,
+ attributes);
+ }
+ this.style = style ? style : null;
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.layer) {
+ this.layer.removeFeatures(this);
+ this.layer = null;
+ }
+
+ this.geometry = null;
+ this.modified = null;
+ OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this vector feature. Does not set any non-standard
+ * properties.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
+ */
+ clone: function () {
+ return new OpenLayers.Feature.Vector(
+ this.geometry ? this.geometry.clone() : null,
+ this.attributes,
+ this.style);
+ },
+
+ /**
+ * Method: onScreen
+ * Determine whether the feature is within the map viewport. This method
+ * tests for an intersection between the geometry and the viewport
+ * bounds. If a more effecient but less precise geometry bounds
+ * intersection is desired, call the method with the boundsOnly
+ * parameter true.
+ *
+ * Parameters:
+ * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
+ * the viewport bounds. Default is false. If false, the feature's
+ * geometry must intersect the viewport for onScreen to return true.
+ *
+ * Returns:
+ * {Boolean} The feature is currently visible on screen (optionally
+ * based on its bounds if boundsOnly is true).
+ */
+ onScreen:function(boundsOnly) {
+ var onScreen = false;
+ if(this.layer && this.layer.map) {
+ var screenBounds = this.layer.map.getExtent();
+ if(boundsOnly) {
+ var featureBounds = this.geometry.getBounds();
+ onScreen = screenBounds.intersectsBounds(featureBounds);
+ } else {
+ var screenPoly = screenBounds.toGeometry();
+ onScreen = screenPoly.intersects(this.geometry);
+ }
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: getVisibility
+ * Determine whether the feature is displayed or not. It may not displayed
+ * because:
+ * - its style display property is set to 'none',
+ * - it doesn't belong to any layer,
+ * - the styleMap creates a symbolizer with display property set to 'none'
+ * for it,
+ * - the layer which it belongs to is not visible.
+ *
+ * Returns:
+ * {Boolean} The feature is currently displayed.
+ */
+ getVisibility: function() {
+ return !(this.style && this.style.display == 'none' ||
+ !this.layer ||
+ this.layer && this.layer.styleMap &&
+ this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
+ this.layer && !this.layer.getVisibility());
+ },
+
+ /**
+ * Method: createMarker
+ * HACK - we need to decide if all vector features should be able to
+ * create markers
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} For now just returns null
+ */
+ createMarker: function() {
+ return null;
+ },
+
+ /**
+ * Method: destroyMarker
+ * HACK - we need to decide if all vector features should be able to
+ * delete markers
+ *
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ // pass
+ },
+
+ /**
+ * Method: createPopup
+ * HACK - we need to decide if all vector features should be able to
+ * create popups
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} For now just returns null
+ */
+ createPopup: function() {
+ return null;
+ },
+
+ /**
+ * Method: atPoint
+ * Determins whether the feature intersects with the specified location.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ if(this.geometry) {
+ atPoint = this.geometry.atPoint(lonlat, toleranceLon,
+ toleranceLat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: destroyPopup
+ * HACK - we need to decide if all vector features should be able to
+ * delete popups
+ */
+ destroyPopup: function() {
+ // pass
+ },
+
+ /**
+ * Method: move
+ * Moves the feature and redraws it at its new location
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} the
+ * location to which to move the feature.
+ */
+ move: function(location) {
+
+ if(!this.layer || !this.geometry.move){
+ //do nothing if no layer or immoveable geometry
+ return undefined;
+ }
+
+ var pixel;
+ if (location.CLASS_NAME == "OpenLayers.LonLat") {
+ pixel = this.layer.getViewPortPxFromLonLat(location);
+ } else {
+ pixel = location;
+ }
+
+ var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
+ var res = this.layer.map.getResolution();
+ this.geometry.move(res * (pixel.x - lastPixel.x),
+ res * (lastPixel.y - pixel.y));
+ this.layer.drawFeature(this);
+ return lastPixel;
+ },
+
+ /**
+ * Method: toState
+ * Sets the new state
+ *
+ * Parameters:
+ * state - {String}
+ */
+ toState: function(state) {
+ if (state == OpenLayers.State.UPDATE) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.DELETE:
+ this.state = state;
+ break;
+ case OpenLayers.State.UPDATE:
+ case OpenLayers.State.INSERT:
+ break;
+ }
+ } else if (state == OpenLayers.State.INSERT) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ break;
+ default:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.DELETE) {
+ switch (this.state) {
+ case OpenLayers.State.INSERT:
+ // the feature should be destroyed
+ break;
+ case OpenLayers.State.DELETE:
+ break;
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.UPDATE:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.UNKNOWN) {
+ this.state = state;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature.Vector"
+});
+
+
+/**
+ * Constant: OpenLayers.Feature.Vector.style
+ * OpenLayers features can have a number of style attributes. The 'default'
+ * style will typically be used if no other style is specified. These
+ * styles correspond for the most part, to the styling properties defined
+ * by the SVG standard.
+ * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
+ * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
+ *
+ * Symbolizer properties:
+ * fill - {Boolean} Set to false if no fill is desired.
+ * fillColor - {String} Hex fill color. Default is "#ee9900".
+ * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4
+ * stroke - {Boolean} Set to false if no stroke is desired.
+ * strokeColor - {String} Hex stroke color. Default is "#ee9900".
+ * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1.
+ * strokeWidth - {Number} Pixel stroke width. Default is 1.
+ * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square]
+ * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
+ * graphic - {Boolean} Set to false if no graphic is desired.
+ * pointRadius - {Number} Pixel point radius. Default is 6.
+ * pointerEvents - {String} Default is "visiblePainted".
+ * cursor - {String} Default is "".
+ * externalGraphic - {String} Url to an external graphic that will be used for rendering points.
+ * graphicWidth - {Number} Pixel width for sizing an external graphic.
+ * graphicHeight - {Number} Pixel height for sizing an external graphic.
+ * graphicOpacity - {Number} Opacity (0-1) for an external graphic.
+ * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
+ * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
+ * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset).
+ * graphicZIndex - {Number} The integer z-index value to use in rendering.
+ * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default),
+ * "square", "star", "x", "cross", "triangle".
+ * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
+ * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
+ * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
+ * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
+ * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
+ * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
+ * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used.
+ * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used.
+ * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
+ * fillText or mozDrawText to be available.
+ * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
+ * composed of two characters. The first character is for the horizontal alignment, the second for the vertical
+ * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
+ * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
+ * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
+ * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
+ * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
+ * Default is false.
+ * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
+ * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers.
+ * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
+ * fontColor - {String} The font color for the label, to be provided like CSS.
+ * fontOpacity - {Number} Opacity (0-1) for the label
+ * fontFamily - {String} The font family for the label, to be provided like in CSS.
+ * fontSize - {String} The font size for the label, to be provided like in CSS.
+ * fontStyle - {String} The font style for the label, to be provided like in CSS.
+ * fontWeight - {String} The font weight for the label, to be provided like in CSS.
+ * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect.
+ */
+OpenLayers.Feature.Vector.style = {
+ 'default': {
+ fillColor: "#ee9900",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#ee9900",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+ },
+ 'select': {
+ fillColor: "blue",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "blue",
+ strokeOpacity: 1,
+ strokeWidth: 2,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "pointer",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'temporary': {
+ fillColor: "#66cccc",
+ fillOpacity: 0.2,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#66cccc",
+ strokeOpacity: 1,
+ strokeLinecap: "round",
+ strokeWidth: 2,
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'delete': {
+ display: "none"
+ }
+};
+/* ======================================================================
+ OpenLayers/Style.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Style
+ * This class represents a UserStyle obtained
+ * from a SLD, containing styling rules.
+ */
+OpenLayers.Style = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this style (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this style (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * APIProperty: layerName
+ * {<String>} name of the layer that this style belongs to, usually
+ * according to the NamedLayer attribute of an SLD document.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: isDefault
+ * {Boolean}
+ */
+ isDefault: false,
+
+ /**
+ * Property: rules
+ * {Array(<OpenLayers.Rule>)}
+ */
+ rules: null,
+
+ /**
+ * APIProperty: context
+ * {Object} An optional object with properties that symbolizers' property
+ * values should be evaluated against. If no context is specified,
+ * feature.attributes will be used
+ */
+ context: null,
+
+ /**
+ * Property: defaultStyle
+ * {Object} hash of style properties to use as default for merging
+ * rule-based style symbolizers onto. If no rules are defined,
+ * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
+ * true, the defaultStyle will only be taken into account if there are
+ * rules defined.
+ */
+ defaultStyle: null,
+
+ /**
+ * Property: defaultsPerSymbolizer
+ * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
+ * of every rule. Properties of the <defaultStyle> will also be used to set
+ * missing symbolizer properties if the symbolizer has stroke, fill or
+ * graphic set to true. Default is false.
+ */
+ defaultsPerSymbolizer: false,
+
+ /**
+ * Property: propertyStyles
+ * {Hash of Boolean} cache of style properties that need to be parsed for
+ * propertyNames. Property names are keys, values won't be used.
+ */
+ propertyStyles: null,
+
+
+ /**
+ * Constructor: OpenLayers.Style
+ * Creates a UserStyle.
+ *
+ * Parameters:
+ * style - {Object} Optional hash of style properties that will be
+ * used as default style for this style object. This style
+ * applies if no rules are specified. Symbolizers defined in
+ * rules will extend this default style.
+ * options - {Object} An optional object with properties to set on the
+ * style.
+ *
+ * Valid options:
+ * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
+ * style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>}
+ */
+ initialize: function(style, options) {
+
+ OpenLayers.Util.extend(this, options);
+ this.rules = [];
+ if(options && options.rules) {
+ this.addRules(options.rules);
+ }
+
+ // use the default style from OpenLayers.Feature.Vector if no style
+ // was given in the constructor
+ this.setDefaultStyle(style ||
+ OpenLayers.Feature.Vector.style["default"]);
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i=0, len=this.rules.length; i<len; i++) {
+ this.rules[i].destroy();
+ this.rules[i] = null;
+ }
+ this.rules = null;
+ this.defaultStyle = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * creates a style by applying all feature-dependent rules to the base
+ * style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to evaluate rules for
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature) {
+ var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
+ OpenLayers.Util.extend({}, this.defaultStyle), feature);
+
+ var rules = this.rules;
+
+ var rule, context;
+ var elseRules = [];
+ var appliedRules = false;
+ for(var i=0, len=rules.length; i<len; i++) {
+ rule = rules[i];
+ // does the rule apply?
+ var applies = rule.evaluate(feature);
+
+ if(applies) {
+ if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+ elseRules.push(rule);
+ } else {
+ appliedRules = true;
+ this.applySymbolizer(rule, style, feature);
+ }
+ }
+ }
+
+ // if no other rules apply, apply the rules with else filters
+ if(appliedRules == false && elseRules.length > 0) {
+ appliedRules = true;
+ for(var i=0, len=elseRules.length; i<len; i++) {
+ this.applySymbolizer(elseRules[i], style, feature);
+ }
+ }
+
+ // don't display if there were rules but none applied
+ if(rules.length > 0 && appliedRules == false) {
+ style.display = "none";
+ }
+
+ if (style.label != null && typeof style.label !== "string") {
+ style.label = String(style.label);
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: applySymbolizer
+ *
+ * Parameters:
+ * rule - {<OpenLayers.Rule>}
+ * style - {Object}
+ * feature - {<OpenLayer.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} A style with new symbolizer applied.
+ */
+ applySymbolizer: function(rule, style, feature) {
+ var symbolizerPrefix = feature.geometry ?
+ this.getSymbolizerPrefix(feature.geometry) :
+ OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
+
+ var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+
+ if(this.defaultsPerSymbolizer === true) {
+ var defaults = this.defaultStyle;
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: defaults.pointRadius
+ });
+ if(symbolizer.stroke === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ strokeWidth: defaults.strokeWidth,
+ strokeColor: defaults.strokeColor,
+ strokeOpacity: defaults.strokeOpacity,
+ strokeDashstyle: defaults.strokeDashstyle,
+ strokeLinecap: defaults.strokeLinecap
+ });
+ }
+ if(symbolizer.fill === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ fillColor: defaults.fillColor,
+ fillOpacity: defaults.fillOpacity
+ });
+ }
+ if(symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: this.defaultStyle.pointRadius,
+ externalGraphic: this.defaultStyle.externalGraphic,
+ graphicName: this.defaultStyle.graphicName,
+ graphicOpacity: this.defaultStyle.graphicOpacity,
+ graphicWidth: this.defaultStyle.graphicWidth,
+ graphicHeight: this.defaultStyle.graphicHeight,
+ graphicXOffset: this.defaultStyle.graphicXOffset,
+ graphicYOffset: this.defaultStyle.graphicYOffset
+ });
+ }
+ }
+
+ // merge the style with the current style
+ return this.createLiterals(
+ OpenLayers.Util.extend(style, symbolizer), feature);
+ },
+
+ /**
+ * Method: createLiterals
+ * creates literals for all style properties that have an entry in
+ * <this.propertyStyles>.
+ *
+ * Parameters:
+ * style - {Object} style to create literals for. Will be modified
+ * inline.
+ * feature - {Object}
+ *
+ * Returns:
+ * {Object} the modified style
+ */
+ createLiterals: function(style, feature) {
+ var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
+ OpenLayers.Util.extend(context, this.context);
+
+ for (var i in this.propertyStyles) {
+ style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
+ }
+ return style;
+ },
+
+ /**
+ * Method: findPropertyStyles
+ * Looks into all rules for this style and the defaultStyle to collect
+ * all the style hash property names containing ${...} strings that have
+ * to be replaced using the createLiteral method before returning them.
+ *
+ * Returns:
+ * {Object} hash of property names that need createLiteral parsing. The
+ * name of the property is the key, and the value is true;
+ */
+ findPropertyStyles: function() {
+ var propertyStyles = {};
+
+ // check the default style
+ var style = this.defaultStyle;
+ this.addPropertyStyles(propertyStyles, style);
+
+ // walk through all rules to check for properties in their symbolizer
+ var rules = this.rules;
+ var symbolizer, value;
+ for (var i=0, len=rules.length; i<len; i++) {
+ symbolizer = rules[i].symbolizer;
+ for (var key in symbolizer) {
+ value = symbolizer[key];
+ if (typeof value == "object") {
+ // symbolizer key is "Point", "Line" or "Polygon"
+ this.addPropertyStyles(propertyStyles, value);
+ } else {
+ // symbolizer is a hash of style properties
+ this.addPropertyStyles(propertyStyles, symbolizer);
+ break;
+ }
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * Method: addPropertyStyles
+ *
+ * Parameters:
+ * propertyStyles - {Object} hash to add new property styles to. Will be
+ * modified inline
+ * symbolizer - {Object} search this symbolizer for property styles
+ *
+ * Returns:
+ * {Object} propertyStyles hash
+ */
+ addPropertyStyles: function(propertyStyles, symbolizer) {
+ var property;
+ for (var key in symbolizer) {
+ property = symbolizer[key];
+ if (typeof property == "string" &&
+ property.match(/\$\{\w+\}/)) {
+ propertyStyles[key] = true;
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * APIMethod: addRules
+ * Adds rules to this style.
+ *
+ * Parameters:
+ * rules - {Array(<OpenLayers.Rule>)}
+ */
+ addRules: function(rules) {
+ Array.prototype.push.apply(this.rules, rules);
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * APIMethod: setDefaultStyle
+ * Sets the default style for this style object.
+ *
+ * Parameters:
+ * style - {Object} Hash of style properties
+ */
+ setDefaultStyle: function(style) {
+ this.defaultStyle = style;
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * Method: getSymbolizerPrefix
+ * Returns the correct symbolizer prefix according to the
+ * geometry type of the passed geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {String} key of the according symbolizer
+ */
+ getSymbolizerPrefix: function(geometry) {
+ var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+ for (var i=0, len=prefixes.length; i<len; i++) {
+ if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+ return prefixes[i];
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>} Clone of this style.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ // clone rules
+ if(this.rules) {
+ options.rules = [];
+ for(var i=0, len=this.rules.length; i<len; ++i) {
+ options.rules.push(this.rules[i].clone());
+ }
+ }
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ //clone default style
+ var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
+ return new OpenLayers.Style(defaultStyle, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ *
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ * will be replaced by the value of the "bar" attribute of the passed
+ * feature.
+ * context - {Object} context to take attribute values from
+ * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
+ * <OpenLayers.String.format> for evaluating functions in the
+ * context.
+ * property - {String} optional, name of the property for which the literal is
+ * being created for evaluating functions in the context.
+ *
+ * Returns:
+ * {String} the parsed value. In the example of the value parameter above, the
+ * result would be "foo valueOfBar", assuming that the passed feature has an
+ * attribute named "bar" with the value "valueOfBar".
+ */
+OpenLayers.Style.createLiteral = function(value, context, feature, property) {
+ if (typeof value == "string" && value.indexOf("${") != -1) {
+ value = OpenLayers.String.format(value, context, [feature, property]);
+ value = (isNaN(value) || !value) ? value : parseFloat(value);
+ }
+ return value;
+};
+
+/**
+ * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
+ * {Array} prefixes of the sld symbolizers. These are the
+ * same as the main geometry types
+ */
+OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text',
+ 'Raster'];
+/* ======================================================================
+ OpenLayers/Filter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Filter
+ * This class represents an OGC Filter.
+ */
+OpenLayers.Filter = OpenLayers.Class({
+
+ /**
+ * Constructor: OpenLayers.Filter
+ * This class represents a generic filter.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to anything added.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context. Instances or subclasses
+ * are supposed to override this method.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ return true;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter. Should be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} Clone of this filter.
+ */
+ clone: function() {
+ return null;
+ },
+
+ /**
+ * APIMethod: toString
+ *
+ * Returns:
+ * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
+ * representation of the filter returned. Otherwise "[Object object]"
+ * will be returned.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.CQL) {
+ string = OpenLayers.Format.CQL.prototype.write(this);
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter"
+});
+/* ======================================================================
+ OpenLayers/Filter/Spatial.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Spatial
+ * This class represents a spatial filter.
+ * Currently implemented: BBOX, DWithin and Intersects
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} Type of spatial filter.
+ *
+ * The type should be one of:
+ * - OpenLayers.Filter.Spatial.BBOX
+ * - OpenLayers.Filter.Spatial.INTERSECTS
+ * - OpenLayers.Filter.Spatial.DWITHIN
+ * - OpenLayers.Filter.Spatial.WITHIN
+ * - OpenLayers.Filter.Spatial.CONTAINS
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String} Name of the context property to compare.
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
+ * to be used by the filter. Use bounds for BBOX filters and geometry
+ * for INTERSECTS or DWITHIN filters.
+ */
+ value: null,
+
+ /**
+ * APIProperty: distance
+ * {Number} The distance to use in a DWithin spatial filter.
+ */
+ distance: null,
+
+ /**
+ * APIProperty: distanceUnits
+ * {String} The units to use for the distance, e.g. 'm'.
+ */
+ distanceUnits: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Spatial
+ * Creates a spatial filter.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>}
+ */
+
+ /**
+ * Method: evaluate
+ * Evaluates this filter for a specific feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
+ *
+ * Returns:
+ * {Boolean} The feature meets filter criteria.
+ */
+ evaluate: function(feature) {
+ var intersect = false;
+ switch(this.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ case OpenLayers.Filter.Spatial.INTERSECTS:
+ if(feature.geometry) {
+ var geom = this.value;
+ if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
+ geom = this.value.toGeometry();
+ }
+ if(feature.geometry.intersects(geom)) {
+ intersect = true;
+ }
+ }
+ break;
+ default:
+ throw new Error('evaluate is not implemented for this filter type.');
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} Clone of this filter.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.applyDefaults({
+ value: this.value && this.value.clone && this.value.clone()
+ }, this);
+ return new OpenLayers.Filter.Spatial(options);
+ },
+ CLASS_NAME: "OpenLayers.Filter.Spatial"
+});
+
+OpenLayers.Filter.Spatial.BBOX = "BBOX";
+OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
+OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
+OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
+OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
+/* ======================================================================
+ OpenLayers/Strategy/BBOX.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.BBOX
+ * A simple strategy that reads new features when the viewport invalidates
+ * some bounds.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The current data bounds (in the same projection
+ * as the layer - not always the same projection as the map).
+ */
+ bounds: null,
+
+ /**
+ * Property: resolution
+ * {Float} The current data resolution.
+ */
+ resolution: null,
+
+ /**
+ * APIProperty: ratio
+ * {Float} The ratio of the data bounds to the viewport bounds (in each
+ * dimension). Default is 2.
+ */
+ ratio: 2,
+
+ /**
+ * Property: resFactor
+ * {Float} Optional factor used to determine when previously requested
+ * features are invalid. If set, the resFactor will be compared to the
+ * resolution of the previous request to the current map resolution.
+ * If resFactor > (old / new) and 1/resFactor < (old / new). If you
+ * set a resFactor of 1, data will be requested every time the
+ * resolution changes. If you set a resFactor of 3, data will be
+ * requested if the old resolution is 3 times the new, or if the new is
+ * 3 times the old. If the old bounds do not contain the new bounds
+ * new data will always be requested (with or without considering
+ * resFactor).
+ */
+ resFactor: null,
+
+ /**
+ * Property: response
+ * {<OpenLayers.Protocol.Response>} The protocol response object returned
+ * by the layer protocol.
+ */
+ response: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.BBOX
+ * Create a new BBOX strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Set up strategy with regard to reading new batches of remote data.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "moveend": this.update,
+ "refresh": this.update,
+ "visibilitychanged": this.update,
+ scope: this
+ });
+ this.update();
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Tear down strategy with regard to reading new batches of remote data.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "moveend": this.update,
+ "refresh": this.update,
+ "visibilitychanged": this.update,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: update
+ * Callback function called on "moveend" or "refresh" layer events.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will determine
+ * the behaviour of this Strategy
+ *
+ * Valid options include:
+ * force - {Boolean} if true, new data must be unconditionally read.
+ * noAbort - {Boolean} if true, do not abort previous requests.
+ */
+ update: function(options) {
+ var mapBounds = this.getMapBounds();
+ if (mapBounds !== null && ((options && options.force) ||
+ (this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
+ this.calculateBounds(mapBounds);
+ this.resolution = this.layer.map.getResolution();
+ this.triggerRead(options);
+ }
+ },
+
+ /**
+ * Method: getMapBounds
+ * Get the map bounds expressed in the same projection as this layer.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
+ */
+ getMapBounds: function() {
+ if (this.layer.map === null) {
+ return null;
+ }
+ var bounds = this.layer.map.getExtent();
+ if(bounds && !this.layer.projection.equals(
+ this.layer.map.getProjectionObject())) {
+ bounds = bounds.clone().transform(
+ this.layer.map.getProjectionObject(), this.layer.projection
+ );
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: invalidBounds
+ * Determine whether the previously requested set of features is invalid.
+ * This occurs when the new map bounds do not contain the previously
+ * requested bounds. In addition, if <resFactor> is set, it will be
+ * considered.
+ *
+ * Parameters:
+ * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+ * retrieved from the map object if not provided
+ *
+ * Returns:
+ * {Boolean}
+ */
+ invalidBounds: function(mapBounds) {
+ if(!mapBounds) {
+ mapBounds = this.getMapBounds();
+ }
+ var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
+ if(!invalid && this.resFactor) {
+ var ratio = this.resolution / this.layer.map.getResolution();
+ invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
+ }
+ return invalid;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+ * retrieved from the map object if not provided
+ */
+ calculateBounds: function(mapBounds) {
+ if(!mapBounds) {
+ mapBounds = this.getMapBounds();
+ }
+ var center = mapBounds.getCenterLonLat();
+ var dataWidth = mapBounds.getWidth() * this.ratio;
+ var dataHeight = mapBounds.getHeight() * this.ratio;
+ this.bounds = new OpenLayers.Bounds(
+ center.lon - (dataWidth / 2),
+ center.lat - (dataHeight / 2),
+ center.lon + (dataWidth / 2),
+ center.lat + (dataHeight / 2)
+ );
+ },
+
+ /**
+ * Method: triggerRead
+ *
+ * Parameters:
+ * options - {Object} Additional options for the protocol's read method
+ * (optional)
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} The protocol response object
+ * returned by the layer protocol.
+ */
+ triggerRead: function(options) {
+ if (this.response && !(options && options.noAbort === true)) {
+ this.layer.protocol.abort(this.response);
+ this.layer.events.triggerEvent("loadend");
+ }
+ var evt = {filter: this.createFilter()};
+ this.layer.events.triggerEvent("loadstart", evt);
+ this.response = this.layer.protocol.read(
+ OpenLayers.Util.applyDefaults({
+ filter: evt.filter,
+ callback: this.merge,
+ scope: this
+ }, options));
+ },
+
+ /**
+ * Method: createFilter
+ * Creates a spatial BBOX filter. If the layer that this strategy belongs
+ * to has a filter property, this filter will be combined with the BBOX
+ * filter.
+ *
+ * Returns
+ * {<OpenLayers.Filter>} The filter object.
+ */
+ createFilter: function() {
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: this.bounds,
+ projection: this.layer.projection
+ });
+ if (this.layer.filter) {
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.layer.filter, filter]
+ });
+ }
+ return filter;
+ },
+
+ /**
+ * Method: merge
+ * Given a list of features, determine which ones to add to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ this.layer.destroyFeatures();
+ if (resp.success()) {
+ var features = resp.features;
+ if(features && features.length > 0) {
+ var remote = this.layer.projection;
+ var local = this.layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ this.layer.addFeatures(features);
+ }
+ } else {
+ this.bounds = null;
+ }
+ this.response = null;
+ this.layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.BBOX"
+});
+/* ======================================================================
+ OpenLayers/Handler/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature
+ * Handler to respond to mouse events related to a drawn feature. Callbacks
+ * with the following keys will be notified of the following events
+ * associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ * browser events target features that can be selected.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: EVENTMAP
+ * {Object} A object mapping the browser events to objects with callback
+ * keys for in and out.
+ */
+ EVENTMAP: {
+ 'click': {'in': 'click', 'out': 'clickout'},
+ 'mousemove': {'in': 'over', 'out': 'out'},
+ 'dblclick': {'in': 'dblclick', 'out': null},
+ 'mousedown': {'in': null, 'out': null},
+ 'mouseup': {'in': null, 'out': null},
+ 'touchstart': {'in': 'click', 'out': 'clickout'}
+ },
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+ */
+ feature: null,
+
+ /**
+ * Property: lastFeature
+ * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+ */
+ lastFeature: null,
+
+ /**
+ * Property: down
+ * {<OpenLayers.Pixel>} The location of the last mousedown.
+ */
+ down: null,
+
+ /**
+ * Property: up
+ * {<OpenLayers.Pixel>} The location of the last mouseup.
+ */
+ up: null,
+
+ /**
+ * Property: clickTolerance
+ * {Number} The number of pixels the mouse can move between mousedown
+ * and mouseup for the event to still be considered a click.
+ * Dragging the map should not trigger the click and clickout callbacks
+ * unless the map is moved by less than this tolerance. Defaults to 4.
+ */
+ clickTolerance: 4,
+
+ /**
+ * Property: geometryTypes
+ * To restrict dragging to a limited set of geometry types, send a list
+ * of strings corresponding to the geometry class names.
+ *
+ * @type Array(String)
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: stopClick
+ * {Boolean} If stopClick is set to true, handled clicks do not
+ * propagate to other click listeners. Otherwise, handled clicks
+ * do propagate. Unhandled clicks always propagate, whatever the
+ * value of stopClick. Defaults to true.
+ */
+ stopClick: true,
+
+ /**
+ * Property: stopDown
+ * {Boolean} If stopDown is set to true, handled mousedowns do not
+ * propagate to other mousedown listeners. Otherwise, handled
+ * mousedowns do propagate. Unhandled mousedowns always propagate,
+ * whatever the value of stopDown. Defaults to true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: stopUp
+ * {Boolean} If stopUp is set to true, handled mouseups do not
+ * propagate to other mouseup listeners. Otherwise, handled mouseups
+ * do propagate. Unhandled mouseups always propagate, whatever the
+ * value of stopUp. Defaults to false.
+ */
+ stopUp: false,
+
+ /**
+ * Constructor: OpenLayers.Handler.Feature
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * layer - {<OpenLayers.Layer.Vector>}
+ * callbacks - {Object} An object with a 'over' property whos value is
+ * a function to be called when the mouse is over a feature. The
+ * callback should expect to recieve a single argument, the feature.
+ * options - {Object}
+ */
+ initialize: function(control, layer, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+ this.layer = layer;
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return OpenLayers.Event.isMultiTouch(evt) ?
+ true : this.mousedown(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events. We just prevent the browser default behavior,
+ * for Android Webkit not to select text when moving the finger after
+ * selecting a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ touchmove: function(evt) {
+ OpenLayers.Event.preventDefault(evt);
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mouse down. Stop propagation if a feature is targeted by this
+ * event (stops map dragging during feature selection).
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mousedown: function(evt) {
+ // Feature selection is only done with a left click. Other handlers may stop the
+ // propagation of left-click mousedown events but not right-click mousedown events.
+ // This mismatch causes problems when comparing the location of the down and up
+ // events in the click function so it is important ignore right-clicks.
+ if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
+ this.down = evt.xy;
+ }
+ return this.handle(evt) ? !this.stopDown : true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouse up. Stop propagation if a feature is targeted by this
+ * event.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mouseup: function(evt) {
+ this.up = evt.xy;
+ return this.handle(evt) ? !this.stopUp : true;
+ },
+
+ /**
+ * Method: click
+ * Handle click. Call the "click" callback if click on a feature,
+ * or the "clickout" callback if click outside any feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ click: function(evt) {
+ return this.handle(evt) ? !this.stopClick : true;
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mouse moves. Call the "over" callback if moving in to a feature,
+ * or the "out" callback if moving out of a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ mousemove: function(evt) {
+ if (!this.callbacks['over'] && !this.callbacks['out']) {
+ return true;
+ }
+ this.handle(evt);
+ return true;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ dblclick: function(evt) {
+ return !this.handle(evt);
+ },
+
+ /**
+ * Method: geometryTypeMatches
+ * Return true if the geometry type of the passed feature matches
+ * one of the geometry types in the geometryTypes array.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ geometryTypeMatches: function(feature) {
+ return this.geometryTypes == null ||
+ OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) > -1;
+ },
+
+ /**
+ * Method: handle
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} The event occurred over a relevant feature.
+ */
+ handle: function(evt) {
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ var type = evt.type;
+ var handled = false;
+ var previouslyIn = !!(this.feature); // previously in a feature
+ var click = (type == "click" || type == "dblclick" || type == "touchstart");
+ this.feature = this.layer.getFeatureFromEvent(evt);
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ if(this.lastFeature && !this.lastFeature.layer) {
+ // last feature has been destroyed
+ this.lastFeature = null;
+ }
+ if(this.feature) {
+ if(type === "touchstart") {
+ // stop the event to prevent Android Webkit from
+ // "flashing" the map div
+ OpenLayers.Event.preventDefault(evt);
+ }
+ var inNew = (this.feature != this.lastFeature);
+ if(this.geometryTypeMatches(this.feature)) {
+ // in to a feature
+ if(previouslyIn && inNew) {
+ // out of last feature and in to another
+ if(this.lastFeature) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ this.triggerCallback(type, 'in', [this.feature]);
+ } else if(!previouslyIn || click) {
+ // in feature for the first time
+ this.triggerCallback(type, 'in', [this.feature]);
+ }
+ this.lastFeature = this.feature;
+ handled = true;
+ } else {
+ // not in to a feature
+ if(this.lastFeature && (previouslyIn && inNew || click)) {
+ // out of last feature for the first time
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ // next time the mouse goes in a feature whose geometry type
+ // doesn't match we don't want to call the 'out' callback
+ // again, so let's set this.feature to null so that
+ // previouslyIn will evaluate to false the next time
+ // we enter handle. Yes, a bit hackish...
+ this.feature = null;
+ }
+ } else if(this.lastFeature && (previouslyIn || click)) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ return handled;
+ },
+
+ /**
+ * Method: triggerCallback
+ * Call the callback keyed in the event map with the supplied arguments.
+ * For click and clickout, the <clickTolerance> is checked first.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ triggerCallback: function(type, mode, args) {
+ var key = this.EVENTMAP[type][mode];
+ if(key) {
+ if(type == 'click' && this.up && this.down) {
+ // for click/clickout, only trigger callback if tolerance is met
+ var dpx = Math.sqrt(
+ Math.pow(this.up.x - this.down.x, 2) +
+ Math.pow(this.up.y - this.down.y, 2)
+ );
+ if(dpx <= this.clickTolerance) {
+ this.callback(key, args);
+ }
+ // we're done with this set of events now: clear the cached
+ // positions so we can't trip over them later (this can occur
+ // if one of the up/down events gets eaten before it gets to us
+ // but we still get the click)
+ this.up = this.down = null;
+ } else {
+ this.callback(key, args);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Turn off the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.feature = null;
+ this.lastFeature = null;
+ this.down = null;
+ this.up = null;
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Feature"
+});
+/* ======================================================================
+ OpenLayers/StyleMap.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.StyleMap
+ */
+OpenLayers.StyleMap = OpenLayers.Class({
+
+ /**
+ * Property: styles
+ * {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
+ * rendering intents (e.g. "default", "temporary", "select", "delete").
+ */
+ styles: null,
+
+ /**
+ * Property: extendDefault
+ * {Boolean} if true, every render intent will extend the symbolizers
+ * specified for the "default" intent at rendering time. Otherwise, every
+ * rendering intent will be treated as a completely independent style.
+ */
+ extendDefault: true,
+
+ /**
+ * Constructor: OpenLayers.StyleMap
+ *
+ * Parameters:
+ * style - {Object} Optional. Either a style hash, or a style object, or
+ * a hash of style objects (style hashes) keyed by rendering
+ * intent. If just one style hash or style object is passed,
+ * this will be used for all known render intents (default,
+ * select, temporary)
+ * options - {Object} optional hash of additional options for this
+ * instance
+ */
+ initialize: function (style, options) {
+ this.styles = {
+ "default": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["default"]),
+ "select": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["select"]),
+ "temporary": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["temporary"]),
+ "delete": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["delete"])
+ };
+
+ // take whatever the user passed as style parameter and convert it
+ // into parts of stylemap.
+ if(style instanceof OpenLayers.Style) {
+ // user passed a style object
+ this.styles["default"] = style;
+ this.styles["select"] = style;
+ this.styles["temporary"] = style;
+ this.styles["delete"] = style;
+ } else if(typeof style == "object") {
+ for(var key in style) {
+ if(style[key] instanceof OpenLayers.Style) {
+ // user passed a hash of style objects
+ this.styles[key] = style[key];
+ } else if(typeof style[key] == "object") {
+ // user passsed a hash of style hashes
+ this.styles[key] = new OpenLayers.Style(style[key]);
+ } else {
+ // user passed a style hash (i.e. symbolizer)
+ this.styles["default"] = new OpenLayers.Style(style);
+ this.styles["select"] = new OpenLayers.Style(style);
+ this.styles["temporary"] = new OpenLayers.Style(style);
+ this.styles["delete"] = new OpenLayers.Style(style);
+ break;
+ }
+ }
+ }
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for(var key in this.styles) {
+ this.styles[key].destroy();
+ }
+ this.styles = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * Creates the symbolizer for a feature for a render intent.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
+ * of the intended style against.
+ * intent - {String} The intent determines the symbolizer that will be
+ * used to draw the feature. Well known intents are "default"
+ * (for just drawing the features), "select" (for selected
+ * features) and "temporary" (for drawing features).
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature, intent) {
+ if(!feature) {
+ feature = new OpenLayers.Feature.Vector();
+ }
+ if(!this.styles[intent]) {
+ intent = "default";
+ }
+ feature.renderIntent = intent;
+ var defaultSymbolizer = {};
+ if(this.extendDefault && intent != "default") {
+ defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
+ }
+ return OpenLayers.Util.extend(defaultSymbolizer,
+ this.styles[intent].createSymbolizer(feature));
+ },
+
+ /**
+ * Method: addUniqueValueRules
+ * Convenience method to create comparison rules for unique values of a
+ * property. The rules will be added to the style object for a specified
+ * rendering intent. This method is a shortcut for creating something like
+ * the "unique value legends" familiar from well known desktop GIS systems
+ *
+ * Parameters:
+ * renderIntent - {String} rendering intent to add the rules to
+ * property - {String} values of feature attributes to create the
+ * rules for
+ * symbolizers - {Object} Hash of symbolizers, keyed by the desired
+ * property values
+ * context - {Object} An optional object with properties that
+ * symbolizers' property values should be evaluated
+ * against. If no context is specified, feature.attributes
+ * will be used
+ */
+ addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
+ var rules = [];
+ for (var value in symbolizers) {
+ rules.push(new OpenLayers.Rule({
+ symbolizer: symbolizers[value],
+ context: context,
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: property,
+ value: value
+ })
+ }));
+ }
+ this.styles[renderIntent].addRules(rules);
+ },
+
+ CLASS_NAME: "OpenLayers.StyleMap"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Renderer.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector
+ * Instances of OpenLayers.Layer.Vector are used to render vector data from
+ * a variety of sources. Create a new vector layer with the
+ * <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types (in addition to those from <OpenLayers.Layer.events>):
+ * beforefeatureadded - Triggered before a feature is added. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be added. To stop the feature from being added, a
+ * listener should return false.
+ * beforefeaturesadded - Triggered before an array of features is added.
+ * Listeners will receive an object with a *features* property
+ * referencing the feature to be added. To stop the features from
+ * being added, a listener should return false.
+ * featureadded - Triggered after a feature is added. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the added feature.
+ * featuresadded - Triggered after features are added. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of added features.
+ * beforefeatureremoved - Triggered before a feature is removed. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be removed.
+ * beforefeaturesremoved - Triggered before multiple features are removed.
+ * Listeners will receive an object with a *features* property
+ * referencing the features to be removed.
+ * featureremoved - Triggerd after a feature is removed. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the removed feature.
+ * featuresremoved - Triggered after features are removed. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of removed features.
+ * beforefeatureselected - Triggered before a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be selected. To stop the feature from being selectd, a
+ * listener should return false.
+ * featureselected - Triggered after a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * selected feature.
+ * featureunselected - Triggered after a feature is unselected.
+ * Listeners will receive an object with a *feature* property
+ * referencing the unselected feature.
+ * beforefeaturemodified - Triggered when a feature is selected to
+ * be modified. Listeners will receive an object with a *feature*
+ * property referencing the selected feature.
+ * featuremodified - Triggered when a feature has been modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * afterfeaturemodified - Triggered when a feature is finished being modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * vertexmodified - Triggered when a vertex within any feature geometry
+ * has been modified. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * modification.
+ * vertexremoved - Triggered when a vertex within any feature geometry
+ * has been deleted. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * removal.
+ * sketchstarted - Triggered when a feature sketch bound for this layer
+ * is started. Listeners will receive an object with a *feature*
+ * property referencing the new sketch feature and a *vertex* property
+ * referencing the creation point.
+ * sketchmodified - Triggered when a feature sketch bound for this layer
+ * is modified. Listeners will receive an object with a *vertex*
+ * property referencing the modified vertex and a *feature* property
+ * referencing the sketch feature.
+ * sketchcomplete - Triggered when a feature sketch bound for this layer
+ * is complete. Listeners will receive an object with a *feature*
+ * property referencing the sketch feature. By returning false, a
+ * listener can stop the sketch feature from being added to the layer.
+ * refresh - Triggered when something wants a strategy to ask the protocol
+ * for a new set of features.
+ */
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is false. Set this property
+ * in the layer options.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} Whether the layer remains in one place while dragging the
+ * map. Note that setting this to true will move the layer to the bottom
+ * of the layer stack.
+ */
+ isFixed: false,
+
+ /**
+ * APIProperty: features
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ features: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} The filter set in this layer,
+ * a strategy launching read requests can combined
+ * this filter with its own filter.
+ */
+ filter: null,
+
+ /**
+ * Property: selectedFeatures
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ selectedFeatures: null,
+
+ /**
+ * Property: unrenderedFeatures
+ * {Object} hash of features, keyed by feature.id, that the renderer
+ * failed to draw
+ */
+ unrenderedFeatures: null,
+
+ /**
+ * APIProperty: reportError
+ * {Boolean} report friendly error message when loading of renderer
+ * fails.
+ */
+ reportError: true,
+
+ /**
+ * APIProperty: style
+ * {Object} Default style for the layer
+ */
+ style: null,
+
+ /**
+ * Property: styleMap
+ * {<OpenLayers.StyleMap>}
+ */
+ styleMap: null,
+
+ /**
+ * Property: strategies
+ * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+ */
+ strategies: null,
+
+ /**
+ * Property: protocol
+ * {<OpenLayers.Protocol>} Optional protocol for the layer.
+ */
+ protocol: null,
+
+ /**
+ * Property: renderers
+ * {Array(String)} List of supported Renderer classes. Add to this list to
+ * add support for additional renderers. This list is ordered:
+ * the first renderer which returns true for the 'supported()'
+ * method will be used, if not defined in the 'renderer' option.
+ */
+ renderers: ['SVG', 'VML', 'Canvas'],
+
+ /**
+ * Property: renderer
+ * {<OpenLayers.Renderer>}
+ */
+ renderer: null,
+
+ /**
+ * APIProperty: rendererOptions
+ * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
+ * supported options.
+ */
+ rendererOptions: null,
+
+ /**
+ * APIProperty: geometryType
+ * {String} geometryType allows you to limit the types of geometries this
+ * layer supports. This should be set to something like
+ * "OpenLayers.Geometry.Point" to limit types.
+ */
+ geometryType: null,
+
+ /**
+ * Property: drawn
+ * {Boolean} Whether the Vector Layer features have been drawn yet.
+ */
+ drawn: false,
+
+ /**
+ * APIProperty: ratio
+ * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map.
+ */
+ ratio: 1,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector
+ * Create a new vector layer
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} A new vector layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+
+ // allow user-set renderer, otherwise assign one
+ if (!this.renderer || !this.renderer.supported()) {
+ this.assignRenderer();
+ }
+
+ // if no valid renderer found, display error
+ if (!this.renderer || !this.renderer.supported()) {
+ this.renderer = null;
+ this.displayError();
+ }
+
+ if (!this.styleMap) {
+ this.styleMap = new OpenLayers.StyleMap();
+ }
+
+ this.features = [];
+ this.selectedFeatures = [];
+ this.unrenderedFeatures = {};
+
+ // Allow for custom layer behavior
+ if(this.strategies){
+ for(var i=0, len=this.strategies.length; i<len; i++) {
+ this.strategies[i].setLayer(this);
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoDestroy) {
+ strategy.destroy();
+ }
+ }
+ this.strategies = null;
+ }
+ if (this.protocol) {
+ if(this.protocol.autoDestroy) {
+ this.protocol.destroy();
+ }
+ this.protocol = null;
+ }
+ this.destroyFeatures();
+ this.features = null;
+ this.selectedFeatures = null;
+ this.unrenderedFeatures = null;
+ if (this.renderer) {
+ this.renderer.destroy();
+ }
+ this.renderer = null;
+ this.geometryType = null;
+ this.drawn = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer.
+ *
+ * Note: Features of the layer are also cloned.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Vector(this.name, this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ var features = this.features;
+ var len = features.length;
+ var clonedFeatures = new Array(len);
+ for(var i=0; i<len; ++i) {
+ clonedFeatures[i] = features[i].clone();
+ }
+ obj.features = clonedFeatures;
+
+ return obj;
+ },
+
+ /**
+ * Method: refresh
+ * Ask the layer to request features again and redraw them. Triggers
+ * the refresh event if the layer is in range and visible.
+ *
+ * Parameters:
+ * obj - {Object} Optional object with properties for any listener of
+ * the refresh event.
+ */
+ refresh: function(obj) {
+ if(this.calculateInRange() && this.visibility) {
+ this.events.triggerEvent("refresh", obj);
+ }
+ },
+
+ /**
+ * Method: assignRenderer
+ * Iterates through the available renderer implementations and selects
+ * and assigns the first one whose "supported()" function returns true.
+ */
+ assignRenderer: function() {
+ for (var i=0, len=this.renderers.length; i<len; i++) {
+ var rendererClass = this.renderers[i];
+ var renderer = (typeof rendererClass == "function") ?
+ rendererClass :
+ OpenLayers.Renderer[rendererClass];
+ if (renderer && renderer.prototype.supported()) {
+ this.renderer = new renderer(this.div, this.rendererOptions);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: displayError
+ * Let the user know their browser isn't supported.
+ */
+ displayError: function() {
+ if (this.reportError) {
+ OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
+ {renderers: this. renderers.join('\n')}));
+ }
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * If there is no renderer set, the layer can't be used. Remove it.
+ * Otherwise, give the renderer a reference to the map and set its size.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ if (!this.renderer) {
+ this.map.removeLayer(this);
+ } else {
+ this.renderer.map = this.map;
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. Any autoActivate strategies will be
+ * activated here.
+ */
+ afterAdd: function() {
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.activate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ this.drawn = false;
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.deactivate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * Notify the renderer of the change in size.
+ *
+ */
+ onMapResize: function() {
+ OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ },
+
+ /**
+ * Method: moveTo
+ * Reset the vector layer's div so that it once again is lined up with
+ * the map. Notify the renderer of the change of extent, and in the
+ * case of a change of zoom level (resolution), have the
+ * renderer redraw features.
+ *
+ * If the layer has not yet been drawn, cycle through the layer's
+ * features and draw each one.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ var coordSysUnchanged = true;
+ if (!dragging) {
+ this.renderer.root.style.visibility = 'hidden';
+
+ var viewSize = this.map.getSize(),
+ viewWidth = viewSize.w,
+ viewHeight = viewSize.h,
+ offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2,
+ offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2;
+ offsetLeft += this.map.layerContainerOriginPx.x;
+ offsetLeft = -Math.round(offsetLeft);
+ offsetTop += this.map.layerContainerOriginPx.y;
+ offsetTop = -Math.round(offsetTop);
+
+ this.div.style.left = offsetLeft + 'px';
+ this.div.style.top = offsetTop + 'px';
+
+ var extent = this.map.getExtent().scale(this.ratio);
+ coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
+
+ this.renderer.root.style.visibility = 'visible';
+
+ // Force a reflow on gecko based browsers to prevent jump/flicker.
+ // This seems to happen on only certain configurations; it was originally
+ // noticed in FF 2.0 and Linux.
+ if (OpenLayers.IS_GECKO === true) {
+ this.div.scrollLeft = this.div.scrollLeft;
+ }
+
+ if (!zoomChanged && coordSysUnchanged) {
+ for (var i in this.unrenderedFeatures) {
+ var feature = this.unrenderedFeatures[i];
+ this.drawFeature(feature);
+ }
+ }
+ }
+ if (!this.drawn || zoomChanged || !coordSysUnchanged) {
+ this.drawn = true;
+ var feature;
+ for(var i=0, len=this.features.length; i<len; i++) {
+ this.renderer.locked = (i !== (len - 1));
+ feature = this.features[i];
+ this.drawFeature(feature);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ // we need to set the display style of the root in case it is attached
+ // to a foreign layer
+ var currentDisplay = this.div.style.display;
+ if(currentDisplay != this.renderer.root.style.display) {
+ this.renderer.root.style.display = currentDisplay;
+ }
+ },
+
+ /**
+ * APIMethod: addFeatures
+ * Add Features to the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ */
+ addFeatures: function(features, options) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var notify = !options || !options.silent;
+ if(notify) {
+ var event = {features: features};
+ var ret = this.events.triggerEvent("beforefeaturesadded", event);
+ if(ret === false) {
+ return;
+ }
+ features = event.features;
+ }
+
+ // Track successfully added features for featuresadded event, since
+ // beforefeatureadded can veto single features.
+ var featuresAdded = [];
+ for (var i=0, len=features.length; i<len; i++) {
+ if (i != (features.length - 1)) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+ var feature = features[i];
+
+ if (this.geometryType &&
+ !(feature.geometry instanceof this.geometryType)) {
+ throw new TypeError('addFeatures: component should be an ' +
+ this.geometryType.prototype.CLASS_NAME);
+ }
+
+ //give feature reference to its layer
+ feature.layer = this;
+
+ if (!feature.style && this.style) {
+ feature.style = OpenLayers.Util.extend({}, this.style);
+ }
+
+ if (notify) {
+ if(this.events.triggerEvent("beforefeatureadded",
+ {feature: feature}) === false) {
+ continue;
+ }
+ this.preFeatureInsert(feature);
+ }
+
+ featuresAdded.push(feature);
+ this.features.push(feature);
+ this.drawFeature(feature);
+
+ if (notify) {
+ this.events.triggerEvent("featureadded", {
+ feature: feature
+ });
+ this.onFeatureInsert(feature);
+ }
+ }
+
+ if(notify) {
+ this.events.triggerEvent("featuresadded", {features: featuresAdded});
+ }
+ },
+
+
+ /**
+ * APIMethod: removeFeatures
+ * Remove features from the layer. This erases any drawn features and
+ * removes them from the layer's control. The beforefeatureremoved
+ * and featureremoved events will be triggered for each feature. The
+ * featuresremoved event will be triggered after all features have
+ * been removed. To supress event triggering, use the silent option.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
+ * removed.
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeFeatures: function(features, options) {
+ if(!features || features.length === 0) {
+ return;
+ }
+ if (features === this.features) {
+ return this.removeAllFeatures(options);
+ }
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ if (features === this.selectedFeatures) {
+ features = features.slice();
+ }
+
+ var notify = !options || !options.silent;
+
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+
+ for (var i = features.length - 1; i >= 0; i--) {
+ // We remain locked so long as we're not at 0
+ // and the 'next' feature has a geometry. We do the geometry check
+ // because if all the features after the current one are 'null', we
+ // won't call eraseGeometry, so we break the 'renderer functions
+ // will always be called with locked=false *last*' rule. The end result
+ // is a possible gratiutious unlocking to save a loop through the rest
+ // of the list checking the remaining features every time. So long as
+ // null geoms are rare, this is probably okay.
+ if (i != 0 && features[i-1].geometry) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+
+ var feature = features[i];
+ delete this.unrenderedFeatures[feature.id];
+
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+
+ this.features = OpenLayers.Util.removeItem(this.features, feature);
+ // feature has no layer at this point
+ feature.layer = null;
+
+ if (feature.geometry) {
+ this.renderer.eraseFeatures(feature);
+ }
+
+ //in the case that this feature is one of the selected features,
+ // remove it from that array as well.
+ if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
+ OpenLayers.Util.removeItem(this.selectedFeatures, feature);
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: removeAllFeatures
+ * Remove all features from the layer.
+ *
+ * Parameters:
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeAllFeatures: function(options) {
+ var notify = !options || !options.silent;
+ var features = this.features;
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+ var feature;
+ for (var i = features.length-1; i >= 0; i--) {
+ feature = features[i];
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+ feature.layer = null;
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+ this.renderer.clear();
+ this.features = [];
+ this.unrenderedFeatures = {};
+ this.selectedFeatures = [];
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: destroyFeatures
+ * Erase and destroy features on the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
+ * features to destroy. If not supplied, all features on the layer
+ * will be destroyed.
+ * options - {Object}
+ */
+ destroyFeatures: function(features, options) {
+ var all = (features == undefined); // evaluates to true if
+ // features is null
+ if(all) {
+ features = this.features;
+ }
+ if(features) {
+ this.removeFeatures(features, options);
+ for(var i=features.length-1; i>=0; i--) {
+ features[i].destroy();
+ }
+ }
+ },
+
+ /**
+ * APIMethod: drawFeature
+ * Draw (or redraw) a feature on the layer. If the optional style argument
+ * is included, this style will be used. If no style is included, the
+ * feature's style will be used. If the feature doesn't have a style,
+ * the layer's style will be used.
+ *
+ * This function is not designed to be used when adding features to
+ * the layer (use addFeatures instead). It is meant to be used when
+ * the style of a feature has changed, or in some other way needs to
+ * visually updated *after* it has already been added to a layer. You
+ * must add the feature to the layer for most layer-related events to
+ * happen.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {String | Object} Named render intent or full symbolizer object.
+ */
+ drawFeature: function(feature, style) {
+ // don't try to draw the feature with the renderer if the layer is not
+ // drawn itself
+ if (!this.drawn) {
+ return;
+ }
+ if (typeof style != "object") {
+ if(!style && feature.state === OpenLayers.State.DELETE) {
+ style = "delete";
+ }
+ var renderIntent = style || feature.renderIntent;
+ style = feature.style || this.style;
+ if (!style) {
+ style = this.styleMap.createSymbolizer(feature, renderIntent);
+ }
+ }
+
+ var drawn = this.renderer.drawFeature(feature, style);
+ //TODO remove the check for null when we get rid of Renderer.SVG
+ if (drawn === false || drawn === null) {
+ this.unrenderedFeatures[feature.id] = feature;
+ } else {
+ delete this.unrenderedFeatures[feature.id];
+ }
+ },
+
+ /**
+ * Method: eraseFeatures
+ * Erase features from the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ this.renderer.eraseFeatures(features);
+ },
+
+ /**
+ * Method: getFeatureFromEvent
+ * Given an event, return a feature if the event occurred over one.
+ * Otherwise, return null.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
+ */
+ getFeatureFromEvent: function(evt) {
+ if (!this.renderer) {
+ throw new Error('getFeatureFromEvent called on layer with no ' +
+ 'renderer. This usually means you destroyed a ' +
+ 'layer, but not some handler which is associated ' +
+ 'with it.');
+ }
+ var feature = null;
+ var featureId = this.renderer.getFeatureIdFromEvent(evt);
+ if (featureId) {
+ if (typeof featureId === "string") {
+ feature = this.getFeatureById(featureId);
+ } else {
+ feature = featureId;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureBy
+ * Given a property value, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * property - {String}
+ * value - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * property value or null if there is no such feature.
+ */
+ getFeatureBy: function(property, value) {
+ //TBD - would it be more efficient to use a hash for this.features?
+ var feature = null;
+ for(var i=0, len=this.features.length; i<len; ++i) {
+ if(this.features[i][property] == value) {
+ feature = this.features[i];
+ break;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureById
+ * Given a feature id, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureId - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureId or null if there is no such feature.
+ */
+ getFeatureById: function(featureId) {
+ return this.getFeatureBy('id', featureId);
+ },
+
+ /**
+ * APIMethod: getFeatureByFid
+ * Given a feature fid, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureFid - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureFid or null if there is no such feature.
+ */
+ getFeatureByFid: function(featureFid) {
+ return this.getFeatureBy('fid', featureFid);
+ },
+
+ /**
+ * APIMethod: getFeaturesByAttribute
+ * Returns an array of features that have the given attribute key set to the
+ * given value. Comparison of attribute values takes care of datatypes, e.g.
+ * the string '1234' is not equal to the number 1234.
+ *
+ * Parameters:
+ * attrName - {String}
+ * attrValue - {Mixed}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>}) An array of features that have the
+ * passed named attribute set to the given value.
+ */
+ getFeaturesByAttribute: function(attrName, attrValue) {
+ var i,
+ feature,
+ len = this.features.length,
+ foundFeatures = [];
+ for(i = 0; i < len; i++) {
+ feature = this.features[i];
+ if(feature && feature.attributes) {
+ if (feature.attributes[attrName] === attrValue) {
+ foundFeatures.push(feature);
+ }
+ }
+ }
+ return foundFeatures;
+ },
+
+ /**
+ * Unselect the selected features
+ * i.e. clears the featureSelection array
+ * change the style back
+ clearSelection: function() {
+
+ var vectorLayer = this.map.vectorLayer;
+ for (var i = 0; i < this.map.featureSelection.length; i++) {
+ var featureSelection = this.map.featureSelection[i];
+ vectorLayer.drawFeature(featureSelection, vectorLayer.style);
+ }
+ this.map.featureSelection = [];
+ },
+ */
+
+
+ /**
+ * APIMethod: onFeatureInsert
+ * method called after a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something on feature updates.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ onFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: preFeatureInsert
+ * method called before a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something when features are first added to the
+ * layer, but before they are drawn, such as adjust the style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ preFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the features.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} or null if the layer has no features with
+ * geometries.
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+ var features = this.features;
+ if(features && (features.length > 0)) {
+ var geometry = null;
+ for(var i=0, len=features.length; i<len; i++) {
+ geometry = features[i].geometry;
+ if (geometry) {
+ if (maxExtent === null) {
+ maxExtent = new OpenLayers.Bounds();
+ }
+ maxExtent.extend(geometry.getBounds());
+ }
+ }
+ }
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector/RootContainer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector.RootContainer
+ * A special layer type to combine multiple vector layers inside a single
+ * renderer root container. This class is not supposed to be instantiated
+ * from user space, it is a helper class for controls that require event
+ * processing for multiple vector layers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * Property: displayInLayerSwitcher
+ * Set to false for this layer type
+ */
+ displayInLayerSwitcher: false,
+
+ /**
+ * APIProperty: layers
+ * Layers that are attached to this container. Required config option.
+ */
+ layers: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector.RootContainer
+ * Create a new root container for multiple vector layer. This constructor
+ * is not supposed to be used from user space, it is only to be used by
+ * controls that need feature selection across multiple vector layers.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Required options properties:
+ * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this
+ * container
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root
+ * container
+ */
+
+ /**
+ * Method: display
+ */
+ display: function() {},
+
+ /**
+ * Method: getFeatureFromEvent
+ * walk through the layers to find the feature returned by the event
+ *
+ * Parameters:
+ * evt - {Object} event object with a feature property
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getFeatureFromEvent: function(evt) {
+ var layers = this.layers;
+ var feature;
+ for(var i=0; i<layers.length; i++) {
+ feature = layers[i].getFeatureFromEvent(evt);
+ if(feature) {
+ return feature;
+ }
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ this.collectRoots();
+ map.events.register("changelayer", this, this.handleChangeLayer);
+ },
+
+ /**
+ * Method: removeMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("changelayer", this, this.handleChangeLayer);
+ this.resetRoots();
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: collectRoots
+ * Collects the root nodes of all layers this control is configured with
+ * and moveswien the nodes to this control's layer
+ */
+ collectRoots: function() {
+ var layer;
+ // walk through all map layers, because we want to keep the order
+ for(var i=0; i<this.map.layers.length; ++i) {
+ layer = this.map.layers[i];
+ if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ layer.renderer.moveRoot(this.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: resetRoots
+ * Resets the root nodes back into the layers they belong to.
+ */
+ resetRoots: function() {
+ var layer;
+ for(var i=0; i<this.layers.length; ++i) {
+ layer = this.layers[i];
+ if(this.renderer && layer.renderer.getRenderLayerId() == this.id) {
+ this.renderer.moveRoot(layer.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: handleChangeLayer
+ * Event handler for the map's changelayer event. We need to rebuild
+ * this container's layer dom if order of one of its layers changes.
+ * This handler is added with the setMap method, and removed with the
+ * removeMap method.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleChangeLayer: function(evt) {
+ var layer = evt.layer;
+ if(evt.property == "order" &&
+ OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ this.resetRoots();
+ this.collectRoots();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
+});
+/* ======================================================================
+ OpenLayers/Control/SelectFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ * @requires OpenLayers/Layer/Vector/RootContainer.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * The SelectFeature control selects vector features from a given layer on
+ * click or hover.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforefeaturehighlighted - Triggered before a feature is highlighted
+ * featurehighlighted - Triggered when a feature is highlighted
+ * featureunhighlighted - Triggered when a feature is unhighlighted
+ * boxselectionstart - Triggered before box selection starts
+ * boxselectionend - Triggered after box selection ends
+ */
+
+ /**
+ * Property: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * Property: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Default is false. Only
+ * has meaning if hover is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Select on mouse over and deselect on mouse out. If true, this
+ * ignores clicks and only listens to mouse moves.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: highlightOnly
+ * {Boolean} If true do not actually select features (that is place them in
+ * the layer's selected features array), just highlight them. This property
+ * has no effect if hover is false. Defaults to false.
+ */
+ highlightOnly: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box.
+ */
+ box: false,
+
+ /**
+ * Property: onBeforeSelect
+ * {Function} Optional function to be called before a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onBeforeSelect: function() {},
+
+ /**
+ * APIProperty: onSelect
+ * {Function} Optional function to be called when a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onSelect: function() {},
+
+ /**
+ * APIProperty: onUnselect
+ * {Function} Optional function to be called when a feature is unselected.
+ * The function should expect to be called with a feature.
+ */
+ onUnselect: function() {},
+
+ /**
+ * Property: scope
+ * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
+ * callbacks. If null the scope will be this control.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict selecting to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
+ * root for all layers this control is configured with (if an array of
+ * layers was passed to the constructor), or the vector layer the control
+ * was configured with (if a single layer was passed to the constructor).
+ */
+ layer: null,
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
+ * or null if the control was configured with a single layer
+ */
+ layers: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} The functions that are sent to the handlers.feature for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectStyle
+ * {Object} Hash of styles
+ */
+ selectStyle: null,
+
+ /**
+ * Property: renderIntent
+ * {String} key used to retrieve the select style from the layer's
+ * style map.
+ */
+ renderIntent: "select",
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Constructor: OpenLayers.Control.SelectFeature
+ * Create a new control for selecting features.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
+ * layer(s) this control will select features from.
+ * options - {Object}
+ */
+ initialize: function(layers, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(this.scope === null) {
+ this.scope = this;
+ }
+ this.initLayer(layers);
+ var callbacks = {
+ click: this.clickFeature,
+ clickout: this.clickoutFeature
+ };
+ if (this.hover) {
+ callbacks.over = this.overFeature;
+ callbacks.out = this.outFeature;
+ }
+
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+ this.handlers = {
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, this.callbacks,
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+
+ if (this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ {boxDivClassName: "olHandlerBoxSelectFeature"}
+ );
+ }
+ },
+
+ /**
+ * Method: initLayer
+ * Assign the layer property. If layers is an array, we need to use
+ * a RootContainer.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
+ */
+ initLayer: function(layers) {
+ if(OpenLayers.Util.isArray(layers)) {
+ this.layers = layers;
+ this.layer = new OpenLayers.Layer.Vector.RootContainer(
+ this.id + "_container", {
+ layers: layers
+ }
+ );
+ } else {
+ this.layer = layers;
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ if(this.active && this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if(this.layers) {
+ this.layer.destroy();
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ if(this.layers) {
+ this.map.addLayer(this.layer);
+ }
+ this.handlers.feature.activate();
+ if(this.box && this.handlers.box) {
+ this.handlers.box.activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ this.handlers.feature.deactivate();
+ if(this.handlers.box) {
+ this.handlers.box.deactivate();
+ }
+ if(this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features. To unselect all except for a single
+ * feature, set the options.except property to the feature.
+ *
+ * Parameters:
+ * options - {Object} Optional configuration object.
+ */
+ unselectAll: function(options) {
+ // we'll want an option to supress notification here
+ var layers = this.layers || [this.layer],
+ layer, feature, l, numExcept;
+ for(l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ numExcept = 0;
+ //layer.selectedFeatures is null when layer is destroyed and
+ //one of it's preremovelayer listener calls setLayer
+ //with another layer on this control
+ if(layer.selectedFeatures != null) {
+ while(layer.selectedFeatures.length > numExcept) {
+ feature = layer.selectedFeatures[numExcept];
+ if(!options || options.except != feature) {
+ this.unselect(feature);
+ } else {
+ ++numExcept;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clickFeature
+ * Called on click in a feature
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if(!this.hover) {
+ var selected = (OpenLayers.Util.indexOf(
+ feature.layer.selectedFeatures, feature) > -1);
+ if(selected) {
+ if(this.toggleSelect()) {
+ this.unselect(feature);
+ } else if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ } else {
+ if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: multipleSelect
+ * Allow for multiple selected features based on <multiple> property and
+ * <multipleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Allow for multiple selected features.
+ */
+ multipleSelect: function() {
+ return this.multiple || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.multipleKey]);
+ },
+
+ /**
+ * Method: toggleSelect
+ * Event should toggle the selected state of a feature based on <toggle>
+ * property and <toggleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Toggle the selected state of a feature.
+ */
+ toggleSelect: function() {
+ return this.toggle || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.toggleKey]);
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called on click outside a previously clicked (selected) feature.
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ */
+ clickoutFeature: function(feature) {
+ if(!this.hover && this.clickout) {
+ this.unselectAll();
+ }
+ },
+
+ /**
+ * Method: overFeature
+ * Called on over a feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ overFeature: function(feature) {
+ var layer = feature.layer;
+ if(this.hover) {
+ if(this.highlightOnly) {
+ this.highlight(feature);
+ } else if(OpenLayers.Util.indexOf(
+ layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: outFeature
+ * Called on out of a selected feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ outFeature: function(feature) {
+ if(this.hover) {
+ if(this.highlightOnly) {
+ // we do nothing if we're not the last highlighter of the
+ // feature
+ if(feature._lastHighlighter == this.id) {
+ // if another select control had highlighted the feature before
+ // we did it ourself then we use that control to highlight the
+ // feature as it was before we highlighted it, else we just
+ // unhighlight it
+ if(feature._prevHighlighter &&
+ feature._prevHighlighter != this.id) {
+ delete feature._lastHighlighter;
+ var control = this.map.getControl(
+ feature._prevHighlighter);
+ if(control) {
+ control.highlight(feature);
+ }
+ } else {
+ this.unhighlight(feature);
+ }
+ }
+ } else {
+ this.unselect(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: highlight
+ * Redraw feature with the select style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ highlight: function(feature) {
+ var layer = feature.layer;
+ var cont = this.events.triggerEvent("beforefeaturehighlighted", {
+ feature : feature
+ });
+ if(cont !== false) {
+ feature._prevHighlighter = feature._lastHighlighter;
+ feature._lastHighlighter = this.id;
+ var style = this.selectStyle || this.renderIntent;
+ layer.drawFeature(feature, style);
+ this.events.triggerEvent("featurehighlighted", {feature : feature});
+ }
+ },
+
+ /**
+ * Method: unhighlight
+ * Redraw feature with the "default" style
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unhighlight: function(feature) {
+ var layer = feature.layer;
+ // three cases:
+ // 1. there's no other highlighter, in that case _prev is undefined,
+ // and we just need to undef _last
+ // 2. another control highlighted the feature after we did it, in
+ // that case _last references this other control, and we just
+ // need to undef _prev
+ // 3. another control highlighted the feature before we did it, in
+ // that case _prev references this other control, and we need to
+ // set _last to _prev and undef _prev
+ if(feature._prevHighlighter == undefined) {
+ delete feature._lastHighlighter;
+ } else if(feature._prevHighlighter == this.id) {
+ delete feature._prevHighlighter;
+ } else {
+ feature._lastHighlighter = feature._prevHighlighter;
+ delete feature._prevHighlighter;
+ }
+ layer.drawFeature(feature, feature.style || feature.layer.style ||
+ "default");
+ this.events.triggerEvent("featureunhighlighted", {feature : feature});
+ },
+
+ /**
+ * Method: select
+ * Add feature to the layer's selectedFeature array, render the feature as
+ * selected, and call the onSelect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ select: function(feature) {
+ var cont = this.onBeforeSelect.call(this.scope, feature);
+ var layer = feature.layer;
+ if(cont !== false) {
+ cont = layer.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ layer.selectedFeatures.push(feature);
+ this.highlight(feature);
+ // if the feature handler isn't involved in the feature
+ // selection (because the box handler is used or the
+ // feature is selected programatically) we fake the
+ // feature handler to allow unselecting on click
+ if(!this.handlers.feature.lastFeature) {
+ this.handlers.feature.lastFeature = layer.selectedFeatures[0];
+ }
+ layer.events.triggerEvent("featureselected", {feature: feature});
+ this.onSelect.call(this.scope, feature);
+ }
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the layer's selectedFeature array, render the feature as
+ * normal, and call the onUnselect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ var layer = feature.layer;
+ // Store feature style for restoration later
+ this.unhighlight(feature);
+ OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
+ layer.events.triggerEvent("featureunselected", {feature: feature});
+ this.onUnselect.call(this.scope, feature);
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is true
+ * on.
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
+ */
+ selectBox: function(position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ var bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ // if multiple is false, first deselect currently selected features
+ if (!this.multipleSelect()) {
+ this.unselectAll();
+ }
+
+ // because we're using a box, we consider we want multiple selection
+ var prevMultiple = this.multiple;
+ this.multiple = true;
+ var layers = this.layers || [this.layer];
+ this.events.triggerEvent("boxselectionstart", {layers: layers});
+ var layer;
+ for(var l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ for(var i=0, len = layer.features.length; i<len; ++i) {
+ var feature = layer.features[i];
+ // check if the feature is displayed
+ if (!feature.getVisibility()) {
+ continue;
+ }
+
+ if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+ this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+ if (bounds.toGeometry().intersects(feature.geometry)) {
+ if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ }
+ }
+ }
+ this.multiple = prevMultiple;
+ this.events.triggerEvent("boxselectionend", {layers: layers});
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.handlers.feature.setMap(map);
+ if (this.box) {
+ this.handlers.box.setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Attach a new layer to the control, overriding any existing layers.
+ *
+ * Parameters:
+ * layers - Array of {<OpenLayers.Layer.Vector>} or a single
+ * {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layers) {
+ var isActive = this.active;
+ this.unselectAll();
+ this.deactivate();
+ if(this.layers) {
+ this.layer.destroy();
+ this.layers = null;
+ }
+ this.initLayer(layers);
+ this.handlers.feature.layer = this.layer;
+ if (isActive) {
+ this.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
+/* ======================================================================
+ OpenLayers/Control/Attribution.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Attribution
+ * The attribution control adds attribution from layers to the map display.
+ * It uses 'attribution' property of each layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Attribution =
+ OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: separator
+ * {String} String used to separate layers.
+ */
+ separator: ", ",
+
+ /**
+ * APIProperty: template
+ * {String} Template for the attribution. This has to include the substring
+ * "${layers}", which will be replaced by the layer specific
+ * attributions, separated by <separator>. The default is "${layers}".
+ */
+ template: "${layers}",
+
+ /**
+ * Constructor: OpenLayers.Control.Attribution
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ * Destroy control.
+ */
+ destroy: function() {
+ this.map.events.un({
+ "removelayer": this.updateAttribution,
+ "addlayer": this.updateAttribution,
+ "changelayer": this.updateAttribution,
+ "changebaselayer": this.updateAttribution,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Initialize control.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ this.map.events.on({
+ 'changebaselayer': this.updateAttribution,
+ 'changelayer': this.updateAttribution,
+ 'addlayer': this.updateAttribution,
+ 'removelayer': this.updateAttribution,
+ scope: this
+ });
+ this.updateAttribution();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateAttribution
+ * Update attribution string.
+ */
+ updateAttribution: function() {
+ var attributions = [];
+ if (this.map && this.map.layers) {
+ for(var i=0, len=this.map.layers.length; i<len; i++) {
+ var layer = this.map.layers[i];
+ if (layer.attribution && layer.getVisibility()) {
+ // add attribution only if attribution text is unique
+ if (OpenLayers.Util.indexOf(
+ attributions, layer.attribution) === -1) {
+ attributions.push( layer.attribution );
+ }
+ }
+ }
+ this.div.innerHTML = OpenLayers.String.format(this.template, {
+ layers: attributions.join(this.separator)
+ });
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Attribution"
+});
+/* ======================================================================
+ OpenLayers/Kinetic.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+OpenLayers.Kinetic = OpenLayers.Class({
+
+ /**
+ * Property: threshold
+ * In most cases changing the threshold isn't needed.
+ * In px/ms, default to 0.
+ */
+ threshold: 0,
+
+ /**
+ * Property: deceleration
+ * {Float} the deseleration in px/ms², default to 0.0035.
+ */
+ deceleration: 0.0035,
+
+ /**
+ * Property: nbPoints
+ * {Integer} the number of points we use to calculate the kinetic
+ * initial values.
+ */
+ nbPoints: 100,
+
+ /**
+ * Property: delay
+ * {Float} time to consider to calculate the kinetic initial values.
+ * In ms, default to 200.
+ */
+ delay: 200,
+
+ /**
+ * Property: points
+ * List of points use to calculate the kinetic initial values.
+ */
+ points: undefined,
+
+ /**
+ * Property: timerId
+ * ID of the timer.
+ */
+ timerId: undefined,
+
+ /**
+ * Constructor: OpenLayers.Kinetic
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: begin
+ * Begins the dragging.
+ */
+ begin: function() {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = undefined;
+ this.points = [];
+ },
+
+ /**
+ * Method: update
+ * Updates during the dragging.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The new position.
+ */
+ update: function(xy) {
+ this.points.unshift({xy: xy, tick: new Date().getTime()});
+ if (this.points.length > this.nbPoints) {
+ this.points.pop();
+ }
+ },
+
+ /**
+ * Method: end
+ * Ends the dragging, start the kinetic.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The last position.
+ *
+ * Returns:
+ * {Object} An object with two properties: "speed", and "theta". The
+ * "speed" and "theta" values are to be passed to the move
+ * function when starting the animation.
+ */
+ end: function(xy) {
+ var last, now = new Date().getTime();
+ for (var i = 0, l = this.points.length, point; i < l; i++) {
+ point = this.points[i];
+ if (now - point.tick > this.delay) {
+ break;
+ }
+ last = point;
+ }
+ if (!last) {
+ return;
+ }
+ var time = new Date().getTime() - last.tick;
+ var dist = Math.sqrt(Math.pow(xy.x - last.xy.x, 2) +
+ Math.pow(xy.y - last.xy.y, 2));
+ var speed = dist / time;
+ if (speed == 0 || speed < this.threshold) {
+ return;
+ }
+ var theta = Math.asin((xy.y - last.xy.y) / dist);
+ if (last.xy.x <= xy.x) {
+ theta = Math.PI - theta;
+ }
+ return {speed: speed, theta: theta};
+ },
+
+ /**
+ * Method: move
+ * Launch the kinetic move pan.
+ *
+ * Parameters:
+ * info - {Object} An object with two properties, "speed", and "theta".
+ * These values are those returned from the "end" call.
+ * callback - {Function} Function called on every step of the animation,
+ * receives x, y (values to pan), end (is the last point).
+ */
+ move: function(info, callback) {
+ var v0 = info.speed;
+ var fx = Math.cos(info.theta);
+ var fy = -Math.sin(info.theta);
+
+ var initialTime = new Date().getTime();
+
+ var lastX = 0;
+ var lastY = 0;
+
+ var timerCallback = function() {
+ if (this.timerId == null) {
+ return;
+ }
+
+ var t = new Date().getTime() - initialTime;
+
+ var p = (-this.deceleration * Math.pow(t, 2)) / 2.0 + v0 * t;
+ var x = p * fx;
+ var y = p * fy;
+
+ var args = {};
+ args.end = false;
+ var v = -this.deceleration * t + v0;
+
+ if (v <= 0) {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = null;
+ args.end = true;
+ }
+
+ args.x = x - lastX;
+ args.y = y - lastY;
+ lastX = x;
+ lastY = y;
+ callback(args.x, args.y, args.end);
+ };
+
+ this.timerId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(timerCallback, this)
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Kinetic"
+});
+/* ======================================================================
+ OpenLayers/Filter/Logical.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: filters
+ * {Array(<OpenLayers.Filter>)} Child filters for this filter.
+ */
+ filters: null,
+
+ /**
+ * APIProperty: type
+ * {String} type of logical operator. Available types are:
+ * - OpenLayers.Filter.Logical.AND = "&&";
+ * - OpenLayers.Filter.Logical.OR = "||";
+ * - OpenLayers.Filter.Logical.NOT = "!";
+ */
+ type: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Logical
+ * Creates a logical filter (And, Or, Not).
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>}
+ */
+ initialize: function(options) {
+ this.filters = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to child filters.
+ */
+ destroy: function() {
+ this.filters = null;
+ OpenLayers.Filter.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. A vector
+ * feature may also be provided to evaluate feature attributes in
+ * comparison filters or geometries in spatial filters.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ var i, len;
+ switch(this.type) {
+ case OpenLayers.Filter.Logical.AND:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == false) {
+ return false;
+ }
+ }
+ return true;
+
+ case OpenLayers.Filter.Logical.OR:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == true) {
+ return true;
+ }
+ }
+ return false;
+
+ case OpenLayers.Filter.Logical.NOT:
+ return (!this.filters[0].evaluate(context));
+ }
+ return undefined;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>} Clone of this filter.
+ */
+ clone: function() {
+ var filters = [];
+ for(var i=0, len=this.filters.length; i<len; ++i) {
+ filters.push(this.filters[i].clone());
+ }
+ return new OpenLayers.Filter.Logical({
+ type: this.type,
+ filters: filters
+ });
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR = "||";
+OpenLayers.Filter.Logical.NOT = "!";
+/* ======================================================================
+ OpenLayers/Handler/Drag.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Drag
+ * The drag handler is used to deal with sequences of browser events related
+ * to dragging. The handler is used by controls that want to know when
+ * a drag sequence begins, when a drag is happening, and when it has
+ * finished.
+ *
+ * Controls that use the drag handler typically construct it with callbacks
+ * for 'down', 'move', and 'done'. Callbacks for these keys are called
+ * when the drag begins, with each move, and when the drag is done. In
+ * addition, controls can have callbacks keyed to 'up' and 'out' if they
+ * care to differentiate between the types of events that correspond with
+ * the end of a drag sequence. If no drag actually occurs (no mouse move)
+ * the 'down' and 'up' callbacks will be called, but not the 'done'
+ * callback.
+ *
+ * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a mousedown or touchstart event is received, we want to
+ * record it, but not set 'dragging' until the mouse moves after starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of mousedown events from getting to listeners
+ * on the same element. Default is true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: dragging
+ * {Boolean}
+ */
+ dragging: false,
+
+ /**
+ * Property: last
+ * {<OpenLayers.Pixel>} The last pixel location of the drag.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {<OpenLayers.Pixel>} The first pixel location of the drag.
+ */
+ start: null,
+
+ /**
+ * Property: lastMoveEvt
+ * {Object} The last mousemove event that occurred. Used to
+ * position the map correctly when our "delay drag"
+ * timeout expired.
+ */
+ lastMoveEvt: null,
+
+ /**
+ * Property: oldOnselectstart
+ * {Function}
+ */
+ oldOnselectstart: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase performance, an interval (in
+ * milliseconds) can be set to reduce the number of drag events
+ * called. If set, a new drag event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: timeoutId
+ * {String} The id of the timeout used for the mousedown interval.
+ * This is "private", and should be left alone.
+ */
+ timeoutId: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, the handler will also handle mouse moves when
+ * the cursor has moved out of the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: documentEvents
+ * {Boolean} Are we currently observing document events?
+ */
+ documentEvents: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Drag
+ * Returns OpenLayers.Handler.Drag
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'move' and 'done' are supported. You can also speficy
+ * callbacks for 'down', 'up', and 'out' to respond to those events.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+
+ if (this.documentDrag === true) {
+ var me = this;
+ this._docMove = function(evt) {
+ me.mousemove({
+ xy: {x: evt.clientX, y: evt.clientY},
+ element: document
+ });
+ };
+ this._docUp = function(evt) {
+ me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
+ };
+ }
+ },
+
+
+ /**
+ * Method: dragstart
+ * This private method is factorized from mousedown and touchstart methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragstart: function (evt) {
+ var propagate = true;
+ this.dragging = false;
+ if (this.checkModifiers(evt) &&
+ (OpenLayers.Event.isLeftClick(evt) ||
+ OpenLayers.Event.isSingleTouch(evt))) {
+ this.started = true;
+ this.start = evt.xy;
+ this.last = evt.xy;
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.down(evt);
+ this.callback("down", [evt.xy]);
+
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart ?
+ document.onselectstart : OpenLayers.Function.True;
+ }
+ document.onselectstart = OpenLayers.Function.False;
+
+ propagate = !this.stopDown;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: dragmove
+ * This private method is factorized from mousemove and touchmove methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragmove: function (evt) {
+ this.lastMoveEvt = evt;
+ if (this.started && !this.timeoutId && (evt.xy.x != this.last.x ||
+ evt.xy.y != this.last.y)) {
+ if(this.documentDrag === true && this.documentEvents) {
+ if(evt.element === document) {
+ this.adjustXY(evt);
+ // do setEvent manually because the documentEvents are not
+ // registered with the map
+ this.setEvent(evt);
+ } else {
+ this.removeDocumentEvents();
+ }
+ }
+ if (this.interval > 0) {
+ this.timeoutId = setTimeout(
+ OpenLayers.Function.bind(this.removeTimeout, this),
+ this.interval);
+ }
+ this.dragging = true;
+
+ this.move(evt);
+ this.callback("move", [evt.xy]);
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart;
+ document.onselectstart = OpenLayers.Function.False;
+ }
+ this.last = evt.xy;
+ }
+ return true;
+ },
+
+ /**
+ * Method: dragend
+ * This private method is factorized from mouseup and touchend methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragend: function (evt) {
+ if (this.started) {
+ if(this.documentDrag === true && this.documentEvents) {
+ this.adjustXY(evt);
+ this.removeDocumentEvents();
+ }
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.up(evt);
+ this.callback("up", [evt.xy]);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ document.onselectstart = this.oldOnselectstart;
+ }
+ return true;
+ },
+
+ /**
+ * The four methods below (down, move, up, and out) are used by subclasses
+ * to do their own processing related to these mouse events.
+ */
+
+ /**
+ * Method: down
+ * This method is called during the handling of the mouse down event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse down event
+ */
+ down: function(evt) {
+ },
+
+ /**
+ * Method: move
+ * This method is called during the handling of the mouse move event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse move event
+ *
+ */
+ move: function(evt) {
+ },
+
+ /**
+ * Method: up
+ * This method is called during the handling of the mouse up event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ },
+
+ /**
+ * Method: out
+ * This method is called during the handling of the mouse out event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ },
+
+ /**
+ * The methods below are part of the magic of event handling. Because
+ * they are named like browser events, they are registered as listeners
+ * for the events they represent.
+ */
+
+ /**
+ * Method: mousedown
+ * Handle mousedown events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousedown: function(evt) {
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousemove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: removeTimeout
+ * Private. Called by mousemove() to remove the drag timeout.
+ */
+ removeTimeout: function() {
+ this.timeoutId = null;
+ // if timeout expires while we're still dragging (mouseup
+ // hasn't occurred) then call mousemove to move to the
+ // correct position
+ if(this.dragging) {
+ this.mousemove(this.lastMoveEvt);
+ }
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseup: function(evt) {
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ // override evt.xy with last position since touchend does not have
+ // any touch position
+ evt.xy = this.last;
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouseout events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseout: function (evt) {
+ if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if(this.documentDrag === true) {
+ this.addDocumentEvents();
+ } else {
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.out(evt);
+ this.callback("out", []);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ if(document.onselectstart) {
+ document.onselectstart = this.oldOnselectstart;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Method: click
+ * The drag handler captures the click event. If something else registers
+ * for clicks on the same element, its listener will not be called
+ * after a drag.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ click: function (evt) {
+ // let the click event propagate only if the mouse moved
+ return (this.start == this.last);
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragging = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.dragging = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: adjustXY
+ * Converts event coordinates that are relative to the document body to
+ * ones that are relative to the map viewport. The latter is the default in
+ * OpenLayers.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ adjustXY: function(evt) {
+ var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
+ evt.xy.x -= pos[0];
+ evt.xy.y -= pos[1];
+ },
+
+ /**
+ * Method: addDocumentEvents
+ * Start observing document events when documentDrag is true and the mouse
+ * cursor leaves the map viewport while dragging.
+ */
+ addDocumentEvents: function() {
+ OpenLayers.Element.addClass(document.body, "olDragDown");
+ this.documentEvents = true;
+ OpenLayers.Event.observe(document, "mousemove", this._docMove);
+ OpenLayers.Event.observe(document, "mouseup", this._docUp);
+ },
+
+ /**
+ * Method: removeDocumentEvents
+ * Stops observing document events when documentDrag is true and the mouse
+ * cursor re-enters the map viewport while dragging.
+ */
+ removeDocumentEvents: function() {
+ OpenLayers.Element.removeClass(document.body, "olDragDown");
+ this.documentEvents = false;
+ OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
+ OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Drag"
+});
+/* ======================================================================
+ OpenLayers/Handler/Box.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Box
+ * Handler for dragging a rectangle across the map. Box is displayed
+ * on mouse down, moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: dragHandler
+ * {<OpenLayers.Handler.Drag>}
+ */
+ dragHandler: null,
+
+ /**
+ * APIProperty: boxDivClassName
+ * {String} The CSS class to use for drawing the box. Default is
+ * olHandlerBoxZoomBox
+ */
+ boxDivClassName: 'olHandlerBoxZoomBox',
+
+ /**
+ * Property: boxOffsets
+ * {Object} Caches box offsets from css. This is used by the getBoxOffsets
+ * method.
+ */
+ boxOffsets: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Box
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object}
+ *
+ * Named callbacks:
+ * start - Called when the box drag operation starts.
+ * done - Called when the box drag operation is finished.
+ * The callback should expect to receive a single argument, the box
+ * bounds or a pixel. If the box dragging didn't span more than a 5
+ * pixel distance, a pixel will be returned instead of a bounds object.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ this.dragHandler = new OpenLayers.Handler.Drag(
+ this,
+ {
+ down: this.startBox,
+ move: this.moveBox,
+ out: this.removeBox,
+ up: this.endBox
+ },
+ {keyMask: this.keyMask}
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ if (this.dragHandler) {
+ this.dragHandler.destroy();
+ this.dragHandler = null;
+ }
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ OpenLayers.Handler.prototype.setMap.apply(this, arguments);
+ if (this.dragHandler) {
+ this.dragHandler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: startBox
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>}
+ */
+ startBox: function (xy) {
+ this.callback("start", []);
+ this.zoomBox = OpenLayers.Util.createDiv('zoomBox', {
+ x: -9999, y: -9999
+ });
+ this.zoomBox.className = this.boxDivClassName;
+ this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+
+ this.map.viewPortDiv.appendChild(this.zoomBox);
+
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDrawBox"
+ );
+ },
+
+ /**
+ * Method: moveBox
+ */
+ moveBox: function (xy) {
+ var startX = this.dragHandler.start.x;
+ var startY = this.dragHandler.start.y;
+ var deltaX = Math.abs(startX - xy.x);
+ var deltaY = Math.abs(startY - xy.y);
+
+ var offset = this.getBoxOffsets();
+ this.zoomBox.style.width = (deltaX + offset.width + 1) + "px";
+ this.zoomBox.style.height = (deltaY + offset.height + 1) + "px";
+ this.zoomBox.style.left = (xy.x < startX ?
+ startX - deltaX - offset.left : startX - offset.left) + "px";
+ this.zoomBox.style.top = (xy.y < startY ?
+ startY - deltaY - offset.top : startY - offset.top) + "px";
+ },
+
+ /**
+ * Method: endBox
+ */
+ endBox: function(end) {
+ var result;
+ if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||
+ Math.abs(this.dragHandler.start.y - end.y) > 5) {
+ var start = this.dragHandler.start;
+ var top = Math.min(start.y, end.y);
+ var bottom = Math.max(start.y, end.y);
+ var left = Math.min(start.x, end.x);
+ var right = Math.max(start.x, end.x);
+ result = new OpenLayers.Bounds(left, bottom, right, top);
+ } else {
+ result = this.dragHandler.start.clone(); // i.e. OL.Pixel
+ }
+ this.removeBox();
+
+ this.callback("done", [result]);
+ },
+
+ /**
+ * Method: removeBox
+ * Remove the zoombox from the screen and nullify our reference to it.
+ */
+ removeBox: function() {
+ this.map.viewPortDiv.removeChild(this.zoomBox);
+ this.zoomBox = null;
+ this.boxOffsets = null;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDrawBox"
+ );
+
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function () {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragHandler.activate();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function () {
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ if (this.dragHandler.deactivate()) {
+ if (this.zoomBox) {
+ this.removeBox();
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getBoxOffsets
+ * Determines border offsets for a box, according to the box model.
+ *
+ * Returns:
+ * {Object} an object with the following offsets:
+ * - left
+ * - right
+ * - top
+ * - bottom
+ * - width
+ * - height
+ */
+ getBoxOffsets: function() {
+ if (!this.boxOffsets) {
+ // Determine the box model. If the testDiv's clientWidth is 3, then
+ // the borders are outside and we are dealing with the w3c box
+ // model. Otherwise, the browser uses the traditional box model and
+ // the borders are inside the box bounds, leaving us with a
+ // clientWidth of 1.
+ var testDiv = document.createElement("div");
+ //testDiv.style.visibility = "hidden";
+ testDiv.style.position = "absolute";
+ testDiv.style.border = "1px solid black";
+ testDiv.style.width = "3px";
+ document.body.appendChild(testDiv);
+ var w3cBoxModel = testDiv.clientWidth == 3;
+ document.body.removeChild(testDiv);
+
+ var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+ "border-left-width"));
+ var right = parseInt(OpenLayers.Element.getStyle(
+ this.zoomBox, "border-right-width"));
+ var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+ "border-top-width"));
+ var bottom = parseInt(OpenLayers.Element.getStyle(
+ this.zoomBox, "border-bottom-width"));
+ this.boxOffsets = {
+ left: left,
+ right: right,
+ top: top,
+ bottom: bottom,
+ width: w3cBoxModel === false ? left + right : 0,
+ height: w3cBoxModel === false ? top + bottom : 0
+ };
+ }
+ return this.boxOffsets;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Box"
+});
+/* ======================================================================
+ OpenLayers/Control/ZoomBox.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Box.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomBox
+ * The ZoomBox control enables zooming directly to a given extent, by drawing
+ * a box on the map. The box is drawn by holding down shift, whilst dragging
+ * the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPE}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: out
+ * {Boolean} Should the control be used for zooming out?
+ */
+ out: false,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Zoom only occurs if the keyMask matches the combination of
+ * keys down. Use bitwise operators and one or more of the
+ * <OpenLayers.Handler> constants to construct a keyMask. Leave null if
+ * not used mask. Default is null.
+ */
+ keyMask: null,
+
+ /**
+ * APIProperty: alwaysZoom
+ * {Boolean} Always zoom in/out when box drawn, even if the zoom level does
+ * not change.
+ */
+ alwaysZoom: false,
+
+ /**
+ * APIProperty: zoomOnClick
+ * {Boolean} Should we zoom when no box was dragged, i.e. the user only
+ * clicked? Default is true.
+ */
+ zoomOnClick: true,
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ this.handler = new OpenLayers.Handler.Box( this,
+ {done: this.zoomBox}, {keyMask: this.keyMask} );
+ },
+
+ /**
+ * Method: zoomBox
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
+ */
+ zoomBox: function (position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var bounds,
+ targetCenterPx = position.getCenterPixel();
+ if (!this.out) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat,
+ maxXY.lon, maxXY.lat);
+ } else {
+ var pixWidth = position.right - position.left;
+ var pixHeight = position.bottom - position.top;
+ var zoomFactor = Math.min((this.map.size.h / pixHeight),
+ (this.map.size.w / pixWidth));
+ var extent = this.map.getExtent();
+ var center = this.map.getLonLatFromPixel(targetCenterPx);
+ var xmin = center.lon - (extent.getWidth()/2)*zoomFactor;
+ var xmax = center.lon + (extent.getWidth()/2)*zoomFactor;
+ var ymin = center.lat - (extent.getHeight()/2)*zoomFactor;
+ var ymax = center.lat + (extent.getHeight()/2)*zoomFactor;
+ bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax);
+ }
+ // always zoom in/out
+ var lastZoom = this.map.getZoom(),
+ size = this.map.getSize(),
+ centerPx = {x: size.w / 2, y: size.h / 2},
+ zoom = this.map.getZoomForExtent(bounds),
+ oldRes = this.map.getResolution(),
+ newRes = this.map.getResolutionForZoom(zoom);
+ if (oldRes == newRes) {
+ this.map.setCenter(this.map.getLonLatFromPixel(targetCenterPx));
+ } else {
+ var zoomOriginPx = {
+ x: (oldRes * targetCenterPx.x - newRes * centerPx.x) /
+ (oldRes - newRes),
+ y: (oldRes * targetCenterPx.y - newRes * centerPx.y) /
+ (oldRes - newRes)
+ };
+ this.map.zoomTo(zoom, zoomOriginPx);
+ }
+ if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){
+ this.map.zoomTo(lastZoom + (this.out ? -1 : 1));
+ }
+ } else if (this.zoomOnClick) { // it's a pixel
+ if (!this.out) {
+ this.map.zoomTo(this.map.getZoom() + 1, position);
+ } else {
+ this.map.zoomTo(this.map.getZoom() - 1, position);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomBox"
+});
+/* ======================================================================
+ OpenLayers/Control/DragPan.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragPan
+ * The DragPan control pans the map with a drag of the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: panned
+ * {Boolean} The map moved.
+ */
+ panned: false,
+
+ /**
+ * Property: interval
+ * {Integer} The number of milliseconds that should ellapse before
+ * panning the map again. Defaults to 0 milliseconds, which means that
+ * no separate cycle is used for panning. In most cases you won't want
+ * to change this value. For slow machines/devices larger values can be
+ * tried out.
+ */
+ interval: 0,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: kinetic
+ * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
+ */
+ kinetic: null,
+
+ /**
+ * APIProperty: enableKinetic
+ * {Boolean} Set this option to enable "kinetic dragging". Can be
+ * set to true or to an object. If set to an object this
+ * object will be passed to the {<OpenLayers.Kinetic>}
+ * constructor. Defaults to true.
+ * To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
+ * included in your build config.
+ */
+ enableKinetic: true,
+
+ /**
+ * APIProperty: kineticInterval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * scrolling". Applies only if enableKinetic is set. Defaults
+ * to 10 milliseconds.
+ */
+ kineticInterval: 10,
+
+
+ /**
+ * Method: draw
+ * Creates a Drag handler, using <panMap> and
+ * <panMapDone> as callbacks.
+ */
+ draw: function() {
+ if (this.enableKinetic && OpenLayers.Kinetic) {
+ var config = {interval: this.kineticInterval};
+ if(typeof this.enableKinetic === "object") {
+ config = OpenLayers.Util.extend(config, this.enableKinetic);
+ }
+ this.kinetic = new OpenLayers.Kinetic(config);
+ }
+ this.handler = new OpenLayers.Handler.Drag(this, {
+ "move": this.panMap,
+ "done": this.panMapDone,
+ "down": this.panMapStart
+ }, {
+ interval: this.interval,
+ documentDrag: this.documentDrag
+ }
+ );
+ },
+
+ /**
+ * Method: panMapStart
+ */
+ panMapStart: function() {
+ if(this.kinetic) {
+ this.kinetic.begin();
+ }
+ },
+
+ /**
+ * Method: panMap
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMap: function(xy) {
+ if(this.kinetic) {
+ this.kinetic.update(xy);
+ }
+ this.panned = true;
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: true, animate: false}
+ );
+ },
+
+ /**
+ * Method: panMapDone
+ * Finish the panning operation. Only call setCenter (through <panMap>)
+ * if the map has actually been moved.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMapDone: function(xy) {
+ if(this.panned) {
+ var res = null;
+ if (this.kinetic) {
+ res = this.kinetic.end(xy);
+ }
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: !!res, animate: false}
+ );
+ if (res) {
+ var self = this;
+ this.kinetic.move(res, function(x, y, end) {
+ self.map.pan(x, y, {dragging: !end, animate: false});
+ });
+ }
+ this.panned = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragPan"
+});
+/* ======================================================================
+ OpenLayers/Handler/Click.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Click
+ * A handler for mouse clicks. The intention of this handler is to give
+ * controls more flexibility with handling clicks. Browsers trigger
+ * click events twice for a double-click. In addition, the mousedown,
+ * mousemove, mouseup sequence fires a click event. With this handler,
+ * controls can decide whether to ignore clicks associated with a double
+ * click. By setting a <pixelTolerance>, controls can also ignore clicks
+ * that include a drag. Create a new instance with the
+ * <OpenLayers.Handler.Click> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * APIProperty: delay
+ * {Number} Number of milliseconds between clicks before the event is
+ * considered a double-click.
+ */
+ delay: 300,
+
+ /**
+ * APIProperty: single
+ * {Boolean} Handle single clicks. Default is true. If false, clicks
+ * will not be reported. If true, single-clicks will be reported.
+ */
+ single: true,
+
+ /**
+ * APIProperty: double
+ * {Boolean} Handle double-clicks. Default is false.
+ */
+ 'double': false,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between mouseup and mousedown for an
+ * event to be considered a click. Default is 0. If set to an
+ * integer value, clicks with a drag greater than the value will be
+ * ignored. This property can only be set when the handler is
+ * constructed.
+ */
+ pixelTolerance: 0,
+
+ /**
+ * APIProperty: dblclickTolerance
+ * {Number} Maximum distance in pixels between clicks for a sequence of
+ * events to be considered a double click. Default is 13. If the
+ * distance between two clicks is greater than this value, a double-
+ * click will not be fired.
+ */
+ dblclickTolerance: 13,
+
+ /**
+ * APIProperty: stopSingle
+ * {Boolean} Stop other listeners from being notified of clicks. Default
+ * is false. If true, any listeners registered before this one for
+ * click or rightclick events will not be notified.
+ */
+ stopSingle: false,
+
+ /**
+ * APIProperty: stopDouble
+ * {Boolean} Stop other listeners from being notified of double-clicks.
+ * Default is false. If true, any click listeners registered before
+ * this one will not be notified of *any* double-click events.
+ *
+ * The one caveat with stopDouble is that given a map with two click
+ * handlers, one with stopDouble true and the other with stopSingle
+ * true, the stopSingle handler should be activated last to get
+ * uniform cross-browser performance. Since IE triggers one click
+ * with a dblclick and FF triggers two, if a stopSingle handler is
+ * activated first, all it gets in IE is a single click when the
+ * second handler stops propagation on the dblclick.
+ */
+ stopDouble: false,
+
+ /**
+ * Property: timerId
+ * {Number} The id of the timeout waiting to clear the <delayedCall>.
+ */
+ timerId: null,
+
+ /**
+ * Property: down
+ * {Object} Object that store relevant information about the last
+ * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ down: null,
+
+ /**
+ * Property: last
+ * {Object} Object that store relevant information about the last
+ * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ last: null,
+
+ /**
+ * Property: first
+ * {Object} When waiting for double clicks, this object will store
+ * information about the first click in a two click sequence.
+ */
+ first: null,
+
+ /**
+ * Property: rightclickTimerId
+ * {Number} The id of the right mouse timeout waiting to clear the
+ * <delayedEvent>.
+ */
+ rightclickTimerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Click
+ * Create a new click handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handler's setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object with keys corresponding to callbacks
+ * that will be called by the handler. The callbacks should
+ * expect to recieve a single argument, the click event.
+ * Callbacks for 'click' and 'dblclick' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchmove
+ * Store position of last move, because touchend event can have
+ * an empty "touches" property.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchmove: function(evt) {
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Correctly set event xy property, and add lastTouches to have
+ * touches property from last touchstart or touchmove
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchend: function(evt) {
+ // touchstart may not have been allowed to propagate
+ if (this.down) {
+ evt.xy = this.last.xy;
+ evt.lastTouches = this.last.touches;
+ this.handleSingle(evt);
+ this.down = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mousedown: function(evt) {
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup. Installed to support collection of right mouse events.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mouseup: function (evt) {
+ var propagate = true;
+
+ // Collect right mouse clicks from the mouseup
+ // IE - ignores the second right click in mousedown so using
+ // mouseup instead
+ if (this.checkModifiers(evt) && this.control.handleRightClicks &&
+ OpenLayers.Event.isRightClick(evt)) {
+ propagate = this.rightclick(evt);
+ }
+
+ return propagate;
+ },
+
+ /**
+ * Method: rightclick
+ * Handle rightclick. For a dblrightclick, we get two clicks so we need
+ * to always register for dblrightclick to properly handle single
+ * clicks.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ rightclick: function(evt) {
+ if(this.passesTolerance(evt)) {
+ if(this.rightclickTimerId != null) {
+ //Second click received before timeout this must be
+ // a double click
+ this.clearTimer();
+ this.callback('dblrightclick', [evt]);
+ return !this.stopDouble;
+ } else {
+ //Set the rightclickTimerId, send evt only if double is
+ // true else trigger single
+ var clickEvent = this['double'] ?
+ OpenLayers.Util.extend({}, evt) :
+ this.callback('rightclick', [evt]);
+
+ var delayedRightCall = OpenLayers.Function.bind(
+ this.delayedRightCall,
+ this,
+ clickEvent
+ );
+ this.rightclickTimerId = window.setTimeout(
+ delayedRightCall, this.delay
+ );
+ }
+ }
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: delayedRightCall
+ * Sets <rightclickTimerId> to null. And optionally triggers the
+ * rightclick callback if evt is set.
+ */
+ delayedRightCall: function(evt) {
+ this.rightclickTimerId = null;
+ if (evt) {
+ this.callback('rightclick', [evt]);
+ }
+ },
+
+ /**
+ * Method: click
+ * Handle click events from the browser. This is registered as a listener
+ * for click events and should not be called from other events in this
+ * handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ click: function(evt) {
+ if (!this.last) {
+ this.last = this.getEventInfo(evt);
+ }
+ this.handleSingle(evt);
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. For a dblclick, we get two clicks in some browsers
+ * (FF) and one in others (IE). So we need to always register for
+ * dblclick to properly handle single clicks. This method is registered
+ * as a listener for the dblclick browser event. It should *not* be
+ * called by other methods in this handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ dblclick: function(evt) {
+ this.handleDouble(evt);
+ return !this.stopDouble;
+ },
+
+ /**
+ * Method: handleDouble
+ * Handle double-click sequence.
+ */
+ handleDouble: function(evt) {
+ if (this.passesDblclickTolerance(evt)) {
+ if (this["double"]) {
+ this.callback("dblclick", [evt]);
+ }
+ // to prevent a dblclick from firing the click callback in IE
+ this.clearTimer();
+ }
+ },
+
+ /**
+ * Method: handleSingle
+ * Handle single click sequence.
+ */
+ handleSingle: function(evt) {
+ if (this.passesTolerance(evt)) {
+ if (this.timerId != null) {
+ // already received a click
+ if (this.last.touches && this.last.touches.length === 1) {
+ // touch device, no dblclick event - this may be a double
+ if (this["double"]) {
+ // on Android don't let the browser zoom on the page
+ OpenLayers.Event.preventDefault(evt);
+ }
+ this.handleDouble(evt);
+ }
+ // if we're not in a touch environment we clear the click timer
+ // if we've got a second touch, we'll get two touchend events
+ if (!this.last.touches || this.last.touches.length !== 2) {
+ this.clearTimer();
+ }
+ } else {
+ // remember the first click info so we can compare to the second
+ this.first = this.getEventInfo(evt);
+ // set the timer, send evt only if single is true
+ //use a clone of the event object because it will no longer
+ //be a valid event object in IE in the timer callback
+ var clickEvent = this.single ?
+ OpenLayers.Util.extend({}, evt) : null;
+ this.queuePotentialClick(clickEvent);
+ }
+ }
+ },
+
+ /**
+ * Method: queuePotentialClick
+ * This method is separated out largely to make testing easier (so we
+ * don't have to override window.setTimeout)
+ */
+ queuePotentialClick: function(evt) {
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance. Note
+ * that the pixel tolerance check only works if mousedown events get to
+ * the listeners registered here. If they are stopped by other elements,
+ * the <pixelTolerance> will have no effect here (this method will always
+ * return true).
+ *
+ * Returns:
+ * {Boolean} The click is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(evt) {
+ var passes = true;
+ if (this.pixelTolerance != null && this.down && this.down.xy) {
+ passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
+ // for touch environments, we also enforce that all touches
+ // start and end within the given tolerance to be considered a click
+ if (passes && this.touch &&
+ this.down.touches.length === this.last.touches.length) {
+ // the touchend event doesn't come with touches, so we check
+ // down and last
+ for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
+ if (this.getTouchDistance(
+ this.down.touches[i],
+ this.last.touches[i]
+ ) > this.pixelTolerance) {
+ passes = false;
+ break;
+ }
+ }
+ }
+ }
+ return passes;
+ },
+
+ /**
+ * Method: getTouchDistance
+ *
+ * Returns:
+ * {Boolean} The pixel displacement between two touches.
+ */
+ getTouchDistance: function(from, to) {
+ return Math.sqrt(
+ Math.pow(from.clientX - to.clientX, 2) +
+ Math.pow(from.clientY - to.clientY, 2)
+ );
+ },
+
+ /**
+ * Method: passesDblclickTolerance
+ * Determine whether the event is within the optional double-cick pixel
+ * tolerance.
+ *
+ * Returns:
+ * {Boolean} The click is within the double-click pixel tolerance.
+ */
+ passesDblclickTolerance: function(evt) {
+ var passes = true;
+ if (this.down && this.first) {
+ passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
+ }
+ return passes;
+ },
+
+ /**
+ * Method: clearTimer
+ * Clear the timer and set <timerId> to null.
+ */
+ clearTimer: function() {
+ if (this.timerId != null) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ if (this.rightclickTimerId != null) {
+ window.clearTimeout(this.rightclickTimerId);
+ this.rightclickTimerId = null;
+ }
+ },
+
+ /**
+ * Method: delayedCall
+ * Sets <timerId> to null. And optionally triggers the click callback if
+ * evt is set.
+ */
+ delayedCall: function(evt) {
+ this.timerId = null;
+ if (evt) {
+ this.callback("click", [evt]);
+ }
+ },
+
+ /**
+ * Method: getEventInfo
+ * This method allows us to store event information without storing the
+ * actual event. In touch devices (at least), the same event is
+ * modified between touchstart, touchmove, and touchend.
+ *
+ * Returns:
+ * {Object} An object with event related info.
+ */
+ getEventInfo: function(evt) {
+ var touches;
+ if (evt.touches) {
+ var len = evt.touches.length;
+ touches = new Array(len);
+ var touch;
+ for (var i=0; i<len; i++) {
+ touch = evt.touches[i];
+ touches[i] = {
+ clientX: touch.olClientX,
+ clientY: touch.olClientY
+ };
+ }
+ }
+ return {
+ xy: evt.xy,
+ touches: touches
+ };
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.clearTimer();
+ this.down = null;
+ this.first = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Click"
+});
+/* ======================================================================
+ OpenLayers/Control/Navigation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/ZoomBox.js
+ * @requires OpenLayers/Control/DragPan.js
+ * @requires OpenLayers/Handler/MouseWheel.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Navigation
+ * The navigation control handles map browsing with mouse events (dragging,
+ * double-clicking, and scrolling the wheel). Create a new navigation
+ * control with the <OpenLayers.Control.Navigation> control.
+ *
+ * Note that this control is added to the map by default (if no controls
+ * array is sent in the options object to the <OpenLayers.Map>
+ * constructor).
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: zoomBox
+ * {<OpenLayers.Control.ZoomBox>}
+ */
+ zoomBox: null,
+
+ /**
+ * APIProperty: zoomBoxEnabled
+ * {Boolean} Whether the user can draw a box to zoom
+ */
+ zoomBoxEnabled: true,
+
+ /**
+ * APIProperty: zoomWheelEnabled
+ * {Boolean} Whether the mousewheel should zoom the map
+ */
+ zoomWheelEnabled: true,
+
+ /**
+ * Property: mouseWheelOptions
+ * {Object} Options passed to the MouseWheel control (only useful if
+ * <zoomWheelEnabled> is set to true). Default is no options for maps
+ * with fractionalZoom set to true, otherwise
+ * {cumulative: false, interval: 50, maxDelta: 6}
+ */
+ mouseWheelOptions: null,
+
+ /**
+ * APIProperty: handleRightClicks
+ * {Boolean} Whether or not to handle right clicks. Default is false.
+ */
+ handleRightClicks: false,
+
+ /**
+ * APIProperty: zoomBoxKeyMask
+ * {Integer} <OpenLayers.Handler> key code of the key, which has to be
+ * pressed, while drawing the zoom box with the mouse on the screen.
+ * You should probably set handleRightClicks to true if you use this
+ * with MOD_CTRL, to disable the context menu for machines which use
+ * CTRL-Click as a right click.
+ * Default: <OpenLayers.Handler.MOD_SHIFT>
+ */
+ zoomBoxKeyMask: OpenLayers.Handler.MOD_SHIFT,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.Navigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ this.deactivate();
+
+ if (this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+
+ if (this.zoomBox) {
+ this.zoomBox.destroy();
+ }
+ this.zoomBox = null;
+
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ }
+ this.pinchZoom = null;
+
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ this.dragPan.activate();
+ if (this.zoomWheelEnabled) {
+ this.handlers.wheel.activate();
+ }
+ this.handlers.click.activate();
+ if (this.zoomBoxEnabled) {
+ this.zoomBox.activate();
+ }
+ if (this.pinchZoom) {
+ this.pinchZoom.activate();
+ }
+ return OpenLayers.Control.prototype.activate.apply(this,arguments);
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if (this.pinchZoom) {
+ this.pinchZoom.deactivate();
+ }
+ this.zoomBox.deactivate();
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.handlers.wheel.deactivate();
+ return OpenLayers.Control.prototype.deactivate.apply(this,arguments);
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ // disable right mouse context menu for support of right click events
+ if (this.handleRightClicks) {
+ this.map.viewPortDiv.oncontextmenu = OpenLayers.Function.False;
+ }
+
+ var clickCallbacks = {
+ 'click': this.defaultClick,
+ 'dblclick': this.defaultDblClick,
+ 'dblrightclick': this.defaultDblRightClick
+ };
+ var clickOptions = {
+ 'double': true,
+ 'stopDouble': true
+ };
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.zoomBox = new OpenLayers.Control.ZoomBox(
+ {map: this.map, keyMask: this.zoomBoxKeyMask});
+ this.dragPan.draw();
+ this.zoomBox.draw();
+ var wheelOptions = this.map.fractionalZoom ? {} : {
+ cumulative: false,
+ interval: 50,
+ maxDelta: 6
+ };
+ this.handlers.wheel = new OpenLayers.Handler.MouseWheel(
+ this, {up : this.wheelUp, down: this.wheelDown},
+ OpenLayers.Util.extend(wheelOptions, this.mouseWheelOptions)
+ );
+ if (OpenLayers.Control.PinchZoom) {
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend(
+ {map: this.map}, this.pinchZoomOptions));
+ }
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if (evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ /**
+ * Method: defaultDblRightClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblRightClick: function (evt) {
+ this.map.zoomTo(this.map.zoom - 1, evt.xy);
+ },
+
+ /**
+ * Method: wheelChange
+ *
+ * Parameters:
+ * evt - {Event}
+ * deltaZ - {Integer}
+ */
+ wheelChange: function(evt, deltaZ) {
+ if (!this.map.fractionalZoom) {
+ deltaZ = Math.round(deltaZ);
+ }
+ var currentZoom = this.map.getZoom(),
+ newZoom = currentZoom + deltaZ;
+ newZoom = Math.max(newZoom, 0);
+ newZoom = Math.min(newZoom, this.map.getNumZoomLevels());
+ if (newZoom === currentZoom) {
+ return;
+ }
+ this.map.zoomTo(newZoom, evt.xy);
+ },
+
+ /**
+ * Method: wheelUp
+ * User spun scroll wheel up
+ *
+ * Parameters:
+ * evt - {Event}
+ * delta - {Integer}
+ */
+ wheelUp: function(evt, delta) {
+ this.wheelChange(evt, delta || 1);
+ },
+
+ /**
+ * Method: wheelDown
+ * User spun scroll wheel down
+ *
+ * Parameters:
+ * evt - {Event}
+ * delta - {Integer}
+ */
+ wheelDown: function(evt, delta) {
+ this.wheelChange(evt, delta || -1);
+ },
+
+ /**
+ * Method: disableZoomBox
+ */
+ disableZoomBox : function() {
+ this.zoomBoxEnabled = false;
+ this.zoomBox.deactivate();
+ },
+
+ /**
+ * Method: enableZoomBox
+ */
+ enableZoomBox : function() {
+ this.zoomBoxEnabled = true;
+ if (this.active) {
+ this.zoomBox.activate();
+ }
+ },
+
+ /**
+ * Method: disableZoomWheel
+ */
+
+ disableZoomWheel : function() {
+ this.zoomWheelEnabled = false;
+ this.handlers.wheel.deactivate();
+ },
+
+ /**
+ * Method: enableZoomWheel
+ */
+
+ enableZoomWheel : function() {
+ this.zoomWheelEnabled = true;
+ if (this.active) {
+ this.handlers.wheel.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Navigation"
+});
+/* ======================================================================
+ OpenLayers/Layer/WMS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMS
+ * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web
+ * Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: { service: "WMS",
+ version: "1.1.1",
+ request: "GetMap",
+ styles: "",
+ format: "image/jpeg"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for WMS layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: encodeBBOX
+ * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
+ * but some services want it that way. Default false.
+ */
+ encodeBBOX: false,
+
+ /**
+ * APIProperty: noMagic
+ * {Boolean} If true, the image format will not be automagicaly switched
+ * from image/jpeg to image/png or image/gif when using
+ * TRANSPARENT=TRUE. Also isBaseLayer will not changed by the
+ * constructor. Default false.
+ */
+ noMagic: false,
+
+ /**
+ * Property: yx
+ * {Object} Keys in this object are EPSG codes for which the axis order
+ * is to be reversed (yx instead of xy, LatLon instead of LonLat), with
+ * true as value. This is only relevant for WMS versions >= 1.3.0, and
+ * only if yx is not set in <OpenLayers.Projection.defaults> for the
+ * used projection.
+ */
+ yx: {},
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS
+ * Create a new WMS layer object
+ *
+ * Examples:
+ *
+ * The code below creates a simple WMS layer using the image/jpeg format.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {layers: "modis,global_mosaic"});
+ * (end)
+ * Note the 3rd argument (params). Properties added to this object will be
+ * added to the WMS GetMap requests used for this layer's tiles. The only
+ * mandatory parameter is "layers". Other common WMS params include
+ * "transparent", "styles" and "format". Note that the "srs" param will
+ * always be ignored. Instead, it will be derived from the baseLayer's or
+ * map's projection.
+ *
+ * The code below creates a transparent WMS layer with additional options.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {
+ * layers: "modis,global_mosaic",
+ * transparent: true
+ * }, {
+ * opacity: 0.5,
+ * singleTile: true
+ * });
+ * (end)
+ * Note that by default, a WMS layer is configured as baseLayer. Setting
+ * the "transparent" param to true will apply some magic (see <noMagic>).
+ * The default image format changes from image/jpeg to image/png, and the
+ * layer is not configured as baseLayer.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the WMS
+ * (e.g. http://wms.jpl.nasa.gov/wms.cgi)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ * These options include all properties listed above, plus the ones
+ * inherited from superclasses.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) {
+ params.EXCEPTIONS = "INIMAGE";
+ }
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+
+ //layer is transparent
+ if (!this.noMagic && this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "image/jpeg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
+ : "image/png";
+ }
+ }
+
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMS(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: reverseAxisOrder
+ * Returns true if the axis order is reversed for the WMS version and
+ * projection of the layer.
+ *
+ * Returns:
+ * {Boolean} true if the axis order is reversed, false otherwise.
+ */
+ reverseAxisOrder: function() {
+ var projCode = this.projection.getCode();
+ return parseFloat(this.params.VERSION) >= 1.3 &&
+ !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] &&
+ OpenLayers.Projection.defaults[projCode].yx));
+ },
+
+ /**
+ * Method: getURL
+ * Return a GetMap query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+
+ var imageSize = this.getImageSize();
+ var newParams = {};
+ // WMS 1.3 introduced axis order
+ var reverseAxisOrder = this.reverseAxisOrder();
+ newParams.BBOX = this.encodeBBOX ?
+ bounds.toBBOX(null, reverseAxisOrder) :
+ bounds.toArray(reverseAxisOrder);
+ newParams.WIDTH = imageSize.w;
+ newParams.HEIGHT = imageSize.h;
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ /**
+ * APIMethod: getFullRequestString
+ * Combine the layer's url with its params and these newParams.
+ *
+ * Add the SRS parameter from projection -- this is probably
+ * more eloquently done via a setProjection() method, but this
+ * works for now and always.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ var mapProjection = this.map.getProjectionObject();
+ var projectionCode = this.projection && this.projection.equals(mapProjection) ?
+ this.projection.getCode() :
+ mapProjection.getCode();
+ var value = (projectionCode == "none") ? null : projectionCode;
+ if (parseFloat(this.params.VERSION) >= 1.3) {
+ this.params.CRS = value;
+ } else {
+ this.params.SRS = value;
+ }
+
+ if (typeof this.params.TRANSPARENT == "boolean") {
+ newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE";
+ }
+
+ return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+ this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMS"
+});
+/* ======================================================================
+ OpenLayers/Renderer/SVG.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: "http://www.w3.org/2000/svg",
+
+ /**
+ * Property: xlinkns
+ * {String}
+ */
+ xlinkns: "http://www.w3.org/1999/xlink",
+
+ /**
+ * Constant: MAX_PIXEL
+ * {Integer} Firefox has a limitation where values larger or smaller than
+ * about 15000 in an SVG document lock the browser up. This
+ * works around it.
+ */
+ MAX_PIXEL: 15000,
+
+ /**
+ * Property: translationParameters
+ * {Object} Hash with "x" and "y" properties
+ */
+ translationParameters: null,
+
+ /**
+ * Property: symbolMetrics
+ * {Object} Cache for symbol metrics according to their svg coordinate
+ * space. This is an object keyed by the symbol's id, and values are
+ * an array of [width, centerX, centerY].
+ */
+ symbolMetrics: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.SVG
+ *
+ * Parameters:
+ * containerID - {String}
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ this.translationParameters = {x: 0, y: 0};
+
+ this.symbolMetrics = {};
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SVG renderer
+ */
+ supported: function() {
+ var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+ return (document.implementation &&
+ (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
+ document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
+ document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+ },
+
+ /**
+ * Method: inValidRange
+ * See #669 for more information
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ * xyOnly - {Boolean} whether or not to just check for x and y, which means
+ * to not take the current translation parameters into account if true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the 'x' and 'y' coordinates are in the
+ * valid range.
+ */
+ inValidRange: function(x, y, xyOnly) {
+ var left = x + (xyOnly ? 0 : this.translationParameters.x);
+ var top = y + (xyOnly ? 0 : this.translationParameters.y);
+ return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+ top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+ },
+
+ /**
+ * Method: setExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+
+ var resolution = this.getResolution(),
+ left = -extent.left / resolution,
+ top = extent.top / resolution;
+
+ // If the resolution has changed, start over changing the corner, because
+ // the features will redraw.
+ if (resolutionChanged) {
+ this.left = left;
+ this.top = top;
+ // Set the viewbox
+ var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+ this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+ this.translate(this.xOffset, 0);
+ return true;
+ } else {
+ var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
+ if (!inRange) {
+ // recenter the coordinate system
+ this.setExtent(extent, true);
+ }
+ return coordSysUnchanged && inRange;
+ }
+ },
+
+ /**
+ * Method: translate
+ * Transforms the SVG coordinate system
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ *
+ * Returns:
+ * {Boolean} true if the translation parameters are in the valid coordinates
+ * range, false otherwise.
+ */
+ translate: function(x, y) {
+ if (!this.inValidRange(x, y, true)) {
+ return false;
+ } else {
+ var transformString = "";
+ if (x || y) {
+ transformString = "translate(" + x + "," + y + ")";
+ }
+ this.root.setAttributeNS(null, "transform", transformString);
+ this.translationParameters = {x: x, y: y};
+ return true;
+ }
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+ this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+ },
+
+ /**
+ * Method: getNodeType
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "image";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "svg";
+ } else {
+ nodeType = "circle";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ nodeType = "polyline";
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ nodeType = "polygon";
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "path";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a SVG node.
+ *
+ * Takes care to adjust stroke width and point radius to be
+ * resolution-relative
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ */
+ setStyle: function(node, style, options) {
+ style = style || node._style;
+ options = options || node._options;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.setAttributeNS(null, "title", title);
+ //Standards-conformant SVG
+ // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
+ var titleNode = node.getElementsByTagName("title");
+ if (titleNode.length > 0) {
+ titleNode[0].firstChild.textContent = title;
+ } else {
+ var label = this.nodeFactory(null, "title");
+ label.textContent = title;
+ node.appendChild(label);
+ }
+ }
+
+ var r = parseFloat(node.getAttributeNS(null, "r"));
+ var widthFactor = 1;
+ var pos;
+ if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+ node.style.visibility = "";
+ if (style.graphic === false) {
+ node.style.visibility = "hidden";
+ } else if (style.externalGraphic) {
+ pos = this.getPosition(node);
+ if (style.graphicWidth && style.graphicHeight) {
+ node.setAttributeNS(null, "preserveAspectRatio", "none");
+ }
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+ node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+ node.setAttributeNS(null, "width", width);
+ node.setAttributeNS(null, "height", height);
+ node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
+ node.setAttributeNS(null, "style", "opacity: "+opacity);
+ node.onclick = OpenLayers.Event.preventDefault;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ // the symbol viewBox is three times as large as the symbol
+ var offset = style.pointRadius * 3;
+ var size = offset * 2;
+ var src = this.importSymbol(style.graphicName);
+ pos = this.getPosition(node);
+ widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
+
+ // remove the node from the dom before we modify it. This
+ // prevents various rendering issues in Safari and FF
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+ if(parent) {
+ parent.removeChild(node);
+ }
+
+ // The more appropriate way to implement this would be use/defs,
+ // but due to various issues in several browsers, it is safer to
+ // copy the symbols instead of referencing them.
+ // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
+ // and this email thread
+ // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
+ node.firstChild && node.removeChild(node.firstChild);
+ node.appendChild(src.firstChild.cloneNode(true));
+ node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
+
+ node.setAttributeNS(null, "width", size);
+ node.setAttributeNS(null, "height", size);
+ node.setAttributeNS(null, "x", pos.x - offset);
+ node.setAttributeNS(null, "y", pos.y - offset);
+
+ // now that the node has all its new properties, insert it
+ // back into the dom where it was
+ if(nextSibling) {
+ parent.insertBefore(node, nextSibling);
+ } else if(parent) {
+ parent.appendChild(node);
+ }
+ } else {
+ node.setAttributeNS(null, "r", style.pointRadius);
+ }
+
+ var rotation = style.rotation;
+
+ if ((rotation !== undefined || node._rotation !== undefined) && pos) {
+ node._rotation = rotation;
+ rotation |= 0;
+ if (node.nodeName !== "svg") {
+ node.setAttributeNS(null, "transform",
+ "rotate(" + rotation + " " + pos.x + " " +
+ pos.y + ")");
+ } else {
+ var metrics = this.symbolMetrics[src.id];
+ node.firstChild.setAttributeNS(null, "transform", "rotate("
+ + rotation + " "
+ + metrics[1] + " "
+ + metrics[2] + ")");
+ }
+ }
+ }
+
+ if (options.isFilled) {
+ node.setAttributeNS(null, "fill", style.fillColor);
+ node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+ } else {
+ node.setAttributeNS(null, "fill", "none");
+ }
+
+ if (options.isStroked) {
+ node.setAttributeNS(null, "stroke", style.strokeColor);
+ node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+ node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+ node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
+ // Hard-coded linejoin for now, to make it look the same as in VML.
+ // There is no strokeLinejoin property yet for symbolizers.
+ node.setAttributeNS(null, "stroke-linejoin", "round");
+ style.strokeDashstyle && node.setAttributeNS(null,
+ "stroke-dasharray", this.dashStyle(style, widthFactor));
+ } else {
+ node.setAttributeNS(null, "stroke", "none");
+ }
+
+ if (style.pointerEvents) {
+ node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+ }
+
+ if (style.cursor != null) {
+ node.setAttributeNS(null, "cursor", style.cursor);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ * widthFactor - {Number}
+ *
+ * Returns:
+ * {String} A SVG compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style, widthFactor) {
+ var w = style.strokeWidth * widthFactor;
+ var str = style.strokeDashstyle;
+ switch (str) {
+ case 'solid':
+ return 'none';
+ case 'dot':
+ return [1, 4 * w].join();
+ case 'dash':
+ return [4 * w, 4 * w].join();
+ case 'dashdot':
+ return [4 * w, 4 * w, 1, 4 * w].join();
+ case 'longdash':
+ return [8 * w, 4 * w].join();
+ case 'longdashdot':
+ return [8 * w, 4 * w, 1, 4 * w].join();
+ default:
+ return OpenLayers.String.trim(str).replace(/\s+/g, ",");
+ }
+ },
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElementNS(this.xmlns, type);
+ if (id) {
+ node.setAttributeNS(null, "id", id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+ return (type == node.nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
+ svg.style.display = "block";
+ return svg;
+ },
+
+ /**
+ * Method: createRoot
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "g");
+ },
+
+ /**
+ * Method: createDefs
+ *
+ * Returns:
+ * {DOMElement} The element to which we'll add the symbol definitions
+ */
+ createDefs: function() {
+ var defs = this.nodeFactory(this.container.id + "_defs", "defs");
+ this.rendererRoot.appendChild(defs);
+ return defs;
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {Float}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry, radius) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "cx", x);
+ node.setAttributeNS(null, "cy", y);
+ node.setAttributeNS(null, "r", radius);
+ return node;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {
+ var d = "";
+ var draw = true;
+ var complete = true;
+ var linearRingResult, path;
+ for (var j=0, len=geometry.components.length; j<len; j++) {
+ d += " M";
+ linearRingResult = this.getComponentsString(
+ geometry.components[j].components, " ");
+ path = linearRingResult.path;
+ if (path) {
+ d += " " + path;
+ complete = linearRingResult.complete && complete;
+ } else {
+ draw = false;
+ }
+ }
+ d += " z";
+ if (draw) {
+ node.setAttributeNS(null, "d", d);
+ node.setAttributeNS(null, "fill-rule", "evenodd");
+ return complete ? node : null;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawRectangle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "x", x);
+ node.setAttributeNS(null, "y", y);
+ node.setAttributeNS(null, "width", geometry.width / resolution);
+ node.setAttributeNS(null, "height", geometry.height / resolution);
+ return node;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var drawOutline = (!!style.labelOutlineWidth);
+ // First draw text in halo color and size and overlay the
+ // normal text afterwards
+ if (drawOutline) {
+ var outlineStyle = OpenLayers.Util.extend({}, style);
+ outlineStyle.fontColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
+ if (style.labelOutlineOpacity) {
+ outlineStyle.fontOpacity = style.labelOutlineOpacity;
+ }
+ delete outlineStyle.labelOutlineWidth;
+ this.drawText(featureId, outlineStyle, location);
+ }
+
+ var resolution = this.getResolution();
+
+ var x = ((location.x - this.featureDx) / resolution + this.left);
+ var y = (location.y / resolution - this.top);
+
+ var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
+ var label = this.nodeFactory(featureId + suffix, "text");
+
+ label.setAttributeNS(null, "x", x);
+ label.setAttributeNS(null, "y", -y);
+
+ if (style.fontColor) {
+ label.setAttributeNS(null, "fill", style.fontColor);
+ }
+ if (style.fontStrokeColor) {
+ label.setAttributeNS(null, "stroke", style.fontStrokeColor);
+ }
+ if (style.fontStrokeWidth) {
+ label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
+ }
+ if (style.fontOpacity) {
+ label.setAttributeNS(null, "opacity", style.fontOpacity);
+ }
+ if (style.fontFamily) {
+ label.setAttributeNS(null, "font-family", style.fontFamily);
+ }
+ if (style.fontSize) {
+ label.setAttributeNS(null, "font-size", style.fontSize);
+ }
+ if (style.fontWeight) {
+ label.setAttributeNS(null, "font-weight", style.fontWeight);
+ }
+ if (style.fontStyle) {
+ label.setAttributeNS(null, "font-style", style.fontStyle);
+ }
+ if (style.labelSelect === true) {
+ label.setAttributeNS(null, "pointer-events", "visible");
+ label._featureId = featureId;
+ } else {
+ label.setAttributeNS(null, "pointer-events", "none");
+ }
+ var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
+ label.setAttributeNS(null, "text-anchor",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
+
+ if (OpenLayers.IS_GECKO === true) {
+ label.setAttributeNS(null, "dominant-baseline",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
+ }
+
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ while (label.childNodes.length > numRows) {
+ label.removeChild(label.lastChild);
+ }
+ for (var i = 0; i < numRows; i++) {
+ var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
+ if (style.labelSelect === true) {
+ tspan._featureId = featureId;
+ tspan._geometry = location;
+ tspan._geometryClass = location.CLASS_NAME;
+ }
+ if (OpenLayers.IS_GECKO === false) {
+ tspan.setAttributeNS(null, "baseline-shift",
+ OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
+ }
+ tspan.setAttribute("x", x);
+ if (i == 0) {
+ var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
+ } else {
+ tspan.setAttribute("dy", "1em");
+ }
+ tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
+ if (!tspan.parentNode) {
+ label.appendChild(tspan);
+ }
+ }
+
+ if (!label.parentNode) {
+ this.textRoot.appendChild(label);
+ }
+ },
+
+ /**
+ * Method: getComponentString
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+ * separator - {String} character between coordinate pairs. Defaults to ","
+ *
+ * Returns:
+ * {Object} hash with properties "path" (the string created from the
+ * components and "complete" (false if the renderer was unable to
+ * draw all components)
+ */
+ getComponentsString: function(components, separator) {
+ var renderCmp = [];
+ var complete = true;
+ var len = components.length;
+ var strings = [];
+ var str, component;
+ for(var i=0; i<len; i++) {
+ component = components[i];
+ renderCmp.push(component);
+ str = this.getShortString(component);
+ if (str) {
+ strings.push(str);
+ } else {
+ // The current component is outside the valid range. Let's
+ // see if the previous or next component is inside the range.
+ // If so, add the coordinate of the intersection with the
+ // valid range bounds.
+ if (i > 0) {
+ if (this.getShortString(components[i - 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i-1]));
+ }
+ }
+ if (i < len - 1) {
+ if (this.getShortString(components[i + 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i+1]));
+ }
+ }
+ complete = false;
+ }
+ }
+
+ return {
+ path: strings.join(separator || ","),
+ complete: complete
+ };
+ },
+
+ /**
+ * Method: clipLine
+ * Given two points (one inside the valid range, and one outside),
+ * clips the line betweeen the two points so that the new points are both
+ * inside the valid range.
+ *
+ * Parameters:
+ * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * invalid point
+ * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * valid point
+ * Returns
+ * {String} the SVG coordinate pair of the clipped point (like
+ * getShortString), or an empty string if both passed componets are at
+ * the same point.
+ */
+ clipLine: function(badComponent, goodComponent) {
+ if (goodComponent.equals(badComponent)) {
+ return "";
+ }
+ var resolution = this.getResolution();
+ var maxX = this.MAX_PIXEL - this.translationParameters.x;
+ var maxY = this.MAX_PIXEL - this.translationParameters.y;
+ var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
+ var y1 = this.top - goodComponent.y / resolution;
+ var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
+ var y2 = this.top - badComponent.y / resolution;
+ var k;
+ if (x2 < -maxX || x2 > maxX) {
+ k = (y2 - y1) / (x2 - x1);
+ x2 = x2 < 0 ? -maxX : maxX;
+ y2 = y1 + (x2 - x1) * k;
+ }
+ if (y2 < -maxY || y2 > maxY) {
+ k = (x2 - x1) / (y2 - y1);
+ y2 = y2 < 0 ? -maxY : maxY;
+ x2 = x1 + (y2 - y1) * k;
+ }
+ return x2 + "," + y2;
+ },
+
+ /**
+ * Method: getShortString
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {String} or false if point is outside the valid range
+ */
+ getShortString: function(point) {
+ var resolution = this.getResolution();
+ var x = ((point.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - point.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ return x + "," + y;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getPosition
+ * Finds the position of an svg node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} hash with x and y properties, representing the coordinates
+ * within the svg coordinate system
+ */
+ getPosition: function(node) {
+ return({
+ x: parseFloat(node.getAttributeNS(null, "cx")),
+ y: parseFloat(node.getAttributeNS(null, "cy"))
+ });
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {DOMElement} - the imported symbol
+ */
+ importSymbol: function (graphicName) {
+ if (!this.defs) {
+ // create svg defs tag
+ this.defs = this.createDefs();
+ }
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the defs
+ var existing = document.getElementById(id);
+ if (existing != null) {
+ return existing;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolNode = this.nodeFactory(id, "symbol");
+ var node = this.nodeFactory(null, "polygon");
+ symbolNode.appendChild(node);
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var points = [];
+ var x,y;
+ for (var i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+ points.push(x, ",", y);
+ }
+
+ node.setAttributeNS(null, "points", points.join(" "));
+
+ var width = symbolExtent.getWidth();
+ var height = symbolExtent.getHeight();
+ // create a viewBox three times as large as the symbol itself,
+ // to allow for strokeWidth being displayed correctly at the corners.
+ var viewBox = [symbolExtent.left - width,
+ symbolExtent.bottom - height, width * 3, height * 3];
+ symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+ this.symbolMetrics[id] = [
+ Math.max(width, height),
+ symbolExtent.getCenterLonLat().lon,
+ symbolExtent.getCenterLonLat().lat
+ ];
+
+ this.defs.appendChild(symbolNode);
+ return symbolNode;
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
+ if(!featureId) {
+ var target = evt.target;
+ featureId = target.parentNode && target != this.rendererRoot ?
+ target.parentNode._featureId : undefined;
+ }
+ return featureId;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_ALIGN = {
+ "l": "start",
+ "r": "end",
+ "b": "bottom",
+ "t": "hanging"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
+ // according to
+ // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
+ // a baseline-shift of -70% shifts the text exactly from the
+ // bottom to the top of the baseline, so -35% moves the text to
+ // the center of the baseline.
+ "t": "-70%",
+ "b": "0"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Function: OpenLayers.Renderer.SVG.preventDefault
+ * *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead.
+ * Used to prevent default events (especially opening images in a new tab on
+ * ctrl-click) from being executed for externalGraphic symbols
+ */
+OpenLayers.Renderer.SVG.preventDefault = function(e) {
+ OpenLayers.Event.preventDefault(e);
+};
+/* ======================================================================
+ OpenLayers/Popup.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+
+/**
+ * Class: OpenLayers.Popup
+ * A popup is a small div that can opened and closed on the map.
+ * Typically opened in response to clicking on a marker.
+ * See <OpenLayers.Marker>. Popup's don't require their own
+ * layer and are added the the map using the <OpenLayers.Map.addPopup>
+ * method.
+ *
+ * Example:
+ * (code)
+ * popup = new OpenLayers.Popup("chicken",
+ * new OpenLayers.LonLat(5,40),
+ * new OpenLayers.Size(200,200),
+ * "example popup",
+ * true);
+ *
+ * map.addPopup(popup);
+ * (end)
+ */
+OpenLayers.Popup = OpenLayers.Class({
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} custom event manager
+ */
+ events: null,
+
+ /** Property: id
+ * {String} the unique identifier assigned to this popup.
+ */
+ id: "",
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>} the position of this popup on the map
+ */
+ lonlat: null,
+
+ /**
+ * Property: div
+ * {DOMElement} the div that contains this popup.
+ */
+ div: null,
+
+ /**
+ * Property: contentSize
+ * {<OpenLayers.Size>} the width and height of the content.
+ */
+ contentSize: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} the width and height of the popup.
+ */
+ size: null,
+
+ /**
+ * Property: contentHTML
+ * {String} An HTML string for this popup to display.
+ */
+ contentHTML: null,
+
+ /**
+ * Property: backgroundColor
+ * {String} the background color used by the popup.
+ */
+ backgroundColor: "",
+
+ /**
+ * Property: opacity
+ * {float} the opacity of this popup (between 0.0 and 1.0)
+ */
+ opacity: "",
+
+ /**
+ * Property: border
+ * {String} the border size of the popup. (eg 2px)
+ */
+ border: "",
+
+ /**
+ * Property: contentDiv
+ * {DOMElement} a reference to the element that holds the content of
+ * the div.
+ */
+ contentDiv: null,
+
+ /**
+ * Property: groupDiv
+ * {DOMElement} First and only child of 'div'. The group Div contains the
+ * 'contentDiv' and the 'closeDiv'.
+ */
+ groupDiv: null,
+
+ /**
+ * Property: closeDiv
+ * {DOMElement} the optional closer image
+ */
+ closeDiv: null,
+
+ /**
+ * APIProperty: autoSize
+ * {Boolean} Resize the popup to auto-fit the contents.
+ * Default is false.
+ */
+ autoSize: false,
+
+ /**
+ * APIProperty: minSize
+ * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
+ */
+ minSize: null,
+
+ /**
+ * APIProperty: maxSize
+ * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
+ */
+ maxSize: null,
+
+ /**
+ * Property: displayClass
+ * {String} The CSS class of the popup.
+ */
+ displayClass: "olPopup",
+
+ /**
+ * Property: contentDisplayClass
+ * {String} The CSS class of the popup content div.
+ */
+ contentDisplayClass: "olPopupContent",
+
+ /**
+ * Property: padding
+ * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal
+ * padding of the content div inside the popup. This was originally
+ * confused with the css padding as specified in style.css's
+ * 'olPopupContent' class. We would like to get rid of this altogether,
+ * except that it does come in handy for the framed and anchoredbubble
+ * popups, who need to maintain yet another barrier between their
+ * content and the outer border of the popup itself.
+ *
+ * Note that in order to not break API, we must continue to support
+ * this property being set as an integer. Really, though, we'd like to
+ * have this specified as a Bounds object so that user can specify
+ * distinct left, top, right, bottom paddings. With the 3.0 release
+ * we can make this only a bounds.
+ */
+ padding: 0,
+
+ /**
+ * Property: disableFirefoxOverflowHack
+ * {Boolean} The hack for overflow in Firefox causes all elements
+ * to be re-drawn, which causes Flash elements to be
+ * re-initialized, which is troublesome.
+ * With this property the hack can be disabled.
+ */
+ disableFirefoxOverflowHack: false,
+
+ /**
+ * Method: fixPadding
+ * To be removed in 3.0, this function merely helps us to deal with the
+ * case where the user may have set an integer value for padding,
+ * instead of an <OpenLayers.Bounds> object.
+ */
+ fixPadding: function() {
+ if (typeof this.padding == "number") {
+ this.padding = new OpenLayers.Bounds(
+ this.padding, this.padding, this.padding, this.padding
+ );
+ }
+ },
+
+ /**
+ * APIProperty: panMapIfOutOfView
+ * {Boolean} When drawn, pan map such that the entire popup is visible in
+ * the current viewport (if necessary).
+ * Default is false.
+ */
+ panMapIfOutOfView: false,
+
+ /**
+ * APIProperty: keepInMap
+ * {Boolean} If panMapIfOutOfView is false, and this property is true,
+ * contrain the popup such that it always fits in the available map
+ * space. By default, this is not set on the base class. If you are
+ * creating popups that are near map edges and not allowing pannning,
+ * and especially if you have a popup which has a
+ * fixedRelativePosition, setting this to false may be a smart thing to
+ * do. Subclasses may want to override this setting.
+ *
+ * Default is false.
+ */
+ keepInMap: false,
+
+ /**
+ * APIProperty: closeOnMove
+ * {Boolean} When map pans, close the popup.
+ * Default is false.
+ */
+ closeOnMove: false,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
+ */
+ map: null,
+
+ /**
+ * Constructor: OpenLayers.Popup
+ * Create a popup.
+ *
+ * Parameters:
+ * id - {String} a unqiue identifier for this popup. If null is passed
+ * an identifier will be automatically generated.
+ * lonlat - {<OpenLayers.LonLat>} The position on the map the popup will
+ * be shown.
+ * contentSize - {<OpenLayers.Size>} The size of the content.
+ * contentHTML - {String} An HTML string to display inside the
+ * popup.
+ * closeBox - {Boolean} Whether to display a close box inside
+ * the popup.
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
+ if (id == null) {
+ id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+
+ this.id = id;
+ this.lonlat = lonlat;
+
+ this.contentSize = (contentSize != null) ? contentSize
+ : new OpenLayers.Size(
+ OpenLayers.Popup.WIDTH,
+ OpenLayers.Popup.HEIGHT);
+ if (contentHTML != null) {
+ this.contentHTML = contentHTML;
+ }
+ this.backgroundColor = OpenLayers.Popup.COLOR;
+ this.opacity = OpenLayers.Popup.OPACITY;
+ this.border = OpenLayers.Popup.BORDER;
+
+ this.div = OpenLayers.Util.createDiv(this.id, null, null,
+ null, null, null, "hidden");
+ this.div.className = this.displayClass;
+
+ var groupDivId = this.id + "_GroupDiv";
+ this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null,
+ null, "relative", null,
+ "hidden");
+
+ var id = this.div.id + "_contentDiv";
+ this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(),
+ null, "relative");
+ this.contentDiv.className = this.contentDisplayClass;
+ this.groupDiv.appendChild(this.contentDiv);
+ this.div.appendChild(this.groupDiv);
+
+ if (closeBox) {
+ this.addCloseBox(closeBoxCallback);
+ }
+
+ this.registerEvents();
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ this.id = null;
+ this.lonlat = null;
+ this.size = null;
+ this.contentHTML = null;
+
+ this.backgroundColor = null;
+ this.opacity = null;
+ this.border = null;
+
+ if (this.closeOnMove && this.map) {
+ this.map.events.unregister("movestart", this, this.hide);
+ }
+
+ this.events.destroy();
+ this.events = null;
+
+ if (this.closeDiv) {
+ OpenLayers.Event.stopObservingElement(this.closeDiv);
+ this.groupDiv.removeChild(this.closeDiv);
+ }
+ this.closeDiv = null;
+
+ this.div.removeChild(this.groupDiv);
+ this.groupDiv = null;
+
+ if (this.map != null) {
+ this.map.removePopup(this);
+ }
+ this.map = null;
+ this.div = null;
+
+ this.autoSize = null;
+ this.minSize = null;
+ this.maxSize = null;
+ this.padding = null;
+ this.panMapIfOutOfView = null;
+ },
+
+ /**
+ * Method: draw
+ * Constructs the elements that make up the popup.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} the position the popup in pixels.
+ *
+ * Returns:
+ * {DOMElement} Reference to a div that contains the drawn popup
+ */
+ draw: function(px) {
+ if (px == null) {
+ if ((this.lonlat != null) && (this.map != null)) {
+ px = this.map.getLayerPxFromLonLat(this.lonlat);
+ }
+ }
+
+ // this assumes that this.map already exists, which is okay because
+ // this.draw is only called once the popup has been added to the map.
+ if (this.closeOnMove) {
+ this.map.events.register("movestart", this, this.hide);
+ }
+
+ //listen to movestart, moveend to disable overflow (FF bug)
+ if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == 'firefox') {
+ this.map.events.register("movestart", this, function() {
+ var style = document.defaultView.getComputedStyle(
+ this.contentDiv, null
+ );
+ var currentOverflow = style.getPropertyValue("overflow");
+ if (currentOverflow != "hidden") {
+ this.contentDiv._oldOverflow = currentOverflow;
+ this.contentDiv.style.overflow = "hidden";
+ }
+ });
+ this.map.events.register("moveend", this, function() {
+ var oldOverflow = this.contentDiv._oldOverflow;
+ if (oldOverflow) {
+ this.contentDiv.style.overflow = oldOverflow;
+ this.contentDiv._oldOverflow = null;
+ }
+ });
+ }
+
+ this.moveTo(px);
+ if (!this.autoSize && !this.size) {
+ this.setSize(this.contentSize);
+ }
+ this.setBackgroundColor();
+ this.setOpacity();
+ this.setBorder();
+ this.setContentHTML();
+
+ if (this.panMapIfOutOfView) {
+ this.panIntoView();
+ }
+
+ return this.div;
+ },
+
+ /**
+ * Method: updatePosition
+ * if the popup has a lonlat and its map members set,
+ * then have it move itself to its proper position
+ */
+ updatePosition: function() {
+ if ((this.lonlat) && (this.map)) {
+ var px = this.map.getLayerPxFromLonLat(this.lonlat);
+ if (px) {
+ this.moveTo(px);
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} the top and left position of the popup div.
+ */
+ moveTo: function(px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * Method: visible
+ *
+ * Returns:
+ * {Boolean} Boolean indicating whether or not the popup is visible
+ */
+ visible: function() {
+ return OpenLayers.Element.visible(this.div);
+ },
+
+ /**
+ * Method: toggle
+ * Toggles visibility of the popup.
+ */
+ toggle: function() {
+ if (this.visible()) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ },
+
+ /**
+ * Method: show
+ * Makes the popup visible.
+ */
+ show: function() {
+ this.div.style.display = '';
+
+ if (this.panMapIfOutOfView) {
+ this.panIntoView();
+ }
+ },
+
+ /**
+ * Method: hide
+ * Makes the popup invisible.
+ */
+ hide: function() {
+ this.div.style.display = 'none';
+ },
+
+ /**
+ * Method: setSize
+ * Used to adjust the size of the popup.
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ this.size = contentSize.clone();
+
+ // if our contentDiv has a css 'padding' set on it by a stylesheet, we
+ // must add that to the desired "size".
+ var contentDivPadding = this.getContentDivPadding();
+ var wPadding = contentDivPadding.left + contentDivPadding.right;
+ var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+ // take into account the popup's 'padding' property
+ this.fixPadding();
+ wPadding += this.padding.left + this.padding.right;
+ hPadding += this.padding.top + this.padding.bottom;
+
+ // make extra space for the close div
+ if (this.closeDiv) {
+ var closeDivWidth = parseInt(this.closeDiv.style.width);
+ wPadding += closeDivWidth + contentDivPadding.right;
+ }
+
+ //increase size of the main popup div to take into account the
+ // users's desired padding and close div.
+ this.size.w += wPadding;
+ this.size.h += hPadding;
+
+ //now if our browser is IE, we need to actually make the contents
+ // div itself bigger to take its own padding into effect. this makes
+ // me want to shoot someone, but so it goes.
+ if (OpenLayers.BROWSER_NAME == "msie") {
+ this.contentSize.w +=
+ contentDivPadding.left + contentDivPadding.right;
+ this.contentSize.h +=
+ contentDivPadding.bottom + contentDivPadding.top;
+ }
+
+ if (this.div != null) {
+ this.div.style.width = this.size.w + "px";
+ this.div.style.height = this.size.h + "px";
+ }
+ if (this.contentDiv != null){
+ this.contentDiv.style.width = contentSize.w + "px";
+ this.contentDiv.style.height = contentSize.h + "px";
+ }
+ },
+
+ /**
+ * APIMethod: updateSize
+ * Auto size the popup so that it precisely fits its contents (as
+ * determined by this.contentDiv.innerHTML). Popup size will, of
+ * course, be limited by the available space on the current map
+ */
+ updateSize: function() {
+
+ // determine actual render dimensions of the contents by putting its
+ // contents into a fake contentDiv (for the CSS) and then measuring it
+ var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" +
+ this.contentDiv.innerHTML +
+ "</div>";
+
+ var containerElement = (this.map) ? this.map.div : document.body;
+ var realSize = OpenLayers.Util.getRenderedDimensions(
+ preparedHTML, null, {
+ displayClass: this.displayClass,
+ containerElement: containerElement
+ }
+ );
+
+ // is the "real" size of the div is safe to display in our map?
+ var safeSize = this.getSafeContentSize(realSize);
+
+ var newSize = null;
+ if (safeSize.equals(realSize)) {
+ //real size of content is small enough to fit on the map,
+ // so we use real size.
+ newSize = realSize;
+
+ } else {
+
+ // make a new 'size' object with the clipped dimensions
+ // set or null if not clipped.
+ var fixedSize = {
+ w: (safeSize.w < realSize.w) ? safeSize.w : null,
+ h: (safeSize.h < realSize.h) ? safeSize.h : null
+ };
+
+ if (fixedSize.w && fixedSize.h) {
+ //content is too big in both directions, so we will use
+ // max popup size (safeSize), knowing well that it will
+ // overflow both ways.
+ newSize = safeSize;
+ } else {
+ //content is clipped in only one direction, so we need to
+ // run getRenderedDimensions() again with a fixed dimension
+ var clippedSize = OpenLayers.Util.getRenderedDimensions(
+ preparedHTML, fixedSize, {
+ displayClass: this.contentDisplayClass,
+ containerElement: containerElement
+ }
+ );
+
+ //if the clipped size is still the same as the safeSize,
+ // that means that our content must be fixed in the
+ // offending direction. If overflow is 'auto', this means
+ // we are going to have a scrollbar for sure, so we must
+ // adjust for that.
+ //
+ var currentOverflow = OpenLayers.Element.getStyle(
+ this.contentDiv, "overflow"
+ );
+ if ( (currentOverflow != "hidden") &&
+ (clippedSize.equals(safeSize)) ) {
+ var scrollBar = OpenLayers.Util.getScrollbarWidth();
+ if (fixedSize.w) {
+ clippedSize.h += scrollBar;
+ } else {
+ clippedSize.w += scrollBar;
+ }
+ }
+
+ newSize = this.getSafeContentSize(clippedSize);
+ }
+ }
+ this.setSize(newSize);
+ },
+
+ /**
+ * Method: setBackgroundColor
+ * Sets the background color of the popup.
+ *
+ * Parameters:
+ * color - {String} the background color. eg "#FFBBBB"
+ */
+ setBackgroundColor:function(color) {
+ if (color != undefined) {
+ this.backgroundColor = color;
+ }
+
+ if (this.div != null) {
+ this.div.style.backgroundColor = this.backgroundColor;
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Sets the opacity of the popup.
+ *
+ * Parameters:
+ * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
+ */
+ setOpacity:function(opacity) {
+ if (opacity != undefined) {
+ this.opacity = opacity;
+ }
+
+ if (this.div != null) {
+ // for Mozilla and Safari
+ this.div.style.opacity = this.opacity;
+
+ // for IE
+ this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
+ }
+ },
+
+ /**
+ * Method: setBorder
+ * Sets the border style of the popup.
+ *
+ * Parameters:
+ * border - {String} The border style value. eg 2px
+ */
+ setBorder:function(border) {
+ if (border != undefined) {
+ this.border = border;
+ }
+
+ if (this.div != null) {
+ this.div.style.border = this.border;
+ }
+ },
+
+ /**
+ * Method: setContentHTML
+ * Allows the user to set the HTML content of the popup.
+ *
+ * Parameters:
+ * contentHTML - {String} HTML for the div.
+ */
+ setContentHTML:function(contentHTML) {
+
+ if (contentHTML != null) {
+ this.contentHTML = contentHTML;
+ }
+
+ if ((this.contentDiv != null) &&
+ (this.contentHTML != null) &&
+ (this.contentHTML != this.contentDiv.innerHTML)) {
+
+ this.contentDiv.innerHTML = this.contentHTML;
+
+ if (this.autoSize) {
+
+ //if popup has images, listen for when they finish
+ // loading and resize accordingly
+ this.registerImageListeners();
+
+ //auto size the popup to its current contents
+ this.updateSize();
+ }
+ }
+
+ },
+
+ /**
+ * Method: registerImageListeners
+ * Called when an image contained by the popup loaded. this function
+ * updates the popup size, then unregisters the image load listener.
+ */
+ registerImageListeners: function() {
+
+ // As the images load, this function will call updateSize() to
+ // resize the popup to fit the content div (which presumably is now
+ // bigger than when the image was not loaded).
+ //
+ // If the 'panMapIfOutOfView' property is set, we will pan the newly
+ // resized popup back into view.
+ //
+ // Note that this function, when called, will have 'popup' and
+ // 'img' properties in the context.
+ //
+ var onImgLoad = function() {
+ if (this.popup.id === null) { // this.popup has been destroyed!
+ return;
+ }
+ this.popup.updateSize();
+
+ if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
+ this.popup.panIntoView();
+ }
+
+ OpenLayers.Event.stopObserving(
+ this.img, "load", this.img._onImgLoad
+ );
+
+ };
+
+ //cycle through the images and if their size is 0x0, that means that
+ // they haven't been loaded yet, so we attach the listener, which
+ // will fire when the images finish loading and will resize the
+ // popup accordingly to its new size.
+ var images = this.contentDiv.getElementsByTagName("img");
+ for (var i = 0, len = images.length; i < len; i++) {
+ var img = images[i];
+ if (img.width == 0 || img.height == 0) {
+
+ var context = {
+ 'popup': this,
+ 'img': img
+ };
+
+ //expando this function to the image itself before registering
+ // it. This way we can easily and properly unregister it.
+ img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
+
+ OpenLayers.Event.observe(img, 'load', img._onImgLoad);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getSafeContentSize
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} Desired size to make the popup.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A size to make the popup which is neither smaller
+ * than the specified minimum size, nor bigger than the maximum
+ * size (which is calculated relative to the size of the viewport).
+ */
+ getSafeContentSize: function(size) {
+
+ var safeContentSize = size.clone();
+
+ // if our contentDiv has a css 'padding' set on it by a stylesheet, we
+ // must add that to the desired "size".
+ var contentDivPadding = this.getContentDivPadding();
+ var wPadding = contentDivPadding.left + contentDivPadding.right;
+ var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+ // take into account the popup's 'padding' property
+ this.fixPadding();
+ wPadding += this.padding.left + this.padding.right;
+ hPadding += this.padding.top + this.padding.bottom;
+
+ if (this.closeDiv) {
+ var closeDivWidth = parseInt(this.closeDiv.style.width);
+ wPadding += closeDivWidth + contentDivPadding.right;
+ }
+
+ // prevent the popup from being smaller than a specified minimal size
+ if (this.minSize) {
+ safeContentSize.w = Math.max(safeContentSize.w,
+ (this.minSize.w - wPadding));
+ safeContentSize.h = Math.max(safeContentSize.h,
+ (this.minSize.h - hPadding));
+ }
+
+ // prevent the popup from being bigger than a specified maximum size
+ if (this.maxSize) {
+ safeContentSize.w = Math.min(safeContentSize.w,
+ (this.maxSize.w - wPadding));
+ safeContentSize.h = Math.min(safeContentSize.h,
+ (this.maxSize.h - hPadding));
+ }
+
+ //make sure the desired size to set doesn't result in a popup that
+ // is bigger than the map's viewport.
+ //
+ if (this.map && this.map.size) {
+
+ var extraX = 0, extraY = 0;
+ if (this.keepInMap && !this.panMapIfOutOfView) {
+ var px = this.map.getPixelFromLonLat(this.lonlat);
+ switch (this.relativePosition) {
+ case "tr":
+ extraX = px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ case "tl":
+ extraX = this.map.size.w - px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ case "bl":
+ extraX = this.map.size.w - px.x;
+ extraY = px.y;
+ break;
+ case "br":
+ extraX = px.x;
+ extraY = px.y;
+ break;
+ default:
+ extraX = px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ }
+ }
+
+ var maxY = this.map.size.h -
+ this.map.paddingForPopups.top -
+ this.map.paddingForPopups.bottom -
+ hPadding - extraY;
+
+ var maxX = this.map.size.w -
+ this.map.paddingForPopups.left -
+ this.map.paddingForPopups.right -
+ wPadding - extraX;
+
+ safeContentSize.w = Math.min(safeContentSize.w, maxX);
+ safeContentSize.h = Math.min(safeContentSize.h, maxY);
+ }
+
+ return safeContentSize;
+ },
+
+ /**
+ * Method: getContentDivPadding
+ * Glorious, oh glorious hack in order to determine the css 'padding' of
+ * the contentDiv. IE/Opera return null here unless we actually add the
+ * popup's main 'div' element (which contains contentDiv) to the DOM.
+ * So we make it invisible and then add it to the document temporarily.
+ *
+ * Once we've taken the padding readings we need, we then remove it
+ * from the DOM (it will actually get added to the DOM in
+ * Map.js's addPopup)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getContentDivPadding: function() {
+
+ //use cached value if we have it
+ var contentDivPadding = this._contentDivPadding;
+ if (!contentDivPadding) {
+
+ if (this.div.parentNode == null) {
+ //make the div invisible and add it to the page
+ this.div.style.display = "none";
+ document.body.appendChild(this.div);
+ }
+
+ //read the padding settings from css, put them in an OL.Bounds
+ contentDivPadding = new OpenLayers.Bounds(
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
+ );
+
+ //cache the value
+ this._contentDivPadding = contentDivPadding;
+
+ if (this.div.parentNode == document.body) {
+ //remove the div from the page and make it visible again
+ document.body.removeChild(this.div);
+ this.div.style.display = "";
+ }
+ }
+ return contentDivPadding;
+ },
+
+ /**
+ * Method: addCloseBox
+ *
+ * Parameters:
+ * callback - {Function} The callback to be called when the close button
+ * is clicked.
+ */
+ addCloseBox: function(callback) {
+
+ this.closeDiv = OpenLayers.Util.createDiv(
+ this.id + "_close", null, {w: 17, h: 17}
+ );
+ this.closeDiv.className = "olPopupCloseBox";
+
+ // use the content div's css padding to determine if we should
+ // padd the close div
+ var contentDivPadding = this.getContentDivPadding();
+
+ this.closeDiv.style.right = contentDivPadding.right + "px";
+ this.closeDiv.style.top = contentDivPadding.top + "px";
+ this.groupDiv.appendChild(this.closeDiv);
+
+ var closePopup = callback || function(e) {
+ this.hide();
+ OpenLayers.Event.stop(e);
+ };
+ OpenLayers.Event.observe(this.closeDiv, "touchend",
+ OpenLayers.Function.bindAsEventListener(closePopup, this));
+ OpenLayers.Event.observe(this.closeDiv, "click",
+ OpenLayers.Function.bindAsEventListener(closePopup, this));
+ },
+
+ /**
+ * Method: panIntoView
+ * Pans the map such that the popup is totaly viewable (if necessary)
+ */
+ panIntoView: function() {
+
+ var mapSize = this.map.getSize();
+
+ //start with the top left corner of the popup, in px,
+ // relative to the viewport
+ var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
+ parseInt(this.div.style.left),
+ parseInt(this.div.style.top)
+ ));
+ var newTL = origTL.clone();
+
+ //new left (compare to margins, using this.size to calculate right)
+ if (origTL.x < this.map.paddingForPopups.left) {
+ newTL.x = this.map.paddingForPopups.left;
+ } else
+ if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
+ newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
+ }
+
+ //new top (compare to margins, using this.size to calculate bottom)
+ if (origTL.y < this.map.paddingForPopups.top) {
+ newTL.y = this.map.paddingForPopups.top;
+ } else
+ if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
+ newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
+ }
+
+ var dx = origTL.x - newTL.x;
+ var dy = origTL.y - newTL.y;
+
+ this.map.pan(dx, dy);
+ },
+
+ /**
+ * Method: registerEvents
+ * Registers events on the popup.
+ *
+ * Do this in a separate function so that subclasses can
+ * choose to override it if they wish to deal differently
+ * with mouse events
+ *
+ * Note in the following handler functions that some special
+ * care is needed to deal correctly with mousing and popups.
+ *
+ * Because the user might select the zoom-rectangle option and
+ * then drag it over a popup, we need a safe way to allow the
+ * mousemove and mouseup events to pass through the popup when
+ * they are initiated from outside. The same procedure is needed for
+ * touchmove and touchend events.
+ *
+ * Otherwise, we want to essentially kill the event propagation
+ * for all other events, though we have to do so carefully,
+ * without disabling basic html functionality, like clicking on
+ * hyperlinks or drag-selecting text.
+ */
+ registerEvents:function() {
+ this.events = new OpenLayers.Events(this, this.div, null, true);
+
+ function onTouchstart(evt) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ this.events.on({
+ "mousedown": this.onmousedown,
+ "mousemove": this.onmousemove,
+ "mouseup": this.onmouseup,
+ "click": this.onclick,
+ "mouseout": this.onmouseout,
+ "dblclick": this.ondblclick,
+ "touchstart": onTouchstart,
+ scope: this
+ });
+
+ },
+
+ /**
+ * Method: onmousedown
+ * When mouse goes down within the popup, make a note of
+ * it locally, and then do not propagate the mousedown
+ * (but do so safely so that user can select text inside)
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmousedown: function (evt) {
+ this.mousedown = true;
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ /**
+ * Method: onmousemove
+ * If the drag was started within the popup, then
+ * do not propagate the mousemove (but do so safely
+ * so that user can select text inside)
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmousemove: function (evt) {
+ if (this.mousedown) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ },
+
+ /**
+ * Method: onmouseup
+ * When mouse comes up within the popup, after going down
+ * in it, reset the flag, and then (once again) do not
+ * propagate the event, but do so safely so that user can
+ * select text inside
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmouseup: function (evt) {
+ if (this.mousedown) {
+ this.mousedown = false;
+ OpenLayers.Event.stop(evt, true);
+ }
+ },
+
+ /**
+ * Method: onclick
+ * Ignore clicks, but allowing default browser handling
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onclick: function (evt) {
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ /**
+ * Method: onmouseout
+ * When mouse goes out of the popup set the flag to false so that
+ * if they let go and then drag back in, we won't be confused.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmouseout: function (evt) {
+ this.mousedown = false;
+ },
+
+ /**
+ * Method: ondblclick
+ * Ignore double-clicks, but allowing default browser handling
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ ondblclick: function (evt) {
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ CLASS_NAME: "OpenLayers.Popup"
+});
+
+OpenLayers.Popup.WIDTH = 200;
+OpenLayers.Popup.HEIGHT = 200;
+OpenLayers.Popup.COLOR = "white";
+OpenLayers.Popup.OPACITY = 1;
+OpenLayers.Popup.BORDER = "0px";
+/* ======================================================================
+ OpenLayers/Popup/Anchored.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Anchored
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup>
+ */
+OpenLayers.Popup.Anchored =
+ OpenLayers.Class(OpenLayers.Popup, {
+
+ /**
+ * Property: relativePosition
+ * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
+ */
+ relativePosition: null,
+
+ /**
+ * APIProperty: keepInMap
+ * {Boolean} If panMapIfOutOfView is false, and this property is true,
+ * contrain the popup such that it always fits in the available map
+ * space. By default, this is set. If you are creating popups that are
+ * near map edges and not allowing pannning, and especially if you have
+ * a popup which has a fixedRelativePosition, setting this to false may
+ * be a smart thing to do.
+ *
+ * For anchored popups, default is true, since subclasses will
+ * usually want this functionality.
+ */
+ keepInMap: true,
+
+ /**
+ * Property: anchor
+ * {Object} Object to which we'll anchor the popup. Must expose a
+ * 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
+ */
+ anchor: null,
+
+ /**
+ * Constructor: OpenLayers.Popup.Anchored
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object which must expose a 'size' <OpenLayers.Size>
+ * and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+ var newArguments = [
+ id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback
+ ];
+ OpenLayers.Popup.prototype.initialize.apply(this, newArguments);
+
+ this.anchor = (anchor != null) ? anchor
+ : { size: new OpenLayers.Size(0,0),
+ offset: new OpenLayers.Pixel(0,0)};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.anchor = null;
+ this.relativePosition = null;
+
+ OpenLayers.Popup.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: show
+ * Overridden from Popup since user might hide popup and then show() it
+ * in a new location (meaning we might want to update the relative
+ * position on the show)
+ */
+ show: function() {
+ this.updatePosition();
+ OpenLayers.Popup.prototype.show.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ * Since the popup is moving to a new px, it might need also to be moved
+ * relative to where the marker is. We first calculate the new
+ * relativePosition, and then we calculate the new px where we will
+ * put the popup, based on the new relative position.
+ *
+ * If the relativePosition has changed, we must also call
+ * updateRelativePosition() to make any visual changes to the popup
+ * which are associated with putting it in a new relativePosition.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function(px) {
+ var oldRelativePosition = this.relativePosition;
+ this.relativePosition = this.calculateRelativePosition(px);
+
+ OpenLayers.Popup.prototype.moveTo.call(this, this.calculateNewPx(px));
+
+ //if this move has caused the popup to change its relative position,
+ // we need to make the appropriate cosmetic changes.
+ if (this.relativePosition != oldRelativePosition) {
+ this.updateRelativePosition();
+ }
+ },
+
+ /**
+ * APIMethod: setSize
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.prototype.setSize.apply(this, arguments);
+
+ if ((this.lonlat) && (this.map)) {
+ var px = this.map.getLayerPxFromLonLat(this.lonlat);
+ this.moveTo(px);
+ }
+ },
+
+ /**
+ * Method: calculateRelativePosition
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {String} The relative position ("br" "tr" "tl" "bl") at which the popup
+ * should be placed.
+ */
+ calculateRelativePosition:function(px) {
+ var lonlat = this.map.getLonLatFromLayerPx(px);
+
+ var extent = this.map.getExtent();
+ var quadrant = extent.determineQuadrant(lonlat);
+
+ return OpenLayers.Bounds.oppositeQuadrant(quadrant);
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * The popup has been moved to a new relative location, so we may want to
+ * make some cosmetic adjustments to it.
+ *
+ * Note that in the classic Anchored popup, there is nothing to do
+ * here, since the popup looks exactly the same in all four positions.
+ * Subclasses such as Framed, however, will want to do something
+ * special here.
+ */
+ updateRelativePosition: function() {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * Method: calculateNewPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+ * relative to the passed-in px.
+ */
+ calculateNewPx:function(px) {
+ var newPx = px.offset(this.anchor.offset);
+
+ //use contentSize if size is not already set
+ var size = this.size || this.contentSize;
+
+ var top = (this.relativePosition.charAt(0) == 't');
+ newPx.y += (top) ? -size.h : this.anchor.size.h;
+
+ var left = (this.relativePosition.charAt(1) == 'l');
+ newPx.x += (left) ? -size.w : this.anchor.size.w;
+
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.Anchored"
+});
+/* ======================================================================
+ OpenLayers/Popup/Framed.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Framed
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.Framed =
+ OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+ /**
+ * Property: imageSrc
+ * {String} location of the image to be used as the popup frame
+ */
+ imageSrc: null,
+
+ /**
+ * Property: imageSize
+ * {<OpenLayers.Size>} Size (measured in pixels) of the image located
+ * by the 'imageSrc' property.
+ */
+ imageSize: null,
+
+ /**
+ * APIProperty: isAlphaImage
+ * {Boolean} The image has some alpha and thus needs to use the alpha
+ * image hack. Note that setting this to true will have no noticeable
+ * effect in FF or IE7 browsers, but will all but crush the ie6
+ * browser.
+ * Default is false.
+ */
+ isAlphaImage: false,
+
+ /**
+ * Property: positionBlocks
+ * {Object} Hash of different position blocks (Object/Hashs). Each block
+ * will be keyed by a two-character 'relativePosition'
+ * code string (ie "tl", "tr", "bl", "br"). Block properties are
+ * 'offset', 'padding' (self-explanatory), and finally the 'blocks'
+ * parameter, which is an array of the block objects.
+ *
+ * Each block object must have 'size', 'anchor', and 'position'
+ * properties.
+ *
+ * Note that positionBlocks should never be modified at runtime.
+ */
+ positionBlocks: null,
+
+ /**
+ * Property: blocks
+ * {Array[Object]} Array of objects, each of which is one "block" of the
+ * popup. Each block has a 'div' and an 'image' property, both of
+ * which are DOMElements, and the latter of which is appended to the
+ * former. These are reused as the popup goes changing positions for
+ * great economy and elegance.
+ */
+ blocks: null,
+
+ /**
+ * APIProperty: fixedRelativePosition
+ * {Boolean} We want the framed popup to work dynamically placed relative
+ * to its anchor but also in just one fixed position. A well designed
+ * framed popup will have the pixels and logic to display itself in
+ * any of the four relative positions, but (understandably), this will
+ * not be the case for all of them. By setting this property to 'true',
+ * framed popup will not recalculate for the best placement each time
+ * it's open, but will always open the same way.
+ * Note that if this is set to true, it is generally advisable to also
+ * set the 'panIntoView' property to true so that the popup can be
+ * scrolled into view (since it will often be offscreen on open)
+ * Default is false.
+ */
+ fixedRelativePosition: false,
+
+ /**
+ * Constructor: OpenLayers.Popup.Framed
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+
+ if (this.fixedRelativePosition) {
+ //based on our decided relativePostion, set the current padding
+ // this keeps us from getting into trouble
+ this.updateRelativePosition();
+
+ //make calculateRelativePosition always return the specified
+ // fixed position.
+ this.calculateRelativePosition = function(px) {
+ return this.relativePosition;
+ };
+ }
+
+ this.contentDiv.style.position = "absolute";
+ this.contentDiv.style.zIndex = 1;
+
+ if (closeBox) {
+ this.closeDiv.style.zIndex = 1;
+ }
+
+ this.groupDiv.style.position = "absolute";
+ this.groupDiv.style.top = "0px";
+ this.groupDiv.style.left = "0px";
+ this.groupDiv.style.height = "100%";
+ this.groupDiv.style.width = "100%";
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.imageSrc = null;
+ this.imageSize = null;
+ this.isAlphaImage = null;
+
+ this.fixedRelativePosition = false;
+ this.positionBlocks = null;
+
+ //remove our blocks
+ for(var i = 0; i < this.blocks.length; i++) {
+ var block = this.blocks[i];
+
+ if (block.image) {
+ block.div.removeChild(block.image);
+ }
+ block.image = null;
+
+ if (block.div) {
+ this.groupDiv.removeChild(block.div);
+ }
+ block.div = null;
+ }
+ this.blocks = null;
+
+ OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setBackgroundColor
+ */
+ setBackgroundColor:function(color) {
+ //does nothing since the framed popup's entire scheme is based on a
+ // an image -- changing the background color makes no sense.
+ },
+
+ /**
+ * APIMethod: setBorder
+ */
+ setBorder:function() {
+ //does nothing since the framed popup's entire scheme is based on a
+ // an image -- changing the popup's border makes no sense.
+ },
+
+ /**
+ * Method: setOpacity
+ * Sets the opacity of the popup.
+ *
+ * Parameters:
+ * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
+ */
+ setOpacity:function(opacity) {
+ //does nothing since we suppose that we'll never apply an opacity
+ // to a framed popup
+ },
+
+ /**
+ * APIMethod: setSize
+ * Overridden here, because we need to update the blocks whenever the size
+ * of the popup has changed.
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+ this.updateBlocks();
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * When the relative position changes, we need to set the new padding
+ * BBOX on the popup, reposition the close div, and update the blocks.
+ */
+ updateRelativePosition: function() {
+
+ //update the padding
+ this.padding = this.positionBlocks[this.relativePosition].padding;
+
+ //update the position of our close box to new padding
+ if (this.closeDiv) {
+ // use the content div's css padding to determine if we should
+ // padd the close div
+ var contentDivPadding = this.getContentDivPadding();
+
+ this.closeDiv.style.right = contentDivPadding.right +
+ this.padding.right + "px";
+ this.closeDiv.style.top = contentDivPadding.top +
+ this.padding.top + "px";
+ }
+
+ this.updateBlocks();
+ },
+
+ /**
+ * Method: calculateNewPx
+ * Besides the standard offset as determined by the Anchored class, our
+ * Framed popups have a special 'offset' property for each of their
+ * positions, which is used to offset the popup relative to its anchor.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+ * relative to the passed-in px.
+ */
+ calculateNewPx:function(px) {
+ var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(
+ this, arguments
+ );
+
+ newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset);
+
+ return newPx;
+ },
+
+ /**
+ * Method: createBlocks
+ */
+ createBlocks: function() {
+ this.blocks = [];
+
+ //since all positions contain the same number of blocks, we can
+ // just pick the first position and use its blocks array to create
+ // our blocks array
+ var firstPosition = null;
+ for(var key in this.positionBlocks) {
+ firstPosition = key;
+ break;
+ }
+
+ var position = this.positionBlocks[firstPosition];
+ for (var i = 0; i < position.blocks.length; i++) {
+
+ var block = {};
+ this.blocks.push(block);
+
+ var divId = this.id + '_FrameDecorationDiv_' + i;
+ block.div = OpenLayers.Util.createDiv(divId,
+ null, null, null, "absolute", null, "hidden", null
+ );
+
+ var imgId = this.id + '_FrameDecorationImg_' + i;
+ var imageCreator =
+ (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv
+ : OpenLayers.Util.createImage;
+
+ block.image = imageCreator(imgId,
+ null, this.imageSize, this.imageSrc,
+ "absolute", null, null, null
+ );
+
+ block.div.appendChild(block.image);
+ this.groupDiv.appendChild(block.div);
+ }
+ },
+
+ /**
+ * Method: updateBlocks
+ * Internal method, called on initialize and when the popup's relative
+ * position has changed. This function takes care of re-positioning
+ * the popup's blocks in their appropropriate places.
+ */
+ updateBlocks: function() {
+ if (!this.blocks) {
+ this.createBlocks();
+ }
+
+ if (this.size && this.relativePosition) {
+ var position = this.positionBlocks[this.relativePosition];
+ for (var i = 0; i < position.blocks.length; i++) {
+
+ var positionBlock = position.blocks[i];
+ var block = this.blocks[i];
+
+ // adjust sizes
+ var l = positionBlock.anchor.left;
+ var b = positionBlock.anchor.bottom;
+ var r = positionBlock.anchor.right;
+ var t = positionBlock.anchor.top;
+
+ //note that we use the isNaN() test here because if the
+ // size object is initialized with a "auto" parameter, the
+ // size constructor calls parseFloat() on the string,
+ // which will turn it into NaN
+ //
+ var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l)
+ : positionBlock.size.w;
+
+ var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t)
+ : positionBlock.size.h;
+
+ block.div.style.width = (w < 0 ? 0 : w) + 'px';
+ block.div.style.height = (h < 0 ? 0 : h) + 'px';
+
+ block.div.style.left = (l != null) ? l + 'px' : '';
+ block.div.style.bottom = (b != null) ? b + 'px' : '';
+ block.div.style.right = (r != null) ? r + 'px' : '';
+ block.div.style.top = (t != null) ? t + 'px' : '';
+
+ block.image.style.left = positionBlock.position.x + 'px';
+ block.image.style.top = positionBlock.position.y + 'px';
+ }
+
+ this.contentDiv.style.left = this.padding.left + "px";
+ this.contentDiv.style.top = this.padding.top + "px";
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.Framed"
+});
+/* ======================================================================
+ OpenLayers/Format.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats. Subclasses
+ * of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+
+ /**
+ * Property: options
+ * {Object} A reference to options passed to the constructor.
+ */
+ options: null,
+
+ /**
+ * APIProperty: externalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The externalProjection is the projection used by
+ * the content which is passed into read or which comes out of write.
+ * In order to reproject, a projection transformation function for the
+ * specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ externalProjection: null,
+
+ /**
+ * APIProperty: internalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The internalProjection is the projection used by
+ * the geometries which are returned by read or which are passed into
+ * write. In order to reproject, a projection transformation function
+ * for the specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ internalProjection: null,
+
+ /**
+ * APIProperty: data
+ * {Object} When <keepData> is true, this is the parsed string sent to
+ * <read>.
+ */
+ data: null,
+
+ /**
+ * APIProperty: keepData
+ * {Object} Maintain a reference (<data>) to the most recently read data.
+ * Default is false.
+ */
+ keepData: false,
+
+ /**
+ * Constructor: OpenLayers.Format
+ * Instances of this class are not useful. See one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * format
+ *
+ * Valid options:
+ * keepData - {Boolean} If true, upon <read>, the data property will be
+ * set to the parsed object (e.g. the json or xml object).
+ *
+ * Returns:
+ * An instance of OpenLayers.Format
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * Method: read
+ * Read data from a string, and return an object whose type depends on the
+ * subclass.
+ *
+ * Parameters:
+ * data - {string} Data to read/parse.
+ *
+ * Returns:
+ * Depends on the subclass
+ */
+ read: function(data) {
+ throw new Error('Read not implemented.');
+ },
+
+ /**
+ * Method: write
+ * Accept an object, and return a string.
+ *
+ * Parameters:
+ * object - {Object} Object to be serialized
+ *
+ * Returns:
+ * {String} A string representation of the object.
+ */
+ write: function(object) {
+ throw new Error('Write not implemented.');
+ },
+
+ CLASS_NAME: "OpenLayers.Format"
+});
+/* ======================================================================
+ OpenLayers/Format/JSON.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * Note:
+ * This work draws heavily from the public domain JSON serializer/deserializer
+ * at http://www.json.org/json.js. Rewritten so that it doesn't modify
+ * basic data prototypes.
+ */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.JSON
+ * A parser to read/write JSON safely. Create a new instance with the
+ * <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: indent
+ * {String} For "pretty" printing, the indent string will be used once for
+ * each indentation level.
+ */
+ indent: " ",
+
+ /**
+ * APIProperty: space
+ * {String} For "pretty" printing, the space string will be used after
+ * the ":" separating a name/value pair.
+ */
+ space: " ",
+
+ /**
+ * APIProperty: newline
+ * {String} For "pretty" printing, the newline string will be used at the
+ * end of each name/value pair or array item.
+ */
+ newline: "\n",
+
+ /**
+ * Property: level
+ * {Integer} For "pretty" printing, this is incremented/decremented during
+ * serialization.
+ */
+ level: 0,
+
+ /**
+ * Property: pretty
+ * {Boolean} Serialize with extra whitespace for structure. This is set
+ * by the <write> method.
+ */
+ pretty: false,
+
+ /**
+ * Property: nativeJSON
+ * {Boolean} Does the browser support native json?
+ */
+ nativeJSON: (function() {
+ return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function");
+ })(),
+
+ /**
+ * Constructor: OpenLayers.Format.JSON
+ * Create a new parser for JSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a json string.
+ *
+ * Parameters:
+ * json - {String} A JSON string
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} An object, array, string, or number .
+ */
+ read: function(json, filter) {
+ var object;
+ if (this.nativeJSON) {
+ object = JSON.parse(json, filter);
+ } else try {
+ /**
+ * Parsing happens in three stages. In the first stage, we run the
+ * text against a regular expression which looks for non-JSON
+ * characters. We are especially concerned with '()' and 'new'
+ * because they can cause invocation, and '=' because it can
+ * cause mutation. But just to be safe, we will reject all
+ * unexpected characters.
+ */
+ if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+ /**
+ * In the second stage we use the eval function to compile the
+ * text into a JavaScript structure. The '{' operator is
+ * subject to a syntactic ambiguity in JavaScript - it can
+ * begin a block or an object literal. We wrap the text in
+ * parens to eliminate the ambiguity.
+ */
+ object = eval('(' + json + ')');
+
+ /**
+ * In the optional third stage, we recursively walk the new
+ * structure, passing each name/value pair to a filter
+ * function for possible transformation.
+ */
+ if(typeof filter === 'function') {
+ function walk(k, v) {
+ if(v && typeof v === 'object') {
+ for(var i in v) {
+ if(v.hasOwnProperty(i)) {
+ v[i] = walk(i, v[i]);
+ }
+ }
+ }
+ return filter(k, v);
+ }
+ object = walk('', object);
+ }
+ }
+ } catch(e) {
+ // Fall through if the regexp test fails.
+ }
+
+ if(this.keepData) {
+ this.data = object;
+ }
+
+ return object;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize an object into a JSON string.
+ *
+ * Parameters:
+ * value - {String} The object, array, string, number, boolean or date
+ * to be serialized.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The JSON string representation of the input value.
+ */
+ write: function(value, pretty) {
+ this.pretty = !!pretty;
+ var json = null;
+ var type = typeof value;
+ if(this.serialize[type]) {
+ try {
+ json = (!this.pretty && this.nativeJSON) ?
+ JSON.stringify(value) :
+ this.serialize[type].apply(this, [value]);
+ } catch(err) {
+ OpenLayers.Console.error("Trouble serializing: " + err);
+ }
+ }
+ return json;
+ },
+
+ /**
+ * Method: writeIndent
+ * Output an indentation string depending on the indentation level.
+ *
+ * Returns:
+ * {String} An appropriate indentation string.
+ */
+ writeIndent: function() {
+ var pieces = [];
+ if(this.pretty) {
+ for(var i=0; i<this.level; ++i) {
+ pieces.push(this.indent);
+ }
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: writeNewline
+ * Output a string representing a newline if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A string representing a new line.
+ */
+ writeNewline: function() {
+ return (this.pretty) ? this.newline : '';
+ },
+
+ /**
+ * Method: writeSpace
+ * Output a string representing a space if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A space.
+ */
+ writeSpace: function() {
+ return (this.pretty) ? this.space : '';
+ },
+
+ /**
+ * Property: serialize
+ * Object with properties corresponding to the serializable data types.
+ * Property values are functions that do the actual serializing.
+ */
+ serialize: {
+ /**
+ * Method: serialize.object
+ * Transform an object into a JSON string.
+ *
+ * Parameters:
+ * object - {Object} The object to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the object.
+ */
+ 'object': function(object) {
+ // three special objects that we want to treat differently
+ if(object == null) {
+ return "null";
+ }
+ if(object.constructor == Date) {
+ return this.serialize.date.apply(this, [object]);
+ }
+ if(object.constructor == Array) {
+ return this.serialize.array.apply(this, [object]);
+ }
+ var pieces = ['{'];
+ this.level += 1;
+ var key, keyJSON, valueJSON;
+
+ var addComma = false;
+ for(key in object) {
+ if(object.hasOwnProperty(key)) {
+ // recursive calls need to allow for sub-classing
+ keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [key, this.pretty]);
+ valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [object[key], this.pretty]);
+ if(keyJSON != null && valueJSON != null) {
+ if(addComma) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(),
+ keyJSON, ':', this.writeSpace(), valueJSON);
+ addComma = true;
+ }
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), '}');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.array
+ * Transform an array into a JSON string.
+ *
+ * Parameters:
+ * array - {Array} The array to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the array.
+ */
+ 'array': function(array) {
+ var json;
+ var pieces = ['['];
+ this.level += 1;
+
+ for(var i=0, len=array.length; i<len; ++i) {
+ // recursive calls need to allow for sub-classing
+ json = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [array[i], this.pretty]);
+ if(json != null) {
+ if(i > 0) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(), json);
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), ']');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.string
+ * Transform a string into a JSON string.
+ *
+ * Parameters:
+ * string - {String} The string to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the string.
+ */
+ 'string': function(string) {
+ // If the string contains no control characters, no quote characters, and no
+ // backslash characters, then we can simply slap some quotes around it.
+ // Otherwise we must also replace the offending characters with safe
+ // sequences.
+ var m = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+ if(/["\\\x00-\x1f]/.test(string)) {
+ return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c) {
+ return c;
+ }
+ c = b.charCodeAt();
+ return '\\u00' +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + string + '"';
+ },
+
+ /**
+ * Method: serialize.number
+ * Transform a number into a JSON string.
+ *
+ * Parameters:
+ * number - {Number} The number to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the number.
+ */
+ 'number': function(number) {
+ return isFinite(number) ? String(number) : "null";
+ },
+
+ /**
+ * Method: serialize.boolean
+ * Transform a boolean into a JSON string.
+ *
+ * Parameters:
+ * bool - {Boolean} The boolean to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the boolean.
+ */
+ 'boolean': function(bool) {
+ return String(bool);
+ },
+
+ /**
+ * Method: serialize.object
+ * Transform a date into a JSON string.
+ *
+ * Parameters:
+ * date - {Date} The date to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the date.
+ */
+ 'date': function(date) {
+ function format(number) {
+ // Format integers to have at least two digits.
+ return (number < 10) ? '0' + number : number;
+ }
+ return '"' + date.getFullYear() + '-' +
+ format(date.getMonth() + 1) + '-' +
+ format(date.getDate()) + 'T' +
+ format(date.getHours()) + ':' +
+ format(date.getMinutes()) + ':' +
+ format(date.getSeconds()) + '"';
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.JSON"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GeoJSON.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ * <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.JSON>
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+ /**
+ * APIProperty: ignoreExtraDims
+ * {Boolean} Ignore dimensions higher than 2 when reading geometry
+ * coordinates.
+ */
+ ignoreExtraDims: false,
+
+ /**
+ * Constructor: OpenLayers.Format.GeoJSON
+ * Create a new parser for GeoJSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a GeoJSON string.
+ *
+ * Parameters:
+ * json - {String} A GeoJSON string
+ * type - {String} Optional string that determines the structure of
+ * the output. Supported values are "Geometry", "Feature", and
+ * "FeatureCollection". If absent or null, a default of
+ * "FeatureCollection" is assumed.
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} The return depends on the value of the type argument. If type
+ * is "FeatureCollection" (the default), the return will be an array
+ * of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+ * must represent a single geometry, and the return will be an
+ * <OpenLayers.Geometry>. If type is "Feature", the input json must
+ * represent a single feature, and the return will be an
+ * <OpenLayers.Feature.Vector>.
+ */
+ read: function(json, type, filter) {
+ type = (type) ? type : "FeatureCollection";
+ var results = null;
+ var obj = null;
+ if (typeof json == "string") {
+ obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+ [json, filter]);
+ } else {
+ obj = json;
+ }
+ if(!obj) {
+ OpenLayers.Console.error("Bad JSON: " + json);
+ } else if(typeof(obj.type) != "string") {
+ OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+ } else if(this.isValidType(obj, type)) {
+ switch(type) {
+ case "Geometry":
+ try {
+ results = this.parseGeometry(obj);
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "Feature":
+ try {
+ results = this.parseFeature(obj);
+ results.type = "Feature";
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ // for type FeatureCollection, we allow input to be any type
+ results = [];
+ switch(obj.type) {
+ case "Feature":
+ try {
+ results.push(this.parseFeature(obj));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ for(var i=0, len=obj.features.length; i<len; ++i) {
+ try {
+ results.push(this.parseFeature(obj.features[i]));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ default:
+ try {
+ var geom = this.parseGeometry(obj);
+ results.push(new OpenLayers.Feature.Vector(geom));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: isValidType
+ * Check if a GeoJSON object is a valid representative of the given type.
+ *
+ * Returns:
+ * {Boolean} The object is valid GeoJSON object of the given type.
+ */
+ isValidType: function(obj, type) {
+ var valid = false;
+ switch(type) {
+ case "Geometry":
+ if(OpenLayers.Util.indexOf(
+ ["Point", "MultiPoint", "LineString", "MultiLineString",
+ "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+ obj.type) == -1) {
+ // unsupported geometry type
+ OpenLayers.Console.error("Unsupported geometry type: " +
+ obj.type);
+ } else {
+ valid = true;
+ }
+ break;
+ case "FeatureCollection":
+ // allow for any type to be converted to a feature collection
+ valid = true;
+ break;
+ default:
+ // for Feature types must match
+ if(obj.type == type) {
+ valid = true;
+ } else {
+ OpenLayers.Console.error("Cannot convert types from " +
+ obj.type + " to " + type);
+ }
+ }
+ return valid;
+ },
+
+ /**
+ * Method: parseFeature
+ * Convert a feature object from GeoJSON into an
+ * <OpenLayers.Feature.Vector>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature.
+ */
+ parseFeature: function(obj) {
+ var feature, geometry, attributes, bbox;
+ attributes = (obj.properties) ? obj.properties : {};
+ bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
+ try {
+ geometry = this.parseGeometry(obj.geometry);
+ } catch(err) {
+ // deal with bad geometries
+ throw err;
+ }
+ feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ if(bbox) {
+ feature.bounds = OpenLayers.Bounds.fromArray(bbox);
+ }
+ if(obj.id) {
+ feature.fid = obj.id;
+ }
+ return feature;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ parseGeometry: function(obj) {
+ if (obj == null) {
+ return null;
+ }
+ var geometry, collection = false;
+ if(obj.type == "GeometryCollection") {
+ if(!(OpenLayers.Util.isArray(obj.geometries))) {
+ throw "GeometryCollection must have geometries array: " + obj;
+ }
+ var numGeom = obj.geometries.length;
+ var components = new Array(numGeom);
+ for(var i=0; i<numGeom; ++i) {
+ components[i] = this.parseGeometry.apply(
+ this, [obj.geometries[i]]
+ );
+ }
+ geometry = new OpenLayers.Geometry.Collection(components);
+ collection = true;
+ } else {
+ if(!(OpenLayers.Util.isArray(obj.coordinates))) {
+ throw "Geometry must have coordinates array: " + obj;
+ }
+ if(!this.parseCoords[obj.type.toLowerCase()]) {
+ throw "Unsupported geometry type: " + obj.type;
+ }
+ try {
+ geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+ this, [obj.coordinates]
+ );
+ } catch(err) {
+ // deal with bad coordinates
+ throw err;
+ }
+ }
+ // We don't reproject collections because the children are reprojected
+ // for us when they are created.
+ if (this.internalProjection && this.externalProjection && !collection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ return geometry;
+ },
+
+ /**
+ * Property: parseCoords
+ * Object with properties corresponding to the GeoJSON geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parseCoords: {
+ /**
+ * Method: parseCoords.point
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "point": function(array) {
+ if (this.ignoreExtraDims == false &&
+ array.length != 2) {
+ throw "Only 2D points are supported: " + array;
+ }
+ return new OpenLayers.Geometry.Point(array[0], array[1]);
+ },
+
+ /**
+ * Method: parseCoords.multipoint
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipoint": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPoint(points);
+ },
+
+ /**
+ * Method: parseCoords.linestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "linestring": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.LineString(points);
+ },
+
+ /**
+ * Method: parseCoords.multilinestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multilinestring": function(array) {
+ var lines = [];
+ var l = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ lines.push(l);
+ }
+ return new OpenLayers.Geometry.MultiLineString(lines);
+ },
+
+ /**
+ * Method: parseCoords.polygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "polygon": function(array) {
+ var rings = [];
+ var r, l;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ r = new OpenLayers.Geometry.LinearRing(l.components);
+ rings.push(r);
+ }
+ return new OpenLayers.Geometry.Polygon(rings);
+ },
+
+ /**
+ * Method: parseCoords.multipolygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipolygon": function(array) {
+ var polys = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["polygon"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ polys.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPolygon(polys);
+ },
+
+ /**
+ * Method: parseCoords.box
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "box": function(array) {
+ if(array.length != 2) {
+ throw "GeoJSON box coordinates must have 2 elements";
+ }
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+ ])
+ ]);
+ }
+
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature, geometry, array of features into a GeoJSON string.
+ *
+ * Parameters:
+ * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+ * or an array of features.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The GeoJSON string representation of the input geometry,
+ * features, or array of features.
+ */
+ write: function(obj, pretty) {
+ var geojson = {
+ "type": null
+ };
+ if(OpenLayers.Util.isArray(obj)) {
+ geojson.type = "FeatureCollection";
+ var numFeatures = obj.length;
+ geojson.features = new Array(numFeatures);
+ for(var i=0; i<numFeatures; ++i) {
+ var element = obj[i];
+ if(!element instanceof OpenLayers.Feature.Vector) {
+ var msg = "FeatureCollection only supports collections " +
+ "of features: " + element;
+ throw msg;
+ }
+ geojson.features[i] = this.extract.feature.apply(
+ this, [element]
+ );
+ }
+ } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+ geojson = this.extract.geometry.apply(this, [obj]);
+ } else if (obj instanceof OpenLayers.Feature.Vector) {
+ geojson = this.extract.feature.apply(this, [obj]);
+ if(obj.layer && obj.layer.projection) {
+ geojson.crs = this.createCRSObject(obj);
+ }
+ }
+ return OpenLayers.Format.JSON.prototype.write.apply(this,
+ [geojson, pretty]);
+ },
+
+ /**
+ * Method: createCRSObject
+ * Create the CRS object for an object.
+ *
+ * Parameters:
+ * object - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object which can be assigned to the crs property
+ * of a GeoJSON object.
+ */
+ createCRSObject: function(object) {
+ var proj = object.layer.projection.toString();
+ var crs = {};
+ if (proj.match(/epsg:/i)) {
+ var code = parseInt(proj.substring(proj.indexOf(":") + 1));
+ if (code == 4326) {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
+ }
+ };
+ } else {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "EPSG:" + code
+ }
+ };
+ }
+ }
+ return crs;
+ },
+
+ /**
+ * Property: extract
+ * Object with properties corresponding to the GeoJSON types.
+ * Property values are functions that do the actual value extraction.
+ */
+ extract: {
+ /**
+ * Method: extract.feature
+ * Return a partial GeoJSON object representing a single feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object representing the point.
+ */
+ 'feature': function(feature) {
+ var geom = this.extract.geometry.apply(this, [feature.geometry]);
+ var json = {
+ "type": "Feature",
+ "properties": feature.attributes,
+ "geometry": geom
+ };
+ if (feature.fid != null) {
+ json.id = feature.fid;
+ }
+ return json;
+ },
+
+ /**
+ * Method: extract.geometry
+ * Return a GeoJSON object representing a single geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Object} An object representing the geometry.
+ */
+ 'geometry': function(geometry) {
+ if (geometry == null) {
+ return null;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var geometryType = geometry.CLASS_NAME.split('.')[2];
+ var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+ var json;
+ if(geometryType == "Collection") {
+ json = {
+ "type": "GeometryCollection",
+ "geometries": data
+ };
+ } else {
+ json = {
+ "type": geometryType,
+ "coordinates": data
+ };
+ }
+
+ return json;
+ },
+
+ /**
+ * Method: extract.point
+ * Return an array of coordinates from a point.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Array} An array of coordinates representing the point.
+ */
+ 'point': function(point) {
+ return [point.x, point.y];
+ },
+
+ /**
+ * Method: extract.multipoint
+ * Return an array of point coordinates from a multipoint.
+ *
+ * Parameters:
+ * multipoint - {<OpenLayers.Geometry.MultiPoint>}
+ *
+ * Returns:
+ * {Array} An array of point coordinate arrays representing
+ * the multipoint.
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0, len=multipoint.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.linestring
+ * Return an array of coordinate arrays from a linestring.
+ *
+ * Parameters:
+ * linestring - {<OpenLayers.Geometry.LineString>}
+ *
+ * Returns:
+ * {Array} An array of coordinate arrays representing
+ * the linestring.
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multilinestring
+ * Return an array of linestring arrays from a linestring.
+ *
+ * Parameters:
+ * multilinestring - {<OpenLayers.Geometry.MultiLineString>}
+ *
+ * Returns:
+ * {Array} An array of linestring arrays representing
+ * the multilinestring.
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.polygon
+ * Return an array of linear ring arrays from a polygon.
+ *
+ * Parameters:
+ * polygon - {<OpenLayers.Geometry.Polygon>}
+ *
+ * Returns:
+ * {Array} An array of linear ring arrays representing the polygon.
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0, len=polygon.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multipolygon
+ * Return an array of polygon arrays from a multipolygon.
+ *
+ * Parameters:
+ * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+ *
+ * Returns:
+ * {Array} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.collection
+ * Return an array of geometries from a geometry collection.
+ *
+ * Parameters:
+ * collection - {<OpenLayers.Geometry.Collection>}
+ *
+ * Returns:
+ * {Array} An array of geometry objects representing the geometry
+ * collection.
+ */
+ 'collection': function(collection) {
+ var len = collection.components.length;
+ var array = new Array(len);
+ for(var i=0; i<len; ++i) {
+ array[i] = this.extract.geometry.apply(
+ this, [collection.components[i]]
+ );
+ }
+ return array;
+ }
+
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoJSON"
+
+});
+/* ======================================================================
+ OpenLayers/Layer/Google/v3.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Google.js
+ */
+
+/**
+ * Constant: OpenLayers.Layer.Google.v3
+ *
+ * Mixin providing functionality specific to the Google Maps API v3.
+ *
+ * To use this layer, you must include the GMaps v3 API in your html.
+ *
+ * Note that this layer configures the google.maps.map object with the
+ * "disableDefaultUI" option set to true. Using UI controls that the Google
+ * Maps API provides is not supported by the OpenLayers API.
+ */
+OpenLayers.Layer.Google.v3 = {
+
+ /**
+ * Constant: DEFAULTS
+ * {Object} It is not recommended to change the properties set here. Note
+ * that Google.v3 layers only work when sphericalMercator is set to true.
+ *
+ * (code)
+ * {
+ * sphericalMercator: true,
+ * projection: "EPSG:900913"
+ * }
+ * (end)
+ */
+ DEFAULTS: {
+ sphericalMercator: true,
+ projection: "EPSG:900913"
+ },
+
+ /**
+ * APIProperty: animationEnabled
+ * {Boolean} If set to true, the transition between zoom levels will be
+ * animated (if supported by the GMaps API for the device used). Set to
+ * false to match the zooming experience of other layer types. Default
+ * is true. Note that the GMaps API does not give us control over zoom
+ * animation, so if set to false, when zooming, this will make the
+ * layer temporarily invisible, wait until GMaps reports the map being
+ * idle, and make it visible again. The result will be a blank layer
+ * for a few moments while zooming.
+ */
+ animationEnabled: true,
+
+ /**
+ * Method: loadMapObject
+ * Load the GMap and register appropriate event listeners.
+ */
+ loadMapObject: function() {
+ if (!this.type) {
+ this.type = google.maps.MapTypeId.ROADMAP;
+ }
+ var mapObject;
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // there are already Google layers added to this map
+ mapObject = cache.mapObject;
+ // increment the layer count
+ ++cache.count;
+ } else {
+ // this is the first Google layer for this map
+ // create GMap
+ var center = this.map.getCenter();
+ var container = document.createElement('div');
+ container.className = "olForeignContainer";
+ container.style.width = '100%';
+ container.style.height = '100%';
+ mapObject = new google.maps.Map(container, {
+ center: center ?
+ new google.maps.LatLng(center.lat, center.lon) :
+ new google.maps.LatLng(0, 0),
+ zoom: this.map.getZoom() || 0,
+ mapTypeId: this.type,
+ disableDefaultUI: true,
+ keyboardShortcuts: false,
+ draggable: false,
+ disableDoubleClickZoom: true,
+ scrollwheel: false,
+ streetViewControl: false
+ });
+ var googleControl = document.createElement('div');
+ googleControl.style.width = '100%';
+ googleControl.style.height = '100%';
+ mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(googleControl);
+
+ // cache elements for use by any other google layers added to
+ // this same map
+ cache = {
+ googleControl: googleControl,
+ mapObject: mapObject,
+ count: 1
+ };
+ OpenLayers.Layer.Google.cache[this.map.id] = cache;
+ }
+ this.mapObject = mapObject;
+ this.setGMapVisibility(this.visibility);
+ },
+
+ /**
+ * APIMethod: onMapResize
+ */
+ onMapResize: function() {
+ if (this.visibility) {
+ google.maps.event.trigger(this.mapObject, "resize");
+ }
+ },
+
+ /**
+ * Method: setGMapVisibility
+ * Display the GMap container and associated elements.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the GMap elements.
+ */
+ setGMapVisibility: function(visible) {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ var map = this.map;
+ if (cache) {
+ var type = this.type;
+ var layers = map.layers;
+ var layer;
+ for (var i=layers.length-1; i>=0; --i) {
+ layer = layers[i];
+ if (layer instanceof OpenLayers.Layer.Google &&
+ layer.visibility === true && layer.inRange === true) {
+ type = layer.type;
+ visible = true;
+ break;
+ }
+ }
+ var container = this.mapObject.getDiv();
+ if (visible === true) {
+ if (container.parentNode !== map.div) {
+ if (!cache.rendered) {
+ var me = this;
+ google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() {
+ cache.rendered = true;
+ me.setGMapVisibility(me.getVisibility());
+ me.moveTo(me.map.getCenter());
+ });
+ } else {
+ map.div.appendChild(container);
+ cache.googleControl.appendChild(map.viewPortDiv);
+ google.maps.event.trigger(this.mapObject, 'resize');
+ }
+ }
+ this.mapObject.setMapTypeId(type);
+ } else if (cache.googleControl.hasChildNodes()) {
+ map.div.appendChild(map.viewPortDiv);
+ map.div.removeChild(container);
+ }
+ }
+ },
+
+ /**
+ * Method: getMapContainer
+ *
+ * Returns:
+ * {DOMElement} the GMap container's div
+ */
+ getMapContainer: function() {
+ return this.mapObject.getDiv();
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getMapObjectBoundsFromOLBounds
+ *
+ * Parameters:
+ * olBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} A MapObject Bounds, translated from olBounds
+ * Returns null if null value is passed in
+ */
+ getMapObjectBoundsFromOLBounds: function(olBounds) {
+ var moBounds = null;
+ if (olBounds != null) {
+ var sw = this.sphericalMercator ?
+ this.inverseMercator(olBounds.bottom, olBounds.left) :
+ new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+ var ne = this.sphericalMercator ?
+ this.inverseMercator(olBounds.top, olBounds.right) :
+ new OpenLayers.LonLat(olBounds.top, olBounds.right);
+ moBounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(sw.lat, sw.lon),
+ new google.maps.LatLng(ne.lat, ne.lon)
+ );
+ }
+ return moBounds;
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ var size = this.map.getSize();
+ var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center);
+ var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center);
+ var res = this.map.getResolution();
+
+ var delta_x = moPixel.x - (size.w / 2);
+ var delta_y = moPixel.y - (size.h / 2);
+
+ var lonlat = new OpenLayers.LonLat(
+ lon + delta_x * res,
+ lat - delta_y * res
+ );
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+ var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+ var res = this.map.getResolution();
+ var extent = this.map.getExtent();
+ return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)),
+ (1/res * (extent.top - lat)));
+ },
+
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ if (this.animationEnabled === false && zoom != this.mapObject.zoom) {
+ var mapContainer = this.getMapContainer();
+ google.maps.event.addListenerOnce(
+ this.mapObject,
+ "idle",
+ function() {
+ mapContainer.style.visibility = "";
+ }
+ );
+ mapContainer.style.visibility = "hidden";
+ }
+ this.mapObject.setOptions({
+ center: center,
+ zoom: zoom
+ });
+ },
+
+
+ // Bounds
+
+ /**
+ * APIMethod: getMapObjectZoomFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object} MapObject Bounds format
+ *
+ * Returns:
+ * {Object} MapObject Zoom for specified MapObject Bounds
+ */
+ getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+ return this.mapObject.getBoundsZoomLevel(moBounds);
+ },
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var gLatLng;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ gLatLng = new google.maps.LatLng(lonlat.lat, lonlat.lon);
+ } else {
+ gLatLng = new google.maps.LatLng(lat, lon);
+ }
+ return gLatLng;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new google.maps.Point(x, y);
+ }
+
+};
+/* ======================================================================
+ OpenLayers/Request.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * TODO: deprecate me
+ * Use OpenLayers.Request.proxy instead.
+ */
+OpenLayers.ProxyHost = "";
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ * with XMLHttpRequests. These methods work with a cross-browser
+ * W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
+ */
+if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request/XMLHttpRequest.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+}
+OpenLayers.Util.extend(OpenLayers.Request, {
+
+ /**
+ * Constant: DEFAULT_CONFIG
+ * {Object} Default configuration for all requests.
+ */
+ DEFAULT_CONFIG: {
+ method: "GET",
+ url: window.location.href,
+ async: true,
+ user: undefined,
+ password: undefined,
+ params: null,
+ proxy: OpenLayers.ProxyHost,
+ headers: {},
+ data: null,
+ callback: function() {},
+ success: null,
+ failure: null,
+ scope: null
+ },
+
+ /**
+ * Constant: URL_SPLIT_REGEX
+ */
+ URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the {<OpenLayers.Request>} object.
+ *
+ * All event listeners will receive an event object with three properties:
+ * request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
+ * config - {Object} The config object sent to the specific request method.
+ * requestUrl - {String} The request url.
+ *
+ * Supported event types:
+ * complete - Triggered when we have a response from the request, if a
+ * listener returns false, no further response processing will take
+ * place.
+ * success - Triggered when the HTTP response has a success code (200-299).
+ * failure - Triggered when the HTTP response does not have a success code.
+ */
+ events: new OpenLayers.Events(this),
+
+ /**
+ * Method: makeSameOrigin
+ * Using the specified proxy, returns a same origin url of the provided url.
+ *
+ * Parameters:
+ * url - {String} An arbitrary url
+ * proxy {String|Function} The proxy to use to make the provided url a
+ * same origin url.
+ *
+ * Returns
+ * {String} the same origin url. If no proxy is provided, the returned url
+ * will be the same as the provided url.
+ */
+ makeSameOrigin: function(url, proxy) {
+ var sameOrigin = url.indexOf("http") !== 0;
+ var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
+ if (urlParts) {
+ var location = window.location;
+ sameOrigin =
+ urlParts[1] == location.protocol &&
+ urlParts[3] == location.hostname;
+ var uPort = urlParts[4], lPort = location.port;
+ if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
+ sameOrigin = sameOrigin && uPort == lPort;
+ }
+ }
+ if (!sameOrigin) {
+ if (proxy) {
+ if (typeof proxy == "function") {
+ url = proxy(url);
+ } else {
+ url = proxy + encodeURIComponent(url);
+ }
+ }
+ }
+ return url;
+ },
+
+ /**
+ * APIMethod: issue
+ * Create a new XMLHttpRequest object, open it, set any headers, bind
+ * a callback to done state, and send any data. It is recommended that
+ * you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
+ * This method is only documented to provide detail on the configuration
+ * options available to all request methods.
+ *
+ * Parameters:
+ * config - {Object} Object containing properties for configuring the
+ * request. Allowed configuration properties are described below.
+ * This object is modified and should not be reused.
+ *
+ * Allowed config properties:
+ * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
+ * OPTIONS. Default is GET.
+ * url - {String} URL for the request.
+ * async - {Boolean} Open an asynchronous request. Default is true.
+ * user - {String} User for relevant authentication scheme. Set
+ * to null to clear current user.
+ * password - {String} Password for relevant authentication scheme.
+ * Set to null to clear current password.
+ * proxy - {String} Optional proxy. Defaults to
+ * <OpenLayers.ProxyHost>.
+ * params - {Object} Any key:value pairs to be appended to the
+ * url as a query string. Assumes url doesn't already include a query
+ * string or hash. Typically, this is only appropriate for <GET>
+ * requests where the query string will be appended to the url.
+ * Parameter values that are arrays will be
+ * concatenated with a comma (note that this goes against form-encoding)
+ * as is done with <OpenLayers.Util.getParameterString>.
+ * headers - {Object} Object with header:value pairs to be set on
+ * the request.
+ * data - {String | Document} Optional data to send with the request.
+ * Typically, this is only used with <POST> and <PUT> requests.
+ * Make sure to provide the appropriate "Content-Type" header for your
+ * data. For <POST> and <PUT> requests, the content type defaults to
+ * "application-xml". If your data is a different content type, or
+ * if you are using a different HTTP method, set the "Content-Type"
+ * header to match your data type.
+ * callback - {Function} Function to call when request is done.
+ * To determine if the request failed, check request.status (200
+ * indicates success).
+ * success - {Function} Optional function to call if request status is in
+ * the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * failure - {Function} Optional function to call if request status is not
+ * in the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * scope - {Object} If callback is a public method on some object,
+ * set the scope to that object.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object. To abort the request before a response
+ * is received, call abort() on the request object.
+ */
+ issue: function(config) {
+ // apply default config - proxy host may have changed
+ var defaultConfig = OpenLayers.Util.extend(
+ this.DEFAULT_CONFIG,
+ {proxy: OpenLayers.ProxyHost}
+ );
+ config = config || {};
+ config.headers = config.headers || {};
+ config = OpenLayers.Util.applyDefaults(config, defaultConfig);
+ config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
+ // Always set the "X-Requested-With" header to signal that this request
+ // was issued through the XHR-object. Since header keys are case
+ // insensitive and we want to allow overriding of the "X-Requested-With"
+ // header through the user we cannot use applyDefaults, but have to
+ // check manually whether we were called with a "X-Requested-With"
+ // header.
+ var customRequestedWithHeader = false,
+ headerKey;
+ for(headerKey in config.headers) {
+ if (config.headers.hasOwnProperty( headerKey )) {
+ if (headerKey.toLowerCase() === 'x-requested-with') {
+ customRequestedWithHeader = true;
+ }
+ }
+ }
+ if (customRequestedWithHeader === false) {
+ // we did not have a custom "X-Requested-With" header
+ config.headers['X-Requested-With'] = 'XMLHttpRequest';
+ }
+
+ // create request, open, and set headers
+ var request = new OpenLayers.Request.XMLHttpRequest();
+ var url = OpenLayers.Util.urlAppend(config.url,
+ OpenLayers.Util.getParameterString(config.params || {}));
+ url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
+ request.open(
+ config.method, url, config.async, config.user, config.password
+ );
+ for(var header in config.headers) {
+ request.setRequestHeader(header, config.headers[header]);
+ }
+
+ var events = this.events;
+
+ // we want to execute runCallbacks with "this" as the
+ // execution scope
+ var self = this;
+
+ request.onreadystatechange = function() {
+ if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
+ var proceed = events.triggerEvent(
+ "complete",
+ {request: request, config: config, requestUrl: url}
+ );
+ if(proceed !== false) {
+ self.runCallbacks(
+ {request: request, config: config, requestUrl: url}
+ );
+ }
+ }
+ };
+
+ // send request (optionally with data) and return
+ // call in a timeout for asynchronous requests so the return is
+ // available before readyState == 4 for cached docs
+ if(config.async === false) {
+ request.send(config.data);
+ } else {
+ window.setTimeout(function(){
+ if (request.readyState !== 0) { // W3C: 0-UNSENT
+ request.send(config.data);
+ }
+ }, 0);
+ }
+ return request;
+ },
+
+ /**
+ * Method: runCallbacks
+ * Calls the complete, success and failure callbacks. Application
+ * can listen to the "complete" event, have the listener
+ * display a confirm window and always return false, and
+ * execute OpenLayers.Request.runCallbacks if the user
+ * hits "yes" in the confirm window.
+ *
+ * Parameters:
+ * options - {Object} Hash containing request, config and requestUrl keys
+ */
+ runCallbacks: function(options) {
+ var request = options.request;
+ var config = options.config;
+
+ // bind callbacks to readyState 4 (done)
+ var complete = (config.scope) ?
+ OpenLayers.Function.bind(config.callback, config.scope) :
+ config.callback;
+
+ // optional success callback
+ var success;
+ if(config.success) {
+ success = (config.scope) ?
+ OpenLayers.Function.bind(config.success, config.scope) :
+ config.success;
+ }
+
+ // optional failure callback
+ var failure;
+ if(config.failure) {
+ failure = (config.scope) ?
+ OpenLayers.Function.bind(config.failure, config.scope) :
+ config.failure;
+ }
+
+ if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
+ request.responseText) {
+ request.status = 200;
+ }
+ complete(request);
+
+ if (!request.status || (request.status >= 200 && request.status < 300)) {
+ this.events.triggerEvent("success", options);
+ if(success) {
+ success(request);
+ }
+ }
+ if(request.status && (request.status < 200 || request.status >= 300)) {
+ this.events.triggerEvent("failure", options);
+ if(failure) {
+ failure(request);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: GET
+ * Send an HTTP GET request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to GET.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ GET: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "GET"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: POST
+ * Send a POST request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to POST and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ POST: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "POST"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: PUT
+ * Send an HTTP PUT request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to PUT and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ PUT: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "PUT"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: DELETE
+ * Send an HTTP DELETE request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to DELETE.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ DELETE: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "DELETE"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: HEAD
+ * Send an HTTP HEAD request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to HEAD.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ HEAD: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "HEAD"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: OPTIONS
+ * Send an HTTP OPTIONS request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to OPTIONS.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ OPTIONS: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+ return OpenLayers.Request.issue(config);
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Request/XMLHttpRequest.js
+ ====================================================================== */
+
+// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+ // Save reference to earlier defined object implementation (if any)
+ var oXMLHttpRequest = window.XMLHttpRequest;
+
+ // Define on browser type
+ var bGecko = !!window.controllers,
+ bIE = window.document.all && !window.opera,
+ bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
+
+ // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
+ function fXMLHttpRequest() {
+ this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
+ this._listeners = [];
+ };
+
+ // Constructor
+ function cXMLHttpRequest() {
+ return new fXMLHttpRequest;
+ };
+ cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
+
+ // BUGFIX: Firefox with Firebug installed would break pages if not executed
+ if (bGecko && oXMLHttpRequest.wrapped)
+ cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
+
+ // Constants
+ cXMLHttpRequest.UNSENT = 0;
+ cXMLHttpRequest.OPENED = 1;
+ cXMLHttpRequest.HEADERS_RECEIVED = 2;
+ cXMLHttpRequest.LOADING = 3;
+ cXMLHttpRequest.DONE = 4;
+
+ // Public Properties
+ cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
+ cXMLHttpRequest.prototype.responseText = '';
+ cXMLHttpRequest.prototype.responseXML = null;
+ cXMLHttpRequest.prototype.status = 0;
+ cXMLHttpRequest.prototype.statusText = '';
+
+ // Priority proposal
+ cXMLHttpRequest.prototype.priority = "NORMAL";
+
+ // Instance-level Events Handlers
+ cXMLHttpRequest.prototype.onreadystatechange = null;
+
+ // Class-level Events Handlers
+ cXMLHttpRequest.onreadystatechange = null;
+ cXMLHttpRequest.onopen = null;
+ cXMLHttpRequest.onsend = null;
+ cXMLHttpRequest.onabort = null;
+
+ // Public Methods
+ cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+ // Delete headers, required when object is reused
+ delete this._headers;
+
+ // When bAsync parameter value is omitted, use true as default
+ if (arguments.length < 3)
+ bAsync = true;
+
+ // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+ this._async = bAsync;
+
+ // Set the onreadystatechange handler
+ var oRequest = this,
+ nState = this.readyState,
+ fOnUnload;
+
+ // BUGFIX: IE - memory leak on page unload (inter-page leak)
+ if (bIE && bAsync) {
+ fOnUnload = function() {
+ if (nState != cXMLHttpRequest.DONE) {
+ fCleanTransport(oRequest);
+ // Safe to abort here since onreadystatechange handler removed
+ oRequest.abort();
+ }
+ };
+ window.attachEvent("onunload", fOnUnload);
+ }
+
+ // Add method sniffer
+ if (cXMLHttpRequest.onopen)
+ cXMLHttpRequest.onopen.apply(this, arguments);
+
+ if (arguments.length > 4)
+ this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ if (arguments.length > 3)
+ this._object.open(sMethod, sUrl, bAsync, sUser);
+ else
+ this._object.open(sMethod, sUrl, bAsync);
+
+ this.readyState = cXMLHttpRequest.OPENED;
+ fReadyStateChange(this);
+
+ this._object.onreadystatechange = function() {
+ if (bGecko && !bAsync)
+ return;
+
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ // BUGFIX: Firefox fires unnecessary DONE when aborting
+ if (oRequest._aborted) {
+ // Reset readyState to UNSENT
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return now
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Free up queue
+ delete oRequest._data;
+/* if (bAsync)
+ fQueue_remove(oRequest);*/
+ //
+ fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+ // BUGFIX: IE - cache issue
+ if (!oRequest._object.getResponseHeader("Date")) {
+ // Save object to cache
+ oRequest._cached = oRequest._object;
+
+ // Instantiate a new transport object
+ cXMLHttpRequest.call(oRequest);
+
+ // Re-send request
+ if (sUser) {
+ if (sPassword)
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser);
+ }
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync);
+ oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+ // Copy headers set
+ if (oRequest._headers)
+ for (var sHeader in oRequest._headers)
+ if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions
+ oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+ oRequest._object.onreadystatechange = function() {
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ if (oRequest._aborted) {
+ //
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Clean Object
+ fCleanTransport(oRequest);
+
+ // get cached request
+ if (oRequest.status == 304)
+ oRequest._object = oRequest._cached;
+
+ //
+ delete oRequest._cached;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ //
+ fReadyStateChange(oRequest);
+
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+ };
+ oRequest._object.send(null);
+
+ // Return now - wait until re-sent request is finished
+ return;
+ };
+*/
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+
+ // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+ if (nState != oRequest.readyState)
+ fReadyStateChange(oRequest);
+
+ nState = oRequest.readyState;
+ }
+ };
+ function fXMLHttpRequest_send(oRequest) {
+ oRequest._object.send(oRequest._data);
+
+ // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+ if (bGecko && !oRequest._async) {
+ oRequest.readyState = cXMLHttpRequest.OPENED;
+
+ // Synchronize state
+ fSynchronizeValues(oRequest);
+
+ // Simulate missing states
+ while (oRequest.readyState < cXMLHttpRequest.DONE) {
+ oRequest.readyState++;
+ fReadyStateChange(oRequest);
+ // Check if we are aborted
+ if (oRequest._aborted)
+ return;
+ }
+ }
+ };
+ cXMLHttpRequest.prototype.send = function(vData) {
+ // Add method sniffer
+ if (cXMLHttpRequest.onsend)
+ cXMLHttpRequest.onsend.apply(this, arguments);
+
+ if (!arguments.length)
+ vData = null;
+
+ // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+ // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+ // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+ if (vData && vData.nodeType) {
+ vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+ if (!this._headers["Content-Type"])
+ this._object.setRequestHeader("Content-Type", "application/xml");
+ }
+
+ this._data = vData;
+/*
+ // Add to queue
+ if (this._async)
+ fQueue_add(this);
+ else*/
+ fXMLHttpRequest_send(this);
+ };
+ cXMLHttpRequest.prototype.abort = function() {
+ // Add method sniffer
+ if (cXMLHttpRequest.onabort)
+ cXMLHttpRequest.onabort.apply(this, arguments);
+
+ // BUGFIX: Gecko - unnecessary DONE when aborting
+ if (this.readyState > cXMLHttpRequest.UNSENT)
+ this._aborted = true;
+
+ this._object.abort();
+
+ // BUGFIX: IE - memory leak
+ fCleanTransport(this);
+
+ this.readyState = cXMLHttpRequest.UNSENT;
+
+ delete this._data;
+/* if (this._async)
+ fQueue_remove(this);*/
+ };
+ cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
+ return this._object.getAllResponseHeaders();
+ };
+ cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
+ return this._object.getResponseHeader(sName);
+ };
+ cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
+ // BUGFIX: IE - cache issue
+ if (!this._headers)
+ this._headers = {};
+ this._headers[sName] = sValue;
+
+ return this._object.setRequestHeader(sName, sValue);
+ };
+
+ // EventTarget interface implementation
+ cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ return;
+ // Add listener
+ this._listeners.push([sName, fHandler, bUseCapture]);
+ };
+
+ cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ break;
+ // Remove listener
+ if (oListener)
+ this._listeners.splice(nIndex, 1);
+ };
+
+ cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
+ var oEventPseudo = {
+ 'type': oEvent.type,
+ 'target': this,
+ 'currentTarget':this,
+ 'eventPhase': 2,
+ 'bubbles': oEvent.bubbles,
+ 'cancelable': oEvent.cancelable,
+ 'timeStamp': oEvent.timeStamp,
+ 'stopPropagation': function() {}, // There is no flow
+ 'preventDefault': function() {}, // There is no default action
+ 'initEvent': function() {} // Original event object should be initialized
+ };
+
+ // Execute onreadystatechange
+ if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
+ (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
+
+ // Execute listeners
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == oEventPseudo.type && !oListener[2])
+ (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
+ };
+
+ //
+ cXMLHttpRequest.prototype.toString = function() {
+ return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+ };
+
+ cXMLHttpRequest.toString = function() {
+ return '[' + "XMLHttpRequest" + ']';
+ };
+
+ // Helper function
+ function fReadyStateChange(oRequest) {
+ // Sniffing code
+ if (cXMLHttpRequest.onreadystatechange)
+ cXMLHttpRequest.onreadystatechange.apply(oRequest);
+
+ // Fake event
+ oRequest.dispatchEvent({
+ 'type': "readystatechange",
+ 'bubbles': false,
+ 'cancelable': false,
+ 'timeStamp': new Date + 0
+ });
+ };
+
+ function fGetDocument(oRequest) {
+ var oDocument = oRequest.responseXML,
+ sResponse = oRequest.responseText;
+ // Try parsing responseText
+ if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+ oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
+ oDocument.async = false;
+ oDocument.validateOnParse = false;
+ oDocument.loadXML(sResponse);
+ }
+ // Check if there is no error in document
+ if (oDocument)
+ if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+ return null;
+ return oDocument;
+ };
+
+ function fSynchronizeValues(oRequest) {
+ try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
+ try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
+ try { oRequest.status = oRequest._object.status; } catch (e) {}
+ try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
+ };
+
+ function fCleanTransport(oRequest) {
+ // BUGFIX: IE - memory leak (on-page leak)
+ oRequest._object.onreadystatechange = new window.Function;
+ };
+/*
+ // Queue manager
+ var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
+ aQueueRunning = [];
+ function fQueue_add(oRequest) {
+ oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_remove(oRequest) {
+ for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
+ if (bFound)
+ aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
+ else
+ if (aQueueRunning[nIndex] == oRequest)
+ bFound = true;
+ if (bFound)
+ aQueueRunning.length--;
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_process() {
+ if (aQueueRunning.length < 6) {
+ for (var sPriority in oQueuePending) {
+ if (oQueuePending[sPriority].length) {
+ var oRequest = oQueuePending[sPriority][0];
+ oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
+ //
+ aQueueRunning.push(oRequest);
+ // Send request
+ fXMLHttpRequest_send(oRequest);
+ break;
+ }
+ }
+ }
+ };
+*/
+ // Internet Explorer 5.0 (missing apply)
+ if (!window.Function.prototype.apply) {
+ window.Function.prototype.apply = function(oRequest, oArguments) {
+ if (!oArguments)
+ oArguments = [];
+ oRequest.__func = this;
+ oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+ delete oRequest.__func;
+ };
+ };
+
+ // Register new object with window
+ /**
+ * Class: OpenLayers.Request.XMLHttpRequest
+ * Standard-compliant (W3C) cross-browser implementation of the
+ * XMLHttpRequest object. From
+ * http://code.google.com/p/xmlhttprequest/.
+ */
+ if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+ }
+ OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
+/* ======================================================================
+ OpenLayers/Filter/Comparison.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Comparison
+ * This class represents a comparison filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} type: type of the comparison. This is one of
+ * - OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+ * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+ * - OpenLayers.Filter.Comparison.LESS_THAN = "<";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+ * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+ * - OpenLayers.Filter.Comparison.BETWEEN = "..";
+ * - OpenLayers.Filter.Comparison.LIKE = "~";
+ * - OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String}
+ * name of the context property to compare
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {Number} or {String}
+ * comparison value for binary comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ value: null,
+
+ /**
+ * Property: matchCase
+ * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
+ * comparisons. The Filter Encoding 1.1 specification added a matchCase
+ * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
+ * elements. This property will be serialized with those elements only
+ * if using the v1.1.0 filter format. However, when evaluating filters
+ * here, the matchCase property will always be respected (for EQUAL_TO
+ * and NOT_EQUAL_TO). Default is true.
+ */
+ matchCase: true,
+
+ /**
+ * APIProperty: lowerBoundary
+ * {Number} or {String}
+ * lower boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ lowerBoundary: null,
+
+ /**
+ * APIProperty: upperBoundary
+ * {Number} or {String}
+ * upper boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ upperBoundary: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Comparison
+ * Creates a comparison rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>}
+ */
+ initialize: function(options) {
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ // since matchCase on PropertyIsLike is not schema compliant, we only
+ // want to use this if explicitly asked for
+ if (this.type === OpenLayers.Filter.Comparison.LIKE
+ && options.matchCase === undefined) {
+ this.matchCase = null;
+ }
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ if (context instanceof OpenLayers.Feature.Vector) {
+ context = context.attributes;
+ }
+ var result = false;
+ var got = context[this.property];
+ var exp;
+ switch(this.type) {
+ case OpenLayers.Filter.Comparison.EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() == exp.toUpperCase());
+ } else {
+ result = (got == exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() != exp.toUpperCase());
+ } else {
+ result = (got != exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN:
+ result = got < this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN:
+ result = got > this.value;
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+ result = got <= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+ result = got >= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.BETWEEN:
+ result = (got >= this.lowerBoundary) &&
+ (got <= this.upperBoundary);
+ break;
+ case OpenLayers.Filter.Comparison.LIKE:
+ var regexp = new RegExp(this.value, "gi");
+ result = regexp.test(got);
+ break;
+ case OpenLayers.Filter.Comparison.IS_NULL:
+ result = (got === null);
+ break;
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: value2regex
+ * Converts the value of this rule into a regular expression string,
+ * according to the wildcard characters specified. This method has to
+ * be called after instantiation of this class, if the value is not a
+ * regular expression already.
+ *
+ * Parameters:
+ * wildCard - {Char} wildcard character in the above value, default
+ * is "*"
+ * singleChar - {Char} single-character wildcard in the above value
+ * default is "."
+ * escapeChar - {Char} escape character in the above value, default is
+ * "!"
+ *
+ * Returns:
+ * {String} regular expression string
+ */
+ value2regex: function(wildCard, singleChar, escapeChar) {
+ if (wildCard == ".") {
+ throw new Error("'.' is an unsupported wildCard character for " +
+ "OpenLayers.Filter.Comparison");
+ }
+
+
+ // set UMN MapServer defaults for unspecified parameters
+ wildCard = wildCard ? wildCard : "*";
+ singleChar = singleChar ? singleChar : ".";
+ escapeChar = escapeChar ? escapeChar : "!";
+
+ this.value = this.value.replace(
+ new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
+ this.value = this.value.replace(
+ new RegExp("\\"+singleChar, "g"), ".");
+ this.value = this.value.replace(
+ new RegExp("\\"+wildCard, "g"), ".*");
+ this.value = this.value.replace(
+ new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
+ this.value = this.value.replace(
+ new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+
+ return this.value;
+ },
+
+ /**
+ * Method: regex2value
+ * Convert the value of this rule from a regular expression string into an
+ * ogc literal string using a wildCard of *, a singleChar of ., and an
+ * escape of !. Leaves the <value> property unmodified.
+ *
+ * Returns:
+ * {String} A string value.
+ */
+ regex2value: function() {
+
+ var value = this.value;
+
+ // replace ! with !!
+ value = value.replace(/!/g, "!!");
+
+ // replace \. with !. (watching out for \\.)
+ value = value.replace(/(\\)?\\\./g, function($0, $1) {
+ return $1 ? $0 : "!.";
+ });
+
+ // replace \* with #* (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "!*";
+ });
+
+ // replace \\ with \
+ value = value.replace(/\\\\/g, "\\");
+
+ // convert .* to * (the sequence #.* is not allowed)
+ value = value.replace(/\.\*/g, "*");
+
+ return value;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>} Clone of this filter.
+ */
+ clone: function() {
+ return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Comparison"
+});
+
+
+OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+OpenLayers.Filter.Comparison.LESS_THAN = "<";
+OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+OpenLayers.Filter.Comparison.BETWEEN = "..";
+OpenLayers.Filter.Comparison.LIKE = "~";
+OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+/* ======================================================================
+ OpenLayers/Popup/FramedCloud.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Framed.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.FramedCloud
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Framed>
+ */
+OpenLayers.Popup.FramedCloud =
+ OpenLayers.Class(OpenLayers.Popup.Framed, {
+
+ /**
+ * Property: contentDisplayClass
+ * {String} The CSS class of the popup content div.
+ */
+ contentDisplayClass: "olFramedCloudPopupContent",
+
+ /**
+ * APIProperty: autoSize
+ * {Boolean} Framed Cloud is autosizing by default.
+ */
+ autoSize: true,
+
+ /**
+ * APIProperty: panMapIfOutOfView
+ * {Boolean} Framed Cloud does pan into view by default.
+ */
+ panMapIfOutOfView: true,
+
+ /**
+ * APIProperty: imageSize
+ * {<OpenLayers.Size>}
+ */
+ imageSize: new OpenLayers.Size(1276, 736),
+
+ /**
+ * APIProperty: isAlphaImage
+ * {Boolean} The FramedCloud does not use an alpha image (in honor of the
+ * good ie6 folk out there)
+ */
+ isAlphaImage: false,
+
+ /**
+ * APIProperty: fixedRelativePosition
+ * {Boolean} The Framed Cloud popup works in just one fixed position.
+ */
+ fixedRelativePosition: false,
+
+ /**
+ * Property: positionBlocks
+ * {Object} Hash of differen position blocks, keyed by relativePosition
+ * two-character code string (ie "tl", "tr", "bl", "br")
+ */
+ positionBlocks: {
+ "tl": {
+ 'offset': new OpenLayers.Pixel(44, 0),
+ 'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 19),
+ anchor: new OpenLayers.Bounds(0, 32, 22, null),
+ position: new OpenLayers.Pixel(0, -631)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 18),
+ anchor: new OpenLayers.Bounds(null, 32, 0, null),
+ position: new OpenLayers.Pixel(-1238, -632)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 35),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(0, -688)
+ }
+ ]
+ },
+ "tr": {
+ 'offset': new OpenLayers.Pixel(-45, 0),
+ 'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 19),
+ anchor: new OpenLayers.Bounds(0, 32, 22, null),
+ position: new OpenLayers.Pixel(0, -631)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 19),
+ anchor: new OpenLayers.Bounds(null, 32, 0, null),
+ position: new OpenLayers.Pixel(-1238, -631)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 35),
+ anchor: new OpenLayers.Bounds(0, 0, null, null),
+ position: new OpenLayers.Pixel(-215, -687)
+ }
+ ]
+ },
+ "bl": {
+ 'offset': new OpenLayers.Pixel(45, 0),
+ 'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 21),
+ anchor: new OpenLayers.Bounds(0, 0, 22, null),
+ position: new OpenLayers.Pixel(0, -629)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 21),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(-1238, -629)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 33),
+ anchor: new OpenLayers.Bounds(null, null, 0, 0),
+ position: new OpenLayers.Pixel(-101, -674)
+ }
+ ]
+ },
+ "br": {
+ 'offset': new OpenLayers.Pixel(-44, 0),
+ 'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 21),
+ anchor: new OpenLayers.Bounds(0, 0, 22, null),
+ position: new OpenLayers.Pixel(0, -629)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 21),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(-1238, -629)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 33),
+ anchor: new OpenLayers.Bounds(0, null, null, 0),
+ position: new OpenLayers.Pixel(-311, -674)
+ }
+ ]
+ }
+ },
+
+ /**
+ * APIProperty: minSize
+ * {<OpenLayers.Size>}
+ */
+ minSize: new OpenLayers.Size(105, 10),
+
+ /**
+ * APIProperty: maxSize
+ * {<OpenLayers.Size>}
+ */
+ maxSize: new OpenLayers.Size(1200, 660),
+
+ /**
+ * Constructor: OpenLayers.Popup.FramedCloud
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ this.imageSrc = OpenLayers.Util.getImageLocation('cloud-popup-relative.png');
+ OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments);
+ this.contentDiv.className = this.contentDisplayClass;
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.FramedCloud"
+});
+/* ======================================================================
+ OpenLayers/Rule.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Rule
+ * This class represents an SLD Rule, as being used for rule-based SLD styling.
+ */
+OpenLayers.Rule = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String} name of this rule
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this rule (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this rule (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * Property: context
+ * {Object} An optional object with properties that the rule should be
+ * evaluated against. If no context is specified, feature.attributes will
+ * be used.
+ */
+ context: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} Optional filter for the rule.
+ */
+ filter: null,
+
+ /**
+ * Property: elseFilter
+ * {Boolean} Determines whether this rule is only to be applied only if
+ * no other rules match (ElseFilter according to the SLD specification).
+ * Default is false. For instances of OpenLayers.Rule, if elseFilter is
+ * false, the rule will always apply. For subclasses, the else property is
+ * ignored.
+ */
+ elseFilter: false,
+
+ /**
+ * Property: symbolizer
+ * {Object} Symbolizer or hash of symbolizers for this rule. If hash of
+ * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The
+ * latter if useful if it is required to style e.g. vertices of a line
+ * with a point symbolizer. Note, however, that this is not implemented
+ * yet in OpenLayers, but it is the way how symbolizers are defined in
+ * SLD.
+ */
+ symbolizer: null,
+
+ /**
+ * Property: symbolizers
+ * {Array} Collection of symbolizers associated with this rule. If
+ * provided at construction, the symbolizers array has precedence
+ * over the deprecated symbolizer property. Note that multiple
+ * symbolizers are not currently supported by the vector renderers.
+ * Rules with multiple symbolizers are currently only useful for
+ * maintaining elements in an SLD document.
+ */
+ symbolizers: null,
+
+ /**
+ * APIProperty: minScaleDenominator
+ * {Number} or {String} minimum scale at which to draw the feature.
+ * In the case of a String, this can be a combination of text and
+ * propertyNames in the form "literal ${propertyName}"
+ */
+ minScaleDenominator: null,
+
+ /**
+ * APIProperty: maxScaleDenominator
+ * {Number} or {String} maximum scale at which to draw the feature.
+ * In the case of a String, this can be a combination of text and
+ * propertyNames in the form "literal ${propertyName}"
+ */
+ maxScaleDenominator: null,
+
+ /**
+ * Constructor: OpenLayers.Rule
+ * Creates a Rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Rule>}
+ */
+ initialize: function(options) {
+ this.symbolizer = {};
+ OpenLayers.Util.extend(this, options);
+ if (this.symbolizers) {
+ delete this.symbolizer;
+ }
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i in this.symbolizer) {
+ this.symbolizer[i] = null;
+ }
+ this.symbolizer = null;
+ delete this.symbolizers;
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+ *
+ * Returns:
+ * {Boolean} true if the rule applies, false if it does not.
+ * This rule is the default rule and always returns true.
+ */
+ evaluate: function(feature) {
+ var context = this.getContext(feature);
+ var applies = true;
+
+ if (this.minScaleDenominator || this.maxScaleDenominator) {
+ var scale = feature.layer.map.getScale();
+ }
+
+ // check if within minScale/maxScale bounds
+ if (this.minScaleDenominator) {
+ applies = scale >= OpenLayers.Style.createLiteral(
+ this.minScaleDenominator, context);
+ }
+ if (applies && this.maxScaleDenominator) {
+ applies = scale < OpenLayers.Style.createLiteral(
+ this.maxScaleDenominator, context);
+ }
+
+ // check if optional filter applies
+ if(applies && this.filter) {
+ // feature id filters get the feature, others get the context
+ if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
+ applies = this.filter.evaluate(feature);
+ } else {
+ applies = this.filter.evaluate(context);
+ }
+ }
+
+ return applies;
+ },
+
+ /**
+ * Method: getContext
+ * Gets the context for evaluating this rule
+ *
+ * Paramters:
+ * feature - {<OpenLayers.Feature>} feature to take the context from if
+ * none is specified.
+ */
+ getContext: function(feature) {
+ var context = this.context;
+ if (!context) {
+ context = feature.attributes || feature.data;
+ }
+ if (typeof this.context == "function") {
+ context = this.context(feature);
+ }
+ return context;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this rule.
+ *
+ * Returns:
+ * {<OpenLayers.Rule>} Clone of this rule.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ if (this.symbolizers) {
+ // clone symbolizers
+ var len = this.symbolizers.length;
+ options.symbolizers = new Array(len);
+ for (var i=0; i<len; ++i) {
+ options.symbolizers[i] = this.symbolizers[i].clone();
+ }
+ } else {
+ // clone symbolizer
+ options.symbolizer = {};
+ var value, type;
+ for(var key in this.symbolizer) {
+ value = this.symbolizer[key];
+ type = typeof value;
+ if(type === "object") {
+ options.symbolizer[key] = OpenLayers.Util.extend({}, value);
+ } else if(type === "string") {
+ options.symbolizer[key] = value;
+ }
+ }
+ }
+ // clone filter
+ options.filter = this.filter && this.filter.clone();
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ return new OpenLayers.Rule(options);
+ },
+
+ CLASS_NAME: "OpenLayers.Rule"
+});
+/* ======================================================================
+ OpenLayers/Renderer/VML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.VML
+ * Render vector features in browsers with VML capability. Construct a new
+ * VML renderer with the <OpenLayers.Renderer.VML> constructor.
+ *
+ * Note that for all calculations in this class, we use (num | 0) to truncate a
+ * float value to an integer. This is done because it seems that VML doesn't
+ * support float values.
+ *
+ * Inherits from:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String} XML Namespace URN
+ */
+ xmlns: "urn:schemas-microsoft-com:vml",
+
+ /**
+ * Property: symbolCache
+ * {DOMElement} node holding symbols. This hash is keyed by symbol name,
+ * and each value is a hash with a "path" and an "extent" property.
+ */
+ symbolCache: {},
+
+ /**
+ * Property: offset
+ * {Object} Hash with "x" and "y" properties
+ */
+ offset: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.VML
+ * Create a new VML renderer.
+ *
+ * Parameters:
+ * containerID - {String} The id for the element that contains the renderer
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ if (!document.namespaces.olv) {
+ document.namespaces.add("olv", this.xmlns);
+ var style = document.createStyleSheet();
+ var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox'];
+ for (var i = 0, len = shapes.length; i < len; i++) {
+
+ style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
+ "position: absolute; display: inline-block;");
+ }
+ }
+
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ },
+
+ /**
+ * APIMethod: supported
+ * Determine whether a browser supports this renderer.
+ *
+ * Returns:
+ * {Boolean} The browser supports the VML renderer
+ */
+ supported: function() {
+ return !!(document.namespaces);
+ },
+
+ /**
+ * Method: setExtent
+ * Set the renderer's extent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+
+ var left = (extent.left/resolution) | 0;
+ var top = (extent.top/resolution - this.size.h) | 0;
+ if (resolutionChanged || !this.offset) {
+ this.offset = {x: left, y: top};
+ left = 0;
+ top = 0;
+ } else {
+ left = left - this.offset.x;
+ top = top - this.offset.y;
+ }
+
+
+ var org = (left - this.xOffset) + " " + top;
+ this.root.coordorigin = org;
+ var roots = [this.root, this.vectorRoot, this.textRoot];
+ var root;
+ for(var i=0, len=roots.length; i<len; ++i) {
+ root = roots[i];
+
+ var size = this.size.w + " " + this.size.h;
+ root.coordsize = size;
+
+ }
+ // flip the VML display Y axis upside down so it
+ // matches the display Y axis of the map
+ this.root.style.flip = "y";
+
+ return coordSysUnchanged;
+ },
+
+
+ /**
+ * Method: setSize
+ * Set the size of the drawing surface
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} the size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ // setting width and height on all roots to avoid flicker which we
+ // would get with 100% width and height on child roots
+ var roots = [
+ this.rendererRoot,
+ this.root,
+ this.vectorRoot,
+ this.textRoot
+ ];
+ var w = this.size.w + "px";
+ var h = this.size.h + "px";
+ var root;
+ for(var i=0, len=roots.length; i<len; ++i) {
+ root = roots[i];
+ root.style.width = w;
+ root.style.height = h;
+ }
+ },
+
+ /**
+ * Method: getNodeType
+ * Get the node type for a geometry and style
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "olv:rect";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "olv:shape";
+ } else {
+ nodeType = "olv:oval";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "olv:rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ case "OpenLayers.Geometry.LinearRing":
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "olv:shape";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a VML node.
+ *
+ * Parameters:
+ * node - {DOMElement} An VML element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ setStyle: function(node, style, options, geometry) {
+ style = style || node._style;
+ options = options || node._options;
+ var fillColor = style.fillColor;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.title = title;
+ }
+
+ if (node._geometryClass === "OpenLayers.Geometry.Point") {
+ if (style.externalGraphic) {
+ options.isFilled = true;
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+
+ var resolution = this.getResolution();
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ node.style.left = ((((geometry.x - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px";
+ node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px";
+ node.style.width = width + "px";
+ node.style.height = height + "px";
+ node.style.flip = "y";
+
+ // modify fillColor and options for stroke styling below
+ fillColor = "none";
+ options.isStroked = false;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ var cache = this.importSymbol(style.graphicName);
+ node.path = cache.path;
+ node.coordorigin = cache.left + "," + cache.bottom;
+ var size = cache.size;
+ node.coordsize = size + "," + size;
+ this.drawCircle(node, geometry, style.pointRadius);
+ node.style.flip = "y";
+ } else {
+ this.drawCircle(node, geometry, style.pointRadius);
+ }
+ }
+
+ // fill
+ if (options.isFilled) {
+ node.fillcolor = fillColor;
+ } else {
+ node.filled = "false";
+ }
+ var fills = node.getElementsByTagName("fill");
+ var fill = (fills.length == 0) ? null : fills[0];
+ if (!options.isFilled) {
+ if (fill) {
+ node.removeChild(fill);
+ }
+ } else {
+ if (!fill) {
+ fill = this.createNode('olv:fill', node.id + "_fill");
+ }
+ fill.opacity = style.fillOpacity;
+
+ if (node._geometryClass === "OpenLayers.Geometry.Point" &&
+ style.externalGraphic) {
+
+ // override fillOpacity
+ if (style.graphicOpacity) {
+ fill.opacity = style.graphicOpacity;
+ }
+
+ fill.src = style.externalGraphic;
+ fill.type = "frame";
+
+ if (!(style.graphicWidth && style.graphicHeight)) {
+ fill.aspect = "atmost";
+ }
+ }
+ if (fill.parentNode != node) {
+ node.appendChild(fill);
+ }
+ }
+
+ // additional rendering for rotated graphics or symbols
+ var rotation = style.rotation;
+ if ((rotation !== undefined || node._rotation !== undefined)) {
+ node._rotation = rotation;
+ if (style.externalGraphic) {
+ this.graphicRotate(node, xOffset, yOffset, style);
+ // make the fill fully transparent, because we now have
+ // the graphic as imagedata element. We cannot just remove
+ // the fill, because this is part of the hack described
+ // in graphicRotate
+ fill.opacity = 0;
+ } else if(node._geometryClass === "OpenLayers.Geometry.Point") {
+ node.style.rotation = rotation || 0;
+ }
+ }
+
+ // stroke
+ var strokes = node.getElementsByTagName("stroke");
+ var stroke = (strokes.length == 0) ? null : strokes[0];
+ if (!options.isStroked) {
+ node.stroked = false;
+ if (stroke) {
+ stroke.on = false;
+ }
+ } else {
+ if (!stroke) {
+ stroke = this.createNode('olv:stroke', node.id + "_stroke");
+ node.appendChild(stroke);
+ }
+ stroke.on = true;
+ stroke.color = style.strokeColor;
+ stroke.weight = style.strokeWidth + "px";
+ stroke.opacity = style.strokeOpacity;
+ stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' :
+ (style.strokeLinecap || 'round');
+ if (style.strokeDashstyle) {
+ stroke.dashstyle = this.dashStyle(style);
+ }
+ }
+
+ if (style.cursor != "inherit" && style.cursor != null) {
+ node.style.cursor = style.cursor;
+ }
+ return node;
+ },
+
+ /**
+ * Method: graphicRotate
+ * If a point is to be styled with externalGraphic and rotation, VML fills
+ * cannot be used to display the graphic, because rotation of graphic
+ * fills is not supported by the VML implementation of Internet Explorer.
+ * This method creates a olv:imagedata element inside the VML node,
+ * DXImageTransform.Matrix and BasicImage filters for rotation and
+ * opacity, and a 3-step hack to remove rendering artefacts from the
+ * graphic and preserve the ability of graphics to trigger events.
+ * Finally, OpenLayers methods are used to determine the correct
+ * insertion point of the rotated image, because DXImageTransform.Matrix
+ * does the rotation without the ability to specify a rotation center
+ * point.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * xOffset - {Number} rotation center relative to image, x coordinate
+ * yOffset - {Number} rotation center relative to image, y coordinate
+ * style - {Object}
+ */
+ graphicRotate: function(node, xOffset, yOffset, style) {
+ var style = style || node._style;
+ var rotation = style.rotation || 0;
+
+ var aspectRatio, size;
+ if (!(style.graphicWidth && style.graphicHeight)) {
+ // load the image to determine its size
+ var img = new Image();
+ img.onreadystatechange = OpenLayers.Function.bind(function() {
+ if(img.readyState == "complete" ||
+ img.readyState == "interactive") {
+ aspectRatio = img.width / img.height;
+ size = Math.max(style.pointRadius * 2,
+ style.graphicWidth || 0,
+ style.graphicHeight || 0);
+ xOffset = xOffset * aspectRatio;
+ style.graphicWidth = size * aspectRatio;
+ style.graphicHeight = size;
+ this.graphicRotate(node, xOffset, yOffset, style);
+ }
+ }, this);
+ img.src = style.externalGraphic;
+
+ // will be called again by the onreadystate handler
+ return;
+ } else {
+ size = Math.max(style.graphicWidth, style.graphicHeight);
+ aspectRatio = style.graphicWidth / style.graphicHeight;
+ }
+
+ var width = Math.round(style.graphicWidth || size * aspectRatio);
+ var height = Math.round(style.graphicHeight || size);
+ node.style.width = width + "px";
+ node.style.height = height + "px";
+
+ // Three steps are required to remove artefacts for images with
+ // transparent backgrounds (resulting from using DXImageTransform
+ // filters on svg objects), while preserving awareness for browser
+ // events on images:
+ // - Use the fill as usual (like for unrotated images) to handle
+ // events
+ // - specify an imagedata element with the same src as the fill
+ // - style the imagedata element with an AlphaImageLoader filter
+ // with empty src
+ var image = document.getElementById(node.id + "_image");
+ if (!image) {
+ image = this.createNode("olv:imagedata", node.id + "_image");
+ node.appendChild(image);
+ }
+ image.style.width = width + "px";
+ image.style.height = height + "px";
+ image.src = style.externalGraphic;
+ image.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(" +
+ "src='', sizingMethod='scale')";
+
+ var rot = rotation * Math.PI / 180;
+ var sintheta = Math.sin(rot);
+ var costheta = Math.cos(rot);
+
+ // do the rotation on the image
+ var filter =
+ "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
+ ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
+ ",SizingMethod='auto expand')\n";
+
+ // set the opacity (needed for the imagedata)
+ var opacity = style.graphicOpacity || style.fillOpacity;
+ if (opacity && opacity != 1) {
+ filter +=
+ "progid:DXImageTransform.Microsoft.BasicImage(opacity=" +
+ opacity+")\n";
+ }
+ node.style.filter = filter;
+
+ // do the rotation again on a box, so we know the insertion point
+ var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
+ var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
+ imgBox.rotate(style.rotation, centerPoint);
+ var imgBounds = imgBox.getBounds();
+
+ node.style.left = Math.round(
+ parseInt(node.style.left) + imgBounds.left) + "px";
+ node.style.top = Math.round(
+ parseInt(node.style.top) - imgBounds.bottom) + "px";
+ },
+
+ /**
+ * Method: postDraw
+ * Does some node postprocessing to work around browser issues:
+ * - Some versions of Internet Explorer seem to be unable to set fillcolor
+ * and strokecolor to "none" correctly before the fill node is appended
+ * to a visible vml node. This method takes care of that and sets
+ * fillcolor and strokecolor again if needed.
+ * - In some cases, a node won't become visible after being drawn. Setting
+ * style.visibility to "visible" works around that.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {
+ node.style.visibility = "visible";
+ var fillColor = node._style.fillColor;
+ var strokeColor = node._style.strokeColor;
+ if (fillColor == "none" &&
+ node.fillcolor != fillColor) {
+ node.fillcolor = fillColor;
+ }
+ if (strokeColor == "none" &&
+ node.strokecolor != strokeColor) {
+ node.strokecolor = strokeColor;
+ }
+ },
+
+
+ /**
+ * Method: setNodeDimension
+ * Get the geometry's bounds, convert it to our vml coordinate system,
+ * then set the node's position, size, and local coordinate system.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ setNodeDimension: function(node, geometry) {
+
+ var bbox = geometry.getBounds();
+ if(bbox) {
+ var resolution = this.getResolution();
+
+ var scaledBox =
+ new OpenLayers.Bounds(((bbox.left - this.featureDx)/resolution - this.offset.x) | 0,
+ (bbox.bottom/resolution - this.offset.y) | 0,
+ ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0,
+ (bbox.top/resolution - this.offset.y) | 0);
+
+ // Set the internal coordinate system to draw the path
+ node.style.left = scaledBox.left + "px";
+ node.style.top = scaledBox.top + "px";
+ node.style.width = scaledBox.getWidth() + "px";
+ node.style.height = scaledBox.getHeight() + "px";
+
+ node.coordorigin = scaledBox.left + " " + scaledBox.top;
+ node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
+ }
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ *
+ * Returns:
+ * {String} A VML compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style) {
+ var dash = style.strokeDashstyle;
+ switch (dash) {
+ case 'solid':
+ case 'dot':
+ case 'dash':
+ case 'dashdot':
+ case 'longdash':
+ case 'longdashdot':
+ return dash;
+ default:
+ // very basic guessing of dash style patterns
+ var parts = dash.split(/[ ,]/);
+ if (parts.length == 2) {
+ if (1*parts[0] >= 2*parts[1]) {
+ return "longdash";
+ }
+ return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
+ } else if (parts.length == 4) {
+ return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
+ "dashdot";
+ }
+ return "solid";
+ }
+ },
+
+ /**
+ * Method: createNode
+ * Create a new node
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElement(type);
+ if (id) {
+ node.id = id;
+ }
+
+ // IE hack to make elements unselectable, to prevent 'blue flash'
+ // while dragging vectors; #1410
+ node.unselectable = 'on';
+ node.onselectstart = OpenLayers.Function.False;
+
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ * Determine whether a node is of a given type
+ *
+ * Parameters:
+ * node - {DOMElement} An VML element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+
+ //split type
+ var subType = type;
+ var splitIndex = subType.indexOf(":");
+ if (splitIndex != -1) {
+ subType = subType.substr(splitIndex+1);
+ }
+
+ //split nodeName
+ var nodeName = node.nodeName;
+ splitIndex = nodeName.indexOf(":");
+ if (splitIndex != -1) {
+ nodeName = nodeName.substr(splitIndex+1);
+ }
+
+ return (subType == nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ * Create the renderer root
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ return this.nodeFactory(this.container.id + "_vmlRoot", "div");
+ },
+
+ /**
+ * Method: createRoot
+ * Create the main root element
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "olv:group");
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * Render a point
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the point could not be drawn
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * Render a circle.
+ * Size and Center a circle given geometry (x,y center) and radius
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {float}
+ *
+ * Returns:
+ * {DOMElement} or false if the circle could not ne drawn
+ */
+ drawCircle: function(node, geometry, radius) {
+ if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
+ var resolution = this.getResolution();
+
+ node.style.left = ((((geometry.x - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px";
+ node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - radius) + "px";
+
+ var diameter = radius * 2;
+
+ node.style.width = diameter + "px";
+ node.style.height = diameter + "px";
+ return node;
+ }
+ return false;
+ },
+
+
+ /**
+ * Method: drawLineString
+ * Render a linestring.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLineString: function(node, geometry) {
+ return this.drawLine(node, geometry, false);
+ },
+
+ /**
+ * Method: drawLinearRing
+ * Render a linearring
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLinearRing: function(node, geometry) {
+ return this.drawLine(node, geometry, true);
+ },
+
+ /**
+ * Method: DrawLine
+ * Render a line.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * closeLine - {Boolean} Close the line? (make it a ring?)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLine: function(node, geometry, closeLine) {
+
+ this.setNodeDimension(node, geometry);
+
+ var resolution = this.getResolution();
+ var numComponents = geometry.components.length;
+ var parts = new Array(numComponents);
+
+ var comp, x, y;
+ for (var i = 0; i < numComponents; i++) {
+ comp = geometry.components[i];
+ x = ((comp.x - this.featureDx)/resolution - this.offset.x) | 0;
+ y = (comp.y/resolution - this.offset.y) | 0;
+ parts[i] = " " + x + "," + y + " l ";
+ }
+ var end = (closeLine) ? " x e" : " e";
+ node.path = "m" + parts.join("") + end;
+ return node;
+ },
+
+ /**
+ * Method: drawPolygon
+ * Render a polygon
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawPolygon: function(node, geometry) {
+ this.setNodeDimension(node, geometry);
+
+ var resolution = this.getResolution();
+
+ var path = [];
+ var j, jj, points, area, first, second, i, ii, comp, pathComp, x, y;
+ for (j=0, jj=geometry.components.length; j<jj; j++) {
+ path.push("m");
+ points = geometry.components[j].components;
+ // we only close paths of interior rings with area
+ area = (j === 0);
+ first = null;
+ second = null;
+ for (i=0, ii=points.length; i<ii; i++) {
+ comp = points[i];
+ x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
+ y = (comp.y / resolution - this.offset.y) | 0;
+ pathComp = " " + x + "," + y;
+ path.push(pathComp);
+ if (i==0) {
+ path.push(" l");
+ }
+ if (!area) {
+ // IE improperly renders sub-paths that have no area.
+ // Instead of checking the area of every ring, we confirm
+ // the ring has at least three distinct points. This does
+ // not catch all non-zero area cases, but it greatly improves
+ // interior ring digitizing and is a minor performance hit
+ // when rendering rings with many points.
+ if (!first) {
+ first = pathComp;
+ } else if (first != pathComp) {
+ if (!second) {
+ second = pathComp;
+ } else if (second != pathComp) {
+ // stop looking
+ area = true;
+ }
+ }
+ }
+ }
+ path.push(area ? " x " : " ");
+ }
+ path.push("e");
+ node.path = path.join("");
+ return node;
+ },
+
+ /**
+ * Method: drawRectangle
+ * Render a rectangle
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+
+ node.style.left = (((geometry.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
+ node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px";
+ node.style.width = ((geometry.width/resolution) | 0) + "px";
+ node.style.height = ((geometry.height/resolution) | 0) + "px";
+
+ return node;
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
+ var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
+
+ var resolution = this.getResolution();
+ label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
+ label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px";
+ label.style.flip = "y";
+
+ textbox.innerText = style.label;
+
+ if (style.cursor != "inherit" && style.cursor != null) {
+ textbox.style.cursor = style.cursor;
+ }
+ if (style.fontColor) {
+ textbox.style.color = style.fontColor;
+ }
+ if (style.fontOpacity) {
+ textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')';
+ }
+ if (style.fontFamily) {
+ textbox.style.fontFamily = style.fontFamily;
+ }
+ if (style.fontSize) {
+ textbox.style.fontSize = style.fontSize;
+ }
+ if (style.fontWeight) {
+ textbox.style.fontWeight = style.fontWeight;
+ }
+ if (style.fontStyle) {
+ textbox.style.fontStyle = style.fontStyle;
+ }
+ if(style.labelSelect === true) {
+ label._featureId = featureId;
+ textbox._featureId = featureId;
+ textbox._geometry = location;
+ textbox._geometryClass = location.CLASS_NAME;
+ }
+ textbox.style.whiteSpace = "nowrap";
+ // fun with IE: IE7 in standards compliant mode does not display any
+ // text with a left inset of 0. So we set this to 1px and subtract one
+ // pixel later when we set label.style.left
+ textbox.inset = "1px,0px,0px,0px";
+
+ if(!label.parentNode) {
+ label.appendChild(textbox);
+ this.textRoot.appendChild(label);
+ }
+
+ var align = style.labelAlign || "cm";
+ if (align.length == 1) {
+ align += "m";
+ }
+ var xshift = textbox.clientWidth *
+ (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
+ var yshift = textbox.clientHeight *
+ (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
+ label.style.left = parseInt(label.style.left)-xshift-1+"px";
+ label.style.top = parseInt(label.style.top)+yshift+"px";
+
+ },
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ * root - {DOMElement} optional root node. To be used when this renderer
+ * holds roots from multiple layers to tell this method which one to
+ * detach
+ *
+ * Returns:
+ * {Boolean} true if successful, false otherwise
+ */
+ moveRoot: function(renderer) {
+ var layer = this.map.getLayer(renderer.container.id);
+ if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
+ layer = this.map.getLayer(this.container.id);
+ }
+ layer && layer.renderer.clear();
+ OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
+ layer && layer.redraw();
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
+ */
+ importSymbol: function (graphicName) {
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the cache
+ var cache = this.symbolCache[id];
+ if (cache) {
+ return cache;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var pathitems = ["m"];
+ for (var i=0; i<symbol.length; i=i+2) {
+ var x = symbol[i];
+ var y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+
+ pathitems.push(x);
+ pathitems.push(y);
+ if (i == 0) {
+ pathitems.push("l");
+ }
+ }
+ pathitems.push("x e");
+ var path = pathitems.join(" ");
+
+ var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
+ if(diff > 0) {
+ symbolExtent.bottom = symbolExtent.bottom - diff;
+ symbolExtent.top = symbolExtent.top + diff;
+ } else {
+ symbolExtent.left = symbolExtent.left + diff;
+ symbolExtent.right = symbolExtent.right - diff;
+ }
+
+ cache = {
+ path: path,
+ size: symbolExtent.getWidth(), // equals getHeight() now
+ left: symbolExtent.left,
+ bottom: symbolExtent.bottom
+ };
+ this.symbolCache[id] = cache;
+
+ return cache;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.VML"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.VML.LABEL_SHIFT = {
+ "l": 0,
+ "c": .5,
+ "r": 1,
+ "t": 0,
+ "m": .5,
+ "b": 1
+};
+/* ======================================================================
+ OpenLayers/Protocol.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class. Not to be instantiated directly. Use
+ * one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} The format used by this protocol.
+ */
+ format: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the protocol can set autoDestroy to false
+ * to fully control when the protocol is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Property: defaultFilter
+ * {<OpenLayers.Filter>} Optional default filter to read requests
+ */
+ defaultFilter: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol
+ * Abstract class for vector protocols. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ options = options || {};
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * Method: mergeWithDefaultFilter
+ * Merge filter passed to the read method with the default one
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ mergeWithDefaultFilter: function(filter) {
+ var merged;
+ if (filter && this.defaultFilter) {
+ merged = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.defaultFilter, filter]
+ });
+ } else {
+ merged = filter || this.defaultFilter || undefined;
+ }
+ return merged;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.options = null;
+ this.format = null;
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ read: function(options) {
+ options = options || {};
+ options.filter = this.mergeWithDefaultFilter(options.filter);
+ },
+
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ create: function() {
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ update: function() {
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ "delete": function() {
+ },
+
+ /**
+ * APIMethod: commit
+ * Go over the features and for each take action
+ * based on the feature state. Possible actions are create,
+ * update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Object whose possible keys are "create", "update",
+ * "delete", "callback" and "scope", the values referenced by the
+ * first three are objects as passed to the "create", "update", and
+ * "delete" methods, the value referenced by the "callback" key is
+ * a function which is called when the commit operation is complete
+ * using the scope referenced by the "scope" key.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Protocol.Response>})} An array of
+ * <OpenLayers.Protocol.Response> objects.
+ */
+ commit: function() {
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ },
+
+ /**
+ * Method: createCallback
+ * Returns a function that applies the given public method with resp and
+ * options arguments.
+ *
+ * Parameters:
+ * method - {Function} The method to be applied by the callback.
+ * response - {<OpenLayers.Protocol.Response>} The protocol response object.
+ * options - {Object} Options sent to the protocol method
+ */
+ createCallback: function(method, response, options) {
+ return OpenLayers.Function.bind(function() {
+ method.apply(this, [response, options]);
+ }, this);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol"
+});
+
+/**
+ * Class: OpenLayers.Protocol.Response
+ * Protocols return Response objects to their users.
+ */
+OpenLayers.Protocol.Response = OpenLayers.Class({
+ /**
+ * Property: code
+ * {Number} - OpenLayers.Protocol.Response.SUCCESS or
+ * OpenLayers.Protocol.Response.FAILURE
+ */
+ code: null,
+
+ /**
+ * Property: requestType
+ * {String} The type of request this response corresponds to. Either
+ * "create", "read", "update" or "delete".
+ */
+ requestType: null,
+
+ /**
+ * Property: last
+ * {Boolean} - true if this is the last response expected in a commit,
+ * false otherwise, defaults to true.
+ */
+ last: true,
+
+ /**
+ * Property: features
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ features: null,
+
+ /**
+ * Property: data
+ * {Object}
+ * The data returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ data: null,
+
+ /**
+ * Property: reqFeatures
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features provided by the user and placed in the request by the
+ * protocol.
+ */
+ reqFeatures: null,
+
+ /**
+ * Property: priv
+ */
+ priv: null,
+
+ /**
+ * Property: error
+ * {Object} The error object in case a service exception was encountered.
+ */
+ error: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Response
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: success
+ *
+ * Returns:
+ * {Boolean} - true on success, false otherwise
+ */
+ success: function() {
+ return this.code > 0;
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
+/* ======================================================================
+ OpenLayers/Protocol/HTTP.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.HTTP
+ * A basic HTTP protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.HTTP> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: url
+ * {String} Service URL, read-only, set through the options
+ * passed to constructor.
+ */
+ url: null,
+
+ /**
+ * Property: headers
+ * {Object} HTTP request headers, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'Content-Type': 'plain/text'}
+ */
+ headers: null,
+
+ /**
+ * Property: params
+ * {Object} Parameters of GET requests, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'bbox': '5,5,5,5'}
+ */
+ params: null,
+
+ /**
+ * Property: callback
+ * {Object} Function to be called when the <read>, <create>,
+ * <update>, <delete> or <commit> operation completes, read-only,
+ * set through the options passed to the constructor.
+ */
+ callback: null,
+
+ /**
+ * Property: scope
+ * {Object} Callback execution scope, read-only, set through the
+ * options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: readWithPOST
+ * {Boolean} true if read operations are done with POST requests
+ * instead of GET, defaults to false.
+ */
+ readWithPOST: false,
+
+ /**
+ * APIProperty: updateWithPOST
+ * {Boolean} true if update operations are done with POST requests
+ * defaults to false.
+ */
+ updateWithPOST: false,
+
+ /**
+ * APIProperty: deleteWithPOST
+ * {Boolean} true if delete operations are done with POST requests
+ * defaults to false.
+ * if true, POST data is set to output of format.write().
+ */
+ deleteWithPOST: false,
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Default is false. If true and the layer has a projection object set,
+ * any BBOX filter will be serialized with a fifth item identifying the
+ * projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * Constructor: OpenLayers.Protocol.HTTP
+ * A class for giving layers generic HTTP protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * headers - {Object}
+ * params - {Object} URL parameters for GET requests
+ * format - {<OpenLayers.Format>}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.headers = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ wildcarded: this.wildcarded,
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.params = null;
+ this.headers = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, the filter will be serialized using the
+ * <OpenLayers.Format.QueryStringFilter> class.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Valid options:
+ * url - {String} Url for the request.
+ * params - {Object} Parameters to get serialized as a query string.
+ * headers - {Object} Headers to be set on the request.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ * readWithPOST - {Boolean} If the request should be done with POST.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the HTTP request, this object is also passed to the
+ * callback function when the request completes, its "features" property
+ * is then populated with the features received from the server.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = options || {};
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var readWithPOST = (options.readWithPOST !== undefined) ?
+ options.readWithPOST : this.readWithPOST;
+ var resp = new OpenLayers.Protocol.Response({requestType: "read"});
+ if(readWithPOST) {
+ var headers = options.headers || {};
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ data: OpenLayers.Util.getParameterString(options.params),
+ headers: headers
+ });
+ } else {
+ resp.priv = OpenLayers.Request.GET({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ params: options.params,
+ headers: options.headers
+ });
+ }
+ return resp;
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the features received from the server.
+ */
+ create: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: features,
+ requestType: "create"
+ });
+
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleCreate, resp, options),
+ headers: options.headers,
+ data: this.format.write(features)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleCreate
+ * Called the the request issued by <create> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create call.
+ */
+ handleCreate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the feature received from the server.
+ */
+ update: function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "update"
+ });
+
+ var method = this.updateWithPOST ? "POST" : "PUT";
+ resp.priv = OpenLayers.Request[method]({
+ url: url,
+ callback: this.createCallback(this.handleUpdate, resp, options),
+ headers: options.headers,
+ data: this.format.write(feature)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleUpdate
+ * Called the the request issued by <update> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the update call.
+ */
+ handleUpdate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes.
+ */
+ "delete": function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "delete"
+ });
+
+ var method = this.deleteWithPOST ? "POST" : "DELETE";
+ var requestOptions = {
+ url: url,
+ callback: this.createCallback(this.handleDelete, resp, options),
+ headers: options.headers
+ };
+ if (this.deleteWithPOST) {
+ requestOptions.data = this.format.write(feature);
+ }
+ resp.priv = OpenLayers.Request[method](requestOptions);
+
+ return resp;
+ },
+
+ /**
+ * Method: handleDelete
+ * Called the the request issued by <delete> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the delete call.
+ */
+ handleDelete: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create, read, update,
+ * or delete call.
+ */
+ handleResponse: function(resp, options) {
+ var request = resp.priv;
+ if(options.callback) {
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ if(resp.requestType != "delete") {
+ resp.features = this.parseFeatures(request);
+ }
+ resp.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ resp.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, resp);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features.
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if (!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ /**
+ * APIMethod: commit
+ * Iterate over each feature and take action based on the feature state.
+ * Possible actions are create, update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Optional object for setting up intermediate commit
+ * callbacks.
+ *
+ * Valid options:
+ * create - {Object} Optional object to be passed to the <create> method.
+ * update - {Object} Optional object to be passed to the <update> method.
+ * delete - {Object} Optional object to be passed to the <delete> method.
+ * callback - {Function} Optional function to be called when the commit
+ * is complete.
+ * scope - {Object} Optional object to be set as the scope of the callback.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
+ * one per request made to the server, each object's "priv" property
+ * references the corresponding HTTP request.
+ */
+ commit: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var resp = [], nResponses = 0;
+
+ // Divide up features before issuing any requests. This properly
+ // counts requests in the event that any responses come in before
+ // all requests have been issued.
+ var types = {};
+ types[OpenLayers.State.INSERT] = [];
+ types[OpenLayers.State.UPDATE] = [];
+ types[OpenLayers.State.DELETE] = [];
+ var feature, list, requestFeatures = [];
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ list = types[feature.state];
+ if(list) {
+ list.push(feature);
+ requestFeatures.push(feature);
+ }
+ }
+ // tally up number of requests
+ var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
+ types[OpenLayers.State.UPDATE].length +
+ types[OpenLayers.State.DELETE].length;
+
+ // This response will be sent to the final callback after all the others
+ // have been fired.
+ var success = true;
+ var finalResponse = new OpenLayers.Protocol.Response({
+ reqFeatures: requestFeatures
+ });
+
+ function insertCallback(response) {
+ var len = response.features ? response.features.length : 0;
+ var fids = new Array(len);
+ for(var i=0; i<len; ++i) {
+ fids[i] = response.features[i].fid;
+ }
+ finalResponse.insertIds = fids;
+ callback.apply(this, [response]);
+ }
+
+ function callback(response) {
+ this.callUserCallback(response, options);
+ success = success && response.success();
+ nResponses++;
+ if (nResponses >= nRequests) {
+ if (options.callback) {
+ finalResponse.code = success ?
+ OpenLayers.Protocol.Response.SUCCESS :
+ OpenLayers.Protocol.Response.FAILURE;
+ options.callback.apply(options.scope, [finalResponse]);
+ }
+ }
+ }
+
+ // start issuing requests
+ var queue = types[OpenLayers.State.INSERT];
+ if(queue.length > 0) {
+ resp.push(this.create(
+ queue, OpenLayers.Util.applyDefaults(
+ {callback: insertCallback, scope: this}, options.create
+ )
+ ));
+ }
+ queue = types[OpenLayers.State.UPDATE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this.update(
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options.update
+ ))
+ );
+ }
+ queue = types[OpenLayers.State.DELETE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this["delete"](
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options["delete"]
+ ))
+ );
+ }
+ return resp;
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this HTTP protocol (as a result
+ * of a create, read, update, delete or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ /**
+ * Method: callUserCallback
+ * This method is used from within the commit method each time an
+ * an HTTP response is received from the server, it is responsible
+ * for calling the user-supplied callbacks.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>}
+ * options - {Object} The map of options passed to the commit call.
+ */
+ callUserCallback: function(resp, options) {
+ var opt = options[resp.requestType];
+ if(opt && opt.callback) {
+ opt.callback.call(opt.scope, resp);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.HTTP"
+});
+/* ======================================================================
+ OpenLayers/Control/LayerSwitcher.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.LayerSwitcher
+ * The LayerSwitcher control displays a table of contents for the map. This
+ * allows the user interface to switch between BaseLasyers and to show or hide
+ * Overlays. By default the switcher is shown minimized on the right edge of
+ * the map, the user may expand it by clicking on the handle.
+ *
+ * To create the LayerSwitcher outside of the map, pass the Id of a html div
+ * as the first argument to the constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.LayerSwitcher = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layerStates
+ * {Array(Object)} Basically a copy of the "state" of the map's layers
+ * the last time the control was drawn. We have this in order to avoid
+ * unnecessarily redrawing the control.
+ */
+ layerStates: null,
+
+ // DOM Elements
+
+ /**
+ * Property: layersDiv
+ * {DOMElement}
+ */
+ layersDiv: null,
+
+ /**
+ * Property: baseLayersDiv
+ * {DOMElement}
+ */
+ baseLayersDiv: null,
+
+ /**
+ * Property: baseLayers
+ * {Array(Object)}
+ */
+ baseLayers: null,
+
+
+ /**
+ * Property: dataLbl
+ * {DOMElement}
+ */
+ dataLbl: null,
+
+ /**
+ * Property: dataLayersDiv
+ * {DOMElement}
+ */
+ dataLayersDiv: null,
+
+ /**
+ * Property: dataLayers
+ * {Array(Object)}
+ */
+ dataLayers: null,
+
+
+ /**
+ * Property: minimizeDiv
+ * {DOMElement}
+ */
+ minimizeDiv: null,
+
+ /**
+ * Property: maximizeDiv
+ * {DOMElement}
+ */
+ maximizeDiv: null,
+
+ /**
+ * APIProperty: ascending
+ * {Boolean}
+ */
+ ascending: true,
+
+ /**
+ * Constructor: OpenLayers.Control.LayerSwitcher
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.layerStates = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+
+ //clear out layers info and unregister their events
+ this.clearLayersArray("base");
+ this.clearLayersArray("data");
+
+ this.map.events.un({
+ buttonclick: this.onButtonClick,
+ addlayer: this.redraw,
+ changelayer: this.redraw,
+ removelayer: this.redraw,
+ changebaselayer: this.redraw,
+ scope: this
+ });
+ this.events.unregister("buttonclick", this, this.onButtonClick);
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Properties:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ this.map.events.on({
+ addlayer: this.redraw,
+ changelayer: this.redraw,
+ removelayer: this.redraw,
+ changebaselayer: this.redraw,
+ scope: this
+ });
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the
+ * switcher tabs.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this);
+
+ // create layout divs
+ this.loadContents();
+
+ // set mode to minimize
+ if(!this.outsideViewport) {
+ this.minimizeControl();
+ }
+
+ // populate div with current info
+ this.redraw();
+
+ return this.div;
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.minimizeDiv) {
+ this.minimizeControl();
+ } else if (button === this.maximizeDiv) {
+ this.maximizeControl();
+ } else if (button._layerSwitcher === this.id) {
+ if (button["for"]) {
+ button = document.getElementById(button["for"]);
+ }
+ if (!button.disabled) {
+ if (button.type == "radio") {
+ button.checked = true;
+ this.map.setBaseLayer(this.map.getLayer(button._layer));
+ } else {
+ button.checked = !button.checked;
+ this.updateMap();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clearLayersArray
+ * User specifies either "base" or "data". we then clear all the
+ * corresponding listeners, the div, and reinitialize a new array.
+ *
+ * Parameters:
+ * layersType - {String}
+ */
+ clearLayersArray: function(layersType) {
+ this[layersType + "LayersDiv"].innerHTML = "";
+ this[layersType + "Layers"] = [];
+ },
+
+
+ /**
+ * Method: checkRedraw
+ * Checks if the layer state has changed since the last redraw() call.
+ *
+ * Returns:
+ * {Boolean} The layer state changed since the last redraw() call.
+ */
+ checkRedraw: function() {
+ if ( !this.layerStates.length ||
+ (this.map.layers.length != this.layerStates.length) ) {
+ return true;
+ }
+
+ for (var i = 0, len = this.layerStates.length; i < len; i++) {
+ var layerState = this.layerStates[i];
+ var layer = this.map.layers[i];
+ if ( (layerState.name != layer.name) ||
+ (layerState.inRange != layer.inRange) ||
+ (layerState.id != layer.id) ||
+ (layerState.visibility != layer.visibility) ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Method: redraw
+ * Goes through and takes the current state of the Map and rebuilds the
+ * control to display that state. Groups base layers into a
+ * radio-button group and lists each data layer with a checkbox.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ redraw: function() {
+ //if the state hasn't changed since last redraw, no need
+ // to do anything. Just return the existing div.
+ if (!this.checkRedraw()) {
+ return this.div;
+ }
+
+ //clear out previous layers
+ this.clearLayersArray("base");
+ this.clearLayersArray("data");
+
+ var containsOverlays = false;
+ var containsBaseLayers = false;
+
+ // Save state -- for checking layer if the map state changed.
+ // We save this before redrawing, because in the process of redrawing
+ // we will trigger more visibility changes, and we want to not redraw
+ // and enter an infinite loop.
+ var len = this.map.layers.length;
+ this.layerStates = new Array(len);
+ for (var i=0; i <len; i++) {
+ var layer = this.map.layers[i];
+ this.layerStates[i] = {
+ 'name': layer.name,
+ 'visibility': layer.visibility,
+ 'inRange': layer.inRange,
+ 'id': layer.id
+ };
+ }
+
+ var layers = this.map.layers.slice();
+ if (!this.ascending) { layers.reverse(); }
+ for(var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ var baseLayer = layer.isBaseLayer;
+
+ if (layer.displayInLayerSwitcher) {
+
+ if (baseLayer) {
+ containsBaseLayers = true;
+ } else {
+ containsOverlays = true;
+ }
+
+ // only check a baselayer if it is *the* baselayer, check data
+ // layers if they are visible
+ var checked = (baseLayer) ? (layer == this.map.baseLayer)
+ : layer.getVisibility();
+
+ // create input element
+ var inputElem = document.createElement("input"),
+ // The input shall have an id attribute so we can use
+ // labels to interact with them.
+ inputId = OpenLayers.Util.createUniqueID(
+ this.id + "_input_"
+ );
+
+ inputElem.id = inputId;
+ inputElem.name = (baseLayer) ? this.id + "_baseLayers" : layer.name;
+ inputElem.type = (baseLayer) ? "radio" : "checkbox";
+ inputElem.value = layer.name;
+ inputElem.checked = checked;
+ inputElem.defaultChecked = checked;
+ inputElem.className = "olButton";
+ inputElem._layer = layer.id;
+ inputElem._layerSwitcher = this.id;
+
+ if (!baseLayer && !layer.inRange) {
+ inputElem.disabled = true;
+ }
+
+ // create span
+ var labelSpan = document.createElement("label");
+ // this isn't the DOM attribute 'for', but an arbitrary name we
+ // use to find the appropriate input element in <onButtonClick>
+ labelSpan["for"] = inputElem.id;
+ OpenLayers.Element.addClass(labelSpan, "labelSpan olButton");
+ labelSpan._layer = layer.id;
+ labelSpan._layerSwitcher = this.id;
+ if (!baseLayer && !layer.inRange) {
+ labelSpan.style.color = "gray";
+ }
+ labelSpan.innerHTML = layer.name;
+ labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
+ : "baseline";
+ // create line break
+ var br = document.createElement("br");
+
+
+ var groupArray = (baseLayer) ? this.baseLayers
+ : this.dataLayers;
+ groupArray.push({
+ 'layer': layer,
+ 'inputElem': inputElem,
+ 'labelSpan': labelSpan
+ });
+
+
+ var groupDiv = (baseLayer) ? this.baseLayersDiv
+ : this.dataLayersDiv;
+ groupDiv.appendChild(inputElem);
+ groupDiv.appendChild(labelSpan);
+ groupDiv.appendChild(br);
+ }
+ }
+
+ // if no overlays, dont display the overlay label
+ this.dataLbl.style.display = (containsOverlays) ? "" : "none";
+
+ // if no baselayers, dont display the baselayer label
+ this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateMap
+ * Cycles through the loaded data and base layer input arrays and makes
+ * the necessary calls to the Map object such that that the map's
+ * visual state corresponds to what the user has selected in
+ * the control.
+ */
+ updateMap: function() {
+
+ // set the newly selected base layer
+ for(var i=0, len=this.baseLayers.length; i<len; i++) {
+ var layerEntry = this.baseLayers[i];
+ if (layerEntry.inputElem.checked) {
+ this.map.setBaseLayer(layerEntry.layer, false);
+ }
+ }
+
+ // set the correct visibilities for the overlays
+ for(var i=0, len=this.dataLayers.length; i<len; i++) {
+ var layerEntry = this.dataLayers[i];
+ layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
+ }
+
+ },
+
+ /**
+ * Method: maximizeControl
+ * Set up the labels and divs for the control
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ maximizeControl: function(e) {
+
+ // set the div's width and height to empty values, so
+ // the div dimensions can be controlled by CSS
+ this.div.style.width = "";
+ this.div.style.height = "";
+
+ this.showControls(false);
+
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: minimizeControl
+ * Hide all the contents of the control, shrink the size,
+ * add the maximize icon
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ minimizeControl: function(e) {
+
+ // to minimize the control we set its div's width
+ // and height to 0px, we cannot just set "display"
+ // to "none" because it would hide the maximize
+ // div
+ this.div.style.width = "0px";
+ this.div.style.height = "0px";
+
+ this.showControls(true);
+
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: showControls
+ * Hide/Show all LayerSwitcher controls depending on whether we are
+ * minimized or not
+ *
+ * Parameters:
+ * minimize - {Boolean}
+ */
+ showControls: function(minimize) {
+
+ this.maximizeDiv.style.display = minimize ? "" : "none";
+ this.minimizeDiv.style.display = minimize ? "none" : "";
+
+ this.layersDiv.style.display = minimize ? "none" : "";
+ },
+
+ /**
+ * Method: loadContents
+ * Set up the labels and divs for the control
+ */
+ loadContents: function() {
+
+ // layers list div
+ this.layersDiv = document.createElement("div");
+ this.layersDiv.id = this.id + "_layersDiv";
+ OpenLayers.Element.addClass(this.layersDiv, "layersDiv");
+
+ this.baseLbl = document.createElement("div");
+ this.baseLbl.innerHTML = OpenLayers.i18n("Base Layer");
+ OpenLayers.Element.addClass(this.baseLbl, "baseLbl");
+
+ this.baseLayersDiv = document.createElement("div");
+ OpenLayers.Element.addClass(this.baseLayersDiv, "baseLayersDiv");
+
+ this.dataLbl = document.createElement("div");
+ this.dataLbl.innerHTML = OpenLayers.i18n("Overlays");
+ OpenLayers.Element.addClass(this.dataLbl, "dataLbl");
+
+ this.dataLayersDiv = document.createElement("div");
+ OpenLayers.Element.addClass(this.dataLayersDiv, "dataLayersDiv");
+
+ if (this.ascending) {
+ this.layersDiv.appendChild(this.baseLbl);
+ this.layersDiv.appendChild(this.baseLayersDiv);
+ this.layersDiv.appendChild(this.dataLbl);
+ this.layersDiv.appendChild(this.dataLayersDiv);
+ } else {
+ this.layersDiv.appendChild(this.dataLbl);
+ this.layersDiv.appendChild(this.dataLayersDiv);
+ this.layersDiv.appendChild(this.baseLbl);
+ this.layersDiv.appendChild(this.baseLayersDiv);
+ }
+
+ this.div.appendChild(this.layersDiv);
+
+ // maximize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
+ this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MaximizeDiv",
+ null,
+ null,
+ img,
+ "absolute");
+ OpenLayers.Element.addClass(this.maximizeDiv, "maximizeDiv olButton");
+ this.maximizeDiv.style.display = "none";
+
+ this.div.appendChild(this.maximizeDiv);
+
+ // minimize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
+ this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MinimizeDiv",
+ null,
+ null,
+ img,
+ "absolute");
+ OpenLayers.Element.addClass(this.minimizeDiv, "minimizeDiv olButton");
+ this.minimizeDiv.style.display = "none";
+
+ this.div.appendChild(this.minimizeDiv);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
+});
diff --git a/misc/openlayers/OpenLayers.light.js b/misc/openlayers/OpenLayers.light.js
new file mode 100644
index 0000000..a83b2ed
--- /dev/null
+++ b/misc/openlayers/OpenLayers.light.js
@@ -0,0 +1,592 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+var OpenLayers={VERSION_NUMBER:"Release 2.13.1",singleFile:!0,_getScriptLocation:function(){for(var a=/(^|(.*?\/))(OpenLayers[^\/]*?\.js)(\?|$)/,b=document.getElementsByTagName("script"),c,d="",e=0,f=b.length;e<f;e++)if(c=b[e].getAttribute("src"))if(c=c.match(a)){d=c[1];break}return function(){return d}}(),ImgPath:""};OpenLayers.String={startsWith:function(a,b){return 0==a.indexOf(b)},contains:function(a,b){return-1!=a.indexOf(b)},trim:function(a){return a.replace(/^\s\s*/,"").replace(/\s\s*$/,"")},camelize:function(a){a=a.split("-");for(var b=a[0],c=1,d=a.length;c<d;c++)var e=a[c],b=b+(e.charAt(0).toUpperCase()+e.substring(1));return b},format:function(a,b,c){b||(b=window);return a.replace(OpenLayers.String.tokenRegEx,function(a,e){for(var f,g=e.split(/\.+/),h=0;h<g.length;h++){0==h&&(f=b);if(void 0===f)break;
+f=f[g[h]]}"function"==typeof f&&(f=c?f.apply(null,c):f());return"undefined"==typeof f?"undefined":f})},tokenRegEx:/\$\{([\w.]+?)\}/g,numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(a){return OpenLayers.String.numberRegEx.test(a)},numericIf:function(a,b){var c=a;!0===b&&(null!=a&&a.replace)&&(a=a.replace(/^\s*|\s*$/g,""));return OpenLayers.String.isNumeric(a)?parseFloat(a):c}};
+OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(a,b){var c=0;0<b&&(c=parseFloat(a.toPrecision(b)));return c},format:function(a,b,c,d){b="undefined"!=typeof b?b:0;c="undefined"!=typeof c?c:OpenLayers.Number.thousandsSeparator;d="undefined"!=typeof d?d:OpenLayers.Number.decimalSeparator;null!=b&&(a=parseFloat(a.toFixed(b)));var e=a.toString().split(".");1==e.length&&null==b&&(b=0);a=e[0];if(c)for(var f=/(-?[0-9]+)([0-9]{3})/;f.test(a);)a=a.replace(f,"$1"+c+"$2");
+0==b?b=a:(c=1<e.length?e[1]:"0",null!=b&&(c+=Array(b-c.length+1).join("0")),b=a+d+c);return b},zeroPad:function(a,b,c){for(a=a.toString(c||10);a.length<b;)a="0"+a;return a}};
+OpenLayers.Function={bind:function(a,b){var c=Array.prototype.slice.apply(arguments,[2]);return function(){var d=c.concat(Array.prototype.slice.apply(arguments,[0]));return a.apply(b,d)}},bindAsEventListener:function(a,b){return function(c){return a.call(b,c||window.event)}},False:function(){return!1},True:function(){return!0},Void:function(){}};
+OpenLayers.Array={filter:function(a,b,c){var d=[];if(Array.prototype.filter)d=a.filter(b,c);else{var e=a.length;if("function"!=typeof b)throw new TypeError;for(var f=0;f<e;f++)if(f in a){var g=a[f];b.call(c,g,f,a)&&d.push(g)}}return d}};OpenLayers.Class=function(){var a=arguments.length,b=arguments[0],c=arguments[a-1],d="function"==typeof c.initialize?c.initialize:function(){b.prototype.initialize.apply(this,arguments)};1<a?(a=[d,b].concat(Array.prototype.slice.call(arguments).slice(1,a-1),c),OpenLayers.inherit.apply(null,a)):d.prototype=c;return d};
+OpenLayers.inherit=function(a,b){var c=function(){};c.prototype=b.prototype;a.prototype=new c;var d,e,c=2;for(d=arguments.length;c<d;c++)e=arguments[c],"function"===typeof e&&(e=e.prototype),OpenLayers.Util.extend(a.prototype,e)};OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.extend=function(a,b){a=a||{};if(b){for(var c in b){var d=b[c];void 0!==d&&(a[c]=d)}"function"==typeof window.Event&&b instanceof window.Event||(!b.hasOwnProperty||!b.hasOwnProperty("toString"))||(a.toString=b.toString)}return a};OpenLayers.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,centerLonLat:null,initialize:function(a,b,c,d){OpenLayers.Util.isArray(a)&&(d=a[3],c=a[2],b=a[1],a=a[0]);null!=a&&(this.left=OpenLayers.Util.toFloat(a));null!=b&&(this.bottom=OpenLayers.Util.toFloat(b));null!=c&&(this.right=OpenLayers.Util.toFloat(c));null!=d&&(this.top=OpenLayers.Util.toFloat(d))},clone:function(){return new OpenLayers.Bounds(this.left,this.bottom,this.right,this.top)},equals:function(a){var b=!1;null!=
+a&&(b=this.left==a.left&&this.right==a.right&&this.top==a.top&&this.bottom==a.bottom);return b},toString:function(){return[this.left,this.bottom,this.right,this.top].join()},toArray:function(a){return!0===a?[this.bottom,this.left,this.top,this.right]:[this.left,this.bottom,this.right,this.top]},toBBOX:function(a,b){null==a&&(a=6);var c=Math.pow(10,a),d=Math.round(this.left*c)/c,e=Math.round(this.bottom*c)/c,f=Math.round(this.right*c)/c,c=Math.round(this.top*c)/c;return!0===b?e+","+d+","+c+","+f:d+
+","+e+","+f+","+c},toGeometry:function(){return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(this.left,this.bottom),new OpenLayers.Geometry.Point(this.right,this.bottom),new OpenLayers.Geometry.Point(this.right,this.top),new OpenLayers.Geometry.Point(this.left,this.top)])])},getWidth:function(){return this.right-this.left},getHeight:function(){return this.top-this.bottom},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight())},
+getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2)},getCenterLonLat:function(){this.centerLonLat||(this.centerLonLat=new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2));return this.centerLonLat},scale:function(a,b){null==b&&(b=this.getCenterLonLat());var c,d;"OpenLayers.LonLat"==b.CLASS_NAME?(c=b.lon,d=b.lat):(c=b.x,d=b.y);return new OpenLayers.Bounds((this.left-c)*a+c,(this.bottom-d)*a+d,(this.right-c)*a+c,(this.top-d)*a+
+d)},add:function(a,b){if(null==a||null==b)throw new TypeError("Bounds.add cannot receive null values");return new OpenLayers.Bounds(this.left+a,this.bottom+b,this.right+a,this.top+b)},extend:function(a){if(a)switch(a.CLASS_NAME){case "OpenLayers.LonLat":this.extendXY(a.lon,a.lat);break;case "OpenLayers.Geometry.Point":this.extendXY(a.x,a.y);break;case "OpenLayers.Bounds":this.centerLonLat=null;if(null==this.left||a.left<this.left)this.left=a.left;if(null==this.bottom||a.bottom<this.bottom)this.bottom=
+a.bottom;if(null==this.right||a.right>this.right)this.right=a.right;if(null==this.top||a.top>this.top)this.top=a.top}},extendXY:function(a,b){this.centerLonLat=null;if(null==this.left||a<this.left)this.left=a;if(null==this.bottom||b<this.bottom)this.bottom=b;if(null==this.right||a>this.right)this.right=a;if(null==this.top||b>this.top)this.top=b},containsLonLat:function(a,b){"boolean"===typeof b&&(b={inclusive:b});b=b||{};var c=this.contains(a.lon,a.lat,b.inclusive),d=b.worldBounds;d&&!c&&(c=d.getWidth(),
+d=Math.round((a.lon-(d.left+d.right)/2)/c),c=this.containsLonLat({lon:a.lon-d*c,lat:a.lat},{inclusive:b.inclusive}));return c},containsPixel:function(a,b){return this.contains(a.x,a.y,b)},contains:function(a,b,c){null==c&&(c=!0);if(null==a||null==b)return!1;a=OpenLayers.Util.toFloat(a);b=OpenLayers.Util.toFloat(b);var d=!1;return d=c?a>=this.left&&a<=this.right&&b>=this.bottom&&b<=this.top:a>this.left&&a<this.right&&b>this.bottom&&b<this.top},intersectsBounds:function(a,b){"boolean"===typeof b&&(b=
+{inclusive:b});b=b||{};if(b.worldBounds){var c=this.wrapDateLine(b.worldBounds);a=a.wrapDateLine(b.worldBounds)}else c=this;null==b.inclusive&&(b.inclusive=!0);var d=!1,e=c.left==a.right||c.right==a.left||c.top==a.bottom||c.bottom==a.top;if(b.inclusive||!e)var d=a.top>=c.bottom&&a.top<=c.top||c.top>a.bottom&&c.top<a.top,e=a.left>=c.left&&a.left<=c.right||c.left>=a.left&&c.left<=a.right,f=a.right>=c.left&&a.right<=c.right||c.right>=a.left&&c.right<=a.right,d=(a.bottom>=c.bottom&&a.bottom<=c.top||c.bottom>=
+a.bottom&&c.bottom<=a.top||d)&&(e||f);if(b.worldBounds&&!d){var g=b.worldBounds,e=g.getWidth(),f=!g.containsBounds(c),g=!g.containsBounds(a);f&&!g?(a=a.add(-e,0),d=c.intersectsBounds(a,{inclusive:b.inclusive})):g&&!f&&(c=c.add(-e,0),d=a.intersectsBounds(c,{inclusive:b.inclusive}))}return d},containsBounds:function(a,b,c){null==b&&(b=!1);null==c&&(c=!0);var d=this.contains(a.left,a.bottom,c),e=this.contains(a.right,a.bottom,c),f=this.contains(a.left,a.top,c);a=this.contains(a.right,a.top,c);return b?
+d||e||f||a:d&&e&&f&&a},determineQuadrant:function(a){var b="",c=this.getCenterLonLat(),b=b+(a.lat<c.lat?"b":"t");return b+=a.lon<c.lon?"l":"r"},transform:function(a,b){this.centerLonLat=null;var c=OpenLayers.Projection.transform({x:this.left,y:this.bottom},a,b),d=OpenLayers.Projection.transform({x:this.right,y:this.bottom},a,b),e=OpenLayers.Projection.transform({x:this.left,y:this.top},a,b),f=OpenLayers.Projection.transform({x:this.right,y:this.top},a,b);this.left=Math.min(c.x,e.x);this.bottom=Math.min(c.y,
+d.y);this.right=Math.max(d.x,f.x);this.top=Math.max(e.y,f.y);return this},wrapDateLine:function(a,b){b=b||{};var c=b.leftTolerance||0,d=b.rightTolerance||0,e=this.clone();if(a){for(var f=a.getWidth();e.left<a.left&&e.right-d<=a.left;)e=e.add(f,0);for(;e.left+c>=a.right&&e.right>a.right;)e=e.add(-f,0);c=e.left+c;c<a.right&&(c>a.left&&e.right-d>a.right)&&(e=e.add(-f,0))}return e},CLASS_NAME:"OpenLayers.Bounds"});
+OpenLayers.Bounds.fromString=function(a,b){var c=a.split(",");return OpenLayers.Bounds.fromArray(c,b)};OpenLayers.Bounds.fromArray=function(a,b){return!0===b?new OpenLayers.Bounds(a[1],a[0],a[3],a[2]):new OpenLayers.Bounds(a[0],a[1],a[2],a[3])};OpenLayers.Bounds.fromSize=function(a){return new OpenLayers.Bounds(0,a.h,a.w,0)};OpenLayers.Bounds.oppositeQuadrant=function(a){var b;b=""+("t"==a.charAt(0)?"b":"t");return b+="l"==a.charAt(1)?"r":"l"};OpenLayers.Element={visible:function(a){return"none"!=OpenLayers.Util.getElement(a).style.display},toggle:function(){for(var a=0,b=arguments.length;a<b;a++){var c=OpenLayers.Util.getElement(arguments[a]),d=OpenLayers.Element.visible(c)?"none":"";c.style.display=d}},remove:function(a){a=OpenLayers.Util.getElement(a);a.parentNode.removeChild(a)},getHeight:function(a){a=OpenLayers.Util.getElement(a);return a.offsetHeight},hasClass:function(a,b){var c=a.className;return!!c&&RegExp("(^|\\s)"+b+"(\\s|$)").test(c)},
+addClass:function(a,b){OpenLayers.Element.hasClass(a,b)||(a.className+=(a.className?" ":"")+b);return a},removeClass:function(a,b){var c=a.className;c&&(a.className=OpenLayers.String.trim(c.replace(RegExp("(^|\\s+)"+b+"(\\s+|$)")," ")));return a},toggleClass:function(a,b){OpenLayers.Element.hasClass(a,b)?OpenLayers.Element.removeClass(a,b):OpenLayers.Element.addClass(a,b);return a},getStyle:function(a,b){a=OpenLayers.Util.getElement(a);var c=null;if(a&&a.style){c=a.style[OpenLayers.String.camelize(b)];
+c||(document.defaultView&&document.defaultView.getComputedStyle?c=(c=document.defaultView.getComputedStyle(a,null))?c.getPropertyValue(b):null:a.currentStyle&&(c=a.currentStyle[OpenLayers.String.camelize(b)]));var d=["left","top","right","bottom"];window.opera&&(-1!=OpenLayers.Util.indexOf(d,b)&&"static"==OpenLayers.Element.getStyle(a,"position"))&&(c="auto")}return"auto"==c?null:c}};OpenLayers.LonLat=OpenLayers.Class({lon:0,lat:0,initialize:function(a,b){OpenLayers.Util.isArray(a)&&(b=a[1],a=a[0]);this.lon=OpenLayers.Util.toFloat(a);this.lat=OpenLayers.Util.toFloat(b)},toString:function(){return"lon="+this.lon+",lat="+this.lat},toShortString:function(){return this.lon+", "+this.lat},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat)},add:function(a,b){if(null==a||null==b)throw new TypeError("LonLat.add cannot receive null values");return new OpenLayers.LonLat(this.lon+
+OpenLayers.Util.toFloat(a),this.lat+OpenLayers.Util.toFloat(b))},equals:function(a){var b=!1;null!=a&&(b=this.lon==a.lon&&this.lat==a.lat||isNaN(this.lon)&&isNaN(this.lat)&&isNaN(a.lon)&&isNaN(a.lat));return b},transform:function(a,b){var c=OpenLayers.Projection.transform({x:this.lon,y:this.lat},a,b);this.lon=c.x;this.lat=c.y;return this},wrapDateLine:function(a){var b=this.clone();if(a){for(;b.lon<a.left;)b.lon+=a.getWidth();for(;b.lon>a.right;)b.lon-=a.getWidth()}return b},CLASS_NAME:"OpenLayers.LonLat"});
+OpenLayers.LonLat.fromString=function(a){a=a.split(",");return new OpenLayers.LonLat(a[0],a[1])};OpenLayers.LonLat.fromArray=function(a){var b=OpenLayers.Util.isArray(a);return new OpenLayers.LonLat(b&&a[0],b&&a[1])};OpenLayers.Pixel=OpenLayers.Class({x:0,y:0,initialize:function(a,b){this.x=parseFloat(a);this.y=parseFloat(b)},toString:function(){return"x="+this.x+",y="+this.y},clone:function(){return new OpenLayers.Pixel(this.x,this.y)},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},distanceTo:function(a){return Math.sqrt(Math.pow(this.x-a.x,2)+Math.pow(this.y-a.y,2))},add:function(a,b){if(null==a||null==b)throw new TypeError("Pixel.add cannot receive null values");
+return new OpenLayers.Pixel(this.x+a,this.y+b)},offset:function(a){var b=this.clone();a&&(b=this.add(a.x,a.y));return b},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Size=OpenLayers.Class({w:0,h:0,initialize:function(a,b){this.w=parseFloat(a);this.h=parseFloat(b)},toString:function(){return"w="+this.w+",h="+this.h},clone:function(){return new OpenLayers.Size(this.w,this.h)},equals:function(a){var b=!1;null!=a&&(b=this.w==a.w&&this.h==a.h||isNaN(this.w)&&isNaN(this.h)&&isNaN(a.w)&&isNaN(a.h));return b},CLASS_NAME:"OpenLayers.Size"});OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},userError:function(a){alert(a)},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};
+(function(){for(var a=document.getElementsByTagName("script"),b=0,c=a.length;b<c;++b)if(-1!=a[b].src.indexOf("firebug.js")&&console){OpenLayers.Util.extend(OpenLayers.Console,console);break}})();OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){OpenLayers.Lang.code||OpenLayers.Lang.setCode();return OpenLayers.Lang.code},setCode:function(a){var b;a||(a="msie"==OpenLayers.BROWSER_NAME?navigator.userLanguage:navigator.language);a=a.split("-");a[0]=a[0].toLowerCase();"object"==typeof OpenLayers.Lang[a[0]]&&(b=a[0]);if(a[1]){var c=a[0]+"-"+a[1].toUpperCase();"object"==typeof OpenLayers.Lang[c]&&(b=c)}b||(OpenLayers.Console.warn("Failed to find OpenLayers.Lang."+a.join("-")+" dictionary, falling back to default language"),
+b=OpenLayers.Lang.defaultCode);OpenLayers.Lang.code=b},translate:function(a,b){var c=OpenLayers.Lang[OpenLayers.Lang.getCode()];(c=c&&c[a])||(c=a);b&&(c=OpenLayers.String.format(c,b));return c}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.getElement=function(){for(var a=[],b=0,c=arguments.length;b<c;b++){var d=arguments[b];"string"==typeof d&&(d=document.getElementById(d));if(1==arguments.length)return d;a.push(d)}return a};OpenLayers.Util.isElement=function(a){return!(!a||1!==a.nodeType)};OpenLayers.Util.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)};OpenLayers.Util.removeItem=function(a,b){for(var c=a.length-1;0<=c;c--)a[c]==b&&a.splice(c,1);return a};
+OpenLayers.Util.indexOf=function(a,b){if("function"==typeof a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;c<d;c++)if(a[c]==b)return c;return-1};OpenLayers.Util.dotless=/\./g;
+OpenLayers.Util.modifyDOMElement=function(a,b,c,d,e,f,g,h){b&&(a.id=b.replace(OpenLayers.Util.dotless,"_"));c&&(a.style.left=c.x+"px",a.style.top=c.y+"px");d&&(a.style.width=d.w+"px",a.style.height=d.h+"px");e&&(a.style.position=e);f&&(a.style.border=f);g&&(a.style.overflow=g);0<=parseFloat(h)&&1>parseFloat(h)?(a.style.filter="alpha(opacity="+100*h+")",a.style.opacity=h):1==parseFloat(h)&&(a.style.filter="",a.style.opacity="")};
+OpenLayers.Util.createDiv=function(a,b,c,d,e,f,g,h){var k=document.createElement("div");d&&(k.style.backgroundImage="url("+d+")");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="absolute");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,g,h);return k};
+OpenLayers.Util.createImage=function(a,b,c,d,e,f,g,h){var k=document.createElement("img");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="relative");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,null,g);h&&(k.style.display="none",b=function(){k.style.display="";OpenLayers.Event.stopObservingElement(k)},OpenLayers.Event.observe(k,"load",b),OpenLayers.Event.observe(k,"error",b));k.style.alt=a;k.galleryImg="no";d&&(k.src=d);return k};OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;
+OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(null==OpenLayers.Util.alphaHackNeeded){var a=navigator.appVersion.split("MSIE"),a=parseFloat(a[1]),b=!1;try{b=!!document.body.filters}catch(c){}OpenLayers.Util.alphaHackNeeded=b&&5.5<=a&&7>a}return OpenLayers.Util.alphaHackNeeded};
+OpenLayers.Util.modifyAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){OpenLayers.Util.modifyDOMElement(a,b,c,d,f,null,null,k);b=a.childNodes[0];e&&(b.src=e);OpenLayers.Util.modifyDOMElement(b,a.id+"_innerImage",null,d,"relative",g);OpenLayers.Util.alphaHack()&&("none"!=a.style.display&&(a.style.display="inline-block"),null==h&&(h="scale"),a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+b.src+"', sizingMethod='"+h+"')",0<=parseFloat(a.style.opacity)&&1>parseFloat(a.style.opacity)&&
+(a.style.filter+=" alpha(opacity="+100*a.style.opacity+")"),b.style.filter="alpha(opacity=0)")};OpenLayers.Util.createAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){var l=OpenLayers.Util.createDiv();k=OpenLayers.Util.createImage(null,null,null,null,null,null,null,k);k.className="olAlphaImg";l.appendChild(k);OpenLayers.Util.modifyAlphaImageDiv(l,a,b,c,d,e,f,g,h);return l};OpenLayers.Util.upperCaseObject=function(a){var b={},c;for(c in a)b[c.toUpperCase()]=a[c];return b};
+OpenLayers.Util.applyDefaults=function(a,b){a=a||{};var c="function"==typeof window.Event&&b instanceof window.Event,d;for(d in b)if(void 0===a[d]||!c&&b.hasOwnProperty&&b.hasOwnProperty(d)&&!a.hasOwnProperty(d))a[d]=b[d];!c&&(b&&b.hasOwnProperty&&b.hasOwnProperty("toString")&&!a.hasOwnProperty("toString"))&&(a.toString=b.toString);return a};
+OpenLayers.Util.getParameterString=function(a){var b=[],c;for(c in a){var d=a[c];if(null!=d&&"function"!=typeof d){if("object"==typeof d&&d.constructor==Array){for(var e=[],f,g=0,h=d.length;g<h;g++)f=d[g],e.push(encodeURIComponent(null===f||void 0===f?"":f));d=e.join(",")}else d=encodeURIComponent(d);b.push(encodeURIComponent(c)+"="+d)}}return b.join("&")};OpenLayers.Util.urlAppend=function(a,b){var c=a;if(b)var d=(a+" ").split(/[?&]/),c=c+(" "===d.pop()?b:d.length?"&"+b:"?"+b);return c};
+OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||OpenLayers._getScriptLocation()+"img/"};OpenLayers.Util.getImageLocation=function(a){return OpenLayers.Util.getImagesLocation()+a};OpenLayers.Util.Try=function(){for(var a=null,b=0,c=arguments.length;b<c;b++){var d=arguments[b];try{a=d();break}catch(e){}}return a};
+OpenLayers.Util.getXmlNodeValue=function(a){var b=null;OpenLayers.Util.Try(function(){b=a.text;b||(b=a.textContent);b||(b=a.firstChild.nodeValue)},function(){b=a.textContent});return b};OpenLayers.Util.mouseLeft=function(a,b){for(var c=a.relatedTarget?a.relatedTarget:a.toElement;c!=b&&null!=c;)c=c.parentNode;return c!=b};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(a,b){null==b&&(b=OpenLayers.Util.DEFAULT_PRECISION);"number"!==typeof a&&(a=parseFloat(a));return 0===b?a:parseFloat(a.toPrecision(b))};
+OpenLayers.Util.rad=function(a){return a*Math.PI/180};OpenLayers.Util.deg=function(a){return 180*a/Math.PI};OpenLayers.Util.VincentyConstants={a:6378137,b:6356752.3142,f:1/298.257223563};
+OpenLayers.Util.distVincenty=function(a,b){for(var c=OpenLayers.Util.VincentyConstants,d=c.a,e=c.b,c=c.f,f=OpenLayers.Util.rad(b.lon-a.lon),g=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(a.lat))),h=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(b.lat))),k=Math.sin(g),g=Math.cos(g),l=Math.sin(h),h=Math.cos(h),m=f,r=2*Math.PI,p=20;1E-12<Math.abs(m-r)&&0<--p;){var n=Math.sin(m),q=Math.cos(m),s=Math.sqrt(h*n*h*n+(g*l-k*h*q)*(g*l-k*h*q));if(0==s)return 0;var q=k*l+g*h*q,t=Math.atan2(s,q),u=Math.asin(g*h*
+n/s),v=Math.cos(u)*Math.cos(u),n=q-2*k*l/v,w=c/16*v*(4+c*(4-3*v)),r=m,m=f+(1-w)*c*Math.sin(u)*(t+w*s*(n+w*q*(-1+2*n*n)))}if(0==p)return NaN;d=v*(d*d-e*e)/(e*e);c=d/1024*(256+d*(-128+d*(74-47*d)));return(e*(1+d/16384*(4096+d*(-768+d*(320-175*d))))*(t-c*s*(n+c/4*(q*(-1+2*n*n)-c/6*n*(-3+4*s*s)*(-3+4*n*n))))).toFixed(3)/1E3};
+OpenLayers.Util.destinationVincenty=function(a,b,c){var d=OpenLayers.Util,e=d.VincentyConstants,f=e.a,g=e.b,h=e.f,e=a.lon;a=a.lat;var k=d.rad(b);b=Math.sin(k);k=Math.cos(k);a=(1-h)*Math.tan(d.rad(a));var l=1/Math.sqrt(1+a*a),m=a*l,r=Math.atan2(a,k);a=l*b;for(var p=1-a*a,f=p*(f*f-g*g)/(g*g),n=1+f/16384*(4096+f*(-768+f*(320-175*f))),q=f/1024*(256+f*(-128+f*(74-47*f))),f=c/(g*n),s=2*Math.PI;1E-12<Math.abs(f-s);)var t=Math.cos(2*r+f),u=Math.sin(f),v=Math.cos(f),w=q*u*(t+q/4*(v*(-1+2*t*t)-q/6*t*(-3+4*
+u*u)*(-3+4*t*t))),s=f,f=c/(g*n)+w;c=m*u-l*v*k;g=Math.atan2(m*v+l*u*k,(1-h)*Math.sqrt(a*a+c*c));b=Math.atan2(u*b,l*v-m*u*k);k=h/16*p*(4+h*(4-3*p));t=b-(1-k)*h*a*(f+k*u*(t+k*v*(-1+2*t*t)));Math.atan2(a,-c);return new OpenLayers.LonLat(e+d.deg(t),d.deg(g))};
+OpenLayers.Util.getParameters=function(a,b){b=b||{};a=null===a||void 0===a?window.location.href:a;var c="";if(OpenLayers.String.contains(a,"?"))var d=a.indexOf("?")+1,c=OpenLayers.String.contains(a,"#")?a.indexOf("#"):a.length,c=a.substring(d,c);for(var d={},c=c.split(/[&;]/),e=0,f=c.length;e<f;++e){var g=c[e].split("=");if(g[0]){var h=g[0];try{h=decodeURIComponent(h)}catch(k){h=unescape(h)}g=(g[1]||"").replace(/\+/g," ");try{g=decodeURIComponent(g)}catch(l){g=unescape(g)}!1!==b.splitArgs&&(g=g.split(","));
+1==g.length&&(g=g[0]);d[h]=g}}return d};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(a){a=null==a?"id_":a.replace(OpenLayers.Util.dotless,"_");OpenLayers.Util.lastSeqID+=1;return a+OpenLayers.Util.lastSeqID};OpenLayers.INCHES_PER_UNIT={inches:1,ft:12,mi:63360,m:39.37,km:39370,dd:4374754,yd:36};OpenLayers.INCHES_PER_UNIT["in"]=OpenLayers.INCHES_PER_UNIT.inches;OpenLayers.INCHES_PER_UNIT.degrees=OpenLayers.INCHES_PER_UNIT.dd;OpenLayers.INCHES_PER_UNIT.nmi=1852*OpenLayers.INCHES_PER_UNIT.m;
+OpenLayers.METERS_PER_INCH=0.0254000508001016;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{Inch:OpenLayers.INCHES_PER_UNIT.inches,Meter:1/OpenLayers.METERS_PER_INCH,Foot:0.3048006096012192/OpenLayers.METERS_PER_INCH,IFoot:0.3048/OpenLayers.METERS_PER_INCH,ClarkeFoot:0.3047972651151/OpenLayers.METERS_PER_INCH,SearsFoot:0.30479947153867626/OpenLayers.METERS_PER_INCH,GoldCoastFoot:0.3047997101815088/OpenLayers.METERS_PER_INCH,IInch:0.0254/OpenLayers.METERS_PER_INCH,MicroInch:2.54E-5/OpenLayers.METERS_PER_INCH,Mil:2.54E-8/OpenLayers.METERS_PER_INCH,
+Centimeter:0.01/OpenLayers.METERS_PER_INCH,Kilometer:1E3/OpenLayers.METERS_PER_INCH,Yard:0.9144018288036576/OpenLayers.METERS_PER_INCH,SearsYard:0.914398414616029/OpenLayers.METERS_PER_INCH,IndianYard:0.9143985307444408/OpenLayers.METERS_PER_INCH,IndianYd37:0.91439523/OpenLayers.METERS_PER_INCH,IndianYd62:0.9143988/OpenLayers.METERS_PER_INCH,IndianYd75:0.9143985/OpenLayers.METERS_PER_INCH,IndianFoot:0.30479951/OpenLayers.METERS_PER_INCH,IndianFt37:0.30479841/OpenLayers.METERS_PER_INCH,IndianFt62:0.3047996/
+OpenLayers.METERS_PER_INCH,IndianFt75:0.3047995/OpenLayers.METERS_PER_INCH,Mile:1609.3472186944373/OpenLayers.METERS_PER_INCH,IYard:0.9144/OpenLayers.METERS_PER_INCH,IMile:1609.344/OpenLayers.METERS_PER_INCH,NautM:1852/OpenLayers.METERS_PER_INCH,"Lat-66":110943.31648893273/OpenLayers.METERS_PER_INCH,"Lat-83":110946.25736872235/OpenLayers.METERS_PER_INCH,Decimeter:0.1/OpenLayers.METERS_PER_INCH,Millimeter:0.001/OpenLayers.METERS_PER_INCH,Dekameter:10/OpenLayers.METERS_PER_INCH,Decameter:10/OpenLayers.METERS_PER_INCH,
+Hectometer:100/OpenLayers.METERS_PER_INCH,GermanMeter:1.0000135965/OpenLayers.METERS_PER_INCH,CaGrid:0.999738/OpenLayers.METERS_PER_INCH,ClarkeChain:20.1166194976/OpenLayers.METERS_PER_INCH,GunterChain:20.11684023368047/OpenLayers.METERS_PER_INCH,BenoitChain:20.116782494375872/OpenLayers.METERS_PER_INCH,SearsChain:20.11676512155/OpenLayers.METERS_PER_INCH,ClarkeLink:0.201166194976/OpenLayers.METERS_PER_INCH,GunterLink:0.2011684023368047/OpenLayers.METERS_PER_INCH,BenoitLink:0.20116782494375873/OpenLayers.METERS_PER_INCH,
+SearsLink:0.2011676512155/OpenLayers.METERS_PER_INCH,Rod:5.02921005842012/OpenLayers.METERS_PER_INCH,IntnlChain:20.1168/OpenLayers.METERS_PER_INCH,IntnlLink:0.201168/OpenLayers.METERS_PER_INCH,Perch:5.02921005842012/OpenLayers.METERS_PER_INCH,Pole:5.02921005842012/OpenLayers.METERS_PER_INCH,Furlong:201.1684023368046/OpenLayers.METERS_PER_INCH,Rood:3.778266898/OpenLayers.METERS_PER_INCH,CapeFoot:0.3047972615/OpenLayers.METERS_PER_INCH,Brealey:375/OpenLayers.METERS_PER_INCH,ModAmFt:0.304812252984506/
+OpenLayers.METERS_PER_INCH,Fathom:1.8288/OpenLayers.METERS_PER_INCH,"NautM-UK":1853.184/OpenLayers.METERS_PER_INCH,"50kilometers":5E4/OpenLayers.METERS_PER_INCH,"150kilometers":15E4/OpenLayers.METERS_PER_INCH});
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{mm:OpenLayers.INCHES_PER_UNIT.Meter/1E3,cm:OpenLayers.INCHES_PER_UNIT.Meter/100,dm:100*OpenLayers.INCHES_PER_UNIT.Meter,km:1E3*OpenLayers.INCHES_PER_UNIT.Meter,kmi:OpenLayers.INCHES_PER_UNIT.nmi,fath:OpenLayers.INCHES_PER_UNIT.Fathom,ch:OpenLayers.INCHES_PER_UNIT.IntnlChain,link:OpenLayers.INCHES_PER_UNIT.IntnlLink,"us-in":OpenLayers.INCHES_PER_UNIT.inches,"us-ft":OpenLayers.INCHES_PER_UNIT.Foot,"us-yd":OpenLayers.INCHES_PER_UNIT.Yard,"us-ch":OpenLayers.INCHES_PER_UNIT.GunterChain,
+"us-mi":OpenLayers.INCHES_PER_UNIT.Mile,"ind-yd":OpenLayers.INCHES_PER_UNIT.IndianYd37,"ind-ft":OpenLayers.INCHES_PER_UNIT.IndianFt37,"ind-ch":20.11669506/OpenLayers.METERS_PER_INCH});OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(a){return 1<a?1/a:a};OpenLayers.Util.getResolutionFromScale=function(a,b){var c;a&&(null==b&&(b="degrees"),c=1/(OpenLayers.Util.normalizeScale(a)*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH));return c};
+OpenLayers.Util.getScaleFromResolution=function(a,b){null==b&&(b="degrees");return a*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH};
+OpenLayers.Util.pagePosition=function(a){var b=[0,0],c=OpenLayers.Util.getViewportElement();if(!a||a==window||a==c)return b;var d=OpenLayers.IS_GECKO&&document.getBoxObjectFor&&"absolute"==OpenLayers.Element.getStyle(a,"position")&&(""==a.style.top||""==a.style.left),e=null;if(a.getBoundingClientRect)a=a.getBoundingClientRect(),e=window.pageYOffset||c.scrollTop,b[0]=a.left+(window.pageXOffset||c.scrollLeft),b[1]=a.top+e;else if(document.getBoxObjectFor&&!d)a=document.getBoxObjectFor(a),c=document.getBoxObjectFor(c),
+b[0]=a.screenX-c.screenX,b[1]=a.screenY-c.screenY;else{b[0]=a.offsetLeft;b[1]=a.offsetTop;e=a.offsetParent;if(e!=a)for(;e;)b[0]+=e.offsetLeft,b[1]+=e.offsetTop,e=e.offsetParent;c=OpenLayers.BROWSER_NAME;if("opera"==c||"safari"==c&&"absolute"==OpenLayers.Element.getStyle(a,"position"))b[1]-=document.body.offsetTop;for(e=a.offsetParent;e&&e!=document.body;){b[0]-=e.scrollLeft;if("opera"!=c||"TR"!=e.tagName)b[1]-=e.scrollTop;e=e.offsetParent}}return b};
+OpenLayers.Util.getViewportElement=function(){var a=arguments.callee.viewportElement;void 0==a&&(a="msie"==OpenLayers.BROWSER_NAME&&"CSS1Compat"!=document.compatMode?document.body:document.documentElement,arguments.callee.viewportElement=a);return a};
+OpenLayers.Util.isEquivalentUrl=function(a,b,c){c=c||{};OpenLayers.Util.applyDefaults(c,{ignoreCase:!0,ignorePort80:!0,ignoreHash:!0,splitArgs:!1});a=OpenLayers.Util.createUrlObject(a,c);b=OpenLayers.Util.createUrlObject(b,c);for(var d in a)if("args"!==d&&a[d]!=b[d])return!1;for(d in a.args){if(a.args[d]!=b.args[d])return!1;delete b.args[d]}for(d in b.args)return!1;return!0};
+OpenLayers.Util.createUrlObject=function(a,b){b=b||{};if(!/^\w+:\/\//.test(a)){var c=window.location,d=c.port?":"+c.port:"",d=c.protocol+"//"+c.host.split(":").shift()+d;0===a.indexOf("/")?a=d+a:(c=c.pathname.split("/"),c.pop(),a=d+c.join("/")+"/"+a)}b.ignoreCase&&(a=a.toLowerCase());c=document.createElement("a");c.href=a;d={};d.host=c.host.split(":").shift();d.protocol=c.protocol;d.port=b.ignorePort80?"80"==c.port||"0"==c.port?"":c.port:""==c.port||"0"==c.port?"80":c.port;d.hash=b.ignoreHash||"#"===
+c.hash?"":c.hash;var e=c.search;e||(e=a.indexOf("?"),e=-1!=e?a.substr(e):"");d.args=OpenLayers.Util.getParameters(e,{splitArgs:b.splitArgs});d.pathname="/"==c.pathname.charAt(0)?c.pathname:"/"+c.pathname;return d};OpenLayers.Util.removeTail=function(a){var b=null,b=a.indexOf("?"),c=a.indexOf("#");return b=-1==b?-1!=c?a.substr(0,c):a:-1!=c?a.substr(0,Math.min(b,c)):a.substr(0,b)};OpenLayers.IS_GECKO=function(){var a=navigator.userAgent.toLowerCase();return-1==a.indexOf("webkit")&&-1!=a.indexOf("gecko")}();
+OpenLayers.CANVAS_SUPPORTED=function(){var a=document.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))}();OpenLayers.BROWSER_NAME=function(){var a="",b=navigator.userAgent.toLowerCase();-1!=b.indexOf("opera")?a="opera":-1!=b.indexOf("msie")?a="msie":-1!=b.indexOf("safari")?a="safari":-1!=b.indexOf("mozilla")&&(a=-1!=b.indexOf("firefox")?"firefox":"mozilla");return a}();OpenLayers.Util.getBrowserName=function(){return OpenLayers.BROWSER_NAME};
+OpenLayers.Util.getRenderedDimensions=function(a,b,c){var d,e,f=document.createElement("div");f.style.visibility="hidden";for(var g=c&&c.containerElement?c.containerElement:document.body,h=!1,k=null,l=g;l&&"body"!=l.tagName.toLowerCase();){var m=OpenLayers.Element.getStyle(l,"position");if("absolute"==m){h=!0;break}else if(m&&"static"!=m)break;l=l.parentNode}!h||0!==g.clientHeight&&0!==g.clientWidth||(k=document.createElement("div"),k.style.visibility="hidden",k.style.position="absolute",k.style.overflow=
+"visible",k.style.width=document.body.clientWidth+"px",k.style.height=document.body.clientHeight+"px",k.appendChild(f));f.style.position="absolute";b&&(b.w?(d=b.w,f.style.width=d+"px"):b.h&&(e=b.h,f.style.height=e+"px"));c&&c.displayClass&&(f.className=c.displayClass);b=document.createElement("div");b.innerHTML=a;b.style.overflow="visible";if(b.childNodes)for(a=0,c=b.childNodes.length;a<c;a++)b.childNodes[a].style&&(b.childNodes[a].style.overflow="visible");f.appendChild(b);k?g.appendChild(k):g.appendChild(f);
+d||(d=parseInt(b.scrollWidth),f.style.width=d+"px");e||(e=parseInt(b.scrollHeight));f.removeChild(b);k?(k.removeChild(f),g.removeChild(k)):g.removeChild(f);return new OpenLayers.Size(d,e)};
+OpenLayers.Util.getScrollbarWidth=function(){var a=OpenLayers.Util._scrollbarWidth;if(null==a){var b=null,c=null,b=a=0,b=document.createElement("div");b.style.position="absolute";b.style.top="-1000px";b.style.left="-1000px";b.style.width="100px";b.style.height="50px";b.style.overflow="hidden";c=document.createElement("div");c.style.width="100%";c.style.height="200px";b.appendChild(c);document.body.appendChild(b);a=c.offsetWidth;b.style.overflow="scroll";b=c.offsetWidth;document.body.removeChild(document.body.lastChild);
+OpenLayers.Util._scrollbarWidth=a-b;a=OpenLayers.Util._scrollbarWidth}return a};
+OpenLayers.Util.getFormattedLonLat=function(a,b,c){c||(c="dms");a=(a+540)%360-180;var d=Math.abs(a),e=Math.floor(d),f=d=(d-e)/(1/60),d=Math.floor(d),f=Math.round(10*((f-d)/(1/60))),f=f/10;60<=f&&(f-=60,d+=1,60<=d&&(d-=60,e+=1));10>e&&(e="0"+e);e+="\u00b0";0<=c.indexOf("dm")&&(10>d&&(d="0"+d),e+=d+"'",0<=c.indexOf("dms")&&(10>f&&(f="0"+f),e+=f+'"'));return e="lon"==b?e+(0>a?OpenLayers.i18n("W"):OpenLayers.i18n("E")):e+(0>a?OpenLayers.i18n("S"):OpenLayers.i18n("N"))};OpenLayers.Event={observers:!1,KEY_SPACE:32,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(a){return a.target||a.srcElement},isSingleTouch:function(a){return a.touches&&1==a.touches.length},isMultiTouch:function(a){return a.touches&&1<a.touches.length},isLeftClick:function(a){return a.which&&1==a.which||a.button&&1==a.button},isRightClick:function(a){return a.which&&3==a.which||a.button&&2==a.button},stop:function(a,
+b){b||OpenLayers.Event.preventDefault(a);a.stopPropagation?a.stopPropagation():a.cancelBubble=!0},preventDefault:function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},findElement:function(a,b){for(var c=OpenLayers.Event.element(a);c.parentNode&&(!c.tagName||c.tagName.toUpperCase()!=b.toUpperCase());)c=c.parentNode;return c},observe:function(a,b,c,d){a=OpenLayers.Util.getElement(a);d=d||!1;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.attachEvent)&&(b="keydown");
+this.observers||(this.observers={});if(!a._eventCacheID){var e="eventCacheID_";a.id&&(e=a.id+"_"+e);a._eventCacheID=OpenLayers.Util.createUniqueID(e)}e=a._eventCacheID;this.observers[e]||(this.observers[e]=[]);this.observers[e].push({element:a,name:b,observer:c,useCapture:d});a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},stopObservingElement:function(a){a=OpenLayers.Util.getElement(a)._eventCacheID;this._removeElementObservers(OpenLayers.Event.observers[a])},
+_removeElementObservers:function(a){if(a)for(var b=a.length-1;0<=b;b--){var c=a[b];OpenLayers.Event.stopObserving.apply(this,[c.element,c.name,c.observer,c.useCapture])}},stopObserving:function(a,b,c,d){d=d||!1;a=OpenLayers.Util.getElement(a);var e=a._eventCacheID;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.detachEvent)&&(b="keydown");var f=!1,g=OpenLayers.Event.observers[e];if(g)for(var h=0;!f&&h<g.length;){var k=g[h];if(k.name==b&&k.observer==c&&k.useCapture==d){g.splice(h,
+1);0==g.length&&delete OpenLayers.Event.observers[e];f=!0;break}h++}f&&(a.removeEventListener?a.removeEventListener(b,c,d):a&&a.detachEvent&&a.detachEvent("on"+b,c));return f},unloadCache:function(){if(OpenLayers.Event&&OpenLayers.Event.observers){for(var a in OpenLayers.Event.observers)OpenLayers.Event._removeElementObservers.apply(this,[OpenLayers.Event.observers[a]]);OpenLayers.Event.observers=!1}},CLASS_NAME:"OpenLayers.Event"};
+OpenLayers.Event.observe(window,"unload",OpenLayers.Event.unloadCache,!1);
+OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:"mouseover mouseout mousedown mouseup mousemove click dblclick rightclick dblrightclick resize focus blur touchstart touchmove touchend keydown".split(" "),listeners:null,object:null,element:null,eventHandler:null,fallThrough:null,includeXY:!1,extensions:null,extensionCount:null,clearMouseListener:null,initialize:function(a,b,c,d,e){OpenLayers.Util.extend(this,e);this.object=a;this.fallThrough=d;this.listeners={};this.extensions={};this.extensionCount=
+{};this._msTouches=[];null!=b&&this.attachToElement(b)},destroy:function(){for(var a in this.extensions)"boolean"!==typeof this.extensions[a]&&this.extensions[a].destroy();this.extensions=null;this.element&&(OpenLayers.Event.stopObservingElement(this.element),this.element.hasScrollEvent&&OpenLayers.Event.stopObserving(window,"scroll",this.clearMouseListener));this.eventHandler=this.fallThrough=this.object=this.listeners=this.element=null},addEventType:function(a){},attachToElement:function(a){this.element?
+OpenLayers.Event.stopObservingElement(this.element):(this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this),this.clearMouseListener=OpenLayers.Function.bind(this.clearMouseCache,this));this.element=a;for(var b=!!window.navigator.msMaxTouchPoints,c,d=0,e=this.BROWSER_EVENTS.length;d<e;d++)c=this.BROWSER_EVENTS[d],OpenLayers.Event.observe(a,c,this.eventHandler),b&&0===c.indexOf("touch")&&this.addMsTouchListener(a,c,this.eventHandler);OpenLayers.Event.observe(a,"dragstart",
+OpenLayers.Event.stop)},on:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.register(b,a.scope,a[b])},register:function(a,b,c,d){a in OpenLayers.Events&&!this.extensions[a]&&(this.extensions[a]=new OpenLayers.Events[a](this));if(null!=c){null==b&&(b=this.object);var e=this.listeners[a];e||(e=[],this.listeners[a]=e,this.extensionCount[a]=0);b={obj:b,func:c};d?(e.splice(this.extensionCount[a],0,b),"object"===typeof d&&d.extension&&this.extensionCount[a]++):e.push(b)}},registerPriority:function(a,
+b,c){this.register(a,b,c,!0)},un:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.unregister(b,a.scope,a[b])},unregister:function(a,b,c){null==b&&(b=this.object);a=this.listeners[a];if(null!=a)for(var d=0,e=a.length;d<e;d++)if(a[d].obj==b&&a[d].func==c){a.splice(d,1);break}},remove:function(a){null!=this.listeners[a]&&(this.listeners[a]=[])},triggerEvent:function(a,b){var c=this.listeners[a];if(c&&0!=c.length){null==b&&(b={});b.object=this.object;b.element=this.element;b.type||(b.type=
+a);for(var c=c.slice(),d,e=0,f=c.length;e<f&&(d=c[e],d=d.func.apply(d.obj,[b]),void 0==d||!1!=d);e++);this.fallThrough||OpenLayers.Event.stop(b,!0);return d}},handleBrowserEvent:function(a){var b=a.type,c=this.listeners[b];if(c&&0!=c.length){if((c=a.touches)&&c[0]){for(var d=0,e=0,f=c.length,g,h=0;h<f;++h)g=this.getTouchClientXY(c[h]),d+=g.clientX,e+=g.clientY;a.clientX=d/f;a.clientY=e/f}this.includeXY&&(a.xy=this.getMousePosition(a));this.triggerEvent(b,a)}},getTouchClientXY:function(a){var b=window.olMockWin||
+window,c=b.pageXOffset,b=b.pageYOffset,d=a.clientX,e=a.clientY;if(0===a.pageY&&Math.floor(e)>Math.floor(a.pageY)||0===a.pageX&&Math.floor(d)>Math.floor(a.pageX))d-=c,e-=b;else if(e<a.pageY-b||d<a.pageX-c)d=a.pageX-c,e=a.pageY-b;a.olClientX=d;a.olClientY=e;return{clientX:d,clientY:e}},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null},getMousePosition:function(a){this.includeXY?this.element.hasScrollEvent||(OpenLayers.Event.observe(window,"scroll",
+this.clearMouseListener),this.element.hasScrollEvent=!0):this.clearMouseCache();if(!this.element.scrolls){var b=OpenLayers.Util.getViewportElement();this.element.scrolls=[window.pageXOffset||b.scrollLeft,window.pageYOffset||b.scrollTop]}this.element.lefttop||(this.element.lefttop=[document.documentElement.clientLeft||0,document.documentElement.clientTop||0]);this.element.offsets||(this.element.offsets=OpenLayers.Util.pagePosition(this.element));return new OpenLayers.Pixel(a.clientX+this.element.scrolls[0]-
+this.element.offsets[0]-this.element.lefttop[0],a.clientY+this.element.scrolls[1]-this.element.offsets[1]-this.element.lefttop[1])},addMsTouchListener:function(a,b,c){function d(a){c(OpenLayers.Util.applyDefaults({stopPropagation:function(){for(var a=e.length-1;0<=a;--a)e[a].stopPropagation()},preventDefault:function(){for(var a=e.length-1;0<=a;--a)e[a].preventDefault()},type:b},a))}var e=this._msTouches;switch(b){case "touchstart":return this.addMsTouchListenerStart(a,b,d);case "touchend":return this.addMsTouchListenerEnd(a,
+b,d);case "touchmove":return this.addMsTouchListenerMove(a,b,d);default:throw"Unknown touch event type";}},addMsTouchListenerStart:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerDown",function(a){for(var b=!1,g=0,h=d.length;g<h;++g)if(d[g].pointerId==a.pointerId){b=!0;break}b||d.push(a);a.touches=d.slice();c(a)});OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,c=d.length;b<c;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}})},addMsTouchListenerMove:function(a,
+b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerMove",function(a){if(a.pointerType!=a.MSPOINTER_TYPE_MOUSE||0!=a.buttons)if(1!=d.length||d[0].pageX!=a.pageX||d[0].pageY!=a.pageY){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d[b]=a;break}a.touches=d.slice();c(a)}})},addMsTouchListenerEnd:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}a.touches=
+d.slice();c(a)})},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Events.buttonclick=OpenLayers.Class({target:null,events:"mousedown mouseup click dblclick touchstart touchmove touchend keydown".split(" "),startRegEx:/^mousedown|touchstart$/,cancelRegEx:/^touchmove$/,completeRegEx:/^mouseup|touchend$/,initialize:function(a){this.target=a;for(a=this.events.length-1;0<=a;--a)this.target.register(this.events[a],this,this.buttonClick,{extension:!0})},destroy:function(){for(var a=this.events.length-1;0<=a;--a)this.target.unregister(this.events[a],this,this.buttonClick);
+delete this.target},getPressedButton:function(a){var b=3,c;do{if(OpenLayers.Element.hasClass(a,"olButton")){c=a;break}a=a.parentNode}while(0<--b&&a);return c},ignore:function(a){var b=3,c=!1;do{if("a"===a.nodeName.toLowerCase()){c=!0;break}a=a.parentNode}while(0<--b&&a);return c},buttonClick:function(a){var b=!0,c=OpenLayers.Event.element(a);if(c&&(OpenLayers.Event.isLeftClick(a)||!~a.type.indexOf("mouse")))if(c=this.getPressedButton(c)){if("keydown"===a.type)switch(a.keyCode){case OpenLayers.Event.KEY_RETURN:case OpenLayers.Event.KEY_SPACE:this.target.triggerEvent("buttonclick",
+{buttonElement:c}),OpenLayers.Event.stop(a),b=!1}else if(this.startEvt){if(this.completeRegEx.test(a.type)){var b=OpenLayers.Util.pagePosition(c),d=OpenLayers.Util.getViewportElement(),e=window.pageYOffset||d.scrollTop;b[0]-=window.pageXOffset||d.scrollLeft;b[1]-=e;this.target.triggerEvent("buttonclick",{buttonElement:c,buttonXY:{x:this.startEvt.clientX-b[0],y:this.startEvt.clientY-b[1]}})}this.cancelRegEx.test(a.type)&&delete this.startEvt;OpenLayers.Event.stop(a);b=!1}this.startRegEx.test(a.type)&&
+(this.startEvt=a,OpenLayers.Event.stop(a),b=!1)}else b=!this.ignore(OpenLayers.Event.element(a)),delete this.startEvt;return b}});OpenLayers.Util=OpenLayers.Util||{};
+OpenLayers.Util.vendorPrefix=function(){function a(a){return a?a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()}).replace(/^ms-/,"-ms-"):null}function b(a,b){if(void 0===g[b]){var c,e=0,f=d.length,p="undefined"!==typeof a.cssText;for(g[b]=null;e<f;e++)if((c=d[e])?(p||(c=c.toLowerCase()),c=c+b.charAt(0).toUpperCase()+b.slice(1)):c=b,void 0!==a[c]){g[b]=c;break}}return g[b]}function c(a){return b(e,a)}var d=["","O","ms","Moz","Webkit"],e=document.createElement("div").style,f={},g={};return{css:function(b){if(void 0===
+f[b]){var d=b.replace(/(-[\s\S])/g,function(a){return a.charAt(1).toUpperCase()}),d=c(d);f[b]=a(d)}return f[b]},js:b,style:c,cssCache:f,jsCache:g}}();OpenLayers.Animation=function(a){var b=OpenLayers.Util.vendorPrefix.js(a,"requestAnimationFrame"),c=!!b,d=function(){var c=a[b]||function(b,c){a.setTimeout(b,16)};return function(b,d){c.apply(a,[b,d])}}(),e=0,f={};return{isNative:c,requestFrame:d,start:function(a,b,c){b=0<b?b:Number.POSITIVE_INFINITY;var l=++e,m=+new Date;f[l]=function(){f[l]&&+new Date-m<=b?(a(),f[l]&&d(f[l],c)):delete f[l]};d(f[l],c);return l},stop:function(a){delete f[a]}}}(window);OpenLayers.Tween=OpenLayers.Class({easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,minFrameRate:null,startTime:null,animationId:null,playing:!1,initialize:function(a){this.easing=a?a:OpenLayers.Easing.Expo.easeOut},start:function(a,b,c,d){this.playing=!0;this.begin=a;this.finish=b;this.duration=c;this.callbacks=d.callbacks;this.minFrameRate=d.minFrameRate||30;this.time=0;this.startTime=(new Date).getTime();OpenLayers.Animation.stop(this.animationId);this.animationId=null;
+this.callbacks&&this.callbacks.start&&this.callbacks.start.call(this,this.begin);this.animationId=OpenLayers.Animation.start(OpenLayers.Function.bind(this.play,this))},stop:function(){this.playing&&(this.callbacks&&this.callbacks.done&&this.callbacks.done.call(this,this.finish),OpenLayers.Animation.stop(this.animationId),this.animationId=null,this.playing=!1)},play:function(){var a={},b;for(b in this.begin){var c=this.begin[b],d=this.finish[b];if(null==c||null==d||isNaN(c)||isNaN(d))throw new TypeError("invalid value for Tween");
+a[b]=this.easing.apply(this,[this.time,c,d-c,this.duration])}this.time++;this.callbacks&&this.callbacks.eachStep&&((new Date).getTime()-this.startTime)/this.time<=1E3/this.minFrameRate&&this.callbacks.eachStep.call(this,a);this.time>this.duration&&this.stop()},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(a,b,c,d){return c*a/d+b},easeOut:function(a,b,c,d){return c*a/d+b},easeInOut:function(a,b,c,d){return c*a/d+b},CLASS_NAME:"OpenLayers.Easing.Linear"};
+OpenLayers.Easing.Expo={easeIn:function(a,b,c,d){return 0==a?b:c*Math.pow(2,10*(a/d-1))+b},easeOut:function(a,b,c,d){return a==d?b+c:c*(-Math.pow(2,-10*a/d)+1)+b},easeInOut:function(a,b,c,d){return 0==a?b:a==d?b+c:1>(a/=d/2)?c/2*Math.pow(2,10*(a-1))+b:c/2*(-Math.pow(2,-10*--a)+2)+b},CLASS_NAME:"OpenLayers.Easing.Expo"};
+OpenLayers.Easing.Quad={easeIn:function(a,b,c,d){return c*(a/=d)*a+b},easeOut:function(a,b,c,d){return-c*(a/=d)*(a-2)+b},easeInOut:function(a,b,c,d){return 1>(a/=d/2)?c/2*a*a+b:-c/2*(--a*(a-2)-1)+b},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,titleRegEx:/\+title=[^\+]*/,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.projCode=a;"object"==typeof Proj4js&&(this.proj=new Proj4js.Proj(a))},getCode:function(){return this.proj?this.proj.srsCode:this.projCode},getUnits:function(){return this.proj?this.proj.units:null},toString:function(){return this.getCode()},equals:function(a){var b=!1;a&&(a instanceof OpenLayers.Projection||(a=new OpenLayers.Projection(a)),"object"==
+typeof Proj4js&&this.proj.defData&&a.proj.defData?b=this.proj.defData.replace(this.titleRegEx,"")==a.proj.defData.replace(this.titleRegEx,""):a.getCode&&(b=this.getCode(),a=a.getCode(),b=b==a||!!OpenLayers.Projection.transforms[b]&&OpenLayers.Projection.transforms[b][a]===OpenLayers.Projection.nullTransform));return b},destroy:function(){delete this.proj;delete this.projCode},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};
+OpenLayers.Projection.defaults={"EPSG:4326":{units:"degrees",maxExtent:[-180,-90,180,90],yx:!0},"CRS:84":{units:"degrees",maxExtent:[-180,-90,180,90]},"EPSG:900913":{units:"m",maxExtent:[-2.003750834E7,-2.003750834E7,2.003750834E7,2.003750834E7]}};
+OpenLayers.Projection.addTransform=function(a,b,c){if(c===OpenLayers.Projection.nullTransform){var d=OpenLayers.Projection.defaults[a];d&&!OpenLayers.Projection.defaults[b]&&(OpenLayers.Projection.defaults[b]=d)}OpenLayers.Projection.transforms[a]||(OpenLayers.Projection.transforms[a]={});OpenLayers.Projection.transforms[a][b]=c};
+OpenLayers.Projection.transform=function(a,b,c){if(b&&c)if(b instanceof OpenLayers.Projection||(b=new OpenLayers.Projection(b)),c instanceof OpenLayers.Projection||(c=new OpenLayers.Projection(c)),b.proj&&c.proj)a=Proj4js.transform(b.proj,c.proj,a);else{b=b.getCode();c=c.getCode();var d=OpenLayers.Projection.transforms;if(d[b]&&d[b][c])d[b][c](a)}return a};OpenLayers.Projection.nullTransform=function(a){return a};
+(function(){function a(a){a.x=180*a.x/d;a.y=180/Math.PI*(2*Math.atan(Math.exp(a.y/d*Math.PI))-Math.PI/2);return a}function b(a){a.x=a.x*d/180;var b=Math.log(Math.tan((90+a.y)*Math.PI/360))/Math.PI*d;a.y=Math.max(-2.003750834E7,Math.min(b,2.003750834E7));return a}function c(c,d){var e=OpenLayers.Projection.addTransform,f=OpenLayers.Projection.nullTransform,g,p,n,q,s;g=0;for(p=d.length;g<p;++g)for(n=d[g],e(c,n,b),e(n,c,a),s=g+1;s<p;++s)q=d[s],e(n,q,f),e(q,n,f)}var d=2.003750834E7,e=["EPSG:900913","EPSG:3857",
+"EPSG:102113","EPSG:102100"],f=["CRS:84","urn:ogc:def:crs:EPSG:6.6:4326","EPSG:4326"],g;for(g=e.length-1;0<=g;--g)c(e[g],f);for(g=f.length-1;0<=g;--g)c(f[g],e)})();OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1E3},id:null,fractionalZoom:!1,events:null,allOverlays:!1,div:null,dragging:!1,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,panRatio:1.5,options:null,tileSize:null,projection:"EPSG:4326",units:null,resolutions:null,maxResolution:null,minResolution:null,maxScale:null,minScale:null,
+maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:!1,autoUpdateSize:!0,eventListeners:null,panTween:null,panMethod:OpenLayers.Easing.Expo.easeOut,panDuration:50,zoomTween:null,zoomMethod:OpenLayers.Easing.Quad.easeOut,zoomDuration:20,paddingForPopups:null,layerContainerOriginPx:null,minPx:null,maxPx:null,initialize:function(a,b){1===arguments.length&&"object"===typeof a&&(a=(b=a)&&b.div);this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+OpenLayers.Map.TILE_HEIGHT);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+"theme/default/style.css";this.options=OpenLayers.Util.extend({},b);OpenLayers.Util.extend(this,b);OpenLayers.Util.applyDefaults(this,OpenLayers.Projection.defaults[this.projection instanceof OpenLayers.Projection?this.projection.projCode:this.projection]);!this.maxExtent||this.maxExtent instanceof OpenLayers.Bounds||(this.maxExtent=new OpenLayers.Bounds(this.maxExtent));
+!this.minExtent||this.minExtent instanceof OpenLayers.Bounds||(this.minExtent=new OpenLayers.Bounds(this.minExtent));!this.restrictedExtent||this.restrictedExtent instanceof OpenLayers.Bounds||(this.restrictedExtent=new OpenLayers.Bounds(this.restrictedExtent));!this.center||this.center instanceof OpenLayers.LonLat||(this.center=new OpenLayers.LonLat(this.center));this.layers=[];this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(a);this.div||(this.div=document.createElement("div"),
+this.div.style.height="1px",this.div.style.width="1px");OpenLayers.Element.addClass(this.div,"olMap");var c=this.id+"_OpenLayers_ViewPort";this.viewPortDiv=OpenLayers.Util.createDiv(c,null,null,null,"relative",null,"hidden");this.viewPortDiv.style.width="100%";this.viewPortDiv.style.height="100%";this.viewPortDiv.className="olMapViewport";this.div.appendChild(this.viewPortDiv);this.events=new OpenLayers.Events(this,this.viewPortDiv,null,this.fallThrough,{includeXY:!0});OpenLayers.TileManager&&null!==
+this.tileManager&&(this.tileManager instanceof OpenLayers.TileManager||(this.tileManager=new OpenLayers.TileManager(this.tileManager)),this.tileManager.addMap(this));c=this.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(c);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE.Popup-1;this.layerContainerOriginPx={x:0,y:0};this.applyTransform();this.viewPortDiv.appendChild(this.layerContainerDiv);this.updateSize();if(this.eventListeners instanceof Object)this.events.on(this.eventListeners);
+!0===this.autoUpdateSize&&(this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this),OpenLayers.Event.observe(window,"resize",this.updateSizeDestroy));if(this.theme){for(var c=!0,d=document.getElementsByTagName("link"),e=0,f=d.length;e<f;++e)if(OpenLayers.Util.isEquivalentUrl(d.item(e).href,this.theme)){c=!1;break}c&&(c=document.createElement("link"),c.setAttribute("rel","stylesheet"),c.setAttribute("type","text/css"),c.setAttribute("href",this.theme),document.getElementsByTagName("head")[0].appendChild(c))}null==
+this.controls&&(this.controls=[],null!=OpenLayers.Control&&(OpenLayers.Control.Navigation?this.controls.push(new OpenLayers.Control.Navigation):OpenLayers.Control.TouchNavigation&&this.controls.push(new OpenLayers.Control.TouchNavigation),OpenLayers.Control.Zoom?this.controls.push(new OpenLayers.Control.Zoom):OpenLayers.Control.PanZoom&&this.controls.push(new OpenLayers.Control.PanZoom),OpenLayers.Control.ArgParser&&this.controls.push(new OpenLayers.Control.ArgParser),OpenLayers.Control.Attribution&&
+this.controls.push(new OpenLayers.Control.Attribution)));e=0;for(f=this.controls.length;e<f;e++)this.addControlToMap(this.controls[e]);this.popups=[];this.unloadDestroy=OpenLayers.Function.bind(this.destroy,this);OpenLayers.Event.observe(window,"unload",this.unloadDestroy);b&&b.layers&&(delete this.center,delete this.zoom,this.addLayers(b.layers),b.center&&!this.getCenter()&&this.setCenter(b.center,b.zoom));this.panMethod&&(this.panTween=new OpenLayers.Tween(this.panMethod));this.zoomMethod&&this.applyTransform.transform&&
+(this.zoomTween=new OpenLayers.Tween(this.zoomMethod))},getViewport:function(){return this.viewPortDiv},render:function(a){this.div=OpenLayers.Util.getElement(a);OpenLayers.Element.addClass(this.div,"olMap");this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);this.div.appendChild(this.viewPortDiv);this.updateSize()},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy)return!1;this.panTween&&(this.panTween.stop(),this.panTween=null);this.zoomTween&&(this.zoomTween.stop(),
+this.zoomTween=null);OpenLayers.Event.stopObserving(window,"unload",this.unloadDestroy);this.unloadDestroy=null;this.updateSizeDestroy&&OpenLayers.Event.stopObserving(window,"resize",this.updateSizeDestroy);this.paddingForPopups=null;if(null!=this.controls){for(var a=this.controls.length-1;0<=a;--a)this.controls[a].destroy();this.controls=null}if(null!=this.layers){for(a=this.layers.length-1;0<=a;--a)this.layers[a].destroy(!1);this.layers=null}this.viewPortDiv&&this.viewPortDiv.parentNode&&this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+this.viewPortDiv=null;this.tileManager&&(this.tileManager.removeMap(this),this.tileManager=null);this.eventListeners&&(this.events.un(this.eventListeners),this.eventListeners=null);this.events.destroy();this.options=this.events=null},setOptions:function(a){var b=this.minPx&&a.restrictedExtent!=this.restrictedExtent;OpenLayers.Util.extend(this,a);b&&this.moveTo(this.getCachedCenter(),this.zoom,{forceZoomChange:!0})},getTileSize:function(){return this.tileSize},getBy:function(a,b,c){var d="function"==
+typeof c.test;return OpenLayers.Array.filter(this[a],function(a){return a[b]==c||d&&c.test(a[b])})},getLayersBy:function(a,b){return this.getBy("layers",a,b)},getLayersByName:function(a){return this.getLayersBy("name",a)},getLayersByClass:function(a){return this.getLayersBy("CLASS_NAME",a)},getControlsBy:function(a,b){return this.getBy("controls",a,b)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},getLayer:function(a){for(var b=null,c=0,d=this.layers.length;c<d;c++){var e=
+this.layers[c];if(e.id==a){b=e;break}}return b},setLayerZIndex:function(a,b){a.setZIndex(this.Z_INDEX_BASE[a.isBaseLayer?"BaseLayer":"Overlay"]+5*b)},resetLayersZIndex:function(){for(var a=0,b=this.layers.length;a<b;a++)this.setLayerZIndex(this.layers[a],a)},addLayer:function(a){for(var b=0,c=this.layers.length;b<c;b++)if(this.layers[b]==a)return!1;if(!1===this.events.triggerEvent("preaddlayer",{layer:a}))return!1;this.allOverlays&&(a.isBaseLayer=!1);a.div.className="olLayerDiv";a.div.style.overflow=
+"";this.setLayerZIndex(a,this.layers.length);a.isFixed?this.viewPortDiv.appendChild(a.div):this.layerContainerDiv.appendChild(a.div);this.layers.push(a);a.setMap(this);a.isBaseLayer||this.allOverlays&&!this.baseLayer?null==this.baseLayer?this.setBaseLayer(a):a.setVisibility(!1):a.redraw();this.events.triggerEvent("addlayer",{layer:a});a.events.triggerEvent("added",{map:this,layer:a});a.afterAdd();return!0},addLayers:function(a){for(var b=0,c=a.length;b<c;b++)this.addLayer(a[b])},removeLayer:function(a,
+b){if(!1!==this.events.triggerEvent("preremovelayer",{layer:a})){null==b&&(b=!0);a.isFixed?this.viewPortDiv.removeChild(a.div):this.layerContainerDiv.removeChild(a.div);OpenLayers.Util.removeItem(this.layers,a);a.removeMap(this);a.map=null;if(this.baseLayer==a&&(this.baseLayer=null,b))for(var c=0,d=this.layers.length;c<d;c++){var e=this.layers[c];if(e.isBaseLayer||this.allOverlays){this.setBaseLayer(e);break}}this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:a});a.events.triggerEvent("removed",
+{map:this,layer:a})}},getNumLayers:function(){return this.layers.length},getLayerIndex:function(a){return OpenLayers.Util.indexOf(this.layers,a)},setLayerIndex:function(a,b){var c=this.getLayerIndex(a);0>b?b=0:b>this.layers.length&&(b=this.layers.length);if(c!=b){this.layers.splice(c,1);this.layers.splice(b,0,a);for(var c=0,d=this.layers.length;c<d;c++)this.setLayerZIndex(this.layers[c],c);this.events.triggerEvent("changelayer",{layer:a,property:"order"});this.allOverlays&&(0===b?this.setBaseLayer(a):
+this.baseLayer!==this.layers[0]&&this.setBaseLayer(this.layers[0]))}},raiseLayer:function(a,b){var c=this.getLayerIndex(a)+b;this.setLayerIndex(a,c)},setBaseLayer:function(a){if(a!=this.baseLayer&&-1!=OpenLayers.Util.indexOf(this.layers,a)){var b=this.getCachedCenter(),c=OpenLayers.Util.getResolutionFromScale(this.getScale(),a.units);null==this.baseLayer||this.allOverlays||this.baseLayer.setVisibility(!1);this.baseLayer=a;if(!this.allOverlays||this.baseLayer.visibility)this.baseLayer.setVisibility(!0),
+!1===this.baseLayer.inRange&&this.baseLayer.redraw();null!=b&&(a=this.getZoomForResolution(c||this.resolution,!0),this.setCenter(b,a,!1,!0));this.events.triggerEvent("changebaselayer",{layer:this.baseLayer})}},addControl:function(a,b){this.controls.push(a);this.addControlToMap(a,b)},addControls:function(a,b){for(var c=1===arguments.length?[]:b,d=0,e=a.length;d<e;d++)this.addControl(a[d],c[d]?c[d]:null)},addControlToMap:function(a,b){a.outsideViewport=null!=a.div;this.displayProjection&&!a.displayProjection&&
+(a.displayProjection=this.displayProjection);a.setMap(this);var c=a.draw(b);c&&!a.outsideViewport&&(c.style.zIndex=this.Z_INDEX_BASE.Control+this.controls.length,this.viewPortDiv.appendChild(c));a.autoActivate&&a.activate()},getControl:function(a){for(var b=null,c=0,d=this.controls.length;c<d;c++){var e=this.controls[c];if(e.id==a){b=e;break}}return b},removeControl:function(a){a&&a==this.getControl(a.id)&&(a.div&&a.div.parentNode==this.viewPortDiv&&this.viewPortDiv.removeChild(a.div),OpenLayers.Util.removeItem(this.controls,
+a))},addPopup:function(a,b){if(b)for(var c=this.popups.length-1;0<=c;--c)this.removePopup(this.popups[c]);a.map=this;this.popups.push(a);if(c=a.draw())c.style.zIndex=this.Z_INDEX_BASE.Popup+this.popups.length,this.layerContainerDiv.appendChild(c)},removePopup:function(a){OpenLayers.Util.removeItem(this.popups,a);if(a.div)try{this.layerContainerDiv.removeChild(a.div)}catch(b){}a.map=null},getSize:function(){var a=null;null!=this.size&&(a=this.size.clone());return a},updateSize:function(){var a=this.getCurrentSize();
+if(a&&!isNaN(a.h)&&!isNaN(a.w)){this.events.clearMouseCache();var b=this.getSize();null==b&&(this.size=b=a);if(!a.equals(b)){this.size=a;a=0;for(b=this.layers.length;a<b;a++)this.layers[a].onMapResize();a=this.getCachedCenter();null!=this.baseLayer&&null!=a&&(b=this.getZoom(),this.zoom=null,this.setCenter(a,b))}}this.events.triggerEvent("updatesize")},getCurrentSize:function(){var a=new OpenLayers.Size(this.div.clientWidth,this.div.clientHeight);if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=this.div.offsetWidth,
+a.h=this.div.offsetHeight;if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=parseInt(this.div.style.width),a.h=parseInt(this.div.style.height);return a},calculateBounds:function(a,b){var c=null;null==a&&(a=this.getCachedCenter());null==b&&(b=this.getResolution());if(null!=a&&null!=b)var c=this.size.w*b/2,d=this.size.h*b/2,c=new OpenLayers.Bounds(a.lon-c,a.lat-d,a.lon+c,a.lat+d);return c},getCenter:function(){var a=null,b=this.getCachedCenter();b&&(a=b.clone());return a},getCachedCenter:function(){!this.center&&
+this.size&&(this.center=this.getLonLatFromViewPortPx({x:this.size.w/2,y:this.size.h/2}));return this.center},getZoom:function(){return this.zoom},pan:function(a,b,c){c=OpenLayers.Util.applyDefaults(c,{animate:!0,dragging:!1});if(c.dragging)0==a&&0==b||this.moveByPx(a,b);else{var d=this.getViewPortPxFromLonLat(this.getCachedCenter());a=d.add(a,b);if(this.dragging||!a.equals(d))d=this.getLonLatFromViewPortPx(a),c.animate?this.panTo(d):(this.moveTo(d),this.dragging&&(this.dragging=!1,this.events.triggerEvent("moveend")))}},
+panTo:function(a){if(this.panTween&&this.getExtent().scale(this.panRatio).containsLonLat(a)){var b=this.getCachedCenter();if(!a.equals(b)){var b=this.getPixelFromLonLat(b),c=this.getPixelFromLonLat(a),d=0,e=0;this.panTween.start({x:0,y:0},{x:c.x-b.x,y:c.y-b.y},this.panDuration,{callbacks:{eachStep:OpenLayers.Function.bind(function(a){this.moveByPx(a.x-d,a.y-e);d=Math.round(a.x);e=Math.round(a.y)},this),done:OpenLayers.Function.bind(function(b){this.moveTo(a);this.dragging=!1;this.events.triggerEvent("moveend")},
+this)}})}}else this.setCenter(a)},setCenter:function(a,b,c,d){this.panTween&&this.panTween.stop();this.zoomTween&&this.zoomTween.stop();this.moveTo(a,b,{dragging:c,forceZoomChange:d})},moveByPx:function(a,b){var c=this.size.w/2,d=this.size.h/2,e=c+a,f=d+b,g=this.baseLayer.wrapDateLine,h=0,k=0;this.restrictedExtent&&(h=c,k=d,g=!1);a=g||e<=this.maxPx.x-h&&e>=this.minPx.x+h?Math.round(a):0;b=f<=this.maxPx.y-k&&f>=this.minPx.y+k?Math.round(b):0;if(a||b){this.dragging||(this.dragging=!0,this.events.triggerEvent("movestart"));
+this.center=null;a&&(this.layerContainerOriginPx.x-=a,this.minPx.x-=a,this.maxPx.x-=a);b&&(this.layerContainerOriginPx.y-=b,this.minPx.y-=b,this.maxPx.y-=b);this.applyTransform();d=0;for(e=this.layers.length;d<e;++d)c=this.layers[d],c.visibility&&(c===this.baseLayer||c.inRange)&&(c.moveByPx(a,b),c.events.triggerEvent("move"));this.events.triggerEvent("move")}},adjustZoom:function(a){if(this.baseLayer&&this.baseLayer.wrapDateLine){var b=this.baseLayer.resolutions,c=this.getMaxExtent().getWidth()/this.size.w;
+if(this.getResolutionForZoom(a)>c)if(this.fractionalZoom)a=this.getZoomForResolution(c);else for(var d=a|0,e=b.length;d<e;++d)if(b[d]<=c){a=d;break}}return a},getMinZoom:function(){return this.adjustZoom(0)},moveTo:function(a,b,c){null==a||a instanceof OpenLayers.LonLat||(a=new OpenLayers.LonLat(a));c||(c={});null!=b&&(b=parseFloat(b),this.fractionalZoom||(b=Math.round(b)));var d=b;b=this.adjustZoom(b);b!==d&&(a=this.getCenter());var d=c.dragging||this.dragging,e=c.forceZoomChange;this.getCachedCenter()||
+this.isValidLonLat(a)||(a=this.maxExtent.getCenterLonLat(),this.center=a.clone());if(null!=this.restrictedExtent){null==a&&(a=this.center);null==b&&(b=this.getZoom());var f=this.getResolutionForZoom(b),f=this.calculateBounds(a,f);if(!this.restrictedExtent.containsBounds(f)){var g=this.restrictedExtent.getCenterLonLat();f.getWidth()>this.restrictedExtent.getWidth()?a=new OpenLayers.LonLat(g.lon,a.lat):f.left<this.restrictedExtent.left?a=a.add(this.restrictedExtent.left-f.left,0):f.right>this.restrictedExtent.right&&
+(a=a.add(this.restrictedExtent.right-f.right,0));f.getHeight()>this.restrictedExtent.getHeight()?a=new OpenLayers.LonLat(a.lon,g.lat):f.bottom<this.restrictedExtent.bottom?a=a.add(0,this.restrictedExtent.bottom-f.bottom):f.top>this.restrictedExtent.top&&(a=a.add(0,this.restrictedExtent.top-f.top))}}e=e||this.isValidZoomLevel(b)&&b!=this.getZoom();f=this.isValidLonLat(a)&&!a.equals(this.center);if(e||f||d){d||this.events.triggerEvent("movestart",{zoomChanged:e});f&&(!e&&this.center&&this.centerLayerContainer(a),
+this.center=a.clone());a=e?this.getResolutionForZoom(b):this.getResolution();if(e||null==this.layerContainerOrigin){this.layerContainerOrigin=this.getCachedCenter();this.layerContainerOriginPx.x=0;this.layerContainerOriginPx.y=0;this.applyTransform();var f=this.getMaxExtent({restricted:!0}),h=f.getCenterLonLat(),g=this.center.lon-h.lon,h=h.lat-this.center.lat,k=Math.round(f.getWidth()/a),l=Math.round(f.getHeight()/a);this.minPx={x:(this.size.w-k)/2-g/a,y:(this.size.h-l)/2-h/a};this.maxPx={x:this.minPx.x+
+Math.round(f.getWidth()/a),y:this.minPx.y+Math.round(f.getHeight()/a)}}e&&(this.zoom=b,this.resolution=a);a=this.getExtent();this.baseLayer.visibility&&(this.baseLayer.moveTo(a,e,c.dragging),c.dragging||this.baseLayer.events.triggerEvent("moveend",{zoomChanged:e}));a=this.baseLayer.getExtent();for(b=this.layers.length-1;0<=b;--b)f=this.layers[b],f===this.baseLayer||f.isBaseLayer||(g=f.calculateInRange(),f.inRange!=g&&((f.inRange=g)||f.display(!1),this.events.triggerEvent("changelayer",{layer:f,property:"visibility"})),
+g&&f.visibility&&(f.moveTo(a,e,c.dragging),c.dragging||f.events.triggerEvent("moveend",{zoomChanged:e})));this.events.triggerEvent("move");d||this.events.triggerEvent("moveend");if(e){b=0;for(c=this.popups.length;b<c;b++)this.popups[b].updatePosition();this.events.triggerEvent("zoomend")}}},centerLayerContainer:function(a){var b=this.getViewPortPxFromLonLat(this.layerContainerOrigin),c=this.getViewPortPxFromLonLat(a);if(null!=b&&null!=c){var d=this.layerContainerOriginPx.x;a=this.layerContainerOriginPx.y;
+var e=Math.round(b.x-c.x),b=Math.round(b.y-c.y);this.applyTransform(this.layerContainerOriginPx.x=e,this.layerContainerOriginPx.y=b);d-=e;a-=b;this.minPx.x-=d;this.maxPx.x-=d;this.minPx.y-=a;this.maxPx.y-=a}},isValidZoomLevel:function(a){return null!=a&&0<=a&&a<this.getNumZoomLevels()},isValidLonLat:function(a){var b=!1;null!=a&&(b=this.getMaxExtent(),b=b.containsLonLat(a,{worldBounds:this.baseLayer.wrapDateLine&&b}));return b},getProjection:function(){var a=this.getProjectionObject();return a?a.getCode():
+null},getProjectionObject:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.projection);return a},getMaxResolution:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.maxResolution);return a},getMaxExtent:function(a){var b=null;a&&a.restricted&&this.restrictedExtent?b=this.restrictedExtent:null!=this.baseLayer&&(b=this.baseLayer.maxExtent);return b},getNumZoomLevels:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.numZoomLevels);return a},getExtent:function(){var a=
+null;null!=this.baseLayer&&(a=this.baseLayer.getExtent());return a},getResolution:function(){var a=null;null!=this.baseLayer?a=this.baseLayer.getResolution():!0===this.allOverlays&&0<this.layers.length&&(a=this.layers[0].getResolution());return a},getUnits:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.units);return a},getScale:function(){var a=null;null!=this.baseLayer&&(a=this.getResolution(),a=OpenLayers.Util.getScaleFromResolution(a,this.baseLayer.units));return a},getZoomForExtent:function(a,
+b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForExtent(a,b));return c},getResolutionForZoom:function(a){var b=null;this.baseLayer&&(b=this.baseLayer.getResolutionForZoom(a));return b},getZoomForResolution:function(a,b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForResolution(a,b));return c},zoomTo:function(a,b){var c=this;if(c.isValidZoomLevel(a))if(c.baseLayer.wrapDateLine&&(a=c.adjustZoom(a)),c.zoomTween){var d=c.getResolution(),e=c.getResolutionForZoom(a),f={scale:1},
+d={scale:d/e};c.zoomTween.playing&&c.zoomTween.duration<3*c.zoomDuration?c.zoomTween.finish={scale:c.zoomTween.finish.scale*d.scale}:(b||(e=c.getSize(),b={x:e.w/2,y:e.h/2}),c.zoomTween.start(f,d,c.zoomDuration,{minFrameRate:50,callbacks:{eachStep:function(a){var d=c.layerContainerOriginPx;a=a.scale;c.applyTransform(d.x+((a-1)*(d.x-b.x)|0),d.y+((a-1)*(d.y-b.y)|0),a)},done:function(a){c.applyTransform();a=c.getResolution()/a.scale;var d=c.getZoomForResolution(a,!0);c.moveTo(c.getZoomTargetCenter(b,
+a),d,!0)}}}))}else f=b?c.getZoomTargetCenter(b,c.getResolutionForZoom(a)):null,c.setCenter(f,a)},zoomIn:function(){this.zoomTo(this.getZoom()+1)},zoomOut:function(){this.zoomTo(this.getZoom()-1)},zoomToExtent:function(a,b){a instanceof OpenLayers.Bounds||(a=new OpenLayers.Bounds(a));var c=a.getCenterLonLat();if(this.baseLayer.wrapDateLine){c=this.getMaxExtent();for(a=a.clone();a.right<a.left;)a.right+=c.getWidth();c=a.getCenterLonLat().wrapDateLine(c)}this.setCenter(c,this.getZoomForExtent(a,b))},
+zoomToMaxExtent:function(a){a=this.getMaxExtent({restricted:a?a.restricted:!0});this.zoomToExtent(a)},zoomToScale:function(a,b){var c=OpenLayers.Util.getResolutionFromScale(a,this.baseLayer.units),d=this.size.w*c/2,c=this.size.h*c/2,e=this.getCachedCenter(),d=new OpenLayers.Bounds(e.lon-d,e.lat-c,e.lon+d,e.lat+c);this.zoomToExtent(d,b)},getLonLatFromViewPortPx:function(a){var b=null;null!=this.baseLayer&&(b=this.baseLayer.getLonLatFromViewPortPx(a));return b},getViewPortPxFromLonLat:function(a){var b=
+null;null!=this.baseLayer&&(b=this.baseLayer.getViewPortPxFromLonLat(a));return b},getZoomTargetCenter:function(a,b){var c=null,d=this.getSize(),e=d.w/2-a.x,d=a.y-d.h/2,f=this.getLonLatFromPixel(a);f&&(c=new OpenLayers.LonLat(f.lon+e*b,f.lat+d*b));return c},getLonLatFromPixel:function(a){return this.getLonLatFromViewPortPx(a)},getPixelFromLonLat:function(a){a=this.getViewPortPxFromLonLat(a);a.x=Math.round(a.x);a.y=Math.round(a.y);return a},getGeodesicPixelSize:function(a){var b=a?this.getLonLatFromPixel(a):
+this.getCachedCenter()||new OpenLayers.LonLat(0,0),c=this.getResolution();a=b.add(-c/2,0);var d=b.add(c/2,0),e=b.add(0,-c/2),b=b.add(0,c/2),c=new OpenLayers.Projection("EPSG:4326"),f=this.getProjectionObject()||c;f.equals(c)||(a.transform(f,c),d.transform(f,c),e.transform(f,c),b.transform(f,c));return new OpenLayers.Size(OpenLayers.Util.distVincenty(a,d),OpenLayers.Util.distVincenty(e,b))},getViewPortPxFromLayerPx:function(a){var b=null;null!=a&&(b=a.add(this.layerContainerOriginPx.x,this.layerContainerOriginPx.y));
+return b},getLayerPxFromViewPortPx:function(a){var b=null;null!=a&&(b=a.add(-this.layerContainerOriginPx.x,-this.layerContainerOriginPx.y),isNaN(b.x)||isNaN(b.y))&&(b=null);return b},getLonLatFromLayerPx:function(a){a=this.getViewPortPxFromLayerPx(a);return this.getLonLatFromViewPortPx(a)},getLayerPxFromLonLat:function(a){a=this.getPixelFromLonLat(a);return this.getLayerPxFromViewPortPx(a)},applyTransform:function(a,b,c){c=c||1;var d=this.layerContainerOriginPx,e=1!==c;a=a||d.x;b=b||d.y;var f=this.layerContainerDiv.style,
+g=this.applyTransform.transform,h=this.applyTransform.template;if(void 0===g&&(g=OpenLayers.Util.vendorPrefix.style("transform"),this.applyTransform.transform=g)){var k=OpenLayers.Element.getStyle(this.viewPortDiv,OpenLayers.Util.vendorPrefix.css("transform"));k&&"none"===k||(h=["translate3d(",",0) ","scale3d(",",1)"],f[g]=[h[0],"0,0",h[1]].join(""));h&&~f[g].indexOf(h[0])||(h=["translate(",") ","scale(",")"]);this.applyTransform.template=h}null===g||"translate3d("!==h[0]&&!0!==e?(f.left=a+"px",f.top=
+b+"px",null!==g&&(f[g]="")):(!0===e&&"translate("===h[0]&&(a-=d.x,b-=d.y,f.left=d.x+"px",f.top=d.y+"px"),f[g]=[h[0],a,"px,",b,"px",h[1],h[2],c,",",c,h[3]].join(""))},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:1,alwaysInRange:null,RESOLUTION_PROPERTIES:"scales resolutions maxScale minScale maxResolution minResolution numZoomLevels maxZoomLevel".split(" "),events:null,map:null,isBaseLayer:!1,alpha:!1,displayInLayerSwitcher:!0,visibility:!0,attribution:null,inRange:!1,imageSize:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,
+numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:!1,wrapDateLine:!1,metadata:null,initialize:function(a,b){this.metadata={};b=OpenLayers.Util.extend({},b);null!=this.alwaysInRange&&(b.alwaysInRange=this.alwaysInRange);this.addOptions(b);this.name=a;if(null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"),this.div=OpenLayers.Util.createDiv(this.id),this.div.style.width="100%",this.div.style.height="100%",this.div.dir="ltr",this.events=new OpenLayers.Events(this,
+this.div),this.eventListeners instanceof Object))this.events.on(this.eventListeners)},destroy:function(a){null==a&&(a=!0);null!=this.map&&this.map.removeLayer(this,a);this.options=this.div=this.name=this.map=this.projection=null;this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy());this.events=this.eventListeners=null},clone:function(a){null==a&&(a=new OpenLayers.Layer(this.name,this.getOptions()));OpenLayers.Util.applyDefaults(a,this);a.map=null;return a},
+getOptions:function(){var a={},b;for(b in this.options)a[b]=this[b];return a},setName:function(a){a!=this.name&&(this.name=a,null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"name"}))},addOptions:function(a,b){null==this.options&&(this.options={});a&&("string"==typeof a.projection&&(a.projection=new OpenLayers.Projection(a.projection)),a.projection&&OpenLayers.Util.applyDefaults(a,OpenLayers.Projection.defaults[a.projection.getCode()]),!a.maxExtent||a.maxExtent instanceof
+OpenLayers.Bounds||(a.maxExtent=new OpenLayers.Bounds(a.maxExtent)),!a.minExtent||a.minExtent instanceof OpenLayers.Bounds||(a.minExtent=new OpenLayers.Bounds(a.minExtent)));OpenLayers.Util.extend(this.options,a);OpenLayers.Util.extend(this,a);this.projection&&this.projection.getUnits()&&(this.units=this.projection.getUnits());if(this.map){var c=this.map.getResolution(),d=this.RESOLUTION_PROPERTIES.concat(["projection","units","minExtent","maxExtent"]),e;for(e in a)if(a.hasOwnProperty(e)&&0<=OpenLayers.Util.indexOf(d,
+e)){this.initResolutions();b&&this.map.baseLayer===this&&(this.map.setCenter(this.map.getCenter(),this.map.getZoomForResolution(c),!1,!0),this.map.events.triggerEvent("changebaselayer",{layer:this}));break}}},onMapResize:function(){},redraw:function(){var a=!1;if(this.map){this.inRange=this.calculateInRange();var b=this.getExtent();b&&(this.inRange&&this.visibility)&&(this.moveTo(b,!0,!1),this.events.triggerEvent("moveend",{zoomChanged:!0}),a=!0)}return a},moveTo:function(a,b,c){a=this.visibility;
+this.isBaseLayer||(a=a&&this.inRange);this.display(a)},moveByPx:function(a,b){},setMap:function(a){null==this.map&&(this.map=a,this.maxExtent=this.maxExtent||this.map.maxExtent,this.minExtent=this.minExtent||this.map.minExtent,this.projection=this.projection||this.map.projection,"string"==typeof this.projection&&(this.projection=new OpenLayers.Projection(this.projection)),this.units=this.projection.getUnits()||this.units||this.map.units,this.initResolutions(),this.isBaseLayer||(this.inRange=this.calculateInRange(),
+this.div.style.display=this.visibility&&this.inRange?"":"none"),this.setTileSize())},afterAdd:function(){},removeMap:function(a){},getImageSize:function(a){return this.imageSize||this.tileSize},setTileSize:function(a){this.tileSize=a=a?a:this.tileSize?this.tileSize:this.map.getTileSize();this.gutter&&(this.imageSize=new OpenLayers.Size(a.w+2*this.gutter,a.h+2*this.gutter))},getVisibility:function(){return this.visibility},setVisibility:function(a){a!=this.visibility&&(this.visibility=a,this.display(a),
+this.redraw(),null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"}),this.events.triggerEvent("visibilitychanged"))},display:function(a){a!=("none"!=this.div.style.display)&&(this.div.style.display=a&&this.calculateInRange()?"block":"none")},calculateInRange:function(){var a=!1;this.alwaysInRange?a=!0:this.map&&(a=this.map.getResolution(),a=a>=this.minResolution&&a<=this.maxResolution);return a},setIsBaseLayer:function(a){a!=this.isBaseLayer&&(this.isBaseLayer=
+a,null!=this.map&&this.map.events.triggerEvent("changebaselayer",{layer:this}))},initResolutions:function(){var a,b,c,d={},e=!0;a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=this.options[c],e&&this.options[c]&&(e=!1);null==this.options.alwaysInRange&&(this.alwaysInRange=e);null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d));if(null==d.resolutions){a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<
+b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=null!=this.options[c]?this.options[c]:this.map[c];null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d))}var f;this.options.maxResolution&&"auto"!==this.options.maxResolution&&(f=this.options.maxResolution);this.options.minScale&&(f=OpenLayers.Util.getResolutionFromScale(this.options.minScale,this.units));var g;this.options.minResolution&&"auto"!==this.options.minResolution&&
+(g=this.options.minResolution);this.options.maxScale&&(g=OpenLayers.Util.getResolutionFromScale(this.options.maxScale,this.units));d.resolutions&&(d.resolutions.sort(function(a,b){return b-a}),f||(f=d.resolutions[0]),g||(g=d.resolutions[d.resolutions.length-1]));if(this.resolutions=d.resolutions){b=this.resolutions.length;this.scales=Array(b);for(a=0;a<b;a++)this.scales[a]=OpenLayers.Util.getScaleFromResolution(this.resolutions[a],this.units);this.numZoomLevels=b}if(this.minResolution=g)this.maxScale=
+OpenLayers.Util.getScaleFromResolution(g,this.units);if(this.maxResolution=f)this.minScale=OpenLayers.Util.getScaleFromResolution(f,this.units)},resolutionsFromScales:function(a){if(null!=a){var b,c,d;d=a.length;b=Array(d);for(c=0;c<d;c++)b[c]=OpenLayers.Util.getResolutionFromScale(a[c],this.units);return b}},calculateResolutions:function(a){var b,c,d=a.maxResolution;null!=a.minScale?d=OpenLayers.Util.getResolutionFromScale(a.minScale,this.units):"auto"==d&&null!=this.maxExtent&&(b=this.map.getSize(),
+c=this.maxExtent.getWidth()/b.w,b=this.maxExtent.getHeight()/b.h,d=Math.max(c,b));c=a.minResolution;null!=a.maxScale?c=OpenLayers.Util.getResolutionFromScale(a.maxScale,this.units):"auto"==a.minResolution&&null!=this.minExtent&&(b=this.map.getSize(),c=this.minExtent.getWidth()/b.w,b=this.minExtent.getHeight()/b.h,c=Math.max(c,b));"number"!==typeof d&&("number"!==typeof c&&null!=this.maxExtent)&&(d=this.map.getTileSize(),d=Math.max(this.maxExtent.getWidth()/d.w,this.maxExtent.getHeight()/d.h));b=a.maxZoomLevel;
+a=a.numZoomLevels;"number"===typeof c&&"number"===typeof d&&void 0===a?a=Math.floor(Math.log(d/c)/Math.log(2))+1:void 0===a&&null!=b&&(a=b+1);if(!("number"!==typeof a||0>=a||"number"!==typeof d&&"number"!==typeof c)){b=Array(a);var e=2;"number"==typeof c&&"number"==typeof d&&(e=Math.pow(d/c,1/(a-1)));var f;if("number"===typeof d)for(f=0;f<a;f++)b[f]=d/Math.pow(e,f);else for(f=0;f<a;f++)b[a-1-f]=c*Math.pow(e,f);return b}},getResolution:function(){var a=this.map.getZoom();return this.getResolutionForZoom(a)},
+getExtent:function(){return this.map.calculateBounds()},getZoomForExtent:function(a,b){var c=this.map.getSize(),c=Math.max(a.getWidth()/c.w,a.getHeight()/c.h);return this.getZoomForResolution(c,b)},getDataExtent:function(){},getResolutionForZoom:function(a){a=Math.max(0,Math.min(a,this.resolutions.length-1));if(this.map.fractionalZoom){var b=Math.floor(a),c=Math.ceil(a);a=this.resolutions[b]-(a-b)*(this.resolutions[b]-this.resolutions[c])}else a=this.resolutions[Math.round(a)];return a},getZoomForResolution:function(a,
+b){var c,d;if(this.map.fractionalZoom){var e=0,f=this.resolutions[e],g=this.resolutions[this.resolutions.length-1],h;c=0;for(d=this.resolutions.length;c<d;++c)if(h=this.resolutions[c],h>=a&&(f=h,e=c),h<=a){g=h;break}c=f-g;c=0<c?e+(f-a)/c:e}else{f=Number.POSITIVE_INFINITY;c=0;for(d=this.resolutions.length;c<d;c++)if(b){e=Math.abs(this.resolutions[c]-a);if(e>f)break;f=e}else if(this.resolutions[c]<a)break;c=Math.max(0,c-1)}return c},getLonLatFromViewPortPx:function(a){var b=null,c=this.map;if(null!=
+a&&c.minPx){var b=c.getResolution(),d=c.getMaxExtent({restricted:!0}),b=new OpenLayers.LonLat((a.x-c.minPx.x)*b+d.left,(c.minPx.y-a.y)*b+d.top);this.wrapDateLine&&(b=b.wrapDateLine(this.maxExtent))}return b},getViewPortPxFromLonLat:function(a,b){var c=null;null!=a&&(b=b||this.map.getResolution(),c=this.map.calculateBounds(null,b),c=new OpenLayers.Pixel(1/b*(a.lon-c.left),1/b*(c.top-a.lat)));return c},setOpacity:function(a){if(a!=this.opacity){this.opacity=a;for(var b=this.div.childNodes,c=0,d=b.length;c<
+d;++c){var e=b[c].firstChild||b[c],f=b[c].lastChild;f&&"iframe"===f.nodeName.toLowerCase()&&(e=f.parentNode);OpenLayers.Util.modifyDOMElement(e,null,null,null,null,null,null,a)}null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"})}},getZIndex:function(){return this.div.style.zIndex},setZIndex:function(a){this.div.style.zIndex=a},adjustBounds:function(a){if(this.gutter){var b=this.gutter*this.map.getResolution();a=new OpenLayers.Bounds(a.left-b,a.bottom-b,a.right+
+b,a.top+b)}this.wrapDateLine&&(b={rightTolerance:this.getResolution(),leftTolerance:this.getResolution()},a=a.wrapDateLine(this.maxExtent,b));return a},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Layer.SphericalMercator={getExtent:function(){var a=null;return a=this.sphericalMercator?this.map.calculateBounds():OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this)},getLonLatFromViewPortPx:function(a){return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this,arguments)},getViewPortPxFromLonLat:function(a){return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this,arguments)},initMercatorParameters:function(){this.RESOLUTIONS=[];for(var a=0;a<=this.MAX_ZOOM_LEVEL;++a)this.RESOLUTIONS[a]=
+156543.03390625/Math.pow(2,a);this.units="m";this.projection=this.projection||"EPSG:900913"},forwardMercator:function(){var a=new OpenLayers.Projection("EPSG:4326"),b=new OpenLayers.Projection("EPSG:900913");return function(c,d){var e=OpenLayers.Projection.transform({x:c,y:d},a,b);return new OpenLayers.LonLat(e.x,e.y)}}(),inverseMercator:function(){var a=new OpenLayers.Projection("EPSG:4326"),b=new OpenLayers.Projection("EPSG:900913");return function(c,d){var e=OpenLayers.Projection.transform({x:c,
+y:d},b,a);return new OpenLayers.LonLat(e.x,e.y)}}()};OpenLayers.Layer.EventPane=OpenLayers.Class(OpenLayers.Layer,{smoothDragPan:!0,isBaseLayer:!0,isFixed:!0,pane:null,mapObject:null,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);null==this.pane&&(this.pane=OpenLayers.Util.createDiv(this.div.id+"_EventPane"))},destroy:function(){this.pane=this.mapObject=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Layer.prototype.setMap.apply(this,arguments);this.pane.style.zIndex=
+parseInt(this.div.style.zIndex)+1;this.pane.style.display=this.div.style.display;this.pane.style.width="100%";this.pane.style.height="100%";"msie"==OpenLayers.BROWSER_NAME&&(this.pane.style.background="url("+OpenLayers.Util.getImageLocation("blank.gif")+")");this.isFixed?this.map.viewPortDiv.appendChild(this.pane):this.map.layerContainerDiv.appendChild(this.pane);this.loadMapObject();null==this.mapObject&&this.loadWarningMessage()},removeMap:function(a){this.pane&&this.pane.parentNode&&this.pane.parentNode.removeChild(this.pane);
+OpenLayers.Layer.prototype.removeMap.apply(this,arguments)},loadWarningMessage:function(){this.div.style.backgroundColor="darkblue";var a=this.map.getSize(),b=Math.min(a.w,300),c=Math.min(a.h,200),b=new OpenLayers.Size(b,c),a=(new OpenLayers.Pixel(a.w/2,a.h/2)).add(-b.w/2,-b.h/2),a=OpenLayers.Util.createDiv(this.name+"_warning",a,b,null,null,null,"auto");a.style.padding="7px";a.style.backgroundColor="yellow";a.innerHTML=this.getWarningHTML();this.div.appendChild(a)},getWarningHTML:function(){return""},
+display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);this.pane.style.display=this.div.style.display},setZIndex:function(a){OpenLayers.Layer.prototype.setZIndex.apply(this,arguments);this.pane.style.zIndex=parseInt(this.div.style.zIndex)+1},moveByPx:function(a,b){OpenLayers.Layer.prototype.moveByPx.apply(this,arguments);this.dragPanMapObject?this.dragPanMapObject(a,-b):this.moveTo(this.map.getCachedCenter())},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,
+arguments);if(null!=this.mapObject){var d=this.map.getCenter(),e=this.map.getZoom();if(null!=d){var f=this.getMapObjectCenter(),f=this.getOLLonLatFromMapObjectLonLat(f),g=this.getMapObjectZoom(),g=this.getOLZoomFromMapObjectZoom(g);d.equals(f)&&e==g||(!b&&f&&this.dragPanMapObject&&this.smoothDragPan?(e=this.map.getViewPortPxFromLonLat(f),d=this.map.getViewPortPxFromLonLat(d),this.dragPanMapObject(d.x-e.x,e.y-d.y)):(d=this.getMapObjectLonLatFromOLLonLat(d),e=this.getMapObjectZoomFromOLZoom(e),this.setMapObjectCenter(d,
+e,c)))}}},getLonLatFromViewPortPx:function(a){var b=null;null!=this.mapObject&&null!=this.getMapObjectCenter()&&(a=this.getMapObjectPixelFromOLPixel(a),a=this.getMapObjectLonLatFromMapObjectPixel(a),b=this.getOLLonLatFromMapObjectLonLat(a));return b},getViewPortPxFromLonLat:function(a){var b=null;null!=this.mapObject&&null!=this.getMapObjectCenter()&&(a=this.getMapObjectLonLatFromOLLonLat(a),a=this.getMapObjectPixelFromMapObjectLonLat(a),b=this.getOLPixelFromMapObjectPixel(a));return b},getOLLonLatFromMapObjectLonLat:function(a){var b=
+null;null!=a&&(b=this.getLongitudeFromMapObjectLonLat(a),a=this.getLatitudeFromMapObjectLonLat(a),b=new OpenLayers.LonLat(b,a));return b},getMapObjectLonLatFromOLLonLat:function(a){var b=null;null!=a&&(b=this.getMapObjectLonLatFromLonLat(a.lon,a.lat));return b},getOLPixelFromMapObjectPixel:function(a){var b=null;null!=a&&(b=this.getXFromMapObjectPixel(a),a=this.getYFromMapObjectPixel(a),b=new OpenLayers.Pixel(b,a));return b},getMapObjectPixelFromOLPixel:function(a){var b=null;null!=a&&(b=this.getMapObjectPixelFromXY(a.x,
+a.y));return b},CLASS_NAME:"OpenLayers.Layer.EventPane"});OpenLayers.Layer.FixedZoomLevels=OpenLayers.Class({initialize:function(){},initResolutions:function(){for(var a=["minZoomLevel","maxZoomLevel","numZoomLevels"],b=0,c=a.length;b<c;b++){var d=a[b];this[d]=null!=this.options[d]?this.options[d]:this.map[d]}if(null==this.minZoomLevel||this.minZoomLevel<this.MIN_ZOOM_LEVEL)this.minZoomLevel=this.MIN_ZOOM_LEVEL;a=this.MAX_ZOOM_LEVEL-this.minZoomLevel+1;b=null==this.options.numZoomLevels&&null!=this.options.maxZoomLevel||null==this.numZoomLevels&&null!=this.maxZoomLevel?
+this.maxZoomLevel-this.minZoomLevel+1:this.numZoomLevels;this.numZoomLevels=null!=b?Math.min(b,a):a;this.maxZoomLevel=this.minZoomLevel+this.numZoomLevels-1;if(null!=this.RESOLUTIONS){a=0;this.resolutions=[];for(b=this.minZoomLevel;b<=this.maxZoomLevel;b++)this.resolutions[a++]=this.RESOLUTIONS[b];this.maxResolution=this.resolutions[0];this.minResolution=this.resolutions[this.resolutions.length-1]}},getResolution:function(){if(null!=this.resolutions)return OpenLayers.Layer.prototype.getResolution.apply(this,
+arguments);var a=null,b=this.map.getSize(),c=this.getExtent();null!=b&&null!=c&&(a=Math.max(c.getWidth()/b.w,c.getHeight()/b.h));return a},getExtent:function(){var a=this.map.getSize(),b=this.getLonLatFromViewPortPx({x:0,y:0}),a=this.getLonLatFromViewPortPx({x:a.w,y:a.h});return null!=b&&null!=a?new OpenLayers.Bounds(b.lon,a.lat,a.lon,b.lat):null},getZoomForResolution:function(a){if(null!=this.resolutions)return OpenLayers.Layer.prototype.getZoomForResolution.apply(this,arguments);var b=OpenLayers.Layer.prototype.getExtent.apply(this,
+[]);return this.getZoomForExtent(b)},getOLZoomFromMapObjectZoom:function(a){var b=null;null!=a&&(b=a-this.minZoomLevel,this.map.baseLayer!==this&&(b=this.map.baseLayer.getZoomForResolution(this.getResolutionForZoom(b))));return b},getMapObjectZoomFromOLZoom:function(a){var b=null;null!=a&&(b=a+this.minZoomLevel,this.map.baseLayer!==this&&(b=this.getZoomForResolution(this.map.baseLayer.getResolutionForZoom(b))));return b},CLASS_NAME:"OpenLayers.Layer.FixedZoomLevels"});OpenLayers.Layer.Google=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:21,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,6.866455078125E-4,3.4332275390625E-4,1.71661376953125E-4,8.58306884765625E-5,4.291534423828125E-5,2.145767211914062E-5,1.072883605957031E-5,5.36441802978515E-6,2.68220901489257E-6,1.341104507446289E-6,6.705522537231445E-7],
+type:null,wrapDateLine:!0,sphericalMercator:!1,version:null,initialize:function(a,b){b=b||{};b.version||(b.version="function"===typeof GMap2?"2":"3");var c=OpenLayers.Layer.Google["v"+b.version.replace(/\./g,"_")];if(c)OpenLayers.Util.applyDefaults(b,c);else throw"Unsupported Google Maps API version: "+b.version;OpenLayers.Util.applyDefaults(b,c.DEFAULTS);b.maxExtent&&(b.maxExtent=b.maxExtent.clone());OpenLayers.Layer.EventPane.prototype.initialize.apply(this,[a,b]);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+[a,b]);this.sphericalMercator&&(OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator),this.initMercatorParameters())},clone:function(){return new OpenLayers.Layer.Google(this.name,this.getOptions())},setVisibility:function(a){var b=null==this.opacity?1:this.opacity;OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this,arguments);this.setOpacity(b)},display:function(a){this._dragging||this.setGMapVisibility(a);OpenLayers.Layer.EventPane.prototype.display.apply(this,arguments)},moveTo:function(a,
+b,c){this._dragging=c;OpenLayers.Layer.EventPane.prototype.moveTo.apply(this,arguments);delete this._dragging},setOpacity:function(a){a!==this.opacity&&(null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"}),this.opacity=a);if(this.getVisibility()){var b=this.getMapContainer();OpenLayers.Util.modifyDOMElement(b,null,null,null,null,null,null,a)}},destroy:function(){if(this.map){this.setGMapVisibility(!1);var a=OpenLayers.Layer.Google.cache[this.map.id];a&&1>=a.count&&
+this.removeGMapElements()}OpenLayers.Layer.EventPane.prototype.destroy.apply(this,arguments)},removeGMapElements:function(){var a=OpenLayers.Layer.Google.cache[this.map.id];if(a){var b=this.mapObject&&this.getMapContainer();b&&b.parentNode&&b.parentNode.removeChild(b);(b=a.termsOfUse)&&b.parentNode&&b.parentNode.removeChild(b);(a=a.poweredBy)&&a.parentNode&&a.parentNode.removeChild(a);this.mapObject&&(window.google&&google.maps&&google.maps.event&&google.maps.event.clearListeners)&&google.maps.event.clearListeners(this.mapObject,
+"tilesloaded")}},removeMap:function(a){this.visibility&&this.mapObject&&this.setGMapVisibility(!1);var b=OpenLayers.Layer.Google.cache[a.id];b&&(1>=b.count?(this.removeGMapElements(),delete OpenLayers.Layer.Google.cache[a.id]):--b.count);delete this.termsOfUse;delete this.poweredBy;delete this.mapObject;delete this.dragObject;OpenLayers.Layer.EventPane.prototype.removeMap.apply(this,arguments)},getOLBoundsFromMapObjectBounds:function(a){var b=null;null!=a&&(b=a.getSouthWest(),a=a.getNorthEast(),this.sphericalMercator?
+(b=this.forwardMercator(b.lng(),b.lat()),a=this.forwardMercator(a.lng(),a.lat())):(b=new OpenLayers.LonLat(b.lng(),b.lat()),a=new OpenLayers.LonLat(a.lng(),a.lat())),b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat));return b},getWarningHTML:function(){return OpenLayers.i18n("googleWarning")},getMapObjectCenter:function(){return this.mapObject.getCenter()},getMapObjectZoom:function(){return this.mapObject.getZoom()},getLongitudeFromMapObjectLonLat:function(a){return this.sphericalMercator?this.forwardMercator(a.lng(),
+a.lat()).lon:a.lng()},getLatitudeFromMapObjectLonLat:function(a){return this.sphericalMercator?this.forwardMercator(a.lng(),a.lat()).lat:a.lat()},getXFromMapObjectPixel:function(a){return a.x},getYFromMapObjectPixel:function(a){return a.y},CLASS_NAME:"OpenLayers.Layer.Google"});OpenLayers.Layer.Google.cache={};
+OpenLayers.Layer.Google.v2={termsOfUse:null,poweredBy:null,dragObject:null,loadMapObject:function(){this.type||(this.type=G_NORMAL_MAP);var a,b,c,d=OpenLayers.Layer.Google.cache[this.map.id];if(d)a=d.mapObject,b=d.termsOfUse,c=d.poweredBy,++d.count;else{var d=this.map.viewPortDiv,e=document.createElement("div");e.id=this.map.id+"_GMap2Container";e.style.position="absolute";e.style.width="100%";e.style.height="100%";d.appendChild(e);try{a=new GMap2(e),b=e.lastChild,d.appendChild(b),b.style.zIndex=
+"1100",b.style.right="",b.style.bottom="",b.className="olLayerGoogleCopyright",c=e.lastChild,d.appendChild(c),c.style.zIndex="1100",c.style.right="",c.style.bottom="",c.className="olLayerGooglePoweredBy gmnoprint"}catch(f){throw f;}OpenLayers.Layer.Google.cache[this.map.id]={mapObject:a,termsOfUse:b,poweredBy:c,count:1}}this.mapObject=a;this.termsOfUse=b;this.poweredBy=c;-1===OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),this.type)&&this.mapObject.addMapType(this.type);"function"==typeof a.getDragObject?
+this.dragObject=a.getDragObject():this.dragPanMapObject=null;!1===this.isBaseLayer&&this.setGMapVisibility("none"!==this.div.style.display)},onMapResize:function(){if(this.visibility&&this.mapObject.isLoaded())this.mapObject.checkResize();else{if(!this._resized)var a=this,b=GEvent.addListener(this.mapObject,"load",function(){GEvent.removeListener(b);delete a._resized;a.mapObject.checkResize();a.moveTo(a.map.getCenter(),a.map.getZoom())});this._resized=!0}},setGMapVisibility:function(a){var b=OpenLayers.Layer.Google.cache[this.map.id];
+if(b){var c=this.mapObject.getContainer();!0===a?(this.mapObject.setMapType(this.type),c.style.display="",this.termsOfUse.style.left="",this.termsOfUse.style.display="",this.poweredBy.style.display="",b.displayed=this.id):(b.displayed===this.id&&delete b.displayed,b.displayed||(c.style.display="none",this.termsOfUse.style.display="none",this.termsOfUse.style.left="-9999px",this.poweredBy.style.display="none"))}},getMapContainer:function(){return this.mapObject.getContainer()},getMapObjectBoundsFromOLBounds:function(a){var b=
+null;null!=a&&(b=this.sphericalMercator?this.inverseMercator(a.bottom,a.left):new OpenLayers.LonLat(a.bottom,a.left),a=this.sphericalMercator?this.inverseMercator(a.top,a.right):new OpenLayers.LonLat(a.top,a.right),b=new GLatLngBounds(new GLatLng(b.lat,b.lon),new GLatLng(a.lat,a.lon)));return b},setMapObjectCenter:function(a,b){this.mapObject.setCenter(a,b)},dragPanMapObject:function(a,b){this.dragObject.moveBy(new GSize(-a,b))},getMapObjectLonLatFromMapObjectPixel:function(a){return this.mapObject.fromContainerPixelToLatLng(a)},
+getMapObjectPixelFromMapObjectLonLat:function(a){return this.mapObject.fromLatLngToContainerPixel(a)},getMapObjectZoomFromMapObjectBounds:function(a){return this.mapObject.getBoundsZoomLevel(a)},getMapObjectLonLatFromLonLat:function(a,b){var c;this.sphericalMercator?(c=this.inverseMercator(a,b),c=new GLatLng(c.lat,c.lon)):c=new GLatLng(b,a);return c},getMapObjectPixelFromXY:function(a,b){return new GPoint(a,b)}};OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,initialize:function(){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){this.bounds=this.id=null},clone:function(){return new OpenLayers.Geometry},setBounds:function(a){a&&(this.bounds=a.clone())},clearBounds:function(){this.bounds=null;this.parent&&this.parent.clearBounds()},extendBounds:function(a){this.getBounds()?this.bounds.extend(a):this.setBounds(a)},getBounds:function(){null==this.bounds&&this.calculateBounds();
+return this.bounds},calculateBounds:function(){},distanceTo:function(a,b){},getVertices:function(a){},atPoint:function(a,b,c){var d=!1;null!=this.getBounds()&&null!=a&&(b=null!=b?b:0,c=null!=c?c:0,d=(new OpenLayers.Bounds(this.bounds.left-b,this.bounds.bottom-c,this.bounds.right+b,this.bounds.top+c)).containsLonLat(a));return d},getLength:function(){return 0},getArea:function(){return 0},getCentroid:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.WKT?OpenLayers.Format.WKT.prototype.write(new OpenLayers.Feature.Vector(this)):
+Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.fromWKT=function(a){var b;if(OpenLayers.Format&&OpenLayers.Format.WKT){var c=OpenLayers.Geometry.fromWKT.format;c||(c=new OpenLayers.Format.WKT,OpenLayers.Geometry.fromWKT.format=c);a=c.read(a);if(a instanceof OpenLayers.Feature.Vector)b=a.geometry;else if(OpenLayers.Util.isArray(a)){b=a.length;for(var c=Array(b),d=0;d<b;++d)c[d]=a[d].geometry;b=new OpenLayers.Geometry.Collection(c)}}return b};
+OpenLayers.Geometry.segmentsIntersect=function(a,b,c){var d=c&&c.point;c=c&&c.tolerance;var e=!1,f=a.x1-b.x1,g=a.y1-b.y1,h=a.x2-a.x1,k=a.y2-a.y1,l=b.y2-b.y1,m=b.x2-b.x1,r=l*h-m*k,l=m*g-l*f,g=h*g-k*f;0==r?0==l&&0==g&&(e=!0):(f=l/r,r=g/r,0<=f&&(1>=f&&0<=r&&1>=r)&&(d?(h=a.x1+f*h,r=a.y1+f*k,e=new OpenLayers.Geometry.Point(h,r)):e=!0));if(c)if(e){if(d)a:for(a=[a,b],b=0;2>b;++b)for(f=a[b],k=1;3>k;++k)if(h=f["x"+k],r=f["y"+k],d=Math.sqrt(Math.pow(h-e.x,2)+Math.pow(r-e.y,2)),d<c){e.x=h;e.y=r;break a}}else a:for(a=
+[a,b],b=0;2>b;++b)for(h=a[b],r=a[(b+1)%2],k=1;3>k;++k)if(f={x:h["x"+k],y:h["y"+k]},g=OpenLayers.Geometry.distanceToSegment(f,r),g.distance<c){e=d?new OpenLayers.Geometry.Point(f.x,f.y):!0;break a}return e};OpenLayers.Geometry.distanceToSegment=function(a,b){var c=OpenLayers.Geometry.distanceSquaredToSegment(a,b);c.distance=Math.sqrt(c.distance);return c};
+OpenLayers.Geometry.distanceSquaredToSegment=function(a,b){var c=a.x,d=a.y,e=b.x1,f=b.y1,g=b.x2,h=b.y2,k=g-e,l=h-f,m=(k*(c-e)+l*(d-f))/(Math.pow(k,2)+Math.pow(l,2));0>=m||(1<=m?(e=g,f=h):(e+=m*k,f+=m*l));return{distance:Math.pow(e-c,2)+Math.pow(f-d,2),x:e,y:f,along:m}};OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(a){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];null!=a&&this.addComponents(a)},destroy:function(){this.components.length=0;this.components=null;OpenLayers.Geometry.prototype.destroy.apply(this,arguments)},clone:function(){for(var a=eval("new "+this.CLASS_NAME+"()"),b=0,c=this.components.length;b<c;b++)a.addComponent(this.components[b].clone());
+OpenLayers.Util.applyDefaults(a,this);return a},getComponentsString:function(){for(var a=[],b=0,c=this.components.length;b<c;b++)a.push(this.components[b].toShortString());return a.join(",")},calculateBounds:function(){this.bounds=null;var a=new OpenLayers.Bounds,b=this.components;if(b)for(var c=0,d=b.length;c<d;c++)a.extend(b[c].getBounds());null!=a.left&&(null!=a.bottom&&null!=a.right&&null!=a.top)&&this.setBounds(a)},addComponents:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<
+c;b++)this.addComponent(a[b])},addComponent:function(a,b){var c=!1;if(a&&(null==this.componentTypes||-1<OpenLayers.Util.indexOf(this.componentTypes,a.CLASS_NAME))){if(null!=b&&b<this.components.length){var c=this.components.slice(0,b),d=this.components.slice(b,this.components.length);c.push(a);this.components=c.concat(d)}else this.components.push(a);a.parent=this;this.clearBounds();c=!0}return c},removeComponents:function(a){var b=!1;OpenLayers.Util.isArray(a)||(a=[a]);for(var c=a.length-1;0<=c;--c)b=
+this.removeComponent(a[c])||b;return b},removeComponent:function(a){OpenLayers.Util.removeItem(this.components,a);this.clearBounds();return!0},getLength:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getLength();return a},getArea:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getArea();return a},getGeodesicArea:function(a){for(var b=0,c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicArea(a);return b},getCentroid:function(a){if(!a)return this.components.length&&
+this.components[0].getCentroid();a=this.components.length;if(!a)return!1;for(var b=[],c=[],d=0,e=Number.MAX_VALUE,f,g=0;g<a;++g){f=this.components[g];var h=f.getArea();f=f.getCentroid(!0);isNaN(h)||(isNaN(f.x)||isNaN(f.y))||(b.push(h),d+=h,e=h<e&&0<h?h:e,c.push(f))}a=b.length;if(0===d){for(g=0;g<a;++g)b[g]=1;d=b.length}else{for(g=0;g<a;++g)b[g]/=e;d/=e}for(var k=e=0,g=0;g<a;++g)f=c[g],h=b[g],e+=f.x*h,k+=f.y*h;return new OpenLayers.Geometry.Point(e/d,k/d)},getGeodesicLength:function(a){for(var b=0,
+c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicLength(a);return b},move:function(a,b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0;d<this.components.length;++d)this.components[d].resize(a,b,c);return this},distanceTo:function(a,b){for(var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g=Number.POSITIVE_INFINITY,h=0,k=this.components.length;h<
+k&&!(d=this.components[h].distanceTo(a,b),f=c?d.distance:d,f<g&&(g=f,e=d,0==g));++h);return e},equals:function(a){var b=!0;if(a&&a.CLASS_NAME&&this.CLASS_NAME==a.CLASS_NAME)if(OpenLayers.Util.isArray(a.components)&&a.components.length==this.components.length)for(var c=0,d=this.components.length;c<d;++c){if(!this.components[c].equals(a.components[c])){b=!1;break}}else b=!1;else b=!1;return b},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].transform(a,
+b);this.bounds=null}return this},intersects:function(a){for(var b=!1,c=0,d=this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);return b},getVertices:function(a){for(var b=[],c=0,d=this.components.length;c<d;++c)Array.prototype.push.apply(b,this.components[c].getVertices(a));return b},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(a,b){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(a);this.y=parseFloat(b)},clone:function(a){null==a&&(a=new OpenLayers.Geometry.Point(this.x,this.y));OpenLayers.Util.applyDefaults(a,this);return a},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x,this.y)},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g,h;a instanceof
+OpenLayers.Geometry.Point?(e=this.x,f=this.y,g=a.x,h=a.y,d=Math.sqrt(Math.pow(e-g,2)+Math.pow(f-h,2)),d=c?{x0:e,y0:f,x1:g,y1:h,distance:d}:d):(d=a.distanceTo(this,b),c&&(d={x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0,distance:d.distance}));return d},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},toShortString:function(){return this.x+", "+this.y},move:function(a,b){this.x+=a;this.y+=b;this.clearBounds()},rotate:function(a,b){a*=
+Math.PI/180;var c=this.distanceTo(b),d=a+Math.atan2(this.y-b.y,this.x-b.x);this.x=b.x+c*Math.cos(d);this.y=b.y+c*Math.sin(d);this.clearBounds()},getCentroid:function(){return new OpenLayers.Geometry.Point(this.x,this.y)},resize:function(a,b,c){this.x=b.x+a*(void 0==c?1:c)*(this.x-b.x);this.y=b.y+a*(this.y-b.y);this.clearBounds();return this},intersects:function(a){var b=!1;return b="OpenLayers.Geometry.Point"==a.CLASS_NAME?this.equals(a):a.intersects(this)},transform:function(a,b){a&&b&&(OpenLayers.Projection.transform(this,
+a,b),this.bounds=null);return this},getVertices:function(a){return[this]},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],addPoint:function(a,b){this.addComponent(a,b)},removePoint:function(a){this.removeComponent(a)},CLASS_NAME:"OpenLayers.Geometry.MultiPoint"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],getLength:function(){var a=0;if(this.components&&1<this.components.length)for(var b=1,c=this.components.length;b<c;b++)a+=this.components[b-1].distanceTo(this.components[b]);return a},getGeodesicLength:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;if(b.components&&1<b.components.length)for(var d,e=1,f=b.components.length;e<
+f;e++)c=b.components[e-1],d=b.components[e],a+=OpenLayers.Util.distVincenty({lon:c.x,lat:c.y},{lon:d.x,lat:d.y});return 1E3*a},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{removeComponent:function(a){var b=this.components&&2<this.components.length;b&&OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);return b},intersects:function(a){var b=!1,c=a.CLASS_NAME;if("OpenLayers.Geometry.LineString"==c||"OpenLayers.Geometry.LinearRing"==c||"OpenLayers.Geometry.Point"==c){var d=this.getSortedSegments();a="OpenLayers.Geometry.Point"==c?[{x1:a.x,y1:a.y,x2:a.x,y2:a.y}]:a.getSortedSegments();
+var e,f,g,h,k,l,m,r=0,p=d.length;a:for(;r<p;++r){c=d[r];e=c.x1;f=c.x2;g=c.y1;h=c.y2;var n=0,q=a.length;for(;n<q;++n){k=a[n];if(k.x1>f)break;if(!(k.x2<e||(l=k.y1,m=k.y2,Math.min(l,m)>Math.max(g,h)||Math.max(l,m)<Math.min(g,h)||!OpenLayers.Geometry.segmentsIntersect(c,k)))){b=!0;break a}}}}else b=a.intersects(this);return b},getSortedSegments:function(){for(var a=this.components.length-1,b=Array(a),c,d,e=0;e<a;++e)c=this.components[e],d=this.components[e+1],b[e]=c.x<d.x?{x1:c.x,y1:c.y,x2:d.x,y2:d.y}:
+{x1:d.x,y1:d.y,x2:c.x,y2:c.y};return b.sort(function(a,b){return a.x1-b.x1})},splitWithSegment:function(a,b){for(var c=!(b&&!1===b.edge),d=b&&b.tolerance,e=[],f=this.getVertices(),g=[],h=[],k=!1,l,m,r,p={point:!0,tolerance:d},n=null,q=0,s=f.length-2;q<=s;++q)if(d=f[q],g.push(d.clone()),l=f[q+1],m={x1:d.x,y1:d.y,x2:l.x,y2:l.y},m=OpenLayers.Geometry.segmentsIntersect(a,m,p),m instanceof OpenLayers.Geometry.Point&&((r=m.x===a.x1&&m.y===a.y1||m.x===a.x2&&m.y===a.y2||m.equals(d)||m.equals(l)?!0:!1)||c))m.equals(h[h.length-
+1])||h.push(m.clone()),0===q&&m.equals(d)||m.equals(l)||(k=!0,m.equals(d)||g.push(m),e.push(new OpenLayers.Geometry.LineString(g)),g=[m.clone()]);k&&(g.push(l.clone()),e.push(new OpenLayers.Geometry.LineString(g)));if(0<h.length)var t=a.x1<a.x2?1:-1,u=a.y1<a.y2?1:-1,n={lines:e,points:h.sort(function(a,b){return t*a.x-t*b.x||u*a.y-u*b.y})};return n},split:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h;if(a instanceof OpenLayers.Geometry.LineString){var k=this.getVertices(),l,m,r,p,n,q=[];g=[];for(var s=
+0,t=k.length-2;s<=t;++s){l=k[s];m=k[s+1];r={x1:l.x,y1:l.y,x2:m.x,y2:m.y};h=h||[a];d&&q.push(l.clone());for(var u=0;u<h.length;++u)if(p=h[u].splitWithSegment(r,b))if(n=p.lines,0<n.length&&(n.unshift(u,1),Array.prototype.splice.apply(h,n),u+=n.length-2),d)for(var v=0,w=p.points.length;v<w;++v)n=p.points[v],n.equals(l)||(q.push(n),g.push(new OpenLayers.Geometry.LineString(q)),q=n.equals(m)?[]:[n.clone()])}d&&(0<g.length&&0<q.length)&&(q.push(m.clone()),g.push(new OpenLayers.Geometry.LineString(q)))}else c=
+a.splitWith(this,b);h&&1<h.length?f=!0:h=[];g&&1<g.length?e=!0:g=[];if(f||e)c=d?[g,h]:h;return c},splitWith:function(a,b){return a.split(this,b)},getVertices:function(a){return!0===a?[this.components[0],this.components[this.components.length-1]]:!1===a?this.components.slice(1,this.components.length-1):this.components.slice()},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e={},f=Number.POSITIVE_INFINITY;if(a instanceof OpenLayers.Geometry.Point){for(var g=this.getSortedSegments(),
+h=a.x,k=a.y,l,m=0,r=g.length;m<r;++m)if(l=g[m],d=OpenLayers.Geometry.distanceToSegment(a,l),d.distance<f){if(f=d.distance,e=d,0===f)break}else if(l.x2>h&&(k>l.y1&&k<l.y2||k<l.y1&&k>l.y2))break;e=c?{distance:e.distance,x0:e.x,y0:e.y,x1:h,y1:k}:e.distance}else if(a instanceof OpenLayers.Geometry.LineString){var g=this.getSortedSegments(),h=a.getSortedSegments(),p,n,q=h.length,s={point:!0},m=0,r=g.length;a:for(;m<r;++m){k=g[m];l=k.x1;n=k.y1;for(var t=0;t<q;++t)if(d=h[t],p=OpenLayers.Geometry.segmentsIntersect(k,
+d,s)){f=0;e={distance:0,x0:p.x,y0:p.y,x1:p.x,y1:p.y};break a}else d=OpenLayers.Geometry.distanceToSegment({x:l,y:n},d),d.distance<f&&(f=d.distance,e={distance:f,x0:l,y0:n,x1:d.x,y1:d.y})}c||(e=e.distance);0!==f&&k&&(d=a.distanceTo(new OpenLayers.Geometry.Point(k.x2,k.y2),b),m=c?d.distance:d,m<f&&(e=c?{distance:f,x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0}:m))}else e=a.distanceTo(this,b),c&&(e={distance:e.distance,x0:e.x1,y0:e.y1,x1:e.x0,y1:e.y0});return e},simplify:function(a){if(this&&null!==this){var b=this.getVertices();
+if(3>b.length)return this;var c=function(a,b,d,k){for(var l=0,m=0,r=b,p;r<d;r++){p=a[b];var n=a[d],q=a[r],q=Math.abs(0.5*(p.x*n.y+n.x*q.y+q.x*p.y-n.x*p.y-q.x*n.y-p.x*q.y));p=Math.sqrt(Math.pow(p.x-n.x,2)+Math.pow(p.y-n.y,2));p=2*(q/p);p>l&&(l=p,m=r)}l>k&&m!=b&&(e.push(m),c(a,b,m,k),c(a,m,d,k))},d=b.length-1,e=[];e.push(0);for(e.push(d);b[0].equals(b[d]);)d--,e.push(d);c(b,0,d,a);a=[];e.sort(function(a,b){return a-b});for(d=0;d<e.length;d++)a.push(b[e[d]]);return new OpenLayers.Geometry.LineString(a)}return this},
+CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Geometry.LinearRing=OpenLayers.Class(OpenLayers.Geometry.LineString,{componentTypes:["OpenLayers.Geometry.Point"],addComponent:function(a,b){var c=!1,d=this.components.pop();null==b&&a.equals(d)||(c=OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,arguments));OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]);return c},removeComponent:function(a){var b=this.components&&3<this.components.length;b&&(this.components.pop(),OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+arguments),OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]));return b},move:function(a,b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d-1;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0,e=this.components.length;d<e-1;++d)this.components[d].resize(a,b,c);return this},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].transform(a,
+b);this.bounds=null}return this},getCentroid:function(){if(this.components){var a=this.components.length;if(0<a&&2>=a)return this.components[0].clone();if(2<a){var b=0,c=0,d=this.components[0].x,e=this.components[0].y,f=-1*this.getArea();if(0!=f){for(var g=0;g<a-1;g++)var h=this.components[g],k=this.components[g+1],b=b+(h.x+k.x-2*d)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e)),c=c+(h.y+k.y-2*e)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e));b=d+b/(6*f);a=e+c/(6*f)}else{for(g=0;g<a-1;g++)b+=this.components[g].x,c+=this.components[g].y;
+b/=a-1;a=c/(a-1)}return new OpenLayers.Geometry.Point(b,a)}return null}},getArea:function(){var a=0;if(this.components&&2<this.components.length){for(var b=a=0,c=this.components.length;b<c-1;b++)var d=this.components[b],e=this.components[b+1],a=a+(d.x+e.x)*(e.y-d.y);a=-a/2}return a},getGeodesicArea:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;c=b.components&&b.components.length;if(2<c){for(var d,e,f=0;f<c-1;f++)d=b.components[f],
+e=b.components[f+1],a+=OpenLayers.Util.rad(e.x-d.x)*(2+Math.sin(OpenLayers.Util.rad(d.y))+Math.sin(OpenLayers.Util.rad(e.y)));a=40680631590769*a/2}return a},containsPoint:function(a){var b=OpenLayers.Number.limitSigDigs,c=b(a.x,14);a=b(a.y,14);for(var d=this.components.length-1,e,f,g,h,k,l=0,m=0;m<d;++m)if(e=this.components[m],g=b(e.x,14),e=b(e.y,14),f=this.components[m+1],h=b(f.x,14),f=b(f.y,14),e==f){if(a==e&&(g<=h&&c>=g&&c<=h||g>=h&&c<=g&&c>=h)){l=-1;break}}else{k=b((a-f)*((h-g)/(f-e))+h,14);if(k==
+c&&(e<f&&a>=e&&a<=f||e>f&&a<=e&&a>=f)){l=-1;break}k<=c||g!=h&&(k<Math.min(g,h)||k>Math.max(g,h))||(e<f&&a>=e&&a<f||e>f&&a<e&&a>=f)&&++l}return-1==l?1:!!(l&1)},intersects:function(a){var b=!1;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME)b=a.intersects(this);else if("OpenLayers.Geometry.LinearRing"==a.CLASS_NAME)b=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[a]);else for(var c=0,d=a.components.length;c<
+d&&!(b=a.components[c].intersects(this));++c);return b},getVertices:function(a){return!0===a?[]:this.components.slice(0,this.components.length-1)},CLASS_NAME:"OpenLayers.Geometry.LinearRing"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:!1,initialize:function(a,b,c,d){OpenLayers.Layer.prototype.initialize.apply(this,[a,d]);this.url=b;this.params||(this.params=OpenLayers.Util.extend({},c))},destroy:function(){this.params=this.url=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.getOptions()));
+return a=OpenLayers.Layer.prototype.clone.apply(this,[a])},setUrl:function(a){this.url=a},mergeNewParams:function(a){this.params=OpenLayers.Util.extend(this.params,a);a=this.redraw();null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"params"});return a},redraw:function(a){return a?this.mergeNewParams({_olSalt:Math.random()}):OpenLayers.Layer.prototype.redraw.apply(this,[])},selectUrl:function(a,b){for(var c=1,d=0,e=a.length;d<e;d++)c*=a.charCodeAt(d)*this.URL_HASH_FACTOR,
+c-=Math.floor(c);return b[Math.floor(c*b.length)]},getFullRequestString:function(a,b){var c=b||this.url,d=OpenLayers.Util.extend({},this.params),d=OpenLayers.Util.extend(d,a),e=OpenLayers.Util.getParameterString(d);OpenLayers.Util.isArray(c)&&(c=this.selectUrl(e,c));var e=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),f;for(f in d)f.toUpperCase()in e&&delete d[f];e=OpenLayers.Util.getParameterString(d);return OpenLayers.Util.urlAppend(c,e)},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Tile=OpenLayers.Class({events:null,eventListeners:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:!1,initialize:function(a,b,c,d,e,f){this.layer=a;this.position=b.clone();this.setBounds(c);this.url=d;e&&(this.size=e.clone());this.id=OpenLayers.Util.createUniqueID("Tile_");OpenLayers.Util.extend(this,f);this.events=new OpenLayers.Events(this);if(this.eventListeners instanceof Object)this.events.on(this.eventListeners)},unload:function(){this.isLoading&&(this.isLoading=
+!1,this.events.triggerEvent("unload"))},destroy:function(){this.position=this.size=this.bounds=this.layer=null;this.eventListeners&&this.events.un(this.eventListeners);this.events.destroy();this.events=this.eventListeners=null},draw:function(a){a||this.clear();var b=this.shouldDraw();b&&(!a&&!1===this.events.triggerEvent("beforedraw"))&&(b=null);return b},shouldDraw:function(){var a=!1,b=this.layer.maxExtent;if(b){var c=this.layer.map,c=c.baseLayer.wrapDateLine&&c.getMaxExtent();this.bounds.intersectsBounds(b,
+{inclusive:!1,worldBounds:c})&&(a=!0)}return a||this.layer.displayOutsideMaxExtent},setBounds:function(a){a=a.clone();if(this.layer.map.baseLayer.wrapDateLine){var b=this.layer.map.getMaxExtent(),c=this.layer.map.getResolution();a=a.wrapDateLine(b,{leftTolerance:c,rightTolerance:c})}this.bounds=a},moveTo:function(a,b,c){null==c&&(c=!0);this.setBounds(a);this.position=b.clone();c&&this.draw()},clear:function(a){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,imageReloadAttempts:null,layerAlphaHack:null,asyncRequestId:null,maxGetUrlLength:null,canvasContext:null,crossOriginKeyword:null,initialize:function(a,b,c,d,e,f){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=d;this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();if(null!=this.maxGetUrlLength||this.layer.gutter||this.layerAlphaHack)this.frame=document.createElement("div"),this.frame.style.position=
+"absolute",this.frame.style.overflow="hidden";null!=this.maxGetUrlLength&&OpenLayers.Util.extend(this,OpenLayers.Tile.Image.IFrame)},destroy:function(){this.imgDiv&&(this.clear(),this.frame=this.imgDiv=null);this.asyncRequestId=null;OpenLayers.Tile.prototype.destroy.apply(this,arguments)},draw:function(){var a=OpenLayers.Tile.prototype.draw.apply(this,arguments);a?(this.layer!=this.layer.map.baseLayer&&this.layer.reproject&&(this.bounds=this.getBoundsFromBaseLayer(this.position)),this.isLoading?this._loadEvent=
+"reload":(this.isLoading=!0,this._loadEvent="loadstart"),this.renderTile(),this.positionTile()):!1===a&&this.unload();return a},renderTile:function(){if(this.layer.async){var a=this.asyncRequestId=(this.asyncRequestId||0)+1;this.layer.getURLasync(this.bounds,function(b){a==this.asyncRequestId&&(this.url=b,this.initImage())},this)}else this.url=this.layer.getURL(this.bounds),this.initImage()},positionTile:function(){var a=this.getTile().style,b=this.frame?this.size:this.layer.getImageSize(this.bounds),
+c=1;this.layer instanceof OpenLayers.Layer.Grid&&(c=this.layer.getServerResolution()/this.layer.map.getResolution());a.left=this.position.x+"px";a.top=this.position.y+"px";a.width=Math.round(c*b.w)+"px";a.height=Math.round(c*b.h)+"px"},clear:function(){OpenLayers.Tile.prototype.clear.apply(this,arguments);var a=this.imgDiv;if(a){var b=this.getTile();b.parentNode===this.layer.div&&this.layer.div.removeChild(b);this.setImgSrc();!0===this.layerAlphaHack&&(a.style.filter="");OpenLayers.Element.removeClass(a,
+"olImageLoadError")}this.canvasContext=null},getImage:function(){if(!this.imgDiv){this.imgDiv=OpenLayers.Tile.Image.IMAGE.cloneNode(!1);var a=this.imgDiv.style;if(this.frame){var b=0,c=0;this.layer.gutter&&(b=100*(this.layer.gutter/this.layer.tileSize.w),c=100*(this.layer.gutter/this.layer.tileSize.h));a.left=-b+"%";a.top=-c+"%";a.width=2*b+100+"%";a.height=2*c+100+"%"}a.visibility="hidden";a.opacity=0;1>this.layer.opacity&&(a.filter="alpha(opacity="+100*this.layer.opacity+")");a.position="absolute";
+this.layerAlphaHack&&(a.paddingTop=a.height,a.height="0",a.width="100%");this.frame&&this.frame.appendChild(this.imgDiv)}return this.imgDiv},setImage:function(a){this.imgDiv=a},initImage:function(){if(this.url||this.imgDiv){this.events.triggerEvent("beforeload");this.layer.div.appendChild(this.getTile());this.events.triggerEvent(this._loadEvent);var a=this.getImage(),b=a.getAttribute("src")||"";this.url&&OpenLayers.Util.isEquivalentUrl(b,this.url)?this._loadTimeout=window.setTimeout(OpenLayers.Function.bind(this.onImageLoad,
+this),0):(this.stopLoading(),this.crossOriginKeyword&&a.removeAttribute("crossorigin"),OpenLayers.Event.observe(a,"load",OpenLayers.Function.bind(this.onImageLoad,this)),OpenLayers.Event.observe(a,"error",OpenLayers.Function.bind(this.onImageError,this)),this.imageReloadAttempts=0,this.setImgSrc(this.url))}else this.isLoading=!1},setImgSrc:function(a){var b=this.imgDiv;a?(b.style.visibility="hidden",b.style.opacity=0,this.crossOriginKeyword&&("data:"!==a.substr(0,5)?b.setAttribute("crossorigin",this.crossOriginKeyword):
+b.removeAttribute("crossorigin")),b.src=a):(this.stopLoading(),this.imgDiv=null,b.parentNode&&b.parentNode.removeChild(b))},getTile:function(){return this.frame?this.frame:this.getImage()},createBackBuffer:function(){if(this.imgDiv&&!this.isLoading){var a;this.frame?(a=this.frame.cloneNode(!1),a.appendChild(this.imgDiv)):a=this.imgDiv;this.imgDiv=null;return a}},onImageLoad:function(){var a=this.imgDiv;this.stopLoading();a.style.visibility="inherit";a.style.opacity=this.layer.opacity;this.isLoading=
+!1;this.canvasContext=null;this.events.triggerEvent("loadend");!0===this.layerAlphaHack&&(a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+a.src+"', sizingMethod='scale')")},onImageError:function(){var a=this.imgDiv;null!=a.src&&(this.imageReloadAttempts++,this.imageReloadAttempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS?this.setImgSrc(this.layer.getURL(this.bounds)):(OpenLayers.Element.addClass(a,"olImageLoadError"),this.events.triggerEvent("loaderror"),this.onImageLoad()))},stopLoading:function(){OpenLayers.Event.stopObservingElement(this.imgDiv);
+window.clearTimeout(this._loadTimeout);delete this._loadTimeout},getCanvasContext:function(){if(OpenLayers.CANVAS_SUPPORTED&&this.imgDiv&&!this.isLoading){if(!this.canvasContext){var a=document.createElement("canvas");a.width=this.size.w;a.height=this.size.h;this.canvasContext=a.getContext("2d");this.canvasContext.drawImage(this.imgDiv,0,0)}return this.canvasContext}},CLASS_NAME:"OpenLayers.Tile.Image"});
+OpenLayers.Tile.Image.IMAGE=function(){var a=new Image;a.className="olTileImage";a.galleryImg="no";return a}();OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,tileOriginCorner:"bl",tileOrigin:null,tileOptions:null,tileClass:OpenLayers.Tile.Image,grid:null,singleTile:!1,ratio:1.5,buffer:0,transitionEffect:"resize",numLoadingTiles:0,serverResolutions:null,loading:!1,backBuffer:null,gridResolution:null,backBufferResolution:null,backBufferLonLat:null,backBufferTimerId:null,removeBackBufferDelay:null,className:null,gridLayout:null,rowSign:null,transitionendEvents:["transitionend",
+"webkitTransitionEnd","otransitionend","oTransitionEnd"],initialize:function(a,b,c,d){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.grid=[];this._removeBackBuffer=OpenLayers.Function.bind(this.removeBackBuffer,this);this.initProperties();this.rowSign="t"===this.tileOriginCorner.substr(0,1)?1:-1},initProperties:function(){void 0===this.options.removeBackBufferDelay&&(this.removeBackBufferDelay=this.singleTile?0:2500);void 0===this.options.className&&(this.className=this.singleTile?
+"olLayerGridSingleTile":"olLayerGrid")},setMap:function(a){OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this,a);OpenLayers.Element.addClass(this.div,this.className)},removeMap:function(a){this.removeBackBuffer()},destroy:function(){this.removeBackBuffer();this.clearGrid();this.tileSize=this.grid=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments)},clearGrid:function(){if(this.grid){for(var a=0,b=this.grid.length;a<b;a++)for(var c=this.grid[a],d=0,e=c.length;d<e;d++)this.destroyTile(c[d]);
+this.grid=[];this.gridLayout=this.gridResolution=null}},addOptions:function(a,b){var c=void 0!==a.singleTile&&a.singleTile!==this.singleTile;OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this,arguments);this.map&&c&&(this.initProperties(),this.clearGrid(),this.tileSize=this.options.tileSize,this.setTileSize(),this.moveTo(null,!0))},clone:function(a){null==a&&(a=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.getOptions()));a=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,
+[a]);null!=this.tileSize&&(a.tileSize=this.tileSize.clone());a.grid=[];a.gridResolution=null;a.backBuffer=null;a.backBufferTimerId=null;a.loading=!1;a.numLoadingTiles=0;return a},moveTo:function(a,b,c){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);a=a||this.map.getExtent();if(null!=a){var d=!this.grid.length||b,e=this.getTilesBounds(),f=this.map.getResolution();this.getServerResolution(f);if(this.singleTile){if(d||!c&&!e.containsBounds(a))b&&"resize"!==this.transitionEffect&&
+this.removeBackBuffer(),b&&"resize"!==this.transitionEffect||this.applyBackBuffer(f),this.initSingleTile(a)}else(d=d||!e.intersectsBounds(a,{worldBounds:this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent()}))?(!b||"resize"!==this.transitionEffect&&this.gridResolution!==f||this.applyBackBuffer(f),this.initGriddedTiles(a)):this.moveGriddedTiles()}},getTileData:function(a){var b=null,c=a.lon,d=a.lat,e=this.grid.length;if(this.map&&e){var f=this.map.getResolution();a=this.tileSize.w;var g=this.tileSize.h,
+h=this.grid[0][0].bounds,k=h.left,h=h.top;if(c<k&&this.map.baseLayer.wrapDateLine)var l=this.map.getMaxExtent().getWidth(),m=Math.ceil((k-c)/l),c=c+l*m;c=(c-k)/(f*a);d=(h-d)/(f*g);f=Math.floor(c);k=Math.floor(d);0<=k&&k<e&&(e=this.grid[k][f])&&(b={tile:e,i:Math.floor((c-f)*a),j:Math.floor((d-k)*g)})}return b},destroyTile:function(a){this.removeTileMonitoringHooks(a);a.destroy()},getServerResolution:function(a){var b=Number.POSITIVE_INFINITY;a=a||this.map.getResolution();if(this.serverResolutions&&
+-1===OpenLayers.Util.indexOf(this.serverResolutions,a)){var c,d,e,f;for(c=this.serverResolutions.length-1;0<=c;c--){e=this.serverResolutions[c];d=Math.abs(e-a);if(d>b)break;b=d;f=e}a=f}return a},getServerZoom:function(){var a=this.getServerResolution();return this.serverResolutions?OpenLayers.Util.indexOf(this.serverResolutions,a):this.map.getZoomForResolution(a)+(this.zoomOffset||0)},applyBackBuffer:function(a){null!==this.backBufferTimerId&&this.removeBackBuffer();var b=this.backBuffer;if(!b){b=
+this.createBackBuffer();if(!b)return;a===this.gridResolution?this.div.insertBefore(b,this.div.firstChild):this.map.baseLayer.div.parentNode.insertBefore(b,this.map.baseLayer.div);this.backBuffer=b;var c=this.grid[0][0].bounds;this.backBufferLonLat={lon:c.left,lat:c.top};this.backBufferResolution=this.gridResolution}for(var c=this.backBufferResolution/a,d=b.childNodes,e,f=d.length-1;0<=f;--f)e=d[f],e.style.top=(c*e._i*e._h|0)+"px",e.style.left=(c*e._j*e._w|0)+"px",e.style.width=Math.round(c*e._w)+
+"px",e.style.height=Math.round(c*e._h)+"px";a=this.getViewPortPxFromLonLat(this.backBufferLonLat,a);c=this.map.layerContainerOriginPx.y;b.style.left=Math.round(a.x-this.map.layerContainerOriginPx.x)+"px";b.style.top=Math.round(a.y-c)+"px"},createBackBuffer:function(){var a;if(0<this.grid.length){a=document.createElement("div");a.id=this.div.id+"_bb";a.className="olBackBuffer";a.style.position="absolute";var b=this.map;a.style.zIndex="resize"===this.transitionEffect?this.getZIndex()-1:b.Z_INDEX_BASE.BaseLayer-
+(b.getNumLayers()-b.getLayerIndex(this));for(var b=0,c=this.grid.length;b<c;b++)for(var d=0,e=this.grid[b].length;d<e;d++){var f=this.grid[b][d],g=this.grid[b][d].createBackBuffer();g&&(g._i=b,g._j=d,g._w=f.size.w,g._h=f.size.h,g.id=f.id+"_bb",a.appendChild(g))}}return a},removeBackBuffer:function(){if(this._transitionElement){for(var a=this.transitionendEvents.length-1;0<=a;--a)OpenLayers.Event.stopObserving(this._transitionElement,this.transitionendEvents[a],this._removeBackBuffer);delete this._transitionElement}this.backBuffer&&
+(this.backBuffer.parentNode&&this.backBuffer.parentNode.removeChild(this.backBuffer),this.backBufferResolution=this.backBuffer=null,null!==this.backBufferTimerId&&(window.clearTimeout(this.backBufferTimerId),this.backBufferTimerId=null))},moveByPx:function(a,b){this.singleTile||this.moveGriddedTiles()},setTileSize:function(a){this.singleTile&&(a=this.map.getSize(),a.h=parseInt(a.h*this.ratio,10),a.w=parseInt(a.w*this.ratio,10));OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this,[a])},getTilesBounds:function(){var a=
+null,b=this.grid.length;if(b)var a=this.grid[b-1][0].bounds,b=this.grid[0].length*a.getWidth(),c=this.grid.length*a.getHeight(),a=new OpenLayers.Bounds(a.left,a.bottom,a.left+b,a.bottom+c);return a},initSingleTile:function(a){this.events.triggerEvent("retile");var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;b=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2);c=this.map.getLayerPxFromLonLat({lon:b.left,lat:b.top});this.grid.length||(this.grid[0]=[]);(a=this.grid[0][0])?
+a.moveTo(b,c):(a=this.addTile(b,c),this.addTileMonitoringHooks(a),a.draw(),this.grid[0][0]=a);this.removeExcessTiles(1,1);this.gridResolution=this.getServerResolution()},calculateGridLayout:function(a,b,c){var d=c*this.tileSize.w;c*=this.tileSize.h;var e=Math.floor((a.left-b.lon)/d)-this.buffer,f=this.rowSign;a=Math[~f?"floor":"ceil"](f*(b.lat-a.top+c)/c)-this.buffer*f;return{tilelon:d,tilelat:c,startcol:e,startrow:a}},getTileOrigin:function(){var a=this.tileOrigin;if(!a)var a=this.getMaxExtent(),
+b={tl:["left","top"],tr:["right","top"],bl:["left","bottom"],br:["right","bottom"]}[this.tileOriginCorner],a=new OpenLayers.LonLat(a[b[0]],a[b[1]]);return a},getTileBoundsForGridIndex:function(a,b){var c=this.getTileOrigin(),d=this.gridLayout,e=d.tilelon,f=d.tilelat,g=d.startcol,d=d.startrow,h=this.rowSign;return new OpenLayers.Bounds(c.lon+(g+b)*e,c.lat-(d+a*h)*f*h,c.lon+(g+b+1)*e,c.lat-(d+(a-1)*h)*f*h)},initGriddedTiles:function(a){this.events.triggerEvent("retile");var b=this.map.getSize(),c=this.getTileOrigin(),
+d=this.map.getResolution(),e=this.getServerResolution(),f=d/e,d=this.tileSize.w/f,f=this.tileSize.h/f,g=Math.ceil(b.h/f)+2*this.buffer+1,b=Math.ceil(b.w/d)+2*this.buffer+1;this.gridLayout=e=this.calculateGridLayout(a,c,e);var c=e.tilelon,h=e.tilelat,e=this.map.layerContainerOriginPx.x,k=this.map.layerContainerOriginPx.y,l=this.getTileBoundsForGridIndex(0,0),m=this.map.getViewPortPxFromLonLat(new OpenLayers.LonLat(l.left,l.top));m.x=Math.round(m.x)-e;m.y=Math.round(m.y)-k;var e=[],k=this.map.getCenter(),
+r=0;do{var p=this.grid[r];p||(p=[],this.grid.push(p));var n=0;do{var l=this.getTileBoundsForGridIndex(r,n),q=m.clone();q.x+=n*Math.round(d);q.y+=r*Math.round(f);var s=p[n];s?s.moveTo(l,q,!1):(s=this.addTile(l,q),this.addTileMonitoringHooks(s),p.push(s));q=l.getCenterLonLat();e.push({tile:s,distance:Math.pow(q.lon-k.lon,2)+Math.pow(q.lat-k.lat,2)});n+=1}while(l.right<=a.right+c*this.buffer||n<b);r+=1}while(l.bottom>=a.bottom-h*this.buffer||r<g);this.removeExcessTiles(r,n);this.gridResolution=d=this.getServerResolution();
+e.sort(function(a,b){return a.distance-b.distance});a=0;for(d=e.length;a<d;++a)e[a].tile.draw()},getMaxExtent:function(){return this.maxExtent},addTile:function(a,b){var c=new this.tileClass(this,b,a,null,this.tileSize,this.tileOptions);this.events.triggerEvent("addtile",{tile:c});return c},addTileMonitoringHooks:function(a){a.onLoadStart=function(){!1===this.loading&&(this.loading=!0,this.events.triggerEvent("loadstart"));this.events.triggerEvent("tileloadstart",{tile:a});this.numLoadingTiles++;
+!this.singleTile&&(this.backBuffer&&this.gridResolution===this.backBufferResolution)&&OpenLayers.Element.addClass(a.getTile(),"olTileReplacing")};a.onLoadEnd=function(b){this.numLoadingTiles--;b="unload"===b.type;this.events.triggerEvent("tileloaded",{tile:a,aborted:b});if(!this.singleTile&&!b&&this.backBuffer&&this.gridResolution===this.backBufferResolution){var c=a.getTile();if("none"===OpenLayers.Element.getStyle(c,"display")){var d=document.getElementById(a.id+"_bb");d&&d.parentNode.removeChild(d)}OpenLayers.Element.removeClass(c,
+"olTileReplacing")}if(0===this.numLoadingTiles){if(this.backBuffer)if(0===this.backBuffer.childNodes.length)this.removeBackBuffer();else{this._transitionElement=b?this.div.lastChild:a.imgDiv;b=this.transitionendEvents;for(c=b.length-1;0<=c;--c)OpenLayers.Event.observe(this._transitionElement,b[c],this._removeBackBuffer);this.backBufferTimerId=window.setTimeout(this._removeBackBuffer,this.removeBackBufferDelay)}this.loading=!1;this.events.triggerEvent("loadend")}};a.onLoadError=function(){this.events.triggerEvent("tileerror",
+{tile:a})};a.events.on({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},removeTileMonitoringHooks:function(a){a.unload();a.events.un({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},moveGriddedTiles:function(){for(var a=this.buffer+1;;){var b=this.grid[0][0],c=b.position.x+this.map.layerContainerOriginPx.x,b=b.position.y+this.map.layerContainerOriginPx.y,d=this.getServerResolution()/this.map.getResolution(),
+d={w:Math.round(this.tileSize.w*d),h:Math.round(this.tileSize.h*d)};if(c>-d.w*(a-1))this.shiftColumn(!0,d);else if(c<-d.w*a)this.shiftColumn(!1,d);else if(b>-d.h*(a-1))this.shiftRow(!0,d);else if(b<-d.h*a)this.shiftRow(!1,d);else break}},shiftRow:function(a,b){var c=this.grid,d=a?0:c.length-1,e=a?-1:1;this.gridLayout.startrow+=e*this.rowSign;for(var f=c[d],g=c[a?"pop":"shift"](),h=0,k=g.length;h<k;h++){var l=g[h],m=f[h].position.clone();m.y+=b.h*e;l.moveTo(this.getTileBoundsForGridIndex(d,h),m)}c[a?
+"unshift":"push"](g)},shiftColumn:function(a,b){var c=this.grid,d=a?0:c[0].length-1,e=a?-1:1;this.gridLayout.startcol+=e;for(var f=0,g=c.length;f<g;f++){var h=c[f],k=h[d].position.clone(),l=h[a?"pop":"shift"]();k.x+=b.w*e;l.moveTo(this.getTileBoundsForGridIndex(f,d),k);h[a?"unshift":"push"](l)}},removeExcessTiles:function(a,b){for(var c,d;this.grid.length>a;){var e=this.grid.pop();c=0;for(d=e.length;c<d;c++){var f=e[c];this.destroyTile(f)}}c=0;for(d=this.grid.length;c<d;c++)for(;this.grid[c].length>
+b;)e=this.grid[c],f=e.pop(),this.destroyTile(f)},onMapResize:function(){this.singleTile&&(this.clearGrid(),this.setTileSize())},getTileBounds:function(a){var b=this.maxExtent,c=this.getResolution(),d=c*this.tileSize.w,c=c*this.tileSize.h,e=this.getLonLatFromViewPortPx(a);a=b.left+d*Math.floor((e.lon-b.left)/d);b=b.bottom+c*Math.floor((e.lat-b.bottom)/c);return new OpenLayers.Bounds(a,b,a+d,b+c)},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Layer.XYZ=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,sphericalMercator:!1,zoomOffset:0,serverResolutions:null,initialize:function(a,b,c){if(c&&c.sphericalMercator||this.sphericalMercator)c=OpenLayers.Util.extend({projection:"EPSG:900913",numZoomLevels:19},c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a||this.name,b||this.url,{},c])},clone:function(a){null==a&&(a=new OpenLayers.Layer.XYZ(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,
+[a])},getURL:function(a){a=this.getXYZ(a);var b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(""+a.x+a.y+a.z,b));return OpenLayers.String.format(b,a)},getXYZ:function(a){var b=this.getServerResolution(),c=Math.round((a.left-this.maxExtent.left)/(b*this.tileSize.w));a=Math.round((this.maxExtent.top-a.top)/(b*this.tileSize.h));b=this.getServerZoom();if(this.wrapDateLine)var d=Math.pow(2,b),c=(c%d+d)%d;return{x:c,y:a,z:b}},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,
+arguments);this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.bottom))},CLASS_NAME:"OpenLayers.Layer.XYZ"});OpenLayers.Layer.OSM=OpenLayers.Class(OpenLayers.Layer.XYZ,{name:"OpenStreetMap",url:["http://a.tile.openstreetmap.org/${z}/${x}/${y}.png","http://b.tile.openstreetmap.org/${z}/${x}/${y}.png","http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"],attribution:"&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",sphericalMercator:!0,wrapDateLine:!0,tileOptions:null,initialize:function(a,b,c){OpenLayers.Layer.XYZ.prototype.initialize.apply(this,arguments);this.tileOptions=
+OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options&&this.options.tileOptions)},clone:function(a){null==a&&(a=new OpenLayers.Layer.OSM(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Layer.Bing=OpenLayers.Class(OpenLayers.Layer.XYZ,{key:null,serverResolutions:[156543.03390625,78271.516953125,39135.7584765625,19567.87923828125,9783.939619140625,4891.9698095703125,2445.9849047851562,1222.9924523925781,611.4962261962891,305.74811309814453,152.87405654907226,76.43702827453613,38.218514137268066,19.109257068634033,9.554628534317017,4.777314267158508,2.388657133579254,1.194328566789627,0.5971642833948135,0.29858214169740677,0.14929107084870338,0.07464553542435169],attributionTemplate:'<span class="olBingAttribution ${type}"><div><a target="_blank" href="http://www.bing.com/maps/"><img src="${logo}" /></a></div>${copyrights}<a style="white-space: nowrap" target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a></span>',
+metadata:null,protocolRegex:/^http:/i,type:"Road",culture:"en-US",metadataParams:null,tileOptions:null,protocol:~window.location.href.indexOf("http")?"":"http:",initialize:function(a){a=OpenLayers.Util.applyDefaults({sphericalMercator:!0},a);OpenLayers.Layer.XYZ.prototype.initialize.apply(this,[a.name||"Bing "+(a.type||this.type),null,a]);this.tileOptions=OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options.tileOptions);this.loadMetadata()},loadMetadata:function(){this._callbackId=
+"_callback_"+this.id.replace(/\./g,"_");window[this._callbackId]=OpenLayers.Function.bind(OpenLayers.Layer.Bing.processMetadata,this);var a=OpenLayers.Util.applyDefaults({key:this.key,jsonp:this._callbackId,include:"ImageryProviders"},this.metadataParams),a=this.protocol+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+this.type+"?"+OpenLayers.Util.getParameterString(a),b=document.createElement("script");b.type="text/javascript";b.src=a;b.id=this._callbackId;document.getElementsByTagName("head")[0].appendChild(b)},
+initLayer:function(){var a=this.metadata.resourceSets[0].resources[0],b=a.imageUrl.replace("{quadkey}","${quadkey}"),b=b.replace("{culture}",this.culture),b=b.replace(this.protocolRegex,this.protocol);this.url=[];for(var c=0;c<a.imageUrlSubdomains.length;++c)this.url.push(b.replace("{subdomain}",a.imageUrlSubdomains[c]));this.addOptions({maxResolution:Math.min(this.serverResolutions[a.zoomMin],this.maxResolution||Number.POSITIVE_INFINITY),numZoomLevels:Math.min(a.zoomMax+1-a.zoomMin,this.numZoomLevels)},
+!0);this.isBaseLayer||this.redraw();this.updateAttribution()},getURL:function(a){if(this.url){var b=this.getXYZ(a);a=b.x;for(var c=b.y,b=b.z,d=[],e=b;0<e;--e){var f="0",g=1<<e-1;0!=(a&g)&&f++;0!=(c&g)&&(f++,f++);d.push(f)}d=d.join("");a=this.selectUrl(""+a+c+b,this.url);return OpenLayers.String.format(a,{quadkey:d})}},updateAttribution:function(){var a=this.metadata;if(a.resourceSets&&this.map&&this.map.center){var b=a.resourceSets[0].resources[0],c=this.map.getExtent().transform(this.map.getProjectionObject(),
+new OpenLayers.Projection("EPSG:4326")),d=b.imageryProviders||[],e=OpenLayers.Util.indexOf(this.serverResolutions,this.getServerResolution()),b="",f,g,h,k,l,m,r;g=0;for(h=d.length;g<h;++g)for(f=d[g],k=0,l=f.coverageAreas.length;k<l;++k)r=f.coverageAreas[k],m=OpenLayers.Bounds.fromArray(r.bbox,!0),c.intersectsBounds(m)&&(e<=r.zoomMax&&e>=r.zoomMin)&&(b+=f.attribution+" ");a=a.brandLogoUri.replace(this.protocolRegex,this.protocol);this.attribution=OpenLayers.String.format(this.attributionTemplate,{type:this.type.toLowerCase(),
+logo:a,copyrights:b});this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"attribution"})}},setMap:function(){OpenLayers.Layer.XYZ.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.updateAttribution)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Bing(this.options));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},destroy:function(){this.map&&this.map.events.unregister("moveend",this,this.updateAttribution);OpenLayers.Layer.XYZ.prototype.destroy.apply(this,
+arguments)},CLASS_NAME:"OpenLayers.Layer.Bing"});OpenLayers.Layer.Bing.processMetadata=function(a){this.metadata=a;this.initLayer();a=document.getElementById(this._callbackId);a.parentNode.removeChild(a);window[this._callbackId]=void 0;delete this._callbackId};OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:!1,evt:null,touch:!1,initialize:function(a,b,c){OpenLayers.Util.extend(this,c);this.control=a;this.callbacks=b;(a=this.map||a.map)&&this.setMap(a);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},setMap:function(a){this.map=a},checkModifiers:function(a){return null==this.keyMask?!0:((a.shiftKey?OpenLayers.Handler.MOD_SHIFT:0)|(a.ctrlKey?OpenLayers.Handler.MOD_CTRL:0)|(a.altKey?OpenLayers.Handler.MOD_ALT:
+0)|(a.metaKey?OpenLayers.Handler.MOD_META:0))==this.keyMask},activate:function(){if(this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.register(a[b],this[a[b]]);return this.active=!0},deactivate:function(){if(!this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]]);this.active=this.touch=!1;return!0},startTouch:function(){if(!this.touch){this.touch=!0;
+for(var a="mousedown mouseup mousemove click dblclick mouseout".split(" "),b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]])}},callback:function(a,b){a&&this.callbacks[a]&&this.callbacks[a].apply(this.control,b)},register:function(a,b){this.map.events.registerPriority(a,this,b);this.map.events.registerPriority(a,this,this.setEvent)},unregister:function(a,b){this.map.events.unregister(a,this,b);this.map.events.unregister(a,this,this.setEvent)},setEvent:function(a){this.evt=a;return!0},
+destroy:function(){this.deactivate();this.control=this.map=null},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Handler.MOD_META=8;OpenLayers.Handler.MouseWheel=OpenLayers.Class(OpenLayers.Handler,{wheelListener:null,interval:0,maxDelta:Number.POSITIVE_INFINITY,delta:0,cumulative:!0,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.wheelListener=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this)},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.wheelListener=null},onWheelEvent:function(a){if(this.map&&this.checkModifiers(a)){for(var b=
+!1,c=!1,d=!1,e=OpenLayers.Event.element(a);null!=e&&!d&&!b;){if(!b)try{var f,b=(f=e.currentStyle?e.currentStyle.overflow:document.defaultView.getComputedStyle(e,null).getPropertyValue("overflow"))&&"auto"==f||"scroll"==f}catch(g){}if(!c&&(c=OpenLayers.Element.hasClass(e,"olScrollable"),!c))for(var d=0,h=this.map.layers.length;d<h;d++){var k=this.map.layers[d];if(e==k.div||e==k.pane){c=!0;break}}d=e==this.map.div;e=e.parentNode}if(!b&&d){if(c)if(b=0,a.wheelDelta?(b=a.wheelDelta,0===b%160&&(b*=0.75),
+b/=120):a.detail&&(b=-(a.detail/Math.abs(a.detail))),this.delta+=b,window.clearTimeout(this._timeoutId),this.interval&&Math.abs(this.delta)<this.maxDelta){var l=OpenLayers.Util.extend({},a);this._timeoutId=window.setTimeout(OpenLayers.Function.bind(function(){this.wheelZoom(l)},this),this.interval)}else this.wheelZoom(a);OpenLayers.Event.stop(a)}}},wheelZoom:function(a){var b=this.delta;this.delta=0;b&&(a.xy=this.map.events.getMousePosition(a),0>b?this.callback("down",[a,this.cumulative?Math.max(-this.maxDelta,
+b):-1]):this.callback("up",[a,this.cumulative?Math.min(this.maxDelta,b):1]))},activate:function(a){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var b=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",b);OpenLayers.Event.observe(window,"mousewheel",b);OpenLayers.Event.observe(document,"mousewheel",b);return!0}return!1},deactivate:function(a){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var b=this.wheelListener;OpenLayers.Event.stopObserving(window,
+"DOMMouseScroll",b);OpenLayers.Event.stopObserving(window,"mousewheel",b);OpenLayers.Event.stopObserving(document,"mousewheel",b);return!0}return!1},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Geometry.MultiLineString=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LineString"],split:function(a,b){for(var c=null,d=b&&b.mutual,e,f,g,h,k=[],l=[a],m=0,r=this.components.length;m<r;++m){f=this.components[m];g=!1;for(var p=0;p<l.length;++p)if(e=f.split(l[p],b)){if(d){g=e[0];for(var n=0,q=g.length;n<q;++n)0===n&&k.length?k[k.length-1].addComponent(g[n]):k.push(new OpenLayers.Geometry.MultiLineString([g[n]]));g=!0;e=e[1]}if(e.length){e.unshift(p,
+1);Array.prototype.splice.apply(l,e);break}}g||(k.length?k[k.length-1].addComponent(f.clone()):k=[new OpenLayers.Geometry.MultiLineString(f.clone())])}k&&1<k.length?g=!0:k=[];l&&1<l.length?h=!0:l=[];if(g||h)c=d?[k,l]:l;return c},splitWith:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h,k,l;if(a instanceof OpenLayers.Geometry.LineString){l=[];k=[a];for(var m=0,r=this.components.length;m<r;++m){g=!1;f=this.components[m];for(var p=0;p<k.length;++p)if(e=k[p].split(f,b)){d&&(g=e[0],g.length&&(g.unshift(p,
+1),Array.prototype.splice.apply(k,g),p+=g.length-2),e=e[1],0===e.length&&(e=[f.clone()]));g=0;for(var n=e.length;g<n;++g)0===g&&l.length?l[l.length-1].addComponent(e[g]):l.push(new OpenLayers.Geometry.MultiLineString([e[g]]));g=!0}g||(l.length?l[l.length-1].addComponent(f.clone()):l=[new OpenLayers.Geometry.MultiLineString([f.clone()])])}}else c=a.split(this);k&&1<k.length?h=!0:k=[];l&&1<l.length?g=!0:l=[];if(h||g)c=d?[k,l]:l;return c},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:!1,size:null,resolution:null,map:null,featureDx:0,initialize:function(a,b){this.container=OpenLayers.Util.getElement(a);OpenLayers.Util.extend(this,b)},destroy:function(){this.map=this.resolution=this.size=this.extent=this.container=null},supported:function(){return!1},setExtent:function(a,b){this.extent=a.clone();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var c=a.getWidth()/this.map.getExtent().getWidth();
+a=a.scale(1/c);this.extent=a.wrapDateLine(this.map.getMaxExtent()).scale(c)}b&&(this.resolution=null);return!0},setSize:function(a){this.size=a.clone();this.resolution=null},getResolution:function(){return this.resolution=this.resolution||this.map.getResolution()},drawFeature:function(a,b){null==b&&(b=a.style);if(a.geometry){var c=a.geometry.getBounds();if(c){var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());c.intersectsBounds(this.extent,{worldBounds:d})?this.calculateFeatureDx(c,
+d):b={display:"none"};c=this.drawGeometry(a.geometry,b,a.id);if("none"!=b.display&&b.label&&!1!==c){d=a.geometry.getCentroid();if(b.labelXOffset||b.labelYOffset){var e=isNaN(b.labelXOffset)?0:b.labelXOffset,f=isNaN(b.labelYOffset)?0:b.labelYOffset,g=this.getResolution();d.move(e*g,f*g)}this.drawText(a.id,b,d)}else this.removeText(a.id);return c}}},calculateFeatureDx:function(a,b){this.featureDx=0;if(b){var c=b.getWidth();this.featureDx=Math.round(((a.left+a.right)/2-(this.extent.left+this.extent.right)/
+2)/c)*c}},drawGeometry:function(a,b,c){},drawText:function(a,b,c){},removeText:function(a){},clear:function(){},getFeatureIdFromEvent:function(a){},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<c;++b){var d=a[b];this.eraseGeometry(d.geometry,d.id);this.removeText(d.id)}},eraseGeometry:function(a,b){},moveRoot:function(a){},getRenderLayerId:function(){return this.container.id},applyDefaultSymbolizer:function(a){var b=OpenLayers.Util.extend({},OpenLayers.Renderer.defaultSymbolizer);
+!1===a.stroke&&(delete b.strokeWidth,delete b.strokeColor);!1===a.fill&&delete b.fillColor;OpenLayers.Util.extend(b,a);return b},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Renderer.defaultSymbolizer={fillColor:"#000000",strokeColor:"#000000",strokeWidth:2,fillOpacity:1,strokeOpacity:1,pointRadius:0,labelAlign:"cm"};
+OpenLayers.Renderer.symbol={star:[350,75,379,161,469,161,397,215,423,301,350,250,277,301,303,215,231,161,321,161,350,75],cross:[4,0,6,0,6,4,10,4,10,6,6,6,6,10,4,10,4,6,0,6,0,4,4,4,4,0],x:[0,0,25,0,50,35,75,0,100,0,65,50,100,100,75,100,50,65,25,100,0,100,35,50,0,0],square:[0,0,0,1,1,1,1,0,0,0],triangle:[0,10,10,10,5,0,0,10]};OpenLayers.ElementsIndexer=OpenLayers.Class({maxZIndex:null,order:null,indices:null,compare:null,initialize:function(a){this.compare=a?OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER:OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;this.clear()},insert:function(a){this.exists(a)&&this.remove(a);var b=a.id;this.determineZIndex(a);for(var c=-1,d=this.order.length,e;1<d-c;)e=parseInt((c+d)/2),0<this.compare(this,a,OpenLayers.Util.getElement(this.order[e]))?c=e:d=e;this.order.splice(d,
+0,b);this.indices[b]=this.getZIndex(a);return this.getNextElement(d)},remove:function(a){a=a.id;var b=OpenLayers.Util.indexOf(this.order,a);0<=b&&(this.order.splice(b,1),delete this.indices[a],this.maxZIndex=0<this.order.length?this.indices[this.order[this.order.length-1]]:0)},clear:function(){this.order=[];this.indices={};this.maxZIndex=0},exists:function(a){return null!=this.indices[a.id]},getZIndex:function(a){return a._style.graphicZIndex},determineZIndex:function(a){var b=a._style.graphicZIndex;
+null==b?(b=this.maxZIndex,a._style.graphicZIndex=b):b>this.maxZIndex&&(this.maxZIndex=b)},getNextElement:function(a){a+=1;if(a<this.order.length){var b=OpenLayers.Util.getElement(this.order[a]);void 0==b&&(b=this.getNextElement(a));return b}return null},CLASS_NAME:"OpenLayers.ElementsIndexer"});
+OpenLayers.ElementsIndexer.IndexingMethods={Z_ORDER:function(a,b,c){b=a.getZIndex(b);var d=0;c&&(a=a.getZIndex(c),d=b-a);return d},Z_ORDER_DRAWING_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0==a&&(a=1);return a},Z_ORDER_Y_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0===a&&(b=c._boundsBottom-b._boundsBottom,a=0===b?1:b);return a}};
+OpenLayers.Renderer.Elements=OpenLayers.Class(OpenLayers.Renderer,{rendererRoot:null,root:null,vectorRoot:null,textRoot:null,xmlns:null,xOffset:0,indexer:null,BACKGROUND_ID_SUFFIX:"_background",LABEL_ID_SUFFIX:"_label",LABEL_OUTLINE_SUFFIX:"_outline",initialize:function(a,b){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.rendererRoot=this.createRenderRoot();this.root=this.createRoot("_root");this.vectorRoot=this.createRoot("_vroot");this.textRoot=this.createRoot("_troot");this.root.appendChild(this.vectorRoot);
+this.root.appendChild(this.textRoot);this.rendererRoot.appendChild(this.root);this.container.appendChild(this.rendererRoot);b&&(b.zIndexing||b.yOrdering)&&(this.indexer=new OpenLayers.ElementsIndexer(b.yOrdering))},destroy:function(){this.clear();this.xmlns=this.root=this.rendererRoot=null;OpenLayers.Renderer.prototype.destroy.apply(this,arguments)},clear:function(){var a,b=this.vectorRoot;if(b)for(;a=b.firstChild;)b.removeChild(a);if(b=this.textRoot)for(;a=b.firstChild;)b.removeChild(a);this.indexer&&
+this.indexer.clear()},setExtent:function(a,b){var c=OpenLayers.Renderer.prototype.setExtent.apply(this,arguments),d=this.getResolution();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var e,f=a.getWidth()/this.map.getExtent().getWidth();a=a.scale(1/f);f=this.map.getMaxExtent();f.right>a.left&&f.right<a.right?e=!0:f.left>a.left&&f.left<a.right&&(e=!1);if(e!==this.rightOfDateLine||b)c=!1,this.xOffset=!0===e?f.getWidth()/d:0;this.rightOfDateLine=e}return c},getNodeType:function(a,b){},drawGeometry:function(a,
+b,c){var d=a.CLASS_NAME,e=!0;if("OpenLayers.Geometry.Collection"==d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d){for(var d=0,f=a.components.length;d<f;d++)e=this.drawGeometry(a.components[d],b,c)&&e;return e}d=e=!1;"none"!=b.display&&(b.backgroundGraphic?this.redrawBackgroundNode(a.id,a,b,c):d=!0,e=this.redrawNode(a.id,a,b,c));!1==e&&(b=document.getElementById(a.id))&&(b._style.backgroundGraphic&&(d=!0),b.parentNode.removeChild(b));
+d&&(b=document.getElementById(a.id+this.BACKGROUND_ID_SUFFIX))&&b.parentNode.removeChild(b);return e},redrawNode:function(a,b,c,d){c=this.applyDefaultSymbolizer(c);a=this.nodeFactory(a,this.getNodeType(b,c));a._featureId=d;a._boundsBottom=b.getBounds().bottom;a._geometryClass=b.CLASS_NAME;a._style=c;b=this.drawGeometryNode(a,b,c);if(!1===b)return!1;a=b.node;this.indexer?(c=this.indexer.insert(a))?this.vectorRoot.insertBefore(a,c):this.vectorRoot.appendChild(a):a.parentNode!==this.vectorRoot&&this.vectorRoot.appendChild(a);
+this.postDraw(a);return b.complete},redrawBackgroundNode:function(a,b,c,d){c=OpenLayers.Util.extend({},c);c.externalGraphic=c.backgroundGraphic;c.graphicXOffset=c.backgroundXOffset;c.graphicYOffset=c.backgroundYOffset;c.graphicZIndex=c.backgroundGraphicZIndex;c.graphicWidth=c.backgroundWidth||c.graphicWidth;c.graphicHeight=c.backgroundHeight||c.graphicHeight;c.backgroundGraphic=null;c.backgroundXOffset=null;c.backgroundYOffset=null;c.backgroundGraphicZIndex=null;return this.redrawNode(a+this.BACKGROUND_ID_SUFFIX,
+b,c,null)},drawGeometryNode:function(a,b,c){c=c||a._style;var d={isFilled:void 0===c.fill?!0:c.fill,isStroked:void 0===c.stroke?!!c.strokeWidth:c.stroke},e;switch(b.CLASS_NAME){case "OpenLayers.Geometry.Point":!1===c.graphic&&(d.isFilled=!1,d.isStroked=!1);e=this.drawPoint(a,b);break;case "OpenLayers.Geometry.LineString":d.isFilled=!1;e=this.drawLineString(a,b);break;case "OpenLayers.Geometry.LinearRing":e=this.drawLinearRing(a,b);break;case "OpenLayers.Geometry.Polygon":e=this.drawPolygon(a,b);break;
+case "OpenLayers.Geometry.Rectangle":e=this.drawRectangle(a,b)}a._options=d;return!1!=e?{node:this.setStyle(a,c,d,b),complete:e}:!1},postDraw:function(a){},drawPoint:function(a,b){},drawLineString:function(a,b){},drawLinearRing:function(a,b){},drawPolygon:function(a,b){},drawRectangle:function(a,b){},drawCircle:function(a,b){},removeText:function(a){var b=document.getElementById(a+this.LABEL_ID_SUFFIX);b&&this.textRoot.removeChild(b);(a=document.getElementById(a+this.LABEL_OUTLINE_SUFFIX))&&this.textRoot.removeChild(a)},
+getFeatureIdFromEvent:function(a){var b=a.target,c=b&&b.correspondingUseElement;return(c?c:b||a.srcElement)._featureId},eraseGeometry:function(a,b){if("OpenLayers.Geometry.MultiPoint"==a.CLASS_NAME||"OpenLayers.Geometry.MultiLineString"==a.CLASS_NAME||"OpenLayers.Geometry.MultiPolygon"==a.CLASS_NAME||"OpenLayers.Geometry.Collection"==a.CLASS_NAME)for(var c=0,d=a.components.length;c<d;c++)this.eraseGeometry(a.components[c],b);else(c=OpenLayers.Util.getElement(a.id))&&c.parentNode&&(c.geometry&&(c.geometry.destroy(),
+c.geometry=null),c.parentNode.removeChild(c),this.indexer&&this.indexer.remove(c),c._style.backgroundGraphic&&(c=OpenLayers.Util.getElement(a.id+this.BACKGROUND_ID_SUFFIX))&&c.parentNode&&c.parentNode.removeChild(c))},nodeFactory:function(a,b){var c=OpenLayers.Util.getElement(a);c?this.nodeTypeCompare(c,b)||(c.parentNode.removeChild(c),c=this.nodeFactory(a,b)):c=this.createNode(b,a);return c},nodeTypeCompare:function(a,b){},createNode:function(a,b){},moveRoot:function(a){var b=this.root;a.root.parentNode==
+this.rendererRoot&&(b=a.root);b.parentNode.removeChild(b);a.rendererRoot.appendChild(b)},getRenderLayerId:function(){return this.root.parentNode.parentNode.id},isComplexSymbol:function(a){return"circle"!=a&&!!a},CLASS_NAME:"OpenLayers.Renderer.Elements"});OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:!1,displayClass:"",title:"",autoActivate:!1,active:null,handlerOptions:null,handler:null,eventListeners:null,events:null,initialize:function(a){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,a);this.events=new OpenLayers.Events(this);if(this.eventListeners instanceof Object)this.events.on(this.eventListeners);null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+
+"_"))},destroy:function(){this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy(),this.events=null);this.eventListeners=null;this.handler&&(this.handler.destroy(),this.handler=null);if(this.handlers){for(var a in this.handlers)this.handlers.hasOwnProperty(a)&&"function"==typeof this.handlers[a].destroy&&this.handlers[a].destroy();this.handlers=null}this.map&&(this.map.removeControl(this),this.map=null);this.div=null},setMap:function(a){this.map=a;this.handler&&
+this.handler.setMap(a)},draw:function(a){null==this.div&&(this.div=OpenLayers.Util.createDiv(this.id),this.div.className=this.displayClass,this.allowSelection||(this.div.className+=" olControlNoSelect",this.div.setAttribute("unselectable","on",0),this.div.onselectstart=OpenLayers.Function.False),""!=this.title&&(this.div.title=this.title));null!=a&&(this.position=a.clone());this.moveTo(this.position);return this.div},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"px",this.div.style.top=
+a.y+"px")},activate:function(){if(this.active)return!1;this.handler&&this.handler.activate();this.active=!0;this.map&&OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");this.events.triggerEvent("activate");return!0},deactivate:function(){return this.active?(this.handler&&this.handler.deactivate(),this.active=!1,this.map&&OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active"),this.events.triggerEvent("deactivate"),
+!0):!1},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,autoActivate:!0,defaultControl:null,saveState:!1,allowDepress:!1,activeState:null,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,[a]);this.controls=[];this.activeState={}},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onButtonClick);OpenLayers.Control.prototype.destroy.apply(this,arguments);for(var a,b=this.controls.length-1;0<=b;b--)a=this.controls[b],a.events&&
+a.events.un({activate:this.iconOn,deactivate:this.iconOff}),a.panel_div=null;this.activeState=null},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){for(var a,b=0,c=this.controls.length;b<c;b++)a=this.controls[b],(a===this.defaultControl||this.saveState&&this.activeState[a.id])&&a.activate();!0===this.saveState&&(this.defaultControl=null);this.redraw();return!0}return!1},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){for(var a,
+b=0,c=this.controls.length;b<c;b++)a=this.controls[b],this.activeState[a.id]=a.deactivate();this.redraw();return!0}return!1},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",this,this.onButtonClick)):this.map.events.register("buttonclick",this,this.onButtonClick);this.addControlsToMap(this.controls);return this.div},redraw:function(){for(var a=this.div.childNodes.length-1;0<=a;a--)this.div.removeChild(this.div.childNodes[a]);
+this.div.innerHTML="";if(this.active)for(var a=0,b=this.controls.length;a<b;a++)this.div.appendChild(this.controls[a].panel_div)},activateControl:function(a){if(!this.active)return!1;if(a.type==OpenLayers.Control.TYPE_BUTTON)a.trigger();else if(a.type==OpenLayers.Control.TYPE_TOGGLE)a.active?a.deactivate():a.activate();else if(this.allowDepress&&a.active)a.deactivate();else{for(var b,c=0,d=this.controls.length;c<d;c++)b=this.controls[c],b==a||b.type!==OpenLayers.Control.TYPE_TOOL&&null!=b.type||b.deactivate();
+a.activate()}},addControls:function(a){OpenLayers.Util.isArray(a)||(a=[a]);this.controls=this.controls.concat(a);for(var b=0,c=a.length;b<c;b++){var d=a[b],e=this.createControlMarkup(d);OpenLayers.Element.addClass(e,d.displayClass+"ItemInactive");OpenLayers.Element.addClass(e,"olButton");""==d.title||e.title||(e.title=d.title);d.panel_div=e}this.map&&(this.addControlsToMap(a),this.redraw())},createControlMarkup:function(a){return document.createElement("div")},addControlsToMap:function(a){for(var b,
+c=0,d=a.length;c<d;c++)b=a[c],!0===b.autoActivate?(b.autoActivate=!1,this.map.addControl(b),b.autoActivate=!0):(this.map.addControl(b),b.deactivate()),b.events.on({activate:this.iconOn,deactivate:this.iconOff})},iconOn:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Inactive\\b"),"$1Active")},iconOff:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Active\\b"),"$1Inactive")},onButtonClick:function(a){var b=
+this.controls;a=a.buttonElement;for(var c=b.length-1;0<=c;--c)if(b[c].panel_div===a){this.activateControl(b[c]);break}},getControlsBy:function(a,b){var c="function"==typeof b.test;return OpenLayers.Array.filter(this.controls,function(d){return d[a]==b||c&&b.test(d[a])})},getControlsByName:function(a){return this.getControlsBy("name",a)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},CLASS_NAME:"OpenLayers.Control.Panel"});OpenLayers.Strategy=OpenLayers.Class({layer:null,options:null,active:null,autoActivate:!0,autoDestroy:!0,initialize:function(a){OpenLayers.Util.extend(this,a);this.options=a;this.active=!1},destroy:function(){this.deactivate();this.options=this.layer=null},setLayer:function(a){this.layer=a},activate:function(){return this.active?!1:this.active=!0},deactivate:function(){return this.active?(this.active=!1,!0):!1},CLASS_NAME:"OpenLayers.Strategy"});OpenLayers.Strategy.Fixed=OpenLayers.Class(OpenLayers.Strategy,{preload:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.apply(this,arguments);if(a)if(this.layer.events.on({refresh:this.load,scope:this}),!0==this.layer.visibility||this.preload)this.load();else this.layer.events.on({visibilitychanged:this.load,scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.layer.events.un({refresh:this.load,visibilitychanged:this.load,
+scope:this});return a},load:function(a){var b=this.layer;b.events.triggerEvent("loadstart",{filter:b.filter});b.protocol.read(OpenLayers.Util.applyDefaults({callback:this.merge,filter:b.filter,scope:this},a));b.events.un({visibilitychanged:this.load,scope:this})},merge:function(a){var b=this.layer;b.destroyFeatures();var c=a.features;if(c&&0<c.length){var d=b.projection,e=b.map.getProjectionObject();if(!e.equals(d))for(var f,g=0,h=c.length;g<h;++g)(f=c[g].geometry)&&f.transform(d,e);b.addFeatures(c)}b.events.triggerEvent("loadend",
+{response:a})},CLASS_NAME:"OpenLayers.Strategy.Fixed"});OpenLayers.Control.Zoom=OpenLayers.Class(OpenLayers.Control,{zoomInText:"+",zoomInId:"olZoomInLink",zoomOutText:"\u2212",zoomOutId:"olZoomOutLink",draw:function(){var a=OpenLayers.Control.prototype.draw.apply(this),b=this.getOrCreateLinks(a),c=b.zoomIn,b=b.zoomOut,d=this.map.events;b.parentNode!==a&&(d=this.events,d.attachToElement(b.parentNode));d.register("buttonclick",this,this.onZoomClick);this.zoomInLink=c;this.zoomOutLink=b;return a},getOrCreateLinks:function(a){var b=document.getElementById(this.zoomInId),
+c=document.getElementById(this.zoomOutId);b||(b=document.createElement("a"),b.href="#zoomIn",b.appendChild(document.createTextNode(this.zoomInText)),b.className="olControlZoomIn",a.appendChild(b));OpenLayers.Element.addClass(b,"olButton");c||(c=document.createElement("a"),c.href="#zoomOut",c.appendChild(document.createTextNode(this.zoomOutText)),c.className="olControlZoomOut",a.appendChild(c));OpenLayers.Element.addClass(c,"olButton");return{zoomIn:b,zoomOut:c}},onZoomClick:function(a){a=a.buttonElement;
+a===this.zoomInLink?this.map.zoomIn():a===this.zoomOutLink&&this.map.zoomOut()},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onZoomClick);delete this.zoomInLink;delete this.zoomOutLink;OpenLayers.Control.prototype.destroy.apply(this)},CLASS_NAME:"OpenLayers.Control.Zoom"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],getArea:function(){var a=0;if(this.components&&0<this.components.length)for(var a=a+Math.abs(this.components[0].getArea()),b=1,c=this.components.length;b<c;b++)a-=Math.abs(this.components[b].getArea());return a},getGeodesicArea:function(a){var b=0;if(this.components&&0<this.components.length)for(var b=b+Math.abs(this.components[0].getGeodesicArea(a)),c=1,d=this.components.length;c<
+d;c++)b-=Math.abs(this.components[c].getGeodesicArea(a));return b},containsPoint:function(a){var b=this.components.length,c=!1;if(0<b&&(c=this.components[0].containsPoint(a),1!==c&&c&&1<b))for(var d,e=1;e<b;++e)if(d=this.components[e].containsPoint(a)){c=1===d?1:!1;break}return c},intersects:function(a){var b=!1,c,d;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME||"OpenLayers.Geometry.LinearRing"==a.CLASS_NAME){c=0;for(d=
+this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);if(!b)for(c=0,d=a.components.length;c<d&&!(b=this.containsPoint(a.components[c]));++c);}else for(c=0,d=a.components.length;c<d&&!(b=this.intersects(a.components[c]));++c);if(!b&&"OpenLayers.Geometry.Polygon"==a.CLASS_NAME){var e=this.components[0];c=0;for(d=e.components.length;c<d&&!(b=a.containsPoint(e.components[c]));++c);}return b},distanceTo:function(a,b){return b&&!1===b.edge&&this.intersects(a)?0:OpenLayers.Geometry.Collection.prototype.distanceTo.apply(this,
+[a,b])},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(a,b,c,d){var e=Math.PI*(1/c-0.5);d&&(e+=d/180*Math.PI);for(var f,g=[],h=0;h<c;++h)f=e+2*h*Math.PI/c,d=a.x+b*Math.cos(f),f=a.y+b*Math.sin(f),g.push(new OpenLayers.Geometry.Point(d,f));a=new OpenLayers.Geometry.LinearRing(g);return new OpenLayers.Geometry.Polygon([a])};OpenLayers.Geometry.MultiPolygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Polygon"],CLASS_NAME:"OpenLayers.Geometry.MultiPolygon"});OpenLayers.Feature=OpenLayers.Class({layer:null,id:null,lonlat:null,data:null,marker:null,popupClass:null,popup:null,initialize:function(a,b,c){this.layer=a;this.lonlat=b;this.data=null!=c?c:{};this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){null!=this.layer&&null!=this.layer.map&&null!=this.popup&&this.layer.map.removePopup(this.popup);null!=this.layer&&null!=this.marker&&this.layer.removeMarker(this.marker);this.data=this.lonlat=this.id=this.layer=null;null!=this.marker&&
+(this.destroyMarker(this.marker),this.marker=null);null!=this.popup&&(this.destroyPopup(this.popup),this.popup=null)},onScreen:function(){var a=!1;null!=this.layer&&null!=this.layer.map&&(a=this.layer.map.getExtent().containsLonLat(this.lonlat));return a},createMarker:function(){null!=this.lonlat&&(this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon));return this.marker},destroyMarker:function(){this.marker.destroy()},createPopup:function(a){null!=this.lonlat&&(this.popup||(this.popup=new (this.popupClass?
+this.popupClass:OpenLayers.Popup.Anchored)(this.id+"_popup",this.lonlat,this.data.popupSize,this.data.popupContentHTML,this.marker?this.marker.icon:null,a)),null!=this.data.overflow&&(this.popup.contentDiv.style.overflow=this.data.overflow),this.popup.feature=this);return this.popup},destroyPopup:function(){this.popup&&(this.popup.feature=null,this.popup.destroy(),this.popup=null)},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.State={UNKNOWN:"Unknown",INSERT:"Insert",UPDATE:"Update",DELETE:"Delete"};
+OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,bounds:null,state:null,style:null,url:null,renderIntent:"default",modified:null,initialize:function(a,b,c){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,b]);this.lonlat=null;this.geometry=a?a:null;this.state=null;this.attributes={};b&&(this.attributes=OpenLayers.Util.extend(this.attributes,b));this.style=c?c:null},destroy:function(){this.layer&&(this.layer.removeFeatures(this),this.layer=
+null);this.modified=this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments)},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style)},onScreen:function(a){var b=!1;this.layer&&this.layer.map&&(b=this.layer.map.getExtent(),a?(a=this.geometry.getBounds(),b=b.intersectsBounds(a)):b=b.toGeometry().intersects(this.geometry));return b},getVisibility:function(){return!(this.style&&"none"==this.style.display||!this.layer||
+this.layer&&this.layer.styleMap&&"none"==this.layer.styleMap.createSymbolizer(this,this.renderIntent).display||this.layer&&!this.layer.getVisibility())},createMarker:function(){return null},destroyMarker:function(){},createPopup:function(){return null},atPoint:function(a,b,c){var d=!1;this.geometry&&(d=this.geometry.atPoint(a,b,c));return d},destroyPopup:function(){},move:function(a){if(this.layer&&this.geometry.move){a="OpenLayers.LonLat"==a.CLASS_NAME?this.layer.getViewPortPxFromLonLat(a):a;var b=
+this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()),c=this.layer.map.getResolution();this.geometry.move(c*(a.x-b.x),c*(b.y-a.y));this.layer.drawFeature(this);return b}},toState:function(a){if(a==OpenLayers.State.UPDATE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=a}else if(a==OpenLayers.State.INSERT)switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=a}else if(a==OpenLayers.State.DELETE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.UPDATE:this.state=
+a}else a==OpenLayers.State.UNKNOWN&&(this.state=a)},CLASS_NAME:"OpenLayers.Feature.Vector"});
+OpenLayers.Feature.Vector.style={"default":{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},select:{fillColor:"blue",fillOpacity:0.4,
+hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},temporary:{fillColor:"#66cccc",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#66cccc",strokeOpacity:1,
+strokeLinecap:"round",strokeWidth:2,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},"delete":{display:"none"}};OpenLayers.Style=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:!1,rules:null,context:null,defaultStyle:null,defaultsPerSymbolizer:!1,propertyStyles:null,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.rules=[];b&&b.rules&&this.addRules(b.rules);this.setDefaultStyle(a||OpenLayers.Feature.Vector.style["default"]);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){for(var a=0,b=this.rules.length;a<b;a++)this.rules[a].destroy(),
+this.rules[a]=null;this.defaultStyle=this.rules=null},createSymbolizer:function(a){for(var b=this.defaultsPerSymbolizer?{}:this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),a),c=this.rules,d,e=[],f=!1,g=0,h=c.length;g<h;g++)d=c[g],d.evaluate(a)&&(d instanceof OpenLayers.Rule&&d.elseFilter?e.push(d):(f=!0,this.applySymbolizer(d,b,a)));if(!1==f&&0<e.length)for(f=!0,g=0,h=e.length;g<h;g++)this.applySymbolizer(e[g],b,a);0<c.length&&!1==f&&(b.display="none");null!=b.label&&"string"!==typeof b.label&&
+(b.label=String(b.label));return b},applySymbolizer:function(a,b,c){var d=c.geometry?this.getSymbolizerPrefix(c.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];a=a.symbolizer[d]||a.symbolizer;!0===this.defaultsPerSymbolizer&&(d=this.defaultStyle,OpenLayers.Util.applyDefaults(a,{pointRadius:d.pointRadius}),!0!==a.stroke&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{strokeWidth:d.strokeWidth,strokeColor:d.strokeColor,strokeOpacity:d.strokeOpacity,strokeDashstyle:d.strokeDashstyle,strokeLinecap:d.strokeLinecap}),
+!0!==a.fill&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{fillColor:d.fillColor,fillOpacity:d.fillOpacity}),!0===a.graphic&&OpenLayers.Util.applyDefaults(a,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOffset:this.defaultStyle.graphicYOffset}));
+return this.createLiterals(OpenLayers.Util.extend(b,a),c)},createLiterals:function(a,b){var c=OpenLayers.Util.extend({},b.attributes||b.data);OpenLayers.Util.extend(c,this.context);for(var d in this.propertyStyles)a[d]=OpenLayers.Style.createLiteral(a[d],c,b,d);return a},findPropertyStyles:function(){var a={};this.addPropertyStyles(a,this.defaultStyle);for(var b=this.rules,c,d,e=0,f=b.length;e<f;e++){c=b[e].symbolizer;for(var g in c)if(d=c[g],"object"==typeof d)this.addPropertyStyles(a,d);else{this.addPropertyStyles(a,
+c);break}}return a},addPropertyStyles:function(a,b){var c,d;for(d in b)c=b[d],"string"==typeof c&&c.match(/\$\{\w+\}/)&&(a[d]=!0);return a},addRules:function(a){Array.prototype.push.apply(this.rules,a);this.propertyStyles=this.findPropertyStyles()},setDefaultStyle:function(a){this.defaultStyle=a;this.propertyStyles=this.findPropertyStyles()},getSymbolizerPrefix:function(a){for(var b=OpenLayers.Style.SYMBOLIZER_PREFIXES,c=0,d=b.length;c<d;c++)if(-1!=a.CLASS_NAME.indexOf(b[c]))return b[c]},clone:function(){var a=
+OpenLayers.Util.extend({},this);if(this.rules){a.rules=[];for(var b=0,c=this.rules.length;b<c;++b)a.rules.push(this.rules[b].clone())}a.context=this.context&&OpenLayers.Util.extend({},this.context);b=OpenLayers.Util.extend({},this.defaultStyle);return new OpenLayers.Style(b,a)},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(a,b,c,d){"string"==typeof a&&-1!=a.indexOf("${")&&(a=OpenLayers.String.format(a,b,[c,d]),a=isNaN(a)||!a?a:parseFloat(a));return a};
+OpenLayers.Style.SYMBOLIZER_PREFIXES=["Point","Line","Polygon","Text","Raster"];OpenLayers.Filter=OpenLayers.Class({initialize:function(a){OpenLayers.Util.extend(this,a)},destroy:function(){},evaluate:function(a){return!0},clone:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.CQL?OpenLayers.Format.CQL.prototype.write(this):Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Filter"});OpenLayers.Filter.Spatial=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,distance:null,distanceUnits:null,evaluate:function(a){var b=!1;switch(this.type){case OpenLayers.Filter.Spatial.BBOX:case OpenLayers.Filter.Spatial.INTERSECTS:if(a.geometry){var c=this.value;"OpenLayers.Bounds"==this.value.CLASS_NAME&&(c=this.value.toGeometry());a.geometry.intersects(c)&&(b=!0)}break;default:throw Error("evaluate is not implemented for this filter type.");}return b},clone:function(){var a=
+OpenLayers.Util.applyDefaults({value:this.value&&this.value.clone&&this.value.clone()},this);return new OpenLayers.Filter.Spatial(a)},CLASS_NAME:"OpenLayers.Filter.Spatial"});OpenLayers.Filter.Spatial.BBOX="BBOX";OpenLayers.Filter.Spatial.INTERSECTS="INTERSECTS";OpenLayers.Filter.Spatial.DWITHIN="DWITHIN";OpenLayers.Filter.Spatial.WITHIN="WITHIN";OpenLayers.Filter.Spatial.CONTAINS="CONTAINS";OpenLayers.Strategy.BBOX=OpenLayers.Class(OpenLayers.Strategy,{bounds:null,resolution:null,ratio:2,resFactor:null,response:null,activate:function(){var a=OpenLayers.Strategy.prototype.activate.call(this);a&&(this.layer.events.on({moveend:this.update,refresh:this.update,visibilitychanged:this.update,scope:this}),this.update());return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.layer.events.un({moveend:this.update,refresh:this.update,visibilitychanged:this.update,
+scope:this});return a},update:function(a){var b=this.getMapBounds();null!==b&&(a&&a.force||this.layer.visibility&&this.layer.calculateInRange()&&this.invalidBounds(b))&&(this.calculateBounds(b),this.resolution=this.layer.map.getResolution(),this.triggerRead(a))},getMapBounds:function(){if(null===this.layer.map)return null;var a=this.layer.map.getExtent();a&&!this.layer.projection.equals(this.layer.map.getProjectionObject())&&(a=a.clone().transform(this.layer.map.getProjectionObject(),this.layer.projection));
+return a},invalidBounds:function(a){a||(a=this.getMapBounds());a=!this.bounds||!this.bounds.containsBounds(a);!a&&this.resFactor&&(a=this.resolution/this.layer.map.getResolution(),a=a>=this.resFactor||a<=1/this.resFactor);return a},calculateBounds:function(a){a||(a=this.getMapBounds());var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;this.bounds=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2)},triggerRead:function(a){!this.response||a&&!0===a.noAbort||
+(this.layer.protocol.abort(this.response),this.layer.events.triggerEvent("loadend"));var b={filter:this.createFilter()};this.layer.events.triggerEvent("loadstart",b);this.response=this.layer.protocol.read(OpenLayers.Util.applyDefaults({filter:b.filter,callback:this.merge,scope:this},a))},createFilter:function(){var a=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:this.bounds,projection:this.layer.projection});this.layer.filter&&(a=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,
+filters:[this.layer.filter,a]}));return a},merge:function(a){this.layer.destroyFeatures();if(a.success()){var b=a.features;if(b&&0<b.length){var c=this.layer.projection,d=this.layer.map.getProjectionObject();if(!d.equals(c))for(var e,f=0,g=b.length;f<g;++f)(e=b[f].geometry)&&e.transform(c,d);this.layer.addFeatures(b)}}else this.bounds=null;this.response=null;this.layer.events.triggerEvent("loadend",{response:a})},CLASS_NAME:"OpenLayers.Strategy.BBOX"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{click:{"in":"click",out:"clickout"},mousemove:{"in":"over",out:"out"},dblclick:{"in":"dblclick",out:null},mousedown:{"in":null,out:null},mouseup:{"in":null,out:null},touchstart:{"in":"click",out:"clickout"}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:!0,stopDown:!0,stopUp:!1,initialize:function(a,b,c,d){OpenLayers.Handler.prototype.initialize.apply(this,[a,c,d]);this.layer=
+b},touchstart:function(a){this.startTouch();return OpenLayers.Event.isMultiTouch(a)?!0:this.mousedown(a)},touchmove:function(a){OpenLayers.Event.preventDefault(a)},mousedown:function(a){if(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))this.down=a.xy;return this.handle(a)?!this.stopDown:!0},mouseup:function(a){this.up=a.xy;return this.handle(a)?!this.stopUp:!0},click:function(a){return this.handle(a)?!this.stopClick:!0},mousemove:function(a){if(!this.callbacks.over&&!this.callbacks.out)return!0;
+this.handle(a);return!0},dblclick:function(a){return!this.handle(a)},geometryTypeMatches:function(a){return null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME)},handle:function(a){this.feature&&!this.feature.layer&&(this.feature=null);var b=a.type,c=!1,d=!!this.feature,e="click"==b||"dblclick"==b||"touchstart"==b;(this.feature=this.layer.getFeatureFromEvent(a))&&!this.feature.layer&&(this.feature=null);this.lastFeature&&!this.lastFeature.layer&&(this.lastFeature=
+null);this.feature?("touchstart"===b&&OpenLayers.Event.preventDefault(a),a=this.feature!=this.lastFeature,this.geometryTypeMatches(this.feature)?(d&&a?(this.lastFeature&&this.triggerCallback(b,"out",[this.lastFeature]),this.triggerCallback(b,"in",[this.feature])):d&&!e||this.triggerCallback(b,"in",[this.feature]),this.lastFeature=this.feature,c=!0):(this.lastFeature&&(d&&a||e)&&this.triggerCallback(b,"out",[this.lastFeature]),this.feature=null)):this.lastFeature&&(d||e)&&this.triggerCallback(b,"out",
+[this.lastFeature]);return c},triggerCallback:function(a,b,c){if(b=this.EVENTMAP[a][b])"click"==a&&this.up&&this.down?(Math.sqrt(Math.pow(this.up.x-this.down.x,2)+Math.pow(this.up.y-this.down.y,2))<=this.clickTolerance&&this.callback(b,c),this.up=this.down=null):this.callback(b,c)},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.moveLayerToTop(),this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);
+return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.up=this.down=this.lastFeature=this.feature=null,this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);return a},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this.layer.setZIndex(a)},
+moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:!0,initialize:function(a,b){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),select:new OpenLayers.Style(OpenLayers.Feature.Vector.style.select),temporary:new OpenLayers.Style(OpenLayers.Feature.Vector.style.temporary),"delete":new OpenLayers.Style(OpenLayers.Feature.Vector.style["delete"])};if(a instanceof OpenLayers.Style)this.styles["default"]=a,this.styles.select=a,this.styles.temporary=a,this.styles["delete"]=
+a;else if("object"==typeof a)for(var c in a)if(a[c]instanceof OpenLayers.Style)this.styles[c]=a[c];else if("object"==typeof a[c])this.styles[c]=new OpenLayers.Style(a[c]);else{this.styles["default"]=new OpenLayers.Style(a);this.styles.select=new OpenLayers.Style(a);this.styles.temporary=new OpenLayers.Style(a);this.styles["delete"]=new OpenLayers.Style(a);break}OpenLayers.Util.extend(this,b)},destroy:function(){for(var a in this.styles)this.styles[a].destroy();this.styles=null},createSymbolizer:function(a,
+b){a||(a=new OpenLayers.Feature.Vector);this.styles[b]||(b="default");a.renderIntent=b;var c={};this.extendDefault&&"default"!=b&&(c=this.styles["default"].createSymbolizer(a));return OpenLayers.Util.extend(c,this.styles[b].createSymbolizer(a))},addUniqueValueRules:function(a,b,c,d){var e=[],f;for(f in c)e.push(new OpenLayers.Rule({symbolizer:c[f],context:d,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:b,value:f})}));this.styles[a].addRules(e)},CLASS_NAME:"OpenLayers.StyleMap"});OpenLayers.Layer.Vector=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:!1,isFixed:!1,features:null,filter:null,selectedFeatures:null,unrenderedFeatures:null,reportError:!0,style:null,styleMap:null,strategies:null,protocol:null,renderers:["SVG","VML","Canvas"],renderer:null,rendererOptions:null,geometryType:null,drawn:!1,ratio:1,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);this.renderer&&this.renderer.supported()||this.assignRenderer();this.renderer&&this.renderer.supported()||
+(this.renderer=null,this.displayError());this.styleMap||(this.styleMap=new OpenLayers.StyleMap);this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies)for(var c=0,d=this.strategies.length;c<d;c++)this.strategies[c].setLayer(this)},destroy:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoDestroy&&a.destroy();this.strategies=null}this.protocol&&(this.protocol.autoDestroy&&this.protocol.destroy(),this.protocol=
+null);this.destroyFeatures();this.unrenderedFeatures=this.selectedFeatures=this.features=null;this.renderer&&this.renderer.destroy();this.drawn=this.geometryType=this.renderer=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Vector(this.name,this.getOptions()));a=OpenLayers.Layer.prototype.clone.apply(this,[a]);for(var b=this.features,c=b.length,d=Array(c),e=0;e<c;++e)d[e]=b[e].clone();a.features=d;return a},refresh:function(a){this.calculateInRange()&&
+this.visibility&&this.events.triggerEvent("refresh",a)},assignRenderer:function(){for(var a=0,b=this.renderers.length;a<b;a++){var c=this.renderers[a];if((c="function"==typeof c?c:OpenLayers.Renderer[c])&&c.prototype.supported()){this.renderer=new c(this.div,this.rendererOptions);break}}},displayError:function(){this.reportError&&OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{renderers:this.renderers.join("\n")}))},setMap:function(a){OpenLayers.Layer.prototype.setMap.apply(this,
+arguments);if(this.renderer){this.renderer.map=this.map;var b=this.map.getSize();b.w*=this.ratio;b.h*=this.ratio;this.renderer.setSize(b)}else this.map.removeLayer(this)},afterAdd:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.activate()}},removeMap:function(a){this.drawn=!1;if(this.strategies){var b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.deactivate()}},onMapResize:function(){OpenLayers.Layer.prototype.onMapResize.apply(this,
+arguments);var a=this.map.getSize();a.w*=this.ratio;a.h*=this.ratio;this.renderer.setSize(a)},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var d=!0;if(!c){this.renderer.root.style.visibility="hidden";var d=this.map.getSize(),e=d.w,d=d.h,e=e/2*this.ratio-e/2,d=d/2*this.ratio-d/2,e=e+this.map.layerContainerOriginPx.x,e=-Math.round(e),d=d+this.map.layerContainerOriginPx.y,d=-Math.round(d);this.div.style.left=e+"px";this.div.style.top=d+"px";e=this.map.getExtent().scale(this.ratio);
+d=this.renderer.setExtent(e,b);this.renderer.root.style.visibility="visible";!0===OpenLayers.IS_GECKO&&(this.div.scrollLeft=this.div.scrollLeft);if(!b&&d)for(var f in this.unrenderedFeatures)e=this.unrenderedFeatures[f],this.drawFeature(e)}if(!this.drawn||b||!d)for(this.drawn=!0,f=0,d=this.features.length;f<d;f++)this.renderer.locked=f!==d-1,e=this.features[f],this.drawFeature(e)},display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);var b=this.div.style.display;b!=this.renderer.root.style.display&&
+(this.renderer.root.style.display=b)},addFeatures:function(a,b){OpenLayers.Util.isArray(a)||(a=[a]);var c=!b||!b.silent;if(c){var d={features:a};if(!1===this.events.triggerEvent("beforefeaturesadded",d))return;a=d.features}for(var d=[],e=0,f=a.length;e<f;e++){this.renderer.locked=e!=a.length-1?!0:!1;var g=a[e];if(this.geometryType&&!(g.geometry instanceof this.geometryType))throw new TypeError("addFeatures: component should be an "+this.geometryType.prototype.CLASS_NAME);g.layer=this;!g.style&&this.style&&
+(g.style=OpenLayers.Util.extend({},this.style));if(c){if(!1===this.events.triggerEvent("beforefeatureadded",{feature:g}))continue;this.preFeatureInsert(g)}d.push(g);this.features.push(g);this.drawFeature(g);c&&(this.events.triggerEvent("featureadded",{feature:g}),this.onFeatureInsert(g))}c&&this.events.triggerEvent("featuresadded",{features:d})},removeFeatures:function(a,b){if(a&&0!==a.length){if(a===this.features)return this.removeAllFeatures(b);OpenLayers.Util.isArray(a)||(a=[a]);a===this.selectedFeatures&&
+(a=a.slice());var c=!b||!b.silent;c&&this.events.triggerEvent("beforefeaturesremoved",{features:a});for(var d=a.length-1;0<=d;d--){this.renderer.locked=0!=d&&a[d-1].geometry?!0:!1;var e=a[d];delete this.unrenderedFeatures[e.id];c&&this.events.triggerEvent("beforefeatureremoved",{feature:e});this.features=OpenLayers.Util.removeItem(this.features,e);e.layer=null;e.geometry&&this.renderer.eraseFeatures(e);-1!=OpenLayers.Util.indexOf(this.selectedFeatures,e)&&OpenLayers.Util.removeItem(this.selectedFeatures,
+e);c&&this.events.triggerEvent("featureremoved",{feature:e})}c&&this.events.triggerEvent("featuresremoved",{features:a})}},removeAllFeatures:function(a){a=!a||!a.silent;var b=this.features;a&&this.events.triggerEvent("beforefeaturesremoved",{features:b});for(var c,d=b.length-1;0<=d;d--)c=b[d],a&&this.events.triggerEvent("beforefeatureremoved",{feature:c}),c.layer=null,a&&this.events.triggerEvent("featureremoved",{feature:c});this.renderer.clear();this.features=[];this.unrenderedFeatures={};this.selectedFeatures=
+[];a&&this.events.triggerEvent("featuresremoved",{features:b})},destroyFeatures:function(a,b){void 0==a&&(a=this.features);if(a){this.removeFeatures(a,b);for(var c=a.length-1;0<=c;c--)a[c].destroy()}},drawFeature:function(a,b){if(this.drawn){if("object"!=typeof b){b||a.state!==OpenLayers.State.DELETE||(b="delete");var c=b||a.renderIntent;(b=a.style||this.style)||(b=this.styleMap.createSymbolizer(a,c))}c=this.renderer.drawFeature(a,b);!1===c||null===c?this.unrenderedFeatures[a.id]=a:delete this.unrenderedFeatures[a.id]}},
+eraseFeatures:function(a){this.renderer.eraseFeatures(a)},getFeatureFromEvent:function(a){if(!this.renderer)throw Error("getFeatureFromEvent called on layer with no renderer. This usually means you destroyed a layer, but not some handler which is associated with it.");var b=null;(a=this.renderer.getFeatureIdFromEvent(a))&&(b="string"===typeof a?this.getFeatureById(a):a);return b},getFeatureBy:function(a,b){for(var c=null,d=0,e=this.features.length;d<e;++d)if(this.features[d][a]==b){c=this.features[d];
+break}return c},getFeatureById:function(a){return this.getFeatureBy("id",a)},getFeatureByFid:function(a){return this.getFeatureBy("fid",a)},getFeaturesByAttribute:function(a,b){var c,d,e=this.features.length,f=[];for(c=0;c<e;c++)(d=this.features[c])&&d.attributes&&d.attributes[a]===b&&f.push(d);return f},onFeatureInsert:function(a){},preFeatureInsert:function(a){},getDataExtent:function(){var a=null,b=this.features;if(b&&0<b.length)for(var c=null,d=0,e=b.length;d<e;d++)if(c=b[d].geometry)null===a&&
+(a=new OpenLayers.Bounds),a.extend(c.getBounds());return a},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:!1,layers:null,display:function(){},getFeatureFromEvent:function(a){for(var b=this.layers,c,d=0;d<b.length;d++)if(c=b[d].getFeatureFromEvent(a))return c},setMap:function(a){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);this.collectRoots();a.events.register("changelayer",this,this.handleChangeLayer)},removeMap:function(a){a.events.unregister("changelayer",this,this.handleChangeLayer);
+this.resetRoots();OpenLayers.Layer.Vector.prototype.removeMap.apply(this,arguments)},collectRoots:function(){for(var a,b=0;b<this.map.layers.length;++b)a=this.map.layers[b],-1!=OpenLayers.Util.indexOf(this.layers,a)&&a.renderer.moveRoot(this.renderer)},resetRoots:function(){for(var a,b=0;b<this.layers.length;++b)a=this.layers[b],this.renderer&&a.renderer.getRenderLayerId()==this.id&&this.renderer.moveRoot(a.renderer)},handleChangeLayer:function(a){var b=a.layer;"order"==a.property&&-1!=OpenLayers.Util.indexOf(this.layers,
+b)&&(this.resetRoots(),this.collectRoots())},CLASS_NAME:"OpenLayers.Layer.Vector.RootContainer"});OpenLayers.Control.SelectFeature=OpenLayers.Class(OpenLayers.Control,{multipleKey:null,toggleKey:null,multiple:!1,clickout:!0,toggle:!1,hover:!1,highlightOnly:!1,box:!1,onBeforeSelect:function(){},onSelect:function(){},onUnselect:function(){},scope:null,geometryTypes:null,layer:null,layers:null,callbacks:null,selectStyle:null,renderIntent:"select",handlers:null,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);null===this.scope&&(this.scope=this);this.initLayer(a);var c=
+{click:this.clickFeature,clickout:this.clickoutFeature};this.hover&&(c.over=this.overFeature,c.out=this.outFeature);this.callbacks=OpenLayers.Util.extend(c,this.callbacks);this.handlers={feature:new OpenLayers.Handler.Feature(this,this.layer,this.callbacks,{geometryTypes:this.geometryTypes})};this.box&&(this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},{boxDivClassName:"olHandlerBoxSelectFeature"}))},initLayer:function(a){OpenLayers.Util.isArray(a)?(this.layers=a,this.layer=
+new OpenLayers.Layer.Vector.RootContainer(this.id+"_container",{layers:a})):this.layer=a},destroy:function(){this.active&&this.layers&&this.map.removeLayer(this.layer);OpenLayers.Control.prototype.destroy.apply(this,arguments);this.layers&&this.layer.destroy()},activate:function(){this.active||(this.layers&&this.map.addLayer(this.layer),this.handlers.feature.activate(),this.box&&this.handlers.box&&this.handlers.box.activate());return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.active&&
+(this.handlers.feature.deactivate(),this.handlers.box&&this.handlers.box.deactivate(),this.layers&&this.map.removeLayer(this.layer));return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},unselectAll:function(a){var b=this.layers||[this.layer],c,d,e,f;for(e=0;e<b.length;++e)if(c=b[e],f=0,null!=c.selectedFeatures)for(;c.selectedFeatures.length>f;)d=c.selectedFeatures[f],a&&a.except==d?++f:this.unselect(d)},clickFeature:function(a){this.hover||(-1<OpenLayers.Util.indexOf(a.layer.selectedFeatures,
+a)?this.toggleSelect()?this.unselect(a):this.multipleSelect()||this.unselectAll({except:a}):(this.multipleSelect()||this.unselectAll({except:a}),this.select(a)))},multipleSelect:function(){return this.multiple||this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]},toggleSelect:function(){return this.toggle||this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]},clickoutFeature:function(a){!this.hover&&this.clickout&&this.unselectAll()},overFeature:function(a){var b=
+a.layer;this.hover&&(this.highlightOnly?this.highlight(a):-1==OpenLayers.Util.indexOf(b.selectedFeatures,a)&&this.select(a))},outFeature:function(a){if(this.hover)if(this.highlightOnly){if(a._lastHighlighter==this.id)if(a._prevHighlighter&&a._prevHighlighter!=this.id){delete a._lastHighlighter;var b=this.map.getControl(a._prevHighlighter);b&&b.highlight(a)}else this.unhighlight(a)}else this.unselect(a)},highlight:function(a){var b=a.layer;!1!==this.events.triggerEvent("beforefeaturehighlighted",{feature:a})&&
+(a._prevHighlighter=a._lastHighlighter,a._lastHighlighter=this.id,b.drawFeature(a,this.selectStyle||this.renderIntent),this.events.triggerEvent("featurehighlighted",{feature:a}))},unhighlight:function(a){var b=a.layer;void 0==a._prevHighlighter?delete a._lastHighlighter:(a._prevHighlighter!=this.id&&(a._lastHighlighter=a._prevHighlighter),delete a._prevHighlighter);b.drawFeature(a,a.style||a.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:a})},select:function(a){var b=
+this.onBeforeSelect.call(this.scope,a),c=a.layer;!1!==b&&(b=c.events.triggerEvent("beforefeatureselected",{feature:a}),!1!==b&&(c.selectedFeatures.push(a),this.highlight(a),this.handlers.feature.lastFeature||(this.handlers.feature.lastFeature=c.selectedFeatures[0]),c.events.triggerEvent("featureselected",{feature:a}),this.onSelect.call(this.scope,a)))},unselect:function(a){var b=a.layer;this.unhighlight(a);OpenLayers.Util.removeItem(b.selectedFeatures,a);b.events.triggerEvent("featureunselected",
+{feature:a});this.onUnselect.call(this.scope,a)},selectBox:function(a){if(a instanceof OpenLayers.Bounds){var b=this.map.getLonLatFromPixel({x:a.left,y:a.bottom});a=this.map.getLonLatFromPixel({x:a.right,y:a.top});b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat);this.multipleSelect()||this.unselectAll();a=this.multiple;this.multiple=!0;var c=this.layers||[this.layer];this.events.triggerEvent("boxselectionstart",{layers:c});for(var d,e=0;e<c.length;++e){d=c[e];for(var f=0,g=d.features.length;f<g;++f){var h=
+d.features[f];h.getVisibility()&&(null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,h.geometry.CLASS_NAME))&&b.toGeometry().intersects(h.geometry)&&-1==OpenLayers.Util.indexOf(d.selectedFeatures,h)&&this.select(h)}}this.multiple=a;this.events.triggerEvent("boxselectionend",{layers:c})}},setMap:function(a){this.handlers.feature.setMap(a);this.box&&this.handlers.box.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},setLayer:function(a){var b=this.active;this.unselectAll();
+this.deactivate();this.layers&&(this.layer.destroy(),this.layers=null);this.initLayer(a);this.handlers.feature.layer=this.layer;b&&this.activate()},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Control.Attribution=OpenLayers.Class(OpenLayers.Control,{separator:", ",template:"${layers}",destroy:function(){this.map.events.un({removelayer:this.updateAttribution,addlayer:this.updateAttribution,changelayer:this.updateAttribution,changebaselayer:this.updateAttribution,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.map.events.on({changebaselayer:this.updateAttribution,changelayer:this.updateAttribution,
+addlayer:this.updateAttribution,removelayer:this.updateAttribution,scope:this});this.updateAttribution();return this.div},updateAttribution:function(){var a=[];if(this.map&&this.map.layers){for(var b=0,c=this.map.layers.length;b<c;b++){var d=this.map.layers[b];d.attribution&&d.getVisibility()&&-1===OpenLayers.Util.indexOf(a,d.attribution)&&a.push(d.attribution)}this.div.innerHTML=OpenLayers.String.format(this.template,{layers:a.join(this.separator)})}},CLASS_NAME:"OpenLayers.Control.Attribution"});OpenLayers.Kinetic=OpenLayers.Class({threshold:0,deceleration:0.0035,nbPoints:100,delay:200,points:void 0,timerId:void 0,initialize:function(a){OpenLayers.Util.extend(this,a)},begin:function(){OpenLayers.Animation.stop(this.timerId);this.timerId=void 0;this.points=[]},update:function(a){this.points.unshift({xy:a,tick:(new Date).getTime()});this.points.length>this.nbPoints&&this.points.pop()},end:function(a){for(var b,c=(new Date).getTime(),d=0,e=this.points.length,f;d<e;d++){f=this.points[d];if(c-
+f.tick>this.delay)break;b=f}if(b&&(d=(new Date).getTime()-b.tick,c=Math.sqrt(Math.pow(a.x-b.xy.x,2)+Math.pow(a.y-b.xy.y,2)),d=c/d,!(0==d||d<this.threshold)))return c=Math.asin((a.y-b.xy.y)/c),b.xy.x<=a.x&&(c=Math.PI-c),{speed:d,theta:c}},move:function(a,b){var c=a.speed,d=Math.cos(a.theta),e=-Math.sin(a.theta),f=(new Date).getTime(),g=0,h=0;this.timerId=OpenLayers.Animation.start(OpenLayers.Function.bind(function(){if(null!=this.timerId){var a=(new Date).getTime()-f,l=-this.deceleration*Math.pow(a,
+2)/2+c*a,m=l*d,l=l*e,r,p;r=!1;0>=-this.deceleration*a+c&&(OpenLayers.Animation.stop(this.timerId),this.timerId=null,r=!0);a=m-g;p=l-h;g=m;h=l;b(a,p,r)}},this))},CLASS_NAME:"OpenLayers.Kinetic"});OpenLayers.Filter.Logical=OpenLayers.Class(OpenLayers.Filter,{filters:null,type:null,initialize:function(a){this.filters=[];OpenLayers.Filter.prototype.initialize.apply(this,[a])},destroy:function(){this.filters=null;OpenLayers.Filter.prototype.destroy.apply(this)},evaluate:function(a){var b,c;switch(this.type){case OpenLayers.Filter.Logical.AND:b=0;for(c=this.filters.length;b<c;b++)if(!1==this.filters[b].evaluate(a))return!1;return!0;case OpenLayers.Filter.Logical.OR:b=0;for(c=this.filters.length;b<
+c;b++)if(!0==this.filters[b].evaluate(a))return!0;return!1;case OpenLayers.Filter.Logical.NOT:return!this.filters[0].evaluate(a)}},clone:function(){for(var a=[],b=0,c=this.filters.length;b<c;++b)a.push(this.filters[b].clone());return new OpenLayers.Filter.Logical({type:this.type,filters:a})},CLASS_NAME:"OpenLayers.Filter.Logical"});OpenLayers.Filter.Logical.AND="&&";OpenLayers.Filter.Logical.OR="||";OpenLayers.Filter.Logical.NOT="!";OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!0,dragging:!1,last:null,start:null,lastMoveEvt:null,oldOnselectstart:null,interval:0,timeoutId:null,documentDrag:!1,documentEvents:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(!0===this.documentDrag){var d=this;this._docMove=function(a){d.mousemove({xy:{x:a.clientX,y:a.clientY},element:document})};this._docUp=function(a){d.mouseup({xy:{x:a.clientX,y:a.clientY}})}}},
+dragstart:function(a){var b=!0;this.dragging=!1;this.checkModifiers(a)&&(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))?(this.started=!0,this.last=this.start=a.xy,OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown"),this.down(a),this.callback("down",[a.xy]),OpenLayers.Event.preventDefault(a),this.oldOnselectstart||(this.oldOnselectstart=document.onselectstart?document.onselectstart:OpenLayers.Function.True),document.onselectstart=OpenLayers.Function.False,b=!this.stopDown):
+(this.started=!1,this.last=this.start=null);return b},dragmove:function(a){this.lastMoveEvt=a;!this.started||(this.timeoutId||a.xy.x==this.last.x&&a.xy.y==this.last.y)||(!0===this.documentDrag&&this.documentEvents&&(a.element===document?(this.adjustXY(a),this.setEvent(a)):this.removeDocumentEvents()),0<this.interval&&(this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval)),this.dragging=!0,this.move(a),this.callback("move",[a.xy]),this.oldOnselectstart||(this.oldOnselectstart=
+document.onselectstart,document.onselectstart=OpenLayers.Function.False),this.last=a.xy);return!0},dragend:function(a){if(this.started){!0===this.documentDrag&&this.documentEvents&&(this.adjustXY(a),this.removeDocumentEvents());var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(a);this.callback("up",[a.xy]);b&&this.callback("done",[a.xy]);document.onselectstart=this.oldOnselectstart}return!0},down:function(a){},move:function(a){},
+up:function(a){},out:function(a){},mousedown:function(a){return this.dragstart(a)},touchstart:function(a){this.startTouch();return this.dragstart(a)},mousemove:function(a){return this.dragmove(a)},touchmove:function(a){return this.dragmove(a)},removeTimeout:function(){this.timeoutId=null;this.dragging&&this.mousemove(this.lastMoveEvt)},mouseup:function(a){return this.dragend(a)},touchend:function(a){a.xy=this.last;return this.dragend(a)},mouseout:function(a){if(this.started&&OpenLayers.Util.mouseLeft(a,
+this.map.viewPortDiv))if(!0===this.documentDrag)this.addDocumentEvents();else{var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(a);this.callback("out",[]);b&&this.callback("done",[a.xy]);document.onselectstart&&(document.onselectstart=this.oldOnselectstart)}return!0},click:function(a){return this.start==this.last},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.dragging=
+!1,a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.dragging=this.started=!1,this.last=this.start=null,a=!0,OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown"));return a},adjustXY:function(a){var b=OpenLayers.Util.pagePosition(this.map.viewPortDiv);a.xy.x-=b[0];a.xy.y-=b[1]},addDocumentEvents:function(){OpenLayers.Element.addClass(document.body,"olDragDown");this.documentEvents=!0;OpenLayers.Event.observe(document,"mousemove",
+this._docMove);OpenLayers.Event.observe(document,"mouseup",this._docUp)},removeDocumentEvents:function(){OpenLayers.Element.removeClass(document.body,"olDragDown");this.documentEvents=!1;OpenLayers.Event.stopObserving(document,"mousemove",this._docMove);OpenLayers.Event.stopObserving(document,"mouseup",this._docUp)},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:"olHandlerBoxZoomBox",boxOffsets:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.dragHandler=new OpenLayers.Handler.Drag(this,{down:this.startBox,move:this.moveBox,out:this.removeBox,up:this.endBox},{keyMask:this.keyMask})},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.dragHandler&&(this.dragHandler.destroy(),this.dragHandler=
+null)},setMap:function(a){OpenLayers.Handler.prototype.setMap.apply(this,arguments);this.dragHandler&&this.dragHandler.setMap(a)},startBox:function(a){this.callback("start",[]);this.zoomBox=OpenLayers.Util.createDiv("zoomBox",{x:-9999,y:-9999});this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE.Popup-1;this.map.viewPortDiv.appendChild(this.zoomBox);OpenLayers.Element.addClass(this.map.viewPortDiv,"olDrawBox")},moveBox:function(a){var b=this.dragHandler.start.x,
+c=this.dragHandler.start.y,d=Math.abs(b-a.x),e=Math.abs(c-a.y),f=this.getBoxOffsets();this.zoomBox.style.width=d+f.width+1+"px";this.zoomBox.style.height=e+f.height+1+"px";this.zoomBox.style.left=(a.x<b?b-d-f.left:b-f.left)+"px";this.zoomBox.style.top=(a.y<c?c-e-f.top:c-f.top)+"px"},endBox:function(a){var b;if(5<Math.abs(this.dragHandler.start.x-a.x)||5<Math.abs(this.dragHandler.start.y-a.y)){var c=this.dragHandler.start;b=Math.min(c.y,a.y);var d=Math.max(c.y,a.y),e=Math.min(c.x,a.x);a=Math.max(c.x,
+a.x);b=new OpenLayers.Bounds(e,d,a,b)}else b=this.dragHandler.start.clone();this.removeBox();this.callback("done",[b])},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.boxOffsets=this.zoomBox=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDrawBox")},activate:function(){return OpenLayers.Handler.prototype.activate.apply(this,arguments)?(this.dragHandler.activate(),!0):!1},deactivate:function(){return OpenLayers.Handler.prototype.deactivate.apply(this,arguments)?
+(this.dragHandler.deactivate()&&this.zoomBox&&this.removeBox(),!0):!1},getBoxOffsets:function(){if(!this.boxOffsets){var a=document.createElement("div");a.style.position="absolute";a.style.border="1px solid black";a.style.width="3px";document.body.appendChild(a);var b=3==a.clientWidth;document.body.removeChild(a);var a=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width")),c=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width")),d=parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+"border-top-width")),e=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"));this.boxOffsets={left:a,right:c,top:d,bottom:e,width:!1===b?a+c:0,height:!1===b?d+e:0}}return this.boxOffsets},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:!1,keyMask:null,alwaysZoom:!1,zoomOnClick:!0,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask})},zoomBox:function(a){if(a instanceof OpenLayers.Bounds){var b,c=a.getCenterPixel();if(this.out){b=Math.min(this.map.size.h/(a.bottom-a.top),this.map.size.w/(a.right-a.left));var d=this.map.getExtent(),e=this.map.getLonLatFromPixel(c),f=e.lon-d.getWidth()/
+2*b;a=e.lon+d.getWidth()/2*b;var g=e.lat-d.getHeight()/2*b;b=e.lat+d.getHeight()/2*b;b=new OpenLayers.Bounds(f,g,a,b)}else f=this.map.getLonLatFromPixel({x:a.left,y:a.bottom}),a=this.map.getLonLatFromPixel({x:a.right,y:a.top}),b=new OpenLayers.Bounds(f.lon,f.lat,a.lon,a.lat);f=this.map.getZoom();g=this.map.getSize();a=g.w/2;g=g.h/2;b=this.map.getZoomForExtent(b);d=this.map.getResolution();e=this.map.getResolutionForZoom(b);d==e?this.map.setCenter(this.map.getLonLatFromPixel(c)):this.map.zoomTo(b,
+{x:(d*c.x-e*a)/(d-e),y:(d*c.y-e*g)/(d-e)});f==this.map.getZoom()&&!0==this.alwaysZoom&&this.map.zoomTo(f+(this.out?-1:1))}else this.zoomOnClick&&(this.out?this.map.zoomTo(this.map.getZoom()-1,a):this.map.zoomTo(this.map.getZoom()+1,a))},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:!1,interval:0,documentDrag:!1,kinetic:null,enableKinetic:!0,kineticInterval:10,draw:function(){if(this.enableKinetic&&OpenLayers.Kinetic){var a={interval:this.kineticInterval};"object"===typeof this.enableKinetic&&(a=OpenLayers.Util.extend(a,this.enableKinetic));this.kinetic=new OpenLayers.Kinetic(a)}this.handler=new OpenLayers.Handler.Drag(this,{move:this.panMap,done:this.panMapDone,down:this.panMapStart},
+{interval:this.interval,documentDrag:this.documentDrag})},panMapStart:function(){this.kinetic&&this.kinetic.begin()},panMap:function(a){this.kinetic&&this.kinetic.update(a);this.panned=!0;this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!0,animate:!1})},panMapDone:function(a){if(this.panned){var b=null;this.kinetic&&(b=this.kinetic.end(a));this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!!b,animate:!1});if(b){var c=this;this.kinetic.move(b,function(a,b,
+f){c.map.pan(a,b,{dragging:!f,animate:!1})})}this.panned=!1}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:!0,"double":!1,pixelTolerance:0,dblclickTolerance:13,stopSingle:!1,stopDouble:!1,timerId:null,down:null,last:null,first:null,rightclickTimerId:null,touchstart:function(a){this.startTouch();this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},touchmove:function(a){this.last=this.getEventInfo(a);return!0},touchend:function(a){this.down&&(a.xy=this.last.xy,a.lastTouches=this.last.touches,this.handleSingle(a),
+this.down=null);return!0},mousedown:function(a){this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},mouseup:function(a){var b=!0;this.checkModifiers(a)&&(this.control.handleRightClicks&&OpenLayers.Event.isRightClick(a))&&(b=this.rightclick(a));return b},rightclick:function(a){if(this.passesTolerance(a)){if(null!=this.rightclickTimerId)return this.clearTimer(),this.callback("dblrightclick",[a]),!this.stopDouble;a=this["double"]?OpenLayers.Util.extend({},a):this.callback("rightclick",
+[a]);a=OpenLayers.Function.bind(this.delayedRightCall,this,a);this.rightclickTimerId=window.setTimeout(a,this.delay)}return!this.stopSingle},delayedRightCall:function(a){this.rightclickTimerId=null;a&&this.callback("rightclick",[a])},click:function(a){this.last||(this.last=this.getEventInfo(a));this.handleSingle(a);return!this.stopSingle},dblclick:function(a){this.handleDouble(a);return!this.stopDouble},handleDouble:function(a){this.passesDblclickTolerance(a)&&(this["double"]&&this.callback("dblclick",
+[a]),this.clearTimer())},handleSingle:function(a){this.passesTolerance(a)&&(null!=this.timerId?(this.last.touches&&1===this.last.touches.length&&(this["double"]&&OpenLayers.Event.preventDefault(a),this.handleDouble(a)),this.last.touches&&2===this.last.touches.length||this.clearTimer()):(this.first=this.getEventInfo(a),a=this.single?OpenLayers.Util.extend({},a):null,this.queuePotentialClick(a)))},queuePotentialClick:function(a){this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,
+this,a),this.delay)},passesTolerance:function(a){var b=!0;if(null!=this.pixelTolerance&&this.down&&this.down.xy&&(b=this.pixelTolerance>=this.down.xy.distanceTo(a.xy))&&this.touch&&this.down.touches.length===this.last.touches.length){a=0;for(var c=this.down.touches.length;a<c;++a)if(this.getTouchDistance(this.down.touches[a],this.last.touches[a])>this.pixelTolerance){b=!1;break}}return b},getTouchDistance:function(a,b){return Math.sqrt(Math.pow(a.clientX-b.clientX,2)+Math.pow(a.clientY-b.clientY,
+2))},passesDblclickTolerance:function(a){a=!0;this.down&&this.first&&(a=this.down.xy.distanceTo(this.first.xy)<=this.dblclickTolerance);return a},clearTimer:function(){null!=this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);null!=this.rightclickTimerId&&(window.clearTimeout(this.rightclickTimerId),this.rightclickTimerId=null)},delayedCall:function(a){this.timerId=null;a&&this.callback("click",[a])},getEventInfo:function(a){var b;if(a.touches){var c=a.touches.length;b=Array(c);for(var d,
+e=0;e<c;e++)d=a.touches[e],b[e]={clientX:d.olClientX,clientY:d.olClientY}}return{xy:a.xy,touches:b}},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.clearTimer(),this.last=this.first=this.down=null,a=!0);return a},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,pinchZoom:null,pinchZoomOptions:null,documentDrag:!1,zoomBox:null,zoomBoxEnabled:!0,zoomWheelEnabled:!0,mouseWheelOptions:null,handleRightClicks:!1,zoomBoxKeyMask:OpenLayers.Handler.MOD_SHIFT,autoActivate:!0,initialize:function(a){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments)},destroy:function(){this.deactivate();this.dragPan&&this.dragPan.destroy();this.dragPan=null;
+this.zoomBox&&this.zoomBox.destroy();this.zoomBox=null;this.pinchZoom&&this.pinchZoom.destroy();this.pinchZoom=null;OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){this.dragPan.activate();this.zoomWheelEnabled&&this.handlers.wheel.activate();this.handlers.click.activate();this.zoomBoxEnabled&&this.zoomBox.activate();this.pinchZoom&&this.pinchZoom.activate();return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.pinchZoom&&this.pinchZoom.deactivate();
+this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},draw:function(){this.handleRightClicks&&(this.map.viewPortDiv.oncontextmenu=OpenLayers.Function.False);this.handlers.click=new OpenLayers.Handler.Click(this,{click:this.defaultClick,dblclick:this.defaultDblClick,dblrightclick:this.defaultDblRightClick},{"double":!0,stopDouble:!0});this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map,
+documentDrag:this.documentDrag},this.dragPanOptions));this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:this.zoomBoxKeyMask});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{up:this.wheelUp,down:this.wheelDown},OpenLayers.Util.extend(this.map.fractionalZoom?{}:{cumulative:!1,interval:50,maxDelta:6},this.mouseWheelOptions));OpenLayers.Control.PinchZoom&&(this.pinchZoom=new OpenLayers.Control.PinchZoom(OpenLayers.Util.extend({map:this.map},
+this.pinchZoomOptions)))},defaultClick:function(a){a.lastTouches&&2==a.lastTouches.length&&this.map.zoomOut()},defaultDblClick:function(a){this.map.zoomTo(this.map.zoom+1,a.xy)},defaultDblRightClick:function(a){this.map.zoomTo(this.map.zoom-1,a.xy)},wheelChange:function(a,b){this.map.fractionalZoom||(b=Math.round(b));var c=this.map.getZoom(),d;d=Math.max(c+b,0);d=Math.min(d,this.map.getNumZoomLevels());d!==c&&this.map.zoomTo(d,a.xy)},wheelUp:function(a,b){this.wheelChange(a,b||1)},wheelDown:function(a,
+b){this.wheelChange(a,b||-1)},disableZoomBox:function(){this.zoomBoxEnabled=!1;this.zoomBox.deactivate()},enableZoomBox:function(){this.zoomBoxEnabled=!0;this.active&&this.zoomBox.activate()},disableZoomWheel:function(){this.zoomWheelEnabled=!1;this.handlers.wheel.deactivate()},enableZoomWheel:function(){this.zoomWheelEnabled=!0;this.active&&this.handlers.wheel.activate()},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1",request:"GetMap",styles:"",format:"image/jpeg"},isBaseLayer:!0,encodeBBOX:!1,noMagic:!1,yx:{},initialize:function(a,b,c,d){var e=[];c=OpenLayers.Util.upperCaseObject(c);1.3<=parseFloat(c.VERSION)&&!c.EXCEPTIONS&&(c.EXCEPTIONS="INIMAGE");e.push(a,b,c,d);OpenLayers.Layer.Grid.prototype.initialize.apply(this,e);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));
+!this.noMagic&&(this.params.TRANSPARENT&&"true"==this.params.TRANSPARENT.toString().toLowerCase())&&(null!=d&&d.isBaseLayer||(this.isBaseLayer=!1),"image/jpeg"==this.params.FORMAT&&(this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png"))},clone:function(a){null==a&&(a=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},reverseAxisOrder:function(){var a=this.projection.getCode();return 1.3<=parseFloat(this.params.VERSION)&&
+!!(this.yx[a]||OpenLayers.Projection.defaults[a]&&OpenLayers.Projection.defaults[a].yx)},getURL:function(a){a=this.adjustBounds(a);var b=this.getImageSize(),c={},d=this.reverseAxisOrder();c.BBOX=this.encodeBBOX?a.toBBOX(null,d):a.toArray(d);c.WIDTH=b.w;c.HEIGHT=b.h;return this.getFullRequestString(c)},mergeNewParams:function(a){a=[OpenLayers.Util.upperCaseObject(a)];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,a)},getFullRequestString:function(a,b){var c=this.map.getProjectionObject(),
+c=this.projection&&this.projection.equals(c)?this.projection.getCode():c.getCode(),c="none"==c?null:c;1.3<=parseFloat(this.params.VERSION)?this.params.CRS=c:this.params.SRS=c;"boolean"==typeof this.params.TRANSPARENT&&(a.TRANSPARENT=this.params.TRANSPARENT?"TRUE":"FALSE");return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments)},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.Renderer.SVG=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"http://www.w3.org/2000/svg",xlinkns:"http://www.w3.org/1999/xlink",MAX_PIXEL:15E3,translationParameters:null,symbolMetrics:null,initialize:function(a){this.supported()&&(OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments),this.translationParameters={x:0,y:0},this.symbolMetrics={})},supported:function(){return document.implementation&&(document.implementation.hasFeature("org.w3c.svg","1.0")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG",
+"1.1")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"))},inValidRange:function(a,b,c){a+=c?0:this.translationParameters.x;b+=c?0:this.translationParameters.y;return a>=-this.MAX_PIXEL&&a<=this.MAX_PIXEL&&b>=-this.MAX_PIXEL&&b<=this.MAX_PIXEL},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=-a.left/d,d=a.top/d;if(b)return this.left=e,this.top=d,this.rendererRoot.setAttributeNS(null,
+"viewBox","0 0 "+this.size.w+" "+this.size.h),this.translate(this.xOffset,0),!0;(e=this.translate(e-this.left+this.xOffset,d-this.top))||this.setExtent(a,!0);return c&&e},translate:function(a,b){if(this.inValidRange(a,b,!0)){var c="";if(a||b)c="translate("+a+","+b+")";this.root.setAttributeNS(null,"transform",c);this.translationParameters={x:a,y:b};return!0}return!1},setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"width",this.size.w);
+this.rendererRoot.setAttributeNS(null,"height",this.size.h)},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"image":this.isComplexSymbol(b.graphicName)?"svg":"circle";break;case "OpenLayers.Geometry.Rectangle":c="rect";break;case "OpenLayers.Geometry.LineString":c="polyline";break;case "OpenLayers.Geometry.LinearRing":c="polygon";break;case "OpenLayers.Geometry.Polygon":case "OpenLayers.Geometry.Curve":c="path"}return c},setStyle:function(a,
+b,c){b=b||a._style;c=c||a._options;var d=b.title||b.graphicTitle;if(d){a.setAttributeNS(null,"title",d);var e=a.getElementsByTagName("title");0<e.length?e[0].firstChild.textContent=d:(e=this.nodeFactory(null,"title"),e.textContent=d,a.appendChild(e))}var e=parseFloat(a.getAttributeNS(null,"r")),d=1,f;if("OpenLayers.Geometry.Point"==a._geometryClass&&e){a.style.visibility="";if(!1===b.graphic)a.style.visibility="hidden";else if(b.externalGraphic){f=this.getPosition(a);b.graphicWidth&&b.graphicHeight&&
+a.setAttributeNS(null,"preserveAspectRatio","none");var e=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),k=b.graphicOpacity||b.fillOpacity;a.setAttributeNS(null,"x",(f.x+(void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e))).toFixed());a.setAttributeNS(null,"y",(f.y+h).toFixed());a.setAttributeNS(null,"width",e);a.setAttributeNS(null,"height",g);a.setAttributeNS(this.xlinkns,"xlink:href",
+b.externalGraphic);a.setAttributeNS(null,"style","opacity: "+k);a.onclick=OpenLayers.Event.preventDefault}else if(this.isComplexSymbol(b.graphicName)){var e=3*b.pointRadius,g=2*e,l=this.importSymbol(b.graphicName);f=this.getPosition(a);d=3*this.symbolMetrics[l.id][0]/g;h=a.parentNode;k=a.nextSibling;h&&h.removeChild(a);a.firstChild&&a.removeChild(a.firstChild);a.appendChild(l.firstChild.cloneNode(!0));a.setAttributeNS(null,"viewBox",l.getAttributeNS(null,"viewBox"));a.setAttributeNS(null,"width",
+g);a.setAttributeNS(null,"height",g);a.setAttributeNS(null,"x",f.x-e);a.setAttributeNS(null,"y",f.y-e);k?h.insertBefore(a,k):h&&h.appendChild(a)}else a.setAttributeNS(null,"r",b.pointRadius);e=b.rotation;void 0===e&&void 0===a._rotation||!f||(a._rotation=e,e|=0,"svg"!==a.nodeName?a.setAttributeNS(null,"transform","rotate("+e+" "+f.x+" "+f.y+")"):(f=this.symbolMetrics[l.id],a.firstChild.setAttributeNS(null,"transform","rotate("+e+" "+f[1]+" "+f[2]+")")))}c.isFilled?(a.setAttributeNS(null,"fill",b.fillColor),
+a.setAttributeNS(null,"fill-opacity",b.fillOpacity)):a.setAttributeNS(null,"fill","none");c.isStroked?(a.setAttributeNS(null,"stroke",b.strokeColor),a.setAttributeNS(null,"stroke-opacity",b.strokeOpacity),a.setAttributeNS(null,"stroke-width",b.strokeWidth*d),a.setAttributeNS(null,"stroke-linecap",b.strokeLinecap||"round"),a.setAttributeNS(null,"stroke-linejoin","round"),b.strokeDashstyle&&a.setAttributeNS(null,"stroke-dasharray",this.dashStyle(b,d))):a.setAttributeNS(null,"stroke","none");b.pointerEvents&&
+a.setAttributeNS(null,"pointer-events",b.pointerEvents);null!=b.cursor&&a.setAttributeNS(null,"cursor",b.cursor);return a},dashStyle:function(a,b){var c=a.strokeWidth*b,d=a.strokeDashstyle;switch(d){case "solid":return"none";case "dot":return[1,4*c].join();case "dash":return[4*c,4*c].join();case "dashdot":return[4*c,4*c,1,4*c].join();case "longdash":return[8*c,4*c].join();case "longdashdot":return[8*c,4*c,1,4*c].join();default:return OpenLayers.String.trim(d).replace(/\s+/g,",")}},createNode:function(a,
+b){var c=document.createElementNS(this.xmlns,a);b&&c.setAttributeNS(null,"id",b);return c},nodeTypeCompare:function(a,b){return b==a.nodeName},createRenderRoot:function(){var a=this.nodeFactory(this.container.id+"_svgRoot","svg");a.style.display="block";return a},createRoot:function(a){return this.nodeFactory(this.container.id+a,"g")},createDefs:function(){var a=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(a);return a},drawPoint:function(a,b){return this.drawCircle(a,
+b,1)},drawCircle:function(a,b,c){var d=this.getResolution(),e=(b.x-this.featureDx)/d+this.left;b=this.top-b.y/d;return this.inValidRange(e,b)?(a.setAttributeNS(null,"cx",e),a.setAttributeNS(null,"cy",b),a.setAttributeNS(null,"r",c),a):!1},drawLineString:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,"points",c.path),c.complete?a:null):!1},drawLinearRing:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,
+"points",c.path),c.complete?a:null):!1},drawPolygon:function(a,b){for(var c="",d=!0,e=!0,f,g,h=0,k=b.components.length;h<k;h++)c+=" M",f=this.getComponentsString(b.components[h].components," "),(g=f.path)?(c+=" "+g,e=f.complete&&e):d=!1;return d?(a.setAttributeNS(null,"d",c+" z"),a.setAttributeNS(null,"fill-rule","evenodd"),e?a:null):!1},drawRectangle:function(a,b){var c=this.getResolution(),d=(b.x-this.featureDx)/c+this.left,e=this.top-b.y/c;return this.inValidRange(d,e)?(a.setAttributeNS(null,"x",
+d),a.setAttributeNS(null,"y",e),a.setAttributeNS(null,"width",b.width/c),a.setAttributeNS(null,"height",b.height/c),a):!1},drawText:function(a,b,c){var d=!!b.labelOutlineWidth;if(d){var e=OpenLayers.Util.extend({},b);e.fontColor=e.labelOutlineColor;e.fontStrokeColor=e.labelOutlineColor;e.fontStrokeWidth=b.labelOutlineWidth;b.labelOutlineOpacity&&(e.fontOpacity=b.labelOutlineOpacity);delete e.labelOutlineWidth;this.drawText(a,e,c)}var f=this.getResolution(),e=(c.x-this.featureDx)/f+this.left,g=c.y/
+f-this.top,d=d?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX,f=this.nodeFactory(a+d,"text");f.setAttributeNS(null,"x",e);f.setAttributeNS(null,"y",-g);b.fontColor&&f.setAttributeNS(null,"fill",b.fontColor);b.fontStrokeColor&&f.setAttributeNS(null,"stroke",b.fontStrokeColor);b.fontStrokeWidth&&f.setAttributeNS(null,"stroke-width",b.fontStrokeWidth);b.fontOpacity&&f.setAttributeNS(null,"opacity",b.fontOpacity);b.fontFamily&&f.setAttributeNS(null,"font-family",b.fontFamily);b.fontSize&&f.setAttributeNS(null,
+"font-size",b.fontSize);b.fontWeight&&f.setAttributeNS(null,"font-weight",b.fontWeight);b.fontStyle&&f.setAttributeNS(null,"font-style",b.fontStyle);!0===b.labelSelect?(f.setAttributeNS(null,"pointer-events","visible"),f._featureId=a):f.setAttributeNS(null,"pointer-events","none");g=b.labelAlign||OpenLayers.Renderer.defaultSymbolizer.labelAlign;f.setAttributeNS(null,"text-anchor",OpenLayers.Renderer.SVG.LABEL_ALIGN[g[0]]||"middle");!0===OpenLayers.IS_GECKO&&f.setAttributeNS(null,"dominant-baseline",
+OpenLayers.Renderer.SVG.LABEL_ALIGN[g[1]]||"central");for(var h=b.label.split("\n"),k=h.length;f.childNodes.length>k;)f.removeChild(f.lastChild);for(var l=0;l<k;l++){var m=this.nodeFactory(a+d+"_tspan_"+l,"tspan");!0===b.labelSelect&&(m._featureId=a,m._geometry=c,m._geometryClass=c.CLASS_NAME);!1===OpenLayers.IS_GECKO&&m.setAttributeNS(null,"baseline-shift",OpenLayers.Renderer.SVG.LABEL_VSHIFT[g[1]]||"-35%");m.setAttribute("x",e);if(0==l){var r=OpenLayers.Renderer.SVG.LABEL_VFACTOR[g[1]];null==r&&
+(r=-0.5);m.setAttribute("dy",r*(k-1)+"em")}else m.setAttribute("dy","1em");m.textContent=""===h[l]?" ":h[l];m.parentNode||f.appendChild(m)}f.parentNode||this.textRoot.appendChild(f)},getComponentsString:function(a,b){for(var c=[],d=!0,e=a.length,f=[],g,h=0;h<e;h++)g=a[h],c.push(g),(g=this.getShortString(g))?f.push(g):(0<h&&this.getShortString(a[h-1])&&f.push(this.clipLine(a[h],a[h-1])),h<e-1&&this.getShortString(a[h+1])&&f.push(this.clipLine(a[h],a[h+1])),d=!1);return{path:f.join(b||","),complete:d}},
+clipLine:function(a,b){if(b.equals(a))return"";var c=this.getResolution(),d=this.MAX_PIXEL-this.translationParameters.x,e=this.MAX_PIXEL-this.translationParameters.y,f=(b.x-this.featureDx)/c+this.left,g=this.top-b.y/c,h=(a.x-this.featureDx)/c+this.left,c=this.top-a.y/c,k;if(h<-d||h>d)k=(c-g)/(h-f),h=0>h?-d:d,c=g+(h-f)*k;if(c<-e||c>e)k=(h-f)/(c-g),c=0>c?-e:e,h=f+(c-g)*k;return h+","+c},getShortString:function(a){var b=this.getResolution(),c=(a.x-this.featureDx)/b+this.left;a=this.top-a.y/b;return this.inValidRange(c,
+a)?c+","+a:!1},getPosition:function(a){return{x:parseFloat(a.getAttributeNS(null,"cx")),y:parseFloat(a.getAttributeNS(null,"cy"))}},importSymbol:function(a){this.defs||(this.defs=this.createDefs());var b=this.container.id+"-"+a,c=document.getElementById(b);if(null!=c)return c;var d=OpenLayers.Renderer.symbol[a];if(!d)throw Error(a+" is not a valid symbol name");a=this.nodeFactory(b,"symbol");var e=this.nodeFactory(null,"polygon");a.appendChild(e);for(var c=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,
+0,0),f=[],g,h,k=0;k<d.length;k+=2)g=d[k],h=d[k+1],c.left=Math.min(c.left,g),c.bottom=Math.min(c.bottom,h),c.right=Math.max(c.right,g),c.top=Math.max(c.top,h),f.push(g,",",h);e.setAttributeNS(null,"points",f.join(" "));d=c.getWidth();e=c.getHeight();a.setAttributeNS(null,"viewBox",[c.left-d,c.bottom-e,3*d,3*e].join(" "));this.symbolMetrics[b]=[Math.max(d,e),c.getCenterLonLat().lon,c.getCenterLonLat().lat];this.defs.appendChild(a);return a},getFeatureIdFromEvent:function(a){var b=OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this,
+arguments);b||(b=a.target,b=b.parentNode&&b!=this.rendererRoot?b.parentNode._featureId:void 0);return b},CLASS_NAME:"OpenLayers.Renderer.SVG"});OpenLayers.Renderer.SVG.LABEL_ALIGN={l:"start",r:"end",b:"bottom",t:"hanging"};OpenLayers.Renderer.SVG.LABEL_VSHIFT={t:"-70%",b:"0"};OpenLayers.Renderer.SVG.LABEL_VFACTOR={t:0,b:-1};OpenLayers.Renderer.SVG.preventDefault=function(a){OpenLayers.Event.preventDefault(a)};OpenLayers.Popup=OpenLayers.Class({events:null,id:"",lonlat:null,div:null,contentSize:null,size:null,contentHTML:null,backgroundColor:"",opacity:"",border:"",contentDiv:null,groupDiv:null,closeDiv:null,autoSize:!1,minSize:null,maxSize:null,displayClass:"olPopup",contentDisplayClass:"olPopupContent",padding:0,disableFirefoxOverflowHack:!1,fixPadding:function(){"number"==typeof this.padding&&(this.padding=new OpenLayers.Bounds(this.padding,this.padding,this.padding,this.padding))},panMapIfOutOfView:!1,
+keepInMap:!1,closeOnMove:!1,map:null,initialize:function(a,b,c,d,e,f){null==a&&(a=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"));this.id=a;this.lonlat=b;this.contentSize=null!=c?c:new OpenLayers.Size(OpenLayers.Popup.WIDTH,OpenLayers.Popup.HEIGHT);null!=d&&(this.contentHTML=d);this.backgroundColor=OpenLayers.Popup.COLOR;this.opacity=OpenLayers.Popup.OPACITY;this.border=OpenLayers.Popup.BORDER;this.div=OpenLayers.Util.createDiv(this.id,null,null,null,null,null,"hidden");this.div.className=this.displayClass;
+this.groupDiv=OpenLayers.Util.createDiv(this.id+"_GroupDiv",null,null,null,"relative",null,"hidden");a=this.div.id+"_contentDiv";this.contentDiv=OpenLayers.Util.createDiv(a,null,this.contentSize.clone(),null,"relative");this.contentDiv.className=this.contentDisplayClass;this.groupDiv.appendChild(this.contentDiv);this.div.appendChild(this.groupDiv);e&&this.addCloseBox(f);this.registerEvents()},destroy:function(){this.border=this.opacity=this.backgroundColor=this.contentHTML=this.size=this.lonlat=this.id=
+null;this.closeOnMove&&this.map&&this.map.events.unregister("movestart",this,this.hide);this.events.destroy();this.events=null;this.closeDiv&&(OpenLayers.Event.stopObservingElement(this.closeDiv),this.groupDiv.removeChild(this.closeDiv));this.closeDiv=null;this.div.removeChild(this.groupDiv);this.groupDiv=null;null!=this.map&&this.map.removePopup(this);this.panMapIfOutOfView=this.padding=this.maxSize=this.minSize=this.autoSize=this.div=this.map=null},draw:function(a){null==a&&null!=this.lonlat&&null!=
+this.map&&(a=this.map.getLayerPxFromLonLat(this.lonlat));this.closeOnMove&&this.map.events.register("movestart",this,this.hide);this.disableFirefoxOverflowHack||"firefox"!=OpenLayers.BROWSER_NAME||(this.map.events.register("movestart",this,function(){var a=document.defaultView.getComputedStyle(this.contentDiv,null).getPropertyValue("overflow");"hidden"!=a&&(this.contentDiv._oldOverflow=a,this.contentDiv.style.overflow="hidden")}),this.map.events.register("moveend",this,function(){var a=this.contentDiv._oldOverflow;
+a&&(this.contentDiv.style.overflow=a,this.contentDiv._oldOverflow=null)}));this.moveTo(a);this.autoSize||this.size||this.setSize(this.contentSize);this.setBackgroundColor();this.setOpacity();this.setBorder();this.setContentHTML();this.panMapIfOutOfView&&this.panIntoView();return this.div},updatePosition:function(){if(this.lonlat&&this.map){var a=this.map.getLayerPxFromLonLat(this.lonlat);a&&this.moveTo(a)}},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"px",this.div.style.top=
+a.y+"px")},visible:function(){return OpenLayers.Element.visible(this.div)},toggle:function(){this.visible()?this.hide():this.show()},show:function(){this.div.style.display="";this.panMapIfOutOfView&&this.panIntoView()},hide:function(){this.div.style.display="none"},setSize:function(a){this.size=a.clone();var b=this.getContentDivPadding(),c=b.left+b.right,d=b.top+b.bottom;this.fixPadding();c+=this.padding.left+this.padding.right;d+=this.padding.top+this.padding.bottom;if(this.closeDiv)var e=parseInt(this.closeDiv.style.width),
+c=c+(e+b.right);this.size.w+=c;this.size.h+=d;"msie"==OpenLayers.BROWSER_NAME&&(this.contentSize.w+=b.left+b.right,this.contentSize.h+=b.bottom+b.top);null!=this.div&&(this.div.style.width=this.size.w+"px",this.div.style.height=this.size.h+"px");null!=this.contentDiv&&(this.contentDiv.style.width=a.w+"px",this.contentDiv.style.height=a.h+"px")},updateSize:function(){var a="<div class='"+this.contentDisplayClass+"'>"+this.contentDiv.innerHTML+"</div>",b=this.map?this.map.div:document.body,c=OpenLayers.Util.getRenderedDimensions(a,
+null,{displayClass:this.displayClass,containerElement:b}),d=this.getSafeContentSize(c),e=null;d.equals(c)?e=c:(c={w:d.w<c.w?d.w:null,h:d.h<c.h?d.h:null},c.w&&c.h?e=d:(a=OpenLayers.Util.getRenderedDimensions(a,c,{displayClass:this.contentDisplayClass,containerElement:b}),"hidden"!=OpenLayers.Element.getStyle(this.contentDiv,"overflow")&&a.equals(d)&&(d=OpenLayers.Util.getScrollbarWidth(),c.w?a.h+=d:a.w+=d),e=this.getSafeContentSize(a)));this.setSize(e)},setBackgroundColor:function(a){void 0!=a&&(this.backgroundColor=
+a);null!=this.div&&(this.div.style.backgroundColor=this.backgroundColor)},setOpacity:function(a){void 0!=a&&(this.opacity=a);null!=this.div&&(this.div.style.opacity=this.opacity,this.div.style.filter="alpha(opacity="+100*this.opacity+")")},setBorder:function(a){void 0!=a&&(this.border=a);null!=this.div&&(this.div.style.border=this.border)},setContentHTML:function(a){null!=a&&(this.contentHTML=a);null!=this.contentDiv&&(null!=this.contentHTML&&this.contentHTML!=this.contentDiv.innerHTML)&&(this.contentDiv.innerHTML=
+this.contentHTML,this.autoSize&&(this.registerImageListeners(),this.updateSize()))},registerImageListeners:function(){for(var a=function(){null!==this.popup.id&&(this.popup.updateSize(),this.popup.visible()&&this.popup.panMapIfOutOfView&&this.popup.panIntoView(),OpenLayers.Event.stopObserving(this.img,"load",this.img._onImgLoad))},b=this.contentDiv.getElementsByTagName("img"),c=0,d=b.length;c<d;c++){var e=b[c];if(0==e.width||0==e.height)e._onImgLoad=OpenLayers.Function.bind(a,{popup:this,img:e}),
+OpenLayers.Event.observe(e,"load",e._onImgLoad)}},getSafeContentSize:function(a){a=a.clone();var b=this.getContentDivPadding(),c=b.left+b.right,d=b.top+b.bottom;this.fixPadding();c+=this.padding.left+this.padding.right;d+=this.padding.top+this.padding.bottom;if(this.closeDiv)var e=parseInt(this.closeDiv.style.width),c=c+(e+b.right);this.minSize&&(a.w=Math.max(a.w,this.minSize.w-c),a.h=Math.max(a.h,this.minSize.h-d));this.maxSize&&(a.w=Math.min(a.w,this.maxSize.w-c),a.h=Math.min(a.h,this.maxSize.h-
+d));if(this.map&&this.map.size){e=b=0;if(this.keepInMap&&!this.panMapIfOutOfView)switch(e=this.map.getPixelFromLonLat(this.lonlat),this.relativePosition){case "tr":b=e.x;e=this.map.size.h-e.y;break;case "tl":b=this.map.size.w-e.x;e=this.map.size.h-e.y;break;case "bl":b=this.map.size.w-e.x;e=e.y;break;case "br":b=e.x;e=e.y;break;default:b=e.x,e=this.map.size.h-e.y}d=this.map.size.h-this.map.paddingForPopups.top-this.map.paddingForPopups.bottom-d-e;a.w=Math.min(a.w,this.map.size.w-this.map.paddingForPopups.left-
+this.map.paddingForPopups.right-c-b);a.h=Math.min(a.h,d)}return a},getContentDivPadding:function(){var a=this._contentDivPadding;a||(null==this.div.parentNode&&(this.div.style.display="none",document.body.appendChild(this.div)),this._contentDivPadding=a=new OpenLayers.Bounds(OpenLayers.Element.getStyle(this.contentDiv,"padding-left"),OpenLayers.Element.getStyle(this.contentDiv,"padding-bottom"),OpenLayers.Element.getStyle(this.contentDiv,"padding-right"),OpenLayers.Element.getStyle(this.contentDiv,
+"padding-top")),this.div.parentNode==document.body&&(document.body.removeChild(this.div),this.div.style.display=""));return a},addCloseBox:function(a){this.closeDiv=OpenLayers.Util.createDiv(this.id+"_close",null,{w:17,h:17});this.closeDiv.className="olPopupCloseBox";var b=this.getContentDivPadding();this.closeDiv.style.right=b.right+"px";this.closeDiv.style.top=b.top+"px";this.groupDiv.appendChild(this.closeDiv);a=a||function(a){this.hide();OpenLayers.Event.stop(a)};OpenLayers.Event.observe(this.closeDiv,
+"touchend",OpenLayers.Function.bindAsEventListener(a,this));OpenLayers.Event.observe(this.closeDiv,"click",OpenLayers.Function.bindAsEventListener(a,this))},panIntoView:function(){var a=this.map.getSize(),b=this.map.getViewPortPxFromLayerPx(new OpenLayers.Pixel(parseInt(this.div.style.left),parseInt(this.div.style.top))),c=b.clone();b.x<this.map.paddingForPopups.left?c.x=this.map.paddingForPopups.left:b.x+this.size.w>a.w-this.map.paddingForPopups.right&&(c.x=a.w-this.map.paddingForPopups.right-this.size.w);
+b.y<this.map.paddingForPopups.top?c.y=this.map.paddingForPopups.top:b.y+this.size.h>a.h-this.map.paddingForPopups.bottom&&(c.y=a.h-this.map.paddingForPopups.bottom-this.size.h);this.map.pan(b.x-c.x,b.y-c.y)},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,!0);this.events.on({mousedown:this.onmousedown,mousemove:this.onmousemove,mouseup:this.onmouseup,click:this.onclick,mouseout:this.onmouseout,dblclick:this.ondblclick,touchstart:function(a){OpenLayers.Event.stop(a,!0)},
+scope:this})},onmousedown:function(a){this.mousedown=!0;OpenLayers.Event.stop(a,!0)},onmousemove:function(a){this.mousedown&&OpenLayers.Event.stop(a,!0)},onmouseup:function(a){this.mousedown&&(this.mousedown=!1,OpenLayers.Event.stop(a,!0))},onclick:function(a){OpenLayers.Event.stop(a,!0)},onmouseout:function(a){this.mousedown=!1},ondblclick:function(a){OpenLayers.Event.stop(a,!0)},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLOR="white";
+OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,keepInMap:!0,anchor:null,initialize:function(a,b,c,d,e,f,g){OpenLayers.Popup.prototype.initialize.apply(this,[a,b,c,d,f,g]);this.anchor=null!=e?e:{size:new OpenLayers.Size(0,0),offset:new OpenLayers.Pixel(0,0)}},destroy:function(){this.relativePosition=this.anchor=null;OpenLayers.Popup.prototype.destroy.apply(this,arguments)},show:function(){this.updatePosition();OpenLayers.Popup.prototype.show.apply(this,arguments)},
+moveTo:function(a){var b=this.relativePosition;this.relativePosition=this.calculateRelativePosition(a);OpenLayers.Popup.prototype.moveTo.call(this,this.calculateNewPx(a));this.relativePosition!=b&&this.updateRelativePosition()},setSize:function(a){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if(this.lonlat&&this.map){var b=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(b)}},calculateRelativePosition:function(a){a=this.map.getLonLatFromLayerPx(a);a=this.map.getExtent().determineQuadrant(a);
+return OpenLayers.Bounds.oppositeQuadrant(a)},updateRelativePosition:function(){},calculateNewPx:function(a){a=a.offset(this.anchor.offset);var b=this.size||this.contentSize,c="t"==this.relativePosition.charAt(0);a.y+=c?-b.h:this.anchor.size.h;c="l"==this.relativePosition.charAt(1);a.x+=c?-b.w:this.anchor.size.w;return a},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Popup.Framed=OpenLayers.Class(OpenLayers.Popup.Anchored,{imageSrc:null,imageSize:null,isAlphaImage:!1,positionBlocks:null,blocks:null,fixedRelativePosition:!1,initialize:function(a,b,c,d,e,f,g){OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);this.fixedRelativePosition&&(this.updateRelativePosition(),this.calculateRelativePosition=function(a){return this.relativePosition});this.contentDiv.style.position="absolute";this.contentDiv.style.zIndex=1;f&&(this.closeDiv.style.zIndex=
+1);this.groupDiv.style.position="absolute";this.groupDiv.style.top="0px";this.groupDiv.style.left="0px";this.groupDiv.style.height="100%";this.groupDiv.style.width="100%"},destroy:function(){this.isAlphaImage=this.imageSize=this.imageSrc=null;this.fixedRelativePosition=!1;this.positionBlocks=null;for(var a=0;a<this.blocks.length;a++){var b=this.blocks[a];b.image&&b.div.removeChild(b.image);b.image=null;b.div&&this.groupDiv.removeChild(b.div);b.div=null}this.blocks=null;OpenLayers.Popup.Anchored.prototype.destroy.apply(this,
+arguments)},setBackgroundColor:function(a){},setBorder:function(){},setOpacity:function(a){},setSize:function(a){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.updateBlocks()},updateRelativePosition:function(){this.padding=this.positionBlocks[this.relativePosition].padding;if(this.closeDiv){var a=this.getContentDivPadding();this.closeDiv.style.right=a.right+this.padding.right+"px";this.closeDiv.style.top=a.top+this.padding.top+"px"}this.updateBlocks()},calculateNewPx:function(a){var b=
+OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(this,arguments);return b=b.offset(this.positionBlocks[this.relativePosition].offset)},createBlocks:function(){this.blocks=[];var a=null,b;for(b in this.positionBlocks){a=b;break}a=this.positionBlocks[a];for(b=0;b<a.blocks.length;b++){var c={};this.blocks.push(c);c.div=OpenLayers.Util.createDiv(this.id+"_FrameDecorationDiv_"+b,null,null,null,"absolute",null,"hidden",null);c.image=(this.isAlphaImage?OpenLayers.Util.createAlphaImageDiv:OpenLayers.Util.createImage)(this.id+
+"_FrameDecorationImg_"+b,null,this.imageSize,this.imageSrc,"absolute",null,null,null);c.div.appendChild(c.image);this.groupDiv.appendChild(c.div)}},updateBlocks:function(){this.blocks||this.createBlocks();if(this.size&&this.relativePosition){for(var a=this.positionBlocks[this.relativePosition],b=0;b<a.blocks.length;b++){var c=a.blocks[b],d=this.blocks[b],e=c.anchor.left,f=c.anchor.bottom,g=c.anchor.right,h=c.anchor.top,k=isNaN(c.size.w)?this.size.w-(g+e):c.size.w,l=isNaN(c.size.h)?this.size.h-(f+
+h):c.size.h;d.div.style.width=(0>k?0:k)+"px";d.div.style.height=(0>l?0:l)+"px";d.div.style.left=null!=e?e+"px":"";d.div.style.bottom=null!=f?f+"px":"";d.div.style.right=null!=g?g+"px":"";d.div.style.top=null!=h?h+"px":"";d.image.style.left=c.position.x+"px";d.image.style.top=c.position.y+"px"}this.contentDiv.style.left=this.padding.left+"px";this.contentDiv.style.top=this.padding.top+"px"}},CLASS_NAME:"OpenLayers.Popup.Framed"});OpenLayers.Format=OpenLayers.Class({options:null,externalProjection:null,internalProjection:null,data:null,keepData:!1,initialize:function(a){OpenLayers.Util.extend(this,a);this.options=a},destroy:function(){},read:function(a){throw Error("Read not implemented.");},write:function(a){throw Error("Write not implemented.");},CLASS_NAME:"OpenLayers.Format"});OpenLayers.Format.JSON=OpenLayers.Class(OpenLayers.Format,{indent:" ",space:" ",newline:"\n",level:0,pretty:!1,nativeJSON:function(){return!(!window.JSON||"function"!=typeof JSON.parse||"function"!=typeof JSON.stringify)}(),read:function(a,b){var c;if(this.nativeJSON)c=JSON.parse(a,b);else try{if(/^[\],:{}\s]*$/.test(a.replace(/\\["\\\/bfnrtu]/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))&&(c=eval("("+a+")"),"function"===
+typeof b)){var d=function(a,c){if(c&&"object"===typeof c)for(var e in c)c.hasOwnProperty(e)&&(c[e]=d(e,c[e]));return b(a,c)};c=d("",c)}}catch(e){}this.keepData&&(this.data=c);return c},write:function(a,b){this.pretty=!!b;var c=null,d=typeof a;if(this.serialize[d])try{c=!this.pretty&&this.nativeJSON?JSON.stringify(a):this.serialize[d].apply(this,[a])}catch(e){OpenLayers.Console.error("Trouble serializing: "+e)}return c},writeIndent:function(){var a=[];if(this.pretty)for(var b=0;b<this.level;++b)a.push(this.indent);
+return a.join("")},writeNewline:function(){return this.pretty?this.newline:""},writeSpace:function(){return this.pretty?this.space:""},serialize:{object:function(a){if(null==a)return"null";if(a.constructor==Date)return this.serialize.date.apply(this,[a]);if(a.constructor==Array)return this.serialize.array.apply(this,[a]);var b=["{"];this.level+=1;var c,d,e,f=!1;for(c in a)a.hasOwnProperty(c)&&(d=OpenLayers.Format.JSON.prototype.write.apply(this,[c,this.pretty]),e=OpenLayers.Format.JSON.prototype.write.apply(this,
+[a[c],this.pretty]),null!=d&&null!=e&&(f&&b.push(","),b.push(this.writeNewline(),this.writeIndent(),d,":",this.writeSpace(),e),f=!0));this.level-=1;b.push(this.writeNewline(),this.writeIndent(),"}");return b.join("")},array:function(a){var b,c=["["];this.level+=1;for(var d=0,e=a.length;d<e;++d)b=OpenLayers.Format.JSON.prototype.write.apply(this,[a[d],this.pretty]),null!=b&&(0<d&&c.push(","),c.push(this.writeNewline(),this.writeIndent(),b));this.level-=1;c.push(this.writeNewline(),this.writeIndent(),
+"]");return c.join("")},string:function(a){var b={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};return/["\\\x00-\x1f]/.test(a)?'"'+a.replace(/([\x00-\x1f\\"])/g,function(a,d){var e=b[d];if(e)return e;e=d.charCodeAt();return"\\u00"+Math.floor(e/16).toString(16)+(e%16).toString(16)})+'"':'"'+a+'"'},number:function(a){return isFinite(a)?String(a):"null"},"boolean":function(a){return String(a)},date:function(a){function b(a){return 10>a?"0"+a:a}return'"'+a.getFullYear()+
+"-"+b(a.getMonth()+1)+"-"+b(a.getDate())+"T"+b(a.getHours())+":"+b(a.getMinutes())+":"+b(a.getSeconds())+'"'}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.GeoJSON=OpenLayers.Class(OpenLayers.Format.JSON,{ignoreExtraDims:!1,read:function(a,b,c){b=b?b:"FeatureCollection";var d=null,e=null,e="string"==typeof a?OpenLayers.Format.JSON.prototype.read.apply(this,[a,c]):a;if(!e)OpenLayers.Console.error("Bad JSON: "+a);else if("string"!=typeof e.type)OpenLayers.Console.error("Bad GeoJSON - no type: "+a);else if(this.isValidType(e,b))switch(b){case "Geometry":try{d=this.parseGeometry(e)}catch(f){OpenLayers.Console.error(f)}break;case "Feature":try{d=
+this.parseFeature(e),d.type="Feature"}catch(g){OpenLayers.Console.error(g)}break;case "FeatureCollection":switch(d=[],e.type){case "Feature":try{d.push(this.parseFeature(e))}catch(h){d=null,OpenLayers.Console.error(h)}break;case "FeatureCollection":a=0;for(b=e.features.length;a<b;++a)try{d.push(this.parseFeature(e.features[a]))}catch(k){d=null,OpenLayers.Console.error(k)}break;default:try{var l=this.parseGeometry(e);d.push(new OpenLayers.Feature.Vector(l))}catch(m){d=null,OpenLayers.Console.error(m)}}}return d},
+isValidType:function(a,b){var c=!1;switch(b){case "Geometry":-1==OpenLayers.Util.indexOf("Point MultiPoint LineString MultiLineString Polygon MultiPolygon Box GeometryCollection".split(" "),a.type)?OpenLayers.Console.error("Unsupported geometry type: "+a.type):c=!0;break;case "FeatureCollection":c=!0;break;default:a.type==b?c=!0:OpenLayers.Console.error("Cannot convert types from "+a.type+" to "+b)}return c},parseFeature:function(a){var b,c,d;c=a.properties?a.properties:{};d=a.geometry&&a.geometry.bbox||
+a.bbox;try{b=this.parseGeometry(a.geometry)}catch(e){throw e;}b=new OpenLayers.Feature.Vector(b,c);d&&(b.bounds=OpenLayers.Bounds.fromArray(d));a.id&&(b.fid=a.id);return b},parseGeometry:function(a){if(null==a)return null;var b,c=!1;if("GeometryCollection"==a.type){if(!OpenLayers.Util.isArray(a.geometries))throw"GeometryCollection must have geometries array: "+a;b=a.geometries.length;for(var c=Array(b),d=0;d<b;++d)c[d]=this.parseGeometry.apply(this,[a.geometries[d]]);b=new OpenLayers.Geometry.Collection(c);
+c=!0}else{if(!OpenLayers.Util.isArray(a.coordinates))throw"Geometry must have coordinates array: "+a;if(!this.parseCoords[a.type.toLowerCase()])throw"Unsupported geometry type: "+a.type;try{b=this.parseCoords[a.type.toLowerCase()].apply(this,[a.coordinates])}catch(e){throw e;}}this.internalProjection&&(this.externalProjection&&!c)&&b.transform(this.externalProjection,this.internalProjection);return b},parseCoords:{point:function(a){if(!1==this.ignoreExtraDims&&2!=a.length)throw"Only 2D points are supported: "+
+a;return new OpenLayers.Geometry.Point(a[0],a[1])},multipoint:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.LineString(b)},multilinestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=
+this.parseCoords.linestring.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){for(var b=[],c,d,e=0,f=a.length;e<f;++e){try{d=this.parseCoords.linestring.apply(this,[a[e]])}catch(g){throw g;}c=new OpenLayers.Geometry.LinearRing(d.components);b.push(c)}return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.polygon.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPolygon(b)},
+box:function(a){if(2!=a.length)throw"GeoJSON box coordinates must have 2 elements";return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(a[0][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[0][1])])])}},write:function(a,b){var c={type:null};if(OpenLayers.Util.isArray(a)){c.type="FeatureCollection";var d=
+a.length;c.features=Array(d);for(var e=0;e<d;++e){var f=a[e];if(!f instanceof OpenLayers.Feature.Vector)throw"FeatureCollection only supports collections of features: "+f;c.features[e]=this.extract.feature.apply(this,[f])}}else 0==a.CLASS_NAME.indexOf("OpenLayers.Geometry")?c=this.extract.geometry.apply(this,[a]):a instanceof OpenLayers.Feature.Vector&&(c=this.extract.feature.apply(this,[a]),a.layer&&a.layer.projection&&(c.crs=this.createCRSObject(a)));return OpenLayers.Format.JSON.prototype.write.apply(this,
+[c,b])},createCRSObject:function(a){a=a.layer.projection.toString();var b={};a.match(/epsg:/i)&&(a=parseInt(a.substring(a.indexOf(":")+1)),b=4326==a?{type:"name",properties:{name:"urn:ogc:def:crs:OGC:1.3:CRS84"}}:{type:"name",properties:{name:"EPSG:"+a}});return b},extract:{feature:function(a){var b=this.extract.geometry.apply(this,[a.geometry]),b={type:"Feature",properties:a.attributes,geometry:b};null!=a.fid&&(b.id=a.fid);return b},geometry:function(a){if(null==a)return null;this.internalProjection&&
+this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME.split(".")[2];a=this.extract[b.toLowerCase()].apply(this,[a]);return"Collection"==b?{type:"GeometryCollection",geometries:a}:{type:b,coordinates:a}},point:function(a){return[a.x,a.y]},multipoint:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},linestring:function(a){for(var b=[],c=0,d=a.components.length;c<
+d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},multilinestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},polygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},multipolygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.polygon.apply(this,[a.components[c]]));return b},collection:function(a){for(var b=
+a.components.length,c=Array(b),d=0;d<b;++d)c[d]=this.extract.geometry.apply(this,[a.components[d]]);return c}},CLASS_NAME:"OpenLayers.Format.GeoJSON"});OpenLayers.Layer.Google.v3={DEFAULTS:{sphericalMercator:!0,projection:"EPSG:900913"},animationEnabled:!0,loadMapObject:function(){this.type||(this.type=google.maps.MapTypeId.ROADMAP);var a,b=OpenLayers.Layer.Google.cache[this.map.id];b?(a=b.mapObject,++b.count):(a=this.map.getCenter(),b=document.createElement("div"),b.className="olForeignContainer",b.style.width="100%",b.style.height="100%",a=new google.maps.Map(b,{center:a?new google.maps.LatLng(a.lat,a.lon):new google.maps.LatLng(0,0),zoom:this.map.getZoom()||
+0,mapTypeId:this.type,disableDefaultUI:!0,keyboardShortcuts:!1,draggable:!1,disableDoubleClickZoom:!0,scrollwheel:!1,streetViewControl:!1}),b=document.createElement("div"),b.style.width="100%",b.style.height="100%",a.controls[google.maps.ControlPosition.TOP_LEFT].push(b),b={googleControl:b,mapObject:a,count:1},OpenLayers.Layer.Google.cache[this.map.id]=b);this.mapObject=a;this.setGMapVisibility(this.visibility)},onMapResize:function(){this.visibility&&google.maps.event.trigger(this.mapObject,"resize")},
+setGMapVisibility:function(a){var b=OpenLayers.Layer.Google.cache[this.map.id],c=this.map;if(b){for(var d=this.type,e=c.layers,f,g=e.length-1;0<=g;--g)if(f=e[g],f instanceof OpenLayers.Layer.Google&&!0===f.visibility&&!0===f.inRange){d=f.type;a=!0;break}e=this.mapObject.getDiv();if(!0===a){if(e.parentNode!==c.div)if(b.rendered)c.div.appendChild(e),b.googleControl.appendChild(c.viewPortDiv),google.maps.event.trigger(this.mapObject,"resize");else{var h=this;google.maps.event.addListenerOnce(this.mapObject,
+"tilesloaded",function(){b.rendered=!0;h.setGMapVisibility(h.getVisibility());h.moveTo(h.map.getCenter())})}this.mapObject.setMapTypeId(d)}else b.googleControl.hasChildNodes()&&(c.div.appendChild(c.viewPortDiv),c.div.removeChild(e))}},getMapContainer:function(){return this.mapObject.getDiv()},getMapObjectBoundsFromOLBounds:function(a){var b=null;null!=a&&(b=this.sphericalMercator?this.inverseMercator(a.bottom,a.left):new OpenLayers.LonLat(a.bottom,a.left),a=this.sphericalMercator?this.inverseMercator(a.top,
+a.right):new OpenLayers.LonLat(a.top,a.right),b=new google.maps.LatLngBounds(new google.maps.LatLng(b.lat,b.lon),new google.maps.LatLng(a.lat,a.lon)));return b},getMapObjectLonLatFromMapObjectPixel:function(a){var b=this.map.getSize(),c=this.getLongitudeFromMapObjectLonLat(this.mapObject.center),d=this.getLatitudeFromMapObjectLonLat(this.mapObject.center),e=this.map.getResolution();a=new OpenLayers.LonLat(c+(a.x-b.w/2)*e,d-(a.y-b.h/2)*e);this.wrapDateLine&&(a=a.wrapDateLine(this.maxExtent));return this.getMapObjectLonLatFromLonLat(a.lon,
+a.lat)},getMapObjectPixelFromMapObjectLonLat:function(a){var b=this.getLongitudeFromMapObjectLonLat(a);a=this.getLatitudeFromMapObjectLonLat(a);var c=this.map.getResolution(),d=this.map.getExtent();return this.getMapObjectPixelFromXY(1/c*(b-d.left),1/c*(d.top-a))},setMapObjectCenter:function(a,b){if(!1===this.animationEnabled&&b!=this.mapObject.zoom){var c=this.getMapContainer();google.maps.event.addListenerOnce(this.mapObject,"idle",function(){c.style.visibility=""});c.style.visibility="hidden"}this.mapObject.setOptions({center:a,
+zoom:b})},getMapObjectZoomFromMapObjectBounds:function(a){return this.mapObject.getBoundsZoomLevel(a)},getMapObjectLonLatFromLonLat:function(a,b){var c;this.sphericalMercator?(c=this.inverseMercator(a,b),c=new google.maps.LatLng(c.lat,c.lon)):c=new google.maps.LatLng(b,a);return c},getMapObjectPixelFromXY:function(a,b){return new google.maps.Point(a,b)}};OpenLayers.ProxyHost="";OpenLayers.Request||(OpenLayers.Request={});
+OpenLayers.Util.extend(OpenLayers.Request,{DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:!0,user:void 0,password:void 0,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},URL_SPLIT_REGEX:/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,events:new OpenLayers.Events(this),makeSameOrigin:function(a,b){var c=0!==a.indexOf("http"),d=!c&&a.match(this.URL_SPLIT_REGEX);if(d){var e=window.location,c=d[1]==e.protocol&&d[3]==
+e.hostname,d=d[4],e=e.port;if(80!=d&&""!=d||"80"!=e&&""!=e)c=c&&d==e}c||b&&(a="function"==typeof b?b(a):b+encodeURIComponent(a));return a},issue:function(a){var b=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});a=a||{};a.headers=a.headers||{};a=OpenLayers.Util.applyDefaults(a,b);a.headers=OpenLayers.Util.applyDefaults(a.headers,b.headers);var b=!1,c;for(c in a.headers)a.headers.hasOwnProperty(c)&&"x-requested-with"===c.toLowerCase()&&(b=!0);!1===b&&(a.headers["X-Requested-With"]=
+"XMLHttpRequest");var d=new OpenLayers.Request.XMLHttpRequest,e=OpenLayers.Util.urlAppend(a.url,OpenLayers.Util.getParameterString(a.params||{})),e=OpenLayers.Request.makeSameOrigin(e,a.proxy);d.open(a.method,e,a.async,a.user,a.password);for(var f in a.headers)d.setRequestHeader(f,a.headers[f]);var g=this.events,h=this;d.onreadystatechange=function(){d.readyState==OpenLayers.Request.XMLHttpRequest.DONE&&!1!==g.triggerEvent("complete",{request:d,config:a,requestUrl:e})&&h.runCallbacks({request:d,config:a,
+requestUrl:e})};!1===a.async?d.send(a.data):window.setTimeout(function(){0!==d.readyState&&d.send(a.data)},0);return d},runCallbacks:function(a){var b=a.request,c=a.config,d=c.scope?OpenLayers.Function.bind(c.callback,c.scope):c.callback,e;c.success&&(e=c.scope?OpenLayers.Function.bind(c.success,c.scope):c.success);var f;c.failure&&(f=c.scope?OpenLayers.Function.bind(c.failure,c.scope):c.failure);"file:"==OpenLayers.Util.createUrlObject(c.url).protocol&&b.responseText&&(b.status=200);d(b);if(!b.status||
+200<=b.status&&300>b.status)this.events.triggerEvent("success",a),e&&e(b);b.status&&(200>b.status||300<=b.status)&&(this.events.triggerEvent("failure",a),f&&f(b))},GET:function(a){a=OpenLayers.Util.extend(a,{method:"GET"});return OpenLayers.Request.issue(a)},POST:function(a){a=OpenLayers.Util.extend(a,{method:"POST"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},PUT:function(a){a=
+OpenLayers.Util.extend(a,{method:"PUT"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},DELETE:function(a){a=OpenLayers.Util.extend(a,{method:"DELETE"});return OpenLayers.Request.issue(a)},HEAD:function(a){a=OpenLayers.Util.extend(a,{method:"HEAD"});return OpenLayers.Request.issue(a)},OPTIONS:function(a){a=OpenLayers.Util.extend(a,{method:"OPTIONS"});return OpenLayers.Request.issue(a)}});(function(){function a(){this._object=f&&!k?new f:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}function b(){return new a}function c(a){b.onreadystatechange&&b.onreadystatechange.apply(a);a.dispatchEvent({type:"readystatechange",bubbles:!1,cancelable:!1,timeStamp:new Date+0})}function d(a){try{a.responseText=a._object.responseText}catch(b){}try{var c;var d=a._object,e=d.responseXML,f=d.responseText;h&&(f&&e&&!e.documentElement&&d.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/))&&
+(e=new window.ActiveXObject("Microsoft.XMLDOM"),e.async=!1,e.validateOnParse=!1,e.loadXML(f));c=e&&(h&&0!=e.parseError||!e.documentElement||e.documentElement&&"parsererror"==e.documentElement.tagName)?null:e;a.responseXML=c}catch(g){}try{a.status=a._object.status}catch(k){}try{a.statusText=a._object.statusText}catch(u){}}function e(a){a._object.onreadystatechange=new window.Function}var f=window.XMLHttpRequest,g=!!window.controllers,h=window.document.all&&!window.opera,k=h&&window.navigator.userAgent.match(/MSIE 7.0/);
+b.prototype=a.prototype;g&&f.wrapped&&(b.wrapped=f.wrapped);b.UNSENT=0;b.OPENED=1;b.HEADERS_RECEIVED=2;b.LOADING=3;b.DONE=4;b.prototype.readyState=b.UNSENT;b.prototype.responseText="";b.prototype.responseXML=null;b.prototype.status=0;b.prototype.statusText="";b.prototype.priority="NORMAL";b.prototype.onreadystatechange=null;b.onreadystatechange=null;b.onopen=null;b.onsend=null;b.onabort=null;b.prototype.open=function(a,f,k,p,n){delete this._headers;3>arguments.length&&(k=!0);this._async=k;var q=this,
+s=this.readyState,t;h&&k&&(t=function(){s!=b.DONE&&(e(q),q.abort())},window.attachEvent("onunload",t));b.onopen&&b.onopen.apply(this,arguments);4<arguments.length?this._object.open(a,f,k,p,n):3<arguments.length?this._object.open(a,f,k,p):this._object.open(a,f,k);this.readyState=b.OPENED;c(this);this._object.onreadystatechange=function(){if(!g||k)q.readyState=q._object.readyState,d(q),q._aborted?q.readyState=b.UNSENT:(q.readyState==b.DONE&&(delete q._data,e(q),h&&k&&window.detachEvent("onunload",t)),
+s!=q.readyState&&c(q),s=q.readyState)}};b.prototype.send=function(a){b.onsend&&b.onsend.apply(this,arguments);arguments.length||(a=null);a&&a.nodeType&&(a=window.XMLSerializer?(new window.XMLSerializer).serializeToString(a):a.xml,this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml"));this._data=a;a:if(this._object.send(this._data),g&&!this._async)for(this.readyState=b.OPENED,d(this);this.readyState<b.DONE;)if(this.readyState++,c(this),this._aborted)break a};
+b.prototype.abort=function(){b.onabort&&b.onabort.apply(this,arguments);this.readyState>b.UNSENT&&(this._aborted=!0);this._object.abort();e(this);this.readyState=b.UNSENT;delete this._data};b.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};b.prototype.getResponseHeader=function(a){return this._object.getResponseHeader(a)};b.prototype.setRequestHeader=function(a,b){this._headers||(this._headers={});this._headers[a]=b;return this._object.setRequestHeader(a,b)};
+b.prototype.addEventListener=function(a,b,c){for(var d=0,e;e=this._listeners[d];d++)if(e[0]==a&&e[1]==b&&e[2]==c)return;this._listeners.push([a,b,c])};b.prototype.removeEventListener=function(a,b,c){for(var d=0,e;(e=this._listeners[d])&&(e[0]!=a||e[1]!=b||e[2]!=c);d++);e&&this._listeners.splice(d,1)};b.prototype.dispatchEvent=function(a){a={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){},preventDefault:function(){},
+initEvent:function(){}};"readystatechange"==a.type&&this.onreadystatechange&&(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[a]);for(var b=0,c;c=this._listeners[b];b++)c[0]!=a.type||c[2]||(c[1].handleEvent||c[1]).apply(this,[a])};b.prototype.toString=function(){return"[object XMLHttpRequest]"};b.toString=function(){return"[XMLHttpRequest]"};window.Function.prototype.apply||(window.Function.prototype.apply=function(a,b){b||(b=[]);a.__func=this;a.__func(b[0],b[1],b[2],b[3],
+b[4]);delete a.__func});OpenLayers.Request||(OpenLayers.Request={});OpenLayers.Request.XMLHttpRequest=b})();OpenLayers.Filter.Comparison=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,matchCase:!0,lowerBoundary:null,upperBoundary:null,initialize:function(a){OpenLayers.Filter.prototype.initialize.apply(this,[a]);this.type===OpenLayers.Filter.Comparison.LIKE&&void 0===a.matchCase&&(this.matchCase=null)},evaluate:function(a){a instanceof OpenLayers.Feature.Vector&&(a=a.attributes);var b=!1;a=a[this.property];switch(this.type){case OpenLayers.Filter.Comparison.EQUAL_TO:b=this.value;
+b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a==b:a.toUpperCase()==b.toUpperCase();break;case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:b=this.value;b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a!=b:a.toUpperCase()!=b.toUpperCase();break;case OpenLayers.Filter.Comparison.LESS_THAN:b=a<this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN:b=a>this.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:b=a<=this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:b=
+a>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:b=a>=this.lowerBoundary&&a<=this.upperBoundary;break;case OpenLayers.Filter.Comparison.LIKE:b=RegExp(this.value,"gi").test(a);break;case OpenLayers.Filter.Comparison.IS_NULL:b=null===a}return b},value2regex:function(a,b,c){if("."==a)throw Error("'.' is an unsupported wildCard character for OpenLayers.Filter.Comparison");a=a?a:"*";b=b?b:".";this.value=this.value.replace(RegExp("\\"+(c?c:"!")+"(.|$)","g"),"\\$1");this.value=this.value.replace(RegExp("\\"+
+b,"g"),".");this.value=this.value.replace(RegExp("\\"+a,"g"),".*");this.value=this.value.replace(RegExp("\\\\.\\*","g"),"\\"+a);return this.value=this.value.replace(RegExp("\\\\\\.","g"),"\\"+b)},regex2value:function(){var a=this.value,a=a.replace(/!/g,"!!"),a=a.replace(/(\\)?\\\./g,function(a,c){return c?a:"!."}),a=a.replace(/(\\)?\\\*/g,function(a,c){return c?a:"!*"}),a=a.replace(/\\\\/g,"\\");return a=a.replace(/\.\*/g,"*")},clone:function(){return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison,
+this)},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.Comparison.IS_NULL="NULL";OpenLayers.Popup.FramedCloud=OpenLayers.Class(OpenLayers.Popup.Framed,{contentDisplayClass:"olFramedCloudPopupContent",autoSize:!0,panMapIfOutOfView:!0,imageSize:new OpenLayers.Size(1276,736),isAlphaImage:!1,fixedRelativePosition:!1,positionBlocks:{tl:{offset:new OpenLayers.Pixel(44,0),padding:new OpenLayers.Bounds(8,40,8,9),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,
+50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,18),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-632)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(0,-688)}]},tr:{offset:new OpenLayers.Pixel(-45,0),padding:new OpenLayers.Bounds(8,40,8,9),blocks:[{size:new OpenLayers.Size("auto",
+"auto"),anchor:new OpenLayers.Bounds(0,51,22,0),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,50,0,0),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",19),anchor:new OpenLayers.Bounds(0,32,22,null),position:new OpenLayers.Pixel(0,-631)},{size:new OpenLayers.Size(22,19),anchor:new OpenLayers.Bounds(null,32,0,null),position:new OpenLayers.Pixel(-1238,-631)},{size:new OpenLayers.Size(81,35),anchor:new OpenLayers.Bounds(0,
+0,null,null),position:new OpenLayers.Pixel(-215,-687)}]},bl:{offset:new OpenLayers.Pixel(45,0),padding:new OpenLayers.Bounds(8,9,8,40),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,0)},{size:new OpenLayers.Size("auto",21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,
+21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(null,null,0,0),position:new OpenLayers.Pixel(-101,-674)}]},br:{offset:new OpenLayers.Pixel(-44,0),padding:new OpenLayers.Bounds(8,9,8,40),blocks:[{size:new OpenLayers.Size("auto","auto"),anchor:new OpenLayers.Bounds(0,21,22,32),position:new OpenLayers.Pixel(0,0)},{size:new OpenLayers.Size(22,"auto"),anchor:new OpenLayers.Bounds(null,21,0,32),position:new OpenLayers.Pixel(-1238,
+0)},{size:new OpenLayers.Size("auto",21),anchor:new OpenLayers.Bounds(0,0,22,null),position:new OpenLayers.Pixel(0,-629)},{size:new OpenLayers.Size(22,21),anchor:new OpenLayers.Bounds(null,0,0,null),position:new OpenLayers.Pixel(-1238,-629)},{size:new OpenLayers.Size(81,33),anchor:new OpenLayers.Bounds(0,null,null,0),position:new OpenLayers.Pixel(-311,-674)}]}},minSize:new OpenLayers.Size(105,10),maxSize:new OpenLayers.Size(1200,660),initialize:function(a,b,c,d,e,f,g){this.imageSrc=OpenLayers.Util.getImageLocation("cloud-popup-relative.png");
+OpenLayers.Popup.Framed.prototype.initialize.apply(this,arguments);this.contentDiv.className=this.contentDisplayClass},CLASS_NAME:"OpenLayers.Popup.FramedCloud"});OpenLayers.Rule=OpenLayers.Class({id:null,name:null,title:null,description:null,context:null,filter:null,elseFilter:!1,symbolizer:null,symbolizers:null,minScaleDenominator:null,maxScaleDenominator:null,initialize:function(a){this.symbolizer={};OpenLayers.Util.extend(this,a);this.symbolizers&&delete this.symbolizer;this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){for(var a in this.symbolizer)this.symbolizer[a]=null;this.symbolizer=null;delete this.symbolizers},evaluate:function(a){var b=
+this.getContext(a),c=!0;if(this.minScaleDenominator||this.maxScaleDenominator)var d=a.layer.map.getScale();this.minScaleDenominator&&(c=d>=OpenLayers.Style.createLiteral(this.minScaleDenominator,b));c&&this.maxScaleDenominator&&(c=d<OpenLayers.Style.createLiteral(this.maxScaleDenominator,b));c&&this.filter&&(c="OpenLayers.Filter.FeatureId"==this.filter.CLASS_NAME?this.filter.evaluate(a):this.filter.evaluate(b));return c},getContext:function(a){var b=this.context;b||(b=a.attributes||a.data);"function"==
+typeof this.context&&(b=this.context(a));return b},clone:function(){var a=OpenLayers.Util.extend({},this);if(this.symbolizers){var b=this.symbolizers.length;a.symbolizers=Array(b);for(var c=0;c<b;++c)a.symbolizers[c]=this.symbolizers[c].clone()}else{a.symbolizer={};for(var d in this.symbolizer)b=this.symbolizer[d],c=typeof b,"object"===c?a.symbolizer[d]=OpenLayers.Util.extend({},b):"string"===c&&(a.symbolizer[d]=b)}a.filter=this.filter&&this.filter.clone();a.context=this.context&&OpenLayers.Util.extend({},
+this.context);return new OpenLayers.Rule(a)},CLASS_NAME:"OpenLayers.Rule"});OpenLayers.Renderer.VML=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"urn:schemas-microsoft-com:vml",symbolCache:{},offset:null,initialize:function(a){if(this.supported()){if(!document.namespaces.olv){document.namespaces.add("olv",this.xmlns);for(var b=document.createStyleSheet(),c="shape rect oval fill stroke imagedata group textbox".split(" "),d=0,e=c.length;d<e;d++)b.addRule("olv\\:"+c[d],"behavior: url(#default#VML); position: absolute; display: inline-block;")}OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+arguments)}},supported:function(){return!!document.namespaces},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=a.left/d|0,d=a.top/d-this.size.h|0;b||!this.offset?(this.offset={x:e,y:d},d=e=0):(e-=this.offset.x,d-=this.offset.y);this.root.coordorigin=e-this.xOffset+" "+d;for(var e=[this.root,this.vectorRoot,this.textRoot],f=0,g=e.length;f<g;++f)d=e[f],d.coordsize=this.size.w+" "+this.size.h;this.root.style.flip="y";return c},
+setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);for(var b=[this.rendererRoot,this.root,this.vectorRoot,this.textRoot],c=this.size.w+"px",d=this.size.h+"px",e,f=0,g=b.length;f<g;++f)e=b[f],e.style.width=c,e.style.height=d},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"olv:rect":this.isComplexSymbol(b.graphicName)?"olv:shape":"olv:oval";break;case "OpenLayers.Geometry.Rectangle":c="olv:rect";break;case "OpenLayers.Geometry.LineString":case "OpenLayers.Geometry.LinearRing":case "OpenLayers.Geometry.Polygon":case "OpenLayers.Geometry.Curve":c=
+"olv:shape"}return c},setStyle:function(a,b,c,d){b=b||a._style;c=c||a._options;var e=b.fillColor,f=b.title||b.graphicTitle;f&&(a.title=f);if("OpenLayers.Geometry.Point"===a._geometryClass)if(b.externalGraphic){c.isFilled=!0;var e=b.graphicWidth||b.graphicHeight,f=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,f=f?f:2*b.pointRadius,g=this.getResolution(),h=void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e),k=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*f);a.style.left=((d.x-this.featureDx)/
+g-this.offset.x+h|0)+"px";a.style.top=(d.y/g-this.offset.y-(k+f)|0)+"px";a.style.width=e+"px";a.style.height=f+"px";a.style.flip="y";e="none";c.isStroked=!1}else this.isComplexSymbol(b.graphicName)?(f=this.importSymbol(b.graphicName),a.path=f.path,a.coordorigin=f.left+","+f.bottom,f=f.size,a.coordsize=f+","+f,this.drawCircle(a,d,b.pointRadius),a.style.flip="y"):this.drawCircle(a,d,b.pointRadius);c.isFilled?a.fillcolor=e:a.filled="false";d=a.getElementsByTagName("fill");d=0==d.length?null:d[0];c.isFilled?
+(d||(d=this.createNode("olv:fill",a.id+"_fill")),d.opacity=b.fillOpacity,"OpenLayers.Geometry.Point"===a._geometryClass&&b.externalGraphic&&(b.graphicOpacity&&(d.opacity=b.graphicOpacity),d.src=b.externalGraphic,d.type="frame",b.graphicWidth&&b.graphicHeight||(d.aspect="atmost")),d.parentNode!=a&&a.appendChild(d)):d&&a.removeChild(d);e=b.rotation;if(void 0!==e||void 0!==a._rotation)a._rotation=e,b.externalGraphic?(this.graphicRotate(a,h,k,b),d.opacity=0):"OpenLayers.Geometry.Point"===a._geometryClass&&
+(a.style.rotation=e||0);h=a.getElementsByTagName("stroke");h=0==h.length?null:h[0];c.isStroked?(h||(h=this.createNode("olv:stroke",a.id+"_stroke"),a.appendChild(h)),h.on=!0,h.color=b.strokeColor,h.weight=b.strokeWidth+"px",h.opacity=b.strokeOpacity,h.endcap="butt"==b.strokeLinecap?"flat":b.strokeLinecap||"round",b.strokeDashstyle&&(h.dashstyle=this.dashStyle(b))):(a.stroked=!1,h&&(h.on=!1));"inherit"!=b.cursor&&null!=b.cursor&&(a.style.cursor=b.cursor);return a},graphicRotate:function(a,b,c,d){d=
+d||a._style;var e=d.rotation||0,f,g;if(d.graphicWidth&&d.graphicHeight){g=Math.max(d.graphicWidth,d.graphicHeight);f=d.graphicWidth/d.graphicHeight;var h=Math.round(d.graphicWidth||g*f),k=Math.round(d.graphicHeight||g);a.style.width=h+"px";a.style.height=k+"px";var l=document.getElementById(a.id+"_image");l||(l=this.createNode("olv:imagedata",a.id+"_image"),a.appendChild(l));l.style.width=h+"px";l.style.height=k+"px";l.src=d.externalGraphic;l.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='', sizingMethod='scale')";
+l=e*Math.PI/180;e=Math.sin(l);l=Math.cos(l);e="progid:DXImageTransform.Microsoft.Matrix(M11="+l+",M12="+-e+",M21="+e+",M22="+l+",SizingMethod='auto expand')\n";(l=d.graphicOpacity||d.fillOpacity)&&1!=l&&(e+="progid:DXImageTransform.Microsoft.BasicImage(opacity="+l+")\n");a.style.filter=e;e=new OpenLayers.Geometry.Point(-b,-c);h=(new OpenLayers.Bounds(0,0,h,k)).toGeometry();h.rotate(d.rotation,e);h=h.getBounds();a.style.left=Math.round(parseInt(a.style.left)+h.left)+"px";a.style.top=Math.round(parseInt(a.style.top)-
+h.bottom)+"px"}else{var m=new Image;m.onreadystatechange=OpenLayers.Function.bind(function(){if("complete"==m.readyState||"interactive"==m.readyState)f=m.width/m.height,g=Math.max(2*d.pointRadius,d.graphicWidth||0,d.graphicHeight||0),b*=f,d.graphicWidth=g*f,d.graphicHeight=g,this.graphicRotate(a,b,c,d)},this);m.src=d.externalGraphic}},postDraw:function(a){a.style.visibility="visible";var b=a._style.fillColor,c=a._style.strokeColor;"none"==b&&a.fillcolor!=b&&(a.fillcolor=b);"none"==c&&a.strokecolor!=
+c&&(a.strokecolor=c)},setNodeDimension:function(a,b){var c=b.getBounds();if(c){var d=this.getResolution(),c=new OpenLayers.Bounds((c.left-this.featureDx)/d-this.offset.x|0,c.bottom/d-this.offset.y|0,(c.right-this.featureDx)/d-this.offset.x|0,c.top/d-this.offset.y|0);a.style.left=c.left+"px";a.style.top=c.top+"px";a.style.width=c.getWidth()+"px";a.style.height=c.getHeight()+"px";a.coordorigin=c.left+" "+c.top;a.coordsize=c.getWidth()+" "+c.getHeight()}},dashStyle:function(a){a=a.strokeDashstyle;switch(a){case "solid":case "dot":case "dash":case "dashdot":case "longdash":case "longdashdot":return a;
+default:return a=a.split(/[ ,]/),2==a.length?1*a[0]>=2*a[1]?"longdash":1==a[0]||1==a[1]?"dot":"dash":4==a.length?1*a[0]>=2*a[1]?"longdashdot":"dashdot":"solid"}},createNode:function(a,b){var c=document.createElement(a);b&&(c.id=b);c.unselectable="on";c.onselectstart=OpenLayers.Function.False;return c},nodeTypeCompare:function(a,b){var c=b,d=c.indexOf(":");-1!=d&&(c=c.substr(d+1));var e=a.nodeName,d=e.indexOf(":");-1!=d&&(e=e.substr(d+1));return c==e},createRenderRoot:function(){return this.nodeFactory(this.container.id+
+"_vmlRoot","div")},createRoot:function(a){return this.nodeFactory(this.container.id+a,"olv:group")},drawPoint:function(a,b){return this.drawCircle(a,b,1)},drawCircle:function(a,b,c){if(!isNaN(b.x)&&!isNaN(b.y)){var d=this.getResolution();a.style.left=((b.x-this.featureDx)/d-this.offset.x|0)-c+"px";a.style.top=(b.y/d-this.offset.y|0)-c+"px";b=2*c;a.style.width=b+"px";a.style.height=b+"px";return a}return!1},drawLineString:function(a,b){return this.drawLine(a,b,!1)},drawLinearRing:function(a,b){return this.drawLine(a,
+b,!0)},drawLine:function(a,b,c){this.setNodeDimension(a,b);for(var d=this.getResolution(),e=b.components.length,f=Array(e),g,h,k=0;k<e;k++)g=b.components[k],h=(g.x-this.featureDx)/d-this.offset.x|0,g=g.y/d-this.offset.y|0,f[k]=" "+h+","+g+" l ";b=c?" x e":" e";a.path="m"+f.join("")+b;return a},drawPolygon:function(a,b){this.setNodeDimension(a,b);var c=this.getResolution(),d=[],e,f,g,h,k,l,m,r,p,n;e=0;for(f=b.components.length;e<f;e++){d.push("m");g=b.components[e].components;h=0===e;l=k=null;m=0;
+for(r=g.length;m<r;m++)p=g[m],n=(p.x-this.featureDx)/c-this.offset.x|0,p=p.y/c-this.offset.y|0,n=" "+n+","+p,d.push(n),0==m&&d.push(" l"),h||(k?k!=n&&(l?l!=n&&(h=!0):l=n):k=n);d.push(h?" x ":" ")}d.push("e");a.path=d.join("");return a},drawRectangle:function(a,b){var c=this.getResolution();a.style.left=((b.x-this.featureDx)/c-this.offset.x|0)+"px";a.style.top=(b.y/c-this.offset.y|0)+"px";a.style.width=(b.width/c|0)+"px";a.style.height=(b.height/c|0)+"px";return a},drawText:function(a,b,c){var d=this.nodeFactory(a+
+this.LABEL_ID_SUFFIX,"olv:rect"),e=this.nodeFactory(a+this.LABEL_ID_SUFFIX+"_textbox","olv:textbox"),f=this.getResolution();d.style.left=((c.x-this.featureDx)/f-this.offset.x|0)+"px";d.style.top=(c.y/f-this.offset.y|0)+"px";d.style.flip="y";e.innerText=b.label;"inherit"!=b.cursor&&null!=b.cursor&&(e.style.cursor=b.cursor);b.fontColor&&(e.style.color=b.fontColor);b.fontOpacity&&(e.style.filter="alpha(opacity="+100*b.fontOpacity+")");b.fontFamily&&(e.style.fontFamily=b.fontFamily);b.fontSize&&(e.style.fontSize=
+b.fontSize);b.fontWeight&&(e.style.fontWeight=b.fontWeight);b.fontStyle&&(e.style.fontStyle=b.fontStyle);!0===b.labelSelect&&(d._featureId=a,e._featureId=a,e._geometry=c,e._geometryClass=c.CLASS_NAME);e.style.whiteSpace="nowrap";e.inset="1px,0px,0px,0px";d.parentNode||(d.appendChild(e),this.textRoot.appendChild(d));b=b.labelAlign||"cm";1==b.length&&(b+="m");a=e.clientWidth*OpenLayers.Renderer.VML.LABEL_SHIFT[b.substr(0,1)];e=e.clientHeight*OpenLayers.Renderer.VML.LABEL_SHIFT[b.substr(1,1)];d.style.left=
+parseInt(d.style.left)-a-1+"px";d.style.top=parseInt(d.style.top)+e+"px"},moveRoot:function(a){var b=this.map.getLayer(a.container.id);b instanceof OpenLayers.Layer.Vector.RootContainer&&(b=this.map.getLayer(this.container.id));b&&b.renderer.clear();OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this,arguments);b&&b.redraw()},importSymbol:function(a){var b=this.container.id+"-"+a,c=this.symbolCache[b];if(c)return c;c=OpenLayers.Renderer.symbol[a];if(!c)throw Error(a+" is not a valid symbol name");
+a=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);for(var d=["m"],e=0;e<c.length;e+=2){var f=c[e],g=c[e+1];a.left=Math.min(a.left,f);a.bottom=Math.min(a.bottom,g);a.right=Math.max(a.right,f);a.top=Math.max(a.top,g);d.push(f);d.push(g);0==e&&d.push("l")}d.push("x e");c=d.join(" ");d=(a.getWidth()-a.getHeight())/2;0<d?(a.bottom-=d,a.top+=d):(a.left+=d,a.right-=d);c={path:c,size:a.getWidth(),left:a.left,bottom:a.bottom};return this.symbolCache[b]=c},CLASS_NAME:"OpenLayers.Renderer.VML"});
+OpenLayers.Renderer.VML.LABEL_SHIFT={l:0,c:0.5,r:1,t:0,m:0.5,b:1};OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:!0,defaultFilter:null,initialize:function(a){a=a||{};OpenLayers.Util.extend(this,a);this.options=a},mergeWithDefaultFilter:function(a){return a&&this.defaultFilter?new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.defaultFilter,a]}):a||this.defaultFilter||void 0},destroy:function(){this.format=this.options=null},read:function(a){a=a||{};a.filter=this.mergeWithDefaultFilter(a.filter)},create:function(){},
+update:function(){},"delete":function(){},commit:function(){},abort:function(a){},createCallback:function(a,b,c){return OpenLayers.Function.bind(function(){a.apply(this,[b,c])},this)},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:!0,features:null,data:null,reqFeatures:null,priv:null,error:null,initialize:function(a){OpenLayers.Util.extend(this,a)},success:function(){return 0<this.code},CLASS_NAME:"OpenLayers.Protocol.Response"});
+OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:!1,updateWithPOST:!1,deleteWithPOST:!1,wildcarded:!1,srsInBBOX:!1,initialize:function(a){a=a||{};this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);if(!this.filterToParams&&OpenLayers.Format.QueryStringFilter){var b=new OpenLayers.Format.QueryStringFilter({wildcarded:this.wildcarded,srsInBBOX:this.srsInBBOX});this.filterToParams=
+function(a,d){return b.write(a,d)}}},destroy:function(){this.headers=this.params=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=a||{};a.params=OpenLayers.Util.applyDefaults(a.params,this.options.params);a=OpenLayers.Util.applyDefaults(a,this.options);a.filter&&this.filterToParams&&(a.params=this.filterToParams(a.filter,a.params));var b=void 0!==a.readWithPOST?a.readWithPOST:this.readWithPOST,c=new OpenLayers.Protocol.Response({requestType:"read"});
+b?(b=a.headers||{},b["Content-Type"]="application/x-www-form-urlencoded",c.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,c,a),data:OpenLayers.Util.getParameterString(a.params),headers:b})):c.priv=OpenLayers.Request.GET({url:a.url,callback:this.createCallback(this.handleRead,c,a),params:a.params,headers:a.headers});return c},handleRead:function(a,b){this.handleResponse(a,b)},create:function(a,b){b=OpenLayers.Util.applyDefaults(b,this.options);var c=new OpenLayers.Protocol.Response({reqFeatures:a,
+requestType:"create"});c.priv=OpenLayers.Request.POST({url:b.url,callback:this.createCallback(this.handleCreate,c,b),headers:b.headers,data:this.format.write(a)});return c},handleCreate:function(a,b){this.handleResponse(a,b)},update:function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"update"});d.priv=OpenLayers.Request[this.updateWithPOST?"POST":"PUT"]({url:c,callback:this.createCallback(this.handleUpdate,
+d,b),headers:b.headers,data:this.format.write(a)});return d},handleUpdate:function(a,b){this.handleResponse(a,b)},"delete":function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"delete"}),e=this.deleteWithPOST?"POST":"DELETE",c={url:c,callback:this.createCallback(this.handleDelete,d,b),headers:b.headers};this.deleteWithPOST&&(c.data=this.format.write(a));d.priv=OpenLayers.Request[e](c);
+return d},handleDelete:function(a,b){this.handleResponse(a,b)},handleResponse:function(a,b){var c=a.priv;b.callback&&(200<=c.status&&300>c.status?("delete"!=a.requestType&&(a.features=this.parseFeatures(c)),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE,b.callback.call(b.scope,a))},parseFeatures:function(a){var b=a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},commit:function(a,b){function c(a){for(var b=
+a.features?a.features.length:0,c=Array(b),e=0;e<b;++e)c[e]=a.features[e].fid;q.insertIds=c;d.apply(this,[a])}function d(a){this.callUserCallback(a,b);n=n&&a.success();f++;f>=p&&b.callback&&(q.code=n?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE,b.callback.apply(b.scope,[q]))}b=OpenLayers.Util.applyDefaults(b,this.options);var e=[],f=0,g={};g[OpenLayers.State.INSERT]=[];g[OpenLayers.State.UPDATE]=[];g[OpenLayers.State.DELETE]=[];for(var h,k,l=[],m=0,r=a.length;m<r;++m)if(h=
+a[m],k=g[h.state])k.push(h),l.push(h);var p=(0<g[OpenLayers.State.INSERT].length?1:0)+g[OpenLayers.State.UPDATE].length+g[OpenLayers.State.DELETE].length,n=!0,q=new OpenLayers.Protocol.Response({reqFeatures:l});h=g[OpenLayers.State.INSERT];0<h.length&&e.push(this.create(h,OpenLayers.Util.applyDefaults({callback:c,scope:this},b.create)));h=g[OpenLayers.State.UPDATE];for(m=h.length-1;0<=m;--m)e.push(this.update(h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b.update)));h=g[OpenLayers.State.DELETE];
+for(m=h.length-1;0<=m;--m)e.push(this["delete"](h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b["delete"])));return e},abort:function(a){a&&a.priv.abort()},callUserCallback:function(a,b){var c=b[a.requestType];c&&c.callback&&c.callback.call(c.scope,a)},CLASS_NAME:"OpenLayers.Protocol.HTTP"});OpenLayers.Control.LayerSwitcher=OpenLayers.Class(OpenLayers.Control,{layerStates:null,layersDiv:null,baseLayersDiv:null,baseLayers:null,dataLbl:null,dataLayersDiv:null,dataLayers:null,minimizeDiv:null,maximizeDiv:null,ascending:!0,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.layerStates=[]},destroy:function(){this.clearLayersArray("base");this.clearLayersArray("data");this.map.events.un({buttonclick:this.onButtonClick,addlayer:this.redraw,changelayer:this.redraw,
+removelayer:this.redraw,changebaselayer:this.redraw,scope:this});this.events.unregister("buttonclick",this,this.onButtonClick);OpenLayers.Control.prototype.destroy.apply(this,arguments)},setMap:function(a){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.on({addlayer:this.redraw,changelayer:this.redraw,removelayer:this.redraw,changebaselayer:this.redraw,scope:this});this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",this,this.onButtonClick)):
+this.map.events.register("buttonclick",this,this.onButtonClick)},draw:function(){OpenLayers.Control.prototype.draw.apply(this);this.loadContents();this.outsideViewport||this.minimizeControl();this.redraw();return this.div},onButtonClick:function(a){a=a.buttonElement;a===this.minimizeDiv?this.minimizeControl():a===this.maximizeDiv?this.maximizeControl():a._layerSwitcher===this.id&&(a["for"]&&(a=document.getElementById(a["for"])),a.disabled||("radio"==a.type?(a.checked=!0,this.map.setBaseLayer(this.map.getLayer(a._layer))):
+(a.checked=!a.checked,this.updateMap())))},clearLayersArray:function(a){this[a+"LayersDiv"].innerHTML="";this[a+"Layers"]=[]},checkRedraw:function(){if(!this.layerStates.length||this.map.layers.length!=this.layerStates.length)return!0;for(var a=0,b=this.layerStates.length;a<b;a++){var c=this.layerStates[a],d=this.map.layers[a];if(c.name!=d.name||c.inRange!=d.inRange||c.id!=d.id||c.visibility!=d.visibility)return!0}return!1},redraw:function(){if(!this.checkRedraw())return this.div;this.clearLayersArray("base");
+this.clearLayersArray("data");var a=!1,b=!1,c=this.map.layers.length;this.layerStates=Array(c);for(var d=0;d<c;d++){var e=this.map.layers[d];this.layerStates[d]={name:e.name,visibility:e.visibility,inRange:e.inRange,id:e.id}}var f=this.map.layers.slice();this.ascending||f.reverse();d=0;for(c=f.length;d<c;d++){var e=f[d],g=e.isBaseLayer;if(e.displayInLayerSwitcher){g?b=!0:a=!0;var h=g?e==this.map.baseLayer:e.getVisibility(),k=document.createElement("input"),l=OpenLayers.Util.createUniqueID(this.id+
+"_input_");k.id=l;k.name=g?this.id+"_baseLayers":e.name;k.type=g?"radio":"checkbox";k.value=e.name;k.checked=h;k.defaultChecked=h;k.className="olButton";k._layer=e.id;k._layerSwitcher=this.id;g||e.inRange||(k.disabled=!0);h=document.createElement("label");h["for"]=k.id;OpenLayers.Element.addClass(h,"labelSpan olButton");h._layer=e.id;h._layerSwitcher=this.id;g||e.inRange||(h.style.color="gray");h.innerHTML=e.name;h.style.verticalAlign=g?"bottom":"baseline";l=document.createElement("br");(g?this.baseLayers:
+this.dataLayers).push({layer:e,inputElem:k,labelSpan:h});e=g?this.baseLayersDiv:this.dataLayersDiv;e.appendChild(k);e.appendChild(h);e.appendChild(l)}}this.dataLbl.style.display=a?"":"none";this.baseLbl.style.display=b?"":"none";return this.div},updateMap:function(){for(var a=0,b=this.baseLayers.length;a<b;a++){var c=this.baseLayers[a];c.inputElem.checked&&this.map.setBaseLayer(c.layer,!1)}a=0;for(b=this.dataLayers.length;a<b;a++)c=this.dataLayers[a],c.layer.setVisibility(c.inputElem.checked)},maximizeControl:function(a){this.div.style.width=
+"";this.div.style.height="";this.showControls(!1);null!=a&&OpenLayers.Event.stop(a)},minimizeControl:function(a){this.div.style.width="0px";this.div.style.height="0px";this.showControls(!0);null!=a&&OpenLayers.Event.stop(a)},showControls:function(a){this.maximizeDiv.style.display=a?"":"none";this.minimizeDiv.style.display=a?"none":"";this.layersDiv.style.display=a?"none":""},loadContents:function(){this.layersDiv=document.createElement("div");this.layersDiv.id=this.id+"_layersDiv";OpenLayers.Element.addClass(this.layersDiv,
+"layersDiv");this.baseLbl=document.createElement("div");this.baseLbl.innerHTML=OpenLayers.i18n("Base Layer");OpenLayers.Element.addClass(this.baseLbl,"baseLbl");this.baseLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.baseLayersDiv,"baseLayersDiv");this.dataLbl=document.createElement("div");this.dataLbl.innerHTML=OpenLayers.i18n("Overlays");OpenLayers.Element.addClass(this.dataLbl,"dataLbl");this.dataLayersDiv=document.createElement("div");OpenLayers.Element.addClass(this.dataLayersDiv,
+"dataLayersDiv");this.ascending?(this.layersDiv.appendChild(this.baseLbl),this.layersDiv.appendChild(this.baseLayersDiv),this.layersDiv.appendChild(this.dataLbl),this.layersDiv.appendChild(this.dataLayersDiv)):(this.layersDiv.appendChild(this.dataLbl),this.layersDiv.appendChild(this.dataLayersDiv),this.layersDiv.appendChild(this.baseLbl),this.layersDiv.appendChild(this.baseLayersDiv));this.div.appendChild(this.layersDiv);var a=OpenLayers.Util.getImageLocation("layer-switcher-maximize.png");this.maximizeDiv=
+OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MaximizeDiv",null,null,a,"absolute");OpenLayers.Element.addClass(this.maximizeDiv,"maximizeDiv olButton");this.maximizeDiv.style.display="none";this.div.appendChild(this.maximizeDiv);a=OpenLayers.Util.getImageLocation("layer-switcher-minimize.png");this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MinimizeDiv",null,null,a,"absolute");OpenLayers.Element.addClass(this.minimizeDiv,"minimizeDiv olButton");this.minimizeDiv.style.display=
+"none";this.div.appendChild(this.minimizeDiv)},CLASS_NAME:"OpenLayers.Control.LayerSwitcher"});
diff --git a/misc/openlayers/OpenLayers.mobile.debug.js b/misc/openlayers/OpenLayers.mobile.debug.js
new file mode 100644
index 0000000..5cb37cd
--- /dev/null
+++ b/misc/openlayers/OpenLayers.mobile.debug.js
@@ -0,0 +1,41831 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* ======================================================================
+ OpenLayers/SingleFile.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+var OpenLayers = {
+ /**
+ * Constant: VERSION_NUMBER
+ */
+ VERSION_NUMBER: "Release 2.13.1",
+
+ /**
+ * Constant: singleFile
+ * TODO: remove this in 3.0 when we stop supporting build profiles that
+ * include OpenLayers.js
+ */
+ singleFile: true,
+
+ /**
+ * Method: _getScriptLocation
+ * Return the path to this script. This is also implemented in
+ * OpenLayers.js
+ *
+ * Returns:
+ * {String} Path to this script
+ */
+ _getScriptLocation: (function() {
+ var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
+ s = document.getElementsByTagName('script'),
+ src, m, l = "";
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+ return (function() { return l; });
+ })(),
+
+ /**
+ * Property: ImgPath
+ * {String} Set this to the path where control images are stored, a path
+ * given here must end with a slash. If set to '' (which is the default)
+ * OpenLayers will use its script location + "img/".
+ *
+ * You will need to set this property when you have a singlefile build of
+ * OpenLayers that either is not named "OpenLayers.js" or if you move
+ * the file in a way such that the image directory cannot be derived from
+ * the script location.
+ *
+ * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+ * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+ * way of including OpenLayers in your HTML would be:
+ *
+ * (code)
+ * <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+ * <script type="text/javascript">
+ * // tell OpenLayers where the control images are
+ * // remember the trailing slash
+ * OpenLayers.ImgPath = "/resources/external/images/ol/";
+ * </script>
+ * (end code)
+ *
+ * Please remember that when your OpenLayers script is not named
+ * "OpenLayers.js" you will have to make sure that the default theme is
+ * loaded into the page by including an appropriate <link>-tag,
+ * e.g.:
+ *
+ * (code)
+ * <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
+ * (end code)
+ */
+ ImgPath : ''
+};
+/* ======================================================================
+ OpenLayers/BaseTypes.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Header: OpenLayers Base Types
+ * OpenLayers custom string, number and function functions are described here.
+ */
+
+/**
+ * Namespace: OpenLayers.String
+ * Contains convenience functions for string manipulation.
+ */
+OpenLayers.String = {
+
+ /**
+ * APIFunction: startsWith
+ * Test whether a string starts with another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string starts with the second.
+ */
+ startsWith: function(str, sub) {
+ return (str.indexOf(sub) == 0);
+ },
+
+ /**
+ * APIFunction: contains
+ * Test whether a string contains another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string contains the second.
+ */
+ contains: function(str, sub) {
+ return (str.indexOf(sub) != -1);
+ },
+
+ /**
+ * APIFunction: trim
+ * Removes leading and trailing whitespace characters from a string.
+ *
+ * Parameters:
+ * str - {String} The (potentially) space padded string. This string is not
+ * modified.
+ *
+ * Returns:
+ * {String} A trimmed version of the string with all leading and
+ * trailing spaces removed.
+ */
+ trim: function(str) {
+ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ },
+
+ /**
+ * APIFunction: camelize
+ * Camel-case a hyphenated string.
+ * Ex. "chicken-head" becomes "chickenHead", and
+ * "-chicken-head" becomes "ChickenHead".
+ *
+ * Parameters:
+ * str - {String} The string to be camelized. The original is not modified.
+ *
+ * Returns:
+ * {String} The string, camelized
+ */
+ camelize: function(str) {
+ var oStringList = str.split('-');
+ var camelizedString = oStringList[0];
+ for (var i=1, len=oStringList.length; i<len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+ return camelizedString;
+ },
+
+ /**
+ * APIFunction: format
+ * Given a string with tokens in the form ${token}, return a string
+ * with tokens replaced with properties from the given context
+ * object. Represent a literal "${" by doubling it, e.g. "${${".
+ *
+ * Parameters:
+ * template - {String} A string with tokens to be replaced. A template
+ * has the form "literal ${token}" where the token will be replaced
+ * by the value of context["token"].
+ * context - {Object} An optional object with properties corresponding
+ * to the tokens in the format string. If no context is sent, the
+ * window object will be used.
+ * args - {Array} Optional arguments to pass to any functions found in
+ * the context. If a context property is a function, the token
+ * will be replaced by the return from the function called with
+ * these arguments.
+ *
+ * Returns:
+ * {String} A string with tokens replaced from the context object.
+ */
+ format: function(template, context, args) {
+ if(!context) {
+ context = window;
+ }
+
+ // Example matching:
+ // str = ${foo.bar}
+ // match = foo.bar
+ var replacer = function(str, match) {
+ var replacement;
+
+ // Loop through all subs. Example: ${a.b.c}
+ // 0 -> replacement = context[a];
+ // 1 -> replacement = context[a][b];
+ // 2 -> replacement = context[a][b][c];
+ var subs = match.split(/\.+/);
+ for (var i=0; i< subs.length; i++) {
+ if (i == 0) {
+ replacement = context;
+ }
+ if (replacement === undefined) {
+ break;
+ }
+ replacement = replacement[subs[i]];
+ }
+
+ if(typeof replacement == "function") {
+ replacement = args ?
+ replacement.apply(null, args) :
+ replacement();
+ }
+
+ // If replacement is undefined, return the string 'undefined'.
+ // This is a workaround for a bugs in browsers not properly
+ // dealing with non-participating groups in regular expressions:
+ // http://blog.stevenlevithan.com/archives/npcg-javascript
+ if (typeof replacement == 'undefined') {
+ return 'undefined';
+ } else {
+ return replacement;
+ }
+ };
+
+ return template.replace(OpenLayers.String.tokenRegEx, replacer);
+ },
+
+ /**
+ * Property: tokenRegEx
+ * Used to find tokens in a string.
+ * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
+ */
+ tokenRegEx: /\$\{([\w.]+?)\}/g,
+
+ /**
+ * Property: numberRegEx
+ * Used to test strings as numbers.
+ */
+ numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+
+ /**
+ * APIFunction: isNumeric
+ * Determine whether a string contains only a numeric value.
+ *
+ * Examples:
+ * (code)
+ * OpenLayers.String.isNumeric("6.02e23") // true
+ * OpenLayers.String.isNumeric("12 dozen") // false
+ * OpenLayers.String.isNumeric("4") // true
+ * OpenLayers.String.isNumeric(" 4 ") // false
+ * (end)
+ *
+ * Returns:
+ * {Boolean} String contains only a number.
+ */
+ isNumeric: function(value) {
+ return OpenLayers.String.numberRegEx.test(value);
+ },
+
+ /**
+ * APIFunction: numericIf
+ * Converts a string that appears to be a numeric value into a number.
+ *
+ * Parameters:
+ * value - {String}
+ * trimWhitespace - {Boolean}
+ *
+ * Returns:
+ * {Number|String} a Number if the passed value is a number, a String
+ * otherwise.
+ */
+ numericIf: function(value, trimWhitespace) {
+ var originalValue = value;
+ if (trimWhitespace === true && value != null && value.replace) {
+ value = value.replace(/^\s*|\s*$/g, "");
+ }
+ return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
+ }
+
+};
+
+/**
+ * Namespace: OpenLayers.Number
+ * Contains convenience functions for manipulating numbers.
+ */
+OpenLayers.Number = {
+
+ /**
+ * Property: decimalSeparator
+ * Decimal separator to use when formatting numbers.
+ */
+ decimalSeparator: ".",
+
+ /**
+ * Property: thousandsSeparator
+ * Thousands separator to use when formatting numbers.
+ */
+ thousandsSeparator: ",",
+
+ /**
+ * APIFunction: limitSigDigs
+ * Limit the number of significant digits on a float.
+ *
+ * Parameters:
+ * num - {Float}
+ * sig - {Integer}
+ *
+ * Returns:
+ * {Float} The number, rounded to the specified number of significant
+ * digits.
+ */
+ limitSigDigs: function(num, sig) {
+ var fig = 0;
+ if (sig > 0) {
+ fig = parseFloat(num.toPrecision(sig));
+ }
+ return fig;
+ },
+
+ /**
+ * APIFunction: format
+ * Formats a number for output.
+ *
+ * Parameters:
+ * num - {Float}
+ * dec - {Integer} Number of decimal places to round to.
+ * Defaults to 0. Set to null to leave decimal places unchanged.
+ * tsep - {String} Thousands separator.
+ * Default is ",".
+ * dsep - {String} Decimal separator.
+ * Default is ".".
+ *
+ * Returns:
+ * {String} A string representing the formatted number.
+ */
+ format: function(num, dec, tsep, dsep) {
+ dec = (typeof dec != "undefined") ? dec : 0;
+ tsep = (typeof tsep != "undefined") ? tsep :
+ OpenLayers.Number.thousandsSeparator;
+ dsep = (typeof dsep != "undefined") ? dsep :
+ OpenLayers.Number.decimalSeparator;
+
+ if (dec != null) {
+ num = parseFloat(num.toFixed(dec));
+ }
+
+ var parts = num.toString().split(".");
+ if (parts.length == 1 && dec == null) {
+ // integer where we do not want to touch the decimals
+ dec = 0;
+ }
+
+ var integer = parts[0];
+ if (tsep) {
+ var thousands = /(-?[0-9]+)([0-9]{3})/;
+ while(thousands.test(integer)) {
+ integer = integer.replace(thousands, "$1" + tsep + "$2");
+ }
+ }
+
+ var str;
+ if (dec == 0) {
+ str = integer;
+ } else {
+ var rem = parts.length > 1 ? parts[1] : "0";
+ if (dec != null) {
+ rem = rem + new Array(dec - rem.length + 1).join("0");
+ }
+ str = integer + dsep + rem;
+ }
+ return str;
+ },
+
+ /**
+ * Method: zeroPad
+ * Create a zero padded string optionally with a radix for casting numbers.
+ *
+ * Parameters:
+ * num - {Number} The number to be zero padded.
+ * len - {Number} The length of the string to be returned.
+ * radix - {Number} An integer between 2 and 36 specifying the base to use
+ * for representing numeric values.
+ */
+ zeroPad: function(num, len, radix) {
+ var str = num.toString(radix || 10);
+ while (str.length < len) {
+ str = "0" + str;
+ }
+ return str;
+ }
+};
+
+/**
+ * Namespace: OpenLayers.Function
+ * Contains convenience functions for function manipulation.
+ */
+OpenLayers.Function = {
+ /**
+ * APIFunction: bind
+ * Bind a function to an object. Method to easily create closures with
+ * 'this' altered.
+ *
+ * Parameters:
+ * func - {Function} Input function.
+ * object - {Object} The object to bind to the input function (as this).
+ *
+ * Returns:
+ * {Function} A closure with 'this' set to the passed in object.
+ */
+ bind: function(func, object) {
+ // create a reference to all arguments past the second one
+ var args = Array.prototype.slice.apply(arguments, [2]);
+ return function() {
+ // Push on any additional arguments from the actual function call.
+ // These will come after those sent to the bind call.
+ var newArgs = args.concat(
+ Array.prototype.slice.apply(arguments, [0])
+ );
+ return func.apply(object, newArgs);
+ };
+ },
+
+ /**
+ * APIFunction: bindAsEventListener
+ * Bind a function to an object, and configure it to receive the event
+ * object as first parameter when called.
+ *
+ * Parameters:
+ * func - {Function} Input function to serve as an event listener.
+ * object - {Object} A reference to this.
+ *
+ * Returns:
+ * {Function}
+ */
+ bindAsEventListener: function(func, object) {
+ return function(event) {
+ return func.call(object, event || window.event);
+ };
+ },
+
+ /**
+ * APIFunction: False
+ * A simple function to that just does "return false". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.False;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ False : function() {
+ return false;
+ },
+
+ /**
+ * APIFunction: True
+ * A simple function to that just does "return true". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.True;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ True : function() {
+ return true;
+ },
+
+ /**
+ * APIFunction: Void
+ * A reusable function that returns ``undefined``.
+ *
+ * Returns:
+ * {undefined}
+ */
+ Void: function() {}
+
+};
+
+/**
+ * Namespace: OpenLayers.Array
+ * Contains convenience functions for array manipulation.
+ */
+OpenLayers.Array = {
+
+ /**
+ * APIMethod: filter
+ * Filter an array. Provides the functionality of the
+ * Array.prototype.filter extension to the ECMA-262 standard. Where
+ * available, Array.prototype.filter will be used.
+ *
+ * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
+ *
+ * Parameters:
+ * array - {Array} The array to be filtered. This array is not mutated.
+ * Elements added to this array by the callback will not be visited.
+ * callback - {Function} A function that is called for each element in
+ * the array. If this function returns true, the element will be
+ * included in the return. The function will be called with three
+ * arguments: the element in the array, the index of that element, and
+ * the array itself. If the optional caller parameter is specified
+ * the callback will be called with this set to caller.
+ * caller - {Object} Optional object to be set as this when the callback
+ * is called.
+ *
+ * Returns:
+ * {Array} An array of elements from the passed in array for which the
+ * callback returns true.
+ */
+ filter: function(array, callback, caller) {
+ var selected = [];
+ if (Array.prototype.filter) {
+ selected = array.filter(callback, caller);
+ } else {
+ var len = array.length;
+ if (typeof callback != "function") {
+ throw new TypeError();
+ }
+ for(var i=0; i<len; i++) {
+ if (i in array) {
+ var val = array[i];
+ if (callback.call(caller, val, i, array)) {
+ selected.push(val);
+ }
+ }
+ }
+ }
+ return selected;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Class.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Constructor: OpenLayers.Class
+ * Base class used to construct all other classes. Includes support for
+ * multiple inheritance.
+ *
+ * This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old
+ * syntax for creating classes and dealing with inheritance
+ * will be removed.
+ *
+ * To create a new OpenLayers-style class, use the following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(prototype);
+ * (end)
+ *
+ * To create a new OpenLayers-style class with multiple inheritance, use the
+ * following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(Class1, Class2, prototype);
+ * (end)
+ *
+ * Note that instanceof reflection will only reveal Class1 as superclass.
+ *
+ */
+OpenLayers.Class = function() {
+ var len = arguments.length;
+ var P = arguments[0];
+ var F = arguments[len-1];
+
+ var C = typeof F.initialize == "function" ?
+ F.initialize :
+ function(){ P.prototype.initialize.apply(this, arguments); };
+
+ if (len > 1) {
+ var newArgs = [C, P].concat(
+ Array.prototype.slice.call(arguments).slice(1, len-1), F);
+ OpenLayers.inherit.apply(null, newArgs);
+ } else {
+ C.prototype = F;
+ }
+ return C;
+};
+
+/**
+ * Function: OpenLayers.inherit
+ *
+ * Parameters:
+ * C - {Object} the class that inherits
+ * P - {Object} the superclass to inherit from
+ *
+ * In addition to the mandatory C and P parameters, an arbitrary number of
+ * objects can be passed, which will extend C.
+ */
+OpenLayers.inherit = function(C, P) {
+ var F = function() {};
+ F.prototype = P.prototype;
+ C.prototype = new F;
+ var i, l, o;
+ for(i=2, l=arguments.length; i<l; i++) {
+ o = arguments[i];
+ if(typeof o === "function") {
+ o = o.prototype;
+ }
+ OpenLayers.Util.extend(C.prototype, o);
+ }
+};
+
+/**
+ * APIFunction: extend
+ * Copy all properties of a source object to a destination object. Modifies
+ * the passed in destination object. Any properties on the source object
+ * that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+OpenLayers.Util.extend = function(destination, source) {
+ destination = destination || {};
+ if (source) {
+ for (var property in source) {
+ var value = source[property];
+ if (value !== undefined) {
+ destination[property] = value;
+ }
+ }
+
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object
+ * is an instance of window.Event.
+ */
+
+ var sourceIsEvt = typeof window.Event == "function"
+ && source instanceof window.Event;
+
+ if (!sourceIsEvt
+ && source.hasOwnProperty && source.hasOwnProperty("toString")) {
+ destination.toString = source.toString;
+ }
+ }
+ return destination;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Bounds.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Bounds
+ * Instances of this class represent bounding boxes. Data stored as left,
+ * bottom, right, top floats. All values are initialized to null, however,
+ * you should make sure you set them before using the bounds for anything.
+ *
+ * Possible use case:
+ * (code)
+ * bounds = new OpenLayers.Bounds();
+ * bounds.extend(new OpenLayers.LonLat(4,5));
+ * bounds.extend(new OpenLayers.LonLat(5,6));
+ * bounds.toBBOX(); // returns 4,5,5,6
+ * (end)
+ */
+OpenLayers.Bounds = OpenLayers.Class({
+
+ /**
+ * Property: left
+ * {Number} Minimum horizontal coordinate.
+ */
+ left: null,
+
+ /**
+ * Property: bottom
+ * {Number} Minimum vertical coordinate.
+ */
+ bottom: null,
+
+ /**
+ * Property: right
+ * {Number} Maximum horizontal coordinate.
+ */
+ right: null,
+
+ /**
+ * Property: top
+ * {Number} Maximum vertical coordinate.
+ */
+ top: null,
+
+ /**
+ * Property: centerLonLat
+ * {<OpenLayers.LonLat>} A cached center location. This should not be
+ * accessed directly. Use <getCenterLonLat> instead.
+ */
+ centerLonLat: null,
+
+ /**
+ * Constructor: OpenLayers.Bounds
+ * Construct a new bounds object. Coordinates can either be passed as four
+ * arguments, or as a single argument.
+ *
+ * Parameters (four arguments):
+ * left - {Number} The left bounds of the box. Note that for width
+ * calculations, this is assumed to be less than the right value.
+ * bottom - {Number} The bottom bounds of the box. Note that for height
+ * calculations, this is assumed to be less than the top value.
+ * right - {Number} The right bounds.
+ * top - {Number} The top bounds.
+ *
+ * Parameters (single argument):
+ * bounds - {Array(Number)} [left, bottom, right, top]
+ */
+ initialize: function(left, bottom, right, top) {
+ if (OpenLayers.Util.isArray(left)) {
+ top = left[3];
+ right = left[2];
+ bottom = left[1];
+ left = left[0];
+ }
+ if (left != null) {
+ this.left = OpenLayers.Util.toFloat(left);
+ }
+ if (bottom != null) {
+ this.bottom = OpenLayers.Util.toFloat(bottom);
+ }
+ if (right != null) {
+ this.right = OpenLayers.Util.toFloat(right);
+ }
+ if (top != null) {
+ this.top = OpenLayers.Util.toFloat(top);
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a cloned instance of this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A fresh copy of the bounds
+ */
+ clone:function() {
+ return new OpenLayers.Bounds(this.left, this.bottom,
+ this.right, this.top);
+ },
+
+ /**
+ * Method: equals
+ * Test a two bounds for equivalence.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object has the same left,
+ * right, top, bottom components as this. Note that if bounds
+ * passed in is null, returns false.
+ */
+ equals:function(bounds) {
+ var equals = false;
+ if (bounds != null) {
+ equals = ((this.left == bounds.left) &&
+ (this.right == bounds.right) &&
+ (this.top == bounds.top) &&
+ (this.bottom == bounds.bottom));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: toString
+ * Returns a string representation of the bounds object.
+ *
+ * Returns:
+ * {String} String representation of bounds object.
+ */
+ toString:function() {
+ return [this.left, this.bottom, this.right, this.top].join(",");
+ },
+
+ /**
+ * APIMethod: toArray
+ * Returns an array representation of the bounds object.
+ *
+ * Returns an array of left, bottom, right, top properties, or -- when the
+ * optional parameter is true -- an array of the bottom, left, top,
+ * right properties.
+ *
+ * Parameters:
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {Array} array of left, bottom, right, top
+ */
+ toArray: function(reverseAxisOrder) {
+ if (reverseAxisOrder === true) {
+ return [this.bottom, this.left, this.top, this.right];
+ } else {
+ return [this.left, this.bottom, this.right, this.top];
+ }
+ },
+
+ /**
+ * APIMethod: toBBOX
+ * Returns a boundingbox-string representation of the bounds object.
+ *
+ * Parameters:
+ * decimal - {Integer} How many significant digits in the bbox coords?
+ * Default is 6
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {String} Simple String representation of bounds object.
+ * (e.g. "5,42,10,45")
+ */
+ toBBOX:function(decimal, reverseAxisOrder) {
+ if (decimal== null) {
+ decimal = 6;
+ }
+ var mult = Math.pow(10, decimal);
+ var xmin = Math.round(this.left * mult) / mult;
+ var ymin = Math.round(this.bottom * mult) / mult;
+ var xmax = Math.round(this.right * mult) / mult;
+ var ymax = Math.round(this.top * mult) / mult;
+ if (reverseAxisOrder === true) {
+ return ymin + "," + xmin + "," + ymax + "," + xmax;
+ } else {
+ return xmin + "," + ymin + "," + xmax + "," + ymax;
+ }
+ },
+
+ /**
+ * APIMethod: toGeometry
+ * Create a new polygon geometry based on this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
+ * of this bounds.
+ */
+ toGeometry: function() {
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(this.left, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.top),
+ new OpenLayers.Geometry.Point(this.left, this.top)
+ ])
+ ]);
+ },
+
+ /**
+ * APIMethod: getWidth
+ * Returns the width of the bounds.
+ *
+ * Returns:
+ * {Float} The width of the bounds (right minus left).
+ */
+ getWidth:function() {
+ return (this.right - this.left);
+ },
+
+ /**
+ * APIMethod: getHeight
+ * Returns the height of the bounds.
+ *
+ * Returns:
+ * {Float} The height of the bounds (top minus bottom).
+ */
+ getHeight:function() {
+ return (this.top - this.bottom);
+ },
+
+ /**
+ * APIMethod: getSize
+ * Returns an <OpenLayers.Size> object of the bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size of the bounds.
+ */
+ getSize:function() {
+ return new OpenLayers.Size(this.getWidth(), this.getHeight());
+ },
+
+ /**
+ * APIMethod: getCenterPixel
+ * Returns the <OpenLayers.Pixel> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
+ */
+ getCenterPixel:function() {
+ return new OpenLayers.Pixel( (this.left + this.right) / 2,
+ (this.bottom + this.top) / 2);
+ },
+
+ /**
+ * APIMethod: getCenterLonLat
+ * Returns the <OpenLayers.LonLat> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The center of the bounds in map space.
+ */
+ getCenterLonLat:function() {
+ if(!this.centerLonLat) {
+ this.centerLonLat = new OpenLayers.LonLat(
+ (this.left + this.right) / 2, (this.bottom + this.top) / 2
+ );
+ }
+ return this.centerLonLat;
+ },
+
+ /**
+ * APIMethod: scale
+ * Scales the bounds around a pixel or lonlat. Note that the new
+ * bounds may return non-integer properties, even if a pixel
+ * is passed.
+ *
+ * Parameters:
+ * ratio - {Float}
+ * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+ * Default is center.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
+ * from origin.
+ */
+ scale: function(ratio, origin){
+ if(origin == null){
+ origin = this.getCenterLonLat();
+ }
+
+ var origx,origy;
+
+ // get origin coordinates
+ if(origin.CLASS_NAME == "OpenLayers.LonLat"){
+ origx = origin.lon;
+ origy = origin.lat;
+ } else {
+ origx = origin.x;
+ origy = origin.y;
+ }
+
+ var left = (this.left - origx) * ratio + origx;
+ var bottom = (this.bottom - origy) * ratio + origy;
+ var right = (this.right - origx) * ratio + origx;
+ var top = (this.top - origy) * ratio + origy;
+
+ return new OpenLayers.Bounds(left, bottom, right, top);
+ },
+
+ /**
+ * APIMethod: add
+ * Shifts the coordinates of the bound by the given horizontal and vertical
+ * deltas.
+ *
+ * (start code)
+ * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+ * bounds.toString();
+ * // => "0,0,10,10"
+ *
+ * bounds.add(-1.5, 4).toString();
+ * // => "-1.5,4,8.5,14"
+ * (end)
+ *
+ * This method will throw a TypeError if it is passed null as an argument.
+ *
+ * Parameters:
+ * x - {Float} horizontal delta
+ * y - {Float} vertical delta
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
+ * this, but shifted by the passed-in x and y values.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Bounds.add cannot receive null values');
+ }
+ return new OpenLayers.Bounds(this.left + x, this.bottom + y,
+ this.right + x, this.top + y);
+ },
+
+ /**
+ * APIMethod: extend
+ * Extend the bounds to include the <OpenLayers.LonLat>,
+ * <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
+ *
+ * Please note that this function assumes that left < right and
+ * bottom < top.
+ *
+ * Parameters:
+ * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
+ * <OpenLayers.Bounds>} The object to be included in the new bounds
+ * object.
+ */
+ extend:function(object) {
+ if (object) {
+ switch(object.CLASS_NAME) {
+ case "OpenLayers.LonLat":
+ this.extendXY(object.lon, object.lat);
+ break;
+ case "OpenLayers.Geometry.Point":
+ this.extendXY(object.x, object.y);
+ break;
+
+ case "OpenLayers.Bounds":
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ( (this.left == null) || (object.left < this.left)) {
+ this.left = object.left;
+ }
+ if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
+ this.bottom = object.bottom;
+ }
+ if ( (this.right == null) || (object.right > this.right) ) {
+ this.right = object.right;
+ }
+ if ( (this.top == null) || (object.top > this.top) ) {
+ this.top = object.top;
+ }
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: extendXY
+ * Extend the bounds to include the XY coordinate specified.
+ *
+ * Parameters:
+ * x - {number} The X part of the the coordinate.
+ * y - {number} The Y part of the the coordinate.
+ */
+ extendXY:function(x, y) {
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ((this.left == null) || (x < this.left)) {
+ this.left = x;
+ }
+ if ((this.bottom == null) || (y < this.bottom)) {
+ this.bottom = y;
+ }
+ if ((this.right == null) || (x > this.right)) {
+ this.right = x;
+ }
+ if ((this.top == null) || (y > this.top)) {
+ this.top = y;
+ }
+ },
+
+ /**
+ * APIMethod: containsLonLat
+ * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * options - {Object} Optional parameters
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Whether or not to include the border.
+ * Default is true.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
+ * ll will be considered as contained if it exceeds the world bounds,
+ * but can be wrapped around the dateline so it is contained by this
+ * bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in lonlat is within this bounds.
+ */
+ containsLonLat: function(ll, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ var contains = this.contains(ll.lon, ll.lat, options.inclusive),
+ worldBounds = options.worldBounds;
+ if (worldBounds && !contains) {
+ var worldWidth = worldBounds.getWidth();
+ var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
+ var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
+ contains = this.containsLonLat({
+ lon: ll.lon - worldsAway * worldWidth,
+ lat: ll.lat
+ }, {inclusive: options.inclusive});
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: containsPixel
+ * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in pixel is within this bounds.
+ */
+ containsPixel:function(px, inclusive) {
+ return this.contains(px.x, px.y, inclusive);
+ },
+
+ /**
+ * APIMethod: contains
+ * Returns whether the bounds object contains the given x and y.
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the passed-in coordinates are within this
+ * bounds.
+ */
+ contains:function(x, y, inclusive) {
+ //set default
+ if (inclusive == null) {
+ inclusive = true;
+ }
+
+ if (x == null || y == null) {
+ return false;
+ }
+
+ x = OpenLayers.Util.toFloat(x);
+ y = OpenLayers.Util.toFloat(y);
+
+ var contains = false;
+ if (inclusive) {
+ contains = ((x >= this.left) && (x <= this.right) &&
+ (y >= this.bottom) && (y <= this.top));
+ } else {
+ contains = ((x > this.left) && (x < this.right) &&
+ (y > this.bottom) && (y < this.top));
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: intersectsBounds
+ * Determine whether the target bounds intersects this bounds. Bounds are
+ * considered intersecting if any of their edges intersect or if one
+ * bounds contains the other.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * options - {Object} Optional parameters.
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Treat coincident borders as intersecting. Default
+ * is true. If false, bounds that do not overlap but only touch at the
+ * border will not be considered as intersecting.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
+ * bounds will be considered as intersecting if they intersect when
+ * shifted to within the world bounds. This applies only to bounds that
+ * cross or are completely outside the world bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object intersects this bounds.
+ */
+ intersectsBounds:function(bounds, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ if (options.worldBounds) {
+ var self = this.wrapDateLine(options.worldBounds);
+ bounds = bounds.wrapDateLine(options.worldBounds);
+ } else {
+ self = this;
+ }
+ if (options.inclusive == null) {
+ options.inclusive = true;
+ }
+ var intersects = false;
+ var mightTouch = (
+ self.left == bounds.right ||
+ self.right == bounds.left ||
+ self.top == bounds.bottom ||
+ self.bottom == bounds.top
+ );
+
+ // if the two bounds only touch at an edge, and inclusive is false,
+ // then the bounds don't *really* intersect.
+ if (options.inclusive || !mightTouch) {
+ // otherwise, if one of the boundaries even partially contains another,
+ // inclusive of the edges, then they do intersect.
+ var inBottom = (
+ ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
+ ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
+ );
+ var inTop = (
+ ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
+ ((self.top > bounds.bottom) && (self.top < bounds.top))
+ );
+ var inLeft = (
+ ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
+ ((self.left >= bounds.left) && (self.left <= bounds.right))
+ );
+ var inRight = (
+ ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
+ ((self.right >= bounds.left) && (self.right <= bounds.right))
+ );
+ intersects = ((inBottom || inTop) && (inLeft || inRight));
+ }
+ // document me
+ if (options.worldBounds && !intersects) {
+ var world = options.worldBounds;
+ var width = world.getWidth();
+ var selfCrosses = !world.containsBounds(self);
+ var boundsCrosses = !world.containsBounds(bounds);
+ if (selfCrosses && !boundsCrosses) {
+ bounds = bounds.add(-width, 0);
+ intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
+ } else if (boundsCrosses && !selfCrosses) {
+ self = self.add(-width, 0);
+ intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});
+ }
+ }
+ return intersects;
+ },
+
+ /**
+ * APIMethod: containsBounds
+ * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
+ *
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * partial - {Boolean} If any of the target corners is within this bounds
+ * consider the bounds contained. Default is false. If false, the
+ * entire target bounds must be contained within this bounds.
+ * inclusive - {Boolean} Treat shared edges as contained. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object is contained within this bounds.
+ */
+ containsBounds:function(bounds, partial, inclusive) {
+ if (partial == null) {
+ partial = false;
+ }
+ if (inclusive == null) {
+ inclusive = true;
+ }
+ var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
+ var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
+ var topLeft = this.contains(bounds.left, bounds.top, inclusive);
+ var topRight = this.contains(bounds.right, bounds.top, inclusive);
+
+ return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
+ : (bottomLeft && bottomRight && topLeft && topRight);
+ },
+
+ /**
+ * APIMethod: determineQuadrant
+ * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
+ * <OpenLayers.LonLat> lies.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
+ * coordinate lies.
+ */
+ determineQuadrant: function(lonlat) {
+
+ var quadrant = "";
+ var center = this.getCenterLonLat();
+
+ quadrant += (lonlat.lat < center.lat) ? "b" : "t";
+ quadrant += (lonlat.lon < center.lon) ? "l" : "r";
+
+ return quadrant;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the Bounds object from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ // clear cached center location
+ this.centerLonLat = null;
+ var ll = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.bottom}, source, dest);
+ var lr = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.bottom}, source, dest);
+ var ul = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.top}, source, dest);
+ var ur = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.top}, source, dest);
+ this.left = Math.min(ll.x, ul.x);
+ this.bottom = Math.min(ll.y, lr.y);
+ this.right = Math.max(lr.x, ur.x);
+ this.top = Math.max(ul.y, ur.y);
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ * Wraps the bounds object around the dateline.
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ * options - {Object} Some possible options are:
+ *
+ * Allowed Options:
+ * leftTolerance - {float} Allow for a margin of error
+ * with the 'left' value of this
+ * bound.
+ * Default is 0.
+ * rightTolerance - {float} Allow for a margin of error
+ * with the 'right' value of
+ * this bound.
+ * Default is 0.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent). Note that this function only returns
+ * a different bounds value if this bounds is
+ * *entirely* outside of the maxExtent. If this
+ * bounds straddles the dateline (is part in/part
+ * out of maxExtent), the returned bounds will always
+ * cross the left edge of the given maxExtent.
+ *.
+ */
+ wrapDateLine: function(maxExtent, options) {
+ options = options || {};
+
+ var leftTolerance = options.leftTolerance || 0;
+ var rightTolerance = options.rightTolerance || 0;
+
+ var newBounds = this.clone();
+
+ if (maxExtent) {
+ var width = maxExtent.getWidth();
+
+ //shift right?
+ while (newBounds.left < maxExtent.left &&
+ newBounds.right - rightTolerance <= maxExtent.left ) {
+ newBounds = newBounds.add(width, 0);
+ }
+
+ //shift left?
+ while (newBounds.left + leftTolerance >= maxExtent.right &&
+ newBounds.right > maxExtent.right ) {
+ newBounds = newBounds.add(-width, 0);
+ }
+
+ // crosses right only? force left
+ var newLeft = newBounds.left + leftTolerance;
+ if (newLeft < maxExtent.right && newLeft > maxExtent.left &&
+ newBounds.right - rightTolerance > maxExtent.right) {
+ newBounds = newBounds.add(-width, 0);
+ }
+ }
+
+ return newBounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Bounds"
+});
+
+/**
+ * APIFunction: fromString
+ * Alternative constructor that builds a new OpenLayers.Bounds from a
+ * parameter string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromString("5,42,10,45");
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
+ * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the
+ * passed-in String.
+ */
+OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
+ var bounds = str.split(",");
+ return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
+};
+
+/**
+ * APIFunction: fromArray
+ * Alternative constructor that builds a new OpenLayers.Bounds from an array.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
+ * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
+ */
+OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
+ return reverseAxisOrder === true ?
+ new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
+ new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
+};
+
+/**
+ * APIFunction: fromSize
+ * Alternative constructor that builds a new OpenLayers.Bounds from a size.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(0, 20, 10, 0);
+ * (end)
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
+ * both 'w' and 'h' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
+ */
+OpenLayers.Bounds.fromSize = function(size) {
+ return new OpenLayers.Bounds(0,
+ size.h,
+ size.w,
+ 0);
+};
+
+/**
+ * Function: oppositeQuadrant
+ * Get the opposite quadrant for a given quadrant string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.oppositeQuadrant( "tl" );
+ * // => "br"
+ *
+ * OpenLayers.Bounds.oppositeQuadrant( "tr" );
+ * // => "bl"
+ * (end)
+ *
+ * Parameters:
+ * quadrant - {String} two character quadrant shortstring
+ *
+ * Returns:
+ * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if
+ * you pass in "bl" it returns "tr", if you pass in "br" it
+ * returns "tl", etc.
+ */
+OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
+ var opp = "";
+
+ opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
+ opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
+
+ return opp;
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Element.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ */
+
+/**
+ * Namespace: OpenLayers.Element
+ */
+OpenLayers.Element = {
+
+ /**
+ * APIFunction: visible
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Boolean} Is the element visible?
+ */
+ visible: function(element) {
+ return OpenLayers.Util.getElement(element).style.display != 'none';
+ },
+
+ /**
+ * APIFunction: toggle
+ * Toggle the visibility of element(s) passed in
+ *
+ * Parameters:
+ * element - {DOMElement} Actually user can pass any number of elements
+ */
+ toggle: function() {
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ var display = OpenLayers.Element.visible(element) ? 'none'
+ : '';
+ element.style.display = display;
+ }
+ },
+
+ /**
+ * APIFunction: remove
+ * Remove the specified element from the DOM.
+ *
+ * Parameters:
+ * element - {DOMElement}
+ */
+ remove: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ element.parentNode.removeChild(element);
+ },
+
+ /**
+ * APIFunction: getHeight
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Integer} The offset height of the element passed in
+ */
+ getHeight: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ return element.offsetHeight;
+ },
+
+ /**
+ * Function: hasClass
+ * Tests if an element has the given CSS class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to search for.
+ *
+ * Returns:
+ * {Boolean} The element has the given class name.
+ */
+ hasClass: function(element, name) {
+ var names = element.className;
+ return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
+ },
+
+ /**
+ * Function: addClass
+ * Add a CSS class name to an element. Safe where element already has
+ * the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to add.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ addClass: function(element, name) {
+ if(!OpenLayers.Element.hasClass(element, name)) {
+ element.className += (element.className ? " " : "") + name;
+ }
+ return element;
+ },
+
+ /**
+ * Function: removeClass
+ * Remove a CSS class name from an element. Safe where element does not
+ * have the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to remove.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ removeClass: function(element, name) {
+ var names = element.className;
+ if(names) {
+ element.className = OpenLayers.String.trim(
+ names.replace(
+ new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
+ )
+ );
+ }
+ return element;
+ },
+
+ /**
+ * Function: toggleClass
+ * Remove a CSS class name from an element if it exists. Add the class name
+ * if it doesn't exist.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to toggle.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ toggleClass: function(element, name) {
+ if(OpenLayers.Element.hasClass(element, name)) {
+ OpenLayers.Element.removeClass(element, name);
+ } else {
+ OpenLayers.Element.addClass(element, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIFunction: getStyle
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * style - {?}
+ *
+ * Returns:
+ * {?}
+ */
+ getStyle: function(element, style) {
+ element = OpenLayers.Util.getElement(element);
+
+ var value = null;
+ if (element && element.style) {
+ value = element.style[OpenLayers.String.camelize(style)];
+ if (!value) {
+ if (document.defaultView &&
+ document.defaultView.getComputedStyle) {
+
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[OpenLayers.String.camelize(style)];
+ }
+ }
+
+ var positions = ['left', 'top', 'right', 'bottom'];
+ if (window.opera &&
+ (OpenLayers.Util.indexOf(positions,style) != -1) &&
+ (OpenLayers.Element.getStyle(element, 'position') == 'static')) {
+ value = 'auto';
+ }
+ }
+
+ return value == 'auto' ? null : value;
+ }
+
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/LonLat.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.LonLat
+ * This class represents a longitude and latitude pair
+ */
+OpenLayers.LonLat = OpenLayers.Class({
+
+ /**
+ * APIProperty: lon
+ * {Float} The x-axis coodinate in map units
+ */
+ lon: 0.0,
+
+ /**
+ * APIProperty: lat
+ * {Float} The y-axis coordinate in map units
+ */
+ lat: 0.0,
+
+ /**
+ * Constructor: OpenLayers.LonLat
+ * Create a new map location. Coordinates can be passed either as two
+ * arguments, or as a single argument.
+ *
+ * Parameters (two arguments):
+ * lon - {Number} The x-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Longitude. Otherwise,
+ * it will be the x coordinate of the map location in your map units.
+ * lat - {Number} The y-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Latitude. Otherwise,
+ * it will be the y coordinate of the map location in your map units.
+ *
+ * Parameters (single argument):
+ * location - {Array(Float)} [lon, lat]
+ */
+ initialize: function(lon, lat) {
+ if (OpenLayers.Util.isArray(lon)) {
+ lat = lon[1];
+ lon = lon[0];
+ }
+ this.lon = OpenLayers.Util.toFloat(lon);
+ this.lat = OpenLayers.Util.toFloat(lat);
+ },
+
+ /**
+ * Method: toString
+ * Return a readable string version of the lonlat
+ *
+ * Returns:
+ * {String} String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"lon=5,lat=42"</i>)
+ */
+ toString:function() {
+ return ("lon=" + this.lon + ",lat=" + this.lat);
+ },
+
+ /**
+ * APIMethod: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"5, 42"</i>)
+ */
+ toShortString:function() {
+ return (this.lon + ", " + this.lat);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon
+ * and lat values
+ */
+ clone:function() {
+ return new OpenLayers.LonLat(this.lon, this.lat);
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and
+ * lat passed-in added to this's.
+ */
+ add:function(lon, lat) {
+ if ( (lon == null) || (lat == null) ) {
+ throw new TypeError('LonLat.add cannot receive null values');
+ }
+ return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon),
+ this.lat + OpenLayers.Util.toFloat(lat));
+ },
+
+ /**
+ * APIMethod: equals
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Boolean value indicating whether the passed-in
+ * <OpenLayers.LonLat> object has the same lon and lat
+ * components as this.
+ * Note: if ll passed in is null, returns false
+ */
+ equals:function(ll) {
+ var equals = false;
+ if (ll != null) {
+ equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
+ (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the LonLat object from source to dest. This transformation is
+ * *in place*: if you want a *new* lonlat, use .clone() first.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ var point = OpenLayers.Projection.transform(
+ {'x': this.lon, 'y': this.lat}, source, dest);
+ this.lon = point.x;
+ this.lat = point.y;
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent)
+ */
+ wrapDateLine: function(maxExtent) {
+
+ var newLonLat = this.clone();
+
+ if (maxExtent) {
+ //shift right?
+ while (newLonLat.lon < maxExtent.left) {
+ newLonLat.lon += maxExtent.getWidth();
+ }
+
+ //shift left?
+ while (newLonLat.lon > maxExtent.right) {
+ newLonLat.lon -= maxExtent.getWidth();
+ }
+ }
+
+ return newLonLat;
+ },
+
+ CLASS_NAME: "OpenLayers.LonLat"
+});
+
+/**
+ * Function: fromString
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from a
+ * parameter string
+ *
+ * Parameters:
+ * str - {String} Comma-separated Lon,Lat coordinate string.
+ * (e.g. <i>"5,40"</i>)
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in String.
+ */
+OpenLayers.LonLat.fromString = function(str) {
+ var pair = str.split(",");
+ return new OpenLayers.LonLat(pair[0], pair[1]);
+};
+
+/**
+ * Function: fromArray
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from an
+ * array of two numbers that represent lon- and lat-values.
+ *
+ * Parameters:
+ * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in array.
+ */
+OpenLayers.LonLat.fromArray = function(arr) {
+ var gotArr = OpenLayers.Util.isArray(arr),
+ lon = gotArr && arr[0],
+ lat = gotArr && arr[1];
+ return new OpenLayers.LonLat(lon, lat);
+};
+/* ======================================================================
+ OpenLayers/BaseTypes/Pixel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Pixel
+ * This class represents a screen coordinate, in x and y coordinates
+ */
+OpenLayers.Pixel = OpenLayers.Class({
+
+ /**
+ * APIProperty: x
+ * {Number} The x coordinate
+ */
+ x: 0.0,
+
+ /**
+ * APIProperty: y
+ * {Number} The y coordinate
+ */
+ y: 0.0,
+
+ /**
+ * Constructor: OpenLayers.Pixel
+ * Create a new OpenLayers.Pixel instance
+ *
+ * Parameters:
+ * x - {Number} The x coordinate
+ * y - {Number} The y coordinate
+ *
+ * Returns:
+ * An instance of OpenLayers.Pixel
+ */
+ initialize: function(x, y) {
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * Method: toString
+ * Cast this object into a string
+ *
+ * Returns:
+ * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
+ */
+ toString:function() {
+ return ("x=" + this.x + ",y=" + this.y);
+ },
+
+ /**
+ * APIMethod: clone
+ * Return a clone of this pixel object
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A clone pixel
+ */
+ clone:function() {
+ return new OpenLayers.Pixel(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether one pixel is equivalent to another
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {Boolean} The point passed in as parameter is equal to this. Note that
+ * if px passed in is null, returns false.
+ */
+ equals:function(px) {
+ var equals = false;
+ if (px != null) {
+ equals = ((this.x == px.x && this.y == px.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Returns the distance to the pixel point passed in as a parameter.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Float} The pixel point passed in as parameter to calculate the
+ * distance to.
+ */
+ distanceTo:function(px) {
+ return Math.sqrt(
+ Math.pow(this.x - px.x, 2) +
+ Math.pow(this.y - px.y, 2)
+ );
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * values passed in.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Pixel.add cannot receive null values');
+ }
+ return new OpenLayers.Pixel(this.x + x, this.y + y);
+ },
+
+ /**
+ * APIMethod: offset
+ *
+ * Parameters
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * x&y values of the pixel passed in.
+ */
+ offset:function(px) {
+ var newPx = this.clone();
+ if (px) {
+ newPx = this.add(px.x, px.y);
+ }
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Pixel"
+});
+/* ======================================================================
+ OpenLayers/BaseTypes/Size.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Size
+ * Instances of this class represent a width/height pair
+ */
+OpenLayers.Size = OpenLayers.Class({
+
+ /**
+ * APIProperty: w
+ * {Number} width
+ */
+ w: 0.0,
+
+ /**
+ * APIProperty: h
+ * {Number} height
+ */
+ h: 0.0,
+
+
+ /**
+ * Constructor: OpenLayers.Size
+ * Create an instance of OpenLayers.Size
+ *
+ * Parameters:
+ * w - {Number} width
+ * h - {Number} height
+ */
+ initialize: function(w, h) {
+ this.w = parseFloat(w);
+ this.h = parseFloat(h);
+ },
+
+ /**
+ * Method: toString
+ * Return the string representation of a size object
+ *
+ * Returns:
+ * {String} The string representation of OpenLayers.Size object.
+ * (e.g. <i>"w=55,h=66"</i>)
+ */
+ toString:function() {
+ return ("w=" + this.w + ",h=" + this.h);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this size object
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
+ * values
+ */
+ clone:function() {
+ return new OpenLayers.Size(this.w, this.h);
+ },
+
+ /**
+ *
+ * APIMethod: equals
+ * Determine where this size is equal to another
+ *
+ * Parameters:
+ * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ *
+ * Returns:
+ * {Boolean} The passed in size has the same h and w properties as this one.
+ * Note that if sz passed in is null, returns false.
+ */
+ equals:function(sz) {
+ var equals = false;
+ if (sz != null) {
+ equals = ((this.w == sz.w && this.h == sz.h) ||
+ (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
+ }
+ return equals;
+ },
+
+ CLASS_NAME: "OpenLayers.Size"
+});
+/* ======================================================================
+ OpenLayers/Console.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Namespace: OpenLayers.Console
+ * The OpenLayers.Console namespace is used for debugging and error logging.
+ * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
+ * calls to OpenLayers.Console methods will get redirected to window.console.
+ * This makes use of the Firebug extension where available and allows for
+ * cross-browser debugging Firebug style.
+ *
+ * Note:
+ * Note that behavior will differ with the Firebug extention and Firebug Lite.
+ * Most notably, the Firebug Lite console does not currently allow for
+ * hyperlinks to code or for clicking on object to explore their properties.
+ *
+ */
+OpenLayers.Console = {
+ /**
+ * Create empty functions for all console methods. The real value of these
+ * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
+ * included. We explicitly require the Firebug Lite script to trigger
+ * functionality of the OpenLayers.Console methods.
+ */
+
+ /**
+ * APIFunction: log
+ * Log an object in the console. The Firebug Lite console logs string
+ * representation of objects. Given multiple arguments, they will
+ * be cast to strings and logged with a space delimiter. If the first
+ * argument is a string with printf-like formatting, subsequent arguments
+ * will be used in string substitution. Any additional arguments (beyond
+ * the number substituted in a format string) will be appended in a space-
+ * delimited line.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ log: function() {},
+
+ /**
+ * APIFunction: debug
+ * Writes a message to the console, including a hyperlink to the line
+ * where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ debug: function() {},
+
+ /**
+ * APIFunction: info
+ * Writes a message to the console with the visual "info" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ info: function() {},
+
+ /**
+ * APIFunction: warn
+ * Writes a message to the console with the visual "warning" icon and
+ * color coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ warn: function() {},
+
+ /**
+ * APIFunction: error
+ * Writes a message to the console with the visual "error" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ error: function() {},
+
+ /**
+ * APIFunction: userError
+ * A single interface for showing error messages to the user. The default
+ * behavior is a Javascript alert, though this can be overridden by
+ * reassigning OpenLayers.Console.userError to a different function.
+ *
+ * Expects a single error message
+ *
+ * Parameters:
+ * error - {Object}
+ */
+ userError: function(error) {
+ alert(error);
+ },
+
+ /**
+ * APIFunction: assert
+ * Tests that an expression is true. If not, it will write a message to
+ * the console and throw an exception.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ assert: function() {},
+
+ /**
+ * APIFunction: dir
+ * Prints an interactive listing of all properties of the object. This
+ * looks identical to the view that you would see in the DOM tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dir: function() {},
+
+ /**
+ * APIFunction: dirxml
+ * Prints the XML source tree of an HTML or XML element. This looks
+ * identical to the view that you would see in the HTML tab. You can click
+ * on any node to inspect it in the HTML tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dirxml: function() {},
+
+ /**
+ * APIFunction: trace
+ * Prints an interactive stack trace of JavaScript execution at the point
+ * where it is called. The stack trace details the functions on the stack,
+ * as well as the values that were passed as arguments to each function.
+ * You can click each function to take you to its source in the Script tab,
+ * and click each argument value to inspect it in the DOM or HTML tabs.
+ *
+ */
+ trace: function() {},
+
+ /**
+ * APIFunction: group
+ * Writes a message to the console and opens a nested block to indent all
+ * future messages sent to the console. Call OpenLayers.Console.groupEnd()
+ * to close the block.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ group: function() {},
+
+ /**
+ * APIFunction: groupEnd
+ * Closes the most recently opened block created by a call to
+ * OpenLayers.Console.group
+ */
+ groupEnd: function() {},
+
+ /**
+ * APIFunction: time
+ * Creates a new timer under the given name. Call
+ * OpenLayers.Console.timeEnd(name)
+ * with the same name to stop the timer and print the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ time: function() {},
+
+ /**
+ * APIFunction: timeEnd
+ * Stops a timer created by a call to OpenLayers.Console.time(name) and
+ * writes the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ timeEnd: function() {},
+
+ /**
+ * APIFunction: profile
+ * Turns on the JavaScript profiler. The optional argument title would
+ * contain the text to be printed in the header of the profile report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title for the profiler
+ */
+ profile: function() {},
+
+ /**
+ * APIFunction: profileEnd
+ * Turns off the JavaScript profiler and prints its report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ */
+ profileEnd: function() {},
+
+ /**
+ * APIFunction: count
+ * Writes the number of times that the line of code where count was called
+ * was executed. The optional argument title will print a message in
+ * addition to the number of the count.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title to be printed with count
+ */
+ count: function() {},
+
+ CLASS_NAME: "OpenLayers.Console"
+};
+
+/**
+ * Execute an anonymous function to extend the OpenLayers.Console namespace
+ * if the firebug.js script is included. This closure is used so that the
+ * "scripts" and "i" variables don't pollute the global namespace.
+ */
+(function() {
+ /**
+ * If Firebug Lite is included (before this script), re-route all
+ * OpenLayers.Console calls to the console object.
+ */
+ var scripts = document.getElementsByTagName("script");
+ for(var i=0, len=scripts.length; i<len; ++i) {
+ if(scripts[i].src.indexOf("firebug.js") != -1) {
+ if(console) {
+ OpenLayers.Util.extend(OpenLayers.Console, console);
+ break;
+ }
+ }
+ }
+})();
+/* ======================================================================
+ OpenLayers/Lang.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang
+ * Internationalization namespace. Contains dictionaries in various languages
+ * and methods to set and get the current language.
+ */
+OpenLayers.Lang = {
+
+ /**
+ * Property: code
+ * {String} Current language code to use in OpenLayers. Use the
+ * <setCode> method to set this value and the <getCode> method to
+ * retrieve it.
+ */
+ code: null,
+
+ /**
+ * APIProperty: defaultCode
+ * {String} Default language to use when a specific language can't be
+ * found. Default is "en".
+ */
+ defaultCode: "en",
+
+ /**
+ * APIFunction: getCode
+ * Get the current language code.
+ *
+ * Returns:
+ * {String} The current language code.
+ */
+ getCode: function() {
+ if(!OpenLayers.Lang.code) {
+ OpenLayers.Lang.setCode();
+ }
+ return OpenLayers.Lang.code;
+ },
+
+ /**
+ * APIFunction: setCode
+ * Set the language code for string translation. This code is used by
+ * the <OpenLayers.Lang.translate> method.
+ *
+ * Parameters:
+ * code - {String} These codes follow the IETF recommendations at
+ * http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the
+ * browser's language setting will be tested. If no <OpenLayers.Lang>
+ * dictionary exists for the code, the <OpenLayers.String.defaultLang>
+ * will be used.
+ */
+ setCode: function(code) {
+ var lang;
+ if(!code) {
+ code = (OpenLayers.BROWSER_NAME == "msie") ?
+ navigator.userLanguage : navigator.language;
+ }
+ var parts = code.split('-');
+ parts[0] = parts[0].toLowerCase();
+ if(typeof OpenLayers.Lang[parts[0]] == "object") {
+ lang = parts[0];
+ }
+
+ // check for regional extensions
+ if(parts[1]) {
+ var testLang = parts[0] + '-' + parts[1].toUpperCase();
+ if(typeof OpenLayers.Lang[testLang] == "object") {
+ lang = testLang;
+ }
+ }
+ if(!lang) {
+ OpenLayers.Console.warn(
+ 'Failed to find OpenLayers.Lang.' + parts.join("-") +
+ ' dictionary, falling back to default language'
+ );
+ lang = OpenLayers.Lang.defaultCode;
+ }
+
+ OpenLayers.Lang.code = lang;
+ },
+
+ /**
+ * APIMethod: translate
+ * Looks up a key from a dictionary based on the current language string.
+ * The value of <getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+ translate: function(key, context) {
+ var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
+ var message = dictionary && dictionary[key];
+ if(!message) {
+ // Message not found, fall back to message key
+ message = key;
+ }
+ if(context) {
+ message = OpenLayers.String.format(message, context);
+ }
+ return message;
+ }
+
+};
+
+
+/**
+ * APIMethod: OpenLayers.i18n
+ * Alias for <OpenLayers.Lang.translate>. Looks up a key from a dictionary
+ * based on the current language string. The value of
+ * <OpenLayers.Lang.getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+OpenLayers.i18n = OpenLayers.Lang.translate;
+/* ======================================================================
+ OpenLayers/Util.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/BaseTypes/LonLat.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: Util
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+
+/**
+ * Function: getElement
+ * This is the old $() from prototype
+ *
+ * Parameters:
+ * e - {String or DOMElement or Window}
+ *
+ * Returns:
+ * {Array(DOMElement) or DOMElement}
+ */
+OpenLayers.Util.getElement = function() {
+ var elements = [];
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ if (arguments.length == 1) {
+ return element;
+ }
+ elements.push(element);
+ }
+ return elements;
+};
+
+/**
+ * Function: isElement
+ * A cross-browser implementation of "e instanceof Element".
+ *
+ * Parameters:
+ * o - {Object} The object to test.
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.isElement = function(o) {
+ return !!(o && o.nodeType === 1);
+};
+
+/**
+ * Function: isArray
+ * Tests that the provided object is an array.
+ * This test handles the cross-IFRAME case not caught
+ * by "a instanceof Array" and should be used instead.
+ *
+ * Parameters:
+ * a - {Object} the object test.
+ *
+ * Returns:
+ * {Boolean} true if the object is an array.
+ */
+OpenLayers.Util.isArray = function(a) {
+ return (Object.prototype.toString.call(a) === '[object Array]');
+};
+
+/**
+ * Function: removeItem
+ * Remove an object from an array. Iterates through the array
+ * to find the item, then removes it.
+ *
+ * Parameters:
+ * array - {Array}
+ * item - {Object}
+ *
+ * Returns:
+ * {Array} A reference to the array
+ */
+OpenLayers.Util.removeItem = function(array, item) {
+ for(var i = array.length - 1; i >= 0; i--) {
+ if(array[i] == item) {
+ array.splice(i,1);
+ //break;more than once??
+ }
+ }
+ return array;
+};
+
+/**
+ * Function: indexOf
+ * Seems to exist already in FF, but not in MOZ.
+ *
+ * Parameters:
+ * array - {Array}
+ * obj - {*}
+ *
+ * Returns:
+ * {Integer} The index at which the first object was found in the array.
+ * If not found, returns -1.
+ */
+OpenLayers.Util.indexOf = function(array, obj) {
+ // use the build-in function if available.
+ if (typeof array.indexOf == "function") {
+ return array.indexOf(obj);
+ } else {
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (array[i] == obj) {
+ return i;
+ }
+ }
+ return -1;
+ }
+};
+
+
+/**
+ * Property: dotless
+ * {RegExp}
+ * Compiled regular expression to match dots ("."). This is used for replacing
+ * dots in identifiers. Because object identifiers are frequently used for
+ * DOM element identifiers by the library, we avoid using dots to make for
+ * more sensible CSS selectors.
+ *
+ * TODO: Use a module pattern to avoid bloating the API with stuff like this.
+ */
+OpenLayers.Util.dotless = /\./g;
+
+/**
+ * Function: modifyDOMElement
+ *
+ * Modifies many properties of a DOM element all at once. Passing in
+ * null to an individual parameter will avoid setting the attribute.
+ *
+ * Parameters:
+ * element - {DOMElement} DOM element to modify.
+ * id - {String} The element id attribute to set. Note that dots (".") will be
+ * replaced with underscore ("_") in setting the element id.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * position - {String} The position attribute. eg: absolute,
+ * relative, etc.
+ * border - {String} The style.border attribute. eg:
+ * solid black 2px
+ * overflow - {String} The style.overview attribute.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position,
+ border, overflow, opacity) {
+
+ if (id) {
+ element.id = id.replace(OpenLayers.Util.dotless, "_");
+ }
+ if (px) {
+ element.style.left = px.x + "px";
+ element.style.top = px.y + "px";
+ }
+ if (sz) {
+ element.style.width = sz.w + "px";
+ element.style.height = sz.h + "px";
+ }
+ if (position) {
+ element.style.position = position;
+ }
+ if (border) {
+ element.style.border = border;
+ }
+ if (overflow) {
+ element.style.overflow = overflow;
+ }
+ if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
+ element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
+ element.style.opacity = opacity;
+ } else if (parseFloat(opacity) == 1.0) {
+ element.style.filter = '';
+ element.style.opacity = '';
+ }
+};
+
+/**
+ * Function: createDiv
+ * Creates a new div and optionally set some standard attributes.
+ * Null may be passed to each parameter if you do not wish to
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
+ *
+ * Parameters:
+ * id - {String} An identifier for this element. If no id is
+ * passed an identifier will be created
+ * automatically. Note that dots (".") will be replaced with
+ * underscore ("_") when generating ids.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} A url pointing to an image to use as a
+ * background image.
+ * position - {String} The style.position value. eg: absolute,
+ * relative etc.
+ * border - {String} The the style.border value.
+ * eg: 2px solid black
+ * overflow - {String} The style.overflow value. Eg. hidden
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with the specified attributes.
+ */
+OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position,
+ border, overflow, opacity) {
+
+ var dom = document.createElement('div');
+
+ if (imgURL) {
+ dom.style.backgroundImage = 'url(' + imgURL + ')';
+ }
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "absolute";
+ }
+ OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position,
+ border, overflow, opacity);
+
+ return dom;
+};
+
+/**
+ * Function: createImage
+ * Creates an img element with specific attribute values.
+ *
+ * Parameters:
+ * id - {String} The id field for the img. If none assigned one will be
+ * automatically generated.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} The url to use as the image source.
+ * position - {String} The style.position value.
+ * border - {String} The border to place around the image.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Image created with the specified attributes.
+ */
+OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
+ opacity, delayDisplay) {
+
+ var image = document.createElement("img");
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "relative";
+ }
+ OpenLayers.Util.modifyDOMElement(image, id, px, sz, position,
+ border, null, opacity);
+
+ if (delayDisplay) {
+ image.style.display = "none";
+ function display() {
+ image.style.display = "";
+ OpenLayers.Event.stopObservingElement(image);
+ }
+ OpenLayers.Event.observe(image, "load", display);
+ OpenLayers.Event.observe(image, "error", display);
+ }
+
+ //set special properties
+ image.style.alt = id;
+ image.galleryImg = "no";
+ if (imgURL) {
+ image.src = imgURL;
+ }
+
+ return image;
+};
+
+/**
+ * Property: IMAGE_RELOAD_ATTEMPTS
+ * {Integer} How many times should we try to reload an image before giving up?
+ * Default is 0
+ */
+OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
+
+/**
+ * Property: alphaHackNeeded
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHackNeeded = null;
+
+/**
+ * Function: alphaHack
+ * Checks whether it's necessary (and possible) to use the png alpha
+ * hack which allows alpha transparency for png images under Internet
+ * Explorer.
+ *
+ * Returns:
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHack = function() {
+ if (OpenLayers.Util.alphaHackNeeded == null) {
+ var arVersion = navigator.appVersion.split("MSIE");
+ var version = parseFloat(arVersion[1]);
+ var filter = false;
+
+ // IEs4Lin dies when trying to access document.body.filters, because
+ // the property is there, but requires a DLL that can't be provided. This
+ // means that we need to wrap this in a try/catch so that this can
+ // continue.
+
+ try {
+ filter = !!(document.body.filters);
+ } catch (e) {}
+
+ OpenLayers.Util.alphaHackNeeded = (filter &&
+ (version >= 5.5) && (version < 7));
+ }
+ return OpenLayers.Util.alphaHackNeeded;
+};
+
+/**
+ * Function: modifyAlphaImageDiv
+ *
+ * Parameters:
+ * div - {DOMElement} Div containing Alpha-adjusted Image
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL,
+ position, border, sizing,
+ opacity) {
+
+ OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+ null, null, opacity);
+
+ var img = div.childNodes[0];
+
+ if (imgURL) {
+ img.src = imgURL;
+ }
+ OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz,
+ "relative", border);
+
+ if (OpenLayers.Util.alphaHack()) {
+ if(div.style.display != "none") {
+ div.style.display = "inline-block";
+ }
+ if (sizing == null) {
+ sizing = "scale";
+ }
+
+ div.style.filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img.src + "', " +
+ "sizingMethod='" + sizing + "')";
+ if (parseFloat(div.style.opacity) >= 0.0 &&
+ parseFloat(div.style.opacity) < 1.0) {
+ div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
+ }
+
+ img.style.filter = "alpha(opacity=0)";
+ }
+};
+
+/**
+ * Function: createAlphaImageDiv
+ *
+ * Parameters:
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is
+ * needed for transparency in IE, it is added.
+ */
+OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL,
+ position, border, sizing,
+ opacity, delayDisplay) {
+
+ var div = OpenLayers.Util.createDiv();
+ var img = OpenLayers.Util.createImage(null, null, null, null, null, null,
+ null, delayDisplay);
+ img.className = "olAlphaImg";
+ div.appendChild(img);
+
+ OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position,
+ border, sizing, opacity);
+
+ return div;
+};
+
+
+/**
+ * Function: upperCaseObject
+ * Creates a new hashtable and copies over all the keys from the
+ * passed-in object, but storing them under an uppercased
+ * version of the key at which they were stored.
+ *
+ * Parameters:
+ * object - {Object}
+ *
+ * Returns:
+ * {Object} A new Object with all the same keys but uppercased
+ */
+OpenLayers.Util.upperCaseObject = function (object) {
+ var uObject = {};
+ for (var key in object) {
+ uObject[key.toUpperCase()] = object[key];
+ }
+ return uObject;
+};
+
+/**
+ * Function: applyDefaults
+ * Takes an object and copies any properties that don't exist from
+ * another properties, by analogy with OpenLayers.Util.extend() from
+ * Prototype.js.
+ *
+ * Parameters:
+ * to - {Object} The destination object.
+ * from - {Object} The source object. Any properties of this object that
+ * are undefined in the to object will be set on the to object.
+ *
+ * Returns:
+ * {Object} A reference to the to object. Note that the to argument is modified
+ * in place and returned by this function.
+ */
+OpenLayers.Util.applyDefaults = function (to, from) {
+ to = to || {};
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object is an
+ * instance of window.Event.
+ */
+ var fromIsEvt = typeof window.Event == "function"
+ && from instanceof window.Event;
+
+ for (var key in from) {
+ if (to[key] === undefined ||
+ (!fromIsEvt && from.hasOwnProperty
+ && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
+ to[key] = from[key];
+ }
+ }
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+ if(!fromIsEvt && from && from.hasOwnProperty
+ && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
+ to.toString = from.toString;
+ }
+
+ return to;
+};
+
+/**
+ * Function: getParameterString
+ *
+ * Parameters:
+ * params - {Object}
+ *
+ * Returns:
+ * {String} A concatenation of the properties of an object in
+ * http parameter notation.
+ * (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
+ * If a parameter is actually a list, that parameter will then
+ * be set to a comma-seperated list of values (foo,bar) instead
+ * of being URL escaped (foo%3Abar).
+ */
+OpenLayers.Util.getParameterString = function(params) {
+ var paramsArray = [];
+
+ for (var key in params) {
+ var value = params[key];
+ if ((value != null) && (typeof value != 'function')) {
+ var encodedValue;
+ if (typeof value == 'object' && value.constructor == Array) {
+ /* value is an array; encode items and separate with "," */
+ var encodedItemArray = [];
+ var item;
+ for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
+ item = value[itemIndex];
+ encodedItemArray.push(encodeURIComponent(
+ (item === null || item === undefined) ? "" : item)
+ );
+ }
+ encodedValue = encodedItemArray.join(",");
+ }
+ else {
+ /* value is a string; simply encode */
+ encodedValue = encodeURIComponent(value);
+ }
+ paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
+ }
+ }
+
+ return paramsArray.join("&");
+};
+
+/**
+ * Function: urlAppend
+ * Appends a parameter string to a url. This function includes the logic for
+ * using the appropriate character (none, & or ?) to append to the url before
+ * appending the param string.
+ *
+ * Parameters:
+ * url - {String} The url to append to
+ * paramStr - {String} The param string to append
+ *
+ * Returns:
+ * {String} The new url
+ */
+OpenLayers.Util.urlAppend = function(url, paramStr) {
+ var newUrl = url;
+ if(paramStr) {
+ var parts = (url + " ").split(/[?&]/);
+ newUrl += (parts.pop() === " " ?
+ paramStr :
+ parts.length ? "&" + paramStr : "?" + paramStr);
+ }
+ return newUrl;
+};
+
+/**
+ * Function: getImagesLocation
+ *
+ * Returns:
+ * {String} The fully formatted image location string
+ */
+OpenLayers.Util.getImagesLocation = function() {
+ return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
+};
+
+/**
+ * Function: getImageLocation
+ *
+ * Returns:
+ * {String} The fully formatted location string for a specified image
+ */
+OpenLayers.Util.getImageLocation = function(image) {
+ return OpenLayers.Util.getImagesLocation() + image;
+};
+
+
+/**
+ * Function: Try
+ * Execute functions until one of them doesn't throw an error.
+ * Capitalized because "try" is a reserved word in JavaScript.
+ * Taken directly from OpenLayers.Util.Try()
+ *
+ * Parameters:
+ * [*] - {Function} Any number of parameters may be passed to Try()
+ * It will attempt to execute each of them until one of them
+ * successfully executes.
+ * If none executes successfully, returns null.
+ *
+ * Returns:
+ * {*} The value returned by the first successfully executed function.
+ */
+OpenLayers.Util.Try = function() {
+ var returnValue = null;
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+};
+
+/**
+ * Function: getXmlNodeValue
+ *
+ * Parameters:
+ * node - {XMLNode}
+ *
+ * Returns:
+ * {String} The text value of the given node, without breaking in firefox or IE
+ */
+OpenLayers.Util.getXmlNodeValue = function(node) {
+ var val = null;
+ OpenLayers.Util.Try(
+ function() {
+ val = node.text;
+ if (!val) {
+ val = node.textContent;
+ }
+ if (!val) {
+ val = node.firstChild.nodeValue;
+ }
+ },
+ function() {
+ val = node.textContent;
+ });
+ return val;
+};
+
+/**
+ * Function: mouseLeft
+ *
+ * Parameters:
+ * evt - {Event}
+ * div - {HTMLDivElement}
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.mouseLeft = function (evt, div) {
+ // start with the element to which the mouse has moved
+ var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
+ // walk up the DOM tree.
+ while (target != div && target != null) {
+ target = target.parentNode;
+ }
+ // if the target we stop at isn't the div, then we've left the div.
+ return (target != div);
+};
+
+/**
+ * Property: precision
+ * {Number} The number of significant digits to retain to avoid
+ * floating point precision errors.
+ *
+ * We use 14 as a "safe" default because, although IEEE 754 double floats
+ * (standard on most modern operating systems) support up to about 16
+ * significant digits, 14 significant digits are sufficient to represent
+ * sub-millimeter accuracy in any coordinate system that anyone is likely to
+ * use with OpenLayers.
+ *
+ * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
+ * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
+ * with certain projections, e.g. spherical Mercator.
+ *
+ */
+OpenLayers.Util.DEFAULT_PRECISION = 14;
+
+/**
+ * Function: toFloat
+ * Convenience method to cast an object to a Number, rounded to the
+ * desired floating point precision.
+ *
+ * Parameters:
+ * number - {Number} The number to cast and round.
+ * precision - {Number} An integer suitable for use with
+ * Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
+ * If set to 0, no rounding is performed.
+ *
+ * Returns:
+ * {Number} The cast, rounded number.
+ */
+OpenLayers.Util.toFloat = function (number, precision) {
+ if (precision == null) {
+ precision = OpenLayers.Util.DEFAULT_PRECISION;
+ }
+ if (typeof number !== "number") {
+ number = parseFloat(number);
+ }
+ return precision === 0 ? number :
+ parseFloat(number.toPrecision(precision));
+};
+
+/**
+ * Function: rad
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
+
+/**
+ * Function: deg
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
+
+/**
+ * Property: VincentyConstants
+ * {Object} Constants for Vincenty functions.
+ */
+OpenLayers.Util.VincentyConstants = {
+ a: 6378137,
+ b: 6356752.3142,
+ f: 1/298.257223563
+};
+
+/**
+ * APIFunction: distVincenty
+ * Given two objects representing points with geographic coordinates, this
+ * calculates the distance between those points on the surface of an
+ * ellipsoid.
+ *
+ * Parameters:
+ * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ *
+ * Returns:
+ * {Float} The distance (in km) between the two input points as measured on an
+ * ellipsoid. Note that the input point objects must be in geographic
+ * coordinates (decimal degrees) and the return distance is in kilometers.
+ */
+OpenLayers.Util.distVincenty = function(p1, p2) {
+ var ct = OpenLayers.Util.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var L = OpenLayers.Util.rad(p2.lon - p1.lon);
+ var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
+ var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
+ var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
+ var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
+ var lambda = L, lambdaP = 2*Math.PI;
+ var iterLimit = 20;
+ while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
+ var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
+ var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
+ (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
+ if (sinSigma==0) {
+ return 0; // co-incident points
+ }
+ var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
+ var sigma = Math.atan2(sinSigma, cosSigma);
+ var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
+ var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
+ var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ lambdaP = lambda;
+ lambda = L + (1-C) * f * Math.sin(alpha) *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+ }
+ if (iterLimit==0) {
+ return NaN; // formula failed to converge
+ }
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ var s = b*A*(sigma-deltaSigma);
+ var d = s.toFixed(3)/1000; // round to 1mm precision
+ return d;
+};
+
+/**
+ * APIFunction: destinationVincenty
+ * Calculate destination point given start point lat/long (numeric degrees),
+ * bearing (numeric degrees) & distance (in m).
+ * Adapted from Chris Veness work, see
+ * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
+ * properties) The start point.
+ * brng - {Float} The bearing (degrees).
+ * dist - {Float} The ground distance (meters).
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The destination point.
+ */
+OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
+ var u = OpenLayers.Util;
+ var ct = u.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var lon1 = lonlat.lon;
+ var lat1 = lonlat.lat;
+
+ var s = dist;
+ var alpha1 = u.rad(brng);
+ var sinAlpha1 = Math.sin(alpha1);
+ var cosAlpha1 = Math.cos(alpha1);
+
+ var tanU1 = (1-f) * Math.tan(u.rad(lat1));
+ var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
+ var sigma1 = Math.atan2(tanU1, cosAlpha1);
+ var sinAlpha = cosU1 * sinAlpha1;
+ var cosSqAlpha = 1 - sinAlpha*sinAlpha;
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+
+ var sigma = s / (b*A), sigmaP = 2*Math.PI;
+ while (Math.abs(sigma-sigmaP) > 1e-12) {
+ var cos2SigmaM = Math.cos(2*sigma1 + sigma);
+ var sinSigma = Math.sin(sigma);
+ var cosSigma = Math.cos(sigma);
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ sigmaP = sigma;
+ sigma = s / (b*A) + deltaSigma;
+ }
+
+ var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
+ var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
+ (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+ var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ var L = lambda - (1-C) * f * sinAlpha *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+
+ var revAz = Math.atan2(sinAlpha, -tmp); // final bearing
+
+ return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
+};
+
+/**
+ * Function: getParameters
+ * Parse the parameters from a URL or from the current page itself into a
+ * JavaScript Object. Note that parameter values with commas are separated
+ * out into an Array.
+ *
+ * Parameters:
+ * url - {String} Optional url used to extract the query string.
+ * If url is null or is not supplied, query string is taken
+ * from the page location.
+ * options - {Object} Additional options. Optional.
+ *
+ * Valid options:
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object of key/value pairs from the query string.
+ */
+OpenLayers.Util.getParameters = function(url, options) {
+ options = options || {};
+ // if no url specified, take it from the location bar
+ url = (url === null || url === undefined) ? window.location.href : url;
+
+ //parse out parameters portion of url string
+ var paramsString = "";
+ if (OpenLayers.String.contains(url, '?')) {
+ var start = url.indexOf('?') + 1;
+ var end = OpenLayers.String.contains(url, "#") ?
+ url.indexOf('#') : url.length;
+ paramsString = url.substring(start, end);
+ }
+
+ var parameters = {};
+ var pairs = paramsString.split(/[&;]/);
+ for(var i=0, len=pairs.length; i<len; ++i) {
+ var keyValue = pairs[i].split('=');
+ if (keyValue[0]) {
+
+ var key = keyValue[0];
+ try {
+ key = decodeURIComponent(key);
+ } catch (err) {
+ key = unescape(key);
+ }
+
+ // being liberal by replacing "+" with " "
+ var value = (keyValue[1] || '').replace(/\+/g, " ");
+
+ try {
+ value = decodeURIComponent(value);
+ } catch (err) {
+ value = unescape(value);
+ }
+
+ // follow OGC convention of comma delimited values
+ if (options.splitArgs !== false) {
+ value = value.split(",");
+ }
+
+ //if there's only one value, do not return as array
+ if (value.length == 1) {
+ value = value[0];
+ }
+
+ parameters[key] = value;
+ }
+ }
+ return parameters;
+};
+
+/**
+ * Property: lastSeqID
+ * {Integer} The ever-incrementing count variable.
+ * Used for generating unique ids.
+ */
+OpenLayers.Util.lastSeqID = 0;
+
+/**
+ * Function: createUniqueID
+ * Create a unique identifier for this session. Each time this function
+ * is called, a counter is incremented. The return will be the optional
+ * prefix (defaults to "id_") appended with the counter value.
+ *
+ * Parameters:
+ * prefix - {String} Optional string to prefix unique id. Default is "id_".
+ * Note that dots (".") in the prefix will be replaced with underscore ("_").
+ *
+ * Returns:
+ * {String} A unique id string, built on the passed in prefix.
+ */
+OpenLayers.Util.createUniqueID = function(prefix) {
+ if (prefix == null) {
+ prefix = "id_";
+ } else {
+ prefix = prefix.replace(OpenLayers.Util.dotless, "_");
+ }
+ OpenLayers.Util.lastSeqID += 1;
+ return prefix + OpenLayers.Util.lastSeqID;
+};
+
+/**
+ * Constant: INCHES_PER_UNIT
+ * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
+ * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
+ * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
+ * and PROJ.4 (http://trac.osgeo.org/proj/)
+ * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
+ * The hardcoded table of PROJ.4 units are in pj_units.c.
+ */
+OpenLayers.INCHES_PER_UNIT = {
+ 'inches': 1.0,
+ 'ft': 12.0,
+ 'mi': 63360.0,
+ 'm': 39.37,
+ 'km': 39370,
+ 'dd': 4374754,
+ 'yd': 36
+};
+OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
+OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
+OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
+
+// Units from CS-Map
+OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "Inch": OpenLayers.INCHES_PER_UNIT.inches,
+ "Meter": 1.0 / OpenLayers.METERS_PER_INCH, //EPSG:9001
+ "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH, //EPSG:9003
+ "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9002
+ "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH, //EPSG:9005
+ "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH, //EPSG:9041
+ "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH, //EPSG:9094
+ "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
+ "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
+ "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
+ "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9036
+ "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
+ "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH, //EPSG:9040
+ "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH, //EPSG:9084
+ "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH, //EPSG:9085
+ "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH, //EPSG:9086
+ "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH, //EPSG:9087
+ "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH, //EPSG:9080
+ "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH, //EPSG:9081
+ "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH, //EPSG:9082
+ "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH, //EPSG:9083
+ "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
+ "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9096
+ "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9093
+ "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9030
+ "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
+ "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
+ "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH, //EPSG:9031
+ "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
+ "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9038
+ "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9033
+ "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9062
+ "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9042
+ "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9039
+ "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9034
+ "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9063
+ "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9043
+ "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH, //EPSG:9097
+ "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH, //EPSG:9098
+ "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
+ "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
+ "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
+ "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
+ "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
+ "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
+ "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
+ "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
+});
+
+//unit abbreviations supported by PROJ.4
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
+ "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
+ "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
+ "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
+ "kmi": OpenLayers.INCHES_PER_UNIT["nmi"], //International Nautical Mile
+ "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
+ "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"], //International Chain
+ "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
+ "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
+ "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
+ "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
+ "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
+ "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"], //U.S. Surveyor's Statute Mile
+ "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"], //Indian Yard
+ "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"], //Indian Foot
+ "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH //Indian Chain
+});
+
+/**
+ * Constant: DOTS_PER_INCH
+ * {Integer} 72 (A sensible default)
+ */
+OpenLayers.DOTS_PER_INCH = 72;
+
+/**
+ * Function: normalizeScale
+ *
+ * Parameters:
+ * scale - {float}
+ *
+ * Returns:
+ * {Float} A normalized scale value, in 1 / X format.
+ * This means that if a value less than one ( already 1/x) is passed
+ * in, it just returns scale directly. Otherwise, it returns
+ * 1 / scale
+ */
+OpenLayers.Util.normalizeScale = function (scale) {
+ var normScale = (scale > 1.0) ? (1.0 / scale)
+ : scale;
+ return normScale;
+};
+
+/**
+ * Function: getResolutionFromScale
+ *
+ * Parameters:
+ * scale - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding resolution given passed-in scale and unit
+ * parameters. If the given scale is falsey, the returned resolution will
+ * be undefined.
+ */
+OpenLayers.Util.getResolutionFromScale = function (scale, units) {
+ var resolution;
+ if (scale) {
+ if (units == null) {
+ units = "degrees";
+ }
+ var normScale = OpenLayers.Util.normalizeScale(scale);
+ resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
+ * OpenLayers.DOTS_PER_INCH);
+ }
+ return resolution;
+};
+
+/**
+ * Function: getScaleFromResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding scale given passed-in resolution and unit
+ * parameters.
+ */
+OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
+
+ if (units == null) {
+ units = "degrees";
+ }
+
+ var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
+ OpenLayers.DOTS_PER_INCH;
+ return scale;
+};
+
+/**
+ * Function: pagePosition
+ * Calculates the position of an element on the page (see
+ * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
+ *
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Parameters:
+ * forElement - {DOMElement}
+ *
+ * Returns:
+ * {Array} two item array, Left value then Top value.
+ */
+OpenLayers.Util.pagePosition = function(forElement) {
+ // NOTE: If element is hidden (display none or disconnected or any the
+ // ancestors are hidden) we get (0,0) by default but we still do the
+ // accumulation of scroll position.
+
+ var pos = [0, 0];
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ if (!forElement || forElement == window || forElement == viewportElement) {
+ // viewport is always at 0,0 as that defined the coordinate system for
+ // this function - this avoids special case checks in the code below
+ return pos;
+ }
+
+ // Gecko browsers normally use getBoxObjectFor to calculate the position.
+ // When invoked for an element with an implicit absolute position though it
+ // can be off by one. Therefore the recursive implementation is used in
+ // those (relatively rare) cases.
+ var BUGGY_GECKO_BOX_OBJECT =
+ OpenLayers.IS_GECKO && document.getBoxObjectFor &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
+ (forElement.style.top == '' || forElement.style.left == '');
+
+ var parent = null;
+ var box;
+
+ if (forElement.getBoundingClientRect) { // IE
+ box = forElement.getBoundingClientRect();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+
+ pos[0] = box.left + scrollLeft;
+ pos[1] = box.top + scrollTop;
+
+ } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
+ // Gecko ignores the scroll values for ancestors, up to 1.9. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
+
+ box = document.getBoxObjectFor(forElement);
+ var vpBox = document.getBoxObjectFor(viewportElement);
+ pos[0] = box.screenX - vpBox.screenX;
+ pos[1] = box.screenY - vpBox.screenY;
+
+ } else { // safari/opera
+ pos[0] = forElement.offsetLeft;
+ pos[1] = forElement.offsetTop;
+ parent = forElement.offsetParent;
+ if (parent != forElement) {
+ while (parent) {
+ pos[0] += parent.offsetLeft;
+ pos[1] += parent.offsetTop;
+ parent = parent.offsetParent;
+ }
+ }
+
+ var browser = OpenLayers.BROWSER_NAME;
+
+ // opera & (safari absolute) incorrectly account for body offsetTop
+ if (browser == "opera" || (browser == "safari" &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
+ pos[1] -= document.body.offsetTop;
+ }
+
+ // accumulate the scroll positions for everything but the body element
+ parent = forElement.offsetParent;
+ while (parent && parent != document.body) {
+ pos[0] -= parent.scrollLeft;
+ // see https://bugs.opera.com/show_bug.cgi?id=249965
+ if (browser != "opera" || parent.tagName != 'TR') {
+ pos[1] -= parent.scrollTop;
+ }
+ parent = parent.offsetParent;
+ }
+ }
+
+ return pos;
+};
+
+/**
+ * Function: getViewportElement
+ * Returns die viewport element of the document. The viewport element is
+ * usually document.documentElement, except in IE,where it is either
+ * document.body or document.documentElement, depending on the document's
+ * compatibility mode (see
+ * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+OpenLayers.Util.getViewportElement = function() {
+ var viewportElement = arguments.callee.viewportElement;
+ if (viewportElement == undefined) {
+ viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
+ document.compatMode != 'CSS1Compat') ? document.body :
+ document.documentElement;
+ arguments.callee.viewportElement = viewportElement;
+ }
+ return viewportElement;
+};
+
+/**
+ * Function: isEquivalentUrl
+ * Test two URLs for equivalence.
+ *
+ * Setting 'ignoreCase' allows for case-independent comparison.
+ *
+ * Comparison is based on:
+ * - Protocol
+ * - Host (evaluated without the port)
+ * - Port (set 'ignorePort80' to ignore "80" values)
+ * - Hash ( set 'ignoreHash' to disable)
+ * - Pathname (for relative <-> absolute comparison)
+ * - Arguments (so they can be out of order)
+ *
+ * Parameters:
+ * url1 - {String}
+ * url2 - {String}
+ * options - {Object} Allows for customization of comparison:
+ * 'ignoreCase' - Default is True
+ * 'ignorePort80' - Default is True
+ * 'ignoreHash' - Default is True
+ *
+ * Returns:
+ * {Boolean} Whether or not the two URLs are equivalent
+ */
+OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
+ options = options || {};
+
+ OpenLayers.Util.applyDefaults(options, {
+ ignoreCase: true,
+ ignorePort80: true,
+ ignoreHash: true,
+ splitArgs: false
+ });
+
+ var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
+ var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
+
+ //compare all keys except for "args" (treated below)
+ for(var key in urlObj1) {
+ if(key !== "args") {
+ if(urlObj1[key] != urlObj2[key]) {
+ return false;
+ }
+ }
+ }
+
+ // compare search args - irrespective of order
+ for(var key in urlObj1.args) {
+ if(urlObj1.args[key] != urlObj2.args[key]) {
+ return false;
+ }
+ delete urlObj2.args[key];
+ }
+ // urlObj2 shouldn't have any args left
+ for(var key in urlObj2.args) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Function: createUrlObject
+ *
+ * Parameters:
+ * url - {String}
+ * options - {Object} A hash of options.
+ *
+ * Valid options:
+ * ignoreCase - {Boolean} lowercase url,
+ * ignorePort80 - {Boolean} don't include explicit port if port is 80,
+ * ignoreHash - {Boolean} Don't include part of url after the hash (#).
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object with separate url, a, port, host, and args parsed out
+ * and ready for comparison
+ */
+OpenLayers.Util.createUrlObject = function(url, options) {
+ options = options || {};
+
+ // deal with relative urls first
+ if(!(/^\w+:\/\//).test(url)) {
+ var loc = window.location;
+ var port = loc.port ? ":" + loc.port : "";
+ var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
+ if(url.indexOf("/") === 0) {
+ // full pathname
+ url = fullUrl + url;
+ } else {
+ // relative to current path
+ var parts = loc.pathname.split("/");
+ parts.pop();
+ url = fullUrl + parts.join("/") + "/" + url;
+ }
+ }
+
+ if (options.ignoreCase) {
+ url = url.toLowerCase();
+ }
+
+ var a = document.createElement('a');
+ a.href = url;
+
+ var urlObject = {};
+
+ //host (without port)
+ urlObject.host = a.host.split(":").shift();
+
+ //protocol
+ urlObject.protocol = a.protocol;
+
+ //port (get uniform browser behavior with port 80 here)
+ if(options.ignorePort80) {
+ urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
+ } else {
+ urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
+ }
+
+ //hash
+ urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;
+
+ //args
+ var queryString = a.search;
+ if (!queryString) {
+ var qMark = url.indexOf("?");
+ queryString = (qMark != -1) ? url.substr(qMark) : "";
+ }
+ urlObject.args = OpenLayers.Util.getParameters(queryString,
+ {splitArgs: options.splitArgs});
+
+ // pathname
+ //
+ // This is a workaround for Internet Explorer where
+ // window.location.pathname has a leading "/", but
+ // a.pathname has no leading "/".
+ urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
+
+ return urlObject;
+};
+
+/**
+ * Function: removeTail
+ * Takes a url and removes everything after the ? and #
+ *
+ * Parameters:
+ * url - {String} The url to process
+ *
+ * Returns:
+ * {String} The string with all queryString and Hash removed
+ */
+OpenLayers.Util.removeTail = function(url) {
+ var head = null;
+
+ var qMark = url.indexOf("?");
+ var hashMark = url.indexOf("#");
+
+ if (qMark == -1) {
+ head = (hashMark != -1) ? url.substr(0,hashMark) : url;
+ } else {
+ head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark))
+ : url.substr(0, qMark);
+ }
+ return head;
+};
+
+/**
+ * Constant: IS_GECKO
+ * {Boolean} True if the userAgent reports the browser to use the Gecko engine
+ */
+OpenLayers.IS_GECKO = (function() {
+ var ua = navigator.userAgent.toLowerCase();
+ return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
+})();
+
+/**
+ * Constant: CANVAS_SUPPORTED
+ * {Boolean} True if canvas 2d is supported.
+ */
+OpenLayers.CANVAS_SUPPORTED = (function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+})();
+
+/**
+ * Constant: BROWSER_NAME
+ * {String}
+ * A substring of the navigator.userAgent property. Depending on the userAgent
+ * property, this will be the empty string or one of the following:
+ * * "opera" -- Opera
+ * * "msie" -- Internet Explorer
+ * * "safari" -- Safari
+ * * "firefox" -- Firefox
+ * * "mozilla" -- Mozilla
+ */
+OpenLayers.BROWSER_NAME = (function() {
+ var name = "";
+ var ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf("opera") != -1) {
+ name = "opera";
+ } else if (ua.indexOf("msie") != -1) {
+ name = "msie";
+ } else if (ua.indexOf("safari") != -1) {
+ name = "safari";
+ } else if (ua.indexOf("mozilla") != -1) {
+ if (ua.indexOf("firefox") != -1) {
+ name = "firefox";
+ } else {
+ name = "mozilla";
+ }
+ }
+ return name;
+})();
+
+/**
+ * Function: getBrowserName
+ *
+ * Returns:
+ * {String} A string which specifies which is the current
+ * browser in which we are running.
+ *
+ * Currently-supported browser detection and codes:
+ * * 'opera' -- Opera
+ * * 'msie' -- Internet Explorer
+ * * 'safari' -- Safari
+ * * 'firefox' -- Firefox
+ * * 'mozilla' -- Mozilla
+ *
+ * If we are unable to property identify the browser, we
+ * return an empty string.
+ */
+OpenLayers.Util.getBrowserName = function() {
+ return OpenLayers.BROWSER_NAME;
+};
+
+/**
+ * Method: getRenderedDimensions
+ * Renders the contentHTML offscreen to determine actual dimensions for
+ * popup sizing. As we need layout to determine dimensions the content
+ * is rendered -9999px to the left and absolute to ensure the
+ * scrollbars do not flicker
+ *
+ * Parameters:
+ * contentHTML
+ * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is
+ * specified, we fix that dimension of the div to be measured. This is
+ * useful in the case where we have a limit in one dimension and must
+ * therefore meaure the flow in the other dimension.
+ * options - {Object}
+ *
+ * Allowed Options:
+ * displayClass - {String} Optional parameter. A CSS class name(s) string
+ * to provide the CSS context of the rendered content.
+ * containerElement - {DOMElement} Optional parameter. Insert the HTML to
+ * this node instead of the body root when calculating dimensions.
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
+
+ var w, h;
+
+ // create temp container div with restricted size
+ var container = document.createElement("div");
+ container.style.visibility = "hidden";
+
+ var containerElement = (options && options.containerElement)
+ ? options.containerElement : document.body;
+
+ // Opera and IE7 can't handle a node with position:aboslute if it inherits
+ // position:absolute from a parent.
+ var parentHasPositionAbsolute = false;
+ var superContainer = null;
+ var parent = containerElement;
+ while (parent && parent.tagName.toLowerCase()!="body") {
+ var parentPosition = OpenLayers.Element.getStyle(parent, "position");
+ if(parentPosition == "absolute") {
+ parentHasPositionAbsolute = true;
+ break;
+ } else if (parentPosition && parentPosition != "static") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+ if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 ||
+ containerElement.clientWidth === 0) ){
+ superContainer = document.createElement("div");
+ superContainer.style.visibility = "hidden";
+ superContainer.style.position = "absolute";
+ superContainer.style.overflow = "visible";
+ superContainer.style.width = document.body.clientWidth + "px";
+ superContainer.style.height = document.body.clientHeight + "px";
+ superContainer.appendChild(container);
+ }
+ container.style.position = "absolute";
+
+ //fix a dimension, if specified.
+ if (size) {
+ if (size.w) {
+ w = size.w;
+ container.style.width = w + "px";
+ } else if (size.h) {
+ h = size.h;
+ container.style.height = h + "px";
+ }
+ }
+
+ //add css classes, if specified
+ if (options && options.displayClass) {
+ container.className = options.displayClass;
+ }
+
+ // create temp content div and assign content
+ var content = document.createElement("div");
+ content.innerHTML = contentHTML;
+
+ // we need overflow visible when calculating the size
+ content.style.overflow = "visible";
+ if (content.childNodes) {
+ for (var i=0, l=content.childNodes.length; i<l; i++) {
+ if (!content.childNodes[i].style) continue;
+ content.childNodes[i].style.overflow = "visible";
+ }
+ }
+
+ // add content to restricted container
+ container.appendChild(content);
+
+ // append container to body for rendering
+ if (superContainer) {
+ containerElement.appendChild(superContainer);
+ } else {
+ containerElement.appendChild(container);
+ }
+
+ // calculate scroll width of content and add corners and shadow width
+ if (!w) {
+ w = parseInt(content.scrollWidth);
+
+ // update container width to allow height to adjust
+ container.style.width = w + "px";
+ }
+ // capture height and add shadow and corner image widths
+ if (!h) {
+ h = parseInt(content.scrollHeight);
+ }
+
+ // remove elements
+ container.removeChild(content);
+ if (superContainer) {
+ superContainer.removeChild(container);
+ containerElement.removeChild(superContainer);
+ } else {
+ containerElement.removeChild(container);
+ }
+
+ return new OpenLayers.Size(w, h);
+};
+
+/**
+ * APIFunction: getScrollbarWidth
+ * This function has been modified by the OpenLayers from the original version,
+ * written by Matthew Eernisse and released under the Apache 2
+ * license here:
+ *
+ * http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
+ *
+ * It has been modified simply to cache its value, since it is physically
+ * impossible that this code could ever run in more than one browser at
+ * once.
+ *
+ * Returns:
+ * {Integer}
+ */
+OpenLayers.Util.getScrollbarWidth = function() {
+
+ var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+
+ if (scrollbarWidth == null) {
+ var scr = null;
+ var inn = null;
+ var wNoScroll = 0;
+ var wScroll = 0;
+
+ // Outer scrolling div
+ scr = document.createElement('div');
+ scr.style.position = 'absolute';
+ scr.style.top = '-1000px';
+ scr.style.left = '-1000px';
+ scr.style.width = '100px';
+ scr.style.height = '50px';
+ // Start with no scrollbar
+ scr.style.overflow = 'hidden';
+
+ // Inner content div
+ inn = document.createElement('div');
+ inn.style.width = '100%';
+ inn.style.height = '200px';
+
+ // Put the inner div in the scrolling div
+ scr.appendChild(inn);
+ // Append the scrolling div to the doc
+ document.body.appendChild(scr);
+
+ // Width of the inner div sans scrollbar
+ wNoScroll = inn.offsetWidth;
+
+ // Add the scrollbar
+ scr.style.overflow = 'scroll';
+ // Width of the inner div width scrollbar
+ wScroll = inn.offsetWidth;
+
+ // Remove the scrolling div from the doc
+ document.body.removeChild(document.body.lastChild);
+
+ // Pixel width of the scroller
+ OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
+ scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+ }
+
+ return scrollbarWidth;
+};
+
+/**
+ * APIFunction: getFormattedLonLat
+ * This function will return latitude or longitude value formatted as
+ *
+ * Parameters:
+ * coordinate - {Float} the coordinate value to be formatted
+ * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
+ * to be formatted (default = lat)
+ * dmsOption - {String} specify the precision of the output can be one of:
+ * 'dms' show degrees minutes and seconds
+ * 'dm' show only degrees and minutes
+ * 'd' show only degrees
+ *
+ * Returns:
+ * {String} the coordinate value formatted as a string
+ */
+OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
+ if (!dmsOption) {
+ dmsOption = 'dms'; //default to show degree, minutes, seconds
+ }
+
+ coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
+
+ var abscoordinate = Math.abs(coordinate);
+ var coordinatedegrees = Math.floor(abscoordinate);
+
+ var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
+ var tempcoordinateminutes = coordinateminutes;
+ coordinateminutes = Math.floor(coordinateminutes);
+ var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
+ coordinateseconds = Math.round(coordinateseconds*10);
+ coordinateseconds /= 10;
+
+ if( coordinateseconds >= 60) {
+ coordinateseconds -= 60;
+ coordinateminutes += 1;
+ if( coordinateminutes >= 60) {
+ coordinateminutes -= 60;
+ coordinatedegrees += 1;
+ }
+ }
+
+ if( coordinatedegrees < 10 ) {
+ coordinatedegrees = "0" + coordinatedegrees;
+ }
+ var str = coordinatedegrees + "\u00B0";
+
+ if (dmsOption.indexOf('dm') >= 0) {
+ if( coordinateminutes < 10 ) {
+ coordinateminutes = "0" + coordinateminutes;
+ }
+ str += coordinateminutes + "'";
+
+ if (dmsOption.indexOf('dms') >= 0) {
+ if( coordinateseconds < 10 ) {
+ coordinateseconds = "0" + coordinateseconds;
+ }
+ str += coordinateseconds + '"';
+ }
+ }
+
+ if (axis == "lon") {
+ str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
+ } else {
+ str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
+ }
+ return str;
+};
+
+/* ======================================================================
+ OpenLayers/Events.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Event
+ * Utility functions for event handling.
+ */
+OpenLayers.Event = {
+
+ /**
+ * Property: observers
+ * {Object} A hashtable cache of the event observers. Keyed by
+ * element._eventCacheID
+ */
+ observers: false,
+
+ /**
+ * Constant: KEY_SPACE
+ * {int}
+ */
+ KEY_SPACE: 32,
+
+ /**
+ * Constant: KEY_BACKSPACE
+ * {int}
+ */
+ KEY_BACKSPACE: 8,
+
+ /**
+ * Constant: KEY_TAB
+ * {int}
+ */
+ KEY_TAB: 9,
+
+ /**
+ * Constant: KEY_RETURN
+ * {int}
+ */
+ KEY_RETURN: 13,
+
+ /**
+ * Constant: KEY_ESC
+ * {int}
+ */
+ KEY_ESC: 27,
+
+ /**
+ * Constant: KEY_LEFT
+ * {int}
+ */
+ KEY_LEFT: 37,
+
+ /**
+ * Constant: KEY_UP
+ * {int}
+ */
+ KEY_UP: 38,
+
+ /**
+ * Constant: KEY_RIGHT
+ * {int}
+ */
+ KEY_RIGHT: 39,
+
+ /**
+ * Constant: KEY_DOWN
+ * {int}
+ */
+ KEY_DOWN: 40,
+
+ /**
+ * Constant: KEY_DELETE
+ * {int}
+ */
+ KEY_DELETE: 46,
+
+
+ /**
+ * Method: element
+ * Cross browser event element detection.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {DOMElement} The element that caused the event
+ */
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ /**
+ * Method: isSingleTouch
+ * Determine whether event was caused by a single touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isSingleTouch: function(event) {
+ return event.touches && event.touches.length == 1;
+ },
+
+ /**
+ * Method: isMultiTouch
+ * Determine whether event was caused by a multi touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isMultiTouch: function(event) {
+ return event.touches && event.touches.length > 1;
+ },
+
+ /**
+ * Method: isLeftClick
+ * Determine whether event was caused by a left click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ /**
+ * Method: isRightClick
+ * Determine whether event was caused by a right mouse click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isRightClick: function(event) {
+ return (((event.which) && (event.which == 3)) ||
+ ((event.button) && (event.button == 2)));
+ },
+
+ /**
+ * Method: stop
+ * Stops an event from propagating.
+ *
+ * Parameters:
+ * event - {Event}
+ * allowDefault - {Boolean} If true, we stop the event chain but
+ * still allow the default browser behaviour (text selection,
+ * radio-button clicking, etc). Default is false.
+ */
+ stop: function(event, allowDefault) {
+
+ if (!allowDefault) {
+ OpenLayers.Event.preventDefault(event);
+ }
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * Method: preventDefault
+ * Cancels the event if it is cancelable, without stopping further
+ * propagation of the event.
+ *
+ * Parameters:
+ * event - {Event}
+ */
+ preventDefault: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
+ },
+
+ /**
+ * Method: findElement
+ *
+ * Parameters:
+ * event - {Event}
+ * tagName - {String}
+ *
+ * Returns:
+ * {DOMElement} The first node with the given tagName, starting from the
+ * node the event was triggered on and traversing the DOM upwards
+ */
+ findElement: function(event, tagName) {
+ var element = OpenLayers.Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase()))){
+ element = element.parentNode;
+ }
+ return element;
+ },
+
+ /**
+ * Method: observe
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ */
+ observe: function(elementParam, name, observer, useCapture) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent)) {
+ name = 'keydown';
+ }
+
+ //if observers cache has not yet been created, create it
+ if (!this.observers) {
+ this.observers = {};
+ }
+
+ //if not already assigned, make a new unique cache ID
+ if (!element._eventCacheID) {
+ var idPrefix = "eventCacheID_";
+ if (element.id) {
+ idPrefix = element.id + "_" + idPrefix;
+ }
+ element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
+ }
+
+ var cacheID = element._eventCacheID;
+
+ //if there is not yet a hash entry for this element, add one
+ if (!this.observers[cacheID]) {
+ this.observers[cacheID] = [];
+ }
+
+ //add a new observer to this element's list
+ this.observers[cacheID].push({
+ 'element': element,
+ 'name': name,
+ 'observer': observer,
+ 'useCapture': useCapture
+ });
+
+ //add the actual browser event listener
+ if (element.addEventListener) {
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ /**
+ * Method: stopObservingElement
+ * Given the id of an element to stop observing, cycle through the
+ * element's cached observers, calling stopObserving on each one,
+ * skipping those entries which can no longer be removed.
+ *
+ * parameters:
+ * elementParam - {DOMElement || String}
+ */
+ stopObservingElement: function(elementParam) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
+ },
+
+ /**
+ * Method: _removeElementObservers
+ *
+ * Parameters:
+ * elementObservers - {Array(Object)} Array of (element, name,
+ * observer, usecapture) objects,
+ * taken directly from hashtable
+ */
+ _removeElementObservers: function(elementObservers) {
+ if (elementObservers) {
+ for(var i = elementObservers.length-1; i >= 0; i--) {
+ var entry = elementObservers[i];
+ OpenLayers.Event.stopObserving.apply(this, [
+ entry.element, entry.name, entry.observer, entry.useCapture
+ ]);
+ }
+ }
+ },
+
+ /**
+ * Method: stopObserving
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ *
+ * Returns:
+ * {Boolean} Whether or not the event observer was removed
+ */
+ stopObserving: function(elementParam, name, observer, useCapture) {
+ useCapture = useCapture || false;
+
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ if (name == 'keypress') {
+ if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
+ element.detachEvent) {
+ name = 'keydown';
+ }
+ }
+
+ // find element's entry in this.observers cache and remove it
+ var foundEntry = false;
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ if (elementObservers) {
+
+ // find the specific event type in the element's list
+ var i=0;
+ while(!foundEntry && i < elementObservers.length) {
+ var cacheEntry = elementObservers[i];
+
+ if ((cacheEntry.name == name) &&
+ (cacheEntry.observer == observer) &&
+ (cacheEntry.useCapture == useCapture)) {
+
+ elementObservers.splice(i, 1);
+ if (elementObservers.length == 0) {
+ delete OpenLayers.Event.observers[cacheID];
+ }
+ foundEntry = true;
+ break;
+ }
+ i++;
+ }
+ }
+
+ //actually remove the event listener from browser
+ if (foundEntry) {
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element && element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+ return foundEntry;
+ },
+
+ /**
+ * Method: unloadCache
+ * Cycle through all the element entries in the events cache and call
+ * stopObservingElement on each.
+ */
+ unloadCache: function() {
+ // check for OpenLayers.Event before checking for observers, because
+ // OpenLayers.Event may be undefined in IE if no map instance was
+ // created
+ if (OpenLayers.Event && OpenLayers.Event.observers) {
+ for (var cacheID in OpenLayers.Event.observers) {
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ OpenLayers.Event._removeElementObservers.apply(this,
+ [elementObservers]);
+ }
+ OpenLayers.Event.observers = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Event"
+};
+
+/* prevent memory leaks in IE */
+OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
+
+/**
+ * Class: OpenLayers.Events
+ */
+OpenLayers.Events = OpenLayers.Class({
+
+ /**
+ * Constant: BROWSER_EVENTS
+ * {Array(String)} supported events
+ */
+ BROWSER_EVENTS: [
+ "mouseover", "mouseout",
+ "mousedown", "mouseup", "mousemove",
+ "click", "dblclick", "rightclick", "dblrightclick",
+ "resize", "focus", "blur",
+ "touchstart", "touchmove", "touchend",
+ "keydown"
+ ],
+
+ /**
+ * Property: listeners
+ * {Object} Hashtable of Array(Function): events listener functions
+ */
+ listeners: null,
+
+ /**
+ * Property: object
+ * {Object} the code object issuing application events
+ */
+ object: null,
+
+ /**
+ * Property: element
+ * {DOMElement} the DOM element receiving browser events
+ */
+ element: null,
+
+ /**
+ * Property: eventHandler
+ * {Function} bound event handler attached to elements
+ */
+ eventHandler: null,
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean}
+ */
+ fallThrough: null,
+
+ /**
+ * APIProperty: includeXY
+ * {Boolean} Should the .xy property automatically be created for browser
+ * mouse events? In general, this should be false. If it is true, then
+ * mouse events will automatically generate a '.xy' property on the
+ * event object that is passed. (Prior to OpenLayers 2.7, this was true
+ * by default.) Otherwise, you can call the getMousePosition on the
+ * relevant events handler on the object available via the 'evt.object'
+ * property of the evt object. So, for most events, you can call:
+ * function named(evt) {
+ * this.xy = this.object.events.getMousePosition(evt)
+ * }
+ *
+ * This option typically defaults to false for performance reasons:
+ * when creating an events object whose primary purpose is to manage
+ * relatively positioned mouse events within a div, it may make
+ * sense to set it to true.
+ *
+ * This option is also used to control whether the events object caches
+ * offsets. If this is false, it will not: the reason for this is that
+ * it is only expected to be called many times if the includeXY property
+ * is set to true. If you set this to true, you are expected to clear
+ * the offset cache manually (using this.clearMouseCache()) if:
+ * the border of the element changes
+ * the location of the element in the page changes
+ */
+ includeXY: false,
+
+ /**
+ * APIProperty: extensions
+ * {Object} Event extensions registered with this instance. Keys are
+ * event types, values are {OpenLayers.Events.*} extension instances or
+ * {Boolean} for events that an instantiated extension provides in
+ * addition to the one it was created for.
+ *
+ * Extensions create an event in addition to browser events, which usually
+ * fires when a sequence of browser events is completed. Extensions are
+ * automatically instantiated when a listener is registered for an event
+ * provided by an extension.
+ *
+ * Extensions are created in the <OpenLayers.Events> namespace using
+ * <OpenLayers.Class>, and named after the event they provide.
+ * The constructor receives the target <OpenLayers.Events> instance as
+ * argument. Extensions that need to capture browser events before they
+ * propagate can register their listeners events using <register>, with
+ * {extension: true} as 4th argument.
+ *
+ * If an extension creates more than one event, an alias for each event
+ * type should be created and reference the same class. The constructor
+ * should set a reference in the target's extensions registry to itself.
+ *
+ * Below is a minimal extension that provides the "foostart" and "fooend"
+ * event types, which replace the native "click" event type if clicked on
+ * an element with the css class "foo":
+ *
+ * (code)
+ * OpenLayers.Events.foostart = OpenLayers.Class({
+ * initialize: function(target) {
+ * this.target = target;
+ * this.target.register("click", this, this.doStuff, {extension: true});
+ * // only required if extension provides more than one event type
+ * this.target.extensions["foostart"] = true;
+ * this.target.extensions["fooend"] = true;
+ * },
+ * destroy: function() {
+ * var target = this.target;
+ * target.unregister("click", this, this.doStuff);
+ * delete this.target;
+ * // only required if extension provides more than one event type
+ * delete target.extensions["foostart"];
+ * delete target.extensions["fooend"];
+ * },
+ * doStuff: function(evt) {
+ * var propagate = true;
+ * if (OpenLayers.Event.element(evt).className === "foo") {
+ * propagate = false;
+ * var target = this.target;
+ * target.triggerEvent("foostart");
+ * window.setTimeout(function() {
+ * target.triggerEvent("fooend");
+ * }, 1000);
+ * }
+ * return propagate;
+ * }
+ * });
+ * // only required if extension provides more than one event type
+ * OpenLayers.Events.fooend = OpenLayers.Events.foostart;
+ * (end)
+ *
+ */
+ extensions: null,
+
+ /**
+ * Property: extensionCount
+ * {Object} Keys are event types (like in <listeners>), values are the
+ * number of extension listeners for each event type.
+ */
+ extensionCount: null,
+
+ /**
+ * Method: clearMouseListener
+ * A version of <clearMouseCache> that is bound to this instance so that
+ * it can be used with <OpenLayers.Event.observe> and
+ * <OpenLayers.Event.stopObserving>.
+ */
+ clearMouseListener: null,
+
+ /**
+ * Constructor: OpenLayers.Events
+ * Construct an OpenLayers.Events object.
+ *
+ * Parameters:
+ * object - {Object} The js object to which this Events object is being added
+ * element - {DOMElement} A dom element to respond to browser events
+ * eventTypes - {Array(String)} Deprecated. Array of custom application
+ * events. A listener may be registered for any named event, regardless
+ * of the values provided here.
+ * fallThrough - {Boolean} Allow events to fall through after these have
+ * been handled?
+ * options - {Object} Options for the events object.
+ */
+ initialize: function (object, element, eventTypes, fallThrough, options) {
+ OpenLayers.Util.extend(this, options);
+ this.object = object;
+ this.fallThrough = fallThrough;
+ this.listeners = {};
+ this.extensions = {};
+ this.extensionCount = {};
+ this._msTouches = [];
+
+ // if a dom element is specified, add a listeners list
+ // for browser events on the element and register them
+ if (element != null) {
+ this.attachToElement(element);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function () {
+ for (var e in this.extensions) {
+ if (typeof this.extensions[e] !== "boolean") {
+ this.extensions[e].destroy();
+ }
+ }
+ this.extensions = null;
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ if(this.element.hasScrollEvent) {
+ OpenLayers.Event.stopObserving(
+ window, "scroll", this.clearMouseListener
+ );
+ }
+ }
+ this.element = null;
+
+ this.listeners = null;
+ this.object = null;
+ this.fallThrough = null;
+ this.eventHandler = null;
+ },
+
+ /**
+ * APIMethod: addEventType
+ * Deprecated. Any event can be triggered without adding it first.
+ *
+ * Parameters:
+ * eventName - {String}
+ */
+ addEventType: function(eventName) {
+ },
+
+ /**
+ * Method: attachToElement
+ *
+ * Parameters:
+ * element - {HTMLDOMElement} a DOM element to attach browser events to
+ */
+ attachToElement: function (element) {
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ } else {
+ // keep a bound copy of handleBrowserEvent() so that we can
+ // pass the same function to both Event.observe() and .stopObserving()
+ this.eventHandler = OpenLayers.Function.bindAsEventListener(
+ this.handleBrowserEvent, this
+ );
+
+ // to be used with observe and stopObserving
+ this.clearMouseListener = OpenLayers.Function.bind(
+ this.clearMouseCache, this
+ );
+ }
+ this.element = element;
+ var msTouch = !!window.navigator.msMaxTouchPoints;
+ var type;
+ for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
+ type = this.BROWSER_EVENTS[i];
+ // register the event cross-browser
+ OpenLayers.Event.observe(element, type, this.eventHandler
+ );
+ if (msTouch && type.indexOf('touch') === 0) {
+ this.addMsTouchListener(element, type, this.eventHandler);
+ }
+ }
+ // disable dragstart in IE so that mousedown/move/up works normally
+ OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
+ },
+
+ /**
+ * APIMethod: on
+ * Convenience method for registering listeners with a common scope.
+ * Internally, this method calls <register> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // register a single listener for the "loadstart" event
+ * events.on({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", undefined, loadStartListener);
+ *
+ * // register multiple listeners to be called with the same `this` object
+ * events.on({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", object, loadStartListener);
+ * events.register("loadend", object, loadEndListener);
+ * (end)
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ on: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.register(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: register
+ * Register an event on the events object.
+ *
+ * When the event is triggered, the 'func' function will be called, in the
+ * context of 'obj'. Imagine we were to register an event, specifying an
+ * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
+ * context in the callback function will be our Bounds object. This means
+ * that within our callback function, we can access the properties and
+ * methods of the Bounds object through the "this" variable. So our
+ * callback could execute something like:
+ * : leftStr = "Left: " + this.left;
+ *
+ * or
+ *
+ * : centerStr = "Center: " + this.getCenterLonLat();
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ * priority - {Boolean|Object} If true, adds the new listener to the
+ * *front* of the events queue instead of to the end.
+ *
+ * Valid options for priority:
+ * extension - {Boolean} If true, then the event will be registered as
+ * extension event. Extension events are handled before all other
+ * events.
+ */
+ register: function (type, obj, func, priority) {
+ if (type in OpenLayers.Events && !this.extensions[type]) {
+ this.extensions[type] = new OpenLayers.Events[type](this);
+ }
+ if (func != null) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (!listeners) {
+ listeners = [];
+ this.listeners[type] = listeners;
+ this.extensionCount[type] = 0;
+ }
+ var listener = {obj: obj, func: func};
+ if (priority) {
+ listeners.splice(this.extensionCount[type], 0, listener);
+ if (typeof priority === "object" && priority.extension) {
+ this.extensionCount[type]++;
+ }
+ } else {
+ listeners.push(listener);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: registerPriority
+ * Same as register() but adds the new listener to the *front* of the
+ * events queue instead of to the end.
+ *
+ * TODO: get rid of this in 3.0 - Decide whether listeners should be
+ * called in the order they were registered or in reverse order.
+ *
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's
+ * 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ */
+ registerPriority: function (type, obj, func) {
+ this.register(type, obj, func, true);
+ },
+
+ /**
+ * APIMethod: un
+ * Convenience method for unregistering listeners with a common scope.
+ * Internally, this method calls <unregister> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // unregister a single listener for the "loadstart" event
+ * events.un({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", undefined, loadStartListener);
+ *
+ * // unregister multiple listeners with the same `this` object
+ * events.un({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", object, loadStartListener);
+ * events.unregister("loadend", object, loadEndListener);
+ * (end)
+ */
+ un: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.unregister(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: unregister
+ *
+ * Parameters:
+ * type - {String}
+ * obj - {Object} If none specified, defaults to this.object
+ * func - {Function}
+ */
+ unregister: function (type, obj, func) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (listeners != null) {
+ for (var i=0, len=listeners.length; i<len; i++) {
+ if (listeners[i].obj == obj && listeners[i].func == func) {
+ listeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: remove
+ * Remove all listeners for a given event type. If type is not registered,
+ * does nothing.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ remove: function(type) {
+ if (this.listeners[type] != null) {
+ this.listeners[type] = [];
+ }
+ },
+
+ /**
+ * APIMethod: triggerEvent
+ * Trigger a specified registered event.
+ *
+ * Parameters:
+ * type - {String}
+ * evt - {Event || Object} will be passed to the listeners.
+ *
+ * Returns:
+ * {Boolean} The last listener return. If a listener returns false, the
+ * chain of listeners will stop getting called.
+ */
+ triggerEvent: function (type, evt) {
+ var listeners = this.listeners[type];
+
+ // fast path
+ if(!listeners || listeners.length == 0) {
+ return undefined;
+ }
+
+ // prep evt object with object & div references
+ if (evt == null) {
+ evt = {};
+ }
+ evt.object = this.object;
+ evt.element = this.element;
+ if(!evt.type) {
+ evt.type = type;
+ }
+
+ // execute all callbacks registered for specified type
+ // get a clone of the listeners array to
+ // allow for splicing during callbacks
+ listeners = listeners.slice();
+ var continueChain;
+ for (var i=0, len=listeners.length; i<len; i++) {
+ var callback = listeners[i];
+ // bind the context to callback.obj
+ continueChain = callback.func.apply(callback.obj, [evt]);
+
+ if ((continueChain != undefined) && (continueChain == false)) {
+ // if callback returns false, execute no more callbacks.
+ break;
+ }
+ }
+ // don't fall through to other DOM elements
+ if (!this.fallThrough) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ return continueChain;
+ },
+
+ /**
+ * Method: handleBrowserEvent
+ * Basically just a wrapper to the triggerEvent() function, but takes
+ * care to set a property 'xy' on the event with the current mouse
+ * position.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ handleBrowserEvent: function (evt) {
+ var type = evt.type, listeners = this.listeners[type];
+ if(!listeners || listeners.length == 0) {
+ // noone's listening, bail out
+ return;
+ }
+ // add clientX & clientY to all events - corresponds to average x, y
+ var touches = evt.touches;
+ if (touches && touches[0]) {
+ var x = 0;
+ var y = 0;
+ var num = touches.length;
+ var touch;
+ for (var i=0; i<num; ++i) {
+ touch = this.getTouchClientXY(touches[i]);
+ x += touch.clientX;
+ y += touch.clientY;
+ }
+ evt.clientX = x / num;
+ evt.clientY = y / num;
+ }
+ if (this.includeXY) {
+ evt.xy = this.getMousePosition(evt);
+ }
+ this.triggerEvent(type, evt);
+ },
+
+ /**
+ * Method: getTouchClientXY
+ * WebKit has a few bugs for clientX/clientY. This method detects them
+ * and calculate the correct values.
+ *
+ * Parameters:
+ * evt - {Touch} a Touch object from a TouchEvent
+ *
+ * Returns:
+ * {Object} An object with only clientX and clientY properties with the
+ * calculated values.
+ */
+ getTouchClientXY: function (evt) {
+ // olMochWin is to override window, used for testing
+ var win = window.olMockWin || window,
+ winPageX = win.pageXOffset,
+ winPageY = win.pageYOffset,
+ x = evt.clientX,
+ y = evt.clientY;
+
+ if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
+ evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
+ // iOS4 include scroll offset in clientX/Y
+ x = x - winPageX;
+ y = y - winPageY;
+ } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
+ // Some Android browsers have totally bogus values for clientX/Y
+ // when scrolling/zooming a page
+ x = evt.pageX - winPageX;
+ y = evt.pageY - winPageY;
+ }
+
+ evt.olClientX = x;
+ evt.olClientY = y;
+
+ return {
+ clientX: x,
+ clientY: y
+ };
+ },
+
+ /**
+ * APIMethod: clearMouseCache
+ * Clear cached data about the mouse position. This should be called any
+ * time the element that events are registered on changes position
+ * within the page.
+ */
+ clearMouseCache: function() {
+ this.element.scrolls = null;
+ this.element.lefttop = null;
+ this.element.offsets = null;
+ },
+
+ /**
+ * Method: getMousePosition
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
+ * for offsets
+ */
+ getMousePosition: function (evt) {
+ if (!this.includeXY) {
+ this.clearMouseCache();
+ } else if (!this.element.hasScrollEvent) {
+ OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
+ this.element.hasScrollEvent = true;
+ }
+
+ if (!this.element.scrolls) {
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ this.element.scrolls = [
+ window.pageXOffset || viewportElement.scrollLeft,
+ window.pageYOffset || viewportElement.scrollTop
+ ];
+ }
+
+ if (!this.element.lefttop) {
+ this.element.lefttop = [
+ (document.documentElement.clientLeft || 0),
+ (document.documentElement.clientTop || 0)
+ ];
+ }
+
+ if (!this.element.offsets) {
+ this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+ }
+
+ return new OpenLayers.Pixel(
+ (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
+ - this.element.lefttop[0],
+ (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
+ - this.element.lefttop[1]
+ );
+ },
+
+ /**
+ * Method: addMsTouchListener
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListener: function (element, type, handler) {
+ var eventHandler = this.eventHandler;
+ var touches = this._msTouches;
+
+ function msHandler(evt) {
+ handler(OpenLayers.Util.applyDefaults({
+ stopPropagation: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].stopPropagation();
+ }
+ },
+ preventDefault: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].preventDefault();
+ }
+ },
+ type: type
+ }, evt));
+ }
+
+ switch (type) {
+ case 'touchstart':
+ return this.addMsTouchListenerStart(element, type, msHandler);
+ case 'touchend':
+ return this.addMsTouchListenerEnd(element, type, msHandler);
+ case 'touchmove':
+ return this.addMsTouchListenerMove(element, type, msHandler);
+ default:
+ throw 'Unknown touch event type';
+ }
+ },
+
+ /**
+ * Method: addMsTouchListenerStart
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerStart: function(element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ var alreadyInArray = false;
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ alreadyInArray = true;
+ break;
+ }
+ }
+ if (!alreadyInArray) {
+ touches.push(e);
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerDown', cb);
+
+ // Need to also listen for end events to keep the _msTouches list
+ // accurate
+ var internalCb = function(e) {
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+ };
+ OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
+ },
+
+ /**
+ * Method: addMsTouchListenerMove
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerMove: function (element, type, handler) {
+ var touches = this._msTouches;
+ var cb = function(e) {
+
+ //Don't fire touch moves when mouse isn't down
+ if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
+ return;
+ }
+
+ if (touches.length == 1 && touches[0].pageX == e.pageX &&
+ touches[0].pageY == e.pageY) {
+ // don't trigger event when pointer has not moved
+ return;
+ }
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches[i] = e;
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerMove', cb);
+ },
+
+ /**
+ * Method: addMsTouchListenerEnd
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerEnd: function (element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerUp', cb);
+ },
+
+ CLASS_NAME: "OpenLayers.Events"
+});
+/* ======================================================================
+ OpenLayers/Events/buttonclick.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.buttonclick
+ * Extension event type for handling buttons on top of a dom element. This
+ * event type fires "buttonclick" on its <target> when a button was
+ * clicked. Buttons are detected by the "olButton" class.
+ *
+ * This event type makes sure that button clicks do not interfere with other
+ * events that are registered on the same <element>.
+ *
+ * Event types provided by this extension:
+ * - *buttonclick* Triggered when a button is clicked. Listeners receive an
+ * object with a *buttonElement* property referencing the dom element of
+ * the clicked button, and an *buttonXY* property with the click position
+ * relative to the button.
+ */
+OpenLayers.Events.buttonclick = OpenLayers.Class({
+
+ /**
+ * Property: target
+ * {<OpenLayers.Events>} The events instance that the buttonclick event will
+ * be triggered on.
+ */
+ target: null,
+
+ /**
+ * Property: events
+ * {Array} Events to observe and conditionally stop from propagating when
+ * an element with the olButton class (or its olAlphaImg child) is
+ * clicked.
+ */
+ events: [
+ 'mousedown', 'mouseup', 'click', 'dblclick',
+ 'touchstart', 'touchmove', 'touchend', 'keydown'
+ ],
+
+ /**
+ * Property: startRegEx
+ * {RegExp} Regular expression to test Event.type for events that start
+ * a buttonclick sequence.
+ */
+ startRegEx: /^mousedown|touchstart$/,
+
+ /**
+ * Property: cancelRegEx
+ * {RegExp} Regular expression to test Event.type for events that cancel
+ * a buttonclick sequence.
+ */
+ cancelRegEx: /^touchmove$/,
+
+ /**
+ * Property: completeRegEx
+ * {RegExp} Regular expression to test Event.type for events that complete
+ * a buttonclick sequence.
+ */
+ completeRegEx: /^mouseup|touchend$/,
+
+ /**
+ * Property: startEvt
+ * {Event} The event that started the click sequence
+ */
+
+ /**
+ * Constructor: OpenLayers.Events.buttonclick
+ * Construct a buttonclick event type. Applications are not supposed to
+ * create instances of this class - they are created on demand by
+ * <OpenLayers.Events> instances.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance that the buttonclick
+ * event will be triggered on.
+ */
+ initialize: function(target) {
+ this.target = target;
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.register(this.events[i], this, this.buttonClick, {
+ extension: true
+ });
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.unregister(this.events[i], this, this.buttonClick);
+ }
+ delete this.target;
+ },
+
+ /**
+ * Method: getPressedButton
+ * Get the pressed button, if any. Returns undefined if no button
+ * was pressed.
+ *
+ * Arguments:
+ * element - {DOMElement} The event target.
+ *
+ * Returns:
+ * {DOMElement} The button element, or undefined.
+ */
+ getPressedButton: function(element) {
+ var depth = 3, // limit the search depth
+ button;
+ do {
+ if(OpenLayers.Element.hasClass(element, "olButton")) {
+ // hit!
+ button = element;
+ break;
+ }
+ element = element.parentNode;
+ } while(--depth > 0 && element);
+ return button;
+ },
+
+ /**
+ * Method: ignore
+ * Check for event target elements that should be ignored by OpenLayers.
+ *
+ * Parameters:
+ * element - {DOMElement} The event target.
+ */
+ ignore: function(element) {
+ var depth = 3,
+ ignore = false;
+ do {
+ if (element.nodeName.toLowerCase() === 'a') {
+ ignore = true;
+ break;
+ }
+ element = element.parentNode;
+ } while (--depth > 0 && element);
+ return ignore;
+ },
+
+ /**
+ * Method: buttonClick
+ * Check if a button was clicked, and fire the buttonclick event
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonClick: function(evt) {
+ var propagate = true,
+ element = OpenLayers.Event.element(evt);
+ if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
+ // was a button pressed?
+ var button = this.getPressedButton(element);
+ if (button) {
+ if (evt.type === "keydown") {
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_RETURN:
+ case OpenLayers.Event.KEY_SPACE:
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button
+ });
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ break;
+ }
+ } else if (this.startEvt) {
+ if (this.completeRegEx.test(evt.type)) {
+ var pos = OpenLayers.Util.pagePosition(button);
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+ pos[0] = pos[0] - scrollLeft;
+ pos[1] = pos[1] - scrollTop;
+
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button,
+ buttonXY: {
+ x: this.startEvt.clientX - pos[0],
+ y: this.startEvt.clientY - pos[1]
+ }
+ });
+ }
+ if (this.cancelRegEx.test(evt.type)) {
+ delete this.startEvt;
+ }
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ if (this.startRegEx.test(evt.type)) {
+ this.startEvt = evt;
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ } else {
+ propagate = !this.ignore(OpenLayers.Event.element(evt));
+ delete this.startEvt;
+ }
+ }
+ return propagate;
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Control.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Control
+ * Controls affect the display or behavior of the map. They allow everything
+ * from panning and zooming to displaying a scale indicator. Controls by
+ * default are added to the map they are contained within however it is
+ * possible to add a control to an external div by passing the div in the
+ * options parameter.
+ *
+ * Example:
+ * The following example shows how to add many of the common controls
+ * to a map.
+ *
+ * > var map = new OpenLayers.Map('map', { controls: [] });
+ * >
+ * > map.addControl(new OpenLayers.Control.PanZoomBar());
+ * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
+ * > map.addControl(new OpenLayers.Control.Permalink());
+ * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
+ * > map.addControl(new OpenLayers.Control.MousePosition());
+ * > map.addControl(new OpenLayers.Control.OverviewMap());
+ * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
+ *
+ * The next code fragment is a quick example of how to intercept
+ * shift-mouse click to display the extent of the bounding box
+ * dragged out by the user. Usually controls are not created
+ * in exactly this manner. See the source for a more complete
+ * example:
+ *
+ * > var control = new OpenLayers.Control();
+ * > OpenLayers.Util.extend(control, {
+ * > draw: function () {
+ * > // this Handler.Box will intercept the shift-mousedown
+ * > // before Control.MouseDefault gets to see it
+ * > this.box = new OpenLayers.Handler.Box( control,
+ * > {"done": this.notice},
+ * > {keyMask: OpenLayers.Handler.MOD_SHIFT});
+ * > this.box.activate();
+ * > },
+ * >
+ * > notice: function (bounds) {
+ * > OpenLayers.Console.userError(bounds);
+ * > }
+ * > });
+ * > map.addControl(control);
+ *
+ */
+OpenLayers.Control = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in the addControl() function in
+ * OpenLayers.Map
+ */
+ map: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement} The element that contains the control, if not present the
+ * control is placed inside the map.
+ */
+ div: null,
+
+ /**
+ * APIProperty: type
+ * {Number} Controls can have a 'type'. The type determines the type of
+ * interactions which are possible with them when they are placed in an
+ * <OpenLayers.Control.Panel>.
+ */
+ type: null,
+
+ /**
+ * Property: allowSelection
+ * {Boolean} By default, controls do not allow selection, because
+ * it may interfere with map dragging. If this is true, OpenLayers
+ * will not prevent selection of the control.
+ * Default is false.
+ */
+ allowSelection: false,
+
+ /**
+ * Property: displayClass
+ * {string} This property is used for CSS related to the drawing of the
+ * Control.
+ */
+ displayClass: "",
+
+ /**
+ * APIProperty: title
+ * {string} This property is used for showing a tooltip over the
+ * Control.
+ */
+ title: "",
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * false.
+ */
+ autoActivate: false,
+
+ /**
+ * APIProperty: active
+ * {Boolean} The control is active (read-only). Use <activate> and
+ * <deactivate> to change control state.
+ */
+ active: null,
+
+ /**
+ * Property: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+ handlerOptions: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler>} null
+ */
+ handler: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to control.events.object (a reference
+ * to the control).
+ * element - {DOMElement} A reference to control.events.element (which
+ * will be null unless documented otherwise).
+ *
+ * Supported map event types:
+ * activate - Triggered when activated.
+ * deactivate - Triggered when deactivated.
+ */
+ events: null,
+
+ /**
+ * Constructor: OpenLayers.Control
+ * Create an OpenLayers Control. The options passed as a parameter
+ * directly extend the control. For example passing the following:
+ *
+ * > var control = new OpenLayers.Control({div: myDiv});
+ *
+ * Overrides the default div attribute value of null.
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function (options) {
+ // We do this before the extend so that instances can override
+ // className in options.
+ this.displayClass =
+ this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ if (this.id == null) {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function () {
+ if(this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.events = null;
+ }
+ this.eventListeners = null;
+
+ // eliminate circular references
+ if (this.handler) {
+ this.handler.destroy();
+ this.handler = null;
+ }
+ if(this.handlers) {
+ for(var key in this.handlers) {
+ if(this.handlers.hasOwnProperty(key) &&
+ typeof this.handlers[key].destroy == "function") {
+ this.handlers[key].destroy();
+ }
+ }
+ this.handlers = null;
+ }
+ if (this.map) {
+ this.map.removeControl(this);
+ this.map = null;
+ }
+ this.div = null;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.map = map;
+ if (this.handler) {
+ this.handler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: draw
+ * The draw method is called when the control is ready to be displayed
+ * on the page. If a div has not been created one is created. Controls
+ * with a visual component will almost always want to override this method
+ * to customize the look of control.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The top-left pixel position of the control
+ * or null.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function (px) {
+ if (this.div == null) {
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.className = this.displayClass;
+ if (!this.allowSelection) {
+ this.div.className += " olControlNoSelect";
+ this.div.setAttribute("unselectable", "on", 0);
+ this.div.onselectstart = OpenLayers.Function.False;
+ }
+ if (this.title != "") {
+ this.div.title = this.title;
+ }
+ }
+ if (px != null) {
+ this.position = px.clone();
+ }
+ this.moveTo(this.position);
+ return this.div;
+ },
+
+ /**
+ * Method: moveTo
+ * Sets the left and top style attributes to the passed in pixel
+ * coordinates.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Explicitly activates a control and it's associated
+ * handler if one has been set. Controls can be
+ * deactivated by calling the deactivate() method.
+ *
+ * Returns:
+ * {Boolean} True if the control was successfully activated or
+ * false if the control was already active.
+ */
+ activate: function () {
+ if (this.active) {
+ return false;
+ }
+ if (this.handler) {
+ this.handler.activate();
+ }
+ this.active = true;
+ if(this.map) {
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("activate");
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates a control and it's associated handler if any. The exact
+ * effect of this depends on the control itself.
+ *
+ * Returns:
+ * {Boolean} True if the control was effectively deactivated or false
+ * if the control was already inactive.
+ */
+ deactivate: function () {
+ if (this.active) {
+ if (this.handler) {
+ this.handler.deactivate();
+ }
+ this.active = false;
+ if(this.map) {
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("deactivate");
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Control"
+});
+
+/**
+ * Constant: OpenLayers.Control.TYPE_BUTTON
+ */
+OpenLayers.Control.TYPE_BUTTON = 1;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOGGLE
+ */
+OpenLayers.Control.TYPE_TOGGLE = 2;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOOL
+ */
+OpenLayers.Control.TYPE_TOOL = 3;
+/* ======================================================================
+ OpenLayers/Geometry.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object. Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor. This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ *
+ * Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
+ * explicitly include the OpenLayers.Format.WKT in your build.
+ */
+OpenLayers.Geometry = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique identifier for this geometry.
+ */
+ id: null,
+
+ /**
+ * Property: parent
+ * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+ * of another geometry
+ */
+ parent: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The bounds of this geometry
+ */
+ bounds: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry
+ * Creates a geometry object.
+ */
+ initialize: function() {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
+ },
+
+ /**
+ * Method: destroy
+ * Destroy this geometry.
+ */
+ destroy: function() {
+ this.id = null;
+ this.bounds = null;
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this geometry. Does not set any non-standard
+ * properties of the cloned geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} An exact clone of this geometry.
+ */
+ clone: function() {
+ return new OpenLayers.Geometry();
+ },
+
+ /**
+ * Method: setBounds
+ * Set the bounds for this Geometry.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ if (bounds) {
+ this.bounds = bounds.clone();
+ }
+ },
+
+ /**
+ * Method: clearBounds
+ * Nullify this components bounds and that of its parent as well.
+ */
+ clearBounds: function() {
+ this.bounds = null;
+ if (this.parent) {
+ this.parent.clearBounds();
+ }
+ },
+
+ /**
+ * Method: extendBounds
+ * Extend the existing bounds to include the new bounds.
+ * If geometry's bounds is not yet set, then set a new Bounds.
+ *
+ * Parameters:
+ * newBounds - {<OpenLayers.Bounds>}
+ */
+ extendBounds: function(newBounds){
+ var bounds = this.getBounds();
+ if (!bounds) {
+ this.setBounds(newBounds);
+ } else {
+ this.bounds.extend(newBounds);
+ }
+ },
+
+ /**
+ * APIMethod: getBounds
+ * Get the bounds for this Geometry. If bounds is not set, it
+ * is calculated again, this makes queries faster.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getBounds: function() {
+ if (this.bounds == null) {
+ this.calculateBounds();
+ }
+ return this.bounds;
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds for the geometry.
+ */
+ calculateBounds: function() {
+ //
+ // This should be overridden by subclasses.
+ //
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options depend on the specific geometry type.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ },
+
+ /**
+ * Method: atPoint
+ * Note - This is only an approximation based on the bounds of the
+ * geometry.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the geometry is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ var bounds = this.getBounds();
+ if ((bounds != null) && (lonlat != null)) {
+
+ var dX = (toleranceLon != null) ? toleranceLon : 0;
+ var dY = (toleranceLat != null) ? toleranceLat : 0;
+
+ var toleranceBounds =
+ new OpenLayers.Bounds(this.bounds.left - dX,
+ this.bounds.bottom - dY,
+ this.bounds.right + dX,
+ this.bounds.top + dY);
+
+ atPoint = toleranceBounds.containsLonLat(lonlat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: getLength
+ * Calculate the length of this geometry. This method is defined in
+ * subclasses.
+ *
+ * Returns:
+ * {Float} The length of the collection by summing its parts
+ */
+ getLength: function() {
+ //to be overridden by geometries that actually have a length
+ //
+ return 0.0;
+ },
+
+ /**
+ * Method: getArea
+ * Calculate the area of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ //to be overridden by geometries that actually have an area
+ //
+ return 0.0;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ * Calculate the centroid of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return null;
+ },
+
+ /**
+ * Method: toString
+ * Returns a text representation of the geometry. If the WKT format is
+ * included in a build, this will be the Well-Known Text
+ * representation.
+ *
+ * Returns:
+ * {String} String representation of this geometry.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ string = OpenLayers.Format.WKT.prototype.write(
+ new OpenLayers.Feature.Vector(this)
+ );
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry"
+});
+
+/**
+ * Function: OpenLayers.Geometry.fromWKT
+ * Generate a geometry given a Well-Known Text string. For this method to
+ * work, you must include the OpenLayers.Format.WKT in your build
+ * explicitly.
+ *
+ * Parameters:
+ * wkt - {String} A string representing the geometry in Well-Known Text.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry of the appropriate class.
+ */
+OpenLayers.Geometry.fromWKT = function(wkt) {
+ var geom;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ var format = OpenLayers.Geometry.fromWKT.format;
+ if (!format) {
+ format = new OpenLayers.Format.WKT();
+ OpenLayers.Geometry.fromWKT.format = format;
+ }
+ var result = format.read(wkt);
+ if (result instanceof OpenLayers.Feature.Vector) {
+ geom = result.geometry;
+ } else if (OpenLayers.Util.isArray(result)) {
+ var len = result.length;
+ var components = new Array(len);
+ for (var i=0; i<len; ++i) {
+ components[i] = result[i].geometry;
+ }
+ geom = new OpenLayers.Geometry.Collection(components);
+ }
+ }
+ return geom;
+};
+
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect. Optionally calculates
+ * and returns the intersection point. This function is optimized for
+ * cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1. In those
+ * obvious cases where there is no intersection, the function should
+ * not be called.
+ *
+ * Parameters:
+ * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * options - {Object} Optional properties for calculating the intersection.
+ *
+ * Valid options:
+ * point - {Boolean} Return the intersection point. If false, the actual
+ * intersection point will not be calculated. If true and the segments
+ * intersect, the intersection point will be returned. If true and
+ * the segments do not intersect, false will be returned. If true and
+ * the segments are coincident, true will be returned.
+ * tolerance - {Number} If a non-null value is provided, if the segments are
+ * within the tolerance distance, this will be considered an intersection.
+ * In addition, if the point option is true and the calculated intersection
+ * is within the tolerance distance of an end point, the endpoint will be
+ * returned instead of the calculated intersection. Further, if the
+ * intersection is within the tolerance of endpoints on both segments, or
+ * if two segment endpoints are within the tolerance distance of eachother
+ * (but no intersection is otherwise calculated), an endpoint on the
+ * first segment provided will be returned.
+ *
+ * Returns:
+ * {Boolean | <OpenLayers.Geometry.Point>} The two segments intersect.
+ * If the point argument is true, the return will be the intersection
+ * point or false if none exists. If point is true and the segments
+ * are coincident, return will be true (and the instersection is equal
+ * to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
+ var point = options && options.point;
+ var tolerance = options && options.tolerance;
+ var intersection = false;
+ var x11_21 = seg1.x1 - seg2.x1;
+ var y11_21 = seg1.y1 - seg2.y1;
+ var x12_11 = seg1.x2 - seg1.x1;
+ var y12_11 = seg1.y2 - seg1.y1;
+ var y22_21 = seg2.y2 - seg2.y1;
+ var x22_21 = seg2.x2 - seg2.x1;
+ var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+ var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+ var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+ if(d == 0) {
+ // parallel
+ if(n1 == 0 && n2 == 0) {
+ // coincident
+ intersection = true;
+ }
+ } else {
+ var along1 = n1 / d;
+ var along2 = n2 / d;
+ if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+ // intersect
+ if(!point) {
+ intersection = true;
+ } else {
+ // calculate the intersection point
+ var x = seg1.x1 + (along1 * x12_11);
+ var y = seg1.y1 + (along1 * y12_11);
+ intersection = new OpenLayers.Geometry.Point(x, y);
+ }
+ }
+ }
+ if(tolerance) {
+ var dist;
+ if(intersection) {
+ if(point) {
+ var segs = [seg1, seg2];
+ var seg, x, y;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ seg = segs[i];
+ for(var j=1; j<3; ++j) {
+ x = seg["x" + j];
+ y = seg["y" + j];
+ dist = Math.sqrt(
+ Math.pow(x - intersection.x, 2) +
+ Math.pow(y - intersection.y, 2)
+ );
+ if(dist < tolerance) {
+ intersection.x = x;
+ intersection.y = y;
+ break outer;
+ }
+ }
+ }
+
+ }
+ } else {
+ // no calculated intersection, but segments could be within
+ // the tolerance of one another
+ var segs = [seg1, seg2];
+ var source, target, x, y, p, result;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ source = segs[i];
+ target = segs[(i+1)%2];
+ for(var j=1; j<3; ++j) {
+ p = {x: source["x"+j], y: source["y"+j]};
+ result = OpenLayers.Geometry.distanceToSegment(p, target);
+ if(result.distance < tolerance) {
+ if(point) {
+ intersection = new OpenLayers.Geometry.Point(p.x, p.y);
+ } else {
+ intersection = true;
+ }
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ return intersection;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceToSegment
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with distance, along, x, and y properties. The distance
+ * will be the shortest distance between the input point and segment.
+ * The x and y properties represent the coordinates along the segment
+ * where the shortest distance meets the segment. The along attribute
+ * describes how far between the two segment points the given point is.
+ */
+OpenLayers.Geometry.distanceToSegment = function(point, segment) {
+ var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
+ result.distance = Math.sqrt(result.distance);
+ return result;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceSquaredToSegment
+ *
+ * Usually the distanceToSegment function should be used. This variant however
+ * can be used for comparisons where the exact distance is not important.
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with squared distance, along, x, and y properties.
+ * The distance will be the shortest distance between the input point and
+ * segment. The x and y properties represent the coordinates along the
+ * segment where the shortest distance meets the segment. The along
+ * attribute describes how far between the two segment points the given
+ * point is.
+ */
+OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
+ var x0 = point.x;
+ var y0 = point.y;
+ var x1 = segment.x1;
+ var y1 = segment.y1;
+ var x2 = segment.x2;
+ var y2 = segment.y2;
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
+ (Math.pow(dx, 2) + Math.pow(dy, 2));
+ var x, y;
+ if(along <= 0.0) {
+ x = x1;
+ y = y1;
+ } else if(along >= 1.0) {
+ x = x2;
+ y = y2;
+ } else {
+ x = x1 + along * dx;
+ y = y1 + along * dy;
+ }
+ return {
+ distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
+ x: x, y: y,
+ along: along
+ };
+};
+/* ======================================================================
+ OpenLayers/Geometry/Collection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different
+ * Geometries. These are stored in the local parameter <components> (which
+ * can be passed as a parameter to the constructor).
+ *
+ * As new geometries are added to the collection, they are NOT cloned.
+ * When removing geometries, they need to be specified by reference (ie you
+ * have to pass in the *exact* geometry to be removed).
+ *
+ * The <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: components
+ * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
+ */
+ components: null,
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Collection
+ * Creates a Geometry Collection -- a list of geoms.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
+ *
+ */
+ initialize: function (components) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+ this.components = [];
+ if (components != null) {
+ this.addComponents(components);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this geometry.
+ */
+ destroy: function () {
+ this.components.length = 0;
+ this.components = null;
+ OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ * Clone this geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+ */
+ clone: function() {
+ var geometry = eval("new " + this.CLASS_NAME + "()");
+ for(var i=0, len=this.components.length; i<len; i++) {
+ geometry.addComponent(this.components[i].clone());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(geometry, this);
+
+ return geometry;
+ },
+
+ /**
+ * Method: getComponentsString
+ * Get a string representing the components for this collection
+ *
+ * Returns:
+ * {String} A string representation of the components of this geometry
+ */
+ getComponentsString: function(){
+ var strings = [];
+ for(var i=0, len=this.components.length; i<len; i++) {
+ strings.push(this.components[i].toShortString());
+ }
+ return strings.join(",");
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds by iterating through the components and
+ * calling calling extendBounds() on each item.
+ */
+ calculateBounds: function() {
+ this.bounds = null;
+ var bounds = new OpenLayers.Bounds();
+ var components = this.components;
+ if (components) {
+ for (var i=0, len=components.length; i<len; i++) {
+ bounds.extend(components[i].getBounds());
+ }
+ }
+ // to preserve old behavior, we only set bounds if non-null
+ // in the future, we could add bounds.isEmpty()
+ if (bounds.left != null && bounds.bottom != null &&
+ bounds.right != null && bounds.top != null) {
+ this.setBounds(bounds);
+ }
+ },
+
+ /**
+ * APIMethod: addComponents
+ * Add components to this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+ */
+ addComponents: function(components){
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=0, len=components.length; i<len; i++) {
+ this.addComponent(components[i]);
+ }
+ },
+
+ /**
+ * Method: addComponent
+ * Add a new component (geometry) to the collection. If this.componentTypes
+ * is set, then the component class name must be in the componentTypes array.
+ *
+ * The bounds cache is reset.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>} A geometry to add
+ * index - {int} Optional index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} The component geometry was successfully added
+ */
+ addComponent: function(component, index) {
+ var added = false;
+ if(component) {
+ if(this.componentTypes == null ||
+ (OpenLayers.Util.indexOf(this.componentTypes,
+ component.CLASS_NAME) > -1)) {
+
+ if(index != null && (index < this.components.length)) {
+ var components1 = this.components.slice(0, index);
+ var components2 = this.components.slice(index,
+ this.components.length);
+ components1.push(component);
+ this.components = components1.concat(components2);
+ } else {
+ this.components.push(component);
+ }
+ component.parent = this;
+ this.clearBounds();
+ added = true;
+ }
+ }
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponents
+ * Remove components from this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} The components to be removed
+ *
+ * Returns:
+ * {Boolean} A component was removed.
+ */
+ removeComponents: function(components) {
+ var removed = false;
+
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=components.length-1; i>=0; --i) {
+ removed = this.removeComponent(components[i]) || removed;
+ }
+ return removed;
+ },
+
+ /**
+ * Method: removeComponent
+ * Remove a component from this geometry.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(component) {
+
+ OpenLayers.Util.removeItem(this.components, component);
+
+ // clearBounds() so that it gets recalculated on the next call
+ // to this.getBounds();
+ this.clearBounds();
+ return true;
+ },
+
+ /**
+ * APIMethod: getLength
+ * Calculate the length of this geometry
+ *
+ * Returns:
+ * {Float} The length of the geometry
+ */
+ getLength: function() {
+ var length = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getLength();
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getArea
+ * Calculate the area of this geometry. Note how this function is overridden
+ * in <OpenLayers.Geometry.Polygon>.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ var area = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getArea();
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the geometry in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getGeodesicArea(projection);
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Compute the centroid for this geometry collection.
+ *
+ * Parameters:
+ * weighted - {Boolean} Perform the getCentroid computation recursively,
+ * returning an area weighted average of all geometries in this collection.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function(weighted) {
+ if (!weighted) {
+ return this.components.length && this.components[0].getCentroid();
+ }
+ var len = this.components.length;
+ if (!len) {
+ return false;
+ }
+
+ var areas = [];
+ var centroids = [];
+ var areaSum = 0;
+ var minArea = Number.MAX_VALUE;
+ var component;
+ for (var i=0; i<len; ++i) {
+ component = this.components[i];
+ var area = component.getArea();
+ var centroid = component.getCentroid(true);
+ if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
+ continue;
+ }
+ areas.push(area);
+ areaSum += area;
+ minArea = (area < minArea && area > 0) ? area : minArea;
+ centroids.push(centroid);
+ }
+ len = areas.length;
+ if (areaSum === 0) {
+ // all the components in this collection have 0 area
+ // probably a collection of points -- weight all the points the same
+ for (var i=0; i<len; ++i) {
+ areas[i] = 1;
+ }
+ areaSum = areas.length;
+ } else {
+ // normalize all the areas where the smallest area will get
+ // a value of 1
+ for (var i=0; i<len; ++i) {
+ areas[i] /= minArea;
+ }
+ areaSum /= minArea;
+ }
+
+ var xSum = 0, ySum = 0, centroid, area;
+ for (var i=0; i<len; ++i) {
+ centroid = centroids[i];
+ area = areas[i];
+ xSum += centroid.x * area;
+ ySum += centroid.y * area;
+ }
+
+ return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var length = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getGeodesicLength(projection);
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i=0, len=this.components.length; i<len; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0; i<this.components.length; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best, distance;
+ var min = Number.POSITIVE_INFINITY;
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ result = this.components[i].distanceTo(geometry, options);
+ distance = details ? result.distance : result;
+ if(distance < min) {
+ min = distance;
+ best = result;
+ if(min == 0) {
+ break;
+ }
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geometry) {
+ var equivalent = true;
+ if(!geometry || !geometry.CLASS_NAME ||
+ (this.CLASS_NAME != geometry.CLASS_NAME)) {
+ equivalent = false;
+ } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
+ (geometry.components.length != this.components.length)) {
+ equivalent = false;
+ } else {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ if(!this.components[i].equals(geometry.components[i])) {
+ equivalent = false;
+ break;
+ }
+ }
+ }
+ return equivalent;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ for(var i=0, len=this.components.length; i<len; ++ i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices = [];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ Array.prototype.push.apply(
+ vertices, this.components[i].getVertices(nodes)
+ );
+ }
+ return vertices;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Point.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Point
+ * Point geometry class.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: x
+ * {float}
+ */
+ x: null,
+
+ /**
+ * APIProperty: y
+ * {float}
+ */
+ y: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Point
+ * Construct a point geometry.
+ *
+ * Parameters:
+ * x - {float}
+ * y - {float}
+ *
+ */
+ initialize: function(x, y) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Geometry.Point(this.x, this.y);
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ return obj;
+ },
+
+ /**
+ * Method: calculateBounds
+ * Create a new Bounds based on the lon/lat
+ */
+ calculateBounds: function () {
+ this.bounds = new OpenLayers.Bounds(this.x, this.y,
+ this.x, this.y);
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var distance, x0, y0, x1, y1, result;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ x0 = this.x;
+ y0 = this.y;
+ x1 = geometry.x;
+ y1 = geometry.y;
+ distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
+ result = !details ?
+ distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
+ } else {
+ result = geometry.distanceTo(this, options);
+ if(details) {
+ // switch coord order since this geom is target
+ result = {
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0,
+ distance: result.distance
+ };
+ }
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geom - {<OpenLayers.Geometry.Point>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geom) {
+ var equals = false;
+ if (geom != null) {
+ equals = ((this.x == geom.x && this.y == geom.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * Method: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of Point object.
+ * (ex. <i>"5, 42"</i>)
+ */
+ toShortString: function() {
+ return (this.x + ", " + this.y);
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ this.x = this.x + x;
+ this.y = this.y + y;
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a point around another.
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ angle *= Math.PI / 180;
+ var radius = this.distanceTo(origin);
+ var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+ this.x = origin.x + (radius * Math.cos(theta));
+ this.y = origin.y + (radius * Math.sin(theta));
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return new OpenLayers.Geometry.Point(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a point relative to some origin. For points, this has the effect
+ * of scaling a vector (from the origin to the point). This method is
+ * more useful on geometry collection subclasses.
+ *
+ * Parameters:
+ * scale - {Float} Ratio of the new distance from the origin to the old
+ * distance from the origin. A scale of 2 doubles the
+ * distance between the point and origin.
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ ratio = (ratio == undefined) ? 1 : ratio;
+ this.x = origin.x + (scale * ratio * (this.x - origin.x));
+ this.y = origin.y + (scale * (this.y - origin.y));
+ this.clearBounds();
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.equals(geometry);
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: transform
+ * Translate the x,y properties of the point from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if ((source && dest)) {
+ OpenLayers.Projection.transform(
+ this, source, dest);
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return [this];
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Point"
+});
+/* ======================================================================
+ OpenLayers/Geometry/MultiPoint.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points. Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPoint
+ * Create a new MultiPoint Geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>}
+ */
+
+ /**
+ * APIMethod: addPoint
+ * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be added
+ * index - {Integer} Optional index
+ */
+ addPoint: function(point, index) {
+ this.addComponent(point, index);
+ },
+
+ /**
+ * APIMethod: removePoint
+ * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be removed
+ */
+ removePoint: function(point){
+ this.removeComponent(point);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
+});
+/* ======================================================================
+ OpenLayers/Geometry/Curve.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Curve
+ * A Curve is a MultiPoint, whose points are assumed to be connected. To
+ * this end, we provide a "getLength()" function, which iterates through
+ * the points, summing the distances between them.
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.MultiPoint>
+ */
+OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Curve
+ *
+ * Parameters:
+ * point - {Array(<OpenLayers.Geometry.Point>)}
+ */
+
+ /**
+ * APIMethod: getLength
+ *
+ * Returns:
+ * {Float} The length of the curve
+ */
+ getLength: function() {
+ var length = 0.0;
+ if ( this.components && (this.components.length > 1)) {
+ for(var i=1, len=this.components.length; i<len; i++) {
+ length += this.components[i-1].distanceTo(this.components[i]);
+ }
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var geom = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ geom = this.clone().transform(projection, gg);
+ }
+ }
+ var length = 0.0;
+ if(geom.components && (geom.components.length > 1)) {
+ var p1, p2;
+ for(var i=1, len=geom.components.length; i<len; i++) {
+ p1 = geom.components[i-1];
+ p2 = geom.components[i];
+ // this returns km and requires lon/lat properties
+ length += OpenLayers.Util.distVincenty(
+ {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
+ );
+ }
+ }
+ // convert to m
+ return length * 1000;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can
+ * never be less than two points long.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+ /**
+ * Constructor: OpenLayers.Geometry.LineString
+ * Create a new LineString geometry
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
+ * generate the linestring
+ *
+ */
+
+ /**
+ * APIMethod: removeComponent
+ * Only allows removal of a point if there are three or more points in
+ * the linestring. (otherwise the result would be just a single point)
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The point to be removed
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 2);
+ if (removed) {
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Test for instersection between two geometries. This is a cheapo
+ * implementation of the Bently-Ottmann algorigithm. It doesn't
+ * really keep track of a sweep line data structure. It is closer
+ * to the brute force method, except that segments are sorted and
+ * potential intersections are only calculated when bounding boxes
+ * intersect.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this geometry.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var type = geometry.CLASS_NAME;
+ if(type == "OpenLayers.Geometry.LineString" ||
+ type == "OpenLayers.Geometry.LinearRing" ||
+ type == "OpenLayers.Geometry.Point") {
+ var segs1 = this.getSortedSegments();
+ var segs2;
+ if(type == "OpenLayers.Geometry.Point") {
+ segs2 = [{
+ x1: geometry.x, y1: geometry.y,
+ x2: geometry.x, y2: geometry.y
+ }];
+ } else {
+ segs2 = geometry.getSortedSegments();
+ }
+ var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
+ seg2, seg2y1, seg2y2;
+ // sweep right
+ outer: for(var i=0, len=segs1.length; i<len; ++i) {
+ seg1 = segs1[i];
+ seg1x1 = seg1.x1;
+ seg1x2 = seg1.x2;
+ seg1y1 = seg1.y1;
+ seg1y2 = seg1.y2;
+ inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+ seg2 = segs2[j];
+ if(seg2.x1 > seg1x2) {
+ // seg1 still left of seg2
+ break;
+ }
+ if(seg2.x2 < seg1x1) {
+ // seg2 still left of seg1
+ continue;
+ }
+ seg2y1 = seg2.y1;
+ seg2y2 = seg2.y2;
+ if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
+ // seg2 above seg1
+ continue;
+ }
+ if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
+ // seg2 below seg1
+ continue;
+ }
+ if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
+ intersect = true;
+ break outer;
+ }
+ }
+ }
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * Method: getSortedSegments
+ *
+ * Returns:
+ * {Array} An array of segment objects. Segment objects have properties
+ * x1, y1, x2, and y2. The start point is represented by x1 and y1.
+ * The end point is represented by x2 and y2. Start and end are
+ * ordered so that x1 < x2.
+ */
+ getSortedSegments: function() {
+ var numSeg = this.components.length - 1;
+ var segments = new Array(numSeg), point1, point2;
+ for(var i=0; i<numSeg; ++i) {
+ point1 = this.components[i];
+ point2 = this.components[i + 1];
+ if(point1.x < point2.x) {
+ segments[i] = {
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y
+ };
+ } else {
+ segments[i] = {
+ x1: point2.x,
+ y1: point2.y,
+ x2: point1.x,
+ y2: point1.y
+ };
+ }
+ }
+ // more efficient to define this somewhere static
+ function byX1(seg1, seg2) {
+ return seg1.x1 - seg2.x1;
+ }
+ return segments.sort(byX1);
+ },
+
+ /**
+ * Method: splitWithSegment
+ * Split this geometry with the given segment.
+ *
+ * Parameters:
+ * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
+ * segment endpoint coordinates.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source segment must be within the
+ * tolerance distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of one of the source segment's
+ * endpoints will be assumed to occur at the endpoint.
+ *
+ * Returns:
+ * {Object} An object with *lines* and *points* properties. If the given
+ * segment intersects this linestring, the lines array will reference
+ * geometries that result from the split. The points array will contain
+ * all intersection points. Intersection points are sorted along the
+ * segment (in order from x1,y1 to x2,y2).
+ */
+ splitWithSegment: function(seg, options) {
+ var edge = !(options && options.edge === false);
+ var tolerance = options && options.tolerance;
+ var lines = [];
+ var verts = this.getVertices();
+ var points = [];
+ var intersections = [];
+ var split = false;
+ var vert1, vert2, point;
+ var node, vertex, target;
+ var interOptions = {point: true, tolerance: tolerance};
+ var result = null;
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ points.push(vert1.clone());
+ vert2 = verts[i+1];
+ target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
+ point = OpenLayers.Geometry.segmentsIntersect(
+ seg, target, interOptions
+ );
+ if(point instanceof OpenLayers.Geometry.Point) {
+ if((point.x === seg.x1 && point.y === seg.y1) ||
+ (point.x === seg.x2 && point.y === seg.y2) ||
+ point.equals(vert1) || point.equals(vert2)) {
+ vertex = true;
+ } else {
+ vertex = false;
+ }
+ if(vertex || edge) {
+ // push intersections different than the previous
+ if(!point.equals(intersections[intersections.length-1])) {
+ intersections.push(point.clone());
+ }
+ if(i === 0) {
+ if(point.equals(vert1)) {
+ continue;
+ }
+ }
+ if(point.equals(vert2)) {
+ continue;
+ }
+ split = true;
+ if(!point.equals(vert1)) {
+ points.push(point);
+ }
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ points = [point.clone()];
+ }
+ }
+ }
+ if(split) {
+ points.push(vert2.clone());
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ }
+ if(intersections.length > 0) {
+ // sort intersections along segment
+ var xDir = seg.x1 < seg.x2 ? 1 : -1;
+ var yDir = seg.y1 < seg.y2 ? 1 : -1;
+ result = {
+ lines: lines,
+ points: intersections.sort(function(p1, p2) {
+ return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
+ })
+ };
+ }
+ return result;
+ },
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(target, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var sourceSplit, targetSplit, sourceParts, targetParts;
+ if(target instanceof OpenLayers.Geometry.LineString) {
+ var verts = this.getVertices();
+ var vert1, vert2, seg, splits, lines, point;
+ var points = [];
+ sourceParts = [];
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ vert2 = verts[i+1];
+ seg = {
+ x1: vert1.x, y1: vert1.y,
+ x2: vert2.x, y2: vert2.y
+ };
+ targetParts = targetParts || [target];
+ if(mutual) {
+ points.push(vert1.clone());
+ }
+ for(var j=0; j<targetParts.length; ++j) {
+ splits = targetParts[j].splitWithSegment(seg, options);
+ if(splits) {
+ // splice in new features
+ lines = splits.lines;
+ if(lines.length > 0) {
+ lines.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, lines);
+ j += lines.length - 2;
+ }
+ if(mutual) {
+ for(var k=0, len=splits.points.length; k<len; ++k) {
+ point = splits.points[k];
+ if(!point.equals(vert1)) {
+ points.push(point);
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ if(point.equals(vert2)) {
+ points = [];
+ } else {
+ points = [point.clone()];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(mutual && sourceParts.length > 0 && points.length > 0) {
+ points.push(vert2.clone());
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ }
+ } else {
+ results = target.splitWith(this, options);
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetSplit || sourceSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ return geometry.split(this, options);
+
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices;
+ if(nodes === true) {
+ vertices = [
+ this.components[0],
+ this.components[this.components.length-1]
+ ];
+ } else if (nodes === false) {
+ vertices = this.components.slice(1, this.components.length-1);
+ } else {
+ vertices = this.components.slice();
+ }
+ return vertices;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best = {};
+ var min = Number.POSITIVE_INFINITY;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ var segs = this.getSortedSegments();
+ var x = geometry.x;
+ var y = geometry.y;
+ var seg;
+ for(var i=0, len=segs.length; i<len; ++i) {
+ seg = segs[i];
+ result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
+ if(result.distance < min) {
+ min = result.distance;
+ best = result;
+ if(min === 0) {
+ break;
+ }
+ } else {
+ // if distance increases and we cross y0 to the right of x0, no need to keep looking.
+ if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {
+ break;
+ }
+ }
+ }
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x, y0: best.y,
+ x1: x, y1: y
+ };
+ } else {
+ best = best.distance;
+ }
+ } else if(geometry instanceof OpenLayers.Geometry.LineString) {
+ var segs0 = this.getSortedSegments();
+ var segs1 = geometry.getSortedSegments();
+ var seg0, seg1, intersection, x0, y0;
+ var len1 = segs1.length;
+ var interOptions = {point: true};
+ outer: for(var i=0, len=segs0.length; i<len; ++i) {
+ seg0 = segs0[i];
+ x0 = seg0.x1;
+ y0 = seg0.y1;
+ for(var j=0; j<len1; ++j) {
+ seg1 = segs1[j];
+ intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
+ if(intersection) {
+ min = 0;
+ best = {
+ distance: 0,
+ x0: intersection.x, y0: intersection.y,
+ x1: intersection.x, y1: intersection.y
+ };
+ break outer;
+ } else {
+ result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
+ if(result.distance < min) {
+ min = result.distance;
+ best = {
+ distance: min,
+ x0: x0, y0: y0,
+ x1: result.x, y1: result.y
+ };
+ }
+ }
+ }
+ }
+ if(!details) {
+ best = best.distance;
+ }
+ if(min !== 0) {
+ // check the final vertex in this line's sorted segments
+ if(seg0) {
+ result = geometry.distanceTo(
+ new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
+ options
+ );
+ var dist = details ? result.distance : result;
+ if(dist < min) {
+ if(details) {
+ best = {
+ distance: min,
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0
+ };
+ } else {
+ best = dist;
+ }
+ }
+ }
+ }
+ } else {
+ best = geometry.distanceTo(this, options);
+ // swap since target comes from this line
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x1, y0: best.y1,
+ x1: best.x0, y1: best.y0
+ };
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: simplify
+ * This function will return a simplified LineString.
+ * Simplification is based on the Douglas-Peucker algorithm.
+ *
+ *
+ * Parameters:
+ * tolerance - {number} threshhold for simplification in map units
+ *
+ * Returns:
+ * {OpenLayers.Geometry.LineString} the simplified LineString
+ */
+ simplify: function(tolerance){
+ if (this && this !== null) {
+ var points = this.getVertices();
+ if (points.length < 3) {
+ return this;
+ }
+
+ var compareNumbers = function(a, b){
+ return (a-b);
+ };
+
+ /**
+ * Private function doing the Douglas-Peucker reduction
+ */
+ var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
+ var maxDistance = 0;
+ var indexFarthest = 0;
+
+ for (var index = firstPoint, distance; index < lastPoint; index++) {
+ distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ indexFarthest = index;
+ }
+ }
+
+ if (maxDistance > tolerance && indexFarthest != firstPoint) {
+ //Add the largest point that exceeds the tolerance
+ pointIndexsToKeep.push(indexFarthest);
+ douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
+ douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
+ }
+ };
+
+ /**
+ * Private function calculating the perpendicular distance
+ * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
+ */
+ var perpendicularDistance = function(point1, point2, point){
+ //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
+ //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
+ //Area = .5*Base*H *Solve for height
+ //Height = Area/.5/Base
+
+ var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
+ var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
+ var height = area / bottom * 2;
+
+ return height;
+ };
+
+ var firstPoint = 0;
+ var lastPoint = points.length - 1;
+ var pointIndexsToKeep = [];
+
+ //Add the first and last index to the keepers
+ pointIndexsToKeep.push(firstPoint);
+ pointIndexsToKeep.push(lastPoint);
+
+ //The first and the last point cannot be the same
+ while (points[firstPoint].equals(points[lastPoint])) {
+ lastPoint--;
+ //Addition: the first point not equal to first point in the LineString is kept as well
+ pointIndexsToKeep.push(lastPoint);
+ }
+
+ douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
+ var returnPoints = [];
+ pointIndexsToKeep.sort(compareNumbers);
+ for (var index = 0; index < pointIndexsToKeep.length; index++) {
+ returnPoints.push(points[pointIndexsToKeep[index]]);
+ }
+ return new OpenLayers.Geometry.LineString(returnPoints);
+
+ }
+ else {
+ return this;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
+/* ======================================================================
+ OpenLayers/Geometry/LinearRing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ *
+ * A Linear Ring is a special LineString which is closed. It closes itself
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point.
+ *
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.LineString>
+ */
+OpenLayers.Geometry.LinearRing = OpenLayers.Class(
+ OpenLayers.Geometry.LineString, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.LinearRing
+ * Linear rings are constructed with an array of points. This array
+ * can represent a closed or open ring. If the ring is open (the last
+ * point does not equal the first point), the constructor will close
+ * the ring. If the ring is already closed (the last point does equal
+ * the first point), it will be left closed.
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} points
+ */
+
+ /**
+ * APIMethod: addComponent
+ * Adds a point to geometry components. If the point is to be added to
+ * the end of the components array and it is the same as the last point
+ * already in that array, the duplicate point is not added. This has
+ * the effect of closing the ring if it is not already closed, and
+ * doing the right thing if it is already closed. This behavior can
+ * be overridden by calling the method with a non-null index as the
+ * second argument.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * index - {Integer} Index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} Was the Point successfully added?
+ */
+ addComponent: function(point, index) {
+ var added = false;
+
+ //remove last point
+ var lastPoint = this.components.pop();
+
+ // given an index, add the point
+ // without an index only add non-duplicate points
+ if(index != null || !point.equals(lastPoint)) {
+ added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ arguments);
+ }
+
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponent
+ * Removes a point from geometry components.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 3);
+ if (removed) {
+ //remove last point
+ this.components.pop();
+
+ //remove our point
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i = 0, len=this.components.length; i<len - 1; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ if (this.components) {
+ var len = this.components.length;
+ if (len > 0 && len <= 2) {
+ return this.components[0].clone();
+ } else if (len > 2) {
+ var sumX = 0.0;
+ var sumY = 0.0;
+ var x0 = this.components[0].x;
+ var y0 = this.components[0].y;
+ var area = -1 * this.getArea();
+ if (area != 0) {
+ for (var i = 0; i < len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ }
+ var x = x0 + sumX / (6 * area);
+ var y = y0 + sumY / (6 * area);
+ } else {
+ for (var i = 0; i < len - 1; i++) {
+ sumX += this.components[i].x;
+ sumY += this.components[i].y;
+ }
+ var x = sumX / (len - 1);
+ var y = sumY / (len - 1);
+ }
+ return new OpenLayers.Geometry.Point(x, y);
+ } else {
+ return null;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getArea
+ * Note - The area is positive if the ring is oriented CW, otherwise
+ * it will be negative.
+ *
+ * Returns:
+ * {Float} The signed area for a ring.
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 2)) {
+ var sum = 0.0;
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sum += (b.x + c.x) * (c.y - b.y);
+ }
+ area = - sum / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth. Note that this area will be positive if ring is oriented
+ * clockwise, otherwise it will be negative.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate signed geodesic area of the polygon in square
+ * meters.
+ */
+ getGeodesicArea: function(projection) {
+ var ring = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ ring = this.clone().transform(projection, gg);
+ }
+ }
+ var area = 0.0;
+ var len = ring.components && ring.components.length;
+ if(len > 2) {
+ var p1, p2;
+ for(var i=0; i<len-1; i++) {
+ p1 = ring.components[i];
+ p2 = ring.components[i+1];
+ area += OpenLayers.Util.rad(p2.x - p1.x) *
+ (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
+ Math.sin(OpenLayers.Util.rad(p2.y)));
+ }
+ area = area * 6378137.0 * 6378137.0 / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a linear ring. For the case where a point
+ * is coincident with a linear ring edge, returns 1. Otherwise,
+ * returns boolean.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the linear ring. Returns 1 if
+ * the point is coincident with an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var approx = OpenLayers.Number.limitSigDigs;
+ var digs = 14;
+ var px = approx(point.x, digs);
+ var py = approx(point.y, digs);
+ function getX(y, x1, y1, x2, y2) {
+ return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
+ }
+ var numSeg = this.components.length - 1;
+ var start, end, x1, y1, x2, y2, cx, cy;
+ var crosses = 0;
+ for(var i=0; i<numSeg; ++i) {
+ start = this.components[i];
+ x1 = approx(start.x, digs);
+ y1 = approx(start.y, digs);
+ end = this.components[i + 1];
+ x2 = approx(end.x, digs);
+ y2 = approx(end.y, digs);
+
+ /**
+ * The following conditions enforce five edge-crossing rules:
+ * 1. points coincident with edges are considered contained;
+ * 2. an upward edge includes its starting endpoint, and
+ * excludes its final endpoint;
+ * 3. a downward edge excludes its starting endpoint, and
+ * includes its final endpoint;
+ * 4. horizontal edges are excluded; and
+ * 5. the edge-ray intersection point must be strictly right
+ * of the point P.
+ */
+ if(y1 == y2) {
+ // horizontal edge
+ if(py == y1) {
+ // point on horizontal line
+ if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
+ x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ // ignore other horizontal edges
+ continue;
+ }
+ cx = approx(getX(py, x1, y1, x2, y2), digs);
+ if(cx == px) {
+ // point on line
+ if(y1 < y2 && (py >= y1 && py <= y2) || // upward
+ y1 > y2 && (py <= y1 && py >= y2)) { // downward
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ if(cx <= px) {
+ // no crossing to the right
+ continue;
+ }
+ if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
+ // no crossing
+ continue;
+ }
+ if(y1 < y2 && (py >= y1 && py < y2) || // upward
+ y1 > y2 && (py < y1 && py >= y2)) { // downward
+ ++crosses;
+ }
+ }
+ var contained = (crosses == -1) ?
+ // on edge
+ 1 :
+ // even (out) or odd (in)
+ !!(crosses & 1);
+
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+ intersect = geometry.intersects(this);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
+ this, [geometry]
+ );
+ } else {
+ // check for component intersections
+ for(var i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = geometry.components[i].intersects(this);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
+/* ======================================================================
+ OpenLayers/Util/vendorPrefix.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+OpenLayers.Util = OpenLayers.Util || {};
+/**
+ * Namespace: OpenLayers.Util.vendorPrefix
+ * A collection of utility functions to detect vendor prefixed features
+ */
+OpenLayers.Util.vendorPrefix = (function() {
+ "use strict";
+
+ var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
+ divStyle = document.createElement("div").style,
+ cssCache = {},
+ jsCache = {};
+
+
+ /**
+ * Function: domToCss
+ * Converts a upper camel case DOM style property name to a CSS property
+ * i.e. transformOrigin -> transform-origin
+ * or WebkitTransformOrigin -> -webkit-transform-origin
+ *
+ * Parameters:
+ * prefixedDom - {String} The property to convert
+ *
+ * Returns:
+ * {String} The CSS property
+ */
+ function domToCss(prefixedDom) {
+ if (!prefixedDom) { return null; }
+ return prefixedDom.
+ replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
+ replace(/^ms-/, "-ms-");
+ }
+
+ /**
+ * APIMethod: css
+ * Detect which property is used for a CSS property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) CSS property name
+ *
+ * Returns:
+ * {String} The standard CSS property, prefixed property or null if not
+ * supported
+ */
+ function css(property) {
+ if (cssCache[property] === undefined) {
+ var domProperty = property.
+ replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
+ var prefixedDom = style(domProperty);
+ cssCache[property] = domToCss(prefixedDom);
+ }
+ return cssCache[property];
+ }
+
+ /**
+ * APIMethod: js
+ * Detect which property is used for a JS property/method
+ *
+ * Parameters:
+ * obj - {Object} The object to test on
+ * property - {String} The standard (unprefixed) JS property name
+ *
+ * Returns:
+ * {String} The standard JS property, prefixed property or null if not
+ * supported
+ */
+ function js(obj, property) {
+ if (jsCache[property] === undefined) {
+ var tmpProp,
+ i = 0,
+ l = VENDOR_PREFIXES.length,
+ prefix,
+ isStyleObj = (typeof obj.cssText !== "undefined");
+
+ jsCache[property] = null;
+ for(; i<l; i++) {
+ prefix = VENDOR_PREFIXES[i];
+ if(prefix) {
+ if (!isStyleObj) {
+ // js prefix should be lower-case, while style
+ // properties have upper case on first character
+ prefix = prefix.toLowerCase();
+ }
+ tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
+ } else {
+ tmpProp = property;
+ }
+
+ if(obj[tmpProp] !== undefined) {
+ jsCache[property] = tmpProp;
+ break;
+ }
+ }
+ }
+ return jsCache[property];
+ }
+
+ /**
+ * APIMethod: style
+ * Detect which property is used for a DOM style property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) style property name
+ *
+ * Returns:
+ * {String} The standard style property, prefixed property or null if not
+ * supported
+ */
+ function style(property) {
+ return js(divStyle, property);
+ }
+
+ return {
+ css: css,
+ js: js,
+ style: style,
+
+ // used for testing
+ cssCache: cssCache,
+ jsCache: jsCache
+ };
+}());
+/* ======================================================================
+ OpenLayers/Animation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ */
+
+/**
+ * Namespace: OpenLayers.Animation
+ * A collection of utility functions for executing methods that repaint a
+ * portion of the browser window. These methods take advantage of the
+ * browser's scheduled repaints where requestAnimationFrame is available.
+ */
+OpenLayers.Animation = (function(window) {
+
+ /**
+ * Property: isNative
+ * {Boolean} true if a native requestAnimationFrame function is available
+ */
+ var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
+ var isNative = !!(requestAnimationFrame);
+
+ /**
+ * Function: requestFrame
+ * Schedule a function to be called at the next available animation frame.
+ * Uses the native method where available. Where requestAnimationFrame is
+ * not available, setTimeout will be called with a 16ms delay.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ */
+ var requestFrame = (function() {
+ var request = window[requestAnimationFrame] ||
+ function(callback, element) {
+ window.setTimeout(callback, 16);
+ };
+ // bind to window to avoid illegal invocation of native function
+ return function(callback, element) {
+ request.apply(window, [callback, element]);
+ };
+ })();
+
+ // private variables for animation loops
+ var counter = 0;
+ var loops = {};
+
+ /**
+ * Function: start
+ * Executes a method with <requestFrame> in series for some
+ * duration.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * duration - {Number} Optional duration for the loop. If not provided, the
+ * animation loop will execute indefinitely.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ *
+ * Returns:
+ * {Number} Identifier for the animation loop. Used to stop animations with
+ * <stop>.
+ */
+ function start(callback, duration, element) {
+ duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
+ var id = ++counter;
+ var start = +new Date;
+ loops[id] = function() {
+ if (loops[id] && +new Date - start <= duration) {
+ callback();
+ if (loops[id]) {
+ requestFrame(loops[id], element);
+ }
+ } else {
+ delete loops[id];
+ }
+ };
+ requestFrame(loops[id], element);
+ return id;
+ }
+
+ /**
+ * Function: stop
+ * Terminates an animation loop started with <start>.
+ *
+ * Parameters:
+ * id - {Number} Identifier returned from <start>.
+ */
+ function stop(id) {
+ delete loops[id];
+ }
+
+ return {
+ isNative: isNative,
+ requestFrame: requestFrame,
+ start: start,
+ stop: stop
+ };
+
+})(window);
+/* ======================================================================
+ OpenLayers/Tween.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+/**
+ * Namespace: OpenLayers.Tween
+ */
+OpenLayers.Tween = OpenLayers.Class({
+
+ /**
+ * APIProperty: easing
+ * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
+ * Defaultly set to OpenLayers.Easing.Expo.easeOut
+ */
+ easing: null,
+
+ /**
+ * APIProperty: begin
+ * {Object} Values to start the animation with
+ */
+ begin: null,
+
+ /**
+ * APIProperty: finish
+ * {Object} Values to finish the animation with
+ */
+ finish: null,
+
+ /**
+ * APIProperty: duration
+ * {int} duration of the tween (number of steps)
+ */
+ duration: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} An object with start, eachStep and done properties whose values
+ * are functions to be call during the animation. They are passed the
+ * current computed value as argument.
+ */
+ callbacks: null,
+
+ /**
+ * Property: time
+ * {int} Step counter
+ */
+ time: null,
+
+ /**
+ * APIProperty: minFrameRate
+ * {Number} The minimum framerate for animations in frames per second. After
+ * each step, the time spent in the animation is compared to the calculated
+ * time at this frame rate. If the animation runs longer than the calculated
+ * time, the next step is skipped. Default is 30.
+ */
+ minFrameRate: null,
+
+ /**
+ * Property: startTime
+ * {Number} The timestamp of the first execution step. Used for skipping
+ * frames
+ */
+ startTime: null,
+
+ /**
+ * Property: animationId
+ * {int} Loop id returned by OpenLayers.Animation.start
+ */
+ animationId: null,
+
+ /**
+ * Property: playing
+ * {Boolean} Tells if the easing is currently playing
+ */
+ playing: false,
+
+ /**
+ * Constructor: OpenLayers.Tween
+ * Creates a Tween.
+ *
+ * Parameters:
+ * easing - {<OpenLayers.Easing>(Function)} easing function method to use
+ */
+ initialize: function(easing) {
+ this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
+ },
+
+ /**
+ * APIMethod: start
+ * Plays the Tween, and calls the callback method on each step
+ *
+ * Parameters:
+ * begin - {Object} values to start the animation with
+ * finish - {Object} values to finish the animation with
+ * duration - {int} duration of the tween (number of steps)
+ * options - {Object} hash of options (callbacks (start, eachStep, done),
+ * minFrameRate)
+ */
+ start: function(begin, finish, duration, options) {
+ this.playing = true;
+ this.begin = begin;
+ this.finish = finish;
+ this.duration = duration;
+ this.callbacks = options.callbacks;
+ this.minFrameRate = options.minFrameRate || 30;
+ this.time = 0;
+ this.startTime = new Date().getTime();
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ if (this.callbacks && this.callbacks.start) {
+ this.callbacks.start.call(this, this.begin);
+ }
+ this.animationId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(this.play, this)
+ );
+ },
+
+ /**
+ * APIMethod: stop
+ * Stops the Tween, and calls the done callback
+ * Doesn't do anything if animation is already finished
+ */
+ stop: function() {
+ if (!this.playing) {
+ return;
+ }
+
+ if (this.callbacks && this.callbacks.done) {
+ this.callbacks.done.call(this, this.finish);
+ }
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ this.playing = false;
+ },
+
+ /**
+ * Method: play
+ * Calls the appropriate easing method
+ */
+ play: function() {
+ var value = {};
+ for (var i in this.begin) {
+ var b = this.begin[i];
+ var f = this.finish[i];
+ if (b == null || f == null || isNaN(b) || isNaN(f)) {
+ throw new TypeError('invalid value for Tween');
+ }
+
+ var c = f - b;
+ value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
+ }
+ this.time++;
+
+ if (this.callbacks && this.callbacks.eachStep) {
+ // skip frames if frame rate drops below threshold
+ if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
+ this.callbacks.eachStep.call(this, value);
+ }
+ }
+
+ if (this.time > this.duration) {
+ this.stop();
+ }
+ },
+
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Tween"
+});
+
+/**
+ * Namespace: OpenLayers.Easing
+ *
+ * Credits:
+ * Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
+ */
+OpenLayers.Easing = {
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Easing"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Linear
+ */
+OpenLayers.Easing.Linear = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Linear"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Expo
+ */
+OpenLayers.Easing.Expo = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if (t==0) return b;
+ if (t==d) return b+c;
+ if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Expo"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Quad
+ */
+OpenLayers.Easing.Quad = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Quad"
+};
+/* ======================================================================
+ OpenLayers/Projection.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Projection
+ * Methods for coordinate transforms between coordinate systems. By default,
+ * OpenLayers ships with the ability to transform coordinates between
+ * geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
+ * coordinate reference systems. See the <transform> method for details
+ * on usage.
+ *
+ * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
+ * library. If the proj4js library is included, the <transform> method
+ * will work between any two coordinate reference systems with proj4js
+ * definitions.
+ *
+ * If the proj4js library is not included, or if you wish to allow transforms
+ * between arbitrary coordinate reference systems, use the <addTransform>
+ * method to register a custom transform method.
+ */
+OpenLayers.Projection = OpenLayers.Class({
+
+ /**
+ * Property: proj
+ * {Object} Proj4js.Proj instance.
+ */
+ proj: null,
+
+ /**
+ * Property: projCode
+ * {String}
+ */
+ projCode: null,
+
+ /**
+ * Property: titleRegEx
+ * {RegExp} regular expression to strip the title from a proj4js definition
+ */
+ titleRegEx: /\+title=[^\+]*/,
+
+ /**
+ * Constructor: OpenLayers.Projection
+ * This class offers several methods for interacting with a wrapped
+ * pro4js projection object.
+ *
+ * Parameters:
+ * projCode - {String} A string identifying the Well Known Identifier for
+ * the projection.
+ * options - {Object} An optional object to set additional properties
+ * on the projection.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} A projection object.
+ */
+ initialize: function(projCode, options) {
+ OpenLayers.Util.extend(this, options);
+ this.projCode = projCode;
+ if (typeof Proj4js == "object") {
+ this.proj = new Proj4js.Proj(projCode);
+ }
+ },
+
+ /**
+ * APIMethod: getCode
+ * Get the string SRS code.
+ *
+ * Returns:
+ * {String} The SRS code.
+ */
+ getCode: function() {
+ return this.proj ? this.proj.srsCode : this.projCode;
+ },
+
+ /**
+ * APIMethod: getUnits
+ * Get the units string for the projection -- returns null if
+ * proj4js is not available.
+ *
+ * Returns:
+ * {String} The units abbreviation.
+ */
+ getUnits: function() {
+ return this.proj ? this.proj.units : null;
+ },
+
+ /**
+ * Method: toString
+ * Convert projection to string (getCode wrapper).
+ *
+ * Returns:
+ * {String} The projection code.
+ */
+ toString: function() {
+ return this.getCode();
+ },
+
+ /**
+ * Method: equals
+ * Test equality of two projection instances. Determines equality based
+ * soley on the projection code.
+ *
+ * Returns:
+ * {Boolean} The two projections are equivalent.
+ */
+ equals: function(projection) {
+ var p = projection, equals = false;
+ if (p) {
+ if (!(p instanceof OpenLayers.Projection)) {
+ p = new OpenLayers.Projection(p);
+ }
+ if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
+ equals = this.proj.defData.replace(this.titleRegEx, "") ==
+ p.proj.defData.replace(this.titleRegEx, "");
+ } else if (p.getCode) {
+ var source = this.getCode(), target = p.getCode();
+ equals = source == target ||
+ !!OpenLayers.Projection.transforms[source] &&
+ OpenLayers.Projection.transforms[source][target] ===
+ OpenLayers.Projection.nullTransform;
+ }
+ }
+ return equals;
+ },
+
+ /* Method: destroy
+ * Destroy projection object.
+ */
+ destroy: function() {
+ delete this.proj;
+ delete this.projCode;
+ },
+
+ CLASS_NAME: "OpenLayers.Projection"
+});
+
+/**
+ * Property: transforms
+ * {Object} Transforms is an object, with from properties, each of which may
+ * have a to property. This allows you to define projections without
+ * requiring support for proj4js to be included.
+ *
+ * This object has keys which correspond to a 'source' projection object. The
+ * keys should be strings, corresponding to the projection.getCode() value.
+ * Each source projection object should have a set of destination projection
+ * keys included in the object.
+ *
+ * Each value in the destination object should be a transformation function,
+ * where the function is expected to be passed an object with a .x and a .y
+ * property. The function should return the object, with the .x and .y
+ * transformed according to the transformation function.
+ *
+ * Note - Properties on this object should not be set directly. To add a
+ * transform method to this object, use the <addTransform> method. For an
+ * example of usage, see the OpenLayers.Layer.SphericalMercator file.
+ */
+OpenLayers.Projection.transforms = {};
+
+/**
+ * APIProperty: defaults
+ * {Object} Defaults for the SRS codes known to OpenLayers (currently
+ * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
+ * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units,
+ * maxExtent (the validity extent for the SRS) and yx (true if this SRS is
+ * known to have a reverse axis order).
+ */
+OpenLayers.Projection.defaults = {
+ "EPSG:4326": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90],
+ yx: true
+ },
+ "CRS:84": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90]
+ },
+ "EPSG:900913": {
+ units: "m",
+ maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
+ }
+};
+
+/**
+ * APIMethod: addTransform
+ * Set a custom transform method between two projections. Use this method in
+ * cases where the proj4js lib is not available or where custom projections
+ * need to be handled.
+ *
+ * Parameters:
+ * from - {String} The code for the source projection
+ * to - {String} the code for the destination projection
+ * method - {Function} A function that takes a point as an argument and
+ * transforms that point from the source to the destination projection
+ * in place. The original point should be modified.
+ */
+OpenLayers.Projection.addTransform = function(from, to, method) {
+ if (method === OpenLayers.Projection.nullTransform) {
+ var defaults = OpenLayers.Projection.defaults[from];
+ if (defaults && !OpenLayers.Projection.defaults[to]) {
+ OpenLayers.Projection.defaults[to] = defaults;
+ }
+ }
+ if(!OpenLayers.Projection.transforms[from]) {
+ OpenLayers.Projection.transforms[from] = {};
+ }
+ OpenLayers.Projection.transforms[from][to] = method;
+};
+
+/**
+ * APIMethod: transform
+ * Transform a point coordinate from one projection to another. Note that
+ * the input point is transformed in place.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
+ * properties representing coordinates in those dimensions.
+ * source - {OpenLayers.Projection} Source map coordinate system
+ * dest - {OpenLayers.Projection} Destination map coordinate system
+ *
+ * Returns:
+ * point - {object} A transformed coordinate. The original point is modified.
+ */
+OpenLayers.Projection.transform = function(point, source, dest) {
+ if (source && dest) {
+ if (!(source instanceof OpenLayers.Projection)) {
+ source = new OpenLayers.Projection(source);
+ }
+ if (!(dest instanceof OpenLayers.Projection)) {
+ dest = new OpenLayers.Projection(dest);
+ }
+ if (source.proj && dest.proj) {
+ point = Proj4js.transform(source.proj, dest.proj, point);
+ } else {
+ var sourceCode = source.getCode();
+ var destCode = dest.getCode();
+ var transforms = OpenLayers.Projection.transforms;
+ if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
+ transforms[sourceCode][destCode](point);
+ }
+ }
+ }
+ return point;
+};
+
+/**
+ * APIFunction: nullTransform
+ * A null transformation - useful for defining projection aliases when
+ * proj4js is not available:
+ *
+ * (code)
+ * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
+ * OpenLayers.Projection.nullTransform);
+ * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
+ * OpenLayers.Projection.nullTransform);
+ * (end)
+ */
+OpenLayers.Projection.nullTransform = function(point) {
+ return point;
+};
+
+/**
+ * Note: Transforms for web mercator <-> geographic
+ * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100.
+ * OpenLayers originally started referring to EPSG:900913 as web mercator.
+ * The EPSG has declared EPSG:3857 to be web mercator.
+ * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
+ * equivalent. See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
+ * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
+ * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
+ * order for EPSG:4326.
+ */
+(function() {
+
+ var pole = 20037508.34;
+
+ function inverseMercator(xy) {
+ xy.x = 180 * xy.x / pole;
+ xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
+ return xy;
+ }
+
+ function forwardMercator(xy) {
+ xy.x = xy.x * pole / 180;
+ var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
+ xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
+ return xy;
+ }
+
+ function map(base, codes) {
+ var add = OpenLayers.Projection.addTransform;
+ var same = OpenLayers.Projection.nullTransform;
+ var i, len, code, other, j;
+ for (i=0, len=codes.length; i<len; ++i) {
+ code = codes[i];
+ add(base, code, forwardMercator);
+ add(code, base, inverseMercator);
+ for (j=i+1; j<len; ++j) {
+ other = codes[j];
+ add(code, other, same);
+ add(other, code, same);
+ }
+ }
+ }
+
+ // list of equivalent codes for web mercator
+ var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
+ geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
+ i;
+ for (i=mercator.length-1; i>=0; --i) {
+ map(mercator[i], geographic);
+ }
+ for (i=geographic.length-1; i>=0; --i) {
+ map(geographic[i], mercator);
+ }
+
+})();
+/* ======================================================================
+ OpenLayers/Map.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Tween.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Map
+ * Instances of OpenLayers.Map are interactive maps embedded in a web page.
+ * Create a new map with the <OpenLayers.Map> constructor.
+ *
+ * On their own maps do not provide much functionality. To extend a map
+ * it's necessary to add controls (<OpenLayers.Control>) and
+ * layers (<OpenLayers.Layer>) to the map.
+ */
+OpenLayers.Map = OpenLayers.Class({
+
+ /**
+ * Constant: Z_INDEX_BASE
+ * {Object} Base z-indexes for different classes of thing
+ */
+ Z_INDEX_BASE: {
+ BaseLayer: 100,
+ Overlay: 325,
+ Feature: 725,
+ Popup: 750,
+ Control: 1000
+ },
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * map.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to map.events.object.
+ * element - {DOMElement} A reference to map.events.element.
+ *
+ * Browser events have the following additional properties:
+ * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
+ * to the the map viewport).
+ *
+ * Supported map event types:
+ * preaddlayer - triggered before a layer has been added. The event
+ * object will include a *layer* property that references the layer
+ * to be added. When a listener returns "false" the adding will be
+ * aborted.
+ * addlayer - triggered after a layer has been added. The event object
+ * will include a *layer* property that references the added layer.
+ * preremovelayer - triggered before a layer has been removed. The event
+ * object will include a *layer* property that references the layer
+ * to be removed. When a listener returns "false" the removal will be
+ * aborted.
+ * removelayer - triggered after a layer has been removed. The event
+ * object will include a *layer* property that references the removed
+ * layer.
+ * changelayer - triggered after a layer name change, order change,
+ * opacity change, params change, visibility change (actual visibility,
+ * not the layer's visibility property) or attribution change (due to
+ * extent change). Listeners will receive an event object with *layer*
+ * and *property* properties. The *layer* property will be a reference
+ * to the changed layer. The *property* property will be a key to the
+ * changed property (name, order, opacity, params, visibility or
+ * attribution).
+ * movestart - triggered after the start of a drag, pan, or zoom. The event
+ * object may include a *zoomChanged* property that tells whether the
+ * zoom has changed.
+ * move - triggered after each drag, pan, or zoom
+ * moveend - triggered after a drag, pan, or zoom completes
+ * zoomend - triggered after a zoom completes
+ * mouseover - triggered after mouseover the map
+ * mouseout - triggered after mouseout the map
+ * mousemove - triggered after mousemove the map
+ * changebaselayer - triggered after the base layer changes
+ * updatesize - triggered after the <updateSize> method was executed
+ */
+
+ /**
+ * Property: id
+ * {String} Unique identifier for the map
+ */
+ id: null,
+
+ /**
+ * Property: fractionalZoom
+ * {Boolean} For a base layer that supports it, allow the map resolution
+ * to be set to a value between one of the values in the resolutions
+ * array. Default is false.
+ *
+ * When fractionalZoom is set to true, it is possible to zoom to
+ * an arbitrary extent. This requires a base layer from a source
+ * that supports requests for arbitrary extents (i.e. not cached
+ * tiles on a regular lattice). This means that fractionalZoom
+ * will not work with commercial layers (Google, Yahoo, VE), layers
+ * using TileCache, or any other pre-cached data sources.
+ *
+ * If you are using fractionalZoom, then you should also use
+ * <getResolutionForZoom> instead of layer.resolutions[zoom] as the
+ * former works for non-integer zoom levels.
+ */
+ fractionalZoom: false,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the map
+ */
+ events: null,
+
+ /**
+ * APIProperty: allOverlays
+ * {Boolean} Allow the map to function with "overlays" only. Defaults to
+ * false. If true, the lowest layer in the draw order will act as
+ * the base layer. In addition, if set to true, all layers will
+ * have isBaseLayer set to false when they are added to the map.
+ *
+ * Note:
+ * If you set map.allOverlays to true, then you *cannot* use
+ * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true,
+ * the lowest layer in the draw layer is the base layer. So, to change
+ * the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
+ * index to 0.
+ */
+ allOverlays: false,
+
+ /**
+ * APIProperty: div
+ * {DOMElement|String} The element that contains the map (or an id for
+ * that element). If the <OpenLayers.Map> constructor is called
+ * with two arguments, this should be provided as the first argument.
+ * Alternatively, the map constructor can be called with the options
+ * object as the only argument. In this case (one argument), a
+ * div property may or may not be provided. If the div property
+ * is not provided, the map can be rendered to a container later
+ * using the <render> method.
+ *
+ * Note:
+ * If you are calling <render> after map construction, do not use
+ * <maxResolution> auto. Instead, divide your <maxExtent> by your
+ * maximum expected dimension.
+ */
+ div: null,
+
+ /**
+ * Property: dragging
+ * {Boolean} The map is currently being dragged.
+ */
+ dragging: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} Size of the main div (this.div)
+ */
+ size: null,
+
+ /**
+ * Property: viewPortDiv
+ * {HTMLDivElement} The element that represents the map viewport
+ */
+ viewPortDiv: null,
+
+ /**
+ * Property: layerContainerOrigin
+ * {<OpenLayers.LonLat>} The lonlat at which the later container was
+ * re-initialized (on-zoom)
+ */
+ layerContainerOrigin: null,
+
+ /**
+ * Property: layerContainerDiv
+ * {HTMLDivElement} The element that contains the layers.
+ */
+ layerContainerDiv: null,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
+ */
+ layers: null,
+
+ /**
+ * APIProperty: controls
+ * {Array(<OpenLayers.Control>)} List of controls associated with the map.
+ *
+ * If not provided in the map options at construction, the map will
+ * by default be given the following controls if present in the build:
+ * - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
+ * - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
+ * - <OpenLayers.Control.ArgParser>
+ * - <OpenLayers.Control.Attribution>
+ */
+ controls: null,
+
+ /**
+ * Property: popups
+ * {Array(<OpenLayers.Popup>)} List of popups associated with the map
+ */
+ popups: null,
+
+ /**
+ * APIProperty: baseLayer
+ * {<OpenLayers.Layer>} The currently selected base layer. This determines
+ * min/max zoom level, projection, etc.
+ */
+ baseLayer: null,
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>} The current center of the map
+ */
+ center: null,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution of the map.
+ */
+ resolution: null,
+
+ /**
+ * Property: zoom
+ * {Integer} The current zoom level of the map
+ */
+ zoom: 0,
+
+ /**
+ * Property: panRatio
+ * {Float} The ratio of the current extent within
+ * which panning will tween.
+ */
+ panRatio: 1.5,
+
+ /**
+ * APIProperty: options
+ * {Object} The options object passed to the class constructor. Read-only.
+ */
+ options: null,
+
+ // Options
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} Set in the map options to override the default tile
+ * size for this map.
+ */
+ tileSize: null,
+
+ /**
+ * APIProperty: projection
+ * {String} Set in the map options to specify the default projection
+ * for layers added to this map. When using a projection other than EPSG:4326
+ * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
+ * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326".
+ * Note that the projection of the map is usually determined
+ * by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
+ */
+ projection: "EPSG:4326",
+
+ /**
+ * APIProperty: units
+ * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm',
+ * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units
+ */
+ units: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array(Float)} A list of map resolutions (map units per pixel) in
+ * descending order. If this is not set in the layer constructor, it
+ * will be set based on other resolution related properties
+ * (maxExtent, maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Required if you are not displaying the whole world on a tile
+ * with the size specified in <tileSize>.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the map.
+ * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
+ * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
+ * else, defaults to null.
+ * To restrict user panning and zooming of the map, use <restrictedExtent> instead.
+ * The value for <maxExtent> will change calculations for tile URLs.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the map. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: restrictedExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * Limit map navigation to this extent where possible.
+ * If a non-null restrictedExtent is set, panning will be restricted
+ * to the given bounds. In addition, zooming to a resolution that
+ * displays more than the restricted extent will center the map
+ * on the restricted extent. If you wish to limit the zoom level
+ * or resolution, use maxResolution.
+ */
+ restrictedExtent: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer} Number of zoom levels for the map. Defaults to 16. Set a
+ * different value in the map options if needed.
+ */
+ numZoomLevels: 16,
+
+ /**
+ * APIProperty: theme
+ * {String} Relative path to a CSS file from which to load theme styles.
+ * Specify null in the map options (e.g. {theme: null}) if you
+ * want to get cascading style declarations - by putting links to
+ * stylesheets or style declarations directly in your page.
+ */
+ theme: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support for projections other
+ * than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
+ * several controls to display data to user. If this property is set,
+ * it will be set on any control which has a null displayProjection
+ * property at the time the control is added to the map.
+ */
+ displayProjection: null,
+
+ /**
+ * APIProperty: tileManager
+ * {<OpenLayers.TileManager>|Object} By default, and if the build contains
+ * TileManager.js, the map will use the TileManager to queue image requests
+ * and to cache tile image elements. To create a map without a TileManager
+ * configure the map with tileManager: null. To create a TileManager with
+ * non-default options, supply the options instead or alternatively supply
+ * an instance of {<OpenLayers.TileManager>}.
+ */
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean} Should OpenLayers allow events on the map to fall through to
+ * other elements on the page, or should it swallow them? (#457)
+ * Default is to swallow.
+ */
+ fallThrough: false,
+
+ /**
+ * APIProperty: autoUpdateSize
+ * {Boolean} Should OpenLayers automatically update the size of the map
+ * when the resize event is fired. Default is true.
+ */
+ autoUpdateSize: true,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * Property: panTween
+ * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
+ */
+ panTween: null,
+
+ /**
+ * APIProperty: panMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
+ * animated panning.
+ */
+ panMethod: OpenLayers.Easing.Expo.easeOut,
+
+ /**
+ * Property: panDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is
+ * panned.
+ * Default is 50.
+ */
+ panDuration: 50,
+
+ /**
+ * Property: zoomTween
+ * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
+ */
+ zoomTween: null,
+
+ /**
+ * APIProperty: zoomMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
+ * animated zooming.
+ */
+ zoomMethod: OpenLayers.Easing.Quad.easeOut,
+
+ /**
+ * Property: zoomDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is zoomed.
+ * Default is 20.
+ */
+ zoomDuration: 20,
+
+ /**
+ * Property: paddingForPopups
+ * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent
+ * the popup from getting too close to the map border.
+ */
+ paddingForPopups : null,
+
+ /**
+ * Property: layerContainerOriginPx
+ * {Object} Cached object representing the layer container origin (in pixels).
+ */
+ layerContainerOriginPx: null,
+
+ /**
+ * Property: minPx
+ * {Object} An object with a 'x' and 'y' values that is the lower
+ * left of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid. It is also used in the getLonLatFromViewPortPx function
+ * of Layer.
+ */
+ minPx: null,
+
+ /**
+ * Property: maxPx
+ * {Object} An object with a 'x' and 'y' values that is the top
+ * right of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid.
+ */
+ maxPx: null,
+
+ /**
+ * Constructor: OpenLayers.Map
+ * Constructor for a new OpenLayers.Map instance. There are two possible
+ * ways to call the map constructor. See the examples below.
+ *
+ * Parameters:
+ * div - {DOMElement|String} The element or id of an element in your page
+ * that will contain the map. May be omitted if the <div> option is
+ * provided or if you intend to call the <render> method later.
+ * options - {Object} Optional object with properties to tag onto the map.
+ *
+ * Valid options (in addition to the listed API properties):
+ * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * Only specify if <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains coordinates, center will be set
+ * by that, and this option will be ignored.
+ * zoom - {Number} The initial zoom level for the map. Only specify if
+ * <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains a zoom level, zoom will be set
+ * by that, and this option will be ignored.
+ * extent - {<OpenLayers.Bounds>|Array} The initial extent of the map.
+ * If provided as an array, the array should consist of
+ * four values (left, bottom, right, top).
+ * Only specify if <center> and <zoom> are not provided.
+ *
+ * Examples:
+ * (code)
+ * // create a map with default options in an element with the id "map1"
+ * var map = new OpenLayers.Map("map1");
+ *
+ * // create a map with non-default options in an element with id "map2"
+ * var options = {
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
+ * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
+ * };
+ * var map = new OpenLayers.Map("map2", options);
+ *
+ * // map with non-default options - same as above but with a single argument,
+ * // a restricted extent, and using arrays for bounds and center
+ * var map = new OpenLayers.Map({
+ * div: "map_id",
+ * projection: "EPSG:3857",
+ * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
+ * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
+ * center: [-12356463.476333, 5621521.4854095]
+ * });
+ *
+ * // create a map without a reference to a container - call render later
+ * var map = new OpenLayers.Map({
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
+ * });
+ * (end)
+ */
+ initialize: function (div, options) {
+
+ // If only one argument is provided, check if it is an object.
+ if(arguments.length === 1 && typeof div === "object") {
+ options = div;
+ div = options && options.div;
+ }
+
+ // Simple-type defaults are set in class definition.
+ // Now set complex-type defaults
+ this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+ OpenLayers.Map.TILE_HEIGHT);
+
+ this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
+
+ this.theme = OpenLayers._getScriptLocation() +
+ 'theme/default/style.css';
+
+ // backup original options
+ this.options = OpenLayers.Util.extend({}, options);
+
+ // now override default options
+ OpenLayers.Util.extend(this, options);
+
+ var projCode = this.projection instanceof OpenLayers.Projection ?
+ this.projection.projCode : this.projection;
+ OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
+
+ // allow extents and center to be arrays
+ if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
+ this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
+ }
+ if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
+ this.minExtent = new OpenLayers.Bounds(this.minExtent);
+ }
+ if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
+ this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
+ }
+ if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
+ this.center = new OpenLayers.LonLat(this.center);
+ }
+
+ // initialize layers array
+ this.layers = [];
+
+ this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
+
+ this.div = OpenLayers.Util.getElement(div);
+ if(!this.div) {
+ this.div = document.createElement("div");
+ this.div.style.height = "1px";
+ this.div.style.width = "1px";
+ }
+
+ OpenLayers.Element.addClass(this.div, 'olMap');
+
+ // the viewPortDiv is the outermost div we modify
+ var id = this.id + "_OpenLayers_ViewPort";
+ this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
+ "relative", null,
+ "hidden");
+ this.viewPortDiv.style.width = "100%";
+ this.viewPortDiv.style.height = "100%";
+ this.viewPortDiv.className = "olMapViewport";
+ this.div.appendChild(this.viewPortDiv);
+
+ this.events = new OpenLayers.Events(
+ this, this.viewPortDiv, null, this.fallThrough,
+ {includeXY: true}
+ );
+
+ if (OpenLayers.TileManager && this.tileManager !== null) {
+ if (!(this.tileManager instanceof OpenLayers.TileManager)) {
+ this.tileManager = new OpenLayers.TileManager(this.tileManager);
+ }
+ this.tileManager.addMap(this);
+ }
+
+ // the layerContainerDiv is the one that holds all the layers
+ id = this.id + "_OpenLayers_Container";
+ this.layerContainerDiv = OpenLayers.Util.createDiv(id);
+ this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
+ this.layerContainerOriginPx = {x: 0, y: 0};
+ this.applyTransform();
+
+ this.viewPortDiv.appendChild(this.layerContainerDiv);
+
+ this.updateSize();
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ if (this.autoUpdateSize === true) {
+ // updateSize on catching the window's resize
+ // Note that this is ok, as updateSize() does nothing if the
+ // map's size has not actually changed.
+ this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize,
+ this);
+ OpenLayers.Event.observe(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ // only append link stylesheet if the theme property is set
+ if(this.theme) {
+ // check existing links for equivalent url
+ var addNode = true;
+ var nodes = document.getElementsByTagName('link');
+ for(var i=0, len=nodes.length; i<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
+ this.theme)) {
+ addNode = false;
+ break;
+ }
+ }
+ // only add a new node if one with an equivalent url hasn't already
+ // been added
+ if(addNode) {
+ var cssNode = document.createElement('link');
+ cssNode.setAttribute('rel', 'stylesheet');
+ cssNode.setAttribute('type', 'text/css');
+ cssNode.setAttribute('href', this.theme);
+ document.getElementsByTagName('head')[0].appendChild(cssNode);
+ }
+ }
+
+ if (this.controls == null) { // default controls
+ this.controls = [];
+ if (OpenLayers.Control != null) { // running full or lite?
+ // Navigation or TouchNavigation depending on what is in build
+ if (OpenLayers.Control.Navigation) {
+ this.controls.push(new OpenLayers.Control.Navigation());
+ } else if (OpenLayers.Control.TouchNavigation) {
+ this.controls.push(new OpenLayers.Control.TouchNavigation());
+ }
+ if (OpenLayers.Control.Zoom) {
+ this.controls.push(new OpenLayers.Control.Zoom());
+ } else if (OpenLayers.Control.PanZoom) {
+ this.controls.push(new OpenLayers.Control.PanZoom());
+ }
+
+ if (OpenLayers.Control.ArgParser) {
+ this.controls.push(new OpenLayers.Control.ArgParser());
+ }
+ if (OpenLayers.Control.Attribution) {
+ this.controls.push(new OpenLayers.Control.Attribution());
+ }
+ }
+ }
+
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ this.addControlToMap(this.controls[i]);
+ }
+
+ this.popups = [];
+
+ this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
+
+
+ // always call map.destroy()
+ OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
+
+ // add any initial layers
+ if (options && options.layers) {
+ /**
+ * If you have set options.center, the map center property will be
+ * set at this point. However, since setCenter has not been called,
+ * addLayers gets confused. So we delete the map center in this
+ * case. Because the check below uses options.center, it will
+ * be properly set below.
+ */
+ delete this.center;
+ delete this.zoom;
+ this.addLayers(options.layers);
+ // set center (and optionally zoom)
+ if (options.center && !this.getCenter()) {
+ // zoom can be undefined here
+ this.setCenter(options.center, options.zoom);
+ }
+ }
+
+ if (this.panMethod) {
+ this.panTween = new OpenLayers.Tween(this.panMethod);
+ }
+ if (this.zoomMethod && this.applyTransform.transform) {
+ this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
+ }
+ },
+
+ /**
+ * APIMethod: getViewport
+ * Get the DOMElement representing the view port.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ getViewport: function() {
+ return this.viewPortDiv;
+ },
+
+ /**
+ * APIMethod: render
+ * Render the map to a specified container.
+ *
+ * Parameters:
+ * div - {String|DOMElement} The container that the map should be rendered
+ * to. If different than the current container, the map viewport
+ * will be moved from the current to the new container.
+ */
+ render: function(div) {
+ this.div = OpenLayers.Util.getElement(div);
+ OpenLayers.Element.addClass(this.div, 'olMap');
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ this.div.appendChild(this.viewPortDiv);
+ this.updateSize();
+ },
+
+ /**
+ * Method: unloadDestroy
+ * Function that is called to destroy the map on page unload. stored here
+ * so that if map is manually destroyed, we can unregister this.
+ */
+ unloadDestroy: null,
+
+ /**
+ * Method: updateSizeDestroy
+ * When the map is destroyed, we need to stop listening to updateSize
+ * events: this method stores the function we need to unregister in
+ * non-IE browsers.
+ */
+ updateSizeDestroy: null,
+
+ /**
+ * APIMethod: destroy
+ * Destroy this map.
+ * Note that if you are using an application which removes a container
+ * of the map from the DOM, you need to ensure that you destroy the
+ * map *before* this happens; otherwise, the page unload handler
+ * will fail because the DOM elements that map.destroy() wants
+ * to clean up will be gone. (See
+ * http://trac.osgeo.org/openlayers/ticket/2277 for more information).
+ * This will apply to GeoExt and also to other applications which
+ * modify the DOM of the container of the OpenLayers Map.
+ */
+ destroy:function() {
+ // if unloadDestroy is null, we've already been destroyed
+ if (!this.unloadDestroy) {
+ return false;
+ }
+
+ // make sure panning doesn't continue after destruction
+ if(this.panTween) {
+ this.panTween.stop();
+ this.panTween = null;
+ }
+ // make sure zooming doesn't continue after destruction
+ if(this.zoomTween) {
+ this.zoomTween.stop();
+ this.zoomTween = null;
+ }
+
+ // map has been destroyed. dont do it again!
+ OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
+ this.unloadDestroy = null;
+
+ if (this.updateSizeDestroy) {
+ OpenLayers.Event.stopObserving(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ this.paddingForPopups = null;
+
+ if (this.controls != null) {
+ for (var i = this.controls.length - 1; i>=0; --i) {
+ this.controls[i].destroy();
+ }
+ this.controls = null;
+ }
+ if (this.layers != null) {
+ for (var i = this.layers.length - 1; i>=0; --i) {
+ //pass 'false' to destroy so that map wont try to set a new
+ // baselayer after each baselayer is removed
+ this.layers[i].destroy(false);
+ }
+ this.layers = null;
+ }
+ if (this.viewPortDiv && this.viewPortDiv.parentNode) {
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ }
+ this.viewPortDiv = null;
+
+ if (this.tileManager) {
+ this.tileManager.removeMap(this);
+ this.tileManager = null;
+ }
+
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ this.eventListeners = null;
+ }
+ this.events.destroy();
+ this.events = null;
+
+ this.options = null;
+ },
+
+ /**
+ * APIMethod: setOptions
+ * Change the map options
+ *
+ * Parameters:
+ * options - {Object} Hashtable of options to tag to the map
+ */
+ setOptions: function(options) {
+ var updatePxExtent = this.minPx &&
+ options.restrictedExtent != this.restrictedExtent;
+ OpenLayers.Util.extend(this, options);
+ // force recalculation of minPx and maxPx
+ updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
+ forceZoomChange: true
+ });
+ },
+
+ /**
+ * APIMethod: getTileSize
+ * Get the tile size for the map
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+ getTileSize: function() {
+ return this.tileSize;
+ },
+
+
+ /**
+ * APIMethod: getBy
+ * Get a list of objects given a property and a match item.
+ *
+ * Parameters:
+ * array - {String} A property on the map whose value is an array.
+ * property - {String} A property on each item of the given array.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(map[array][i][property]) evaluates to true, the item will
+ * be included in the array returned. If no items are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array} An array of items where the given property matches the given
+ * criteria.
+ */
+ getBy: function(array, property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this[array], function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getLayersBy
+ * Get a list of layers with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A layer property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersBy: function(property, match) {
+ return this.getBy("layers", property, match);
+ },
+
+ /**
+ * APIMethod: getLayersByName
+ * Get a list of layers with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A layer name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(layer.name) evaluates to true, the layer will be included
+ * in the list of layers returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByName: function(match) {
+ return this.getLayersBy("name", match);
+ },
+
+ /**
+ * APIMethod: getLayersByClass
+ * Get a list of layers of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A layer class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(layer.CLASS_NAME) evaluates to true, the layer will
+ * be included in the list of layers returned. If no layers are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByClass: function(match) {
+ return this.getLayersBy("CLASS_NAME", match);
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given
+ * criteria. An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ return this.getBy("controls", property, match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Layers to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getLayer
+ * Get a layer based on its id
+ *
+ * Parameters:
+ * id - {String} A layer id
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
+ * layer collection, or null if not found.
+ */
+ getLayer: function(id) {
+ var foundLayer = null;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer.id == id) {
+ foundLayer = layer;
+ break;
+ }
+ }
+ return foundLayer;
+ },
+
+ /**
+ * Method: setLayerZIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * zIdx - {int}
+ */
+ setLayerZIndex: function (layer, zIdx) {
+ layer.setZIndex(
+ this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+ + zIdx * 5 );
+ },
+
+ /**
+ * Method: resetLayersZIndex
+ * Reset each layer's z-index based on layer's array index
+ */
+ resetLayersZIndex: function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ this.setLayerZIndex(layer, i);
+ }
+ },
+
+ /**
+ * APIMethod: addLayer
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Boolean} True if the layer has been added to the map.
+ */
+ addLayer: function (layer) {
+ for(var i = 0, len = this.layers.length; i < len; i++) {
+ if (this.layers[i] == layer) {
+ return false;
+ }
+ }
+ if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
+ return false;
+ }
+ if(this.allOverlays) {
+ layer.isBaseLayer = false;
+ }
+
+ layer.div.className = "olLayerDiv";
+ layer.div.style.overflow = "";
+ this.setLayerZIndex(layer, this.layers.length);
+
+ if (layer.isFixed) {
+ this.viewPortDiv.appendChild(layer.div);
+ } else {
+ this.layerContainerDiv.appendChild(layer.div);
+ }
+ this.layers.push(layer);
+ layer.setMap(this);
+
+ if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) {
+ if (this.baseLayer == null) {
+ // set the first baselaye we add as the baselayer
+ this.setBaseLayer(layer);
+ } else {
+ layer.setVisibility(false);
+ }
+ } else {
+ layer.redraw();
+ }
+
+ this.events.triggerEvent("addlayer", {layer: layer});
+ layer.events.triggerEvent("added", {map: this, layer: layer});
+ layer.afterAdd();
+
+ return true;
+ },
+
+ /**
+ * APIMethod: addLayers
+ *
+ * Parameters:
+ * layers - {Array(<OpenLayers.Layer>)}
+ */
+ addLayers: function (layers) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ /**
+ * APIMethod: removeLayer
+ * Removes a layer from the map by removing its visual element (the
+ * layer.div property), then removing it from the map's internal list
+ * of layers, setting the layer's map property to null.
+ *
+ * a "removelayer" event is triggered.
+ *
+ * very worthy of mention is that simply removing a layer from a map
+ * will not cause the removal of any popups which may have been created
+ * by the layer. this is due to the fact that it was decided at some
+ * point that popups would not belong to layers. thus there is no way
+ * for us to know here to which layer the popup belongs.
+ *
+ * A simple solution to this is simply to call destroy() on the layer.
+ * the default OpenLayers.Layer class's destroy() function
+ * automatically takes care to remove itself from whatever map it has
+ * been attached to.
+ *
+ * The correct solution is for the layer itself to register an
+ * event-handler on "removelayer" and when it is called, if it
+ * recognizes itself as the layer being removed, then it cycles through
+ * its own personal list of popups, removing them from the map.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * setNewBaseLayer - {Boolean} Default is true
+ */
+ removeLayer: function(layer, setNewBaseLayer) {
+ if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
+ return;
+ }
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+
+ if (layer.isFixed) {
+ this.viewPortDiv.removeChild(layer.div);
+ } else {
+ this.layerContainerDiv.removeChild(layer.div);
+ }
+ OpenLayers.Util.removeItem(this.layers, layer);
+ layer.removeMap(this);
+ layer.map = null;
+
+ // if we removed the base layer, need to set a new one
+ if(this.baseLayer == layer) {
+ this.baseLayer = null;
+ if(setNewBaseLayer) {
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ var iLayer = this.layers[i];
+ if (iLayer.isBaseLayer || this.allOverlays) {
+ this.setBaseLayer(iLayer);
+ break;
+ }
+ }
+ }
+ }
+
+ this.resetLayersZIndex();
+
+ this.events.triggerEvent("removelayer", {layer: layer});
+ layer.events.triggerEvent("removed", {map: this, layer: layer});
+ },
+
+ /**
+ * APIMethod: getNumLayers
+ *
+ * Returns:
+ * {Int} The number of layers attached to the map.
+ */
+ getNumLayers: function () {
+ return this.layers.length;
+ },
+
+ /**
+ * APIMethod: getLayerIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Integer} The current (zero-based) index of the given layer in the map's
+ * layer stack. Returns -1 if the layer isn't on the map.
+ */
+ getLayerIndex: function (layer) {
+ return OpenLayers.Util.indexOf(this.layers, layer);
+ },
+
+ /**
+ * APIMethod: setLayerIndex
+ * Move the given layer to the specified (zero-based) index in the layer
+ * list, changing its z-index in the map display. Use
+ * map.getLayerIndex() to find out the current index of a layer. Note
+ * that this cannot (or at least should not) be effectively used to
+ * raise base layers above overlays.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * idx - {int}
+ */
+ setLayerIndex: function (layer, idx) {
+ var base = this.getLayerIndex(layer);
+ if (idx < 0) {
+ idx = 0;
+ } else if (idx > this.layers.length) {
+ idx = this.layers.length;
+ }
+ if (base != idx) {
+ this.layers.splice(base, 1);
+ this.layers.splice(idx, 0, layer);
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ this.setLayerZIndex(this.layers[i], i);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "order"
+ });
+ if(this.allOverlays) {
+ if(idx === 0) {
+ this.setBaseLayer(layer);
+ } else if(this.baseLayer !== this.layers[0]) {
+ this.setBaseLayer(this.layers[0]);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: raiseLayer
+ * Change the index of the given layer by delta. If delta is positive,
+ * the layer is moved up the map's layer stack; if delta is negative,
+ * the layer is moved down. Again, note that this cannot (or at least
+ * should not) be effectively used to raise base layers above overlays.
+ *
+ * Paremeters:
+ * layer - {<OpenLayers.Layer>}
+ * delta - {int}
+ */
+ raiseLayer: function (layer, delta) {
+ var idx = this.getLayerIndex(layer) + delta;
+ this.setLayerIndex(layer, idx);
+ },
+
+ /**
+ * APIMethod: setBaseLayer
+ * Allows user to specify one of the currently-loaded layers as the Map's
+ * new base layer.
+ *
+ * Parameters:
+ * newBaseLayer - {<OpenLayers.Layer>}
+ */
+ setBaseLayer: function(newBaseLayer) {
+
+ if (newBaseLayer != this.baseLayer) {
+
+ // ensure newBaseLayer is already loaded
+ if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
+
+ // preserve center and scale when changing base layers
+ var center = this.getCachedCenter();
+ var newResolution = OpenLayers.Util.getResolutionFromScale(
+ this.getScale(), newBaseLayer.units
+ );
+
+ // make the old base layer invisible
+ if (this.baseLayer != null && !this.allOverlays) {
+ this.baseLayer.setVisibility(false);
+ }
+
+ // set new baselayer
+ this.baseLayer = newBaseLayer;
+
+ if(!this.allOverlays || this.baseLayer.visibility) {
+ this.baseLayer.setVisibility(true);
+ // Layer may previously have been visible but not in range.
+ // In this case we need to redraw it to make it visible.
+ if (this.baseLayer.inRange === false) {
+ this.baseLayer.redraw();
+ }
+ }
+
+ // recenter the map
+ if (center != null) {
+ // new zoom level derived from old scale
+ var newZoom = this.getZoomForResolution(
+ newResolution || this.resolution, true
+ );
+ // zoom and force zoom change
+ this.setCenter(center, newZoom, false, true);
+ }
+
+ this.events.triggerEvent("changebaselayer", {
+ layer: this.baseLayer
+ });
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Control Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Controls to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addControl
+ * Add the passed over control to the map. Optionally
+ * position the control at the given pixel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControl: function (control, px) {
+ this.controls.push(control);
+ this.addControlToMap(control, px);
+ },
+
+ /**
+ * APIMethod: addControls
+ * Add all of the passed over controls to the map.
+ * You can pass over an optional second array
+ * with pixel-objects to position the controls.
+ * The indices of the two arrays should match and
+ * you can add null as pixel for those controls
+ * you want to be autopositioned.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)}
+ * pixels - {Array(<OpenLayers.Pixel>)}
+ */
+ addControls: function (controls, pixels) {
+ var pxs = (arguments.length === 1) ? [] : pixels;
+ for (var i=0, len=controls.length; i<len; i++) {
+ var ctrl = controls[i];
+ var px = (pxs[i]) ? pxs[i] : null;
+ this.addControl( ctrl, px );
+ }
+ },
+
+ /**
+ * Method: addControlToMap
+ *
+ * Parameters:
+ *
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControlToMap: function (control, px) {
+ // If a control doesn't have a div at this point, it belongs in the
+ // viewport.
+ control.outsideViewport = (control.div != null);
+
+ // If the map has a displayProjection, and the control doesn't, set
+ // the display projection.
+ if (this.displayProjection && !control.displayProjection) {
+ control.displayProjection = this.displayProjection;
+ }
+
+ control.setMap(this);
+ var div = control.draw(px);
+ if (div) {
+ if(!control.outsideViewport) {
+ div.style.zIndex = this.Z_INDEX_BASE['Control'] +
+ this.controls.length;
+ this.viewPortDiv.appendChild( div );
+ }
+ }
+ if(control.autoActivate) {
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: getControl
+ *
+ * Parameters:
+ * id - {String} ID of the control to return.
+ *
+ * Returns:
+ * {<OpenLayers.Control>} The control from the map's list of controls
+ * which has a matching 'id'. If none found,
+ * returns null.
+ */
+ getControl: function (id) {
+ var returnControl = null;
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ var control = this.controls[i];
+ if (control.id == id) {
+ returnControl = control;
+ break;
+ }
+ }
+ return returnControl;
+ },
+
+ /**
+ * APIMethod: removeControl
+ * Remove a control from the map. Removes the control both from the map
+ * object's internal array of controls, as well as from the map's
+ * viewPort (assuming the control was not added outsideViewport)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to remove.
+ */
+ removeControl: function (control) {
+ //make sure control is non-null and actually part of our map
+ if ( (control) && (control == this.getControl(control.id)) ) {
+ if (control.div && (control.div.parentNode == this.viewPortDiv)) {
+ this.viewPortDiv.removeChild(control.div);
+ }
+ OpenLayers.Util.removeItem(this.controls, control);
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Popup Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Popups to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addPopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ * exclusive - {Boolean} If true, closes all other popups first
+ */
+ addPopup: function(popup, exclusive) {
+
+ if (exclusive) {
+ //remove all other popups from screen
+ for (var i = this.popups.length - 1; i >= 0; --i) {
+ this.removePopup(this.popups[i]);
+ }
+ }
+
+ popup.map = this;
+ this.popups.push(popup);
+ var popupDiv = popup.draw();
+ if (popupDiv) {
+ popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
+ this.popups.length;
+ this.layerContainerDiv.appendChild(popupDiv);
+ }
+ },
+
+ /**
+ * APIMethod: removePopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ */
+ removePopup: function(popup) {
+ OpenLayers.Util.removeItem(this.popups, popup);
+ if (popup.div) {
+ try { this.layerContainerDiv.removeChild(popup.div); }
+ catch (e) { } // Popups sometimes apparently get disconnected
+ // from the layerContainerDiv, and cause complaints.
+ }
+ popup.map = null;
+ },
+
+ /********************************************************/
+ /* */
+ /* Container Div Functions */
+ /* */
+ /* The following functions deal with the access to */
+ /* and maintenance of the size of the container div */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
+ * size, in pixels, of the div into which OpenLayers
+ * has been loaded.
+ * Note - A clone() of this locally cached variable is
+ * returned, so as not to allow users to modify it.
+ */
+ getSize: function () {
+ var size = null;
+ if (this.size != null) {
+ size = this.size.clone();
+ }
+ return size;
+ },
+
+ /**
+ * APIMethod: updateSize
+ * This function should be called by any external code which dynamically
+ * changes the size of the map div (because mozilla wont let us catch
+ * the "onresize" for an element)
+ */
+ updateSize: function() {
+ // the div might have moved on the page, also
+ var newSize = this.getCurrentSize();
+ if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
+ this.events.clearMouseCache();
+ var oldSize = this.getSize();
+ if (oldSize == null) {
+ this.size = oldSize = newSize;
+ }
+ if (!newSize.equals(oldSize)) {
+
+ // store the new size
+ this.size = newSize;
+
+ //notify layers of mapresize
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ this.layers[i].onMapResize();
+ }
+
+ var center = this.getCachedCenter();
+
+ if (this.baseLayer != null && center != null) {
+ var zoom = this.getZoom();
+ this.zoom = null;
+ this.setCenter(center, zoom);
+ }
+
+ }
+ }
+ this.events.triggerEvent("updatesize");
+ },
+
+ /**
+ * Method: getCurrentSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
+ * of the map div
+ */
+ getCurrentSize: function() {
+
+ var size = new OpenLayers.Size(this.div.clientWidth,
+ this.div.clientHeight);
+
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = this.div.offsetWidth;
+ size.h = this.div.offsetHeight;
+ }
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = parseInt(this.div.style.width);
+ size.h = parseInt(this.div.style.height);
+ }
+ return size;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * center - {<OpenLayers.LonLat>} Default is this.getCenter()
+ * resolution - {float} Default is this.getResolution()
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds based on resolution, center, and
+ * current mapsize.
+ */
+ calculateBounds: function(center, resolution) {
+
+ var extent = null;
+
+ if (center == null) {
+ center = this.getCachedCenter();
+ }
+ if (resolution == null) {
+ resolution = this.getResolution();
+ }
+
+ if ((center != null) && (resolution != null)) {
+ var halfWDeg = (this.size.w * resolution) / 2;
+ var halfHDeg = (this.size.h * resolution) / 2;
+
+ extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ }
+
+ return extent;
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Zoom, Center, Pan Functions */
+ /* */
+ /* The following functions handle the validation, */
+ /* getting and setting of the Zoom Level and Center */
+ /* as well as the panning of the Map */
+ /* */
+ /********************************************************/
+ /**
+ * APIMethod: getCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCenter: function () {
+ var center = null;
+ var cachedCenter = this.getCachedCenter();
+ if (cachedCenter) {
+ center = cachedCenter.clone();
+ }
+ return center;
+ },
+
+ /**
+ * Method: getCachedCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCachedCenter: function() {
+ if (!this.center && this.size) {
+ this.center = this.getLonLatFromViewPortPx({
+ x: this.size.w / 2,
+ y: this.size.h / 2
+ });
+ }
+ return this.center;
+ },
+
+ /**
+ * APIMethod: getZoom
+ *
+ * Returns:
+ * {Integer}
+ */
+ getZoom: function () {
+ return this.zoom;
+ },
+
+ /**
+ * APIMethod: pan
+ * Allows user to pan by a value of screen pixels
+ *
+ * Parameters:
+ * dx - {Integer}
+ * dy - {Integer}
+ * options - {Object} Options to configure panning:
+ * - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
+ * - *dragging* {Boolean} Call setCenter with dragging true. Default is
+ * false.
+ */
+ pan: function(dx, dy, options) {
+ options = OpenLayers.Util.applyDefaults(options, {
+ animate: true,
+ dragging: false
+ });
+ if (options.dragging) {
+ if (dx != 0 || dy != 0) {
+ this.moveByPx(dx, dy);
+ }
+ } else {
+ // getCenter
+ var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());
+
+ // adjust
+ var newCenterPx = centerPx.add(dx, dy);
+
+ if (this.dragging || !newCenterPx.equals(centerPx)) {
+ var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
+ if (options.animate) {
+ this.panTo(newCenterLonLat);
+ } else {
+ this.moveTo(newCenterLonLat);
+ if(this.dragging) {
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }
+ }
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: panTo
+ * Allows user to pan to a new lonlat
+ * If the new lonlat is in the current extent the map will slide smoothly
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ panTo: function(lonlat) {
+ if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
+ var center = this.getCachedCenter();
+
+ // center will not change, don't do nothing
+ if (lonlat.equals(center)) {
+ return;
+ }
+
+ var from = this.getPixelFromLonLat(center);
+ var to = this.getPixelFromLonLat(lonlat);
+ var vector = { x: to.x - from.x, y: to.y - from.y };
+ var last = { x: 0, y: 0 };
+
+ this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
+ callbacks: {
+ eachStep: OpenLayers.Function.bind(function(px) {
+ var x = px.x - last.x,
+ y = px.y - last.y;
+ this.moveByPx(x, y);
+ last.x = Math.round(px.x);
+ last.y = Math.round(px.y);
+ }, this),
+ done: OpenLayers.Function.bind(function(px) {
+ this.moveTo(lonlat);
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }, this)
+ }
+ });
+ } else {
+ this.setCenter(lonlat);
+ }
+ },
+
+ /**
+ * APIMethod: setCenter
+ * Set the map center (and optionally, the zoom level).
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * zoom - {Integer} Optional zoom level.
+ * dragging - {Boolean} Specifies whether or not to trigger
+ * movestart/end events
+ * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom
+ * change events (needed on baseLayer change)
+ *
+ * TBD: reconsider forceZoomChange in 3.0
+ */
+ setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
+ if (this.panTween) {
+ this.panTween.stop();
+ }
+ if (this.zoomTween) {
+ this.zoomTween.stop();
+ }
+ this.moveTo(lonlat, zoom, {
+ 'dragging': dragging,
+ 'forceZoomChange': forceZoomChange
+ });
+ },
+
+ /**
+ * Method: moveByPx
+ * Drag the map by pixels.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ var hw = this.size.w / 2;
+ var hh = this.size.h / 2;
+ var x = hw + dx;
+ var y = hh + dy;
+ var wrapDateLine = this.baseLayer.wrapDateLine;
+ var xRestriction = 0;
+ var yRestriction = 0;
+ if (this.restrictedExtent) {
+ xRestriction = hw;
+ yRestriction = hh;
+ // wrapping the date line makes no sense for restricted extents
+ wrapDateLine = false;
+ }
+ dx = wrapDateLine ||
+ x <= this.maxPx.x - xRestriction &&
+ x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
+ dy = y <= this.maxPx.y - yRestriction &&
+ y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
+ if (dx || dy) {
+ if (!this.dragging) {
+ this.dragging = true;
+ this.events.triggerEvent("movestart");
+ }
+ this.center = null;
+ if (dx) {
+ this.layerContainerOriginPx.x -= dx;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ }
+ if (dy) {
+ this.layerContainerOriginPx.y -= dy;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ this.applyTransform();
+ var layer, i, len;
+ for (i=0, len=this.layers.length; i<len; ++i) {
+ layer = this.layers[i];
+ if (layer.visibility &&
+ (layer === this.baseLayer || layer.inRange)) {
+ layer.moveByPx(dx, dy);
+ layer.events.triggerEvent("move");
+ }
+ }
+ this.events.triggerEvent("move");
+ }
+ },
+
+ /**
+ * Method: adjustZoom
+ *
+ * Parameters:
+ * zoom - {Number} The zoom level to adjust
+ *
+ * Returns:
+ * {Integer} Adjusted zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent.
+ */
+ adjustZoom: function(zoom) {
+ if (this.baseLayer && this.baseLayer.wrapDateLine) {
+ var resolution, resolutions = this.baseLayer.resolutions,
+ maxResolution = this.getMaxExtent().getWidth() / this.size.w;
+ if (this.getResolutionForZoom(zoom) > maxResolution) {
+ if (this.fractionalZoom) {
+ zoom = this.getZoomForResolution(maxResolution);
+ } else {
+ for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
+ if (resolutions[i] <= maxResolution) {
+ zoom = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getMinZoom
+ * Returns the minimum zoom level for the current map view. If the base
+ * layer is configured with <wrapDateLine> set to true, this will be the
+ * first zoom level that shows no more than one world width in the current
+ * map viewport. Components that rely on this value (e.g. zoom sliders)
+ * should also listen to the map's "updatesize" event and call this method
+ * in the "updatesize" listener.
+ *
+ * Returns:
+ * {Number} Minimum zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
+ * configured with <fractionalZoom> set to true.
+ */
+ getMinZoom: function() {
+ return this.adjustZoom(0);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ * zoom - {Integer}
+ * options - {Object}
+ */
+ moveTo: function(lonlat, zoom, options) {
+ if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
+ lonlat = new OpenLayers.LonLat(lonlat);
+ }
+ if (!options) {
+ options = {};
+ }
+ if (zoom != null) {
+ zoom = parseFloat(zoom);
+ if (!this.fractionalZoom) {
+ zoom = Math.round(zoom);
+ }
+ }
+ var requestedZoom = zoom;
+ zoom = this.adjustZoom(zoom);
+ if (zoom !== requestedZoom) {
+ // zoom was adjusted, so keep old lonlat to avoid panning
+ lonlat = this.getCenter();
+ }
+ // dragging is false by default
+ var dragging = options.dragging || this.dragging;
+ // forceZoomChange is false by default
+ var forceZoomChange = options.forceZoomChange;
+
+ if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
+ lonlat = this.maxExtent.getCenterLonLat();
+ this.center = lonlat.clone();
+ }
+
+ if(this.restrictedExtent != null) {
+ // In 3.0, decide if we want to change interpretation of maxExtent.
+ if(lonlat == null) {
+ lonlat = this.center;
+ }
+ if(zoom == null) {
+ zoom = this.getZoom();
+ }
+ var resolution = this.getResolutionForZoom(zoom);
+ var extent = this.calculateBounds(lonlat, resolution);
+ if(!this.restrictedExtent.containsBounds(extent)) {
+ var maxCenter = this.restrictedExtent.getCenterLonLat();
+ if(extent.getWidth() > this.restrictedExtent.getWidth()) {
+ lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);
+ } else if(extent.left < this.restrictedExtent.left) {
+ lonlat = lonlat.add(this.restrictedExtent.left -
+ extent.left, 0);
+ } else if(extent.right > this.restrictedExtent.right) {
+ lonlat = lonlat.add(this.restrictedExtent.right -
+ extent.right, 0);
+ }
+ if(extent.getHeight() > this.restrictedExtent.getHeight()) {
+ lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);
+ } else if(extent.bottom < this.restrictedExtent.bottom) {
+ lonlat = lonlat.add(0, this.restrictedExtent.bottom -
+ extent.bottom);
+ }
+ else if(extent.top > this.restrictedExtent.top) {
+ lonlat = lonlat.add(0, this.restrictedExtent.top -
+ extent.top);
+ }
+ }
+ }
+
+ var zoomChanged = forceZoomChange || (
+ (this.isValidZoomLevel(zoom)) &&
+ (zoom != this.getZoom()) );
+
+ var centerChanged = (this.isValidLonLat(lonlat)) &&
+ (!lonlat.equals(this.center));
+
+ // if neither center nor zoom will change, no need to do anything
+ if (zoomChanged || centerChanged || dragging) {
+ dragging || this.events.triggerEvent("movestart", {
+ zoomChanged: zoomChanged
+ });
+
+ if (centerChanged) {
+ if (!zoomChanged && this.center) {
+ // if zoom hasnt changed, just slide layerContainer
+ // (must be done before setting this.center to new value)
+ this.centerLayerContainer(lonlat);
+ }
+ this.center = lonlat.clone();
+ }
+
+ var res = zoomChanged ?
+ this.getResolutionForZoom(zoom) : this.getResolution();
+ // (re)set the layerContainerDiv's location
+ if (zoomChanged || this.layerContainerOrigin == null) {
+ this.layerContainerOrigin = this.getCachedCenter();
+ this.layerContainerOriginPx.x = 0;
+ this.layerContainerOriginPx.y = 0;
+ this.applyTransform();
+ var maxExtent = this.getMaxExtent({restricted: true});
+ var maxExtentCenter = maxExtent.getCenterLonLat();
+ var lonDelta = this.center.lon - maxExtentCenter.lon;
+ var latDelta = maxExtentCenter.lat - this.center.lat;
+ var extentWidth = Math.round(maxExtent.getWidth() / res);
+ var extentHeight = Math.round(maxExtent.getHeight() / res);
+ this.minPx = {
+ x: (this.size.w - extentWidth) / 2 - lonDelta / res,
+ y: (this.size.h - extentHeight) / 2 - latDelta / res
+ };
+ this.maxPx = {
+ x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
+ y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
+ };
+ }
+
+ if (zoomChanged) {
+ this.zoom = zoom;
+ this.resolution = res;
+ }
+
+ var bounds = this.getExtent();
+
+ //send the move call to the baselayer and all the overlays
+
+ if(this.baseLayer.visibility) {
+ this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || this.baseLayer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+
+ bounds = this.baseLayer.getExtent();
+
+ for (var i=this.layers.length-1; i>=0; --i) {
+ var layer = this.layers[i];
+ if (layer !== this.baseLayer && !layer.isBaseLayer) {
+ var inRange = layer.calculateInRange();
+ if (layer.inRange != inRange) {
+ // the inRange property has changed. If the layer is
+ // no longer in range, we turn it off right away. If
+ // the layer is no longer out of range, the moveTo
+ // call below will turn on the layer.
+ layer.inRange = inRange;
+ if (!inRange) {
+ layer.display(false);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "visibility"
+ });
+ }
+ if (inRange && layer.visibility) {
+ layer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || layer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+ }
+ }
+
+ this.events.triggerEvent("move");
+ dragging || this.events.triggerEvent("moveend");
+
+ if (zoomChanged) {
+ //redraw popups
+ for (var i=0, len=this.popups.length; i<len; i++) {
+ this.popups[i].updatePosition();
+ }
+ this.events.triggerEvent("zoomend");
+ }
+ }
+ },
+
+ /**
+ * Method: centerLayerContainer
+ * This function takes care to recenter the layerContainerDiv.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ centerLayerContainer: function (lonlat) {
+ var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
+ var newPx = this.getViewPortPxFromLonLat(lonlat);
+
+ if ((originPx != null) && (newPx != null)) {
+ var oldLeft = this.layerContainerOriginPx.x;
+ var oldTop = this.layerContainerOriginPx.y;
+ var newLeft = Math.round(originPx.x - newPx.x);
+ var newTop = Math.round(originPx.y - newPx.y);
+ this.applyTransform(
+ (this.layerContainerOriginPx.x = newLeft),
+ (this.layerContainerOriginPx.y = newTop));
+ var dx = oldLeft - newLeft;
+ var dy = oldTop - newTop;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ },
+
+ /**
+ * Method: isValidZoomLevel
+ *
+ * Parameters:
+ * zoomLevel - {Integer}
+ *
+ * Returns:
+ * {Boolean} Whether or not the zoom level passed in is non-null and
+ * within the min/max range of zoom levels.
+ */
+ isValidZoomLevel: function(zoomLevel) {
+ return ( (zoomLevel != null) &&
+ (zoomLevel >= 0) &&
+ (zoomLevel < this.getNumZoomLevels()) );
+ },
+
+ /**
+ * Method: isValidLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Whether or not the lonlat passed in is non-null and within
+ * the maxExtent bounds
+ */
+ isValidLonLat: function(lonlat) {
+ var valid = false;
+ if (lonlat != null) {
+ var maxExtent = this.getMaxExtent();
+ var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
+ valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
+ }
+ return valid;
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Options */
+ /* */
+ /* Accessor functions to Layer Options parameters */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getProjection
+ * This method returns a string representing the projection. In
+ * the case of projection support, this will be the srsCode which
+ * is loaded -- otherwise it will simply be the string value that
+ * was passed to the projection at startup.
+ *
+ * FIXME: In 3.0, we will remove getProjectionObject, and instead
+ * return a Projection object from this function.
+ *
+ * Returns:
+ * {String} The Projection string from the base layer or null.
+ */
+ getProjection: function() {
+ var projection = this.getProjectionObject();
+ return projection ? projection.getCode() : null;
+ },
+
+ /**
+ * APIMethod: getProjectionObject
+ * Returns the projection obect from the baselayer.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} The Projection of the base layer.
+ */
+ getProjectionObject: function() {
+ var projection = null;
+ if (this.baseLayer != null) {
+ projection = this.baseLayer.projection;
+ }
+ return projection;
+ },
+
+ /**
+ * APIMethod: getMaxResolution
+ *
+ * Returns:
+ * {String} The Map's Maximum Resolution
+ */
+ getMaxResolution: function() {
+ var maxResolution = null;
+ if (this.baseLayer != null) {
+ maxResolution = this.baseLayer.maxResolution;
+ }
+ return maxResolution;
+ },
+
+ /**
+ * APIMethod: getMaxExtent
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} If true, returns restricted extent (if it is
+ * available.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The maxExtent property as set on the current
+ * baselayer, unless the 'restricted' option is set, in which case
+ * the 'restrictedExtent' option from the map is returned (if it
+ * is set).
+ */
+ getMaxExtent: function (options) {
+ var maxExtent = null;
+ if(options && options.restricted && this.restrictedExtent){
+ maxExtent = this.restrictedExtent;
+ } else if (this.baseLayer != null) {
+ maxExtent = this.baseLayer.maxExtent;
+ }
+ return maxExtent;
+ },
+
+ /**
+ * APIMethod: getNumZoomLevels
+ *
+ * Returns:
+ * {Integer} The total number of zoom levels that can be displayed by the
+ * current baseLayer.
+ */
+ getNumZoomLevels: function() {
+ var numZoomLevels = null;
+ if (this.baseLayer != null) {
+ numZoomLevels = this.baseLayer.numZoomLevels;
+ }
+ return numZoomLevels;
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API?, are all merely wrappers to the */
+ /* the same calls on whatever layer is set as */
+ /* the current base layer */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ * If no baselayer is set, returns null.
+ */
+ getExtent: function () {
+ var extent = null;
+ if (this.baseLayer != null) {
+ extent = this.baseLayer.getExtent();
+ }
+ return extent;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The current resolution of the map.
+ * If no baselayer is set, returns null.
+ */
+ getResolution: function () {
+ var resolution = null;
+ if (this.baseLayer != null) {
+ resolution = this.baseLayer.getResolution();
+ } else if(this.allOverlays === true && this.layers.length > 0) {
+ // while adding the 1st layer to the map in allOverlays mode,
+ // this.baseLayer is not set yet when we need the resolution
+ // for calculateInRange.
+ resolution = this.layers[0].getResolution();
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getUnits
+ *
+ * Returns:
+ * {Float} The current units of the map.
+ * If no baselayer is set, returns null.
+ */
+ getUnits: function () {
+ var units = null;
+ if (this.baseLayer != null) {
+ units = this.baseLayer.units;
+ }
+ return units;
+ },
+
+ /**
+ * APIMethod: getScale
+ *
+ * Returns:
+ * {Float} The current scale denominator of the map.
+ * If no baselayer is set, returns null.
+ */
+ getScale: function () {
+ var scale = null;
+ if (this.baseLayer != null) {
+ var res = this.getResolution();
+ var units = this.baseLayer.units;
+ scale = OpenLayers.Util.getScaleFromResolution(res, units);
+ }
+ return scale;
+ },
+
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified bounds.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForExtent: function (bounds, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForExtent(bounds, closest);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom. If no baselayer
+ * is set, returns null.
+ */
+ getResolutionForZoom: function(zoom) {
+ var resolution = null;
+ if(this.baseLayer) {
+ resolution = this.baseLayer.getResolutionForZoom(zoom);
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForResolution(resolution, closest);
+ }
+ return zoom;
+ },
+
+ /********************************************************/
+ /* */
+ /* Zooming Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API, are all merely wrappers to the */
+ /* the setCenter() function */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: zoomTo
+ * Zoom to a specific zoom level. Zooming will be animated unless the map
+ * is configured with {zoomMethod: null}. To zoom without animation, use
+ * <setCenter> without a lonlat argument.
+ *
+ * Parameters:
+ * zoom - {Integer}
+ */
+ zoomTo: function(zoom, xy) {
+ // non-API arguments:
+ // xy - {<OpenLayers.Pixel>} optional zoom origin
+
+ var map = this;
+ if (map.isValidZoomLevel(zoom)) {
+ if (map.baseLayer.wrapDateLine) {
+ zoom = map.adjustZoom(zoom);
+ }
+ if (map.zoomTween) {
+ var currentRes = map.getResolution(),
+ targetRes = map.getResolutionForZoom(zoom),
+ start = {scale: 1},
+ end = {scale: currentRes / targetRes};
+ if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
+ // update the end scale, and reuse the running zoomTween
+ map.zoomTween.finish = {
+ scale: map.zoomTween.finish.scale * end.scale
+ };
+ } else {
+ if (!xy) {
+ var size = map.getSize();
+ xy = {x: size.w / 2, y: size.h / 2};
+ }
+ map.zoomTween.start(start, end, map.zoomDuration, {
+ minFrameRate: 50, // don't spend much time zooming
+ callbacks: {
+ eachStep: function(data) {
+ var containerOrigin = map.layerContainerOriginPx,
+ scale = data.scale,
+ dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
+ dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
+ map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
+ },
+ done: function(data) {
+ map.applyTransform();
+ var resolution = map.getResolution() / data.scale,
+ zoom = map.getZoomForResolution(resolution, true)
+ map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
+ }
+ }
+ });
+ }
+ } else {
+ var center = xy ?
+ map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
+ null;
+ map.setCenter(center, zoom);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: zoomIn
+ *
+ */
+ zoomIn: function() {
+ this.zoomTo(this.getZoom() + 1);
+ },
+
+ /**
+ * APIMethod: zoomOut
+ *
+ */
+ zoomOut: function() {
+ this.zoomTo(this.getZoom() - 1);
+ },
+
+ /**
+ * APIMethod: zoomToExtent
+ * Zoom to the passed in bounds, recenter
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToExtent: function(bounds, closest) {
+ if (!(bounds instanceof OpenLayers.Bounds)) {
+ bounds = new OpenLayers.Bounds(bounds);
+ }
+ var center = bounds.getCenterLonLat();
+ if (this.baseLayer.wrapDateLine) {
+ var maxExtent = this.getMaxExtent();
+
+ //fix straddling bounds (in the case of a bbox that straddles the
+ // dateline, it's left and right boundaries will appear backwards.
+ // we fix this by allowing a right value that is greater than the
+ // max value at the dateline -- this allows us to pass a valid
+ // bounds to calculate zoom)
+ //
+ bounds = bounds.clone();
+ while (bounds.right < bounds.left) {
+ bounds.right += maxExtent.getWidth();
+ }
+ //if the bounds was straddling (see above), then the center point
+ // we got from it was wrong. So we take our new bounds and ask it
+ // for the center.
+ //
+ center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
+ }
+ this.setCenter(center, this.getZoomForExtent(bounds, closest));
+ },
+
+ /**
+ * APIMethod: zoomToMaxExtent
+ * Zoom to the full extent and recenter.
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} True to zoom to restricted extent if it is
+ * set. Defaults to true.
+ */
+ zoomToMaxExtent: function(options) {
+ //restricted is true by default
+ var restricted = (options) ? options.restricted : true;
+
+ var maxExtent = this.getMaxExtent({
+ 'restricted': restricted
+ });
+ this.zoomToExtent(maxExtent);
+ },
+
+ /**
+ * APIMethod: zoomToScale
+ * Zoom to a specified scale
+ *
+ * Parameters:
+ * scale - {float}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified scale. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToScale: function(scale, closest) {
+ var res = OpenLayers.Util.getResolutionFromScale(scale,
+ this.baseLayer.units);
+
+ var halfWDeg = (this.size.w * res) / 2;
+ var halfHDeg = (this.size.h * res) / 2;
+ var center = this.getCachedCenter();
+
+ var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ this.zoomToExtent(extent, closest);
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate between */
+ /* LonLat, LayerPx, and ViewPortPx */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: LonLat <-> ViewPortPx
+ //
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port <OpenLayers.Pixel>, translated into lon/lat
+ * by the current base layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if (this.baseLayer != null) {
+ lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into view port
+ * pixels by the current base layer.
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var px = null;
+ if (this.baseLayer != null) {
+ px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
+ }
+ return px;
+ },
+
+ /**
+ * Method: getZoomTargetCenter
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
+ * resolution - {Float} The resolution we want to get the center for
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The location of the map center after the
+ * transformation described by the origin xy and the target resolution.
+ */
+ getZoomTargetCenter: function (xy, resolution) {
+ var lonlat = null,
+ size = this.getSize(),
+ deltaX = size.w/2 - xy.x,
+ deltaY = xy.y - size.h/2,
+ zoomPoint = this.getLonLatFromPixel(xy);
+ if (zoomPoint) {
+ lonlat = new OpenLayers.LonLat(
+ zoomPoint.lon + deltaX * resolution,
+ zoomPoint.lat + deltaY * resolution
+ );
+ }
+ return lonlat;
+ },
+
+ //
+ // CONVENIENCE TRANSLATION FUNCTIONS FOR API
+ //
+
+ /**
+ * APIMethod: getLonLatFromPixel
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
+ * OpenLayers.Pixel, translated into lon/lat by the
+ * current base layer
+ */
+ getLonLatFromPixel: function (px) {
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getPixelFromLonLat
+ * Returns a pixel location given a map location. The map location is
+ * translated to an integer pixel location (in viewport pixel
+ * coordinates) by the current base layer.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} A map location.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
+ * <OpenLayers.LonLat> translated into view port pixels by the current
+ * base layer.
+ */
+ getPixelFromLonLat: function (lonlat) {
+ var px = this.getViewPortPxFromLonLat(lonlat);
+ px.x = Math.round(px.x);
+ px.y = Math.round(px.y);
+ return px;
+ },
+
+ /**
+ * Method: getGeodesicPixelSize
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
+ * not provided, the center pixel of the map viewport will be used.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The geodesic size of the pixel in kilometers.
+ */
+ getGeodesicPixelSize: function(px) {
+ var lonlat = px ? this.getLonLatFromPixel(px) : (
+ this.getCachedCenter() || new OpenLayers.LonLat(0, 0));
+ var res = this.getResolution();
+ var left = lonlat.add(-res / 2, 0);
+ var right = lonlat.add(res / 2, 0);
+ var bottom = lonlat.add(0, -res / 2);
+ var top = lonlat.add(0, res / 2);
+ var dest = new OpenLayers.Projection("EPSG:4326");
+ var source = this.getProjectionObject() || dest;
+ if(!source.equals(dest)) {
+ left.transform(source, dest);
+ right.transform(source, dest);
+ bottom.transform(source, dest);
+ top.transform(source, dest);
+ }
+
+ return new OpenLayers.Size(
+ OpenLayers.Util.distVincenty(left, right),
+ OpenLayers.Util.distVincenty(bottom, top)
+ );
+ },
+
+
+
+ //
+ // TRANSLATION: ViewPortPx <-> LayerPx
+ //
+
+ /**
+ * APIMethod: getViewPortPxFromLayerPx
+ *
+ * Parameters:
+ * layerPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel
+ * coordinates
+ */
+ getViewPortPxFromLayerPx:function(layerPx) {
+ var viewPortPx = null;
+ if (layerPx != null) {
+ var dX = this.layerContainerOriginPx.x;
+ var dY = this.layerContainerOriginPx.y;
+ viewPortPx = layerPx.add(dX, dY);
+ }
+ return viewPortPx;
+ },
+
+ /**
+ * APIMethod: getLayerPxFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel
+ * coordinates
+ */
+ getLayerPxFromViewPortPx:function(viewPortPx) {
+ var layerPx = null;
+ if (viewPortPx != null) {
+ var dX = -this.layerContainerOriginPx.x;
+ var dY = -this.layerContainerOriginPx.y;
+ layerPx = viewPortPx.add(dX, dY);
+ if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
+ layerPx = null;
+ }
+ }
+ return layerPx;
+ },
+
+ //
+ // TRANSLATION: LonLat <-> LayerPx
+ //
+
+ /**
+ * Method: getLonLatFromLayerPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getLonLatFromLayerPx: function (px) {
+ //adjust for displacement of layerContainerDiv
+ px = this.getViewPortPxFromLayerPx(px);
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getLayerPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} lonlat
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into layer pixels
+ * by the current base layer
+ */
+ getLayerPxFromLonLat: function (lonlat) {
+ //adjust for displacement of layerContainerDiv
+ var px = this.getPixelFromLonLat(lonlat);
+ return this.getLayerPxFromViewPortPx(px);
+ },
+
+ /**
+ * Method: applyTransform
+ * Applies the given transform to the <layerContainerDiv>. This method has
+ * a 2-stage fallback from translate3d/scale3d via translate/scale to plain
+ * style.left/style.top, in which case no scaling is supported.
+ *
+ * Parameters:
+ * x - {Number} x parameter for the translation. Defaults to the x value of
+ * the map's <layerContainerOriginPx>
+ * y - {Number} y parameter for the translation. Defaults to the y value of
+ * the map's <layerContainerOriginPx>
+ * scale - {Number} scale. Defaults to 1 if not provided.
+ */
+ applyTransform: function(x, y, scale) {
+ scale = scale || 1;
+ var origin = this.layerContainerOriginPx,
+ needTransform = scale !== 1;
+ x = x || origin.x;
+ y = y || origin.y;
+
+ var style = this.layerContainerDiv.style,
+ transform = this.applyTransform.transform,
+ template = this.applyTransform.template;
+
+ if (transform === undefined) {
+ transform = OpenLayers.Util.vendorPrefix.style('transform');
+ this.applyTransform.transform = transform;
+ if (transform) {
+ // Try translate3d, but only if the viewPortDiv has a transform
+ // defined in a stylesheet
+ var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
+ OpenLayers.Util.vendorPrefix.css('transform'));
+ if (!computedStyle || computedStyle !== 'none') {
+ template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
+ style[transform] = [template[0], '0,0', template[1]].join('');
+ }
+ // If no transform is defined in the stylesheet or translate3d
+ // does not stick, use translate and scale
+ if (!template || !~style[transform].indexOf(template[0])) {
+ template = ['translate(', ') ', 'scale(', ')'];
+ }
+ this.applyTransform.template = template;
+ }
+ }
+
+ // If we do 3d transforms, we always want to use them. If we do 2d
+ // transforms, we only use them when we need to.
+ if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
+ // Our 2d transforms are combined with style.left and style.top, so
+ // adjust x and y values and set the origin as left and top
+ if (needTransform === true && template[0] === 'translate(') {
+ x -= origin.x;
+ y -= origin.y;
+ style.left = origin.x + 'px';
+ style.top = origin.y + 'px';
+ }
+ style[transform] = [
+ template[0], x, 'px,', y, 'px', template[1],
+ template[2], scale, ',', scale, template[3]
+ ].join('');
+ } else {
+ style.left = x + 'px';
+ style.top = y + 'px';
+ // We previously might have had needTransform, so remove transform
+ if (transform !== null) {
+ style[transform] = '';
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Map"
+});
+
+/**
+ * Constant: TILE_WIDTH
+ * {Integer} 256 Default tile width (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_WIDTH = 256;
+/**
+ * Constant: TILE_HEIGHT
+ * {Integer} 256 Default tile height (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_HEIGHT = 256;
+/* ======================================================================
+ OpenLayers/Layer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer
+ */
+OpenLayers.Layer = OpenLayers.Class({
+
+ /**
+ * APIProperty: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * APIProperty: opacity
+ * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
+ * is 1.
+ */
+ opacity: 1,
+
+ /**
+ * APIProperty: alwaysInRange
+ * {Boolean} If a layer's display should not be scale-based, this should
+ * be set to true. This will cause the layer, as an overlay, to always
+ * be 'active', by always returning true from the calculateInRange()
+ * function.
+ *
+ * If not explicitly specified for a layer, its value will be
+ * determined on startup in initResolutions() based on whether or not
+ * any scale-specific properties have been set as options on the
+ * layer. If no scale-specific options have been set on the layer, we
+ * assume that it should always be in range.
+ *
+ * See #987 for more info.
+ */
+ alwaysInRange: null,
+
+ /**
+ * Constant: RESOLUTION_PROPERTIES
+ * {Array} The properties that are used for calculating resolutions
+ * information.
+ */
+ RESOLUTION_PROPERTIES: [
+ 'scales', 'resolutions',
+ 'maxScale', 'minScale',
+ 'maxResolution', 'minResolution',
+ 'numZoomLevels', 'maxZoomLevel'
+ ],
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types:
+ * loadstart - Triggered when layer loading starts. When using a Vector
+ * layer with a Fixed or BBOX strategy, the event object includes
+ * a *filter* property holding the OpenLayers.Filter used when
+ * calling read on the protocol.
+ * loadend - Triggered when layer loading ends. When using a Vector layer
+ * with a Fixed or BBOX strategy, the event object includes a
+ * *response* property holding an OpenLayers.Protocol.Response object.
+ * visibilitychanged - Triggered when the layer's visibility property is
+ * changed, e.g. by turning the layer on or off in the layer switcher.
+ * Note that the actual visibility of the layer can also change if it
+ * gets out of range (see <calculateInRange>). If you also want to catch
+ * these cases, register for the map's 'changelayer' event instead.
+ * move - Triggered when layer moves (triggered with every mousemove
+ * during a drag).
+ * moveend - Triggered when layer is done moving, object passed as
+ * argument has a zoomChanged boolean property which tells that the
+ * zoom has changed.
+ * added - Triggered after the layer is added to a map. Listeners will
+ * receive an object with a *map* property referencing the map and a
+ * *layer* property referencing the layer.
+ * removed - Triggered after the layer is removed from the map. Listeners
+ * will receive an object with a *map* property referencing the map and
+ * a *layer* property referencing the layer.
+ */
+ events: null,
+
+ /**
+ * APIProperty: map
+ * {<OpenLayers.Map>} This variable is set when the layer is added to
+ * the map, via the accessor function setMap().
+ */
+ map: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Whether or not the layer is a base layer. This should be set
+ * individually by all subclasses. Default is false
+ */
+ isBaseLayer: false,
+
+ /**
+ * Property: alpha
+ * {Boolean} The layer's images have an alpha channel. Default is false.
+ */
+ alpha: false,
+
+ /**
+ * APIProperty: displayInLayerSwitcher
+ * {Boolean} Display the layer's name in the layer switcher. Default is
+ * true.
+ */
+ displayInLayerSwitcher: true,
+
+ /**
+ * APIProperty: visibility
+ * {Boolean} The layer should be displayed in the map. Default is true.
+ */
+ visibility: true,
+
+ /**
+ * APIProperty: attribution
+ * {String} Attribution string, displayed when an
+ * <OpenLayers.Control.Attribution> has been added to the map.
+ */
+ attribution: null,
+
+ /**
+ * Property: inRange
+ * {Boolean} The current map resolution is within the layer's min/max
+ * range. This is set in <OpenLayers.Map.setCenter> whenever the zoom
+ * changes.
+ */
+ inRange: false,
+
+ /**
+ * Propery: imageSize
+ * {<OpenLayers.Size>} For layers with a gutter, the image is larger than
+ * the tile by twice the gutter in each dimension.
+ */
+ imageSize: null,
+
+ // OPTIONS
+
+ /**
+ * Property: options
+ * {Object} An optional object whose properties will be set on the layer.
+ * Any of the layer properties can be set as a property of the options
+ * object and sent to the constructor when the layer is created.
+ */
+ options: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: gutter
+ * {Integer} Determines the width (in pixels) of the gutter around image
+ * tiles to ignore. By setting this property to a non-zero value,
+ * images will be requested that are wider and taller than the tile
+ * size by a value of 2 x gutter. This allows artifacts of rendering
+ * at tile edges to be ignored. Set a gutter value that is equal to
+ * half the size of the widest symbol that needs to be displayed.
+ * Defaults to zero. Non-tiled layers always have zero gutter.
+ */
+ gutter: 0,
+
+ /**
+ * APIProperty: projection
+ * {<OpenLayers.Projection>} or {<String>} Specifies the projection of the layer.
+ * Can be set in the layer options. If not specified in the layer options,
+ * it is set to the default projection specified in the map,
+ * when the layer is added to the map.
+ * Projection along with default maxExtent and resolutions
+ * are set automatically with commercial baselayers in EPSG:3857,
+ * such as Google, Bing and OpenStreetMap, and do not need to be specified.
+ * Otherwise, if specifying projection, also set maxExtent,
+ * maxResolution or resolutions as appropriate.
+ * When using vector layers with strategies, layer projection should be set
+ * to the projection of the source data if that is different from the map default.
+ *
+ * Can be either a string or an <OpenLayers.Projection> object;
+ * if a string is passed, will be converted to an object when
+ * the layer is added to the map.
+ *
+ */
+ projection: null,
+
+ /**
+ * APIProperty: units
+ * {String} The layer map units. Defaults to null. Possible values
+ * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
+ * Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units.
+ */
+ units: null,
+
+ /**
+ * APIProperty: scales
+ * {Array} An array of map scales in descending order. The values in the
+ * array correspond to the map scale denominator. Note that these
+ * values only make sense if the display (monitor) resolution of the
+ * client is correctly guessed by whomever is configuring the
+ * application. In addition, the units property must also be set.
+ * Use <resolutions> instead wherever possible.
+ */
+ scales: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array} A list of map resolutions (map units per pixel) in descending
+ * order. If this is not set in the layer constructor, it will be set
+ * based on other resolution related properties (maxExtent,
+ * maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the layer. Defaults to null.
+ *
+ * The center of these bounds will not stray outside
+ * of the viewport extent during panning. In addition, if
+ * <displayOutsideMaxExtent> is set to false, data will not be
+ * requested that falls completely outside of these bounds.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the layer. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Default max is 360 deg / 256 px, which corresponds to
+ * zoom level 0 on gmaps. Specify a different value in the layer
+ * options if you are not using the default <OpenLayers.Map.tileSize>
+ * and displaying the whole world.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer}
+ */
+ numZoomLevels: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: displayOutsideMaxExtent
+ * {Boolean} Request map tiles that are completely outside of the max
+ * extent for this layer. Defaults to false.
+ */
+ displayOutsideMaxExtent: false,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Wraps the world at the international dateline, so the map can
+ * be panned infinitely in longitudinal direction. Only use this on the
+ * base layer, and only if the layer's maxExtent equals the world bounds.
+ * #487 for more info.
+ */
+ wrapDateLine: false,
+
+ /**
+ * Property: metadata
+ * {Object} This object can be used to store additional information on a
+ * layer object.
+ */
+ metadata: null,
+
+ /**
+ * Constructor: OpenLayers.Layer
+ *
+ * Parameters:
+ * name - {String} The layer name
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+
+ this.metadata = {};
+
+ options = OpenLayers.Util.extend({}, options);
+ // make sure we respect alwaysInRange if set on the prototype
+ if (this.alwaysInRange != null) {
+ options.alwaysInRange = this.alwaysInRange;
+ }
+ this.addOptions(options);
+
+ this.name = name;
+
+ if (this.id == null) {
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.style.width = "100%";
+ this.div.style.height = "100%";
+ this.div.dir = "ltr";
+
+ this.events = new OpenLayers.Events(this, this.div);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Destroy is a destructor: this is to alleviate cyclic references which
+ * the Javascript garbage cleaner can not take care of on its own.
+ *
+ * Parameters:
+ * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
+ * been destroyed. Default is true.
+ */
+ destroy: function(setNewBaseLayer) {
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+ if (this.map != null) {
+ this.map.removeLayer(this, setNewBaseLayer);
+ }
+ this.projection = null;
+ this.map = null;
+ this.name = null;
+ this.div = null;
+ this.options = null;
+
+ if (this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ }
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: clone
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Layer>} The layer to be cloned
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer(this.name, this.getOptions());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ // a cloned layer should never have its map property set
+ // because it has not been added to a map yet.
+ obj.map = null;
+
+ return obj;
+ },
+
+ /**
+ * Method: getOptions
+ * Extracts an object from the layer with the properties that were set as
+ * options, but updates them with the values currently set on the
+ * instance.
+ *
+ * Returns:
+ * {Object} the <options> of the layer, representing the current state.
+ */
+ getOptions: function() {
+ var options = {};
+ for(var o in this.options) {
+ options[o] = this[o];
+ }
+ return options;
+ },
+
+ /**
+ * APIMethod: setName
+ * Sets the new layer name for this layer. Can trigger a changelayer event
+ * on the map.
+ *
+ * Parameters:
+ * newName - {String} The new name.
+ */
+ setName: function(newName) {
+ if (newName != this.name) {
+ this.name = newName;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "name"
+ });
+ }
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+
+ if (this.options == null) {
+ this.options = {};
+ }
+
+ if (newOptions) {
+ // make sure this.projection references a projection object
+ if(typeof newOptions.projection == "string") {
+ newOptions.projection = new OpenLayers.Projection(newOptions.projection);
+ }
+ if (newOptions.projection) {
+ // get maxResolution, units and maxExtent from projection defaults if
+ // they are not defined already
+ OpenLayers.Util.applyDefaults(newOptions,
+ OpenLayers.Projection.defaults[newOptions.projection.getCode()]);
+ }
+ // allow array for extents
+ if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) {
+ newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent);
+ }
+ if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) {
+ newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent);
+ }
+ }
+
+ // update our copy for clone
+ OpenLayers.Util.extend(this.options, newOptions);
+
+ // add new options to this
+ OpenLayers.Util.extend(this, newOptions);
+
+ // get the units from the projection, if we have a projection
+ // and it it has units
+ if(this.projection && this.projection.getUnits()) {
+ this.units = this.projection.getUnits();
+ }
+
+ // re-initialize resolutions if necessary, i.e. if any of the
+ // properties of the "properties" array defined below is set
+ // in the new options
+ if(this.map) {
+ // store current resolution so we can try to restore it later
+ var resolution = this.map.getResolution();
+ var properties = this.RESOLUTION_PROPERTIES.concat(
+ ["projection", "units", "minExtent", "maxExtent"]
+ );
+ for(var o in newOptions) {
+ if(newOptions.hasOwnProperty(o) &&
+ OpenLayers.Util.indexOf(properties, o) >= 0) {
+
+ this.initResolutions();
+ if (reinitialize && this.map.baseLayer === this) {
+ // update map position, and restore previous resolution
+ this.map.setCenter(this.map.getCenter(),
+ this.map.getZoomForResolution(resolution),
+ false, true
+ );
+ // trigger a changebaselayer event to make sure that
+ // all controls (especially
+ // OpenLayers.Control.PanZoomBar) get notified of the
+ // new options
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: onMapResize
+ * This function can be implemented by subclasses
+ */
+ onMapResize: function() {
+ //this function can be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function() {
+ var redrawn = false;
+ if (this.map) {
+
+ // min/max Range may have changed
+ this.inRange = this.calculateInRange();
+
+ // map's center might not yet be set
+ var extent = this.getExtent();
+
+ if (extent && this.inRange && this.visibility) {
+ var zoomChanged = true;
+ this.moveTo(extent, zoomChanged, false);
+ this.events.triggerEvent("moveend",
+ {"zoomChanged": zoomChanged});
+ redrawn = true;
+ }
+ }
+ return redrawn;
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ var display = this.visibility;
+ if (!this.isBaseLayer) {
+ display = display && this.inRange;
+ }
+ this.display(display);
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Here we take care to bring over any of the necessary default
+ * properties from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ if (this.map == null) {
+
+ this.map = map;
+
+ // grab some essential layer data from the map if it hasn't already
+ // been set
+ this.maxExtent = this.maxExtent || this.map.maxExtent;
+ this.minExtent = this.minExtent || this.map.minExtent;
+
+ this.projection = this.projection || this.map.projection;
+ if (typeof this.projection == "string") {
+ this.projection = new OpenLayers.Projection(this.projection);
+ }
+
+ // Check the projection to see if we can get units -- if not, refer
+ // to properties.
+ this.units = this.projection.getUnits() ||
+ this.units || this.map.units;
+
+ this.initResolutions();
+
+ if (!this.isBaseLayer) {
+ this.inRange = this.calculateInRange();
+ var show = ((this.visibility) && (this.inRange));
+ this.div.style.display = show ? "" : "none";
+ }
+
+ // deal with gutters
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. To be overridden by subclasses.
+ */
+ afterAdd: function() {
+ },
+
+ /**
+ * APIMethod: removeMap
+ * Just as setMap() allows each layer the possibility to take a
+ * personalized action on being added to the map, removeMap() allows
+ * each layer to take a personalized action on being removed from it.
+ * For now, this will be mostly unused, except for the EventPane layer,
+ * which needs this hook so that it can remove the special invisible
+ * pane.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * APIMethod: getImageSize
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
+ * by subclasses that have to deal with different tile sizes at the
+ * layer extent edges (e.g. Zoomify)
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size that the image should be, taking into
+ * account gutters.
+ */
+ getImageSize: function(bounds) {
+ return (this.imageSize || this.tileSize);
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Set the tile size based on the map size. This also sets layer.imageSize
+ * or use by Tile.Image.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ var tileSize = (size) ? size :
+ ((this.tileSize) ? this.tileSize :
+ this.map.getTileSize());
+ this.tileSize = tileSize;
+ if(this.gutter) {
+ // layers with gutters need non-null tile sizes
+ //if(tileSize == null) {
+ // OpenLayers.console.error("Error in layer.setMap() for " +
+ // this.name + ": layers with " +
+ // "gutters need non-null tile sizes");
+ //}
+ this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter),
+ tileSize.h + (2*this.gutter));
+ }
+ },
+
+ /**
+ * APIMethod: getVisibility
+ *
+ * Returns:
+ * {Boolean} The layer should be displayed (if in range).
+ */
+ getVisibility: function() {
+ return this.visibility;
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visibility - {Boolean} Whether or not to display the layer (if in range)
+ */
+ setVisibility: function(visibility) {
+ if (visibility != this.visibility) {
+ this.visibility = visibility;
+ this.display(visibility);
+ this.redraw();
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "visibility"
+ });
+ }
+ this.events.triggerEvent("visibilitychanged");
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer. This is designed to be used internally, and
+ * is not generally the way to enable or disable the layer. For that,
+ * use the setVisibility function instead..
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ if (display != (this.div.style.display != "none")) {
+ this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
+ }
+ },
+
+ /**
+ * APIMethod: calculateInRange
+ *
+ * Returns:
+ * {Boolean} The layer is displayable at the current map's current
+ * resolution. Note that if 'alwaysInRange' is true for the layer,
+ * this function will always return true.
+ */
+ calculateInRange: function() {
+ var inRange = false;
+
+ if (this.alwaysInRange) {
+ inRange = true;
+ } else {
+ if (this.map) {
+ var resolution = this.map.getResolution();
+ inRange = ( (resolution >= this.minResolution) &&
+ (resolution <= this.maxResolution) );
+ }
+ }
+ return inRange;
+ },
+
+ /**
+ * APIMethod: setIsBaseLayer
+ *
+ * Parameters:
+ * isBaseLayer - {Boolean}
+ */
+ setIsBaseLayer: function(isBaseLayer) {
+ if (isBaseLayer != this.isBaseLayer) {
+ this.isBaseLayer = isBaseLayer;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: initResolutions
+ * This method's responsibility is to set up the 'resolutions' array
+ * for the layer -- this array is what the layer will use to interface
+ * between the zoom levels of the map and the resolution display
+ * of the layer.
+ *
+ * The user has several options that determine how the array is set up.
+ *
+ * For a detailed explanation, see the following wiki from the
+ * openlayers.org homepage:
+ * http://trac.openlayers.org/wiki/SettingZoomLevels
+ */
+ initResolutions: function() {
+
+ // ok we want resolutions, here's our strategy:
+ //
+ // 1. if resolutions are defined in the layer config, use them
+ // 2. else, if scales are defined in the layer config then derive
+ // resolutions from these scales
+ // 3. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // layer config
+ // 4. if we still don't have resolutions, and if resolutions
+ // are defined in the same, use them
+ // 5. else, if scales are defined in the map then derive
+ // resolutions from these scales
+ // 6. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // map
+ // 7. hope for the best!
+
+ var i, len, p;
+ var props = {}, alwaysInRange = true;
+
+ // get resolution data from layer config
+ // (we also set alwaysInRange in the layer as appropriate)
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p];
+ if(alwaysInRange && this.options[p]) {
+ alwaysInRange = false;
+ }
+ }
+ if(this.options.alwaysInRange == null) {
+ this.alwaysInRange = alwaysInRange;
+ }
+
+ // if we don't have resolutions then attempt to derive them from scales
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+
+ // if we still don't have resolutions then attempt to calculate them
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+
+ // if we couldn't calculate resolutions then we look at we have
+ // in the map
+ if(props.resolutions == null) {
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p] != null ?
+ this.options[p] : this.map[p];
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+ }
+
+ // ok, we new need to set properties in the instance
+
+ // get maxResolution from the config if it's defined there
+ var maxResolution;
+ if(this.options.maxResolution &&
+ this.options.maxResolution !== "auto") {
+ maxResolution = this.options.maxResolution;
+ }
+ if(this.options.minScale) {
+ maxResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.minScale, this.units);
+ }
+
+ // get minResolution from the config if it's defined there
+ var minResolution;
+ if(this.options.minResolution &&
+ this.options.minResolution !== "auto") {
+ minResolution = this.options.minResolution;
+ }
+ if(this.options.maxScale) {
+ minResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.maxScale, this.units);
+ }
+
+ if(props.resolutions) {
+
+ //sort resolutions array descendingly
+ props.resolutions.sort(function(a, b) {
+ return (b - a);
+ });
+
+ // if we still don't have a maxResolution get it from the
+ // resolutions array
+ if(!maxResolution) {
+ maxResolution = props.resolutions[0];
+ }
+
+ // if we still don't have a minResolution get it from the
+ // resolutions array
+ if(!minResolution) {
+ var lastIdx = props.resolutions.length - 1;
+ minResolution = props.resolutions[lastIdx];
+ }
+ }
+
+ this.resolutions = props.resolutions;
+ if(this.resolutions) {
+ len = this.resolutions.length;
+ this.scales = new Array(len);
+ for(i=0; i<len; i++) {
+ this.scales[i] = OpenLayers.Util.getScaleFromResolution(
+ this.resolutions[i], this.units);
+ }
+ this.numZoomLevels = len;
+ }
+ this.minResolution = minResolution;
+ if(minResolution) {
+ this.maxScale = OpenLayers.Util.getScaleFromResolution(
+ minResolution, this.units);
+ }
+ this.maxResolution = maxResolution;
+ if(maxResolution) {
+ this.minScale = OpenLayers.Util.getScaleFromResolution(
+ maxResolution, this.units);
+ }
+ },
+
+ /**
+ * Method: resolutionsFromScales
+ * Derive resolutions from scales.
+ *
+ * Parameters:
+ * scales - {Array(Number)} Scales
+ *
+ * Returns
+ * {Array(Number)} Resolutions
+ */
+ resolutionsFromScales: function(scales) {
+ if(scales == null) {
+ return;
+ }
+ var resolutions, i, len;
+ len = scales.length;
+ resolutions = new Array(len);
+ for(i=0; i<len; i++) {
+ resolutions[i] = OpenLayers.Util.getResolutionFromScale(
+ scales[i], this.units);
+ }
+ return resolutions;
+ },
+
+ /**
+ * Method: calculateResolutions
+ * Calculate resolutions based on the provided properties.
+ *
+ * Parameters:
+ * props - {Object} Properties
+ *
+ * Returns:
+ * {Array({Number})} Array of resolutions.
+ */
+ calculateResolutions: function(props) {
+
+ var viewSize, wRes, hRes;
+
+ // determine maxResolution
+ var maxResolution = props.maxResolution;
+ if(props.minScale != null) {
+ maxResolution =
+ OpenLayers.Util.getResolutionFromScale(props.minScale,
+ this.units);
+ } else if(maxResolution == "auto" && this.maxExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.maxExtent.getWidth() / viewSize.w;
+ hRes = this.maxExtent.getHeight() / viewSize.h;
+ maxResolution = Math.max(wRes, hRes);
+ }
+
+ // determine minResolution
+ var minResolution = props.minResolution;
+ if(props.maxScale != null) {
+ minResolution =
+ OpenLayers.Util.getResolutionFromScale(props.maxScale,
+ this.units);
+ } else if(props.minResolution == "auto" && this.minExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.minExtent.getWidth() / viewSize.w;
+ hRes = this.minExtent.getHeight()/ viewSize.h;
+ minResolution = Math.max(wRes, hRes);
+ }
+
+ if(typeof maxResolution !== "number" &&
+ typeof minResolution !== "number" &&
+ this.maxExtent != null) {
+ // maxResolution for default grid sets assumes that at zoom
+ // level zero, the whole world fits on one tile.
+ var tileSize = this.map.getTileSize();
+ maxResolution = Math.max(
+ this.maxExtent.getWidth() / tileSize.w,
+ this.maxExtent.getHeight() / tileSize.h
+ );
+ }
+
+ // determine numZoomLevels
+ var maxZoomLevel = props.maxZoomLevel;
+ var numZoomLevels = props.numZoomLevels;
+ if(typeof minResolution === "number" &&
+ typeof maxResolution === "number" && numZoomLevels === undefined) {
+ var ratio = maxResolution / minResolution;
+ numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
+ } else if(numZoomLevels === undefined && maxZoomLevel != null) {
+ numZoomLevels = maxZoomLevel + 1;
+ }
+
+ // are we able to calculate resolutions?
+ if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
+ (typeof maxResolution !== "number" &&
+ typeof minResolution !== "number")) {
+ return;
+ }
+
+ // now we have numZoomLevels and at least one of maxResolution
+ // or minResolution, we can populate the resolutions array
+
+ var resolutions = new Array(numZoomLevels);
+ var base = 2;
+ if(typeof minResolution == "number" &&
+ typeof maxResolution == "number") {
+ // if maxResolution and minResolution are set, we calculate
+ // the base for exponential scaling that starts at
+ // maxResolution and ends at minResolution in numZoomLevels
+ // steps.
+ base = Math.pow(
+ (maxResolution / minResolution),
+ (1 / (numZoomLevels - 1))
+ );
+ }
+
+ var i;
+ if(typeof maxResolution === "number") {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[i] = maxResolution / Math.pow(base, i);
+ }
+ } else {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[numZoomLevels - 1 - i] =
+ minResolution * Math.pow(base, i);
+ }
+ }
+
+ return resolutions;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The currently selected resolution of the map, taken from the
+ * resolutions array, indexed by current zoom level.
+ */
+ getResolution: function() {
+ var zoom = this.map.getZoom();
+ return this.getResolutionForZoom(zoom);
+ },
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function() {
+ // just use stock map calculateBounds function -- passing no arguments
+ // means it will user map's current center & resolution
+ //
+ return this.map.calculateBounds();
+ },
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * for the passed-in extent. We do this by calculating the ideal
+ * resolution for the given extent (based on the map size) and then
+ * calling getZoomForResolution(), passing along the 'closest'
+ * parameter.
+ */
+ getZoomForExtent: function(extent, closest) {
+ var viewSize = this.map.getSize();
+ var idealResolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+
+ return this.getZoomForResolution(idealResolution, closest);
+ },
+
+ /**
+ * Method: getDataExtent
+ * Calculates the max extent which includes all of the data for the layer.
+ * This function is to be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ //to be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom.
+ */
+ getResolutionForZoom: function(zoom) {
+ zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
+ var resolution;
+ if(this.map.fractionalZoom) {
+ var low = Math.floor(zoom);
+ var high = Math.ceil(zoom);
+ resolution = this.resolutions[low] -
+ ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
+ } else {
+ resolution = this.resolutions[Math.round(zoom)];
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * that corresponds to the best fit resolution given the passed in
+ * value and the 'closest' specification.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom, i, len;
+ if(this.map.fractionalZoom) {
+ var lowZoom = 0;
+ var highZoom = this.resolutions.length - 1;
+ var highRes = this.resolutions[lowZoom];
+ var lowRes = this.resolutions[highZoom];
+ var res;
+ for(i=0, len=this.resolutions.length; i<len; ++i) {
+ res = this.resolutions[i];
+ if(res >= resolution) {
+ highRes = res;
+ lowZoom = i;
+ }
+ if(res <= resolution) {
+ lowRes = res;
+ highZoom = i;
+ break;
+ }
+ }
+ var dRes = highRes - lowRes;
+ if(dRes > 0) {
+ zoom = lowZoom + ((highRes - resolution) / dRes);
+ } else {
+ zoom = lowZoom;
+ }
+ } else {
+ var diff;
+ var minDiff = Number.POSITIVE_INFINITY;
+ for(i=0, len=this.resolutions.length; i<len; i++) {
+ if (closest) {
+ diff = Math.abs(this.resolutions[i] - resolution);
+ if (diff > minDiff) {
+ break;
+ }
+ minDiff = diff;
+ } else {
+ if (this.resolutions[i] < resolution) {
+ break;
+ }
+ }
+ }
+ zoom = Math.max(0, i-1);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in
+ * view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ var map = this.map;
+ if (viewPortPx != null && map.minPx) {
+ var res = map.getResolution();
+ var maxExtent = map.getMaxExtent({restricted: true});
+ var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
+ var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
+ lonlat = new OpenLayers.LonLat(lon, lat);
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ * Returns a pixel location given a map location. This method will return
+ * fractional pixel values.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
+ * an object with a 'lon'
+ * and 'lat' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in
+ * lonlat translated into view port pixels.
+ */
+ getViewPortPxFromLonLat: function (lonlat, resolution) {
+ var px = null;
+ if (lonlat != null) {
+ resolution = resolution || this.map.getResolution();
+ var extent = this.map.calculateBounds(null, resolution);
+ px = new OpenLayers.Pixel(
+ (1/resolution * (lonlat.lon - extent.left)),
+ (1/resolution * (extent.top - lonlat.lat))
+ );
+ }
+ return px;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity != this.opacity) {
+ this.opacity = opacity;
+ var childNodes = this.div.childNodes;
+ for(var i = 0, len = childNodes.length; i < len; ++i) {
+ var element = childNodes[i].firstChild || childNodes[i];
+ var lastChild = childNodes[i].lastChild;
+ //TODO de-uglify this
+ if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") {
+ element = lastChild.parentNode;
+ }
+ OpenLayers.Util.modifyDOMElement(element, null, null, null,
+ null, null, null, opacity);
+ }
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ }
+ },
+
+ /**
+ * Method: getZIndex
+ *
+ * Returns:
+ * {Integer} the z-index of this layer
+ */
+ getZIndex: function () {
+ return this.div.style.zIndex;
+ },
+
+ /**
+ * Method: setZIndex
+ *
+ * Parameters:
+ * zIndex - {Integer}
+ */
+ setZIndex: function (zIndex) {
+ this.div.style.zIndex = zIndex;
+ },
+
+ /**
+ * Method: adjustBounds
+ * This function will take a bounds, and if wrapDateLine option is set
+ * on the layer, it will return a bounds which is wrapped around the
+ * world. We do not wrap for bounds which *cross* the
+ * maxExtent.left/right, only bounds which are entirely to the left
+ * or entirely to the right.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ adjustBounds: function (bounds) {
+
+ if (this.gutter) {
+ // Adjust the extent of a bounds in map units by the
+ // layer's gutter in pixels.
+ var mapGutter = this.gutter * this.map.getResolution();
+ bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
+ bounds.bottom - mapGutter,
+ bounds.right + mapGutter,
+ bounds.top + mapGutter);
+ }
+
+ if (this.wrapDateLine) {
+ // wrap around the date line, within the limits of rounding error
+ var wrappingOptions = {
+ 'rightTolerance':this.getResolution(),
+ 'leftTolerance':this.getResolution()
+ };
+ bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
+
+ }
+ return bounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer"
+});
+/* ======================================================================
+ OpenLayers/Layer/HTTPRequest.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.HTTPRequest
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * Constant: URL_HASH_FACTOR
+ * {Float} Used to hash URL param strings for multi-WMS server selection.
+ * Set to the Golden Ratio per Knuth's recommendation.
+ */
+ URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2,
+
+ /**
+ * Property: url
+ * {Array(String) or String} This is either an array of url strings or
+ * a single url string.
+ */
+ url: null,
+
+ /**
+ * Property: params
+ * {Object} Hashtable of key/value parameters
+ */
+ params: null,
+
+ /**
+ * APIProperty: reproject
+ * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html
+ * for information on the replacement for this functionality.
+ * {Boolean} Whether layer should reproject itself based on base layer
+ * locations. This allows reprojection onto commercial layers.
+ * Default is false: Most layers can't reproject, but layers
+ * which can create non-square geographic pixels can, like WMS.
+ *
+ */
+ reproject: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.HTTPRequest
+ *
+ * Parameters:
+ * name - {String}
+ * url - {Array(String) or String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+ this.url = url;
+ if (!this.params) {
+ this.params = OpenLayers.Util.extend({}, params);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.url = null;
+ this.params = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this
+ * <OpenLayers.Layer.HTTPRequest>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.HTTPRequest(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: setUrl
+ *
+ * Parameters:
+ * newUrl - {String}
+ */
+ setUrl: function(newUrl) {
+ this.url = newUrl;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+ mergeNewParams:function(newParams) {
+ this.params = OpenLayers.Util.extend(this.params, newParams);
+ var ret = this.redraw();
+ if(this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "params"
+ });
+ }
+ return ret;
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Parameters:
+ * force - {Boolean} Force redraw by adding random parameter.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function(force) {
+ if (force) {
+ return this.mergeNewParams({"_olSalt": Math.random()});
+ } else {
+ return OpenLayers.Layer.prototype.redraw.apply(this, []);
+ }
+ },
+
+ /**
+ * Method: selectUrl
+ * selectUrl() implements the standard floating-point multiplicative
+ * hash function described by Knuth, and hashes the contents of the
+ * given param string into a float between 0 and 1. This float is then
+ * scaled to the size of the provided urls array, and used to select
+ * a URL.
+ *
+ * Parameters:
+ * paramString - {String}
+ * urls - {Array(String)}
+ *
+ * Returns:
+ * {String} An entry from the urls array, deterministically selected based
+ * on the paramString.
+ */
+ selectUrl: function(paramString, urls) {
+ var product = 1;
+ for (var i=0, len=paramString.length; i<len; i++) {
+ product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR;
+ product -= Math.floor(product);
+ }
+ return urls[Math.floor(product * urls.length)];
+ },
+
+ /**
+ * Method: getFullRequestString
+ * Combine url with layer's params and these newParams.
+ *
+ * does checking on the serverPath variable, allowing for cases when it
+ * is supplied with trailing ? or &, as well as cases where not.
+ *
+ * return in formatted string like this:
+ * "server?key1=value1&key2=value2&key3=value3"
+ *
+ * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+
+ // if not altUrl passed in, use layer's url
+ var url = altUrl || this.url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ //
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams =
+ OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ return OpenLayers.Util.urlAppend(url, paramsString);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
+});
+/* ======================================================================
+ OpenLayers/Tile.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile
+ * This is a class designed to designate a single tile, however
+ * it is explicitly designed to do relatively little. Tiles store
+ * information about themselves -- such as the URL that they are related
+ * to, and their size - but do not add themselves to the layer div
+ * automatically, for example. Create a new tile with the
+ * <OpenLayers.Tile> constructor, or a subclass.
+ *
+ * TBD 3.0 - remove reference to url in above paragraph
+ *
+ */
+OpenLayers.Tile = OpenLayers.Class({
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types:
+ * beforedraw - Triggered before the tile is drawn. Used to defer
+ * drawing to an animation queue. To defer drawing, listeners need
+ * to return false, which will abort drawing. The queue handler needs
+ * to call <draw>(true) to actually draw the tile.
+ * loadstart - Triggered when tile loading starts.
+ * loadend - Triggered when tile loading ends.
+ * loaderror - Triggered before the loadend event (i.e. when the tile is
+ * still hidden) if the tile could not be loaded.
+ * reload - Triggered when an already loading tile is reloaded.
+ * unload - Triggered before a tile is unloaded.
+ */
+ events: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ *
+ * This options can be set in the ``tileOptions`` option from
+ * <OpenLayers.Layer.Grid>. For example, to be notified of the
+ * ``loadend`` event of each tiles:
+ * (code)
+ * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', {
+ * tileOptions: {
+ * eventListeners: {
+ * 'loadend': function(evt) {
+ * // do something on loadend
+ * }
+ * }
+ * }
+ * });
+ * (end)
+ */
+ eventListeners: null,
+
+ /**
+ * Property: id
+ * {String} null
+ */
+ id: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>} layer the tile is attached to
+ */
+ layer: null,
+
+ /**
+ * Property: url
+ * {String} url of the request.
+ *
+ * TBD 3.0
+ * Deprecated. The base tile class does not need an url. This should be
+ * handled in subclasses. Does not belong here.
+ */
+ url: null,
+
+ /**
+ * APIProperty: bounds
+ * {<OpenLayers.Bounds>} null
+ */
+ bounds: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} null
+ */
+ size: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>} Top Left pixel of the tile
+ */
+ position: null,
+
+ /**
+ * Property: isLoading
+ * {Boolean} Is the tile loading?
+ */
+ isLoading: false,
+
+ /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
+ * there is no need for the base tile class to have a url.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile
+ * Constructor for a new <OpenLayers.Tile> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>}
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ this.layer = layer;
+ this.position = position.clone();
+ this.setBounds(bounds);
+ this.url = url;
+ if (size) {
+ this.size = size.clone();
+ }
+
+ //give the tile a unique id based on its BBOX.
+ this.id = OpenLayers.Util.createUniqueID("Tile_");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if (this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ },
+
+ /**
+ * Method: unload
+ * Call immediately before destroying if you are listening to tile
+ * events, so that counters are properly handled if tile is still
+ * loading at destroy-time. Will only fire an event if the tile is
+ * still loading.
+ */
+ unload: function() {
+ if (this.isLoading) {
+ this.isLoading = false;
+ this.events.triggerEvent("unload");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Nullify references to prevent circular references and memory leaks.
+ */
+ destroy:function() {
+ this.layer = null;
+ this.bounds = null;
+ this.size = null;
+ this.position = null;
+
+ if (this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: draw
+ * Clear whatever is currently in the tile, then return whether or not
+ * it should actually be re-drawn. This is an example implementation
+ * that can be overridden by subclasses. The minimum thing to do here
+ * is to call <clear> and return the result from <shouldDraw>.
+ *
+ * Parameters:
+ * force - {Boolean} If true, the tile will not be cleared and no beforedraw
+ * event will be fired. This is used for drawing tiles asynchronously
+ * after drawing has been cancelled by returning false from a beforedraw
+ * listener.
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn. Returns null
+ * if a beforedraw listener returned false.
+ */
+ draw: function(force) {
+ if (!force) {
+ //clear tile's contents and mark as not drawn
+ this.clear();
+ }
+ var draw = this.shouldDraw();
+ if (draw && !force && this.events.triggerEvent("beforedraw") === false) {
+ draw = null;
+ }
+ return draw;
+ },
+
+ /**
+ * Method: shouldDraw
+ * Return whether or not the tile should actually be (re-)drawn. The only
+ * case where we *wouldn't* want to draw the tile is if the tile is outside
+ * its layer's maxExtent
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn.
+ */
+ shouldDraw: function() {
+ var withinMaxExtent = false,
+ maxExtent = this.layer.maxExtent;
+ if (maxExtent) {
+ var map = this.layer.map;
+ var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent();
+ if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) {
+ withinMaxExtent = true;
+ }
+ }
+
+ return withinMaxExtent || this.layer.displayOutsideMaxExtent;
+ },
+
+ /**
+ * Method: setBounds
+ * Sets the bounds on this instance
+ *
+ * Parameters:
+ * bounds {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ bounds = bounds.clone();
+ if (this.layer.map.baseLayer.wrapDateLine) {
+ var worldExtent = this.layer.map.getMaxExtent(),
+ tolerance = this.layer.map.getResolution();
+ bounds = bounds.wrapDateLine(worldExtent, {
+ leftTolerance: tolerance,
+ rightTolerance: tolerance
+ });
+ }
+ this.bounds = bounds;
+ },
+
+ /**
+ * Method: moveTo
+ * Reposition the tile.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ * redraw - {Boolean} Call draw method on tile after moving.
+ * Default is true
+ */
+ moveTo: function (bounds, position, redraw) {
+ if (redraw == null) {
+ redraw = true;
+ }
+
+ this.setBounds(bounds);
+ this.position = position.clone();
+ if (redraw) {
+ this.draw();
+ }
+ },
+
+ /**
+ * Method: clear
+ * Clear the tile of any bounds/position-related data so that it can
+ * be reused in a new location.
+ */
+ clear: function(draw) {
+ // to be extended by subclasses
+ },
+
+ CLASS_NAME: "OpenLayers.Tile"
+});
+/* ======================================================================
+ OpenLayers/Tile/Image.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Animation.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.Image
+ * Instances of OpenLayers.Tile.Image are used to manage the image tiles
+ * used by various layers. Create a new image tile with the
+ * <OpenLayers.Tile.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to the <OpenLayers.Tile> events):
+ * beforeload - Triggered before an image is prepared for loading, when the
+ * url for the image is known already. Listeners may call <setImage> on
+ * the tile instance. If they do so, that image will be used and no new
+ * one will be created.
+ */
+
+ /**
+ * APIProperty: url
+ * {String} The URL of the image being requested. No default. Filled in by
+ * layer.getURL() function. May be modified by loadstart listeners.
+ */
+ url: null,
+
+ /**
+ * Property: imgDiv
+ * {HTMLImageElement} The image for this tile.
+ */
+ imgDiv: null,
+
+ /**
+ * Property: frame
+ * {DOMElement} The image element is appended to the frame. Any gutter on
+ * the image will be hidden behind the frame. If no gutter is set,
+ * this will be null.
+ */
+ frame: null,
+
+ /**
+ * Property: imageReloadAttempts
+ * {Integer} Attempts to load the image.
+ */
+ imageReloadAttempts: null,
+
+ /**
+ * Property: layerAlphaHack
+ * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
+ */
+ layerAlphaHack: null,
+
+ /**
+ * Property: asyncRequestId
+ * {Integer} ID of an request to see if request is still valid. This is a
+ * number which increments by 1 for each asynchronous request.
+ */
+ asyncRequestId: null,
+
+ /**
+ * APIProperty: maxGetUrlLength
+ * {Number} If set, requests that would result in GET urls with more
+ * characters than the number provided will be made using form-encoded
+ * HTTP POST. It is good practice to avoid urls that are longer than 2048
+ * characters.
+ *
+ * Caution:
+ * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
+ * Opera versions do not fully support this option. On all browsers,
+ * transition effects are not supported if POST requests are used.
+ */
+ maxGetUrlLength: null,
+
+ /**
+ * Property: canvasContext
+ * {CanvasRenderingContext2D} A canvas context associated with
+ * the tile image.
+ */
+ canvasContext: null,
+
+ /**
+ * APIProperty: crossOriginKeyword
+ * The value of the crossorigin keyword to use when loading images. This is
+ * only relevant when using <getCanvasContext> for tiles from remote
+ * origins and should be set to either 'anonymous' or 'use-credentials'
+ * for servers that send Access-Control-Allow-Origin headers with their
+ * tiles.
+ */
+ crossOriginKeyword: null,
+
+ /** TBD 3.0 - reorder the parameters to the init function to remove
+ * URL. the getUrl() function on the layer gets called on
+ * each draw(), so no need to specify it here.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile.Image
+ * Constructor for a new <OpenLayers.Tile.Image> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+
+ this.url = url; //deprecated remove me
+
+ this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
+
+ if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
+ // only create frame if it's needed
+ this.frame = document.createElement("div");
+ this.frame.style.position = "absolute";
+ this.frame.style.overflow = "hidden";
+ }
+ if (this.maxGetUrlLength != null) {
+ OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.imgDiv) {
+ this.clear();
+ this.imgDiv = null;
+ this.frame = null;
+ }
+ // don't handle async requests any more
+ this.asyncRequestId = null;
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and draw it.
+ *
+ * Returns:
+ * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
+ * false.
+ */
+ draw: function() {
+ var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+ if (shouldDraw) {
+ // The layer's reproject option is deprecated.
+ if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
+ // getBoundsFromBaseLayer is defined in deprecated.js.
+ this.bounds = this.getBoundsFromBaseLayer(this.position);
+ }
+ if (this.isLoading) {
+ //if we're already loading, send 'reload' instead of 'loadstart'.
+ this._loadEvent = "reload";
+ } else {
+ this.isLoading = true;
+ this._loadEvent = "loadstart";
+ }
+ this.renderTile();
+ this.positionTile();
+ } else if (shouldDraw === false) {
+ this.unload();
+ }
+ return shouldDraw;
+ },
+
+ /**
+ * Method: renderTile
+ * Internal function to actually initialize the image tile,
+ * position it correctly, and set its url.
+ */
+ renderTile: function() {
+ if (this.layer.async) {
+ // Asynchronous image requests call the asynchronous getURL method
+ // on the layer to fetch an image that covers 'this.bounds'.
+ var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
+ this.layer.getURLasync(this.bounds, function(url) {
+ if (id == this.asyncRequestId) {
+ this.url = url;
+ this.initImage();
+ }
+ }, this);
+ } else {
+ // synchronous image requests get the url immediately.
+ this.url = this.layer.getURL(this.bounds);
+ this.initImage();
+ }
+ },
+
+ /**
+ * Method: positionTile
+ * Using the properties currenty set on the layer, position the tile correctly.
+ * This method is used both by the async and non-async versions of the Tile.Image
+ * code.
+ */
+ positionTile: function() {
+ var style = this.getTile().style,
+ size = this.frame ? this.size :
+ this.layer.getImageSize(this.bounds),
+ ratio = 1;
+ if (this.layer instanceof OpenLayers.Layer.Grid) {
+ ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
+ }
+ style.left = this.position.x + "px";
+ style.top = this.position.y + "px";
+ style.width = Math.round(ratio * size.w) + "px";
+ style.height = Math.round(ratio * size.h) + "px";
+ },
+
+ /**
+ * Method: clear
+ * Remove the tile from the DOM, clear it of any image related data so that
+ * it can be reused in a new location.
+ */
+ clear: function() {
+ OpenLayers.Tile.prototype.clear.apply(this, arguments);
+ var img = this.imgDiv;
+ if (img) {
+ var tile = this.getTile();
+ if (tile.parentNode === this.layer.div) {
+ this.layer.div.removeChild(tile);
+ }
+ this.setImgSrc();
+ if (this.layerAlphaHack === true) {
+ img.style.filter = "";
+ }
+ OpenLayers.Element.removeClass(img, "olImageLoadError");
+ }
+ this.canvasContext = null;
+ },
+
+ /**
+ * Method: getImage
+ * Returns or creates and returns the tile image.
+ */
+ getImage: function() {
+ if (!this.imgDiv) {
+ this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
+
+ var style = this.imgDiv.style;
+ if (this.frame) {
+ var left = 0, top = 0;
+ if (this.layer.gutter) {
+ left = this.layer.gutter / this.layer.tileSize.w * 100;
+ top = this.layer.gutter / this.layer.tileSize.h * 100;
+ }
+ style.left = -left + "%";
+ style.top = -top + "%";
+ style.width = (2 * left + 100) + "%";
+ style.height = (2 * top + 100) + "%";
+ }
+ style.visibility = "hidden";
+ style.opacity = 0;
+ if (this.layer.opacity < 1) {
+ style.filter = 'alpha(opacity=' +
+ (this.layer.opacity * 100) +
+ ')';
+ }
+ style.position = "absolute";
+ if (this.layerAlphaHack) {
+ // move the image out of sight
+ style.paddingTop = style.height;
+ style.height = "0";
+ style.width = "100%";
+ }
+ if (this.frame) {
+ this.frame.appendChild(this.imgDiv);
+ }
+ }
+
+ return this.imgDiv;
+ },
+
+ /**
+ * APIMethod: setImage
+ * Sets the image element for this tile. This method should only be called
+ * from beforeload listeners.
+ *
+ * Parameters
+ * img - {HTMLImageElement} The image to use for this tile.
+ */
+ setImage: function(img) {
+ this.imgDiv = img;
+ },
+
+ /**
+ * Method: initImage
+ * Creates the content for the frame on the tile.
+ */
+ initImage: function() {
+ if (!this.url && !this.imgDiv) {
+ // fast path out - if there is no tile url and no previous image
+ this.isLoading = false;
+ return;
+ }
+ this.events.triggerEvent('beforeload');
+ this.layer.div.appendChild(this.getTile());
+ this.events.triggerEvent(this._loadEvent);
+ var img = this.getImage();
+ var src = img.getAttribute('src') || '';
+ if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
+ this._loadTimeout = window.setTimeout(
+ OpenLayers.Function.bind(this.onImageLoad, this), 0
+ );
+ } else {
+ this.stopLoading();
+ if (this.crossOriginKeyword) {
+ img.removeAttribute("crossorigin");
+ }
+ OpenLayers.Event.observe(img, "load",
+ OpenLayers.Function.bind(this.onImageLoad, this)
+ );
+ OpenLayers.Event.observe(img, "error",
+ OpenLayers.Function.bind(this.onImageError, this)
+ );
+ this.imageReloadAttempts = 0;
+ this.setImgSrc(this.url);
+ }
+ },
+
+ /**
+ * Method: setImgSrc
+ * Sets the source for the tile image
+ *
+ * Parameters:
+ * url - {String} or undefined to hide the image
+ */
+ setImgSrc: function(url) {
+ var img = this.imgDiv;
+ if (url) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ // don't set crossOrigin if the url is a data URL
+ if (this.crossOriginKeyword) {
+ if (url.substr(0, 5) !== 'data:') {
+ img.setAttribute("crossorigin", this.crossOriginKeyword);
+ } else {
+ img.removeAttribute("crossorigin");
+ }
+ }
+ img.src = url;
+ } else {
+ // Remove reference to the image, and leave it to the browser's
+ // caching and garbage collection.
+ this.stopLoading();
+ this.imgDiv = null;
+ if (img.parentNode) {
+ img.parentNode.removeChild(img);
+ }
+ }
+ },
+
+ /**
+ * Method: getTile
+ * Get the tile's markup.
+ *
+ * Returns:
+ * {DOMElement} The tile's markup
+ */
+ getTile: function() {
+ return this.frame ? this.frame : this.getImage();
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
+ * of the tile's markup, because we want to avoid the reloading of the
+ * image. So we clone the frame, and steal the image from the tile.
+ *
+ * Returns:
+ * {DOMElement} The markup, or undefined if the tile has no image
+ * or if it's currently loading.
+ */
+ createBackBuffer: function() {
+ if (!this.imgDiv || this.isLoading) {
+ return;
+ }
+ var backBuffer;
+ if (this.frame) {
+ backBuffer = this.frame.cloneNode(false);
+ backBuffer.appendChild(this.imgDiv);
+ } else {
+ backBuffer = this.imgDiv;
+ }
+ this.imgDiv = null;
+ return backBuffer;
+ },
+
+ /**
+ * Method: onImageLoad
+ * Handler for the image onload event
+ */
+ onImageLoad: function() {
+ var img = this.imgDiv;
+ this.stopLoading();
+ img.style.visibility = 'inherit';
+ img.style.opacity = this.layer.opacity;
+ this.isLoading = false;
+ this.canvasContext = null;
+ this.events.triggerEvent("loadend");
+
+ if (this.layerAlphaHack === true) {
+ img.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
+ img.src + "', sizingMethod='scale')";
+ }
+ },
+
+ /**
+ * Method: onImageError
+ * Handler for the image onerror event
+ */
+ onImageError: function() {
+ var img = this.imgDiv;
+ if (img.src != null) {
+ this.imageReloadAttempts++;
+ if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
+ this.setImgSrc(this.layer.getURL(this.bounds));
+ } else {
+ OpenLayers.Element.addClass(img, "olImageLoadError");
+ this.events.triggerEvent("loaderror");
+ this.onImageLoad();
+ }
+ }
+ },
+
+ /**
+ * Method: stopLoading
+ * Stops a loading sequence so <onImageLoad> won't be executed.
+ */
+ stopLoading: function() {
+ OpenLayers.Event.stopObservingElement(this.imgDiv);
+ window.clearTimeout(this._loadTimeout);
+ delete this._loadTimeout;
+ },
+
+ /**
+ * APIMethod: getCanvasContext
+ * Returns a canvas context associated with the tile image (with
+ * the image drawn on it).
+ * Returns undefined if the browser does not support canvas, if
+ * the tile has no image or if it's currently loading.
+ *
+ * The function returns a canvas context instance but the
+ * underlying canvas is still available in the 'canvas' property:
+ * (code)
+ * var context = tile.getCanvasContext();
+ * if (context) {
+ * var data = context.canvas.toDataURL('image/jpeg');
+ * }
+ * (end)
+ *
+ * Returns:
+ * {Boolean}
+ */
+ getCanvasContext: function() {
+ if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
+ if (!this.canvasContext) {
+ var canvas = document.createElement("canvas");
+ canvas.width = this.size.w;
+ canvas.height = this.size.h;
+ this.canvasContext = canvas.getContext("2d");
+ this.canvasContext.drawImage(this.imgDiv, 0, 0);
+ }
+ return this.canvasContext;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.Image"
+
+});
+
+/**
+ * Constant: OpenLayers.Tile.Image.IMAGE
+ * {HTMLImageElement} The image for a tile.
+ */
+OpenLayers.Tile.Image.IMAGE = (function() {
+ var img = new Image();
+ img.className = "olTileImage";
+ // avoid image gallery menu in IE6
+ img.galleryImg = "no";
+ return img;
+}());
+
+/* ======================================================================
+ OpenLayers/Layer/Grid.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/HTTPRequest.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Grid
+ * Base class for layers that use a lattice of tiles. Create a new grid
+ * layer with the <OpenLayers.Layer.Grid> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.HTTPRequest>
+ */
+OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>}
+ */
+ tileSize: null,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} If the <tileOrigin> property is not provided, the tile origin
+ * will be derived from the layer's <maxExtent>. The corner of the
+ * <maxExtent> used is determined by this property. Acceptable values
+ * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
+ * (bottom right). Default is "bl".
+ */
+ tileOriginCorner: "bl",
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
+ * If provided, requests for tiles at all resolutions will be aligned
+ * with this location (no tiles shall overlap this location). If
+ * not provided, the grid of tiles will be aligned with the layer's
+ * <maxExtent>. Default is ``null``.
+ */
+ tileOrigin: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer, if supported by the tile class.
+ */
+ tileOptions: null,
+
+ /**
+ * APIProperty: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is OpenLayers.Tile.Image.
+ */
+ tileClass: OpenLayers.Tile.Image,
+
+ /**
+ * Property: grid
+ * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is
+ * an array of tiles.
+ */
+ grid: null,
+
+ /**
+ * APIProperty: singleTile
+ * {Boolean} Moves the layer into single-tile mode, meaning that one tile
+ * will be loaded. The tile's size will be determined by the 'ratio'
+ * property. When the tile is dragged such that it does not cover the
+ * entire viewport, it is reloaded.
+ */
+ singleTile: false,
+
+ /** APIProperty: ratio
+ * {Float} Used only when in single-tile mode, this specifies the
+ * ratio of the size of the single tile to the size of the map.
+ * Default value is 1.5.
+ */
+ ratio: 1.5,
+
+ /**
+ * APIProperty: buffer
+ * {Integer} Used only when in gridded mode, this specifies the number of
+ * extra rows and colums of tiles on each side which will
+ * surround the minimum grid tiles to cover the map.
+ * For very slow loading layers, a larger value may increase
+ * performance somewhat when dragging, but will increase bandwidth
+ * use significantly.
+ */
+ buffer: 0,
+
+ /**
+ * APIProperty: transitionEffect
+ * {String} The transition effect to use when the map is zoomed.
+ * Two posible values:
+ *
+ * "resize" - Existing tiles are resized on zoom to provide a visual
+ * effect of the zoom having taken place immediately. As the
+ * new tiles become available, they are drawn on top of the
+ * resized tiles (this is the default setting).
+ * "map-resize" - Existing tiles are resized on zoom and placed below the
+ * base layer. New tiles for the base layer will cover existing tiles.
+ * This setting is recommended when having an overlay duplicated during
+ * the transition is undesirable (e.g. street labels or big transparent
+ * fills).
+ * null - No transition effect.
+ *
+ * Using "resize" on non-opaque layers can cause undesired visual
+ * effects. Set transitionEffect to null in this case.
+ */
+ transitionEffect: "resize",
+
+ /**
+ * APIProperty: numLoadingTiles
+ * {Integer} How many tiles are still loading?
+ */
+ numLoadingTiles: 0,
+
+ /**
+ * Property: serverResolutions
+ * {Array(Number}} This property is documented in subclasses as
+ * an API property.
+ */
+ serverResolutions: null,
+
+ /**
+ * Property: loading
+ * {Boolean} Indicates if tiles are being loaded.
+ */
+ loading: false,
+
+ /**
+ * Property: backBuffer
+ * {DOMElement} The back buffer.
+ */
+ backBuffer: null,
+
+ /**
+ * Property: gridResolution
+ * {Number} The resolution of the current grid. Used for backbuffer and
+ * client zoom. This property is updated every time the grid is
+ * initialized.
+ */
+ gridResolution: null,
+
+ /**
+ * Property: backBufferResolution
+ * {Number} The resolution of the current back buffer. This property is
+ * updated each time a back buffer is created.
+ */
+ backBufferResolution: null,
+
+ /**
+ * Property: backBufferLonLat
+ * {Object} The top-left corner of the current back buffer. Includes lon
+ * and lat properties. This object is updated each time a back buffer
+ * is created.
+ */
+ backBufferLonLat: null,
+
+ /**
+ * Property: backBufferTimerId
+ * {Number} The id of the back buffer timer. This timer is used to
+ * delay the removal of the back buffer, thereby preventing
+ * flash effects caused by tile animation.
+ */
+ backBufferTimerId: null,
+
+ /**
+ * APIProperty: removeBackBufferDelay
+ * {Number} Delay for removing the backbuffer when all tiles have finished
+ * loading. Can be set to 0 when no css opacity transitions for the
+ * olTileImage class are used. Default is 0 for <singleTile> layers,
+ * 2500 for tiled layers. See <className> for more information on
+ * tile animation.
+ */
+ removeBackBufferDelay: null,
+
+ /**
+ * APIProperty: className
+ * {String} Name of the class added to the layer div. If not set in the
+ * options passed to the constructor then className defaults to
+ * "olLayerGridSingleTile" for single tile layers (see <singleTile>),
+ * and "olLayerGrid" for non single tile layers.
+ *
+ * Note:
+ *
+ * The displaying of tiles is not animated by default for single tile
+ * layers - OpenLayers' default theme (style.css) includes this:
+ * (code)
+ * .olLayerGrid .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * To animate tile displaying for any grid layer the following
+ * CSS rule can be used:
+ * (code)
+ * .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * In that case, to avoid flash effects, <removeBackBufferDelay>
+ * should not be zero.
+ */
+ className: null,
+
+ /**
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported event types:
+ * addtile - Triggered when a tile is added to this layer. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that has been added.
+ * tileloadstart - Triggered when a tile starts loading. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that starts loading.
+ * tileloaded - Triggered when each new tile is
+ * loaded, as a means of progress update to listeners.
+ * listeners can access 'numLoadingTiles' if they wish to keep
+ * track of the loading progress. Listeners are called with an object
+ * with a 'tile' property as first argument, making the loaded tile
+ * available to the listener, and an 'aborted' property, which will be
+ * true when loading was aborted and no tile data is available.
+ * tileerror - Triggered before the tileloaded event (i.e. when the tile is
+ * still hidden) if a tile failed to load. Listeners receive an object
+ * as first argument, which has a tile property that references the
+ * tile that could not be loaded.
+ * retile - Triggered when the layer recreates its tile grid.
+ */
+
+ /**
+ * Property: gridLayout
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ gridLayout: null,
+
+ /**
+ * Property: rowSign
+ * {Number} 1 for grids starting at the top, -1 for grids starting at the
+ * bottom. This is used for several grid index and offset calculations.
+ */
+ rowSign: null,
+
+ /**
+ * Property: transitionendEvents
+ * {Array} Event names for transitionend
+ */
+ transitionendEvents: [
+ 'transitionend', 'webkitTransitionEnd', 'otransitionend',
+ 'oTransitionEnd'
+ ],
+
+ /**
+ * Constructor: OpenLayers.Layer.Grid
+ * Create a new grid layer
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
+ arguments);
+ this.grid = [];
+ this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);
+
+ this.initProperties();
+
+ this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
+ },
+
+ /**
+ * Method: initProperties
+ * Set any properties that depend on the value of singleTile.
+ * Currently sets removeBackBufferDelay and className
+ */
+ initProperties: function() {
+ if (this.options.removeBackBufferDelay === undefined) {
+ this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
+ }
+
+ if (this.options.className === undefined) {
+ this.className = this.singleTile ? 'olLayerGridSingleTile' :
+ 'olLayerGrid';
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
+ OpenLayers.Element.addClass(this.div, this.className);
+ },
+
+ /**
+ * Method: removeMap
+ * Called when the layer is removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ removeMap: function(map) {
+ this.removeBackBuffer();
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct the layer and clear the grid.
+ */
+ destroy: function() {
+ this.removeBackBuffer();
+ this.clearGrid();
+
+ this.grid = null;
+ this.tileSize = null;
+ OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Refetches tiles with new params merged, keeping a backbuffer. Each
+ * loading new tile will have a css class of '.olTileReplacing'. If a
+ * stylesheet applies a 'display: none' style to that class, any fade-in
+ * transition will not apply, and backbuffers for each tile will be removed
+ * as soon as the tile is loaded.
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+
+ /**
+ * Method: clearGrid
+ * Go through and remove all tiles from the grid, calling
+ * destroy() on each of them to kill circular references
+ */
+ clearGrid:function() {
+ if (this.grid) {
+ for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
+ var row = this.grid[iRow];
+ for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
+ var tile = row[iCol];
+ this.destroyTile(tile);
+ }
+ }
+ this.grid = [];
+ this.gridResolution = null;
+ this.gridLayout = null;
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+ var singleTileChanged = newOptions.singleTile !== undefined &&
+ newOptions.singleTile !== this.singleTile;
+ OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
+ if (this.map && singleTileChanged) {
+ this.initProperties();
+ this.clearGrid();
+ this.tileSize = this.options.tileSize;
+ this.setTileSize();
+ this.moveTo(null, true);
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Grid(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ if (this.tileSize != null) {
+ obj.tileSize = this.tileSize.clone();
+ }
+
+ // we do not want to copy reference to grid, so we make a new array
+ obj.grid = [];
+ obj.gridResolution = null;
+ // same for backbuffer
+ obj.backBuffer = null;
+ obj.backBufferTimerId = null;
+ obj.loading = false;
+ obj.numLoadingTiles = 0;
+
+ return obj;
+ },
+
+ /**
+ * Method: moveTo
+ * This function is called whenever the map is moved. All the moving
+ * of actual 'tiles' is done by the map, but moveTo's role is to accept
+ * a bounds and make sure the data that that bounds requires is pre-loaded.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+
+ OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
+
+ bounds = bounds || this.map.getExtent();
+
+ if (bounds != null) {
+
+ // if grid is empty or zoom has changed, we *must* re-tile
+ var forceReTile = !this.grid.length || zoomChanged;
+
+ // total bounds of the tiles
+ var tilesBounds = this.getTilesBounds();
+
+ // the new map resolution
+ var resolution = this.map.getResolution();
+
+ // the server-supported resolution for the new map resolution
+ var serverResolution = this.getServerResolution(resolution);
+
+ if (this.singleTile) {
+
+ // We want to redraw whenever even the slightest part of the
+ // current bounds is not contained by our tile.
+ // (thus, we do not specify partial -- its default is false)
+
+ if ( forceReTile ||
+ (!dragging && !tilesBounds.containsBounds(bounds))) {
+
+ // In single tile mode with no transition effect, we insert
+ // a non-scaled backbuffer when the layer is moved. But if
+ // a zoom occurs right after a move, i.e. before the new
+ // image is received, we need to remove the backbuffer, or
+ // an ill-positioned image will be visible during the zoom
+ // transition.
+
+ if(zoomChanged && this.transitionEffect !== 'resize') {
+ this.removeBackBuffer();
+ }
+
+ if(!zoomChanged || this.transitionEffect === 'resize') {
+ this.applyBackBuffer(resolution);
+ }
+
+ this.initSingleTile(bounds);
+ }
+ } else {
+
+ // if the bounds have changed such that they are not even
+ // *partially* contained by our tiles (e.g. when user has
+ // programmatically panned to the other side of the earth on
+ // zoom level 18), then moveGriddedTiles could potentially have
+ // to run through thousands of cycles, so we want to reTile
+ // instead (thus, partial true).
+ forceReTile = forceReTile ||
+ !tilesBounds.intersectsBounds(bounds, {
+ worldBounds: this.map.baseLayer.wrapDateLine &&
+ this.map.getMaxExtent()
+ });
+
+ if(forceReTile) {
+ if(zoomChanged && (this.transitionEffect === 'resize' ||
+ this.gridResolution === resolution)) {
+ this.applyBackBuffer(resolution);
+ }
+ this.initGriddedTiles(bounds);
+ } else {
+ this.moveGriddedTiles();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getTileData
+ * Given a map location, retrieve a tile and the pixel offset within that
+ * tile corresponding to the location. If there is not an existing
+ * tile in the grid that covers the given location, null will be
+ * returned.
+ *
+ * Parameters:
+ * loc - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
+ * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
+ * offset from top left).
+ */
+ getTileData: function(loc) {
+ var data = null,
+ x = loc.lon,
+ y = loc.lat,
+ numRows = this.grid.length;
+
+ if (this.map && numRows) {
+ var res = this.map.getResolution(),
+ tileWidth = this.tileSize.w,
+ tileHeight = this.tileSize.h,
+ bounds = this.grid[0][0].bounds,
+ left = bounds.left,
+ top = bounds.top;
+
+ if (x < left) {
+ // deal with multiple worlds
+ if (this.map.baseLayer.wrapDateLine) {
+ var worldWidth = this.map.getMaxExtent().getWidth();
+ var worldsAway = Math.ceil((left - x) / worldWidth);
+ x += worldWidth * worldsAway;
+ }
+ }
+ // tile distance to location (fractional number of tiles);
+ var dtx = (x - left) / (res * tileWidth);
+ var dty = (top - y) / (res * tileHeight);
+ // index of tile in grid
+ var col = Math.floor(dtx);
+ var row = Math.floor(dty);
+ if (row >= 0 && row < numRows) {
+ var tile = this.grid[row][col];
+ if (tile) {
+ data = {
+ tile: tile,
+ // pixel index within tile
+ i: Math.floor((dtx - col) * tileWidth),
+ j: Math.floor((dty - row) * tileHeight)
+ };
+ }
+ }
+ }
+ return data;
+ },
+
+ /**
+ * Method: destroyTile
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ destroyTile: function(tile) {
+ this.removeTileMonitoringHooks(tile);
+ tile.destroy();
+ },
+
+ /**
+ * Method: getServerResolution
+ * Return the closest server-supported resolution.
+ *
+ * Parameters:
+ * resolution - {Number} The base resolution. If undefined the
+ * map resolution is used.
+ *
+ * Returns:
+ * {Number} The closest server resolution value.
+ */
+ getServerResolution: function(resolution) {
+ var distance = Number.POSITIVE_INFINITY;
+ resolution = resolution || this.map.getResolution();
+ if(this.serverResolutions &&
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
+ var i, newDistance, newResolution, serverResolution;
+ for(i=this.serverResolutions.length-1; i>= 0; i--) {
+ newResolution = this.serverResolutions[i];
+ newDistance = Math.abs(newResolution - resolution);
+ if (newDistance > distance) {
+ break;
+ }
+ distance = newDistance;
+ serverResolution = newResolution;
+ }
+ resolution = serverResolution;
+ }
+ return resolution;
+ },
+
+ /**
+ * Method: getServerZoom
+ * Return the zoom value corresponding to the best matching server
+ * resolution, taking into account <serverResolutions> and <zoomOffset>.
+ *
+ * Returns:
+ * {Number} The closest server supported zoom. This is not the map zoom
+ * level, but an index of the server's resolutions array.
+ */
+ getServerZoom: function() {
+ var resolution = this.getServerResolution();
+ return this.serverResolutions ?
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
+ this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
+ },
+
+ /**
+ * Method: applyBackBuffer
+ * Create, insert, scale and position a back buffer for the layer.
+ *
+ * Parameters:
+ * resolution - {Number} The resolution to transition to.
+ */
+ applyBackBuffer: function(resolution) {
+ if(this.backBufferTimerId !== null) {
+ this.removeBackBuffer();
+ }
+ var backBuffer = this.backBuffer;
+ if(!backBuffer) {
+ backBuffer = this.createBackBuffer();
+ if(!backBuffer) {
+ return;
+ }
+ if (resolution === this.gridResolution) {
+ this.div.insertBefore(backBuffer, this.div.firstChild);
+ } else {
+ this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
+ }
+ this.backBuffer = backBuffer;
+
+ // set some information in the instance for subsequent
+ // calls to applyBackBuffer where the same back buffer
+ // is reused
+ var topLeftTileBounds = this.grid[0][0].bounds;
+ this.backBufferLonLat = {
+ lon: topLeftTileBounds.left,
+ lat: topLeftTileBounds.top
+ };
+ this.backBufferResolution = this.gridResolution;
+ }
+
+ var ratio = this.backBufferResolution / resolution;
+
+ // scale the tiles inside the back buffer
+ var tiles = backBuffer.childNodes, tile;
+ for (var i=tiles.length-1; i>=0; --i) {
+ tile = tiles[i];
+ tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px';
+ tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px';
+ tile.style.width = Math.round(ratio * tile._w) + 'px';
+ tile.style.height = Math.round(ratio * tile._h) + 'px';
+ }
+
+ // and position it (based on the grid's top-left corner)
+ var position = this.getViewPortPxFromLonLat(
+ this.backBufferLonLat, resolution);
+ var leftOffset = this.map.layerContainerOriginPx.x;
+ var topOffset = this.map.layerContainerOriginPx.y;
+ backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
+ backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a back buffer.
+ *
+ * Returns:
+ * {DOMElement} The DOM element for the back buffer, undefined if the
+ * grid isn't initialized yet.
+ */
+ createBackBuffer: function() {
+ var backBuffer;
+ if(this.grid.length > 0) {
+ backBuffer = document.createElement('div');
+ backBuffer.id = this.div.id + '_bb';
+ backBuffer.className = 'olBackBuffer';
+ backBuffer.style.position = 'absolute';
+ var map = this.map;
+ backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
+ this.getZIndex() - 1 :
+ // 'map-resize':
+ map.Z_INDEX_BASE.BaseLayer -
+ (map.getNumLayers() - map.getLayerIndex(this));
+ for(var i=0, lenI=this.grid.length; i<lenI; i++) {
+ for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
+ var tile = this.grid[i][j],
+ markup = this.grid[i][j].createBackBuffer();
+ if (markup) {
+ markup._i = i;
+ markup._j = j;
+ markup._w = tile.size.w;
+ markup._h = tile.size.h;
+ markup.id = tile.id + '_bb';
+ backBuffer.appendChild(markup);
+ }
+ }
+ }
+ }
+ return backBuffer;
+ },
+
+ /**
+ * Method: removeBackBuffer
+ * Remove back buffer from DOM.
+ */
+ removeBackBuffer: function() {
+ if (this._transitionElement) {
+ for (var i=this.transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.stopObserving(this._transitionElement,
+ this.transitionendEvents[i], this._removeBackBuffer);
+ }
+ delete this._transitionElement;
+ }
+ if(this.backBuffer) {
+ if (this.backBuffer.parentNode) {
+ this.backBuffer.parentNode.removeChild(this.backBuffer);
+ }
+ this.backBuffer = null;
+ this.backBufferResolution = null;
+ if(this.backBufferTimerId !== null) {
+ window.clearTimeout(this.backBufferTimerId);
+ this.backBufferTimerId = null;
+ }
+ }
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ if (!this.singleTile) {
+ this.moveGriddedTiles();
+ }
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Check if we are in singleTile mode and if so, set the size as a ratio
+ * of the map size (as specified by the layer's 'ratio' property).
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ if (this.singleTile) {
+ size = this.map.getSize();
+ size.h = parseInt(size.h * this.ratio, 10);
+ size.w = parseInt(size.w * this.ratio, 10);
+ }
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
+ },
+
+ /**
+ * APIMethod: getTilesBounds
+ * Return the bounds of the tile grid.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
+ * currently loaded tiles (including those partially or not at all seen
+ * onscreen).
+ */
+ getTilesBounds: function() {
+ var bounds = null;
+
+ var length = this.grid.length;
+ if (length) {
+ var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
+ width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
+ height = this.grid.length * bottomLeftTileBounds.getHeight();
+
+ bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left,
+ bottomLeftTileBounds.bottom,
+ bottomLeftTileBounds.left + width,
+ bottomLeftTileBounds.bottom + height);
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: initSingleTile
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initSingleTile: function(bounds) {
+ this.events.triggerEvent("retile");
+
+ //determine new tile bounds
+ var center = bounds.getCenterLonLat();
+ var tileWidth = bounds.getWidth() * this.ratio;
+ var tileHeight = bounds.getHeight() * this.ratio;
+
+ var tileBounds =
+ new OpenLayers.Bounds(center.lon - (tileWidth/2),
+ center.lat - (tileHeight/2),
+ center.lon + (tileWidth/2),
+ center.lat + (tileHeight/2));
+
+ var px = this.map.getLayerPxFromLonLat({
+ lon: tileBounds.left,
+ lat: tileBounds.top
+ });
+
+ if (!this.grid.length) {
+ this.grid[0] = [];
+ }
+
+ var tile = this.grid[0][0];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+
+ this.addTileMonitoringHooks(tile);
+ tile.draw();
+ this.grid[0][0] = tile;
+ } else {
+ tile.moveTo(tileBounds, px);
+ }
+
+ //remove all but our single tile
+ this.removeExcessTiles(1,1);
+
+ // store the resolution of the grid
+ this.gridResolution = this.getServerResolution();
+ },
+
+ /**
+ * Method: calculateGridLayout
+ * Generate parameters for the grid layout.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
+ * object with a 'left' and 'top' properties.
+ * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * resolution - {Number}
+ *
+ * Returns:
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ calculateGridLayout: function(bounds, origin, resolution) {
+ var tilelon = resolution * this.tileSize.w;
+ var tilelat = resolution * this.tileSize.h;
+
+ var offsetlon = bounds.left - origin.lon;
+ var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+
+ var rowSign = this.rowSign;
+
+ var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);
+ var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
+
+ return {
+ tilelon: tilelon, tilelat: tilelat,
+ startcol: tilecol, startrow: tilerow
+ };
+
+ },
+
+ /**
+ * Method: getTileOrigin
+ * Determine the origin for aligning the grid of tiles. If a <tileOrigin>
+ * property is supplied, that will be returned. Otherwise, the origin
+ * will be derived from the layer's <maxExtent> property. In this case,
+ * the tile origin will be the corner of the <maxExtent> given by the
+ * <tileOriginCorner> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The tile origin.
+ */
+ getTileOrigin: function() {
+ var origin = this.tileOrigin;
+ if (!origin) {
+ var extent = this.getMaxExtent();
+ var edges = ({
+ "tl": ["left", "top"],
+ "tr": ["right", "top"],
+ "bl": ["left", "bottom"],
+ "br": ["right", "bottom"]
+ })[this.tileOriginCorner];
+ origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
+ }
+ return origin;
+ },
+
+ /**
+ * Method: getTileBoundsForGridIndex
+ *
+ * Parameters:
+ * row - {Number} The row of the grid
+ * col - {Number} The column of the grid
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
+ */
+ getTileBoundsForGridIndex: function(row, col) {
+ var origin = this.getTileOrigin();
+ var tileLayout = this.gridLayout;
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+ var startcol = tileLayout.startcol;
+ var startrow = tileLayout.startrow;
+ var rowSign = this.rowSign;
+ return new OpenLayers.Bounds(
+ origin.lon + (startcol + col) * tilelon,
+ origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
+ origin.lon + (startcol + col + 1) * tilelon,
+ origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
+ );
+ },
+
+ /**
+ * Method: initGriddedTiles
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initGriddedTiles:function(bounds) {
+ this.events.triggerEvent("retile");
+
+ // work out mininum number of rows and columns; this is the number of
+ // tiles required to cover the viewport plus at least one for panning
+
+ var viewSize = this.map.getSize();
+
+ var origin = this.getTileOrigin();
+ var resolution = this.map.getResolution(),
+ serverResolution = this.getServerResolution(),
+ ratio = resolution / serverResolution,
+ tileSize = {
+ w: this.tileSize.w / ratio,
+ h: this.tileSize.h / ratio
+ };
+
+ var minRows = Math.ceil(viewSize.h/tileSize.h) +
+ 2 * this.buffer + 1;
+ var minCols = Math.ceil(viewSize.w/tileSize.w) +
+ 2 * this.buffer + 1;
+
+ var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
+ this.gridLayout = tileLayout;
+
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+
+ var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
+ var layerContainerDivTop = this.map.layerContainerOriginPx.y;
+
+ var tileBounds = this.getTileBoundsForGridIndex(0, 0);
+ var startPx = this.map.getViewPortPxFromLonLat(
+ new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
+ );
+ startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
+ startPx.y = Math.round(startPx.y) - layerContainerDivTop;
+
+ var tileData = [], center = this.map.getCenter();
+
+ var rowidx = 0;
+ do {
+ var row = this.grid[rowidx];
+ if (!row) {
+ row = [];
+ this.grid.push(row);
+ }
+
+ var colidx = 0;
+ do {
+ tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
+ var px = startPx.clone();
+ px.x = px.x + colidx * Math.round(tileSize.w);
+ px.y = px.y + rowidx * Math.round(tileSize.h);
+ var tile = row[colidx];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+ this.addTileMonitoringHooks(tile);
+ row.push(tile);
+ } else {
+ tile.moveTo(tileBounds, px, false);
+ }
+ var tileCenter = tileBounds.getCenterLonLat();
+ tileData.push({
+ tile: tile,
+ distance: Math.pow(tileCenter.lon - center.lon, 2) +
+ Math.pow(tileCenter.lat - center.lat, 2)
+ });
+
+ colidx += 1;
+ } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
+ || colidx < minCols);
+
+ rowidx += 1;
+ } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
+ || rowidx < minRows);
+
+ //shave off exceess rows and colums
+ this.removeExcessTiles(rowidx, colidx);
+
+ var resolution = this.getServerResolution();
+ // store the resolution of the grid
+ this.gridResolution = resolution;
+
+ //now actually draw the tiles
+ tileData.sort(function(a, b) {
+ return a.distance - b.distance;
+ });
+ for (var i=0, ii=tileData.length; i<ii; ++i) {
+ tileData[i].tile.draw();
+ }
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent. (Implemented as a getter for
+ * potential specific implementations in sub-classes.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ return this.maxExtent;
+ },
+
+ /**
+ * APIMethod: addTile
+ * Create a tile, initialize it, and add it to the layer div.
+ *
+ * Parameters
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Tile>} The added OpenLayers.Tile
+ */
+ addTile: function(bounds, position) {
+ var tile = new this.tileClass(
+ this, position, bounds, null, this.tileSize, this.tileOptions
+ );
+ this.events.triggerEvent("addtile", {tile: tile});
+ return tile;
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tiles.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+
+ var replacingCls = 'olTileReplacing';
+
+ tile.onLoadStart = function() {
+ //if that was first tile then trigger a 'loadstart' on the layer
+ if (this.loading === false) {
+ this.loading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.events.triggerEvent("tileloadstart", {tile: tile});
+ this.numLoadingTiles++;
+ if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ OpenLayers.Element.addClass(tile.getTile(), replacingCls);
+ }
+ };
+
+ tile.onLoadEnd = function(evt) {
+ this.numLoadingTiles--;
+ var aborted = evt.type === 'unload';
+ this.events.triggerEvent("tileloaded", {
+ tile: tile,
+ aborted: aborted
+ });
+ if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ var tileDiv = tile.getTile();
+ if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
+ var bufferTile = document.getElementById(tile.id + '_bb');
+ if (bufferTile) {
+ bufferTile.parentNode.removeChild(bufferTile);
+ }
+ }
+ OpenLayers.Element.removeClass(tileDiv, replacingCls);
+ }
+ //if that was the last tile, then trigger a 'loadend' on the layer
+ if (this.numLoadingTiles === 0) {
+ if (this.backBuffer) {
+ if (this.backBuffer.childNodes.length === 0) {
+ // no tiles transitioning, remove immediately
+ this.removeBackBuffer();
+ } else {
+ // wait until transition has ended or delay has passed
+ this._transitionElement = aborted ?
+ this.div.lastChild : tile.imgDiv;
+ var transitionendEvents = this.transitionendEvents;
+ for (var i=transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.observe(this._transitionElement,
+ transitionendEvents[i],
+ this._removeBackBuffer);
+ }
+ // the removal of the back buffer is delayed to prevent
+ // flash effects due to the animation of tile displaying
+ this.backBufferTimerId = window.setTimeout(
+ this._removeBackBuffer, this.removeBackBufferDelay
+ );
+ }
+ }
+ this.loading = false;
+ this.events.triggerEvent("loadend");
+ }
+ };
+
+ tile.onLoadError = function() {
+ this.events.triggerEvent("tileerror", {tile: tile});
+ };
+
+ tile.events.on({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in addTileMonitoringHooks()
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: moveGriddedTiles
+ */
+ moveGriddedTiles: function() {
+ var buffer = this.buffer + 1;
+ while(true) {
+ var tlTile = this.grid[0][0];
+ var tlViewPort = {
+ x: tlTile.position.x +
+ this.map.layerContainerOriginPx.x,
+ y: tlTile.position.y +
+ this.map.layerContainerOriginPx.y
+ };
+ var ratio = this.getServerResolution() / this.map.getResolution();
+ var tileSize = {
+ w: Math.round(this.tileSize.w * ratio),
+ h: Math.round(this.tileSize.h * ratio)
+ };
+ if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
+ this.shiftColumn(true, tileSize);
+ } else if (tlViewPort.x < -tileSize.w * buffer) {
+ this.shiftColumn(false, tileSize);
+ } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
+ this.shiftRow(true, tileSize);
+ } else if (tlViewPort.y < -tileSize.h * buffer) {
+ this.shiftRow(false, tileSize);
+ } else {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: shiftRow
+ * Shifty grid work
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftRow: function(prepend, tileSize) {
+ var grid = this.grid;
+ var rowIndex = prepend ? 0 : (grid.length - 1);
+ var sign = prepend ? -1 : 1;
+ var rowSign = this.rowSign;
+ var tileLayout = this.gridLayout;
+ tileLayout.startrow += sign * rowSign;
+
+ var modelRow = grid[rowIndex];
+ var row = grid[prepend ? 'pop' : 'shift']();
+ for (var i=0, len=row.length; i<len; i++) {
+ var tile = row[i];
+ var position = modelRow[i].position.clone();
+ position.y += tileSize.h * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
+ }
+ grid[prepend ? 'unshift' : 'push'](row);
+ },
+
+ /**
+ * Method: shiftColumn
+ * Shift grid work in the other dimension
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftColumn: function(prepend, tileSize) {
+ var grid = this.grid;
+ var colIndex = prepend ? 0 : (grid[0].length - 1);
+ var sign = prepend ? -1 : 1;
+ var tileLayout = this.gridLayout;
+ tileLayout.startcol += sign;
+
+ for (var i=0, len=grid.length; i<len; i++) {
+ var row = grid[i];
+ var position = row[colIndex].position.clone();
+ var tile = row[prepend ? 'pop' : 'shift']();
+ position.x += tileSize.w * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
+ row[prepend ? 'unshift' : 'push'](tile);
+ }
+ },
+
+ /**
+ * Method: removeExcessTiles
+ * When the size of the map or the buffer changes, we may need to
+ * remove some excess rows and columns.
+ *
+ * Parameters:
+ * rows - {Integer} Maximum number of rows we want our grid to have.
+ * columns - {Integer} Maximum number of columns we want our grid to have.
+ */
+ removeExcessTiles: function(rows, columns) {
+ var i, l;
+
+ // remove extra rows
+ while (this.grid.length > rows) {
+ var row = this.grid.pop();
+ for (i=0, l=row.length; i<l; i++) {
+ var tile = row[i];
+ this.destroyTile(tile);
+ }
+ }
+
+ // remove extra columns
+ for (i=0, l=this.grid.length; i<l; i++) {
+ while (this.grid[i].length > columns) {
+ var row = this.grid[i];
+ var tile = row.pop();
+ this.destroyTile(tile);
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * For singleTile layers, this will set a new tile size according to the
+ * dimensions of the map pane.
+ */
+ onMapResize: function() {
+ if (this.singleTile) {
+ this.clearGrid();
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * APIMethod: getTileBounds
+ * Returns The tile bounds for a layer given a pixel location.
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var maxExtent = this.maxExtent;
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = maxExtent.left + (tileMapWidth *
+ Math.floor((mapPoint.lon -
+ maxExtent.left) /
+ tileMapWidth));
+ var tileBottom = maxExtent.bottom + (tileMapHeight *
+ Math.floor((mapPoint.lat -
+ maxExtent.bottom) /
+ tileMapHeight));
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Grid"
+});
+/* ======================================================================
+ OpenLayers/Layer/XYZ.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.XYZ
+ * The XYZ class is designed to make it easier for people who have tiles
+ * arranged by a standard XYZ grid.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * Default is true, as this is designed to be a base tile source.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * Whether the tile extents should be set to the defaults for
+ * spherical mercator. Useful for things like OpenStreetMap.
+ * Default is false, except for the OSM subclass.
+ */
+ sphericalMercator: false,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more zoom levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Using <zoomOffset> is an alternative to
+ * setting <serverResolutions> if you only want to expose a subset
+ * of the server resolutions.
+ */
+ zoomOffset: 0,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.XYZ
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, options) {
+ if (options && options.sphericalMercator || this.sphericalMercator) {
+ options = OpenLayers.Util.extend({
+ projection: "EPSG:900913",
+ numZoomLevels: 19
+ }, options);
+ }
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
+ name || this.name, url || this.url, {}, options
+ ]);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.XYZ(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ var xyz = this.getXYZ(bounds);
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ var s = '' + xyz.x + xyz.y + xyz.z;
+ url = this.selectUrl(s, url);
+ }
+
+ return OpenLayers.String.format(url, xyz);
+ },
+
+ /**
+ * Method: getXYZ
+ * Calculates x, y and z for the given bounds.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} - an object with x, y and z properties.
+ */
+ getXYZ: function(bounds) {
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.maxExtent.left) /
+ (res * this.tileSize.w));
+ var y = Math.round((this.maxExtent.top - bounds.top) /
+ (res * this.tileSize.h));
+ var z = this.getServerZoom();
+
+ if (this.wrapDateLine) {
+ var limit = Math.pow(2, z);
+ x = ((x % limit) + limit) % limit;
+ }
+
+ return {'x': x, 'y': y, 'z': z};
+ },
+
+ /* APIMethod: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left,
+ this.maxExtent.bottom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.XYZ"
+});
+/* ======================================================================
+ OpenLayers/Layer/OSM.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.OSM
+ * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap
+ * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use
+ * a different layer instead, you need to provide a different
+ * URL to the constructor. Here's an example for using OpenCycleMap:
+ *
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: name
+ * {String} The layer name. Defaults to "OpenStreetMap" if the first
+ * argument to the constructor is null or undefined.
+ */
+ name: "OpenStreetMap",
+
+ /**
+ * APIProperty: url
+ * {String} The tileset URL scheme. Defaults to
+ * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png
+ * (the official OSM tileset) if the second argument to the constructor
+ * is null or undefined. To use another tileset you can have something
+ * like this:
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ */
+ url: [
+ 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png'
+ ],
+
+ /**
+ * Property: attribution
+ * {String} The layer attribution.
+ */
+ attribution: "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
+
+ /**
+ * Property: sphericalMercator
+ * {Boolean}
+ */
+ sphericalMercator: true,
+
+ /**
+ * Property: wrapDateLine
+ * {Boolean}
+ */
+ wrapDateLine: true,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ *
+ * When using OSM tilesets other than the default ones, it may be
+ * necessary to set this to
+ *
+ * (code)
+ * {crossOriginKeyword: null}
+ * (end)
+ *
+ * if the server does not send Access-Control-Allow-Origin headers.
+ */
+ tileOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.OSM
+ *
+ * Parameters:
+ * name - {String} The layer name.
+ * url - {String} The tileset URL scheme.
+ * options - {Object} Configuration options for the layer. Any inherited
+ * layer option can be set in this object (e.g.
+ * <OpenLayers.Layer.Grid.buffer>).
+ */
+ initialize: function(name, url, options) {
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options && this.options.tileOptions);
+ },
+
+ /**
+ * Method: clone
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.OSM(
+ this.name, this.url, this.getOptions());
+ }
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM"
+});
+/* ======================================================================
+ OpenLayers/Renderer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer
+ * This is the base class for all renderers.
+ *
+ * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
+ * It is largely composed of virtual functions that are to be implemented
+ * in technology-specific subclasses, but there is some generic code too.
+ *
+ * The functions that *are* implemented here merely deal with the maintenance
+ * of the size and extent variables, as well as the cached 'resolution'
+ * value.
+ *
+ * A note to the user that all subclasses should use getResolution() instead
+ * of directly accessing this.resolution in order to correctly use the
+ * cacheing system.
+ *
+ */
+OpenLayers.Renderer = OpenLayers.Class({
+
+ /**
+ * Property: container
+ * {DOMElement}
+ */
+ container: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: extent
+ * {<OpenLayers.Bounds>}
+ */
+ extent: null,
+
+ /**
+ * Property: locked
+ * {Boolean} If the renderer is currently in a state where many things
+ * are changing, the 'locked' property is set to true. This means
+ * that renderers can expect at least one more drawFeature event to be
+ * called with the 'locked' property set to 'true': In some renderers,
+ * this might make sense to use as a 'only update local information'
+ * flag.
+ */
+ locked: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>}
+ */
+ size: null,
+
+ /**
+ * Property: resolution
+ * {Float} cache of current map resolution
+ */
+ resolution: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
+ */
+ map: null,
+
+ /**
+ * Property: featureDx
+ * {Number} Feature offset in x direction. Will be calculated for and
+ * applied to the current feature while rendering (see
+ * <calculateFeatureDx>).
+ */
+ featureDx: 0,
+
+ /**
+ * Constructor: OpenLayers.Renderer
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} options for this renderer. See sublcasses for
+ * supported options.
+ */
+ initialize: function(containerID, options) {
+ this.container = OpenLayers.Util.getElement(containerID);
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.container = null;
+ this.extent = null;
+ this.size = null;
+ this.resolution = null;
+ this.map = null;
+ },
+
+ /**
+ * APIMethod: supported
+ * This should be overridden by specific subclasses
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return false;
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ * We nullify the resolution cache (this.resolution) if resolutionChanged
+ * is set to true - this way it will be re-computed on the next
+ * getResolution() request.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ this.extent = extent.clone();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio);
+ this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
+ }
+ if (resolutionChanged) {
+ this.resolution = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ this.resolution = null;
+ },
+
+ /**
+ * Method: getResolution
+ * Uses cached copy of resolution if available to minimize computing
+ *
+ * Returns:
+ * {Float} The current map's resolution
+ */
+ getResolution: function() {
+ this.resolution = this.resolution || this.map.getResolution();
+ return this.resolution;
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. The optional style argument can be used
+ * to override the feature's own style. This method should only
+ * be called from layer.drawFeature().
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} true if the feature has been drawn completely, false if not,
+ * undefined if the feature had no geometry
+ */
+ drawFeature: function(feature, style) {
+ if(style == null) {
+ style = feature.style;
+ }
+ if (feature.geometry) {
+ var bounds = feature.geometry.getBounds();
+ if(bounds) {
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+ if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
+ style = {display: "none"};
+ } else {
+ this.calculateFeatureDx(bounds, worldBounds);
+ }
+ var rendered = this.drawGeometry(feature.geometry, style, feature.id);
+ if(style.display != "none" && style.label && rendered !== false) {
+
+ var location = feature.geometry.getCentroid();
+ if(style.labelXOffset || style.labelYOffset) {
+ var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
+ var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
+ var res = this.getResolution();
+ location.move(xOffset*res, yOffset*res);
+ }
+ this.drawText(feature.id, style, location);
+ } else {
+ this.removeText(feature.id);
+ }
+ return rendered;
+ }
+ }
+ },
+
+ /**
+ * Method: calculateFeatureDx
+ * {Number} Calculates the feature offset in x direction. Looking at the
+ * center of the feature bounds and the renderer extent, we calculate how
+ * many world widths the two are away from each other. This distance is
+ * used to shift the feature as close as possible to the center of the
+ * current enderer extent, which ensures that the feature is visible in the
+ * current viewport.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} Bounds of the feature
+ * worldBounds - {<OpenLayers.Bounds>} Bounds of the world
+ */
+ calculateFeatureDx: function(bounds, worldBounds) {
+ this.featureDx = 0;
+ if (worldBounds) {
+ var worldWidth = worldBounds.getWidth(),
+ rendererCenterX = (this.extent.left + this.extent.right) / 2,
+ featureCenterX = (bounds.left + bounds.right) / 2,
+ worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
+ this.featureDx = worldsAway * worldWidth;
+ }
+ },
+
+ /**
+ * Method: drawGeometry
+ *
+ * Draw a geometry. This should only be called from the renderer itself.
+ * Use layer.drawFeature() from outside the renderer.
+ * virtual function
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {<String>}
+ */
+ drawGeometry: function(geometry, style, featureId) {},
+
+ /**
+ * Method: drawText
+ * Function for drawing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {},
+
+ /**
+ * Method: removeText
+ * Function for removing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {},
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ * virtual function.
+ */
+ clear: function() {},
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ * How this happens is specific to the renderer. This should be
+ * called from layer.getFeatureFromEvent().
+ * Virtual function.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {},
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0, len=features.length; i<len; ++i) {
+ var feature = features[i];
+ this.eraseGeometry(feature.geometry, feature.id);
+ this.removeText(feature.id);
+ }
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Remove a geometry from the renderer (by id).
+ * virtual function.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a (different) renderer.
+ * To be implemented by subclasses that require a common renderer root for
+ * feature selection.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {},
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.container.id;
+ },
+
+ /**
+ * Method: applyDefaultSymbolizer
+ *
+ * Parameters:
+ * symbolizer - {Object}
+ *
+ * Returns:
+ * {Object}
+ */
+ applyDefaultSymbolizer: function(symbolizer) {
+ var result = OpenLayers.Util.extend({},
+ OpenLayers.Renderer.defaultSymbolizer);
+ if(symbolizer.stroke === false) {
+ delete result.strokeWidth;
+ delete result.strokeColor;
+ }
+ if(symbolizer.fill === false) {
+ delete result.fillColor;
+ }
+ OpenLayers.Util.extend(result, symbolizer);
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.defaultSymbolizer
+ * {Object} Properties from this symbolizer will be applied to symbolizers
+ * with missing properties. This can also be used to set a global
+ * symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
+ * following code before rendering any vector features:
+ * (code)
+ * OpenLayers.Renderer.defaultSymbolizer = {
+ * fillColor: "#808080",
+ * fillOpacity: 1,
+ * strokeColor: "#000000",
+ * strokeOpacity: 1,
+ * strokeWidth: 1,
+ * pointRadius: 3,
+ * graphicName: "square"
+ * };
+ * (end)
+ */
+OpenLayers.Renderer.defaultSymbolizer = {
+ fillColor: "#000000",
+ strokeColor: "#000000",
+ strokeWidth: 2,
+ fillOpacity: 1,
+ strokeOpacity: 1,
+ pointRadius: 0,
+ labelAlign: 'cm'
+};
+
+
+
+/**
+ * Constant: OpenLayers.Renderer.symbol
+ * Coordinate arrays for well known (named) symbols.
+ */
+OpenLayers.Renderer.symbol = {
+ "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
+ 303,215, 231,161, 321,161, 350,75],
+ "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
+ 4,0],
+ "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
+ "square": [0,0, 0,1, 1,1, 1,0, 0,0],
+ "triangle": [0,10, 10,10, 5,0, 0,10]
+};
+/* ======================================================================
+ OpenLayers/Renderer/Canvas.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.Canvas
+ * A renderer based on the 2D 'canvas' drawing element.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * APIProperty: hitDetection
+ * {Boolean} Allow for hit detection of features. Default is true.
+ */
+ hitDetection: true,
+
+ /**
+ * Property: hitOverflow
+ * {Number} The method for converting feature identifiers to color values
+ * supports 16777215 sequential values. Two features cannot be
+ * predictably detected if their identifiers differ by more than this
+ * value. The hitOverflow allows for bigger numbers (but the
+ * difference in values is still limited).
+ */
+ hitOverflow: 0,
+
+ /**
+ * Property: canvas
+ * {Canvas} The canvas context object.
+ */
+ canvas: null,
+
+ /**
+ * Property: features
+ * {Object} Internal object of feature/style pairs for use in redrawing the layer.
+ */
+ features: null,
+
+ /**
+ * Property: pendingRedraw
+ * {Boolean} The renderer needs a redraw call to render features added while
+ * the renderer was locked.
+ */
+ pendingRedraw: false,
+
+ /**
+ * Property: cachedSymbolBounds
+ * {Object} Internal cache of calculated symbol extents.
+ */
+ cachedSymbolBounds: {},
+
+ /**
+ * Constructor: OpenLayers.Renderer.Canvas
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} Optional properties to be set on the renderer.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+ this.root = document.createElement("canvas");
+ this.container.appendChild(this.root);
+ this.canvas = this.root.getContext("2d");
+ this.features = {};
+ if (this.hitDetection) {
+ this.hitCanvas = document.createElement("canvas");
+ this.hitContext = this.hitCanvas.getContext("2d");
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function() {
+ OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ // always redraw features
+ return false;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. Because the Canvas renderer has
+ * 'memory' of the features that it has drawn, we have to remove the
+ * feature so it doesn't redraw.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ this.eraseFeatures(this.features[featureId][0]);
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return OpenLayers.CANVAS_SUPPORTED;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Once the size is updated, redraw the canvas.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ var root = this.root;
+ root.style.width = size.w + "px";
+ root.style.height = size.h + "px";
+ root.width = size.w;
+ root.height = size.h;
+ this.resolution = null;
+ if (this.hitDetection) {
+ var hitCanvas = this.hitCanvas;
+ hitCanvas.style.width = size.w + "px";
+ hitCanvas.style.height = size.h + "px";
+ hitCanvas.width = size.w;
+ hitCanvas.height = size.h;
+ }
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. Stores the feature in the features list,
+ * then redraws the layer.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} The feature has been drawn completely. If the feature has no
+ * geometry, undefined will be returned. If the feature is not rendered
+ * for other reasons, false will be returned.
+ */
+ drawFeature: function(feature, style) {
+ var rendered;
+ if (feature.geometry) {
+ style = this.applyDefaultSymbolizer(style || feature.style);
+ // don't render if display none or feature outside extent
+ var bounds = feature.geometry.getBounds();
+
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+
+ var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});
+
+ rendered = (style.display !== "none") && !!bounds && intersects;
+ if (rendered) {
+ // keep track of what we have rendered for redraw
+ this.features[feature.id] = [feature, style];
+ }
+ else {
+ // remove from features tracked for redraw
+ delete(this.features[feature.id]);
+ }
+ this.pendingRedraw = true;
+ }
+ if (this.pendingRedraw && !this.locked) {
+ this.redraw();
+ this.pendingRedraw = false;
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: drawGeometry
+ * Used when looping (in redraw) over the features; draws
+ * the canvas.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0; i < geometry.components.length; i++) {
+ this.drawGeometry(geometry.components[i], style, featureId);
+ }
+ return;
+ }
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ this.drawPoint(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ this.drawLineString(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ this.drawLinearRing(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ this.drawPolygon(geometry, style, featureId);
+ break;
+ default:
+ break;
+ }
+ },
+
+ /**
+ * Method: drawExternalGraphic
+ * Called to draw External graphics.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawExternalGraphic: function(geometry, style, featureId) {
+ var img = new Image();
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ img.title = title;
+ }
+
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius * 2;
+ height = height ? height : style.pointRadius * 2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ var onLoad = function() {
+ if(!this.features[featureId]) {
+ return;
+ }
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+ if(!isNaN(p0) && !isNaN(p1)) {
+ var x = (p0 + xOffset) | 0;
+ var y = (p1 + yOffset) | 0;
+ var canvas = this.canvas;
+ canvas.globalAlpha = opacity;
+ var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
+ (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
+ /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
+ // 320 is the screen width of the G1 phone, for
+ // which drawImage works out of the box.
+ 320 / window.screen.width : 1
+ );
+ canvas.drawImage(
+ img, x*factor, y*factor, width*factor, height*factor
+ );
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId);
+ this.hitContext.fillRect(x, y, width, height);
+ }
+ }
+ };
+
+ img.onload = OpenLayers.Function.bind(onLoad, this);
+ img.src = style.externalGraphic;
+ },
+
+ /**
+ * Method: drawNamedSymbol
+ * Called to draw Well Known Graphic Symbol Name.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawNamedSymbol: function(geometry, style, featureId) {
+ var x, y, cx, cy, i, symbolBounds, scaling, angle;
+ var unscaledStrokeWidth;
+ var deg2rad = Math.PI / 180.0;
+
+ var symbol = OpenLayers.Renderer.symbol[style.graphicName];
+
+ if (!symbol) {
+ throw new Error(style.graphicName + ' is not a valid symbol name');
+ }
+
+ if (!symbol.length || symbol.length < 2) return;
+
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+
+ if (isNaN(p0) || isNaN(p1)) return;
+
+ // Use rounded line caps
+ this.canvas.lineCap = "round";
+ this.canvas.lineJoin = "round";
+
+ if (this.hitDetection) {
+ this.hitContext.lineCap = "round";
+ this.hitContext.lineJoin = "round";
+ }
+
+ // Scale and rotate symbols, using precalculated bounds whenever possible.
+ if (style.graphicName in this.cachedSymbolBounds) {
+ symbolBounds = this.cachedSymbolBounds[style.graphicName];
+ } else {
+ symbolBounds = new OpenLayers.Bounds();
+ for(i = 0; i < symbol.length; i+=2) {
+ symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
+ }
+ this.cachedSymbolBounds[style.graphicName] = symbolBounds;
+ }
+
+ // Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
+ // Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
+ this.canvas.save();
+ if (this.hitDetection) { this.hitContext.save(); }
+
+ // Step 3: place symbol at the desired location
+ this.canvas.translate(p0,p1);
+ if (this.hitDetection) { this.hitContext.translate(p0,p1); }
+
+ // Step 2a. rotate the symbol if necessary
+ angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
+ if (!isNaN(angle)) {
+ this.canvas.rotate(angle);
+ if (this.hitDetection) { this.hitContext.rotate(angle); }
+ }
+
+ // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
+ scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
+ this.canvas.scale(scaling,scaling);
+ if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
+
+ // Step 1: center the symbol at the origin
+ cx = symbolBounds.getCenterLonLat().lon;
+ cy = symbolBounds.getCenterLonLat().lat;
+ this.canvas.translate(-cx,-cy);
+ if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }
+
+ // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
+ // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
+ unscaledStrokeWidth = style.strokeWidth;
+ style.strokeWidth = unscaledStrokeWidth / scaling;
+
+ if (style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.fill();
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.fill();
+ }
+ }
+
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.stroke();
+
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style, scaling);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.hitContext.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.stroke();
+ }
+
+ }
+
+ style.strokeWidth = unscaledStrokeWidth;
+ this.canvas.restore();
+ if (this.hitDetection) { this.hitContext.restore(); }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: setCanvasStyle
+ * Prepare the canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * style - {Object} Symbolizer hash
+ */
+ setCanvasStyle: function(type, style) {
+ if (type === "fill") {
+ this.canvas.globalAlpha = style['fillOpacity'];
+ this.canvas.fillStyle = style['fillColor'];
+ } else if (type === "stroke") {
+ this.canvas.globalAlpha = style['strokeOpacity'];
+ this.canvas.strokeStyle = style['strokeColor'];
+ this.canvas.lineWidth = style['strokeWidth'];
+ } else {
+ this.canvas.globalAlpha = 0;
+ this.canvas.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: featureIdToHex
+ * Convert a feature ID string into an RGB hex string.
+ *
+ * Parameters:
+ * featureId - {String} Feature id
+ *
+ * Returns:
+ * {String} RGB hex string.
+ */
+ featureIdToHex: function(featureId) {
+ var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
+ if (id >= 16777216) {
+ this.hitOverflow = id - 16777215;
+ id = id % 16777216 + 1;
+ }
+ var hex = "000000" + id.toString(16);
+ var len = hex.length;
+ hex = "#" + hex.substring(len-6, len);
+ return hex;
+ },
+
+ /**
+ * Method: setHitContextStyle
+ * Prepare the hit canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * featureId - {String} The feature id.
+ * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
+ */
+ setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
+ var hex = this.featureIdToHex(featureId);
+ if (type == "fill") {
+ this.hitContext.globalAlpha = 1.0;
+ this.hitContext.fillStyle = hex;
+ } else if (type == "stroke") {
+ this.hitContext.globalAlpha = 1.0;
+ this.hitContext.strokeStyle = hex;
+ // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol
+ // on a transformed canvas, so the antialias width bump has to scale as well.
+ if (typeof strokeScaling === "undefined") {
+ this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
+ } else {
+ if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
+ }
+ } else {
+ this.hitContext.globalAlpha = 0;
+ this.hitContext.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawPoint: function(geometry, style, featureId) {
+ if(style.graphic !== false) {
+ if(style.externalGraphic) {
+ this.drawExternalGraphic(geometry, style, featureId);
+ } else if (style.graphicName && (style.graphicName != "circle")) {
+ this.drawNamedSymbol(geometry, style, featureId);
+ } else {
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+ if(!isNaN(p0) && !isNaN(p1)) {
+ var twoPi = Math.PI*2;
+ var radius = style.pointRadius;
+ if(style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.canvas.beginPath();
+ this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+ this.canvas.fill();
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+ this.hitContext.fill();
+ }
+ }
+
+ if(style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+ this.canvas.stroke();
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style);
+ this.hitContext.beginPath();
+ this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+ this.hitContext.stroke();
+ }
+ this.setCanvasStyle("reset");
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawLineString: function(geometry, style, featureId) {
+ style = OpenLayers.Util.applyDefaults({fill: false}, style);
+ this.drawLinearRing(geometry, style, featureId);
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawLinearRing: function(geometry, style, featureId) {
+ if (style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.renderPath(this.canvas, geometry, style, featureId, "fill");
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.renderPath(this.hitContext, geometry, style, featureId, "fill");
+ }
+ }
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.renderPath(this.canvas, geometry, style, featureId, "stroke");
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style);
+ this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: renderPath
+ * Render a path with stroke and optional fill.
+ */
+ renderPath: function(context, geometry, style, featureId, type) {
+ var components = geometry.components;
+ var len = components.length;
+ context.beginPath();
+ var start = this.getLocalXY(components[0]);
+ var x = start[0];
+ var y = start[1];
+ if (!isNaN(x) && !isNaN(y)) {
+ context.moveTo(start[0], start[1]);
+ for (var i=1; i<len; ++i) {
+ var pt = this.getLocalXY(components[i]);
+ context.lineTo(pt[0], pt[1]);
+ }
+ if (type === "fill") {
+ context.fill();
+ } else {
+ context.stroke();
+ }
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawPolygon: function(geometry, style, featureId) {
+ var components = geometry.components;
+ var len = components.length;
+ this.drawLinearRing(components[0], style, featureId);
+ // erase inner rings
+ for (var i=1; i<len; ++i) {
+ /**
+ * Note that this is overly agressive. Here we punch holes through
+ * all previously rendered features on the same canvas. A better
+ * solution for polygons with interior rings would be to draw the
+ * polygon on a sketch canvas first. We could erase all holes
+ * there and then copy the drawing to the layer canvas.
+ * TODO: http://trac.osgeo.org/openlayers/ticket/3130
+ */
+ this.canvas.globalCompositeOperation = "destination-out";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "destination-out";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
+ featureId
+ );
+ this.canvas.globalCompositeOperation = "source-over";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "source-over";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({fill: false}, style),
+ featureId
+ );
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * location - {<OpenLayers.Point>}
+ * style - {Object}
+ */
+ drawText: function(location, style) {
+ var pt = this.getLocalXY(location);
+
+ this.setCanvasStyle("reset");
+ this.canvas.fillStyle = style.fontColor;
+ this.canvas.globalAlpha = style.fontOpacity || 1.0;
+ var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
+ "normal", // "font-variant" not supported
+ style.fontWeight ? style.fontWeight : "normal",
+ style.fontSize ? style.fontSize : "1em",
+ style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ if (this.canvas.fillText) {
+ // HTML5
+ this.canvas.font = fontStyle;
+ this.canvas.textAlign =
+ OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
+ "center";
+ this.canvas.textBaseline =
+ OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
+ "middle";
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight =
+ this.canvas.measureText('Mg').height ||
+ this.canvas.measureText('xx').width;
+ pt[1] += lineHeight*vfactor*(numRows-1);
+ for (var i = 0; i < numRows; i++) {
+ if (style.labelOutlineWidth) {
+ this.canvas.save();
+ this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
+ this.canvas.strokeStyle = style.labelOutlineColor;
+ this.canvas.lineWidth = style.labelOutlineWidth;
+ this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
+ this.canvas.restore();
+ }
+ this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
+ }
+ } else if (this.canvas.mozDrawText) {
+ // Mozilla pre-Gecko1.9.1 (<FF3.1)
+ this.canvas.mozTextStyle = fontStyle;
+ // No built-in text alignment, so we measure and adjust the position
+ var hfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
+ if (hfactor == null) {
+ hfactor = -.5;
+ }
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight = this.canvas.mozMeasureText('xx');
+ pt[1] += lineHeight*(1 + (vfactor*numRows));
+ for (var i = 0; i < numRows; i++) {
+ var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
+ var y = pt[1] + (i*lineHeight);
+ this.canvas.translate(x, y);
+ this.canvas.mozDrawText(labelRows[i]);
+ this.canvas.translate(-x, -y);
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: getLocalXY
+ * transform geographic xy into pixel xy
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ */
+ getLocalXY: function(point) {
+ var resolution = this.getResolution();
+ var extent = this.extent;
+ var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution));
+ var y = ((extent.top / resolution) - point.y / resolution);
+ return [x, y];
+ },
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ */
+ clear: function() {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ this.features = {};
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a
+ * feature instead of a feature id to avoid an unnecessary lookup on the
+ * layer.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId, feature;
+
+ if (this.hitDetection && this.root.style.display !== "none") {
+ // this dragging check should go in the feature handler
+ if (!this.map.dragging) {
+ var xy = evt.xy;
+ var x = xy.x | 0;
+ var y = xy.y | 0;
+ var data = this.hitContext.getImageData(x, y, 1, 1).data;
+ if (data[3] === 255) { // antialiased
+ var id = data[2] + (256 * (data[1] + (256 * data[0])));
+ if (id) {
+ featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
+ try {
+ feature = this.features[featureId][0];
+ } catch(err) {
+ // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
+ // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
+ // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
+ }
+ }
+ }
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features; removes the feature from
+ * the list, then redraws the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0; i<features.length; ++i) {
+ delete this.features[features[i].id];
+ }
+ this.redraw();
+ },
+
+ /**
+ * Method: redraw
+ * The real 'meat' of the function: any time things have changed,
+ * redraw() can be called to loop over all the data and (you guessed
+ * it) redraw it. Unlike Elements-based Renderers, we can't interact
+ * with things once they're drawn, to remove them, for example, so
+ * instead we have to just clear everything and draw from scratch.
+ */
+ redraw: function() {
+ if (!this.locked) {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ var labelMap = [];
+ var feature, geometry, style;
+ var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
+ for (var id in this.features) {
+ if (!this.features.hasOwnProperty(id)) { continue; }
+ feature = this.features[id][0];
+ geometry = feature.geometry;
+ this.calculateFeatureDx(geometry.getBounds(), worldBounds);
+ style = this.features[id][1];
+ this.drawGeometry(geometry, style, feature.id);
+ if(style.label) {
+ labelMap.push([feature, style]);
+ }
+ }
+ var item;
+ for (var i=0, len=labelMap.length; i<len; ++i) {
+ item = labelMap[i];
+ this.drawText(item[0].geometry.getCentroid(), item[1]);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Canvas"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
+ "l": "left",
+ "r": "right",
+ "t": "top",
+ "b": "bottom"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
+ "l": 0,
+ "r": -1,
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
+ * {Number} Scale factor to apply to the canvas drawImage arguments. This
+ * is always 1 except for Android 2.1 devices, to work around
+ * http://code.google.com/p/android/issues/detail?id=5141.
+ */
+OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
+/* ======================================================================
+ OpenLayers/Handler.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Handler
+ * Base class to construct a higher-level handler for event sequences. All
+ * handlers have activate and deactivate methods. In addition, they have
+ * methods named like browser events. When a handler is activated, any
+ * additional methods named like a browser event is registered as a
+ * listener for the corresponding event. When a handler is deactivated,
+ * those same methods are unregistered as event listeners.
+ *
+ * Handlers also typically have a callbacks object with keys named like
+ * the abstracted events or event sequences that they are in charge of
+ * handling. The controls that wrap handlers define the methods that
+ * correspond to these abstract events - so instead of listening for
+ * individual browser events, they only listen for the abstract events
+ * defined by the handler.
+ *
+ * Handlers are created by controls, which ultimately have the responsibility
+ * of making changes to the the state of the application. Handlers
+ * themselves may make temporary changes, but in general are expected to
+ * return the application in the same state that they found it.
+ */
+OpenLayers.Handler = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: control
+ * {<OpenLayers.Control>}. The control that initialized this handler. The
+ * control is assumed to have a valid map property - that map is used
+ * in the handler's own setMap method.
+ */
+ control: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>}
+ */
+ map: null,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
+ * constants to construct a keyMask. The keyMask is used by
+ * <checkModifiers>. If the keyMask matches the combination of keys
+ * down on an event, checkModifiers returns true.
+ *
+ * Example:
+ * (code)
+ * // handler only responds if the Shift key is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
+ *
+ * // handler only responds if Ctrl-Shift is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
+ * OpenLayers.Handler.MOD_CTRL;
+ * (end)
+ */
+ keyMask: null,
+
+ /**
+ * Property: active
+ * {Boolean}
+ */
+ active: false,
+
+ /**
+ * Property: evt
+ * {Event} This property references the last event handled by the handler.
+ * Note that this property is not part of the stable API. Use of the
+ * evt property should be restricted to controls in the library
+ * or other applications that are willing to update with changes to
+ * the OpenLayers code.
+ */
+ evt: null,
+
+ /**
+ * Property: touch
+ * {Boolean} Indicates the support of touch events. When touch events are
+ * started touch will be true and all mouse related listeners will do
+ * nothing.
+ */
+ touch: false,
+
+ /**
+ * Constructor: OpenLayers.Handler
+ * Construct a handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that initialized this
+ * handler. The control is assumed to have a valid map property; that
+ * map is used in the handler's own setMap method. If a map property
+ * is present in the options argument it will be used instead.
+ * callbacks - {Object} An object whose properties correspond to abstracted
+ * events or sequences of browser events. The values for these
+ * properties are functions defined by the control that get called by
+ * the handler.
+ * options - {Object} An optional object whose properties will be set on
+ * the handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Util.extend(this, options);
+ this.control = control;
+ this.callbacks = callbacks;
+
+ var map = this.map || control.map;
+ if (map) {
+ this.setMap(map);
+ }
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ this.map = map;
+ },
+
+ /**
+ * Method: checkModifiers
+ * Check the keyMask on the handler. If no <keyMask> is set, this always
+ * returns true. If a <keyMask> is set and it matches the combination
+ * of keys down on an event, this returns true.
+ *
+ * Returns:
+ * {Boolean} The keyMask matches the keys down on an event.
+ */
+ checkModifiers: function (evt) {
+ if(this.keyMask == null) {
+ return true;
+ }
+ /* calculate the keyboard modifier mask for this event */
+ var keyModifiers =
+ (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
+ (evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) |
+ (evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) |
+ (evt.metaKey ? OpenLayers.Handler.MOD_META : 0);
+
+ /* if it differs from the handler object's key mask,
+ bail out of the event handler */
+ return (keyModifiers == this.keyMask);
+ },
+
+ /**
+ * APIMethod: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean} The handler was activated.
+ */
+ activate: function() {
+ if(this.active) {
+ return false;
+ }
+ // register for event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.register(events[i], this[events[i]]);
+ }
+ }
+ this.active = true;
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler. Returns false if the handler was already inactive.
+ *
+ * Returns:
+ * {Boolean} The handler was deactivated.
+ */
+ deactivate: function() {
+ if(!this.active) {
+ return false;
+ }
+ // unregister event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ this.touch = false;
+ this.active = false;
+ return true;
+ },
+
+ /**
+ * Method: startTouch
+ * Start touch events, this method must be called by subclasses in
+ * "touchstart" method. When touch events are started <touch> will be
+ * true and all mouse related listeners will do nothing.
+ */
+ startTouch: function() {
+ if (!this.touch) {
+ this.touch = true;
+ var events = [
+ "mousedown", "mouseup", "mousemove", "click", "dblclick",
+ "mouseout"
+ ];
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array(*)} An array of arguments (any type) with which to call
+ * the callback (defined by the control).
+ */
+ callback: function (name, args) {
+ if (name && this.callbacks[name]) {
+ this.callbacks[name].apply(this.control, args);
+ }
+ },
+
+ /**
+ * Method: register
+ * register an event on the map
+ */
+ register: function (name, method) {
+ // TODO: deal with registerPriority in 3.0
+ this.map.events.registerPriority(name, this, method);
+ this.map.events.registerPriority(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: unregister
+ * unregister an event from the map
+ */
+ unregister: function (name, method) {
+ this.map.events.unregister(name, this, method);
+ this.map.events.unregister(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: setEvent
+ * With each registered browser event, the handler sets its own evt
+ * property. This property can be accessed by controls if needed
+ * to get more information about the event that the handler is
+ * processing.
+ *
+ * This allows modifier keys on the event to be checked (alt, shift, ctrl,
+ * and meta cannot be checked with the keyboard handler). For a
+ * control to determine which modifier keys are associated with the
+ * event that a handler is currently processing, it should access
+ * (code)handler.evt.altKey || handler.evt.shiftKey ||
+ * handler.evt.ctrlKey || handler.evt.metaKey(end).
+ *
+ * Parameters:
+ * evt - {Event} The browser event.
+ */
+ setEvent: function(evt) {
+ this.evt = evt;
+ return true;
+ },
+
+ /**
+ * Method: destroy
+ * Deconstruct the handler.
+ */
+ destroy: function () {
+ // unregister event listeners
+ this.deactivate();
+ // eliminate circular references
+ this.control = this.map = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler"
+});
+
+/**
+ * Constant: OpenLayers.Handler.MOD_NONE
+ * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
+ */
+OpenLayers.Handler.MOD_NONE = 0;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_SHIFT
+ * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
+ */
+OpenLayers.Handler.MOD_SHIFT = 1;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_CTRL
+ * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
+ */
+OpenLayers.Handler.MOD_CTRL = 2;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_ALT
+ * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
+ */
+OpenLayers.Handler.MOD_ALT = 4;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_META
+ * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
+ */
+OpenLayers.Handler.MOD_META = 8;
+
+
+/* ======================================================================
+ OpenLayers/Handler/Drag.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Drag
+ * The drag handler is used to deal with sequences of browser events related
+ * to dragging. The handler is used by controls that want to know when
+ * a drag sequence begins, when a drag is happening, and when it has
+ * finished.
+ *
+ * Controls that use the drag handler typically construct it with callbacks
+ * for 'down', 'move', and 'done'. Callbacks for these keys are called
+ * when the drag begins, with each move, and when the drag is done. In
+ * addition, controls can have callbacks keyed to 'up' and 'out' if they
+ * care to differentiate between the types of events that correspond with
+ * the end of a drag sequence. If no drag actually occurs (no mouse move)
+ * the 'down' and 'up' callbacks will be called, but not the 'done'
+ * callback.
+ *
+ * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a mousedown or touchstart event is received, we want to
+ * record it, but not set 'dragging' until the mouse moves after starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of mousedown events from getting to listeners
+ * on the same element. Default is true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: dragging
+ * {Boolean}
+ */
+ dragging: false,
+
+ /**
+ * Property: last
+ * {<OpenLayers.Pixel>} The last pixel location of the drag.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {<OpenLayers.Pixel>} The first pixel location of the drag.
+ */
+ start: null,
+
+ /**
+ * Property: lastMoveEvt
+ * {Object} The last mousemove event that occurred. Used to
+ * position the map correctly when our "delay drag"
+ * timeout expired.
+ */
+ lastMoveEvt: null,
+
+ /**
+ * Property: oldOnselectstart
+ * {Function}
+ */
+ oldOnselectstart: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase performance, an interval (in
+ * milliseconds) can be set to reduce the number of drag events
+ * called. If set, a new drag event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: timeoutId
+ * {String} The id of the timeout used for the mousedown interval.
+ * This is "private", and should be left alone.
+ */
+ timeoutId: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, the handler will also handle mouse moves when
+ * the cursor has moved out of the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: documentEvents
+ * {Boolean} Are we currently observing document events?
+ */
+ documentEvents: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Drag
+ * Returns OpenLayers.Handler.Drag
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'move' and 'done' are supported. You can also speficy
+ * callbacks for 'down', 'up', and 'out' to respond to those events.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+
+ if (this.documentDrag === true) {
+ var me = this;
+ this._docMove = function(evt) {
+ me.mousemove({
+ xy: {x: evt.clientX, y: evt.clientY},
+ element: document
+ });
+ };
+ this._docUp = function(evt) {
+ me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
+ };
+ }
+ },
+
+
+ /**
+ * Method: dragstart
+ * This private method is factorized from mousedown and touchstart methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragstart: function (evt) {
+ var propagate = true;
+ this.dragging = false;
+ if (this.checkModifiers(evt) &&
+ (OpenLayers.Event.isLeftClick(evt) ||
+ OpenLayers.Event.isSingleTouch(evt))) {
+ this.started = true;
+ this.start = evt.xy;
+ this.last = evt.xy;
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.down(evt);
+ this.callback("down", [evt.xy]);
+
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart ?
+ document.onselectstart : OpenLayers.Function.True;
+ }
+ document.onselectstart = OpenLayers.Function.False;
+
+ propagate = !this.stopDown;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: dragmove
+ * This private method is factorized from mousemove and touchmove methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragmove: function (evt) {
+ this.lastMoveEvt = evt;
+ if (this.started && !this.timeoutId && (evt.xy.x != this.last.x ||
+ evt.xy.y != this.last.y)) {
+ if(this.documentDrag === true && this.documentEvents) {
+ if(evt.element === document) {
+ this.adjustXY(evt);
+ // do setEvent manually because the documentEvents are not
+ // registered with the map
+ this.setEvent(evt);
+ } else {
+ this.removeDocumentEvents();
+ }
+ }
+ if (this.interval > 0) {
+ this.timeoutId = setTimeout(
+ OpenLayers.Function.bind(this.removeTimeout, this),
+ this.interval);
+ }
+ this.dragging = true;
+
+ this.move(evt);
+ this.callback("move", [evt.xy]);
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart;
+ document.onselectstart = OpenLayers.Function.False;
+ }
+ this.last = evt.xy;
+ }
+ return true;
+ },
+
+ /**
+ * Method: dragend
+ * This private method is factorized from mouseup and touchend methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragend: function (evt) {
+ if (this.started) {
+ if(this.documentDrag === true && this.documentEvents) {
+ this.adjustXY(evt);
+ this.removeDocumentEvents();
+ }
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.up(evt);
+ this.callback("up", [evt.xy]);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ document.onselectstart = this.oldOnselectstart;
+ }
+ return true;
+ },
+
+ /**
+ * The four methods below (down, move, up, and out) are used by subclasses
+ * to do their own processing related to these mouse events.
+ */
+
+ /**
+ * Method: down
+ * This method is called during the handling of the mouse down event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse down event
+ */
+ down: function(evt) {
+ },
+
+ /**
+ * Method: move
+ * This method is called during the handling of the mouse move event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse move event
+ *
+ */
+ move: function(evt) {
+ },
+
+ /**
+ * Method: up
+ * This method is called during the handling of the mouse up event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ },
+
+ /**
+ * Method: out
+ * This method is called during the handling of the mouse out event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ },
+
+ /**
+ * The methods below are part of the magic of event handling. Because
+ * they are named like browser events, they are registered as listeners
+ * for the events they represent.
+ */
+
+ /**
+ * Method: mousedown
+ * Handle mousedown events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousedown: function(evt) {
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousemove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: removeTimeout
+ * Private. Called by mousemove() to remove the drag timeout.
+ */
+ removeTimeout: function() {
+ this.timeoutId = null;
+ // if timeout expires while we're still dragging (mouseup
+ // hasn't occurred) then call mousemove to move to the
+ // correct position
+ if(this.dragging) {
+ this.mousemove(this.lastMoveEvt);
+ }
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseup: function(evt) {
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ // override evt.xy with last position since touchend does not have
+ // any touch position
+ evt.xy = this.last;
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouseout events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseout: function (evt) {
+ if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if(this.documentDrag === true) {
+ this.addDocumentEvents();
+ } else {
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.out(evt);
+ this.callback("out", []);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ if(document.onselectstart) {
+ document.onselectstart = this.oldOnselectstart;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Method: click
+ * The drag handler captures the click event. If something else registers
+ * for clicks on the same element, its listener will not be called
+ * after a drag.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ click: function (evt) {
+ // let the click event propagate only if the mouse moved
+ return (this.start == this.last);
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragging = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.dragging = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: adjustXY
+ * Converts event coordinates that are relative to the document body to
+ * ones that are relative to the map viewport. The latter is the default in
+ * OpenLayers.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ adjustXY: function(evt) {
+ var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
+ evt.xy.x -= pos[0];
+ evt.xy.y -= pos[1];
+ },
+
+ /**
+ * Method: addDocumentEvents
+ * Start observing document events when documentDrag is true and the mouse
+ * cursor leaves the map viewport while dragging.
+ */
+ addDocumentEvents: function() {
+ OpenLayers.Element.addClass(document.body, "olDragDown");
+ this.documentEvents = true;
+ OpenLayers.Event.observe(document, "mousemove", this._docMove);
+ OpenLayers.Event.observe(document, "mouseup", this._docUp);
+ },
+
+ /**
+ * Method: removeDocumentEvents
+ * Stops observing document events when documentDrag is true and the mouse
+ * cursor re-enters the map viewport while dragging.
+ */
+ removeDocumentEvents: function() {
+ OpenLayers.Element.removeClass(document.body, "olDragDown");
+ this.documentEvents = false;
+ OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
+ OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Drag"
+});
+/* ======================================================================
+ OpenLayers/Handler/Keyboard.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.handler.Keyboard
+ * A handler for keyboard events. Create a new instance with the
+ * <OpenLayers.Handler.Keyboard> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, {
+
+ /* http://www.quirksmode.org/js/keys.html explains key x-browser
+ key handling quirks in pretty nice detail */
+
+ /**
+ * Constant: KEY_EVENTS
+ * keydown, keypress, keyup
+ */
+ KEY_EVENTS: ["keydown", "keyup"],
+
+ /**
+ * Property: eventListener
+ * {Function}
+ */
+ eventListener: null,
+
+ /**
+ * Property: observeElement
+ * {DOMElement|String} The DOM element on which we listen for
+ * key events. Default to the document.
+ */
+ observeElement: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Keyboard
+ * Returns a new keyboard handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'keydown', 'keypress', and 'keyup' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ // cache the bound event listener method so it can be unobserved later
+ this.eventListener = OpenLayers.Function.bindAsEventListener(
+ this.handleKeyEvent, this
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ this.eventListener = null;
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.observeElement = this.observeElement || document;
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.observe(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.stopObserving(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleKeyEvent
+ */
+ handleKeyEvent: function (evt) {
+ if (this.checkModifiers(evt)) {
+ this.callback(evt.type, [evt]);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Keyboard"
+});
+/* ======================================================================
+ OpenLayers/Control/ModifyFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ModifyFeature
+ * Control to modify features. When activated, a click renders the vertices
+ * of a feature - these vertices can then be dragged. By default, the
+ * delete key will delete the vertex under the mouse. New features are
+ * added by dragging "virtual vertices" between vertices. Create a new
+ * control with the <OpenLayers.Control.ModifyFeature> constructor.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, dragging vertices will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict modification to a limited set of geometry
+ * types, send a list of strings corresponding to the geometry class
+ * names.
+ */
+ geometryTypes: null,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click.
+ * Default is true.
+ */
+ toggle: true,
+
+ /**
+ * APIProperty: standalone
+ * {Boolean} Set to true to create a control without SelectFeature
+ * capabilities. Default is false. If standalone is true, to modify
+ * a feature, call the <selectFeature> method with the target feature.
+ * Note that you must call the <unselectFeature> method to finish
+ * feature modification in standalone mode (before starting to modify
+ * another feature).
+ */
+ standalone: false,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} Feature currently available for modification.
+ */
+ feature: null,
+
+ /**
+ * Property: vertex
+ * {<OpenLayers.Feature.Vector>} Vertex currently being modified.
+ */
+ vertex: null,
+
+ /**
+ * Property: vertices
+ * {Array(<OpenLayers.Feature.Vector>)} Verticies currently available
+ * for dragging.
+ */
+ vertices: null,
+
+ /**
+ * Property: virtualVertices
+ * {Array(<OpenLayers.Feature.Vector>)} Virtual vertices in the middle
+ * of each edge.
+ */
+ virtualVertices: null,
+
+ /**
+ * Property: handlers
+ * {Object}
+ */
+ handlers: null,
+
+ /**
+ * APIProperty: deleteCodes
+ * {Array(Integer)} Keycodes for deleting verticies. Set to null to disable
+ * vertex deltion by keypress. If non-null, keypresses with codes
+ * in this array will delete vertices under the mouse. Default
+ * is 46 and 68, the 'delete' and lowercase 'd' keys.
+ */
+ deleteCodes: null,
+
+ /**
+ * APIProperty: virtualStyle
+ * {Object} A symbolizer to be used for virtual vertices.
+ */
+ virtualStyle: null,
+
+ /**
+ * APIProperty: vertexRenderIntent
+ * {String} The renderIntent to use for vertices. If no <virtualStyle> is
+ * provided, this renderIntent will also be used for virtual vertices, with
+ * a fillOpacity and strokeOpacity of 0.3. Default is null, which means
+ * that the layer's default style will be used for vertices.
+ */
+ vertexRenderIntent: null,
+
+ /**
+ * APIProperty: mode
+ * {Integer} Bitfields specifying the modification mode. Defaults to
+ * OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a
+ * combination of options, use the | operator. For example, to allow
+ * the control to both resize and rotate features, use the following
+ * syntax
+ * (code)
+ * control.mode = OpenLayers.Control.ModifyFeature.RESIZE |
+ * OpenLayers.Control.ModifyFeature.ROTATE;
+ * (end)
+ */
+ mode: null,
+
+ /**
+ * APIProperty: createVertices
+ * {Boolean} Create new vertices by dragging the virtual vertices
+ * in the middle of each edge. Default is true.
+ */
+ createVertices: true,
+
+ /**
+ * Property: modified
+ * {Boolean} The currently selected feature has been modified.
+ */
+ modified: false,
+
+ /**
+ * Property: radiusHandle
+ * {<OpenLayers.Feature.Vector>} A handle for rotating/resizing a feature.
+ */
+ radiusHandle: null,
+
+ /**
+ * Property: dragHandle
+ * {<OpenLayers.Feature.Vector>} A handle for dragging a feature.
+ */
+ dragHandle: null,
+
+ /**
+ * APIProperty: onModificationStart
+ * {Function} *Deprecated*. Register for "beforefeaturemodified" instead.
+ * The "beforefeaturemodified" event is triggered on the layer before
+ * any modification begins.
+ *
+ * Optional function to be called when a feature is selected
+ * to be modified. The function should expect to be called with a
+ * feature. This could be used for example to allow to lock the
+ * feature on server-side.
+ */
+ onModificationStart: function() {},
+
+ /**
+ * APIProperty: onModification
+ * {Function} *Deprecated*. Register for "featuremodified" instead.
+ * The "featuremodified" event is triggered on the layer with each
+ * feature modification.
+ *
+ * Optional function to be called when a feature has been
+ * modified. The function should expect to be called with a feature.
+ */
+ onModification: function() {},
+
+ /**
+ * APIProperty: onModificationEnd
+ * {Function} *Deprecated*. Register for "afterfeaturemodified" instead.
+ * The "afterfeaturemodified" event is triggered on the layer after
+ * a feature has been modified.
+ *
+ * Optional function to be called when a feature is finished
+ * being modified. The function should expect to be called with a
+ * feature.
+ */
+ onModificationEnd: function() {},
+
+ /**
+ * Constructor: OpenLayers.Control.ModifyFeature
+ * Create a new modify feature control.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+ * will be modified.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ options = options || {};
+ this.layer = layer;
+ this.vertices = [];
+ this.virtualVertices = [];
+ this.virtualStyle = OpenLayers.Util.extend({},
+ this.layer.style ||
+ this.layer.styleMap.createSymbolizer(null, options.vertexRenderIntent)
+ );
+ this.virtualStyle.fillOpacity = 0.3;
+ this.virtualStyle.strokeOpacity = 0.3;
+ this.deleteCodes = [46, 68];
+ this.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ if(!(OpenLayers.Util.isArray(this.deleteCodes))) {
+ this.deleteCodes = [this.deleteCodes];
+ }
+
+ // configure the drag handler
+ var dragCallbacks = {
+ down: function(pixel) {
+ this.vertex = null;
+ var feature = this.layer.getFeatureFromEvent(
+ this.handlers.drag.evt);
+ if (feature) {
+ this.dragStart(feature);
+ } else if (this.clickout) {
+ this._unselect = this.feature;
+ }
+ },
+ move: function(pixel) {
+ delete this._unselect;
+ if (this.vertex) {
+ this.dragVertex(this.vertex, pixel);
+ }
+ },
+ up: function() {
+ this.handlers.drag.stopDown = false;
+ if (this._unselect) {
+ this.unselectFeature(this._unselect);
+ delete this._unselect;
+ }
+ },
+ done: function(pixel) {
+ if (this.vertex) {
+ this.dragComplete(this.vertex);
+ }
+ }
+ };
+ var dragOptions = {
+ documentDrag: this.documentDrag,
+ stopDown: false
+ };
+
+ // configure the keyboard handler
+ var keyboardOptions = {
+ keydown: this.handleKeypress
+ };
+ this.handlers = {
+ keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions),
+ drag: new OpenLayers.Handler.Drag(this, dragCallbacks, dragOptions)
+ };
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ }
+ this.layer = null;
+ OpenLayers.Control.prototype.destroy.apply(this, []);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the control.
+ */
+ activate: function() {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ return (this.handlers.keyboard.activate() &&
+ this.handlers.drag.activate() &&
+ OpenLayers.Control.prototype.activate.apply(this, arguments));
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control.
+ *
+ * Returns:
+ * {Boolean} Successfully deactivated the control.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ // the return from the controls is unimportant in this case
+ if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.layer.removeFeatures(this.virtualVertices, {silent: true});
+ this.vertices = [];
+ this.handlers.drag.deactivate();
+ this.handlers.keyboard.deactivate();
+ var feature = this.feature;
+ if (feature && feature.geometry && feature.layer) {
+ this.unselectFeature(feature);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: beforeSelectFeature
+ * Called before a feature is selected.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature about to be selected.
+ */
+ beforeSelectFeature: function(feature) {
+ return this.layer.events.triggerEvent(
+ "beforefeaturemodified", {feature: feature}
+ );
+ },
+
+ /**
+ * APIMethod: selectFeature
+ * Select a feature for modification in standalone mode. In non-standalone
+ * mode, this method is called when a feature is selected by clicking.
+ * Register a listener to the beforefeaturemodified event and return false
+ * to prevent feature modification.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} the selected feature.
+ */
+ selectFeature: function(feature) {
+ if (this.feature === feature ||
+ (this.geometryTypes && OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) == -1)) {
+ return;
+ }
+ if (this.beforeSelectFeature(feature) !== false) {
+ if (this.feature) {
+ this.unselectFeature(this.feature);
+ }
+ this.feature = feature;
+ this.layer.selectedFeatures.push(feature);
+ this.layer.drawFeature(feature, 'select');
+ this.modified = false;
+ this.resetVertices();
+ this.onModificationStart(this.feature);
+ }
+ // keep track of geometry modifications
+ var modified = feature.modified;
+ if (feature.geometry && !(modified && modified.geometry)) {
+ this._originalGeometry = feature.geometry.clone();
+ }
+ },
+
+ /**
+ * APIMethod: unselectFeature
+ * Called when the select feature control unselects a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The unselected feature.
+ */
+ unselectFeature: function(feature) {
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ if(this.dragHandle) {
+ this.layer.destroyFeatures([this.dragHandle], {silent: true});
+ delete this.dragHandle;
+ }
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ delete this.radiusHandle;
+ }
+ this.layer.drawFeature(this.feature, 'default');
+ this.feature = null;
+ OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature);
+ this.onModificationEnd(feature);
+ this.layer.events.triggerEvent("afterfeaturemodified", {
+ feature: feature,
+ modified: this.modified
+ });
+ this.modified = false;
+ },
+
+
+ /**
+ * Method: dragStart
+ * Called by the drag handler before a feature is dragged. This method is
+ * used to differentiate between points and vertices
+ * of higher order geometries.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
+ * dragged.
+ */
+ dragStart: function(feature) {
+ var isPoint = feature.geometry.CLASS_NAME ==
+ 'OpenLayers.Geometry.Point';
+ if (!this.standalone &&
+ ((!feature._sketch && isPoint) || !feature._sketch)) {
+ if (this.toggle && this.feature === feature) {
+ // mark feature for unselection
+ this._unselect = feature;
+ }
+ this.selectFeature(feature);
+ }
+ if (feature._sketch || isPoint) {
+ // feature is a drag or virtual handle or point
+ this.vertex = feature;
+ this.handlers.drag.stopDown = true;
+ }
+ },
+
+ /**
+ * Method: dragVertex
+ * Called by the drag handler with each drag move of a vertex.
+ *
+ * Parameters:
+ * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
+ */
+ dragVertex: function(vertex, pixel) {
+ var pos = this.map.getLonLatFromViewPortPx(pixel);
+ var geom = vertex.geometry;
+ geom.move(pos.lon - geom.x, pos.lat - geom.y);
+ this.modified = true;
+ /**
+ * Five cases:
+ * 1) dragging a simple point
+ * 2) dragging a virtual vertex
+ * 3) dragging a drag handle
+ * 4) dragging a real vertex
+ * 5) dragging a radius handle
+ */
+ if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ // dragging a simple point
+ this.layer.events.triggerEvent("vertexmodified", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: pixel
+ });
+ } else {
+ if(vertex._index) {
+ // dragging a virtual vertex
+ vertex.geometry.parent.addComponent(vertex.geometry,
+ vertex._index);
+ // move from virtual to real vertex
+ delete vertex._index;
+ OpenLayers.Util.removeItem(this.virtualVertices, vertex);
+ this.vertices.push(vertex);
+ } else if(vertex == this.dragHandle) {
+ // dragging a drag handle
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ this.radiusHandle = null;
+ }
+ } else if(vertex !== this.radiusHandle) {
+ // dragging a real vertex
+ this.layer.events.triggerEvent("vertexmodified", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: pixel
+ });
+ }
+ // dragging a radius handle - no special treatment
+ if(this.virtualVertices.length > 0) {
+ this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ }
+ this.layer.drawFeature(this.feature, this.standalone ? undefined :
+ 'select');
+ }
+ // keep the vertex on top so it gets the mouseout after dragging
+ // this should be removed in favor of an option to draw under or
+ // maintain node z-index
+ this.layer.drawFeature(vertex);
+ },
+
+ /**
+ * Method: dragComplete
+ * Called by the drag handler when the feature dragging is complete.
+ *
+ * Parameters:
+ * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ */
+ dragComplete: function(vertex) {
+ this.resetVertices();
+ this.setFeatureState();
+ this.onModification(this.feature);
+ this.layer.events.triggerEvent("featuremodified",
+ {feature: this.feature});
+ },
+
+ /**
+ * Method: setFeatureState
+ * Called when the feature is modified. If the current state is not
+ * INSERT or DELETE, the state is set to UPDATE.
+ */
+ setFeatureState: function() {
+ if(this.feature.state != OpenLayers.State.INSERT &&
+ this.feature.state != OpenLayers.State.DELETE) {
+ this.feature.state = OpenLayers.State.UPDATE;
+ if (this.modified && this._originalGeometry) {
+ var feature = this.feature;
+ feature.modified = OpenLayers.Util.extend(feature.modified, {
+ geometry: this._originalGeometry
+ });
+ delete this._originalGeometry;
+ }
+ }
+ },
+
+ /**
+ * Method: resetVertices
+ */
+ resetVertices: function() {
+ if(this.vertices.length > 0) {
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ }
+ if(this.virtualVertices.length > 0) {
+ this.layer.removeFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ }
+ if(this.dragHandle) {
+ this.layer.destroyFeatures([this.dragHandle], {silent: true});
+ this.dragHandle = null;
+ }
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ this.radiusHandle = null;
+ }
+ if(this.feature &&
+ this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
+ if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
+ this.collectDragHandle();
+ }
+ if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
+ OpenLayers.Control.ModifyFeature.RESIZE))) {
+ this.collectRadiusHandle();
+ }
+ if(this.mode & OpenLayers.Control.ModifyFeature.RESHAPE){
+ // Don't collect vertices when we're resizing
+ if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)){
+ this.collectVertices();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: handleKeypress
+ * Called by the feature handler on keypress. This is used to delete
+ * vertices. If the <deleteCode> property is set, vertices will
+ * be deleted when a feature is selected for modification and
+ * the mouse is over a vertex.
+ *
+ * Parameters:
+ * evt - {Event} Keypress event.
+ */
+ handleKeypress: function(evt) {
+ var code = evt.keyCode;
+
+ // check for delete key
+ if(this.feature &&
+ OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
+ var vertex = this.layer.getFeatureFromEvent(this.handlers.drag.evt);
+ if (vertex &&
+ OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
+ !this.handlers.drag.dragging && vertex.geometry.parent) {
+ // remove the vertex
+ vertex.geometry.parent.removeComponent(vertex.geometry);
+ this.layer.events.triggerEvent("vertexremoved", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: evt.xy
+ });
+ this.layer.drawFeature(this.feature, this.standalone ?
+ undefined : 'select');
+ this.modified = true;
+ this.resetVertices();
+ this.setFeatureState();
+ this.onModification(this.feature);
+ this.layer.events.triggerEvent("featuremodified",
+ {feature: this.feature});
+ }
+ }
+ },
+
+ /**
+ * Method: collectVertices
+ * Collect the vertices from the modifiable feature's geometry and push
+ * them on to the control's vertices array.
+ */
+ collectVertices: function() {
+ this.vertices = [];
+ this.virtualVertices = [];
+ var control = this;
+ function collectComponentVertices(geometry) {
+ var i, vertex, component, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(geometry);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ var numVert = geometry.components.length;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ numVert -= 1;
+ }
+ for(i=0; i<numVert; ++i) {
+ component = geometry.components[i];
+ if(component.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(component);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ collectComponentVertices(component);
+ }
+ }
+
+ // add virtual vertices in the middle of each edge
+ if (control.createVertices && geometry.CLASS_NAME != "OpenLayers.Geometry.MultiPoint") {
+ for(i=0, len=geometry.components.length; i<len-1; ++i) {
+ var prevVertex = geometry.components[i];
+ var nextVertex = geometry.components[i + 1];
+ if(prevVertex.CLASS_NAME == "OpenLayers.Geometry.Point" &&
+ nextVertex.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ var x = (prevVertex.x + nextVertex.x) / 2;
+ var y = (prevVertex.y + nextVertex.y) / 2;
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y),
+ null, control.virtualStyle
+ );
+ // set the virtual parent and intended index
+ point.geometry.parent = geometry;
+ point._index = i + 1;
+ point._sketch = true;
+ control.virtualVertices.push(point);
+ }
+ }
+ }
+ }
+ }
+ collectComponentVertices.call(this, this.feature.geometry);
+ this.layer.addFeatures(this.virtualVertices, {silent: true});
+ this.layer.addFeatures(this.vertices, {silent: true});
+ },
+
+ /**
+ * Method: collectDragHandle
+ * Collect the drag handle for the selected geometry.
+ */
+ collectDragHandle: function() {
+ var geometry = this.feature.geometry;
+ var center = geometry.getBounds().getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var origin = new OpenLayers.Feature.Vector(originGeometry);
+ originGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ geometry.move(x, y);
+ };
+ origin._sketch = true;
+ this.dragHandle = origin;
+ this.dragHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.dragHandle], {silent: true});
+ },
+
+ /**
+ * Method: collectRadiusHandle
+ * Collect the radius handle for the selected geometry.
+ */
+ collectRadiusHandle: function() {
+ var geometry = this.feature.geometry;
+ var bounds = geometry.getBounds();
+ var center = bounds.getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var radiusGeometry = new OpenLayers.Geometry.Point(
+ bounds.right, bounds.bottom
+ );
+ var radius = new OpenLayers.Feature.Vector(radiusGeometry);
+ var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
+ var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
+ var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
+
+ radiusGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ var dx1 = this.x - originGeometry.x;
+ var dy1 = this.y - originGeometry.y;
+ var dx0 = dx1 - x;
+ var dy0 = dy1 - y;
+ if(rotate) {
+ var a0 = Math.atan2(dy0, dx0);
+ var a1 = Math.atan2(dy1, dx1);
+ var angle = a1 - a0;
+ angle *= 180 / Math.PI;
+ geometry.rotate(angle, originGeometry);
+ }
+ if(resize) {
+ var scale, ratio;
+ // 'resize' together with 'reshape' implies that the aspect
+ // ratio of the geometry will not be preserved whilst resizing
+ if (reshape) {
+ scale = dy1 / dy0;
+ ratio = (dx1 / dx0) / scale;
+ } else {
+ var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+ var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+ scale = l1 / l0;
+ }
+ geometry.resize(scale, originGeometry, ratio);
+ }
+ };
+ radius._sketch = true;
+ this.radiusHandle = radius;
+ this.radiusHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.radiusHandle], {silent: true});
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and all handlers.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The control's map.
+ */
+ setMap: function(map) {
+ this.handlers.drag.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ModifyFeature"
+});
+
+/**
+ * Constant: RESHAPE
+ * {Integer} Constant used to make the control work in reshape mode
+ */
+OpenLayers.Control.ModifyFeature.RESHAPE = 1;
+/**
+ * Constant: RESIZE
+ * {Integer} Constant used to make the control work in resize mode
+ */
+OpenLayers.Control.ModifyFeature.RESIZE = 2;
+/**
+ * Constant: ROTATE
+ * {Integer} Constant used to make the control work in rotate mode
+ */
+OpenLayers.Control.ModifyFeature.ROTATE = 4;
+/**
+ * Constant: DRAG
+ * {Integer} Constant used to make the control work in drag mode
+ */
+OpenLayers.Control.ModifyFeature.DRAG = 8;
+/* ======================================================================
+ OpenLayers/Layer/Bing.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Bing
+ * Bing layer using direct tile access as provided by Bing Maps REST Services.
+ * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more
+ * information. Note: Terms of Service compliant use requires the map to be
+ * configured with an <OpenLayers.Control.Attribution> control and the
+ * attribution placed on or near the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * Property: key
+ * {String} API key for Bing maps, get your own key
+ * at http://bingmapsportal.com/ .
+ */
+ key: null,
+
+ /**
+ * Property: serverResolutions
+ * {Array} the resolutions provided by the Bing servers.
+ */
+ serverResolutions: [
+ 156543.03390625, 78271.516953125, 39135.7584765625,
+ 19567.87923828125, 9783.939619140625, 4891.9698095703125,
+ 2445.9849047851562, 1222.9924523925781, 611.4962261962891,
+ 305.74811309814453, 152.87405654907226, 76.43702827453613,
+ 38.218514137268066, 19.109257068634033, 9.554628534317017,
+ 4.777314267158508, 2.388657133579254, 1.194328566789627,
+ 0.5971642833948135, 0.29858214169740677, 0.14929107084870338,
+ 0.07464553542435169
+ ],
+
+ /**
+ * Property: attributionTemplate
+ * {String}
+ */
+ attributionTemplate: '<span class="olBingAttribution ${type}">' +
+ '<div><a target="_blank" href="http://www.bing.com/maps/">' +
+ '<img src="${logo}" /></a></div>${copyrights}' +
+ '<a style="white-space: nowrap" target="_blank" '+
+ 'href="http://www.microsoft.com/maps/product/terms.html">' +
+ 'Terms of Use</a></span>',
+
+ /**
+ * Property: metadata
+ * {Object} Metadata for this layer, as returned by the callback script
+ */
+ metadata: null,
+
+ /**
+ * Property: protocolRegex
+ * {RegExp} Regular expression to match and replace http: in bing urls
+ */
+ protocolRegex: /^http:/i,
+
+ /**
+ * APIProperty: type
+ * {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used. Default is "Road".
+ */
+ type: "Road",
+
+ /**
+ * APIProperty: culture
+ * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx
+ * for the definition and the possible values. Default is "en-US".
+ */
+ culture: "en-US",
+
+ /**
+ * APIProperty: metadataParams
+ * {Object} Optional url parameters for the Get Imagery Metadata request
+ * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx
+ */
+ metadataParams: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ */
+ tileOptions: null,
+
+ /** APIProperty: protocol
+ * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo
+ * Can be 'http:' 'https:' or ''
+ *
+ * Warning: tiles may not be available under both HTTP and HTTPS protocols.
+ * Microsoft approved use of both HTTP and HTTPS urls for tiles. However
+ * this is undocumented and the Imagery Metadata API always returns HTTP
+ * urls.
+ *
+ * Default is '', unless when executed from a file:/// uri, in which case
+ * it is 'http:'.
+ */
+ protocol: ~window.location.href.indexOf('http') ? '' : 'http:',
+
+ /**
+ * Constructor: OpenLayers.Layer.Bing
+ * Create a new Bing layer.
+ *
+ * Example:
+ * (code)
+ * var road = new OpenLayers.Layer.Bing({
+ * name: "My Bing Aerial Layer",
+ * type: "Aerial",
+ * key: "my-api-key-here",
+ * });
+ * (end)
+ *
+ * Parameters:
+ * options - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * key - {String} Bing Maps API key for your application. Get one at
+ * http://bingmapsportal.com/.
+ * type - {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used.
+ *
+ * Any other documented layer properties can be provided in the config object.
+ */
+ initialize: function(options) {
+ options = OpenLayers.Util.applyDefaults({
+ sphericalMercator: true
+ }, options);
+ var name = options.name || "Bing " + (options.type || this.type);
+
+ var newArgs = [name, null, options];
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options.tileOptions);
+ this.loadMetadata();
+ },
+
+ /**
+ * Method: loadMetadata
+ */
+ loadMetadata: function() {
+ this._callbackId = "_callback_" + this.id.replace(/\./g, "_");
+ // link the processMetadata method to the global scope and bind it
+ // to this instance
+ window[this._callbackId] = OpenLayers.Function.bind(
+ OpenLayers.Layer.Bing.processMetadata, this
+ );
+ var params = OpenLayers.Util.applyDefaults({
+ key: this.key,
+ jsonp: this._callbackId,
+ include: "ImageryProviders"
+ }, this.metadataParams);
+ var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" +
+ this.type + "?" + OpenLayers.Util.getParameterString(params);
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.id = this._callbackId;
+ document.getElementsByTagName("head")[0].appendChild(script);
+ },
+
+ /**
+ * Method: initLayer
+ *
+ * Sets layer properties according to the metadata provided by the API
+ */
+ initLayer: function() {
+ var res = this.metadata.resourceSets[0].resources[0];
+ var url = res.imageUrl.replace("{quadkey}", "${quadkey}");
+ url = url.replace("{culture}", this.culture);
+ url = url.replace(this.protocolRegex, this.protocol);
+ this.url = [];
+ for (var i=0; i<res.imageUrlSubdomains.length; ++i) {
+ this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
+ }
+ this.addOptions({
+ maxResolution: Math.min(
+ this.serverResolutions[res.zoomMin],
+ this.maxResolution || Number.POSITIVE_INFINITY
+ ),
+ numZoomLevels: Math.min(
+ res.zoomMax + 1 - res.zoomMin, this.numZoomLevels
+ )
+ }, true);
+ if (!this.isBaseLayer) {
+ this.redraw();
+ }
+ this.updateAttribution();
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Paramters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getURL: function(bounds) {
+ if (!this.url) {
+ return;
+ }
+ var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z;
+ var quadDigits = [];
+ for (var i = z; i > 0; --i) {
+ var digit = '0';
+ var mask = 1 << (i - 1);
+ if ((x & mask) != 0) {
+ digit++;
+ }
+ if ((y & mask) != 0) {
+ digit++;
+ digit++;
+ }
+ quadDigits.push(digit);
+ }
+ var quadKey = quadDigits.join("");
+ var url = this.selectUrl('' + x + y + z, this.url);
+
+ return OpenLayers.String.format(url, {'quadkey': quadKey});
+ },
+
+ /**
+ * Method: updateAttribution
+ * Updates the attribution according to the requirements outlined in
+ * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html
+ */
+ updateAttribution: function() {
+ var metadata = this.metadata;
+ if (!metadata.resourceSets || !this.map || !this.map.center) {
+ return;
+ }
+ var res = metadata.resourceSets[0].resources[0];
+ var extent = this.map.getExtent().transform(
+ this.map.getProjectionObject(),
+ new OpenLayers.Projection("EPSG:4326")
+ );
+ var providers = res.imageryProviders || [],
+ zoom = OpenLayers.Util.indexOf(this.serverResolutions,
+ this.getServerResolution()),
+ copyrights = "", provider, i, ii, j, jj, bbox, coverage;
+ for (i=0,ii=providers.length; i<ii; ++i) {
+ provider = providers[i];
+ for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
+ coverage = provider.coverageAreas[j];
+ // axis order provided is Y,X
+ bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true);
+ if (extent.intersectsBounds(bbox) &&
+ zoom <= coverage.zoomMax && zoom >= coverage.zoomMin) {
+ copyrights += provider.attribution + " ";
+ }
+ }
+ }
+ var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol);
+ this.attribution = OpenLayers.String.format(this.attributionTemplate, {
+ type: this.type.toLowerCase(),
+ logo: logo,
+ copyrights: copyrights
+ });
+ this.map && this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "attribution"
+ });
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function() {
+ OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments);
+ this.map.events.register("moveend", this, this.updateAttribution);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Bing(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.map &&
+ this.map.events.unregister("moveend", this, this.updateAttribution);
+ OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Bing"
+});
+
+/**
+ * Function: OpenLayers.Layer.Bing.processMetadata
+ * This function will be bound to an instance, linked to the global scope with
+ * an id, and called by the JSONP script returned by the API.
+ *
+ * Parameters:
+ * metadata - {Object} metadata as returned by the API
+ */
+OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ this.metadata = metadata;
+ this.initLayer();
+ var script = document.getElementById(this._callbackId);
+ script.parentNode.removeChild(script);
+ window[this._callbackId] = undefined; // cannot delete from window in IE
+ delete this._callbackId;
+};
+/* ======================================================================
+ OpenLayers/Geometry/MultiLineString.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LineString"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiLineString
+ * Constructor for a MultiLineString Geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LineString>)}
+ *
+ */
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
+ var sourceParts = [];
+ var targetParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ sourceLine = this.components[i];
+ sourceSplit = false;
+ for(var j=0; j < targetParts.length; ++j) {
+ splits = sourceLine.split(targetParts[j], options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ for(var k=0, klen=sourceLines.length; k<klen; ++k) {
+ if(k===0 && sourceParts.length) {
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLines[k]
+ );
+ } else {
+ sourceParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ sourceLines[k]
+ ])
+ );
+ }
+ }
+ sourceSplit = true;
+ splits = splits[1];
+ }
+ if(splits.length) {
+ // splice in new target parts
+ splits.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, splits);
+ break;
+ }
+ }
+ }
+ if(!sourceSplit) {
+ // source line was not hit
+ if(sourceParts.length) {
+ // add line to existing multi
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLine.clone()
+ );
+ } else {
+ // create a fresh multi
+ sourceParts = [
+ new OpenLayers.Geometry.MultiLineString(
+ sourceLine.clone()
+ )
+ ];
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
+ if(geometry instanceof OpenLayers.Geometry.LineString) {
+ targetParts = [];
+ sourceParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ targetSplit = false;
+ targetLine = this.components[i];
+ for(var j=0; j<sourceParts.length; ++j) {
+ splits = sourceParts[j].split(targetLine, options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ if(sourceLines.length) {
+ // splice in new source parts
+ sourceLines.unshift(j, 1);
+ Array.prototype.splice.apply(sourceParts, sourceLines);
+ j += sourceLines.length - 2;
+ }
+ splits = splits[1];
+ if(splits.length === 0) {
+ splits = [targetLine.clone()];
+ }
+ }
+ for(var k=0, klen=splits.length; k<klen; ++k) {
+ if(k===0 && targetParts.length) {
+ targetParts[targetParts.length-1].addComponent(
+ splits[k]
+ );
+ } else {
+ targetParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ splits[k]
+ ])
+ );
+ }
+ }
+ targetSplit = true;
+ }
+ }
+ if(!targetSplit) {
+ // target component was not hit
+ if(targetParts.length) {
+ // add it to any existing multi-line
+ targetParts[targetParts.length-1].addComponent(
+ targetLine.clone()
+ );
+ } else {
+ // or start with a fresh multi-line
+ targetParts = [
+ new OpenLayers.Geometry.MultiLineString([
+ targetLine.clone()
+ ])
+ ];
+ }
+
+ }
+ }
+ } else {
+ results = geometry.split(this);
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
+/* ======================================================================
+ OpenLayers/Format.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats. Subclasses
+ * of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+
+ /**
+ * Property: options
+ * {Object} A reference to options passed to the constructor.
+ */
+ options: null,
+
+ /**
+ * APIProperty: externalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The externalProjection is the projection used by
+ * the content which is passed into read or which comes out of write.
+ * In order to reproject, a projection transformation function for the
+ * specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ externalProjection: null,
+
+ /**
+ * APIProperty: internalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The internalProjection is the projection used by
+ * the geometries which are returned by read or which are passed into
+ * write. In order to reproject, a projection transformation function
+ * for the specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ internalProjection: null,
+
+ /**
+ * APIProperty: data
+ * {Object} When <keepData> is true, this is the parsed string sent to
+ * <read>.
+ */
+ data: null,
+
+ /**
+ * APIProperty: keepData
+ * {Object} Maintain a reference (<data>) to the most recently read data.
+ * Default is false.
+ */
+ keepData: false,
+
+ /**
+ * Constructor: OpenLayers.Format
+ * Instances of this class are not useful. See one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * format
+ *
+ * Valid options:
+ * keepData - {Boolean} If true, upon <read>, the data property will be
+ * set to the parsed object (e.g. the json or xml object).
+ *
+ * Returns:
+ * An instance of OpenLayers.Format
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * Method: read
+ * Read data from a string, and return an object whose type depends on the
+ * subclass.
+ *
+ * Parameters:
+ * data - {string} Data to read/parse.
+ *
+ * Returns:
+ * Depends on the subclass
+ */
+ read: function(data) {
+ throw new Error('Read not implemented.');
+ },
+
+ /**
+ * Method: write
+ * Accept an object, and return a string.
+ *
+ * Parameters:
+ * object - {Object} Object to be serialized
+ *
+ * Returns:
+ * {String} A string representation of the object.
+ */
+ write: function(object) {
+ throw new Error('Write not implemented.');
+ },
+
+ CLASS_NAME: "OpenLayers.Format"
+});
+/* ======================================================================
+ OpenLayers/Format/XML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML
+ * Read and write XML. For cross-browser XML generation, use methods on an
+ * instance of the XML format class instead of on <code>document<end>.
+ * The DOM creation and traversing methods exposed here all mimic the
+ * W3C XML DOM methods. Create a new parser with the
+ * <OpenLayers.Format.XML> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs. Properties
+ * of this object should not be set individually. Read-only. All
+ * XML subclasses should have their own namespaces object. Use
+ * <setNamespace> to add or set a namespace alias after construction.
+ */
+ namespaces: null,
+
+ /**
+ * Property: namespaceAlias
+ * {Object} Mapping of namespace URI to namespace alias. This object
+ * is read-only. Use <setNamespace> to add or set a namespace alias.
+ */
+ namespaceAlias: null,
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default namespace alias for creating element nodes.
+ */
+ defaultPrefix: null,
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {},
+
+ /**
+ * Property: writers
+ * As a compliment to the <readers> property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {},
+
+ /**
+ * Property: xmldom
+ * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
+ * object. It is not intended to be a browser sniffing property.
+ * Instead, the xmldom property is used instead of <code>document<end>
+ * where namespaced node creation methods are not supported. In all
+ * other browsers, this remains null.
+ */
+ xmldom: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XML
+ * Construct an XML parser. The parser is used to read and write XML.
+ * Reading XML from a string returns a DOM element. Writing XML from
+ * a DOM element returns a string.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on
+ * the object.
+ */
+ initialize: function(options) {
+ if(window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ // clone the namespace object and set all namespace aliases
+ this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
+ this.namespaceAlias = {};
+ for(var alias in this.namespaces) {
+ this.namespaceAlias[this.namespaces[alias]] = alias;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ this.xmldom = null;
+ OpenLayers.Format.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setNamespace
+ * Set a namespace alias and URI for the format.
+ *
+ * Parameters:
+ * alias - {String} The namespace alias (prefix).
+ * uri - {String} The namespace URI.
+ */
+ setNamespace: function(alias, uri) {
+ this.namespaces[alias] = uri;
+ this.namespaceAlias[uri] = alias;
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a XML string and return a DOM node.
+ *
+ * Parameters:
+ * text - {String} A XML string
+
+ * Returns:
+ * {DOMElement} A DOM node
+ */
+ read: function(text) {
+ var index = text.indexOf('<');
+ if(index > 0) {
+ text = text.substring(index);
+ }
+ var node = OpenLayers.Util.Try(
+ OpenLayers.Function.bind((
+ function() {
+ var xmldom;
+ /**
+ * Since we want to be able to call this method on the prototype
+ * itself, this.xmldom may not exist even if in IE.
+ */
+ if(window.ActiveXObject && !this.xmldom) {
+ xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ } else {
+ xmldom = this.xmldom;
+
+ }
+ xmldom.loadXML(text);
+ return xmldom;
+ }
+ ), this),
+ function() {
+ return new DOMParser().parseFromString(text, 'text/xml');
+ },
+ function() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "data:" + "text/xml" +
+ ";charset=utf-8," + encodeURIComponent(text), false);
+ if(req.overrideMimeType) {
+ req.overrideMimeType("text/xml");
+ }
+ req.send(null);
+ return req.responseXML;
+ }
+ );
+
+ if(this.keepData) {
+ this.data = node;
+ }
+
+ return node;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a DOM node into a XML string.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM node.
+ *
+ * Returns:
+ * {String} The XML string representation of the input node.
+ */
+ write: function(node) {
+ var data;
+ if(this.xmldom) {
+ data = node.xml;
+ } else {
+ var serializer = new XMLSerializer();
+ if (node.nodeType == 1) {
+ // Add nodes to a document before serializing. Everything else
+ // is serialized as is. This may need more work. See #1218 .
+ var doc = document.implementation.createDocument("", "", null);
+ if (doc.importNode) {
+ node = doc.importNode(node, true);
+ }
+ doc.appendChild(node);
+ data = serializer.serializeToString(doc);
+ } else {
+ data = serializer.serializeToString(node);
+ }
+ }
+ return data;
+ },
+
+ /**
+ * APIMethod: createElementNS
+ * Create a new element with namespace. This node can be appended to
+ * another node with the standard node.appendChild method. For
+ * cross-browser support, this method must be used instead of
+ * document.createElementNS.
+ *
+ * Parameters:
+ * uri - {String} Namespace URI for the element.
+ * name - {String} The qualified name of the element (prefix:localname).
+ *
+ * Returns:
+ * {Element} A DOM element with namespace.
+ */
+ createElementNS: function(uri, name) {
+ var element;
+ if(this.xmldom) {
+ if(typeof uri == "string") {
+ element = this.xmldom.createNode(1, name, uri);
+ } else {
+ element = this.xmldom.createNode(1, name, "");
+ }
+ } else {
+ element = document.createElementNS(uri, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIMethod: createDocumentFragment
+ * Create a document fragment node that can be appended to another node
+ * created by createElementNS. This will call
+ * document.createDocumentFragment outside of IE. In IE, the ActiveX
+ * object's createDocumentFragment method is used.
+ *
+ * Returns:
+ * {Element} A document fragment.
+ */
+ createDocumentFragment: function() {
+ var element;
+ if (this.xmldom) {
+ element = this.xmldom.createDocumentFragment();
+ } else {
+ element = document.createDocumentFragment();
+ }
+ return element;
+ },
+
+ /**
+ * APIMethod: createTextNode
+ * Create a text node. This node can be appended to another node with
+ * the standard node.appendChild method. For cross-browser support,
+ * this method must be used instead of document.createTextNode.
+ *
+ * Parameters:
+ * text - {String} The text of the node.
+ *
+ * Returns:
+ * {DOMElement} A DOM text node.
+ */
+ createTextNode: function(text) {
+ var node;
+ if (typeof text !== "string") {
+ text = String(text);
+ }
+ if(this.xmldom) {
+ node = this.xmldom.createTextNode(text);
+ } else {
+ node = document.createTextNode(text);
+ }
+ return node;
+ },
+
+ /**
+ * APIMethod: getElementsByTagNameNS
+ * Get a list of elements on a node given the namespace URI and local name.
+ * To return all nodes in a given namespace, use '*' for the name
+ * argument. To return all nodes of a given (local) name, regardless
+ * of namespace, use '*' for the uri argument.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for other nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the tag (without the prefix).
+ *
+ * Returns:
+ * {NodeList} A node list or array of elements.
+ */
+ getElementsByTagNameNS: function(node, uri, name) {
+ var elements = [];
+ if(node.getElementsByTagNameNS) {
+ elements = node.getElementsByTagNameNS(uri, name);
+ } else {
+ // brute force method
+ var allNodes = node.getElementsByTagName("*");
+ var potentialNode, fullName;
+ for(var i=0, len=allNodes.length; i<len; ++i) {
+ potentialNode = allNodes[i];
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if((name == "*") || (fullName == potentialNode.nodeName)) {
+ if((uri == "*") || (uri == potentialNode.namespaceURI)) {
+ elements.push(potentialNode);
+ }
+ }
+ }
+ }
+ return elements;
+ },
+
+ /**
+ * APIMethod: getAttributeNodeNS
+ * Get an attribute node given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for attribute nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {DOMElement} An attribute node or null if none found.
+ */
+ getAttributeNodeNS: function(node, uri, name) {
+ var attributeNode = null;
+ if(node.getAttributeNodeNS) {
+ attributeNode = node.getAttributeNodeNS(uri, name);
+ } else {
+ var attributes = node.attributes;
+ var potentialNode, fullName;
+ for(var i=0, len=attributes.length; i<len; ++i) {
+ potentialNode = attributes[i];
+ if(potentialNode.namespaceURI == uri) {
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if(fullName == potentialNode.nodeName) {
+ attributeNode = potentialNode;
+ break;
+ }
+ }
+ }
+ }
+ return attributeNode;
+ },
+
+ /**
+ * APIMethod: getAttributeNS
+ * Get an attribute value given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {String} An attribute value or and empty string if none found.
+ */
+ getAttributeNS: function(node, uri, name) {
+ var attributeValue = "";
+ if(node.getAttributeNS) {
+ attributeValue = node.getAttributeNS(uri, name) || "";
+ } else {
+ var attributeNode = this.getAttributeNodeNS(node, uri, name);
+ if(attributeNode) {
+ attributeValue = attributeNode.nodeValue;
+ }
+ }
+ return attributeValue;
+ },
+
+ /**
+ * APIMethod: getChildValue
+ * Get the textual value of the node if it exists, or return an
+ * optional default string. Returns an empty string if no first child
+ * exists and no default value is supplied.
+ *
+ * Parameters:
+ * node - {DOMElement} The element used to look for a first child value.
+ * def - {String} Optional string to return in the event that no
+ * first child value exists.
+ *
+ * Returns:
+ * {String} The value of the first child of the given node.
+ */
+ getChildValue: function(node, def) {
+ var value = def || "";
+ if(node) {
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ }
+ return value;
+ },
+
+ /**
+ * APIMethod: isSimpleContent
+ * Test if the given node has only simple content (i.e. no child element
+ * nodes).
+ *
+ * Parameters:
+ * node - {DOMElement} An element node.
+ *
+ * Returns:
+ * {Boolean} The node has no child element nodes (nodes of type 1).
+ */
+ isSimpleContent: function(node) {
+ var simple = true;
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ if(child.nodeType === 1) {
+ simple = false;
+ break;
+ }
+ }
+ return simple;
+ },
+
+ /**
+ * APIMethod: contentType
+ * Determine the content type for a given node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
+ * if the node has no, simple, complex, or mixed content.
+ */
+ contentType: function(node) {
+ var simple = false,
+ complex = false;
+
+ var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
+
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1: // element
+ complex = true;
+ break;
+ case 8: // comment
+ break;
+ default:
+ simple = true;
+ }
+ if(complex && simple) {
+ break;
+ }
+ }
+
+ if(complex && simple) {
+ type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
+ } else if(complex) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
+ } else if(simple) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
+ }
+ return type;
+ },
+
+ /**
+ * APIMethod: hasAttributeNS
+ * Determine whether a node has a particular attribute matching the given
+ * name and namespace.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {Boolean} The node has an attribute matching the name and namespace.
+ */
+ hasAttributeNS: function(node, uri, name) {
+ var found = false;
+ if(node.hasAttributeNS) {
+ found = node.hasAttributeNS(uri, name);
+ } else {
+ found = !!this.getAttributeNodeNS(node, uri, name);
+ }
+ return found;
+ },
+
+ /**
+ * APIMethod: setAttributeNS
+ * Adds a new attribute or changes the value of an attribute with the given
+ * namespace and name.
+ *
+ * Parameters:
+ * node - {Element} Element node on which to set the attribute.
+ * uri - {String} Namespace URI for the attribute.
+ * name - {String} Qualified name (prefix:localname) for the attribute.
+ * value - {String} Attribute value.
+ */
+ setAttributeNS: function(node, uri, name, value) {
+ if(node.setAttributeNS) {
+ node.setAttributeNS(uri, name, value);
+ } else {
+ if(this.xmldom) {
+ if(uri) {
+ var attribute = node.ownerDocument.createNode(
+ 2, name, uri
+ );
+ attribute.nodeValue = value;
+ node.setAttributeNode(attribute);
+ } else {
+ node.setAttribute(name, value);
+ }
+ } else {
+ throw "setAttributeNS not implemented";
+ }
+ }
+ },
+
+ /**
+ * Method: createElementNSPlus
+ * Shorthand for creating namespaced elements with optional attributes and
+ * child text nodes.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * options - {Object} Optional object for node configuration.
+ *
+ * Valid options:
+ * uri - {String} Optional namespace uri for the element - supply a prefix
+ * instead if the namespace uri is a property of the format's namespace
+ * object.
+ * attributes - {Object} Optional attributes to be set using the
+ * <setAttributes> method.
+ * value - {String} Optional text to be appended as a text node.
+ *
+ * Returns:
+ * {Element} An element node.
+ */
+ createElementNSPlus: function(name, options) {
+ options = options || {};
+ // order of prefix preference
+ // 1. in the uri option
+ // 2. in the prefix option
+ // 3. in the qualified name
+ // 4. from the defaultPrefix
+ var uri = options.uri || this.namespaces[options.prefix];
+ if(!uri) {
+ var loc = name.indexOf(":");
+ uri = this.namespaces[name.substring(0, loc)];
+ }
+ if(!uri) {
+ uri = this.namespaces[this.defaultPrefix];
+ }
+ var node = this.createElementNS(uri, name);
+ if(options.attributes) {
+ this.setAttributes(node, options.attributes);
+ }
+ var value = options.value;
+ if(value != null) {
+ node.appendChild(this.createTextNode(value));
+ }
+ return node;
+ },
+
+ /**
+ * Method: setAttributes
+ * Set multiple attributes given key value pairs from an object.
+ *
+ * Parameters:
+ * node - {Element} An element node.
+ * obj - {Object || Array} An object whose properties represent attribute
+ * names and values represent attribute values. If an attribute name
+ * is a qualified name ("prefix:local"), the prefix will be looked up
+ * in the parsers {namespaces} object. If the prefix is found,
+ * setAttributeNS will be used instead of setAttribute.
+ */
+ setAttributes: function(node, obj) {
+ var value, uri;
+ for(var name in obj) {
+ if(obj[name] != null && obj[name].toString) {
+ value = obj[name].toString();
+ // check for qualified attribute name ("prefix:local")
+ uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+ this.setAttributeNS(node, uri, name, value);
+ }
+ }
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj) {
+ if(!obj) {
+ obj = {};
+ }
+ var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
+ if(group) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var reader = group[local] || group["*"];
+ if(reader) {
+ reader.apply(this, [node, obj]);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: readChildNodes
+ * Shorthand for applying the named readers to all children of a node.
+ * For each child of type 1 (element), <readSelf> is called.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ *
+ * Returns:
+ * {Object} The input object, modified.
+ */
+ readChildNodes: function(node, obj) {
+ if(!obj) {
+ obj = {};
+ }
+ var children = node.childNodes;
+ var child;
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ this.readNode(child, obj);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: writeNode
+ * Shorthand for applying one of the named writers and appending the
+ * results to a node. If a qualified name is not provided for the
+ * second argument (and a local name is used instead), the namespace
+ * of the parent node will be assumed.
+ *
+ * Parameters:
+ * name - {String} The name of a node to generate. If a qualified name
+ * (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+ * in the <writers> group. If a local name is used (e.g. "Name") then
+ * the namespace of the parent is assumed. If a local name is used
+ * and no parent is supplied, then the default namespace is assumed.
+ * obj - {Object} Structure containing data for the writer.
+ * parent - {DOMElement} Result will be appended to this node. If no parent
+ * is supplied, the node will not be appended to anything.
+ *
+ * Returns:
+ * {DOMElement} The child node.
+ */
+ writeNode: function(name, obj, parent) {
+ var prefix, local;
+ var split = name.indexOf(":");
+ if(split > 0) {
+ prefix = name.substring(0, split);
+ local = name.substring(split + 1);
+ } else {
+ if(parent) {
+ prefix = this.namespaceAlias[parent.namespaceURI];
+ } else {
+ prefix = this.defaultPrefix;
+ }
+ local = name;
+ }
+ var child = this.writers[prefix][local].apply(this, [obj]);
+ if(parent) {
+ parent.appendChild(child);
+ }
+ return child;
+ },
+
+ /**
+ * APIMethod: getChildEl
+ * Get the first child element. Optionally only return the first child
+ * if it matches the given name and namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The parent node.
+ * name - {String} Optional node name (local) to search for.
+ * uri - {String} Optional namespace URI to search for.
+ *
+ * Returns:
+ * {DOMElement} The first child. Returns null if no element is found, if
+ * something significant besides an element is found, or if the element
+ * found does not match the optional name and uri.
+ */
+ getChildEl: function(node, name, uri) {
+ return node && this.getThisOrNextEl(node.firstChild, name, uri);
+ },
+
+ /**
+ * APIMethod: getNextEl
+ * Get the next sibling element. Optionally get the first sibling only
+ * if it matches the given local name and namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The node.
+ * name - {String} Optional local name of the sibling to search for.
+ * uri - {String} Optional namespace URI of the sibling to search for.
+ *
+ * Returns:
+ * {DOMElement} The next sibling element. Returns null if no element is
+ * found, something significant besides an element is found, or the
+ * found element does not match the optional name and uri.
+ */
+ getNextEl: function(node, name, uri) {
+ return node && this.getThisOrNextEl(node.nextSibling, name, uri);
+ },
+
+ /**
+ * Method: getThisOrNextEl
+ * Return this node or the next element node. Optionally get the first
+ * sibling with the given local name or namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The node.
+ * name - {String} Optional local name of the sibling to search for.
+ * uri - {String} Optional namespace URI of the sibling to search for.
+ *
+ * Returns:
+ * {DOMElement} The next sibling element. Returns null if no element is
+ * found, something significant besides an element is found, or the
+ * found element does not match the query.
+ */
+ getThisOrNextEl: function(node, name, uri) {
+ outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
+ switch(sibling.nodeType) {
+ case 1: // Element
+ if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
+ (!uri || uri === sibling.namespaceURI)) {
+ // matches
+ break outer;
+ }
+ sibling = null;
+ break outer;
+ case 3: // Text
+ if(/^\s*$/.test(sibling.nodeValue)) {
+ break;
+ }
+ case 4: // CDATA
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ sibling = null;
+ break outer;
+ } // ignore comments and processing instructions
+ }
+ return sibling || null;
+ },
+
+ /**
+ * APIMethod: lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+ lookupNamespaceURI: function(node, prefix) {
+ var uri = null;
+ if(node) {
+ if(node.lookupNamespaceURI) {
+ uri = node.lookupNamespaceURI(prefix);
+ } else {
+ outer: switch(node.nodeType) {
+ case 1: // ELEMENT_NODE
+ if(node.namespaceURI !== null && node.prefix === prefix) {
+ uri = node.namespaceURI;
+ break outer;
+ }
+ var len = node.attributes.length;
+ if(len) {
+ var attr;
+ for(var i=0; i<len; ++i) {
+ attr = node.attributes[i];
+ if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
+ uri = attr.value || null;
+ break outer;
+ } else if(attr.name === "xmlns" && prefix === null) {
+ uri = attr.value || null;
+ break outer;
+ }
+ }
+ }
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ case 2: // ATTRIBUTE_NODE
+ uri = this.lookupNamespaceURI(node.ownerElement, prefix);
+ break outer;
+ case 9: // DOCUMENT_NODE
+ uri = this.lookupNamespaceURI(node.documentElement, prefix);
+ break outer;
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ break outer;
+ default:
+ // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
+ // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ }
+ }
+ }
+ return uri;
+ },
+
+ /**
+ * Method: getXMLDoc
+ * Get an XML document for nodes that are not supported in HTML (e.g.
+ * createCDATASection). On IE, this will either return an existing or
+ * create a new <xmldom> on the instance. On other browsers, this will
+ * either return an existing or create a new shared document (see
+ * <OpenLayers.Format.XML.document>).
+ *
+ * Returns:
+ * {XMLDocument}
+ */
+ getXMLDoc: function() {
+ if (!OpenLayers.Format.XML.document && !this.xmldom) {
+ if (document.implementation && document.implementation.createDocument) {
+ OpenLayers.Format.XML.document =
+ document.implementation.createDocument("", "", null);
+ } else if (!this.xmldom && window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ }
+ return OpenLayers.Format.XML.document || this.xmldom;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML"
+
+});
+
+OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
+
+/**
+ * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
+ OpenLayers.Format.XML.prototype.lookupNamespaceURI,
+ OpenLayers.Format.XML.prototype
+);
+
+/**
+ * Property: OpenLayers.Format.XML.document
+ * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
+ * like document.createCDATASection.
+ */
+OpenLayers.Format.XML.document = null;
+/* ======================================================================
+ OpenLayers/Format/OGCExceptionReport.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OGCExceptionReport
+ * Class to read exception reports for various OGC services and versions.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ogc: "http://www.opengis.net/ogc"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "ogc",
+
+ /**
+ * Constructor: OpenLayers.Format.OGCExceptionReport
+ * Create a new parser for OGC exception reports.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read OGC exception report data from a string, and return an object with
+ * information about the exceptions.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the exceptions that occurred.
+ */
+ read: function(data) {
+ var result;
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var exceptionInfo = {exceptionReport: null};
+ if (root) {
+ this.readChildNodes(data, exceptionInfo);
+ if (exceptionInfo.exceptionReport === null) {
+ // fall-back to OWSCommon since this is a common output format for exceptions
+ // we cannot easily use the ows readers directly since they differ for 1.0 and 1.1
+ exceptionInfo = new OpenLayers.Format.OWSCommon().read(data);
+ }
+ }
+ return exceptionInfo;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": {
+ "ServiceExceptionReport": function(node, obj) {
+ obj.exceptionReport = {exceptions: []};
+ this.readChildNodes(node, obj.exceptionReport);
+ },
+ "ServiceException": function(node, exceptionReport) {
+ var exception = {
+ code: node.getAttribute("code"),
+ locator: node.getAttribute("locator"),
+ text: this.getChildValue(node)
+ };
+ exceptionReport.exceptions.push(exception);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OGCExceptionReport"
+
+});
+/* ======================================================================
+ OpenLayers/Format/XML/VersionedOGC.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML.VersionedOGC
+ * Base class for versioned formats, i.e. a format which supports multiple
+ * versions.
+ *
+ * To enable checking if parsing succeeded, you will need to define a property
+ * called errorProperty on the parser you want to check. The parser will then
+ * check the returned object to see if that property is present. If it is, it
+ * assumes the parsing was successful. If it is not present (or is null), it will
+ * pass the document through an OGCExceptionReport parser.
+ *
+ * If errorProperty is undefined for the parser, this error checking mechanism
+ * will be disabled.
+ *
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found.
+ */
+ defaultVersion: null,
+
+ /**
+ * APIProperty: version
+ * {String} Specify a version string if one is known.
+ */
+ version: null,
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ */
+ profile: null,
+
+ /**
+ * APIProperty: allowFallback
+ * {Boolean} If a profiled parser cannot be found for the returned version,
+ * use a non-profiled parser as the fallback. Application code using this
+ * should take into account that the return object structure might be
+ * missing the specifics of the profile. Defaults to false.
+ */
+ allowFallback: false,
+
+ /**
+ * Property: name
+ * {String} The name of this parser, this is the part of the CLASS_NAME
+ * except for "OpenLayers.Format."
+ */
+ name: null,
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is false.
+ */
+ stringifyOutput: false,
+
+ /**
+ * Property: parser
+ * {Object} Instance of the versioned parser. Cached for multiple read and
+ * write calls of the same version.
+ */
+ parser: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XML.VersionedOGC.
+ * Constructor.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on
+ * the object.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ var className = this.CLASS_NAME;
+ this.name = className.substring(className.lastIndexOf(".")+1);
+ },
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version;
+ // read
+ if (root) {
+ version = this.version;
+ if(!version) {
+ version = root.getAttribute("version");
+ if(!version) {
+ version = this.defaultVersion;
+ }
+ }
+ } else { // write
+ version = (options && options.version) ||
+ this.version || this.defaultVersion;
+ }
+ return version;
+ },
+
+ /**
+ * Method: getParser
+ * Get an instance of the cached parser if available, otherwise create one.
+ *
+ * Parameters:
+ * version - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Format>}
+ */
+ getParser: function(version) {
+ version = version || this.defaultVersion;
+ var profile = this.profile ? "_" + this.profile : "";
+ if(!this.parser || this.parser.VERSION != version) {
+ var format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_") + profile
+ ];
+ if(!format) {
+ if (profile !== "" && this.allowFallback) {
+ // fallback to the non-profiled version of the parser
+ profile = "";
+ format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_")
+ ];
+ }
+ if (!format) {
+ throw "Can't find a " + this.name + " parser for version " +
+ version + profile;
+ }
+ }
+ this.parser = new format(this.options);
+ }
+ return this.parser;
+ },
+
+ /**
+ * APIMethod: write
+ * Write a document.
+ *
+ * Parameters:
+ * obj - {Object} An object representing the document.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The document as a string
+ */
+ write: function(obj, options) {
+ var version = this.getVersion(null, options);
+ this.parser = this.getParser(version);
+ var root = this.parser.write(obj, options);
+ if (this.stringifyOutput === false) {
+ return root;
+ } else {
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ }
+ },
+
+ /**
+ * APIMethod: read
+ * Read a doc and return an object representing the document.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the document.
+ */
+ read: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var version = this.getVersion(root);
+ this.parser = this.getParser(version); // Select the parser
+ var obj = this.parser.read(data, options); // Parse the data
+
+ var errorProperty = this.parser.errorProperty || null;
+ if (errorProperty !== null && obj[errorProperty] === undefined) {
+ // an error must have happened, so parse it and report back
+ var format = new OpenLayers.Format.OGCExceptionReport();
+ obj.error = format.read(data);
+ }
+ obj.version = version;
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
+});
+/* ======================================================================
+ OpenLayers/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ * class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>}
+ */
+ lonlat: null,
+
+ /**
+ * Property: data
+ * {Object}
+ */
+ data: null,
+
+ /**
+ * Property: marker
+ * {<OpenLayers.Marker>}
+ */
+ marker: null,
+
+ /**
+ * APIProperty: popupClass
+ * {<OpenLayers.Class>} The class which will be used to instantiate
+ * a new Popup. Default is <OpenLayers.Popup.Anchored>.
+ */
+ popupClass: null,
+
+ /**
+ * Property: popup
+ * {<OpenLayers.Popup>}
+ */
+ popup: null,
+
+ /**
+ * Constructor: OpenLayers.Feature
+ * Constructor for features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * lonlat - {<OpenLayers.LonLat>}
+ * data - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Feature>}
+ */
+ initialize: function(layer, lonlat, data) {
+ this.layer = layer;
+ this.lonlat = lonlat;
+ this.data = (data != null) ? data : {};
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ //remove the popup from the map
+ if ((this.layer != null) && (this.layer.map != null)) {
+ if (this.popup != null) {
+ this.layer.map.removePopup(this.popup);
+ }
+ }
+ // remove the marker from the layer
+ if (this.layer != null && this.marker != null) {
+ this.layer.removeMarker(this.marker);
+ }
+
+ this.layer = null;
+ this.id = null;
+ this.lonlat = null;
+ this.data = null;
+ if (this.marker != null) {
+ this.destroyMarker(this.marker);
+ this.marker = null;
+ }
+ if (this.popup != null) {
+ this.destroyPopup(this.popup);
+ this.popup = null;
+ }
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is currently visible on screen
+ * (based on its 'lonlat' property)
+ */
+ onScreen:function() {
+
+ var onScreen = false;
+ if ((this.layer != null) && (this.layer.map != null)) {
+ var screenBounds = this.layer.map.getExtent();
+ onScreen = screenBounds.containsLonLat(this.lonlat);
+ }
+ return onScreen;
+ },
+
+
+ /**
+ * Method: createMarker
+ * Based on the data associated with the Feature, create and return a marker object.
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
+ * set in this.data. If no 'lonlat' is set, returns null. If no
+ * 'icon' is set, OpenLayers.Marker() will load the default image.
+ *
+ * Note - this.marker is set to return value
+ *
+ */
+ createMarker: function() {
+
+ if (this.lonlat != null) {
+ this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
+ }
+ return this.marker;
+ },
+
+ /**
+ * Method: destroyMarker
+ * Destroys marker.
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ this.marker.destroy();
+ },
+
+ /**
+ * Method: createPopup
+ * Creates a popup object created from the 'lonlat', 'popupSize',
+ * and 'popupContentHTML' properties set in this.data. It uses
+ * this.marker.icon as default anchor.
+ *
+ * If no 'lonlat' is set, returns null.
+ * If no this.marker has been created, no anchor is sent.
+ *
+ * Note - the returned popup object is 'owned' by the feature, so you
+ * cannot use the popup's destroy method to discard the popup.
+ * Instead, you must use the feature's destroyPopup
+ *
+ * Note - this.popup is set to return value
+ *
+ * Parameters:
+ * closeBox - {Boolean} create popup with closebox or not
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} Returns the created popup, which is also set
+ * as 'popup' property of this feature. Will be of whatever type
+ * specified by this feature's 'popupClass' property, but must be
+ * of type <OpenLayers.Popup>.
+ *
+ */
+ createPopup: function(closeBox) {
+
+ if (this.lonlat != null) {
+ if (!this.popup) {
+ var anchor = (this.marker) ? this.marker.icon : null;
+ var popupClass = this.popupClass ?
+ this.popupClass : OpenLayers.Popup.Anchored;
+ this.popup = new popupClass(this.id + "_popup",
+ this.lonlat,
+ this.data.popupSize,
+ this.data.popupContentHTML,
+ anchor,
+ closeBox);
+ }
+ if (this.data.overflow != null) {
+ this.popup.contentDiv.style.overflow = this.data.overflow;
+ }
+
+ this.popup.feature = this;
+ }
+ return this.popup;
+ },
+
+
+ /**
+ * Method: destroyPopup
+ * Destroys the popup created via createPopup.
+ *
+ * As with the marker, if user overrides the createPopup() function, s/he
+ * should also be able to override the destruction
+ */
+ destroyPopup: function() {
+ if (this.popup) {
+ this.popup.feature = null;
+ this.popup.destroy();
+ this.popup = null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature"
+});
+/* ======================================================================
+ OpenLayers/Feature/Vector.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+// TRASH THIS
+OpenLayers.State = {
+ /** states */
+ UNKNOWN: 'Unknown',
+ INSERT: 'Insert',
+ UPDATE: 'Update',
+ DELETE: 'Delete'
+};
+
+/**
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.Vector
+ * Vector features use the OpenLayers.Geometry classes as geometry description.
+ * They have an 'attributes' property, which is the data object, and a 'style'
+ * property, the default values of which are defined in the
+ * <OpenLayers.Feature.Vector.style> objects.
+ *
+ * Inherits from:
+ * - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+ /**
+ * Property: fid
+ * {String}
+ */
+ fid: null,
+
+ /**
+ * APIProperty: geometry
+ * {<OpenLayers.Geometry>}
+ */
+ geometry: null,
+
+ /**
+ * APIProperty: attributes
+ * {Object} This object holds arbitrary, serializable properties that
+ * describe the feature.
+ */
+ attributes: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
+ * property can be set by an <OpenLayers.Format> object when
+ * deserializing the feature, so in most cases it represents an
+ * information set by the server.
+ */
+ bounds: null,
+
+ /**
+ * Property: state
+ * {String}
+ */
+ state: null,
+
+ /**
+ * APIProperty: style
+ * {Object}
+ */
+ style: null,
+
+ /**
+ * APIProperty: url
+ * {String} If this property is set it will be taken into account by
+ * {<OpenLayers.HTTP>} when upadting or deleting the feature.
+ */
+ url: null,
+
+ /**
+ * Property: renderIntent
+ * {String} rendering intent currently being used
+ */
+ renderIntent: "default",
+
+ /**
+ * APIProperty: modified
+ * {Object} An object with the originals of the geometry and attributes of
+ * the feature, if they were changed. Currently this property is only read
+ * by <OpenLayers.Format.WFST.v1>, and written by
+ * <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
+ * Applications can set the originals of modified attributes in the
+ * attributes property. Note that applications have to check if this
+ * object and the attributes property is already created before using it.
+ * After a change made with ModifyFeature, this object could look like
+ *
+ * (code)
+ * {
+ * geometry: >Object
+ * }
+ * (end)
+ *
+ * When an application has made changes to feature attributes, it could
+ * have set the attributes to something like this:
+ *
+ * (code)
+ * {
+ * attributes: {
+ * myAttribute: "original"
+ * }
+ * }
+ * (end)
+ *
+ * Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
+ * *modified.geometry* and the attribute names in *modified.attributes*,
+ * but it is recommended to set the original values (and not just true) as
+ * attribute value, so applications could use this information to undo
+ * changes.
+ */
+ modified: null,
+
+ /**
+ * Constructor: OpenLayers.Feature.Vector
+ * Create a vector feature.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry that this feature
+ * represents.
+ * attributes - {Object} An optional object that will be mapped to the
+ * <attributes> property.
+ * style - {Object} An optional style object.
+ */
+ initialize: function(geometry, attributes, style) {
+ OpenLayers.Feature.prototype.initialize.apply(this,
+ [null, null, attributes]);
+ this.lonlat = null;
+ this.geometry = geometry ? geometry : null;
+ this.state = null;
+ this.attributes = {};
+ if (attributes) {
+ this.attributes = OpenLayers.Util.extend(this.attributes,
+ attributes);
+ }
+ this.style = style ? style : null;
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.layer) {
+ this.layer.removeFeatures(this);
+ this.layer = null;
+ }
+
+ this.geometry = null;
+ this.modified = null;
+ OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this vector feature. Does not set any non-standard
+ * properties.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
+ */
+ clone: function () {
+ return new OpenLayers.Feature.Vector(
+ this.geometry ? this.geometry.clone() : null,
+ this.attributes,
+ this.style);
+ },
+
+ /**
+ * Method: onScreen
+ * Determine whether the feature is within the map viewport. This method
+ * tests for an intersection between the geometry and the viewport
+ * bounds. If a more effecient but less precise geometry bounds
+ * intersection is desired, call the method with the boundsOnly
+ * parameter true.
+ *
+ * Parameters:
+ * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
+ * the viewport bounds. Default is false. If false, the feature's
+ * geometry must intersect the viewport for onScreen to return true.
+ *
+ * Returns:
+ * {Boolean} The feature is currently visible on screen (optionally
+ * based on its bounds if boundsOnly is true).
+ */
+ onScreen:function(boundsOnly) {
+ var onScreen = false;
+ if(this.layer && this.layer.map) {
+ var screenBounds = this.layer.map.getExtent();
+ if(boundsOnly) {
+ var featureBounds = this.geometry.getBounds();
+ onScreen = screenBounds.intersectsBounds(featureBounds);
+ } else {
+ var screenPoly = screenBounds.toGeometry();
+ onScreen = screenPoly.intersects(this.geometry);
+ }
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: getVisibility
+ * Determine whether the feature is displayed or not. It may not displayed
+ * because:
+ * - its style display property is set to 'none',
+ * - it doesn't belong to any layer,
+ * - the styleMap creates a symbolizer with display property set to 'none'
+ * for it,
+ * - the layer which it belongs to is not visible.
+ *
+ * Returns:
+ * {Boolean} The feature is currently displayed.
+ */
+ getVisibility: function() {
+ return !(this.style && this.style.display == 'none' ||
+ !this.layer ||
+ this.layer && this.layer.styleMap &&
+ this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
+ this.layer && !this.layer.getVisibility());
+ },
+
+ /**
+ * Method: createMarker
+ * HACK - we need to decide if all vector features should be able to
+ * create markers
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} For now just returns null
+ */
+ createMarker: function() {
+ return null;
+ },
+
+ /**
+ * Method: destroyMarker
+ * HACK - we need to decide if all vector features should be able to
+ * delete markers
+ *
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ // pass
+ },
+
+ /**
+ * Method: createPopup
+ * HACK - we need to decide if all vector features should be able to
+ * create popups
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} For now just returns null
+ */
+ createPopup: function() {
+ return null;
+ },
+
+ /**
+ * Method: atPoint
+ * Determins whether the feature intersects with the specified location.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ if(this.geometry) {
+ atPoint = this.geometry.atPoint(lonlat, toleranceLon,
+ toleranceLat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: destroyPopup
+ * HACK - we need to decide if all vector features should be able to
+ * delete popups
+ */
+ destroyPopup: function() {
+ // pass
+ },
+
+ /**
+ * Method: move
+ * Moves the feature and redraws it at its new location
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} the
+ * location to which to move the feature.
+ */
+ move: function(location) {
+
+ if(!this.layer || !this.geometry.move){
+ //do nothing if no layer or immoveable geometry
+ return undefined;
+ }
+
+ var pixel;
+ if (location.CLASS_NAME == "OpenLayers.LonLat") {
+ pixel = this.layer.getViewPortPxFromLonLat(location);
+ } else {
+ pixel = location;
+ }
+
+ var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
+ var res = this.layer.map.getResolution();
+ this.geometry.move(res * (pixel.x - lastPixel.x),
+ res * (lastPixel.y - pixel.y));
+ this.layer.drawFeature(this);
+ return lastPixel;
+ },
+
+ /**
+ * Method: toState
+ * Sets the new state
+ *
+ * Parameters:
+ * state - {String}
+ */
+ toState: function(state) {
+ if (state == OpenLayers.State.UPDATE) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.DELETE:
+ this.state = state;
+ break;
+ case OpenLayers.State.UPDATE:
+ case OpenLayers.State.INSERT:
+ break;
+ }
+ } else if (state == OpenLayers.State.INSERT) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ break;
+ default:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.DELETE) {
+ switch (this.state) {
+ case OpenLayers.State.INSERT:
+ // the feature should be destroyed
+ break;
+ case OpenLayers.State.DELETE:
+ break;
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.UPDATE:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.UNKNOWN) {
+ this.state = state;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature.Vector"
+});
+
+
+/**
+ * Constant: OpenLayers.Feature.Vector.style
+ * OpenLayers features can have a number of style attributes. The 'default'
+ * style will typically be used if no other style is specified. These
+ * styles correspond for the most part, to the styling properties defined
+ * by the SVG standard.
+ * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
+ * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
+ *
+ * Symbolizer properties:
+ * fill - {Boolean} Set to false if no fill is desired.
+ * fillColor - {String} Hex fill color. Default is "#ee9900".
+ * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4
+ * stroke - {Boolean} Set to false if no stroke is desired.
+ * strokeColor - {String} Hex stroke color. Default is "#ee9900".
+ * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1.
+ * strokeWidth - {Number} Pixel stroke width. Default is 1.
+ * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square]
+ * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
+ * graphic - {Boolean} Set to false if no graphic is desired.
+ * pointRadius - {Number} Pixel point radius. Default is 6.
+ * pointerEvents - {String} Default is "visiblePainted".
+ * cursor - {String} Default is "".
+ * externalGraphic - {String} Url to an external graphic that will be used for rendering points.
+ * graphicWidth - {Number} Pixel width for sizing an external graphic.
+ * graphicHeight - {Number} Pixel height for sizing an external graphic.
+ * graphicOpacity - {Number} Opacity (0-1) for an external graphic.
+ * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
+ * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
+ * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset).
+ * graphicZIndex - {Number} The integer z-index value to use in rendering.
+ * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default),
+ * "square", "star", "x", "cross", "triangle".
+ * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
+ * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
+ * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
+ * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
+ * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
+ * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
+ * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used.
+ * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used.
+ * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
+ * fillText or mozDrawText to be available.
+ * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
+ * composed of two characters. The first character is for the horizontal alignment, the second for the vertical
+ * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
+ * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
+ * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
+ * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
+ * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
+ * Default is false.
+ * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
+ * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers.
+ * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
+ * fontColor - {String} The font color for the label, to be provided like CSS.
+ * fontOpacity - {Number} Opacity (0-1) for the label
+ * fontFamily - {String} The font family for the label, to be provided like in CSS.
+ * fontSize - {String} The font size for the label, to be provided like in CSS.
+ * fontStyle - {String} The font style for the label, to be provided like in CSS.
+ * fontWeight - {String} The font weight for the label, to be provided like in CSS.
+ * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect.
+ */
+OpenLayers.Feature.Vector.style = {
+ 'default': {
+ fillColor: "#ee9900",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#ee9900",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+ },
+ 'select': {
+ fillColor: "blue",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "blue",
+ strokeOpacity: 1,
+ strokeWidth: 2,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "pointer",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'temporary': {
+ fillColor: "#66cccc",
+ fillOpacity: 0.2,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#66cccc",
+ strokeOpacity: 1,
+ strokeLinecap: "round",
+ strokeWidth: 2,
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'delete': {
+ display: "none"
+ }
+};
+/* ======================================================================
+ OpenLayers/Style.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Style
+ * This class represents a UserStyle obtained
+ * from a SLD, containing styling rules.
+ */
+OpenLayers.Style = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this style (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this style (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * APIProperty: layerName
+ * {<String>} name of the layer that this style belongs to, usually
+ * according to the NamedLayer attribute of an SLD document.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: isDefault
+ * {Boolean}
+ */
+ isDefault: false,
+
+ /**
+ * Property: rules
+ * {Array(<OpenLayers.Rule>)}
+ */
+ rules: null,
+
+ /**
+ * APIProperty: context
+ * {Object} An optional object with properties that symbolizers' property
+ * values should be evaluated against. If no context is specified,
+ * feature.attributes will be used
+ */
+ context: null,
+
+ /**
+ * Property: defaultStyle
+ * {Object} hash of style properties to use as default for merging
+ * rule-based style symbolizers onto. If no rules are defined,
+ * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
+ * true, the defaultStyle will only be taken into account if there are
+ * rules defined.
+ */
+ defaultStyle: null,
+
+ /**
+ * Property: defaultsPerSymbolizer
+ * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
+ * of every rule. Properties of the <defaultStyle> will also be used to set
+ * missing symbolizer properties if the symbolizer has stroke, fill or
+ * graphic set to true. Default is false.
+ */
+ defaultsPerSymbolizer: false,
+
+ /**
+ * Property: propertyStyles
+ * {Hash of Boolean} cache of style properties that need to be parsed for
+ * propertyNames. Property names are keys, values won't be used.
+ */
+ propertyStyles: null,
+
+
+ /**
+ * Constructor: OpenLayers.Style
+ * Creates a UserStyle.
+ *
+ * Parameters:
+ * style - {Object} Optional hash of style properties that will be
+ * used as default style for this style object. This style
+ * applies if no rules are specified. Symbolizers defined in
+ * rules will extend this default style.
+ * options - {Object} An optional object with properties to set on the
+ * style.
+ *
+ * Valid options:
+ * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
+ * style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>}
+ */
+ initialize: function(style, options) {
+
+ OpenLayers.Util.extend(this, options);
+ this.rules = [];
+ if(options && options.rules) {
+ this.addRules(options.rules);
+ }
+
+ // use the default style from OpenLayers.Feature.Vector if no style
+ // was given in the constructor
+ this.setDefaultStyle(style ||
+ OpenLayers.Feature.Vector.style["default"]);
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i=0, len=this.rules.length; i<len; i++) {
+ this.rules[i].destroy();
+ this.rules[i] = null;
+ }
+ this.rules = null;
+ this.defaultStyle = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * creates a style by applying all feature-dependent rules to the base
+ * style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to evaluate rules for
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature) {
+ var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
+ OpenLayers.Util.extend({}, this.defaultStyle), feature);
+
+ var rules = this.rules;
+
+ var rule, context;
+ var elseRules = [];
+ var appliedRules = false;
+ for(var i=0, len=rules.length; i<len; i++) {
+ rule = rules[i];
+ // does the rule apply?
+ var applies = rule.evaluate(feature);
+
+ if(applies) {
+ if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+ elseRules.push(rule);
+ } else {
+ appliedRules = true;
+ this.applySymbolizer(rule, style, feature);
+ }
+ }
+ }
+
+ // if no other rules apply, apply the rules with else filters
+ if(appliedRules == false && elseRules.length > 0) {
+ appliedRules = true;
+ for(var i=0, len=elseRules.length; i<len; i++) {
+ this.applySymbolizer(elseRules[i], style, feature);
+ }
+ }
+
+ // don't display if there were rules but none applied
+ if(rules.length > 0 && appliedRules == false) {
+ style.display = "none";
+ }
+
+ if (style.label != null && typeof style.label !== "string") {
+ style.label = String(style.label);
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: applySymbolizer
+ *
+ * Parameters:
+ * rule - {<OpenLayers.Rule>}
+ * style - {Object}
+ * feature - {<OpenLayer.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} A style with new symbolizer applied.
+ */
+ applySymbolizer: function(rule, style, feature) {
+ var symbolizerPrefix = feature.geometry ?
+ this.getSymbolizerPrefix(feature.geometry) :
+ OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
+
+ var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+
+ if(this.defaultsPerSymbolizer === true) {
+ var defaults = this.defaultStyle;
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: defaults.pointRadius
+ });
+ if(symbolizer.stroke === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ strokeWidth: defaults.strokeWidth,
+ strokeColor: defaults.strokeColor,
+ strokeOpacity: defaults.strokeOpacity,
+ strokeDashstyle: defaults.strokeDashstyle,
+ strokeLinecap: defaults.strokeLinecap
+ });
+ }
+ if(symbolizer.fill === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ fillColor: defaults.fillColor,
+ fillOpacity: defaults.fillOpacity
+ });
+ }
+ if(symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: this.defaultStyle.pointRadius,
+ externalGraphic: this.defaultStyle.externalGraphic,
+ graphicName: this.defaultStyle.graphicName,
+ graphicOpacity: this.defaultStyle.graphicOpacity,
+ graphicWidth: this.defaultStyle.graphicWidth,
+ graphicHeight: this.defaultStyle.graphicHeight,
+ graphicXOffset: this.defaultStyle.graphicXOffset,
+ graphicYOffset: this.defaultStyle.graphicYOffset
+ });
+ }
+ }
+
+ // merge the style with the current style
+ return this.createLiterals(
+ OpenLayers.Util.extend(style, symbolizer), feature);
+ },
+
+ /**
+ * Method: createLiterals
+ * creates literals for all style properties that have an entry in
+ * <this.propertyStyles>.
+ *
+ * Parameters:
+ * style - {Object} style to create literals for. Will be modified
+ * inline.
+ * feature - {Object}
+ *
+ * Returns:
+ * {Object} the modified style
+ */
+ createLiterals: function(style, feature) {
+ var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
+ OpenLayers.Util.extend(context, this.context);
+
+ for (var i in this.propertyStyles) {
+ style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
+ }
+ return style;
+ },
+
+ /**
+ * Method: findPropertyStyles
+ * Looks into all rules for this style and the defaultStyle to collect
+ * all the style hash property names containing ${...} strings that have
+ * to be replaced using the createLiteral method before returning them.
+ *
+ * Returns:
+ * {Object} hash of property names that need createLiteral parsing. The
+ * name of the property is the key, and the value is true;
+ */
+ findPropertyStyles: function() {
+ var propertyStyles = {};
+
+ // check the default style
+ var style = this.defaultStyle;
+ this.addPropertyStyles(propertyStyles, style);
+
+ // walk through all rules to check for properties in their symbolizer
+ var rules = this.rules;
+ var symbolizer, value;
+ for (var i=0, len=rules.length; i<len; i++) {
+ symbolizer = rules[i].symbolizer;
+ for (var key in symbolizer) {
+ value = symbolizer[key];
+ if (typeof value == "object") {
+ // symbolizer key is "Point", "Line" or "Polygon"
+ this.addPropertyStyles(propertyStyles, value);
+ } else {
+ // symbolizer is a hash of style properties
+ this.addPropertyStyles(propertyStyles, symbolizer);
+ break;
+ }
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * Method: addPropertyStyles
+ *
+ * Parameters:
+ * propertyStyles - {Object} hash to add new property styles to. Will be
+ * modified inline
+ * symbolizer - {Object} search this symbolizer for property styles
+ *
+ * Returns:
+ * {Object} propertyStyles hash
+ */
+ addPropertyStyles: function(propertyStyles, symbolizer) {
+ var property;
+ for (var key in symbolizer) {
+ property = symbolizer[key];
+ if (typeof property == "string" &&
+ property.match(/\$\{\w+\}/)) {
+ propertyStyles[key] = true;
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * APIMethod: addRules
+ * Adds rules to this style.
+ *
+ * Parameters:
+ * rules - {Array(<OpenLayers.Rule>)}
+ */
+ addRules: function(rules) {
+ Array.prototype.push.apply(this.rules, rules);
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * APIMethod: setDefaultStyle
+ * Sets the default style for this style object.
+ *
+ * Parameters:
+ * style - {Object} Hash of style properties
+ */
+ setDefaultStyle: function(style) {
+ this.defaultStyle = style;
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * Method: getSymbolizerPrefix
+ * Returns the correct symbolizer prefix according to the
+ * geometry type of the passed geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {String} key of the according symbolizer
+ */
+ getSymbolizerPrefix: function(geometry) {
+ var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+ for (var i=0, len=prefixes.length; i<len; i++) {
+ if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+ return prefixes[i];
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>} Clone of this style.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ // clone rules
+ if(this.rules) {
+ options.rules = [];
+ for(var i=0, len=this.rules.length; i<len; ++i) {
+ options.rules.push(this.rules[i].clone());
+ }
+ }
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ //clone default style
+ var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
+ return new OpenLayers.Style(defaultStyle, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ *
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ * will be replaced by the value of the "bar" attribute of the passed
+ * feature.
+ * context - {Object} context to take attribute values from
+ * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
+ * <OpenLayers.String.format> for evaluating functions in the
+ * context.
+ * property - {String} optional, name of the property for which the literal is
+ * being created for evaluating functions in the context.
+ *
+ * Returns:
+ * {String} the parsed value. In the example of the value parameter above, the
+ * result would be "foo valueOfBar", assuming that the passed feature has an
+ * attribute named "bar" with the value "valueOfBar".
+ */
+OpenLayers.Style.createLiteral = function(value, context, feature, property) {
+ if (typeof value == "string" && value.indexOf("${") != -1) {
+ value = OpenLayers.String.format(value, context, [feature, property]);
+ value = (isNaN(value) || !value) ? value : parseFloat(value);
+ }
+ return value;
+};
+
+/**
+ * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
+ * {Array} prefixes of the sld symbolizers. These are the
+ * same as the main geometry types
+ */
+OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text',
+ 'Raster'];
+/* ======================================================================
+ OpenLayers/Filter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Filter
+ * This class represents an OGC Filter.
+ */
+OpenLayers.Filter = OpenLayers.Class({
+
+ /**
+ * Constructor: OpenLayers.Filter
+ * This class represents a generic filter.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to anything added.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context. Instances or subclasses
+ * are supposed to override this method.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ return true;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter. Should be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} Clone of this filter.
+ */
+ clone: function() {
+ return null;
+ },
+
+ /**
+ * APIMethod: toString
+ *
+ * Returns:
+ * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
+ * representation of the filter returned. Otherwise "[Object object]"
+ * will be returned.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.CQL) {
+ string = OpenLayers.Format.CQL.prototype.write(this);
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter"
+});
+/* ======================================================================
+ OpenLayers/Filter/FeatureId.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.FeatureId
+ * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
+ * styling
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: fids
+ * {Array(String)} Feature Ids to evaluate this rule against.
+ * To be passed inside the params object.
+ */
+ fids: null,
+
+ /**
+ * Property: type
+ * {String} Type to identify this filter.
+ */
+ type: "FID",
+
+ /**
+ * Constructor: OpenLayers.Filter.FeatureId
+ * Creates an ogc:FeatureId rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>}
+ */
+ initialize: function(options) {
+ this.fids = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+ * For vector features, the check is run against the fid,
+ * for plain features against the id.
+ *
+ * Returns:
+ * {Boolean} true if the rule applies, false if it does not
+ */
+ evaluate: function(feature) {
+ for (var i=0, len=this.fids.length; i<len; i++) {
+ var fid = feature.fid || feature.id;
+ if (fid == this.fids[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>} Clone of this filter.
+ */
+ clone: function() {
+ var filter = new OpenLayers.Filter.FeatureId();
+ OpenLayers.Util.extend(filter, this);
+ filter.fids = this.fids.slice();
+ return filter;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.FeatureId"
+});
+/* ======================================================================
+ OpenLayers/Filter/Logical.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: filters
+ * {Array(<OpenLayers.Filter>)} Child filters for this filter.
+ */
+ filters: null,
+
+ /**
+ * APIProperty: type
+ * {String} type of logical operator. Available types are:
+ * - OpenLayers.Filter.Logical.AND = "&&";
+ * - OpenLayers.Filter.Logical.OR = "||";
+ * - OpenLayers.Filter.Logical.NOT = "!";
+ */
+ type: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Logical
+ * Creates a logical filter (And, Or, Not).
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>}
+ */
+ initialize: function(options) {
+ this.filters = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to child filters.
+ */
+ destroy: function() {
+ this.filters = null;
+ OpenLayers.Filter.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. A vector
+ * feature may also be provided to evaluate feature attributes in
+ * comparison filters or geometries in spatial filters.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ var i, len;
+ switch(this.type) {
+ case OpenLayers.Filter.Logical.AND:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == false) {
+ return false;
+ }
+ }
+ return true;
+
+ case OpenLayers.Filter.Logical.OR:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == true) {
+ return true;
+ }
+ }
+ return false;
+
+ case OpenLayers.Filter.Logical.NOT:
+ return (!this.filters[0].evaluate(context));
+ }
+ return undefined;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>} Clone of this filter.
+ */
+ clone: function() {
+ var filters = [];
+ for(var i=0, len=this.filters.length; i<len; ++i) {
+ filters.push(this.filters[i].clone());
+ }
+ return new OpenLayers.Filter.Logical({
+ type: this.type,
+ filters: filters
+ });
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR = "||";
+OpenLayers.Filter.Logical.NOT = "!";
+/* ======================================================================
+ OpenLayers/Filter/Comparison.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Comparison
+ * This class represents a comparison filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} type: type of the comparison. This is one of
+ * - OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+ * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+ * - OpenLayers.Filter.Comparison.LESS_THAN = "<";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+ * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+ * - OpenLayers.Filter.Comparison.BETWEEN = "..";
+ * - OpenLayers.Filter.Comparison.LIKE = "~";
+ * - OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String}
+ * name of the context property to compare
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {Number} or {String}
+ * comparison value for binary comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ value: null,
+
+ /**
+ * Property: matchCase
+ * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
+ * comparisons. The Filter Encoding 1.1 specification added a matchCase
+ * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
+ * elements. This property will be serialized with those elements only
+ * if using the v1.1.0 filter format. However, when evaluating filters
+ * here, the matchCase property will always be respected (for EQUAL_TO
+ * and NOT_EQUAL_TO). Default is true.
+ */
+ matchCase: true,
+
+ /**
+ * APIProperty: lowerBoundary
+ * {Number} or {String}
+ * lower boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ lowerBoundary: null,
+
+ /**
+ * APIProperty: upperBoundary
+ * {Number} or {String}
+ * upper boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ upperBoundary: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Comparison
+ * Creates a comparison rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>}
+ */
+ initialize: function(options) {
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ // since matchCase on PropertyIsLike is not schema compliant, we only
+ // want to use this if explicitly asked for
+ if (this.type === OpenLayers.Filter.Comparison.LIKE
+ && options.matchCase === undefined) {
+ this.matchCase = null;
+ }
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ if (context instanceof OpenLayers.Feature.Vector) {
+ context = context.attributes;
+ }
+ var result = false;
+ var got = context[this.property];
+ var exp;
+ switch(this.type) {
+ case OpenLayers.Filter.Comparison.EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() == exp.toUpperCase());
+ } else {
+ result = (got == exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() != exp.toUpperCase());
+ } else {
+ result = (got != exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN:
+ result = got < this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN:
+ result = got > this.value;
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+ result = got <= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+ result = got >= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.BETWEEN:
+ result = (got >= this.lowerBoundary) &&
+ (got <= this.upperBoundary);
+ break;
+ case OpenLayers.Filter.Comparison.LIKE:
+ var regexp = new RegExp(this.value, "gi");
+ result = regexp.test(got);
+ break;
+ case OpenLayers.Filter.Comparison.IS_NULL:
+ result = (got === null);
+ break;
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: value2regex
+ * Converts the value of this rule into a regular expression string,
+ * according to the wildcard characters specified. This method has to
+ * be called after instantiation of this class, if the value is not a
+ * regular expression already.
+ *
+ * Parameters:
+ * wildCard - {Char} wildcard character in the above value, default
+ * is "*"
+ * singleChar - {Char} single-character wildcard in the above value
+ * default is "."
+ * escapeChar - {Char} escape character in the above value, default is
+ * "!"
+ *
+ * Returns:
+ * {String} regular expression string
+ */
+ value2regex: function(wildCard, singleChar, escapeChar) {
+ if (wildCard == ".") {
+ throw new Error("'.' is an unsupported wildCard character for " +
+ "OpenLayers.Filter.Comparison");
+ }
+
+
+ // set UMN MapServer defaults for unspecified parameters
+ wildCard = wildCard ? wildCard : "*";
+ singleChar = singleChar ? singleChar : ".";
+ escapeChar = escapeChar ? escapeChar : "!";
+
+ this.value = this.value.replace(
+ new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
+ this.value = this.value.replace(
+ new RegExp("\\"+singleChar, "g"), ".");
+ this.value = this.value.replace(
+ new RegExp("\\"+wildCard, "g"), ".*");
+ this.value = this.value.replace(
+ new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
+ this.value = this.value.replace(
+ new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+
+ return this.value;
+ },
+
+ /**
+ * Method: regex2value
+ * Convert the value of this rule from a regular expression string into an
+ * ogc literal string using a wildCard of *, a singleChar of ., and an
+ * escape of !. Leaves the <value> property unmodified.
+ *
+ * Returns:
+ * {String} A string value.
+ */
+ regex2value: function() {
+
+ var value = this.value;
+
+ // replace ! with !!
+ value = value.replace(/!/g, "!!");
+
+ // replace \. with !. (watching out for \\.)
+ value = value.replace(/(\\)?\\\./g, function($0, $1) {
+ return $1 ? $0 : "!.";
+ });
+
+ // replace \* with #* (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "!*";
+ });
+
+ // replace \\ with \
+ value = value.replace(/\\\\/g, "\\");
+
+ // convert .* to * (the sequence #.* is not allowed)
+ value = value.replace(/\.\*/g, "*");
+
+ return value;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>} Clone of this filter.
+ */
+ clone: function() {
+ return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Comparison"
+});
+
+
+OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+OpenLayers.Filter.Comparison.LESS_THAN = "<";
+OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+OpenLayers.Filter.Comparison.BETWEEN = "..";
+OpenLayers.Filter.Comparison.LIKE = "~";
+OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+/* ======================================================================
+ OpenLayers/Format/Filter.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter
+ * Read/Write ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIMethod: write
+ * Write an ogc:Filter given a filter object.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} An filter.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {Elment} An ogc:Filter element node.
+ */
+
+ /**
+ * APIMethod: read
+ * Read and Filter doc and return an object representing the Filter.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.Filter"
+});
+/* ======================================================================
+ OpenLayers/Format/WFST.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Function: OpenLayers.Format.WFST
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A WFST format of the given version.
+ */
+OpenLayers.Format.WFST = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.WFST.DEFAULTS
+ );
+ var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFST version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Format.WFST.DEFAULTS
+ * {Object} Default properties for the WFST format.
+ */
+OpenLayers.Format.WFST.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Filter/Spatial.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Spatial
+ * This class represents a spatial filter.
+ * Currently implemented: BBOX, DWithin and Intersects
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} Type of spatial filter.
+ *
+ * The type should be one of:
+ * - OpenLayers.Filter.Spatial.BBOX
+ * - OpenLayers.Filter.Spatial.INTERSECTS
+ * - OpenLayers.Filter.Spatial.DWITHIN
+ * - OpenLayers.Filter.Spatial.WITHIN
+ * - OpenLayers.Filter.Spatial.CONTAINS
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String} Name of the context property to compare.
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
+ * to be used by the filter. Use bounds for BBOX filters and geometry
+ * for INTERSECTS or DWITHIN filters.
+ */
+ value: null,
+
+ /**
+ * APIProperty: distance
+ * {Number} The distance to use in a DWithin spatial filter.
+ */
+ distance: null,
+
+ /**
+ * APIProperty: distanceUnits
+ * {String} The units to use for the distance, e.g. 'm'.
+ */
+ distanceUnits: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Spatial
+ * Creates a spatial filter.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>}
+ */
+
+ /**
+ * Method: evaluate
+ * Evaluates this filter for a specific feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
+ *
+ * Returns:
+ * {Boolean} The feature meets filter criteria.
+ */
+ evaluate: function(feature) {
+ var intersect = false;
+ switch(this.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ case OpenLayers.Filter.Spatial.INTERSECTS:
+ if(feature.geometry) {
+ var geom = this.value;
+ if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
+ geom = this.value.toGeometry();
+ }
+ if(feature.geometry.intersects(geom)) {
+ intersect = true;
+ }
+ }
+ break;
+ default:
+ throw new Error('evaluate is not implemented for this filter type.');
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} Clone of this filter.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.applyDefaults({
+ value: this.value && this.value.clone && this.value.clone()
+ }, this);
+ return new OpenLayers.Filter.Spatial(options);
+ },
+ CLASS_NAME: "OpenLayers.Filter.Spatial"
+});
+
+OpenLayers.Filter.Spatial.BBOX = "BBOX";
+OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
+OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
+OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
+OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
+/* ======================================================================
+ OpenLayers/Format/WFST/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/WFST.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1
+ * Superclass for WFST parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ wfs: "http://www.opengis.net/wfs",
+ gml: "http://www.opengis.net/gml",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wfs",
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocations: null,
+
+ /**
+ * APIProperty: srsName
+ * {String} URI for spatial reference system.
+ */
+ srsName: null,
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: stateName
+ * {Object} Maps feature states to node names.
+ */
+ stateName: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
+ * constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // set state name mapping
+ this.stateName = {};
+ this.stateName[OpenLayers.State.INSERT] = "wfs:Insert";
+ this.stateName[OpenLayers.State.UPDATE] = "wfs:Update";
+ this.stateName[OpenLayers.State.DELETE] = "wfs:Delete";
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: getSrsName
+ */
+ getSrsName: function(feature, options) {
+ var srsName = options && options.srsName;
+ if(!srsName) {
+ if(feature && feature.layer) {
+ srsName = feature.layer.projection.getCode();
+ } else {
+ srsName = this.srsName;
+ }
+ }
+ return srsName;
+ },
+
+ /**
+ * APIMethod: read
+ * Parse the response from a transaction. Because WFS is split into
+ * Transaction requests (create, update, and delete) and GetFeature
+ * requests (read), this method handles parsing of both types of
+ * responses.
+ *
+ * Parameters:
+ * data - {String | Document} The WFST document to read
+ * options - {Object} Options for the reader
+ *
+ * Valid options properties:
+ * output - {String} either "features" or "object". The default is
+ * "features", which means that the method will return an array of
+ * features. If set to "object", an object with a "features" property
+ * and other properties read by the parser will be returned.
+ *
+ * Returns:
+ * {Array | Object} Output depending on the output option.
+ */
+ read: function(data, options) {
+ options = options || {};
+ OpenLayers.Util.applyDefaults(options, {
+ output: "features"
+ });
+
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ if(data) {
+ this.readNode(data, obj, true);
+ }
+ if(obj.features && options.output === "features") {
+ obj = obj.features;
+ }
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ obj.features = [];
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ * Given an array of features, write a WFS transaction. This assumes
+ * the features have a state property that determines the operation
+ * type - insert, update, or delete.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} A list of features. See
+ * below for a more detailed description of the influence of the
+ * feature's *modified* property.
+ * options - {Object}
+ *
+ * feature.modified rules:
+ * If a feature has a modified property set, the following checks will be
+ * made before a feature's geometry or attribute is included in an Update
+ * transaction:
+ * - *modified* is not set at all: The geometry and all attributes will be
+ * included.
+ * - *modified.geometry* is set (null or a geometry): The geometry will be
+ * included. If *modified.attributes* is not set, all attributes will
+ * be included.
+ * - *modified.attributes* is set: Only the attributes set (i.e. to null or
+ * a value) in *modified.attributes* will be included.
+ * If *modified.geometry* is not set, the geometry will not be included.
+ *
+ * Valid options include:
+ * - *multi* {Boolean} If set to true, geometries will be casted to
+ * Multi geometries before writing.
+ *
+ * Returns:
+ * {String} A serialized WFS transaction.
+ */
+ write: function(features, options) {
+ var node = this.writeNode("wfs:Transaction", {
+ features:features,
+ options: options
+ });
+ var value = this.schemaLocationAttr();
+ if(value) {
+ this.setAttributeNS(
+ node, this.namespaces["xsi"], "xsi:schemaLocation", value
+ );
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": {
+ "GetFeature": function(options) {
+ var node = this.createElementNSPlus("wfs:GetFeature", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options && options.handle,
+ outputFormat: options && options.outputFormat,
+ maxFeatures: options && options.maxFeatures,
+ "xsi:schemaLocation": this.schemaLocationAttr(options)
+ }
+ });
+ if (typeof this.featureType == "string") {
+ this.writeNode("Query", options, node);
+ } else {
+ for (var i=0,len = this.featureType.length; i<len; i++) {
+ options.featureType = this.featureType[i];
+ this.writeNode("Query", options, node);
+ }
+ }
+ return node;
+ },
+ "Transaction": function(obj) {
+ obj = obj || {};
+ var options = obj.options || {};
+ var node = this.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options.handle
+ }
+ });
+ var i, len;
+ var features = obj.features;
+ if(features) {
+ // temporarily re-assigning geometry types
+ if (options.multi === true) {
+ OpenLayers.Util.extend(this.geometryTypes, {
+ "OpenLayers.Geometry.Point": "MultiPoint",
+ "OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
+ });
+ }
+ var name, feature;
+ for(i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ name = this.stateName[feature.state];
+ if(name) {
+ this.writeNode(name, {
+ feature: feature,
+ options: options
+ }, node);
+ }
+ }
+ // switch back to original geometry types assignment
+ if (options.multi === true) {
+ this.setGeometryTypes();
+ }
+ }
+ if (options.nativeElements) {
+ for (i=0, len=options.nativeElements.length; i<len; ++i) {
+ this.writeNode("wfs:Native",
+ options.nativeElements[i], node);
+ }
+ }
+ return node;
+ },
+ "Native": function(nativeElement) {
+ var node = this.createElementNSPlus("wfs:Native", {
+ attributes: {
+ vendorId: nativeElement.vendorId,
+ safeToIgnore: nativeElement.safeToIgnore
+ },
+ value: nativeElement.value
+ });
+ return node;
+ },
+ "Insert": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Insert", {
+ attributes: {
+ handle: options && options.handle
+ }
+ });
+ this.srsName = this.getSrsName(feature);
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "Update": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Update", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+
+ // add in geometry
+ var modified = feature.modified;
+ if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
+ this.srsName = this.getSrsName(feature);
+ this.writeNode(
+ "Property", {name: this.geometryName, value: feature.geometry}, node
+ );
+ }
+
+ // add in attributes
+ for(var key in feature.attributes) {
+ if(feature.attributes[key] !== undefined &&
+ (!modified || !modified.attributes ||
+ (modified.attributes && modified.attributes[key] !== undefined))) {
+ this.writeNode(
+ "Property", {name: key, value: feature.attributes[key]}, node
+ );
+ }
+ }
+
+ // add feature id filter
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+
+ return node;
+ },
+ "Property": function(obj) {
+ var node = this.createElementNSPlus("wfs:Property");
+ this.writeNode("Name", obj.name, node);
+ if(obj.value !== null) {
+ this.writeNode("Value", obj.value, node);
+ }
+ return node;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("wfs:Name", {value: name});
+ },
+ "Value": function(obj) {
+ var node;
+ if(obj instanceof OpenLayers.Geometry) {
+ node = this.createElementNSPlus("wfs:Value");
+ var geom = this.writeNode("feature:_geometry", obj).firstChild;
+ node.appendChild(geom);
+ } else {
+ node = this.createElementNSPlus("wfs:Value", {value: obj});
+ }
+ return node;
+ },
+ "Delete": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: schemaLocationAttr
+ * Generate the xsi:schemaLocation attribute value.
+ *
+ * Returns:
+ * {String} The xsi:schemaLocation attribute or undefined if none.
+ */
+ schemaLocationAttr: function(options) {
+ options = OpenLayers.Util.extend({
+ featurePrefix: this.featurePrefix,
+ schema: this.schema
+ }, options);
+ var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
+ if(options.schema) {
+ schemaLocations[options.featurePrefix] = options.schema;
+ }
+ var parts = [];
+ var uri;
+ for(var key in schemaLocations) {
+ uri = this.namespaces[key];
+ if(uri) {
+ parts.push(uri + " " + schemaLocations[key]);
+ }
+ }
+ var value = parts.join(" ") || undefined;
+ return value;
+ },
+
+ /**
+ * Method: setFilterProperty
+ * Set the property of each spatial filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ setFilterProperty: function(filter) {
+ if(filter.filters) {
+ for(var i=0, len=filter.filters.length; i<len; ++i) {
+ OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
+ }
+ } else {
+ if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
+ // got a spatial filter without property, so set it
+ filter.property = this.geometryName;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Geometry/Polygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon
+ * Polygon is a collection of Geometry.LinearRings.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Polygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LinearRing"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Polygon
+ * Constructor for a Polygon geometry.
+ * The first ring (this.component[0])is the outer bounds of the polygon and
+ * all subsequent rings (this.component[1-n]) are internal holes.
+ *
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LinearRing>)}
+ */
+
+ /**
+ * APIMethod: getArea
+ * Calculated by subtracting the areas of the internal holes from the
+ * area of the outer hole.
+ *
+ * Returns:
+ * {float} The area of the geometry
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getArea());
+ for (var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getArea());
+ }
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the polygon in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ if(this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getGeodesicArea(projection));
+ for(var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getGeodesicArea(projection));
+ }
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a polygon. Points on a polygon edge are
+ * considered inside.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the polygon. Returns 1 if the
+ * point is on an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var numRings = this.components.length;
+ var contained = false;
+ if(numRings > 0) {
+ // check exterior ring - 1 means on edge, boolean otherwise
+ contained = this.components[0].containsPoint(point);
+ if(contained !== 1) {
+ if(contained && numRings > 1) {
+ // check interior rings
+ var hole;
+ for(var i=1; i<numRings; ++i) {
+ hole = this.components[i].containsPoint(point);
+ if(hole) {
+ if(hole === 1) {
+ // on edge
+ contained = 1;
+ } else {
+ // in hole
+ contained = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var i, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+ geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ // check if rings/linestrings intersect
+ for(i=0, len=this.components.length; i<len; ++i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ if(!intersect) {
+ // check if this poly contains points of the ring/linestring
+ for(i=0, len=geometry.components.length; i<len; ++i) {
+ intersect = this.containsPoint(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ } else {
+ for(i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = this.intersects(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ // check case where this poly is wholly contained by another
+ if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ // exterior ring points will be contained in the other geometry
+ var ring = this.components[0];
+ for(i=0, len=ring.components.length; i<len; ++i) {
+ intersect = geometry.containsPoint(ring.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var result;
+ // this is the case where we might not be looking for distance to edge
+ if(!edge && this.intersects(geometry)) {
+ result = 0;
+ } else {
+ result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
+ this, [geometry, options]
+ );
+ }
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Polygon"
+});
+
+/**
+ * APIMethod: createRegularPolygon
+ * Create a regular polygon around a radius. Useful for creating circles
+ * and the like.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.Geometry.Point>} center of polygon.
+ * radius - {Float} distance to vertex, in map units.
+ * sides - {Integer} Number of sides. 20 approximates a circle.
+ * rotation - {Float} original angle of rotation, in degrees.
+ */
+OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {
+ var angle = Math.PI * ((1/sides) - (1/2));
+ if(rotation) {
+ angle += (rotation / 180) * Math.PI;
+ }
+ var rotatedAngle, x, y;
+ var points = [];
+ for(var i=0; i<sides; ++i) {
+ rotatedAngle = angle + (i * 2 * Math.PI / sides);
+ x = origin.x + (radius * Math.cos(rotatedAngle));
+ y = origin.y + (radius * Math.sin(rotatedAngle));
+ points.push(new OpenLayers.Geometry.Point(x, y));
+ }
+ var ring = new OpenLayers.Geometry.LinearRing(points);
+ return new OpenLayers.Geometry.Polygon([ring]);
+};
+/* ======================================================================
+ OpenLayers/Geometry/MultiPolygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components. Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ */
+OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Polygon"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPolygon
+ * Create a new MultiPolygon geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
+ * used to generate the MultiPolygon
+ *
+ */
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
+});
+/* ======================================================================
+ OpenLayers/Format/GML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML
+ * Read/Write GML. Create a new instance with the <OpenLayers.Format.GML>
+ * constructor. Supports the GML simple features profile.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: featureNS
+ * {String} Namespace used for feature attributes. Default is
+ * "http://mapserver.gis.umn.edu/mapserver".
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * APIProperty: featurePrefix
+ * {String} Namespace alias (or prefix) for feature nodes. Default is
+ * "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * APIProperty: featureName
+ * {String} Element name for features. Default is "featureMember".
+ */
+ featureName: "featureMember",
+
+ /**
+ * APIProperty: layerName
+ * {String} Name of data layer. Default is "features".
+ */
+ layerName: "features",
+
+ /**
+ * APIProperty: geometryName
+ * {String} Name of geometry element. Defaults to "geometry".
+ */
+ geometryName: "geometry",
+
+ /**
+ * APIProperty: collectionName
+ * {String} Name of featureCollection element.
+ */
+ collectionName: "FeatureCollection",
+
+ /**
+ * APIProperty: gmlns
+ * {String} GML Namespace.
+ */
+ gmlns: "http://www.opengis.net/gml",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Constructor: OpenLayers.Format.GML
+ * Create a new parser for GML.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // compile regular expressions once instead of every time they are used
+ this.regExes = {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ };
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var featureNodes = this.getElementsByTagNameNS(data.documentElement,
+ this.gmlns,
+ this.featureName);
+ var features = [];
+ for(var i=0; i<featureNodes.length; i++) {
+ var feature = this.parseFeature(featureNodes[i]);
+ if(feature) {
+ features.push(feature);
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the GML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML feature node.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiPolygon", "Polygon",
+ "MultiLineString", "LineString",
+ "MultiPoint", "Point", "Envelope"];
+ // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
+ // this code creates a geometry derived from the Envelope. This is not correct.
+ var type, nodeList, geometry, parser;
+ for(var i=0; i<order.length; ++i) {
+ type = order[i];
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ geometry = parser.apply(this, [nodeList[0]]);
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ } else {
+ throw new TypeError("Unsupported geometry type: " + type);
+ }
+ // stop looking for different geometry types
+ break;
+ }
+ }
+
+ var bounds;
+ var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box");
+ for(i=0; i<boxNodes.length; ++i) {
+ var boxNode = boxNodes[i];
+ var box = this.parseGeometry["box"].apply(this, [boxNode]);
+ var parentNode = boxNode.parentNode;
+ var parentName = parentNode.localName ||
+ parentNode.nodeName.split(":").pop();
+ if(parentName === "boundedBy") {
+ bounds = box;
+ } else {
+ geometry = box.toGeometry();
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ feature.bounds = bounds;
+
+ feature.gml = {
+ featureType: node.firstChild.nodeName.split(":")[1],
+ featureNS: node.firstChild.namespaceURI,
+ featureNSPrefix: node.firstChild.prefix
+ };
+
+ // assign fid - this can come from a "fid" or "id" attribute
+ var childNode = node.firstChild;
+ var fid;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ fid = childNode.getAttribute("fid") ||
+ childNode.getAttribute("id");
+ if(fid) {
+ break;
+ }
+ }
+ childNode = childNode.nextSibling;
+ }
+ feature.fid = fid;
+ return feature;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a GML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ /**
+ * Three coordinate variations to consider:
+ * 1) <gml:pos>x y z</gml:pos>
+ * 2) <gml:coordinates>x, y, z</gml:coordinates>
+ * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
+ */
+ var nodeList, coordString;
+ var coords = [];
+
+ // look for <gml:pos>
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
+ if(nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ // look for <gml:coordinates>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ if(nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace,
+ "");
+ coords = coordString.split(",");
+ }
+ }
+
+ // look for <gml:coord>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coord");
+ if(nodeList.length > 0) {
+ var xList = this.getElementsByTagNameNS(nodeList[0],
+ this.gmlns, "X");
+ var yList = this.getElementsByTagNameNS(nodeList[0],
+ this.gmlns, "Y");
+ if(xList.length > 0 && yList.length > 0) {
+ coords = [xList[0].firstChild.nodeValue,
+ yList[0].firstChild.nodeValue];
+ }
+ }
+ }
+
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+
+ if (this.xy) {
+ return new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ }
+ else{
+ return new OpenLayers.Geometry.Point(coords[1], coords[0],
+ coords[2]);
+ }
+ },
+
+ /**
+ * Method: parseGeometry.multipoint
+ * Given a GML node representing a multipoint geometry, create an
+ * OpenLayers multipoint geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+ */
+ multipoint: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "Point");
+ var components = [];
+ if(nodeList.length > 0) {
+ var point;
+ for(var i=0; i<nodeList.length; ++i) {
+ point = this.parseGeometry.point.apply(this, [nodeList[i]]);
+ if(point) {
+ components.push(point);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPoint(components);
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a GML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ /**
+ * Two coordinate variations to consider:
+ * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
+ * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
+ */
+ var nodeList, coordString;
+ var coords = [];
+ var points = [];
+
+ // look for <gml:posList>
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
+ if(nodeList.length > 0) {
+ coordString = this.getChildValue(nodeList[0]);
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ var dim = parseInt(nodeList[0].getAttribute("dimension"));
+ var j, x, y, z;
+ for(var i=0; i<coords.length/dim; ++i) {
+ j = i * dim;
+ x = coords[j];
+ y = coords[j+1];
+ z = (dim == 2) ? null : coords[j+2];
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(x, y, z));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(y, x, z));
+ }
+ }
+ }
+
+ // look for <gml:coordinates>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ if(nodeList.length > 0) {
+ coordString = this.getChildValue(nodeList[0]);
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ for(var i=0; i<pointList.length; ++i) {
+ coords = pointList[i].split(",");
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(coords[1],
+ coords[0],
+ coords[2]));
+ }
+ }
+ }
+ }
+
+ var line = null;
+ if(points.length != 0) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ }
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.multilinestring
+ * Given a GML node representing a multilinestring geometry, create an
+ * OpenLayers multilinestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
+ */
+ multilinestring: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "LineString");
+ var components = [];
+ if(nodeList.length > 0) {
+ var line;
+ for(var i=0; i<nodeList.length; ++i) {
+ line = this.parseGeometry.linestring.apply(this,
+ [nodeList[i]]);
+ if(line) {
+ components.push(line);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiLineString(components);
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a GML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "LinearRing");
+ var components = [];
+ if(nodeList.length > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0; i<nodeList.length; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components.push(ring);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multipolygon
+ * Given a GML node representing a multipolygon geometry, create an
+ * OpenLayers multipolygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
+ */
+ multipolygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "Polygon");
+ var components = [];
+ if(nodeList.length > 0) {
+ var polygon;
+ for(var i=0; i<nodeList.length; ++i) {
+ polygon = this.parseGeometry.polygon.apply(this,
+ [nodeList[i]]);
+ if(polygon) {
+ components.push(polygon);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPolygon(components);
+ },
+
+ envelope: function(node) {
+ var components = [];
+ var coordString;
+ var envelope;
+
+ var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
+ if (lpoint.length > 0) {
+ var coords = [];
+
+ if(lpoint.length > 0) {
+ coordString = lpoint[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+ } else {
+ var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+ }
+ }
+
+ var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
+ if (upoint.length > 0) {
+ var coords = [];
+
+ if(upoint.length > 0) {
+ coordString = upoint[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+ } else {
+ var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+ }
+ }
+
+ if (lowerPoint && upperPoint) {
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+ components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
+ components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+ envelope = new OpenLayers.Geometry.Polygon([ring]);
+ }
+ return envelope;
+ },
+
+ /**
+ * Method: parseGeometry.box
+ * Given a GML node representing a box geometry, create an
+ * OpenLayers.Bounds.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds representing the box.
+ */
+ box: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ var coordString;
+ var coords, beginPoint = null, endPoint = null;
+ if (nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coords = coordString.split(" ");
+ if (coords.length == 2) {
+ beginPoint = coords[0].split(",");
+ endPoint = coords[1].split(",");
+ }
+ }
+ if (beginPoint !== null && endPoint !== null) {
+ return new OpenLayers.Bounds(parseFloat(beginPoint[0]),
+ parseFloat(beginPoint[1]),
+ parseFloat(endPoint[0]),
+ parseFloat(endPoint[1]) );
+ }
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+ // assume attributes are children of the first type 1 child
+ var childNode = node.firstChild;
+ var children, i, child, grandchildren, grandchild, name, value;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ // attributes are type 1 children with one type 3 child
+ children = childNode.childNodes;
+ for(i=0; i<children.length; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length == 1) {
+ grandchild = grandchildren[0];
+ if(grandchild.nodeType == 3 ||
+ grandchild.nodeType == 4) {
+ name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ value = grandchild.nodeValue.replace(
+ this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ } else {
+ // If child has no childNodes (grandchildren),
+ // set an attribute with null value.
+ // e.g. <prefix:fieldname/> becomes
+ // {fieldname: null}
+ attributes[child.nodeName.split(":").pop()] = null;
+ }
+ }
+ }
+ break;
+ }
+ childNode = childNode.nextSibling;
+ }
+ return attributes;
+ },
+
+ /**
+ * APIMethod: write
+ * Generate a GML document string given a list of features.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
+ * serialize into a string.
+ *
+ * Returns:
+ * {String} A string representing the GML document.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var gml = this.createElementNS("http://www.opengis.net/wfs",
+ "wfs:" + this.collectionName);
+ for(var i=0; i<features.length; i++) {
+ gml.appendChild(this.createFeatureXML(features[i]));
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
+ *
+ * Returns:
+ * {DOMElement} A node reprensting the feature in GML.
+ */
+ createFeatureXML: function(feature) {
+ var geometry = feature.geometry;
+ var geometryNode = this.buildGeometryNode(geometry);
+ var geomContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ this.geometryName);
+ geomContainer.appendChild(geometryNode);
+ var featureNode = this.createElementNS(this.gmlns,
+ "gml:" + this.featureName);
+ var featureContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ this.layerName);
+ var fid = feature.fid || feature.id;
+ featureContainer.setAttribute("fid", fid);
+ featureContainer.appendChild(geomContainer);
+ for(var attr in feature.attributes) {
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr.substring(attr.lastIndexOf(":") + 1);
+ var attrContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ nodename);
+ attrContainer.appendChild(attrText);
+ featureContainer.appendChild(attrContainer);
+ }
+ featureNode.appendChild(featureContainer);
+ return featureNode;
+ },
+
+ /**
+ * APIMethod: buildGeometryNode
+ */
+ buildGeometryNode: function(geometry) {
+ if (this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ return builder.apply(this, [geometry]);
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD retrieve the srs from layer
+ // srsName is non-standard, so not including it until it's right.
+ // gml.setAttribute("srsName",
+ // "http://www.opengis.net/gml/srs/epsg.xml#4326");
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a GML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML point node.
+ */
+ point: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:Point");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a GML multipoint.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multipoint node.
+ */
+ multipoint: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
+ var points = geometry.components;
+ var pointMember, pointGeom;
+ for(var i=0; i<points.length; i++) {
+ pointMember = this.createElementNS(this.gmlns,
+ "gml:pointMember");
+ pointGeom = this.buildGeometry.point.apply(this,
+ [points[i]]);
+ pointMember.appendChild(pointGeom);
+ gml.appendChild(pointMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a GML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML linestring node.
+ */
+ linestring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:LineString");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a GML
+ * multilinestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
+ * geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multilinestring node.
+ */
+ multilinestring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
+ var lines = geometry.components;
+ var lineMember, lineGeom;
+ for(var i=0; i<lines.length; ++i) {
+ lineMember = this.createElementNS(this.gmlns,
+ "gml:lineStringMember");
+ lineGeom = this.buildGeometry.linestring.apply(this,
+ [lines[i]]);
+ lineMember.appendChild(lineGeom);
+ gml.appendChild(lineMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a GML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML linearring node.
+ */
+ linearring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a GML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML polygon node.
+ */
+ polygon: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0; i<rings.length; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.gmlns,
+ "gml:" + type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ gml.appendChild(ringMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
+ * geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multipolygon node.
+ */
+ multipolygon: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
+ var polys = geometry.components;
+ var polyMember, polyGeom;
+ for(var i=0; i<polys.length; ++i) {
+ polyMember = this.createElementNS(this.gmlns,
+ "gml:polygonMember");
+ polyGeom = this.buildGeometry.polygon.apply(this,
+ [polys[i]]);
+ polyMember.appendChild(polyGeom);
+ gml.appendChild(polyMember);
+ }
+ return gml;
+
+ },
+
+ /**
+ * Method: buildGeometry.bounds
+ * Given an OpenLayers bounds, create a GML box.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
+ *
+ * Returns:
+ * {DOMElement} A GML box node.
+ */
+ bounds: function(bounds) {
+ var gml = this.createElementNS(this.gmlns, "gml:Box");
+ gml.appendChild(this.buildCoordinatesNode(bounds));
+ return gml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinates
+ * builds the coordinates XmlNode
+ * (code)
+ * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
+ * (end)
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {XmlNode} created xmlNode
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.gmlns,
+ "gml:coordinates");
+ coordinatesNode.setAttribute("decimal", ".");
+ coordinatesNode.setAttribute("cs", ",");
+ coordinatesNode.setAttribute("ts", " ");
+
+ var parts = [];
+
+ if(geometry instanceof OpenLayers.Bounds){
+ parts.push(geometry.left + "," + geometry.bottom);
+ parts.push(geometry.right + "," + geometry.top);
+ } else {
+ var points = (geometry.components) ? geometry.components : [geometry];
+ for(var i=0; i<points.length; i++) {
+ parts.push(points[i].x + "," + points[i].y);
+ }
+ }
+
+ var txtNode = this.createTextNode(parts.join(" "));
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML"
+});
+/* ======================================================================
+ OpenLayers/Format/GML/Base.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML.js
+ */
+
+/**
+ * Though required in the full build, if the GML format is excluded, we set
+ * the namespace here.
+ */
+if(!OpenLayers.Format.GML) {
+ OpenLayers.Format.GML = {};
+}
+
+/**
+ * Class: OpenLayers.Format.GML.Base
+ * Superclass for GML parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "gml",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * APIProperty: featureType
+ * {Array(String) or String} The local (without prefix) feature typeName(s).
+ */
+ featureType: null,
+
+ /**
+ * APIProperty: featureNS
+ * {String} The feature namespace. Must be set in the options at
+ * construction.
+ */
+ featureNS: null,
+
+ /**
+ * APIProperty: geometry
+ * {String} Name of geometry element. Defaults to "geometry". If null, it
+ * will be set on <read> when the first geometry is parsed.
+ */
+ geometryName: "geometry",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: srsName
+ * {String} URI for spatial reference system. This is optional for
+ * single part geometries and mandatory for collections and multis.
+ * If set, the srsName attribute will be written for all geometries.
+ * Default is null.
+ */
+ srsName: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: geometryTypes
+ * {Object} Maps OpenLayers geometry class names to GML element names.
+ * Use <setGeometryTypes> before accessing this property.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: singleFeatureType
+ * {Boolean} True if there is only 1 featureType, and not an array
+ * of featuretypes.
+ */
+ singleFeatureType: null,
+
+ /**
+ * Property: autoConfig
+ * {Boolean} Indicates if the format was configured without a <featureNS>,
+ * but auto-configured <featureNS> and <featureType> during read.
+ * Subclasses making use of <featureType> auto-configuration should make
+ * the first call to the <readNode> method (usually in the read method)
+ * with true as 3rd argument, so the auto-configured featureType can be
+ * reset and the format can be reused for subsequent reads with data from
+ * different featureTypes. Set to false after read if you want to keep the
+ * auto-configured values.
+ */
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g),
+ featureMember: (/^(.*:)?featureMembers?$/)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.GML.Base
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> constructor
+ * instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {Array(String) or String} Local (without prefix) feature
+ * typeName(s) (required for write).
+ * featureNS - {String} Feature namespace (required for write).
+ * geometryName - {String} Geometry element name (required for write).
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.setGeometryTypes();
+ if(options && options.featureNS) {
+ this.setNamespace("feature", options.featureNS);
+ }
+ this.singleFeatureType = !options || (typeof options.featureType === "string");
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} A gml:featureMember element, a gml:featureMembers
+ * element, or an element containing either of the above at any level.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var features = [];
+ this.readNode(data, {features: features}, true);
+ if(features.length == 0) {
+ // look for gml:featureMember elements
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMember"
+ );
+ if(elements.length) {
+ for(var i=0, len=elements.length; i<len; ++i) {
+ this.readNode(elements[i], {features: features}, true);
+ }
+ } else {
+ // look for gml:featureMembers elements (this is v3, but does no harm here)
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMembers"
+ );
+ if(elements.length) {
+ // there can be only one
+ this.readNode(elements[0], {features: features}, true);
+ }
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // on subsequent calls of format.read(), we want to reset auto-
+ // configured properties and auto-configure again.
+ if (first === true && this.autoConfig === true) {
+ this.featureType = null;
+ delete this.namespaceAlias[this.featureNS];
+ delete this.namespaces["feature"];
+ this.featureNS = null;
+ }
+ // featureType auto-configuration
+ if (!this.featureNS && (!(node.prefix in this.namespaces) &&
+ node.parentNode.namespaceURI == this.namespaces["gml"] &&
+ this.regExes.featureMember.test(node.parentNode.nodeName))) {
+ this.featureType = node.nodeName.split(":").pop();
+ this.setNamespace("feature", node.namespaceURI);
+ this.featureNS = node.namespaceURI;
+ this.autoConfig = true;
+ }
+ return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, obj]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": {
+ "_inherit": function(node, obj, container) {
+ // To be implemented by version specific parsers
+ },
+ "featureMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "featureMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "boundedBy": function(node, obj) {
+ var container = {};
+ this.readChildNodes(node, container);
+ if(container.components && container.components.length > 0) {
+ obj.bounds = container.components[0];
+ }
+ },
+ "Point": function(node, container) {
+ var obj = {points: []};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(obj.points[0]);
+ },
+ "coordinates": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ var coords;
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ if (this.xy) {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[0], coords[1], coords[2]
+ );
+ } else {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[1], coords[0], coords[2]
+ );
+ }
+ }
+ obj.points = points;
+ },
+ "coord": function(node, obj) {
+ var coord = {};
+ this.readChildNodes(node, coord);
+ if(!obj.points) {
+ obj.points = [];
+ }
+ obj.points.push(new OpenLayers.Geometry.Point(
+ coord.x, coord.y, coord.z
+ ));
+ },
+ "X": function(node, coord) {
+ coord.x = this.getChildValue(node);
+ },
+ "Y": function(node, coord) {
+ coord.y = this.getChildValue(node);
+ },
+ "Z": function(node, coord) {
+ coord.z = this.getChildValue(node);
+ },
+ "MultiPoint": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPoint(obj.components)
+ ];
+ },
+ "pointMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LineString": function(node, container) {
+ var obj = {};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.LineString(obj.points)
+ );
+ },
+ "MultiLineString": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiLineString(obj.components)
+ ];
+ },
+ "lineStringMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Polygon": function(node, container) {
+ var obj = {outer: null, inner: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ obj.inner.unshift(obj.outer);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.Polygon(obj.inner)
+ );
+ },
+ "LinearRing": function(node, obj) {
+ var container = {};
+ this.readers.gml._inherit.apply(this, [node, container]);
+ this.readChildNodes(node, container);
+ obj.components = [new OpenLayers.Geometry.LinearRing(
+ container.points
+ )];
+ },
+ "MultiPolygon": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPolygon(obj.components)
+ ];
+ },
+ "polygonMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "GeometryCollection": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.Collection(obj.components)
+ ];
+ },
+ "geometryMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ },
+ "feature": {
+ "*": function(node, obj) {
+ // The node can either be named like the featureType, or it
+ // can be a child of the feature:featureType. Children can be
+ // geometry or attributes.
+ var name;
+ var local = node.localName || node.nodeName.split(":").pop();
+ // Since an attribute can have the same name as the feature type
+ // we only want to read the node as a feature if the parent
+ // node can have feature nodes as children. In this case, the
+ // obj.features property is set.
+ if (obj.features) {
+ if (!this.singleFeatureType &&
+ (OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
+ name = "_typeName";
+ } else if(local === this.featureType) {
+ name = "_typeName";
+ }
+ } else {
+ // Assume attribute elements have one child node and that the child
+ // is a text node. Otherwise assume it is a geometry node.
+ if(node.childNodes.length == 0 ||
+ (node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
+ if(this.extractAttributes) {
+ name = "_attribute";
+ }
+ } else {
+ name = "_geometry";
+ }
+ }
+ if(name) {
+ this.readers.feature[name].apply(this, [node, obj]);
+ }
+ },
+ "_typeName": function(node, obj) {
+ var container = {components: [], attributes: {}};
+ this.readChildNodes(node, container);
+ // look for common gml namespaced elements
+ if(container.name) {
+ container.attributes.name = container.name;
+ }
+ var feature = new OpenLayers.Feature.Vector(
+ container.components[0], container.attributes
+ );
+ if (!this.singleFeatureType) {
+ feature.type = node.nodeName.split(":").pop();
+ feature.namespace = node.namespaceURI;
+ }
+ var fid = node.getAttribute("fid") ||
+ this.getAttributeNS(node, this.namespaces["gml"], "id");
+ if(fid) {
+ feature.fid = fid;
+ }
+ if(this.internalProjection && this.externalProjection &&
+ feature.geometry) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if(container.bounds) {
+ feature.bounds = container.bounds;
+ }
+ obj.features.push(feature);
+ },
+ "_geometry": function(node, obj) {
+ if (!this.geometryName) {
+ this.geometryName = node.nodeName.split(":").pop();
+ }
+ this.readChildNodes(node, obj);
+ },
+ "_attribute": function(node, obj) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var value = this.getChildValue(node);
+ obj.attributes[local] = value;
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ name = "featureMembers";
+ } else {
+ name = "featureMember";
+ }
+ var root = this.writeNode("gml:" + name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": {
+ "featureMember": function(feature) {
+ var node = this.createElementNSPlus("gml:featureMember");
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "MultiPoint": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPoint");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("pointMember", components[i], node);
+ }
+ return node;
+ },
+ "pointMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:pointMember");
+ this.writeNode("Point", geometry, node);
+ return node;
+ },
+ "MultiLineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiLineString");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("lineStringMember", components[i], node);
+ }
+ return node;
+ },
+ "lineStringMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:lineStringMember");
+ this.writeNode("LineString", geometry, node);
+ return node;
+ },
+ "MultiPolygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPolygon");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode(
+ "polygonMember", components[i], node
+ );
+ }
+ return node;
+ },
+ "polygonMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:polygonMember");
+ this.writeNode("Polygon", geometry, node);
+ return node;
+ },
+ "GeometryCollection": function(geometry) {
+ var node = this.createElementNSPlus("gml:GeometryCollection");
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ this.writeNode("geometryMember", geometry.components[i], node);
+ }
+ return node;
+ },
+ "geometryMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:geometryMember");
+ var child = this.writeNode("feature:_geometry", geometry);
+ node.appendChild(child.firstChild);
+ return node;
+ }
+ },
+ "feature": {
+ "_typeName": function(feature) {
+ var node = this.createElementNSPlus("feature:" + this.featureType, {
+ attributes: {fid: feature.fid}
+ });
+ if(feature.geometry) {
+ this.writeNode("feature:_geometry", feature.geometry, node);
+ }
+ for(var name in feature.attributes) {
+ var value = feature.attributes[name];
+ if(value != null) {
+ this.writeNode(
+ "feature:_attribute",
+ {name: name, value: value}, node
+ );
+ }
+ }
+ return node;
+ },
+ "_geometry": function(geometry) {
+ if(this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone().transform(
+ this.internalProjection, this.externalProjection
+ );
+ }
+ var node = this.createElementNSPlus(
+ "feature:" + this.geometryName
+ );
+ var type = this.geometryTypes[geometry.CLASS_NAME];
+ var child = this.writeNode("gml:" + type, geometry, node);
+ if(this.srsName) {
+ child.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ },
+ "_attribute": function(obj) {
+ return this.createElementNSPlus("feature:" + obj.name, {
+ value: obj.value
+ });
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(features) {
+ /**
+ * This is only here because GML2 only describes abstract
+ * feature collections. Typically, you would not be using
+ * the GML format to write wfs elements. This just provides
+ * some way to write out lists of features. GML3 defines the
+ * featureMembers element, so that is used by default instead.
+ */
+ var node = this.createElementNSPlus("wfs:FeatureCollection");
+ for(var i=0, len=features.length; i<len; ++i) {
+ this.writeNode("gml:featureMember", features[i], node);
+ }
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: setGeometryTypes
+ * Sets the <geometryTypes> mapping.
+ */
+ setGeometryTypes: function() {
+ this.geometryTypes = {
+ "OpenLayers.Geometry.Point": "Point",
+ "OpenLayers.Geometry.MultiPoint": "MultiPoint",
+ "OpenLayers.Geometry.LineString": "LineString",
+ "OpenLayers.Geometry.MultiLineString": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": "Polygon",
+ "OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
+ "OpenLayers.Geometry.Collection": "GeometryCollection"
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.Base"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GML/v2.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/Base.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML.v2
+ * Parses GML version 2.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.Base>
+ */
+OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.GML.v2
+ * Create a parser for GML v2.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required).
+ * geometryName - {String} Geometry element name.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "outerBoundaryIs": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.outer = obj.components[0];
+ },
+ "innerBoundaryIs": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.inner.push(obj.components[0]);
+ },
+ "Box": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ var min = obj.points[0];
+ var max = obj.points[1];
+ container.components.push(
+ new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
+ );
+ }
+ }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ // GML2 only has abstract feature collections
+ // wfs provides a feature collection from a well-known schema
+ name = "wfs:FeatureCollection";
+ } else {
+ name = "gml:featureMember";
+ }
+ var root = this.writeNode(name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "Point": function(geometry) {
+ var node = this.createElementNSPlus("gml:Point");
+ this.writeNode("coordinates", [geometry], node);
+ return node;
+ },
+ "coordinates": function(points) {
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ var point;
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ if(this.xy) {
+ parts[i] = point.x + "," + point.y;
+ } else {
+ parts[i] = point.y + "," + point.x;
+ }
+ if(point.z != undefined) { // allow null or undefined
+ parts[i] += "," + point.z;
+ }
+ }
+ return this.createElementNSPlus("gml:coordinates", {
+ attributes: {
+ decimal: ".", cs: ",", ts: " "
+ },
+ value: (numPoints == 1) ? parts[0] : parts.join(" ")
+ });
+ },
+ "LineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineString");
+ this.writeNode("coordinates", geometry.components, node);
+ return node;
+ },
+ "Polygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:Polygon");
+ this.writeNode("outerBoundaryIs", geometry.components[0], node);
+ for(var i=1; i<geometry.components.length; ++i) {
+ this.writeNode(
+ "innerBoundaryIs", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "outerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:outerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "innerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:innerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "LinearRing": function(ring) {
+ var node = this.createElementNSPlus("gml:LinearRing");
+ this.writeNode("coordinates", ring.components, node);
+ return node;
+ },
+ "Box": function(bounds) {
+ var node = this.createElementNSPlus("gml:Box");
+ this.writeNode("coordinates", [
+ {x: bounds.left, y: bounds.bottom},
+ {x: bounds.right, y: bounds.top}
+ ], node);
+ // srsName attribute is optional for gml:Box
+ if(this.srsName) {
+ node.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.v2"
+
+});
+/* ======================================================================
+ OpenLayers/Filter/Function.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Function
+ * This class represents a filter function.
+ * We are using this class for creation of complex
+ * filters that can contain filter functions as values.
+ * Nesting function as other functions parameter is supported.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: name
+ * {String} Name of the function.
+ */
+ name: null,
+
+ /**
+ * APIProperty: params
+ * {Array(<OpenLayers.Filter.Function> || String || Number)} Function parameters
+ * For now support only other Functions, String or Number
+ */
+ params: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Function
+ * Creates a filter function.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * function.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Function>}
+ */
+
+ CLASS_NAME: "OpenLayers.Filter.Function"
+});
+
+/* ======================================================================
+ OpenLayers/BaseTypes/Date.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Namespace: OpenLayers.Date
+ * Contains implementations of Date.parse and date.toISOString that match the
+ * ECMAScript 5 specification for parsing RFC 3339 dates.
+ * http://tools.ietf.org/html/rfc3339
+ */
+OpenLayers.Date = {
+
+ /**
+ * APIProperty: dateRegEx
+ * The regex to be used for validating dates. You can provide your own
+ * regex for instance for adding support for years before BC. Default
+ * value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/
+ */
+ dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,
+
+ /**
+ * APIMethod: toISOString
+ * Generates a string representing a date. The format of the string follows
+ * the profile of ISO 8601 for date and time on the Internet (see
+ * http://tools.ietf.org/html/rfc3339). If the toISOString method is
+ * available on the Date prototype, that is used. The toISOString
+ * method for Date instances is defined in ECMA-262.
+ *
+ * Parameters:
+ * date - {Date} A date object.
+ *
+ * Returns:
+ * {String} A string representing the date (e.g.
+ * "2010-08-07T16:58:23.123Z"). If the date does not have a valid time
+ * (i.e. isNaN(date.getTime())) this method returns the string "Invalid
+ * Date". The ECMA standard says the toISOString method should throw
+ * RangeError in this case, but Firefox returns a string instead. For
+ * best results, use isNaN(date.getTime()) to determine date validity
+ * before generating date strings.
+ */
+ toISOString: (function() {
+ if ("toISOString" in Date.prototype) {
+ return function(date) {
+ return date.toISOString();
+ };
+ } else {
+ return function(date) {
+ var str;
+ if (isNaN(date.getTime())) {
+ // ECMA-262 says throw RangeError, Firefox returns
+ // "Invalid Date"
+ str = "Invalid Date";
+ } else {
+ str =
+ date.getUTCFullYear() + "-" +
+ OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" +
+ OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" +
+ OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" +
+ OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" +
+ OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." +
+ OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z";
+ }
+ return str;
+ };
+ }
+
+ })(),
+
+ /**
+ * APIMethod: parse
+ * Generate a date object from a string. The format for the string follows
+ * the profile of ISO 8601 for date and time on the Internet (see
+ * http://tools.ietf.org/html/rfc3339). We don't call the native
+ * Date.parse because of inconsistency between implmentations. In
+ * Chrome, calling Date.parse with a string that doesn't contain any
+ * indication of the timezone (e.g. "2011"), the date is interpreted
+ * in local time. On Firefox, the assumption is UTC.
+ *
+ * Parameters:
+ * str - {String} A string representing the date (e.g.
+ * "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z",
+ * "2010-08-07T11:58:23.123-06").
+ *
+ * Returns:
+ * {Date} A date object. If the string could not be parsed, an invalid
+ * date is returned (i.e. isNaN(date.getTime())).
+ */
+ parse: function(str) {
+ var date;
+ var match = str.match(this.dateRegEx);
+ if (match && (match[1] || match[7])) { // must have at least year or time
+ var year = parseInt(match[1], 10) || 0;
+ var month = (parseInt(match[2], 10) - 1) || 0;
+ var day = parseInt(match[3], 10) || 1;
+ date = new Date(Date.UTC(year, month, day));
+ // optional time
+ var type = match[7];
+ if (type) {
+ var hours = parseInt(match[4], 10);
+ var minutes = parseInt(match[5], 10);
+ var secFrac = parseFloat(match[6]);
+ var seconds = secFrac | 0;
+ var milliseconds = Math.round(1000 * (secFrac - seconds));
+ date.setUTCHours(hours, minutes, seconds, milliseconds);
+ // check offset
+ if (type !== "Z") {
+ var hoursOffset = parseInt(type, 10);
+ var minutesOffset = parseInt(match[8], 10) || 0;
+ var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60);
+ date = new Date(date.getTime() + offset);
+ }
+ }
+ } else {
+ date = new Date("invalid");
+ }
+ return date;
+ }
+};
+/* ======================================================================
+ OpenLayers/Format/Filter/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+/**
+ * @requires OpenLayers/Format/Filter.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Filter/Function.js
+ * @requires OpenLayers/BaseTypes/Date.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1
+ * Superclass for Filter version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ogc: "http://www.opengis.net/ogc",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "ogc",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} A Filter document element.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+ read: function(data) {
+ var obj = {};
+ this.readers.ogc["Filter"].apply(this, [data, obj]);
+ return obj.filter;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": {
+ "_expression": function(node) {
+ // only the simplest of ogc:expression handled
+ // "some text and an <PropertyName>attribute</PropertyName>"}
+ var obj, value = "";
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1:
+ obj = this.readNode(child);
+ if (obj.property) {
+ value += "${" + obj.property + "}";
+ } else if (obj.value !== undefined) {
+ value += obj.value;
+ }
+ break;
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ return value;
+ },
+ "Filter": function(node, parent) {
+ // Filters correspond to subclasses of OpenLayers.Filter.
+ // Since they contain information we don't persist, we
+ // create a temporary object and then pass on the filter
+ // (ogc:Filter) to the parent obj.
+ var obj = {
+ fids: [],
+ filters: []
+ };
+ this.readChildNodes(node, obj);
+ if(obj.fids.length > 0) {
+ parent.filter = new OpenLayers.Filter.FeatureId({
+ fids: obj.fids
+ });
+ } else if(obj.filters.length > 0) {
+ parent.filter = obj.filters[0];
+ }
+ },
+ "FeatureId": function(node, obj) {
+ var fid = node.getAttribute("fid");
+ if(fid) {
+ obj.fids.push(fid);
+ }
+ },
+ "And": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Or": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Not": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLessThan": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsGreaterThan": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLessThanOrEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsBetween": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Literal": function(node, obj) {
+ obj.value = OpenLayers.String.numericIf(
+ this.getChildValue(node), true);
+ },
+ "PropertyName": function(node, filter) {
+ filter.property = this.getChildValue(node);
+ },
+ "LowerBoundary": function(node, filter) {
+ filter.lowerBoundary = OpenLayers.String.numericIf(
+ this.readers.ogc._expression.call(this, node), true);
+ },
+ "UpperBoundary": function(node, filter) {
+ filter.upperBoundary = OpenLayers.String.numericIf(
+ this.readers.ogc._expression.call(this, node), true);
+ },
+ "Intersects": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
+ },
+ "Within": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN);
+ },
+ "Contains": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
+ },
+ "DWithin": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
+ },
+ "Distance": function(node, obj) {
+ obj.distance = parseInt(this.getChildValue(node));
+ obj.distanceUnits = node.getAttribute("units");
+ },
+ "Function": function(node, obj) {
+ //TODO write decoder for it
+ return;
+ },
+ "PropertyIsNull": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ }
+ }
+ },
+
+ /**
+ * Method: readSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM element that contains an ogc:expression.
+ * obj - {Object} The target object.
+ * type - {String} One of the OpenLayers.Filter.Spatial.* constants.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} The created filter.
+ */
+ readSpatial: function(node, obj, type) {
+ var filter = new OpenLayers.Filter.Spatial({
+ type: type
+ });
+ this.readChildNodes(node, filter);
+ filter.value = filter.components[0];
+ delete filter.components;
+ obj.filters.push(filter);
+ },
+
+ /**
+ * APIMethod: encodeLiteral
+ * Generates the string representation of a value for use in <Literal>
+ * elements. The default encoder writes Date values as ISO 8601
+ * strings.
+ *
+ * Parameters:
+ * value - {Object} Literal value to encode
+ *
+ * Returns:
+ * {String} String representation of the provided value.
+ */
+ encodeLiteral: function(value) {
+ if (value instanceof Date) {
+ value = OpenLayers.Date.toISOString(value);
+ }
+ return value;
+ },
+
+ /**
+ * Method: writeOgcExpression
+ * Limited support for writing OGC expressions. Currently it supports
+ * (<OpenLayers.Filter.Function> || String || Number)
+ *
+ * Parameters:
+ * value - (<OpenLayers.Filter.Function> || String || Number)
+ * node - {DOMElement} A parent DOM element
+ *
+ * Returns:
+ * {DOMElement} Updated node element.
+ */
+ writeOgcExpression: function(value, node) {
+ if (value instanceof OpenLayers.Filter.Function){
+ this.writeNode("Function", value, node);
+ } else {
+ this.writeNode("Literal", value, node);
+ }
+ return node;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} A filter object.
+ *
+ * Returns:
+ * {DOMElement} An ogc:Filter element.
+ */
+ write: function(filter) {
+ return this.writers.ogc["Filter"].apply(this, [filter]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": {
+ "Filter": function(filter) {
+ var node = this.createElementNSPlus("ogc:Filter");
+ this.writeNode(this.getFilterType(filter), filter, node);
+ return node;
+ },
+ "_featureIds": function(filter) {
+ var node = this.createDocumentFragment();
+ for (var i=0, ii=filter.fids.length; i<ii; ++i) {
+ this.writeNode("ogc:FeatureId", filter.fids[i], node);
+ }
+ return node;
+ },
+ "FeatureId": function(fid) {
+ return this.createElementNSPlus("ogc:FeatureId", {
+ attributes: {fid: fid}
+ });
+ },
+ "And": function(filter) {
+ var node = this.createElementNSPlus("ogc:And");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Or": function(filter) {
+ var node = this.createElementNSPlus("ogc:Or");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Not": function(filter) {
+ var node = this.createElementNSPlus("ogc:Not");
+ var childFilter = filter.filters[0];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ return node;
+ },
+ "PropertyIsLessThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsGreaterThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLessThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsBetween": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsBetween");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ this.writeNode("LowerBoundary", filter, node);
+ this.writeNode("UpperBoundary", filter, node);
+ return node;
+ },
+ "PropertyName": function(filter) {
+ // no ogc:expression handling for now
+ return this.createElementNSPlus("ogc:PropertyName", {
+ value: filter.property
+ });
+ },
+ "Literal": function(value) {
+ var encode = this.encodeLiteral ||
+ OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
+ return this.createElementNSPlus("ogc:Literal", {
+ value: encode(value)
+ });
+ },
+ "LowerBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:LowerBoundary");
+ this.writeOgcExpression(filter.lowerBoundary, node);
+ return node;
+ },
+ "UpperBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:UpperBoundary");
+ this.writeNode("Literal", filter.upperBoundary, node);
+ return node;
+ },
+ "INTERSECTS": function(filter) {
+ return this.writeSpatial(filter, "Intersects");
+ },
+ "WITHIN": function(filter) {
+ return this.writeSpatial(filter, "Within");
+ },
+ "CONTAINS": function(filter) {
+ return this.writeSpatial(filter, "Contains");
+ },
+ "DWITHIN": function(filter) {
+ var node = this.writeSpatial(filter, "DWithin");
+ this.writeNode("Distance", filter, node);
+ return node;
+ },
+ "Distance": function(filter) {
+ return this.createElementNSPlus("ogc:Distance", {
+ attributes: {
+ units: filter.distanceUnits
+ },
+ value: filter.distance
+ });
+ },
+ "Function": function(filter) {
+ var node = this.createElementNSPlus("ogc:Function", {
+ attributes: {
+ name: filter.name
+ }
+ });
+ var params = filter.params;
+ for(var i=0, len=params.length; i<len; i++){
+ this.writeOgcExpression(params[i], node);
+ }
+ return node;
+ },
+ "PropertyIsNull": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNull");
+ this.writeNode("PropertyName", filter, node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: getFilterType
+ */
+ getFilterType: function(filter) {
+ var filterType = this.filterMap[filter.type];
+ if(!filterType) {
+ throw "Filter writing not supported for rule type: " + filter.type;
+ }
+ return filterType;
+ },
+
+ /**
+ * Property: filterMap
+ * {Object} Contains a member for each filter type. Values are node names
+ * for corresponding OGC Filter child elements.
+ */
+ filterMap: {
+ "&&": "And",
+ "||": "Or",
+ "!": "Not",
+ "==": "PropertyIsEqualTo",
+ "!=": "PropertyIsNotEqualTo",
+ "<": "PropertyIsLessThan",
+ ">": "PropertyIsGreaterThan",
+ "<=": "PropertyIsLessThanOrEqualTo",
+ ">=": "PropertyIsGreaterThanOrEqualTo",
+ "..": "PropertyIsBetween",
+ "~": "PropertyIsLike",
+ "NULL": "PropertyIsNull",
+ "BBOX": "BBOX",
+ "DWITHIN": "DWITHIN",
+ "WITHIN": "WITHIN",
+ "CONTAINS": "CONTAINS",
+ "INTERSECTS": "INTERSECTS",
+ "FID": "_featureIds"
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1"
+
+});
+/* ======================================================================
+ OpenLayers/Format/Filter/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/v2.js
+ * @requires OpenLayers/Format/Filter/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_0_0
+ * Write ogc:Filter version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.v2>
+ * - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
+ */
+ schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.v2.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsNotEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLike": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE
+ });
+ this.readChildNodes(node, filter);
+ var wildCard = node.getAttribute("wildCard");
+ var singleChar = node.getAttribute("singleChar");
+ var esc = node.getAttribute("escape");
+ filter.value2regex(wildCard, singleChar, esc);
+ obj.filters.push(filter);
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsNotEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLike": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+ attributes: {
+ wildCard: "*", singleChar: ".", escape: "!"
+ }
+ });
+ // no ogc:expression handling for now
+ this.writeNode("PropertyName", filter, node);
+ // convert regex string to ogc string
+ this.writeNode("Literal", filter.regex2value(), node);
+ return node;
+ },
+ "BBOX": function(filter) {
+ var node = this.createElementNSPlus("ogc:BBOX");
+ // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
+ // accepts filters without it. When this is used with
+ // OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a
+ // missing filter.property to the geometryName that is
+ // configured with the protocol, which defaults to "the_geom".
+ // So the only way to omit this mandatory property is to not
+ // set the property on the filter and to set the geometryName
+ // on the WFS protocol to null. The latter also happens when
+ // the protocol is configured without a geometryName and a
+ // featureNS.
+ filter.property && this.writeNode("PropertyName", filter, node);
+ var box = this.writeNode("gml:Box", filter.value, node);
+ if(filter.projection) {
+ box.setAttribute("srsName", filter.projection);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
+ },
+
+ /**
+ * Method: writeSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter.Spatial>} The filter.
+ * name - {String} Name of the generated XML element.
+ *
+ * Returns:
+ * {DOMElement} The created XML element.
+ */
+ writeSpatial: function(filter, name) {
+ var node = this.createElementNSPlus("ogc:"+name);
+ this.writeNode("PropertyName", filter, node);
+ if(filter.value instanceof OpenLayers.Filter.Function) {
+ this.writeNode("Function", filter.value, node);
+ } else {
+ var child;
+ if(filter.value instanceof OpenLayers.Geometry) {
+ child = this.writeNode("feature:_geometry", filter.value).firstChild;
+ } else {
+ child = this.writeNode("gml:Box", filter.value);
+ }
+ if(filter.projection) {
+ child.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(child);
+ }
+ return node;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0"
+
+});
+/* ======================================================================
+ OpenLayers/Format/WFST/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFST/v1.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1_0_0
+ * A format for creating WFS v1.0.0 transactions. Create a new instance with the
+ * <OpenLayers.Format.WFST.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_0_0>
+ * - <OpenLayers.Format.WFST.v1>
+ */
+OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * APIProperty: srsNameInQuery
+ * {Boolean} If true the reference system is passed in Query requests
+ * via the "srsName" attribute to the "wfs:Query" element, this
+ * property defaults to false as it isn't WFS 1.0.0 compliant.
+ */
+ srsNameInQuery: false,
+
+ /**
+ * Property: schemaLocations
+ * {Object} Properties are namespace aliases, values are schema locations.
+ */
+ schemaLocations: {
+ "wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1_0_0
+ * A class for parsing and generating WFS v1.0.0 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // Not the superclass, only the mixin classes inherit from
+ // Format.GML.v2. We need this because we don't want to get readNode
+ // from the superclass's superclass, which is OpenLayers.Format.XML.
+ return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "WFS_TransactionResponse": function(node, obj) {
+ obj.insertIds = [];
+ obj.success = false;
+ this.readChildNodes(node, obj);
+ },
+ "InsertResult": function(node, container) {
+ var obj = {fids: []};
+ this.readChildNodes(node, obj);
+ container.insertIds = container.insertIds.concat(obj.fids);
+ },
+ "TransactionResult": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Status": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "SUCCESS": function(node, obj) {
+ obj.success = true;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "Query": function(options) {
+ options = OpenLayers.Util.extend({
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ featureType: this.featureType,
+ srsName: this.srsName,
+ srsNameInQuery: this.srsNameInQuery
+ }, options);
+ var prefix = options.featurePrefix;
+ var node = this.createElementNSPlus("wfs:Query", {
+ attributes: {
+ typeName: (prefix ? prefix + ":" : "") +
+ options.featureType
+ }
+ });
+ if(options.srsNameInQuery && options.srsName) {
+ node.setAttribute("srsName", options.srsName);
+ }
+ if(options.featureNS) {
+ node.setAttribute("xmlns:" + prefix, options.featureNS);
+ }
+ if(options.propertyNames) {
+ for(var i=0,len = options.propertyNames.length; i<len; i++) {
+ this.writeNode(
+ "ogc:PropertyName",
+ {property: options.propertyNames[i]},
+ node
+ );
+ }
+ }
+ if(options.filter) {
+ this.setFilterProperty(options.filter);
+ this.writeNode("ogc:Filter", options.filter, node);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/Renderer/Elements.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ * placed in the DOM based on given indexing methods.
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+
+ /**
+ * Property: maxZIndex
+ * {Integer} This is the largest-most z-index value for a node
+ * contained within the indexer.
+ */
+ maxZIndex: null,
+
+ /**
+ * Property: order
+ * {Array<String>} This is an array of node id's stored in the
+ * order that they should show up on screen. Id's higher up in the
+ * array (higher array index) represent nodes with higher z-indeces.
+ */
+ order: null,
+
+ /**
+ * Property: indices
+ * {Object} This is a hash that maps node ids to their z-index value
+ * stored in the indexer. This is done to make finding a nodes z-index
+ * value O(1).
+ */
+ indices: null,
+
+ /**
+ * Property: compare
+ * {Function} This is the function used to determine placement of
+ * of a new node within the indexer. If null, this defaults to to
+ * the Z_ORDER_DRAWING_ORDER comparison method.
+ */
+ compare: null,
+
+ /**
+ * APIMethod: initialize
+ * Create a new indexer with
+ *
+ * Parameters:
+ * yOrdering - {Boolean} Whether to use y-ordering.
+ */
+ initialize: function(yOrdering) {
+
+ this.compare = yOrdering ?
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
+
+ this.clear();
+ },
+
+ /**
+ * APIMethod: insert
+ * Insert a new node into the indexer. In order to find the correct
+ * positioning for the node to be inserted, this method uses a binary
+ * search. This makes inserting O(log(n)).
+ *
+ * Parameters:
+ * newNode - {DOMElement} The new node to be inserted.
+ *
+ * Returns
+ * {DOMElement} the node before which we should insert our newNode, or
+ * null if newNode can just be appended.
+ */
+ insert: function(newNode) {
+ // If the node is known to the indexer, remove it so we can
+ // recalculate where it should go.
+ if (this.exists(newNode)) {
+ this.remove(newNode);
+ }
+
+ var nodeId = newNode.id;
+
+ this.determineZIndex(newNode);
+
+ var leftIndex = -1;
+ var rightIndex = this.order.length;
+ var middle;
+
+ while (rightIndex - leftIndex > 1) {
+ middle = parseInt((leftIndex + rightIndex) / 2);
+
+ var placement = this.compare(this, newNode,
+ OpenLayers.Util.getElement(this.order[middle]));
+
+ if (placement > 0) {
+ leftIndex = middle;
+ } else {
+ rightIndex = middle;
+ }
+ }
+
+ this.order.splice(rightIndex, 0, nodeId);
+ this.indices[nodeId] = this.getZIndex(newNode);
+
+ // If the new node should be before another in the index
+ // order, return the node before which we have to insert the new one;
+ // else, return null to indicate that the new node can be appended.
+ return this.getNextElement(rightIndex);
+ },
+
+ /**
+ * APIMethod: remove
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be removed.
+ */
+ remove: function(node) {
+ var nodeId = node.id;
+ var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
+ if (arrayIndex >= 0) {
+ // Remove it from the order array, as well as deleting the node
+ // from the indeces hash.
+ this.order.splice(arrayIndex, 1);
+ delete this.indices[nodeId];
+
+ // Reset the maxium z-index based on the last item in the
+ // order array.
+ if (this.order.length > 0) {
+ var lastId = this.order[this.order.length - 1];
+ this.maxZIndex = this.indices[lastId];
+ } else {
+ this.maxZIndex = 0;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clear
+ */
+ clear: function() {
+ this.order = [];
+ this.indices = {};
+ this.maxZIndex = 0;
+ },
+
+ /**
+ * APIMethod: exists
+ *
+ * Parameters:
+ * node - {DOMElement} The node to test for existence.
+ *
+ * Returns:
+ * {Boolean} Whether or not the node exists in the indexer?
+ */
+ exists: function(node) {
+ return (this.indices[node.id] != null);
+ },
+
+ /**
+ * APIMethod: getZIndex
+ * Get the z-index value for the current node from the node data itself.
+ *
+ * Parameters:
+ * node - {DOMElement} The node whose z-index to get.
+ *
+ * Returns:
+ * {Integer} The z-index value for the specified node (from the node
+ * data itself).
+ */
+ getZIndex: function(node) {
+ return node._style.graphicZIndex;
+ },
+
+ /**
+ * Method: determineZIndex
+ * Determine the z-index for the current node if there isn't one,
+ * and set the maximum value if we've found a new maximum.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ determineZIndex: function(node) {
+ var zIndex = node._style.graphicZIndex;
+
+ // Everything must have a zIndex. If none is specified,
+ // this means the user *must* (hint: assumption) want this
+ // node to succomb to drawing order. To enforce drawing order
+ // over all indexing methods, we'll create a new z-index that's
+ // greater than any currently in the indexer.
+ if (zIndex == null) {
+ zIndex = this.maxZIndex;
+ node._style.graphicZIndex = zIndex;
+ } else if (zIndex > this.maxZIndex) {
+ this.maxZIndex = zIndex;
+ }
+ },
+
+ /**
+ * APIMethod: getNextElement
+ * Get the next element in the order stack.
+ *
+ * Parameters:
+ * index - {Integer} The index of the current node in this.order.
+ *
+ * Returns:
+ * {DOMElement} the node following the index passed in, or
+ * null.
+ */
+ getNextElement: function(index) {
+ var nextIndex = index + 1;
+ if (nextIndex < this.order.length) {
+ var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
+ if (nextElement == undefined) {
+ nextElement = this.getNextElement(nextIndex);
+ }
+ return nextElement;
+ } else {
+ return null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.ElementsIndexer"
+});
+
+/**
+ * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
+ * These are the compare methods for figuring out where a new node should be
+ * placed within the indexer. These methods are very similar to general
+ * sorting methods in that they return -1, 0, and 1 to specify the
+ * direction in which new nodes fall in the ordering.
+ */
+OpenLayers.ElementsIndexer.IndexingMethods = {
+
+ /**
+ * Method: Z_ORDER
+ * This compare method is used by other comparison methods.
+ * It can be used individually for ordering, but is not recommended,
+ * because it doesn't subscribe to drawing order.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER: function(indexer, newNode, nextNode) {
+ var newZIndex = indexer.getZIndex(newNode);
+
+ var returnVal = 0;
+ if (nextNode) {
+ var nextZIndex = indexer.getZIndex(nextNode);
+ returnVal = newZIndex - nextZIndex;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_DRAWING_ORDER
+ * This method orders nodes by their z-index, but does so in a way
+ * that, if there are other nodes with the same z-index, the newest
+ * drawn will be the front most within that z-index. This is the
+ * default indexing method.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ // Make Z_ORDER subscribe to drawing order by pushing it above
+ // all of the other nodes with the same z-index.
+ if (nextNode && returnVal == 0) {
+ returnVal = 1;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_Y_ORDER
+ * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
+ * best describes which ordering methods have precedence (though, the
+ * name would be too long). This method orders nodes by their z-index,
+ * but does so in a way that, if there are other nodes with the same
+ * z-index, the nodes with the lower y position will be "closer" than
+ * those with a higher y position. If two nodes have the exact same y
+ * position, however, then this method will revert to using drawing
+ * order to decide placement.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ if (nextNode && returnVal === 0) {
+ var result = nextNode._boundsBottom - newNode._boundsBottom;
+ returnVal = (result === 0) ? 1 : result;
+ }
+
+ return returnVal;
+ }
+};
+
+/**
+ * Class: OpenLayers.Renderer.Elements
+ * This is another virtual class in that it should never be instantiated by
+ * itself as a Renderer. It exists because there is *tons* of shared
+ * functionality between different vector libraries which use nodes/elements
+ * as a base for rendering vectors.
+ *
+ * The highlevel bits of code that are implemented here are the adding and
+ * removing of geometries, which is essentially the same for any
+ * element-based renderer. The details of creating each node and drawing the
+ * paths are of course different, but the machinery is the same.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * Property: rendererRoot
+ * {DOMElement}
+ */
+ rendererRoot: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: vectorRoot
+ * {DOMElement}
+ */
+ vectorRoot: null,
+
+ /**
+ * Property: textRoot
+ * {DOMElement}
+ */
+ textRoot: null,
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: null,
+
+ /**
+ * Property: xOffset
+ * {Number} Offset to apply to the renderer viewport translation in x
+ * direction. If the renderer extent's center is on the right of the
+ * dateline (i.e. exceeds the world bounds), we shift the viewport to the
+ * left by one world width. This avoids that features disappear from the
+ * map viewport. Because our dateline handling logic in other places
+ * ensures that extents crossing the dateline always have a center
+ * exceeding the world bounds on the left, we need this offset to make sure
+ * that the same is true for the renderer extent in pixel space as well.
+ */
+ xOffset: 0,
+
+ /**
+ * Property: rightOfDateLine
+ * {Boolean} Keeps track of the location of the map extent relative to the
+ * date line. The <setExtent> method compares this value (which is the one
+ * from the previous <setExtent> call) with the current position of the map
+ * extent relative to the date line and updates the xOffset when the extent
+ * has moved from one side of the date line to the other.
+ */
+
+ /**
+ * Property: Indexer
+ * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
+ * created upon initialization if the zIndexing or yOrdering options
+ * passed to this renderer's constructor are set to true.
+ */
+ indexer: null,
+
+ /**
+ * Constant: BACKGROUND_ID_SUFFIX
+ * {String}
+ */
+ BACKGROUND_ID_SUFFIX: "_background",
+
+ /**
+ * Constant: LABEL_ID_SUFFIX
+ * {String}
+ */
+ LABEL_ID_SUFFIX: "_label",
+
+ /**
+ * Constant: LABEL_OUTLINE_SUFFIX
+ * {String}
+ */
+ LABEL_OUTLINE_SUFFIX: "_outline",
+
+ /**
+ * Constructor: OpenLayers.Renderer.Elements
+ *
+ * Parameters:
+ * containerID - {String}
+ * options - {Object} options for this renderer.
+ *
+ * Supported options are:
+ * yOrdering - {Boolean} Whether to use y-ordering
+ * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+ * if yOrdering is set to true.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+
+ this.rendererRoot = this.createRenderRoot();
+ this.root = this.createRoot("_root");
+ this.vectorRoot = this.createRoot("_vroot");
+ this.textRoot = this.createRoot("_troot");
+
+ this.root.appendChild(this.vectorRoot);
+ this.root.appendChild(this.textRoot);
+
+ this.rendererRoot.appendChild(this.root);
+ this.container.appendChild(this.rendererRoot);
+
+ if(options && (options.zIndexing || options.yOrdering)) {
+ this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+
+ this.clear();
+
+ this.rendererRoot = null;
+ this.root = null;
+ this.xmlns = null;
+
+ OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clear
+ * Remove all the elements from the root
+ */
+ clear: function() {
+ var child;
+ var root = this.vectorRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ root = this.textRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ if (this.indexer) {
+ this.indexer.clear();
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var rightOfDateLine,
+ ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio),
+ world = this.map.getMaxExtent();
+ if (world.right > extent.left && world.right < extent.right) {
+ rightOfDateLine = true;
+ } else if (world.left > extent.left && world.left < extent.right) {
+ rightOfDateLine = false;
+ }
+ if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
+ coordSysUnchanged = false;
+ this.xOffset = rightOfDateLine === true ?
+ world.getWidth() / resolution : 0;
+ }
+ this.rightOfDateLine = rightOfDateLine;
+ }
+ return coordSysUnchanged;
+ },
+
+ /**
+ * Method: getNodeType
+ * This function is in charge of asking the specific renderer which type
+ * of node to create for the given geometry and style. All geometries
+ * in an Elements-based renderer consist of one node and some
+ * attributes. We have the nodeFactory() function which creates a node
+ * for us, but it takes a 'type' as input, and that is precisely what
+ * this function tells us.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) { },
+
+ /**
+ * Method: drawGeometry
+ * Draw the geometry, creating new nodes, setting paths, setting style,
+ * setting featureId on the node. This method should only be called
+ * by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the geometry has been drawn completely; null if
+ * incomplete; false otherwise
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ var rendered = true;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0, len=geometry.components.length; i<len; i++) {
+ rendered = this.drawGeometry(
+ geometry.components[i], style, featureId) && rendered;
+ }
+ return rendered;
+ }
+
+ rendered = false;
+ var removeBackground = false;
+ if (style.display != "none") {
+ if (style.backgroundGraphic) {
+ this.redrawBackgroundNode(geometry.id, geometry, style,
+ featureId);
+ } else {
+ removeBackground = true;
+ }
+ rendered = this.redrawNode(geometry.id, geometry, style,
+ featureId);
+ }
+ if (rendered == false) {
+ var node = document.getElementById(geometry.id);
+ if (node) {
+ if (node._style.backgroundGraphic) {
+ removeBackground = true;
+ }
+ node.parentNode.removeChild(node);
+ }
+ }
+ if (removeBackground) {
+ var node = document.getElementById(
+ geometry.id + this.BACKGROUND_ID_SUFFIX);
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: redrawNode
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawNode: function(id, geometry, style, featureId) {
+ style = this.applyDefaultSymbolizer(style);
+ // Get the node if it's already on the map.
+ var node = this.nodeFactory(id, this.getNodeType(geometry, style));
+
+ // Set the data for the node, then draw it.
+ node._featureId = featureId;
+ node._boundsBottom = geometry.getBounds().bottom;
+ node._geometryClass = geometry.CLASS_NAME;
+ node._style = style;
+
+ var drawResult = this.drawGeometryNode(node, geometry, style);
+ if(drawResult === false) {
+ return false;
+ }
+
+ node = drawResult.node;
+
+ // Insert the node into the indexer so it can show us where to
+ // place it. Note that this operation is O(log(n)). If there's a
+ // performance problem (when dragging, for instance) this is
+ // likely where it would be.
+ if (this.indexer) {
+ var insert = this.indexer.insert(node);
+ if (insert) {
+ this.vectorRoot.insertBefore(node, insert);
+ } else {
+ this.vectorRoot.appendChild(node);
+ }
+ } else {
+ // if there's no indexer, simply append the node to root,
+ // but only if the node is a new one
+ if (node.parentNode !== this.vectorRoot){
+ this.vectorRoot.appendChild(node);
+ }
+ }
+
+ this.postDraw(node);
+
+ return drawResult.complete;
+ },
+
+ /**
+ * Method: redrawBackgroundNode
+ * Redraws the node using special 'background' style properties. Basically
+ * just calls redrawNode(), but instead of directly using the
+ * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
+ * 'graphicZIndex' properties directly from the specified 'style'
+ * parameter, we create a new style object and set those properties
+ * from the corresponding 'background'-prefixed properties from
+ * specified 'style' parameter.
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawBackgroundNode: function(id, geometry, style, featureId) {
+ var backgroundStyle = OpenLayers.Util.extend({}, style);
+
+ // Set regular style attributes to apply to the background styles.
+ backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
+ backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
+ backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
+ backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
+ backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
+ backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
+
+ // Erase background styles.
+ backgroundStyle.backgroundGraphic = null;
+ backgroundStyle.backgroundXOffset = null;
+ backgroundStyle.backgroundYOffset = null;
+ backgroundStyle.backgroundGraphicZIndex = null;
+
+ return this.redrawNode(
+ id + this.BACKGROUND_ID_SUFFIX,
+ geometry,
+ backgroundStyle,
+ null
+ );
+ },
+
+ /**
+ * Method: drawGeometryNode
+ * Given a node, draw a geometry on the specified layer.
+ * node and geometry are required arguments, style is optional.
+ * This method is only called by the render itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {Object} a hash with properties "node" (the drawn node) and "complete"
+ * (null if parts of the geometry could not be drawn, false if nothing
+ * could be drawn)
+ */
+ drawGeometryNode: function(node, geometry, style) {
+ style = style || node._style;
+
+ var options = {
+ 'isFilled': style.fill === undefined ?
+ true :
+ style.fill,
+ 'isStroked': style.stroke === undefined ?
+ !!style.strokeWidth :
+ style.stroke
+ };
+ var drawn;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if(style.graphic === false) {
+ options.isFilled = false;
+ options.isStroked = false;
+ }
+ drawn = this.drawPoint(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ options.isFilled = false;
+ drawn = this.drawLineString(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ drawn = this.drawLinearRing(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ drawn = this.drawPolygon(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ drawn = this.drawRectangle(node, geometry);
+ break;
+ default:
+ break;
+ }
+
+ node._options = options;
+
+ //set style
+ //TBD simplify this
+ if (drawn != false) {
+ return {
+ node: this.setStyle(node, style, options, geometry),
+ complete: drawn
+ };
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: postDraw
+ * Things that have do be done after the geometry node is appended
+ * to its parent node. To be overridden by subclasses.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {},
+
+ /**
+ * Method: drawPoint
+ * Virtual function for drawing Point Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {},
+
+ /**
+ * Method: drawLineString
+ * Virtual function for drawing LineString Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {},
+
+ /**
+ * Method: drawLinearRing
+ * Virtual function for drawing LinearRing Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {},
+
+ /**
+ * Method: drawPolygon
+ * Virtual function for drawing Polygon Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {},
+
+ /**
+ * Method: drawRectangle
+ * Virtual function for drawing Rectangle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {},
+
+ /**
+ * Method: drawCircle
+ * Virtual function for drawing Circle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry) {},
+
+ /**
+ * Method: removeText
+ * Removes a label
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {
+ var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
+ if (label) {
+ this.textRoot.removeChild(label);
+ }
+ var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
+ if (outline) {
+ this.textRoot.removeChild(outline);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var target = evt.target;
+ var useElement = target && target.correspondingUseElement;
+ var node = useElement ? useElement : (target || evt.srcElement);
+ return node._featureId;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. In the case of a multi-geometry,
+ * we cycle through and recurse on ourselves. Otherwise, we look for a
+ * node with the geometry.id, destroy its geometry, and remove it from
+ * the DOM.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
+ for (var i=0, len=geometry.components.length; i<len; i++) {
+ this.eraseGeometry(geometry.components[i], featureId);
+ }
+ } else {
+ var element = OpenLayers.Util.getElement(geometry.id);
+ if (element && element.parentNode) {
+ if (element.geometry) {
+ element.geometry.destroy();
+ element.geometry = null;
+ }
+ element.parentNode.removeChild(element);
+
+ if (this.indexer) {
+ this.indexer.remove(element);
+ }
+
+ if (element._style.backgroundGraphic) {
+ var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+ var bElem = OpenLayers.Util.getElement(backgroundId);
+ if (bElem && bElem.parentNode) {
+ // No need to destroy the geometry since the element and the background
+ // node share the same geometry.
+ bElem.parentNode.removeChild(bElem);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: nodeFactory
+ * Create new node of the specified type, with the (optional) specified id.
+ *
+ * If node already exists with same ID and a different type, we remove it
+ * and then call ourselves again to recreate it.
+ *
+ * Parameters:
+ * id - {String}
+ * type - {String} type Kind of node to draw.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ */
+ nodeFactory: function(id, type) {
+ var node = OpenLayers.Util.getElement(id);
+ if (node) {
+ if (!this.nodeTypeCompare(node, type)) {
+ node.parentNode.removeChild(node);
+ node = this.nodeFactory(id, type);
+ }
+ } else {
+ node = this.createNode(type, id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ * This function must be overridden by subclasses.
+ */
+ nodeTypeCompare: function(node, type) {},
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw.
+ * id - {String} Id for node.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ * This function must be overridden by subclasses.
+ */
+ createNode: function(type, id) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {
+ var root = this.root;
+ if(renderer.root.parentNode == this.rendererRoot) {
+ root = renderer.root;
+ }
+ root.parentNode.removeChild(root);
+ renderer.rendererRoot.appendChild(root);
+ },
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.root.parentNode.parentNode.id;
+ },
+
+ /**
+ * Method: isComplexSymbol
+ * Determines if a symbol cannot be rendered using drawCircle
+ *
+ * Parameters:
+ * graphicName - {String}
+ *
+ * Returns
+ * {Boolean} true if the symbol is complex, false if not
+ */
+ isComplexSymbol: function(graphicName) {
+ return (graphicName != "circle") && !!graphicName;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
+/* ======================================================================
+ OpenLayers/Control/Panel.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ * The Panel control is a container for other controls. With it toolbars
+ * may be composed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: controls
+ * {Array(<OpenLayers.Control>)}
+ */
+ controls: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: defaultControl
+ * {<OpenLayers.Control>} The control which is activated when the control is
+ * activated (turned on), which also happens at instantiation.
+ * If <saveState> is true, <defaultControl> will be nullified after the
+ * first activation of the panel.
+ */
+ defaultControl: null,
+
+ /**
+ * APIProperty: saveState
+ * {Boolean} If set to true, the active state of this panel's controls will
+ * be stored on panel deactivation, and restored on reactivation. Default
+ * is false.
+ */
+ saveState: false,
+
+ /**
+ * APIProperty: allowDepress
+ * {Boolean} If is true the <OpenLayers.Control.TYPE_TOOL> controls can
+ * be deactivated by clicking the icon that represents them. Default
+ * is false.
+ */
+ allowDepress: false,
+
+ /**
+ * Property: activeState
+ * {Object} stores the active state of this panel's controls.
+ */
+ activeState: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Panel
+ * Create a new control panel.
+ *
+ * Each control in the panel is represented by an icon. When clicking
+ * on an icon, the <activateControl> method is called.
+ *
+ * Specific properties for controls on a panel:
+ * type - {Number} One of <OpenLayers.Control.TYPE_TOOL>,
+ * <OpenLayers.Control.TYPE_TOGGLE>, <OpenLayers.Control.TYPE_BUTTON>.
+ * If not provided, <OpenLayers.Control.TYPE_TOOL> is assumed.
+ * title - {string} Text displayed when mouse is over the icon that
+ * represents the control.
+ *
+ * The <OpenLayers.Control.type> of a control determines the behavior when
+ * clicking its icon:
+ * <OpenLayers.Control.TYPE_TOOL> - The control is activated and other
+ * controls of this type in the same panel are deactivated. This is
+ * the default type.
+ * <OpenLayers.Control.TYPE_TOGGLE> - The active state of the control is
+ * toggled.
+ * <OpenLayers.Control.TYPE_BUTTON> - The
+ * <OpenLayers.Control.Button.trigger> method of the control is called,
+ * but its active state is not changed.
+ *
+ * If a control is <OpenLayers.Control.active>, it will be drawn with the
+ * olControl[Name]ItemActive class, otherwise with the
+ * olControl[Name]ItemInactive class.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.controls = [];
+ this.activeState = {};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onButtonClick);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ for (var ctl, i = this.controls.length - 1; i >= 0; i--) {
+ ctl = this.controls[i];
+ if (ctl.events) {
+ ctl.events.un({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ ctl.panel_div = null;
+ }
+ this.activeState = null;
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ if (control === this.defaultControl ||
+ (this.saveState && this.activeState[control.id])) {
+ control.activate();
+ }
+ }
+ if (this.saveState === true) {
+ this.defaultControl = null;
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ this.activeState[control.id] = control.deactivate();
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ this.addControlsToMap(this.controls);
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function() {
+ for (var l=this.div.childNodes.length, i=l-1; i>=0; i--) {
+ this.div.removeChild(this.div.childNodes[i]);
+ }
+ this.div.innerHTML = "";
+ if (this.active) {
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ this.div.appendChild(this.controls[i].panel_div);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: activateControl
+ * This method is called when the user click on the icon representing a
+ * control in the panel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ */
+ activateControl: function (control) {
+ if (!this.active) { return false; }
+ if (control.type == OpenLayers.Control.TYPE_BUTTON) {
+ control.trigger();
+ return;
+ }
+ if (control.type == OpenLayers.Control.TYPE_TOGGLE) {
+ if (control.active) {
+ control.deactivate();
+ } else {
+ control.activate();
+ }
+ return;
+ }
+ if (this.allowDepress && control.active) {
+ control.deactivate();
+ } else {
+ var c;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ c = this.controls[i];
+ if (c != control &&
+ (c.type === OpenLayers.Control.TYPE_TOOL || c.type == null)) {
+ c.deactivate();
+ }
+ }
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: addControls
+ * To build a toolbar, you add a set of controls to it. addControls
+ * lets you add a single control or a list of controls to the
+ * Control Panel.
+ *
+ * Parameters:
+ * controls - {<OpenLayers.Control>} Controls to add in the panel.
+ */
+ addControls: function(controls) {
+ if (!(OpenLayers.Util.isArray(controls))) {
+ controls = [controls];
+ }
+ this.controls = this.controls.concat(controls);
+
+ for (var i=0, len=controls.length; i<len; i++) {
+ var control = controls[i],
+ element = this.createControlMarkup(control);
+ OpenLayers.Element.addClass(element,
+ control.displayClass + "ItemInactive");
+ OpenLayers.Element.addClass(element, "olButton");
+ if (control.title != "" && !element.title) {
+ element.title = control.title;
+ }
+ control.panel_div = element;
+ }
+
+ if (this.map) { // map.addControl() has already been called on the panel
+ this.addControlsToMap(controls);
+ this.redraw();
+ }
+ },
+
+ /**
+ * APIMethod: createControlMarkup
+ * This function just creates a div for the control. If specific HTML
+ * markup is needed this function can be overridden in specific classes,
+ * or at panel instantiation time:
+ *
+ * Example:
+ * (code)
+ * var panel = new OpenLayers.Control.Panel({
+ * defaultControl: control,
+ * // ovverride createControlMarkup to create actual buttons
+ * // including texts wrapped into span elements.
+ * createControlMarkup: function(control) {
+ * var button = document.createElement('button'),
+ * span = document.createElement('span');
+ * if (control.text) {
+ * span.innerHTML = control.text;
+ * }
+ * return button;
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to create the HTML
+ * markup for.
+ *
+ * Returns:
+ * {DOMElement} The markup.
+ */
+ createControlMarkup: function(control) {
+ return document.createElement("div");
+ },
+
+ /**
+ * Method: addControlsToMap
+ * Only for internal use in draw() and addControls() methods.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)} Controls to add into map.
+ */
+ addControlsToMap: function (controls) {
+ var control;
+ for (var i=0, len=controls.length; i<len; i++) {
+ control = controls[i];
+ if (control.autoActivate === true) {
+ control.autoActivate = false;
+ this.map.addControl(control);
+ control.autoActivate = true;
+ } else {
+ this.map.addControl(control);
+ control.deactivate();
+ }
+ control.events.on({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ },
+
+ /**
+ * Method: iconOn
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOn: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Inactive\\b");
+ d.className = d.className.replace(re, "$1Active");
+ },
+
+ /**
+ * Method: iconOff
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOff: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Active\\b");
+ d.className = d.className.replace(re, "$1Inactive");
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function (evt) {
+ var controls = this.controls,
+ button = evt.buttonElement;
+ for (var i=controls.length-1; i>=0; --i) {
+ if (controls[i].panel_div === button) {
+ this.activateControl(controls[i]);
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(control[property]) evaluates to true, the control will be
+ * included in the array returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this.controls, function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getControlsByName
+ * Get a list of contorls with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A control name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(control.name) evaluates to true, the control will be included
+ * in the list of controls returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByName: function(match) {
+ return this.getControlsBy("name", match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given type (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The type can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given type.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
+/* ======================================================================
+ OpenLayers/Strategy.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class. Not to be instantiated directly. Use
+ * one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The layer this strategy belongs to.
+ */
+ layer: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: active
+ * {Boolean} The control is active.
+ */
+ active: null,
+
+ /**
+ * Property: autoActivate
+ * {Boolean} The creator of the strategy can set autoActivate to false
+ * to fully control when the protocol is activated and deactivated.
+ * Defaults to true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the strategy can set autoDestroy to false
+ * to fully control when the strategy is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Constructor: OpenLayers.Strategy
+ * Abstract class for vector strategies. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ // set the active property here, so that user cannot override it
+ this.active = false;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the strategy.
+ */
+ destroy: function() {
+ this.deactivate();
+ this.layer = null;
+ this.options = null;
+ },
+
+ /**
+ * Method: setLayer
+ * Called to set the <layer> property.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layer) {
+ this.layer = layer;
+ },
+
+ /**
+ * Method: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ if (!this.active) {
+ this.active = true;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ if (this.active) {
+ this.active = false;
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy"
+});
+/* ======================================================================
+ OpenLayers/Strategy/Fixed.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: preload
+ * {Boolean} Load data before layer made visible. Enabling this may result
+ * in considerable overhead if your application loads many data layers
+ * that are not visible by default. Default is false.
+ */
+ preload: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Fixed
+ * Create a new Fixed strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Activate the strategy: load data or add listener to load when visible
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
+ if(activated) {
+ this.layer.events.on({
+ "refresh": this.load,
+ scope: this
+ });
+ if(this.layer.visibility == true || this.preload) {
+ this.load();
+ } else {
+ this.layer.events.on({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Undo what is done in <activate>.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "refresh": this.load,
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: load
+ * Tells protocol to load data and unhooks the visibilitychanged event
+ *
+ * Parameters:
+ * options - {Object} options to pass to protocol read.
+ */
+ load: function(options) {
+ var layer = this.layer;
+ layer.events.triggerEvent("loadstart", {filter: layer.filter});
+ layer.protocol.read(OpenLayers.Util.applyDefaults({
+ callback: this.merge,
+ filter: layer.filter,
+ scope: this
+ }, options));
+ layer.events.un({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: merge
+ * Add all features to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ var layer = this.layer;
+ layer.destroyFeatures();
+ var features = resp.features;
+ if (features && features.length > 0) {
+ var remote = layer.projection;
+ var local = layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ layer.addFeatures(features);
+ }
+ layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
+/* ======================================================================
+ OpenLayers/Control/Zoom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Zoom
+ * The Zoom control is a pair of +/- links for zooming in and out.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: zoomInText
+ * {String}
+ * Text for zoom-in link. Default is "+".
+ */
+ zoomInText: "+",
+
+ /**
+ * APIProperty: zoomInId
+ * {String}
+ * Instead of having the control create a zoom in link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomInLink" will be searched for
+ * and used if it exists.
+ */
+ zoomInId: "olZoomInLink",
+
+ /**
+ * APIProperty: zoomOutText
+ * {String}
+ * Text for zoom-out link. Default is "\u2212".
+ */
+ zoomOutText: "\u2212",
+
+ /**
+ * APIProperty: zoomOutId
+ * {String}
+ * Instead of having the control create a zoom out link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomOutLink" will be searched for
+ * and used if it exists.
+ */
+ zoomOutId: "olZoomOutLink",
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DOMElement containing the zoom links.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.prototype.draw.apply(this),
+ links = this.getOrCreateLinks(div),
+ zoomIn = links.zoomIn,
+ zoomOut = links.zoomOut,
+ eventsInstance = this.map.events;
+
+ if (zoomOut.parentNode !== div) {
+ eventsInstance = this.events;
+ eventsInstance.attachToElement(zoomOut.parentNode);
+ }
+ eventsInstance.register("buttonclick", this, this.onZoomClick);
+
+ this.zoomInLink = zoomIn;
+ this.zoomOutLink = zoomOut;
+ return div;
+ },
+
+ /**
+ * Method: getOrCreateLinks
+ *
+ * Parameters:
+ * el - {DOMElement}
+ *
+ * Return:
+ * {Object} Object with zoomIn and zoomOut properties referencing links.
+ */
+ getOrCreateLinks: function(el) {
+ var zoomIn = document.getElementById(this.zoomInId),
+ zoomOut = document.getElementById(this.zoomOutId);
+ if (!zoomIn) {
+ zoomIn = document.createElement("a");
+ zoomIn.href = "#zoomIn";
+ zoomIn.appendChild(document.createTextNode(this.zoomInText));
+ zoomIn.className = "olControlZoomIn";
+ el.appendChild(zoomIn);
+ }
+ OpenLayers.Element.addClass(zoomIn, "olButton");
+ if (!zoomOut) {
+ zoomOut = document.createElement("a");
+ zoomOut.href = "#zoomOut";
+ zoomOut.appendChild(document.createTextNode(this.zoomOutText));
+ zoomOut.className = "olControlZoomOut";
+ el.appendChild(zoomOut);
+ }
+ OpenLayers.Element.addClass(zoomOut, "olButton");
+ return {
+ zoomIn: zoomIn, zoomOut: zoomOut
+ };
+ },
+
+ /**
+ * Method: onZoomClick
+ * Called when zoomin/out link is clicked.
+ */
+ onZoomClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.zoomInLink) {
+ this.map.zoomIn();
+ } else if (button === this.zoomOutLink) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onZoomClick);
+ }
+ delete this.zoomInLink;
+ delete this.zoomOutLink;
+ OpenLayers.Control.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Zoom"
+});
+/* ======================================================================
+ OpenLayers/Protocol.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class. Not to be instantiated directly. Use
+ * one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} The format used by this protocol.
+ */
+ format: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the protocol can set autoDestroy to false
+ * to fully control when the protocol is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Property: defaultFilter
+ * {<OpenLayers.Filter>} Optional default filter to read requests
+ */
+ defaultFilter: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol
+ * Abstract class for vector protocols. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ options = options || {};
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * Method: mergeWithDefaultFilter
+ * Merge filter passed to the read method with the default one
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ mergeWithDefaultFilter: function(filter) {
+ var merged;
+ if (filter && this.defaultFilter) {
+ merged = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.defaultFilter, filter]
+ });
+ } else {
+ merged = filter || this.defaultFilter || undefined;
+ }
+ return merged;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.options = null;
+ this.format = null;
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ read: function(options) {
+ options = options || {};
+ options.filter = this.mergeWithDefaultFilter(options.filter);
+ },
+
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ create: function() {
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ update: function() {
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ "delete": function() {
+ },
+
+ /**
+ * APIMethod: commit
+ * Go over the features and for each take action
+ * based on the feature state. Possible actions are create,
+ * update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Object whose possible keys are "create", "update",
+ * "delete", "callback" and "scope", the values referenced by the
+ * first three are objects as passed to the "create", "update", and
+ * "delete" methods, the value referenced by the "callback" key is
+ * a function which is called when the commit operation is complete
+ * using the scope referenced by the "scope" key.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Protocol.Response>})} An array of
+ * <OpenLayers.Protocol.Response> objects.
+ */
+ commit: function() {
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ },
+
+ /**
+ * Method: createCallback
+ * Returns a function that applies the given public method with resp and
+ * options arguments.
+ *
+ * Parameters:
+ * method - {Function} The method to be applied by the callback.
+ * response - {<OpenLayers.Protocol.Response>} The protocol response object.
+ * options - {Object} Options sent to the protocol method
+ */
+ createCallback: function(method, response, options) {
+ return OpenLayers.Function.bind(function() {
+ method.apply(this, [response, options]);
+ }, this);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol"
+});
+
+/**
+ * Class: OpenLayers.Protocol.Response
+ * Protocols return Response objects to their users.
+ */
+OpenLayers.Protocol.Response = OpenLayers.Class({
+ /**
+ * Property: code
+ * {Number} - OpenLayers.Protocol.Response.SUCCESS or
+ * OpenLayers.Protocol.Response.FAILURE
+ */
+ code: null,
+
+ /**
+ * Property: requestType
+ * {String} The type of request this response corresponds to. Either
+ * "create", "read", "update" or "delete".
+ */
+ requestType: null,
+
+ /**
+ * Property: last
+ * {Boolean} - true if this is the last response expected in a commit,
+ * false otherwise, defaults to true.
+ */
+ last: true,
+
+ /**
+ * Property: features
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ features: null,
+
+ /**
+ * Property: data
+ * {Object}
+ * The data returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ data: null,
+
+ /**
+ * Property: reqFeatures
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features provided by the user and placed in the request by the
+ * protocol.
+ */
+ reqFeatures: null,
+
+ /**
+ * Property: priv
+ */
+ priv: null,
+
+ /**
+ * Property: error
+ * {Object} The error object in case a service exception was encountered.
+ */
+ error: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Response
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: success
+ *
+ * Returns:
+ * {Boolean} - true on success, false otherwise
+ */
+ success: function() {
+ return this.code > 0;
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
+/* ======================================================================
+ OpenLayers/Protocol/WFS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol>} A WFS protocol of the given version.
+ *
+ * Example:
+ * (code)
+ * var protocol = new OpenLayers.Protocol.WFS({
+ * version: "1.1.0",
+ * url: "http://demo.opengeo.org/geoserver/wfs",
+ * featureType: "tasmania_roads",
+ * featureNS: "http://www.openplans.org/topp",
+ * geometryName: "the_geom"
+ * });
+ * (end)
+ *
+ * See the protocols for specific WFS versions for more detail.
+ */
+OpenLayers.Protocol.WFS = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.WFS.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFS version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Function: fromWMSLayer
+ * Convenience function to create a WFS protocol from a WMS layer. This makes
+ * the assumption that a WFS requests can be issued at the same URL as
+ * WMS requests and that a WFS featureType exists with the same name as the
+ * WMS layer.
+ *
+ * This function is designed to auto-configure <url>, <featureType>,
+ * <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
+ * srsName matching with the WMS layer will not work with WFS 1.0.0.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} WMS layer that has a matching WFS
+ * FeatureType at the same server url with the same typename.
+ * options - {Object} Default properties to be set on the protocol.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.WFS>}
+ */
+OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
+ var typeName, featurePrefix;
+ var param = layer.params["LAYERS"];
+ var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
+ if(parts.length > 1) {
+ featurePrefix = parts[0];
+ }
+ typeName = parts.pop();
+ var protocolOptions = {
+ url: layer.url,
+ featureType: typeName,
+ featurePrefix: featurePrefix,
+ srsName: layer.projection && layer.projection.getCode() ||
+ layer.map && layer.map.getProjectionObject().getCode(),
+ version: "1.1.0"
+ };
+ return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
+ options, protocolOptions
+ ));
+};
+
+/**
+ * Constant: OpenLayers.Protocol.WFS.DEFAULTS
+ */
+OpenLayers.Protocol.WFS.DEFAULTS = {
+ "version": "1.0.0"
+};
+/* ======================================================================
+ OpenLayers/Request.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * TODO: deprecate me
+ * Use OpenLayers.Request.proxy instead.
+ */
+OpenLayers.ProxyHost = "";
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ * with XMLHttpRequests. These methods work with a cross-browser
+ * W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
+ */
+if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request/XMLHttpRequest.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+}
+OpenLayers.Util.extend(OpenLayers.Request, {
+
+ /**
+ * Constant: DEFAULT_CONFIG
+ * {Object} Default configuration for all requests.
+ */
+ DEFAULT_CONFIG: {
+ method: "GET",
+ url: window.location.href,
+ async: true,
+ user: undefined,
+ password: undefined,
+ params: null,
+ proxy: OpenLayers.ProxyHost,
+ headers: {},
+ data: null,
+ callback: function() {},
+ success: null,
+ failure: null,
+ scope: null
+ },
+
+ /**
+ * Constant: URL_SPLIT_REGEX
+ */
+ URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the {<OpenLayers.Request>} object.
+ *
+ * All event listeners will receive an event object with three properties:
+ * request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
+ * config - {Object} The config object sent to the specific request method.
+ * requestUrl - {String} The request url.
+ *
+ * Supported event types:
+ * complete - Triggered when we have a response from the request, if a
+ * listener returns false, no further response processing will take
+ * place.
+ * success - Triggered when the HTTP response has a success code (200-299).
+ * failure - Triggered when the HTTP response does not have a success code.
+ */
+ events: new OpenLayers.Events(this),
+
+ /**
+ * Method: makeSameOrigin
+ * Using the specified proxy, returns a same origin url of the provided url.
+ *
+ * Parameters:
+ * url - {String} An arbitrary url
+ * proxy {String|Function} The proxy to use to make the provided url a
+ * same origin url.
+ *
+ * Returns
+ * {String} the same origin url. If no proxy is provided, the returned url
+ * will be the same as the provided url.
+ */
+ makeSameOrigin: function(url, proxy) {
+ var sameOrigin = url.indexOf("http") !== 0;
+ var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
+ if (urlParts) {
+ var location = window.location;
+ sameOrigin =
+ urlParts[1] == location.protocol &&
+ urlParts[3] == location.hostname;
+ var uPort = urlParts[4], lPort = location.port;
+ if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
+ sameOrigin = sameOrigin && uPort == lPort;
+ }
+ }
+ if (!sameOrigin) {
+ if (proxy) {
+ if (typeof proxy == "function") {
+ url = proxy(url);
+ } else {
+ url = proxy + encodeURIComponent(url);
+ }
+ }
+ }
+ return url;
+ },
+
+ /**
+ * APIMethod: issue
+ * Create a new XMLHttpRequest object, open it, set any headers, bind
+ * a callback to done state, and send any data. It is recommended that
+ * you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
+ * This method is only documented to provide detail on the configuration
+ * options available to all request methods.
+ *
+ * Parameters:
+ * config - {Object} Object containing properties for configuring the
+ * request. Allowed configuration properties are described below.
+ * This object is modified and should not be reused.
+ *
+ * Allowed config properties:
+ * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
+ * OPTIONS. Default is GET.
+ * url - {String} URL for the request.
+ * async - {Boolean} Open an asynchronous request. Default is true.
+ * user - {String} User for relevant authentication scheme. Set
+ * to null to clear current user.
+ * password - {String} Password for relevant authentication scheme.
+ * Set to null to clear current password.
+ * proxy - {String} Optional proxy. Defaults to
+ * <OpenLayers.ProxyHost>.
+ * params - {Object} Any key:value pairs to be appended to the
+ * url as a query string. Assumes url doesn't already include a query
+ * string or hash. Typically, this is only appropriate for <GET>
+ * requests where the query string will be appended to the url.
+ * Parameter values that are arrays will be
+ * concatenated with a comma (note that this goes against form-encoding)
+ * as is done with <OpenLayers.Util.getParameterString>.
+ * headers - {Object} Object with header:value pairs to be set on
+ * the request.
+ * data - {String | Document} Optional data to send with the request.
+ * Typically, this is only used with <POST> and <PUT> requests.
+ * Make sure to provide the appropriate "Content-Type" header for your
+ * data. For <POST> and <PUT> requests, the content type defaults to
+ * "application-xml". If your data is a different content type, or
+ * if you are using a different HTTP method, set the "Content-Type"
+ * header to match your data type.
+ * callback - {Function} Function to call when request is done.
+ * To determine if the request failed, check request.status (200
+ * indicates success).
+ * success - {Function} Optional function to call if request status is in
+ * the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * failure - {Function} Optional function to call if request status is not
+ * in the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * scope - {Object} If callback is a public method on some object,
+ * set the scope to that object.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object. To abort the request before a response
+ * is received, call abort() on the request object.
+ */
+ issue: function(config) {
+ // apply default config - proxy host may have changed
+ var defaultConfig = OpenLayers.Util.extend(
+ this.DEFAULT_CONFIG,
+ {proxy: OpenLayers.ProxyHost}
+ );
+ config = config || {};
+ config.headers = config.headers || {};
+ config = OpenLayers.Util.applyDefaults(config, defaultConfig);
+ config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
+ // Always set the "X-Requested-With" header to signal that this request
+ // was issued through the XHR-object. Since header keys are case
+ // insensitive and we want to allow overriding of the "X-Requested-With"
+ // header through the user we cannot use applyDefaults, but have to
+ // check manually whether we were called with a "X-Requested-With"
+ // header.
+ var customRequestedWithHeader = false,
+ headerKey;
+ for(headerKey in config.headers) {
+ if (config.headers.hasOwnProperty( headerKey )) {
+ if (headerKey.toLowerCase() === 'x-requested-with') {
+ customRequestedWithHeader = true;
+ }
+ }
+ }
+ if (customRequestedWithHeader === false) {
+ // we did not have a custom "X-Requested-With" header
+ config.headers['X-Requested-With'] = 'XMLHttpRequest';
+ }
+
+ // create request, open, and set headers
+ var request = new OpenLayers.Request.XMLHttpRequest();
+ var url = OpenLayers.Util.urlAppend(config.url,
+ OpenLayers.Util.getParameterString(config.params || {}));
+ url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
+ request.open(
+ config.method, url, config.async, config.user, config.password
+ );
+ for(var header in config.headers) {
+ request.setRequestHeader(header, config.headers[header]);
+ }
+
+ var events = this.events;
+
+ // we want to execute runCallbacks with "this" as the
+ // execution scope
+ var self = this;
+
+ request.onreadystatechange = function() {
+ if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
+ var proceed = events.triggerEvent(
+ "complete",
+ {request: request, config: config, requestUrl: url}
+ );
+ if(proceed !== false) {
+ self.runCallbacks(
+ {request: request, config: config, requestUrl: url}
+ );
+ }
+ }
+ };
+
+ // send request (optionally with data) and return
+ // call in a timeout for asynchronous requests so the return is
+ // available before readyState == 4 for cached docs
+ if(config.async === false) {
+ request.send(config.data);
+ } else {
+ window.setTimeout(function(){
+ if (request.readyState !== 0) { // W3C: 0-UNSENT
+ request.send(config.data);
+ }
+ }, 0);
+ }
+ return request;
+ },
+
+ /**
+ * Method: runCallbacks
+ * Calls the complete, success and failure callbacks. Application
+ * can listen to the "complete" event, have the listener
+ * display a confirm window and always return false, and
+ * execute OpenLayers.Request.runCallbacks if the user
+ * hits "yes" in the confirm window.
+ *
+ * Parameters:
+ * options - {Object} Hash containing request, config and requestUrl keys
+ */
+ runCallbacks: function(options) {
+ var request = options.request;
+ var config = options.config;
+
+ // bind callbacks to readyState 4 (done)
+ var complete = (config.scope) ?
+ OpenLayers.Function.bind(config.callback, config.scope) :
+ config.callback;
+
+ // optional success callback
+ var success;
+ if(config.success) {
+ success = (config.scope) ?
+ OpenLayers.Function.bind(config.success, config.scope) :
+ config.success;
+ }
+
+ // optional failure callback
+ var failure;
+ if(config.failure) {
+ failure = (config.scope) ?
+ OpenLayers.Function.bind(config.failure, config.scope) :
+ config.failure;
+ }
+
+ if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
+ request.responseText) {
+ request.status = 200;
+ }
+ complete(request);
+
+ if (!request.status || (request.status >= 200 && request.status < 300)) {
+ this.events.triggerEvent("success", options);
+ if(success) {
+ success(request);
+ }
+ }
+ if(request.status && (request.status < 200 || request.status >= 300)) {
+ this.events.triggerEvent("failure", options);
+ if(failure) {
+ failure(request);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: GET
+ * Send an HTTP GET request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to GET.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ GET: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "GET"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: POST
+ * Send a POST request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to POST and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ POST: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "POST"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: PUT
+ * Send an HTTP PUT request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to PUT and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ PUT: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "PUT"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: DELETE
+ * Send an HTTP DELETE request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to DELETE.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ DELETE: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "DELETE"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: HEAD
+ * Send an HTTP HEAD request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to HEAD.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ HEAD: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "HEAD"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: OPTIONS
+ * Send an HTTP OPTIONS request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to OPTIONS.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ OPTIONS: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+ return OpenLayers.Request.issue(config);
+ }
+
+});
+/* ======================================================================
+ OpenLayers/Request/XMLHttpRequest.js
+ ====================================================================== */
+
+// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+ // Save reference to earlier defined object implementation (if any)
+ var oXMLHttpRequest = window.XMLHttpRequest;
+
+ // Define on browser type
+ var bGecko = !!window.controllers,
+ bIE = window.document.all && !window.opera,
+ bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
+
+ // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
+ function fXMLHttpRequest() {
+ this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
+ this._listeners = [];
+ };
+
+ // Constructor
+ function cXMLHttpRequest() {
+ return new fXMLHttpRequest;
+ };
+ cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
+
+ // BUGFIX: Firefox with Firebug installed would break pages if not executed
+ if (bGecko && oXMLHttpRequest.wrapped)
+ cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
+
+ // Constants
+ cXMLHttpRequest.UNSENT = 0;
+ cXMLHttpRequest.OPENED = 1;
+ cXMLHttpRequest.HEADERS_RECEIVED = 2;
+ cXMLHttpRequest.LOADING = 3;
+ cXMLHttpRequest.DONE = 4;
+
+ // Public Properties
+ cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
+ cXMLHttpRequest.prototype.responseText = '';
+ cXMLHttpRequest.prototype.responseXML = null;
+ cXMLHttpRequest.prototype.status = 0;
+ cXMLHttpRequest.prototype.statusText = '';
+
+ // Priority proposal
+ cXMLHttpRequest.prototype.priority = "NORMAL";
+
+ // Instance-level Events Handlers
+ cXMLHttpRequest.prototype.onreadystatechange = null;
+
+ // Class-level Events Handlers
+ cXMLHttpRequest.onreadystatechange = null;
+ cXMLHttpRequest.onopen = null;
+ cXMLHttpRequest.onsend = null;
+ cXMLHttpRequest.onabort = null;
+
+ // Public Methods
+ cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+ // Delete headers, required when object is reused
+ delete this._headers;
+
+ // When bAsync parameter value is omitted, use true as default
+ if (arguments.length < 3)
+ bAsync = true;
+
+ // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+ this._async = bAsync;
+
+ // Set the onreadystatechange handler
+ var oRequest = this,
+ nState = this.readyState,
+ fOnUnload;
+
+ // BUGFIX: IE - memory leak on page unload (inter-page leak)
+ if (bIE && bAsync) {
+ fOnUnload = function() {
+ if (nState != cXMLHttpRequest.DONE) {
+ fCleanTransport(oRequest);
+ // Safe to abort here since onreadystatechange handler removed
+ oRequest.abort();
+ }
+ };
+ window.attachEvent("onunload", fOnUnload);
+ }
+
+ // Add method sniffer
+ if (cXMLHttpRequest.onopen)
+ cXMLHttpRequest.onopen.apply(this, arguments);
+
+ if (arguments.length > 4)
+ this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ if (arguments.length > 3)
+ this._object.open(sMethod, sUrl, bAsync, sUser);
+ else
+ this._object.open(sMethod, sUrl, bAsync);
+
+ this.readyState = cXMLHttpRequest.OPENED;
+ fReadyStateChange(this);
+
+ this._object.onreadystatechange = function() {
+ if (bGecko && !bAsync)
+ return;
+
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ // BUGFIX: Firefox fires unnecessary DONE when aborting
+ if (oRequest._aborted) {
+ // Reset readyState to UNSENT
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return now
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Free up queue
+ delete oRequest._data;
+/* if (bAsync)
+ fQueue_remove(oRequest);*/
+ //
+ fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+ // BUGFIX: IE - cache issue
+ if (!oRequest._object.getResponseHeader("Date")) {
+ // Save object to cache
+ oRequest._cached = oRequest._object;
+
+ // Instantiate a new transport object
+ cXMLHttpRequest.call(oRequest);
+
+ // Re-send request
+ if (sUser) {
+ if (sPassword)
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser);
+ }
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync);
+ oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+ // Copy headers set
+ if (oRequest._headers)
+ for (var sHeader in oRequest._headers)
+ if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions
+ oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+ oRequest._object.onreadystatechange = function() {
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ if (oRequest._aborted) {
+ //
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Clean Object
+ fCleanTransport(oRequest);
+
+ // get cached request
+ if (oRequest.status == 304)
+ oRequest._object = oRequest._cached;
+
+ //
+ delete oRequest._cached;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ //
+ fReadyStateChange(oRequest);
+
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+ };
+ oRequest._object.send(null);
+
+ // Return now - wait until re-sent request is finished
+ return;
+ };
+*/
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+
+ // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+ if (nState != oRequest.readyState)
+ fReadyStateChange(oRequest);
+
+ nState = oRequest.readyState;
+ }
+ };
+ function fXMLHttpRequest_send(oRequest) {
+ oRequest._object.send(oRequest._data);
+
+ // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+ if (bGecko && !oRequest._async) {
+ oRequest.readyState = cXMLHttpRequest.OPENED;
+
+ // Synchronize state
+ fSynchronizeValues(oRequest);
+
+ // Simulate missing states
+ while (oRequest.readyState < cXMLHttpRequest.DONE) {
+ oRequest.readyState++;
+ fReadyStateChange(oRequest);
+ // Check if we are aborted
+ if (oRequest._aborted)
+ return;
+ }
+ }
+ };
+ cXMLHttpRequest.prototype.send = function(vData) {
+ // Add method sniffer
+ if (cXMLHttpRequest.onsend)
+ cXMLHttpRequest.onsend.apply(this, arguments);
+
+ if (!arguments.length)
+ vData = null;
+
+ // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+ // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+ // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+ if (vData && vData.nodeType) {
+ vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+ if (!this._headers["Content-Type"])
+ this._object.setRequestHeader("Content-Type", "application/xml");
+ }
+
+ this._data = vData;
+/*
+ // Add to queue
+ if (this._async)
+ fQueue_add(this);
+ else*/
+ fXMLHttpRequest_send(this);
+ };
+ cXMLHttpRequest.prototype.abort = function() {
+ // Add method sniffer
+ if (cXMLHttpRequest.onabort)
+ cXMLHttpRequest.onabort.apply(this, arguments);
+
+ // BUGFIX: Gecko - unnecessary DONE when aborting
+ if (this.readyState > cXMLHttpRequest.UNSENT)
+ this._aborted = true;
+
+ this._object.abort();
+
+ // BUGFIX: IE - memory leak
+ fCleanTransport(this);
+
+ this.readyState = cXMLHttpRequest.UNSENT;
+
+ delete this._data;
+/* if (this._async)
+ fQueue_remove(this);*/
+ };
+ cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
+ return this._object.getAllResponseHeaders();
+ };
+ cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
+ return this._object.getResponseHeader(sName);
+ };
+ cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
+ // BUGFIX: IE - cache issue
+ if (!this._headers)
+ this._headers = {};
+ this._headers[sName] = sValue;
+
+ return this._object.setRequestHeader(sName, sValue);
+ };
+
+ // EventTarget interface implementation
+ cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ return;
+ // Add listener
+ this._listeners.push([sName, fHandler, bUseCapture]);
+ };
+
+ cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ break;
+ // Remove listener
+ if (oListener)
+ this._listeners.splice(nIndex, 1);
+ };
+
+ cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
+ var oEventPseudo = {
+ 'type': oEvent.type,
+ 'target': this,
+ 'currentTarget':this,
+ 'eventPhase': 2,
+ 'bubbles': oEvent.bubbles,
+ 'cancelable': oEvent.cancelable,
+ 'timeStamp': oEvent.timeStamp,
+ 'stopPropagation': function() {}, // There is no flow
+ 'preventDefault': function() {}, // There is no default action
+ 'initEvent': function() {} // Original event object should be initialized
+ };
+
+ // Execute onreadystatechange
+ if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
+ (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
+
+ // Execute listeners
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == oEventPseudo.type && !oListener[2])
+ (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
+ };
+
+ //
+ cXMLHttpRequest.prototype.toString = function() {
+ return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+ };
+
+ cXMLHttpRequest.toString = function() {
+ return '[' + "XMLHttpRequest" + ']';
+ };
+
+ // Helper function
+ function fReadyStateChange(oRequest) {
+ // Sniffing code
+ if (cXMLHttpRequest.onreadystatechange)
+ cXMLHttpRequest.onreadystatechange.apply(oRequest);
+
+ // Fake event
+ oRequest.dispatchEvent({
+ 'type': "readystatechange",
+ 'bubbles': false,
+ 'cancelable': false,
+ 'timeStamp': new Date + 0
+ });
+ };
+
+ function fGetDocument(oRequest) {
+ var oDocument = oRequest.responseXML,
+ sResponse = oRequest.responseText;
+ // Try parsing responseText
+ if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+ oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
+ oDocument.async = false;
+ oDocument.validateOnParse = false;
+ oDocument.loadXML(sResponse);
+ }
+ // Check if there is no error in document
+ if (oDocument)
+ if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+ return null;
+ return oDocument;
+ };
+
+ function fSynchronizeValues(oRequest) {
+ try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
+ try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
+ try { oRequest.status = oRequest._object.status; } catch (e) {}
+ try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
+ };
+
+ function fCleanTransport(oRequest) {
+ // BUGFIX: IE - memory leak (on-page leak)
+ oRequest._object.onreadystatechange = new window.Function;
+ };
+/*
+ // Queue manager
+ var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
+ aQueueRunning = [];
+ function fQueue_add(oRequest) {
+ oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_remove(oRequest) {
+ for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
+ if (bFound)
+ aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
+ else
+ if (aQueueRunning[nIndex] == oRequest)
+ bFound = true;
+ if (bFound)
+ aQueueRunning.length--;
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_process() {
+ if (aQueueRunning.length < 6) {
+ for (var sPriority in oQueuePending) {
+ if (oQueuePending[sPriority].length) {
+ var oRequest = oQueuePending[sPriority][0];
+ oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
+ //
+ aQueueRunning.push(oRequest);
+ // Send request
+ fXMLHttpRequest_send(oRequest);
+ break;
+ }
+ }
+ }
+ };
+*/
+ // Internet Explorer 5.0 (missing apply)
+ if (!window.Function.prototype.apply) {
+ window.Function.prototype.apply = function(oRequest, oArguments) {
+ if (!oArguments)
+ oArguments = [];
+ oRequest.__func = this;
+ oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+ delete oRequest.__func;
+ };
+ };
+
+ // Register new object with window
+ /**
+ * Class: OpenLayers.Request.XMLHttpRequest
+ * Standard-compliant (W3C) cross-browser implementation of the
+ * XMLHttpRequest object. From
+ * http://code.google.com/p/xmlhttprequest/.
+ */
+ if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+ }
+ OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
+/* ======================================================================
+ OpenLayers/Format/KML.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Date.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.KML
+ * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ kml: "http://www.opengis.net/kml/2.2",
+ gx: "http://www.google.com/kml/ext/2.2"
+ },
+
+ /**
+ * APIProperty: kmlns
+ * {String} KML Namespace to use. Defaults to 2.0 namespace.
+ */
+ kmlns: "http://earth.google.com/kml/2.0",
+
+ /**
+ * APIProperty: placemarksDesc
+ * {String} Name of the placemarks. Default is "No description available".
+ */
+ placemarksDesc: "No description available",
+
+ /**
+ * APIProperty: foldersName
+ * {String} Name of the folders. Default is "OpenLayers export".
+ * If set to null, no name element will be created.
+ */
+ foldersName: "OpenLayers export",
+
+ /**
+ * APIProperty: foldersDesc
+ * {String} Description of the folders. Default is "Exported on [date]."
+ * If set to null, no description element will be created.
+ */
+ foldersDesc: "Exported on " + new Date(),
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from KML. Default is true.
+ * Extracting styleUrls requires this to be set to true
+ * Note that currently only Data and SimpleData
+ * elements are handled.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: kvpAttributes
+ * {Boolean} Only used if extractAttributes is true.
+ * If set to true, attributes will be simple
+ * key-value pairs, compatible with other formats,
+ * Any displayName elements will be ignored.
+ * If set to false, attributes will be objects,
+ * retaining any displayName elements, but not
+ * compatible with other formats. Any CDATA in
+ * displayName will be read in as a string value.
+ * Default is false.
+ */
+ kvpAttributes: false,
+
+ /**
+ * Property: extractStyles
+ * {Boolean} Extract styles from KML. Default is false.
+ * Extracting styleUrls also requires extractAttributes to be
+ * set to true
+ */
+ extractStyles: false,
+
+ /**
+ * APIProperty: extractTracks
+ * {Boolean} Extract gx:Track elements from Placemark elements. Default
+ * is false. If true, features will be generated for all points in
+ * all gx:Track elements. Features will have a when (Date) attribute
+ * based on when elements in the track. If tracks include angle
+ * elements, features will have heading, tilt, and roll attributes.
+ * If track point coordinates have three values, features will have
+ * an altitude attribute with the third coordinate value.
+ */
+ extractTracks: false,
+
+ /**
+ * APIProperty: trackAttributes
+ * {Array} If <extractTracks> is true, points within gx:Track elements will
+ * be parsed as features with when, heading, tilt, and roll attributes.
+ * Any additional attribute names can be provided in <trackAttributes>.
+ */
+ trackAttributes: null,
+
+ /**
+ * Property: internalns
+ * {String} KML Namespace to use -- defaults to the namespace of the
+ * Placemark node being parsed, but falls back to kmlns.
+ */
+ internalns: null,
+
+ /**
+ * Property: features
+ * {Array} Array of features
+ *
+ */
+ features: null,
+
+ /**
+ * Property: styles
+ * {Object} Storage of style objects
+ *
+ */
+ styles: null,
+
+ /**
+ * Property: styleBaseUrl
+ * {String}
+ */
+ styleBaseUrl: "",
+
+ /**
+ * Property: fetched
+ * {Object} Storage of KML URLs that have been fetched before
+ * in order to prevent reloading them.
+ */
+ fetched: null,
+
+ /**
+ * APIProperty: maxDepth
+ * {Integer} Maximum depth for recursive loading external KML URLs
+ * Defaults to 0: do no external fetching
+ */
+ maxDepth: 0,
+
+ /**
+ * Constructor: OpenLayers.Format.KML
+ * Create a new parser for KML.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // compile regular expressions once instead of every time they are used
+ this.regExes = {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g),
+ kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
+ kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
+ straightBracket: (/\$\[(.*?)\]/g)
+ };
+ // KML coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features.
+ */
+ read: function(data) {
+ this.features = [];
+ this.styles = {};
+ this.fetched = {};
+
+ // Set default options
+ var options = {
+ depth: 0,
+ styleBaseUrl: this.styleBaseUrl
+ };
+
+ return this.parseData(data, options);
+ },
+
+ /**
+ * Method: parseData
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features.
+ */
+ parseData: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+
+ // Loop throught the following node types in this order and
+ // process the nodes found
+ var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
+ for(var i=0, len=types.length; i<len; ++i) {
+ var type = types[i];
+
+ var nodes = this.getElementsByTagNameNS(data, "*", type);
+
+ // skip to next type if no nodes are found
+ if(nodes.length == 0) {
+ continue;
+ }
+
+ switch (type.toLowerCase()) {
+
+ // Fetch external links
+ case "link":
+ case "networklink":
+ this.parseLinks(nodes, options);
+ break;
+
+ // parse style information
+ case "style":
+ if (this.extractStyles) {
+ this.parseStyles(nodes, options);
+ }
+ break;
+ case "stylemap":
+ if (this.extractStyles) {
+ this.parseStyleMaps(nodes, options);
+ }
+ break;
+
+ // parse features
+ case "placemark":
+ this.parseFeatures(nodes, options);
+ break;
+ }
+ }
+
+ return this.features;
+ },
+
+ /**
+ * Method: parseLinks
+ * Finds URLs of linked KML documents and fetches them
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseLinks: function(nodes, options) {
+
+ // Fetch external links <NetworkLink> and <Link>
+ // Don't do anything if we have reached our maximum depth for recursion
+ if (options.depth >= this.maxDepth) {
+ return false;
+ }
+
+ // increase depth
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var href = this.parseProperty(nodes[i], "*", "href");
+ if(href && !this.fetched[href]) {
+ this.fetched[href] = true; // prevent reloading the same urls
+ var data = this.fetchLink(href);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Method: fetchLink
+ * Fetches a URL and returns the result
+ *
+ * Parameters:
+ * href - {String} url to be fetched
+ *
+ */
+ fetchLink: function(href) {
+ var request = OpenLayers.Request.GET({url: href, async: false});
+ if (request) {
+ return request.responseText;
+ }
+ },
+
+ /**
+ * Method: parseStyles
+ * Parses <Style> nodes
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyles: function(nodes, options) {
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var style = this.parseStyle(nodes[i]);
+ if(style) {
+ var styleName = (options.styleBaseUrl || "") + "#" + style.id;
+
+ this.styles[styleName] = style;
+ }
+ }
+ },
+
+ /**
+ * Method: parseKmlColor
+ * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
+ * color and opacity or null if the color is invalid.
+ *
+ * Parameters:
+ * kmlColor - {String} a kml formated color
+ *
+ * Returns:
+ * {Object}
+ */
+ parseKmlColor: function(kmlColor) {
+ var color = null;
+ if (kmlColor) {
+ var matches = kmlColor.match(this.regExes.kmlColor);
+ if (matches) {
+ color = {
+ color: '#' + matches[4] + matches[3] + matches[2],
+ opacity: parseInt(matches[1], 16) / 255
+ };
+ }
+ }
+ return color;
+ },
+
+ /**
+ * Method: parseStyle
+ * Parses the children of a <Style> node and builds the style hash
+ * accordingly
+ *
+ * Parameters:
+ * node - {DOMElement} <Style> node
+ *
+ */
+ parseStyle: function(node) {
+ var style = {};
+
+ var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle",
+ "LabelStyle"];
+ var type, styleTypeNode, nodeList, geometry, parser;
+ for(var i=0, len=types.length; i<len; ++i) {
+ type = types[i];
+ styleTypeNode = this.getElementsByTagNameNS(node, "*", type)[0];
+ if(!styleTypeNode) {
+ continue;
+ }
+
+ // only deal with first geometry of this type
+ switch (type.toLowerCase()) {
+ case "linestyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["strokeColor"] = color.color;
+ style["strokeOpacity"] = color.opacity;
+ }
+
+ var width = this.parseProperty(styleTypeNode, "*", "width");
+ if (width) {
+ style["strokeWidth"] = width;
+ }
+ break;
+
+ case "polystyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fillOpacity"] = color.opacity;
+ style["fillColor"] = color.color;
+ }
+ // Check if fill is disabled
+ var fill = this.parseProperty(styleTypeNode, "*", "fill");
+ if (fill == "0") {
+ style["fillColor"] = "none";
+ }
+ // Check if outline is disabled
+ var outline = this.parseProperty(styleTypeNode, "*", "outline");
+ if (outline == "0") {
+ style["strokeWidth"] = "0";
+ }
+
+ break;
+
+ case "iconstyle":
+ // set scale
+ var scale = parseFloat(this.parseProperty(styleTypeNode,
+ "*", "scale") || 1);
+
+ // set default width and height of icon
+ var width = 32 * scale;
+ var height = 32 * scale;
+
+ var iconNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "Icon")[0];
+ if (iconNode) {
+ var href = this.parseProperty(iconNode, "*", "href");
+ if (href) {
+
+ var w = this.parseProperty(iconNode, "*", "w");
+ var h = this.parseProperty(iconNode, "*", "h");
+
+ // Settings for Google specific icons that are 64x64
+ // We set the width and height to 64 and halve the
+ // scale to prevent icons from being too big
+ var google = "http://maps.google.com/mapfiles/kml";
+ if (OpenLayers.String.startsWith(
+ href, google) && !w && !h) {
+ w = 64;
+ h = 64;
+ scale = scale / 2;
+ }
+
+ // if only dimension is defined, make sure the
+ // other one has the same value
+ w = w || h;
+ h = h || w;
+
+ if (w) {
+ width = parseInt(w) * scale;
+ }
+
+ if (h) {
+ height = parseInt(h) * scale;
+ }
+
+ // support for internal icons
+ // (/root://icons/palette-x.png)
+ // x and y tell the position on the palette:
+ // - in pixels
+ // - starting from the left bottom
+ // We translate that to a position in the list
+ // and request the appropriate icon from the
+ // google maps website
+ var matches = href.match(this.regExes.kmlIconPalette);
+ if (matches) {
+ var palette = matches[1];
+ var file_extension = matches[2];
+
+ var x = this.parseProperty(iconNode, "*", "x");
+ var y = this.parseProperty(iconNode, "*", "y");
+
+ var posX = x ? x/32 : 0;
+ var posY = y ? (7 - y/32) : 7;
+
+ var pos = posY * 8 + posX;
+ href = "http://maps.google.com/mapfiles/kml/pal"
+ + palette + "/icon" + pos + file_extension;
+ }
+
+ style["graphicOpacity"] = 1; // fully opaque
+ style["externalGraphic"] = href;
+ }
+
+ }
+
+
+ // hotSpots define the offset for an Icon
+ var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "hotSpot")[0];
+ if (hotSpotNode) {
+ var x = parseFloat(hotSpotNode.getAttribute("x"));
+ var y = parseFloat(hotSpotNode.getAttribute("y"));
+
+ var xUnits = hotSpotNode.getAttribute("xunits");
+ if (xUnits == "pixels") {
+ style["graphicXOffset"] = -x * scale;
+ }
+ else if (xUnits == "insetPixels") {
+ style["graphicXOffset"] = -width + (x * scale);
+ }
+ else if (xUnits == "fraction") {
+ style["graphicXOffset"] = -width * x;
+ }
+
+ var yUnits = hotSpotNode.getAttribute("yunits");
+ if (yUnits == "pixels") {
+ style["graphicYOffset"] = -height + (y * scale) + 1;
+ }
+ else if (yUnits == "insetPixels") {
+ style["graphicYOffset"] = -(y * scale) + 1;
+ }
+ else if (yUnits == "fraction") {
+ style["graphicYOffset"] = -height * (1 - y) + 1;
+ }
+ }
+
+ style["graphicWidth"] = width;
+ style["graphicHeight"] = height;
+ break;
+
+ case "balloonstyle":
+ var balloonStyle = OpenLayers.Util.getXmlNodeValue(
+ styleTypeNode);
+ if (balloonStyle) {
+ style["balloonStyle"] = balloonStyle.replace(
+ this.regExes.straightBracket, "${$1}");
+ }
+ break;
+ case "labelstyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fontColor"] = color.color;
+ style["fontOpacity"] = color.opacity;
+ }
+ break;
+
+ default:
+ }
+ }
+
+ // Some polygons have no line color, so we use the fillColor for that
+ if (!style["strokeColor"] && style["fillColor"]) {
+ style["strokeColor"] = style["fillColor"];
+ }
+
+ var id = node.getAttribute("id");
+ if (id && style) {
+ style.id = id;
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: parseStyleMaps
+ * Parses <StyleMap> nodes, but only uses the 'normal' key
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyleMaps: function(nodes, options) {
+ // Only the default or "normal" part of the StyleMap is processed now
+ // To do the select or "highlight" bit, we'd need to change lots more
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var node = nodes[i];
+ var pairs = this.getElementsByTagNameNS(node, "*",
+ "Pair");
+
+ var id = node.getAttribute("id");
+ for (var j=0, jlen=pairs.length; j<jlen; j++) {
+ var pair = pairs[j];
+ // Use the shortcut in the SLD format to quickly retrieve the
+ // value of a node. Maybe it's good to have a method in
+ // Format.XML to do this
+ var key = this.parseProperty(pair, "*", "key");
+ var styleUrl = this.parseProperty(pair, "*", "styleUrl");
+
+ if (styleUrl && key == "normal") {
+ this.styles[(options.styleBaseUrl || "") + "#" + id] =
+ this.styles[(options.styleBaseUrl || "") + styleUrl];
+ }
+
+ // TODO: implement the "select" part
+ //if (styleUrl && key == "highlight") {
+ //}
+
+ }
+ }
+
+ },
+
+
+ /**
+ * Method: parseFeatures
+ * Loop through all Placemark nodes and parse them.
+ * Will create a list of features
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseFeatures: function(nodes, options) {
+ var features = [];
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var featureNode = nodes[i];
+ var feature = this.parseFeature.apply(this,[featureNode]) ;
+ if(feature) {
+
+ // Create reference to styleUrl
+ if (this.extractStyles && feature.attributes &&
+ feature.attributes.styleUrl) {
+ feature.style = this.getStyle(feature.attributes.styleUrl, options);
+ }
+
+ if (this.extractStyles) {
+ // Make sure that <Style> nodes within a placemark are
+ // processed as well
+ var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
+ "*",
+ "Style")[0];
+ if (inlineStyleNode) {
+ var inlineStyle= this.parseStyle(inlineStyleNode);
+ if (inlineStyle) {
+ feature.style = OpenLayers.Util.extend(
+ feature.style, inlineStyle
+ );
+ }
+ }
+ }
+
+ // check if gx:Track elements should be parsed
+ if (this.extractTracks) {
+ var tracks = this.getElementsByTagNameNS(
+ featureNode, this.namespaces.gx, "Track"
+ );
+ if (tracks && tracks.length > 0) {
+ var track = tracks[0];
+ var container = {
+ features: [],
+ feature: feature
+ };
+ this.readNode(track, container);
+ if (container.features.length > 0) {
+ features.push.apply(features, container.features);
+ }
+ }
+ } else {
+ // add feature to list of features
+ features.push(feature);
+ }
+ } else {
+ throw "Bad Placemark: " + i;
+ }
+ }
+
+ // add new features to existing feature list
+ this.features = this.features.concat(features);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "kml": {
+ "when": function(node, container) {
+ container.whens.push(OpenLayers.Date.parse(
+ this.getChildValue(node)
+ ));
+ },
+ "_trackPointAttribute": function(node, container) {
+ var name = node.nodeName.split(":").pop();
+ container.attributes[name].push(this.getChildValue(node));
+ }
+ },
+ "gx": {
+ "Track": function(node, container) {
+ var obj = {
+ whens: [],
+ points: [],
+ angles: []
+ };
+ if (this.trackAttributes) {
+ var name;
+ obj.attributes = {};
+ for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) {
+ name = this.trackAttributes[i];
+ obj.attributes[name] = [];
+ if (!(name in this.readers.kml)) {
+ this.readers.kml[name] = this.readers.kml._trackPointAttribute;
+ }
+ }
+ }
+ this.readChildNodes(node, obj);
+ if (obj.whens.length !== obj.points.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:coord (" +
+ obj.points.length + ") elements.");
+ }
+ var hasAngles = obj.angles.length > 0;
+ if (hasAngles && obj.whens.length !== obj.angles.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:angles (" +
+ obj.angles.length + ") elements.");
+ }
+ var feature, point, angles;
+ for (var i=0, ii=obj.whens.length; i<ii; ++i) {
+ feature = container.feature.clone();
+ feature.fid = container.feature.fid || container.feature.id;
+ point = obj.points[i];
+ feature.geometry = point;
+ if ("z" in point) {
+ feature.attributes.altitude = point.z;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if (this.trackAttributes) {
+ for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) {
+ var name = this.trackAttributes[j];
+ feature.attributes[name] = obj.attributes[name][i];
+ }
+ }
+ feature.attributes.when = obj.whens[i];
+ feature.attributes.trackId = container.feature.id;
+ if (hasAngles) {
+ angles = obj.angles[i];
+ feature.attributes.heading = parseFloat(angles[0]);
+ feature.attributes.tilt = parseFloat(angles[1]);
+ feature.attributes.roll = parseFloat(angles[2]);
+ }
+ container.features.push(feature);
+ }
+ },
+ "coord": function(node, container) {
+ var str = this.getChildValue(node);
+ var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ var point = new OpenLayers.Geometry.Point(coords[0], coords[1]);
+ if (coords.length > 2) {
+ point.z = parseFloat(coords[2]);
+ }
+ container.points.push(point);
+ },
+ "angles": function(node, container) {
+ var str = this.getChildValue(node);
+ var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ container.angles.push(parts);
+ }
+ }
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the KML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A vector feature.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+ var type, nodeList, geometry, parser;
+ for(var i=0, len=order.length; i<len; ++i) {
+ type = order[i];
+ this.internalns = node.namespaceURI ?
+ node.namespaceURI : this.kmlns;
+ nodeList = this.getElementsByTagNameNS(node,
+ this.internalns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ geometry = parser.apply(this, [nodeList[0]]);
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ } else {
+ throw new TypeError("Unsupported geometry type: " + type);
+ }
+ // stop looking for different geometry types
+ break;
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+
+ var fid = node.getAttribute("id") || node.getAttribute("name");
+ if(fid != null) {
+ feature.fid = fid;
+ }
+
+ return feature;
+ },
+
+ /**
+ * Method: getStyle
+ * Retrieves a style from a style hash using styleUrl as the key
+ * If the styleUrl doesn't exist yet, we try to fetch it
+ * Internet
+ *
+ * Parameters:
+ * styleUrl - {String} URL of style
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Object} - (reference to) Style hash
+ */
+ getStyle: function(styleUrl, options) {
+
+ var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
+
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+ newOptions.styleBaseUrl = styleBaseUrl;
+
+ // Fetch remote Style URLs (if not fetched before)
+ if (!this.styles[styleUrl]
+ && !OpenLayers.String.startsWith(styleUrl, "#")
+ && newOptions.depth <= this.maxDepth
+ && !this.fetched[styleBaseUrl] ) {
+
+ var data = this.fetchLink(styleBaseUrl);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+
+ }
+
+ // return requested style
+ var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
+ return style;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a KML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Point node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var coords = [];
+ if(nodeList.length > 0) {
+ var coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace, "");
+ coords = coordString.split(",");
+ }
+
+ var point = null;
+ if(coords.length > 1) {
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ } else {
+ throw "Bad coordinate string: " + coordString;
+ }
+ return point;
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a KML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML LineString node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var line = null;
+ if(nodeList.length > 0) {
+ var coordString = this.getChildValue(nodeList[0]);
+
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ var coords, numCoords;
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ numCoords = coords.length;
+ if(numCoords > 1) {
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ points[i] = new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]);
+ } else {
+ throw "Bad LineString point coordinates: " +
+ pointList[i];
+ }
+ }
+ if(numPoints) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ } else {
+ throw "Bad LineString coordinates: " + coordString;
+ }
+ }
+
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a KML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Polygon node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "LinearRing");
+ var numRings = nodeList.length;
+ var components = new Array(numRings);
+ if(numRings > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0, len=nodeList.length; i<len; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components[i] = ring;
+ } else {
+ throw "Bad LinearRing geometry: " + i;
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multigeometry
+ * Given a KML node representing a multigeometry, create an
+ * OpenLayers geometry collection.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML MultiGeometry node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} A geometry collection.
+ */
+ multigeometry: function(node) {
+ var child, parser;
+ var parts = [];
+ var children = node.childNodes;
+ for(var i=0, len=children.length; i<len; ++i ) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ var type = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ parts.push(parser.apply(this, [child]));
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Collection(parts);
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+
+ // Extended Data is parsed first.
+ var edNodes = node.getElementsByTagName("ExtendedData");
+ if (edNodes.length) {
+ attributes = this.parseExtendedData(edNodes[0]);
+ }
+
+ // assume attribute nodes are type 1 children with a type 3 or 4 child
+ var child, grandchildren, grandchild;
+ var children = node.childNodes;
+
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length >= 1 && grandchildren.length <= 3) {
+ var grandchild;
+ switch (grandchildren.length) {
+ case 1:
+ grandchild = grandchildren[0];
+ break;
+ case 2:
+ var c1 = grandchildren[0];
+ var c2 = grandchildren[1];
+ grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ?
+ c1 : c2;
+ break;
+ case 3:
+ default:
+ grandchild = grandchildren[1];
+ break;
+ }
+ if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+ var name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var value = OpenLayers.Util.getXmlNodeValue(grandchild);
+ if (value) {
+ value = value.replace(this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ }
+ }
+ }
+ }
+ return attributes;
+ },
+
+ /**
+ * Method: parseExtendedData
+ * Parse ExtendedData from KML. Limited support for schemas/datatypes.
+ * See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
+ * for more information on extendeddata.
+ */
+ parseExtendedData: function(node) {
+ var attributes = {};
+ var i, len, data, key;
+ var dataNodes = node.getElementsByTagName("Data");
+ for (i = 0, len = dataNodes.length; i < len; i++) {
+ data = dataNodes[i];
+ key = data.getAttribute("name");
+ var ed = {};
+ var valueNode = data.getElementsByTagName("value");
+ if (valueNode.length) {
+ ed['value'] = this.getChildValue(valueNode[0]);
+ }
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ var nameNode = data.getElementsByTagName("displayName");
+ if (nameNode.length) {
+ ed['displayName'] = this.getChildValue(nameNode[0]);
+ }
+ attributes[key] = ed;
+ }
+ }
+ var simpleDataNodes = node.getElementsByTagName("SimpleData");
+ for (i = 0, len = simpleDataNodes.length; i < len; i++) {
+ var ed = {};
+ data = simpleDataNodes[i];
+ key = data.getAttribute("name");
+ ed['value'] = this.getChildValue(data);
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ ed['displayName'] = key;
+ attributes[key] = ed;
+ }
+ }
+
+ return attributes;
+ },
+
+ /**
+ * Method: parseProperty
+ * Convenience method to find a node and return its value
+ *
+ * Parameters:
+ * xmlNode - {<DOMElement>}
+ * namespace - {String} namespace of the node to find
+ * tagName - {String} name of the property to parse
+ *
+ * Returns:
+ * {String} The value for the requested property (defaults to null)
+ */
+ parseProperty: function(xmlNode, namespace, tagName) {
+ var value;
+ var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
+ try {
+ value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
+ } catch(e) {
+ value = null;
+ }
+
+ return value;
+ },
+
+ /**
+ * APIMethod: write
+ * Accept Feature Collection, and return a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ *
+ * Returns:
+ * {String} A KML string.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var kml = this.createElementNS(this.kmlns, "kml");
+ var folder = this.createFolderXML();
+ for(var i=0, len=features.length; i<len; ++i) {
+ folder.appendChild(this.createPlacemarkXML(features[i]));
+ }
+ kml.appendChild(folder);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+ },
+
+ /**
+ * Method: createFolderXML
+ * Creates and returns a KML folder node
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createFolderXML: function() {
+ // Folder
+ var folder = this.createElementNS(this.kmlns, "Folder");
+
+ // Folder name
+ if (this.foldersName) {
+ var folderName = this.createElementNS(this.kmlns, "name");
+ var folderNameText = this.createTextNode(this.foldersName);
+ folderName.appendChild(folderNameText);
+ folder.appendChild(folderName);
+ }
+
+ // Folder description
+ if (this.foldersDesc) {
+ var folderDesc = this.createElementNS(this.kmlns, "description");
+ var folderDescText = this.createTextNode(this.foldersDesc);
+ folderDesc.appendChild(folderDescText);
+ folder.appendChild(folderDesc);
+ }
+
+ return folder;
+ },
+
+ /**
+ * Method: createPlacemarkXML
+ * Creates and returns a KML placemark node representing the given feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createPlacemarkXML: function(feature) {
+ // Placemark name
+ var placemarkName = this.createElementNS(this.kmlns, "name");
+ var label = (feature.style && feature.style.label) ? feature.style.label : feature.id;
+ var name = feature.attributes.name || label;
+ placemarkName.appendChild(this.createTextNode(name));
+
+ // Placemark description
+ var placemarkDesc = this.createElementNS(this.kmlns, "description");
+ var desc = feature.attributes.description || this.placemarksDesc;
+ placemarkDesc.appendChild(this.createTextNode(desc));
+
+ // Placemark
+ var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+ if(feature.fid != null) {
+ placemarkNode.setAttribute("id", feature.fid);
+ }
+ placemarkNode.appendChild(placemarkName);
+ placemarkNode.appendChild(placemarkDesc);
+
+ // Geometry node (Point, LineString, etc. nodes)
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ placemarkNode.appendChild(geometryNode);
+
+ // output attributes as extendedData
+ if (feature.attributes) {
+ var edNode = this.buildExtendedData(feature.attributes);
+ if (edNode) {
+ placemarkNode.appendChild(edNode);
+ }
+ }
+
+ return placemarkNode;
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * Builds and returns a KML geometry node with the given geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildGeometryNode: function(geometry) {
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ var node = null;
+ if(builder) {
+ node = builder.apply(this, [geometry]);
+ }
+ return node;
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD: Anybody care about namespace aliases here (these nodes have
+ // no prefixes)?
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a KML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML point node.
+ */
+ point: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Point");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipoint: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a KML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linestring node.
+ */
+ linestring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LineString");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multilinestring: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a KML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linearring node.
+ */
+ linearring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LinearRing");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a KML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML polygon node.
+ */
+ polygon: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0, len=rings.length; i<len; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.kmlns, type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ kml.appendChild(ringMember);
+ }
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipolygon: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.collection
+ * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+ *
+ * Returns:
+ * {DOMElement} A KML MultiGeometry node.
+ */
+ collection: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+ var child;
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ child = this.buildGeometryNode.apply(this,
+ [geometry.components[i]]);
+ if(child) {
+ kml.appendChild(child);
+ }
+ }
+ return kml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinatesNode
+ * Builds and returns the KML coordinates node with the given geometry
+ * <coordinates>...</coordinates>
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+
+ var path;
+ var points = geometry.components;
+ if(points) {
+ // LineString or LinearRing
+ var point;
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ parts[i] = this.buildCoordinates(point);
+ }
+ path = parts.join(" ");
+ } else {
+ // Point
+ path = this.buildCoordinates(geometry);
+ }
+
+ var txtNode = this.createTextNode(path);
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ /**
+ * Method: buildCoordinates
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns
+ * {String} a coordinate pair
+ */
+ buildCoordinates: function(point) {
+ if (this.internalProjection && this.externalProjection) {
+ point = point.clone();
+ point.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ return point.x + "," + point.y;
+ },
+
+ /**
+ * Method: buildExtendedData
+ *
+ * Parameters:
+ * attributes - {Object}
+ *
+ * Returns
+ * {DOMElement} A KML ExtendedData node or {null} if no attributes.
+ */
+ buildExtendedData: function(attributes) {
+ var extendedData = this.createElementNS(this.kmlns, "ExtendedData");
+ for (var attributeName in attributes) {
+ // empty, name, description, styleUrl attributes ignored
+ if (attributes[attributeName] && attributeName != "name" && attributeName != "description" && attributeName != "styleUrl") {
+ var data = this.createElementNS(this.kmlns, "Data");
+ data.setAttribute("name", attributeName);
+ var value = this.createElementNS(this.kmlns, "value");
+ if (typeof attributes[attributeName] == "object") {
+ // cater for object attributes with 'value' properties
+ // other object properties will output an empty node
+ if (attributes[attributeName].value) {
+ value.appendChild(this.createTextNode(attributes[attributeName].value));
+ }
+ if (attributes[attributeName].displayName) {
+ var displayName = this.createElementNS(this.kmlns, "displayName");
+ // displayName always written as CDATA
+ displayName.appendChild(this.getXMLDoc().createCDATASection(attributes[attributeName].displayName));
+ data.appendChild(displayName);
+ }
+ } else {
+ value.appendChild(this.createTextNode(attributes[attributeName]));
+ }
+ data.appendChild(value);
+ extendedData.appendChild(data);
+ }
+ }
+ if (this.isSimpleContent(extendedData)) {
+ return null;
+ } else {
+ return extendedData;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.KML"
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/v1.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1
+ * Abstract class for for v1.0.0 and v1.1.0 protocol.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: srsName
+ * {String} Name of spatial reference system. Default is "EPSG:4326".
+ */
+ srsName: "EPSG:4326",
+
+ /**
+ * Property: featureType
+ * {String} Local feature typeName.
+ */
+ featureType: null,
+
+ /**
+ * Property: featureNS
+ * {String} Feature namespace.
+ */
+ featureNS: null,
+
+ /**
+ * Property: geometryName
+ * {String} Name of the geometry attribute for features. Default is
+ * "the_geom" for WFS <version> 1.0, and null for higher versions.
+ */
+ geometryName: "the_geom",
+
+ /**
+ * Property: maxFeatures
+ * {Integer} Optional maximum number of features to retrieve.
+ */
+
+ /**
+ * Property: schema
+ * {String} Optional schema location that will be included in the
+ * schemaLocation attribute value. Note that the feature type schema
+ * is required for a strict XML validator (on transactions with an
+ * insert for example), but is *not* required by the WFS specification
+ * (since the server is supposed to know about feature type schemas).
+ */
+ schema: null,
+
+ /**
+ * Property: featurePrefix
+ * {String} Namespace alias for feature type. Default is "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: readFormat
+ * {<OpenLayers.Format>} For WFS requests it is possible to get a
+ * different output format than GML. In that case, we cannot parse
+ * the response with the default format (WFST) and we need a different
+ * format for reading.
+ */
+ readFormat: null,
+
+ /**
+ * Property: readOptions
+ * {Object} Optional object to pass to format's read.
+ */
+ readOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS
+ * A class for giving layers WFS protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * url - {String} URL to send requests to (required).
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required, but can be autodetected
+ * during the first query if GML is used as readFormat and
+ * featurePrefix is provided and matches the prefix used by the server
+ * for this featureType).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * for writing if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. The default is
+ * 'the_geom' for WFS <version> 1.0, and null for higher versions. If
+ * null, it will be set to the name of the first geometry found in the
+ * first read operation.
+ * multi - {Boolean} If set to true, geometries will be casted to Multi
+ * geometries before they are written in a transaction. No casting will
+ * be done when reading features.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
+ version: this.version,
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ geometryName: this.geometryName,
+ srsName: this.srsName,
+ schema: this.schema
+ }, this.formatOptions));
+ }
+ if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
+ this.setGeometryName(null);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features. Since WFS splits the
+ * basic CRUD operations into GetFeature requests (for read) and
+ * Transactions (for all others), this method does not make use of the
+ * format's read method (that is only about reading transaction
+ * responses).
+ *
+ * Parameters:
+ * options - {Object} Options for the read operation, in addition to the
+ * options set on the instance (options set here will take precedence).
+ *
+ * To use a configured protocol to get e.g. a WFS hit count, applications
+ * could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * readOptions: {output: "object"},
+ * resultType: "hits",
+ * maxFeatures: null,
+ * callback: function(resp) {
+ * // process resp.numberOfFeatures here
+ * }
+ * });
+ * (end)
+ *
+ * To use a configured protocol to use WFS paging (if supported by the
+ * server), applications could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * startIndex: 0,
+ * count: 50
+ * });
+ * (end)
+ *
+ * To limit the attributes returned by the GetFeature request, applications
+ * can use the propertyNames option to specify the properties to include in
+ * the response:
+ *
+ * (code)
+ * protocol.read({
+ * propertyNames: ["DURATION", "INTENSITY"]
+ * });
+ * (end)
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [this.format.writeNode("wfs:GetFeature", options)]
+ );
+
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ params: options.params,
+ headers: options.headers,
+ data: data
+ });
+
+ return response;
+ },
+
+ /**
+ * APIMethod: setFeatureType
+ * Change the feature type on the fly.
+ *
+ * Parameters:
+ * featureType - {String} Local (without prefix) feature typeName.
+ */
+ setFeatureType: function(featureType) {
+ this.featureType = featureType;
+ this.format.featureType = featureType;
+ },
+
+ /**
+ * APIMethod: setGeometryName
+ * Sets the geometryName option after instantiation.
+ *
+ * Parameters:
+ * geometryName - {String} Name of geometry attribute.
+ */
+ setGeometryName: function(geometryName) {
+ this.geometryName = geometryName;
+ this.format.geometryName = geometryName;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ var result = this.parseResponse(request, options.readOptions);
+ if (result && result.success !== false) {
+ if (options.readOptions && options.readOptions.output == "object") {
+ OpenLayers.Util.extend(response, result);
+ } else {
+ response.features = result;
+ }
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure (service exception)
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = result;
+ }
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseResponse
+ * Read HTTP response body and return features
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ * options - {Object} Optional object to pass to format's read
+ *
+ * Returns:
+ * {Object} or {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * An object with a features property, an array of features or a single
+ * feature.
+ */
+ parseResponse: function(request, options) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ var result = (this.readFormat !== null) ? this.readFormat.read(doc) :
+ this.format.read(doc, options);
+ if (!this.featureNS) {
+ var format = this.readFormat || this.format;
+ this.featureNS = format.featureNS;
+ // no need to auto-configure again on subsequent reads
+ format.autoConfig = false;
+ if (!this.geometryName) {
+ this.setGeometryName(format.geometryName);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Method: commit
+ * Given a list of feature, assemble a batch request for update, create,
+ * and delete transactions. A commit call on the prototype amounts
+ * to writing a WFS transaction - so the write method on the format
+ * is used.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ *
+ * Valid options properties:
+ * nativeElements - {Array({Object})} Array of objects with information for writing
+ * out <Native> elements, these objects have vendorId, safeToIgnore and
+ * value properties. The <Native> element is intended to allow access to
+ * vendor specific capabilities of any particular web feature server or
+ * datastore.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object with a features
+ * property containing any insertIds and a priv property referencing
+ * the XMLHttpRequest object.
+ */
+ commit: function(features, options) {
+
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit",
+ reqFeatures: features
+ });
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ headers: options.headers,
+ data: this.format.write(features, options),
+ callback: this.createCallback(this.handleCommit, response, options)
+ });
+
+ return response;
+ },
+
+ /**
+ * Method: handleCommit
+ * Called when the commit request returns.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the commit call.
+ */
+ handleCommit: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+
+ // ensure that we have an xml doc
+ var data = request.responseXML;
+ if(!data || !data.documentElement) {
+ data = request.responseText;
+ }
+
+ var obj = this.format.read(data) || {};
+
+ response.insertIds = obj.insertIds || [];
+ if (obj.success) {
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = obj;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: filterDelete
+ * Send a request that deletes all features by their filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter
+ */
+ filterDelete: function(filter, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit"
+ });
+
+ var root = this.format.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version
+ }
+ });
+
+ var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
+ options.featureType
+ }
+ });
+
+ if(options.featureNS) {
+ deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
+ }
+ var filterNode = this.format.writeNode("ogc:Filter", filter);
+
+ deleteNode.appendChild(filterNode);
+
+ root.appendChild(deleteNode);
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [root]
+ );
+
+ return OpenLayers.Request.POST({
+ url: this.url,
+ callback : options.callback || function(){},
+ data: data
+ });
+
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this protocol (as a result
+ * of a read, or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1"
+});
+/* ======================================================================
+ OpenLayers/Handler/Feature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature
+ * Handler to respond to mouse events related to a drawn feature. Callbacks
+ * with the following keys will be notified of the following events
+ * associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ * browser events target features that can be selected.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: EVENTMAP
+ * {Object} A object mapping the browser events to objects with callback
+ * keys for in and out.
+ */
+ EVENTMAP: {
+ 'click': {'in': 'click', 'out': 'clickout'},
+ 'mousemove': {'in': 'over', 'out': 'out'},
+ 'dblclick': {'in': 'dblclick', 'out': null},
+ 'mousedown': {'in': null, 'out': null},
+ 'mouseup': {'in': null, 'out': null},
+ 'touchstart': {'in': 'click', 'out': 'clickout'}
+ },
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+ */
+ feature: null,
+
+ /**
+ * Property: lastFeature
+ * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+ */
+ lastFeature: null,
+
+ /**
+ * Property: down
+ * {<OpenLayers.Pixel>} The location of the last mousedown.
+ */
+ down: null,
+
+ /**
+ * Property: up
+ * {<OpenLayers.Pixel>} The location of the last mouseup.
+ */
+ up: null,
+
+ /**
+ * Property: clickTolerance
+ * {Number} The number of pixels the mouse can move between mousedown
+ * and mouseup for the event to still be considered a click.
+ * Dragging the map should not trigger the click and clickout callbacks
+ * unless the map is moved by less than this tolerance. Defaults to 4.
+ */
+ clickTolerance: 4,
+
+ /**
+ * Property: geometryTypes
+ * To restrict dragging to a limited set of geometry types, send a list
+ * of strings corresponding to the geometry class names.
+ *
+ * @type Array(String)
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: stopClick
+ * {Boolean} If stopClick is set to true, handled clicks do not
+ * propagate to other click listeners. Otherwise, handled clicks
+ * do propagate. Unhandled clicks always propagate, whatever the
+ * value of stopClick. Defaults to true.
+ */
+ stopClick: true,
+
+ /**
+ * Property: stopDown
+ * {Boolean} If stopDown is set to true, handled mousedowns do not
+ * propagate to other mousedown listeners. Otherwise, handled
+ * mousedowns do propagate. Unhandled mousedowns always propagate,
+ * whatever the value of stopDown. Defaults to true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: stopUp
+ * {Boolean} If stopUp is set to true, handled mouseups do not
+ * propagate to other mouseup listeners. Otherwise, handled mouseups
+ * do propagate. Unhandled mouseups always propagate, whatever the
+ * value of stopUp. Defaults to false.
+ */
+ stopUp: false,
+
+ /**
+ * Constructor: OpenLayers.Handler.Feature
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * layer - {<OpenLayers.Layer.Vector>}
+ * callbacks - {Object} An object with a 'over' property whos value is
+ * a function to be called when the mouse is over a feature. The
+ * callback should expect to recieve a single argument, the feature.
+ * options - {Object}
+ */
+ initialize: function(control, layer, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+ this.layer = layer;
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return OpenLayers.Event.isMultiTouch(evt) ?
+ true : this.mousedown(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events. We just prevent the browser default behavior,
+ * for Android Webkit not to select text when moving the finger after
+ * selecting a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ touchmove: function(evt) {
+ OpenLayers.Event.preventDefault(evt);
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mouse down. Stop propagation if a feature is targeted by this
+ * event (stops map dragging during feature selection).
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mousedown: function(evt) {
+ // Feature selection is only done with a left click. Other handlers may stop the
+ // propagation of left-click mousedown events but not right-click mousedown events.
+ // This mismatch causes problems when comparing the location of the down and up
+ // events in the click function so it is important ignore right-clicks.
+ if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
+ this.down = evt.xy;
+ }
+ return this.handle(evt) ? !this.stopDown : true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouse up. Stop propagation if a feature is targeted by this
+ * event.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mouseup: function(evt) {
+ this.up = evt.xy;
+ return this.handle(evt) ? !this.stopUp : true;
+ },
+
+ /**
+ * Method: click
+ * Handle click. Call the "click" callback if click on a feature,
+ * or the "clickout" callback if click outside any feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ click: function(evt) {
+ return this.handle(evt) ? !this.stopClick : true;
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mouse moves. Call the "over" callback if moving in to a feature,
+ * or the "out" callback if moving out of a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ mousemove: function(evt) {
+ if (!this.callbacks['over'] && !this.callbacks['out']) {
+ return true;
+ }
+ this.handle(evt);
+ return true;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ dblclick: function(evt) {
+ return !this.handle(evt);
+ },
+
+ /**
+ * Method: geometryTypeMatches
+ * Return true if the geometry type of the passed feature matches
+ * one of the geometry types in the geometryTypes array.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ geometryTypeMatches: function(feature) {
+ return this.geometryTypes == null ||
+ OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) > -1;
+ },
+
+ /**
+ * Method: handle
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} The event occurred over a relevant feature.
+ */
+ handle: function(evt) {
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ var type = evt.type;
+ var handled = false;
+ var previouslyIn = !!(this.feature); // previously in a feature
+ var click = (type == "click" || type == "dblclick" || type == "touchstart");
+ this.feature = this.layer.getFeatureFromEvent(evt);
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ if(this.lastFeature && !this.lastFeature.layer) {
+ // last feature has been destroyed
+ this.lastFeature = null;
+ }
+ if(this.feature) {
+ if(type === "touchstart") {
+ // stop the event to prevent Android Webkit from
+ // "flashing" the map div
+ OpenLayers.Event.preventDefault(evt);
+ }
+ var inNew = (this.feature != this.lastFeature);
+ if(this.geometryTypeMatches(this.feature)) {
+ // in to a feature
+ if(previouslyIn && inNew) {
+ // out of last feature and in to another
+ if(this.lastFeature) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ this.triggerCallback(type, 'in', [this.feature]);
+ } else if(!previouslyIn || click) {
+ // in feature for the first time
+ this.triggerCallback(type, 'in', [this.feature]);
+ }
+ this.lastFeature = this.feature;
+ handled = true;
+ } else {
+ // not in to a feature
+ if(this.lastFeature && (previouslyIn && inNew || click)) {
+ // out of last feature for the first time
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ // next time the mouse goes in a feature whose geometry type
+ // doesn't match we don't want to call the 'out' callback
+ // again, so let's set this.feature to null so that
+ // previouslyIn will evaluate to false the next time
+ // we enter handle. Yes, a bit hackish...
+ this.feature = null;
+ }
+ } else if(this.lastFeature && (previouslyIn || click)) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ return handled;
+ },
+
+ /**
+ * Method: triggerCallback
+ * Call the callback keyed in the event map with the supplied arguments.
+ * For click and clickout, the <clickTolerance> is checked first.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ triggerCallback: function(type, mode, args) {
+ var key = this.EVENTMAP[type][mode];
+ if(key) {
+ if(type == 'click' && this.up && this.down) {
+ // for click/clickout, only trigger callback if tolerance is met
+ var dpx = Math.sqrt(
+ Math.pow(this.up.x - this.down.x, 2) +
+ Math.pow(this.up.y - this.down.y, 2)
+ );
+ if(dpx <= this.clickTolerance) {
+ this.callback(key, args);
+ }
+ // we're done with this set of events now: clear the cached
+ // positions so we can't trip over them later (this can occur
+ // if one of the up/down events gets eaten before it gets to us
+ // but we still get the click)
+ this.up = this.down = null;
+ } else {
+ this.callback(key, args);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Turn off the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.feature = null;
+ this.lastFeature = null;
+ this.down = null;
+ this.up = null;
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Feature"
+});
+/* ======================================================================
+ OpenLayers/StyleMap.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.StyleMap
+ */
+OpenLayers.StyleMap = OpenLayers.Class({
+
+ /**
+ * Property: styles
+ * {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
+ * rendering intents (e.g. "default", "temporary", "select", "delete").
+ */
+ styles: null,
+
+ /**
+ * Property: extendDefault
+ * {Boolean} if true, every render intent will extend the symbolizers
+ * specified for the "default" intent at rendering time. Otherwise, every
+ * rendering intent will be treated as a completely independent style.
+ */
+ extendDefault: true,
+
+ /**
+ * Constructor: OpenLayers.StyleMap
+ *
+ * Parameters:
+ * style - {Object} Optional. Either a style hash, or a style object, or
+ * a hash of style objects (style hashes) keyed by rendering
+ * intent. If just one style hash or style object is passed,
+ * this will be used for all known render intents (default,
+ * select, temporary)
+ * options - {Object} optional hash of additional options for this
+ * instance
+ */
+ initialize: function (style, options) {
+ this.styles = {
+ "default": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["default"]),
+ "select": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["select"]),
+ "temporary": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["temporary"]),
+ "delete": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["delete"])
+ };
+
+ // take whatever the user passed as style parameter and convert it
+ // into parts of stylemap.
+ if(style instanceof OpenLayers.Style) {
+ // user passed a style object
+ this.styles["default"] = style;
+ this.styles["select"] = style;
+ this.styles["temporary"] = style;
+ this.styles["delete"] = style;
+ } else if(typeof style == "object") {
+ for(var key in style) {
+ if(style[key] instanceof OpenLayers.Style) {
+ // user passed a hash of style objects
+ this.styles[key] = style[key];
+ } else if(typeof style[key] == "object") {
+ // user passsed a hash of style hashes
+ this.styles[key] = new OpenLayers.Style(style[key]);
+ } else {
+ // user passed a style hash (i.e. symbolizer)
+ this.styles["default"] = new OpenLayers.Style(style);
+ this.styles["select"] = new OpenLayers.Style(style);
+ this.styles["temporary"] = new OpenLayers.Style(style);
+ this.styles["delete"] = new OpenLayers.Style(style);
+ break;
+ }
+ }
+ }
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for(var key in this.styles) {
+ this.styles[key].destroy();
+ }
+ this.styles = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * Creates the symbolizer for a feature for a render intent.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
+ * of the intended style against.
+ * intent - {String} The intent determines the symbolizer that will be
+ * used to draw the feature. Well known intents are "default"
+ * (for just drawing the features), "select" (for selected
+ * features) and "temporary" (for drawing features).
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature, intent) {
+ if(!feature) {
+ feature = new OpenLayers.Feature.Vector();
+ }
+ if(!this.styles[intent]) {
+ intent = "default";
+ }
+ feature.renderIntent = intent;
+ var defaultSymbolizer = {};
+ if(this.extendDefault && intent != "default") {
+ defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
+ }
+ return OpenLayers.Util.extend(defaultSymbolizer,
+ this.styles[intent].createSymbolizer(feature));
+ },
+
+ /**
+ * Method: addUniqueValueRules
+ * Convenience method to create comparison rules for unique values of a
+ * property. The rules will be added to the style object for a specified
+ * rendering intent. This method is a shortcut for creating something like
+ * the "unique value legends" familiar from well known desktop GIS systems
+ *
+ * Parameters:
+ * renderIntent - {String} rendering intent to add the rules to
+ * property - {String} values of feature attributes to create the
+ * rules for
+ * symbolizers - {Object} Hash of symbolizers, keyed by the desired
+ * property values
+ * context - {Object} An optional object with properties that
+ * symbolizers' property values should be evaluated
+ * against. If no context is specified, feature.attributes
+ * will be used
+ */
+ addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
+ var rules = [];
+ for (var value in symbolizers) {
+ rules.push(new OpenLayers.Rule({
+ symbolizer: symbolizers[value],
+ context: context,
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: property,
+ value: value
+ })
+ }));
+ }
+ this.styles[renderIntent].addRules(rules);
+ },
+
+ CLASS_NAME: "OpenLayers.StyleMap"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Renderer.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector
+ * Instances of OpenLayers.Layer.Vector are used to render vector data from
+ * a variety of sources. Create a new vector layer with the
+ * <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types (in addition to those from <OpenLayers.Layer.events>):
+ * beforefeatureadded - Triggered before a feature is added. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be added. To stop the feature from being added, a
+ * listener should return false.
+ * beforefeaturesadded - Triggered before an array of features is added.
+ * Listeners will receive an object with a *features* property
+ * referencing the feature to be added. To stop the features from
+ * being added, a listener should return false.
+ * featureadded - Triggered after a feature is added. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the added feature.
+ * featuresadded - Triggered after features are added. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of added features.
+ * beforefeatureremoved - Triggered before a feature is removed. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be removed.
+ * beforefeaturesremoved - Triggered before multiple features are removed.
+ * Listeners will receive an object with a *features* property
+ * referencing the features to be removed.
+ * featureremoved - Triggerd after a feature is removed. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the removed feature.
+ * featuresremoved - Triggered after features are removed. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of removed features.
+ * beforefeatureselected - Triggered before a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be selected. To stop the feature from being selectd, a
+ * listener should return false.
+ * featureselected - Triggered after a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * selected feature.
+ * featureunselected - Triggered after a feature is unselected.
+ * Listeners will receive an object with a *feature* property
+ * referencing the unselected feature.
+ * beforefeaturemodified - Triggered when a feature is selected to
+ * be modified. Listeners will receive an object with a *feature*
+ * property referencing the selected feature.
+ * featuremodified - Triggered when a feature has been modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * afterfeaturemodified - Triggered when a feature is finished being modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * vertexmodified - Triggered when a vertex within any feature geometry
+ * has been modified. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * modification.
+ * vertexremoved - Triggered when a vertex within any feature geometry
+ * has been deleted. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * removal.
+ * sketchstarted - Triggered when a feature sketch bound for this layer
+ * is started. Listeners will receive an object with a *feature*
+ * property referencing the new sketch feature and a *vertex* property
+ * referencing the creation point.
+ * sketchmodified - Triggered when a feature sketch bound for this layer
+ * is modified. Listeners will receive an object with a *vertex*
+ * property referencing the modified vertex and a *feature* property
+ * referencing the sketch feature.
+ * sketchcomplete - Triggered when a feature sketch bound for this layer
+ * is complete. Listeners will receive an object with a *feature*
+ * property referencing the sketch feature. By returning false, a
+ * listener can stop the sketch feature from being added to the layer.
+ * refresh - Triggered when something wants a strategy to ask the protocol
+ * for a new set of features.
+ */
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is false. Set this property
+ * in the layer options.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} Whether the layer remains in one place while dragging the
+ * map. Note that setting this to true will move the layer to the bottom
+ * of the layer stack.
+ */
+ isFixed: false,
+
+ /**
+ * APIProperty: features
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ features: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} The filter set in this layer,
+ * a strategy launching read requests can combined
+ * this filter with its own filter.
+ */
+ filter: null,
+
+ /**
+ * Property: selectedFeatures
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ selectedFeatures: null,
+
+ /**
+ * Property: unrenderedFeatures
+ * {Object} hash of features, keyed by feature.id, that the renderer
+ * failed to draw
+ */
+ unrenderedFeatures: null,
+
+ /**
+ * APIProperty: reportError
+ * {Boolean} report friendly error message when loading of renderer
+ * fails.
+ */
+ reportError: true,
+
+ /**
+ * APIProperty: style
+ * {Object} Default style for the layer
+ */
+ style: null,
+
+ /**
+ * Property: styleMap
+ * {<OpenLayers.StyleMap>}
+ */
+ styleMap: null,
+
+ /**
+ * Property: strategies
+ * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+ */
+ strategies: null,
+
+ /**
+ * Property: protocol
+ * {<OpenLayers.Protocol>} Optional protocol for the layer.
+ */
+ protocol: null,
+
+ /**
+ * Property: renderers
+ * {Array(String)} List of supported Renderer classes. Add to this list to
+ * add support for additional renderers. This list is ordered:
+ * the first renderer which returns true for the 'supported()'
+ * method will be used, if not defined in the 'renderer' option.
+ */
+ renderers: ['SVG', 'VML', 'Canvas'],
+
+ /**
+ * Property: renderer
+ * {<OpenLayers.Renderer>}
+ */
+ renderer: null,
+
+ /**
+ * APIProperty: rendererOptions
+ * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
+ * supported options.
+ */
+ rendererOptions: null,
+
+ /**
+ * APIProperty: geometryType
+ * {String} geometryType allows you to limit the types of geometries this
+ * layer supports. This should be set to something like
+ * "OpenLayers.Geometry.Point" to limit types.
+ */
+ geometryType: null,
+
+ /**
+ * Property: drawn
+ * {Boolean} Whether the Vector Layer features have been drawn yet.
+ */
+ drawn: false,
+
+ /**
+ * APIProperty: ratio
+ * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map.
+ */
+ ratio: 1,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector
+ * Create a new vector layer
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} A new vector layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+
+ // allow user-set renderer, otherwise assign one
+ if (!this.renderer || !this.renderer.supported()) {
+ this.assignRenderer();
+ }
+
+ // if no valid renderer found, display error
+ if (!this.renderer || !this.renderer.supported()) {
+ this.renderer = null;
+ this.displayError();
+ }
+
+ if (!this.styleMap) {
+ this.styleMap = new OpenLayers.StyleMap();
+ }
+
+ this.features = [];
+ this.selectedFeatures = [];
+ this.unrenderedFeatures = {};
+
+ // Allow for custom layer behavior
+ if(this.strategies){
+ for(var i=0, len=this.strategies.length; i<len; i++) {
+ this.strategies[i].setLayer(this);
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoDestroy) {
+ strategy.destroy();
+ }
+ }
+ this.strategies = null;
+ }
+ if (this.protocol) {
+ if(this.protocol.autoDestroy) {
+ this.protocol.destroy();
+ }
+ this.protocol = null;
+ }
+ this.destroyFeatures();
+ this.features = null;
+ this.selectedFeatures = null;
+ this.unrenderedFeatures = null;
+ if (this.renderer) {
+ this.renderer.destroy();
+ }
+ this.renderer = null;
+ this.geometryType = null;
+ this.drawn = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer.
+ *
+ * Note: Features of the layer are also cloned.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Vector(this.name, this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ var features = this.features;
+ var len = features.length;
+ var clonedFeatures = new Array(len);
+ for(var i=0; i<len; ++i) {
+ clonedFeatures[i] = features[i].clone();
+ }
+ obj.features = clonedFeatures;
+
+ return obj;
+ },
+
+ /**
+ * Method: refresh
+ * Ask the layer to request features again and redraw them. Triggers
+ * the refresh event if the layer is in range and visible.
+ *
+ * Parameters:
+ * obj - {Object} Optional object with properties for any listener of
+ * the refresh event.
+ */
+ refresh: function(obj) {
+ if(this.calculateInRange() && this.visibility) {
+ this.events.triggerEvent("refresh", obj);
+ }
+ },
+
+ /**
+ * Method: assignRenderer
+ * Iterates through the available renderer implementations and selects
+ * and assigns the first one whose "supported()" function returns true.
+ */
+ assignRenderer: function() {
+ for (var i=0, len=this.renderers.length; i<len; i++) {
+ var rendererClass = this.renderers[i];
+ var renderer = (typeof rendererClass == "function") ?
+ rendererClass :
+ OpenLayers.Renderer[rendererClass];
+ if (renderer && renderer.prototype.supported()) {
+ this.renderer = new renderer(this.div, this.rendererOptions);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: displayError
+ * Let the user know their browser isn't supported.
+ */
+ displayError: function() {
+ if (this.reportError) {
+ OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
+ {renderers: this. renderers.join('\n')}));
+ }
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * If there is no renderer set, the layer can't be used. Remove it.
+ * Otherwise, give the renderer a reference to the map and set its size.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ if (!this.renderer) {
+ this.map.removeLayer(this);
+ } else {
+ this.renderer.map = this.map;
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. Any autoActivate strategies will be
+ * activated here.
+ */
+ afterAdd: function() {
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.activate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ this.drawn = false;
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.deactivate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * Notify the renderer of the change in size.
+ *
+ */
+ onMapResize: function() {
+ OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ },
+
+ /**
+ * Method: moveTo
+ * Reset the vector layer's div so that it once again is lined up with
+ * the map. Notify the renderer of the change of extent, and in the
+ * case of a change of zoom level (resolution), have the
+ * renderer redraw features.
+ *
+ * If the layer has not yet been drawn, cycle through the layer's
+ * features and draw each one.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ var coordSysUnchanged = true;
+ if (!dragging) {
+ this.renderer.root.style.visibility = 'hidden';
+
+ var viewSize = this.map.getSize(),
+ viewWidth = viewSize.w,
+ viewHeight = viewSize.h,
+ offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2,
+ offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2;
+ offsetLeft += this.map.layerContainerOriginPx.x;
+ offsetLeft = -Math.round(offsetLeft);
+ offsetTop += this.map.layerContainerOriginPx.y;
+ offsetTop = -Math.round(offsetTop);
+
+ this.div.style.left = offsetLeft + 'px';
+ this.div.style.top = offsetTop + 'px';
+
+ var extent = this.map.getExtent().scale(this.ratio);
+ coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
+
+ this.renderer.root.style.visibility = 'visible';
+
+ // Force a reflow on gecko based browsers to prevent jump/flicker.
+ // This seems to happen on only certain configurations; it was originally
+ // noticed in FF 2.0 and Linux.
+ if (OpenLayers.IS_GECKO === true) {
+ this.div.scrollLeft = this.div.scrollLeft;
+ }
+
+ if (!zoomChanged && coordSysUnchanged) {
+ for (var i in this.unrenderedFeatures) {
+ var feature = this.unrenderedFeatures[i];
+ this.drawFeature(feature);
+ }
+ }
+ }
+ if (!this.drawn || zoomChanged || !coordSysUnchanged) {
+ this.drawn = true;
+ var feature;
+ for(var i=0, len=this.features.length; i<len; i++) {
+ this.renderer.locked = (i !== (len - 1));
+ feature = this.features[i];
+ this.drawFeature(feature);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ // we need to set the display style of the root in case it is attached
+ // to a foreign layer
+ var currentDisplay = this.div.style.display;
+ if(currentDisplay != this.renderer.root.style.display) {
+ this.renderer.root.style.display = currentDisplay;
+ }
+ },
+
+ /**
+ * APIMethod: addFeatures
+ * Add Features to the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ */
+ addFeatures: function(features, options) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var notify = !options || !options.silent;
+ if(notify) {
+ var event = {features: features};
+ var ret = this.events.triggerEvent("beforefeaturesadded", event);
+ if(ret === false) {
+ return;
+ }
+ features = event.features;
+ }
+
+ // Track successfully added features for featuresadded event, since
+ // beforefeatureadded can veto single features.
+ var featuresAdded = [];
+ for (var i=0, len=features.length; i<len; i++) {
+ if (i != (features.length - 1)) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+ var feature = features[i];
+
+ if (this.geometryType &&
+ !(feature.geometry instanceof this.geometryType)) {
+ throw new TypeError('addFeatures: component should be an ' +
+ this.geometryType.prototype.CLASS_NAME);
+ }
+
+ //give feature reference to its layer
+ feature.layer = this;
+
+ if (!feature.style && this.style) {
+ feature.style = OpenLayers.Util.extend({}, this.style);
+ }
+
+ if (notify) {
+ if(this.events.triggerEvent("beforefeatureadded",
+ {feature: feature}) === false) {
+ continue;
+ }
+ this.preFeatureInsert(feature);
+ }
+
+ featuresAdded.push(feature);
+ this.features.push(feature);
+ this.drawFeature(feature);
+
+ if (notify) {
+ this.events.triggerEvent("featureadded", {
+ feature: feature
+ });
+ this.onFeatureInsert(feature);
+ }
+ }
+
+ if(notify) {
+ this.events.triggerEvent("featuresadded", {features: featuresAdded});
+ }
+ },
+
+
+ /**
+ * APIMethod: removeFeatures
+ * Remove features from the layer. This erases any drawn features and
+ * removes them from the layer's control. The beforefeatureremoved
+ * and featureremoved events will be triggered for each feature. The
+ * featuresremoved event will be triggered after all features have
+ * been removed. To supress event triggering, use the silent option.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
+ * removed.
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeFeatures: function(features, options) {
+ if(!features || features.length === 0) {
+ return;
+ }
+ if (features === this.features) {
+ return this.removeAllFeatures(options);
+ }
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ if (features === this.selectedFeatures) {
+ features = features.slice();
+ }
+
+ var notify = !options || !options.silent;
+
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+
+ for (var i = features.length - 1; i >= 0; i--) {
+ // We remain locked so long as we're not at 0
+ // and the 'next' feature has a geometry. We do the geometry check
+ // because if all the features after the current one are 'null', we
+ // won't call eraseGeometry, so we break the 'renderer functions
+ // will always be called with locked=false *last*' rule. The end result
+ // is a possible gratiutious unlocking to save a loop through the rest
+ // of the list checking the remaining features every time. So long as
+ // null geoms are rare, this is probably okay.
+ if (i != 0 && features[i-1].geometry) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+
+ var feature = features[i];
+ delete this.unrenderedFeatures[feature.id];
+
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+
+ this.features = OpenLayers.Util.removeItem(this.features, feature);
+ // feature has no layer at this point
+ feature.layer = null;
+
+ if (feature.geometry) {
+ this.renderer.eraseFeatures(feature);
+ }
+
+ //in the case that this feature is one of the selected features,
+ // remove it from that array as well.
+ if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
+ OpenLayers.Util.removeItem(this.selectedFeatures, feature);
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: removeAllFeatures
+ * Remove all features from the layer.
+ *
+ * Parameters:
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeAllFeatures: function(options) {
+ var notify = !options || !options.silent;
+ var features = this.features;
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+ var feature;
+ for (var i = features.length-1; i >= 0; i--) {
+ feature = features[i];
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+ feature.layer = null;
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+ this.renderer.clear();
+ this.features = [];
+ this.unrenderedFeatures = {};
+ this.selectedFeatures = [];
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: destroyFeatures
+ * Erase and destroy features on the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
+ * features to destroy. If not supplied, all features on the layer
+ * will be destroyed.
+ * options - {Object}
+ */
+ destroyFeatures: function(features, options) {
+ var all = (features == undefined); // evaluates to true if
+ // features is null
+ if(all) {
+ features = this.features;
+ }
+ if(features) {
+ this.removeFeatures(features, options);
+ for(var i=features.length-1; i>=0; i--) {
+ features[i].destroy();
+ }
+ }
+ },
+
+ /**
+ * APIMethod: drawFeature
+ * Draw (or redraw) a feature on the layer. If the optional style argument
+ * is included, this style will be used. If no style is included, the
+ * feature's style will be used. If the feature doesn't have a style,
+ * the layer's style will be used.
+ *
+ * This function is not designed to be used when adding features to
+ * the layer (use addFeatures instead). It is meant to be used when
+ * the style of a feature has changed, or in some other way needs to
+ * visually updated *after* it has already been added to a layer. You
+ * must add the feature to the layer for most layer-related events to
+ * happen.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {String | Object} Named render intent or full symbolizer object.
+ */
+ drawFeature: function(feature, style) {
+ // don't try to draw the feature with the renderer if the layer is not
+ // drawn itself
+ if (!this.drawn) {
+ return;
+ }
+ if (typeof style != "object") {
+ if(!style && feature.state === OpenLayers.State.DELETE) {
+ style = "delete";
+ }
+ var renderIntent = style || feature.renderIntent;
+ style = feature.style || this.style;
+ if (!style) {
+ style = this.styleMap.createSymbolizer(feature, renderIntent);
+ }
+ }
+
+ var drawn = this.renderer.drawFeature(feature, style);
+ //TODO remove the check for null when we get rid of Renderer.SVG
+ if (drawn === false || drawn === null) {
+ this.unrenderedFeatures[feature.id] = feature;
+ } else {
+ delete this.unrenderedFeatures[feature.id];
+ }
+ },
+
+ /**
+ * Method: eraseFeatures
+ * Erase features from the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ this.renderer.eraseFeatures(features);
+ },
+
+ /**
+ * Method: getFeatureFromEvent
+ * Given an event, return a feature if the event occurred over one.
+ * Otherwise, return null.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
+ */
+ getFeatureFromEvent: function(evt) {
+ if (!this.renderer) {
+ throw new Error('getFeatureFromEvent called on layer with no ' +
+ 'renderer. This usually means you destroyed a ' +
+ 'layer, but not some handler which is associated ' +
+ 'with it.');
+ }
+ var feature = null;
+ var featureId = this.renderer.getFeatureIdFromEvent(evt);
+ if (featureId) {
+ if (typeof featureId === "string") {
+ feature = this.getFeatureById(featureId);
+ } else {
+ feature = featureId;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureBy
+ * Given a property value, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * property - {String}
+ * value - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * property value or null if there is no such feature.
+ */
+ getFeatureBy: function(property, value) {
+ //TBD - would it be more efficient to use a hash for this.features?
+ var feature = null;
+ for(var i=0, len=this.features.length; i<len; ++i) {
+ if(this.features[i][property] == value) {
+ feature = this.features[i];
+ break;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureById
+ * Given a feature id, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureId - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureId or null if there is no such feature.
+ */
+ getFeatureById: function(featureId) {
+ return this.getFeatureBy('id', featureId);
+ },
+
+ /**
+ * APIMethod: getFeatureByFid
+ * Given a feature fid, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureFid - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureFid or null if there is no such feature.
+ */
+ getFeatureByFid: function(featureFid) {
+ return this.getFeatureBy('fid', featureFid);
+ },
+
+ /**
+ * APIMethod: getFeaturesByAttribute
+ * Returns an array of features that have the given attribute key set to the
+ * given value. Comparison of attribute values takes care of datatypes, e.g.
+ * the string '1234' is not equal to the number 1234.
+ *
+ * Parameters:
+ * attrName - {String}
+ * attrValue - {Mixed}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>}) An array of features that have the
+ * passed named attribute set to the given value.
+ */
+ getFeaturesByAttribute: function(attrName, attrValue) {
+ var i,
+ feature,
+ len = this.features.length,
+ foundFeatures = [];
+ for(i = 0; i < len; i++) {
+ feature = this.features[i];
+ if(feature && feature.attributes) {
+ if (feature.attributes[attrName] === attrValue) {
+ foundFeatures.push(feature);
+ }
+ }
+ }
+ return foundFeatures;
+ },
+
+ /**
+ * Unselect the selected features
+ * i.e. clears the featureSelection array
+ * change the style back
+ clearSelection: function() {
+
+ var vectorLayer = this.map.vectorLayer;
+ for (var i = 0; i < this.map.featureSelection.length; i++) {
+ var featureSelection = this.map.featureSelection[i];
+ vectorLayer.drawFeature(featureSelection, vectorLayer.style);
+ }
+ this.map.featureSelection = [];
+ },
+ */
+
+
+ /**
+ * APIMethod: onFeatureInsert
+ * method called after a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something on feature updates.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ onFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: preFeatureInsert
+ * method called before a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something when features are first added to the
+ * layer, but before they are drawn, such as adjust the style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ preFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the features.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} or null if the layer has no features with
+ * geometries.
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+ var features = this.features;
+ if(features && (features.length > 0)) {
+ var geometry = null;
+ for(var i=0, len=features.length; i<len; i++) {
+ geometry = features[i].geometry;
+ if (geometry) {
+ if (maxExtent === null) {
+ maxExtent = new OpenLayers.Bounds();
+ }
+ maxExtent.extend(geometry.getBounds());
+ }
+ }
+ }
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector"
+});
+/* ======================================================================
+ OpenLayers/Layer/Vector/RootContainer.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector.RootContainer
+ * A special layer type to combine multiple vector layers inside a single
+ * renderer root container. This class is not supposed to be instantiated
+ * from user space, it is a helper class for controls that require event
+ * processing for multiple vector layers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * Property: displayInLayerSwitcher
+ * Set to false for this layer type
+ */
+ displayInLayerSwitcher: false,
+
+ /**
+ * APIProperty: layers
+ * Layers that are attached to this container. Required config option.
+ */
+ layers: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector.RootContainer
+ * Create a new root container for multiple vector layer. This constructor
+ * is not supposed to be used from user space, it is only to be used by
+ * controls that need feature selection across multiple vector layers.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Required options properties:
+ * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this
+ * container
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root
+ * container
+ */
+
+ /**
+ * Method: display
+ */
+ display: function() {},
+
+ /**
+ * Method: getFeatureFromEvent
+ * walk through the layers to find the feature returned by the event
+ *
+ * Parameters:
+ * evt - {Object} event object with a feature property
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getFeatureFromEvent: function(evt) {
+ var layers = this.layers;
+ var feature;
+ for(var i=0; i<layers.length; i++) {
+ feature = layers[i].getFeatureFromEvent(evt);
+ if(feature) {
+ return feature;
+ }
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ this.collectRoots();
+ map.events.register("changelayer", this, this.handleChangeLayer);
+ },
+
+ /**
+ * Method: removeMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("changelayer", this, this.handleChangeLayer);
+ this.resetRoots();
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: collectRoots
+ * Collects the root nodes of all layers this control is configured with
+ * and moveswien the nodes to this control's layer
+ */
+ collectRoots: function() {
+ var layer;
+ // walk through all map layers, because we want to keep the order
+ for(var i=0; i<this.map.layers.length; ++i) {
+ layer = this.map.layers[i];
+ if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ layer.renderer.moveRoot(this.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: resetRoots
+ * Resets the root nodes back into the layers they belong to.
+ */
+ resetRoots: function() {
+ var layer;
+ for(var i=0; i<this.layers.length; ++i) {
+ layer = this.layers[i];
+ if(this.renderer && layer.renderer.getRenderLayerId() == this.id) {
+ this.renderer.moveRoot(layer.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: handleChangeLayer
+ * Event handler for the map's changelayer event. We need to rebuild
+ * this container's layer dom if order of one of its layers changes.
+ * This handler is added with the setMap method, and removed with the
+ * removeMap method.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleChangeLayer: function(evt) {
+ var layer = evt.layer;
+ if(evt.property == "order" &&
+ OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ this.resetRoots();
+ this.collectRoots();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
+});
+/* ======================================================================
+ OpenLayers/Control/SelectFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ * @requires OpenLayers/Layer/Vector/RootContainer.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * The SelectFeature control selects vector features from a given layer on
+ * click or hover.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforefeaturehighlighted - Triggered before a feature is highlighted
+ * featurehighlighted - Triggered when a feature is highlighted
+ * featureunhighlighted - Triggered when a feature is unhighlighted
+ * boxselectionstart - Triggered before box selection starts
+ * boxselectionend - Triggered after box selection ends
+ */
+
+ /**
+ * Property: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * Property: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Default is false. Only
+ * has meaning if hover is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Select on mouse over and deselect on mouse out. If true, this
+ * ignores clicks and only listens to mouse moves.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: highlightOnly
+ * {Boolean} If true do not actually select features (that is place them in
+ * the layer's selected features array), just highlight them. This property
+ * has no effect if hover is false. Defaults to false.
+ */
+ highlightOnly: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box.
+ */
+ box: false,
+
+ /**
+ * Property: onBeforeSelect
+ * {Function} Optional function to be called before a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onBeforeSelect: function() {},
+
+ /**
+ * APIProperty: onSelect
+ * {Function} Optional function to be called when a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onSelect: function() {},
+
+ /**
+ * APIProperty: onUnselect
+ * {Function} Optional function to be called when a feature is unselected.
+ * The function should expect to be called with a feature.
+ */
+ onUnselect: function() {},
+
+ /**
+ * Property: scope
+ * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
+ * callbacks. If null the scope will be this control.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict selecting to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
+ * root for all layers this control is configured with (if an array of
+ * layers was passed to the constructor), or the vector layer the control
+ * was configured with (if a single layer was passed to the constructor).
+ */
+ layer: null,
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
+ * or null if the control was configured with a single layer
+ */
+ layers: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} The functions that are sent to the handlers.feature for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectStyle
+ * {Object} Hash of styles
+ */
+ selectStyle: null,
+
+ /**
+ * Property: renderIntent
+ * {String} key used to retrieve the select style from the layer's
+ * style map.
+ */
+ renderIntent: "select",
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Constructor: OpenLayers.Control.SelectFeature
+ * Create a new control for selecting features.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
+ * layer(s) this control will select features from.
+ * options - {Object}
+ */
+ initialize: function(layers, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(this.scope === null) {
+ this.scope = this;
+ }
+ this.initLayer(layers);
+ var callbacks = {
+ click: this.clickFeature,
+ clickout: this.clickoutFeature
+ };
+ if (this.hover) {
+ callbacks.over = this.overFeature;
+ callbacks.out = this.outFeature;
+ }
+
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+ this.handlers = {
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, this.callbacks,
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+
+ if (this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ {boxDivClassName: "olHandlerBoxSelectFeature"}
+ );
+ }
+ },
+
+ /**
+ * Method: initLayer
+ * Assign the layer property. If layers is an array, we need to use
+ * a RootContainer.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
+ */
+ initLayer: function(layers) {
+ if(OpenLayers.Util.isArray(layers)) {
+ this.layers = layers;
+ this.layer = new OpenLayers.Layer.Vector.RootContainer(
+ this.id + "_container", {
+ layers: layers
+ }
+ );
+ } else {
+ this.layer = layers;
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ if(this.active && this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if(this.layers) {
+ this.layer.destroy();
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ if(this.layers) {
+ this.map.addLayer(this.layer);
+ }
+ this.handlers.feature.activate();
+ if(this.box && this.handlers.box) {
+ this.handlers.box.activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ this.handlers.feature.deactivate();
+ if(this.handlers.box) {
+ this.handlers.box.deactivate();
+ }
+ if(this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features. To unselect all except for a single
+ * feature, set the options.except property to the feature.
+ *
+ * Parameters:
+ * options - {Object} Optional configuration object.
+ */
+ unselectAll: function(options) {
+ // we'll want an option to supress notification here
+ var layers = this.layers || [this.layer],
+ layer, feature, l, numExcept;
+ for(l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ numExcept = 0;
+ //layer.selectedFeatures is null when layer is destroyed and
+ //one of it's preremovelayer listener calls setLayer
+ //with another layer on this control
+ if(layer.selectedFeatures != null) {
+ while(layer.selectedFeatures.length > numExcept) {
+ feature = layer.selectedFeatures[numExcept];
+ if(!options || options.except != feature) {
+ this.unselect(feature);
+ } else {
+ ++numExcept;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clickFeature
+ * Called on click in a feature
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if(!this.hover) {
+ var selected = (OpenLayers.Util.indexOf(
+ feature.layer.selectedFeatures, feature) > -1);
+ if(selected) {
+ if(this.toggleSelect()) {
+ this.unselect(feature);
+ } else if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ } else {
+ if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: multipleSelect
+ * Allow for multiple selected features based on <multiple> property and
+ * <multipleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Allow for multiple selected features.
+ */
+ multipleSelect: function() {
+ return this.multiple || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.multipleKey]);
+ },
+
+ /**
+ * Method: toggleSelect
+ * Event should toggle the selected state of a feature based on <toggle>
+ * property and <toggleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Toggle the selected state of a feature.
+ */
+ toggleSelect: function() {
+ return this.toggle || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.toggleKey]);
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called on click outside a previously clicked (selected) feature.
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ */
+ clickoutFeature: function(feature) {
+ if(!this.hover && this.clickout) {
+ this.unselectAll();
+ }
+ },
+
+ /**
+ * Method: overFeature
+ * Called on over a feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ overFeature: function(feature) {
+ var layer = feature.layer;
+ if(this.hover) {
+ if(this.highlightOnly) {
+ this.highlight(feature);
+ } else if(OpenLayers.Util.indexOf(
+ layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: outFeature
+ * Called on out of a selected feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ outFeature: function(feature) {
+ if(this.hover) {
+ if(this.highlightOnly) {
+ // we do nothing if we're not the last highlighter of the
+ // feature
+ if(feature._lastHighlighter == this.id) {
+ // if another select control had highlighted the feature before
+ // we did it ourself then we use that control to highlight the
+ // feature as it was before we highlighted it, else we just
+ // unhighlight it
+ if(feature._prevHighlighter &&
+ feature._prevHighlighter != this.id) {
+ delete feature._lastHighlighter;
+ var control = this.map.getControl(
+ feature._prevHighlighter);
+ if(control) {
+ control.highlight(feature);
+ }
+ } else {
+ this.unhighlight(feature);
+ }
+ }
+ } else {
+ this.unselect(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: highlight
+ * Redraw feature with the select style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ highlight: function(feature) {
+ var layer = feature.layer;
+ var cont = this.events.triggerEvent("beforefeaturehighlighted", {
+ feature : feature
+ });
+ if(cont !== false) {
+ feature._prevHighlighter = feature._lastHighlighter;
+ feature._lastHighlighter = this.id;
+ var style = this.selectStyle || this.renderIntent;
+ layer.drawFeature(feature, style);
+ this.events.triggerEvent("featurehighlighted", {feature : feature});
+ }
+ },
+
+ /**
+ * Method: unhighlight
+ * Redraw feature with the "default" style
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unhighlight: function(feature) {
+ var layer = feature.layer;
+ // three cases:
+ // 1. there's no other highlighter, in that case _prev is undefined,
+ // and we just need to undef _last
+ // 2. another control highlighted the feature after we did it, in
+ // that case _last references this other control, and we just
+ // need to undef _prev
+ // 3. another control highlighted the feature before we did it, in
+ // that case _prev references this other control, and we need to
+ // set _last to _prev and undef _prev
+ if(feature._prevHighlighter == undefined) {
+ delete feature._lastHighlighter;
+ } else if(feature._prevHighlighter == this.id) {
+ delete feature._prevHighlighter;
+ } else {
+ feature._lastHighlighter = feature._prevHighlighter;
+ delete feature._prevHighlighter;
+ }
+ layer.drawFeature(feature, feature.style || feature.layer.style ||
+ "default");
+ this.events.triggerEvent("featureunhighlighted", {feature : feature});
+ },
+
+ /**
+ * Method: select
+ * Add feature to the layer's selectedFeature array, render the feature as
+ * selected, and call the onSelect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ select: function(feature) {
+ var cont = this.onBeforeSelect.call(this.scope, feature);
+ var layer = feature.layer;
+ if(cont !== false) {
+ cont = layer.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ layer.selectedFeatures.push(feature);
+ this.highlight(feature);
+ // if the feature handler isn't involved in the feature
+ // selection (because the box handler is used or the
+ // feature is selected programatically) we fake the
+ // feature handler to allow unselecting on click
+ if(!this.handlers.feature.lastFeature) {
+ this.handlers.feature.lastFeature = layer.selectedFeatures[0];
+ }
+ layer.events.triggerEvent("featureselected", {feature: feature});
+ this.onSelect.call(this.scope, feature);
+ }
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the layer's selectedFeature array, render the feature as
+ * normal, and call the onUnselect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ var layer = feature.layer;
+ // Store feature style for restoration later
+ this.unhighlight(feature);
+ OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
+ layer.events.triggerEvent("featureunselected", {feature: feature});
+ this.onUnselect.call(this.scope, feature);
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is true
+ * on.
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
+ */
+ selectBox: function(position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ var bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ // if multiple is false, first deselect currently selected features
+ if (!this.multipleSelect()) {
+ this.unselectAll();
+ }
+
+ // because we're using a box, we consider we want multiple selection
+ var prevMultiple = this.multiple;
+ this.multiple = true;
+ var layers = this.layers || [this.layer];
+ this.events.triggerEvent("boxselectionstart", {layers: layers});
+ var layer;
+ for(var l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ for(var i=0, len = layer.features.length; i<len; ++i) {
+ var feature = layer.features[i];
+ // check if the feature is displayed
+ if (!feature.getVisibility()) {
+ continue;
+ }
+
+ if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+ this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+ if (bounds.toGeometry().intersects(feature.geometry)) {
+ if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ }
+ }
+ }
+ this.multiple = prevMultiple;
+ this.events.triggerEvent("boxselectionend", {layers: layers});
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.handlers.feature.setMap(map);
+ if (this.box) {
+ this.handlers.box.setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Attach a new layer to the control, overriding any existing layers.
+ *
+ * Parameters:
+ * layers - Array of {<OpenLayers.Layer.Vector>} or a single
+ * {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layers) {
+ var isActive = this.active;
+ this.unselectAll();
+ this.deactivate();
+ if(this.layers) {
+ this.layer.destroy();
+ this.layers = null;
+ }
+ this.initLayer(layers);
+ this.handlers.feature.layer = this.layer;
+ if (isActive) {
+ this.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/Point.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Point
+ * Handler to draw a point on the map. Point is displayed on activation,
+ * moves on mouse move, and is finished on mouse up. The handler triggers
+ * callbacks for 'done', 'cancel', and 'modify'. The modify callback is
+ * called with each change in the sketch and will receive the latest point
+ * drawn. Create a new instance with the <OpenLayers.Handler.Point>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: point
+ * {<OpenLayers.Feature.Vector>} The currently drawn point
+ */
+ point: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+ */
+ layer: null,
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Property: mouseDown
+ * {Boolean} The mouse is down
+ */
+ mouseDown: false,
+
+ /**
+ * Property: stoppedDown
+ * {Boolean} Indicate whether the last mousedown stopped the event
+ * propagation.
+ */
+ stoppedDown: null,
+
+ /**
+ * Property: lastDown
+ * {<OpenLayers.Pixel>} Location of the last mouse down
+ */
+ lastDown: null,
+
+ /**
+ * Property: lastUp
+ * {<OpenLayers.Pixel>}
+ */
+ lastUp: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Leave the feature rendered until destroyFeature is called.
+ * Default is false. If set to true, the feature remains rendered until
+ * destroyFeature is called, typically by deactivating the handler or
+ * starting another drawing.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: stopDown
+ * {Boolean} Stop event propagation on mousedown. Must be false to
+ * allow "pan while drawing". Defaults to false.
+ */
+ stopDown: false,
+
+ /**
+ * APIPropery: stopUp
+ * {Boolean} Stop event propagation on mouse. Must be false to
+ * allow "pan while dragging". Defaults to fase.
+ */
+ stopUp: false,
+
+ /**
+ * Property: layerOptions
+ * {Object} Any optional properties to be set on the sketch layer.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between down and up (mousedown
+ * and mouseup, or touchstart and touchend) for the handler to
+ * add a new point. If set to an integer value, if the
+ * displacement between down and up is great to this value
+ * no point will be added. Default value is 5.
+ */
+ pixelTolerance: 5,
+
+ /**
+ * Property: lastTouchPx
+ * {<OpenLayers.Pixel>} The last pixel used to know the distance between
+ * two touches (for double touch).
+ */
+ lastTouchPx: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Point
+ * Create a new point handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the point geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+ initialize: function(control, callbacks, options) {
+ if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
+ this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+ }
+
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: activate
+ * turn on the handler
+ */
+ activate: function() {
+ if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ return false;
+ }
+ // create temporary vector layer for rendering geometry sketch
+ // TBD: this could be moved to initialize/destroy - setting visibility here
+ var options = OpenLayers.Util.extend({
+ displayInLayerSwitcher: false,
+ // indicate that the temp vector layer will never be out of range
+ // without this, resolution properties must be specified at the
+ // map-level for this temporary layer to init its resolutions
+ // correctly
+ calculateInRange: OpenLayers.Function.True,
+ wrapDateLine: this.citeCompliant
+ }, this.layerOptions);
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+ this.map.addLayer(this.layer);
+ return true;
+ },
+
+ /**
+ * Method: createFeature
+ * Add temporary features
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.callback("create", [this.point.geometry, this.point]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.point], {silent: true});
+ },
+
+ /**
+ * APIMethod: deactivate
+ * turn off the handler
+ */
+ deactivate: function() {
+ if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ return false;
+ }
+ this.cancel();
+ // If a layer's map property is set to null, it means that that layer
+ // isn't added to the map. Since we ourself added the layer to the map
+ // in activate(), we can assume that if this.layer.map is null it means
+ // that the layer has been destroyed (as a result of map.destroy() for
+ // example.
+ if (this.layer.map != null) {
+ this.destroyFeature(true);
+ this.layer.destroy(false);
+ }
+ this.layer = null;
+ return true;
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy the temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ if(this.layer && (force || !this.persist)) {
+ this.layer.destroyFeatures();
+ }
+ this.point = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 1) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: finalize
+ * Finish the geometry and call the "done" callback.
+ *
+ * Parameters:
+ * cancel - {Boolean} Call cancel instead of done callback. Default
+ * is false.
+ */
+ finalize: function(cancel) {
+ var key = cancel ? "cancel" : "done";
+ this.mouseDown = false;
+ this.lastDown = null;
+ this.lastUp = null;
+ this.lastTouchPx = null;
+ this.callback(key, [this.geometryClone()]);
+ this.destroyFeature(cancel);
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ this.finalize(true);
+ },
+
+ /**
+ * Method: click
+ * Handle clicks. Clicks are stopped from propagating to other listeners
+ * on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ click: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks. Double-clicks are stopped from propagating to other
+ * listeners on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given a pixel location.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ modifyFeature: function(pixel) {
+ if(!this.point) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.point, false]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render features on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>}
+ */
+ getGeometry: function() {
+ var geometry = this.point && this.point.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPoint([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * Method: geometryClone
+ * Return a clone of the relevant geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ geometryClone: function() {
+ var geom = this.getGeometry();
+ return geom && geom.clone();
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousedown: function(evt) {
+ return this.down(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.lastTouchPx = evt.xy;
+ return this.down(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousemove: function(evt) {
+ return this.move(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchmove: function(evt) {
+ this.lastTouchPx = evt.xy;
+ return this.move(evt);
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mouseup: function(evt) {
+ return this.up(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchend: function(evt) {
+ evt.xy = this.lastTouchPx;
+ return this.up(evt);
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ if(!this.touch) { // no point displayed until up on touch devices
+ this.modifyFeature(evt.xy);
+ }
+ this.stoppedDown = this.stopDown;
+ return !this.stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(!this.touch // no point displayed until up on touch devices
+ && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to the control.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ this.mouseDown = false;
+ this.stoppedDown = this.stopDown;
+
+ // check keyboard modifiers
+ if(!this.checkModifiers(evt)) {
+ return true;
+ }
+ // ignore double-clicks
+ if (this.lastUp && this.lastUp.equals(evt.xy)) {
+ return true;
+ }
+ if (this.lastDown && this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.lastUp = evt.xy;
+ this.finalize();
+ return !this.stopUp;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouse out. For better user experience reset mouseDown
+ * and stoppedDown when the mouse leaves the map viewport.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ */
+ mouseout: function(evt) {
+ if(OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ }
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance.
+ *
+ * Returns:
+ * {Boolean} The event is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(pixel1, pixel2, tolerance) {
+ var passes = true;
+
+ if (tolerance != null && pixel1 && pixel2) {
+ var dist = pixel1.distanceTo(pixel2);
+ if (dist > tolerance) {
+ passes = false;
+ }
+ }
+ return passes;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Point"
+});
+/* ======================================================================
+ OpenLayers/Handler/Path.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Path
+ * Handler to draw a path on the map. Path is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Point>
+ */
+OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
+
+ /**
+ * Property: line
+ * {<OpenLayers.Feature.Vector>}
+ */
+ line: null,
+
+ /**
+ * APIProperty: maxVertices
+ * {Number} The maximum number of vertices which can be drawn by this
+ * handler. When the number of vertices reaches maxVertices, the
+ * geometry is automatically finalized. Default is null.
+ */
+ maxVertices: null,
+
+ /**
+ * Property: doubleTouchTolerance
+ * {Number} Maximum number of pixels between two touches for
+ * the gesture to be considered a "finalize feature" action.
+ * Default is 20.
+ */
+ doubleTouchTolerance: 20,
+
+ /**
+ * Property: freehand
+ * {Boolean} In freehand mode, the handler starts the path on mouse down,
+ * adds a point for every mouse move, and finishes the path on mouse up.
+ * Outside of freehand mode, a point is added to the path on every mouse
+ * click and double-click finishes the path.
+ */
+ freehand: false,
+
+ /**
+ * Property: freehandToggle
+ * {String} If set, freehandToggle is checked on mouse events and will set
+ * the freehand mode to the opposite of this.freehand. To disallow
+ * toggling between freehand and non-freehand mode, set freehandToggle to
+ * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
+ */
+ freehandToggle: 'shiftKey',
+
+ /**
+ * Property: timerId
+ * {Integer} The timer used to test the double touch.
+ */
+ timerId: null,
+
+ /**
+ * Property: redoStack
+ * {Array} Stack containing points removed with <undo>.
+ */
+ redoStack: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Path
+ * Create a new path hander
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the linestring geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([this.point.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.line, this.point], {silent: true});
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Point.prototype.destroyFeature.call(
+ this, force);
+ this.line = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 2) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: removePoint
+ * Destroy the temporary point.
+ */
+ removePoint: function() {
+ if(this.point) {
+ this.layer.removeFeatures([this.point]);
+ }
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry. Send the point index to override
+ * the behavior of LinearRing that disregards adding duplicate points.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ this.layer.removeFeatures([this.point]);
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
+ );
+ this.line.geometry.addComponent(
+ this.point.geometry, this.line.geometry.components.length
+ );
+ this.layer.addFeatures([this.point]);
+ this.callback("point", [this.point.geometry, this.getGeometry()]);
+ this.callback("modify", [this.point.geometry, this.getSketch()]);
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertXY
+ * Insert a point in the current sketch given x & y coordinates. The new
+ * point is inserted immediately before the most recently drawn point.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ this.line.geometry.addComponent(
+ new OpenLayers.Geometry.Point(x, y),
+ this.getCurrentPointIndex()
+ );
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ var p0 = this.line.geometry.components[previousIndex];
+ if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) {
+ this.insertXY(p0.x + dx, p0.y + dy);
+ }
+ },
+
+ /**
+ * Method: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ direction *= Math.PI / 180;
+ var dx = length * Math.cos(direction);
+ var dy = length * Math.sin(direction);
+ this.insertDeltaXY(dx, dy);
+ },
+
+ /**
+ * Method: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ if (previousIndex > 0) {
+ var p1 = this.line.geometry.components[previousIndex];
+ var p0 = this.line.geometry.components[previousIndex-1];
+ var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x);
+ this.insertDirectionLength(
+ (theta * 180 / Math.PI) + deflection, length
+ );
+ }
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 1;
+ },
+
+
+ /**
+ * Method: undo
+ * Remove the most recently added point in the sketch geometry.
+ *
+ * Returns:
+ * {Boolean} A point was removed.
+ */
+ undo: function() {
+ var geometry = this.line.geometry;
+ var components = geometry.components;
+ var index = this.getCurrentPointIndex() - 1;
+ var target = components[index];
+ var undone = geometry.removeComponent(target);
+ if (undone) {
+ // On touch devices, set the current ("mouse location") point to
+ // match the last digitized point.
+ if (this.touch && index > 0) {
+ components = geometry.components; // safety
+ var lastpt = components[index - 1];
+ var curptidx = this.getCurrentPointIndex();
+ var curpt = components[curptidx];
+ curpt.x = lastpt.x;
+ curpt.y = lastpt.y;
+ }
+ if (!this.redoStack) {
+ this.redoStack = [];
+ }
+ this.redoStack.push(target);
+ this.drawFeature();
+ }
+ return undone;
+ },
+
+ /**
+ * Method: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} A point was added.
+ */
+ redo: function() {
+ var target = this.redoStack && this.redoStack.pop();
+ if (target) {
+ this.line.geometry.addComponent(target, this.getCurrentPointIndex());
+ this.drawFeature();
+ }
+ return !!target;
+ },
+
+ /**
+ * Method: freehandMode
+ * Determine whether to behave in freehand mode or not.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ freehandMode: function(evt) {
+ return (this.freehandToggle && evt[this.freehandToggle]) ?
+ !this.freehand : this.freehand;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given the new point
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest
+ * point.
+ * drawing - {Boolean} Indicate if we're currently drawing.
+ */
+ modifyFeature: function(pixel, drawing) {
+ if(!this.line) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.line, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.line;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>}
+ */
+ getGeometry: function() {
+ var geometry = this.line && this.line.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * method: touchstart
+ * handle touchstart.
+ *
+ * parameters:
+ * evt - {event} the browser event
+ *
+ * returns:
+ * {boolean} allow event propagation
+ */
+ touchstart: function(evt) {
+ if (this.timerId &&
+ this.passesTolerance(this.lastTouchPx, evt.xy,
+ this.doubleTouchTolerance)) {
+ // double-tap, finalize the geometry
+ this.finishGeometry();
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ return false;
+ } else {
+ if (this.timerId) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.timerId = null;
+ }, this), 300);
+ return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt);
+ }
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Add a new point to the geometry and
+ * render it. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ var stopDown = this.stopDown;
+ if(this.freehandMode(evt)) {
+ stopDown = true;
+ if (this.touch) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ OpenLayers.Event.stop(evt);
+ }
+ }
+ if (!this.touch && (!this.lastDown ||
+ !this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance))) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ this.stoppedDown = stopDown;
+ return !stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ if(this.maxVertices && this.line &&
+ this.line.geometry.components.length === this.maxVertices) {
+ this.removePoint();
+ this.finalize();
+ } else {
+ this.addPoint(evt.xy);
+ }
+ return false;
+ }
+ if (!this.touch && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to
+ * the control. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if (this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.removePoint();
+ this.finalize();
+ } else {
+ if (this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.lastUp == null && this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.addPoint(evt.xy);
+ this.lastUp = evt.xy;
+ if(this.line.geometry.components.length === this.maxVertices + 1) {
+ this.finishGeometry();
+ }
+ }
+ }
+ }
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ return !this.stopUp;
+ },
+
+ /**
+ * APIMethod: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 1;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ if(!this.freehandMode(evt)) {
+ this.finishGeometry();
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Path"
+});
+/* ======================================================================
+ OpenLayers/Control/Attribution.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Attribution
+ * The attribution control adds attribution from layers to the map display.
+ * It uses 'attribution' property of each layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Attribution =
+ OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: separator
+ * {String} String used to separate layers.
+ */
+ separator: ", ",
+
+ /**
+ * APIProperty: template
+ * {String} Template for the attribution. This has to include the substring
+ * "${layers}", which will be replaced by the layer specific
+ * attributions, separated by <separator>. The default is "${layers}".
+ */
+ template: "${layers}",
+
+ /**
+ * Constructor: OpenLayers.Control.Attribution
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ * Destroy control.
+ */
+ destroy: function() {
+ this.map.events.un({
+ "removelayer": this.updateAttribution,
+ "addlayer": this.updateAttribution,
+ "changelayer": this.updateAttribution,
+ "changebaselayer": this.updateAttribution,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Initialize control.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ this.map.events.on({
+ 'changebaselayer': this.updateAttribution,
+ 'changelayer': this.updateAttribution,
+ 'addlayer': this.updateAttribution,
+ 'removelayer': this.updateAttribution,
+ scope: this
+ });
+ this.updateAttribution();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateAttribution
+ * Update attribution string.
+ */
+ updateAttribution: function() {
+ var attributions = [];
+ if (this.map && this.map.layers) {
+ for(var i=0, len=this.map.layers.length; i<len; i++) {
+ var layer = this.map.layers[i];
+ if (layer.attribution && layer.getVisibility()) {
+ // add attribution only if attribution text is unique
+ if (OpenLayers.Util.indexOf(
+ attributions, layer.attribution) === -1) {
+ attributions.push( layer.attribution );
+ }
+ }
+ }
+ this.div.innerHTML = OpenLayers.String.format(this.template, {
+ layers: attributions.join(this.separator)
+ });
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Attribution"
+});
+/* ======================================================================
+ OpenLayers/Kinetic.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+OpenLayers.Kinetic = OpenLayers.Class({
+
+ /**
+ * Property: threshold
+ * In most cases changing the threshold isn't needed.
+ * In px/ms, default to 0.
+ */
+ threshold: 0,
+
+ /**
+ * Property: deceleration
+ * {Float} the deseleration in px/ms², default to 0.0035.
+ */
+ deceleration: 0.0035,
+
+ /**
+ * Property: nbPoints
+ * {Integer} the number of points we use to calculate the kinetic
+ * initial values.
+ */
+ nbPoints: 100,
+
+ /**
+ * Property: delay
+ * {Float} time to consider to calculate the kinetic initial values.
+ * In ms, default to 200.
+ */
+ delay: 200,
+
+ /**
+ * Property: points
+ * List of points use to calculate the kinetic initial values.
+ */
+ points: undefined,
+
+ /**
+ * Property: timerId
+ * ID of the timer.
+ */
+ timerId: undefined,
+
+ /**
+ * Constructor: OpenLayers.Kinetic
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: begin
+ * Begins the dragging.
+ */
+ begin: function() {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = undefined;
+ this.points = [];
+ },
+
+ /**
+ * Method: update
+ * Updates during the dragging.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The new position.
+ */
+ update: function(xy) {
+ this.points.unshift({xy: xy, tick: new Date().getTime()});
+ if (this.points.length > this.nbPoints) {
+ this.points.pop();
+ }
+ },
+
+ /**
+ * Method: end
+ * Ends the dragging, start the kinetic.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The last position.
+ *
+ * Returns:
+ * {Object} An object with two properties: "speed", and "theta". The
+ * "speed" and "theta" values are to be passed to the move
+ * function when starting the animation.
+ */
+ end: function(xy) {
+ var last, now = new Date().getTime();
+ for (var i = 0, l = this.points.length, point; i < l; i++) {
+ point = this.points[i];
+ if (now - point.tick > this.delay) {
+ break;
+ }
+ last = point;
+ }
+ if (!last) {
+ return;
+ }
+ var time = new Date().getTime() - last.tick;
+ var dist = Math.sqrt(Math.pow(xy.x - last.xy.x, 2) +
+ Math.pow(xy.y - last.xy.y, 2));
+ var speed = dist / time;
+ if (speed == 0 || speed < this.threshold) {
+ return;
+ }
+ var theta = Math.asin((xy.y - last.xy.y) / dist);
+ if (last.xy.x <= xy.x) {
+ theta = Math.PI - theta;
+ }
+ return {speed: speed, theta: theta};
+ },
+
+ /**
+ * Method: move
+ * Launch the kinetic move pan.
+ *
+ * Parameters:
+ * info - {Object} An object with two properties, "speed", and "theta".
+ * These values are those returned from the "end" call.
+ * callback - {Function} Function called on every step of the animation,
+ * receives x, y (values to pan), end (is the last point).
+ */
+ move: function(info, callback) {
+ var v0 = info.speed;
+ var fx = Math.cos(info.theta);
+ var fy = -Math.sin(info.theta);
+
+ var initialTime = new Date().getTime();
+
+ var lastX = 0;
+ var lastY = 0;
+
+ var timerCallback = function() {
+ if (this.timerId == null) {
+ return;
+ }
+
+ var t = new Date().getTime() - initialTime;
+
+ var p = (-this.deceleration * Math.pow(t, 2)) / 2.0 + v0 * t;
+ var x = p * fx;
+ var y = p * fy;
+
+ var args = {};
+ args.end = false;
+ var v = -this.deceleration * t + v0;
+
+ if (v <= 0) {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = null;
+ args.end = true;
+ }
+
+ args.x = x - lastX;
+ args.y = y - lastY;
+ lastX = x;
+ lastY = y;
+ callback(args.x, args.y, args.end);
+ };
+
+ this.timerId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(timerCallback, this)
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Kinetic"
+});
+/* ======================================================================
+ OpenLayers/Layer/WMS.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMS
+ * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web
+ * Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: { service: "WMS",
+ version: "1.1.1",
+ request: "GetMap",
+ styles: "",
+ format: "image/jpeg"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for WMS layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: encodeBBOX
+ * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
+ * but some services want it that way. Default false.
+ */
+ encodeBBOX: false,
+
+ /**
+ * APIProperty: noMagic
+ * {Boolean} If true, the image format will not be automagicaly switched
+ * from image/jpeg to image/png or image/gif when using
+ * TRANSPARENT=TRUE. Also isBaseLayer will not changed by the
+ * constructor. Default false.
+ */
+ noMagic: false,
+
+ /**
+ * Property: yx
+ * {Object} Keys in this object are EPSG codes for which the axis order
+ * is to be reversed (yx instead of xy, LatLon instead of LonLat), with
+ * true as value. This is only relevant for WMS versions >= 1.3.0, and
+ * only if yx is not set in <OpenLayers.Projection.defaults> for the
+ * used projection.
+ */
+ yx: {},
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS
+ * Create a new WMS layer object
+ *
+ * Examples:
+ *
+ * The code below creates a simple WMS layer using the image/jpeg format.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {layers: "modis,global_mosaic"});
+ * (end)
+ * Note the 3rd argument (params). Properties added to this object will be
+ * added to the WMS GetMap requests used for this layer's tiles. The only
+ * mandatory parameter is "layers". Other common WMS params include
+ * "transparent", "styles" and "format". Note that the "srs" param will
+ * always be ignored. Instead, it will be derived from the baseLayer's or
+ * map's projection.
+ *
+ * The code below creates a transparent WMS layer with additional options.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {
+ * layers: "modis,global_mosaic",
+ * transparent: true
+ * }, {
+ * opacity: 0.5,
+ * singleTile: true
+ * });
+ * (end)
+ * Note that by default, a WMS layer is configured as baseLayer. Setting
+ * the "transparent" param to true will apply some magic (see <noMagic>).
+ * The default image format changes from image/jpeg to image/png, and the
+ * layer is not configured as baseLayer.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the WMS
+ * (e.g. http://wms.jpl.nasa.gov/wms.cgi)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ * These options include all properties listed above, plus the ones
+ * inherited from superclasses.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) {
+ params.EXCEPTIONS = "INIMAGE";
+ }
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+
+ //layer is transparent
+ if (!this.noMagic && this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "image/jpeg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
+ : "image/png";
+ }
+ }
+
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMS(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: reverseAxisOrder
+ * Returns true if the axis order is reversed for the WMS version and
+ * projection of the layer.
+ *
+ * Returns:
+ * {Boolean} true if the axis order is reversed, false otherwise.
+ */
+ reverseAxisOrder: function() {
+ var projCode = this.projection.getCode();
+ return parseFloat(this.params.VERSION) >= 1.3 &&
+ !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] &&
+ OpenLayers.Projection.defaults[projCode].yx));
+ },
+
+ /**
+ * Method: getURL
+ * Return a GetMap query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+
+ var imageSize = this.getImageSize();
+ var newParams = {};
+ // WMS 1.3 introduced axis order
+ var reverseAxisOrder = this.reverseAxisOrder();
+ newParams.BBOX = this.encodeBBOX ?
+ bounds.toBBOX(null, reverseAxisOrder) :
+ bounds.toArray(reverseAxisOrder);
+ newParams.WIDTH = imageSize.w;
+ newParams.HEIGHT = imageSize.h;
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ /**
+ * APIMethod: getFullRequestString
+ * Combine the layer's url with its params and these newParams.
+ *
+ * Add the SRS parameter from projection -- this is probably
+ * more eloquently done via a setProjection() method, but this
+ * works for now and always.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ var mapProjection = this.map.getProjectionObject();
+ var projectionCode = this.projection && this.projection.equals(mapProjection) ?
+ this.projection.getCode() :
+ mapProjection.getCode();
+ var value = (projectionCode == "none") ? null : projectionCode;
+ if (parseFloat(this.params.VERSION) >= 1.3) {
+ this.params.CRS = value;
+ } else {
+ this.params.SRS = value;
+ }
+
+ if (typeof this.params.TRANSPARENT == "boolean") {
+ newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE";
+ }
+
+ return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+ this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMS"
+});
+/* ======================================================================
+ OpenLayers/Renderer/SVG.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: "http://www.w3.org/2000/svg",
+
+ /**
+ * Property: xlinkns
+ * {String}
+ */
+ xlinkns: "http://www.w3.org/1999/xlink",
+
+ /**
+ * Constant: MAX_PIXEL
+ * {Integer} Firefox has a limitation where values larger or smaller than
+ * about 15000 in an SVG document lock the browser up. This
+ * works around it.
+ */
+ MAX_PIXEL: 15000,
+
+ /**
+ * Property: translationParameters
+ * {Object} Hash with "x" and "y" properties
+ */
+ translationParameters: null,
+
+ /**
+ * Property: symbolMetrics
+ * {Object} Cache for symbol metrics according to their svg coordinate
+ * space. This is an object keyed by the symbol's id, and values are
+ * an array of [width, centerX, centerY].
+ */
+ symbolMetrics: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.SVG
+ *
+ * Parameters:
+ * containerID - {String}
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ this.translationParameters = {x: 0, y: 0};
+
+ this.symbolMetrics = {};
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SVG renderer
+ */
+ supported: function() {
+ var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+ return (document.implementation &&
+ (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
+ document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
+ document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+ },
+
+ /**
+ * Method: inValidRange
+ * See #669 for more information
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ * xyOnly - {Boolean} whether or not to just check for x and y, which means
+ * to not take the current translation parameters into account if true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the 'x' and 'y' coordinates are in the
+ * valid range.
+ */
+ inValidRange: function(x, y, xyOnly) {
+ var left = x + (xyOnly ? 0 : this.translationParameters.x);
+ var top = y + (xyOnly ? 0 : this.translationParameters.y);
+ return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+ top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+ },
+
+ /**
+ * Method: setExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+
+ var resolution = this.getResolution(),
+ left = -extent.left / resolution,
+ top = extent.top / resolution;
+
+ // If the resolution has changed, start over changing the corner, because
+ // the features will redraw.
+ if (resolutionChanged) {
+ this.left = left;
+ this.top = top;
+ // Set the viewbox
+ var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+ this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+ this.translate(this.xOffset, 0);
+ return true;
+ } else {
+ var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
+ if (!inRange) {
+ // recenter the coordinate system
+ this.setExtent(extent, true);
+ }
+ return coordSysUnchanged && inRange;
+ }
+ },
+
+ /**
+ * Method: translate
+ * Transforms the SVG coordinate system
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ *
+ * Returns:
+ * {Boolean} true if the translation parameters are in the valid coordinates
+ * range, false otherwise.
+ */
+ translate: function(x, y) {
+ if (!this.inValidRange(x, y, true)) {
+ return false;
+ } else {
+ var transformString = "";
+ if (x || y) {
+ transformString = "translate(" + x + "," + y + ")";
+ }
+ this.root.setAttributeNS(null, "transform", transformString);
+ this.translationParameters = {x: x, y: y};
+ return true;
+ }
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+ this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+ },
+
+ /**
+ * Method: getNodeType
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "image";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "svg";
+ } else {
+ nodeType = "circle";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ nodeType = "polyline";
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ nodeType = "polygon";
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "path";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a SVG node.
+ *
+ * Takes care to adjust stroke width and point radius to be
+ * resolution-relative
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ */
+ setStyle: function(node, style, options) {
+ style = style || node._style;
+ options = options || node._options;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.setAttributeNS(null, "title", title);
+ //Standards-conformant SVG
+ // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
+ var titleNode = node.getElementsByTagName("title");
+ if (titleNode.length > 0) {
+ titleNode[0].firstChild.textContent = title;
+ } else {
+ var label = this.nodeFactory(null, "title");
+ label.textContent = title;
+ node.appendChild(label);
+ }
+ }
+
+ var r = parseFloat(node.getAttributeNS(null, "r"));
+ var widthFactor = 1;
+ var pos;
+ if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+ node.style.visibility = "";
+ if (style.graphic === false) {
+ node.style.visibility = "hidden";
+ } else if (style.externalGraphic) {
+ pos = this.getPosition(node);
+ if (style.graphicWidth && style.graphicHeight) {
+ node.setAttributeNS(null, "preserveAspectRatio", "none");
+ }
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+ node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+ node.setAttributeNS(null, "width", width);
+ node.setAttributeNS(null, "height", height);
+ node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
+ node.setAttributeNS(null, "style", "opacity: "+opacity);
+ node.onclick = OpenLayers.Event.preventDefault;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ // the symbol viewBox is three times as large as the symbol
+ var offset = style.pointRadius * 3;
+ var size = offset * 2;
+ var src = this.importSymbol(style.graphicName);
+ pos = this.getPosition(node);
+ widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
+
+ // remove the node from the dom before we modify it. This
+ // prevents various rendering issues in Safari and FF
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+ if(parent) {
+ parent.removeChild(node);
+ }
+
+ // The more appropriate way to implement this would be use/defs,
+ // but due to various issues in several browsers, it is safer to
+ // copy the symbols instead of referencing them.
+ // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
+ // and this email thread
+ // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
+ node.firstChild && node.removeChild(node.firstChild);
+ node.appendChild(src.firstChild.cloneNode(true));
+ node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
+
+ node.setAttributeNS(null, "width", size);
+ node.setAttributeNS(null, "height", size);
+ node.setAttributeNS(null, "x", pos.x - offset);
+ node.setAttributeNS(null, "y", pos.y - offset);
+
+ // now that the node has all its new properties, insert it
+ // back into the dom where it was
+ if(nextSibling) {
+ parent.insertBefore(node, nextSibling);
+ } else if(parent) {
+ parent.appendChild(node);
+ }
+ } else {
+ node.setAttributeNS(null, "r", style.pointRadius);
+ }
+
+ var rotation = style.rotation;
+
+ if ((rotation !== undefined || node._rotation !== undefined) && pos) {
+ node._rotation = rotation;
+ rotation |= 0;
+ if (node.nodeName !== "svg") {
+ node.setAttributeNS(null, "transform",
+ "rotate(" + rotation + " " + pos.x + " " +
+ pos.y + ")");
+ } else {
+ var metrics = this.symbolMetrics[src.id];
+ node.firstChild.setAttributeNS(null, "transform", "rotate("
+ + rotation + " "
+ + metrics[1] + " "
+ + metrics[2] + ")");
+ }
+ }
+ }
+
+ if (options.isFilled) {
+ node.setAttributeNS(null, "fill", style.fillColor);
+ node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+ } else {
+ node.setAttributeNS(null, "fill", "none");
+ }
+
+ if (options.isStroked) {
+ node.setAttributeNS(null, "stroke", style.strokeColor);
+ node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+ node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+ node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
+ // Hard-coded linejoin for now, to make it look the same as in VML.
+ // There is no strokeLinejoin property yet for symbolizers.
+ node.setAttributeNS(null, "stroke-linejoin", "round");
+ style.strokeDashstyle && node.setAttributeNS(null,
+ "stroke-dasharray", this.dashStyle(style, widthFactor));
+ } else {
+ node.setAttributeNS(null, "stroke", "none");
+ }
+
+ if (style.pointerEvents) {
+ node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+ }
+
+ if (style.cursor != null) {
+ node.setAttributeNS(null, "cursor", style.cursor);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ * widthFactor - {Number}
+ *
+ * Returns:
+ * {String} A SVG compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style, widthFactor) {
+ var w = style.strokeWidth * widthFactor;
+ var str = style.strokeDashstyle;
+ switch (str) {
+ case 'solid':
+ return 'none';
+ case 'dot':
+ return [1, 4 * w].join();
+ case 'dash':
+ return [4 * w, 4 * w].join();
+ case 'dashdot':
+ return [4 * w, 4 * w, 1, 4 * w].join();
+ case 'longdash':
+ return [8 * w, 4 * w].join();
+ case 'longdashdot':
+ return [8 * w, 4 * w, 1, 4 * w].join();
+ default:
+ return OpenLayers.String.trim(str).replace(/\s+/g, ",");
+ }
+ },
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElementNS(this.xmlns, type);
+ if (id) {
+ node.setAttributeNS(null, "id", id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+ return (type == node.nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
+ svg.style.display = "block";
+ return svg;
+ },
+
+ /**
+ * Method: createRoot
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "g");
+ },
+
+ /**
+ * Method: createDefs
+ *
+ * Returns:
+ * {DOMElement} The element to which we'll add the symbol definitions
+ */
+ createDefs: function() {
+ var defs = this.nodeFactory(this.container.id + "_defs", "defs");
+ this.rendererRoot.appendChild(defs);
+ return defs;
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {Float}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry, radius) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "cx", x);
+ node.setAttributeNS(null, "cy", y);
+ node.setAttributeNS(null, "r", radius);
+ return node;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {
+ var d = "";
+ var draw = true;
+ var complete = true;
+ var linearRingResult, path;
+ for (var j=0, len=geometry.components.length; j<len; j++) {
+ d += " M";
+ linearRingResult = this.getComponentsString(
+ geometry.components[j].components, " ");
+ path = linearRingResult.path;
+ if (path) {
+ d += " " + path;
+ complete = linearRingResult.complete && complete;
+ } else {
+ draw = false;
+ }
+ }
+ d += " z";
+ if (draw) {
+ node.setAttributeNS(null, "d", d);
+ node.setAttributeNS(null, "fill-rule", "evenodd");
+ return complete ? node : null;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawRectangle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "x", x);
+ node.setAttributeNS(null, "y", y);
+ node.setAttributeNS(null, "width", geometry.width / resolution);
+ node.setAttributeNS(null, "height", geometry.height / resolution);
+ return node;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var drawOutline = (!!style.labelOutlineWidth);
+ // First draw text in halo color and size and overlay the
+ // normal text afterwards
+ if (drawOutline) {
+ var outlineStyle = OpenLayers.Util.extend({}, style);
+ outlineStyle.fontColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
+ if (style.labelOutlineOpacity) {
+ outlineStyle.fontOpacity = style.labelOutlineOpacity;
+ }
+ delete outlineStyle.labelOutlineWidth;
+ this.drawText(featureId, outlineStyle, location);
+ }
+
+ var resolution = this.getResolution();
+
+ var x = ((location.x - this.featureDx) / resolution + this.left);
+ var y = (location.y / resolution - this.top);
+
+ var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
+ var label = this.nodeFactory(featureId + suffix, "text");
+
+ label.setAttributeNS(null, "x", x);
+ label.setAttributeNS(null, "y", -y);
+
+ if (style.fontColor) {
+ label.setAttributeNS(null, "fill", style.fontColor);
+ }
+ if (style.fontStrokeColor) {
+ label.setAttributeNS(null, "stroke", style.fontStrokeColor);
+ }
+ if (style.fontStrokeWidth) {
+ label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
+ }
+ if (style.fontOpacity) {
+ label.setAttributeNS(null, "opacity", style.fontOpacity);
+ }
+ if (style.fontFamily) {
+ label.setAttributeNS(null, "font-family", style.fontFamily);
+ }
+ if (style.fontSize) {
+ label.setAttributeNS(null, "font-size", style.fontSize);
+ }
+ if (style.fontWeight) {
+ label.setAttributeNS(null, "font-weight", style.fontWeight);
+ }
+ if (style.fontStyle) {
+ label.setAttributeNS(null, "font-style", style.fontStyle);
+ }
+ if (style.labelSelect === true) {
+ label.setAttributeNS(null, "pointer-events", "visible");
+ label._featureId = featureId;
+ } else {
+ label.setAttributeNS(null, "pointer-events", "none");
+ }
+ var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
+ label.setAttributeNS(null, "text-anchor",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
+
+ if (OpenLayers.IS_GECKO === true) {
+ label.setAttributeNS(null, "dominant-baseline",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
+ }
+
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ while (label.childNodes.length > numRows) {
+ label.removeChild(label.lastChild);
+ }
+ for (var i = 0; i < numRows; i++) {
+ var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
+ if (style.labelSelect === true) {
+ tspan._featureId = featureId;
+ tspan._geometry = location;
+ tspan._geometryClass = location.CLASS_NAME;
+ }
+ if (OpenLayers.IS_GECKO === false) {
+ tspan.setAttributeNS(null, "baseline-shift",
+ OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
+ }
+ tspan.setAttribute("x", x);
+ if (i == 0) {
+ var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
+ } else {
+ tspan.setAttribute("dy", "1em");
+ }
+ tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
+ if (!tspan.parentNode) {
+ label.appendChild(tspan);
+ }
+ }
+
+ if (!label.parentNode) {
+ this.textRoot.appendChild(label);
+ }
+ },
+
+ /**
+ * Method: getComponentString
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+ * separator - {String} character between coordinate pairs. Defaults to ","
+ *
+ * Returns:
+ * {Object} hash with properties "path" (the string created from the
+ * components and "complete" (false if the renderer was unable to
+ * draw all components)
+ */
+ getComponentsString: function(components, separator) {
+ var renderCmp = [];
+ var complete = true;
+ var len = components.length;
+ var strings = [];
+ var str, component;
+ for(var i=0; i<len; i++) {
+ component = components[i];
+ renderCmp.push(component);
+ str = this.getShortString(component);
+ if (str) {
+ strings.push(str);
+ } else {
+ // The current component is outside the valid range. Let's
+ // see if the previous or next component is inside the range.
+ // If so, add the coordinate of the intersection with the
+ // valid range bounds.
+ if (i > 0) {
+ if (this.getShortString(components[i - 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i-1]));
+ }
+ }
+ if (i < len - 1) {
+ if (this.getShortString(components[i + 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i+1]));
+ }
+ }
+ complete = false;
+ }
+ }
+
+ return {
+ path: strings.join(separator || ","),
+ complete: complete
+ };
+ },
+
+ /**
+ * Method: clipLine
+ * Given two points (one inside the valid range, and one outside),
+ * clips the line betweeen the two points so that the new points are both
+ * inside the valid range.
+ *
+ * Parameters:
+ * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * invalid point
+ * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * valid point
+ * Returns
+ * {String} the SVG coordinate pair of the clipped point (like
+ * getShortString), or an empty string if both passed componets are at
+ * the same point.
+ */
+ clipLine: function(badComponent, goodComponent) {
+ if (goodComponent.equals(badComponent)) {
+ return "";
+ }
+ var resolution = this.getResolution();
+ var maxX = this.MAX_PIXEL - this.translationParameters.x;
+ var maxY = this.MAX_PIXEL - this.translationParameters.y;
+ var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
+ var y1 = this.top - goodComponent.y / resolution;
+ var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
+ var y2 = this.top - badComponent.y / resolution;
+ var k;
+ if (x2 < -maxX || x2 > maxX) {
+ k = (y2 - y1) / (x2 - x1);
+ x2 = x2 < 0 ? -maxX : maxX;
+ y2 = y1 + (x2 - x1) * k;
+ }
+ if (y2 < -maxY || y2 > maxY) {
+ k = (x2 - x1) / (y2 - y1);
+ y2 = y2 < 0 ? -maxY : maxY;
+ x2 = x1 + (y2 - y1) * k;
+ }
+ return x2 + "," + y2;
+ },
+
+ /**
+ * Method: getShortString
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {String} or false if point is outside the valid range
+ */
+ getShortString: function(point) {
+ var resolution = this.getResolution();
+ var x = ((point.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - point.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ return x + "," + y;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getPosition
+ * Finds the position of an svg node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} hash with x and y properties, representing the coordinates
+ * within the svg coordinate system
+ */
+ getPosition: function(node) {
+ return({
+ x: parseFloat(node.getAttributeNS(null, "cx")),
+ y: parseFloat(node.getAttributeNS(null, "cy"))
+ });
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {DOMElement} - the imported symbol
+ */
+ importSymbol: function (graphicName) {
+ if (!this.defs) {
+ // create svg defs tag
+ this.defs = this.createDefs();
+ }
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the defs
+ var existing = document.getElementById(id);
+ if (existing != null) {
+ return existing;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolNode = this.nodeFactory(id, "symbol");
+ var node = this.nodeFactory(null, "polygon");
+ symbolNode.appendChild(node);
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var points = [];
+ var x,y;
+ for (var i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+ points.push(x, ",", y);
+ }
+
+ node.setAttributeNS(null, "points", points.join(" "));
+
+ var width = symbolExtent.getWidth();
+ var height = symbolExtent.getHeight();
+ // create a viewBox three times as large as the symbol itself,
+ // to allow for strokeWidth being displayed correctly at the corners.
+ var viewBox = [symbolExtent.left - width,
+ symbolExtent.bottom - height, width * 3, height * 3];
+ symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+ this.symbolMetrics[id] = [
+ Math.max(width, height),
+ symbolExtent.getCenterLonLat().lon,
+ symbolExtent.getCenterLonLat().lat
+ ];
+
+ this.defs.appendChild(symbolNode);
+ return symbolNode;
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
+ if(!featureId) {
+ var target = evt.target;
+ featureId = target.parentNode && target != this.rendererRoot ?
+ target.parentNode._featureId : undefined;
+ }
+ return featureId;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_ALIGN = {
+ "l": "start",
+ "r": "end",
+ "b": "bottom",
+ "t": "hanging"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
+ // according to
+ // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
+ // a baseline-shift of -70% shifts the text exactly from the
+ // bottom to the top of the baseline, so -35% moves the text to
+ // the center of the baseline.
+ "t": "-70%",
+ "b": "0"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Function: OpenLayers.Renderer.SVG.preventDefault
+ * *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead.
+ * Used to prevent default events (especially opening images in a new tab on
+ * ctrl-click) from being executed for externalGraphic symbols
+ */
+OpenLayers.Renderer.SVG.preventDefault = function(e) {
+ OpenLayers.Event.preventDefault(e);
+};
+/* ======================================================================
+ OpenLayers/Format/JSON.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * Note:
+ * This work draws heavily from the public domain JSON serializer/deserializer
+ * at http://www.json.org/json.js. Rewritten so that it doesn't modify
+ * basic data prototypes.
+ */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.JSON
+ * A parser to read/write JSON safely. Create a new instance with the
+ * <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: indent
+ * {String} For "pretty" printing, the indent string will be used once for
+ * each indentation level.
+ */
+ indent: " ",
+
+ /**
+ * APIProperty: space
+ * {String} For "pretty" printing, the space string will be used after
+ * the ":" separating a name/value pair.
+ */
+ space: " ",
+
+ /**
+ * APIProperty: newline
+ * {String} For "pretty" printing, the newline string will be used at the
+ * end of each name/value pair or array item.
+ */
+ newline: "\n",
+
+ /**
+ * Property: level
+ * {Integer} For "pretty" printing, this is incremented/decremented during
+ * serialization.
+ */
+ level: 0,
+
+ /**
+ * Property: pretty
+ * {Boolean} Serialize with extra whitespace for structure. This is set
+ * by the <write> method.
+ */
+ pretty: false,
+
+ /**
+ * Property: nativeJSON
+ * {Boolean} Does the browser support native json?
+ */
+ nativeJSON: (function() {
+ return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function");
+ })(),
+
+ /**
+ * Constructor: OpenLayers.Format.JSON
+ * Create a new parser for JSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a json string.
+ *
+ * Parameters:
+ * json - {String} A JSON string
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} An object, array, string, or number .
+ */
+ read: function(json, filter) {
+ var object;
+ if (this.nativeJSON) {
+ object = JSON.parse(json, filter);
+ } else try {
+ /**
+ * Parsing happens in three stages. In the first stage, we run the
+ * text against a regular expression which looks for non-JSON
+ * characters. We are especially concerned with '()' and 'new'
+ * because they can cause invocation, and '=' because it can
+ * cause mutation. But just to be safe, we will reject all
+ * unexpected characters.
+ */
+ if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+ /**
+ * In the second stage we use the eval function to compile the
+ * text into a JavaScript structure. The '{' operator is
+ * subject to a syntactic ambiguity in JavaScript - it can
+ * begin a block or an object literal. We wrap the text in
+ * parens to eliminate the ambiguity.
+ */
+ object = eval('(' + json + ')');
+
+ /**
+ * In the optional third stage, we recursively walk the new
+ * structure, passing each name/value pair to a filter
+ * function for possible transformation.
+ */
+ if(typeof filter === 'function') {
+ function walk(k, v) {
+ if(v && typeof v === 'object') {
+ for(var i in v) {
+ if(v.hasOwnProperty(i)) {
+ v[i] = walk(i, v[i]);
+ }
+ }
+ }
+ return filter(k, v);
+ }
+ object = walk('', object);
+ }
+ }
+ } catch(e) {
+ // Fall through if the regexp test fails.
+ }
+
+ if(this.keepData) {
+ this.data = object;
+ }
+
+ return object;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize an object into a JSON string.
+ *
+ * Parameters:
+ * value - {String} The object, array, string, number, boolean or date
+ * to be serialized.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The JSON string representation of the input value.
+ */
+ write: function(value, pretty) {
+ this.pretty = !!pretty;
+ var json = null;
+ var type = typeof value;
+ if(this.serialize[type]) {
+ try {
+ json = (!this.pretty && this.nativeJSON) ?
+ JSON.stringify(value) :
+ this.serialize[type].apply(this, [value]);
+ } catch(err) {
+ OpenLayers.Console.error("Trouble serializing: " + err);
+ }
+ }
+ return json;
+ },
+
+ /**
+ * Method: writeIndent
+ * Output an indentation string depending on the indentation level.
+ *
+ * Returns:
+ * {String} An appropriate indentation string.
+ */
+ writeIndent: function() {
+ var pieces = [];
+ if(this.pretty) {
+ for(var i=0; i<this.level; ++i) {
+ pieces.push(this.indent);
+ }
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: writeNewline
+ * Output a string representing a newline if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A string representing a new line.
+ */
+ writeNewline: function() {
+ return (this.pretty) ? this.newline : '';
+ },
+
+ /**
+ * Method: writeSpace
+ * Output a string representing a space if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A space.
+ */
+ writeSpace: function() {
+ return (this.pretty) ? this.space : '';
+ },
+
+ /**
+ * Property: serialize
+ * Object with properties corresponding to the serializable data types.
+ * Property values are functions that do the actual serializing.
+ */
+ serialize: {
+ /**
+ * Method: serialize.object
+ * Transform an object into a JSON string.
+ *
+ * Parameters:
+ * object - {Object} The object to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the object.
+ */
+ 'object': function(object) {
+ // three special objects that we want to treat differently
+ if(object == null) {
+ return "null";
+ }
+ if(object.constructor == Date) {
+ return this.serialize.date.apply(this, [object]);
+ }
+ if(object.constructor == Array) {
+ return this.serialize.array.apply(this, [object]);
+ }
+ var pieces = ['{'];
+ this.level += 1;
+ var key, keyJSON, valueJSON;
+
+ var addComma = false;
+ for(key in object) {
+ if(object.hasOwnProperty(key)) {
+ // recursive calls need to allow for sub-classing
+ keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [key, this.pretty]);
+ valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [object[key], this.pretty]);
+ if(keyJSON != null && valueJSON != null) {
+ if(addComma) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(),
+ keyJSON, ':', this.writeSpace(), valueJSON);
+ addComma = true;
+ }
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), '}');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.array
+ * Transform an array into a JSON string.
+ *
+ * Parameters:
+ * array - {Array} The array to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the array.
+ */
+ 'array': function(array) {
+ var json;
+ var pieces = ['['];
+ this.level += 1;
+
+ for(var i=0, len=array.length; i<len; ++i) {
+ // recursive calls need to allow for sub-classing
+ json = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [array[i], this.pretty]);
+ if(json != null) {
+ if(i > 0) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(), json);
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), ']');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.string
+ * Transform a string into a JSON string.
+ *
+ * Parameters:
+ * string - {String} The string to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the string.
+ */
+ 'string': function(string) {
+ // If the string contains no control characters, no quote characters, and no
+ // backslash characters, then we can simply slap some quotes around it.
+ // Otherwise we must also replace the offending characters with safe
+ // sequences.
+ var m = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+ if(/["\\\x00-\x1f]/.test(string)) {
+ return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c) {
+ return c;
+ }
+ c = b.charCodeAt();
+ return '\\u00' +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + string + '"';
+ },
+
+ /**
+ * Method: serialize.number
+ * Transform a number into a JSON string.
+ *
+ * Parameters:
+ * number - {Number} The number to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the number.
+ */
+ 'number': function(number) {
+ return isFinite(number) ? String(number) : "null";
+ },
+
+ /**
+ * Method: serialize.boolean
+ * Transform a boolean into a JSON string.
+ *
+ * Parameters:
+ * bool - {Boolean} The boolean to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the boolean.
+ */
+ 'boolean': function(bool) {
+ return String(bool);
+ },
+
+ /**
+ * Method: serialize.object
+ * Transform a date into a JSON string.
+ *
+ * Parameters:
+ * date - {Date} The date to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the date.
+ */
+ 'date': function(date) {
+ function format(number) {
+ // Format integers to have at least two digits.
+ return (number < 10) ? '0' + number : number;
+ }
+ return '"' + date.getFullYear() + '-' +
+ format(date.getMonth() + 1) + '-' +
+ format(date.getDate()) + 'T' +
+ format(date.getHours()) + ':' +
+ format(date.getMinutes()) + ':' +
+ format(date.getSeconds()) + '"';
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.JSON"
+
+});
+/* ======================================================================
+ OpenLayers/Format/GeoJSON.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ * <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.JSON>
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+ /**
+ * APIProperty: ignoreExtraDims
+ * {Boolean} Ignore dimensions higher than 2 when reading geometry
+ * coordinates.
+ */
+ ignoreExtraDims: false,
+
+ /**
+ * Constructor: OpenLayers.Format.GeoJSON
+ * Create a new parser for GeoJSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a GeoJSON string.
+ *
+ * Parameters:
+ * json - {String} A GeoJSON string
+ * type - {String} Optional string that determines the structure of
+ * the output. Supported values are "Geometry", "Feature", and
+ * "FeatureCollection". If absent or null, a default of
+ * "FeatureCollection" is assumed.
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} The return depends on the value of the type argument. If type
+ * is "FeatureCollection" (the default), the return will be an array
+ * of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+ * must represent a single geometry, and the return will be an
+ * <OpenLayers.Geometry>. If type is "Feature", the input json must
+ * represent a single feature, and the return will be an
+ * <OpenLayers.Feature.Vector>.
+ */
+ read: function(json, type, filter) {
+ type = (type) ? type : "FeatureCollection";
+ var results = null;
+ var obj = null;
+ if (typeof json == "string") {
+ obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+ [json, filter]);
+ } else {
+ obj = json;
+ }
+ if(!obj) {
+ OpenLayers.Console.error("Bad JSON: " + json);
+ } else if(typeof(obj.type) != "string") {
+ OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+ } else if(this.isValidType(obj, type)) {
+ switch(type) {
+ case "Geometry":
+ try {
+ results = this.parseGeometry(obj);
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "Feature":
+ try {
+ results = this.parseFeature(obj);
+ results.type = "Feature";
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ // for type FeatureCollection, we allow input to be any type
+ results = [];
+ switch(obj.type) {
+ case "Feature":
+ try {
+ results.push(this.parseFeature(obj));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ for(var i=0, len=obj.features.length; i<len; ++i) {
+ try {
+ results.push(this.parseFeature(obj.features[i]));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ default:
+ try {
+ var geom = this.parseGeometry(obj);
+ results.push(new OpenLayers.Feature.Vector(geom));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: isValidType
+ * Check if a GeoJSON object is a valid representative of the given type.
+ *
+ * Returns:
+ * {Boolean} The object is valid GeoJSON object of the given type.
+ */
+ isValidType: function(obj, type) {
+ var valid = false;
+ switch(type) {
+ case "Geometry":
+ if(OpenLayers.Util.indexOf(
+ ["Point", "MultiPoint", "LineString", "MultiLineString",
+ "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+ obj.type) == -1) {
+ // unsupported geometry type
+ OpenLayers.Console.error("Unsupported geometry type: " +
+ obj.type);
+ } else {
+ valid = true;
+ }
+ break;
+ case "FeatureCollection":
+ // allow for any type to be converted to a feature collection
+ valid = true;
+ break;
+ default:
+ // for Feature types must match
+ if(obj.type == type) {
+ valid = true;
+ } else {
+ OpenLayers.Console.error("Cannot convert types from " +
+ obj.type + " to " + type);
+ }
+ }
+ return valid;
+ },
+
+ /**
+ * Method: parseFeature
+ * Convert a feature object from GeoJSON into an
+ * <OpenLayers.Feature.Vector>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature.
+ */
+ parseFeature: function(obj) {
+ var feature, geometry, attributes, bbox;
+ attributes = (obj.properties) ? obj.properties : {};
+ bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
+ try {
+ geometry = this.parseGeometry(obj.geometry);
+ } catch(err) {
+ // deal with bad geometries
+ throw err;
+ }
+ feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ if(bbox) {
+ feature.bounds = OpenLayers.Bounds.fromArray(bbox);
+ }
+ if(obj.id) {
+ feature.fid = obj.id;
+ }
+ return feature;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ parseGeometry: function(obj) {
+ if (obj == null) {
+ return null;
+ }
+ var geometry, collection = false;
+ if(obj.type == "GeometryCollection") {
+ if(!(OpenLayers.Util.isArray(obj.geometries))) {
+ throw "GeometryCollection must have geometries array: " + obj;
+ }
+ var numGeom = obj.geometries.length;
+ var components = new Array(numGeom);
+ for(var i=0; i<numGeom; ++i) {
+ components[i] = this.parseGeometry.apply(
+ this, [obj.geometries[i]]
+ );
+ }
+ geometry = new OpenLayers.Geometry.Collection(components);
+ collection = true;
+ } else {
+ if(!(OpenLayers.Util.isArray(obj.coordinates))) {
+ throw "Geometry must have coordinates array: " + obj;
+ }
+ if(!this.parseCoords[obj.type.toLowerCase()]) {
+ throw "Unsupported geometry type: " + obj.type;
+ }
+ try {
+ geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+ this, [obj.coordinates]
+ );
+ } catch(err) {
+ // deal with bad coordinates
+ throw err;
+ }
+ }
+ // We don't reproject collections because the children are reprojected
+ // for us when they are created.
+ if (this.internalProjection && this.externalProjection && !collection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ return geometry;
+ },
+
+ /**
+ * Property: parseCoords
+ * Object with properties corresponding to the GeoJSON geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parseCoords: {
+ /**
+ * Method: parseCoords.point
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "point": function(array) {
+ if (this.ignoreExtraDims == false &&
+ array.length != 2) {
+ throw "Only 2D points are supported: " + array;
+ }
+ return new OpenLayers.Geometry.Point(array[0], array[1]);
+ },
+
+ /**
+ * Method: parseCoords.multipoint
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipoint": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPoint(points);
+ },
+
+ /**
+ * Method: parseCoords.linestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "linestring": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.LineString(points);
+ },
+
+ /**
+ * Method: parseCoords.multilinestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multilinestring": function(array) {
+ var lines = [];
+ var l = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ lines.push(l);
+ }
+ return new OpenLayers.Geometry.MultiLineString(lines);
+ },
+
+ /**
+ * Method: parseCoords.polygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "polygon": function(array) {
+ var rings = [];
+ var r, l;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ r = new OpenLayers.Geometry.LinearRing(l.components);
+ rings.push(r);
+ }
+ return new OpenLayers.Geometry.Polygon(rings);
+ },
+
+ /**
+ * Method: parseCoords.multipolygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipolygon": function(array) {
+ var polys = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["polygon"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ polys.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPolygon(polys);
+ },
+
+ /**
+ * Method: parseCoords.box
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "box": function(array) {
+ if(array.length != 2) {
+ throw "GeoJSON box coordinates must have 2 elements";
+ }
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+ ])
+ ]);
+ }
+
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature, geometry, array of features into a GeoJSON string.
+ *
+ * Parameters:
+ * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+ * or an array of features.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The GeoJSON string representation of the input geometry,
+ * features, or array of features.
+ */
+ write: function(obj, pretty) {
+ var geojson = {
+ "type": null
+ };
+ if(OpenLayers.Util.isArray(obj)) {
+ geojson.type = "FeatureCollection";
+ var numFeatures = obj.length;
+ geojson.features = new Array(numFeatures);
+ for(var i=0; i<numFeatures; ++i) {
+ var element = obj[i];
+ if(!element instanceof OpenLayers.Feature.Vector) {
+ var msg = "FeatureCollection only supports collections " +
+ "of features: " + element;
+ throw msg;
+ }
+ geojson.features[i] = this.extract.feature.apply(
+ this, [element]
+ );
+ }
+ } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+ geojson = this.extract.geometry.apply(this, [obj]);
+ } else if (obj instanceof OpenLayers.Feature.Vector) {
+ geojson = this.extract.feature.apply(this, [obj]);
+ if(obj.layer && obj.layer.projection) {
+ geojson.crs = this.createCRSObject(obj);
+ }
+ }
+ return OpenLayers.Format.JSON.prototype.write.apply(this,
+ [geojson, pretty]);
+ },
+
+ /**
+ * Method: createCRSObject
+ * Create the CRS object for an object.
+ *
+ * Parameters:
+ * object - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object which can be assigned to the crs property
+ * of a GeoJSON object.
+ */
+ createCRSObject: function(object) {
+ var proj = object.layer.projection.toString();
+ var crs = {};
+ if (proj.match(/epsg:/i)) {
+ var code = parseInt(proj.substring(proj.indexOf(":") + 1));
+ if (code == 4326) {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
+ }
+ };
+ } else {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "EPSG:" + code
+ }
+ };
+ }
+ }
+ return crs;
+ },
+
+ /**
+ * Property: extract
+ * Object with properties corresponding to the GeoJSON types.
+ * Property values are functions that do the actual value extraction.
+ */
+ extract: {
+ /**
+ * Method: extract.feature
+ * Return a partial GeoJSON object representing a single feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object representing the point.
+ */
+ 'feature': function(feature) {
+ var geom = this.extract.geometry.apply(this, [feature.geometry]);
+ var json = {
+ "type": "Feature",
+ "properties": feature.attributes,
+ "geometry": geom
+ };
+ if (feature.fid != null) {
+ json.id = feature.fid;
+ }
+ return json;
+ },
+
+ /**
+ * Method: extract.geometry
+ * Return a GeoJSON object representing a single geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Object} An object representing the geometry.
+ */
+ 'geometry': function(geometry) {
+ if (geometry == null) {
+ return null;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var geometryType = geometry.CLASS_NAME.split('.')[2];
+ var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+ var json;
+ if(geometryType == "Collection") {
+ json = {
+ "type": "GeometryCollection",
+ "geometries": data
+ };
+ } else {
+ json = {
+ "type": geometryType,
+ "coordinates": data
+ };
+ }
+
+ return json;
+ },
+
+ /**
+ * Method: extract.point
+ * Return an array of coordinates from a point.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Array} An array of coordinates representing the point.
+ */
+ 'point': function(point) {
+ return [point.x, point.y];
+ },
+
+ /**
+ * Method: extract.multipoint
+ * Return an array of point coordinates from a multipoint.
+ *
+ * Parameters:
+ * multipoint - {<OpenLayers.Geometry.MultiPoint>}
+ *
+ * Returns:
+ * {Array} An array of point coordinate arrays representing
+ * the multipoint.
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0, len=multipoint.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.linestring
+ * Return an array of coordinate arrays from a linestring.
+ *
+ * Parameters:
+ * linestring - {<OpenLayers.Geometry.LineString>}
+ *
+ * Returns:
+ * {Array} An array of coordinate arrays representing
+ * the linestring.
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multilinestring
+ * Return an array of linestring arrays from a linestring.
+ *
+ * Parameters:
+ * multilinestring - {<OpenLayers.Geometry.MultiLineString>}
+ *
+ * Returns:
+ * {Array} An array of linestring arrays representing
+ * the multilinestring.
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.polygon
+ * Return an array of linear ring arrays from a polygon.
+ *
+ * Parameters:
+ * polygon - {<OpenLayers.Geometry.Polygon>}
+ *
+ * Returns:
+ * {Array} An array of linear ring arrays representing the polygon.
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0, len=polygon.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multipolygon
+ * Return an array of polygon arrays from a multipolygon.
+ *
+ * Parameters:
+ * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+ *
+ * Returns:
+ * {Array} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.collection
+ * Return an array of geometries from a geometry collection.
+ *
+ * Parameters:
+ * collection - {<OpenLayers.Geometry.Collection>}
+ *
+ * Returns:
+ * {Array} An array of geometry objects representing the geometry
+ * collection.
+ */
+ 'collection': function(collection) {
+ var len = collection.components.length;
+ var array = new Array(len);
+ for(var i=0; i<len; ++i) {
+ array[i] = this.extract.geometry.apply(
+ this, [collection.components[i]]
+ );
+ }
+ return array;
+ }
+
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoJSON"
+
+});
+/* ======================================================================
+ OpenLayers/Control/DrawFeature.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DrawFeature
+ * The DrawFeature control draws point, line or polygon features on a vector
+ * layer when active.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * featureadded - Triggered when a feature is added
+ */
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: featureAdded
+ * {Function} Called after each feature is added
+ */
+ featureAdded: function() {},
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.DrawFeature
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ * handler - {<OpenLayers.Handler>}
+ * options - {Object}
+ */
+ initialize: function(layer, handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.callbacks = OpenLayers.Util.extend(
+ {
+ done: this.drawFeature,
+ modify: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchmodified", {vertex: vertex, feature: feature}
+ );
+ },
+ create: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchstarted", {vertex: vertex, feature: feature}
+ );
+ }
+ },
+ this.callbacks
+ );
+ this.layer = layer;
+ this.handlerOptions = this.handlerOptions || {};
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions, {
+ renderers: layer.renderers, rendererOptions: layer.rendererOptions
+ }
+ );
+ if (!("multi" in this.handlerOptions)) {
+ this.handlerOptions.multi = this.multi;
+ }
+ var sketchStyle = this.layer.styleMap && this.layer.styleMap.styles.temporary;
+ if(sketchStyle) {
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions,
+ {styleMap: new OpenLayers.StyleMap({"default": sketchStyle})}
+ );
+ }
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * Method: drawFeature
+ */
+ drawFeature: function(geometry) {
+ var feature = new OpenLayers.Feature.Vector(geometry);
+ var proceed = this.layer.events.triggerEvent(
+ "sketchcomplete", {feature: feature}
+ );
+ if(proceed !== false) {
+ feature.state = OpenLayers.State.INSERT;
+ this.layer.addFeatures([feature]);
+ this.featureAdded(feature);
+ this.events.triggerEvent("featureadded",{feature : feature});
+ }
+ },
+
+ /**
+ * APIMethod: insertXY
+ * Insert a point in the current sketch given x & y coordinates.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertXY(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeltaXY(dx, dy);
+ }
+ },
+
+ /**
+ * APIMethod: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDirectionLength(direction, length);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeflectionLength(deflection, length);
+ }
+ },
+
+ /**
+ * APIMethod: undo
+ * Remove the most recently added point in the current sketch geometry.
+ *
+ * Returns:
+ * {Boolean} An edit was undone.
+ */
+ undo: function() {
+ return this.handler.undo && this.handler.undo();
+ },
+
+ /**
+ * APIMethod: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} An edit was redone.
+ */
+ redo: function() {
+ return this.handler.redo && this.handler.redo();
+ },
+
+ /**
+ * APIMethod: finishSketch
+ * Finishes the sketch without including the currently drawn point.
+ * This method can be called to terminate drawing programmatically
+ * instead of waiting for the user to end the sketch.
+ */
+ finishSketch: function() {
+ this.handler.finishGeometry();
+ },
+
+ /**
+ * APIMethod: cancel
+ * Cancel the current sketch. This removes the current sketch and keeps
+ * the drawing control active.
+ */
+ cancel: function() {
+ this.handler.cancel();
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DrawFeature"
+});
+/* ======================================================================
+ OpenLayers/Handler/Pinch.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Pinch
+ * The pinch handler is used to deal with sequences of browser events related
+ * to pinch gestures. The handler is used by controls that want to know
+ * when a pinch sequence begins, when a pinch is happening, and when it has
+ * finished.
+ *
+ * Controls that use the pinch handler typically construct it with callbacks
+ * for 'start', 'move', and 'done'. Callbacks for these keys are
+ * called when the pinch begins, with each change, and when the pinch is
+ * done.
+ *
+ * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a touchstart event is received, we want to record it,
+ * but not set 'pinching' until the touchmove get started after
+ * starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of touchstart events from getting to
+ * listeners on the same element. Default is false.
+ */
+ stopDown: false,
+
+ /**
+ * Property: pinching
+ * {Boolean}
+ */
+ pinching: false,
+
+ /**
+ * Property: last
+ * {Object} Object that store informations related to pinch last touch.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {Object} Object that store informations related to pinch touchstart.
+ */
+ start: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Pinch
+ * Returns OpenLayers.Handler.Pinch
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing functions to be called when
+ * the pinch operation start, change, or is finished. The callbacks
+ * should expect to receive an object argument, which contains
+ * information about scale, distance, and position of touch points.
+ * options - {Object}
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ var propagate = true;
+ this.pinching = false;
+ if (OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = true;
+ this.last = this.start = {
+ distance: this.getDistance(evt.touches),
+ delta: 0,
+ scale: 1
+ };
+ this.callback("start", [evt, this.start]);
+ propagate = !this.stopDown;
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+ return propagate;
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
+ this.pinching = true;
+ var current = this.getPinchData(evt);
+ this.callback("move", [evt, current]);
+ this.last = current;
+ // prevent document dragging
+ OpenLayers.Event.stop(evt);
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ if (this.started && !OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = false;
+ this.pinching = false;
+ this.callback("done", [evt, this.start, this.last]);
+ this.start = null;
+ this.last = null;
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.pinching = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.pinching = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: getDistance
+ * Get the distance in pixels between two touches.
+ *
+ * Parameters:
+ * touches - {Array(Object)}
+ *
+ * Returns:
+ * {Number} The distance in pixels.
+ */
+ getDistance: function(touches) {
+ var t0 = touches[0];
+ var t1 = touches[1];
+ return Math.sqrt(
+ Math.pow(t0.olClientX - t1.olClientX, 2) +
+ Math.pow(t0.olClientY - t1.olClientY, 2)
+ );
+ },
+
+
+ /**
+ * Method: getPinchData
+ * Get informations about the pinch event.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Object} Object that contains data about the current pinch.
+ */
+ getPinchData: function(evt) {
+ var distance = this.getDistance(evt.touches);
+ var scale = distance / this.start.distance;
+ return {
+ distance: distance,
+ delta: this.last.distance - distance,
+ scale: scale
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Pinch"
+});
+
+/* ======================================================================
+ OpenLayers/Handler/Polygon.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Polygon
+ * Handler to draw a polygon on the map. Polygon is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Path>
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
+
+ /**
+ * APIProperty: holeModifier
+ * {String} Key modifier to trigger hole digitizing. Acceptable values are
+ * "altKey", "shiftKey", or "ctrlKey". If not set, no hole digitizing
+ * will take place. Default is null.
+ */
+ holeModifier: null,
+
+ /**
+ * Property: drawingHole
+ * {Boolean} Currently drawing an interior ring.
+ */
+ drawingHole: false,
+
+ /**
+ * Property: polygon
+ * {<OpenLayers.Feature.Vector>}
+ */
+ polygon: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Polygon
+ * Create a Polygon Handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the polygon geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing([this.point.geometry])
+ );
+ this.polygon = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([this.line.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.polygon, this.point], {silent: true});
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ if(!this.drawingHole && this.holeModifier &&
+ this.evt && this.evt[this.holeModifier]) {
+ var geometry = this.point.geometry;
+ var features = this.control.layer.features;
+ var candidate, polygon;
+ // look for intersections, last drawn gets priority
+ for (var i=features.length-1; i>=0; --i) {
+ candidate = features[i].geometry;
+ if ((candidate instanceof OpenLayers.Geometry.Polygon ||
+ candidate instanceof OpenLayers.Geometry.MultiPolygon) &&
+ candidate.intersects(geometry)) {
+ polygon = features[i];
+ this.control.layer.removeFeatures([polygon], {silent: true});
+ this.control.layer.events.registerPriority(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.registerPriority(
+ "sketchmodified", this, this.enforceTopology
+ );
+ polygon.geometry.addComponent(this.line.geometry);
+ this.polygon = polygon;
+ this.drawingHole = true;
+ break;
+ }
+ }
+ }
+ OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments);
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 2;
+ },
+
+ /**
+ * Method: enforceTopology
+ * Simple topology enforcement for drawing interior rings. Ensures vertices
+ * of interior rings are contained by exterior ring. Other topology
+ * rules are enforced in <finalizeInteriorRing> to allow drawing of
+ * rings that intersect only during the sketch (e.g. a "C" shaped ring
+ * that nearly encloses another ring).
+ */
+ enforceTopology: function(event) {
+ var point = event.vertex;
+ var components = this.line.geometry.components;
+ // ensure that vertices of interior ring are contained by exterior ring
+ if (!this.polygon.geometry.intersects(point)) {
+ var last = components[components.length-3];
+ point.x = last.x;
+ point.y = last.y;
+ }
+ },
+
+ /**
+ * Method: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 2;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: finalizeInteriorRing
+ * Enforces that new ring has some area and doesn't contain vertices of any
+ * other rings.
+ */
+ finalizeInteriorRing: function() {
+ var ring = this.line.geometry;
+ // ensure that ring has some area
+ var modified = (ring.getArea() !== 0);
+ if (modified) {
+ // ensure that new ring doesn't intersect any other rings
+ var rings = this.polygon.geometry.components;
+ for (var i=rings.length-2; i>=0; --i) {
+ if (ring.intersects(rings[i])) {
+ modified = false;
+ break;
+ }
+ }
+ if (modified) {
+ // ensure that new ring doesn't contain any other rings
+ var target;
+ outer: for (var i=rings.length-2; i>0; --i) {
+ var points = rings[i].components;
+ for (var j=0, jj=points.length; j<jj; ++j) {
+ if (ring.containsPoint(points[j])) {
+ modified = false;
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ if (modified) {
+ if (this.polygon.state !== OpenLayers.State.INSERT) {
+ this.polygon.state = OpenLayers.State.UPDATE;
+ }
+ } else {
+ this.polygon.geometry.removeComponent(ring);
+ }
+ this.restoreFeature();
+ return false;
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ if (this.drawingHole) {
+ this.polygon.geometry.removeComponent(this.line.geometry);
+ this.restoreFeature(true);
+ }
+ return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
+ },
+
+ /**
+ * Method: restoreFeature
+ * Move the feature from the sketch layer to the target layer.
+ *
+ * Properties:
+ * cancel - {Boolean} Cancel drawing. If falsey, the "sketchcomplete" event
+ * will be fired.
+ */
+ restoreFeature: function(cancel) {
+ this.control.layer.events.unregister(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.unregister(
+ "sketchmodified", this, this.enforceTopology
+ );
+ this.layer.removeFeatures([this.polygon], {silent: true});
+ this.control.layer.addFeatures([this.polygon], {silent: true});
+ this.drawingHole = false;
+ if (!cancel) {
+ // Re-trigger "sketchcomplete" so other listeners can do their
+ // business. While this is somewhat sloppy (if a listener is
+ // registered with registerPriority - not common - between the start
+ // and end of a single ring drawing - very uncommon - it will be
+ // called twice).
+ // TODO: In 3.0, collapse sketch handlers into geometry specific
+ // drawing controls.
+ this.control.layer.events.triggerEvent(
+ "sketchcomplete", {feature : this.polygon}
+ );
+ }
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Path.prototype.destroyFeature.call(
+ this, force);
+ this.polygon = null;
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.polygon, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.polygon;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>}
+ */
+ getGeometry: function() {
+ var geometry = this.polygon && this.polygon.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPolygon([geometry]);
+ }
+ return geometry;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Polygon"
+});
+/* ======================================================================
+ OpenLayers/Control/Geolocate.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Geolocate
+ * The Geolocate control wraps w3c geolocation API into control that can be
+ * bound to a map, and generate events on location update
+ *
+ * To use this control requires to load the proj4js library if the projection
+ * of the map is not EPSG:4326 or EPSG:900913.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Geolocate = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * locationupdated - Triggered when browser return a new position. Listeners will
+ * receive an object with a 'position' property which is the browser.geolocation.position
+ * native object, as well as a 'point' property which is the location transformed in the
+ * current map projection.
+ * locationfailed - Triggered when geolocation has failed
+ * locationuncapable - Triggered when control is activated on a browser
+ * which doesn't support geolocation
+ */
+
+ /**
+ * Property: geolocation
+ * {Object} The geolocation engine, as a property to be possibly mocked.
+ * This is set lazily to avoid a memory leak in IE9.
+ */
+ geolocation: null,
+
+ /**
+ * Property: available
+ * {Boolean} The navigator.geolocation object is available.
+ */
+ available: ('geolocation' in navigator),
+
+ /**
+ * APIProperty: bind
+ * {Boolean} If true, map center will be set on location update.
+ */
+ bind: true,
+
+ /**
+ * APIProperty: watch
+ * {Boolean} If true, position will be update regularly.
+ */
+ watch: false,
+
+ /**
+ * APIProperty: geolocationOptions
+ * {Object} Options to pass to the navigator's geolocation API. See
+ * <http://dev.w3.org/geo/api/spec-source.html>. No specific
+ * option is passed to the geolocation API by default.
+ */
+ geolocationOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Geolocate
+ * Create a new control to deal with browser geolocation API
+ *
+ */
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (this.available && !this.geolocation) {
+ // set lazily to avoid IE9 memory leak
+ this.geolocation = navigator.geolocation;
+ }
+ if (!this.geolocation) {
+ this.events.triggerEvent("locationuncapable");
+ return false;
+ }
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ if (this.watch) {
+ this.watchId = this.geolocation.watchPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ } else {
+ this.getCurrentLocation();
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active && this.watchId !== null) {
+ this.geolocation.clearWatch(this.watchId);
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: geolocate
+ * Activates the control.
+ *
+ */
+ geolocate: function (position) {
+ var center = new OpenLayers.LonLat(
+ position.coords.longitude,
+ position.coords.latitude
+ ).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ this.map.getProjectionObject()
+ );
+ if (this.bind) {
+ this.map.setCenter(center);
+ }
+ this.events.triggerEvent("locationupdated", {
+ position: position,
+ point: new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ )
+ });
+ },
+
+ /**
+ * APIMethod: getCurrentLocation
+ *
+ * Returns:
+ * {Boolean} Returns true if a event will be fired (successfull
+ * registration)
+ */
+ getCurrentLocation: function() {
+ if (!this.active || this.watch) {
+ return false;
+ }
+ this.geolocation.getCurrentPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ return true;
+ },
+
+ /**
+ * Method: failure
+ * method called on browser's geolocation failure
+ *
+ */
+ failure: function (error) {
+ this.events.triggerEvent("locationfailed", {error: error});
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Geolocate"
+});
+/* ======================================================================
+ OpenLayers/Protocol/HTTP.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.HTTP
+ * A basic HTTP protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.HTTP> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: url
+ * {String} Service URL, read-only, set through the options
+ * passed to constructor.
+ */
+ url: null,
+
+ /**
+ * Property: headers
+ * {Object} HTTP request headers, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'Content-Type': 'plain/text'}
+ */
+ headers: null,
+
+ /**
+ * Property: params
+ * {Object} Parameters of GET requests, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'bbox': '5,5,5,5'}
+ */
+ params: null,
+
+ /**
+ * Property: callback
+ * {Object} Function to be called when the <read>, <create>,
+ * <update>, <delete> or <commit> operation completes, read-only,
+ * set through the options passed to the constructor.
+ */
+ callback: null,
+
+ /**
+ * Property: scope
+ * {Object} Callback execution scope, read-only, set through the
+ * options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: readWithPOST
+ * {Boolean} true if read operations are done with POST requests
+ * instead of GET, defaults to false.
+ */
+ readWithPOST: false,
+
+ /**
+ * APIProperty: updateWithPOST
+ * {Boolean} true if update operations are done with POST requests
+ * defaults to false.
+ */
+ updateWithPOST: false,
+
+ /**
+ * APIProperty: deleteWithPOST
+ * {Boolean} true if delete operations are done with POST requests
+ * defaults to false.
+ * if true, POST data is set to output of format.write().
+ */
+ deleteWithPOST: false,
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Default is false. If true and the layer has a projection object set,
+ * any BBOX filter will be serialized with a fifth item identifying the
+ * projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * Constructor: OpenLayers.Protocol.HTTP
+ * A class for giving layers generic HTTP protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * headers - {Object}
+ * params - {Object} URL parameters for GET requests
+ * format - {<OpenLayers.Format>}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.headers = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ wildcarded: this.wildcarded,
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.params = null;
+ this.headers = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, the filter will be serialized using the
+ * <OpenLayers.Format.QueryStringFilter> class.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Valid options:
+ * url - {String} Url for the request.
+ * params - {Object} Parameters to get serialized as a query string.
+ * headers - {Object} Headers to be set on the request.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ * readWithPOST - {Boolean} If the request should be done with POST.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the HTTP request, this object is also passed to the
+ * callback function when the request completes, its "features" property
+ * is then populated with the features received from the server.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = options || {};
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var readWithPOST = (options.readWithPOST !== undefined) ?
+ options.readWithPOST : this.readWithPOST;
+ var resp = new OpenLayers.Protocol.Response({requestType: "read"});
+ if(readWithPOST) {
+ var headers = options.headers || {};
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ data: OpenLayers.Util.getParameterString(options.params),
+ headers: headers
+ });
+ } else {
+ resp.priv = OpenLayers.Request.GET({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ params: options.params,
+ headers: options.headers
+ });
+ }
+ return resp;
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the features received from the server.
+ */
+ create: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: features,
+ requestType: "create"
+ });
+
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleCreate, resp, options),
+ headers: options.headers,
+ data: this.format.write(features)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleCreate
+ * Called the the request issued by <create> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create call.
+ */
+ handleCreate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the feature received from the server.
+ */
+ update: function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "update"
+ });
+
+ var method = this.updateWithPOST ? "POST" : "PUT";
+ resp.priv = OpenLayers.Request[method]({
+ url: url,
+ callback: this.createCallback(this.handleUpdate, resp, options),
+ headers: options.headers,
+ data: this.format.write(feature)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleUpdate
+ * Called the the request issued by <update> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the update call.
+ */
+ handleUpdate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes.
+ */
+ "delete": function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "delete"
+ });
+
+ var method = this.deleteWithPOST ? "POST" : "DELETE";
+ var requestOptions = {
+ url: url,
+ callback: this.createCallback(this.handleDelete, resp, options),
+ headers: options.headers
+ };
+ if (this.deleteWithPOST) {
+ requestOptions.data = this.format.write(feature);
+ }
+ resp.priv = OpenLayers.Request[method](requestOptions);
+
+ return resp;
+ },
+
+ /**
+ * Method: handleDelete
+ * Called the the request issued by <delete> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the delete call.
+ */
+ handleDelete: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create, read, update,
+ * or delete call.
+ */
+ handleResponse: function(resp, options) {
+ var request = resp.priv;
+ if(options.callback) {
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ if(resp.requestType != "delete") {
+ resp.features = this.parseFeatures(request);
+ }
+ resp.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ resp.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, resp);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features.
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if (!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ /**
+ * APIMethod: commit
+ * Iterate over each feature and take action based on the feature state.
+ * Possible actions are create, update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Optional object for setting up intermediate commit
+ * callbacks.
+ *
+ * Valid options:
+ * create - {Object} Optional object to be passed to the <create> method.
+ * update - {Object} Optional object to be passed to the <update> method.
+ * delete - {Object} Optional object to be passed to the <delete> method.
+ * callback - {Function} Optional function to be called when the commit
+ * is complete.
+ * scope - {Object} Optional object to be set as the scope of the callback.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
+ * one per request made to the server, each object's "priv" property
+ * references the corresponding HTTP request.
+ */
+ commit: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var resp = [], nResponses = 0;
+
+ // Divide up features before issuing any requests. This properly
+ // counts requests in the event that any responses come in before
+ // all requests have been issued.
+ var types = {};
+ types[OpenLayers.State.INSERT] = [];
+ types[OpenLayers.State.UPDATE] = [];
+ types[OpenLayers.State.DELETE] = [];
+ var feature, list, requestFeatures = [];
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ list = types[feature.state];
+ if(list) {
+ list.push(feature);
+ requestFeatures.push(feature);
+ }
+ }
+ // tally up number of requests
+ var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
+ types[OpenLayers.State.UPDATE].length +
+ types[OpenLayers.State.DELETE].length;
+
+ // This response will be sent to the final callback after all the others
+ // have been fired.
+ var success = true;
+ var finalResponse = new OpenLayers.Protocol.Response({
+ reqFeatures: requestFeatures
+ });
+
+ function insertCallback(response) {
+ var len = response.features ? response.features.length : 0;
+ var fids = new Array(len);
+ for(var i=0; i<len; ++i) {
+ fids[i] = response.features[i].fid;
+ }
+ finalResponse.insertIds = fids;
+ callback.apply(this, [response]);
+ }
+
+ function callback(response) {
+ this.callUserCallback(response, options);
+ success = success && response.success();
+ nResponses++;
+ if (nResponses >= nRequests) {
+ if (options.callback) {
+ finalResponse.code = success ?
+ OpenLayers.Protocol.Response.SUCCESS :
+ OpenLayers.Protocol.Response.FAILURE;
+ options.callback.apply(options.scope, [finalResponse]);
+ }
+ }
+ }
+
+ // start issuing requests
+ var queue = types[OpenLayers.State.INSERT];
+ if(queue.length > 0) {
+ resp.push(this.create(
+ queue, OpenLayers.Util.applyDefaults(
+ {callback: insertCallback, scope: this}, options.create
+ )
+ ));
+ }
+ queue = types[OpenLayers.State.UPDATE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this.update(
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options.update
+ ))
+ );
+ }
+ queue = types[OpenLayers.State.DELETE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this["delete"](
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options["delete"]
+ ))
+ );
+ }
+ return resp;
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this HTTP protocol (as a result
+ * of a create, read, update, delete or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ /**
+ * Method: callUserCallback
+ * This method is used from within the commit method each time an
+ * an HTTP response is received from the server, it is responsible
+ * for calling the user-supplied callbacks.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>}
+ * options - {Object} The map of options passed to the commit call.
+ */
+ callUserCallback: function(resp, options) {
+ var opt = options[resp.requestType];
+ if(opt && opt.callback) {
+ opt.callback.call(opt.scope, resp);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.HTTP"
+});
+/* ======================================================================
+ OpenLayers/Control/DragPan.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragPan
+ * The DragPan control pans the map with a drag of the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: panned
+ * {Boolean} The map moved.
+ */
+ panned: false,
+
+ /**
+ * Property: interval
+ * {Integer} The number of milliseconds that should ellapse before
+ * panning the map again. Defaults to 0 milliseconds, which means that
+ * no separate cycle is used for panning. In most cases you won't want
+ * to change this value. For slow machines/devices larger values can be
+ * tried out.
+ */
+ interval: 0,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: kinetic
+ * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
+ */
+ kinetic: null,
+
+ /**
+ * APIProperty: enableKinetic
+ * {Boolean} Set this option to enable "kinetic dragging". Can be
+ * set to true or to an object. If set to an object this
+ * object will be passed to the {<OpenLayers.Kinetic>}
+ * constructor. Defaults to true.
+ * To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
+ * included in your build config.
+ */
+ enableKinetic: true,
+
+ /**
+ * APIProperty: kineticInterval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * scrolling". Applies only if enableKinetic is set. Defaults
+ * to 10 milliseconds.
+ */
+ kineticInterval: 10,
+
+
+ /**
+ * Method: draw
+ * Creates a Drag handler, using <panMap> and
+ * <panMapDone> as callbacks.
+ */
+ draw: function() {
+ if (this.enableKinetic && OpenLayers.Kinetic) {
+ var config = {interval: this.kineticInterval};
+ if(typeof this.enableKinetic === "object") {
+ config = OpenLayers.Util.extend(config, this.enableKinetic);
+ }
+ this.kinetic = new OpenLayers.Kinetic(config);
+ }
+ this.handler = new OpenLayers.Handler.Drag(this, {
+ "move": this.panMap,
+ "done": this.panMapDone,
+ "down": this.panMapStart
+ }, {
+ interval: this.interval,
+ documentDrag: this.documentDrag
+ }
+ );
+ },
+
+ /**
+ * Method: panMapStart
+ */
+ panMapStart: function() {
+ if(this.kinetic) {
+ this.kinetic.begin();
+ }
+ },
+
+ /**
+ * Method: panMap
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMap: function(xy) {
+ if(this.kinetic) {
+ this.kinetic.update(xy);
+ }
+ this.panned = true;
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: true, animate: false}
+ );
+ },
+
+ /**
+ * Method: panMapDone
+ * Finish the panning operation. Only call setCenter (through <panMap>)
+ * if the map has actually been moved.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMapDone: function(xy) {
+ if(this.panned) {
+ var res = null;
+ if (this.kinetic) {
+ res = this.kinetic.end(xy);
+ }
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: !!res, animate: false}
+ );
+ if (res) {
+ var self = this;
+ this.kinetic.move(res, function(x, y, end) {
+ self.map.pan(x, y, {dragging: !end, animate: false});
+ });
+ }
+ this.panned = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragPan"
+});
+/* ======================================================================
+ OpenLayers/Control/PinchZoom.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler/Pinch.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PinchZoom
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: pinchOrigin
+ * {Object} Cached object representing the pinch start (in pixels).
+ */
+ pinchOrigin: null,
+
+ /**
+ * Property: currentCenter
+ * {Object} Cached object representing the latest pinch center (in pixels).
+ */
+ currentCenter: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: preserveCenter
+ * {Boolean} Set this to true if you don't want the map center to change
+ * while pinching. For example you may want to set preserveCenter to
+ * true when the user location is being watched and you want to preserve
+ * the user location at the center of the map even if he zooms in or
+ * out using pinch. This property's value can be changed any time on an
+ * existing instance. Default is false.
+ */
+ preserveCenter: false,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the pinch handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.PinchZoom
+ * Create a control for zooming with pinch gestures. This works on devices
+ * with multi-touch support.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.handler = new OpenLayers.Handler.Pinch(this, {
+ start: this.pinchStart,
+ move: this.pinchMove,
+ done: this.pinchDone
+ }, this.handlerOptions);
+ },
+
+ /**
+ * Method: pinchStart
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchStart: function(evt, pinchData) {
+ var xy = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+ this.pinchOrigin = xy;
+ this.currentCenter = xy;
+ },
+
+ /**
+ * Method: pinchMove
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchMove: function(evt, pinchData) {
+ var scale = pinchData.scale;
+ var containerOrigin = this.map.layerContainerOriginPx;
+ var pinchOrigin = this.pinchOrigin;
+ var current = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+
+ var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
+ var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
+
+ this.map.applyTransform(dx, dy, scale);
+ this.currentCenter = current;
+ },
+
+ /**
+ * Method: pinchDone
+ *
+ * Parameters:
+ * evt - {Event}
+ * start - {Object} pinch data object related to the touchstart event that
+ * started the pinch gesture.
+ * last - {Object} pinch data object related to the last touchmove event
+ * of the pinch gesture. This give us the final scale of the pinch.
+ */
+ pinchDone: function(evt, start, last) {
+ this.map.applyTransform();
+ var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
+ if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
+ var resolution = this.map.getResolutionForZoom(zoom);
+
+ var location = this.map.getLonLatFromPixel(this.pinchOrigin);
+ var zoomPixel = this.currentCenter;
+ var size = this.map.getSize();
+
+ location.lon += resolution * ((size.w / 2) - zoomPixel.x);
+ location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
+
+ // Force a reflow before calling setCenter. This is to work
+ // around an issue occuring in iOS.
+ //
+ // See https://github.com/openlayers/openlayers/pull/351.
+ //
+ // Without a reflow setting the layer container div's top left
+ // style properties to "0px" - as done in Map.moveTo when zoom
+ // is changed - won't actually correctly reposition the layer
+ // container div.
+ //
+ // Also, we need to use a statement that the Google Closure
+ // compiler won't optimize away.
+ this.map.div.clientWidth = this.map.div.clientWidth;
+
+ this.map.setCenter(location, zoom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PinchZoom"
+
+});
+/* ======================================================================
+ OpenLayers/Handler/Click.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Click
+ * A handler for mouse clicks. The intention of this handler is to give
+ * controls more flexibility with handling clicks. Browsers trigger
+ * click events twice for a double-click. In addition, the mousedown,
+ * mousemove, mouseup sequence fires a click event. With this handler,
+ * controls can decide whether to ignore clicks associated with a double
+ * click. By setting a <pixelTolerance>, controls can also ignore clicks
+ * that include a drag. Create a new instance with the
+ * <OpenLayers.Handler.Click> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * APIProperty: delay
+ * {Number} Number of milliseconds between clicks before the event is
+ * considered a double-click.
+ */
+ delay: 300,
+
+ /**
+ * APIProperty: single
+ * {Boolean} Handle single clicks. Default is true. If false, clicks
+ * will not be reported. If true, single-clicks will be reported.
+ */
+ single: true,
+
+ /**
+ * APIProperty: double
+ * {Boolean} Handle double-clicks. Default is false.
+ */
+ 'double': false,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between mouseup and mousedown for an
+ * event to be considered a click. Default is 0. If set to an
+ * integer value, clicks with a drag greater than the value will be
+ * ignored. This property can only be set when the handler is
+ * constructed.
+ */
+ pixelTolerance: 0,
+
+ /**
+ * APIProperty: dblclickTolerance
+ * {Number} Maximum distance in pixels between clicks for a sequence of
+ * events to be considered a double click. Default is 13. If the
+ * distance between two clicks is greater than this value, a double-
+ * click will not be fired.
+ */
+ dblclickTolerance: 13,
+
+ /**
+ * APIProperty: stopSingle
+ * {Boolean} Stop other listeners from being notified of clicks. Default
+ * is false. If true, any listeners registered before this one for
+ * click or rightclick events will not be notified.
+ */
+ stopSingle: false,
+
+ /**
+ * APIProperty: stopDouble
+ * {Boolean} Stop other listeners from being notified of double-clicks.
+ * Default is false. If true, any click listeners registered before
+ * this one will not be notified of *any* double-click events.
+ *
+ * The one caveat with stopDouble is that given a map with two click
+ * handlers, one with stopDouble true and the other with stopSingle
+ * true, the stopSingle handler should be activated last to get
+ * uniform cross-browser performance. Since IE triggers one click
+ * with a dblclick and FF triggers two, if a stopSingle handler is
+ * activated first, all it gets in IE is a single click when the
+ * second handler stops propagation on the dblclick.
+ */
+ stopDouble: false,
+
+ /**
+ * Property: timerId
+ * {Number} The id of the timeout waiting to clear the <delayedCall>.
+ */
+ timerId: null,
+
+ /**
+ * Property: down
+ * {Object} Object that store relevant information about the last
+ * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ down: null,
+
+ /**
+ * Property: last
+ * {Object} Object that store relevant information about the last
+ * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ last: null,
+
+ /**
+ * Property: first
+ * {Object} When waiting for double clicks, this object will store
+ * information about the first click in a two click sequence.
+ */
+ first: null,
+
+ /**
+ * Property: rightclickTimerId
+ * {Number} The id of the right mouse timeout waiting to clear the
+ * <delayedEvent>.
+ */
+ rightclickTimerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Click
+ * Create a new click handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handler's setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object with keys corresponding to callbacks
+ * that will be called by the handler. The callbacks should
+ * expect to recieve a single argument, the click event.
+ * Callbacks for 'click' and 'dblclick' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchmove
+ * Store position of last move, because touchend event can have
+ * an empty "touches" property.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchmove: function(evt) {
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Correctly set event xy property, and add lastTouches to have
+ * touches property from last touchstart or touchmove
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchend: function(evt) {
+ // touchstart may not have been allowed to propagate
+ if (this.down) {
+ evt.xy = this.last.xy;
+ evt.lastTouches = this.last.touches;
+ this.handleSingle(evt);
+ this.down = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mousedown: function(evt) {
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup. Installed to support collection of right mouse events.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mouseup: function (evt) {
+ var propagate = true;
+
+ // Collect right mouse clicks from the mouseup
+ // IE - ignores the second right click in mousedown so using
+ // mouseup instead
+ if (this.checkModifiers(evt) && this.control.handleRightClicks &&
+ OpenLayers.Event.isRightClick(evt)) {
+ propagate = this.rightclick(evt);
+ }
+
+ return propagate;
+ },
+
+ /**
+ * Method: rightclick
+ * Handle rightclick. For a dblrightclick, we get two clicks so we need
+ * to always register for dblrightclick to properly handle single
+ * clicks.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ rightclick: function(evt) {
+ if(this.passesTolerance(evt)) {
+ if(this.rightclickTimerId != null) {
+ //Second click received before timeout this must be
+ // a double click
+ this.clearTimer();
+ this.callback('dblrightclick', [evt]);
+ return !this.stopDouble;
+ } else {
+ //Set the rightclickTimerId, send evt only if double is
+ // true else trigger single
+ var clickEvent = this['double'] ?
+ OpenLayers.Util.extend({}, evt) :
+ this.callback('rightclick', [evt]);
+
+ var delayedRightCall = OpenLayers.Function.bind(
+ this.delayedRightCall,
+ this,
+ clickEvent
+ );
+ this.rightclickTimerId = window.setTimeout(
+ delayedRightCall, this.delay
+ );
+ }
+ }
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: delayedRightCall
+ * Sets <rightclickTimerId> to null. And optionally triggers the
+ * rightclick callback if evt is set.
+ */
+ delayedRightCall: function(evt) {
+ this.rightclickTimerId = null;
+ if (evt) {
+ this.callback('rightclick', [evt]);
+ }
+ },
+
+ /**
+ * Method: click
+ * Handle click events from the browser. This is registered as a listener
+ * for click events and should not be called from other events in this
+ * handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ click: function(evt) {
+ if (!this.last) {
+ this.last = this.getEventInfo(evt);
+ }
+ this.handleSingle(evt);
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. For a dblclick, we get two clicks in some browsers
+ * (FF) and one in others (IE). So we need to always register for
+ * dblclick to properly handle single clicks. This method is registered
+ * as a listener for the dblclick browser event. It should *not* be
+ * called by other methods in this handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ dblclick: function(evt) {
+ this.handleDouble(evt);
+ return !this.stopDouble;
+ },
+
+ /**
+ * Method: handleDouble
+ * Handle double-click sequence.
+ */
+ handleDouble: function(evt) {
+ if (this.passesDblclickTolerance(evt)) {
+ if (this["double"]) {
+ this.callback("dblclick", [evt]);
+ }
+ // to prevent a dblclick from firing the click callback in IE
+ this.clearTimer();
+ }
+ },
+
+ /**
+ * Method: handleSingle
+ * Handle single click sequence.
+ */
+ handleSingle: function(evt) {
+ if (this.passesTolerance(evt)) {
+ if (this.timerId != null) {
+ // already received a click
+ if (this.last.touches && this.last.touches.length === 1) {
+ // touch device, no dblclick event - this may be a double
+ if (this["double"]) {
+ // on Android don't let the browser zoom on the page
+ OpenLayers.Event.preventDefault(evt);
+ }
+ this.handleDouble(evt);
+ }
+ // if we're not in a touch environment we clear the click timer
+ // if we've got a second touch, we'll get two touchend events
+ if (!this.last.touches || this.last.touches.length !== 2) {
+ this.clearTimer();
+ }
+ } else {
+ // remember the first click info so we can compare to the second
+ this.first = this.getEventInfo(evt);
+ // set the timer, send evt only if single is true
+ //use a clone of the event object because it will no longer
+ //be a valid event object in IE in the timer callback
+ var clickEvent = this.single ?
+ OpenLayers.Util.extend({}, evt) : null;
+ this.queuePotentialClick(clickEvent);
+ }
+ }
+ },
+
+ /**
+ * Method: queuePotentialClick
+ * This method is separated out largely to make testing easier (so we
+ * don't have to override window.setTimeout)
+ */
+ queuePotentialClick: function(evt) {
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance. Note
+ * that the pixel tolerance check only works if mousedown events get to
+ * the listeners registered here. If they are stopped by other elements,
+ * the <pixelTolerance> will have no effect here (this method will always
+ * return true).
+ *
+ * Returns:
+ * {Boolean} The click is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(evt) {
+ var passes = true;
+ if (this.pixelTolerance != null && this.down && this.down.xy) {
+ passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
+ // for touch environments, we also enforce that all touches
+ // start and end within the given tolerance to be considered a click
+ if (passes && this.touch &&
+ this.down.touches.length === this.last.touches.length) {
+ // the touchend event doesn't come with touches, so we check
+ // down and last
+ for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
+ if (this.getTouchDistance(
+ this.down.touches[i],
+ this.last.touches[i]
+ ) > this.pixelTolerance) {
+ passes = false;
+ break;
+ }
+ }
+ }
+ }
+ return passes;
+ },
+
+ /**
+ * Method: getTouchDistance
+ *
+ * Returns:
+ * {Boolean} The pixel displacement between two touches.
+ */
+ getTouchDistance: function(from, to) {
+ return Math.sqrt(
+ Math.pow(from.clientX - to.clientX, 2) +
+ Math.pow(from.clientY - to.clientY, 2)
+ );
+ },
+
+ /**
+ * Method: passesDblclickTolerance
+ * Determine whether the event is within the optional double-cick pixel
+ * tolerance.
+ *
+ * Returns:
+ * {Boolean} The click is within the double-click pixel tolerance.
+ */
+ passesDblclickTolerance: function(evt) {
+ var passes = true;
+ if (this.down && this.first) {
+ passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
+ }
+ return passes;
+ },
+
+ /**
+ * Method: clearTimer
+ * Clear the timer and set <timerId> to null.
+ */
+ clearTimer: function() {
+ if (this.timerId != null) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ if (this.rightclickTimerId != null) {
+ window.clearTimeout(this.rightclickTimerId);
+ this.rightclickTimerId = null;
+ }
+ },
+
+ /**
+ * Method: delayedCall
+ * Sets <timerId> to null. And optionally triggers the click callback if
+ * evt is set.
+ */
+ delayedCall: function(evt) {
+ this.timerId = null;
+ if (evt) {
+ this.callback("click", [evt]);
+ }
+ },
+
+ /**
+ * Method: getEventInfo
+ * This method allows us to store event information without storing the
+ * actual event. In touch devices (at least), the same event is
+ * modified between touchstart, touchmove, and touchend.
+ *
+ * Returns:
+ * {Object} An object with event related info.
+ */
+ getEventInfo: function(evt) {
+ var touches;
+ if (evt.touches) {
+ var len = evt.touches.length;
+ touches = new Array(len);
+ var touch;
+ for (var i=0; i<len; i++) {
+ touch = evt.touches[i];
+ touches[i] = {
+ clientX: touch.olClientX,
+ clientY: touch.olClientY
+ };
+ }
+ }
+ return {
+ xy: evt.xy,
+ touches: touches
+ };
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.clearTimer();
+ this.down = null;
+ this.first = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Click"
+});
+/* ======================================================================
+ OpenLayers/Control/TouchNavigation.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/DragPan.js
+ * @requires OpenLayers/Control/PinchZoom.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.TouchNavigation
+ * The navigation control handles map browsing with touch events (dragging,
+ * double-tapping, tap with two fingers, and pinch zoom). Create a new
+ * control with the <OpenLayers.Control.TouchNavigation> constructor.
+ *
+ * If you’re only targeting touch enabled devices with your mapping application,
+ * you can create a map with only a TouchNavigation control. The
+ * <OpenLayers.Control.Navigation> control is mobile ready by default, but
+ * you can generate a smaller build of the library by only including this
+ * touch navigation control if you aren't concerned about mouse interaction.
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: clickHandlerOptions
+ * {Object} Options passed to the Click handler.
+ */
+ clickHandlerOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.TouchNavigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ this.deactivate();
+ if(this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ delete this.pinchZoom;
+ }
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
+ this.dragPan.activate();
+ this.handlers.click.activate();
+ this.pinchZoom.activate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.pinchZoom.deactivate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ var clickCallbacks = {
+ click: this.defaultClick,
+ dblclick: this.defaultDblClick
+ };
+ var clickOptions = OpenLayers.Util.extend({
+ "double": true,
+ stopDouble: true,
+ pixelTolerance: 2
+ }, this.clickHandlerOptions);
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.dragPan.draw();
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
+ );
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if(evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.TouchNavigation"
+});
+/* ======================================================================
+ OpenLayers/Protocol/WFS/v1_0_0.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS/v1.js
+ * @requires OpenLayers/Format/WFST/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1_0_0
+ * A WFS v1.0.0 protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.WFS.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.WFS.v1>
+ */
+OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS.v1_0_0
+ * A class for giving layers WFS v1.0.0 protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0"
+});
+/* ======================================================================
+ OpenLayers/TileManager.js
+ ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.TileManager
+ * Provides queueing of image requests and caching of image elements.
+ *
+ * Queueing avoids unnecessary image requests while changing zoom levels
+ * quickly, and helps improve dragging performance on mobile devices that show
+ * a lag in dragging when loading of new images starts. <zoomDelay> and
+ * <moveDelay> are the configuration options to control this behavior.
+ *
+ * Caching avoids setting the src on image elements for images that have already
+ * been used. Several maps can share a TileManager instance, in which case each
+ * map gets its own tile queue, but all maps share the same tile cache.
+ */
+OpenLayers.TileManager = OpenLayers.Class({
+
+ /**
+ * APIProperty: cacheSize
+ * {Number} Number of image elements to keep referenced in this instance's
+ * cache for fast reuse. Default is 256.
+ */
+ cacheSize: 256,
+
+ /**
+ * APIProperty: tilesPerFrame
+ * {Number} Number of queued tiles to load per frame (see <frameDelay>).
+ * Default is 2.
+ */
+ tilesPerFrame: 2,
+
+ /**
+ * APIProperty: frameDelay
+ * {Number} Delay between tile loading frames (see <tilesPerFrame>) in
+ * milliseconds. Default is 16.
+ */
+ frameDelay: 16,
+
+ /**
+ * APIProperty: moveDelay
+ * {Number} Delay in milliseconds after a map's move event before loading
+ * tiles. Default is 100.
+ */
+ moveDelay: 100,
+
+ /**
+ * APIProperty: zoomDelay
+ * {Number} Delay in milliseconds after a map's zoomend event before loading
+ * tiles. Default is 200.
+ */
+ zoomDelay: 200,
+
+ /**
+ * Property: maps
+ * {Array(<OpenLayers.Map>)} The maps to manage tiles on.
+ */
+ maps: null,
+
+ /**
+ * Property: tileQueueId
+ * {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
+ */
+ tileQueueId: null,
+
+ /**
+ * Property: tileQueue
+ * {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
+ * map id.
+ */
+ tileQueue: null,
+
+ /**
+ * Property: tileCache
+ * {Object} Cached image elements, keyed by URL.
+ */
+ tileCache: null,
+
+ /**
+ * Property: tileCacheIndex
+ * {Array(String)} URLs of cached tiles. First entry is the least recently
+ * used.
+ */
+ tileCacheIndex: null,
+
+ /**
+ * Constructor: OpenLayers.TileManager
+ * Constructor for a new <OpenLayers.TileManager> instance.
+ *
+ * Parameters:
+ * options - {Object} Configuration for this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.maps = [];
+ this.tileQueueId = {};
+ this.tileQueue = {};
+ this.tileCache = {};
+ this.tileCacheIndex = [];
+ },
+
+ /**
+ * Method: addMap
+ * Binds this instance to a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ addMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ this.maps.push(map);
+ this.tileQueue[map.id] = [];
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.addLayer({layer: map.layers[i]});
+ }
+ map.events.on({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeMap
+ * Unbinds this instance from a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ window.clearTimeout(this.tileQueueId[map.id]);
+ if (map.layers) {
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.removeLayer({layer: map.layers[i]});
+ }
+ }
+ if (map.events) {
+ map.events.un({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ }
+ delete this.tileQueue[map.id];
+ delete this.tileQueueId[map.id];
+ OpenLayers.Util.removeItem(this.maps, map);
+ },
+
+ /**
+ * Method: move
+ * Handles the map's move event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ move: function(evt) {
+ this.updateTimeout(evt.object, this.moveDelay, true);
+ },
+
+ /**
+ * Method: zoomEnd
+ * Handles the map's zoomEnd event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ zoomEnd: function(evt) {
+ this.updateTimeout(evt.object, this.zoomDelay);
+ },
+
+ /**
+ * Method: changeLayer
+ * Handles the map's changeLayer event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ changeLayer: function(evt) {
+ if (evt.property === 'visibility' || evt.property === 'params') {
+ this.updateTimeout(evt.object, 0);
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Handles the map's addlayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ layer.events.on({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.addTile({tile: tile});
+ if (tile.url && !tile.imgDiv) {
+ this.manageTileCache({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeLayer
+ * Handles the map's preremovelayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ removeLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ this.clearTileQueue({object: layer});
+ if (layer.events) {
+ layer.events.un({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ }
+ if (layer.grid) {
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.unloadTile({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: updateTimeout
+ * Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop,
+ * and schedules more queue processing after <frameDelay> if there are still
+ * tiles in the queue.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map to update the timeout for
+ * delay - {Number} The delay to apply
+ * nice - {Boolean} If true, the timeout function will only be created if
+ * the tilequeue is not empty. This is used by the move handler to
+ * avoid impacts on dragging performance. For other events, the tile
+ * queue may not be populated yet, so we need to set the timer
+ * regardless of the queue size.
+ */
+ updateTimeout: function(map, delay, nice) {
+ window.clearTimeout(this.tileQueueId[map.id]);
+ var tileQueue = this.tileQueue[map.id];
+ if (!nice || tileQueue.length) {
+ this.tileQueueId[map.id] = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.drawTilesFromQueue(map);
+ if (tileQueue.length) {
+ this.updateTimeout(map, this.frameDelay);
+ }
+ }, this), delay
+ );
+ }
+ },
+
+ /**
+ * Method: addTile
+ * Listener for the layer's addtile event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addTile: function(evt) {
+ if (evt.tile instanceof OpenLayers.Tile.Image) {
+ evt.tile.events.on({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ } else {
+ // Layer has the wrong tile type, so don't handle it any longer
+ this.removeLayer({layer: evt.tile.layer});
+ }
+ },
+
+ /**
+ * Method: unloadTile
+ * Listener for the tile's unload event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ unloadTile: function(evt) {
+ var tile = evt.object;
+ tile.events.un({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile);
+ },
+
+ /**
+ * Method: queueTileDraw
+ * Adds a tile to the queue that will draw it.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforedraw event
+ */
+ queueTileDraw: function(evt) {
+ var tile = evt.object;
+ var queued = false;
+ var layer = tile.layer;
+ var url = layer.getURL(tile.bounds);
+ var img = this.tileCache[url];
+ if (img && img.className !== 'olTileImage') {
+ // cached image no longer valid, e.g. because we're olTileReplacing
+ delete this.tileCache[url];
+ OpenLayers.Util.removeItem(this.tileCacheIndex, url);
+ img = null;
+ }
+ // queue only if image with same url not cached already
+ if (layer.url && (layer.async || !img)) {
+ // add to queue only if not in queue already
+ var tileQueue = this.tileQueue[layer.map.id];
+ if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
+ tileQueue.push(tile);
+ }
+ queued = true;
+ }
+ return !queued;
+ },
+
+ /**
+ * Method: drawTilesFromQueue
+ * Draws tiles from the tileQueue, and unqueues the tiles
+ */
+ drawTilesFromQueue: function(map) {
+ var tileQueue = this.tileQueue[map.id];
+ var limit = this.tilesPerFrame;
+ var animating = map.zoomTween && map.zoomTween.playing;
+ while (!animating && tileQueue.length && limit) {
+ tileQueue.shift().draw(true);
+ --limit;
+ }
+ },
+
+ /**
+ * Method: manageTileCache
+ * Adds, updates, removes and fetches cache entries.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforeload event
+ */
+ manageTileCache: function(evt) {
+ var tile = evt.object;
+ var img = this.tileCache[tile.url];
+ if (img) {
+ // if image is on its layer's backbuffer, remove it from backbuffer
+ if (img.parentNode &&
+ OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer')) {
+ img.parentNode.removeChild(img);
+ img.id = null;
+ }
+ // only use image from cache if it is not on a layer already
+ if (!img.parentNode) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ tile.setImage(img);
+ // LRU - move tile to the end of the array to mark it as the most
+ // recently used
+ OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url);
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: addToCache
+ *
+ * Parameters:
+ * evt - {Object} Listener argument for the tile's loadend event
+ */
+ addToCache: function(evt) {
+ var tile = evt.object;
+ if (!this.tileCache[tile.url]) {
+ if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) {
+ if (this.tileCacheIndex.length >= this.cacheSize) {
+ delete this.tileCache[this.tileCacheIndex[0]];
+ this.tileCacheIndex.shift();
+ }
+ this.tileCache[tile.url] = tile.imgDiv;
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: clearTileQueue
+ * Clears the tile queue from tiles of a specific layer
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the layer's retile event
+ */
+ clearTileQueue: function(evt) {
+ var layer = evt.object;
+ var tileQueue = this.tileQueue[layer.map.id];
+ for (var i=tileQueue.length-1; i>=0; --i) {
+ if (tileQueue[i].layer === layer) {
+ tileQueue.splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.maps.length-1; i>=0; --i) {
+ this.removeMap(this.maps[i]);
+ }
+ this.maps = null;
+ this.tileQueue = null;
+ this.tileQueueId = null;
+ this.tileCache = null;
+ this.tileCacheIndex = null;
+ this._destroyed = true;
+ }
+
+});
diff --git a/misc/openlayers/OpenLayers.mobile.js b/misc/openlayers/OpenLayers.mobile.js
new file mode 100644
index 0000000..c5d0aa3
--- /dev/null
+++ b/misc/openlayers/OpenLayers.mobile.js
@@ -0,0 +1,681 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+var OpenLayers={VERSION_NUMBER:"Release 2.13.1",singleFile:!0,_getScriptLocation:function(){for(var a=/(^|(.*?\/))(OpenLayers[^\/]*?\.js)(\?|$)/,b=document.getElementsByTagName("script"),c,d="",e=0,f=b.length;e<f;e++)if(c=b[e].getAttribute("src"))if(c=c.match(a)){d=c[1];break}return function(){return d}}(),ImgPath:""};OpenLayers.String={startsWith:function(a,b){return 0==a.indexOf(b)},contains:function(a,b){return-1!=a.indexOf(b)},trim:function(a){return a.replace(/^\s\s*/,"").replace(/\s\s*$/,"")},camelize:function(a){a=a.split("-");for(var b=a[0],c=1,d=a.length;c<d;c++)var e=a[c],b=b+(e.charAt(0).toUpperCase()+e.substring(1));return b},format:function(a,b,c){b||(b=window);return a.replace(OpenLayers.String.tokenRegEx,function(a,e){for(var f,g=e.split(/\.+/),h=0;h<g.length;h++){0==h&&(f=b);if(void 0===f)break;
+f=f[g[h]]}"function"==typeof f&&(f=c?f.apply(null,c):f());return"undefined"==typeof f?"undefined":f})},tokenRegEx:/\$\{([\w.]+?)\}/g,numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(a){return OpenLayers.String.numberRegEx.test(a)},numericIf:function(a,b){var c=a;!0===b&&(null!=a&&a.replace)&&(a=a.replace(/^\s*|\s*$/g,""));return OpenLayers.String.isNumeric(a)?parseFloat(a):c}};
+OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(a,b){var c=0;0<b&&(c=parseFloat(a.toPrecision(b)));return c},format:function(a,b,c,d){b="undefined"!=typeof b?b:0;c="undefined"!=typeof c?c:OpenLayers.Number.thousandsSeparator;d="undefined"!=typeof d?d:OpenLayers.Number.decimalSeparator;null!=b&&(a=parseFloat(a.toFixed(b)));var e=a.toString().split(".");1==e.length&&null==b&&(b=0);a=e[0];if(c)for(var f=/(-?[0-9]+)([0-9]{3})/;f.test(a);)a=a.replace(f,"$1"+c+"$2");
+0==b?b=a:(c=1<e.length?e[1]:"0",null!=b&&(c+=Array(b-c.length+1).join("0")),b=a+d+c);return b},zeroPad:function(a,b,c){for(a=a.toString(c||10);a.length<b;)a="0"+a;return a}};
+OpenLayers.Function={bind:function(a,b){var c=Array.prototype.slice.apply(arguments,[2]);return function(){var d=c.concat(Array.prototype.slice.apply(arguments,[0]));return a.apply(b,d)}},bindAsEventListener:function(a,b){return function(c){return a.call(b,c||window.event)}},False:function(){return!1},True:function(){return!0},Void:function(){}};
+OpenLayers.Array={filter:function(a,b,c){var d=[];if(Array.prototype.filter)d=a.filter(b,c);else{var e=a.length;if("function"!=typeof b)throw new TypeError;for(var f=0;f<e;f++)if(f in a){var g=a[f];b.call(c,g,f,a)&&d.push(g)}}return d}};OpenLayers.Class=function(){var a=arguments.length,b=arguments[0],c=arguments[a-1],d="function"==typeof c.initialize?c.initialize:function(){b.prototype.initialize.apply(this,arguments)};1<a?(a=[d,b].concat(Array.prototype.slice.call(arguments).slice(1,a-1),c),OpenLayers.inherit.apply(null,a)):d.prototype=c;return d};
+OpenLayers.inherit=function(a,b){var c=function(){};c.prototype=b.prototype;a.prototype=new c;var d,e,c=2;for(d=arguments.length;c<d;c++)e=arguments[c],"function"===typeof e&&(e=e.prototype),OpenLayers.Util.extend(a.prototype,e)};OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.extend=function(a,b){a=a||{};if(b){for(var c in b){var d=b[c];void 0!==d&&(a[c]=d)}"function"==typeof window.Event&&b instanceof window.Event||(!b.hasOwnProperty||!b.hasOwnProperty("toString"))||(a.toString=b.toString)}return a};OpenLayers.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,centerLonLat:null,initialize:function(a,b,c,d){OpenLayers.Util.isArray(a)&&(d=a[3],c=a[2],b=a[1],a=a[0]);null!=a&&(this.left=OpenLayers.Util.toFloat(a));null!=b&&(this.bottom=OpenLayers.Util.toFloat(b));null!=c&&(this.right=OpenLayers.Util.toFloat(c));null!=d&&(this.top=OpenLayers.Util.toFloat(d))},clone:function(){return new OpenLayers.Bounds(this.left,this.bottom,this.right,this.top)},equals:function(a){var b=!1;null!=
+a&&(b=this.left==a.left&&this.right==a.right&&this.top==a.top&&this.bottom==a.bottom);return b},toString:function(){return[this.left,this.bottom,this.right,this.top].join()},toArray:function(a){return!0===a?[this.bottom,this.left,this.top,this.right]:[this.left,this.bottom,this.right,this.top]},toBBOX:function(a,b){null==a&&(a=6);var c=Math.pow(10,a),d=Math.round(this.left*c)/c,e=Math.round(this.bottom*c)/c,f=Math.round(this.right*c)/c,c=Math.round(this.top*c)/c;return!0===b?e+","+d+","+c+","+f:d+
+","+e+","+f+","+c},toGeometry:function(){return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(this.left,this.bottom),new OpenLayers.Geometry.Point(this.right,this.bottom),new OpenLayers.Geometry.Point(this.right,this.top),new OpenLayers.Geometry.Point(this.left,this.top)])])},getWidth:function(){return this.right-this.left},getHeight:function(){return this.top-this.bottom},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight())},
+getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2)},getCenterLonLat:function(){this.centerLonLat||(this.centerLonLat=new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2));return this.centerLonLat},scale:function(a,b){null==b&&(b=this.getCenterLonLat());var c,d;"OpenLayers.LonLat"==b.CLASS_NAME?(c=b.lon,d=b.lat):(c=b.x,d=b.y);return new OpenLayers.Bounds((this.left-c)*a+c,(this.bottom-d)*a+d,(this.right-c)*a+c,(this.top-d)*a+
+d)},add:function(a,b){if(null==a||null==b)throw new TypeError("Bounds.add cannot receive null values");return new OpenLayers.Bounds(this.left+a,this.bottom+b,this.right+a,this.top+b)},extend:function(a){if(a)switch(a.CLASS_NAME){case "OpenLayers.LonLat":this.extendXY(a.lon,a.lat);break;case "OpenLayers.Geometry.Point":this.extendXY(a.x,a.y);break;case "OpenLayers.Bounds":this.centerLonLat=null;if(null==this.left||a.left<this.left)this.left=a.left;if(null==this.bottom||a.bottom<this.bottom)this.bottom=
+a.bottom;if(null==this.right||a.right>this.right)this.right=a.right;if(null==this.top||a.top>this.top)this.top=a.top}},extendXY:function(a,b){this.centerLonLat=null;if(null==this.left||a<this.left)this.left=a;if(null==this.bottom||b<this.bottom)this.bottom=b;if(null==this.right||a>this.right)this.right=a;if(null==this.top||b>this.top)this.top=b},containsLonLat:function(a,b){"boolean"===typeof b&&(b={inclusive:b});b=b||{};var c=this.contains(a.lon,a.lat,b.inclusive),d=b.worldBounds;d&&!c&&(c=d.getWidth(),
+d=Math.round((a.lon-(d.left+d.right)/2)/c),c=this.containsLonLat({lon:a.lon-d*c,lat:a.lat},{inclusive:b.inclusive}));return c},containsPixel:function(a,b){return this.contains(a.x,a.y,b)},contains:function(a,b,c){null==c&&(c=!0);if(null==a||null==b)return!1;a=OpenLayers.Util.toFloat(a);b=OpenLayers.Util.toFloat(b);var d=!1;return d=c?a>=this.left&&a<=this.right&&b>=this.bottom&&b<=this.top:a>this.left&&a<this.right&&b>this.bottom&&b<this.top},intersectsBounds:function(a,b){"boolean"===typeof b&&(b=
+{inclusive:b});b=b||{};if(b.worldBounds){var c=this.wrapDateLine(b.worldBounds);a=a.wrapDateLine(b.worldBounds)}else c=this;null==b.inclusive&&(b.inclusive=!0);var d=!1,e=c.left==a.right||c.right==a.left||c.top==a.bottom||c.bottom==a.top;if(b.inclusive||!e)var d=a.top>=c.bottom&&a.top<=c.top||c.top>a.bottom&&c.top<a.top,e=a.left>=c.left&&a.left<=c.right||c.left>=a.left&&c.left<=a.right,f=a.right>=c.left&&a.right<=c.right||c.right>=a.left&&c.right<=a.right,d=(a.bottom>=c.bottom&&a.bottom<=c.top||c.bottom>=
+a.bottom&&c.bottom<=a.top||d)&&(e||f);if(b.worldBounds&&!d){var g=b.worldBounds,e=g.getWidth(),f=!g.containsBounds(c),g=!g.containsBounds(a);f&&!g?(a=a.add(-e,0),d=c.intersectsBounds(a,{inclusive:b.inclusive})):g&&!f&&(c=c.add(-e,0),d=a.intersectsBounds(c,{inclusive:b.inclusive}))}return d},containsBounds:function(a,b,c){null==b&&(b=!1);null==c&&(c=!0);var d=this.contains(a.left,a.bottom,c),e=this.contains(a.right,a.bottom,c),f=this.contains(a.left,a.top,c);a=this.contains(a.right,a.top,c);return b?
+d||e||f||a:d&&e&&f&&a},determineQuadrant:function(a){var b="",c=this.getCenterLonLat(),b=b+(a.lat<c.lat?"b":"t");return b+=a.lon<c.lon?"l":"r"},transform:function(a,b){this.centerLonLat=null;var c=OpenLayers.Projection.transform({x:this.left,y:this.bottom},a,b),d=OpenLayers.Projection.transform({x:this.right,y:this.bottom},a,b),e=OpenLayers.Projection.transform({x:this.left,y:this.top},a,b),f=OpenLayers.Projection.transform({x:this.right,y:this.top},a,b);this.left=Math.min(c.x,e.x);this.bottom=Math.min(c.y,
+d.y);this.right=Math.max(d.x,f.x);this.top=Math.max(e.y,f.y);return this},wrapDateLine:function(a,b){b=b||{};var c=b.leftTolerance||0,d=b.rightTolerance||0,e=this.clone();if(a){for(var f=a.getWidth();e.left<a.left&&e.right-d<=a.left;)e=e.add(f,0);for(;e.left+c>=a.right&&e.right>a.right;)e=e.add(-f,0);c=e.left+c;c<a.right&&(c>a.left&&e.right-d>a.right)&&(e=e.add(-f,0))}return e},CLASS_NAME:"OpenLayers.Bounds"});
+OpenLayers.Bounds.fromString=function(a,b){var c=a.split(",");return OpenLayers.Bounds.fromArray(c,b)};OpenLayers.Bounds.fromArray=function(a,b){return!0===b?new OpenLayers.Bounds(a[1],a[0],a[3],a[2]):new OpenLayers.Bounds(a[0],a[1],a[2],a[3])};OpenLayers.Bounds.fromSize=function(a){return new OpenLayers.Bounds(0,a.h,a.w,0)};OpenLayers.Bounds.oppositeQuadrant=function(a){var b;b=""+("t"==a.charAt(0)?"b":"t");return b+="l"==a.charAt(1)?"r":"l"};OpenLayers.Element={visible:function(a){return"none"!=OpenLayers.Util.getElement(a).style.display},toggle:function(){for(var a=0,b=arguments.length;a<b;a++){var c=OpenLayers.Util.getElement(arguments[a]),d=OpenLayers.Element.visible(c)?"none":"";c.style.display=d}},remove:function(a){a=OpenLayers.Util.getElement(a);a.parentNode.removeChild(a)},getHeight:function(a){a=OpenLayers.Util.getElement(a);return a.offsetHeight},hasClass:function(a,b){var c=a.className;return!!c&&RegExp("(^|\\s)"+b+"(\\s|$)").test(c)},
+addClass:function(a,b){OpenLayers.Element.hasClass(a,b)||(a.className+=(a.className?" ":"")+b);return a},removeClass:function(a,b){var c=a.className;c&&(a.className=OpenLayers.String.trim(c.replace(RegExp("(^|\\s+)"+b+"(\\s+|$)")," ")));return a},toggleClass:function(a,b){OpenLayers.Element.hasClass(a,b)?OpenLayers.Element.removeClass(a,b):OpenLayers.Element.addClass(a,b);return a},getStyle:function(a,b){a=OpenLayers.Util.getElement(a);var c=null;if(a&&a.style){c=a.style[OpenLayers.String.camelize(b)];
+c||(document.defaultView&&document.defaultView.getComputedStyle?c=(c=document.defaultView.getComputedStyle(a,null))?c.getPropertyValue(b):null:a.currentStyle&&(c=a.currentStyle[OpenLayers.String.camelize(b)]));var d=["left","top","right","bottom"];window.opera&&(-1!=OpenLayers.Util.indexOf(d,b)&&"static"==OpenLayers.Element.getStyle(a,"position"))&&(c="auto")}return"auto"==c?null:c}};OpenLayers.LonLat=OpenLayers.Class({lon:0,lat:0,initialize:function(a,b){OpenLayers.Util.isArray(a)&&(b=a[1],a=a[0]);this.lon=OpenLayers.Util.toFloat(a);this.lat=OpenLayers.Util.toFloat(b)},toString:function(){return"lon="+this.lon+",lat="+this.lat},toShortString:function(){return this.lon+", "+this.lat},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat)},add:function(a,b){if(null==a||null==b)throw new TypeError("LonLat.add cannot receive null values");return new OpenLayers.LonLat(this.lon+
+OpenLayers.Util.toFloat(a),this.lat+OpenLayers.Util.toFloat(b))},equals:function(a){var b=!1;null!=a&&(b=this.lon==a.lon&&this.lat==a.lat||isNaN(this.lon)&&isNaN(this.lat)&&isNaN(a.lon)&&isNaN(a.lat));return b},transform:function(a,b){var c=OpenLayers.Projection.transform({x:this.lon,y:this.lat},a,b);this.lon=c.x;this.lat=c.y;return this},wrapDateLine:function(a){var b=this.clone();if(a){for(;b.lon<a.left;)b.lon+=a.getWidth();for(;b.lon>a.right;)b.lon-=a.getWidth()}return b},CLASS_NAME:"OpenLayers.LonLat"});
+OpenLayers.LonLat.fromString=function(a){a=a.split(",");return new OpenLayers.LonLat(a[0],a[1])};OpenLayers.LonLat.fromArray=function(a){var b=OpenLayers.Util.isArray(a);return new OpenLayers.LonLat(b&&a[0],b&&a[1])};OpenLayers.Pixel=OpenLayers.Class({x:0,y:0,initialize:function(a,b){this.x=parseFloat(a);this.y=parseFloat(b)},toString:function(){return"x="+this.x+",y="+this.y},clone:function(){return new OpenLayers.Pixel(this.x,this.y)},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},distanceTo:function(a){return Math.sqrt(Math.pow(this.x-a.x,2)+Math.pow(this.y-a.y,2))},add:function(a,b){if(null==a||null==b)throw new TypeError("Pixel.add cannot receive null values");
+return new OpenLayers.Pixel(this.x+a,this.y+b)},offset:function(a){var b=this.clone();a&&(b=this.add(a.x,a.y));return b},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Size=OpenLayers.Class({w:0,h:0,initialize:function(a,b){this.w=parseFloat(a);this.h=parseFloat(b)},toString:function(){return"w="+this.w+",h="+this.h},clone:function(){return new OpenLayers.Size(this.w,this.h)},equals:function(a){var b=!1;null!=a&&(b=this.w==a.w&&this.h==a.h||isNaN(this.w)&&isNaN(this.h)&&isNaN(a.w)&&isNaN(a.h));return b},CLASS_NAME:"OpenLayers.Size"});OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},userError:function(a){alert(a)},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};
+(function(){for(var a=document.getElementsByTagName("script"),b=0,c=a.length;b<c;++b)if(-1!=a[b].src.indexOf("firebug.js")&&console){OpenLayers.Util.extend(OpenLayers.Console,console);break}})();OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){OpenLayers.Lang.code||OpenLayers.Lang.setCode();return OpenLayers.Lang.code},setCode:function(a){var b;a||(a="msie"==OpenLayers.BROWSER_NAME?navigator.userLanguage:navigator.language);a=a.split("-");a[0]=a[0].toLowerCase();"object"==typeof OpenLayers.Lang[a[0]]&&(b=a[0]);if(a[1]){var c=a[0]+"-"+a[1].toUpperCase();"object"==typeof OpenLayers.Lang[c]&&(b=c)}b||(OpenLayers.Console.warn("Failed to find OpenLayers.Lang."+a.join("-")+" dictionary, falling back to default language"),
+b=OpenLayers.Lang.defaultCode);OpenLayers.Lang.code=b},translate:function(a,b){var c=OpenLayers.Lang[OpenLayers.Lang.getCode()];(c=c&&c[a])||(c=a);b&&(c=OpenLayers.String.format(c,b));return c}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Util=OpenLayers.Util||{};OpenLayers.Util.getElement=function(){for(var a=[],b=0,c=arguments.length;b<c;b++){var d=arguments[b];"string"==typeof d&&(d=document.getElementById(d));if(1==arguments.length)return d;a.push(d)}return a};OpenLayers.Util.isElement=function(a){return!(!a||1!==a.nodeType)};OpenLayers.Util.isArray=function(a){return"[object Array]"===Object.prototype.toString.call(a)};OpenLayers.Util.removeItem=function(a,b){for(var c=a.length-1;0<=c;c--)a[c]==b&&a.splice(c,1);return a};
+OpenLayers.Util.indexOf=function(a,b){if("function"==typeof a.indexOf)return a.indexOf(b);for(var c=0,d=a.length;c<d;c++)if(a[c]==b)return c;return-1};OpenLayers.Util.dotless=/\./g;
+OpenLayers.Util.modifyDOMElement=function(a,b,c,d,e,f,g,h){b&&(a.id=b.replace(OpenLayers.Util.dotless,"_"));c&&(a.style.left=c.x+"px",a.style.top=c.y+"px");d&&(a.style.width=d.w+"px",a.style.height=d.h+"px");e&&(a.style.position=e);f&&(a.style.border=f);g&&(a.style.overflow=g);0<=parseFloat(h)&&1>parseFloat(h)?(a.style.filter="alpha(opacity="+100*h+")",a.style.opacity=h):1==parseFloat(h)&&(a.style.filter="",a.style.opacity="")};
+OpenLayers.Util.createDiv=function(a,b,c,d,e,f,g,h){var k=document.createElement("div");d&&(k.style.backgroundImage="url("+d+")");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="absolute");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,g,h);return k};
+OpenLayers.Util.createImage=function(a,b,c,d,e,f,g,h){var k=document.createElement("img");a||(a=OpenLayers.Util.createUniqueID("OpenLayersDiv"));e||(e="relative");OpenLayers.Util.modifyDOMElement(k,a,b,c,e,f,null,g);h&&(k.style.display="none",b=function(){k.style.display="";OpenLayers.Event.stopObservingElement(k)},OpenLayers.Event.observe(k,"load",b),OpenLayers.Event.observe(k,"error",b));k.style.alt=a;k.galleryImg="no";d&&(k.src=d);return k};OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;
+OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(null==OpenLayers.Util.alphaHackNeeded){var a=navigator.appVersion.split("MSIE"),a=parseFloat(a[1]),b=!1;try{b=!!document.body.filters}catch(c){}OpenLayers.Util.alphaHackNeeded=b&&5.5<=a&&7>a}return OpenLayers.Util.alphaHackNeeded};
+OpenLayers.Util.modifyAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){OpenLayers.Util.modifyDOMElement(a,b,c,d,f,null,null,k);b=a.childNodes[0];e&&(b.src=e);OpenLayers.Util.modifyDOMElement(b,a.id+"_innerImage",null,d,"relative",g);OpenLayers.Util.alphaHack()&&("none"!=a.style.display&&(a.style.display="inline-block"),null==h&&(h="scale"),a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+b.src+"', sizingMethod='"+h+"')",0<=parseFloat(a.style.opacity)&&1>parseFloat(a.style.opacity)&&
+(a.style.filter+=" alpha(opacity="+100*a.style.opacity+")"),b.style.filter="alpha(opacity=0)")};OpenLayers.Util.createAlphaImageDiv=function(a,b,c,d,e,f,g,h,k){var l=OpenLayers.Util.createDiv();k=OpenLayers.Util.createImage(null,null,null,null,null,null,null,k);k.className="olAlphaImg";l.appendChild(k);OpenLayers.Util.modifyAlphaImageDiv(l,a,b,c,d,e,f,g,h);return l};OpenLayers.Util.upperCaseObject=function(a){var b={},c;for(c in a)b[c.toUpperCase()]=a[c];return b};
+OpenLayers.Util.applyDefaults=function(a,b){a=a||{};var c="function"==typeof window.Event&&b instanceof window.Event,d;for(d in b)if(void 0===a[d]||!c&&b.hasOwnProperty&&b.hasOwnProperty(d)&&!a.hasOwnProperty(d))a[d]=b[d];!c&&(b&&b.hasOwnProperty&&b.hasOwnProperty("toString")&&!a.hasOwnProperty("toString"))&&(a.toString=b.toString);return a};
+OpenLayers.Util.getParameterString=function(a){var b=[],c;for(c in a){var d=a[c];if(null!=d&&"function"!=typeof d){if("object"==typeof d&&d.constructor==Array){for(var e=[],f,g=0,h=d.length;g<h;g++)f=d[g],e.push(encodeURIComponent(null===f||void 0===f?"":f));d=e.join(",")}else d=encodeURIComponent(d);b.push(encodeURIComponent(c)+"="+d)}}return b.join("&")};OpenLayers.Util.urlAppend=function(a,b){var c=a;if(b)var d=(a+" ").split(/[?&]/),c=c+(" "===d.pop()?b:d.length?"&"+b:"?"+b);return c};
+OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||OpenLayers._getScriptLocation()+"img/"};OpenLayers.Util.getImageLocation=function(a){return OpenLayers.Util.getImagesLocation()+a};OpenLayers.Util.Try=function(){for(var a=null,b=0,c=arguments.length;b<c;b++){var d=arguments[b];try{a=d();break}catch(e){}}return a};
+OpenLayers.Util.getXmlNodeValue=function(a){var b=null;OpenLayers.Util.Try(function(){b=a.text;b||(b=a.textContent);b||(b=a.firstChild.nodeValue)},function(){b=a.textContent});return b};OpenLayers.Util.mouseLeft=function(a,b){for(var c=a.relatedTarget?a.relatedTarget:a.toElement;c!=b&&null!=c;)c=c.parentNode;return c!=b};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(a,b){null==b&&(b=OpenLayers.Util.DEFAULT_PRECISION);"number"!==typeof a&&(a=parseFloat(a));return 0===b?a:parseFloat(a.toPrecision(b))};
+OpenLayers.Util.rad=function(a){return a*Math.PI/180};OpenLayers.Util.deg=function(a){return 180*a/Math.PI};OpenLayers.Util.VincentyConstants={a:6378137,b:6356752.3142,f:1/298.257223563};
+OpenLayers.Util.distVincenty=function(a,b){for(var c=OpenLayers.Util.VincentyConstants,d=c.a,e=c.b,c=c.f,f=OpenLayers.Util.rad(b.lon-a.lon),g=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(a.lat))),h=Math.atan((1-c)*Math.tan(OpenLayers.Util.rad(b.lat))),k=Math.sin(g),g=Math.cos(g),l=Math.sin(h),h=Math.cos(h),m=f,p=2*Math.PI,n=20;1E-12<Math.abs(m-p)&&0<--n;){var q=Math.sin(m),r=Math.cos(m),s=Math.sqrt(h*q*h*q+(g*l-k*h*r)*(g*l-k*h*r));if(0==s)return 0;var r=k*l+g*h*r,t=Math.atan2(s,r),u=Math.asin(g*h*
+q/s),v=Math.cos(u)*Math.cos(u),q=r-2*k*l/v,w=c/16*v*(4+c*(4-3*v)),p=m,m=f+(1-w)*c*Math.sin(u)*(t+w*s*(q+w*r*(-1+2*q*q)))}if(0==n)return NaN;d=v*(d*d-e*e)/(e*e);c=d/1024*(256+d*(-128+d*(74-47*d)));return(e*(1+d/16384*(4096+d*(-768+d*(320-175*d))))*(t-c*s*(q+c/4*(r*(-1+2*q*q)-c/6*q*(-3+4*s*s)*(-3+4*q*q))))).toFixed(3)/1E3};
+OpenLayers.Util.destinationVincenty=function(a,b,c){var d=OpenLayers.Util,e=d.VincentyConstants,f=e.a,g=e.b,h=e.f,e=a.lon;a=a.lat;var k=d.rad(b);b=Math.sin(k);k=Math.cos(k);a=(1-h)*Math.tan(d.rad(a));var l=1/Math.sqrt(1+a*a),m=a*l,p=Math.atan2(a,k);a=l*b;for(var n=1-a*a,f=n*(f*f-g*g)/(g*g),q=1+f/16384*(4096+f*(-768+f*(320-175*f))),r=f/1024*(256+f*(-128+f*(74-47*f))),f=c/(g*q),s=2*Math.PI;1E-12<Math.abs(f-s);)var t=Math.cos(2*p+f),u=Math.sin(f),v=Math.cos(f),w=r*u*(t+r/4*(v*(-1+2*t*t)-r/6*t*(-3+4*
+u*u)*(-3+4*t*t))),s=f,f=c/(g*q)+w;c=m*u-l*v*k;g=Math.atan2(m*v+l*u*k,(1-h)*Math.sqrt(a*a+c*c));b=Math.atan2(u*b,l*v-m*u*k);k=h/16*n*(4+h*(4-3*n));t=b-(1-k)*h*a*(f+k*u*(t+k*v*(-1+2*t*t)));Math.atan2(a,-c);return new OpenLayers.LonLat(e+d.deg(t),d.deg(g))};
+OpenLayers.Util.getParameters=function(a,b){b=b||{};a=null===a||void 0===a?window.location.href:a;var c="";if(OpenLayers.String.contains(a,"?"))var d=a.indexOf("?")+1,c=OpenLayers.String.contains(a,"#")?a.indexOf("#"):a.length,c=a.substring(d,c);for(var d={},c=c.split(/[&;]/),e=0,f=c.length;e<f;++e){var g=c[e].split("=");if(g[0]){var h=g[0];try{h=decodeURIComponent(h)}catch(k){h=unescape(h)}g=(g[1]||"").replace(/\+/g," ");try{g=decodeURIComponent(g)}catch(l){g=unescape(g)}!1!==b.splitArgs&&(g=g.split(","));
+1==g.length&&(g=g[0]);d[h]=g}}return d};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(a){a=null==a?"id_":a.replace(OpenLayers.Util.dotless,"_");OpenLayers.Util.lastSeqID+=1;return a+OpenLayers.Util.lastSeqID};OpenLayers.INCHES_PER_UNIT={inches:1,ft:12,mi:63360,m:39.37,km:39370,dd:4374754,yd:36};OpenLayers.INCHES_PER_UNIT["in"]=OpenLayers.INCHES_PER_UNIT.inches;OpenLayers.INCHES_PER_UNIT.degrees=OpenLayers.INCHES_PER_UNIT.dd;OpenLayers.INCHES_PER_UNIT.nmi=1852*OpenLayers.INCHES_PER_UNIT.m;
+OpenLayers.METERS_PER_INCH=0.0254000508001016;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{Inch:OpenLayers.INCHES_PER_UNIT.inches,Meter:1/OpenLayers.METERS_PER_INCH,Foot:0.3048006096012192/OpenLayers.METERS_PER_INCH,IFoot:0.3048/OpenLayers.METERS_PER_INCH,ClarkeFoot:0.3047972651151/OpenLayers.METERS_PER_INCH,SearsFoot:0.30479947153867626/OpenLayers.METERS_PER_INCH,GoldCoastFoot:0.3047997101815088/OpenLayers.METERS_PER_INCH,IInch:0.0254/OpenLayers.METERS_PER_INCH,MicroInch:2.54E-5/OpenLayers.METERS_PER_INCH,Mil:2.54E-8/OpenLayers.METERS_PER_INCH,
+Centimeter:0.01/OpenLayers.METERS_PER_INCH,Kilometer:1E3/OpenLayers.METERS_PER_INCH,Yard:0.9144018288036576/OpenLayers.METERS_PER_INCH,SearsYard:0.914398414616029/OpenLayers.METERS_PER_INCH,IndianYard:0.9143985307444408/OpenLayers.METERS_PER_INCH,IndianYd37:0.91439523/OpenLayers.METERS_PER_INCH,IndianYd62:0.9143988/OpenLayers.METERS_PER_INCH,IndianYd75:0.9143985/OpenLayers.METERS_PER_INCH,IndianFoot:0.30479951/OpenLayers.METERS_PER_INCH,IndianFt37:0.30479841/OpenLayers.METERS_PER_INCH,IndianFt62:0.3047996/
+OpenLayers.METERS_PER_INCH,IndianFt75:0.3047995/OpenLayers.METERS_PER_INCH,Mile:1609.3472186944373/OpenLayers.METERS_PER_INCH,IYard:0.9144/OpenLayers.METERS_PER_INCH,IMile:1609.344/OpenLayers.METERS_PER_INCH,NautM:1852/OpenLayers.METERS_PER_INCH,"Lat-66":110943.31648893273/OpenLayers.METERS_PER_INCH,"Lat-83":110946.25736872235/OpenLayers.METERS_PER_INCH,Decimeter:0.1/OpenLayers.METERS_PER_INCH,Millimeter:0.001/OpenLayers.METERS_PER_INCH,Dekameter:10/OpenLayers.METERS_PER_INCH,Decameter:10/OpenLayers.METERS_PER_INCH,
+Hectometer:100/OpenLayers.METERS_PER_INCH,GermanMeter:1.0000135965/OpenLayers.METERS_PER_INCH,CaGrid:0.999738/OpenLayers.METERS_PER_INCH,ClarkeChain:20.1166194976/OpenLayers.METERS_PER_INCH,GunterChain:20.11684023368047/OpenLayers.METERS_PER_INCH,BenoitChain:20.116782494375872/OpenLayers.METERS_PER_INCH,SearsChain:20.11676512155/OpenLayers.METERS_PER_INCH,ClarkeLink:0.201166194976/OpenLayers.METERS_PER_INCH,GunterLink:0.2011684023368047/OpenLayers.METERS_PER_INCH,BenoitLink:0.20116782494375873/OpenLayers.METERS_PER_INCH,
+SearsLink:0.2011676512155/OpenLayers.METERS_PER_INCH,Rod:5.02921005842012/OpenLayers.METERS_PER_INCH,IntnlChain:20.1168/OpenLayers.METERS_PER_INCH,IntnlLink:0.201168/OpenLayers.METERS_PER_INCH,Perch:5.02921005842012/OpenLayers.METERS_PER_INCH,Pole:5.02921005842012/OpenLayers.METERS_PER_INCH,Furlong:201.1684023368046/OpenLayers.METERS_PER_INCH,Rood:3.778266898/OpenLayers.METERS_PER_INCH,CapeFoot:0.3047972615/OpenLayers.METERS_PER_INCH,Brealey:375/OpenLayers.METERS_PER_INCH,ModAmFt:0.304812252984506/
+OpenLayers.METERS_PER_INCH,Fathom:1.8288/OpenLayers.METERS_PER_INCH,"NautM-UK":1853.184/OpenLayers.METERS_PER_INCH,"50kilometers":5E4/OpenLayers.METERS_PER_INCH,"150kilometers":15E4/OpenLayers.METERS_PER_INCH});
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT,{mm:OpenLayers.INCHES_PER_UNIT.Meter/1E3,cm:OpenLayers.INCHES_PER_UNIT.Meter/100,dm:100*OpenLayers.INCHES_PER_UNIT.Meter,km:1E3*OpenLayers.INCHES_PER_UNIT.Meter,kmi:OpenLayers.INCHES_PER_UNIT.nmi,fath:OpenLayers.INCHES_PER_UNIT.Fathom,ch:OpenLayers.INCHES_PER_UNIT.IntnlChain,link:OpenLayers.INCHES_PER_UNIT.IntnlLink,"us-in":OpenLayers.INCHES_PER_UNIT.inches,"us-ft":OpenLayers.INCHES_PER_UNIT.Foot,"us-yd":OpenLayers.INCHES_PER_UNIT.Yard,"us-ch":OpenLayers.INCHES_PER_UNIT.GunterChain,
+"us-mi":OpenLayers.INCHES_PER_UNIT.Mile,"ind-yd":OpenLayers.INCHES_PER_UNIT.IndianYd37,"ind-ft":OpenLayers.INCHES_PER_UNIT.IndianFt37,"ind-ch":20.11669506/OpenLayers.METERS_PER_INCH});OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(a){return 1<a?1/a:a};OpenLayers.Util.getResolutionFromScale=function(a,b){var c;a&&(null==b&&(b="degrees"),c=1/(OpenLayers.Util.normalizeScale(a)*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH));return c};
+OpenLayers.Util.getScaleFromResolution=function(a,b){null==b&&(b="degrees");return a*OpenLayers.INCHES_PER_UNIT[b]*OpenLayers.DOTS_PER_INCH};
+OpenLayers.Util.pagePosition=function(a){var b=[0,0],c=OpenLayers.Util.getViewportElement();if(!a||a==window||a==c)return b;var d=OpenLayers.IS_GECKO&&document.getBoxObjectFor&&"absolute"==OpenLayers.Element.getStyle(a,"position")&&(""==a.style.top||""==a.style.left),e=null;if(a.getBoundingClientRect)a=a.getBoundingClientRect(),e=window.pageYOffset||c.scrollTop,b[0]=a.left+(window.pageXOffset||c.scrollLeft),b[1]=a.top+e;else if(document.getBoxObjectFor&&!d)a=document.getBoxObjectFor(a),c=document.getBoxObjectFor(c),
+b[0]=a.screenX-c.screenX,b[1]=a.screenY-c.screenY;else{b[0]=a.offsetLeft;b[1]=a.offsetTop;e=a.offsetParent;if(e!=a)for(;e;)b[0]+=e.offsetLeft,b[1]+=e.offsetTop,e=e.offsetParent;c=OpenLayers.BROWSER_NAME;if("opera"==c||"safari"==c&&"absolute"==OpenLayers.Element.getStyle(a,"position"))b[1]-=document.body.offsetTop;for(e=a.offsetParent;e&&e!=document.body;){b[0]-=e.scrollLeft;if("opera"!=c||"TR"!=e.tagName)b[1]-=e.scrollTop;e=e.offsetParent}}return b};
+OpenLayers.Util.getViewportElement=function(){var a=arguments.callee.viewportElement;void 0==a&&(a="msie"==OpenLayers.BROWSER_NAME&&"CSS1Compat"!=document.compatMode?document.body:document.documentElement,arguments.callee.viewportElement=a);return a};
+OpenLayers.Util.isEquivalentUrl=function(a,b,c){c=c||{};OpenLayers.Util.applyDefaults(c,{ignoreCase:!0,ignorePort80:!0,ignoreHash:!0,splitArgs:!1});a=OpenLayers.Util.createUrlObject(a,c);b=OpenLayers.Util.createUrlObject(b,c);for(var d in a)if("args"!==d&&a[d]!=b[d])return!1;for(d in a.args){if(a.args[d]!=b.args[d])return!1;delete b.args[d]}for(d in b.args)return!1;return!0};
+OpenLayers.Util.createUrlObject=function(a,b){b=b||{};if(!/^\w+:\/\//.test(a)){var c=window.location,d=c.port?":"+c.port:"",d=c.protocol+"//"+c.host.split(":").shift()+d;0===a.indexOf("/")?a=d+a:(c=c.pathname.split("/"),c.pop(),a=d+c.join("/")+"/"+a)}b.ignoreCase&&(a=a.toLowerCase());c=document.createElement("a");c.href=a;d={};d.host=c.host.split(":").shift();d.protocol=c.protocol;d.port=b.ignorePort80?"80"==c.port||"0"==c.port?"":c.port:""==c.port||"0"==c.port?"80":c.port;d.hash=b.ignoreHash||"#"===
+c.hash?"":c.hash;var e=c.search;e||(e=a.indexOf("?"),e=-1!=e?a.substr(e):"");d.args=OpenLayers.Util.getParameters(e,{splitArgs:b.splitArgs});d.pathname="/"==c.pathname.charAt(0)?c.pathname:"/"+c.pathname;return d};OpenLayers.Util.removeTail=function(a){var b=null,b=a.indexOf("?"),c=a.indexOf("#");return b=-1==b?-1!=c?a.substr(0,c):a:-1!=c?a.substr(0,Math.min(b,c)):a.substr(0,b)};OpenLayers.IS_GECKO=function(){var a=navigator.userAgent.toLowerCase();return-1==a.indexOf("webkit")&&-1!=a.indexOf("gecko")}();
+OpenLayers.CANVAS_SUPPORTED=function(){var a=document.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))}();OpenLayers.BROWSER_NAME=function(){var a="",b=navigator.userAgent.toLowerCase();-1!=b.indexOf("opera")?a="opera":-1!=b.indexOf("msie")?a="msie":-1!=b.indexOf("safari")?a="safari":-1!=b.indexOf("mozilla")&&(a=-1!=b.indexOf("firefox")?"firefox":"mozilla");return a}();OpenLayers.Util.getBrowserName=function(){return OpenLayers.BROWSER_NAME};
+OpenLayers.Util.getRenderedDimensions=function(a,b,c){var d,e,f=document.createElement("div");f.style.visibility="hidden";for(var g=c&&c.containerElement?c.containerElement:document.body,h=!1,k=null,l=g;l&&"body"!=l.tagName.toLowerCase();){var m=OpenLayers.Element.getStyle(l,"position");if("absolute"==m){h=!0;break}else if(m&&"static"!=m)break;l=l.parentNode}!h||0!==g.clientHeight&&0!==g.clientWidth||(k=document.createElement("div"),k.style.visibility="hidden",k.style.position="absolute",k.style.overflow=
+"visible",k.style.width=document.body.clientWidth+"px",k.style.height=document.body.clientHeight+"px",k.appendChild(f));f.style.position="absolute";b&&(b.w?(d=b.w,f.style.width=d+"px"):b.h&&(e=b.h,f.style.height=e+"px"));c&&c.displayClass&&(f.className=c.displayClass);b=document.createElement("div");b.innerHTML=a;b.style.overflow="visible";if(b.childNodes)for(a=0,c=b.childNodes.length;a<c;a++)b.childNodes[a].style&&(b.childNodes[a].style.overflow="visible");f.appendChild(b);k?g.appendChild(k):g.appendChild(f);
+d||(d=parseInt(b.scrollWidth),f.style.width=d+"px");e||(e=parseInt(b.scrollHeight));f.removeChild(b);k?(k.removeChild(f),g.removeChild(k)):g.removeChild(f);return new OpenLayers.Size(d,e)};
+OpenLayers.Util.getScrollbarWidth=function(){var a=OpenLayers.Util._scrollbarWidth;if(null==a){var b=null,c=null,b=a=0,b=document.createElement("div");b.style.position="absolute";b.style.top="-1000px";b.style.left="-1000px";b.style.width="100px";b.style.height="50px";b.style.overflow="hidden";c=document.createElement("div");c.style.width="100%";c.style.height="200px";b.appendChild(c);document.body.appendChild(b);a=c.offsetWidth;b.style.overflow="scroll";b=c.offsetWidth;document.body.removeChild(document.body.lastChild);
+OpenLayers.Util._scrollbarWidth=a-b;a=OpenLayers.Util._scrollbarWidth}return a};
+OpenLayers.Util.getFormattedLonLat=function(a,b,c){c||(c="dms");a=(a+540)%360-180;var d=Math.abs(a),e=Math.floor(d),f=d=(d-e)/(1/60),d=Math.floor(d),f=Math.round(10*((f-d)/(1/60))),f=f/10;60<=f&&(f-=60,d+=1,60<=d&&(d-=60,e+=1));10>e&&(e="0"+e);e+="\u00b0";0<=c.indexOf("dm")&&(10>d&&(d="0"+d),e+=d+"'",0<=c.indexOf("dms")&&(10>f&&(f="0"+f),e+=f+'"'));return e="lon"==b?e+(0>a?OpenLayers.i18n("W"):OpenLayers.i18n("E")):e+(0>a?OpenLayers.i18n("S"):OpenLayers.i18n("N"))};OpenLayers.Event={observers:!1,KEY_SPACE:32,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(a){return a.target||a.srcElement},isSingleTouch:function(a){return a.touches&&1==a.touches.length},isMultiTouch:function(a){return a.touches&&1<a.touches.length},isLeftClick:function(a){return a.which&&1==a.which||a.button&&1==a.button},isRightClick:function(a){return a.which&&3==a.which||a.button&&2==a.button},stop:function(a,
+b){b||OpenLayers.Event.preventDefault(a);a.stopPropagation?a.stopPropagation():a.cancelBubble=!0},preventDefault:function(a){a.preventDefault?a.preventDefault():a.returnValue=!1},findElement:function(a,b){for(var c=OpenLayers.Event.element(a);c.parentNode&&(!c.tagName||c.tagName.toUpperCase()!=b.toUpperCase());)c=c.parentNode;return c},observe:function(a,b,c,d){a=OpenLayers.Util.getElement(a);d=d||!1;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.attachEvent)&&(b="keydown");
+this.observers||(this.observers={});if(!a._eventCacheID){var e="eventCacheID_";a.id&&(e=a.id+"_"+e);a._eventCacheID=OpenLayers.Util.createUniqueID(e)}e=a._eventCacheID;this.observers[e]||(this.observers[e]=[]);this.observers[e].push({element:a,name:b,observer:c,useCapture:d});a.addEventListener?a.addEventListener(b,c,d):a.attachEvent&&a.attachEvent("on"+b,c)},stopObservingElement:function(a){a=OpenLayers.Util.getElement(a)._eventCacheID;this._removeElementObservers(OpenLayers.Event.observers[a])},
+_removeElementObservers:function(a){if(a)for(var b=a.length-1;0<=b;b--){var c=a[b];OpenLayers.Event.stopObserving.apply(this,[c.element,c.name,c.observer,c.useCapture])}},stopObserving:function(a,b,c,d){d=d||!1;a=OpenLayers.Util.getElement(a);var e=a._eventCacheID;"keypress"==b&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||a.detachEvent)&&(b="keydown");var f=!1,g=OpenLayers.Event.observers[e];if(g)for(var h=0;!f&&h<g.length;){var k=g[h];if(k.name==b&&k.observer==c&&k.useCapture==d){g.splice(h,
+1);0==g.length&&delete OpenLayers.Event.observers[e];f=!0;break}h++}f&&(a.removeEventListener?a.removeEventListener(b,c,d):a&&a.detachEvent&&a.detachEvent("on"+b,c));return f},unloadCache:function(){if(OpenLayers.Event&&OpenLayers.Event.observers){for(var a in OpenLayers.Event.observers)OpenLayers.Event._removeElementObservers.apply(this,[OpenLayers.Event.observers[a]]);OpenLayers.Event.observers=!1}},CLASS_NAME:"OpenLayers.Event"};
+OpenLayers.Event.observe(window,"unload",OpenLayers.Event.unloadCache,!1);
+OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:"mouseover mouseout mousedown mouseup mousemove click dblclick rightclick dblrightclick resize focus blur touchstart touchmove touchend keydown".split(" "),listeners:null,object:null,element:null,eventHandler:null,fallThrough:null,includeXY:!1,extensions:null,extensionCount:null,clearMouseListener:null,initialize:function(a,b,c,d,e){OpenLayers.Util.extend(this,e);this.object=a;this.fallThrough=d;this.listeners={};this.extensions={};this.extensionCount=
+{};this._msTouches=[];null!=b&&this.attachToElement(b)},destroy:function(){for(var a in this.extensions)"boolean"!==typeof this.extensions[a]&&this.extensions[a].destroy();this.extensions=null;this.element&&(OpenLayers.Event.stopObservingElement(this.element),this.element.hasScrollEvent&&OpenLayers.Event.stopObserving(window,"scroll",this.clearMouseListener));this.eventHandler=this.fallThrough=this.object=this.listeners=this.element=null},addEventType:function(a){},attachToElement:function(a){this.element?
+OpenLayers.Event.stopObservingElement(this.element):(this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this),this.clearMouseListener=OpenLayers.Function.bind(this.clearMouseCache,this));this.element=a;for(var b=!!window.navigator.msMaxTouchPoints,c,d=0,e=this.BROWSER_EVENTS.length;d<e;d++)c=this.BROWSER_EVENTS[d],OpenLayers.Event.observe(a,c,this.eventHandler),b&&0===c.indexOf("touch")&&this.addMsTouchListener(a,c,this.eventHandler);OpenLayers.Event.observe(a,"dragstart",
+OpenLayers.Event.stop)},on:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.register(b,a.scope,a[b])},register:function(a,b,c,d){a in OpenLayers.Events&&!this.extensions[a]&&(this.extensions[a]=new OpenLayers.Events[a](this));if(null!=c){null==b&&(b=this.object);var e=this.listeners[a];e||(e=[],this.listeners[a]=e,this.extensionCount[a]=0);b={obj:b,func:c};d?(e.splice(this.extensionCount[a],0,b),"object"===typeof d&&d.extension&&this.extensionCount[a]++):e.push(b)}},registerPriority:function(a,
+b,c){this.register(a,b,c,!0)},un:function(a){for(var b in a)"scope"!=b&&a.hasOwnProperty(b)&&this.unregister(b,a.scope,a[b])},unregister:function(a,b,c){null==b&&(b=this.object);a=this.listeners[a];if(null!=a)for(var d=0,e=a.length;d<e;d++)if(a[d].obj==b&&a[d].func==c){a.splice(d,1);break}},remove:function(a){null!=this.listeners[a]&&(this.listeners[a]=[])},triggerEvent:function(a,b){var c=this.listeners[a];if(c&&0!=c.length){null==b&&(b={});b.object=this.object;b.element=this.element;b.type||(b.type=
+a);for(var c=c.slice(),d,e=0,f=c.length;e<f&&(d=c[e],d=d.func.apply(d.obj,[b]),void 0==d||!1!=d);e++);this.fallThrough||OpenLayers.Event.stop(b,!0);return d}},handleBrowserEvent:function(a){var b=a.type,c=this.listeners[b];if(c&&0!=c.length){if((c=a.touches)&&c[0]){for(var d=0,e=0,f=c.length,g,h=0;h<f;++h)g=this.getTouchClientXY(c[h]),d+=g.clientX,e+=g.clientY;a.clientX=d/f;a.clientY=e/f}this.includeXY&&(a.xy=this.getMousePosition(a));this.triggerEvent(b,a)}},getTouchClientXY:function(a){var b=window.olMockWin||
+window,c=b.pageXOffset,b=b.pageYOffset,d=a.clientX,e=a.clientY;if(0===a.pageY&&Math.floor(e)>Math.floor(a.pageY)||0===a.pageX&&Math.floor(d)>Math.floor(a.pageX))d-=c,e-=b;else if(e<a.pageY-b||d<a.pageX-c)d=a.pageX-c,e=a.pageY-b;a.olClientX=d;a.olClientY=e;return{clientX:d,clientY:e}},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null},getMousePosition:function(a){this.includeXY?this.element.hasScrollEvent||(OpenLayers.Event.observe(window,"scroll",
+this.clearMouseListener),this.element.hasScrollEvent=!0):this.clearMouseCache();if(!this.element.scrolls){var b=OpenLayers.Util.getViewportElement();this.element.scrolls=[window.pageXOffset||b.scrollLeft,window.pageYOffset||b.scrollTop]}this.element.lefttop||(this.element.lefttop=[document.documentElement.clientLeft||0,document.documentElement.clientTop||0]);this.element.offsets||(this.element.offsets=OpenLayers.Util.pagePosition(this.element));return new OpenLayers.Pixel(a.clientX+this.element.scrolls[0]-
+this.element.offsets[0]-this.element.lefttop[0],a.clientY+this.element.scrolls[1]-this.element.offsets[1]-this.element.lefttop[1])},addMsTouchListener:function(a,b,c){function d(a){c(OpenLayers.Util.applyDefaults({stopPropagation:function(){for(var a=e.length-1;0<=a;--a)e[a].stopPropagation()},preventDefault:function(){for(var a=e.length-1;0<=a;--a)e[a].preventDefault()},type:b},a))}var e=this._msTouches;switch(b){case "touchstart":return this.addMsTouchListenerStart(a,b,d);case "touchend":return this.addMsTouchListenerEnd(a,
+b,d);case "touchmove":return this.addMsTouchListenerMove(a,b,d);default:throw"Unknown touch event type";}},addMsTouchListenerStart:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerDown",function(a){for(var b=!1,g=0,h=d.length;g<h;++g)if(d[g].pointerId==a.pointerId){b=!0;break}b||d.push(a);a.touches=d.slice();c(a)});OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,c=d.length;b<c;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}})},addMsTouchListenerMove:function(a,
+b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerMove",function(a){if(a.pointerType!=a.MSPOINTER_TYPE_MOUSE||0!=a.buttons)if(1!=d.length||d[0].pageX!=a.pageX||d[0].pageY!=a.pageY){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d[b]=a;break}a.touches=d.slice();c(a)}})},addMsTouchListenerEnd:function(a,b,c){var d=this._msTouches;OpenLayers.Event.observe(a,"MSPointerUp",function(a){for(var b=0,g=d.length;b<g;++b)if(d[b].pointerId==a.pointerId){d.splice(b,1);break}a.touches=
+d.slice();c(a)})},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Events.buttonclick=OpenLayers.Class({target:null,events:"mousedown mouseup click dblclick touchstart touchmove touchend keydown".split(" "),startRegEx:/^mousedown|touchstart$/,cancelRegEx:/^touchmove$/,completeRegEx:/^mouseup|touchend$/,initialize:function(a){this.target=a;for(a=this.events.length-1;0<=a;--a)this.target.register(this.events[a],this,this.buttonClick,{extension:!0})},destroy:function(){for(var a=this.events.length-1;0<=a;--a)this.target.unregister(this.events[a],this,this.buttonClick);
+delete this.target},getPressedButton:function(a){var b=3,c;do{if(OpenLayers.Element.hasClass(a,"olButton")){c=a;break}a=a.parentNode}while(0<--b&&a);return c},ignore:function(a){var b=3,c=!1;do{if("a"===a.nodeName.toLowerCase()){c=!0;break}a=a.parentNode}while(0<--b&&a);return c},buttonClick:function(a){var b=!0,c=OpenLayers.Event.element(a);if(c&&(OpenLayers.Event.isLeftClick(a)||!~a.type.indexOf("mouse")))if(c=this.getPressedButton(c)){if("keydown"===a.type)switch(a.keyCode){case OpenLayers.Event.KEY_RETURN:case OpenLayers.Event.KEY_SPACE:this.target.triggerEvent("buttonclick",
+{buttonElement:c}),OpenLayers.Event.stop(a),b=!1}else if(this.startEvt){if(this.completeRegEx.test(a.type)){var b=OpenLayers.Util.pagePosition(c),d=OpenLayers.Util.getViewportElement(),e=window.pageYOffset||d.scrollTop;b[0]-=window.pageXOffset||d.scrollLeft;b[1]-=e;this.target.triggerEvent("buttonclick",{buttonElement:c,buttonXY:{x:this.startEvt.clientX-b[0],y:this.startEvt.clientY-b[1]}})}this.cancelRegEx.test(a.type)&&delete this.startEvt;OpenLayers.Event.stop(a);b=!1}this.startRegEx.test(a.type)&&
+(this.startEvt=a,OpenLayers.Event.stop(a),b=!1)}else b=!this.ignore(OpenLayers.Event.element(a)),delete this.startEvt;return b}});OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:!1,displayClass:"",title:"",autoActivate:!1,active:null,handlerOptions:null,handler:null,eventListeners:null,events:null,initialize:function(a){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,a);this.events=new OpenLayers.Events(this);if(this.eventListeners instanceof Object)this.events.on(this.eventListeners);null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+
+"_"))},destroy:function(){this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy(),this.events=null);this.eventListeners=null;this.handler&&(this.handler.destroy(),this.handler=null);if(this.handlers){for(var a in this.handlers)this.handlers.hasOwnProperty(a)&&"function"==typeof this.handlers[a].destroy&&this.handlers[a].destroy();this.handlers=null}this.map&&(this.map.removeControl(this),this.map=null);this.div=null},setMap:function(a){this.map=a;this.handler&&
+this.handler.setMap(a)},draw:function(a){null==this.div&&(this.div=OpenLayers.Util.createDiv(this.id),this.div.className=this.displayClass,this.allowSelection||(this.div.className+=" olControlNoSelect",this.div.setAttribute("unselectable","on",0),this.div.onselectstart=OpenLayers.Function.False),""!=this.title&&(this.div.title=this.title));null!=a&&(this.position=a.clone());this.moveTo(this.position);return this.div},moveTo:function(a){null!=a&&null!=this.div&&(this.div.style.left=a.x+"px",this.div.style.top=
+a.y+"px")},activate:function(){if(this.active)return!1;this.handler&&this.handler.activate();this.active=!0;this.map&&OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");this.events.triggerEvent("activate");return!0},deactivate:function(){return this.active?(this.handler&&this.handler.deactivate(),this.active=!1,this.map&&OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active"),this.events.triggerEvent("deactivate"),
+!0):!1},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,initialize:function(){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){this.bounds=this.id=null},clone:function(){return new OpenLayers.Geometry},setBounds:function(a){a&&(this.bounds=a.clone())},clearBounds:function(){this.bounds=null;this.parent&&this.parent.clearBounds()},extendBounds:function(a){this.getBounds()?this.bounds.extend(a):this.setBounds(a)},getBounds:function(){null==this.bounds&&this.calculateBounds();
+return this.bounds},calculateBounds:function(){},distanceTo:function(a,b){},getVertices:function(a){},atPoint:function(a,b,c){var d=!1;null!=this.getBounds()&&null!=a&&(b=null!=b?b:0,c=null!=c?c:0,d=(new OpenLayers.Bounds(this.bounds.left-b,this.bounds.bottom-c,this.bounds.right+b,this.bounds.top+c)).containsLonLat(a));return d},getLength:function(){return 0},getArea:function(){return 0},getCentroid:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.WKT?OpenLayers.Format.WKT.prototype.write(new OpenLayers.Feature.Vector(this)):
+Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.fromWKT=function(a){var b;if(OpenLayers.Format&&OpenLayers.Format.WKT){var c=OpenLayers.Geometry.fromWKT.format;c||(c=new OpenLayers.Format.WKT,OpenLayers.Geometry.fromWKT.format=c);a=c.read(a);if(a instanceof OpenLayers.Feature.Vector)b=a.geometry;else if(OpenLayers.Util.isArray(a)){b=a.length;for(var c=Array(b),d=0;d<b;++d)c[d]=a[d].geometry;b=new OpenLayers.Geometry.Collection(c)}}return b};
+OpenLayers.Geometry.segmentsIntersect=function(a,b,c){var d=c&&c.point;c=c&&c.tolerance;var e=!1,f=a.x1-b.x1,g=a.y1-b.y1,h=a.x2-a.x1,k=a.y2-a.y1,l=b.y2-b.y1,m=b.x2-b.x1,p=l*h-m*k,l=m*g-l*f,g=h*g-k*f;0==p?0==l&&0==g&&(e=!0):(f=l/p,p=g/p,0<=f&&(1>=f&&0<=p&&1>=p)&&(d?(h=a.x1+f*h,p=a.y1+f*k,e=new OpenLayers.Geometry.Point(h,p)):e=!0));if(c)if(e){if(d)a:for(a=[a,b],b=0;2>b;++b)for(f=a[b],k=1;3>k;++k)if(h=f["x"+k],p=f["y"+k],d=Math.sqrt(Math.pow(h-e.x,2)+Math.pow(p-e.y,2)),d<c){e.x=h;e.y=p;break a}}else a:for(a=
+[a,b],b=0;2>b;++b)for(h=a[b],p=a[(b+1)%2],k=1;3>k;++k)if(f={x:h["x"+k],y:h["y"+k]},g=OpenLayers.Geometry.distanceToSegment(f,p),g.distance<c){e=d?new OpenLayers.Geometry.Point(f.x,f.y):!0;break a}return e};OpenLayers.Geometry.distanceToSegment=function(a,b){var c=OpenLayers.Geometry.distanceSquaredToSegment(a,b);c.distance=Math.sqrt(c.distance);return c};
+OpenLayers.Geometry.distanceSquaredToSegment=function(a,b){var c=a.x,d=a.y,e=b.x1,f=b.y1,g=b.x2,h=b.y2,k=g-e,l=h-f,m=(k*(c-e)+l*(d-f))/(Math.pow(k,2)+Math.pow(l,2));0>=m||(1<=m?(e=g,f=h):(e+=m*k,f+=m*l));return{distance:Math.pow(e-c,2)+Math.pow(f-d,2),x:e,y:f,along:m}};OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(a){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];null!=a&&this.addComponents(a)},destroy:function(){this.components.length=0;this.components=null;OpenLayers.Geometry.prototype.destroy.apply(this,arguments)},clone:function(){for(var a=eval("new "+this.CLASS_NAME+"()"),b=0,c=this.components.length;b<c;b++)a.addComponent(this.components[b].clone());
+OpenLayers.Util.applyDefaults(a,this);return a},getComponentsString:function(){for(var a=[],b=0,c=this.components.length;b<c;b++)a.push(this.components[b].toShortString());return a.join(",")},calculateBounds:function(){this.bounds=null;var a=new OpenLayers.Bounds,b=this.components;if(b)for(var c=0,d=b.length;c<d;c++)a.extend(b[c].getBounds());null!=a.left&&(null!=a.bottom&&null!=a.right&&null!=a.top)&&this.setBounds(a)},addComponents:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<
+c;b++)this.addComponent(a[b])},addComponent:function(a,b){var c=!1;if(a&&(null==this.componentTypes||-1<OpenLayers.Util.indexOf(this.componentTypes,a.CLASS_NAME))){if(null!=b&&b<this.components.length){var c=this.components.slice(0,b),d=this.components.slice(b,this.components.length);c.push(a);this.components=c.concat(d)}else this.components.push(a);a.parent=this;this.clearBounds();c=!0}return c},removeComponents:function(a){var b=!1;OpenLayers.Util.isArray(a)||(a=[a]);for(var c=a.length-1;0<=c;--c)b=
+this.removeComponent(a[c])||b;return b},removeComponent:function(a){OpenLayers.Util.removeItem(this.components,a);this.clearBounds();return!0},getLength:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getLength();return a},getArea:function(){for(var a=0,b=0,c=this.components.length;b<c;b++)a+=this.components[b].getArea();return a},getGeodesicArea:function(a){for(var b=0,c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicArea(a);return b},getCentroid:function(a){if(!a)return this.components.length&&
+this.components[0].getCentroid();a=this.components.length;if(!a)return!1;for(var b=[],c=[],d=0,e=Number.MAX_VALUE,f,g=0;g<a;++g){f=this.components[g];var h=f.getArea();f=f.getCentroid(!0);isNaN(h)||(isNaN(f.x)||isNaN(f.y))||(b.push(h),d+=h,e=h<e&&0<h?h:e,c.push(f))}a=b.length;if(0===d){for(g=0;g<a;++g)b[g]=1;d=b.length}else{for(g=0;g<a;++g)b[g]/=e;d/=e}for(var k=e=0,g=0;g<a;++g)f=c[g],h=b[g],e+=f.x*h,k+=f.y*h;return new OpenLayers.Geometry.Point(e/d,k/d)},getGeodesicLength:function(a){for(var b=0,
+c=0,d=this.components.length;c<d;c++)b+=this.components[c].getGeodesicLength(a);return b},move:function(a,b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0;d<this.components.length;++d)this.components[d].resize(a,b,c);return this},distanceTo:function(a,b){for(var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g=Number.POSITIVE_INFINITY,h=0,k=this.components.length;h<
+k&&!(d=this.components[h].distanceTo(a,b),f=c?d.distance:d,f<g&&(g=f,e=d,0==g));++h);return e},equals:function(a){var b=!0;if(a&&a.CLASS_NAME&&this.CLASS_NAME==a.CLASS_NAME)if(OpenLayers.Util.isArray(a.components)&&a.components.length==this.components.length)for(var c=0,d=this.components.length;c<d;++c){if(!this.components[c].equals(a.components[c])){b=!1;break}}else b=!1;else b=!1;return b},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d;c++)this.components[c].transform(a,
+b);this.bounds=null}return this},intersects:function(a){for(var b=!1,c=0,d=this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);return b},getVertices:function(a){for(var b=[],c=0,d=this.components.length;c<d;++c)Array.prototype.push.apply(b,this.components[c].getVertices(a));return b},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(a,b){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(a);this.y=parseFloat(b)},clone:function(a){null==a&&(a=new OpenLayers.Geometry.Point(this.x,this.y));OpenLayers.Util.applyDefaults(a,this);return a},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x,this.y)},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e,f,g,h;a instanceof
+OpenLayers.Geometry.Point?(e=this.x,f=this.y,g=a.x,h=a.y,d=Math.sqrt(Math.pow(e-g,2)+Math.pow(f-h,2)),d=c?{x0:e,y0:f,x1:g,y1:h,distance:d}:d):(d=a.distanceTo(this,b),c&&(d={x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0,distance:d.distance}));return d},equals:function(a){var b=!1;null!=a&&(b=this.x==a.x&&this.y==a.y||isNaN(this.x)&&isNaN(this.y)&&isNaN(a.x)&&isNaN(a.y));return b},toShortString:function(){return this.x+", "+this.y},move:function(a,b){this.x+=a;this.y+=b;this.clearBounds()},rotate:function(a,b){a*=
+Math.PI/180;var c=this.distanceTo(b),d=a+Math.atan2(this.y-b.y,this.x-b.x);this.x=b.x+c*Math.cos(d);this.y=b.y+c*Math.sin(d);this.clearBounds()},getCentroid:function(){return new OpenLayers.Geometry.Point(this.x,this.y)},resize:function(a,b,c){this.x=b.x+a*(void 0==c?1:c)*(this.x-b.x);this.y=b.y+a*(this.y-b.y);this.clearBounds();return this},intersects:function(a){var b=!1;return b="OpenLayers.Geometry.Point"==a.CLASS_NAME?this.equals(a):a.intersects(this)},transform:function(a,b){a&&b&&(OpenLayers.Projection.transform(this,
+a,b),this.bounds=null);return this},getVertices:function(a){return[this]},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],addPoint:function(a,b){this.addComponent(a,b)},removePoint:function(a){this.removeComponent(a)},CLASS_NAME:"OpenLayers.Geometry.MultiPoint"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],getLength:function(){var a=0;if(this.components&&1<this.components.length)for(var b=1,c=this.components.length;b<c;b++)a+=this.components[b-1].distanceTo(this.components[b]);return a},getGeodesicLength:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;if(b.components&&1<b.components.length)for(var d,e=1,f=b.components.length;e<
+f;e++)c=b.components[e-1],d=b.components[e],a+=OpenLayers.Util.distVincenty({lon:c.x,lat:c.y},{lon:d.x,lat:d.y});return 1E3*a},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{removeComponent:function(a){var b=this.components&&2<this.components.length;b&&OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);return b},intersects:function(a){var b=!1,c=a.CLASS_NAME;if("OpenLayers.Geometry.LineString"==c||"OpenLayers.Geometry.LinearRing"==c||"OpenLayers.Geometry.Point"==c){var d=this.getSortedSegments();a="OpenLayers.Geometry.Point"==c?[{x1:a.x,y1:a.y,x2:a.x,y2:a.y}]:a.getSortedSegments();
+var e,f,g,h,k,l,m,p=0,n=d.length;a:for(;p<n;++p){c=d[p];e=c.x1;f=c.x2;g=c.y1;h=c.y2;var q=0,r=a.length;for(;q<r;++q){k=a[q];if(k.x1>f)break;if(!(k.x2<e||(l=k.y1,m=k.y2,Math.min(l,m)>Math.max(g,h)||Math.max(l,m)<Math.min(g,h)||!OpenLayers.Geometry.segmentsIntersect(c,k)))){b=!0;break a}}}}else b=a.intersects(this);return b},getSortedSegments:function(){for(var a=this.components.length-1,b=Array(a),c,d,e=0;e<a;++e)c=this.components[e],d=this.components[e+1],b[e]=c.x<d.x?{x1:c.x,y1:c.y,x2:d.x,y2:d.y}:
+{x1:d.x,y1:d.y,x2:c.x,y2:c.y};return b.sort(function(a,b){return a.x1-b.x1})},splitWithSegment:function(a,b){for(var c=!(b&&!1===b.edge),d=b&&b.tolerance,e=[],f=this.getVertices(),g=[],h=[],k=!1,l,m,p,n={point:!0,tolerance:d},q=null,r=0,s=f.length-2;r<=s;++r)if(d=f[r],g.push(d.clone()),l=f[r+1],m={x1:d.x,y1:d.y,x2:l.x,y2:l.y},m=OpenLayers.Geometry.segmentsIntersect(a,m,n),m instanceof OpenLayers.Geometry.Point&&((p=m.x===a.x1&&m.y===a.y1||m.x===a.x2&&m.y===a.y2||m.equals(d)||m.equals(l)?!0:!1)||c))m.equals(h[h.length-
+1])||h.push(m.clone()),0===r&&m.equals(d)||m.equals(l)||(k=!0,m.equals(d)||g.push(m),e.push(new OpenLayers.Geometry.LineString(g)),g=[m.clone()]);k&&(g.push(l.clone()),e.push(new OpenLayers.Geometry.LineString(g)));if(0<h.length)var t=a.x1<a.x2?1:-1,u=a.y1<a.y2?1:-1,q={lines:e,points:h.sort(function(a,b){return t*a.x-t*b.x||u*a.y-u*b.y})};return q},split:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h;if(a instanceof OpenLayers.Geometry.LineString){var k=this.getVertices(),l,m,p,n,q,r=[];g=[];for(var s=
+0,t=k.length-2;s<=t;++s){l=k[s];m=k[s+1];p={x1:l.x,y1:l.y,x2:m.x,y2:m.y};h=h||[a];d&&r.push(l.clone());for(var u=0;u<h.length;++u)if(n=h[u].splitWithSegment(p,b))if(q=n.lines,0<q.length&&(q.unshift(u,1),Array.prototype.splice.apply(h,q),u+=q.length-2),d)for(var v=0,w=n.points.length;v<w;++v)q=n.points[v],q.equals(l)||(r.push(q),g.push(new OpenLayers.Geometry.LineString(r)),r=q.equals(m)?[]:[q.clone()])}d&&(0<g.length&&0<r.length)&&(r.push(m.clone()),g.push(new OpenLayers.Geometry.LineString(r)))}else c=
+a.splitWith(this,b);h&&1<h.length?f=!0:h=[];g&&1<g.length?e=!0:g=[];if(f||e)c=d?[g,h]:h;return c},splitWith:function(a,b){return a.split(this,b)},getVertices:function(a){return!0===a?[this.components[0],this.components[this.components.length-1]]:!1===a?this.components.slice(1,this.components.length-1):this.components.slice()},distanceTo:function(a,b){var c=!(b&&!1===b.edge)&&b&&b.details,d,e={},f=Number.POSITIVE_INFINITY;if(a instanceof OpenLayers.Geometry.Point){for(var g=this.getSortedSegments(),
+h=a.x,k=a.y,l,m=0,p=g.length;m<p;++m)if(l=g[m],d=OpenLayers.Geometry.distanceToSegment(a,l),d.distance<f){if(f=d.distance,e=d,0===f)break}else if(l.x2>h&&(k>l.y1&&k<l.y2||k<l.y1&&k>l.y2))break;e=c?{distance:e.distance,x0:e.x,y0:e.y,x1:h,y1:k}:e.distance}else if(a instanceof OpenLayers.Geometry.LineString){var g=this.getSortedSegments(),h=a.getSortedSegments(),n,q,r=h.length,s={point:!0},m=0,p=g.length;a:for(;m<p;++m){k=g[m];l=k.x1;q=k.y1;for(var t=0;t<r;++t)if(d=h[t],n=OpenLayers.Geometry.segmentsIntersect(k,
+d,s)){f=0;e={distance:0,x0:n.x,y0:n.y,x1:n.x,y1:n.y};break a}else d=OpenLayers.Geometry.distanceToSegment({x:l,y:q},d),d.distance<f&&(f=d.distance,e={distance:f,x0:l,y0:q,x1:d.x,y1:d.y})}c||(e=e.distance);0!==f&&k&&(d=a.distanceTo(new OpenLayers.Geometry.Point(k.x2,k.y2),b),m=c?d.distance:d,m<f&&(e=c?{distance:f,x0:d.x1,y0:d.y1,x1:d.x0,y1:d.y0}:m))}else e=a.distanceTo(this,b),c&&(e={distance:e.distance,x0:e.x1,y0:e.y1,x1:e.x0,y1:e.y0});return e},simplify:function(a){if(this&&null!==this){var b=this.getVertices();
+if(3>b.length)return this;var c=function(a,b,d,k){for(var l=0,m=0,p=b,n;p<d;p++){n=a[b];var q=a[d],r=a[p],r=Math.abs(0.5*(n.x*q.y+q.x*r.y+r.x*n.y-q.x*n.y-r.x*q.y-n.x*r.y));n=Math.sqrt(Math.pow(n.x-q.x,2)+Math.pow(n.y-q.y,2));n=2*(r/n);n>l&&(l=n,m=p)}l>k&&m!=b&&(e.push(m),c(a,b,m,k),c(a,m,d,k))},d=b.length-1,e=[];e.push(0);for(e.push(d);b[0].equals(b[d]);)d--,e.push(d);c(b,0,d,a);a=[];e.sort(function(a,b){return a-b});for(d=0;d<e.length;d++)a.push(b[e[d]]);return new OpenLayers.Geometry.LineString(a)}return this},
+CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Geometry.LinearRing=OpenLayers.Class(OpenLayers.Geometry.LineString,{componentTypes:["OpenLayers.Geometry.Point"],addComponent:function(a,b){var c=!1,d=this.components.pop();null==b&&a.equals(d)||(c=OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,arguments));OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]);return c},removeComponent:function(a){var b=this.components&&3<this.components.length;b&&(this.components.pop(),OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+arguments),OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[this.components[0]]));return b},move:function(a,b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].move(a,b)},rotate:function(a,b){for(var c=0,d=this.components.length;c<d-1;++c)this.components[c].rotate(a,b)},resize:function(a,b,c){for(var d=0,e=this.components.length;d<e-1;++d)this.components[d].resize(a,b,c);return this},transform:function(a,b){if(a&&b){for(var c=0,d=this.components.length;c<d-1;c++)this.components[c].transform(a,
+b);this.bounds=null}return this},getCentroid:function(){if(this.components){var a=this.components.length;if(0<a&&2>=a)return this.components[0].clone();if(2<a){var b=0,c=0,d=this.components[0].x,e=this.components[0].y,f=-1*this.getArea();if(0!=f){for(var g=0;g<a-1;g++)var h=this.components[g],k=this.components[g+1],b=b+(h.x+k.x-2*d)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e)),c=c+(h.y+k.y-2*e)*((h.x-d)*(k.y-e)-(k.x-d)*(h.y-e));b=d+b/(6*f);a=e+c/(6*f)}else{for(g=0;g<a-1;g++)b+=this.components[g].x,c+=this.components[g].y;
+b/=a-1;a=c/(a-1)}return new OpenLayers.Geometry.Point(b,a)}return null}},getArea:function(){var a=0;if(this.components&&2<this.components.length){for(var b=a=0,c=this.components.length;b<c-1;b++)var d=this.components[b],e=this.components[b+1],a=a+(d.x+e.x)*(e.y-d.y);a=-a/2}return a},getGeodesicArea:function(a){var b=this;if(a){var c=new OpenLayers.Projection("EPSG:4326");c.equals(a)||(b=this.clone().transform(a,c))}a=0;c=b.components&&b.components.length;if(2<c){for(var d,e,f=0;f<c-1;f++)d=b.components[f],
+e=b.components[f+1],a+=OpenLayers.Util.rad(e.x-d.x)*(2+Math.sin(OpenLayers.Util.rad(d.y))+Math.sin(OpenLayers.Util.rad(e.y)));a=40680631590769*a/2}return a},containsPoint:function(a){var b=OpenLayers.Number.limitSigDigs,c=b(a.x,14);a=b(a.y,14);for(var d=this.components.length-1,e,f,g,h,k,l=0,m=0;m<d;++m)if(e=this.components[m],g=b(e.x,14),e=b(e.y,14),f=this.components[m+1],h=b(f.x,14),f=b(f.y,14),e==f){if(a==e&&(g<=h&&c>=g&&c<=h||g>=h&&c<=g&&c>=h)){l=-1;break}}else{k=b((a-f)*((h-g)/(f-e))+h,14);if(k==
+c&&(e<f&&a>=e&&a<=f||e>f&&a<=e&&a>=f)){l=-1;break}k<=c||g!=h&&(k<Math.min(g,h)||k>Math.max(g,h))||(e<f&&a>=e&&a<f||e>f&&a<e&&a>=f)&&++l}return-1==l?1:!!(l&1)},intersects:function(a){var b=!1;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME)b=a.intersects(this);else if("OpenLayers.Geometry.LinearRing"==a.CLASS_NAME)b=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[a]);else for(var c=0,d=a.components.length;c<
+d&&!(b=a.components[c].intersects(this));++c);return b},getVertices:function(a){return!0===a?[]:this.components.slice(0,this.components.length-1)},CLASS_NAME:"OpenLayers.Geometry.LinearRing"});OpenLayers.Util=OpenLayers.Util||{};
+OpenLayers.Util.vendorPrefix=function(){function a(a){return a?a.replace(/([A-Z])/g,function(a){return"-"+a.toLowerCase()}).replace(/^ms-/,"-ms-"):null}function b(a,b){if(void 0===g[b]){var c,e=0,f=d.length,n="undefined"!==typeof a.cssText;for(g[b]=null;e<f;e++)if((c=d[e])?(n||(c=c.toLowerCase()),c=c+b.charAt(0).toUpperCase()+b.slice(1)):c=b,void 0!==a[c]){g[b]=c;break}}return g[b]}function c(a){return b(e,a)}var d=["","O","ms","Moz","Webkit"],e=document.createElement("div").style,f={},g={};return{css:function(b){if(void 0===
+f[b]){var d=b.replace(/(-[\s\S])/g,function(a){return a.charAt(1).toUpperCase()}),d=c(d);f[b]=a(d)}return f[b]},js:b,style:c,cssCache:f,jsCache:g}}();OpenLayers.Animation=function(a){var b=OpenLayers.Util.vendorPrefix.js(a,"requestAnimationFrame"),c=!!b,d=function(){var c=a[b]||function(b,c){a.setTimeout(b,16)};return function(b,d){c.apply(a,[b,d])}}(),e=0,f={};return{isNative:c,requestFrame:d,start:function(a,b,c){b=0<b?b:Number.POSITIVE_INFINITY;var l=++e,m=+new Date;f[l]=function(){f[l]&&+new Date-m<=b?(a(),f[l]&&d(f[l],c)):delete f[l]};d(f[l],c);return l},stop:function(a){delete f[a]}}}(window);OpenLayers.Tween=OpenLayers.Class({easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,minFrameRate:null,startTime:null,animationId:null,playing:!1,initialize:function(a){this.easing=a?a:OpenLayers.Easing.Expo.easeOut},start:function(a,b,c,d){this.playing=!0;this.begin=a;this.finish=b;this.duration=c;this.callbacks=d.callbacks;this.minFrameRate=d.minFrameRate||30;this.time=0;this.startTime=(new Date).getTime();OpenLayers.Animation.stop(this.animationId);this.animationId=null;
+this.callbacks&&this.callbacks.start&&this.callbacks.start.call(this,this.begin);this.animationId=OpenLayers.Animation.start(OpenLayers.Function.bind(this.play,this))},stop:function(){this.playing&&(this.callbacks&&this.callbacks.done&&this.callbacks.done.call(this,this.finish),OpenLayers.Animation.stop(this.animationId),this.animationId=null,this.playing=!1)},play:function(){var a={},b;for(b in this.begin){var c=this.begin[b],d=this.finish[b];if(null==c||null==d||isNaN(c)||isNaN(d))throw new TypeError("invalid value for Tween");
+a[b]=this.easing.apply(this,[this.time,c,d-c,this.duration])}this.time++;this.callbacks&&this.callbacks.eachStep&&((new Date).getTime()-this.startTime)/this.time<=1E3/this.minFrameRate&&this.callbacks.eachStep.call(this,a);this.time>this.duration&&this.stop()},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(a,b,c,d){return c*a/d+b},easeOut:function(a,b,c,d){return c*a/d+b},easeInOut:function(a,b,c,d){return c*a/d+b},CLASS_NAME:"OpenLayers.Easing.Linear"};
+OpenLayers.Easing.Expo={easeIn:function(a,b,c,d){return 0==a?b:c*Math.pow(2,10*(a/d-1))+b},easeOut:function(a,b,c,d){return a==d?b+c:c*(-Math.pow(2,-10*a/d)+1)+b},easeInOut:function(a,b,c,d){return 0==a?b:a==d?b+c:1>(a/=d/2)?c/2*Math.pow(2,10*(a-1))+b:c/2*(-Math.pow(2,-10*--a)+2)+b},CLASS_NAME:"OpenLayers.Easing.Expo"};
+OpenLayers.Easing.Quad={easeIn:function(a,b,c,d){return c*(a/=d)*a+b},easeOut:function(a,b,c,d){return-c*(a/=d)*(a-2)+b},easeInOut:function(a,b,c,d){return 1>(a/=d/2)?c/2*a*a+b:-c/2*(--a*(a-2)-1)+b},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,titleRegEx:/\+title=[^\+]*/,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.projCode=a;"object"==typeof Proj4js&&(this.proj=new Proj4js.Proj(a))},getCode:function(){return this.proj?this.proj.srsCode:this.projCode},getUnits:function(){return this.proj?this.proj.units:null},toString:function(){return this.getCode()},equals:function(a){var b=!1;a&&(a instanceof OpenLayers.Projection||(a=new OpenLayers.Projection(a)),"object"==
+typeof Proj4js&&this.proj.defData&&a.proj.defData?b=this.proj.defData.replace(this.titleRegEx,"")==a.proj.defData.replace(this.titleRegEx,""):a.getCode&&(b=this.getCode(),a=a.getCode(),b=b==a||!!OpenLayers.Projection.transforms[b]&&OpenLayers.Projection.transforms[b][a]===OpenLayers.Projection.nullTransform));return b},destroy:function(){delete this.proj;delete this.projCode},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};
+OpenLayers.Projection.defaults={"EPSG:4326":{units:"degrees",maxExtent:[-180,-90,180,90],yx:!0},"CRS:84":{units:"degrees",maxExtent:[-180,-90,180,90]},"EPSG:900913":{units:"m",maxExtent:[-2.003750834E7,-2.003750834E7,2.003750834E7,2.003750834E7]}};
+OpenLayers.Projection.addTransform=function(a,b,c){if(c===OpenLayers.Projection.nullTransform){var d=OpenLayers.Projection.defaults[a];d&&!OpenLayers.Projection.defaults[b]&&(OpenLayers.Projection.defaults[b]=d)}OpenLayers.Projection.transforms[a]||(OpenLayers.Projection.transforms[a]={});OpenLayers.Projection.transforms[a][b]=c};
+OpenLayers.Projection.transform=function(a,b,c){if(b&&c)if(b instanceof OpenLayers.Projection||(b=new OpenLayers.Projection(b)),c instanceof OpenLayers.Projection||(c=new OpenLayers.Projection(c)),b.proj&&c.proj)a=Proj4js.transform(b.proj,c.proj,a);else{b=b.getCode();c=c.getCode();var d=OpenLayers.Projection.transforms;if(d[b]&&d[b][c])d[b][c](a)}return a};OpenLayers.Projection.nullTransform=function(a){return a};
+(function(){function a(a){a.x=180*a.x/d;a.y=180/Math.PI*(2*Math.atan(Math.exp(a.y/d*Math.PI))-Math.PI/2);return a}function b(a){a.x=a.x*d/180;var b=Math.log(Math.tan((90+a.y)*Math.PI/360))/Math.PI*d;a.y=Math.max(-2.003750834E7,Math.min(b,2.003750834E7));return a}function c(c,d){var e=OpenLayers.Projection.addTransform,f=OpenLayers.Projection.nullTransform,g,n,q,r,s;g=0;for(n=d.length;g<n;++g)for(q=d[g],e(c,q,b),e(q,c,a),s=g+1;s<n;++s)r=d[s],e(q,r,f),e(r,q,f)}var d=2.003750834E7,e=["EPSG:900913","EPSG:3857",
+"EPSG:102113","EPSG:102100"],f=["CRS:84","urn:ogc:def:crs:EPSG:6.6:4326","EPSG:4326"],g;for(g=e.length-1;0<=g;--g)c(e[g],f);for(g=f.length-1;0<=g;--g)c(f[g],e)})();OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1E3},id:null,fractionalZoom:!1,events:null,allOverlays:!1,div:null,dragging:!1,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,panRatio:1.5,options:null,tileSize:null,projection:"EPSG:4326",units:null,resolutions:null,maxResolution:null,minResolution:null,maxScale:null,minScale:null,
+maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:!1,autoUpdateSize:!0,eventListeners:null,panTween:null,panMethod:OpenLayers.Easing.Expo.easeOut,panDuration:50,zoomTween:null,zoomMethod:OpenLayers.Easing.Quad.easeOut,zoomDuration:20,paddingForPopups:null,layerContainerOriginPx:null,minPx:null,maxPx:null,initialize:function(a,b){1===arguments.length&&"object"===typeof a&&(a=(b=a)&&b.div);this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+OpenLayers.Map.TILE_HEIGHT);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+"theme/default/style.css";this.options=OpenLayers.Util.extend({},b);OpenLayers.Util.extend(this,b);OpenLayers.Util.applyDefaults(this,OpenLayers.Projection.defaults[this.projection instanceof OpenLayers.Projection?this.projection.projCode:this.projection]);!this.maxExtent||this.maxExtent instanceof OpenLayers.Bounds||(this.maxExtent=new OpenLayers.Bounds(this.maxExtent));
+!this.minExtent||this.minExtent instanceof OpenLayers.Bounds||(this.minExtent=new OpenLayers.Bounds(this.minExtent));!this.restrictedExtent||this.restrictedExtent instanceof OpenLayers.Bounds||(this.restrictedExtent=new OpenLayers.Bounds(this.restrictedExtent));!this.center||this.center instanceof OpenLayers.LonLat||(this.center=new OpenLayers.LonLat(this.center));this.layers=[];this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(a);this.div||(this.div=document.createElement("div"),
+this.div.style.height="1px",this.div.style.width="1px");OpenLayers.Element.addClass(this.div,"olMap");var c=this.id+"_OpenLayers_ViewPort";this.viewPortDiv=OpenLayers.Util.createDiv(c,null,null,null,"relative",null,"hidden");this.viewPortDiv.style.width="100%";this.viewPortDiv.style.height="100%";this.viewPortDiv.className="olMapViewport";this.div.appendChild(this.viewPortDiv);this.events=new OpenLayers.Events(this,this.viewPortDiv,null,this.fallThrough,{includeXY:!0});OpenLayers.TileManager&&null!==
+this.tileManager&&(this.tileManager instanceof OpenLayers.TileManager||(this.tileManager=new OpenLayers.TileManager(this.tileManager)),this.tileManager.addMap(this));c=this.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(c);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE.Popup-1;this.layerContainerOriginPx={x:0,y:0};this.applyTransform();this.viewPortDiv.appendChild(this.layerContainerDiv);this.updateSize();if(this.eventListeners instanceof Object)this.events.on(this.eventListeners);
+!0===this.autoUpdateSize&&(this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this),OpenLayers.Event.observe(window,"resize",this.updateSizeDestroy));if(this.theme){for(var c=!0,d=document.getElementsByTagName("link"),e=0,f=d.length;e<f;++e)if(OpenLayers.Util.isEquivalentUrl(d.item(e).href,this.theme)){c=!1;break}c&&(c=document.createElement("link"),c.setAttribute("rel","stylesheet"),c.setAttribute("type","text/css"),c.setAttribute("href",this.theme),document.getElementsByTagName("head")[0].appendChild(c))}null==
+this.controls&&(this.controls=[],null!=OpenLayers.Control&&(OpenLayers.Control.Navigation?this.controls.push(new OpenLayers.Control.Navigation):OpenLayers.Control.TouchNavigation&&this.controls.push(new OpenLayers.Control.TouchNavigation),OpenLayers.Control.Zoom?this.controls.push(new OpenLayers.Control.Zoom):OpenLayers.Control.PanZoom&&this.controls.push(new OpenLayers.Control.PanZoom),OpenLayers.Control.ArgParser&&this.controls.push(new OpenLayers.Control.ArgParser),OpenLayers.Control.Attribution&&
+this.controls.push(new OpenLayers.Control.Attribution)));e=0;for(f=this.controls.length;e<f;e++)this.addControlToMap(this.controls[e]);this.popups=[];this.unloadDestroy=OpenLayers.Function.bind(this.destroy,this);OpenLayers.Event.observe(window,"unload",this.unloadDestroy);b&&b.layers&&(delete this.center,delete this.zoom,this.addLayers(b.layers),b.center&&!this.getCenter()&&this.setCenter(b.center,b.zoom));this.panMethod&&(this.panTween=new OpenLayers.Tween(this.panMethod));this.zoomMethod&&this.applyTransform.transform&&
+(this.zoomTween=new OpenLayers.Tween(this.zoomMethod))},getViewport:function(){return this.viewPortDiv},render:function(a){this.div=OpenLayers.Util.getElement(a);OpenLayers.Element.addClass(this.div,"olMap");this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);this.div.appendChild(this.viewPortDiv);this.updateSize()},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy)return!1;this.panTween&&(this.panTween.stop(),this.panTween=null);this.zoomTween&&(this.zoomTween.stop(),
+this.zoomTween=null);OpenLayers.Event.stopObserving(window,"unload",this.unloadDestroy);this.unloadDestroy=null;this.updateSizeDestroy&&OpenLayers.Event.stopObserving(window,"resize",this.updateSizeDestroy);this.paddingForPopups=null;if(null!=this.controls){for(var a=this.controls.length-1;0<=a;--a)this.controls[a].destroy();this.controls=null}if(null!=this.layers){for(a=this.layers.length-1;0<=a;--a)this.layers[a].destroy(!1);this.layers=null}this.viewPortDiv&&this.viewPortDiv.parentNode&&this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+this.viewPortDiv=null;this.tileManager&&(this.tileManager.removeMap(this),this.tileManager=null);this.eventListeners&&(this.events.un(this.eventListeners),this.eventListeners=null);this.events.destroy();this.options=this.events=null},setOptions:function(a){var b=this.minPx&&a.restrictedExtent!=this.restrictedExtent;OpenLayers.Util.extend(this,a);b&&this.moveTo(this.getCachedCenter(),this.zoom,{forceZoomChange:!0})},getTileSize:function(){return this.tileSize},getBy:function(a,b,c){var d="function"==
+typeof c.test;return OpenLayers.Array.filter(this[a],function(a){return a[b]==c||d&&c.test(a[b])})},getLayersBy:function(a,b){return this.getBy("layers",a,b)},getLayersByName:function(a){return this.getLayersBy("name",a)},getLayersByClass:function(a){return this.getLayersBy("CLASS_NAME",a)},getControlsBy:function(a,b){return this.getBy("controls",a,b)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},getLayer:function(a){for(var b=null,c=0,d=this.layers.length;c<d;c++){var e=
+this.layers[c];if(e.id==a){b=e;break}}return b},setLayerZIndex:function(a,b){a.setZIndex(this.Z_INDEX_BASE[a.isBaseLayer?"BaseLayer":"Overlay"]+5*b)},resetLayersZIndex:function(){for(var a=0,b=this.layers.length;a<b;a++)this.setLayerZIndex(this.layers[a],a)},addLayer:function(a){for(var b=0,c=this.layers.length;b<c;b++)if(this.layers[b]==a)return!1;if(!1===this.events.triggerEvent("preaddlayer",{layer:a}))return!1;this.allOverlays&&(a.isBaseLayer=!1);a.div.className="olLayerDiv";a.div.style.overflow=
+"";this.setLayerZIndex(a,this.layers.length);a.isFixed?this.viewPortDiv.appendChild(a.div):this.layerContainerDiv.appendChild(a.div);this.layers.push(a);a.setMap(this);a.isBaseLayer||this.allOverlays&&!this.baseLayer?null==this.baseLayer?this.setBaseLayer(a):a.setVisibility(!1):a.redraw();this.events.triggerEvent("addlayer",{layer:a});a.events.triggerEvent("added",{map:this,layer:a});a.afterAdd();return!0},addLayers:function(a){for(var b=0,c=a.length;b<c;b++)this.addLayer(a[b])},removeLayer:function(a,
+b){if(!1!==this.events.triggerEvent("preremovelayer",{layer:a})){null==b&&(b=!0);a.isFixed?this.viewPortDiv.removeChild(a.div):this.layerContainerDiv.removeChild(a.div);OpenLayers.Util.removeItem(this.layers,a);a.removeMap(this);a.map=null;if(this.baseLayer==a&&(this.baseLayer=null,b))for(var c=0,d=this.layers.length;c<d;c++){var e=this.layers[c];if(e.isBaseLayer||this.allOverlays){this.setBaseLayer(e);break}}this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:a});a.events.triggerEvent("removed",
+{map:this,layer:a})}},getNumLayers:function(){return this.layers.length},getLayerIndex:function(a){return OpenLayers.Util.indexOf(this.layers,a)},setLayerIndex:function(a,b){var c=this.getLayerIndex(a);0>b?b=0:b>this.layers.length&&(b=this.layers.length);if(c!=b){this.layers.splice(c,1);this.layers.splice(b,0,a);for(var c=0,d=this.layers.length;c<d;c++)this.setLayerZIndex(this.layers[c],c);this.events.triggerEvent("changelayer",{layer:a,property:"order"});this.allOverlays&&(0===b?this.setBaseLayer(a):
+this.baseLayer!==this.layers[0]&&this.setBaseLayer(this.layers[0]))}},raiseLayer:function(a,b){var c=this.getLayerIndex(a)+b;this.setLayerIndex(a,c)},setBaseLayer:function(a){if(a!=this.baseLayer&&-1!=OpenLayers.Util.indexOf(this.layers,a)){var b=this.getCachedCenter(),c=OpenLayers.Util.getResolutionFromScale(this.getScale(),a.units);null==this.baseLayer||this.allOverlays||this.baseLayer.setVisibility(!1);this.baseLayer=a;if(!this.allOverlays||this.baseLayer.visibility)this.baseLayer.setVisibility(!0),
+!1===this.baseLayer.inRange&&this.baseLayer.redraw();null!=b&&(a=this.getZoomForResolution(c||this.resolution,!0),this.setCenter(b,a,!1,!0));this.events.triggerEvent("changebaselayer",{layer:this.baseLayer})}},addControl:function(a,b){this.controls.push(a);this.addControlToMap(a,b)},addControls:function(a,b){for(var c=1===arguments.length?[]:b,d=0,e=a.length;d<e;d++)this.addControl(a[d],c[d]?c[d]:null)},addControlToMap:function(a,b){a.outsideViewport=null!=a.div;this.displayProjection&&!a.displayProjection&&
+(a.displayProjection=this.displayProjection);a.setMap(this);var c=a.draw(b);c&&!a.outsideViewport&&(c.style.zIndex=this.Z_INDEX_BASE.Control+this.controls.length,this.viewPortDiv.appendChild(c));a.autoActivate&&a.activate()},getControl:function(a){for(var b=null,c=0,d=this.controls.length;c<d;c++){var e=this.controls[c];if(e.id==a){b=e;break}}return b},removeControl:function(a){a&&a==this.getControl(a.id)&&(a.div&&a.div.parentNode==this.viewPortDiv&&this.viewPortDiv.removeChild(a.div),OpenLayers.Util.removeItem(this.controls,
+a))},addPopup:function(a,b){if(b)for(var c=this.popups.length-1;0<=c;--c)this.removePopup(this.popups[c]);a.map=this;this.popups.push(a);if(c=a.draw())c.style.zIndex=this.Z_INDEX_BASE.Popup+this.popups.length,this.layerContainerDiv.appendChild(c)},removePopup:function(a){OpenLayers.Util.removeItem(this.popups,a);if(a.div)try{this.layerContainerDiv.removeChild(a.div)}catch(b){}a.map=null},getSize:function(){var a=null;null!=this.size&&(a=this.size.clone());return a},updateSize:function(){var a=this.getCurrentSize();
+if(a&&!isNaN(a.h)&&!isNaN(a.w)){this.events.clearMouseCache();var b=this.getSize();null==b&&(this.size=b=a);if(!a.equals(b)){this.size=a;a=0;for(b=this.layers.length;a<b;a++)this.layers[a].onMapResize();a=this.getCachedCenter();null!=this.baseLayer&&null!=a&&(b=this.getZoom(),this.zoom=null,this.setCenter(a,b))}}this.events.triggerEvent("updatesize")},getCurrentSize:function(){var a=new OpenLayers.Size(this.div.clientWidth,this.div.clientHeight);if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=this.div.offsetWidth,
+a.h=this.div.offsetHeight;if(0==a.w&&0==a.h||isNaN(a.w)&&isNaN(a.h))a.w=parseInt(this.div.style.width),a.h=parseInt(this.div.style.height);return a},calculateBounds:function(a,b){var c=null;null==a&&(a=this.getCachedCenter());null==b&&(b=this.getResolution());if(null!=a&&null!=b)var c=this.size.w*b/2,d=this.size.h*b/2,c=new OpenLayers.Bounds(a.lon-c,a.lat-d,a.lon+c,a.lat+d);return c},getCenter:function(){var a=null,b=this.getCachedCenter();b&&(a=b.clone());return a},getCachedCenter:function(){!this.center&&
+this.size&&(this.center=this.getLonLatFromViewPortPx({x:this.size.w/2,y:this.size.h/2}));return this.center},getZoom:function(){return this.zoom},pan:function(a,b,c){c=OpenLayers.Util.applyDefaults(c,{animate:!0,dragging:!1});if(c.dragging)0==a&&0==b||this.moveByPx(a,b);else{var d=this.getViewPortPxFromLonLat(this.getCachedCenter());a=d.add(a,b);if(this.dragging||!a.equals(d))d=this.getLonLatFromViewPortPx(a),c.animate?this.panTo(d):(this.moveTo(d),this.dragging&&(this.dragging=!1,this.events.triggerEvent("moveend")))}},
+panTo:function(a){if(this.panTween&&this.getExtent().scale(this.panRatio).containsLonLat(a)){var b=this.getCachedCenter();if(!a.equals(b)){var b=this.getPixelFromLonLat(b),c=this.getPixelFromLonLat(a),d=0,e=0;this.panTween.start({x:0,y:0},{x:c.x-b.x,y:c.y-b.y},this.panDuration,{callbacks:{eachStep:OpenLayers.Function.bind(function(a){this.moveByPx(a.x-d,a.y-e);d=Math.round(a.x);e=Math.round(a.y)},this),done:OpenLayers.Function.bind(function(b){this.moveTo(a);this.dragging=!1;this.events.triggerEvent("moveend")},
+this)}})}}else this.setCenter(a)},setCenter:function(a,b,c,d){this.panTween&&this.panTween.stop();this.zoomTween&&this.zoomTween.stop();this.moveTo(a,b,{dragging:c,forceZoomChange:d})},moveByPx:function(a,b){var c=this.size.w/2,d=this.size.h/2,e=c+a,f=d+b,g=this.baseLayer.wrapDateLine,h=0,k=0;this.restrictedExtent&&(h=c,k=d,g=!1);a=g||e<=this.maxPx.x-h&&e>=this.minPx.x+h?Math.round(a):0;b=f<=this.maxPx.y-k&&f>=this.minPx.y+k?Math.round(b):0;if(a||b){this.dragging||(this.dragging=!0,this.events.triggerEvent("movestart"));
+this.center=null;a&&(this.layerContainerOriginPx.x-=a,this.minPx.x-=a,this.maxPx.x-=a);b&&(this.layerContainerOriginPx.y-=b,this.minPx.y-=b,this.maxPx.y-=b);this.applyTransform();d=0;for(e=this.layers.length;d<e;++d)c=this.layers[d],c.visibility&&(c===this.baseLayer||c.inRange)&&(c.moveByPx(a,b),c.events.triggerEvent("move"));this.events.triggerEvent("move")}},adjustZoom:function(a){if(this.baseLayer&&this.baseLayer.wrapDateLine){var b=this.baseLayer.resolutions,c=this.getMaxExtent().getWidth()/this.size.w;
+if(this.getResolutionForZoom(a)>c)if(this.fractionalZoom)a=this.getZoomForResolution(c);else for(var d=a|0,e=b.length;d<e;++d)if(b[d]<=c){a=d;break}}return a},getMinZoom:function(){return this.adjustZoom(0)},moveTo:function(a,b,c){null==a||a instanceof OpenLayers.LonLat||(a=new OpenLayers.LonLat(a));c||(c={});null!=b&&(b=parseFloat(b),this.fractionalZoom||(b=Math.round(b)));var d=b;b=this.adjustZoom(b);b!==d&&(a=this.getCenter());var d=c.dragging||this.dragging,e=c.forceZoomChange;this.getCachedCenter()||
+this.isValidLonLat(a)||(a=this.maxExtent.getCenterLonLat(),this.center=a.clone());if(null!=this.restrictedExtent){null==a&&(a=this.center);null==b&&(b=this.getZoom());var f=this.getResolutionForZoom(b),f=this.calculateBounds(a,f);if(!this.restrictedExtent.containsBounds(f)){var g=this.restrictedExtent.getCenterLonLat();f.getWidth()>this.restrictedExtent.getWidth()?a=new OpenLayers.LonLat(g.lon,a.lat):f.left<this.restrictedExtent.left?a=a.add(this.restrictedExtent.left-f.left,0):f.right>this.restrictedExtent.right&&
+(a=a.add(this.restrictedExtent.right-f.right,0));f.getHeight()>this.restrictedExtent.getHeight()?a=new OpenLayers.LonLat(a.lon,g.lat):f.bottom<this.restrictedExtent.bottom?a=a.add(0,this.restrictedExtent.bottom-f.bottom):f.top>this.restrictedExtent.top&&(a=a.add(0,this.restrictedExtent.top-f.top))}}e=e||this.isValidZoomLevel(b)&&b!=this.getZoom();f=this.isValidLonLat(a)&&!a.equals(this.center);if(e||f||d){d||this.events.triggerEvent("movestart",{zoomChanged:e});f&&(!e&&this.center&&this.centerLayerContainer(a),
+this.center=a.clone());a=e?this.getResolutionForZoom(b):this.getResolution();if(e||null==this.layerContainerOrigin){this.layerContainerOrigin=this.getCachedCenter();this.layerContainerOriginPx.x=0;this.layerContainerOriginPx.y=0;this.applyTransform();var f=this.getMaxExtent({restricted:!0}),h=f.getCenterLonLat(),g=this.center.lon-h.lon,h=h.lat-this.center.lat,k=Math.round(f.getWidth()/a),l=Math.round(f.getHeight()/a);this.minPx={x:(this.size.w-k)/2-g/a,y:(this.size.h-l)/2-h/a};this.maxPx={x:this.minPx.x+
+Math.round(f.getWidth()/a),y:this.minPx.y+Math.round(f.getHeight()/a)}}e&&(this.zoom=b,this.resolution=a);a=this.getExtent();this.baseLayer.visibility&&(this.baseLayer.moveTo(a,e,c.dragging),c.dragging||this.baseLayer.events.triggerEvent("moveend",{zoomChanged:e}));a=this.baseLayer.getExtent();for(b=this.layers.length-1;0<=b;--b)f=this.layers[b],f===this.baseLayer||f.isBaseLayer||(g=f.calculateInRange(),f.inRange!=g&&((f.inRange=g)||f.display(!1),this.events.triggerEvent("changelayer",{layer:f,property:"visibility"})),
+g&&f.visibility&&(f.moveTo(a,e,c.dragging),c.dragging||f.events.triggerEvent("moveend",{zoomChanged:e})));this.events.triggerEvent("move");d||this.events.triggerEvent("moveend");if(e){b=0;for(c=this.popups.length;b<c;b++)this.popups[b].updatePosition();this.events.triggerEvent("zoomend")}}},centerLayerContainer:function(a){var b=this.getViewPortPxFromLonLat(this.layerContainerOrigin),c=this.getViewPortPxFromLonLat(a);if(null!=b&&null!=c){var d=this.layerContainerOriginPx.x;a=this.layerContainerOriginPx.y;
+var e=Math.round(b.x-c.x),b=Math.round(b.y-c.y);this.applyTransform(this.layerContainerOriginPx.x=e,this.layerContainerOriginPx.y=b);d-=e;a-=b;this.minPx.x-=d;this.maxPx.x-=d;this.minPx.y-=a;this.maxPx.y-=a}},isValidZoomLevel:function(a){return null!=a&&0<=a&&a<this.getNumZoomLevels()},isValidLonLat:function(a){var b=!1;null!=a&&(b=this.getMaxExtent(),b=b.containsLonLat(a,{worldBounds:this.baseLayer.wrapDateLine&&b}));return b},getProjection:function(){var a=this.getProjectionObject();return a?a.getCode():
+null},getProjectionObject:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.projection);return a},getMaxResolution:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.maxResolution);return a},getMaxExtent:function(a){var b=null;a&&a.restricted&&this.restrictedExtent?b=this.restrictedExtent:null!=this.baseLayer&&(b=this.baseLayer.maxExtent);return b},getNumZoomLevels:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.numZoomLevels);return a},getExtent:function(){var a=
+null;null!=this.baseLayer&&(a=this.baseLayer.getExtent());return a},getResolution:function(){var a=null;null!=this.baseLayer?a=this.baseLayer.getResolution():!0===this.allOverlays&&0<this.layers.length&&(a=this.layers[0].getResolution());return a},getUnits:function(){var a=null;null!=this.baseLayer&&(a=this.baseLayer.units);return a},getScale:function(){var a=null;null!=this.baseLayer&&(a=this.getResolution(),a=OpenLayers.Util.getScaleFromResolution(a,this.baseLayer.units));return a},getZoomForExtent:function(a,
+b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForExtent(a,b));return c},getResolutionForZoom:function(a){var b=null;this.baseLayer&&(b=this.baseLayer.getResolutionForZoom(a));return b},getZoomForResolution:function(a,b){var c=null;null!=this.baseLayer&&(c=this.baseLayer.getZoomForResolution(a,b));return c},zoomTo:function(a,b){var c=this;if(c.isValidZoomLevel(a))if(c.baseLayer.wrapDateLine&&(a=c.adjustZoom(a)),c.zoomTween){var d=c.getResolution(),e=c.getResolutionForZoom(a),f={scale:1},
+d={scale:d/e};c.zoomTween.playing&&c.zoomTween.duration<3*c.zoomDuration?c.zoomTween.finish={scale:c.zoomTween.finish.scale*d.scale}:(b||(e=c.getSize(),b={x:e.w/2,y:e.h/2}),c.zoomTween.start(f,d,c.zoomDuration,{minFrameRate:50,callbacks:{eachStep:function(a){var d=c.layerContainerOriginPx;a=a.scale;c.applyTransform(d.x+((a-1)*(d.x-b.x)|0),d.y+((a-1)*(d.y-b.y)|0),a)},done:function(a){c.applyTransform();a=c.getResolution()/a.scale;var d=c.getZoomForResolution(a,!0);c.moveTo(c.getZoomTargetCenter(b,
+a),d,!0)}}}))}else f=b?c.getZoomTargetCenter(b,c.getResolutionForZoom(a)):null,c.setCenter(f,a)},zoomIn:function(){this.zoomTo(this.getZoom()+1)},zoomOut:function(){this.zoomTo(this.getZoom()-1)},zoomToExtent:function(a,b){a instanceof OpenLayers.Bounds||(a=new OpenLayers.Bounds(a));var c=a.getCenterLonLat();if(this.baseLayer.wrapDateLine){c=this.getMaxExtent();for(a=a.clone();a.right<a.left;)a.right+=c.getWidth();c=a.getCenterLonLat().wrapDateLine(c)}this.setCenter(c,this.getZoomForExtent(a,b))},
+zoomToMaxExtent:function(a){a=this.getMaxExtent({restricted:a?a.restricted:!0});this.zoomToExtent(a)},zoomToScale:function(a,b){var c=OpenLayers.Util.getResolutionFromScale(a,this.baseLayer.units),d=this.size.w*c/2,c=this.size.h*c/2,e=this.getCachedCenter(),d=new OpenLayers.Bounds(e.lon-d,e.lat-c,e.lon+d,e.lat+c);this.zoomToExtent(d,b)},getLonLatFromViewPortPx:function(a){var b=null;null!=this.baseLayer&&(b=this.baseLayer.getLonLatFromViewPortPx(a));return b},getViewPortPxFromLonLat:function(a){var b=
+null;null!=this.baseLayer&&(b=this.baseLayer.getViewPortPxFromLonLat(a));return b},getZoomTargetCenter:function(a,b){var c=null,d=this.getSize(),e=d.w/2-a.x,d=a.y-d.h/2,f=this.getLonLatFromPixel(a);f&&(c=new OpenLayers.LonLat(f.lon+e*b,f.lat+d*b));return c},getLonLatFromPixel:function(a){return this.getLonLatFromViewPortPx(a)},getPixelFromLonLat:function(a){a=this.getViewPortPxFromLonLat(a);a.x=Math.round(a.x);a.y=Math.round(a.y);return a},getGeodesicPixelSize:function(a){var b=a?this.getLonLatFromPixel(a):
+this.getCachedCenter()||new OpenLayers.LonLat(0,0),c=this.getResolution();a=b.add(-c/2,0);var d=b.add(c/2,0),e=b.add(0,-c/2),b=b.add(0,c/2),c=new OpenLayers.Projection("EPSG:4326"),f=this.getProjectionObject()||c;f.equals(c)||(a.transform(f,c),d.transform(f,c),e.transform(f,c),b.transform(f,c));return new OpenLayers.Size(OpenLayers.Util.distVincenty(a,d),OpenLayers.Util.distVincenty(e,b))},getViewPortPxFromLayerPx:function(a){var b=null;null!=a&&(b=a.add(this.layerContainerOriginPx.x,this.layerContainerOriginPx.y));
+return b},getLayerPxFromViewPortPx:function(a){var b=null;null!=a&&(b=a.add(-this.layerContainerOriginPx.x,-this.layerContainerOriginPx.y),isNaN(b.x)||isNaN(b.y))&&(b=null);return b},getLonLatFromLayerPx:function(a){a=this.getViewPortPxFromLayerPx(a);return this.getLonLatFromViewPortPx(a)},getLayerPxFromLonLat:function(a){a=this.getPixelFromLonLat(a);return this.getLayerPxFromViewPortPx(a)},applyTransform:function(a,b,c){c=c||1;var d=this.layerContainerOriginPx,e=1!==c;a=a||d.x;b=b||d.y;var f=this.layerContainerDiv.style,
+g=this.applyTransform.transform,h=this.applyTransform.template;if(void 0===g&&(g=OpenLayers.Util.vendorPrefix.style("transform"),this.applyTransform.transform=g)){var k=OpenLayers.Element.getStyle(this.viewPortDiv,OpenLayers.Util.vendorPrefix.css("transform"));k&&"none"===k||(h=["translate3d(",",0) ","scale3d(",",1)"],f[g]=[h[0],"0,0",h[1]].join(""));h&&~f[g].indexOf(h[0])||(h=["translate(",") ","scale(",")"]);this.applyTransform.template=h}null===g||"translate3d("!==h[0]&&!0!==e?(f.left=a+"px",f.top=
+b+"px",null!==g&&(f[g]="")):(!0===e&&"translate("===h[0]&&(a-=d.x,b-=d.y,f.left=d.x+"px",f.top=d.y+"px"),f[g]=[h[0],a,"px,",b,"px",h[1],h[2],c,",",c,h[3]].join(""))},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:1,alwaysInRange:null,RESOLUTION_PROPERTIES:"scales resolutions maxScale minScale maxResolution minResolution numZoomLevels maxZoomLevel".split(" "),events:null,map:null,isBaseLayer:!1,alpha:!1,displayInLayerSwitcher:!0,visibility:!0,attribution:null,inRange:!1,imageSize:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,
+numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:!1,wrapDateLine:!1,metadata:null,initialize:function(a,b){this.metadata={};b=OpenLayers.Util.extend({},b);null!=this.alwaysInRange&&(b.alwaysInRange=this.alwaysInRange);this.addOptions(b);this.name=a;if(null==this.id&&(this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_"),this.div=OpenLayers.Util.createDiv(this.id),this.div.style.width="100%",this.div.style.height="100%",this.div.dir="ltr",this.events=new OpenLayers.Events(this,
+this.div),this.eventListeners instanceof Object))this.events.on(this.eventListeners)},destroy:function(a){null==a&&(a=!0);null!=this.map&&this.map.removeLayer(this,a);this.options=this.div=this.name=this.map=this.projection=null;this.events&&(this.eventListeners&&this.events.un(this.eventListeners),this.events.destroy());this.events=this.eventListeners=null},clone:function(a){null==a&&(a=new OpenLayers.Layer(this.name,this.getOptions()));OpenLayers.Util.applyDefaults(a,this);a.map=null;return a},
+getOptions:function(){var a={},b;for(b in this.options)a[b]=this[b];return a},setName:function(a){a!=this.name&&(this.name=a,null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"name"}))},addOptions:function(a,b){null==this.options&&(this.options={});a&&("string"==typeof a.projection&&(a.projection=new OpenLayers.Projection(a.projection)),a.projection&&OpenLayers.Util.applyDefaults(a,OpenLayers.Projection.defaults[a.projection.getCode()]),!a.maxExtent||a.maxExtent instanceof
+OpenLayers.Bounds||(a.maxExtent=new OpenLayers.Bounds(a.maxExtent)),!a.minExtent||a.minExtent instanceof OpenLayers.Bounds||(a.minExtent=new OpenLayers.Bounds(a.minExtent)));OpenLayers.Util.extend(this.options,a);OpenLayers.Util.extend(this,a);this.projection&&this.projection.getUnits()&&(this.units=this.projection.getUnits());if(this.map){var c=this.map.getResolution(),d=this.RESOLUTION_PROPERTIES.concat(["projection","units","minExtent","maxExtent"]),e;for(e in a)if(a.hasOwnProperty(e)&&0<=OpenLayers.Util.indexOf(d,
+e)){this.initResolutions();b&&this.map.baseLayer===this&&(this.map.setCenter(this.map.getCenter(),this.map.getZoomForResolution(c),!1,!0),this.map.events.triggerEvent("changebaselayer",{layer:this}));break}}},onMapResize:function(){},redraw:function(){var a=!1;if(this.map){this.inRange=this.calculateInRange();var b=this.getExtent();b&&(this.inRange&&this.visibility)&&(this.moveTo(b,!0,!1),this.events.triggerEvent("moveend",{zoomChanged:!0}),a=!0)}return a},moveTo:function(a,b,c){a=this.visibility;
+this.isBaseLayer||(a=a&&this.inRange);this.display(a)},moveByPx:function(a,b){},setMap:function(a){null==this.map&&(this.map=a,this.maxExtent=this.maxExtent||this.map.maxExtent,this.minExtent=this.minExtent||this.map.minExtent,this.projection=this.projection||this.map.projection,"string"==typeof this.projection&&(this.projection=new OpenLayers.Projection(this.projection)),this.units=this.projection.getUnits()||this.units||this.map.units,this.initResolutions(),this.isBaseLayer||(this.inRange=this.calculateInRange(),
+this.div.style.display=this.visibility&&this.inRange?"":"none"),this.setTileSize())},afterAdd:function(){},removeMap:function(a){},getImageSize:function(a){return this.imageSize||this.tileSize},setTileSize:function(a){this.tileSize=a=a?a:this.tileSize?this.tileSize:this.map.getTileSize();this.gutter&&(this.imageSize=new OpenLayers.Size(a.w+2*this.gutter,a.h+2*this.gutter))},getVisibility:function(){return this.visibility},setVisibility:function(a){a!=this.visibility&&(this.visibility=a,this.display(a),
+this.redraw(),null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"}),this.events.triggerEvent("visibilitychanged"))},display:function(a){a!=("none"!=this.div.style.display)&&(this.div.style.display=a&&this.calculateInRange()?"block":"none")},calculateInRange:function(){var a=!1;this.alwaysInRange?a=!0:this.map&&(a=this.map.getResolution(),a=a>=this.minResolution&&a<=this.maxResolution);return a},setIsBaseLayer:function(a){a!=this.isBaseLayer&&(this.isBaseLayer=
+a,null!=this.map&&this.map.events.triggerEvent("changebaselayer",{layer:this}))},initResolutions:function(){var a,b,c,d={},e=!0;a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=this.options[c],e&&this.options[c]&&(e=!1);null==this.options.alwaysInRange&&(this.alwaysInRange=e);null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d));if(null==d.resolutions){a=0;for(b=this.RESOLUTION_PROPERTIES.length;a<
+b;a++)c=this.RESOLUTION_PROPERTIES[a],d[c]=null!=this.options[c]?this.options[c]:this.map[c];null==d.resolutions&&(d.resolutions=this.resolutionsFromScales(d.scales));null==d.resolutions&&(d.resolutions=this.calculateResolutions(d))}var f;this.options.maxResolution&&"auto"!==this.options.maxResolution&&(f=this.options.maxResolution);this.options.minScale&&(f=OpenLayers.Util.getResolutionFromScale(this.options.minScale,this.units));var g;this.options.minResolution&&"auto"!==this.options.minResolution&&
+(g=this.options.minResolution);this.options.maxScale&&(g=OpenLayers.Util.getResolutionFromScale(this.options.maxScale,this.units));d.resolutions&&(d.resolutions.sort(function(a,b){return b-a}),f||(f=d.resolutions[0]),g||(g=d.resolutions[d.resolutions.length-1]));if(this.resolutions=d.resolutions){b=this.resolutions.length;this.scales=Array(b);for(a=0;a<b;a++)this.scales[a]=OpenLayers.Util.getScaleFromResolution(this.resolutions[a],this.units);this.numZoomLevels=b}if(this.minResolution=g)this.maxScale=
+OpenLayers.Util.getScaleFromResolution(g,this.units);if(this.maxResolution=f)this.minScale=OpenLayers.Util.getScaleFromResolution(f,this.units)},resolutionsFromScales:function(a){if(null!=a){var b,c,d;d=a.length;b=Array(d);for(c=0;c<d;c++)b[c]=OpenLayers.Util.getResolutionFromScale(a[c],this.units);return b}},calculateResolutions:function(a){var b,c,d=a.maxResolution;null!=a.minScale?d=OpenLayers.Util.getResolutionFromScale(a.minScale,this.units):"auto"==d&&null!=this.maxExtent&&(b=this.map.getSize(),
+c=this.maxExtent.getWidth()/b.w,b=this.maxExtent.getHeight()/b.h,d=Math.max(c,b));c=a.minResolution;null!=a.maxScale?c=OpenLayers.Util.getResolutionFromScale(a.maxScale,this.units):"auto"==a.minResolution&&null!=this.minExtent&&(b=this.map.getSize(),c=this.minExtent.getWidth()/b.w,b=this.minExtent.getHeight()/b.h,c=Math.max(c,b));"number"!==typeof d&&("number"!==typeof c&&null!=this.maxExtent)&&(d=this.map.getTileSize(),d=Math.max(this.maxExtent.getWidth()/d.w,this.maxExtent.getHeight()/d.h));b=a.maxZoomLevel;
+a=a.numZoomLevels;"number"===typeof c&&"number"===typeof d&&void 0===a?a=Math.floor(Math.log(d/c)/Math.log(2))+1:void 0===a&&null!=b&&(a=b+1);if(!("number"!==typeof a||0>=a||"number"!==typeof d&&"number"!==typeof c)){b=Array(a);var e=2;"number"==typeof c&&"number"==typeof d&&(e=Math.pow(d/c,1/(a-1)));var f;if("number"===typeof d)for(f=0;f<a;f++)b[f]=d/Math.pow(e,f);else for(f=0;f<a;f++)b[a-1-f]=c*Math.pow(e,f);return b}},getResolution:function(){var a=this.map.getZoom();return this.getResolutionForZoom(a)},
+getExtent:function(){return this.map.calculateBounds()},getZoomForExtent:function(a,b){var c=this.map.getSize(),c=Math.max(a.getWidth()/c.w,a.getHeight()/c.h);return this.getZoomForResolution(c,b)},getDataExtent:function(){},getResolutionForZoom:function(a){a=Math.max(0,Math.min(a,this.resolutions.length-1));if(this.map.fractionalZoom){var b=Math.floor(a),c=Math.ceil(a);a=this.resolutions[b]-(a-b)*(this.resolutions[b]-this.resolutions[c])}else a=this.resolutions[Math.round(a)];return a},getZoomForResolution:function(a,
+b){var c,d;if(this.map.fractionalZoom){var e=0,f=this.resolutions[e],g=this.resolutions[this.resolutions.length-1],h;c=0;for(d=this.resolutions.length;c<d;++c)if(h=this.resolutions[c],h>=a&&(f=h,e=c),h<=a){g=h;break}c=f-g;c=0<c?e+(f-a)/c:e}else{f=Number.POSITIVE_INFINITY;c=0;for(d=this.resolutions.length;c<d;c++)if(b){e=Math.abs(this.resolutions[c]-a);if(e>f)break;f=e}else if(this.resolutions[c]<a)break;c=Math.max(0,c-1)}return c},getLonLatFromViewPortPx:function(a){var b=null,c=this.map;if(null!=
+a&&c.minPx){var b=c.getResolution(),d=c.getMaxExtent({restricted:!0}),b=new OpenLayers.LonLat((a.x-c.minPx.x)*b+d.left,(c.minPx.y-a.y)*b+d.top);this.wrapDateLine&&(b=b.wrapDateLine(this.maxExtent))}return b},getViewPortPxFromLonLat:function(a,b){var c=null;null!=a&&(b=b||this.map.getResolution(),c=this.map.calculateBounds(null,b),c=new OpenLayers.Pixel(1/b*(a.lon-c.left),1/b*(c.top-a.lat)));return c},setOpacity:function(a){if(a!=this.opacity){this.opacity=a;for(var b=this.div.childNodes,c=0,d=b.length;c<
+d;++c){var e=b[c].firstChild||b[c],f=b[c].lastChild;f&&"iframe"===f.nodeName.toLowerCase()&&(e=f.parentNode);OpenLayers.Util.modifyDOMElement(e,null,null,null,null,null,null,a)}null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"opacity"})}},getZIndex:function(){return this.div.style.zIndex},setZIndex:function(a){this.div.style.zIndex=a},adjustBounds:function(a){if(this.gutter){var b=this.gutter*this.map.getResolution();a=new OpenLayers.Bounds(a.left-b,a.bottom-b,a.right+
+b,a.top+b)}this.wrapDateLine&&(b={rightTolerance:this.getResolution(),leftTolerance:this.getResolution()},a=a.wrapDateLine(this.maxExtent,b));return a},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:!1,initialize:function(a,b,c,d){OpenLayers.Layer.prototype.initialize.apply(this,[a,d]);this.url=b;this.params||(this.params=OpenLayers.Util.extend({},c))},destroy:function(){this.params=this.url=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.getOptions()));
+return a=OpenLayers.Layer.prototype.clone.apply(this,[a])},setUrl:function(a){this.url=a},mergeNewParams:function(a){this.params=OpenLayers.Util.extend(this.params,a);a=this.redraw();null!=this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"params"});return a},redraw:function(a){return a?this.mergeNewParams({_olSalt:Math.random()}):OpenLayers.Layer.prototype.redraw.apply(this,[])},selectUrl:function(a,b){for(var c=1,d=0,e=a.length;d<e;d++)c*=a.charCodeAt(d)*this.URL_HASH_FACTOR,
+c-=Math.floor(c);return b[Math.floor(c*b.length)]},getFullRequestString:function(a,b){var c=b||this.url,d=OpenLayers.Util.extend({},this.params),d=OpenLayers.Util.extend(d,a),e=OpenLayers.Util.getParameterString(d);OpenLayers.Util.isArray(c)&&(c=this.selectUrl(e,c));var e=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(c)),f;for(f in d)f.toUpperCase()in e&&delete d[f];e=OpenLayers.Util.getParameterString(d);return OpenLayers.Util.urlAppend(c,e)},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Tile=OpenLayers.Class({events:null,eventListeners:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:!1,initialize:function(a,b,c,d,e,f){this.layer=a;this.position=b.clone();this.setBounds(c);this.url=d;e&&(this.size=e.clone());this.id=OpenLayers.Util.createUniqueID("Tile_");OpenLayers.Util.extend(this,f);this.events=new OpenLayers.Events(this);if(this.eventListeners instanceof Object)this.events.on(this.eventListeners)},unload:function(){this.isLoading&&(this.isLoading=
+!1,this.events.triggerEvent("unload"))},destroy:function(){this.position=this.size=this.bounds=this.layer=null;this.eventListeners&&this.events.un(this.eventListeners);this.events.destroy();this.events=this.eventListeners=null},draw:function(a){a||this.clear();var b=this.shouldDraw();b&&(!a&&!1===this.events.triggerEvent("beforedraw"))&&(b=null);return b},shouldDraw:function(){var a=!1,b=this.layer.maxExtent;if(b){var c=this.layer.map,c=c.baseLayer.wrapDateLine&&c.getMaxExtent();this.bounds.intersectsBounds(b,
+{inclusive:!1,worldBounds:c})&&(a=!0)}return a||this.layer.displayOutsideMaxExtent},setBounds:function(a){a=a.clone();if(this.layer.map.baseLayer.wrapDateLine){var b=this.layer.map.getMaxExtent(),c=this.layer.map.getResolution();a=a.wrapDateLine(b,{leftTolerance:c,rightTolerance:c})}this.bounds=a},moveTo:function(a,b,c){null==c&&(c=!0);this.setBounds(a);this.position=b.clone();c&&this.draw()},clear:function(a){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,imageReloadAttempts:null,layerAlphaHack:null,asyncRequestId:null,maxGetUrlLength:null,canvasContext:null,crossOriginKeyword:null,initialize:function(a,b,c,d,e,f){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=d;this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();if(null!=this.maxGetUrlLength||this.layer.gutter||this.layerAlphaHack)this.frame=document.createElement("div"),this.frame.style.position=
+"absolute",this.frame.style.overflow="hidden";null!=this.maxGetUrlLength&&OpenLayers.Util.extend(this,OpenLayers.Tile.Image.IFrame)},destroy:function(){this.imgDiv&&(this.clear(),this.frame=this.imgDiv=null);this.asyncRequestId=null;OpenLayers.Tile.prototype.destroy.apply(this,arguments)},draw:function(){var a=OpenLayers.Tile.prototype.draw.apply(this,arguments);a?(this.layer!=this.layer.map.baseLayer&&this.layer.reproject&&(this.bounds=this.getBoundsFromBaseLayer(this.position)),this.isLoading?this._loadEvent=
+"reload":(this.isLoading=!0,this._loadEvent="loadstart"),this.renderTile(),this.positionTile()):!1===a&&this.unload();return a},renderTile:function(){if(this.layer.async){var a=this.asyncRequestId=(this.asyncRequestId||0)+1;this.layer.getURLasync(this.bounds,function(b){a==this.asyncRequestId&&(this.url=b,this.initImage())},this)}else this.url=this.layer.getURL(this.bounds),this.initImage()},positionTile:function(){var a=this.getTile().style,b=this.frame?this.size:this.layer.getImageSize(this.bounds),
+c=1;this.layer instanceof OpenLayers.Layer.Grid&&(c=this.layer.getServerResolution()/this.layer.map.getResolution());a.left=this.position.x+"px";a.top=this.position.y+"px";a.width=Math.round(c*b.w)+"px";a.height=Math.round(c*b.h)+"px"},clear:function(){OpenLayers.Tile.prototype.clear.apply(this,arguments);var a=this.imgDiv;if(a){var b=this.getTile();b.parentNode===this.layer.div&&this.layer.div.removeChild(b);this.setImgSrc();!0===this.layerAlphaHack&&(a.style.filter="");OpenLayers.Element.removeClass(a,
+"olImageLoadError")}this.canvasContext=null},getImage:function(){if(!this.imgDiv){this.imgDiv=OpenLayers.Tile.Image.IMAGE.cloneNode(!1);var a=this.imgDiv.style;if(this.frame){var b=0,c=0;this.layer.gutter&&(b=100*(this.layer.gutter/this.layer.tileSize.w),c=100*(this.layer.gutter/this.layer.tileSize.h));a.left=-b+"%";a.top=-c+"%";a.width=2*b+100+"%";a.height=2*c+100+"%"}a.visibility="hidden";a.opacity=0;1>this.layer.opacity&&(a.filter="alpha(opacity="+100*this.layer.opacity+")");a.position="absolute";
+this.layerAlphaHack&&(a.paddingTop=a.height,a.height="0",a.width="100%");this.frame&&this.frame.appendChild(this.imgDiv)}return this.imgDiv},setImage:function(a){this.imgDiv=a},initImage:function(){if(this.url||this.imgDiv){this.events.triggerEvent("beforeload");this.layer.div.appendChild(this.getTile());this.events.triggerEvent(this._loadEvent);var a=this.getImage(),b=a.getAttribute("src")||"";this.url&&OpenLayers.Util.isEquivalentUrl(b,this.url)?this._loadTimeout=window.setTimeout(OpenLayers.Function.bind(this.onImageLoad,
+this),0):(this.stopLoading(),this.crossOriginKeyword&&a.removeAttribute("crossorigin"),OpenLayers.Event.observe(a,"load",OpenLayers.Function.bind(this.onImageLoad,this)),OpenLayers.Event.observe(a,"error",OpenLayers.Function.bind(this.onImageError,this)),this.imageReloadAttempts=0,this.setImgSrc(this.url))}else this.isLoading=!1},setImgSrc:function(a){var b=this.imgDiv;a?(b.style.visibility="hidden",b.style.opacity=0,this.crossOriginKeyword&&("data:"!==a.substr(0,5)?b.setAttribute("crossorigin",this.crossOriginKeyword):
+b.removeAttribute("crossorigin")),b.src=a):(this.stopLoading(),this.imgDiv=null,b.parentNode&&b.parentNode.removeChild(b))},getTile:function(){return this.frame?this.frame:this.getImage()},createBackBuffer:function(){if(this.imgDiv&&!this.isLoading){var a;this.frame?(a=this.frame.cloneNode(!1),a.appendChild(this.imgDiv)):a=this.imgDiv;this.imgDiv=null;return a}},onImageLoad:function(){var a=this.imgDiv;this.stopLoading();a.style.visibility="inherit";a.style.opacity=this.layer.opacity;this.isLoading=
+!1;this.canvasContext=null;this.events.triggerEvent("loadend");!0===this.layerAlphaHack&&(a.style.filter="progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+a.src+"', sizingMethod='scale')")},onImageError:function(){var a=this.imgDiv;null!=a.src&&(this.imageReloadAttempts++,this.imageReloadAttempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS?this.setImgSrc(this.layer.getURL(this.bounds)):(OpenLayers.Element.addClass(a,"olImageLoadError"),this.events.triggerEvent("loaderror"),this.onImageLoad()))},stopLoading:function(){OpenLayers.Event.stopObservingElement(this.imgDiv);
+window.clearTimeout(this._loadTimeout);delete this._loadTimeout},getCanvasContext:function(){if(OpenLayers.CANVAS_SUPPORTED&&this.imgDiv&&!this.isLoading){if(!this.canvasContext){var a=document.createElement("canvas");a.width=this.size.w;a.height=this.size.h;this.canvasContext=a.getContext("2d");this.canvasContext.drawImage(this.imgDiv,0,0)}return this.canvasContext}},CLASS_NAME:"OpenLayers.Tile.Image"});
+OpenLayers.Tile.Image.IMAGE=function(){var a=new Image;a.className="olTileImage";a.galleryImg="no";return a}();OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,tileOriginCorner:"bl",tileOrigin:null,tileOptions:null,tileClass:OpenLayers.Tile.Image,grid:null,singleTile:!1,ratio:1.5,buffer:0,transitionEffect:"resize",numLoadingTiles:0,serverResolutions:null,loading:!1,backBuffer:null,gridResolution:null,backBufferResolution:null,backBufferLonLat:null,backBufferTimerId:null,removeBackBufferDelay:null,className:null,gridLayout:null,rowSign:null,transitionendEvents:["transitionend",
+"webkitTransitionEnd","otransitionend","oTransitionEnd"],initialize:function(a,b,c,d){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.grid=[];this._removeBackBuffer=OpenLayers.Function.bind(this.removeBackBuffer,this);this.initProperties();this.rowSign="t"===this.tileOriginCorner.substr(0,1)?1:-1},initProperties:function(){void 0===this.options.removeBackBufferDelay&&(this.removeBackBufferDelay=this.singleTile?0:2500);void 0===this.options.className&&(this.className=this.singleTile?
+"olLayerGridSingleTile":"olLayerGrid")},setMap:function(a){OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this,a);OpenLayers.Element.addClass(this.div,this.className)},removeMap:function(a){this.removeBackBuffer()},destroy:function(){this.removeBackBuffer();this.clearGrid();this.tileSize=this.grid=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments)},clearGrid:function(){if(this.grid){for(var a=0,b=this.grid.length;a<b;a++)for(var c=this.grid[a],d=0,e=c.length;d<e;d++)this.destroyTile(c[d]);
+this.grid=[];this.gridLayout=this.gridResolution=null}},addOptions:function(a,b){var c=void 0!==a.singleTile&&a.singleTile!==this.singleTile;OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this,arguments);this.map&&c&&(this.initProperties(),this.clearGrid(),this.tileSize=this.options.tileSize,this.setTileSize(),this.moveTo(null,!0))},clone:function(a){null==a&&(a=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.getOptions()));a=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,
+[a]);null!=this.tileSize&&(a.tileSize=this.tileSize.clone());a.grid=[];a.gridResolution=null;a.backBuffer=null;a.backBufferTimerId=null;a.loading=!1;a.numLoadingTiles=0;return a},moveTo:function(a,b,c){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);a=a||this.map.getExtent();if(null!=a){var d=!this.grid.length||b,e=this.getTilesBounds(),f=this.map.getResolution();this.getServerResolution(f);if(this.singleTile){if(d||!c&&!e.containsBounds(a))b&&"resize"!==this.transitionEffect&&
+this.removeBackBuffer(),b&&"resize"!==this.transitionEffect||this.applyBackBuffer(f),this.initSingleTile(a)}else(d=d||!e.intersectsBounds(a,{worldBounds:this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent()}))?(!b||"resize"!==this.transitionEffect&&this.gridResolution!==f||this.applyBackBuffer(f),this.initGriddedTiles(a)):this.moveGriddedTiles()}},getTileData:function(a){var b=null,c=a.lon,d=a.lat,e=this.grid.length;if(this.map&&e){var f=this.map.getResolution();a=this.tileSize.w;var g=this.tileSize.h,
+h=this.grid[0][0].bounds,k=h.left,h=h.top;if(c<k&&this.map.baseLayer.wrapDateLine)var l=this.map.getMaxExtent().getWidth(),m=Math.ceil((k-c)/l),c=c+l*m;c=(c-k)/(f*a);d=(h-d)/(f*g);f=Math.floor(c);k=Math.floor(d);0<=k&&k<e&&(e=this.grid[k][f])&&(b={tile:e,i:Math.floor((c-f)*a),j:Math.floor((d-k)*g)})}return b},destroyTile:function(a){this.removeTileMonitoringHooks(a);a.destroy()},getServerResolution:function(a){var b=Number.POSITIVE_INFINITY;a=a||this.map.getResolution();if(this.serverResolutions&&
+-1===OpenLayers.Util.indexOf(this.serverResolutions,a)){var c,d,e,f;for(c=this.serverResolutions.length-1;0<=c;c--){e=this.serverResolutions[c];d=Math.abs(e-a);if(d>b)break;b=d;f=e}a=f}return a},getServerZoom:function(){var a=this.getServerResolution();return this.serverResolutions?OpenLayers.Util.indexOf(this.serverResolutions,a):this.map.getZoomForResolution(a)+(this.zoomOffset||0)},applyBackBuffer:function(a){null!==this.backBufferTimerId&&this.removeBackBuffer();var b=this.backBuffer;if(!b){b=
+this.createBackBuffer();if(!b)return;a===this.gridResolution?this.div.insertBefore(b,this.div.firstChild):this.map.baseLayer.div.parentNode.insertBefore(b,this.map.baseLayer.div);this.backBuffer=b;var c=this.grid[0][0].bounds;this.backBufferLonLat={lon:c.left,lat:c.top};this.backBufferResolution=this.gridResolution}for(var c=this.backBufferResolution/a,d=b.childNodes,e,f=d.length-1;0<=f;--f)e=d[f],e.style.top=(c*e._i*e._h|0)+"px",e.style.left=(c*e._j*e._w|0)+"px",e.style.width=Math.round(c*e._w)+
+"px",e.style.height=Math.round(c*e._h)+"px";a=this.getViewPortPxFromLonLat(this.backBufferLonLat,a);c=this.map.layerContainerOriginPx.y;b.style.left=Math.round(a.x-this.map.layerContainerOriginPx.x)+"px";b.style.top=Math.round(a.y-c)+"px"},createBackBuffer:function(){var a;if(0<this.grid.length){a=document.createElement("div");a.id=this.div.id+"_bb";a.className="olBackBuffer";a.style.position="absolute";var b=this.map;a.style.zIndex="resize"===this.transitionEffect?this.getZIndex()-1:b.Z_INDEX_BASE.BaseLayer-
+(b.getNumLayers()-b.getLayerIndex(this));for(var b=0,c=this.grid.length;b<c;b++)for(var d=0,e=this.grid[b].length;d<e;d++){var f=this.grid[b][d],g=this.grid[b][d].createBackBuffer();g&&(g._i=b,g._j=d,g._w=f.size.w,g._h=f.size.h,g.id=f.id+"_bb",a.appendChild(g))}}return a},removeBackBuffer:function(){if(this._transitionElement){for(var a=this.transitionendEvents.length-1;0<=a;--a)OpenLayers.Event.stopObserving(this._transitionElement,this.transitionendEvents[a],this._removeBackBuffer);delete this._transitionElement}this.backBuffer&&
+(this.backBuffer.parentNode&&this.backBuffer.parentNode.removeChild(this.backBuffer),this.backBufferResolution=this.backBuffer=null,null!==this.backBufferTimerId&&(window.clearTimeout(this.backBufferTimerId),this.backBufferTimerId=null))},moveByPx:function(a,b){this.singleTile||this.moveGriddedTiles()},setTileSize:function(a){this.singleTile&&(a=this.map.getSize(),a.h=parseInt(a.h*this.ratio,10),a.w=parseInt(a.w*this.ratio,10));OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this,[a])},getTilesBounds:function(){var a=
+null,b=this.grid.length;if(b)var a=this.grid[b-1][0].bounds,b=this.grid[0].length*a.getWidth(),c=this.grid.length*a.getHeight(),a=new OpenLayers.Bounds(a.left,a.bottom,a.left+b,a.bottom+c);return a},initSingleTile:function(a){this.events.triggerEvent("retile");var b=a.getCenterLonLat(),c=a.getWidth()*this.ratio;a=a.getHeight()*this.ratio;b=new OpenLayers.Bounds(b.lon-c/2,b.lat-a/2,b.lon+c/2,b.lat+a/2);c=this.map.getLayerPxFromLonLat({lon:b.left,lat:b.top});this.grid.length||(this.grid[0]=[]);(a=this.grid[0][0])?
+a.moveTo(b,c):(a=this.addTile(b,c),this.addTileMonitoringHooks(a),a.draw(),this.grid[0][0]=a);this.removeExcessTiles(1,1);this.gridResolution=this.getServerResolution()},calculateGridLayout:function(a,b,c){var d=c*this.tileSize.w;c*=this.tileSize.h;var e=Math.floor((a.left-b.lon)/d)-this.buffer,f=this.rowSign;a=Math[~f?"floor":"ceil"](f*(b.lat-a.top+c)/c)-this.buffer*f;return{tilelon:d,tilelat:c,startcol:e,startrow:a}},getTileOrigin:function(){var a=this.tileOrigin;if(!a)var a=this.getMaxExtent(),
+b={tl:["left","top"],tr:["right","top"],bl:["left","bottom"],br:["right","bottom"]}[this.tileOriginCorner],a=new OpenLayers.LonLat(a[b[0]],a[b[1]]);return a},getTileBoundsForGridIndex:function(a,b){var c=this.getTileOrigin(),d=this.gridLayout,e=d.tilelon,f=d.tilelat,g=d.startcol,d=d.startrow,h=this.rowSign;return new OpenLayers.Bounds(c.lon+(g+b)*e,c.lat-(d+a*h)*f*h,c.lon+(g+b+1)*e,c.lat-(d+(a-1)*h)*f*h)},initGriddedTiles:function(a){this.events.triggerEvent("retile");var b=this.map.getSize(),c=this.getTileOrigin(),
+d=this.map.getResolution(),e=this.getServerResolution(),f=d/e,d=this.tileSize.w/f,f=this.tileSize.h/f,g=Math.ceil(b.h/f)+2*this.buffer+1,b=Math.ceil(b.w/d)+2*this.buffer+1;this.gridLayout=e=this.calculateGridLayout(a,c,e);var c=e.tilelon,h=e.tilelat,e=this.map.layerContainerOriginPx.x,k=this.map.layerContainerOriginPx.y,l=this.getTileBoundsForGridIndex(0,0),m=this.map.getViewPortPxFromLonLat(new OpenLayers.LonLat(l.left,l.top));m.x=Math.round(m.x)-e;m.y=Math.round(m.y)-k;var e=[],k=this.map.getCenter(),
+p=0;do{var n=this.grid[p];n||(n=[],this.grid.push(n));var q=0;do{var l=this.getTileBoundsForGridIndex(p,q),r=m.clone();r.x+=q*Math.round(d);r.y+=p*Math.round(f);var s=n[q];s?s.moveTo(l,r,!1):(s=this.addTile(l,r),this.addTileMonitoringHooks(s),n.push(s));r=l.getCenterLonLat();e.push({tile:s,distance:Math.pow(r.lon-k.lon,2)+Math.pow(r.lat-k.lat,2)});q+=1}while(l.right<=a.right+c*this.buffer||q<b);p+=1}while(l.bottom>=a.bottom-h*this.buffer||p<g);this.removeExcessTiles(p,q);this.gridResolution=d=this.getServerResolution();
+e.sort(function(a,b){return a.distance-b.distance});a=0;for(d=e.length;a<d;++a)e[a].tile.draw()},getMaxExtent:function(){return this.maxExtent},addTile:function(a,b){var c=new this.tileClass(this,b,a,null,this.tileSize,this.tileOptions);this.events.triggerEvent("addtile",{tile:c});return c},addTileMonitoringHooks:function(a){a.onLoadStart=function(){!1===this.loading&&(this.loading=!0,this.events.triggerEvent("loadstart"));this.events.triggerEvent("tileloadstart",{tile:a});this.numLoadingTiles++;
+!this.singleTile&&(this.backBuffer&&this.gridResolution===this.backBufferResolution)&&OpenLayers.Element.addClass(a.getTile(),"olTileReplacing")};a.onLoadEnd=function(b){this.numLoadingTiles--;b="unload"===b.type;this.events.triggerEvent("tileloaded",{tile:a,aborted:b});if(!this.singleTile&&!b&&this.backBuffer&&this.gridResolution===this.backBufferResolution){var c=a.getTile();if("none"===OpenLayers.Element.getStyle(c,"display")){var d=document.getElementById(a.id+"_bb");d&&d.parentNode.removeChild(d)}OpenLayers.Element.removeClass(c,
+"olTileReplacing")}if(0===this.numLoadingTiles){if(this.backBuffer)if(0===this.backBuffer.childNodes.length)this.removeBackBuffer();else{this._transitionElement=b?this.div.lastChild:a.imgDiv;b=this.transitionendEvents;for(c=b.length-1;0<=c;--c)OpenLayers.Event.observe(this._transitionElement,b[c],this._removeBackBuffer);this.backBufferTimerId=window.setTimeout(this._removeBackBuffer,this.removeBackBufferDelay)}this.loading=!1;this.events.triggerEvent("loadend")}};a.onLoadError=function(){this.events.triggerEvent("tileerror",
+{tile:a})};a.events.on({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},removeTileMonitoringHooks:function(a){a.unload();a.events.un({loadstart:a.onLoadStart,loadend:a.onLoadEnd,unload:a.onLoadEnd,loaderror:a.onLoadError,scope:this})},moveGriddedTiles:function(){for(var a=this.buffer+1;;){var b=this.grid[0][0],c=b.position.x+this.map.layerContainerOriginPx.x,b=b.position.y+this.map.layerContainerOriginPx.y,d=this.getServerResolution()/this.map.getResolution(),
+d={w:Math.round(this.tileSize.w*d),h:Math.round(this.tileSize.h*d)};if(c>-d.w*(a-1))this.shiftColumn(!0,d);else if(c<-d.w*a)this.shiftColumn(!1,d);else if(b>-d.h*(a-1))this.shiftRow(!0,d);else if(b<-d.h*a)this.shiftRow(!1,d);else break}},shiftRow:function(a,b){var c=this.grid,d=a?0:c.length-1,e=a?-1:1;this.gridLayout.startrow+=e*this.rowSign;for(var f=c[d],g=c[a?"pop":"shift"](),h=0,k=g.length;h<k;h++){var l=g[h],m=f[h].position.clone();m.y+=b.h*e;l.moveTo(this.getTileBoundsForGridIndex(d,h),m)}c[a?
+"unshift":"push"](g)},shiftColumn:function(a,b){var c=this.grid,d=a?0:c[0].length-1,e=a?-1:1;this.gridLayout.startcol+=e;for(var f=0,g=c.length;f<g;f++){var h=c[f],k=h[d].position.clone(),l=h[a?"pop":"shift"]();k.x+=b.w*e;l.moveTo(this.getTileBoundsForGridIndex(f,d),k);h[a?"unshift":"push"](l)}},removeExcessTiles:function(a,b){for(var c,d;this.grid.length>a;){var e=this.grid.pop();c=0;for(d=e.length;c<d;c++){var f=e[c];this.destroyTile(f)}}c=0;for(d=this.grid.length;c<d;c++)for(;this.grid[c].length>
+b;)e=this.grid[c],f=e.pop(),this.destroyTile(f)},onMapResize:function(){this.singleTile&&(this.clearGrid(),this.setTileSize())},getTileBounds:function(a){var b=this.maxExtent,c=this.getResolution(),d=c*this.tileSize.w,c=c*this.tileSize.h,e=this.getLonLatFromViewPortPx(a);a=b.left+d*Math.floor((e.lon-b.left)/d);b=b.bottom+c*Math.floor((e.lat-b.bottom)/c);return new OpenLayers.Bounds(a,b,a+d,b+c)},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Layer.XYZ=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:!0,sphericalMercator:!1,zoomOffset:0,serverResolutions:null,initialize:function(a,b,c){if(c&&c.sphericalMercator||this.sphericalMercator)c=OpenLayers.Util.extend({projection:"EPSG:900913",numZoomLevels:19},c);OpenLayers.Layer.Grid.prototype.initialize.apply(this,[a||this.name,b||this.url,{},c])},clone:function(a){null==a&&(a=new OpenLayers.Layer.XYZ(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,
+[a])},getURL:function(a){a=this.getXYZ(a);var b=this.url;OpenLayers.Util.isArray(b)&&(b=this.selectUrl(""+a.x+a.y+a.z,b));return OpenLayers.String.format(b,a)},getXYZ:function(a){var b=this.getServerResolution(),c=Math.round((a.left-this.maxExtent.left)/(b*this.tileSize.w));a=Math.round((this.maxExtent.top-a.top)/(b*this.tileSize.h));b=this.getServerZoom();if(this.wrapDateLine)var d=Math.pow(2,b),c=(c%d+d)%d;return{x:c,y:a,z:b}},setMap:function(a){OpenLayers.Layer.Grid.prototype.setMap.apply(this,
+arguments);this.tileOrigin||(this.tileOrigin=new OpenLayers.LonLat(this.maxExtent.left,this.maxExtent.bottom))},CLASS_NAME:"OpenLayers.Layer.XYZ"});OpenLayers.Layer.OSM=OpenLayers.Class(OpenLayers.Layer.XYZ,{name:"OpenStreetMap",url:["http://a.tile.openstreetmap.org/${z}/${x}/${y}.png","http://b.tile.openstreetmap.org/${z}/${x}/${y}.png","http://c.tile.openstreetmap.org/${z}/${x}/${y}.png"],attribution:"&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",sphericalMercator:!0,wrapDateLine:!0,tileOptions:null,initialize:function(a,b,c){OpenLayers.Layer.XYZ.prototype.initialize.apply(this,arguments);this.tileOptions=
+OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options&&this.options.tileOptions)},clone:function(a){null==a&&(a=new OpenLayers.Layer.OSM(this.name,this.url,this.getOptions()));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:!1,size:null,resolution:null,map:null,featureDx:0,initialize:function(a,b){this.container=OpenLayers.Util.getElement(a);OpenLayers.Util.extend(this,b)},destroy:function(){this.map=this.resolution=this.size=this.extent=this.container=null},supported:function(){return!1},setExtent:function(a,b){this.extent=a.clone();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var c=a.getWidth()/this.map.getExtent().getWidth();
+a=a.scale(1/c);this.extent=a.wrapDateLine(this.map.getMaxExtent()).scale(c)}b&&(this.resolution=null);return!0},setSize:function(a){this.size=a.clone();this.resolution=null},getResolution:function(){return this.resolution=this.resolution||this.map.getResolution()},drawFeature:function(a,b){null==b&&(b=a.style);if(a.geometry){var c=a.geometry.getBounds();if(c){var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());c.intersectsBounds(this.extent,{worldBounds:d})?this.calculateFeatureDx(c,
+d):b={display:"none"};c=this.drawGeometry(a.geometry,b,a.id);if("none"!=b.display&&b.label&&!1!==c){d=a.geometry.getCentroid();if(b.labelXOffset||b.labelYOffset){var e=isNaN(b.labelXOffset)?0:b.labelXOffset,f=isNaN(b.labelYOffset)?0:b.labelYOffset,g=this.getResolution();d.move(e*g,f*g)}this.drawText(a.id,b,d)}else this.removeText(a.id);return c}}},calculateFeatureDx:function(a,b){this.featureDx=0;if(b){var c=b.getWidth();this.featureDx=Math.round(((a.left+a.right)/2-(this.extent.left+this.extent.right)/
+2)/c)*c}},drawGeometry:function(a,b,c){},drawText:function(a,b,c){},removeText:function(a){},clear:function(){},getFeatureIdFromEvent:function(a){},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0,c=a.length;b<c;++b){var d=a[b];this.eraseGeometry(d.geometry,d.id);this.removeText(d.id)}},eraseGeometry:function(a,b){},moveRoot:function(a){},getRenderLayerId:function(){return this.container.id},applyDefaultSymbolizer:function(a){var b=OpenLayers.Util.extend({},OpenLayers.Renderer.defaultSymbolizer);
+!1===a.stroke&&(delete b.strokeWidth,delete b.strokeColor);!1===a.fill&&delete b.fillColor;OpenLayers.Util.extend(b,a);return b},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Renderer.defaultSymbolizer={fillColor:"#000000",strokeColor:"#000000",strokeWidth:2,fillOpacity:1,strokeOpacity:1,pointRadius:0,labelAlign:"cm"};
+OpenLayers.Renderer.symbol={star:[350,75,379,161,469,161,397,215,423,301,350,250,277,301,303,215,231,161,321,161,350,75],cross:[4,0,6,0,6,4,10,4,10,6,6,6,6,10,4,10,4,6,0,6,0,4,4,4,4,0],x:[0,0,25,0,50,35,75,0,100,0,65,50,100,100,75,100,50,65,25,100,0,100,35,50,0,0],square:[0,0,0,1,1,1,1,0,0,0],triangle:[0,10,10,10,5,0,0,10]};OpenLayers.Renderer.Canvas=OpenLayers.Class(OpenLayers.Renderer,{hitDetection:!0,hitOverflow:0,canvas:null,features:null,pendingRedraw:!1,cachedSymbolBounds:{},initialize:function(a,b){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.root=document.createElement("canvas");this.container.appendChild(this.root);this.canvas=this.root.getContext("2d");this.features={};this.hitDetection&&(this.hitCanvas=document.createElement("canvas"),this.hitContext=this.hitCanvas.getContext("2d"))},
+setExtent:function(){OpenLayers.Renderer.prototype.setExtent.apply(this,arguments);return!1},eraseGeometry:function(a,b){this.eraseFeatures(this.features[b][0])},supported:function(){return OpenLayers.CANVAS_SUPPORTED},setSize:function(a){this.size=a.clone();var b=this.root;b.style.width=a.w+"px";b.style.height=a.h+"px";b.width=a.w;b.height=a.h;this.resolution=null;this.hitDetection&&(b=this.hitCanvas,b.style.width=a.w+"px",b.style.height=a.h+"px",b.width=a.w,b.height=a.h)},drawFeature:function(a,
+b){var c;if(a.geometry){b=this.applyDefaultSymbolizer(b||a.style);c=a.geometry.getBounds();var d;this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&(d=this.map.getMaxExtent());d=c&&c.intersectsBounds(this.extent,{worldBounds:d});(c="none"!==b.display&&!!c&&d)?this.features[a.id]=[a,b]:delete this.features[a.id];this.pendingRedraw=!0}this.pendingRedraw&&!this.locked&&(this.redraw(),this.pendingRedraw=!1);return c},drawGeometry:function(a,b,c){var d=a.CLASS_NAME;if("OpenLayers.Geometry.Collection"==
+d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d)for(d=0;d<a.components.length;d++)this.drawGeometry(a.components[d],b,c);else switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":this.drawPoint(a,b,c);break;case "OpenLayers.Geometry.LineString":this.drawLineString(a,b,c);break;case "OpenLayers.Geometry.LinearRing":this.drawLinearRing(a,b,c);break;case "OpenLayers.Geometry.Polygon":this.drawPolygon(a,b,c)}},drawExternalGraphic:function(a,
+b,c){var d=new Image,e=b.title||b.graphicTitle;e&&(d.title=e);var f=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,f=f?f:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*f),k=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),l=b.graphicOpacity||b.fillOpacity;d.onload=OpenLayers.Function.bind(function(){if(this.features[c]){var b=this.getLocalXY(a),e=b[0],b=b[1];if(!isNaN(e)&&!isNaN(b)){var e=e+h|0,b=b+k|0,n=this.canvas;n.globalAlpha=l;var q=
+OpenLayers.Renderer.Canvas.drawImageScaleFactor||(OpenLayers.Renderer.Canvas.drawImageScaleFactor=/android 2.1/.test(navigator.userAgent.toLowerCase())?320/window.screen.width:1);n.drawImage(d,e*q,b*q,f*q,g*q);this.hitDetection&&(this.setHitContextStyle("fill",c),this.hitContext.fillRect(e,b,f,g))}}},this);d.src=b.externalGraphic},drawNamedSymbol:function(a,b,c){var d,e,f,g;f=Math.PI/180;var h=OpenLayers.Renderer.symbol[b.graphicName];if(!h)throw Error(b.graphicName+" is not a valid symbol name");
+if(!(!h.length||2>h.length||(a=this.getLocalXY(a),e=a[0],g=a[1],isNaN(e)||isNaN(g)))){this.canvas.lineCap="round";this.canvas.lineJoin="round";this.hitDetection&&(this.hitContext.lineCap="round",this.hitContext.lineJoin="round");if(b.graphicName in this.cachedSymbolBounds)d=this.cachedSymbolBounds[b.graphicName];else{d=new OpenLayers.Bounds;for(a=0;a<h.length;a+=2)d.extend(new OpenLayers.LonLat(h[a],h[a+1]));this.cachedSymbolBounds[b.graphicName]=d}this.canvas.save();this.hitDetection&&this.hitContext.save();
+this.canvas.translate(e,g);this.hitDetection&&this.hitContext.translate(e,g);a=f*b.rotation;isNaN(a)||(this.canvas.rotate(a),this.hitDetection&&this.hitContext.rotate(a));f=2*b.pointRadius/Math.max(d.getWidth(),d.getHeight());this.canvas.scale(f,f);this.hitDetection&&this.hitContext.scale(f,f);a=d.getCenterLonLat().lon;d=d.getCenterLonLat().lat;this.canvas.translate(-a,-d);this.hitDetection&&this.hitContext.translate(-a,-d);g=b.strokeWidth;b.strokeWidth=g/f;if(!1!==b.fill){this.setCanvasStyle("fill",
+b);this.canvas.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.canvas.lineTo(d,e);this.canvas.closePath();this.canvas.fill();if(this.hitDetection){this.setHitContextStyle("fill",c,b);this.hitContext.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.hitContext.lineTo(d,e);this.hitContext.closePath();this.hitContext.fill()}}if(!1!==b.stroke){this.setCanvasStyle("stroke",b);this.canvas.beginPath();for(a=0;a<h.length;a+=2)d=h[a],
+e=h[a+1],0==a&&this.canvas.moveTo(d,e),this.canvas.lineTo(d,e);this.canvas.closePath();this.canvas.stroke();if(this.hitDetection){this.setHitContextStyle("stroke",c,b,f);this.hitContext.beginPath();for(a=0;a<h.length;a+=2)d=h[a],e=h[a+1],0==a&&this.hitContext.moveTo(d,e),this.hitContext.lineTo(d,e);this.hitContext.closePath();this.hitContext.stroke()}}b.strokeWidth=g;this.canvas.restore();this.hitDetection&&this.hitContext.restore();this.setCanvasStyle("reset")}},setCanvasStyle:function(a,b){"fill"===
+a?(this.canvas.globalAlpha=b.fillOpacity,this.canvas.fillStyle=b.fillColor):"stroke"===a?(this.canvas.globalAlpha=b.strokeOpacity,this.canvas.strokeStyle=b.strokeColor,this.canvas.lineWidth=b.strokeWidth):(this.canvas.globalAlpha=0,this.canvas.lineWidth=1)},featureIdToHex:function(a){a=Number(a.split("_").pop())+1;16777216<=a&&(this.hitOverflow=a-16777215,a=a%16777216+1);a="000000"+a.toString(16);var b=a.length;return a="#"+a.substring(b-6,b)},setHitContextStyle:function(a,b,c,d){b=this.featureIdToHex(b);
+"fill"==a?(this.hitContext.globalAlpha=1,this.hitContext.fillStyle=b):"stroke"==a?(this.hitContext.globalAlpha=1,this.hitContext.strokeStyle=b,"undefined"===typeof d?this.hitContext.lineWidth=c.strokeWidth+2:isNaN(d)||(this.hitContext.lineWidth=c.strokeWidth+2/d)):(this.hitContext.globalAlpha=0,this.hitContext.lineWidth=1)},drawPoint:function(a,b,c){if(!1!==b.graphic)if(b.externalGraphic)this.drawExternalGraphic(a,b,c);else if(b.graphicName&&"circle"!=b.graphicName)this.drawNamedSymbol(a,b,c);else{var d=
+this.getLocalXY(a);a=d[0];d=d[1];if(!isNaN(a)&&!isNaN(d)){var e=2*Math.PI,f=b.pointRadius;!1!==b.fill&&(this.setCanvasStyle("fill",b),this.canvas.beginPath(),this.canvas.arc(a,d,f,0,e,!0),this.canvas.fill(),this.hitDetection&&(this.setHitContextStyle("fill",c,b),this.hitContext.beginPath(),this.hitContext.arc(a,d,f,0,e,!0),this.hitContext.fill()));!1!==b.stroke&&(this.setCanvasStyle("stroke",b),this.canvas.beginPath(),this.canvas.arc(a,d,f,0,e,!0),this.canvas.stroke(),this.hitDetection&&(this.setHitContextStyle("stroke",
+c,b),this.hitContext.beginPath(),this.hitContext.arc(a,d,f,0,e,!0),this.hitContext.stroke()),this.setCanvasStyle("reset"))}}},drawLineString:function(a,b,c){b=OpenLayers.Util.applyDefaults({fill:!1},b);this.drawLinearRing(a,b,c)},drawLinearRing:function(a,b,c){!1!==b.fill&&(this.setCanvasStyle("fill",b),this.renderPath(this.canvas,a,b,c,"fill"),this.hitDetection&&(this.setHitContextStyle("fill",c,b),this.renderPath(this.hitContext,a,b,c,"fill")));!1!==b.stroke&&(this.setCanvasStyle("stroke",b),this.renderPath(this.canvas,
+a,b,c,"stroke"),this.hitDetection&&(this.setHitContextStyle("stroke",c,b),this.renderPath(this.hitContext,a,b,c,"stroke")));this.setCanvasStyle("reset")},renderPath:function(a,b,c,d,e){b=b.components;c=b.length;a.beginPath();d=this.getLocalXY(b[0]);var f=d[1];if(!isNaN(d[0])&&!isNaN(f)){a.moveTo(d[0],d[1]);for(d=1;d<c;++d)f=this.getLocalXY(b[d]),a.lineTo(f[0],f[1]);"fill"===e?a.fill():a.stroke()}},drawPolygon:function(a,b,c){a=a.components;var d=a.length;this.drawLinearRing(a[0],b,c);for(var e=1;e<
+d;++e)this.canvas.globalCompositeOperation="destination-out",this.hitDetection&&(this.hitContext.globalCompositeOperation="destination-out"),this.drawLinearRing(a[e],OpenLayers.Util.applyDefaults({stroke:!1,fillOpacity:1},b),c),this.canvas.globalCompositeOperation="source-over",this.hitDetection&&(this.hitContext.globalCompositeOperation="source-over"),this.drawLinearRing(a[e],OpenLayers.Util.applyDefaults({fill:!1},b),c)},drawText:function(a,b){var c=this.getLocalXY(a);this.setCanvasStyle("reset");
+this.canvas.fillStyle=b.fontColor;this.canvas.globalAlpha=b.fontOpacity||1;var d=[b.fontStyle?b.fontStyle:"normal","normal",b.fontWeight?b.fontWeight:"normal",b.fontSize?b.fontSize:"1em",b.fontFamily?b.fontFamily:"sans-serif"].join(" "),e=b.label.split("\n"),f=e.length;if(this.canvas.fillText){this.canvas.font=d;this.canvas.textAlign=OpenLayers.Renderer.Canvas.LABEL_ALIGN[b.labelAlign[0]]||"center";this.canvas.textBaseline=OpenLayers.Renderer.Canvas.LABEL_ALIGN[b.labelAlign[1]]||"middle";var g=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[1]];
+null==g&&(g=-0.5);d=this.canvas.measureText("Mg").height||this.canvas.measureText("xx").width;c[1]+=d*g*(f-1);for(g=0;g<f;g++)b.labelOutlineWidth&&(this.canvas.save(),this.canvas.globalAlpha=b.labelOutlineOpacity||b.fontOpacity||1,this.canvas.strokeStyle=b.labelOutlineColor,this.canvas.lineWidth=b.labelOutlineWidth,this.canvas.strokeText(e[g],c[0],c[1]+d*g+1),this.canvas.restore()),this.canvas.fillText(e[g],c[0],c[1]+d*g)}else if(this.canvas.mozDrawText){this.canvas.mozTextStyle=d;var h=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[0]];
+null==h&&(h=-0.5);g=OpenLayers.Renderer.Canvas.LABEL_FACTOR[b.labelAlign[1]];null==g&&(g=-0.5);d=this.canvas.mozMeasureText("xx");c[1]+=d*(1+g*f);for(g=0;g<f;g++){var k=c[0]+h*this.canvas.mozMeasureText(e[g]),l=c[1]+g*d;this.canvas.translate(k,l);this.canvas.mozDrawText(e[g]);this.canvas.translate(-k,-l)}}this.setCanvasStyle("reset")},getLocalXY:function(a){var b=this.getResolution(),c=this.extent;return[(a.x-this.featureDx)/b+-c.left/b,c.top/b-a.y/b]},clear:function(){var a=this.root.height,b=this.root.width;
+this.canvas.clearRect(0,0,b,a);this.features={};this.hitDetection&&this.hitContext.clearRect(0,0,b,a)},getFeatureIdFromEvent:function(a){var b;if(this.hitDetection&&"none"!==this.root.style.display&&!this.map.dragging&&(a=a.xy,a=this.hitContext.getImageData(a.x|0,a.y|0,1,1).data,255===a[3]&&(a=a[2]+256*(a[1]+256*a[0])))){a="OpenLayers_Feature_Vector_"+(a-1+this.hitOverflow);try{b=this.features[a][0]}catch(c){}}return b},eraseFeatures:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=0;b<a.length;++b)delete this.features[a[b].id];
+this.redraw()},redraw:function(){if(!this.locked){var a=this.root.height,b=this.root.width;this.canvas.clearRect(0,0,b,a);this.hitDetection&&this.hitContext.clearRect(0,0,b,a);var a=[],c,d,e=this.map.baseLayer&&this.map.baseLayer.wrapDateLine&&this.map.getMaxExtent(),f;for(f in this.features)this.features.hasOwnProperty(f)&&(b=this.features[f][0],c=b.geometry,this.calculateFeatureDx(c.getBounds(),e),d=this.features[f][1],this.drawGeometry(c,d,b.id),d.label&&a.push([b,d]));b=0;for(c=a.length;b<c;++b)f=
+a[b],this.drawText(f[0].geometry.getCentroid(),f[1])}},CLASS_NAME:"OpenLayers.Renderer.Canvas"});OpenLayers.Renderer.Canvas.LABEL_ALIGN={l:"left",r:"right",t:"top",b:"bottom"};OpenLayers.Renderer.Canvas.LABEL_FACTOR={l:0,r:-1,t:0,b:-1};OpenLayers.Renderer.Canvas.drawImageScaleFactor=null;OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:!1,evt:null,touch:!1,initialize:function(a,b,c){OpenLayers.Util.extend(this,c);this.control=a;this.callbacks=b;(a=this.map||a.map)&&this.setMap(a);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},setMap:function(a){this.map=a},checkModifiers:function(a){return null==this.keyMask?!0:((a.shiftKey?OpenLayers.Handler.MOD_SHIFT:0)|(a.ctrlKey?OpenLayers.Handler.MOD_CTRL:0)|(a.altKey?OpenLayers.Handler.MOD_ALT:
+0)|(a.metaKey?OpenLayers.Handler.MOD_META:0))==this.keyMask},activate:function(){if(this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.register(a[b],this[a[b]]);return this.active=!0},deactivate:function(){if(!this.active)return!1;for(var a=OpenLayers.Events.prototype.BROWSER_EVENTS,b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]]);this.active=this.touch=!1;return!0},startTouch:function(){if(!this.touch){this.touch=!0;
+for(var a="mousedown mouseup mousemove click dblclick mouseout".split(" "),b=0,c=a.length;b<c;b++)this[a[b]]&&this.unregister(a[b],this[a[b]])}},callback:function(a,b){a&&this.callbacks[a]&&this.callbacks[a].apply(this.control,b)},register:function(a,b){this.map.events.registerPriority(a,this,b);this.map.events.registerPriority(a,this,this.setEvent)},unregister:function(a,b){this.map.events.unregister(a,this,b);this.map.events.unregister(a,this,this.setEvent)},setEvent:function(a){this.evt=a;return!0},
+destroy:function(){this.deactivate();this.control=this.map=null},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Handler.MOD_META=8;OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!0,dragging:!1,last:null,start:null,lastMoveEvt:null,oldOnselectstart:null,interval:0,timeoutId:null,documentDrag:!1,documentEvents:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(!0===this.documentDrag){var d=this;this._docMove=function(a){d.mousemove({xy:{x:a.clientX,y:a.clientY},element:document})};this._docUp=function(a){d.mouseup({xy:{x:a.clientX,y:a.clientY}})}}},
+dragstart:function(a){var b=!0;this.dragging=!1;this.checkModifiers(a)&&(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))?(this.started=!0,this.last=this.start=a.xy,OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown"),this.down(a),this.callback("down",[a.xy]),OpenLayers.Event.preventDefault(a),this.oldOnselectstart||(this.oldOnselectstart=document.onselectstart?document.onselectstart:OpenLayers.Function.True),document.onselectstart=OpenLayers.Function.False,b=!this.stopDown):
+(this.started=!1,this.last=this.start=null);return b},dragmove:function(a){this.lastMoveEvt=a;!this.started||(this.timeoutId||a.xy.x==this.last.x&&a.xy.y==this.last.y)||(!0===this.documentDrag&&this.documentEvents&&(a.element===document?(this.adjustXY(a),this.setEvent(a)):this.removeDocumentEvents()),0<this.interval&&(this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval)),this.dragging=!0,this.move(a),this.callback("move",[a.xy]),this.oldOnselectstart||(this.oldOnselectstart=
+document.onselectstart,document.onselectstart=OpenLayers.Function.False),this.last=a.xy);return!0},dragend:function(a){if(this.started){!0===this.documentDrag&&this.documentEvents&&(this.adjustXY(a),this.removeDocumentEvents());var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(a);this.callback("up",[a.xy]);b&&this.callback("done",[a.xy]);document.onselectstart=this.oldOnselectstart}return!0},down:function(a){},move:function(a){},
+up:function(a){},out:function(a){},mousedown:function(a){return this.dragstart(a)},touchstart:function(a){this.startTouch();return this.dragstart(a)},mousemove:function(a){return this.dragmove(a)},touchmove:function(a){return this.dragmove(a)},removeTimeout:function(){this.timeoutId=null;this.dragging&&this.mousemove(this.lastMoveEvt)},mouseup:function(a){return this.dragend(a)},touchend:function(a){a.xy=this.last;return this.dragend(a)},mouseout:function(a){if(this.started&&OpenLayers.Util.mouseLeft(a,
+this.map.viewPortDiv))if(!0===this.documentDrag)this.addDocumentEvents();else{var b=this.start!=this.last;this.dragging=this.started=!1;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(a);this.callback("out",[]);b&&this.callback("done",[a.xy]);document.onselectstart&&(document.onselectstart=this.oldOnselectstart)}return!0},click:function(a){return this.start==this.last},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.dragging=
+!1,a=!0);return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.dragging=this.started=!1,this.last=this.start=null,a=!0,OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown"));return a},adjustXY:function(a){var b=OpenLayers.Util.pagePosition(this.map.viewPortDiv);a.xy.x-=b[0];a.xy.y-=b[1]},addDocumentEvents:function(){OpenLayers.Element.addClass(document.body,"olDragDown");this.documentEvents=!0;OpenLayers.Event.observe(document,"mousemove",
+this._docMove);OpenLayers.Event.observe(document,"mouseup",this._docUp)},removeDocumentEvents:function(){OpenLayers.Element.removeClass(document.body,"olDragDown");this.documentEvents=!1;OpenLayers.Event.stopObserving(document,"mousemove",this._docMove);OpenLayers.Event.stopObserving(document,"mouseup",this._docUp)},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Handler.Keyboard=OpenLayers.Class(OpenLayers.Handler,{KEY_EVENTS:["keydown","keyup"],eventListener:null,observeElement:null,initialize:function(a,b,c){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.eventListener=OpenLayers.Function.bindAsEventListener(this.handleKeyEvent,this)},destroy:function(){this.deactivate();this.eventListener=null;OpenLayers.Handler.prototype.destroy.apply(this,arguments)},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,
+arguments)){this.observeElement=this.observeElement||document;for(var a=0,b=this.KEY_EVENTS.length;a<b;a++)OpenLayers.Event.observe(this.observeElement,this.KEY_EVENTS[a],this.eventListener);return!0}return!1},deactivate:function(){var a=!1;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){for(var a=0,b=this.KEY_EVENTS.length;a<b;a++)OpenLayers.Event.stopObserving(this.observeElement,this.KEY_EVENTS[a],this.eventListener);a=!0}return a},handleKeyEvent:function(a){this.checkModifiers(a)&&
+this.callback(a.type,[a])},CLASS_NAME:"OpenLayers.Handler.Keyboard"});OpenLayers.Control.ModifyFeature=OpenLayers.Class(OpenLayers.Control,{documentDrag:!1,geometryTypes:null,clickout:!0,toggle:!0,standalone:!1,layer:null,feature:null,vertex:null,vertices:null,virtualVertices:null,handlers:null,deleteCodes:null,virtualStyle:null,vertexRenderIntent:null,mode:null,createVertices:!0,modified:!1,radiusHandle:null,dragHandle:null,onModificationStart:function(){},onModification:function(){},onModificationEnd:function(){},initialize:function(a,b){b=b||{};this.layer=a;this.vertices=
+[];this.virtualVertices=[];this.virtualStyle=OpenLayers.Util.extend({},this.layer.style||this.layer.styleMap.createSymbolizer(null,b.vertexRenderIntent));this.virtualStyle.fillOpacity=0.3;this.virtualStyle.strokeOpacity=0.3;this.deleteCodes=[46,68];this.mode=OpenLayers.Control.ModifyFeature.RESHAPE;OpenLayers.Control.prototype.initialize.apply(this,[b]);OpenLayers.Util.isArray(this.deleteCodes)||(this.deleteCodes=[this.deleteCodes]);var c={documentDrag:this.documentDrag,stopDown:!1};this.handlers=
+{keyboard:new OpenLayers.Handler.Keyboard(this,{keydown:this.handleKeypress}),drag:new OpenLayers.Handler.Drag(this,{down:function(a){this.vertex=null;(a=this.layer.getFeatureFromEvent(this.handlers.drag.evt))?this.dragStart(a):this.clickout&&(this._unselect=this.feature)},move:function(a){delete this._unselect;this.vertex&&this.dragVertex(this.vertex,a)},up:function(){this.handlers.drag.stopDown=!1;this._unselect&&(this.unselectFeature(this._unselect),delete this._unselect)},done:function(a){this.vertex&&
+this.dragComplete(this.vertex)}},c)}},destroy:function(){this.map&&this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this});this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[])},activate:function(){this.moveLayerToTop();this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this});return this.handlers.keyboard.activate()&&this.handlers.drag.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments)},
+deactivate:function(){var a=!1;OpenLayers.Control.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),this.layer.removeFeatures(this.vertices,{silent:!0}),this.layer.removeFeatures(this.virtualVertices,{silent:!0}),this.vertices=[],this.handlers.drag.deactivate(),this.handlers.keyboard.deactivate(),(a=this.feature)&&(a.geometry&&a.layer)&&this.unselectFeature(a),a=!0);return a},beforeSelectFeature:function(a){return this.layer.events.triggerEvent("beforefeaturemodified",
+{feature:a})},selectFeature:function(a){if(!(this.feature===a||this.geometryTypes&&-1==OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME))){!1!==this.beforeSelectFeature(a)&&(this.feature&&this.unselectFeature(this.feature),this.feature=a,this.layer.selectedFeatures.push(a),this.layer.drawFeature(a,"select"),this.modified=!1,this.resetVertices(),this.onModificationStart(this.feature));var b=a.modified;!a.geometry||b&&b.geometry||(this._originalGeometry=a.geometry.clone())}},unselectFeature:function(a){this.layer.removeFeatures(this.vertices,
+{silent:!0});this.vertices=[];this.layer.destroyFeatures(this.virtualVertices,{silent:!0});this.virtualVertices=[];this.dragHandle&&(this.layer.destroyFeatures([this.dragHandle],{silent:!0}),delete this.dragHandle);this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),delete this.radiusHandle);this.layer.drawFeature(this.feature,"default");this.feature=null;OpenLayers.Util.removeItem(this.layer.selectedFeatures,a);this.onModificationEnd(a);this.layer.events.triggerEvent("afterfeaturemodified",
+{feature:a,modified:this.modified});this.modified=!1},dragStart:function(a){var b="OpenLayers.Geometry.Point"==a.geometry.CLASS_NAME;this.standalone||(a._sketch||!b)&&a._sketch||(this.toggle&&this.feature===a&&(this._unselect=a),this.selectFeature(a));if(a._sketch||b)this.vertex=a,this.handlers.drag.stopDown=!0},dragVertex:function(a,b){var c=this.map.getLonLatFromViewPortPx(b),d=a.geometry;d.move(c.lon-d.x,c.lat-d.y);this.modified=!0;"OpenLayers.Geometry.Point"==this.feature.geometry.CLASS_NAME?
+this.layer.events.triggerEvent("vertexmodified",{vertex:a.geometry,feature:this.feature,pixel:b}):(a._index?(a.geometry.parent.addComponent(a.geometry,a._index),delete a._index,OpenLayers.Util.removeItem(this.virtualVertices,a),this.vertices.push(a)):a==this.dragHandle?(this.layer.removeFeatures(this.vertices,{silent:!0}),this.vertices=[],this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),this.radiusHandle=null)):a!==this.radiusHandle&&this.layer.events.triggerEvent("vertexmodified",
+{vertex:a.geometry,feature:this.feature,pixel:b}),0<this.virtualVertices.length&&(this.layer.destroyFeatures(this.virtualVertices,{silent:!0}),this.virtualVertices=[]),this.layer.drawFeature(this.feature,this.standalone?void 0:"select"));this.layer.drawFeature(a)},dragComplete:function(a){this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature})},setFeatureState:function(){if(this.feature.state!=OpenLayers.State.INSERT&&
+this.feature.state!=OpenLayers.State.DELETE&&(this.feature.state=OpenLayers.State.UPDATE,this.modified&&this._originalGeometry)){var a=this.feature;a.modified=OpenLayers.Util.extend(a.modified,{geometry:this._originalGeometry});delete this._originalGeometry}},resetVertices:function(){0<this.vertices.length&&(this.layer.removeFeatures(this.vertices,{silent:!0}),this.vertices=[]);0<this.virtualVertices.length&&(this.layer.removeFeatures(this.virtualVertices,{silent:!0}),this.virtualVertices=[]);this.dragHandle&&
+(this.layer.destroyFeatures([this.dragHandle],{silent:!0}),this.dragHandle=null);this.radiusHandle&&(this.layer.destroyFeatures([this.radiusHandle],{silent:!0}),this.radiusHandle=null);this.feature&&"OpenLayers.Geometry.Point"!=this.feature.geometry.CLASS_NAME&&(this.mode&OpenLayers.Control.ModifyFeature.DRAG&&this.collectDragHandle(),this.mode&(OpenLayers.Control.ModifyFeature.ROTATE|OpenLayers.Control.ModifyFeature.RESIZE)&&this.collectRadiusHandle(),this.mode&OpenLayers.Control.ModifyFeature.RESHAPE&&
+(this.mode&OpenLayers.Control.ModifyFeature.RESIZE||this.collectVertices()))},handleKeypress:function(a){var b=a.keyCode;this.feature&&-1!=OpenLayers.Util.indexOf(this.deleteCodes,b)&&(b=this.layer.getFeatureFromEvent(this.handlers.drag.evt))&&(-1!=OpenLayers.Util.indexOf(this.vertices,b)&&!this.handlers.drag.dragging&&b.geometry.parent)&&(b.geometry.parent.removeComponent(b.geometry),this.layer.events.triggerEvent("vertexremoved",{vertex:b.geometry,feature:this.feature,pixel:a.xy}),this.layer.drawFeature(this.feature,
+this.standalone?void 0:"select"),this.modified=!0,this.resetVertices(),this.setFeatureState(),this.onModification(this.feature),this.layer.events.triggerEvent("featuremodified",{feature:this.feature}))},collectVertices:function(){function a(c){var d,e,f;if("OpenLayers.Geometry.Point"==c.CLASS_NAME)e=new OpenLayers.Feature.Vector(c),e._sketch=!0,e.renderIntent=b.vertexRenderIntent,b.vertices.push(e);else{f=c.components.length;"OpenLayers.Geometry.LinearRing"==c.CLASS_NAME&&(f-=1);for(d=0;d<f;++d)e=
+c.components[d],"OpenLayers.Geometry.Point"==e.CLASS_NAME?(e=new OpenLayers.Feature.Vector(e),e._sketch=!0,e.renderIntent=b.vertexRenderIntent,b.vertices.push(e)):a(e);if(b.createVertices&&"OpenLayers.Geometry.MultiPoint"!=c.CLASS_NAME)for(d=0,f=c.components.length;d<f-1;++d){e=c.components[d];var g=c.components[d+1];"OpenLayers.Geometry.Point"==e.CLASS_NAME&&"OpenLayers.Geometry.Point"==g.CLASS_NAME&&(e=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point((e.x+g.x)/2,(e.y+g.y)/2),null,b.virtualStyle),
+e.geometry.parent=c,e._index=d+1,e._sketch=!0,b.virtualVertices.push(e))}}}this.vertices=[];this.virtualVertices=[];var b=this;a.call(this,this.feature.geometry);this.layer.addFeatures(this.virtualVertices,{silent:!0});this.layer.addFeatures(this.vertices,{silent:!0})},collectDragHandle:function(){var a=this.feature.geometry,b=a.getBounds().getCenterLonLat(),b=new OpenLayers.Geometry.Point(b.lon,b.lat),c=new OpenLayers.Feature.Vector(b);b.move=function(b,c){OpenLayers.Geometry.Point.prototype.move.call(this,
+b,c);a.move(b,c)};c._sketch=!0;this.dragHandle=c;this.dragHandle.renderIntent=this.vertexRenderIntent;this.layer.addFeatures([this.dragHandle],{silent:!0})},collectRadiusHandle:function(){var a=this.feature.geometry,b=a.getBounds(),c=b.getCenterLonLat(),d=new OpenLayers.Geometry.Point(c.lon,c.lat),b=new OpenLayers.Geometry.Point(b.right,b.bottom),c=new OpenLayers.Feature.Vector(b),e=this.mode&OpenLayers.Control.ModifyFeature.RESIZE,f=this.mode&OpenLayers.Control.ModifyFeature.RESHAPE,g=this.mode&
+OpenLayers.Control.ModifyFeature.ROTATE;b.move=function(b,c){OpenLayers.Geometry.Point.prototype.move.call(this,b,c);var l=this.x-d.x,m=this.y-d.y,p=l-b,n=m-c;if(g){var q=Math.atan2(n,p),q=Math.atan2(m,l)-q,q=q*(180/Math.PI);a.rotate(q,d)}if(e){var r;f?(m/=n,r=l/p/m):(p=Math.sqrt(p*p+n*n),m=Math.sqrt(l*l+m*m)/p);a.resize(m,d,r)}};c._sketch=!0;this.radiusHandle=c;this.radiusHandle.renderIntent=this.vertexRenderIntent;this.layer.addFeatures([this.radiusHandle],{silent:!0})},setMap:function(a){this.handlers.drag.setMap(a);
+OpenLayers.Control.prototype.setMap.apply(this,arguments)},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this.layer.setZIndex(a)},moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Control.ModifyFeature"});
+OpenLayers.Control.ModifyFeature.RESHAPE=1;OpenLayers.Control.ModifyFeature.RESIZE=2;OpenLayers.Control.ModifyFeature.ROTATE=4;OpenLayers.Control.ModifyFeature.DRAG=8;OpenLayers.Layer.Bing=OpenLayers.Class(OpenLayers.Layer.XYZ,{key:null,serverResolutions:[156543.03390625,78271.516953125,39135.7584765625,19567.87923828125,9783.939619140625,4891.9698095703125,2445.9849047851562,1222.9924523925781,611.4962261962891,305.74811309814453,152.87405654907226,76.43702827453613,38.218514137268066,19.109257068634033,9.554628534317017,4.777314267158508,2.388657133579254,1.194328566789627,0.5971642833948135,0.29858214169740677,0.14929107084870338,0.07464553542435169],attributionTemplate:'<span class="olBingAttribution ${type}"><div><a target="_blank" href="http://www.bing.com/maps/"><img src="${logo}" /></a></div>${copyrights}<a style="white-space: nowrap" target="_blank" href="http://www.microsoft.com/maps/product/terms.html">Terms of Use</a></span>',
+metadata:null,protocolRegex:/^http:/i,type:"Road",culture:"en-US",metadataParams:null,tileOptions:null,protocol:~window.location.href.indexOf("http")?"":"http:",initialize:function(a){a=OpenLayers.Util.applyDefaults({sphericalMercator:!0},a);OpenLayers.Layer.XYZ.prototype.initialize.apply(this,[a.name||"Bing "+(a.type||this.type),null,a]);this.tileOptions=OpenLayers.Util.extend({crossOriginKeyword:"anonymous"},this.options.tileOptions);this.loadMetadata()},loadMetadata:function(){this._callbackId=
+"_callback_"+this.id.replace(/\./g,"_");window[this._callbackId]=OpenLayers.Function.bind(OpenLayers.Layer.Bing.processMetadata,this);var a=OpenLayers.Util.applyDefaults({key:this.key,jsonp:this._callbackId,include:"ImageryProviders"},this.metadataParams),a=this.protocol+"//dev.virtualearth.net/REST/v1/Imagery/Metadata/"+this.type+"?"+OpenLayers.Util.getParameterString(a),b=document.createElement("script");b.type="text/javascript";b.src=a;b.id=this._callbackId;document.getElementsByTagName("head")[0].appendChild(b)},
+initLayer:function(){var a=this.metadata.resourceSets[0].resources[0],b=a.imageUrl.replace("{quadkey}","${quadkey}"),b=b.replace("{culture}",this.culture),b=b.replace(this.protocolRegex,this.protocol);this.url=[];for(var c=0;c<a.imageUrlSubdomains.length;++c)this.url.push(b.replace("{subdomain}",a.imageUrlSubdomains[c]));this.addOptions({maxResolution:Math.min(this.serverResolutions[a.zoomMin],this.maxResolution||Number.POSITIVE_INFINITY),numZoomLevels:Math.min(a.zoomMax+1-a.zoomMin,this.numZoomLevels)},
+!0);this.isBaseLayer||this.redraw();this.updateAttribution()},getURL:function(a){if(this.url){var b=this.getXYZ(a);a=b.x;for(var c=b.y,b=b.z,d=[],e=b;0<e;--e){var f="0",g=1<<e-1;0!=(a&g)&&f++;0!=(c&g)&&(f++,f++);d.push(f)}d=d.join("");a=this.selectUrl(""+a+c+b,this.url);return OpenLayers.String.format(a,{quadkey:d})}},updateAttribution:function(){var a=this.metadata;if(a.resourceSets&&this.map&&this.map.center){var b=a.resourceSets[0].resources[0],c=this.map.getExtent().transform(this.map.getProjectionObject(),
+new OpenLayers.Projection("EPSG:4326")),d=b.imageryProviders||[],e=OpenLayers.Util.indexOf(this.serverResolutions,this.getServerResolution()),b="",f,g,h,k,l,m,p;g=0;for(h=d.length;g<h;++g)for(f=d[g],k=0,l=f.coverageAreas.length;k<l;++k)p=f.coverageAreas[k],m=OpenLayers.Bounds.fromArray(p.bbox,!0),c.intersectsBounds(m)&&(e<=p.zoomMax&&e>=p.zoomMin)&&(b+=f.attribution+" ");a=a.brandLogoUri.replace(this.protocolRegex,this.protocol);this.attribution=OpenLayers.String.format(this.attributionTemplate,{type:this.type.toLowerCase(),
+logo:a,copyrights:b});this.map&&this.map.events.triggerEvent("changelayer",{layer:this,property:"attribution"})}},setMap:function(){OpenLayers.Layer.XYZ.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.updateAttribution)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Bing(this.options));return a=OpenLayers.Layer.XYZ.prototype.clone.apply(this,[a])},destroy:function(){this.map&&this.map.events.unregister("moveend",this,this.updateAttribution);OpenLayers.Layer.XYZ.prototype.destroy.apply(this,
+arguments)},CLASS_NAME:"OpenLayers.Layer.Bing"});OpenLayers.Layer.Bing.processMetadata=function(a){this.metadata=a;this.initLayer();a=document.getElementById(this._callbackId);a.parentNode.removeChild(a);window[this._callbackId]=void 0;delete this._callbackId};OpenLayers.Geometry.MultiLineString=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LineString"],split:function(a,b){for(var c=null,d=b&&b.mutual,e,f,g,h,k=[],l=[a],m=0,p=this.components.length;m<p;++m){f=this.components[m];g=!1;for(var n=0;n<l.length;++n)if(e=f.split(l[n],b)){if(d){g=e[0];for(var q=0,r=g.length;q<r;++q)0===q&&k.length?k[k.length-1].addComponent(g[q]):k.push(new OpenLayers.Geometry.MultiLineString([g[q]]));g=!0;e=e[1]}if(e.length){e.unshift(n,
+1);Array.prototype.splice.apply(l,e);break}}g||(k.length?k[k.length-1].addComponent(f.clone()):k=[new OpenLayers.Geometry.MultiLineString(f.clone())])}k&&1<k.length?g=!0:k=[];l&&1<l.length?h=!0:l=[];if(g||h)c=d?[k,l]:l;return c},splitWith:function(a,b){var c=null,d=b&&b.mutual,e,f,g,h,k,l;if(a instanceof OpenLayers.Geometry.LineString){l=[];k=[a];for(var m=0,p=this.components.length;m<p;++m){g=!1;f=this.components[m];for(var n=0;n<k.length;++n)if(e=k[n].split(f,b)){d&&(g=e[0],g.length&&(g.unshift(n,
+1),Array.prototype.splice.apply(k,g),n+=g.length-2),e=e[1],0===e.length&&(e=[f.clone()]));g=0;for(var q=e.length;g<q;++g)0===g&&l.length?l[l.length-1].addComponent(e[g]):l.push(new OpenLayers.Geometry.MultiLineString([e[g]]));g=!0}g||(l.length?l[l.length-1].addComponent(f.clone()):l=[new OpenLayers.Geometry.MultiLineString([f.clone()])])}}else c=a.split(this);k&&1<k.length?h=!0:k=[];l&&1<l.length?g=!0:l=[];if(h||g)c=d?[k,l]:l;return c},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});OpenLayers.Format=OpenLayers.Class({options:null,externalProjection:null,internalProjection:null,data:null,keepData:!1,initialize:function(a){OpenLayers.Util.extend(this,a);this.options=a},destroy:function(){},read:function(a){throw Error("Read not implemented.");},write:function(a){throw Error("Write not implemented.");},CLASS_NAME:"OpenLayers.Format"});OpenLayers.Format.XML=OpenLayers.Class(OpenLayers.Format,{namespaces:null,namespaceAlias:null,defaultPrefix:null,readers:{},writers:{},xmldom:null,initialize:function(a){window.ActiveXObject&&(this.xmldom=new ActiveXObject("Microsoft.XMLDOM"));OpenLayers.Format.prototype.initialize.apply(this,[a]);this.namespaces=OpenLayers.Util.extend({},this.namespaces);this.namespaceAlias={};for(var b in this.namespaces)this.namespaceAlias[this.namespaces[b]]=b},destroy:function(){this.xmldom=null;OpenLayers.Format.prototype.destroy.apply(this,
+arguments)},setNamespace:function(a,b){this.namespaces[a]=b;this.namespaceAlias[b]=a},read:function(a){var b=a.indexOf("<");0<b&&(a=a.substring(b));b=OpenLayers.Util.Try(OpenLayers.Function.bind(function(){var b;b=window.ActiveXObject&&!this.xmldom?new ActiveXObject("Microsoft.XMLDOM"):this.xmldom;b.loadXML(a);return b},this),function(){return(new DOMParser).parseFromString(a,"text/xml")},function(){var b=new XMLHttpRequest;b.open("GET","data:text/xml;charset=utf-8,"+encodeURIComponent(a),!1);b.overrideMimeType&&
+b.overrideMimeType("text/xml");b.send(null);return b.responseXML});this.keepData&&(this.data=b);return b},write:function(a){if(this.xmldom)a=a.xml;else{var b=new XMLSerializer;if(1==a.nodeType){var c=document.implementation.createDocument("","",null);c.importNode&&(a=c.importNode(a,!0));c.appendChild(a);a=b.serializeToString(c)}else a=b.serializeToString(a)}return a},createElementNS:function(a,b){return this.xmldom?"string"==typeof a?this.xmldom.createNode(1,b,a):this.xmldom.createNode(1,b,""):document.createElementNS(a,
+b)},createDocumentFragment:function(){return this.xmldom?this.xmldom.createDocumentFragment():document.createDocumentFragment()},createTextNode:function(a){"string"!==typeof a&&(a=String(a));return this.xmldom?this.xmldom.createTextNode(a):document.createTextNode(a)},getElementsByTagNameNS:function(a,b,c){var d=[];if(a.getElementsByTagNameNS)d=a.getElementsByTagNameNS(b,c);else{a=a.getElementsByTagName("*");for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],f=e.prefix?e.prefix+":"+c:c,"*"==c||f==e.nodeName)"*"!=
+b&&b!=e.namespaceURI||d.push(e)}return d},getAttributeNodeNS:function(a,b,c){var d=null;if(a.getAttributeNodeNS)d=a.getAttributeNodeNS(b,c);else{a=a.attributes;for(var e,f,g=0,h=a.length;g<h;++g)if(e=a[g],e.namespaceURI==b&&(f=e.prefix?e.prefix+":"+c:c,f==e.nodeName)){d=e;break}}return d},getAttributeNS:function(a,b,c){var d="";if(a.getAttributeNS)d=a.getAttributeNS(b,c)||"";else if(a=this.getAttributeNodeNS(a,b,c))d=a.nodeValue;return d},getChildValue:function(a,b){var c=b||"";if(a)for(var d=a.firstChild;d;d=
+d.nextSibling)switch(d.nodeType){case 3:case 4:c+=d.nodeValue}return c},isSimpleContent:function(a){var b=!0;for(a=a.firstChild;a;a=a.nextSibling)if(1===a.nodeType){b=!1;break}return b},contentType:function(a){var b=!1,c=!1,d=OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;for(a=a.firstChild;a;a=a.nextSibling){switch(a.nodeType){case 1:c=!0;break;case 8:break;default:b=!0}if(c&&b)break}if(c&&b)d=OpenLayers.Format.XML.CONTENT_TYPE.MIXED;else{if(c)return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;if(b)return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE}return d},
+hasAttributeNS:function(a,b,c){var d=!1;return d=a.hasAttributeNS?a.hasAttributeNS(b,c):!!this.getAttributeNodeNS(a,b,c)},setAttributeNS:function(a,b,c,d){if(a.setAttributeNS)a.setAttributeNS(b,c,d);else if(this.xmldom)b?(b=a.ownerDocument.createNode(2,c,b),b.nodeValue=d,a.setAttributeNode(b)):a.setAttribute(c,d);else throw"setAttributeNS not implemented";},createElementNSPlus:function(a,b){b=b||{};var c=b.uri||this.namespaces[b.prefix];c||(c=a.indexOf(":"),c=this.namespaces[a.substring(0,c)]);c||
+(c=this.namespaces[this.defaultPrefix]);c=this.createElementNS(c,a);b.attributes&&this.setAttributes(c,b.attributes);var d=b.value;null!=d&&c.appendChild(this.createTextNode(d));return c},setAttributes:function(a,b){var c,d,e;for(e in b)null!=b[e]&&b[e].toString&&(c=b[e].toString(),d=this.namespaces[e.substring(0,e.indexOf(":"))]||null,this.setAttributeNS(a,d,e,c))},readNode:function(a,b){b||(b={});var c=this.readers[a.namespaceURI?this.namespaceAlias[a.namespaceURI]:this.defaultPrefix];if(c){var d=
+a.localName||a.nodeName.split(":").pop();(c=c[d]||c["*"])&&c.apply(this,[a,b])}return b},readChildNodes:function(a,b){b||(b={});for(var c=a.childNodes,d,e=0,f=c.length;e<f;++e)d=c[e],1==d.nodeType&&this.readNode(d,b);return b},writeNode:function(a,b,c){var d,e=a.indexOf(":");0<e?(d=a.substring(0,e),a=a.substring(e+1)):d=c?this.namespaceAlias[c.namespaceURI]:this.defaultPrefix;b=this.writers[d][a].apply(this,[b]);c&&c.appendChild(b);return b},getChildEl:function(a,b,c){return a&&this.getThisOrNextEl(a.firstChild,
+b,c)},getNextEl:function(a,b,c){return a&&this.getThisOrNextEl(a.nextSibling,b,c)},getThisOrNextEl:function(a,b,c){a:for(;a;a=a.nextSibling)switch(a.nodeType){case 1:if(!(b&&b!==(a.localName||a.nodeName.split(":").pop())||c&&c!==a.namespaceURI))break a;a=null;break a;case 3:if(/^\s*$/.test(a.nodeValue))break;case 4:case 6:case 12:case 10:case 11:a=null;break a}return a||null},lookupNamespaceURI:function(a,b){var c=null;if(a)if(a.lookupNamespaceURI)c=a.lookupNamespaceURI(b);else a:switch(a.nodeType){case 1:if(null!==
+a.namespaceURI&&a.prefix===b){c=a.namespaceURI;break a}if(c=a.attributes.length)for(var d,e=0;e<c;++e)if(d=a.attributes[e],"xmlns"===d.prefix&&d.name==="xmlns:"+b){c=d.value||null;break a}else if("xmlns"===d.name&&null===b){c=d.value||null;break a}c=this.lookupNamespaceURI(a.parentNode,b);break a;case 2:c=this.lookupNamespaceURI(a.ownerElement,b);break a;case 9:c=this.lookupNamespaceURI(a.documentElement,b);break a;case 6:case 12:case 10:case 11:break a;default:c=this.lookupNamespaceURI(a.parentNode,
+b)}return c},getXMLDoc:function(){OpenLayers.Format.XML.document||this.xmldom||(document.implementation&&document.implementation.createDocument?OpenLayers.Format.XML.document=document.implementation.createDocument("","",null):!this.xmldom&&window.ActiveXObject&&(this.xmldom=new ActiveXObject("Microsoft.XMLDOM")));return OpenLayers.Format.XML.document||this.xmldom},CLASS_NAME:"OpenLayers.Format.XML"});OpenLayers.Format.XML.CONTENT_TYPE={EMPTY:0,SIMPLE:1,COMPLEX:2,MIXED:3};
+OpenLayers.Format.XML.lookupNamespaceURI=OpenLayers.Function.bind(OpenLayers.Format.XML.prototype.lookupNamespaceURI,OpenLayers.Format.XML.prototype);OpenLayers.Format.XML.document=null;OpenLayers.Format.OGCExceptionReport=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc"},regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g},defaultPrefix:"ogc",read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var b={exceptionReport:null};a.documentElement&&(this.readChildNodes(a,b),null===b.exceptionReport&&(b=(new OpenLayers.Format.OWSCommon).read(a)));return b},readers:{ogc:{ServiceExceptionReport:function(a,
+b){b.exceptionReport={exceptions:[]};this.readChildNodes(a,b.exceptionReport)},ServiceException:function(a,b){var c={code:a.getAttribute("code"),locator:a.getAttribute("locator"),text:this.getChildValue(a)};b.exceptions.push(c)}}},CLASS_NAME:"OpenLayers.Format.OGCExceptionReport"});OpenLayers.Format.XML.VersionedOGC=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:null,version:null,profile:null,allowFallback:!1,name:null,stringifyOutput:!1,parser:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);a=this.CLASS_NAME;this.name=a.substring(a.lastIndexOf(".")+1)},getVersion:function(a,b){var c;a?(c=this.version,c||(c=a.getAttribute("version"),c||(c=this.defaultVersion))):c=b&&b.version||this.version||this.defaultVersion;return c},getParser:function(a){a=
+a||this.defaultVersion;var b=this.profile?"_"+this.profile:"";if(!this.parser||this.parser.VERSION!=a){var c=OpenLayers.Format[this.name]["v"+a.replace(/\./g,"_")+b];if(!c&&(""!==b&&this.allowFallback&&(b="",c=OpenLayers.Format[this.name]["v"+a.replace(/\./g,"_")]),!c))throw"Can't find a "+this.name+" parser for version "+a+b;this.parser=new c(this.options)}return this.parser},write:function(a,b){var c=this.getVersion(null,b);this.parser=this.getParser(c);c=this.parser.write(a,b);return!1===this.stringifyOutput?
+c:OpenLayers.Format.XML.prototype.write.apply(this,[c])},read:function(a,b){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));var c=this.getVersion(a.documentElement);this.parser=this.getParser(c);var d=this.parser.read(a,b),e=this.parser.errorProperty||null;null!==e&&void 0===d[e]&&(e=new OpenLayers.Format.OGCExceptionReport,d.error=e.read(a));d.version=c;return d},CLASS_NAME:"OpenLayers.Format.XML.VersionedOGC"});OpenLayers.Feature=OpenLayers.Class({layer:null,id:null,lonlat:null,data:null,marker:null,popupClass:null,popup:null,initialize:function(a,b,c){this.layer=a;this.lonlat=b;this.data=null!=c?c:{};this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){null!=this.layer&&null!=this.layer.map&&null!=this.popup&&this.layer.map.removePopup(this.popup);null!=this.layer&&null!=this.marker&&this.layer.removeMarker(this.marker);this.data=this.lonlat=this.id=this.layer=null;null!=this.marker&&
+(this.destroyMarker(this.marker),this.marker=null);null!=this.popup&&(this.destroyPopup(this.popup),this.popup=null)},onScreen:function(){var a=!1;null!=this.layer&&null!=this.layer.map&&(a=this.layer.map.getExtent().containsLonLat(this.lonlat));return a},createMarker:function(){null!=this.lonlat&&(this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon));return this.marker},destroyMarker:function(){this.marker.destroy()},createPopup:function(a){null!=this.lonlat&&(this.popup||(this.popup=new (this.popupClass?
+this.popupClass:OpenLayers.Popup.Anchored)(this.id+"_popup",this.lonlat,this.data.popupSize,this.data.popupContentHTML,this.marker?this.marker.icon:null,a)),null!=this.data.overflow&&(this.popup.contentDiv.style.overflow=this.data.overflow),this.popup.feature=this);return this.popup},destroyPopup:function(){this.popup&&(this.popup.feature=null,this.popup.destroy(),this.popup=null)},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.State={UNKNOWN:"Unknown",INSERT:"Insert",UPDATE:"Update",DELETE:"Delete"};
+OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,bounds:null,state:null,style:null,url:null,renderIntent:"default",modified:null,initialize:function(a,b,c){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,b]);this.lonlat=null;this.geometry=a?a:null;this.state=null;this.attributes={};b&&(this.attributes=OpenLayers.Util.extend(this.attributes,b));this.style=c?c:null},destroy:function(){this.layer&&(this.layer.removeFeatures(this),this.layer=
+null);this.modified=this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments)},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style)},onScreen:function(a){var b=!1;this.layer&&this.layer.map&&(b=this.layer.map.getExtent(),a?(a=this.geometry.getBounds(),b=b.intersectsBounds(a)):b=b.toGeometry().intersects(this.geometry));return b},getVisibility:function(){return!(this.style&&"none"==this.style.display||!this.layer||
+this.layer&&this.layer.styleMap&&"none"==this.layer.styleMap.createSymbolizer(this,this.renderIntent).display||this.layer&&!this.layer.getVisibility())},createMarker:function(){return null},destroyMarker:function(){},createPopup:function(){return null},atPoint:function(a,b,c){var d=!1;this.geometry&&(d=this.geometry.atPoint(a,b,c));return d},destroyPopup:function(){},move:function(a){if(this.layer&&this.geometry.move){a="OpenLayers.LonLat"==a.CLASS_NAME?this.layer.getViewPortPxFromLonLat(a):a;var b=
+this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()),c=this.layer.map.getResolution();this.geometry.move(c*(a.x-b.x),c*(b.y-a.y));this.layer.drawFeature(this);return b}},toState:function(a){if(a==OpenLayers.State.UPDATE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=a}else if(a==OpenLayers.State.INSERT)switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=a}else if(a==OpenLayers.State.DELETE)switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.UPDATE:this.state=
+a}else a==OpenLayers.State.UNKNOWN&&(this.state=a)},CLASS_NAME:"OpenLayers.Feature.Vector"});
+OpenLayers.Feature.Vector.style={"default":{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},select:{fillColor:"blue",fillOpacity:0.4,
+hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},temporary:{fillColor:"#66cccc",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#66cccc",strokeOpacity:1,
+strokeLinecap:"round",strokeWidth:2,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit",fontColor:"#000000",labelAlign:"cm",labelOutlineColor:"white",labelOutlineWidth:3},"delete":{display:"none"}};OpenLayers.Style=OpenLayers.Class({id:null,name:null,title:null,description:null,layerName:null,isDefault:!1,rules:null,context:null,defaultStyle:null,defaultsPerSymbolizer:!1,propertyStyles:null,initialize:function(a,b){OpenLayers.Util.extend(this,b);this.rules=[];b&&b.rules&&this.addRules(b.rules);this.setDefaultStyle(a||OpenLayers.Feature.Vector.style["default"]);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_")},destroy:function(){for(var a=0,b=this.rules.length;a<b;a++)this.rules[a].destroy(),
+this.rules[a]=null;this.defaultStyle=this.rules=null},createSymbolizer:function(a){for(var b=this.defaultsPerSymbolizer?{}:this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),a),c=this.rules,d,e=[],f=!1,g=0,h=c.length;g<h;g++)d=c[g],d.evaluate(a)&&(d instanceof OpenLayers.Rule&&d.elseFilter?e.push(d):(f=!0,this.applySymbolizer(d,b,a)));if(!1==f&&0<e.length)for(f=!0,g=0,h=e.length;g<h;g++)this.applySymbolizer(e[g],b,a);0<c.length&&!1==f&&(b.display="none");null!=b.label&&"string"!==typeof b.label&&
+(b.label=String(b.label));return b},applySymbolizer:function(a,b,c){var d=c.geometry?this.getSymbolizerPrefix(c.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];a=a.symbolizer[d]||a.symbolizer;!0===this.defaultsPerSymbolizer&&(d=this.defaultStyle,OpenLayers.Util.applyDefaults(a,{pointRadius:d.pointRadius}),!0!==a.stroke&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{strokeWidth:d.strokeWidth,strokeColor:d.strokeColor,strokeOpacity:d.strokeOpacity,strokeDashstyle:d.strokeDashstyle,strokeLinecap:d.strokeLinecap}),
+!0!==a.fill&&!0!==a.graphic||OpenLayers.Util.applyDefaults(a,{fillColor:d.fillColor,fillOpacity:d.fillOpacity}),!0===a.graphic&&OpenLayers.Util.applyDefaults(a,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOffset:this.defaultStyle.graphicYOffset}));
+return this.createLiterals(OpenLayers.Util.extend(b,a),c)},createLiterals:function(a,b){var c=OpenLayers.Util.extend({},b.attributes||b.data);OpenLayers.Util.extend(c,this.context);for(var d in this.propertyStyles)a[d]=OpenLayers.Style.createLiteral(a[d],c,b,d);return a},findPropertyStyles:function(){var a={};this.addPropertyStyles(a,this.defaultStyle);for(var b=this.rules,c,d,e=0,f=b.length;e<f;e++){c=b[e].symbolizer;for(var g in c)if(d=c[g],"object"==typeof d)this.addPropertyStyles(a,d);else{this.addPropertyStyles(a,
+c);break}}return a},addPropertyStyles:function(a,b){var c,d;for(d in b)c=b[d],"string"==typeof c&&c.match(/\$\{\w+\}/)&&(a[d]=!0);return a},addRules:function(a){Array.prototype.push.apply(this.rules,a);this.propertyStyles=this.findPropertyStyles()},setDefaultStyle:function(a){this.defaultStyle=a;this.propertyStyles=this.findPropertyStyles()},getSymbolizerPrefix:function(a){for(var b=OpenLayers.Style.SYMBOLIZER_PREFIXES,c=0,d=b.length;c<d;c++)if(-1!=a.CLASS_NAME.indexOf(b[c]))return b[c]},clone:function(){var a=
+OpenLayers.Util.extend({},this);if(this.rules){a.rules=[];for(var b=0,c=this.rules.length;b<c;++b)a.rules.push(this.rules[b].clone())}a.context=this.context&&OpenLayers.Util.extend({},this.context);b=OpenLayers.Util.extend({},this.defaultStyle);return new OpenLayers.Style(b,a)},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(a,b,c,d){"string"==typeof a&&-1!=a.indexOf("${")&&(a=OpenLayers.String.format(a,b,[c,d]),a=isNaN(a)||!a?a:parseFloat(a));return a};
+OpenLayers.Style.SYMBOLIZER_PREFIXES=["Point","Line","Polygon","Text","Raster"];OpenLayers.Filter=OpenLayers.Class({initialize:function(a){OpenLayers.Util.extend(this,a)},destroy:function(){},evaluate:function(a){return!0},clone:function(){return null},toString:function(){return OpenLayers.Format&&OpenLayers.Format.CQL?OpenLayers.Format.CQL.prototype.write(this):Object.prototype.toString.call(this)},CLASS_NAME:"OpenLayers.Filter"});OpenLayers.Filter.FeatureId=OpenLayers.Class(OpenLayers.Filter,{fids:null,type:"FID",initialize:function(a){this.fids=[];OpenLayers.Filter.prototype.initialize.apply(this,[a])},evaluate:function(a){for(var b=0,c=this.fids.length;b<c;b++)if((a.fid||a.id)==this.fids[b])return!0;return!1},clone:function(){var a=new OpenLayers.Filter.FeatureId;OpenLayers.Util.extend(a,this);a.fids=this.fids.slice();return a},CLASS_NAME:"OpenLayers.Filter.FeatureId"});OpenLayers.Filter.Logical=OpenLayers.Class(OpenLayers.Filter,{filters:null,type:null,initialize:function(a){this.filters=[];OpenLayers.Filter.prototype.initialize.apply(this,[a])},destroy:function(){this.filters=null;OpenLayers.Filter.prototype.destroy.apply(this)},evaluate:function(a){var b,c;switch(this.type){case OpenLayers.Filter.Logical.AND:b=0;for(c=this.filters.length;b<c;b++)if(!1==this.filters[b].evaluate(a))return!1;return!0;case OpenLayers.Filter.Logical.OR:b=0;for(c=this.filters.length;b<
+c;b++)if(!0==this.filters[b].evaluate(a))return!0;return!1;case OpenLayers.Filter.Logical.NOT:return!this.filters[0].evaluate(a)}},clone:function(){for(var a=[],b=0,c=this.filters.length;b<c;++b)a.push(this.filters[b].clone());return new OpenLayers.Filter.Logical({type:this.type,filters:a})},CLASS_NAME:"OpenLayers.Filter.Logical"});OpenLayers.Filter.Logical.AND="&&";OpenLayers.Filter.Logical.OR="||";OpenLayers.Filter.Logical.NOT="!";OpenLayers.Filter.Comparison=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,matchCase:!0,lowerBoundary:null,upperBoundary:null,initialize:function(a){OpenLayers.Filter.prototype.initialize.apply(this,[a]);this.type===OpenLayers.Filter.Comparison.LIKE&&void 0===a.matchCase&&(this.matchCase=null)},evaluate:function(a){a instanceof OpenLayers.Feature.Vector&&(a=a.attributes);var b=!1;a=a[this.property];switch(this.type){case OpenLayers.Filter.Comparison.EQUAL_TO:b=this.value;
+b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a==b:a.toUpperCase()==b.toUpperCase();break;case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:b=this.value;b=this.matchCase||"string"!=typeof a||"string"!=typeof b?a!=b:a.toUpperCase()!=b.toUpperCase();break;case OpenLayers.Filter.Comparison.LESS_THAN:b=a<this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN:b=a>this.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:b=a<=this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:b=
+a>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:b=a>=this.lowerBoundary&&a<=this.upperBoundary;break;case OpenLayers.Filter.Comparison.LIKE:b=RegExp(this.value,"gi").test(a);break;case OpenLayers.Filter.Comparison.IS_NULL:b=null===a}return b},value2regex:function(a,b,c){if("."==a)throw Error("'.' is an unsupported wildCard character for OpenLayers.Filter.Comparison");a=a?a:"*";b=b?b:".";this.value=this.value.replace(RegExp("\\"+(c?c:"!")+"(.|$)","g"),"\\$1");this.value=this.value.replace(RegExp("\\"+
+b,"g"),".");this.value=this.value.replace(RegExp("\\"+a,"g"),".*");this.value=this.value.replace(RegExp("\\\\.\\*","g"),"\\"+a);return this.value=this.value.replace(RegExp("\\\\\\.","g"),"\\"+b)},regex2value:function(){var a=this.value,a=a.replace(/!/g,"!!"),a=a.replace(/(\\)?\\\./g,function(a,c){return c?a:"!."}),a=a.replace(/(\\)?\\\*/g,function(a,c){return c?a:"!*"}),a=a.replace(/\\\\/g,"\\");return a=a.replace(/\.\*/g,"*")},clone:function(){return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison,
+this)},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.Comparison.IS_NULL="NULL";OpenLayers.Format.Filter=OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC,{defaultVersion:"1.0.0",CLASS_NAME:"OpenLayers.Format.Filter"});OpenLayers.Format.WFST=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Format.WFST.DEFAULTS);var b=OpenLayers.Format.WFST["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported WFST version: "+a.version;return new b(a)};OpenLayers.Format.WFST.DEFAULTS={version:"1.0.0"};OpenLayers.Filter.Spatial=OpenLayers.Class(OpenLayers.Filter,{type:null,property:null,value:null,distance:null,distanceUnits:null,evaluate:function(a){var b=!1;switch(this.type){case OpenLayers.Filter.Spatial.BBOX:case OpenLayers.Filter.Spatial.INTERSECTS:if(a.geometry){var c=this.value;"OpenLayers.Bounds"==this.value.CLASS_NAME&&(c=this.value.toGeometry());a.geometry.intersects(c)&&(b=!0)}break;default:throw Error("evaluate is not implemented for this filter type.");}return b},clone:function(){var a=
+OpenLayers.Util.applyDefaults({value:this.value&&this.value.clone&&this.value.clone()},this);return new OpenLayers.Filter.Spatial(a)},CLASS_NAME:"OpenLayers.Filter.Spatial"});OpenLayers.Filter.Spatial.BBOX="BBOX";OpenLayers.Filter.Spatial.INTERSECTS="INTERSECTS";OpenLayers.Filter.Spatial.DWITHIN="DWITHIN";OpenLayers.Filter.Spatial.WITHIN="WITHIN";OpenLayers.Filter.Spatial.CONTAINS="CONTAINS";OpenLayers.Format.WFST.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs",gml:"http://www.opengis.net/gml",ogc:"http://www.opengis.net/ogc",ows:"http://www.opengis.net/ows"},defaultPrefix:"wfs",version:null,schemaLocations:null,srsName:null,extractAttributes:!0,xy:!0,stateName:null,initialize:function(a){this.stateName={};this.stateName[OpenLayers.State.INSERT]="wfs:Insert";this.stateName[OpenLayers.State.UPDATE]=
+"wfs:Update";this.stateName[OpenLayers.State.DELETE]="wfs:Delete";OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},getSrsName:function(a,b){var c=b&&b.srsName;c||(c=a&&a.layer?a.layer.projection.getCode():this.srsName);return c},read:function(a,b){b=b||{};OpenLayers.Util.applyDefaults(b,{output:"features"});"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var c={};a&&this.readNode(a,c,!0);c.features&&"features"===b.output&&
+(c=c.features);return c},readers:{wfs:{FeatureCollection:function(a,b){b.features=[];this.readChildNodes(a,b)}}},write:function(a,b){var c=this.writeNode("wfs:Transaction",{features:a,options:b}),d=this.schemaLocationAttr();d&&this.setAttributeNS(c,this.namespaces.xsi,"xsi:schemaLocation",d);return OpenLayers.Format.XML.prototype.write.apply(this,[c])},writers:{wfs:{GetFeature:function(a){var b=this.createElementNSPlus("wfs:GetFeature",{attributes:{service:"WFS",version:this.version,handle:a&&a.handle,
+outputFormat:a&&a.outputFormat,maxFeatures:a&&a.maxFeatures,"xsi:schemaLocation":this.schemaLocationAttr(a)}});if("string"==typeof this.featureType)this.writeNode("Query",a,b);else for(var c=0,d=this.featureType.length;c<d;c++)a.featureType=this.featureType[c],this.writeNode("Query",a,b);return b},Transaction:function(a){a=a||{};var b=a.options||{},c=this.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version,handle:b.handle}}),d,e=a.features;if(e){!0===b.multi&&OpenLayers.Util.extend(this.geometryTypes,
+{"OpenLayers.Geometry.Point":"MultiPoint","OpenLayers.Geometry.LineString":!0===this.multiCurve?"MultiCurve":"MultiLineString","OpenLayers.Geometry.Polygon":!0===this.multiSurface?"MultiSurface":"MultiPolygon"});var f,g;a=0;for(d=e.length;a<d;++a)g=e[a],(f=this.stateName[g.state])&&this.writeNode(f,{feature:g,options:b},c);!0===b.multi&&this.setGeometryTypes()}if(b.nativeElements)for(a=0,d=b.nativeElements.length;a<d;++a)this.writeNode("wfs:Native",b.nativeElements[a],c);return c},Native:function(a){return this.createElementNSPlus("wfs:Native",
+{attributes:{vendorId:a.vendorId,safeToIgnore:a.safeToIgnore},value:a.value})},Insert:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Insert",{attributes:{handle:a&&a.handle}});this.srsName=this.getSrsName(b);this.writeNode("feature:_typeName",b,a);return a},Update:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Update",{attributes:{handle:a&&a.handle,typeName:(this.featureNS?this.featurePrefix+":":"")+this.featureType}});this.featureNS&&a.setAttribute("xmlns:"+
+this.featurePrefix,this.featureNS);var c=b.modified;null===this.geometryName||c&&void 0===c.geometry||(this.srsName=this.getSrsName(b),this.writeNode("Property",{name:this.geometryName,value:b.geometry},a));for(var d in b.attributes)void 0===b.attributes[d]||c&&c.attributes&&(!c.attributes||void 0===c.attributes[d])||this.writeNode("Property",{name:d,value:b.attributes[d]},a);this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[b.fid]}),a);return a},Property:function(a){var b=this.createElementNSPlus("wfs:Property");
+this.writeNode("Name",a.name,b);null!==a.value&&this.writeNode("Value",a.value,b);return b},Name:function(a){return this.createElementNSPlus("wfs:Name",{value:a})},Value:function(a){var b;a instanceof OpenLayers.Geometry?(b=this.createElementNSPlus("wfs:Value"),a=this.writeNode("feature:_geometry",a).firstChild,b.appendChild(a)):b=this.createElementNSPlus("wfs:Value",{value:a});return b},Delete:function(a){var b=a.feature;a=a.options;a=this.createElementNSPlus("wfs:Delete",{attributes:{handle:a&&
+a.handle,typeName:(this.featureNS?this.featurePrefix+":":"")+this.featureType}});this.featureNS&&a.setAttribute("xmlns:"+this.featurePrefix,this.featureNS);this.writeNode("ogc:Filter",new OpenLayers.Filter.FeatureId({fids:[b.fid]}),a);return a}}},schemaLocationAttr:function(a){a=OpenLayers.Util.extend({featurePrefix:this.featurePrefix,schema:this.schema},a);var b=OpenLayers.Util.extend({},this.schemaLocations);a.schema&&(b[a.featurePrefix]=a.schema);a=[];var c,d;for(d in b)(c=this.namespaces[d])&&
+a.push(c+" "+b[d]);return a.join(" ")||void 0},setFilterProperty:function(a){if(a.filters)for(var b=0,c=a.filters.length;b<c;++b)OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this,a.filters[b]);else a instanceof OpenLayers.Filter.Spatial&&!a.property&&(a.property=this.geometryName)},CLASS_NAME:"OpenLayers.Format.WFST.v1"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],getArea:function(){var a=0;if(this.components&&0<this.components.length)for(var a=a+Math.abs(this.components[0].getArea()),b=1,c=this.components.length;b<c;b++)a-=Math.abs(this.components[b].getArea());return a},getGeodesicArea:function(a){var b=0;if(this.components&&0<this.components.length)for(var b=b+Math.abs(this.components[0].getGeodesicArea(a)),c=1,d=this.components.length;c<
+d;c++)b-=Math.abs(this.components[c].getGeodesicArea(a));return b},containsPoint:function(a){var b=this.components.length,c=!1;if(0<b&&(c=this.components[0].containsPoint(a),1!==c&&c&&1<b))for(var d,e=1;e<b;++e)if(d=this.components[e].containsPoint(a)){c=1===d?1:!1;break}return c},intersects:function(a){var b=!1,c,d;if("OpenLayers.Geometry.Point"==a.CLASS_NAME)b=this.containsPoint(a);else if("OpenLayers.Geometry.LineString"==a.CLASS_NAME||"OpenLayers.Geometry.LinearRing"==a.CLASS_NAME){c=0;for(d=
+this.components.length;c<d&&!(b=a.intersects(this.components[c]));++c);if(!b)for(c=0,d=a.components.length;c<d&&!(b=this.containsPoint(a.components[c]));++c);}else for(c=0,d=a.components.length;c<d&&!(b=this.intersects(a.components[c]));++c);if(!b&&"OpenLayers.Geometry.Polygon"==a.CLASS_NAME){var e=this.components[0];c=0;for(d=e.components.length;c<d&&!(b=a.containsPoint(e.components[c]));++c);}return b},distanceTo:function(a,b){return b&&!1===b.edge&&this.intersects(a)?0:OpenLayers.Geometry.Collection.prototype.distanceTo.apply(this,
+[a,b])},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(a,b,c,d){var e=Math.PI*(1/c-0.5);d&&(e+=d/180*Math.PI);for(var f,g=[],h=0;h<c;++h)f=e+2*h*Math.PI/c,d=a.x+b*Math.cos(f),f=a.y+b*Math.sin(f),g.push(new OpenLayers.Geometry.Point(d,f));a=new OpenLayers.Geometry.LinearRing(g);return new OpenLayers.Geometry.Polygon([a])};OpenLayers.Geometry.MultiPolygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Polygon"],CLASS_NAME:"OpenLayers.Geometry.MultiPolygon"});OpenLayers.Format.GML=OpenLayers.Class(OpenLayers.Format.XML,{featureNS:"http://mapserver.gis.umn.edu/mapserver",featurePrefix:"feature",featureName:"featureMember",layerName:"features",geometryName:"geometry",collectionName:"FeatureCollection",gmlns:"http://www.opengis.net/gml",extractAttributes:!0,xy:!0,initialize:function(a){this.regExes={trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g};OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){"string"==
+typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a=this.getElementsByTagNameNS(a.documentElement,this.gmlns,this.featureName);for(var b=[],c=0;c<a.length;c++){var d=this.parseFeature(a[c]);d&&b.push(d)}return b},parseFeature:function(a){for(var b="MultiPolygon Polygon MultiLineString LineString MultiPoint Point Envelope".split(" "),c,d,e,f=0;f<b.length;++f)if(c=b[f],d=this.getElementsByTagNameNS(a,this.gmlns,c),0<d.length){if(e=this.parseGeometry[c.toLowerCase()])e=e.apply(this,
+[d[0]]),this.internalProjection&&this.externalProjection&&e.transform(this.externalProjection,this.internalProjection);else throw new TypeError("Unsupported geometry type: "+c);break}var g;c=this.getElementsByTagNameNS(a,this.gmlns,"Box");for(f=0;f<c.length;++f)b=c[f],d=this.parseGeometry.box.apply(this,[b]),b=b.parentNode,"boundedBy"===(b.localName||b.nodeName.split(":").pop())?g=d:e=d.toGeometry();var h;this.extractAttributes&&(h=this.parseAttributes(a));h=new OpenLayers.Feature.Vector(e,h);h.bounds=
+g;h.gml={featureType:a.firstChild.nodeName.split(":")[1],featureNS:a.firstChild.namespaceURI,featureNSPrefix:a.firstChild.prefix};a=a.firstChild;for(var k;a&&(1!=a.nodeType||!(k=a.getAttribute("fid")||a.getAttribute("id")));)a=a.nextSibling;h.fid=k;return h},parseGeometry:{point:function(a){var b,c;c=[];b=this.getElementsByTagNameNS(a,this.gmlns,"pos");0<b.length&&(c=b[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));0==c.length&&(b=this.getElementsByTagNameNS(a,
+this.gmlns,"coordinates"),0<b.length&&(c=b[0].firstChild.nodeValue,c=c.replace(this.regExes.removeSpace,""),c=c.split(",")));0==c.length&&(b=this.getElementsByTagNameNS(a,this.gmlns,"coord"),0<b.length&&(a=this.getElementsByTagNameNS(b[0],this.gmlns,"X"),b=this.getElementsByTagNameNS(b[0],this.gmlns,"Y"),0<a.length&&0<b.length&&(c=[a[0].firstChild.nodeValue,b[0].firstChild.nodeValue])));2==c.length&&(c[2]=null);return this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],
+c[0],c[2])},multipoint:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"Point");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.point.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a,b){var c,d;d=[];var e=[];c=this.getElementsByTagNameNS(a,this.gmlns,"posList");if(0<c.length){d=this.getChildValue(c[0]);d=d.replace(this.regExes.trimSpace,"");d=d.split(this.regExes.splitSpace);var f=parseInt(c[0].getAttribute("dimension")),
+g,h,k;for(c=0;c<d.length/f;++c)g=c*f,h=d[g],k=d[g+1],g=2==f?null:d[g+2],this.xy?e.push(new OpenLayers.Geometry.Point(h,k,g)):e.push(new OpenLayers.Geometry.Point(k,h,g))}if(0==d.length&&(c=this.getElementsByTagNameNS(a,this.gmlns,"coordinates"),0<c.length))for(d=this.getChildValue(c[0]),d=d.replace(this.regExes.trimSpace,""),d=d.replace(this.regExes.trimComma,","),f=d.split(this.regExes.splitSpace),c=0;c<f.length;++c)d=f[c].split(","),2==d.length&&(d[2]=null),this.xy?e.push(new OpenLayers.Geometry.Point(d[0],
+d[1],d[2])):e.push(new OpenLayers.Geometry.Point(d[1],d[0],d[2]));d=null;0!=e.length&&(d=b?new OpenLayers.Geometry.LinearRing(e):new OpenLayers.Geometry.LineString(e));return d},multilinestring:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"LineString");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.linestring.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"LinearRing");
+var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.linestring.apply(this,[a[d],!0]))&&b.push(c);return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){a=this.getElementsByTagNameNS(a,this.gmlns,"Polygon");var b=[];if(0<a.length)for(var c,d=0;d<a.length;++d)(c=this.parseGeometry.polygon.apply(this,[a[d]]))&&b.push(c);return new OpenLayers.Geometry.MultiPolygon(b)},envelope:function(a){var b=[],c,d,e=this.getElementsByTagNameNS(a,this.gmlns,"lowerCorner");if(0<e.length){c=
+[];0<e.length&&(c=e[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));2==c.length&&(c[2]=null);var f=this.xy?new OpenLayers.Geometry.Point(c[0],c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2])}a=this.getElementsByTagNameNS(a,this.gmlns,"upperCorner");if(0<a.length){c=[];0<a.length&&(c=a[0].firstChild.nodeValue,c=c.replace(this.regExes.trimSpace,""),c=c.split(this.regExes.splitSpace));2==c.length&&(c[2]=null);var g=this.xy?new OpenLayers.Geometry.Point(c[0],
+c[1],c[2]):new OpenLayers.Geometry.Point(c[1],c[0],c[2])}f&&g&&(b.push(new OpenLayers.Geometry.Point(f.x,f.y)),b.push(new OpenLayers.Geometry.Point(g.x,f.y)),b.push(new OpenLayers.Geometry.Point(g.x,g.y)),b.push(new OpenLayers.Geometry.Point(f.x,g.y)),b.push(new OpenLayers.Geometry.Point(f.x,f.y)),b=new OpenLayers.Geometry.LinearRing(b),d=new OpenLayers.Geometry.Polygon([b]));return d},box:function(a){var b=this.getElementsByTagNameNS(a,this.gmlns,"coordinates"),c=a=null;0<b.length&&(b=b[0].firstChild.nodeValue,
+b=b.split(" "),2==b.length&&(a=b[0].split(","),c=b[1].split(",")));if(null!==a&&null!==c)return new OpenLayers.Bounds(parseFloat(a[0]),parseFloat(a[1]),parseFloat(c[0]),parseFloat(c[1]))}},parseAttributes:function(a){var b={};a=a.firstChild;for(var c,d,e;a;){if(1==a.nodeType){a=a.childNodes;for(c=0;c<a.length;++c)if(d=a[c],1==d.nodeType)if(e=d.childNodes,1==e.length){if(e=e[0],3==e.nodeType||4==e.nodeType)d=d.prefix?d.nodeName.split(":")[1]:d.nodeName,e=e.nodeValue.replace(this.regExes.trimSpace,
+""),b[d]=e}else b[d.nodeName.split(":").pop()]=null;break}a=a.nextSibling}return b},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=this.createElementNS("http://www.opengis.net/wfs","wfs:"+this.collectionName),c=0;c<a.length;c++)b.appendChild(this.createFeatureXML(a[c]));return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFeatureXML:function(a){var b=this.buildGeometryNode(a.geometry),c=this.createElementNS(this.featureNS,this.featurePrefix+":"+this.geometryName);c.appendChild(b);
+var b=this.createElementNS(this.gmlns,"gml:"+this.featureName),d=this.createElementNS(this.featureNS,this.featurePrefix+":"+this.layerName);d.setAttribute("fid",a.fid||a.id);d.appendChild(c);for(var e in a.attributes){var c=this.createTextNode(a.attributes[e]),f=e.substring(e.lastIndexOf(":")+1),f=this.createElementNS(this.featureNS,this.featurePrefix+":"+f);f.appendChild(c);d.appendChild(f)}b.appendChild(d);return b},buildGeometryNode:function(a){this.externalProjection&&this.internalProjection&&
+(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME,b=b.substring(b.lastIndexOf(".")+1);return this.buildGeometry[b.toLowerCase()].apply(this,[a])},buildGeometry:{point:function(a){var b=this.createElementNS(this.gmlns,"gml:Point");b.appendChild(this.buildCoordinatesNode(a));return b},multipoint:function(a){var b=this.createElementNS(this.gmlns,"gml:MultiPoint");a=a.components;for(var c,d,e=0;e<a.length;e++)c=this.createElementNS(this.gmlns,"gml:pointMember"),
+d=this.buildGeometry.point.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},linestring:function(a){var b=this.createElementNS(this.gmlns,"gml:LineString");b.appendChild(this.buildCoordinatesNode(a));return b},multilinestring:function(a){var b=this.createElementNS(this.gmlns,"gml:MultiLineString");a=a.components;for(var c,d,e=0;e<a.length;++e)c=this.createElementNS(this.gmlns,"gml:lineStringMember"),d=this.buildGeometry.linestring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);
+return b},linearring:function(a){var b=this.createElementNS(this.gmlns,"gml:LinearRing");b.appendChild(this.buildCoordinatesNode(a));return b},polygon:function(a){var b=this.createElementNS(this.gmlns,"gml:Polygon");a=a.components;for(var c,d,e=0;e<a.length;++e)c=0==e?"outerBoundaryIs":"innerBoundaryIs",c=this.createElementNS(this.gmlns,"gml:"+c),d=this.buildGeometry.linearring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},multipolygon:function(a){var b=this.createElementNS(this.gmlns,
+"gml:MultiPolygon");a=a.components;for(var c,d,e=0;e<a.length;++e)c=this.createElementNS(this.gmlns,"gml:polygonMember"),d=this.buildGeometry.polygon.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},bounds:function(a){var b=this.createElementNS(this.gmlns,"gml:Box");b.appendChild(this.buildCoordinatesNode(a));return b}},buildCoordinatesNode:function(a){var b=this.createElementNS(this.gmlns,"gml:coordinates");b.setAttribute("decimal",".");b.setAttribute("cs",",");b.setAttribute("ts",
+" ");var c=[];if(a instanceof OpenLayers.Bounds)c.push(a.left+","+a.bottom),c.push(a.right+","+a.top);else{a=a.components?a.components:[a];for(var d=0;d<a.length;d++)c.push(a[d].x+","+a[d].y)}c=this.createTextNode(c.join(" "));b.appendChild(c);return b},CLASS_NAME:"OpenLayers.Format.GML"});OpenLayers.Format.GML||(OpenLayers.Format.GML={});
+OpenLayers.Format.GML.Base=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance",wfs:"http://www.opengis.net/wfs"},defaultPrefix:"gml",schemaLocation:null,featureType:null,featureNS:null,geometryName:"geometry",extractAttributes:!0,srsName:null,xy:!0,geometryTypes:null,singleFeatureType:null,regExes:{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g,featureMember:/^(.*:)?featureMembers?$/},
+initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a]);this.setGeometryTypes();a&&a.featureNS&&this.setNamespace("feature",a.featureNS);this.singleFeatureType=!a||"string"===typeof a.featureType},read:function(a){"string"==typeof a&&(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));a&&9==a.nodeType&&(a=a.documentElement);var b=[];this.readNode(a,{features:b},!0);if(0==b.length){var c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMember");if(c.length){a=
+0;for(var d=c.length;a<d;++a)this.readNode(c[a],{features:b},!0)}else c=this.getElementsByTagNameNS(a,this.namespaces.gml,"featureMembers"),c.length&&this.readNode(c[0],{features:b},!0)}return b},readNode:function(a,b,c){!0===c&&!0===this.autoConfig&&(this.featureType=null,delete this.namespaceAlias[this.featureNS],delete this.namespaces.feature,this.featureNS=null);this.featureNS||(a.prefix in this.namespaces||a.parentNode.namespaceURI!=this.namespaces.gml||!this.regExes.featureMember.test(a.parentNode.nodeName))||
+(this.featureType=a.nodeName.split(":").pop(),this.setNamespace("feature",a.namespaceURI),this.featureNS=a.namespaceURI,this.autoConfig=!0);return OpenLayers.Format.XML.prototype.readNode.apply(this,[a,b])},readers:{gml:{_inherit:function(a,b,c){},featureMember:function(a,b){this.readChildNodes(a,b)},featureMembers:function(a,b){this.readChildNodes(a,b)},name:function(a,b){b.name=this.getChildValue(a)},boundedBy:function(a,b){var c={};this.readChildNodes(a,c);c.components&&0<c.components.length&&
+(b.bounds=c.components[0])},Point:function(a,b){var c={points:[]};this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(c.points[0])},coordinates:function(a,b){for(var c=this.getChildValue(a).replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),c=c.split(this.regExes.splitSpace),d,e=c.length,f=Array(e),g=0;g<e;++g)d=c[g].split(","),f[g]=this.xy?new OpenLayers.Geometry.Point(d[0],d[1],d[2]):new OpenLayers.Geometry.Point(d[1],d[0],d[2]);b.points=f},coord:function(a,
+b){var c={};this.readChildNodes(a,c);b.points||(b.points=[]);b.points.push(new OpenLayers.Geometry.Point(c.x,c.y,c.z))},X:function(a,b){b.x=this.getChildValue(a)},Y:function(a,b){b.y=this.getChildValue(a)},Z:function(a,b){b.z=this.getChildValue(a)},MultiPoint:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiPoint(c.components)]},pointMember:function(a,b){this.readChildNodes(a,b)},LineString:function(a,
+b){var c={};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.LineString(c.points))},MultiLineString:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiLineString(c.components)]},lineStringMember:function(a,b){this.readChildNodes(a,b)},Polygon:function(a,b){var c={outer:null,inner:[]};this.readers.gml._inherit.apply(this,
+[a,c,b]);this.readChildNodes(a,c);c.inner.unshift(c.outer);b.components||(b.components=[]);b.components.push(new OpenLayers.Geometry.Polygon(c.inner))},LinearRing:function(a,b){var c={};this.readers.gml._inherit.apply(this,[a,c]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.LinearRing(c.points)]},MultiPolygon:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.MultiPolygon(c.components)]},
+polygonMember:function(a,b){this.readChildNodes(a,b)},GeometryCollection:function(a,b){var c={components:[]};this.readers.gml._inherit.apply(this,[a,c,b]);this.readChildNodes(a,c);b.components=[new OpenLayers.Geometry.Collection(c.components)]},geometryMember:function(a,b){this.readChildNodes(a,b)}},feature:{"*":function(a,b){var c,d=a.localName||a.nodeName.split(":").pop();b.features?this.singleFeatureType||-1===OpenLayers.Util.indexOf(this.featureType,d)?d===this.featureType&&(c="_typeName"):c=
+"_typeName":0==a.childNodes.length||1==a.childNodes.length&&3==a.firstChild.nodeType?this.extractAttributes&&(c="_attribute"):c="_geometry";c&&this.readers.feature[c].apply(this,[a,b])},_typeName:function(a,b){var c={components:[],attributes:{}};this.readChildNodes(a,c);c.name&&(c.attributes.name=c.name);var d=new OpenLayers.Feature.Vector(c.components[0],c.attributes);this.singleFeatureType||(d.type=a.nodeName.split(":").pop(),d.namespace=a.namespaceURI);var e=a.getAttribute("fid")||this.getAttributeNS(a,
+this.namespaces.gml,"id");e&&(d.fid=e);this.internalProjection&&(this.externalProjection&&d.geometry)&&d.geometry.transform(this.externalProjection,this.internalProjection);c.bounds&&(d.bounds=c.bounds);b.features.push(d)},_geometry:function(a,b){this.geometryName||(this.geometryName=a.nodeName.split(":").pop());this.readChildNodes(a,b)},_attribute:function(a,b){var c=a.localName||a.nodeName.split(":").pop(),d=this.getChildValue(a);b.attributes[c]=d}},wfs:{FeatureCollection:function(a,b){this.readChildNodes(a,
+b)}}},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"featureMembers":"featureMember";a=this.writeNode("gml:"+b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:{featureMember:function(a){var b=this.createElementNSPlus("gml:featureMember");this.writeNode("feature:_typeName",a,b);return b},MultiPoint:function(a){var b=this.createElementNSPlus("gml:MultiPoint");a=a.components||[a];
+for(var c=0,d=a.length;c<d;++c)this.writeNode("pointMember",a[c],b);return b},pointMember:function(a){var b=this.createElementNSPlus("gml:pointMember");this.writeNode("Point",a,b);return b},MultiLineString:function(a){var b=this.createElementNSPlus("gml:MultiLineString");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("lineStringMember",a[c],b);return b},lineStringMember:function(a){var b=this.createElementNSPlus("gml:lineStringMember");this.writeNode("LineString",a,b);return b},
+MultiPolygon:function(a){var b=this.createElementNSPlus("gml:MultiPolygon");a=a.components||[a];for(var c=0,d=a.length;c<d;++c)this.writeNode("polygonMember",a[c],b);return b},polygonMember:function(a){var b=this.createElementNSPlus("gml:polygonMember");this.writeNode("Polygon",a,b);return b},GeometryCollection:function(a){for(var b=this.createElementNSPlus("gml:GeometryCollection"),c=0,d=a.components.length;c<d;++c)this.writeNode("geometryMember",a.components[c],b);return b},geometryMember:function(a){var b=
+this.createElementNSPlus("gml:geometryMember");a=this.writeNode("feature:_geometry",a);b.appendChild(a.firstChild);return b}},feature:{_typeName:function(a){var b=this.createElementNSPlus("feature:"+this.featureType,{attributes:{fid:a.fid}});a.geometry&&this.writeNode("feature:_geometry",a.geometry,b);for(var c in a.attributes){var d=a.attributes[c];null!=d&&this.writeNode("feature:_attribute",{name:c,value:d},b)}return b},_geometry:function(a){this.externalProjection&&this.internalProjection&&(a=
+a.clone().transform(this.internalProjection,this.externalProjection));var b=this.createElementNSPlus("feature:"+this.geometryName);a=this.writeNode("gml:"+this.geometryTypes[a.CLASS_NAME],a,b);this.srsName&&a.setAttribute("srsName",this.srsName);return b},_attribute:function(a){return this.createElementNSPlus("feature:"+a.name,{value:a.value})}},wfs:{FeatureCollection:function(a){for(var b=this.createElementNSPlus("wfs:FeatureCollection"),c=0,d=a.length;c<d;++c)this.writeNode("gml:featureMember",
+a[c],b);return b}}},setGeometryTypes:function(){this.geometryTypes={"OpenLayers.Geometry.Point":"Point","OpenLayers.Geometry.MultiPoint":"MultiPoint","OpenLayers.Geometry.LineString":"LineString","OpenLayers.Geometry.MultiLineString":"MultiLineString","OpenLayers.Geometry.Polygon":"Polygon","OpenLayers.Geometry.MultiPolygon":"MultiPolygon","OpenLayers.Geometry.Collection":"GeometryCollection"}},CLASS_NAME:"OpenLayers.Format.GML.Base"});OpenLayers.Format.GML.v2=OpenLayers.Class(OpenLayers.Format.GML.Base,{schemaLocation:"http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",initialize:function(a){OpenLayers.Format.GML.Base.prototype.initialize.apply(this,[a])},readers:{gml:OpenLayers.Util.applyDefaults({outerBoundaryIs:function(a,b){var c={};this.readChildNodes(a,c);b.outer=c.components[0]},innerBoundaryIs:function(a,b){var c={};this.readChildNodes(a,c);b.inner.push(c.components[0])},Box:function(a,b){var c=
+{};this.readChildNodes(a,c);b.components||(b.components=[]);var d=c.points[0],c=c.points[1];b.components.push(new OpenLayers.Bounds(d.x,d.y,c.x,c.y))}},OpenLayers.Format.GML.Base.prototype.readers.gml),feature:OpenLayers.Format.GML.Base.prototype.readers.feature,wfs:OpenLayers.Format.GML.Base.prototype.readers.wfs},write:function(a){var b;b=OpenLayers.Util.isArray(a)?"wfs:FeatureCollection":"gml:featureMember";a=this.writeNode(b,a);this.setAttributeNS(a,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);
+return OpenLayers.Format.XML.prototype.write.apply(this,[a])},writers:{gml:OpenLayers.Util.applyDefaults({Point:function(a){var b=this.createElementNSPlus("gml:Point");this.writeNode("coordinates",[a],b);return b},coordinates:function(a){for(var b=a.length,c=Array(b),d,e=0;e<b;++e)d=a[e],c[e]=this.xy?d.x+","+d.y:d.y+","+d.x,void 0!=d.z&&(c[e]+=","+d.z);return this.createElementNSPlus("gml:coordinates",{attributes:{decimal:".",cs:",",ts:" "},value:1==b?c[0]:c.join(" ")})},LineString:function(a){var b=
+this.createElementNSPlus("gml:LineString");this.writeNode("coordinates",a.components,b);return b},Polygon:function(a){var b=this.createElementNSPlus("gml:Polygon");this.writeNode("outerBoundaryIs",a.components[0],b);for(var c=1;c<a.components.length;++c)this.writeNode("innerBoundaryIs",a.components[c],b);return b},outerBoundaryIs:function(a){var b=this.createElementNSPlus("gml:outerBoundaryIs");this.writeNode("LinearRing",a,b);return b},innerBoundaryIs:function(a){var b=this.createElementNSPlus("gml:innerBoundaryIs");
+this.writeNode("LinearRing",a,b);return b},LinearRing:function(a){var b=this.createElementNSPlus("gml:LinearRing");this.writeNode("coordinates",a.components,b);return b},Box:function(a){var b=this.createElementNSPlus("gml:Box");this.writeNode("coordinates",[{x:a.left,y:a.bottom},{x:a.right,y:a.top}],b);this.srsName&&b.setAttribute("srsName",this.srsName);return b}},OpenLayers.Format.GML.Base.prototype.writers.gml),feature:OpenLayers.Format.GML.Base.prototype.writers.feature,wfs:OpenLayers.Format.GML.Base.prototype.writers.wfs},
+CLASS_NAME:"OpenLayers.Format.GML.v2"});OpenLayers.Filter.Function=OpenLayers.Class(OpenLayers.Filter,{name:null,params:null,CLASS_NAME:"OpenLayers.Filter.Function"});OpenLayers.Date={dateRegEx:/^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,toISOString:function(){return"toISOString"in Date.prototype?function(a){return a.toISOString()}:function(a){return isNaN(a.getTime())?"Invalid Date":a.getUTCFullYear()+"-"+OpenLayers.Number.zeroPad(a.getUTCMonth()+1,2)+"-"+OpenLayers.Number.zeroPad(a.getUTCDate(),2)+"T"+OpenLayers.Number.zeroPad(a.getUTCHours(),2)+":"+OpenLayers.Number.zeroPad(a.getUTCMinutes(),
+2)+":"+OpenLayers.Number.zeroPad(a.getUTCSeconds(),2)+"."+OpenLayers.Number.zeroPad(a.getUTCMilliseconds(),3)+"Z"}}(),parse:function(a){var b;if((a=a.match(this.dateRegEx))&&(a[1]||a[7])){b=parseInt(a[1],10)||0;var c=parseInt(a[2],10)-1||0,d=parseInt(a[3],10)||1;b=new Date(Date.UTC(b,c,d));if(c=a[7]){var d=parseInt(a[4],10),e=parseInt(a[5],10),f=parseFloat(a[6]),g=f|0,f=Math.round(1E3*(f-g));b.setUTCHours(d,e,g,f);"Z"!==c&&(c=parseInt(c,10),a=parseInt(a[8],10)||0,a=-1E3*(60*60*c+60*a),b=new Date(b.getTime()+
+a))}}else b=new Date("invalid");return b}};OpenLayers.Format.Filter.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"ogc",schemaLocation:null,initialize:function(a){OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){var b={};this.readers.ogc.Filter.apply(this,[a,b]);return b.filter},readers:{ogc:{_expression:function(a){for(var b="",c=a.firstChild;c;c=
+c.nextSibling)switch(c.nodeType){case 1:a=this.readNode(c);a.property?b+="${"+a.property+"}":void 0!==a.value&&(b+=a.value);break;case 3:case 4:b+=c.nodeValue}return b},Filter:function(a,b){var c={fids:[],filters:[]};this.readChildNodes(a,c);0<c.fids.length?b.filter=new OpenLayers.Filter.FeatureId({fids:c.fids}):0<c.filters.length&&(b.filter=c.filters[0])},FeatureId:function(a,b){var c=a.getAttribute("fid");c&&b.fids.push(c)},And:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND});
+this.readChildNodes(a,c);b.filters.push(c)},Or:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR});this.readChildNodes(a,c);b.filters.push(c)},Not:function(a,b){var c=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.NOT});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLessThan:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsGreaterThan:function(a,
+b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLessThanOrEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsGreaterThanOrEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},
+PropertyIsBetween:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.BETWEEN});this.readChildNodes(a,c);b.filters.push(c)},Literal:function(a,b){b.value=OpenLayers.String.numericIf(this.getChildValue(a),!0)},PropertyName:function(a,b){b.property=this.getChildValue(a)},LowerBoundary:function(a,b){b.lowerBoundary=OpenLayers.String.numericIf(this.readers.ogc._expression.call(this,a),!0)},UpperBoundary:function(a,b){b.upperBoundary=OpenLayers.String.numericIf(this.readers.ogc._expression.call(this,
+a),!0)},Intersects:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.INTERSECTS)},Within:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.WITHIN)},Contains:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.CONTAINS)},DWithin:function(a,b){this.readSpatial(a,b,OpenLayers.Filter.Spatial.DWITHIN)},Distance:function(a,b){b.distance=parseInt(this.getChildValue(a));b.distanceUnits=a.getAttribute("units")},Function:function(a,b){},PropertyIsNull:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.IS_NULL});
+this.readChildNodes(a,c);b.filters.push(c)}}},readSpatial:function(a,b,c){c=new OpenLayers.Filter.Spatial({type:c});this.readChildNodes(a,c);c.value=c.components[0];delete c.components;b.filters.push(c)},encodeLiteral:function(a){a instanceof Date&&(a=OpenLayers.Date.toISOString(a));return a},writeOgcExpression:function(a,b){a instanceof OpenLayers.Filter.Function?this.writeNode("Function",a,b):this.writeNode("Literal",a,b);return b},write:function(a){return this.writers.ogc.Filter.apply(this,[a])},
+writers:{ogc:{Filter:function(a){var b=this.createElementNSPlus("ogc:Filter");this.writeNode(this.getFilterType(a),a,b);return b},_featureIds:function(a){for(var b=this.createDocumentFragment(),c=0,d=a.fids.length;c<d;++c)this.writeNode("ogc:FeatureId",a.fids[c],b);return b},FeatureId:function(a){return this.createElementNSPlus("ogc:FeatureId",{attributes:{fid:a}})},And:function(a){for(var b=this.createElementNSPlus("ogc:And"),c,d=0,e=a.filters.length;d<e;++d)c=a.filters[d],this.writeNode(this.getFilterType(c),
+c,b);return b},Or:function(a){for(var b=this.createElementNSPlus("ogc:Or"),c,d=0,e=a.filters.length;d<e;++d)c=a.filters[d],this.writeNode(this.getFilterType(c),c,b);return b},Not:function(a){var b=this.createElementNSPlus("ogc:Not");a=a.filters[0];this.writeNode(this.getFilterType(a),a,b);return b},PropertyIsLessThan:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLessThan");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsGreaterThan:function(a){var b=
+this.createElementNSPlus("ogc:PropertyIsGreaterThan");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLessThanOrEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsGreaterThanOrEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);
+return b},PropertyIsBetween:function(a){var b=this.createElementNSPlus("ogc:PropertyIsBetween");this.writeNode("PropertyName",a,b);this.writeNode("LowerBoundary",a,b);this.writeNode("UpperBoundary",a,b);return b},PropertyName:function(a){return this.createElementNSPlus("ogc:PropertyName",{value:a.property})},Literal:function(a){return this.createElementNSPlus("ogc:Literal",{value:(this.encodeLiteral||OpenLayers.Format.Filter.v1.prototype.encodeLiteral)(a)})},LowerBoundary:function(a){var b=this.createElementNSPlus("ogc:LowerBoundary");
+this.writeOgcExpression(a.lowerBoundary,b);return b},UpperBoundary:function(a){var b=this.createElementNSPlus("ogc:UpperBoundary");this.writeNode("Literal",a.upperBoundary,b);return b},INTERSECTS:function(a){return this.writeSpatial(a,"Intersects")},WITHIN:function(a){return this.writeSpatial(a,"Within")},CONTAINS:function(a){return this.writeSpatial(a,"Contains")},DWITHIN:function(a){var b=this.writeSpatial(a,"DWithin");this.writeNode("Distance",a,b);return b},Distance:function(a){return this.createElementNSPlus("ogc:Distance",
+{attributes:{units:a.distanceUnits},value:a.distance})},Function:function(a){var b=this.createElementNSPlus("ogc:Function",{attributes:{name:a.name}});a=a.params;for(var c=0,d=a.length;c<d;c++)this.writeOgcExpression(a[c],b);return b},PropertyIsNull:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNull");this.writeNode("PropertyName",a,b);return b}}},getFilterType:function(a){var b=this.filterMap[a.type];if(!b)throw"Filter writing not supported for rule type: "+a.type;return b},filterMap:{"&&":"And",
+"||":"Or","!":"Not","==":"PropertyIsEqualTo","!=":"PropertyIsNotEqualTo","<":"PropertyIsLessThan",">":"PropertyIsGreaterThan","<=":"PropertyIsLessThanOrEqualTo",">=":"PropertyIsGreaterThanOrEqualTo","..":"PropertyIsBetween","~":"PropertyIsLike",NULL:"PropertyIsNull",BBOX:"BBOX",DWITHIN:"DWITHIN",WITHIN:"WITHIN",CONTAINS:"CONTAINS",INTERSECTS:"INTERSECTS",FID:"_featureIds"},CLASS_NAME:"OpenLayers.Format.Filter.v1"});OpenLayers.Format.Filter.v1_0_0=OpenLayers.Class(OpenLayers.Format.GML.v2,OpenLayers.Format.Filter.v1,{VERSION:"1.0.0",schemaLocation:"http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",initialize:function(a){OpenLayers.Format.GML.v2.prototype.initialize.apply(this,[a])},readers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsNotEqualTo:function(a,
+b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.NOT_EQUAL_TO});this.readChildNodes(a,c);b.filters.push(c)},PropertyIsLike:function(a,b){var c=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(a,c);var d=a.getAttribute("wildCard"),e=a.getAttribute("singleChar"),f=a.getAttribute("escape");c.value2regex(d,e,f);b.filters.push(c)}},OpenLayers.Format.Filter.v1.prototype.readers.ogc),gml:OpenLayers.Format.GML.v2.prototype.readers.gml,
+feature:OpenLayers.Format.GML.v2.prototype.readers.feature},writers:{ogc:OpenLayers.Util.applyDefaults({PropertyIsEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsNotEqualTo:function(a){var b=this.createElementNSPlus("ogc:PropertyIsNotEqualTo");this.writeNode("PropertyName",a,b);this.writeOgcExpression(a.value,b);return b},PropertyIsLike:function(a){var b=this.createElementNSPlus("ogc:PropertyIsLike",
+{attributes:{wildCard:"*",singleChar:".",escape:"!"}});this.writeNode("PropertyName",a,b);this.writeNode("Literal",a.regex2value(),b);return b},BBOX:function(a){var b=this.createElementNSPlus("ogc:BBOX");a.property&&this.writeNode("PropertyName",a,b);var c=this.writeNode("gml:Box",a.value,b);a.projection&&c.setAttribute("srsName",a.projection);return b}},OpenLayers.Format.Filter.v1.prototype.writers.ogc),gml:OpenLayers.Format.GML.v2.prototype.writers.gml,feature:OpenLayers.Format.GML.v2.prototype.writers.feature},
+writeSpatial:function(a,b){var c=this.createElementNSPlus("ogc:"+b);this.writeNode("PropertyName",a,c);if(a.value instanceof OpenLayers.Filter.Function)this.writeNode("Function",a.value,c);else{var d;d=a.value instanceof OpenLayers.Geometry?this.writeNode("feature:_geometry",a.value).firstChild:this.writeNode("gml:Box",a.value);a.projection&&d.setAttribute("srsName",a.projection);c.appendChild(d)}return c},CLASS_NAME:"OpenLayers.Format.Filter.v1_0_0"});OpenLayers.Format.WFST.v1_0_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,OpenLayers.Format.WFST.v1,{version:"1.0.0",srsNameInQuery:!1,schemaLocations:{wfs:"http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"},initialize:function(a){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[a]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[a])},readNode:function(a,b,c){return OpenLayers.Format.GML.v2.prototype.readNode.apply(this,arguments)},readers:{wfs:OpenLayers.Util.applyDefaults({WFS_TransactionResponse:function(a,
+b){b.insertIds=[];b.success=!1;this.readChildNodes(a,b)},InsertResult:function(a,b){var c={fids:[]};this.readChildNodes(a,c);b.insertIds=b.insertIds.concat(c.fids)},TransactionResult:function(a,b){this.readChildNodes(a,b)},Status:function(a,b){this.readChildNodes(a,b)},SUCCESS:function(a,b){b.success=!0}},OpenLayers.Format.WFST.v1.prototype.readers.wfs),gml:OpenLayers.Format.GML.v2.prototype.readers.gml,feature:OpenLayers.Format.GML.v2.prototype.readers.feature,ogc:OpenLayers.Format.Filter.v1_0_0.prototype.readers.ogc},
+writers:{wfs:OpenLayers.Util.applyDefaults({Query:function(a){a=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName,srsNameInQuery:this.srsNameInQuery},a);var b=a.featurePrefix,c=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(b?b+":":"")+a.featureType}});a.srsNameInQuery&&a.srsName&&c.setAttribute("srsName",a.srsName);a.featureNS&&c.setAttribute("xmlns:"+b,a.featureNS);if(a.propertyNames)for(var b=0,d=a.propertyNames.length;b<
+d;b++)this.writeNode("ogc:PropertyName",{property:a.propertyNames[b]},c);a.filter&&(this.setFilterProperty(a.filter),this.writeNode("ogc:Filter",a.filter,c));return c}},OpenLayers.Format.WFST.v1.prototype.writers.wfs),gml:OpenLayers.Format.GML.v2.prototype.writers.gml,feature:OpenLayers.Format.GML.v2.prototype.writers.feature,ogc:OpenLayers.Format.Filter.v1_0_0.prototype.writers.ogc},CLASS_NAME:"OpenLayers.Format.WFST.v1_0_0"});OpenLayers.ElementsIndexer=OpenLayers.Class({maxZIndex:null,order:null,indices:null,compare:null,initialize:function(a){this.compare=a?OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER:OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;this.clear()},insert:function(a){this.exists(a)&&this.remove(a);var b=a.id;this.determineZIndex(a);for(var c=-1,d=this.order.length,e;1<d-c;)e=parseInt((c+d)/2),0<this.compare(this,a,OpenLayers.Util.getElement(this.order[e]))?c=e:d=e;this.order.splice(d,
+0,b);this.indices[b]=this.getZIndex(a);return this.getNextElement(d)},remove:function(a){a=a.id;var b=OpenLayers.Util.indexOf(this.order,a);0<=b&&(this.order.splice(b,1),delete this.indices[a],this.maxZIndex=0<this.order.length?this.indices[this.order[this.order.length-1]]:0)},clear:function(){this.order=[];this.indices={};this.maxZIndex=0},exists:function(a){return null!=this.indices[a.id]},getZIndex:function(a){return a._style.graphicZIndex},determineZIndex:function(a){var b=a._style.graphicZIndex;
+null==b?(b=this.maxZIndex,a._style.graphicZIndex=b):b>this.maxZIndex&&(this.maxZIndex=b)},getNextElement:function(a){a+=1;if(a<this.order.length){var b=OpenLayers.Util.getElement(this.order[a]);void 0==b&&(b=this.getNextElement(a));return b}return null},CLASS_NAME:"OpenLayers.ElementsIndexer"});
+OpenLayers.ElementsIndexer.IndexingMethods={Z_ORDER:function(a,b,c){b=a.getZIndex(b);var d=0;c&&(a=a.getZIndex(c),d=b-a);return d},Z_ORDER_DRAWING_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0==a&&(a=1);return a},Z_ORDER_Y_ORDER:function(a,b,c){a=OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(a,b,c);c&&0===a&&(b=c._boundsBottom-b._boundsBottom,a=0===b?1:b);return a}};
+OpenLayers.Renderer.Elements=OpenLayers.Class(OpenLayers.Renderer,{rendererRoot:null,root:null,vectorRoot:null,textRoot:null,xmlns:null,xOffset:0,indexer:null,BACKGROUND_ID_SUFFIX:"_background",LABEL_ID_SUFFIX:"_label",LABEL_OUTLINE_SUFFIX:"_outline",initialize:function(a,b){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.rendererRoot=this.createRenderRoot();this.root=this.createRoot("_root");this.vectorRoot=this.createRoot("_vroot");this.textRoot=this.createRoot("_troot");this.root.appendChild(this.vectorRoot);
+this.root.appendChild(this.textRoot);this.rendererRoot.appendChild(this.root);this.container.appendChild(this.rendererRoot);b&&(b.zIndexing||b.yOrdering)&&(this.indexer=new OpenLayers.ElementsIndexer(b.yOrdering))},destroy:function(){this.clear();this.xmlns=this.root=this.rendererRoot=null;OpenLayers.Renderer.prototype.destroy.apply(this,arguments)},clear:function(){var a,b=this.vectorRoot;if(b)for(;a=b.firstChild;)b.removeChild(a);if(b=this.textRoot)for(;a=b.firstChild;)b.removeChild(a);this.indexer&&
+this.indexer.clear()},setExtent:function(a,b){var c=OpenLayers.Renderer.prototype.setExtent.apply(this,arguments),d=this.getResolution();if(this.map.baseLayer&&this.map.baseLayer.wrapDateLine){var e,f=a.getWidth()/this.map.getExtent().getWidth();a=a.scale(1/f);f=this.map.getMaxExtent();f.right>a.left&&f.right<a.right?e=!0:f.left>a.left&&f.left<a.right&&(e=!1);if(e!==this.rightOfDateLine||b)c=!1,this.xOffset=!0===e?f.getWidth()/d:0;this.rightOfDateLine=e}return c},getNodeType:function(a,b){},drawGeometry:function(a,
+b,c){var d=a.CLASS_NAME,e=!0;if("OpenLayers.Geometry.Collection"==d||"OpenLayers.Geometry.MultiPoint"==d||"OpenLayers.Geometry.MultiLineString"==d||"OpenLayers.Geometry.MultiPolygon"==d){for(var d=0,f=a.components.length;d<f;d++)e=this.drawGeometry(a.components[d],b,c)&&e;return e}d=e=!1;"none"!=b.display&&(b.backgroundGraphic?this.redrawBackgroundNode(a.id,a,b,c):d=!0,e=this.redrawNode(a.id,a,b,c));!1==e&&(b=document.getElementById(a.id))&&(b._style.backgroundGraphic&&(d=!0),b.parentNode.removeChild(b));
+d&&(b=document.getElementById(a.id+this.BACKGROUND_ID_SUFFIX))&&b.parentNode.removeChild(b);return e},redrawNode:function(a,b,c,d){c=this.applyDefaultSymbolizer(c);a=this.nodeFactory(a,this.getNodeType(b,c));a._featureId=d;a._boundsBottom=b.getBounds().bottom;a._geometryClass=b.CLASS_NAME;a._style=c;b=this.drawGeometryNode(a,b,c);if(!1===b)return!1;a=b.node;this.indexer?(c=this.indexer.insert(a))?this.vectorRoot.insertBefore(a,c):this.vectorRoot.appendChild(a):a.parentNode!==this.vectorRoot&&this.vectorRoot.appendChild(a);
+this.postDraw(a);return b.complete},redrawBackgroundNode:function(a,b,c,d){c=OpenLayers.Util.extend({},c);c.externalGraphic=c.backgroundGraphic;c.graphicXOffset=c.backgroundXOffset;c.graphicYOffset=c.backgroundYOffset;c.graphicZIndex=c.backgroundGraphicZIndex;c.graphicWidth=c.backgroundWidth||c.graphicWidth;c.graphicHeight=c.backgroundHeight||c.graphicHeight;c.backgroundGraphic=null;c.backgroundXOffset=null;c.backgroundYOffset=null;c.backgroundGraphicZIndex=null;return this.redrawNode(a+this.BACKGROUND_ID_SUFFIX,
+b,c,null)},drawGeometryNode:function(a,b,c){c=c||a._style;var d={isFilled:void 0===c.fill?!0:c.fill,isStroked:void 0===c.stroke?!!c.strokeWidth:c.stroke},e;switch(b.CLASS_NAME){case "OpenLayers.Geometry.Point":!1===c.graphic&&(d.isFilled=!1,d.isStroked=!1);e=this.drawPoint(a,b);break;case "OpenLayers.Geometry.LineString":d.isFilled=!1;e=this.drawLineString(a,b);break;case "OpenLayers.Geometry.LinearRing":e=this.drawLinearRing(a,b);break;case "OpenLayers.Geometry.Polygon":e=this.drawPolygon(a,b);break;
+case "OpenLayers.Geometry.Rectangle":e=this.drawRectangle(a,b)}a._options=d;return!1!=e?{node:this.setStyle(a,c,d,b),complete:e}:!1},postDraw:function(a){},drawPoint:function(a,b){},drawLineString:function(a,b){},drawLinearRing:function(a,b){},drawPolygon:function(a,b){},drawRectangle:function(a,b){},drawCircle:function(a,b){},removeText:function(a){var b=document.getElementById(a+this.LABEL_ID_SUFFIX);b&&this.textRoot.removeChild(b);(a=document.getElementById(a+this.LABEL_OUTLINE_SUFFIX))&&this.textRoot.removeChild(a)},
+getFeatureIdFromEvent:function(a){var b=a.target,c=b&&b.correspondingUseElement;return(c?c:b||a.srcElement)._featureId},eraseGeometry:function(a,b){if("OpenLayers.Geometry.MultiPoint"==a.CLASS_NAME||"OpenLayers.Geometry.MultiLineString"==a.CLASS_NAME||"OpenLayers.Geometry.MultiPolygon"==a.CLASS_NAME||"OpenLayers.Geometry.Collection"==a.CLASS_NAME)for(var c=0,d=a.components.length;c<d;c++)this.eraseGeometry(a.components[c],b);else(c=OpenLayers.Util.getElement(a.id))&&c.parentNode&&(c.geometry&&(c.geometry.destroy(),
+c.geometry=null),c.parentNode.removeChild(c),this.indexer&&this.indexer.remove(c),c._style.backgroundGraphic&&(c=OpenLayers.Util.getElement(a.id+this.BACKGROUND_ID_SUFFIX))&&c.parentNode&&c.parentNode.removeChild(c))},nodeFactory:function(a,b){var c=OpenLayers.Util.getElement(a);c?this.nodeTypeCompare(c,b)||(c.parentNode.removeChild(c),c=this.nodeFactory(a,b)):c=this.createNode(b,a);return c},nodeTypeCompare:function(a,b){},createNode:function(a,b){},moveRoot:function(a){var b=this.root;a.root.parentNode==
+this.rendererRoot&&(b=a.root);b.parentNode.removeChild(b);a.rendererRoot.appendChild(b)},getRenderLayerId:function(){return this.root.parentNode.parentNode.id},isComplexSymbol:function(a){return"circle"!=a&&!!a},CLASS_NAME:"OpenLayers.Renderer.Elements"});OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,autoActivate:!0,defaultControl:null,saveState:!1,allowDepress:!1,activeState:null,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,[a]);this.controls=[];this.activeState={}},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onButtonClick);OpenLayers.Control.prototype.destroy.apply(this,arguments);for(var a,b=this.controls.length-1;0<=b;b--)a=this.controls[b],a.events&&
+a.events.un({activate:this.iconOn,deactivate:this.iconOff}),a.panel_div=null;this.activeState=null},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){for(var a,b=0,c=this.controls.length;b<c;b++)a=this.controls[b],(a===this.defaultControl||this.saveState&&this.activeState[a.id])&&a.activate();!0===this.saveState&&(this.defaultControl=null);this.redraw();return!0}return!1},deactivate:function(){if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)){for(var a,
+b=0,c=this.controls.length;b<c;b++)a=this.controls[b],this.activeState[a.id]=a.deactivate();this.redraw();return!0}return!1},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.outsideViewport?(this.events.attachToElement(this.div),this.events.register("buttonclick",this,this.onButtonClick)):this.map.events.register("buttonclick",this,this.onButtonClick);this.addControlsToMap(this.controls);return this.div},redraw:function(){for(var a=this.div.childNodes.length-1;0<=a;a--)this.div.removeChild(this.div.childNodes[a]);
+this.div.innerHTML="";if(this.active)for(var a=0,b=this.controls.length;a<b;a++)this.div.appendChild(this.controls[a].panel_div)},activateControl:function(a){if(!this.active)return!1;if(a.type==OpenLayers.Control.TYPE_BUTTON)a.trigger();else if(a.type==OpenLayers.Control.TYPE_TOGGLE)a.active?a.deactivate():a.activate();else if(this.allowDepress&&a.active)a.deactivate();else{for(var b,c=0,d=this.controls.length;c<d;c++)b=this.controls[c],b==a||b.type!==OpenLayers.Control.TYPE_TOOL&&null!=b.type||b.deactivate();
+a.activate()}},addControls:function(a){OpenLayers.Util.isArray(a)||(a=[a]);this.controls=this.controls.concat(a);for(var b=0,c=a.length;b<c;b++){var d=a[b],e=this.createControlMarkup(d);OpenLayers.Element.addClass(e,d.displayClass+"ItemInactive");OpenLayers.Element.addClass(e,"olButton");""==d.title||e.title||(e.title=d.title);d.panel_div=e}this.map&&(this.addControlsToMap(a),this.redraw())},createControlMarkup:function(a){return document.createElement("div")},addControlsToMap:function(a){for(var b,
+c=0,d=a.length;c<d;c++)b=a[c],!0===b.autoActivate?(b.autoActivate=!1,this.map.addControl(b),b.autoActivate=!0):(this.map.addControl(b),b.deactivate()),b.events.on({activate:this.iconOn,deactivate:this.iconOff})},iconOn:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Inactive\\b"),"$1Active")},iconOff:function(){var a=this.panel_div;a.className=a.className.replace(RegExp("\\b("+this.displayClass+"Item)Active\\b"),"$1Inactive")},onButtonClick:function(a){var b=
+this.controls;a=a.buttonElement;for(var c=b.length-1;0<=c;--c)if(b[c].panel_div===a){this.activateControl(b[c]);break}},getControlsBy:function(a,b){var c="function"==typeof b.test;return OpenLayers.Array.filter(this.controls,function(d){return d[a]==b||c&&b.test(d[a])})},getControlsByName:function(a){return this.getControlsBy("name",a)},getControlsByClass:function(a){return this.getControlsBy("CLASS_NAME",a)},CLASS_NAME:"OpenLayers.Control.Panel"});OpenLayers.Strategy=OpenLayers.Class({layer:null,options:null,active:null,autoActivate:!0,autoDestroy:!0,initialize:function(a){OpenLayers.Util.extend(this,a);this.options=a;this.active=!1},destroy:function(){this.deactivate();this.options=this.layer=null},setLayer:function(a){this.layer=a},activate:function(){return this.active?!1:this.active=!0},deactivate:function(){return this.active?(this.active=!1,!0):!1},CLASS_NAME:"OpenLayers.Strategy"});OpenLayers.Strategy.Fixed=OpenLayers.Class(OpenLayers.Strategy,{preload:!1,activate:function(){var a=OpenLayers.Strategy.prototype.activate.apply(this,arguments);if(a)if(this.layer.events.on({refresh:this.load,scope:this}),!0==this.layer.visibility||this.preload)this.load();else this.layer.events.on({visibilitychanged:this.load,scope:this});return a},deactivate:function(){var a=OpenLayers.Strategy.prototype.deactivate.call(this);a&&this.layer.events.un({refresh:this.load,visibilitychanged:this.load,
+scope:this});return a},load:function(a){var b=this.layer;b.events.triggerEvent("loadstart",{filter:b.filter});b.protocol.read(OpenLayers.Util.applyDefaults({callback:this.merge,filter:b.filter,scope:this},a));b.events.un({visibilitychanged:this.load,scope:this})},merge:function(a){var b=this.layer;b.destroyFeatures();var c=a.features;if(c&&0<c.length){var d=b.projection,e=b.map.getProjectionObject();if(!e.equals(d))for(var f,g=0,h=c.length;g<h;++g)(f=c[g].geometry)&&f.transform(d,e);b.addFeatures(c)}b.events.triggerEvent("loadend",
+{response:a})},CLASS_NAME:"OpenLayers.Strategy.Fixed"});OpenLayers.Control.Zoom=OpenLayers.Class(OpenLayers.Control,{zoomInText:"+",zoomInId:"olZoomInLink",zoomOutText:"\u2212",zoomOutId:"olZoomOutLink",draw:function(){var a=OpenLayers.Control.prototype.draw.apply(this),b=this.getOrCreateLinks(a),c=b.zoomIn,b=b.zoomOut,d=this.map.events;b.parentNode!==a&&(d=this.events,d.attachToElement(b.parentNode));d.register("buttonclick",this,this.onZoomClick);this.zoomInLink=c;this.zoomOutLink=b;return a},getOrCreateLinks:function(a){var b=document.getElementById(this.zoomInId),
+c=document.getElementById(this.zoomOutId);b||(b=document.createElement("a"),b.href="#zoomIn",b.appendChild(document.createTextNode(this.zoomInText)),b.className="olControlZoomIn",a.appendChild(b));OpenLayers.Element.addClass(b,"olButton");c||(c=document.createElement("a"),c.href="#zoomOut",c.appendChild(document.createTextNode(this.zoomOutText)),c.className="olControlZoomOut",a.appendChild(c));OpenLayers.Element.addClass(c,"olButton");return{zoomIn:b,zoomOut:c}},onZoomClick:function(a){a=a.buttonElement;
+a===this.zoomInLink?this.map.zoomIn():a===this.zoomOutLink&&this.map.zoomOut()},destroy:function(){this.map&&this.map.events.unregister("buttonclick",this,this.onZoomClick);delete this.zoomInLink;delete this.zoomOutLink;OpenLayers.Control.prototype.destroy.apply(this)},CLASS_NAME:"OpenLayers.Control.Zoom"});OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:!0,defaultFilter:null,initialize:function(a){a=a||{};OpenLayers.Util.extend(this,a);this.options=a},mergeWithDefaultFilter:function(a){return a&&this.defaultFilter?new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.defaultFilter,a]}):a||this.defaultFilter||void 0},destroy:function(){this.format=this.options=null},read:function(a){a=a||{};a.filter=this.mergeWithDefaultFilter(a.filter)},create:function(){},
+update:function(){},"delete":function(){},commit:function(){},abort:function(a){},createCallback:function(a,b,c){return OpenLayers.Function.bind(function(){a.apply(this,[b,c])},this)},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:!0,features:null,data:null,reqFeatures:null,priv:null,error:null,initialize:function(a){OpenLayers.Util.extend(this,a)},success:function(){return 0<this.code},CLASS_NAME:"OpenLayers.Protocol.Response"});
+OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Protocol.WFS=function(a){a=OpenLayers.Util.applyDefaults(a,OpenLayers.Protocol.WFS.DEFAULTS);var b=OpenLayers.Protocol.WFS["v"+a.version.replace(/\./g,"_")];if(!b)throw"Unsupported WFS version: "+a.version;return new b(a)};
+OpenLayers.Protocol.WFS.fromWMSLayer=function(a,b){var c,d;c=a.params.LAYERS;c=(OpenLayers.Util.isArray(c)?c[0]:c).split(":");1<c.length&&(d=c[0]);c=c.pop();d={url:a.url,featureType:c,featurePrefix:d,srsName:a.projection&&a.projection.getCode()||a.map&&a.map.getProjectionObject().getCode(),version:"1.1.0"};return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(b,d))};OpenLayers.Protocol.WFS.DEFAULTS={version:"1.0.0"};OpenLayers.ProxyHost="";OpenLayers.Request||(OpenLayers.Request={});
+OpenLayers.Util.extend(OpenLayers.Request,{DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:!0,user:void 0,password:void 0,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},URL_SPLIT_REGEX:/([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,events:new OpenLayers.Events(this),makeSameOrigin:function(a,b){var c=0!==a.indexOf("http"),d=!c&&a.match(this.URL_SPLIT_REGEX);if(d){var e=window.location,c=d[1]==e.protocol&&d[3]==
+e.hostname,d=d[4],e=e.port;if(80!=d&&""!=d||"80"!=e&&""!=e)c=c&&d==e}c||b&&(a="function"==typeof b?b(a):b+encodeURIComponent(a));return a},issue:function(a){var b=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});a=a||{};a.headers=a.headers||{};a=OpenLayers.Util.applyDefaults(a,b);a.headers=OpenLayers.Util.applyDefaults(a.headers,b.headers);var b=!1,c;for(c in a.headers)a.headers.hasOwnProperty(c)&&"x-requested-with"===c.toLowerCase()&&(b=!0);!1===b&&(a.headers["X-Requested-With"]=
+"XMLHttpRequest");var d=new OpenLayers.Request.XMLHttpRequest,e=OpenLayers.Util.urlAppend(a.url,OpenLayers.Util.getParameterString(a.params||{})),e=OpenLayers.Request.makeSameOrigin(e,a.proxy);d.open(a.method,e,a.async,a.user,a.password);for(var f in a.headers)d.setRequestHeader(f,a.headers[f]);var g=this.events,h=this;d.onreadystatechange=function(){d.readyState==OpenLayers.Request.XMLHttpRequest.DONE&&!1!==g.triggerEvent("complete",{request:d,config:a,requestUrl:e})&&h.runCallbacks({request:d,config:a,
+requestUrl:e})};!1===a.async?d.send(a.data):window.setTimeout(function(){0!==d.readyState&&d.send(a.data)},0);return d},runCallbacks:function(a){var b=a.request,c=a.config,d=c.scope?OpenLayers.Function.bind(c.callback,c.scope):c.callback,e;c.success&&(e=c.scope?OpenLayers.Function.bind(c.success,c.scope):c.success);var f;c.failure&&(f=c.scope?OpenLayers.Function.bind(c.failure,c.scope):c.failure);"file:"==OpenLayers.Util.createUrlObject(c.url).protocol&&b.responseText&&(b.status=200);d(b);if(!b.status||
+200<=b.status&&300>b.status)this.events.triggerEvent("success",a),e&&e(b);b.status&&(200>b.status||300<=b.status)&&(this.events.triggerEvent("failure",a),f&&f(b))},GET:function(a){a=OpenLayers.Util.extend(a,{method:"GET"});return OpenLayers.Request.issue(a)},POST:function(a){a=OpenLayers.Util.extend(a,{method:"POST"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},PUT:function(a){a=
+OpenLayers.Util.extend(a,{method:"PUT"});a.headers=a.headers?a.headers:{};"CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(a.headers)||(a.headers["Content-Type"]="application/xml");return OpenLayers.Request.issue(a)},DELETE:function(a){a=OpenLayers.Util.extend(a,{method:"DELETE"});return OpenLayers.Request.issue(a)},HEAD:function(a){a=OpenLayers.Util.extend(a,{method:"HEAD"});return OpenLayers.Request.issue(a)},OPTIONS:function(a){a=OpenLayers.Util.extend(a,{method:"OPTIONS"});return OpenLayers.Request.issue(a)}});(function(){function a(){this._object=f&&!k?new f:new window.ActiveXObject("Microsoft.XMLHTTP");this._listeners=[]}function b(){return new a}function c(a){b.onreadystatechange&&b.onreadystatechange.apply(a);a.dispatchEvent({type:"readystatechange",bubbles:!1,cancelable:!1,timeStamp:new Date+0})}function d(a){try{a.responseText=a._object.responseText}catch(b){}try{var c;var d=a._object,e=d.responseXML,f=d.responseText;h&&(f&&e&&!e.documentElement&&d.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/))&&
+(e=new window.ActiveXObject("Microsoft.XMLDOM"),e.async=!1,e.validateOnParse=!1,e.loadXML(f));c=e&&(h&&0!=e.parseError||!e.documentElement||e.documentElement&&"parsererror"==e.documentElement.tagName)?null:e;a.responseXML=c}catch(g){}try{a.status=a._object.status}catch(k){}try{a.statusText=a._object.statusText}catch(u){}}function e(a){a._object.onreadystatechange=new window.Function}var f=window.XMLHttpRequest,g=!!window.controllers,h=window.document.all&&!window.opera,k=h&&window.navigator.userAgent.match(/MSIE 7.0/);
+b.prototype=a.prototype;g&&f.wrapped&&(b.wrapped=f.wrapped);b.UNSENT=0;b.OPENED=1;b.HEADERS_RECEIVED=2;b.LOADING=3;b.DONE=4;b.prototype.readyState=b.UNSENT;b.prototype.responseText="";b.prototype.responseXML=null;b.prototype.status=0;b.prototype.statusText="";b.prototype.priority="NORMAL";b.prototype.onreadystatechange=null;b.onreadystatechange=null;b.onopen=null;b.onsend=null;b.onabort=null;b.prototype.open=function(a,f,k,n,q){delete this._headers;3>arguments.length&&(k=!0);this._async=k;var r=this,
+s=this.readyState,t;h&&k&&(t=function(){s!=b.DONE&&(e(r),r.abort())},window.attachEvent("onunload",t));b.onopen&&b.onopen.apply(this,arguments);4<arguments.length?this._object.open(a,f,k,n,q):3<arguments.length?this._object.open(a,f,k,n):this._object.open(a,f,k);this.readyState=b.OPENED;c(this);this._object.onreadystatechange=function(){if(!g||k)r.readyState=r._object.readyState,d(r),r._aborted?r.readyState=b.UNSENT:(r.readyState==b.DONE&&(delete r._data,e(r),h&&k&&window.detachEvent("onunload",t)),
+s!=r.readyState&&c(r),s=r.readyState)}};b.prototype.send=function(a){b.onsend&&b.onsend.apply(this,arguments);arguments.length||(a=null);a&&a.nodeType&&(a=window.XMLSerializer?(new window.XMLSerializer).serializeToString(a):a.xml,this._headers["Content-Type"]||this._object.setRequestHeader("Content-Type","application/xml"));this._data=a;a:if(this._object.send(this._data),g&&!this._async)for(this.readyState=b.OPENED,d(this);this.readyState<b.DONE;)if(this.readyState++,c(this),this._aborted)break a};
+b.prototype.abort=function(){b.onabort&&b.onabort.apply(this,arguments);this.readyState>b.UNSENT&&(this._aborted=!0);this._object.abort();e(this);this.readyState=b.UNSENT;delete this._data};b.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders()};b.prototype.getResponseHeader=function(a){return this._object.getResponseHeader(a)};b.prototype.setRequestHeader=function(a,b){this._headers||(this._headers={});this._headers[a]=b;return this._object.setRequestHeader(a,b)};
+b.prototype.addEventListener=function(a,b,c){for(var d=0,e;e=this._listeners[d];d++)if(e[0]==a&&e[1]==b&&e[2]==c)return;this._listeners.push([a,b,c])};b.prototype.removeEventListener=function(a,b,c){for(var d=0,e;(e=this._listeners[d])&&(e[0]!=a||e[1]!=b||e[2]!=c);d++);e&&this._listeners.splice(d,1)};b.prototype.dispatchEvent=function(a){a={type:a.type,target:this,currentTarget:this,eventPhase:2,bubbles:a.bubbles,cancelable:a.cancelable,timeStamp:a.timeStamp,stopPropagation:function(){},preventDefault:function(){},
+initEvent:function(){}};"readystatechange"==a.type&&this.onreadystatechange&&(this.onreadystatechange.handleEvent||this.onreadystatechange).apply(this,[a]);for(var b=0,c;c=this._listeners[b];b++)c[0]!=a.type||c[2]||(c[1].handleEvent||c[1]).apply(this,[a])};b.prototype.toString=function(){return"[object XMLHttpRequest]"};b.toString=function(){return"[XMLHttpRequest]"};window.Function.prototype.apply||(window.Function.prototype.apply=function(a,b){b||(b=[]);a.__func=this;a.__func(b[0],b[1],b[2],b[3],
+b[4]);delete a.__func});OpenLayers.Request||(OpenLayers.Request={});OpenLayers.Request.XMLHttpRequest=b})();OpenLayers.Format.KML=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{kml:"http://www.opengis.net/kml/2.2",gx:"http://www.google.com/kml/ext/2.2"},kmlns:"http://earth.google.com/kml/2.0",placemarksDesc:"No description available",foldersName:"OpenLayers export",foldersDesc:"Exported on "+new Date,extractAttributes:!0,kvpAttributes:!1,extractStyles:!1,extractTracks:!1,trackAttributes:null,internalns:null,features:null,styles:null,styleBaseUrl:"",fetched:null,maxDepth:0,initialize:function(a){this.regExes=
+{trimSpace:/^\s*|\s*$/g,removeSpace:/\s*/g,splitSpace:/\s+/,trimComma:/\s*,\s*/g,kmlColor:/(\w{2})(\w{2})(\w{2})(\w{2})/,kmlIconPalette:/root:\/\/icons\/palette-(\d+)(\.\w+)/,straightBracket:/\$\[(.*?)\]/g};this.externalProjection=new OpenLayers.Projection("EPSG:4326");OpenLayers.Format.XML.prototype.initialize.apply(this,[a])},read:function(a){this.features=[];this.styles={};this.fetched={};return this.parseData(a,{depth:0,styleBaseUrl:this.styleBaseUrl})},parseData:function(a,b){"string"==typeof a&&
+(a=OpenLayers.Format.XML.prototype.read.apply(this,[a]));for(var c=["Link","NetworkLink","Style","StyleMap","Placemark"],d=0,e=c.length;d<e;++d){var f=c[d],g=this.getElementsByTagNameNS(a,"*",f);if(0!=g.length)switch(f.toLowerCase()){case "link":case "networklink":this.parseLinks(g,b);break;case "style":this.extractStyles&&this.parseStyles(g,b);break;case "stylemap":this.extractStyles&&this.parseStyleMaps(g,b);break;case "placemark":this.parseFeatures(g,b)}}return this.features},parseLinks:function(a,
+b){if(b.depth>=this.maxDepth)return!1;var c=OpenLayers.Util.extend({},b);c.depth++;for(var d=0,e=a.length;d<e;d++){var f=this.parseProperty(a[d],"*","href");f&&!this.fetched[f]&&(this.fetched[f]=!0,(f=this.fetchLink(f))&&this.parseData(f,c))}},fetchLink:function(a){if(a=OpenLayers.Request.GET({url:a,async:!1}))return a.responseText},parseStyles:function(a,b){for(var c=0,d=a.length;c<d;c++){var e=this.parseStyle(a[c]);e&&(this.styles[(b.styleBaseUrl||"")+"#"+e.id]=e)}},parseKmlColor:function(a){var b=
+null;a&&(a=a.match(this.regExes.kmlColor))&&(b={color:"#"+a[4]+a[3]+a[2],opacity:parseInt(a[1],16)/255});return b},parseStyle:function(a){for(var b={},c=["LineStyle","PolyStyle","IconStyle","BalloonStyle","LabelStyle"],d,e,f=0,g=c.length;f<g;++f)if(d=c[f],e=this.getElementsByTagNameNS(a,"*",d)[0])switch(d.toLowerCase()){case "linestyle":d=this.parseProperty(e,"*","color");if(d=this.parseKmlColor(d))b.strokeColor=d.color,b.strokeOpacity=d.opacity;(d=this.parseProperty(e,"*","width"))&&(b.strokeWidth=
+d);break;case "polystyle":d=this.parseProperty(e,"*","color");if(d=this.parseKmlColor(d))b.fillOpacity=d.opacity,b.fillColor=d.color;"0"==this.parseProperty(e,"*","fill")&&(b.fillColor="none");"0"==this.parseProperty(e,"*","outline")&&(b.strokeWidth="0");break;case "iconstyle":var h=parseFloat(this.parseProperty(e,"*","scale")||1);d=32*h;var k=32*h,l=this.getElementsByTagNameNS(e,"*","Icon")[0];if(l){var m=this.parseProperty(l,"*","href");if(m){var p=this.parseProperty(l,"*","w"),n=this.parseProperty(l,
+"*","h");!OpenLayers.String.startsWith(m,"http://maps.google.com/mapfiles/kml")||(p||n)||(n=p=64,h/=2);p=p||n;n=n||p;p&&(d=parseInt(p)*h);n&&(k=parseInt(n)*h);if(n=m.match(this.regExes.kmlIconPalette))p=n[1],n=n[2],m=this.parseProperty(l,"*","x"),l=this.parseProperty(l,"*","y"),m="http://maps.google.com/mapfiles/kml/pal"+p+"/icon"+(8*(l?7-l/32:7)+(m?m/32:0))+n;b.graphicOpacity=1;b.externalGraphic=m}}if(e=this.getElementsByTagNameNS(e,"*","hotSpot")[0])m=parseFloat(e.getAttribute("x")),l=parseFloat(e.getAttribute("y")),
+p=e.getAttribute("xunits"),"pixels"==p?b.graphicXOffset=-m*h:"insetPixels"==p?b.graphicXOffset=-d+m*h:"fraction"==p&&(b.graphicXOffset=-d*m),e=e.getAttribute("yunits"),"pixels"==e?b.graphicYOffset=-k+l*h+1:"insetPixels"==e?b.graphicYOffset=-(l*h)+1:"fraction"==e&&(b.graphicYOffset=-k*(1-l)+1);b.graphicWidth=d;b.graphicHeight=k;break;case "balloonstyle":(e=OpenLayers.Util.getXmlNodeValue(e))&&(b.balloonStyle=e.replace(this.regExes.straightBracket,"${$1}"));break;case "labelstyle":if(d=this.parseProperty(e,
+"*","color"),d=this.parseKmlColor(d))b.fontColor=d.color,b.fontOpacity=d.opacity}!b.strokeColor&&b.fillColor&&(b.strokeColor=b.fillColor);(a=a.getAttribute("id"))&&b&&(b.id=a);return b},parseStyleMaps:function(a,b){for(var c=0,d=a.length;c<d;c++)for(var e=a[c],f=this.getElementsByTagNameNS(e,"*","Pair"),e=e.getAttribute("id"),g=0,h=f.length;g<h;g++){var k=f[g],l=this.parseProperty(k,"*","key");(k=this.parseProperty(k,"*","styleUrl"))&&"normal"==l&&(this.styles[(b.styleBaseUrl||"")+"#"+e]=this.styles[(b.styleBaseUrl||
+"")+k])}},parseFeatures:function(a,b){for(var c=[],d=0,e=a.length;d<e;d++){var f=a[d],g=this.parseFeature.apply(this,[f]);if(g){this.extractStyles&&(g.attributes&&g.attributes.styleUrl)&&(g.style=this.getStyle(g.attributes.styleUrl,b));if(this.extractStyles){var h=this.getElementsByTagNameNS(f,"*","Style")[0];h&&(h=this.parseStyle(h))&&(g.style=OpenLayers.Util.extend(g.style,h))}this.extractTracks?(f=this.getElementsByTagNameNS(f,this.namespaces.gx,"Track"))&&0<f.length&&(g={features:[],feature:g},
+this.readNode(f[0],g),0<g.features.length&&c.push.apply(c,g.features)):c.push(g)}else throw"Bad Placemark: "+d;}this.features=this.features.concat(c)},readers:{kml:{when:function(a,b){b.whens.push(OpenLayers.Date.parse(this.getChildValue(a)))},_trackPointAttribute:function(a,b){var c=a.nodeName.split(":").pop();b.attributes[c].push(this.getChildValue(a))}},gx:{Track:function(a,b){var c={whens:[],points:[],angles:[]};if(this.trackAttributes){var d;c.attributes={};for(var e=0,f=this.trackAttributes.length;e<
+f;++e)d=this.trackAttributes[e],c.attributes[d]=[],d in this.readers.kml||(this.readers.kml[d]=this.readers.kml._trackPointAttribute)}this.readChildNodes(a,c);if(c.whens.length!==c.points.length)throw Error("gx:Track with unequal number of when ("+c.whens.length+") and gx:coord ("+c.points.length+") elements.");var g=0<c.angles.length;if(g&&c.whens.length!==c.angles.length)throw Error("gx:Track with unequal number of when ("+c.whens.length+") and gx:angles ("+c.angles.length+") elements.");for(var h,
+e=0,f=c.whens.length;e<f;++e){h=b.feature.clone();h.fid=b.feature.fid||b.feature.id;d=c.points[e];h.geometry=d;"z"in d&&(h.attributes.altitude=d.z);this.internalProjection&&this.externalProjection&&h.geometry.transform(this.externalProjection,this.internalProjection);if(this.trackAttributes)for(var k=0,l=this.trackAttributes.length;k<l;++k)d=this.trackAttributes[k],h.attributes[d]=c.attributes[d][e];h.attributes.when=c.whens[e];h.attributes.trackId=b.feature.id;g&&(d=c.angles[e],h.attributes.heading=
+parseFloat(d[0]),h.attributes.tilt=parseFloat(d[1]),h.attributes.roll=parseFloat(d[2]));b.features.push(h)}},coord:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(/\s+/),d=new OpenLayers.Geometry.Point(c[0],c[1]);2<c.length&&(d.z=parseFloat(c[2]));b.points.push(d)},angles:function(a,b){var c=this.getChildValue(a).replace(this.regExes.trimSpace,"").split(/\s+/);b.angles.push(c)}}},parseFeature:function(a){for(var b=["MultiGeometry","Polygon","LineString","Point"],
+c,d,e,f=0,g=b.length;f<g;++f)if(c=b[f],this.internalns=a.namespaceURI?a.namespaceURI:this.kmlns,d=this.getElementsByTagNameNS(a,this.internalns,c),0<d.length){if(b=this.parseGeometry[c.toLowerCase()])e=b.apply(this,[d[0]]),this.internalProjection&&this.externalProjection&&e.transform(this.externalProjection,this.internalProjection);else throw new TypeError("Unsupported geometry type: "+c);break}var h;this.extractAttributes&&(h=this.parseAttributes(a));c=new OpenLayers.Feature.Vector(e,h);a=a.getAttribute("id")||
+a.getAttribute("name");null!=a&&(c.fid=a);return c},getStyle:function(a,b){var c=OpenLayers.Util.removeTail(a),d=OpenLayers.Util.extend({},b);d.depth++;d.styleBaseUrl=c;!this.styles[a]&&!OpenLayers.String.startsWith(a,"#")&&d.depth<=this.maxDepth&&!this.fetched[c]&&(c=this.fetchLink(c))&&this.parseData(c,d);return OpenLayers.Util.extend({},this.styles[a])},parseGeometry:{point:function(a){var b=this.getElementsByTagNameNS(a,this.internalns,"coordinates");a=[];if(0<b.length){var c=b[0].firstChild.nodeValue,
+c=c.replace(this.regExes.removeSpace,"");a=c.split(",")}b=null;if(1<a.length)2==a.length&&(a[2]=null),b=new OpenLayers.Geometry.Point(a[0],a[1],a[2]);else throw"Bad coordinate string: "+c;return b},linestring:function(a,b){var c=this.getElementsByTagNameNS(a,this.internalns,"coordinates"),d=null;if(0<c.length){for(var c=this.getChildValue(c[0]),c=c.replace(this.regExes.trimSpace,""),c=c.replace(this.regExes.trimComma,","),d=c.split(this.regExes.splitSpace),e=d.length,f=Array(e),g,h,k=0;k<e;++k)if(g=
+d[k].split(","),h=g.length,1<h)2==g.length&&(g[2]=null),f[k]=new OpenLayers.Geometry.Point(g[0],g[1],g[2]);else throw"Bad LineString point coordinates: "+d[k];if(e)d=b?new OpenLayers.Geometry.LinearRing(f):new OpenLayers.Geometry.LineString(f);else throw"Bad LineString coordinates: "+c;}return d},polygon:function(a){a=this.getElementsByTagNameNS(a,this.internalns,"LinearRing");var b=a.length,c=Array(b);if(0<b)for(var d=0,e=a.length;d<e;++d)if(b=this.parseGeometry.linestring.apply(this,[a[d],!0]))c[d]=
+b;else throw"Bad LinearRing geometry: "+d;return new OpenLayers.Geometry.Polygon(c)},multigeometry:function(a){for(var b,c=[],d=a.childNodes,e=0,f=d.length;e<f;++e)a=d[e],1==a.nodeType&&(b=a.prefix?a.nodeName.split(":")[1]:a.nodeName,(b=this.parseGeometry[b.toLowerCase()])&&c.push(b.apply(this,[a])));return new OpenLayers.Geometry.Collection(c)}},parseAttributes:function(a){var b={},c=a.getElementsByTagName("ExtendedData");c.length&&(b=this.parseExtendedData(c[0]));var d,e,f;a=a.childNodes;for(var c=
+0,g=a.length;c<g;++c)if(d=a[c],1==d.nodeType&&(e=d.childNodes,1<=e.length&&3>=e.length)){switch(e.length){case 1:f=e[0];break;case 2:f=e[0];e=e[1];f=3==f.nodeType||4==f.nodeType?f:e;break;default:f=e[1]}if(3==f.nodeType||4==f.nodeType)if(d=d.prefix?d.nodeName.split(":")[1]:d.nodeName,f=OpenLayers.Util.getXmlNodeValue(f))f=f.replace(this.regExes.trimSpace,""),b[d]=f}return b},parseExtendedData:function(a){var b={},c,d,e,f,g=a.getElementsByTagName("Data");c=0;for(d=g.length;c<d;c++){e=g[c];f=e.getAttribute("name");
+var h={},k=e.getElementsByTagName("value");k.length&&(h.value=this.getChildValue(k[0]));this.kvpAttributes?b[f]=h.value:(e=e.getElementsByTagName("displayName"),e.length&&(h.displayName=this.getChildValue(e[0])),b[f]=h)}a=a.getElementsByTagName("SimpleData");c=0;for(d=a.length;c<d;c++)h={},e=a[c],f=e.getAttribute("name"),h.value=this.getChildValue(e),this.kvpAttributes?b[f]=h.value:(h.displayName=f,b[f]=h);return b},parseProperty:function(a,b,c){var d;a=this.getElementsByTagNameNS(a,b,c);try{d=OpenLayers.Util.getXmlNodeValue(a[0])}catch(e){d=
+null}return d},write:function(a){OpenLayers.Util.isArray(a)||(a=[a]);for(var b=this.createElementNS(this.kmlns,"kml"),c=this.createFolderXML(),d=0,e=a.length;d<e;++d)c.appendChild(this.createPlacemarkXML(a[d]));b.appendChild(c);return OpenLayers.Format.XML.prototype.write.apply(this,[b])},createFolderXML:function(){var a=this.createElementNS(this.kmlns,"Folder");if(this.foldersName){var b=this.createElementNS(this.kmlns,"name"),c=this.createTextNode(this.foldersName);b.appendChild(c);a.appendChild(b)}this.foldersDesc&&
+(b=this.createElementNS(this.kmlns,"description"),c=this.createTextNode(this.foldersDesc),b.appendChild(c),a.appendChild(b));return a},createPlacemarkXML:function(a){var b=this.createElementNS(this.kmlns,"name"),c=a.style&&a.style.label?a.style.label:a.id;b.appendChild(this.createTextNode(a.attributes.name||c));var d=this.createElementNS(this.kmlns,"description");d.appendChild(this.createTextNode(a.attributes.description||this.placemarksDesc));c=this.createElementNS(this.kmlns,"Placemark");null!=
+a.fid&&c.setAttribute("id",a.fid);c.appendChild(b);c.appendChild(d);b=this.buildGeometryNode(a.geometry);c.appendChild(b);a.attributes&&(a=this.buildExtendedData(a.attributes))&&c.appendChild(a);return c},buildGeometryNode:function(a){var b=a.CLASS_NAME,b=b.substring(b.lastIndexOf(".")+1),b=this.buildGeometry[b.toLowerCase()],c=null;b&&(c=b.apply(this,[a]));return c},buildGeometry:{point:function(a){var b=this.createElementNS(this.kmlns,"Point");b.appendChild(this.buildCoordinatesNode(a));return b},
+multipoint:function(a){return this.buildGeometry.collection.apply(this,[a])},linestring:function(a){var b=this.createElementNS(this.kmlns,"LineString");b.appendChild(this.buildCoordinatesNode(a));return b},multilinestring:function(a){return this.buildGeometry.collection.apply(this,[a])},linearring:function(a){var b=this.createElementNS(this.kmlns,"LinearRing");b.appendChild(this.buildCoordinatesNode(a));return b},polygon:function(a){var b=this.createElementNS(this.kmlns,"Polygon");a=a.components;
+for(var c,d,e=0,f=a.length;e<f;++e)c=0==e?"outerBoundaryIs":"innerBoundaryIs",c=this.createElementNS(this.kmlns,c),d=this.buildGeometry.linearring.apply(this,[a[e]]),c.appendChild(d),b.appendChild(c);return b},multipolygon:function(a){return this.buildGeometry.collection.apply(this,[a])},collection:function(a){for(var b=this.createElementNS(this.kmlns,"MultiGeometry"),c,d=0,e=a.components.length;d<e;++d)(c=this.buildGeometryNode.apply(this,[a.components[d]]))&&b.appendChild(c);return b}},buildCoordinatesNode:function(a){var b=
+this.createElementNS(this.kmlns,"coordinates"),c;if(c=a.components){for(var d=c.length,e=Array(d),f=0;f<d;++f)a=c[f],e[f]=this.buildCoordinates(a);c=e.join(" ")}else c=this.buildCoordinates(a);c=this.createTextNode(c);b.appendChild(c);return b},buildCoordinates:function(a){this.internalProjection&&this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));return a.x+","+a.y},buildExtendedData:function(a){var b=this.createElementNS(this.kmlns,"ExtendedData"),
+c;for(c in a)if(a[c]&&"name"!=c&&"description"!=c&&"styleUrl"!=c){var d=this.createElementNS(this.kmlns,"Data");d.setAttribute("name",c);var e=this.createElementNS(this.kmlns,"value");if("object"==typeof a[c]){if(a[c].value&&e.appendChild(this.createTextNode(a[c].value)),a[c].displayName){var f=this.createElementNS(this.kmlns,"displayName");f.appendChild(this.getXMLDoc().createCDATASection(a[c].displayName));d.appendChild(f)}}else e.appendChild(this.createTextNode(a[c]));d.appendChild(e);b.appendChild(d)}return this.isSimpleContent(b)?
+null:b},CLASS_NAME:"OpenLayers.Format.KML"});OpenLayers.Protocol.WFS.v1=OpenLayers.Class(OpenLayers.Protocol,{version:null,srsName:"EPSG:4326",featureType:null,featureNS:null,geometryName:"the_geom",schema:null,featurePrefix:"feature",formatOptions:null,readFormat:null,readOptions:null,initialize:function(a){OpenLayers.Protocol.prototype.initialize.apply(this,[a]);a.format||(this.format=OpenLayers.Format.WFST(OpenLayers.Util.extend({version:this.version,featureType:this.featureType,featureNS:this.featureNS,featurePrefix:this.featurePrefix,geometryName:this.geometryName,
+srsName:this.srsName,schema:this.schema},this.formatOptions)));!a.geometryName&&1<parseFloat(this.format.version)&&this.setGeometryName(null)},destroy:function(){this.options&&!this.options.format&&this.format.destroy();this.format=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=OpenLayers.Util.extend({},a);OpenLayers.Util.applyDefaults(a,this.options||{});var b=new OpenLayers.Protocol.Response({requestType:"read"}),
+c=OpenLayers.Format.XML.prototype.write.apply(this.format,[this.format.writeNode("wfs:GetFeature",a)]);b.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,b,a),params:a.params,headers:a.headers,data:c});return b},setFeatureType:function(a){this.featureType=a;this.format.featureType=a},setGeometryName:function(a){this.geometryName=a;this.format.geometryName=a},handleRead:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);if(b.callback){var c=
+a.priv;200<=c.status&&300>c.status?(c=this.parseResponse(c,b.readOptions))&&!1!==c.success?(b.readOptions&&"object"==b.readOptions.output?OpenLayers.Util.extend(a,c):a.features=c,a.code=OpenLayers.Protocol.Response.SUCCESS):(a.code=OpenLayers.Protocol.Response.FAILURE,a.error=c):a.code=OpenLayers.Protocol.Response.FAILURE;b.callback.call(b.scope,a)}},parseResponse:function(a,b){var c=a.responseXML;c&&c.documentElement||(c=a.responseText);if(!c||0>=c.length)return null;c=null!==this.readFormat?this.readFormat.read(c):
+this.format.read(c,b);if(!this.featureNS){var d=this.readFormat||this.format;this.featureNS=d.featureNS;d.autoConfig=!1;this.geometryName||this.setGeometryName(d.geometryName)}return c},commit:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);var c=new OpenLayers.Protocol.Response({requestType:"commit",reqFeatures:a});c.priv=OpenLayers.Request.POST({url:b.url,headers:b.headers,data:this.format.write(a,b),callback:this.createCallback(this.handleCommit,c,b)});
+return c},handleCommit:function(a,b){if(b.callback){var c=a.priv,d=c.responseXML;d&&d.documentElement||(d=c.responseText);c=this.format.read(d)||{};a.insertIds=c.insertIds||[];c.success?a.code=OpenLayers.Protocol.Response.SUCCESS:(a.code=OpenLayers.Protocol.Response.FAILURE,a.error=c);b.callback.call(b.scope,a)}},filterDelete:function(a,b){b=OpenLayers.Util.extend({},b);OpenLayers.Util.applyDefaults(b,this.options);new OpenLayers.Protocol.Response({requestType:"commit"});var c=this.format.createElementNSPlus("wfs:Transaction",
+{attributes:{service:"WFS",version:this.version}}),d=this.format.createElementNSPlus("wfs:Delete",{attributes:{typeName:(b.featureNS?this.featurePrefix+":":"")+b.featureType}});b.featureNS&&d.setAttribute("xmlns:"+this.featurePrefix,b.featureNS);var e=this.format.writeNode("ogc:Filter",a);d.appendChild(e);c.appendChild(d);c=OpenLayers.Format.XML.prototype.write.apply(this.format,[c]);return OpenLayers.Request.POST({url:this.url,callback:b.callback||function(){},data:c})},abort:function(a){a&&a.priv.abort()},
+CLASS_NAME:"OpenLayers.Protocol.WFS.v1"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{click:{"in":"click",out:"clickout"},mousemove:{"in":"over",out:"out"},dblclick:{"in":"dblclick",out:null},mousedown:{"in":null,out:null},mouseup:{"in":null,out:null},touchstart:{"in":"click",out:"clickout"}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:!0,stopDown:!0,stopUp:!1,initialize:function(a,b,c,d){OpenLayers.Handler.prototype.initialize.apply(this,[a,c,d]);this.layer=
+b},touchstart:function(a){this.startTouch();return OpenLayers.Event.isMultiTouch(a)?!0:this.mousedown(a)},touchmove:function(a){OpenLayers.Event.preventDefault(a)},mousedown:function(a){if(OpenLayers.Event.isLeftClick(a)||OpenLayers.Event.isSingleTouch(a))this.down=a.xy;return this.handle(a)?!this.stopDown:!0},mouseup:function(a){this.up=a.xy;return this.handle(a)?!this.stopUp:!0},click:function(a){return this.handle(a)?!this.stopClick:!0},mousemove:function(a){if(!this.callbacks.over&&!this.callbacks.out)return!0;
+this.handle(a);return!0},dblclick:function(a){return!this.handle(a)},geometryTypeMatches:function(a){return null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,a.geometry.CLASS_NAME)},handle:function(a){this.feature&&!this.feature.layer&&(this.feature=null);var b=a.type,c=!1,d=!!this.feature,e="click"==b||"dblclick"==b||"touchstart"==b;(this.feature=this.layer.getFeatureFromEvent(a))&&!this.feature.layer&&(this.feature=null);this.lastFeature&&!this.lastFeature.layer&&(this.lastFeature=
+null);this.feature?("touchstart"===b&&OpenLayers.Event.preventDefault(a),a=this.feature!=this.lastFeature,this.geometryTypeMatches(this.feature)?(d&&a?(this.lastFeature&&this.triggerCallback(b,"out",[this.lastFeature]),this.triggerCallback(b,"in",[this.feature])):d&&!e||this.triggerCallback(b,"in",[this.feature]),this.lastFeature=this.feature,c=!0):(this.lastFeature&&(d&&a||e)&&this.triggerCallback(b,"out",[this.lastFeature]),this.feature=null)):this.lastFeature&&(d||e)&&this.triggerCallback(b,"out",
+[this.lastFeature]);return c},triggerCallback:function(a,b,c){if(b=this.EVENTMAP[a][b])"click"==a&&this.up&&this.down?(Math.sqrt(Math.pow(this.up.x-this.down.x,2)+Math.pow(this.up.y-this.down.y,2))<=this.clickTolerance&&this.callback(b,c),this.up=this.down=null):this.callback(b,c)},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.moveLayerToTop(),this.map.events.on({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);
+return a},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.moveLayerBack(),this.up=this.down=this.lastFeature=this.feature=null,this.map.events.un({removelayer:this.handleMapEvents,changelayer:this.handleMapEvents,scope:this}),a=!0);return a},handleMapEvents:function(a){"removelayer"!=a.type&&"order"!=a.property||this.moveLayerToTop()},moveLayerToTop:function(){var a=Math.max(this.map.Z_INDEX_BASE.Feature-1,this.layer.getZIndex())+1;this.layer.setZIndex(a)},
+moveLayerBack:function(){var a=this.layer.getZIndex()-1;a>=this.map.Z_INDEX_BASE.Feature?this.layer.setZIndex(a):this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer))},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:!0,initialize:function(a,b){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),select:new OpenLayers.Style(OpenLayers.Feature.Vector.style.select),temporary:new OpenLayers.Style(OpenLayers.Feature.Vector.style.temporary),"delete":new OpenLayers.Style(OpenLayers.Feature.Vector.style["delete"])};if(a instanceof OpenLayers.Style)this.styles["default"]=a,this.styles.select=a,this.styles.temporary=a,this.styles["delete"]=
+a;else if("object"==typeof a)for(var c in a)if(a[c]instanceof OpenLayers.Style)this.styles[c]=a[c];else if("object"==typeof a[c])this.styles[c]=new OpenLayers.Style(a[c]);else{this.styles["default"]=new OpenLayers.Style(a);this.styles.select=new OpenLayers.Style(a);this.styles.temporary=new OpenLayers.Style(a);this.styles["delete"]=new OpenLayers.Style(a);break}OpenLayers.Util.extend(this,b)},destroy:function(){for(var a in this.styles)this.styles[a].destroy();this.styles=null},createSymbolizer:function(a,
+b){a||(a=new OpenLayers.Feature.Vector);this.styles[b]||(b="default");a.renderIntent=b;var c={};this.extendDefault&&"default"!=b&&(c=this.styles["default"].createSymbolizer(a));return OpenLayers.Util.extend(c,this.styles[b].createSymbolizer(a))},addUniqueValueRules:function(a,b,c,d){var e=[],f;for(f in c)e.push(new OpenLayers.Rule({symbolizer:c[f],context:d,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:b,value:f})}));this.styles[a].addRules(e)},CLASS_NAME:"OpenLayers.StyleMap"});OpenLayers.Layer.Vector=OpenLayers.Class(OpenLayers.Layer,{isBaseLayer:!1,isFixed:!1,features:null,filter:null,selectedFeatures:null,unrenderedFeatures:null,reportError:!0,style:null,styleMap:null,strategies:null,protocol:null,renderers:["SVG","VML","Canvas"],renderer:null,rendererOptions:null,geometryType:null,drawn:!1,ratio:1,initialize:function(a,b){OpenLayers.Layer.prototype.initialize.apply(this,arguments);this.renderer&&this.renderer.supported()||this.assignRenderer();this.renderer&&this.renderer.supported()||
+(this.renderer=null,this.displayError());this.styleMap||(this.styleMap=new OpenLayers.StyleMap);this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies)for(var c=0,d=this.strategies.length;c<d;c++)this.strategies[c].setLayer(this)},destroy:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoDestroy&&a.destroy();this.strategies=null}this.protocol&&(this.protocol.autoDestroy&&this.protocol.destroy(),this.protocol=
+null);this.destroyFeatures();this.unrenderedFeatures=this.selectedFeatures=this.features=null;this.renderer&&this.renderer.destroy();this.drawn=this.geometryType=this.renderer=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments)},clone:function(a){null==a&&(a=new OpenLayers.Layer.Vector(this.name,this.getOptions()));a=OpenLayers.Layer.prototype.clone.apply(this,[a]);for(var b=this.features,c=b.length,d=Array(c),e=0;e<c;++e)d[e]=b[e].clone();a.features=d;return a},refresh:function(a){this.calculateInRange()&&
+this.visibility&&this.events.triggerEvent("refresh",a)},assignRenderer:function(){for(var a=0,b=this.renderers.length;a<b;a++){var c=this.renderers[a];if((c="function"==typeof c?c:OpenLayers.Renderer[c])&&c.prototype.supported()){this.renderer=new c(this.div,this.rendererOptions);break}}},displayError:function(){this.reportError&&OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{renderers:this.renderers.join("\n")}))},setMap:function(a){OpenLayers.Layer.prototype.setMap.apply(this,
+arguments);if(this.renderer){this.renderer.map=this.map;var b=this.map.getSize();b.w*=this.ratio;b.h*=this.ratio;this.renderer.setSize(b)}else this.map.removeLayer(this)},afterAdd:function(){if(this.strategies){var a,b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.activate()}},removeMap:function(a){this.drawn=!1;if(this.strategies){var b,c;b=0;for(c=this.strategies.length;b<c;b++)a=this.strategies[b],a.autoActivate&&a.deactivate()}},onMapResize:function(){OpenLayers.Layer.prototype.onMapResize.apply(this,
+arguments);var a=this.map.getSize();a.w*=this.ratio;a.h*=this.ratio;this.renderer.setSize(a)},moveTo:function(a,b,c){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var d=!0;if(!c){this.renderer.root.style.visibility="hidden";var d=this.map.getSize(),e=d.w,d=d.h,e=e/2*this.ratio-e/2,d=d/2*this.ratio-d/2,e=e+this.map.layerContainerOriginPx.x,e=-Math.round(e),d=d+this.map.layerContainerOriginPx.y,d=-Math.round(d);this.div.style.left=e+"px";this.div.style.top=d+"px";e=this.map.getExtent().scale(this.ratio);
+d=this.renderer.setExtent(e,b);this.renderer.root.style.visibility="visible";!0===OpenLayers.IS_GECKO&&(this.div.scrollLeft=this.div.scrollLeft);if(!b&&d)for(var f in this.unrenderedFeatures)e=this.unrenderedFeatures[f],this.drawFeature(e)}if(!this.drawn||b||!d)for(this.drawn=!0,f=0,d=this.features.length;f<d;f++)this.renderer.locked=f!==d-1,e=this.features[f],this.drawFeature(e)},display:function(a){OpenLayers.Layer.prototype.display.apply(this,arguments);var b=this.div.style.display;b!=this.renderer.root.style.display&&
+(this.renderer.root.style.display=b)},addFeatures:function(a,b){OpenLayers.Util.isArray(a)||(a=[a]);var c=!b||!b.silent;if(c){var d={features:a};if(!1===this.events.triggerEvent("beforefeaturesadded",d))return;a=d.features}for(var d=[],e=0,f=a.length;e<f;e++){this.renderer.locked=e!=a.length-1?!0:!1;var g=a[e];if(this.geometryType&&!(g.geometry instanceof this.geometryType))throw new TypeError("addFeatures: component should be an "+this.geometryType.prototype.CLASS_NAME);g.layer=this;!g.style&&this.style&&
+(g.style=OpenLayers.Util.extend({},this.style));if(c){if(!1===this.events.triggerEvent("beforefeatureadded",{feature:g}))continue;this.preFeatureInsert(g)}d.push(g);this.features.push(g);this.drawFeature(g);c&&(this.events.triggerEvent("featureadded",{feature:g}),this.onFeatureInsert(g))}c&&this.events.triggerEvent("featuresadded",{features:d})},removeFeatures:function(a,b){if(a&&0!==a.length){if(a===this.features)return this.removeAllFeatures(b);OpenLayers.Util.isArray(a)||(a=[a]);a===this.selectedFeatures&&
+(a=a.slice());var c=!b||!b.silent;c&&this.events.triggerEvent("beforefeaturesremoved",{features:a});for(var d=a.length-1;0<=d;d--){this.renderer.locked=0!=d&&a[d-1].geometry?!0:!1;var e=a[d];delete this.unrenderedFeatures[e.id];c&&this.events.triggerEvent("beforefeatureremoved",{feature:e});this.features=OpenLayers.Util.removeItem(this.features,e);e.layer=null;e.geometry&&this.renderer.eraseFeatures(e);-1!=OpenLayers.Util.indexOf(this.selectedFeatures,e)&&OpenLayers.Util.removeItem(this.selectedFeatures,
+e);c&&this.events.triggerEvent("featureremoved",{feature:e})}c&&this.events.triggerEvent("featuresremoved",{features:a})}},removeAllFeatures:function(a){a=!a||!a.silent;var b=this.features;a&&this.events.triggerEvent("beforefeaturesremoved",{features:b});for(var c,d=b.length-1;0<=d;d--)c=b[d],a&&this.events.triggerEvent("beforefeatureremoved",{feature:c}),c.layer=null,a&&this.events.triggerEvent("featureremoved",{feature:c});this.renderer.clear();this.features=[];this.unrenderedFeatures={};this.selectedFeatures=
+[];a&&this.events.triggerEvent("featuresremoved",{features:b})},destroyFeatures:function(a,b){void 0==a&&(a=this.features);if(a){this.removeFeatures(a,b);for(var c=a.length-1;0<=c;c--)a[c].destroy()}},drawFeature:function(a,b){if(this.drawn){if("object"!=typeof b){b||a.state!==OpenLayers.State.DELETE||(b="delete");var c=b||a.renderIntent;(b=a.style||this.style)||(b=this.styleMap.createSymbolizer(a,c))}c=this.renderer.drawFeature(a,b);!1===c||null===c?this.unrenderedFeatures[a.id]=a:delete this.unrenderedFeatures[a.id]}},
+eraseFeatures:function(a){this.renderer.eraseFeatures(a)},getFeatureFromEvent:function(a){if(!this.renderer)throw Error("getFeatureFromEvent called on layer with no renderer. This usually means you destroyed a layer, but not some handler which is associated with it.");var b=null;(a=this.renderer.getFeatureIdFromEvent(a))&&(b="string"===typeof a?this.getFeatureById(a):a);return b},getFeatureBy:function(a,b){for(var c=null,d=0,e=this.features.length;d<e;++d)if(this.features[d][a]==b){c=this.features[d];
+break}return c},getFeatureById:function(a){return this.getFeatureBy("id",a)},getFeatureByFid:function(a){return this.getFeatureBy("fid",a)},getFeaturesByAttribute:function(a,b){var c,d,e=this.features.length,f=[];for(c=0;c<e;c++)(d=this.features[c])&&d.attributes&&d.attributes[a]===b&&f.push(d);return f},onFeatureInsert:function(a){},preFeatureInsert:function(a){},getDataExtent:function(){var a=null,b=this.features;if(b&&0<b.length)for(var c=null,d=0,e=b.length;d<e;d++)if(c=b[d].geometry)null===a&&
+(a=new OpenLayers.Bounds),a.extend(c.getBounds());return a},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:!1,layers:null,display:function(){},getFeatureFromEvent:function(a){for(var b=this.layers,c,d=0;d<b.length;d++)if(c=b[d].getFeatureFromEvent(a))return c},setMap:function(a){OpenLayers.Layer.Vector.prototype.setMap.apply(this,arguments);this.collectRoots();a.events.register("changelayer",this,this.handleChangeLayer)},removeMap:function(a){a.events.unregister("changelayer",this,this.handleChangeLayer);
+this.resetRoots();OpenLayers.Layer.Vector.prototype.removeMap.apply(this,arguments)},collectRoots:function(){for(var a,b=0;b<this.map.layers.length;++b)a=this.map.layers[b],-1!=OpenLayers.Util.indexOf(this.layers,a)&&a.renderer.moveRoot(this.renderer)},resetRoots:function(){for(var a,b=0;b<this.layers.length;++b)a=this.layers[b],this.renderer&&a.renderer.getRenderLayerId()==this.id&&this.renderer.moveRoot(a.renderer)},handleChangeLayer:function(a){var b=a.layer;"order"==a.property&&-1!=OpenLayers.Util.indexOf(this.layers,
+b)&&(this.resetRoots(),this.collectRoots())},CLASS_NAME:"OpenLayers.Layer.Vector.RootContainer"});OpenLayers.Control.SelectFeature=OpenLayers.Class(OpenLayers.Control,{multipleKey:null,toggleKey:null,multiple:!1,clickout:!0,toggle:!1,hover:!1,highlightOnly:!1,box:!1,onBeforeSelect:function(){},onSelect:function(){},onUnselect:function(){},scope:null,geometryTypes:null,layer:null,layers:null,callbacks:null,selectStyle:null,renderIntent:"select",handlers:null,initialize:function(a,b){OpenLayers.Control.prototype.initialize.apply(this,[b]);null===this.scope&&(this.scope=this);this.initLayer(a);var c=
+{click:this.clickFeature,clickout:this.clickoutFeature};this.hover&&(c.over=this.overFeature,c.out=this.outFeature);this.callbacks=OpenLayers.Util.extend(c,this.callbacks);this.handlers={feature:new OpenLayers.Handler.Feature(this,this.layer,this.callbacks,{geometryTypes:this.geometryTypes})};this.box&&(this.handlers.box=new OpenLayers.Handler.Box(this,{done:this.selectBox},{boxDivClassName:"olHandlerBoxSelectFeature"}))},initLayer:function(a){OpenLayers.Util.isArray(a)?(this.layers=a,this.layer=
+new OpenLayers.Layer.Vector.RootContainer(this.id+"_container",{layers:a})):this.layer=a},destroy:function(){this.active&&this.layers&&this.map.removeLayer(this.layer);OpenLayers.Control.prototype.destroy.apply(this,arguments);this.layers&&this.layer.destroy()},activate:function(){this.active||(this.layers&&this.map.addLayer(this.layer),this.handlers.feature.activate(),this.box&&this.handlers.box&&this.handlers.box.activate());return OpenLayers.Control.prototype.activate.apply(this,arguments)},deactivate:function(){this.active&&
+(this.handlers.feature.deactivate(),this.handlers.box&&this.handlers.box.deactivate(),this.layers&&this.map.removeLayer(this.layer));return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},unselectAll:function(a){var b=this.layers||[this.layer],c,d,e,f;for(e=0;e<b.length;++e)if(c=b[e],f=0,null!=c.selectedFeatures)for(;c.selectedFeatures.length>f;)d=c.selectedFeatures[f],a&&a.except==d?++f:this.unselect(d)},clickFeature:function(a){this.hover||(-1<OpenLayers.Util.indexOf(a.layer.selectedFeatures,
+a)?this.toggleSelect()?this.unselect(a):this.multipleSelect()||this.unselectAll({except:a}):(this.multipleSelect()||this.unselectAll({except:a}),this.select(a)))},multipleSelect:function(){return this.multiple||this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]},toggleSelect:function(){return this.toggle||this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]},clickoutFeature:function(a){!this.hover&&this.clickout&&this.unselectAll()},overFeature:function(a){var b=
+a.layer;this.hover&&(this.highlightOnly?this.highlight(a):-1==OpenLayers.Util.indexOf(b.selectedFeatures,a)&&this.select(a))},outFeature:function(a){if(this.hover)if(this.highlightOnly){if(a._lastHighlighter==this.id)if(a._prevHighlighter&&a._prevHighlighter!=this.id){delete a._lastHighlighter;var b=this.map.getControl(a._prevHighlighter);b&&b.highlight(a)}else this.unhighlight(a)}else this.unselect(a)},highlight:function(a){var b=a.layer;!1!==this.events.triggerEvent("beforefeaturehighlighted",{feature:a})&&
+(a._prevHighlighter=a._lastHighlighter,a._lastHighlighter=this.id,b.drawFeature(a,this.selectStyle||this.renderIntent),this.events.triggerEvent("featurehighlighted",{feature:a}))},unhighlight:function(a){var b=a.layer;void 0==a._prevHighlighter?delete a._lastHighlighter:(a._prevHighlighter!=this.id&&(a._lastHighlighter=a._prevHighlighter),delete a._prevHighlighter);b.drawFeature(a,a.style||a.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:a})},select:function(a){var b=
+this.onBeforeSelect.call(this.scope,a),c=a.layer;!1!==b&&(b=c.events.triggerEvent("beforefeatureselected",{feature:a}),!1!==b&&(c.selectedFeatures.push(a),this.highlight(a),this.handlers.feature.lastFeature||(this.handlers.feature.lastFeature=c.selectedFeatures[0]),c.events.triggerEvent("featureselected",{feature:a}),this.onSelect.call(this.scope,a)))},unselect:function(a){var b=a.layer;this.unhighlight(a);OpenLayers.Util.removeItem(b.selectedFeatures,a);b.events.triggerEvent("featureunselected",
+{feature:a});this.onUnselect.call(this.scope,a)},selectBox:function(a){if(a instanceof OpenLayers.Bounds){var b=this.map.getLonLatFromPixel({x:a.left,y:a.bottom});a=this.map.getLonLatFromPixel({x:a.right,y:a.top});b=new OpenLayers.Bounds(b.lon,b.lat,a.lon,a.lat);this.multipleSelect()||this.unselectAll();a=this.multiple;this.multiple=!0;var c=this.layers||[this.layer];this.events.triggerEvent("boxselectionstart",{layers:c});for(var d,e=0;e<c.length;++e){d=c[e];for(var f=0,g=d.features.length;f<g;++f){var h=
+d.features[f];h.getVisibility()&&(null==this.geometryTypes||-1<OpenLayers.Util.indexOf(this.geometryTypes,h.geometry.CLASS_NAME))&&b.toGeometry().intersects(h.geometry)&&-1==OpenLayers.Util.indexOf(d.selectedFeatures,h)&&this.select(h)}}this.multiple=a;this.events.triggerEvent("boxselectionend",{layers:c})}},setMap:function(a){this.handlers.feature.setMap(a);this.box&&this.handlers.box.setMap(a);OpenLayers.Control.prototype.setMap.apply(this,arguments)},setLayer:function(a){var b=this.active;this.unselectAll();
+this.deactivate();this.layers&&(this.layer.destroy(),this.layers=null);this.initLayer(a);this.handlers.feature.layer=this.layer;b&&this.activate()},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Handler.Point=OpenLayers.Class(OpenLayers.Handler,{point:null,layer:null,multi:!1,citeCompliant:!1,mouseDown:!1,stoppedDown:null,lastDown:null,lastUp:null,persist:!1,stopDown:!1,stopUp:!1,layerOptions:null,pixelTolerance:5,lastTouchPx:null,initialize:function(a,b,c){c&&c.layerOptions&&c.layerOptions.styleMap||(this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style["default"],{}));OpenLayers.Handler.prototype.initialize.apply(this,arguments)},activate:function(){if(!OpenLayers.Handler.prototype.activate.apply(this,
+arguments))return!1;var a=OpenLayers.Util.extend({displayInLayerSwitcher:!1,calculateInRange:OpenLayers.Function.True,wrapDateLine:this.citeCompliant},this.layerOptions);this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,a);this.map.addLayer(this.layer);return!0},createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.callback("create",[this.point.geometry,this.point]);this.point.geometry.clearBounds();
+this.layer.addFeatures([this.point],{silent:!0})},deactivate:function(){if(!OpenLayers.Handler.prototype.deactivate.apply(this,arguments))return!1;this.cancel();null!=this.layer.map&&(this.destroyFeature(!0),this.layer.destroy(!1));this.layer=null;return!0},destroyFeature:function(a){!this.layer||!a&&this.persist||this.layer.destroyFeatures();this.point=null},destroyPersistedFeature:function(){var a=this.layer;a&&1<a.features.length&&this.layer.features[0].destroy()},finalize:function(a){this.mouseDown=
+!1;this.lastTouchPx=this.lastUp=this.lastDown=null;this.callback(a?"cancel":"done",[this.geometryClone()]);this.destroyFeature(a)},cancel:function(){this.finalize(!0)},click:function(a){OpenLayers.Event.stop(a);return!1},dblclick:function(a){OpenLayers.Event.stop(a);return!1},modifyFeature:function(a){this.point||this.createFeature(a);a=this.layer.getLonLatFromViewPortPx(a);this.point.geometry.x=a.lon;this.point.geometry.y=a.lat;this.callback("modify",[this.point.geometry,this.point,!1]);this.point.geometry.clearBounds();
+this.drawFeature()},drawFeature:function(){this.layer.drawFeature(this.point,this.style)},getGeometry:function(){var a=this.point&&this.point.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiPoint([a]));return a},geometryClone:function(){var a=this.getGeometry();return a&&a.clone()},mousedown:function(a){return this.down(a)},touchstart:function(a){this.startTouch();this.lastTouchPx=a.xy;return this.down(a)},mousemove:function(a){return this.move(a)},touchmove:function(a){this.lastTouchPx=a.xy;
+return this.move(a)},mouseup:function(a){return this.up(a)},touchend:function(a){a.xy=this.lastTouchPx;return this.up(a)},down:function(a){this.mouseDown=!0;this.lastDown=a.xy;this.touch||this.modifyFeature(a.xy);this.stoppedDown=this.stopDown;return!this.stopDown},move:function(a){this.touch||this.mouseDown&&!this.stoppedDown||this.modifyFeature(a.xy);return!0},up:function(a){this.mouseDown=!1;this.stoppedDown=this.stopDown;if(!this.checkModifiers(a)||this.lastUp&&this.lastUp.equals(a.xy)||!this.lastDown||
+!this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance))return!0;this.touch&&this.modifyFeature(a.xy);this.persist&&this.destroyPersistedFeature();this.lastUp=a.xy;this.finalize();return!this.stopUp},mouseout:function(a){OpenLayers.Util.mouseLeft(a,this.map.viewPortDiv)&&(this.stoppedDown=this.stopDown,this.mouseDown=!1)},passesTolerance:function(a,b,c){var d=!0;null!=c&&a&&b&&a.distanceTo(b)>c&&(d=!1);return d},CLASS_NAME:"OpenLayers.Handler.Point"});OpenLayers.Handler.Path=OpenLayers.Class(OpenLayers.Handler.Point,{line:null,maxVertices:null,doubleTouchTolerance:20,freehand:!1,freehandToggle:"shiftKey",timerId:null,redoStack:null,createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([this.point.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);
+this.point.geometry.clearBounds();this.layer.addFeatures([this.line,this.point],{silent:!0})},destroyFeature:function(a){OpenLayers.Handler.Point.prototype.destroyFeature.call(this,a);this.line=null},destroyPersistedFeature:function(){var a=this.layer;a&&2<a.features.length&&this.layer.features[0].destroy()},removePoint:function(){this.point&&this.layer.removeFeatures([this.point])},addPoint:function(a){this.layer.removeFeatures([this.point]);a=this.layer.getLonLatFromViewPortPx(a);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(a.lon,
+a.lat));this.line.geometry.addComponent(this.point.geometry,this.line.geometry.components.length);this.layer.addFeatures([this.point]);this.callback("point",[this.point.geometry,this.getGeometry()]);this.callback("modify",[this.point.geometry,this.getSketch()]);this.drawFeature();delete this.redoStack},insertXY:function(a,b){this.line.geometry.addComponent(new OpenLayers.Geometry.Point(a,b),this.getCurrentPointIndex());this.drawFeature();delete this.redoStack},insertDeltaXY:function(a,b){var c=this.getCurrentPointIndex()-
+1,c=this.line.geometry.components[c];!c||(isNaN(c.x)||isNaN(c.y))||this.insertXY(c.x+a,c.y+b)},insertDirectionLength:function(a,b){a*=Math.PI/180;var c=b*Math.cos(a),d=b*Math.sin(a);this.insertDeltaXY(c,d)},insertDeflectionLength:function(a,b){var c=this.getCurrentPointIndex()-1;if(0<c){var d=this.line.geometry.components[c],c=this.line.geometry.components[c-1],d=Math.atan2(d.y-c.y,d.x-c.x);this.insertDirectionLength(180*d/Math.PI+a,b)}},getCurrentPointIndex:function(){return this.line.geometry.components.length-
+1},undo:function(){var a=this.line.geometry,b=a.components,c=this.getCurrentPointIndex()-1,d=b[c],e=a.removeComponent(d);e&&(this.touch&&0<c&&(b=a.components,a=b[c-1],c=this.getCurrentPointIndex(),b=b[c],b.x=a.x,b.y=a.y),this.redoStack||(this.redoStack=[]),this.redoStack.push(d),this.drawFeature());return e},redo:function(){var a=this.redoStack&&this.redoStack.pop();a&&(this.line.geometry.addComponent(a,this.getCurrentPointIndex()),this.drawFeature());return!!a},freehandMode:function(a){return this.freehandToggle&&
+a[this.freehandToggle]?!this.freehand:this.freehand},modifyFeature:function(a,b){this.line||this.createFeature(a);var c=this.layer.getLonLatFromViewPortPx(a);this.point.geometry.x=c.lon;this.point.geometry.y=c.lat;this.callback("modify",[this.point.geometry,this.getSketch(),b]);this.point.geometry.clearBounds();this.drawFeature()},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style)},getSketch:function(){return this.line},getGeometry:function(){var a=
+this.line&&this.line.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiLineString([a]));return a},touchstart:function(a){if(this.timerId&&this.passesTolerance(this.lastTouchPx,a.xy,this.doubleTouchTolerance))return this.finishGeometry(),window.clearTimeout(this.timerId),this.timerId=null,!1;this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);this.timerId=window.setTimeout(OpenLayers.Function.bind(function(){this.timerId=null},this),300);return OpenLayers.Handler.Point.prototype.touchstart.call(this,
+a)},down:function(a){var b=this.stopDown;this.freehandMode(a)&&(b=!0,this.touch&&(this.modifyFeature(a.xy,!!this.lastUp),OpenLayers.Event.stop(a)));this.touch||this.lastDown&&this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance)||this.modifyFeature(a.xy,!!this.lastUp);this.mouseDown=!0;this.lastDown=a.xy;this.stoppedDown=b;return!b},move:function(a){if(this.stoppedDown&&this.freehandMode(a))return this.persist&&this.destroyPersistedFeature(),this.maxVertices&&this.line&&this.line.geometry.components.length===
+this.maxVertices?(this.removePoint(),this.finalize()):this.addPoint(a.xy),!1;this.touch||this.mouseDown&&!this.stoppedDown||this.modifyFeature(a.xy,!!this.lastUp);return!0},up:function(a){!this.mouseDown||this.lastUp&&this.lastUp.equals(a.xy)||(this.stoppedDown&&this.freehandMode(a)?(this.persist&&this.destroyPersistedFeature(),this.removePoint(),this.finalize()):this.passesTolerance(this.lastDown,a.xy,this.pixelTolerance)&&(this.touch&&this.modifyFeature(a.xy),null==this.lastUp&&this.persist&&this.destroyPersistedFeature(),
+this.addPoint(a.xy),this.lastUp=a.xy,this.line.geometry.components.length===this.maxVertices+1&&this.finishGeometry()));this.stoppedDown=this.stopDown;this.mouseDown=!1;return!this.stopUp},finishGeometry:function(){this.line.geometry.removeComponent(this.line.geometry.components[this.line.geometry.components.length-1]);this.removePoint();this.finalize()},dblclick:function(a){this.freehandMode(a)||this.finishGeometry();return!1},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Control.Attribution=OpenLayers.Class(OpenLayers.Control,{separator:", ",template:"${layers}",destroy:function(){this.map.events.un({removelayer:this.updateAttribution,addlayer:this.updateAttribution,changelayer:this.updateAttribution,changebaselayer:this.updateAttribution,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments)},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);this.map.events.on({changebaselayer:this.updateAttribution,changelayer:this.updateAttribution,
+addlayer:this.updateAttribution,removelayer:this.updateAttribution,scope:this});this.updateAttribution();return this.div},updateAttribution:function(){var a=[];if(this.map&&this.map.layers){for(var b=0,c=this.map.layers.length;b<c;b++){var d=this.map.layers[b];d.attribution&&d.getVisibility()&&-1===OpenLayers.Util.indexOf(a,d.attribution)&&a.push(d.attribution)}this.div.innerHTML=OpenLayers.String.format(this.template,{layers:a.join(this.separator)})}},CLASS_NAME:"OpenLayers.Control.Attribution"});OpenLayers.Kinetic=OpenLayers.Class({threshold:0,deceleration:0.0035,nbPoints:100,delay:200,points:void 0,timerId:void 0,initialize:function(a){OpenLayers.Util.extend(this,a)},begin:function(){OpenLayers.Animation.stop(this.timerId);this.timerId=void 0;this.points=[]},update:function(a){this.points.unshift({xy:a,tick:(new Date).getTime()});this.points.length>this.nbPoints&&this.points.pop()},end:function(a){for(var b,c=(new Date).getTime(),d=0,e=this.points.length,f;d<e;d++){f=this.points[d];if(c-
+f.tick>this.delay)break;b=f}if(b&&(d=(new Date).getTime()-b.tick,c=Math.sqrt(Math.pow(a.x-b.xy.x,2)+Math.pow(a.y-b.xy.y,2)),d=c/d,!(0==d||d<this.threshold)))return c=Math.asin((a.y-b.xy.y)/c),b.xy.x<=a.x&&(c=Math.PI-c),{speed:d,theta:c}},move:function(a,b){var c=a.speed,d=Math.cos(a.theta),e=-Math.sin(a.theta),f=(new Date).getTime(),g=0,h=0;this.timerId=OpenLayers.Animation.start(OpenLayers.Function.bind(function(){if(null!=this.timerId){var a=(new Date).getTime()-f,l=-this.deceleration*Math.pow(a,
+2)/2+c*a,m=l*d,l=l*e,p,n;p=!1;0>=-this.deceleration*a+c&&(OpenLayers.Animation.stop(this.timerId),this.timerId=null,p=!0);a=m-g;n=l-h;g=m;h=l;b(a,n,p)}},this))},CLASS_NAME:"OpenLayers.Kinetic"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1",request:"GetMap",styles:"",format:"image/jpeg"},isBaseLayer:!0,encodeBBOX:!1,noMagic:!1,yx:{},initialize:function(a,b,c,d){var e=[];c=OpenLayers.Util.upperCaseObject(c);1.3<=parseFloat(c.VERSION)&&!c.EXCEPTIONS&&(c.EXCEPTIONS="INIMAGE");e.push(a,b,c,d);OpenLayers.Layer.Grid.prototype.initialize.apply(this,e);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));
+!this.noMagic&&(this.params.TRANSPARENT&&"true"==this.params.TRANSPARENT.toString().toLowerCase())&&(null!=d&&d.isBaseLayer||(this.isBaseLayer=!1),"image/jpeg"==this.params.FORMAT&&(this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png"))},clone:function(a){null==a&&(a=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.getOptions()));return a=OpenLayers.Layer.Grid.prototype.clone.apply(this,[a])},reverseAxisOrder:function(){var a=this.projection.getCode();return 1.3<=parseFloat(this.params.VERSION)&&
+!!(this.yx[a]||OpenLayers.Projection.defaults[a]&&OpenLayers.Projection.defaults[a].yx)},getURL:function(a){a=this.adjustBounds(a);var b=this.getImageSize(),c={},d=this.reverseAxisOrder();c.BBOX=this.encodeBBOX?a.toBBOX(null,d):a.toArray(d);c.WIDTH=b.w;c.HEIGHT=b.h;return this.getFullRequestString(c)},mergeNewParams:function(a){a=[OpenLayers.Util.upperCaseObject(a)];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,a)},getFullRequestString:function(a,b){var c=this.map.getProjectionObject(),
+c=this.projection&&this.projection.equals(c)?this.projection.getCode():c.getCode(),c="none"==c?null:c;1.3<=parseFloat(this.params.VERSION)?this.params.CRS=c:this.params.SRS=c;"boolean"==typeof this.params.TRANSPARENT&&(a.TRANSPARENT=this.params.TRANSPARENT?"TRUE":"FALSE");return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments)},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.Renderer.SVG=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"http://www.w3.org/2000/svg",xlinkns:"http://www.w3.org/1999/xlink",MAX_PIXEL:15E3,translationParameters:null,symbolMetrics:null,initialize:function(a){this.supported()&&(OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments),this.translationParameters={x:0,y:0},this.symbolMetrics={})},supported:function(){return document.implementation&&(document.implementation.hasFeature("org.w3c.svg","1.0")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#SVG",
+"1.1")||document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1"))},inValidRange:function(a,b,c){a+=c?0:this.translationParameters.x;b+=c?0:this.translationParameters.y;return a>=-this.MAX_PIXEL&&a<=this.MAX_PIXEL&&b>=-this.MAX_PIXEL&&b<=this.MAX_PIXEL},setExtent:function(a,b){var c=OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments),d=this.getResolution(),e=-a.left/d,d=a.top/d;if(b)return this.left=e,this.top=d,this.rendererRoot.setAttributeNS(null,
+"viewBox","0 0 "+this.size.w+" "+this.size.h),this.translate(this.xOffset,0),!0;(e=this.translate(e-this.left+this.xOffset,d-this.top))||this.setExtent(a,!0);return c&&e},translate:function(a,b){if(this.inValidRange(a,b,!0)){var c="";if(a||b)c="translate("+a+","+b+")";this.root.setAttributeNS(null,"transform",c);this.translationParameters={x:a,y:b};return!0}return!1},setSize:function(a){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"width",this.size.w);
+this.rendererRoot.setAttributeNS(null,"height",this.size.h)},getNodeType:function(a,b){var c=null;switch(a.CLASS_NAME){case "OpenLayers.Geometry.Point":c=b.externalGraphic?"image":this.isComplexSymbol(b.graphicName)?"svg":"circle";break;case "OpenLayers.Geometry.Rectangle":c="rect";break;case "OpenLayers.Geometry.LineString":c="polyline";break;case "OpenLayers.Geometry.LinearRing":c="polygon";break;case "OpenLayers.Geometry.Polygon":case "OpenLayers.Geometry.Curve":c="path"}return c},setStyle:function(a,
+b,c){b=b||a._style;c=c||a._options;var d=b.title||b.graphicTitle;if(d){a.setAttributeNS(null,"title",d);var e=a.getElementsByTagName("title");0<e.length?e[0].firstChild.textContent=d:(e=this.nodeFactory(null,"title"),e.textContent=d,a.appendChild(e))}var e=parseFloat(a.getAttributeNS(null,"r")),d=1,f;if("OpenLayers.Geometry.Point"==a._geometryClass&&e){a.style.visibility="";if(!1===b.graphic)a.style.visibility="hidden";else if(b.externalGraphic){f=this.getPosition(a);b.graphicWidth&&b.graphicHeight&&
+a.setAttributeNS(null,"preserveAspectRatio","none");var e=b.graphicWidth||b.graphicHeight,g=b.graphicHeight||b.graphicWidth,e=e?e:2*b.pointRadius,g=g?g:2*b.pointRadius,h=void 0!=b.graphicYOffset?b.graphicYOffset:-(0.5*g),k=b.graphicOpacity||b.fillOpacity;a.setAttributeNS(null,"x",(f.x+(void 0!=b.graphicXOffset?b.graphicXOffset:-(0.5*e))).toFixed());a.setAttributeNS(null,"y",(f.y+h).toFixed());a.setAttributeNS(null,"width",e);a.setAttributeNS(null,"height",g);a.setAttributeNS(this.xlinkns,"xlink:href",
+b.externalGraphic);a.setAttributeNS(null,"style","opacity: "+k);a.onclick=OpenLayers.Event.preventDefault}else if(this.isComplexSymbol(b.graphicName)){var e=3*b.pointRadius,g=2*e,l=this.importSymbol(b.graphicName);f=this.getPosition(a);d=3*this.symbolMetrics[l.id][0]/g;h=a.parentNode;k=a.nextSibling;h&&h.removeChild(a);a.firstChild&&a.removeChild(a.firstChild);a.appendChild(l.firstChild.cloneNode(!0));a.setAttributeNS(null,"viewBox",l.getAttributeNS(null,"viewBox"));a.setAttributeNS(null,"width",
+g);a.setAttributeNS(null,"height",g);a.setAttributeNS(null,"x",f.x-e);a.setAttributeNS(null,"y",f.y-e);k?h.insertBefore(a,k):h&&h.appendChild(a)}else a.setAttributeNS(null,"r",b.pointRadius);e=b.rotation;void 0===e&&void 0===a._rotation||!f||(a._rotation=e,e|=0,"svg"!==a.nodeName?a.setAttributeNS(null,"transform","rotate("+e+" "+f.x+" "+f.y+")"):(f=this.symbolMetrics[l.id],a.firstChild.setAttributeNS(null,"transform","rotate("+e+" "+f[1]+" "+f[2]+")")))}c.isFilled?(a.setAttributeNS(null,"fill",b.fillColor),
+a.setAttributeNS(null,"fill-opacity",b.fillOpacity)):a.setAttributeNS(null,"fill","none");c.isStroked?(a.setAttributeNS(null,"stroke",b.strokeColor),a.setAttributeNS(null,"stroke-opacity",b.strokeOpacity),a.setAttributeNS(null,"stroke-width",b.strokeWidth*d),a.setAttributeNS(null,"stroke-linecap",b.strokeLinecap||"round"),a.setAttributeNS(null,"stroke-linejoin","round"),b.strokeDashstyle&&a.setAttributeNS(null,"stroke-dasharray",this.dashStyle(b,d))):a.setAttributeNS(null,"stroke","none");b.pointerEvents&&
+a.setAttributeNS(null,"pointer-events",b.pointerEvents);null!=b.cursor&&a.setAttributeNS(null,"cursor",b.cursor);return a},dashStyle:function(a,b){var c=a.strokeWidth*b,d=a.strokeDashstyle;switch(d){case "solid":return"none";case "dot":return[1,4*c].join();case "dash":return[4*c,4*c].join();case "dashdot":return[4*c,4*c,1,4*c].join();case "longdash":return[8*c,4*c].join();case "longdashdot":return[8*c,4*c,1,4*c].join();default:return OpenLayers.String.trim(d).replace(/\s+/g,",")}},createNode:function(a,
+b){var c=document.createElementNS(this.xmlns,a);b&&c.setAttributeNS(null,"id",b);return c},nodeTypeCompare:function(a,b){return b==a.nodeName},createRenderRoot:function(){var a=this.nodeFactory(this.container.id+"_svgRoot","svg");a.style.display="block";return a},createRoot:function(a){return this.nodeFactory(this.container.id+a,"g")},createDefs:function(){var a=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(a);return a},drawPoint:function(a,b){return this.drawCircle(a,
+b,1)},drawCircle:function(a,b,c){var d=this.getResolution(),e=(b.x-this.featureDx)/d+this.left;b=this.top-b.y/d;return this.inValidRange(e,b)?(a.setAttributeNS(null,"cx",e),a.setAttributeNS(null,"cy",b),a.setAttributeNS(null,"r",c),a):!1},drawLineString:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,"points",c.path),c.complete?a:null):!1},drawLinearRing:function(a,b){var c=this.getComponentsString(b.components);return c.path?(a.setAttributeNS(null,
+"points",c.path),c.complete?a:null):!1},drawPolygon:function(a,b){for(var c="",d=!0,e=!0,f,g,h=0,k=b.components.length;h<k;h++)c+=" M",f=this.getComponentsString(b.components[h].components," "),(g=f.path)?(c+=" "+g,e=f.complete&&e):d=!1;return d?(a.setAttributeNS(null,"d",c+" z"),a.setAttributeNS(null,"fill-rule","evenodd"),e?a:null):!1},drawRectangle:function(a,b){var c=this.getResolution(),d=(b.x-this.featureDx)/c+this.left,e=this.top-b.y/c;return this.inValidRange(d,e)?(a.setAttributeNS(null,"x",
+d),a.setAttributeNS(null,"y",e),a.setAttributeNS(null,"width",b.width/c),a.setAttributeNS(null,"height",b.height/c),a):!1},drawText:function(a,b,c){var d=!!b.labelOutlineWidth;if(d){var e=OpenLayers.Util.extend({},b);e.fontColor=e.labelOutlineColor;e.fontStrokeColor=e.labelOutlineColor;e.fontStrokeWidth=b.labelOutlineWidth;b.labelOutlineOpacity&&(e.fontOpacity=b.labelOutlineOpacity);delete e.labelOutlineWidth;this.drawText(a,e,c)}var f=this.getResolution(),e=(c.x-this.featureDx)/f+this.left,g=c.y/
+f-this.top,d=d?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX,f=this.nodeFactory(a+d,"text");f.setAttributeNS(null,"x",e);f.setAttributeNS(null,"y",-g);b.fontColor&&f.setAttributeNS(null,"fill",b.fontColor);b.fontStrokeColor&&f.setAttributeNS(null,"stroke",b.fontStrokeColor);b.fontStrokeWidth&&f.setAttributeNS(null,"stroke-width",b.fontStrokeWidth);b.fontOpacity&&f.setAttributeNS(null,"opacity",b.fontOpacity);b.fontFamily&&f.setAttributeNS(null,"font-family",b.fontFamily);b.fontSize&&f.setAttributeNS(null,
+"font-size",b.fontSize);b.fontWeight&&f.setAttributeNS(null,"font-weight",b.fontWeight);b.fontStyle&&f.setAttributeNS(null,"font-style",b.fontStyle);!0===b.labelSelect?(f.setAttributeNS(null,"pointer-events","visible"),f._featureId=a):f.setAttributeNS(null,"pointer-events","none");g=b.labelAlign||OpenLayers.Renderer.defaultSymbolizer.labelAlign;f.setAttributeNS(null,"text-anchor",OpenLayers.Renderer.SVG.LABEL_ALIGN[g[0]]||"middle");!0===OpenLayers.IS_GECKO&&f.setAttributeNS(null,"dominant-baseline",
+OpenLayers.Renderer.SVG.LABEL_ALIGN[g[1]]||"central");for(var h=b.label.split("\n"),k=h.length;f.childNodes.length>k;)f.removeChild(f.lastChild);for(var l=0;l<k;l++){var m=this.nodeFactory(a+d+"_tspan_"+l,"tspan");!0===b.labelSelect&&(m._featureId=a,m._geometry=c,m._geometryClass=c.CLASS_NAME);!1===OpenLayers.IS_GECKO&&m.setAttributeNS(null,"baseline-shift",OpenLayers.Renderer.SVG.LABEL_VSHIFT[g[1]]||"-35%");m.setAttribute("x",e);if(0==l){var p=OpenLayers.Renderer.SVG.LABEL_VFACTOR[g[1]];null==p&&
+(p=-0.5);m.setAttribute("dy",p*(k-1)+"em")}else m.setAttribute("dy","1em");m.textContent=""===h[l]?" ":h[l];m.parentNode||f.appendChild(m)}f.parentNode||this.textRoot.appendChild(f)},getComponentsString:function(a,b){for(var c=[],d=!0,e=a.length,f=[],g,h=0;h<e;h++)g=a[h],c.push(g),(g=this.getShortString(g))?f.push(g):(0<h&&this.getShortString(a[h-1])&&f.push(this.clipLine(a[h],a[h-1])),h<e-1&&this.getShortString(a[h+1])&&f.push(this.clipLine(a[h],a[h+1])),d=!1);return{path:f.join(b||","),complete:d}},
+clipLine:function(a,b){if(b.equals(a))return"";var c=this.getResolution(),d=this.MAX_PIXEL-this.translationParameters.x,e=this.MAX_PIXEL-this.translationParameters.y,f=(b.x-this.featureDx)/c+this.left,g=this.top-b.y/c,h=(a.x-this.featureDx)/c+this.left,c=this.top-a.y/c,k;if(h<-d||h>d)k=(c-g)/(h-f),h=0>h?-d:d,c=g+(h-f)*k;if(c<-e||c>e)k=(h-f)/(c-g),c=0>c?-e:e,h=f+(c-g)*k;return h+","+c},getShortString:function(a){var b=this.getResolution(),c=(a.x-this.featureDx)/b+this.left;a=this.top-a.y/b;return this.inValidRange(c,
+a)?c+","+a:!1},getPosition:function(a){return{x:parseFloat(a.getAttributeNS(null,"cx")),y:parseFloat(a.getAttributeNS(null,"cy"))}},importSymbol:function(a){this.defs||(this.defs=this.createDefs());var b=this.container.id+"-"+a,c=document.getElementById(b);if(null!=c)return c;var d=OpenLayers.Renderer.symbol[a];if(!d)throw Error(a+" is not a valid symbol name");a=this.nodeFactory(b,"symbol");var e=this.nodeFactory(null,"polygon");a.appendChild(e);for(var c=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,
+0,0),f=[],g,h,k=0;k<d.length;k+=2)g=d[k],h=d[k+1],c.left=Math.min(c.left,g),c.bottom=Math.min(c.bottom,h),c.right=Math.max(c.right,g),c.top=Math.max(c.top,h),f.push(g,",",h);e.setAttributeNS(null,"points",f.join(" "));d=c.getWidth();e=c.getHeight();a.setAttributeNS(null,"viewBox",[c.left-d,c.bottom-e,3*d,3*e].join(" "));this.symbolMetrics[b]=[Math.max(d,e),c.getCenterLonLat().lon,c.getCenterLonLat().lat];this.defs.appendChild(a);return a},getFeatureIdFromEvent:function(a){var b=OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this,
+arguments);b||(b=a.target,b=b.parentNode&&b!=this.rendererRoot?b.parentNode._featureId:void 0);return b},CLASS_NAME:"OpenLayers.Renderer.SVG"});OpenLayers.Renderer.SVG.LABEL_ALIGN={l:"start",r:"end",b:"bottom",t:"hanging"};OpenLayers.Renderer.SVG.LABEL_VSHIFT={t:"-70%",b:"0"};OpenLayers.Renderer.SVG.LABEL_VFACTOR={t:0,b:-1};OpenLayers.Renderer.SVG.preventDefault=function(a){OpenLayers.Event.preventDefault(a)};OpenLayers.Format.JSON=OpenLayers.Class(OpenLayers.Format,{indent:" ",space:" ",newline:"\n",level:0,pretty:!1,nativeJSON:function(){return!(!window.JSON||"function"!=typeof JSON.parse||"function"!=typeof JSON.stringify)}(),read:function(a,b){var c;if(this.nativeJSON)c=JSON.parse(a,b);else try{if(/^[\],:{}\s]*$/.test(a.replace(/\\["\\\/bfnrtu]/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))&&(c=eval("("+a+")"),"function"===
+typeof b)){var d=function(a,c){if(c&&"object"===typeof c)for(var e in c)c.hasOwnProperty(e)&&(c[e]=d(e,c[e]));return b(a,c)};c=d("",c)}}catch(e){}this.keepData&&(this.data=c);return c},write:function(a,b){this.pretty=!!b;var c=null,d=typeof a;if(this.serialize[d])try{c=!this.pretty&&this.nativeJSON?JSON.stringify(a):this.serialize[d].apply(this,[a])}catch(e){OpenLayers.Console.error("Trouble serializing: "+e)}return c},writeIndent:function(){var a=[];if(this.pretty)for(var b=0;b<this.level;++b)a.push(this.indent);
+return a.join("")},writeNewline:function(){return this.pretty?this.newline:""},writeSpace:function(){return this.pretty?this.space:""},serialize:{object:function(a){if(null==a)return"null";if(a.constructor==Date)return this.serialize.date.apply(this,[a]);if(a.constructor==Array)return this.serialize.array.apply(this,[a]);var b=["{"];this.level+=1;var c,d,e,f=!1;for(c in a)a.hasOwnProperty(c)&&(d=OpenLayers.Format.JSON.prototype.write.apply(this,[c,this.pretty]),e=OpenLayers.Format.JSON.prototype.write.apply(this,
+[a[c],this.pretty]),null!=d&&null!=e&&(f&&b.push(","),b.push(this.writeNewline(),this.writeIndent(),d,":",this.writeSpace(),e),f=!0));this.level-=1;b.push(this.writeNewline(),this.writeIndent(),"}");return b.join("")},array:function(a){var b,c=["["];this.level+=1;for(var d=0,e=a.length;d<e;++d)b=OpenLayers.Format.JSON.prototype.write.apply(this,[a[d],this.pretty]),null!=b&&(0<d&&c.push(","),c.push(this.writeNewline(),this.writeIndent(),b));this.level-=1;c.push(this.writeNewline(),this.writeIndent(),
+"]");return c.join("")},string:function(a){var b={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"};return/["\\\x00-\x1f]/.test(a)?'"'+a.replace(/([\x00-\x1f\\"])/g,function(a,d){var e=b[d];if(e)return e;e=d.charCodeAt();return"\\u00"+Math.floor(e/16).toString(16)+(e%16).toString(16)})+'"':'"'+a+'"'},number:function(a){return isFinite(a)?String(a):"null"},"boolean":function(a){return String(a)},date:function(a){function b(a){return 10>a?"0"+a:a}return'"'+a.getFullYear()+
+"-"+b(a.getMonth()+1)+"-"+b(a.getDate())+"T"+b(a.getHours())+":"+b(a.getMinutes())+":"+b(a.getSeconds())+'"'}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.GeoJSON=OpenLayers.Class(OpenLayers.Format.JSON,{ignoreExtraDims:!1,read:function(a,b,c){b=b?b:"FeatureCollection";var d=null,e=null,e="string"==typeof a?OpenLayers.Format.JSON.prototype.read.apply(this,[a,c]):a;if(!e)OpenLayers.Console.error("Bad JSON: "+a);else if("string"!=typeof e.type)OpenLayers.Console.error("Bad GeoJSON - no type: "+a);else if(this.isValidType(e,b))switch(b){case "Geometry":try{d=this.parseGeometry(e)}catch(f){OpenLayers.Console.error(f)}break;case "Feature":try{d=
+this.parseFeature(e),d.type="Feature"}catch(g){OpenLayers.Console.error(g)}break;case "FeatureCollection":switch(d=[],e.type){case "Feature":try{d.push(this.parseFeature(e))}catch(h){d=null,OpenLayers.Console.error(h)}break;case "FeatureCollection":a=0;for(b=e.features.length;a<b;++a)try{d.push(this.parseFeature(e.features[a]))}catch(k){d=null,OpenLayers.Console.error(k)}break;default:try{var l=this.parseGeometry(e);d.push(new OpenLayers.Feature.Vector(l))}catch(m){d=null,OpenLayers.Console.error(m)}}}return d},
+isValidType:function(a,b){var c=!1;switch(b){case "Geometry":-1==OpenLayers.Util.indexOf("Point MultiPoint LineString MultiLineString Polygon MultiPolygon Box GeometryCollection".split(" "),a.type)?OpenLayers.Console.error("Unsupported geometry type: "+a.type):c=!0;break;case "FeatureCollection":c=!0;break;default:a.type==b?c=!0:OpenLayers.Console.error("Cannot convert types from "+a.type+" to "+b)}return c},parseFeature:function(a){var b,c,d;c=a.properties?a.properties:{};d=a.geometry&&a.geometry.bbox||
+a.bbox;try{b=this.parseGeometry(a.geometry)}catch(e){throw e;}b=new OpenLayers.Feature.Vector(b,c);d&&(b.bounds=OpenLayers.Bounds.fromArray(d));a.id&&(b.fid=a.id);return b},parseGeometry:function(a){if(null==a)return null;var b,c=!1;if("GeometryCollection"==a.type){if(!OpenLayers.Util.isArray(a.geometries))throw"GeometryCollection must have geometries array: "+a;b=a.geometries.length;for(var c=Array(b),d=0;d<b;++d)c[d]=this.parseGeometry.apply(this,[a.geometries[d]]);b=new OpenLayers.Geometry.Collection(c);
+c=!0}else{if(!OpenLayers.Util.isArray(a.coordinates))throw"Geometry must have coordinates array: "+a;if(!this.parseCoords[a.type.toLowerCase()])throw"Unsupported geometry type: "+a.type;try{b=this.parseCoords[a.type.toLowerCase()].apply(this,[a.coordinates])}catch(e){throw e;}}this.internalProjection&&(this.externalProjection&&!c)&&b.transform(this.externalProjection,this.internalProjection);return b},parseCoords:{point:function(a){if(!1==this.ignoreExtraDims&&2!=a.length)throw"Only 2D points are supported: "+
+a;return new OpenLayers.Geometry.Point(a[0],a[1])},multipoint:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPoint(b)},linestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.point.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.LineString(b)},multilinestring:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=
+this.parseCoords.linestring.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiLineString(b)},polygon:function(a){for(var b=[],c,d,e=0,f=a.length;e<f;++e){try{d=this.parseCoords.linestring.apply(this,[a[e]])}catch(g){throw g;}c=new OpenLayers.Geometry.LinearRing(d.components);b.push(c)}return new OpenLayers.Geometry.Polygon(b)},multipolygon:function(a){for(var b=[],c=null,d=0,e=a.length;d<e;++d){try{c=this.parseCoords.polygon.apply(this,[a[d]])}catch(f){throw f;}b.push(c)}return new OpenLayers.Geometry.MultiPolygon(b)},
+box:function(a){if(2!=a.length)throw"GeoJSON box coordinates must have 2 elements";return new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([new OpenLayers.Geometry.Point(a[0][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[0][1]),new OpenLayers.Geometry.Point(a[1][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[1][1]),new OpenLayers.Geometry.Point(a[0][0],a[0][1])])])}},write:function(a,b){var c={type:null};if(OpenLayers.Util.isArray(a)){c.type="FeatureCollection";var d=
+a.length;c.features=Array(d);for(var e=0;e<d;++e){var f=a[e];if(!f instanceof OpenLayers.Feature.Vector)throw"FeatureCollection only supports collections of features: "+f;c.features[e]=this.extract.feature.apply(this,[f])}}else 0==a.CLASS_NAME.indexOf("OpenLayers.Geometry")?c=this.extract.geometry.apply(this,[a]):a instanceof OpenLayers.Feature.Vector&&(c=this.extract.feature.apply(this,[a]),a.layer&&a.layer.projection&&(c.crs=this.createCRSObject(a)));return OpenLayers.Format.JSON.prototype.write.apply(this,
+[c,b])},createCRSObject:function(a){a=a.layer.projection.toString();var b={};a.match(/epsg:/i)&&(a=parseInt(a.substring(a.indexOf(":")+1)),b=4326==a?{type:"name",properties:{name:"urn:ogc:def:crs:OGC:1.3:CRS84"}}:{type:"name",properties:{name:"EPSG:"+a}});return b},extract:{feature:function(a){var b=this.extract.geometry.apply(this,[a.geometry]),b={type:"Feature",properties:a.attributes,geometry:b};null!=a.fid&&(b.id=a.fid);return b},geometry:function(a){if(null==a)return null;this.internalProjection&&
+this.externalProjection&&(a=a.clone(),a.transform(this.internalProjection,this.externalProjection));var b=a.CLASS_NAME.split(".")[2];a=this.extract[b.toLowerCase()].apply(this,[a]);return"Collection"==b?{type:"GeometryCollection",geometries:a}:{type:b,coordinates:a}},point:function(a){return[a.x,a.y]},multipoint:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},linestring:function(a){for(var b=[],c=0,d=a.components.length;c<
+d;++c)b.push(this.extract.point.apply(this,[a.components[c]]));return b},multilinestring:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},polygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.linestring.apply(this,[a.components[c]]));return b},multipolygon:function(a){for(var b=[],c=0,d=a.components.length;c<d;++c)b.push(this.extract.polygon.apply(this,[a.components[c]]));return b},collection:function(a){for(var b=
+a.components.length,c=Array(b),d=0;d<b;++d)c[d]=this.extract.geometry.apply(this,[a.components[d]]);return c}},CLASS_NAME:"OpenLayers.Format.GeoJSON"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{layer:null,callbacks:null,multi:!1,featureAdded:function(){},initialize:function(a,b,c){OpenLayers.Control.prototype.initialize.apply(this,[c]);this.callbacks=OpenLayers.Util.extend({done:this.drawFeature,modify:function(a,b){this.layer.events.triggerEvent("sketchmodified",{vertex:a,feature:b})},create:function(a,b){this.layer.events.triggerEvent("sketchstarted",{vertex:a,feature:b})}},this.callbacks);this.layer=a;this.handlerOptions=
+this.handlerOptions||{};this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{renderers:a.renderers,rendererOptions:a.rendererOptions});"multi"in this.handlerOptions||(this.handlerOptions.multi=this.multi);if(a=this.layer.styleMap&&this.layer.styleMap.styles.temporary)this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":a})});this.handler=new b(this,this.callbacks,this.handlerOptions)},
+drawFeature:function(a){a=new OpenLayers.Feature.Vector(a);!1!==this.layer.events.triggerEvent("sketchcomplete",{feature:a})&&(a.state=OpenLayers.State.INSERT,this.layer.addFeatures([a]),this.featureAdded(a),this.events.triggerEvent("featureadded",{feature:a}))},insertXY:function(a,b){this.handler&&this.handler.line&&this.handler.insertXY(a,b)},insertDeltaXY:function(a,b){this.handler&&this.handler.line&&this.handler.insertDeltaXY(a,b)},insertDirectionLength:function(a,b){this.handler&&this.handler.line&&
+this.handler.insertDirectionLength(a,b)},insertDeflectionLength:function(a,b){this.handler&&this.handler.line&&this.handler.insertDeflectionLength(a,b)},undo:function(){return this.handler.undo&&this.handler.undo()},redo:function(){return this.handler.redo&&this.handler.redo()},finishSketch:function(){this.handler.finishGeometry()},cancel:function(){this.handler.cancel()},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Handler.Pinch=OpenLayers.Class(OpenLayers.Handler,{started:!1,stopDown:!1,pinching:!1,last:null,start:null,touchstart:function(a){var b=!0;this.pinching=!1;if(OpenLayers.Event.isMultiTouch(a))this.started=!0,this.last=this.start={distance:this.getDistance(a.touches),delta:0,scale:1},this.callback("start",[a,this.start]),b=!this.stopDown;else{if(this.started)return!1;this.started=!1;this.last=this.start=null}OpenLayers.Event.preventDefault(a);return b},touchmove:function(a){if(this.started&&
+OpenLayers.Event.isMultiTouch(a)){this.pinching=!0;var b=this.getPinchData(a);this.callback("move",[a,b]);this.last=b;OpenLayers.Event.stop(a)}else if(this.started)return!1;return!0},touchend:function(a){return this.started&&!OpenLayers.Event.isMultiTouch(a)?(this.pinching=this.started=!1,this.callback("done",[a,this.start,this.last]),this.last=this.start=null,!1):!0},activate:function(){var a=!1;OpenLayers.Handler.prototype.activate.apply(this,arguments)&&(this.pinching=!1,a=!0);return a},deactivate:function(){var a=
+!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.pinching=this.started=!1,this.last=this.start=null,a=!0);return a},getDistance:function(a){var b=a[0];a=a[1];return Math.sqrt(Math.pow(b.olClientX-a.olClientX,2)+Math.pow(b.olClientY-a.olClientY,2))},getPinchData:function(a){a=this.getDistance(a.touches);return{distance:a,delta:this.last.distance-a,scale:a/this.start.distance}},CLASS_NAME:"OpenLayers.Handler.Pinch"});OpenLayers.Handler.Polygon=OpenLayers.Class(OpenLayers.Handler.Path,{holeModifier:null,drawingHole:!1,polygon:null,createFeature:function(a){a=this.layer.getLonLatFromViewPortPx(a);a=new OpenLayers.Geometry.Point(a.lon,a.lat);this.point=new OpenLayers.Feature.Vector(a);this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LinearRing([this.point.geometry]));this.polygon=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([this.line.geometry]));this.callback("create",[this.point.geometry,
+this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.polygon,this.point],{silent:!0})},addPoint:function(a){if(!this.drawingHole&&this.holeModifier&&this.evt&&this.evt[this.holeModifier])for(var b=this.point.geometry,c=this.control.layer.features,d,e=c.length-1;0<=e;--e)if(d=c[e].geometry,(d instanceof OpenLayers.Geometry.Polygon||d instanceof OpenLayers.Geometry.MultiPolygon)&&d.intersects(b)){b=c[e];this.control.layer.removeFeatures([b],{silent:!0});this.control.layer.events.registerPriority("sketchcomplete",
+this,this.finalizeInteriorRing);this.control.layer.events.registerPriority("sketchmodified",this,this.enforceTopology);b.geometry.addComponent(this.line.geometry);this.polygon=b;this.drawingHole=!0;break}OpenLayers.Handler.Path.prototype.addPoint.apply(this,arguments)},getCurrentPointIndex:function(){return this.line.geometry.components.length-2},enforceTopology:function(a){a=a.vertex;var b=this.line.geometry.components;this.polygon.geometry.intersects(a)||(b=b[b.length-3],a.x=b.x,a.y=b.y)},finishGeometry:function(){this.line.geometry.removeComponent(this.line.geometry.components[this.line.geometry.components.length-
+2]);this.removePoint();this.finalize()},finalizeInteriorRing:function(){var a=this.line.geometry,b=0!==a.getArea();if(b){for(var c=this.polygon.geometry.components,d=c.length-2;0<=d;--d)if(a.intersects(c[d])){b=!1;break}if(b)a:for(d=c.length-2;0<d;--d)for(var e=c[d].components,f=0,g=e.length;f<g;++f)if(a.containsPoint(e[f])){b=!1;break a}}b?this.polygon.state!==OpenLayers.State.INSERT&&(this.polygon.state=OpenLayers.State.UPDATE):this.polygon.geometry.removeComponent(a);this.restoreFeature();return!1},
+cancel:function(){this.drawingHole&&(this.polygon.geometry.removeComponent(this.line.geometry),this.restoreFeature(!0));return OpenLayers.Handler.Path.prototype.cancel.apply(this,arguments)},restoreFeature:function(a){this.control.layer.events.unregister("sketchcomplete",this,this.finalizeInteriorRing);this.control.layer.events.unregister("sketchmodified",this,this.enforceTopology);this.layer.removeFeatures([this.polygon],{silent:!0});this.control.layer.addFeatures([this.polygon],{silent:!0});this.drawingHole=
+!1;a||this.control.layer.events.triggerEvent("sketchcomplete",{feature:this.polygon})},destroyFeature:function(a){OpenLayers.Handler.Path.prototype.destroyFeature.call(this,a);this.polygon=null},drawFeature:function(){this.layer.drawFeature(this.polygon,this.style);this.layer.drawFeature(this.point,this.style)},getSketch:function(){return this.polygon},getGeometry:function(){var a=this.polygon&&this.polygon.geometry;a&&this.multi&&(a=new OpenLayers.Geometry.MultiPolygon([a]));return a},CLASS_NAME:"OpenLayers.Handler.Polygon"});OpenLayers.Control.Geolocate=OpenLayers.Class(OpenLayers.Control,{geolocation:null,available:"geolocation"in navigator,bind:!0,watch:!1,geolocationOptions:null,destroy:function(){this.deactivate();OpenLayers.Control.prototype.destroy.apply(this,arguments)},activate:function(){this.available&&!this.geolocation&&(this.geolocation=navigator.geolocation);return this.geolocation?OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.watch?this.watchId=this.geolocation.watchPosition(OpenLayers.Function.bind(this.geolocate,
+this),OpenLayers.Function.bind(this.failure,this),this.geolocationOptions):this.getCurrentLocation(),!0):!1:(this.events.triggerEvent("locationuncapable"),!1)},deactivate:function(){this.active&&null!==this.watchId&&this.geolocation.clearWatch(this.watchId);return OpenLayers.Control.prototype.deactivate.apply(this,arguments)},geolocate:function(a){var b=(new OpenLayers.LonLat(a.coords.longitude,a.coords.latitude)).transform(new OpenLayers.Projection("EPSG:4326"),this.map.getProjectionObject());this.bind&&
+this.map.setCenter(b);this.events.triggerEvent("locationupdated",{position:a,point:new OpenLayers.Geometry.Point(b.lon,b.lat)})},getCurrentLocation:function(){if(!this.active||this.watch)return!1;this.geolocation.getCurrentPosition(OpenLayers.Function.bind(this.geolocate,this),OpenLayers.Function.bind(this.failure,this),this.geolocationOptions);return!0},failure:function(a){this.events.triggerEvent("locationfailed",{error:a})},CLASS_NAME:"OpenLayers.Control.Geolocate"});OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:!1,updateWithPOST:!1,deleteWithPOST:!1,wildcarded:!1,srsInBBOX:!1,initialize:function(a){a=a||{};this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);if(!this.filterToParams&&OpenLayers.Format.QueryStringFilter){var b=new OpenLayers.Format.QueryStringFilter({wildcarded:this.wildcarded,srsInBBOX:this.srsInBBOX});this.filterToParams=
+function(a,d){return b.write(a,d)}}},destroy:function(){this.headers=this.params=null;OpenLayers.Protocol.prototype.destroy.apply(this)},read:function(a){OpenLayers.Protocol.prototype.read.apply(this,arguments);a=a||{};a.params=OpenLayers.Util.applyDefaults(a.params,this.options.params);a=OpenLayers.Util.applyDefaults(a,this.options);a.filter&&this.filterToParams&&(a.params=this.filterToParams(a.filter,a.params));var b=void 0!==a.readWithPOST?a.readWithPOST:this.readWithPOST,c=new OpenLayers.Protocol.Response({requestType:"read"});
+b?(b=a.headers||{},b["Content-Type"]="application/x-www-form-urlencoded",c.priv=OpenLayers.Request.POST({url:a.url,callback:this.createCallback(this.handleRead,c,a),data:OpenLayers.Util.getParameterString(a.params),headers:b})):c.priv=OpenLayers.Request.GET({url:a.url,callback:this.createCallback(this.handleRead,c,a),params:a.params,headers:a.headers});return c},handleRead:function(a,b){this.handleResponse(a,b)},create:function(a,b){b=OpenLayers.Util.applyDefaults(b,this.options);var c=new OpenLayers.Protocol.Response({reqFeatures:a,
+requestType:"create"});c.priv=OpenLayers.Request.POST({url:b.url,callback:this.createCallback(this.handleCreate,c,b),headers:b.headers,data:this.format.write(a)});return c},handleCreate:function(a,b){this.handleResponse(a,b)},update:function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"update"});d.priv=OpenLayers.Request[this.updateWithPOST?"POST":"PUT"]({url:c,callback:this.createCallback(this.handleUpdate,
+d,b),headers:b.headers,data:this.format.write(a)});return d},handleUpdate:function(a,b){this.handleResponse(a,b)},"delete":function(a,b){b=b||{};var c=b.url||a.url||this.options.url+"/"+a.fid;b=OpenLayers.Util.applyDefaults(b,this.options);var d=new OpenLayers.Protocol.Response({reqFeatures:a,requestType:"delete"}),e=this.deleteWithPOST?"POST":"DELETE",c={url:c,callback:this.createCallback(this.handleDelete,d,b),headers:b.headers};this.deleteWithPOST&&(c.data=this.format.write(a));d.priv=OpenLayers.Request[e](c);
+return d},handleDelete:function(a,b){this.handleResponse(a,b)},handleResponse:function(a,b){var c=a.priv;b.callback&&(200<=c.status&&300>c.status?("delete"!=a.requestType&&(a.features=this.parseFeatures(c)),a.code=OpenLayers.Protocol.Response.SUCCESS):a.code=OpenLayers.Protocol.Response.FAILURE,b.callback.call(b.scope,a))},parseFeatures:function(a){var b=a.responseXML;b&&b.documentElement||(b=a.responseText);return!b||0>=b.length?null:this.format.read(b)},commit:function(a,b){function c(a){for(var b=
+a.features?a.features.length:0,c=Array(b),e=0;e<b;++e)c[e]=a.features[e].fid;r.insertIds=c;d.apply(this,[a])}function d(a){this.callUserCallback(a,b);q=q&&a.success();f++;f>=n&&b.callback&&(r.code=q?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE,b.callback.apply(b.scope,[r]))}b=OpenLayers.Util.applyDefaults(b,this.options);var e=[],f=0,g={};g[OpenLayers.State.INSERT]=[];g[OpenLayers.State.UPDATE]=[];g[OpenLayers.State.DELETE]=[];for(var h,k,l=[],m=0,p=a.length;m<p;++m)if(h=
+a[m],k=g[h.state])k.push(h),l.push(h);var n=(0<g[OpenLayers.State.INSERT].length?1:0)+g[OpenLayers.State.UPDATE].length+g[OpenLayers.State.DELETE].length,q=!0,r=new OpenLayers.Protocol.Response({reqFeatures:l});h=g[OpenLayers.State.INSERT];0<h.length&&e.push(this.create(h,OpenLayers.Util.applyDefaults({callback:c,scope:this},b.create)));h=g[OpenLayers.State.UPDATE];for(m=h.length-1;0<=m;--m)e.push(this.update(h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b.update)));h=g[OpenLayers.State.DELETE];
+for(m=h.length-1;0<=m;--m)e.push(this["delete"](h[m],OpenLayers.Util.applyDefaults({callback:d,scope:this},b["delete"])));return e},abort:function(a){a&&a.priv.abort()},callUserCallback:function(a,b){var c=b[a.requestType];c&&c.callback&&c.callback.call(c.scope,a)},CLASS_NAME:"OpenLayers.Protocol.HTTP"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:!1,interval:0,documentDrag:!1,kinetic:null,enableKinetic:!0,kineticInterval:10,draw:function(){if(this.enableKinetic&&OpenLayers.Kinetic){var a={interval:this.kineticInterval};"object"===typeof this.enableKinetic&&(a=OpenLayers.Util.extend(a,this.enableKinetic));this.kinetic=new OpenLayers.Kinetic(a)}this.handler=new OpenLayers.Handler.Drag(this,{move:this.panMap,done:this.panMapDone,down:this.panMapStart},
+{interval:this.interval,documentDrag:this.documentDrag})},panMapStart:function(){this.kinetic&&this.kinetic.begin()},panMap:function(a){this.kinetic&&this.kinetic.update(a);this.panned=!0;this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!0,animate:!1})},panMapDone:function(a){if(this.panned){var b=null;this.kinetic&&(b=this.kinetic.end(a));this.map.pan(this.handler.last.x-a.x,this.handler.last.y-a.y,{dragging:!!b,animate:!1});if(b){var c=this;this.kinetic.move(b,function(a,b,
+f){c.map.pan(a,b,{dragging:!f,animate:!1})})}this.panned=!1}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Control.PinchZoom=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,pinchOrigin:null,currentCenter:null,autoActivate:!0,preserveCenter:!1,initialize:function(a){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.handler=new OpenLayers.Handler.Pinch(this,{start:this.pinchStart,move:this.pinchMove,done:this.pinchDone},this.handlerOptions)},pinchStart:function(a,b){var c=this.preserveCenter?this.map.getPixelFromLonLat(this.map.getCenter()):a.xy;this.currentCenter=
+this.pinchOrigin=c},pinchMove:function(a,b){var c=b.scale,d=this.map.layerContainerOriginPx,e=this.pinchOrigin,f=this.preserveCenter?this.map.getPixelFromLonLat(this.map.getCenter()):a.xy,g=Math.round(d.x+f.x-e.x+(c-1)*(d.x-e.x)),d=Math.round(d.y+f.y-e.y+(c-1)*(d.y-e.y));this.map.applyTransform(g,d,c);this.currentCenter=f},pinchDone:function(a,b,c){this.map.applyTransform();a=this.map.getZoomForResolution(this.map.getResolution()/c.scale,!0);if(a!==this.map.getZoom()||!this.currentCenter.equals(this.pinchOrigin)){b=
+this.map.getResolutionForZoom(a);c=this.map.getLonLatFromPixel(this.pinchOrigin);var d=this.currentCenter,e=this.map.getSize();c.lon+=b*(e.w/2-d.x);c.lat-=b*(e.h/2-d.y);this.map.div.clientWidth=this.map.div.clientWidth;this.map.setCenter(c,a)}},CLASS_NAME:"OpenLayers.Control.PinchZoom"});OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:!0,"double":!1,pixelTolerance:0,dblclickTolerance:13,stopSingle:!1,stopDouble:!1,timerId:null,down:null,last:null,first:null,rightclickTimerId:null,touchstart:function(a){this.startTouch();this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},touchmove:function(a){this.last=this.getEventInfo(a);return!0},touchend:function(a){this.down&&(a.xy=this.last.xy,a.lastTouches=this.last.touches,this.handleSingle(a),
+this.down=null);return!0},mousedown:function(a){this.down=this.getEventInfo(a);this.last=this.getEventInfo(a);return!0},mouseup:function(a){var b=!0;this.checkModifiers(a)&&(this.control.handleRightClicks&&OpenLayers.Event.isRightClick(a))&&(b=this.rightclick(a));return b},rightclick:function(a){if(this.passesTolerance(a)){if(null!=this.rightclickTimerId)return this.clearTimer(),this.callback("dblrightclick",[a]),!this.stopDouble;a=this["double"]?OpenLayers.Util.extend({},a):this.callback("rightclick",
+[a]);a=OpenLayers.Function.bind(this.delayedRightCall,this,a);this.rightclickTimerId=window.setTimeout(a,this.delay)}return!this.stopSingle},delayedRightCall:function(a){this.rightclickTimerId=null;a&&this.callback("rightclick",[a])},click:function(a){this.last||(this.last=this.getEventInfo(a));this.handleSingle(a);return!this.stopSingle},dblclick:function(a){this.handleDouble(a);return!this.stopDouble},handleDouble:function(a){this.passesDblclickTolerance(a)&&(this["double"]&&this.callback("dblclick",
+[a]),this.clearTimer())},handleSingle:function(a){this.passesTolerance(a)&&(null!=this.timerId?(this.last.touches&&1===this.last.touches.length&&(this["double"]&&OpenLayers.Event.preventDefault(a),this.handleDouble(a)),this.last.touches&&2===this.last.touches.length||this.clearTimer()):(this.first=this.getEventInfo(a),a=this.single?OpenLayers.Util.extend({},a):null,this.queuePotentialClick(a)))},queuePotentialClick:function(a){this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,
+this,a),this.delay)},passesTolerance:function(a){var b=!0;if(null!=this.pixelTolerance&&this.down&&this.down.xy&&(b=this.pixelTolerance>=this.down.xy.distanceTo(a.xy))&&this.touch&&this.down.touches.length===this.last.touches.length){a=0;for(var c=this.down.touches.length;a<c;++a)if(this.getTouchDistance(this.down.touches[a],this.last.touches[a])>this.pixelTolerance){b=!1;break}}return b},getTouchDistance:function(a,b){return Math.sqrt(Math.pow(a.clientX-b.clientX,2)+Math.pow(a.clientY-b.clientY,
+2))},passesDblclickTolerance:function(a){a=!0;this.down&&this.first&&(a=this.down.xy.distanceTo(this.first.xy)<=this.dblclickTolerance);return a},clearTimer:function(){null!=this.timerId&&(window.clearTimeout(this.timerId),this.timerId=null);null!=this.rightclickTimerId&&(window.clearTimeout(this.rightclickTimerId),this.rightclickTimerId=null)},delayedCall:function(a){this.timerId=null;a&&this.callback("click",[a])},getEventInfo:function(a){var b;if(a.touches){var c=a.touches.length;b=Array(c);for(var d,
+e=0;e<c;e++)d=a.touches[e],b[e]={clientX:d.olClientX,clientY:d.olClientY}}return{xy:a.xy,touches:b}},deactivate:function(){var a=!1;OpenLayers.Handler.prototype.deactivate.apply(this,arguments)&&(this.clearTimer(),this.last=this.first=this.down=null,a=!0);return a},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Control.TouchNavigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,pinchZoom:null,pinchZoomOptions:null,clickHandlerOptions:null,documentDrag:!1,autoActivate:!0,initialize:function(a){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments)},destroy:function(){this.deactivate();this.dragPan&&this.dragPan.destroy();this.dragPan=null;this.pinchZoom&&(this.pinchZoom.destroy(),delete this.pinchZoom);OpenLayers.Control.prototype.destroy.apply(this,
+arguments)},activate:function(){return OpenLayers.Control.prototype.activate.apply(this,arguments)?(this.dragPan.activate(),this.handlers.click.activate(),this.pinchZoom.activate(),!0):!1},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments)?(this.dragPan.deactivate(),this.handlers.click.deactivate(),this.pinchZoom.deactivate(),!0):!1},draw:function(){var a={click:this.defaultClick,dblclick:this.defaultDblClick},b=OpenLayers.Util.extend({"double":!0,stopDouble:!0,
+pixelTolerance:2},this.clickHandlerOptions);this.handlers.click=new OpenLayers.Handler.Click(this,a,b);this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map,documentDrag:this.documentDrag},this.dragPanOptions));this.dragPan.draw();this.pinchZoom=new OpenLayers.Control.PinchZoom(OpenLayers.Util.extend({map:this.map},this.pinchZoomOptions))},defaultClick:function(a){a.lastTouches&&2==a.lastTouches.length&&this.map.zoomOut()},defaultDblClick:function(a){this.map.zoomTo(this.map.zoom+
+1,a.xy)},CLASS_NAME:"OpenLayers.Control.TouchNavigation"});OpenLayers.Protocol.WFS.v1_0_0=OpenLayers.Class(OpenLayers.Protocol.WFS.v1,{version:"1.0.0",CLASS_NAME:"OpenLayers.Protocol.WFS.v1_0_0"});OpenLayers.TileManager=OpenLayers.Class({cacheSize:256,tilesPerFrame:2,frameDelay:16,moveDelay:100,zoomDelay:200,maps:null,tileQueueId:null,tileQueue:null,tileCache:null,tileCacheIndex:null,initialize:function(a){OpenLayers.Util.extend(this,a);this.maps=[];this.tileQueueId={};this.tileQueue={};this.tileCache={};this.tileCacheIndex=[]},addMap:function(a){if(!this._destroyed&&OpenLayers.Layer.Grid){this.maps.push(a);this.tileQueue[a.id]=[];for(var b=0,c=a.layers.length;b<c;++b)this.addLayer({layer:a.layers[b]});
+a.events.on({move:this.move,zoomend:this.zoomEnd,changelayer:this.changeLayer,addlayer:this.addLayer,preremovelayer:this.removeLayer,scope:this})}},removeMap:function(a){if(!this._destroyed&&OpenLayers.Layer.Grid){window.clearTimeout(this.tileQueueId[a.id]);if(a.layers)for(var b=0,c=a.layers.length;b<c;++b)this.removeLayer({layer:a.layers[b]});a.events&&a.events.un({move:this.move,zoomend:this.zoomEnd,changelayer:this.changeLayer,addlayer:this.addLayer,preremovelayer:this.removeLayer,scope:this});
+delete this.tileQueue[a.id];delete this.tileQueueId[a.id];OpenLayers.Util.removeItem(this.maps,a)}},move:function(a){this.updateTimeout(a.object,this.moveDelay,!0)},zoomEnd:function(a){this.updateTimeout(a.object,this.zoomDelay)},changeLayer:function(a){"visibility"!==a.property&&"params"!==a.property||this.updateTimeout(a.object,0)},addLayer:function(a){a=a.layer;if(a instanceof OpenLayers.Layer.Grid){a.events.on({addtile:this.addTile,retile:this.clearTileQueue,scope:this});var b,c,d;for(b=a.grid.length-
+1;0<=b;--b)for(c=a.grid[b].length-1;0<=c;--c)d=a.grid[b][c],this.addTile({tile:d}),d.url&&!d.imgDiv&&this.manageTileCache({object:d})}},removeLayer:function(a){a=a.layer;if(a instanceof OpenLayers.Layer.Grid&&(this.clearTileQueue({object:a}),a.events&&a.events.un({addtile:this.addTile,retile:this.clearTileQueue,scope:this}),a.grid)){var b,c,d;for(b=a.grid.length-1;0<=b;--b)for(c=a.grid[b].length-1;0<=c;--c)d=a.grid[b][c],this.unloadTile({object:d})}},updateTimeout:function(a,b,c){window.clearTimeout(this.tileQueueId[a.id]);
+var d=this.tileQueue[a.id];if(!c||d.length)this.tileQueueId[a.id]=window.setTimeout(OpenLayers.Function.bind(function(){this.drawTilesFromQueue(a);d.length&&this.updateTimeout(a,this.frameDelay)},this),b)},addTile:function(a){if(a.tile instanceof OpenLayers.Tile.Image)a.tile.events.on({beforedraw:this.queueTileDraw,beforeload:this.manageTileCache,loadend:this.addToCache,unload:this.unloadTile,scope:this});else this.removeLayer({layer:a.tile.layer})},unloadTile:function(a){a=a.object;a.events.un({beforedraw:this.queueTileDraw,
+beforeload:this.manageTileCache,loadend:this.addToCache,unload:this.unloadTile,scope:this});OpenLayers.Util.removeItem(this.tileQueue[a.layer.map.id],a)},queueTileDraw:function(a){a=a.object;var b=!1,c=a.layer,d=c.getURL(a.bounds),e=this.tileCache[d];e&&"olTileImage"!==e.className&&(delete this.tileCache[d],OpenLayers.Util.removeItem(this.tileCacheIndex,d),e=null);!c.url||!c.async&&e||(b=this.tileQueue[c.map.id],~OpenLayers.Util.indexOf(b,a)||b.push(a),b=!0);return!b},drawTilesFromQueue:function(a){var b=
+this.tileQueue[a.id],c=this.tilesPerFrame;for(a=a.zoomTween&&a.zoomTween.playing;!a&&b.length&&c;)b.shift().draw(!0),--c},manageTileCache:function(a){a=a.object;var b=this.tileCache[a.url];b&&(b.parentNode&&OpenLayers.Element.hasClass(b.parentNode,"olBackBuffer")&&(b.parentNode.removeChild(b),b.id=null),b.parentNode||(b.style.visibility="hidden",b.style.opacity=0,a.setImage(b),OpenLayers.Util.removeItem(this.tileCacheIndex,a.url),this.tileCacheIndex.push(a.url)))},addToCache:function(a){a=a.object;
+this.tileCache[a.url]||OpenLayers.Element.hasClass(a.imgDiv,"olImageLoadError")||(this.tileCacheIndex.length>=this.cacheSize&&(delete this.tileCache[this.tileCacheIndex[0]],this.tileCacheIndex.shift()),this.tileCache[a.url]=a.imgDiv,this.tileCacheIndex.push(a.url))},clearTileQueue:function(a){a=a.object;for(var b=this.tileQueue[a.map.id],c=b.length-1;0<=c;--c)b[c].layer===a&&b.splice(c,1)},destroy:function(){for(var a=this.maps.length-1;0<=a;--a)this.removeMap(this.maps[a]);this.tileCacheIndex=this.tileCache=
+this.tileQueueId=this.tileQueue=this.maps=null;this._destroyed=!0}});
diff --git a/misc/openlayers/apidoc_config/Languages.txt b/misc/openlayers/apidoc_config/Languages.txt
new file mode 100644
index 0000000..85d5fde
--- /dev/null
+++ b/misc/openlayers/apidoc_config/Languages.txt
@@ -0,0 +1,113 @@
+Format: 1.51
+
+# This is the Natural Docs languages file for this project. If you change
+# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change
+# something for all your projects, edit the Languages.txt in Natural Docs'
+# Config directory instead.
+
+
+# You can prevent certain file extensions from being scanned like this:
+# Ignore Extensions: [extension] [extension] ...
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Unlike other Natural Docs configuration files, in this file all comments
+# MUST be alone on a line. Some languages deal with the # character, so you
+# cannot put comments on the same line as content.
+#
+# Also, all lists are separated with spaces, not commas, again because some
+# languages may need to use them.
+#
+# Language: [name]
+# Alter Language: [name]
+# Defines a new language or alters an existing one. Its name can use any
+# characters. If any of the properties below have an add/replace form, you
+# must use that when using Alter Language.
+#
+# The language Shebang Script is special. It's entry is only used for
+# extensions, and files with those extensions have their shebang (#!) lines
+# read to determine the real language of the file. Extensionless files are
+# always treated this way.
+#
+# The language Text File is also special. It's treated as one big comment
+# so you can put Natural Docs content in them without special symbols. Also,
+# if you don't specify a package separator, ignored prefixes, or enum value
+# behavior, it will copy those settings from the language that is used most
+# in the source tree.
+#
+# Extensions: [extension] [extension] ...
+# [Add/Replace] Extensions: [extension] [extension] ...
+# Defines the file extensions of the language's source files. You can
+# redefine extensions found in the main languages file. You can use * to
+# mean any undefined extension.
+#
+# Shebang Strings: [string] [string] ...
+# [Add/Replace] Shebang Strings: [string] [string] ...
+# Defines a list of strings that can appear in the shebang (#!) line to
+# designate that it's part of the language. You can redefine strings found
+# in the main languages file.
+#
+# Ignore Prefixes in Index: [prefix] [prefix] ...
+# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ...
+#
+# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+# Specifies prefixes that should be ignored when sorting symbols in an
+# index. Can be specified in general or for a specific topic type.
+#
+#------------------------------------------------------------------------------
+# For basic language support only:
+#
+# Line Comments: [symbol] [symbol] ...
+# Defines a space-separated list of symbols that are used for line comments,
+# if any.
+#
+# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...
+# Defines a space-separated list of symbol pairs that are used for block
+# comments, if any.
+#
+# Package Separator: [symbol]
+# Defines the default package separator symbol. The default is a dot.
+#
+# [Topic Type] Prototype Enders: [symbol] [symbol] ...
+# When defined, Natural Docs will attempt to get a prototype from the code
+# immediately following the topic type. It stops when it reaches one of
+# these symbols. Use \n for line breaks.
+#
+# Line Extender: [symbol]
+# Defines the symbol that allows a prototype to span multiple lines if
+# normally a line break would end it.
+#
+# Enum Values: [global|under type|under parent]
+# Defines how enum values are referenced. The default is global.
+# global - Values are always global, referenced as 'value'.
+# under type - Values are under the enum type, referenced as
+# 'package.enum.value'.
+# under parent - Values are under the enum's parent, referenced as
+# 'package.value'.
+#
+# Perl Package: [perl package]
+# Specifies the Perl package used to fine-tune the language behavior in ways
+# too complex to do in this file.
+#
+#------------------------------------------------------------------------------
+# For full language support only:
+#
+# Full Language Support: [perl package]
+# Specifies the Perl package that has the parsing routines necessary for full
+# language support.
+#
+#-------------------------------------------------------------------------------
+
+# The following languages are defined in the main file, if you'd like to alter
+# them:
+#
+# Text File, Shebang Script, C/C++, C#, Java, JavaScript, Perl, Python,
+# PHP, SQL, Visual Basic, Pascal, Assembly, Ada, Tcl, Ruby, Makefile,
+# ActionScript, ColdFusion, R, Fortran
+
+# If you add a language that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# languages [at] naturaldocs [dot] org.
diff --git a/misc/openlayers/apidoc_config/Menu.txt b/misc/openlayers/apidoc_config/Menu.txt
new file mode 100644
index 0000000..4e27104
--- /dev/null
+++ b/misc/openlayers/apidoc_config/Menu.txt
@@ -0,0 +1,520 @@
+Format: 1.51
+
+
+Title: OpenLayers
+SubTitle: JavaScript Mapping Library
+
+# You can add a footer to your documentation like this:
+# Footer: [text]
+# If you want to add a copyright notice, this would be the place to do it.
+
+# You can add a timestamp to your documentation like one of these:
+# Timestamp: Generated on month day, year
+# Timestamp: Updated mm/dd/yyyy
+# Timestamp: Last updated mon day
+#
+# m - One or two digit month. January is "1"
+# mm - Always two digit month. January is "01"
+# mon - Short month word. January is "Jan"
+# month - Long month word. January is "January"
+# d - One or two digit day. 1 is "1"
+# dd - Always two digit day. 1 is "01"
+# day - Day with letter extension. 1 is "1st"
+# yy - Two digit year. 2006 is "06"
+# yyyy - Four digit year. 2006 is "2006"
+# year - Four digit year. 2006 is "2006"
+
+
+# --------------------------------------------------------------------------
+#
+# Cut and paste the lines below to change the order in which your files
+# appear on the menu. Don't worry about adding or removing files, Natural
+# Docs will take care of that.
+#
+# You can further organize the menu by grouping the entries. Add a
+# "Group: [name] {" line to start a group, and add a "}" to end it.
+#
+# You can add text and web links to the menu by adding "Text: [text]" and
+# "Link: [name] ([URL])" lines, respectively.
+#
+# The formatting and comments are auto-generated, so don't worry about
+# neatness when editing the file. Natural Docs will clean it up the next
+# time it is run. When working with groups, just deal with the braces and
+# forget about the indentation and comments.
+#
+# --------------------------------------------------------------------------
+
+
+Group: OpenLayers {
+
+ File: OpenLayers (no auto-title, OpenLayers.js)
+
+ Group: BaseTypes {
+
+ File: Base Types (no auto-title, OpenLayers/BaseTypes.js)
+ File: Bounds (no auto-title, OpenLayers/BaseTypes/Bounds.js)
+ File: Class (no auto-title, OpenLayers/BaseTypes/Class.js)
+ File: Date (no auto-title, OpenLayers/BaseTypes/Date.js)
+ File: Element (no auto-title, OpenLayers/BaseTypes/Element.js)
+ File: LonLat (no auto-title, OpenLayers/BaseTypes/LonLat.js)
+ File: Pixel (no auto-title, OpenLayers/BaseTypes/Pixel.js)
+ File: Size (no auto-title, OpenLayers/BaseTypes/Size.js)
+ } # Group: BaseTypes
+
+ Group: Control {
+
+ File: Control (no auto-title, OpenLayers/Control.js)
+
+ Group: Control {
+
+ File: ArgParser (no auto-title, OpenLayers/Control/ArgParser.js)
+ File: Attribution (no auto-title, OpenLayers/Control/Attribution.js)
+ File: Button (no auto-title, OpenLayers/Control/Button.js)
+ File: CacheRead (OpenLayers/Control/CacheRead.js)
+ File: CacheWrite (OpenLayers/Control/CacheWrite.js)
+ File: DragFeature (no auto-title, OpenLayers/Control/DragFeature.js)
+ File: DragPan (no auto-title, OpenLayers/Control/DragPan.js)
+ File: DrawFeature (no auto-title, OpenLayers/Control/DrawFeature.js)
+ File: EditingToolbar (no auto-title, OpenLayers/Control/EditingToolbar.js)
+ File: Geolocate (no auto-title, OpenLayers/Control/Geolocate.js)
+ File: GetFeature (no auto-title, OpenLayers/Control/GetFeature.js)
+ File: Graticule (no auto-title, OpenLayers/Control/Graticule.js)
+ File: KeyboardDefaults (no auto-title, OpenLayers/Control/KeyboardDefaults.js)
+ File: LayerSwitcher (no auto-title, OpenLayers/Control/LayerSwitcher.js)
+ File: Measure (no auto-title, OpenLayers/Control/Measure.js)
+ File: ModifyFeature (no auto-title, OpenLayers/Control/ModifyFeature.js)
+ File: MousePosition (no auto-title, OpenLayers/Control/MousePosition.js)
+ File: Navigation (no auto-title, OpenLayers/Control/Navigation.js)
+ File: NavigationHistory (no auto-title, OpenLayers/Control/NavigationHistory.js)
+ File: NavToolbar (no auto-title, OpenLayers/Control/NavToolbar.js)
+ File: OverviewMap (no auto-title, OpenLayers/Control/OverviewMap.js)
+ File: Pan (no auto-title, OpenLayers/Control/Pan.js)
+ File: Panel (no auto-title, OpenLayers/Control/Panel.js)
+ File: PanPanel (no auto-title, OpenLayers/Control/PanPanel.js)
+ File: PanZoom (no auto-title, OpenLayers/Control/PanZoom.js)
+ File: PanZoomBar (no auto-title, OpenLayers/Control/PanZoomBar.js)
+ File: Permalink (no auto-title, OpenLayers/Control/Permalink.js)
+ File: PinchZoom (no auto-title, OpenLayers/Control/PinchZoom.js)
+ File: Scale (no auto-title, OpenLayers/Control/Scale.js)
+ File: ScaleLine (no auto-title, OpenLayers/Control/ScaleLine.js)
+ File: SelectFeature (no auto-title, OpenLayers/Control/SelectFeature.js)
+ File: SLDSelect (no auto-title, OpenLayers/Control/SLDSelect.js)
+ File: Snapping (no auto-title, OpenLayers/Control/Snapping.js)
+ File: Split (no auto-title, OpenLayers/Control/Split.js)
+ File: TouchNavigation (no auto-title, OpenLayers/Control/TouchNavigation.js)
+ File: TransformFeature (no auto-title, OpenLayers/Control/TransformFeature.js)
+ File: UTFGrid (OpenLayers/Control/UTFGrid.js)
+ File: WMSGetFeatureInfo (no auto-title, OpenLayers/Control/WMSGetFeatureInfo.js)
+ File: WMTSGetFeatureInfo (no auto-title, OpenLayers/Control/WMTSGetFeatureInfo.js)
+ File: Zoom (OpenLayers/Control/Zoom.js)
+ File: ZoomBox (no auto-title, OpenLayers/Control/ZoomBox.js)
+ File: ZoomIn (no auto-title, OpenLayers/Control/ZoomIn.js)
+ File: ZoomOut (no auto-title, OpenLayers/Control/ZoomOut.js)
+ File: ZoomPanel (no auto-title, OpenLayers/Control/ZoomPanel.js)
+ File: ZoomToMaxExtent (no auto-title, OpenLayers/Control/ZoomToMaxExtent.js)
+ } # Group: Control
+
+ } # Group: Control
+
+ Group: Feature {
+
+ File: Feature (no auto-title, OpenLayers/Feature.js)
+ File: Vector (no auto-title, OpenLayers/Feature/Vector.js)
+ } # Group: Feature
+
+ Group: Filter {
+
+ File: Filter (no auto-title, OpenLayers/Filter.js)
+ File: Comparison (no auto-title, OpenLayers/Filter/Comparison.js)
+ File: FeatureId (no auto-title, OpenLayers/Filter/FeatureId.js)
+ File: Function (no auto-title, OpenLayers/Filter/Function.js)
+ File: Logical (no auto-title, OpenLayers/Filter/Logical.js)
+ File: Spatial (no auto-title, OpenLayers/Filter/Spatial.js)
+ } # Group: Filter
+
+ Group: Format {
+
+ File: Format (no auto-title, OpenLayers/Format.js)
+
+ Group: Filter {
+
+ File: Filter (no auto-title, OpenLayers/Format/Filter.js)
+ File: v1 (no auto-title, OpenLayers/Format/Filter/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/Filter/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/Filter/v1_1_0.js)
+ } # Group: Filter
+
+ Group: GML {
+
+ File: GML (no auto-title, OpenLayers/Format/GML.js)
+ File: Base (no auto-title, OpenLayers/Format/GML/Base.js)
+ File: v2 (no auto-title, OpenLayers/Format/GML/v2.js)
+ File: v3 (no auto-title, OpenLayers/Format/GML/v3.js)
+ } # Group: GML
+
+ Group: SLD {
+
+ File: SLD (no auto-title, OpenLayers/Format/SLD.js)
+ File: SLD/v1_0_0_GeoServer (OpenLayers/Format/SLD/v1_0_0_GeoServer.js)
+ File: v1 (no auto-title, OpenLayers/Format/SLD/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/SLD/v1_0_0.js)
+ } # Group: SLD
+
+ Group: OWSCommon {
+
+ File: OWSCommon (no auto-title, OpenLayers/Format/OWSCommon.js)
+ File: v1 (no auto-title, OpenLayers/Format/OWSCommon/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/OWSCommon/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/OWSCommon/v1_1_0.js)
+ } # Group: OWSCommon
+
+ Group: WFSCapabilities {
+
+ File: WFSCapabilities (no auto-title, OpenLayers/Format/WFSCapabilities.js)
+ File: v1 (no auto-title, OpenLayers/Format/WFSCapabilities/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/WFSCapabilities/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WFSCapabilities/v1_1_0.js)
+ } # Group: WFSCapabilities
+
+ Group: WFST {
+
+ File: WFST (no auto-title, OpenLayers/Format/WFST.js)
+ File: v1 (no auto-title, OpenLayers/Format/WFST/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/WFST/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WFST/v1_1_0.js)
+ } # Group: WFST
+
+ Group: WMC {
+
+ File: WMC (no auto-title, OpenLayers/Format/WMC.js)
+ File: v1 (no auto-title, OpenLayers/Format/WMC/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/WMC/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WMC/v1_1_0.js)
+ } # Group: WMC
+
+ Group: WMSCapabilities {
+
+ File: WMSCapabilities (no auto-title, OpenLayers/Format/WMSCapabilities.js)
+ File: v1 (no auto-title, OpenLayers/Format/WMSCapabilities/v1.js)
+ File: v1_1 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1_0.js)
+ File: v1_1_1 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1_1.js)
+ File: v1_3 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_3.js)
+ File: v1_3_0 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_3_0.js)
+ File: WMSCapabilities/v1_1_1_WMSC (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js)
+ } # Group: WMSCapabilities
+
+ Group: WMSDescribeLayer {
+
+ File: WMSDescribeLayer (no auto-title, OpenLayers/Format/WMSDescribeLayer.js)
+ File: v1_1 (no auto-title, OpenLayers/Format/WMSDescribeLayer/v1_1.js)
+ } # Group: WMSDescribeLayer
+
+ Group: Format {
+
+ File: ArcXML (no auto-title, OpenLayers/Format/ArcXML.js)
+ File: ArcXML.Features (no auto-title, OpenLayers/Format/ArcXML/Features.js)
+ File: Atom (no auto-title, OpenLayers/Format/Atom.js)
+ File: Context (no auto-title, OpenLayers/Format/Context.js)
+ File: CQL (no auto-title, OpenLayers/Format/CQL.js)
+ File: CSWGetDomain (no auto-title, OpenLayers/Format/CSWGetDomain.js)
+ File: CSWGetDomain.v2_0_2 (no auto-title, OpenLayers/Format/CSWGetDomain/v2_0_2.js)
+ File: CSWGetRecords (no auto-title, OpenLayers/Format/CSWGetRecords.js)
+ File: CSWGetRecords.v2_0_2 (no auto-title, OpenLayers/Format/CSWGetRecords/v2_0_2.js)
+ File: EncodedPolyline (OpenLayers/Format/EncodedPolyline.js)
+ File: GeoJSON (no auto-title, OpenLayers/Format/GeoJSON.js)
+ File: GeoRSS (no auto-title, OpenLayers/Format/GeoRSS.js)
+ File: GPX (no auto-title, OpenLayers/Format/GPX.js)
+ File: JSON (no auto-title, OpenLayers/Format/JSON.js)
+ File: KML (no auto-title, OpenLayers/Format/KML.js)
+ File: OGCExceptionReport (no auto-title, OpenLayers/Format/OGCExceptionReport.js)
+ File: OSM (no auto-title, OpenLayers/Format/OSM.js)
+ File: OWSContext (no auto-title, OpenLayers/Format/OWSContext.js)
+ File: OWSContext.v0_3_1 (no auto-title, OpenLayers/Format/OWSContext/v0_3_1.js)
+ File: QueryStringFilter (no auto-title, OpenLayers/Format/QueryStringFilter.js)
+ File: SOSCapabilities (no auto-title, OpenLayers/Format/SOSCapabilities.js)
+ File: SOSCapabilities.v1_0_0 (no auto-title, OpenLayers/Format/SOSCapabilities/v1_0_0.js)
+ File: SOSGetFeatureOfInterest (no auto-title, OpenLayers/Format/SOSGetFeatureOfInterest.js)
+ File: SOSGetObservation (no auto-title, OpenLayers/Format/SOSGetObservation.js)
+ File: Text (no auto-title, OpenLayers/Format/Text.js)
+ File: WCSCapabilities (OpenLayers/Format/WCSCapabilities.js)
+
+ Group: WCSCapabilities {
+
+ File: WCSCapabilities.v1 (OpenLayers/Format/WCSCapabilities/v1.js)
+ File: WCSCapabilities/v1_0_0 (OpenLayers/Format/WCSCapabilities/v1_0_0.js)
+ File: WCSCapabilities/v1_1_0 (OpenLayers/Format/WCSCapabilities/v1_1_0.js)
+ } # Group: WCSCapabilities
+
+ File: WCSGetCoverage version 1.1.0 (no auto-title, OpenLayers/Format/WCSGetCoverage.js)
+ File: WFS (no auto-title, OpenLayers/Format/WFS.js)
+ File: WFSDescribeFeatureType (no auto-title, OpenLayers/Format/WFSDescribeFeatureType.js)
+ File: WKT (no auto-title, OpenLayers/Format/WKT.js)
+ File: WMSGetFeatureInfo (no auto-title, OpenLayers/Format/WMSGetFeatureInfo.js)
+ File: WMTSCapabilities (no auto-title, OpenLayers/Format/WMTSCapabilities.js)
+ File: WMTSCapabilities.v1_0_0 (no auto-title, OpenLayers/Format/WMTSCapabilities/v1_0_0.js)
+ File: WPSCapabilities (no auto-title, OpenLayers/Format/WPSCapabilities.js)
+ File: WPSCapabilities.v1_0_0 (no auto-title, OpenLayers/Format/WPSCapabilities/v1_0_0.js)
+ File: WPSDescribeProcess (no auto-title, OpenLayers/Format/WPSDescribeProcess.js)
+ File: WPSExecute version 1.0.0 (no auto-title, OpenLayers/Format/WPSExecute.js)
+ File: XLS (no auto-title, OpenLayers/Format/XLS.js)
+ File: XLS.v1 (no auto-title, OpenLayers/Format/XLS/v1.js)
+ File: XLS.v1_1_0 (no auto-title, OpenLayers/Format/XLS/v1_1_0.js)
+ File: XML (no auto-title, OpenLayers/Format/XML.js)
+ File: XML.VersionedOGC (OpenLayers/Format/XML/VersionedOGC.js)
+ } # Group: Format
+
+ } # Group: Format
+
+ Group: Geometry {
+
+ File: Geometry (no auto-title, OpenLayers/Geometry.js)
+ File: Collection (no auto-title, OpenLayers/Geometry/Collection.js)
+ File: Curve (no auto-title, OpenLayers/Geometry/Curve.js)
+ File: LinearRing (no auto-title, OpenLayers/Geometry/LinearRing.js)
+ File: LineString (no auto-title, OpenLayers/Geometry/LineString.js)
+ File: MultiLineString (no auto-title, OpenLayers/Geometry/MultiLineString.js)
+ File: MultiPoint (no auto-title, OpenLayers/Geometry/MultiPoint.js)
+ File: MultiPolygon (no auto-title, OpenLayers/Geometry/MultiPolygon.js)
+ File: Point (no auto-title, OpenLayers/Geometry/Point.js)
+ File: Polygon (no auto-title, OpenLayers/Geometry/Polygon.js)
+ } # Group: Geometry
+
+ Group: Handler {
+
+ File: Handler (no auto-title, OpenLayers/Handler.js)
+ File: Box (no auto-title, OpenLayers/Handler/Box.js)
+ File: Click (no auto-title, OpenLayers/Handler/Click.js)
+ File: Drag (no auto-title, OpenLayers/Handler/Drag.js)
+ File: Feature (no auto-title, OpenLayers/Handler/Feature.js)
+ File: Hover (no auto-title, OpenLayers/Handler/Hover.js)
+ File: Keyboard (no auto-title, OpenLayers/Handler/Keyboard.js)
+ File: MouseWheel (no auto-title, OpenLayers/Handler/MouseWheel.js)
+ File: Path (no auto-title, OpenLayers/Handler/Path.js)
+ File: Pinch (no auto-title, OpenLayers/Handler/Pinch.js)
+ File: Point (no auto-title, OpenLayers/Handler/Point.js)
+ File: Polygon (no auto-title, OpenLayers/Handler/Polygon.js)
+ File: RegularPolygon (no auto-title, OpenLayers/Handler/RegularPolygon.js)
+ } # Group: Handler
+
+ Group: Lang {
+
+ File: Lang (no auto-title, OpenLayers/Lang.js)
+
+ Group: Lang {
+
+ File: ar (no auto-title, OpenLayers/Lang/ar.js)
+ File: be-tarask (no auto-title, OpenLayers/Lang/be-tarask.js)
+ File: bg (no auto-title, OpenLayers/Lang/bg.js)
+ File: br (no auto-title, OpenLayers/Lang/br.js)
+ File: ca (no auto-title, OpenLayers/Lang/ca.js)
+ File: cs-CZ (no auto-title, OpenLayers/Lang/cs-CZ.js)
+ File: da-DK (no auto-title, OpenLayers/Lang/da-DK.js)
+ File: de (no auto-title, OpenLayers/Lang/de.js)
+ File: en (no auto-title, OpenLayers/Lang/en.js)
+ File: en-CA (no auto-title, OpenLayers/Lang/en-CA.js)
+ File: es (no auto-title, OpenLayers/Lang/es.js)
+ File: el (no auto-title, OpenLayers/Lang/el.js)
+ File: fi (no auto-title, OpenLayers/Lang/fi.js)
+ File: fr (no auto-title, OpenLayers/Lang/fr.js)
+ File: fur (no auto-title, OpenLayers/Lang/fur.js)
+ File: gl (no auto-title, OpenLayers/Lang/gl.js)
+ File: gsw (no auto-title, OpenLayers/Lang/gsw.js)
+ File: hr (no auto-title, OpenLayers/Lang/hr.js)
+ File: hsb (no auto-title, OpenLayers/Lang/hsb.js)
+ File: hu (no auto-title, OpenLayers/Lang/hu.js)
+ File: ia (no auto-title, OpenLayers/Lang/ia.js)
+ File: id (no auto-title, OpenLayers/Lang/id.js)
+ File: io (no auto-title, OpenLayers/Lang/io.js)
+ File: is (no auto-title, OpenLayers/Lang/is.js)
+ File: it (no auto-title, OpenLayers/Lang/it.js)
+ File: ja (no auto-title, OpenLayers/Lang/ja.js)
+ File: km (no auto-title, OpenLayers/Lang/km.js)
+ File: ksh (no auto-title, OpenLayers/Lang/ksh.js)
+ File: lt (no auto-title, OpenLayers/Lang/lt.js)
+ File: nds (no auto-title, OpenLayers/Lang/nds.js)
+ File: nb (no auto-title, OpenLayers/Lang/nb.js)
+ File: nl (no auto-title, OpenLayers/Lang/nl.js)
+ File: nn (no auto-title, OpenLayers/Lang/nn.js)
+ File: oc (no auto-title, OpenLayers/Lang/oc.js)
+ File: pt (no auto-title, OpenLayers/Lang/pt.js)
+ File: pl (no auto-title, OpenLayers/Lang/pl.js)
+ File: pt-BR (no auto-title, OpenLayers/Lang/pt-BR.js)
+ File: ru (no auto-title, OpenLayers/Lang/ru.js)
+ File: sk (no auto-title, OpenLayers/Lang/sk.js)
+ File: sv-SE (no auto-title, OpenLayers/Lang/sv-SE.js)
+ File: te (no auto-title, OpenLayers/Lang/te.js)
+ File: vi (no auto-title, OpenLayers/Lang/vi.js)
+ File: zh-CN (no auto-title, OpenLayers/Lang/zh-CN.js)
+ File: zh-TW (no auto-title, OpenLayers/Lang/zh-TW.js)
+ File: Lang["ro"] (OpenLayers/Lang/ro.js)
+ } # Group: Lang
+
+ } # Group: Lang
+
+ Group: Layer {
+
+ File: Layer (no auto-title, OpenLayers/Layer.js)
+
+ Group: Layer {
+
+ File: ArcGISCache.js (no auto-title, OpenLayers/Layer/ArcGISCache.js)
+ File: ArcGIS93Rest (no auto-title, OpenLayers/Layer/ArcGIS93Rest.js)
+ File: ArcIMS (no auto-title, OpenLayers/Layer/ArcIMS.js)
+ File: Bing (no auto-title, OpenLayers/Layer/Bing.js)
+ File: Boxes (no auto-title, OpenLayers/Layer/Boxes.js)
+ File: EventPane (no auto-title, OpenLayers/Layer/EventPane.js)
+ File: FixedZoomLevels (no auto-title, OpenLayers/Layer/FixedZoomLevels.js)
+ File: GeoRSS (no auto-title, OpenLayers/Layer/GeoRSS.js)
+ File: Google (no auto-title, OpenLayers/Layer/Google.js)
+ File: Google.v3 (no auto-title, OpenLayers/Layer/Google/v3.js)
+ File: Grid (no auto-title, OpenLayers/Layer/Grid.js)
+ File: HTTPRequest (no auto-title, OpenLayers/Layer/HTTPRequest.js)
+ File: Image (no auto-title, OpenLayers/Layer/Image.js)
+ File: KaMap (no auto-title, OpenLayers/Layer/KaMap.js)
+ File: KaMapCache (no auto-title, OpenLayers/Layer/KaMapCache.js)
+ File: MapGuide (no auto-title, OpenLayers/Layer/MapGuide.js)
+ File: MapServer (no auto-title, OpenLayers/Layer/MapServer.js)
+ File: Markers (no auto-title, OpenLayers/Layer/Markers.js)
+ File: OSM (no auto-title, OpenLayers/Layer/OSM.js)
+ File: PointGrid (no auto-title, OpenLayers/Layer/PointGrid.js)
+ File: PointTrack (no auto-title, OpenLayers/Layer/PointTrack.js)
+ File: SphericalMercator (no auto-title, OpenLayers/Layer/SphericalMercator.js)
+ File: Text (no auto-title, OpenLayers/Layer/Text.js)
+ File: TileCache (no auto-title, OpenLayers/Layer/TileCache.js)
+ File: TMS (no auto-title, OpenLayers/Layer/TMS.js)
+ File: Vector (no auto-title, OpenLayers/Layer/Vector.js)
+ File: Vector.RootContainer (no auto-title, OpenLayers/Layer/Vector/RootContainer.js)
+ File: WMS (no auto-title, OpenLayers/Layer/WMS.js)
+ File: WMTS (no auto-title, OpenLayers/Layer/WMTS.js)
+ File: WorldWind (no auto-title, OpenLayers/Layer/WorldWind.js)
+ File: XYZ (no auto-title, OpenLayers/Layer/XYZ.js)
+ File: Zoomify (no auto-title, OpenLayers/Layer/Zoomify.js)
+ File: UTFGrid (OpenLayers/Layer/UTFGrid.js)
+ } # Group: Layer
+
+ } # Group: Layer
+
+ Group: Marker {
+
+ File: Marker (no auto-title, OpenLayers/Marker.js)
+ File: Box (no auto-title, OpenLayers/Marker/Box.js)
+ } # Group: Marker
+
+ Group: Popup {
+
+ File: Popup (no auto-title, OpenLayers/Popup.js)
+ File: Anchored (no auto-title, OpenLayers/Popup/Anchored.js)
+ File: Framed (no auto-title, OpenLayers/Popup/Framed.js)
+ File: FramedCloud (no auto-title, OpenLayers/Popup/FramedCloud.js)
+ } # Group: Popup
+
+ Group: Protocol {
+
+ File: Protocol (no auto-title, OpenLayers/Protocol.js)
+
+ Group: Protocol {
+
+ File: CSW (OpenLayers/Protocol/CSW.js)
+ File: CSW.v2_0_2 (OpenLayers/Protocol/CSW/v2_0_2.js)
+ File: HTTP (no auto-title, OpenLayers/Protocol/HTTP.js)
+ File: Script (no auto-title, OpenLayers/Protocol/Script.js)
+ File: SOS.DEFAULTS (no auto-title, OpenLayers/Protocol/SOS.js)
+ File: SOS.v1_0_0 (no auto-title, OpenLayers/Protocol/SOS/v1_0_0.js)
+ } # Group: Protocol
+
+ Group: WFS {
+
+ File: WFS (no auto-title, OpenLayers/Protocol/WFS.js)
+ File: v1 (no auto-title, OpenLayers/Protocol/WFS/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Protocol/WFS/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Protocol/WFS/v1_1_0.js)
+ } # Group: WFS
+
+ } # Group: Protocol
+
+ Group: Renderer {
+
+ File: Renderer (no auto-title, OpenLayers/Renderer.js)
+ File: Canvas (no auto-title, OpenLayers/Renderer/Canvas.js)
+ File: ElementsIndexer (no auto-title, OpenLayers/Renderer/Elements.js)
+ File: SVG (no auto-title, OpenLayers/Renderer/SVG.js)
+ File: VML (no auto-title, OpenLayers/Renderer/VML.js)
+ } # Group: Renderer
+
+ Group: Request {
+
+ File: Request (no auto-title, OpenLayers/Request.js)
+ File: XMLHttpRequest (no auto-title, OpenLayers/Request/XMLHttpRequest.js)
+ } # Group: Request
+
+ Group: Strategy {
+
+ File: Strategy (no auto-title, OpenLayers/Strategy.js)
+ File: BBOX (no auto-title, OpenLayers/Strategy/BBOX.js)
+ File: Cluster (no auto-title, OpenLayers/Strategy/Cluster.js)
+ File: Filter (no auto-title, OpenLayers/Strategy/Filter.js)
+ File: Fixed (no auto-title, OpenLayers/Strategy/Fixed.js)
+ File: Paging (no auto-title, OpenLayers/Strategy/Paging.js)
+ File: Refresh (no auto-title, OpenLayers/Strategy/Refresh.js)
+ File: Save (no auto-title, OpenLayers/Strategy/Save.js)
+ } # Group: Strategy
+
+ Group: Symbolizer {
+
+ File: Symbolizer (no auto-title, OpenLayers/Symbolizer.js)
+ File: Line (no auto-title, OpenLayers/Symbolizer/Line.js)
+ File: Point (no auto-title, OpenLayers/Symbolizer/Point.js)
+ File: Polygon (no auto-title, OpenLayers/Symbolizer/Polygon.js)
+ File: Raster (no auto-title, OpenLayers/Symbolizer/Raster.js)
+ File: Text (no auto-title, OpenLayers/Symbolizer/Text.js)
+ } # Group: Symbolizer
+
+ Group: Tile {
+
+ File: Tile (no auto-title, OpenLayers/Tile.js)
+ File: Image (no auto-title, OpenLayers/Tile/Image.js)
+ File: Image.IFrame (no auto-title, OpenLayers/Tile/Image/IFrame.js)
+ File: UTFGrid (OpenLayers/Tile/UTFGrid.js)
+ } # Group: Tile
+
+ File: Deprecated (no auto-title, deprecated.js)
+
+ Group: OpenLayers {
+
+ File: Console (no auto-title, OpenLayers/Console.js)
+ File: Events (no auto-title, OpenLayers/Events.js)
+ File: Icon (no auto-title, OpenLayers/Icon.js)
+ File: Map (no auto-title, OpenLayers/Map.js)
+ File: OpenLayers.Animation (OpenLayers/Animation.js)
+ File: OpenLayers.Events.buttonclick (OpenLayers/Events/buttonclick.js)
+ File: OpenLayers.Events.featureclick (OpenLayers/Events/featureclick.js)
+ File: OpenLayers.Kinetic (OpenLayers/Kinetic.js)
+ File: OpenLayers.TileManager (OpenLayers/TileManager.js)
+ File: OpenLayers.Util.vendorPrefix (OpenLayers/Util/vendorPrefix.js)
+ File: OpenLayers.WPSClient (OpenLayers/WPSClient.js)
+ File: OpenLayers.WPSProcess (OpenLayers/WPSProcess.js)
+ File: Projection (no auto-title, OpenLayers/Projection.js)
+ File: Rule (no auto-title, OpenLayers/Rule.js)
+ File: SingleFile.js (no auto-title, OpenLayers/SingleFile.js)
+ File: Spherical (OpenLayers/Spherical.js)
+ File: Style (no auto-title, OpenLayers/Style.js)
+ File: Style2 (no auto-title, OpenLayers/Style2.js)
+ File: StyleMap (no auto-title, OpenLayers/StyleMap.js)
+ File: Tween (no auto-title, OpenLayers/Tween.js)
+ File: Util (no auto-title, OpenLayers/Util.js)
+ } # Group: OpenLayers
+
+ } # Group: OpenLayers
+
+Group: Index {
+
+ Index: Everything
+ Class Index: Classes
+ Constant Index: Constants
+ Function Index: Functions
+ Property Index: Properties
+ File Index: Files
+ Constructor Index: Constructor
+ } # Group: Index
+
diff --git a/misc/openlayers/apidoc_config/OL.css b/misc/openlayers/apidoc_config/OL.css
new file mode 100644
index 0000000..a397119
--- /dev/null
+++ b/misc/openlayers/apidoc_config/OL.css
@@ -0,0 +1,20 @@
+p {
+ text-indent: 0; margin-bottom: 1em;
+}
+
+.MGroup {
+ font-variant: normal;
+ margin: 0.4em 0 0em 10px
+}
+
+.MTitle {
+ font-variant: normal;
+}
+
+.CGroup .CTitle {
+ font-variant: normal;
+}
+
+.SGroup .SEntry {
+ font-variant: normal;
+} \ No newline at end of file
diff --git a/misc/openlayers/apidoc_config/Topics.txt b/misc/openlayers/apidoc_config/Topics.txt
new file mode 100644
index 0000000..6951a51
--- /dev/null
+++ b/misc/openlayers/apidoc_config/Topics.txt
@@ -0,0 +1,105 @@
+Format: 1.51
+
+# This is the Natural Docs topics file for this project. If you change anything
+# here, it will apply to THIS PROJECT ONLY. If you'd like to change something
+# for all your projects, edit the Topics.txt in Natural Docs' Config directory
+# instead.
+
+
+Ignore Keywords:
+ function, functions
+ func, funcs
+ procedure, procedures
+ proc, procs
+ routine, routines
+ subroutine, subroutines
+ sub, subs
+ method, methods
+ callback, callbacks
+ property, properties
+ prop, props
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Topic Type: [name]
+# Alter Topic Type: [name]
+# Creates a new topic type or alters one from the main file. Each type gets
+# its own index and behavior settings. Its name can have letters, numbers,
+# spaces, and these charaters: - / . '
+#
+# Plural: [name]
+# Sets the plural name of the topic type, if different.
+#
+# Keywords:
+# [keyword]
+# [keyword], [plural keyword]
+# ...
+# Defines or adds to the list of keywords for the topic type. They may only
+# contain letters, numbers, and spaces and are not case sensitive. Plural
+# keywords are used for list topics. You can redefine keywords found in the
+# main topics file.
+#
+# Index: [yes|no]
+# Whether the topics get their own index. Defaults to yes. Everything is
+# included in the general index regardless of this setting.
+#
+# Scope: [normal|start|end|always global]
+# How the topics affects scope. Defaults to normal.
+# normal - Topics stay within the current scope.
+# start - Topics start a new scope for all the topics beneath it,
+# like class topics.
+# end - Topics reset the scope back to global for all the topics
+# beneath it.
+# always global - Topics are defined as global, but do not change the scope
+# for any other topics.
+#
+# Class Hierarchy: [yes|no]
+# Whether the topics are part of the class hierarchy. Defaults to no.
+#
+# Page Title If First: [yes|no]
+# Whether the topic's title becomes the page title if it's the first one in
+# a file. Defaults to no.
+#
+# Break Lists: [yes|no]
+# Whether list topics should be broken into individual topics in the output.
+# Defaults to no.
+#
+# Can Group With: [type], [type], ...
+# Defines a list of topic types that this one can possibly be grouped with.
+# Defaults to none.
+#-------------------------------------------------------------------------------
+
+# The following topics are defined in the main file, if you'd like to alter
+# their behavior or add keywords:
+#
+# Generic, Class, Interface, Section, File, Group, Function, Variable,
+# Property, Type, Constant, Enumeration, Event, Delegate, Macro,
+# Database, Database Table, Database View, Database Index, Database
+# Cursor, Database Trigger, Cookie, Build Target
+
+# If you add something that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# topics [at] naturaldocs [dot] org.
+
+
+Topic Type: Constructor
+
+ Class Hierarchy: Yes
+ Keywords:
+ constructor
+ initialize
+
+
+Alter Topic Type: Function
+
+ Add Keywords:
+ apimethod
+ apifunction
+
+
+Alter Topic Type: Property
+
+ Add Keywords:
+ apiproperty
diff --git a/misc/openlayers/art/arrows.svg b/misc/openlayers/art/arrows.svg
new file mode 100644
index 0000000..d40712d
--- /dev/null
+++ b/misc/openlayers/art/arrows.svg
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18.000000px"
+ height="18.000000px"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.42"
+ sodipodi:docbase="/Users/phil/Documents/work/metacarta/2svn.openlayers.net/follower/code/b-edits-1/openlayers/assets/ui_elements"
+ sodipodi:docname="pan_icons.svg"
+ inkscape:export-filename="/Users/phil/Documents/work/metacarta/2svn.openlayers.net/follower/code/b-edits-1/openlayers/assets/ui_elements/zoom-minus-mini.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1.0000000"
+ inkscape:cx="9.0000000"
+ inkscape:cy="9.0000000"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1226"
+ inkscape:window-height="800"
+ inkscape:window-x="42"
+ inkscape:window-y="22" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g2110">
+ <path
+ transform="matrix(0.000000,-1.003160,1.003160,0.000000,-2.844000e-2,18.02844)"
+ d="M 17.971654 9.0000000 A 8.9716539 8.9716539 0 1 1 0.028346062,9.0000000 A 8.9716539 8.9716539 0 1 1 17.971654 9.0000000 z"
+ sodipodi:ry="8.9716539"
+ sodipodi:rx="8.9716539"
+ sodipodi:cy="9.0000000"
+ sodipodi:cx="9.0000000"
+ id="path2059"
+ style="fill:#00008b;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000"
+ sodipodi:type="arc" />
+ <rect
+ transform="matrix(0.000000,-1.000000,1.000000,0.000000,0.000000,0.000000)"
+ y="4.3599998e-06"
+ x="-9.0000048"
+ height="18.000000"
+ width="9.0000000"
+ id="rect2061"
+ style="fill:#00008b;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="zoom"
+ style="display:inline">
+ <text
+ xml:space="preserve"
+ style="font-size:18.000000px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1.0000000;font-family:Helvetica"
+ x="3.7485352"
+ y="-3.4077148"
+ id="text2114"
+ sodipodi:linespacing="125.00000%"
+ transform="scale(1.000000,-1.000000)"><tspan
+ sodipodi:role="line"
+ id="tspan2118"
+ x="3.7485352"
+ y="-3.4077148">−</tspan></text>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="overlay"
+ style="display:none">
+ <path
+ style="font-size:12.000000px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Verdana"
+ d="M 2.9678210,9.0332227 C 2.9678314,9.5015070 3.1752531,9.8938504 3.5900866,10.210254 C 3.6463464,10.254553 3.9451742,10.453889 4.4865710,10.808262 L 8.1568835,13.247754 C 8.2693886,13.323691 8.4240759,13.393300 8.6209460,13.456582 C 8.8107943,13.519862 9.0041535,13.551503 9.2010240,13.551504 C 9.5736843,13.551503 9.8936056,13.431269 10.160789,13.190801 C 10.427980,12.950332 10.561574,12.659238 10.561572,12.317520 C 10.561574,11.861896 10.322511,11.488536 9.8443836,11.197442 L 8.5998522,10.428575 L 14.548408,10.428575 C 15.026535,10.428577 15.406221,10.298849 15.687470,10.039395 C 15.968721,9.7799443 16.109345,9.4318977 16.109345,8.9952540 C 16.109345,8.5396330 15.961690,8.1915865 15.666377,7.9511133 C 15.364034,7.7106494 14.980831,7.5904152 14.516767,7.5904102 L 8.6314928,7.5904102 L 9.9920390,6.7076368 C 10.160793,6.5937365 10.290871,6.4481897 10.382274,6.2709962 C 10.466652,6.0874870 10.508840,5.8913153 10.508837,5.6824805 C 10.508840,5.3281127 10.378761,5.0338552 10.118602,4.7997070 C 9.8584498,4.5655745 9.5279814,4.4485043 9.1271961,4.4484962 C 8.7615757,4.4485043 8.4029823,4.5624104 8.0514147,4.7902149 L 3.9697741,7.5239649 C 3.6393151,7.7454540 3.4002529,7.9542820 3.2525866,8.1504493 C 3.0627532,8.3972503 2.9678314,8.6756875 2.9678210,8.9857618 L 2.9678210,9.0332227"
+ id="text2082"
+ sodipodi:nodetypes="csccsssscccssscccssssccscc" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="pan triangle"
+ style="display:none">
+ <path
+ sodipodi:type="star"
+ style="fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000"
+ id="path2107"
+ sodipodi:sides="3"
+ sodipodi:cx="6.8031497"
+ sodipodi:cy="5.1874018"
+ sodipodi:r1="5.0880113"
+ sodipodi:r2="2.5440056"
+ sodipodi:arg1="1.0471976"
+ sodipodi:arg2="2.0943951"
+ inkscape:flatsided="false"
+ inkscape:rounded="0.0000000"
+ inkscape:randomized="0.0000000"
+ d="M 9.3471551,9.5937489 L 5.5311469,7.3905753 L 1.7151384,5.1874015 L 5.5311469,2.9842283 L 9.3471555,0.78105489 L 9.3471553,5.1874018 L 9.3471551,9.5937489 z "
+ transform="matrix(0.000000,-1.331000,1.331000,0.000000,2.038879,16.92102)" />
+ </g>
+</svg>
diff --git a/misc/openlayers/art/layer-switcher-maximize.svg b/misc/openlayers/art/layer-switcher-maximize.svg
new file mode 100644
index 0000000..6b9f0d3
--- /dev/null
+++ b/misc/openlayers/art/layer-switcher-maximize.svg
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18.000000px"
+ height="18.000000px"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.43"
+ sodipodi:docbase="/home/sderle/projects/openlayers/layerswitcher/art"
+ sodipodi:docname="layer-switcher-maximize.svg"
+ inkscape:export-filename="/Users/phil/Documents/work/metacarta/2svn.openlayers.net/follower/code/b-edits-1/openlayers/assets/ui_elements/zoom-minus-mini.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="9"
+ inkscape:cy="9"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer4"
+ inkscape:window-width="1226"
+ inkscape:window-height="800"
+ inkscape:window-x="42"
+ inkscape:window-y="25" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="background"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <g
+ id="g2110"
+ transform="matrix(-7.849624e-17,1,-1,-7.849624e-17,18,0)">
+ <path
+ transform="matrix(0,-1.00316,1.00316,0,-2.844e-2,18.02844)"
+ d="M 17.971654 9 A 8.9716539 8.9716539 0 1 1 0.028346062,9 A 8.9716539 8.9716539 0 1 1 17.971654 9 z"
+ sodipodi:ry="8.9716539"
+ sodipodi:rx="8.9716539"
+ sodipodi:cy="9"
+ sodipodi:cx="9"
+ id="path2059"
+ style="fill:#00008b;fill-opacity:1;stroke:none;stroke-width:39.98414612;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="arc" />
+ <rect
+ transform="matrix(0,-1,1,0,0,0)"
+ y="4.3599998e-06"
+ x="-9.0000048"
+ height="18"
+ width="9"
+ id="rect2061"
+ style="fill:#00008b;fill-opacity:1;stroke:none;stroke-width:39.98414612;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="zoom"
+ style="display:inline">
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;display:inline;font-family:Helvetica"
+ x="1.5214844"
+ y="-3.2949219"
+ id="text2114"
+ sodipodi:linespacing="125%"
+ transform="scale(1,-1)"><tspan
+ sodipodi:role="line"
+ id="tspan2118"
+ x="1.5214844"
+ y="-3.2949219">+</tspan></text>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="overlay"
+ style="display:none">
+ <path
+ style="font-size:12.000000px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125.00000%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Verdana"
+ d="M 2.9678210,9.0332227 C 2.9678314,9.5015070 3.1752531,9.8938504 3.5900866,10.210254 C 3.6463464,10.254553 3.9451742,10.453889 4.4865710,10.808262 L 8.1568835,13.247754 C 8.2693886,13.323691 8.4240759,13.393300 8.6209460,13.456582 C 8.8107943,13.519862 9.0041535,13.551503 9.2010240,13.551504 C 9.5736843,13.551503 9.8936056,13.431269 10.160789,13.190801 C 10.427980,12.950332 10.561574,12.659238 10.561572,12.317520 C 10.561574,11.861896 10.322511,11.488536 9.8443836,11.197442 L 8.5998522,10.428575 L 14.548408,10.428575 C 15.026535,10.428577 15.406221,10.298849 15.687470,10.039395 C 15.968721,9.7799443 16.109345,9.4318977 16.109345,8.9952540 C 16.109345,8.5396330 15.961690,8.1915865 15.666377,7.9511133 C 15.364034,7.7106494 14.980831,7.5904152 14.516767,7.5904102 L 8.6314928,7.5904102 L 9.9920390,6.7076368 C 10.160793,6.5937365 10.290871,6.4481897 10.382274,6.2709962 C 10.466652,6.0874870 10.508840,5.8913153 10.508837,5.6824805 C 10.508840,5.3281127 10.378761,5.0338552 10.118602,4.7997070 C 9.8584498,4.5655745 9.5279814,4.4485043 9.1271961,4.4484962 C 8.7615757,4.4485043 8.4029823,4.5624104 8.0514147,4.7902149 L 3.9697741,7.5239649 C 3.6393151,7.7454540 3.4002529,7.9542820 3.2525866,8.1504493 C 3.0627532,8.3972503 2.9678314,8.6756875 2.9678210,8.9857618 L 2.9678210,9.0332227"
+ id="text2082"
+ sodipodi:nodetypes="csccsssscccssscccssssccscc" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="pan triangle"
+ style="display:none">
+ <path
+ sodipodi:type="star"
+ style="fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none;stroke-opacity:1.0000000"
+ id="path2107"
+ sodipodi:sides="3"
+ sodipodi:cx="6.8031497"
+ sodipodi:cy="5.1874018"
+ sodipodi:r1="5.0880113"
+ sodipodi:r2="2.5440056"
+ sodipodi:arg1="1.0471976"
+ sodipodi:arg2="2.0943951"
+ inkscape:flatsided="false"
+ inkscape:rounded="0.0000000"
+ inkscape:randomized="0.0000000"
+ d="M 9.3471551,9.5937489 L 5.5311469,7.3905753 L 1.7151384,5.1874015 L 5.5311469,2.9842283 L 9.3471555,0.78105489 L 9.3471553,5.1874018 L 9.3471551,9.5937489 z "
+ transform="matrix(0.000000,-1.331000,1.331000,0.000000,2.038879,16.92102)" />
+ </g>
+</svg>
diff --git a/misc/openlayers/art/layer-switcher-minimize.svg b/misc/openlayers/art/layer-switcher-minimize.svg
new file mode 100644
index 0000000..70fcb8c
--- /dev/null
+++ b/misc/openlayers/art/layer-switcher-minimize.svg
@@ -0,0 +1,142 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ inkscape:export-ydpi="90.000000"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-filename="/home/sderle/projects/openlayers/layerswitcher/img/layer-switcher-minimize.png"
+ sodipodi:docname="layer-switcher-minimize.svg"
+ sodipodi:docbase="/home/sderle/projects/openlayers/layerswitcher/art"
+ inkscape:version="0.43"
+ sodipodi:version="0.32"
+ id="svg2"
+ height="18.000000px"
+ width="18.000000px">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ inkscape:window-y="26"
+ inkscape:window-x="42"
+ inkscape:window-height="800"
+ inkscape:window-width="1226"
+ inkscape:current-layer="layer1"
+ inkscape:document-units="px"
+ inkscape:cy="9.3025513"
+ inkscape:cx="9"
+ inkscape:zoom="16"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ style="display:inline"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="background">
+ <rect
+ style="fill:#00008b;fill-opacity:0;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2061"
+ width="18.014498"
+ height="18.000000"
+ x="-18.014502"
+ y="4.3599998e-06"
+ transform="matrix(0.000000,-1.000000,1.000000,0.000000,0.000000,0.000000)"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ inkscape:export-filename="/home/sderle/projects/openlayers/layerswitcher/art/layer-switcher-minimize.png" />
+ </g>
+ <g
+ inkscape:label="graticule"
+ id="layer5"
+ inkscape:groupmode="layer">
+ <text
+ xml:space="preserve"
+ style="font-size:18px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;display:inline;font-family:Helvetica"
+ x="5.2802601"
+ y="-3.8032362"
+ id="text1432"
+ sodipodi:linespacing="125%"
+ transform="scale(1,-1)"><tspan
+ sodipodi:role="line"
+ id="tspan1434"
+ x="5.2802601"
+ y="-3.8032362">-</tspan></text>
+ <rect
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.99699599px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1"
+ id="rect1438"
+ width="13.003004"
+ height="13.002999"
+ x="2.498498"
+ y="2.4984975" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="zoom"
+ id="layer4"
+ inkscape:groupmode="layer">
+ <text
+ transform="scale(1.000000,-1.000000)"
+ sodipodi:linespacing="125.00000%"
+ id="text2114"
+ y="-3.4077148"
+ x="3.7485352"
+ style="font-size:18.000000;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ffffff;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1.0000000;font-family:Helvetica;text-anchor:start;writing-mode:lr-tb"
+ xml:space="preserve"><tspan
+ y="-3.4077148"
+ x="3.7485352"
+ id="tspan2118"
+ sodipodi:role="line">−</tspan></text>
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="overlay"
+ id="layer2"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:nodetypes="csccsssscccssscccssssccscc"
+ id="text2082"
+ d="M 2.9678210,9.0332227 C 2.9678314,9.5015070 3.1752531,9.8938504 3.5900866,10.210254 C 3.6463464,10.254553 3.9451742,10.453889 4.4865710,10.808262 L 8.1568835,13.247754 C 8.2693886,13.323691 8.4240759,13.393300 8.6209460,13.456582 C 8.8107943,13.519862 9.0041535,13.551503 9.2010240,13.551504 C 9.5736843,13.551503 9.8936056,13.431269 10.160789,13.190801 C 10.427980,12.950332 10.561574,12.659238 10.561572,12.317520 C 10.561574,11.861896 10.322511,11.488536 9.8443836,11.197442 L 8.5998522,10.428575 L 14.548408,10.428575 C 15.026535,10.428577 15.406221,10.298849 15.687470,10.039395 C 15.968721,9.7799443 16.109345,9.4318977 16.109345,8.9952540 C 16.109345,8.5396330 15.961690,8.1915865 15.666377,7.9511133 C 15.364034,7.7106494 14.980831,7.5904152 14.516767,7.5904102 L 8.6314928,7.5904102 L 9.9920390,6.7076368 C 10.160793,6.5937365 10.290871,6.4481897 10.382274,6.2709962 C 10.466652,6.0874870 10.508840,5.8913153 10.508837,5.6824805 C 10.508840,5.3281127 10.378761,5.0338552 10.118602,4.7997070 C 9.8584498,4.5655745 9.5279814,4.4485043 9.1271961,4.4484962 C 8.7615757,4.4485043 8.4029823,4.5624104 8.0514147,4.7902149 L 3.9697741,7.5239649 C 3.6393151,7.7454540 3.4002529,7.9542820 3.2525866,8.1504493 C 3.0627532,8.3972503 2.9678314,8.6756875 2.9678210,8.9857618 L 2.9678210,9.0332227"
+ style="font-size:12.000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Verdana;text-anchor:start;writing-mode:lr-tb" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="pan triangle"
+ id="layer3"
+ inkscape:groupmode="layer">
+ <path
+ transform="matrix(0.000000,-1.331000,1.331000,0.000000,2.038879,16.92102)"
+ d="M 9.3471551,9.5937489 L 5.5311469,7.3905753 L 1.7151384,5.1874015 L 5.5311469,2.9842283 L 9.3471555,0.78105489 L 9.3471553,5.1874018 L 9.3471551,9.5937489 z "
+ inkscape:randomized="0.0000000"
+ inkscape:rounded="0.0000000"
+ inkscape:flatsided="false"
+ sodipodi:arg2="2.0943951"
+ sodipodi:arg1="1.0471976"
+ sodipodi:r2="2.5440056"
+ sodipodi:r1="5.0880113"
+ sodipodi:cy="5.1874018"
+ sodipodi:cx="6.8031497"
+ sodipodi:sides="3"
+ id="path2107"
+ style="fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ sodipodi:type="star" />
+ </g>
+</svg>
diff --git a/misc/openlayers/art/marker.svg b/misc/openlayers/art/marker.svg
new file mode 100644
index 0000000..9f0d8dc
--- /dev/null
+++ b/misc/openlayers/art/marker.svg
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+]>
+<svg version="1.1" id="Layer_1" xmlns:i="&ns_ai;"
+ xmlns="&ns_svg;" width="20.5" height="24.5" viewBox="0 0 20.5 24.5"
+ overflow="visible" enable-background="new 0 0 20.5 24.5" xml:space="preserve">
+ <g i:extraneous="self">
+ <rect x="0.25" y="0.25" opacity="0" stroke="#000000" stroke-width="1" width="20" height="24"/>
+ <g id="XMLID_2_">
+ <g>
+ <polygon fill="#FF0000" points="10.12,0.6 19.93,7.71 10.06,24.03 0.6,7.58 "/>
+ </g>
+ <g>
+ <polyline fill="none" stroke="#000000" stroke-width="1" points="9.98,24.16 10.06,24.03 19.93,7.71 20.06,7.49 "/>
+ <polyline fill="none" stroke="#000000" stroke-width="1" points="10.13,24.16 10.06,24.03 0.6,7.58 0.5,7.41 "/>
+ <polyline fill="none" stroke="#000000" stroke-width="1" points="0.41,7.72 0.6,7.58 10.12,0.6 10.29,0.47 "/>
+ <polyline fill="none" stroke="#000000" stroke-width="1" points="9.94,0.47 10.12,0.6 19.93,7.71 20.06,7.8 "/>
+ </g>
+ </g>
+ <ellipse stroke="#000000" stroke-width="1" cx="10.125" cy="9.25" rx="1.5" ry="1.5"/>
+ </g>
+</svg>
diff --git a/misc/openlayers/art/measuring-stick-off.svg b/misc/openlayers/art/measuring-stick-off.svg
new file mode 100644
index 0000000..2e47a2f
--- /dev/null
+++ b/misc/openlayers/art/measuring-stick-off.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="28.375" height="28" viewBox="0 0 28.375 28"
+ overflow="visible" enable-background="new 0 0 28.375 28" xml:space="preserve">
+ <g i:extraneous="self">
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M27.875,1.375"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M0,1.375"/>
+ <path fill="none" stroke="#FFFFFF" d="M0,27.475"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,27.645"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,0.544"/>
+ <line fill="none" stroke="#00008B" x1="0.875" y1="27.5" x2="27.875" y2="27.475"/>
+ <rect x="0.875" y="1.045" fill="#00008B" width="27.125" height="26"/>
+ <line fill="none" stroke="#00008B" x1="27.875" y1="26.925" x2="27.875" y2="0.925"/>
+ <line fill="none" stroke="#FFFFFF" x1="1.375" y1="0.5" x2="27.375" y2="0.5"/>
+ <line fill="none" stroke="#FFFFFF" x1="0.875" y1="1" x2="0.875" y2="27"/>
+
+ <rect x="5.252" y="11.157" transform="matrix(0.7933 -0.6088 0.6088 0.7933 -5.1621 12.1086)" fill="#FFBF00" width="20" height="5"/>
+ <line fill="#FFBF00" stroke="#000000" x1="8.376" y1="15.783" x2="10.717" y2="18.766"/>
+ <line fill="#FFBF00" stroke="#000000" x1="11.946" y1="13.043" x2="14.288" y2="16.027"/>
+ <line fill="#FFBF00" stroke="#000000" x1="15.342" y1="10.437" x2="17.684" y2="13.42"/>
+ <line fill="#FFBF00" stroke="#000000" x1="18.69" y1="7.869" x2="21.03" y2="10.852"/>
+ </g>
+</svg>
diff --git a/misc/openlayers/art/measuring-stick-on.svg b/misc/openlayers/art/measuring-stick-on.svg
new file mode 100644
index 0000000..ca0c18c
--- /dev/null
+++ b/misc/openlayers/art/measuring-stick-on.svg
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="28.375" height="28" viewBox="0 0 28.375 28"
+ overflow="visible" enable-background="new 0 0 28.375 28" xml:space="preserve">
+ <g i:extraneous="self">
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M27.875,1.375"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M0,1.375"/>
+ <path fill="none" stroke="#FFFFFF" d="M0,27.475"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,27.645"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,0.544"/>
+ <line fill="none" stroke="#FFFFFF" x1="0.875" y1="27.5" x2="27.875" y2="27.475"/>
+ <rect x="0.875" y="1.045" fill="#ADD8E6" width="27.125" height="26"/>
+ <line fill="none" stroke="#FFFFFF" x1="27.875" y1="26.925" x2="27.875" y2="0.925"/>
+ <line fill="none" stroke="#00008B" x1="1.375" y1="0.5" x2="27.375" y2="0.5"/>
+ <line fill="none" stroke="#00008B" x1="0.875" y1="1" x2="0.875" y2="27"/>
+
+ <rect x="4.252" y="12.157" transform="matrix(0.7933 -0.6088 0.6088 0.7933 -5.9776 11.7065)" fill="#FFBF00" width="20" height="5"/>
+ <line fill="#FFBF00" stroke="#000000" x1="7.376" y1="16.783" x2="9.717" y2="19.766"/>
+ <line fill="#FFBF00" stroke="#000000" x1="10.946" y1="14.043" x2="13.288" y2="17.027"/>
+ <line fill="#FFBF00" stroke="#000000" x1="14.342" y1="11.437" x2="16.684" y2="14.42"/>
+ <line fill="#FFBF00" stroke="#000000" x1="17.69" y1="8.869" x2="20.03" y2="11.852"/>
+ </g>
+</svg>
diff --git a/misc/openlayers/art/panning-hand-off.svg b/misc/openlayers/art/panning-hand-off.svg
new file mode 100644
index 0000000..bf2e40b
--- /dev/null
+++ b/misc/openlayers/art/panning-hand-off.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="28.375" height="28" viewBox="0 0 28.375 28"
+ overflow="visible" enable-background="new 0 0 28.375 28" xml:space="preserve">
+ <g i:extraneous="self">
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M27.875,1.375"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M0,1.375"/>
+ <path fill="none" stroke="#FFFFFF" d="M0,27.475"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,27.645"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,0.544"/>
+ <line fill="none" stroke="#00008B" x1="0.875" y1="27.5" x2="27.875" y2="27.475"/>
+ <rect x="0.875" y="1.045" fill="#00008B" width="27.125" height="26"/>
+ <line fill="none" stroke="#00008B" x1="27.875" y1="26.925" x2="27.875" y2="0.925"/>
+ <line fill="none" stroke="#FFFFFF" x1="1.375" y1="0.5" x2="27.375" y2="0.5"/>
+ <line fill="none" stroke="#FFFFFF" x1="0.875" y1="1" x2="0.875" y2="27"/>
+ <path fill="#FFFFFF" stroke="#000000" d="M9.458,22.458c-0.473-0.168-1.02-1.269-1.363-1.695
+ c-0.479-0.595-0.894-1.245-1.377-1.829c-0.733-0.887-1.356-1.729-1.854-2.764c-0.375-0.782-0.884-1.997-0.005-2.648
+ c1.036-0.767,2.095-0.162,2.934,0.479c0.844,0.646,1.729,1.18,2.526,1.869c-0.372-0.305-0.548-0.986-0.778-1.421
+ c-0.253-0.477-0.565-0.906-0.822-1.375c-0.359-0.657-0.767-1.201-1.21-1.823c-0.544-0.763-1.051-1.74-0.925-2.709
+ c0.133-1.025,0.972-1.527,1.924-1.207c1.202,0.405,1.969,1.692,2.627,2.698c0.636,0.971,1.201,2.159,1.938,3.042
+ c-0.222-0.209-0.148-0.508-0.155-0.782c-0.01-0.37-0.097-0.626-0.219-0.967c-0.162-0.455-0.219-0.972-0.24-1.45
+ c-0.034-0.776-0.25-1.465-0.25-2.247c0-0.988-0.14-1.963,0.71-2.631c0.666-0.523,1.649-0.764,2.381-0.248
+ c1.385,0.976,1.057,2.999,1.209,4.458c0.079,0.759,0.165,1.489,0.2,2.263c0.022,0.496,0.209,1.067,0.168,1.558
+ c-0.146-0.269-0.113-0.749-0.127-1.057c-0.022-0.502-0.041-0.982-0.115-1.47c-0.114-0.743-0.188-1.564-0.25-2.324
+ c-0.152-1.877,2.054-3.17,3.301-1.469c0.884,1.206,0.773,2.643,0.773,4.085c0,0.698-0.034,1.251-0.22,1.91
+ c-0.13,0.459-0.293,1.222-0.239,1.638c0-0.902,0.334-1.697,0.334-2.588c0-0.678-0.272-2.099,0.581-2.334
+ c1.065-0.294,2.008,0.875,2.428,1.66c0.458,0.855,0.555,1.901,0.409,2.882c-0.318,2.154-0.845,4.227-2.096,6.072
+ c-0.268,0.396-0.485,0.838-0.79,1.22c-0.361,0.452-0.923,0.772-1.416,1.071c-0.381,0.232-0.764,0.53-1.176,0.604
+ c-0.822,0.149-1.725,0.157-2.565,0.157c-1.29,0-2.564-0.068-3.832-0.251c-0.799-0.115-2.12,0.153-2.668-0.54"/>
+ </g>
+</svg>
diff --git a/misc/openlayers/art/panning-hand-on.svg b/misc/openlayers/art/panning-hand-on.svg
new file mode 100644
index 0000000..139ce97
--- /dev/null
+++ b/misc/openlayers/art/panning-hand-on.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+ <!ENTITY ns_svg "http://www.w3.org/2000/svg">
+ <!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="28.375" height="28" viewBox="0 0 28.375 28"
+ overflow="visible" enable-background="new 0 0 28.375 28" xml:space="preserve">
+ <g i:extraneous="self">
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M27.875,1.375"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M0,1.375"/>
+ <path fill="none" stroke="#FFFFFF" d="M0,27.475"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,27.645"/>
+ <path fill="none" stroke="#00008B" stroke-width="1.5" d="M1,0.544"/>
+ <line fill="none" stroke="#FFFFFF" x1="0.875" y1="27.5" x2="27.875" y2="27.475"/>
+ <rect x="0.875" y="1.045" fill="#ADD8E6" width="27.125" height="26"/>
+ <line fill="none" stroke="#FFFFFF" x1="27.875" y1="26.925" x2="27.875" y2="0.925"/>
+ <line fill="none" stroke="#00008B" x1="1.375" y1="0.5" x2="27.375" y2="0.5"/>
+ <line fill="none" stroke="#00008B" x1="0.875" y1="1" x2="0.875" y2="27"/>
+ <path fill="#FFFFFF" stroke="#000000" d="M8.458,23.458c-0.473-0.168-1.02-1.269-1.363-1.695
+ c-0.479-0.595-0.894-1.245-1.377-1.829c-0.733-0.887-1.356-1.729-1.854-2.764c-0.375-0.782-0.884-1.997-0.005-2.648
+ c1.036-0.767,2.095-0.161,2.934,0.479c0.844,0.646,1.729,1.18,2.526,1.869c-0.372-0.305-0.548-0.986-0.778-1.421
+ c-0.253-0.477-0.565-0.906-0.822-1.375c-0.359-0.657-0.767-1.201-1.21-1.823c-0.544-0.763-1.051-1.74-0.925-2.709
+ c0.133-1.025,0.972-1.527,1.924-1.207c1.202,0.405,1.969,1.692,2.627,2.698c0.636,0.971,1.201,2.159,1.938,3.042
+ c-0.222-0.21-0.148-0.509-0.155-0.783c-0.01-0.37-0.097-0.626-0.219-0.967c-0.162-0.455-0.219-0.972-0.24-1.45
+ c-0.034-0.776-0.25-1.465-0.25-2.247c0-0.988-0.14-1.963,0.71-2.631c0.666-0.523,1.649-0.764,2.381-0.248
+ c1.385,0.976,1.057,2.999,1.209,4.458c0.079,0.759,0.165,1.489,0.2,2.263c0.022,0.496,0.209,1.068,0.168,1.558
+ c-0.146-0.27-0.113-0.75-0.127-1.058c-0.022-0.502-0.041-0.982-0.115-1.47c-0.114-0.743-0.188-1.564-0.25-2.324
+ c-0.152-1.877,2.054-3.17,3.301-1.469c0.884,1.206,0.773,2.643,0.773,4.085c0,0.698-0.034,1.251-0.22,1.91
+ c-0.13,0.459-0.293,1.222-0.239,1.639c0-0.902,0.334-1.697,0.334-2.589c0-0.678-0.272-2.099,0.581-2.334
+ c1.065-0.294,2.008,0.875,2.428,1.66c0.458,0.855,0.555,1.901,0.409,2.883c-0.318,2.153-0.845,4.227-2.096,6.071
+ c-0.268,0.396-0.485,0.838-0.79,1.22c-0.361,0.452-0.923,0.772-1.416,1.071c-0.381,0.232-0.764,0.53-1.176,0.604
+ c-0.822,0.149-1.725,0.157-2.565,0.157c-1.29,0-2.564-0.068-3.832-0.251c-0.799-0.115-2.12,0.153-2.668-0.54"/>
+ </g>
+</svg>
diff --git a/misc/openlayers/art/slider.svg b/misc/openlayers/art/slider.svg
new file mode 100644
index 0000000..9fa435d
--- /dev/null
+++ b/misc/openlayers/art/slider.svg
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="210mm"
+ height="297mm"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.41.1"
+ sodipodi:docbase="/afs/metacarta.com/user/sderle/public_html/ol.zoombar/art"
+ sodipodi:docname="slider.svg"
+ inkscape:export-filename="/afs/metacarta.com/user/sderle/public_html/ol.zoombar/img/slider.png"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-ydpi="90.000000">
+ <defs
+ id="defs3" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="39.870978"
+ inkscape:cx="140.00732"
+ inkscape:cy="695.49796"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1010"
+ inkscape:window-height="538"
+ inkscape:window-x="20"
+ inkscape:window-y="107" />
+ <metadata
+ id="metadata4">
+ <rdf:RDF
+ id="RDF5">
+ <cc:Work
+ rdf:about=""
+ id="Work6">
+ <dc:format
+ id="format7">image/svg+xml</dc:format>
+ <dc:type
+ id="type9"
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="fill:#00008b;fill-opacity:1.0000000;stroke:#000080;stroke-width:4.9999957;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect1291"
+ x="132.50000"
+ y="354.86218"
+ width="15.014628"
+ height="4.0040474" />
+ <path
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:2.0000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ d="M 134.08880,356.89603 L 146.00000,356.87002"
+ id="path2052" />
+ </g>
+</svg>
diff --git a/misc/openlayers/art/zoom-world.svg b/misc/openlayers/art/zoom-world.svg
new file mode 100644
index 0000000..749e7c7
--- /dev/null
+++ b/misc/openlayers/art/zoom-world.svg
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:xml="http://www.w3.org/XML/1998/namespace"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ inkscape:export-ydpi="90.000000"
+ inkscape:export-xdpi="90.000000"
+ inkscape:export-filename="/Users/phil/Documents/work/metacarta/2svn.openlayers.net/follower/code/b-edits-1/openlayers/assets/ui_elements/zoom-minus-mini.png"
+ sodipodi:docname="zoom-world.svg"
+ sodipodi:docbase="/afs/metacarta.com/user/sderle/OL/openlayers/assets/ui_elements"
+ inkscape:version="0.41.1"
+ sodipodi:version="0.32"
+ id="svg2"
+ height="18.000000px"
+ width="18.000000px">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ inkscape:window-y="26"
+ inkscape:window-x="42"
+ inkscape:window-height="800"
+ inkscape:window-width="1226"
+ inkscape:current-layer="layer5"
+ inkscape:document-units="px"
+ inkscape:cy="9.3025513"
+ inkscape:cx="9.0000000"
+ inkscape:zoom="1.0000000"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF
+ id="RDF1295">
+ <cc:Work
+ id="Work1297"
+ rdf:about="">
+ <dc:format
+ id="format1299">image/svg+xml</dc:format>
+ <dc:type
+ id="type1301"
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ style="display:inline"
+ id="layer1"
+ inkscape:groupmode="layer"
+ inkscape:label="background">
+ <rect
+ style="fill:#00008b;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ id="rect2061"
+ width="18.014498"
+ height="18.000000"
+ x="-18.014502"
+ y="4.3599998e-06"
+ transform="matrix(0.000000,-1.000000,1.000000,0.000000,0.000000,0.000000)" />
+ </g>
+ <g
+ inkscape:label="graticule"
+ id="layer5"
+ inkscape:groupmode="layer">
+ <g
+ transform="translate(0.000000,-4.419417e-2)"
+ id="g2876">
+ <path
+ transform="matrix(1.155321,0.000000,0.000000,1.155321,-2.292065,-1.751761)"
+ d="M 16.869573 9.3188362 A 7.0579743 7.0579743 0 1 1 2.7536244,9.3188362 A 7.0579743 7.0579743 0 1 1 16.869573 9.3188362 z"
+ sodipodi:ry="7.0579743"
+ sodipodi:rx="7.0579743"
+ sodipodi:cy="9.3188362"
+ sodipodi:cx="9.8115988"
+ id="path1321"
+ style="fill:none;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:0.50000000;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2086"
+ d="M 8.1145142,1.1289949 C 15.315267,9.5238831 8.3963197,17.101449 8.3963197,17.057255"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2846"
+ d="M 11.588885,1.3666059 C 18.922222,9.5405232 11.605526,16.764536 11.605526,16.764536"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2848"
+ d="M 5.2258471,2.0593951 C 11.080923,9.7405585 5.3432210,16.278941 5.3432210,16.278941"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2850"
+ d="M 2.7388198,3.7571959 C 6.3686281,9.1817837 2.9127331,14.361601 2.9127331,14.361601"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ </g>
+ <g
+ transform="matrix(-6.269315e-2,0.998033,-0.998033,-6.269315e-2,18.62352,0.612232)"
+ id="g2883">
+ <path
+ transform="matrix(1.155321,0.000000,0.000000,1.155321,-2.292065,-1.751761)"
+ d="M 16.869573 9.3188362 A 7.0579743 7.0579743 0 1 1 2.7536244,9.3188362 A 7.0579743 7.0579743 0 1 1 16.869573 9.3188362 z"
+ sodipodi:ry="7.0579743"
+ sodipodi:rx="7.0579743"
+ sodipodi:cy="9.3188362"
+ sodipodi:cx="9.8115988"
+ id="path2885"
+ style="fill:none;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:0.50000000;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ sodipodi:type="arc" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2887"
+ d="M 8.1145142,1.1289949 C 15.315267,9.5238831 8.3963197,17.101449 8.3963197,17.057255"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2889"
+ d="M 11.588885,1.3666059 C 18.922222,9.5405232 11.605526,16.764536 11.605526,16.764536"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2891"
+ d="M 5.2258471,2.0593951 C 11.080923,9.7405585 5.3432210,16.278941 5.3432210,16.278941"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ <path
+ sodipodi:nodetypes="cc"
+ id="path2893"
+ d="M 2.7388198,3.7571959 C 6.3686281,9.1817837 2.9127331,14.361601 2.9127331,14.361601"
+ style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.50000000;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000" />
+ </g>
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="zoom"
+ id="layer4"
+ inkscape:groupmode="layer">
+ <text
+ transform="scale(1.000000,-1.000000)"
+ sodipodi:linespacing="125.00000%"
+ id="text2114"
+ y="-3.4077148"
+ x="3.7485352"
+ style="font-size:18.000000;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;fill:#ffffff;fill-opacity:1.0000000;stroke:#ffffff;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1.0000000;font-family:Helvetica;text-anchor:start;writing-mode:lr-tb"
+ xml:space="preserve"><tspan
+ y="-3.4077148"
+ x="3.7485352"
+ id="tspan2118"
+ sodipodi:role="line">−</tspan></text>
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="overlay"
+ id="layer2"
+ inkscape:groupmode="layer">
+ <path
+ sodipodi:nodetypes="csccsssscccssscccssssccscc"
+ id="text2082"
+ d="M 2.9678210,9.0332227 C 2.9678314,9.5015070 3.1752531,9.8938504 3.5900866,10.210254 C 3.6463464,10.254553 3.9451742,10.453889 4.4865710,10.808262 L 8.1568835,13.247754 C 8.2693886,13.323691 8.4240759,13.393300 8.6209460,13.456582 C 8.8107943,13.519862 9.0041535,13.551503 9.2010240,13.551504 C 9.5736843,13.551503 9.8936056,13.431269 10.160789,13.190801 C 10.427980,12.950332 10.561574,12.659238 10.561572,12.317520 C 10.561574,11.861896 10.322511,11.488536 9.8443836,11.197442 L 8.5998522,10.428575 L 14.548408,10.428575 C 15.026535,10.428577 15.406221,10.298849 15.687470,10.039395 C 15.968721,9.7799443 16.109345,9.4318977 16.109345,8.9952540 C 16.109345,8.5396330 15.961690,8.1915865 15.666377,7.9511133 C 15.364034,7.7106494 14.980831,7.5904152 14.516767,7.5904102 L 8.6314928,7.5904102 L 9.9920390,6.7076368 C 10.160793,6.5937365 10.290871,6.4481897 10.382274,6.2709962 C 10.466652,6.0874870 10.508840,5.8913153 10.508837,5.6824805 C 10.508840,5.3281127 10.378761,5.0338552 10.118602,4.7997070 C 9.8584498,4.5655745 9.5279814,4.4485043 9.1271961,4.4484962 C 8.7615757,4.4485043 8.4029823,4.5624104 8.0514147,4.7902149 L 3.9697741,7.5239649 C 3.6393151,7.7454540 3.4002529,7.9542820 3.2525866,8.1504493 C 3.0627532,8.3972503 2.9678314,8.6756875 2.9678210,8.9857618 L 2.9678210,9.0332227"
+ style="font-size:12.000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000;font-family:Verdana;text-anchor:start;writing-mode:lr-tb" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="pan triangle"
+ id="layer3"
+ inkscape:groupmode="layer">
+ <path
+ transform="matrix(0.000000,-1.331000,1.331000,0.000000,2.038879,16.92102)"
+ d="M 9.3471551,9.5937489 L 5.5311469,7.3905753 L 1.7151384,5.1874015 L 5.5311469,2.9842283 L 9.3471555,0.78105489 L 9.3471553,5.1874018 L 9.3471551,9.5937489 z "
+ inkscape:randomized="0.0000000"
+ inkscape:rounded="0.0000000"
+ inkscape:flatsided="false"
+ sodipodi:arg2="2.0943951"
+ sodipodi:arg1="1.0471976"
+ sodipodi:r2="2.5440056"
+ sodipodi:r1="5.0880113"
+ sodipodi:cy="5.1874018"
+ sodipodi:cx="6.8031497"
+ sodipodi:sides="3"
+ id="path2107"
+ style="fill:#ffffff;fill-opacity:1.0000000;stroke:none;stroke-width:39.984146;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-opacity:1.0000000"
+ sodipodi:type="star" />
+ </g>
+</svg>
diff --git a/misc/openlayers/art/zoombar.svg b/misc/openlayers/art/zoombar.svg
new file mode 100644
index 0000000..a88ff36
--- /dev/null
+++ b/misc/openlayers/art/zoombar.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://web.resource.org/cc/"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="18"
+ height="18"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.43"
+ version="1.0"
+ inkscape:export-filename="C:\Documents and Settings\crschmidt\Desktop\zoombar.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ sodipodi:docbase="C:\Documents and Settings\crschmidt\Desktop"
+ sodipodi:docname="zoombar.svg">
+ <defs
+ id="defs4" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="1"
+ inkscape:cx="-117.50597"
+ inkscape:cy="65.94562"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showguides="false"
+ showgrid="false"
+ inkscape:window-width="1024"
+ inkscape:window-height="721"
+ inkscape:window-x="-4"
+ inkscape:window-y="-4" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <rect
+ style="opacity:0.5;fill:#808080;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.00000012;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="rect1307"
+ width="16.999998"
+ height="10.000004"
+ x="-125.51302"
+ y="-52.420002" />
+ <path
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999958;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196078"
+ d="M -125.54176,-52.415494 L -125.54176,-42.415495"
+ id="path8297" />
+ <path
+ style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.99999982;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.50196078"
+ d="M -108.48157,-52.418044 L -108.48157,-42.418046"
+ id="path12667" />
+ </g>
+</svg>
diff --git a/misc/openlayers/authors.txt b/misc/openlayers/authors.txt
new file mode 100644
index 0000000..dfcd9ff
--- /dev/null
+++ b/misc/openlayers/authors.txt
@@ -0,0 +1,56 @@
+OpenLayers contributors:
+
+Antoine Abt
+Mike Adair
+Jeff Adams
+Seb Benthall
+Bruno Binet
+Stéphane Brunner
+Howard Butler
+Bertil Chaupis
+John Cole
+Tim Coulter
+Robert Coup
+Jeff Dege
+Roald de Wit
+Schuyler Erle
+Christian López Espínola
+John Frank
+Sean Gilles
+Pierre Giraud
+Ivan Grcic
+Andreas Hocevar
+Marc Jansen
+Ian Johnson
+Frédéric Junod
+Eric Lemoine
+Philip Lindsay
+Martijn van Oosterhout
+David Overstrom
+Corey Puffault
+Peter William Robins
+Gregers Rygg
+Tim Schaub
+Christopher Schmidt
+Cameron Shorter
+Pedro Simonetti
+Paul Spencer
+Paul Smith
+Glen Stampoultzis
+James Stembridge
+Erik Uzureau
+Bart van den Eijnden
+Ivan Willig
+Thomas Wood
+Bill Woodall
+Steve Woodbridge
+David Zwarg
+
+Some portions of OpenLayers are used under the Apache 2.0 license, available
+in doc/licenses/APACHE-2.0.txt.
+
+Some portions of OpenLayers are used under the MIT license, availabie in
+doc/licenses/MIT-LICENSE.txt.
+
+Some portions of OpenLayers are Copyright 2001 Robert Penner, and are used
+under the BSD license, available in doc/licenses/BSD-LICENSE.txt
diff --git a/misc/openlayers/build/README.txt b/misc/openlayers/build/README.txt
new file mode 100644
index 0000000..50798db
--- /dev/null
+++ b/misc/openlayers/build/README.txt
@@ -0,0 +1,46 @@
+The OpenLayers build tool supports several different
+forms of compressing your javascript code, and a method
+of describing build profiles to create customized
+OpenLayers builds with only the components you need.
+
+When building a file, you can choose to build with several
+different compression options to the Python-based build.py
+script. The following is an example script:
+
+python build.py -c closure full OpenLayers-closure.js
+
+This script selects the 'closure' compression mechanism,
+uses a config file called 'full.cfg', and writes the output
+to OpenLayers-closure.js.
+
+The options available for compression are:
+
+ * closure
+ This requires you to have a closure-compiler.jar in your
+ tools directory. You can do this by fetching the compiler
+ from:
+
+ http://closure-compiler.googlecode.com/files/compiler-latest.zip
+
+ Then unzipping that file, and placing compiler.jar into tools
+ and renaming it closure-compiler.jar.
+
+ * closure_ws
+ This uses the closure compiler webservice. This will only work
+ for files source Javascript files which are under 1MB. (Note that
+ the default OpenLayers full build is not under 1MB.)
+
+ * jsmin
+ jsmin is the default compiler, and uses the Python-based
+ jsmin script to compress the Javascript.
+
+ * minimize
+ This is a simple whitespace removing Python script, designed
+ to fill in when other tools are unavailable.
+
+ * none
+ None will leave the Javascript uncompressed.
+
+
+For more information on the build script and custom build profiles,
+see http://docs.openlayers.org/library/deploying.html
diff --git a/misc/openlayers/build/build.py b/misc/openlayers/build/build.py
new file mode 100755
index 0000000..fd0f6e9
--- /dev/null
+++ b/misc/openlayers/build/build.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+
+import sys
+import os
+sys.path.append("../tools")
+import mergejs
+import optparse
+
+def build(config_file = None, output_file = None, options = None):
+ have_compressor = []
+ try:
+ import jsmin
+ have_compressor.append("jsmin")
+ except ImportError:
+ print "No jsmin"
+ try:
+ # tools/closure_library_jscompiler.py from:
+ # http://code.google.com/p/closure-library/source/browse/trunk/closure/bin/build/jscompiler.py
+ import closure_library_jscompiler as closureCompiler
+ have_compressor.append("closure")
+ except Exception, E:
+ print "No closure (%s)" % E
+ try:
+ import closure_ws
+ have_compressor.append("closure_ws")
+ except ImportError:
+ print "No closure_ws"
+
+ try:
+ import minimize
+ have_compressor.append("minimize")
+ except ImportError:
+ print "No minimize"
+
+ try:
+ import uglify_js
+ uglify_js.check_available()
+ have_compressor.append("uglify-js")
+ except Exception, E:
+ print "No uglify-js (%s)" % E
+
+ use_compressor = None
+ if options.compressor and options.compressor in have_compressor:
+ use_compressor = options.compressor
+
+ sourceDirectory = "../lib"
+ configFilename = "full.cfg"
+ outputFilename = "OpenLayers.js"
+
+ if config_file:
+ configFilename = config_file
+ extension = configFilename[-4:]
+
+ if extension != ".cfg":
+ configFilename = config_file + ".cfg"
+
+ if output_file:
+ outputFilename = output_file
+
+ print "Merging libraries."
+ try:
+ if use_compressor == "closure" or use_compressor == 'uglify-js':
+ sourceFiles = mergejs.getNames(sourceDirectory, configFilename)
+ else:
+ merged = mergejs.run(sourceDirectory, None, configFilename)
+ except mergejs.MissingImport, E:
+ print "\nAbnormal termination."
+ sys.exit("ERROR: %s" % E)
+
+ if options.amdname:
+ options.amdname = "'" + options.amdname + "',"
+ else:
+ options.amdname = ""
+
+ if options.amd == 'pre':
+ print "\nAdding AMD function."
+ merged = "define(%sfunction(){%sreturn OpenLayers;});" % (options.amdname, merged)
+
+ print "Compressing using %s" % use_compressor
+ if use_compressor == "jsmin":
+ minimized = jsmin.jsmin(merged)
+ elif use_compressor == "minimize":
+ minimized = minimize.minimize(merged)
+ elif use_compressor == "closure_ws":
+ if len(merged) > 1000000: # The maximum file size for this web service is 1000 KB.
+ print "\nPre-compressing using jsmin"
+ merged = jsmin.jsmin(merged)
+ print "\nIs being compressed using Closure Compiler Service."
+ try:
+ minimized = closure_ws.minimize(merged)
+ except Exception, E:
+ print "\nAbnormal termination."
+ sys.exit("ERROR: Closure Compilation using Web service failed!\n%s" % E)
+ if len(minimized) <= 2:
+ print "\nAbnormal termination due to compilation errors."
+ sys.exit("ERROR: Closure Compilation using Web service failed!")
+ else:
+ print "Closure Compilation using Web service has completed successfully."
+ elif use_compressor == "closure":
+ jscompilerJar = "../tools/closure-compiler.jar"
+ if not os.path.isfile(jscompilerJar):
+ print "\nNo closure-compiler.jar; read README.txt!"
+ sys.exit("ERROR: Closure Compiler \"%s\" does not exist! Read README.txt" % jscompilerJar)
+ minimized = closureCompiler.Compile(
+ jscompilerJar,
+ sourceFiles, [
+ "--externs", "closure-compiler/Externs.js",
+ "--jscomp_warning", "checkVars", # To enable "undefinedVars"
+ "--jscomp_error", "checkRegExp", # Also necessary to enable "undefinedVars"
+ "--jscomp_error", "undefinedVars"
+ ]
+ )
+ if minimized is None:
+ print "\nAbnormal termination due to compilation errors."
+ sys.exit("ERROR: Closure Compilation failed! See compilation errors.")
+ print "Closure Compilation has completed successfully."
+ elif use_compressor == "uglify-js":
+ minimized = uglify_js.compile(sourceFiles)
+ if minimized is None:
+ print "\nAbnormal termination due to compilation errors."
+ sys.exit("ERROR: Uglify JS compilation failed! See compilation errors.")
+
+ print "Uglify JS compilation has completed successfully."
+
+ else: # fallback
+ minimized = merged
+
+ if options.amd == 'post':
+ print "\nAdding AMD function."
+ minimized = "define(%sfunction(){%sreturn OpenLayers;});" % (options.amdname, minimized)
+
+ if options.status:
+ print "\nAdding status file."
+ minimized = "// status: " + file(options.status).read() + minimized
+
+ print "\nAdding license file."
+ minimized = file("license.txt").read() + minimized
+
+ print "Writing to %s." % outputFilename
+ file(outputFilename, "w").write(minimized)
+
+ print "Done."
+
+if __name__ == '__main__':
+ opt = optparse.OptionParser(usage="%s [options] [config_file] [output_file]\n Default config_file is 'full.cfg', Default output_file is 'OpenLayers.js'")
+ opt.add_option("-c", "--compressor", dest="compressor", help="compression method: one of 'jsmin' (default), 'minimize', 'closure_ws', 'closure', or 'none'", default="jsmin")
+ opt.add_option("-s", "--status", dest="status", help="name of a file whose contents will be added as a comment at the front of the output file. For example, when building from a git repo, you can save the output of 'git describe --tags' in this file. Default is no file.", default=False)
+ opt.add_option("--amd", dest="amd", help="output should be AMD module; wrap merged files in define function; can be either 'pre' (before compilation) or 'post' (after compilation). Wrapping the OpenLayers var in a function means the filesize can be reduced by the closure compiler using 'pre', but be aware that a few functions depend on the OpenLayers variable being present. Either option can be used with jsmin or minimize compression. Default false, not AMD.", default=False)
+ opt.add_option("--amdname", dest="amdname", help="only useful with amd option. Name of AMD module. Default no name, anonymous module.", default=False)
+ (options, args) = opt.parse_args()
+ if not len(args):
+ build(options=options)
+ elif len(args) == 1:
+ build(args[0], options=options)
+ elif len(args) == 2:
+ build(args[0], args[1], options=options)
+ else:
+ print "Wrong number of arguments" \ No newline at end of file
diff --git a/misc/openlayers/build/buildUncompressed.py b/misc/openlayers/build/buildUncompressed.py
new file mode 100755
index 0000000..fd38aa7
--- /dev/null
+++ b/misc/openlayers/build/buildUncompressed.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import sys
+sys.path.append("../tools")
+
+import jsmin, mergejs
+
+sourceDirectory = "../lib"
+configFilename = "full.cfg"
+outputFilename = "OpenLayers.js"
+
+if len(sys.argv) > 1:
+ configFilename = sys.argv[1] + ".cfg"
+if len(sys.argv) > 2:
+ outputFilename = sys.argv[2]
+
+print "Merging libraries."
+merged = mergejs.run(sourceDirectory, None, configFilename)
+print "Adding license file."
+merged = file("license.txt").read() + merged
+
+print "Writing to %s." % outputFilename
+file(outputFilename, "w").write(merged)
+
+print "Done."
diff --git a/misc/openlayers/build/closure-compiler/Externs.js b/misc/openlayers/build/closure-compiler/Externs.js
new file mode 100644
index 0000000..3bb9464
--- /dev/null
+++ b/misc/openlayers/build/closure-compiler/Externs.js
@@ -0,0 +1,50 @@
+// ********************************************
+// This source file serves *ONLY* to avoid some compilation errors when the
+// compiler uses the flag:
+// --jscomp_error undefinedVars
+//
+// In this source are declared all variables from other programs that use
+// OpenLayers. This avoids the error of undefined variable for these names.
+//
+// NOTE: The compiler does not include externs files like this in the
+// compilation result.
+// ********************************************
+
+// Used in lib/Firebug/firebug.js when gecko_dom
+ var frames;
+
+// Check the console when using Firebug Lite
+ var console;
+
+// Proj4js
+ var Proj4js = {Proj: function(){}};
+
+// Check JSON in lib/OpenLayers/Format/JSON.js
+ var JSON = {};
+
+// Google Maps
+ var GMap2;
+ var G_NORMAL_MAP;
+ var GEvent;
+ var GLatLngBounds = function(){};
+ var GSize = function(x, y){};
+ var GPoint = function(x, y){};
+ var GLatLng = function(lat, lon){};
+
+// Multimap
+ var MultimapViewer = function(div){};
+ var MMLatLon = function(lat, lon){};
+ var MMPoint = function(x, y){};
+
+//VirtualEarth
+ var VEMap = function(name){};
+ var VEPixel = function(x, y){};
+ var VELatLong = function(lat, lon){};
+ var Msn = {VE:{}};
+
+// Yahoo
+ var YMap = function(div, type, size){};
+ var YGeoPoint = function(lat, lon){};
+ var YCoordPoint = function(x, y){};
+ var YSize = function(w, h){};
+
diff --git a/misc/openlayers/build/full.cfg b/misc/openlayers/build/full.cfg
new file mode 100644
index 0000000..91c817a
--- /dev/null
+++ b/misc/openlayers/build/full.cfg
@@ -0,0 +1,14 @@
+# This is the full build with all files: this includes the vector-related files
+# like Renderers and Formats.
+
+[first]
+
+[last]
+
+[include]
+
+[exclude]
+Firebug
+OpenLayers.js
+OpenLayers/Lang
+deprecated.js
diff --git a/misc/openlayers/build/license.txt b/misc/openlayers/build/license.txt
new file mode 100644
index 0000000..7ea36ac
--- /dev/null
+++ b/misc/openlayers/build/license.txt
@@ -0,0 +1,57 @@
+/*
+
+ OpenLayers.js -- OpenLayers Map Viewer Library
+
+ Copyright (c) 2006-2013 by OpenLayers Contributors
+ Published under the 2-clause BSD license.
+ See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+ Includes compressed code under the following licenses:
+
+ (For uncompressed versions of the code used, please see the
+ OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
diff --git a/misc/openlayers/build/light.cfg b/misc/openlayers/build/light.cfg
new file mode 100644
index 0000000..b5d7392
--- /dev/null
+++ b/misc/openlayers/build/light.cfg
@@ -0,0 +1,32 @@
+[first]
+
+[last]
+
+[include]
+OpenLayers/Map.js
+OpenLayers/Kinetic.js
+OpenLayers/Projection.js
+OpenLayers/Layer/Vector.js
+OpenLayers/Layer/OSM.js
+OpenLayers/Layer/Bing.js
+OpenLayers/Layer/WMS.js
+OpenLayers/Layer/Google/v3.js
+OpenLayers/Popup/FramedCloud.js
+OpenLayers/Control/Navigation.js
+OpenLayers/Control/Zoom.js
+OpenLayers/Control/Attribution.js
+OpenLayers/Control/SelectFeature.js
+OpenLayers/Control/Panel.js
+OpenLayers/Control/LayerSwitcher.js
+OpenLayers/Renderer/SVG.js
+OpenLayers/Renderer/VML.js
+OpenLayers/Format/GeoJSON.js
+OpenLayers/Protocol/HTTP.js
+OpenLayers/Strategy/Fixed.js
+OpenLayers/Strategy/BBOX.js
+OpenLayers/StyleMap.js
+OpenLayers/Rule.js
+OpenLayers/Filter/Comparison.js
+OpenLayers/Filter/Logical.js
+
+[exclude]
diff --git a/misc/openlayers/build/lite.cfg b/misc/openlayers/build/lite.cfg
new file mode 100644
index 0000000..d274e27
--- /dev/null
+++ b/misc/openlayers/build/lite.cfg
@@ -0,0 +1,17 @@
+# This file includes a small subset of OpenLayers code, designed to be
+# integrated into another application. It includes only the Layer types
+# neccesary to create tiled or untiled WMS, and does not include any Controls.
+# This is the result of what was at the time called "Webmap.js" at the FOSS4G
+# Web Mapping BOF.
+
+[first]
+
+[last]
+
+[include]
+OpenLayers/Map.js
+OpenLayers/Layer/WMS.js
+
+[exclude]
+
+
diff --git a/misc/openlayers/build/mobile.cfg b/misc/openlayers/build/mobile.cfg
new file mode 100644
index 0000000..b41f0bd
--- /dev/null
+++ b/misc/openlayers/build/mobile.cfg
@@ -0,0 +1,36 @@
+[first]
+
+[last]
+
+[include]
+OpenLayers/Map.js
+OpenLayers/Kinetic.js
+OpenLayers/Projection.js
+OpenLayers/Layer/OSM.js
+OpenLayers/Layer/Bing.js
+OpenLayers/Layer/WMS.js
+OpenLayers/Control/TouchNavigation.js
+OpenLayers/Control/Geolocate.js
+OpenLayers/Control/Zoom.js
+OpenLayers/Control/Attribution.js
+OpenLayers/Control/SelectFeature.js
+OpenLayers/Control/DrawFeature.js
+OpenLayers/Control/ModifyFeature.js
+OpenLayers/Control/Panel.js
+OpenLayers/Handler/Point.js
+OpenLayers/Handler/Path.js
+OpenLayers/Handler/Polygon.js
+OpenLayers/Layer/Vector.js
+OpenLayers/Renderer/SVG.js
+OpenLayers/Renderer/Canvas.js
+OpenLayers/Format/GeoJSON.js
+OpenLayers/Format/KML.js
+OpenLayers/Protocol/HTTP.js
+OpenLayers/Protocol/WFS.js
+OpenLayers/Protocol/WFS/v1_0_0.js
+OpenLayers/Strategy/Fixed.js
+OpenLayers/TileManager.js
+
+[exclude]
+
+
diff --git a/misc/openlayers/build/tests.cfg b/misc/openlayers/build/tests.cfg
new file mode 100644
index 0000000..557b16b
--- /dev/null
+++ b/misc/openlayers/build/tests.cfg
@@ -0,0 +1,11 @@
+# This build config is supposed to be used for the units tests with "mode=build"
+
+[first]
+
+[last]
+
+[include]
+
+[exclude]
+Firebug
+OpenLayers.js
diff --git a/misc/openlayers/doc_config/Data/ClassHierarchy.nd b/misc/openlayers/doc_config/Data/ClassHierarchy.nd
new file mode 100644
index 0000000..f0ab8e6
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/ClassHierarchy.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/ConfigFileInfo.nd b/misc/openlayers/doc_config/Data/ConfigFileInfo.nd
new file mode 100644
index 0000000..abb83a9
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/ConfigFileInfo.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/FileInfo.nd b/misc/openlayers/doc_config/Data/FileInfo.nd
new file mode 100644
index 0000000..85b49bd
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/FileInfo.nd
@@ -0,0 +1,323 @@
+1.51
+JavaScript
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OWSContext/v0_3_1.js 1373361972 1 OpenLayers.Format.OWSContext.v0_3_1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFSCapabilities/v1.js 1373361972 1 OpenLayers.Format.WFSCapabilities.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SOSGetObservation.js 1373361972 1 OpenLayers.Format.SOSGetObservation
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/zh-TW.js 1373361972 1 OpenLayers.Lang["zh-TW"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/JSON.js 1373361972 1 OpenLayers.Format.JSON
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/PanPanel.js 1373361972 1 OpenLayers.Control.PanPanel
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Symbolizer.js 1373361972 1 OpenLayers.Symbolizer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Symbolizer/Point.js 1373361972 1 OpenLayers.Symbolizer.Point
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Style.js 1373361972 1 OpenLayers.Style
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMC.js 1373361972 1 OpenLayers.Format.WMC
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/WFS.js 1373361972 1 OpenLayers.Protocol.WFS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Animation.js 1373361972 1 OpenLayers.Animation
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WCSGetCoverage.js 1373361972 1 OpenLayers.Format.WCSGetCoverage version 1.1.0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/vi.js 1373361972 1 OpenLayers.Lang["vi"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Rico/license.js 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Rico/license.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Navigation.js 1373361972 1 OpenLayers.Control.Navigation
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ia.js 1373361972 1 OpenLayers.Lang["ia"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Split.js 1373361972 1 OpenLayers.Control.Split
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/gsw.js 1373361972 1 OpenLayers.Lang["gsw"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js 1373361972 1 OpenLayers.Format.WMSCapabilities/v1_1_1_WMSC
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/EventPane.js 1373361972 1 OpenLayers.Layer.EventPane
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/pl.js 1373361972 1 OpenLayers.Lang["pl"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Filter.js 1373361972 1 OpenLayers.Format.Filter
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GML/v3.js 1373361972 1 OpenLayers.Format.GML.v3
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OWSCommon/v1.js 1373361972 1 OpenLayers.Format.OWSCommon.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/TileCache.js 1373361972 1 OpenLayers.Layer.TileCache
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/CSWGetDomain.js 1373361972 1 OpenLayers.Format.CSWGetDomain
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/PointTrack.js 1373361972 1 OpenLayers.Layer.PointTrack
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/CacheRead.js 1373361972 1 OpenLayers.Control.CacheRead
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OWSCommon.js 1373361972 1 OpenLayers.Format.OWSCommon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities.js 1373361972 1 OpenLayers.Format.WMSCapabilities
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/XML.js 1373361972 1 OpenLayers.Format.XML
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/SingleFile.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/SingleFile.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/firebug.js 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/firebug.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/WMTSGetFeatureInfo.js 1373361972 1 OpenLayers.Control.WMTSGetFeatureInfo
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/CSW.js 1373361972 1 OpenLayers.Protocol.CSW
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/CSWGetDomain/v2_0_2.js 1373361972 1 OpenLayers.Format.CSWGetDomain.v2_0_2
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Popup/Anchored.js 1373361972 1 OpenLayers.Popup.Anchored
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GML/Base.js 1373361972 1 OpenLayers.Format.GML.Base
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/TMS.js 1373361972 1 OpenLayers.Layer.TMS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/WMTS.js 1373361972 1 OpenLayers.Layer.WMTS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFST.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFST.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/sk.js 1373361972 1 OpenLayers.Lang["sk"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WPSExecute.js 1373361972 1 OpenLayers.Format.WPSExecute version 1.0.0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/ArcXML.js 1373361972 1 OpenLayers.Format.ArcXML
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/WMSGetFeatureInfo.js 1373361972 1 OpenLayers.Control.WMSGetFeatureInfo
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WPSCapabilities.js 1373361972 1 OpenLayers.Format.WPSCapabilities
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Hover.js 1373361972 1 OpenLayers.Handler.Hover
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/GetFeature.js 1373361972 1 OpenLayers.Control.GetFeature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/HTTP.js 1373361972 1 OpenLayers.Protocol.HTTP
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/MouseWheel.js 1373361972 1 OpenLayers.Handler.MouseWheel
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Tile/Image/IFrame.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Tile/Image/IFrame.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Google.js 1373361972 1 OpenLayers.Layer.Google
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/WFS/v1_0_0.js 1373361972 1 OpenLayers.Protocol.WFS.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Google/v3.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Google/v3.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/DragPan.js 1373361972 1 OpenLayers.Control.DragPan
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ModifyFeature.js 1373361972 1 OpenLayers.Control.ModifyFeature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Projection.js 1373361972 1 OpenLayers.Projection
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/Point.js 1373361972 1 OpenLayers.Geometry.Point
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Size.js 1373361972 1 OpenLayers.Size
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Snapping.js 1373361972 1 OpenLayers.Control.Snapping
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SOSCapabilities.js 1373361972 1 OpenLayers.Format.SOSCapabilities
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/nb.js 1373361972 1 OpenLayers.Lang["nb"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/firebugx.js 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/firebugx.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSGetFeatureInfo.js 1373361972 1 OpenLayers.Format.WMSGetFeatureInfo
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/te.js 1373361972 1 OpenLayers.Lang["te"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SLD/v1_0_0.js 1373361972 1 OpenLayers.Format.SLD.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SLD.js 1373361972 1 OpenLayers.Format.SLD
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Symbolizer/Raster.js 1373361972 1 OpenLayers.Symbolizer.Raster
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Rule.js 1373361972 1 OpenLayers.Rule
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/TileManager.js 1373361972 1 OpenLayers.TileManager
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/cs-CZ.js 1373361972 1 OpenLayers.Lang["cs-CZ"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OSM.js 1373361972 1 OpenLayers.Format.OSM
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Tween.js 1373361972 1 OpenLayers.Tween
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ZoomPanel.js 1373361972 1 OpenLayers.Control.ZoomPanel
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFSCapabilities/v1_1_0.js 1373361972 1 OpenLayers.Format.WFSCapabilities/v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Pinch.js 1373361972 1 OpenLayers.Handler.Pinch
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFST/v1.js 1373361972 1 OpenLayers.Format.WFST.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/MapServer.js 1373361972 1 OpenLayers.Layer.MapServer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/Refresh.js 1373361972 1 OpenLayers.Strategy.Refresh
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Button.js 1373361972 1 OpenLayers.Control.Button
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/CSWGetRecords.js 1373361972 1 OpenLayers.Format.CSWGetRecords
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Filter/Spatial.js 1373361972 1 OpenLayers.Filter.Spatial
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Graticule.js 1373361972 1 OpenLayers.Control.Graticule
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/OSM.js 1373361972 1 OpenLayers.Layer.OSM
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WPSCapabilities/v1_0_0.js 1373361972 1 OpenLayers.Format.WPSCapabilities.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1_3_0.js 1373361972 1 OpenLayers.Format.WMSCapabilities/v1_3_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/TransformFeature.js 1373361972 1 OpenLayers.Control.TransformFeature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Geolocate.js 1373361972 1 OpenLayers.Control.Geolocate
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Text.js 1373361972 1 OpenLayers.Layer.Text
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/QueryStringFilter.js 1373361972 1 OpenLayers.Format.QueryStringFilter
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/zh-CN.js 1373361972 1 OpenLayers.Lang["zh-CN"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/bg.js 1373361972 1 OpenLayers.Lang["bg"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/SelectFeature.js 1373361972 1 OpenLayers.Control.SelectFeature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/sv-SE.js 1373361972 1 OpenLayers.Lang["sv"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Filter/Comparison.js 1373361972 1 OpenLayers.Filter.Comparison
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/pt.js 1373361972 1 OpenLayers.Lang["pt"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Popup/FramedCloud.js 1373361972 1 OpenLayers.Popup.FramedCloud
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/readme.txt 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/readme.txt
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/CQL.js 1373361972 1 OpenLayers.Format.CQL
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OWSCommon/v1_0_0.js 1373361972 1 OpenLayers.Format.OWSCommon.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Zoom.js 1373361972 1 OpenLayers.Control.Zoom
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GML.js 1373361972 1 OpenLayers.Format.GML
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Tile/UTFGrid.js 1373361972 1 OpenLayers.Tile.UTFGrid
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes.js 1373361972 1 OpenLayers Base Types
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFS.js 1373361972 1 OpenLayers.Format.WFS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Zoomify.js 1373361972 1 OpenLayers.Layer.Zoomify
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/SOS/v1_0_0.js 1373361972 1 OpenLayers.Protocol.SOS.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Pan.js 1373361972 1 OpenLayers.Control.Pan
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Vector/RootContainer.js 1373361972 1 OpenLayers.Layer.Vector.RootContainer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Request.js 1373361972 1 OpenLayers.Request
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/PanZoomBar.js 1373361972 1 OpenLayers.Control.PanZoomBar
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/XLS/v1_1_0.js 1373361972 1 OpenLayers.Format.XLS.v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/Curve.js 1373361972 1 OpenLayers.Geometry.Curve
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/deprecated.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/deprecated.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Filter/Logical.js 1373361972 1 OpenLayers.Filter.Logical
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/hu.js 1373361972 1 OpenLayers.Lang["hu"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Symbolizer/Polygon.js 1373361972 1 OpenLayers.Symbolizer.Polygon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/io.js 1373361972 1 OpenLayers.Lang["io"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/Script.js 1373361972 1 OpenLayers.Protocol.Script
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/Paging.js 1373361972 1 OpenLayers.Strategy.Paging
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Renderer/SVG.js 1373361972 1 OpenLayers.Renderer.SVG
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Console.js 1373361972 1 OpenLayers.Console
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GML/v2.js 1373361972 1 OpenLayers.Format.GML.v2
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers.js 1373361972 1 OpenLayers
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/es.js 1373361972 1 OpenLayers.Lang["es"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/WorldWind.js 1373361972 1 OpenLayers.Layer.WorldWind
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy.js 1373361972 1 OpenLayers.Strategy
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js 1373361972 1 OpenLayers.Format.WMTSCapabilities.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Panel.js 1373361972 1 OpenLayers.Control.Panel
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Polygon.js 1373361972 1 OpenLayers.Handler.Polygon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Kinetic.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Kinetic.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1.js 1373361972 1 OpenLayers.Format.WMSCapabilities.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Markers.js 1373361972 1 OpenLayers.Layer.Markers
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Filter/Function.js 1373361972 1 OpenLayers.Filter.Function
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Box.js 1373361972 1 OpenLayers.Handler.Box
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol.js 1373361972 1 OpenLayers.Protocol
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Marker/Box.js 1373361972 1 OpenLayers.Marker.Box
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Rico/Color.js 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Rico/Color.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer.js 1373361972 1 OpenLayers.Layer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Feature/Vector.js 1373361972 1 OpenLayers.Feature.Vector
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry.js 1373361972 1 OpenLayers.Geometry
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/hsb.js 1373361972 1 OpenLayers.Lang["hsb"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WCSCapabilities/v1.js 1373361972 1 OpenLayers.Format.WCSCapabilities.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/SLDSelect.js 1373361972 1 OpenLayers.Control.SLDSelect
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Filter/v1_0_0.js 1373361972 1 OpenLayers.Format.Filter.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/CSW/v2_0_2.js 1373361972 1 OpenLayers.Protocol.CSW.v2_0_2
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Keyboard.js 1373361972 1 OpenLayers.handler.Keyboard
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/KeyboardDefaults.js 1373361972 1 OpenLayers.Control.KeyboardDefaults
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Renderer.js 1373361972 1 OpenLayers.Renderer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Vector.js 1373361972 1 OpenLayers.Layer.Vector
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/gl.js 1373361972 1 OpenLayers.Lang["gl"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Attribution.js 1373361972 1 OpenLayers.Control.Attribution
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFSCapabilities.js 1373361972 1 OpenLayers.Format.WFSCapabilities
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js 1373361972 1 OpenLayers.Format.WFSCapabilities/v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WCSCapabilities/v1_0_0.js 1373361972 1 OpenLayers.Format.WCSCapabilities/v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Renderer/VML.js 1373361972 1 OpenLayers.Renderer.VML
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Filter.js 1373361972 1 OpenLayers.Filter
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/Filter.js 1373361972 1 OpenLayers.Strategy.Filter
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Context.js 1373361972 1 OpenLayers.Format.Context
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/LinearRing.js 1373361972 1 OpenLayers.Geometry.LinearRing
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/it.js 1373361972 1 OpenLayers.Lang["it"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/KaMapCache.js 1373361972 1 OpenLayers.Layer.KaMapCache
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Pixel.js 1373361972 1 OpenLayers.Pixel
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ScaleLine.js 1373361972 1 OpenLayers.Control.ScaleLine
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFSDescribeFeatureType.js 1373361972 1 OpenLayers.Format.WFSDescribeFeatureType
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/LineString.js 1373361972 1 OpenLayers.Geometry.LineString
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/SOS.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/SOS.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/MultiPoint.js 1373361972 1 OpenLayers.Geometry.MultiPoint
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/Fixed.js 1373361972 1 OpenLayers.Strategy.Fixed
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/da-DK.js 1373361972 1 OpenLayers.Lang["da-DK"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SOSGetFeatureOfInterest.js 1373361972 1 OpenLayers.Format.SOSGetFeatureOfInterest
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1_1.js 1373361972 1 OpenLayers.Format.WMSCapabilities.v1_1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/WFS/v1.js 1373361972 1 OpenLayers.Protocol.WFS.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/oc.js 1373361972 1 OpenLayers.Lang["oc"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/XYZ.js 1373361972 1 OpenLayers.Layer.XYZ
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMC/v1.js 1373361972 1 OpenLayers.Format.WMC.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ZoomToMaxExtent.js 1373361972 1 OpenLayers.Control.ZoomToMaxExtent
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/KaMap.js 1373361972 1 OpenLayers.Layer.KaMap
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/en.js 1373361972 1 OpenLayers.Lang["en"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Tile/Image.js 1373361972 1 OpenLayers.Tile.Image
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WKT.js 1373361972 1 OpenLayers.Format.WKT
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GeoJSON.js 1373361972 1 OpenLayers.Format.GeoJSON
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/BBOX.js 1373361972 1 OpenLayers.Strategy.BBOX
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFST/v1_0_0.js 1373361972 1 OpenLayers.Format.WFST.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/XLS/v1.js 1373361972 1 OpenLayers.Format.XLS.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/ArcIMS.js 1373361972 1 OpenLayers.Layer.ArcIMS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Symbolizer/Text.js 1373361972 1 OpenLayers.Symbolizer.Text
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler.js 1373361972 1 OpenLayers.Handler
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Symbolizer/Line.js 1373361972 1 OpenLayers.Symbolizer.Line
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/StyleMap.js 1373361972 1 OpenLayers.StyleMap
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Date.js 1373361972 1 OpenLayers.Date
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1_1_0.js 1373361972 1 OpenLayers.Format.WMSCapabilities/v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Protocol/WFS/v1_1_0.js 1373361972 1 OpenLayers.Protocol.WFS.v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Filter/v1_1_0.js 1373361972 1 OpenLayers.Format.Filter.v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Util.js 1373361972 1 Util
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/hr.js 1373361972 1 OpenLayers.Lang["hr"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format.js 1373361972 1 OpenLayers.Format
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Marker.js 1373361972 1 OpenLayers.Marker
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSDescribeLayer/v1_1.js 1373361972 1 OpenLayers.Format.WMSDescribeLayer.v1_1_1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/EditingToolbar.js 1373361972 1 OpenLayers.Control.EditingToolbar
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/Save.js 1373361972 1 OpenLayers.Strategy.Save
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/MultiLineString.js 1373361972 1 OpenLayers.Geometry.MultiLineString
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ja.js 1373361972 1 OpenLayers.Lang["ja"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/GeoRSS.js 1373361972 1 OpenLayers.Layer.GeoRSS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/nn.js 1373361972 1 OpenLayers.Lang["nn"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js 1373361972 1 OpenLayers.Format.SLD/v1_0_0_GeoServer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Image.js 1373361972 1 OpenLayers.Layer.Image
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/UTFGrid.js 1373361972 1 OpenLayers.Layer.UTFGrid
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Style2.js 1373361972 1 OpenLayers.Style2
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WPSDescribeProcess.js 1373361972 1 OpenLayers.Format.WPSDescribeProcess
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/license.txt 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Firebug/license.txt
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/ArcGIS93Rest.js 1373361972 1 OpenLayers.Layer.ArcGIS93Rest
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GeoRSS.js 1373361972 1 OpenLayers.Format.GeoRSS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Bing.js 1373361972 1 OpenLayers.Layer.Bing
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/nl.js 1373361972 1 OpenLayers.Lang["nl"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ZoomIn.js 1373361972 1 OpenLayers.Control.ZoomIn
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/DragFeature.js 1373361972 1 OpenLayers.Control.DragFeature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/CacheWrite.js 1373361972 1 OpenLayers.Control.CacheWrite
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Atom.js 1373361972 1 OpenLayers.Format.Atom
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Click.js 1373361972 1 OpenLayers.Handler.Click
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/be-tarask.js 1373361972 1 OpenLayers.Lang["be-tarask"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/MultiPolygon.js 1373361972 1 OpenLayers.Geometry.MultiPolygon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ZoomOut.js 1373361972 1 OpenLayers.Control.ZoomOut
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/Polygon.js 1373361972 1 OpenLayers.Geometry.Polygon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/KML.js 1373361972 1 OpenLayers.Format.KML
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Strategy/Cluster.js 1373361972 1 OpenLayers.Strategy.Cluster
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/PanZoom.js 1373361972 1 OpenLayers.Control.PanZoom
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/RegularPolygon.js 1373361972 1 OpenLayers.Handler.RegularPolygon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Util/vendorPrefix.js 1373361972 1 OpenLayers.Util.vendorPrefix
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/PinchZoom.js 1373361972 1 OpenLayers.Control.PinchZoom
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/MousePosition.js 1373361972 1 OpenLayers.Control.MousePosition
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/SphericalMercator.js 1373361972 1 OpenLayers.Layer.SphericalMercator
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Geometry/Collection.js 1373361972 1 OpenLayers.Geometry.Collection
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/TouchNavigation.js 1373361972 1 OpenLayers.Control.TouchNavigation
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OGCExceptionReport.js 1373361972 1 OpenLayers.Format.OGCExceptionReport
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/HTTPRequest.js 1373361972 1 OpenLayers.Layer.HTTPRequest
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Element.js 1373361972 1 OpenLayers.Element
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/GPX.js 1373361972 1 OpenLayers.Format.GPX
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/el.js 1373361972 1 OpenLayers.Lang["el"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SOSCapabilities/v1_0_0.js 1373361972 1 OpenLayers.Format.SOSCapabilities.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Popup.js 1373361972 1 OpenLayers.Popup
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/NavToolbar.js 1373361972 1 OpenLayers.Control.NavToolbar
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ca.js 1373361972 1 OpenLayers.Lang["ca"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Measure.js 1373361972 1 OpenLayers.Control.Measure
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Popup/Framed.js 1373361972 1 OpenLayers.Popup.Framed
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Boxes.js 1373361972 1 OpenLayers.Layer.Boxes
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WCSCapabilities.js 1373361972 1 OpenLayers.Format.WCSCapabilities
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/XML/VersionedOGC.js 1373361972 1 OpenLayers.Format.XML.VersionedOGC
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/WMS.js 1373361972 1 OpenLayers.Layer.WMS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/CSWGetRecords/v2_0_2.js 1373361972 1 OpenLayers.Format.CSWGetRecords.v2_0_2
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/ArcGISCache.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/ArcGISCache.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/km.js 1373361972 1 OpenLayers.Lang["km"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Path.js 1373361972 1 OpenLayers.Handler.Path
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1_1_1.js 1373361972 1 OpenLayers.Format.WMSCapabilities/v1_1_1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/br.js 1373361972 1 OpenLayers.Lang["br"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Map.js 1373361972 1 OpenLayers.Map
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/PointGrid.js 1373361972 1 OpenLayers.Layer.PointGrid
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/SLD/v1.js 1373361972 1 OpenLayers.Format.SLD.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WCSCapabilities/v1_1_0.js 1373361972 1 OpenLayers.Format.WCSCapabilities/v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/de.js 1373361972 1 OpenLayers.Lang["de"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMTSCapabilities.js 1373361972 1 OpenLayers.Format.WMTSCapabilities
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/XLS.js 1373361972 1 OpenLayers.Format.XLS
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ro.js 1373361972 1 OpenLayers.Lang["ro"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/OverviewMap.js 1373361972 1 OpenLayers.Control.OverviewMap
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Icon.js 1373361972 1 OpenLayers.Icon
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/fr.js 1373361972 1 OpenLayers.Lang["fr"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/LonLat.js 1373361972 1 OpenLayers.LonLat
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSDescribeLayer.js 1373361972 1 OpenLayers.Format.WMSDescribeLayer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Renderer/Canvas.js 1373361972 1 OpenLayers.Renderer.Canvas
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/fi.js 1373361972 1 OpenLayers.Lang["fi"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Feature.js 1373361972 1 OpenLayers.Handler.Feature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ZoomBox.js 1373361972 1 OpenLayers.Control.ZoomBox
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang.js 1373361972 1 OpenLayers.Lang
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/pt-BR.js 1373361972 1 OpenLayers.Lang["pt-br"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Class.js 1373361972 1 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Class.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Renderer/Elements.js 1373361972 1 OpenLayers.ElementsIndexer
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Events/featureclick.js 1373361972 1 OpenLayers.Events.featureclick
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/Rico/Corner.js 1373361972 0 /tmp/openlayers/tools/OpenLayers-2.13.1/lib/Rico/Corner.js
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/EncodedPolyline.js 1373361972 1 OpenLayers.Format.EncodedPolyline
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/UTFGrid.js 1373361972 1 OpenLayers.Control.UTFGrid
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/MapGuide.js 1373361972 1 OpenLayers.Layer.MapGuide
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/BaseTypes/Bounds.js 1373361972 1 OpenLayers.Bounds
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ksh.js 1373361972 1 OpenLayers.Lang["ksh"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Scale.js 1373361972 1 OpenLayers.Control.Scale
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMC/v1_0_0.js 1373361972 1 OpenLayers.Format.WMC.v1_0_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/DrawFeature.js 1373361972 1 OpenLayers.Control.DrawFeature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/ArcXML/Features.js 1373361972 1 OpenLayers.Format.ArcXML.Features
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/WPSClient.js 1373361972 1 OpenLayers.WPSClient
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/nds.js 1373361972 1 OpenLayers.Lang["nds"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/WPSProcess.js 1373361972 1 OpenLayers.WPSProcess
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OWSContext.js 1373361972 1 OpenLayers.Format.OWSContext
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Events.js 1373361972 1 OpenLayers.Event
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ar.js 1373361972 1 OpenLayers.Lang["ar"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/OWSCommon/v1_1_0.js 1373361972 1 OpenLayers.Format.OWSCommon.v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/en-CA.js 1373361972 1 OpenLayers.Lang["en-CA"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Text.js 1373361972 1 OpenLayers.Format.Text
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMSCapabilities/v1_3.js 1373361972 1 OpenLayers.Format.WMSCapabilities/v1_3
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/ru.js 1373361972 1 OpenLayers.Lang["ru"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/fur.js 1373361972 1 OpenLayers.Lang["fur"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Tile.js 1373361972 1 OpenLayers.Tile
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/id.js 1373361972 1 OpenLayers.Lang["id"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/is.js 1373361972 1 OpenLayers.Lang["is"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Drag.js 1373361972 1 OpenLayers.Handler.Drag
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/LayerSwitcher.js 1373361972 1 OpenLayers.Control.LayerSwitcher
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/FixedZoomLevels.js 1373361972 1 OpenLayers.Layer.FixedZoomLevels
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/ArgParser.js 1373361972 1 OpenLayers.Control.ArgParser
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Events/buttonclick.js 1373361972 1 OpenLayers.Events.buttonclick
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Handler/Point.js 1373361972 1 OpenLayers.Handler.Point
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/Filter/v1.js 1373361972 1 OpenLayers.Format.Filter.v1
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Filter/FeatureId.js 1373361972 1 OpenLayers.Filter.FeatureId
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Lang/lt.js 1373361972 1 OpenLayers.Lang["lt"]
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WMC/v1_1_0.js 1373361972 1 OpenLayers.Format.WMC.v1_1_0
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/NavigationHistory.js 1373361972 1 OpenLayers.Control.NavigationHistory
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Request/XMLHttpRequest.js 1373361972 1 OpenLayers.Request.XMLHttpRequest
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control/Permalink.js 1373361972 1 OpenLayers.Control.Permalink
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Spherical.js 1373361972 1 Spherical
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Feature.js 1373361972 1 OpenLayers.Feature
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Control.js 1373361972 1 OpenLayers.Control
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Layer/Grid.js 1373361972 1 OpenLayers.Layer.Grid
+/tmp/openlayers/tools/OpenLayers-2.13.1/lib/OpenLayers/Format/WFST/v1_1_0.js 1373361972 1 OpenLayers.Format.WFST.v1_1_0
diff --git a/misc/openlayers/doc_config/Data/ImageFileInfo.nd b/misc/openlayers/doc_config/Data/ImageFileInfo.nd
new file mode 100644
index 0000000..bedfcbe
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/ImageFileInfo.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/ImageReferenceTable.nd b/misc/openlayers/doc_config/Data/ImageReferenceTable.nd
new file mode 100644
index 0000000..3c88131
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/ImageReferenceTable.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/IndexInfo.nd b/misc/openlayers/doc_config/Data/IndexInfo.nd
new file mode 100644
index 0000000..bc6f315
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/IndexInfo.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/PreviousMenuState.nd b/misc/openlayers/doc_config/Data/PreviousMenuState.nd
new file mode 100644
index 0000000..fb0f99e
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/PreviousMenuState.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/PreviousSettings.nd b/misc/openlayers/doc_config/Data/PreviousSettings.nd
new file mode 100644
index 0000000..2ab4ab3
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/PreviousSettings.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Data/SymbolTable.nd b/misc/openlayers/doc_config/Data/SymbolTable.nd
new file mode 100644
index 0000000..4ed1d0a
--- /dev/null
+++ b/misc/openlayers/doc_config/Data/SymbolTable.nd
Binary files differ
diff --git a/misc/openlayers/doc_config/Languages.txt b/misc/openlayers/doc_config/Languages.txt
new file mode 100644
index 0000000..85d5fde
--- /dev/null
+++ b/misc/openlayers/doc_config/Languages.txt
@@ -0,0 +1,113 @@
+Format: 1.51
+
+# This is the Natural Docs languages file for this project. If you change
+# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change
+# something for all your projects, edit the Languages.txt in Natural Docs'
+# Config directory instead.
+
+
+# You can prevent certain file extensions from being scanned like this:
+# Ignore Extensions: [extension] [extension] ...
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Unlike other Natural Docs configuration files, in this file all comments
+# MUST be alone on a line. Some languages deal with the # character, so you
+# cannot put comments on the same line as content.
+#
+# Also, all lists are separated with spaces, not commas, again because some
+# languages may need to use them.
+#
+# Language: [name]
+# Alter Language: [name]
+# Defines a new language or alters an existing one. Its name can use any
+# characters. If any of the properties below have an add/replace form, you
+# must use that when using Alter Language.
+#
+# The language Shebang Script is special. It's entry is only used for
+# extensions, and files with those extensions have their shebang (#!) lines
+# read to determine the real language of the file. Extensionless files are
+# always treated this way.
+#
+# The language Text File is also special. It's treated as one big comment
+# so you can put Natural Docs content in them without special symbols. Also,
+# if you don't specify a package separator, ignored prefixes, or enum value
+# behavior, it will copy those settings from the language that is used most
+# in the source tree.
+#
+# Extensions: [extension] [extension] ...
+# [Add/Replace] Extensions: [extension] [extension] ...
+# Defines the file extensions of the language's source files. You can
+# redefine extensions found in the main languages file. You can use * to
+# mean any undefined extension.
+#
+# Shebang Strings: [string] [string] ...
+# [Add/Replace] Shebang Strings: [string] [string] ...
+# Defines a list of strings that can appear in the shebang (#!) line to
+# designate that it's part of the language. You can redefine strings found
+# in the main languages file.
+#
+# Ignore Prefixes in Index: [prefix] [prefix] ...
+# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ...
+#
+# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ...
+# Specifies prefixes that should be ignored when sorting symbols in an
+# index. Can be specified in general or for a specific topic type.
+#
+#------------------------------------------------------------------------------
+# For basic language support only:
+#
+# Line Comments: [symbol] [symbol] ...
+# Defines a space-separated list of symbols that are used for line comments,
+# if any.
+#
+# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...
+# Defines a space-separated list of symbol pairs that are used for block
+# comments, if any.
+#
+# Package Separator: [symbol]
+# Defines the default package separator symbol. The default is a dot.
+#
+# [Topic Type] Prototype Enders: [symbol] [symbol] ...
+# When defined, Natural Docs will attempt to get a prototype from the code
+# immediately following the topic type. It stops when it reaches one of
+# these symbols. Use \n for line breaks.
+#
+# Line Extender: [symbol]
+# Defines the symbol that allows a prototype to span multiple lines if
+# normally a line break would end it.
+#
+# Enum Values: [global|under type|under parent]
+# Defines how enum values are referenced. The default is global.
+# global - Values are always global, referenced as 'value'.
+# under type - Values are under the enum type, referenced as
+# 'package.enum.value'.
+# under parent - Values are under the enum's parent, referenced as
+# 'package.value'.
+#
+# Perl Package: [perl package]
+# Specifies the Perl package used to fine-tune the language behavior in ways
+# too complex to do in this file.
+#
+#------------------------------------------------------------------------------
+# For full language support only:
+#
+# Full Language Support: [perl package]
+# Specifies the Perl package that has the parsing routines necessary for full
+# language support.
+#
+#-------------------------------------------------------------------------------
+
+# The following languages are defined in the main file, if you'd like to alter
+# them:
+#
+# Text File, Shebang Script, C/C++, C#, Java, JavaScript, Perl, Python,
+# PHP, SQL, Visual Basic, Pascal, Assembly, Ada, Tcl, Ruby, Makefile,
+# ActionScript, ColdFusion, R, Fortran
+
+# If you add a language that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# languages [at] naturaldocs [dot] org.
diff --git a/misc/openlayers/doc_config/Menu.txt b/misc/openlayers/doc_config/Menu.txt
new file mode 100644
index 0000000..6f19572
--- /dev/null
+++ b/misc/openlayers/doc_config/Menu.txt
@@ -0,0 +1,520 @@
+Format: 1.51
+
+
+Title: OpenLayers
+SubTitle: JavaScript Mapping Library
+
+# You can add a footer to your documentation like this:
+# Footer: [text]
+# If you want to add a copyright notice, this would be the place to do it.
+
+# You can add a timestamp to your documentation like one of these:
+# Timestamp: Generated on month day, year
+# Timestamp: Updated mm/dd/yyyy
+# Timestamp: Last updated mon day
+#
+# m - One or two digit month. January is "1"
+# mm - Always two digit month. January is "01"
+# mon - Short month word. January is "Jan"
+# month - Long month word. January is "January"
+# d - One or two digit day. 1 is "1"
+# dd - Always two digit day. 1 is "01"
+# day - Day with letter extension. 1 is "1st"
+# yy - Two digit year. 2006 is "06"
+# yyyy - Four digit year. 2006 is "2006"
+# year - Four digit year. 2006 is "2006"
+
+
+# --------------------------------------------------------------------------
+#
+# Cut and paste the lines below to change the order in which your files
+# appear on the menu. Don't worry about adding or removing files, Natural
+# Docs will take care of that.
+#
+# You can further organize the menu by grouping the entries. Add a
+# "Group: [name] {" line to start a group, and add a "}" to end it.
+#
+# You can add text and web links to the menu by adding "Text: [text]" and
+# "Link: [name] ([URL])" lines, respectively.
+#
+# The formatting and comments are auto-generated, so don't worry about
+# neatness when editing the file. Natural Docs will clean it up the next
+# time it is run. When working with groups, just deal with the braces and
+# forget about the indentation and comments.
+#
+# --------------------------------------------------------------------------
+
+
+Group: OpenLayers {
+
+ File: OpenLayers (no auto-title, OpenLayers.js)
+
+ Group: BaseTypes {
+
+ File: Base Types (no auto-title, OpenLayers/BaseTypes.js)
+ File: Bounds (no auto-title, OpenLayers/BaseTypes/Bounds.js)
+ File: Class (no auto-title, OpenLayers/BaseTypes/Class.js)
+ File: Date (no auto-title, OpenLayers/BaseTypes/Date.js)
+ File: Element (no auto-title, OpenLayers/BaseTypes/Element.js)
+ File: LonLat (no auto-title, OpenLayers/BaseTypes/LonLat.js)
+ File: Pixel (no auto-title, OpenLayers/BaseTypes/Pixel.js)
+ File: Size (no auto-title, OpenLayers/BaseTypes/Size.js)
+ } # Group: BaseTypes
+
+ Group: Control {
+
+ File: Control (no auto-title, OpenLayers/Control.js)
+
+ Group: Control {
+
+ File: ArgParser (no auto-title, OpenLayers/Control/ArgParser.js)
+ File: Attribution (no auto-title, OpenLayers/Control/Attribution.js)
+ File: Button (no auto-title, OpenLayers/Control/Button.js)
+ File: CacheRead (OpenLayers/Control/CacheRead.js)
+ File: CacheWrite (OpenLayers/Control/CacheWrite.js)
+ File: DragFeature (no auto-title, OpenLayers/Control/DragFeature.js)
+ File: DragPan (no auto-title, OpenLayers/Control/DragPan.js)
+ File: DrawFeature (no auto-title, OpenLayers/Control/DrawFeature.js)
+ File: EditingToolbar (no auto-title, OpenLayers/Control/EditingToolbar.js)
+ File: Geolocate (no auto-title, OpenLayers/Control/Geolocate.js)
+ File: GetFeature (no auto-title, OpenLayers/Control/GetFeature.js)
+ File: Graticule (no auto-title, OpenLayers/Control/Graticule.js)
+ File: KeyboardDefaults (no auto-title, OpenLayers/Control/KeyboardDefaults.js)
+ File: LayerSwitcher (no auto-title, OpenLayers/Control/LayerSwitcher.js)
+ File: Measure (no auto-title, OpenLayers/Control/Measure.js)
+ File: ModifyFeature (no auto-title, OpenLayers/Control/ModifyFeature.js)
+ File: MousePosition (no auto-title, OpenLayers/Control/MousePosition.js)
+ File: Navigation (no auto-title, OpenLayers/Control/Navigation.js)
+ File: NavigationHistory (no auto-title, OpenLayers/Control/NavigationHistory.js)
+ File: NavToolbar (no auto-title, OpenLayers/Control/NavToolbar.js)
+ File: OverviewMap (no auto-title, OpenLayers/Control/OverviewMap.js)
+ File: Pan (no auto-title, OpenLayers/Control/Pan.js)
+ File: Panel (no auto-title, OpenLayers/Control/Panel.js)
+ File: PanPanel (no auto-title, OpenLayers/Control/PanPanel.js)
+ File: PanZoom (no auto-title, OpenLayers/Control/PanZoom.js)
+ File: PanZoomBar (no auto-title, OpenLayers/Control/PanZoomBar.js)
+ File: Permalink (no auto-title, OpenLayers/Control/Permalink.js)
+ File: PinchZoom (no auto-title, OpenLayers/Control/PinchZoom.js)
+ File: Scale (no auto-title, OpenLayers/Control/Scale.js)
+ File: ScaleLine (no auto-title, OpenLayers/Control/ScaleLine.js)
+ File: SelectFeature (no auto-title, OpenLayers/Control/SelectFeature.js)
+ File: SLDSelect (no auto-title, OpenLayers/Control/SLDSelect.js)
+ File: Snapping (no auto-title, OpenLayers/Control/Snapping.js)
+ File: Split (no auto-title, OpenLayers/Control/Split.js)
+ File: TouchNavigation (no auto-title, OpenLayers/Control/TouchNavigation.js)
+ File: TransformFeature (no auto-title, OpenLayers/Control/TransformFeature.js)
+ File: UTFGrid (OpenLayers/Control/UTFGrid.js)
+ File: WMSGetFeatureInfo (no auto-title, OpenLayers/Control/WMSGetFeatureInfo.js)
+ File: WMTSGetFeatureInfo (no auto-title, OpenLayers/Control/WMTSGetFeatureInfo.js)
+ File: Zoom (OpenLayers/Control/Zoom.js)
+ File: ZoomBox (no auto-title, OpenLayers/Control/ZoomBox.js)
+ File: ZoomIn (no auto-title, OpenLayers/Control/ZoomIn.js)
+ File: ZoomOut (no auto-title, OpenLayers/Control/ZoomOut.js)
+ File: ZoomPanel (no auto-title, OpenLayers/Control/ZoomPanel.js)
+ File: ZoomToMaxExtent (no auto-title, OpenLayers/Control/ZoomToMaxExtent.js)
+ } # Group: Control
+
+ } # Group: Control
+
+ Group: Feature {
+
+ File: Feature (no auto-title, OpenLayers/Feature.js)
+ File: Vector (no auto-title, OpenLayers/Feature/Vector.js)
+ } # Group: Feature
+
+ Group: Filter {
+
+ File: Filter (no auto-title, OpenLayers/Filter.js)
+ File: Comparison (no auto-title, OpenLayers/Filter/Comparison.js)
+ File: FeatureId (no auto-title, OpenLayers/Filter/FeatureId.js)
+ File: Function (no auto-title, OpenLayers/Filter/Function.js)
+ File: Logical (no auto-title, OpenLayers/Filter/Logical.js)
+ File: Spatial (no auto-title, OpenLayers/Filter/Spatial.js)
+ } # Group: Filter
+
+ Group: Format {
+
+ File: Format (no auto-title, OpenLayers/Format.js)
+
+ Group: Filter {
+
+ File: Filter (no auto-title, OpenLayers/Format/Filter.js)
+ File: v1 (no auto-title, OpenLayers/Format/Filter/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/Filter/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/Filter/v1_1_0.js)
+ } # Group: Filter
+
+ Group: GML {
+
+ File: GML (no auto-title, OpenLayers/Format/GML.js)
+ File: Base (no auto-title, OpenLayers/Format/GML/Base.js)
+ File: v2 (no auto-title, OpenLayers/Format/GML/v2.js)
+ File: v3 (no auto-title, OpenLayers/Format/GML/v3.js)
+ } # Group: GML
+
+ Group: SLD {
+
+ File: SLD (no auto-title, OpenLayers/Format/SLD.js)
+ File: SLD/v1_0_0_GeoServer (OpenLayers/Format/SLD/v1_0_0_GeoServer.js)
+ File: v1 (no auto-title, OpenLayers/Format/SLD/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/SLD/v1_0_0.js)
+ } # Group: SLD
+
+ Group: OWSCommon {
+
+ File: OWSCommon (no auto-title, OpenLayers/Format/OWSCommon.js)
+ File: v1 (no auto-title, OpenLayers/Format/OWSCommon/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/OWSCommon/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/OWSCommon/v1_1_0.js)
+ } # Group: OWSCommon
+
+ Group: WFSCapabilities {
+
+ File: WFSCapabilities (no auto-title, OpenLayers/Format/WFSCapabilities.js)
+ File: v1 (no auto-title, OpenLayers/Format/WFSCapabilities/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/WFSCapabilities/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WFSCapabilities/v1_1_0.js)
+ } # Group: WFSCapabilities
+
+ Group: WFST {
+
+ File: WFST (no auto-title, OpenLayers/Format/WFST.js)
+ File: v1 (no auto-title, OpenLayers/Format/WFST/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/WFST/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WFST/v1_1_0.js)
+ } # Group: WFST
+
+ Group: WMC {
+
+ File: WMC (no auto-title, OpenLayers/Format/WMC.js)
+ File: v1 (no auto-title, OpenLayers/Format/WMC/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Format/WMC/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WMC/v1_1_0.js)
+ } # Group: WMC
+
+ Group: WMSCapabilities {
+
+ File: WMSCapabilities (no auto-title, OpenLayers/Format/WMSCapabilities.js)
+ File: v1 (no auto-title, OpenLayers/Format/WMSCapabilities/v1.js)
+ File: v1_1 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1_0.js)
+ File: v1_1_1 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1_1.js)
+ File: v1_3 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_3.js)
+ File: v1_3_0 (no auto-title, OpenLayers/Format/WMSCapabilities/v1_3_0.js)
+ File: WMSCapabilities/v1_1_1_WMSC (no auto-title, OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js)
+ } # Group: WMSCapabilities
+
+ Group: WMSDescribeLayer {
+
+ File: WMSDescribeLayer (no auto-title, OpenLayers/Format/WMSDescribeLayer.js)
+ File: v1_1 (no auto-title, OpenLayers/Format/WMSDescribeLayer/v1_1.js)
+ } # Group: WMSDescribeLayer
+
+ Group: Format {
+
+ File: ArcXML (no auto-title, OpenLayers/Format/ArcXML.js)
+ File: ArcXML.Features (no auto-title, OpenLayers/Format/ArcXML/Features.js)
+ File: Atom (no auto-title, OpenLayers/Format/Atom.js)
+ File: Context (no auto-title, OpenLayers/Format/Context.js)
+ File: CQL (no auto-title, OpenLayers/Format/CQL.js)
+ File: CSWGetDomain (no auto-title, OpenLayers/Format/CSWGetDomain.js)
+ File: CSWGetDomain.v2_0_2 (no auto-title, OpenLayers/Format/CSWGetDomain/v2_0_2.js)
+ File: CSWGetRecords (no auto-title, OpenLayers/Format/CSWGetRecords.js)
+ File: CSWGetRecords.v2_0_2 (no auto-title, OpenLayers/Format/CSWGetRecords/v2_0_2.js)
+ File: EncodedPolyline (OpenLayers/Format/EncodedPolyline.js)
+ File: GeoJSON (no auto-title, OpenLayers/Format/GeoJSON.js)
+ File: GeoRSS (no auto-title, OpenLayers/Format/GeoRSS.js)
+ File: GPX (no auto-title, OpenLayers/Format/GPX.js)
+ File: JSON (no auto-title, OpenLayers/Format/JSON.js)
+ File: KML (no auto-title, OpenLayers/Format/KML.js)
+ File: OGCExceptionReport (no auto-title, OpenLayers/Format/OGCExceptionReport.js)
+ File: OSM (no auto-title, OpenLayers/Format/OSM.js)
+ File: OWSContext (no auto-title, OpenLayers/Format/OWSContext.js)
+ File: OWSContext.v0_3_1 (no auto-title, OpenLayers/Format/OWSContext/v0_3_1.js)
+ File: QueryStringFilter (no auto-title, OpenLayers/Format/QueryStringFilter.js)
+ File: SOSCapabilities (no auto-title, OpenLayers/Format/SOSCapabilities.js)
+ File: SOSCapabilities.v1_0_0 (no auto-title, OpenLayers/Format/SOSCapabilities/v1_0_0.js)
+ File: SOSGetFeatureOfInterest (no auto-title, OpenLayers/Format/SOSGetFeatureOfInterest.js)
+ File: SOSGetObservation (no auto-title, OpenLayers/Format/SOSGetObservation.js)
+ File: Text (no auto-title, OpenLayers/Format/Text.js)
+ File: WCSCapabilities (OpenLayers/Format/WCSCapabilities.js)
+
+ Group: WCSCapabilities {
+
+ File: WCSCapabilities.v1 (OpenLayers/Format/WCSCapabilities/v1.js)
+ File: WCSCapabilities/v1_0_0 (OpenLayers/Format/WCSCapabilities/v1_0_0.js)
+ File: WCSCapabilities/v1_1_0 (OpenLayers/Format/WCSCapabilities/v1_1_0.js)
+ } # Group: WCSCapabilities
+
+ File: WCSGetCoverage version 1.1.0 (no auto-title, OpenLayers/Format/WCSGetCoverage.js)
+ File: WFS (no auto-title, OpenLayers/Format/WFS.js)
+ File: WFSDescribeFeatureType (no auto-title, OpenLayers/Format/WFSDescribeFeatureType.js)
+ File: WKT (no auto-title, OpenLayers/Format/WKT.js)
+ File: WMSGetFeatureInfo (no auto-title, OpenLayers/Format/WMSGetFeatureInfo.js)
+ File: WMTSCapabilities (no auto-title, OpenLayers/Format/WMTSCapabilities.js)
+ File: WMTSCapabilities.v1_0_0 (no auto-title, OpenLayers/Format/WMTSCapabilities/v1_0_0.js)
+ File: WPSCapabilities (no auto-title, OpenLayers/Format/WPSCapabilities.js)
+ File: WPSCapabilities.v1_0_0 (no auto-title, OpenLayers/Format/WPSCapabilities/v1_0_0.js)
+ File: WPSDescribeProcess (no auto-title, OpenLayers/Format/WPSDescribeProcess.js)
+ File: WPSExecute version 1.0.0 (no auto-title, OpenLayers/Format/WPSExecute.js)
+ File: XLS (no auto-title, OpenLayers/Format/XLS.js)
+ File: XLS.v1 (no auto-title, OpenLayers/Format/XLS/v1.js)
+ File: XLS.v1_1_0 (no auto-title, OpenLayers/Format/XLS/v1_1_0.js)
+ File: XML (no auto-title, OpenLayers/Format/XML.js)
+ File: XML.VersionedOGC (OpenLayers/Format/XML/VersionedOGC.js)
+ } # Group: Format
+
+ } # Group: Format
+
+ Group: Geometry {
+
+ File: Geometry (no auto-title, OpenLayers/Geometry.js)
+ File: Collection (no auto-title, OpenLayers/Geometry/Collection.js)
+ File: Curve (no auto-title, OpenLayers/Geometry/Curve.js)
+ File: LinearRing (no auto-title, OpenLayers/Geometry/LinearRing.js)
+ File: LineString (no auto-title, OpenLayers/Geometry/LineString.js)
+ File: MultiLineString (no auto-title, OpenLayers/Geometry/MultiLineString.js)
+ File: MultiPoint (no auto-title, OpenLayers/Geometry/MultiPoint.js)
+ File: MultiPolygon (no auto-title, OpenLayers/Geometry/MultiPolygon.js)
+ File: Point (no auto-title, OpenLayers/Geometry/Point.js)
+ File: Polygon (no auto-title, OpenLayers/Geometry/Polygon.js)
+ } # Group: Geometry
+
+ Group: Handler {
+
+ File: Handler (no auto-title, OpenLayers/Handler.js)
+ File: Box (no auto-title, OpenLayers/Handler/Box.js)
+ File: Click (no auto-title, OpenLayers/Handler/Click.js)
+ File: Drag (no auto-title, OpenLayers/Handler/Drag.js)
+ File: Feature (no auto-title, OpenLayers/Handler/Feature.js)
+ File: Hover (no auto-title, OpenLayers/Handler/Hover.js)
+ File: Keyboard (no auto-title, OpenLayers/Handler/Keyboard.js)
+ File: MouseWheel (no auto-title, OpenLayers/Handler/MouseWheel.js)
+ File: Path (no auto-title, OpenLayers/Handler/Path.js)
+ File: Pinch (no auto-title, OpenLayers/Handler/Pinch.js)
+ File: Point (no auto-title, OpenLayers/Handler/Point.js)
+ File: Polygon (no auto-title, OpenLayers/Handler/Polygon.js)
+ File: RegularPolygon (no auto-title, OpenLayers/Handler/RegularPolygon.js)
+ } # Group: Handler
+
+ Group: Lang {
+
+ File: Lang (no auto-title, OpenLayers/Lang.js)
+
+ Group: Lang {
+
+ File: ar (no auto-title, OpenLayers/Lang/ar.js)
+ File: be-tarask (no auto-title, OpenLayers/Lang/be-tarask.js)
+ File: bg (no auto-title, OpenLayers/Lang/bg.js)
+ File: br (no auto-title, OpenLayers/Lang/br.js)
+ File: ca (no auto-title, OpenLayers/Lang/ca.js)
+ File: cs-CZ (no auto-title, OpenLayers/Lang/cs-CZ.js)
+ File: da-DK (no auto-title, OpenLayers/Lang/da-DK.js)
+ File: de (no auto-title, OpenLayers/Lang/de.js)
+ File: en (no auto-title, OpenLayers/Lang/en.js)
+ File: en-CA (no auto-title, OpenLayers/Lang/en-CA.js)
+ File: es (no auto-title, OpenLayers/Lang/es.js)
+ File: el (no auto-title, OpenLayers/Lang/el.js)
+ File: fi (no auto-title, OpenLayers/Lang/fi.js)
+ File: fr (no auto-title, OpenLayers/Lang/fr.js)
+ File: fur (no auto-title, OpenLayers/Lang/fur.js)
+ File: gl (no auto-title, OpenLayers/Lang/gl.js)
+ File: gsw (no auto-title, OpenLayers/Lang/gsw.js)
+ File: hr (no auto-title, OpenLayers/Lang/hr.js)
+ File: hsb (no auto-title, OpenLayers/Lang/hsb.js)
+ File: hu (no auto-title, OpenLayers/Lang/hu.js)
+ File: ia (no auto-title, OpenLayers/Lang/ia.js)
+ File: id (no auto-title, OpenLayers/Lang/id.js)
+ File: io (no auto-title, OpenLayers/Lang/io.js)
+ File: is (no auto-title, OpenLayers/Lang/is.js)
+ File: it (no auto-title, OpenLayers/Lang/it.js)
+ File: ja (no auto-title, OpenLayers/Lang/ja.js)
+ File: km (no auto-title, OpenLayers/Lang/km.js)
+ File: ksh (no auto-title, OpenLayers/Lang/ksh.js)
+ File: lt (no auto-title, OpenLayers/Lang/lt.js)
+ File: nds (no auto-title, OpenLayers/Lang/nds.js)
+ File: nb (no auto-title, OpenLayers/Lang/nb.js)
+ File: nl (no auto-title, OpenLayers/Lang/nl.js)
+ File: nn (no auto-title, OpenLayers/Lang/nn.js)
+ File: oc (no auto-title, OpenLayers/Lang/oc.js)
+ File: pl (no auto-title, OpenLayers/Lang/pl.js)
+ File: pt (no auto-title, OpenLayers/Lang/pt.js)
+ File: pt-BR (no auto-title, OpenLayers/Lang/pt-BR.js)
+ File: ru (no auto-title, OpenLayers/Lang/ru.js)
+ File: sk (no auto-title, OpenLayers/Lang/sk.js)
+ File: sv-SE (no auto-title, OpenLayers/Lang/sv-SE.js)
+ File: te (no auto-title, OpenLayers/Lang/te.js)
+ File: vi (no auto-title, OpenLayers/Lang/vi.js)
+ File: zh-CN (no auto-title, OpenLayers/Lang/zh-CN.js)
+ File: zh-TW (no auto-title, OpenLayers/Lang/zh-TW.js)
+ File: Lang["ro"] (OpenLayers/Lang/ro.js)
+ } # Group: Lang
+
+ } # Group: Lang
+
+ Group: Layer {
+
+ File: Layer (no auto-title, OpenLayers/Layer.js)
+
+ Group: Layer {
+
+ File: ArcGISCache.js (no auto-title, OpenLayers/Layer/ArcGISCache.js)
+ File: ArcGIS93Rest (no auto-title, OpenLayers/Layer/ArcGIS93Rest.js)
+ File: ArcIMS (no auto-title, OpenLayers/Layer/ArcIMS.js)
+ File: Bing (no auto-title, OpenLayers/Layer/Bing.js)
+ File: Boxes (no auto-title, OpenLayers/Layer/Boxes.js)
+ File: EventPane (no auto-title, OpenLayers/Layer/EventPane.js)
+ File: FixedZoomLevels (no auto-title, OpenLayers/Layer/FixedZoomLevels.js)
+ File: GeoRSS (no auto-title, OpenLayers/Layer/GeoRSS.js)
+ File: Google (no auto-title, OpenLayers/Layer/Google.js)
+ File: Google.v3 (no auto-title, OpenLayers/Layer/Google/v3.js)
+ File: Grid (no auto-title, OpenLayers/Layer/Grid.js)
+ File: HTTPRequest (no auto-title, OpenLayers/Layer/HTTPRequest.js)
+ File: Image (no auto-title, OpenLayers/Layer/Image.js)
+ File: KaMap (no auto-title, OpenLayers/Layer/KaMap.js)
+ File: KaMapCache (no auto-title, OpenLayers/Layer/KaMapCache.js)
+ File: MapGuide (no auto-title, OpenLayers/Layer/MapGuide.js)
+ File: MapServer (no auto-title, OpenLayers/Layer/MapServer.js)
+ File: Markers (no auto-title, OpenLayers/Layer/Markers.js)
+ File: PointGrid (no auto-title, OpenLayers/Layer/PointGrid.js)
+ File: PointTrack (no auto-title, OpenLayers/Layer/PointTrack.js)
+ File: SphericalMercator (no auto-title, OpenLayers/Layer/SphericalMercator.js)
+ File: Text (no auto-title, OpenLayers/Layer/Text.js)
+ File: TileCache (no auto-title, OpenLayers/Layer/TileCache.js)
+ File: TMS (no auto-title, OpenLayers/Layer/TMS.js)
+ File: Vector (no auto-title, OpenLayers/Layer/Vector.js)
+ File: Vector.RootContainer (no auto-title, OpenLayers/Layer/Vector/RootContainer.js)
+ File: WMS (no auto-title, OpenLayers/Layer/WMS.js)
+ File: WMTS (no auto-title, OpenLayers/Layer/WMTS.js)
+ File: WorldWind (no auto-title, OpenLayers/Layer/WorldWind.js)
+ File: XYZ (no auto-title, OpenLayers/Layer/XYZ.js)
+ File: Zoomify (no auto-title, OpenLayers/Layer/Zoomify.js)
+ File: OSM (OpenLayers/Layer/OSM.js)
+ File: UTFGrid (OpenLayers/Layer/UTFGrid.js)
+ } # Group: Layer
+
+ } # Group: Layer
+
+ Group: Marker {
+
+ File: Marker (no auto-title, OpenLayers/Marker.js)
+ File: Box (no auto-title, OpenLayers/Marker/Box.js)
+ } # Group: Marker
+
+ Group: Popup {
+
+ File: Popup (no auto-title, OpenLayers/Popup.js)
+ File: Anchored (no auto-title, OpenLayers/Popup/Anchored.js)
+ File: Framed (no auto-title, OpenLayers/Popup/Framed.js)
+ File: FramedCloud (no auto-title, OpenLayers/Popup/FramedCloud.js)
+ } # Group: Popup
+
+ Group: Protocol {
+
+ File: Protocol (no auto-title, OpenLayers/Protocol.js)
+
+ Group: Protocol {
+
+ File: CSW (OpenLayers/Protocol/CSW.js)
+ File: CSW.v2_0_2 (OpenLayers/Protocol/CSW/v2_0_2.js)
+ File: HTTP (no auto-title, OpenLayers/Protocol/HTTP.js)
+ File: Script (no auto-title, OpenLayers/Protocol/Script.js)
+ File: SOS.DEFAULTS (no auto-title, OpenLayers/Protocol/SOS.js)
+ File: SOS.v1_0_0 (no auto-title, OpenLayers/Protocol/SOS/v1_0_0.js)
+ } # Group: Protocol
+
+ Group: WFS {
+
+ File: WFS (no auto-title, OpenLayers/Protocol/WFS.js)
+ File: v1 (no auto-title, OpenLayers/Protocol/WFS/v1.js)
+ File: v1_0_0 (no auto-title, OpenLayers/Protocol/WFS/v1_0_0.js)
+ File: v1_1_0 (no auto-title, OpenLayers/Protocol/WFS/v1_1_0.js)
+ } # Group: WFS
+
+ } # Group: Protocol
+
+ Group: Renderer {
+
+ File: Renderer (no auto-title, OpenLayers/Renderer.js)
+ File: Canvas (no auto-title, OpenLayers/Renderer/Canvas.js)
+ File: ElementsIndexer (no auto-title, OpenLayers/Renderer/Elements.js)
+ File: SVG (no auto-title, OpenLayers/Renderer/SVG.js)
+ File: VML (no auto-title, OpenLayers/Renderer/VML.js)
+ } # Group: Renderer
+
+ Group: Request {
+
+ File: Request (no auto-title, OpenLayers/Request.js)
+ File: XMLHttpRequest (no auto-title, OpenLayers/Request/XMLHttpRequest.js)
+ } # Group: Request
+
+ Group: Strategy {
+
+ File: Strategy (no auto-title, OpenLayers/Strategy.js)
+ File: BBOX (no auto-title, OpenLayers/Strategy/BBOX.js)
+ File: Cluster (no auto-title, OpenLayers/Strategy/Cluster.js)
+ File: Filter (no auto-title, OpenLayers/Strategy/Filter.js)
+ File: Fixed (no auto-title, OpenLayers/Strategy/Fixed.js)
+ File: Paging (no auto-title, OpenLayers/Strategy/Paging.js)
+ File: Refresh (no auto-title, OpenLayers/Strategy/Refresh.js)
+ File: Save (no auto-title, OpenLayers/Strategy/Save.js)
+ } # Group: Strategy
+
+ Group: Symbolizer {
+
+ File: Symbolizer (no auto-title, OpenLayers/Symbolizer.js)
+ File: Line (no auto-title, OpenLayers/Symbolizer/Line.js)
+ File: Point (no auto-title, OpenLayers/Symbolizer/Point.js)
+ File: Polygon (no auto-title, OpenLayers/Symbolizer/Polygon.js)
+ File: Raster (no auto-title, OpenLayers/Symbolizer/Raster.js)
+ File: Text (no auto-title, OpenLayers/Symbolizer/Text.js)
+ } # Group: Symbolizer
+
+ Group: Tile {
+
+ File: Tile (no auto-title, OpenLayers/Tile.js)
+ File: Image (no auto-title, OpenLayers/Tile/Image.js)
+ File: Image.IFrame (no auto-title, OpenLayers/Tile/Image/IFrame.js)
+ File: UTFGrid (OpenLayers/Tile/UTFGrid.js)
+ } # Group: Tile
+
+ File: Deprecated (no auto-title, deprecated.js)
+
+ Group: OpenLayers {
+
+ File: Console (no auto-title, OpenLayers/Console.js)
+ File: Events (no auto-title, OpenLayers/Events.js)
+ File: Icon (no auto-title, OpenLayers/Icon.js)
+ File: Kinetic (no auto-title, OpenLayers/Kinetic.js)
+ File: Map (no auto-title, OpenLayers/Map.js)
+ File: Projection (no auto-title, OpenLayers/Projection.js)
+ File: Rule (no auto-title, OpenLayers/Rule.js)
+ File: SingleFile.js (no auto-title, OpenLayers/SingleFile.js)
+ File: Style (no auto-title, OpenLayers/Style.js)
+ File: Style2 (no auto-title, OpenLayers/Style2.js)
+ File: StyleMap (no auto-title, OpenLayers/StyleMap.js)
+ File: Tween (no auto-title, OpenLayers/Tween.js)
+ File: Util (no auto-title, OpenLayers/Util.js)
+ File: Spherical (no auto-title, OpenLayers/Spherical.js)
+ File: Animation (OpenLayers/Animation.js)
+ File: Events.buttonclick (OpenLayers/Events/buttonclick.js)
+ File: Events.featureclick (OpenLayers/Events/featureclick.js)
+ File: TileManager (OpenLayers/TileManager.js)
+ File: Util.vendorPrefix (OpenLayers/Util/vendorPrefix.js)
+ File: WPSClient (OpenLayers/WPSClient.js)
+ File: WPSProcess (OpenLayers/WPSProcess.js)
+ } # Group: OpenLayers
+
+ } # Group: OpenLayers
+
+Group: Index {
+
+ Index: Everything
+ Class Index: Classes
+ Constant Index: Constants
+ Function Index: Functions
+ Property Index: Properties
+ File Index: Files
+ Constructor Index: Constructor
+ } # Group: Index
+
diff --git a/misc/openlayers/doc_config/OL.css b/misc/openlayers/doc_config/OL.css
new file mode 100644
index 0000000..a397119
--- /dev/null
+++ b/misc/openlayers/doc_config/OL.css
@@ -0,0 +1,20 @@
+p {
+ text-indent: 0; margin-bottom: 1em;
+}
+
+.MGroup {
+ font-variant: normal;
+ margin: 0.4em 0 0em 10px
+}
+
+.MTitle {
+ font-variant: normal;
+}
+
+.CGroup .CTitle {
+ font-variant: normal;
+}
+
+.SGroup .SEntry {
+ font-variant: normal;
+} \ No newline at end of file
diff --git a/misc/openlayers/doc_config/Topics.txt b/misc/openlayers/doc_config/Topics.txt
new file mode 100644
index 0000000..84d6dcc
--- /dev/null
+++ b/misc/openlayers/doc_config/Topics.txt
@@ -0,0 +1,102 @@
+Format: 1.51
+
+# This is the Natural Docs topics file for this project. If you change anything
+# here, it will apply to THIS PROJECT ONLY. If you'd like to change something
+# for all your projects, edit the Topics.txt in Natural Docs' Config directory
+# instead.
+
+
+# If you'd like to prevent keywords from being recognized by Natural Docs, you
+# can do it like this:
+# Ignore Keywords: [keyword], [keyword], ...
+#
+# Or you can use the list syntax like how they are defined:
+# Ignore Keywords:
+# [keyword]
+# [keyword], [plural keyword]
+# ...
+
+
+#-------------------------------------------------------------------------------
+# SYNTAX:
+#
+# Topic Type: [name]
+# Alter Topic Type: [name]
+# Creates a new topic type or alters one from the main file. Each type gets
+# its own index and behavior settings. Its name can have letters, numbers,
+# spaces, and these charaters: - / . '
+#
+# Plural: [name]
+# Sets the plural name of the topic type, if different.
+#
+# Keywords:
+# [keyword]
+# [keyword], [plural keyword]
+# ...
+# Defines or adds to the list of keywords for the topic type. They may only
+# contain letters, numbers, and spaces and are not case sensitive. Plural
+# keywords are used for list topics. You can redefine keywords found in the
+# main topics file.
+#
+# Index: [yes|no]
+# Whether the topics get their own index. Defaults to yes. Everything is
+# included in the general index regardless of this setting.
+#
+# Scope: [normal|start|end|always global]
+# How the topics affects scope. Defaults to normal.
+# normal - Topics stay within the current scope.
+# start - Topics start a new scope for all the topics beneath it,
+# like class topics.
+# end - Topics reset the scope back to global for all the topics
+# beneath it.
+# always global - Topics are defined as global, but do not change the scope
+# for any other topics.
+#
+# Class Hierarchy: [yes|no]
+# Whether the topics are part of the class hierarchy. Defaults to no.
+#
+# Page Title If First: [yes|no]
+# Whether the topic's title becomes the page title if it's the first one in
+# a file. Defaults to no.
+#
+# Break Lists: [yes|no]
+# Whether list topics should be broken into individual topics in the output.
+# Defaults to no.
+#
+# Can Group With: [type], [type], ...
+# Defines a list of topic types that this one can possibly be grouped with.
+# Defaults to none.
+#-------------------------------------------------------------------------------
+
+# The following topics are defined in the main file, if you'd like to alter
+# their behavior or add keywords:
+#
+# Generic, Class, Interface, Section, File, Group, Function, Variable,
+# Property, Type, Constant, Enumeration, Event, Delegate, Macro,
+# Database, Database Table, Database View, Database Index, Database
+# Cursor, Database Trigger, Cookie, Build Target
+
+# If you add something that you think would be useful to other developers
+# and should be included in Natural Docs by default, please e-mail it to
+# topics [at] naturaldocs [dot] org.
+
+
+Topic Type: Constructor
+
+ Class Hierarchy: Yes
+ Keywords:
+ constructor
+ initialize
+
+
+Alter Topic Type: Function
+
+ Add Keywords:
+ apimethod
+ apifunction
+
+
+Alter Topic Type: Property
+
+ Add Keywords:
+ apiproperty
diff --git a/misc/openlayers/examples/Jugl.js b/misc/openlayers/examples/Jugl.js
new file mode 100644
index 0000000..4f81a27
--- /dev/null
+++ b/misc/openlayers/examples/Jugl.js
@@ -0,0 +1,8 @@
+/*
+ * Jugl.js -- JavaScript Template Library
+ *
+ * Copyright 2007-2010 Tim Schaub
+ * Released under the MIT license. Please see
+ * http://github.com/tschaub/jugl/blob/master/license.txt for the full license.
+ */
+(function(){var f={prefix:"jugl",namespaceURI:null,loadTemplate:function(h){var i=function(l){var m,k,n=!l.status||(l.status>=200&&l.status<300);if(n){try{m=l.responseXML;k=new e(m.documentElement)}catch(j){m=document.createElement("div");m.innerHTML=l.responseText;k=new e(m.firstChild)}if(h.callback){h.callback.call(h.scope,k)}}else{if(h.failure){h.failure.call(h.scope,l)}}};d(h.url,i)}};var g=function(h,j){h=h||{};j=j||{};for(var i in j){h[i]=j[i]}return h};var a=function(l,o){var m,n,k,j,h;if(typeof(l)==="string"){m=document.getElementById(l);if(!m){throw Error("Element id not found: "+l)}l=m}if(typeof(o)==="string"){m=document.getElementById(o);if(!m){throw Error("Element id not found: "+o)}o=m}if(o.namespaceURI&&o.xml){n=document.createElement("div");n.innerHTML=o.xml;k=n.childNodes;for(j=0,h=k.length;j<h;++j){l.appendChild(k[j])}}else{if(l.ownerDocument&&l.ownerDocument.importNode&&l.ownerDocument!==o.ownerDocument){o=l.ownerDocument.importNode(o,true)}l.appendChild(o)}return o};var d=function(h,k,i){var j;if(typeof XMLHttpRequest!=="undefined"){j=new XMLHttpRequest()}else{if(typeof ActiveXObject!=="undefined"){j=new ActiveXObject("Microsoft.XMLHTTP")}else{throw new Error("XMLHttpRequest not supported")}}j.open("GET",h);j.onreadystatechange=function(){if(j.readyState===4){k.call(i,j)}};j.send(null)};var b=function(h,i){this.template=h;this.node=i;this.scope={};this.scope.repeat={}};g(b.prototype,{clone:function(){var i=this.node.cloneNode(true);i.removeAttribute("id");var h=new b(this.template,i);g(h.scope,this.scope);return h},getAttribute:function(h){var j;if(this.node.nodeType===1){if(this.template.usingNS){j=this.node.getAttributeNodeNS(f.namespaceURI,h)}else{j=this.node.getAttributeNode(f.prefix+":"+h)}if(j&&!j.specified){j=false}}var i;if(j){i=new c(this,j,h)}else{i=j}return i},setAttribute:function(h,i){this.node.setAttribute(h,i)},removeAttributeNode:function(h){this.node.removeAttributeNode(h.node)},getChildNodes:function(){var k=this.node.childNodes.length;var j=new Array(k);var l;for(var h=0;h<k;++h){l=new b(this.template,this.node.childNodes[h]);l.scope=g({},this.scope);j[h]=l}return j},removeChildNodes:function(){while(this.node.hasChildNodes()){this.node.removeChild(this.node.firstChild)}},removeChild:function(h){this.node.removeChild(h.node);return node},removeSelf:function(){this.node.parentNode.removeChild(this.node)},importNode:function(h){if(this.node.ownerDocument&&this.node.ownerDocument.importNode){if(h.node.ownerDocument!==this.node.ownerDocument){h.node=this.node.ownerDocument.importNode(h.node,true)}}},appendChild:function(h){this.importNode(h);this.node.appendChild(h.node)},insertAfter:function(h){this.importNode(h);var j=this.node.parentNode;var i=this.node.nextSibling;if(i){j.insertBefore(h.node,i)}else{j.appendChild(h.node)}},insertBefore:function(h){this.importNode(h);var i=this.node.parentNode;i.insertBefore(h.node,this.node)},process:function(){var j;var r=true;var n=["define","condition","repeat"];for(var o=0,p=n.length;o<p;++o){j=this.getAttribute(n[o]);if(j){r=j.process();if(!r){return}}}var q=this.getAttribute("content");if(q){q.process()}else{var k=this.getAttribute("replace");if(k){k.process()}}var m=this.getAttribute("attributes");if(m){m.process()}if(!q&&!k){this.processChildNodes()}var h=this.getAttribute("omit-tag");if(h){h.process()}var l=this.getAttribute("reflow");if(l){l.process()}},processChildNodes:function(){var k=this.getChildNodes();for(var j=0,h=k.length;j<h;++j){k[j].process()}}});var e=function(h){h=h||{};if(typeof h==="string"||(h.nodeType===1)){h={node:h}}if(typeof(h.node)==="string"){h.node=document.getElementById(h.node);if(!h.node){throw Error("Element id not found: "+h.node)}}if(h.node){this.node=h.node;this.loaded=true}else{if(h.url){this.load({url:h.url,callback:h.callback,scope:h.scope})}}};g(e.prototype,{node:null,usingNS:false,xmldom:window.ActiveXObject?new ActiveXObject("Microsoft.XMLDOM"):null,trimSpace:(/^\s*(\w+)\s+(.*?)\s*$/),loaded:false,loading:false,process:function(h){var i,j;h=g({context:null,clone:false,string:false},h);this.usingNS=this.node.getAttributeNodeNS&&f.namespaceURI;i=new b(this,this.node);if(h.clone||h.string){i=i.clone()}if(h.context){i.scope=h.context}i.process();if(h.string){if(i.node.innerHTML){j=i.node.innerHTML}else{if(this.xmldom){j=i.node.xml}else{j=(new XMLSerializer).serializeToString(i.node)}}}else{j=i.node;if(h.parent){if(h.clone){j=a(h.parent,i.node)}else{this.appendTo(h.parent)}}}return j},load:function(j){if(typeof j==="string"){j={url:j}}j=j||{};this.loading=true;var h=function(k){this.node=k.node;this.loading=false;this.loaded=true;if(j.callback){j.callback.apply(j.scope,[k])}};var i;if(j.failure){i=(function(){return function(k){j.failure.call(j.scope,k)}})()}f.loadTemplate({url:j.url,callback:h,failure:i,scope:this})},appendTo:function(h){this.node=a(h,this.node);return this}});var c=function(h,j,i){this.element=h;this.node=j;this.type=i;this.nodeValue=j.nodeValue;this.nodeName=j.nodeName;this.template=h.template};g(c.prototype,{splitAttributeValue:function(i){i=(i!=null)?i:this.nodeValue;var h=this.template.trimSpace.exec(i);return h&&h.length===3&&[h[1],h[2]]},splitExpressionPrefix:function(){var h=this.splitAttributeValue();if(!h||(h[0]!="structure"&&h[0]!="text")){h=[null,this.nodeValue]}return h},getAttributeValues:function(){return this.nodeValue.replace(/[\t\n]/g,"").replace(/;\s*$/,"").replace(/;;/g,"\t").split(";").join("\n").replace(/\t/g,";").split(/\n/g)},removeSelf:function(){this.element.removeAttributeNode(this)},process:function(){return this.processAttribute[this.type].apply(this,[])},evalInScope:function(k){var i=this.element.scope;var h=[];var j=[];for(key in i){h.push(key);j.push(i[key])}var l=new Function(h.join(","),"return "+k);return l.apply({},j)},processAttribute:{define:function(){var l,k,j,h=this.getAttributeValues();for(k=0,j=h.length;k<j;++k){l=this.splitAttributeValue(h[k]);this.element.scope[l[0]]=this.evalInScope(l[1])}this.removeSelf();return true},condition:function(){var h=!!(this.evalInScope(this.nodeValue));this.removeSelf();if(!h){this.element.removeSelf()}return h},repeat:function(){var l=this.splitAttributeValue();var r=l[0];var o=this.evalInScope(l[1]);this.removeSelf();if(!(o instanceof Array)){var q=new Array();for(var j in o){q.push(j)}o=q}var m;var h=this.element;for(var n=0,k=o.length;n<k;++n){m=this.element.clone();m.scope[r]=o[n];m.scope.repeat[r]={index:n,number:n+1,even:!(n%2),odd:!!(n%2),start:(n===0),end:(n===k-1),length:k};h.insertAfter(m);m.process();h=m}this.element.removeSelf();return false},content:function(){var m=this.splitExpressionPrefix();var p=this.evalInScope(m[1]);this.removeSelf();if(m[0]==="structure"){try{this.element.node.innerHTML=p}catch(l){var h=document.createElement("div");h.innerHTML=p;if(this.element.node.xml&&this.template.xmldom){while(this.element.node.firstChild){this.element.node.removeChild(this.element.node.firstChild)}this.template.xmldom.loadXML(h.outerHTML);var k=this.template.xmldom.firstChild.childNodes;for(var n=0,o=k.length;n<o;++n){this.element.node.appendChild(k[n])}}else{this.element.node.innerHTML=h.innerHTML}}}else{var q;if(this.element.node.xml&&this.template.xmldom){q=this.template.xmldom.createTextNode(p)}else{q=document.createTextNode(p)}var j=new b(this.template,q);this.element.removeChildNodes();this.element.appendChild(j)}return true},replace:function(){var k=this.splitExpressionPrefix();var j=this.evalInScope(k[1]);this.removeSelf();if(k[0]==="structure"){var m=document.createElement("div");m.innerHTML=j;if(this.element.node.xml&&this.template.xmldom){this.template.xmldom.loadXML(m.outerHTML);m=this.template.xmldom.firstChild}while(m.firstChild){var l=m.removeChild(m.firstChild);if(this.element.node.ownerDocument&&this.element.node.ownerDocument.importNode){if(l.ownerDocument!=this.element.node.ownerDocument){l=this.element.node.ownerDocument.importNode(l,true)}}this.element.node.parentNode.insertBefore(l,this.element.node)}}else{var i;if(this.element.node.xml&&this.template.xmldom){i=this.template.xmldom.createTextNode(j)}else{i=document.createTextNode(j)}var h=new b(this.template,i);this.element.insertBefore(h)}this.element.removeSelf();return true},attributes:function(){var h=this.getAttributeValues();var n,k,m;for(var l=0,j=h.length;l<j;++l){n=this.splitAttributeValue(h[l]);k=n[0];m=this.evalInScope(n[1]);if(m!==false){this.element.setAttribute(k,m)}}this.removeSelf();return true},"omit-tag":function(){var l=((this.nodeValue==="")||!!(this.evalInScope(this.nodeValue)));this.removeSelf();if(l){var k=this.element.getChildNodes();for(var j=0,h=k.length;j<h;++j){this.element.insertBefore(k[j])}this.element.removeSelf()}},reflow:function(){var h=((this.nodeValue==="")||!!(this.evalInScope(this.nodeValue)));this.removeSelf();if(h){if(this.element.node.outerHTML){this.element.node.outerHTML=this.element.node.outerHTML}else{this.element.node.innerHTML=this.element.node.innerHTML}}}}});window.jugl=g(f,{Template:e})})(); \ No newline at end of file
diff --git a/misc/openlayers/examples/KMLParser.html b/misc/openlayers/examples/KMLParser.html
new file mode 100644
index 0000000..7b36ec3
--- /dev/null
+++ b/misc/openlayers/examples/KMLParser.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers KML Parser Example</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function parseData(req) {
+ g = new OpenLayers.Format.KML({extractStyles: true});
+ html = "";
+ features = g.read(req.responseText);
+ for(var feat in features) {
+ html += "Feature: Geometry: "+ features[feat].geometry+",";
+ html += "<ul>";
+ for (var j in features[feat].attributes) {
+ html += "<li>Attribute "+j+":"+features[feat].attributes[j]+"</li>";
+ }
+ html += "</ul>";
+ html += "<ul>";
+ for (var j in features[feat].style) {
+ html += "<li>Style "+j+":"+features[feat].style[j]+"</li>";
+ }
+ html += "</ul>"
+ }
+ document.getElementById('output').innerHTML = html;
+ }
+ function load() {
+ OpenLayers.Request.GET({
+ url: "kml/lines.kml",
+ success: parseData
+ });
+ }
+ </script>
+ </head>
+ <body onload="load()">
+ <h1 id="title">KML Parser Example</h1>
+
+ <div id="tags">
+ KML, parsing, format
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate the operation of the KML parser.
+ </p>
+
+ <div id="output"></div>
+
+ <div id="docs">
+ <p>This script reads data from a KML file and parses out the
+ coordinates, appending them to a HTML string with markup tags.
+ This markup is dumped to an element in the page.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/SLDSelect.html b/misc/openlayers/examples/SLDSelect.html
new file mode 100644
index 0000000..ff07e31
--- /dev/null
+++ b/misc/openlayers/examples/SLDSelect.html
@@ -0,0 +1,202 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers SLD based selection control</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlSLDSelectBoxActive {
+ cursor: crosshair;
+ }
+ .olControlSLDSelectPolygonActive {
+ cursor: crosshair;
+ }
+ .olControlSLDSelectLineActive {
+ cursor: crosshair;
+ }
+ .olControlSLDSelectPointActive {
+ cursor: pointer;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, controls, layers;
+
+ function init(){
+ OpenLayers.ProxyHost= "proxy.cgi?url=";
+ map = new OpenLayers.Map('map', {allOverlays: true, controls: []});
+ var url = "http://demo.opengeo.org/geoserver/wms";
+ layers = {
+ states: new OpenLayers.Layer.WMS("State boundary", url,
+ {layers: 'topp:tasmania_state_boundaries', format: 'image/gif', transparent: 'TRUE'},
+ {singleTile: true}),
+ roads: new OpenLayers.Layer.WMS("Roads", url,
+ {layers: 'topp:tasmania_roads', format: 'image/gif', transparent: 'TRUE'},
+ {singleTile: true}),
+ waterbodies: new OpenLayers.Layer.WMS("Water bodies", url,
+ {layers: 'topp:tasmania_water_bodies', format: 'image/gif', transparent: 'TRUE'},
+ {singleTile: true}),
+ cities: new OpenLayers.Layer.WMS("Cities", url,
+ {layers: 'topp:tasmania_cities', format: 'image/gif', transparent: 'TRUE'},
+ {singleTile: true})
+ };
+
+ for (var key in layers) {
+ map.addLayer(layers[key]);
+ }
+
+ map.setCenter(new OpenLayers.LonLat(146.65748632815,-42.230763671875), 7);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ controls = {
+ navigation: new OpenLayers.Control.Navigation(),
+ box: new OpenLayers.Control.SLDSelect(
+ OpenLayers.Handler.RegularPolygon,
+ {
+ displayClass: 'olControlSLDSelectBox',
+ layers: [layers['waterbodies']],
+ handlerOptions: {irregular: true}
+ }
+ ),
+ polygon: new OpenLayers.Control.SLDSelect(
+ OpenLayers.Handler.Polygon,
+ {
+ displayClass: 'olControlSLDSelectPolygon',
+ layers: [layers['waterbodies']]
+ }
+ ),
+ line: new OpenLayers.Control.SLDSelect(
+ OpenLayers.Handler.Path,
+ {
+ displayClass: 'olControlSLDSelectLine',
+ layers: [layers['waterbodies']]
+ }
+ ),
+ point: new OpenLayers.Control.SLDSelect(
+ OpenLayers.Handler.Click,
+ {
+ displayClass: 'olControlSLDSelectPoint',
+ layers: [layers['waterbodies']]
+ }
+ ),
+ circle: new OpenLayers.Control.SLDSelect(
+ OpenLayers.Handler.RegularPolygon,
+ {
+ displayClass: 'olControlSLDSelectBox',
+ layers: [layers['waterbodies']],
+ handlerOptions: {sides: 30}
+ }
+ )
+ };
+
+ for(var key in controls) {
+ map.addControl(controls[key]);
+ }
+ }
+
+ function toggleControl(element) {
+ for(var key in controls) {
+ var control = controls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+
+ function toggleSelectionLayer(element) {
+ var selectLayers = [];
+ var elements = element.value.split("_");
+ for (var key in layers) {
+ var layer = layers[key];
+ for (var i=0, len=elements.length; i<len; i++) {
+ var value = elements[i];
+ if (value == key && element.checked) {
+ selectLayers.push(layer);
+ }
+ }
+ }
+ for (var i=0, len=this.map.controls.length; i<len; i++) {
+ var control = this.map.controls[i];
+ if (control instanceof OpenLayers.Control.SLDSelect) {
+ control.setLayers(selectLayers);
+ }
+ }
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">SLD based selection on WMS layers</h1>
+
+ <div id="tags">
+ sld, sldselect, styling, style
+ </div>
+
+ <div id="shortdesc">Using Styled Layer Descriptors to make a selection on WMS layers</div>
+
+ <div id="map" style="width: 512px; height: 256px; border: 1px solid red;"></div>
+
+ <div id="docs">
+ This example uses the OpenLayers.Control.SLDSelect to select features in a WMS
+ layer. The features are highlighted using Styled Layer Descriptors (SLD). The
+ control supports point, box, line and polygon selection modes by configuring the
+ appriopriate handler.
+ </div>
+
+ <div id="controls">
+ <ul id="controlToggle"><b>Map Controls</b>
+ <li>
+ <input type="radio" name="control" value="navigation" id="noneToggle" onclick="toggleControl(this);" CHECKED>
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="control" value="box" id="boxToggle" onclick="toggleControl(this);">
+ <label for="boxToggle">SLD select with box</label>
+ </li>
+ <li>
+ <input type="radio" name="control" value="polygon" id="polygonToggle" onclick="toggleControl(this);">
+ <label for="polygonToggle">SLD select with polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="control" value="line" id="lineToggle" onclick="toggleControl(this);">
+ <label for="lineToggle">SLD select with line</label>
+ </li>
+ <li>
+ <input type="radio" name="control" value="point" id="pointToggle" onclick="toggleControl(this);">
+ <label for="pointToggle">SLD select with point</label>
+ </li>
+ <li>
+ <input type="radio" name="control" value="circle" id="circleToggle" onclick="toggleControl(this);">
+ <label for="circleToggle">SLD select with circle</label>
+ </li>
+ </ul>
+ </div>
+ <div id="layers">
+ <ul id="layerToggle"><b>Selection layer</b>
+ <li>
+ <input type="radio" name="layer" value="waterbodies" id="waterbodiesToggle" onclick="toggleSelectionLayer(this);" CHECKED>
+ <label for="noneToggle">Water bodies</label>
+ </li>
+ <li>
+ <input type="radio" name="layer" value="cities" id="citiesToggle" onclick="toggleSelectionLayer(this);">
+ <label for="citiesToggle">Cities</label>
+ </li>
+ <li>
+ <input type="radio" name="layer" value="roads" id="roadsToggle" onclick="toggleSelectionLayer(this);">
+ <label for="roadsToggle">Roads</label>
+ </li>
+ <li>
+ <input type="radio" name="layer" value="roads_cities" id="roadsCitiesToggle" onclick="toggleSelectionLayer(this);">
+ <label for="roadsCitiesToggle">Roads and cities</label>
+ </li>
+ </ul>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/WMSDescribeLayerParser.html b/misc/openlayers/examples/WMSDescribeLayerParser.html
new file mode 100644
index 0000000..94be309
--- /dev/null
+++ b/misc/openlayers/examples/WMSDescribeLayerParser.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WMSDescribeLayer Parser Example</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function parseData(req) {
+ format = new OpenLayers.Format.WMSDescribeLayer();
+ html = "<br>";
+ resp = format.read(req.responseText);
+ var layerDescriptions = resp.layerDescriptions;
+ for(var i = 0; i < layerDescriptions.length; i++) {
+ html += "Layer: typeName: "+layerDescriptions[i].typeName+",";
+ html += "<ul>";
+ html += "<li>owsURL: "+layerDescriptions[i].owsURL+"</li>";
+ html += "<li>owsType: "+layerDescriptions[i].owsType+"</li>";
+ html += "</ul>"
+ }
+ document.getElementById('output').innerHTML = html;
+ }
+ function load() {
+ OpenLayers.Request.GET({
+ url: "xml/wmsdescribelayer.xml",
+ success: parseData
+ });
+ }
+ </script>
+ </head>
+ <body onload="load()">
+ <h1 id="title">WMSDescribeLayer Parser Example</h1>
+
+ <div id="tags">
+ wmsdescribelayer, parser, cleanup
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate the operation of the WMSDescribeLayer parser.
+ </p>
+
+ <div id="output"></div>
+
+ <div id="docs">
+ <p>This script reads data from a file and parses out the coordinates,
+ appending them to a HTML string with markup tags. This markup is
+ dumped to an element in the page.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/accelerometer.html b/misc/openlayers/examples/accelerometer.html
new file mode 100644
index 0000000..0721519
--- /dev/null
+++ b/misc/openlayers/examples/accelerometer.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Accelerometer Usage</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script type="text/javascript" src="browser.js"></script>
+
+ <style type="text/css">
+ .olControlAttribution {
+ bottom: 5px;
+ }
+ </style>
+ <script type="text/javascript">
+ function init() {
+ if (isEventSupported('deviceorientation', window) || isEventSupported('mozorientation', window) || isEventSupported('devicemotion', window)) {
+ if (window.DeviceOrientationEvent) {
+ window.addEventListener("deviceorientation", function (event) {
+ document.getElementById('resultDeviceOrientation').innerHTML = '';
+ if (typeof(event.alpha) != 'undefined') {
+ document.getElementById('resultDeviceOrientation').innerHTML = document.getElementById('resultDeviceOrientation').innerHTML + "Alpha: " + event.alpha + "<br>";
+ document.getElementById('resultDeviceOrientation').innerHTML = document.getElementById('resultDeviceOrientation').innerHTML + "Beta: " + event.beta + "<br>";
+ document.getElementById('resultDeviceOrientation').innerHTML = document.getElementById('resultDeviceOrientation').innerHTML + "Gamma: " + event.gamma + "<br>";
+ }
+ if (typeof(event.absolute) != 'undefined') {
+ document.getElementById('resultDeviceOrientation').innerHTML = document.getElementById('resultDeviceOrientation').innerHTML + "Gamma: " + event.absolute + "<br>";
+ }
+ if (typeof(event.compassCalibrate) != 'undefined') {
+ document.getElementById('resultDeviceOrientation').innerHTML = document.getElementById('resultDeviceOrientation').innerHTML + "Gamma: " + event.compassCalibrated + "<br>";
+ }
+ }, true);
+ }
+ if (window.DeviceMotionEvent) {
+ window.addEventListener('devicemotion', function (event) {
+ document.getElementById('resultDeviceMotion').innerHTML = '';
+ if (typeof(event.accelerationIncludingGravity) != 'undefined') {
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "accelerationIncludingGravity.x: " + event.accelerationIncludingGravity.x + "<br>";
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "accelerationIncludingGravity.y: " + event.accelerationIncludingGravity.y + "<br>";
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "accelerationIncludingGravity.z: " + event.accelerationIncludingGravity.z + "<br>";
+ }
+ if (typeof(event.acceleration) != 'undefined') {
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "acceleration.x: " + event.acceleration.x + "<br>";
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "acceleration.y: " + event.acceleration.y + "<br>";
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "acceleration.z: " + event.acceleration.z + "<br>";
+ }
+ if (typeof(event.rotationRate) != 'undefined') {
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "rotationRate.alpha: " + event.rotationRate.alpha + "<br>";
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "rotationRate.beta: " + event.rotationRate.beta + "<br>";
+ document.getElementById('resultDeviceMotion').innerHTML = document.getElementById('resultDeviceMotion').innerHTML + "rotationRate.gamma: " + event.rotationRate.gamma + "<br>";
+ }
+ }, true);
+ }
+ if (window.MozOrientation) {
+ window.addEventListener("MozOrientation", function (orientation) {
+ document.getElementById('resultMozOrientation').innerHTML = "orientation.x: " + orientation.x + "<br>";
+ document.getElementById('resultMozOrientation').innerHTML = document.getElementById('resultMozOrientation').innerHTML + "orientation.y: " + orientation.y + "<br>";
+ document.getElementById('resultMozOrientation').innerHTML = document.getElementById('resultMozOrientation').innerHTML + "orientation.z: " + orientation.z + "<br>";
+ }, true);
+ }
+ } else {
+ alert("Unfortunately, your brower doesn't support the orientation usage");
+ }
+
+ }
+ </script>
+</head>
+<body onload="init()">
+<h1 id="title">Accelerometer</h1>
+
+<p id="shortdesc">
+ The goal of this script is to demonstrate the usage of accelerometer.
+</p>
+<p>
+ The orientation specification can be found <a href="http://dev.w3.org/geo/api/spec-source-orientation.html">here</a>.
+</p>
+
+<div id="tags">
+ browser, vendor, mobile, orientation
+</div>
+
+<h1>Device motion</h1>
+
+<div id="resultDeviceMotion">
+
+</div>
+<h1>Device orientation</h1>
+
+<div id="resultDeviceOrientation">
+
+</div>
+<h1>MOZ orientation</h1>
+
+<div id="resultMozOrientation">
+
+</div>
+</body>
+</html>
diff --git a/misc/openlayers/examples/accessible-click-control.html b/misc/openlayers/examples/accessible-click-control.html
new file mode 100644
index 0000000..c8d97cd
--- /dev/null
+++ b/misc/openlayers/examples/accessible-click-control.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Accessible Custom Click Control</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ <style type="text/css">
+ a {
+ text-decoration: none;
+ font-size: 1.2em;
+ }
+ a em {
+ font-style: normal;
+ font-weight: normal;
+ text-decoration: underline;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ a.api {
+ font-size:1em;
+ text-decoration:underline;
+ }
+ a.accesskey {
+ color: white;
+ }
+ a.accesskey:focus {
+ color: #436976;
+ }
+ a.zoom {
+ padding-right: 20px;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="accessible-click-control.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">An accessible click control implementation</h1>
+
+ <div id="tags">
+ click, control, accessibility
+ </div>
+
+ <a class="accesskey"
+ href=""
+ accesskey="1"
+ onclick="document.getElementById('map').focus();return false;">
+ Jump to map
+ </a>
+
+ <div id="map" class="smallmap" tabindex="0"></div>
+
+ <p id="desc">
+ Demonstrate the KeyboardDefaults control as well as a control that
+ allows clicking on the map using the keyboard.
+ First focus the map (using tab key or mouse), then press the 'i'
+ key to activate the query control. You can then move the point
+ using arrow keys. Press 'RETURN' to get the coordinate. Press 'i'
+ again to deactivate the control.
+ </p>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/accessible-click-control.js b/misc/openlayers/examples/accessible-click-control.js
new file mode 100644
index 0000000..328e0da
--- /dev/null
+++ b/misc/openlayers/examples/accessible-click-control.js
@@ -0,0 +1,199 @@
+var map, navigationControl, queryControl;
+
+function init(){
+ map = new OpenLayers.Map('map', {controls: []});
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ map.addLayers([layer]);
+
+ navigationControl = new OpenLayers.Control.KeyboardDefaults({
+ observeElement: 'map'
+ });
+ map.addControl(navigationControl);
+
+ queryControl = new OpenLayers.Control.KeyboardClick({
+ observeElement: 'map'
+ });
+ map.addControl(queryControl);
+
+ map.zoomToMaxExtent();
+}
+
+/**
+ * Class: OpenLayers.Control.KeyboardClick
+ *
+ * A custom control that (a) adds a vector point that can be moved using the
+ * arrow keys of the keyboard, and (b) displays a browser alert window when the
+ * RETURN key is pressed. The control can be activated/deactivated using the
+ * "i" key. When activated the control deactivates any KeyboardDefaults control
+ * in the map so that the map is not moved when the arrow keys are pressed.
+ *
+ * This control relies on the OpenLayers.Handler.KeyboardPoint custom handler.
+ */
+OpenLayers.Control.KeyboardClick = OpenLayers.Class(OpenLayers.Control, {
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ var observeElement = this.observeElement || document;
+ this.handler = new OpenLayers.Handler.KeyboardPoint(this, {
+ done: this.onClick,
+ cancel: this.deactivate
+ }, {
+ observeElement: observeElement
+ });
+ OpenLayers.Event.observe(
+ observeElement,
+ "keydown",
+ OpenLayers.Function.bindAsEventListener(
+ function(evt) {
+ if (evt.keyCode == 73) { // "i"
+ if (this.active) {
+ this.deactivate();
+ } else {
+ this.activate();
+ }
+ }
+ },
+ this
+ )
+ );
+ },
+
+ onClick: function(geometry) {
+ alert("You clicked near " + geometry.x + " N, " +
+ geometry.y + " E");
+ },
+
+ activate: function() {
+ if(!OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ return false;
+ }
+ // deactivate any KeyboardDefaults control
+ var keyboardDefaults = this.map.getControlsByClass(
+ 'OpenLayers.Control.KeyboardDefaults')[0];
+ if (keyboardDefaults) {
+ keyboardDefaults.deactivate();
+ }
+ return true;
+ },
+
+ deactivate: function() {
+ if(!OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ return false;
+ }
+ // reactivate any KeyboardDefaults control
+ var keyboardDefaults = this.map.getControlsByClass(
+ 'OpenLayers.Control.KeyboardDefaults')[0];
+ if (keyboardDefaults) {
+ keyboardDefaults.activate();
+ }
+ return true;
+ }
+});
+
+/**
+ * Class: OpenLayers.Handler.KeyboardPoint
+ *
+ * A custom handler that displays a vector point that can be moved
+ * using the arrow keys of the keyboard.
+ */
+OpenLayers.Handler.KeyboardPoint = OpenLayers.Class(OpenLayers.Handler, {
+
+ KEY_EVENTS: ["keydown"],
+
+
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ // cache the bound event listener method so it can be unobserved later
+ this.eventListener = OpenLayers.Function.bindAsEventListener(
+ this.handleKeyEvent, this
+ );
+ },
+
+ activate: function() {
+ if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ return false;
+ }
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME);
+ this.map.addLayer(this.layer);
+ this.observeElement = this.observeElement || document;
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.observe(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ if(!this.point) {
+ this.createFeature();
+ }
+ return true;
+ },
+
+ deactivate: function() {
+ if (!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ return false;
+ }
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.stopObserving(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ this.map.removeLayer(this.layer);
+ this.destroyFeature();
+ return true;
+ },
+
+ handleKeyEvent: function (evt) {
+ switch(evt.keyCode) {
+ case OpenLayers.Event.KEY_LEFT:
+ this.modifyFeature(-3, 0);
+ break;
+ case OpenLayers.Event.KEY_RIGHT:
+ this.modifyFeature(3, 0);
+ break;
+ case OpenLayers.Event.KEY_UP:
+ this.modifyFeature(0, 3);
+ break;
+ case OpenLayers.Event.KEY_DOWN:
+ this.modifyFeature(0, -3);
+ break;
+ case OpenLayers.Event.KEY_RETURN:
+ this.callback('done', [this.point.geometry.clone()]);
+ break;
+ case OpenLayers.Event.KEY_ESC:
+ this.callback('cancel');
+ break;
+ }
+ },
+
+ modifyFeature: function(lon, lat) {
+ if(!this.point) {
+ this.createFeature();
+ }
+ var resolution = this.map.getResolution();
+ this.point.geometry.x = this.point.geometry.x + lon * resolution;
+ this.point.geometry.y = this.point.geometry.y + lat * resolution;
+ this.callback("modify", [this.point.geometry, this.point, false]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ createFeature: function() {
+ var center = this.map.getCenter();
+ var geometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.callback("create", [this.point.geometry, this.point]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.point], {silent: true});
+ },
+
+ destroyFeature: function() {
+ this.layer.destroyFeatures([this.point]);
+ this.point = null;
+ },
+
+ drawFeature: function() {
+ this.layer.drawFeature(this.point, this.style);
+ }
+});
diff --git a/misc/openlayers/examples/accessible-panel.html b/misc/openlayers/examples/accessible-panel.html
new file mode 100644
index 0000000..d46d4fb
--- /dev/null
+++ b/misc/openlayers/examples/accessible-panel.html
@@ -0,0 +1,130 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Custom and accessible panel</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+
+ .olControlPanel button {
+ position: relative;
+ display: block;
+ margin: 2px;
+ border: 1px solid;
+ padding: 0 5px;
+ border-radius: 4px;
+ height: 35px;
+ background-color: white;
+ float: left;
+ overflow: visible; /* needed to remove padding from buttons in IE */
+ }
+ .olControlPanel button span {
+ padding-left: 25px;
+ }
+ .olControlPanel button span:first-child {
+ padding-left: 0;
+ display: block;
+ position: absolute;
+ left: 2px;
+ }
+ .olControlPanel .olControlDrawFeatureItemActive span:first-child {
+ background-image: url("../theme/default/img/draw_line_on.png");
+ height: 22px;
+ width: 24px;
+ top: 5px;
+ }
+ .olControlPanel .olControlDrawFeatureItemInactive span:first-child {
+ background-image: url("../theme/default/img/draw_line_off.png");
+ height: 22px;
+ width: 24px;
+ top: 5px;
+ }
+ .olControlPanel .olControlZoomBoxItemInactive span:first-child {
+ background-image: url("../img/drag-rectangle-off.png");
+ height: 29px;
+ width: 29px;
+ top: 2px;
+ }
+ .olControlPanel .olControlZoomBoxItemActive span:first-child {
+ background-image: url("../img/drag-rectangle-on.png");
+ height: 29px;
+ width: 29px;
+ top: 2px;
+ }
+ .olControlPanel .olControlZoomToMaxExtentItemInactive span:first-child {
+ background-image: url("../img/zoom-world-mini.png");
+ height: 18px;
+ width: 18px;
+ top: 8px;
+ }
+ .olControlPanel .navHistory span:first-child {
+ background-image: url("../theme/default/img/navigation_history.png");
+ height: 24px;
+ width: 24px;
+ top: 4px;
+ }
+ .olControlPanel .navHistoryPreviousItemActive span:first-child {
+ background-position: 0 0;
+ }
+ .olControlPanel .navHistoryPreviousItemInactive span:first-child {
+ background-position: 0 -24px;
+ }
+ .olControlPanel .navHistoryNextItemActive span:first-child {
+ background-position: -24px 0;
+ }
+ .olControlPanel .navHistoryNextItemInactive span:first-child {
+ background-position: -24px -24px;
+ }
+
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="accessible-panel.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Custom and accessible panel</h1>
+ <div id="tags">
+ panels, CSS, style, accessibility, button
+ </div>
+ <p id="shortdesc">
+ Create a custom and accessible panel, styled entirely with
+ CSS.
+ </p>
+ <div id="panel"></div>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+
+ <p>An accessible panel:
+
+ <ul>
+ <li>The buttons are actual HTML buttons. You can therefore
+ use the TAB key to give the focus to the panel's buttons, and the "ENTER"
+ key to activate or trigger the corresponding control.</li>
+ <li>The buttons include text and titles (displayed when a button
+ is hovered).</li>
+ <li>If you remove colors from the page (for example using FireFox's <a
+ href="https://addons.mozilla.org/en-US/firefox/addon/no-color/">No
+ Color extension</a>) the buttons are still visible, and
+ accessible using the keyboard.</li>
+ </ul>
+ </p>
+
+ <p>By default a panel creates buttons as divs. In this example the
+ <code>createControlMarkup</code> panel function is overridden to create
+ a more accessible markup for the buttons. See the <a
+ href="accessible-panel.js" target="_blank"> accessible-panel.js
+ source</a> to see how this is done.</p>
+
+ <p>Note: in IE 8, when a button is pressed its content shifts by 1 pixel.
+ This is a <a
+ href="http://labs.findsubstance.com/2009/05/21/ie8-form-button-with-background-image-on-click-css-bug/">known
+ IE8 bug</a>, with known workarounds. No workaround is applied in this
+ example though.</p>
+
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/accessible-panel.js b/misc/openlayers/examples/accessible-panel.js
new file mode 100644
index 0000000..f982fc6
--- /dev/null
+++ b/misc/openlayers/examples/accessible-panel.js
@@ -0,0 +1,64 @@
+var lon = 5;
+var lat = 40;
+var zoom = 5;
+var map, layer;
+
+function init() {
+ map = new OpenLayers.Map( 'map', { controls: [] } );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ vlayer = new OpenLayers.Layer.Vector( "Editable" );
+ map.addLayer(vlayer);
+
+ zb = new OpenLayers.Control.ZoomBox({
+ title: "Zoom box: zoom clicking and dragging",
+ text: "Zoom"
+ });
+
+ var panel = new OpenLayers.Control.Panel({
+ defaultControl: zb,
+ createControlMarkup: function(control) {
+ var button = document.createElement('button'),
+ iconSpan = document.createElement('span'),
+ textSpan = document.createElement('span');
+ iconSpan.innerHTML = '&nbsp;';
+ button.appendChild(iconSpan);
+ if (control.text) {
+ textSpan.innerHTML = control.text;
+ }
+ button.appendChild(textSpan);
+ return button;
+ }
+ });
+
+ panel.addControls([
+ zb,
+ new OpenLayers.Control.DrawFeature(vlayer, OpenLayers.Handler.Path,
+ {title:'Draw a feature', text: 'Draw'}),
+ new OpenLayers.Control.ZoomToMaxExtent({
+ title:"Zoom to the max extent",
+ text: "World"
+ })
+ ]);
+
+ nav = new OpenLayers.Control.NavigationHistory({
+ previousOptions: {
+ title: "Go to previous map position",
+ text: "Prev"
+ },
+ nextOptions: {
+ title: "Go to next map position",
+ text: "Next"
+ },
+ displayClass: "navHistory"
+ });
+ // parent control must be added to the map
+ map.addControl(nav);
+ panel.addControls([nav.next, nav.previous]);
+
+ map.addControl(panel);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+}
diff --git a/misc/openlayers/examples/accessible.html b/misc/openlayers/examples/accessible.html
new file mode 100644
index 0000000..36236d5
--- /dev/null
+++ b/misc/openlayers/examples/accessible.html
@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Accessible Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ table {
+ border: 1 px solid white;
+ padding: 0;
+ }
+ td {
+ text-align: center;
+ }
+ a {
+ text-decoration: none;
+ font-size: 1.2em;
+ }
+ a em {
+ font-style: normal;
+ font-weight: normal;
+ text-decoration: underline;
+ }
+ a:hover {
+ text-decoration: underline;
+ }
+ a.api {
+ font-size:1em;
+ text-decoration:underline;
+ }
+ a.accesskey {
+ color: white;
+ }
+ a.accesskey:focus {
+ color: #436976;
+ }
+ a.zoom {
+ padding-right: 20px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map = null;
+ function init(){
+ var options = {
+ controls: [
+ new OpenLayers.Control.KeyboardDefaults({
+ observeElement: 'map'
+ })
+ ]
+ };
+ map = new OpenLayers.Map('map', options);
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'}
+ );
+ map.addLayer(wms);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Accessible Example</h1>
+
+ <div id="tags">
+ keyboard, pan, panning, zoom, zooming, accesskey
+ </div>
+
+ <a class="accesskey"
+ href=""
+ accesskey="1"
+ onclick="document.getElementById('map').focus(); return false;">
+ Go to map
+ </a>
+
+ <p id="shortdesc">
+ Demonstrate the KeyboardDefaults control and how to use links
+ with Access Keys to navigate the map with the keyboard.
+ </p>
+
+ <a class="zoom"
+ href="javascript: void map.zoomIn();"
+ accesskey="i">
+ zoom <em>i</em>n</a>
+ <a class="zoom"
+ href="javascript: void map.zoomOut();"
+ accesskey="o">
+ zoom <em>o</em>ut</a>
+
+ <table>
+ <tbody>
+ <tr>
+ <td>&nbsp;</td>
+ <td>
+ <a href="javascript: void map.pan(0, -map.getSize().h / 4);"
+ accesskey="n">
+ pan <em>n</em>orth
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <a href="javascript: void map.pan(-map.getSize().w / 4, 0);"
+ accesskey="w">
+ pan <em>w</em>est
+ </a>
+ </td>
+ <td id="map" class="smallmap" tabindex="0"></td>
+ <td>
+ <a href="javascript: void map.pan(map.getSize().w / 4, 0);"
+ accesskey="e">
+ pan <em>e</em>ast
+ </a>
+ </td>
+ </tr>
+ <tr>
+ <td>&nbsp;</td>
+ <td>
+ <a href="javascript: void map.pan(0, map.getSize().h / 4);"
+ accesskey="s">
+ pan <em>s</em>outh
+ </a>
+ </td>
+ <td>&nbsp;</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <div id="docs">
+ <p>Navigate the map in one of three ways:</p>
+ <ol>
+ <li>Use Access Key "1" (alt + 1) to focus the map element, and
+ use following keys to pan and zoom:
+ <ul>
+ <li>+ (zoom in)</li>
+ <li>- (zoom out)</li>
+ <li>up-arrow (pan north)</li>
+ <li>down-arrow (pan south)</li>
+ <li>left-arrow (pan east)</li>
+ <li>right-arrow (pan west)</li>
+ </ul>
+ See <a href=http://en.wikipedia.org/wiki/Access_key>wikipedia</a> for
+ more detail about Access Keys.
+ </li>
+ <li>Navigate to pan and zoom links using the "tab" key, and
+ press "enter" to pan and zoom</li>
+ <li>If Access Keys work for links in your browser, use:
+ <ul>
+ <li>i (zoom in)</li>
+ <li>o (zoom out)</li>
+ <li>n (pan north)</li>
+ <li>s (pan south)</li>
+ <li>e (pan east)</li>
+ <li>w (pan west)</li>
+ </ul>
+ </li>
+ </ol>
+ <p>
+
+ This is an example of using alternate methods to control panning and zooming. This approach uses map.pan() and map.zoom(). You'll note that to pan, additional math is necessary along with map.size() in order to set the distance to pan.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/all-overlays-google.html b/misc/openlayers/examples/all-overlays-google.html
new file mode 100644
index 0000000..c05fc0c
--- /dev/null
+++ b/misc/openlayers/examples/all-overlays-google.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers All Overlays with Google and OSM</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="http://maps.google.com/maps/api/js?v=3&amp;sensor=false"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="all-overlays-google.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">All Overlays with Google and OSM</h1>
+ <div id="tags">
+ overlay, baselayer, google, osm, openstreetmap, light
+ </div>
+ <p id="shortdesc">
+ Using the Google and OSM layers as overlays.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ Using the allOverlays property on the map, the first layer added
+ must initially be visible. This example demonstrates the use of
+ a Google layer and an OSM layer treated as overlays.
+ </p><p>
+ See the <a href="all-overlays-google.js" target="_blank">
+ all-overlays-google.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/all-overlays-google.js b/misc/openlayers/examples/all-overlays-google.js
new file mode 100644
index 0000000..f26d3fc
--- /dev/null
+++ b/misc/openlayers/examples/all-overlays-google.js
@@ -0,0 +1,19 @@
+var map;
+
+function init() {
+
+ map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true
+ });
+
+ var osm = new OpenLayers.Layer.OSM();
+ var gmap = new OpenLayers.Layer.Google("Google Streets", {visibility: false});
+
+ // note that first layer must be visible
+ map.addLayers([osm, gmap]);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+
+}
diff --git a/misc/openlayers/examples/all-overlays.html b/misc/openlayers/examples/all-overlays.html
new file mode 100644
index 0000000..49e138e
--- /dev/null
+++ b/misc/openlayers/examples/all-overlays.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>All Overlays Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ maxExtent: new OpenLayers.Bounds(
+ 1549471.9221, 6403610.94, 1550001.32545, 6404015.8
+ )
+ });
+
+ // give the features some style
+ var styles = new OpenLayers.StyleMap({
+ "default": {
+ strokeWidth: 2
+ },
+ "select": {
+ strokeColor: "#0099cc",
+ strokeWidth: 4
+ }
+ });
+
+ // add rules from the above lookup table
+ styles.addUniqueValueRules("default", "RP_TYPE", {
+ 10: {strokeColor: "#000000", strokeWidth: 2},
+ 12: {strokeColor: "#222222", strokeWidth: 2},
+ 14: {strokeColor: "#444444", strokeWidth: 2},
+ 16: {strokeColor: "#666666", strokeWidth: 2},
+ 18: {strokeColor: "#888888", strokeWidth: 2},
+ 19: {strokeColor: "#666666", strokeWidth: 1}
+ });
+
+ var vectors = new OpenLayers.Layer.Vector("Lines", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "data/roads.json",
+ format: new OpenLayers.Format.GeoJSON()
+ }),
+ styleMap: styles
+ });
+
+ map.addLayer(vectors);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Overlays Only Example</h1>
+ <div id="tags">
+ overlay, baselayer, light
+ </div>
+ <p id="shortdesc">
+ Demonstrates a map with overlays only.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ To create a map that allows any draw order with all layer types
+ and lets you set the visibility of any layer independently, set
+ the allOverlays property on the map to true.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/anchor-permalink.html b/misc/openlayers/examples/anchor-permalink.html
new file mode 100644
index 0000000..3905207
--- /dev/null
+++ b/misc/openlayers/examples/anchor-permalink.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <title>AnchorPermalink Example</title>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="anchor-permalink.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">AnchorPermalink Example</h1>
+ <div id="tags">
+ anchor, permalink
+ </div>
+ <p id="shortdesc">
+ Place a permalink in the anchor of the url.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ See the <a href="anchor-permalink.js" target="_blank">anchor-permalink.js
+ source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/anchor-permalink.js b/misc/openlayers/examples/anchor-permalink.js
new file mode 100644
index 0000000..1ad2939
--- /dev/null
+++ b/misc/openlayers/examples/anchor-permalink.js
@@ -0,0 +1,13 @@
+function init() {
+ var map = new OpenLayers.Map({
+ div: "map",
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326"),
+ layers: [
+ new OpenLayers.Layer.OSM()
+ ]
+ });
+ if (!map.getCenter()) map.zoomToMaxExtent();
+
+ map.addControl(new OpenLayers.Control.Permalink({anchor: true}));
+}
diff --git a/misc/openlayers/examples/animated_panning.html b/misc/openlayers/examples/animated_panning.html
new file mode 100644
index 0000000..a07017c
--- /dev/null
+++ b/misc/openlayers/examples/animated_panning.html
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Animated Panning of the Map via map.panTo</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer, running = false;
+
+ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
+ defaultHandlerOptions: {
+ 'single': true,
+ 'delay': 200
+ },
+
+ initialize: function(options) {
+ this.handlerOptions = OpenLayers.Util.extend(
+ {}, this.defaultHandlerOptions
+ );
+ OpenLayers.Control.prototype.initialize.apply(
+ this, arguments
+ );
+ this.handler = new OpenLayers.Handler.Click(
+ this, {
+ 'click': this.onClick
+ }, this.handlerOptions
+ );
+ },
+
+ onClick: function(evt) {
+ map.panTo(map.getLonLatFromPixel(evt.xy));
+ }
+
+ });
+
+ function init(){
+ map = new OpenLayers.Map('map', {numZoomLevels: 2});
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ var click = new OpenLayers.Control.Click();
+ map.addControl(click);
+ click.activate();
+ map.addControl(new OpenLayers.Control.OverviewMap());
+
+ map2 = new OpenLayers.Map('map2', {'panMethod': null, numZoomLevels: 2} );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ map2.addLayer(layer);
+ map2.zoomToMaxExtent();
+ }
+
+ function setCenterInterval() {
+ if (!running) {
+ setCenter();
+ running = setInterval('setCenter()', 500);
+ } else {
+ clearInterval(running);
+ running = false;
+ }
+ }
+
+ function setCenter() {
+ var lon = Math.random() * 360 - 180;
+ var lat = Math.random() * 180 - 90;
+ var lonlat = new OpenLayers.LonLat(lon, lat);
+ map.panTo(lonlat);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">map.panTo Example</h1>
+ <div id="tags">
+ panning, animation, effect, smooth, panMethod
+ </div>
+ <div id="shortdesc">Show animated panning effects in the map</div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This is an example of transition effects. If the new random center is in the current extent, the map will pan smoothly. <br>
+ The random selection will continue until you press it again. Additionally, you can single click in the map to pan smoothly
+ to that area, or use the pan control to pan smoothly.
+ </p>
+ </div>
+ <button onclick="setCenterInterval()">Start/stop random recenter</button>
+ <div id="map2" class="smallmap"></div>
+ <div>
+ <p>To turn off Animated Panning, create a map with an panMethod set to
+ null. </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/animator.js b/misc/openlayers/examples/animator.js
new file mode 100644
index 0000000..5ed0f0c
--- /dev/null
+++ b/misc/openlayers/examples/animator.js
@@ -0,0 +1,670 @@
+/*
+ Animator.js 1.1.9
+
+ This library is released under the BSD license:
+
+ Copyright (c) 2006, Bernard Sumption. All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer. Redistributions in binary
+ form must reproduce the above copyright notice, this list of conditions and
+ the following disclaimer in the documentation and/or other materials
+ provided with the distribution. Neither the name BernieCode nor
+ the names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGE.
+
+*/
+
+
+// Applies a sequence of numbers between 0 and 1 to a number of subjects
+// construct - see setOptions for parameters
+function Animator(options) {
+ this.setOptions(options);
+ var _this = this;
+ this.timerDelegate = function(){_this.onTimerEvent()};
+ this.subjects = [];
+ this.target = 0;
+ this.state = 0;
+ this.lastTime = null;
+};
+Animator.prototype = {
+ // apply defaults
+ setOptions: function(options) {
+ this.options = Animator.applyDefaults({
+ interval: 20, // time between animation frames
+ duration: 400, // length of animation
+ onComplete: function(){},
+ onStep: function(){},
+ transition: Animator.tx.easeInOut
+ }, options);
+ },
+ // animate from the current state to provided value
+ seekTo: function(to) {
+ this.seekFromTo(this.state, to);
+ },
+ // animate from the current state to provided value
+ seekFromTo: function(from, to) {
+ this.target = Math.max(0, Math.min(1, to));
+ this.state = Math.max(0, Math.min(1, from));
+ this.lastTime = new Date().getTime();
+ if (!this.intervalId) {
+ this.intervalId = window.setInterval(this.timerDelegate, this.options.interval);
+ }
+ },
+ // animate from the current state to provided value
+ jumpTo: function(to) {
+ this.target = this.state = Math.max(0, Math.min(1, to));
+ this.propagate();
+ },
+ // seek to the opposite of the current target
+ toggle: function() {
+ this.seekTo(1 - this.target);
+ },
+ // add a function or an object with a method setState(state) that will be called with a number
+ // between 0 and 1 on each frame of the animation
+ addSubject: function(subject) {
+ this.subjects[this.subjects.length] = subject;
+ return this;
+ },
+ // remove all subjects
+ clearSubjects: function() {
+ this.subjects = [];
+ },
+ // forward the current state to the animation subjects
+ propagate: function() {
+ var value = this.options.transition(this.state);
+ for (var i=0; i<this.subjects.length; i++) {
+ if (this.subjects[i].setState) {
+ this.subjects[i].setState(value);
+ } else {
+ this.subjects[i](value);
+ }
+ }
+ },
+ // called once per frame to update the current state
+ onTimerEvent: function() {
+ var now = new Date().getTime();
+ var timePassed = now - this.lastTime;
+ this.lastTime = now;
+ var movement = (timePassed / this.options.duration) * (this.state < this.target ? 1 : -1);
+ if (Math.abs(movement) >= Math.abs(this.state - this.target)) {
+ this.state = this.target;
+ } else {
+ this.state += movement;
+ }
+
+ try {
+ this.propagate();
+ } finally {
+ this.options.onStep.call(this);
+ if (this.target == this.state) {
+ window.clearInterval(this.intervalId);
+ this.intervalId = null;
+ this.options.onComplete.call(this);
+ }
+ }
+ },
+ // shortcuts
+ play: function() {this.seekFromTo(0, 1)},
+ reverse: function() {this.seekFromTo(1, 0)},
+ // return a string describing this Animator, for debugging
+ inspect: function() {
+ var str = "#<Animator:\n";
+ for (var i=0; i<this.subjects.length; i++) {
+ str += this.subjects[i].inspect();
+ }
+ str += ">";
+ return str;
+ }
+};
+// merge the properties of two objects
+Animator.applyDefaults = function(defaults, prefs) {
+ prefs = prefs || {};
+ var prop, result = {};
+ for (prop in defaults) result[prop] = prefs[prop] !== undefined ? prefs[prop] : defaults[prop];
+ return result;
+};
+// make an array from any object
+Animator.makeArray = function(o) {
+ if (o == null) return [];
+ if (!o.length) return [o];
+ var result = [];
+ for (var i=0; i<o.length; i++) result[i] = o[i];
+ return result;
+};
+// convert a dash-delimited-property to a camelCaseProperty (c/o Prototype, thanks Sam!)
+Animator.camelize = function(string) {
+ var oStringList = string.split('-');
+ if (oStringList.length == 1) return oStringList[0];
+
+ var camelizedString = string.indexOf('-') == 0
+ ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1)
+ : oStringList[0];
+
+ for (var i = 1, len = oStringList.length; i < len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+ return camelizedString;
+};
+// syntactic sugar for creating CSSStyleSubjects
+Animator.apply = function(el, style, options) {
+ if (style instanceof Array) {
+ return new Animator(options).addSubject(new CSSStyleSubject(el, style[0], style[1]));
+ }
+ return new Animator(options).addSubject(new CSSStyleSubject(el, style));
+};
+// make a transition function that gradually accelerates. pass a=1 for smooth
+// gravitational acceleration, higher values for an exaggerated effect
+Animator.makeEaseIn = function(a) {
+ return function(state) {
+ return Math.pow(state, a*2);
+ }
+};
+// as makeEaseIn but for deceleration
+Animator.makeEaseOut = function(a) {
+ return function(state) {
+ return 1 - Math.pow(1 - state, a*2);
+ }
+};
+// make a transition function that, like an object with momentum being attracted to a point,
+// goes past the target then returns
+Animator.makeElastic = function(bounces) {
+ return function(state) {
+ state = Animator.tx.easeInOut(state);
+ return ((1-Math.cos(state * Math.PI * bounces)) * (1 - state)) + state;
+ }
+};
+// make an Attack Decay Sustain Release envelope that starts and finishes on the same level
+//
+Animator.makeADSR = function(attackEnd, decayEnd, sustainEnd, sustainLevel) {
+ if (sustainLevel == null) sustainLevel = 0.5;
+ return function(state) {
+ if (state < attackEnd) {
+ return state / attackEnd;
+ }
+ if (state < decayEnd) {
+ return 1 - ((state - attackEnd) / (decayEnd - attackEnd) * (1 - sustainLevel));
+ }
+ if (state < sustainEnd) {
+ return sustainLevel;
+ }
+ return sustainLevel * (1 - ((state - sustainEnd) / (1 - sustainEnd)));
+ }
+};
+// make a transition function that, like a ball falling to floor, reaches the target and/
+// bounces back again
+Animator.makeBounce = function(bounces) {
+ var fn = Animator.makeElastic(bounces);
+ return function(state) {
+ state = fn(state);
+ return state <= 1 ? state : 2-state;
+ }
+};
+
+// pre-made transition functions to use with the 'transition' option
+Animator.tx = {
+ easeInOut: function(pos){
+ return ((-Math.cos(pos*Math.PI)/2) + 0.5);
+ },
+ linear: function(x) {
+ return x;
+ },
+ easeIn: Animator.makeEaseIn(1.5),
+ easeOut: Animator.makeEaseOut(1.5),
+ strongEaseIn: Animator.makeEaseIn(2.5),
+ strongEaseOut: Animator.makeEaseOut(2.5),
+ elastic: Animator.makeElastic(1),
+ veryElastic: Animator.makeElastic(3),
+ bouncy: Animator.makeBounce(1),
+ veryBouncy: Animator.makeBounce(3)
+};
+
+// animates a pixel-based style property between two integer values
+function NumericalStyleSubject(els, property, from, to, units) {
+ this.els = Animator.makeArray(els);
+ if (property == 'opacity' && window.ActiveXObject) {
+ this.property = 'filter';
+ } else {
+ this.property = Animator.camelize(property);
+ }
+ this.from = parseFloat(from);
+ this.to = parseFloat(to);
+ this.units = units != null ? units : 'px';
+}
+NumericalStyleSubject.prototype = {
+ setState: function(state) {
+ var style = this.getStyle(state);
+ var visibility = (this.property == 'opacity' && state == 0) ? 'hidden' : '';
+ var j=0;
+ for (var i=0; i<this.els.length; i++) {
+ try {
+ this.els[i].style[this.property] = style;
+ } catch (e) {
+ // ignore fontWeight - intermediate numerical values cause exeptions in firefox
+ if (this.property != 'fontWeight') throw e;
+ }
+ if (j++ > 20) return;
+ }
+ },
+ getStyle: function(state) {
+ state = this.from + ((this.to - this.from) * state);
+ if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")";
+ if (this.property == 'opacity') return state;
+ return Math.round(state) + this.units;
+ },
+ inspect: function() {
+ return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n";
+ }
+};
+
+// animates a colour based style property between two hex values
+function ColorStyleSubject(els, property, from, to) {
+ this.els = Animator.makeArray(els);
+ this.property = Animator.camelize(property);
+ this.to = this.expandColor(to);
+ this.from = this.expandColor(from);
+ this.origFrom = from;
+ this.origTo = to;
+}
+
+ColorStyleSubject.prototype = {
+ // parse "#FFFF00" to [256, 256, 0]
+ expandColor: function(color) {
+ var hexColor, red, green, blue;
+ hexColor = ColorStyleSubject.parseColor(color);
+ if (hexColor) {
+ red = parseInt(hexColor.slice(1, 3), 16);
+ green = parseInt(hexColor.slice(3, 5), 16);
+ blue = parseInt(hexColor.slice(5, 7), 16);
+ return [red,green,blue]
+ }
+ if (window.DEBUG) {
+ alert("Invalid colour: '" + color + "'");
+ }
+ },
+ getValueForState: function(color, state) {
+ return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state));
+ },
+ setState: function(state) {
+ var color = '#'
+ + ColorStyleSubject.toColorPart(this.getValueForState(0, state))
+ + ColorStyleSubject.toColorPart(this.getValueForState(1, state))
+ + ColorStyleSubject.toColorPart(this.getValueForState(2, state));
+ for (var i=0; i<this.els.length; i++) {
+ this.els[i].style[this.property] = color;
+ }
+ },
+ inspect: function() {
+ return "\t" + this.property + "(" + this.origFrom + " to " + this.origTo + ")\n";
+ }
+};
+
+// return a properly formatted 6-digit hex colour spec, or false
+ColorStyleSubject.parseColor = function(string) {
+ var color = '#', match;
+ if(match = ColorStyleSubject.parseColor.rgbRe.exec(string)) {
+ var part;
+ for (var i=1; i<=3; i++) {
+ part = Math.max(0, Math.min(255, parseInt(match[i])));
+ color += ColorStyleSubject.toColorPart(part);
+ }
+ return color;
+ }
+ if (match = ColorStyleSubject.parseColor.hexRe.exec(string)) {
+ if(match[1].length == 3) {
+ for (var i=0; i<3; i++) {
+ color += match[1].charAt(i) + match[1].charAt(i);
+ }
+ return color;
+ }
+ return '#' + match[1];
+ }
+ return false;
+};
+// convert a number to a 2 digit hex string
+ColorStyleSubject.toColorPart = function(number) {
+ if (number > 255) number = 255;
+ var digits = number.toString(16);
+ if (number < 16) return '0' + digits;
+ return digits;
+};
+ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i;
+ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
+
+// Animates discrete styles, i.e. ones that do not scale but have discrete values
+// that can't be interpolated
+function DiscreteStyleSubject(els, property, from, to, threshold) {
+ this.els = Animator.makeArray(els);
+ this.property = Animator.camelize(property);
+ this.from = from;
+ this.to = to;
+ this.threshold = threshold || 0.5;
+}
+
+DiscreteStyleSubject.prototype = {
+ setState: function(state) {
+ var j=0;
+ for (var i=0; i<this.els.length; i++) {
+ this.els[i].style[this.property] = state <= this.threshold ? this.from : this.to;
+ }
+ },
+ inspect: function() {
+ return "\t" + this.property + "(" + this.from + " to " + this.to + " @ " + this.threshold + ")\n";
+ }
+};
+
+// animates between two styles defined using CSS.
+// if style1 and style2 are present, animate between them, if only style1
+// is present, animate between the element's current style and style1
+function CSSStyleSubject(els, style1, style2) {
+ els = Animator.makeArray(els);
+ this.subjects = [];
+ if (els.length == 0) return;
+ var prop, toStyle, fromStyle;
+ if (style2) {
+ fromStyle = this.parseStyle(style1, els[0]);
+ toStyle = this.parseStyle(style2, els[0]);
+ } else {
+ toStyle = this.parseStyle(style1, els[0]);
+ fromStyle = {};
+ for (prop in toStyle) {
+ fromStyle[prop] = CSSStyleSubject.getStyle(els[0], prop);
+ }
+ }
+ // remove unchanging properties
+ var prop;
+ for (prop in fromStyle) {
+ if (fromStyle[prop] == toStyle[prop]) {
+ delete fromStyle[prop];
+ delete toStyle[prop];
+ }
+ }
+ // discover the type (numerical or colour) of each style
+ var prop, units, match, type, from, to;
+ for (prop in fromStyle) {
+ var fromProp = String(fromStyle[prop]);
+ var toProp = String(toStyle[prop]);
+ if (toStyle[prop] == null) {
+ if (window.DEBUG) alert("No to style provided for '" + prop + '"');
+ continue;
+ }
+
+ if (from = ColorStyleSubject.parseColor(fromProp)) {
+ to = ColorStyleSubject.parseColor(toProp);
+ type = ColorStyleSubject;
+ } else if (fromProp.match(CSSStyleSubject.numericalRe)
+ && toProp.match(CSSStyleSubject.numericalRe)) {
+ from = parseFloat(fromProp);
+ to = parseFloat(toProp);
+ type = NumericalStyleSubject;
+ match = CSSStyleSubject.numericalRe.exec(fromProp);
+ var reResult = CSSStyleSubject.numericalRe.exec(toProp);
+ if (match[1] != null) {
+ units = match[1];
+ } else if (reResult[1] != null) {
+ units = reResult[1];
+ } else {
+ units = reResult;
+ }
+ } else if (fromProp.match(CSSStyleSubject.discreteRe)
+ && toProp.match(CSSStyleSubject.discreteRe)) {
+ from = fromProp;
+ to = toProp;
+ type = DiscreteStyleSubject;
+ units = 0; // hack - how to get an animator option down to here
+ } else {
+ if (window.DEBUG) {
+ alert("Unrecognised format for value of "
+ + prop + ": '" + fromStyle[prop] + "'");
+ }
+ continue;
+ }
+ this.subjects[this.subjects.length] = new type(els, prop, from, to, units);
+ }
+}
+
+CSSStyleSubject.prototype = {
+ // parses "width: 400px; color: #FFBB2E" to {width: "400px", color: "#FFBB2E"}
+ parseStyle: function(style, el) {
+ var rtn = {};
+ // if style is a rule set
+ if (style.indexOf(":") != -1) {
+ var styles = style.split(";");
+ for (var i=0; i<styles.length; i++) {
+ var parts = CSSStyleSubject.ruleRe.exec(styles[i]);
+ if (parts) {
+ rtn[parts[1]] = parts[2];
+ }
+ }
+ }
+ // else assume style is a class name
+ else {
+ var prop, value, oldClass;
+ oldClass = el.className;
+ el.className = style;
+ for (var i=0; i<CSSStyleSubject.cssProperties.length; i++) {
+ prop = CSSStyleSubject.cssProperties[i];
+ value = CSSStyleSubject.getStyle(el, prop);
+ if (value != null) {
+ rtn[prop] = value;
+ }
+ }
+ el.className = oldClass;
+ }
+ return rtn;
+
+ },
+ setState: function(state) {
+ for (var i=0; i<this.subjects.length; i++) {
+ this.subjects[i].setState(state);
+ }
+ },
+ inspect: function() {
+ var str = "";
+ for (var i=0; i<this.subjects.length; i++) {
+ str += this.subjects[i].inspect();
+ }
+ return str;
+ }
+};
+// get the current value of a css property,
+CSSStyleSubject.getStyle = function(el, property){
+ var style;
+ if(document.defaultView && document.defaultView.getComputedStyle){
+ style = document.defaultView.getComputedStyle(el, "").getPropertyValue(property);
+ if (style) {
+ return style;
+ }
+ }
+ property = Animator.camelize(property);
+ if(el.currentStyle){
+ style = el.currentStyle[property];
+ }
+ return style || el.style[property]
+};
+
+
+CSSStyleSubject.ruleRe = /^\s*([a-zA-Z\-]+)\s*:\s*(\S(.+\S)?)\s*$/;
+CSSStyleSubject.numericalRe = /^-?\d+(?:\.\d+)?(%|[a-zA-Z]{2})?$/;
+CSSStyleSubject.discreteRe = /^\w+$/;
+
+// required because the style object of elements isn't enumerable in Safari
+/*
+CSSStyleSubject.cssProperties = ['background-color','border','border-color','border-spacing',
+'border-style','border-top','border-right','border-bottom','border-left','border-top-color',
+'border-right-color','border-bottom-color','border-left-color','border-top-width','border-right-width',
+'border-bottom-width','border-left-width','border-width','bottom','color','font-size','font-size-adjust',
+'font-stretch','font-style','height','left','letter-spacing','line-height','margin','margin-top',
+'margin-right','margin-bottom','margin-left','marker-offset','max-height','max-width','min-height',
+'min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding',
+'padding-top','padding-right','padding-bottom','padding-left','quotes','right','size','text-indent',
+'top','width','word-spacing','z-index','opacity','outline-offset'];*/
+
+
+CSSStyleSubject.cssProperties = ['azimuth','background','background-attachment','background-color','background-image','background-position','background-repeat','border-collapse','border-color','border-spacing','border-style','border-top','border-top-color','border-right-color','border-bottom-color','border-left-color','border-top-style','border-right-style','border-bottom-style','border-left-style','border-top-width','border-right-width','border-bottom-width','border-left-width','border-width','bottom','clear','clip','color','content','cursor','direction','display','elevation','empty-cells','css-float','font','font-family','font-size','font-size-adjust','font-stretch','font-style','font-variant','font-weight','height','left','letter-spacing','line-height','list-style','list-style-image','list-style-position','list-style-type','margin','margin-top','margin-right','margin-bottom','margin-left','max-height','max-width','min-height','min-width','orphans','outline','outline-color','outline-style','outline-width','overflow','padding','padding-top','padding-right','padding-bottom','padding-left','pause','position','right','size','table-layout','text-align','text-decoration','text-indent','text-shadow','text-transform','top','vertical-align','visibility','white-space','width','word-spacing','z-index','opacity','outline-offset','overflow-x','overflow-y'];
+
+
+// chains several Animator objects together
+function AnimatorChain(animators, options) {
+ this.animators = animators;
+ this.setOptions(options);
+ for (var i=0; i<this.animators.length; i++) {
+ this.listenTo(this.animators[i]);
+ }
+ this.forwards = false;
+ this.current = 0;
+}
+
+AnimatorChain.prototype = {
+ // apply defaults
+ setOptions: function(options) {
+ this.options = Animator.applyDefaults({
+ // by default, each call to AnimatorChain.play() calls jumpTo(0) of each animator
+ // before playing, which can cause flickering if you have multiple animators all
+ // targeting the same element. Set this to false to avoid this.
+ resetOnPlay: true
+ }, options);
+ },
+ // play each animator in turn
+ play: function() {
+ this.forwards = true;
+ this.current = -1;
+ if (this.options.resetOnPlay) {
+ for (var i=0; i<this.animators.length; i++) {
+ this.animators[i].jumpTo(0);
+ }
+ }
+ this.advance();
+ },
+ // play all animators backwards
+ reverse: function() {
+ this.forwards = false;
+ this.current = this.animators.length;
+ if (this.options.resetOnPlay) {
+ for (var i=0; i<this.animators.length; i++) {
+ this.animators[i].jumpTo(1);
+ }
+ }
+ this.advance();
+ },
+ // if we have just play()'d, then call reverse(), and vice versa
+ toggle: function() {
+ if (this.forwards) {
+ this.seekTo(0);
+ } else {
+ this.seekTo(1);
+ }
+ },
+ // internal: install an event listener on an animator's onComplete option
+ // to trigger the next animator
+ listenTo: function(animator) {
+ var oldOnComplete = animator.options.onComplete;
+ var _this = this;
+ animator.options.onComplete = function() {
+ if (oldOnComplete) oldOnComplete.call(animator);
+ _this.advance();
+ }
+ },
+ // play the next animator
+ advance: function() {
+ if (this.forwards) {
+ if (this.animators[this.current + 1] == null) return;
+ this.current++;
+ this.animators[this.current].play();
+ } else {
+ if (this.animators[this.current - 1] == null) return;
+ this.current--;
+ this.animators[this.current].reverse();
+ }
+ },
+ // this function is provided for drop-in compatibility with Animator objects,
+ // but only accepts 0 and 1 as target values
+ seekTo: function(target) {
+ if (target <= 0) {
+ this.forwards = false;
+ this.animators[this.current].seekTo(0);
+ } else {
+ this.forwards = true;
+ this.animators[this.current].seekTo(1);
+ }
+ }
+};
+
+// an Accordion is a class that creates and controls a number of Animators. An array of elements is passed in,
+// and for each element an Animator and a activator button is created. When an Animator's activator button is
+// clicked, the Animator and all before it seek to 0, and all Animators after it seek to 1. This can be used to
+// create the classic Accordion effect, hence the name.
+// see setOptions for arguments
+function Accordion(options) {
+ this.setOptions(options);
+ var selected = this.options.initialSection, current;
+ if (this.options.rememberance) {
+ current = document.location.hash.substring(1);
+ }
+ this.rememberanceTexts = [];
+ this.ans = [];
+ var _this = this;
+ for (var i=0; i<this.options.sections.length; i++) {
+ var el = this.options.sections[i];
+ var an = new Animator(this.options.animatorOptions);
+ var from = this.options.from + (this.options.shift * i);
+ var to = this.options.to + (this.options.shift * i);
+ an.addSubject(new NumericalStyleSubject(el, this.options.property, from, to, this.options.units));
+ an.jumpTo(0);
+ var activator = this.options.getActivator(el);
+ activator.index = i;
+ activator.onclick = function(){_this.show(this.index)};
+ this.ans[this.ans.length] = an;
+ this.rememberanceTexts[i] = activator.innerHTML.replace(/\s/g, "");
+ if (this.rememberanceTexts[i] === current) {
+ selected = i;
+ }
+ }
+ this.show(selected);
+}
+
+Accordion.prototype = {
+ // apply defaults
+ setOptions: function(options) {
+ this.options = Object.extend({
+ // REQUIRED: an array of elements to use as the accordion sections
+ sections: null,
+ // a function that locates an activator button element given a section element.
+ // by default it takes a button id from the section's "activator" attibute
+ getActivator: function(el) {return document.getElementById(el.getAttribute("activator"))},
+ // shifts each animator's range, for example with options {from:0,to:100,shift:20}
+ // the animators' ranges will be 0-100, 20-120, 40-140 etc.
+ shift: 0,
+ // the first page to show
+ initialSection: 0,
+ // if set to true, document.location.hash will be used to preserve the open section across page reloads
+ rememberance: true,
+ // constructor arguments to the Animator objects
+ animatorOptions: {}
+ }, options || {});
+ },
+ show: function(section) {
+ for (var i=0; i<this.ans.length; i++) {
+ this.ans[i].seekTo(i > section ? 1 : 0);
+ }
+ if (this.options.rememberance) {
+ document.location.hash = this.rememberanceTexts[section];
+ }
+ }
+};
diff --git a/misc/openlayers/examples/arcgis93rest.html b/misc/openlayers/examples/arcgis93rest.html
new file mode 100644
index 0000000..67a96da
--- /dev/null
+++ b/misc/openlayers/examples/arcgis93rest.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ var layer;
+
+ function init(){
+ var mapOptions = {
+ maxExtent: new OpenLayers.Bounds(-174,18.4,-63.5,71),
+ maxResolution: 0.25,
+ projection: "EPSG:4326"};
+ map = new OpenLayers.Map( 'map', mapOptions );
+ layer = new OpenLayers.Layer.ArcGIS93Rest( "ArcGIS Server Layer",
+ "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer/export",
+ {layers: "show:0,2"});
+ map.addLayer(layer);
+
+ map.addControl( new OpenLayers.Control.MousePosition() );
+
+ map.setCenter(new OpenLayers.LonLat(-115, 45), 0);
+ }
+
+ function enableFilter() {
+ layer.setLayerFilter(2, "STATE_NAME LIKE '%" + document.getElementById('filterValueField').value + "%'");
+ layer.redraw();
+ }
+ function disableFilter() {
+ layer.setLayerFilter(2, null);
+ layer.redraw();
+ }
+ function updateButton() {
+ document.getElementById('filterButton').value = "Show '" +
+ document.getElementById('filterValueField').value + "' States";
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">ArcGIS Server 9.3 Rest API Example</h1>
+
+ <div id="tags">
+ ESRI, ArcGIS, REST, filter
+ </div>
+ <p id="shortdesc">
+ Shows the basic use of openlayers using an ArcGIS Server 9.3 Rest API layer
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ This is an example of how to add an ArcGIS Server 9.3 Rest API layer to the OpenLayers window.
+ </div>
+ <input id="filterValueField" type="textfield" value="A"/>
+ <input id="filterButton" type="button" onclick="enableFilter();" value="Filter States"/>
+ <input type="button" onclick="disableFilter();" value="Show All States"/>
+ <br>
+ (Filter is case sensitive.)
+ </body>
+</html>
+
+
+
+
diff --git a/misc/openlayers/examples/arcgiscache_ags.html b/misc/openlayers/examples/arcgiscache_ags.html
new file mode 100644
index 0000000..2b92954
--- /dev/null
+++ b/misc/openlayers/examples/arcgiscache_ags.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers ArcGIS Cache Example (MapServer Access)</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="../lib/OpenLayers/Layer/ArcGISCache.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ var map,
+ cacheLayer,
+ testLayer,
+ //This layer requires meta data about the ArcGIS service. Typically you should use a
+ //JSONP call to get this dynamically. For this example, we are just going to hard-code
+ //an example that we got from here (yes, it's very big):
+ // http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer?f=json&pretty=true
+ layerInfo = {
+ "currentVersion" : 10.01,
+ "serviceDescription" : "This worldwide street map presents highway-level data for the world and street-level data for the United States, Canada, Japan, Southern Africa, and a number of countries in Europe and elsewhere. This comprehensive street map includes highways, major roads, minor roads, railways, water features, administrative boundaries, cities, parks, and landmarks, overlaid on shaded relief imagery for added context. The street map was developed by ESRI using ESRI basemap data, AND road data, USGS elevation data, and UNEP-WCMC parks and protected areas for the world, and Tele Atlas Dynamap® and Multinet® street data for North America and Europe. Coverage for street-level data in Europe includes Andorra, Austria, Belgium, Czech Republic, Denmark, France, Germany, Great Britain, Greece, Hungary, Ireland, Italy, Luxembourg, Netherlands, Northern Ireland (Belfast only), Norway, Poland, Portugal, San Marino, Slovakia, Spain, Sweden, and Switzerland. Coverage for street-level data elsewhere in the world includes China (Hong Kong only), Colombia, Egypt (Cairo only), Indonesia (Jakarta only), Japan, Mexico (Mexico City only), Russia (Moscow and St. Petersburg only), South Africa, Thailand, and Turkey (Istanbul and Ankara only). For more information on this map, visit us \u003ca href=\"http://goto.arcgisonline.com/maps/World_Street_Map \" target=\"_new\"\u003eonline\u003c/a\u003e.",
+ "mapName" : "Layers",
+ "description" : "This worldwide street map presents highway-level data for the world and street-level data for the United States, Canada, Japan, Southern Africa, most countries in Europe, and several other countries. This comprehensive street map includes highways, major roads, minor roads, one-way arrow indicators, railways, water features, administrative boundaries, cities, parks, and landmarks, overlaid on shaded relief imagery for added context. The map also includes building footprints for selected areas in the United States and Europe and parcel boundaries for much of the lower 48 states.\n\nThe street map was developed by ESRI using ESRI basemap data, DeLorme base map layers, AND road data, USGS elevation data, UNEP-WCMC parks and protected areas for the world, Tele Atlas Dynamap® and Multinet® street data for North America and Europe, and First American parcel data for the United States. Coverage for street-level data in Europe includes Andorra, Austria, Belgium, Czech Republic, Denmark, France, Germany, Great Britain, Greece, Hungary, Ireland, Italy, Luxembourg, Netherlands, Norway, Poland, Portugal, San Marino, Slovakia, Spain, Sweden, and Switzerland. Coverage for street-level data elsewhere in the world includes China (Hong Kong only), Colombia, Egypt (Cairo only), Indonesia (Jakarta only), Japan, Mexico, Russia, South Africa, Thailand, and Turkey (Istanbul and Ankara only). For more information on this map, visit us online at http://goto.arcgisonline.com/maps/World_Street_Map\n",
+ "copyrightText" : "Sources: ESRI, DeLorme, AND, Tele Atlas, First American, ESRI Japan, UNEP-WCMC, USGS, METI, ESRI Hong Kong, ESRI Thailand, Procalculo Prosis",
+ "layers" : [
+ {
+ "id" : 0,
+ "name" : "World Street Map",
+ "parentLayerId" : -1,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 0
+ }
+ ],
+ "tables" : [
+
+ ],
+ "spatialReference" : {
+ "wkid" : 102100
+ },
+ "singleFusedMapCache" : true,
+ "tileInfo" : {
+ "rows" : 256,
+ "cols" : 256,
+ "dpi" : 96,
+ "format" : "JPEG",
+ "compressionQuality" : 90,
+ "origin" : {
+ "x" : -20037508.342787,
+ "y" : 20037508.342787
+ },
+ "spatialReference" : {
+ "wkid" : 102100
+ },
+ "lods" : [
+ {"level" : 0, "resolution" : 156543.033928, "scale" : 591657527.591555},
+ {"level" : 1, "resolution" : 78271.5169639999, "scale" : 295828763.795777},
+ {"level" : 2, "resolution" : 39135.7584820001, "scale" : 147914381.897889},
+ {"level" : 3, "resolution" : 19567.8792409999, "scale" : 73957190.948944},
+ {"level" : 4, "resolution" : 9783.93962049996, "scale" : 36978595.474472},
+ {"level" : 5, "resolution" : 4891.96981024998, "scale" : 18489297.737236},
+ {"level" : 6, "resolution" : 2445.98490512499, "scale" : 9244648.868618},
+ {"level" : 7, "resolution" : 1222.99245256249, "scale" : 4622324.434309},
+ {"level" : 8, "resolution" : 611.49622628138, "scale" : 2311162.217155},
+ {"level" : 9, "resolution" : 305.748113140558, "scale" : 1155581.108577},
+ {"level" : 10, "resolution" : 152.874056570411, "scale" : 577790.554289},
+ {"level" : 11, "resolution" : 76.4370282850732, "scale" : 288895.277144},
+ {"level" : 12, "resolution" : 38.2185141425366, "scale" : 144447.638572},
+ {"level" : 13, "resolution" : 19.1092570712683, "scale" : 72223.819286},
+ {"level" : 14, "resolution" : 9.55462853563415, "scale" : 36111.909643},
+ {"level" : 15, "resolution" : 4.77731426794937, "scale" : 18055.954822},
+ {"level" : 16, "resolution" : 2.38865713397468, "scale" : 9027.977411},
+ {"level" : 17, "resolution" : 1.19432856685505, "scale" : 4513.988705}
+ ]
+ },
+ "initialExtent" : {
+ "xmin" : -20037507.0671618,
+ "ymin" : -20037507.0671618,
+ "xmax" : 20037507.0671618,
+ "ymax" : 20037507.0671619,
+ "spatialReference" : {
+ "wkid" : 102100
+ }
+ },
+ "fullExtent" : {
+ "xmin" : -20037507.0671618,
+ "ymin" : -19971868.8804086,
+ "xmax" : 20037507.0671618,
+ "ymax" : 19971868.8804086,
+ "spatialReference" : {
+ "wkid" : 102100
+ }
+ },
+ "units" : "esriMeters",
+ "supportedImageFormatTypes" : "PNG24,PNG,JPG,DIB,TIFF,EMF,PS,PDF,GIF,SVG,SVGZ,AI,BMP",
+ "documentInfo" : {
+ "Title" : "World Street Map",
+ "Author" : "ESRI",
+ "Comments" : "",
+ "Subject" : "streets, highways, major roads, railways, water features, administrative boundaries, cities, parks, protected areas, landmarks ",
+ "Category" : "transportation(Transportation Networks) ",
+ "Keywords" : "World, Global, 2009, Japan, UNEP-WCMC",
+ "Credits" : ""
+ },
+ "capabilities" : "Map"
+ };
+
+ function init(){
+ //The max extent for spherical mercator
+ var maxExtent = new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34);
+
+ //Max extent from layerInfo above
+ var layerMaxExtent = new OpenLayers.Bounds(
+ layerInfo.fullExtent.xmin,
+ layerInfo.fullExtent.ymin,
+ layerInfo.fullExtent.xmax,
+ layerInfo.fullExtent.ymax
+ );
+
+ var resolutions = [];
+ for (var i=0; i<layerInfo.tileInfo.lods.length; i++) {
+ resolutions.push(layerInfo.tileInfo.lods[i].resolution);
+ }
+
+ map = new OpenLayers.Map('map', {
+ maxExtent: maxExtent,
+ StartBounds: layerMaxExtent,
+ units: (layerInfo.units == "esriFeet") ? 'ft' : 'm',
+ resolutions: resolutions,
+ tileSize: new OpenLayers.Size(layerInfo.tileInfo.width, layerInfo.tileInfo.height),
+ projection: 'EPSG:' + layerInfo.spatialReference.wkid
+ });
+
+
+
+ cacheLayer = new OpenLayers.Layer.ArcGISCache( "AGSCache",
+ "http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer", {
+ isBaseLayer: true,
+
+ //From layerInfo above
+ resolutions: resolutions,
+ tileSize: new OpenLayers.Size(layerInfo.tileInfo.cols, layerInfo.tileInfo.rows),
+ tileOrigin: new OpenLayers.LonLat(layerInfo.tileInfo.origin.x , layerInfo.tileInfo.origin.y),
+ maxExtent: layerMaxExtent,
+ projection: 'EPSG:' + layerInfo.spatialReference.wkid
+ });
+
+
+ // create Google Mercator layers
+ testLayer = new OpenLayers.Layer.Google(
+ "Google Streets",
+ {'sphericalMercator': true}
+ );
+
+ map.addLayers([testLayer, cacheLayer]);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl( new OpenLayers.Control.MousePosition() );
+
+ map.zoomToExtent(new OpenLayers.Bounds(-8341644, 4711236, -8339198, 4712459));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers ArcGIS Cache Example (MapServer Access)</h1>
+
+ <div id="tags">
+ arcgis, arcgiscache, cache, tms
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the basic initialization of the ArcGIS Cache layer using a prebuilt configuration, and standard tile access.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example demonstrates using the ArcGISCache layer for
+ accessing ESRI's ArcGIS Server (AGS) Map Cache tiles through
+ an AGS MapServer. Toggle the visibility of the AGS layer to
+ demonstrate how the two maps are lined up correctly.</p>
+
+ <h2>Notes on this layer</h2>
+ <p>A few attempts have been made at this kind of layer before. See
+ <a href="http://trac.osgeo.org/openlayers/ticket/1967">here</a> and
+ <a href="http://trac.osgeo.org/openlayers/browser/sandbox/tschaub/arcgiscache/lib/OpenLayers/Layer/ArcGISCache.js">here</a>.
+ A problem the users encounter is that the tiles seem to "jump around".
+ This is due to the fact that the max extent for the cached layer actually
+ changes at each zoom level due to the way these caches are constructed.
+ We have attempted to use the resolutions, tile size, and tile origin
+ from the cache meta data to make the appropriate changes to the max extent
+ of the tile to compensate for this behavior.</p>
+ You will need to know:
+ <ul>
+ <li>Max Extent: The max extent of the layer</li>
+ <li>Resolutions: An array of resolutions, one for each zoom level</li>
+ <li>Tile Origin: The location of the tile origin for the cache in the upper left.</li>
+ <li>Tile Size: The size of each tile in the cache. Commonly 256 x 256</li>
+ </ul>
+ <p>It's important that you set the correct values in your layer, and these
+ values will differ from layer to layer. You can find these values for your
+ layer in a metadata page in ArcGIS Server.
+ (ie. <a href="http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer">http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer</a>)</p>
+ <ul>
+ <li>Max Extent: Full Extent</li>
+ <li>Resolutions: Tile Info -> Levels of Detail -> Resolution</li>
+ <li>Tile Origin: Origin -> X,Y</li>
+ <li>Tile Size: Tile Info -> Height,Width</li>
+ </ul>
+
+ <h2> Other Examples </h2>
+ <p>This is one of three examples for this layer. You can also configure this
+ layer to use <a href="arcgiscache_direct.html">prebuilt tiles in a file store
+ (not a live server).</a> It is also possible to let this
+ <a href="arcgiscache_jsonp.html">layer 'auto-configure' itself using the
+ capabilities json object from the server itself when using a live ArcGIS server.</a>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/arcgiscache_direct.html b/misc/openlayers/examples/arcgiscache_direct.html
new file mode 100644
index 0000000..472a480
--- /dev/null
+++ b/misc/openlayers/examples/arcgiscache_direct.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>ArcGIS Server Map Cache Example (Direct Access)</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script src="../lib/OpenLayers/Layer/ArcGISCache.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ /* First 4 variables extracted from conf.xml file */
+
+ /* Tile layers & map MUST have same projection */
+ var proj='EPSG:26915';
+
+
+ /* Layer can also accept serverResolutions array
+ * to deal with situation in which layer resolution array & map resolution
+ * array are out of sync*/
+ var mapResolutions = [33.0729828126323,16.9333672000677,8.46668360003387,4.23334180001693,2.11667090000847,1.05833545000423];
+
+ /* For this example this next line is not really needed, 256x256 is default.
+ * However, you would need to change this if your layer had different tile sizes */
+ var tileSize = new OpenLayers.Size(256,256);
+
+ /* Tile Origin is required unless it is the same as the implicit map origin
+ * which can be affected by several variables including maxExtent for map or base layer */
+ var agsTileOrigin = new OpenLayers.LonLat(-5120900,9998100);
+
+ /* This can really be any valid bounds that the map would reasonably be within */
+ /* var mapExtent = new OpenLayers.Bounds(293449.454286,4307691.661132,314827.830376,4323381.484178); */
+ var mapExtent = new OpenLayers.Bounds(289310.8204,4300021.937,314710.8712,4325421.988);
+
+ var aerialsUrl = 'http://serverx.esri.com/arcgiscache/dgaerials/Layers/_alllayers';
+ var roadsUrl = 'http://serverx.esri.com/arcgiscache/DG_County_roads_yesA_backgroundDark/Layers/_alllayers';
+
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map', {
+ maxExtent:mapExtent,
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.LayerSwitcher(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.MousePosition()]
+ });
+
+ var baseLayer = new OpenLayers.Layer.ArcGISCache('Aerials', aerialsUrl, {
+ tileOrigin: agsTileOrigin,
+ resolutions: mapResolutions,
+ sphericalMercator: true,
+ maxExtent: mapExtent,
+ useArcGISServer: false,
+ isBaseLayer: true,
+ type: 'jpg',
+ projection: proj
+ });
+ var overlayLayer = new OpenLayers.Layer.ArcGISCache('Roads', roadsUrl, {
+ tileOrigin: agsTileOrigin,
+ resolutions: mapResolutions,
+ sphericalMercator: true,
+ maxExtent: mapExtent,
+ useArcGISServer: false,
+ isBaseLayer: false,
+ projection: proj
+ });
+ map.addLayers([baseLayer, overlayLayer]);
+
+ //map.zoomToExtent(new OpenLayers.Bounds(295892.34, 4308521.69, 312825.71, 4316988.37));
+ map.zoomToExtent(new OpenLayers.Bounds(-8341644, 4711236, -8339198, 4712459));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">ArcGIS Server Map Cache Example (Direct Access)</h1>
+
+ <div id="tags">
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the basic initialization of the ArcGIS Cache layer using a prebuilt configuration, and direct tile access from a file store.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example demonstrates using the ArcGISCache layer for
+ accessing ESRI's ArcGIS Server (AGS) Map Cache tiles directly
+ via the folder structure and HTTP. Toggle the visibility of the AGS layer to
+ demonstrate how the two maps are lined up correctly.</p>
+
+ <h2>Notes on this Layer</h2>
+ <p>It's important that you set the correct values in your layer, and these
+ values will differ between tile sets. You can find these values for your
+ layer in conf.xml at the root of your cache.
+ (ie. <a href="http://serverx.esri.com/arcgiscache/dgaerials/Layers/conf.xml">http://serverx.esri.com/arcgiscache/dgaerials/Layers/conf.xml</a>)</p>
+
+ <p>For fused map caches this is often http:<i>ServerName</i>/arcgiscache/<i>MapServiceName</i>/Layers <br>
+ For individual layer caches this is often http:<i>ServerName</i>/arcgiscache/<i>LayerName</i>/Layers </p>
+
+ <h2> Other Examples </h2>
+ <p>This is one of three examples for this layer. You can also configure this
+ layer to use <a href="arcgiscache_ags.html">prebuilt tiles from a live server.</a> It is also
+ possible to let this <a href="arcgiscache_jsonp.html">layer 'auto-configure' itself using the capabilities json object from the server itself when using a live ArcGIS server.</a>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/arcgiscache_jsonp.html b/misc/openlayers/examples/arcgiscache_jsonp.html
new file mode 100644
index 0000000..5a92427
--- /dev/null
+++ b/misc/openlayers/examples/arcgiscache_jsonp.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers ArcGIS Cache Example (Autoconfigure with JSONP)</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+
+ <script src="../lib/OpenLayers.js"></script>
+
+ <script type="text/javascript">
+ var map,
+ layerURL = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";
+
+ function init() {
+ var jsonp = new OpenLayers.Protocol.Script();
+ jsonp.createRequest(layerURL, {
+ f: 'json',
+ pretty: 'true'
+ }, initMap);
+ }
+
+ function initMap(layerInfo){
+ /*
+ * The initialize function in this layer has the ability to automatically configure
+ * itself if given the JSON capabilities object from the ArcGIS map server.
+ * This hugely simplifies setting up a new layer, and switching basemaps when using this technique.
+ *
+ * see the 'initialize' function in ArcGISCache.js, or
+ * see the other two ArcGISCache.js examples for direct manual configuration options
+ *
+ */
+ var baseLayer = new OpenLayers.Layer.ArcGISCache("AGSCache", layerURL, {
+ layerInfo: layerInfo
+ });
+
+ /*
+ * Make sure our baselayer and our map are synced up
+ */
+ map = new OpenLayers.Map('map', {
+ maxExtent: baseLayer.maxExtent,
+ units: baseLayer.units,
+ resolutions: baseLayer.resolutions,
+ numZoomLevels: baseLayer.numZoomLevels,
+ tileSize: baseLayer.tileSize,
+ displayProjection: baseLayer.displayProjection
+ });
+ map.addLayers([baseLayer]);
+
+
+ //overlay test layer
+ //http://openlayers.org/dev/examples/web-mercator.html
+ var wms = new OpenLayers.Layer.WMS("Highways",
+ "http://sampleserver1.arcgisonline.com/arcgis/services/Specialty/ESRI_StateCityHighway_USA/MapServer/WMSServer",
+ {layers: "2", format: "image/gif", transparent: "true"},
+ { isBaseLayer: false, wrapDateLine: false }
+ );
+ map.addLayers([wms]);
+
+
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition() );
+ //map.zoomToExtent(new OpenLayers.Bounds(-8341644, 4711236, -8339198, 4712459));
+ map.zoomToExtent(new OpenLayers.Bounds(-8725663.6225564, 4683718.6735907, -8099491.4868444, 4996804.7414467));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers ArcGIS Cache Example (Autoconfigure with JSONP)</h1>
+
+ <div id="tags">
+ arcgis, arcgiscache, cache, tms, jsonp
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the basic initialization of the ArcGIS Cache layer by using the server capabilities object.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example demonstrates using the ArcGISCache layer for
+ accessing ESRI's ArcGIS Server (AGS) Map Cache tiles normally through
+ a live AGS MapServer. Toggle the visibility of the overlay to
+ demonstrate how the two layers are lined up correctly.</p>
+
+ <h2>Notes on this Layer</h2>
+ <p>
+ This method automatically configures the layer using the capabilities object
+ generated by the server itself. This page shows how to construct the url for the server capabilities object,
+ retrieve it using JSONP, and pass it in during construction. Note that in this case,
+ the layer is constructed before the map. This approach greatly simplifies the
+ configuration of your map, and works best when all your tiles / overlays are similarly laid out.
+ If you are using a live AGS map server for your layer, it can be helpful to check your
+ server configuration using this technique before trying one of the other examples for this layer.
+ </p>
+
+ <h2> Other Examples </h2>
+ <p>This is one of three examples for this layer. You can also configure this
+ layer to use <a href="arcgiscache_direct.html">prebuilt tiles in a file store (not a live server).</a>
+ As well a retrieve <a href="arcgiscache_ags.html">tiles from a live server.</a>
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/arcims-thematic.html b/misc/openlayers/examples/arcims-thematic.html
new file mode 100644
index 0000000..7f21d13
--- /dev/null
+++ b/misc/openlayers/examples/arcims-thematic.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>ArcIMS Thematic Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 0;
+ var lat = 0;
+ var zoom = 1;
+ var map, layer;
+ var query, renderer;
+
+ function init() {
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+ map = new OpenLayers.Map('map');
+
+ query = {where: "FIPS_ID>100 AND FIPS_ID<200"};
+
+ renderer = {
+ type: 'valuemap',
+ lookupfield: 'FIPS_ID',
+ ranges: [
+ { lower: 100, upper: 120, symbol: { type: 'simplepolygon', fillcolor: '255,0,0' } },
+ { lower: 120, upper: 140, symbol: { type: 'simplepolygon', fillcolor: '255,255,0' } },
+ { lower: 140, upper: 160, symbol: { type: 'simplepolygon', fillcolor: '0,255,0' } },
+ { lower: 160, upper: 180, symbol: { type: 'simplepolygon', fillcolor: '0,255,255' } },
+ { lower: 180, upper: 200, symbol: { type: 'simplepolygon', fillcolor: '0,0,255' } }
+ ]
+ };
+
+ var options = {
+ layers: [{
+ id: "1",
+ visible: true,
+ query: query,
+ renderer: renderer
+ }],
+ serviceName: "OpenLayers_Sample",
+ singleTile: true,
+ async: true
+ };
+
+ layer = new OpenLayers.Layer.ArcIMS(
+ "Global Sample Map",
+ "http://sample.azavea.com/servlet/com.esri.esrimap.Esrimap",
+ options
+ );
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">ArcIMS Thematic Example</h1>
+
+ <div id="tags">
+ ESRI, ArcIMS, ArcXML, style, thematic, chloropleth, representation
+ </div>
+ <p id="shortdesc">
+ Shows the advanced use of OpenLayers using a thematic ArcIMS layer
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This is an example of how to add an ArcIMS layer to an OpenLayers map.</p>
+
+ <p>Following the ArcXML convention to create a thematic (or chloropleth) map,
+ a layer definition is created with a query and a renderer to select portions
+ of the map data, and change their representation in the generated map tiles.</p>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/arcims.html b/misc/openlayers/examples/arcims.html
new file mode 100644
index 0000000..060a674
--- /dev/null
+++ b/misc/openlayers/examples/arcims.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>ArcIMS Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 0;
+ var lat = 0;
+ var zoom = 1;
+ var map, layer;
+
+ function init(){
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+ map = new OpenLayers.Map( 'map' );
+
+ var options = {
+ serviceName: "OpenLayers_Sample",
+ async: true
+ };
+
+ layer = new OpenLayers.Layer.ArcIMS( "Global Sample Map",
+ "http://sample.azavea.com/servlet/com.esri.esrimap.Esrimap", options );
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">ArcIMS Example</h1>
+
+ <div id="tags">
+ ESRI, ArcIMS
+ </div>
+ <p id="shortdesc">
+ Shows the basic use of OpenLayers using an ArcIMS layer
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ This is an example of how to add an ArcIMS layer to the OpenLayers window.
+ </div>
+
+ </body>
+</html>
+
+
+
+
diff --git a/misc/openlayers/examples/attribution.html b/misc/openlayers/examples/attribution.html
new file mode 100644
index 0000000..1f4ce12
--- /dev/null
+++ b/misc/openlayers/examples/attribution.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Attribution Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'},
+ {'attribution': 'Provided by OSGeo'});
+
+ var jpl_wms = new OpenLayers.Layer.WMS( "NASA Global Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {layers: "landsat7"},{attribution:"Provided by Telascience"});
+
+ var vector = new OpenLayers.Layer.Vector("Simple Geometry",
+ {attribution:"Vector Attibution in 2nd arg"});
+
+ map.addLayers([ol_wms, jpl_wms, vector]);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ // OpenLayers.Control.Attribution is one of the default
+ // controls - only needs to be added when the map instance is
+ // created with the controls option
+ //map.addControl(new OpenLayers.Control.Attribution());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Attribution Example</h1>
+
+ <div id="tags">
+ copyright, watermark, logo, attribution, light
+ </div>
+
+ <p id="shortdesc">
+ Shows the use of the attribution layer option on a number of layer types.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This is an example of how to add an attribution block to the OpenLayers window. In order to use an
+ attribution block, an attribution parameter must be set in each layer that requires attribution. In
+ addition, an attribution control must be added to the map, though one is added to all OpenLayers Maps by default.
+ Be aware that this is a layer <strong>option</strong>: the options hash goes in
+ different places depending on the layer type you are using.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/behavior-fixed-http-gml.html b/misc/openlayers/examples/behavior-fixed-http-gml.html
new file mode 100644
index 0000000..c1a11c3
--- /dev/null
+++ b/misc/openlayers/examples/behavior-fixed-http-gml.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Vector Behavior Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var layer = new OpenLayers.Layer.Vector("GML", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "gml/polygon.xml",
+ format: new OpenLayers.Format.GML()
+ })
+ });
+
+ map.addLayers([wms, layer]);
+ map.zoomToExtent(new OpenLayers.Bounds(
+ -3.92, 44.34, 4.87, 49.55
+ ));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Vector Behavior Example (Fixed/HTTP/GML)</h1>
+ <div id="tags">
+ vector, strategy, strategies, protocoll, advanced, gml, http, fixed
+ </div>
+ <p id="shortdesc">
+ Vector layer with a Fixed strategy, HTTP protocol, and GML format.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The vector layer shown uses the Fixed strategy, the HTTP protocol,
+ and the GML format.
+ The Fixed strategy is a simple strategy that fetches features once
+ and never re-requests new data.
+ The HTTP protocol makes requests using HTTP verbs. It should be
+ constructed with a url that corresponds to a collection of features
+ (a resource on some server).
+ The GML format is used to serialize features.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/bing-tiles-restrictedzoom.html b/misc/openlayers/examples/bing-tiles-restrictedzoom.html
new file mode 100644
index 0000000..afbd6a9
--- /dev/null
+++ b/misc/openlayers/examples/bing-tiles-restrictedzoom.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Basic Bing Tiles with a Subset of Resolutions Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ left: 2px;
+ right: inherit;
+ bottom: 3px;
+ line-height: 11px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Bing Tiles with a Subset of Resolutions Example</h1>
+
+ <div id="tags">
+ bing tiles restrictedMinZoom numZoomLevels
+ </div>
+
+ <div id="shortdesc">Use Bing with direct tile access</div>
+
+ <div id="map" class="smallmap" style="height: 350px;"></div>
+
+ <div id="docs">
+ <p>
+ This example shows how to use the <code>maxResolution</code> and
+ <code>numZoomLevels</code> layer properties to restrict
+ the number of zoom levels displayed on the Bing layer.
+ </p><p>
+ See <a target="_blank" href="bing-tiles-restrictedzoom.js">bing-tiles-restrictedzoom.js</a>
+ for the source code.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="bing-tiles-restrictedzoom.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/bing-tiles-restrictedzoom.js b/misc/openlayers/examples/bing-tiles-restrictedzoom.js
new file mode 100644
index 0000000..45c226f
--- /dev/null
+++ b/misc/openlayers/examples/bing-tiles-restrictedzoom.js
@@ -0,0 +1,37 @@
+// API key for http://openlayers.org. Please get your own at
+// http://bingmapsportal.com/ and use that instead.
+var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+
+var map = new OpenLayers.Map('map', {
+ controls: [
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.LayerSwitcher()
+ ]
+});
+
+var road3 = new OpenLayers.Layer.Bing({
+ name: "Road tiles with 3 zoom levels",
+ type: "Road",
+ key: apiKey,
+ maxResolution: 76.43702827453613,
+ numZoomLevels: 3
+});
+var road5 = new OpenLayers.Layer.Bing({
+ name: "Road tiles with 5 zoom levels",
+ type: "Road",
+ key: apiKey,
+ numZoomLevels: 5
+});
+var road = new OpenLayers.Layer.Bing({
+ name: "Road tiles with all zoom levels",
+ type: "Road",
+ key: apiKey
+});
+
+map.addLayers([road3, road5, road]);
+map.setCenter(new OpenLayers.LonLat(-71.147, 42.472).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+), 1);
diff --git a/misc/openlayers/examples/bing-tiles.html b/misc/openlayers/examples/bing-tiles.html
new file mode 100644
index 0000000..f3fe61d
--- /dev/null
+++ b/misc/openlayers/examples/bing-tiles.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Bing Tiles Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ left: 2px;
+ right: inherit;
+ bottom: 3px;
+ line-height: 11px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Basic Bing Tiles Example</h1>
+
+ <div id="tags">
+ bing tiles, light
+ </div>
+
+ <div id="shortdesc">Use Bing with direct tile access</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows a very simple map with Bing layers that use
+ direct tile access through Bing Maps REST Services.</p><p>See
+ <a target="_blank" href="bing-tiles.js">bing-tiles.js</a> for the
+ source code.</p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="bing-tiles.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/bing-tiles.js b/misc/openlayers/examples/bing-tiles.js
new file mode 100644
index 0000000..e99c589
--- /dev/null
+++ b/misc/openlayers/examples/bing-tiles.js
@@ -0,0 +1,31 @@
+// API key for http://openlayers.org. Please get your own at
+// http://bingmapsportal.com/ and use that instead.
+var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+
+var map = new OpenLayers.Map( 'map');
+
+var road = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "Road",
+ // custom metadata parameter to request the new map style - only useful
+ // before May 1st, 2011
+ metadataParams: {mapVersion: "v1"}
+});
+var aerial = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "Aerial"
+});
+var hybrid = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "AerialWithLabels",
+ name: "Bing Aerial With Labels"
+});
+
+map.addLayers([road, aerial, hybrid]);
+map.addControl(new OpenLayers.Control.LayerSwitcher());
+// Zoom level numbering depends on metadata from Bing, which is not yet loaded.
+var zoom = map.getZoomForResolution(76.43702827453613);
+map.setCenter(new OpenLayers.LonLat(-71.147, 42.472).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+), zoom);
diff --git a/misc/openlayers/examples/bing.html b/misc/openlayers/examples/bing.html
new file mode 100644
index 0000000..3f0c4cb
--- /dev/null
+++ b/misc/openlayers/examples/bing.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Bing Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+
+ <script src="../lib/OpenLayers.js"></script>
+ <script>
+
+ // API key for http://openlayers.org. Please get your own at
+ // http://bingmapsportal.com/ and use that instead.
+ var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+ var map;
+
+ function init() {
+ map = new OpenLayers.Map("map");
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ var road = new OpenLayers.Layer.Bing({
+ name: "Road",
+ key: apiKey,
+ type: "Road"
+ });
+ var hybrid = new OpenLayers.Layer.Bing({
+ name: "Hybrid",
+ key: apiKey,
+ type: "AerialWithLabels"
+ });
+ var aerial = new OpenLayers.Layer.Bing({
+ name: "Aerial",
+ key: apiKey,
+ type: "Aerial"
+ });
+
+ map.addLayers([road, hybrid, aerial]);
+
+ map.setCenter(new OpenLayers.LonLat(-110, 45), 3);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Bing Example</h1>
+
+ <div id="tags">
+ Bing, Microsoft, Virtual Earth, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the use of Bing layers.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="docs"><p>This example demonstrates the ability to create layers
+ using tiles from Bing maps.</p></div>
+ </body>
+</html>
+
+
diff --git a/misc/openlayers/examples/bootstrap.html b/misc/openlayers/examples/bootstrap.html
new file mode 100644
index 0000000..7f3b78b
--- /dev/null
+++ b/misc/openlayers/examples/bootstrap.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Bootstraped OpenLayers</title>
+ <link rel="stylesheet" href="../theme/default/style.css">
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap.min.css" rel="stylesheet">
+ <style>
+ body {
+ padding-top: 60px;
+ padding-bottom: 40px;
+ }
+ #map {
+ height: 350px;
+ background-color: #eee;
+ }
+ .olControlAttribution {
+ bottom: 3px;
+ left: 10px;
+ line-height: 9px;
+ font-size: 9px;
+ color: #ccc;
+ }
+ </style>
+ <link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/css/bootstrap-responsive.min.css" rel="stylesheet">
+</head>
+<body>
+ <div class="navbar navbar-inverse navbar-fixed-top">
+ <div class="navbar-inner">
+ <div class="container-fluid">
+ <a class="brand" href="/">openlayers.org</a>
+ <ul class="nav">
+ <li>
+ <a href="./">
+ <i class="icon-globe icon-white"></i> examples
+ </a>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ <div class="container-fluid" ng-controller="AlertsCtrl">
+ <div class="row-fluid">
+ <div class="span7">
+ <div id="map"></div>
+ </div>
+ <div class="span5">
+ <h4>OpenLayers and Bootstrap</h4>
+ <p>
+ This example demonstrates an OpenLayers map in a fluid
+ layout using Bootstrap CSS.
+ </p>
+ <p>
+ Note that the OpenLayers stylesheet is loaded before
+ Bootstrap. The Bootstrap CSS sets the maximum width for
+ images to be 100% (of their containing element).
+ </p>
+<pre><code>img {
+ max-width: 100%;
+}
+</code></pre>
+ <p>
+ This causes problems for images that you might want to be
+ bigger than their containing element (e.g. big tile in small
+ map viewport). To overcome this, the OpenLayers CSS
+ overrides this <code>max-width</code> setting. If you are
+ not loading the OpenLayers default CSS or are having trouble
+ with tile sizing and Bootstrap, add the following to your
+ markup:
+ </p>
+<pre><code>&lt;style>
+ img.olTileImage {
+ max-width: none;
+ }
+&lt;/style></code></pre>
+ </div>
+ </div>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="bootstrap.js"></script>
+</body>
+</html>
diff --git a/misc/openlayers/examples/bootstrap.js b/misc/openlayers/examples/bootstrap.js
new file mode 100644
index 0000000..e31b0a1
--- /dev/null
+++ b/misc/openlayers/examples/bootstrap.js
@@ -0,0 +1,31 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.XYZ(
+ "Imagery",
+ [
+ "http://oatile1.mqcdn.com/naip/${z}/${x}/${y}.png",
+ "http://oatile2.mqcdn.com/naip/${z}/${x}/${y}.png",
+ "http://oatile3.mqcdn.com/naip/${z}/${x}/${y}.png",
+ "http://oatile4.mqcdn.com/naip/${z}/${x}/${y}.png"
+ ],
+ {
+ attribution: "Tiles Courtesy of <a href='http://open.mapquest.co.uk/' target='_blank'>MapQuest</a>. Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency. <img src='http://developer.mapquest.com/content/osm/mq_logo.png' border='0'>",
+ transitionEffect: "resize",
+ wrapDateLine: true
+ }
+ )
+ ],
+ controls: [
+ new OpenLayers.Control.Navigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Zoom(),
+ new OpenLayers.Control.Attribution()
+ ],
+ center: [0, 0],
+ zoom: 1
+});
diff --git a/misc/openlayers/examples/boxes-vector.html b/misc/openlayers/examples/boxes-vector.html
new file mode 100644
index 0000000..d18dbbd
--- /dev/null
+++ b/misc/openlayers/examples/boxes-vector.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Boxes Vector Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var box_extents = [
+ [-10, 50, 5, 60],
+ [-75, 41, -71, 44],
+ [-122.6, 37.6, -122.3, 37.9],
+ [10, 10, 20, 20]
+ ];
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'} );
+
+ var boxes = new OpenLayers.Layer.Vector( "Boxes" );
+
+ for (var i = 0; i < box_extents.length; i++) {
+ ext = box_extents[i];
+ bounds = OpenLayers.Bounds.fromArray(ext);
+
+ box = new OpenLayers.Feature.Vector(bounds.toGeometry());
+ boxes.addFeatures(box);
+ }
+
+ map.addLayers([ol_wms, boxes]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ var sf = new OpenLayers.Control.SelectFeature(boxes);
+ map.addControl(sf);
+ sf.activate();
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Boxes Example Vector</h1>
+
+ <div id="tags">
+ box, vector, annotation, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate marker and box type annotations on a map.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/boxes.html b/misc/openlayers/examples/boxes.html
new file mode 100644
index 0000000..d2d9ccf
--- /dev/null
+++ b/misc/openlayers/examples/boxes.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Boxes Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var box_extents = [
+ [-10, 50, 5, 60],
+ [-75, 41, -71, 44],
+ [-122.6, 37.6, -122.3, 37.9],
+ [10, 10, 20, 20]
+ ];
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'} );
+
+ var boxes = new OpenLayers.Layer.Boxes( "Boxes" );
+
+ for (var i = 0; i < box_extents.length; i++) {
+ ext = box_extents[i];
+ bounds = OpenLayers.Bounds.fromArray(ext);
+ box = new OpenLayers.Marker.Box(bounds);
+ box.events.register("click", box, function (e) {
+ this.setBorder("yellow");
+ });
+ boxes.addMarker(box);
+ }
+
+ map.addLayers([ol_wms, boxes]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Boxes Example</h1>
+
+ <div id="tags">
+ box, annotation
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate marker and box type annotations on a map.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/browser.html b/misc/openlayers/examples/browser.html
new file mode 100644
index 0000000..195f7d4
--- /dev/null
+++ b/misc/openlayers/examples/browser.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Browser Detection</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script type="text/javascript" src="browser.js"></script>
+ <style type="text/css">
+ .olControlAttribution {
+ bottom: 5px;
+ }
+
+ .tester {
+ margin: 3px;
+ }
+ </style>
+ <script type="text/javascript">
+ function init() {
+ var result = document.getElementById('result');
+ result.innerHTML = result.innerHTML + "Browser CodeName: " + navigator.appCodeName + '<br>';
+ result.innerHTML = result.innerHTML + "Browser Name: " + navigator.appName + '<br>';
+ result.innerHTML = result.innerHTML + "Browser Version: " + navigator.appVersion + '<br>';
+ result.innerHTML = result.innerHTML + "Cookies Enabled: " + navigator.cookieEnabled + '<br>';
+ result.innerHTML = result.innerHTML + "Platform: " + navigator.platform + '<br>';
+ result.innerHTML = result.innerHTML + 'User agent: ' + navigator.userAgent + '<br>';
+ divResult('mouse', 'click', null, result);
+ divResult('mouse', 'dblclick', null, result);
+ divResult('mouse', 'mousedown', null, result);
+ divResult('mouse', 'mouseup', null, result);
+ divResult('mouse', 'mouseover', null, result);
+ divResult('mouse', 'mousemove', null, result);
+ divResult('mouse', 'mouseout', null, result);
+
+ divResult('key', 'keypress', null, result);
+ divResult('key', 'keydown', null, result);
+ divResult('key', 'keyup', null, result);
+
+ divResult('HTML', 'load', null, result);
+ divResult('HTML', 'unload', window, result);
+ divResult('HTML', 'abort', null, result);
+ divResult('HTML', 'error', null, result);
+
+ divResult('view', 'resize', window, result);
+ divResult('view', 'scroll', null, result);
+
+ divResult('form', 'submit', null, result);
+ divResult('form', 'reset', null, result);
+
+ divResult('form control', 'select', null, result);
+ divResult('form control', 'change', null, result);
+
+ divResult('activation', 'focus', null, result);
+ divResult('activation', 'blur', null, result);
+
+ divResult('touch', 'touchstart', null, result);
+ divResult('touch', 'touchend', null, result);
+ divResult('touch', 'touchmove', null, result);
+ divResult('touch', 'touchcancel', null, result);
+
+ divResult('gesture', 'gesturestart', null, result);
+ divResult('gesture', 'gesturechange', null, result);
+ divResult('gesture', 'gestureend', null, result);
+
+ divResult('HTML5', 'hashchange', document.body, result);
+ divResult('HTML5', 'online', document.body, result);
+ divResult('HTML5', 'offline', document.body, result);
+ divResult('HTML5', 'message', window, result);
+ divResult('HTML5', 'undo', document.body, result);
+ divResult('HTML5', 'redo', document.body, result);
+ divResult('HTML5', 'storage', window, result);
+ divResult('HTML5', 'popstate', window, result);
+ divResult('HTML5', 'canplay', document.createElement('video'), result);
+ divResult('HTML5', 'seeking', document.createElement('video'), result);
+ divResult('HTML5', 'seekend', document.createElement('video'), result);
+
+ divResult('orientation', 'deviceorientation', window, result);
+ divResult('orientation', 'mozorientation', window, result);
+ divResult('orientation', 'devicemotion', window, result);
+ }
+ </script>
+</head>
+<body onload="init()">
+<h1 id="title">Browser detection</h1>
+
+<div id="tags">
+ browser, vendor, mobile, events, HTML5, gesture, touch
+</div>
+
+<p id="shortdesc">
+ The goal of this script is to inform about the capacity of the browser used by the user.
+</p>
+
+<div id="docs">
+ <p>
+ See the <a href="browser.js" target="_blank">
+ browser.js source</a> to see how this is done.
+ </p>
+</div>
+
+<h1>Your browser information</h1>
+
+<div id="result">
+</div>
+
+<h1>Click or touch the red square to get information about the selected events</h1>
+
+<div>
+ <div class="tester">
+ <INPUT TYPE=CHECKBOX ID="clickID" checked>click<BR>
+ <INPUT TYPE=CHECKBOX ID="dblclickID">dblclick<BR>
+ <INPUT TYPE=CHECKBOX ID="mousedownID">mousedown<BR>
+ <INPUT TYPE=CHECKBOX ID="mouseupID">mouseup<BR>
+ <INPUT TYPE=CHECKBOX ID="mouseoverID">mouseover<BR>
+ <INPUT TYPE=CHECKBOX ID="mousemoveID">mousemove<BR>
+ <INPUT TYPE=CHECKBOX ID="mouseoutID">mouseout<BR>
+ <INPUT TYPE=CHECKBOX ID="touchstartID">touchstart<BR>
+ <INPUT TYPE=CHECKBOX ID="touchendID">touchend<BR>
+ <INPUT TYPE=CHECKBOX ID="touchmoveID">touchmove<BR>
+ <INPUT TYPE=CHECKBOX ID="touchcancelID">touchcancel<BR>
+ <INPUT TYPE=CHECKBOX ID="gesturestartID">gesturestart<BR>
+ <INPUT TYPE=CHECKBOX ID="gesturechangeID">gesturechange<BR>
+ <INPUT TYPE=CHECKBOX ID="gestureendID">gestureend<BR>
+ </div>
+
+ <div style="height: 200px;width: 200px;" class="tester">
+ <div id="box" style="height: 200px; width: 200px; background: none repeat scroll 0% 0% red; "
+ onclick="click(event)"
+ ondblclick="dblclick(event)"
+ onmousedown="mousedown(event)"
+ onmouseup="mouseup(event)"
+ onmouseover="mouseover(event)"
+ onmousemove="mousemove(event)"
+ onmouseout="mouseout(event)"
+ ontouchstart="touchstart(event)"
+ ontouchend="touchend(event)"
+ ontouchmove="touchmove(event)"
+ ontouchcancel="touchcancel(event)"
+ ongesturestart="gesturestart(event)"
+ ongesturechange="gesturechange(event)"
+ ongestureend="gestureend(event)">
+ </div>
+ </div>
+
+ <div id="log" class="tester"></div>
+</div>
+
+
+</body>
+</html>
diff --git a/misc/openlayers/examples/browser.js b/misc/openlayers/examples/browser.js
new file mode 100644
index 0000000..a593ca6
--- /dev/null
+++ b/misc/openlayers/examples/browser.js
@@ -0,0 +1,241 @@
+var isEventSupported = (function(undef) {
+
+ var TAGNAMES = {
+ 'select':'input',
+ 'change':'input',
+ 'submit':'form',
+ 'reset':'form',
+ 'error':'img',
+ 'load':'img',
+ 'abort':'img'
+ };
+
+ function isEventSupported(eventName, element) {
+ element = element || document.createElement(TAGNAMES[eventName] || 'div');
+ eventName = 'on' + eventName;
+
+ var isSupported = (eventName in element);
+
+ if (!isSupported) {
+ // if it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element
+ if (!element.setAttribute) {
+ element = document.createElement('div');
+ }
+ if (element.setAttribute && element.removeAttribute) {
+ element.setAttribute(eventName, '');
+ isSupported = typeof element[eventName] == 'function';
+
+ // if property was created, "remove it" (by setting value to `undefined`)
+ if (typeof element[eventName] != 'undefined') {
+ element[eventName] = undef;
+ }
+ element.removeAttribute(eventName);
+ }
+ }
+
+ element = null;
+ return isSupported;
+ }
+
+ return isEventSupported;
+})();
+
+function divResult(category, name, element, div) {
+ div.innerHTML = div.innerHTML + category + " " + name + ": ";
+ div.innerHTML = div.innerHTML + (
+ isEventSupported(name, element)
+ ? '<span style="background-color:green;color:white;">true</span></td>'
+ : '<span style="background-color:red;color:white;">false</span></td>'
+ );
+ div.innerHTML = div.innerHTML + "<br>";
+}
+var counter = 1;
+
+function log(title, detail) {
+ var logDiv = document.getElementById("log");
+ idString = "'id" + counter + "'";
+ var newlink = document.createElement('a');
+ newlink.setAttribute('href', "javascript:toggle_visibility(" + idString + ")");
+ newlink.innerHTML = counter + ". " + title;
+ var br1 = document.createElement('br');
+ logDiv.appendChild(newlink);
+ logDiv.appendChild(br1);
+
+ var childDiv = document.createElement('div');
+ childDiv.setAttribute("id", idString.replace("'", "").replace("'", ""));
+ childDiv.setAttribute("style", 'display: none; margin-left : 5px;');
+ childDiv.innerHTML = detail;
+ var br2 = document.createElement('br');
+ logDiv.appendChild(childDiv);
+
+ counter = counter + 1;
+}
+
+function inspect(obj) {
+ if (typeof obj === "undefined") {
+ return "undefined";
+ }
+ var _props = [];
+
+ for (var i in obj) {
+ _props.push(i + " : " + obj[i]);
+ }
+ return " {" + _props.join(",<br>") + "} ";
+}
+
+function click(e) {
+ if (document.getElementById("clickID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function dblclick(e) {
+ if (document.getElementById("dblclickID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function mousedown(e) {
+ if (document.getElementById("mousedownID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function mouseup(e) {
+ if (document.getElementById("mouseupID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function mouseover(e) {
+ if (document.getElementById("mouseoverID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function mousemove(e) {
+ if (document.getElementById("mousemoveID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function mouseout(e) {
+ if (document.getElementById("mouseoutID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function touchstart(e) {
+ if (document.getElementById("touchstartID").checked) {
+ var box = document.getElementById("box");
+ var result = inspect(e);
+ for (var i = 0; i < e.touches.length; i++) {
+ result = result + "<br> Touches nr." + i + " <br>" + inspect(e.touches[i]);
+ }
+ log(e.type, result);
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function touchend(e) {
+ if (document.getElementById("touchendID").checked) {
+ var box = document.getElementById("box");
+ var result = inspect(e);
+ for (var i = 0; i < e.touches.length; i++) {
+ result = result + "<br> Touches nr." + i + " <br>" + inspect(e.touches[i]);
+ }
+ log(e.type, result);
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function touchmove(e) {
+ if (document.getElementById("touchmoveID").checked) {
+ var targetEvent = e.touches.item(0);
+ var box = document.getElementById("box");
+ box.style.left = targetEvent.clientX + "px";
+ box.style.top = targetEvent.clientY + "px";
+ var result = inspect(e);
+ for (var i = 0; i < e.touches.length; i++) {
+ result = result + "<br> Touches nr." + i + " <br>" + inspect(e.touches[i]);
+ }
+ log(e.type, result);
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function touchcancel(e) {
+ if (document.getElementById("touchcancelID").checked) {
+ var box = document.getElementById("box");
+ var result = inspect(e);
+ for (var i = 0; i < e.touches.length; i++) {
+ result = result + "<br> Touches nr." + i + " <br>" + inspect(e.touches[i]);
+ }
+ log(e.type, result);
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function gesturestart(e) {
+ if (document.getElementById("gesturestartID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function gesturechange(e) {
+ if (document.getElementById("gesturechangeID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function gestureend(e) {
+ if (document.getElementById("gestureendID").checked) {
+ var box = document.getElementById("box");
+ log(e.type, inspect(e));
+ if (e.preventDefault) e.preventDefault();
+ }
+ return false;
+}
+
+function toggle_visibility(id) {
+ var e = document.getElementById(id);
+ if (e.style.display == 'block') {
+ e.style.display = 'none';
+ } else {
+ e.style.display = 'block';
+ }
+}
+
+
+
diff --git a/misc/openlayers/examples/buffer.html b/misc/openlayers/examples/buffer.html
new file mode 100644
index 0000000..77e88c4
--- /dev/null
+++ b/misc/openlayers/examples/buffer.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Buffer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 0;
+ var lat = 0;
+ var zoom = 2;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "0 buffer: OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}, {'buffer':0} );
+ map.addLayer(layer);
+ layer = new OpenLayers.Layer.WMS( "1 buffer: OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}, {'buffer':1} );
+ map.addLayer(layer);
+ layer = new OpenLayers.Layer.WMS( "4 buffer: OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}, {'buffer':4} );
+ map.addLayer(layer);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Buffer Example</h1>
+
+ <div id="tags">
+ buffer, performance, tile, light
+ </div>
+
+ <p id="shortdesc">
+ This example shows the use of the buffer layer option for any layer that inherits from OpenLayers.Layer.Grid.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ Use the buffer property to control how many tiles are included
+ outside the visible map area. Default is 0.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cache-read.html b/misc/openlayers/examples/cache-read.html
new file mode 100644
index 0000000..1db6a69
--- /dev/null
+++ b/misc/openlayers/examples/cache-read.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Cache Read Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="cache-read.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Cache Read Example</h1>
+
+ <div id="tags">
+ mobile, local storage, persistence, cache, html5
+ </div>
+
+ <div id="shortdesc">Caching viewed tiles</div>
+
+ <div id="map" class="smallmap"></div>
+ <div id="status"></div>
+ <br>
+ <div id="docs">
+ <p>This example shows how to use the CacheRead control to fetch cached
+ tiles from the browser's Local Storage. As you pan and zoom the map,
+ you can see how the number of cache hits incrases as you browse regions
+ that are available in the cache.</p>
+ <p>To fill the cache with tiles, switch to the
+ <a href="cache-write.html">cache-write.html</a> example.</p>
+ <p>See <a href="cache-read.js">cache-read.js</a> for the source
+ code.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cache-read.js b/misc/openlayers/examples/cache-read.js
new file mode 100644
index 0000000..1f79889
--- /dev/null
+++ b/misc/openlayers/examples/cache-read.js
@@ -0,0 +1,36 @@
+var map, cacheRead;
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.WMS("OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
+ layers: "basic"
+ }, {
+ eventListeners: {
+ tileloaded: updateHits
+ }
+ })
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+ cacheRead = new OpenLayers.Control.CacheRead();
+ map.addControl(cacheRead);
+
+
+
+ // User interface
+ var status = document.getElementById("status"),
+ hits = 0;
+
+ // update the number of cached tiles and detect local storage support
+ function updateHits(evt) {
+ hits += evt.tile.url.substr(0, 5) === "data:";
+ if (window.localStorage) {
+ status.innerHTML = hits + " cache hits.";
+ } else {
+ status.innerHTML = "Local storage not supported. Try a different browser.";
+ }
+ }
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/cache-write.html b/misc/openlayers/examples/cache-write.html
new file mode 100644
index 0000000..a5ad4ea
--- /dev/null
+++ b/misc/openlayers/examples/cache-write.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Cache Write Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script>OpenLayers.Console = window.console || OpenLayers.Console;</script>
+ <script src="cache-write.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Cache Write Example</h1>
+
+ <div id="tags">
+ mobile, local storage, persistence, cache, html5
+ </div>
+
+ <div id="shortdesc">Caching viewed tiles</div>
+
+ <div id="map" class="smallmap"></div>
+ <div>Cache status: <span id="status"></span></div>
+ <div><button id="clear">Clear cache</button></div>
+ <br>
+ <div id="docs">
+ <p>This example shows how to use the CacheWrite control to cache the
+ tiles. Caching is turned on, and as you pan and zoom the map, every
+ tile that is loaded is also copied to the browsers Local Storage.</p>
+ <p>To use the cached tiles, switch to the
+ <a href="cache-read.html">cache-read.html</a> example.</p>
+ <p>See <a href="cache-write.js">cache-write.js</a> for the source
+ code.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cache-write.js b/misc/openlayers/examples/cache-write.js
new file mode 100644
index 0000000..e9db31a
--- /dev/null
+++ b/misc/openlayers/examples/cache-write.js
@@ -0,0 +1,46 @@
+// Use proxy to get same origin URLs for tiles that don't support CORS.
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+var map, cacheWrite;
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "OSGeo", "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ )
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+ cacheWrite = new OpenLayers.Control.CacheWrite({
+ autoActivate: true,
+ imageFormat: "image/jpeg",
+ eventListeners: {
+ cachefull: function() { status.innerHTML = "Cache full."; }
+ }
+ });
+ map.addControl(cacheWrite);
+
+
+
+ // User interface
+ var status = document.getElementById("status");
+ document.getElementById("clear").onclick = function() {
+ OpenLayers.Control.CacheWrite.clearCache();
+ updateStatus();
+ };
+
+ // update the number of cached tiles and detect local storage support
+ map.layers[0].events.on({'tileloaded': updateStatus});
+ function updateStatus() {
+ if (window.localStorage) {
+ status.innerHTML = localStorage.length + " entries in cache.";
+ } else {
+ status.innerHTML = "Local storage not supported. Try a different browser.";
+ }
+ }
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/canvas-hit-detection.html b/misc/openlayers/examples/canvas-hit-detection.html
new file mode 100644
index 0000000..2f86ea7
--- /dev/null
+++ b/misc/openlayers/examples/canvas-hit-detection.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Canvas Hit Detection Example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Feature Hit Detection with Canvas</h1>
+ <p id="shortdesc">
+ Demonstrates detection of feature hits with the canvas renderer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ Click on the features above to see them selected. This example
+ uses the Canvas renderer so it only works on browsers where
+ canvas is supported.
+ </p>
+ <p>
+ View the <a href="canvas-hit-detection.js" target="_blank">canvas-hit-detection.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+ <script src="canvas-hit-detection.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/canvas-hit-detection.js b/misc/openlayers/examples/canvas-hit-detection.js
new file mode 100644
index 0000000..abc6897
--- /dev/null
+++ b/misc/openlayers/examples/canvas-hit-detection.js
@@ -0,0 +1,88 @@
+
+// create some sample features
+var Feature = OpenLayers.Feature.Vector;
+var Geometry = OpenLayers.Geometry;
+var features = [
+ new Feature(new Geometry.Point(-90, 45)),
+ new Feature(
+ new Geometry.Point(0, 45),
+ {cls: "one"}
+ ),
+ new Feature(
+ new Geometry.Point(90, 45),
+ {cls: "two"}
+ ),
+ new Feature(
+ Geometry.fromWKT("LINESTRING(-110 -60, -80 -40, -50 -60, -20 -40)")
+ ),
+ new Feature(
+ Geometry.fromWKT("POLYGON((20 -20, 110 -20, 110 -80, 20 -80, 20 -20), (40 -40, 90 -40, 90 -60, 40 -60, 40 -40))")
+ )
+];
+
+// create rule based styles
+var Rule = OpenLayers.Rule;
+var Filter = OpenLayers.Filter;
+var style = new OpenLayers.Style({
+ pointRadius: 10,
+ strokeWidth: 3,
+ strokeOpacity: 0.7,
+ strokeColor: "navy",
+ fillColor: "#ffcc66",
+ fillOpacity: 1
+}, {
+ rules: [
+ new Rule({
+ filter: new Filter.Comparison({
+ type: "==",
+ property: "cls",
+ value: "one"
+ }),
+ symbolizer: {
+ externalGraphic: "../img/marker-blue.png"
+ }
+ }),
+ new Rule({
+ filter: new Filter.Comparison({
+ type: "==",
+ property: "cls",
+ value: "two"
+ }),
+ symbolizer: {
+ externalGraphic: "../img/marker-green.png"
+ }
+ }),
+ new Rule({
+ elseFilter: true,
+ symbolizer: {
+ graphicName: "circle"
+ }
+ })
+ ]
+});
+
+var layer = new OpenLayers.Layer.Vector(null, {
+ styleMap: new OpenLayers.StyleMap({
+ "default": style,
+ select: {
+ fillColor: "red",
+ pointRadius: 13,
+ strokeColor: "yellow",
+ strokeWidth: 3
+ }
+ }),
+ isBaseLayer: true,
+ renderers: ["Canvas"]
+});
+layer.addFeatures(features);
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+});
+
+var select = new OpenLayers.Control.SelectFeature(layer);
+map.addControl(select);
+select.activate();
diff --git a/misc/openlayers/examples/canvas-inspector.html b/misc/openlayers/examples/canvas-inspector.html
new file mode 100644
index 0000000..8f2d8bc
--- /dev/null
+++ b/misc/openlayers/examples/canvas-inspector.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Canvas Inspector</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="Jugl.js"></script>
+ <style>
+ #template {
+ display: none;
+ }
+ #inspector table {
+ border-right: 1px solid #666;
+ border-bottom: 1px solid #666;
+ }
+ #inspector table td {
+ font-size: 9px;
+ text-align: center;
+ width: 60px;
+ height: 60px;
+ border-top: 1px solid #666;
+ border-left: 1px solid #666;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Canvas Inspector</h1>
+ <p id="shortdesc">
+ Displays pixel values for canvas context.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ View the <a href="canvas-inspector.js" target="_blank">canvas-inspector.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+ <div id="inspector">
+ </div>
+ <table id="template">
+ <tr jugl:repeat="row new Array(rows)">
+ <td jugl:repeat="col new Array(cols)"
+ jugl:attributes="id 'c' + repeat.col.index + 'r' + repeat.row.index">
+ &nbsp;
+ </td>
+ </tr>
+ </table>
+ <script src="canvas-inspector.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/canvas-inspector.js b/misc/openlayers/examples/canvas-inspector.js
new file mode 100644
index 0000000..064b4d5
--- /dev/null
+++ b/misc/openlayers/examples/canvas-inspector.js
@@ -0,0 +1,91 @@
+
+var features = [
+
+ new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT(
+ "LINESTRING(-90 90, 90 -90)"
+ ),
+ {color: "#0f0000"}
+ ),
+
+ new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT(
+ "LINESTRING(100 50, -100 -50)"
+ ),
+ {color: "#00ff00"}
+ )
+
+];
+
+var layer = new OpenLayers.Layer.Vector(null, {
+ styleMap: new OpenLayers.StyleMap({
+ strokeWidth: 3,
+ strokeColor: "${color}"
+ }),
+ isBaseLayer: true,
+ renderers: ["Canvas"],
+ rendererOptions: {hitDetection: true}
+});
+layer.addFeatures(features);
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+});
+
+var xOff = 2, yOff = 2;
+
+var rows = 1 + (2 * yOff);
+var cols = 1 + (2 * xOff);
+
+var template = new jugl.Template("template");
+template.process({
+ clone: true,
+ parent: "inspector",
+ context: {
+ rows: rows,
+ cols: cols
+ }
+});
+
+function isDark(r, g, b, a) {
+ a = a / 255;
+ var da = 1 - a;
+ // convert color values to decimal (assume white background)
+ r = (a * r / 255) + da;
+ g = (a * g / 255) + da;
+ b = (a * b / 255) + da;
+ // use w3C brightness measure
+ var brightness = (r * 0.299) + (g * 0.587) + (b * 0.144);
+ return brightness < 0.5;
+}
+
+var context = layer.renderer.canvas; //layer.renderer.hitContext;
+var size = map.getSize();
+map.events.on({
+ mousemove: function(event) {
+ var x = event.xy.x - 1; // TODO: fix this elsewhere
+ var y = event.xy.y;
+ if ((x >= xOff) && (x < size.w - xOff) && (y >= yOff) && (y < size.h - yOff)) {
+ var data = context.getImageData(x - xOff, y - yOff, rows, cols).data;
+ var offset, red, green, blue, alpha, cell;
+ for (var i=0; i<cols; ++i) {
+ for (var j=0; j<rows; ++j) {
+ offset = (i * 4) + (j * 4 * cols);
+ red = data[offset];
+ green = data[offset + 1];
+ blue = data[offset + 2];
+ alpha = data[offset + 3];
+ cell = document.getElementById("c" + i + "r" + j);
+ cell.innerHTML = "R: " + red + "<br>G: " + green + "<br>B: " + blue + "<br>A: " + alpha;
+ cell.style.backgroundColor = "rgba(" + red + ", " + green + ", " + blue + ", " + (alpha / 255) + ")";
+ cell.style.color = isDark(red, green, blue, alpha) ? "#ffffff" : "#000000";
+ }
+ }
+ }
+ }
+});
+
+
diff --git a/misc/openlayers/examples/canvas.html b/misc/openlayers/examples/canvas.html
new file mode 100644
index 0000000..06beef8
--- /dev/null
+++ b/misc/openlayers/examples/canvas.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Canvas Renderer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script src="canvas.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Canvas Renderer Example</h1>
+ <div id="tags">
+ canvas, renderer, advanced,
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of the canvas renderer with a vector layer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This example shows a vector layer that uses the Canvas renderer
+ where available. The order of the renderers given in the layer
+ options is used to locate the first available renderer.
+ </p>
+ <p>
+ See the <a href="canvas.js" target="_blank">canvas.js source</a>
+ to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/canvas.js b/misc/openlayers/examples/canvas.js
new file mode 100644
index 0000000..bb2f224
--- /dev/null
+++ b/misc/openlayers/examples/canvas.js
@@ -0,0 +1,57 @@
+var map, layer, styleMap;
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326")
+ });
+
+ var g = new OpenLayers.Layer.Google("Google Layer", {
+ sphericalMercator: true
+ });
+ map.addLayers([g]);
+
+ // prepare to style the data
+ styleMap = new OpenLayers.StyleMap({
+ strokeColor: "black",
+ strokeWidth: 2,
+ strokeOpacity: 0.5,
+ fillOpacity: 0.2
+ });
+
+ // create a color table for state FIPS code
+ var colors = ["red", "orange", "yellow", "green", "blue", "purple"];
+ var code, fips = {};
+ for(var i=1; i<=66; ++i) {
+ code = "0" + i;
+ code = code.substring(code.length - 2);
+ fips[code] = {fillColor: colors[i % colors.length]};
+ }
+ // add unique value rules with your color lookup
+ styleMap.addUniqueValueRules("default", "STATE_FIPS", fips);
+
+ // create a vector layer using the canvas renderer (where available)
+ var wfs = new OpenLayers.Layer.Vector("States", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ version: "1.1.0",
+ srsName: "EPSG:900913",
+ url: "http://v2.suite.opengeo.org/geoserver/wfs",
+ featureType: "states",
+ featureNS: "http://usa.opengeo.org"
+ }),
+ styleMap: styleMap,
+ renderers: ["Canvas", "SVG", "VML"]
+ });
+ map.addLayer(wfs);
+
+ // if you want to use Geographic coords, transform to ESPG:900913
+ var ddBounds = new OpenLayers.Bounds(
+ -73.839111,40.287907,-68.214111,44.441624
+ );
+ map.zoomToExtent(
+ ddBounds.transform(map.displayProjection, map.getProjectionObject())
+ );
+}
diff --git a/misc/openlayers/examples/cartodb-geojson.html b/misc/openlayers/examples/cartodb-geojson.html
new file mode 100644
index 0000000..2d78970
--- /dev/null
+++ b/misc/openlayers/examples/cartodb-geojson.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Reading Features From CartoDB using GeoJSON</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Reading Features From CartoDB using GeoJSON</h1>
+ <div id="tags">
+ protocol, script, cartodb
+ </div>
+ <p id="shortdesc">
+ Demonstrates how to load features on OpenLayers using CartoDB SQL API.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ <a href="http://cartodb.com/">CartoDB</a> is an Open Source
+ Geopatial Database on the cloud. It allows you to import your
+ data in shapefiles, KML, OpenStreeMap files, CSV, etc. and
+ then analyze and visualize it. Internally CartoDB uses PostGIS
+ 2.0 so all functionality in PostGIS can be used straight
+ away. CartoDB exposes two APIS. One
+ to <a href="http://developers.cartodb.com/documentation/cartodb-apis.html#maps_api">generate maps</a>
+ as tiles with interactivity, and another <a href="http://developers.cartodb.com/documentation/cartodb-apis.html#sql_api">SQL API</a>
+ to retrieve vector data using among other formats, GeoJSON. In
+ this example we do a very simple query to obtain all protected
+ areas in Costa Rica from a public table. You can adapt the SQL
+ to include where clauses or complicate geospatial queries.
+ </p>
+ <p>
+ View the source code of this page to see how this is done. And
+ check the table on CartoDB
+ for <a href="https://examples.cartodb.com/tables/costa_rica_pa/public#/map">Protected Areas in Costa Rica</a>
+ </p>
+ </div>
+ <script>
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.OSM(),
+ new OpenLayers.Layer.Vector("Vectors", {
+ projection: new OpenLayers.Projection("EPSG:4326"),
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "http://examples.cartodb.com/api/v2/sql",
+ params: {
+ q: "select * from costa_rica_pa LIMIT 50",
+ format: "geojson"
+ },
+ format: new OpenLayers.Format.GeoJSON({
+ ignoreExtraDims: true
+ }),
+ callbackKey: "callback"
+ }),
+ eventListeners: {
+ "featuresadded": function() {
+ this.map.zoomToExtent(this.getDataExtent());
+ }
+ }
+ })
+ ]
+ });
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/click-handler.html b/misc/openlayers/examples/click-handler.html
new file mode 100644
index 0000000..d0bd9d4
--- /dev/null
+++ b/misc/openlayers/examples/click-handler.html
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Click Handler Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ width: 340px;
+ height: 170px;
+ border: 1px solid gray;
+ }
+ #west {
+ width: 350px;
+ }
+ #east {
+ position: absolute;
+ left: 370px;
+ top: 4em;
+ }
+
+ table td {
+ text-align: center;
+ margin: 0;
+ border: 1px solid gray;
+ }
+ textarea.output {
+ text-align: left;
+ font-size: 0.9em;
+ width: 250px;
+ height: 65px;
+ overflow: auto;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
+ defaultHandlerOptions: {
+ 'single': true,
+ 'double': false,
+ 'pixelTolerance': 0,
+ 'stopSingle': false,
+ 'stopDouble': false
+ },
+
+ initialize: function(options) {
+ this.handlerOptions = OpenLayers.Util.extend(
+ {}, this.defaultHandlerOptions
+ );
+ OpenLayers.Control.prototype.initialize.apply(
+ this, arguments
+ );
+ this.handler = new OpenLayers.Handler.Click(
+ this, {
+ 'click': this.onClick,
+ 'dblclick': this.onDblclick
+ }, this.handlerOptions
+ );
+ },
+
+ onClick: function(evt) {
+ var output = document.getElementById(this.key + "Output");
+ var msg = "click " + evt.xy;
+ output.value = output.value + msg + "\r\n";
+ },
+
+ onDblclick: function(evt) {
+ var output = document.getElementById(this.key + "Output");
+ var msg = "dblclick " + evt.xy;
+ output.value = output.value + msg + "\n";
+ }
+
+ });
+
+ var map, controls;
+
+ function init(){
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ map.addLayers([layer]);
+
+ controls = {
+ "single": new OpenLayers.Control.Click({
+ handlerOptions: {
+ "single": true
+ }
+ }),
+ "double": new OpenLayers.Control.Click({
+ handlerOptions: {
+ "single": false,
+ "double": true
+ }
+ }),
+ "both": new OpenLayers.Control.Click({
+ handlerOptions: {
+ "single": true,
+ "double": true
+ }
+ }),
+ "drag": new OpenLayers.Control.Click({
+ handlerOptions: {
+ "single": true,
+ "pixelTolerance": null
+ }
+ }),
+ "stopsingle": new OpenLayers.Control.Click({
+ handlerOptions: {
+ "single": true,
+ "stopSingle": true
+ }
+ }),
+ "stopdouble": new OpenLayers.Control.Click({
+ handlerOptions: {
+ "single": false,
+ "double": true,
+ "stopDouble": true
+ }
+ })
+ };
+
+ var props = document.getElementById("props");
+ var control;
+ for(var key in controls) {
+ control = controls[key];
+ // only to route output here
+ control.key = key;
+ map.addControl(control);
+ }
+
+ map.zoomToMaxExtent();
+ }
+
+ function toggle(key) {
+ var control = controls[key];
+ if(control.active) {
+ control.deactivate();
+ } else {
+ control.activate();
+ }
+ var status = document.getElementById(key + "Status");
+ status.innerHTML = control.active ? "on" : "off";
+ var output = document.getElementById(key + "Output");
+ output.value = "";
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Click Handler Example</h1>
+ <div id="west">
+
+ <div id="tags">
+ event, events, propagation, advanced
+ </div>
+
+ <p id="shortdesc">
+ This example shows the use of the click handler.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <p>
+ The click handler can be used to gain more flexibility over handling
+ click events. The handler can be constructed with options to handle
+ only single click events, to handle single and double-click events,
+ to ignore clicks that include a drag, and to stop propagation of
+ single and/or double-click events. A single click is a click that
+ is not followed by another click for more than 300ms. This delay
+ is configured with the delay property.
+ </p>
+ <p>
+ The options to stop single and double clicks have to do with
+ stopping event propagation on the map events listener queue
+ (not stopping events from cascading to other elements). The
+ ability to stop an event from propagating has to do with the
+ order in which listeners are registered. With stopSingle or
+ stopDouble true, a click handler will stop propagation to all
+ listeners that were registered (or all handlers that were
+ activated) before the click handler was activated. So, for
+ example, activating a click handler with stopDouble true after
+ the navigation control is active will stop double-clicks from
+ zooming in.
+ </p>
+ </div>
+ <div id="east">
+ <table>
+ <caption>Controls with click handlers (toggle on/off to clear output)</caption>
+ <tbody>
+ <tr>
+ <td>single only</td>
+ <td><button id="singleStatus" onclick="toggle('single')">off</button></td>
+ <td><textarea class="output" id="singleOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>double only</td>
+ <td><button id="doubleStatus" onclick="toggle('double')">off</button></td>
+ <td><textarea class="output" id="doubleOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>both</td>
+ <td><button id="bothStatus" onclick="toggle('both')">off</button></td>
+ <td><textarea class="output" id="bothOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>single with drag</td>
+ <td><button id="dragStatus" onclick="toggle('drag')">off</button></td>
+ <td><textarea class="output" id="dragOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>single with stop</td>
+ <td><button id="stopsingleStatus" onclick="toggle('stopsingle')">off</button></td>
+ <td><textarea class="output" id="stopsingleOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>double with stop</td>
+ <td><button id="stopdoubleStatus" onclick="toggle('stopdouble')">off</button></td>
+ <td><textarea class="output" id="stopdoubleOutput"></textarea></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/click.html b/misc/openlayers/examples/click.html
new file mode 100644
index 0000000..5b6a025
--- /dev/null
+++ b/misc/openlayers/examples/click.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Click Event Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
+ defaultHandlerOptions: {
+ 'single': true,
+ 'double': false,
+ 'pixelTolerance': 0,
+ 'stopSingle': false,
+ 'stopDouble': false
+ },
+
+ initialize: function(options) {
+ this.handlerOptions = OpenLayers.Util.extend(
+ {}, this.defaultHandlerOptions
+ );
+ OpenLayers.Control.prototype.initialize.apply(
+ this, arguments
+ );
+ this.handler = new OpenLayers.Handler.Click(
+ this, {
+ 'click': this.trigger
+ }, this.handlerOptions
+ );
+ },
+
+ trigger: function(e) {
+ var lonlat = map.getLonLatFromPixel(e.xy);
+ alert("You clicked near " + lonlat.lat + " N, " +
+ + lonlat.lon + " E");
+ }
+
+ });
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'} );
+
+ var jpl_wms = new OpenLayers.Layer.WMS( "NASA Global Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {layers: "landsat7"});
+
+ jpl_wms.setVisibility(false);
+
+ map.addLayers([ol_wms, jpl_wms]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ // map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ map.zoomToMaxExtent();
+
+ var click = new OpenLayers.Control.Click();
+ map.addControl(click);
+ click.activate();
+
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Click Event Example</h1>
+
+ <div id="tags">
+ click control, double, doubleclick, double-click, event, events,
+ propagation, light
+ </div>
+
+ <p id="shortdesc">
+ This example shows the use of the click handler and
+ getLonLatFromPixel functions to trigger events on mouse click.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>Using the Click handler allows you to (for example) catch clicks
+ without catching double clicks, something that standard browser
+ events don't do for you. (Try double clicking: you'll zoom in,
+ whereas using the browser click event, you would just get two
+ alerts.) This example click control shows you how to use it.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/clientzoom.html b/misc/openlayers/examples/clientzoom.html
new file mode 100644
index 0000000..c32c7c1
--- /dev/null
+++ b/misc/openlayers/examples/clientzoom.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Client Zoom Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="clientzoom.js"></script>
+ <style>
+ .olControlAttribution {
+ bottom: 5px;
+ }
+ #map {
+ width: 600px;
+ height: 400px;
+ }
+ </style>
+ </head>
+ <body onload="init();">
+ <h1 id="title">Client Zoom</h1>
+ <div id="tags">
+ client zoom continuous zooming
+ </div>
+ <p id="shortdesc">
+
+ This example demonstrates the <strong>"client zoom"</strong>
+ functionality, where OpenLayers stretches the layer div when the
+ resolution is not supported by that layer's tile service.
+
+ </p>
+
+ <div id="map"></div>
+
+ <div id="docs">
+
+ <p>
+
+ The map of this example is configured with 22 resolutions, while
+ the OSM tile server supports the first 19 resolutions only. When
+ the zoom level is 19, 20 or 21 "client zoom" is applied to the OSM
+ layer, i.e. the OSM layer div is stretched as necessary. The map's
+ initial zoom is 18. So if you zoom in using the zoom bar's "+"
+ button you'll effectively trigger "client zoom".
+
+ </p>
+
+ <p>
+
+ For demonstration purpose the map of this example has
+ <code>fractionalZoom</code> set to true. So "client zoom" also
+ applies if you choose arbitrary zoom levels using the slider of the
+ zoom bar, or shift-drag boxes to zoom to arbitrary extents.
+ "client zoom" therefore allows continous zooming for tiled layers.
+
+ </p>
+
+ <p>
+
+ Enabling "client zoom" on a layer is done by passing
+ <code>serverResolutions</code> to the layer constructor.
+ <code>serverResolutions</code> is the list of resolutions supported
+ by the tile service. See the <a href="clientzoom.js"
+ target="_blank"> clientzoom.js source</a>.
+
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/clientzoom.js b/misc/openlayers/examples/clientzoom.js
new file mode 100644
index 0000000..30071ed
--- /dev/null
+++ b/misc/openlayers/examples/clientzoom.js
@@ -0,0 +1,39 @@
+var map;
+
+function init() {
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ controls: [],
+ fractionalZoom: true
+ });
+
+ var osm = new OpenLayers.Layer.OSM(null, null, {
+ resolutions: [156543.03390625, 78271.516953125, 39135.7584765625,
+ 19567.87923828125, 9783.939619140625, 4891.9698095703125,
+ 2445.9849047851562, 1222.9924523925781, 611.4962261962891,
+ 305.74811309814453, 152.87405654907226, 76.43702827453613,
+ 38.218514137268066, 19.109257068634033, 9.554628534317017,
+ 4.777314267158508, 2.388657133579254, 1.194328566789627,
+ 0.5971642833948135, 0.25, 0.1, 0.05],
+ serverResolutions: [156543.03390625, 78271.516953125, 39135.7584765625,
+ 19567.87923828125, 9783.939619140625,
+ 4891.9698095703125, 2445.9849047851562,
+ 1222.9924523925781, 611.4962261962891,
+ 305.74811309814453, 152.87405654907226,
+ 76.43702827453613, 38.218514137268066,
+ 19.109257068634033, 9.554628534317017,
+ 4.777314267158508, 2.388657133579254,
+ 1.194328566789627, 0.5971642833948135],
+ transitionEffect: 'resize'
+ });
+
+ map.addLayers([osm]);
+ map.addControls([
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.PanZoomBar()
+ ]);
+ map.setCenter(new OpenLayers.LonLat(659688.852138, 5710701.2962197), 18);
+}
diff --git a/misc/openlayers/examples/controls.html b/misc/openlayers/examples/controls.html
new file mode 100644
index 0000000..36c8825
--- /dev/null
+++ b/misc/openlayers/examples/controls.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Map Controls Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+
+ <style>
+ /* round corners of layer switcher, and make it transparent */
+ .olControlLayerSwitcher .layersDiv {
+ border-radius: 10px 0 0 10px;
+ opacity: 0.75;
+ filter: alpha(opacity=75);
+ }
+ </style>
+
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map', {
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.LayerSwitcher({'ascending':false}),
+ new OpenLayers.Control.Permalink(),
+ new OpenLayers.Control.ScaleLine(),
+ new OpenLayers.Control.Permalink('permalink'),
+ new OpenLayers.Control.MousePosition(),
+ new OpenLayers.Control.OverviewMap(),
+ new OpenLayers.Control.KeyboardDefaults()
+ ],
+ numZoomLevels: 6
+
+ });
+
+ var ol_wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var gwc = new OpenLayers.Layer.WMS(
+ "Global Imagery",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "bluemarble"},
+ {tileOrigin: new OpenLayers.LonLat(-180, -90)}
+ );
+ var dm_wms = new OpenLayers.Layer.WMS(
+ "DM Solutions Demo",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap",
+ {layers: "bathymetry,land_fn,park,drain_fn,drainage," +
+ "prov_bound,fedlimit,rail,road,popplace",
+ transparent: "true", format: "image/png"},
+ {visibility: false}
+ );
+
+ map.addLayers([ol_wms, gwc, dm_wms]);
+
+ if (!map.getCenter()) {
+ map.zoomToMaxExtent();
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Map Controls Example</h1>
+
+ <div id="tags">
+ control, basic
+ </div>
+
+ <p id="shortdesc">
+ Attach zooming, panning, layer switcher, overview map, and permalink map controls to an OpenLayers window.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <a href="#" id="permalink">Permalink</a>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cql-format.html b/misc/openlayers/examples/cql-format.html
new file mode 100644
index 0000000..7a00509
--- /dev/null
+++ b/misc/openlayers/examples/cql-format.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>
+ OpenLayers CQL Example
+ </title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #cql {
+ width: 400px;
+ }
+ #output {
+ padding-top: 1em;
+ width: 512px;
+ height: 60px;
+ border: none;
+ color: #ff3333;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">CQL Filter Example</h1>
+ <div id="tags">
+ CQL, filter
+ </div>
+ <p id="shortdesc">
+ Demonstrate use the CQL filter.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ Enter text for a CQL filter to update the features displayed.
+ <br>
+ <form name="cql_form" id="cql_form">
+ <label for="cql">CQL</label>
+ <input id="cql" type="text" value="STATE_ABBR >= 'B' AND STATE_ABBR <= 'O'">
+ <input type="submit" value="update">
+ <input type="reset" value="reset">
+ </form>
+ <textarea id="output"></textarea>
+ </p><p>
+ View the <a href="cql-format.js" target="_blank">cql-format.js source</a>
+ to see how this is done.
+ </p>
+ </div>
+ <script src="cql-format.js"></script>
+ <script src="http://demo.opengeo.org/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=GetFeature&amp;typename=topp:states&amp;outputFormat=json&amp;format_options=callback:loadFeatures" type="text/javascript"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cql-format.js b/misc/openlayers/examples/cql-format.js
new file mode 100644
index 0000000..9b4a210
--- /dev/null
+++ b/misc/openlayers/examples/cql-format.js
@@ -0,0 +1,61 @@
+
+// use a CQL parser for easy filter creation
+var format = new OpenLayers.Format.CQL();
+
+// this rule will get a filter from the CQL text in the form
+var rule = new OpenLayers.Rule({
+ // We could also set a filter here. E.g.
+ // filter: format.read("STATE_ABBR >= 'B' AND STATE_ABBR <= 'O'"),
+ symbolizer: {
+ fillColor: "#ff0000",
+ strokeColor: "#ffcccc",
+ fillOpacity: "0.5"
+ }
+});
+
+var states = new OpenLayers.Layer.Vector("States", {
+ styleMap: new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style(null, {rules: [rule]})
+ })
+});
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "openstreetmap", format: "image/png"}
+ ),
+ states
+ ],
+ center: new OpenLayers.LonLat(-101, 39),
+ zoom: 3
+});
+
+// called when features are fetched
+function loadFeatures(data) {
+ var features = new OpenLayers.Format.GeoJSON().read(data);
+ states.addFeatures(features);
+}
+
+// update filter and redraw when form is submitted
+var cql = document.getElementById("cql");
+var output = document.getElementById("output");
+function updateFilter() {
+ var filter;
+ try {
+ filter = format.read(cql.value);
+ } catch (err) {
+ output.value = err.message;
+ }
+ if (filter) {
+ output.value = "";
+ rule.filter = filter;
+ states.redraw();
+ }
+ return false;
+}
+updateFilter();
+var form = document.getElementById("cql_form");
+form.onsubmit = updateFilter;
diff --git a/misc/openlayers/examples/cross-origin-xml.html b/misc/openlayers/examples/cross-origin-xml.html
new file mode 100644
index 0000000..b811bf7
--- /dev/null
+++ b/misc/openlayers/examples/cross-origin-xml.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Script Protocol XML Example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Script Protocol With XML</h1>
+ <div id="tags">
+ protocol, script, cross origin, xml, advanced
+ </div>
+ <p id="shortdesc">
+ Demonstrates how, with a custom parseFeatures method, the script protocol can be used with YQL for cross-origin loading of files in any of the XML formats supported by OpenLayers.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ YQL can wrap a jsonp callback around an XML file, which effectively means Yahoo's servers are acting as a proxy for cross-origin feature loading. This example uses a GPX file, but the same technique can be used for other formats such as KML.
+ </p>
+ <p>
+ View the <a href="cross-origin-xml.js" target="_blank">cross-origin-xml.js</a>
+ source to see how this is done
+ </p>
+ </div>
+ <script src="cross-origin-xml.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cross-origin-xml.js b/misc/openlayers/examples/cross-origin-xml.js
new file mode 100644
index 0000000..a97cc1f
--- /dev/null
+++ b/misc/openlayers/examples/cross-origin-xml.js
@@ -0,0 +1,25 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.OSM(),
+ new OpenLayers.Layer.Vector("Vectors", {
+ projection: new OpenLayers.Projection("EPSG:4326"),
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "http://query.yahooapis.com/v1/public/yql",
+ params: {
+ q: "select * from xml where url='http://www.topografix.com/fells_loop.gpx'"
+ },
+ format: new OpenLayers.Format.GPX(),
+ parseFeatures: function(data) {
+ return this.format.read(data.results[0]);
+ }
+ }),
+ eventListeners: {
+ "featuresadded": function () {
+ this.map.zoomToExtent(this.getDataExtent());
+ }
+ }
+ })
+ ]
+});
diff --git a/misc/openlayers/examples/cross-origin.html b/misc/openlayers/examples/cross-origin.html
new file mode 100644
index 0000000..246047a
--- /dev/null
+++ b/misc/openlayers/examples/cross-origin.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Script Protocol Example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Script Protocol</h1>
+ <div id="tags">
+ protocol, script, cross origin, advanced
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of a script protocol for making feature requests
+ cross origin.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ In cases where a service returns serialized features and accepts
+ a named callback (e.g. http://example.com/features.json?callback=foo),
+ the script protocol can be used to read features without being
+ restricted by the same origin policy.
+ </p>
+ <p>
+ View the <a href="cross-origin.js" target="_blank">cross-origin.js</a>
+ source to see how this is done
+ </p>
+ </div>
+ <script src="cross-origin.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/cross-origin.js b/misc/openlayers/examples/cross-origin.js
new file mode 100644
index 0000000..6cf39ec
--- /dev/null
+++ b/misc/openlayers/examples/cross-origin.js
@@ -0,0 +1,39 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "World Map",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "bluemarble"}
+ ),
+ new OpenLayers.Layer.Vector("States", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "http://suite.opengeo.org/geoserver/wfs",
+ callbackKey: "format_options",
+ callbackPrefix: "callback:",
+ params: {
+ service: "WFS",
+ version: "1.1.0",
+ srsName: "EPSG:4326",
+ request: "GetFeature",
+ typeName: "world:cities",
+ outputFormat: "json"
+ },
+ filterToParams: function(filter, params) {
+ // example to demonstrate BBOX serialization
+ if (filter.type === OpenLayers.Filter.Spatial.BBOX) {
+ params.bbox = filter.value.toArray();
+ if (filter.projection) {
+ params.bbox.push(filter.projection.getCode());
+ }
+ }
+ return params;
+ }
+ })
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+});
+
diff --git a/misc/openlayers/examples/custom-control.html b/misc/openlayers/examples/custom-control.html
new file mode 100644
index 0000000..8688751
--- /dev/null
+++ b/misc/openlayers/examples/custom-control.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Custom Control Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ var control = new OpenLayers.Control();
+ OpenLayers.Util.extend(control, {
+ draw: function () {
+ // this Handler.Box will intercept the shift-mousedown
+ // before Control.MouseDefault gets to see it
+ this.box = new OpenLayers.Handler.Box( control,
+ {"done": this.notice},
+ {keyMask: OpenLayers.Handler.MOD_SHIFT});
+ this.box.activate();
+ },
+
+ notice: function (bounds) {
+ var ll = map.getLonLatFromPixel(new OpenLayers.Pixel(bounds.left, bounds.bottom));
+ var ur = map.getLonLatFromPixel(new OpenLayers.Pixel(bounds.right, bounds.top));
+ alert(ll.lon.toFixed(4) + ", " +
+ ll.lat.toFixed(4) + ", " +
+ ur.lon.toFixed(4) + ", " +
+ ur.lat.toFixed(4));
+ }
+ });
+
+ map.addLayer(layer);
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Custom Control Example</h1>
+
+ <div id="tags">
+ control, panel, rectangle, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate the addition of a rectangle to the OpenLayers window.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>The control allows you to draw a rectangle, that reports its coordinates
+ after creation. Hold down the shift key on your keyboard and draw a
+ rectangle with the mouse.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/custom-style.html b/misc/openlayers/examples/custom-style.html
new file mode 100644
index 0000000..7b1f369
--- /dev/null
+++ b/misc/openlayers/examples/custom-style.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Custom Style Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ p {
+ width: 500px;
+ }
+ div.olControlMousePosition {
+ font-family: Verdana;
+ font-size: 2em;
+ color: red;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ var options = {theme: null};
+ map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Custom Style Example</h1>
+
+ <div id="tags">
+ styling, css, stylesheet, theming, theme
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate changing CSS styles on controls in the OpenLayers window.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>If you care to modify the style of any OpenLayers element, include
+ the default stylesheet as a link and declare any style modifications
+ below that link. These style declarations can be in other linked
+ stylesheets or in style tags. In addition, construct your map with
+ options that include {theme: null}. This will disable the default
+ method of loading the stylesheet and allow you to declare style rules
+ in your own linked stylesheets or style tags.</p>
+ <p>This example shows how to declare the font family, size, and color
+ for the mouse position. Note that only the style keys that you want to
+ modify (change from the default) need to be specified.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/data/4_m_citylights_lg.gif b/misc/openlayers/examples/data/4_m_citylights_lg.gif
new file mode 100644
index 0000000..4bf9b87
--- /dev/null
+++ b/misc/openlayers/examples/data/4_m_citylights_lg.gif
Binary files differ
diff --git a/misc/openlayers/examples/data/line.json b/misc/openlayers/examples/data/line.json
new file mode 100644
index 0000000..942a920
--- /dev/null
+++ b/misc/openlayers/examples/data/line.json
@@ -0,0 +1,10 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_458", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[-121.640625, 24.2578125], [-78.046875, 27.7734375], [-45.703125, 24.9609375], [-13.359375, 16.5234375], [12.65625, 6.6796875], [39.375, 1.0546875], [76.640625, 1.0546875], [108.28125, 1.7578125], [156.09375, 15.8203125]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1111", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[-122.34375, -35.5078125], [-48.515625, -33.3984375], [-5.625, -37.6171875], [20.390625, -32.6953125], [69.609375, -34.1015625], [121.640625, -38.3203125], [150.46875, -33.3984375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_634", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[-54.84375, 69.9609375], [-56.953125, 31.9921875], [-56.953125, 5.2734375], [-65.390625, -34.8046875], [-66.09375, -61.5234375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_820", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[39.375, 58.0078125], [42.890625, 25.6640625], [42.1875, -1.0546875], [37.96875, -50.2734375], [37.265625, -64.3359375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1280", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[101.25, 42.5390625], [106.875, 13.7109375], [106.171875, -17.9296875], [104.765625, -49.5703125], [102.65625, -67.1484375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}
+ ]
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/data/point.json b/misc/openlayers/examples/data/point.json
new file mode 100644
index 0000000..96b934e
--- /dev/null
+++ b/misc/openlayers/examples/data/point.json
@@ -0,0 +1,8 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1721", "properties":{}, "geometry":{"type":"Point", "coordinates":[-89.296875, -14.4140625]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1715", "properties":{}, "geometry":{"type":"Point", "coordinates":[-25.3125, -54.4921875]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1709", "properties":{}, "geometry":{"type":"Point", "coordinates":[73.828125, -23.5546875]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}
+ ]
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/data/poly.json b/misc/openlayers/examples/data/poly.json
new file mode 100644
index 0000000..f15c0e9
--- /dev/null
+++ b/misc/openlayers/examples/data/poly.json
@@ -0,0 +1,9 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1489", "properties":{}, "geometry":{"type":"Polygon", "coordinates":[[[-109.6875, 63.6328125], [-112.5, 35.5078125], [-85.078125, 34.8046875], [-68.90625, 39.7265625], [-68.203125, 67.1484375], [-109.6875, 63.6328125]]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}},
+ {"type":"Feature", "id":"OpenLayers.Feature.Vector_1668", "properties":{}, "geometry":{"type":"Polygon", "coordinates":[[[-40.78125, 65.0390625], [-40.078125, 34.8046875], [-12.65625, 25.6640625], [21.09375, 17.2265625], [22.5, 58.0078125], [-40.78125, 65.0390625]]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}
+ ]
+}
+
+
diff --git a/misc/openlayers/examples/data/roads.json b/misc/openlayers/examples/data/roads.json
new file mode 100644
index 0000000..c6d4866
--- /dev/null
+++ b/misc/openlayers/examples/data/roads.json
@@ -0,0 +1,349 @@
+{
+"type": "FeatureCollection",
+"features": [
+{ "type": "Feature", "properties": { "LINK_ID": 30760460.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "24", "L_NREFADDR": "22", "R_REFADDR": "27", "R_NREFADDR": "23", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 41.871700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.330250, 6403958.170400 ], [ 1549594.439950, 6403973.130400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730499.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 46.382600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549497.669850, 6403707.960000 ], [ 1549491.100000, 6403710.100000 ], [ 1549488.039950, 6403716.750400 ], [ 1549488.540100, 6403724.550400 ], [ 1549494.379850, 6403733.540000 ], [ 1549499.679900, 6403738.050400 ], [ 1549506.220000, 6403739.250400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760556.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "24", "L_NREFADDR": "16", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 70.310600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549754.276900, 6403854.802400 ], [ 1549728.459850, 6403920.200000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760712.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "9", "R_NREFADDR": "9", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 40.068900 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549571.899950, 6403675.450400 ], [ 1549592.674200, 6403684.530400 ], [ 1549608.619850, 6403691.500000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30837043.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 78.203400 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549741.089950, 6403765.520000 ], [ 1549730.790150, 6403779.880000 ], [ 1549703.919950, 6403834.130400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80545558.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 20.687400 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549909.400050, 6403973.670400 ], [ 1549900.829950, 6403992.491200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760549.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "38", "L_NREFADDR": "36", "R_REFADDR": "33", "R_NREFADDR": "31", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 32.788800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549878.029900, 6403861.890400 ], [ 1549867.520100, 6403892.960000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547479.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "18", "L_NREFADDR": "14", "R_REFADDR": "15", "R_NREFADDR": "13", "SPEED_CAT": "8", "ZIPCODE": "59330", "SHAPE_LEN": 15.654700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549645.069900, 6403971.520000 ], [ 1549638.940000, 6403985.930400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760575.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "24", "L_NREFADDR": "14", "R_REFADDR": "19", "R_NREFADDR": "13", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 118.385000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549703.919950, 6403834.130400 ], [ 1549656.739950, 6403942.710400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760608.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "32", "L_NREFADDR": "32", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 74.462800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549566.450100, 6403780.090400 ], [ 1549635.170150, 6403808.780000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547481.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 13.834500 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549625.900050, 6403981.310400 ], [ 1549638.940000, 6403985.930400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730495.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 63.537000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549535.370100, 6403692.830400 ], [ 1549549.530050, 6403703.030400 ], [ 1549570.300100, 6403708.850400 ], [ 1549570.600050, 6403733.360000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80545560.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 20.545100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.119850, 6403985.020000 ], [ 1549944.182350, 6403996.455200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760664.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "3", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 59.030600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549679.130150, 6403720.210400 ], [ 1549717.099900, 6403730.700000 ], [ 1549726.590150, 6403734.160000 ], [ 1549734.260050, 6403739.820000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547480.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "20", "L_NREFADDR": "20", "R_REFADDR": "21", "R_NREFADDR": "21", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 12.375300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549614.030150, 6403977.820000 ], [ 1549621.149850, 6403980.140000 ], [ 1549625.900050, 6403981.310400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760739.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "13", "R_NREFADDR": "11", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 57.793000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549522.250000, 6403645.880000 ], [ 1549571.899950, 6403675.450400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80545557.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "26", "L_NREFADDR": "20", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 62.216100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549929.770050, 6403914.890400 ], [ 1549909.400050, 6403973.670400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760610.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SLOTTSHOLMSVÄGEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 60.324700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549921.910100, 6403780.010400 ], [ 1549931.136800, 6403785.640000 ], [ 1549946.150050, 6403794.800000 ], [ 1549960.880150, 6403807.230400 ], [ 1549962.209450, 6403808.998400 ], [ 1549968.489850, 6403817.350400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760475.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SPÖTORGET", "L_REFADDR": "9", "L_NREFADDR": "1", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "8", "ZIPCODE": "59330", "SHAPE_LEN": 70.301600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549656.739950, 6403942.710400 ], [ 1549631.800000, 6403936.830400 ], [ 1549614.030150, 6403977.820000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547460.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "30", "L_NREFADDR": "26", "R_REFADDR": "31", "R_NREFADDR": "29", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 62.288000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549496.649950, 6403937.400000 ], [ 1549525.699950, 6403946.670400 ], [ 1549555.330250, 6403958.170400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547482.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 22.019100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549645.069900, 6403971.520000 ], [ 1549637.249850, 6403978.110400 ], [ 1549633.070150, 6403979.170400 ], [ 1549625.900050, 6403981.310400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730502.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 26.440100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549535.370100, 6403692.830400 ], [ 1549528.510100, 6403718.360000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730491.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "48", "L_NREFADDR": "48", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 53.485400 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549608.619850, 6403691.500000 ], [ 1549600.079850, 6403708.100000 ], [ 1549584.219950, 6403739.090400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760461.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "RÅDHUSGATAN", "L_REFADDR": "52", "L_NREFADDR": "50", "R_REFADDR": "43", "R_NREFADDR": "41", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 62.397200 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.330250, 6403958.170400 ], [ 1549531.400050, 6404015.800000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760674.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 13.834500 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549666.080050, 6403715.590400 ], [ 1549679.130150, 6403720.210400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80545555.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SLOTTSHOLMSVÄGEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 185.679000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549968.489850, 6403817.350400 ], [ 1549977.779900, 6403836.400000 ], [ 1549983.460050, 6403858.740000 ], [ 1549982.539900, 6403884.350400 ], [ 1549978.140050, 6403903.230400 ], [ 1549947.139850, 6403954.090400 ], [ 1549927.119850, 6403985.020000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760515.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 22.968600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549938.960000, 6403893.840000 ], [ 1549929.770050, 6403914.890400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760497.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 24.829800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549488.599950, 6403913.910400 ], [ 1549496.649950, 6403937.400000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30837044.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 146.769000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549776.080150, 6403777.100000 ], [ 1549785.590000, 6403778.330400 ], [ 1549886.280100, 6403772.890400 ], [ 1549908.484450, 6403777.327200 ], [ 1549921.910100, 6403780.010400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760477.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "14", "L_NREFADDR": "12", "R_REFADDR": "19", "R_NREFADDR": "11", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 78.700300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549723.519950, 6403934.620000 ], [ 1549697.600000, 6404008.930400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760542.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "22", "L_NREFADDR": "18", "R_REFADDR": "29", "R_NREFADDR": "21", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 34.587000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549798.179850, 6403867.590400 ], [ 1549830.790050, 6403879.130400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760457.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NYGATAN", "L_REFADDR": "8", "L_NREFADDR": "6", "R_REFADDR": "15", "R_NREFADDR": "7", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 45.468000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549796.459950, 6403958.910400 ], [ 1549839.739900, 6403972.810400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573703846.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 8.208130 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549734.260050, 6403739.820000 ], [ 1549738.939900, 6403746.560000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760631.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 46.824600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549741.290150, 6403748.820000 ], [ 1549753.539450, 6403766.201600 ], [ 1549754.750100, 6403767.920000 ], [ 1549761.249950, 6403772.460000 ], [ 1549776.080150, 6403777.100000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760491.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 15.240700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549728.459850, 6403920.200000 ], [ 1549723.519950, 6403934.620000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760566.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 54.648300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549960.410100, 6403843.980000 ], [ 1549959.139950, 6403850.640000 ], [ 1549952.470000, 6403860.580000 ], [ 1549938.960000, 6403893.840000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547447.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 13.369300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.421200, 6403767.822400 ], [ 1549921.910100, 6403780.010400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730503.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 44.681900 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549528.510100, 6403718.360000 ], [ 1549570.600050, 6403733.360000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80545559.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 21.047100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549909.400050, 6403973.670400 ], [ 1549927.119850, 6403985.020000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547444.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 46.504800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549967.599100, 6403744.932000 ], [ 1549943.650000, 6403755.770400 ], [ 1549927.421200, 6403767.822400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730492.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 44.681800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549584.219950, 6403739.090400 ], [ 1549566.450100, 6403780.090400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760700.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "7", "R_NREFADDR": "5", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 62.310700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549608.619850, 6403691.500000 ], [ 1549666.080050, 6403715.590400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760611.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 51.110800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549921.910100, 6403780.010400 ], [ 1549913.480000, 6403787.710400 ], [ 1549891.640000, 6403820.850400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547478.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "24", "L_NREFADDR": "20", "R_REFADDR": "19", "R_NREFADDR": "17", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 31.088600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549656.739950, 6403942.710400 ], [ 1549645.069900, 6403971.520000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760451.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 20.146600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549594.439950, 6403973.130400 ], [ 1549614.030150, 6403977.820000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760525.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "16", "L_NREFADDR": "14", "R_REFADDR": "19", "R_NREFADDR": "15", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 39.254300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549830.790050, 6403879.130400 ], [ 1549867.520100, 6403892.960000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760497.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 24.829800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549488.599950, 6403913.910400 ], [ 1549496.649950, 6403937.400000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573703847.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 3.259030 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549738.939900, 6403746.560000 ], [ 1549741.290150, 6403748.820000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730500.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 31.544900 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549528.510100, 6403718.360000 ], [ 1549511.590050, 6403738.200000 ], [ 1549506.220000, 6403739.250400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730504.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 32.542600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549497.669850, 6403707.960000 ], [ 1549528.510100, 6403718.360000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760589.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "23", "R_NREFADDR": "21", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 47.569300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549771.489900, 6403810.460000 ], [ 1549754.276900, 6403854.802400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270836", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 34.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549967.599100, 6403744.932000 ], [ 1549999.352500, 6403730.830400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270839", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 9.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549967.599100, 6403744.932000 ], [ 1549975.575600, 6403750.824800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270840", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 18.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549975.575600, 6403750.824800 ], [ 1549992.301750, 6403743.152800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 22, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270840", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 16.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1550001.325450, 6403756.464000 ], [ 1549992.301750, 6403743.152800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270842", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 12.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.421200, 6403767.822400 ], [ 1549936.717550, 6403775.876000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270842", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 46.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549936.717550, 6403775.876000 ], [ 1549958.789600, 6403758.524000 ], [ 1549975.575600, 6403750.824800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547691.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270844", "USERID": "LO-JKP", "ST_NAME": "NORRA BANGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 209.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549937.660100, 6403662.140000 ], [ 1549881.800100, 6403701.550400 ], [ 1549764.730000, 6403731.290400 ], [ 1549745.501350, 6403736.423200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547691.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270844", "USERID": "LO-JKP", "ST_NAME": "NORRA BANGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 11.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549734.260050, 6403739.820000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270847", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 32.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.421200, 6403767.822400 ], [ 1549930.803600, 6403753.404000 ], [ 1549928.832400, 6403735.662400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270847", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 53.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549928.832400, 6403735.662400 ], [ 1549962.732350, 6403727.381600 ], [ 1549967.599100, 6403744.932000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270848", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 44.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549928.832400, 6403735.662400 ], [ 1549886.025300, 6403747.621600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270848", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 11.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549886.025300, 6403747.621600 ], [ 1549875.211350, 6403750.643200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270848", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 19.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549888.409150, 6403767.056000 ], [ 1549886.025300, 6403747.621600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270922", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 20.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549760.669300, 6403722.331200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270923", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 126.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549760.669300, 6403722.331200 ], [ 1549771.919700, 6403716.340800 ], [ 1549815.248650, 6403610.940000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547535.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808270933", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 5.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549681.045700, 6403715.598400 ], [ 1549679.130150, 6403720.210400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270933", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 68.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549740.387150, 6403731.321600 ], [ 1549681.045700, 6403715.598400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760732.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270934", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "2", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 56.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549683.510050, 6403654.550400 ], [ 1549667.935400, 6403709.100000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760732.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270934", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "2", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 6.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549667.935400, 6403709.100000 ], [ 1549666.080050, 6403715.590400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270934", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 14.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549681.045700, 6403715.598400 ], [ 1549667.935400, 6403709.100000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270935", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 40.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549763.755600, 6403714.004800 ], [ 1549738.019750, 6403704.509600 ], [ 1549731.660600, 6403715.640800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547428.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "10", "L_NREFADDR": "2", "R_REFADDR": "1", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59331", "SHAPE_LEN": 15.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549766.018350, 6403708.067200 ], [ 1549763.755600, 6403714.004800 ], [ 1549760.669300, 6403722.331200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 48.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549766.018350, 6403708.067200 ], [ 1549736.048550, 6403696.628800 ], [ 1549743.183300, 6403681.558400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547428.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "10", "L_NREFADDR": "2", "R_REFADDR": "1", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59331", "SHAPE_LEN": 22.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549774.118750, 6403686.709600 ], [ 1549766.018350, 6403708.067200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 36.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549774.118750, 6403686.709600 ], [ 1549747.876450, 6403676.916800 ], [ 1549751.868550, 6403670.136800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547428.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270937", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "10", "L_NREFADDR": "2", "R_REFADDR": "1", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59331", "SHAPE_LEN": 25.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549783.651700, 6403662.588800 ], [ 1549778.530150, 6403674.660000 ], [ 1549774.118750, 6403686.709600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547535.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808270938", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 18.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549703.060000, 6403662.590400 ], [ 1549695.854900, 6403679.940000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547535.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808270938", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 38.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549695.854900, 6403679.940000 ], [ 1549681.045700, 6403715.598400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270938", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 27.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549695.854900, 6403679.940000 ], [ 1549710.817400, 6403684.797600 ], [ 1549716.384850, 6403674.867200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270939", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 18.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549704.982200, 6403658.172000 ], [ 1549717.515000, 6403662.725600 ], [ 1549719.527500, 6403657.506400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271124", "USERID": "LO-JKP", "ST_NAME": "FÄNGELSETORGET", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 192.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549908.200950, 6403637.271200 ], [ 1549910.100750, 6403648.924800 ], [ 1549887.237000, 6403693.868800 ], [ 1549878.956000, 6403699.779200 ], [ 1549807.205300, 6403714.370400 ], [ 1549760.669300, 6403722.331200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 1900112527.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808271126", "USERID": "LO-JKP", "ST_NAME": "FÄNGELSETORGET", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 100.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549809.770450, 6403695.048800 ], [ 1549792.424450, 6403687.958400 ], [ 1549824.218800, 6403612.351200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271126", "USERID": "LO-JKP", "ST_NAME": "FÄNGELSETOGET", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 17.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549817.102950, 6403710.910400 ], [ 1549809.770450, 6403695.048800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547449.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271128", "USERID": "LO-JKP", "ST_NAME": "SÖDRA VARVSGATAN", "L_REFADDR": "40", "L_NREFADDR": "32", "R_REFADDR": "21", "R_NREFADDR": "15", "SPEED_CAT": "6", "ZIPCODE": "59350", "SHAPE_LEN": 23.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549989.554600, 6403806.848000 ], [ 1549976.880050, 6403812.990400 ], [ 1549968.489850, 6403817.350400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730501.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271500", "USERID": "LO-JKP", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 9.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549570.600050, 6403733.360000 ], [ 1549579.722100, 6403737.201600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730501.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271500", "USERID": "LO-JKP", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 4.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549579.722100, 6403737.201600 ], [ 1549584.219950, 6403739.090400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 573730505.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271504", "USERID": "LO-JKP", "ST_NAME": "LÄROVERKSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 79.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549471.922100, 6403800.288000 ], [ 1549539.838900, 6403825.187200 ], [ 1549546.809850, 6403827.740000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59332", "SHAPE_LEN": 23.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549574.529850, 6403669.305600 ], [ 1549580.125650, 6403672.576800 ], [ 1549595.345750, 6403678.918400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59332", "SHAPE_LEN": 80.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549595.345750, 6403678.918400 ], [ 1549617.976400, 6403688.348000 ], [ 1549648.329450, 6403702.939200 ], [ 1549654.639250, 6403704.509600 ], [ 1549660.157350, 6403703.329600 ], [ 1549667.935400, 6403709.100000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 68.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549579.722100, 6403737.201600 ], [ 1549586.633550, 6403729.352000 ], [ 1549598.065250, 6403704.509600 ], [ 1549595.698200, 6403698.599200 ], [ 1549588.604750, 6403693.078400 ], [ 1549592.674200, 6403684.530400 ], [ 1549595.345750, 6403678.918400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 185.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549495.671350, 6403901.486400 ], [ 1549498.716350, 6403900.056000 ], [ 1549509.356700, 6403886.655200 ], [ 1549520.005100, 6403865.753600 ], [ 1549533.012150, 6403839.740800 ], [ 1549539.838900, 6403825.187200 ], [ 1549547.990750, 6403807.808000 ], [ 1549557.459650, 6403786.516800 ], [ 1549566.128450, 6403765.624800 ], [ 1549574.805650, 6403746.692800 ], [ 1549579.722100, 6403737.201600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547462.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "44", "L_NREFADDR": "38", "R_REFADDR": "61", "R_NREFADDR": "53", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 14.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549501.325600, 6403908.552800 ], [ 1549497.550150, 6403911.790400 ], [ 1549488.599950, 6403913.910400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 9.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549495.671350, 6403901.486400 ], [ 1549501.325600, 6403908.552800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 29.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549760.487650, 6403761.674400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 130.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549760.487650, 6403761.674400 ], [ 1549773.107600, 6403768.775200 ], [ 1549794.000150, 6403771.925600 ], [ 1549806.223900, 6403771.925600 ], [ 1549847.621200, 6403768.775200 ], [ 1549874.427700, 6403767.595200 ], [ 1549888.409150, 6403767.056000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760574.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "26", "L_NREFADDR": "24", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 5.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549703.919950, 6403834.130400 ], [ 1549709.114300, 6403836.262400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760574.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "26", "L_NREFADDR": "24", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 48.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549709.114300, 6403836.262400 ], [ 1549754.276900, 6403854.802400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760590.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271510", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 37.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549635.170150, 6403808.780000 ], [ 1549670.099800, 6403821.660000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271510", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 45.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549670.099800, 6403821.660000 ], [ 1549661.138750, 6403843.681600 ], [ 1549652.074100, 6403839.340800 ], [ 1549647.702600, 6403850.082400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760590.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271511", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 4.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549670.099800, 6403821.660000 ], [ 1549674.526600, 6403823.292000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760590.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271511", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 31.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549674.526600, 6403823.292000 ], [ 1549703.919950, 6403834.130400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271511", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 23.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549674.526600, 6403823.292000 ], [ 1549664.644350, 6403844.952000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271512", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 47.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549760.487650, 6403761.674400 ], [ 1549753.539450, 6403766.201600 ], [ 1549734.473200, 6403778.625600 ], [ 1549728.022350, 6403793.287200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271513", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 9.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549728.022350, 6403793.287200 ], [ 1549724.270700, 6403801.813600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271513", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 37.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549724.270700, 6403801.813600 ], [ 1549709.114300, 6403836.262400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 51.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549754.276900, 6403854.802400 ], [ 1549737.228050, 6403806.618400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 13.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549737.228050, 6403806.618400 ], [ 1549724.270700, 6403801.813600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547503.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "25", "R_NREFADDR": "25", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 17.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549774.889100, 6403793.607200 ], [ 1549771.489900, 6403810.460000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 40.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549737.228050, 6403806.618400 ], [ 1549769.560700, 6403793.607200 ], [ 1549774.889100, 6403793.607200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547503.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271515", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "25", "R_NREFADDR": "25", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 14.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549776.080150, 6403777.100000 ], [ 1549775.301400, 6403791.567200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547503.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271515", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "25", "R_NREFADDR": "25", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 2.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549775.301400, 6403791.567200 ], [ 1549774.889100, 6403793.607200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 23.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549888.409150, 6403767.056000 ], [ 1549894.924400, 6403766.804800 ], [ 1549911.882600, 6403767.984800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 15.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549911.882600, 6403767.984800 ], [ 1549927.421200, 6403767.822400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760596.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "12", "L_NREFADDR": "6", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 68.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549818.068600, 6403799.888800 ], [ 1549884.091550, 6403818.700000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760596.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "12", "L_NREFADDR": "6", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 7.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549884.091550, 6403818.700000 ], [ 1549891.640000, 6403820.850400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 58.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549911.882600, 6403767.984800 ], [ 1549908.484450, 6403777.327200 ], [ 1549905.284050, 6403786.126400 ], [ 1549884.091550, 6403818.700000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760579.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271517", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 6.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549891.640000, 6403820.850400 ], [ 1549897.839200, 6403822.604000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271517", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 61.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549936.717550, 6403775.876000 ], [ 1549931.136800, 6403785.640000 ], [ 1549927.257050, 6403792.427200 ], [ 1549897.839200, 6403822.604000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271517", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 7.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549982.841250, 6403805.048000 ], [ 1549989.554600, 6403806.848000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271518", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 14.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549775.301400, 6403791.567200 ], [ 1549789.273750, 6403792.036800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271518", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 116.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549789.273750, 6403792.036800 ], [ 1549820.418850, 6403786.116800 ], [ 1549862.599800, 6403784.936000 ], [ 1549905.284050, 6403786.126400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271518", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 29.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549818.068600, 6403799.888800 ], [ 1549789.273750, 6403792.036800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547461.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280759", "USERID": "LO-JKP", "ST_NAME": "VÅRDTRÄDSPLAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "7", "R_NREFADDR": "3", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 93.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549635.170150, 6403808.780000 ], [ 1549633.900000, 6403814.330400 ], [ 1549618.609900, 6403847.560000 ], [ 1549614.980050, 6403851.970400 ], [ 1549605.460050, 6403851.850400 ], [ 1549590.180100, 6403883.960800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547461.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280759", "USERID": "LO-JKP", "ST_NAME": "VÅRDTRÄDSPLAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "7", "R_NREFADDR": "3", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 51.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549590.180100, 6403883.960800 ], [ 1549568.259950, 6403874.780000 ], [ 1549542.790100, 6403864.450400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280759", "USERID": "LO-JKP", "ST_NAME": "VÅRDTRÄDSPLAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 99.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.330250, 6403958.170400 ], [ 1549562.119900, 6403948.800800 ], [ 1549569.040100, 6403929.018400 ], [ 1549576.364550, 6403927.638400 ], [ 1549583.482500, 6403925.468800 ], [ 1549590.600800, 6403917.357600 ], [ 1549593.767950, 6403909.047200 ], [ 1549596.737350, 6403902.126400 ], [ 1549585.857950, 6403894.605600 ], [ 1549590.180100, 6403883.960800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760476.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280801", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "12", "L_NREFADDR": "10", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 62.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549723.519950, 6403934.620000 ], [ 1549782.972000, 6403954.418400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760476.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280801", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "12", "L_NREFADDR": "10", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 14.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549782.972000, 6403954.418400 ], [ 1549796.459950, 6403958.910400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760555.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280803", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "33", "R_NREFADDR": "31", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 37.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549754.276900, 6403854.802400 ], [ 1549789.877500, 6403865.172000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760555.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280803", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "33", "R_NREFADDR": "31", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 8.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549789.877500, 6403865.172000 ], [ 1549798.179850, 6403867.590400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280803", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 44.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549763.127350, 6403899.236800 ], [ 1549769.602000, 6403885.904800 ], [ 1549772.571400, 6403880.964800 ], [ 1549789.877500, 6403865.172000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280805", "USERID": "LO-JKP", "ST_NAME": "GRÖNA GRÄND", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 42.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549782.972000, 6403954.418400 ], [ 1549793.934000, 6403923.878400 ], [ 1549786.164350, 6403917.663200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280805", "USERID": "LO-JKP", "ST_NAME": "GRÖNA GRÄND", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 29.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549786.164350, 6403917.663200 ], [ 1549763.127350, 6403899.236800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760512.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "TRÄDGÅRDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "3", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 16.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549763.127350, 6403899.236800 ], [ 1549754.840050, 6403906.050400 ], [ 1549749.691200, 6403908.812000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760512.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "TRÄDGÅRDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "3", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 24.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549749.691200, 6403908.812000 ], [ 1549728.459850, 6403920.200000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 49.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549786.477600, 6403917.288000 ], [ 1549786.164350, 6403917.663200 ], [ 1549781.471200, 6403923.288000 ], [ 1549778.501800, 6403931.988800 ], [ 1549753.386150, 6403924.078400 ], [ 1549757.605750, 6403919.492000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 7.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549757.605750, 6403919.492000 ], [ 1549762.549850, 6403914.117600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 13.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549749.691200, 6403908.812000 ], [ 1549757.605750, 6403919.492000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760580.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280807", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "42", "L_NREFADDR": "40", "R_REFADDR": "37", "R_NREFADDR": "35", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 25.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549891.640000, 6403820.850400 ], [ 1549883.651250, 6403844.940000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760580.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280807", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "42", "L_NREFADDR": "40", "R_REFADDR": "37", "R_NREFADDR": "35", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 17.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549883.651250, 6403844.940000 ], [ 1549878.029900, 6403861.890400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280807", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 19.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549883.651250, 6403844.940000 ], [ 1549902.215600, 6403851.322400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760588.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "24", "L_NREFADDR": "14", "R_REFADDR": "23", "R_NREFADDR": "15", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 85.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549771.489900, 6403810.460000 ], [ 1549848.872800, 6403847.815200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760588.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "24", "L_NREFADDR": "14", "R_REFADDR": "23", "R_NREFADDR": "15", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 32.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.872800, 6403847.815200 ], [ 1549878.029900, 6403861.890400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 16.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.872800, 6403847.815200 ], [ 1549858.013750, 6403836.851200 ], [ 1549859.956250, 6403835.057600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 8.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549859.956250, 6403835.057600 ], [ 1549865.800200, 6403829.660800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 7.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549859.956250, 6403835.057600 ], [ 1549866.996150, 6403838.471200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760516.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 39.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549867.520100, 6403892.960000 ], [ 1549904.353050, 6403905.936000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760516.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 26.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549904.353050, 6403905.936000 ], [ 1549929.770050, 6403914.890400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760548.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "13", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 43.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549878.029900, 6403861.890400 ], [ 1549916.095200, 6403881.850400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760548.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "13", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 25.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549916.095200, 6403881.850400 ], [ 1549938.960000, 6403893.840000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 26.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549904.353050, 6403905.936000 ], [ 1549912.608150, 6403888.475200 ], [ 1549916.095200, 6403881.850400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760517.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280811", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "34", "L_NREFADDR": "24", "R_REFADDR": "29", "R_NREFADDR": "19", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 23.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.114000, 6403950.774400 ], [ 1549839.739900, 6403972.810400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280811", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 30.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.114000, 6403950.774400 ], [ 1549869.289000, 6403957.700800 ], [ 1549866.484800, 6403965.702400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760517.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280812", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "34", "L_NREFADDR": "24", "R_REFADDR": "29", "R_NREFADDR": "19", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 36.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549867.520100, 6403892.960000 ], [ 1549856.605700, 6403927.937600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760517.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280812", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "34", "L_NREFADDR": "24", "R_REFADDR": "29", "R_NREFADDR": "19", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 24.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549856.605700, 6403927.937600 ], [ 1549855.749950, 6403930.680000 ], [ 1549848.114000, 6403950.774400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280812", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 22.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549856.605700, 6403927.937600 ], [ 1549842.391850, 6403922.888000 ], [ 1549839.991550, 6403930.109600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760453.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280813", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 44.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549839.739900, 6403972.810400 ], [ 1549882.122450, 6403986.464000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760453.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280814", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 4.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549882.122450, 6403986.464000 ], [ 1549885.859750, 6403988.054400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760453.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280814", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 15.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549885.859750, 6403988.054400 ], [ 1549900.829950, 6403992.491200 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280814", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 30.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549885.859750, 6403988.054400 ], [ 1549892.433450, 6403972.732800 ], [ 1549900.598950, 6403962.172000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "SÖDRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 43.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549936.717550, 6403775.876000 ], [ 1549969.438000, 6403803.858400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "SÖDRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 13.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549969.438000, 6403803.858400 ], [ 1549982.841250, 6403805.048000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760579.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 37.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549897.839200, 6403822.604000 ], [ 1549934.253900, 6403832.906400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760579.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 29.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549934.253900, 6403832.906400 ], [ 1549957.490050, 6403839.480000 ], [ 1549960.410100, 6403843.980000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 45.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549969.438000, 6403803.858400 ], [ 1549962.209450, 6403808.998400 ], [ 1549954.459400, 6403814.508800 ], [ 1549934.253900, 6403832.906400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547462.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290805", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "44", "L_NREFADDR": "38", "R_REFADDR": "61", "R_NREFADDR": "53", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 23.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549546.809850, 6403827.740000 ], [ 1549537.632500, 6403849.605600 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 80547462.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290805", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "44", "L_NREFADDR": "38", "R_REFADDR": "61", "R_NREFADDR": "53", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 70.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549537.632500, 6403849.605600 ], [ 1549533.320150, 6403859.880000 ], [ 1549518.660050, 6403889.780000 ], [ 1549507.790100, 6403903.010400 ], [ 1549501.325600, 6403908.552800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808290805", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 46.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549537.632500, 6403849.605600 ], [ 1549561.402400, 6403858.662400 ], [ 1549569.328850, 6403839.652000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760609.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290829", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "46", "L_NREFADDR": "46", "R_REFADDR": "65", "R_NREFADDR": "63", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 26.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549566.450100, 6403780.090400 ], [ 1549555.974950, 6403804.818400 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760609.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290829", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "46", "L_NREFADDR": "46", "R_REFADDR": "65", "R_NREFADDR": "63", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 24.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.974950, 6403804.818400 ], [ 1549546.809850, 6403827.740000 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808290829", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 37.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.974950, 6403804.818400 ], [ 1549577.560500, 6403813.319200 ], [ 1549572.603450, 6403826.820800 ] ] } }
+,
+{ "type": "Feature", "properties": { "LINK_ID": 30760474.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808290830", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "36", "L_NREFADDR": "32", "R_REFADDR": "51", "R_NREFADDR": "49", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 58.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549496.649950, 6403937.400000 ], [ 1549483.100050, 6403973.990400 ], [ 1549475.242550, 6403991.259200 ] ] } }
+
+]
+}
diff --git a/misc/openlayers/examples/data/tazdem.tiff b/misc/openlayers/examples/data/tazdem.tiff
new file mode 100644
index 0000000..4f58402
--- /dev/null
+++ b/misc/openlayers/examples/data/tazdem.tiff
Binary files differ
diff --git a/misc/openlayers/examples/debug.html b/misc/openlayers/examples/debug.html
new file mode 100644
index 0000000..95a909b
--- /dev/null
+++ b/misc/openlayers/examples/debug.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Debug Example</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function consoleLog() {
+ OpenLayers.Console.log("This is the result of an OpenLayers.Console.log() call");
+ }
+ function consoleWarn() {
+ OpenLayers.Console.warn("This is the result of an OpenLayers.Console.warn() call");
+ }
+ function consoleError() {
+ OpenLayers.Console.error("This is the result of an OpenLayers.Console.error() call");
+ }
+ function consoleDir() {
+ OpenLayers.Console.dir(OpenLayers);
+ }
+ function consoleDirxml() {
+ OpenLayers.Console.dirxml(document.getElementsByTagName('body')[0]);
+ }
+ </script>
+ </head>
+ <body>
+ <h1 id="title">Debug Example</h1>
+
+ <div id="tags">
+ debugging, error, fix, fixing, console, firebug, developers, advanced
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate console calls to a Firebug console. Requires Firefox. Mostly for developers.
+ </p>
+
+ <div id="docs">
+ <p>To run OpenLayers in debug mode, include the following script
+ tag <b>before</b> the tag that loads OpenLayers:</p>
+
+ <pre> &lt;script src="../lib/Firebug/firebug.js"&gt;&lt;/script&gt;</pre>
+
+ <p>The path to firebug.js must be relative to your
+ html file. With this script included calls to OpenLayers.Console
+ will be displayed in the Firebug console. For browsers without
+ the Firebug extension, the script creates a Firebug Lite console.
+ This console can be opened by hitting <b>F12</b> or <b>Ctrl+Shift+L</b>
+ (<b>Command+Shift+L</b> on a Mac). If you want the Firebug Lite console
+ to be open when the page loads, add <b>debug="true"</b> to the opening
+ html tag of your page. Open the console and click on the links below
+ to see console calls.</p>
+ <ul>
+ <li>
+ <a href="javascript: void(consoleLog());">OpenLayers.Console.log()</a>
+ </li>
+ <li>
+ <a href="javascript: void(consoleWarn());">OpenLayers.Console.warn()</a>
+ </li>
+ <li>
+ <a href="javascript: void(consoleError());">OpenLayers.Console.error()</a>
+ </li>
+ <li>
+ <a href="javascript: void(consoleDir());">OpenLayers.Console.dir()</a>
+ </li>
+ <li>
+ <a href="javascript: void(consoleDirxml());">OpenLayers.Console.dirxml()</a>
+ </li>
+ </ul>
+ <p>The Firebug website has a complete list of
+ <a href="http://www.getfirebug.com/console.html">console calls</a>.
+ Note that not all are supported with Firebug Lite.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/document-drag.html b/misc/openlayers/examples/document-drag.html
new file mode 100644
index 0000000..7f64b2b
--- /dev/null
+++ b/misc/openlayers/examples/document-drag.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Document Drag Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map', {controls: [
+ new OpenLayers.Control.Navigation({documentDrag: true}),
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.ArgParser(),
+ new OpenLayers.Control.Attribution()
+ ]} );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Document Drag Example</h1>
+
+ <div id="tags">
+ drag
+ </div>
+
+ <div id="shortdesc">Keep on dragging even when the mouse cursor moves outside of the map</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows how to make a map draggable outside of the map itself.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/donut.html b/misc/openlayers/examples/donut.html
new file mode 100644
index 0000000..4142b75
--- /dev/null
+++ b/misc/openlayers/examples/donut.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Polygon Hole Digitizing</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #controlToggle li {
+ list-style: none;
+ }
+ .olControlAttribution {
+ font-size: 9px;
+ bottom: 2px;
+ }
+ #output {
+ margin: 1em;
+ font-size: 0.9em;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Drawing Holes in Polygons</h1>
+ <div id="tags">
+ draw polygon hole
+ </div>
+ <p id="shortdesc">
+ The DrawFeature control can be used to digitize donut polygons.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked">
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);">
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ </ul>
+ <div id="output"></div>
+ <div id="docs">
+ <p>
+ To digitize holes in polygons, hold down the <code>Alt</code>
+ key and draw over an existing polygon. By default, the
+ <code>Shift</code> key triggers freehand drawing. Use a
+ combination of the <code>Shift</code> and <code>Alt</code> keys
+ to digitize holes in freehand mode.
+ </p>
+ <p>
+ See the <a href="donut.js" target="_blank">
+ donut.js source</a> for details on how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="donut.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/donut.js b/misc/openlayers/examples/donut.js
new file mode 100644
index 0000000..067be62
--- /dev/null
+++ b/misc/openlayers/examples/donut.js
@@ -0,0 +1,44 @@
+// allow testing of specific renderers via "?renderer=Canvas", etc
+var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.OSM(),
+ new OpenLayers.Layer.Vector("Vector Layer", {
+ renderers: renderer
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+});
+
+var draw = new OpenLayers.Control.DrawFeature(
+ map.layers[1],
+ OpenLayers.Handler.Polygon,
+ {handlerOptions: {holeModifier: "altKey"}}
+);
+map.addControl(draw);
+
+// optionally listen for sketch events on the layer
+var output = document.getElementById("output");
+function updateOutput(event) {
+ window.setTimeout(function() {
+ output.innerHTML = event.type + " " + event.feature.id;
+ }, 100);
+}
+map.layers[1].events.on({
+ sketchmodified: updateOutput,
+ sketchcomplete: updateOutput
+});
+
+// add behavior to UI elements
+function toggleControl(element) {
+ if (element.value === "polygon" && element.checked) {
+ draw.activate();
+ } else {
+ draw.deactivate();
+ }
+}
+document.getElementById("noneToggle").checked = true;
diff --git a/misc/openlayers/examples/drag-feature.html b/misc/openlayers/examples/drag-feature.html
new file mode 100644
index 0000000..1b2f649
--- /dev/null
+++ b/misc/openlayers/examples/drag-feature.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Drag Feature Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controls {
+ width: 512px;
+ }
+ #controlToggle {
+ padding-left: 1em;
+ }
+ #controlToggle li {
+ list-style: none;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectors, controls;
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ vectors = new OpenLayers.Layer.Vector("Vector Layer", {
+ renderers: renderer
+ });
+
+ map.addLayers([wms, vectors]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ controls = {
+ point: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.Point),
+ line: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.Path),
+ polygon: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.Polygon),
+ drag: new OpenLayers.Control.DragFeature(vectors)
+ };
+
+ for(var key in controls) {
+ map.addControl(controls[key]);
+ }
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+ document.getElementById('noneToggle').checked = true;
+ }
+
+ function toggleControl(element) {
+ for(key in controls) {
+ var control = controls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Drag Feature Example</h1>
+
+ <div id="tags">
+ point, line, linestring, polygon, digitizing, geometry, draw, drag
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates point, line and polygon creation and editing.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="controls">
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="point" id="pointToggle" onclick="toggleControl(this);" />
+ <label for="pointToggle">draw point</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="line" id="lineToggle" onclick="toggleControl(this);" />
+ <label for="lineToggle">draw line</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="drag" id="dragToggle"
+ onclick="toggleControl(this);" />
+ <label for="dragToggle">drag feature</label>
+ </li>
+ </ul>
+ </div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/draw-feature.html b/misc/openlayers/examples/draw-feature.html
new file mode 100644
index 0000000..f70e9ea
--- /dev/null
+++ b/misc/openlayers/examples/draw-feature.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Draw Feature Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ p {
+ width: 512px;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, drawControls;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ var pointLayer = new OpenLayers.Layer.Vector("Point Layer");
+ var lineLayer = new OpenLayers.Layer.Vector("Line Layer");
+ var polygonLayer = new OpenLayers.Layer.Vector("Polygon Layer");
+ var boxLayer = new OpenLayers.Layer.Vector("Box layer");
+
+ map.addLayers([wmsLayer, pointLayer, lineLayer, polygonLayer, boxLayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ drawControls = {
+ point: new OpenLayers.Control.DrawFeature(pointLayer,
+ OpenLayers.Handler.Point),
+ line: new OpenLayers.Control.DrawFeature(lineLayer,
+ OpenLayers.Handler.Path),
+ polygon: new OpenLayers.Control.DrawFeature(polygonLayer,
+ OpenLayers.Handler.Polygon),
+ box: new OpenLayers.Control.DrawFeature(boxLayer,
+ OpenLayers.Handler.RegularPolygon, {
+ handlerOptions: {
+ sides: 4,
+ irregular: true
+ }
+ }
+ )
+ };
+
+ for(var key in drawControls) {
+ map.addControl(drawControls[key]);
+ }
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ document.getElementById('noneToggle').checked = true;
+ }
+
+ function toggleControl(element) {
+ for(key in drawControls) {
+ var control = drawControls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+
+ function allowPan(element) {
+ var stop = !element.checked;
+ for(var key in drawControls) {
+ drawControls[key].handler.stopDown = stop;
+ drawControls[key].handler.stopUp = stop;
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Draw Feature Example</h1>
+
+ <div id="tags">
+ point, line, linestring, polygon, box, digitizing, geometry, draw, drag
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate on-screen digitizing tools for point, line, polygon and box creation.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="point" id="pointToggle" onclick="toggleControl(this);" />
+ <label for="pointToggle">draw point</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="line" id="lineToggle" onclick="toggleControl(this);" />
+ <label for="lineToggle">draw line</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="box" id="boxToggle" onclick="toggleControl(this);" />
+ <label for="boxToggle">draw box</label>
+ </li>
+ <li>
+ <input type="checkbox" name="allow-pan" value="allow-pan" id="allowPanCheckbox" checked=true onclick="allowPan(this);" />
+ <label for="allowPanCheckbox">allow pan while drawing</label>
+ </li>
+ </ul>
+
+ <div id="docs">
+ <p>With the point drawing control active, click on the map to add a point.</p>
+ <p>With the line drawing control active, click on the map to add the points that make up your line.
+ Double-click to finish drawing.</p>
+ <p>With the polygon drawing control active, click on the map to add the points that make up your
+ polygon. Double-click to finish drawing.</p>
+ <p>With the box drawing control active, click in the map and drag the mouse to get a rectangle. Release
+ the mouse to finish.</p>
+ <p>With any drawing control active, paning the map can still be achieved. Drag the map as
+ usual for that.</p>
+ <p>Hold down the shift key while drawing to activate freehand mode. While drawing lines or polygons
+ in freehand mode, hold the mouse down and a point will be added with every mouse movement.<p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/draw-undo-redo.html b/misc/openlayers/examples/draw-undo-redo.html
new file mode 100644
index 0000000..6d5fa72
--- /dev/null
+++ b/misc/openlayers/examples/draw-undo-redo.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Undo/Redo Drawing Methods</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Undo/Redo Drawing</h1>
+ <p id="shortdesc">
+ Demonstrates the use of undo &amp; redo methods while drawing.
+ </p>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ Use <code>Ctrl-Z</code> or <code>⌘-Z</code> to undo while drawing.
+ Use <code>Ctrl-Y</code> or <code>⌘-Y</code> to redo what you have
+ undone. Use <code>Esc</code> to cancel the current sketch.
+ <p>
+ The <code>control.undo</code> method works on the current
+ sketch, removing the most recently added point.
+ The <code>control.redo</code> method adds back items that were
+ removed from an undo. To fully erase a sketch, call the
+ <code>control.cancel</code> method.
+ </p><p>
+ View the <a href="draw-undo-redo.js" target="_blank">draw-undo-redo.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+
+ <script src="draw-undo-redo.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/draw-undo-redo.js b/misc/openlayers/examples/draw-undo-redo.js
new file mode 100644
index 0000000..73900ab
--- /dev/null
+++ b/misc/openlayers/examples/draw-undo-redo.js
@@ -0,0 +1,45 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "Global Imagery",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "bluemarble"},
+ {tileOrigin: new OpenLayers.LonLat(-180, -90)}
+ ),
+ new OpenLayers.Layer.Vector()
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+});
+
+var draw = new OpenLayers.Control.DrawFeature(
+ map.layers[1], OpenLayers.Handler.Path
+);
+map.addControl(draw);
+draw.activate();
+
+OpenLayers.Event.observe(document, "keydown", function(evt) {
+ var handled = false;
+ switch (evt.keyCode) {
+ case 90: // z
+ if (evt.metaKey || evt.ctrlKey) {
+ draw.undo();
+ handled = true;
+ }
+ break;
+ case 89: // y
+ if (evt.metaKey || evt.ctrlKey) {
+ draw.redo();
+ handled = true;
+ }
+ break;
+ case 27: // esc
+ draw.cancel();
+ handled = true;
+ break;
+ }
+ if (handled) {
+ OpenLayers.Event.stop(evt);
+ }
+}); \ No newline at end of file
diff --git a/misc/openlayers/examples/dynamic-text-layer.html b/misc/openlayers/examples/dynamic-text-layer.html
new file mode 100644
index 0000000..a361b73
--- /dev/null
+++ b/misc/openlayers/examples/dynamic-text-layer.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Vector Behavior Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var layer = new OpenLayers.Layer.Vector("POIs", {
+ strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1.1})],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "textfile.txt",
+ format: new OpenLayers.Format.Text()
+ })
+ });
+
+ map.addLayers([wms, layer]);
+ map.zoomToMaxExtent();
+
+ // Interaction; not needed for initial display.
+ selectControl = new OpenLayers.Control.SelectFeature(layer);
+ map.addControl(selectControl);
+ selectControl.activate();
+ layer.events.on({
+ 'featureselected': onFeatureSelect,
+ 'featureunselected': onFeatureUnselect
+ });
+ }
+
+
+ // Needed only for interaction, not for the display.
+ function onPopupClose(evt) {
+ // 'this' is the popup.
+ var feature = this.feature;
+ if (feature.layer) { // The feature is not destroyed
+ selectControl.unselect(feature);
+ } else { // After "moveend" or "refresh" events on POIs layer all
+ // features have been destroyed by the Strategy.BBOX
+ this.destroy();
+ }
+ }
+ function onFeatureSelect(evt) {
+ feature = evt.feature;
+ popup = new OpenLayers.Popup.FramedCloud("featurePopup",
+ feature.geometry.getBounds().getCenterLonLat(),
+ new OpenLayers.Size(100,100),
+ "<h2>"+feature.attributes.title + "</h2>" +
+ feature.attributes.description,
+ null, true, onPopupClose);
+ feature.popup = popup;
+ popup.feature = feature;
+ map.addPopup(popup, true);
+ }
+ function onFeatureUnselect(evt) {
+ feature = evt.feature;
+ if (feature.popup) {
+ popup.feature = null;
+ map.removePopup(feature.popup);
+ feature.popup.destroy();
+ feature.popup = null;
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Dynamic POIs via a Text Layer</h1>
+ <div id="tags">
+ poi, dynamic data, text, format, strategy, popup, select, selection
+ </div>
+ <p id="shortdesc">
+ Loading dynamic data from a text file.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The vector layer shown uses the BBOX strategy, the HTTP protocol,
+ and the Text format.
+ This setup appends "?bbox=west,south,east,north" to every
+ request. This allows you to configure the location as something
+ like 'textfile.php', and take the '?bbox=' parameter to select
+ data from a database or the like.</p>
+ <p>There is nothing about this example that limits it to text files;
+ you can do the same thing with KML, GeoJSON, etc.</p>
+ <p>This is an alternative to something like the <a href="http://wiki.openstreetmap.org/index.php/OpenLayers_Dynamic_POI">OpenStreetMap "Dynamic POI"</a> example. The Layer is a standard vector layer, and interaction can be
+ configured via the SelectFeature control, as you can see in the
+ latter half of the code, which allows you to open a popup when
+ a feature is selected.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/editing-methods.html b/misc/openlayers/examples/editing-methods.html
new file mode 100644
index 0000000..5a28710
--- /dev/null
+++ b/misc/openlayers/examples/editing-methods.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Editing Methods</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Editing Methods</h1>
+ <p id="shortdesc">
+ Demonstrates the use of editing methods for manipulating geometries
+ while drawing.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <ul id="methods">
+ <li><a href="#" id="insertXY">insert x,y</a></li>
+ <li><a href="#" id="insertDeltaXY">insert dx,dy</a></li>
+ <li><a href="#" id="insertDirectionLength">insert direction/length</a></li>
+ <li><a href="#" id="insertDeflectionLength">insert deflection/length</a></li>
+ <li><a href="#" id="finishSketch">finish sketch</a></li>
+ <li><a href="#" id="cancel">cancel sketch</a></li>
+ </ul>
+
+ <div id="docs">
+ <p>
+ The <code>control.insertXY</code> method inserts a point at the given
+ map coordinates (x, y) immediately prior to the most recent point
+ (under the mouse).
+ The <code>control.insertDeltaXY</code> method inserts a point at
+ the given offset values (dx, dy) from the previously added point.
+ The <code>control.insertDirectionLength</code> method inserts a
+ point at offset direction and length from the previously added point.
+ Direction is measured counter-clockwise from the positive x-axis.
+ The <code>control.insertDeflectionLength</code> method inserts a
+ point at offset deflection and length from the previously added point.
+ Deflection is measured counter-clockwise from the previous line
+ segment.
+ The <code>control.finishSketch</code> method completes the current
+ sketch without adding the point under the user's mouse. This
+ allows a sketch to be finished without a double-click.
+ The <code>control.cancel</code> method discards the current
+ sketch and leaves the control active.
+ The <code>control.insertXY</code> method may be called before
+ any points are digitized manually. The other methods have no
+ effect until at least one point has been added to the sketch.
+ </p><p>
+ View the <a href="editing-methods.js" target="_blank">editing-methods.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+
+ <script src="editing-methods.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/editing-methods.js b/misc/openlayers/examples/editing-methods.js
new file mode 100644
index 0000000..03d5e82
--- /dev/null
+++ b/misc/openlayers/examples/editing-methods.js
@@ -0,0 +1,83 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "Global Imagery",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "bluemarble"},
+ {tileOrigin: new OpenLayers.LonLat(-180, -90)}
+ ),
+ new OpenLayers.Layer.Vector()
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+});
+
+var draw = new OpenLayers.Control.DrawFeature(
+ map.layers[1], OpenLayers.Handler.Path
+);
+map.addControl(draw);
+draw.activate();
+
+// handle clicks on method links
+document.getElementById("insertXY").onclick = function() {
+ var values = parseInput(
+ window.prompt(
+ "Enter map coordinates for new point (e.g. '-111, 46')", "x, y"
+ )
+ );
+ if (values != null) {
+ draw.insertXY(values[0], values[1]);
+ }
+};
+document.getElementById("insertDeltaXY").onclick = function() {
+ var values = parseInput(
+ window.prompt(
+ "Enter offset values for new point (e.g. '15, -10')", "dx, dy"
+ )
+ );
+ if (values != null) {
+ draw.insertDeltaXY(values[0], values[1]);
+ }
+};
+document.getElementById("insertDirectionLength").onclick = function() {
+ var values = parseInput(
+ window.prompt(
+ "Enter direction and length offset values for new point (e.g. '-45, 10')", "direction, length"
+ )
+ );
+ if (values != null) {
+ draw.insertDirectionLength(values[0], values[1]);
+ }
+};
+document.getElementById("insertDeflectionLength").onclick = function() {
+ var values = parseInput(
+ window.prompt(
+ "Enter deflection and length offset values for new point (e.g. '15, 20')", "deflection, length"
+ )
+ );
+ if (values != null) {
+ draw.insertDeflectionLength(values[0], values[1]);
+ }
+};
+document.getElementById("cancel").onclick = function() {
+ draw.cancel();
+};
+document.getElementById("finishSketch").onclick = function() {
+ draw.finishSketch();
+};
+
+function parseInput(text) {
+ var values = text.split(",");
+ if (values.length !== 2) {
+ values = null;
+ } else {
+ values[0] = parseFloat(values[0]);
+ values[1] = parseFloat(values[1]);
+ if (isNaN(values[0]) || isNaN(values[1])) {
+ window.alert("The two values must be numeric.");
+ values = null;
+ }
+ }
+ return values;
+}
diff --git a/misc/openlayers/examples/editingtoolbar-outside.html b/misc/openlayers/examples/editingtoolbar-outside.html
new file mode 100644
index 0000000..5acc625
--- /dev/null
+++ b/misc/openlayers/examples/editingtoolbar-outside.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Custom Editing Toolbar</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlEditingToolbar {
+ float:left;
+ width: 116px;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map('map', {theme: null});
+ layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ );
+ map.addLayer(layer);
+
+ vlayer = new OpenLayers.Layer.Vector( "Editable" );
+ map.addLayer(vlayer);
+ var container = document.getElementById("panel");
+ var panel = new OpenLayers.Control.EditingToolbar(
+ vlayer, {div: container}
+ );
+ map.addControl(panel);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers EditingToolbar Outside Viewport</h1>
+ <div id="tags">
+ digitizing, point, line, linestring, polygon, editing, positioning, style
+ </div>
+ <p id="shortdesc">
+ Display an editing toolbar panel outside the map viewport.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="panel" class="olControlEditingToolbar"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/editingtoolbar.html b/misc/openlayers/examples/editingtoolbar.html
new file mode 100644
index 0000000..d317183
--- /dev/null
+++ b/misc/openlayers/examples/editingtoolbar.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Editing Toolbar Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css">
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ vlayer = new OpenLayers.Layer.Vector( "Editable" );
+ map = new OpenLayers.Map( 'map', {
+ controls: [
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.EditingToolbar(vlayer)
+ ]
+ });
+ map.addLayers([layer, vlayer]);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Editing Toolbar Example</h1>
+
+ <div id="tags">
+ digitizing, point, line, linestring, polygon, editing
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate polygon, polyline and point creation and editing tools.
+ </p>
+
+ <div id="panel"></div>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/encoded-polyline.html b/misc/openlayers/examples/encoded-polyline.html
new file mode 100644
index 0000000..c40a243
--- /dev/null
+++ b/misc/openlayers/examples/encoded-polyline.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 12;
+ var lat = 52.85;
+ var zoom = 9;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var encoded = "m}e`IqvbgAkJqyAoI}w@wTupA}Myb@{CmFsDcEuEgCgFa@oFj@mNzFsFpEiLpQmDxIkMxa@cGpVqE`XqC~Y_Bfw@b@jdAyAzHyCeBRmGlDIp@zHcDpBi@kG`D{@hArHuCtDqBiEhCsCxBfGiBzGcE@{@yF|GtBg@xIsDrB{BcEbAoFnDt@t@|I{BxGiDgABqG`DkAzBbG_A`IoDd@q@cGrD}AhBvGcC~FgDeBbAwF~DjBPrIqDjC}B}DlBmF|DvARbJeDlEuCqCfAiFdExAh@fIwCbEuCgCf@mG~CqBdDvDE|IgDzCsBaE~AgFvD|BV|IsCvG{Et@qCgERaHnCyElEBtCrFMrI_D|DqCyCz@_GlQ~ApEcBdq@es@vx@srApC_Fjc@}y@fCoEfCkCtCvArAvHf@tJCbJeBnHqDrD_E\\qDuC{AeHTaHvBsDbD?lEvPbD~C~Jb@`DaCvDjAStJmEnCwDmAEsGxDoA}B|S}AuEdC}DfEfDNbJmDjCsB}Dj@wG~CyDvJaE~HkHxMwOjMiMjMoK~KeGfEmDdHaMvLka@tNc]pLeSzMcQ|I_UjEcOnCaOh@oG~@iPc@gGqBwEuCmDuCuCeDyA{DHoP|FyIpG{BtF}@lIRzUcAbI_Np\\uHrKsGpOgMtm@o\\bdA_Qvo@uHb_@{Gl^_Df^iB`JyCvHgEnCuCyCzEkCt@rKiC|FyD^sBsD|@cEhDRtBtF_FpNeD}Ag@kFpAgE|Co@pCjECjJkDlEmDqCIqG`C{CvDdBpAfI_BhIgEpCwG_InAmEhDe@~CvDt@hIsAnIsDjEcEWwAqFz@qGfDgChD|DsF|L_AeFdCqBxC|FoAdIoEn@cB{E|A{DlDrC\\rIiCrG_E`@yA}DpAoEbDI|BbFF|IkCrGaEp@eC{CYmF|AkD`DDrCzEHvIkCdGkEEyA{ExAkElDF|BbGm@hJaErDyD_B{@gGzAqElDRlBzGk@pJuDtEaEeAq@wGlBgEnDQ|CbFmAhTqEvAaFcLf@kGpCqMvBaUvA_FvCcD`E`BxAtIaHlK{BgEn@uGjDqApDhE`AxJiA|IkDpCsDuAcBoF~D}K~CiBzIwBtWoJlRoI`VsLzPmKrv@_r@`e@m`@jKcK|IuKpYeWbDmBrDhCNnIyC`EkD}Aa@yFtAyD|CJdBxHiB|GoDEgBuEk@oFf@{ErD]fBvGqG`LuCmBAmF`DgAtCbGHnIoBpG{DnA}BcDt@uFxD]tCbGNnIwCnI{DB_BmEz@sF`EBrB|Ga@xI}BnFsDR{BoDm@oF]wNwDy]gEap@eFu^YmOu@yFmB_FwCuD{DaC}q@oT_Y{Eo_@{E{Oq@}DHoYtCiEtCkBbI~@xIhDnClBoDoAoF}DCeCxFHbJpBzGnDj@hA{E}BsD}DdDo@tJtBnGjDSeA}LuDdCa@lJfC~FrDbA~CeCx@qGoC}DiDvDBbJnDrCpCyCUyFsDYqBfH`AdIlDtBdC}CC{FgCiCqDfDm@rJdAvIbD~EfE[nB{EqAeFgEL{DpEuAfJ~@lJnDtEfEDjCyDNmGsBcEcEr@eCbIf@nKvDtErDiAv@_GsB}DwDr@wIdMcO~OcE~CoEpA{EUaDqDR_GrDl@T`JsDzCwC_Dh@uFzDIvChFFpJ_DzFkEAmBmFIyGnDmB`ChGgAbIaEGYeGxD[nAnHoChF_DkC\\cGlDcAzCdEt@tIcBrHyDdAuBgEn@kG`DyAbDfDp@tI}BlGaE\\qBqE`@mGlDmB`DpDBrIcDbEcEEgCaE]aHxBqEtErA`CpHO~J_CnIyD`CuCeDEsHfC}E`EDlCfGLdK}BvHgE`BsDaCcAqGlA}F`D{AfDvBbB~H]lJyCxFkERkCkE]cHxAoFpDyA~DIvDq@tUiKhFuAnNv@vNCxZoBpE@rd@pEpRRhLi@rPuBvj@sLbNwE|I{@tD~A~CbEzBjHjCpTnCjm@tAnv@u@vv@}DtnAyA`T}Jpd@wNxa@_Srd@iAbHBvIrAbIhDlC`CeDo@gGiDgAwCfEu@dIf@zHlB`H~CpD`CaCaAoFwDRkAhIdBbH|CY?uF_DmByCbENrIhDhD`CkCoAeFyDj@gBfHdA~HhDhBfBiDkAgFaERmBjHz@rIdD|BdAkKsDk@}BbGl@hIbD|BvBeD{@yFoDm@wBnG|@xHlDtA~A_E{AcFwDP_B`HnAlHjDbAxAqE{A{EqDXeBvGxAtHtDLn@yFaCgDcDtBk@zH`CxFpDa@f@cGaCuDgDfAkAhHhAjHdDnBfCmCOkG{ByDkN_PsIwL{^}o@}M}PqZw[_f@gk@sb@o_@wJkFie@yRwSmEuE[qEnA{E\\wCqCDuFxCy@dBtG}BhGmDsAOaFxCoAzC`EHtIeDzDuCoC^iFbDSlBfGkAlIsDdBuBeDz@mEpDdA|@jIwBlGuDBgTqo@kLcm@cEwNqT}y@eIib@kLqc@yLuZcNgb@kOua@gOgYuMwa@}B{EoInDmB`JWfK~AtHpDd@tAqEwBsDwDlCBlIhG_BmCgD}CfEt@fI`EJ^mGeDiB{CzEf@|IjDtA|AyFmBsEsDtAjAlQxCsBu@gGcEj@yAzHvBrG~CqASuGcEOcBtHvBpG`DyAK{GgDsBwCtFfAhIrDDn@eGkCsD}CpEx@~IhDFGaG_EUwAjIzBbGpC_BSwGkb@c`A_CgHsb@scB}S_eAkHa\\gYunAgKy\\qVa`AaHy_@aUcbA}Nwt@{BuHeHmLaC{FyAuGd@mGhDs@pChGg@lJmDhCuC_C_@{EvBgCrDjCpAnI{ApIsDdAmB_DXmFtCwBnDzB~@`JeCbHsDf@gE{Hw@{MiBuLmAmG_XwpAaRoiAyQux@_Ho_@gPkn@o@uHp@gIpDqCpCbGiBfGwDiA]qGjCkD|DvB\\nIqCfEyCqBz@yFjE~@\\vIcD`CmBkEhA{GfEApA~HcC`FwDkAcByFiFoWaHyUcByMiGcYmSoq@uSs_@uh@yx@{O}XyIyPsNy_@yU}g@wb@ss@ui@qw@eJ{JgU}Oko@ko@gEoDeLaFaCuEp@iGtDg@`B`IiCvF{CmBLcGtCkCxDlC`@pJeCxH}DnCaEe@kC}DeBgFoCyCcEl@cBnHvCtEjCaE{AoF}Df@iCvGHtJhDzErD}Aj@mGqBuF{CwBcIcB{HwD_SyRoa@gh@mt@mfAyn@st@sS_X{^qo@}Vii@y\\_h@mM_UgE{L|@uFnEf@lCtHYjJ{CfFwDf@aMiIeDw@sCiBoB}CsDkMgOmw@_Nox@SwG^eHlDyBzBzFwAjG}DS{AoFlB{EzC|DeA`HcDu@R}FpDR^|HeDzAm@kFhDyA~A|GeCtE}BeD~AcFhDrCShIiD|@]oGlDiA~@tH_DnDcBmEfCeDfC~F_BnHeDiAp@iGjDxAH~IkDvBsAkFnCyC|BjGiBlHsD_@NcGrDp@BnIsDdCqBaE|BwD|CnEcAzHsDLg@gG|CiBjCdFSbJsDbBsAoFpCqC`CpF_BpGcDoARgGlDSbBhHyApHyDp@g@oFjDSZjJiClGiD[f@_GvDbBBvJkC~EaCeDrBoEdDzDw@pHiDq@`@iG|DDnAfH}CrDsBqE`CiDdCvFcB`HmDmAj@sGfEp@p@nIiDnCcBqEbCwDfD|NiDnAmAqFZyHy@aH}B{EmVuYqJkNmI_SuNco@eEgBsBrGfCpG`EwDyAqEkChFzB`HrDiBu@mFmD~Bn@jIbELt@sG}CcBcBxGtCdFfDqCg@mGsDb@q@hIrDdElCaEgAuFmD`AYbIrDjD~CkD]kHqBuGwc@}kAyd@stAkHyUa[}dA}Tsq@kGgPkSuc@oa@ubAsP{^{EiNkM{h@uS_qAqEi`@kEmb@_D}b@iXqlDuEib@wHoj@aJ{cA_K{z@cJqo@iGkm@cIqm@aW{eBqEc`@cNqfAwOqfA}DaT_Du_@aFsNsDeAwDxAyCzFYdJjCjFhD{@u@qLoDvBu@|IpB~GpGyEiAeFiDHgBrRfDnDdDsBDaHmCoCmDnCq@fJfC~F|Dq@bAyFyA}DiDOiDfEu@rItA`I|D~BnCiDg@_HgEeAwDvGn@fLvEvBnC_FiAcGkEj@_B`JtC|GlDgCg@wGkEGsBnHzAlIfEHn@qGyH~CjA`JpE^rAiGeCgEcEfBk@fJhD`FvFiKiDqOuBsS_JiuAsCcl@gFap@cGsl@qIog@aMigAqPk|@]yGjCgBvBtFyAdHeEYoBeFz@eFjDxB]tHiD\\q@kFdCeDbDb@dAdHkCjFiD_A{BqOqBaFuHyIaPuJ{CqCmAyE`CuDjCzE{ArG_DuApAeFnA`NeDeAv@yF`E~@p@vIuChEkC{CjA_FrDtBd@tI}C`EkD{BmDiOkJ}q@wHi\\yFkZgDmXKcHpCqDlDbDl@vI}AjIyDvAoA{EnC_DjDfCf@dI{C~EoCgCbFiCCvImDxAgAaFpCsCnCtE_AtHoDIQgGbD}@lBlGiBdGiD{@RoGtDy@~BrF}AhGeDqAHwG~De@hCrHmApHuDIq@iGtC{DtDrCPxIcDrDyC{BRsGzDuA`CrQgDhBuAaFrBeF`ExA~@vIoC|E{CsBRmG|DeA`DtFYfJiD`CuCuCdDkKpDbEHxJiD|DyC_Db@aHnEIxAhTqDbAeB_FxAiGfE]tC`H[bJkD~AkCsEaEwPoMkb@oDkQc^omBmVsyAeA_F}CkAoDjE}ApJHzKbId^xFfa@}BbtAlBxGhE|CdC|GDnIaCzE_Dg@kCkCiBcEuCiByDfAcDjEyAbI`Fj_@iB|UoArIoHb[u\\hpAp@fT_@tJcBlJuChI_Zna@}AzGSfJiB|IcExFwSzMoDbDaElOBlJeA|IkG~QcBfJaHzk@uR|rCaA~H{BvGeBbJwAzYiBnu@_BxGcDx@cB}DwDi_AvAoFdDzBm@bIiDWJcGpDBfAhIqCdFoCkC~@wFxDPzB~Fk@bIyDz@aAsFfCeDhD`CCtIiDzDgEJ}DyAcBsEzBsDfClEoAfHiEp@eCoDxAgEbDhDq@tHgDYd@iFxDdAdAdIqBpGkCeHvDx@l@tI_B~VIlWlBbv@Eta@TrLhDrbBHtgAPjHnBfE|CgBiF{HeCnF]zHZnRxFfnArB`r@v@pJxCbE~CqASgG}DH}B|G^fIfDnAdAmFaCqE{DlA{@lH`CrDvBeDoAiGuDnAShIlCnBrAeFiB{EmDdAaAbIHnUaBtp@mClq@]fm@qDrpB{Ctr@CzJhCdFvCoCkAyFmEz@u@xIfCpEtBaDgBqEaE|BBhIdDtAn@qFcDuBaC`GnArGdDg@I}GkDcBoCfF~@nHtDCZoGiDaBiCxFnAxHfDDJuFyD_AgCjG^tIjB~HhQpg@~EnQnDt@`AwGqCyCuCxEdAhIxDa@ZgHyC{B}BjFzAdHlDaA?_HeDm@kB~FxA~GzD_AGeHaDw@aB|FbBnGlDqAMoHeDw@_BnGlBfGhD}A@gHaD_AyApGvB~FrD}Bg@eHqDOuAfGxAxGrDZbByFiB_FgDxAeBfG{BjPeCrDcEa@yDj@kCtETjIpD~BtAsFyCuFqE^aA|HzCnDpBuFwBsFwDjC`@xHvDl@XkHmDmBoBhGxBlFjCcD{AkFiD`Cl@pHnDBPiHsDqAmBlGxBzEjCcDuAuFyDhBDdInDbAf@qG_D_BgCnGiBfe@u@d]yDjkBiDt`AmFv{A{AjZmG~p@o@lLd@vr@dBfIbDbGzB`Ir@nJ_@nX_DfWuBvIuC|DoDBqBoErA{EbDdCM~HeDz@cAiFdCwBrBtEkA|G_DSM_GxCu@rBrF_@pHyC~B{B{DrA}EbDjAtAzHyCfg@I~HbBdSZvJS|KmBlYiDhZiFzp@gGhf@gJzbAsD`u@cGhmCeDlc@HnJlBnIrDjChBgEmBaEuDlDCvIzCjCfBkEuAeF}DjA{@rIfCrFzCeA]cG_EDuBnH|AdHlDe@@eGsDq@cCdGdAfIrDDT_GsDeAkCrGEhKl@xUtCnj@pDrtAfF|vAnKdwDvFl|AmAtIsDz@{@uFhDeAz@nH{ClE{CkCp@sFnD~@t@bIkCpFmDuANiG`E[pBtGaCvGaE]g@cGbDeCzChD]hIaEjA_BeFlBsE|D`CYrIgElCiCuDlAoFxD|@p@pIcDhEmCaDfA_FpDrBp@jJoJh`@uAtLcNjeBcEdgA{C`d@a@fj@f@~fAMvf@zBhHbE`AhCwEWeHeD}BiDtCe@hIhChG|D]|@mG}ByDiDjCSrIdDrElDkCIsGoDoAiC|F~@tIfEbBdC{Eq@gH{D_BeDxDa@|IdCjHfELbByFoAqGcEm@gDhE}DvPqJtl@sBlIkc@dtA}Yrq@{I`MsSpSoKlO{s@jsAgLxRac@xt@yPfVqRxRyCfHaAjWdDhF~CmB{@}FiEz@o@xIrDxCzBaFgBmFsEbBeA|IlCzGfEc@xAkG}@yGmDkCmE\\mDtDwBhHiLbj@kOpm@_FxVmJf]cNzu@{O`w@me@zsBaPji@mBrIsDlDgCcF|@_IbD}ErPwQdDcFjHgMjZoi@vFyMjK}`@|P{t@tUwiAvDi\\Dg^f@}GbB_FlJ_BvBcDrFyVbNu\\bE{IzCHlAvIyCpGyDDoAwE`C}C`CrFaBjHkDWWyFhDc@`AtHgCfGaEb@uD_B}CuCg@}FjCkC~BhFoAnIeEjC{CyCl@mGlDHxAdHwBlGcDcBp@}FhD~@HdIoDnCqBaE`BsEtCvCy@|HuD?[cGhCkEjRuP~k@if@tZyUxI_JvDX`@lImDtCvBqJrB|GxChDvDg@~DmDvd@el@vOwTtK}RrDbAAnIkE~@{@oFnC}AxBpGaClGwC{BdAuE`DhCc@tIwDR?cGjExAIjJsDzB_AoFxCq@nAlHqCtE{BsDhByDnCfEkAzHsDu@RqFhDh@b@vI}DzC_B{EzF|AkAlI}DCSwGbDk@nBvGkA|H}DGc@yF~Cs@z@dIcD~EgCuCnAgEzCdDHjJsDzCgBkErCmErJlGzDlBfh@hXdg@v]lNfHnjBrbA`iAxq@~a@`Ub\\jOjRpMhMdF|`@tHvLnAb[hAja@lE|SHlJYbb@aFlLl@t\\hFbEnBpVzTd_@lUf`@|OvLxCbRpCfYfJfDfBdAnHsCbGoDmA_AmGrA_E~CxBMdJyDbB_BkE~AiDxCtCSnI{DxAgAaF`CqChJdDzE`@ll@v@p\\u@zDs@f@eFqD]uAbJxC~F`CkDeCyCqC|GrBfIlDsAeA_FwDpDLjKrDzBf@yF{D]eAxIxCzE`CoDwBkEoD|DpAtJzD[@cGoDs@gB~HxC~FtCgCaAwFaEdA{@nJtCvGbDoA{@aGaErAItJvDzCa@iLyChObDlD`E]pc@}LdUmDxi@}InLgCzLg@xRdA|DMpAmEaCcD}DrDeA`Jz@~HjCxDxC]pBqCf@kEcAsFAeFnBwBlDY`EuAzY{NrVgJtd@aUhb@}ZhW{O`SwJhDpBrC~FjDdAnCwBrGkJvIiIb`@qTxKqA`Pu@nKsBbQ}FvPmDvTmJ~CIvG|LlDBpC{BhGkIfXyWhJ{Ev[yMdTmQ|NcJrBoCg@aFyDHmBrIhBhIrDj@pB_DoAkEeE~A_BvI|ApHpCgAm@mF}Dz@cA|IjCxFbDw@NaF_D}AgE~CiBdI`AzIvDtCxD@vZaDrV{@du@e@|q@vB`H{CnDW|D`@zBxFq@nI_DlGqEfDkEIwDsDiEGqBjHpBtGtCwByAyFaElCBjJhD`ATiGyDkAoCxGnAlIrDDHcGiEaAiCdI|AlIrDS?iG}Dy@eClIbBlIzDSHqGsDmBgDlG`@vJrD`CbB}EaCqEcExD@zJrD|BbBwEcCcEeEnDKzJhDbEdDcCm@sFoEl@oAzIjCdFhCaCiAcFcE~@wAhIdCzErBoDgCiDaE`DYzIbDtC~AkE{B}DyDdCq@bJzCxDvBeDyBaEgEfD]hJtCdE~CuBlBkFnDeCfEHdD`EIhIyDvBgBmEtB}CdDdAxAnHkCvGoDsAXyFzCwBh]{FnTiC`k@eL|Eq@brAcP`VyFle@}E`RcDvm@gJdUiFjOsBtg@gL~^{JnKgE|g@gT`J{ArYI~RnAnZnDvXnA|PMlEz@dDzE~@lI{BzG_Dq@`@oFbD|@HjJqDbDoA}DnCwBzBhGgBzHyC_BbAmE~I[bDm@v[_NfRaFrMuEtx@a^dP{EtYkDrOH~LdB`}@~GnMdDlEExAmGMwIoVgjDsHebBgKqxBaG_wAgEgkAoBkf@y@yIc@gI??EI";
+ var format = new OpenLayers.Format.EncodedPolyline({geometryType:"polygon"});
+ var vector_layer = new OpenLayers.Layer.Vector();
+ map.addLayer(vector_layer);
+ vector_layer.addFeatures(format.read(encoded));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Encoded Polyline Example</h1>
+
+ <div id="tags">
+ Encoded Polyline, Google
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate the use of the Encoded Polyline format.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This example uses the Encoded Polyline format.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/events.html b/misc/openlayers/examples/events.html
new file mode 100644
index 0000000..652dda0
--- /dev/null
+++ b/misc/openlayers/examples/events.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Event Handling</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #panel {
+ margin: 5px;
+ height: 30px;
+ width: 200px;
+ }
+ #panel div {
+ float: left;
+ margin-left: 5px;
+ width: 25px;
+ height: 25px;
+ border: 1px solid gray;
+ }
+ #output {
+ position: absolute;
+ left: 550px;
+ top: 4em;
+ width: 350px;
+ height: 400px;
+ }
+ div.blueItemInactive {
+ background-color: #aac;
+ }
+ div.blueItemActive {
+ background-color: #33c;
+ }
+ div.orangeItemInactive {
+ background-color: #ca6;
+ }
+ div.orangeItemActive {
+ background-color: #ea0;
+ }
+ div.greenItemInactive {
+ background-color: #aca;
+ }
+ div.greenItemActive {
+ background-color: #3c3;
+ }
+
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, panel;
+
+ function init(){
+
+ // define custom map event listeners
+ function mapEvent(event) {
+ log(event.type);
+ }
+ function mapBaseLayerChanged(event) {
+ log(event.type + " " + event.layer.name);
+ }
+ function mapLayerChanged(event) {
+ log(event.type + " " + event.layer.name + " " + event.property);
+ }
+ map = new OpenLayers.Map('map', {
+ eventListeners: {
+ "moveend": mapEvent,
+ "zoomend": mapEvent,
+ "changelayer": mapLayerChanged,
+ "changebaselayer": mapBaseLayerChanged
+ }
+ });
+
+ panel = new OpenLayers.Control.Panel(
+ {div: document.getElementById("panel")}
+ );
+
+ // define custom event listeners
+ function toolActivate(event) {
+ log("activate " + event.object.displayClass);
+ }
+ function toolDeactivate(event) {
+ log("deactivate " + event.object.displayClass);
+ }
+
+ // Multiple objects can share listeners with the same scope
+ var toolListeners = {
+ "activate": toolActivate,
+ "deactivate": toolDeactivate
+ };
+ var blue = new OpenLayers.Control({
+ type: OpenLayers.Control.TYPE_TOGGLE,
+ eventListeners: toolListeners,
+ displayClass: "blue"
+ });
+ var orange = new OpenLayers.Control({
+ type: OpenLayers.Control.TYPE_TOGGLE,
+ eventListeners: toolListeners,
+ displayClass: "orange"
+ });
+ var green = new OpenLayers.Control({
+ type: OpenLayers.Control.TYPE_TOGGLE,
+ eventListeners: toolListeners,
+ displayClass: "green"
+ });
+
+ // add buttons to a panel
+ panel.addControls([blue, orange, green]);
+ map.addControl(panel);
+
+ var vmap = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ var landsat = new OpenLayers.Layer.WMS(
+ "NASA Global Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {layers: "landsat7"}
+ );
+ var nexrad = new OpenLayers.Layer.WMS(
+ "Nexrad",
+ "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi",
+ {layers:"nexrad-n0r-wmst", transparent: "TRUE", format: 'image/png'},
+ {isBaseLayer: false}
+ );
+
+
+ map.addLayers([vmap, landsat, nexrad]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+
+ }
+ function log(msg) {
+ document.getElementById("output").innerHTML += msg + "\n";
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Event Handling</h1>
+
+ <div id="tags">
+ event, events, handler, listener, cleanup
+ </div>
+
+ <p id="shortdesc">
+ Demonstrating various styles of event handling in OpenLayers.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="panel"></div>
+ <textarea id="output"></textarea>
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/example-list.html b/misc/openlayers/examples/example-list.html
new file mode 100644
index 0000000..3ac9120
--- /dev/null
+++ b/misc/openlayers/examples/example-list.html
@@ -0,0 +1,302 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <!-- This is the example list source: if you are trying to look at the
+ source of an example, YOU ARE IN THE WRONG PLACE. If you want to view
+ the source of just one example, you can typically choose
+ "This Frame -> View source" when right clicking on the exmaple. If not,
+ choose to open the example in a new window (via the context menu
+ click on the link), and view source from there. -->
+ <title>OpenLayers Examples</title>
+ <link rel="alternate" href="example-list.xml" type="application/atom+xml" />
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ html, body {
+ margin: 0;
+ padding: 0;
+ line-height: 1.25em;
+ }
+ #logo {
+ text-shadow: 2px 2px 3px gray;
+ color: white;
+ vertical-align: middle;
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ font-size: 34px;
+ font-family: "Trebuchet MS",Helvetica,Arial,sans-serif;
+ }
+ #logo img {
+ vertical-align: middle;
+ }
+ .ex_container{
+ }
+ .ex_container a {
+ text-decoration: none;
+ padding: 5px 1em;
+ display: block;
+ }
+ .ex_container a:hover {
+ background-color: #eeeeee;
+ }
+ .ex_title{
+ display: inline;
+ font-weight: bold;
+ color: #333;
+ }
+ .ex_tags{
+ display: inline;
+ font-size: smaller;
+ font-style: italic;
+ color: #333;
+ }
+ .ex_filename {
+ font-weight: normal;
+ font-size: 0.8em;
+ color: #ccc
+ }
+ .ex_description{
+ color: #222;
+ padding: 3px;
+ }
+ .ex_classes{
+ font-size: .7em;
+ color: gray;
+ display: none;
+ }
+ #toc {
+ width: 100%;
+ height: 100%;
+ }
+ #filter {
+ position: fixed;
+ text-align: center;
+ top: 0px;
+ background: #9D9FA1;
+ width: 100%;
+ padding: 1.3em 0;
+ }
+ #examples {
+ overflow: auto;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+ #examples ul {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ margin-top: 4em;
+ }
+ #examples ul li {
+ display: inline;
+ float: left;
+ width: 350px;
+ margin: 10px 0 0 10px;
+ padding: 0;
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ }
+ #examples .mainlink {
+ height: 8em;
+ overflow: auto;
+ }
+ #exwin {
+ position: absolute;
+ top: 0;
+ left: 30%;
+ width: 70%;
+ height: 100%;
+ border: none;
+ border-left: 1px solid #cccccc;
+ margin: 0;
+ }
+ @media only screen and (max-width: 600px) {
+ #examples ul {
+ margin-top: 100px;
+ }
+ #filter {
+ padding-top: 50px;
+ }
+ #examples ul li {
+ margin-left: 0;
+ border-radius: 0;
+ border-width: 1px 0;
+ width: 100%;
+ }
+ #examples .mainlink {
+ height: auto;
+ }
+ #examples .ex_tags, #examples .ex_filename {
+ display: none;
+ }
+ }
+ </style>
+ <script type="text/javascript" src="Jugl.js"></script>
+ <script type="text/javascript" src="example-list.js"></script>
+ <script type="text/javascript">
+ var template, target;
+
+ function listExamples(examples) {
+ target.innerHTML = "";
+ var node = template.process({
+ context: {examples: examples},
+ clone: true,
+ parent: target
+ });
+ document.getElementById("count").innerHTML = "(" + examples.length + ")";
+ }
+
+ var timerId;
+ function inputChange() {
+ if(timerId) {
+ window.clearTimeout(timerId);
+ }
+ var text = this.value;
+ timerId = window.setTimeout(function() {
+ filterList(text);
+ }, 500);
+ }
+
+ function filterList(text) {
+ var examples;
+ if(text.length < 2) {
+ examples = info.examples;
+ } else {
+ var words = text.split(/\W+/);
+ var scores = {};
+ for(var i=0; i<words.length; ++i) {
+ var word = words[i].toLowerCase();
+ var dict = info.index[word];
+ var updateScores = function() {
+ for(exIndex in dict) {
+ var count = dict[exIndex];
+ if(scores[exIndex]) {
+ if(scores[exIndex][word]) {
+ scores[exIndex][word] += count;
+ } else {
+ scores[exIndex][word] = count;
+ }
+ } else {
+ scores[exIndex] = {};
+ scores[exIndex][word] = count;
+ }
+ }
+ };
+ if(dict) {
+ updateScores();
+ } else {
+ var r;
+ for (idx in info.index) {
+ r = new RegExp(word);
+ if (r.test(idx)) {
+ dict = info.index[idx];
+ updateScores();
+ }
+ }
+ }
+ }
+ examples = [];
+ for(var j in scores) {
+ var ex = info.examples[j];
+ ex.score = scores[j];
+ examples.push(ex);
+ }
+ // sort examples by first by number of words matched, then
+ // by word frequency
+ examples.sort(function(a, b) {
+ var cmp;
+ var aWords = 0, bWords = 0;
+ var aScore = 0, bScore = 0;
+ for(var i in a.score) {
+ aScore += a.score[i];
+ aWords += 1;
+ }
+ for(var j in b.score) {
+ bScore += b.score[j];
+ bWords += 1;
+ }
+ if(aWords == bWords) {
+ cmp = bScore - aScore;
+ } else {
+ cmp = bWords - aWords;
+ }
+ return cmp;
+ });
+ }
+ listExamples(examples);
+ }
+
+ function showAll() {
+ document.getElementById("keywords").value = "";
+ listExamples(info.examples);
+ }
+
+ function parseQuery() {
+ var params = {};
+ var list = window.location.search.substring(1).split("&");
+ for(var i=0; i<list.length; ++i) {
+ var pair = list[i].split("=");
+ if(pair.length == 2) {
+ params[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]);
+ }
+ }
+ if(params["q"]) {
+ var input = document.getElementById("keywords");
+ input.value = params["q"];
+ inputChange.call(input);
+ }
+ }
+ window.onload = function() {
+ //document.getElementById('keywords').focus();
+ template = new jugl.Template("template");
+ target = document.getElementById("examples");
+ listExamples(info.examples);
+ document.getElementById("keywords").onkeyup = inputChange;
+ parseQuery();
+ };
+ </script>
+ </head>
+ <body>
+ <div id="toc">
+ <div id="filter">
+ <div id="logo">
+ <img src="http://www.openlayers.org/images/OpenLayers.trac.png"
+ />
+ OpenLayers
+ </div>
+ <p>
+ <input autofocus placeholder="filter by keywords..." type="text" id="keywords" />
+ <span id="count"></span>
+ <a href="javascript:void showAll();">show all</a>
+ </p>
+ </div>
+ <div id="examples"></div>
+ </div>
+ <div style="display: none;">
+ <ul id="template">
+ <li class="ex_container" jugl:repeat="example examples">
+ <a jugl:attributes="href example.link" class="mainlink"
+ target="_blank">
+ <h5 class="ex_title">
+ <span jugl:replace="example.title">title</span><br>
+ <span class="ex_filename" jugl:content="'(' + example.example + ')'">filename</span>
+ </h5>
+ <div class="ex_description" jugl:content="example.shortdesc">
+ Short Description goes here
+ </div>
+ <p class="ex_classes" jugl:content="example.classes">
+ Related Classes go here
+ </p>
+ <div class="ex_tags" jugl:content="'...tagged with ' + example.tags">
+
+ </div>
+ </a>
+ </li>
+ </ul>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/example.html b/misc/openlayers/examples/example.html
new file mode 100644
index 0000000..cfaccb8
--- /dev/null
+++ b/misc/openlayers/examples/example.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">OpenLayers Example</h1>
+ <div id="tags">simple, basic, light</div>
+ <p id="shortdesc">
+ Demonstrate a simple map with an overlay that includes layer switching controls.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This is a basic example demonstrating the use of a map with two layers and a few controls.</p>
+ <p>View the <a href="example.js" target="_blank">example.js</a> source to see how this is done.</p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="example.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/example.js b/misc/openlayers/examples/example.js
new file mode 100644
index 0000000..d02e530
--- /dev/null
+++ b/misc/openlayers/examples/example.js
@@ -0,0 +1,23 @@
+var map = new OpenLayers.Map("map");
+
+var ol_wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+);
+
+var dm_wms = new OpenLayers.Layer.WMS(
+ "Canadian Data",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap",
+ {
+ layers: "bathymetry,land_fn,park,drain_fn,drainage," +
+ "prov_bound,fedlimit,rail,road,popplace",
+ transparent: "true",
+ format: "image/png"
+ },
+ {isBaseLayer: false, visibility: false}
+);
+
+map.addLayers([ol_wms, dm_wms]);
+map.addControl(new OpenLayers.Control.LayerSwitcher());
+map.zoomToMaxExtent();
diff --git a/misc/openlayers/examples/feature-events.html b/misc/openlayers/examples/feature-events.html
new file mode 100644
index 0000000..923e554
--- /dev/null
+++ b/misc/openlayers/examples/feature-events.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Feature Events Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #result {
+ height: 60px;
+ width: 514px;
+ font-size: smaller;
+ overflow: auto;
+ margin-top: 5px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Feature Events Example</h1>
+
+ <div id="tags">
+ feature, select, hover
+ </div>
+
+ <div id="shortdesc">Feature hover and click events</div>
+
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p id="result">Hover over or click features on the map.</p>
+
+ <p>This example shows how to use the 'featureclick', 'nofeatureclick',
+ 'featureover' and 'featureout' events to make features interactive.
+ Look at the <a href="feature-events.js">feature-events.js</a> source
+ code to see how this is done.</p>
+
+ <p>Note that these events can be registered both on the map and on
+ individual layers. If many layers need to be observed, it is
+ recommended to register listeners once on the map for performance
+ reasons.</p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="feature-events.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/feature-events.js b/misc/openlayers/examples/feature-events.js
new file mode 100644
index 0000000..8a6fe28
--- /dev/null
+++ b/misc/openlayers/examples/feature-events.js
@@ -0,0 +1,67 @@
+var layerListeners = {
+ featureclick: function(e) {
+ log(e.object.name + " says: " + e.feature.id + " clicked.");
+ return false;
+ },
+ nofeatureclick: function(e) {
+ log(e.object.name + " says: No feature clicked.");
+ }
+};
+
+var style = new OpenLayers.StyleMap({
+ 'default': OpenLayers.Util.applyDefaults(
+ {label: "${l}", pointRadius: 10},
+ OpenLayers.Feature.Vector.style["default"]
+ ),
+ 'select': OpenLayers.Util.applyDefaults(
+ {pointRadius: 10},
+ OpenLayers.Feature.Vector.style.select
+ )
+});
+var layer1 = new OpenLayers.Layer.Vector("Layer 1", {
+ styleMap: style,
+ eventListeners: layerListeners
+});
+layer1.addFeatures([
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(-1 -1)"), {l:1}),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(1 1)"), {l:1})
+]);
+var layer2 = new OpenLayers.Layer.Vector("Layer 2", {
+ styleMap: style,
+ eventListeners: layerListeners
+});
+layer2.addFeatures([
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(-1 1)"), {l:2}),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(1 -1)"), {l:2})
+]);
+
+var map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ layers: [layer1, layer2],
+ zoom: 6,
+ center: [0, 0],
+ eventListeners: {
+ featureover: function(e) {
+ e.feature.renderIntent = "select";
+ e.feature.layer.drawFeature(e.feature);
+ log("Map says: Pointer entered " + e.feature.id + " on " + e.feature.layer.name);
+ },
+ featureout: function(e) {
+ e.feature.renderIntent = "default";
+ e.feature.layer.drawFeature(e.feature);
+ log("Map says: Pointer left " + e.feature.id + " on " + e.feature.layer.name);
+ },
+ featureclick: function(e) {
+ log("Map says: " + e.feature.id + " clicked on " + e.feature.layer.name);
+ }
+ }
+});
+
+function log(msg) {
+ if (!log.timer) {
+ result.innerHTML = "";
+ log.timer = window.setTimeout(function() {delete log.timer;}, 100);
+ }
+ result.innerHTML += msg + "<br>";
+}
diff --git a/misc/openlayers/examples/filter-strategy.html b/misc/openlayers/examples/filter-strategy.html
new file mode 100644
index 0000000..c9eafa0
--- /dev/null
+++ b/misc/openlayers/examples/filter-strategy.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Filter Strategy Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script>OpenLayers.ImgPath = "../img/";</script>
+ <style>
+ .olControlAttribution {
+ font-size: 9px;
+ bottom: 2px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Filter Strategy</h1>
+ <div id="tags">
+ filter, strategy, strategies, kml, advanced
+ </div>
+ <p id="shortdesc">
+ Demonstrates the filter strategy for limiting features passed to the layer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <label for="span">time span (seconds)</label>
+ <select id="span" name="span">
+ <option value="15">15</option>
+ <option value="30">30</option>
+ <option value="60" selected>60</option>
+ <option value="120">120</option>
+ <option value="240">240</option>
+ </select>
+ <input type="button" id="start" value="start">
+ <input type="button" id="stop" value="stop"><br><br>
+ <div id="docs">
+ <p>
+ This example uses a filter strategy to limit the features that are passed
+ to a layer. Features bound for this layer have a <code>when</code> attribute
+ with date values. A filter strategy is constructed with a between filter
+ that limits the span of dates shown. A simple animation cycles through
+ the domain of the <code>when</code> values, calling <code>setFilter</code>
+ on the strategy with an updated filter.
+ </p><p>
+ View the <a href="filter-strategy.js" target="_blank">filter-strategy.js</a>
+ source to see how this is done
+ </p>
+ </div>
+ <script src="filter-strategy.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/filter-strategy.js b/misc/openlayers/examples/filter-strategy.js
new file mode 100644
index 0000000..da5656a
--- /dev/null
+++ b/misc/openlayers/examples/filter-strategy.js
@@ -0,0 +1,84 @@
+var map, filter, filterStrategy;
+var animationTimer;
+var currentDate;
+var startDate = new Date(1272736800000); // lower bound of when values
+var endDate = new Date(1272737100000); // upper value of when values
+var step = 8; // sencods to advance each interval
+var interval = 0.125; // seconds between each step in the animation
+
+function startAnimation() {
+ if (animationTimer) {
+ stopAnimation(true);
+ }
+ if (!currentDate) {
+ currentDate = startDate;
+ }
+ var spanEl = document.getElementById("span");
+ var next = function() {
+ var span = parseInt(spanEl.value, 10);
+ if (currentDate < endDate) {
+ filter.lowerBoundary = currentDate;
+ filter.upperBoundary = new Date(currentDate.getTime() + (span * 1000));
+ filterStrategy.setFilter(filter);
+ currentDate = new Date(currentDate.getTime() + (step * 1000));
+ } else {
+ stopAnimation(true);
+ }
+ };
+ animationTimer = window.setInterval(next, interval * 1000);
+}
+
+function stopAnimation(reset) {
+ window.clearInterval(animationTimer);
+ animationTimer = null;
+ if (reset === true) {
+ currentDate = null;
+ }
+}
+
+// add behavior to elements
+document.getElementById("start").onclick = startAnimation;
+document.getElementById("stop").onclick = stopAnimation;
+var spanEl = document.getElementById("span");
+
+var mercator = new OpenLayers.Projection("EPSG:900913");
+var geographic = new OpenLayers.Projection("EPSG:4326");
+map = new OpenLayers.Map("map");
+
+var osm = new OpenLayers.Layer.OSM();
+
+filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "when",
+ lowerBoundary: startDate,
+ upperBoundary: new Date(startDate.getTime() + (parseInt(spanEl.value, 10) * 1000))
+});
+
+filterStrategy = new OpenLayers.Strategy.Filter({filter: filter});
+
+var flights = new OpenLayers.Layer.Vector("Aircraft Locations", {
+ projection: geographic,
+ strategies: [new OpenLayers.Strategy.Fixed(), filterStrategy],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml-track.kml",
+ format: new OpenLayers.Format.KML({
+ extractTracks: true
+ //,extractStyles: true // use style from KML instead of styleMap below
+ })
+ }),
+ styleMap: new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style({
+ graphicName: "circle",
+ pointRadius: 3,
+ fillOpacity: 0.25,
+ fillColor: "#ffcc66",
+ strokeColor: "#ff9933",
+ strokeWidth: 1
+ })
+ }),
+ renderers: ["Canvas", "SVG", "VML"]
+});
+
+map.addLayers([osm, flights]);
+map.setCenter(new OpenLayers.LonLat(-93.2735, 44.8349).transform(geographic, mercator), 8);
+
diff --git a/misc/openlayers/examples/filter.html b/misc/openlayers/examples/filter.html
new file mode 100644
index 0000000..866495f
--- /dev/null
+++ b/misc/openlayers/examples/filter.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #in {
+ width: 90%;
+ height: 250px;
+ }
+ #out0, #out1 {
+ height: 100px;
+ width: 90%;
+ overflow: auto;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var filter_1_0 = new OpenLayers.Format.Filter({version: "1.0.0"});
+ var filter_1_1 = new OpenLayers.Format.Filter({version: "1.1.0"});
+ var xml = new OpenLayers.Format.XML();
+
+ var filter;
+ function write() {
+ var code = input.value;
+ try {
+ eval(code);
+ } catch(err) {
+ out0.value = err.message;
+ out1.value = "";
+ }
+ try {
+ out0.value = xml.write(filter_1_0.write(filter));
+ } catch(err) {
+ out0.value = err.message;
+ if(err.lineNumber != undefined) {
+ out0.value += " (line " + err.lineNumber + " " +
+ err.fileName + ")";
+ }
+ }
+ try {
+ out1.value = xml.write(filter_1_1.write(filter));
+ } catch(err) {
+ out1.value = err.message;
+ if(err.lineNumber != undefined) {
+ out1.value += " (line " + err.lineNumber + " " +
+ err.fileName + ")";
+ }
+ }
+ }
+
+ var input, out0;
+ window.onload = function() {
+ input = document.getElementById("in");
+ out0 = document.getElementById("out0");
+ out1 = document.getElementById("out1");
+ out0.value = "";
+ out1.value = "";
+ document.getElementById("write").onclick = write;
+ };
+
+ </script>
+ </head>
+ <body>
+ <h1 id="title">Filter Encoding</h1>
+ <div id="tags">
+ filter, format, comparison, filter encoding, fe, logical, attribute,
+ attributive, spatial, advanced
+ </div>
+ <p id="shortdesc">
+ Using the filter format write out filter objects.
+ </p>
+ <textarea id="in">
+filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "person",
+ value: "me"
+ }),
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "mean",
+ value: "yes"
+ }),
+ new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ })
+ ]
+});
+ </textarea>
+ <button id="write">write</button><br>
+ Filter Encoding 1.0
+ <textarea id="out0"></textarea><br>
+ Filter Encoding 1.1
+ <textarea id="out1"></textarea><br>
+ <p id="docs">
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/fractional-zoom.html b/misc/openlayers/examples/fractional-zoom.html
new file mode 100644
index 0000000..b01c2d6
--- /dev/null
+++ b/misc/openlayers/examples/fractional-zoom.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ map = new OpenLayers.Map('map',
+ {controls: [new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanZoomBar()],
+ numZoomLevels: 10 });
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ map.addLayers([wms]);
+
+ map.events.register("moveend", null, displayZoom);
+
+ map.zoomToMaxExtent();
+
+ update(document.getElementById("fractional"));
+
+ }
+
+ function displayZoom() {
+ document.getElementById("zoom").innerHTML = map.zoom.toFixed(4);
+ }
+
+ function update(input) {
+ map.fractionalZoom = input.checked;
+ map.zoomTo(Math.round(map.zoom));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Fractional Zoom Example</h1>
+
+ <div id="tags">
+ zoomlevel, unlimited zoom, scale
+ </div>
+ <p id="shortdesc">
+ Shows the use of a map with fractional (or non-discrete) zoom levels.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <input type="checkbox" name="fractional"
+ id="fractional" checked="checked" onclick="update(this)" />
+ <label for="fractional">Fractional Zoom</label>
+ (zoom: <span id="zoom"></span>)
+ <br><br>
+ <div id="docs">
+ <p>
+ Setting the map.fractionalZoom property to true allows zooming to
+ an arbitrary level (between the min and max resolutions). This
+ can be demonstrated by shift-dragging a box to zoom to an arbitrary
+ extent.
+ </p>
+ </div>
+ </body>
+</html>
+
+
+
+
diff --git a/misc/openlayers/examples/fullScreen.html b/misc/openlayers/examples/fullScreen.html
new file mode 100644
index 0000000..f5df04a
--- /dev/null
+++ b/misc/openlayers/examples/fullScreen.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Full Screen Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ html, body, #map {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ #text {
+ position: absolute;
+ bottom: 1em;
+ left: 1em;
+ width: 512px;
+ z-index: 20000;
+ background-color: white;
+ padding: 0 0.5em 0.5em 0.5em;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <div id="map"></div>
+
+ <div id="text">
+ <h1 id="title">Full Screen Example</h1>
+
+ <div id="tags">
+ css, style, fullscreen, window, margin, padding, scrollbar
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate a map that fills the entire browser window.
+ </p>
+
+ <div id="docs">
+ <p>This example uses CSS to define the dimensions of the map element in order to fill the screen.
+ When the user resizes the window, the map size changes correspondingly. No scroll bars!</p>
+ <p>See the
+ <a href="fullScreen.js" target="_blank">fullScreen.js source</a>
+ to see how this is done.</p>
+ </div>
+ <script src="fullScreen.js"></script>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/fullScreen.js b/misc/openlayers/examples/fullScreen.js
new file mode 100644
index 0000000..1e03a53
--- /dev/null
+++ b/misc/openlayers/examples/fullScreen.js
@@ -0,0 +1,20 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.OSM("OSM (without buffer)"),
+ new OpenLayers.Layer.OSM("OSM (with buffer)", null, {buffer: 2})
+ ],
+ controls: [
+ new OpenLayers.Control.Navigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.Attribution()
+ ],
+ center: [0, 0],
+ zoom: 3
+});
+
+map.addControl(new OpenLayers.Control.LayerSwitcher());
diff --git a/misc/openlayers/examples/fusiontables.html b/misc/openlayers/examples/fusiontables.html
new file mode 100644
index 0000000..655ff19
--- /dev/null
+++ b/misc/openlayers/examples/fusiontables.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Example For Reading Features From Google Fusion Tables</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Reading Features From A Google Fusion Tables Table</h1>
+ <div id="tags">
+ protocol, script, fusion tables
+ </div>
+ <p id="shortdesc">
+ Demonstrates how, with a custom read method, the script protocol and GeoJSON format can be used to read features stored in a table on Google Fusion Tables.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ Google Fusion Tables can be used to store features, and access them using SQL-type commands over HTTP. Tables are accessed using an authorization key; create/update/delete of tables requires an OAuth2 token, but tables can be public, in which case a simple apikey is all that's needed to read them. Geometries can be stored in Location columns in KML format, but the default output is a JSON object with the geometry as GeoJSON. With a custom read method, this example parses the geometry for each row, storing the other columns as feature attributes. You can of course add a 'where' clause to the SQL statement or change the column names to limit the data retrieved. Point geometries can also be stored in Latitude/Longitude columns, and the script could easily be modified to use those instead.
+ </p>
+ <p>
+ View the <a href="fusiontables.js" target="_blank">fusiontables.js</a>
+ source to see how this is done. You will need to get your own apikey from <a href="https://code.google.com/apis/console">Google's API Console</a> for this to function on your domain.
+ </p>
+ <p>
+ <a href="https://www.google.com/fusiontables/DataSource?docid=1g5DrXcdotCiO_yffkdW0zhuJk0a1i80SPvERHI8">Table used</a>. <a href="https://developers.google.com/fusiontables/docs/v1/using">Fusion Tables Developers Guide</a>
+ </p>
+ </div>
+ <script src="fusiontables.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/fusiontables.js b/misc/openlayers/examples/fusiontables.js
new file mode 100644
index 0000000..57ae6e5
--- /dev/null
+++ b/misc/openlayers/examples/fusiontables.js
@@ -0,0 +1,51 @@
+// change this to your api key
+var apikey = "AIzaSyD_1zzMAoZjuP-m4LyhieuYmqiVJTEajyI";
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.OSM(),
+ new OpenLayers.Layer.Vector("Vectors", {
+ projection: new OpenLayers.Projection("EPSG:4326"),
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "https://www.googleapis.com/fusiontables/v1/query",
+ params: {
+ sql: "select * from 1g5DrXcdotCiO_yffkdW0zhuJk0a1i80SPvERHI8",
+ key: apikey
+ },
+ format: new OpenLayers.Format.GeoJSON({
+ ignoreExtraDims: true,
+ read: function(json) {
+ var row, feature, atts = {}, features = [];
+ var cols = json.columns; // column names
+ for (var i = 0; i < json.rows.length; i++) {
+ row = json.rows[i];
+ feature = new OpenLayers.Feature.Vector();
+ atts = {};
+ for (var j = 0; j < row.length; j++) {
+ // 'location's are json objects, other types are strings
+ if (typeof row[j] === "object") {
+ feature.geometry = this.parseGeometry(row[j].geometry);
+ } else {
+ atts[cols[j]] = row[j];
+ }
+ }
+ feature.attributes = atts;
+ // if no geometry, not much point in continuing with this row
+ if (feature.geometry) {
+ features.push(feature);
+ }
+ }
+ return features;
+ }
+ })
+ }),
+ eventListeners: {
+ "featuresadded": function () {
+ this.map.zoomToExtent(this.getDataExtent());
+ }
+ }
+ })
+ ]
+});
diff --git a/misc/openlayers/examples/game-accel-ball.html b/misc/openlayers/examples/game-accel-ball.html
new file mode 100644
index 0000000..c832e86
--- /dev/null
+++ b/misc/openlayers/examples/game-accel-ball.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Game: Bounce Ball</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js?mobile"></script>
+ <style type="text/css">
+ html, body { height: 100%; }
+ #shortdesc { display: none; }
+ #tags { display: none; }
+ </style>
+
+ <script type="text/javascript">
+ var map, vlayer;
+ function adjustLocation(delta, feature) {
+ feature.geometry.move(delta.x, delta.y);
+ var me = map.maxExtent;
+ var rad = 6;
+ if (feature.geometry.x > (me.right - rad)) {
+ feature.geometry.x = me.right - rad;
+ } else if (feature.geometry.x < (me.left+rad)) {
+ feature.geometry.x = me.left+rad;
+ }
+ if (feature.geometry.y > (me.top-rad)) {
+ feature.geometry.y = me.top-rad;
+ } else if (feature.geometry.y < (me.bottom+rad)) {
+ feature.geometry.y = me.bottom+rad;
+ }
+ vlayer.drawFeature(feature);
+ }
+ function init() {
+ map = new OpenLayers.Map( 'map',
+ {
+ 'maxExtent': new OpenLayers.Bounds(0, 0, document.getElementById("map").clientWidth, document.getElementById("map").clientHeight),
+ controls: [],
+ maxResolution: 'auto'}
+ );
+ var layer = new OpenLayers.Layer("",
+ {isBaseLayer: true} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ vlayer = new OpenLayers.Layer.Vector();
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(map.getCenter().lon, map.getCenter().lat));
+ vlayer.addFeatures(feature);
+ map.addLayer(vlayer);
+ if (window.DeviceMotionEvent) {
+ window.addEventListener('devicemotion', function (evt) {
+ var delta = null;
+ if (typeof(evt.accelerationIncludingGravity) != 'undefined') {
+ delta = {
+ 'x': evt.accelerationIncludingGravity.x * 3,
+ 'y': evt.accelerationIncludingGravity.y * 3,
+ 'z': evt.accelerationIncludingGravity.z
+ }
+ }
+ adjustLocation(delta, feature);
+ }, true);
+ } else {
+ alert("This demo does not work on your browser.");
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Accelerometer Example</h1>
+ <div id="tags">
+ mobile, game
+ </div>
+ <div id="shortdesc">Simple acceleration demo; roll a vector feature around
+ on a map. (Only tested on iOS 4.)</div>
+
+ <div id="map" width="100%" height="100%" style="background-color: gray"></div>
+ <div id="docs">
+ <p>Demo works best when device is locked in portrait mode.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/geojson-reprojected.html b/misc/openlayers/examples/geojson-reprojected.html
new file mode 100644
index 0000000..2e60638
--- /dev/null
+++ b/misc/openlayers/examples/geojson-reprojected.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers GeoJSON Reprojected Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ left: 2px;
+ right: inherit;
+ bottom: 3px;
+ line-height: 11px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">GeoJSON Reprojected</h1>
+
+ <div id="tags">
+ geojson, bing, projection
+ </div>
+
+ <div id="shortdesc">Display GeoJSON data over Bing tiles</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example demonstrates the use of GeoJSON over Bing tiles. The
+ GeoJSON vector data is in geographic coordinates (EPSG:4326). The Bing
+ tiles are in a spherical mercator projection (EPSG:900913). By setting
+ the <code>projection</code> property of the GeoJSON layer to the source
+ projection (EPSG:4326), the features are properly displayed over the
+ base layer. In general, the map projection determines how raster or
+ vector data is displayed. The layer projection corresponds to the
+ projection of the data source.
+ <p>See the
+ <a target="_blank" href="geojson-reprojected.js">geojson-reprojected.js</a>
+ source for details on how this is done.</p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="geojson-reprojected.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/geojson-reprojected.js b/misc/openlayers/examples/geojson-reprojected.js
new file mode 100644
index 0000000..d54c6e9
--- /dev/null
+++ b/misc/openlayers/examples/geojson-reprojected.js
@@ -0,0 +1,27 @@
+// API key for http://openlayers.org. Please get your own at
+// http://bingmapsportal.com/ and use that instead.
+var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+
+var hybrid = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "AerialWithLabels",
+ name: "Bing Aerial With Labels"
+});
+
+var vector = new OpenLayers.Layer.Vector("GeoJSON", {
+ projection: "EPSG:4326",
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "geojson-reprojected.json",
+ format: new OpenLayers.Format.GeoJSON()
+ })
+});
+
+var center = new OpenLayers.LonLat(-109.6, 46.7).transform("EPSG:4326", "EPSG:900913");
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [hybrid, vector],
+ center: center,
+ zoom: 4
+});
diff --git a/misc/openlayers/examples/geojson-reprojected.json b/misc/openlayers/examples/geojson-reprojected.json
new file mode 100644
index 0000000..82153fa
--- /dev/null
+++ b/misc/openlayers/examples/geojson-reprojected.json
@@ -0,0 +1 @@
+{"type":"FeatureCollection","features":[{"type":"Feature","id":"USA-MT","properties":{"fips":"30","name":"Montana"},"geometry":{"type":"Polygon","coordinates":[[[-104.047534,49.000239],[-104.042057,47.861036],[-104.047534,45.944106],[-104.042057,44.996596],[-104.058488,44.996596],[-105.91517,45.002073],[-109.080842,45.002073],[-111.05254,45.002073],[-111.047063,44.476286],[-111.227803,44.580348],[-111.386634,44.75561],[-111.616665,44.547487],[-111.819312,44.509148],[-111.868605,44.563917],[-112.104113,44.520102],[-112.241036,44.569394],[-112.471068,44.481763],[-112.783254,44.48724],[-112.887315,44.394132],[-113.002331,44.448902],[-113.133778,44.772041],[-113.341901,44.782995],[-113.456917,44.865149],[-113.45144,45.056842],[-113.571933,45.128042],[-113.736241,45.330689],[-113.834826,45.522382],[-113.807441,45.604536],[-113.98818,45.703121],[-114.086765,45.593582],[-114.333228,45.456659],[-114.546828,45.560721],[-114.497536,45.670259],[-114.568736,45.774321],[-114.387997,45.88386],[-114.492059,46.037214],[-114.464674,46.272723],[-114.322274,46.645155],[-114.612552,46.639678],[-114.623506,46.705401],[-114.886399,46.809463],[-114.930214,46.919002],[-115.302646,47.187372],[-115.324554,47.258572],[-115.527201,47.302388],[-115.718894,47.42288],[-115.724371,47.696727],[-116.04751,47.976051],[-116.04751,49.000239],[-111.50165,48.994762],[-109.453274,49.000239],[-104.047534,49.000239]]]}}]}
diff --git a/misc/openlayers/examples/geojson.html b/misc/openlayers/examples/geojson.html
new file mode 100644
index 0000000..741add6
--- /dev/null
+++ b/misc/openlayers/examples/geojson.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ var featurecollection = {
+ "type": "FeatureCollection",
+ "features": [
+ {"geometry": {
+ "type": "GeometryCollection",
+ "geometries": [
+ {
+ "type": "LineString",
+ "coordinates":
+ [[11.0878902207, 45.1602390564],
+ [15.01953125, 48.1298828125]]
+ },
+ {
+ "type": "Polygon",
+ "coordinates":
+ [[[11.0878902207, 45.1602390564],
+ [14.931640625, 40.9228515625],
+ [0.8251953125, 41.0986328125],
+ [7.63671875, 48.96484375],
+ [11.0878902207, 45.1602390564]]]
+ },
+ {
+ "type":"Point",
+ "coordinates":[15.87646484375, 44.1748046875]
+ }
+ ]
+ },
+ "type": "Feature",
+ "properties": {}}
+ ]
+ };
+ var geojson_format = new OpenLayers.Format.GeoJSON();
+ var vector_layer = new OpenLayers.Layer.Vector();
+ map.addLayer(vector_layer);
+ vector_layer.addFeatures(geojson_format.read(featurecollection));
+
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">GeoJSON Example</h1>
+
+ <div id="tags">
+ JSON, GeoJSON, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate the use of the GeoJSON format.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This example uses the GeoJSON format.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/geolocation.html b/misc/openlayers/examples/geolocation.html
new file mode 100644
index 0000000..673e086
--- /dev/null
+++ b/misc/openlayers/examples/geolocation.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Geolocation</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ .olControlAttribution {
+ bottom: 3px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Geolocation Example</h1>
+
+ <div id="tags">
+ geolocation, geolocate, mobile
+ </div>
+
+ <p id="shortdesc">
+ Track current position and display it with its accuracy.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <button id="locate">Locate me!</button>
+ <input type="checkbox" name="track" id="track">
+ <label for="track">Track my position</label>
+ <div id="docs">
+ <p>
+ View the <a href="geolocation.js" target="_blank">geolocation.js source</a>
+ to see how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="geolocation.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/geolocation.js b/misc/openlayers/examples/geolocation.js
new file mode 100644
index 0000000..3d8d6f4
--- /dev/null
+++ b/misc/openlayers/examples/geolocation.js
@@ -0,0 +1,112 @@
+var style = {
+ fillColor: '#000',
+ fillOpacity: 0.1,
+ strokeWidth: 0
+};
+
+var map = new OpenLayers.Map('map');
+var layer = new OpenLayers.Layer.OSM( "Simple OSM Map");
+var vector = new OpenLayers.Layer.Vector('vector');
+map.addLayers([layer, vector]);
+
+map.setCenter(
+ new OpenLayers.LonLat(-71.147, 42.472).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+ ), 12
+);
+
+var pulsate = function(feature) {
+ var point = feature.geometry.getCentroid(),
+ bounds = feature.geometry.getBounds(),
+ radius = Math.abs((bounds.right - bounds.left)/2),
+ count = 0,
+ grow = 'up';
+
+ var resize = function(){
+ if (count>16) {
+ clearInterval(window.resizeInterval);
+ }
+ var interval = radius * 0.03;
+ var ratio = interval/radius;
+ switch(count) {
+ case 4:
+ case 12:
+ grow = 'down'; break;
+ case 8:
+ grow = 'up'; break;
+ }
+ if (grow!=='up') {
+ ratio = - Math.abs(ratio);
+ }
+ feature.geometry.resize(1+ratio, point);
+ vector.drawFeature(feature);
+ count++;
+ };
+ window.resizeInterval = window.setInterval(resize, 50, point, radius);
+};
+
+var geolocate = new OpenLayers.Control.Geolocate({
+ bind: false,
+ geolocationOptions: {
+ enableHighAccuracy: false,
+ maximumAge: 0,
+ timeout: 7000
+ }
+});
+map.addControl(geolocate);
+var firstGeolocation = true;
+geolocate.events.register("locationupdated",geolocate,function(e) {
+ vector.removeAllFeatures();
+ var circle = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(e.point.x, e.point.y),
+ e.position.coords.accuracy/2,
+ 40,
+ 0
+ ),
+ {},
+ style
+ );
+ vector.addFeatures([
+ new OpenLayers.Feature.Vector(
+ e.point,
+ {},
+ {
+ graphicName: 'cross',
+ strokeColor: '#f00',
+ strokeWidth: 2,
+ fillOpacity: 0,
+ pointRadius: 10
+ }
+ ),
+ circle
+ ]);
+ if (firstGeolocation) {
+ map.zoomToExtent(vector.getDataExtent());
+ pulsate(circle);
+ firstGeolocation = false;
+ this.bind = true;
+ }
+});
+geolocate.events.register("locationfailed",this,function() {
+ OpenLayers.Console.log('Location detection failed');
+});
+document.getElementById('locate').onclick = function() {
+ vector.removeAllFeatures();
+ geolocate.deactivate();
+ document.getElementById('track').checked = false;
+ geolocate.watch = false;
+ firstGeolocation = true;
+ geolocate.activate();
+};
+document.getElementById('track').onclick = function() {
+ vector.removeAllFeatures();
+ geolocate.deactivate();
+ if (this.checked) {
+ geolocate.watch = true;
+ firstGeolocation = true;
+ geolocate.activate();
+ }
+};
+document.getElementById('track').checked = false;
diff --git a/misc/openlayers/examples/georss-flickr.html b/misc/openlayers/examples/georss-flickr.html
new file mode 100644
index 0000000..5591b0c
--- /dev/null
+++ b/misc/openlayers/examples/georss-flickr.html
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olPopupContent {
+ font-size: smaller;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer, markerLayer, style, popup;
+
+
+ function init(){
+ map = new OpenLayers.Map('map', {maxResolution:'auto'});
+
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ // create a property style that reads the externalGraphic url from
+ // the thumbail attribute of the rss item
+ style = new OpenLayers.Style({externalGraphic: "${thumbnail}"});
+
+ // create a rule with a point symbolizer that will make the thumbnail
+ // larger if the title of the rss item contains "powder"
+ var rule = new OpenLayers.Rule({
+ symbolizer: {pointRadius: 30},
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "title",
+ value: "*powder*"
+ })
+ });
+ rule.filter.value2regex("*");
+
+ // If the above rule does not apply, use a smaller pointRadius.
+ var elseRule = new OpenLayers.Rule({
+ elseFilter: true,
+ symbolizer: {pointRadius: 20}
+ });
+
+ style.addRules([rule, elseRule]);
+
+ // Create a Vector layer with GeoRSS format and a style map.
+ markerLayer = new OpenLayers.Layer.Vector("Some images from Flickr", {
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "xml/georss-flickr.xml",
+ format: new OpenLayers.Format.GeoRSS({
+ // adds the thumbnail attribute to the feature
+ createFeatureFromItem: function(item) {
+ var feature = OpenLayers.Format.GeoRSS.prototype.createFeatureFromItem.apply(this, arguments);
+ feature.attributes.thumbnail = this.getElementsByTagNameNS(item, "*", "thumbnail")[0].getAttribute("url");
+ return feature;
+ }
+ })
+ }),
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ // Giving the style map keys for "default" and "select"
+ // rendering intent, to make the image larger when selected
+ styleMap: new OpenLayers.StyleMap({
+ "default": style,
+ "select": new OpenLayers.Style({pointRadius: 35})
+ })
+ });
+ map.addLayer(markerLayer);
+
+ // control that will show a popup when clicking on a thumbnail
+ var popupControl = new OpenLayers.Control.SelectFeature(markerLayer, {
+ onSelect: function(feature) {
+ var pos = feature.geometry;
+ if (popup) {
+ map.removePopup(popup);
+ }
+ popup = new OpenLayers.Popup("popup",
+ new OpenLayers.LonLat(pos.x, pos.y),
+ new OpenLayers.Size(254,320),
+ "<h3>" + feature.attributes.title + "</h3>" +
+ feature.attributes.description,
+ true);
+ map.addPopup(popup);
+ }
+ });
+ map.addControl(popupControl);
+
+ popupControl.activate();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">GeoRSS from Flickr in OpenLayers</h1>
+ <div id="tags">
+ georss, style, styling, marker, flickr, thumbnail, image, rule
+ </div>
+
+ <p id="shortdesc">
+ Display a flickr-feed on top of the map
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The displayed GeoRSS feed has a <tt>&lt;media:thumbnail/&gt;</tt>
+ property for each item. An extended <tt>createFeatureFromItem()</tt>
+ function is used to add this attribute to the attributes hash of each
+ feature read in by <tt>OpenLayers.Format.GeoRSS</tt>. The example is
+ configured with a style to render each item with its thumbnail image.
+ Also, to show how rules work, we defined a rule that if the title of an
+ rss item contains "powder", it will be rendered larger than the others.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/georss-markers.html b/misc/openlayers/examples/georss-markers.html
new file mode 100644
index 0000000..3800100
--- /dev/null
+++ b/misc/openlayers/examples/georss-markers.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers GeoRSS Marker Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ OpenLayers.ProxyHost = "/proxy/?url=";
+ function init(){
+ map = new OpenLayers.Map('map', {maxResolution:'auto'});
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ var newl = new OpenLayers.Layer.GeoRSS( 'GeoRSS', 'georss.xml');
+ map.addLayer(newl);
+ var yelp = new OpenLayers.Icon("http://www.openlayers.org/images/OpenLayers.trac.png", new OpenLayers.Size(49,44));
+ var newl = new OpenLayers.Layer.GeoRSS( 'Yelp GeoRSS', 'yelp-georss.xml', {'icon':yelp});
+ map.addLayer(newl);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">GeoRSS Marker Example</h1>
+
+ <div id="tags">
+ georss, style, styling, marker, flickr, image
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate loading a GeoRSS feed using the GeoRSS parser.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/georss.html b/misc/openlayers/examples/georss.html
new file mode 100644
index 0000000..816fcf6
--- /dev/null
+++ b/misc/openlayers/examples/georss.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers GeoRSS Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ OpenLayers.ProxyHost = "/proxy/?url=";
+ function init(){
+ map = new OpenLayers.Map('map', {maxResolution:'auto'});
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ }
+ function addUrl() {
+ var urlObj = OpenLayers.Util.getElement('url');
+ var value = urlObj.value;
+ var parts = value.split("/");
+ var newl = new OpenLayers.Layer.GeoRSS( parts[parts.length-1], value);
+ map.addLayer(newl);
+ urlObj.value = "";
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">GeoRSS Example</h1>
+
+ <div id="tags">
+ georss, style, styling, marker
+ </div>
+
+ <p id="shortdesc">
+ Display a couple of locally cached georss feeds on an a basemap.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This demo uses the OpenLayers GeoRSS parser, which supports GeoRSS Simple and W3C GeoRSS. Only points are
+ currently supported. The OpenLayers GeoRSS parser will automatically connect an information bubble to the map
+ markers, similar to Google maps. In addition, the parser can use custom PNG icons for markers. A sample GeoRSS
+ file (georss.xml) is included.</p>
+
+ <form onsubmit="return false;">
+ GeoRSS URL: <input type="text" id="url" size="50" value="georss.xml" />
+ <input type="submit" onclick="addUrl(); return false;" value="Load Feed" onsubmit="addUrl(); return false;">
+ </form>
+
+ <p>The above input box allows the input of a URL to a GeoRSS feed. This feed can be local to the HTML page &mdash;
+ for example, entering 'georss.xml' will work by default, because there is a local file in the directory called
+ georss.xml &mdash; or, with a properly set up ProxyHost variable (as is used here), it will be able to load any
+ HTTP URL which contains GeoRSS and display it. Anything else will simply have no effect.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/georss.xml b/misc/openlayers/examples/georss.xml
new file mode 100644
index 0000000..fecf77a
--- /dev/null
+++ b/misc/openlayers/examples/georss.xml
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/css" href="/css/rss.css" ?>
+
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://purl.org/rss/1.0/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:georss="http://www.georss.org/georss">
+<docs>This is an RSS file. Copy the URL into your aggregator of choice. If you don't know what this means and want to learn more, please see: <span>http://platial.typepad.com/news/2006/04/really_simple_t.html</span> for more info.</docs><channel rdf:about="http://platial.com">
+<link>http://platial.com</link>
+<title>Crschmidt's Places At Platial</title>
+<description></description>
+<items>
+<rdf:Seq>
+<rdf:li resource="http://platial.com/place/90306"/>
+<rdf:li resource="http://platial.com/place/67230"/>
+<rdf:li resource="http://platial.com/place/65645"/>
+<rdf:li resource="http://platial.com/place/62200"/>
+<rdf:li resource="http://platial.com/place/28232"/>
+<rdf:li resource="http://platial.com/place/43666"/>
+<rdf:li resource="http://platial.com/place/28394"/>
+<rdf:li resource="http://platial.com/place/28251"/>
+<rdf:li resource="http://platial.com/place/28392"/>
+<rdf:li resource="http://platial.com/place/28391"/>
+<rdf:li resource="http://platial.com/place/28231"/>
+<rdf:li resource="http://platial.com/place/28393"/>
+<rdf:li resource="http://platial.com/place/31685"/>
+<rdf:li resource="http://platial.com/place/28596"/>
+<rdf:li resource="http://platial.com/place/28595"/>
+<rdf:li resource="http://platial.com/place/28594"/>
+<rdf:li resource="http://platial.com/place/28593"/>
+<rdf:li resource="http://platial.com/place/28592"/>
+<rdf:li resource="http://platial.com/place/28591"/>
+<rdf:li resource="http://platial.com/place/28590"/>
+<rdf:li resource="http://platial.com/place/28589"/>
+<rdf:li resource="http://platial.com/place/28588"/>
+<rdf:li resource="http://platial.com/place/28587"/>
+<rdf:li resource="http://platial.com/place/28586"/>
+<rdf:li resource="http://platial.com/place/28585"/>
+<rdf:li resource="http://platial.com/place/28584"/>
+<rdf:li resource="http://platial.com/place/28583"/>
+<rdf:li resource="http://platial.com/place/28582"/>
+<rdf:li resource="http://platial.com/place/28581"/>
+<rdf:li resource="http://platial.com/place/28580"/>
+<rdf:li resource="http://platial.com/place/28579"/>
+<rdf:li resource="http://platial.com/place/28578"/>
+<rdf:li resource="http://platial.com/place/28577"/>
+<rdf:li resource="http://platial.com/place/28576"/>
+<rdf:li resource="http://platial.com/place/28575"/>
+<rdf:li resource="http://platial.com/place/28574"/>
+<rdf:li resource="http://platial.com/place/28573"/>
+<rdf:li resource="http://platial.com/place/28572"/>
+<rdf:li resource="http://platial.com/place/28571"/>
+<rdf:li resource="http://platial.com/place/28570"/>
+</rdf:Seq>
+</items>
+</channel>
+<item rdf:about="http://platial.com/place/90306">
+<link>http://platial.com/place/90306</link>
+<title>Knitting Room</title>
+<description><![CDATA[This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/90306">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/90306">Grab this on Platial</a> ]]></description>
+<georss:point>42.405696 -71.142197</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-06-08T17:35:01.942452+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/67230">
+<link>http://platial.com/place/67230</link>
+<title>Knitting Room</title>
+<description><![CDATA[This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/67230">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/67230">Grab this on Platial</a> ]]></description>
+<georss:point>42.405524 -71.142273</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-24T11:35:26.733857+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/65645">
+<link>http://platial.com/place/65645</link>
+<title>†¢¢™£ˆøœ</title>
+<description><![CDATA[ijeª£∆µˆ˚î<br/>Address: 151 Erie St., Cambridge, MA<br/>Tags: platial graffiti<br /><br /><a href="http://platial.com/place/65645">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/65645">Grab this on Platial</a> ]]></description>
+<georss:point>42.352455 -71.110210</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-20T08:56:12.696224+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/62200">
+<link>http://platial.com/place/62200</link>
+<title>Allen Hall</title>
+<description><![CDATA[My dorm at UIUC.<br/>Address: 1301 W Gregory Dr, Urbana, IL<br/>Tags: dorm, uiuc, college<br/><a href="http://platial.com/place/62200"><img src="http://platial.comhttp://static.flickr.com/4/8576450_0d59cc2531_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/62200">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/62200">Grab this on Platial</a> ]]></description>
+<georss:point>40.104172 -88.220623</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-14T08:01:01.872873+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28232">
+<link>http://platial.com/place/28232</link>
+<title>Bagby Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C. However, the area around the springs are not exactly well looked upon by people who know the place.
+
+<br/>Tags: 20s, rosalie, romance, childhood, hike, camping, soak, relax, beautiful, hot springs, bathhouse, favorite, popular, crowded, organized, honeymoon tub, plumbing made from hollowed out trees, hot springs, mt hood, notorious car break in spot, rash, bacteria<br /><br /><a href="http://platial.com/place/28232">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28232">Grab this on Platial</a> ]]></description>
+<georss:point>44.936000 -122.173000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:10:18.553063+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/43666">
+<link>http://platial.com/place/43666</link>
+<title>Shooting Location for "The Field of Dreams" Film</title>
+<description><![CDATA[1989's Field of Dreams was a Best Picture Academy Award nominee, and the baseball field in the cornfield still stands today, and has become quite a tourist destination. Games are occasionally played at the field, re-enacting professional baseball at the turn of the 20th Century.<br/>Address: Dyersville, Iowa<br/>Tags: iowa, baseball, movie locations, field of dreams, kevin costner, costner, dyersville, kinsella, james earl jones, chicago black sox, shoeless joe, joe jackson, famous farms, film, movie, cinema, shooting location<br /><br /><a href="http://platial.com/place/43666">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/43666">Grab this on Platial</a> ]]></description>
+<georss:point>42.481213 -91.111679</georss:point>
+<dc:creator>echinodermata</dc:creator>
+<dc:date>2006-03-23T11:40:17.654061+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28394">
+<link>http://platial.com/place/28394</link>
+<title>Moffetts (Bonneville) Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 97 degress F, 36 degress C<br/>Tags: soak, hot springs, relax, nature<br /><br /><a href="http://platial.com/place/28394">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28394">Grab this on Platial</a> ]]></description>
+<georss:point>45.658000 -121.962000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:27.329816+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28251">
+<link>http://platial.com/place/28251</link>
+<title>Austin Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 186 degress F, 86 degress C<br/>Tags: soak, hot springs, relax, nature, popular, crowded<br /><br /><a href="http://platial.com/place/28251">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28251">Grab this on Platial</a> ]]></description>
+<georss:point>45.021000 -122.009000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:11:04.489886+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28392">
+<link>http://platial.com/place/28392</link>
+<title>Rock Creek Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br/>Tags: soak, hot springs, relax, nature<br /><br /><a href="http://platial.com/place/28392">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28392">Grab this on Platial</a> ]]></description>
+<georss:point>45.723000 -121.927000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:22.636855+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28391">
+<link>http://platial.com/place/28391</link>
+<title>St. Martins (Wind River) Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 120 degress F, 49 degress C<br/>Tags: hot springs, soak, relax, nature, wonderful<br /><br /><a href="http://platial.com/place/28391">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28391">Grab this on Platial</a> ]]></description>
+<georss:point>45.728000 -121.800000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:20.383244+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28231">
+<link>http://platial.com/place/28231</link>
+<title>Breitenbush Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br/>Tags: hot springs, resort, relax, nature, beautiful, http:www.breitenbush.com, soaking<br /><br /><a href="http://platial.com/place/28231">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28231">Grab this on Platial</a> ]]></description>
+<georss:point>44.782000 -121.975000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:10:16.529195+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28393">
+<link>http://platial.com/place/28393</link>
+<title>Collins Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 122 degress F, 50 degress C<br/>Tags: portland, nice, hot springs, soak<br /><br /><a href="http://platial.com/place/28393">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28393">Grab this on Platial</a> ]]></description>
+<georss:point>45.701000 -121.728000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:24.648745+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/31685">
+<link>http://platial.com/place/31685</link>
+<title>Darwin's Ltd.</title>
+<description><![CDATA[Nice little coffee shop/cafe, free Wifi, close enough to walk from Harvard Square.<br/>Address: 148 Mount Auburn St, Cambridge, MA<br/>Tags: coffee, beer, sandwiches, freewifi<br/><a href="http://platial.com/place/31685"><img src="http://platial.comhttp://static.flickr.com/38/84885937_74fd3d1025_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/31685">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/31685">Grab this on Platial</a> ]]></description>
+<georss:point>42.373974 -71.125053</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-10T09:24:08.152985+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28596">
+<link>http://platial.com/place/28596</link>
+<title>Huckleberry Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, Boiling degress C<br /><br /><a href="http://platial.com/place/28596">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28596">Grab this on Platial</a> ]]></description>
+<georss:point>44.115000 -110.684000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:32.283094+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28595">
+<link>http://platial.com/place/28595</link>
+<title>South Entrance Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 156 degress F, 69 degress C<br/><a href="http://platial.com/place/28595"><img src="http://platial.comhttp://static.flickr.com/52/130989872_f1457f68b5_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/28595">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28595">Grab this on Platial</a> ]]></description>
+<georss:point>44.142000 -110.656000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:30.279497+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28594">
+<link>http://platial.com/place/28594</link>
+<title>Crawfish Creek Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C<br/><a href="http://platial.com/place/28594"><img src="http://platial.comhttp://static.flickr.com/52/128312256_d6a879924c_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/28594">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28594">Grab this on Platial</a> ]]></description>
+<georss:point>44.157000 -110.699000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:28.280271+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28593">
+<link>http://platial.com/place/28593</link>
+<title>Crawfish Creek Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 138 degress F, 59 degress C<br /><br /><a href="http://platial.com/place/28593">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28593">Grab this on Platial</a> ]]></description>
+<georss:point>44.165000 -110.723000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:20.364077+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28592">
+<link>http://platial.com/place/28592</link>
+<title>Snake Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C<br /><br /><a href="http://platial.com/place/28592">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28592">Grab this on Platial</a> ]]></description>
+<georss:point>44.169000 -110.583000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:12.234974+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28591">
+<link>http://platial.com/place/28591</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 142 degress F, 61 degress C<br /><br /><a href="http://platial.com/place/28591">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28591">Grab this on Platial</a> ]]></description>
+<georss:point>44.187000 -110.726000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:10.027857+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28590">
+<link>http://platial.com/place/28590</link>
+<title>Hot Springs on Upper Snake River, WY</title>
+<description><![CDATA[Hot spring, temperature: 167 degress F, 75 degress C<br /><br /><a href="http://platial.com/place/28590">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28590">Grab this on Platial</a> ]]></description>
+<georss:point>44.204000 -110.486000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:07.79658+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28589">
+<link>http://platial.com/place/28589</link>
+<title>Hot Springs on lewis Lake, WY</title>
+<description><![CDATA[Hot spring, temperature: 154 degress F, 68 degress C<br /><br /><a href="http://platial.com/place/28589">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28589">Grab this on Platial</a> ]]></description>
+<georss:point>44.276000 -110.636000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:05.683418+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28588">
+<link>http://platial.com/place/28588</link>
+<title>Rustic Geyser, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28588">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28588">Grab this on Platial</a> ]]></description>
+<georss:point>44.282000 -110.506000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:03.66329+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28587">
+<link>http://platial.com/place/28587</link>
+<title>Bechler River Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 194 degress F, 90 degress C<br /><br /><a href="http://platial.com/place/28587">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28587">Grab this on Platial</a> ]]></description>
+<georss:point>44.285000 -110.900000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:01.611442+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28586">
+<link>http://platial.com/place/28586</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, 201 degress C<br /><br /><a href="http://platial.com/place/28586">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28586">Grab this on Platial</a> ]]></description>
+<georss:point>44.290000 -110.504000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:59.658699+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28585">
+<link>http://platial.com/place/28585</link>
+<title>Heart Lake Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: Middle Group degress F, 174 degress C<br /><br /><a href="http://platial.com/place/28585">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28585">Grab this on Platial</a> ]]></description>
+<georss:point>44.299000 -110.517000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:57.181801+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28584">
+<link>http://platial.com/place/28584</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, 201 degress C<br /><br /><a href="http://platial.com/place/28584">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28584">Grab this on Platial</a> ]]></description>
+<georss:point>44.307000 -110.526000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:55.240485+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28583">
+<link>http://platial.com/place/28583</link>
+<title>Hot Springs on lewis Lake, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28583">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28583">Grab this on Platial</a> ]]></description>
+<georss:point>44.309000 -110.654000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:53.22295+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28582">
+<link>http://platial.com/place/28582</link>
+<title>Shoshone Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28582">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28582">Grab this on Platial</a> ]]></description>
+<georss:point>44.354000 -110.800000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:51.179049+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28581">
+<link>http://platial.com/place/28581</link>
+<title>Hot Springs on Continental Divide, WY</title>
+<description><![CDATA[Hot spring, temperature: 189 degress F, 87 degress C<br /><br /><a href="http://platial.com/place/28581">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28581">Grab this on Platial</a> ]]></description>
+<georss:point>44.401000 -110.936000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:49.077176+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28580">
+<link>http://platial.com/place/28580</link>
+<title>Hot Springs on Upper Firehole River, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28580">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28580">Grab this on Platial</a> ]]></description>
+<georss:point>44.404000 -110.824000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:47.054664+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28579">
+<link>http://platial.com/place/28579</link>
+<title>Summit Lake Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 162 degress F, 72 degress C<br /><br /><a href="http://platial.com/place/28579">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28579">Grab this on Platial</a> ]]></description>
+<georss:point>44.410000 -110.953000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:45.039394+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28578">
+<link>http://platial.com/place/28578</link>
+<title>Lone Star Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: Footbridge degress F, 183 degress C<br /><br /><a href="http://platial.com/place/28578">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28578">Grab this on Platial</a> ]]></description>
+<georss:point>44.414000 -110.817000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:42.938808+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28577">
+<link>http://platial.com/place/28577</link>
+<title>West. Thumb Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28577">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28577">Grab this on Platial</a> ]]></description>
+<georss:point>44.417000 -110.570000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:40.90238+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28576">
+<link>http://platial.com/place/28576</link>
+<title>Lone Star Geyser, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28576">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28576">Grab this on Platial</a> ]]></description>
+<georss:point>44.418000 -110.805000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:38.844625+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28575">
+<link>http://platial.com/place/28575</link>
+<title>Smoke Jumper Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br /><br /><a href="http://platial.com/place/28575">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28575">Grab this on Platial</a> ]]></description>
+<georss:point>44.421000 -110.952000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:36.818513+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28574">
+<link>http://platial.com/place/28574</link>
+<title>West. Thumb Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 196 degress F, 91 degress C<br /><br /><a href="http://platial.com/place/28574">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28574">Grab this on Platial</a> ]]></description>
+<georss:point>44.422000 -110.574000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:34.767729+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28573">
+<link>http://platial.com/place/28573</link>
+<title>Potts Hot Spring Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28573">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28573">Grab this on Platial</a> ]]></description>
+<georss:point>44.433000 -110.581000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:32.749915+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28572">
+<link>http://platial.com/place/28572</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28572">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28572">Grab this on Platial</a> ]]></description>
+<georss:point>44.433000 -110.813000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:30.829745+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28571">
+<link>http://platial.com/place/28571</link>
+<title>Hot Springs on Continental Divide, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28571">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28571">Grab this on Platial</a> ]]></description>
+<georss:point>44.438000 -110.977000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:28.730401+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28570">
+<link>http://platial.com/place/28570</link>
+<title>SouthEastern Group, WY</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br /><br /><a href="http://platial.com/place/28570">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28570">Grab this on Platial</a> ]]></description>
+<georss:point>44.459000 -110.817000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:26.706763+00:00</dc:date>
+</item>
+</rdf:RDF>
diff --git a/misc/openlayers/examples/getfeature-wfs.html b/misc/openlayers/examples/getfeature-wfs.html
new file mode 100644
index 0000000..0f2096f
--- /dev/null
+++ b/misc/openlayers/examples/getfeature-wfs.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <title>WFS: GetFeature Example (GeoServer)</title>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer, select, hover, control;
+
+ function init(){
+ OpenLayers.ProxyHost= "proxy.cgi?url=";
+ map = new OpenLayers.Map('map', {
+ controls: [
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.Permalink(),
+ new OpenLayers.Control.Navigation()
+ ]
+ });
+ layer = new OpenLayers.Layer.WMS(
+ "States WMS/WFS",
+ "http://v2.suite.opengeo.org/geoserver/ows",
+ {layers: 'usa:states', format: 'image/gif'}
+ );
+ select = new OpenLayers.Layer.Vector("Selection", {styleMap:
+ new OpenLayers.Style(OpenLayers.Feature.Vector.style["select"])
+ });
+ hover = new OpenLayers.Layer.Vector("Hover");
+ map.addLayers([layer, hover, select]);
+
+ control = new OpenLayers.Control.GetFeature({
+ protocol: OpenLayers.Protocol.WFS.fromWMSLayer(layer),
+ box: true,
+ hover: true,
+ multipleKey: "shiftKey",
+ toggleKey: "ctrlKey"
+ });
+ control.events.register("featureselected", this, function(e) {
+ select.addFeatures([e.feature]);
+ });
+ control.events.register("featureunselected", this, function(e) {
+ select.removeFeatures([e.feature]);
+ });
+ control.events.register("hoverfeature", this, function(e) {
+ hover.addFeatures([e.feature]);
+ });
+ control.events.register("outfeature", this, function(e) {
+ hover.removeFeatures([e.feature]);
+ });
+ map.addControl(control);
+ control.activate();
+
+ map.setCenter(new OpenLayers.Bounds(-140.444336,25.115234,-44.438477,50.580078).getCenterLonLat(), 3);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+<h1 id="title">WFS GetFeature Example (GeoServer)</h1>
+
+<div id="tags">
+WFS, GetFeature
+</div>
+
+<p id="shortdesc">
+ Shows how to use the GetFeature control to select features from a WMS layer.
+</p>
+
+<div id="map" class="smallmap"></div>
+
+<div id="docs">
+ <p>
+ Click or drag a box to select features, use the Shift key to add
+ features to the selection, use the Ctrl key to toggle a feature's
+ selected status. Note that this control also has a hover option, which
+ is enabled in this example. This gives you a visual feedback by loading
+ the feature underneath the mouse pointer from the WFS, but causes a lot
+ of GetFeature requests to be issued.
+ </p>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/examples/getfeatureinfo-control.html b/misc/openlayers/examples/getfeatureinfo-control.html
new file mode 100644
index 0000000..baecd42
--- /dev/null
+++ b/misc/openlayers/examples/getfeatureinfo-control.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WMS Feature Info Example (GeoServer)</title>
+ <script src="../lib/OpenLayers.js"></script>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ ul, li {
+ padding-left: 0px;
+ margin-left: 0px;
+ list-style: none;
+ }
+ #info {
+ position: absolute;
+ top: 6em;
+ left: 550px;
+ }
+ #info table td {
+ border:1px solid #ddd;
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ font-size: 90%;
+ padding: .2em .1em;
+ background:#fff;
+ }
+ #info table th{
+ padding:.2em .2em;
+ text-transform: uppercase;
+ font-weight: bold;
+ background: #eee;
+ }
+ tr.odd td {
+ background:#eee;
+ }
+ table.featureInfo caption {
+ text-align:left;
+ font-size:100%;
+ font-weight:bold;
+ padding:.2em .2em;
+ }
+
+
+ </style>
+ <script defer="defer" type="text/javascript">
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+ var map, infocontrols, water, highlightlayer;
+
+ function load() {
+ map = new OpenLayers.Map('map', {
+ maxExtent: new OpenLayers.Bounds(143.834,-43.648,148.479,-39.573)
+ });
+
+ var political = new OpenLayers.Layer.WMS("State Boundaries",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_state_boundaries', transparent: true, format: 'image/gif'},
+ {isBaseLayer: true}
+ );
+
+ var roads = new OpenLayers.Layer.WMS("Roads",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_roads', transparent: true, format: 'image/gif'},
+ {isBaseLayer: false}
+ );
+
+ var cities = new OpenLayers.Layer.WMS("Cities",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_cities', transparent: true, format: 'image/gif'},
+ {isBaseLayer: false}
+ );
+
+ water = new OpenLayers.Layer.WMS("Bodies of Water",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_water_bodies', transparent: true, format: 'image/gif'},
+ {isBaseLayer: false}
+ );
+
+ highlightLayer = new OpenLayers.Layer.Vector("Highlighted Features", {
+ displayInLayerSwitcher: false,
+ isBaseLayer: false
+ }
+ );
+
+ infoControls = {
+ click: new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://demo.opengeo.org/geoserver/wms',
+ title: 'Identify features by clicking',
+ layers: [water],
+ queryVisible: true
+ }),
+ hover: new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://demo.opengeo.org/geoserver/wms',
+ title: 'Identify features by clicking',
+ layers: [water],
+ hover: true,
+ // defining a custom format options here
+ formatOptions: {
+ typeName: 'water_bodies',
+ featureNS: 'http://www.openplans.org/topp'
+ },
+ queryVisible: true
+ })
+ };
+
+ map.addLayers([political, roads, cities, water, highlightLayer]);
+ for (var i in infoControls) {
+ infoControls[i].events.register("getfeatureinfo", this, showInfo);
+ map.addControl(infoControls[i]);
+ }
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ infoControls.click.activate();
+ map.zoomToMaxExtent();
+ }
+
+ function showInfo(evt) {
+ if (evt.features && evt.features.length) {
+ highlightLayer.destroyFeatures();
+ highlightLayer.addFeatures(evt.features);
+ highlightLayer.redraw();
+ } else {
+ document.getElementById('responseText').innerHTML = evt.text;
+ }
+ }
+
+ function toggleControl(element) {
+ for (var key in infoControls) {
+ var control = infoControls[key];
+ if (element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+
+ function toggleFormat(element) {
+ for (var key in infoControls) {
+ var control = infoControls[key];
+ control.infoFormat = element.value;
+ }
+ }
+
+ function toggleLayers(element) {
+ for (var key in infoControls) {
+ var control = infoControls[key];
+ if (element.value == 'Specified') {
+ control.layers = [water];
+ } else {
+ control.layers = null;
+ }
+ }
+ }
+
+ // function toggle(key
+ </script>
+ </head>
+ <body onload="load()">
+ <h1 id="title">Feature Info Example</h1>
+
+ <div id="tags">
+ WMS, GetFeatureInfo
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the WMSGetFeatureInfo control for fetching information about a position from WMS (via GetFeatureInfo request).
+ </p>
+
+ <div id="info">
+ <h1>Tasmania</h1>
+ <p>Click on the map to get feature info.</p>
+ <div id="responseText">
+ </div>
+ </div>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ </div>
+ <ul id="control">
+ <li>
+ <input type="radio" name="controlType" value="click" id="click"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="click">Click</label>
+ </li>
+ <li>
+ <input type="radio" name="controlType" value="hover" id="hover"
+ onclick="toggleControl(this);" />
+ <label for="hover">Hover</label>
+ </li>
+ </ul>
+ <ul id="format">
+ <li>
+ <input type="radio" name="formatType" value="text/html" id="html"
+ onclick="toggleFormat(this);" checked="checked" />
+ <label for="html">Show HTML Description</label>
+ </li>
+ <li>
+ <input type="radio" name="formatType" value="application/vnd.ogc.gml" id="highlight"
+ onclick="toggleFormat(this);" />
+ <label for="highlight">Highlight Feature on Map</label>
+ </li>
+ </ul>
+ <ul id="layers">
+ <li>
+ <input type="radio" name="layerSelection" value="Specified" id="Specified"
+ onclick="toggleLayers(this);" checked="checked" />
+ <label for="Specified">Get water body info</label>
+ </li>
+ <li>
+ <input type="radio" name="layerSelection" value="Auto" id="Auto"
+ onclick="toggleLayers(this);" />
+ <label for="Auto">Get info for visible layers</label>
+ </li>
+ </ul>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/getfeatureinfo-popup.html b/misc/openlayers/examples/getfeatureinfo-popup.html
new file mode 100644
index 0000000..cecdebe
--- /dev/null
+++ b/misc/openlayers/examples/getfeatureinfo-popup.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>GetFeatureInfo Popup</title>
+ <script src="../lib/OpenLayers.js"></script>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script>
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+ var map, info;
+
+ function load() {
+ map = new OpenLayers.Map({
+ div: "map",
+ maxExtent: new OpenLayers.Bounds(143.834,-43.648,148.479,-39.573)
+ });
+
+ var political = new OpenLayers.Layer.WMS("State Boundaries",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_state_boundaries', transparent: true, format: 'image/gif'},
+ {isBaseLayer: true}
+ );
+
+ var roads = new OpenLayers.Layer.WMS("Roads",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_roads', transparent: true, format: 'image/gif'},
+ {isBaseLayer: false}
+ );
+
+ var cities = new OpenLayers.Layer.WMS("Cities",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_cities', transparent: true, format: 'image/gif'},
+ {isBaseLayer: false}
+ );
+
+ var water = new OpenLayers.Layer.WMS("Bodies of Water",
+ "http://demo.opengeo.org/geoserver/wms",
+ {'layers': 'topp:tasmania_water_bodies', transparent: true, format: 'image/gif'},
+ {isBaseLayer: false}
+ );
+
+ var highlight = new OpenLayers.Layer.Vector("Highlighted Features", {
+ displayInLayerSwitcher: false,
+ isBaseLayer: false
+ });
+
+ map.addLayers([political, roads, cities, water, highlight]);
+
+ info = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://demo.opengeo.org/geoserver/wms',
+ title: 'Identify features by clicking',
+ queryVisible: true,
+ eventListeners: {
+ getfeatureinfo: function(event) {
+ map.addPopup(new OpenLayers.Popup.FramedCloud(
+ "chicken",
+ map.getLonLatFromPixel(event.xy),
+ null,
+ event.text,
+ null,
+ true
+ ));
+ }
+ }
+ });
+ map.addControl(info);
+ info.activate();
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+
+ </script>
+ </head>
+ <body onload="load()">
+ <h1 id="title">Feature Info in Popup</h1>
+
+ <div id="tags">
+ WMS, GetFeatureInfo, popup
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the WMSGetFeatureInfo control for fetching information
+ about a position from WMS (via GetFeatureInfo request). Results
+ are displayed in a popup.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/gml-layer.html b/misc/openlayers/examples/gml-layer.html
new file mode 100644
index 0000000..a87a5f3
--- /dev/null
+++ b/misc/openlayers/examples/gml-layer.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers GML Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToExtent(new OpenLayers.Bounds(-3.922119,44.335327,4.866943,49.553833));
+ map.addLayer(new OpenLayers.Layer.Vector("GML", {
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "gml/polygon.xml",
+ format: new OpenLayers.Format.GML()
+ }),
+ strategies: [new OpenLayers.Strategy.Fixed()]
+ }));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">GML Layer Example</h1>
+
+ <div id="tags">
+ GML
+ </div>
+
+ <p id="shortdesc">
+ Loads locally stored GML vector data on a basemap. Includes GML example file.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/gml/line.xml b/misc/openlayers/examples/gml/line.xml
new file mode 100644
index 0000000..4f42499
--- /dev/null
+++ b/misc/openlayers/examples/gml/line.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<wfs:FeatureCollection xmlns:ms="http://mapserver.gis.umn.edu/mapserver" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd http://mapserver.gis.umn.edu/mapserver http://aneto.oco/cgi-bin/worldwfs?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=line&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-3.924027,46.037889 2.193186,47.897181</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <ms:line fid="1">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-0.631235,46.037889 2.193186,46.704963</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:LineString srsName="EPSG:4326">
+ <gml:coordinates>-0.631235,46.307557 -0.262215,46.577225 0.106805,46.477874 0.220349,46.293364 0.475824,46.406909 0.887424,46.350136 1.029354,46.563032 1.213864,46.648191 1.526112,46.421102 1.795780,46.066275 2.108028,46.037889 2.178993,46.250785 2.193186,46.492067 2.193186,46.492067 2.051255,46.704963 2.051255,46.704963 </gml:coordinates>
+ </gml:LineString>
+ </ms:msGeometry>
+ <ms:ogc_fid>1</ms:ogc_fid>
+ <ms:name/>
+ <ms:id>0</ms:id>
+ </ms:line>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:line fid="2">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-3.924027,46.279171 -1.127992,47.897181</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:LineString srsName="EPSG:4326">
+ <gml:coordinates>-1.127992,46.279171 -1.369275,46.364329 -1.624750,46.406909 -1.866032,46.492067 -1.993770,46.704963 -2.178280,46.846894 -1.979577,47.059790 -2.164087,47.144948 -2.135700,47.215914 -2.093121,47.357844 -2.277631,47.258493 -2.391176,47.301072 -2.490527,47.315265 -2.476334,47.443003 -2.575686,47.599127 -2.703423,47.542354 -2.873740,47.471389 -3.285339,47.670092 -3.597587,47.769443 -3.824676,47.840409 -3.924027,47.897181 </gml:coordinates>
+ </gml:LineString>
+ </ms:msGeometry>
+ <ms:ogc_fid>2</ms:ogc_fid>
+ <ms:name/>
+ <ms:id>0</ms:id>
+ </ms:line>
+ </gml:featureMember>
+</wfs:FeatureCollection> \ No newline at end of file
diff --git a/misc/openlayers/examples/gml/multipoint.xml b/misc/openlayers/examples/gml/multipoint.xml
new file mode 100644
index 0000000..803fd47
--- /dev/null
+++ b/misc/openlayers/examples/gml/multipoint.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<wfs:FeatureCollection xmlns:ms="http://mapserver.gis.umn.edu/mapserver" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd http://mapserver.gis.umn.edu/mapserver http://aneto.oco/cgi-bin/worldwfs?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=multipoint&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>0.490018,45.001795 3.016384,45.839186</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <ms:multipoint fid="1">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>0.930003,45.001795 3.016384,45.541131</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:MultiPoint srsName="EPSG:4326">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates>2.079641,45.001795</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates>2.718330,45.541131</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates>3.016384,45.143725</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates>0.930003,45.001795</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ </gml:MultiPoint>
+ </ms:msGeometry>
+ <ms:ogc_fid>1</ms:ogc_fid>
+ <ms:name>4 points</ms:name>
+ <ms:id>1</ms:id>
+ </ms:multipoint>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:multipoint fid="2">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>0.490018,45.654676 1.157092,45.839186</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:MultiPoint srsName="EPSG:4326">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates>0.490018,45.654676</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates>1.157092,45.839186</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ </gml:MultiPoint>
+ </ms:msGeometry>
+ <ms:ogc_fid>2</ms:ogc_fid>
+ <ms:name>2 points</ms:name>
+ <ms:id>2</ms:id>
+ </ms:multipoint>
+ </gml:featureMember>
+</wfs:FeatureCollection> \ No newline at end of file
diff --git a/misc/openlayers/examples/gml/multipolygon.xml b/misc/openlayers/examples/gml/multipolygon.xml
new file mode 100644
index 0000000..bcdb39e
--- /dev/null
+++ b/misc/openlayers/examples/gml/multipolygon.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<wfs:FeatureCollection xmlns:ms="http://mapserver.gis.umn.edu/mapserver" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd http://mapserver.gis.umn.edu/mapserver http://aneto.oco/cgi-bin/worldwfs?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=multipolygon&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-1.738295,46.307557 3.754424,47.244300</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <ms:multipolygon fid="1">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-1.738295,46.605612 1.767394,47.244300</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:MultiPolygon srsName="EPSG:4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>1.313216,46.690770 1.000968,46.861087 0.887424,47.059790 1.142899,47.244300 1.355795,47.244300 1.554498,47.017211 1.710622,47.059790 1.767394,46.747542 1.313216,46.690770 1.313216,46.690770 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>0.731300,46.605612 -0.191250,46.704963 -0.191250,46.846894 0.177770,46.988824 0.447438,46.960438 0.589369,46.804315 0.688721,46.832701 0.731300,46.605612 0.731300,46.605612 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>-1.610557,46.733349 -1.184765,46.704963 -1.198958,46.704963 -0.943483,46.619805 -0.915096,46.818508 -0.659621,46.775928 -0.688007,47.017211 -0.943483,47.003018 -1.127992,47.088176 -1.397661,47.102369 -1.624750,47.073983 -1.738295,46.917859 -1.610557,46.733349 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </ms:msGeometry>
+ <ms:ogc_fid>1</ms:ogc_fid>
+ <ms:name>My first Multipolygon</ms:name>
+ <ms:id>0</ms:id>
+ </ms:multipolygon>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:multipolygon fid="2">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>2.789295,46.392716 3.754424,46.903666</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:Polygon srsName="EPSG:4326">
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>2.959612,46.392716 2.789295,46.775928 3.172508,46.903666 3.498949,46.903666 3.498949,46.662384 3.754424,46.563032 2.959612,46.392716 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </ms:msGeometry>
+ <ms:ogc_fid>2</ms:ogc_fid>
+ <ms:name>My second Multipolygon</ms:name>
+ <ms:id>0</ms:id>
+ </ms:multipolygon>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:multipolygon fid="3">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>2.207379,46.307557 2.803488,47.045597</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:MultiPolygon srsName="EPSG:4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>2.292538,46.804315 2.207379,47.017211 2.391889,47.045597 2.562206,46.832701 2.292538,46.804315 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>2.789295,46.307557 2.789295,46.307557 2.803488,46.506260 2.618978,46.676577 2.349310,46.633998 2.448661,46.392716 2.789295,46.307557 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </ms:msGeometry>
+ <ms:ogc_fid>3</ms:ogc_fid>
+ <ms:name>My third Multipolygon</ms:name>
+ <ms:id>0</ms:id>
+ </ms:multipolygon>
+ </gml:featureMember>
+</wfs:FeatureCollection> \ No newline at end of file
diff --git a/misc/openlayers/examples/gml/owls.xml b/misc/openlayers/examples/gml/owls.xml
new file mode 100644
index 0000000..4a001ec
--- /dev/null
+++ b/misc/openlayers/examples/gml/owls.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:bsc="http://www.bsc-eoc.org/bsc"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd
+ http://www.bsc-eoc.org/bsc http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=OWLS&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-89.817223,45.005555 -74.755001,51.701388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember><bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110 -79.771668,45.891110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277 -83.755834,46.365277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:owlname>owl</bsc:owlname>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277 -83.808612,46.175277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166 -84.111112,46.309166</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110 -83.678612,46.821110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888 -83.664445,46.518888</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277 -80.613334,46.730277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054 -79.676946,45.428054</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944 -83.853056,46.236944</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388 -82.289167,45.896388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+
diff --git a/misc/openlayers/examples/gml/point.xml b/misc/openlayers/examples/gml/point.xml
new file mode 100644
index 0000000..10a4820
--- /dev/null
+++ b/misc/openlayers/examples/gml/point.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<wfs:FeatureCollection xmlns:ms="http://mapserver.gis.umn.edu/mapserver" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd http://mapserver.gis.umn.edu/mapserver http://aneto.oco/cgi-bin/worldwfs?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=point&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-0.608315,44.857522 -0.021418,45.477577</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <ms:point fid="1">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-0.608315,44.857522 -0.608315,44.857522</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-0.608315,44.857522</gml:coordinates>
+ </gml:Point>
+ </ms:msGeometry>
+ <ms:ogc_fid>1</ms:ogc_fid>
+ <ms:name>Bordeaux</ms:name>
+ <ms:id>124</ms:id>
+ </ms:point>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:point fid="2">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-0.021418,45.477577 -0.021418,45.477577</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-0.021418,45.477577</gml:coordinates>
+ </gml:Point>
+ </ms:msGeometry>
+ <ms:ogc_fid>2</ms:ogc_fid>
+ <ms:name>Barbezieux</ms:name>
+ <ms:id>0</ms:id>
+ </ms:point>
+ </gml:featureMember>
+</wfs:FeatureCollection> \ No newline at end of file
diff --git a/misc/openlayers/examples/gml/polygon.xml b/misc/openlayers/examples/gml/polygon.xml
new file mode 100644
index 0000000..e4f6903
--- /dev/null
+++ b/misc/openlayers/examples/gml/polygon.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<wfs:FeatureCollection xmlns:ms="http://mapserver.gis.umn.edu/mapserver" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd http://mapserver.gis.umn.edu/mapserver http://aneto.oco/cgi-bin/worldwfs?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=polygon&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-0.768746,47.003018 3.002191,47.925567</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <ms:polygon fid="1">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-0.768746,47.003018 0.532597,47.925567</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:MultiPolygon srsName="EPSG:4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>-0.318987,47.003018 -0.768746,47.358268 -0.574463,47.684285 -0.347374,47.854602 -0.006740,47.925567 0.135191,47.726864 0.149384,47.599127 0.419052,47.670092 0.532597,47.428810 0.305508,47.443003 0.475824,47.144948 0.064225,47.201721 -0.318987,47.003018 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>-0.035126,47.485582 -0.035126,47.485582 -0.049319,47.641706 -0.233829,47.655899 -0.375760,47.457196 -0.276408,47.286879 -0.035126,47.485582 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </ms:msGeometry>
+ <ms:ogc_fid>1</ms:ogc_fid>
+ <ms:name>My Polygon with hole</ms:name>
+ <ms:id>0</ms:id>
+ </ms:polygon>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:polygon fid="2">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>1.511919,47.088176 3.002191,47.882988</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:Polygon srsName="EPSG:4326">
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>1.625463,47.357844 1.511919,47.741057 1.880938,47.882988 2.420275,47.797830 2.789295,47.485582 3.002191,47.457196 2.874453,47.088176 2.178993,47.343651 1.625463,47.357844 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </ms:msGeometry>
+ <ms:ogc_fid>2</ms:ogc_fid>
+ <ms:name>My simple Polygon</ms:name>
+ <ms:id>0</ms:id>
+ </ms:polygon>
+ </gml:featureMember>
+ <gml:featureMember>
+ <ms:polygon fid="3">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>0.000000,45.000000 2.000000,47.000000</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <ms:msGeometry>
+ <gml:MultiPolygon srsName="EPSG:4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>0.000000,45.000000 2.000000,45.000000 2.000000,47.000000 0.000000,47.000000 0.000000,45.000000 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates>0.500000,45.500000 1.500000,45.500000 1.500000,46.500000 0.500000,46.500000 0.500000,45.500000 </gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </ms:msGeometry>
+ <ms:ogc_fid>3</ms:ogc_fid>
+ <ms:name>my polygon with hole</ms:name>
+ <ms:id>3</ms:id>
+ </ms:polygon>
+ </gml:featureMember>
+</wfs:FeatureCollection> \ No newline at end of file
diff --git a/misc/openlayers/examples/google-static.html b/misc/openlayers/examples/google-static.html
new file mode 100644
index 0000000..d35efb0
--- /dev/null
+++ b/misc/openlayers/examples/google-static.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Google (Static Maps API) Grid Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">Google (Static Maps API) Grid Layer Example</h1>
+ <div id="tags">
+ Google, grid, static, GMaps, light
+ </div>
+ <p id="shortdesc">
+ Using the Google Static Maps API with a Grid Layer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>A Grid layer with a custom <code>getURL</code> method can be
+ used to request static maps for a specific extent and zoom
+ level. The Google Static Maps API is the most reliable way to
+ get Google base maps in OpenLayers. Note, however, that the
+ free version of this is limited to a map size of 640x640 pixels
+ (1280x1280 if the <code>scale=2</code> url parameter is used)
+ and 1000 page views per viewer per day. Every map center
+ or zoom level change increases the page view counter by 1.
+ </p>
+ <p>Look at the
+ <a href="google-static.js" target="_blank">google-static.js
+ source</a> to see how this is done. See the
+ <a href="http://code.google.com/apis/maps/documentation/staticmaps/">Static Maps API V2 Developer Guide</a>
+ for details on the API.
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="google-static.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/google-static.js b/misc/openlayers/examples/google-static.js
new file mode 100644
index 0000000..f984f1a
--- /dev/null
+++ b/misc/openlayers/examples/google-static.js
@@ -0,0 +1,61 @@
+var options = {
+ singleTile: true,
+ ratio: 1,
+ isBaseLayer: true,
+ wrapDateLine: true,
+ getURL: function() {
+ var center = this.map.getCenter().transform("EPSG:3857", "EPSG:4326"),
+ size = this.map.getSize();
+ return [
+ this.url, "&center=", center.lat, ",", center.lon,
+ "&zoom=", this.map.getZoom(), "&size=", size.w, "x", size.h
+ ].join("");
+ }
+};
+
+var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:3857",
+ numZoomLevels: 22,
+ layers: [
+ new OpenLayers.Layer.Grid(
+ "Google Physical",
+ "http://maps.googleapis.com/maps/api/staticmap?sensor=false&maptype=terrain",
+ null, options
+ ),
+ new OpenLayers.Layer.Grid(
+ "Google Streets",
+ "http://maps.googleapis.com/maps/api/staticmap?sensor=false&maptype=roadmap",
+ null, options
+ ),
+ new OpenLayers.Layer.Grid(
+ "Google Hybrid",
+ "http://maps.googleapis.com/maps/api/staticmap?sensor=false&maptype=hybrid",
+ null, options
+ ),
+ new OpenLayers.Layer.Grid(
+ "Google Satellite",
+ "http://maps.googleapis.com/maps/api/staticmap?sensor=false&maptype=satellite",
+ null, options
+ ),
+ // the same layer again, but scaled to allow map sizes up to 1280x1280 pixels
+ new OpenLayers.Layer.Grid(
+ "Google Satellite (scale=2)",
+ "http://maps.googleapis.com/maps/api/staticmap?sensor=false&maptype=satellite&scale=2",
+ null, OpenLayers.Util.applyDefaults({
+ getURL: function() {
+ var center = this.map.getCenter().transform("EPSG:3857", "EPSG:4326"),
+ size = this.map.getSize();
+ return [
+ this.url, "&center=", center.lat, ",", center.lon,
+ "&zoom=", (this.map.getZoom() - 1),
+ "&size=", Math.floor(size.w / 2), "x", Math.floor(size.h / 2)
+ ].join("");
+ }
+ }, options)
+ )
+ ],
+ center: new OpenLayers.LonLat(10.2, 48.9).transform("EPSG:4326", "EPSG:3857"),
+ zoom: 5
+});
+map.addControl(new OpenLayers.Control.LayerSwitcher());
diff --git a/misc/openlayers/examples/google-v3-alloverlays.html b/misc/openlayers/examples/google-v3-alloverlays.html
new file mode 100644
index 0000000..b244d9e
--- /dev/null
+++ b/misc/openlayers/examples/google-v3-alloverlays.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Google (v3) Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="http://maps.google.com/maps/api/js?v=3&amp;sensor=false"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="google-v3-alloverlays.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Google (v3) allOverlays Layer Example</h1>
+ <div id="tags">
+ Google, overlay, light
+ </div>
+ <p id="shortdesc">
+ Demonstrate use the Google Maps v3 API with allOverlays set to true on the map.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ You can also use Google layers as overlays, e.g. in a map with
+ allOverlays set to true. Note some of the layers disappear as
+ you zoom in to levels that are not supported by all layers. See the
+ <a href="google-v3-alloverlays.js" target="_blank">google-v3-alloverlays.js source</a>
+ to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/google-v3-alloverlays.js b/misc/openlayers/examples/google-v3-alloverlays.js
new file mode 100644
index 0000000..e2e4da4
--- /dev/null
+++ b/misc/openlayers/examples/google-v3-alloverlays.js
@@ -0,0 +1,35 @@
+var map;
+
+function init() {
+ map = new OpenLayers.Map('map', {allOverlays: true});
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ // the SATELLITE layer has all 22 zoom level, so we add it first to
+ // become the internal base layer that determines the zoom levels of the
+ // map.
+ var gsat = new OpenLayers.Layer.Google(
+ "Google Satellite",
+ {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22}
+ );
+ var gphy = new OpenLayers.Layer.Google(
+ "Google Physical",
+ {type: google.maps.MapTypeId.TERRAIN, visibility: false}
+ );
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", // the default
+ {numZoomLevels: 20, visibility: false}
+ );
+ var ghyb = new OpenLayers.Layer.Google(
+ "Google Hybrid",
+ {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 22, visibility: false}
+ );
+
+ map.addLayers([gsat, gphy, gmap, ghyb]);
+
+ // Google.v3 uses EPSG:900913 as projection, so we have to
+ // transform our coordinates
+ map.setCenter(new OpenLayers.LonLat(10.2, 48.9).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+ ), 5);
+}
diff --git a/misc/openlayers/examples/google-v3.html b/misc/openlayers/examples/google-v3.html
new file mode 100644
index 0000000..5c11ae9
--- /dev/null
+++ b/misc/openlayers/examples/google-v3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Google (v3) Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="http://maps.google.com/maps/api/js?v=3&amp;sensor=false"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="google-v3.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Google (v3) Layer Example</h1>
+ <div id="tags">
+ Google, api key, apikey, light
+ </div>
+ <p id="shortdesc">
+ Demonstrate use the Google Maps v3 API.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p><input id="animate" type="checkbox" checked="checked">Animated
+ zoom (if supported by GMaps on your device)</input></p>
+ <p>
+ If you use the Google Maps v3 API with a Google layer, you don't
+ need to include an API key. This layer only works in the
+ spherical mercator projection. See the
+ <a href="google-v3.js" target="_blank">google-v3.js source</a>
+ to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/google-v3.js b/misc/openlayers/examples/google-v3.js
new file mode 100644
index 0000000..e81c6a4
--- /dev/null
+++ b/misc/openlayers/examples/google-v3.js
@@ -0,0 +1,39 @@
+var map;
+
+function init() {
+ map = new OpenLayers.Map('map', {
+ projection: 'EPSG:3857',
+ layers: [
+ new OpenLayers.Layer.Google(
+ "Google Physical",
+ {type: google.maps.MapTypeId.TERRAIN}
+ ),
+ new OpenLayers.Layer.Google(
+ "Google Streets", // the default
+ {numZoomLevels: 20}
+ ),
+ new OpenLayers.Layer.Google(
+ "Google Hybrid",
+ {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20}
+ ),
+ new OpenLayers.Layer.Google(
+ "Google Satellite",
+ {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22}
+ )
+ ],
+ center: new OpenLayers.LonLat(10.2, 48.9)
+ // Google.v3 uses web mercator as projection, so we have to
+ // transform our coordinates
+ .transform('EPSG:4326', 'EPSG:3857'),
+ zoom: 5
+ });
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ // add behavior to html
+ var animate = document.getElementById("animate");
+ animate.onclick = function() {
+ for (var i=map.layers.length-1; i>=0; --i) {
+ map.layers[i].animationEnabled = this.checked;
+ }
+ };
+}
diff --git a/misc/openlayers/examples/google.html b/misc/openlayers/examples/google.html
new file mode 100644
index 0000000..e6e20b2
--- /dev/null
+++ b/misc/openlayers/examples/google.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Google Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <!-- this gmaps key generated for http://openlayers.org/dev/ -->
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ map = new OpenLayers.Map('map');
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ var gphy = new OpenLayers.Layer.Google(
+ "Google Physical",
+ {type: G_PHYSICAL_MAP}
+ );
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", // the default
+ {numZoomLevels: 20}
+ );
+ var ghyb = new OpenLayers.Layer.Google(
+ "Google Hybrid",
+ {type: G_HYBRID_MAP, numZoomLevels: 20}
+ );
+ var gsat = new OpenLayers.Layer.Google(
+ "Google Satellite",
+ {type: G_SATELLITE_MAP, numZoomLevels: 22}
+ );
+
+
+ map.addLayers([gphy, gmap, ghyb, gsat]);
+
+ map.setCenter(new OpenLayers.LonLat(10.2, 48.9), 5);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Google Layer Example</h1>
+
+ <div id="tags">
+ Google
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate use of the various types of Google layers.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ For best performance, you must be using a version of the Google Maps
+ API which is v2.93 or higher. In order to use this version of the API,
+ it is best to simply set your application to use the string "v=2" in
+ the request, rather than tying your application to an explicit version.</p>
+ <p>
+ In order to position the Google attribution div in the default location,
+ you must include the extra theme/default/google.css stylesheet.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/graphic-name.html b/misc/openlayers/examples/graphic-name.html
new file mode 100644
index 0000000..a530f10
--- /dev/null
+++ b/misc/openlayers/examples/graphic-name.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>OpenLayers Graphic Names</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script src="./graphic-name.js" type="text/javascript"></script>
+ </head>
+ <body onload="init();">
+ <h1 id="title">Named Graphics Example</h1>
+ <div id="tags">
+ vector, named graphic, star, cross, x, square, triangle, circle, style, light
+ </div>
+ <p id="shortdesc">
+ Shows how to use well-known graphic names.
+ </p>
+ <div id="map" class="smallmap">
+ </div>
+ <div id="docs">
+ <p>
+ OpenLayers supports well-known names for a few graphics. You
+ can use the names &quot;star&quot;, &quot;cross&quot;,
+ &quot;x&quot;, &quot;square&quot;, &quot;triangle&quot;, and
+ &quot;circle&quot; as value for the graphicName property of a
+ symbolizer.
+ </p>
+ <p>
+ The named symbols &quot;lightning&quot;, &quot;rectangle&quot;
+ and &quot;church&quot; are user defined.
+ </p>
+ <p>
+ See <a href="./graphic-name.js">graphic-name.js</a>
+ for the source code of this example.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/graphic-name.js b/misc/openlayers/examples/graphic-name.js
new file mode 100644
index 0000000..654a4c9
--- /dev/null
+++ b/misc/openlayers/examples/graphic-name.js
@@ -0,0 +1,67 @@
+// user custom graphicname
+OpenLayers.Renderer.symbol.lightning = [0, 0, 4, 2, 6, 0, 10, 5, 6, 3, 4, 5, 0, 0];
+OpenLayers.Renderer.symbol.rectangle = [0, 0, 4, 0, 4, 10, 0, 10, 0, 0];
+OpenLayers.Renderer.symbol.church = [4, 0, 6, 0, 6, 4, 10, 4, 10, 6, 6, 6, 6, 14, 4, 14, 4, 6, 0, 6, 0, 4, 4, 4, 4, 0];
+var map;
+
+function init(){
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ map = new OpenLayers.Map('map', {
+ controls: []
+ });
+
+ // list of well-known graphic names
+ var graphics = ["star", "cross", "x", "square", "triangle", "circle", "lightning", "rectangle", "church"];
+
+ // Create one feature for each well known graphic.
+ // Give features a type attribute with the graphic name.
+ var num = graphics.length;
+ var slot = map.maxExtent.getWidth() / num;
+ var features = Array(num);
+ for (var i = 0; i < graphics.length; ++i) {
+ lon = map.maxExtent.left + (i * slot) + (slot / 2);
+ features[i] = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(map.maxExtent.left + (i * slot) + (slot / 2), 0), {
+ type: graphics[i]
+ });
+ }
+
+ // Create a style map for painting the features.
+ // The graphicName property of the symbolizer is evaluated using
+ // the type attribute on each feature (set above).
+ var styles = new OpenLayers.StyleMap({
+ "default": {
+ graphicName: "${type}",
+ pointRadius: 10,
+ strokeColor: "fuchsia",
+ strokeWidth: 2,
+ fillColor: "lime",
+ fillOpacity: 0.6
+ },
+ "select": {
+ pointRadius: 20,
+ fillOpacity: 1,
+ rotation: 45
+ }
+ });
+
+ // Create a vector layer and give it your style map.
+ var layer = new OpenLayers.Layer.Vector("Graphics", {
+ styleMap: styles,
+ isBaseLayer: true,
+ renderers: renderer
+ });
+ layer.addFeatures(features);
+ map.addLayer(layer);
+
+ // Create a select feature control and add it to the map.
+ var select = new OpenLayers.Control.SelectFeature(layer, {
+ hover: true
+ });
+ map.addControl(select);
+ select.activate();
+
+ map.zoomToMaxExtent();
+}
diff --git a/misc/openlayers/examples/graticule.html b/misc/openlayers/examples/graticule.html
new file mode 100644
index 0000000..c5a116d
--- /dev/null
+++ b/misc/openlayers/examples/graticule.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Graticule Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ width: 600px;
+ height: 300px;
+ border: 1px solid black;
+ float:left;
+ }
+ #map2 {
+ width: 400px;
+ height: 400px;
+ border: 1px solid black;
+ float:left;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="http://svn.osgeo.org/metacrs/proj4js/trunk/lib/proj4js-compressed.js"></script>
+ <script type="text/javascript">
+ Proj4js.defs["EPSG:42304"]="+title=Atlas of Canada, LCC +proj=lcc +lat_1=49 +lat_2=77 +lat_0=49 +lon_0=-95 +x_0=0 +y_0=0 +ellps=GRS80 +datum=NAD83 +units=m +no_defs";
+
+ var graticuleCtl1, graticuleCtl2;
+ var map1, map2;
+ function init(){
+ initLonLat();
+ initProjected();
+ }
+ function initLonLat(){
+ graticuleCtl1 = new OpenLayers.Control.Graticule({
+ numPoints: 2,
+ labelled: true
+ });
+ map1 = new OpenLayers.Map('map', {
+ controls: [
+ graticuleCtl1,
+ new OpenLayers.Control.LayerSwitcher(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.Navigation()
+ ]
+ });
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}, {wrapDateLine: true} );
+
+ map1.addLayers([ol_wms]);
+ if (!map1.getCenter()) map1.zoomToMaxExtent();
+ };
+
+ function initProjected(){
+ var extent = new OpenLayers.Bounds(-2200000,-712631,3072800,3840000);
+ graticuleCtl2 = new OpenLayers.Control.Graticule({
+ labelled: true,
+ targetSize: 200
+ });
+ var mapOptions = {
+ controls: [
+ graticuleCtl2,
+ new OpenLayers.Control.LayerSwitcher(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.Navigation()
+ ],
+ //scales: tempScales,
+ maxExtent: extent,
+ maxResolution: 50000,
+ units: 'm',
+ projection: 'EPSG:42304'
+ };
+ map2 = new OpenLayers.Map('map2', mapOptions);
+
+ var dm_wms = new OpenLayers.Layer.WMS( "DM Solutions Demo",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap", {
+ layers: "bathymetry",
+ format: "image/png"
+ },{
+ singleTile: true
+ });
+
+ map2.addLayers([dm_wms]);
+ if (!map2.getCenter()) map2.zoomToExtent(extent);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Graticule Example</h1>
+
+ <div id="tags">
+ graticule, grid, projection, proj4js, reproject, transform
+ </div>
+
+ <p id="shortdesc">
+ Adds a Graticule control to the map to display a grid of
+ latitude and longitude.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="map2" class="smallmap"></div>
+
+ <div id="docs"></div>
+ <br style="clear:both" />
+ <ul>
+ <li><a href="#"
+ onclick="graticuleCtl1.activate(); graticuleCtl2.activate(); return false;">Activate graticule controls</a></li>
+ <li><a href="#"
+ onclick="graticuleCtl1.deactivate(); graticuleCtl2.deactivate(); return false;">Deactivate graticule controls</a></li>
+ </ul>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/gutter.html b/misc/openlayers/examples/gutter.html
new file mode 100644
index 0000000..1a98a5c
--- /dev/null
+++ b/misc/openlayers/examples/gutter.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Gutter Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ p.caption {
+ width: 512px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Gutter Example</h1>
+
+ <div id="tags">
+ gutter, quality, tile, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates OpenLayer's facility for dealing with tiling artifacts.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p class="caption">
+ When you render tiles with certain types of symbols, some map
+ servers may render artifacts at tile edges that make symbology not
+ look continuous. Look at the state abbreviations, open the layer
+ switcher and change to the layer with a 15 pixel gutter to see how
+ the symbology looks different (the server in this example doesn't
+ render such artifacts, so the client-side gutter won't make things
+ look nicer).
+ </p>
+ </div>
+ </body>
+ <script type="text/javascript">
+ var map = new OpenLayers.Map('map');
+ var states15 = new OpenLayers.Layer.WMS( "States (15px gutter)",
+ "http://suite.opengeo.org/geoserver/wms",
+ {layers: 'usa:states'},
+ {gutter: 15, transitionEffect: "resize"});
+ var states = new OpenLayers.Layer.WMS( "States (no gutter)",
+ "http://suite.opengeo.org/geoserver/wms",
+ {layers: 'usa:states'});
+ map.addLayers([states, states15]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(-71.848, 42.2), 5);
+ </script>
+</html>
diff --git a/misc/openlayers/examples/highlight-feature.html b/misc/openlayers/examples/highlight-feature.html
new file mode 100644
index 0000000..81e5c6f
--- /dev/null
+++ b/misc/openlayers/examples/highlight-feature.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>SelectFeature Control for Select and Highlight</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css">
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var map, controls;
+
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var vectors = new OpenLayers.Layer.Vector("vector", {isBaseLayer: true});
+ map.addLayers([vectors]);
+
+ var feature = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT(
+ "POLYGON((28.828125 0.3515625, 132.1875 -13.0078125, -1.40625 -59.4140625, 28.828125 0.3515625))"
+ )
+ );
+ vectors.addFeatures([feature]);
+
+ var feature2 = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT(
+ "POLYGON((-120.828125 -50.3515625, -80.1875 -80.0078125, -40.40625 -20.4140625, -120.828125 -50.3515625))"
+ )
+ );
+ vectors.addFeatures([feature2]);
+
+ var report = function(e) {
+ OpenLayers.Console.log(e.type, e.feature.id);
+ };
+
+ var highlightCtrl = new OpenLayers.Control.SelectFeature(vectors, {
+ hover: true,
+ highlightOnly: true,
+ renderIntent: "temporary",
+ eventListeners: {
+ beforefeaturehighlighted: report,
+ featurehighlighted: report,
+ featureunhighlighted: report
+ }
+ });
+
+ var selectCtrl = new OpenLayers.Control.SelectFeature(vectors,
+ {clickout: true}
+ );
+
+ map.addControl(highlightCtrl);
+ map.addControl(selectCtrl);
+
+ highlightCtrl.activate();
+ selectCtrl.activate();
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Select and Highlight Feature Example</h1>
+ <div id="tags">
+ select, highlight, hover, onmouseover, click, vector
+ </div>
+ <p id="shortdesc">
+ Select features on click, highlight features on hover.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <p>Select features by clicking on them. Just highlight features by hovering over
+ them.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/hover-handler.html b/misc/openlayers/examples/hover-handler.html
new file mode 100644
index 0000000..84d7f1c
--- /dev/null
+++ b/misc/openlayers/examples/hover-handler.html
@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Hover Handler Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ width: 340px;
+ height: 170px;
+ border: 1px solid gray;
+ }
+ #west {
+ width: 350px;
+ }
+ #east {
+ position: absolute;
+ left: 370px;
+ top: 3em;
+ }
+
+ table td {
+ text-align: center;
+ margin: 0;
+ border: 1px solid gray;
+ }
+ textarea.output {
+ text-align: left;
+ font-size: 0.9em;
+ width: 250px;
+ height: 65px;
+ overflow: auto;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ OpenLayers.Control.Hover = OpenLayers.Class(OpenLayers.Control, {
+ defaultHandlerOptions: {
+ 'delay': 500,
+ 'pixelTolerance': null,
+ 'stopMove': false
+ },
+
+ initialize: function(options) {
+ this.handlerOptions = OpenLayers.Util.extend(
+ {}, this.defaultHandlerOptions
+ );
+ OpenLayers.Control.prototype.initialize.apply(
+ this, arguments
+ );
+ this.handler = new OpenLayers.Handler.Hover(
+ this,
+ {'pause': this.onPause, 'move': this.onMove},
+ this.handlerOptions
+ );
+ },
+
+ onPause: function(evt) {
+ var output = document.getElementById(this.key + 'Output');
+ var msg = 'pause ' + evt.xy;
+ output.value = output.value + msg + "\r\n";
+ },
+
+ onMove: function(evt) {
+ // if this control sent an Ajax request (e.g. GetFeatureInfo) when
+ // the mouse pauses the onMove callback could be used to abort that
+ // request.
+ }
+ });
+
+ var map, controls;
+
+ function init(){
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS(
+ 'OpenLayers WMS',
+ 'http://vmap0.tiles.osgeo.org/wms/vmap0',
+ {layers: 'basic'}
+ );
+ map.addLayers([layer]);
+
+ controls = {
+ 'long': new OpenLayers.Control.Hover({
+ handlerOptions: {
+ 'delay': 2000
+ }
+ }),
+ 'short': new OpenLayers.Control.Hover({
+ handlerOptions: {
+ 'delay': 100
+ }
+ }),
+ 'tolerant': new OpenLayers.Control.Hover({
+ handlerOptions: {
+ 'delay': 1000,
+ 'pixelTolerance': 6
+ }
+ }),
+ 'untolerant': new OpenLayers.Control.Hover({
+ handlerOptions: {
+ 'delay': 1000,
+ 'pixelTolerance': 1
+ }
+ }),
+ 'stoppropag': new OpenLayers.Control.Hover({
+ handlerOptions: {
+ 'stopMove': true
+ }
+ })
+ };
+
+ var props = document.getElementById("props");
+ var control;
+ for(var key in controls) {
+ control = controls[key];
+ // only to route output here
+ control.key = key;
+ map.addControl(control);
+ }
+
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.zoomToMaxExtent();
+ }
+
+ function toggle(key) {
+ var control = controls[key];
+ if(control.active) {
+ control.deactivate();
+ } else {
+ control.activate();
+ }
+ var status = document.getElementById(key + "Status");
+ status.innerHTML = control.active ? "on" : "off";
+ var output = document.getElementById(key + "Output");
+ output.value = "";
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Hover Handler Example</h1>
+ <div id="west">
+
+ <div id="tags">
+ hover, onmouseover, handler, listener, event, events
+ </div>
+
+ <p id="shortdesc">
+ This example shows the use of the hover handler.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <p>
+ The hover handler is to be used to emulate mouseovers on
+ objects on the map that aren't DOM elements. For example
+ one can use the hover hander to send WMS/GetFeatureInfo
+ requests as the user moves the mouse over the map.
+ </p>
+ <p>
+ The "delay" option specifies the number of milliseconds
+ before the event is considered a hover. Default is 500
+ milliseconds.
+ </p>
+ <p>
+ The "pixelTolerance" option specifies the maximum number
+ of pixels between mousemoves for an event to be
+ considered a hover. Default is null, which means no
+ pixel tolerance.
+ </p>
+ <p>
+ The "stopMove" option specifies whether other mousemove
+ listeners registered before the hover handler listener must
+ be notified on mousemoves or not. Default is false (meaning
+ that the other mousemove listeners will be notified on
+ mousemove).
+ </p>
+ </div>
+ <div id="east">
+ <table>
+ <caption>Controls with hover handlers (toggle on/off to clear output)</caption>
+ <tbody>
+ <tr>
+ <td>long delay (2 sec)</td>
+ <td><button id="longStatus" onclick="toggle('long')">off</button></td>
+ <td><textarea class="output" id="longOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>short delay (100 msec)</td>
+ <td><button id="shortStatus" onclick="toggle('short')">off</button></td>
+ <td><textarea class="output" id="shortOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>tolerant (6 pixels)</td>
+ <td><button id="tolerantStatus" onclick="toggle('tolerant')">off</button></td>
+ <td><textarea class="output" id="tolerantOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>untolerant (1 pixel)</td>
+ <td><button id="untolerantStatus" onclick="toggle('untolerant')">off</button></td>
+ <td><textarea class="output" id="untolerantOutput"></textarea></td>
+ </tr>
+ <tr>
+ <td>stop propagation</td>
+ <td><button id="stoppropagStatus" onclick="toggle('stoppropag')">off</button></td>
+ <td><textarea class="output" id="stoppropagOutput"></textarea></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/image-layer.html b/misc/openlayers/examples/image-layer.html
new file mode 100644
index 0000000..235f9fc
--- /dev/null
+++ b/misc/openlayers/examples/image-layer.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Image Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ p.caption {
+ width: 512px;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var graphic = new OpenLayers.Layer.Image(
+ 'City Lights',
+ 'data/4_m_citylights_lg.gif',
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288),
+ {numZoomLevels: 3}
+ );
+
+ graphic.events.on({
+ loadstart: function() {
+ OpenLayers.Console.log("loadstart");
+ },
+ loadend: function() {
+ OpenLayers.Console.log("loadend");
+ }
+ });
+
+ var jpl_wms = new OpenLayers.Layer.WMS(
+ "Global Imagery",
+ "http://demo.opengeo.org/geoserver/wms",
+ {layers: "bluemarble"},
+ {maxExtent: [-160, -88.759, 160, 88.759], numZoomLevels: 3}
+ );
+
+ map.addLayers([graphic, jpl_wms]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Image Layer Example</h1>
+
+ <div id="tags">
+ image, imagelayer
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate a single non-tiled image as a selectable base layer.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p class="caption">
+ The "City Lights" layer above is created from a single web accessible
+ image. If you construct it without any resolution related options,
+ the layer will be given a single resolution based on the extent/size.
+ Otherwise, it behaves much like a regular layer. This is primarily
+ intended to be used in an overview map - where another layer type
+ might not make a good overview.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/img/check-round-green.png b/misc/openlayers/examples/img/check-round-green.png
new file mode 100644
index 0000000..176fed1
--- /dev/null
+++ b/misc/openlayers/examples/img/check-round-green.png
Binary files differ
diff --git a/misc/openlayers/examples/img/check-round-grey.png b/misc/openlayers/examples/img/check-round-grey.png
new file mode 100644
index 0000000..dc90efb
--- /dev/null
+++ b/misc/openlayers/examples/img/check-round-grey.png
Binary files differ
diff --git a/misc/openlayers/examples/img/list.png b/misc/openlayers/examples/img/list.png
new file mode 100644
index 0000000..f214206
--- /dev/null
+++ b/misc/openlayers/examples/img/list.png
Binary files differ
diff --git a/misc/openlayers/examples/img/locate.png b/misc/openlayers/examples/img/locate.png
new file mode 100644
index 0000000..c61b499
--- /dev/null
+++ b/misc/openlayers/examples/img/locate.png
Binary files differ
diff --git a/misc/openlayers/examples/img/marker_shadow.png b/misc/openlayers/examples/img/marker_shadow.png
new file mode 100644
index 0000000..a5afa6e
--- /dev/null
+++ b/misc/openlayers/examples/img/marker_shadow.png
Binary files differ
diff --git a/misc/openlayers/examples/img/minus1.png b/misc/openlayers/examples/img/minus1.png
new file mode 100644
index 0000000..df446c7
--- /dev/null
+++ b/misc/openlayers/examples/img/minus1.png
Binary files differ
diff --git a/misc/openlayers/examples/img/mobile-layers.png b/misc/openlayers/examples/img/mobile-layers.png
new file mode 100644
index 0000000..c4a6335
--- /dev/null
+++ b/misc/openlayers/examples/img/mobile-layers.png
Binary files differ
diff --git a/misc/openlayers/examples/img/mobile-loc.png b/misc/openlayers/examples/img/mobile-loc.png
new file mode 100644
index 0000000..c2d89a7
--- /dev/null
+++ b/misc/openlayers/examples/img/mobile-loc.png
Binary files differ
diff --git a/misc/openlayers/examples/img/mobile-zoombar.png b/misc/openlayers/examples/img/mobile-zoombar.png
new file mode 100644
index 0000000..ff8e049
--- /dev/null
+++ b/misc/openlayers/examples/img/mobile-zoombar.png
Binary files differ
diff --git a/misc/openlayers/examples/img/openlayers.png b/misc/openlayers/examples/img/openlayers.png
new file mode 100644
index 0000000..f7800fe
--- /dev/null
+++ b/misc/openlayers/examples/img/openlayers.png
Binary files differ
diff --git a/misc/openlayers/examples/img/popupMatrix.jpg b/misc/openlayers/examples/img/popupMatrix.jpg
new file mode 100644
index 0000000..0f67368
--- /dev/null
+++ b/misc/openlayers/examples/img/popupMatrix.jpg
Binary files differ
diff --git a/misc/openlayers/examples/img/small.jpg b/misc/openlayers/examples/img/small.jpg
new file mode 100644
index 0000000..1ba22e6
--- /dev/null
+++ b/misc/openlayers/examples/img/small.jpg
Binary files differ
diff --git a/misc/openlayers/examples/img/thinlong.jpg b/misc/openlayers/examples/img/thinlong.jpg
new file mode 100644
index 0000000..a063ab4
--- /dev/null
+++ b/misc/openlayers/examples/img/thinlong.jpg
Binary files differ
diff --git a/misc/openlayers/examples/img/widelong.jpg b/misc/openlayers/examples/img/widelong.jpg
new file mode 100644
index 0000000..7ed1c5e
--- /dev/null
+++ b/misc/openlayers/examples/img/widelong.jpg
Binary files differ
diff --git a/misc/openlayers/examples/img/wideshort.jpg b/misc/openlayers/examples/img/wideshort.jpg
new file mode 100644
index 0000000..9839b82
--- /dev/null
+++ b/misc/openlayers/examples/img/wideshort.jpg
Binary files differ
diff --git a/misc/openlayers/examples/intersects.html b/misc/openlayers/examples/intersects.html
new file mode 100644
index 0000000..31bdee0
--- /dev/null
+++ b/misc/openlayers/examples/intersects.html
@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Geometry Intersections</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css">
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ html, body {
+ margin: 0;
+ padding: 1em;
+ font: 0.9em Verdana, Arial, sans serif;
+ }
+ input, select, textarea {
+ font: 0.9em Verdana, Arial, sans-serif;
+ }
+ h2 {
+ margin-top: 0.75em;
+ font-size: 1.6em;
+ }
+ #leftcol {
+ position: absolute;
+ top: 0;
+ left: 1em;
+ padding: 0;
+ width: 455px;
+ }
+ #map {
+ width: 450px;
+ height: 225px;
+ border: 1px solid #ccc;
+ }
+ #input {
+ width: 450px;
+ }
+ #text {
+ font-size: 0.85em;
+ margin: 1em 0 1em 0;
+ width: 100%;
+ height: 10em;
+ }
+ #info {
+ position: relative;
+ padding: 2em 0;
+ margin-left: 470px;
+ }
+ #features {
+ font-size: 0.8em;
+ width: 100%;
+ height: 200px;
+ }
+ #intersections {
+ font-size: 0.8em;
+ width: 100%;
+ height: 200px;
+ }
+ p {
+ margin: 0;
+ padding: 0.75em 0 0.75em 0;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectors, geojson;
+ function init(){
+ map = new OpenLayers.Map('map');
+ vectors = new OpenLayers.Layer.Vector(
+ "Vector Layer",
+ {isBaseLayer: true}
+ );
+
+ map.addLayers([vectors]);
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ var panel = new OpenLayers.Control.EditingToolbar(vectors);
+ map.addControl(panel);
+
+ geojson = new OpenLayers.Format.GeoJSON();
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ }
+
+ function serialize() {
+ var str = geojson.write(vectors.features, true);
+ document.getElementById('features').value = str;
+ }
+
+ function deserialize() {
+ var element = document.getElementById('text');
+ var features = geojson.read(element.value);
+ var bounds;
+ if(features) {
+ if(features.constructor != Array) {
+ features = [features];
+ }
+ for(var i=0; i<features.length; ++i) {
+ if (!bounds) {
+ bounds = features[i].geometry.getBounds();
+ } else {
+ bounds.extend(features[i].geometry.getBounds());
+ }
+
+ }
+ vectors.addFeatures(features);
+ map.zoomToExtent(bounds);
+ var plural = (features.length > 1) ? 's' : '';
+ element.value = features.length + ' feature' + plural + ' added'
+ } else {
+ element.value = 'Bad input';
+ }
+ }
+
+ function intersect() {
+ var features = vectors.features;
+ var feat1, feat2, intersects12, intersects21;
+ var parts = [];
+ // reset attributes
+ for(var i=0; i<features.length; ++i) {
+ features[i].attributes.intersectsWith = [];
+ }
+ for(var i=0; i<features.length-1; ++i) {
+ feat1 = features[i];
+ for(var j=i+1; j<features.length; ++j) {
+ feat2 = features[j];
+ intersects12 = feat1.geometry.intersects(feat2.geometry);
+ if(intersects12) {
+ feat1.attributes.intersectsWith.push("f" + j);
+ parts.push("f" + i + " intersects f" + j + "\n");
+ }
+ intersects21 = feat2.geometry.intersects(feat1.geometry);
+ if(intersects21) {
+ feat2.attributes.intersectsWith.push("f" + i);
+ parts.push("f" + j + " intersects f" + i + "\n");
+ }
+ if(intersects12 != intersects21) {
+ parts.push("trouble with " + i + " and " + j + "\n");
+ }
+ }
+ }
+ if(parts.length > 0) {
+ document.getElementById("intersections").value = parts.join("");
+ } else {
+ document.getElementById("intersections").value = "no intersections";
+ }
+ }
+
+ // preload images
+ (function() {
+ var roots = ["draw_point", "draw_line", "draw_polygon", "pan"];
+ var onImages = [];
+ var offImages = [];
+ for(var i=0; i<roots.length; ++i) {
+ onImages[i] = new Image();
+ onImages[i].src = "../theme/default/img/" + roots[i] + "_on.png";
+ offImages[i] = new Image();
+ offImages[i].src = "../theme/default/img/" + roots[i] + "_on.png";
+ }
+ })();
+
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="leftcol">
+ <h1 id="title">OpenLayers Geometry Intersection Example</h1>
+ <div id="tags">
+ intersection, geometry
+ </div>
+ <p id="shortdesc">
+ Use of geometry.intersects method for testing geometry intersections.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="input">
+ <textarea id="text"></textarea>
+ <input type="button" value="add feature" onclick="deserialize();" />
+ <span id="selected"></span>
+ </div>
+ </div>
+ <div id="info">
+ <p>Features</p>
+ <input type="button" value="refresh" onclick="serialize();"><br>
+ <textarea id="features"></textarea>
+ <p>Intersections</p>
+ <input type="button" value="intersect all" onclick="intersect();"><br>
+ <textarea id="intersections"></textarea>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/kamap.html b/misc/openlayers/examples/kamap.html
new file mode 100644
index 0000000..9219375
--- /dev/null
+++ b/misc/openlayers/examples/kamap.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers KaMap Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var jpl_wms = new OpenLayers.Layer.KaMap( "Satellite",
+ "http://www.openlayers.org/world/index.php", {g: "satellite", map: "world"});
+ var dm_wms = new OpenLayers.Layer.WMS( "Canada",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap",
+ {layers: "bathymetry,land_fn,park,drain_fn,drainage," +
+ "prov_bound,fedlimit,rail,road,popplace",
+ transparent: "true", format: "image/png" });
+
+ map.addLayers([jpl_wms, dm_wms]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">KaMap Example</h1>
+
+ <div id="tags">
+ KaMap
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate a tiled kamap layer as the base map, which can be pre-cached for higher performance.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/kamap.txt b/misc/openlayers/examples/kamap.txt
new file mode 100644
index 0000000..50fa4e0
--- /dev/null
+++ b/misc/openlayers/examples/kamap.txt
@@ -0,0 +1,508 @@
+<?php
+/*
+
+This is a PHP file to be used as a backend for a ka-Map layer. It requires
+PHP with Mapscript and libgd modules installed. The top of the file
+is a configuration section: please edit the variables in this configuration
+section to meet your needs, then rename this file to tile.php or something
+similar and put it in a web accessible directory. More information
+on the OpenLayers ka-Map layer is available from:
+
+ http://trac.openlayers.org/wiki/OpenLayers.Layer.KaMap
+
+*/
+/**********************************************************************
+ *
+ * $Id: tile.php,v 1.33 2006/02/07 03:19:55 pspencer Exp $
+ *
+ * purpose: a simple phpmapscript-based tile renderer that implements
+ * rudimentary caching for reasonable efficiency. Note the
+ * cache never shrinks in this version so your disk could
+ * easily fill up!
+ *
+ * author: Paul Spencer (pspencer@dmsolutions.ca)
+ *
+ * modifications by Daniel Morissette (dmorissette@dmsolutions.ca)
+ *
+ * Modified by Christopher Schmidt for OpenLayers redistribution.
+ *
+ **********************************************************************
+ *
+ * Copyright (c) 2005, DM Solutions Group Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ **********************************************************************/
+
+
+/******************************************************************************
+ * basic system configuration
+ *
+ * kaMap! uses PHP/MapScript and the PHP GD extension to
+ * render tiles, and uses PHP/MapScript to generate initialization parameters
+ * a legend, and a keymap from the selected map file.
+ *
+ * Make sure to set the correct module names for your PHP extensions.
+ *
+ * WINDOWS USERS: you will likely need to use php_gd2.dll instead of php_gd.dll
+ */
+$szPHPMapScriptModule = 'php_mapscript.'.PHP_SHLIB_SUFFIX;
+$szPHPGDModule = 'php_gd.'.PHP_SHLIB_SUFFIX;
+
+/******************************************************************************
+ * tile generation parameters
+ *
+ * kaMap! generates tiles to load in the client application by first rendering
+ * larger areas from the map file and then slicing them up into smaller tiles.
+ * This approach reduces the overhead of loading PHP/MapScript and PHP GD and
+ * drawing the map file. These larger areas are referred to as metaTiles in
+ * the code. You can set the size of both the small tiles and the metaTiles
+ * here. A reasonable size for the small tiles seems to be 200 pixels square.
+ * Smaller tiles seem to cause problems in client browsers by causing too many
+ * images to be created and thus slowing performance of live dragging. Larger
+ * tiles take longer to download to the client and are inefficient.
+ *
+ * The number of smaller tiles that form a metaTile can also be configured.
+ * This parameter allows tuning of the tile generator to ensure optimal
+ * performance and for label placement. MapServer will produce labels only
+ * within a rendered area. If the area is too small then features may be
+ * labelled multiple times. If the area is too large, it may exceed MapServer,s
+ * maximum map size (by default 2000x2000) or be too resource-intensive on the
+ * server, ultimately reducing performance.
+ */
+$tileWidth = 256;
+$tileHeight = 256;
+$metaWidth = 5;
+$metaHeight = 5;
+/* $metaBuffer = Buffer size in pixels to add around metatiles to avoid
+ * rendering issues along the edge of the map image
+ */
+$metaBuffer = 10;
+
+/******************************************************************************
+ * in-image debugging information - tile location, outlines etc.
+ * to use this, you need to remove images from your cache first. This also
+ * affects the meta tiles - if debug is on, they are not deleted.
+ */
+$bDebug = false;
+
+/******************************************************************************
+ * aszMapFiles - an array of map files available to the application. How this
+ * is used is determined by the application. Each map file is entered into
+ * this array as a key->value pair.
+ *
+ * The key is the name to be used by the tile caching system to store cached
+ * tiles within the base cache directory. This key should be a single word
+ * that uniquely identifies the map.
+ *
+ * The value associated with each key is an array of three values. The first
+ * value is a human-readable name to be presented to the user (should the
+ * application choose to do so) and the second value is the path to the map
+ * file. It is assumed that the map file is fully configured for use with
+ * MapServer/MapScript as no error checking or setting of values is done. The
+ * third value is an array of scale values for zooming.
+ */
+
+$aszMapFiles = array(
+ "world" => array( "World", "/path/to/your/mapfile",
+ array( 10000 ), # in openlayers, the scale array doesn't matter.
+ "PNG24")
+
+/* Add more elements to this array to offer multiple mapfiles */
+
+);
+
+/******************************************************************************
+ * figure out which map file to use and set up the necessary variables for
+ * the rest of the code to use. This does need to be done on every page load
+ * unfortunately.
+ *
+ * szMap should be set to the default map file to use but can change if
+ * this script is called with map=<mapname>.
+ */
+$szMap = 'world';
+
+/******************************************************************************
+ * kaMap! caching
+ *
+ * this is the directory within which kaMap! will create its tile cache. The
+ * directory does NOT have to be web-accessible, but it must be writable by the
+ * web-server-user and allow creation of both directories AND files.
+ *
+ * the tile caching system will create a separate subdirectory within the base
+ * cache directory for each map file. Within the cache directory for each map
+ * file, directories will be created for each group of layers. Within the group
+ * directories, directories will be created at each of the configured scales
+ * for the application (see mapfile configuration above.)
+ */
+$szBaseCacheDir = "/var/cache/kamap/";
+
+/***** END OF CONFIGURABLE STUFF - unless you know what you are doing *****/
+/***** *****/
+/***** *****/
+/***** *****/
+/***** END OF CONFIGURABLE STUFF - unless you know what you are doing *****/
+
+if (isset($_REQUEST['map']) && isset($aszMapFiles[$_REQUEST['map']]))
+{
+ $szMap = $_REQUEST['map'];
+}
+
+$szMapCacheDir = $szBaseCacheDir.$szMap."/";
+$szMapName = $aszMapFiles[$szMap][0];
+$szMapFile = $aszMapFiles[$szMap][1];
+$anScales = $aszMapFiles[$szMap][2];
+setOutputFormat($aszMapFiles[$szMap][3]);
+/******************************************************************************
+ * output format of the map and resulting tiles
+ *
+ * The output format used with MapServer can greatly affect appearance and
+ * performance. It is recommended to use an 8 bit format such as PNG
+ *
+ * NOTE: the tile caching code in tile.php is not configurable here. It
+ * currently assumes that it is outputting 8bit PNG files. If you change to
+ * PNG24 here then you will need to update tile.php to use the gd function
+ * imagecreatetruecolor. If you change the output format to jpeg then
+ * you would need to change imagepng() to imagejpeg(). A nice enhancement
+ * would be to make that fully configurable from here.
+ */
+function setOutputFormat($szFormat)
+{
+ switch($szFormat) {
+ case "PNG24":
+ $GLOBALS['szMapImageFormat'] = 'PNG24'; //mapscript format name
+ $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; // appropriate GD function
+ $GLOBALS['szImageExtension'] = '.png'; //file extension
+ $GLOBALS['szImageCreateFunction'] = "imagecreatetruecolor"; //or imagecreatetruecolor if PNG24 ...
+ $GLOBALS['szImageOutputFunction'] = "imagepng"; //or imagegif, imagejpeg ...
+ $GLOBALS['szImageHeader'] = 'image/png'; //the content-type of the image
+ break;
+ case "GIF":
+ $GLOBALS['szMapImageFormat'] = 'GIF'; //mapscript format name
+ $GLOBALS['szMapImageCreateFunction'] = "imagecreatefromgif"; // appropriate GD function
+ $GLOBALS['szImageExtension'] = '.gif'; //file extension
+ $GLOBALS['szImageCreateFunction'] = "imagecreate"; //or imagecreatetruecolor if PNG24 ...
+ $GLOBALS['szImageOutputFunction'] = "imagegif"; //or imagegif, imagejpeg ...
+ $GLOBALS['szImageHeader'] = 'image/gif'; //the content-type of the image
+ break;
+ case "JPEG":
+ $GLOBALS['szMapImageFormat'] = 'JPEG'; //mapscript format name
+ $GLOBALS['szMapImageCreateFunction'] = "imagecreatefromjpeg"; // appropriate GD function
+ $GLOBALS['szImageExtension'] = '.jpg'; //file extension
+ $GLOBALS['szImageCreateFunction'] = "imagecreatetruecolor"; //or imagecreatetruecolor if PNG24 ...
+ $GLOBALS['szImageOutputFunction'] = "imagejpeg"; //or imagegif, imagejpeg ...
+ $GLOBALS['szImageHeader'] = 'image/jpeg'; //the content-type of the image
+ break;
+ case "PNG":
+ $GLOBALS['szMapImageFormat'] = 'PNG'; //mapscript format name
+ $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; // appropriate GD function
+ $GLOBALS['szImageExtension'] = '.png'; //file extension
+ $GLOBALS['szImageCreateFunction'] = "imagecreate"; //or imagecreatetruecolor if PNG24 ...
+ $GLOBALS['szImageOutputFunction'] = "imagepng"; //or imagegif, imagejpeg ...
+ $GLOBALS['szImageHeader'] = 'image/png'; //the content-type of the image
+ break;
+ case "DITHERED":
+ case "PNG8":
+ $GLOBALS['szMapImageFormat'] = 'dithered';
+ $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng";
+ $GLOBALS['szImageExtension'] = '.png';
+ $GLOBALS['szImageCreateFunction'] = "imagecreate";
+ $GLOBALS['szImageOutputFunction'] = "imagepng";
+ $GLOBALS['szImageHeader'] = 'image/png';
+ break;
+ }
+}
+
+/**
+ * create all directories in a directory tree - found on the php web site
+ * under the mkdir function ...
+ */
+function makeDirs($strPath, $mode = 0775)
+{
+ return is_dir($strPath) or ( makeDirs(dirname($strPath), $mode) and mkdir($strPath, $mode) );
+}
+
+/**
+ * This function replaces all special characters in the given string.
+ *
+ * @param szString string - The string to convert.
+ *
+ * @return string converted
+ */
+function normalizeString($szString)
+{
+ // Normalize string by replacing all special characters
+ // e.g. "http://my.host.com/cgi-bin/mywms?"
+ // becomes "http___my_host_com_cgi_bin_mywms_"
+ return preg_replace("/(\W)/", "_", $szString);
+}
+
+/* bug 1253 - root permissions required to delete cached files */
+$orig_umask = umask(0);
+
+/* create the main cache directory if necessary */
+if (!@is_dir($szMapCacheDir))
+ makeDirs($szMapCacheDir);
+
+/* get the various request parameters
+ * also need to make sure inputs are clean, especially those used to
+ * build paths and filenames
+ */
+ /*
+ * the tile renderer accepts several parameters and returns a tile image from
+ * the cache, creating the tile only if necessary.
+ *
+ * all requests include the pixel location of the request at a certain scale
+ * and this script figures out the geographic location of the tile from the
+ * scale assuming that 0,0 in pixels is 0,0 in geographic units
+ *
+ * Request parameters are:
+ *
+ * map: the name of the map to use. This is handled by config.php.
+ *
+ * t: top pixel position
+ * l: left pixel position
+ * s: scale
+ * g: (optional) comma-delimited list of group names to draw
+ * layers: (optional) comma-delimited list of layers to draw
+ * force: optional. If set, force redraw of the meta tile. This was added to
+ * help with invalid images sometimes being generated.
+ * tileid: (optional) can be used instead of t+l to specify the tile coord.,
+ * useful in regenerating the cache
+ */
+
+$top = isset( $_REQUEST['t'] ) ? intval($_REQUEST['t']) : 0;
+$left = isset( $_REQUEST['l'] ) ? intval($_REQUEST['l']) : 0;
+$scale = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : $anScales[0];
+$bForce = isset($_REQUEST['force'])? true : false;
+$groups = isset( $_REQUEST['g'] ) ? $_REQUEST['g'] : "";
+$layers = isset( $_REQUEST['layers'] ) ? $_REQUEST['layers'] : "";
+
+// dynamic imageformat ----------------------------------------------
+//use the function in config.php to set the output format
+if (isset($_REQUEST['i']))
+ setOutputFormat( $_REQUEST['i'] );
+//----------------------------------------------------------------
+
+/* tileid=t#####l#### can be used instead of t+l parameters. Useful in
+ * regenerating the cache for instance.
+ */
+if (isset( $_REQUEST['tileid']) &&
+ preg_match("/t(-?\d+)l(-?\d+)/", $_REQUEST['tileid'], $aMatch) )
+{
+ $top = intval($aMatch[1]);
+ $left = intval($aMatch[2]);
+}
+
+/* Calculate the metatile's top-left corner coordinates.
+ * Include the $metaBuffer around the metatile to account for various
+ * rendering issues happening around the edge of a map
+ */
+$metaLeft = floor( ($left)/($tileWidth*$metaWidth) ) * $tileWidth * $metaWidth;
+$metaTop = floor( ($top)/($tileHeight*$metaHeight) ) * $tileHeight *$metaHeight;
+$szMetaTileId = "t".$metaTop."l".$metaLeft;
+$metaLeft -= $metaBuffer;
+$metaTop -= $metaBuffer;
+
+/* caching is done by scale value, then groups and layers and finally metatile
+ * and tile id. Create a new directory if necessary
+ */
+$szGroupDir = $groups != "" ? normalizeString($groups) : "def";
+$szLayerDir = $layers != "" ? normalizeString($layers) : "def";
+
+$szCacheDir = $szMapCacheDir."/".$scale."/".$szGroupDir."/".$szLayerDir."/".$szMetaTileId;
+if (!@is_dir($szCacheDir))
+ makeDirs($szCacheDir);
+
+/* resolve cache hit - clear the os stat cache if necessary */
+$szTileId = "t".$top."l".$left;
+$szCacheFile = $szCacheDir."/".$szTileId.$szImageExtension;
+clearstatcache();
+
+$szMetaDir = $szCacheDir."/meta";
+if (!@is_Dir($szMetaDir))
+ makeDirs($szMetaDir);
+
+/* simple locking in case there are several requests for the same meta
+ tile at the same time - only draw it once to help with performance */
+$szLockFile = $szMetaDir."/lock_".$metaTop."_".$metaLeft;
+$fpLockFile = fopen($szLockFile, "a+");
+clearstatcache();
+if (!file_exists($szCacheFile) || $bForce)
+{
+ flock($fpLockFile, LOCK_EX);
+ fwrite($fpLockFile, ".");
+
+ //check once more to see if the cache file was created while waiting for
+ //the lock
+ clearstatcache();
+ if (!file_exists($szCacheFile) || $bForce)
+ {
+ if (!extension_loaded('MapScript'))
+ {
+ dl( $szPHPMapScriptModule );
+ }
+ if (!extension_loaded('gd'))
+ {
+ dl( $szPHPGDModule);
+ }
+
+ if (!@is_Dir($szMetaDir))
+ makeDirs($szMetaDir);
+
+ $oMap = ms_newMapObj($szMapFile);
+
+ /* Metatile width/height include 2x the metaBuffer value */
+ $oMap->set('width', $tileWidth * $metaWidth + 2*$metaBuffer);
+ $oMap->set('height', $tileHeight * $metaHeight + 2*$metaBuffer);
+
+ /* Tell MapServer to not render labels inside the metaBuffer area
+ * (new in 4.6)
+ * TODO: Until MapServer bugs 1353/1355 are resolved, we need to
+ * pass a negative value for "labelcache_map_edge_buffer"
+ */
+ $oMap->setMetadata("labelcache_map_edge_buffer", -$metaBuffer);
+
+ $inchesPerUnit = array(1, 12, 63360.0, 39.3701, 39370.1, 4374754);
+ $geoWidth = $scale/($oMap->resolution*$inchesPerUnit[$oMap->units]);
+ $geoHeight = $scale/($oMap->resolution*$inchesPerUnit[$oMap->units]);
+
+ /* draw the metatile */
+ $minx = $metaLeft * $geoWidth;
+ $maxx = $minx + $geoWidth * $oMap->width;
+ $maxy = -1 * $metaTop * $geoHeight;
+ $miny = $maxy - $geoHeight * $oMap->height;
+
+ $nLayers = $oMap->numlayers;
+ $oMap->setExtent($minx,$miny,$maxx,$maxy);
+ $oMap->selectOutputFormat( $szMapImageFormat );
+ $aszLayers = array();
+ if ($groups || $layers)
+ {
+ /* Draw only specified layers instead of default from mapfile*/
+ if ($layers)
+ {
+ $aszLayers = explode(",", $layers);
+ }
+
+ if ($groups)
+ {
+ $aszGroups = explode(",", $groups);
+ }
+
+ for($i=0;$i<$nLayers;$i++)
+ {
+ $oLayer = $oMap->getLayer($i);
+ if (($aszGroups && in_array($oLayer->group,$aszGroups)) ||
+ ($aszLayers && in_array($oLayer->name,$aszLayers)) ||
+ ($aszGroups && $oLayer->group == '' &&
+ in_array( "__base__", $aszGroups)))
+ {
+ $oLayer->set("status", MS_ON );
+ }
+ else
+ {
+ $oLayer->set("status", MS_OFF );
+ }
+ }
+ //need transparency if groups or layers are used
+ $oMap->outputformat->set("transparent", MS_ON );
+ }
+ else
+ {
+ $oMap->outputformat->set("transparent", MS_OFF );
+ }
+
+
+ $szMetaImg = $szMetaDir."/t".$metaTop."l".$metaLeft.$szImageExtension;
+ $oImg = $oMap->draw();
+ $oImg->saveImage($szMetaImg);
+ $oImg->free();
+ eval("\$oGDImg = ".$szMapImageCreateFunction."('".$szMetaImg."');");
+ if ($bDebug)
+ {
+ $blue = imagecolorallocate($oGDImg, 0, 0, 255);
+ imagerectangle($oGDImg, 0, 0, $tileWidth * $metaWidth - 1, $tileHeight * $metaHeight - 1, $blue );
+ }
+ for($i=0;$i<$metaWidth;$i++)
+ {
+ for ($j=0;$j<$metaHeight;$j++)
+ {
+ eval("\$oTile = ".$szImageCreateFunction."( ".$tileWidth.",".$tileHeight." );");
+ // Allocate BG color for the tile (in case the metatile has transparent BG)
+ $nTransparent = imagecolorallocate($oTile, $oMap->imagecolor->red, $oMap->imagecolor->green, $oMap->imagecolor->blue);
+ //if ($oMap->outputformat->transparent == MS_ON)
+ //{
+ imagecolortransparent( $oTile,$nTransparent);
+ //}
+ $tileTop = $j*$tileHeight + $metaBuffer;
+ $tileLeft = $i*$tileWidth + $metaBuffer;
+ imagecopy( $oTile, $oGDImg, 0, 0, $tileLeft, $tileTop, $tileWidth, $tileHeight );
+ /* debugging stuff */
+ if ($bDebug)
+ {
+ $black = imagecolorallocate($oTile, 1, 1, 1);
+ $green = imagecolorallocate($oTile, 0, 128, 0 );
+ $red = imagecolorallocate($oTile, 255, 0, 0);
+ imagerectangle( $oTile, 1, 1, $tileWidth-2, $tileHeight-2, $green );
+ imageline( $oTile, 0, $tileHeight/2, $tileWidth-1, $tileHeight/2, $red);
+ imageline( $oTile, $tileWidth/2, 0, $tileWidth/2, $tileHeight-1, $red);
+ imagestring ( $oTile, 3, 10, 10, ($metaLeft+$tileLeft)." x ".($metaTop+$tileTop), $black );
+ imagestring ( $oTile, 3, 10, 30, ($minx+$i*$geoWidth)." x ".($maxy - $j*$geoHeight), $black );
+ }
+ $szTileImg = $szCacheDir."/t".($metaTop+$tileTop)."l".($metaLeft+$tileLeft).$szImageExtension;
+ eval("$szImageOutputFunction( \$oTile, '".$szTileImg."' );");
+ imagedestroy($oTile);
+ $oTile = null;
+ }
+ }
+ if ($oGDImg != null)
+ {
+ imagedestroy($oGDImg);
+ $oGDImg = null;
+ }
+ if (!$bDebug)
+ {
+ unlink( $szMetaImg );
+ }
+ }
+ //release the exclusive lock
+ flock($fpLockFile, LOCK_UN );
+}
+
+//acquire shared lock for reading to prevent a problem that could occur
+//if a tile exists but is only partially generated.
+flock($fpLockFile, LOCK_SH);
+
+$h = fopen($szCacheFile, "r");
+header("Content-Type: ".$szImageHeader);
+header("Content-Length: " . filesize($szCacheFile));
+header("Expires: " . date( "D, d M Y H:i:s GMT", time() + 31536000 ));
+header("Cache-Control: max-age=31536000, must-revalidate" );
+fpassthru($h);
+fclose($h);
+
+//release lock
+fclose($fpLockFile);
+
+/* bug 1253 - root permissions required to delete cached files */
+umask($orig_umask);
+
+exit;
+?>
diff --git a/misc/openlayers/examples/kinetic.html b/misc/openlayers/examples/kinetic.html
new file mode 100644
index 0000000..19919ae
--- /dev/null
+++ b/misc/openlayers/examples/kinetic.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Kinetic Dragging Example</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">Kinetic Dragging Example</h1>
+
+ <div id="tags">
+ kinetic, dragging
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates Kinetic Dragging.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ OpenLayers Kinetic Dragging inspired from <a href="http://www.tile5.org">Tile5</a>, and
+ <a href="http://code.google.com/p/kineticscrolling/">kineticscrolling</a> for Google Maps API V3.
+ </p><p>
+ As shown in this example Kinetic Dragging is enabled by setting
+ <code>enableKinetic</code> to true in the config object provided to the
+ <code>Control.DragPan</code> constructor. When using
+ <code>Control.Navigation</code> or <code>Control.TouchNavigation</code>
+ providing options to the underlying <code>Control.DragPan</code>
+ instance is done through the <code>dragPanOptions</code> config
+ property.
+ </p><p>
+ View the <a href="kinetic.js" target="_blank">kinetic.js source</a>
+ to see how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="kinetic.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/kinetic.js b/misc/openlayers/examples/kinetic.js
new file mode 100644
index 0000000..2daca16
--- /dev/null
+++ b/misc/openlayers/examples/kinetic.js
@@ -0,0 +1,27 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ resolutions: [0.087890625, 0.0439453125, 0.02197265625, 0.010986328125],
+ controls: [
+ new OpenLayers.Control.Navigation(
+ {dragPanOptions: {enableKinetic: true}}
+ )
+ ]
+});
+var layer = new OpenLayers.Layer.TileCache("TileCache Layer",
+ ["http://c0.tilecache.osgeo.org/wms-c/cache/",
+ "http://c1.tilecache.osgeo.org/wms-c/cache/",
+ "http://c2.tilecache.osgeo.org/wms-c/cache/",
+ "http://c3.tilecache.osgeo.org/wms-c/cache/",
+ "http://c4.tilecache.osgeo.org/wms-c/cache/"],
+ "basic",
+ {
+ serverResolutions: [0.703125, 0.3515625, 0.17578125, 0.087890625,
+ 0.0439453125, 0.02197265625, 0.010986328125,
+ 0.0054931640625, 0.00274658203125, 0.001373291015625,
+ 0.0006866455078125, 0.00034332275390625, 0.000171661376953125,
+ 0.0000858306884765625, 0.00004291534423828125, 0.000021457672119140625],
+ buffer: 4
+ }
+);
+map.addLayer(layer);
+map.setCenter(new OpenLayers.LonLat(0, 0), 0); \ No newline at end of file
diff --git a/misc/openlayers/examples/kml-layer.html b/misc/openlayers/examples/kml-layer.html
new file mode 100644
index 0000000..5ae07da
--- /dev/null
+++ b/misc/openlayers/examples/kml-layer.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">KML Layer Example</h1>
+
+ <div id="tags">KML</div>
+
+ <p id="shortdesc">
+ Demonstrates loading and displaying a KML file on top of a basemap.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ A vector layer can be populated with features from a KML document
+ by configuring the layer with an HTTP protocol that points to the
+ KML document and is configured with a KML format for parsing features.
+ The fixed strategy is used to load all features at once.
+ </p>
+ <p>
+ View the <a href="kml-layer.js" target="_blank">kml-layer.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="kml-layer.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/kml-layer.js b/misc/openlayers/examples/kml-layer.js
new file mode 100644
index 0000000..1b0e85e
--- /dev/null
+++ b/misc/openlayers/examples/kml-layer.js
@@ -0,0 +1,22 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ ),
+ new OpenLayers.Layer.Vector("KML", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml/lines.kml",
+ format: new OpenLayers.Format.KML({
+ extractStyles: true,
+ extractAttributes: true,
+ maxDepth: 2
+ })
+ })
+ })
+ ],
+ center: new OpenLayers.LonLat(-112.169, 36.099),
+ zoom: 11
+});
diff --git a/misc/openlayers/examples/kml-pointtrack.html b/misc/openlayers/examples/kml-pointtrack.html
new file mode 100644
index 0000000..a3bad5a
--- /dev/null
+++ b/misc/openlayers/examples/kml-pointtrack.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers KML Track in a PointTrack Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ .olControlAttribution {
+ bottom: 2px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="kml-pointtrack.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Parsing gx:Track in KML</h1>
+ <p id="shortdesc">
+ Demonstrates populating a PointTrack layer with gx:Track elements from KML.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ If a KML document contains <code>&lt;gx:Track&gt;</code>
+ elements and the extractTracks property is set true on the
+ parser, features will be created that represent track points.
+ These track points can easily be visualized as track lines with
+ a <code>PointTrack</code> layer, preserving the KML's original
+ styles.
+ </p>
+ <p>
+ View the <a href="kml-pointtrack.js" target="_blank">kml-pointtrack.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/kml-pointtrack.js b/misc/openlayers/examples/kml-pointtrack.js
new file mode 100644
index 0000000..7d48ce3
--- /dev/null
+++ b/misc/openlayers/examples/kml-pointtrack.js
@@ -0,0 +1,52 @@
+var map;
+
+function init() {
+
+ var mercator = new OpenLayers.Projection("EPSG:900913");
+ var geographic = new OpenLayers.Projection("EPSG:4326");
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: mercator,
+ layers: [
+ new OpenLayers.Layer.OSM(),
+ new OpenLayers.Layer.PointTrack("Aircraft Tracks", {
+ projection: geographic,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml-track.kml",
+ format: new OpenLayers.Format.KML({
+ extractTracks: true,
+ extractStyles: true
+ })
+ }),
+ dataFrom: OpenLayers.Layer.PointTrack.TARGET_NODE,
+ styleFrom: OpenLayers.Layer.PointTrack.TARGET_NODE,
+ eventListeners: {
+ "beforefeaturesadded": function(e) {
+ // group the tracks by fid and create one track for
+ // every fid
+ var fid, points = [], feature;
+ for (var i=0, len=e.features.length; i<len; i++) {
+ feature = e.features[i];
+ if ((fid && feature.fid !== fid) || i === len-1) {
+ this.addNodes(points, {silent: true});
+ points = [];
+ } else {
+ points.push(feature);
+ }
+ fid = feature.fid;
+ }
+ return false;
+ }
+ }
+ })
+ ],
+ center: new OpenLayers.LonLat(-93.2735, 44.8349).transform(geographic, mercator),
+ zoom: 8
+ });
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+};
+
diff --git a/misc/openlayers/examples/kml-track.html b/misc/openlayers/examples/kml-track.html
new file mode 100644
index 0000000..0f0032d
--- /dev/null
+++ b/misc/openlayers/examples/kml-track.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers KLM Track Parsing Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ .olControlAttribution {
+ bottom: 2px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="kml-track.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Parsing gx:Track in KML</h1>
+ <p id="shortdesc">
+ Demonstrates parsing of gx:Track elements from KML.
+ </p>
+ <div id="tags">
+ KML, parser, parsing, tracks
+ </div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ If a KML document contains <code>&lt;gx:Track&gt;</code>
+ elements and the extractTracks property is set true on the
+ parer, features will be created that represent track points.
+ Each feature will have a when attribute that contains the
+ value of the relevant <code>&lt;when&gt;</code> element from
+ the track.
+ </p>
+ <p>
+ View the <a href="kml-track.js" target="_blank">kml-track.js</a>
+ source to see how this is done.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/kml-track.js b/misc/openlayers/examples/kml-track.js
new file mode 100644
index 0000000..1c6a809
--- /dev/null
+++ b/misc/openlayers/examples/kml-track.js
@@ -0,0 +1,40 @@
+var map;
+
+function init() {
+
+ var mercator = new OpenLayers.Projection("EPSG:900913");
+ var geographic = new OpenLayers.Projection("EPSG:4326");
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: mercator,
+ layers: [
+ new OpenLayers.Layer.OSM(),
+ new OpenLayers.Layer.Vector("Aircraft Locations", {
+ projection: geographic,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml-track.kml",
+ format: new OpenLayers.Format.KML({
+ extractTracks: true,
+ trackAttributes: ["speed"]
+ })
+ }),
+ styleMap: new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style({
+ graphicName: "circle",
+ pointRadius: 2,
+ fillOpacity: 0.5,
+ fillColor: "#ffcc66",
+ strokeColor: "#666633",
+ strokeWidth: 1
+ })
+ })
+ })
+ ],
+ center: new OpenLayers.LonLat(-93.2735, 44.8349).transform(geographic, mercator),
+ zoom: 8
+ });
+
+};
+
diff --git a/misc/openlayers/examples/kml-track.kml b/misc/openlayers/examples/kml-track.kml
new file mode 100644
index 0000000..2ab90ae
--- /dev/null
+++ b/misc/openlayers/examples/kml-track.kml
@@ -0,0 +1,3359 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
+<Document>
+<Camera>
+ <gx:TimeStamp>
+ <when>2010-05-01T13:00:00-05:00</when>
+ </gx:TimeStamp>
+ <longitude>-93.2207</longitude>
+ <latitude>44.882</latitude>
+ <altitude>50000</altitude>
+ <heading>0</heading>
+ <tilt>0</tilt>
+</Camera>
+<Style id="arrival">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.macnoise.com/scripts/plane.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>ff0000ff</color>
+ <width>3</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7fffffff</color>
+ </PolyStyle>
+</Style>
+<Style id="departure">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.macnoise.com/scripts/plane.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>ff00ff00</color>
+ <width>3</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7fffffff</color>
+ </PolyStyle>
+</Style>
+<Style id="overflight">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.macnoise.com/scripts/plane.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>ff222222</color>
+ <width>3</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7fffffff</color>
+ </PolyStyle>
+</Style>
+<Style id='rmt'>
+ <LabelStyle>
+ <color>ff0000cc</color>
+ <colorMode>normal</colorMode>
+ <scale>1</scale>
+ </LabelStyle>
+</Style>
+
+<name>Flight Tracks</name>
+<Folder>
+ <name>Arrivals</name>
+<Placemark>
+ <name>B752</name>
+ <adflag>A</adflag>
+ <flightid>DAL2973</flightid>
+ <styleUrl>#arrival</styleUrl>
+</Placemark>
+<Placemark>
+ <name>E170</name>
+ <adflag>A</adflag>
+ <flightid>TCF7521</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:04-05</when>
+ <when>2010-05-01T13:00:09-05</when>
+ <when>2010-05-01T13:00:13-05</when>
+ <when>2010-05-01T13:00:18-05</when>
+ <when>2010-05-01T13:00:23-05</when>
+ <when>2010-05-01T13:00:27-05</when>
+ <when>2010-05-01T13:00:32-05</when>
+ <when>2010-05-01T13:00:37-05</when>
+ <when>2010-05-01T13:00:41-05</when>
+ <when>2010-05-01T13:00:46-05</when>
+ <when>2010-05-01T13:00:51-05</when>
+ <when>2010-05-01T13:00:55-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <when>2010-05-01T13:01:05-05</when>
+ <when>2010-05-01T13:01:09-05</when>
+ <when>2010-05-01T13:01:14-05</when>
+ <when>2010-05-01T13:01:19-05</when>
+ <when>2010-05-01T13:01:23-05</when>
+ <when>2010-05-01T13:01:28-05</when>
+ <when>2010-05-01T13:01:33-05</when>
+ <when>2010-05-01T13:01:37-05</when>
+ <when>2010-05-01T13:01:42-05</when>
+ <when>2010-05-01T13:01:47-05</when>
+ <when>2010-05-01T13:01:51-05</when>
+ <when>2010-05-01T13:01:56-05</when>
+ <when>2010-05-01T13:02:00-05</when>
+ <when>2010-05-01T13:02:05-05</when>
+ <when>2010-05-01T13:02:10-05</when>
+ <when>2010-05-01T13:02:14-05</when>
+ <when>2010-05-01T13:02:19-05</when>
+ <when>2010-05-01T13:02:24-05</when>
+ <when>2010-05-01T13:02:28-05</when>
+ <when>2010-05-01T13:02:33-05</when>
+ <when>2010-05-01T13:02:38-05</when>
+ <when>2010-05-01T13:02:42-05</when>
+ <when>2010-05-01T13:02:47-05</when>
+ <when>2010-05-01T13:02:52-05</when>
+ <when>2010-05-01T13:02:56-05</when>
+ <when>2010-05-01T13:03:01-05</when>
+ <when>2010-05-01T13:03:06-05</when>
+ <when>2010-05-01T13:03:10-05</when>
+ <when>2010-05-01T13:03:15-05</when>
+ <when>2010-05-01T13:03:20-05</when>
+ <when>2010-05-01T13:03:24-05</when>
+ <when>2010-05-01T13:03:29-05</when>
+ <when>2010-05-01T13:03:33-05</when>
+ <when>2010-05-01T13:03:38-05</when>
+ <when>2010-05-01T13:03:43-05</when>
+ <when>2010-05-01T13:03:47-05</when>
+ <when>2010-05-01T13:03:52-05</when>
+ <when>2010-05-01T13:03:57-05</when>
+ <when>2010-05-01T13:04:01-05</when>
+ <when>2010-05-01T13:04:06-05</when>
+ <when>2010-05-01T13:04:11-05</when>
+ <when>2010-05-01T13:04:15-05</when>
+ <when>2010-05-01T13:04:20-05</when>
+ <when>2010-05-01T13:04:24-05</when>
+ <when>2010-05-01T13:04:29-05</when>
+ <when>2010-05-01T13:04:34-05</when>
+ <when>2010-05-01T13:04:38-05</when>
+ <when>2010-05-01T13:04:43-05</when>
+ <when>2010-05-01T13:04:48-05</when>
+ <when>2010-05-01T13:04:52-05</when>
+ <when>2010-05-01T13:04:57-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.3806146339391 44.8823651507134 2743</gx:coord>
+ <gx:coord>-93.3773041814209 44.887531728655 2743</gx:coord>
+ <gx:coord>-93.3742856469083 44.8942041806778 2743</gx:coord>
+ <gx:coord>-93.3722375106026 44.9009231720158 2743</gx:coord>
+ <gx:coord>-93.3711934089417 44.9077495987718 2712</gx:coord>
+ <gx:coord>-93.3707288919852 44.9145219645156 2712</gx:coord>
+ <gx:coord>-93.3703882714439 44.921240089024 2682</gx:coord>
+ <gx:coord>-93.3700882719793 44.9278850664392 2682</gx:coord>
+ <gx:coord>-93.369810041597 44.934389356737 2651</gx:coord>
+ <gx:coord>-93.3696836566166 44.9408553642446 2651</gx:coord>
+ <gx:coord>-93.3695425129226 44.9473561165969 2621</gx:coord>
+ <gx:coord>-93.3693185423471 44.9537360442564 2621</gx:coord>
+ <gx:coord>-93.3693194298816 44.9599975904123 2590</gx:coord>
+ <gx:coord>-93.3694031671108 44.9661411653607 2590</gx:coord>
+ <gx:coord>-93.3693840701674 44.9721433662718 2560</gx:coord>
+ <gx:coord>-93.3692180132117 44.9781295444861 2530</gx:coord>
+ <gx:coord>-93.3691451194519 44.9840448037796 2530</gx:coord>
+ <gx:coord>-93.3691016671806 44.9899713582099 2499</gx:coord>
+ <gx:coord>-93.3689494749454 44.9958413836039 2469</gx:coord>
+ <gx:coord>-93.3687664425911 45.0015898503441 2469</gx:coord>
+ <gx:coord>-93.3686331392066 45.0072067405394 2438</gx:coord>
+ <gx:coord>-93.368599726987 45.0127741072778 2438</gx:coord>
+ <gx:coord>-93.3686335399802 45.0181909829245 2408</gx:coord>
+ <gx:coord>-93.3686494842522 45.0234209328517 2377</gx:coord>
+ <gx:coord>-93.3684675008434 45.0286421277802 2377</gx:coord>
+ <gx:coord>-93.3683004008135 45.0337736830037 2347</gx:coord>
+ <gx:coord>-93.3682154531592 45.0388787100883 2347</gx:coord>
+ <gx:coord>-93.3683732351584 45.0439463933312 2316</gx:coord>
+ <gx:coord>-93.3684142261585 45.0490625635571 2286</gx:coord>
+ <gx:coord>-93.368143196103 45.0541794203461 2286</gx:coord>
+ <gx:coord>-93.367535632513 45.0592327492686 2255</gx:coord>
+ <gx:coord>-93.3659957839062 45.0642802941983 2225</gx:coord>
+ <gx:coord>-93.3633687278349 45.0690971409498 2194</gx:coord>
+ <gx:coord>-93.3595471289752 45.0735562314314 2164</gx:coord>
+ <gx:coord>-93.354507806741 45.0775832626329 2133</gx:coord>
+ <gx:coord>-93.3485772854268 45.0808293296313 2103</gx:coord>
+ <gx:coord>-93.3421088995911 45.0832469498159 2072</gx:coord>
+ <gx:coord>-93.3351951799649 45.0848109253641 2042</gx:coord>
+ <gx:coord>-93.3280418232705 45.0854246893649 2011</gx:coord>
+ <gx:coord>-93.3209037884868 45.085161376704 1981</gx:coord>
+ <gx:coord>-93.3144723535558 45.0839515303103 1920</gx:coord>
+ <gx:coord>-93.3088086501455 45.0819151336509 1859</gx:coord>
+ <gx:coord>-93.3036917357871 45.0792511074707 1828</gx:coord>
+ <gx:coord>-93.2993102013018 45.0761649196153 1798</gx:coord>
+ <gx:coord>-93.2958637974439 45.0728030913231 1768</gx:coord>
+ <gx:coord>-93.2932247031583 45.0693710694135 1737</gx:coord>
+ <gx:coord>-93.2910486937635 45.0659261208859 1707</gx:coord>
+ <gx:coord>-93.2888955993508 45.0625213360315 1646</gx:coord>
+ <gx:coord>-93.2867217490801 45.0591551785287 1615</gx:coord>
+ <gx:coord>-93.2847336413534 45.0557231883841 1554</gx:coord>
+ <gx:coord>-93.28312407167 45.0523278244803 1493</gx:coord>
+ <gx:coord>-93.2820244198825 45.0489932635616 1463</gx:coord>
+ <gx:coord>-93.280973634799 45.045699024227 1432</gx:coord>
+ <gx:coord>-93.2799787649067 45.0423671615142 1402</gx:coord>
+ <gx:coord>-93.2791066054659 45.0390946347227 1341</gx:coord>
+ <gx:coord>-93.2784127726862 45.0358634874951 1310</gx:coord>
+ <gx:coord>-93.2779112647802 45.0326008999249 1249</gx:coord>
+ <gx:coord>-93.2774525889269 45.029330264578 1219</gx:coord>
+ <gx:coord>-93.2770784201422 45.0260213245381 1188</gx:coord>
+ <gx:coord>-93.2766188240203 45.0227403501287 1158</gx:coord>
+ <gx:coord>-93.275816823547 45.0195461585342 1127</gx:coord>
+ <gx:coord>-93.2748914840222 45.0163603671711 1066</gx:coord>
+ <gx:coord>-93.2740540575136 45.0131542183389 1036</gx:coord>
+ <gx:coord>-93.2733145981662 45.010040506328 1006</gx:coord>
+ <gx:coord>-93.2724700860766 45.0070495365802 975</gx:coord>
+ <gx:coord>-93.2720166974715 45.0052389419128 957</gx:coord>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>40 0 0</gx:angles>
+ <gx:angles>50 0 0</gx:angles>
+ <gx:angles>60 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>80 0 0</gx:angles>
+ <gx:angles>90 0 0</gx:angles>
+ <gx:angles>100 0 0</gx:angles>
+ <gx:angles>110 0 0</gx:angles>
+ <gx:angles>120 0 0</gx:angles>
+ <gx:angles>130 0 0</gx:angles>
+ <gx:angles>140 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <speed>376</speed>
+ <speed>367</speed>
+ <speed>361</speed>
+ <speed>371</speed>
+ <speed>367</speed>
+ <speed>363</speed>
+ <speed>359</speed>
+ <speed>356</speed>
+ <speed>352</speed>
+ <speed>347</speed>
+ <speed>343</speed>
+ <speed>339</speed>
+ <speed>334</speed>
+ <speed>329</speed>
+ <speed>326</speed>
+ <speed>321</speed>
+ <speed>318</speed>
+ <speed>315</speed>
+ <speed>311</speed>
+ <speed>307</speed>
+ <speed>301</speed>
+ <speed>294</speed>
+ <speed>289</speed>
+ <speed>295</speed>
+ <speed>280</speed>
+ <speed>277</speed>
+ <speed>287</speed>
+ <speed>275</speed>
+ <speed>275</speed>
+ <speed>276</speed>
+ <speed>277</speed>
+ <speed>279</speed>
+ <speed>281</speed>
+ <speed>282</speed>
+ <speed>282</speed>
+ <speed>281</speed>
+ <speed>280</speed>
+ <speed>274</speed>
+ <speed>266</speed>
+ <speed>260</speed>
+ <speed>254</speed>
+ <speed>244</speed>
+ <speed>235</speed>
+ <speed>235</speed>
+ <speed>219</speed>
+ <speed>212</speed>
+ <speed>214</speed>
+ <speed>201</speed>
+ <speed>197</speed>
+ <speed>193</speed>
+ <speed>190</speed>
+ <speed>187</speed>
+ <speed>183</speed>
+ <speed>180</speed>
+ <speed>186</speed>
+ <speed>178</speed>
+ <speed>177</speed>
+ <speed>183</speed>
+ <speed>177</speed>
+ <speed>176</speed>
+ <speed>175</speed>
+ <speed>173</speed>
+ <speed>171</speed>
+ <speed>165</speed>
+ <speed>166</speed>
+ <speed>167</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>BE33</name>
+ <adflag>A</adflag>
+ <flightid>N38175</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:02-05</when>
+ <when>2010-05-01T13:00:07-05</when>
+ <when>2010-05-01T13:00:12-05</when>
+ <when>2010-05-01T13:00:16-05</when>
+ <when>2010-05-01T13:00:21-05</when>
+ <when>2010-05-01T13:00:25-05</when>
+ <when>2010-05-01T13:00:30-05</when>
+ <when>2010-05-01T13:00:35-05</when>
+ <when>2010-05-01T13:00:39-05</when>
+ <when>2010-05-01T13:00:44-05</when>
+ <when>2010-05-01T13:00:49-05</when>
+ <when>2010-05-01T13:00:53-05</when>
+ <when>2010-05-01T13:00:58-05</when>
+ <when>2010-05-01T13:01:03-05</when>
+ <when>2010-05-01T13:01:07-05</when>
+ <when>2010-05-01T13:01:12-05</when>
+ <when>2010-05-01T13:01:16-05</when>
+ <when>2010-05-01T13:01:21-05</when>
+ <when>2010-05-01T13:01:26-05</when>
+ <when>2010-05-01T13:01:30-05</when>
+ <when>2010-05-01T13:01:35-05</when>
+ <when>2010-05-01T13:01:40-05</when>
+ <when>2010-05-01T13:01:44-05</when>
+ <when>2010-05-01T13:01:49-05</when>
+ <when>2010-05-01T13:01:54-05</when>
+ <when>2010-05-01T13:01:58-05</when>
+ <when>2010-05-01T13:02:03-05</when>
+ <when>2010-05-01T13:02:08-05</when>
+ <when>2010-05-01T13:02:12-05</when>
+ <when>2010-05-01T13:02:17-05</when>
+ <when>2010-05-01T13:02:21-05</when>
+ <when>2010-05-01T13:02:26-05</when>
+ <when>2010-05-01T13:02:31-05</when>
+ <when>2010-05-01T13:02:35-05</when>
+ <when>2010-05-01T13:02:40-05</when>
+ <when>2010-05-01T13:02:45-05</when>
+ <when>2010-05-01T13:02:49-05</when>
+ <when>2010-05-01T13:02:54-05</when>
+ <when>2010-05-01T13:02:59-05</when>
+ <when>2010-05-01T13:03:03-05</when>
+ <when>2010-05-01T13:03:08-05</when>
+ <when>2010-05-01T13:03:13-05</when>
+ <when>2010-05-01T13:03:17-05</when>
+ <when>2010-05-01T13:03:22-05</when>
+ <when>2010-05-01T13:03:27-05</when>
+ <when>2010-05-01T13:03:31-05</when>
+ <when>2010-05-01T13:03:36-05</when>
+ <when>2010-05-01T13:03:40-05</when>
+ <when>2010-05-01T13:03:45-05</when>
+ <when>2010-05-01T13:03:50-05</when>
+ <when>2010-05-01T13:03:54-05</when>
+ <when>2010-05-01T13:03:59-05</when>
+ <when>2010-05-01T13:04:04-05</when>
+ <when>2010-05-01T13:04:08-05</when>
+ <when>2010-05-01T13:04:13-05</when>
+ <when>2010-05-01T13:04:18-05</when>
+ <when>2010-05-01T13:04:22-05</when>
+ <when>2010-05-01T13:04:27-05</when>
+ <when>2010-05-01T13:04:32-05</when>
+ <when>2010-05-01T13:04:36-05</when>
+ <when>2010-05-01T13:04:41-05</when>
+ <when>2010-05-01T13:04:46-05</when>
+ <when>2010-05-01T13:04:50-05</when>
+ <when>2010-05-01T13:04:55-05</when>
+ <when>2010-05-01T13:04:59-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.0144637208028 44.6541474764804 1006</gx:coord>
+ <gx:coord>-93.0162681345228 44.6547274296664 1006</gx:coord>
+ <gx:coord>-93.0196734868835 44.6559915702004 975</gx:coord>
+ <gx:coord>-93.0231899415297 44.657188463998 945</gx:coord>
+ <gx:coord>-93.0267619421777 44.6582849847887 945</gx:coord>
+ <gx:coord>-93.0302021384369 44.6594728216183 914</gx:coord>
+ <gx:coord>-93.0338776768471 44.6606515995762 914</gx:coord>
+ <gx:coord>-93.0375866343814 44.6618806707998 884</gx:coord>
+ <gx:coord>-93.0411146687035 44.6632657982455 884</gx:coord>
+ <gx:coord>-93.0447829038862 44.6646495821585 884</gx:coord>
+ <gx:coord>-93.0486933143218 44.6659856209571 914</gx:coord>
+ <gx:coord>-93.0525604964428 44.6672664774449 884</gx:coord>
+ <gx:coord>-93.0559892061682 44.6686325276705 884</gx:coord>
+ <gx:coord>-93.0595122787868 44.6700360197293 884</gx:coord>
+ <gx:coord>-93.0633002358996 44.6714677760105 884</gx:coord>
+ <gx:coord>-93.0669378047758 44.6729112967405 884</gx:coord>
+ <gx:coord>-93.0703945562928 44.6742924439153 884</gx:coord>
+ <gx:coord>-93.0739155391788 44.675662416586 853</gx:coord>
+ <gx:coord>-93.0775155708379 44.677089176175 853</gx:coord>
+ <gx:coord>-93.0809933799389 44.6786451836444 884</gx:coord>
+ <gx:coord>-93.0844890660754 44.6803751966183 884</gx:coord>
+ <gx:coord>-93.0880299182291 44.6822044360867 884</gx:coord>
+ <gx:coord>-93.0915094168569 44.6840756286875 884</gx:coord>
+ <gx:coord>-93.0948937737562 44.6859682015167 853</gx:coord>
+ <gx:coord>-93.0981262632978 44.6879373605934 853</gx:coord>
+ <gx:coord>-93.101454986707 44.6899364101225 792</gx:coord>
+ <gx:coord>-93.1050116792292 44.6917700662615 823</gx:coord>
+ <gx:coord>-93.1086488406447 44.6935571270851 792</gx:coord>
+ <gx:coord>-93.1123714592033 44.6950844029867 792</gx:coord>
+ <gx:coord>-93.1160669441025 44.6961547755501 792</gx:coord>
+ <gx:coord>-93.1198701422529 44.6969844340505 823</gx:coord>
+ <gx:coord>-93.1236851662824 44.6978291490322 823</gx:coord>
+ <gx:coord>-93.1274225659796 44.6986718065416 823</gx:coord>
+ <gx:coord>-93.1311942704264 44.6993984412966 853</gx:coord>
+ <gx:coord>-93.1349381107515 44.6999999769729 823</gx:coord>
+ <gx:coord>-93.1389399866831 44.7004676966664 823</gx:coord>
+ <gx:coord>-93.1429353283304 44.7008467726719 792</gx:coord>
+ <gx:coord>-93.1467319575358 44.7012413854652 792</gx:coord>
+ <gx:coord>-93.1499628617348 44.701745671311 256</gx:coord>
+ <gx:coord>-93.153336892791 44.7021601177798 823</gx:coord>
+ <gx:coord>-93.1573155649233 44.7025431241565 823</gx:coord>
+ <gx:coord>-93.1612285414011 44.7030631821633 853</gx:coord>
+ <gx:coord>-93.1650893906409 44.7036343060226 823</gx:coord>
+ <gx:coord>-93.168735434804 44.7041440584898 823</gx:coord>
+ <gx:coord>-93.1724202011042 44.7046128372079 823</gx:coord>
+ <gx:coord>-93.1761398862948 44.7051091435166 792</gx:coord>
+ <gx:coord>-93.1796630936383 44.7055777394683 792</gx:coord>
+ <gx:coord>-93.1832380178971 44.7060406072565 823</gx:coord>
+ <gx:coord>-93.1866638342882 44.7066093849988 823</gx:coord>
+ <gx:coord>-93.1899087146892 44.7071801343989 823</gx:coord>
+ <gx:coord>-93.193359587537 44.7076743817907 823</gx:coord>
+ <gx:coord>-93.1967000778824 44.7081822996347 823</gx:coord>
+ <gx:coord>-93.1999669003743 44.7087817760063 823</gx:coord>
+ <gx:coord>-93.2034706963438 44.7093224014614 823</gx:coord>
+ <gx:coord>-93.2071875434321 44.7097715459537 823</gx:coord>
+ <gx:coord>-93.2107765241539 44.7103153755538 823</gx:coord>
+ <gx:coord>-93.2143295791529 44.7108254548145 823</gx:coord>
+ <gx:coord>-93.2178486234666 44.7112392078782 792</gx:coord>
+ <gx:coord>-93.2211867867256 44.7116696952986 823</gx:coord>
+ <gx:coord>-93.2243580018062 44.7121483598855 823</gx:coord>
+ <gx:coord>-93.2273334445383 44.712639974576 823</gx:coord>
+ <gx:coord>-93.230487243959 44.7131510651587 823</gx:coord>
+ <gx:coord>-93.233844667064 44.7137558527546 823</gx:coord>
+ <gx:coord>-93.2369967848442 44.714497155781 823</gx:coord>
+ <gx:coord>-93.2401184808953 44.7154113173173 823</gx:coord>
+ <gx:coord>-93.2431805770012 44.7167484248595 792</gx:coord>
+ <gx:coord>-93.2437334091088 44.7170975413723 792</gx:coord>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <speed>150</speed>
+ <speed>156</speed>
+ <speed>152</speed>
+ <speed>156</speed>
+ <speed>151</speed>
+ <speed>152</speed>
+ <speed>160</speed>
+ <speed>157</speed>
+ <speed>159</speed>
+ <speed>158</speed>
+ <speed>158</speed>
+ <speed>160</speed>
+ <speed>160</speed>
+ <speed>158</speed>
+ <speed>162</speed>
+ <speed>157</speed>
+ <speed>158</speed>
+ <speed>164</speed>
+ <speed>159</speed>
+ <speed>161</speed>
+ <speed>163</speed>
+ <speed>164</speed>
+ <speed>166</speed>
+ <speed>167</speed>
+ <speed>167</speed>
+ <speed>166</speed>
+ <speed>164</speed>
+ <speed>163</speed>
+ <speed>166</speed>
+ <speed>157</speed>
+ <speed>154</speed>
+ <speed>157</speed>
+ <speed>152</speed>
+ <speed>152</speed>
+ <speed>151</speed>
+ <speed>147</speed>
+ <speed>144</speed>
+ <speed>146</speed>
+ <speed>145</speed>
+ <speed>145</speed>
+ <speed>144</speed>
+ <speed>146</speed>
+ <speed>148</speed>
+ <speed>145</speed>
+ <speed>143</speed>
+ <speed>146</speed>
+ <speed>138</speed>
+ <speed>137</speed>
+ <speed>140</speed>
+ <speed>133</speed>
+ <speed>133</speed>
+ <speed>135</speed>
+ <speed>137</speed>
+ <speed>137</speed>
+ <speed>138</speed>
+ <speed>138</speed>
+ <speed>136</speed>
+ <speed>131</speed>
+ <speed>129</speed>
+ <speed>128</speed>
+ <speed>126</speed>
+ <speed>126</speed>
+ <speed>133</speed>
+ <speed>132</speed>
+ <speed>136</speed>
+ <speed>139</speed>
+ <speed>136</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>A319</name>
+ <adflag>A</adflag>
+ <flightid>DAL1588</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:04-05</when>
+ <when>2010-05-01T13:00:08-05</when>
+ <when>2010-05-01T13:00:13-05</when>
+ <when>2010-05-01T13:00:18-05</when>
+ <when>2010-05-01T13:00:22-05</when>
+ <when>2010-05-01T13:00:27-05</when>
+ <when>2010-05-01T13:00:31-05</when>
+ <when>2010-05-01T13:00:36-05</when>
+ <when>2010-05-01T13:00:41-05</when>
+ <when>2010-05-01T13:00:45-05</when>
+ <when>2010-05-01T13:00:50-05</when>
+ <when>2010-05-01T13:00:55-05</when>
+ <when>2010-05-01T13:00:59-05</when>
+ <when>2010-05-01T13:01:04-05</when>
+ <when>2010-05-01T13:01:09-05</when>
+ <when>2010-05-01T13:01:13-05</when>
+ <when>2010-05-01T13:01:18-05</when>
+ <when>2010-05-01T13:01:22-05</when>
+ <when>2010-05-01T13:01:27-05</when>
+ <when>2010-05-01T13:01:32-05</when>
+ <when>2010-05-01T13:01:36-05</when>
+ <when>2010-05-01T13:01:41-05</when>
+ <when>2010-05-01T13:01:46-05</when>
+ <when>2010-05-01T13:01:50-05</when>
+ <when>2010-05-01T13:01:55-05</when>
+ <when>2010-05-01T13:02:00-05</when>
+ <when>2010-05-01T13:02:04-05</when>
+ <when>2010-05-01T13:02:09-05</when>
+ <when>2010-05-01T13:02:13-05</when>
+ <when>2010-05-01T13:02:18-05</when>
+ <when>2010-05-01T13:02:23-05</when>
+ <when>2010-05-01T13:02:27-05</when>
+ <when>2010-05-01T13:02:32-05</when>
+ <when>2010-05-01T13:02:37-05</when>
+ <when>2010-05-01T13:02:41-05</when>
+ <when>2010-05-01T13:02:46-05</when>
+ <when>2010-05-01T13:02:51-05</when>
+ <when>2010-05-01T13:02:55-05</when>
+ <when>2010-05-01T13:03:00-05</when>
+ <when>2010-05-01T13:03:05-05</when>
+ <when>2010-05-01T13:03:09-05</when>
+ <when>2010-05-01T13:03:14-05</when>
+ <when>2010-05-01T13:03:19-05</when>
+ <when>2010-05-01T13:03:23-05</when>
+ <when>2010-05-01T13:03:28-05</when>
+ <when>2010-05-01T13:03:33-05</when>
+ <when>2010-05-01T13:03:37-05</when>
+ <when>2010-05-01T13:03:42-05</when>
+ <when>2010-05-01T13:03:47-05</when>
+ <when>2010-05-01T13:03:51-05</when>
+ <when>2010-05-01T13:03:56-05</when>
+ <when>2010-05-01T13:04:01-05</when>
+ <when>2010-05-01T13:04:05-05</when>
+ <when>2010-05-01T13:04:10-05</when>
+ <when>2010-05-01T13:04:15-05</when>
+ <when>2010-05-01T13:04:19-05</when>
+ <when>2010-05-01T13:04:24-05</when>
+ <when>2010-05-01T13:04:29-05</when>
+ <when>2010-05-01T13:04:33-05</when>
+ <when>2010-05-01T13:04:38-05</when>
+ <when>2010-05-01T13:04:42-05</when>
+ <when>2010-05-01T13:04:47-05</when>
+ <when>2010-05-01T13:04:52-05</when>
+ <when>2010-05-01T13:04:56-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.6927825194056 44.7952011849485 3011</gx:coord>
+ <gx:coord>-93.6850156681578 44.7968042586582 2987</gx:coord>
+ <gx:coord>-93.6752785488692 44.7990458605003 2956</gx:coord>
+ <gx:coord>-93.6657083011645 44.8014897663497 2926</gx:coord>
+ <gx:coord>-93.6560029615388 44.803768841381 2865</gx:coord>
+ <gx:coord>-93.6462045264035 44.8058749817725 2834</gx:coord>
+ <gx:coord>-93.6365671200126 44.8080848199989 2804</gx:coord>
+ <gx:coord>-93.6269933807039 44.8102767000109 2773</gx:coord>
+ <gx:coord>-93.6175405757462 44.8123960709083 2743</gx:coord>
+ <gx:coord>-93.6082528975965 44.8146455509748 2743</gx:coord>
+ <gx:coord>-93.599077315807 44.816765612372 2743</gx:coord>
+ <gx:coord>-93.5899428762254 44.8186933623744 2743</gx:coord>
+ <gx:coord>-93.5809104439923 44.8205403457841 2743</gx:coord>
+ <gx:coord>-93.5720785209701 44.8224608846058 2743</gx:coord>
+ <gx:coord>-93.5634871751281 44.8245259755976 2743</gx:coord>
+ <gx:coord>-93.5549873819943 44.8264288380043 2743</gx:coord>
+ <gx:coord>-93.5465301417765 44.828146963076 2743</gx:coord>
+ <gx:coord>-93.5382602633868 44.8299225976982 2743</gx:coord>
+ <gx:coord>-93.5299909540853 44.8317218299661 2743</gx:coord>
+ <gx:coord>-93.5217290971281 44.8335486849228 2743</gx:coord>
+ <gx:coord>-93.5135254319341 44.8354478299135 2743</gx:coord>
+ <gx:coord>-93.5052463800971 44.8374557781543 2743</gx:coord>
+ <gx:coord>-93.4970241378696 44.8393862625467 2743</gx:coord>
+ <gx:coord>-93.4888916549316 44.8410628089589 2743</gx:coord>
+ <gx:coord>-93.48064759949 44.8427813728647 2743</gx:coord>
+ <gx:coord>-93.4722750572418 44.8445241451071 2712</gx:coord>
+ <gx:coord>-93.4639262889443 44.8463688032483 2743</gx:coord>
+ <gx:coord>-93.4556378890352 44.8482208160082 2743</gx:coord>
+ <gx:coord>-93.447407568623 44.8500947691895 2743</gx:coord>
+ <gx:coord>-93.4393642055014 44.8523517774191 2743</gx:coord>
+ <gx:coord>-93.4316071047585 44.8551246076581 2743</gx:coord>
+ <gx:coord>-93.4244028068218 44.8584705613027 2743</gx:coord>
+ <gx:coord>-93.4178621631751 44.8625068369064 2743</gx:coord>
+ <gx:coord>-93.412146307774 44.867174139387 2743</gx:coord>
+ <gx:coord>-93.4075995385136 44.8722931076546 2743</gx:coord>
+ <gx:coord>-93.4039820359465 44.8777375352403 2743</gx:coord>
+ <gx:coord>-93.4016072978871 44.8833117162528 2743</gx:coord>
+ <gx:coord>-93.4005924913122 44.8890542850171 2743</gx:coord>
+ <gx:coord>-93.4005563275156 44.8948199828389 2712</gx:coord>
+ <gx:coord>-93.401452844832 44.9002595243996 2682</gx:coord>
+ <gx:coord>-93.4032713926758 44.905357711587 2651</gx:coord>
+ <gx:coord>-93.4058979070097 44.9101654056189 2621</gx:coord>
+ <gx:coord>-93.4092802306306 44.9145600538157 2590</gx:coord>
+ <gx:coord>-93.4134192058116 44.9185233235535 2530</gx:coord>
+ <gx:coord>-93.4181155067703 44.9222086893794 2499</gx:coord>
+ <gx:coord>-93.4230280156053 44.9256003980833 2469</gx:coord>
+ <gx:coord>-93.4278299295206 44.9290448932076 2469</gx:coord>
+ <gx:coord>-93.4322535173586 44.9329315139411 2438</gx:coord>
+ <gx:coord>-93.4361102418566 44.9372336672133 2438</gx:coord>
+ <gx:coord>-93.4389664177141 44.9421107629499 2438</gx:coord>
+ <gx:coord>-93.4407103051748 44.9473646343685 2438</gx:coord>
+ <gx:coord>-93.4416032158439 44.9527430754122 2408</gx:coord>
+ <gx:coord>-93.4419308994101 44.9581538029148 2408</gx:coord>
+ <gx:coord>-93.4419313717103 44.9636029026039 2377</gx:coord>
+ <gx:coord>-93.4417378352424 44.9690628839115 2347</gx:coord>
+ <gx:coord>-93.4415990458805 44.9744028948354 2347</gx:coord>
+ <gx:coord>-93.4414478519305 44.9796663959001 2316</gx:coord>
+ <gx:coord>-93.4413557290344 44.9848518867987 2316</gx:coord>
+ <gx:coord>-93.4412896011133 44.9899566690879 2316</gx:coord>
+ <gx:coord>-93.4411625354696 44.9949926823698 2286</gx:coord>
+ <gx:coord>-93.4411216122071 45.000018474264 2225</gx:coord>
+ <gx:coord>-93.4409537301264 45.0051267594771 2194</gx:coord>
+ <gx:coord>-93.4408143120176 45.0101358999996 2133</gx:coord>
+ <gx:coord>-93.4405516208864 45.0150761969136 2103</gx:coord>
+ <gx:coord>-93.4397025278204 45.0199965135021 2042</gx:coord>
+ <gx:coord>-93.4384243921567 45.02391596133 1993.2</gx:coord>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>60 0 0</gx:angles>
+ <gx:angles>50 0 0</gx:angles>
+ <gx:angles>50 0 0</gx:angles>
+ <gx:angles>40 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>360 0 0</gx:angles>
+ <gx:angles>350 0 0</gx:angles>
+ <gx:angles>340 0 0</gx:angles>
+ <gx:angles>340 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>340 0 0</gx:angles>
+ <gx:angles>350 0 0</gx:angles>
+ <gx:angles>350 0 0</gx:angles>
+ <gx:angles>360 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <speed>390</speed>
+ <speed>383</speed>
+ <speed>397</speed>
+ <speed>390</speed>
+ <speed>405</speed>
+ <speed>388</speed>
+ <speed>386</speed>
+ <speed>397</speed>
+ <speed>377</speed>
+ <speed>373</speed>
+ <speed>367</speed>
+ <speed>362</speed>
+ <speed>357</speed>
+ <speed>350</speed>
+ <speed>345</speed>
+ <speed>353</speed>
+ <speed>336</speed>
+ <speed>334</speed>
+ <speed>346</speed>
+ <speed>332</speed>
+ <speed>331</speed>
+ <speed>330</speed>
+ <speed>331</speed>
+ <speed>332</speed>
+ <speed>331</speed>
+ <speed>331</speed>
+ <speed>345</speed>
+ <speed>333</speed>
+ <speed>332</speed>
+ <speed>344</speed>
+ <speed>331</speed>
+ <speed>331</speed>
+ <speed>329</speed>
+ <speed>326</speed>
+ <speed>324</speed>
+ <speed>320</speed>
+ <speed>314</speed>
+ <speed>307</speed>
+ <speed>298</speed>
+ <speed>291</speed>
+ <speed>284</speed>
+ <speed>276</speed>
+ <speed>271</speed>
+ <speed>268</speed>
+ <speed>266</speed>
+ <speed>267</speed>
+ <speed>270</speed>
+ <speed>274</speed>
+ <speed>279</speed>
+ <speed>283</speed>
+ <speed>288</speed>
+ <speed>291</speed>
+ <speed>292</speed>
+ <speed>290</speed>
+ <speed>288</speed>
+ <speed>286</speed>
+ <speed>281</speed>
+ <speed>278</speed>
+ <speed>286</speed>
+ <speed>273</speed>
+ <speed>271</speed>
+ <speed>280</speed>
+ <speed>270</speed>
+ <speed>274</speed>
+ <speed>263</speed>
+ <speed>268</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>E145</name>
+ <adflag>A</adflag>
+ <flightid>CHQ1453</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:11-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:48-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:02-05</when>
+ <when>2010-05-01T13:01:06-05</when>
+ <when>2010-05-01T13:01:11-05</when>
+ <when>2010-05-01T13:01:15-05</when>
+ <when>2010-05-01T13:01:20-05</when>
+ <when>2010-05-01T13:01:25-05</when>
+ <when>2010-05-01T13:01:29-05</when>
+ <when>2010-05-01T13:01:34-05</when>
+ <when>2010-05-01T13:01:39-05</when>
+ <when>2010-05-01T13:01:43-05</when>
+ <when>2010-05-01T13:01:48-05</when>
+ <when>2010-05-01T13:01:52-05</when>
+ <when>2010-05-01T13:01:57-05</when>
+ <when>2010-05-01T13:02:02-05</when>
+ <when>2010-05-01T13:02:06-05</when>
+ <when>2010-05-01T13:02:11-05</when>
+ <when>2010-05-01T13:02:16-05</when>
+ <when>2010-05-01T13:02:20-05</when>
+ <when>2010-05-01T13:02:25-05</when>
+ <when>2010-05-01T13:02:29-05</when>
+ <when>2010-05-01T13:02:34-05</when>
+ <when>2010-05-01T13:02:39-05</when>
+ <when>2010-05-01T13:02:43-05</when>
+ <when>2010-05-01T13:02:48-05</when>
+ <when>2010-05-01T13:02:53-05</when>
+ <when>2010-05-01T13:02:57-05</when>
+ <when>2010-05-01T13:03:02-05</when>
+ <when>2010-05-01T13:03:07-05</when>
+ <when>2010-05-01T13:03:11-05</when>
+ <when>2010-05-01T13:03:16-05</when>
+ <when>2010-05-01T13:03:21-05</when>
+ <when>2010-05-01T13:03:25-05</when>
+ <when>2010-05-01T13:03:30-05</when>
+ <when>2010-05-01T13:03:34-05</when>
+ <when>2010-05-01T13:03:39-05</when>
+ <when>2010-05-01T13:03:44-05</when>
+ <when>2010-05-01T13:03:48-05</when>
+ <when>2010-05-01T13:03:53-05</when>
+ <when>2010-05-01T13:03:58-05</when>
+ <when>2010-05-01T13:04:02-05</when>
+ <when>2010-05-01T13:04:07-05</when>
+ <when>2010-05-01T13:04:11-05</when>
+ <when>2010-05-01T13:04:16-05</when>
+ <when>2010-05-01T13:04:21-05</when>
+ <when>2010-05-01T13:04:25-05</when>
+ <when>2010-05-01T13:04:30-05</when>
+ <when>2010-05-01T13:04:35-05</when>
+ <when>2010-05-01T13:04:39-05</when>
+ <when>2010-05-01T13:04:44-05</when>
+ <when>2010-05-01T13:04:49-05</when>
+ <when>2010-05-01T13:04:53-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.5727580977974 45.0236058844647 2530</gx:coord>
+ <gx:coord>-92.5742776202954 45.0237913896498 2530</gx:coord>
+ <gx:coord>-92.5803397933112 45.0241784662561 2499</gx:coord>
+ <gx:coord>-92.5865075192046 45.0247891381303 2469</gx:coord>
+ <gx:coord>-92.5926877928765 45.0257073410966 2469</gx:coord>
+ <gx:coord>-92.5986546763805 45.0261844476041 2438</gx:coord>
+ <gx:coord>-92.6046737535477 45.0267206733977 2438</gx:coord>
+ <gx:coord>-92.6106885874739 45.0275061986719 2438</gx:coord>
+ <gx:coord>-92.616359210337 45.027935793162 2438</gx:coord>
+ <gx:coord>-92.6220735719954 45.028379077688 2438</gx:coord>
+ <gx:coord>-92.6280403097635 45.0290552550566 2438</gx:coord>
+ <gx:coord>-92.6341725652711 45.029824064212 2438</gx:coord>
+ <gx:coord>-92.640279209769 45.0304963952702 2438</gx:coord>
+ <gx:coord>-92.6463747377703 45.0311129317319 2438</gx:coord>
+ <gx:coord>-92.6524891739589 45.0317396965059 2438</gx:coord>
+ <gx:coord>-92.6587083612282 45.0325526597288 2438</gx:coord>
+ <gx:coord>-92.6649573988971 45.0334560566121 2438</gx:coord>
+ <gx:coord>-92.6712436344147 45.0343516389227 2438</gx:coord>
+ <gx:coord>-92.6777900587447 45.0353199754833 2438</gx:coord>
+ <gx:coord>-92.6842020644974 45.0361081217423 2438</gx:coord>
+ <gx:coord>-92.6904510353584 45.0368379981793 2438</gx:coord>
+ <gx:coord>-92.6968618406938 45.0376828531019 2438</gx:coord>
+ <gx:coord>-92.7033318031208 45.0383078021685 2438</gx:coord>
+ <gx:coord>-92.709766951172 45.0386241893014 2438</gx:coord>
+ <gx:coord>-92.7161769864286 45.0390317903939 2438</gx:coord>
+ <gx:coord>-92.7225665589756 45.0396570251316 2408</gx:coord>
+ <gx:coord>-92.7288886541216 45.0403373286575 2438</gx:coord>
+ <gx:coord>-92.7352120601109 45.0409943934305 2438</gx:coord>
+ <gx:coord>-92.7414745561156 45.0416276553236 2438</gx:coord>
+ <gx:coord>-92.7477923122779 45.0424046535325 2438</gx:coord>
+ <gx:coord>-92.7541218465412 45.0434006217761 2438</gx:coord>
+ <gx:coord>-92.7601214481636 45.0440713086474 2438</gx:coord>
+ <gx:coord>-92.7660333478225 45.0444426749968 2438</gx:coord>
+ <gx:coord>-92.772102853148 45.0448779180664 2438</gx:coord>
+ <gx:coord>-92.7780236703859 45.0449122731228 2408</gx:coord>
+ <gx:coord>-92.7839974197715 45.0449532357526 2408</gx:coord>
+ <gx:coord>-92.7902562936361 45.0450709796934 2377</gx:coord>
+ <gx:coord>-92.7962688995386 45.0448540267375 2347</gx:coord>
+ <gx:coord>-92.8024120242439 45.0448640459334 2316</gx:coord>
+ <gx:coord>-92.8087530574681 45.0449050506622 2316</gx:coord>
+ <gx:coord>-92.814709697375 45.0446514037676 2286</gx:coord>
+ <gx:coord>-92.8205575663732 45.0444101119805 2255</gx:coord>
+ <gx:coord>-92.8266048584444 45.0442428819735 2225</gx:coord>
+ <gx:coord>-92.8327618067112 45.0440942522516 2194</gx:coord>
+ <gx:coord>-92.83872651911 45.0438644076684 2164</gx:coord>
+ <gx:coord>-92.8446994303267 45.043730942658 2133</gx:coord>
+ <gx:coord>-92.8506627055935 45.0435520713609 2103</gx:coord>
+ <gx:coord>-92.8563938230908 45.0431897062426 2072</gx:coord>
+ <gx:coord>-92.8622525737075 45.0428768437665 2042</gx:coord>
+ <gx:coord>-92.8680590561999 45.0424504399663 2011</gx:coord>
+ <gx:coord>-92.8739470985612 45.0422191353343 1981</gx:coord>
+ <gx:coord>-92.879905503922 45.0421676833604 1950</gx:coord>
+ <gx:coord>-92.8859780438424 45.0420919545536 1920</gx:coord>
+ <gx:coord>-92.8920993846605 45.0419574098772 1889</gx:coord>
+ <gx:coord>-92.8980850189767 45.041613347859 1859</gx:coord>
+ <gx:coord>-92.9042733870782 45.041256341571 1828</gx:coord>
+ <gx:coord>-92.9105676382912 45.0409944306292 1798</gx:coord>
+ <gx:coord>-92.9169019856279 45.0406669834687 1768</gx:coord>
+ <gx:coord>-92.9233572619921 45.0402533884047 1737</gx:coord>
+ <gx:coord>-92.9301295670095 45.0401453351324 1707</gx:coord>
+ <gx:coord>-92.9368012064813 45.0400078656145 1676</gx:coord>
+ <gx:coord>-92.943436221178 45.0397167044808 1646</gx:coord>
+ <gx:coord>-92.9503058450392 45.0396542676205 1615</gx:coord>
+ <gx:coord>-92.9570389363135 45.0394266771585 1585</gx:coord>
+ <gx:coord>-92.9637736326563 45.0390859598898 1554</gx:coord>
+ <gx:coord>-92.9705134597343 45.0387846980464 1524</gx:coord>
+ <gx:coord>-92.973755360354 45.0384258824988 1508.5</gx:coord>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <speed>235</speed>
+ <speed>246</speed>
+ <speed>239</speed>
+ <speed>244</speed>
+ <speed>234</speed>
+ <speed>232</speed>
+ <speed>238</speed>
+ <speed>227</speed>
+ <speed>228</speed>
+ <speed>229</speed>
+ <speed>229</speed>
+ <speed>232</speed>
+ <speed>236</speed>
+ <speed>238</speed>
+ <speed>249</speed>
+ <speed>243</speed>
+ <speed>245</speed>
+ <speed>255</speed>
+ <speed>247</speed>
+ <speed>248</speed>
+ <speed>248</speed>
+ <speed>247</speed>
+ <speed>256</speed>
+ <speed>247</speed>
+ <speed>246</speed>
+ <speed>254</speed>
+ <speed>244</speed>
+ <speed>245</speed>
+ <speed>242</speed>
+ <speed>239</speed>
+ <speed>246</speed>
+ <speed>235</speed>
+ <speed>232</speed>
+ <speed>240</speed>
+ <speed>231</speed>
+ <speed>232</speed>
+ <speed>234</speed>
+ <speed>234</speed>
+ <speed>233</speed>
+ <speed>232</speed>
+ <speed>233</speed>
+ <speed>232</speed>
+ <speed>229</speed>
+ <speed>229</speed>
+ <speed>237</speed>
+ <speed>227</speed>
+ <speed>225</speed>
+ <speed>233</speed>
+ <speed>224</speed>
+ <speed>225</speed>
+ <speed>228</speed>
+ <speed>228</speed>
+ <speed>240</speed>
+ <speed>233</speed>
+ <speed>236</speed>
+ <speed>248</speed>
+ <speed>243</speed>
+ <speed>246</speed>
+ <speed>250</speed>
+ <speed>253</speed>
+ <speed>255</speed>
+ <speed>257</speed>
+ <speed>257</speed>
+ <speed>266</speed>
+ <speed>261</speed>
+ <speed>265</speed>
+ <speed>275</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>E170</name>
+ <adflag>A</adflag>
+ <flightid>CPZ5695</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:11-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:25-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:48-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:02-05</when>
+ <when>2010-05-01T13:01:06-05</when>
+ <when>2010-05-01T13:01:11-05</when>
+ <when>2010-05-01T13:01:16-05</when>
+ <when>2010-05-01T13:01:20-05</when>
+ <when>2010-05-01T13:01:25-05</when>
+ <when>2010-05-01T13:01:29-05</when>
+ <when>2010-05-01T13:01:34-05</when>
+ <when>2010-05-01T13:01:39-05</when>
+ <when>2010-05-01T13:01:43-05</when>
+ <when>2010-05-01T13:01:48-05</when>
+ <when>2010-05-01T13:01:53-05</when>
+ <when>2010-05-01T13:01:57-05</when>
+ <when>2010-05-01T13:02:02-05</when>
+ <when>2010-05-01T13:02:06-05</when>
+ <when>2010-05-01T13:02:11-05</when>
+ <when>2010-05-01T13:02:16-05</when>
+ <when>2010-05-01T13:02:20-05</when>
+ <when>2010-05-01T13:02:25-05</when>
+ <when>2010-05-01T13:02:30-05</when>
+ <when>2010-05-01T13:02:34-05</when>
+ <when>2010-05-01T13:02:39-05</when>
+ <when>2010-05-01T13:02:44-05</when>
+ <when>2010-05-01T13:02:48-05</when>
+ <when>2010-05-01T13:02:53-05</when>
+ <when>2010-05-01T13:02:58-05</when>
+ <when>2010-05-01T13:03:02-05</when>
+ <when>2010-05-01T13:03:07-05</when>
+ <when>2010-05-01T13:03:11-05</when>
+ <when>2010-05-01T13:03:16-05</when>
+ <when>2010-05-01T13:03:21-05</when>
+ <when>2010-05-01T13:03:25-05</when>
+ <when>2010-05-01T13:03:30-05</when>
+ <when>2010-05-01T13:03:35-05</when>
+ <when>2010-05-01T13:03:39-05</when>
+ <when>2010-05-01T13:03:44-05</when>
+ <when>2010-05-01T13:03:48-05</when>
+ <when>2010-05-01T13:03:53-05</when>
+ <when>2010-05-01T13:03:58-05</when>
+ <when>2010-05-01T13:04:02-05</when>
+ <when>2010-05-01T13:04:07-05</when>
+ <when>2010-05-01T13:04:12-05</when>
+ <when>2010-05-01T13:04:16-05</when>
+ <when>2010-05-01T13:04:21-05</when>
+ <when>2010-05-01T13:04:25-05</when>
+ <when>2010-05-01T13:04:30-05</when>
+ <when>2010-05-01T13:04:35-05</when>
+ <when>2010-05-01T13:04:39-05</when>
+ <when>2010-05-01T13:04:44-05</when>
+ <when>2010-05-01T13:04:49-05</when>
+ <when>2010-05-01T13:04:53-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.3689380245182 45.0389467469425 2804</gx:coord>
+ <gx:coord>-92.3759530819834 45.0380951007958 2773</gx:coord>
+ <gx:coord>-92.3831159633175 45.0369957486846 2712</gx:coord>
+ <gx:coord>-92.3901362714549 45.0355238496347 2651</gx:coord>
+ <gx:coord>-92.3970814910858 45.0339385808083 2621</gx:coord>
+ <gx:coord>-92.4043121546626 45.032585906621 2560</gx:coord>
+ <gx:coord>-92.4118367565321 45.0319048652958 2499</gx:coord>
+ <gx:coord>-92.419078934653 45.030875157485 2469</gx:coord>
+ <gx:coord>-92.4262095560369 45.0291153314744 2438</gx:coord>
+ <gx:coord>-92.4335237384463 45.0273941113051 2438</gx:coord>
+ <gx:coord>-92.4408178608932 45.0260076351757 2438</gx:coord>
+ <gx:coord>-92.4480506692593 45.0250407396261 2438</gx:coord>
+ <gx:coord>-92.4553504288427 45.0241919539362 2438</gx:coord>
+ <gx:coord>-92.4628196268122 45.0233514202756 2438</gx:coord>
+ <gx:coord>-92.4702544151504 45.0225228770055 2438</gx:coord>
+ <gx:coord>-92.47749082249 45.0211454469826 2438</gx:coord>
+ <gx:coord>-92.4849952170224 45.020108381381 2438</gx:coord>
+ <gx:coord>-92.4924975545976 45.0191930140492 2438</gx:coord>
+ <gx:coord>-92.4998773018653 45.018051767506 2438</gx:coord>
+ <gx:coord>-92.507186344501 45.0168407571941 2438</gx:coord>
+ <gx:coord>-92.5143825240876 45.0156216694574 2438</gx:coord>
+ <gx:coord>-92.5215706342598 45.0143945866018 2438</gx:coord>
+ <gx:coord>-92.5287558465591 45.0131646175633 2408</gx:coord>
+ <gx:coord>-92.535858877656 45.0118804989009 2438</gx:coord>
+ <gx:coord>-92.5428413996463 45.0103972607613 2438</gx:coord>
+ <gx:coord>-92.5499799537839 45.0091469907013 2438</gx:coord>
+ <gx:coord>-92.5571487214372 45.0079107943641 2438</gx:coord>
+ <gx:coord>-92.5643503087637 45.0069312146329 2438</gx:coord>
+ <gx:coord>-92.5715906639656 45.0060256188488 2438</gx:coord>
+ <gx:coord>-92.5787232800865 45.0051593960756 2438</gx:coord>
+ <gx:coord>-92.5859075456731 45.0042853983707 2438</gx:coord>
+ <gx:coord>-92.5932558590921 45.0033774426771 2438</gx:coord>
+ <gx:coord>-92.6008071462461 45.003154553905 2438</gx:coord>
+ <gx:coord>-92.6083537686074 45.0033879703399 2438</gx:coord>
+ <gx:coord>-92.6158581079963 45.0039900406543 2438</gx:coord>
+ <gx:coord>-92.6233760961899 45.0046768119547 2438</gx:coord>
+ <gx:coord>-92.6308149850999 45.0051419435105 2438</gx:coord>
+ <gx:coord>-92.6382172211892 45.0057401438498 2438</gx:coord>
+ <gx:coord>-92.6454696132537 45.005920412465 2438</gx:coord>
+ <gx:coord>-92.6528385211424 45.0061349890872 2438</gx:coord>
+ <gx:coord>-92.6604262143734 45.0071927884136 2438</gx:coord>
+ <gx:coord>-92.6679454156809 45.0082888895876 2438</gx:coord>
+ <gx:coord>-92.6753888547959 45.008928558351 2438</gx:coord>
+ <gx:coord>-92.6828869677601 45.0095857895273 2438</gx:coord>
+ <gx:coord>-92.6904366005728 45.0101503984089 2438</gx:coord>
+ <gx:coord>-92.6979032678841 45.0107232636276 2438</gx:coord>
+ <gx:coord>-92.7052708180676 45.0115414340457 2438</gx:coord>
+ <gx:coord>-92.7127263858549 45.0123186978698 2438</gx:coord>
+ <gx:coord>-92.7203010090271 45.0129672732945 2438</gx:coord>
+ <gx:coord>-92.7279385048165 45.0135255760157 2438</gx:coord>
+ <gx:coord>-92.7356653752599 45.0142972080147 2438</gx:coord>
+ <gx:coord>-92.7433569853567 45.0149059605824 2438</gx:coord>
+ <gx:coord>-92.7510393079923 45.0155634422272 2438</gx:coord>
+ <gx:coord>-92.7586012608679 45.0164147107502 2438</gx:coord>
+ <gx:coord>-92.7660563085583 45.0171035403725 2438</gx:coord>
+ <gx:coord>-92.7735654020359 45.0178109394289 2408</gx:coord>
+ <gx:coord>-92.7808966683949 45.0181973511467 2347</gx:coord>
+ <gx:coord>-92.7882227912656 45.0186079478789 2316</gx:coord>
+ <gx:coord>-92.7955583985804 45.0193002290468 2255</gx:coord>
+ <gx:coord>-92.802877137723 45.0198997944223 2194</gx:coord>
+ <gx:coord>-92.810330496953 45.0205558578153 2164</gx:coord>
+ <gx:coord>-92.8178805010647 45.0213805814075 2103</gx:coord>
+ <gx:coord>-92.8253364059255 45.0220160857506 2072</gx:coord>
+ <gx:coord>-92.8282952283228 45.0222965993536 2047.6</gx:coord>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <speed>277</speed>
+ <speed>288</speed>
+ <speed>283</speed>
+ <speed>291</speed>
+ <speed>283</speed>
+ <speed>284</speed>
+ <speed>298</speed>
+ <speed>288</speed>
+ <speed>288</speed>
+ <speed>286</speed>
+ <speed>287</speed>
+ <speed>287</speed>
+ <speed>286</speed>
+ <speed>286</speed>
+ <speed>299</speed>
+ <speed>289</speed>
+ <speed>289</speed>
+ <speed>299</speed>
+ <speed>287</speed>
+ <speed>286</speed>
+ <speed>284</speed>
+ <speed>282</speed>
+ <speed>292</speed>
+ <speed>281</speed>
+ <speed>281</speed>
+ <speed>291</speed>
+ <speed>280</speed>
+ <speed>280</speed>
+ <speed>281</speed>
+ <speed>282</speed>
+ <speed>283</speed>
+ <speed>284</speed>
+ <speed>286</speed>
+ <speed>287</speed>
+ <speed>287</speed>
+ <speed>286</speed>
+ <speed>295</speed>
+ <speed>285</speed>
+ <speed>286</speed>
+ <speed>297</speed>
+ <speed>287</speed>
+ <speed>289</speed>
+ <speed>290</speed>
+ <speed>288</speed>
+ <speed>298</speed>
+ <speed>288</speed>
+ <speed>289</speed>
+ <speed>302</speed>
+ <speed>292</speed>
+ <speed>294</speed>
+ <speed>294</speed>
+ <speed>294</speed>
+ <speed>304</speed>
+ <speed>290</speed>
+ <speed>288</speed>
+ <speed>297</speed>
+ <speed>284</speed>
+ <speed>284</speed>
+ <speed>284</speed>
+ <speed>285</speed>
+ <speed>286</speed>
+ <speed>278</speed>
+ <speed>282</speed>
+ <speed>288</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>DC95</name>
+ <adflag>A</adflag>
+ <flightid>DAL2858</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:03-05</when>
+ <when>2010-05-01T13:00:07-05</when>
+ <when>2010-05-01T13:00:12-05</when>
+ <when>2010-05-01T13:00:17-05</when>
+ <when>2010-05-01T13:00:21-05</when>
+ <when>2010-05-01T13:00:26-05</when>
+ <when>2010-05-01T13:00:30-05</when>
+ <when>2010-05-01T13:00:35-05</when>
+ <when>2010-05-01T13:00:40-05</when>
+ <when>2010-05-01T13:00:44-05</when>
+ <when>2010-05-01T13:00:49-05</when>
+ <when>2010-05-01T13:00:54-05</when>
+ <when>2010-05-01T13:00:58-05</when>
+ <when>2010-05-01T13:01:03-05</when>
+ <when>2010-05-01T13:01:07-05</when>
+ <when>2010-05-01T13:01:12-05</when>
+ <when>2010-05-01T13:01:17-05</when>
+ <when>2010-05-01T13:01:21-05</when>
+ <when>2010-05-01T13:01:26-05</when>
+ <when>2010-05-01T13:01:31-05</when>
+ <when>2010-05-01T13:01:35-05</when>
+ <when>2010-05-01T13:01:40-05</when>
+ <when>2010-05-01T13:01:45-05</when>
+ <when>2010-05-01T13:01:49-05</when>
+ <when>2010-05-01T13:01:54-05</when>
+ <when>2010-05-01T13:01:58-05</when>
+ <when>2010-05-01T13:02:03-05</when>
+ <when>2010-05-01T13:02:08-05</when>
+ <when>2010-05-01T13:02:12-05</when>
+ <when>2010-05-01T13:02:17-05</when>
+ <when>2010-05-01T13:02:22-05</when>
+ <when>2010-05-01T13:02:26-05</when>
+ <when>2010-05-01T13:02:31-05</when>
+ <when>2010-05-01T13:02:35-05</when>
+ <when>2010-05-01T13:02:40-05</when>
+ <when>2010-05-01T13:02:45-05</when>
+ <when>2010-05-01T13:02:49-05</when>
+ <when>2010-05-01T13:02:54-05</when>
+ <when>2010-05-01T13:02:59-05</when>
+ <when>2010-05-01T13:03:03-05</when>
+ <when>2010-05-01T13:03:08-05</when>
+ <when>2010-05-01T13:03:12-05</when>
+ <when>2010-05-01T13:03:17-05</when>
+ <when>2010-05-01T13:03:22-05</when>
+ <when>2010-05-01T13:03:26-05</when>
+ <when>2010-05-01T13:03:31-05</when>
+ <when>2010-05-01T13:03:36-05</when>
+ <when>2010-05-01T13:03:40-05</when>
+ <when>2010-05-01T13:03:45-05</when>
+ <when>2010-05-01T13:03:49-05</when>
+ <when>2010-05-01T13:03:54-05</when>
+ <when>2010-05-01T13:03:59-05</when>
+ <when>2010-05-01T13:04:03-05</when>
+ <when>2010-05-01T13:04:08-05</when>
+ <when>2010-05-01T13:04:12-05</when>
+ <when>2010-05-01T13:04:17-05</when>
+ <when>2010-05-01T13:04:22-05</when>
+ <when>2010-05-01T13:04:26-05</when>
+ <when>2010-05-01T13:04:31-05</when>
+ <when>2010-05-01T13:04:35-05</when>
+ <when>2010-05-01T13:04:40-05</when>
+ <when>2010-05-01T13:04:45-05</when>
+ <when>2010-05-01T13:04:50-05</when>
+ <when>2010-05-01T13:04:54-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.1962465696187 44.4584257162471 3078</gx:coord>
+ <gx:coord>-93.1954858158128 44.462643897726 3078</gx:coord>
+ <gx:coord>-93.1945524569257 44.4696206853623 3048</gx:coord>
+ <gx:coord>-93.1935347734104 44.4765680167011 3048</gx:coord>
+ <gx:coord>-93.1921548885013 44.4834366892852 3048</gx:coord>
+ <gx:coord>-93.1912787899895 44.4902740201102 3048</gx:coord>
+ <gx:coord>-93.190869393024 44.496999598511 3048</gx:coord>
+ <gx:coord>-93.190355669541 44.503701889363 3048</gx:coord>
+ <gx:coord>-93.1899042890233 44.510392533924 3048</gx:coord>
+ <gx:coord>-93.1894352972433 44.5171043633827 3048</gx:coord>
+ <gx:coord>-93.1887272976791 44.523838031578 3017</gx:coord>
+ <gx:coord>-93.1882343860587 44.5305421014878 2987</gx:coord>
+ <gx:coord>-93.1878483537445 44.5373007218153 2987</gx:coord>
+ <gx:coord>-93.187206305476 44.5440099500882 2956</gx:coord>
+ <gx:coord>-93.1868272718258 44.5507044137326 2956</gx:coord>
+ <gx:coord>-93.1868012917709 44.5573772972405 2926</gx:coord>
+ <gx:coord>-93.1866210269778 44.5640837167977 2895</gx:coord>
+ <gx:coord>-93.1864907616916 44.5708828364002 2865</gx:coord>
+ <gx:coord>-93.1863883659992 44.5775823065512 2865</gx:coord>
+ <gx:coord>-93.1863783383684 44.5842436541366 2834</gx:coord>
+ <gx:coord>-93.1864309457268 44.5909344741626 2804</gx:coord>
+ <gx:coord>-93.1861870344 44.5974636699094 2804</gx:coord>
+ <gx:coord>-93.1859399656477 44.6039556552385 2804</gx:coord>
+ <gx:coord>-93.1853781106637 44.6104625660741 2773</gx:coord>
+ <gx:coord>-93.1842558921345 44.6168860904061 2743</gx:coord>
+ <gx:coord>-93.1824878787618 44.6232658876223 2712</gx:coord>
+ <gx:coord>-93.1803879773166 44.6294813300019 2743</gx:coord>
+ <gx:coord>-93.1780367881352 44.6355848757922 2743</gx:coord>
+ <gx:coord>-93.1752316985335 44.6415358145216 2743</gx:coord>
+ <gx:coord>-93.1723853204738 44.6473610477966 2743</gx:coord>
+ <gx:coord>-93.1695650439908 44.6531642714264 2743</gx:coord>
+ <gx:coord>-93.1665274417428 44.6589294401132 2743</gx:coord>
+ <gx:coord>-93.163312582578 44.6647085135481 2743</gx:coord>
+ <gx:coord>-93.160128277284 44.6704265732562 2743</gx:coord>
+ <gx:coord>-93.1572001510497 44.6760520191633 2743</gx:coord>
+ <gx:coord>-93.1543945309268 44.6816953047965 2743</gx:coord>
+ <gx:coord>-93.1513717350775 44.6874085817504 2743</gx:coord>
+ <gx:coord>-93.148373004873 44.693058643812 2743</gx:coord>
+ <gx:coord>-93.1453860883093 44.6986645847547 2743</gx:coord>
+ <gx:coord>-93.1421804531017 44.7042897996493 2743</gx:coord>
+ <gx:coord>-93.1388918899721 44.7099624804852 2743</gx:coord>
+ <gx:coord>-93.1358117624936 44.7156532681924 2743</gx:coord>
+ <gx:coord>-93.1330575833882 44.7212682920708 2743</gx:coord>
+ <gx:coord>-93.1302162164891 44.7268585149398 2743</gx:coord>
+ <gx:coord>-93.1271891227658 44.7324687008066 2743</gx:coord>
+ <gx:coord>-93.1242151781308 44.7380337584283 2743</gx:coord>
+ <gx:coord>-93.1211166531293 44.7436002967353 2743</gx:coord>
+ <gx:coord>-93.1178719942563 44.7492107287761 2743</gx:coord>
+ <gx:coord>-93.1146752953943 44.7548599499827 2743</gx:coord>
+ <gx:coord>-93.1117422413574 44.7605559725452 2743</gx:coord>
+ <gx:coord>-93.1091424380409 44.7663214899376 2743</gx:coord>
+ <gx:coord>-93.1066566399229 44.7720715320148 2743</gx:coord>
+ <gx:coord>-93.1040152138285 44.7778692510771 2743</gx:coord>
+ <gx:coord>-93.1012154435684 44.7836013270224 2743</gx:coord>
+ <gx:coord>-93.0982479017436 44.7892173348525 2743</gx:coord>
+ <gx:coord>-93.0950640890821 44.7947430846626 2743</gx:coord>
+ <gx:coord>-93.0915034480367 44.800094039287 2743</gx:coord>
+ <gx:coord>-93.0873387008124 44.8052382540424 2743</gx:coord>
+ <gx:coord>-93.0825976468131 44.8101709774442 2743</gx:coord>
+ <gx:coord>-93.0776830792116 44.815032321238 2773</gx:coord>
+ <gx:coord>-93.0728317182526 44.8197880022073 2773</gx:coord>
+ <gx:coord>-93.0680578728105 44.8244689148117 2773</gx:coord>
+ <gx:coord>-93.0633853777291 44.829181080911 2743</gx:coord>
+ <gx:coord>-93.0589797309512 44.8338258031244 2743</gx:coord>
+ <gx:coord>-93.0546552480593 44.8384413086509 2743</gx:coord>
+ <gx:coord>-93.0501805533684 44.8430463359799 2743</gx:coord>
+ <gx:coord>-93.0484252769533 44.8448678241347 2743</gx:coord>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>40 0 0</gx:angles>
+ <gx:angles>40 0 0</gx:angles>
+ <gx:angles>40 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <gx:angles>30 0 0</gx:angles>
+ <speed>378</speed>
+ <speed>370</speed>
+ <speed>381</speed>
+ <speed>373</speed>
+ <speed>384</speed>
+ <speed>367</speed>
+ <speed>365</speed>
+ <speed>377</speed>
+ <speed>362</speed>
+ <speed>362</speed>
+ <speed>362</speed>
+ <speed>362</speed>
+ <speed>376</speed>
+ <speed>361</speed>
+ <speed>362</speed>
+ <speed>375</speed>
+ <speed>361</speed>
+ <speed>361</speed>
+ <speed>359</speed>
+ <speed>358</speed>
+ <speed>355</speed>
+ <speed>353</speed>
+ <speed>352</speed>
+ <speed>362</speed>
+ <speed>347</speed>
+ <speed>346</speed>
+ <speed>355</speed>
+ <speed>339</speed>
+ <speed>336</speed>
+ <speed>335</speed>
+ <speed>333</speed>
+ <speed>343</speed>
+ <speed>329</speed>
+ <speed>329</speed>
+ <speed>340</speed>
+ <speed>325</speed>
+ <speed>325</speed>
+ <speed>326</speed>
+ <speed>327</speed>
+ <speed>338</speed>
+ <speed>325</speed>
+ <speed>325</speed>
+ <speed>336</speed>
+ <speed>322</speed>
+ <speed>322</speed>
+ <speed>324</speed>
+ <speed>325</speed>
+ <speed>338</speed>
+ <speed>326</speed>
+ <speed>327</speed>
+ <speed>339</speed>
+ <speed>326</speed>
+ <speed>337</speed>
+ <speed>324</speed>
+ <speed>323</speed>
+ <speed>334</speed>
+ <speed>321</speed>
+ <speed>332</speed>
+ <speed>318</speed>
+ <speed>317</speed>
+ <speed>314</speed>
+ <speed>310</speed>
+ <speed>318</speed>
+ <speed>303</speed>
+ <speed>306</speed>
+ <speed>311</speed>
+ <speed>322</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>B737</name>
+ <adflag>A</adflag>
+ <flightid>SWA1488</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:11-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:48-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:01-05</when>
+ <when>2010-05-01T13:01:06-05</when>
+ <when>2010-05-01T13:01:11-05</when>
+ <when>2010-05-01T13:01:15-05</when>
+ <when>2010-05-01T13:01:20-05</when>
+ <when>2010-05-01T13:01:25-05</when>
+ <when>2010-05-01T13:01:29-05</when>
+ <when>2010-05-01T13:01:34-05</when>
+ <when>2010-05-01T13:01:38-05</when>
+ <when>2010-05-01T13:01:43-05</when>
+ <when>2010-05-01T13:01:48-05</when>
+ <when>2010-05-01T13:01:52-05</when>
+ <when>2010-05-01T13:01:57-05</when>
+ <when>2010-05-01T13:02:02-05</when>
+ <when>2010-05-01T13:02:06-05</when>
+ <when>2010-05-01T13:02:11-05</when>
+ <when>2010-05-01T13:02:15-05</when>
+ <when>2010-05-01T13:02:20-05</when>
+ <when>2010-05-01T13:02:25-05</when>
+ <when>2010-05-01T13:02:29-05</when>
+ <when>2010-05-01T13:02:34-05</when>
+ <when>2010-05-01T13:02:39-05</when>
+ <when>2010-05-01T13:02:43-05</when>
+ <when>2010-05-01T13:02:48-05</when>
+ <when>2010-05-01T13:02:53-05</when>
+ <when>2010-05-01T13:02:57-05</when>
+ <when>2010-05-01T13:03:02-05</when>
+ <when>2010-05-01T13:03:06-05</when>
+ <when>2010-05-01T13:03:11-05</when>
+ <when>2010-05-01T13:03:16-05</when>
+ <when>2010-05-01T13:03:20-05</when>
+ <when>2010-05-01T13:03:25-05</when>
+ <when>2010-05-01T13:03:30-05</when>
+ <when>2010-05-01T13:03:34-05</when>
+ <when>2010-05-01T13:03:39-05</when>
+ <when>2010-05-01T13:03:44-05</when>
+ <when>2010-05-01T13:03:48-05</when>
+ <when>2010-05-01T13:03:53-05</when>
+ <when>2010-05-01T13:03:57-05</when>
+ <when>2010-05-01T13:04:02-05</when>
+ <when>2010-05-01T13:04:07-05</when>
+ <when>2010-05-01T13:04:11-05</when>
+ <when>2010-05-01T13:04:16-05</when>
+ <when>2010-05-01T13:04:21-05</when>
+ <when>2010-05-01T13:04:25-05</when>
+ <when>2010-05-01T13:04:30-05</when>
+ <when>2010-05-01T13:04:35-05</when>
+ <when>2010-05-01T13:04:39-05</when>
+ <when>2010-05-01T13:04:44-05</when>
+ <when>2010-05-01T13:04:49-05</when>
+ <when>2010-05-01T13:04:53-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.7436038977339 45.0176449723009 2438</gx:coord>
+ <gx:coord>-92.745419752639 45.0178405701636 2438</gx:coord>
+ <gx:coord>-92.7525586927583 45.0181852080204 2438</gx:coord>
+ <gx:coord>-92.7599978682742 45.0189437491361 2438</gx:coord>
+ <gx:coord>-92.7673964649616 45.0200176804669 2438</gx:coord>
+ <gx:coord>-92.7743047878147 45.0206512321095 2438</gx:coord>
+ <gx:coord>-92.7812211106102 45.0212438545962 2438</gx:coord>
+ <gx:coord>-92.7880905786106 45.0219352711124 2438</gx:coord>
+ <gx:coord>-92.7948110303679 45.0225135550872 2438</gx:coord>
+ <gx:coord>-92.8016256231407 45.0231539091809 2377</gx:coord>
+ <gx:coord>-92.808436321378 45.0237782407713 2316</gx:coord>
+ <gx:coord>-92.8153060032773 45.0245123996427 2255</gx:coord>
+ <gx:coord>-92.8220950756464 45.0250388052127 2194</gx:coord>
+ <gx:coord>-92.8289929014999 45.0256725515916 2164</gx:coord>
+ <gx:coord>-92.8360303531199 45.0266058986232 2103</gx:coord>
+ <gx:coord>-92.8429329578141 45.0273764305379 2072</gx:coord>
+ <gx:coord>-92.8498901242601 45.0280031718838 2011</gx:coord>
+ <gx:coord>-92.8570769257727 45.0288350738651 1981</gx:coord>
+ <gx:coord>-92.8642468830706 45.0297437485852 1920</gx:coord>
+ <gx:coord>-92.87096733955 45.0302316004222 1859</gx:coord>
+ <gx:coord>-92.8776991433842 45.0308036595577 1828</gx:coord>
+ <gx:coord>-92.8848051869188 45.0317355139572 1768</gx:coord>
+ <gx:coord>-92.891849836226 45.032372254553 1737</gx:coord>
+ <gx:coord>-92.8988806858275 45.0330472653869 1676</gx:coord>
+ <gx:coord>-92.9059183042329 45.0336591058208 1646</gx:coord>
+ <gx:coord>-92.9127864875957 45.0340529790218 1554</gx:coord>
+ <gx:coord>-92.9198394657117 45.0347605723218 1554</gx:coord>
+ <gx:coord>-92.9271188759936 45.0355320490291 1493</gx:coord>
+ <gx:coord>-92.9342496165443 45.0361866089878 1463</gx:coord>
+ <gx:coord>-92.9413321497396 45.0366031935849 1402</gx:coord>
+ <gx:coord>-92.9482307097935 45.0364375819171 1371</gx:coord>
+ <gx:coord>-92.9549267830033 45.0357359075476 1341</gx:coord>
+ <gx:coord>-92.9616308114574 45.0349106615543 1310</gx:coord>
+ <gx:coord>-92.9680840982828 45.0340026299843 1280</gx:coord>
+ <gx:coord>-92.9744518648424 45.0330474137801 1280</gx:coord>
+ <gx:coord>-92.9808447078198 45.0322448064613 1249</gx:coord>
+ <gx:coord>-92.9869393112267 45.0312693675023 1219</gx:coord>
+ <gx:coord>-92.9930579883147 45.0303271096009 1219</gx:coord>
+ <gx:coord>-92.9991883691893 45.0295800716662 1219</gx:coord>
+ <gx:coord>-93.0050223477826 45.028724083281 1219</gx:coord>
+ <gx:coord>-93.010614076045 45.0278629900138 1219</gx:coord>
+ <gx:coord>-93.0160206405037 45.0268346460011 1219</gx:coord>
+ <gx:coord>-93.0211552000865 45.0253145800507 1219</gx:coord>
+ <gx:coord>-93.0258637412524 45.0233023458284 1219</gx:coord>
+ <gx:coord>-93.0300671724338 45.0208133465794 1219</gx:coord>
+ <gx:coord>-93.0339928023023 45.0180815293661 1219</gx:coord>
+ <gx:coord>-93.0378123650471 45.015386905955 1219</gx:coord>
+ <gx:coord>-93.0413573567597 45.0126147468646 1219</gx:coord>
+ <gx:coord>-93.0448863339261 45.0099395682965 1219</gx:coord>
+ <gx:coord>-93.0485234513263 45.0073532174657 1219</gx:coord>
+ <gx:coord>-93.0521310871894 45.0048422081768 1219</gx:coord>
+ <gx:coord>-93.0555350014272 45.0023982293894 1219</gx:coord>
+ <gx:coord>-93.0589786824276 45.0000288885742 1188</gx:coord>
+ <gx:coord>-93.0623077105646 44.9977133640953 1188</gx:coord>
+ <gx:coord>-93.065360230814 44.995356896404 1158</gx:coord>
+ <gx:coord>-93.0685763415021 44.9931569267686 1158</gx:coord>
+ <gx:coord>-93.0718407580212 44.9911674357548 1097</gx:coord>
+ <gx:coord>-93.0748577258473 44.9891037291536 1066</gx:coord>
+ <gx:coord>-93.0778092168993 44.9869633801591 1036</gx:coord>
+ <gx:coord>-93.0808539061589 44.9848563483924 1006</gx:coord>
+ <gx:coord>-93.0836846650629 44.9827278139486 975</gx:coord>
+ <gx:coord>-93.0863847135489 44.9806419407598 945</gx:coord>
+ <gx:coord>-93.0891432094711 44.978586338985 945</gx:coord>
+ <gx:coord>-93.0918882385755 44.9764807737863 945</gx:coord>
+ <gx:coord>-93.0946313764692 44.9743266948072 914</gx:coord>
+ <gx:coord>-93.0974123770403 44.9722534220515 914</gx:coord>
+ <gx:coord>-93.0987847859357 44.9712598545857 899</gx:coord>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>240 0 0</gx:angles>
+ <gx:angles>240 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <speed>280</speed>
+ <speed>293</speed>
+ <speed>284</speed>
+ <speed>288</speed>
+ <speed>274</speed>
+ <speed>272</speed>
+ <speed>279</speed>
+ <speed>263</speed>
+ <speed>263</speed>
+ <speed>262</speed>
+ <speed>262</speed>
+ <speed>275</speed>
+ <speed>266</speed>
+ <speed>267</speed>
+ <speed>279</speed>
+ <speed>272</speed>
+ <speed>271</speed>
+ <speed>268</speed>
+ <speed>269</speed>
+ <speed>280</speed>
+ <speed>269</speed>
+ <speed>267</speed>
+ <speed>278</speed>
+ <speed>270</speed>
+ <speed>271</speed>
+ <speed>272</speed>
+ <speed>272</speed>
+ <speed>281</speed>
+ <speed>270</speed>
+ <speed>268</speed>
+ <speed>273</speed>
+ <speed>259</speed>
+ <speed>255</speed>
+ <speed>250</speed>
+ <speed>247</speed>
+ <speed>244</speed>
+ <speed>239</speed>
+ <speed>235</speed>
+ <speed>238</speed>
+ <speed>224</speed>
+ <speed>220</speed>
+ <speed>224</speed>
+ <speed>212</speed>
+ <speed>210</speed>
+ <speed>208</speed>
+ <speed>206</speed>
+ <speed>204</speed>
+ <speed>200</speed>
+ <speed>197</speed>
+ <speed>200</speed>
+ <speed>189</speed>
+ <speed>185</speed>
+ <speed>188</speed>
+ <speed>176</speed>
+ <speed>172</speed>
+ <speed>168</speed>
+ <speed>165</speed>
+ <speed>163</speed>
+ <speed>159</speed>
+ <speed>158</speed>
+ <speed>157</speed>
+ <speed>156</speed>
+ <speed>155</speed>
+ <speed>159</speed>
+ <speed>156</speed>
+ <speed>160</speed>
+ <speed>165</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>CRJ2</name>
+ <adflag>A</adflag>
+ <flightid>MES3237</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:02:11-05</when>
+ <when>2010-05-01T13:02:16-05</when>
+ <when>2010-05-01T13:02:20-05</when>
+ <when>2010-05-01T13:02:25-05</when>
+ <when>2010-05-01T13:02:30-05</when>
+ <when>2010-05-01T13:02:34-05</when>
+ <when>2010-05-01T13:02:39-05</when>
+ <when>2010-05-01T13:02:44-05</when>
+ <when>2010-05-01T13:02:48-05</when>
+ <when>2010-05-01T13:02:53-05</when>
+ <when>2010-05-01T13:02:58-05</when>
+ <when>2010-05-01T13:03:02-05</when>
+ <when>2010-05-01T13:03:07-05</when>
+ <when>2010-05-01T13:03:11-05</when>
+ <when>2010-05-01T13:03:16-05</when>
+ <when>2010-05-01T13:03:21-05</when>
+ <when>2010-05-01T13:03:25-05</when>
+ <when>2010-05-01T13:03:30-05</when>
+ <when>2010-05-01T13:03:35-05</when>
+ <when>2010-05-01T13:03:39-05</when>
+ <when>2010-05-01T13:03:44-05</when>
+ <when>2010-05-01T13:03:49-05</when>
+ <when>2010-05-01T13:03:53-05</when>
+ <when>2010-05-01T13:03:58-05</when>
+ <when>2010-05-01T13:04:02-05</when>
+ <when>2010-05-01T13:04:07-05</when>
+ <when>2010-05-01T13:04:12-05</when>
+ <when>2010-05-01T13:04:16-05</when>
+ <when>2010-05-01T13:04:21-05</when>
+ <when>2010-05-01T13:04:26-05</when>
+ <when>2010-05-01T13:04:30-05</when>
+ <when>2010-05-01T13:04:35-05</when>
+ <when>2010-05-01T13:04:39-05</when>
+ <when>2010-05-01T13:04:44-05</when>
+ <when>2010-05-01T13:04:49-05</when>
+ <when>2010-05-01T13:04:53-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.3654525809466 45.0395326832503 2865</gx:coord>
+ <gx:coord>-92.3722148453194 45.0383957360594 2804</gx:coord>
+ <gx:coord>-92.3789968405533 45.0372550297716 2743</gx:coord>
+ <gx:coord>-92.3857717937338 45.035790850493 2712</gx:coord>
+ <gx:coord>-92.3928268643983 45.0344988210948 2651</gx:coord>
+ <gx:coord>-92.4001469312933 45.033871851974 2560</gx:coord>
+ <gx:coord>-92.4074037761026 45.0334790794383 2530</gx:coord>
+ <gx:coord>-92.4143764863149 45.0325702739764 2469</gx:coord>
+ <gx:coord>-92.4212444848282 45.0311814951256 2438</gx:coord>
+ <gx:coord>-92.4279010170141 45.0296690432212 2438</gx:coord>
+ <gx:coord>-92.4345565361906 45.0284146419212 2438</gx:coord>
+ <gx:coord>-92.4413302267182 45.0272251811423 2438</gx:coord>
+ <gx:coord>-92.4480836488953 45.0260669070124 2438</gx:coord>
+ <gx:coord>-92.4551038107528 45.0251701649012 2438</gx:coord>
+ <gx:coord>-92.4620867595928 45.0240801934066 2469</gx:coord>
+ <gx:coord>-92.4688445943801 45.0226696219111 2438</gx:coord>
+ <gx:coord>-92.475770828578 45.0214068742927 2438</gx:coord>
+ <gx:coord>-92.4828770275976 45.0207461539528 2438</gx:coord>
+ <gx:coord>-92.4898573176066 45.0197355515252 2438</gx:coord>
+ <gx:coord>-92.4967644710332 45.0183323474054 2438</gx:coord>
+ <gx:coord>-92.5037089663701 45.0170733409348 2438</gx:coord>
+ <gx:coord>-92.5107248421742 45.0161283120616 2438</gx:coord>
+ <gx:coord>-92.5176434168212 45.0153021033734 2438</gx:coord>
+ <gx:coord>-92.5246160799064 45.014766408047 2438</gx:coord>
+ <gx:coord>-92.5318394590384 45.0148138551787 2438</gx:coord>
+ <gx:coord>-92.5390862704917 45.0145094336569 2438</gx:coord>
+ <gx:coord>-92.5463963852328 45.0143318745824 2438</gx:coord>
+ <gx:coord>-92.5536631015454 45.0147147936455 2438</gx:coord>
+ <gx:coord>-92.5607664550331 45.014765244052 2438</gx:coord>
+ <gx:coord>-92.5679894227165 45.0149152572076 2438</gx:coord>
+ <gx:coord>-92.5752195833593 45.0150827900687 2438</gx:coord>
+ <gx:coord>-92.582448917158 45.015235432103 2438</gx:coord>
+ <gx:coord>-92.5897932283758 45.0155897891663 2438</gx:coord>
+ <gx:coord>-92.5970842773637 45.0160017913126 2438</gx:coord>
+ <gx:coord>-92.6042355431238 45.0161855996339 2438</gx:coord>
+ <gx:coord>-92.6114545174405 45.0162505274554 2438</gx:coord>
+ <gx:coord>-92.6187520939916 45.0164837409472 2438</gx:coord>
+ <gx:coord>-92.6216565981247 45.0165937676212 2438</gx:coord>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <speed>267</speed>
+ <speed>265</speed>
+ <speed>277</speed>
+ <speed>273</speed>
+ <speed>274</speed>
+ <speed>275</speed>
+ <speed>275</speed>
+ <speed>272</speed>
+ <speed>270</speed>
+ <speed>268</speed>
+ <speed>278</speed>
+ <speed>268</speed>
+ <speed>268</speed>
+ <speed>280</speed>
+ <speed>271</speed>
+ <speed>272</speed>
+ <speed>273</speed>
+ <speed>273</speed>
+ <speed>273</speed>
+ <speed>272</speed>
+ <speed>271</speed>
+ <speed>283</speed>
+ <speed>273</speed>
+ <speed>273</speed>
+ <speed>285</speed>
+ <speed>275</speed>
+ <speed>276</speed>
+ <speed>276</speed>
+ <speed>276</speed>
+ <speed>287</speed>
+ <speed>277</speed>
+ <speed>277</speed>
+ <speed>288</speed>
+ <speed>278</speed>
+ <speed>278</speed>
+ <speed>268</speed>
+ <speed>271</speed>
+ <speed>277</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>A318</name>
+ <adflag>A</adflag>
+ <flightid>FFT106</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:05-05</when>
+ <when>2010-05-01T13:00:09-05</when>
+ <when>2010-05-01T13:00:14-05</when>
+ <when>2010-05-01T13:00:19-05</when>
+ <when>2010-05-01T13:00:23-05</when>
+ <when>2010-05-01T13:00:28-05</when>
+ <when>2010-05-01T13:00:33-05</when>
+ <when>2010-05-01T13:00:37-05</when>
+ <when>2010-05-01T13:00:42-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:51-05</when>
+ <when>2010-05-01T13:00:56-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <when>2010-05-01T13:01:05-05</when>
+ <when>2010-05-01T13:01:10-05</when>
+ <when>2010-05-01T13:01:14-05</when>
+ <when>2010-05-01T13:01:19-05</when>
+ <when>2010-05-01T13:01:24-05</when>
+ <when>2010-05-01T13:01:28-05</when>
+ <when>2010-05-01T13:01:33-05</when>
+ <when>2010-05-01T13:01:37-05</when>
+ <when>2010-05-01T13:01:42-05</when>
+ <when>2010-05-01T13:01:47-05</when>
+ <when>2010-05-01T13:01:51-05</when>
+ <when>2010-05-01T13:01:56-05</when>
+ <when>2010-05-01T13:02:01-05</when>
+ <when>2010-05-01T13:02:05-05</when>
+ <when>2010-05-01T13:02:10-05</when>
+ <when>2010-05-01T13:02:15-05</when>
+ <when>2010-05-01T13:02:19-05</when>
+ <when>2010-05-01T13:02:24-05</when>
+ <when>2010-05-01T13:02:28-05</when>
+ <when>2010-05-01T13:02:33-05</when>
+ <when>2010-05-01T13:02:38-05</when>
+ <when>2010-05-01T13:02:42-05</when>
+ <when>2010-05-01T13:02:47-05</when>
+ <when>2010-05-01T13:02:52-05</when>
+ <when>2010-05-01T13:02:56-05</when>
+ <when>2010-05-01T13:03:01-05</when>
+ <when>2010-05-01T13:03:06-05</when>
+ <when>2010-05-01T13:03:10-05</when>
+ <when>2010-05-01T13:03:15-05</when>
+ <when>2010-05-01T13:03:19-05</when>
+ <when>2010-05-01T13:03:24-05</when>
+ <when>2010-05-01T13:03:29-05</when>
+ <when>2010-05-01T13:03:33-05</when>
+ <when>2010-05-01T13:03:38-05</when>
+ <when>2010-05-01T13:03:43-05</when>
+ <when>2010-05-01T13:03:47-05</when>
+ <when>2010-05-01T13:03:52-05</when>
+ <when>2010-05-01T13:03:56-05</when>
+ <when>2010-05-01T13:04:01-05</when>
+ <when>2010-05-01T13:04:05-05</when>
+ <when>2010-05-01T13:04:10-05</when>
+ <when>2010-05-01T13:04:14-05</when>
+ <when>2010-05-01T13:04:18-05</when>
+ <when>2010-05-01T13:04:23-05</when>
+ <when>2010-05-01T13:04:27-05</when>
+ <when>2010-05-01T13:04:32-05</when>
+ <when>2010-05-01T13:04:37-05</when>
+ <when>2010-05-01T13:04:41-05</when>
+ <when>2010-05-01T13:04:46-05</when>
+ <when>2010-05-01T13:04:50-05</when>
+ <when>2010-05-01T13:04:55-05</when>
+ <when>2010-05-01T13:04:59-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.2974568508014 45.0687622602847 1432</gx:coord>
+ <gx:coord>-93.2934457905393 45.0660257042941 1371</gx:coord>
+ <gx:coord>-93.2902010482642 45.0627382200457 1341</gx:coord>
+ <gx:coord>-93.2880735868205 45.0592062737728 1280</gx:coord>
+ <gx:coord>-93.2866251180089 45.0556538417996 1280</gx:coord>
+ <gx:coord>-93.2855706436895 45.0521555770546 1249</gx:coord>
+ <gx:coord>-93.2848929213344 45.0486326683558 1249</gx:coord>
+ <gx:coord>-93.284149302237 45.0450445279501 1219</gx:coord>
+ <gx:coord>-93.2832681542582 45.0414770478452 1219</gx:coord>
+ <gx:coord>-93.2822163760078 45.0378266141909 1219</gx:coord>
+ <gx:coord>-93.2810695206555 45.0339762188888 1249</gx:coord>
+ <gx:coord>-93.2800852709943 45.0300242656845 1249</gx:coord>
+ <gx:coord>-93.2789451826991 45.026165428423 1249</gx:coord>
+ <gx:coord>-93.2776553627852 45.0222881273358 1219</gx:coord>
+ <gx:coord>-93.2762849051262 45.0183879412865 1219</gx:coord>
+ <gx:coord>-93.2750227859231 45.01452278975 1188</gx:coord>
+ <gx:coord>-93.2739788608525 45.0107480537055 1188</gx:coord>
+ <gx:coord>-93.27273416536 45.0071654180353 1158</gx:coord>
+ <gx:coord>-93.271440533456 45.0036211770402 1127</gx:coord>
+ <gx:coord>-93.2702510339155 45.0000676438878 1066</gx:coord>
+ <gx:coord>-93.2689856900965 44.9965088916327 1036</gx:coord>
+ <gx:coord>-93.2677450407515 44.9930289132183 1006</gx:coord>
+ <gx:coord>-93.2665628070763 44.9897678001495 975</gx:coord>
+ <gx:coord>-93.2654695900875 44.9865668331562 945</gx:coord>
+ <gx:coord>-93.2643275310433 44.9833330918205 914</gx:coord>
+ <gx:coord>-93.2631023843797 44.9801905024626 823</gx:coord>
+ <gx:coord>-93.2621060751847 44.9769860428905 823</gx:coord>
+ <gx:coord>-93.2613793333571 44.9737243608145 762</gx:coord>
+ <gx:coord>-93.2609358268711 44.970517162552 762</gx:coord>
+ <gx:coord>-93.260628015146 44.9674064044388 762</gx:coord>
+ <gx:coord>-93.2602996952247 44.9643597216492 731</gx:coord>
+ <gx:coord>-93.2599595576737 44.9613320303757 731</gx:coord>
+ <gx:coord>-93.2594994071955 44.9582185681901 701</gx:coord>
+ <gx:coord>-93.2589507888497 44.9549930481613 670</gx:coord>
+ <gx:coord>-93.2583578824759 44.9518211731838 670</gx:coord>
+ <gx:coord>-93.2577038531017 44.9485831657195 640</gx:coord>
+ <gx:coord>-93.2570809594468 44.9453063523228 609</gx:coord>
+ <gx:coord>-93.2563271653062 44.942138873467 609</gx:coord>
+ <gx:coord>-93.2554358149374 44.9390293085691 579</gx:coord>
+ <gx:coord>-93.2546255139468 44.9359025243045 579</gx:coord>
+ <gx:coord>-93.2538265267143 44.9327450699088 548</gx:coord>
+ <gx:coord>-93.2530252021259 44.9297128380021 548</gx:coord>
+ <gx:coord>-93.2522809727351 44.9267689034144 518</gx:coord>
+ <gx:coord>-93.2515035867768 44.9237188014152 487</gx:coord>
+ <gx:coord>-93.2506543465894 44.9207369723461 487</gx:coord>
+ <gx:coord>-93.2498548488919 44.9178124047958 457</gx:coord>
+ <gx:coord>-93.2489961276719 44.9148538675761 426</gx:coord>
+ <gx:coord>-93.2481063345252 44.9118432075909 426</gx:coord>
+ <gx:coord>-93.2475702164253 44.9090871778968 396</gx:coord>
+ <gx:coord>-93.2468054019883 44.9062896891392 365</gx:coord>
+ <gx:coord>-93.2459138821779 44.9031220636101 365</gx:coord>
+ <gx:coord>-93.2451839956313 44.9003646144392 335</gx:coord>
+ <gx:coord>-93.2442620734973 44.8974631820496 335</gx:coord>
+ <gx:coord>-93.2437934615496 44.8946084310426 335</gx:coord>
+ <gx:coord>-93.2430623256379 44.8915836945618 365</gx:coord>
+ <gx:coord>-93.2424772474959 44.8888394893853 426</gx:coord>
+ <gx:coord>-93.2417795129824 44.8858318116166 487</gx:coord>
+ <gx:coord>-93.2411065382114 44.882678391429 518</gx:coord>
+ <gx:coord>-93.2402313646157 44.879530182788 579</gx:coord>
+ <gx:coord>-93.2392009410817 44.8759747599643 609</gx:coord>
+ <gx:coord>-93.2377852820119 44.872769339825 670</gx:coord>
+ <gx:coord>-93.2363530715176 44.8696281486003 731</gx:coord>
+ <gx:coord>-93.23475664131 44.866270773938 762</gx:coord>
+ <gx:coord>-93.2331575993176 44.8629492601519 823</gx:coord>
+ <gx:coord>-93.2317272590921 44.8596791368118 853</gx:coord>
+ <gx:coord>-93.2301662617953 44.8564215369107 884</gx:coord>
+ <gx:coord>-93.2298549002314 44.8557795687872 884</gx:coord>
+ <gx:angles>140 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <speed>212</speed>
+ <speed>205</speed>
+ <speed>208</speed>
+ <speed>203</speed>
+ <speed>201</speed>
+ <speed>196</speed>
+ <speed>196</speed>
+ <speed>197</speed>
+ <speed>202</speed>
+ <speed>205</speed>
+ <speed>216</speed>
+ <speed>212</speed>
+ <speed>214</speed>
+ <speed>221</speed>
+ <speed>210</speed>
+ <speed>208</speed>
+ <speed>205</speed>
+ <speed>202</speed>
+ <speed>206</speed>
+ <speed>194</speed>
+ <speed>191</speed>
+ <speed>195</speed>
+ <speed>184</speed>
+ <speed>181</speed>
+ <speed>178</speed>
+ <speed>177</speed>
+ <speed>175</speed>
+ <speed>173</speed>
+ <speed>171</speed>
+ <speed>176</speed>
+ <speed>169</speed>
+ <speed>168</speed>
+ <speed>176</speed>
+ <speed>172</speed>
+ <speed>174</speed>
+ <speed>174</speed>
+ <speed>174</speed>
+ <speed>174</speed>
+ <speed>172</speed>
+ <speed>169</speed>
+ <speed>175</speed>
+ <speed>167</speed>
+ <speed>165</speed>
+ <speed>170</speed>
+ <speed>164</speed>
+ <speed>162</speed>
+ <speed>159</speed>
+ <speed>161</speed>
+ <speed>165</speed>
+ <speed>158</speed>
+ <speed>163</speed>
+ <speed>165</speed>
+ <speed>164</speed>
+ <speed>169</speed>
+ <speed>167</speed>
+ <speed>175</speed>
+ <speed>175</speed>
+ <speed>178</speed>
+ <speed>183</speed>
+ <speed>181</speed>
+ <speed>191</speed>
+ <speed>186</speed>
+ <speed>192</speed>
+ <speed>192</speed>
+ <speed>189</speed>
+ <speed>193</speed>
+ <speed>184</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>BE35</name>
+ <adflag>A</adflag>
+ <flightid>N46JJ</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:01:21-05</when>
+ <when>2010-05-01T13:01:26-05</when>
+ <when>2010-05-01T13:01:31-05</when>
+ <when>2010-05-01T13:01:35-05</when>
+ <when>2010-05-01T13:01:40-05</when>
+ <when>2010-05-01T13:01:44-05</when>
+ <when>2010-05-01T13:01:49-05</when>
+ <when>2010-05-01T13:01:54-05</when>
+ <when>2010-05-01T13:01:58-05</when>
+ <when>2010-05-01T13:02:03-05</when>
+ <when>2010-05-01T13:02:08-05</when>
+ <when>2010-05-01T13:02:12-05</when>
+ <when>2010-05-01T13:02:17-05</when>
+ <when>2010-05-01T13:02:22-05</when>
+ <when>2010-05-01T13:02:26-05</when>
+ <when>2010-05-01T13:02:31-05</when>
+ <when>2010-05-01T13:02:35-05</when>
+ <when>2010-05-01T13:02:40-05</when>
+ <when>2010-05-01T13:02:45-05</when>
+ <when>2010-05-01T13:02:49-05</when>
+ <when>2010-05-01T13:02:54-05</when>
+ <when>2010-05-01T13:02:59-05</when>
+ <when>2010-05-01T13:03:03-05</when>
+ <when>2010-05-01T13:03:08-05</when>
+ <when>2010-05-01T13:03:13-05</when>
+ <when>2010-05-01T13:03:17-05</when>
+ <when>2010-05-01T13:03:22-05</when>
+ <when>2010-05-01T13:03:26-05</when>
+ <when>2010-05-01T13:03:31-05</when>
+ <when>2010-05-01T13:03:36-05</when>
+ <when>2010-05-01T13:03:40-05</when>
+ <when>2010-05-01T13:03:45-05</when>
+ <when>2010-05-01T13:03:50-05</when>
+ <when>2010-05-01T13:03:54-05</when>
+ <when>2010-05-01T13:03:59-05</when>
+ <when>2010-05-01T13:04:04-05</when>
+ <when>2010-05-01T13:04:08-05</when>
+ <when>2010-05-01T13:04:13-05</when>
+ <when>2010-05-01T13:04:17-05</when>
+ <when>2010-05-01T13:04:22-05</when>
+ <when>2010-05-01T13:04:27-05</when>
+ <when>2010-05-01T13:04:31-05</when>
+ <when>2010-05-01T13:04:36-05</when>
+ <when>2010-05-01T13:04:41-05</when>
+ <when>2010-05-01T13:04:45-05</when>
+ <when>2010-05-01T13:04:50-05</when>
+ <when>2010-05-01T13:04:55-05</when>
+ <when>2010-05-01T13:04:59-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.9339221048924 44.2950315742565 1524</gx:coord>
+ <gx:coord>-92.9350064014678 44.2979570591066 1524</gx:coord>
+ <gx:coord>-92.937652997869 44.3004478107577 1524</gx:coord>
+ <gx:coord>-92.9407116824041 44.302846514598 1524</gx:coord>
+ <gx:coord>-92.9430921358657 44.3054902041603 1524</gx:coord>
+ <gx:coord>-92.9452136372834 44.308154578993 1524</gx:coord>
+ <gx:coord>-92.9479783757094 44.310667507076 1524</gx:coord>
+ <gx:coord>-92.9505645579644 44.3132980584321 1524</gx:coord>
+ <gx:coord>-92.953176931421 44.3159244553921 1524</gx:coord>
+ <gx:coord>-92.955790238918 44.3185524033008 1524</gx:coord>
+ <gx:coord>-92.9581111706922 44.3212990950149 1524</gx:coord>
+ <gx:coord>-92.9605941160522 44.3239309610271 1524</gx:coord>
+ <gx:coord>-92.9634150903891 44.3264339577567 1524</gx:coord>
+ <gx:coord>-92.9661669042714 44.3290084280208 1524</gx:coord>
+ <gx:coord>-92.9689057275993 44.3316511959644 1524</gx:coord>
+ <gx:coord>-92.9719191039836 44.3342289723207 1524</gx:coord>
+ <gx:coord>-92.9745609220571 44.3367593382531 1524</gx:coord>
+ <gx:coord>-92.9767073261514 44.3394303305052 1524</gx:coord>
+ <gx:coord>-92.9791044580601 44.34211951331 1524</gx:coord>
+ <gx:coord>-92.9818312662522 44.3446672608847 1524</gx:coord>
+ <gx:coord>-92.98437591379 44.3472600312903 1524</gx:coord>
+ <gx:coord>-92.9873161522272 44.3497184463263 1524</gx:coord>
+ <gx:coord>-92.9898453395122 44.3523721849065 1524</gx:coord>
+ <gx:coord>-92.992276996923 44.3551963485207 1524</gx:coord>
+ <gx:coord>-92.9947092219658 44.3579338326741 1524</gx:coord>
+ <gx:coord>-92.9972281517299 44.3606240814545 1524</gx:coord>
+ <gx:coord>-92.9993151368602 44.3634480822621 1524</gx:coord>
+ <gx:coord>-93.0016285033253 44.3662342282271 1524</gx:coord>
+ <gx:coord>-93.0048280633172 44.3687843972879 1524</gx:coord>
+ <gx:coord>-93.0078776508536 44.371448246948 1524</gx:coord>
+ <gx:coord>-93.0107556818704 44.3741327819505 1524</gx:coord>
+ <gx:coord>-93.0132474745541 44.3767834196569 1524</gx:coord>
+ <gx:coord>-93.015638082508 44.3795081186135 1524</gx:coord>
+ <gx:coord>-93.0183495942011 44.3821828750482 1524</gx:coord>
+ <gx:coord>-93.0215077436058 44.3847489346551 1524</gx:coord>
+ <gx:coord>-93.024007943771 44.3874959321693 1524</gx:coord>
+ <gx:coord>-93.0264526837138 44.3902468927735 1524</gx:coord>
+ <gx:coord>-93.0287728968074 44.3929994156644 1524</gx:coord>
+ <gx:coord>-93.0313252807714 44.3957423196104 1524</gx:coord>
+ <gx:coord>-93.0340309029643 44.3984682572521 1554</gx:coord>
+ <gx:coord>-93.0367834033903 44.4012140197658 1554</gx:coord>
+ <gx:coord>-93.039886374743 44.4039013532069 1524</gx:coord>
+ <gx:coord>-93.0431213002073 44.4066598090273 1524</gx:coord>
+ <gx:coord>-93.0456886621799 44.4095616223744 1524</gx:coord>
+ <gx:coord>-93.0477227123297 44.4124371862128 1524</gx:coord>
+ <gx:coord>-93.0506396295538 44.4152339455378 1524</gx:coord>
+ <gx:coord>-93.0533566431572 44.4181925397398 1524</gx:coord>
+ <gx:coord>-93.0556214357794 44.421167115874 1524</gx:coord>
+ <gx:coord>-93.0562077996189 44.4217279400145 1524</gx:coord>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>320 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <gx:angles>330 0 0</gx:angles>
+ <speed>169</speed>
+ <speed>166</speed>
+ <speed>171</speed>
+ <speed>169</speed>
+ <speed>171</speed>
+ <speed>178</speed>
+ <speed>171</speed>
+ <speed>172</speed>
+ <speed>173</speed>
+ <speed>173</speed>
+ <speed>173</speed>
+ <speed>174</speed>
+ <speed>175</speed>
+ <speed>181</speed>
+ <speed>174</speed>
+ <speed>174</speed>
+ <speed>180</speed>
+ <speed>172</speed>
+ <speed>171</speed>
+ <speed>172</speed>
+ <speed>174</speed>
+ <speed>174</speed>
+ <speed>174</speed>
+ <speed>175</speed>
+ <speed>181</speed>
+ <speed>176</speed>
+ <speed>177</speed>
+ <speed>186</speed>
+ <speed>179</speed>
+ <speed>179</speed>
+ <speed>179</speed>
+ <speed>179</speed>
+ <speed>178</speed>
+ <speed>177</speed>
+ <speed>177</speed>
+ <speed>184</speed>
+ <speed>178</speed>
+ <speed>177</speed>
+ <speed>186</speed>
+ <speed>182</speed>
+ <speed>184</speed>
+ <speed>184</speed>
+ <speed>185</speed>
+ <speed>186</speed>
+ <speed>185</speed>
+ <speed>184</speed>
+ <speed>187</speed>
+ <speed>184</speed>
+ <speed>187</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name></name>
+ <adflag>A</adflag>
+ <flightid></flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:05-05</when>
+ <when>2010-05-01T13:00:10-05</when>
+ <when>2010-05-01T13:00:14-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:33-05</when>
+ <when>2010-05-01T13:00:37-05</when>
+ <when>2010-05-01T13:00:42-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:51-05</when>
+ <when>2010-05-01T13:00:56-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <when>2010-05-01T13:01:05-05</when>
+ <when>2010-05-01T13:01:10-05</when>
+ <when>2010-05-01T13:01:14-05</when>
+ <when>2010-05-01T13:01:19-05</when>
+ <when>2010-05-01T13:01:24-05</when>
+ <when>2010-05-01T13:01:28-05</when>
+ <when>2010-05-01T13:01:33-05</when>
+ <when>2010-05-01T13:01:38-05</when>
+ <when>2010-05-01T13:01:43-05</when>
+ <when>2010-05-01T13:01:47-05</when>
+ <when>2010-05-01T13:01:51-05</when>
+ <when>2010-05-01T13:01:56-05</when>
+ <when>2010-05-01T13:02:01-05</when>
+ <when>2010-05-01T13:02:05-05</when>
+ <when>2010-05-01T13:02:10-05</when>
+ <when>2010-05-01T13:02:15-05</when>
+ <when>2010-05-01T13:02:19-05</when>
+ <when>2010-05-01T13:02:24-05</when>
+ <when>2010-05-01T13:02:28-05</when>
+ <when>2010-05-01T13:02:33-05</when>
+ <when>2010-05-01T13:02:38-05</when>
+ <when>2010-05-01T13:02:42-05</when>
+ <when>2010-05-01T13:02:47-05</when>
+ <when>2010-05-01T13:02:52-05</when>
+ <when>2010-05-01T13:02:56-05</when>
+ <when>2010-05-01T13:03:01-05</when>
+ <when>2010-05-01T13:03:06-05</when>
+ <when>2010-05-01T13:03:10-05</when>
+ <when>2010-05-01T13:03:15-05</when>
+ <when>2010-05-01T13:03:20-05</when>
+ <when>2010-05-01T13:03:24-05</when>
+ <when>2010-05-01T13:03:29-05</when>
+ <when>2010-05-01T13:03:33-05</when>
+ <when>2010-05-01T13:03:38-05</when>
+ <when>2010-05-01T13:03:43-05</when>
+ <when>2010-05-01T13:03:47-05</when>
+ <when>2010-05-01T13:03:52-05</when>
+ <when>2010-05-01T13:03:57-05</when>
+ <when>2010-05-01T13:04:01-05</when>
+ <when>2010-05-01T13:04:06-05</when>
+ <when>2010-05-01T13:04:11-05</when>
+ <when>2010-05-01T13:04:15-05</when>
+ <when>2010-05-01T13:04:20-05</when>
+ <when>2010-05-01T13:04:24-05</when>
+ <when>2010-05-01T13:04:29-05</when>
+ <when>2010-05-01T13:04:34-05</when>
+ <when>2010-05-01T13:04:38-05</when>
+ <when>2010-05-01T13:04:43-05</when>
+ <when>2010-05-01T13:04:48-05</when>
+ <when>2010-05-01T13:04:52-05</when>
+ <when>2010-05-01T13:04:57-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.5287325331323 45.3502794027397 731</gx:coord>
+ <gx:coord>-93.5305174337715 45.3463816209029 731</gx:coord>
+ <gx:coord>-93.532323089283 45.3433065196778 731</gx:coord>
+ <gx:coord>-93.5344374505075 45.3397938806867 731</gx:coord>
+ <gx:coord>-93.5365879669744 45.3355152994798 731</gx:coord>
+ <gx:coord>-93.538455345577 45.3317693717468 731</gx:coord>
+ <gx:coord>-93.5402440337749 45.3288175816964 731</gx:coord>
+ <gx:coord>-93.5420054353005 45.3261482119682 701</gx:coord>
+ <gx:coord>-93.5437972875724 45.3236486426325 701</gx:coord>
+ <gx:coord>-93.5449025453586 45.3213557809437 670</gx:coord>
+ <gx:coord>-93.5460939368394 45.3190373998605 670</gx:coord>
+ <gx:coord>-93.5479457332637 45.3165177805485 670</gx:coord>
+ <gx:coord>-93.5493974388824 45.3141793458801 670</gx:coord>
+ <gx:coord>-93.5513867211372 45.311763387862 640</gx:coord>
+ <gx:coord>-93.5535208279901 45.3092989037314 640</gx:coord>
+ <gx:coord>-93.5553972702218 45.3069522366272 609</gx:coord>
+ <gx:coord>-93.5571429777693 45.3046054644141 609</gx:coord>
+ <gx:coord>-93.5579199353617 45.3025960765579 640</gx:coord>
+ <gx:coord>-93.5593045947048 45.3003990165413 640</gx:coord>
+ <gx:coord>-93.5616831509882 45.2976828740205 640</gx:coord>
+ <gx:coord>-93.5637771433208 45.2950299257309 640</gx:coord>
+ <gx:coord>-93.5655282859852 45.2925928168771 640</gx:coord>
+ <gx:coord>-93.5670151031996 45.2901828629185 640</gx:coord>
+ <gx:coord>-93.5687097888584 45.2875722909995 609</gx:coord>
+ <gx:coord>-93.5700169391262 45.2851834796592 670</gx:coord>
+ <gx:coord>-93.5710302700083 45.2828077246619 640</gx:coord>
+ <gx:coord>-93.5718507391893 45.2803449539575 670</gx:coord>
+ <gx:coord>-93.5725921190677 45.2778546051997 670</gx:coord>
+ <gx:coord>-93.5735869984384 45.2752499819516 670</gx:coord>
+ <gx:coord>-93.5746474214783 45.2726078789038 670</gx:coord>
+ <gx:coord>-93.5759690717845 45.2698099999195 670</gx:coord>
+ <gx:coord>-93.5773880658931 45.2669964536541 701</gx:coord>
+ <gx:coord>-93.5786320195651 45.2643944197042 701</gx:coord>
+ <gx:coord>-93.5801045228797 45.2617721181735 731</gx:coord>
+ <gx:coord>-93.5812823080336 45.2592837181772 762</gx:coord>
+ <gx:coord>-93.5824999029929 45.2568982323771 792</gx:coord>
+ <gx:coord>-93.584184493492 45.2545288880291 792</gx:coord>
+ <gx:coord>-93.5856799945281 45.2523235684068 792</gx:coord>
+ <gx:coord>-93.5867865417154 45.2502484182149 792</gx:coord>
+ <gx:coord>-93.5877350378085 45.2481996073608 792</gx:coord>
+ <gx:coord>-93.5890621470214 45.2458286959404 762</gx:coord>
+ <gx:coord>-93.5904952245442 45.2433496248092 762</gx:coord>
+ <gx:coord>-93.5917459859832 45.2410175205115 762</gx:coord>
+ <gx:coord>-93.592940308901 45.2387518649986 792</gx:coord>
+ <gx:coord>-93.5943516581034 45.2363760400415 792</gx:coord>
+ <gx:coord>-93.595835737429 45.2339795097202 792</gx:coord>
+ <gx:coord>-93.5970428000944 45.2316738651172 792</gx:coord>
+ <gx:coord>-93.598068247895 45.2293303072495 792</gx:coord>
+ <gx:coord>-93.5992987604295 45.2268750160339 762</gx:coord>
+ <gx:coord>-93.6008769052334 45.2242985661919 762</gx:coord>
+ <gx:coord>-93.6025298777898 45.2216628823159 762</gx:coord>
+ <gx:coord>-93.6039679259902 45.2191586079975 762</gx:coord>
+ <gx:coord>-93.6055530853699 45.2165615203343 762</gx:coord>
+ <gx:coord>-93.6071850685486 45.213948758836 792</gx:coord>
+ <gx:coord>-93.6085800541819 45.2114666338841 792</gx:coord>
+ <gx:coord>-93.6099900017953 45.2087907684969 762</gx:coord>
+ <gx:coord>-93.6111813373289 45.2058856405005 762</gx:coord>
+ <gx:coord>-93.6116978316508 45.2030015871681 762</gx:coord>
+ <gx:coord>-93.6118935129054 45.2001474423799 762</gx:coord>
+ <gx:coord>-93.6120686576365 45.1971548169968 731</gx:coord>
+ <gx:coord>-93.6120488607103 45.1942250308012 731</gx:coord>
+ <gx:coord>-93.6121619193052 45.1911822627783 731</gx:coord>
+ <gx:coord>-93.6123153707665 45.188122812492 731</gx:coord>
+ <gx:coord>-93.6121210225109 45.1864342009565 731</gx:coord>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>210 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>190 0 0</gx:angles>
+ <gx:angles>190 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>190 0 0</gx:angles>
+ <gx:angles>190 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <speed>202</speed>
+ <speed>180</speed>
+ <speed>166</speed>
+ <speed>171</speed>
+ <speed>162</speed>
+ <speed>157</speed>
+ <speed>143</speed>
+ <speed>145</speed>
+ <speed>156</speed>
+ <speed>144</speed>
+ <speed>142</speed>
+ <speed>149</speed>
+ <speed>146</speed>
+ <speed>148</speed>
+ <speed>140</speed>
+ <speed>139</speed>
+ <speed>142</speed>
+ <speed>137</speed>
+ <speed>143</speed>
+ <speed>149</speed>
+ <speed>151</speed>
+ <speed>152</speed>
+ <speed>151</speed>
+ <speed>147</speed>
+ <speed>140</speed>
+ <speed>141</speed>
+ <speed>140</speed>
+ <speed>149</speed>
+ <speed>148</speed>
+ <speed>150</speed>
+ <speed>158</speed>
+ <speed>152</speed>
+ <speed>150</speed>
+ <speed>147</speed>
+ <speed>142</speed>
+ <speed>138</speed>
+ <speed>131</speed>
+ <speed>131</speed>
+ <speed>132</speed>
+ <speed>130</speed>
+ <speed>130</speed>
+ <speed>138</speed>
+ <speed>138</speed>
+ <speed>137</speed>
+ <speed>140</speed>
+ <speed>136</speed>
+ <speed>139</speed>
+ <speed>142</speed>
+ <speed>143</speed>
+ <speed>146</speed>
+ <speed>150</speed>
+ <speed>151</speed>
+ <speed>157</speed>
+ <speed>152</speed>
+ <speed>154</speed>
+ <speed>160</speed>
+ <speed>155</speed>
+ <speed>157</speed>
+ <speed>159</speed>
+ <speed>160</speed>
+ <speed>159</speed>
+ <speed>155</speed>
+ <speed>157</speed>
+ <speed>161</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>B752</name>
+ <adflag>A</adflag>
+ <flightid>DAL2731</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:04:40-05</when>
+ <when>2010-05-01T13:04:44-05</when>
+ <when>2010-05-01T13:04:49-05</when>
+ <when>2010-05-01T13:04:53-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.3671504733075 45.0392472395977 2743</gx:coord>
+ <gx:coord>-92.3742258682339 45.0377289236059 2712</gx:coord>
+ <gx:coord>-92.3813215867021 45.0364293844267 2682</gx:coord>
+ <gx:coord>-92.3883643499875 45.0352118386382 2651</gx:coord>
+ <gx:coord>-92.3954606917206 45.0343296776778 2621</gx:coord>
+ <gx:coord>-92.3983011205325 45.0338926726637 2608.6</gx:coord>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <speed>301</speed>
+ <speed>289</speed>
+ <speed>283</speed>
+ <speed>283</speed>
+ <speed>272</speed>
+ <speed>276</speed>
+</gx:Track></Placemark>
+<Placemark>
+ <name>CRJ2</name>
+ <adflag>A</adflag>
+ <flightid>SKW4805</flightid>
+ <styleUrl>#arrival</styleUrl>
+</Placemark>
+<Placemark>
+ <name>CRJ2</name>
+ <adflag>A</adflag>
+ <flightid>FLG4092</flightid>
+ <styleUrl>#arrival</styleUrl>
+</Placemark>
+<Placemark>
+ <name>E170</name>
+ <adflag>A</adflag>
+ <flightid>CPZ5667</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:10-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:01-05</when>
+ <when>2010-05-01T13:01:06-05</when>
+ <when>2010-05-01T13:01:11-05</when>
+ <when>2010-05-01T13:01:15-05</when>
+ <when>2010-05-01T13:01:20-05</when>
+ <when>2010-05-01T13:01:24-05</when>
+ <when>2010-05-01T13:01:29-05</when>
+ <when>2010-05-01T13:01:34-05</when>
+ <when>2010-05-01T13:01:38-05</when>
+ <when>2010-05-01T13:01:43-05</when>
+ <when>2010-05-01T13:01:48-05</when>
+ <when>2010-05-01T13:01:52-05</when>
+ <when>2010-05-01T13:01:57-05</when>
+ <when>2010-05-01T13:02:02-05</when>
+ <when>2010-05-01T13:02:06-05</when>
+ <when>2010-05-01T13:02:11-05</when>
+ <when>2010-05-01T13:02:15-05</when>
+ <when>2010-05-01T13:02:20-05</when>
+ <when>2010-05-01T13:02:25-05</when>
+ <when>2010-05-01T13:02:29-05</when>
+ <when>2010-05-01T13:02:34-05</when>
+ <when>2010-05-01T13:02:39-05</when>
+ <when>2010-05-01T13:02:43-05</when>
+ <when>2010-05-01T13:02:48-05</when>
+ <when>2010-05-01T13:02:53-05</when>
+ <when>2010-05-01T13:02:57-05</when>
+ <when>2010-05-01T13:03:02-05</when>
+ <when>2010-05-01T13:03:06-05</when>
+ <when>2010-05-01T13:03:11-05</when>
+ <when>2010-05-01T13:03:16-05</when>
+ <when>2010-05-01T13:03:20-05</when>
+ <when>2010-05-01T13:03:25-05</when>
+ <when>2010-05-01T13:03:30-05</when>
+ <when>2010-05-01T13:03:34-05</when>
+ <when>2010-05-01T13:03:39-05</when>
+ <when>2010-05-01T13:03:44-05</when>
+ <when>2010-05-01T13:03:48-05</when>
+ <when>2010-05-01T13:03:53-05</when>
+ <when>2010-05-01T13:03:57-05</when>
+ <when>2010-05-01T13:04:02-05</when>
+ <when>2010-05-01T13:04:07-05</when>
+ <when>2010-05-01T13:04:11-05</when>
+ <when>2010-05-01T13:04:16-05</when>
+ <when>2010-05-01T13:04:21-05</when>
+ <when>2010-05-01T13:04:25-05</when>
+ <when>2010-05-01T13:04:30-05</when>
+ <when>2010-05-01T13:04:35-05</when>
+ <when>2010-05-01T13:04:39-05</when>
+ <when>2010-05-01T13:04:44-05</when>
+ <when>2010-05-01T13:04:49-05</when>
+ <when>2010-05-01T13:04:53-05</when>
+ <when>2010-05-01T13:04:58-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-92.9496238812799 45.0117549407746 1438.2</gx:coord>
+ <gx:coord>-92.9507065768732 45.0116702587604 1432</gx:coord>
+ <gx:coord>-92.9563739191926 45.0116271226204 1432</gx:coord>
+ <gx:coord>-92.9620225732021 45.0115639668496 1432</gx:coord>
+ <gx:coord>-92.9673675587699 45.0113432900049 1402</gx:coord>
+ <gx:coord>-92.9725115032188 45.0111442254373 1402</gx:coord>
+ <gx:coord>-92.9778810091229 45.0112050922639 1371</gx:coord>
+ <gx:coord>-92.9832227114571 45.0112143826731 1371</gx:coord>
+ <gx:coord>-92.9884546803523 45.0110418166788 1341</gx:coord>
+ <gx:coord>-92.9938268606229 45.0109652220709 1341</gx:coord>
+ <gx:coord>-92.9991151069756 45.010802144845 1310</gx:coord>
+ <gx:coord>-93.0041467584036 45.0105516668541 1310</gx:coord>
+ <gx:coord>-93.0090742909164 45.0105233046799 1280</gx:coord>
+ <gx:coord>-93.0139435770527 45.0106265340001 1280</gx:coord>
+ <gx:coord>-93.0186698179379 45.010634924101 1249</gx:coord>
+ <gx:coord>-93.0233769482656 45.0105798571028 1219</gx:coord>
+ <gx:coord>-93.027863445495 45.0103319372353 1219</gx:coord>
+ <gx:coord>-93.0321355024912 45.009785470284 1188</gx:coord>
+ <gx:coord>-93.0364774006258 45.0090804055343 1188</gx:coord>
+ <gx:coord>-93.0406972054631 45.008159893417 1158</gx:coord>
+ <gx:coord>-93.044688438093 45.0070424610069 1158</gx:coord>
+ <gx:coord>-93.048236193366 45.0055626328365 1127</gx:coord>
+ <gx:coord>-93.0515060655523 45.0038918034748 1097</gx:coord>
+ <gx:coord>-93.0547412568513 45.002203639943 1097</gx:coord>
+ <gx:coord>-93.057960971331 45.0002785469345 1066</gx:coord>
+ <gx:coord>-93.061163597597 44.9982000732934 1036</gx:coord>
+ <gx:coord>-93.0642340616386 44.9961004469539 1036</gx:coord>
+ <gx:coord>-93.0673288162316 44.9939827866134 1036</gx:coord>
+ <gx:coord>-93.0705257535347 44.9919340234479 1006</gx:coord>
+ <gx:coord>-93.0737651809484 44.9898497469776 1006</gx:coord>
+ <gx:coord>-93.0766698334355 44.9874752633062 975</gx:coord>
+ <gx:coord>-93.0795448300029 44.9851003293423 945</gx:coord>
+ <gx:coord>-93.0826513591394 44.982853369523 914</gx:coord>
+ <gx:coord>-93.0857494236443 44.9806128435883 914</gx:coord>
+ <gx:coord>-93.0889594989987 44.9783354445401 884</gx:coord>
+ <gx:coord>-93.0921516080765 44.9761326356492 853</gx:coord>
+ <gx:coord>-93.0951593343498 44.9739412329465 823</gx:coord>
+ <gx:coord>-93.098173526634 44.9718087345519 792</gx:coord>
+ <gx:coord>-93.1011828507638 44.9697896030084 792</gx:coord>
+ <gx:coord>-93.1041138105741 44.9676784537011 762</gx:coord>
+ <gx:coord>-93.1070716804749 44.9654796588945 762</gx:coord>
+ <gx:coord>-93.1101959375488 44.9632479940121 731</gx:coord>
+ <gx:coord>-93.1134259541861 44.9611142324701 731</gx:coord>
+ <gx:coord>-93.1164777263599 44.9590021654861 701</gx:coord>
+ <gx:coord>-93.119453084479 44.9567137200248 701</gx:coord>
+ <gx:coord>-93.1225749783361 44.9543749518252 670</gx:coord>
+ <gx:coord>-93.1257330391052 44.9521434289046 640</gx:coord>
+ <gx:coord>-93.1288583838247 44.9499086265813 640</gx:coord>
+ <gx:coord>-93.1320823896043 44.947732382611 609</gx:coord>
+ <gx:coord>-93.1352777130563 44.9456935460161 609</gx:coord>
+ <gx:coord>-93.1382372228923 44.9435026223594 579</gx:coord>
+ <gx:coord>-93.1412308158626 44.9413228585563 548</gx:coord>
+ <gx:coord>-93.1440834422772 44.9393578781327 548</gx:coord>
+ <gx:coord>-93.1468380987104 44.937418270883 548</gx:coord>
+ <gx:coord>-93.1496706928566 44.9354098449433 548</gx:coord>
+ <gx:coord>-93.1524193130388 44.9334175710809 548</gx:coord>
+ <gx:coord>-93.1552204975698 44.931467153437 548</gx:coord>
+ <gx:coord>-93.1580221467789 44.9294054329873 548</gx:coord>
+ <gx:coord>-93.1608324983225 44.9273103160518 548</gx:coord>
+ <gx:coord>-93.163576735833 44.9252792086421 518</gx:coord>
+ <gx:coord>-93.1662777029414 44.923281165701 518</gx:coord>
+ <gx:coord>-93.1691629183162 44.9213004519466 518</gx:coord>
+ <gx:coord>-93.1721326207182 44.9193080290794 487</gx:coord>
+ <gx:coord>-93.1750564756636 44.9172689130085 487</gx:coord>
+ <gx:coord>-93.1778338144972 44.915261757476 457</gx:coord>
+ <gx:coord>-93.1805696776089 44.9132626732327 457</gx:coord>
+ <gx:coord>-93.1819903937475 44.9122233325116 441.5</gx:coord>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>240 0 0</gx:angles>
+ <gx:angles>240 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <speed>214</speed>
+ <speed>207</speed>
+ <speed>202</speed>
+ <speed>208</speed>
+ <speed>207</speed>
+ <speed>205</speed>
+ <speed>203</speed>
+ <speed>202</speed>
+ <speed>209</speed>
+ <speed>199</speed>
+ <speed>196</speed>
+ <speed>200</speed>
+ <speed>188</speed>
+ <speed>183</speed>
+ <speed>178</speed>
+ <speed>175</speed>
+ <speed>179</speed>
+ <speed>170</speed>
+ <speed>166</speed>
+ <speed>169</speed>
+ <speed>161</speed>
+ <speed>160</speed>
+ <speed>159</speed>
+ <speed>159</speed>
+ <speed>160</speed>
+ <speed>162</speed>
+ <speed>164</speed>
+ <speed>172</speed>
+ <speed>166</speed>
+ <speed>167</speed>
+ <speed>174</speed>
+ <speed>169</speed>
+ <speed>170</speed>
+ <speed>169</speed>
+ <speed>168</speed>
+ <speed>166</speed>
+ <speed>165</speed>
+ <speed>163</speed>
+ <speed>169</speed>
+ <speed>164</speed>
+ <speed>164</speed>
+ <speed>172</speed>
+ <speed>168</speed>
+ <speed>169</speed>
+ <speed>169</speed>
+ <speed>169</speed>
+ <speed>169</speed>
+ <speed>168</speed>
+ <speed>167</speed>
+ <speed>170</speed>
+ <speed>160</speed>
+ <speed>158</speed>
+ <speed>161</speed>
+ <speed>153</speed>
+ <speed>151</speed>
+ <speed>152</speed>
+ <speed>153</speed>
+ <speed>152</speed>
+ <speed>153</speed>
+ <speed>154</speed>
+ <speed>154</speed>
+ <speed>153</speed>
+ <speed>153</speed>
+ <speed>160</speed>
+ <speed>156</speed>
+ <speed>160</speed>
+ <speed>164</speed>
+</gx:Track></Placemark>
+</Folder>
+<Folder>
+ <name>Departures</name>
+<Placemark>
+ <name>TEX2</name>
+ <adflag>D</adflag>
+ <flightid>HOOK67</flightid>
+ <styleUrl>#departure</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:02:46-05</when>
+ <when>2010-05-01T13:02:50-05</when>
+ <when>2010-05-01T13:02:54-05</when>
+ <when>2010-05-01T13:02:59-05</when>
+ <when>2010-05-01T13:03:04-05</when>
+ <when>2010-05-01T13:03:08-05</when>
+ <when>2010-05-01T13:03:13-05</when>
+ <when>2010-05-01T13:03:18-05</when>
+ <when>2010-05-01T13:03:22-05</when>
+ <when>2010-05-01T13:03:27-05</when>
+ <when>2010-05-01T13:03:31-05</when>
+ <when>2010-05-01T13:03:36-05</when>
+ <when>2010-05-01T13:03:41-05</when>
+ <when>2010-05-01T13:03:45-05</when>
+ <when>2010-05-01T13:03:50-05</when>
+ <when>2010-05-01T13:03:55-05</when>
+ <when>2010-05-01T13:03:59-05</when>
+ <when>2010-05-01T13:04:04-05</when>
+ <when>2010-05-01T13:04:09-05</when>
+ <when>2010-05-01T13:04:13-05</when>
+ <when>2010-05-01T13:04:18-05</when>
+ <when>2010-05-01T13:04:23-05</when>
+ <when>2010-05-01T13:04:28-05</when>
+ <when>2010-05-01T13:04:32-05</when>
+ <when>2010-05-01T13:04:37-05</when>
+ <when>2010-05-01T13:04:42-05</when>
+ <when>2010-05-01T13:04:46-05</when>
+ <when>2010-05-01T13:04:51-05</when>
+ <when>2010-05-01T13:04:56-05</when>
+ <when>2010-05-01T13:05:00-05</when>
+ <gx:coord>-93.2379571205595 44.872806349747 365</gx:coord>
+ <gx:coord>-93.2370660925484 44.870006118743 396</gx:coord>
+ <gx:coord>-93.236355767523 44.8669752777211 426</gx:coord>
+ <gx:coord>-93.2354887209031 44.863712193489 487</gx:coord>
+ <gx:coord>-93.2347087148419 44.8604536579846 548</gx:coord>
+ <gx:coord>-93.2338531241111 44.8572464977323 609</gx:coord>
+ <gx:coord>-93.2329069833652 44.8540674818656 670</gx:coord>
+ <gx:coord>-93.2321075679892 44.8508271074111 731</gx:coord>
+ <gx:coord>-93.2318979317232 44.8475791496379 792</gx:coord>
+ <gx:coord>-93.2324245825346 44.8444239832126 884</gx:coord>
+ <gx:coord>-93.2337414411031 44.8414077607553 945</gx:coord>
+ <gx:coord>-93.2358704572033 44.8386783246771 1006</gx:coord>
+ <gx:coord>-93.2388663703645 44.836365445841 1066</gx:coord>
+ <gx:coord>-93.2426861295915 44.8345537010783 1127</gx:coord>
+ <gx:coord>-93.2472528925157 44.8333824186694 1158</gx:coord>
+ <gx:coord>-93.252467378877 44.8329692039001 1188</gx:coord>
+ <gx:coord>-93.25805239674 44.8333893976675 1219</gx:coord>
+ <gx:coord>-93.2638450577518 44.8346083411457 1219</gx:coord>
+ <gx:coord>-93.2696754993405 44.83650914188 1219</gx:coord>
+ <gx:coord>-93.2753673121587 44.8390951418887 1219</gx:coord>
+ <gx:coord>-93.2808543977574 44.8421681587795 1219</gx:coord>
+ <gx:coord>-93.2861853262416 44.8454128516506 1249</gx:coord>
+ <gx:coord>-93.29145969331 44.8487370983379 1219</gx:coord>
+ <gx:coord>-93.2967095159 44.8520389729185 1219</gx:coord>
+ <gx:coord>-93.3019214165294 44.8553364257712 1219</gx:coord>
+ <gx:coord>-93.3070477220233 44.8587223340278 1219</gx:coord>
+ <gx:coord>-93.3121286565238 44.8620050415952 1219</gx:coord>
+ <gx:coord>-93.3171626530446 44.8652559523285 1219</gx:coord>
+ <gx:coord>-93.3221179929219 44.868582313462 1219</gx:coord>
+ <gx:coord>-93.3270963865766 44.8718050975636 1219</gx:coord>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>180 0 0</gx:angles>
+ <gx:angles>190 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>240 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <gx:angles>310 0 0</gx:angles>
+ <speed>178</speed>
+ <speed>175</speed>
+ <speed>180</speed>
+ <speed>177</speed>
+ <speed>175</speed>
+ <speed>177</speed>
+ <speed>175</speed>
+ <speed>181</speed>
+ <speed>173</speed>
+ <speed>172</speed>
+ <speed>178</speed>
+ <speed>173</speed>
+ <speed>178</speed>
+ <speed>187</speed>
+ <speed>196</speed>
+ <speed>208</speed>
+ <speed>224</speed>
+ <speed>237</speed>
+ <speed>247</speed>
+ <speed>247</speed>
+ <speed>263</speed>
+ <speed>266</speed>
+ <speed>258</speed>
+ <speed>267</speed>
+ <speed>266</speed>
+ <speed>265</speed>
+ <speed>263</speed>
+ <speed>266</speed>
+ <speed>270</speed>
+ <speed>260</speed>
+</gx:Track></Placemark>
+</Folder>
+<Folder>
+ <name>Overflights</name>
+</Folder>
+</Document>
+</kml>
diff --git a/misc/openlayers/examples/kml/lines.kml b/misc/openlayers/examples/kml/lines.kml
new file mode 100644
index 0000000..5999aaa
--- /dev/null
+++ b/misc/openlayers/examples/kml/lines.kml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://earth.google.com/kml/2.0">
+ <Document>
+ <name>KML Samples</name>
+ <open>1</open>
+ <description>Unleash your creativity with the help of these examples!</description>
+ <Style id="downArrowIcon">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/pal4/icon28.png</href>
+ </Icon>
+ </IconStyle>
+ </Style>
+ <Style id="globeIcon">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/pal3/icon19.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <width>2</width>
+ </LineStyle>
+ </Style>
+ <Style id="transPurpleLineGreenPoly">
+ <LineStyle>
+ <color>7fff00ff</color>
+ <width>4</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7f00ff00</color>
+ </PolyStyle>
+ </Style>
+ <Style id="yellowLineGreenPoly">
+ <LineStyle>
+ <color>7f00ffff</color>
+ <width>4</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7f00ff00</color>
+ </PolyStyle>
+ </Style>
+ <Style id="thickBlackLine">
+ <LineStyle>
+ <color>87000000</color>
+ <width>10</width>
+ </LineStyle>
+ </Style>
+ <Style id="redLineBluePoly">
+ <LineStyle>
+ <color>ff0000ff</color>
+ </LineStyle>
+ <PolyStyle>
+ <color>ffff0000</color>
+ </PolyStyle>
+ </Style>
+ <Style id="blueLineRedPoly">
+ <LineStyle>
+ <color>ffff0000</color>
+ </LineStyle>
+ <PolyStyle>
+ <color>ff0000ff</color>
+ </PolyStyle>
+ </Style>
+ <Style id="transRedPoly">
+ <LineStyle>
+ <width>1.5</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7d0000ff</color>
+ </PolyStyle>
+ </Style>
+ <Style id="transBluePoly">
+ <LineStyle>
+ <width>1.5</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7dff0000</color>
+ </PolyStyle>
+ </Style>
+ <Style id="transGreenPoly">
+ <LineStyle>
+ <width>1.5</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7d00ff00</color>
+ </PolyStyle>
+ </Style>
+ <Style id="transYellowPoly">
+ <LineStyle>
+ <width>1.5</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7d00ffff</color>
+ </PolyStyle>
+ </Style>
+ <Style id="noDrivingDirections">
+ <BalloonStyle>
+ <text><![CDATA[
+ <b>$[name]</b>
+ <br /><br />
+ $[description]
+ ]]></text>
+ </BalloonStyle>
+ </Style>
+ <Folder>
+ <name>Paths</name>
+ <visibility>0</visibility>
+ <description>Examples of paths. Note that the tessellate tag is by default
+ set to 0. If you want to create tessellated lines, they must be authored
+ (or edited) directly in KML.</description>
+ <Placemark>
+ <name>Tessellated</name>
+ <visibility>0</visibility>
+ <description><![CDATA[If the <tessellate> tag has a value of 1, the line will contour to the underlying terrain]]></description>
+ <LookAt>
+ <longitude>-112.0822680013139</longitude>
+ <latitude>36.09825589333556</latitude>
+ <altitude>0</altitude>
+ <range>2889.145007690472</range>
+ <tilt>62.04855796276328</tilt>
+ <heading>103.8120432044965</heading>
+ </LookAt>
+ <LineString>
+ <tessellate>1</tessellate>
+ <coordinates> -112.0814237830345,36.10677870477137,0
+ -112.0870267752693,36.0905099328766,0 </coordinates>
+ </LineString>
+ </Placemark>
+ <Placemark>
+ <name>Untessellated</name>
+ <visibility>0</visibility>
+ <description><![CDATA[If the <tessellate> tag has a value of 0, the line follow a simple straight-line path from point to point]]></description>
+ <LookAt>
+ <longitude>-112.0822680013139</longitude>
+ <latitude>36.09825589333556</latitude>
+ <altitude>0</altitude>
+ <range>2889.145007690472</range>
+ <tilt>62.04855796276328</tilt>
+ <heading>103.8120432044965</heading>
+ </LookAt>
+ <LineString>
+ <tessellate>0</tessellate>
+ <coordinates> -112.080622229595,36.10673460007995,0
+ -112.085242575315,36.09049598612422,0 </coordinates>
+ </LineString>
+ </Placemark>
+ <Placemark>
+ <name>Absolute</name>
+ <visibility>0</visibility>
+ <description>Transparent purple line</description>
+ <LookAt>
+ <longitude>-112.2719329043177</longitude>
+ <latitude>36.08890633450894</latitude>
+ <altitude>0</altitude>
+ <range>2569.386744398339</range>
+ <tilt>44.60763714063257</tilt>
+ <heading>-106.8161545998597</heading>
+ </LookAt>
+ <styleUrl>#transPurpleLineGreenPoly</styleUrl>
+ <LineString>
+ <tessellate>1</tessellate>
+ <altitudeMode>absolute</altitudeMode>
+ <coordinates> -112.265654928602,36.09447672602546,2357
+ -112.2660384528238,36.09342608838671,2357
+ -112.2668139013453,36.09251058776881,2357
+ -112.2677826834445,36.09189827357996,2357
+ -112.2688557510952,36.0913137941187,2357
+ -112.2694810717219,36.0903677207521,2357
+ -112.2695268555611,36.08932171487285,2357
+ -112.2690144567276,36.08850916060472,2357
+ -112.2681528815339,36.08753813597956,2357
+ -112.2670588176031,36.08682685262568,2357
+ -112.2657374587321,36.08646312301303,2357 </coordinates>
+ </LineString>
+ </Placemark>
+ <Placemark>
+ <name>Absolute Extruded</name>
+ <visibility>0</visibility>
+ <description>Transparent green wall with yellow outlines</description>
+ <LookAt>
+ <longitude>-112.2643334742529</longitude>
+ <latitude>36.08563154742419</latitude>
+ <altitude>0</altitude>
+ <range>4451.842204068102</range>
+ <tilt>44.61038665812578</tilt>
+ <heading>-125.7518698668815</heading>
+ </LookAt>
+ <styleUrl>#yellowLineGreenPoly</styleUrl>
+ <LineString>
+ <extrude>1</extrude>
+ <tessellate>1</tessellate>
+ <altitudeMode>absolute</altitudeMode>
+ <coordinates> -112.2550785337791,36.07954952145647,2357
+ -112.2549277039738,36.08117083492122,2357
+ -112.2552505069063,36.08260761307279,2357
+ -112.2564540158376,36.08395660588506,2357
+ -112.2580238976449,36.08511401044813,2357
+ -112.2595218489022,36.08584355239394,2357
+ -112.2608216347552,36.08612634548589,2357
+ -112.262073428656,36.08626019085147,2357
+ -112.2633204928495,36.08621519860091,2357
+ -112.2644963846444,36.08627897945274,2357
+ -112.2656969554589,36.08649599090644,2357 </coordinates>
+ </LineString>
+ </Placemark>
+ <Placemark>
+ <name>Relative</name>
+ <visibility>0</visibility>
+ <description>Black line (10 pixels wide), height tracks terrain</description>
+ <LookAt>
+ <longitude>-112.2580438551384</longitude>
+ <latitude>36.1072674824385</latitude>
+ <altitude>0</altitude>
+ <range>2927.61105910266</range>
+ <tilt>44.61324882043339</tilt>
+ <heading>4.947421249553717</heading>
+ </LookAt>
+ <styleUrl>#thickBlackLine</styleUrl>
+ <LineString>
+ <tessellate>1</tessellate>
+ <altitudeMode>relativeToGround</altitudeMode>
+ <coordinates> -112.2532845153347,36.09886943729116,645
+ -112.2540466121145,36.09919570465255,645
+ -112.254734666947,36.09984998366178,645
+ -112.255493345654,36.10051310621746,645
+ -112.2563157098468,36.10108441943419,645
+ -112.2568033076439,36.10159722088088,645
+ -112.257494011321,36.10204323542867,645
+ -112.2584106072308,36.10229131995655,645
+ -112.2596588987972,36.10240001286358,645
+ -112.2610581199487,36.10213176873407,645
+ -112.2626285262793,36.10157011437219,645 </coordinates>
+ </LineString>
+ </Placemark>
+ <Placemark>
+ <name>Relative Extruded</name>
+ <visibility>0</visibility>
+ <description>Opaque blue walls with red outline, height tracks terrain</description>
+ <LookAt>
+ <longitude>-112.2683594333433</longitude>
+ <latitude>36.09884362144909</latitude>
+ <altitude>0</altitude>
+ <range>2184.193522571467</range>
+ <tilt>44.60855445139561</tilt>
+ <heading>-72.24271551768405</heading>
+ </LookAt>
+ <styleUrl>#redLineBluePoly</styleUrl>
+ <LineString>
+ <extrude>1</extrude>
+ <tessellate>1</tessellate>
+ <altitudeMode>relativeToGround</altitudeMode>
+ <coordinates> -112.2656634181359,36.09445214722695,630
+ -112.2652238941097,36.09520916122063,630
+ -112.2645079986395,36.09580763864907,630
+ -112.2638827428817,36.09628572284063,630
+ -112.2635746835406,36.09679275951239,630
+ -112.2635711822407,36.09740038871899,630
+ -112.2640296531825,36.09804913435539,630
+ -112.264327720538,36.09880337400301,630
+ -112.2642436562271,36.09963644790288,630
+ -112.2639148687042,36.10055381117246,630
+ -112.2626894973474,36.10149062823369,630 </coordinates>
+ </LineString>
+ </Placemark>
+ <Placemark>
+ <name>Blue Icon</name>
+ <description>Just another blue icon.</description>
+ <styleUrl>kml/styles.kml#blueIcons</styleUrl>
+ <Point>
+ <coordinates>-112.292238941097,36.09520916122063,630</coordinates>
+ </Point>
+ </Placemark>
+ </Folder>
+ </Document>
+</kml>
diff --git a/misc/openlayers/examples/kml/styles.kml b/misc/openlayers/examples/kml/styles.kml
new file mode 100644
index 0000000..24350ad
--- /dev/null
+++ b/misc/openlayers/examples/kml/styles.kml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+This file contains styles referenced by other KML files in this project.
+http://kml-samples.googlecode.com/svn/trunk/kml/Style/styles.kml
+
+-->
+<kml xmlns="http://earth.google.com/kml/2.1">
+<Document id="globalStyles">
+
+ <Style id="blueIcons">
+ <IconStyle>
+ <color>ffff0000</color>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/shapes/sunny.png</href>
+ </Icon>
+ </IconStyle>
+ </Style>
+
+</Document>
+</kml>
diff --git a/misc/openlayers/examples/kml/sundials.kml b/misc/openlayers/examples/kml/sundials.kml
new file mode 100644
index 0000000..8a68305
--- /dev/null
+++ b/misc/openlayers/examples/kml/sundials.kml
@@ -0,0 +1,2273 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://earth.google.com/kml/2.2">
+<Document>
+ <name>Sundial Collection.kmz</name>
+ <StyleMap id="msn_sunny_copy70">
+ <Pair>
+ <key>normal</key>
+ <styleUrl>#sn_sunny_copy69</styleUrl>
+ </Pair>
+ <Pair>
+ <key>highlight</key>
+ <styleUrl>#sh_sunny_copy70</styleUrl>
+ </Pair>
+ </StyleMap>
+ <Style id="sh_sunny_copy69">
+ <IconStyle>
+ <scale>1.4</scale>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/shapes/sunny.png</href>
+ </Icon>
+ <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
+ </IconStyle>
+ <LabelStyle>
+ <color>ff00aaff</color>
+ </LabelStyle>
+ </Style>
+ <Style id="sn_sunny_copy68">
+ <IconStyle>
+ <scale>1.2</scale>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/shapes/sunny.png</href>
+ </Icon>
+ <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
+ </IconStyle>
+ <LabelStyle>
+ <color>ff00aaff</color>
+ </LabelStyle>
+ </Style>
+ <Style id="sn_sunny_copy69">
+ <IconStyle>
+ <scale>1.2</scale>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/shapes/sunny.png</href>
+ </Icon>
+ <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
+ </IconStyle>
+ <LabelStyle>
+ <color>ff00aaff</color>
+ </LabelStyle>
+ </Style>
+ <Style id="sh_sunny_copy70">
+ <IconStyle>
+ <scale>1.4</scale>
+ <Icon>
+ <href>http://maps.google.com/mapfiles/kml/shapes/sunny.png</href>
+ </Icon>
+ <hotSpot x="0.5" y="0.5" xunits="fraction" yunits="fraction"/>
+ </IconStyle>
+ <LabelStyle>
+ <color>ff00aaff</color>
+ </LabelStyle>
+ </Style>
+ <StyleMap id="msn_sunny_copy69">
+ <Pair>
+ <key>normal</key>
+ <styleUrl>#sn_sunny_copy68</styleUrl>
+ </Pair>
+ <Pair>
+ <key>highlight</key>
+ <styleUrl>#sh_sunny_copy69</styleUrl>
+ </Pair>
+ </StyleMap>
+ <Folder>
+ <name>Sundial Collection</name>
+ <open>1</open>
+ <LookAt>
+ <longitude>-56.6884384968692</longitude>
+ <latitude>47.91963617483238</latitude>
+ <altitude>0</altitude>
+ <range>9958750.824018393</range>
+ <tilt>1.303827428939919e-015</tilt>
+ <heading>-16.31426621668193</heading>
+ </LookAt>
+ <Style>
+ <ListStyle>
+ <listItemType>check</listItemType>
+ <bgColor>00ffffff</bgColor>
+ </ListStyle>
+ </Style>
+ <Folder>
+ <name>High Resolution</name>
+ <Placemark>
+ <name>Sundial, Madestein, Den Haag</name>
+ <description><![CDATA[Horizontal sundial on the campus of the ‘Parnassia’ psychomedic centre in The Hague. The numbers on the left, 6,7 and 8, are wrong spelled.
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/zw-07-05-02.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>4.213227700645635</longitude>
+ <latitude>52.04260288332888</latitude>
+ <altitude>0</altitude>
+ <range>24.63686803544318</range>
+ <tilt>0</tilt>
+ <heading>1.387289180270979e-005</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>4.213209970684247,52.04268354765237,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Den Haag - Loosduinen</name>
+ <description><![CDATA[<p>Thanks to <b>A30</b></p>
+
+A sundial made of wooden blocks.
+The highest block in the middle is the style and casts its shadow each hour on one of the other blocks.
+
+<p><img src="http://www.dse.nl/~zonnewijzer/loosduin.jpg"></p>
+Image source:<a href="http://www.dse.nl/~zonnewijzer/loosduin.jpg">www.dse.nl</a>]]></description>
+ <LookAt>
+ <longitude>4.236038669148795</longitude>
+ <latitude>52.0499434967447</latitude>
+ <altitude>0</altitude>
+ <range>18.37312193280116</range>
+ <tilt>2.202011190893535e-011</tilt>
+ <heading>-0.3988978466888938</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>4.236026636181407,52.049986562365,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial with light conductors - Paris, Les Halles</name>
+ <description><![CDATA[<p><img src="http://www.home.uni-osnabrueck.de/ahaenel/sonnuhr/paris1.jpg"></p>
+
+The sunlight falls on one of the three windows in the column (east, south, west) and over light conductors on the wall is indicated.
+
+<p><img src="http://www.home.uni-osnabrueck.de/ahaenel/sonnuhr/paris4.jpg"></p>
+
+The clock shows 16,40 o'clock.
+
+<p><img src="http://www.home.uni-osnabrueck.de/ahaenel/sonnuhr/paris5.jpg"></p>
+
+<a>Quelle:http://www.home.uni-osnabrueck.de/ahaenel/sonnuhr/paris_halles.htm</a>
+
+http://perso.orange.fr/cadrans.solaires/cadrans/cadran-halles-paris.html]]></description>
+ <LookAt>
+ <longitude>2.344185113917775</longitude>
+ <latitude>48.86294270160059</latitude>
+ <altitude>0</altitude>
+ <range>39.52787486507292</range>
+ <tilt>0</tilt>
+ <heading>-0.003533584730563007</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>2.344143312335305,48.86302323987447,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Plymouth, Devon, UK</name>
+ <description><![CDATA[<p>The gnonom is 27 foot high, the pool has 21 feet diameter. It was designed by architect Carole Vincent from Boscastle in Cornwall and was unvieled by Her Majesty the Queen on Friday July 22nd 1988 for a cost of cost £70,000 . The sundial runs one hour and seventeen minutes behind local clocks.</p>
+<p><img src="http://www.photoready.co.uk/people-life/images/sundial-fountain.jpg"></p>
+<p>Image source:<a href="www.photoready.co.uk</a></p>]]></description>
+ <LookAt>
+ <longitude>-4.142398271107962</longitude>
+ <latitude>50.37145390235462</latitude>
+ <altitude>0</altitude>
+ <range>63.33410419881957</range>
+ <tilt>0</tilt>
+ <heading>-0.0001034131369701296</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-4.142446411782089,50.37160252809223,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial Millennium Timespace at Gosport, UK</name>
+ <description><![CDATA[<p><img src="http://www.sundials.co.uk/pix/gosport.jpg"></p>
+Image source:<a href="http://www.sundials.co.uk/pix/gosport.jpg">www.sundials.co.uk</a>]]></description>
+ <LookAt>
+ <longitude>-1.117890647596098</longitude>
+ <latitude>50.79319978711329</latitude>
+ <altitude>0</altitude>
+ <range>79.08348690288113</range>
+ <tilt>0</tilt>
+ <heading>0.02100880488328328</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-1.117887915142518,50.79336425684474,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Britzer Garten, Berlin</name>
+ <description>See photos on this page:
+http://home.arcor.de/ruth.kirsch/sonnenuhr/berlin_1xxxx/berlin_1xxxx.htm</description>
+ <LookAt>
+ <longitude>13.42078373972489</longitude>
+ <latitude>52.4366841172644</latitude>
+ <altitude>0</altitude>
+ <range>102.2086892967038</range>
+ <tilt>0</tilt>
+ <heading>-0.004885703167479627</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>13.4207448482471,52.43682055829985,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Falkenplatz, Berlin</name>
+ <description><![CDATA[<p>The original reasoning event for the construction of the sundial was the UNO climate conference 1995 in Berlin. The base stone of the wall spiral was layed at a festivity at the equinox of March 1995. Until June 1995 the main construction was completed, and at another festivity at the summer solstice the gnonom and the totem ("Lebensbaum") was installed by Berlin fire fighters.</p>
+<p><img src="http://www.surveyor.in-berlin.de/sundials/imgs/Mauerpark-SD01.5.jpg"></p>
+<p>The nearly spiral sundial was planned as a "living sundial" and initiated by the groups of the "Netzwerk Klimagipfel 95", mainly by the journalist T. Römer with the "Verein zur Rettung des Regenwaldes und Naturschutzgebietes La Macarena", and the "Netzwerk Spiel/Kultur" at the Prenzlauer Berg.
+
+
+The covering clay stones were made out of three metric tons of white and brown clay, formed by children of about 50 institutions like school classes and kindergardens of the closer region. The stones were burned and installed in the summer of 1995. Partly they are constructed out of different materials, partly especially formed or ornamented. Six detail images are showing some examples: (White near Red - MC?, Smiley with Heart Eyes, Sun-Moon-Star, Red Broken and Patterned, Rain pits and Stone Hearts in Clay, Red near White - Clay Fish and Sunshine over the Sea).
+
+In September 1995 the sundial was completed. It was called "living sundial" because it was planned to replace the clay stones regulary when they are destroyed and to add some green to the outside wall of the clock. In December 1995 the clock got a special price of the local environmental administration.</p>
+<p><img src="http://www.surveyor.in-berlin.de/sundials/imgs/Mauerpark-Detail02.jpg">.<img src="http://www.surveyor.in-berlin.de/sundials/imgs/Mauerpark-Detail06.jpg"></p>
+
+
+<p>In September 1995 the sundial was completed. It was called "living sundial" because it was planned to replace the clay stones regulary when they are destroyed and to add some green to the outside wall of the clock. In December 1995 the clock got a special price of the local environmental administration.</p>
+<p><img src="http://www.surveyor.in-berlin.de/sundials/imgs/Mauerpark-SD02.5.jpg"></p>
+
+<p>This sundial was deconstructed at the end of 2002 or at the beginning of 2003:</p>
+<p><img src="http://www.surveyor.in-berlin.de/sundials/imgs/Mauerpark-SD03.5.jpg"></p>
+
+<p>Image source and infos:<a href="http://www.surveyor.in-berlin.de/sundials/Falkplatz-e.html">www.surveyor.in-berlin.de</a></p>]]></description>
+ <LookAt>
+ <longitude>13.40239121468946</longitude>
+ <latitude>52.54640622802566</latitude>
+ <altitude>0</altitude>
+ <range>55.75497205265645</range>
+ <tilt>1.489511345854323e-009</tilt>
+ <heading>2.6367660621925e-005</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>13.40233774797299,52.54645010247089,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Halde Schwerin, Castrop-Rauxel, Germany</name>
+ <description><![CDATA[<p>Thanks to <b>htd42</b></p>
+<p><img src="http://www.ruhrgebiet.de/cgi-bin/imp?freizeit/sehenswuerdigkeiten/bindata/castrop-rauxel_halde_schwerin_RVR.jpg&articleID=5824&padding=no&thumbnailtype=jpg&width=250"></p>
+
+http://www.ruhrgebiet.de/freizeit/sehenswuerdigkeiten/cr_halde_schwerin.shtml?print]]></description>
+ <LookAt>
+ <longitude>7.337404407947669</longitude>
+ <latitude>51.54597716006042</latitude>
+ <altitude>0</altitude>
+ <range>51.28632275218226</range>
+ <tilt>2.512805793870883e-009</tilt>
+ <heading>-6.529566789930303e-005</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>7.337359256982781,51.54610609965799,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Lloydminster, Canada</name>
+ <description><![CDATA[This sundial has not the longest gnomon but I think it has the biggest clock face in the world.
+
+<p><img src="http://www.mts.net/~sabanski/sundial/images/sundials%20of%20the%20world/Canada/Lloydminster/BMASP.jpg"></p>
+<p>Image source and infos:<a href="http://www.mts.net/~sabanski/sundial/sotw_canada_lloydminster.htm">www.mts.net</a></p>]]></description>
+ <LookAt>
+ <longitude>-110.0353754682919</longitude>
+ <latitude>53.26386357821667</latitude>
+ <altitude>0</altitude>
+ <range>155.9861269181855</range>
+ <tilt>0</tilt>
+ <heading>-0.01432903343453666</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-110.0355256583979,53.26413794825379,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Giant Lady&apos;s Leg Sundial, Roselawn, Indiana, USA</name>
+ <description><![CDATA[<p>The Sun Aura Nudist Resort opened in 1933. Back then it was called Club Zoro and its founder was Alois Knapp, a Chicago lawyer, German Nacktkulturist, editor of Sunshine and Health magazine, and "the father of nudism in America." </p>
+
+<p><img src="http://www.roadsideamerica.com/attract/images/in/INLAKsundialleg4_0716.jpg"></p>
+
+<p>The club eventually passed into the hands of Dale and Mary Drost. Their son, Dick, had big ideas: he renamed the place Naked City, made it the home of the Ms. Nude Teeny Bopper Contest and the "Erin Go Bra-less" Dance on St. Patrick's Day, and had built the giant lady's leg sundial, 63 feet long and properly positioned to tell time -- a useful feature for wristwatchless nudists. </p>
+
+<p>Naked City closed in 1986 when Dick was run out of Indiana on child molestation charges, but the leg remains and so does the resort, now under new management. The circular main building with the mirror gold windows is a combination office-sauna-restaurant. </p>
+
+<p>The guy who paints the leg told us that Sun Aura is a "clothing optional" camp -- in other words, you don't have to get nude to take a picture of the big lady's leg. But for those who do choose to get into the spirit of things, a helpful sign on the exit road reads, "Stop. You Must Be Dressed Beyond This Point."</p>
+
+<p><a href="http://www.roadsideamerica.com/sights/sightstory.php?tip_AttrId=%3D11825">Roadside America</a></p>]]></description>
+ <LookAt>
+ <longitude>-87.32599841452155</longitude>
+ <latitude>41.14248697221019</latitude>
+ <altitude>0</altitude>
+ <range>40.06529731982877</range>
+ <tilt>0</tilt>
+ <heading>-108.7495178792767</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-87.32608203713804,41.14242622349031,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Ingleside, San Francisco, USA</name>
+ <description><![CDATA[<p>Thanks to <b>CostaPacific</b></p>
+
+Most people in San Francsco have no idea that their city is host to the world's second largest sundial. It was built in 1913 as a gimic to attract people to a new housing development that was built arround the configuration of the old Ingleside Race Track.
+
+<p><img src="http://p.vtourist.com/2174717-Architecture-San_Francisco.jpg"></p>
+<p>Image source:<a href="www.virtualtourist.com"></a>]]></description>
+ <LookAt>
+ <longitude>-122.4687521474299</longitude>
+ <latitude>37.72475779376939</latitude>
+ <altitude>0</altitude>
+ <range>104.1096478961583</range>
+ <tilt>0</tilt>
+ <heading>-6.694029629862418e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-122.4687727980979,37.72497790751523,59.97947112427937</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial Bridge</name>
+ <description>Located in Redding, CA. Opened in 2004 this bridge actually acts as a sundial. The time can be read in a garden on the North side of the bridge.
+
+http://www.turtlebay.org/sundial/sundial.shtml</description>
+ <LookAt>
+ <longitude>-122.3775376532067</longitude>
+ <latitude>40.59329504591046</latitude>
+ <altitude>0</altitude>
+ <range>160.1654912126178</range>
+ <tilt>7.884938307004504e-010</tilt>
+ <heading>0.008470312235033726</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-122.3777030796087,40.59376952663914,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Jaipur,India</name>
+ <description><![CDATA[Thanks to <b>Villaman</b>
+
+<p align="center"><table width=400><tr><td><b><br>Jaipur Observatory Sundial</b><br><br>
+</td>
+</tr>
+<tr>
+<td>
+<p align="justify"> Walk through these doors and up the stairs to begin your journey along a line from Jaipur, India toward the North Celestial Pole. Such cosmic alignments abound in marvelous Indian observatories where the architecture itself allows astronomical measurements. The structures were built in Jaipur and other cities in the eighteenth century by the Maharaja Jai Singh II (1686-1743). Rising about 90 feet high, this stairway actually forms a shadow caster or gnomon, part of what is still perhaps the largest sundial on planet Earth. Testaments to Jai Singh II's passion for astronomy, the design and large scale of his observatories' structures still provide impressively accurate measurements of shadows and sightings of celestial angles.
+<p align="justify"><center><b>Jaipur Observatory Sundial<br><img src="http://apod.nasa.gov/apod/image/0312/02mantar_feresten.jpg" width=300></b></center></p>
+<p align="justify">More <a href="http://apod.nasa.gov/apod/ap031206.html">here.</a>
+
+</td>
+</tr>
+</table>
+<font color="white">]]></description>
+ <LookAt>
+ <longitude>75.82482649881683</longitude>
+ <latitude>26.924766672173</latitude>
+ <altitude>0</altitude>
+ <range>164.397137416247</range>
+ <tilt>0</tilt>
+ <heading>-0.02454798212483729</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>75.82474437483685,26.92504292845888,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Schothorstpark, Amersfoort, Netherlands</name>
+ <description><![CDATA[<p>A large sundial in the Schothorstpark in Amersfoort.
+Thanks to <b>Acadvice</b>]]></description>
+ <LookAt>
+ <longitude>5.385083481782106</longitude>
+ <latitude>52.17868238866643</latitude>
+ <altitude>0</altitude>
+ <range>49.70911801163624</range>
+ <tilt>5.249316070079438e-010</tilt>
+ <heading>6.699999294207586e-006</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>5.385063337537176,52.17873082332495,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Jardin de Reuilly, Paris</name>
+ <description><![CDATA[A huge horizontal sundial in Paris.
+<p><img src="http://perso.orange.fr/cadrans.solaires/cadrans/images/reuilly1-500.jpg" width="400" hight="250"></p>
+
+<p><img src="http://perso.orange.fr/cadrans.solaires/cadrans/images/reuilly2-500.jpg" width="360" hight="250"></p>
+Image source:<a href="http://perso.orange.fr/cadrans.solaires/cadrans/originaux-paris.html">http://perso.orange.fr</a></p>]]></description>
+ <LookAt>
+ <longitude>2.387204592843604</longitude>
+ <latitude>48.84242901629369</latitude>
+ <altitude>0</altitude>
+ <range>50.11592463998582</range>
+ <tilt>8.113900329668256e-010</tilt>
+ <heading>-0.001210217218456717</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>2.38716774037826,48.84252766103683,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Stockgrove, Soulbury, Buckinghamshire, UK</name>
+ <description><![CDATA[<p>thanks to <b>houdinia</b></p>
+Sundial with analemmatic clock face.
+<p><img src="http://www.greensandtrust.org/images/newImages/stockStrip1.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-0.666503881371199</longitude>
+ <latitude>51.95548351688392</latitude>
+ <altitude>0</altitude>
+ <range>55.27920580004575</range>
+ <tilt>6.264058771241075e-010</tilt>
+ <heading>0.06911766261471311</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-0.6665014664411046,51.95551857959676,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Halde Hoheward, Germany</name>
+ <description><![CDATA[<p>The Obelisk – The Sundial</p>
+
+<p>The seeming movement of the sun in the sky, resulting in the discrimination between day and night, was one of the earliest observations of nature performed by men. It enables us to experience the phenomenon “time†with our own senses. The first examples for telling the time with the help of the sun or its shadow date back to the Ancient World. The are numerous archetypes for sundials built inmany different styles, using different techniques.</p>
+
+<p>The archetype for the horizontal sundial on top of the slagheap Hoheward is the sundial of the Roman Emperor Augustus on the Campus Martius in Rome.</p>
+(It is unknown, whether this ancient obelisk was part of a complete sun dial with hour and declination lines on the morning and afternoon side or whether only a meridian line existed to measure the elevation of the sun in upper culmination. The today's scientific knowledge indicates the existence of a meridian.) The observation of the Obelisk's shadow on the sundial enables the observer to easily determine date and time. Apart from “time†one can also experience the laws of celestial mechanics. Men encounter themselves in relation to the cosmos.</p>
+
+<p>Representing the first step in the realisation of the Astronomical Theme Park the Obelisk was opened on May 17th, 2005. It is located on the already completed south-eastern plateau of the slagheap at a height of 140 m above sea level. The shadowed area is 62 m in diameter.</p>
+
+
+
+<p><img src="http://www.horizontastronomie.de/bilder/ansicht-obelisk2.jpg"></p>
+This picture shows the Obelisk after the end of the assembly on the day of the opening. Shortly before it was put on top of the readily prepared pedestal by a helicopter and then bolted.
+
+<p><img src="http://www.horizontastronomie.de/bilder/o2.jpg" width="300" hight="360"></p>
+
+<p><a href="http://www.horizontastronomie.de/eindex.htm">http://www.horizontastronomie.de</a></p>
+
+
+
+<p>http://de.wikipedia.org/wiki/Halde_Hoheward#Sonnenuhr_mit_Obelisk</p>
+
+<p>http://www.horizontastronomie.de/animationen.htm</p>]]></description>
+ <LookAt>
+ <longitude>7.170033145228383</longitude>
+ <latitude>51.56646738931531</latitude>
+ <altitude>0</altitude>
+ <range>96.7791497847863</range>
+ <tilt>4.155528307086707e-010</tilt>
+ <heading>0.006376147752644328</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>7.169892708740022,51.56683509795316,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Fachhochschule (FH) Bielefeld</name>
+ <description><![CDATA[A sundial with analemmatic layout.
+<p><img src="http://www.fh-bielefeld.de/ezimagecatalogue/catalogue/variations/4651-400x500.jpg"></p>
+http://www.fh-bielefeld.de/article/fh/4412/1/505?NavItemID=0&NavCatID=162]]></description>
+ <LookAt>
+ <longitude>8.555263115842216</longitude>
+ <latitude>52.02672953436973</latitude>
+ <altitude>0</altitude>
+ <range>50.10364671714684</range>
+ <tilt>0</tilt>
+ <heading>0.001255164290936946</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>8.555215193531964,52.02681111856448,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Sun City, Arizona</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/008_az_suncity.jpg"></p>
+
+<p><img src="http://www.sundials.org/registry/regphotos/008_az_suncity_2.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-112.2739996808105</longitude>
+ <latitude>33.61902729376313</latitude>
+ <altitude>0</altitude>
+ <range>44.66059102278575</range>
+ <tilt>0</tilt>
+ <heading>0.0001994953180518285</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-112.2740228273864,33.61913038777643,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Georgina Blach Intermediate School, Los Altos, CA</name>
+ <description><![CDATA[A corner of a roof used as sundial.
+<p><img src="http://www.sundials.org/registry/regphotos/498_ca_losaltos_blach.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-122.083063541274</longitude>
+ <latitude>37.36394994353518</latitude>
+ <altitude>0</altitude>
+ <range>99.46493929648614</range>
+ <tilt>0</tilt>
+ <heading>-6.524992683547596e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-122.0831077334675,37.3641379192763,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Hilltop Park, San Francisco</name>
+ <description><![CDATA[A giant sundial 70 feet in diameter with a bright yellow painted steel gnomon 78 feet long. The dial has a cement base and was designed to be used as the stage for a surrounding amphitheater.
+<p><img src="http://www.sundials.org/registry/regphotos/419_ca_sanf_hunters_ridge.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-122.3837414260284</longitude>
+ <latitude>37.73308769461563</latitude>
+ <altitude>0</altitude>
+ <range>76.96447255875415</range>
+ <tilt>0</tilt>
+ <heading>-0.0001251047167258125</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-122.3837885185873,37.73313852750733,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Berlin-Weißensee</name>
+ <description><![CDATA[A nice sundial made of flowers.
+<p><img src="http://www.be.schule.de/schulen/wfs/pages/sundials/Blumenuhr,Weissensee.gif"></p>
+
+http://www.be.schule.de/schulen/wfs/pages/sundials/Weissensee.html]]></description>
+ <LookAt>
+ <longitude>13.46637059089964</longitude>
+ <latitude>52.55408525446345</latitude>
+ <altitude>0</altitude>
+ <range>35.24186259647233</range>
+ <tilt>0</tilt>
+ <heading>-0.002133411261797274</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>13.46637589519183,52.55412143657096,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Olbers-Planetarium, Bremen</name>
+ <description><![CDATA[This is an armillary sphere sundial.
+
+<p><img src="http://planetarium.hs-bremen.de/bremen/pics/suhs.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>8.806980778676786</longitude>
+ <latitude>53.06988134466393</latitude>
+ <altitude>0</altitude>
+ <range>24.09705977000565</range>
+ <tilt>0</tilt>
+ <heading>-0.001876272046377585</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>8.806963468445417,53.0698959991562,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Westbroekpark, Denn Haag</name>
+ <description><![CDATA[This sundial is located at the Rosarium in the Westbroekpark, Denn Haag.
+<p><img src="http://www.denhaag.nl/Pics/dsb/Ststr/westbroekpark/rozenconcours45/rozenbedden-met-zonnewijzer.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>4.290891177932192</longitude>
+ <latitude>52.10450647693549</latitude>
+ <altitude>0</altitude>
+ <range>20.57779559985518</range>
+ <tilt>0</tilt>
+ <heading>-0.8669355345663358</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>4.290865552422943,52.10453275113748,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Amersfoort, Netherlands</name>
+ <description><![CDATA[In the arrow are 2 digital displays, one for civil time and one for local suntime.
+<p><img src="http://sundials.org/links/local/sunpointer/sunpointer.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>5.374167244217593</longitude>
+ <latitude>52.15310253836927</latitude>
+ <altitude>0</altitude>
+ <range>31.45592479376158</range>
+ <tilt>1.426589610824431e-009</tilt>
+ <heading>-0.01164696084898205</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>5.374145665653813,52.15310809583514,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Botanical Gardens, Sydney, Australia</name>
+ <description><![CDATA[This is an armillary sphere sundial.
+
+<p><img src="http://www.sundials.co.uk/PC090189.jpg"></p>
+
+<p><img src="http://www.sundials.co.uk/PC090193.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>151.2154952669206</longitude>
+ <latitude>-33.86399908828604</latitude>
+ <altitude>0</altitude>
+ <range>16.43666728184123</range>
+ <tilt>8.675342058213797e-007</tilt>
+ <heading>-0.002067228419448193</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>151.2154882763944,-33.86398565287625,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Team Disney Sundial, Walt Disney World, Florida</name>
+ <description><![CDATA[Thanks to <b>Oftencold</b>
+
+<p><img src="http://www.sunpath-designs.com/disdial2b.gif"></p>
+
+http://www.de-zonnewijzerkring.nl/zw-arch/eng-home-zw-07-02.htm]]></description>
+ <LookAt>
+ <longitude>-81.52113085122878</longitude>
+ <latitude>28.36541360352638</latitude>
+ <altitude>0</altitude>
+ <range>167.7307771712135</range>
+ <tilt>1.015026730473625e-011</tilt>
+ <heading>-0.006287852151169638</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-81.52134276012195,28.36559634883421,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Janskerkhof, Utrecht, Netherlands</name>
+ <description><![CDATA[A sundial with analemmatic layout.
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/zw-06-06-01.jpg"></p>
+
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/zw-06-06-02.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>5.121095723583527</longitude>
+ <latitude>52.09338586502101</latitude>
+ <altitude>0</altitude>
+ <range>24.25734051739648</range>
+ <tilt>5.490226183683639e-010</tilt>
+ <heading>-0.0007122606404517594</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>5.121088800707085,52.09341776135472,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, San Jose Rep Theater, San Jose, CA</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/f96acd0f-6ad7-4b0a-a4a0-e52d14dd0015.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM247Q">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-121.8860266085782</longitude>
+ <latitude>37.33361545835343</latitude>
+ <altitude>0</altitude>
+ <range>32.31958319185324</range>
+ <tilt>0</tilt>
+ <heading>1.418565866412994e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-121.886064353331,37.33364018615777,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Millennium Sundial, Greenwich Park, London</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/7cdcd20c-806c-4cbd-8a9c-5de27ffb115d.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM273J">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-0.001522539653513039</longitude>
+ <latitude>51.48136176862654</latitude>
+ <altitude>0</altitude>
+ <range>61.96314954770909</range>
+ <tilt>2.850197260451716e-009</tilt>
+ <heading>-0.002911073287638733</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-0.00156808979284051,51.48142700407306,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Veterans Park, Waukesha, WI</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/0e6202ea-2b62-4491-9029-0983445e7cde.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM2DTR">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-88.2367572684424</longitude>
+ <latitude>43.00995357504599</latitude>
+ <altitude>0</altitude>
+ <range>49.0879478099675</range>
+ <tilt>0</tilt>
+ <heading>-2.769547716555237e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-88.23678272979073,43.01004377682637,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Underground Sundial, Munich, Germany</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/6f0381f1-4c18-4d9d-95e5-2f9a79842dbe.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM12X2">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>11.70480163926041</longitude>
+ <latitude>48.13338615699044</latitude>
+ <altitude>0</altitude>
+ <range>49.09160069235252</range>
+ <tilt>7.359413992305611e-011</tilt>
+ <heading>1.363313751616389e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>11.70474103166116,48.13350333174798,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Crown Hill cemetery, Indianapolis, Indiana, USA</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/d62d27fc-f175-4ac8-aab3-7ba9748d6b82.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1310">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-86.17300915391851</longitude>
+ <latitude>39.82668935299838</latitude>
+ <altitude>0</altitude>
+ <range>35.63730089613371</range>
+ <tilt>0</tilt>
+ <heading>2.616180723282867e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-86.17304253331795,39.82668119645058,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Coppell, TX</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/2bb0da85-d5db-44ed-ae70-d89f290ad7fd.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1WJG">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-97.02194975520763</longitude>
+ <latitude>32.95633568822581</latitude>
+ <altitude>0</altitude>
+ <range>61.19896168864369</range>
+ <tilt>0</tilt>
+ <heading>1.826645706530163e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-97.02199840401494,32.95643533824669,0</coordinates>
+ </Point>
+ </Placemark>
+ </Folder>
+ <Folder>
+ <name>Low Resolution</name>
+ <Placemark>
+ <name>Sundial, Kota Baru Parahyangan</name>
+ <description><![CDATA[Thanks to <b>voorburger</b>.
+<p><img src="http://www.thebiggestsundial.com/php/thebiggestsundial/images/Exhibits/13/03052007134130.gif"></p>]]></description>
+ <LookAt>
+ <longitude>107.4940550739811</longitude>
+ <latitude>-6.852038750176605</latitude>
+ <altitude>0</altitude>
+ <range>296.7282563680993</range>
+ <tilt>2.08633946131246e-011</tilt>
+ <heading>0.5509822616366601</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>107.4939718861608,-6.851748821808833,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Pajala, Sweden</name>
+ <description><![CDATA[<p>The world's biggest sundial today is in the Torne Valley, north of the Arctic Circle. The Guinness Book of Records has put Pajala, northern Sweden, on the map, and its sundial - formed as a "round square". </p>
+<p><img src="http://www.pajala.se/mun/pajala/www.nsf/($all)/A43859111165E6F2C1256EE40021DDF3/$file/soltorg23.JPG"></p>
+<p>The sundial in Pajala, 38.33 m. in diameter, holds the world record, according to the Guinness Book of Records. The previous record was held by Disney World in Orlando, Florida, with 37.18 m. </p>
+
+<p>The sundial was inaugurated by the Swedish Minister of Labour Margareta Winberg in July 1996. Pajala is situated at 23.28 ° East, 67.21 ° North, which is 70 km north of the Arctic Circle, making a circular sundial possible. This is due to the fact that the Midnight Sun describes a complete circle over the horizon.
+
+<p>Its masts of dried fir form a unique spatiality around a circular "square". The site is especially used for local functions such as Pajala Fair, Romp Week and the Northern Lights Festival. </p>
+
+<p>The central square in Pajala, through its size and latitude, offered conditions for a sundial dedicated to the Midnight Sun. Architect Mats Winsa took his inspiration from the square in Siena, and for the sculptures in the park - astronomical instruments in India dating back to the 18th century. Naturally, it was a challenge to compete with the previous record from 1991 by the world-famous Japanese architect, Arata Isozaki. </p>
+
+<p>The sundial captures the sun's movement by allowing the shadow of the central gnomon to fall across the hour divisions of the surrounding posts. The gnomon, like the Earth's axis, points toward the Pole Star, which according to Finnish-Ugrian mythology (the region has Finnish roots) holds up the firmament. The "sun wheel" embedded in the ground here (forming a cross in the circle) is in fact a calendar. Water bubbles up from four sources corresponding to the four principal points of the compass. The water gathers in the central pond, which was designed with children in mind. </p>
+
+<p>For their survival, humans have followed the rhythm of the sun. The need to observe the changing seasons and days led to the early development of the sundial. Our lives today are characterised by obedience to mechanical and national time - inventions separate from true solar time. The sundial displays true solar time, which in Pajala is half-an-hour ahead of national time. </p>
+
+<p>The sundial in Pajala celebrates light, and acts as a reminder of its significance for all life by functioning as a biological clock in a world fettered by artificial time. The hormone rush in spring reminds us of our direct dependence on sunlight as living beings.</p>
+
+<p>Info and image source:<a href="http://www.pajala.se/mun/pajala/www.nsf/english/$first?open&mname=Menu%C2%A4menuID=2CAE%C2%A4start=1">www.pajala.se</a></p>
+
+<p><img src="http://holmers.com/Pajala/Pajala1_050709.jpg"></p>
+<p>Image source:<a href="http://holmers.com/Pajala/Pajala1_050709.jpg">http://holmers.com</a></p>]]></description>
+ <LookAt>
+ <longitude>23.36723004664742</longitude>
+ <latitude>67.21282676944374</latitude>
+ <altitude>0</altitude>
+ <range>124.9604027877409</range>
+ <tilt>3.010594647959025e-010</tilt>
+ <heading>-1.130925335798896</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>23.36716252896882,67.21299216873888,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Tenerife, Spain</name>
+ <description><![CDATA[Reloj del sol
+
+<p><img src="http://members.aon.at/mbrandn6/media/fotos/landart/watchteneriffa/tentotal1.jpg"></p>
+
+<p><img src="http://members.aon.at/mbrandn6/media/fotos/landart/watchteneriffa/tennah.jpg"></p>
+
+<p><img src="http://members.aon.at/mbrandn6/media/fotos/landart/watchteneriffa/tentotal3.jpg"></p>
+
+<p><img src="http://members.aon.at/mbrandn6/media/fotos/landart/watchteneriffa/draft.jpg"></p>
+<p>Image source:<a href="http://members.aon.at/mbrandn6/pagesgerman/framesetg/framelandart.html">http://members.aon.at</a></p>
+
+<p><a href="http://www.teneriffa.panoshot.de/de/panorama-interaktiv/gernot-huber-stiftung-reloj-del-sol-223.html
+">Interactice picture</a></p>]]></description>
+ <LookAt>
+ <longitude>-16.56926659562192</longitude>
+ <latitude>28.08256590461729</latitude>
+ <altitude>0</altitude>
+ <range>88.29371157400612</range>
+ <tilt>0</tilt>
+ <heading>8.633540737161729e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-16.5693071701084,28.08261960124695,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Perranporth, UK</name>
+ <description><![CDATA[The Millennium Sundial was designed by Stuart Thorn. It tells Cornish time which is 20 minutes ahead of GMT.
+<p><img src="http://www.cornwalls.co.uk/photos/data/media/2/perranporth_sundial.jpg" width="421" hight="280"></p>]]></description>
+ <LookAt>
+ <longitude>-5.157517535037663</longitude>
+ <latitude>50.34723421976403</latitude>
+ <altitude>0</altitude>
+ <range>65.69642310338585</range>
+ <tilt>0</tilt>
+ <heading>-0.01795551609583625</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-5.157537433789316,50.34733238709538,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Council Bluffs, Iowa, USA</name>
+ <description><![CDATA[Large sundial at Council Bluffs Public Library
+
+<p><img src="http://www.sunpath-designs.com/librarydial1b.jpg" width="400" hight="300"></p>
+<p><img src="http://www.sunpath-designs.com/librarydial2b.jpg" width="400" hight="300"></p>
+<p><img src="http://www.sunpath-designs.com/librarydial3b.jpg" width="400" hight="300"></p>]]></description>
+ <LookAt>
+ <longitude>-95.84953495410247</longitude>
+ <latitude>41.25887711431908</latitude>
+ <altitude>0</altitude>
+ <range>196.5752069699831</range>
+ <tilt>2.583166383376495e-010</tilt>
+ <heading>0.0002124063872384501</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-95.84981881431206,41.25888611306795,294.4878429401121</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial. Meckhofen, Leverkusen, Germany</name>
+ <description><![CDATA[<p><img src="http://www.leverkusen.de/stadtportrait/partnerstaedte/400pxPartnerstaedteSonnenuhr01.jpg"></p>
+<p>Image source:<a href="v">www.leverkusen.de</a></p>
+
+<p><img src="http://www.lev2000.de/guide/Archiv1.jpg/39/Sonnenuhr01.jpg"></p>
+
+<p><img src="http://www.lev2000.de/guide/Archiv1.jpg/39/Sonnenuhr02.jpg"></p>
+<p>Image source:<a href="http://www.lev2000.de/guide/Archiv1.jpg/39/Sonnenuhr02.jpg">www.lev2000.de</a></p>]]></description>
+ <LookAt>
+ <longitude>7.083354426150351</longitude>
+ <latitude>51.04845387008112</latitude>
+ <altitude>0</altitude>
+ <range>66.17616066250443</range>
+ <tilt>9.735256695418331e-010</tilt>
+ <heading>0.0006924896867520876</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>7.083321386023442,51.04852440832129,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Adler Planetarium, Chicago, USA</name>
+ <description><![CDATA[<p><img src="http://www.wherry.com/photos/2001-04-29-chicago/DSCN1221-m.jpg" width="500" hight="375"></p>]]></description>
+ <LookAt>
+ <longitude>-87.60711153340705</longitude>
+ <latitude>41.86674796371171</latitude>
+ <altitude>0</altitude>
+ <range>27.37440941953917</range>
+ <tilt>0</tilt>
+ <heading>0.008419825260544345</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-87.60710764637246,41.86681374132155,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Rose Garden Sundial, Christchurch, New Zealand</name>
+ <description><![CDATA[<p>Thanks to <b>NormB</b></p>
+<a href="http://www.ccc.govt.nz/Parks/BotanicGardens/tour_gardens_central_rose_garden.asp">Rose Garden History</a>
+
+<p>
+<b>Photo - NormB 11th April 2006<br>
+<img src="http://img527.imageshack.us/img527/509/sundialig8.jpg" alt="Image Hosted by ImageShack.us" /><p>
+<b>Photo - NormB 11th April 2006<br>
+<img src="http://img527.imageshack.us/img527/786/sundialplaquefn9.jpg" alt="Image Hosted by ImageShack.us" />]]></description>
+ <LookAt>
+ <longitude>172.621331272394</longitude>
+ <latitude>-43.53038034442864</latitude>
+ <altitude>0</altitude>
+ <range>86.04933199573917</range>
+ <tilt>0</tilt>
+ <heading>1.801092527765711</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>172.6213650004974,-43.53035465311722,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Natchez Park</name>
+ <description><![CDATA[<p>Thanks to <b>caroling</b></p>
+
+In Seaside, NW FL, USA on the Emerald Coast. Panoramic images and movies of a sundial and visions of Xtals (energy crystals) on the March equinox, 2006. See http://www.wholeo.net/Trips/Art/Web/TripsArt/Travel/Florida/borders/flBorders.htm]]></description>
+ <LookAt>
+ <longitude>-86.14177717779702</longitude>
+ <latitude>30.32184243688109</latitude>
+ <altitude>0</altitude>
+ <range>46.50596341362312</range>
+ <tilt>9.523139707563741e-010</tilt>
+ <heading>0.0925379903960088</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <altitudeMode>relativeToGround</altitudeMode>
+ <coordinates>-86.14183223138707,30.32193188899003,3</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Charlotte, North Carolina, USA</name>
+ <description><![CDATA[<p>Thanks to <b>BrettHo</b></p>
+On the roof of the International Trade Center is this gigantic sundial.]]></description>
+ <LookAt>
+ <longitude>-80.84002590296151</longitude>
+ <latitude>35.22682691631484</latitude>
+ <altitude>0</altitude>
+ <range>73.21919569418378</range>
+ <tilt>0</tilt>
+ <heading>12.34188537748346</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-80.84002447413604,35.22696160522812,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>KTPalmerSundial, Carefree, Arizona, USA</name>
+ <description><![CDATA[Thanks to <b>seer</b>.
+<p><img src="http://www.sundials.org/registry/regphotos/001_az_carefree_2.jpg"></p>
+http://www.bigwaste.com/photos/az/sundial/]]></description>
+ <LookAt>
+ <longitude>-111.9217799027029</longitude>
+ <latitude>33.8245907883639</latitude>
+ <altitude>0</altitude>
+ <range>119.8165563905356</range>
+ <tilt>2.774426682549449e-010</tilt>
+ <heading>-1.574999619300427e-005</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-111.9218327194278,33.82468559440962,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, University of Science and Technology, Hong Kong</name>
+ <description><![CDATA[<p><img src="http://perso.orange.fr/cadrans.solaires/cadrans/images/Maes-hongkong.jpg" width="295" height="370"></p>
+
+<p>Image source:<a href="http://perso.orange.fr/cadrans.solaires/cadrans/originaux-monde.html">http://perso.orange.fr</a></p>]]></description>
+ <LookAt>
+ <longitude>114.2630116779084</longitude>
+ <latitude>22.33749401387006</latitude>
+ <altitude>0</altitude>
+ <range>111.6162130745504</range>
+ <tilt>0</tilt>
+ <heading>0.0003913059632004609</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>114.2629690669868,22.33764072332584,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Pekin, Illinois</name>
+ <description><![CDATA[<p><img src="http://www.pekin.net/sundial/sundial.jpg"></p>
+<p>Image source:<a href="http://www.pekin.net/sundial/index.html">www.pekin.net</a></p>
+
+<p><img src="http://www.sundials.org/registry/regphotos/233_il_perkin_parkdial.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-89.63076522889526</longitude>
+ <latitude>40.56267466732153</latitude>
+ <altitude>0</altitude>
+ <range>161.1716772997438</range>
+ <tilt>0</tilt>
+ <heading>0.009112399365723663</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-89.63089561079578,40.56281064339486,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Edinburg, Hidalgo, USA</name>
+ <description><![CDATA[<p><img src="http://www.sunpath-designs.com/utdial2b.GIF" width="300" hight="400"></p>
+
+<p><img src="http://www.sunpath-designs.com/utdial1b.gif"width="400" hight="300"><7p>]]></description>
+ <LookAt>
+ <longitude>-98.17095602857175</longitude>
+ <latitude>26.30618568257091</latitude>
+ <altitude>0</altitude>
+ <range>122.1950947751469</range>
+ <tilt>0</tilt>
+ <heading>-0.005400653570135644</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-98.17104492887813,26.30639237212602,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Keppel Henge,</name>
+ <description><![CDATA[<p><img src="http://www.mts.net/~sabanski/sundial/images/sundials%20of%20the%20world/Canada/Keppel%20Henge/sundial1.jpg"></p>
+<p>http://www.steveirvine.com/sundial.html</p>
+
+
+http://www.mts.net/~sabanski/sundial/sotw_canada_keppel.htm]]></description>
+ <LookAt>
+ <longitude>-80.94374423682251</longitude>
+ <latitude>44.79038599160477</latitude>
+ <altitude>0</altitude>
+ <range>164.0454159373261</range>
+ <tilt>0</tilt>
+ <heading>-0.007334046679263517</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-80.94383190841853,44.79038705635566,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial at Science North, Sudbury, Ontario</name>
+ <description><![CDATA[<p><img src="http://www.mts.net/~sabanski/sundial/images/sundials%20of%20the%20world/Canada/Science%20North/celeste1.jpg"></p>
+
+<p><img src="http://www.mts.net/~sabanski/sundial/images/sundials%20of%20the%20world/Canada/Science%20North/celeste2.jpg"></p>
+
+<p><img src="http://www.mts.net/~sabanski/sundial/images/sundials%20of%20the%20world/Canada/Science%20North/celeste3.jpg"></p>
+
+http://www.mts.net/~sabanski/sundial/sotw_canada_sn.htm]]></description>
+ <LookAt>
+ <longitude>-80.99582033913947</longitude>
+ <latitude>46.46976830028441</latitude>
+ <altitude>0</altitude>
+ <range>85.82915438648354</range>
+ <tilt>0</tilt>
+ <heading>0.0003317215281456315</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-80.99588716181201,46.46988111501548,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Amble, UK</name>
+ <description><![CDATA[<p><img src="http://ourworld.compuserve.com/homepages/Patrick_Powers/Amble.jpg"></p>
+http://ourworld.compuserve.com/homepages/Patrick_Powers/amble.htm]]></description>
+ <LookAt>
+ <longitude>-1.581634687429885</longitude>
+ <latitude>55.33514811404725</latitude>
+ <altitude>0</altitude>
+ <range>62.55005662709024</range>
+ <tilt>8.224100904372228e-010</tilt>
+ <heading>-0.008198736253532122</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-1.581720999552488,55.3352025087941,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, University of Maryland, College Park</name>
+ <description><![CDATA[<p><img src="http://www.mts.net/~sabanski/sundial/images/sundials%20of%20the%20world/USA/Maryland/Univ%20of%20Maryland%20.jpg"></p>
+http://www.mts.net/~sabanski/sundial/sotw_usa_mland.htm]]></description>
+ <LookAt>
+ <longitude>-76.94256839624576</longitude>
+ <latitude>38.98603731470438</latitude>
+ <altitude>0</altitude>
+ <range>69.47353847793947</range>
+ <tilt>0</tilt>
+ <heading>-0.00947513273561203</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-76.94253686137193,38.98616316295006,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Fort San Felipe del Morro, Puerto Rico</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/564_cuba_habana_1.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-66.11899284422442</longitude>
+ <latitude>18.46786530709565</latitude>
+ <altitude>0</altitude>
+ <range>122.9114928009769</range>
+ <tilt>0</tilt>
+ <heading>0.001639161983653822</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-66.11900470518663,18.4679529172629,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Rose Garden, Phoenix</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/007_az_phoenix.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-112.0911976535298</longitude>
+ <latitude>33.47007786030556</latitude>
+ <altitude>0</altitude>
+ <range>26.72933602203598</range>
+ <tilt>4.053298886062559e-011</tilt>
+ <heading>0.0001093808645832187</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-112.0912131593616,33.4701136927338,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Tucson, Arizona</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/012_az_tucson_la_pilita.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-110.9748374101104</longitude>
+ <latitude>32.21591986778585</latitude>
+ <altitude>0</altitude>
+ <range>42.12321141209996</range>
+ <tilt>0</tilt>
+ <heading>-0.0002919115031976927</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-110.9748562940359,32.21593667064053,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Flandrau Planetarium, Tucson</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/013_az_tuscon_flandrau_planetarium.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-110.9477979774635</longitude>
+ <latitude>32.23224398378896</latitude>
+ <altitude>0</altitude>
+ <range>33.29181342845133</range>
+ <tilt>3.6608792363658e-017</tilt>
+ <heading>0.0001605580448802178</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-110.9478231994691,32.23228861367718,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Vietnam Veterans Memorial, Kentucky</name>
+ <description><![CDATA[<p><img src="http://www.vietvet.org/images/vn/billm/rags9.gif"></p>
+
+http://www.vietvet.org/kymem.htm]]></description>
+ <LookAt>
+ <longitude>-84.8640348419774</longitude>
+ <latitude>38.17725413584271</latitude>
+ <altitude>0</altitude>
+ <range>136.9757698325458</range>
+ <tilt>1.322889725758878e-010</tilt>
+ <heading>-0.0003615314930558497</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-84.86405079639164,38.17749508752453,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Claremont, California</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/505_ca_claremont_1.jpg"></p>
+
+<p><img src="http://www.sundials.org/registry/regphotos/505_ca_claremont_2.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-117.7288129576152</longitude>
+ <latitude>34.0992297660836</latitude>
+ <altitude>0</altitude>
+ <range>60.73786036422235</range>
+ <tilt>1.321942869740197e-009</tilt>
+ <heading>-0.002677989156069468</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-117.7288254316814,34.09928418001653,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Hoogezand, Netherlands</name>
+ <description><![CDATA[The sundial in the Gorechtpark was built in 1994 by the artist Chris Verbeek.
+<p><img src="http://www.hoogezand-sappemeer.nl/plaat.php?fileid=3054"></p>
+
+http://www.hoogezand-sappemeer.nl/index.php?simaction=content&mediumid=10&pagid=335&fontsize=10&stukid=2597]]></description>
+ <LookAt>
+ <longitude>6.73589462010654</longitude>
+ <latitude>53.15594584104552</latitude>
+ <altitude>0</altitude>
+ <range>139.6528910743265</range>
+ <tilt>1.45482979338997e-010</tilt>
+ <heading>0.002950231733866508</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>6.73578802230557,53.15607461082266,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Hoogeveen, Netherlands</name>
+ <description><![CDATA[Sundial at Unigarant, Hoogeveen.
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/werk-07-05-01.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>6.469908327982116</longitude>
+ <latitude>52.72012840714818</latitude>
+ <altitude>0</altitude>
+ <range>73.67703044709106</range>
+ <tilt>4.200981642085038e-012</tilt>
+ <heading>-0.0001367978398152192</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>6.469803362243752,52.72017851542101,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Bicentennial Park, Homebush, Australia</name>
+ <description><![CDATA[This 'walkthrough' sundial with a gnomon 8m long is well worth a visit and is near to the Olympic Park site of the 2000 Olympics. Hour lines and declination lines are set as brass strips in a concrete base dialface.
+<p><img src="http://www.rnzih.org.nz/images/sun13.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>151.0785472180646</longitude>
+ <latitude>-33.84641177017981</latitude>
+ <altitude>0</altitude>
+ <range>163.2808310648841</range>
+ <tilt>1.201655085829064e-011</tilt>
+ <heading>4.265000695512084e-006</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>151.0784520824468,-33.84631674048389,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Heerenveen, Netherlands</name>
+ <description><![CDATA[A sundial in a roundabout.
+<p><img src="http://upload.wikimedia.org/wikipedia/commons/thumb/0/02/Zonnewijzer_Heerenveen_16.JPG/300px-Zonnewijzer_Heerenveen_16.JPG"></p>]]></description>
+ <LookAt>
+ <longitude>5.948360581453846</longitude>
+ <latitude>52.95021342348947</latitude>
+ <altitude>0</altitude>
+ <range>125.5263208537314</range>
+ <tilt>3.779142327674902e-010</tilt>
+ <heading>2.174750871196e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>5.94828156186523,52.95041125062435,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Zoetermeer, Netherlands</name>
+ <description><![CDATA[A triangle shaped sundial.
+<p><img src="http://www.chabot.demon.nl/images/Zoetermeer03.jpg"></p>
+
+http://www.chabot.demon.nl/sundials/index3.htm]]></description>
+ <LookAt>
+ <longitude>4.48801136665803</longitude>
+ <latitude>52.03630549285332</latitude>
+ <altitude>0</altitude>
+ <range>64.63218166015471</range>
+ <tilt>0</tilt>
+ <heading>-0.0001443420778625332</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>4.48796065586356,52.03633054351467,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Lancaster, Lancashire, UK</name>
+ <description><![CDATA[A very nice analemmanic sundial made of stone.
+<p><img src="http://static2.bareka.com/photos/medium/2472257/lancaster-sundial.jpg"></p>
+
+<p><img src="http://static4.bareka.com/photos/medium/2472163/becoming-gnomon.jpg"></p>
+
+<p><img src="http://static2.bareka.com/photos/medium/2472201/telling-time.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-2.781711751106886</longitude>
+ <latitude>54.04618182827939</latitude>
+ <altitude>0</altitude>
+ <range>59.27999100628823</range>
+ <tilt>0</tilt>
+ <heading>2.19677695423205e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-2.781728090880108,54.0462693701831,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Nida, Lithuania</name>
+ <description><![CDATA[A large horizontal sundial.
+<p><img src="http://static1.bareka.com/photos/medium/47224/nida.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>20.99037235133227</longitude>
+ <latitude>55.29501544197078</latitude>
+ <altitude>0</altitude>
+ <range>177.9373429950499</range>
+ <tilt>4.620370977113893e-011</tilt>
+ <heading>0.0005344762650417512</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>20.99033020665709,55.29525661423606,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Tavel, France</name>
+ <description><![CDATA[A very large sundial, sadly in low res.
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/zw-07-03-01.jpg" width="360" hight="270"></p>
+
+http://www.de-zonnewijzerkring.nl/zw-arch/eng-home-zw-07-03.htm]]></description>
+ <LookAt>
+ <longitude>4.700355808916944</longitude>
+ <latitude>44.00154771856498</latitude>
+ <altitude>0</altitude>
+ <range>254.0752666918187</range>
+ <tilt>2.46623000787332e-010</tilt>
+ <heading>-0.0002391009248289202</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>4.700351044280055,44.00172761202828,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, St. Michielsgestel, Netherlands</name>
+ <description><![CDATA[A large sundial in the gardens of the Institute for the Deaf in Sint Michielsgestel.
+
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/zw-06-10-01.jpg"></p>
+
+<p><img src="http://www.de-zonnewijzerkring.nl/imgs-arch/zw-06-10-02.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>5.346086124850936</longitude>
+ <latitude>51.64327189620946</latitude>
+ <altitude>0</altitude>
+ <range>111.9437734662239</range>
+ <tilt>6.47253437341193e-010</tilt>
+ <heading>0.002375287388397793</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>5.346049636943462,51.64334209867396,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Halle Saale, Germany</name>
+ <description><![CDATA[Analemmatic sundial at the Planetarium
+"Sigmund Jähn" planetarium, Halle.
+<p><img src="http://home.arcor.de/peter.lindner/sonnenuhr/h/halle_saale_061xx/5973_peissnitzinsel_planetarium_200707163002.jpg"></p>
+
+More photos:http://home.arcor.de/peter.lindner/sonnenuhr/h/halle_saale_061xx/halle_saale_061xx.htm]]></description>
+ <LookAt>
+ <longitude>11.94846541701928</longitude>
+ <latitude>51.49449346439673</latitude>
+ <altitude>0</altitude>
+ <range>51.14591828296211</range>
+ <tilt>0</tilt>
+ <heading>0.0003207363265956715</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>11.9484244247504,51.49452140024068,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Abano Terme, Italy</name>
+ <description><![CDATA[<p><img src="http://members.aon.at/sundials/images/italia/i_07207.jpg"></p>
+
+http://members.aon.at/sundials/bild43_d.htm]]></description>
+ <LookAt>
+ <longitude>11.7902588657866</longitude>
+ <latitude>45.36024293920432</latitude>
+ <altitude>0</altitude>
+ <range>88.27261765894279</range>
+ <tilt>5.645767845941023e-011</tilt>
+ <heading>0.0006214879519801648</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>11.79017178556217,45.36037512888652,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Gasworks Park, Seattle, USA</name>
+ <description><![CDATA[The sundial at Gas Works Park
+<p><img src="http://www.magnusonpark.org/images/sundial.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-122.3362979085422</longitude>
+ <latitude>47.64532276753428</latitude>
+ <altitude>0</altitude>
+ <range>144.9142629968483</range>
+ <tilt>1.398328526418574e-010</tilt>
+ <heading>-0.002257590778485548</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-122.3363617333393,47.64542146106401,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Biarritz, France</name>
+ <description><![CDATA[<p><img src="http://www.santiago-compostela.net/pix/biar2.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-1.554172472866423</longitude>
+ <latitude>43.49326953476108</latitude>
+ <altitude>0</altitude>
+ <range>25.2767811064234</range>
+ <tilt>1.050991544755056e-009</tilt>
+ <heading>0.004580769709390601</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-1.554180928484328,43.49329288628002,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Biarritz, France</name>
+ <description><![CDATA[This is a small analemmatic sundial painted on the ground.
+<p><img src="http://www.santiago-compostela.net/pix/biar1.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-1.566562523529829</longitude>
+ <latitude>43.48379630381277</latitude>
+ <altitude>0</altitude>
+ <range>21.65084680450158</range>
+ <tilt>3.134801309486055e-010</tilt>
+ <heading>-0.003938809314781338</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-1.566563732160418,43.48381046528652,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Gardens of Easton Lodge, UK</name>
+ <description><![CDATA[Easton Lodge, Little Easton, Dunmow, Essex, UK
+
+<p><img src="http://www.sundials.co.uk/pix/easton1.jpg"></p>
+
+<p><img src="http://www.sundials.co.uk/pix/easton2.jpg"></p>
+
+http://www.sundials.co.uk/newdials.htm]]></description>
+ <LookAt>
+ <longitude>0.3149818536825662</longitude>
+ <latitude>51.89078052542742</latitude>
+ <altitude>0</altitude>
+ <range>55.96449646091584</range>
+ <tilt>4.648925194094304e-010</tilt>
+ <heading>0.00100690081475908</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>0.3149732952370528,51.89085713320831,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Drake University, Des Moines, Iowa, USA</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/483_io_desmoines_drake_univ.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-93.65212284615454</longitude>
+ <latitude>41.60195541103381</latitude>
+ <altitude>0</altitude>
+ <range>78.72982107342352</range>
+ <tilt>1.765081595632672e-010</tilt>
+ <heading>0.001286572559733559</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-93.65218647671061,41.60204463077999,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, River State Park, Indianapolis, USA</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/276_in_indi_wrsp.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-86.17152524772823</longitude>
+ <latitude>39.76773209074677</latitude>
+ <altitude>0</altitude>
+ <range>54.39844455317644</range>
+ <tilt>0</tilt>
+ <heading>0.002275213067656348</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-86.17158357583082,39.76772499391254,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Lawrence Hall of Science, Berkeley, CA, USA</name>
+ <description><![CDATA[<p><img src="http://sundials.org/conference/2000/image176.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-122.2467934369336</longitude>
+ <latitude>37.87843955912407</latitude>
+ <altitude>0</altitude>
+ <range>75.73772829567238</range>
+ <tilt>0</tilt>
+ <heading>-0.0005027058674008065</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-122.2468395686324,37.87850249930867,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Riverwalk, Augusta, Georgia, USA</name>
+ <description><![CDATA[<p><img src="http://www.coe.uga.edu/sdpl/3hgh_res/sundial1.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-81.96495913544699</longitude>
+ <latitude>33.47855115889769</latitude>
+ <altitude>0</altitude>
+ <range>24.10683016246917</range>
+ <tilt>0</tilt>
+ <heading>-0.004039593559848222</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-81.96497422223469,33.47856125757188,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Reggio nell&apos;Emilia, Italy</name>
+ <description><![CDATA[<p><img src="http://perso.orange.fr/cadrans.solaires/cadrans/images/cadran_righi_araign%E9e.jpg"></p>
+<p>Image source:<a href="http://perso.orange.fr/cadrans.solaires/cadrans/Cadran-brescia.html">http://perso.orange.fr</a></p>]]></description>
+ <LookAt>
+ <longitude>10.64303919389926</longitude>
+ <latitude>44.71779646338597</latitude>
+ <altitude>0</altitude>
+ <range>189.7095730357674</range>
+ <tilt>0</tilt>
+ <heading>0.0003188808607201916</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>10.64294491831197,44.71794161105381,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Rennes, France</name>
+ <description><![CDATA[<p><img src="http://perso.orange.fr/cadrans.solaires/cadrans/images/cadran_beauregard_300.jpg"></p>
+
+<p>Image source:<a href="http://perso.orange.fr/cadrans.solaires/cadrans/images/cadran_beauregard_300.jpg">http://perso.orange.fr</a></p>]]></description>
+ <LookAt>
+ <longitude>-1.701676278457902</longitude>
+ <latitude>48.13126501865703</latitude>
+ <altitude>0</altitude>
+ <range>61.61200771227915</range>
+ <tilt>0</tilt>
+ <heading>-7.297875936612596e-006</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-1.701699217745187,48.13129604209563,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Schneverdingen, Germany</name>
+ <description><![CDATA[<p><img src="http://www.schneverdingen-touristik.de/Rest/Sonnenuhr%20quer.jpg" width="320" hight ="240"></p>]]></description>
+ <LookAt>
+ <longitude>9.790867938787324</longitude>
+ <latitude>53.12943797238091</latitude>
+ <altitude>0</altitude>
+ <range>106.7617213575405</range>
+ <tilt>4.722006958129564e-010</tilt>
+ <heading>0.00116683463628678</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>9.790707601654233,53.12958381093443,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial Obelisk, Charleston, South Carolina</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/10a1f88a-71e0-4341-96f3-e8e687af05c9.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM9J1">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-79.93166502066842</longitude>
+ <latitude>32.76970334074068</latitude>
+ <altitude>0</altitude>
+ <range>75.62015855417492</range>
+ <tilt>4.052617221081382e-011</tilt>
+ <heading>1.8933011389851e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-79.93172500691688,32.76973746165206,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Morehead Planetarium, Chapel Hill, North Carolina</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/log/display/a71ce131-2875-4856-958f-2ca5e57a1df9.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WMCBZ">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-79.050938325099</longitude>
+ <latitude>35.91448691988588</latitude>
+ <altitude>0</altitude>
+ <range>64.77863580575449</range>
+ <tilt>0</tilt>
+ <heading>1.662447442472179e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-79.05097493816135,35.91457037097104,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Berkeley,
+California</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/log/display/9c455a92-2816-4071-950e-91f17b6fa4bd.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM988">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-122.3174670551103</longitude>
+ <latitude>37.86291969151575</latitude>
+ <altitude>0</altitude>
+ <range>46.45520126730318</range>
+ <tilt>1.288314315217904e-009</tilt>
+ <heading>2.022127862982459e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-122.317517078111,37.86295037394118,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Forest Lawn Cemetery Sundial, Buffalo, NY</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/c6aef5f9-367d-45f2-8f8c-d01bf5d233ae.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WMJQ8">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-78.85654999999994</longitude>
+ <latitude>42.92531666666667</latitude>
+ <altitude>0</altitude>
+ <range>76.25216432595194</range>
+ <tilt>0</tilt>
+ <heading>1.305178628551349e-014</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-78.85660856395873,42.92539096384056,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Ruston Way Sundial ,Tacoma, Washington</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/dd5439e7-0312-443d-9557-d4986582ef59.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WMXQB">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-122.4622812282256</longitude>
+ <latitude>47.27566486193976</latitude>
+ <altitude>0</altitude>
+ <range>62.22457114364932</range>
+ <tilt>0</tilt>
+ <heading>0.0001239840172672445</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-122.4623519976878,47.27567991760397,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial ,Science Central, Fort Wayne, Indiana</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/e78d7f23-8999-47be-b678-e7140aa82ca7.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WMYG1">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-85.1392719076301</longitude>
+ <latitude>41.09135262868964</latitude>
+ <altitude>0</altitude>
+ <range>39.45104173043256</range>
+ <tilt>0</tilt>
+ <heading>-1.439899387224993e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-85.13931366905783,41.09136114213859,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Berkswich Millennium Sundial, Broc Hill, Staffordshire, UK</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/3351745c-c573-4970-98ef-8ba7740c9bca.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WMR8X">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-2.038136129920761</longitude>
+ <latitude>52.77711389120437</latitude>
+ <altitude>0</altitude>
+ <range>29.50351061813827</range>
+ <tilt>0</tilt>
+ <heading>3.758749562499077e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-2.038162283146562,52.77714418176907,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Tazacorte Beach ,La Palma island</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/10cced19-2b34-482d-86c5-ee72fca9ab87.jpg" width="400" height="300"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM173Y">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-17.9461489138289</longitude>
+ <latitude>28.65121498294262</latitude>
+ <altitude>0</altitude>
+ <range>64.36805201552387</range>
+ <tilt>0</tilt>
+ <heading>4.047704004228316e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-17.94620263531645,28.65124065936443,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Rochester, NY</name>
+ <description><![CDATA[Residence Hall Quad Sundial
+<p><img src="http://img.groundspeak.com/waymarking/display/86272fd4-17ae-4b5d-a120-35ba49574a1d.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM13N7">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-77.66915367541856</longitude>
+ <latitude>43.0844306339545</latitude>
+ <altitude>0</altitude>
+ <range>61.69080872372956</range>
+ <tilt>0</tilt>
+ <heading>9.994948692290747e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-77.66917908415978,43.08440844604031,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Center of the World, Felicity, CA</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/log/691c99be-50f7-4916-88e7-9e103d994b22.jpg"></p>
+
+<p>
+The 15 foot Sundial at Felicity is a three-dimensional bronze of Michelangelo's Arm of God painted on the Sistine Chapel ceiling. The arm was sculpted and cast in bronze in New England. The rock is local but the installation required the assistance of a mining engineer and a special drill. The bronze Roman numerals give the time. A sundial is precisely accurate once a year and this was set at noon on Christmas Day. The arm points to the Hill of Prayer, site of the Church on the Hill at Felicity.
+At the entrance to The Center of the World campus is a 25 ft. high section of the original stairway of the Eiffel Tower. In 1983, the Government of France removed approximately 500 ft. of the original stairway. Built with the technology of the 1860's, the weight of approximately 54,000 lbs. was causing sway at the top of the then 94 year old tower. The 6,600 lb. section serves no practical purpose, but is part of the spirit of Felicity.
+The idea of making Felicity the Center of the World came to Jacques-André when he'd been mayor only a few months. Somehow he convinced Imperial County, CA, to recognize his claim. Soon he had convinced the Institut Geographique National of France, General Dynamics Corporation, and The People's Republic of China to recognize it as well. "I knew I had to build something, but I didn't know what. My wife said, 'It's a desert; why not a pyramid?' So Jacques-André had built a 21-foot-tall pink marble pyramid, its interior lined with mirrors, a plaque embedded in the floor, marking the exact spot. For a dollar, tourists can now stand on the official Center Of The World and take a picture themselves at the official "Center Of The World".
+The Felicity Post Office was dedicated on 5 December 1987 at a time when thousands of small post offices were being eliminated as an economy measure. The town, whose population numbered two, saw over 2,300 letters mailed that day. The dedication ceremony was highlighted by a speech in Chinese by Consul Zhou of the People’s Republic of China who traveled 600 miles for the occasion. It is operated by the town at a cost to the Federal Government of one dollar per year. Twenty uncashed one dollar checks are on file.</p>
+
+<p>Image and info credit:<a href="http://www.waymarking.com/waymarks/WM19WB">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-114.7654750861393</longitude>
+ <latitude>32.74988921016088</latitude>
+ <altitude>0</altitude>
+ <range>72.95555856498569</range>
+ <tilt>0</tilt>
+ <heading>3.146266385893141e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-114.7655284077745,32.74992976207647,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>University of São Paulo Sundial, Sao Paulo, Brazi</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/a5362062-9509-4022-9693-fc2a064301d8.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1DJT">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-46.7204986760494</longitude>
+ <latitude>-23.56120553413547</latitude>
+ <altitude>0</altitude>
+ <range>122.7188487961642</range>
+ <tilt>0</tilt>
+ <heading>2.610051397350573e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-46.7205459522717,-23.56115337159118,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Slate bowl Sundial, Holker, UK</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/c6aca7e1-9221-4d8f-a0f4-e062e06346f9.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1DRW">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-2.987191130383048</longitude>
+ <latitude>54.188865359179</latitude>
+ <altitude>0</altitude>
+ <range>98.16442365143851</range>
+ <tilt>2.595660029656298e-010</tilt>
+ <heading>0.000142350860720713</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-2.987342530279506,54.18895843924356,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Jardin des Doms, Avignon</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/46383080-d099-4450-98a9-6d15c1c16441.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1M8Z">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>4.807697613943427</longitude>
+ <latitude>43.95301885165002</latitude>
+ <altitude>0</altitude>
+ <range>32.75914708134153</range>
+ <tilt>1.205283678723288e-009</tilt>
+ <heading>2.147953504845766e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>4.807672022945837,43.95303620373285,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Rillito Riverpark Sundial, Tucson, AZ</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/6c0bf495-b4fe-4549-a1c1-af8fb4328eae.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1TC7">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-111.0075277787534</longitude>
+ <latitude>32.30113621710221</latitude>
+ <altitude>0</altitude>
+ <range>94.15682746212195</range>
+ <tilt>0</tilt>
+ <heading>2.968664599173171e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-111.0075933392788,32.30113929573149,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Helium Monument Sundial, Amarillo, TX</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/a0b45c91-a572-4ca1-8a9d-b5ebb84c028b.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1WJY">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-101.9132978901728</longitude>
+ <latitude>35.19956726276647</latitude>
+ <altitude>0</altitude>
+ <range>60.53404995378031</range>
+ <tilt>5.162016480480558e-011</tilt>
+ <heading>3.00374135059527e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-101.9133182362553,35.19966266329223,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Hershey, Pennsylvania</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/fbb33ffa-3867-4f28-b12c-77bf647e1d13.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM1XC1">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-76.62980321024054</longitude>
+ <latitude>40.27170452963257</latitude>
+ <altitude>0</altitude>
+ <range>72.41553799015709</range>
+ <tilt>2.798231534250927e-010</tilt>
+ <heading>6.256605840320539e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-76.6298565862236,40.2718754812139,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>King Neptune Sundial, Hilton Head Island, South Carolina</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/e48230f9-b1fd-4cf6-bc81-7f0719cc13a5.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM2EY7">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-80.72801698168738</longitude>
+ <latitude>32.18076491029077</latitude>
+ <altitude>0</altitude>
+ <range>86.36647046692004</range>
+ <tilt>2.346233297172379e-010</tilt>
+ <heading>8.72178085589082e-006</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-80.72806102317654,32.18084680335104,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Jane Larue Memorial Sundial - Ann Arbor, Michigan</name>
+ <description><![CDATA[<p><img src="http://img.groundspeak.com/waymarking/display/936cd562-a97c-44bd-8ed5-fcdf2495076d.jpg"></p>
+
+<p>Image credit:<a href="http://www.waymarking.com/waymarks/WM290J">www.groundspeak.com</a></p>]]></description>
+ <LookAt>
+ <longitude>-83.66222470201295</longitude>
+ <latitude>42.30114702626376</latitude>
+ <altitude>0</altitude>
+ <range>26.29476256996721</range>
+ <tilt>0</tilt>
+ <heading>0.0001291940559531826</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy70</styleUrl>
+ <Point>
+ <coordinates>-83.66224748552365,42.30117333470928,0</coordinates>
+ </Point>
+ </Placemark>
+ </Folder>
+ <Folder>
+ <name>Schoolyard Sundials</name>
+ <Placemark>
+ <name>Sundial, Julius-Brecht-Allee, Bremen</name>
+ <LookAt>
+ <longitude>8.8674012861685</longitude>
+ <latitude>53.07651505713779</latitude>
+ <altitude>0</altitude>
+ <range>20.6687721420542</range>
+ <tilt>9.001122528249614e-011</tilt>
+ <heading>-0.00437506724289509</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>8.867391721405184,53.07654483342672,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Drebberstraße, Bremen</name>
+ <description><![CDATA[<p><img src="http://planetarium.hs-bremen.de/planetarium/pics/drebber.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>8.898052233187912</longitude>
+ <latitude>53.0400952944841</latitude>
+ <altitude>0</altitude>
+ <range>14.44345748598086</range>
+ <tilt>0</tilt>
+ <heading>-0.001637659480767247</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>8.898047664850367,53.04011230005033,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Butjadingersrasse, Bremen</name>
+ <description><![CDATA[<p><img src="http://planetarium.hs-bremen.de/planetarium/astroinfo/sonnenuhren/sonstige/butjadinger.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>8.759747956980032</longitude>
+ <latitude>53.08143879125452</latitude>
+ <altitude>0</altitude>
+ <range>37.83897098076405</range>
+ <tilt>0</tilt>
+ <heading>-0.002629545926081431</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>8.759712018733111,53.08151322706201,0</coordinates>
+ </Point>
+ </Placemark>
+ </Folder>
+ <Folder>
+ <name>In Progress</name>
+ <Placemark>
+ <name>Sundial, Greenwich, USA</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/345_ct_greenwich.jpg"></p>]]></description>
+ <LookAt>
+ <longitude>-73.61498302559443</longitude>
+ <latitude>41.02226092221508</latitude>
+ <altitude>0</altitude>
+ <range>149.2259168633856</range>
+ <tilt>1.357926888487057e-010</tilt>
+ <heading>-0.001539166856947675</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-73.61504279924034,41.022311140554,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sonnenuhr?</name>
+ <LookAt>
+ <longitude>11.05508326700377</longitude>
+ <latitude>49.45922489288633</latitude>
+ <altitude>0</altitude>
+ <range>50.88443884213967</range>
+ <tilt>8.335955203191607e-009</tilt>
+ <heading>0.0196675278275586</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>11.0551380716084,49.45927364486676,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Edgewood Park, New Haven, USA</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/279_ct_newha_eng.jpg"><p>
+
+<p><img src="http://www.sundials.org/registry/regphotos/279_ct_newha_eng2.jpg"><p>]]></description>
+ <LookAt>
+ <longitude>-72.95215163561284</longitude>
+ <latitude>41.31399188322968</latitude>
+ <altitude>0</altitude>
+ <range>154.2904142456261</range>
+ <tilt>0</tilt>
+ <heading>0.002187256502984029</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-72.95224192688632,41.31401837758977,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial, Pearl City, Oahu, USA</name>
+ <description><![CDATA[<p><img src="http://www.sundials.org/registry/regphotos/535_hi_pearlcity_uofhi_1.jpg" height="300px" width="450px"></p>]]></description>
+ <LookAt>
+ <longitude>-157.975920511215</longitude>
+ <latitude>21.39370171784438</latitude>
+ <altitude>0</altitude>
+ <range>93.82906502148613</range>
+ <tilt>0</tilt>
+ <heading>-0.001426474135915891</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-157.9759385077734,21.39376422631041,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial</name>
+ <description>http://maget.maget.free.fr/SiteMont/index.html</description>
+ <LookAt>
+ <longitude>-1.511135684750573</longitude>
+ <latitude>48.63640399624012</latitude>
+ <altitude>0</altitude>
+ <range>623.6899626138724</range>
+ <tilt>0</tilt>
+ <heading>-1.851737885201182e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-1.511518347366319,48.63786003229999,0</coordinates>
+ </Point>
+ </Placemark>
+ <GroundOverlay>
+ <name>Mont-Saint-Michel</name>
+ <description>http://maget.maget.free.fr/SiteMont/MSpage4.htm</description>
+ <Icon>
+ <href>http://maget.maget.free.fr/SiteMont/images/le_Mont_Solaire-Land%20Art.jpg</href>
+ <viewBoundScale>0.75</viewBoundScale>
+ </Icon>
+ <LatLonBox>
+ <north>48.63770978435333</north>
+ <south>48.6344604605756</south>
+ <east>-1.5070705975067</east>
+ <west>-1.514375149320612</west>
+ <rotation>-11.46597601725745</rotation>
+ </LatLonBox>
+ </GroundOverlay>
+ <Placemark>
+ <name>Sundial Park, Ludiver park</name>
+ <description>http://perso.orange.fr/cadrans.solaires/cadrans/cadran-parc-ludiver.html</description>
+ <LookAt>
+ <longitude>-1.727863357864637</longitude>
+ <latitude>49.63119498354116</latitude>
+ <altitude>0</altitude>
+ <range>473.1060190443535</range>
+ <tilt>3.883358970183465e-011</tilt>
+ <heading>0.0005701632901766135</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-1.728331456927833,49.63191584214422,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sonnenuhr?</name>
+ <LookAt>
+ <longitude>7.68545763101957</longitude>
+ <latitude>51.53642499090419</latitude>
+ <altitude>0</altitude>
+ <range>23.70363190324798</range>
+ <tilt>4.475657800962137e-010</tilt>
+ <heading>-0.1674345977313924</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>7.685416995069303,51.53648149450991,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>Sundial at Tower of London</name>
+ <description><![CDATA[<img src="http://gallery.ontopof-theworld.com/albums/photohunt/IMG_1031_t.jpg">]]></description>
+ <LookAt>
+ <longitude>-0.07656780337525181</longitude>
+ <latitude>51.50981727675416</latitude>
+ <altitude>0</altitude>
+ <range>61.73369699893549</range>
+ <tilt>9.251491983355112e-010</tilt>
+ <heading>0.009688876514144714</heading>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-0.07656780337525181,51.50981727675417,0</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>War Veterans&apos; Memorial Park Sundial, Florida, United States</name>
+ <LookAt>
+ <longitude>-82.77333790901622</longitude>
+ <latitude>27.8036881517592</latitude>
+ <altitude>0</altitude>
+ <range>96.43655563554265</range>
+ <tilt>4.405141995417006e-010</tilt>
+ <heading>9.892674215924156e-005</heading>
+ <altitudeMode>relativeToGround</altitudeMode>
+ </LookAt>
+ <styleUrl>#msn_sunny_copy69</styleUrl>
+ <Point>
+ <coordinates>-82.77341348054247,27.80374932310448,0</coordinates>
+ </Point>
+ </Placemark>
+ </Folder>
+ </Folder>
+</Document>
+</kml>
diff --git a/misc/openlayers/examples/label-scale.html b/misc/openlayers/examples/label-scale.html
new file mode 100644
index 0000000..d6d32ed
--- /dev/null
+++ b/misc/openlayers/examples/label-scale.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Scale Dependent Labels</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">Scale Dependent Labels Example</h1>
+ <div id="tags">
+ label, scale, stylemap
+ </div>
+ <p id="shortdesc">
+ Demonstrates how to use a StyleMap for displaying scale dependent labels.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This example uses rule based styling to change the how features are
+ labeled at different scales. An <code>OpenLayers.Rule</code> object
+ can have <code>minScaleDenominator</code> and
+ <code>maxScaleDenominator</code> properties to control when the
+ provided symbolizer should be used.
+ </p><p>
+ View the <a href="label-scale.js">source</a> to see how this is done.
+ </p>
+ </div>
+ <script src="label-scale.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/label-scale.js b/misc/openlayers/examples/label-scale.js
new file mode 100644
index 0000000..a46fe4b
--- /dev/null
+++ b/misc/openlayers/examples/label-scale.js
@@ -0,0 +1,72 @@
+// Create 50 random features, and give them a "type" attribute that
+// will be used for the label text.
+var features = new Array(50);
+for (var i=0; i<features.length; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(
+ (360 * Math.random()) - 180, (180 * Math.random()) - 90
+ ), {
+ type: 5 + parseInt(5 * Math.random())
+ }
+ );
+}
+
+/**
+ * Create a style instance that is a collection of rules with symbolizers.
+ * Use a default symbolizer to extend symoblizers for all rules.
+ */
+var style = new OpenLayers.Style({
+ fillColor: "#ffcc66",
+ strokeColor: "#ff9933",
+ strokeWidth: 2,
+ label: "${type}",
+ fontColor: "#333333",
+ fontFamily: "sans-serif",
+ fontWeight: "bold"
+}, {
+ rules: [
+ new OpenLayers.Rule({
+ minScaleDenominator: 200000000,
+ symbolizer: {
+ pointRadius: 7,
+ fontSize: "9px"
+ }
+ }),
+ new OpenLayers.Rule({
+ maxScaleDenominator: 200000000,
+ minScaleDenominator: 100000000,
+ symbolizer: {
+ pointRadius: 10,
+ fontSize: "12px"
+ }
+ }),
+ new OpenLayers.Rule({
+ maxScaleDenominator: 100000000,
+ symbolizer: {
+ pointRadius: 13,
+ fontSize: "15px"
+ }
+ })
+ ]
+});
+
+// Create a vector layer and give it your style map.
+var points = new OpenLayers.Layer.Vector("Points", {
+ styleMap: new OpenLayers.StyleMap(style)
+});
+points.addFeatures(features);
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ ),
+ points
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+});
+
diff --git a/misc/openlayers/examples/late-render.html b/misc/openlayers/examples/late-render.html
new file mode 100644
index 0000000..8502e2a
--- /dev/null
+++ b/misc/openlayers/examples/late-render.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Late Rendering Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map();
+ layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ map.addLayer(layer);
+ map.render("container_id");
+ map.zoomTo(2);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Late Rendering</h1>
+
+ <div id="tags">
+ creation, render, div, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates how a map can be rendered to an empty container after
+ construction by calling the render method.
+ </p>
+ <div id="container_id" class="smallmap"></div>
+ <div id="docs">
+ <p>In cases where you need to create a map first and render it to some
+ container later, call the map constructor without a "div" argument.
+ In this case, you can provide the options object as the first argument.
+ To render your map to some container after construction, call the map's
+ render method with the container id.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/layer-opacity.html b/misc/openlayers/examples/layer-opacity.html
new file mode 100644
index 0000000..555cc95
--- /dev/null
+++ b/misc/openlayers/examples/layer-opacity.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Layer Opacity Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ body {
+ font-family: sans-serif;
+ }
+ p {
+ width: 512px;
+ }
+ a {
+ text-decoration: none;
+ color: black;
+ font-weight: bold;
+ font-size: 1.1em;
+ }
+ #opacity {
+ padding: 0;
+ text-align: center;
+ width: 2em;
+ font-family: sans-serif;
+ background: transparent;
+ color: black;
+ border: 0;
+ }
+ p.note {
+ font-style: italic;
+ font-size: 0.8em;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map = null;
+ var shade = null;
+ var maxOpacity = 0.9;
+ var minOpacity = 0.1;
+ function changeOpacity(byOpacity) {
+ var newOpacity = (parseFloat(OpenLayers.Util.getElement('opacity').value) + byOpacity).toFixed(1);
+ newOpacity = Math.min(maxOpacity,
+ Math.max(minOpacity, newOpacity));
+ OpenLayers.Util.getElement('opacity').value = newOpacity;
+ shade.setOpacity(newOpacity);
+ }
+ function init(){
+ var options = {
+ maxExtent: new OpenLayers.Bounds(-110.994, 45.885, -110.950, 45.929),
+ maxResolution: "auto"
+ };
+ map = new OpenLayers.Map('map', options);
+ var drg = new OpenLayers.Layer.WMS("Topo Maps",
+ "http://terraservice.net/ogcmap.ashx",
+ {layers: "DRG"});
+ shade = new OpenLayers.Layer.WMS("Shaded Relief",
+ "http://gisdata.usgs.gov/wmsconnector/com.esri.wms.Esrimap?ServiceName=USGS_EDC_Elev_NED_3",
+ {layers: "HR-NED.IMAGE", reaspect: "false", transparent: 'true'},
+ {isBaseLayer: false, opacity: 0.3});
+ map.addLayers([drg, shade]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Layer Opacity Example</h1>
+
+ <div id="tags">
+ opacity, transparent, transparency, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate a change in the opacity for an overlay layer.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ Note that if you also have the setOpacity method defined on the Layer
+ class, you can tweak the layer opacity after it has been added to the map.
+ </p>
+ <p>Opacity:
+ <a title="decrease opacity" href="javascript: changeOpacity(-0.1);">&lt;&lt;</a>
+ <input id="opacity" type="text" value="0.3" size="3" disabled="true" />
+ <a title="increase opacity" href="javascript: changeOpacity(0.1);">&gt;&gt;</a>
+ </p>
+ <p class="note">IE users: Wait until the shade layer has finished loading to try this.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/layerLoadMonitoring.html b/misc/openlayers/examples/layerLoadMonitoring.html
new file mode 100644
index 0000000..f96d49c
--- /dev/null
+++ b/misc/openlayers/examples/layerLoadMonitoring.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Layer Load Monitoring Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controls {
+ float: left;
+ text-align: right;
+ }
+ #eventsLogID {
+ text-align: left;
+ width: 350px;
+ height: 475px;
+ overflow: auto;
+ border: 1px solid black;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+
+ eventsLog = OpenLayers.Util.getElement("eventsLogID");
+
+ map = new OpenLayers.Map( 'map' );
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ buffer0 = new OpenLayers.Layer.WMS( "WMS Buffer 0",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} ,
+ { singleTile: false, buffer:0}
+ );
+ registerEvents(buffer0);
+
+ buffer1 = new OpenLayers.Layer.WMS( "WMS Buffer 1",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} ,
+ { singleTile: false, buffer:1}
+ );
+ registerEvents(buffer1);
+
+ buffer2 = new OpenLayers.Layer.WMS( "WMS Buffer 2",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} ,
+ { singleTile: false, buffer:2 }
+ );
+ registerEvents(buffer2);
+
+ singleTileLayer = new OpenLayers.Layer.WMS( "Single Tile Layer",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} ,
+ { singleTile: true}
+ );
+ registerEvents(singleTileLayer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+
+ function registerEvents(layer) {
+
+ layer.logEvent = function(event) {
+ eventsLog.innerHTML += "<br>(" + getTimeStamp() + ") " +
+ this.name + ": " + event;
+ };
+
+ layer.events.register("loadstart", layer, function() {
+ this.logEvent("Load Start");
+ });
+
+ layer.events.register("tileloaded", layer, function() {
+ this.logEvent("Tile loaded. " + this.numLoadingTiles + " left.");
+ });
+
+ layer.events.register("loadend", layer, function() {
+ this.logEvent("Load End. Grid:" + this.grid.length + "x" + this.grid[0].length);
+ });
+
+ map.addLayer(layer);
+ }
+
+
+ function getTimeStamp() {
+ var date = new Date();
+
+ var timeParts = [
+ date.getHours(),
+ date.getMinutes(),
+ date.getSeconds()
+ ];
+
+ var timeStamp = timeParts.join(":");
+
+ return timeStamp;
+ }
+
+ function clearLog() {
+ eventsLog.innerHTML = "<b>Events Log:</b>";
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Layer Load Monitoring Example</h1>
+
+ <div id="tags">
+ monitor, loading, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate a method for monitoring tile loading performance.
+ </p>
+
+ <div id="map" class="smallmap" style="float:left;"></div>
+
+ <div id="controls">
+ <div id="eventsLogID">
+ <b>Events Log:</b>
+ </div>
+
+ <input type="button" value="Clear" onclick="clearLog()"/>
+ </div>
+
+ <div id="docs">
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/layerswitcher.html b/misc/openlayers/examples/layerswitcher.html
new file mode 100644
index 0000000..791a4d0
--- /dev/null
+++ b/misc/openlayers/examples/layerswitcher.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Layer Switcher Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map('map', { controls: [] });
+ map.addControl(new OpenLayers.Control.Navigation());
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher({'div':OpenLayers.Util.getElement('layerswitcher')}));
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+ var jpl_wms = new OpenLayers.Layer.WMS( "NASA Global Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {layers: "landsat7"}, {'isBaseLayer': false});
+
+ var dm_wms = new OpenLayers.Layer.WMS( "DM Solutions Demo",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap",
+ {layers: "bathymetry,land_fn,park,drain_fn,drainage," +
+ "prov_bound,fedlimit,rail,road,popplace",
+ transparent: "true", format: "image/png" });
+
+ jpl_wms.setVisibility(false);
+ dm_wms.setVisibility(false);
+
+ map.addLayers([ol_wms, jpl_wms, dm_wms]);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Layer Switcher Example</h1>
+
+ <div id="tags">
+ tree, layerswitcher, reposition, light
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates the use of the LayerSwitcher outside of the OpenLayers window.
+ </p>
+
+ <div id="layerswitcher" class="olControlLayerSwitcher"></div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This demonstrates use of the LayerSwitcher outside the map div. It also shows use
+ of the displayInLayerSwitcher option on the Layer to cause it to not display in the
+ LayerSwitcher.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/light-basic.html b/misc/openlayers/examples/light-basic.html
new file mode 100644
index 0000000..634b450
--- /dev/null
+++ b/misc/openlayers/examples/light-basic.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Light - Basic Popups</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../OpenLayers.light.debug.js"></script>
+ <script src="light-basic.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Light - Basic Popups</h1>
+ <div id="tags">
+ light, vector, feature, popup
+ </div>
+ <p id="shortdesc">
+ A basic use case example using the OpenLayers.light version of the library.<br>
+ Shows popup info bubble when hovering over features on the map
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ This example uses OpenLayers.light.js to display features and show
+ popup info bubbles when the feature is hovered over.
+ </p>
+ See the <a href="light-basic.js" target="_blank">
+ light-basic.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/light-basic.js b/misc/openlayers/examples/light-basic.js
new file mode 100644
index 0000000..89465be
--- /dev/null
+++ b/misc/openlayers/examples/light-basic.js
@@ -0,0 +1,67 @@
+var map;
+
+function init() {
+ map = new OpenLayers.Map("map",{projection:"EPSG:3857"});
+
+ var osm = new OpenLayers.Layer.OSM();
+ var toMercator = OpenLayers.Projection.transforms['EPSG:4326']['EPSG:3857'];
+ var center = toMercator({x:-0.05,y:51.5});
+
+ /**
+ * Create 5 random vector features. Your features would typically be fetched
+ * from the server. The features are given an attribute named "foo".
+ * The value of this attribute is an integer that ranges from 0 to 100.
+ */
+ var features = [];
+ for(var i = 0; i < 5; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ toMercator(new OpenLayers.Geometry.Point(
+ -0.040 - 0.05*Math.random(),
+ 51.49 + 0.02*Math.random())),
+ {
+ foo : 100 * Math.random() | 0
+ }, {
+ fillColor : '#008040',
+ fillOpacity : 0.8,
+ strokeColor : "#ee9900",
+ strokeOpacity : 1,
+ strokeWidth : 1,
+ pointRadius : 8
+ });
+ }
+
+ // create the layer with listeners to create and destroy popups
+ var vector = new OpenLayers.Layer.Vector("Points",{
+ eventListeners:{
+ 'featureselected':function(evt){
+ var feature = evt.feature;
+ var popup = new OpenLayers.Popup.FramedCloud("popup",
+ OpenLayers.LonLat.fromString(feature.geometry.toShortString()),
+ null,
+ "<div style='font-size:.8em'>Feature: " + feature.id +"<br>Foo: " + feature.attributes.foo+"</div>",
+ null,
+ true
+ );
+ feature.popup = popup;
+ map.addPopup(popup);
+ },
+ 'featureunselected':function(evt){
+ var feature = evt.feature;
+ map.removePopup(feature.popup);
+ feature.popup.destroy();
+ feature.popup = null;
+ }
+ }
+ });
+ vector.addFeatures(features);
+
+ // create the select feature control
+ var selector = new OpenLayers.Control.SelectFeature(vector,{
+ hover:true,
+ autoActivate:true
+ });
+
+ map.addLayers([osm, vector]);
+ map.addControl(selector);
+ map.setCenter(new OpenLayers.LonLat(center.x,center.y), 13);
+}
diff --git a/misc/openlayers/examples/lite.html b/misc/openlayers/examples/lite.html
new file mode 100644
index 0000000..d4ae7e0
--- /dev/null
+++ b/misc/openlayers/examples/lite.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Basic Single WMS Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Basic Single WMS Example</h1>
+
+ <div id="tags">
+ basic, simple, minimal, cleanup
+ </div>
+
+ <div id="shortdesc">Show a Simple Map</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows a very simple layout with minimal controls.
+ This example uses a single WMS base layer.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mapbox.html b/misc/openlayers/examples/mapbox.html
new file mode 100644
index 0000000..4ccac14
--- /dev/null
+++ b/misc/openlayers/examples/mapbox.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers MapBox Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">Basic MapBox OSM Example</h1>
+ <div id="tags">mapbox xyz osm</div>
+
+ <div id="shortdesc">Shows how to use MapBox tiles in an OpenLayers map.</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example demonstrates the use of an XYZ layer that accesses tiles from MapBox.</p>
+ <p>
+ See the <a href="mapbox.js" target="_blank">mapbox.js</a> source
+ for details. Make sure to read the <a href="http://mapbox.com/tos/">Terms of Service</a>
+ before using MapBox tiles in your application.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="mapbox.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mapbox.js b/misc/openlayers/examples/mapbox.js
new file mode 100644
index 0000000..f5679dc
--- /dev/null
+++ b/misc/openlayers/examples/mapbox.js
@@ -0,0 +1,21 @@
+var earth = new OpenLayers.Layer.XYZ(
+ "Natural Earth",
+ [
+ "http://a.tiles.mapbox.com/v3/mapbox.natural-earth-hypso-bathy/${z}/${x}/${y}.png",
+ "http://b.tiles.mapbox.com/v3/mapbox.natural-earth-hypso-bathy/${z}/${x}/${y}.png",
+ "http://c.tiles.mapbox.com/v3/mapbox.natural-earth-hypso-bathy/${z}/${x}/${y}.png",
+ "http://d.tiles.mapbox.com/v3/mapbox.natural-earth-hypso-bathy/${z}/${x}/${y}.png"
+ ], {
+ attribution: "Tiles &copy; <a href='http://mapbox.com/'>MapBox</a>",
+ sphericalMercator: true,
+ wrapDateLine: true,
+ numZoomLevels: 5
+ }
+);
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [earth],
+ center: [0, 0],
+ zoom: 1
+});
diff --git a/misc/openlayers/examples/mapguide.html b/misc/openlayers/examples/mapguide.html
new file mode 100644
index 0000000..e8ffd0b
--- /dev/null
+++ b/misc/openlayers/examples/mapguide.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers MapGuide Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ width: 400px;
+ height: 400px;
+ border: 1px solid black;
+ float:left;
+ }
+ #map2 {
+ width: 400px;
+ height: 400px;
+ border: 1px solid black;
+ float:left;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var map, layer;
+ var url = "http://data.mapguide.com/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous&";
+ //you can use this URL when MapGuide OS is installed locally
+ //var url = "/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous&";
+
+ //Adjust the scale assumptions for MapGuide layers
+ //Tiled layers MUST use a DPI value of 96, untiled layers can use a
+ //different DPI value which will be passed to the server as a parameter.
+ //Tiled and untiled layers must adjust the OL INCHES_PER_UNIT values
+ //for any degree-based projections.
+ var metersPerUnit = 111319.4908; //value returned from mapguide
+ var inPerUnit = OpenLayers.INCHES_PER_UNIT.m * metersPerUnit;
+ OpenLayers.INCHES_PER_UNIT["dd"] = inPerUnit;
+ OpenLayers.INCHES_PER_UNIT["degrees"] = inPerUnit;
+ OpenLayers.DOTS_PER_INCH = 96;
+
+ //tiled version
+ function initTiled(){
+
+ var extent = new OpenLayers.Bounds(-87.764987,43.691398,-87.695522,43.797520);
+ var tempScales = [100000,51794.74679,26826.95795,13894.95494,7196.85673,3727.59372,1930.69773,1000];
+ var mapOptions = {
+ maxExtent: extent,
+ scales: tempScales
+ };
+ map = new OpenLayers.Map( 'map', mapOptions );
+
+ var params = {
+ mapdefinition: 'Library://Samples/Sheboygan/MapsTiled/Sheboygan.MapDefinition',
+ basemaplayergroupname: "Base Layer Group"
+ };
+ var options = {
+ singleTile: false
+ };
+ var layer = new OpenLayers.Layer.MapGuide( "MapGuide OS tiled layer", url, params, options );
+ map.addLayer(layer);
+
+ /**
+ The following example shows how to access an MG tile cache directly
+ through HTTP bypassing the MG mapagent. This depends on having a
+ pre-populated tile cache
+ */
+ /*
+ options.useHttpTile = true;
+ var cacheUrl = "http://localhost:8008/sheboygan";
+ var httpLayer = new OpenLayers.Layer.MapGuide( "MapGuide HTTP cache tiled layer", cacheUrl, params, options );
+ map.addLayer(httpLayer);
+ */
+
+ map.zoomToMaxExtent();
+ }
+
+ //un-tiled version
+ function initUntiled() {
+
+ var extent = new OpenLayers.Bounds(-87.865114442365922,43.665065564837931,-87.595394059497067,43.823852564430069);
+ var mapOptions = {
+ maxExtent: extent,
+ maxResolution: 'auto'
+ };
+ map = new OpenLayers.Map( 'map2', mapOptions );
+
+ var options = {
+ isBaseLayer: true,
+ transitionEffect: "resize",
+ buffer: 1,
+ useOverlay: false,
+ useAsyncOverlay: false,
+ singleTile: true
+ };
+
+ var params = {
+ mapdefinition: 'Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition'
+ };
+ /*
+ The MapGuide layer can also be created using mapname and session as follows provided there
+ is some wrapper code to obtain a valid session id and mapname */
+ /*
+ var params = {
+ mapname: 'Sheboygan49ad9e20e7171',
+ session: '7405c17a-0000-1000-8000-0017a4e6ff5d_en_7F0000010AFC0AFB0AFA'
+ };
+ */
+
+ var layer = new OpenLayers.Layer.MapGuide( "MapGuide OS untiled baselayer", url, params, options );
+ map.addLayer(layer);
+
+ //this is how to set up the layer for transparent overlays. Requires a valid session ID
+ //and mapName stored in that session.
+ /*
+ var options = {
+ isBaseLayer: false,
+ useOverlay: true,
+ useAsyncOverlay: false,
+ buffer: 1,
+ singleTile: true
+ };
+ var params = {
+ mapName: 'Sheboygan49aeaa04487af',
+ session: '208fd92c-0000-1000-8000-0017a4e6ff5d_en_7F0000010AFC0AFB0AFA',
+ selectioncolor: '0xFF000000',
+ behavior: 7
+ };
+ layer = new OpenLayers.Layer.MapGuide( "MapGuide OS Overlay layer", url, params, options );
+ map.addLayer(layer);
+ */
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="initUntiled(); initTiled()">
+ <h1 id="title">MapGuide Layer Example</h1>
+
+ <div id="tags">
+ MapGuide, basic
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates how to create MapGuide tiled and untiled layers.
+ </p>
+
+ <p>If prompted for a password, username is Anonymous and an empty password</p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="map2">
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mapquest.html b/misc/openlayers/examples/mapquest.html
new file mode 100644
index 0000000..0fc02ec
--- /dev/null
+++ b/misc/openlayers/examples/mapquest.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers MapQuest Demo</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+</head>
+<body>
+ <h1 id="title">OpenLayers with MapQuest Tiles</h1>
+ <div id="shortdesc">
+ This example demonstrates the use of MapQuest tiles with OpenLayers.
+ </div>
+ <div id="tags">
+ MapQuest, OSM, XYZ
+ </div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ See the <a href="mapquest.js" target="_blank">mapquest.js source</a> for
+ detail on using MapQuest tiles in OpenLayers.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="mapquest.js"></script>
+</body> \ No newline at end of file
diff --git a/misc/openlayers/examples/mapquest.js b/misc/openlayers/examples/mapquest.js
new file mode 100644
index 0000000..5a45d1c
--- /dev/null
+++ b/misc/openlayers/examples/mapquest.js
@@ -0,0 +1,36 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.XYZ(
+ "OpenStreetMap",
+ [
+ "http://otile1.mqcdn.com/tiles/1.0.0/map/${z}/${x}/${y}.png",
+ "http://otile2.mqcdn.com/tiles/1.0.0/map/${z}/${x}/${y}.png",
+ "http://otile3.mqcdn.com/tiles/1.0.0/map/${z}/${x}/${y}.png",
+ "http://otile4.mqcdn.com/tiles/1.0.0/map/${z}/${x}/${y}.png"
+ ],
+ {
+ attribution: "Data, imagery and map information provided by <a href='http://www.mapquest.com/' target='_blank'>MapQuest</a>, <a href='http://www.openstreetmap.org/' target='_blank'>Open Street Map</a> and contributors, <a href='http://creativecommons.org/licenses/by-sa/2.0/' target='_blank'>CC-BY-SA</a> <img src='http://developer.mapquest.com/content/osm/mq_logo.png' border='0'>",
+ transitionEffect: "resize"
+ }
+ ),
+ new OpenLayers.Layer.XYZ(
+ "Imagery",
+ [
+ "http://otile1.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.png",
+ "http://otile2.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.png",
+ "http://otile3.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.png",
+ "http://otile4.mqcdn.com/tiles/1.0.0/sat/${z}/${x}/${y}.png"
+ ],
+ {
+ attribution: "Tiles Courtesy of <a href='http://open.mapquest.co.uk/' target='_blank'>MapQuest</a>. Portions Courtesy NASA/JPL-Caltech and U.S. Depart. of Agriculture, Farm Service Agency. <img src='http://developer.mapquest.com/content/osm/mq_logo.png' border='0'>",
+ transitionEffect: "resize"
+ }
+ )
+ ],
+ center: [0, 0],
+ zoom: 1
+});
+
+map.addControl(new OpenLayers.Control.LayerSwitcher());
diff --git a/misc/openlayers/examples/mapserver.html b/misc/openlayers/examples/mapserver.html
new file mode 100644
index 0000000..39e76de
--- /dev/null
+++ b/misc/openlayers/examples/mapserver.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>MapServer Layer</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.MapServer( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'},
+ {gutter: 15});
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">MapServer Layer</h1>
+ <div id="tags">UMN Mapserver, tile, tiled</div>
+ <div id="shortdesc">Shows MapServer Layer</div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This is an example of using a MapServer Layer with a gutter
+ parameter. The gutter parameter is used to try to limit the edge
+ effects between tiles.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mapserver_untiled.html b/misc/openlayers/examples/mapserver_untiled.html
new file mode 100644
index 0000000..d416532
--- /dev/null
+++ b/misc/openlayers/examples/mapserver_untiled.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>MapServer Single Tile Mode</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ width: 100%;
+ height: 100%;
+ border: 1px solid black;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ window.onload = function(){
+ map = new OpenLayers.Map( 'map', {maxResolution: 'auto'} );
+ var layer = new OpenLayers.Layer.MapServer( "MapServer Untiled",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'}, {singleTile: true} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ }
+ </script>
+ </head>
+ <body>
+ <h1 id="title">MapServer Single Tile Mode</h1>
+ <div id="tags">
+ UMN Mapserver, basic, singleTile
+ </div>
+ <div id="shortdesc">Shows single tile MapServer Layer</div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This shows an example of using a MapServer Layer in single tile
+ mode. Single tile mode can be useful when pulling data from dynamic
+ sources.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/marker-shadow.html b/misc/openlayers/examples/marker-shadow.html
new file mode 100644
index 0000000..a244653
--- /dev/null
+++ b/misc/openlayers/examples/marker-shadow.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Vector Graphics with Shadows</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .smallmap {
+ width: 300px;
+ }
+
+ .docs {
+ padding: 0px 5px;
+ }
+
+ td {
+ vertical-align: top;
+ }
+
+ </style>
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+ var SHADOW_Z_INDEX = 10;
+ var MARKER_Z_INDEX = 11;
+
+ var DIAMETER = 200;
+ var NUMBER_OF_FEATURES = 15;
+
+ var map, layer;
+
+ function init() {
+ map = new OpenLayers.Map("map");
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ layer = new OpenLayers.Layer.Vector(
+ "Marker Drop Shadows",
+ {
+ styleMap: new OpenLayers.StyleMap({
+ // Set the external graphic and background graphic images.
+ externalGraphic: "../img/marker-gold.png",
+ backgroundGraphic: "./img/marker_shadow.png",
+
+ // Makes sure the background graphic is placed correctly relative
+ // to the external graphic.
+ backgroundXOffset: 0,
+ backgroundYOffset: -7,
+
+ // Set the z-indexes of both graphics to make sure the background
+ // graphics stay in the background (shadows on top of markers looks
+ // odd; let's not do that).
+ graphicZIndex: MARKER_Z_INDEX,
+ backgroundGraphicZIndex: SHADOW_Z_INDEX,
+
+ pointRadius: 10
+ }),
+ isBaseLayer: true,
+ rendererOptions: {yOrdering: true},
+ renderers: renderer
+ }
+ );
+
+ map.addLayers([layer]);
+
+ // Add a drag feature control to move features around.
+ var dragFeature = new OpenLayers.Control.DragFeature(layer);
+
+ map.addControl(dragFeature);
+
+ dragFeature.activate();
+
+ map.zoomToMaxExtent();
+
+ drawFeatures();
+ }
+
+ function drawFeatures() {
+
+ layer.removeFeatures(layer.features);
+
+ // Create features at random around the center.
+ var center = map.getViewPortPxFromLonLat(map.getCenter());
+
+ // Add the ordering features. These are the gold ones that all have the same z-index
+ // and succomb to y-ordering.
+ var features = [];
+
+ for (var index = 0; index < NUMBER_OF_FEATURES; index++) {
+ // Calculate a random x/y. Subtract half the diameter to make some
+ // features negative.
+ var x = (parseInt(Math.random() * DIAMETER)) - (DIAMETER / 2);
+ var y = (parseInt(Math.random() * DIAMETER)) - (DIAMETER / 2);
+
+ var pixel = new OpenLayers.Pixel(center.x + x, center.y + y);
+
+ var lonLat = map.getLonLatFromPixel(pixel);
+ features.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat)
+ )
+ );
+ }
+
+ layer.addFeatures(features);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Marker Shadows using Background Graphics/Z-Indexes</h1>
+
+ <div id="tags">
+ markers, shadow, style
+ </div>
+
+ <p id="shortdesc">
+ This example shows off marker shadows using background graphics and z-indexes. Move the features around to show the shadows' interaction.
+ </p>
+
+ <br>
+
+ <table>
+ <tr>
+ <td>
+ <div id="map" class="smallmap"></div>
+ </td>
+ <td>
+ <div class="docs">
+ The features in this map were generated at random. Each of these features have a <i>backgroundGraphic</i> property set in the style map to add a shadow image. Note that the background graphics are not duplicated features with a different style.
+ <br><br>
+ The shadows were set to have a different z-index than the markers themselves, using the <i>backgroundGraphicZIndex</i> property. This makes sure all shadows stay behind the markers, keeping a clean look. The shadows were also placed nicely relative to the external graphic using the <i>backgroundXOffset</i> and <i>backgroundYOffset</i> property.
+ <br><br>
+ Y-ordering on the layer is enabled. See the <a href="./ordering.html">ordering example</a>.
+ </div>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <button onclick="drawFeatures()">Redraw Features</button>
+ </td>
+ </tr>
+ </table>
+
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/markerResize.html b/misc/openlayers/examples/markerResize.html
new file mode 100644
index 0000000..cb59bcf
--- /dev/null
+++ b/misc/openlayers/examples/markerResize.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Resize a Marker</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ var size, icon;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ map.addLayer(layer);
+ var markers = new OpenLayers.Layer.Markers( "Markers" );
+ map.addLayer(markers);
+
+ size = new OpenLayers.Size(21, 25);
+ calculateOffset = function(size) {
+ return new OpenLayers.Pixel(-(size.w/2), -size.h); };
+ icon = new OpenLayers.Icon(
+ 'http://www.openlayers.org/dev/img/marker.png',
+ size, null, calculateOffset);
+ markers.addMarker(
+ new OpenLayers.Marker(new OpenLayers.LonLat(-71,40), icon));
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+
+ function resize() {
+
+ size = new OpenLayers.Size(size.w + 10, size.h + 10);
+ icon.setSize(size);
+
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Resize a Marker</h1>
+ <div id="tags">
+ animation, resizing, style, size
+ </div>
+ <div id="shortdesc">Dynamically resize a marker</div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This example shows how to create a OpenLayers.Layer.Markers layer,
+ add an icon, put it into a marker, and add the marker to the layer.
+ Once the marker has been added it is possible to use setSize() on the
+ icon in order to resize the marker.</p>
+ </div>
+ <div style="background-color:purple" onclick="resize()"> click to resize marker</div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/markers.html b/misc/openlayers/examples/markers.html
new file mode 100644
index 0000000..7d9552f
--- /dev/null
+++ b/misc/openlayers/examples/markers.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Markers Layer Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function init(){
+ OpenLayers.ProxyHost="/proxy/?url=";
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ var newl = new OpenLayers.Layer.Text( "text", { location:"./textfile.txt"} );
+ map.addLayer(newl);
+
+ var markers = new OpenLayers.Layer.Markers( "Markers" );
+ map.addLayer(markers);
+
+ var size = new OpenLayers.Size(21,25);
+ var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+ var icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png',size,offset);
+ markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
+
+ var halfIcon = icon.clone();
+ markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,45),halfIcon));
+
+ marker = new OpenLayers.Marker(new OpenLayers.LonLat(90,10),icon.clone());
+ marker.setOpacity(0.2);
+ marker.events.register('mousedown', marker, function(evt) { alert(this.icon.url); OpenLayers.Event.stop(evt); });
+ markers.addMarker(marker);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+
+ halfIcon.setOpacity(0.5);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Markers Layer Example</h1>
+ <div id="tags">Marker, event, mousedown, popup, inco</div>
+ <div id="shortdesc">Show markers layer with different markers</div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This is an example of an OpenLayers.Layers.Markers layer that shows
+ some examples of adding markers. Also demonstrated is registering a
+ mousedown effect on a marker.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/markersTextLayer.html b/misc/openlayers/examples/markersTextLayer.html
new file mode 100644
index 0000000..b8e1acb
--- /dev/null
+++ b/misc/openlayers/examples/markersTextLayer.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Using a Layer.Text to display markers</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function init(){
+ OpenLayers.ProxyHost="/proxy/?url=";
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ map.addLayer(layer);
+
+ var newl = new OpenLayers.Layer.Text( "text", {location: "./textfile.txt"} );
+ map.addLayer(newl);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Using a Layer.Text to display markers</h1>
+ <div id="tags">
+ textlayer, csv, tsv, basic, popup
+ </div>
+ <p id="shortdesc">
+ The Layer.Text class reads a Tab seperated values file and displays it as markers on
+ the map.
+ </p>
+ <div id="map" class="smallmap"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/measure.html b/misc/openlayers/examples/measure.html
new file mode 100644
index 0000000..1cf61ad
--- /dev/null
+++ b/misc/openlayers/examples/measure.html
@@ -0,0 +1,203 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ p {
+ width: 512px;
+ }
+ #options {
+ position: relative;
+ width: 512px;
+ }
+ #output {
+ float: right;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, measureControls;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ map.addLayers([wmsLayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ // style the sketch fancy
+ var sketchSymbolizers = {
+ "Point": {
+ pointRadius: 4,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 1,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#333333"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#666666",
+ strokeDashstyle: "dash"
+ },
+ "Polygon": {
+ strokeWidth: 2,
+ strokeOpacity: 1,
+ strokeColor: "#666666",
+ fillColor: "white",
+ fillOpacity: 0.3
+ }
+ };
+ var style = new OpenLayers.Style();
+ style.addRules([
+ new OpenLayers.Rule({symbolizer: sketchSymbolizers})
+ ]);
+ var styleMap = new OpenLayers.StyleMap({"default": style});
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ measureControls = {
+ line: new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Path, {
+ persist: true,
+ handlerOptions: {
+ layerOptions: {
+ renderers: renderer,
+ styleMap: styleMap
+ }
+ }
+ }
+ ),
+ polygon: new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Polygon, {
+ persist: true,
+ handlerOptions: {
+ layerOptions: {
+ renderers: renderer,
+ styleMap: styleMap
+ }
+ }
+ }
+ )
+ };
+
+ var control;
+ for(var key in measureControls) {
+ control = measureControls[key];
+ control.events.on({
+ "measure": handleMeasurements,
+ "measurepartial": handleMeasurements
+ });
+ map.addControl(control);
+ }
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ document.getElementById('noneToggle').checked = true;
+ }
+
+ function handleMeasurements(event) {
+ var geometry = event.geometry;
+ var units = event.units;
+ var order = event.order;
+ var measure = event.measure;
+ var element = document.getElementById('output');
+ var out = "";
+ if(order == 1) {
+ out += "measure: " + measure.toFixed(3) + " " + units;
+ } else {
+ out += "measure: " + measure.toFixed(3) + " " + units + "<sup>2</" + "sup>";
+ }
+ element.innerHTML = out;
+ }
+
+ function toggleControl(element) {
+ for(key in measureControls) {
+ var control = measureControls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+
+ function toggleGeodesic(element) {
+ for(key in measureControls) {
+ var control = measureControls[key];
+ control.geodesic = element.checked;
+ }
+ }
+
+ function toggleImmediate(element) {
+ for(key in measureControls) {
+ var control = measureControls[key];
+ control.setImmediate(element.checked);
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Measure Example</h1>
+ <div id="tags">
+ measuring, geodesic, area, length, distance
+ </div>
+ <p id="shortdesc">
+ Demonstrates the measure control to measure distances and areas.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="options">
+ <div id="output">
+ </div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="line" id="lineToggle" onclick="toggleControl(this);" />
+ <label for="lineToggle">measure distance</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);" />
+ <label for="polygonToggle">measure area</label>
+ </li>
+ <li>
+ <input type="checkbox" name="geodesic" id="geodesicToggle" onclick="toggleGeodesic(this);" />
+ <label for="geodesicToggle">use geodesic measures</label>
+ </li>
+ <li>
+ <input type="checkbox" name="immediate" id="immediateToggle" onclick="toggleImmediate(this);" />
+ <label for="immediateToggle">use immediate measures</label>
+ </li>
+ </ul>
+ <p>Note that the geometries drawn are planar geometries and the
+ metrics returned by the measure control are planar measures by
+ default. If your map is in a geographic projection or you have the
+ appropriate projection definitions to transform your geometries into
+ geographic coordinates, you can set the "geodesic" property of the control
+ to true to calculate geodesic measures instead of planar measures.
+ Also you have the possibility to set the "immediate" property to true
+ to get a new calculated value once the mouse has been mooved.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile-base.js b/misc/openlayers/examples/mobile-base.js
new file mode 100644
index 0000000..5440f93
--- /dev/null
+++ b/misc/openlayers/examples/mobile-base.js
@@ -0,0 +1,167 @@
+// API key for http://openlayers.org. Please get your own at
+// http://bingmapsportal.com/ and use that instead.
+var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+
+// initialize map when page ready
+var map;
+var gg = new OpenLayers.Projection("EPSG:4326");
+var sm = new OpenLayers.Projection("EPSG:900913");
+
+var init = function (onSelectFeatureFunction) {
+
+ var vector = new OpenLayers.Layer.Vector("Vector Layer", {});
+
+ var sprintersLayer = new OpenLayers.Layer.Vector("Sprinters", {
+ styleMap: new OpenLayers.StyleMap({
+ externalGraphic: "img/mobile-loc.png",
+ graphicOpacity: 1.0,
+ graphicWidth: 16,
+ graphicHeight: 26,
+ graphicYOffset: -26
+ })
+ });
+
+ var sprinters = getFeatures();
+ sprintersLayer.addFeatures(sprinters);
+
+ var selectControl = new OpenLayers.Control.SelectFeature(sprintersLayer, {
+ autoActivate:true,
+ onSelect: onSelectFeatureFunction});
+
+ var geolocate = new OpenLayers.Control.Geolocate({
+ id: 'locate-control',
+ geolocationOptions: {
+ enableHighAccuracy: false,
+ maximumAge: 0,
+ timeout: 7000
+ }
+ });
+ // create map
+ map = new OpenLayers.Map({
+ div: "map",
+ theme: null,
+ projection: sm,
+ numZoomLevels: 18,
+ controls: [
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.TouchNavigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ geolocate,
+ selectControl
+ ],
+ layers: [
+ new OpenLayers.Layer.OSM("OpenStreetMap", null, {
+ transitionEffect: 'resize'
+ }),
+ new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "Road",
+ // custom metadata parameter to request the new map style - only useful
+ // before May 1st, 2011
+ metadataParams: {
+ mapVersion: "v1"
+ },
+ name: "Bing Road",
+ transitionEffect: 'resize'
+ }),
+ new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "Aerial",
+ name: "Bing Aerial",
+ transitionEffect: 'resize'
+ }),
+ new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "AerialWithLabels",
+ name: "Bing Aerial + Labels",
+ transitionEffect: 'resize'
+ }),
+ vector,
+ sprintersLayer
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+
+ var style = {
+ fillOpacity: 0.1,
+ fillColor: '#000',
+ strokeColor: '#f00',
+ strokeOpacity: 0.6
+ };
+ geolocate.events.register("locationupdated", this, function(e) {
+ vector.removeAllFeatures();
+ vector.addFeatures([
+ new OpenLayers.Feature.Vector(
+ e.point,
+ {},
+ {
+ graphicName: 'cross',
+ strokeColor: '#f00',
+ strokeWidth: 2,
+ fillOpacity: 0,
+ pointRadius: 10
+ }
+ ),
+ new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(e.point.x, e.point.y),
+ e.position.coords.accuracy / 2,
+ 50,
+ 0
+ ),
+ {},
+ style
+ )
+ ]);
+ map.zoomToExtent(vector.getDataExtent());
+ });
+
+ function getFeatures() {
+ var features = {
+ "type": "FeatureCollection",
+ "features": [
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [1332700, 7906300]},
+ "properties": {"Name": "Igor Tihonov", "Country":"Sweden", "City":"Gothenburg"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [790300, 6573900]},
+ "properties": {"Name": "Marc Jansen", "Country":"Germany", "City":"Bonn"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [568600, 6817300]},
+ "properties": {"Name": "Bart van den Eijnden", "Country":"Netherlands", "City":"Utrecht"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [-7909900, 5215100]},
+ "properties": {"Name": "Christopher Schmidt", "Country":"United States of America", "City":"Boston"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [-937400, 5093200]},
+ "properties": {"Name": "Jorge Gustavo Rocha", "Country":"Portugal", "City":"Braga"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [-355300, 7547800]},
+ "properties": {"Name": "Jennie Fletcher ", "Country":"Scotland", "City":"Edinburgh"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [657068.53608487, 5712321.2472725]},
+ "properties": {"Name": "Bruno Binet ", "Country":"France", "City":"Chambéry"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [667250.8958124, 5668048.6072737]},
+ "properties": {"Name": "Eric Lemoine", "Country":"France", "City":"Theys"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [653518.03606319, 5721118.5122914]},
+ "properties": {"Name": "Antoine Abt", "Country":"France", "City":"La Motte Servolex"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [657985.78042416, 5711862.6251028]},
+ "properties": {"Name": "Pierre Giraud", "Country":"France", "City":"Chambéry"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [742941.93818208, 5861818.9477535]},
+ "properties": {"Name": "Stéphane Brunner", "Country":"Switzerland", "City":"Paudex"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [736082.61064069, 5908165.4649505]},
+ "properties": {"Name": "Frédéric Junod", "Country":"Switzerland", "City":"Montagny-près-Yverdon"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [771595.97057525, 5912284.7041793]},
+ "properties": {"Name": "Cédric Moullet", "Country":"Switzerland", "City":"Payerne"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [744205.23922364, 5861277.319748]},
+ "properties": {"Name": "Benoit Quartier", "Country":"Switzerland", "City":"Lutry"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [1717430.147101, 5954568.7127565]},
+ "properties": {"Name": "Andreas Hocevar", "Country":"Austria", "City":"Graz"}},
+ { "type": "Feature", "geometry": {"type": "Point", "coordinates": [-12362007.067301,5729082.2365672]},
+ "properties": {"Name": "Tim Schaub", "Country":"United States of America", "City":"Bozeman"}}
+ ]
+ };
+
+ var reader = new OpenLayers.Format.GeoJSON();
+
+ return reader.read(features);
+ }
+
+};
diff --git a/misc/openlayers/examples/mobile-drawing.html b/misc/openlayers/examples/mobile-drawing.html
new file mode 100644
index 0000000..0cb9c52
--- /dev/null
+++ b/misc/openlayers/examples/mobile-drawing.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Mobile Drawing</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.mobile.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <script src="../lib/OpenLayers.js?mobile"></script>
+ <script src="mobile-drawing.js"></script>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ }
+ #map {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+ .olControlAttribution {
+ font-size: 10px;
+ bottom: 5px;
+ right: 5px;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemInactive {
+ background-position: -1px -1px;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemActive {
+ background-position: -1px -24px;
+ }
+ #title, #tags, #shortdesc {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Mobile Drawing Example</h1>
+ <div id="tags">
+ mobile, drawing
+ </div>
+ <p id="shortdesc">
+ A full-screen map with drawing tools for mobile devices.
+ </p>
+ <div id="map"></div>
+ <script>
+ init();
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile-drawing.js b/misc/openlayers/examples/mobile-drawing.js
new file mode 100644
index 0000000..bac903c
--- /dev/null
+++ b/misc/openlayers/examples/mobile-drawing.js
@@ -0,0 +1,71 @@
+function init() {
+
+ // create a vector layer for drawing
+ var vector = new OpenLayers.Layer.Vector('Vector Layer', {
+ styleMap: new OpenLayers.StyleMap({
+ temporary: OpenLayers.Util.applyDefaults({
+ pointRadius: 16
+ }, OpenLayers.Feature.Vector.style.temporary),
+ 'default': OpenLayers.Util.applyDefaults({
+ pointRadius: 16,
+ strokeWidth: 3,
+ }, OpenLayers.Feature.Vector.style['default']),
+ select: OpenLayers.Util.applyDefaults({
+ pointRadius: 16,
+ strokeWidth: 3
+ }, OpenLayers.Feature.Vector.style.select)
+ })
+ });
+
+ // OpenLayers' EditingToolbar internally creates a Navigation control, we
+ // want a TouchNavigation control here so we create our own editing toolbar
+ var toolbar = new OpenLayers.Control.Panel({
+ displayClass: 'olControlEditingToolbar'
+ });
+ toolbar.addControls([
+ // this control is just there to be able to deactivate the drawing
+ // tools
+ new OpenLayers.Control({
+ displayClass: 'olControlNavigation'
+ }),
+ new OpenLayers.Control.ModifyFeature(vector, {
+ vertexRenderIntent: 'temporary',
+ displayClass: 'olControlModifyFeature'
+ }),
+ new OpenLayers.Control.DrawFeature(vector, OpenLayers.Handler.Point, {
+ displayClass: 'olControlDrawFeaturePoint'
+ }),
+ new OpenLayers.Control.DrawFeature(vector, OpenLayers.Handler.Path, {
+ displayClass: 'olControlDrawFeaturePath'
+ }),
+ new OpenLayers.Control.DrawFeature(vector, OpenLayers.Handler.Polygon, {
+ displayClass: 'olControlDrawFeaturePolygon'
+ })
+ ]);
+
+ var osm = new OpenLayers.Layer.OSM();
+ osm.wrapDateLine = false;
+
+ map = new OpenLayers.Map({
+ div: 'map',
+ projection: 'EPSG:900913',
+ numZoomLevels: 18,
+ controls: [
+ new OpenLayers.Control.TouchNavigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Zoom(),
+ toolbar
+ ],
+ layers: [osm, vector],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1,
+ theme: null
+ });
+
+ // activate the first control to render the "navigation icon"
+ // as active
+ toolbar.controls[0].activate();
+}
diff --git a/misc/openlayers/examples/mobile-jq.html b/misc/openlayers/examples/mobile-jq.html
new file mode 100644
index 0000000..5e16caa
--- /dev/null
+++ b/misc/openlayers/examples/mobile-jq.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ <title>OpenLayers with jQuery Mobile</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0/jquery.mobile-1.0.min.css">
+ <script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
+ <script src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js"></script>
+ <link rel="stylesheet" href="../theme/default/style.mobile.css" type="text/css">
+ <link rel="stylesheet" href="style.mobile-jq.css" type="text/css">
+ <script src="../lib/OpenLayers.js?mobile"></script>
+ <script src="mobile-base.js"></script>
+ <script src="mobile-jq.js"></script>
+ </head>
+ <body>
+ <h1 id="title">OpenLayers with jQuery Mobile</h1>
+ <div id="tags">
+ mobile, jquery
+ </div>
+ <p id="shortdesc">
+ Using jQuery Mobile to display an OpenLayers map.
+ </p>
+
+ <div data-role="page" id="mappage">
+ <div data-role="content">
+ <div id="map"></div>
+ </div>
+
+ <div data-role="footer">
+ <a href="#searchpage" data-icon="search" data-role="button">Search</a>
+ <a href="#" id="locate" data-icon="locate" data-role="button">Locate</a>
+ <a href="#layerspage" data-icon="layers" data-role="button">Layers</a>
+ </div>
+ <div id="navigation" data-role="controlgroup" data-type="vertical">
+ <a href="#" data-role="button" data-icon="plus" id="plus"
+ data-iconpos="notext"></a>
+ <a href="#" data-role="button" data-icon="minus" id="minus"
+ data-iconpos="notext"></a>
+ </div>
+ </div>
+
+ <div data-role="page" id="searchpage">
+ <div data-role="header">
+ <h1>Search</h1>
+ </div>
+ <div data-role="fieldcontain">
+ <input type="search" name="query" id="query"
+ value="" placeholder="Search for places"
+ autocomplete="off"/>
+ </div>
+ <ul data-role="listview" data-inset="true" id="search_results"></ul>
+ </div>
+
+ <div data-role="page" id="layerspage">
+ <div data-role="header">
+ <h1>Layers</h1>
+ </div>
+ <div data-role="content">
+ <ul data-role="listview" data-inset="true" data-theme="d" data-dividertheme="c" id="layerslist">
+ </ul>
+ </div>
+ </div>
+
+ <div id="popup" data-role="dialog">
+ <div data-position="inline" data-theme="d" data-role="header">
+ <h1>Details</h1>
+ </div>
+ <div data-theme="c" data-role="content">
+ <ul id="details-list" data-role="listview">
+ </ul>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile-jq.js b/misc/openlayers/examples/mobile-jq.js
new file mode 100644
index 0000000..7e487cd
--- /dev/null
+++ b/misc/openlayers/examples/mobile-jq.js
@@ -0,0 +1,159 @@
+// Start with the map page
+window.location.replace(window.location.href.split("#")[0] + "#mappage");
+
+var selectedFeature = null;
+
+// fix height of content
+function fixContentHeight() {
+ var footer = $("div[data-role='footer']:visible"),
+ content = $("div[data-role='content']:visible:visible"),
+ viewHeight = $(window).height(),
+ contentHeight = viewHeight - footer.outerHeight();
+
+ if ((content.outerHeight() + footer.outerHeight()) !== viewHeight) {
+ contentHeight -= (content.outerHeight() - content.height() + 1);
+ content.height(contentHeight);
+ }
+
+ if (window.map && window.map instanceof OpenLayers.Map) {
+ map.updateSize();
+ } else {
+ // initialize map
+ init(function(feature) {
+ selectedFeature = feature;
+ $.mobile.changePage("#popup", "pop");
+ });
+ initLayerList();
+ }
+}
+
+// one-time initialisation of button handlers
+
+$("#plus").live('click', function(){
+ map.zoomIn();
+});
+
+$("#minus").live('click', function(){
+ map.zoomOut();
+});
+
+$("#locate").live('click',function(){
+ var control = map.getControlsBy("id", "locate-control")[0];
+ if (control.active) {
+ control.getCurrentLocation();
+ } else {
+ control.activate();
+ }
+});
+
+//fix the content height AFTER jQuery Mobile has rendered the map page
+$('#mappage').live('pageshow',function (){
+ fixContentHeight();
+});
+
+$(window).bind("orientationchange resize pageshow", fixContentHeight);
+
+
+
+$('#popup').live('pageshow',function(event, ui){
+ var li = "";
+ for(var attr in selectedFeature.attributes){
+ li += "<li><div style='width:25%;float:left'>" + attr + "</div><div style='width:75%;float:right'>"
+ + selectedFeature.attributes[attr] + "</div></li>";
+ }
+ $("ul#details-list").empty().append(li).listview("refresh");
+});
+
+$('#searchpage').live('pageshow',function(event, ui){
+ $('#query').bind('change', function(e){
+ $('#search_results').empty();
+ if ($('#query')[0].value === '') {
+ return;
+ }
+ $.mobile.showPageLoadingMsg();
+
+ // Prevent form send
+ e.preventDefault();
+
+ var searchUrl = 'http://ws.geonames.org/searchJSON?featureClass=P&maxRows=10';
+ searchUrl += '&name_startsWith=' + $('#query')[0].value;
+ $.getJSON(searchUrl, function(data) {
+ $.each(data.geonames, function() {
+ var place = this;
+ $('<li>')
+ .hide()
+ .append($('<h2 />', {
+ text: place.name
+ }))
+ .append($('<p />', {
+ html: '<b>' + place.countryName + '</b> ' + place.fcodeName
+ }))
+ .appendTo('#search_results')
+ .click(function() {
+ $.mobile.changePage('#mappage');
+ var lonlat = new OpenLayers.LonLat(place.lng, place.lat);
+ map.setCenter(lonlat.transform(gg, sm), 10);
+ })
+ .show();
+ });
+ $('#search_results').listview('refresh');
+ $.mobile.hidePageLoadingMsg();
+ });
+ });
+ // only listen to the first event triggered
+ $('#searchpage').die('pageshow', arguments.callee);
+});
+
+
+function initLayerList() {
+ $('#layerspage').page();
+ $('<li>', {
+ "data-role": "list-divider",
+ text: "Base Layers"
+ })
+ .appendTo('#layerslist');
+ var baseLayers = map.getLayersBy("isBaseLayer", true);
+ $.each(baseLayers, function() {
+ addLayerToList(this);
+ });
+
+ $('<li>', {
+ "data-role": "list-divider",
+ text: "Overlay Layers"
+ })
+ .appendTo('#layerslist');
+ var overlayLayers = map.getLayersBy("isBaseLayer", false);
+ $.each(overlayLayers, function() {
+ addLayerToList(this);
+ });
+ $('#layerslist').listview('refresh');
+
+ map.events.register("addlayer", this, function(e) {
+ addLayerToList(e.layer);
+ });
+}
+
+function addLayerToList(layer) {
+ var item = $('<li>', {
+ "data-icon": "check",
+ "class": layer.visibility ? "checked" : ""
+ })
+ .append($('<a />', {
+ text: layer.name
+ })
+ .click(function() {
+ $.mobile.changePage('#mappage');
+ if (layer.isBaseLayer) {
+ layer.map.setBaseLayer(layer);
+ } else {
+ layer.setVisibility(!layer.getVisibility());
+ }
+ })
+ )
+ .appendTo('#layerslist');
+ layer.events.on({
+ 'visibilitychanged': function() {
+ $(item).toggleClass('checked');
+ }
+ });
+}
diff --git a/misc/openlayers/examples/mobile-layers.html b/misc/openlayers/examples/mobile-layers.html
new file mode 100644
index 0000000..d258674
--- /dev/null
+++ b/misc/openlayers/examples/mobile-layers.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>OpenLayers Mobile Layers</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.mobile.css" type="text/css">
+ <script src="../lib/OpenLayers.js?mobile"></script>
+ <script src="mobile-layers.js"></script>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+ width: 100%;
+ }
+
+ @media only screen and (max-width: 600px) {
+ html, body {
+ height: 117%;
+ }
+ }
+
+ #map {
+ width: 100%;
+ position: relative;
+ height: 100%;
+ }
+
+ .olControlAttribution {
+ position: absolute;
+ font-size: 10px;
+ bottom: 0 !important;
+ right: 0 !important;
+ background: rgba(0, 0, 0, 0.1);
+ font-family: Arial;
+ padding: 2px 4px;
+ border-radius: 5px 0 0 0;
+ }
+
+ #title, #tags, #shortdesc {
+ display: none;
+ }
+ </style>
+</head>
+<body>
+<h1 id="title">Mobile example with various layer types</h1>
+
+<div id="tags">
+ mobile, WMS, WFS, KML
+</div>
+<p id="shortdesc">
+ A mobile example displaying various layer types: WMS, WFS, KML.
+</p>
+
+<div id="map"></div>
+<script>
+ init();
+</script>
+</body>
+</html>
diff --git a/misc/openlayers/examples/mobile-layers.js b/misc/openlayers/examples/mobile-layers.js
new file mode 100644
index 0000000..62c65e1
--- /dev/null
+++ b/misc/openlayers/examples/mobile-layers.js
@@ -0,0 +1,71 @@
+// initialize map when page ready
+var map;
+
+// Get rid of address bar on iphone/ipod
+var fixSize = function() {
+ window.scrollTo(0, 0);
+ document.body.style.height = '100%';
+ if (!(/(iphone|ipod)/.test(navigator.userAgent.toLowerCase()))) {
+ if (document.body.parentNode) {
+ document.body.parentNode.style.height = '100%';
+ }
+ }
+};
+setTimeout(fixSize, 700);
+setTimeout(fixSize, 1500);
+
+// allow testing of specific renderers via "?renderer=Canvas", etc
+var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+function init() {
+
+ map = new OpenLayers.Map({
+ div: "map",
+ theme: null,
+ controls: [
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.TouchNavigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Zoom()
+ ]
+ });
+
+ var wms = new OpenLayers.Layer.WMS("OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'},
+ {isBaseLayer: true, transitionEffect: 'resize'}
+ );
+
+ var kml = new OpenLayers.Layer.Vector("KML", {
+ projection: map.displayProjection,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml/sundials.kml",
+ format: new OpenLayers.Format.KML({
+ extractStyles: true,
+ extractAttributes: true
+ })
+ }),
+ renderers: renderer
+ });
+
+ var wfs = new OpenLayers.Layer.Vector("States", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.WFS({
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp"
+ }),
+ renderers: renderer
+ });
+
+ map.addLayers([wms, wfs, kml]);
+
+ map.setCenter(new OpenLayers.LonLat(-104, 42), 3);
+};
diff --git a/misc/openlayers/examples/mobile-navigation.html b/misc/openlayers/examples/mobile-navigation.html
new file mode 100644
index 0000000..6814a72
--- /dev/null
+++ b/misc/openlayers/examples/mobile-navigation.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Mobile Navigation Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/style.mobile.css" type="text/css" />
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script type="text/javascript" src="../lib/OpenLayers.js?mobile"></script>
+ <script type="text/javascript" src="mobile-navigation.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Mobile Navigation</h1>
+
+ <div id="tags">
+ mobile, touch, drag, move, zoom, navigate
+ </div>
+
+ <div id="shortdesc">Demonstrate map navigation on mobile</div>
+
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+
+ <p>
+ This example demonstrates what OpenLayers provides for map
+ navigation on mobile devices.
+ </p>
+
+ <p>
+ The TouchNavigation control allows to pan the map with touch
+ gestures on the screen &ndash; "touchstart", "touchmove",
+ "touchend" sequences. It also allows to zoom in with double taps,
+ and to zoom out with two-finger single taps. The latter is only
+ available on devices supporting multi-touch. Note that in most
+ devices Android doesn't support multi-touch in the browser.
+ </p>
+
+ <p>
+ The Zoom control provides + and - buttons for zooming in and
+ out. These buttons should work on any device, and the zoom out
+ button is especially needed for devices that don't support
+ multi-touch.
+ </p>
+ <p>
+ See the <a href="mobile-navigation.js" target="_blank">mobile-navigation.js
+ source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile-navigation.js b/misc/openlayers/examples/mobile-navigation.js
new file mode 100644
index 0000000..3d4818a
--- /dev/null
+++ b/misc/openlayers/examples/mobile-navigation.js
@@ -0,0 +1,24 @@
+var map;
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ theme: null,
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ numZoomLevels: 18,
+ controls: [
+ new OpenLayers.Control.TouchNavigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Zoom()
+ ],
+ layers: [
+ new OpenLayers.Layer.OSM("OpenStreetMap", null, {
+ transitionEffect: 'resize'
+ })
+ ]
+ });
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+}
diff --git a/misc/openlayers/examples/mobile-sencha.html b/misc/openlayers/examples/mobile-sencha.html
new file mode 100644
index 0000000..3b491b2
--- /dev/null
+++ b/misc/openlayers/examples/mobile-sencha.html
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
+ <meta name="apple-mobile-web-app-capable" content="yes"/>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>OpenLayers with Sencha Touch</title>
+ <script src="../lib/OpenLayers.js?mobile"></script>
+ <link rel="stylesheet" href="../theme/default/style.mobile.css" type="text/css">
+ <link rel="stylesheet" href="http://cdn.sencha.io/touch/1.1.0/resources/css/sencha-touch.css">
+ <script src="http://cdn.sencha.io/touch/1.1.0/sencha-touch.js"></script>
+ <script src="mobile-sencha.js"></script>
+ <script src="mobile-base.js"></script>
+ <style>
+ .searchList {
+ min-height: 150px;
+ }
+
+ .close-btn {
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ }
+
+ img.minus {
+ -webkit-mask-image: url(img/minus1.png);
+ }
+
+ img.layers {
+ -webkit-mask-image: url(img/list.png);
+ }
+
+ .gx-layer-item {
+ margin-left: 10px;
+ }
+
+ #map {
+ width: 100%;
+ height: 100%;
+ }
+
+ .olControlAttribution {
+ font-size: 10px;
+ bottom: 5px;
+ right: 5px;
+ }
+
+ #title, #tags, #shortdesc {
+ display: none;
+ }
+ </style>
+ <script>
+
+ var app = new Ext.Application({
+ name: "ol",
+ launch: function() {
+ this.viewport = new Ext.Panel({
+ fullscreen: true,
+ dockedItems: [{
+ dock: "bottom",
+ xtype: "toolbar",
+ ui: "light",
+ layout: {
+ pack: "center"
+ },
+ items: [{
+ iconCls: "search",
+ iconMask: true,
+ handler: function() {
+ // this is the app
+ if (!app.searchFormPopupPanel) {
+ app.searchFormPopupPanel = new App.SearchFormPopupPanel({
+ map: map
+ });
+ }
+ app.searchFormPopupPanel.show('pop');
+ }
+ }, {
+ iconCls: "locate",
+ iconMask: true,
+ handler: function() {
+ var geolocate = map.getControlsBy("id", "locate-control")[0];
+ if (geolocate.active) {
+ geolocate.getCurrentLocation();
+ } else {
+ geolocate.activate();
+ }
+ }
+ }, {
+ xtype: "spacer"
+ }, {
+ iconMask: true,
+ iconCls: "add",
+ handler: function() {
+ map.zoomIn();
+ }
+ }, {
+ iconMask: true,
+ iconCls: "minus",
+ handler: function() {
+ map.zoomOut();
+ }
+ }, {
+ xtype: "spacer"
+ }, {
+ iconMask: true,
+ iconCls: "layers",
+ handler: function() {
+ if (!app.popup) {
+ app.popup = new Ext.Panel({
+ floating: true,
+ modal: true,
+ centered: true,
+ hideOnMaskTap: true,
+ width: 240,
+ items: [{
+ xtype: 'app_layerlist',
+ map: map
+ }],
+ scroll: 'vertical'
+ });
+ }
+ app.popup.show('pop');
+ }
+ }]
+ }],
+ items: [
+ {
+ xtype: "component",
+ scroll: false,
+ monitorResize: true,
+ id: "map",
+ listeners: {
+ render: function() {
+ var self = this;
+ init(function(feature) {
+ var htmlContent = "";
+ for (var property in feature.data) {
+ if (feature.data[property] != 'undefined') {
+ htmlContent = htmlContent + feature.data[property] + "<br>";
+ }
+ }
+ if (self.featurePopup) {
+ self.featurePopup.destroy();
+ }
+ self.featurePopup = new Ext.Panel({
+ floating: true,
+ modal: true,
+ centered: true,
+ hideOnMaskTap: true,
+ width: 240,
+ html: htmlContent,
+ scroll: 'vertical'
+ });
+ self.featurePopup.show();
+ })
+ },
+ resize: function() {
+ if (window.map) {
+ map.updateSize();
+ }
+ },
+ scope: {
+ featurePopup: null
+ }
+ }
+ }
+ ]
+ });
+ }
+ });
+ </script>
+ </head>
+ <body>
+ <h1 id="title">OpenLayers with Sencha Touch</h1>
+
+ <div id="tags">
+ mobile, sencha touch
+ </div>
+ <p id="shortdesc">
+ Using Sencha Touch to display an OpenLayers map.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile-sencha.js b/misc/openlayers/examples/mobile-sencha.js
new file mode 100644
index 0000000..1b79455
--- /dev/null
+++ b/misc/openlayers/examples/mobile-sencha.js
@@ -0,0 +1,198 @@
+Ext.ns('App');
+
+/**
+ * The model for the geonames records used in the search
+ */
+Ext.regModel('Geonames', {
+ fields: ['countryName', 'toponymName', 'name', 'lat', 'lng']
+});
+
+/**
+ * Custom class for the Search
+ */
+App.SearchFormPopupPanel = Ext.extend(Ext.Panel, {
+ map: null,
+ floating: true,
+ modal: true,
+ centered: true,
+ hideOnMaskTap: true,
+ width: Ext.is.Phone ? undefined : 400,
+ height: Ext.is.Phone ? undefined : 400,
+ scroll: false,
+ layout: 'fit',
+ fullscreen: Ext.is.Phone ? true : undefined,
+ url: 'http://ws.geonames.org/searchJSON?',
+ errorText: 'Sorry, we had problems communicating with geonames.org. Please try again.',
+ errorTitle: 'Communication error',
+ maxResults: 6,
+ featureClass: "P",
+
+ createStore: function(){
+ this.store = new Ext.data.Store({
+ model: 'Geonames',
+ proxy: {
+ type: 'scripttag',
+ timeout: 5000,
+ listeners: {
+ exception: function(){
+ this.hide();
+ Ext.Msg.alert(this.errorTitle, this.errorText, Ext.emptyFn);
+ },
+ scope: this
+ },
+ url: this.url,
+ reader: {
+ type: 'json',
+ root: 'geonames'
+ }
+ }
+ });
+ },
+
+ doSearch: function(searchfield, evt){
+ var q = searchfield.getValue();
+ this.store.load({
+ params: {
+ featureClass: this.featureClass,
+ maxRows: this.maxResults,
+ name_startsWith: encodeURIComponent(q)
+ }
+ });
+ },
+
+ onItemTap: function(dataView, index, item, event){
+ var record = this.store.getAt(index);
+ var lon = record.get('lng');
+ var lat = record.get('lat');
+ var lonlat = new OpenLayers.LonLat(lon, lat);
+ map.setCenter(lonlat.transform(gg, sm), 12);
+ this.hide("pop");
+ },
+
+ initComponent: function(){
+ this.createStore();
+ this.resultList = new Ext.List({
+ scroll: 'vertical',
+ cls: 'searchList',
+ loadingText: "Searching ...",
+ store: this.store,
+ itemTpl: '<div>{name} ({countryName})</div>',
+ listeners: {
+ itemtap: this.onItemTap,
+ scope: this
+ }
+ });
+ this.formContainer = new Ext.form.FormPanel({
+ scroll: false,
+ items: [{
+ xtype: 'button',
+ cls: 'close-btn',
+ ui: 'decline-small',
+ text: 'Close',
+ handler: function(){
+ this.hide();
+ },
+ scope: this
+ }, {
+ xtype: 'fieldset',
+ scroll: false,
+ title: 'Search for a place',
+ items: [{
+ xtype: 'searchfield',
+ label: 'Search',
+ placeHolder: 'placename',
+ listeners: {
+ action: this.doSearch,
+ scope: this
+ }
+ },
+ this.resultList
+ ]
+ }]
+ });
+ this.items = [{
+ xtype: 'panel',
+ layout: 'fit',
+ items: [this.formContainer]
+ }];
+ App.SearchFormPopupPanel.superclass.initComponent.call(this);
+ }
+});
+
+App.LayerList = Ext.extend(Ext.List, {
+
+ map: null,
+
+ createStore: function(){
+ Ext.regModel('Layer', {
+ fields: ['id', 'name', 'visibility', 'zindex']
+ });
+ var data = [];
+ Ext.each(this.map.layers, function(layer){
+ if (layer.displayInLayerSwitcher === true) {
+ var visibility = layer.isBaseLayer ? (this.map.baseLayer == layer) : layer.getVisibility();
+ data.push({
+ id: layer.id,
+ name: layer.name,
+ visibility: visibility,
+ zindex: layer.getZIndex()
+ });
+ }
+ });
+ return new Ext.data.Store({
+ model: 'Layer',
+ sorters: 'zindex',
+ data: data
+ });
+ },
+
+ initComponent: function(){
+ this.store = this.createStore();
+ this.itemTpl = new Ext.XTemplate(
+ '<tpl if="visibility == true">',
+ '<img width="20" src="img/check-round-green.png">',
+ '</tpl>',
+ '<tpl if="visibility == false">',
+ '<img width="20" src="img/check-round-grey.png">',
+ '</tpl>',
+ '<span class="gx-layer-item">{name}</span>'
+ );
+ this.listeners = {
+ itemtap: function(dataview, index, item, e){
+ var record = dataview.getStore().getAt(index);
+ var layer = this.map.getLayersBy("id", record.get("id"))[0];
+ if (layer.isBaseLayer) {
+ this.map.setBaseLayer(layer);
+ }
+ else {
+ layer.setVisibility(!layer.getVisibility());
+ }
+ record.set("visibility", layer.getVisibility());
+ }
+ };
+ this.map.events.on({
+ "changelayer": this.onChangeLayer,
+ scope: this
+ });
+ App.LayerList.superclass.initComponent.call(this);
+ },
+
+ findLayerRecord: function(layer){
+ var found;
+ this.store.each(function(record){
+ if (record.get("id") === layer.id) {
+ found = record;
+ }
+ }, this);
+ return found;
+ },
+
+ onChangeLayer: function(evt){
+ if (evt.property == "visibility") {
+ var record = this.findLayerRecord(evt.layer);
+ record.set("visibility", evt.layer.getVisibility());
+ }
+ }
+
+});
+Ext.reg('app_layerlist', App.LayerList);
diff --git a/misc/openlayers/examples/mobile-wmts-vienna.css b/misc/openlayers/examples/mobile-wmts-vienna.css
new file mode 100644
index 0000000..605932a
--- /dev/null
+++ b/misc/openlayers/examples/mobile-wmts-vienna.css
@@ -0,0 +1,205 @@
+html, body, #map {
+ margin: 0;
+ height: 100%;
+ width: 100%;
+}
+#map {
+ cursor: move;
+ background-color: #CCCCCC;
+ /* no highlighting of the map area when tapping the map on touch devices */
+ -webkit-tap-highlight-color: transparent;
+}
+#title, #tags, #shortdesc {
+ display: none;
+}
+div.olMapViewport {
+ -ms-touch-action: none;
+}
+
+/* Turn on GPU support where available */
+.olTileImage {
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -ms-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-perspective: 1000;
+ -moz-perspective: 1000;
+ -ms-perspective: 1000;
+ perspective: 1000;
+}
+
+/* Tile fade animation */
+.olLayerGrid .olTileImage {
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ -o-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+}
+
+/* Zoom Box */
+.olHandlerBoxZoomBox {
+ border: 2px solid red;
+ position: absolute;
+ background-color: white;
+ opacity: 0.50;
+ font-size: 1px;
+ filter: alpha(opacity=50);
+}
+.olDrawBox {
+ cursor: crosshair;
+}
+
+div.olControlAttribution {
+ position: absolute;
+ font-size: 10px;
+ text-align: right;
+ color: #BFEFFF;
+ bottom: 0;
+ right: 0;
+ background: rgba(0,0,100,0.2);
+ font-family: Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ padding: 2px 4px;
+ border-radius: 5px 0 0 0;
+}
+.olControlAttribution a {
+ font-weight: bold;
+ color: #BFEFFF;
+ text-decoration: none;
+}
+div.olControlZoomPanel {
+ height: 108px;
+ width: 36px;
+ position: absolute;
+ top: 20px;
+ left: inherit;
+ right: 20px;
+}
+div.olControlZoomPanel div {
+ cursor: pointer;
+ width: 36px;
+ height: 36px;
+ left: 0;
+ background-color: #ccc;
+ background-image: none;
+}
+div.olControlZoomPanel .olControlZoomInItemInactive,
+div.olControlZoomPanel .olControlZoomOutItemInactive {
+ top: 0;
+ background: rgba(0,0,100,0.4);
+ position: absolute;
+}
+div.olControlZoomPanel .olControlZoomInItemInactive {
+ border-radius: 5px 5px 0 0;
+}
+div.olControlZoomPanel .olControlZoomOutItemInactive {
+ border-radius: 0 0 5px 5px;
+ top: 37px;
+}
+div.olControlZoomPanel .olControlZoomOutItemInactive:after,
+div.olControlZoomPanel .olControlZoomInItemInactive:after {
+ font-weight: bold;
+ content: '+';
+ font-size: 36px;
+ padding: 7px;
+ z-index: 2000;
+ color: #BFEFFF;
+ line-height: 1em;
+}
+div.olControlZoomPanel .olControlZoomOutItemInactive:after {
+ content: '–';
+ line-height: 0.9em;
+ padding: 0 8px;
+}
+div.olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
+ display: none;
+}
+div.olControlZoomPanel div.olControlGeolocateItemInactive,
+div.olControlZoomPanel div.olControlGeolocateItemActive {
+ position: absolute;
+ right: 20px;
+ top: 98px;
+ border-radius: 5px 5px 5px 5px;
+ background: #ccc url(img/locate.png) center no-repeat;
+ background-color: rgba(0,0,100,0.4);
+}
+div.olControlZoomPanel div.olControlGeolocateItemActive {
+ background-color: rgba(0,0,100,0.2);
+}
+div.olControlGeolocateItemInactive:after {
+ font-weight: bold;
+ font-size: 36px;
+ padding: 7px;
+ z-index: 2000;
+ color: #BFEFFF;
+ line-height: 1em;
+ background: none;
+}
+.layerPanel {
+ position: absolute;
+ top: 20px;
+ right: 82px;
+}
+div.layerPanel div {
+ display: inline;
+ margin-left: 5px;
+ cursor: pointer;
+}
+div.layerPanel div:after {
+ font-weight: bold;
+ font-size: 18px;
+ font-family: arial;
+ padding: 8px;
+ color: #BFEFFF;
+ line-height: 36px;
+ border-radius: 5px 5px 5px 5px;
+ background-color: #ccc;
+ background: rgba(0,0,100,0.4);
+}
+div.layerPanel div.labelButtonItemInactive:after,
+div.layerPanel div.labelButtonItemActive:after {
+ content: 'Labels';
+}
+:lang(de) div.layerPanel div.labelButtonItemInactive:after,
+:lang(de) div.layerPanel div.labelButtonItemActive:after {
+ content: 'Text';
+}
+div.layerPanel div.labelButtonItemActive:after {
+ text-decoration: underline;
+ background: rgba(0,0,100,0.2);
+}
+div.layerPanel div.aerialButtonItemInactive:after,
+div.layerPanel div.aerialButtonItemActive:after {
+ content: 'Aerial';
+ border-radius: 5px 0 0 5px;
+}
+:lang(de) div.layerPanel div.aerialButtonItemInactive:after,
+:lang(de) div.layerPanel div.aerialButtonItemActive:after {
+ content: 'Luftbild';
+}
+div.layerPanel div.aerialButtonItemActive:after {
+ text-decoration: underline;
+ background: rgba(0,0,100,0.2);
+}
+div.layerPanel div.mapButtonItemInactive:after,
+div.layerPanel div.mapButtonItemActive:after {
+ content: 'Map';
+ border-radius: 0 5px 5px 0;
+}
+:lang(de) div.layerPanel div.mapButtonItemInactive:after,
+:lang(de) div.layerPanel div.mapButtonItemActive:after {
+ content: 'Karte';
+}
+div.layerPanel div.mapButtonItemActive:after {
+ text-decoration: underline;
+ background: rgba(0,0,100,0.2);
+}
+div.layerPanel div.mapButtonItemInactive,
+div.layerPanel div.mapButtonItemActive {
+ margin-left: 1px;
+}
diff --git a/misc/openlayers/examples/mobile-wmts-vienna.html b/misc/openlayers/examples/mobile-wmts-vienna.html
new file mode 100644
index 0000000..d6d127c
--- /dev/null
+++ b/misc/openlayers/examples/mobile-wmts-vienna.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <title>City of Vienna WMTS with REST Encoding and Geolocate</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black">
+ <link rel="stylesheet" href="mobile-wmts-vienna.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">City of Vienna WMTS for Desktop and Mobile Devices</h1>
+ <div id="tags">
+ mobile, vienna, ogdwien, rest, restful, wmts, geolocate, permalink
+ </div>
+ <p id="shortdesc">
+ A full-screen map for both desktop and mobile devices. Uses
+ language dependent CSS content and the WMTSCapabilities format to
+ retrieve layers from the ogdwien open data initiative of the City
+ of Vienna. Also has a lightweight custom anchor permalink
+ functionality and uses the Geolocate control.
+ </p>
+ <div id="map"></div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="mobile-wmts-vienna.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile-wmts-vienna.js b/misc/openlayers/examples/mobile-wmts-vienna.js
new file mode 100644
index 0000000..45ebecb
--- /dev/null
+++ b/misc/openlayers/examples/mobile-wmts-vienna.js
@@ -0,0 +1,281 @@
+var map;
+
+(function() {
+ // Set document language for css content
+ document.documentElement.lang = (navigator.userLanguage || navigator.language).split("-")[0];
+
+ // A panel for switching between Aerial and Map, and for turning labels
+ // on and off.
+ var layerPanel = new OpenLayers.Control.Panel({
+ displayClass: "layerPanel",
+ autoActivate: true
+ });
+ var aerialButton = new OpenLayers.Control({
+ type: OpenLayers.Control.TYPE_TOOL,
+ displayClass: "aerialButton",
+ eventListeners: {
+ activate: function() {
+ if (aerial) {map.setBaseLayer(aerial);}
+ }
+ }
+ });
+ var mapButton = new OpenLayers.Control({
+ type: OpenLayers.Control.TYPE_TOOL,
+ displayClass: "mapButton",
+ eventListeners: {
+ activate: function() {
+ if (fmzk) {map.setBaseLayer(fmzk);}
+ }
+ }
+ });
+ var labelButton = new OpenLayers.Control({
+ type: OpenLayers.Control.TYPE_TOGGLE,
+ displayClass: "labelButton",
+ eventListeners: {
+ activate: function() {
+ if (labels) {labels.setVisibility(true);}
+ },
+ deactivate: function() {
+ if (labels) {labels.setVisibility(false);}
+ }
+ }
+ });
+ layerPanel.addControls([aerialButton, mapButton, labelButton]);
+
+ var zoomPanel = new OpenLayers.Control.ZoomPanel();
+
+ // Geolocate control for the Locate button - the locationupdated handler
+ // draws a cross at the location and a circle showing the accuracy radius.
+ var geolocate = new OpenLayers.Control.Geolocate({
+ type: OpenLayers.Control.TYPE_TOGGLE,
+ bind: false,
+ watch: true,
+ geolocationOptions: {
+ enableHighAccuracy: false,
+ maximumAge: 0,
+ timeout: 7000
+ },
+ eventListeners: {
+ activate: function() {
+ map.addLayer(vector);
+ },
+ deactivate: function() {
+ map.removeLayer(vector);
+ vector.removeAllFeatures();
+ },
+ locationupdated: function(e) {
+ vector.removeAllFeatures();
+ vector.addFeatures([
+ new OpenLayers.Feature.Vector(e.point, null, {
+ graphicName: 'cross',
+ strokeColor: '#f00',
+ strokeWidth: 2,
+ fillOpacity: 0,
+ pointRadius: 10
+ }),
+ new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(e.point.x, e.point.y),
+ e.position.coords.accuracy / 2, 50, 0
+ ), null, {
+ fillOpacity: 0.1,
+ fillColor: '#000',
+ strokeColor: '#f00',
+ strokeOpacity: 0.6
+ }
+ )
+ ]);
+ map.zoomToExtent(vector.getDataExtent());
+ }
+ }
+ });
+ zoomPanel.addControls([geolocate]);
+
+ // Fallback layer when outside Vienna
+ var osm = new OpenLayers.Layer.OSM();
+
+ // Map with navigation controls optimized for touch devices
+ map = new OpenLayers.Map({
+ div: "map",
+ theme: null,
+ projection: "EPSG:3857",
+ units: "m",
+ maxResolution: 38.21851413574219,
+ numZoomLevels: 8,
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.Attribution(),
+ zoomPanel,
+ layerPanel
+ ],
+ eventListeners: {
+ moveend: function() {
+ // update anchor for permalinks
+ var ctr = map.getCenter();
+ window.location.hash = "x="+ctr.lon+"&y="+ctr.lat+"&z="+map.getZoom();
+ // switch to OSM when outside Vienna
+ if (!map.getExtent().intersectsBounds(fmzk.tileFullExtent)) {
+ if (map.baseLayer !== osm) {
+ map.addLayer(osm);
+ map.setBaseLayer(osm);
+ }
+ } else if (map.baseLayer === osm) {
+ map.removeLayer(osm);
+ }
+ }
+ }
+ });
+ layerPanel.activateControl(mapButton);
+ layerPanel.activateControl(labelButton);
+
+ // Vector layer for the location cross and circle
+ var vector = new OpenLayers.Layer.Vector("Vector Layer");
+
+ // Defaults for the WMTS layers
+ var defaults = {
+ zoomOffset: 12,
+ requestEncoding: "REST",
+ matrixSet: "google3857",
+ attribution: 'Datenquelle: Stadt Wien - <a href="http://data.wien.gv.at">data.wien.gv.at</a>'
+ };
+
+ // The WMTS layers we're going to add
+ var fmzk, aerial, labels;
+
+ // zoom to initial extent or restore position from permalink
+ function zoomToInitialExtent() {
+ var extent = fmzk.tileFullExtent,
+ ctr = extent.getCenterLonLat(),
+ zoom = map.getZoomForExtent(extent, true),
+ params = OpenLayers.Util.getParameters("?"+window.location.hash.substr(1));
+ OpenLayers.Util.applyDefaults(params, {x:ctr.lon, y:ctr.lat, z:zoom});
+ map.setCenter(new OpenLayers.LonLat(params.x, params.y), params.z);
+ }
+
+ // Request capabilities and create layers
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+ OpenLayers.Request.GET({
+ url: "http://maps.wien.gv.at/wmts/1.0.0/WMTSCapabilities.xml",
+ success: function(request) {
+ var format = new OpenLayers.Format.WMTSCapabilities();
+ var doc = request.responseText,
+ caps = format.read(doc);
+ fmzk = format.createLayer(caps, OpenLayers.Util.applyDefaults(
+ {layer:"fmzk"}, defaults
+ ));
+ aerial = format.createLayer(caps, OpenLayers.Util.applyDefaults(
+ {layer:"lb"}, defaults
+ ));
+ labels = format.createLayer(caps, OpenLayers.Util.applyDefaults(
+ {layer:"beschriftung", isBaseLayer: false, transitionEffect: 'map-resize'},
+ defaults
+ ));
+ map.addLayers([fmzk, aerial, labels]);
+ zoomToInitialExtent();
+ }
+ });
+
+ // Instead of building the layers from the capabilities document, we could
+ // look at it ourselves and create the layers manually. If you want to try
+ // that, uncomment the following code and remove the "Request capabilities
+ // and create layers" block above.
+ /*
+ var extent = new OpenLayers.Bounds(1799448.394855, 6124949.74777, 1848250.442089, 6162571.828177);
+ defaults.tileFullExtent = extent;
+ fmzk = new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults({
+ url: [
+ "http://maps.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps1.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps2.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps3.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps4.wien.gv.at/wmts/fmzk/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg"
+ ],
+ layer: "fmzk",
+ style: "pastell"
+ },
+ defaults));
+ aerial = new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults({
+ url: [
+ "http://maps.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps1.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps2.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps3.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg",
+ "http://maps4.wien.gv.at/wmts/lb/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.jpeg"
+ ],
+ layer: "lb",
+ style: "farbe"
+ },
+ defaults));
+ labels = new OpenLayers.Layer.WMTS(OpenLayers.Util.applyDefaults({
+ url: [
+ "http://maps.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png",
+ "http://maps1.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png",
+ "http://maps2.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png",
+ "http://maps3.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png",
+ "http://maps4.wien.gv.at/wmts/beschriftung/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png"
+ ],
+ layer: "beschriftung",
+ style: "normal",
+ isBaseLayer: false,
+ transitionEffect: 'map-resize'
+ },
+ defaults));
+ map.addLayers([fmzk, aerial, labels]);
+ zoomToInitialExtent();
+ */
+
+})();
+
+// Reliably hide the address bar on Android and iOS devices. From
+// http://blog.nateps.com/how-to-hide-the-address-bar-in-a-full-screen
+(function() {
+ var page = document.getElementById("map"),
+ ua = navigator.userAgent,
+ iphone = ~ua.indexOf('iPhone') || ~ua.indexOf('iPod'),
+ ipad = ~ua.indexOf('iPad'),
+ ios = iphone || ipad,
+ // Detect if this is running as a fullscreen app from the homescreen
+ fullscreen = window.navigator.standalone,
+ android = ~ua.indexOf('Android'),
+ lastWidth = 0;
+
+ if (android) {
+ // Android's browser adds the scroll position to the innerHeight, just to
+ // make this really fucking difficult. Thus, once we are scrolled, the
+ // page height value needs to be corrected in case the page is loaded
+ // when already scrolled down. The pageYOffset is of no use, since it always
+ // returns 0 while the address bar is displayed.
+ window.onscroll = function() {
+ page.style.height = window.innerHeight + 'px';
+ };
+ }
+ var setupScroll = window.onload = function() {
+ // Start out by adding the height of the location bar to the width, so that
+ // we can scroll past it
+ if (ios) {
+ // iOS reliably returns the innerWindow size for documentElement.clientHeight
+ // but window.innerHeight is sometimes the wrong value after rotating
+ // the orientation
+ var height = document.documentElement.clientHeight;
+ // Only add extra padding to the height on iphone / ipod, since the ipad
+ // browser doesn't scroll off the location bar.
+ if (iphone && !fullscreen) height += 60;
+ page.style.height = height + 'px';
+ } else if (android) {
+ // The stock Android browser has a location bar height of 56 pixels, but
+ // this very likely could be broken in other Android browsers.
+ page.style.height = (window.innerHeight + 56) + 'px';
+ }
+ // Scroll after a timeout, since iOS will scroll to the top of the page
+ // after it fires the onload event
+ setTimeout(scrollTo, 0, 0, 1);
+ };
+ (window.onresize = function() {
+ var pageWidth = page.offsetWidth;
+ // Android doesn't support orientation change, so check for when the width
+ // changes to figure out when the orientation changes
+ if (lastWidth == pageWidth) return;
+ lastWidth = pageWidth;
+ setupScroll();
+ })();
+})();
diff --git a/misc/openlayers/examples/mobile.html b/misc/openlayers/examples/mobile.html
new file mode 100644
index 0000000..b2685e0
--- /dev/null
+++ b/misc/openlayers/examples/mobile.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>OpenLayers Mobile</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.mobile.css" type="text/css">
+ <script src="../lib/OpenLayers.js?mobile"></script>
+ <script src="mobile.js"></script>
+ <style>
+ html, body {
+ margin : 0;
+ padding : 0;
+ height : 100%;
+ width : 100%;
+ }
+ @media only screen and (max-width: 600px) {
+ html, body {
+ height : 117%;
+ }
+ }
+ #map {
+ width : 100%;
+ position : relative;
+ height : 100%;
+ }
+ .olControlAttribution {
+ position : absolute;
+ font-size : 10px;
+ bottom : 0 !important;
+ right : 0 !important;
+ background : rgba(0,0,0,0.1);
+ font-family : Arial;
+ padding : 2px 4px;
+ border-radius : 5px 0 0 0;
+ }
+ #title, #tags, #shortdesc {
+ display: none;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Basic Mobile Example</h1>
+ <div id="tags">
+ mobile
+ </div>
+ <p id="shortdesc">
+ A basic full-screen map for mobile devices.
+ </p>
+ <div id="map"></div>
+ <script>
+ init();
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mobile.js b/misc/openlayers/examples/mobile.js
new file mode 100644
index 0000000..9bbcb91
--- /dev/null
+++ b/misc/openlayers/examples/mobile.js
@@ -0,0 +1,39 @@
+// initialize map when page ready
+var map;
+
+// Get rid of address bar on iphone/ipod
+var fixSize = function() {
+ window.scrollTo(0,0);
+ document.body.style.height = '100%';
+ if (!(/(iphone|ipod)/.test(navigator.userAgent.toLowerCase()))) {
+ if (document.body.parentNode) {
+ document.body.parentNode.style.height = '100%';
+ }
+ }
+};
+setTimeout(fixSize, 700);
+setTimeout(fixSize, 1500);
+
+var init = function () {
+ // create map
+ map = new OpenLayers.Map({
+ div: "map",
+ theme: null,
+ controls: [
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.TouchNavigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Zoom()
+ ],
+ layers: [
+ new OpenLayers.Layer.OSM("OpenStreetMap", null, {
+ transitionEffect: 'resize'
+ })
+ ],
+ center: new OpenLayers.LonLat(742000, 5861000),
+ zoom: 3
+ });
+};
diff --git a/misc/openlayers/examples/modify-feature.html b/misc/openlayers/examples/modify-feature.html
new file mode 100644
index 0000000..cb19858
--- /dev/null
+++ b/misc/openlayers/examples/modify-feature.html
@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Modify Feature</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controls {
+ width: 512px;
+ }
+ #controlToggle {
+ padding-left: 1em;
+ }
+ #controlToggle li {
+ list-style: none;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectors, controls;
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ vectors = new OpenLayers.Layer.Vector("Vector Layer", {
+ renderers: renderer
+ });
+
+ map.addLayers([wms, vectors]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ if (console && console.log) {
+ function report(event) {
+ console.log(event.type, event.feature ? event.feature.id : event.components);
+ }
+ vectors.events.on({
+ "beforefeaturemodified": report,
+ "featuremodified": report,
+ "afterfeaturemodified": report,
+ "vertexmodified": report,
+ "sketchmodified": report,
+ "sketchstarted": report,
+ "sketchcomplete": report
+ });
+ }
+ controls = {
+ point: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.Point),
+ line: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.Path),
+ polygon: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.Polygon),
+ regular: new OpenLayers.Control.DrawFeature(vectors,
+ OpenLayers.Handler.RegularPolygon,
+ {handlerOptions: {sides: 5}}),
+ modify: new OpenLayers.Control.ModifyFeature(vectors)
+ };
+
+ for(var key in controls) {
+ map.addControl(controls[key]);
+ }
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+ document.getElementById('noneToggle').checked = true;
+ }
+
+ function update() {
+ // reset modification mode
+ controls.modify.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+ var rotate = document.getElementById("rotate").checked;
+ if(rotate) {
+ controls.modify.mode |= OpenLayers.Control.ModifyFeature.ROTATE;
+ }
+ var resize = document.getElementById("resize").checked;
+ if(resize) {
+ controls.modify.mode |= OpenLayers.Control.ModifyFeature.RESIZE;
+ var keepAspectRatio = document.getElementById("keepAspectRatio").checked;
+ if (keepAspectRatio) {
+ controls.modify.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE;
+ }
+ }
+ var drag = document.getElementById("drag").checked;
+ if(drag) {
+ controls.modify.mode |= OpenLayers.Control.ModifyFeature.DRAG;
+ }
+ if (rotate || drag) {
+ controls.modify.mode &= ~OpenLayers.Control.ModifyFeature.RESHAPE;
+ }
+ controls.modify.createVertices = document.getElementById("createVertices").checked;
+ var sides = parseInt(document.getElementById("sides").value);
+ sides = Math.max(3, isNaN(sides) ? 0 : sides);
+ controls.regular.handler.sides = sides;
+ var irregular = document.getElementById("irregular").checked;
+ controls.regular.handler.irregular = irregular;
+ }
+
+ function toggleControl(element) {
+ for(key in controls) {
+ var control = controls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Modify Feature Example</h1>
+ <div id="tags">
+ vertices, digitizing, draw, drawing
+ </div>
+ <div id="shortdesc">A demonstration of the ModifyFeature control for editing vector features.</div>
+ <div id="map" class="smallmap"></div>
+ <div id="controls">
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="point" id="pointToggle" onclick="toggleControl(this);" />
+ <label for="pointToggle">draw point</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="line" id="lineToggle" onclick="toggleControl(this);" />
+ <label for="lineToggle">draw line</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="regular" id="regularToggle" onclick="toggleControl(this);" />
+ <label for="regularToggle">draw regular polygon</label>
+ <label for="sides"> - sides</label>
+ <input id="sides" type="text" size="2" maxlength="2"
+ name="sides" value="5" onchange="update()" />
+ <ul>
+ <li>
+ <input id="irregular" type="checkbox"
+ name="irregular" onchange="update()" />
+ <label for="irregular">irregular</label>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <input type="radio" name="type" value="modify" id="modifyToggle"
+ onclick="toggleControl(this);" />
+ <label for="modifyToggle">modify feature</label>
+ <ul>
+ <li>
+ <input id="createVertices" type="checkbox" checked
+ name="createVertices" onchange="update()" />
+ <label for="createVertices">allow vertices creation</label>
+ </li>
+ <li>
+ <input id="rotate" type="checkbox"
+ name="rotate" onchange="update()" />
+ <label for="rotate">allow rotation</label>
+ </li>
+ <li>
+ <input id="resize" type="checkbox"
+ name="resize" onchange="update()" />
+ <label for="resize">allow resizing</label>
+ (<input id="keepAspectRatio" type="checkbox"
+ name="keepAspectRatio" onchange="update()" checked="checked" />
+ <label for="keepAspectRatio">keep aspect ratio</label>)
+ </li>
+ <li>
+ <input id="drag" type="checkbox"
+ name="drag" onchange="update()" />
+ <label for="drag">allow dragging</label>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mouse-position.html b/misc/openlayers/examples/mouse-position.html
new file mode 100644
index 0000000..924d3bc
--- /dev/null
+++ b/misc/openlayers/examples/mouse-position.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>MousePosition Control</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ var map = new OpenLayers.Map('map');
+
+ map.addControl(
+ new OpenLayers.Control.MousePosition({
+ prefix: '<a target="_blank" ' +
+ 'href="http://spatialreference.org/ref/epsg/4326/">' +
+ 'EPSG:4326</a> coordinates: ',
+ separator: ' | ',
+ numDigits: 2,
+ emptyString: 'Mouse is not over map.'
+ })
+ );
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+
+ map.addLayers([ol_wms]);
+ if (!map.getCenter()) {
+ map.zoomToMaxExtent();
+ }
+
+ map.events.register("mousemove", map, function(e) {
+ var position = this.events.getMousePosition(e);
+ OpenLayers.Util.getElement("coords").innerHTML = position;
+ });
+ }
+ </script>
+ </head>
+ <body onload="init();">
+ <h1 id="title">MousePosition Control</h1>
+ <div id="tags">
+ coordinate
+ </div>
+ <p id="shortdesc">
+ Use the MousePosition Control to display the coordinates of the cursor
+ inside or outside the map div.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="coords" style="height: 1.5em;"></div>
+ <p>
+ This example also shows how to use the the "prefix", "separator" and
+ "numDigits" options to customize the output of the MousePosition-Control.
+ By also setting the "emptyString"-property, the contents of the controls
+ element are resetted to the given string when the mouse isn't above the
+ map.
+ </p>
+ <p>
+ Moving your mouse to the upper left corner of this map should return
+ 'x=0,y=0' (pixel coordinates) -- in the past, it didn't in IE. If it
+ returns 'x=2,y=2', consider it a bug, and report it.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mousewheel-interval.html b/misc/openlayers/examples/mousewheel-interval.html
new file mode 100644
index 0000000..ce8d6a6
--- /dev/null
+++ b/misc/openlayers/examples/mousewheel-interval.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Mousewheel Interval Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function setCumulative() {
+ var nav = map.getControlsByClass("OpenLayers.Control.Navigation")[0];
+ var cumulative = document.getElementById("cumulative");
+ nav.handlers.wheel.cumulative = cumulative.checked;
+ }
+
+ function init(){
+ map = new OpenLayers.Map( 'map', {controls: [
+ new OpenLayers.Control.Navigation(
+ {mouseWheelOptions: {interval: 100}}
+ ),
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.ArgParser(),
+ new OpenLayers.Control.Attribution()
+ ]} );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Mousewheel Interval Example</h1>
+
+ <div id="tags">
+ performance, zoom by wheel
+ </div>
+
+ <div id="shortdesc">Let OpenLayers send less tile requests to the server when wheel-zooming.</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows how to configure the Navigation control to use
+ the mousewheel in a less server resource consuming way: as long as you
+ spin the mousewheel, no request will be sent to the server. Instead,
+ the zoomlevel delta will be recorded. After a delay (in this example
+ 100ms), a zoom action with the cumulated delta will be performed.</p>
+ <div>
+ <input id="cumulative" type="checkbox" checked="checked"
+ onchange="setCumulative()"/>
+ <label for="cumulative">Cumulative mode. If this mode is deactivated,
+ only one zoom event will be performed after the delay.</label>
+ </div>
+
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/multiserver.html b/misc/openlayers/examples/multiserver.html
new file mode 100644
index 0000000..64a5a45
--- /dev/null
+++ b/misc/openlayers/examples/multiserver.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Tiles from Multiple Servers</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+
+ var urlArray = ["http://tilecache.osgeo.org/wms-c/Basic.py",
+ "http://tilecache.osgeo.org/wms-c/Basic.py"];
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ urlArray,
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Multiple Server URLS</h1>
+
+ <div id="tags">
+ performance, multiple urls, request, light
+ </div>
+ <p id="shortdesc">
+ Load your tiles faster by pointing to the same server, but with different urls
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>Browsers typically limit the number of concurrent requests to the same
+ server, based on hostname. In order to ake tiles load more quickly, it
+ often makes sense to distribute requests over multiple hostnames to achieve
+ more concurrency. Typically, browsers perform best with 3 different
+ hostnames -- your performance may vary. (For example, if your server can't
+ handle more than 2 requests simultaneously, then additional hostnames will
+ not help you.)</p>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/multitouch.html b/misc/openlayers/examples/multitouch.html
new file mode 100644
index 0000000..0cab78a
--- /dev/null
+++ b/misc/openlayers/examples/multitouch.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Multitouch Test</title>
+ </head>
+ <body>
+ <div style="width:80%; height: 200px; border: 1px solid black; font-size: 5em;" id="box">
+ </div>
+ Touch inside the box. On a touch enabled browser, you will get the number
+ of detected touch events. If the box is red, your browser does not support
+ touch events.
+ <script>
+ var box = document.getElementById("box");
+ box.addEventListener("touchstart", function(evt) {
+ box.innerHTML = evt.touches.length;
+ evt.preventDefault();
+ });
+ box.addEventListener("touchmove", function(evt) {
+ box.innerHTML = evt.touches.length;
+ evt.preventDefault();
+ });
+ if (!(typeof box.ontouchstart != 'undefined')) {
+ box.style.backgroundColor = "red";
+ }
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/mvs.html b/misc/openlayers/examples/mvs.html
new file mode 100644
index 0000000..f3866f5
--- /dev/null
+++ b/misc/openlayers/examples/mvs.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+<!--
+ This probably needs to be renamed index.html for deployment.
+ Specifically, it needs to be the default page for whatever
+ directory it is in.
+-->
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+
+ <!-- this gmaps key generated for http://openlayers.org/dev/ -->
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA9XNhd8q0UdwNC7YSO4YZghSPUCi5aRYVveCcVYxzezM4iaj_gxQ9t-UajFL70jfcpquH5l1IJ-Zyyw'></script>
+ <!-- Localhost key -->
+ <!--<script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhT2yXp_ZAY8_ufC3CFXhHIE1NvwkxTS6gjckBmeABOGXIUiOiZObZESPg'></script>-->
+<script src="http://openlayers.org/dev/lib/OpenLayers.js"></script>
+
+<script>
+
+function runMVS() {
+ OpenLayers.ProxyHost = '/proxy/?url=';
+ if (document.location.protocol != "file:") {
+ theArgs = OpenLayers.Util.getParameters();
+ } else {
+ theArgs = {};
+ theArgs.center = "0,0";
+ theArgs.zoom = "0";
+ theArgs.data = "textfile.txt";
+ theArgs.controls = 'panzoom,mouse';
+ theArgs.layers = 'openlayers';
+ }
+
+
+ // ----
+ // TODO: Handle all this parsing better.
+ var safeArgs = {};
+
+ var DEFAULT_LAT = 0;
+ var DEFAULT_LON = 0;
+ var DEFAULT_ZOOM_LEVEL = 0;
+ var DEFAULT_CONTROLS = ['panzoom','mouse'];
+ var DEFAULT_LAYERS = ['openlayers'];
+
+ var IDX_LAT = 0;
+ var IDX_LON = 1;
+
+ safeArgs.centerLat = theArgs.center ?
+ parseFloat(theArgs.center.split(",")[IDX_LAT]) : DEFAULT_LAT;
+
+ safeArgs.centerLon = theArgs.center ?
+ parseFloat(theArgs.center.split(",")[IDX_LON]) : DEFAULT_LON;
+
+ safeArgs.zoom = theArgs.zoom ? parseInt(theArgs.zoom) : DEFAULT_ZOOM_LEVEL;
+
+ safeArgs.controls = theArgs.controls ?
+ theArgs.controls.split(",") : DEFAULT_CONTROLS;
+
+ safeArgs.layers = theArgs.layers ?
+ theArgs.layers.split(",") : DEFAULT_LAYERS;
+
+ safeArgs.data = theArgs.data; // TODO: Make this "safe".
+ safeArgs.marker = theArgs.marker; // TODO: Make this "safe".
+
+ // -----
+ var theMVS = new OpenLayers.Map('map', {controls: [], maxResolution: 'auto'});
+ for(var i = 0; i < safeArgs.controls.length; i++) {
+ switch(safeArgs.controls[i]) {
+ case 'panzoombar':
+ theMVS.addControl(new OpenLayers.Control.PanZoomBar());
+ break;
+ case 'panzoom':
+ theMVS.addControl(new OpenLayers.Control.PanZoom());
+ break;
+ case 'layerswitcher':
+ theMVS.addControl(new OpenLayers.Control.LayerSwitcher());
+ break;
+ case 'mouse':
+ theMVS.addControl(new OpenLayers.Control.MouseDefaults());
+ break;
+ }
+ }
+ for(var i = 0; i < safeArgs.layers.length; i++) {
+ switch(safeArgs.layers[i]) {
+ case 'openlayers':
+ theMVS.addLayer(
+ new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} )
+ );
+ break;
+ case 'nasa':
+ theMVS.addLayer(
+ new OpenLayers.Layer.WMS("NASA Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {"EXCEPTIONS" : "application/vnd.ogc.se_inimage",
+ "format" : "image/jpeg",
+ layers:"landsat7"}
+ ));
+ break;
+ case 'gmaps':
+ theMVS.addLayer(
+ new OpenLayers.Layer.Google( "Google" , {type: G_HYBRID_MAP })
+ );
+ break;
+ }
+ }
+ theMVS.setCenter(new OpenLayers.LonLat(safeArgs.centerLon, safeArgs.centerLat), safeArgs.zoom);
+
+ if (safeArgs.marker) {
+ var m = new OpenLayers.Layer.Markers("Marker");
+ m.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(safeArgs.centerLon, safeArgs.centerLat)));
+ theMVS.addLayer(m);
+ }
+ if (safeArgs.data) {
+ theMVS.addLayer(new OpenLayers.Layer.Text("Data", {location: safeArgs.data}));
+ }
+
+}
+</script>
+</head>
+<body style="margin:0px;"
+ onload="runMVS();">
+<div id="map"
+ style="width: 100%; height: 100%;
+ background: lightyellow;
+ "></div>
+</body>
+</html>
diff --git a/misc/openlayers/examples/navigation-control.html b/misc/openlayers/examples/navigation-control.html
new file mode 100644
index 0000000..a0c272e
--- /dev/null
+++ b/misc/openlayers/examples/navigation-control.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Navigation Control</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map', { controls: [] });
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ nav = new OpenLayers.Control.Navigation({'zoomWheelEnabled': false});
+ map.addControl(nav);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Navigation Control</h1>
+
+ <div id="tags">
+ drag, move, zoom, navigate, light
+ </div>
+
+ <div id="shortdesc">Demonstrate Navigation Control features</div>
+
+ <div id="map" class="smallmap"></div>
+ <a href="#" onclick="nav.enableZoomWheel();return false">Turn on Wheel Zoom</a> | <a href="#" onclick="nav.disableZoomWheel(); return false;">Turn off Wheel Zoom</a>
+ <div id="docs">
+ <p>This example demonstrates a couple features of the Navigation
+ control. The Navigation control controls most map dragging, movement,
+ zooming, etc. In this case, we have a demonstration of how to create a
+ navigation control with no zoom wheel action, which can then be enabled
+ or disabled by the user.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/navigation-history.html b/misc/openlayers/examples/navigation-history.html
new file mode 100644
index 0000000..e93007d
--- /dev/null
+++ b/misc/openlayers/examples/navigation-history.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Navigation History Example</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #panel {
+ right: 0px;
+ height: 30px;
+ width: 200px;
+ }
+ #panel div {
+ float: left;
+ margin: 5px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, nav, panel;
+
+ function init() {
+ map = new OpenLayers.Map('map');
+
+ nav = new OpenLayers.Control.NavigationHistory();
+ // parent control must be added to the map
+ map.addControl(nav);
+
+ panel = new OpenLayers.Control.Panel(
+ {div: document.getElementById("panel")}
+ );
+ panel.addControls([nav.next, nav.previous]);
+ map.addControl(panel);
+
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Map Navigation History Example</h1>
+
+ <div id="tags">
+ history, basic
+ </div>
+
+ <p id="shortdesc">
+ A control for zooming to previous and next map extents.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ Map navigation history controls<div id="panel"></div>
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/navtoolbar-alwaysZoom.html b/misc/openlayers/examples/navtoolbar-alwaysZoom.html
new file mode 100644
index 0000000..7976918
--- /dev/null
+++ b/misc/openlayers/examples/navtoolbar-alwaysZoom.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>A navToolbar with an alwaysZoom ZoomBox</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <!-- Override the position of the toolbar to make it fit in a small map -->
+ <style type='text/css'>
+ .olControlNavToolbar {
+ top: 150px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function init() {
+
+ //Creation of a custom panel with a ZoomBox control with the alwaysZoom option sets to true
+ OpenLayers.Control.CustomNavToolbar = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * Constructor: OpenLayers.Control.NavToolbar
+ * Add our two mousedefaults controls.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+
+
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ this.addControls([
+ new OpenLayers.Control.Navigation(),
+ //Here it come
+ new OpenLayers.Control.ZoomBox({alwaysZoom:true})
+ ]);
+ // To make the custom navtoolbar use the regular navtoolbar style
+ this.displayClass = 'olControlNavToolbar'
+ },
+
+
+
+ /**
+ * Method: draw
+ * calls the default draw, and then activates mouse defaults.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+ this.defaultControl = this.controls[0];
+ return div;
+ }
+ });
+
+ var map;
+
+ map = new OpenLayers.Map('map');
+
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ map.addLayers([wms]);
+ map.zoomToMaxExtent();
+
+ var panel = new OpenLayers.Control.CustomNavToolbar();
+ map.addControl(panel);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">A navToolbar with an alwaysZoom ZoomBox</h1>
+ <div id="tags">
+ navigation toolbar
+ </div>
+ <p id="shortdesc">
+ Demo of a custom NavToolbar which uses a zoomBox tool that always zoom in even when the zoom box is too big.
+ </p>
+ <div id="map" class="smallmap"> </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/navtoolbar-outsidemap.html b/misc/openlayers/examples/navtoolbar-outsidemap.html
new file mode 100644
index 0000000..f41142b
--- /dev/null
+++ b/misc/openlayers/examples/navtoolbar-outsidemap.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Custom Navigation Toolbar</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map', { controls: [new OpenLayers.Control.PanZoom()] } );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+ var panel = new OpenLayers.Control.NavToolbar({'div':OpenLayers.Util.getElement('paneldiv')});
+ map.addControl(panel);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Navigation Toolbar: Outside the Map</h1>
+ <div id="tags">
+ navigation toolbar, style, position, div
+ </div>
+ <div id="map" class="smallmap"></div>
+ <div id="paneldiv" class="olControlNavToolbar"></div>
+ <div id="docs">
+ <p> To place the Naviation Toolbar outside the map:</p>
+ <ul>
+ <li>Load the default stylesheet into the page.</li>
+ <li>Override the location of the Navigation toolbar in your CSS by setting <tt>#yourElementId div</tt> to have a top of 0px</li>
+ <li>Specify the HTML element as a 'div' option in your NavToolbar constructor</li>
+ <li>Add the olControlNavToolbar class to your div.</li>
+ </ul>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/navtoolbar.html b/misc/openlayers/examples/navtoolbar.html
new file mode 100644
index 0000000..e4de88f
--- /dev/null
+++ b/misc/openlayers/examples/navtoolbar.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <title>NavToolbar Demo</title>
+ <style type='text/css'>
+ #map {
+ height: 512px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init() {
+ map = new OpenLayers.Map( 'map', { controls: [new OpenLayers.Control.PanZoom()] } );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ var panel = new OpenLayers.Control.NavToolbar();
+ map.addControl(panel);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">NavToolbar Demo</h1>
+ <div id="tags">
+ navigation toolbar, basic
+ </div>
+ <p id="shortdesc">
+ Demo the NavToolbar, a subclass of Control.Panel which shows icons for
+ navigation.
+ </p>
+ <div id="map" class="smallmap"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/offline-storage.html b/misc/openlayers/examples/offline-storage.html
new file mode 100644
index 0000000..6a1ebd6
--- /dev/null
+++ b/misc/openlayers/examples/offline-storage.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Offline Storage Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ bottom: 0;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script>OpenLayers.Console = window.console || OpenLayers.Console;</script>
+ <script src="offline-storage.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Offline Storage Example</h1>
+
+ <div id="tags">
+ mobile, local storage, persistence, cache, html5
+ </div>
+
+ <div id="shortdesc">Caching viewed tiles</div>
+
+ <div id="map" class="smallmap"></div>
+ <div>Cache status: <span id="hits"></span> <span id="status"></span></div>
+ <div><input id="read" type="checkbox">Read from cache [<input id="tileloadstart" name="type" type="radio">try cache first] [<input id="tileerror" name="type" type="radio">try online first<sup>1</sup>]</div>
+ <div><input id="write" type="checkbox">Write to cache</div>
+ <div><button id="clear">Clear cached tiles</button><button id="seed">Seed current extent</button>
+ <br>
+ <p><sup>1</sup> <small>Disconnect your device from the network to test - only works for same origin layers.</small></p>
+ <br>
+ <div id="docs">
+ <p>This example shows how to use the CacheWrite control to cache tiles
+ that are being viewed in the browser's local storage, and how to use
+ the CacheRead control to use cached tiles when offline or on a slow
+ connection. See <a href="offline-storage.js">offline-storage.js</a>
+ for the source code.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/offline-storage.js b/misc/openlayers/examples/offline-storage.js
new file mode 100644
index 0000000..e0b5929
--- /dev/null
+++ b/misc/openlayers/examples/offline-storage.js
@@ -0,0 +1,199 @@
+// Use proxy to get same origin URLs for tiles that don't support CORS.
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+var map, cacheWrite, cacheRead1, cacheRead2;
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.OSM("OpenStreetMap (CORS)", null, {
+ eventListeners: {
+ tileloaded: updateStatus,
+ loadend: detect
+ }
+ }),
+ new OpenLayers.Layer.WMS("OSGeo (same origin - proxied)", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
+ layers: "basic"
+ }, {
+ eventListeners: {
+ tileloaded: updateStatus
+ }
+ })
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+ // try cache before loading from remote resource
+ cacheRead1 = new OpenLayers.Control.CacheRead({
+ eventListeners: {
+ activate: function() {
+ cacheRead2.deactivate();
+ }
+ }
+ });
+ // try loading from remote resource and fall back to cache
+ cacheRead2 = new OpenLayers.Control.CacheRead({
+ autoActivate: false,
+ fetchEvent: "tileerror",
+ eventListeners: {
+ activate: function() {
+ cacheRead1.deactivate();
+ }
+ }
+ });
+ cacheWrite = new OpenLayers.Control.CacheWrite({
+ imageFormat: "image/jpeg",
+ eventListeners: {
+ cachefull: function() {
+ if (seeding) {
+ stopSeeding();
+ }
+ status.innerHTML = "Cache full.";
+ }
+ }
+ });
+ var layerSwitcher = new OpenLayers.Control.LayerSwitcher();
+ map.addControls([cacheRead1, cacheRead2, cacheWrite, layerSwitcher]);
+ layerSwitcher.maximizeControl();
+
+
+
+ // add UI and behavior
+ var status = document.getElementById("status"),
+ hits = document.getElementById("hits"),
+ cacheHits = 0,
+ seeding = false;
+ var read = document.getElementById("read");
+ read.checked = true;
+ read.onclick = toggleRead;
+ var write = document.getElementById("write");
+ write.checked = false;
+ write.onclick = toggleWrite;
+ document.getElementById("clear").onclick = clearCache;
+ var tileloadstart = document.getElementById("tileloadstart");
+ tileloadstart.checked = "checked";
+ tileloadstart.onclick = setType;
+ document.getElementById("tileerror").onclick = setType;
+ document.getElementById("seed").onclick = startSeeding;
+
+ // detect what the browser supports
+ function detect(evt) {
+ // detection is only done once, so we remove the listener.
+ evt.object.events.unregister("loadend", null, detect);
+ var tile = map.baseLayer.grid[0][0];
+ try {
+ var canvasContext = tile.getCanvasContext();
+ if (canvasContext) {
+ // will throw an exception if CORS image requests are not supported
+ canvasContext.canvas.toDataURL();
+ } else {
+ status.innerHTML = "Canvas not supported. Try a different browser.";
+ }
+ } catch(e) {
+ // we remove the OSM layer if CORS image requests are not supported.
+ map.setBaseLayer(map.layers[1]);
+ evt.object.destroy();
+ layerSwitcher.destroy();
+ }
+ }
+
+ // update the number of cache hits and detect missing CORS support
+ function updateStatus(evt) {
+ if (window.localStorage) {
+ status.innerHTML = localStorage.length + " entries in cache.";
+ } else {
+ status.innerHTML = "Local storage not supported. Try a different browser.";
+ }
+ if (evt && evt.tile.url.substr(0, 5) === "data:") {
+ cacheHits++;
+ }
+ hits.innerHTML = cacheHits + " cache hits.";
+ }
+
+ // turn the cacheRead controls on and off
+ function toggleRead() {
+ if (!this.checked) {
+ cacheRead1.deactivate();
+ cacheRead2.deactivate();
+ } else {
+ setType();
+ }
+ }
+
+ // turn the cacheWrite control on and off
+ function toggleWrite() {
+ cacheWrite[cacheWrite.active ? "deactivate" : "activate"]();
+ }
+
+ // clear all tiles from the cache
+ function clearCache() {
+ OpenLayers.Control.CacheWrite.clearCache();
+ updateStatus();
+ }
+
+ // activate the cacheRead control that matches the desired fetch strategy
+ function setType() {
+ if (tileloadstart.checked) {
+ cacheRead1.activate();
+ } else {
+ cacheRead2.activate();
+ }
+ }
+
+ // start seeding the cache
+ function startSeeding() {
+ var layer = map.baseLayer,
+ zoom = map.getZoom();
+ seeding = {
+ zoom: zoom,
+ extent: map.getExtent(),
+ center: map.getCenter(),
+ cacheWriteActive: cacheWrite.active,
+ buffer: layer.buffer,
+ layer: layer
+ };
+ // make sure the next setCenter triggers a load
+ map.zoomTo(zoom === layer.numZoomLevels-1 ? zoom - 1 : zoom + 1);
+ // turn on cache writing
+ cacheWrite.activate();
+ // turn off cache reading
+ cacheRead1.deactivate();
+ cacheRead2.deactivate();
+
+ layer.events.register("loadend", null, seed);
+
+ // start seeding
+ map.setCenter(seeding.center, zoom);
+ }
+
+ // seed a zoom level based on the extent at the time startSeeding was called
+ function seed() {
+ var layer = seeding.layer;
+ var tileWidth = layer.tileSize.w;
+ var nextZoom = map.getZoom() + 1;
+ var extentWidth = seeding.extent.getWidth() / map.getResolutionForZoom(nextZoom);
+ // adjust the layer's buffer size so we don't have to pan
+ layer.buffer = Math.ceil((extentWidth / tileWidth - map.getSize().w / tileWidth) / 2);
+ map.zoomIn();
+ if (nextZoom === layer.numZoomLevels-1) {
+ stopSeeding();
+ }
+ }
+
+ // stop seeding (when done or when cache is full)
+ function stopSeeding() {
+ // we're done - restore previous settings
+ seeding.layer.events.unregister("loadend", null, seed);
+ seeding.layer.buffer = seeding.buffer;
+ map.setCenter(seeding.center, seeding.zoom);
+ if (!seeding.cacheWriteActive) {
+ cacheWrite.deactivate();
+ }
+ if (read.checked) {
+ setType();
+ }
+ seeding = false;
+ }
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/openls.html b/misc/openlayers/examples/openls.html
new file mode 100644
index 0000000..257ef61
--- /dev/null
+++ b/misc/openlayers/examples/openls.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
+ <meta name="apple-mobile-web-app-capable" content="yes"/>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+ <title>OpenLS: Geocoding Example</title>
+ <script type="text/javascript" src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function init() {
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+ map = new OpenLayers.Map('map', {
+ controls: [
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.Permalink(),
+ new OpenLayers.Control.Navigation()
+ ]
+ });
+ layer = new OpenLayers.Layer.OSM("OpenStreetMap", null, {
+ transitionEffect: 'resize'
+ });
+ map.addLayers([layer]);
+ map.zoomToMaxExtent();
+ }
+ function submitform() {
+ var queryString = document.forms[0].query.value;
+ OpenLayers.Request.POST({
+ url: "http://www.openrouteservice.org/php/OpenLSLUS_Geocode.php",
+ scope: this,
+ failure: this.requestFailure,
+ success: this.requestSuccess,
+ headers: {"Content-Type": "application/x-www-form-urlencoded"},
+ data: "FreeFormAdress=" + encodeURIComponent(queryString) + "&MaxResponse=1"
+ });
+ }
+ function requestSuccess(response) {
+ var format = new OpenLayers.Format.XLS();
+ var output = format.read(response.responseXML);
+ if (output.responseLists[0]) {
+ var geometry = output.responseLists[0].features[0].geometry;
+ var foundPosition = new OpenLayers.LonLat(geometry.x, geometry.y).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+ );
+ map.setCenter(foundPosition, 16);
+ } else {
+ alert("Sorry, no address found");
+ }
+ }
+ function requestFailure(response) {
+ alert("An error occurred while communicating with the OpenLS service. Please try again.");
+ }
+
+ </script>
+</head>
+<body onload="init()">
+<h1 id="title">OpenLS Geocoding Example</h1>
+
+<div id="tags">
+ OpenLS, XLS, Geocoding
+</div>
+
+<p id="shortdesc">
+ Show how to use an OpenLS service.
+</p>
+
+<form name="input" action="javascript: submitform();" method="post">
+ <label for="query">Search for address:</label> <input type="text" id="query" size=50 name="query"
+ value="Rue des Berges 37 Payerne"/>
+ <input type="submit" value="Submit"/>
+</form>
+
+<br>
+
+<div id="map" class="smallmap"></div>
+
+
+<div id="docs">
+ <p>
+ Geocoding example using the http://www.openrouteservice.org/ OpenLS service. Recenter to the first item of the results.
+ </p>
+</div>
+</body>
+</html>
diff --git a/misc/openlayers/examples/ordering.html b/misc/openlayers/examples/ordering.html
new file mode 100644
index 0000000..cb15d8e
--- /dev/null
+++ b/misc/openlayers/examples/ordering.html
@@ -0,0 +1,221 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Z-Ordering and Y-Ordering of Vector Features</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .smallmap {
+ width: 300px;
+ }
+
+ .docs {
+ padding: 0px 5px;
+ }
+
+ td {
+ vertical-align: top;
+ }
+
+ </style>
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+ var GOLD_Z_INDEX = 15;
+ var FIRST_RED_Z_INDEX = 10;
+ var SECOND_RED_Z_INDEX = 11;
+
+ var RADIUS_FROM_CENTER = 40;
+ var POINT_DISTANCE = 10;
+
+ function initYOrderMap() {
+ var map = new OpenLayers.Map("yorder");
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var layer = new OpenLayers.Layer.Vector(
+ "Y-Order",
+ {
+ styleMap: new OpenLayers.StyleMap({
+ externalGraphic: "../img/marker-gold.png",
+ pointRadius: 10,
+ graphicZIndex: GOLD_Z_INDEX
+ }),
+ isBaseLayer: true,
+ rendererOptions: {yOrdering: true},
+ renderers: renderer
+ }
+ );
+
+ map.addLayers([layer]);
+ map.zoomToMaxExtent();
+
+ // Add features to the layers to show off z-index/y-ordering.
+ // We do this after adding the layer so we can work in pixels.
+ var center = map.getViewPortPxFromLonLat(map.getCenter());
+
+ var top = new OpenLayers.Pixel(center.x, center.y - RADIUS_FROM_CENTER);
+ var bottom = new OpenLayers.Pixel(center.x, center.y + RADIUS_FROM_CENTER);
+ var left = new OpenLayers.Pixel(center.x - RADIUS_FROM_CENTER, center.y - POINT_DISTANCE / 2);
+ var right = new OpenLayers.Pixel(center.x + RADIUS_FROM_CENTER, center.y - POINT_DISTANCE / 2);
+
+ // Add the ordering features. These are the gold ones that all have the same z-index
+ // and succomb to y-ordering.
+ var orderingFeatures = [];
+ // Note: We use > here on purpose (instead of >= ), as well as subtracting the
+ // the POINT_DISTANCE in the beginning of the loop (as opposed to the end).
+ // This is purely for symmetry. Also note that the gold features are drawn
+ // from bottom to top so as to quickly signal whether or not y-ordering is working.
+ while (bottom.y > top.y) {
+ bottom.y -= POINT_DISTANCE;
+ var lonLat = map.getLonLatFromPixel(bottom);
+ orderingFeatures.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat)
+ )
+ );
+ }
+
+ layer.addFeatures(orderingFeatures);
+
+ // Add the z-index features. Technically, these features succomb to y-ordering
+ // as well; however, since they have different z-indexes, the z-indexes take
+ // precedence.
+ var indexFeatures = [];
+ var useFirst = true;
+ while (left.x <= right.x) {
+ var lonLat = map.getLonLatFromPixel(left);
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat)
+ );
+
+ // This is where the magic happens. We override the style on the layer
+ // to give our own style with alternativing z-indexes.
+ point.style = {
+ graphicZIndex: useFirst ? FIRST_RED_Z_INDEX : SECOND_RED_Z_INDEX,
+ externalGraphic: "../img/marker.png",
+ pointRadius: 10
+ };
+
+ indexFeatures.push(
+ point
+ );
+
+ left.x += POINT_DISTANCE;
+ useFirst = !useFirst;
+ }
+
+ layer.addFeatures(indexFeatures);
+ }
+
+ function initDrawingOrderMap() {
+ var map = new OpenLayers.Map("drawingorder");
+
+ var layer = new OpenLayers.Layer.Vector(
+ "Drawing Order",
+ {
+ // The zIndex is taken from the zIndex attribute of the features
+ styleMap: new OpenLayers.StyleMap({
+ graphicZIndex: "${zIndex}",
+ externalGraphic: "../img/marker-green.png",
+ pointRadius: 10
+ }),
+ isBaseLayer: true,
+ // enable the indexer by setting zIndexing to true
+ rendererOptions: {zIndexing: true}
+ }
+ );
+
+ map.addLayers([layer]);
+ map.zoomToMaxExtent();
+
+ // Add features to the layers to show off z-index/y-ordering.
+ // We do this after adding the layer so we can work in pixels.
+ var center = map.getViewPortPxFromLonLat(map.getCenter());
+
+ var top = new OpenLayers.Pixel(center.x, center.y - RADIUS_FROM_CENTER);
+ var bottom = new OpenLayers.Pixel(center.x, center.y + RADIUS_FROM_CENTER);
+ var left = new OpenLayers.Pixel(center.x - RADIUS_FROM_CENTER, center.y);
+ var right = new OpenLayers.Pixel(center.x + RADIUS_FROM_CENTER, center.y);
+
+ // Add the ordering features. These are the gold ones that all have the same z-index
+ // and succomb to y-ordering.
+ var orderingFeatures = [];
+ while (bottom.y > top.y && left.x < right.x) {
+ var bottomLonLat = map.getLonLatFromPixel(bottom);
+ var leftLonLat = map.getLonLatFromPixel(left);
+ orderingFeatures.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(leftLonLat.lon, bottomLonLat.lat),
+ // Set the zIndex attribute of all features to 0.
+ // This attribute will be assigned to the graphicZIndex symbolizer property by the layer's styleMap
+ {zIndex: 0}
+ )
+ );
+ bottom.y -= POINT_DISTANCE / 2; // Divide by 2 for better visual.
+ left.x += POINT_DISTANCE / 2;
+ }
+ // only the first feature gets a zIndex attribute of 1
+ orderingFeatures[0].attributes.zIndex = 1;
+
+ layer.addFeatures(orderingFeatures);
+ }
+
+ function init(){
+ initYOrderMap();
+ initDrawingOrderMap();
+ };
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Z-Index/Y-Order Example</h1>
+
+ <div id="tags">
+ stack, stacking, zindex, ordering, light
+ </div>
+
+ <p id="shortdesc">
+ This example shows the use of z-indexing and y-ordering of external graphics. Zoom in and out to see this behavior.
+ </p>
+
+ <h3>Z-Index (with Y-Ordering enabled)</h3>
+ <table>
+ <tr>
+ <td>
+ <div id="yorder" class="smallmap"></div>
+ </td>
+ <td>
+ <div class="docs">
+ In this map, the gold features all have the same z-index, and the red features have alternating z-indeces. The gold features' z-index is greater than the red features' z-indeces, which is why gold features look to be drawn on top of the red features. Since each gold feature has the same z-index, gold features succomb to y-ordering: this is where features that seem closest to the viewer (lower lattitude) show up above those that seem farther away (higher lattitude).
+ <br><br>
+ You can enable y-ordering by passing the parameter <i>yOrdering: true</i> in the vector layer's options hash. For all configurations (with yOrdering or zIndexing set to true), if features have the same z-index -- and if y-ordering is enabled: the same latitude -- those features will succomb to drawing order, where the last feature to be drawn will appear above the rest.
+ </div>
+ </td>
+ </tr>
+ </table>
+ <br>
+ <h3>Z-Index and Drawing Order (Z-Indexes set, and Y-Ordering disabled)</h3>
+ <table>
+ <tr>
+ <td>
+ <div id="drawingorder" class="smallmap"></div>
+ </td>
+ <td>
+ <div class="docs">
+ In this map, <i>zIndexing</i> is set to true. All features are given the same z-index (0), except for the first feature which has a z-index of 1. The layer's <i>yOrdering</i> parameter is set to the default (false). This configuration makes features succomb to z-index and drawing order (for the features with the same z-index), instead of y-order.
+ <br><br>
+ The features in this map were drawn from left to right and bottom to top, diagonally, to show that y-ordering is not enabled. Only the lower-left corner feature is drawn on top of the others, because it has a higher z-index (1 instead of 0).
+ </div>
+ </td>
+ </tr>
+ </table>
+
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/osm-google.html b/misc/openlayers/examples/osm-google.html
new file mode 100644
index 0000000..e1ee6d8
--- /dev/null
+++ b/misc/openlayers/examples/osm-google.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers OSM and Google Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="http://maps.google.com/maps/api/js?v=3&amp;sensor=false"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="osm-google.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OSM and Google Together</h1>
+ <p id="shortdesc">
+ Demonstrate use of an OSM layer and a Google layer as base layers.
+ </p>
+ <div id="tags">
+ openstreetmap google light
+ </div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ The Google(v3) layer and the OSM are both in the same projection
+ - spherical mercator - and can be used on a map together.
+ See the <a href="osm-google.js" target="_blank">
+ osm-google.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/osm-google.js b/misc/openlayers/examples/osm-google.js
new file mode 100644
index 0000000..aaa8233
--- /dev/null
+++ b/misc/openlayers/examples/osm-google.js
@@ -0,0 +1,23 @@
+var map;
+
+function init() {
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: new OpenLayers.Projection("EPSG:900913")
+ });
+
+ var osm = new OpenLayers.Layer.OSM();
+ var gmap = new OpenLayers.Layer.Google("Google Streets");
+
+ map.addLayers([osm, gmap]);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ map.setCenter(
+ new OpenLayers.LonLat(10.2, 48.9).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+ ),
+ 5
+ );
+}
diff --git a/misc/openlayers/examples/osm-grayscale.html b/misc/openlayers/examples/osm-grayscale.html
new file mode 100644
index 0000000..0ff3729
--- /dev/null
+++ b/misc/openlayers/examples/osm-grayscale.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Grayscale OSM Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function init() {
+ if (!OpenLayers.CANVAS_SUPPORTED) {
+ var unsupported = OpenLayers.Util.getElement('unsupported');
+ unsupported.innerHTML = 'Your browser does not support canvas, nothing to see here !';
+ }
+
+ layer = new OpenLayers.Layer.OSM('Simple OSM Map', null, {
+ eventListeners: {
+ tileloaded: function(evt) {
+ var ctx = evt.tile.getCanvasContext();
+ if (ctx) {
+ var imgd = ctx.getImageData(0, 0, evt.tile.size.w, evt.tile.size.h);
+ var pix = imgd.data;
+ for (var i = 0, n = pix.length; i < n; i += 4) {
+ pix[i] = pix[i + 1] = pix[i + 2] = (3 * pix[i] + 4 * pix[i + 1] + pix[i + 2]) / 8;
+ }
+ ctx.putImageData(imgd, 0, 0);
+ evt.tile.imgDiv.removeAttribute("crossorigin");
+ evt.tile.imgDiv.src = ctx.canvas.toDataURL();
+ }
+ }
+ }
+ });
+
+ // If you get a security error because the tile are not
+ // from the same domain as this page, a simple Apache
+ // proxy can be created to workaround this issue:
+ //
+ // <Proxy *>
+ // Order deny,allow
+ // Allow from localhost
+ // </Proxy>
+ // ProxyPass /osm http://tile.openstreetmap.org/
+ //
+ // Then, in the layer definition above, replace null with '/osm/${z}/${x}/${y}.png'
+
+ map = new OpenLayers.Map('map', {
+ layers: [layer],
+ zoom: 3,
+ center: [-1081125, 6212801]
+ });
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Grayscale OSM Example</h1>
+
+ <div id="tags">
+ openstreetmap canvas grayscale light
+ </div>
+
+ <div id="shortdesc">Show an OSM Map in grayscale</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows an OSM layer where the tiles were
+ converted to grayscale
+ with <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html">canvas</a>.</p>
+ <p style="color:red;" id="unsupported"></p>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/osm-marker-popup.html b/misc/openlayers/examples/osm-marker-popup.html
new file mode 100644
index 0000000..8744ec8
--- /dev/null
+++ b/misc/openlayers/examples/osm-marker-popup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers OSM and Google Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="osm-marker-popup.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OSM with Marker and Popup</h1>
+ <p id="shortdesc">
+ Demonstrate use of an OSM layer with a marker and a popup.
+ </p>
+ <div id="tags">
+ openstreetmap osm marker popup
+ </div>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ A common use case for OpenLayers is to display a marker at a
+ location on the map, and add some information in a popup. It
+ is also easy to add a tooltip with a short description.
+ See the <a href="osm-marker-popup.js" target="_blank">
+ osm-marker-popup.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/osm-marker-popup.js b/misc/openlayers/examples/osm-marker-popup.js
new file mode 100644
index 0000000..e8f39b5
--- /dev/null
+++ b/misc/openlayers/examples/osm-marker-popup.js
@@ -0,0 +1,39 @@
+var map;
+function init() {
+
+ // The overlay layer for our marker, with a simple diamond as symbol
+ var overlay = new OpenLayers.Layer.Vector('Overlay', {
+ styleMap: new OpenLayers.StyleMap({
+ externalGraphic: '../img/marker.png',
+ graphicWidth: 20, graphicHeight: 24, graphicYOffset: -24,
+ title: '${tooltip}'
+ })
+ });
+
+ // The location of our marker and popup. We usually think in geographic
+ // coordinates ('EPSG:4326'), but the map is projected ('EPSG:3857').
+ var myLocation = new OpenLayers.Geometry.Point(10.2, 48.9)
+ .transform('EPSG:4326', 'EPSG:3857');
+
+ // We add the marker with a tooltip text to the overlay
+ overlay.addFeatures([
+ new OpenLayers.Feature.Vector(myLocation, {tooltip: 'OpenLayers'})
+ ]);
+
+ // A popup with some information about our location
+ var popup = new OpenLayers.Popup.FramedCloud("Popup",
+ myLocation.getBounds().getCenterLonLat(), null,
+ '<a target="_blank" href="http://openlayers.org/">We</a> ' +
+ 'could be here.<br>Or elsewhere.', null,
+ true // <-- true if we want a close (X) button, false otherwise
+ );
+
+ // Finally we create the map
+ map = new OpenLayers.Map({
+ div: "map", projection: "EPSG:3857",
+ layers: [new OpenLayers.Layer.OSM(), overlay],
+ center: myLocation.getBounds().getCenterLonLat(), zoom: 15
+ });
+ // and add the popup to it.
+ map.addPopup(popup);
+}
diff --git a/misc/openlayers/examples/osm.html b/misc/openlayers/examples/osm.html
new file mode 100644
index 0000000..ecd7c01
--- /dev/null
+++ b/misc/openlayers/examples/osm.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Basic OSM Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map');
+ layer = new OpenLayers.Layer.OSM( "Simple OSM Map");
+ map.addLayer(layer);
+ map.setCenter(
+ new OpenLayers.LonLat(-71.147, 42.472).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+ ), 12
+ );
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Basic OSM Example</h1>
+
+ <div id="tags">
+ openstreetmap basic light
+ </div>
+
+ <div id="shortdesc">Show a Simple OSM Map</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows a very simple OSM layout with minimal controls.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/osm/sutton_coldfield.osm b/misc/openlayers/examples/osm/sutton_coldfield.osm
new file mode 100644
index 0000000..db77309
--- /dev/null
+++ b/misc/openlayers/examples/osm/sutton_coldfield.osm
@@ -0,0 +1,662 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<osm version="0.5" generator="OpenStreetMap server">
+ <node id="200545" lat="52.5503033" lon="-1.8166417" user="blackadder" visible="true" timestamp="2006-03-22T16:33:41+00:00"/>
+ <node id="200546" lat="52.5501965" lon="-1.8168261" user="blackadder" visible="true" timestamp="2006-03-22T16:33:43+00:00"/>
+ <node id="200547" lat="52.5501645" lon="-1.8169929" user="blackadder" visible="true" timestamp="2006-03-22T16:33:45+00:00"/>
+ <node id="200548" lat="52.5501805" lon="-1.8172475" user="blackadder" visible="true" timestamp="2006-03-22T16:33:47+00:00"/>
+ <node id="200549" lat="52.5502392" lon="-1.8175372" user="blackadder" visible="true" timestamp="2006-03-22T16:33:50+00:00"/>
+ <node id="200627" lat="52.5499115" lon="-1.8161012" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200628" lat="52.5497408" lon="-1.81586" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200752" lat="52.5505598" lon="-1.8140051" user="blackadder" visible="true" timestamp="2006-03-22T16:36:20+00:00"/>
+ <node id="200758" lat="52.5501087" lon="-1.8142773" user="blackadder" visible="true" timestamp="2006-03-22T16:36:32+00:00"/>
+ <node id="645729" lat="52.5504008" lon="-1.8169154" user="blackadder" visible="true" timestamp="2006-07-24T23:21:42+01:00">
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="ref" v="B72 1162"/>
+ <tag k="amenity" v="post_box"/>
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="653121" lat="52.5506073" lon="-1.8137881" user="blackadder" visible="true" timestamp="2006-03-25T18:08:29+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783468" lat="52.5506446" lon="-1.8141177" user="blackadder" visible="true" timestamp="2007-05-30T14:22:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783470" lat="52.5501275" lon="-1.8151451" user="blackadder" visible="true" timestamp="2007-05-30T14:22:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783471" lat="52.5505521" lon="-1.8157703" user="blackadder" visible="true" timestamp="2007-12-18T15:33:59+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783472" lat="52.5501836" lon="-1.8164007" user="blackadder" visible="true" timestamp="2007-12-18T15:33:59+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783473" lat="52.5506035" lon="-1.8170311" user="blackadder" visible="true" timestamp="2007-05-30T14:21:32+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783474" lat="52.5509559" lon="-1.8164092" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783571" lat="52.5506331" lon="-1.813672" user="blackadder" visible="true" timestamp="2007-05-30T14:25:11+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175922968" lat="52.5508635" lon="-1.8167837" user="blackadder" visible="true" timestamp="2007-12-18T16:12:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175922970" lat="52.5507591" lon="-1.8169628" user="blackadder" visible="true" timestamp="2007-12-17T23:10:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923086" lat="52.5506787" lon="-1.8175799" user="blackadder" visible="true" timestamp="2007-12-17T23:10:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923089" lat="52.5505886" lon="-1.8177227" user="blackadder" visible="true" timestamp="2007-12-18T16:12:51+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128255" lat="52.5504144" lon="-1.8171548" user="blackadder" visible="true" timestamp="2007-12-18T16:12:52+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128257" lat="52.5506497" lon="-1.8175392" user="blackadder" visible="true" timestamp="2007-12-18T16:12:52+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128259" lat="52.550557" lon="-1.8176871" user="blackadder" visible="true" timestamp="2007-12-18T16:12:52+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128262" lat="52.5503122" lon="-1.8173195" user="blackadder" visible="true" timestamp="2007-12-18T16:12:52+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128265" lat="52.5499209" lon="-1.8164147" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128266" lat="52.550178" lon="-1.8175708" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128268" lat="52.550066" lon="-1.8176374" user="blackadder" visible="true" timestamp="2007-12-19T17:06:34+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128270" lat="52.5498069" lon="-1.8164854" user="blackadder" visible="true" timestamp="2007-12-19T17:06:34+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128273" lat="52.5498629" lon="-1.8162923" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128275" lat="52.5497909" lon="-1.8164134" user="blackadder" visible="true" timestamp="2007-12-19T17:06:34+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128278" lat="52.5502519" lon="-1.8173348" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128280" lat="52.5504396" lon="-1.817036" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128281" lat="52.5503051" lon="-1.816799" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128283" lat="52.5502337" lon="-1.8169643" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128286" lat="52.5503168" lon="-1.8170397" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ <tag k="amenity" v="parking"/>
+ </node>
+ <node id="177177218" lat="52.5501201" lon="-1.8175441" user="blackadder" visible="true" timestamp="2007-12-18T16:53:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ <tag k="ref" v="28"/>
+ <tag k="building" v="residential"/>
+ </node>
+ <node id="177177220" lat="52.5498859" lon="-1.8164918" user="blackadder" visible="true" timestamp="2007-12-18T16:53:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ <tag k="ref" v="2"/>
+ <tag k="building" v="residential"/>
+ </node>
+ <node id="177177222" lat="52.5498106" lon="-1.8162993" user="blackadder" visible="true" timestamp="2007-12-18T16:53:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ <tag k="ref" v="160"/>
+ <tag k="building" v="residential"/>
+ </node>
+ <node id="177230961" lat="52.550392" lon="-1.8172751" user="blackadder" visible="true" timestamp="2007-12-18T16:53:44+00:00">
+ <tag k="created_by" v="JOSM"/>
+ <tag k="ref" v="158"/>
+ <tag k="building" v="residential"/>
+ </node>
+ <node id="177230963" lat="52.5501893" lon="-1.8162725" user="blackadder" visible="true" timestamp="2007-12-18T16:53:44+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230965" lat="52.5502522" lon="-1.8161638" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230968" lat="52.5498311" lon="-1.8155709" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231022" lat="52.5497724" lon="-1.8156875" user="blackadder" visible="true" timestamp="2007-12-18T16:53:47+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231024" lat="52.5501922" lon="-1.8161729" user="blackadder" visible="true" timestamp="2007-12-18T16:53:47+00:00">
+ <tag k="created_by" v="JOSM"/>
+ <tag k="ref" v="145"/>
+ <tag k="building" v="residential"/>
+ </node>
+ <node id="200551" lat="52.5520062" lon="-1.8172738" user="blackadder" visible="true" timestamp="2006-03-22T16:33:54+00:00"/>
+ <node id="200552" lat="52.5520649" lon="-1.8171246" user="blackadder" visible="true" timestamp="2006-03-22T16:33:55+00:00"/>
+ <node id="200553" lat="52.5520008" lon="-1.8172124" user="blackadder" visible="true" timestamp="2006-03-22T16:33:59+00:00"/>
+ <node id="200554" lat="52.5517927" lon="-1.8169666" user="blackadder" visible="true" timestamp="2006-03-22T16:34:01+00:00"/>
+ <node id="200555" lat="52.5517499" lon="-1.8168788" user="blackadder" visible="true" timestamp="2006-03-22T16:34:03+00:00"/>
+ <node id="200556" lat="52.5517248" lon="-1.8167286" user="blackadder" visible="true" timestamp="2007-11-22T17:33:33+00:00"/>
+ <node id="200557" lat="52.5518792" lon="-1.8163956" user="blackadder" visible="true" timestamp="2007-11-22T17:33:33+00:00"/>
+ <node id="200558" lat="52.5519665" lon="-1.8163705" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200562" lat="52.5519848" lon="-1.815281" user="blackadder" visible="true" timestamp="2006-03-22T16:34:21+00:00"/>
+ <node id="200573" lat="52.5520736" lon="-1.8145054" user="blackadder" visible="true" timestamp="2006-03-22T16:34:49+00:00"/>
+ <node id="200751" lat="52.5511951" lon="-1.8142246" user="blackadder" visible="true" timestamp="2006-03-22T16:36:18+00:00"/>
+ <node id="29783476" lat="52.5513103" lon="-1.8169385" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783477" lat="52.5517893" lon="-1.8159626" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783478" lat="52.5518461" lon="-1.8145067" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783479" lat="52.5511883" lon="-1.8143197" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="200511" lat="52.5558034" lon="-1.8267378" user="blackadder" visible="true" timestamp="2006-03-22T16:32:25+00:00"/>
+ <node id="200512" lat="52.5550357" lon="-1.8249151" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200513" lat="52.5553343" lon="-1.8257585" user="blackadder" visible="true" timestamp="2006-03-22T16:32:28+00:00"/>
+ <node id="200514" lat="52.5546308" lon="-1.8238934" user="blackadder" visible="true" timestamp="2006-03-22T16:32:30+00:00"/>
+ <node id="200515" lat="52.5544693" lon="-1.823135" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200517" lat="52.5545308" lon="-1.8235208" user="blackadder" visible="true" timestamp="2006-03-22T16:32:37+00:00"/>
+ <node id="200526" lat="52.5543989" lon="-1.8225501" user="blackadder" visible="true" timestamp="2006-03-22T16:33:02+00:00"/>
+ <node id="200527" lat="52.5543241" lon="-1.8221902" user="blackadder" visible="true" timestamp="2006-03-22T16:33:05+00:00"/>
+ <node id="200528" lat="52.5542654" lon="-1.8219444" user="blackadder" visible="true" timestamp="2006-03-22T16:33:07+00:00"/>
+ <node id="200530" lat="52.5541587" lon="-1.8216547" user="blackadder" visible="true" timestamp="2006-03-22T16:33:11+00:00"/>
+ <node id="200532" lat="52.5538918" lon="-1.821163" user="blackadder" visible="true" timestamp="2006-03-22T16:33:15+00:00"/>
+ <node id="200533" lat="52.5536782" lon="-1.8208031" user="blackadder" visible="true" timestamp="2006-03-22T16:33:17+00:00"/>
+ <node id="200534" lat="52.5534108" lon="-1.8204187" user="blackadder" visible="true" timestamp="2006-03-22T16:33:19+00:00"/>
+ <node id="200535" lat="52.5530804" lon="-1.8200481" user="blackadder" visible="true" timestamp="2006-03-22T16:33:21+00:00"/>
+ <node id="200536" lat="52.5527228" lon="-1.8197232" user="blackadder" visible="true" timestamp="2006-03-22T16:33:23+00:00"/>
+ <node id="200537" lat="52.5525413" lon="-1.8195652" user="blackadder" visible="true" timestamp="2006-03-22T16:33:25+00:00"/>
+ <node id="200539" lat="52.5522844" lon="-1.8193896" user="blackadder" visible="true" timestamp="2006-03-22T16:33:29+00:00"/>
+ <node id="200540" lat="52.5519374" lon="-1.8190999" user="blackadder" visible="true" timestamp="2006-03-22T16:33:31+00:00"/>
+ <node id="200541" lat="52.5521848" lon="-1.8193018" user="blackadder" visible="true" timestamp="2007-12-17T23:11:03+00:00"/>
+ <node id="200542" lat="52.5516186" lon="-1.8186947" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200543" lat="52.5517933" lon="-1.8189419" user="blackadder" visible="true" timestamp="2006-03-22T16:33:37+00:00"/>
+ <node id="200544" lat="52.5513983" lon="-1.8183449" user="blackadder" visible="true" timestamp="2006-03-22T16:33:39+00:00"/>
+ <node id="200550" lat="52.5519976" lon="-1.8179888" user="blackadder" visible="true" timestamp="2007-11-22T17:33:33+00:00"/>
+ <node id="200559" lat="52.5523069" lon="-1.816393" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200560" lat="52.5523905" lon="-1.8163169" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200561" lat="52.552524" lon="-1.8159306" user="blackadder" visible="true" timestamp="2006-03-22T21:55:12+00:00"/>
+ <node id="200563" lat="52.5526121" lon="-1.8152809" user="blackadder" visible="true" timestamp="2006-03-22T16:34:24+00:00"/>
+ <node id="200564" lat="52.5526358" lon="-1.8149766" user="blackadder" visible="true" timestamp="2007-11-22T17:33:34+00:00"/>
+ <node id="200565" lat="52.5526654" lon="-1.8146664" user="blackadder" visible="true" timestamp="2006-03-22T16:34:29+00:00"/>
+ <node id="200566" lat="52.5525937" lon="-1.8156438" user="blackadder" visible="true" timestamp="2006-03-22T16:34:31+00:00"/>
+ <node id="200571" lat="52.5535575" lon="-1.8148566" user="blackadder" visible="true" timestamp="2007-11-15T12:54:40+00:00"/>
+ <node id="200572" lat="52.5523885" lon="-1.8145932" user="blackadder" visible="true" timestamp="2006-03-22T16:34:46+00:00"/>
+ <node id="200629" lat="52.5495701" lon="-1.8156789" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200630" lat="52.5493694" lon="-1.815524" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200631" lat="52.5490781" lon="-1.8153587" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200632" lat="52.5488293" lon="-1.815287" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200633" lat="52.5485269" lon="-1.8153192" user="blackadder" visible="true" timestamp="2007-11-22T17:33:32+00:00"/>
+ <node id="200650" lat="52.5482109" lon="-1.815399" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200651" lat="52.5478984" lon="-1.8153867" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200652" lat="52.5474767" lon="-1.8152603" user="blackadder" visible="true" timestamp="2007-11-22T17:33:31+00:00"/>
+ <node id="200653" lat="52.5468312" lon="-1.8150679" user="blackadder" visible="true" timestamp="2007-11-22T17:33:29+00:00"/>
+ <node id="200753" lat="52.5496876" lon="-1.8136891" user="blackadder" visible="true" timestamp="2006-03-22T21:55:13+00:00"/>
+ <node id="200754" lat="52.549009" lon="-1.8133906" user="blackadder" visible="true" timestamp="2006-03-22T16:36:24+00:00"/>
+ <node id="200755" lat="52.5478879" lon="-1.8128287" user="blackadder" visible="true" timestamp="2006-03-22T16:36:26+00:00"/>
+ <node id="200756" lat="52.548993" lon="-1.8134871" user="blackadder" visible="true" timestamp="2006-03-22T16:36:28+00:00"/>
+ <node id="200757" lat="52.5490304" lon="-1.8135925" user="blackadder" visible="true" timestamp="2006-03-22T16:36:31+00:00"/>
+ <node id="200759" lat="52.5464722" lon="-1.8119684" user="blackadder" visible="true" timestamp="2006-03-22T16:36:34+00:00"/>
+ <node id="200771" lat="52.5466312" lon="-1.8121126" user="blackadder" visible="true" timestamp="2006-03-22T16:37:02+00:00"/>
+ <node id="653117" lat="52.5423754" lon="-1.8078749" user="blackadder" visible="true" timestamp="2007-11-26T18:40:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="653118" lat="52.5465842" lon="-1.81179" user="blackadder" visible="true" timestamp="2006-03-25T18:08:29+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="653119" lat="52.5444001" lon="-1.8100834" user="blackadder" visible="true" timestamp="2006-03-25T18:08:29+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="653120" lat="52.5490713" lon="-1.8131664" user="blackadder" visible="true" timestamp="2006-03-25T18:08:29+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="653122" lat="52.5526798" lon="-1.814402" user="blackadder" visible="true" timestamp="2006-03-25T18:08:29+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="653123" lat="52.5536564" lon="-1.814631" user="blackadder" visible="true" timestamp="2007-11-15T12:54:42+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783564" lat="52.5538289" lon="-1.8145536" user="blackadder" visible="true" timestamp="2007-11-15T12:53:07+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783565" lat="52.5539485" lon="-1.8128538" user="blackadder" visible="true" timestamp="2007-11-15T12:53:07+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783566" lat="52.5509299" lon="-1.8119156" user="blackadder" visible="true" timestamp="2007-05-30T14:25:10+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783567" lat="52.5470281" lon="-1.8097771" user="blackadder" visible="true" timestamp="2007-05-30T14:25:11+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783568" lat="52.5465979" lon="-1.811457" user="blackadder" visible="true" timestamp="2007-05-30T14:25:11+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783569" lat="52.5466086" lon="-1.8115118" user="blackadder" visible="true" timestamp="2007-05-30T14:25:11+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783570" lat="52.5491153" lon="-1.8130205" user="blackadder" visible="true" timestamp="2007-05-30T14:25:11+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="29783572" lat="52.5527014" lon="-1.8142812" user="blackadder" visible="true" timestamp="2007-05-30T14:25:11+01:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="135352210" lat="52.5549423" lon="-1.814954" user="blackadder" visible="true" timestamp="2007-11-26T18:37:58+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175922965" lat="52.551783" lon="-1.8181591" user="blackadder" visible="true" timestamp="2007-12-18T16:12:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175922972" lat="52.5516751" lon="-1.8183582" user="blackadder" visible="true" timestamp="2007-12-17T23:10:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923064" lat="52.5542375" lon="-1.8222046" user="blackadder" visible="true" timestamp="2007-12-18T15:34:00+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923066" lat="52.5540618" lon="-1.8217823" user="blackadder" visible="true" timestamp="2007-12-17T23:10:49+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923068" lat="52.5537913" lon="-1.8213338" user="blackadder" visible="true" timestamp="2007-12-17T23:10:49+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923070" lat="52.5529993" lon="-1.8203219" user="blackadder" visible="true" timestamp="2007-12-17T23:10:49+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923072" lat="52.5521418" lon="-1.8195539" user="blackadder" visible="true" timestamp="2007-12-17T23:10:49+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923077" lat="52.5517862" lon="-1.8192228" user="blackadder" visible="true" timestamp="2007-12-18T16:12:51+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923081" lat="52.55152" lon="-1.8188757" user="blackadder" visible="true" timestamp="2007-12-17T23:10:49+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923083" lat="52.5510247" lon="-1.8181078" user="blackadder" visible="true" timestamp="2007-12-17T23:10:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923092" lat="52.5514479" lon="-1.8190434" user="blackadder" visible="true" timestamp="2007-12-18T16:12:51+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923093" lat="52.5517434" lon="-1.8194353" user="blackadder" visible="true" timestamp="2007-12-18T16:12:51+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923094" lat="52.5529379" lon="-1.8205018" user="blackadder" visible="true" timestamp="2007-12-18T16:12:51+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923095" lat="52.5537161" lon="-1.8214889" user="blackadder" visible="true" timestamp="2007-12-18T16:12:51+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923096" lat="52.5539755" lon="-1.8218808" user="blackadder" visible="true" timestamp="2007-12-18T16:12:50+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="175923099" lat="52.5541405" lon="-1.8222767" user="blackadder" visible="true" timestamp="2007-12-18T15:34:00+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177081428" lat="52.5552884" lon="-1.8256253" user="blackadder" visible="true" timestamp="2007-12-18T15:01:16+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177081440" lat="52.5551147" lon="-1.825166" user="blackadder" visible="true" timestamp="2007-12-18T15:01:17+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128288" lat="52.5496708" lon="-1.816009" user="blackadder" visible="true" timestamp="2007-12-18T15:33:55+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128291" lat="52.5494958" lon="-1.8157939" user="blackadder" visible="true" timestamp="2007-12-18T15:33:56+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128294" lat="52.5492862" lon="-1.8156326" user="blackadder" visible="true" timestamp="2007-12-18T15:33:56+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128297" lat="52.5490612" lon="-1.8155156" user="blackadder" visible="true" timestamp="2007-12-18T15:33:56+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128299" lat="52.5488284" lon="-1.8154808" user="blackadder" visible="true" timestamp="2007-12-18T15:33:56+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128301" lat="52.5488246" lon="-1.8156477" user="blackadder" visible="true" timestamp="2007-12-19T17:06:33+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128303" lat="52.5490573" lon="-1.8156698" user="blackadder" visible="true" timestamp="2007-12-19T17:06:33+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128306" lat="52.5492802" lon="-1.8157959" user="blackadder" visible="true" timestamp="2007-12-19T17:06:33+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128308" lat="52.5494967" lon="-1.8159735" user="blackadder" visible="true" timestamp="2007-12-19T17:06:33+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177128310" lat="52.549613" lon="-1.8161509" user="blackadder" visible="true" timestamp="2007-12-19T17:06:33+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230972" lat="52.5496355" lon="-1.8153859" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230975" lat="52.5494398" lon="-1.8152371" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230979" lat="52.5492418" lon="-1.8151165" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230981" lat="52.5490437" lon="-1.8150441" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230983" lat="52.5488114" lon="-1.8149878" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230985" lat="52.5485375" lon="-1.815024" user="blackadder" visible="true" timestamp="2007-12-18T16:53:45+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230988" lat="52.5481977" lon="-1.8150843" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230989" lat="52.5480118" lon="-1.8150884" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177230996" lat="52.5480143" lon="-1.8152371" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231000" lat="52.5482099" lon="-1.8152331" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231003" lat="52.5485498" lon="-1.8151567" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231007" lat="52.5488212" lon="-1.8151246" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231010" lat="52.5490364" lon="-1.8151728" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231014" lat="52.5492393" lon="-1.8152452" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231016" lat="52.5494081" lon="-1.8153618" user="blackadder" visible="true" timestamp="2007-12-18T16:53:46+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231019" lat="52.5495915" lon="-1.8155106" user="blackadder" visible="true" timestamp="2007-12-18T16:53:47+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <node id="177231081" lat="52.5555213" lon="-1.8261466" user="blackadder" visible="true" timestamp="2007-12-18T16:53:49+00:00">
+ <tag k="created_by" v="JOSM"/>
+ </node>
+ <way id="35" visible="true" timestamp="2007-09-18T02:37:16+01:00" user="crschmidt">
+ <nd ref="200542"/>
+ <nd ref="200550"/>
+ <nd ref="200551"/>
+ <nd ref="200553"/>
+ <tag k="note" v="fire access route"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="highway" v="footway"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="surface" v="paved"/>
+ </way>
+ <way id="37" visible="true" timestamp="2007-12-18T16:53:51+00:00" user="blackadder">
+ <nd ref="200511"/>
+ <nd ref="177231081"/>
+ <nd ref="200513"/>
+ <nd ref="177081428"/>
+ <nd ref="177081440"/>
+ <nd ref="200512"/>
+ <nd ref="200514"/>
+ <nd ref="200517"/>
+ <nd ref="200515"/>
+ <nd ref="200526"/>
+ <nd ref="200527"/>
+ <nd ref="200528"/>
+ <nd ref="200530"/>
+ <nd ref="200532"/>
+ <nd ref="200533"/>
+ <nd ref="200534"/>
+ <nd ref="200535"/>
+ <nd ref="200536"/>
+ <nd ref="200537"/>
+ <nd ref="200539"/>
+ <nd ref="200541"/>
+ <nd ref="200540"/>
+ <nd ref="200543"/>
+ <nd ref="200542"/>
+ <nd ref="200544"/>
+ <nd ref="200545"/>
+ <nd ref="200627"/>
+ <nd ref="200628"/>
+ <nd ref="200629"/>
+ <nd ref="200630"/>
+ <nd ref="200631"/>
+ <nd ref="200632"/>
+ <nd ref="200633"/>
+ <nd ref="200650"/>
+ <nd ref="200651"/>
+ <nd ref="200652"/>
+ <nd ref="200653"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="postal_code" v="B72"/>
+ <tag k="name" v="Maney Hill Road"/>
+ <tag k="abutters" v="residential"/>
+ <tag k="highway" v="residential"/>
+ </way>
+ <way id="54" visible="true" timestamp="2006-03-29T09:51:45+01:00" user="blackadder">
+ <nd ref="200545"/>
+ <nd ref="200546"/>
+ <nd ref="200547"/>
+ <nd ref="200548"/>
+ <nd ref="200549"/>
+ <tag k="name" v="Mottrams Close"/>
+ <tag k="abutters" v="residential"/>
+ <tag k="postal_code" v="B72"/>
+ <tag k="highway" v="residential"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="created_by" v="JOSM"/>
+ </way>
+ <way id="55" visible="true" timestamp="2006-03-22T21:55:16+00:00" user="blackadder">
+ <nd ref="200758"/>
+ <nd ref="200757"/>
+ <nd ref="200756"/>
+ <nd ref="200754"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="highway" v="residential"/>
+ <tag k="postal_code" v="B72"/>
+ <tag k="name" v="East View Road"/>
+ <tag k="created_by" v="JOSM"/>
+ </way>
+ <way id="57" visible="true" timestamp="2007-09-26T14:35:13+01:00" user="blackadder">
+ <nd ref="200571"/>
+ <nd ref="200565"/>
+ <nd ref="200572"/>
+ <nd ref="200573"/>
+ <nd ref="200751"/>
+ <nd ref="200752"/>
+ <nd ref="200753"/>
+ <nd ref="200754"/>
+ <nd ref="200755"/>
+ <nd ref="200771"/>
+ <nd ref="200759"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="postal_code" v="B72"/>
+ <tag k="name" v="East View Road"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="highway" v="unclassified"/>
+ </way>
+ <way id="395" visible="true" timestamp="2007-12-19T17:50:08+00:00" user="blackadder">
+ <nd ref="200552"/>
+ <nd ref="200553"/>
+ <nd ref="200554"/>
+ <nd ref="200555"/>
+ <nd ref="200556"/>
+ <nd ref="200557"/>
+ <nd ref="200558"/>
+ <nd ref="200559"/>
+ <nd ref="200560"/>
+ <nd ref="200561"/>
+ <nd ref="200566"/>
+ <nd ref="200563"/>
+ <nd ref="200564"/>
+ <nd ref="200565"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="postal_code" v="B72"/>
+ <tag k="name" v="Shooters Hill"/>
+ <tag k="abutters" v="residential"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="highway" v="residential"/>
+ </way>
+ <way id="418" visible="true" timestamp="2006-03-25T11:39:32+00:00" user="blackadder">
+ <nd ref="200562"/>
+ <nd ref="200563"/>
+ <tag k="place_name" v="Sutton Coldfield"/>
+ <tag k="highway" v="residential"/>
+ <tag k="postal_code" v="B72"/>
+ <tag k="abutters" v="residential"/>
+ <tag k="name" v="Corncrake Close"/>
+ <tag k="created_by" v="JOSM"/>
+ </way>
+ <way id="4685537" visible="true" timestamp="2007-05-30T14:21:35+01:00" user="blackadder">
+ <nd ref="29783472"/>
+ <nd ref="29783473"/>
+ <nd ref="29783474"/>
+ <nd ref="29783476"/>
+ <nd ref="29783477"/>
+ <nd ref="29783478"/>
+ <nd ref="29783479"/>
+ <nd ref="29783468"/>
+ <nd ref="29783470"/>
+ <nd ref="29783471"/>
+ <nd ref="29783472"/>
+ <tag k="name" v="Maney Hill School"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="landuse" v="school"/>
+ <tag k="amenity" v="school"/>
+ </way>
+ <way id="4685542" visible="true" timestamp="2007-05-30T14:25:15+01:00" user="blackadder">
+ <nd ref="29783570"/>
+ <nd ref="29783571"/>
+ <nd ref="29783572"/>
+ <nd ref="29783564"/>
+ <nd ref="29783565"/>
+ <nd ref="29783566"/>
+ <nd ref="29783567"/>
+ <nd ref="29783568"/>
+ <nd ref="29783569"/>
+ <nd ref="29783570"/>
+ <tag k="landuse" v="school"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="amenity" v="school"/>
+ </way>
+ <way id="14166897" visible="true" timestamp="2007-11-26T18:40:45+00:00" user="blackadder">
+ <nd ref="135352210"/>
+ <nd ref="653123"/>
+ <nd ref="653122"/>
+ <nd ref="653121"/>
+ <nd ref="653120"/>
+ <nd ref="653118"/>
+ <nd ref="653119"/>
+ <nd ref="653117"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="name" v="Freight Line"/>
+ <tag k="railway" v="rail"/>
+ </way>
+ <way id="16966783" visible="true" timestamp="2007-12-17T23:11:00+00:00" user="blackadder">
+ <nd ref="175922965"/>
+ <nd ref="175922968"/>
+ <nd ref="175922970"/>
+ <nd ref="175922972"/>
+ <nd ref="175922965"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="building" v="residential"/>
+ </way>
+ <way id="16966792" visible="true" timestamp="2007-12-17T23:11:01+00:00" user="blackadder">
+ <nd ref="175923064"/>
+ <nd ref="175923066"/>
+ <nd ref="175923068"/>
+ <nd ref="175923070"/>
+ <nd ref="175923072"/>
+ <nd ref="175923077"/>
+ <nd ref="175923081"/>
+ <nd ref="175923083"/>
+ <nd ref="175923086"/>
+ <nd ref="175923089"/>
+ <nd ref="175923092"/>
+ <nd ref="175923093"/>
+ <nd ref="175923094"/>
+ <nd ref="175923095"/>
+ <nd ref="175923096"/>
+ <nd ref="175923099"/>
+ <nd ref="175923064"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="building" v="residential"/>
+ </way>
+ <way id="17060325" visible="true" timestamp="2007-12-18T15:37:01+00:00" user="blackadder">
+ <nd ref="177128255"/>
+ <nd ref="177128262"/>
+ <nd ref="177128259"/>
+ <nd ref="177128257"/>
+ <nd ref="177128255"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="building" v="residential"/>
+ </way>
+ <way id="17060326" visible="true" timestamp="2007-12-18T15:37:01+00:00" user="blackadder">
+ <nd ref="177128265"/>
+ <nd ref="177128270"/>
+ <nd ref="177128268"/>
+ <nd ref="177128266"/>
+ <nd ref="177128265"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="building" v="residential"/>
+ </way>
+ <way id="17060327" visible="true" timestamp="2007-12-18T15:37:01+00:00" user="blackadder">
+ <nd ref="177128275"/>
+ <nd ref="177128273"/>
+ <nd ref="177128288"/>
+ <nd ref="177128291"/>
+ <nd ref="177128294"/>
+ <nd ref="177128297"/>
+ <nd ref="177128299"/>
+ <nd ref="177128301"/>
+ <nd ref="177128303"/>
+ <nd ref="177128306"/>
+ <nd ref="177128308"/>
+ <nd ref="177128310"/>
+ <nd ref="177128275"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="building" v="residential"/>
+ </way>
+ <way id="17060329" visible="true" timestamp="2007-12-18T15:33:58+00:00" user="blackadder">
+ <nd ref="177128278"/>
+ <nd ref="177128280"/>
+ <nd ref="177128281"/>
+ <nd ref="177128283"/>
+ <nd ref="177128278"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="amenity" v="parking"/>
+ </way>
+ <way id="17067452" visible="true" timestamp="2007-12-18T16:53:49+00:00" user="blackadder">
+ <nd ref="177230963"/>
+ <nd ref="177230965"/>
+ <nd ref="177230968"/>
+ <nd ref="177230972"/>
+ <nd ref="177230975"/>
+ <nd ref="177230979"/>
+ <nd ref="177230981"/>
+ <nd ref="177230983"/>
+ <nd ref="177230985"/>
+ <nd ref="177230988"/>
+ <nd ref="177230989"/>
+ <nd ref="177230996"/>
+ <nd ref="177231000"/>
+ <nd ref="177231003"/>
+ <nd ref="177231007"/>
+ <nd ref="177231010"/>
+ <nd ref="177231014"/>
+ <nd ref="177231016"/>
+ <nd ref="177231019"/>
+ <nd ref="177231022"/>
+ <nd ref="177230963"/>
+ <tag k="created_by" v="JOSM"/>
+ <tag k="building" v="residential"/>
+ </way>
+</osm>
diff --git a/misc/openlayers/examples/overviewmap.html b/misc/openlayers/examples/overviewmap.html
new file mode 100644
index 0000000..5a8cc3f
--- /dev/null
+++ b/misc/openlayers/examples/overviewmap.html
@@ -0,0 +1,120 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Overview Map Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <style>
+ #map1 {
+ width: 500px;
+ height: 300px;
+ border: 1px solid gray;
+ }
+ #map2 {
+ width: 500px;
+ height: 300px;
+ border: 1px solid gray;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Overview Map</h1>
+
+ <div id="tags">
+ overview, mapOptions, basic
+ </div>
+ <p id="shortdesc">
+ Enable a small Overview Map that moves/interacts with your main map.
+ </p>
+ <div id="map1"></div>
+ <p>The above map has an overview map control that is created with
+ the default options. Much like a regular map, the map contained by
+ the overview map control defaults to a geographic projection.</p>
+ <div id="map2"></div>
+ <p>The second map has an overview map control that is created with
+ non-default options. In this case, the mapOptions property of the
+ control has been set to use non-default projection related properties,
+ and the layers property has been set to use a layer different from the main
+ map. In addition, any other properties of the overview map control can be
+ set in this way.</p>
+ <script defer="defer" type="text/javascript">
+
+ // set up some layers
+
+ var ol = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var jpl = new OpenLayers.Layer.WMS(
+ "NASA Global Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {layers: "landsat7"}
+ );
+
+ // A clone of the above layer that we will use as overview for map2.
+ // We need to clone jpl before the it gets added to a map, so the
+ // clone can have its own maxExtent and maxResolution instead of
+ // getting these settings initialized from map1.
+ var jplOverview = jpl.clone();
+
+ // A more detailled layer of Manhattan for map2
+ var ny = new OpenLayers.Layer.WMS(
+ "Manhattan",
+ "http://demo.opengeo.org/geoserver/wms",
+ {
+ layers: 'tiger-ny',
+ format: 'image/png'
+ }
+ );
+
+ // create the top map (with default overview map control)
+ var map1 = new OpenLayers.Map('map1');
+
+ map1.addLayers([ol, jpl]);
+ map1.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ // create an overview map control with the default options
+ var overview1 = new OpenLayers.Control.OverviewMap({
+ maximized: true,
+ maximizeTitle: 'Show the overview map',
+ minimizeTitle: 'Hide the overview map'
+ });
+ map1.addControl(overview1);
+
+ map1.setCenter(new OpenLayers.LonLat(0, 0), 2);
+
+ // create the bottom map (with advanced overview map control)
+ var mapOptions = {
+ maxExtent: new OpenLayers.Bounds(-8242894.927728, 4965204.031195,
+ -8227290.161511, 4994963.723637),
+ maxResolution: 116.24879860156216,
+ projection: "EPSG:900913"
+ };
+
+ var map2 = new OpenLayers.Map('map2', mapOptions);
+
+ map2.addLayers([ny]);
+
+ // create an overview map control with non-default options
+ var controlOptions = {
+ maximized: true,
+ mapOptions: OpenLayers.Util.extend(mapOptions, {
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34,
+ 20037508.34, 20037508.34)
+ }),
+ layers: [jplOverview]
+ };
+ var overview2 = new OpenLayers.Control.OverviewMap(controlOptions);
+ map2.addControl(overview2);
+
+ map2.setCenter(new OpenLayers.LonLat(-8233165.3575055, 4980298.21113769), 3);
+
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/pan-zoom-panels.html b/misc/openlayers/examples/pan-zoom-panels.html
new file mode 100644
index 0000000..0c48498
--- /dev/null
+++ b/misc/openlayers/examples/pan-zoom-panels.html
@@ -0,0 +1,97 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Pan and Zoom Panels</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+
+ <!--
+ -- Special stylesheet inclusion for ie6, which doesn't handle the alpha
+ -- channel of images correctly. The special ie6 stylesheet will only
+ -- be included if the browser running is ie6. For now, the only thing it
+ -- does is load alternative, non-alpha pngs for the zoom/pan panels.
+ -->
+
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ <![endif]-->
+
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script type="text/javascript" src="../lib/OpenLayers.js"></script>
+ <script>
+ var map;
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ function init(){
+ map = new OpenLayers.Map("map", {
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanPanel(),
+ new OpenLayers.Control.ZoomPanel()
+ ]
+ });
+
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+ map.addLayers([wms]);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ <style type="text/css">
+ .olControlPanPanel {
+ width: 100%;
+ height: 100%;
+ left: 0;
+ top: 0;
+ }
+ .olControlPanPanel .olControlPanNorthItemInactive {
+ left: 50%;
+ margin-left: -9px;
+ top: 0;
+ }
+ .olControlPanPanel .olControlPanSouthItemInactive {
+ left: 50%;
+ margin-left: -9px;
+ top: auto;
+ bottom: 0;
+ }
+ .olControlPanPanel .olControlPanWestItemInactive {
+ top: 50%;
+ margin-top: -9px;
+ left: 0;
+ }
+ .olControlPanPanel .olControlPanEastItemInactive {
+ top: 50%;
+ margin-top: -9px;
+ left: auto;
+ right: 0;
+ }
+ .olControlZoomPanel {
+ left: auto;
+ right: 23px;
+ top: 8px;
+ }
+ </style>
+</head>
+<body onload='init();'>
+ <h1 id="title">Pan and Zoom Panels</h1>
+ <div id="tags">
+ panning, zooming, panel, CSS, style
+ </div>
+ <p id="shortdesc">
+ Customizable pan and zoom panels
+ </p>
+ <div id="map" class="smallmap"></div>
+ <p id="docs">
+ The pan and zoom panels allow you to use CSS styling to change the
+ look and feel of the panels, including changing their position
+ and their icons without needing to change any code.
+ </p>
+</body>
+</html>
diff --git a/misc/openlayers/examples/panel.html b/misc/openlayers/examples/panel.html
new file mode 100644
index 0000000..be9785c
--- /dev/null
+++ b/misc/openlayers/examples/panel.html
@@ -0,0 +1,99 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Control Panel</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlPanel div {
+ display:block;
+ width: 24px;
+ height: 24px;
+ margin: 5px;
+ background-color:white;
+ }
+
+ .olControlPanel .olControlDrawFeatureItemActive {
+ width: 22px;
+ height: 22px;
+ background-image: url("../theme/default/img/draw_line_on.png");
+ }
+ .olControlPanel .olControlDrawFeatureItemInactive {
+ width: 22px;
+ height: 22px;
+ background-image: url("../theme/default/img/draw_line_off.png");
+ }
+ .olControlPanel .olControlZoomBoxItemInactive {
+ width: 22px;
+ height: 22px;
+ background-color: orange;
+ background-image: url("../img/drag-rectangle-off.png");
+ }
+ .olControlPanel .olControlZoomBoxItemActive {
+ width: 22px;
+ height: 22px;
+ background-color: blue;
+ background-image: url("../img/drag-rectangle-on.png");
+ }
+ .olControlPanel .olControlZoomToMaxExtentItemInactive {
+ width: 18px;
+ height: 18px;
+ background-image: url("../img/zoom-world-mini.png");
+ }
+
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map', { controls: [] } );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ vlayer = new OpenLayers.Layer.Vector( "Editable" );
+ map.addLayer(vlayer);
+
+
+ zb = new OpenLayers.Control.ZoomBox(
+ {title:"Zoom box: Selecting it you can zoom on an area by clicking and dragging."});
+ var panel = new OpenLayers.Control.Panel({defaultControl: zb});
+ panel.addControls([
+ zb,
+ new OpenLayers.Control.DrawFeature(vlayer, OpenLayers.Handler.Path,
+ {title:'Draw a feature'}),
+ new OpenLayers.Control.ZoomToMaxExtent({title:"Zoom to the max extent"})
+ ]);
+
+ nav = new OpenLayers.Control.NavigationHistory();
+ // parent control must be added to the map
+ map.addControl(nav);
+ panel.addControls([nav.next, nav.previous]);
+
+ map.addControl(panel);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Custom Control.Panel</h1>
+ <div id="tags">
+ panels, CSS, style, basic
+ </div>
+ <p id="shortdesc">
+ Create a custom control.panel, styled entirely with
+ CSS, and add your own controls to it.
+ </p>
+ <div id="panel"></div>
+ <div id="map" class="smallmap"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/point-grid.html b/misc/openlayers/examples/point-grid.html
new file mode 100644
index 0000000..8508fdb
--- /dev/null
+++ b/misc/openlayers/examples/point-grid.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Point Grid Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ left: 5px;
+ bottom: 5px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Point Grid Example</h1>
+
+ <div id="tags">
+ point grid
+ </div>
+
+ <div id="shortdesc">Use a PointGrid layer to display a grid of regularly spaced points</div>
+
+ <div id="map" class="smallmap"></div>
+
+ Grid rotation:
+ <select name="rotation" id="rotation">
+ <option value="-45">-45</option>
+ <option value="-30">-30</option>
+ <option value="-15">-15</option>
+ <option value="0">0</option>
+ <option value="15">15</option>
+ <option value="30">30</option>
+ <option value="45">45</option>
+ </select>
+
+ &nbsp;
+ Grid spacing:
+ <select name="dx" id="dx">
+ <option value="10">10</option>
+ <option value="15">15</option>
+ <option value="20">20</option>
+ <option value="25">25</option>
+ <option value="30">30</option>
+ </select> x
+ <select name="dy" id="dy">
+ <option value="10">10</option>
+ <option value="15">15</option>
+ <option value="20">20</option>
+ <option value="25">25</option>
+ <option value="30">30</option>
+ </select>
+
+ &nbsp;
+ Max points:
+ <select name="max" id="max">
+ <option value="150">150</option>
+ <option value="250">250</option>
+ <option value="350">350</option>
+ </select>
+
+ <div class="docs">
+ <p>
+ This example demonstrates a <code>OpenLayers.Layer.PointGrid</code>
+ layer to render a regularly spaced grid of point features.
+ </p><p>
+ See the <a href="point-grid.js" target="_blank">
+ point-grid.js source</a> to see how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="point-grid.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/point-grid.js b/misc/openlayers/examples/point-grid.js
new file mode 100644
index 0000000..e7b2e2e
--- /dev/null
+++ b/misc/openlayers/examples/point-grid.js
@@ -0,0 +1,33 @@
+var points = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true, dx: 15, dy: 15
+});
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [points],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 2
+});
+
+var rotation = document.getElementById("rotation");
+rotation.value = String(points.rotation);
+rotation.onchange = function() {
+ points.setRotation(Number(rotation.value));
+};
+
+var dx = document.getElementById("dx");
+var dy = document.getElementById("dy");
+dx.value = String(points.dx);
+dy.value = String(points.dy);
+dx.onchange = function() {
+ points.setSpacing(Number(dx.value), Number(dy.value));
+};
+dy.onchange = function() {
+ points.setSpacing(Number(dx.value), Number(dy.value));
+};
+
+var max = document.getElementById("max");
+max.value = String(points.maxFeatures);
+max.onchange = function() {
+ points.setMaxFeatures(Number(max.value));
+};
diff --git a/misc/openlayers/examples/point-track-markers.html b/misc/openlayers/examples/point-track-markers.html
new file mode 100644
index 0000000..0cb2c5d
--- /dev/null
+++ b/misc/openlayers/examples/point-track-markers.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Point Track Markers</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer, rss, lineFeatures, popup;
+
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+ function init(){
+ map = new OpenLayers.Map('map', {maxResolution:'auto'});
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(20.22, 22.05), 9);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ }
+
+ function addUrl() {
+ var urlObj = OpenLayers.Util.getElement('url');
+ var value = urlObj.value;
+ var parts = value.split("/");
+ rss = new OpenLayers.Layer.GeoRSS(parts[parts.length-1], value);
+ rss.events.register("loadend", window, populateMap);
+ map.addLayer(rss);
+ }
+
+ function populateMap() {
+ // create the point track layer
+ var lineLayer = new OpenLayers.Layer.PointTrack(rss.name + " Track",
+ {dataFrom: OpenLayers.Layer.PointTrack.dataFrom.SOURCE_NODE});
+ // add the features from the rss layer to the track layer. This
+ // also works with OpenLayers.Feature.Vector features.
+ lineLayer.addNodes(rss.features);
+ map.addLayer(lineLayer);
+
+ rss.setName(rss.name + " Comments");
+
+ var feature, marker;
+ // only show markers for features that are not "Untitled"
+ for (var i = rss.features.length-1; i>0; i--) {
+ if (rss.features[i].data.popupContentHTML.indexOf(
+ "Untitled") != -1) {
+ rss.removeMarker(rss.markers[i]);
+ }
+ }
+
+ // keep markers on top of tracks
+ map.raiseLayer(rss, 1);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">GeoRSS PointTrack in OpenLayers</h1>
+ <div id="tags">
+ GeoRSS, PointTrack
+ </div>
+ <p id="shortdesc">This demo uses OpenLayers.Layer.GeoRSS and OpenLayers.Layer.PointTrack.</p>
+ <p style="font-size:.9em;">The track is created by connecting the points of the GeoRSS feed.</a></p>
+ <form onsubmit="return false;">
+ GeoRSS URL: <input type="text" id="url" size="50" /><input type="submit" onclick="addUrl(); return false;" value="Load Feed" onsubmit="addUrl(); return false;" />
+ </form>
+ <p>The above input box allows the input of a URL to a GeoRSS feed. This feed can be local to the HTML page -- for example, entering 'xml/track1.xml' will work by default.</p>
+ <p>The example shows a track, displayed as a line connecting the points of the feed. It also shows markers at positions that have a title tag in the rss item. If clicked, a popup will show title and description.</p>
+ <div id="map" class="smallmap"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/polar-projections.html b/misc/openlayers/examples/polar-projections.html
new file mode 100644
index 0000000..de51cb0
--- /dev/null
+++ b/misc/openlayers/examples/polar-projections.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Switch between polar projections</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script type="text/javascript" src="http://svn.osgeo.org/metacrs/proj4js/trunk/lib/proj4js-compressed.js"></script>
+ <script type="text/javascript" src="http://spatialreference.org/ref/epsg/3574/proj4js/"></script>
+ <script type="text/javascript" src="http://spatialreference.org/ref/epsg/3576/proj4js/"></script>
+ <script type="text/javascript" src="http://spatialreference.org/ref/epsg/3571/proj4js/"></script>
+ <script type="text/javascript" src="http://spatialreference.org/ref/epsg/3573/proj4js/"></script>
+ <script type="text/javascript" src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript" src="polar-projections.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Polar Projections WMS Example</h1>
+
+ <div id="tags">
+ switch projections polar
+ </div>
+
+ <div id="shortdesc">Switch between different projections</div>
+
+ <div id="map" class="smallmap" style="height:512px"></div>
+ <button id='epsg3574'>EPSG:3574</button>
+ <button id='epsg3576'>EPSG:3576</button>
+ <button id='epsg3571'>EPSG:3571</button>
+ <button id='epsg3573'>EPSG:3573</button>
+
+ <div id="docs">
+ <p>This example shows how to switch between different projections,
+ maintaining the center and resolution.</p>
+ <p>Click the buttons above to try it, and see
+ <a href='polar-projections.js'>polar-projections.js</a> for the
+ source code.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/polar-projections.js b/misc/openlayers/examples/polar-projections.js
new file mode 100644
index 0000000..ac717fb
--- /dev/null
+++ b/misc/openlayers/examples/polar-projections.js
@@ -0,0 +1,84 @@
+var map, layer, overlay;
+
+var projectionOptions = {
+ 'EPSG:3574': {
+ projection: new OpenLayers.Projection('EPSG:3574'),
+ units: 'm',
+ maxExtent: new OpenLayers.Bounds(-5505054, -5505054, 5505054, 5505054),
+ maxResolution: 5505054 / 128,
+ numZoomLevels: 18
+ },
+ 'EPSG:3576': {
+ projection: new OpenLayers.Projection('EPSG:3576'),
+ units: 'm',
+ maxExtent: new OpenLayers.Bounds(-5505054, -5505054, 5505054, 5505054),
+ maxResolution: 5505054 / 128,
+ numZoomLevels: 18
+ },
+ 'EPSG:3571': {
+ projection: new OpenLayers.Projection('EPSG:3571'),
+ units: 'm',
+ maxExtent: new OpenLayers.Bounds(-5505054, -5505054, 5505054, 5505054),
+ maxResolution: 5505054 / 128,
+ numZoomLevels: 18
+ },
+ 'EPSG:3573': {
+ projection: new OpenLayers.Projection('EPSG:3573'),
+ units: 'm',
+ maxExtent: new OpenLayers.Bounds(-5505054, -5505054, 5505054, 5505054),
+ maxResolution: 5505054 / 128,
+ numZoomLevels: 18
+ }
+};
+
+function setProjection() {
+ projCode = this.innerHTML;
+ var oldExtent = map.getExtent();
+ var oldCenter = map.getCenter();
+ var oldProjection = map.getProjectionObject();
+
+ // map projection is controlled by the base layer
+ map.baseLayer.addOptions(projectionOptions[projCode]);
+
+ // with the base layer updated, the map has the new projection now
+ var newProjection = map.getProjectionObject();
+
+ // transform the center of the old projection, not the extent
+ map.setCenter(
+ oldCenter.transform(oldProjection, newProjection,
+ map.getZoomForExtent(oldExtent.transform(oldProjection, newProjection))
+ ));
+
+ for (var i=map.layers.length-1; i>=0; --i) {
+ // update grid settings
+ map.layers[i].addOptions(projectionOptions[projCode]);
+ // redraw layer - just in case center and zoom are the same in old and
+ // new projection
+ map.layers[i].redraw();
+ }
+}
+
+function init() {
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(
+ 'world',
+ 'http://v2.suite.opengeo.org/geoserver/wms',
+ {layers: 'world', version: '1.1.1'},
+ projectionOptions['EPSG:3574']
+ );
+ overlay = new OpenLayers.Layer.WMS(
+ 'world',
+ 'http://v2.suite.opengeo.org/geoserver/wms',
+ {transparent: 'true', layers: 'world:borders', styles: 'line'},
+ projectionOptions['EPSG:3574']
+ );
+ overlay.isBaseLayer = false;
+ map.addLayers([layer, overlay]);
+ map.zoomToMaxExtent();
+
+ // add behaviour to dom elements
+ document.getElementById('epsg3574').onclick = setProjection;
+ document.getElementById('epsg3576').onclick = setProjection;
+ document.getElementById('epsg3571').onclick = setProjection;
+ document.getElementById('epsg3573').onclick = setProjection;
+}
diff --git a/misc/openlayers/examples/popupMatrix.html b/misc/openlayers/examples/popupMatrix.html
new file mode 100644
index 0000000..213f580
--- /dev/null
+++ b/misc/openlayers/examples/popupMatrix.html
@@ -0,0 +1,652 @@
+<!DOCTYPE html>
+<html debug="true">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Popup Mayhem</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ width: 900px;
+ height: 500px;
+ border: 1px solid black;
+ background-color: blue;
+ }
+ </style>
+
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ var layer, markers;
+
+ var currentPopup;
+
+ var samplePopupContentsHTML = "Old man down, way down down, down by the docks of the city.<br>Blind and dirty, asked me for a dime, a dime for a cup of coffee.<br>I got no dime but I got some time to hear his story.<br>My name is August West, and I love my Pearly Baker best more than my wine.<br>More than my wine - more than my maker, though he's no friend of mine.<br><br>Everyone said, I'd come to no good, I knew I would Pearly, believe them.<br>Half of my life, I spent doin' time for some other fucker's crime,<br>The other half found me stumbling 'round drunk on Burgundy wine.<br><br>But I'll get back on my feet again someday,<br>The good Lord willin', if He says I may.<br>I know that the life i'm livin's no good,<br>I'll get a new start, live the life I should.<br>I'll get up and fly away, I'll get up and fly away, fly away.<br><br>Pearly's been true, true to me, true to my dyin' day he said,<br>I said to him, I said to him, I'm sure she's been.<br>I said to him, I'm sure she's been tru to you.<br><br>Got up and wandered, wandered downtown, nowhere to go but just hang around.<br>I've got a girl, named Bonnie Lee, I know that girl's been true to me.<br>I know she's been, I'm sure she's been true to me.<br><br>";
+ var samplePopupContentsHTML_WideShort = "Old man down, way down down, down by the docks of the city.Blind and dirty, asked me for a dime, a dime for a cup of coffee.I got no dime but I got some time to hear his story.My name is August West, and I love my Pearly Baker best more than my wine.More than my wine - more than my maker, though he's no friend of mine.Everyone said, I'd come to no good, I knew I would Pearly, believe them.<br>Half of my life, I spent doin' time for some other fucker's crime,The other half found me stumbling 'round drunk on Burgundy wine.But I'll get back on my feet again someday,The good Lord willin', if He says I may.I know that the life i'm livin's no good,I'll get a new start, live the life I should.I'll get up and fly away, I'll get up and fly away, fly away.Pearly's been true, true to me, true to my dyin' day he said,I said to him, I said to him, I'm sure she's been.I said to him, I'm sure she's been tru to you.Got up and wandered, wandered downtown, nowhere to go but just hang around.I've got a girl, named Bonnie Lee, I know that girl's been true to me.I know she's been, I'm sure she's been true to me.";
+
+// different popup types
+
+ //anchored
+ AutoSizeAnchored = OpenLayers.Class(OpenLayers.Popup.Anchored, {
+ 'autoSize': true
+ });
+
+ AutoSizeAnchoredMinSize = OpenLayers.Class(OpenLayers.Popup.Anchored, {
+ 'autoSize': true,
+ 'minSize': new OpenLayers.Size(400,400)
+ });
+
+ AutoSizeAnchoredMaxSize = OpenLayers.Class(OpenLayers.Popup.Anchored, {
+ 'autoSize': true,
+ 'maxSize': new OpenLayers.Size(100,100)
+ });
+
+ //framed
+
+ //disable the autosize for the purpose of our matrix
+ OpenLayers.Popup.FramedCloud.prototype.autoSize = false;
+
+ AutoSizeFramedCloud = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
+ 'autoSize': true
+ });
+
+ AutoSizeFramedCloudMinSize = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
+ 'autoSize': true,
+ 'minSize': new OpenLayers.Size(400,400)
+ });
+
+ AutoSizeFramedCloudMaxSize = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
+ 'autoSize': true,
+ 'maxSize': new OpenLayers.Size(100,100)
+ });
+
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.Image(
+ "popupMatrix",
+ "img/popupMatrix.jpg",
+ new OpenLayers.Bounds(-82.5,-71.5,97.5,67.5),
+ new OpenLayers.Size(1024,768)
+ );
+ map.addLayer(layer);
+
+ markers = new OpenLayers.Layer.Markers("zibo");
+ map.addLayer(markers);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+
+ addMarkers();
+ }
+
+ function addMarkers() {
+
+ var ll, popupClass, popupContentHTML;
+
+ //
+ //Anchored NO OVERFLOW
+ //
+
+ //anchored popup small contents no autosize
+ ll = new OpenLayers.LonLat(-55,20);
+ popupClass = OpenLayers.Popup.Anchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup small contents no autosize closebox
+ var ll = new OpenLayers.LonLat(-50,20);
+ popupClass = OpenLayers.Popup.Anchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup small contents autosize
+ ll = new OpenLayers.LonLat(-40,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup small contents autosize closebox
+ ll = new OpenLayers.LonLat(-35,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup small contents autosize minsize
+ ll = new OpenLayers.LonLat(-25,20);
+ popupClass = AutoSizeAnchoredMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup small contents autosize minsize closebox
+ ll = new OpenLayers.LonLat(-20,20);
+ popupClass = AutoSizeAnchoredMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup small contents autosize maxsize
+ ll = new OpenLayers.LonLat(-10,20);
+ popupClass = AutoSizeAnchoredMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup small contents autosize maxsize closebox
+ ll = new OpenLayers.LonLat(-5,20);
+ popupClass = AutoSizeAnchoredMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup bigger contents autosize
+ ll = new OpenLayers.LonLat(5,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup bigger contents autosize closebox
+ ll = new OpenLayers.LonLat(10,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize<br>closebox<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+ //anchored popup wide short text contents autosize
+ ll = new OpenLayers.LonLat(20,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize - wide short text<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup wide short text contents autosize closebox
+ ll = new OpenLayers.LonLat(25,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize - wide short text<br>closebox<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup wide short fixed contents autosize
+ ll = new OpenLayers.LonLat(35,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup wide short fixed contents autosize closebox
+ ll = new OpenLayers.LonLat(40,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup thin long fixed contents autosize
+ ll = new OpenLayers.LonLat(50,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup thin long fixed contents autosize closebox
+ ll = new OpenLayers.LonLat(55,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored popup wide long fixed contents autosize
+ ll = new OpenLayers.LonLat(65,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored popup wide long fixed contents autosize closebox
+ ll = new OpenLayers.LonLat(70,20);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+ //
+ //Anchored WITH OVERFLOW
+ //
+
+ //anchored popup small contents no autosize overflow
+ var ll = new OpenLayers.LonLat(-55,15);
+ popupClass = OpenLayers.Popup.Anchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup small contents no autosize closebox overflow
+ var ll = new OpenLayers.LonLat(-50,15);
+ popupClass = OpenLayers.Popup.Anchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup small contents autosize overflow
+ ll = new OpenLayers.LonLat(-40,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup small contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(-35,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup small contents autosize minsize overflow
+ ll = new OpenLayers.LonLat(-25,15);
+ popupClass = AutoSizeAnchoredMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup small contents autosize minsize closebox overflow
+ ll = new OpenLayers.LonLat(-20,15);
+ popupClass = AutoSizeAnchoredMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup small contents autosize maxsize overflow
+ ll = new OpenLayers.LonLat(-10,15);
+ popupClass = AutoSizeAnchoredMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup small contents autosize maxsize closebox overflow
+ ll = new OpenLayers.LonLat(-5,15);
+ popupClass = AutoSizeAnchoredMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup bigger contents autosize overflow
+ ll = new OpenLayers.LonLat(5,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize<br>overflow<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup bigger contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(10,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize<br>overflow<br>closebox<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup wide short text contents autosize overflow
+ ll = new OpenLayers.LonLat(20,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize<br>overflow<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup wide short text contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(25,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<div style="background-color:red;">Popup.Anchored<br>autosize<br>overflow<br>closebox<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+ //anchored popup wide short fixed contents autosize overflow
+ ll = new OpenLayers.LonLat(35,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup wide short fixed contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(40,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup thin long fixed contents autosize overflow
+ ll = new OpenLayers.LonLat(50,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup thin long fixed contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(55,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored popup wide long fixed contents autosize overflow
+ ll = new OpenLayers.LonLat(65,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored popup wide long fixed contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(70,15);
+ popupClass = AutoSizeAnchored;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+ //FRAMED
+
+ //
+ //FRAMED NO OVERFLOW
+ //
+
+ //anchored bubble popup small contents no autosize
+ var ll = new OpenLayers.LonLat(-55,-15);
+ popupClass = OpenLayers.Popup.FramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false);
+
+ //anchored bubble popup small contents no autosize closebox
+ var ll = new OpenLayers.LonLat(-50,-15);
+ popupClass = OpenLayers.Popup.FramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup small contents autosize
+ ll = new OpenLayers.LonLat(-40,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false);
+
+ //anchored bubble popup small contents autosize closebox
+ ll = new OpenLayers.LonLat(-35,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup small contents autosize minsize
+ ll = new OpenLayers.LonLat(-25,-15);
+ popupClass = AutoSizeFramedCloudMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false);
+
+ //anchored bubble popup small contents autosize minsize closebox
+ ll = new OpenLayers.LonLat(-20,-15);
+ popupClass = AutoSizeFramedCloudMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup small contents autosize maxsize
+ ll = new OpenLayers.LonLat(-10,-15);
+ popupClass = AutoSizeFramedCloudMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false);
+
+ //anchored bubble popup small contents autosize maxsize closebox
+ ll = new OpenLayers.LonLat(-5,-15);
+ popupClass = AutoSizeFramedCloudMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup bigger contents autosize closebox
+ ll = new OpenLayers.LonLat(5,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, false);
+
+ //anchored bubble popup bigger contents autosize closebox
+ ll = new OpenLayers.LonLat(10,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize<br>closebox<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup wide short text contents autosize
+ ll = new OpenLayers.LonLat(20,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize - wide short text<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored bubble popup wide short text contents autosize closebox
+ ll = new OpenLayers.LonLat(25,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize - wide short text<br>closebox<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup wide short fixed contents autosize
+ ll = new OpenLayers.LonLat(35,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored bubble popup wide short fixed contents autosize closebox
+ ll = new OpenLayers.LonLat(40,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup thin long fixed contents autosize
+ ll = new OpenLayers.LonLat(50,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored bubble popup thin long fixed contents autosize closebox
+ ll = new OpenLayers.LonLat(55,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ //anchored bubble popup wide long fixed contents autosize
+ ll = new OpenLayers.LonLat(65,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML);
+
+ //anchored bubble popup wide long fixed contents autosize closebox
+ ll = new OpenLayers.LonLat(70,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+ //
+ //FRAMED OVERFLOW
+ //
+
+ //anchored bubble popup small contents no autosize
+ var ll = new OpenLayers.LonLat(-55,-20);
+ popupClass = OpenLayers.Popup.FramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup small contents no autosize closebox
+ var ll = new OpenLayers.LonLat(-50,-20);
+ popupClass = OpenLayers.Popup.FramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup small contents autosize
+ ll = new OpenLayers.LonLat(-40,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup small contents autosize closebox
+ ll = new OpenLayers.LonLat(-35,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup small contents autosize minsize
+ ll = new OpenLayers.LonLat(-25,-20);
+ popupClass = AutoSizeFramedCloudMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup small contents autosize minsize closebox
+ ll = new OpenLayers.LonLat(-20,-20);
+ popupClass = AutoSizeFramedCloudMinSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup small contents autosize maxsize
+ ll = new OpenLayers.LonLat(-10,-20);
+ popupClass = AutoSizeFramedCloudMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup small contents autosize maxsize closebox
+ ll = new OpenLayers.LonLat(-5,-20);
+ popupClass = AutoSizeFramedCloudMaxSize;
+ popupContentHTML = '<img src="img/small.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup bigger contents autosize closebox
+ ll = new OpenLayers.LonLat(5,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize<br>overflow<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup bigger contents autosize closebox
+ ll = new OpenLayers.LonLat(10,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize<br>closebox<br>overflow<br>' + samplePopupContentsHTML + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup wide short contents autosize overflow
+ ll = new OpenLayers.LonLat(20,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize<br>overflow<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup wide short contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(25,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<div style="background-color:red;">Popup.FramedCloud<br>autosize<br>overflow<br>closebox<br>' + samplePopupContentsHTML_WideShort + '</div>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup wide short fixed contents autosize overflow
+ ll = new OpenLayers.LonLat(35,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup wide short fixed contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(40,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/wideshort.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup thin long fixed contents autosize overflow
+ ll = new OpenLayers.LonLat(50,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup thin long fixed contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(55,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/thinlong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ //anchored bubble popup wide long fixed contents autosize overflow
+ ll = new OpenLayers.LonLat(65,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, false, true);
+
+ //anchored bubble popup wide long fixed contents autosize closebox overflow
+ ll = new OpenLayers.LonLat(70,-20);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = '<img src="img/widelong.jpg"></img>';
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ }
+
+ /**
+ * Function: addMarker
+ * Add a new marker to the markers layer given the following lonlat,
+ * popupClass, and popup contents HTML. Also allow specifying
+ * whether or not to give the popup a close box.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>} Where to place the marker
+ * popupClass - {<OpenLayers.Class>} Which class of popup to bring up
+ * when the marker is clicked.
+ * popupContentHTML - {String} What to put in the popup
+ * closeBox - {Boolean} Should popup have a close box?
+ * overflow - {Boolean} Let the popup overflow scrollbars?
+ */
+ function addMarker(ll, popupClass, popupContentHTML, closeBox, overflow) {
+
+ var feature = new OpenLayers.Feature(markers, ll);
+ feature.closeBox = closeBox;
+ feature.popupClass = popupClass;
+ feature.data.popupContentHTML = popupContentHTML;
+ feature.data.overflow = (overflow) ? "auto" : "hidden";
+
+ var marker = feature.createMarker();
+
+ var markerClick = function (evt) {
+ if (this.popup == null) {
+ this.popup = this.createPopup(this.closeBox);
+ map.addPopup(this.popup);
+ this.popup.show();
+ } else {
+ this.popup.toggle();
+ }
+ currentPopup = this.popup;
+ OpenLayers.Event.stop(evt);
+ };
+ marker.events.register("mousedown", feature, markerClick);
+
+ markers.addMarker(marker);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Popup Matrix</h1>
+
+ <div id="tags">
+ popup, popups
+ </div>
+ <p id="shortdesc">
+ All kinds of different popup configurations.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <!-- preloading these images so the autosize will work correctly -->
+ <img src="img/wideshort.jpg" style="position:absolute; top:-5000px; left: -5000px"></img>
+ <img src="img/widelong.jpg" style="position:absolute; top:-5000px; left: -5000px"></img>
+ <img src="img/thinlong.jpg" style="position:absolute; top:-5000px; left: -5000px"></img>
+
+ <p> All of the images in this file a pre-cached, meaning they are
+ loaded immediately when you load the page (they are just placed
+ far offscreen, that's why you don't see them).
+ </p>
+ <br>
+ <p> The only image that is *not* preloaded is img/small.jpg, the brazilian
+ flag. We do this in order to test out to make sure that our auto-sizing
+ code does in fact activate itself as the images load. To verify
+ this, clear your cache and reload this example page. Click on
+ any of the markers in the 'AutoSize' row. If the popup autosizes
+ to correctly contain the entire flag: golden. If the popup is
+ tiny and you can only see a corner of it, then this code is broken.
+ </p>
+
+ <br>
+
+
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/proxy.cgi b/misc/openlayers/examples/proxy.cgi
new file mode 100755
index 0000000..1d2818f
--- /dev/null
+++ b/misc/openlayers/examples/proxy.cgi
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+
+"""This is a blind proxy that we use to get around browser
+restrictions that prevent the Javascript from loading pages not on the
+same server as the Javascript. This has several problems: it's less
+efficient, it might break some sites, and it's a security risk because
+people can use this proxy to browse the web and possibly do bad stuff
+with it. It only loads pages via http and https, but it can load any
+content type. It supports GET and POST requests."""
+
+import urllib2
+import cgi
+import sys, os
+
+# Designed to prevent Open Proxy type stuff.
+
+allowedHosts = ['www.openlayers.org', 'openlayers.org',
+ 'labs.metacarta.com', 'world.freemap.in',
+ 'prototype.openmnnd.org', 'geo.openplans.org',
+ 'sigma.openplans.org', 'demo.opengeo.org',
+ 'www.openstreetmap.org', 'sample.azavea.com',
+ 'v2.suite.opengeo.org', 'v-swe.uni-muenster.de:8080',
+ 'vmap0.tiles.osgeo.org', 'www.openrouteservice.org',
+ 'maps.wien.gv.at']
+
+method = os.environ["REQUEST_METHOD"]
+
+if method == "POST":
+ qs = os.environ["QUERY_STRING"]
+ d = cgi.parse_qs(qs)
+ if d.has_key("url"):
+ url = d["url"][0]
+ else:
+ url = "http://www.openlayers.org"
+else:
+ fs = cgi.FieldStorage()
+ url = fs.getvalue('url', "http://www.openlayers.org")
+
+try:
+ host = url.split("/")[2]
+ if allowedHosts and not host in allowedHosts:
+ print "Status: 502 Bad Gateway"
+ print "Content-Type: text/plain"
+ print
+ print "This proxy does not allow you to access that location (%s)." % (host,)
+ print
+ print os.environ
+
+ elif url.startswith("http://") or url.startswith("https://"):
+
+ if method == "POST":
+ length = int(os.environ["CONTENT_LENGTH"])
+ headers = {"Content-Type": os.environ["CONTENT_TYPE"]}
+ body = sys.stdin.read(length)
+ r = urllib2.Request(url, body, headers)
+ y = urllib2.urlopen(r)
+ else:
+ y = urllib2.urlopen(url)
+
+ # print content type header
+ i = y.info()
+ if i.has_key("Content-Type"):
+ print "Content-Type: %s" % (i["Content-Type"])
+ else:
+ print "Content-Type: text/plain"
+ print
+
+ print y.read()
+
+ y.close()
+ else:
+ print "Content-Type: text/plain"
+ print
+ print "Illegal request."
+
+except Exception, E:
+ print "Status: 500 Unexpected Error"
+ print "Content-Type: text/plain"
+ print
+ print "Some unexpected error occurred. Error text was:", E
diff --git a/misc/openlayers/examples/regular-polygons.html b/misc/openlayers/examples/regular-polygons.html
new file mode 100644
index 0000000..f2725dd
--- /dev/null
+++ b/misc/openlayers/examples/regular-polygons.html
@@ -0,0 +1,177 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Regular Polygon Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ p {
+ width: 512px;
+ }
+ #config {
+ margin-top: 1em;
+ width: 512px;
+ position: relative;
+ height: 8em;
+ }
+ #controls {
+ padding-left: 2em;
+ margin-left: 0;
+ width: 12em;
+ }
+ #controls li {
+ padding-top: 0.5em;
+ list-style: none;
+ }
+ #options {
+ font-size: 1em;
+ top: 0;
+ margin-left: 15em;
+ position: absolute;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, polygonControl;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ var polygonLayer = new OpenLayers.Layer.Vector("Polygon Layer");
+
+ map.addLayers([wmsLayer, polygonLayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ polyOptions = {sides: 4};
+ polygonControl = new OpenLayers.Control.DrawFeature(polygonLayer,
+ OpenLayers.Handler.RegularPolygon,
+ {handlerOptions: polyOptions});
+
+ map.addControl(polygonControl);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ document.getElementById('noneToggle').checked = true;
+ document.getElementById('irregularToggle').checked = false;
+ }
+ function setOptions(options) {
+ polygonControl.handler.setOptions(options);
+ }
+ function setSize(fraction) {
+ var radius = fraction * map.getExtent().getHeight();
+ polygonControl.handler.setOptions({radius: radius,
+ angle: 0});
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Regular Polygon Example</h1>
+ <div id="tags">
+ vector, feature, regularpolygon, drawing, draw, advanced
+ </div>
+ <p id="shortdesc">
+ Shows how to use the RegularPolygon handler to draw features with
+ different numbers of sides.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="config">
+
+ <ul id="controls"><b>Map Controls</b>
+ <li>
+ <input type="radio" name="type"
+ value="none" id="noneToggle"
+ onclick="polygonControl.deactivate()"
+ checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type"
+ value="polygon" id="polygonToggle"
+ onclick="polygonControl.activate()" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ </ul>
+ <table id="options">
+ <tbody>
+ <tr>
+ <th>Draw Option</th>
+ <th>Value</th>
+ </tr>
+ <tr>
+ <td>
+ shape
+ </td>
+ <td>
+ <select name="sides"
+ onchange="setOptions({sides: parseInt(this.value)})">
+ <option value="3">triangle</option>
+ <option value="4" selected="selected">square</option>
+ <option value="5">pentagon</option>
+ <option value="6">hexagon</option>
+ <option value="40">circle</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ snap angle
+ </td>
+ <td>
+ <select name="angle"
+ onchange="setOptions({snapAngle: parseFloat(this.value)})">
+ <option value="" selected="selected">no snap</option>
+ <option value="15">15&deg;</option>
+ <option value="45">45&deg;</option>
+ <option value="90">90&deg;</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ size
+ </td>
+ <td>
+ <select name="size"
+ onchange="setSize(parseFloat(this.value))">
+ <option value="" selected="selected">variable</option>
+ <option value="0.1">small</option>
+ <option value="0.2">medium</option>
+ <option value="0.4">large</option>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ irregular
+ </td>
+ <td>
+ <input id="irregularToggle" name="irregular"
+ type="checkbox"
+ onchange="setOptions({irregular: this.checked})") />
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ <p>
+ Regular polygons can be drawn by pointing a DrawFeature control to the
+ RegularPolygon handler class. The options above demonstrate how the
+ handler can be configured. Note if you are in angle snapping mode (if
+ the snap angle is non-null) and you hold down the <b>Shift</b> key, you
+ will toggle to non-snapping mode.
+ </p>
+ <p>
+ The <i>irregular</i> option allows drawing of irregular polygons. With this option, the fixed radius option is ignored.
+ </body>
+</html>
diff --git a/misc/openlayers/examples/resize-features.html b/misc/openlayers/examples/resize-features.html
new file mode 100644
index 0000000..2bf68f7
--- /dev/null
+++ b/misc/openlayers/examples/resize-features.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Resize Features Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ p {
+ width: 500px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectorLayer, pointFeature, lineFeature, polygonFeature;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ var style_blue = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
+ style_blue.strokeColor = "blue";
+ style_blue.fillColor = "blue";
+ var style_green = {
+ strokeColor: "#339933",
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ pointRadius: 6,
+ pointerEvents: "visiblePainted"
+ };
+
+ vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry");
+
+ // create a point feature
+ var point = new OpenLayers.Geometry.Point(-110, 45);
+ pointFeature = new OpenLayers.Feature.Vector(point, null, style_blue);
+
+ // create a line feature from a list of points
+ var pointList = [];
+ var newPoint = point;
+ for(var p=0; p<5; ++p) {
+ newPoint = new OpenLayers.Geometry.Point(newPoint.x + Math.random(1),
+ newPoint.y + Math.random(1));
+ pointList.push(newPoint);
+ }
+ lineFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(pointList),null,style_green);
+
+ // create a polygon feature from a linear ring of points
+ var pointList = [];
+ for(var p=0; p<6; ++p) {
+ var a = p * (2 * Math.PI) / 7;
+ var r = Math.random(1) + 1;
+ var newPoint = new OpenLayers.Geometry.Point(point.x + (r * Math.cos(a)),
+ point.y + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+
+ var linearRing = new OpenLayers.Geometry.LinearRing(pointList);
+ polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([linearRing]));
+
+
+ map.addLayer(vectorLayer);
+ map.setCenter(new OpenLayers.LonLat(point.x, point.y), 5);
+ vectorLayer.addFeatures([pointFeature, lineFeature, polygonFeature]);
+
+ }
+
+ var origin = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ function resizeFeatures(scale) {
+ pointFeature.geometry.resize(scale, origin);
+ lineFeature.geometry.resize(scale, origin);
+ polygonFeature.geometry.resize(scale, origin);
+ vectorLayer.redraw();
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Resize Features Programatically</h1>
+ <div id="tags">
+ vector, feature, resizing, resize, light
+ </div>
+ <p id="shortdesc">
+ Demonstration of how to use the geometry resize methods to
+ change feature sizes programatically.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <p>This example demonstrates how features can be resized. There is not yet
+ a control built that provides a tool for resizing, but the geometry.resize
+ method can be accessed to resize programmatically.</p>
+ <p>Make the features <a href="javascript: resizeFeatures(1.5);">bigger</a>
+ or <a href="javascript: resizeFeatures(1 / 1.5);">smaller</a>.
+ </body>
+</html>
diff --git a/misc/openlayers/examples/restricted-extent.html b/misc/openlayers/examples/restricted-extent.html
new file mode 100644
index 0000000..7ab4ca8
--- /dev/null
+++ b/misc/openlayers/examples/restricted-extent.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Restricted Extent Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map = null;
+ var extent = new OpenLayers.Bounds(8, 44.5, 19, 50);
+
+ function init() {
+ var options = {
+ restrictedExtent: extent
+ };
+ map = new OpenLayers.Map('map', options);
+
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'}
+ );
+
+ map.addLayers([wms]);
+ map.zoomToExtent(extent);
+ document.getElementById("toggle").checked = true;
+ }
+
+ function toggleRestrictedExtent() {
+ if(map.restrictedExtent == null) {
+ map.setOptions({restrictedExtent: extent});
+ } else {
+ map.setOptions({restrictedExtent: null});
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Restricted Extent Example</h1>
+ <div id="tags">
+ map, restrict, restrictedextent, extent, light
+ </div>
+ <p id="shortdesc">
+ Don't let users drag outside the map extent: instead, limit dragging such
+ that the extent of the layer is the maximum viewable area.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <p>
+ Map navigation is limited by a combination of map and layer properties.
+ The base layer resolutions array controls the resolutions (or zoom
+ levels) available. The resolutions can be limited by setting a
+ maxResolution property or by explicitly specifying a resolutions
+ array.
+ </p>
+ <p>
+ Navigation limited by the maxExtent property. A map cannot be panned
+ so that the center of the viewport is outside of the bounds specified
+ in maxExtent. If you wish to further restrict panning, use the
+ restrictedExtent property. With restrictedExtent set, the map cannot
+ be panned beyond the given bounds. If the maxResolution allows the
+ map to be zoomed to a resolution that displays an area bigger than
+ the restrictedExtent, the viewport will remain centered on the
+ restrictedExtent.
+ </p>
+ <p>
+ <input type="checkbox" id="toggle" checked="checked"
+ onclick="toggleRestrictedExtent();" />
+ <label for="toggle">
+ Toggle restricted extent (to [8, 44.5, 19, 50]).
+ </label>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/rotate-features.html b/misc/openlayers/examples/rotate-features.html
new file mode 100644
index 0000000..51e559b
--- /dev/null
+++ b/misc/openlayers/examples/rotate-features.html
@@ -0,0 +1,113 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Rotate Features Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ p {
+ width: 500px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, pointFeature, lineFeature, polygonFeature;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ var style_blue = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
+ style_blue.strokeColor = "blue";
+ style_blue.fillColor = "blue";
+ var style_green = {
+ strokeColor: "#339933",
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ pointRadius: 6,
+ pointerEvents: "visiblePainted"
+ };
+
+ var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry");
+
+ // create a point feature
+ var point = new OpenLayers.Geometry.Point(-110, 45);
+ pointFeature = new OpenLayers.Feature.Vector(point, null, style_blue);
+
+ // create a line feature from a list of points
+ var pointList = [];
+ var newPoint = point;
+ for(var p=0; p<5; ++p) {
+ newPoint = new OpenLayers.Geometry.Point(newPoint.x + Math.random(1),
+ newPoint.y + Math.random(1));
+ pointList.push(newPoint);
+ }
+ lineFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(pointList),null,style_green);
+
+ // create a polygon feature from a linear ring of points
+ var pointList = [];
+ for(var p=0; p<6; ++p) {
+ var a = p * (2 * Math.PI) / 7;
+ var r = Math.random(1) + 1;
+ var newPoint = new OpenLayers.Geometry.Point(point.x + (r * Math.cos(a)),
+ point.y + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+
+ var linearRing = new OpenLayers.Geometry.LinearRing(pointList);
+ polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([linearRing]));
+
+
+ map.addLayer(vectorLayer);
+ map.setCenter(new OpenLayers.LonLat(point.x, point.y), 5);
+ vectorLayer.addFeatures([pointFeature, lineFeature, polygonFeature]);
+
+ // start rotating
+ var origin = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var style = {
+ strokeColor: "#666666",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ pointRadius: 2,
+ pointerEvents: "visiblePainted"
+ };
+ var center = new OpenLayers.Feature.Vector(origin, null, style);
+ vectorLayer.addFeatures([center]);
+ window.setInterval(function() {rotateFeature(
+ pointFeature, 360 / 20, origin)}, 100);
+ window.setInterval(function() {rotateFeature(
+ lineFeature, 360 / 40, origin)}, 100);
+ window.setInterval(function(){rotateFeature(
+ polygonFeature, -360 / 20, origin)}, 100);
+ }
+
+ function rotateFeature(feature, angle, origin) {
+ feature.geometry.rotate(angle, origin);
+ feature.layer.drawFeature(feature);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Rotate vector features</h1>
+
+ <div id="tags">
+ vector, feature, rotating, rotation, rotate, advanced, light
+ </div>
+ <p id="shortdesc">
+ Details on how to create and rotate vector features programmatically
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="docs">This example shows a few features rotating. There is not yet a control
+ built that provides a tool for rotating, but the geometry.rotate method
+ can be accessed to rotate programmatically.</div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/select-feature-multilayer.html b/misc/openlayers/examples/select-feature-multilayer.html
new file mode 100644
index 0000000..6b8f3f4
--- /dev/null
+++ b/misc/openlayers/examples/select-feature-multilayer.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>SelectFeature Control on multiple vector layers</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, selectControl;
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wmsLayer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var vectors1 = new OpenLayers.Layer.Vector("Vector Layer 1", {
+ renderers: renderer,
+ styleMap: new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style(OpenLayers.Util.applyDefaults({
+ externalGraphic: "../img/marker-green.png",
+ graphicOpacity: 1,
+ rotation: -45,
+ pointRadius: 10
+ }, OpenLayers.Feature.Vector.style["default"])),
+ "select": new OpenLayers.Style({
+ externalGraphic: "../img/marker-blue.png"
+ })
+ })
+ });
+ var vectors2 = new OpenLayers.Layer.Vector("Vector Layer 2", {
+ renderers: renderer,
+ styleMap: new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style(OpenLayers.Util.applyDefaults({
+ fillColor: "red",
+ strokeColor: "gray",
+ graphicName: "square",
+ rotation: 45,
+ pointRadius: 15
+ }, OpenLayers.Feature.Vector.style["default"])),
+ "select": new OpenLayers.Style(OpenLayers.Util.applyDefaults({
+ graphicName: "square",
+ rotation: 45,
+ pointRadius: 15
+ }, OpenLayers.Feature.Vector.style["select"]))
+ })
+ });
+ map.addLayers([wmsLayer, vectors1, vectors2]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ selectControl = new OpenLayers.Control.SelectFeature(
+ [vectors1, vectors2],
+ {
+ clickout: true, toggle: false,
+ multiple: false, hover: false,
+ toggleKey: "ctrlKey", // ctrl key removes from selection
+ multipleKey: "shiftKey" // shift key adds to selection
+ }
+ );
+
+ map.addControl(selectControl);
+ selectControl.activate();
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+ vectors1.addFeatures(createFeatures());
+ vectors2.addFeatures(createFeatures());
+
+ vectors1.events.on({
+ "featureselected": function(e) {
+ showStatus("selected feature "+e.feature.id+" on Vector Layer 1");
+ },
+ "featureunselected": function(e) {
+ showStatus("unselected feature "+e.feature.id+" on Vector Layer 1");
+ }
+ });
+ vectors2.events.on({
+ "featureselected": function(e) {
+ showStatus("selected feature "+e.feature.id+" on Vector Layer 2");
+ },
+ "featureunselected": function(e) {
+ showStatus("unselected feature "+e.feature.id+" on Vector Layer 2");
+ }
+ });
+ }
+
+ function createFeatures() {
+ var extent = map.getExtent();
+ var features = [];
+ for(var i=0; i<10; ++i) {
+ features.push(new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(extent.left + (extent.right - extent.left) * Math.random(),
+ extent.bottom + (extent.top - extent.bottom) * Math.random()
+ )));
+ }
+ return features;
+ }
+
+ function showStatus(text) {
+ document.getElementById("status").innerHTML = text;
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Select Feature on Multiple Layers Example</h1>
+ <div id="tags">
+ vector, feature, selecting, selection, advanced, light
+ </div>
+ <p id="shortdesc">
+ Select a feature on click with the Control.SelectFeature on multiple
+ vector layers.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="status"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/select-feature-openpopup.html b/misc/openlayers/examples/select-feature-openpopup.html
new file mode 100644
index 0000000..cdd0e41
--- /dev/null
+++ b/misc/openlayers/examples/select-feature-openpopup.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Open Popup on Layer.Vector</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, drawControls, selectControl, selectedFeature;
+ function onPopupClose(evt) {
+ selectControl.unselect(selectedFeature);
+ }
+ function onFeatureSelect(feature) {
+ selectedFeature = feature;
+ popup = new OpenLayers.Popup.FramedCloud("chicken",
+ feature.geometry.getBounds().getCenterLonLat(),
+ null,
+ "<div style='font-size:.8em'>Feature: " + feature.id +"<br>Area: " + feature.geometry.getArea()+"</div>",
+ null, true, onPopupClose);
+ feature.popup = popup;
+ map.addPopup(popup);
+ }
+ function onFeatureUnselect(feature) {
+ map.removePopup(feature.popup);
+ feature.popup.destroy();
+ feature.popup = null;
+ }
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ var polygonLayer = new OpenLayers.Layer.Vector("Polygon Layer");
+
+ map.addLayers([wmsLayer, polygonLayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ selectControl = new OpenLayers.Control.SelectFeature(polygonLayer,
+ {onSelect: onFeatureSelect, onUnselect: onFeatureUnselect});
+ drawControls = {
+ polygon: new OpenLayers.Control.DrawFeature(polygonLayer,
+ OpenLayers.Handler.Polygon),
+ select: selectControl
+ };
+
+ for(var key in drawControls) {
+ map.addControl(drawControls[key]);
+ }
+
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ }
+
+ function toggleControl(element) {
+ for(key in drawControls) {
+ var control = drawControls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Open Popup on Layer.Vector</h1>
+ <div id="tags">
+ vector, feature, selecting, selection, popup
+ </div>
+ <p id="shortdesc">
+ Using a Control.SelectFeature, open a popup on click.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle"
+ onclick="toggleControl(this);" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="select" id="selectToggle"
+ onclick="toggleControl(this);" />
+ <label for="selectToggle">select polygon on click</label>
+ </li>
+ </ul>
+ <p>It is possible to use the onSelect/onUnselect hooks on the SelectFeature
+ to do fun things -- like open a popup.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/select-feature.html b/misc/openlayers/examples/select-feature.html
new file mode 100644
index 0000000..fe5243e
--- /dev/null
+++ b/misc/openlayers/examples/select-feature.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>SelectFeature Control on Layer.Vector</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, drawControls;
+
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wmsLayer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var vectors = new OpenLayers.Layer.Vector("Vector Layer", {
+ renderers: renderer
+ });
+ vectors.events.on({
+ 'featureselected': function(feature) {
+ document.getElementById('counter').innerHTML = this.selectedFeatures.length;
+ },
+ 'featureunselected': function(feature) {
+ document.getElementById('counter').innerHTML = this.selectedFeatures.length;
+ }
+ });
+
+ map.addLayers([wmsLayer, vectors]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ drawControls = {
+ point: new OpenLayers.Control.DrawFeature(
+ vectors, OpenLayers.Handler.Point
+ ),
+ line: new OpenLayers.Control.DrawFeature(
+ vectors, OpenLayers.Handler.Path
+ ),
+ polygon: new OpenLayers.Control.DrawFeature(
+ vectors, OpenLayers.Handler.Polygon
+ ),
+ select: new OpenLayers.Control.SelectFeature(
+ vectors,
+ {
+ clickout: false, toggle: false,
+ multiple: false, hover: false,
+ toggleKey: "ctrlKey", // ctrl key removes from selection
+ multipleKey: "shiftKey", // shift key adds to selection
+ box: true
+ }
+ ),
+ selecthover: new OpenLayers.Control.SelectFeature(
+ vectors,
+ {
+ multiple: false, hover: true,
+ toggleKey: "ctrlKey", // ctrl key removes from selection
+ multipleKey: "shiftKey" // shift key adds to selection
+ }
+ )
+ };
+
+ for(var key in drawControls) {
+ map.addControl(drawControls[key]);
+ }
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ }
+
+ function toggleControl(element) {
+ for(key in drawControls) {
+ var control = drawControls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+
+ function update() {
+ var clickout = document.getElementById("clickout").checked;
+ if(clickout != drawControls.select.clickout) {
+ drawControls.select.clickout = clickout;
+ }
+
+ var box = document.getElementById("box").checked;
+ if(box != drawControls.select.box) {
+ drawControls.select.box = box;
+ if(drawControls.select.active) {
+ drawControls.select.deactivate();
+ drawControls.select.activate();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Select Feature Example</h1>
+ <div id="tags">
+ vector, feature, selecting, selection, advanced
+ </div>
+ <p id="shortdesc">
+ Select a feature on hover or click with the Control.SelectFeature on a
+ vector layer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="point" id="pointToggle"
+ onclick="toggleControl(this);" />
+ <label for="pointToggle">draw point</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="line" id="lineToggle"
+ onclick="toggleControl(this);" />
+ <label for="lineToggle">draw line</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle"
+ onclick="toggleControl(this);" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="selecthover" id="selecthoverToggle"
+ onclick="toggleControl(this);" />
+ <label for="selecthoverToggle">Select features on hover</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="select" id="selectToggle"
+ onclick="toggleControl(this);" />
+ <label for="selectToggle">select feature (<span id="counter">0</span> features selected)</label>
+ <ul>
+ <li>
+ <input id="box" type="checkbox" checked="checked"
+ name="box" onchange="update()" />
+ <label for="box">select features in a box</label>
+ </li>
+ <li>
+ <input id="clickout" type="checkbox"
+ name="clickout" onchange="update()" />
+ <label for="clickout">click out to unselect features</label>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <p>Use the shift key to select multiple features. Use the ctrl key to
+ toggle selection on features one at a time. Note: the "clickout" option has no
+ effect when "hover" is selected.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/setextent.html b/misc/openlayers/examples/setextent.html
new file mode 100644
index 0000000..5cf5685
--- /dev/null
+++ b/misc/openlayers/examples/setextent.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+<title>Setting a visual Extent</title>
+<link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+<script src="../lib/OpenLayers.js"></script>
+</head>
+<body>
+<h1 id="title">Setting a Visual Extent</h1>
+<div id="tags">
+ boxes, box, marker
+</div>
+<p id='shortdesc'>
+ Use a boxes layer to visually display the area of interest indicated by a user.
+</p>
+<p>
+ Because the ability to set the map to a given extent is limited by the
+ current resolutions available, zoomToExtent will not always set the map to
+ exactly the right extent. In order to visually annotate the actual extent,
+ this example, will use the Boxes layer to visually describe the desired
+ extent as well as setting the map extent.
+</p>
+ <div style="width:100%; height:75%" id="map"></div>
+ <script defer="defer" type="text/javascript">
+ var map = new OpenLayers.Map('map');
+ var bounds = new OpenLayers.Bounds(-45,-45, 0, 45);
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(wms);
+ map.zoomToExtent(bounds);
+ var boxes = new OpenLayers.Layer.Boxes("boxes");
+ var box = new OpenLayers.Marker.Box(bounds);
+ boxes.addMarker(box);
+ map.addLayer(boxes);
+ </script>
+</body>
+</html>
diff --git a/misc/openlayers/examples/simplify-linestring.html b/misc/openlayers/examples/simplify-linestring.html
new file mode 100644
index 0000000..15160a0
--- /dev/null
+++ b/misc/openlayers/examples/simplify-linestring.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Simplify a LineString geometry</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #map, #map-simplify {
+ height: 400px;
+ width: 400px;
+ margin: 5px !important;
+ float: left;
+ }
+ #info {
+ width: 300px;
+ float: left;
+ }
+ #docs {
+ clear: both;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Simplify a LineString geometry</h1>
+ <div id="tags">
+ Douglas-Peucker, Douglas, Peucker, Peuker, tolerance
+ </div>
+ <p id="shortdesc">
+ Shows the usage of the method &quot;simplify&quot; that implements
+ the Douglas-Peucker algorithm to remove &quot;insignificant&quot;
+ vertices from LineString geometries.
+ </p>
+ <div id="control-simplify">
+ <label for="tolerance">Tolerance factor:</label>
+ <input name="tolerance" id="tolerance" type="number" min="0" max="1" step="0.02" value="0.1">
+ <input type="button" id="simplify" value="Simplify LineString">
+ <input type="button" id="animation" value="Start animation">
+ </div>
+ <div id="map" class="smallmap">
+ </div>
+ <div id="map-simplify" class="smallmap">
+ </div>
+ <div id="info">
+ </div>
+ <div id="docs">
+ <p>
+ Instances of OpenLayers.Geometry.LineString have a method simplify,
+ that can be used to simplify linestring geometries.
+ Simplification sometimes is useful to enhance the perfomance of
+ vector rendering or to reduce complexity of geometries. This
+ might be especially handy when viewing geometries a small
+ scales.
+ </p>
+ <p>
+ OpenLayers.Geometry.LineString::simplify is a recursive
+ implementation of the famous Douglas-Peucker algorithm. It is
+ controlled by a tolerance factor that defines the threshold for
+ vertices to be considered &quot;insignificant&quot; for the
+ general structure of the geometry.
+ </p>
+ <p>
+ The LineString on the left map can be simplified according to
+ the tolerance value one enters in the form-field above the maps.
+ Use a value between 0 and 1 for best results. If you navigate
+ the left map, the right map will show the same location to make
+ it easier to spot the differeces between the LineStrings.
+ </p>
+ <p>
+ You can also use the button &quot;Start animation&quot; to get
+ results for increasing tolerance-factors from 0.02 to 1.0. The
+ animation can be paused by clicking on the button &quot;Stop
+ animation&quot;.
+ </p>
+ <p>
+ The LineString represents a part of the coastline of
+ <a href="http://www.openstreetmap.org/?lat=54.7309684753418&amp;lon=83.1809234619141&amp;zoom=11">this
+ place southeast of Novosibirsk in Russia</a> &mdash; found via
+ <a href="http://ryba4.com/python/ramer-douglas-peucker">an
+ example implementation of the algorithm in python</a>.
+ </p>
+ <p>
+ For a detailled explanation of the algorithm see
+ <a href="http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm">the
+ Wikipedia article</a> or the original publication: David Douglas
+ &amp; Thomas Peucker, "Algorithms for the reduction of the
+ number of points required to represent a digitized line or its
+ caricature", The Canadian Cartographer 10(2), 112-122 (1973)
+ (<a href="http://dx.doi.org/10.3138/FM57-6770-U75U-7727">DOI:
+ 10.3138/FM57-6770-U75U-7727</a>).
+ </p>
+ <p>See <a href="simplify-linestring.js">simplify-linestring.js</a>
+ for the source code of this example.</p>
+ </div>
+ <script type="text/javascript" src="../lib/OpenLayers.js">
+ </script>
+ <script type="text/javascript" src="./simplify-linestring.js">
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/simplify-linestring.js b/misc/openlayers/examples/simplify-linestring.js
new file mode 100644
index 0000000..3f4c6f6
--- /dev/null
+++ b/misc/openlayers/examples/simplify-linestring.js
@@ -0,0 +1,599 @@
+// global variables
+var map, map2;
+
+// wrap the instanciation code in an anonymous function that gets executed
+// immedeately
+(function(){
+ // style the vectorlayer
+ var styleMap = new OpenLayers.StyleMap({
+ 'default': new OpenLayers.Style({
+ strokeColor: "#333333",
+ strokeWidth: 1.2,
+ strokeOpacity: 1
+ })
+ });
+
+ // the vectorlayer
+ var vectorlayer = new OpenLayers.Layer.Vector('Vectorlayer', {
+ isBaseLayer: true,
+ styleMap: styleMap
+ });
+
+ var original = OpenLayers.Geometry.fromWKT("LINESTRING(" +
+ "6.247872 11.316756," +
+ "6.338566 11.316756," +
+ "6.633323 11.205644," +
+ "6.724018 11.205644," +
+ "6.792039 11.205644," +
+ "7.154817 11.372311," +
+ "7.313532 11.400089," +
+ "7.381553 11.344533," +
+ "7.336206 11.288978," +
+ "7.200164 11.288978," +
+ "7.154817 11.261200," +
+ "7.132143 11.233422," +
+ "7.154817 11.150089," +
+ "7.268185 11.177867," +
+ "7.313532 11.122311," +
+ "7.404227 11.150089," +
+ "7.472248 11.094533," +
+ "7.767005 10.900089," +
+ "7.758951 10.864989," +
+ "7.752684 10.837656," +
+ "7.426900 10.927867," +
+ "6.519955 10.927867," +
+ "6.429261 10.900089," +
+ "6.315893 10.955644," +
+ "6.270545 10.955644," +
+ "6.247872 10.927867," +
+ "6.111830 11.011200," +
+ "6.066483 11.066756," +
+ "5.862420 11.038978," +
+ "5.817073 10.955644," +
+ "5.771726 10.900089," +
+ "5.862420 10.761200," +
+ "5.975788 10.733422," +
+ "6.157177 10.566756," +
+ "6.247872 10.511200," +
+ "6.293219 10.427867," +
+ "6.315893 10.233422," +
+ "6.315893 10.177867," +
+ "6.542629 9.844533," +
+ "6.587976 9.761200," +
+ "6.610650 9.288978," +
+ "6.542629 9.066756," +
+ "6.565303 8.900089," +
+ "6.519955 8.816756," +
+ "6.542629 8.761200," +
+ "6.565303 8.733422," +
+ "6.429261 8.427867," +
+ "6.474608 8.316756," +
+ "6.724018 8.288978," +
+ "6.882733 8.538978," +
+ "6.973428 8.594533," +
+ "6.996101 8.622311," +
+ "7.200164 8.650089," +
+ "7.290859 8.650089," +
+ "7.426900 8.483422," +
+ "7.404227 8.455644," +
+ "7.245511 8.511200," +
+ "6.996101 8.427867," +
+ "7.041449 8.372311," +
+ "7.154817 8.455644," +
+ "7.200164 8.455644," +
+ "7.245511 8.455644," +
+ "7.381553 8.316756," +
+ "7.381553 8.261200," +
+ "7.404227 8.233422," +
+ "7.494921 8.205644," +
+ "7.767005 8.288978," +
+ "7.948394 8.233422," +
+ "8.016415 8.261200," +
+ "8.197804 8.094533," +
+ "8.084435 7.816756," +
+ "8.152456 7.733422," +
+ "8.175130 7.650089," +
+ "8.175130 7.511200," +
+ "8.311172 7.427867," +
+ "8.311172 7.372311," +
+ "8.651276 7.372311," +
+ "8.923360 7.316756," +
+ "8.900686 7.261200," +
+ "8.809991 7.261200," +
+ "8.472735 7.171122," +
+ "8.333845 7.038978," +
+ "8.282022 6.981100," +
+ "8.254778 6.848911," +
+ "8.265824 6.816756," +
+ "8.239206 6.711211," +
+ "8.219743 6.612067," +
+ "8.130227 6.433044," +
+ "8.084435 6.316756," +
+ "8.107109 6.288978," +
+ "7.948394 6.177867," +
+ "7.925720 5.983422," +
+ "7.857699 5.816756," +
+ "7.835026 5.788978," +
+ "7.857699 5.511200," +
+ "7.812352 5.400089," +
+ "7.812352 5.344533," +
+ "7.812352 5.177867," +
+ "8.084435 4.733422," +
+ "8.107109 4.622311," +
+ "7.857699 4.344533," +
+ "7.630963 4.261200," +
+ "7.540268 4.177867," +
+ "7.494921 4.150089," +
+ "7.449574 4.150089," +
+ "7.404227 4.150089," +
+ "7.336206 4.094533," +
+ "7.313532 4.066756," +
+ "7.041449 4.011200," +
+ "6.905407 3.955644," +
+ "6.950754 3.900089," +
+ "7.200164 3.927867," +
+ "7.630963 3.872311," +
+ "7.721657 3.872311," +
+ "7.948394 3.788978," +
+ "7.993741 3.705644," +
+ "7.971067 3.677867," +
+ "7.925720 3.622311," +
+ "8.175130 3.705644," +
+ "8.401866 3.650089," +
+ "8.492561 3.650089," +
+ "8.605929 3.538978," +
+ "8.651276 3.566756," +
+ "8.855339 3.372311," +
+ "8.900686 3.316756," +
+ "8.900686 3.150089," +
+ "8.787318 2.900089," +
+ "8.787318 2.844533," +
+ "8.946033 2.816756," +
+ "8.991380 2.788978," +
+ "9.014054 2.705644," +
+ "8.886928 2.524989," +
+ "8.832665 2.538978," +
+ "8.809991 2.455644," +
+ "8.923360 2.538978," +
+ "9.014054 2.400089," +
+ "9.308811 2.288978," +
+ "9.399506 2.261200," +
+ "9.512874 2.122311," +
+ "9.535548 1.983422," +
+ "9.512874 1.955644," +
+ "9.467527 1.816756," +
+ "9.036728 1.816756," +
+ "8.991380 1.927867," +
+ "8.946033 1.955644," +
+ "8.900686 1.983422," +
+ "8.946033 2.122311," +
+ "8.968707 2.150089," +
+ "9.195443 1.927867," +
+ "9.354158 1.955644," +
+ "9.376832 2.038978," +
+ "9.376832 2.094533," +
+ "9.240790 2.205644," +
+ "9.195443 2.205644," +
+ "9.263464 2.150089," +
+ "9.240790 2.122311," +
+ "9.195443 2.122311," +
+ "9.104749 2.122311," +
+ "8.900686 2.316756," +
+ "8.787318 2.344533," +
+ "8.696623 2.372311," +
+ "8.651276 2.427867," +
+ "8.719297 2.455644," +
+ "8.787318 2.650089," +
+ "8.832665 2.705644," +
+ "8.605929 2.677867," +
+ "8.537908 2.788978," +
+ "8.333845 2.788978," +
+ "7.925720 2.316756," +
+ "7.925720 2.261200," +
+ "7.903046 2.233422," +
+ "7.857699 2.233422," +
+ "7.857699 2.177867," +
+ "7.789678 1.983422," +
+ "7.812352 1.788978," +
+ "7.948394 1.538978," +
+ "7.971067 1.511200," +
+ "8.129783 1.511200," +
+ "8.243151 1.594533," +
+ "8.333845 1.594533," +
+ "8.424540 1.622311," +
+ "8.515234 1.566756," +
+ "8.673950 1.400089," +
+ "8.771174 1.291756," +
+ "8.828938 1.119878," +
+ "8.762504 0.972544," +
+ "9.238614 0.759633," +
+ "9.492323 0.627022," +
+ "9.820891 0.644711," +
+ "10.376567 0.800622," +
+ "10.651961 1.085978," +
+ "10.762173 1.132022," +
+ "10.943045 1.095989," +
+ "11.256739 0.999878," +
+ "11.576074 0.761611," +
+ "11.768247 0.425211," +
+ "11.960165 0.074778," +
+ "11.953907 0.000000," +
+ "11.629411 0.258767," +
+ "11.229920 0.582278," +
+ "11.001633 0.564300," +
+ "10.868476 0.447478," +
+ "10.633849 0.541833," +
+ "10.513370 0.672133," +
+ "11.188700 0.820078," +
+ "11.194014 0.859656," +
+ "11.118212 0.905822," +
+ "10.874860 0.930311," +
+ "10.427319 0.716522," +
+ "10.023620 0.374211," +
+ "9.434614 0.360144," +
+ "8.455131 0.859544," +
+ "8.180481 0.920500," +
+ "7.902529 1.115078," +
+ "7.823108 1.269800," +
+ "7.830482 1.403778," +
+ "7.791937 1.496744," +
+ "7.767005 1.538978," +
+ "7.676310 1.622311," +
+ "7.653637 1.650089," +
+ "7.585616 1.955644," +
+ "7.562942 1.983422," +
+ "7.562942 2.233422," +
+ "7.608289 2.400089," +
+ "7.630963 2.427867," +
+ "7.608289 2.538978," +
+ "7.585616 2.566756," +
+ "7.653637 2.705644," +
+ "7.630963 2.816756," +
+ "7.336206 3.011200," +
+ "7.290859 3.011200," +
+ "7.245511 3.011200," +
+ "7.041449 2.955644," +
+ "6.928081 2.816756," +
+ "6.928081 2.733422," +
+ "6.905407 2.622311," +
+ "6.860060 2.677867," +
+ "6.814712 2.677867," +
+ "6.678671 2.677867," +
+ "6.678671 2.733422," +
+ "6.769365 2.733422," +
+ "6.814712 2.733422," +
+ "6.792039 2.788978," +
+ "6.293219 3.066756," +
+ "6.225198 3.122311," +
+ "6.202525 3.233422," +
+ "6.134504 3.344533," +
+ "5.907767 3.261200," +
+ "5.862420 3.288978," +
+ "6.043809 3.427867," +
+ "6.021136 3.483422," +
+ "5.975788 3.483422," +
+ "5.930441 3.511200," +
+ "5.953115 3.566756," +
+ "5.975788 3.594533," +
+ "5.749052 3.788978," +
+ "5.703705 3.788978," +
+ "5.635684 3.788978," +
+ "5.703705 3.844533," +
+ "5.703705 4.011200," +
+ "5.499642 4.011200," +
+ "5.862420 4.372311," +
+ "5.975788 4.427867," +
+ "6.021136 4.427867," +
+ "6.089156 4.538978," +
+ "6.111830 4.566756," +
+ "6.089156 4.650089," +
+ "5.998462 4.650089," +
+ "5.817073 4.788978," +
+ "5.771726 4.816756," +
+ "5.681031 4.816756," +
+ "5.749052 4.927867," +
+ "5.749052 5.038978," +
+ "5.839747 5.177867," +
+ "5.998462 5.233422," +
+ "6.225198 5.233422," +
+ "6.270545 5.233422," +
+ "6.383914 5.288978," +
+ "6.406587 5.372311," +
+ "6.429261 5.400089," +
+ "6.587976 5.483422," +
+ "6.670626 5.490000," +
+ "6.700845 5.564100," +
+ "6.860060 5.927867," +
+ "6.860060 6.038978," +
+ "6.950754 6.205644," +
+ "6.973428 6.316756," +
+ "7.041449 6.344533," +
+ "7.064122 6.455644," +
+ "7.116072 6.541989," +
+ "7.114313 6.603667," +
+ "7.025305 6.741422," +
+ "6.736924 6.701367," +
+ "6.641658 6.741467," +
+ "6.500574 6.761389," +
+ "6.435410 6.733422," +
+ "6.224291 6.728556," +
+ "6.191759 6.738989," +
+ "6.099124 6.755000," +
+ "6.041805 6.749733," +
+ "6.001672 6.742967," +
+ "5.905382 6.718300," +
+ "5.817073 6.677867," +
+ "5.611713 6.686622," +
+ "5.401366 6.864333," +
+ "5.386274 6.927867," +
+ "5.356608 6.981811," +
+ "5.404095 7.111822," +
+ "5.561958 7.216133," +
+ "5.660643 7.244722," +
+ "5.366149 7.489478," +
+ "5.340927 7.511200," +
+ "5.114998 7.592867," +
+ "4.870667 7.692033," +
+ "4.746560 7.781856," +
+ "4.708060 7.760867," +
+ "4.692225 7.802500," +
+ "4.607090 7.849044," +
+ "4.481324 7.879711," +
+ "4.340031 8.093378," +
+ "4.181171 8.158044," +
+ "4.116415 8.200800," +
+ "4.081135 8.195278," +
+ "4.090912 8.272500," +
+ "4.032232 8.378311," +
+ "3.779566 8.791278," +
+ "3.769654 8.849022," +
+ "3.598177 8.955178," +
+ "3.576828 9.059633," +
+ "3.527037 9.066756," +
+ "3.498069 9.082022," +
+ "3.541865 9.174211," +
+ "3.542409 9.234411," +
+ "3.576275 9.262711," +
+ "3.582279 9.287744," +
+ "3.390995 9.316756," +
+ "3.209606 9.344533," +
+ "3.100836 9.367511," +
+ "2.957466 9.370756," +
+ "2.870844 9.366222," +
+ "2.777211 9.285222," +
+ "2.744851 9.285900," +
+ "2.775397 9.294867," +
+ "2.832661 9.341156," +
+ "2.868114 9.373300," +
+ "2.869502 9.400089," +
+ "2.794434 9.420178," +
+ "2.714423 9.440078," +
+ "2.641124 9.441944," +
+ "2.572096 9.428378," +
+ "2.548379 9.418600," +
+ "2.573130 9.388211," +
+ "2.563126 9.333567," +
+ "2.535855 9.320067," +
+ "2.517670 9.282778," +
+ "2.479488 9.260278," +
+ "2.483125 9.239067," +
+ "2.464034 9.224278," +
+ "2.468586 9.180556," +
+ "2.443129 9.168989," +
+ "2.439084 9.147456," +
+ "2.448389 9.129344," +
+ "2.444897 9.109600," +
+ "2.450720 9.097256," +
+ "2.444897 9.080389," +
+ "2.447808 9.045822," +
+ "2.424536 9.024011," +
+ "2.415811 9.000133," +
+ "2.442457 8.957422," +
+ "2.429887 8.946567," +
+ "2.455028 8.894556," +
+ "2.435936 8.879078," +
+ "2.413136 8.853411," +
+ "2.410805 8.836944," +
+ "2.412202 8.822133," +
+ "2.387533 8.789544," +
+ "2.386608 8.776044," +
+ "2.398706 8.757278," +
+ "2.373103 8.739511," +
+ "2.387070 8.769467," +
+ "2.375434 8.784611," +
+ "2.358674 8.785922," +
+ "2.337270 8.793167," +
+ "2.365195 8.790533," +
+ "2.399169 8.821478," +
+ "2.396376 8.837933," +
+ "2.408946 8.879078," +
+ "2.432218 8.894878," +
+ "2.414995 8.963022," +
+ "2.390961 8.983722," +
+ "2.340091 8.969389," +
+ "2.332091 8.946244," +
+ "2.340091 8.927722," +
+ "2.332091 8.912289," +
+ "2.316093 8.904067," +
+ "2.311730 8.874744," +
+ "2.288975 8.861244," +
+ "2.247727 8.856233," +
+ "2.233180 8.861889," +
+ "2.209436 8.859233," +
+ "2.231003 8.871144," +
+ "2.265911 8.873200," +
+ "2.277548 8.869600," +
+ "2.290635 8.873711," +
+ "2.299360 8.904578," +
+ "2.268088 8.909622," +
+ "2.247727 8.925256," +
+ "2.225734 8.920756," +
+ "2.208747 8.909622," +
+ "2.203768 8.921811," +
+ "2.214352 8.931822," +
+ "2.197138 8.933811," +
+ "2.148725 8.907478," +
+ "2.134577 8.904844," +
+ "2.113354 8.917222," +
+ "2.095107 8.918800," +
+ "2.079961 8.912944," +
+ "2.060761 8.913356," +
+ "2.034577 8.902656," +
+ "1.983589 8.895400," +
+ "2.033997 8.913356," +
+ "2.062502 8.918700," +
+ "2.092758 8.929811," +
+ "2.148090 8.928756," +
+ "2.168397 8.937878," +
+ "2.146421 8.965533," +
+ "2.182173 8.943933," +
+ "2.201537 8.951311," +
+ "2.239138 8.938400," +
+ "2.267063 8.944989," +
+ "2.284939 8.925767," +
+ "2.306887 8.926022," +
+ "2.311086 8.936356," +
+ "2.296312 8.952489," +
+ "2.317254 8.981122," +
+ "2.334939 9.003844," +
+ "2.374500 9.014044," +
+ "2.386136 9.034778," +
+ "2.401962 9.044656," +
+ "2.418723 9.044889," +
+ "2.426287 9.054878," +
+ "2.411739 9.063522," +
+ "2.426867 9.099311," +
+ "2.398362 9.125233," +
+ "2.373339 9.121944," +
+ "2.403595 9.134289," +
+ "2.417680 9.165778," +
+ "2.425860 9.192778," +
+ "2.423783 9.231400," +
+ "2.400330 9.237022," +
+ "2.419494 9.243567," +
+ "2.429815 9.246711," +
+ "2.449495 9.245489," +
+ "2.457676 9.289856," +
+ "2.481311 9.298211," +
+ "2.488585 9.334211," +
+ "2.520255 9.353822," +
+ "2.520400 9.369944," +
+ "2.494960 9.432511," +
+ "2.463671 9.469200," +
+ "2.406950 9.500578," +
+ "2.240907 9.536433," +
+ "2.129969 9.569467," +
+ "2.031530 9.607422," +
+ "1.932328 9.658044," +
+ "1.835167 9.695656," +
+ "1.746196 9.760744," +
+ "1.667446 9.789667," +
+ "1.575400 9.797622," +
+ "1.562104 9.828722," +
+ "1.531422 9.846800," +
+ "1.415859 9.888744," +
+ "1.315206 9.942167," +
+ "1.175573 10.083667," +
+ "1.147394 10.090267," +
+ "1.118064 10.086567," +
+ "0.990883 9.998400," +
+ "0.778930 9.990856," +
+ "0.592924 10.033144," +
+ "0.507490 10.125422," +
+ "0.419562 10.320811," +
+ "0.375403 10.344533," +
+ "0.276464 10.431189," +
+ "0.220170 10.534911," +
+ "0.181271 10.571000," +
+ "0.153745 10.620156," +
+ "0.114973 10.653889," +
+ "0.103274 10.707756," +
+ "0.097914 10.761511," +
+ "0.076256 10.811522," +
+ "0.061935 10.867833," +
+ "0.000000 10.960167)"
+ );
+ vectorlayer.addFeatures([new OpenLayers.Feature.Vector(original)]);
+ var maxExtent = vectorlayer.getDataExtent();
+ // instanciate the map
+ map = new OpenLayers.Map("map", {
+ fractionalZoom: true,
+ maxExtent: maxExtent,
+ layers: [vectorlayer]
+ });
+ map.zoomToMaxExtent();
+ map.events.register('moveend', map, function(){
+ map2.setCenter(map.getCenter(), map.getZoom());
+ });
+
+
+ var vectorlayer2 = new OpenLayers.Layer.Vector('Vectorlayer simplified', {
+ isBaseLayer: true,
+ styleMap: styleMap
+ });
+
+ map2 = new OpenLayers.Map("map-simplify", {
+ fractionalZoom: true,
+ maxExtent: maxExtent,
+ controls: [],
+ layers: [vectorlayer2]
+ });
+ map2.zoomToExtent(maxExtent);
+
+ // Control behaviour
+ var lastValue = 0.1;
+ var simplify = function() {
+ var min = 0;
+ var max = 1;
+ var givenVal= parseFloat(document.getElementById('tolerance').value);
+ var useVal = lastValue;
+ if (!isNaN(givenVal)) {
+ if (givenVal >= min && givenVal <= max) {
+ useVal = givenVal;
+ } else {
+ useVal = (givenVal < min) ? min : max;
+ }
+ }
+ document.getElementById('tolerance').value = useVal;
+ vectorlayer2.removeFeatures(vectorlayer2.features);
+ var newLineString = original.simplify(useVal);
+ vectorlayer2.addFeatures([new OpenLayers.Feature.Vector(newLineString)]);
+ var originalVerticesCnt = original.getVertices().length;
+ var simplifiedVerticesCnt = newLineString.getVertices().length;
+ var infotxt = '<ul><li>Original LineString: <strong>';
+ infotxt += originalVerticesCnt + ' vertices</strong></li>';
+ infotxt += ' <li>Simplified geometry: <strong>' + simplifiedVerticesCnt + ' vertices</strong></li>';
+ infotxt += ' <li>Decreased by <strong>' + (((originalVerticesCnt-simplifiedVerticesCnt)/originalVerticesCnt)*100).toFixed(2) + ' per cent</strong></li></ul>';
+ document.getElementById('info').innerHTML = infotxt;
+ lastValue = useVal;
+ };
+ document.getElementById('tolerance').value = lastValue;
+ document.getElementById('simplify').onclick = simplify;
+ simplify();
+
+ var animationInterval;
+ var animationHandler = function(){
+ if (this.value === 'Start animation') {
+ document.getElementById('simplify').disabled = true;
+ document.getElementById('animation').value = "Stop animation";
+ animationInterval = window.setInterval(function(){
+ var tolerance = parseFloat(document.getElementById('tolerance').value);
+ if (tolerance < 1) {
+ tolerance+=0.02;
+ } else {
+ tolerance = 0.02;
+ }
+ document.getElementById('tolerance').value = tolerance.toFixed(2);
+ simplify();
+ }, 500);
+ simplify();
+ } else {
+ if (animationInterval) {
+ window.clearInterval(animationInterval);
+ }
+ document.getElementById('simplify').disabled = false;
+ document.getElementById('animation').value = "Start animation";
+ }
+ };
+ document.getElementById('animation').onclick = animationHandler;
+})();
diff --git a/misc/openlayers/examples/single-tile.html b/misc/openlayers/examples/single-tile.html
new file mode 100644
index 0000000..54da081
--- /dev/null
+++ b/misc/openlayers/examples/single-tile.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Single Tile</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">Single Tile Example</h1>
+ <div id="tags">tile, ratio, singleTile, performance, light</div>
+ <p id="shortdesc">
+ Use the singleTile option on gridded layers to request a single tile.
+ </p>
+ <div id="mapDiv" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This map demonstrates the use of the singleTile property as an
+ alternative to the default tiled behavior of layers. The first
+ layer in the map is a WMS layer with the singleTile option set
+ true. The second layer is a WMS layer with the default options.
+ </p>
+ <p>
+ View the <a href="single-tile.js" target="_blank">single-tile.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="single-tile.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/single-tile.js b/misc/openlayers/examples/single-tile.js
new file mode 100644
index 0000000..26d94f0
--- /dev/null
+++ b/misc/openlayers/examples/single-tile.js
@@ -0,0 +1,20 @@
+var map = new OpenLayers.Map({
+ div: "mapDiv",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "Single Tile",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"},
+ {singleTile: true, ratio: 1}
+ ),
+ new OpenLayers.Layer.WMS(
+ "Multiple Tiles",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ )
+ ],
+ center: new OpenLayers.LonLat(6.5, 40.5),
+ zoom: 4
+});
+
+map.addControl(new OpenLayers.Control.LayerSwitcher());
diff --git a/misc/openlayers/examples/sld-parser.html b/misc/openlayers/examples/sld-parser.html
new file mode 100644
index 0000000..14f87ea
--- /dev/null
+++ b/misc/openlayers/examples/sld-parser.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers SLD Parser</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <style>
+ #input {
+ width: 90%;
+ height: 300px;
+ }
+ #output {
+ width: 90%;
+ height: 300px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">SLD Parser</h1>
+ <div id="tags">
+ sld, sldselect, styling, style, parser, cleanup
+ </div>
+ <div id="shortdesc">Parsing Styled Layer Descriptor (SLD) documents with the SLD format.</div>
+
+ <textarea id="input">paste SLD here</textarea><br>
+ <input type="checkbox" id="symbolizers" checked="checked"><label for="symbolizers">Maintain multiple symbolizers and FeatureTypeStyle elements</label><br>
+ <input type="checkbox" id="array"><label for="array">Compile an array of named styles instead of an object.</label><br>
+ <input type="button" id="button" value="Parse SLD">
+
+ <div id="docs">
+ This example uses the SLD format to parse SLD documents pasted into the textarea above.
+ A rough representation of the parsed style is shown in the textarea below.
+ </div>
+
+ <textarea id="output"></textarea>
+
+ <script>
+
+ var button = document.getElementById("button");
+ var input = document.getElementById("input");
+ var output = document.getElementById("output");
+ var symbolizers = document.getElementById("symbolizers");
+ var array = document.getElementById("array");
+
+ var json = new OpenLayers.Format.JSON();
+
+ var format, obj;
+
+ button.onclick = function() {
+ var str = input.value;
+ format = new OpenLayers.Format.SLD({
+ multipleSymbolizers: !!symbolizers.checked,
+ namedLayersAsArray: !!array.checked
+ });
+ obj = format.read(str);
+ try {
+ output.value = json.write(obj, true);
+ } catch (err) {
+ output.value = "Trouble: " + err;
+ }
+ }
+
+ </script>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/sld.html b/misc/openlayers/examples/sld.html
new file mode 100644
index 0000000..4667ba3
--- /dev/null
+++ b/misc/openlayers/examples/sld.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <!--script src="../lib/Firebug/firebug.js"></script-->
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="sld.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Styled Layer Descriptor (SLD) Example</h1>
+ <div id="tags">
+ vector, feature, sld, styling, style
+ </div>
+ <p id="shortdesc">
+ Parsing SLD and applying styles to a vector layer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <p id="docs">This example uses a <a target="_blank" href="tasmania/sld-tasmania.xml">SLD
+ file</a> to style the vector features. To construct layers that use styles
+ from SLD, create a StyleMap for the layer that uses one of the userStyles in the
+ namedLayers object of the return from format.read(). Look at the <a href="sld.js">sld.js source</a>
+ to see how this is done.</p>
+ <p>Select a new style for the WaterBodies layer below:</p>
+ <ul id="style_chooser">
+ </ul>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/sld.js b/misc/openlayers/examples/sld.js
new file mode 100644
index 0000000..888a9c6
--- /dev/null
+++ b/misc/openlayers/examples/sld.js
@@ -0,0 +1,102 @@
+var map, sld, waterBodies;
+var format = new OpenLayers.Format.SLD();
+function init() {
+
+ map = new OpenLayers.Map('map', {allOverlays: true});
+ var layers = createLayers();
+ map.addLayers(layers);
+
+ waterBodies = layers[2];
+ map.addControl(new OpenLayers.Control.SelectFeature(
+ waterBodies, {hover: true, autoActivate: true}
+ ));
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ OpenLayers.Request.GET({
+ url: "tasmania/sld-tasmania.xml",
+ success: complete
+ });
+}
+
+// handler for the OpenLayers.Request.GET function in the init method
+function complete(req) {
+ sld = format.read(req.responseXML || req.responseText);
+ buildStyleChooser();
+ setLayerStyles();
+
+ map.zoomToExtent(new OpenLayers.Bounds(143,-39,150,-45));
+}
+
+function createLayers() {
+ // the name of each layer matches a NamedLayer name in the SLD document
+ var layerData = [{
+ name: "Land",
+ url: "tasmania/TasmaniaStateBoundaries.xml"
+ }, {
+ name: "Roads",
+ url: "tasmania/TasmaniaRoads.xml"
+ }, {
+ name: "WaterBodies",
+ url: "tasmania/TasmaniaWaterBodies.xml"
+ }, {
+ name: "Cities",
+ url: "tasmania/TasmaniaCities.xml"
+ }];
+
+ var layers = [];
+ for (var i=0,ii=layerData.length; i<ii; ++i) {
+ layers.push(new OpenLayers.Layer.Vector(
+ layerData[i].name, {
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: layerData[i].url,
+ format: new OpenLayers.Format.GML.v2()
+ }),
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ // empty style map, will be populated in setLayerStyles
+ styleMap: new OpenLayers.StyleMap()
+ }
+ ));
+ }
+ return layers;
+}
+
+function setLayerStyles() {
+ // set the default style for each layer from sld
+ for (var l in sld.namedLayers) {
+ var styles = sld.namedLayers[l].userStyles, style;
+ for (var i=0,ii=styles.length; i<ii; ++i) {
+ style = styles[i];
+ if (style.isDefault) {
+ map.getLayersByName(l)[0].styleMap.styles["default"] = style;
+ break;
+ }
+ }
+ }
+ // select style for mouseover on WaterBodies objects
+ waterBodies.styleMap.styles.select = sld.namedLayers["WaterBodies"].userStyles[1];
+}
+
+// add a radio button for each userStyle
+function buildStyleChooser() {
+ var styles = sld.namedLayers["WaterBodies"].userStyles;
+ var chooser = document.getElementById("style_chooser"), input, li;
+ for (var i=0,ii=styles.length; i<ii; ++i) {
+ input = document.createElement("input");
+ input.type = "radio";
+ input.name = "style";
+ input.value = i;
+ input.checked = i == 0;
+ input.onclick = function() { setStyle(this.value); };
+ li = document.createElement("li");
+ li.appendChild(input);
+ li.appendChild(document.createTextNode(styles[i].title));
+ chooser.appendChild(li);
+ }
+}
+
+// set a new style when the radio button changes
+function setStyle(index) {
+ waterBodies.styleMap.styles["default"] = sld.namedLayers["WaterBodies"].userStyles[index];
+ // apply the new style of the features of the Water Bodies layer
+ waterBodies.redraw();
+}
diff --git a/misc/openlayers/examples/snap-grid.html b/misc/openlayers/examples/snap-grid.html
new file mode 100644
index 0000000..9d0604b
--- /dev/null
+++ b/misc/openlayers/examples/snap-grid.html
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Snap Grid Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ left: 5px;
+ bottom: 5px;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemInactive {
+ background-position: -1px -1px;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemActive {
+ background-position: -1px -24px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Snap Grid Example</h1>
+
+ <div id="tags">
+ snap grid
+ </div>
+
+ <div id="shortdesc">Use a PointGrid layer and a Snapping control to snap to a grid of regularly spaced points</div>
+
+ <div id="map" class="smallmap"></div>
+
+ Grid rotation:
+ <select name="rotation" id="rotation">
+ <option value="-45">-45</option>
+ <option value="-30">-30</option>
+ <option value="-15">-15</option>
+ <option value="0">0</option>
+ <option value="15">15</option>
+ <option value="30">30</option>
+ <option value="45">45</option>
+ </select>
+
+ &nbsp;
+ Grid spacing:
+ <select name="spacing" id="spacing">
+ <option value="150">150</option>
+ <option value="300">300</option>
+ <option value="600">600</option>
+ <option value="1200">1200</option>
+ <option value="2400">2400</option>
+ </select>
+
+ &nbsp;
+ Max points:
+ <select name="max" id="max">
+ <option value="150">150</option>
+ <option value="250">250</option>
+ <option value="350">350</option>
+ </select>
+
+ <div class="docs">
+ <p>
+ This example demonstrates feature editing with snapping to a regular
+ grid. The map is configured with a <code>OpenLayers.Layer.PointGrid</code>
+ layer and a <code>OpenLayers.Control.Snapping</code> agent. For the
+ best performance, the point grid layer should not made visible.
+ Snapping still works with layers that are not visible.
+ </p><p>
+ See the <a href="snap-grid.js" target="_blank">
+ snap-grid.js source</a> to see how this is done.
+ </p>
+ </div>
+
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="snap-grid.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/snap-grid.js b/misc/openlayers/examples/snap-grid.js
new file mode 100644
index 0000000..4478c5a
--- /dev/null
+++ b/misc/openlayers/examples/snap-grid.js
@@ -0,0 +1,81 @@
+var points = new OpenLayers.Layer.PointGrid({
+ name: "Snap Grid",
+ dx: 600, dy: 600,
+ styleMap: new OpenLayers.StyleMap({
+ pointRadius: 1,
+ strokeColor: "#3333ff",
+ strokeWidth: 1,
+ fillOpacity: 1,
+ fillColor: "#ffffff",
+ graphicName: "square"
+ })
+});
+
+var lines = new OpenLayers.Layer.Vector("Lines", {
+ styleMap: new OpenLayers.StyleMap({
+ pointRadius: 3,
+ strokeColor: "#ff3300",
+ strokeWidth: 3,
+ fillOpacity: 0
+ })
+});
+
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [new OpenLayers.Layer.OSM(), points, lines],
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.LayerSwitcher(),
+ new OpenLayers.Control.Attribution()
+ ],
+ restrictedExtent: new OpenLayers.Bounds(
+ 1035374, 7448940, 1074510, 7468508
+ ),
+ center: new OpenLayers.LonLat(1054942, 7458724),
+ zoom: 13
+});
+
+// configure the snapping agent
+var snap = new OpenLayers.Control.Snapping({
+ layer: lines,
+ targets: [{
+ layer: points,
+ tolerance: 15
+ }]
+});
+snap.activate();
+
+// add some editing tools to a panel
+var panel = new OpenLayers.Control.Panel({
+ displayClass: "olControlEditingToolbar"
+});
+var draw = new OpenLayers.Control.DrawFeature(
+ lines, OpenLayers.Handler.Path,
+ {displayClass: "olControlDrawFeaturePath", title: "Draw Features"}
+);
+modify = new OpenLayers.Control.ModifyFeature(
+ lines, {displayClass: "olControlModifyFeature", title: "Modify Features"}
+);
+panel.addControls([
+ new OpenLayers.Control.Navigation({title: "Navigate"}),
+ modify, draw
+]);
+map.addControl(panel);
+
+var rotation = document.getElementById("rotation");
+rotation.value = String(points.rotation);
+rotation.onchange = function() {
+ points.setRotation(Number(rotation.value));
+};
+
+var spacing = document.getElementById("spacing");
+spacing.value = String(points.dx);
+spacing.onchange = function() {
+ points.setSpacing(Number(spacing.value));
+};
+
+var max = document.getElementById("max");
+max.value = String(points.maxFeatures);
+max.onchange = function() {
+ points.setMaxFeatures(Number(max.value));
+};
diff --git a/misc/openlayers/examples/snap-split.html b/misc/openlayers/examples/snap-split.html
new file mode 100644
index 0000000..13306cc
--- /dev/null
+++ b/misc/openlayers/examples/snap-split.html
@@ -0,0 +1,281 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Snapping &amp; Splitting</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlEditingToolbar .olControlModifyFeatureItemInactive {
+ background-position: -1px -1px;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemActive {
+ background-position: -1px -24px;
+ }
+ label.head {
+ font-weight: bold;
+ padding: 1em 0 0.1em 0;
+ border-bottom: 1px solid gray;
+ }
+ td {
+ padding: 0.25em 1em;
+ }
+ tr.head td {
+ text-align: center;
+ font-weight: bold;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+
+ function init() {
+ initMap();
+ initUI();
+ }
+
+ var map, draw, modify, snap, split, vectors;
+ function initMap() {
+
+ map = new OpenLayers.Map('map');
+ var styles = new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ pointRadius: 5,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#333333"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#666666"
+ }
+ }
+ })
+ ]
+ }),
+ "select": new OpenLayers.Style({
+ strokeColor: "#00ccff",
+ strokeWidth: 4
+ }),
+ "temporary": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ pointRadius: 5,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#333333"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#00ccff"
+ }
+ }
+ })
+ ]
+ })
+ });
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ // create three vector layers
+ vectors = new OpenLayers.Layer.Vector("Lines", {
+ isBaseLayer: true,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "data/roads.json",
+ format: new OpenLayers.Format.GeoJSON()
+ }),
+ styleMap: styles,
+ maxExtent: new OpenLayers.Bounds(
+ 1549471.9221, 6403610.94, 1550001.32545, 6404015.8
+ ),
+ renderers: renderer
+ });
+ map.addLayer(vectors);
+
+ // configure the snapping agent
+ snap = new OpenLayers.Control.Snapping({layer: vectors});
+ map.addControl(snap);
+ snap.activate();
+
+ // configure split agent
+ split = new OpenLayers.Control.Split({
+ layer: vectors,
+ source: vectors,
+ tolerance: 0.0001,
+ eventListeners: {
+ aftersplit: function(event) {
+ flashFeatures(event.features);
+ }
+ }
+ });
+ map.addControl(split);
+ split.activate();
+
+ // add some editing tools to a panel
+ var panel = new OpenLayers.Control.Panel({
+ displayClass: "olControlEditingToolbar"
+ });
+ draw = new OpenLayers.Control.DrawFeature(
+ vectors, OpenLayers.Handler.Path,
+ {displayClass: "olControlDrawFeaturePoint", title: "Draw Features"}
+ );
+ modify = new OpenLayers.Control.ModifyFeature(
+ vectors, {displayClass: "olControlModifyFeature", title: "Modify Features"}
+ );
+ panel.addControls([
+ new OpenLayers.Control.Navigation({title: "Navigate"}),
+ draw, modify
+ ]);
+ map.addControl(panel);
+
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ map.zoomToMaxExtent();
+ }
+
+ function flashFeatures(features, index) {
+ if(!index) {
+ index = 0;
+ }
+ var current = features[index];
+ if(current && current.layer === vectors) {
+ vectors.drawFeature(features[index], "select");
+ }
+ var prev = features[index-1];
+ if(prev && prev.layer === vectors) {
+ vectors.drawFeature(prev, "default");
+ }
+ ++index;
+ if(index <= features.length) {
+ window.setTimeout(function() {flashFeatures(features, index)}, 75);
+ }
+ }
+
+ /**
+ * Add behavior to page elements. This basically lets us set snapping
+ * target properties with the checkboxes and text inputs. The checkboxes
+ * toggle the target node, vertex, or edge (boolean) values. The
+ * text inputs set the nodeTolerance, vertexTolerance, or edgeTolerance
+ * property values.
+ */
+ function initUI() {
+ // add behavior to snap elements
+ var snapCheck = document.getElementById("snap_toggle");
+ snapCheck.checked = true;
+ snapCheck.onclick = function() {
+ if(snapCheck.checked) {
+ snap.activate();
+ document.getElementById("snap_options").style.display = "block";
+ } else {
+ snap.deactivate();
+ document.getElementById("snap_options").style.display = "none";
+ }
+ };
+ var target, type, tog, tol;
+ var types = ["node", "vertex", "edge"];
+ var target = snap.targets[0];
+ for(var j=0; j<types.length; ++j) {
+ type = types[j];
+ tog = document.getElementById("target_" + type);
+ tog.checked = target[type];
+ tog.onclick = (function(tog, type, target) {
+ return function() {target[type] = tog.checked;}
+ })(tog, type, target);
+ tol = document.getElementById("target_" + type + "Tolerance");
+ tol.value = target[type + "Tolerance"];
+ tol.onchange = (function(tol, type, target) {
+ return function() {
+ target[type + "Tolerance"] = Number(tol.value) || 0;
+ }
+ })(tol, type, target);
+ }
+
+ // add behavior to split elements
+ var splitCheck = document.getElementById("split_toggle");
+ splitCheck.checked = true;
+ splitCheck.onclick = function() {
+ if(splitCheck.checked) {
+ split.activate();
+ document.getElementById("split_options").style.display = "block";
+ } else {
+ split.deactivate();
+ document.getElementById("split_options").style.display = "none";
+ }
+ };
+ var edgeCheck = document.getElementById("edge_toggle");
+ edgeCheck.checked = split.edge;
+ edgeCheck.onclick = function() {
+ split.edge = edgeCheck.checked;
+ };
+
+ document.getElementById("clear").onclick = function() {
+ modify.deactivate();
+ vectors.destroyFeatures();
+ };
+
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Snapping & Splitting Example</h1>
+ <div id="tags">
+ vector, feature, splitting, snapping, stylemap, advanced
+ </div>
+ <div id="shortdesc">A demonstration snapping and splitting while editing vector features.</div>
+ <div id="map" class="smallmap"></div>
+ <br>
+ <input type="checkbox" id="snap_toggle" />
+ <label for="snap_toggle" class="head">Enable Snapping</label>
+ <table id="snap_options">
+ <tbody>
+ <tr class="head">
+ <td>target</td><td>node</td><td>vertex</td><td>edge</td>
+ </tr>
+ <tr>
+ <td>roads</td>
+ <td><input type="checkbox" id="target_node" /><input id="target_nodeTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="target_vertex" /><input id="target_vertexTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="target_edge" /><input id="target_edgeTolerance" type="text" size="3" /></td>
+ </tr>
+ </tbody>
+ </table>
+ <br>
+ <input type="checkbox" id="split_toggle" />
+ <label for="split_toggle" class="head">Enable Splitting</label>
+ <table id="split_options">
+ <tbody>
+ <tr>
+ <td><label for="edge_toggle">edges split</label></td>
+ <td><input type="checkbox" id="edge_toggle" /></td>
+ </tr>
+ </tbody>
+ </table>
+ <br>
+ <button id="clear">clear</button> Clear all features.
+ </body>
+</html>
diff --git a/misc/openlayers/examples/snapping.html b/misc/openlayers/examples/snapping.html
new file mode 100644
index 0000000..944b4c2
--- /dev/null
+++ b/misc/openlayers/examples/snapping.html
@@ -0,0 +1,324 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Snapping</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlEditingToolbar .olControlModifyFeatureItemInactive {
+ background-position: -1px -1px;
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemActive {
+ background-position: -1px -24px;
+ }
+ table {
+ padding: 1em 0 1em;
+ }
+ td {
+ padding: 0.5em 1em;
+ border: 1px solid #ddd;
+ }
+ tr.head td {
+ text-align: center;
+ font-weight: bold;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+
+ function init() {
+ initMap();
+ initUI();
+ }
+
+ var map, draw, modify, snap, point, line, poly;
+ function initMap() {
+
+ map = new OpenLayers.Map('map');
+ var styles = new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ pointRadius: 5,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#3333aa"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#6666aa"
+ },
+ "Polygon": {
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ fillColor: "#9999aa",
+ strokeColor: "#6666aa"
+ }
+ }
+ })
+ ]
+ }),
+ "select": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ pointRadius: 5,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 2,
+ strokeOpacity: 1,
+ strokeColor: "#0000ff"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#0000ff"
+ },
+ "Polygon": {
+ strokeWidth: 2,
+ strokeOpacity: 1,
+ fillColor: "#0000ff",
+ strokeColor: "#0000ff"
+ }
+ }
+ })
+ ]
+ }),
+ "temporary": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ graphicName: "square",
+ pointRadius: 5,
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 2,
+ strokeColor: "#0000ff"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#0000ff"
+ },
+ "Polygon": {
+ strokeWidth: 2,
+ strokeOpacity: 1,
+ strokeColor: "#0000ff",
+ fillColor: "#0000ff"
+ }
+ }
+ })
+ ]
+ })
+ });
+
+ // create three vector layers
+ poly = new OpenLayers.Layer.Vector("polygons", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "data/poly.json",
+ format: new OpenLayers.Format.GeoJSON()
+ }),
+ styleMap: styles,
+ isBaseLayer: true
+ });
+ line = new OpenLayers.Layer.Vector("lines", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "data/line.json",
+ format: new OpenLayers.Format.GeoJSON()
+ }),
+ styleMap: styles
+ });
+ point = new OpenLayers.Layer.Vector("points", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "data/point.json",
+ format: new OpenLayers.Format.GeoJSON()
+ }),
+ styleMap: styles
+ });
+ map.addLayers([poly, line, point]);
+
+ // configure the snapping agent
+ snap = new OpenLayers.Control.Snapping({
+ layer: poly,
+ targets: [point, line, poly],
+ greedy: false
+ });
+ snap.activate();
+
+ // add some editing tools to a panel
+ var panel = new OpenLayers.Control.Panel({
+ displayClass: "olControlEditingToolbar"
+ });
+ draw = new OpenLayers.Control.DrawFeature(
+ poly, OpenLayers.Handler.Polygon,
+ {displayClass: "olControlDrawFeaturePoint", title: "Draw Features", handlerOptions: {holeModifier: "altKey"}}
+ );
+ modify = new OpenLayers.Control.ModifyFeature(
+ poly, {displayClass: "olControlModifyFeature", title: "Modify Features"}
+ );
+ panel.addControls([
+ new OpenLayers.Control.Navigation({title: "Navigate"}),
+ draw, modify
+ ]);
+ map.addControl(panel);
+
+ // give the map a location
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ }
+
+ /**
+ * Add behavior to page elements. This basically lets us set snapping
+ * target properties with the checkboxes and text inputs. The checkboxes
+ * toggle the target node, vertex, or edge (boolean) values. The
+ * text inputs set the nodeTolerance, vertexTolerance, or edgeTolerance
+ * property values.
+ */
+ function initUI() {
+ var check = document.getElementById("snapping");
+ check.checked = true;
+ check.onclick = function() {
+ if(check.checked) {
+ snap.activate();
+ } else {
+ snap.deactivate();
+ }
+ };
+
+ var sel = document.getElementById("editable");
+ sel.value = "poly";
+ sel.onchange = function() {
+ updateEditable(sel.value);
+ };
+
+ var target, type, tog, tol;
+ var types = ["node", "vertex", "edge"];
+ for(var i=0; i<snap.targets.length; ++i) {
+ target = snap.targets[i];
+ for(var j=0; j<types.length; ++j) {
+ type = types[j];
+ tog = document.getElementById(i + "_" + type);
+ tog.checked = target[type];
+ tog.onclick = (function(tog, type, target) {
+ return function() {target[type] = tog.checked;}
+ })(tog, type, target);
+ tol = document.getElementById(i + "_" + type + "Tolerance");
+ tol.value = target[type + "Tolerance"];
+ tol.onchange = (function(tol, type, target) {
+ return function() {
+ target[type + "Tolerance"] = Number(tol.value) || 0;
+ }
+ })(tol, type, target);
+ }
+ }
+
+ }
+
+ // this function allows the editable layer to be changed
+ // for the snapping control, this amounts to calling setLayer
+ function updateEditable(name) {
+
+ layer = window[name];
+
+ // update the editable layer for the snapping control (nice)
+ snap.setLayer(layer);
+
+ // update the editable layer for the modify control (ugly)
+ var modActive = modify.active;
+ if(modActive) {
+ modify.deactivate();
+ }
+ modify.layer = layer;
+ modify.selectControl.layer = layer;
+ modify.selectControl.handlers.feature.layer = layer;
+ modify.dragControl.layer = layer;
+ modify.dragControl.handlers.drag.layer = layer;
+ modify.dragControl.handlers.feature.layer = layer;
+ if(modActive) {
+ modify.activate();
+ }
+
+ // update the editable layer for the draw control (very ugly)
+ var drawActive = draw.active;
+ if(drawActive) {
+ draw.deactivate();
+ }
+ draw.layer = layer;
+ var handler = ({
+ point: OpenLayers.Handler.Point,
+ line: OpenLayers.Handler.Path,
+ poly: OpenLayers.Handler.Polygon
+ })[name];
+ draw.handler = new handler(draw, draw.callbacks, draw.handlerOptions);
+ if(drawActive) {
+ draw.activate();
+ }
+
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Snapping Example</h1>
+ <div id="tags">
+ vector, feature, snapping, stylemap, advanced
+ </div>
+ <div id="shortdesc">A demonstration snapping while editing vector features.</div>
+ <div id="map" class="smallmap"></div>
+ <br>
+ <label for="editable">Editable Layer:</label>
+ <select id="editable" name="editable">
+ <option value="poly">polygons</option>
+ <option value="line">lines</option>
+ <option value="point">points</option>
+ </select>
+ <label for="snapping">Enable Snapping</label>
+ <input type="checkbox" name="snapping" id="snapping" checked="checked" />
+ <table>
+ <tbody>
+ <tr class="head">
+ <td>targets</td><td>node</td><td>vertex</td><td>edge</td>
+ </tr>
+ <tr>
+ <td>points</td>
+ <td><input type="checkbox" id="0_node" /><input id="0_nodeTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="0_vertex" /><input id="0_vertexTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="0_edge" /><input id="0_edgeTolerance" type="text" size="3" /></td>
+ </tr>
+ <tr>
+ <td>lines</td>
+ <td><input type="checkbox" id="1_node" /><input id="1_nodeTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="1_vertex" /><input id="1_vertexTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="1_edge" /><input id="1_edgeTolerance" type="text" size="3" /></td>
+ </tr>
+ <tr>
+ <td>polygons</td>
+ <td><input type="checkbox" id="2_node" /><input id="2_nodeTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="2_vertex" /><input id="2_vertexTolerance" type="text" size="3" /></td>
+ <td><input type="checkbox" id="2_edge" /><input id="2_edgeTolerance" type="text" size="3" /></td>
+ </tr>
+ </tbody>
+ </table>
+ <p>Though all snapping types are shown here for all target layers, not all are sensible.
+ Points don't have edges, for example.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/sos.html b/misc/openlayers/examples/sos.html
new file mode 100644
index 0000000..096d19d
--- /dev/null
+++ b/misc/openlayers/examples/sos.html
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>SOS Client Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .sosmap {
+ width: 768px;
+ height: 512px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+ OpenLayers.Util.extend(OpenLayers.Lang.en,
+ {
+ 'SOSClientType': "Type",
+ 'SOSClientTime': "Date/time",
+ 'SOSClientLastvalue': "Last value"
+ }
+ );
+
+ // Example class on how to put all the OpenLayers SOS pieces together
+ OpenLayers.SOSClient = OpenLayers.Class({
+ url: null,
+ map: null,
+ capsformat: new OpenLayers.Format.SOSCapabilities(),
+ obsformat: new OpenLayers.Format.SOSGetObservation(),
+ initialize: function (options) {
+ OpenLayers.Util.extend(this, options);
+ var params = {'service': 'SOS', 'request': 'GetCapabilities'};
+ var paramString = OpenLayers.Util.getParameterString(params);
+ url = OpenLayers.Util.urlAppend(this.url, paramString);
+ OpenLayers.Request.GET({url: url,
+ success: this.parseSOSCaps, scope: this});
+ },
+ getFois: function() {
+ var result = [];
+ this.offeringCount = 0;
+ for (var name in this.SOSCapabilities.contents.offeringList) {
+ var offering = this.SOSCapabilities.contents.offeringList[name];
+ this.offeringCount++;
+ for (var i=0, len=offering.featureOfInterestIds.length; i<len; i++) {
+ var foi = offering.featureOfInterestIds[i];
+ if (OpenLayers.Util.indexOf(result, foi) === -1) {
+ result.push(foi);
+ }
+ }
+ }
+ return result;
+ },
+ parseSOSCaps: function(response) {
+ // cache capabilities for future use
+ this.SOSCapabilities = this.capsformat.read(response.responseXML || response.responseText);
+ this.layer = new OpenLayers.Layer.Vector("Stations", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.SOS({
+ formatOptions: {internalProjection: map.getProjectionObject()},
+ url: this.url,
+ fois: this.getFois()
+ })
+ });
+ this.map.addLayer(this.layer);
+ this.ctrl = new OpenLayers.Control.SelectFeature(this.layer,
+ {scope: this, onSelect: this.onFeatureSelect});
+ this.map.addControl(this.ctrl);
+ this.ctrl.activate();
+ },
+ getTitleForObservedProperty: function(property) {
+ for (var name in this.SOSCapabilities.contents.offeringList) {
+ var offering = this.SOSCapabilities.contents.offeringList[name];
+ if (offering.observedProperties[0] === property) {
+ return offering.name;
+ }
+ }
+ },
+ showPopup: function(response) {
+ this.count++;
+ var output = this.obsformat.read(response.responseXML || response.responseText);
+ if (output.measurements.length > 0) {
+ this.html += '<tr>';
+ this.html += '<td width="100">'+this.getTitleForObservedProperty(output.measurements[0].observedProperty)+'</td>';
+ this.html += '<td>'+output.measurements[0].samplingTime.timeInstant.timePosition+'</td>';
+ this.html += '<td>'+output.measurements[0].result.value + ' ' + output.measurements[0].result.uom + '</td>';
+ this.html += '</tr>';
+ }
+ // check if we are done
+ if (this.count === this.numRequests) {
+ var html = '<table cellspacing="10"><tbody>';
+ html += '<tr>';
+ html += '<th><b>'+OpenLayers.i18n('SOSClientType')+'</b></th>';
+ html += '<th><b>'+OpenLayers.i18n('SOSClientTime')+'</b></th>';
+ html += '<th><b>'+OpenLayers.i18n('SOSClientLastvalue')+'</b></th>';
+ html += '</tr>';
+ html += this.html;
+ html += '</tbody></table>';
+ var popup = new OpenLayers.Popup.FramedCloud("sensor",
+ this.feature.geometry.getBounds().getCenterLonLat(),
+ null,
+ html,
+ null,
+ true,
+ function(e) {
+ this.hide();
+ OpenLayers.Event.stop(e);
+ // unselect so popup can be shown again
+ this.map.getControlsByClass('OpenLayers.Control.SelectFeature')[0].unselectAll();
+ }
+ );
+ this.feature.popup = popup;
+ this.map.addPopup(popup);
+ }
+ },
+ onFeatureSelect: function(feature) {
+ this.feature = feature;
+ this.count = 0;
+ this.html = '';
+ this.numRequests = this.offeringCount;
+ if (!this.responseFormat) {
+ for (format in this.SOSCapabilities.operationsMetadata.GetObservation.parameters.responseFormat.allowedValues) {
+ // look for a text/xml type of format
+ if (format.indexOf('text/xml') >= 0) {
+ this.responseFormat = format;
+ }
+ }
+ }
+ // do a GetObservation request to get the latest values
+ for (var name in this.SOSCapabilities.contents.offeringList) {
+ var offering = this.SOSCapabilities.contents.offeringList[name];
+ var xml = this.obsformat.write({
+ eventTime: 'latest',
+ resultModel: 'om:Measurement',
+ responseMode: 'inline',
+ procedure: feature.attributes.id,
+ offering: name,
+ observedProperties: offering.observedProperties,
+ responseFormat: this.responseFormat
+ });
+ OpenLayers.Request.POST({
+ url: this.url,
+ scope: this,
+ failure: this.showPopup,
+ success: this.showPopup,
+ data: xml
+ });
+ }
+ },
+ destroy: function () {
+ },
+ CLASS_NAME: "OpenLayers.SOSClient"
+ });
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer", "http://vmap0.tiles.osgeo.org/wms/vmap0?", {
+ layers: "basic"}, {singleTile: true});
+
+ var sos = new OpenLayers.SOSClient({map: map, url: 'http://v-swe.uni-muenster.de:8080/WeatherSOS/sos?'});
+
+ map.addLayers([baseLayer]);
+ map.setCenter(new OpenLayers.LonLat(5, 45), 4);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ map.addControl( new OpenLayers.Control.MousePosition() );
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">SOS client example</h1>
+
+ <div id="tags">
+ sos, sensor, observation, popup, advanced
+ </div>
+ <p id="shortdesc">
+ Shows how to connect OpenLayers to a Sensor Observation Service (SOS)
+ </p>
+ <div id="map" class="sosmap"></div>
+ <div id="docs">
+ <p>This example uses a vector layer with a Protocol.SOS and a fixed Strategy.
+ </p><p>When clicking on a point feature (the weather stations offered by the SOS), the
+ latest values for all offerings are displayed in a popup.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/spherical-mercator.html b/misc/openlayers/examples/spherical-mercator.html
new file mode 100644
index 0000000..443ba3b
--- /dev/null
+++ b/misc/openlayers/examples/spherical-mercator.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Spherical Mercator</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlAttribution {
+ bottom: 0px;
+ left: 2px;
+ right: inherit;
+ width: 400px;
+ }
+ /* conditionally position control differently for Google Maps */
+ .olForeignContainer div.olControlMousePosition {
+ bottom: 28px;
+ }
+ #map {
+ height: 512px;
+ }
+ </style>
+
+ <script src="http://maps.google.com/maps/api/js?v=3&amp;sensor=false"></script>
+
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">OpenLayers Spherical Mercator Example</h1>
+
+ <div id="tags">
+ spherical, mercator, osm, xyz, google, virtual earth, tile
+ </div>
+ <p id="shortdesc">
+ Shows the use of the Spherical Mercator Layers, for overlaying
+ Google, Microsoft, and other layers with XYZ tiles.
+ </p>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>Note that maps with Google layers are a special case, because we
+ cannot control the position of the attribution. To conditionally
+ position controls differently for Google layers, prepend the
+ css selector with <code>.olForeignContainer</code>.</p>
+ </div>
+ <script type="text/javascript">
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ displayProjection: "EPSG:4326",
+ numZoomLevels: 18
+ });
+
+ // create Google Mercator layers
+ var gphy = new OpenLayers.Layer.Google(
+ "Google Physical",
+ {type: google.maps.MapTypeId.TERRAIN}
+ );
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", // the default
+ {numZoomLevels: 20}
+ );
+ var ghyb = new OpenLayers.Layer.Google(
+ "Google Hybrid",
+ {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20}
+ );
+ var gsat = new OpenLayers.Layer.Google(
+ "Google Satellite",
+ {type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22}
+ );
+
+ // create Bing layers
+
+ // API key for http://openlayers.org. Please get your own at
+ // http://bingmapsportal.com/ and use that instead.
+ var apiKey = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+
+ var veroad = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "Road",
+ wrapDateLine: true
+ });
+ var veaer = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "Aerial",
+ wrapDateLine: true
+ });
+ var vehyb = new OpenLayers.Layer.Bing({
+ key: apiKey,
+ type: "AerialWithLabels",
+ wrapDateLine: true
+ });
+
+ // create OSM layers
+ var mapnik = new OpenLayers.Layer.OSM();
+
+ // create a vector layer for drawing
+ var vector = new OpenLayers.Layer.Vector("Editable Vectors");
+
+ map.addLayers([
+ gphy, gmap, gsat, ghyb, veroad, veaer, vehyb, mapnik, vector
+ ]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.EditingToolbar(vector));
+ map.addControl(new OpenLayers.Control.Permalink());
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.zoomToMaxExtent();
+
+ </script>
+ </body>
+</html>
+
+
+
diff --git a/misc/openlayers/examples/split-feature.html b/misc/openlayers/examples/split-feature.html
new file mode 100644
index 0000000..7a434ea
--- /dev/null
+++ b/misc/openlayers/examples/split-feature.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Split Feature Example</title>
+
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectors, split;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ // give the features some style
+ var styles = new OpenLayers.StyleMap({
+ "default": {
+ strokeWidth: 2
+ },
+ "select": {
+ strokeColor: "#0099cc",
+ strokeWidth: 4
+ }
+ });
+
+ // add rules from the above lookup table
+ styles.addUniqueValueRules("default", "RP_TYPE", {
+ 10: {strokeColor: "#000000", strokeWidth: 2},
+ 12: {strokeColor: "#222222", strokeWidth: 2},
+ 14: {strokeColor: "#444444", strokeWidth: 2},
+ 16: {strokeColor: "#666666", strokeWidth: 2},
+ 18: {strokeColor: "#888888", strokeWidth: 2},
+ 19: {strokeColor: "#666666", strokeWidth: 1}
+ });
+
+ vectors = new OpenLayers.Layer.Vector("Lines", {
+ isBaseLayer: true,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "data/roads.json",
+ format: new OpenLayers.Format.GeoJSON()
+ }),
+ styleMap: styles,
+ maxExtent: new OpenLayers.Bounds(
+ 1549471.9221, 6403610.94, 1550001.32545, 6404015.8
+ )
+ });
+
+
+
+
+ map.addLayer(vectors);
+
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ var split = new OpenLayers.Control.Split({
+ layer: vectors,
+ eventListeners: {
+ aftersplit: function(event) {
+ flashFeatures(event.features);
+ }
+ }
+ });
+ map.addControl(split);
+
+ map.zoomToMaxExtent();
+
+ split.activate();
+ }
+
+ function flashFeatures(features, index) {
+ if(!index) {
+ index = 0;
+ }
+ var current = features[index];
+ if(current && current.layer === vectors) {
+ vectors.drawFeature(features[index], "select");
+ }
+ var prev = features[index-1];
+ if(prev && prev.layer === vectors) {
+ vectors.drawFeature(prev, "default");
+ }
+ ++index;
+ if(index <= features.length) {
+ window.setTimeout(function() {flashFeatures(features, index)}, 100);
+ }
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Split Feature Example</h1>
+ <div id="tags">
+ vector, feature, splitting, split, stylemap
+ </div>
+ <p id="shortdesc">
+ Demonstrates splitting of line features.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The split control can be configured to listen for edits on any vector layer
+ or it can allow for creation of temporary sketch features. Modified or
+ newly drawn features will be used to split existing features on any target
+ layer. This example shows the split control configured to use temporary
+ sketches for the split.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/strategy-bbox.html b/misc/openlayers/examples/strategy-bbox.html
new file mode 100644
index 0000000..1674113
--- /dev/null
+++ b/misc/openlayers/examples/strategy-bbox.html
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers BBOX Strategy Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, photos;
+
+ /**
+ * A specific format for parsing Flickr API JSON responses.
+ */
+ OpenLayers.Format.Flickr = OpenLayers.Class(OpenLayers.Format, {
+ read: function(obj) {
+ if(obj.stat === 'fail') {
+ throw new Error(
+ ['Flickr failure response (',
+ obj.code,
+ '): ',
+ obj.message].join(''));
+ }
+ if(!obj || !obj.photos ||
+ !OpenLayers.Util.isArray(obj.photos.photo)) {
+ throw new Error(
+ 'Unexpected Flickr response');
+ }
+ var photos = obj.photos.photo, photo,
+ x, y, point,
+ feature, features = [];
+ for(var i=0,l=photos.length; i<l; i++) {
+ photo = photos[i];
+ x = photo.longitude;
+ y = photo.latitude;
+ point = new OpenLayers.Geometry.Point(x, y);
+ feature = new OpenLayers.Feature.Vector(point, {
+ title: photo.title,
+ img_url: photo.url_s
+ });
+ features.push(feature);
+ }
+ return features;
+ }
+ });
+
+ function init() {
+ map = new OpenLayers.Map('map');
+
+ var base = new OpenLayers.Layer.OSM();
+
+ var style = new OpenLayers.Style({
+ externalGraphic: "${img_url}",
+ pointRadius: 30
+ });
+
+ photos = new OpenLayers.Layer.Vector("Photos", {
+ projection: "EPSG:4326",
+ strategies: [new OpenLayers.Strategy.BBOX({resFactor: 1})],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "http://api.flickr.com/services/rest",
+ params: {
+ api_key: 'b5e8c0e287e678671c3d8b2c0f3ced85',
+ format: 'json',
+ method: 'flickr.photos.search',
+ extras: 'geo,url_s',
+ per_page: 10,
+ page: 1
+ },
+ callbackKey: 'jsoncallback',
+ format: new OpenLayers.Format.Flickr()
+ }),
+ styleMap: new OpenLayers.StyleMap(style)
+ });
+
+ map.addLayers([base, photos]);
+ map.setCenter(
+ new OpenLayers.LonLat(-567468.5392481,
+ 4950672.5471436), 5);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">BBOX Strategy Example</h1>
+ <div id="tags">
+ vector, feature, stylemap, bbox, strategy, script, flickr
+ </div>
+ <p id="shortdesc">
+ Uses a BBOX strategy to request features within a bounding box.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The BBOX strategy requests data within a bounding box. When the
+ previously requested data bounds are invalidated (by browsing to
+ some area not covered by those bounds), another request for data
+ is issued.</p>
+
+ <p>This particular example uses the <a
+ href="http://www.flickr.com/services/api/">Flickr API.</a></p>
+
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/strategy-cluster-extended.html b/misc/openlayers/examples/strategy-cluster-extended.html
new file mode 100644
index 0000000..51d3e87
--- /dev/null
+++ b/misc/openlayers/examples/strategy-cluster-extended.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Extended clustering example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ label {
+ cursor: pointer
+ }
+
+ #wrap {
+ width: 925px;
+ margin: 10px;
+ }
+
+ #strategy-chooser, #generalinfo, #info {
+ width: 400px;
+ padding: 0;
+ float: right;
+ clear: right;
+ margin-bottom: 4px;
+ }
+
+ #map {
+ float: left;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Extended clustering</h1>
+ <div id="tags">
+ cluster, advanced
+ </div>
+ <p id="shortdesc">
+ Shows the usage of custom classes for a fine grained control about
+ the clustering behaviour.
+ </p>
+ <div id="wrap">
+ <div id="map" class="smallmap">
+ </div>
+ <div id="strategy-chooser">
+ <p>
+ Select the desired clustering strategy:
+ </p>
+ <label>
+ <input type="radio" name="strategy" value="none" id="no-strategy" checked="checked">No strategy
+ </label>
+ <br>
+ <label>
+ <input type="radio" name="strategy" value="cluster" id="cluster-strategy">Simple cluster-strategy
+ </label>
+ <br>
+ <label>
+ <input type="radio" name="strategy" value="attribute-cluster" id="attributive-cluster-strategy">Attributive cluster-strategy
+ </label>
+ <br>
+ <label>
+ <input type="radio" name="strategy" value="rule-cluster" id="rulebased-cluster-strategy">Rulebased cluster-strategy
+ </label>
+ </div>
+ <div id="generalinfo">
+ </div>
+ <div id="info">
+ </div>
+ </div>
+ <div id="docs" style="clear: both; padding-top: 10px">
+ <p>
+ The vectorlayer in this example contains random data with an
+ attribute "clazz" that can take the values 1, 2, 3 and 4. The
+ features with clazz = 4 are considered more important than the
+ others.
+ </p>
+ <p>
+ The radiobuttons on the right of the map control the
+ cluster strategy to be applied to the features.
+ </p>
+ <ul>
+ <li>
+ <strong>No strategy</strong>
+ means that all features are
+ rendered, no clustering shall be applied
+ </li>
+ <li>
+ <strong>Simple cluster-strategy</strong>
+ applies the cluster
+ strategy with default options to the layer. You should notice
+ that many of the important features with clazz = 4 are getting
+ lost, since clustering happens regardless of feature attributes
+ </li>
+ <li>
+ <strong>Attributive cluster-strategy</strong>
+ uses a
+ customized cluster strategy. This strategy is configured to
+ cluster features of the same clazz only. You should be able to see all
+ red points (clazz = 4) even though the data is clustered. A
+ cluster now contains only features of the same clazz.
+ </li>
+ <li>
+ <strong>Rulebased cluster-strategy</strong>
+ uses another
+ customized cluster strategy. This strategy is configured to
+ cluster features that follow a certain rule only. In this case only
+ features with a clazz different from 4 are considered as
+ candidates for clustering. That means that usually you have fewer
+ clusters on the map, yet all with clazz = 4 are easily
+ distinguishable
+ </li>
+ </ul>
+ <p>
+ Hover over the features to get a short infomation about the
+ feature or cluster of features.
+ </p>
+ </div>
+ <p>
+ View the <a href="strategy-cluster-extended.js" target="_blank">strategy-cluster-extended.js</a>
+ source to see how this is done.
+ </p>
+ <script type="text/javascript" src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript" src="strategy-cluster-extended.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/strategy-cluster-extended.js b/misc/openlayers/examples/strategy-cluster-extended.js
new file mode 100644
index 0000000..163cbf4
--- /dev/null
+++ b/misc/openlayers/examples/strategy-cluster-extended.js
@@ -0,0 +1,247 @@
+/**
+ * Class: OpenLayers.Strategy.AttributeCluster
+ * Strategy for vector feature clustering based on feature attributes.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy.Cluster>
+ */
+OpenLayers.Strategy.AttributeCluster = OpenLayers.Class(OpenLayers.Strategy.Cluster, {
+ /**
+ * the attribute to use for comparison
+ */
+ attribute: null,
+ /**
+ * Method: shouldCluster
+ * Determine whether to include a feature in a given cluster.
+ *
+ * Parameters:
+ * cluster - {<OpenLayers.Feature.Vector>} A cluster.
+ * feature - {<OpenLayers.Feature.Vector>} A feature.
+ *
+ * Returns:
+ * {Boolean} The feature should be included in the cluster.
+ */
+ shouldCluster: function(cluster, feature) {
+ var cc_attrval = cluster.cluster[0].attributes[this.attribute];
+ var fc_attrval = feature.attributes[this.attribute];
+ var superProto = OpenLayers.Strategy.Cluster.prototype;
+ return cc_attrval === fc_attrval &&
+ superProto.shouldCluster.apply(this, arguments);
+ },
+ CLASS_NAME: "OpenLayers.Strategy.AttributeCluster"
+});
+
+/**
+ * Class: OpenLayers.Strategy.RuleCluster
+ * Strategy for vector feature clustering according to a given rule.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy.Cluster>
+ */
+OpenLayers.Strategy.RuleCluster = OpenLayers.Class(OpenLayers.Strategy.Cluster, {
+ /**
+ * the rule to use for comparison
+ */
+ rule: null,
+ /**
+ * Method: shouldCluster
+ * Determine whether to include a feature in a given cluster.
+ *
+ * Parameters:
+ * cluster - {<OpenLayers.Feature.Vector>} A cluster.
+ * feature - {<OpenLayers.Feature.Vector>} A feature.
+ *
+ * Returns:
+ * {Boolean} The feature should be included in the cluster.
+ */
+ shouldCluster: function(cluster, feature) {
+ var superProto = OpenLayers.Strategy.Cluster.prototype;
+ return this.rule.evaluate(cluster.cluster[0]) &&
+ this.rule.evaluate(feature) &&
+ superProto.shouldCluster.apply(this, arguments);
+ },
+ CLASS_NAME: "OpenLayers.Strategy.RuleCluster"
+});
+
+
+// global variables
+var map, vectorlayer, features, stylemap, select;
+
+// wrap the instanciation code in an anonymous function that gets executed
+// immeadeately
+(function(){
+
+ // The function that gets called on feature selection: shows information
+ // about the feature/cluser in a div on the page
+ var showInformation = function(evt){
+ var feature = evt.feature;
+ var info = 'Last hovered feature:<br>';
+ if (feature.cluster) {
+ info += '&nbsp;&nbsp;Cluster of ' + feature.attributes.count + ' features:';
+ var clazzes = {
+ '1': 0,
+ '2': 0,
+ '3': 0,
+ '4': 0
+ };
+ for (var i = 0; i < feature.attributes.count; i++) {
+ var feat = feature.cluster[i];
+ clazzes[feat.attributes.clazz]++;
+ }
+ for (var j=1; j<=4; j++) {
+ var plural_s = (clazzes[j] !== 1) ? 's' : '';
+ info += '<br>&nbsp;&nbsp;&nbsp;&nbsp;&bull;&nbsp;clazz ' + j + ': ' + clazzes[j] + ' feature' + plural_s;
+ }
+ } else {
+ info += '&nbsp;&nbsp;Single feature of clazz = ' + feature.attributes.clazz;
+ }
+ document.getElementById('info').innerHTML = info;
+ };
+
+ // The function that gets called on feature selection. Shows information
+ // about the number of "points" on the map.
+ var updateGeneralInformation = function() {
+ var info = 'Currently ' + vectorlayer.features.length + ' points are shown on the map.';
+ document.getElementById('generalinfo').innerHTML = info;
+ };
+
+ // instanciate the map
+ map = new OpenLayers.Map("map");
+
+ // background WMS
+ var ol_wms = new OpenLayers.Layer.WMS("OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0", {
+ layers: "basic"
+ });
+
+ // context to style the vectorlayer
+ var context = {
+ getColor: function(feature){
+ var color = '#aaaaaa';
+ if (feature.attributes.clazz && feature.attributes.clazz === 4) {
+ color = '#ee0000';
+ } else if(feature.cluster) {
+ var onlyFour = true;
+ for (var i = 0; i < feature.cluster.length; i++) {
+ if (onlyFour && feature.cluster[i].attributes.clazz !== 4) {
+ onlyFour = false;
+ }
+ }
+ if (onlyFour === true) {
+ color = '#ee0000';
+ }
+ }
+ return color;
+ }
+ };
+
+ // style the vectorlayer
+ stylemap = new OpenLayers.StyleMap({
+ 'default': new OpenLayers.Style({
+ pointRadius: 5,
+ fillColor: "${getColor}",
+ fillOpacity: 0.7,
+ strokeColor: "#666666",
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ graphicZIndex: 1
+ }, {
+ context: context
+ }),
+ 'select' : new OpenLayers.Style({
+ pointRadius: 5,
+ fillColor: "#ffff00",
+ fillOpacity: 1,
+ strokeColor: "#666666",
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ graphicZIndex: 2
+ })
+ });
+
+ // the vectorlayer
+ vectorlayer = new OpenLayers.Layer.Vector('Vectorlayer', {styleMap: stylemap, strategies: []});
+
+ // the select control
+ select = new OpenLayers.Control.SelectFeature(
+ vectorlayer, {hover: true}
+ );
+ map.addControl(select);
+ select.activate();
+ vectorlayer.events.on({"featureselected": showInformation});
+
+ map.addLayers([ol_wms, vectorlayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToMaxExtent();
+
+ features = [];
+ // adding lots of features:
+ for (var i = 0; i < 700; i++) {
+ var r1 = Math.random();
+ var r2 = Math.random();
+ var r3 = Math.random();
+ var r4 = Math.random();
+ var px = r1 * 180 * ((r2 < 0.5) ? -1 : 1);
+ var py = r3 * 90 * ((r4 < 0.5) ? -1 : 1);
+ var p = new OpenLayers.Geometry.Point(px, py);
+ var clazz = (i % 10 === 0) ? 4 : Math.ceil(r4 * 3);
+ var f = new OpenLayers.Feature.Vector(p, {clazz: clazz});
+ features.push(f);
+ }
+ vectorlayer.addFeatures(features);
+ updateGeneralInformation();
+
+ // the behaviour and methods for the radioboxes
+ var changeStrategy = function() {
+ var strategies = [];
+ // this is the checkbox
+ switch(this.value) {
+ case 'cluster':
+ // standard clustering
+ strategies.push(new OpenLayers.Strategy.Cluster());
+ break;
+ case 'attribute-cluster':
+ // use the custom class: only cluster features of the same clazz
+ strategies.push(new OpenLayers.Strategy.AttributeCluster({
+ attribute:'clazz'
+ }));
+ break;
+ case 'rule-cluster':
+ // use the custom class: only cluster features that have a
+ // clazz smaller than 4
+ strategies.push(new OpenLayers.Strategy.RuleCluster({
+ rule: new OpenLayers.Rule({
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "clazz",
+ value: 4
+ })
+ })
+ }));
+ break;
+ }
+ // remove layer and control
+ map.removeLayer(vectorlayer);
+ map.removeControl(select);
+ // rebuild layer
+ vectorlayer = new OpenLayers.Layer.Vector('Vectorlayer', {styleMap: stylemap, strategies: strategies});
+ map.addLayer( vectorlayer );
+ vectorlayer.addFeatures(features);
+ // rebuild select control
+ select = new OpenLayers.Control.SelectFeature(
+ vectorlayer, {hover: true}
+ );
+ map.addControl(select);
+ select.activate();
+ vectorlayer.events.on({"featureselected": showInformation});
+ // update meta information
+ updateGeneralInformation();
+ };
+ // bind the behviour to the radios
+ var inputs = document.getElementsByTagName('input');
+ for( var cnt = 0; cnt < inputs.length; cnt++) {
+ var input = inputs[cnt];
+ if (input.name === 'strategy') {
+ input.onclick = changeStrategy;
+ }
+ }
+})();
diff --git a/misc/openlayers/examples/strategy-cluster-threshold.html b/misc/openlayers/examples/strategy-cluster-threshold.html
new file mode 100644
index 0000000..a47e08d
--- /dev/null
+++ b/misc/openlayers/examples/strategy-cluster-threshold.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Cluster Strategy Threshold</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ ul {
+ list-style: none;
+ padding-left: 2em;
+ }
+ #reset {
+ margin-left: 2em;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ // create a semi-random grid of features to be clustered
+ var dx = 3;
+ var dy = 3;
+ var px, py;
+ var features = [];
+ for(var x=-45; x<=45; x+=dx) {
+ for(var y=-22.5; y<=22.5; y+=dy) {
+ px = x + (2 * dx * (Math.random() - 0.5));
+ py = y + (2 * dy * (Math.random() - 0.5));
+ features.push(new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(px, py), {x: px, y: py}
+ ));
+ }
+ }
+
+ var map, strategy, clusters;
+ function init() {
+ map = new OpenLayers.Map('map');
+ var base = new OpenLayers.Layer.WMS("OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var style = new OpenLayers.Style({
+ pointRadius: "${radius}",
+ fillColor: "#ffcc66",
+ fillOpacity: 0.8,
+ strokeColor: "#cc6633",
+ strokeWidth: "${width}",
+ strokeOpacity: 0.8
+ }, {
+ context: {
+ width: function(feature) {
+ return (feature.cluster) ? 2 : 1;
+ },
+ radius: function(feature) {
+ var pix = 2;
+ if(feature.cluster) {
+ pix = Math.min(feature.attributes.count, 7) + 2;
+ }
+ return pix;
+ }
+ }
+ });
+
+ strategy = new OpenLayers.Strategy.Cluster();
+
+ clusters = new OpenLayers.Layer.Vector("Clusters", {
+ strategies: [strategy],
+ styleMap: new OpenLayers.StyleMap({
+ "default": style,
+ "select": {
+ fillColor: "#8aeeef",
+ strokeColor: "#32a8a9"
+ }
+ })
+ });
+
+ var select = new OpenLayers.Control.SelectFeature(
+ clusters, {hover: true}
+ );
+ map.addControl(select);
+ select.activate();
+ clusters.events.on({"featureselected": display});
+
+ map.addLayers([base, clusters]);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+
+ reset();
+ document.getElementById("reset").onclick = reset;
+
+ }
+
+ function reset() {
+ var distance = parseInt(document.getElementById("distance").value);
+ var threshold = parseInt(document.getElementById("threshold").value);
+ strategy.distance = distance || strategy.distance;
+ strategy.threshold = threshold || strategy.threshold;
+ document.getElementById("distance").value = strategy.distance;
+ document.getElementById("threshold").value = strategy.threshold || "null";
+ clusters.removeFeatures(clusters.features);
+ clusters.addFeatures(features);
+ }
+
+ function display(event) {
+ var f = event.feature;
+ var el = document.getElementById("output");
+ if(f.cluster) {
+ el.innerHTML = "cluster of " + f.attributes.count;
+ } else {
+ el.innerHTML = "unclustered " + f.geometry;
+ }
+ }
+
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Cluster Strategy Threshold</h1>
+ <div id="tags">
+ vector, feature, stylemap, wfs, cluster, strategy, cleanup
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of the cluster strategy threshold property.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The Cluster strategy lets you display points representing clusters
+ of features within some pixel distance. You can control the behavior
+ of the cluster strategy by setting its distance and threshold properties.
+ The distance determines the search radius (in pixels) for features to
+ cluster. The threshold determines the minimum number of features to
+ be considered a cluster.</p>
+ </div>
+ <br>
+ <p>Cluster details: <span id="output">hover over a feature to see details.</span></p>
+ <ul>
+ <li>
+ <input id="distance" name="distance" type="text" value="" size="3" />
+ <label for="distance">distance</label>
+ </li>
+ <li>
+ <input id="threshold" name="threshold" type="text" value="" size="3" />
+ <label for="threshold">threshold</label>
+ </li>
+ </ul>
+ <button id="reset">recluster</button>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/strategy-cluster.html b/misc/openlayers/examples/strategy-cluster.html
new file mode 100644
index 0000000..d695f71
--- /dev/null
+++ b/misc/openlayers/examples/strategy-cluster.html
@@ -0,0 +1,238 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Cluster Strategy Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #photos {
+ height: 100px;
+ width: 512px;
+ position: relative;
+ white-space: nowrap;
+ }
+ .shift {
+ height: 25px;
+ line-height: 25px;
+ background-color: #fefefe;
+ text-align: center;
+ position: absolute;
+ bottom: 10px;
+ font-size: 8px;
+ font-weight: bold;
+ color: #696969;
+ width: 25px;
+ }
+ #scroll-start {
+ left: 0px;
+ }
+ #scroll-end {
+ right: 0px;
+ }
+ #scroll {
+ left: 30px;
+ width: 452px;
+ height: 100px;
+ overflow: hidden;
+ position: absolute;
+ bottom: 0px;
+ }
+ #photos ul {
+ position: absolute;
+ bottom: 0px;
+ padding: 0;
+ margin: 0;
+ }
+ #photos ul.start {
+ left: 0px;
+ }
+ #photos ul.end {
+ right: 80px;
+ }
+ #photos ul li {
+ padding: 10px;
+ margin: 0;
+ list-style: none;
+ display: inline;
+ }
+ img.thumb {
+ height: 30px;
+ }
+ img.big {
+ height: 90px;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="Jugl.js"></script>
+ <script src="animator.js"></script>
+ <script type="text/javascript">
+
+ var map, template;
+
+ /**
+ * A specific format for parsing Flickr API JSON responses.
+ */
+ OpenLayers.Format.Flickr = OpenLayers.Class(OpenLayers.Format, {
+ read: function(obj) {
+ if(obj.stat === 'fail') {
+ throw new Error(
+ ['Flickr failure response (',
+ obj.code,
+ '): ',
+ obj.message].join(''));
+ }
+ if(!obj || !obj.photos ||
+ !OpenLayers.Util.isArray(obj.photos.photo)) {
+ throw new Error(
+ 'Unexpected Flickr response');
+ }
+ var photos = obj.photos.photo, photo,
+ x, y, point,
+ feature, features = [];
+ for(var i=0,l=photos.length; i<l; i++) {
+ photo = photos[i];
+ x = photo.longitude;
+ y = photo.latitude;
+ point = new OpenLayers.Geometry.Point(x, y);
+ feature = new OpenLayers.Feature.Vector(point, {
+ title: photo.title,
+ img_url: photo.url_s
+ });
+ features.push(feature);
+ }
+ return features;
+ }
+ });
+
+ function init() {
+ map = new OpenLayers.Map('map');
+ var base = new OpenLayers.Layer.OSM();
+
+ var style = new OpenLayers.Style({
+ pointRadius: "${radius}",
+ fillColor: "#ffcc66",
+ fillOpacity: 0.8,
+ strokeColor: "#cc6633",
+ strokeWidth: 2,
+ strokeOpacity: 0.8
+ }, {
+ context: {
+ radius: function(feature) {
+ return Math.min(feature.attributes.count, 7) + 3;
+ }
+ }
+ });
+
+ var photos = new OpenLayers.Layer.Vector("Photos", {
+ projection: "EPSG:4326",
+ strategies: [
+ new OpenLayers.Strategy.Fixed(),
+ new OpenLayers.Strategy.Cluster()
+ ],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "http://api.flickr.com/services/rest",
+ params: {
+ api_key: 'b5e8c0e287e678671c3d8b2c0f3ced85',
+ format: 'json',
+ method: 'flickr.photos.search',
+ extras: 'geo,url_s',
+ per_page: 150,
+ page: 1,
+ bbox: [-180, -90, 180, 90]
+ },
+ callbackKey: 'jsoncallback',
+ format: new OpenLayers.Format.Flickr()
+ }),
+ styleMap: new OpenLayers.StyleMap({
+ "default": style,
+ "select": {
+ fillColor: "#8aeeef",
+ strokeColor: "#32a8a9"
+ }
+ })
+ });
+
+ var select = new OpenLayers.Control.SelectFeature(
+ photos, {hover: true}
+ );
+ map.addControl(select);
+ select.activate();
+ photos.events.on({"featureselected": display});
+
+ map.addLayers([base, photos]);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+
+ // template setup
+ template = new jugl.Template("template");
+
+ }
+
+ function display(event) {
+ // clear previous photo list and create new one
+ document.getElementById("photos").innerHTML = "";
+ var node = template.process({
+ context: {features: event.feature.cluster},
+ clone: true,
+ parent: document.getElementById("photos")
+ });
+ // set up forward/rewind
+ var forward = Animator.apply(document.getElementById("list"), ["start", "end"], {duration: 1500});
+ document.getElementById("scroll-end").onmouseover = function() {forward.seekTo(1)};
+ document.getElementById("scroll-end").onmouseout = function() {forward.seekTo(forward.state)};
+ document.getElementById("scroll-start").onmouseover = function() {forward.seekTo(0)};
+ document.getElementById("scroll-start").onmouseout = function() {forward.seekTo(forward.state)};
+ // set up photo zoom
+ for(var i=0; i<event.feature.cluster.length; ++i) {
+ listen(document.getElementById("link-" + i), Animator.apply(document.getElementById("photo-" + i), ["thumb", "big"]));
+ }
+ }
+
+ function listen(el, anim) {
+ el.onmouseover = function() {anim.seekTo(1)};
+ el.onmouseout = function() {anim.seekTo(0)};
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Cluster Strategy Example</h1>
+ <div id="tags">
+ vector, feature, stylemap, cluster, strategy, flickr, script
+ </div>
+ <p id="shortdesc">
+ Uses a cluster strategy to render points representing clusters of features.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The Cluster strategy lets you display points representing clusters
+ of features within some pixel distance.</p>
+ <p>This particular example uses the <a
+ href="http://www.flickr.com/services/api/">Flickr API.</a></p>
+ </div>
+ <div id="photos"></div>
+ <p>Hover over a cluster on the map to see the photos it includes.</p>
+ <div style="display: none;">
+ <div id="template">
+ <div class="shift" id="scroll-start">&lt;&lt;</div>
+ <div id="scroll">
+ <ul id="list" class="start">
+ <li jugl:repeat="feature features">
+ <a jugl:attributes="href feature.attributes.img_url;
+ id 'link-' + repeat.feature.index"
+ target="_blank">
+ <img jugl:attributes="src feature.attributes.img_url;
+ title feature.attributes.title;
+ id 'photo-' + repeat.feature.index"
+ class="thumb" />
+ </a>
+ </li>
+ </ul>
+ </div>
+ <div class="shift" id="scroll-end">&gt;&gt;</div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/strategy-paging.html b/misc/openlayers/examples/strategy-paging.html
new file mode 100644
index 0000000..204bac9
--- /dev/null
+++ b/misc/openlayers/examples/strategy-paging.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Paging Strategy Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, photos, paging;
+
+ /**
+ * A specific format for parsing Flickr API JSON responses.
+ */
+ OpenLayers.Format.Flickr = OpenLayers.Class(OpenLayers.Format, {
+ read: function(obj) {
+ if(obj.stat === 'fail') {
+ throw new Error(
+ ['Flickr failure response (',
+ obj.code,
+ '): ',
+ obj.message].join(''));
+ }
+ if(!obj || !obj.photos ||
+ !OpenLayers.Util.isArray(obj.photos.photo)) {
+ throw new Error(
+ 'Unexpected Flickr response');
+ }
+ var photos = obj.photos.photo, photo,
+ x, y, point,
+ feature, features = [];
+ for(var i=0,l=photos.length; i<l; i++) {
+ photo = photos[i];
+ x = photo.longitude;
+ y = photo.latitude;
+ point = new OpenLayers.Geometry.Point(x, y);
+ feature = new OpenLayers.Feature.Vector(point, {
+ title: photo.title,
+ img_url: photo.url_s
+ });
+ features.push(feature);
+ }
+ return features;
+ }
+ });
+
+ function init() {
+ map = new OpenLayers.Map('map');
+ var base = new OpenLayers.Layer.OSM();
+
+ var style = new OpenLayers.Style({
+ externalGraphic: "${img_url}",
+ pointRadius: 30
+ });
+
+ paging = new OpenLayers.Strategy.Paging();
+
+ photos = new OpenLayers.Layer.Vector("Photos", {
+ projection: "EPSG:4326",
+ strategies: [new OpenLayers.Strategy.Fixed(), paging],
+ protocol: new OpenLayers.Protocol.Script({
+ url: "http://api.flickr.com/services/rest",
+ params: {
+ api_key: 'b5e8c0e287e678671c3d8b2c0f3ced85',
+ format: 'json',
+ method: 'flickr.photos.search',
+ extras: 'geo,url_s',
+ per_page: 100,
+ page: 1,
+ bbox: [-180, -90, 180, 90]
+ },
+ callbackKey: 'jsoncallback',
+ format: new OpenLayers.Format.Flickr()
+ }),
+ styleMap: new OpenLayers.StyleMap(style)
+ });
+
+ map.addLayers([base, photos]);
+ photos.events.on({"featuresadded": updateButtons});
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ }
+
+ function updateButtons() {
+ document.getElementById("prev").disabled = (paging.pageNum() < 1);
+ document.getElementById("next").disabled = (paging.pageNum() >= paging.pageCount() - 1);
+ document.getElementById("num").innerHTML = paging.pageNum() + 1;
+ document.getElementById("count").innerHTML = paging.pageCount();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Paging Strategy Example</h1>
+ <div id="tags">
+ vector, feature, stylemap, paging, strategy, flickr, script
+ </div>
+ <p id="shortdesc">
+ Uses a paging strategy to cache large batches of features and render a page at a time.
+ </p>
+ <div id="map" class="smallmap"></div>
+ Displaying page <span id="num">0</span> of <span id="count">...</span>
+ <button id="prev" disabled="disabled" onclick="paging.pagePrevious();">previous</button>
+ <button id="next" disabled="disabled" onclick="paging.pageNext();">next</button>
+ <br><br>
+ <div id="docs">
+ <p>The Paging strategy lets you apply client side paging for protocols
+ that do not support paging on the server. In this case, the protocol requests a
+ batch of 100 features, the strategy caches those and supplies a single
+ page at a time to the layer.</p>
+ <p>This particular example uses the <a
+ href="http://www.flickr.com/services/api/">Flickr API.</a></p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/style-rules.html b/misc/openlayers/examples/style-rules.html
new file mode 100644
index 0000000..27f31f1
--- /dev/null
+++ b/misc/openlayers/examples/style-rules.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Rule Based Style</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="style-rules.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Rule Based Style</h1>
+ <div id="tags">
+ vector, feature, stylemap, filter, comparison, light
+ </div>
+ <p id="shortdesc">
+ Use rule based styling to use different symbolizers for different
+ feature groups.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ This example uses four rules to render features. Rules are
+ based on a feature attribute and determine which symbolizer
+ is applied when rendering a feature. The rules in this example
+ change which marker is used by providing an externalGraphic
+ property in the symbolizer.
+ </p>
+ The features are labeled with the same attribute that determines
+ the symbolizer used. You should be able to confirm that the
+ graphic color corresponds to the range of numbers given below.
+ </p>
+ <ul>
+ <li>0 &lt;= blue &lt; 25
+ <li>25 &lt;= green &lt; 50
+ <li>50 &lt;= gold &lt;= 75
+ <li>75 &lt; red &lt;= 100
+ </ul>
+ <p>
+ See the <a href="style-rules.js" target="_blank">
+ style-rules.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/style-rules.js b/misc/openlayers/examples/style-rules.js
new file mode 100644
index 0000000..42d3f00
--- /dev/null
+++ b/misc/openlayers/examples/style-rules.js
@@ -0,0 +1,99 @@
+var map;
+
+function init() {
+ map = new OpenLayers.Map("map");
+
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ );
+
+ /**
+ * Create 50 vector features. Your features would typically be fetched
+ * from the server. These are created here to demonstrate a rule based
+ * style. The features are given an attribute named "foo". The value
+ * of this attribute is an integer that ranges from 0 to 100.
+ */
+ var features = new Array(25);
+ for (var i=0; i<features.length; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(
+ (340 * Math.random()) - 170,
+ (160 * Math.random()) - 80
+ ), {
+ foo: 100 * Math.random() | 0
+ }
+ );
+ }
+
+ /**
+ * Here we create a new style object with rules that determine
+ * which symbolizer will be used to render each feature.
+ */
+ var style = new OpenLayers.Style(
+ // the first argument is a base symbolizer
+ // all other symbolizers in rules will extend this one
+ {
+ graphicWidth: 21,
+ graphicHeight: 25,
+ graphicYOffset: -28, // shift graphic up 28 pixels
+ label: "${foo}" // label will be foo attribute value
+ },
+ // the second argument will include all rules
+ {
+ rules: [
+ new OpenLayers.Rule({
+ // a rule contains an optional filter
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "foo", // the "foo" feature attribute
+ value: 25
+ }),
+ // if a feature matches the above filter, use this symbolizer
+ symbolizer: {
+ externalGraphic: "../img/marker-blue.png"
+ }
+ }),
+ new OpenLayers.Rule({
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "foo",
+ lowerBoundary: 25,
+ upperBoundary: 50
+ }),
+ symbolizer: {
+ externalGraphic: "../img/marker-green.png"
+ }
+ }),
+ new OpenLayers.Rule({
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "foo",
+ lowerBoundary: 50,
+ upperBoundary: 75
+ }),
+ symbolizer: {
+ externalGraphic: "../img/marker-gold.png"
+ }
+ }),
+ new OpenLayers.Rule({
+ // apply this rule if no others apply
+ elseFilter: true,
+ symbolizer: {
+ externalGraphic: "../img/marker.png"
+ }
+ })
+ ]
+ }
+ );
+
+ // create the layer styleMap that uses the above style for all render intents
+ var vector = new OpenLayers.Layer.Vector("Points", {
+ styleMap: new OpenLayers.StyleMap(style)
+ });
+ vector.addFeatures(features);
+
+ map.addLayers([wms, vector]);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+}
diff --git a/misc/openlayers/examples/style.css b/misc/openlayers/examples/style.css
new file mode 100644
index 0000000..237f940
--- /dev/null
+++ b/misc/openlayers/examples/style.css
@@ -0,0 +1,143 @@
+/**
+ * CSS Reset
+ * From Blueprint reset.css
+ * http://blueprintcss.googlecode.com
+ */
+html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}
+body {line-height:1.5;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;}
+table, td, th {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:"";}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+
+/**
+ * Basic Typography
+ */
+body {
+ font-family: "Lucida Grande", Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif;
+ font-size: 80%;
+ color: #222;
+ background: #fff;
+ margin: 1em 1.5em;
+}
+pre, code {
+ margin: 1.5em 0;
+ white-space: pre;
+}
+pre, code {
+ font: 1em 'andale mono', 'lucida console', monospace;
+ line-height:1.5;
+}
+a[href] {
+ color: #436976;
+ background-color: transparent;
+}
+h1, h2, h3, h4, h5, h6 {
+ color: #003a6b;
+ background-color: transparent;
+ font: 100% 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif;
+ margin: 0;
+ padding-top: 0.5em;
+}
+h1 {
+ font-size: 130%;
+ margin-bottom: 0.5em;
+ border-bottom: 1px solid #fcb100;
+}
+h2 {
+ font-size: 120%;
+ margin-bottom: 0.5em;
+ border-bottom: 1px solid #aaa;
+}
+h3 {
+ font-size: 110%;
+ margin-bottom: 0.5em;
+ text-decoration: underline;
+}
+h4 {
+ font-size: 100%;
+ font-weight: bold;
+}
+h5 {
+ font-size: 100%;
+ font-weight: bold;
+}
+h6 {
+ font-size: 80%;
+ font-weight: bold;
+}
+
+.olControlAttribution {
+ bottom: 5px;
+}
+
+/**
+ * Map Examples Specific
+ */
+.smallmap {
+ width: 512px;
+ height: 256px;
+ border: 1px solid #ccc;
+}
+#tags {
+ display: none;
+}
+
+#docs p {
+ margin-bottom: 0.5em;
+}
+/* mobile specific */
+@media only screen and (max-width: 600px) {
+ body {
+ height : 100%;
+ margin : 0;
+ padding : 0;
+ width : 100%;
+ }
+ #map {
+ background : #7391ad;
+ width : 100%;
+ }
+ #map {
+ border : 0;
+ height : 250px;
+ }
+ #title {
+ font-size : 1.3em;
+ line-height : 2em;
+ text-indent : 1em;
+ margin : 0;
+ padding : 0;
+ }
+ #docs {
+ bottom : 0;
+ padding : 1em;
+ }
+ #shortdesc {
+ color : #aaa;
+ font-size : 0.8em;
+ padding : 1em;
+ text-align : right;
+ }
+ #tags {
+ display : none;
+ }
+}
+@media only screen and (orientation: landscape) and (max-width: 600px) {
+ #shortdesc {
+ float: right;
+ width: 25%;
+ }
+ #map {
+ width: 70%;
+ }
+ #docs {
+ font-size: 12px;
+ }
+}
+body {
+ -webkit-text-size-adjust: none;
+}
+
diff --git a/misc/openlayers/examples/style.mobile-jq.css b/misc/openlayers/examples/style.mobile-jq.css
new file mode 100644
index 0000000..280f7ba
--- /dev/null
+++ b/misc/openlayers/examples/style.mobile-jq.css
@@ -0,0 +1,62 @@
+html ,
+body {
+ margin: 0;
+ padding: 0;
+ height: 100%;
+}
+.ui-content {
+ padding: 0;
+}
+.ui-footer {
+ text-align: center;
+ padding: 5px 0;
+}
+.portrait, .portrait #mappage {
+ min-height: 0;
+}
+/*.portrait, .portrait .ui-page{*/
+ /*min-height: 0;*/
+/*}*/
+#mappage, #mappage .ui-content, #map {
+ width: 100%;
+ height: 100%;
+}
+.olControlAttribution {
+ font-size: 10px;
+ bottom: 5px;
+ right: 5px;
+}
+#navigation {
+ position: absolute;
+ bottom: 70px;
+ left: 10px;
+ z-index: 1000;
+}
+#navigation .ui-btn-icon-notext {
+ display: block;
+ padding: 7px 6px 7px 8px;
+}
+#title, #tags, #shortdesc {
+ display: none;
+}
+.ui-icon-check {
+ opacity: 0.3;
+}
+.checked .ui-icon-check {
+ opacity: 1;
+}
+.ui-icon-locate {
+ background-image: url(img/locate.png);
+}
+.ui-icon-layers {
+ background-image: url(img/openlayers.png);
+}
+.ui-content .ui-listview-inset, #search_results {
+ margin: 1em;
+}
+.ui-content .ui-listview {
+ margin: 0;
+}
+#details-list li{
+ padding:15px 10px;
+}
diff --git a/misc/openlayers/examples/stylemap.html b/misc/openlayers/examples/stylemap.html
new file mode 100644
index 0000000..b80961d
--- /dev/null
+++ b/misc/openlayers/examples/stylemap.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers StyleMap</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ // Create 50 random features, and give them a "type" attribute that
+ // will be used to style them by size.
+ var features = new Array(50);
+ for (var i=0; i<features.length; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(
+ (360 * Math.random()) - 180, (180 * Math.random()) - 90
+ ), {
+ type: 5 + parseInt(5 * Math.random())
+ }
+ );
+ }
+
+ // Create a styleMap to style your features for two different
+ // render intents. The style for the 'default' render intent will
+ // be applied when the feature is first drawn. The style for the
+ // 'select' render intent will be applied when the feature is
+ // selected.
+ var myStyles = new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style({
+ pointRadius: "${type}", // sized according to type attribute
+ fillColor: "#ffcc66",
+ strokeColor: "#ff9933",
+ strokeWidth: 2,
+ graphicZIndex: 1
+ }),
+ "select": new OpenLayers.Style({
+ fillColor: "#66ccff",
+ strokeColor: "#3399ff",
+ graphicZIndex: 2
+ })
+ });
+
+ // Create a vector layer and give it your style map.
+ var points = new OpenLayers.Layer.Vector("Points", {
+ styleMap: myStyles,
+ rendererOptions: {zIndexing: true}
+ });
+ points.addFeatures(features);
+ map.addLayers([wms, points]);
+
+ // Create a select feature control and add it to the map.
+ var select = new OpenLayers.Control.SelectFeature(points, {hover: true});
+ map.addControl(select);
+ select.activate();
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">StyleMap Example</h1>
+
+ <div id="tags">
+ vector, feature, stylemap, light
+ </div>
+
+ <p id="shortdesc">
+ Shows how to use a StyleMap to style features with rule based styling.
+ A style map references one or more OpenLayers.Style objects. These
+ OpenLayers.Style objects are collections of OpenLayers.Rule objects
+ that determine how features are styled. An OpenLayers.Rule object
+ combines an OpenLayers.Filter object with a symbolizer. A filter is used
+ to determine whether a rule applies for a given feature, and a symbolizer
+ is used to draw the feature if the rule applies.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>A style map is used with vector layers to define styles for various
+ rendering intents. The style map used here has styles defined for the
+ "default" and "select" rendering intents. This map also has an active
+ select feature control. When you hover over features, they are selected
+ and drawn with the style corresponding the the "select" render intent.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/styles-context.html b/misc/openlayers/examples/styles-context.html
new file mode 100644
index 0000000..853e8c3
--- /dev/null
+++ b/misc/openlayers/examples/styles-context.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Vector Styles</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init(){
+ map = new OpenLayers.Map('map', {maxResolution:'auto'});
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(wms);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // Strategy 1: Style features based on some attribute.
+
+ // create 50 random features in the northern hemisphere
+ // give them a "type" attribute that will be used to style
+ // them by size
+ var features = new Array(50);
+ for (var i=0; i<features.length; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(
+ (360 * Math.random()) - 180, 90 * Math.random()
+ ), {
+ type: 5 + parseInt(5 * Math.random())
+ }
+ );
+ }
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ // create the layer styleMap with a simple symbolizer template
+ var layer1 = new OpenLayers.Layer.Vector('Points', {
+ styleMap: new OpenLayers.StyleMap({
+ pointRadius: "${type}", // based on feature.attributes.type
+ fillColor: "#666666"
+ }),
+ renderers: renderer
+ });
+ layer1.addFeatures(features);
+
+ // Strategy 2: Style features based on something besides attributes.
+
+ // create 50 random features in the southern hemisphere
+ var features = new Array(50);
+ for (var i=0; i<features.length; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(
+ (360 * Math.random()) - 180, -90 * Math.random()
+ ), {
+ type: 5 + parseInt(5 * Math.random())
+ }
+ );
+ }
+ // create the layer styleMap by giving the default style a context
+ var colors = ["red", "green", "blue"];
+ var context = {
+ getColor: function(feature) {
+ var region = parseInt((feature.geometry.x + 180) / 120);
+ return colors[region];
+ },
+ getSize: function(feature) {
+ return feature.attributes["type"] / map.getResolution() * .703125;
+ }
+ };
+ var template = {
+ pointRadius: "${getSize}", // using context.getSize(feature)
+ fillColor: "${getColor}" // using context.getColor(feature)
+ };
+ var style = new OpenLayers.Style(template, {context: context});
+ var layer2 = new OpenLayers.Layer.Vector('Points', {
+ styleMap: new OpenLayers.StyleMap(style),
+ renderers: renderer
+ });
+ layer2.addFeatures(features);
+
+
+ map.addLayers([layer1, layer2]);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Feature Styles Example</h1>
+
+ <div id="tags">
+ vector, feature, stylemap, light
+ </div>
+
+ <p id="shortdesc">
+ To style features with a custom function that evaluates each feature, use
+ the context option of an OpenLayers.Style object. If the context object
+ contains a function and this function is referenced in a symbolizer, the
+ function will be called with the feature as an argument..
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>Features in the northern hemisphere are styled according to their
+ "type" attribute. This is accomplished with a simple template that
+ is evaluated with the feature attributes as context.</p>
+ <p>Features in the sourthern hemisphere are styled according to a
+ combination of their attributes and non-attribute properties. This
+ is accomplished using an advanced template that calls functions
+ on the context object passed to the Style constructor.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/styles-rotation.html b/misc/openlayers/examples/styles-rotation.html
new file mode 100644
index 0000000..b6d6f95
--- /dev/null
+++ b/misc/openlayers/examples/styles-rotation.html
@@ -0,0 +1,93 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Styles Rotation Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+ var map;
+ var vectors;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ vectors = new OpenLayers.Layer.Vector(
+ "Simple Geometry",
+ {
+ styleMap: new OpenLayers.StyleMap({
+ "default": {
+ externalGraphic: "../img/marker-gold.png",
+ //graphicWidth: 17,
+ graphicHeight: 20,
+ graphicYOffset: -19,
+ rotation: "${angle}",
+ fillOpacity: "${opacity}"
+ },
+ "select": {
+ cursor: "crosshair",
+ externalGraphic: "../img/marker.png"
+ }
+ })
+ }
+ );
+
+ map.addLayers([wms, vectors]);
+
+ var features = [];
+ var x = -111.04;
+ var y = 45.68;
+ for(var i = 0; i < 10; i++){
+ x += i * .5;
+ y += i * .1;
+ features.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y), {angle: (i*36)%360-180, opacity:i/10+.1}
+ )
+ );
+ features.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y), {angle: (i*36)%360, opacity:i/10+.1}
+ )
+ );
+ }
+
+ map.setCenter(new OpenLayers.LonLat(x-10, y), 5);
+ vectors.addFeatures(features);
+
+ var selectControl = new OpenLayers.Control.SelectFeature(
+ vectors, {hover: true});
+ map.addControl(selectControl);
+ selectControl.activate();
+
+ };
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Rotation Style Example</h1>
+ <div id="tags">
+ vector, feature, stylemap, rotation, cleanup, light
+ </div>
+ <p id="shortdesc">
+ Use the rotation property of a point symbolizer to rotate
+ point symbolizers.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ To style point features with rotation, use the rotation property of the
+ symbolizer. The center of the rotation is the point of the image
+ specified by graphicXOffset and graphicYOffset. The rotation is
+ specified in degrees clockwise.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/styles-unique.html b/misc/openlayers/examples/styles-unique.html
new file mode 100644
index 0000000..ccea4ed
--- /dev/null
+++ b/misc/openlayers/examples/styles-unique.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Styles Unique Value Styles Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map('map', {maxResolution:'auto'});
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(wms);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // create 20 random features with a random type attribute. The
+ // type attribute is a value between 0 and 2.
+ var features = new Array(20);
+ for (var i=0; i<20; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(Math.random()*360-180, Math.random()*180-90),
+ {type: parseInt(Math.random()*3)}
+ );
+ }
+
+ // create a styleMap with a custom default symbolizer
+ var styleMap = new OpenLayers.StyleMap({
+ fillOpacity: 1,
+ pointRadius: 10
+ });
+
+ // create a lookup table with different symbolizers for 0, 1 and 2
+ var lookup = {
+ 0: {externalGraphic: "../img/marker-blue.png"},
+ 1: {externalGraphic: "../img/marker-green.png"},
+ 2: {externalGraphic: "../img/marker-gold.png"}
+ };
+
+ // add rules from the above lookup table, with the keyes mapped to
+ // the "type" property of the features, for the "default" intent
+ styleMap.addUniqueValueRules("default", "type", lookup);
+
+ layer = new OpenLayers.Layer.Vector('Points', {
+ styleMap: styleMap
+ });
+
+ layer.addFeatures(features);
+ map.addLayer(layer);
+
+ // create 20 random features with a random state property.
+ var features = new Array(20);
+ var states = [
+ OpenLayers.State.UNKNOWN,
+ OpenLayers.State.UPDATE,
+ OpenLayers.State.DELETE,
+ OpenLayers.State.INSERT
+ ];
+ for (var i=0; i<20; i++) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(Math.random()*360-180, Math.random()*180-90)
+ );
+ features[i].state = states[parseInt(Math.random()*4)];
+ }
+
+ var context = function(feature) {
+ return feature;
+ };
+ var styleMap = new OpenLayers.StyleMap();
+
+ // create a lookup table with different symbolizers for the different
+ // state values
+ var lookup = {};
+ lookup[OpenLayers.State.UNKNOWN] = {fillColor: "green"};
+ lookup[OpenLayers.State.UPDATE] = {fillColor: "green"};
+ lookup[OpenLayers.State.DELETE] = {fillColor: "red"};
+ lookup[OpenLayers.State.INSERT] = {fillColor: "orange"};
+
+ styleMap.addUniqueValueRules("default", "state", lookup, context);
+ layer = new OpenLayers.Layer.Vector('Points', {
+ styleMap: styleMap
+ });
+
+ layer.addFeatures(features);
+ map.addLayer(layer);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Unique Value Styles Example</h1>
+
+ <div id="tags">
+ vector, feature, stylemap, uniquevalue, cleanup, light
+ </div>
+
+ <p id="shortdesc">
+ Shows how to create a style based on unique feature attribute values (markers)
+ and feature state values (circles).
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/sundials-spherical-mercator.html b/misc/openlayers/examples/sundials-spherical-mercator.html
new file mode 100644
index 0000000..3ee6144
--- /dev/null
+++ b/misc/openlayers/examples/sundials-spherical-mercator.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Sundials on a Spherical Mercator Map</title>
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+
+ <style type="text/css">
+ html, body {
+ height: 100%;
+ }
+ #map {
+ width: 100%;
+ height: 80%;
+ border: 1px solid black;
+ }
+ .olPopup p { margin:0px; font-size: .9em;}
+ .olPopup h2 { font-size:1.2em; }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, select;
+
+ function init(){
+ var options = {
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326")
+ };
+ map = new OpenLayers.Map('map', options);
+ var mapnik = new OpenLayers.Layer.OSM("OpenStreetMap (Mapnik)");
+ var gmap = new OpenLayers.Layer.Google("Google", {sphericalMercator:true});
+ var sundials = new OpenLayers.Layer.Vector("KML", {
+ projection: map.displayProjection,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml/sundials.kml",
+ format: new OpenLayers.Format.KML({
+ extractStyles: true,
+ extractAttributes: true
+ })
+ })
+ });
+
+ map.addLayers([mapnik, gmap, sundials]);
+
+ select = new OpenLayers.Control.SelectFeature(sundials);
+
+ sundials.events.on({
+ "featureselected": onFeatureSelect,
+ "featureunselected": onFeatureUnselect
+ });
+
+ map.addControl(select);
+ select.activate();
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ map.zoomToExtent(
+ new OpenLayers.Bounds(
+ 68.774414, 11.381836, 123.662109, 34.628906
+ ).transform(map.displayProjection, map.projection)
+ );
+ }
+ function onPopupClose(evt) {
+ select.unselectAll();
+ }
+ function onFeatureSelect(event) {
+ var feature = event.feature;
+ var selectedFeature = feature;
+ var popup = new OpenLayers.Popup.FramedCloud("chicken",
+ feature.geometry.getBounds().getCenterLonLat(),
+ new OpenLayers.Size(100,100),
+ "<h2>"+feature.attributes.name + "</h2>" + feature.attributes.description,
+ null, true, onPopupClose
+ );
+ feature.popup = popup;
+ map.addPopup(popup);
+ }
+ function onFeatureUnselect(event) {
+ var feature = event.feature;
+ if(feature.popup) {
+ map.removePopup(feature.popup);
+ feature.popup.destroy();
+ delete feature.popup;
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OSM + Google Maps + KML Reprojection</h1>
+
+ <div id="tags">
+ osm, kml, spherical, mercator, reprojection, feature, popup, advanced
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates loading and displaying a KML file on top of OpenStreetMap (OSM) and Google Maps data. Loads data from a KML file of sundials.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/sundials.html b/misc/openlayers/examples/sundials.html
new file mode 100644
index 0000000..b718755
--- /dev/null
+++ b/misc/openlayers/examples/sundials.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+
+ <style type="text/css">
+ html, body {
+ height: 100%;
+ }
+ #map {
+ width: 100%;
+ height: 80%;
+ border: 1px solid black;
+ }
+ .olPopup p { margin:0px; font-size: .9em;}
+ .olPopup h2 { font-size:1.2em; }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, select;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var sundials = new OpenLayers.Layer.Vector("KML", {
+ projection: map.displayProjection,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "kml/sundials.kml",
+ format: new OpenLayers.Format.KML({
+ extractStyles: true,
+ extractAttributes: true
+ })
+ })
+ });
+
+ map.addLayers([wms, sundials]);
+
+ select = new OpenLayers.Control.SelectFeature(sundials);
+
+ sundials.events.on({
+ "featureselected": onFeatureSelect,
+ "featureunselected": onFeatureUnselect
+ });
+
+ map.addControl(select);
+ select.activate();
+ map.zoomToExtent(new OpenLayers.Bounds(68.774414,11.381836,123.662109,34.628906));
+ }
+ function onPopupClose(evt) {
+ select.unselectAll();
+ }
+ function onFeatureSelect(event) {
+ var feature = event.feature;
+ // Since KML is user-generated, do naive protection against
+ // Javascript.
+ var content = "<h2>"+feature.attributes.name + "</h2>" + feature.attributes.description;
+ if (content.search("<script") != -1) {
+ content = "Content contained Javascript! Escaped content below.<br>" + content.replace(/</g, "&lt;");
+ }
+ popup = new OpenLayers.Popup.FramedCloud("chicken",
+ feature.geometry.getBounds().getCenterLonLat(),
+ new OpenLayers.Size(100,100),
+ content,
+ null, true, onPopupClose);
+ feature.popup = popup;
+ map.addPopup(popup);
+ }
+ function onFeatureUnselect(event) {
+ var feature = event.feature;
+ if(feature.popup) {
+ map.removePopup(feature.popup);
+ feature.popup.destroy();
+ delete feature.popup;
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">KML Layer Example</h1>
+
+ <div id="tags">
+ kml, popup, feature
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates loading and displaying a KML file on top of a basemap.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/symbolizers-fill-stroke-graphic.html b/misc/openlayers/examples/symbolizers-fill-stroke-graphic.html
new file mode 100644
index 0000000..27a0b58
--- /dev/null
+++ b/misc/openlayers/examples/symbolizers-fill-stroke-graphic.html
@@ -0,0 +1,141 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Fill, Stroke, and Graphic Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ map = new OpenLayers.Map('map');
+
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", {
+ styleMap: new OpenLayers.StyleMap({
+ 'default': new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ graphic: false,
+ label: "Label for invisible point",
+ labelSelect: true,
+ fontStyle: "italic"
+ },
+ filter: new OpenLayers.Filter.Comparison({
+ type: "==",
+ property: "topic",
+ value: "point_invisible"
+ })
+ }),
+ new OpenLayers.Rule({
+ symbolizer: {
+ stroke: true,
+ fill: true,
+ label: "Polygon with stroke and fill defaults"
+ },
+ filter: new OpenLayers.Filter.Comparison({
+ type: "==",
+ property: "topic",
+ value: "polygon_defaults"
+ })
+ }),
+ new OpenLayers.Rule({
+ symbolizer: {
+ stroke: true,
+ fill: false,
+ label: "Point without fill",
+ labelAlign: "rb",
+ fontColor: "#ff0000",
+ fontOpacity: 0.4
+ },
+ filter: new OpenLayers.Filter.Comparison({
+ type: "==",
+ property: "topic",
+ value: "point_nofill"
+ })
+ })
+ ]
+ })
+ }),
+ renderers: renderer
+ });
+
+ // create a point feature
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointFeature = new OpenLayers.Feature.Vector(point);
+ pointFeature.attributes = {
+ topic: "point_invisible"
+ };
+
+ // create a polygon feature from a linear ring of points
+ var pointList = [];
+ for(var p=0; p<6; ++p) {
+ var a = p * (2 * Math.PI) / 7;
+ var r = Math.random(1) + 1;
+ var newPoint = new OpenLayers.Geometry.Point(point.x + 5 + (r * Math.cos(a)),
+ point.y + 5 + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+
+ var linearRing = new OpenLayers.Geometry.LinearRing(pointList);
+ var polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([linearRing]));
+ polygonFeature.attributes = {
+ topic: "polygon_defaults"
+ };
+
+ multiFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Collection([
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-105,40),
+ new OpenLayers.Geometry.Point(-95,45)
+ ]),
+ new OpenLayers.Geometry.Point(-105, 40)
+ ]),
+ {
+ topic: "point_nofill"
+ });
+
+ map.addLayer(vectorLayer);
+ vectorLayer.drawFeature(multiFeature);
+ map.setCenter(new OpenLayers.LonLat(point.x, point.y), 4);
+ vectorLayer.addFeatures([pointFeature, polygonFeature, multiFeature]);
+ var select = new OpenLayers.Control.SelectFeature(vectorLayer, {
+ selectStyle: OpenLayers.Util.extend(
+ {fill: true, stroke: true},
+ OpenLayers.Feature.Vector.style["select"])
+ });
+ map.addControl(select);
+ select.activate();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Example</h1>
+ <div id="tags">
+ vector, feature, symbolizer, filter, comparison, labeling, light
+ </div>
+ <p id="shortdesc">
+ Demonstrate fill, stroke, and graphic property of symbolizers.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ This example shows how to use symbolizers with defaults for stroke, fill, and graphic.
+ This also allows to create labels for a feature without the feature rendered. Click on
+ the label in the middle to see selection of features with labelSelect set to true.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/tasmania/TasmaniaCities.xml b/misc/openlayers/examples/tasmania/TasmaniaCities.xml
new file mode 100644
index 0000000..11f5bd7
--- /dev/null
+++ b/misc/openlayers/examples/tasmania/TasmaniaCities.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openplans.org/topp http://192.168.0.100:8080/geoserver-1.4.0-RC3/wfs/DescribeFeatureType?typeName=topp:tasmania_cities http://www.opengis.net/wfs http://192.168.0.100:8080/geoserver-1.4.0-RC3/schemas/wfs/1.0.0/WFS-basic.xsd">
+ <gml:boundedBy>
+ <gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.29100045,-42.85100182 147.29100045,-42.85100182</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <topp:tasmania_cities fid="tasmania_cities.1">
+ <topp:the_geom>
+ <gml:MultiPoint srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.29100045,-42.85100182</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ </gml:MultiPoint>
+ </topp:the_geom>
+ <topp:CITY_NAME>Hobart</topp:CITY_NAME>
+ <topp:ADMIN_NAME>Tasmania</topp:ADMIN_NAME>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:STATUS>Provincial capital</topp:STATUS>
+ <topp:POP_CLASS>100,000 to 250,000</topp:POP_CLASS>
+ </topp:tasmania_cities>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_cities fid="tasmania_cities.2">
+ <topp:the_geom>
+ <gml:MultiPoint srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147,-41.1</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ </gml:MultiPoint>
+ </topp:the_geom>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ </topp:tasmania_cities>
+ </gml:featureMember>
+</wfs:FeatureCollection>
diff --git a/misc/openlayers/examples/tasmania/TasmaniaRoads.xml b/misc/openlayers/examples/tasmania/TasmaniaRoads.xml
new file mode 100644
index 0000000..f01b56d
--- /dev/null
+++ b/misc/openlayers/examples/tasmania/TasmaniaRoads.xml
@@ -0,0 +1,204 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openplans.org/topp http://192.168.0.100:8080/geoserver-1.4.0-RC3/wfs/DescribeFeatureType?typeName=topp:tasmania_roads http://www.opengis.net/wfs http://192.168.0.100:8080/geoserver-1.4.0-RC3/schemas/wfs/1.0.0/WFS-basic.xsd">
+ <gml:boundedBy>
+ <gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">145.19754,-43.423512 148.27298,-40.852802</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.1">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.468582,-41.241478 146.574768,-41.251186 146.640411,-41.255154 146.766129,-41.332348 146.794189,-41.34417 146.822174,-41.362988 146.863434,-41.380234 146.899521,-41.379452 146.929504,-41.378227 147.008041,-41.356079 147.098343,-41.362919</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>alley</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.2">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.098343,-41.362919 147.17305,-41.452778 147.213867,-41.503773 147.234894,-41.546661 147.251129,-41.573826 147.264664,-41.602474 147.284485,-41.617554 147.300583,-41.637878</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>highway</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.3">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.300583,-41.637878 147.225815,-41.626938 147.183319,-41.619236 147.082367,-41.577755 147.031326,-41.565205 146.961487,-41.564186 146.924545,-41.568565 146.876328,-41.569614 146.783722,-41.56073 146.684937,-41.536232 146.614258,-41.478153 146.619995,-41.423958 146.582581,-41.365482 146.52478,-41.29541 146.477493,-41.277622 146.468582,-41.241478</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>lane</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.4">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.522247,-41.859921 147.551865,-41.927834 147.597321,-42.017418 147.578644,-42.113216 147.541656,-42.217743 147.468674,-42.22662</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>highway</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.5">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.103699,-41.171677 146.303619,-41.237202 146.362228,-41.236279 146.39418,-41.245384 146.443726,-41.244308 146.468582,-41.241478</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>gravel</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.6">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">145.856018,-41.08007 145.944839,-41.119896 146.037994,-41.150059 146.103699,-41.171677</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>road</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.7">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.468674,-42.22662 147.474945,-42.292259 147.467697,-42.301292 147.451828,-42.341656 147.424545,-42.378723 147.366013,-42.412552 147.345779,-42.432449 147.289322,-42.476475 147.264511,-42.503899 147.259918,-42.547539 147.249405,-42.614006 147.278351,-42.693249 147.284271,-42.757759 147.256744,-42.778393</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>highway</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.8">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">148.249252,-41.860851 148.234436,-41.901783 148.192123,-41.93721 148.155762,-41.953667 148.127731,-41.994537 148.053131,-42.100563</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>road</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.9">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">145.19754,-40.878323 145.246674,-40.86021 145.293289,-40.852802 145.465225,-40.897865 145.538498,-40.936264 145.554062,-40.939201 145.602112,-40.962936 145.646362,-40.98243 145.683838,-40.989883 145.710587,-40.996201 145.744293,-41.007545 145.801956,-41.041782 145.856018,-41.08007</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>logging</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.10">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.360001,-42.91993 147.348816,-42.93726 147.285049,-42.979027 147.220886,-42.995876 147.164429,-43.027004 147.068237,-43.06319 146.96463,-43.116447 146.949554,-43.17004 146.95369,-43.209591 146.964127,-43.224545 146.975723,-43.250484 146.980759,-43.2701 146.982605,-43.287716 146.970871,-43.31691 146.940521,-43.33812 146.943054,-43.362263 146.952194,-43.39278 146.955429,-43.423512</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>road</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.11">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.300583,-41.637878 147.372009,-41.695503 147.402588,-41.725574 147.444061,-41.749676 147.490433,-41.782482 147.506866,-41.795624 147.522919,-41.835609 147.522247,-41.859921</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>highway</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.12">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">148.053131,-42.100563 148.028229,-42.188286 148.002258,-42.2295 147.969955,-42.254417 147.960297,-42.284897 147.942719,-42.398819 147.926407,-42.486034 147.875092,-42.538582 147.832001,-42.587299 147.744217,-42.631607 147.693298,-42.656067 147.618195,-42.691135 147.575317,-42.743092 147.578293,-42.769539 147.547852,-42.814312 147.506699,-42.842907 147.488312,-42.877041 147.449692,-42.901054 147.416809,-42.902828</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>road</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.13">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.098343,-41.362919 147.065445,-41.311977 147.024078,-41.257534 146.981445,-41.211391 146.948227,-41.181595 146.926773,-41.172501 146.905029,-41.147144 146.940765,-41.085857 146.962662,-41.075096 147.021088,-41.080925 147.099228,-41.123959 147.187607,-41.150597 147.282028,-41.104244 147.295715,-41.075798 147.306595,-41.062832 147.325745,-41.053524 147.362991,-41.080441 147.419022,-41.081764 147.465881,-41.06089 147.519302,-41.092793 147.528595,-41.137089 147.552521,-41.193565 147.594223,-41.233875 147.734406,-41.239891 147.829376,-41.196636 147.882614,-41.163197 147.91127,-41.163109 147.985168,-41.226128 148.022156,-41.292599 148.075119,-41.313915 148.200104,-41.323097 148.236191,-41.339245 148.27298,-41.383488 148.25,-41.45713 148.254395,-41.53941 148.262436,-41.585217 148.249252,-41.860851</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>road</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_roads fid="tasmania_roads.14">
+ <topp:the_geom>
+ <gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.256744,-42.778393 147.220184,-42.824776 147.179596,-42.82143 147.111328,-42.795731 147.057098,-42.741581 147.003479,-42.704803 146.919098,-42.622734 146.910538,-42.610928 146.889984,-42.585396 146.83844,-42.572792 146.78569,-42.539352 146.724335,-42.485966 146.695023,-42.469582 146.649872,-42.450371 146.604965,-42.432274 146.578781,-42.408531 146.539307,-42.364208 146.525055,-42.30883 146.558044,-42.275948 146.576248,-42.23777 146.581467,-42.203426 146.490005,-42.180222 146.3797,-42.146332 146.334061,-42.138741 146.270966,-42.165703 146.197296,-42.224072 146.167908,-42.244835 146.164932,-42.245171 146.111023,-42.265202 146.037476,-42.239738 145.981628,-42.187851 145.853912,-42.133492 145.819611,-42.129154 145.72052,-42.104084 145.618576,-42.056023 145.541718,-42.027241 145.486282,-41.983326 145.452744,-41.926544 145.494034,-41.896477 145.591736,-41.860214 145.64212,-41.838398 145.669449,-41.830734 145.680923,-41.795753 145.682968,-41.743221 145.675156,-41.710377 145.680115,-41.688908 145.701065,-41.648228 145.714798,-41.609509 145.629196,-41.462051 145.648895,-41.470337 145.633423,-41.420902 145.631866,-41.36528 145.640854,-41.301533 145.700424,-41.242611 145.77243,-41.193897 145.802338,-41.161488 145.856018,-41.08007</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ </gml:MultiLineString>
+ </topp:the_geom>
+ <topp:TYPE>road</topp:TYPE>
+ </topp:tasmania_roads>
+ </gml:featureMember>
+</wfs:FeatureCollection>
diff --git a/misc/openlayers/examples/tasmania/TasmaniaStateBoundaries.xml b/misc/openlayers/examples/tasmania/TasmaniaStateBoundaries.xml
new file mode 100644
index 0000000..5edb4d7
--- /dev/null
+++ b/misc/openlayers/examples/tasmania/TasmaniaStateBoundaries.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openplans.org/topp http://192.168.0.100:8080/geoserver-1.4.0-RC3/wfs/DescribeFeatureType?typeName=topp:tasmania_state_boundaries http://www.opengis.net/wfs http://192.168.0.100:8080/geoserver-1.4.0-RC3/schemas/wfs/1.0.0/WFS-basic.xsd">
+ <gml:boundedBy>
+ <gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">143.834824,-43.648056 148.479141,-39.573891</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <topp:tasmania_state_boundaries fid="tasmania_state_boundaries.1">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">148.01416,-42.753059 148.009979,-42.73111 148.011108,-42.652222 148.012634,-42.628613 148.018738,-42.61972 148.076492,-42.586945 148.128006,-42.590275 148.172897,-42.655277 148.168167,-42.665554 148.154984,-42.668888 148.097748,-42.666107 148.041656,-42.732216 148.01416,-42.753059</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.361633,-43.263062 147.29303,-43.157082 147.329132,-43.102638 147.357178,-43.075005 147.396515,-43.11972 147.431641,-43.213886 147.432739,-43.241943 147.429688,-43.253616 147.361633,-43.263062</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">148.128845,-40.274445 148.115234,-40.271667 148.101074,-40.26722 148.064423,-40.253891 148.049133,-40.245552 148.038589,-40.236248 148.013184,-40.161388 148.018311,-40.140209 147.90387,-39.975555 147.809616,-39.913815 147.773865,-39.894722 147.760742,-39.877983 147.783875,-39.850281 147.881897,-39.754173 147.925812,-39.737503 147.967743,-39.725555 147.971069,-39.736389 147.978302,-39.74472 148.069427,-39.83889 148.165527,-39.929443 148.174408,-39.936111 148.186783,-39.944443 148.202759,-39.950279 148.243851,-39.962082 148.279419,-39.965836 148.288025,-39.99472 148.335236,-40.192223 148.33136,-40.219166 148.32135,-40.231941 148.303314,-40.239025 148.17746,-40.25695 148.128845,-40.274445</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">148.339142,-40.503334 148.339691,-40.466942 148.33609,-40.45472 148.329971,-40.442917 148.318298,-40.435272 148.292206,-40.434441 148.129944,-40.44722 148.114685,-40.448883 148.103851,-40.454445 148.086639,-40.458057 148.068573,-40.45472 147.99704,-40.428196 147.993561,-40.417084 147.995514,-40.40139 147.998566,-40.389725 148.008041,-40.379162 148.065247,-40.348194 148.083588,-40.344719 148.099121,-40.34333 148.116913,-40.343613 148.133026,-40.345001 148.148315,-40.347221 148.187744,-40.362503 148.202606,-40.361252 148.288025,-40.324722 148.308868,-40.314163 148.329132,-40.305138 148.343018,-40.306664 148.354675,-40.315552 148.479141,-40.430695 148.477188,-40.441387 148.463226,-40.442081 148.407608,-40.461945 148.358307,-40.490555 148.339142,-40.503334</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.302765,-43.513336 147.239136,-43.491669 147.175537,-43.501671 147.123016,-43.421944 147.190247,-43.354446 147.289566,-43.26403 147.300262,-43.262779 147.307739,-43.270279 147.362457,-43.374168 147.36496,-43.385834 147.362732,-43.398056 147.320663,-43.502918 147.310516,-43.511948 147.302765,-43.513336</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">144.888885,-40.729439 144.878571,-40.726246 144.870941,-40.71944 144.865936,-40.671116 144.926224,-40.617222 144.993286,-40.666664 145.016083,-40.695549 144.926361,-40.722496 144.888885,-40.729439</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.916702,-43.617844 146.863281,-43.636391 146.833588,-43.648056 146.81517,-43.617912 146.770401,-43.610695 146.686371,-43.603333 146.599548,-43.55611 146.514435,-43.542778 146.296082,-43.534729 146.275177,-43.52375 146.260269,-43.49514 146.231476,-43.488888 146.110367,-43.515423 146.0383,-43.498055 145.932678,-43.376316 145.991913,-43.345833 146.1026,-43.357918 146.156647,-43.379723 146.232529,-43.390972 146.234543,-43.325142 146.163239,-43.28236 146.139709,-43.31472 146.12468,-43.333332 145.858856,-43.30875 145.836914,-43.297226 145.758881,-43.184441 145.726898,-43.133331 145.595245,-42.979164 145.573776,-42.963818 145.54747,-42.961391 145.511581,-42.965656 145.459686,-42.904442 145.423157,-42.846664 145.397766,-42.775558 145.353851,-42.658535 145.310516,-42.623611 145.259979,-42.612431 145.231354,-42.45639 145.197617,-42.313473 145.205231,-42.25695 145.223846,-42.239166 145.250107,-42.27486 145.323029,-42.32 145.378021,-42.349167 145.426361,-42.37458 145.439758,-42.398746 145.445663,-42.457359 145.459061,-42.505627 145.469421,-42.523056 145.47525,-42.520279 145.552048,-42.351109 145.49968,-42.323475 145.458313,-42.326393 145.280273,-42.181114 145.260681,-42.139999 145.265121,-42.111389 145.262772,-42.080002 145.247879,-42.034863 145.184555,-41.938332 145.054962,-41.846664 144.954956,-41.713333 144.858582,-41.544449 144.781647,-41.390556 144.731628,-41.306107 144.685791,-41.216595 144.695389,-41.18111 144.667755,-41.075211 144.653595,-41.046951 144.637207,-41.031944 144.618713,-40.93111 144.648865,-40.901245 144.680664,-40.896114 144.699692,-40.875484 144.708588,-40.825562 144.701355,-40.759171 144.762207,-40.72805 144.985992,-40.74868 145.036102,-40.779167 145.080383,-40.810276 145.116348,-40.822365 145.274994,-40.80278 145.335663,-40.842079 145.539154,-40.892776 145.751373,-40.987778 145.872192,-41.042778 146.169434,-41.149994 146.193176,-41.15694 146.229126,-41.160553 146.36969,-41.170837 146.405411,-41.171669 146.450378,-41.16486 146.499146,-41.150139 146.564697,-41.175278 146.58609,-41.186661 146.581909,-41.151527 146.660385,-41.088749 146.731491,-41.069725 146.76416,-41.073059 146.784409,-41.082291 146.801086,-41.107506 146.806458,-41.148365 146.859131,-41.168335 146.94281,-41.166874 146.912964,-41.134789 146.879837,-41.126804 146.843018,-41.123055 146.823013,-41.108124 146.818726,-41.059792 146.86377,-41.028404 147.017059,-40.976109 147.08609,-40.991943 147.105804,-40.99778 147.124664,-41.005005 147.147354,-41.008892 147.171783,-41.008892 147.199127,-41.002228 147.356079,-40.976387 147.416504,-41.017776 147.461914,-41.001396 147.488373,-40.984997 147.517487,-40.953331 147.541656,-40.924171 147.573166,-40.879028 147.589127,-40.853058 147.611618,-40.842358 147.674835,-40.830837 147.698792,-40.857361 147.803162,-40.89278 147.838165,-40.891251 147.876083,-40.878746 147.901779,-40.863194 147.921631,-40.840836 147.933594,-40.822086 147.944138,-40.795277 147.951157,-40.761322 147.971832,-40.744789 148.01416,-40.745972 148.079407,-40.76889 148.221069,-40.84903 148.273315,-40.901108 148.307419,-40.957478 148.318863,-40.972359 148.328308,-40.995419 148.302185,-41.075562 148.290253,-41.10778 148.279846,-41.130833 148.264343,-41.167221 148.272003,-41.218468 148.313568,-41.259308 148.316925,-41.334724 148.287476,-41.423889 148.273804,-41.454166 148.280396,-41.539234 148.296356,-41.565834 148.312195,-41.591248 148.314285,-41.612919 148.292206,-41.728882 148.270966,-41.782642 148.264709,-41.814587 148.298035,-42.035004 148.311493,-42.063473 148.333984,-42.087639 148.358719,-42.108681 148.363846,-42.222427 148.346619,-42.249168 148.324554,-42.270695 148.311096,-42.277779 148.302765,-42.27639 148.275269,-42.255562 148.270813,-42.231667 148.29776,-42.206249 148.309692,-42.140556 148.238846,-41.998196 148.195267,-41.94545 148.079117,-42.117218 148.004013,-42.522499 147.958725,-42.556389 147.943848,-42.613892 147.955521,-42.666527 147.954956,-42.717499 147.924835,-42.741108 147.898865,-42.756535 147.883179,-42.772221 147.84288,-42.872917 147.856415,-42.888889 147.899796,-42.886631 147.881836,-42.857224 147.910873,-42.840832 147.974197,-42.869511 147.999695,-42.907078 148.004776,-42.976868 147.96701,-42.995449 147.951492,-43.082291 147.979126,-43.126663 148.0047,-43.170837 147.995529,-43.227589 147.970795,-43.229092 147.899414,-43.183434 147.827179,-43.206108 147.789703,-43.246948 147.697205,-43.163612 147.631622,-43.065552 147.618973,-43.017708 147.67392,-42.945133 147.706497,-42.938328 147.730789,-42.95472 147.735794,-42.978886 147.719894,-43.002499 147.759979,-43.039864 147.781784,-43.051109 147.808868,-43.054722 147.867874,-43.046528 147.899414,-43.026875 147.825806,-42.931946 147.591629,-42.826736 147.557465,-42.830559 147.502121,-42.860764 147.521362,-42.928886 147.53595,-42.949024 147.55275,-42.978954 147.525604,-43.018333 147.476624,-43.034172 147.427124,-43.04174 147.403732,-43.000072 147.423019,-42.991112 147.40802,-42.889725 147.351624,-42.861389 147.317474,-42.846664 147.348572,-42.904716 147.34079,-42.951111 147.32608,-43.008614 147.292755,-43.028053 147.268188,-43.060432 147.241913,-43.133614 147.240311,-43.155487 147.262482,-43.203888 147.263321,-43.224861 147.247452,-43.269169 147.213287,-43.285625 147.178162,-43.282223 147.098297,-43.244446 147.041351,-43.199722 147.025742,-43.181873 147.022766,-43.138332 147.01207,-43.118752 146.991287,-43.112431 146.970093,-43.137085 146.964417,-43.164162 146.964142,-43.184307 146.969269,-43.204304 146.993988,-43.223747 147.01944,-43.237778 147.061096,-43.258339 147.095337,-43.288715 147.054962,-43.362503 147.002213,-43.422638 146.952179,-43.528053 146.937469,-43.600624 146.916702,-43.617844</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">143.921631,-40.136391 143.913879,-40.134727 143.886307,-40.116734 143.873566,-40.065002 143.892349,-40.054302 143.891937,-39.984722 143.885544,-39.970139 143.870514,-39.956947 143.851624,-39.945274 143.840378,-39.936802 143.834824,-39.927502 143.837738,-39.873055 143.85524,-39.711945 143.871063,-39.700279 143.899719,-39.688606 143.916656,-39.680557 143.925812,-39.674171 143.933594,-39.666946 143.941635,-39.655693 143.945526,-39.640839 143.943848,-39.628883 143.935516,-39.608612 143.931915,-39.598335 143.935455,-39.583054 143.977463,-39.573891 143.987732,-39.57695 144.066788,-39.616112 144.108582,-39.662498 144.112183,-39.673058 144.122192,-39.812218 144.122192,-39.825005 144.146454,-39.92944 144.136917,-39.984306 144.106064,-40.036392 144.008881,-40.087776 143.957733,-40.110001 143.921631,-40.136391</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:STATE>Tasmania</topp:STATE>
+ <topp:COUNTRY>Australia</topp:COUNTRY>
+ <topp:CURR_TYPE>Australia Dollar</topp:CURR_TYPE>
+ <topp:CURR_CODE>AUD</topp:CURR_CODE>
+ </topp:tasmania_state_boundaries>
+ </gml:featureMember>
+</wfs:FeatureCollection>
diff --git a/misc/openlayers/examples/tasmania/TasmaniaWaterBodies.xml b/misc/openlayers/examples/tasmania/TasmaniaWaterBodies.xml
new file mode 100644
index 0000000..ba96e13
--- /dev/null
+++ b/misc/openlayers/examples/tasmania/TasmaniaWaterBodies.xml
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openplans.org/topp http://192.168.0.100:8080/geoserver-1.4.0-RC3/wfs/DescribeFeatureType?typeName=topp:tasmania_water_bodies http://www.opengis.net/wfs http://192.168.0.100:8080/geoserver-1.4.0-RC3/schemas/wfs/1.0.0/WFS-basic.xsd">
+ <gml:boundedBy>
+ <gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">145.971619,-43.031944 147.219696,-41.775558</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.1">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.232727,-42.157501 146.238007,-42.16111 146.24411,-42.169724 146.257202,-42.193329 146.272217,-42.209442 146.274689,-42.214165 146.27832,-42.21833 146.282471,-42.228882 146.282745,-42.241943 146.291351,-42.255836 146.290253,-42.261948 146.288025,-42.267502 146.282471,-42.269997 146.274994,-42.271111 146.266663,-42.270279 146.251373,-42.262505 146.246918,-42.258057 146.241333,-42.256111 146.23468,-42.257782 146.221344,-42.269165 146.210785,-42.274445 146.20163,-42.27417 146.196075,-42.271385 146.186646,-42.258057 146.188568,-42.252785 146.193298,-42.249443 146.200806,-42.248055 146.209137,-42.249168 146.217468,-42.248611 146.222473,-42.245277 146.22525,-42.240555 146.224121,-42.22805 146.224396,-42.221382 146.228302,-42.217216 146.231354,-42.212502 146.231628,-42.205559 146.219421,-42.186943 146.21637,-42.17028 146.216644,-42.16333 146.219696,-42.158607 146.225525,-42.156105 146.232727,-42.157501</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1064866676</topp:AREA>
+ <topp:PERIMETER>1071221047</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.2">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.284424,-43.031944 146.265808,-43.029442 146.257751,-43.021667 146.252197,-43.01889 146.243561,-43.017776 146.23053,-43.021667 146.21524,-43.02417 146.209686,-43.021942 146.209961,-43.015007 146.21579,-42.991112 146.21524,-42.985001 146.213593,-42.979439 146.21109,-42.974716 146.207458,-42.970276 146.193024,-42.959724 146.181915,-42.95472 146.166931,-42.951393 146.1586,-42.950554 146.123016,-42.951111 146.116364,-42.948883 146.112732,-42.944717 146.110229,-42.93972 146.101349,-42.932777 146.094971,-42.929726 146.084961,-42.922775 146.054138,-42.897781 146.041656,-42.886665 146.038025,-42.882217 146.035522,-42.877495 146.035248,-42.86528 146.036652,-42.852226 146.034424,-42.840279 146.030823,-42.836113 146.026367,-42.832504 146.018036,-42.831673 146.010529,-42.832779 146.003876,-42.834724 145.995514,-42.835274 145.990784,-42.831673 145.990234,-42.825562 145.996338,-42.815277 146.000549,-42.805275 145.997192,-42.800278 145.984406,-42.789726 145.981079,-42.785561 145.976898,-42.775002 145.97995,-42.770279 145.985504,-42.767776 145.994965,-42.768059 146.002472,-42.769447 146.008881,-42.772499 146.025818,-42.786667 146.032196,-42.788895 146.040802,-42.788338 146.061646,-42.783333 146.068848,-42.785004 146.074402,-42.787781 146.086914,-42.799995 146.109131,-42.825279 146.117188,-42.832222 146.122742,-42.834442 146.131073,-42.835274 146.139709,-42.834999 146.147217,-42.833061 146.163025,-42.83139 146.170532,-42.833611 146.174988,-42.837219 146.176636,-42.841942 146.17746,-42.848053 146.173309,-42.852226 146.165802,-42.853333 146.155243,-42.859169 146.141937,-42.86306 146.12912,-42.858612 146.118011,-42.852783 146.110779,-42.851395 146.102173,-42.852501 146.098297,-42.855003 146.097198,-42.861389 146.102173,-42.871666 146.111359,-42.878883 146.121338,-42.884445 146.132446,-42.889442 146.146942,-42.899445 146.154968,-42.907219 146.164978,-42.914444 146.174988,-42.92028 146.181366,-42.923058 146.195251,-42.926109 146.204681,-42.926392 146.220795,-42.924721 146.227448,-42.922775 146.233032,-42.92028 146.241913,-42.913612 146.247742,-42.904167 146.260529,-42.891945 146.265533,-42.888611 146.272217,-42.886948 146.281372,-42.886948 146.289703,-42.888054 146.300812,-42.893616 146.308014,-42.902779 146.308594,-42.908333 146.302185,-42.925278 146.301086,-42.931389 146.301636,-42.9375 146.303314,-42.943054 146.307739,-42.946663 146.320801,-42.951111 146.330261,-42.951393 146.352753,-42.947777 146.360229,-42.949165 146.361908,-42.95472 146.358002,-42.959442 146.347473,-42.965553 146.335785,-42.969994 146.331085,-42.973328 146.328033,-42.97805 146.329681,-42.983612 146.33609,-42.985832 146.36496,-42.9925 146.371338,-42.99472 146.383331,-43.000557 146.389984,-43.002785 146.39444,-43.006393 146.391357,-43.011116 146.383881,-43.012222 146.368561,-43.020836 146.355225,-43.024719 146.339142,-43.02639 146.332458,-43.028336 146.323853,-43.02861 146.313019,-43.023056 146.306366,-43.020836 146.298859,-43.022774 146.290253,-43.029442 146.284424,-43.031944</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1067509088</topp:AREA>
+ <topp:PERIMETER>1073140989</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.3">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.191925,-42.116112 146.184692,-42.114449 146.174988,-42.107506 146.171356,-42.103333 146.167755,-42.101944 146.167206,-42.095001 146.170532,-42.077225 146.169128,-42.071671 146.163879,-42.061943 146.159698,-42.057777 146.140808,-42.044167 146.09024,-42.014168 146.08609,-42.010559 146.083313,-42.005005 146.084686,-41.999443 146.089417,-41.996109 146.097748,-41.99778 146.109406,-42.002228 146.129395,-42.008057 146.146637,-42.016113 146.153046,-42.018333 146.169128,-42.026947 146.179138,-42.033615 146.182739,-42.036949 146.203583,-42.062775 146.20636,-42.06778 146.207733,-42.073334 146.206635,-42.079445 146.207184,-42.085556 146.208862,-42.09111 146.214417,-42.094162 146.21579,-42.099724 146.209961,-42.109169 146.205231,-42.11306 146.200256,-42.115555 146.191925,-42.116112</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1064598241</topp:AREA>
+ <topp:PERIMETER>1071187492</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.4">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.697205,-41.988892 146.688873,-41.988052 146.682465,-41.985832 146.67746,-41.976105 146.673859,-41.973328 146.674133,-41.966393 146.673309,-41.960281 146.674408,-41.95417 146.680817,-41.937218 146.696625,-41.907219 146.69693,-41.900551 146.694122,-41.895554 146.693573,-41.889442 146.695526,-41.883888 146.702179,-41.875275 146.703583,-41.869164 146.700256,-41.858055 146.697754,-41.853058 146.684418,-41.834999 146.680817,-41.83139 146.675812,-41.821671 146.674988,-41.815552 146.680267,-41.797783 146.683319,-41.792503 146.684418,-41.786949 146.691071,-41.778336 146.69693,-41.775558 146.704132,-41.776947 146.708588,-41.781387 146.714691,-41.789726 146.722748,-41.797226 146.728027,-41.800835 146.733582,-41.803055 146.75,-41.804718 146.761658,-41.816666 146.766663,-41.826393 146.772217,-41.828613 146.780548,-41.828613 146.808319,-41.821671 146.815796,-41.820557 146.823029,-41.822777 146.825531,-41.833061 146.824677,-41.853615 146.822754,-41.858894 146.816925,-41.868607 146.80304,-41.871666 146.786377,-41.872772 146.777191,-41.872498 146.764984,-41.876389 146.761108,-41.880554 146.759979,-41.886665 146.762207,-41.898338 146.767487,-41.908607 146.774414,-41.917221 146.779694,-41.927498 146.777771,-41.93222 146.765259,-41.943611 146.754425,-41.963333 146.749695,-41.96666 146.732727,-41.974716 146.728027,-41.97805 146.703857,-41.987778 146.697205,-41.988892</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1066494066</topp:AREA>
+ <topp:PERIMETER>1071999090</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.5">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.899719,-42.032776 146.892487,-42.030556 146.886932,-42.027779 146.882446,-42.02417 146.87912,-42.018608 146.878571,-42.006111 146.876892,-42 146.871338,-41.99778 146.864136,-41.996391 146.859406,-41.993614 146.855225,-41.983055 146.856354,-41.976944 146.866913,-41.963615 146.871613,-41.959999 146.883881,-41.955559 146.88858,-41.951668 146.891663,-41.947495 146.893585,-41.941383 146.88858,-41.92556 146.887756,-41.919167 146.888031,-41.912498 146.891937,-41.907776 146.896637,-41.904999 146.90387,-41.906387 146.907471,-41.910828 146.911652,-41.922501 146.914429,-41.926666 146.919708,-41.929443 146.926361,-41.931671 146.953033,-41.931389 146.961365,-41.93222 146.968567,-41.933884 146.973846,-41.936661 146.983032,-41.943611 146.985504,-41.948334 146.987183,-41.953888 146.982178,-41.965004 146.972748,-41.978333 146.971619,-41.983887 146.966644,-41.99472 146.963593,-41.999443 146.958862,-42.003616 146.956085,-42.007782 146.946625,-42.015007 146.940796,-42.016945 146.932739,-42.016113 146.926086,-42.018059 146.921356,-42.022224 146.914703,-42.030281 146.90802,-42.032219 146.899719,-42.032776</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1065512599</topp:AREA>
+ <topp:PERIMETER>1071304933</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.6">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">147.149719,-42.203056 147.142212,-42.201668 147.131348,-42.195831 147.127747,-42.191666 147.125244,-42.186111 147.12439,-42.180832 147.126343,-42.175278 147.132172,-42.165833 147.136108,-42.16111 147.137207,-42.155556 147.135529,-42.149994 147.12912,-42.14167 147.126617,-42.136948 147.128845,-42.124443 147.12912,-42.117775 147.122742,-42.115555 147.11441,-42.116112 147.101349,-42.120552 147.093842,-42.119164 147.092194,-42.114449 147.093292,-42.108337 147.097198,-42.097221 147.103302,-42.080833 147.108307,-42.07 147.112183,-42.066666 147.117737,-42.063889 147.124115,-42.061943 147.131622,-42.060829 147.138031,-42.063614 147.140808,-42.06778 147.145264,-42.071945 147.150818,-42.074173 147.159973,-42.074173 147.16748,-42.073059 147.180542,-42.069725 147.188873,-42.069168 147.19693,-42.07 147.209686,-42.075005 147.216919,-42.082779 147.219696,-42.087502 147.219421,-42.094444 147.216644,-42.099167 147.211914,-42.103333 147.190521,-42.106949 147.185791,-42.110283 147.182739,-42.115005 147.180542,-42.127495 147.180267,-42.134445 147.18219,-42.140556 147.182739,-42.146111 147.187744,-42.16861 147.188568,-42.175003 147.187195,-42.187775 147.184143,-42.192772 147.180542,-42.196663 147.169128,-42.201942 147.149719,-42.203056</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1065646817</topp:AREA>
+ <topp:PERIMETER>1071606923</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+ <gml:featureMember>
+ <topp:tasmania_water_bodies fid="tasmania_water_bodies.7">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">146.240784,-42.851112 146.231628,-42.850838 146.228027,-42.846664 146.218842,-42.83889 146.214691,-42.831116 146.206635,-42.823334 146.195801,-42.810829 146.173859,-42.77861 146.171356,-42.773888 146.169708,-42.768333 146.166382,-42.762779 146.160522,-42.748886 146.155243,-42.739166 146.151642,-42.735001 146.142761,-42.727776 146.127747,-42.725555 146.118561,-42.72583 146.111908,-42.726944 146.096344,-42.736389 146.09079,-42.738892 146.082184,-42.739998 146.077759,-42.737221 146.074127,-42.733055 146.060791,-42.722221 146.053314,-42.720833 146.041077,-42.725273 146.031372,-42.731941 146.01886,-42.736946 146.011383,-42.738335 145.994415,-42.739166 145.979675,-42.736115 145.974121,-42.733055 145.971619,-42.728333 145.973022,-42.721382 145.976074,-42.71666 145.985504,-42.709999 146.001923,-42.701668 146.011658,-42.695 146.01944,-42.686943 146.022491,-42.681671 146.02359,-42.676109 146.023041,-42.669724 146.01886,-42.65889 146.018311,-42.653328 146.014984,-42.642227 146.014435,-42.636116 146.016937,-42.623611 146.020813,-42.61972 146.025543,-42.616394 146.032196,-42.614449 146.03775,-42.6175 146.041351,-42.621666 146.043854,-42.626389 146.046936,-42.637505 146.048309,-42.649994 146.048035,-42.656662 146.049133,-42.669167 146.05246,-42.680832 146.054962,-42.684998 146.060516,-42.688049 146.068848,-42.6875 146.073853,-42.683609 146.075806,-42.678886 146.078308,-42.666389 146.079407,-42.645836 146.084412,-42.61528 146.088867,-42.604172 146.095795,-42.595001 146.099701,-42.590836 146.104401,-42.587502 146.111908,-42.589165 146.115234,-42.59333 146.116058,-42.599442 146.113586,-42.612503 146.113281,-42.619446 146.113861,-42.626106 146.113281,-42.639168 146.11441,-42.652222 146.116058,-42.657219 146.11969,-42.661385 146.126068,-42.664162 146.13443,-42.665276 146.161377,-42.665276 146.16803,-42.662498 146.171082,-42.658607 146.172211,-42.652496 146.173035,-42.638611 146.172211,-42.6325 146.173309,-42.611946 146.17746,-42.600281 146.183594,-42.590836 146.195801,-42.586388 146.203308,-42.585274 146.210785,-42.586662 146.21524,-42.590279 146.221344,-42.599998 146.221893,-42.60556 146.2258,-42.622223 146.228577,-42.626945 146.235779,-42.628609 146.247192,-42.623329 146.253876,-42.621384 146.261383,-42.623055 146.265808,-42.62722 146.267487,-42.631943 146.268036,-42.638893 146.264435,-42.656662 146.261658,-42.661385 146.254974,-42.66333 146.246613,-42.662498 146.240784,-42.665001 146.236084,-42.668335 146.233032,-42.673058 146.232727,-42.68 146.236084,-42.684723 146.241638,-42.686943 146.257477,-42.688889 146.265808,-42.688889 146.279694,-42.692772 146.283325,-42.696945 146.288574,-42.706665 146.291931,-42.710831 146.296356,-42.714447 146.307465,-42.720833 146.311096,-42.724442 146.308868,-42.730553 146.304962,-42.734444 146.293579,-42.739166 146.293304,-42.745834 146.29776,-42.749443 146.303314,-42.752502 146.306915,-42.756393 146.309418,-42.761391 146.315521,-42.769722 146.323578,-42.777496 146.334412,-42.790001 146.338867,-42.793617 146.346069,-42.801941 146.348572,-42.806946 146.349121,-42.813057 146.345245,-42.817223 146.340515,-42.820557 146.333862,-42.822227 146.324402,-42.822227 146.3172,-42.820557 146.306091,-42.815002 146.295532,-42.802223 146.28775,-42.787781 146.282196,-42.784729 146.273865,-42.785278 146.268036,-42.787781 146.263306,-42.791115 146.256378,-42.799995 146.254974,-42.806664 146.25415,-42.826668 146.251648,-42.83889 146.249695,-42.844444 146.246613,-42.849167 146.240784,-42.851112</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:AREA>1067743969</topp:AREA>
+ <topp:PERIMETER>1073212293</topp:PERIMETER>
+ <topp:WATER_TYPE>Lake</topp:WATER_TYPE>
+ <topp:CNTRY_NAME>Australia</topp:CNTRY_NAME>
+ <topp:CONTINENT>Australia</topp:CONTINENT>
+ </topp:tasmania_water_bodies>
+ </gml:featureMember>
+</wfs:FeatureCollection>
diff --git a/misc/openlayers/examples/tasmania/sld-tasmania.xml b/misc/openlayers/examples/tasmania/sld-tasmania.xml
new file mode 100644
index 0000000..1c41225
--- /dev/null
+++ b/misc/openlayers/examples/tasmania/sld-tasmania.xml
@@ -0,0 +1,594 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<sld:StyledLayerDescriptor version="1.0.0"
+ xmlns:sld="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">
+ <sld:NamedLayer>
+ <sld:Name>WaterBodies</sld:Name>
+ <sld:UserStyle>
+ <sld:Name>Default Styler</sld:Name>
+ <sld:Title>Default Styler (zoom in to see more objects)</sld:Title>
+ <sld:Abstract></sld:Abstract>
+ <sld:IsDefault>1</sld:IsDefault>
+ <sld:FeatureTypeStyle>
+ <sld:Name>testStyleName</sld:Name>
+ <sld:Title>title</sld:Title>
+ <sld:Abstract>abstract</sld:Abstract>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <sld:Rule>
+ <sld:Name>testRuleName</sld:Name>
+ <sld:Title>title</sld:Title>
+ <sld:Abstract>Abstract</sld:Abstract>
+ <ogc:Filter>
+ <ogc:FeatureId fid="tasmania_water_bodies.2" />
+ <ogc:FeatureId fid="tasmania_water_bodies.3" />
+ </ogc:Filter>
+ <sld:MaxScaleDenominator>3000000</sld:MaxScaleDenominator>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">blue</sld:CssParameter>
+ <sld:CssParameter name="fill-opacity">
+ <ogc:Literal>1.0</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">
+ <ogc:Literal>#C0C0C0</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-linecap">
+ <ogc:Literal>butt</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-linejoin">
+ <ogc:Literal>miter</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-opacity">
+ <ogc:Literal>1</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-width">
+ <ogc:Literal>1</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-dashoffset">
+ <ogc:Literal>0</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Stroke>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ <sld:Rule>
+ <sld:Name>testRuleNameElse</sld:Name>
+ <sld:Title>title</sld:Title>
+ <sld:Abstract>Abstract</sld:Abstract>
+ <sld:ElseFilter/>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">#aaaaff</sld:CssParameter>
+ <sld:CssParameter name="fill-opacity">
+ <ogc:Literal>0.5</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">
+ <ogc:Literal>#C0C0C0</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-opacity">
+ <ogc:Literal>1</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-width">
+ <ogc:Literal>1</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Stroke>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Hover Styler</sld:Name>
+ <sld:Title>Hover Styler</sld:Title>
+ <sld:Abstract></sld:Abstract>
+ <sld:FeatureTypeStyle>
+ <sld:Name>testStyleHover</sld:Name>
+ <sld:Title>title</sld:Title>
+ <sld:Abstract>abstract</sld:Abstract>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <sld:Rule>
+ <sld:Name>testRuleNameHover</sld:Name>
+ <sld:Title>title</sld:Title>
+ <sld:Abstract>Abstract</sld:Abstract>
+ <ogc:Filter>
+ <ogc:Not>
+ <ogc:Or>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>PERIMETER</ogc:PropertyName>
+ <ogc:Literal>1071304933</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ <ogc:PropertyIsLessThan>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>1065512599</ogc:Literal>
+ </ogc:PropertyIsLessThan>
+ </ogc:Or>
+ </ogc:Not>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>black</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="fill-opacity">
+ <ogc:Literal>0.5</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">
+ <ogc:Literal>green</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-linecap">
+ <ogc:Literal>butt</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-linejoin">
+ <ogc:Literal>miter</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-opacity">
+ <ogc:Literal>0.5</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-width">
+ <ogc:Literal>5</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-dashoffset">
+ <ogc:Literal>0</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Stroke>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ <sld:Rule>
+ <sld:Name>testRuleNameHoverElse</sld:Name>
+ <sld:Title>title</sld:Title>
+ <sld:Abstract>Abstract</sld:Abstract>
+ <sld:ElseFilter/>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>black</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="fill-opacity">
+ <ogc:Literal>0.5</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">
+ <ogc:Literal>fuchsia</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-opacity">
+ <ogc:Literal>0.5</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-width">
+ <ogc:Literal>5</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-dashoffset">
+ <ogc:Literal>0</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Stroke>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Attribute Filter Styler</sld:Name>
+ <sld:Title>Attribute Filter Styler</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>rulePropertyIsEqualTo</sld:Name>
+ <sld:Title>rulePropertyIsEqualTo</sld:Title>
+ <sld:Abstract>rulePropertyIsEqualTo</sld:Abstract>
+ <ogc:Filter>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>name</ogc:PropertyName>
+ <ogc:Literal>My simple Polygon</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>#000033</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsEqualTo</sld:Name>
+ <sld:Title>Styler Test PropertyIsEqualTo</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>rulePropertyIsEqualTo</sld:Name>
+ <sld:Title>rulePropertyIsEqualTo</sld:Title>
+ <sld:Abstract>rulePropertyIsEqualTo</sld:Abstract>
+ <ogc:Filter>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>1067743969</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>red</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test WATER_TYPE</sld:Name>
+ <sld:Title>Styler Test WATER_TYPE</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>rulePropertyIsEqualTo</sld:Name>
+ <sld:Title>rulePropertyIsEqualTo</sld:Title>
+ <sld:Abstract>rulePropertyIsEqualTo</sld:Abstract>
+ <ogc:Filter>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>WATER_TYPE</ogc:PropertyName>
+ <ogc:Literal>Lake</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>red</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsGreaterThanOrEqualTo</sld:Name>
+ <sld:Title>Styler Test PropertyIsGreaterThanOrEqualTo</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>PropertyIsGreaterThanOrEqualTo</sld:Name>
+ <sld:Title>PropertyIsGreaterThanOrEqualTo</sld:Title>
+ <sld:Abstract>PropertyIsGreaterThanOrEqualTo</sld:Abstract>
+ <ogc:Filter>
+ <ogc:And>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>WATER_TYPE</ogc:PropertyName>
+ <ogc:Literal>Lake</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ <ogc:PropertyIsGreaterThanOrEqualTo>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>1067509088</ogc:Literal>
+ </ogc:PropertyIsGreaterThanOrEqualTo>
+ </ogc:And>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>yellow</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsLessThanOrEqualTo</sld:Name>
+ <sld:Title>Styler Test PropertyIsLessThanOrEqualTo</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>PropertyIsLessThanOrEqualTo</sld:Name>
+ <sld:Title>PropertyIsLessThanOrEqualTo</sld:Title>
+ <sld:Abstract>PropertyIsLessThanOrEqualTo</sld:Abstract>
+ <ogc:Filter>
+ <ogc:And>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>WATER_TYPE</ogc:PropertyName>
+ <ogc:Literal>Lake</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ <ogc:PropertyIsLessThanOrEqualTo>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>1067509088</ogc:Literal>
+ </ogc:PropertyIsLessThanOrEqualTo>
+ </ogc:And>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>yellow</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsGreaterThan</sld:Name>
+ <sld:Title>Styler Test PropertyIsGreaterThan</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>PropertyIsGreaterThan</sld:Name>
+ <sld:Title>PropertyIsGreaterThan</sld:Title>
+ <sld:Abstract>PropertyIsGreaterThan</sld:Abstract>
+ <ogc:Filter>
+ <ogc:And>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>WATER_TYPE</ogc:PropertyName>
+ <ogc:Literal>Lake</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ <ogc:PropertyIsGreaterThan>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>1067000000</ogc:Literal>
+ </ogc:PropertyIsGreaterThan>
+ </ogc:And>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>yellow</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsLessThan</sld:Name>
+ <sld:Title>Styler Test PropertyIsLessThan</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>PropertyIsLessThan</sld:Name>
+ <sld:Title>PropertyIsLessThan</sld:Title>
+ <sld:Abstract>PropertyIsLessThan</sld:Abstract>
+ <ogc:Filter>
+ <ogc:And>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>WATER_TYPE</ogc:PropertyName>
+ <ogc:Literal>Lake</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ <ogc:PropertyIsLessThan>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>1067000000</ogc:Literal>
+ </ogc:PropertyIsLessThan>
+ </ogc:And>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>yellow</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsLike</sld:Name>
+ <sld:Title>Styler Test PropertyIsLike</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>PropertyIsLike</sld:Name>
+ <sld:Title>PropertyIsLike</sld:Title>
+ <sld:Abstract>PropertyIsLike</sld:Abstract>
+ <ogc:Filter>
+ <ogc:PropertyIsLike wildCard='*' singleChar='.' escape='!'>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:Literal>106774*</ogc:Literal>
+ </ogc:PropertyIsLike>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>green</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>Styler Test PropertyIsBetween</sld:Name>
+ <sld:Title>Styler Test PropertyIsBetween</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>attribute filter type</sld:Name>
+ <sld:Title>attribute filter type</sld:Title>
+ <sld:FeatureTypeName>Feature</sld:FeatureTypeName>
+ <sld:SemanticTypeIdentifier>generic:geometry</sld:SemanticTypeIdentifier>
+ <!-- Attribute filters -->
+ <sld:Rule>
+ <sld:Name>PropertyIsBetween</sld:Name>
+ <sld:Title>PropertyIsBetween</sld:Title>
+ <sld:Abstract>PropertyIsBetween</sld:Abstract>
+ <ogc:Filter>
+ <ogc:PropertyIsBetween>
+ <ogc:PropertyName>AREA</ogc:PropertyName>
+ <ogc:LowerBoundary>
+ <ogc:Literal>1064866676</ogc:Literal>
+ </ogc:LowerBoundary>
+ <ogc:UpperBoundary>
+ <ogc:Literal>1065512599</ogc:Literal>
+ </ogc:UpperBoundary>
+ </ogc:PropertyIsBetween>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">
+ <ogc:Literal>blue</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ <sld:UserStyle>
+ <sld:Name>FeatureId</sld:Name>
+ <sld:Title>Styler Test FeatureId</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <ogc:Filter>
+ <ogc:FeatureId fid="tasmania_water_bodies.4"/>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">blue</sld:CssParameter>
+ </sld:Fill>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+
+ </sld:NamedLayer>
+
+ <sld:NamedLayer>
+ <sld:Name>Roads</sld:Name>
+ <sld:UserStyle>
+ <sld:Name>RoadsDefault</sld:Name>
+ <sld:IsDefault>1</sld:IsDefault>
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <sld:Name>justAStyler</sld:Name>
+ <sld:LineSymbolizer>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">
+ <ogc:Literal>red</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-width">
+ <ogc:Literal>2</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Stroke>
+ </sld:LineSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+ </sld:NamedLayer>
+
+ <sld:NamedLayer>
+ <sld:Name>Cities</sld:Name>
+ <sld:UserStyle>
+ <sld:Name>DefaultCities</sld:Name>
+ <sld:IsDefault>1</sld:IsDefault>
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <ogc:Filter>
+ <ogc:FeatureId fid="tasmania_cities.1"/>
+ </ogc:Filter>
+ <sld:PointSymbolizer>
+ <sld:Graphic>
+ <sld:ExternalGraphic>
+ <sld:OnlineResource xlink:href="../img/marker.png" />
+ <sld:Format>image/png</sld:Format>
+ </sld:ExternalGraphic>
+ <sld:Opacity>0.7</sld:Opacity>
+ <sld:Size>14</sld:Size>
+ </sld:Graphic>
+ </sld:PointSymbolizer>
+ </sld:Rule>
+ <sld:Rule>
+ <sld:ElseFilter/>
+ <sld:PointSymbolizer>
+ <sld:Graphic>
+ <sld:Mark>
+ <sld:WellKnownName>cross</sld:WellKnownName>
+ </sld:Mark>
+ <sld:Size>10</sld:Size>
+ </sld:Graphic>
+ </sld:PointSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+ </sld:NamedLayer>
+
+ <sld:NamedLayer>
+ <sld:Name>Land</sld:Name>
+ <sld:UserStyle>
+ <sld:Name>Land Style</sld:Name>
+ <sld:IsDefault>1</sld:IsDefault>
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">#ccffaa</sld:CssParameter>
+ <sld:CssParameter name="fill-opacity">
+ <ogc:Literal>0.5</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Fill>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">
+ <ogc:Literal>#C0C0C0</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-opacity">
+ <ogc:Literal>1</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-width">
+ <ogc:Literal>1</ogc:Literal>
+ </sld:CssParameter>
+ <sld:CssParameter name="stroke-dasharray">
+ <ogc:Literal>3 5 1 5</ogc:Literal>
+ </sld:CssParameter>
+ </sld:Stroke>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+ </sld:NamedLayer>
+
+</sld:StyledLayerDescriptor>
diff --git a/misc/openlayers/examples/teleportation.html b/misc/openlayers/examples/teleportation.html
new file mode 100644
index 0000000..3154c56
--- /dev/null
+++ b/misc/openlayers/examples/teleportation.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Teleporter Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+
+ <style type="text/css">
+ #wrapper {
+ position: relative;
+ }
+ .spot1 {
+ width: 250px;
+ }
+ .spot2 {
+ width: 300px;
+ position: absolute;
+ left: 300px;
+ top: 0;
+ }
+ </style>
+
+ <script type="text/javascript">
+
+ var map, layer, spot=1;
+ function init(){
+ map = new OpenLayers.Map({
+ div: "spot1"
+ });
+ map.addControl(new OpenLayers.Control.OverviewMap());
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomTo(2);
+ }
+
+ function teleport() {
+ if (spot == 1) {
+ spot = 2;
+ } else {
+ spot = 1;
+ }
+ map.render("spot" + spot);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Map "Teleportation" and Rendering</h1>
+
+ <div id="tags">
+ map, rendering
+ </div>
+
+ <p id="shortdesc">Call the map's render method to change its container.</p>
+
+ <div id="wrapper">
+ <div id="spot1" class="smallmap spot1"></div>
+ <div id="spot2" class="smallmap spot2"></div>
+ </div>
+
+ <input type="button" onclick="teleport()" value="Teleport!"></input>
+
+ <div id="docs">
+ <p>This example demonstrates how a map can be rendered initially in one
+ container and then moved to a new container. At any point after map
+ construction, the map's render method can be called with the id of
+ an empty container, moving the map to the new container.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/textfile.txt b/misc/openlayers/examples/textfile.txt
new file mode 100644
index 0000000..f7678c4
--- /dev/null
+++ b/misc/openlayers/examples/textfile.txt
@@ -0,0 +1,4 @@
+point title description icon
+10,20 my orange title my orange description
+2,4 my aqua title my aqua description
+42,-71 my purple title my purple description<br/>is great. http://www.openlayers.org/api/img/zoom-world-mini.png
diff --git a/misc/openlayers/examples/tile-origin.html b/misc/openlayers/examples/tile-origin.html
new file mode 100644
index 0000000..6e97aeb
--- /dev/null
+++ b/misc/openlayers/examples/tile-origin.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Tile Origin Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">Tile Origin</h1>
+ <div id="tags">
+ grid, tileOrigin, light
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of the tileExtent property to differentiate
+ between the maximum extent and the tile extent for a layer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This example uses a layer that requests map tiles from a WMS
+ that only generates image responses for requests that align with
+ a particular tile lattice. In this case, the layer's
+ <code>maxExtent</code> does not align with that tile lattice.
+ To configure the layer with a tile extent that conforms to the
+ tile origin configured on the server, use the layer's
+ <code>tileOrigin</code> property.
+ </p><p>
+ View the <a href="tile-origin.js" target="_blank">tile-origin.js</a>
+ source to see how this is done
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="tile-origin.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/tile-origin.js b/misc/openlayers/examples/tile-origin.js
new file mode 100644
index 0000000..61c5b8e
--- /dev/null
+++ b/misc/openlayers/examples/tile-origin.js
@@ -0,0 +1,16 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ maxExtent: new OpenLayers.Bounds(-130, 30, -80, 55),
+ maxResolution: 360 / 256 / Math.pow(2, 4),
+ numZoomLevels: 12,
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "Global Imagery",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "bluemarble"},
+ {tileOrigin: new OpenLayers.LonLat(-180, -90)}
+ )
+ ],
+ center: new OpenLayers.LonLat(-110, 45),
+ zoom: 0
+});
diff --git a/misc/openlayers/examples/tilecache.html b/misc/openlayers/examples/tilecache.html
new file mode 100644
index 0000000..82f3a55
--- /dev/null
+++ b/misc/openlayers/examples/tilecache.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers TileCache Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map', {
+ resolutions: [0.087890625, 0.0439453125, 0.02197265625, 0.010986328125]
+ });
+ layer = new OpenLayers.Layer.TileCache("TileCache Layer",
+ ["http://c0.tilecache.osgeo.org/wms-c/cache/",
+ "http://c1.tilecache.osgeo.org/wms-c/cache/",
+ "http://c2.tilecache.osgeo.org/wms-c/cache/",
+ "http://c3.tilecache.osgeo.org/wms-c/cache/",
+ "http://c4.tilecache.osgeo.org/wms-c/cache/"],
+ "basic",
+ {
+ serverResolutions: [0.703125, 0.3515625, 0.17578125, 0.087890625,
+ 0.0439453125, 0.02197265625, 0.010986328125,
+ 0.0054931640625, 0.00274658203125, 0.001373291015625,
+ 0.0006866455078125, 0.00034332275390625, 0.000171661376953125,
+ 0.0000858306884765625, 0.00004291534423828125, 0.000021457672119140625]
+ }
+ );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">TileCache Example</h1>
+
+ <div id="tags">
+ tile, cache, tilecache, wmsc, wms-c
+ </div>
+
+ <p id="shortdesc">
+ Demonstrates a TileCache layer that loads tiles from from a web
+ accessible disk-based cache only.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This layer should be used for web accessible disk-based caches only.
+ It is not used to request new tiles from TileCache. Note that you
+ should specify resolutions explicitly on this layer so that they match
+ your TileCache configuration.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/tms.html b/misc/openlayers/examples/tms.html
new file mode 100644
index 0000000..ef4bf8c
--- /dev/null
+++ b/misc/openlayers/examples/tms.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Tiled Map Service Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map', {maxResolution:1.40625/2} );
+ layer = new OpenLayers.Layer.TMS( "TMS",
+ "http://tilecache.osgeo.org/wms-c/Basic.py/", {layername: 'basic', type:'png'} );
+ map.addLayer(layer);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+ function addTMS() {
+ l = new OpenLayers.Layer.TMS(
+ OpenLayers.Util.getElement('layer').value,
+ OpenLayers.Util.getElement('url').value,
+ {
+ 'layername': OpenLayers.Util.getElement('layer').value,
+ 'type': OpenLayers.Util.getElement('type').value
+ });
+ map.addLayer(l);
+ map.setBaseLayer(l);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Tiled Map Service Example</h1>
+
+ <div id="tags">
+ tile, cache, tms
+ </div>
+
+ <p id="shortdesc">
+ Demonstrate the initialization and modification of a Tiled Map Service layer.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ URL of TMS (Should end in /): <input type="text" id="url" size="60" value="http://tilecache.osgeo.org/wms-c/Basic.py/" /> layer_name <input type="text" id="layer" value="basic" /> <select id="type"><option>jpg</option><option>png</option></select> <input type="submit" onclick="addTMS()"/><br>
+ <p>
+ Example: http://tilecache.osgeo.org/wms-c/Basic.py/, basic, jpg<br>
+ The first input must be an HTTP URL pointing to a TMS instance. The second
+ input must be a layer name available from that instance, and the third must
+ be the output format used by that layer. (Any other behavior will result in
+ broken images being displayed.)
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/transform-feature.html b/misc/openlayers/examples/transform-feature.html
new file mode 100644
index 0000000..a0c5645
--- /dev/null
+++ b/misc/openlayers/examples/transform-feature.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Transformation Box</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ var map, control;
+
+ function init(){
+ map = new OpenLayers.Map('map', {allOverlays: true});
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ // the layer that we want to transform features on
+ var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", {
+ styleMap: new OpenLayers.StyleMap({
+ // a nice style for the transformation box
+ "transform": new OpenLayers.Style({
+ display: "${getDisplay}",
+ cursor: "${role}",
+ pointRadius: 5,
+ fillColor: "white",
+ fillOpacity: 1,
+ strokeColor: "black"
+ }, {
+ context: {
+ getDisplay: function(feature) {
+ // hide the resize handle at the south-east corner
+ return feature.attributes.role === "se-resize" ? "none" : "";
+ }
+ }
+ }),
+ "rotate": new OpenLayers.Style({
+ display: "${getDisplay}",
+ pointRadius: 10,
+ fillColor: "#ddd",
+ fillOpacity: 1,
+ strokeColor: "black"
+ }, {
+ context: {
+ getDisplay: function(feature) {
+ // only display the rotate handle at the south-east corner
+ return feature.attributes.role === "se-rotate" ? "" : "none";
+ }
+ }
+ })
+ }),
+ renderers: renderer
+ });
+
+ // create the TransformFeature control, using the renderIntent
+ // from above
+ control = new OpenLayers.Control.TransformFeature(vectorLayer, {
+ renderIntent: "transform",
+ rotationHandleSymbolizer: "rotate"
+ });
+ map.addControl(control);
+
+ // create a polygon feature from a linear ring of points
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointList = [];
+ for(var p=0; p<6; ++p) {
+ var a = p * (2 * Math.PI) / 7;
+ var r = Math.random(1) + 2;
+ var newPoint = new OpenLayers.Geometry.Point(point.x + (r * Math.cos(a)),
+ point.y + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+
+ var linearRing = new OpenLayers.Geometry.LinearRing(pointList);
+ var polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([linearRing]));
+
+
+ map.addLayer(vectorLayer);
+ map.setCenter(new OpenLayers.LonLat(point.x, point.y), 5);
+ var anotherFeature = polygonFeature.clone();
+ polygonFeature.geometry.move(-3, 0);
+ anotherFeature.geometry.move(3, 0);
+ vectorLayer.addFeatures([polygonFeature, anotherFeature]);
+
+ // start with the transformation box on polygonFeature
+ control.setFeature(polygonFeature, {rotation: 45, scale: 0.5, ratio: 1.5});
+ }
+ </script>
+ </head>
+ <body onload="init()">
+<h1 id="title">Vector Feature Transformation Box Example</h1>
+
+<div id="tags">
+ vector, feature, transformation, stylemap
+</div>
+<p id="shortdesc">
+ Shows the use of the TransformFeature control.
+</p>
+<div style="text-align: right">
+ <div dir="rtl" id="map" class="smallmap"></div>
+</div>
+<div id="docs">
+ <p>This example shows transformation of vector features with a
+ tranformation box. Grab one of the handles to resize the feature.
+ Holding the SHIFT key will preserve the aspect ratio. Use the gray
+ handle to rotate the feature and hold the SHIFT key to only rotate
+ in 45° increments.
+ </p>
+ <p>In this example, the transformation box has been set on the left
+ feature, with a rotation preset of 45°. Clicking on the right feature
+ will set it for transformation, starting with an unrotated box.
+ Dragging a feature or the box edges will move it around.
+ </p>
+</div>
+
+ </body>
+</html>
+
diff --git a/misc/openlayers/examples/transition.html b/misc/openlayers/examples/transition.html
new file mode 100644
index 0000000..7d82b8b
--- /dev/null
+++ b/misc/openlayers/examples/transition.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Transitions Example</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map('mapDiv', {maxResolution: 'auto'});
+
+ var single_default_effect = new OpenLayers.Layer.WMS(
+ "WMS untiled default",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'},
+ {singleTile: true}
+ );
+ var single_resize_effect = new OpenLayers.Layer.WMS(
+ "WMS untiled resize",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'},
+ {singleTile: true, transitionEffect: 'resize'}
+ );
+ var tiled_default_effect = new OpenLayers.Layer.WMS(
+ "WMS tiled default ",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'}
+ );
+ var tiled_resize_effect = new OpenLayers.Layer.WMS(
+ "WMS tiled resize",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'},
+ {transitionEffect: 'resize'}
+ );
+
+ map.addLayers([single_default_effect, single_resize_effect,
+ tiled_default_effect, tiled_resize_effect]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(6.5, 40.5), 4);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Transition Example</h1>
+ <div id="tags">
+ transition, resize, tile, singletile, light
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of transition effects in tiled and untiled layers.
+ </p>
+ <div id="mapDiv" class="smallmap"></div>
+ <div id="docs">
+ There are two transitions that are currently implemented: null (the
+ default) and 'resize'. The default transition effect is used when no
+ transition is specified and is implemented as no transition effect except
+ for panning singleTile layers. The 'resize' effect resamples the current
+ tile and displays it stretched or compressed until the new tile is available.
+ <ul>
+ <li>The first layer is an untiled WMS layer with no transition effect.</li>
+ <li>The second layer is an untiled WMS layer with a 'resize' effect. </li>
+ <li>The third layer is a tiled WMS layer with no transition effect. </li>
+ <li>The fourth layer is a tiled WMS layer with a 'resize' effect. </li>
+ </ul>
+ </div>
+ </body>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/using-proj4js.html b/misc/openlayers/examples/using-proj4js.html
new file mode 100644
index 0000000..6883d9b
--- /dev/null
+++ b/misc/openlayers/examples/using-proj4js.html
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Using Proj4JS for vector reprojection</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script type="text/javascript" src="http://svn.osgeo.org/metacrs/proj4js/trunk/lib/proj4js-compressed.js"></script>
+ <script type="text/javascript" src="http://spatialreference.org/ref/epsg/31467/proj4js/"></script>
+ <script type="text/javascript" src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript" src="./using-proj4js.js"></script>
+ <style type="text/css">
+ ul {
+ width: 300px;
+ float: left;
+ }
+ ul li {
+ list-style: none;
+ margin-bottom: .2em;
+ }
+ input {
+ width: 250px;
+ }
+ #shortdesc {
+ margin-bottom: .5em;
+ }
+ #map {
+ width: 256px;
+ height: 256px;
+ float: left;
+ margin-right: .2em;
+ }
+ #attribution,
+ #mouse-position-31467,
+ #mouse-position-4326 {
+ float: left;
+ clear: left;
+ font-size: .8em;
+ color: #444;
+ }
+ .emph {
+ font-weight: bold;
+ }
+ </style>
+ </head>
+ <body onload="init();">
+ <h1 id="title">Using Proj4JS for vector reprojection</h1>
+ <div id="tags">
+ projection, proj, proj4js, reprojection, reproject,
+ transform, transformation, epsg, srs
+ </div>
+ <p id="shortdesc">
+ This example shows how to reproject vector features within
+ OpenLayers. The baselayer shows Germany in the projection
+ EPSG:31467 (GK 3). When one clicks on the buttons, features with
+ geometries originally in EPSG:4326 will be transformed to the
+ projection of the map on-the-fly.
+ </p>
+ <p>
+ The features are internally reprojected with the JavaScript library
+ <a href="http://proj4js.org/">Proj4JS</a>. Please note that usually
+ you would not inlude Proj4JS the way it is done in this example.
+ In a production environment you would furthermore have a local copy
+ of the Proj4JS-projection definition that is hotlinked in this
+ example (see Graticule example for how to do this).
+ </p>
+ <div id="map">
+ </div>
+ <ul>
+ <li>
+ <input type="button" value="Add Cologne (~ 6.97, 50.95)"
+ onclick="addVector(6.966667, 50.95, this);"
+ id="btnCologne">
+ </li>
+ <li>
+ <input type="button" value="Add Berlin (~ 13.40, 52.50)"
+ onclick="addVector(13.398889, 52.500556, this);"
+ id="btnBerlin">
+ </li>
+ <li>
+ <input type="button" value="Add Hamburg (~ 10.00, 53.57)"
+ onclick="addVector(10.001389, 53.565278, this);"
+ id="btnHamburg">
+ </li>
+ <li>
+ <input type="button" value="Add Munich (~ 11.57, 48.13)"
+ onclick="addVector(11.566667, 48.133333, this);"
+ id="btnMunich">
+ </li>
+ <li>
+ <input type="button" value="Add country outline (polygon)"
+ onclick="addOutline(this);" id="btnGermany">
+ </li>
+ <li>
+ <input type="button" value="...clear vector features"
+ onclick="clearVectors();">
+ </li>
+ <li>
+ <div id="status">
+ </div>
+ </li>
+ </ul>
+ <div id="attribution"></div>
+ <div id="mouse-position-4326"></div>
+ <div id="mouse-position-31467"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/using-proj4js.js b/misc/openlayers/examples/using-proj4js.js
new file mode 100644
index 0000000..044b872
--- /dev/null
+++ b/misc/openlayers/examples/using-proj4js.js
@@ -0,0 +1,132 @@
+var map, vector;
+
+function init(){
+ map = new OpenLayers.Map('map', {
+ projection: 'EPSG:31467',
+ maxResolution: 3457.03125,
+ units: 'm',
+ numZoomLevels: 1,
+ controls: [
+ new OpenLayers.Control.Attribution({
+ div: document.getElementById('attribution')
+ }),
+ new OpenLayers.Control.MousePosition({
+ div: document.getElementById('mouse-position-31467'),
+ prefix: 'Coordinates: ',
+ suffix: ' (in <a href="http://spatialreference.org/ref/epsg/'
+ + '31467/">EPSG:31467</a>)'
+ }),
+ new OpenLayers.Control.MousePosition({
+ div: document.getElementById('mouse-position-4326'),
+ displayProjection: new OpenLayers.Projection('EPSG:4326'),
+ prefix: 'Coordinates: ',
+ suffix: ' (in <a href="http://spatialreference.org/ref/epsg/'
+ + '4326/">EPSG:4326</a>)'
+ })
+ ],
+ maxExtent: new OpenLayers.Bounds(3146150, 5223600, 4031150, 6108600)
+ });
+ var germany_gk3 = new OpenLayers.Layer.WMS(
+ 'Germany (MetaSpatial)',
+ 'http://www.metaspatial.net/cgi-bin/germany-wms',
+ {
+ layers: 'germany'
+ },
+ {
+ singleTile: true,
+ ratio: 1.0,
+ attribution: 'Background WMS offered without restrictions by '
+ + '<a href="http://www.metaspatial.net/">MetaSpatial</a>'
+ }
+ );
+
+ vector = new OpenLayers.Layer.Vector();
+ map.addLayers( [ germany_gk3, vector ] );
+
+ map.zoomToMaxExtent();
+}
+
+function addVector(x, y, btn){
+ var status = "Transformed ",
+ geometry = new OpenLayers.Geometry.Point(x, y);
+
+ status += '<br /><code class="emph"> '
+ + geometry.toString() + '</code> to';
+
+ geometry.transform(
+ new OpenLayers.Projection('EPSG:4326'),
+ new OpenLayers.Projection('EPSG:31467')
+ );
+
+ status += '<br /><code class="emph"> '
+ + geometry.toString() + '</code>.';
+ document.getElementById('status').innerHTML = status;
+
+ var feature = new OpenLayers.Feature.Vector(geometry, {}, {
+ strokeColor: '#333333',
+ strokeOpacity: 1,
+ strokeWidth: 2,
+ fillColor: '#9966cc',
+ fillOpacity: 0.9,
+ pointRadius: 12,
+ graphicName: 'star'
+ });
+ vector.addFeatures([feature]);
+ btn.disabled = true;
+}
+function addOutline(btn) {
+ var wkt = 'POLYGON(('
+ + ' 9.921906 54.983104, 9.939580 54.596642,'
+ + '10.950112 54.363607,10.939467 54.008693,11.956252 54.196486,'
+ + '12.518440 54.470371,13.647467 54.075511,14.119686 53.757029,'
+ + '14.353315 53.248171,14.074521 52.981263,14.437600 52.624850,'
+ + '14.685026 52.089947,14.607098 51.745188,15.016996 51.106674,'
+ + '14.570718 51.002339,14.307013 51.117268,14.056228 50.926918,'
+ + '13.338132 50.733234,12.966837 50.484076,12.240111 50.266338,'
+ + '12.415191 49.969121,12.521024 49.547415,13.031329 49.307068,'
+ + '13.595946 48.877172,13.243357 48.416115,12.884103 48.289146,'
+ + '13.025851 47.637584,12.932627 47.467646,12.620760 47.672388,'
+ + '12.141357 47.703083,11.426414 47.523766,10.544504 47.566399,'
+ + '10.402084 47.302488, 9.896068 47.580197, 9.594226 47.525058,'
+ + ' 8.522612 47.830828, 8.317301 47.613580, 7.466759 47.620582,'
+ + ' 7.593676 48.333019, 8.099279 49.017784, 6.658230 49.201958,'
+ + ' 6.186320 49.463803, 6.242751 49.902226, 6.043073 50.128052,'
+ + ' 6.156658 50.803721, 5.988658 51.851616, 6.589397 51.852029,'
+ + ' 6.842870 52.228440, 7.092053 53.144043, 6.905140 53.482162,'
+ + ' 7.100425 53.693932, 7.936239 53.748296, 8.121706 53.527792,'
+ + ' 8.800734 54.020786, 8.572118 54.395646, 8.526229 54.962744,'
+ + ' 9.282049 54.830865, 9.921906 54.983104))',
+ feature = new OpenLayers.Format.WKT().read(wkt),
+ geometry = feature.geometry.transform(
+ new OpenLayers.Projection('EPSG:4326'),
+ new OpenLayers.Projection('EPSG:31467')
+ ),
+ style = {
+ strokeColor: '#9966cc',
+ strokeOpacity: 1,
+ strokeWidth: 5,
+ fillColor: '#ffffff',
+ fill: false
+ },
+ transformedFeature = new OpenLayers.Feature.Vector(geometry, {}, style);
+
+ vector.addFeatures([transformedFeature]);
+ document.getElementById('status').innerHTML = 'Transformed polygon';
+ btn.disabled = true;
+}
+
+function clearVectors(){
+ vector.removeAllFeatures();
+ var ids = [
+ 'btnCologne',
+ 'btnBerlin',
+ 'btnHamburg',
+ 'btnMunich',
+ 'btnGermany'
+ ];
+ for (var i = 0, len = ids.length; i < len; i++) {
+ var elem = document.getElementById(ids[i]);
+ elem.disabled = false;
+ }
+ document.getElementById('status').innerHTML = '';
+}
diff --git a/misc/openlayers/examples/utfgrid-geography-class.html b/misc/openlayers/examples/utfgrid-geography-class.html
new file mode 100644
index 0000000..43d5222
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid-geography-class.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers UTFGrid Geography Class</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #flag {
+ position: relative;
+ z-index: 999;
+ height: 0px;
+ width: 0px;
+ -moz-transition: all 0.1s linear;
+ -webkit-transition: all 0.1s linear;
+ }
+ #flag img {
+ position: absolute;
+ width: 80px;
+ -moz-box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.3);
+ -webkit-box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.3);
+ box-shadow: 2px 2px 1px 1px rgba(0, 0, 0, 0.3);
+ }
+ .olControlAttribution {
+ bottom: 5px;
+ font-size: 9px;
+ }
+ </style>
+</head>
+<body>
+ <h1 id="title">OpenLayers UTFGrid Geography Class Example</h1>
+
+ <div id="shortdesc">
+ This page demonstrates the use of the OpenLayers UTFGrid Controls.
+ </div>
+ <div id="map" class="smallmap">
+ <div id="flag"></div>
+ </div>
+ <p>Point to a country and try to guess the name before it shows up: <strong id="output">&nbsp;</strong>
+ <div id="docs">
+ <p>
+ See the <a href="utfgrid-geography-class.js" target="_blank">utfgrid-geography-class.js</a> source for
+ detail on using UTFGrids in OpenLayers.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="utfgrid-geography-class.js"></script>
+</body>
+</html>
diff --git a/misc/openlayers/examples/utfgrid-geography-class.js b/misc/openlayers/examples/utfgrid-geography-class.js
new file mode 100644
index 0000000..9377df6
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid-geography-class.js
@@ -0,0 +1,62 @@
+var osm = new OpenLayers.Layer.XYZ(
+ "MapQuest OSM",
+ [
+ "http://otile1.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
+ "http://otile2.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
+ "http://otile3.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png",
+ "http://otile4.mqcdn.com/tiles/1.0.0/osm/${z}/${x}/${y}.png"
+ ],
+ {transitionEffect: "resize", wrapDateLine: true}
+);
+
+var utfgrid = new OpenLayers.Layer.UTFGrid({
+ url: "utfgrid/geography-class/${z}/${x}/${y}.grid.json",
+ utfgridResolution: 4, // default is 2
+ displayInLayerSwitcher: false
+});
+
+var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ numZoomLevels: 3,
+ layers: [osm, utfgrid],
+ controls: [
+ new OpenLayers.Control.Navigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Zoom()
+ ],
+ center: [0, 0],
+ zoom: 1
+});
+
+var output = document.getElementById("output");
+var flag = document.getElementById("flag");
+
+var callback = function(infoLookup, loc, pixel) {
+ var msg = "";
+ if (infoLookup) {
+ var info;
+ for (var idx in infoLookup) {
+ // idx can be used to retrieve layer from map.layers[idx]
+ info = infoLookup[idx];
+ if (info && info.data) {
+ output.innerHTML = info.data.admin;
+ flag.innerHTML = "<img src='data:image/png;base64," + info.data.flag_png + "'>";
+ flag.style.left = (pixel.x + 15) + "px";
+ flag.style.top = (pixel.y + 15) + "px";
+ } else {
+ output.innerHTML = flag.innerHTML = "&nbsp;";
+ }
+ }
+ }
+};
+
+var control = new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ handlerMode: "move"
+});
+
+map.addControl(control);
diff --git a/misc/openlayers/examples/utfgrid.html b/misc/openlayers/examples/utfgrid.html
new file mode 100644
index 0000000..4ed6ef0
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers UTFGrid Demo</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #attrs {
+ height: 1.5em;
+ }
+ #controlToggle li { list-style: none; }
+ </style>
+</head>
+<body>
+ <h1 id="title">OpenLayers UTFGrid Demo</h1>
+
+ <div>
+ <div id="shortdesc">
+ This page demonstrates the use of the OpenLayers UTFGrid Controls.
+ </div>
+ <div id="map" class="smallmap"></div>
+ <p>
+ When the selected event is triggered, the underlying feature
+ attributes are shown below.
+ </p>
+ <div id="attrs">&nbsp;</div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="move" id="moveHandler"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="moveHandler">Move</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="hover" id="hoverHandler"
+ onclick="toggleControl(this);" />
+ <label for="hoverHandler">Hover</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="click" id="clickHandler"
+ onclick="toggleControl(this);" />
+ <label for="clickHandler">Click</label>
+ </li>
+ </ul>
+ </div>
+ <div id="docs">
+ <p>UTFGrids can be used to output highly optimized feature "hit grids."
+ The UTFGrid encoding scheme encodes interactivity data for a tile in a
+ space efficient manner. It is designed to be used in browsers for
+ interactive features like displaying tooltips without having to hit the
+ server for an "info query."
+ </p>
+ <p>
+ See the <a href="utfgrid.js" target="_blank">utfgrid.js source</a> for
+ detail on using UTFGrids in OpenLayers. For more info, view the
+ <a href="https://github.com/mapbox/utfgrid-spec">UTFGrid Specification</a>.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="utfgrid.js"></script>
+</body>
+</html>
diff --git a/misc/openlayers/examples/utfgrid.js b/misc/openlayers/examples/utfgrid.js
new file mode 100644
index 0000000..dc06c41
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid.js
@@ -0,0 +1,61 @@
+var osm = new OpenLayers.Layer.OSM();
+
+var utfgrid = new OpenLayers.Layer.UTFGrid({
+ url: "utfgrid/world_utfgrid/${z}/${x}/${y}.json",
+ utfgridResolution: 4, // default is 2
+ displayInLayerSwitcher: false
+});
+
+var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ controls: [],
+ layers: [osm, utfgrid],
+ center: [0, 0],
+ zoom: 1
+});
+
+var callback = function(infoLookup) {
+ var msg = "";
+ if (infoLookup) {
+ var info;
+ for (var idx in infoLookup) {
+ // idx can be used to retrieve layer from map.layers[idx]
+ info = infoLookup[idx];
+ if (info && info.data) {
+ msg += "[" + info.id + "] <strong>In 2005, " +
+ info.data.NAME + " had a population of " +
+ info.data.POP2005 + " people.</strong> ";
+ }
+ }
+ }
+ document.getElementById("attrs").innerHTML = msg;
+};
+
+var controls = {
+ move: new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ handlerMode: "move"
+ }),
+ hover: new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ handlerMode: "hover"
+ }),
+ click: new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ handlerMode: "click"
+ })
+};
+for (var key in controls) {
+ map.addControl(controls[key]);
+}
+
+function toggleControl(el) {
+ for (var c in controls) {
+ controls[c].deactivate();
+ }
+ controls[el.value].activate();
+}
+
+// activate the control that responds to mousemove
+toggleControl({value: "move"});
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/0.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/0.json
new file mode 100644
index 0000000..e1f305b
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/0.json
@@ -0,0 +1 @@
+{"keys": ["", "269", "270", "572", "271", "272", "585", "586", "273", "589", "573", "274", "275", "560", "558", "559", "562", "561", "279", "563", "566", "564", "281", "574", "565", "285", "286", "287", "576", "575", "1", "289", "569", "568", "567", "590", "295", "292", "294", "2", "299", "297", "578", "587", "556", "309", "570", "577", "313", "310", "312", "588", "315", "579", "592", "591", "557", "582", "580", "318", "319", "583", "321", "571", "584", "322", "323", "326", "325", "329", "332", "331", "336", "337", "611", "612", "339", "341", "617", "622", "623", "18", "349", "624", "350", "19", "20", "619", "625", "353", "357", "361", "5", "364", "359", "338", "620", "367", "370", "626", "365", "627", "376", "9", "7", "377", "378", "621", "383", "6", "11", "374", "380", "385", "394", "386", "396", "399", "398", "407", "400", "409", "412", "4", "24", "405", "427", "424", "420", "404", "431", "432", "433", "419", "429", "92", "117", "88", "440", "441", "94", "442", "91", "444", "97", "96", "95", "443", "439", "449", "446", "100", "451", "106", "109", "105", "103", "102", "456", "453", "450", "113", "112", "459", "114", "458", "461", "111", "467", "473", "118", "462", "474", "480", "479"], "data": {"623": {"dom_desc": "", "pro_desc": ""}, "622": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SMALL-LEAFED AND CONIFEROUS WOODED STEPPES OF CONTINENTAL CLIMATE"}, "621": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "620": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "627": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "571": {"dom_desc": "", "pro_desc": ""}, "626": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "24": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "1": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "20": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "624": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "289": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "573": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "405": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "404": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "4": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "400": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "281": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "5": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "285": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "349": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "287": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "286": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "453": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "577": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "575": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "420": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "269": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "574": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "378": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "412": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "299": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "370": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "294": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "295": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "292": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "374": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "377": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "376": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "591": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "586": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "319": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "318": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "587": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "313": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "312": {"dom_desc": "", "pro_desc": ""}, "310": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "584": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "315": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "270": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "271": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "117": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "273": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "111": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "275": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "113": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "112": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "279": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "399": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "398": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "118": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "429": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "7": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "367": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "364": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "365": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "424": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "427": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "361": {"dom_desc": "", "pro_desc": ""}, "570": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "309": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "449": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "585": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "582": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "583": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "580": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "443": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "442": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "441": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "440": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "446": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "588": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "444": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "380": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "109": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "385": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "386": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "297": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "102": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "103": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "100": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "589": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "106": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "105": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "419": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "383": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "88": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "439": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "432": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "433": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "431": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT"}, "458": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "459": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "579": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "578": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "339": {"dom_desc": "POLAR DOMAIN", "pro_desc": "MODERATE CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "338": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "625": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "590": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "450": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "451": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "337": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "336": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "331": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "576": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "456": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "332": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY CONTINENTAL AND CONTINENTAL CLIMATE"}, "592": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC MOSS-AND-GRASS TUNDRA"}, "407": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-MEADOW OF EASTERN OCEANIC (MONSOON CLIMATE)"}, "2": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "6": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "341": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "568": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "569": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "556": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "560": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "561": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "467": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "563": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "461": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "565": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "566": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "462": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "91": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "92": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "95": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "94": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "97": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "96": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "11": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "114": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "19": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "18": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "272": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "409": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "274": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "396": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "559": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "558": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "557": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC MOSS-AND-GRASS TUNDRA"}, "394": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "322": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "323": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "321": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "326": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MIXED CONIFEROUS AND SMALL-LEAFED FOREST"}, "325": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "9": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "329": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "562": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "619": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "612": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "564": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "611": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW-TUNDRA"}, "617": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "567": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "480": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "357": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "473": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "353": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "474": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "350": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "479": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "572": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "359": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}}, "grid": [" ", " ", " ", " ", " ! ", " ! !!!! ", " !!!!!!!! ## # ", " !!!!!!!!!! ##### ", " !!$!$$!!!!! ####%%# ", " &&!!$$$$$$$!! ' (%%#%%%%# ", " &&!!$$$$$$$!!)'' (%%%%%%%## * ", " &&!!!!!!!!!! )''(((%%%%%%## ** ", " & !!! !!!!! %%%%%(%%%%%%%* * ", " &&& !!++!,,%%%%%%%%%%%%**** ", " --&!!!+++,,,%%%%%%%%%%%%**** ", " &-&!!+++ ,%%%%%%%%%%%%**** ", " .. &-&!++ ,,%%%%%%%%%%%%%%** ", " / ..00&&!!+++,,,,%%%%%%%%%%%%%%%* ", " / .. 0& !!+++%%%%%%%%%%%%%%%%%%%* ", " 1 2 !!!!+ %%%%%%%%%%%%%%%%%%* ", " 333 1 4 !!!!! %%%%%%%%%%%%%%%%%%* ", " 3333 5 666447!!! %%%%%%%%%%%%%%%%%* ", " 5555 66677788 % %%%%%%%%%%%%%%* ", " 5555 66997888 %%%%%%%%%%%%%%* ", " :::5555 ; < => %%%%%%%%%%%%** ", " ?? @::A BB;;;<<<= %%%%%%%%%%%%* ", " @@@AA AACBB;;<<<<<< %%%%%%%%%%%%D ", " E @@FAAAA BBG <<<<<<<< %%%%%%%%%%DD ", "H EEEEEEI J FFAAA GG <<KKK<<< %%%%%%%%%%%% ", "HH IIEEEIIIIJJJJJJJ FFFAAA GG GK KK<< LL%%%%%%%%% ", "MMH IIIIIIIIIIINNJJJJJJJ JAAOOGG G PK<< LL%%%%%%%% ", "MMM MMIIIIIIQQRRNNNNNSNJJJJJJJGGGGG P KK< TT%%%%%% ", "MMMMMUUUUUUUQQQRRRNNNSSNJJJJJJJJGGG KVKK< TT%%%%% WXX ", "YM M UUUUUUUQQRRRRRRRSNNNJJJJJJJJZ KKK[KK TT%%% WXWX ", " UUU]]]^^RQRRRRRRNNNNJJJJJJJJZZZKKK_KK T%%% WWW ", " QU]]^``^RRRRRRRNNaaNNJJJJJ b c K_K %%%% ", " QQdd^````^^RRRRReeafNNNNJJ cccc %%% ", " Qdd```ggg^^^RRheeeeffNNNN cccc i % ", " dj^ ^^^RhheeeefffNNN cccNii k", " lmm ^hhhheeeeefnnnNJ cccNiii kk", " l ^hhhheeeeennnnNNJ cNNNNii ko", " p^^hhhqqqqqernnnnN NNNNNNNi ssso", " ^hhtqquuuqrnnnnNNNNNNvvvvN wsxo", " ythttuzzzuq{{nnnNNvvvvvv|| oo", " yytttzzzzzu{{}}{nnvvv~\u007f \u0080\u0080 \u0081\u0081", " \u0082\u0083\u0083ttzzzzuu}}}}{{{{{\u007f\u007f\u007f \u0080\u0080 \u0081", " \u0082\u0083\u0084tt\u0084\u0085zzzu\u0086{}}}{{{\u007f\u007f\u007f\u007f ", " ^\u0084\u0084\u0084\u0087\u0084\u0085zzuu\u0088\u0088}\u0086}{\u0089\u008a\u008a \u008b\u008b\u008c", " \u008d\u008e\u0084\u0084\u0087\u0087\u0085\u0085zu\u0088\u0088\u0088\u0088\u0086\u0089\u0089\u008f\u0090\u008a \u0091\u0091\u0092", " \u008d\u0093\u0084\u0084\u0084\u0087\u0085\u0085zuu\u0094\u0088\u0088\u0086\u0089\u0095\u0095 \u0091\u0091\u0092", " \u0096\u008e\u0084\u0084\u0084\u0085z\u0097\u0098\u0099\u0099\u0094\u0095\u0095\u0095\u0095\u0095 \u009a ", " \u0096\u0096\u009b\u009c\u009d\u009d\u0085\u0097\u0098\u0094\u0099\u0095\u0095\u0095\u0095\u0095 \u009e\u009a\u009f", " \u0096\u009b\u00a0\u00a1\u00a2\u00a2\u0097\u0098\u0094\u0095\u0095\u0095\u0095\u0095 \u009a\u00a3\u00a3", " \u00a4\u00a0\u00a1\u00a5\u00a6\u00a6\u0097\u0094\u0095\u0095 \u0095\u0095 \u00a3\u00a3\u00a7\u00a7", " \u00a8\u00a4\u00a0\u00a5\u00a2\u00a6\u0097 \u00a9 \u00aa\u00a7\u00a7\u00a7\u00a7", " \u00a8\u00a4\u00a1\u00a5\u00a5\u00a6 \u00aa\u00a7\u00a7\u00a7\u00a7", " \u00ab\u00a1\u00a5\u00a5 \u00ac\u00ad\u00ae\u00ae\u00ae\u00ae \u00aa\u00a7\u00a7\u00a7\u00a7\u00a7", " \u00af\u00af\u00b0\u00b1\u00ad\u00ad \u00b2 \u00b3\u00b3\u00b4 \u00b5\u00b5\u00b5\u00b6\u00b6\u00b6", " \u00af\u00af\u00b7\u00b8\u00b8\u00b9 \u00b5\u00b5\u00b5\u00b5\u00b5\u00b5", " \u00b7\u00af\u00b8 \u00ba\u00ba\u00ba\u00ba\u00ba\u00ba", " \u00af \u00bb\u00bc\u00bd\u00be\u00bf\u00c0 \u00c1\u00c2\u00c2\u00c2\u00ba", " \u00c3\u00c4\u00c4\u00c5\u00c6\u00c6\u00c7\u00c8\u00c0 \u00c1\u00c1\u00c1\u00c1\u00c1", " \u00c5\u00c5\u00c9\u00c7\u00ca\u00ca\u00cb\u00c8\u00c8 \u00cc\u00cc\u00c1\u00c1", " \u00cd\u00ce\u00ce\u00ce\u00ce\u00ce\u00ce\u00cf\u00c8\u00d0\u00c0 "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/1.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/1.json
new file mode 100644
index 0000000..d3a0f0d
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/1.json
@@ -0,0 +1 @@
+{"keys": ["", "487", "474", "483", "489", "161", "492", "494", "171", "162", "459", "173", "164", "172", "502", "505", "175", "166", "511", "508", "510", "512", "519", "518", "177", "168", "176", "517", "522", "528", "538", "541", "542", "534", "549", "235", "237", "550", "239", "241", "240", "243", "242", "244", "245", "246", "247", "552", "248", "249", "553", "268", "555", "554"], "data": {"459": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "489": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "555": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "554": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "510": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "550": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "553": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "552": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "487": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "239": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "176": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "177": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "235": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "175": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "237": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "173": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "172": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "171": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "483": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "248": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "542": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "541": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES, OPEN WOODLAND, AND SHRUB OF CONTINENTAL CLIMATE"}, "492": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "508": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "505": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "502": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "522": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "245": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "244": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "247": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "246": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "241": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC CONSTANTLY HUMID FOREST-ALPINE MEADOWS"}, "240": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "243": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "242": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "164": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "166": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE"}, "268": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "249": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "161": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "162": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "549": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-MEADOW-STEPPE OF CONTINENTAL CLIMATE"}, "494": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "528": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "519": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "518": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "534": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "474": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "511": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT"}, "168": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "512": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "517": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "538": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE AND DESERT-STEPPE-DESERT OF CONTINENTAL CLIMATE"}}, "grid": [" !######$$$$% ", " &!######$$%%'()) ", " *+#####$%''''()), ", " -+###%%''''.(),, ", " -#//((''(.(((, ", " *-++/0(((((((1 ", " 232445/(((((1 ", " 336778((((91 ", " :36778(;;<< ", " :36788==< ", " 3>7788== ", " ??@AABBB ", " ??CAABB ", " D?EFAAB ", " G?@FFF ", " GHI@FF ", " JJIK ", " LMII ", " NOI ", " NJOI ", " JJOI ", " JM P ", " JQ ", " JRJ ", " ", " ", " ", " ", " ", " S ", " SS ", " SS ", " SSS ", " S ", " TSU ", " TUU ", " TTUUU V ", " TTTUUU VV", " TTTUUU V", " UU ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/2.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/0/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/0.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/0.json
new file mode 100644
index 0000000..0c2dede
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/0.json
@@ -0,0 +1 @@
+{"keys": ["", "276", "593", "277", "595", "594", "602", "604", "596", "603", "597", "278", "280", "606", "282", "283", "284", "288", "607", "608", "598", "600", "290", "291", "293", "301", "296", "300", "601", "605", "609", "298", "303", "599", "304", "305", "306", "302", "307", "308", "610", "311", "316", "314", "317", "320", "324", "328", "330", "327", "333", "334", "335", "338", "25", "26", "342", "340", "87", "341", "27", "344", "345", "343", "347", "68", "346", "348", "351", "71", "32", "28", "29", "30", "352", "356", "355", "74", "69", "72", "31", "358", "363", "360", "65", "362", "70", "34", "35", "369", "66", "366", "368", "374", "44", "52", "53", "54", "373", "375", "372", "371", "79", "380", "38", "40", "379", "56", "78", "46", "41", "384", "382", "57", "381", "80", "82", "47", "48", "393", "58", "387", "62", "389", "390", "391", "392", "388", "86", "83", "400", "405", "404", "49", "403", "60", "401", "402", "406", "397", "419", "61", "408", "418", "414", "411", "415", "410", "85", "417", "84", "416", "422", "425", "421", "423", "426", "434", "430", "428", "436", "136", "138", "435", "438", "439", "126", "146", "149", "437", "128", "140", "141", "150", "153", "155", "152", "443", "127", "154", "448", "451", "129", "131", "143", "452", "132", "130", "133", "455", "134", "142", "454", "157", "158", "450", "121", "457", "144", "159", "160", "453", "122", "123", "124", "470", "465", "471", "472", "135", "145", "469", "464", "466", "478", "475", "463"], "data": {"133": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "132": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "131": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "130": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "136": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "135": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "134": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "138": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "25": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "26": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "27": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "28": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "29": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "344": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SMALL-LEAFED AND CONIFEROUS WOODED STEPPES OF CONTINENTAL CLIMATE"}, "345": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "CONTINENTAL STEPPE-FOREST-TUNDRA AND STEPPE-FOREST-MEADOW"}, "346": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "347": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY AND CONTINENTAL CLIMATE"}, "340": {"dom_desc": "POLAR DOMAIN", "pro_desc": "MODERATE CONTINENTAL SMALL-LEAFED FOREST"}, "341": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "342": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "BROADLEAF-WOODED STEPPES AND MEADOW STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "343": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SMALL-LEAFED AND CONIFEROUS WOODED STEPPES OF CONTINENTAL CLIMATE"}, "280": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "283": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "282": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "348": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "284": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "408": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS AND DESERTS"}, "455": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "121": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "122": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "123": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "124": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "126": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT"}, "127": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "128": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "129": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "69": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-CREEPING TREES"}, "58": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "425": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "57": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "56": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "53": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "52": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "379": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC CONSTANTLY HUMID FOREST-ALPINE MEADOWS"}, "415": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "416": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "417": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "410": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "411": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-MEADOW OF EASTERN OCEANIC (MONSOON CLIMATE)"}, "298": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC TAYGA"}, "54": {"dom_desc": "", "pro_desc": ""}, "296": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "373": {"dom_desc": "DRY DOMAIN", "pro_desc": "EXTREME CONTINENTAL DESERT-STEPPE"}, "372": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "375": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "293": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL LIGHT DECIDUOUS NEEDLELEAF OPEN FOREST"}, "290": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-CREEPING TREES-TUNDRA"}, "291": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "593": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "443": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "595": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "594": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "597": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "596": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "599": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "598": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "311": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "317": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "316": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "314": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL AND EXTREME CONTINENTAL LIGHT DECIDUOUS TAYGA"}, "393": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-MEADOW-STEPPE OF CONTINENTAL CLIMATE"}, "392": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-MEADOW-STEPPE OF CONTINENTAL CLIMATE"}, "391": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE AND DESERT-STEPPE-DESERT OF CONTINENTAL CLIMATE"}, "390": {"dom_desc": "DRY DOMAIN", "pro_desc": "EXTREME CONTINENTAL DESERT"}, "397": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "276": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "277": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "278": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "83": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "80": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "86": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "87": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "84": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "85": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "414": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "428": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "368": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "369": {"dom_desc": "", "pro_desc": ""}, "366": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "423": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "422": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SEMI-DESERTS AND DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "362": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "363": {"dom_desc": "DRY DOMAIN", "pro_desc": "CONTINENTAL OPEN WOODLAND-STEPPE"}, "360": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "426": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "308": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "448": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "300": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "301": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "302": {"dom_desc": "POLAR DOMAIN", "pro_desc": "MODERATE CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "303": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "304": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL LIGHT DECIDUOUS NEEDLELEAF OPEN FOREST"}, "305": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "306": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL BUSH-AND-SHRUB TUNDRA"}, "307": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "380": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "371": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "382": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES, OPEN WOODLAND, AND SHRUB OF CONTINENTAL CLIMATE"}, "384": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "406": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "387": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "388": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "389": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "607": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "38": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "381": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "32": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "31": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "BROADLEAF-WOODED STEPPES AND MEADOW STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "30": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "35": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "34": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "438": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "439": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "436": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "437": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "434": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "435": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "430": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "338": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "604": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "335": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY AND CONTINENTAL CLIMATE"}, "334": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-CREEPING TREES-TUNDRA OF EXTREME CONTINENTAL CLIMATE"}, "452": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "453": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "454": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "330": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "333": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY AND CONTINENTAL CLIMATE"}, "457": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "60": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC CONSTANTLY HUMID FOREST-ALPINE MEADOWS"}, "61": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "62": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE AND DESERT-STEPPE-DESERT OF CONTINENTAL CLIMATE"}, "606": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "65": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "CONTINENTAL STEPPE-FOREST-TUNDRA AND STEPPE-FOREST-MEADOW"}, "66": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "CONTINENTAL STEPPE-FOREST-TUNDRA AND STEPPE-FOREST-MEADOW"}, "68": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "601": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "600": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "603": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "288": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL BUSH-AND-SHRUB TUNDRA"}, "405": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "404": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "403": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "402": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "469": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "401": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS AND DESERTS"}, "465": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "464": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "400": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "463": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "160": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "419": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "605": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "150": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "153": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "152": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "155": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "154": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "157": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "602": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "159": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "158": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "609": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL BUSH-AND-SHRUB TUNDRA"}, "608": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "82": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "EASTERN OCEANIC MIXED MONSOON FOREST"}, "466": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "48": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "49": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "46": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "47": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "44": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "470": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "40": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "41": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "418": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "320": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-CREEPING TREES-TUNDRA"}, "327": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "324": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "328": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "374": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "146": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "144": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "145": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "142": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "143": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "140": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "141": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "610": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL LIGHT DECIDUOUS NEEDLELEAF OPEN FOREST"}, "475": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "450": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "149": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "74": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "72": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-CREEPING TREES"}, "71": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "70": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "79": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "78": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "EASTERN OCEANIC MIXED MONSOON FOREST"}, "451": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "472": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "356": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "355": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF EXTREME CONTINENTAL CLIMATE"}, "471": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "352": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "351": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY CONTINENTAL AND CONTINENTAL CLIMATE"}, "421": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "478": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "358": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERTS OF CONTINENTAL CLIMATE"}}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ! ", " #### $!! ", " %%% && $!''( ", " ))%& ****(( ", " )))+ (( ", " )) + ( ", " )) + ,, ", " - ,,, ", " --- ,,,,,, .../// ", " --- ,,,,,00,, ...// ", " 1- ,,,,,00000 . ", " 11 ,,,0000200 333 ", " 11 ,,,,00222222223333 4 ", " 55 66 ,,,,002222222222733 888 ", " 55 66,,,,222222999999229738888888 ", " :;; < 6======22229999>>9999977?????88888 8 ", " @:::AAB < =======22999999>>>99997777?C?DEEE88888 ", " :FFFFABB G GGGG<=======9HHHHH9>>>9999777II77CDEEEEE777 ", " @:FFFFFFBB GGJJJJG<======9HHHHH99999999777II7CDDCCCCCC777 ", " :FFFFFFFAB GGFJJJJ<======KHHHHH9999999LL7III7CCDCCCCCC777 ", " @:FF FFFF FFFFFFFFK=K=9999KHHHHH999999LLL7II7777CCC7777777 ", " :FFFFFFFFFFFFFFFMMKKKKKKKKKLHHHH9999LLLLLL7777777CC777777 ", " @::FF FFFFFFFFFFFFMMKKKKKKKKKLLLLLLLLLLLLLLLLLL77777777777NNNN", " @::FF FOOFFFFFFFFFMMKKKKKKKKKLLLLLLLLLLLLLLLLLL7777777777NNN ", " @:FFP OQQQFFFFFFFFMMKKKKKKKKKLLLLLLLLLLLLLLLLL77777777 RRN ", " @:PPP QQQQQFFFFFFFMMKKKKKKKKKSSLLLLLLLLLLLLLLTTUU 7 R ", " VWP QQQQQQQQFFFFQMKKKKKKKKKSSLLLLLLTTTTTTTTTUU RR ", " VXW QQQQQQQQQQQYQQZZZZZZKKZZKSLLLLLTTTTTTTTTUU [RR ", "] VVV^QQQQQQQYYYYYYY________``ZabLLLTTTTTTTTTUUcc [RR ", "] VVVV^^QQQQQYYddddddeeeeeee_```bbbaLTTTTTfffUUUccg [R ", "]VVhijik^YYYYdddlllllelleeeee`bmmbbbaTTTnnnffoUUfpqq R ", "VVVhirrrrYYdddddlsssslllleeee`ttubbnnvvnnnnwwfffxpqq ", "VVViyzrrrdddddddsssss{ssllllllttun|nnnnnnnn}}ww~pp q ", "V\u007fVy\u007f\u0080\u0080rddd \u0081 \u0082ds\u0083sss{{sssss\u0084\u0084\u0085\u0086\u0086tnnnnnnnnn\u0087}~~~p \u0088\u0088 ", "\u0089\u007f\u007f\u007f\u007f\u007f\u0080\u008a\u008b\u008b \u008c \u008c\u008d\u0083\u0083sssss\u0084s\u0084\u0084\u0084\u0085\u0085\u0085\u0085\u0085\u0085\u0086\u0086\u0086\u0086nnn\u0087\u0087~~~p \u008e\u0088 ", "\u0089 \u008f \u007f\u007f\u0080\u0090\u0090\u0091\u0092\u0092\u0092\u008c\u008d\u0093\u0093\u0093sssss\u0084\u0084\u0084\u0084\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0086nn\u0094\u0094~~\u0095~ \u0096 ", "\u007f \u008f \u0097\u007f\u0090\u0098\u0091\u0091\u0092\u0092\u0092\u008d\u008d\u0099\u0099\u0083\u009a\u009b\u009bss\u009c\u009d\u009e\u0085\u0085\u0085\u0085\u009f\u009f\u009f\u00a0\u00a0\u0085\u0085\u0086n\u0094\u00a1\u00a1~~~ \u00a2\u00a3 ", "\u00a4\u00a5\u00a5\u00a6\u00a7\u0097 \u0098 \u0091\u0091\u0091\u00a8\u00a8\u00a8\u0099\u0099\u00a9\u00aa\u00aa\u009bss\u009d\u009f\u009f\u009e\u009f\u009f\u009f\u009e\u009f\u00ab\u00ab\u00a0\u00a0\u0086n\u0094\u0094\u00a1\u00ac\u00ad ~~ \u00a2\u00a2\u00a3 ", "\u00a5\u00a6\u00ae\u00ae \u00af\u00a8\u00a8\u00a8\u00a8\u0099\u00aa\u00aa\u00aa\u009b\u00b0\u009d\u009d\u00b1\u00b2\u009f\u009e\u009e\u009e\u009e\u009f\u00ab\u00ab\u00ab\u00a0\u00b3\u00b3\u00b3\u00b3\u00b4\u00b5 \u00b6 \u00b7\u00b8 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00b9\u00b9\u00b9\u00b9\u00b9\u00b9\u00b9\u00b9\u00a8\u00ba\u00ba\u00ba\u00ba\u00bb\u00bc\u00bc\u00aa\u00b0\u00b0\u00b0\u00bd\u00b1\u00b2\u009f\u009f\u009e\u009e\u009f\u00ab\u00ab\u00ab\u00b3\u00b4\u00b4\u00b3\u00b4\u00b4\u00b4\u00b4 \u00b7 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00bb\u00bb\u00bb\u00bc\u00b0\u00bb\u00be\u00bd\u00bd\u00bd\u00bf\u00c0\u009f\u009f\u009f\u00c1\u00c0\u00ab\u00b3\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ba\u00ba\u00ba\u00ba\u00ba \u00bb\u00c2\u00bb\u00bb\u00bb\u00bd\u00bd\u00bd\u00b1\u00c3\u00bf\u00bf\u00c0\u00bf\u00bf\u00bf\u00c0\u00b3\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae \u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00c2\u00c2 \u00bd\u00bd\u00b1\u00b1\u00c3\u00c4\u00bf\u00bf\u00bf\u00bf\u00c0\u00b3\u00b3\u00b3\u00b4\u00b4\u00b4\u00c5\u00c5\u00c6 ", "\u00ae\u00c7\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00c7\u00c8\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba \u00bd\u00b1\u00c3\u00c3\u00c4\u00c4\u00bf\u00bf\u00bf\u00c0\u00c9\u00c9\u00ca\u00c5\u00c5\u00c5\u00c5 \u00cb ", "\u00c7\u00c7\u00c7\u00c7\u00ae\u00ae\u00ae\u00c7\u00c7\u00ae\u00ae\u00c7\u00c7\u00c8\u00cc\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba \u00cd\u00c3\u00c3\u00ce\u00ce \u00bf\u00cf\u00c9\u00d0\u00d1 \u00d2 ", "\u00d3\u00d3\u00c7\u00d3\u00d3\u00c7\u00c7\u00d3\u00d3\u00c7\u00c7\u00c7\u00d3\u00d3\u00d4\u00cc\u00ba\u00ba\u00ba \u00cd\u00c3\u00ce\u00ce \u00bf\u00d5\u00d0\u00d0\u00c5 \u00d6 ", "\u00d7\u00d7\u00d7\u00d7\u00d3\u00d3\u00d7\u00d7\u00d7\u00d3\u00d3\u00d3\u00d3\u00d3\u00d8\u00d9\u00cc\u00cc \u00da\u00da\u00ce \u00d5\u00d5\u00d0\u00d1\u00c5 \u00db\u00d6 ", "\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d3\u00d3\u00dc\u00dc\u00dd\u00de\u00df\u00e0 \u00e1\u00da \u00d5 \u00d5\u00d1 \u00e2\u00e3\u00e4 ", "\u00e5\u00e5\u00e5\u00e5\u00e6\u00e7\u00e7\u00e7\u00e7\u00e7\u00e7\u00d7\u00d7\u00dc\u00dc\u00df\u00df\u00e0\u00e0 \u00e1\u00e8\u00e8 \u00d5 \u00d5 \u00e9\u00ea ", "\u00eb\u00ec\u00ed\u00ee\u00ef\u00ef\u00ef\u00ef\u00ef\u00f0\u00f0\u00f1\u00f2\u00d3\u00f3\u00df\u00df\u00df \u00f4 \u00f5 \u00f6 \u00f7\u00f7 ", " \u00ef\u00f8\u00ef\u00f9\u00f9\u00f9\u00f9\u00f9\u00f1\u00f2\u00df\u00df\u00df\u00df \u00f5\u00f5\u00f6 \u00fa\u00fa\u00f7\u00fa "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/1.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/1.json
new file mode 100644
index 0000000..5457be3
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/1.json
@@ -0,0 +1 @@
+{"keys": ["", "486", "478", "475", "471", "493", "491", "484", "455", "468", "469", "463", "466", "481", "490", "201", "207", "208", "209", "488", "495", "179", "497", "496", "185", "202", "498", "499", "500", "234", "181", "186", "203", "501", "215", "214", "213", "504", "188", "218", "217", "216", "183", "195", "189", "219", "220", "221", "506", "507", "196", "194", "509", "228", "513", "199", "514", "520", "515", "516", "182", "521", "198", "523", "525", "524", "530", "527", "537", "539", "531", "545", "544", "543", "536", "548", "546", "547", "255", "250", "254", "262", "258", "257", "259", "263", "261", "265", "264", "266", "267", "554"], "data": {"216": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "217": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "214": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "215": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "213": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "218": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "219": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "498": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "499": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "495": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "496": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "497": {"dom_desc": "", "pro_desc": ""}, "490": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "491": {"dom_desc": "", "pro_desc": ""}, "493": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "543": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "546": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "547": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "544": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "545": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "548": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "263": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "262": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "261": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "267": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "266": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "265": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "264": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "537": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "536": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "531": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "530": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "539": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "199": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "198": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "195": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "194": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "196": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "524": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "525": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "527": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "520": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "521": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "523": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "513": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "515": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "514": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "516": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "455": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "258": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-MEADOW OF EASTERN OCEANIC (MONSOON CLIMATE)"}, "259": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "179": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "250": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "257": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "254": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "255": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES, OPEN WOODLAND, AND SHRUB OF CONTINENTAL CLIMATE"}, "182": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "183": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "181": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "186": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "185": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "506": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "507": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "188": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "189": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "500": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "501": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "469": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "468": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "509": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "463": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "228": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "504": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "221": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "220": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "554": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "234": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "466": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "201": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "203": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "202": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "207": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "209": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "208": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "488": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "486": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "484": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "481": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "471": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "475": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "478": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}}, "grid": [" !##$$$$%&'()) *++ ,-- .. / ", " !!####%&'(( ++0,,,.. 1234444 5 ", " 6!!!!!78889 +: . 4;;;<=> ", " ?!!!!!788@ AA B CC DDE;; ", " ?!!!!FF%88 G HH IJ ", " K!!!!!!!%88 LM NNNON PQ ", " KRRRRRRR%8@ ST UVVUUUUQ ", " WRRRRRRRR SX YZ[[[]]]UUQ ", " W^^^RRRR SX ZZZ[[]]]]]UUQ ", " WK^^RR_R ` ZZZZ[]]][]UUab ", " WKKccd eeZZ[[]][]]Uab ", " fggcdh ieee[[[jjklmno ", " ggdh iieeeepjjklmbb ", " qh ii rpmmmb s ", " matb ss", " uvt sw", " xx yss", " x z{ ", " || ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " }}} ", " }}}}}}}}} ", " } }}}}}}}}}}}} ", " } }}}}}}}} }} ", " }}}}}}}}}}}} }} ", "}}}}}}}}} }} ", "}}}}}} ", " }}} ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/2.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/1/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/0.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/0.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/0.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/1.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/1.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/1.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/2.json b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/bio_utfgrid/1/2/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/geography-class/0/0/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/0/0/0.grid.json
new file mode 100644
index 0000000..ea81c26
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/0/0/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," !!!! "," #### !!!!! "," #####!!!!!!!!! "," ######!!!!!!!!! $$$ $ $$ "," ######!!!!!!!!! %% $$ $$ "," ## ### !!!!!!!!! %% % $$ "," # ## ##!!!!!!!!!! %% $ "," # ##### !!!!!!!!! % $ $$$ "," ####### !!!!!!! $$ $$$$$ $$$ "," ## ### ## !!!!!!! $ $$$$$$ $ $$ "," ######### !!!!!!! $ $$$$$$$$$$$$ $$ "," &&&& # ######### !!!!!! %% $ $$$$$$$$$$$$$$$$$ ","$ &&&&####### ### ## !!!!! ''$$$$$$$$$$$$$$$$$$$$$$$$$$$","$$ &&&&########### ### !!! (( %''$$$$$$$$$$$$$$$$$$$$$$$$$$$"," &&&############## !! (( ''))$$$$$$$$$$$$$$$$$$$$$$$$$$"," &&&&######### ## !! %'')$$$$$$$$$$$$$$$$$$$$$$$ $$ "," && &####### ### * %''+$$$$$$$$$$$$$$$$$$$$$ $$ "," & &############# * , $$$$$$$$$$$$$$$$$$$$$ $ "," & ############## -**./0111$$$$222$$$$$$$$3$$$ $ "," &&&&&&&#### # 444/51111$22222226666663$$$ "," &&&&&&&&#&&# 47789:1$$2;2222336666333$<$ "," &&&&&&&&&& == 7>???@ A;;;33333333BB < "," &&&&&&&& CDD >??EFGGAHI33333333 J<< "," K&&&&&& CCDDLLLMNOFGGHIIP3333333 < "," KKK Q RDDDLLLMMOOSTIIPPUPV3333 "," & KK KWWX RYZZD[]L^^OOOT PPP_V``3 "," Kaa 4 bYYZ[[]]^^cdd PP Ve` "," f ggg hiZjkk]]llmno p ` qq "," rrggs t kuvwlxyo z {| || "," } }r~~~~~ €wwxy { {{ {{ ÂÂ"," ‚‚~~~~~~ wwwƒƒƒ {{ { „„ ","  ‚‚…~~~~~ ††‡ƒˆ ‰ ŠŠŠ „ ","‹ ÂŒ ‚……~~~~ ‡ŽÂˆ ŠŠŠŠ "," ‘’’~~ “Žˆ  ŠŠŠŠŠŠŠ "," ””’~ ••• ŠŠŠŠŠŠŠŠ "," ‘””– ••• ŠŠ ŠŠŠŠ "," ‘”” ŠŠ —"," ‘” Š —"," ‘” — "," ‘” ˜ "," ‘ ™ — "," ‘ "," "," "," š "," š šš ššššššššššš š "," ššš ššššššš šššššššššššššš "," ššš šš ššššššššššššššššššššššššššššš "," šš ššš šššššššššššššššššššššššššššššššššš "," šššš šššššššš šššššššššššššššššššššššššššššššššš "," ššššššššššššššš ššššššššššššššššššššššššššššššššš "," ššššššššššššššš šššššššššššššššššššššššššššššššššššš "," ššššššššššššššš š šš šššššššššššššššššššššššššššššššššššš "," š šššššššššššššššš šš ššššššššššššššššššššššššššššššššššš "," šššššššššššššš šš šš šššššššššššššššššššššššššššššššššš "," šššššššššššššššššš šššššššššššššššššššššššššššššššššššš "," šššššššššššššššš š ššššššššššššššššššššššššššššššššššššš "," š šššššššššššššššš šššššššššššššššššššššššššššššššššššššššš "," ššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššš "," ššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššš","šš ššššššššššššššššššššššššššššššššššššššššššššššššššššššššššššš"],"keys":["","89","40","185","165","228","207","107","71","79","69","62","104","164","59","177","226","115","43","74","205","149","109","96","202","184","229","113","68","87","222","19","217","179","137","64","211","106","105","3","170","121","142","127","66","237","188","101","27","187","183","169","166","147","53","212","152","145","159","213","189","24","235","95","191","67","240","215","162","232","85","196","214","161","190","70","199","200","130","174","49","204","44","45","39","46","225","116","141","99","156","65","34","77","47","119","173","224","176","33","4","242","151","102","17","72","182","157","243","155","140","42","181","38","10","241","227","168","15","94","13"],"data":{"3":{"admin":"Afghanistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG70lEQVR42u2cX2iWVRzH3/fmzSgYi0V0sS66mUTscipm2FphRhbEfJUuwqK1wgZd6C5WrNFYrsz+YTlMsSB1A6ELXU2h2GbiZssczrXwzSnOWrVw6kAprDyfc/F7ObyrFOM97/vdxeHhPOc55xnnw/f3Pb9znjeRuDFx5S/ysqnzStnXnvwiWf/lZ4nSRCqukjdfs79qy9//RrLjvQIoBVY+ICWwIgArXrwEVpRg5TNwAkuh8CqhEVgCSx5LYGlVKLCiBut6IyuwtCoUWAIr/5RJHkuK9b9CKbBk3hUKpVgCS2AVLVjyWAJLiiWwBJbAknkXWAJLHktgXXPZ31VSWbKFPgd67uqYe2F/z21nbk1ej2M5AiuvwboWjIDm21tq5t73+khreuvycVuOtdWnnk0dObP0ySWXvzpRsfD29qG6+fOqMrQHO0CkFFgy7ykQoQSgTHnj4jXrLVKnHnijvH0rYFmkbBtK7oKpwCpSsFAaUAAaQOEaUEJ0whIQLVjUcC2PVURggRSqY7EAKe7Ojl3Yg707Xtn+clsD9f9GvQRWxOa974WyV27aa6GxmoRzsg6JNtZp2fDHXUbkKRDkLkhZTyawClaxhltf/PSRd050ba9o+fnwxfS91atGHqu7nH48l6JQDxwgZdHJZdItjkMlS2aqmo93b7rQWPt1W+2yBU8JrIJSLCb1ZF3ninWtlEw2pQWLa7vKo8YiZdvbNITtgZ6/37FxTkPqh9KPNzftAujBV2sO3X1OHit6xSIMMc0WKSYbDctStbKVi5dWnTzywf3ti6jJLGj7ae2lmaHjXaODM8cmnv+ue6z8pTfrDhBYaYmq2X6O7m5ckU4AE3hxzehhcBRY0YDFxH9TumrfwhkwGlv32ubn7rRlGAQBa+KOnX++P4RKTQ0fLP18+veV50d+S1JOru2t2HGOu7TkqTCMhiMCFm/FGwqsyEIhU3t48Onx6rfQD6YTvRmonPdRRVkYNLHzZ0sPJnsbCHMhWNRwd7ppYNmBt3kq7I3AB1K4uuFF9dMPn6a0WAusaBQLdJhaFIWgdurQnuYNx8AiF1iTyztXb7+Bld2lvl87fxy1YBEWbctcYDHKxEN7dm3KMDp4Yep5Q3msaMDCwdgAhK5Mvrs7s7MZ9UItaMk15en6jkc3zifYTf9ydMPAg95dBSV3p57Z29FdTV7e7y26AGd7ZkTwQgvtu9FSYEUAFiqFWSbwWSDQDBSFdAOByXosnBDWnhUlGkPP1HAX98ZderCJDL9l5O5aHHkrFhP0KbAiAIsQA1ioBeGPoEbKAF3BLZ2dM1zT24NyAJk37w4Ia7FtohW86BmYfJ9Ow+iZGkYkjAITb8W1H0Vg5T9YTDlaYjNJhCGck91+Rl3AkWkmSNEPihKafWpoyTUlXsquE9neAV9gsm/loRRY+Q8WoKA6TCGhjSnnroUp1KG+fc2f1D5BjorcFQpkg6b3Rq4lT9nslD9a4zBlLEbnTcAR1+UVVGDlP1hsv+BpUCnUgiDFCg5o0JIwR99/c8tQehvqAlgWL3Dxe4uuZbhdA0asGQl8jO7fxPXDGyoURrYqBAs7hT5Z4BABvjADTmBC5+gBF8U6kWdRGtSOlnZBYA07WsVTf0zN3HN+3ALq+zGb2QIrgjwWOkHoYZqpIelgV222tGHUrhD9tsy2D/tbRgHC2vxcvflcl4OS0ekTd0WN8liRgUXOHb0hNUDmHR3C5YRrPUBB4UAQFEgQoEyEUcBCe+zeYhZYbhSbkuBN/FrSvaEy75GBZbef0RurYdSw4qMlSU7vyUyJTwKyrGtX+uSCK3Fs1raHG952MWEDscCK7HSDnWD0A6XxQc3pE0jZc6ThgWOuwzZhe/CiZ6ttoXrpdEP0x2ZwRUw26oKVxs6z4WPzW7kOMdtvcuxWtw98LlySW886DeG0jc0iAnG4/SywIgOL/TtUhMw7JXiRQyI82Z07e8LdnmQPPwuz599JMVCiYTgwu40dJjikWNEfTWbt5jFy6mJ9j8UCZbIn4mf/0pDASukDruuZa+rDM1sCq6C+0iGZaW0114RLAhm2He+V6yMwtIcAx1P0wB6l3bqZ/bS7wCoQsEgrZFl4B4TNsJNuIFziwAhn/viNWxuClA95riVt6A2w0K1wG1tn3gv2u0LasAmNuba5K0DxyQVX2hp/Zstdo23gCFj0kOsgoRSriD6xx+DjlmzqAYAobbrB1tOea3r4rx/XC6wC/1EQmz4IPwILQbQffl0dTAJLv4+l38eSYsX2W35SLIElsGTe9eO2AqvIwZLHknmXYgksgSWw5LEElsCSxxJYUiyBJbAElsy7wBJY8lgCS4olsASWwJJ5F1gCS2AJLIXCf0BEYP0F7SYyPlPSiAIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE1OjEzLTA1OjAw7m98/AAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUZHLnN2Z3V4Eb0AAAAASUVORK5CYII="},"4":{"admin":"Angola","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFP0lEQVR42u2bTWgdVRiGr1HTGmOav4YELWmT9EbiwkWQIooLQYlBwUpttI1iRZqqiNiq0FKVCmL9qVYFBQNabKRokaqLWFq0FYTSTbAUm2IhxoUYaKHGv4U0KtxnFt/l5Exmmrlw78m7ebmcOTMnzDy833u+meTGxhoa8nmpNFvN6RZIBZZUYEkFlm6EVGBJBZZUYEmlAksqsKQCSyoVWFKBJRVYUqnAkgosqcCSzq3X192Y31CkhfGTF7oHujbp/gisi1QAmthzf/fye/h9prPv9IpPpus/+7ftkakzOweu2cZIEYgCSxqvP383dKB95T/j4z+0dPx97fGZlmmUERTIIg8TWNIk+kvts2eXTVqMXAWs8f5V5zpmBJY0kU6+vmFf+4HzH+3ra/v9jx8PnW59wIcXRyma+BxQCixplJAoaicmGo/mX3EDO+ic/e3dl6++1y2LtmjymxwWXU1gLWSwQAf1lTZAYQ6l0OdkU2u/uWXpaNiFUmDN1VYo/MaN0DlQMA6HM80CVmFcpXBB96hAhLSEpt3ruXhRFotaEgIr7O4UwdyqxQIgGOEokZzfszRIDZq4ncWL+E/pDA8ygVUEFrs2XwD3RXLO8obxAl4kKt8ukkwWUpwXWLOo7aTHd6rSdtjje2DxmwOBFUjG4jH7XKoIgiQomLLo69RHmAqssMFy05Xbi/K9kE4b56PGaUCtB4HlbxYUuk1EbBISjsIIR1PsEGO9MGpkBPTSWmBFjU3brgQgXKQIHYMdR1MXL3N963+2FIbx+U3uxJq6kfw0NzdS34gdjz/Lzi/x9SM40q5o1L6QYcS+wIn/26KZvtU9fzMQu0nL9rdmuWaS+5P8Hmb7LJxzc0MPLd7c1LyQ9ciH22eWTvFovx167PvWJ0q94ranOt9seppi6m4O3t9792jzykq/q7lcW64lV7WQdf/gO2uWrLCe8eCO1W8sXl26FTs6l62r+s8F69fdx9qbt9x1w62D1Scr/t4KrJG/dh2ve8t9wKXD6+ba3uHL+1mFFcfu+OLOxlWMtw42n6/qFlgVr1u3b9pY87VbknjwGwcG/ryipn60bv0lW7NaEWTtWrhmUPdWYPXWXDdx2YvWPyxYn3/13vSSXcCXFV7D9730wlWH7VpcX2AFqDsefrLnys2ub1GkwOvtx587VXtuPqWqp7fr9ksPnTp8cHfjBbvKsdf2P9+w57ZHb/q0ernACkpxI9Bx8QIFChZ+g89lVXbRQGK7wPLhRa5yfWWy6+jepnHcC13b09+0aHg+XmUdK9skJ7DKVEEBf7IJjJbEwS0fvFrflySBMW6bGhYmgnxQXiWw0jYIgMN2zIHDJjBwjC+vQfWrBJbrRjQq0xZKPAakbAIDL7pixHDU3W8yJ6jCJ7BACizY8eEcaR8zUFIEyV7Ww0CHoz4QLy7+C6wyDeZu1qG0gQKQJW8oMJOzuAL+ZHeRlELm8DvbrpjAKgvFP3y7M9u7AoudPc9U1y4CBascZSbKXpLrMwJkRz4e+bL+J7DjaOBIKWPZ8jR/BSOujJMBJeOsBY6gmTbhCawKUB68CxZpyY3b8f+lY+fbzhbORITHt5iJe9HrD6rbLrBQHiqdKhvkaS64odvtPzGTs7iC75sIULZllHJsy6gapMF6mPtoQQEIUPeFMWclj/y4Gi1WPAznCyrUC6kke0lXs7oykNmwj4NWPF5Cp3yKcrwvCixpBp8cglcFf02qx1men+7wgkhgSUuS6lQKpVKBJRVYUoEllQosqcCSCiypVGBJBZZUYEmlAktaMv0fswCmUwz2euEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE1OjMzLTA1OjAwrEp7gQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUdPLnN2Z45UidkAAAAASUVORK5CYII="},"10":{"admin":"Argentina","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADtUlEQVR42u2dT0gUURzHpyA61BIUgtEfNvBSRGHRuVOEdAksRPJsWOBBwouXoEzyEIFUUu6pKIpiIbMQkZAwiKVM+oebtpq0kZbChh0qtsCvh98y7vRcXSHmc/kwzHvzm5m3H37vzZs3rHeq9u6N1DCES0uPJoCIBRELhpVn3nd9GD+JWJCMBRELIhYNARELIhZELBoCIhZELIhYECIWRCyIWBAiFkQsiFgQLpFYWpZll2jZbT/9pcHH+peABS8QW0z8hcZxrx/cMi53VOz7dbkelxYIjuxynXNiZSuyX/+c/pmYKZ2u1bal9vtL7Z7g0nwxXc5bWHyXOIWdN7imS8zC2mShVxt8Pe6R3a/KX+q5/Dxhpm1i9x8MItY8zDxM9yWjYqqyf9W9Z5MTQ7H+pKg9Kp2eHp0aiNNiiPWPzPQl/bq0+7IESu99/vl+dLztSerKiXRL4u3tFaLdo5o6ikyGWDn8Uf3t16dGyWFlEsfijycvbkmVd/9u3jj6ondt6x5t5wg3e5QiKBqt6qGUujPJIVGkznDdg/1N0Tcbridq6sXBgx1TR2qSJfGtDWWi6qR6+jbFHimCchh6eYylbAennPSu+87Ouipp9PT7+fp9vX4OXL3WVLlLetkMpxEYYnmMqKxYVqn+wbOD2zOvyi5UlTSMHejoXNcnao9KB3a3l1ec01GSUtEYdXlhzlVWLHV/6vKUk4Z6Lm1bv2NmpLM60mWZWR1fEzmsUtXUUYpgx1s6C2KFdcA+K5ZGS8pViUMtzZsrJJCy1Ei2/VhkpWTStkpVU92iIihvMZAPqVh2RkpDb2mhrk2dnfLTx+OxukirKMm0rVLV1FGKYEdaOgtihTRjSQUrls1Yfr1sh+jPWIpGxgr1GMtOMdjJBf8Ya+LWzZeRo6L2qEO0YywrlnIVY6yQPhVKLHVbmgjV9IHyltNToW/SwU6Z8lQY6glSiWXnoub0mpUm3zyWnSzVgF1qSizmsRCr0b4Z1EBeEwd2hl0aab5KtM+A9k2i5vERi3eF8+llnhYlmbKRHUVZ8q4QsZxGXXYywqqTs7rB1GSeHbGcqO7MrseSOnYFhF2zRYshFitIl0usfGuZXRrUvU7x4rvUDD5j8Hnd72J57ncxXyEUo33ylXru3+G4fL3jXrMY8d2/LVno1bp85bI895vv2MJafvHtky8C3xVCPliFiAUR6//4Cw1+KsSCELEgYkHEgohFQ0DEgogFEYuGgIgFEQsiFoSIBRELIhaEiAURC4aRfwGxCDVrmSqS1AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTc6NDMtMDU6MDCieqKlAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BUkcuc3Zn7T9RMAAAAABJRU5ErkJggg=="},"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="},"15":{"admin":"French Southern and Antarctic Lands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFd0lEQVR42u2cbWhWZRjHn8h9KaPEqOiLOWN+cJSShZKYOQXTUKgRRoKEwgJXKIXZBwcaI2iQ+DYksAQzjJHBFmOViNF8KWyJjAbORKZB08039PPC/Q5yPdzdx/N0ztl5zp7/lz/jPOfc5xn37/lf133d1zmFwqM1T9YvTUq/Pv/91O4XRqpGakfejK9XW9vWtvf39E16oqY2lk5+aEL1hgu/rmvctOetpveHtu8pFJ6qXtGa7P8uLVKBJRVYyYGliRdYciyBJceSyrHkWAJLYAkshUKpwJKO6sTTM4+tahdYCoVyLDmWwJJjSQWWwBJYAktgCSypwFLyLrAElsBSKJQKLIElsBQKBZYcSyqwBJbAElgCSzmWVGAJrLLs6EqO0DvBRaFQGujW+3a1tX2YlP42dKb3XMO1fTcfvn1/fB1c3FPXO79/zad/tR6LrwP97RN/3Lly5fqBbbM18anr8w31Szb9kbbO7FpR98EP0Y+4nyb1HR5vmHtwTYcmPoscy4aJOH+XOmZokI10bfhoCn/5Td6l/y83/Y8fjO9I+M/Jd47AGn9KIH770kfLd/enoSxH3ONP1y5qblwksMatMvEsR5JaL4frnzvOHbk0R2BVRI1n365DJ44+mDZS4ItjZRwQNfFjozO+WTZtQ9PpL/oOXXiR6b98cvj6jZOu+rwtylXgm8oDqAIrK6WQET6pUyYsePWdG0DmU1+RGR/yXRWlmPJI73Ndq6vGqOAiIJLSluG9c9qnM82FwpT65bcireCcTxnHBeuV62sPNx9wg2wkfxodmRGKAmV64VJAxPEnVnx1i1fP2rKQlHnzte1VBz/BPwLIShx590sHHuh63ZeSdz/7+2N9Ayj7HIDoG40Unm9CoOz86edvewbsN8zBuxsqRwlqQGCzIv5m4l2PieIrPrB8WVdwF8cLwQV/Irez154fvHh28BQbcYRIgVV2vnV43vE3znx2j8mO/HaX6GBxZhS/2djdMn3/FffaFPckBEdSYDFVeFWQx3iuwideO9v4XktHdMfi+PrPm49/2fnuko+r936Fa3pHc/I2vmfH0iO/nAr6UFJcPwqOOFsx5CjkWDZE+oIgZ+JqZEjuGs0HVnAXsyzgLjb4BqMZBwIdejrsutUeEVg5RtANmqibevvAsuGV0YDJnsPSoYRNfZUb8q5uloNS3mR1GR4KLVgA5J5z8eo/1cOz7GgqkI5bZWHPKozpJ7+x7kUJgNWZW8eyCwKgAUd7LUhxhCwq4/p7Bm+nrLRdwqHvao4us/4Eai93rnqm6W+LCIm5z7HIioDGVrbI80jh3Up9Zr6Vyxen5iSvAgU38OFYqAULV3MzJxwLpGzNjPNd/yuLHgf5SnrlU7csOfZaVK8SWHn3OTec4TS4C8ER5Uip3Vq4kR2H7RrXtxjZW+WSY+XLq5hsghcaXgjwbTzfYxvHd3ezJwhq3H1Mf65CIT3HjT6R0UNnZqFNYOU32Q9vYi6LhmOBVc71d99GCg4X3ugXp81QYI3bB7yAI3ga29Nq5xZUvQVPcy11rLKouQustGGikm6f6iaVxpPCW+3YAiraonGcj+MUWgmULAI4nmITn8DKVgGLFZl1IJsz4WHu9IOOr0zAp2BkN3NQ+iZKbjMUWPntiLfTjyfFeS0AONpNnqKqlTahK0HtNg4ohPeqR28zpF5FGYJQSxugcqyK6DIlMOExhEh7JDw/C69+2ZDn3ktgSe+uH+/0iJKSp96ZLrAqoVJP8zHpPEGTxJx2Go5n9X6vkv1P7S7l9l4a30YyyX4qj2rJsSohFNoHLmzHqV68Jo2lBD46FAh/rCJJ1QWWNFaPfJCKjHoYG885e3uqJlIqsKQCS+WDyl5T/wu/C/ZEKz4GwAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MjI6MzktMDU6MDBzBFm3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEYuc3ZnBgabnQAAACl0RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgV2FsbGlzIGFuZCBGdXR1bmGg6A9zAAAAAElFTkSuQmCC"},"17":{"admin":"Australia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFyklEQVR42u2cX2gcVRTGN4ogJaVIHzRtqaJBS7RbFGpqQEGkIiy1KpY+9KEvhaQgSIRCSvpQ/0AjtQ9qER9MpJpQiMaqGyoKpg8VYUFMzUO6FUu7MRBfKhafgoaR7G8Xj96dyczOnZm7yXn5WGZm596595tzzv3OmZvzvJ5ST8nzRl8ffW2p9+8nlj4ycXjHB7MzX+Y7H37r499zHblNJ9+Ij7u6dj0+dif397zj54+/4Hlt97fd1wiXz3Kl3T5wN57OK04uTN70vH1X9t1Tb3fLS1u2c2Tulc9yU3fcveHel4fbbbW+yrE+fAxl4VDhufoQz+/5dYek1/yG+fyffW9+OHSx9My2I9tGRtalTyz+Faddes5TQBfvzOGv+04EjwPPrsSKgGEG17Rh8UkGRbhP0sTCMkUlk3ze0tnShYUDHZ929L33k5ImFHYd2d555lT4QQ8mGXdzwWJFtUxJvDxrGuO82XFIJolVKfc/+ern8YlFi+/c9e5T04/E77/deC4JbF9qX3r72RYgVppvvC2LFd/iXitfK988GNXiuoCMxu5Luy99sq5liBVu8uT0hyUZIXAcYnEHW/1pLTcHjXoXexe/OTf2/NnbygMgRxwiWdKrKj8LURwvjl/tCUesgcJAgSsZxKtd4+u/vSXpWNBNR8MC4sfzM4dudMqn44hDywvJ+qgoH8/zpg9OP40eVkOfFZaJwcQiAqtfuXy3Bm1VjwS3Qm+be1LitmynDaL3D/UPXXiIl8R8bTjrxCtRn6pMsYEjE1g7m2UPK/nSYnnMBWIRDGB3JbE4wllHiIUrQX/nd1bo5wqz7dUyVvKbNz+4sPV6e+nEAy44GiIq7BPIEaeUd7/pVBTuOJ/LrR/deqpt52DRnUgLywQ6J94qaVqRWC2RK1TqKLESjLFcwOAJ1hirxYjl/KqwqmPpqjAJlGK1ozpWI3XKR3MyBMzwyvtKbUll6//6VqvrWHYTQXsreyvnfmZFSUUaqr017T6qjiLTO6ZMV5vmmjRa1cGlI6taJvT6L7onfinPRFXe0evDtSvcq2i3VRLMSa8opyamJub+M4Ozj85evjFszXpFzRXGIZPMzTG1zeUK7fYn/cRzttlJbBIVZqZ2v797f3dxpwWJNRaZau4mLJniVDfQB/N9krlLspCukYxJop9YC1wPv4ly0tHKaQXq0Ac5SngDhFYLTj9py5R+PVY6/Y86ndwTO0Hf+M3x9JMwxFgygiTestaKLO5LczKiWKx/Y6zmKkhdIBnTZvYhq7QxL6FU8C2vDVcadHKI9t1HczFWc0F3sCWrrSgDSfbDi++PT37XnIPANsjFiumAHC3Wi4NZxSJJWyy7lqwmkMZQ3rEKrMVAKLVqP9CALpWjhccOXKnkbz258asaVo9QUpdEpSW2hyUu1CFt0gCrERhX2pUJJMm4P1QDK/nLE78dnfvj9F+jM99vzN++53Qc5R0CERrLIHrVEou30ERWK8k9tszJ89uvJ0ynvDI5dYenls+e9DgkgYwb1j0RVT2J0mRF9xFbyKoTJM5TYilaCDOkW88g06DTsFqTNlgpMANXrtOguCaIhdFey0liJVYiOHhs8NjFEVCnR4llYccBjsu1jN+VuuuLEst3MUx2XRZpmGsZ+ZWc+S+dPCVWA8skP7GnttOsECL1IetU+ZfTu6woZhtjyY3XgsuOZRmaBvVKrFBofiruh9QM6YQpsVaQ74LrC/wsFqtFDd6VWL45rDBk8kPuoCE81RmO7u+VZsBOORuEoIBElqz40YizXEm9JXdwaGeV1AVk6uEYDaw49MqslsFNgVSuEKXj4ze1l+pcJLGk1ZcV9EqsBqIowyQ36JFihEoMsjLdtPGWvwpsdWJRRsegmF+JYOQ5qxtiyx39UPjM2nnOOrG4ccGwB394hA1TBcscE4IE0LmN19zfqVxuL6aUkvKyLJ52yAlqPZaiEktRiaWoqMRSVGIpthL+Aw9FiM784caRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyMzoxMS0wNTowMO0Me5MAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0FVUy5zdmdlWlDKAAAAAElFTkSuQmCC"},"19":{"admin":"Azerbaijan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACw0lEQVR42u2aPWgUQRiGp7KysJADK3+wsbCRWBmL+I9gjpB0iohyNiLKWYhgRIJVIERJcyBamKCIEjgLUXKKkkJFLQQtREKEJBqjBHL+K4cWbwIjyx6zu3PnXfZpHvb29r7ZnX3u+2Zn1hhTGB39CKFv0gUQsSBiQcSiIyBiQcSCiAUhYkHEgogFIWJBxIKIBSFiQcSC6WN25ODp829gtm3/QN/9eXYfONu/c572/lq0q1aq76k17ev1dLwZ/2xutuTd+XbKrGlpDdL+NmrMJPHDjq8eJ6zdicHMnR27353ZsHffxpnOPaV8Zqa36+HJgrYnL6y+0f4j2G6S621kxutbbZuoP07SmMvNrs/5BCl1vvfczT4qVb7OPi93/LlWWVEpir9fjF2cHPxZeJp5NTH3oHfzlW1T4+uWdT1Lfg6N0P+1IGLl55ace33pm61RZen7wqf+8pOB7uunPqzdPnRkqzQSpeDsiWNDfcuV2+p/2xqfJm0XXF2pX7df9ozlJJNLHKmmAopMiJWfXrlp+tBVZSY7S7koVV1WJEu1WCpzdq7Snqjl2M5bklVZUHppsI9YqaBuuYbhtljSIp6mX1Zdfnxrl3KeBv6KHzX//a+BNmJ5oLKInvLsIhjvKU/qBDUV7exFxkKsCNGU54JiKW8hFqWwmGTYrlIoWe1SqIkJxlgpYtjgPZ6myoIM3hGrVROb9gy7tu285bLE5GsxBLEWFZVdbL3sCVIXsZggRazQQqa1v3+yV8iSjkobSzqIFWEAXs9FaMRqsrU/X9GCr80ob4W9NrOY/mC+pnBNduT4vc5y07P96JaO9Z6j2TH9xk8BjTHF0uE2CH3TDA/ncgsftO3CsON97Xdv1z1C9SPDOiheHF/n0yxxgt9GuKkQupMugIgFEQsiFh0BEQsiFkQsCBELIhZELAgRCyIWRCwIPfEv4VStsKuTQYgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI0OjQ1LTA1OjAws39KnQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQVpFLnN2Z3usgD0AAAAASUVORK5CYII="},"24":{"admin":"Bangladesh","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACn0lEQVR42u2dPS9EQRSGtxalaIhGSFYk66tQEcuusGyyFY0NiYpOIhK1hlqpQjZR+An4AVQKttSpVEKikChexSTiI+7dufPxNE/Bunfu5smZM+fOHLlc+8ZGtQphyuQrgIgFEQsiFl8ERCyIWBCxIEQsiFhus/N5fW+x62eiCGL9ItD0yPLd/MXOUqVv9vW4d/KxdH65Nb49k79ujr1Mb933DL8Xm6J+IuqT+itdAeGiFquQr7dVbvavyrXymxR5GRjonppIwqeDwa6phq6mKw9trjQrV2gUuFimTA8dhUaxnlymv6imO/bfrq4tzKBUUGKt9tVKc6OayFot03fU3TUSxMr5njkpWmQl088xLOpszF+llFa7o9RXaoSR6oVS6IVY1f9NfJqestVLI0csR9NzX6LUd3JHlNq7P0Qt47Nd8aW7coyiMBHq9Ofy5BjFtOjy4FTXtlPqtCmfrhx41T6MWOVCek7c8kAsLc7TesfnJvV0wZYh3ByW9hG0Ig65E9s0Ej0pYlmitqmEGqtM6kkRyxL9qq0nr8sjliX6nl39fcLVZsMAMy0303YXyqF2srFgU/hQxfKlACGxAqzFE7EoOiAWYiFWMrGU0saTvLMqtMSTs8nd0iHlBsQKqkBqM/GnQOr0Kx0fX0LzSoeX0KTtsW6b8XGiZNtMxiebs9ro1zrqifR0iOV93DIjDVuTEevzMEUY+RaHKRw9/uXj6s9k/ah2Olfm+BfpPNNfTEfs/arLc8SeDg4plxVoCuJ9GyPXjkjQxojGaymv+CJKz+NpFallvM1WkboLrSIjam5rSqY9T8mny6/NbZGJdtyf7bglhNmO22zEbVK/pR03YqXwbwQUgVAHsSBiQcSCELEgYkHEghCxIGJBxILw3/wAd3GLQCDAyg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI2OjE3LTA1OjAwaPWF7QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkdELnN2Z8hsolUAAAAASUVORK5CYII="},"27":{"admin":"The Bahamas","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAB4UlEQVR42u2dO0jDUBRAryhuDgVRKnEQu4haQQWHDEpBqNCt+AEdxVU6uLqo4OxnMDp0EQqliIgIQkUF8d9BXRxEKQhCt6IOTg5ZIsWCmheb5CxnKSk0OfTk8/KeiHQf6mmRuaChiaST5wUI7WBAQtIsEjipf61qGtmYLIoYl7tv7Bpoh1hWSutM27ZIIrrQwQ6C9on1RTK9c3BJZDGffGFnQfvEIpRQoViEEioXi1BC5WIRSqhQLEIJlYtFKKFysQglYjlEQolY6iUjlIhFKKHLxCKUiEUoofvFKgllfyQVXj6e0I6mjYzJsceD+bVcKb/7tPxW5b/td9v6mZUtloU9q+GputvN8ZXR9oePwtNzJO5PFvfuUwMxZ7b6C10jlpXDp7G7hvWzrp1477WfJatkulIsk8GrRq02NxtKVLcM5aMXWX2fw4lYCkPp/N8+9KxYhBKxCCVieYWEErEcCuXNe7amb4vDj1i2xRGxEIsUIhYn75DbDRCxeJKIWP8aO86cEItbBojFlR30plglA/28NxDPewMJzd8ilSkTQ5MZmszLFJDXv6BvXlhlUl3EInaQSUGgF8Xiyg4SO8hUkZDJbSFi/VAmc+0dYgdZQACy5AlkkSYIWVYOOk2e2UEF/ARzD1bDiEJXIAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjc6MDgtMDU6MDC91Z6kAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CSFMuc3Zn6/pbEgAAAABJRU5ErkJggg=="},"33":{"admin":"Bolivia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABEEAIAAACovNt2AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZ0lEQVR42u3aoU7DUBSA4eNQEzNVCFRxYDENjwFiz4CChOxVJnghMtkgCE+Aw0BmECCalJLbreva9TOfWG633tM/65I11uv5PM/Jbg0joLAoLArLICgsCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCxSWON3Nlvld1uubDo25T3TP3er9fF7wBitbpgDM95Oby7O7sluja/n8iQ75yDclC9Z1uTnR/mQXf+/poVtz6GyJuVMIn1jbH3BJjwHYe0zr0mHJQIKi8KisAyCwqKwKCxSWBQWB2fHfw0Ji76xKCxSWBTWFH/wCosUFoU1kKc6f258R3L7O9CDnDGJmFo+V360YfUYWdRHOV7rD/zXX6+OdZR7rOSyy0z2bdw+ra4uF2S3RsTja/E+ZpfLovjL9PWHOuf+59PnHhsvDLmLRkBhUVgUlkFQWBQWhUUKi8KisEhhUVgUFiksCovCIoVFYVFYpLA4TL8BaXQsNkZuXjAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI5OjAwLTA1OjAwkPPgcAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQk9MLnN2ZxRPa/kAAAAASUVORK5CYII="},"34":{"admin":"Brazil","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABGEAIAAADldHp9AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHTklEQVR42u1dXWwUVRjdGiQYfCiNsmmJlfqzqAGxkbYKwZjGUIk/JBIWMfFBIrpW/GlJfNhYSCQEESNaEBRrmq2pCJXSSLDaNJUEUwMqldTaJaikGqsSElATrFHQh+PDl1zv+M3cO7Mzs9/LyWZm587MnTPnfvfc795JJC57Y+f8BYKCllGqQFCIJSjEEhRiSUUICrEEhViCQixBQSFWAXBRc3f2hplAqQ0hlhGWv96RnteyecfBiYr0qcr84xd/CcQW7JVaEmKxsHRP+3BtLr3/vZHUY59eeezC1Pzf42P1ickUL2RP9iRqsBf/xFFSe0Ks/8AbG9+5aG7Xa6Mfr08uhTJRGtHfdAv+iaNS5Z0D1a9KTQqx/sXM1b3rrnoy/8LIm1OOUupwkCoZSkBpomGJYtanvZ1HystavZFJtxcahpJxFiFWUcRPzQf7ls+chMdvTiNnDcNZcMai07DiMQsOnPysoXS/W32yRT6c/ZYf94zOWSjEirxZsGHyR7fNyPP1yZk0at/QLeJKcFUxNyzi19jd29jTft191Cxw++APL/zg6/Kpb3e016V+bq17paJmxfPZTefqpgGxBXvxT2/EpYaFECvU+kTNAj6N3q/uWlA18OgdTUOL7pr9UkPpQ79cPrtmdPU3iQ2zpjzT4oyX/ll9pKUVR6EElOb2GmJoWERdn6hZwNEnPEjoTf309L7l4xwCuUWUvHtbxyWzPuRrGO4iJqZr1M0C1czU9dG+qPqkftq1D8zL3H93mx9k0ukZzoiz8+M5aFiEDYsomgVUnzgBNZonNFjBUEpFnB1X4jbmi6TpGv5LRBedmgX8kByNET9m8htxJW6byEgaFmEOxp9N9uevGHNrZgKhDeGhlEovt+qlGhah1rCwXRA1C9yakDSWKmzDx28cObGXrh5QS6gxIRbLLPA2kIJjgwzPzRFXa2LeUsMiRKZrODOfvA2kwESICqVozxFXbuLphy5LrLCZT94GenX41Jl1uaaVDdlVE23XLMk0PpJLpjeumXjr7BOVLybeHQCuXrFt065WbKeIo2pPL/tr66HUloahzTfhkQdDL/heJkNPOsOiYKZrmM1MPu3Or/yh5NaSzuvz5/q6DqS+XzW4dMeJ44u7M0BswV4gtg+W/fTtcPPa4c/XtM3B7zMlv23/9Y+eh4f7D9+8M9czMDgDCFKCdpV7b/99Y9YPevHDeX7tFSxLLBizwFvmEx/Ht3+X6+oFgYAgB0VQipJGRyxKO2zB7619x45234kSoHxU50A+E2JhUMhWAo+KeAoBGRZhMAvMc5623LO7qrdJ16glM/NPrJ8EpaGIvXMPLXnw5SSaTtpookyqYaAXflM6grIgK85u0k/01n1xa1j4niXmX+aTSdXwzQXkF1C/Co/WVoMFUoJ8lHCUaiCWjmT8WA3/xB0514At2iHY92Vamx/TpPiDLW73qiWrPcFgQm8QFxoJqtHmEs0xfmMvX8lwRyYvmzfDAk/QWrBv1yzwL4rSEQvZUYW1DKBqaHwRe9GoTiUZ9E9XGu7IFo10L6euVq0ZFrbMTD/eJw6xkHxXWGJBvahGQjXRdFINo6oGtVOVFSG834GE8/8tmK4mxLLb3jvP49PRy4RYUBqTcJsTM4FkNDKjvU5Vw3BH5mnQ5uQLlFi6JGC/tUpXpnlTCHoF47CDxKAXdAskwxbspcTSvU5+1LPaFIY0eDd/bzjpe8EM41BjwlZpiMZoXxL0OjVy/LnFQ/69irrg3fIUj2hNtOLYDVSH8AjLltVWrH3aWz8RR6EEqm3mvU6UgEgLlIJnhlEEP2IpXY6XL/kR/g3gUIPUPMB3ftvUJBkE1LA9TR68qlLYYku9qPeG8YPz/WP7pn9lN3hXLWU8HR+zIaI4pKNWHHpSIBO6/c7NEN8+VbXKlt2qRnjOQzrmGPkhHc4gtF2pR7IvhwR0kKdQjhfVUZDMOafUJGCP7SA0J23G1viX83QuXdPGQaigsxbyEdcAintLm3EOGEKxMEl4Ev3cxli62Ticpo3qB91iqwF1tlLpoBPQeXoFPzalycpFl+jndrTRrW7xU5NpHoSJcUCpSekCZcJeUApbKEElNTnQlRe8TfYCBjmZgtJIpRoUEXtVtaOTKbw5fzKZIqDpX2qzGMz0L11vFIqodhRoqO6WUtQskOlflies8jGYCas6TVIHp3UTVjmRpUxYDZFhYXeKvVu3nQbp3qbYR3hd03isIMpZ4Tj4RUFoeO48MVXtrER+FdO4rsnu3KzQZYz8yDVFyRx9UtddjslSbPFY5gsJtW7nUsvCa0IsC6s/2FoqEmhrqciQmgVCLP7itnaTp/kTRmRx29gi37AwSVF0PlaW45YPCFjLGUcwjjMW3dfC5JMnfsx+kU+eFPVHmqhhQU1Xb9mb1MyUT1/J17+MFn+Tz8oJsYymtanOfsy/KyHECtKwkE/3CrHkY+NCLEEhlqCgEEtQiCUoxBIUFGIJCrEEhViCgv+L/wADAE2QfXfbBwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjk6MTQtMDU6MDCoFsT9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CUkEuc3ZnU5e+DQAAABh0RVh0c3ZnOnRpdGxlAEZsYWcgb2YgQnJhemlsnLDlWgAAAABJRU5ErkJggg=="},"38":{"admin":"Botswana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABAklEQVR42u3bsalCQRCG0SnCRLAjYzvxpsaWYA2bGNqI2YIYGIlgaiBoAWIgzoByz79wIp88Ll+kawxDa72TuYZHQGFRWBSWB0FhUVgUFiksCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCzy27CW57bou1ff/UHW6z/1399/bP9PrKbb+2FG5hqn43V+u5C5xsOsYLFfO07+icnGcfJPmJmZmZmZmZmZmY12PiN2Sj55962WU/Jdoe/hreR2g5tDLLmP5a4jS26QuqvurnrF+/uVDv38i8KisDwICovCorA8CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFn/SJxiv5sAOieSHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMDoyNC0wNTowMBsCNxQAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JXQS5zdmcDWi++AAAAAElFTkSuQmCC"},"39":{"admin":"Central African Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADs0lEQVR42u3dTUgUYRzH8YHIBfUQ2otGHTQhA6UCMygvRRYVeAgihKgIMUI6FL1TVIJJSgkhBZ0yKshL0SGRQFEoCiNRumRgdQmDQBIC7W2D+W/wyLPPMPsy+/Z8Lz9kdmaemWc/83+emV1cx8lbV9vaYVvuGqx41Nw7/npRec2m4FJasbOHHWABK9tgjVUfu/TnTOjcVP1k2alt/cfvAAtYScgVn7c8PVk2GnpyumR234vmtr0fgJVQLqhuuHLTblhuFwim8Le3407+rbnrbRumgEXFiuUaUq8k5W/BFA6P/XYcqVuLl9WFzr7MhGsRWOp0JeNgbZ1s7Dp0Y8fd/S0HStSUWvW+tG9N8YRULEmZb+nrS6ZyHgasjK5YAuVL52Be4ZzQmXo2VFTwScWkp75Oz0x3+9rvKYLlVkdgZcEcS+qTDHbepCLpDo7CS2jmh2r+XqhkKARWlJR6I7XHG5YQlGGUyXvQtTlH7gqFi16fIuku6Zi92rR5IL2nneOw9JuqZEH0v8/kqhc0xhmVy+vN4ccblx+M3CGmiRewsqxiCRoB9Lz4fvmqMrnj08FFhkL/J692VsJdlmWw/JyvPDhQM+thuScggKQ+CSP9qZX6GCLmATGpXZaDFUuHFdwzqhhg6W+V/9qgPGeP8tGNtp/1ww0TR7fPux+M74pUO87wkNZ03WcQrPguEn0r/7CCmHvp60cZYuJK45zJsH6ElFqB9JpkelXruHlAvVtMPSzvS9R01t5L9DT1j3eatk3kHZHc2dgy/rArXSkfVKe+3fMVu+sv/gwalrSS3h6OL6VMJLIHJ/wg3Bd+Z1tOD/UO9t0LGpa0YmcPAwtYwAIWsIAFLGABC1jAAhawgAUsYAELWMACFrCABSxgAQtYwAIWsIAFLGABC1g2wuouHPnYvdK27N1zre7EkqBhSSt29rBTWXW7Z/VX27Kp/0h7VX3QsKQVO3sYWMACFrCABSxgAQtYwAIWsIAFLGABC1jAAhawgAUsYAELWMACFrCABSxgAQtYwLIR1vDSgdmCNkn5P8fqEj2HRoZD+aN+1vFeM+j9mLaVJa8WXv5ROhM0LPk5BfVI9MzM/onveNR0/PzWTZQfBDAt0V/1sx/TVt5t+V+ivTr9q7O2qDXwrya7rRiP0Mdxxtw/yXqPYu1VLWNYNUcyE2BZkMACFrASw6QmsICV/bBsGxMYClNasSyrW8BiKAQWsIAFLGABC1jAAhawgAUsYAELWMlIYAGLigUsYAHrf/4D+/vF6KjUa1UAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjM1LTA1OjAwcd88PgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FGLnN2Z8KCGZwAAAAASUVORK5CYII="},"40":{"admin":"Canada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC6klEQVR42u2dMU4rMRBAU3AIGmqOgESNBC0VN0A5AhWIkhtwCQoqOiQKRMEFqLmIKZ4i+ct/FxPbG2/2NaPIduzx+sUej2edVQirw9VhT/Lo/ug+hO+777sQwmf4VA5LnhJPrK9xXAmWYAmWUrAES7AES7AEa1fy4f3hfSMFS7AqyK+zr7MQjtfH643sRzfBmjFYN5c3l/9qSIpgCVbRXJUOGynkCpZg/VmuX9evw3qSK1iC9R/5+Pb4tjHJ4xY/Dj4OfhswcikZDzO1UbNgLRos2j19Pn3eYHF+cn6Spy3fwuriM+mCtWiwXq5frtv0gpoFa6FgtRiYKV0SgtW18Z6/8OVIatN4F6xwdXF1UU9/ahOsRYCFST7keSK3lv5Dxz7xnlGwugaLMuMlYycnc0lavq4Jn5rttMgSmeNczemXYDUEi18/xjLQkMLQMnjpbJQ6Noc87NtpHkNDL9KlNp5B8YSRQkl6lDO3CVYTsMZnmvHHHeNFW7EXajtJDePnjPka5jgsBKvhUsjvu9weKgeLxS62pVo7LASrofFevqcDqVrDU15b/r5SsBqCVXdP14PM3zkKVkOwcg6M5yXjg23B2hlYGLn7ARa9yD9nFKzKYJHLzm6f5qr4CdC7nOcgWJXBqnsU05sccucK1kTuhnFf0RxlzlwlWBOdFRJYN99lEc3/Gh4oWBMdQmP2lrhMp5dou11goGBNGt3AGVzdKKsWEg1L3vYRrB2EzcQ7x/laUYI1g3isHvqVvhEkWLOPIH26fbrdxTDQIq0bQbq3ocnTe7/aRcELVhdglYfZ9PbejmB1AVYaGEhbDDzzCvMZTle8SnyLXCSLGrmU5FsEzFBbOtgt3jQUrC7AImogxoWU/DDo/HBhnAi0Qov5MQuCtaCL15iZ+rnASLAES7AE67crjXq4ukiwBEuwBCvP+yVYgtXk4pAp72UQrEWAhY9KsARLsARLG0uwFgtWn3/BIlj+l45gCZZglcsfHBPAdZqOceQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjUyLTA1OjAwchcLNwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FOLnN2Z/LyUl0AAAAASUVORK5CYII="},"42":{"admin":"Chile","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACcElEQVR42u3aQShkcRzA8XddzR7YJoVcOIjLpJAjtYcNSRIXDvYgZg+THEiNcti0tRFxkJs4bIhEidGSGg6aLcZBkWLmgEREI+04/C6vXm883j/NPN/Ltzm8mf8079P//5/3nqa5y8uml1K9U56VnpPq+EM8Fn+iyVANWBRYwAIWsIAFLAosYAELWMACFk0xWPkVDemLha6xysif/8AClrIOPE76D7zCC1jAUjZXHZ1F/Hfullh/ILgALGApqGCSYeTEAwtYik92uP14/eZzTqj2y3wPsIBlWtmMm1W/COoHkznM7F3u0m9ps5vA+qCwBIGvasi7Gw5G932XbcbK/GQcTKiZvWv068zq4fDbZjVgOWrG6vaM/f43ftl0vRbLs/MlhIWdf47AcuAeq9L3YzuwL7OO9YEj2kXa/YjMfPavcgHLsZt32SHNhf4+nc4lHlIWSuHI5h1Ylir7pMRD7uUeZV7X2NmqA+vDzVjGbbvZDqzG1fVrow5YwHqhAkX/0cs7wYJopyx5cmNHj0zmNmABy9IiKHSEkX5LLq8bsnrrt5rlooPaBRFYDoQlOOTUWlng5LKCHK9qCw8sx8J67YVNmcNU3eQBFg/6cRMaWMCiwAIWsIAFLGBRYHE6gQUsYAGL2oT12ueiEh+vf+zYDhrjrSHjJ6uFZeXRRVXHvOf3ec+xtI6r4pLW76newM/Oxr6y89vRicEMmgzVwodF2bmfKFVbYFFgUWBRYPFDUGBRYFFg8UNQYFFgUWBRCiwKLAosSoFFgUWBRSmwKLAosCgFFgUWBRalwKLAosCiFFgUWBRYlAKLAosCi1Jg0WTqM17OTk6rT8x7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMToxNC0wNTowMHpPW8kAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NITC5zdmevPVD1AAAAAElFTkSuQmCC"},"43":{"admin":"China","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADC0lEQVR42u2cv0ocURSHL674L/gvr2AVLaIi6CsoadIYfANJuqRQ2yVgZSk+gIXaaCWChVgmD5AqKAqCIEgIEhESWMHfFAduZh135u7MznzNx7DizO7cb88599w7687P3oyNjMIi8KL29tNwvRyfxTGcELEgYuXBy+O5H0P7DCpitVqLzE1uDi9bjW4OP04PvL9e/LDz6g+DilipItNdbeuk55349+LbQZe7XVj93P+aQS3CVMO1J7qEOLPiU2P37IsbbzQul5yTXr9nN/71TpAWSx6xlKRCDPOvta/rfbVIrCcqel19n98enAonNMxZLMn08PPorrYivbKNgjqnNJJS2V4FFlQsm6qUnkJLLMkY1JKLJZlU/Shu5ZWe0K4kYtkkaGugvNoBUup+Zq/efapjSyToGLH8+ZoYOiE2r8Y0ZxQlGTPHQoil77cGqTlVSttYZdsBSc4gNdOkTv2vOlu6br6KI1aitqQfjf7zin3d/tW+4lEJNNukqbNJL53fpkUkKFAqtMklVpckepnjcI1N+5XQMeV8oWssWxS/OEp5ybGda4uwA4p3DVvUXIiLW96xUhIxA7ESRa/m9ZPVSwsyDABiJVu5i0uFHpVAWdFDrGdSYdQO9cSKKrCYequdqVDXotLqGLE0YHaGaHdHSTtN+P1efDt3UOla7NkqkFjNt3RpqOyaYFwXyu5EsCV86E07mljYzrvdDUE6LmjEUrKze6GSx4/Qc8O4hojtafHgV4j349I3HqPvfUtvRQMfenHaLuyIzEnZ856ZWNJXKkf1X4keEEWs3J7qKeszx4gVvJVAHx+xMlhUtglO5XmINUdiW+UiVtTm8J7SSb+XC1ZaLMUnK5ZtzNJhR6xU+ynEEA+ZweqJ9VT38NsNiJVbWwEiFkQsCBELIhZELH5kDGYlFjcXErEgYkHEghCxIGJBxIIQseic5XZ+xKLRilgIhFiQGosbARELIhZELG4EpTdiQcSCiAUhYsFqikXxCx0zLEgqhB2zAwKxIBELIhZELG4EExfEgogFq8xH2u1W8NlacbkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMxOjI1LTA1OjAwUrdXngAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0hOLnN2Z9X9A5UAAAAASUVORK5CYII="},"44":{"admin":"Ivory Coast","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABIElEQVR42u3aIRIBYRjH4ZdBFh1BUVRRVzSKZFxA1jmAISkcwA0EM04gusYGxREIPnz2eW5g9jc7/u9spSgWi8heozts7ZfVa2c2muf7Kw6r2/qyGTdP/U07pnGOXb6/pRogLISFsEBYCOsj6jGIrQcpLJ7J/NAgrF+1jV5MhAV/GdY9jjH1IIWFsKxCvLEQFsLi89yxrMIk3LHAKkRYCAuEhbDKvAqFBVYhwkJYICyEZRUKC6xChFVavsciCd9jgbAQFsL6PncsYSGsfLhjCYuXuGORhDsWWIUIC2FZhXhjISyExZu5Y1mFSbhjgVWIsBAWCAthlXkVCgusQoSFsEBYCMsqFBZYhQgLYfmP9X6Zf5XljSWvJGqe3Y/K/APlB6G5QLnEpZn8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMjowNi0wNTowMMpN8X0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NJVi5zdmdOMQxzAAAAAElFTkSuQmCC"},"45":{"admin":"Cameroon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFQUlEQVR42u2dTUhUURTHZ6WbyIQaNb9txiIigjIigiKCFiER0SZrtNoFCRJFCG0KFxW1iBJdJCZSESR90M5N4EKC/IxIneyLJIwikgpslGDOCE/evDf3vnPvzPv4bw6PN/fd4N1f5/zPuec+Q6FQa2ss5nXb1r791M6VYxcKtkbXDw0VFtbWesY2FeyrrSc7/eJwvKpnYePYjXCe0Sbqhu6GG8XvW1nzeMuZx0aj4ajVtf2vdB0CWO4B6/2GIwerzsviYg+NNHwWoIgDRxZguQgvI1jiXkotiLJeymo8wHIdWJyQZz9SZB5ZgBAKPQCWlcaSBUVCS5lH2gY4ccgAlqs1lrMwx7f2MIl4NYDlUo1l5V34GaLVPKn7AhmfiJAHWK7WWM40E1/go9wQCz1trYxtNoLl3YAoW8figCICR7DFuwksf2gsVb7KCgtxpFBu8K3GylBzsr1OA4HVfYF8EJV3X9Wxspn98X0YPJZL9wqFypjiOSBDXXFGAizXaSz+NrN42RPiPaAaK4N+0rBFA/FuCZZ3/ZYqjQWPBY/lsI7F78RSfB97hf6oY8lqLLX+T6RsAbA8Vsfib1Gr6ke113MAKxgaS1soDFK5gYKLZzWWuE/KfpepeNaJrNADhynUts04g8++zQYF0sB1kKoCSHZrCGC5tPKezd5RTk89skKWHekuvhO9TFaxhmOcK9Qn6vkFWIAltPDvGuoT1UVk9YElfq5QY++obOhE5d2ZpTm/Xb2ytixlc3uuULyaxZ+BgxeywgyLPT4Y/Ro5/Wfb89biFrLj3dEVkYs6/JYbzhVKQwmPlSEYWVxT+Evkv3obbia7LCCardK2GT4iCipV9jkmwLJfVCs7u+rS57KehUevn6yZoFdGd0SeFUJN6blC/uaPs5EQ70vKKVqRiMxPfd4/Xd1LHijef6Cl+qjRUo5G4c/4QukO/Wp+iizNTP+K2nOF/M1m87Mc/wePlaZ88KnhZF/lib+z/QNFcWOwW2YTo2PhcBprWB6jpdlo5lR5Qtu5Qn1lBX5uGNRQaAhSk3m7P9Ts+dV/b7HkocT/+CReqfvJQEkz0Gyq9gr5nyvS8X0H83swW4j3pewvmeuRfvq392Vn+LEIZPMDg9fCcXqK8kd9e4Vuq8KnxgMsETt8f3VHdHLmzNmu8p8iL5RGKuiqEN6EdhtwAQKLhVdygWemzh0vH8nwipPhj0bq2IRW9pEPyfvSdSx4LPFsca7yQUdJm7HEQCGPrPEV00iJ7M+RxuLU2ZVpKXgsztKS9E4BlATrd9ez/OJeWnKydId+pZETm3btqOnkF0ideazcdjoALKGlNaqr73M3F0sb3hza0rTuunEM3aFfaSQ9pVtjudMCrAyLSuHsx63bw6VlX+qa2yuOpWmSMeBF42kkQcYKiIxQKKulZD80AvHuVLxTuaGo5mOkLxXUJLGgp2gGTkB0VsdSK/algYbHkt6Q1v2UojqWvuNcCIWB6HnPbR0LYHkeLIdaR5uWyjAeYHnx+BenC1T79xqgsfx0rhChEGB5so4l7bd8C5ZPP8etWzOhbSbQ4p2zl2e/8LotwPKMxlKrgQAWNBbAAlg6NZazPiqABbBYGktWXAOsgGosZUdSARY8lu46FsBCKGxU1dueurY/IwmwggyWvdcxAgSPBbCk61j2YEFj4Q9hOuzHEgcr++EPe4Ve3iu0XU54LIRCa43F10mGGQAWwFrusUx4pQltFsEOHgtgSdexrPxZrgACWD4JhW7L/gCWl/9eocugCShYXs8K3aOTAJa/wMpp/UmV/Q9UzC80rC9rUwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MzI6MzktMDU6MDCyioZ3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DTVIuc3ZnICDopQAAACB0RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgQ2FtZXJvb26Jr9hlAAAAAElFTkSuQmCC"},"46":{"admin":"Democratic Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGgklEQVR42u1dXYhVVRQ+pFD0kL1I2sSoIwSBCkEDPkX4EDOIBVr0A0FFBUUUWUERNAzGUCkSDSkNFf0YvqRDZtFEUkyp1MAVBmHGQRLtpWCgFwmloqCvh3057T3rnL3W/rvrZTGce+ecvc/9zvrW+tba+1TVFSPP//2XWtgrV46OXn5Q70Pd3nvgvZcPrZ7fO3Ni8IVL03OPXPdY3f72xKF9qy8tTA1P9o9V+pOotdmbqze+m90wMf755aefWtwxe2zgjzqYLk6evHrVqZ+Hn73qhg9m59fvX3tr58Vrnlt3S6W3T32haVcuG/ts8b6dfQePvDXk9k+/nnjzob7BuYHNd63Z1pm/dsu6NYCUAksB2nXOrZ9ObJwaPDYyfdsdxylkVweTAkstA9l1WYDMsIGAddOevX1nBmD158yS7JwwigYsTANWiSmWpZPd2R93fNy/oovs3GCqfacKc7O+OP3N2buXw6rPyIDsCNCJ7LEwsfM7O4sb18PiiAbgYcju3Gznnk2fSJBdY4/Fe4MwPXMyj585cPTdZepLkiM7G6Tqxwngq6Sfy8Orvj7+wCZzYkqIuWR2PraSzgRBf+YklRB5UyJmsvP51PiOILAe/fKjDe9fsD09+FSjJcr5TYuaHUNmxxRLicRYbnvw4lcTDy/aJg+KVK9DARZ8f0uy84GRHBWawqZp1w69/tP5ju04Qki3i8anW67fd2r6RtvZbNcCjeJ4L8iY9XDCBBOJ7Hj9E8ELVu6JvXLy8NCuGeizpmQAWMDajtggZd6U+v/azmz+jVGV6p8EZUyfyIlgAe5fntz9fd9oRQ8SKXCRs7h6qVIFQ2YnEYY7y8ywGAlGhRFitA1irM3D43fO3A+xICSk8Ozi6iWF5ww1OzGxYAlg/Xsc/hK+sz7mxsE7IpvXbp+cG/nQ9my1sybecWZcBT+AypgtIyefeKsGJoAbQHdHz15Z4fYf3nn7yLjtaTOBQrc4G5JqJbsGnonXbxHIzrSY0f7fj/75zIX/FEouIbSusJuXpEAK8kQZrTUgUOaaHRfxkQHqJjvYqVe/Xdj+EnxwV9jA22zvQ45leCncXPhy5pqdHOXVMjs32YFV8MBYAxWuGwon7xNj4Qy5kx3owCuzCy5m0skOv5GtHMfssRDOu/MaenSVl+xJyewayJjS+lPDzM7MykF29AIUA7Dg9t2gMWVP9zcpE4hLc14yZixrQMqUMb3Izm39bzecv22I0L2gQlGUMJyt2JqdR1nXRwhtmtkxpFASJIihQ4Wy0Qc+rU8SZ0tHu2KTMSU8kO0IV2YXC1gYivns4taDHH2UsBQIMUJmx1QwZsvsYgHLJEEfFQoUaSphNm+XZc1OTsBsRXaYnbhe6EMQeJp5F3XhbIjDwhAinewQ8NoWlYuH3h4yJj2ziwwsRFdyhWGzTyu5mh2XUEnXrmoRWyyyaxCB6dKDlpldGBmzVc0uENkpsOg1uy6y4+rG9A7Go2V2YYCV4w5YXpldmA5MCyEmkdmpxxIhO17Kc3crxJIxFVj+ZPc/NTsJuLRqwcuS7MoGVuSanUcZOHuyKw9Y2ciYHjW7JDK7XgAWw96YXOtVxDI7U8aMS3YMVy88swvZNFe7VqAGFfVYhSw9aLXOLsvMrgxg0bsxrTW7MIF5q3V2qZFdTwCrKdmRGuikl256yJilQmqJnvcw02bbG7Pd0k0PmaCHMruUPZYJU3QrNF564LPql5UQKWSHhp/yyK7xXJLeQSUBm7KMmTRwC5cx6X7O+CbXOrue3oM+s3V2EluKpd+NmSMEM2hQ4VWkkunGLNz6oJWZ7KSreP47qChc5DwWczdmkpldeWSXNLCCbhfGtR1PD5BdorlhtMxOuMyiNbtYULZuCiLYoBKks6CpjKlAEfRY4jKmxPIEzexSpk627cL863c9XLPLvq2vbt27twfdQYVs88rs5ECTdEmncWbH+1KNVm89kCC7wrujYgGrcTdmkG0wKGRn7l+qmV1CdgkZM+Q+TypjlmQF95UTrtnpj5czsBJYZ2fuTVp2za6oOC9Wfqdkp1TI1sRikp3NP6UpY9Z9Sb7ehVSQiRxj6dIDJb7GwKJ7JqZ1dgqIokbl1SHu8T47rdkV7oP9l5Yr2anlkBtUxkw/cE6BarN864Ha7IN3XXqgVoIKyyM77WIIdGfcDSq21j8lO7Wkko5mdiX5uSRGuzA1PNk/5iY7+KcyXgauNgxY/wHtNammNY8UKQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6MDktMDU6MDArJxFdAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0Quc3ZngkgrjAAAAABJRU5ErkJggg=="},"47":{"admin":"Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrklEQVR42u3dv0tbYRTG8Qt1dCyCIBgkiKijtEgNhEaMOvUfEPwLSku3IrVbXRwUhWopLg4OLgpZBAlWEEUo0qEFKZrNoYg/IKW0JTi8S6D1F7nvveec97uc1SEfn/PkwL2Jos75xUKBme7sqC2NDfZsH3x+O7z+59fR4eiIxlltKo3ny6ezU1uPXkR8qBJIra3vPR7u0UvKTUfqqLv3YeY3sFKbLc8+rBa+WCL1/TDb2t7mJrBSTqm/ucrL0RVLpIBFSnkhBSxIxdClgKWelISleXNKAYuU8kgKWBwRYlt8wEoopVZGdl4XT5JHEO+6vG9KAYvF55EUsIyklITFByxSymNKAYuU8pJSwIKUR1LAgpQXUsAKiFT9GcI3KWCJIHXfy1Mjl6pkSAHLbEo5fMmnFLCCIOXm2fu54/5ckqSAZbaeO1LJpxSw+MYHrPBINVLP01p8wPoPqYXdT/niJEcEYMVGamai/LVY0E5KTkoFDYsu1ci8+9+KICUzh7R0qaBhsfiART1XnFJBwNKeUnpJmYVFSgELUgZJmYLF4gMWpG4hdTzd96rjh15S6mGRUsCiS107z6sfm59kLJFSDMtSPbex+BTD4hsfsEip4BafMliW6nk4pBTAghSw6FLXktJVz+P6B4hIKe0pJTMLI7oUKWUQliP1bn/zwVCNlAIWpEgpqbAgBSy6FKRkw9JOyj3UACkRsCx1qcuny98GTiElAhaLD1iQIqVkw4IUM6JLQUooLE6dTC+wbial5TePraaUyldFak8ph14yqeAeWPVHynfC1b9R2JGq1HJd2QuWV8qw9KbUv6ToUiJgsfiYMcOCFDNmWG9+bjwf2rRBii4lApalUyekRLzclnrOjC2x3PVc++IjpcTBskSKlBIBy0aXqjaVxvNlnpCRM68Am91UDENA4fcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjM4OjE4LTA1OjAwQfoadwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ09HLnN2Z8XoUVwAAAAASUVORK5CYII="},"49":{"admin":"Colombia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3ZsUrDUBSA4Tvo5tCAZHKUOLto+wBdOnXo4lLwCXyD4KA4S2kfK9CWvk1LOujQIgGRY2zkW74h5N7ce/i3pO12ucxzMtZkBBQWhUVhGQSFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWOQPw6rr9e5y3EUPr/FXX2/nnW7d6zOssnw9n2zIWFNKdxfzh2j7g9nid3Y+5dN+7POd3bo4n6YzNz0/WkZGaQQUFoVFYRkEhUVhUViksCgsCosUFoVFYf0rv/6ZZ4uePd9P32/JWNPs7Wo0fCFjTaub3tP1IxlrqqosKwoyVmFRWBQWhWUQFBaFRWEZBIVFYVFYpLAoLAqLFBZP2z3VWG8eJgK6DAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6NTAtMDU6MDD2X1rqAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0wuc3ZnsjhgTQAAAABJRU5ErkJggg=="},"53":{"admin":"Cuba","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFRUlEQVR42u1daUgVURQ2kkgCabHCMC3wGRm0R0UWhGErSUZRgVG2C4VUP4y0iKyoJKsf/qmgjZIWopVSishooY0WbTdbeCgtlCZFBC/wMxiZZrp35p5Z3jt/Ph4z49zr3G/O+eacc++NKj3SIzXj9oifyQmro6M69v1SkhsVSM3ZuV0Icb05ml+pP6s9It6KLBq1Ittb+09A9pnQtWvt+Ri18rAgLiO1/NLFbiuGPc0v6TlrxrT4mj7LN6ZYJJYqKuj/Gbo7qx1OCorQEUjVK6fHB1e6fOwXA3oBj4W6Z6fNnv6pd9qSNq1IZm7PVD0CkX+G2pKpJbEqy2HnOHW7+rN6YgFxHI5yysFegaXHpYllh3D2HaIqEqt1K245OLXWV+RuIJARgmTX47tmDXxSdDAhPnNNYELKkMISixbLO4bdSQ3npEakILS1V86cWHqSQY0tvpXUJbviH2qMTp3IHqejkZ3XyXnL4TzpZYklrca8gxSOzN0hlJXYFC+/iMayRiytozQMWzgvJL1AblmF55bFontWdohlpMY2ViYkZwZb1JjXrBejM6iKWNqz+K1VY73LhpQWt088mV69L44xElCaWNbwatqAtZPrHtTuzjxy503Wq+P1N99ODeY0bNOj+Vm16GRb1O167Z7kxGqlyWoSigYfra2c+3tFUlPxzf13L4Z+hd6Hgozhh38DpM1D7oz1un8ltiYl//GqwPtRoz5k55/f1PBz68u8N215MMKQWO5idWh4+qS6+oJdZ/au/1UVPF2fygMTocSis3Av9k+MnvP56/PzNy53+F35NfnbKR4ktli2dJieuLWdFhSufPQ99l7Z4+E8VEwsxVg1rn9l+pjg6A3PdgxiNRbRxIIwp7jzsw1jDkwr/jh3T5vDH9hRRhCxQCnIcAodhi9K/H7dbUbnRXe//aiYdC2aSRbmxMJgw2FBhpPrs2b6vjuUl7EuC2qsJbh3Ibi5cbwfw57hEbZVTCzYKnAWMSqtjVEl7Y3wTm0gZ/TeLWdGtss9N2zm2IW7Ezm14suUDkgDhOXQfsEhZKB3YfapJoIo6WlV+8rozSQ0wpgIAegRX21arYNQJ+yW9sq3ecsa8/fgN53Mh50Dif9R0uNMzTsTS1aY2xHLIByISBdoNaobw0wkw5IeZ2qVmFhGYhn2RjaqBEcJge9uMBYlPfMmJDXNj2mpffVaqaD3a2vp6rHgHKGlzCn15dWJDufGItTpjMYSJxa7RXJiiX9/aa+v67U9vrTInFgIB7hLKSFXyOiWxdK7RX1llb5CARZLbXBBfOKahHiPTKSwynYCDQiBQsgDIe1xHIkXEAtqDK5T1m5ZoxqHG3xpsUAObWIYdaHas7Bn2lI+OEQ6Z8cBUt8HSLWkQWLY3A7BhiGCpTZviJ7gzvjqRDJHNqXjx4SMW8kfoZSOnUgS/lbEtamV7WiXk9C+TEI7WQUvrrG4bIYL/ZQlZBDx50I/JlaM0Txp2dJkrXLioYo4Yql1mi2TKaaeHVp+iZ0dWyxl07+YTBFELFXRcH2tBE9YZYtla+EQnmLPxCKR8PpFQZyvSWd0NEBKZ6u0pSm8jBGndCx+8fHCa4yG67zbcXZCpSlq1+v1zrKI4bFKqtrnY4dM2sVtPTfA7rborxosunosow0EjJRTq+W4xdfxVbuoP6O/SpP1YQLDDQTo3g/vr2bud9JT9998ypSCRbZlF/IPj90imGpC2wLY74rsAvbsBP2O/1FOqjYF8QtRKPpsf6M2d3e4sNZnkhksdPsyqNrDgm4DTpE+qLXW4tucOEOpZvwDsTW0wvU+jjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjQwOjA1LTA1OjAw1msuXgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1VCLnN2Z69F/9EAAAAASUVORK5CYII="},"59":{"admin":"Germany","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA60lEQVR42u3VMQ4BQRiG4f8Y4hqO5ULOoHcC/UYrIqJwAoVCMS5gWPyTjM3zFk9lN0w+uxGSJEmSJEmSJEmSJEmSJEmS9HvzNZlvbBdkvnFake897p9b+3z1gtcXj7lqvFn3//QO4w+u3e/97tp+zqdm8pFNzxbDmtL3qQ3RdNhER0DD8pI1LHpikYblRfMn5+OI/QHaD6v1j+z/ED3nPLFoWDQs0rBoWDQs0rDY67DOt9mBzDbul82VzDZK2S3JbKOUYSCzdQQ0LBoWDctB0LBoWDQs0rBoWDQs0rBoWDQs0rBoWDQs0rDYsQ+EqqV97vWrJgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6NDE6NDEtMDU6MDBJrG+JAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ERVUuc3Znu/SIVgAAAB90RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgR2VybWFuecjsIlEAAAAASUVORK5CYII="},"62":{"admin":"Denmark","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABMEAIAAABE71kbAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAByUlEQVR42u3bMUoDURQF0FGIomQRLkBRyAKs0ggJrkBEbIRgFV2DaCq1tAyptLOxE6xDUtrYiCJoGsFCFBnB32g7Zob55LzilpMQDnd+8ibJcFit1mpRZnOutZK+1E9ve/Nptpn+uP+8vPtaX2xth6tF/GmULBOwAqxBuzK1tAYEWBoLLLDAAgsLsMACy+EdLI0lwdJYYGkssCaxsd5nL5YPgAALLLDcCsFyeMcCLI0FFlhggSXBAgssh3ewNJYES2OBpbHAstKRYIEFllshWA7vEiyNBRZYYIElwcoKa6sxs3PtjAWWxooBVvj9Jsr86Zjnp+NRd3dcjVXMOw+Ii3zF4jN53Nzf6OzFmA+v7YXDk7fmzVF/Nf3HjAZn/fOrcLWQ8X4m5ckkNSaHAcuAZcAyYBkDlgHLgGUMWAYsM7mwrHR+r3SsYsa20rGE/rOEDg/PhCVxDu82lyuXcwntsRmPzXgeCyywPEEKlsbSWGD5JzRYGgsssJyxwNJYYGkssDSWxgILLLD83AAWWECA5VYIlm+FYGksLMDSWGBpLLDAkmCBBZYzFlgaS4IFFlhWOmCBJcFyKwTLt0KwNJYES2OBpbHAAktmy2/hVcWm46JcXgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDM6NTEtMDU6MDCnkUmcAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ETksuc3ZnDuNRdgAAAABJRU5ErkJggg=="},"64":{"admin":"Algeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEqklEQVR42u2dTUgVURzFJ0NxIREYRlD2hSGJPWEetbCFFc8+rDSN0lAiTVpkipliEoQgtWilhdiikIJKS4XQUoO0xMgIUwsz0/xANPArI4qyNOi0uHCdYZ7vKbyZsznIPHU2P8793/P/3/sUxTv5uBpC1dfA2mxHVPXAnvHAkbOz07Njs5NUfVUIDcEiWASLYBEsgkV0CBbBIlgEy3Rg9c0MJw/UERqCRcciWASLYBEsokOwCBbBIlgEi7tCKsGiYxEsgkWwCBaVYDmnfrakQTUi4ETCM/Xg1lP736ohMZkJfg6f3oKqwZr+b1PNjtbVPyN74gdqf1dMJE/5EiOCNYcCIEdd5GN1Q/4h24TacTd4fZD6p6XEv8weLGrHzPIm+xto50DQrchLPT0Rz5PeD1VleBd8/FJZWVrfPd02kjcaQ7AUK8OUciM8Ty1/EL92pd0HuLzzX5aq7hZVhElW+Xe6J8PD4jvHthS33LlpZcgsBxacCZ6kBYf+c9m3oOJftSf7ZoTWA7KprIdtT9MJlmlrJviTvKgZcaOa3KDC7Ut6T2ceKUibbC07+SgXldaPtI5zHw5A8QSfDl/LSbyyA2ABu88VBeuKm61TkylWQCon375RbTK+nEFLyje9VsfhcLZf6bFR953dFQIj1F59WXG2M9sAnBXwUqyAlPFqCX4Gb8N/cFfcAJhQe0EJlkdqnG2nl1oiL3wyZHjyxGtVoj0A4cJC51hYNKEEy2M0OCD2q5oCUIzUUvpILVxAij2jWZdFE4IlV1RaSyH8DN62EMk7oLFmiGoqsOA6wMVIXXV5c+g9tVOspdwL1sTS0uxKxg1m9CotxwJ8WDQXolf4/fyrFR0pXZ/C9kZfRQBBsDx4D4gMXas8F3+GVznbhEavUD9Px8LX+3JfdGoF3oW4gWCZZBHUXwqRTjkLVnvR9b7SBrE/ODVZc7ixUERt5MXFo0XV4tsJlseHC/oZlRgroFc4vwlSICWig4S9v/nYrqxwOeAgWB5fXclLnuxb6BIaKdi1aiz4E3xL7AxqAY1mDsZsCJaHKcZdjLST0ahxfdAP8Sb6gFpgiYpCfnA09faFIZTz5p7oMglYKMZlx5LxcgUs+TAFQDESwwI+EUEU+GZdKE27FGr5lrP7QSOOJb5Xy720HItLoceApVVpQd1bYwEj/X0oaiyrDf2ZfFcoQ4ZdofFoVGtXKDoT3EjcFTJuUKzZzHE9x8JkFdo1ohvBnwiWYqYZdnF6XSt0cCV57ypuWNOYYiR5x1gfk/cQK5Tw4vP59QqNH7Fnr1Ax3ySW8an2RZpu+Nf2IVgmCUuNgMV5LILFCVKC5QkBhFyHceadYLlhTFmr4aN/SseV+7F4Ssfk5wqNTGuJXUXXzxUi6+K5QkuchNaPJORPxZPQ2OvJJ6ERMfAktEXvbkAVpX93g1asavzuBuj/uxsYN1j5tpn53TMjD/rxthnejzXH/VioxuBn2Cdqxa28H4tgzbMmQx4m3uiHXiFqKd7oR7B4BynBIlgEi2BRCRa/mYJg0bEIFsGiEiyCRbAIFsEiWFSCxV0hwaJjESyCRSVYBItgESyCRbCoBItgLYb+BfM3DsS1ovzOAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NDozMC0wNTowMCVVUNYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0RaQS5zdmfcFAlaAAAAH3RFWHRzdmc6ZGVzY3JpcHRpb24ARmxhZyBvZiBBbGdlcmlho8plBgAAAABJRU5ErkJggg=="},"65":{"admin":"Ecuador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2bfWhVZRzHTyt0f0iu5gs5S3S2dPiSrAnpTCnIVWhtLigs51wrt1kWFbGQXJl/pA3Wi2Nh5ksLRAqckbXUP0Zjki+9KZUta1AxlZSCojGQgvu5f3wvj+dy1zZ3zzm/f74cnvuc3zn3eT73+/ye3znX+7fr9Ckv29R0cNWzITA1sEyHWy92/+Z56QRWyjdkao5lampgmRpYpgaWaVSScQPLthrmWKYGlqk5n4EV8GkOBY7mWKYGlqmBZWpg2UCYGlhDoOeWf/3RtCtdvdD9Td/VfclT6YSetiscNrDSYKCBoLOsdV3eqsbW2rEPF635+dGtVYs4bjz+xsi13UfnHDjQUg80Cfcsxz9NPr7xkRH059yGKY0Ti1vQzhWfFJbmdfV05syYFE3IIvFVz1QeOTSyrTWr4Zf8b6uXLPts+j9FFXk5k2fO33jjuzfsQIt75vw1bS7toKZwAGJcY+3AxFkahwi0o1yRc7kTAyvwVauvej9cPerHLb1rn87+gglGdeJBQVWBUA/zg8kFy8WrNGP+tTm7gNt36TSwgqVMJG6h6mIHHEDw1oyq+6f1vF+/40x5JWA1ZG7LefsZP6SIQLR9hxuaxiwiPqrXNccKe92Za8X05KaO2dfsBQvA0iWSxVHb8SRaOCtq6JhjxZVsCb3E9AvQfAouuqhxjDPhSb5xJJpe18AKLVjxXVsslXYh0BYFSxdHkErFC+lJlmZghfbBM5kW6bMuWyyCqOZewKRJN+pCSWSNgP7Qsacma5MfiOFO4b2wVln8FiB8CHTey9hdMuWKinlv9s18hWMUFGjH4VaOaFxe2Yz3cAxeWkTVCJqwg2Mq92aOFZh6Ons6Kk84CtAw/dWvf/z74gcULPoAUP6xzbO3t95Vs3fWkVHuccXu5lXvjCA+ACmmKHhxRSJzJ5wVL8AaWMFa/pi2eDEzBgrOoV7lOhb+BDRghL74QtOYkjL6KGT05Cp44aj9G6YvGI1quUGLF1TtL1HZD9Ezj5AXSON4xZYtMh6mWX1lXHZVe1Xd40cLuson6WJH1YoWziKOLouqRFCwAFHzLSKE26siARaKV7lgMf2zSqv3vdoDHMDU9NiJrF//QHEmt0WVsx6cVzdu80EwJTJXUbDcfMvACrAqWLSoYxXet+DISxsUqdMXz353foWriperRHDB4oqhAsst00QTLNJn3cHNXVxTVtcLUjhNKmAlR03BGrulbGHTsZKzzx164notuiaUKkIOlnYKn8a+pG7+aWHKSy4UHnyy7YPpBX/fuRW8eHTDMYk5uCRXenIW0YgcByt2J1ohC98Iuy0e+x3VZ+/YvKw002330+Hqn3o7ez39PeFYTP/n9xbffXu5pu1oHD5H9VM9JsKXHTcfLng5ASwph3InqX/3/o5b+vTxMj5dmrvrZLgVjBQsppx9HGAxHFS24vWtmA9pS3J1wQI7XQrZKERhzCMElj5CUccCBbBQUMCCqhVKix9Y1Kj8HCs6YGVOXTJ+e7MX1i+mLUynPsVTsE6dyN1500KMXUFRpLTm7gcZrwESjW1BEMFyR88cq9+ONb+2aML6q86fm5gzNZcFUV3quo21q7eVogDhtqMARISgg5U6XqOPltXvbDewEsACIIavbXzuyltv0wVRYQIOdnycxbFCxlkgRTQi44K6K6QMYTnWyXCYdgJYUosnAim8+paLjp+CmnoV0YjMIx0Fy5L3UCnTyfsF+iIy7bgI9ac/9+csnTIBUKhp0cdVPsXnOOu19vx199wCUprV6RXB0cBKC08aeDpJBPcFPZYqrbyDF94DNLyVoC/60cKn9AQpAOVaCZUzeVsreV4yvOl25MAarP6a8ei7D2RIVK1I51vWr/l+TBEAcQxSHLvKUgtY8as4jzhoDwoWA/8BeOlzQ0Pxa9bIOJO7IFJ/QhUm3q/yg0k/1dcJ3fdCcUoWx/+HUXrC12+whm6Ch+uXx6TqMQsT6bz7p3v90wTHKP/PUbxoYeEDU42Jz7lX57i/4+z2T2fgvHSz0MFVlid3k6/TjJNpz/huLuY3QEMWxY5P33ZngSMa5xLNBcjV4LpRRHeFqUyVVqp0mqlaue9BUJ0CLE3McSkFd+DOFKxcKtLlhuSLow4u7fgQSg1dwQI1PqWO5U6Pe5WojXPkwNKiQPIChy6OWlbQT7W/n/a32h6OxdHj95TKAKWDXs67jS+O8qAa38LDwjQmA4ngd6635/n8p4ofGlylzDgUkS+/ApOChbp98LNwfOuBq8czMh5KDK4OXeRwK+MW3NHjzj39Gqamg6UGlqmBZWpgmRpYNhCmBpapgWVqYNlAmBpYpgaWqYEVFXUfm5gaWKYGlqmBZQNhamCZBkP/A0k6122K1m2UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToyMS0wNTowMKBKMMIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VDVS5zdmfL2mD/AAAAAElFTkSuQmCC"},"66":{"admin":"Egypt","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADJUlEQVR42u2aPUhVYRiAz5ItFhTRUARCYEVL0w2CApeGIlwKCqIlpCFBSppqCLKoiAoiQhTRyCT7gZKgGqIfbZASSQzCLIzsh4uSgQkut+G5wyunazdwO8/ycHi/Hzifj+/7ne+7ycDgstXVm6RcWCYugVQsqVhSsVwIqVhSsaRiSalYUrGkYkmpWFKxpGJJqVhSsaRiSalYUrGkYkmpWFKxZKbEmrx4e9eDF7IUx1e0VFw4AL+/7WhsOeOalMOkMFuYKeRlmrMnfp//sX+s9/W60zdHtj4/cvjU1w1D09caibs+86+bYv2DMy+nxj52/6r+1t83ESOuzPxUrLL+/xBr+uREYfi4YinWf6iDLukyR3xy9HPTk7UQvcqfQbEyRxSZSD4d6qlEiKgIrVGpn7vHq55diXpFmWA+NzJ4q8mslmmxUGpgcfee3KLR4b76Y70x3/CMQJRCmJaGCDMwGzMrVkZfnuyCCkO1PQd3NiAHcciXIHpBIrEPo5iB2YgrlmIVxXp39FHv3urBrnsfah6+yrVOrjxHa8xhRGilJ6MUS7GKJPekxeK5lFj9d25cWn+f1vQo+jOzYinWHLEgcRi35zEe+yuWYhXJqXopsYjAdCmMrWmxmFmxFOsvYpF16BO/Ftmq0/r+7tPauuWKpVhzGA8IoljcDHJexcFBPHRgY86ZVimxmFmxMvryCJQubcTjASkCwXhASiSOVSzFyqNFFIsIZY78RMbiSxASoTUWxCgWEcVSrGKOocDFK2fUiWLFI9N4Ih8LK8VRsTL68vz50Sve/cWbwagLYrExRyZ6MgpSRhXLc6zGeBUN01c6yASJpC92pgpf3jzuQi/PsfzZTJ7MhFI8owi6xJ1W3F3FnrGA8uyqKlaechZlirkKmdJ9ECjmrVgcXVXFmvMbUYhAMF0cY+GLz4z1h35FsTprOrd1bpbw+o72jc1bOra31V9d1b6mteFyXVtVc8XZfWnSChnl6kUmSWWyJFkq5QLTJZCKJRVLKpYLIRVLKpZULCkVSyqWVCwpFUsqllQsKRVLKpZULCkVSyqWVCwpFUsqlswS/wBhPLGpf7jDnwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDU6MDYtMDU6MDAnyAkxAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9FR1kuc3ZnlbvP6AAAAABJRU5ErkJggg=="},"67":{"admin":"Eritrea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGV0lEQVR42u2cT2hdRRTGLxQqIraQgqhgW/yTaJ5pmz8miqg0inRjRWmlXUgbBKspiFA0qRshQjQ8N6K0KEVBRetC2k3FgiihQlwYQottBJUqunioaKsVbSmNi8/FJ4cZzr0zc+/c92ZzuNx339ybN7/3ne+cmZds8b1Va4ev/+DykXcHp/cMjB3bMHPv/PRXfa21Z9/ualzsWvHh4UZj5eOHPm8sR8SZFFO0x+yXVcvOjXRz/HbfytHhnZ+dumluaH7f3vt7BsZ2PTN+Zn3rroXmVN9rErj0IaaoAssUoW164BJ2Caxul6hXuBQTWN2+gJs9f+PVA8dMwEHVVrx4eE/vP4hpShJYwRUuoZbA8hBPb71yzeibCbgEVqQKl7BLYDlFfZWaYgKrpKIhKVwC67/4a+uq6dvecFc4Cdzwfa88PdJMHq72YP3x4KbZ/jsQgcvvS4P9g9f+Of/IgQ3LcF6+6++xiZN9q02vhlA409JWgiY6sM41H31u/fiFE/t/7n0YoOD44uT7O2++cP66l65oHME1Z765c+PALqCGM7j+0kMff9rzbAi8UuO3BmD9dqjnsqFeTl5ABFgsLcy1um/AMWDCq9AtIAXUcCUiziCWiZeLh+tkhctCOCHoEPACKIwU69NfR57Ysm6SkyCOWc9wJSLO8DicTKFtdWmLJLByRGCBKQdYQAFg4TygAXAAAkgBMsZFqiAnRODFEOMYY8ZfpZqKhvbALtN8KPoPEbrCmgQUMNnAhaEBUuy0cCzBwhlGEBHjs8LhvHsVGU9KrWNizXylP0w5YGL/hGm2j8BaxSDiGGpkcnKMr/Rteb8YMQNXrw2Yma/2AasOJpgdkt3gs09i7WEFknjhXayO8r5IuJ3Z+K1W5zK/voqrOUytSW+AC6sagyIrR1Nqg2GX9SPeW62dr7ZosCtcaOAyX90pTCQiVMfucjh5AQUon2xJ6EdjvBA1ibi9gdOk1BCJ1ZtisVrYO0yyvmO8OAkiAlw7WFLtEKvqdSUPl/FeKHewNAmIe10yhcEVcUGgr+/kmAmsqhq/TooFCJBuGCyNZcY13PZkfeK+lMmlSVgxGmOKZ4uts9WuwHkDC3rAZlmTCu3jcEK023bp84AR14YYDZqaECmnaEDj14NimZZrTEAg7fKrGIdTGKdU1jC7eedKk91bUqxwW8m/+HL14rbjB2+9fWryxxe2bJ060Nr25MTo0YXMvTUqGwSIJhTwLqAALWFAOS1yy9SuW6x5nBBNffwUXQB6effm7a9eeuyap374ZP/m5c9PHn9g48TMju8O3tPfvOX0T4iZC1K89Cv1xtTSBCisK3yGITAdy4gnYe1EBLixdeFdSqVw8eu5rtFNS1KBJEB3720e/f51xgjnOTopFm++44pM09I0bYzBuwAlrsT49lTIBQQiJ8dOaJO6pzAGiKExocNRXuPNvEvjLKeTazfe9SArSl7M1qQzTn/cr+d9FjGrRVUAyRRmAsWOkXzVQ4OUkZJRVohIW+yiOPJorH/SgAMXXsCW6bgT+ljuAGk0KW/09r8beGFHTrDsbPHyM3svU4pk1eEWq7xjsZZHewPkK+rH9LbRDxMPmyw74Jp0xj6J6zsJpSwX+C51by7gi5oXoBAYRQEWb3rhik+/JMym3t6qMC0HaRoTscW8JlpOrd5c+4XPPlrAH1OwoZb1mkSHf41jampIpBgszXJ1VSls7p01s9sXY1MgiUix+8pnDv7zL7u55v1YcoeWfBe/F5Ah+cYAk72RyB0gjvqJzDvlZaKpUqwQjUToEww1p0uTAZd7QYGapqdVrYnmDxfouH/7w+FS7C6aZ6j4J/ZIc6ZKsFobrgco/li+elUMFm9rlquE5XTM9WV8DCkmZtRK9Vj6f/UR2ifF0AcKMcGhn7bYyFnqRNdFgVye06VJUcx7ZXUHqFgZH2JZw8Wqx++Z8j5J1h6NRLmdI9yUFAOoTODc+1K1N+/FFMjUB4rnY+0EU19B591FgeKxwzFPc7ULzMH3Y2kWU+0KJH1POYlG/7EWex6Xv6I9VDaLYUdiMfucd0nE7xTG9gXwm8hKBUu/J1qzJl8MrPKVoEyAQk+2r4KgIFjFFEh/47xTFW5qq1Id+32rSoXu25H/BxZ06KOZvqHxt+w/69H8NiOGGCcu1UJQ/nz9C9XYLU2nyDoEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToxNy0wNTowME0VAhsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VSSS5zdmemQMtCAAAAAElFTkSuQmCC"},"68":{"admin":"Spain","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFlklEQVR42u2cb2hVZRzHL6m0zN2x25y3ICUpsz9UUGkUVkiBK1PDP3OlAzFsZVi6YX+c9qIUq7EXjXm3OUGUuWlp7s+9Llxobk2d3HpRWGBFgUJSLyItwhcW+LkvfvJ0LltnXjxn3zcfHp577vPcnefD7/c7zzk7kf4DY09NnCKKw8uIToEosUSJJUosnQhRYokSS5RYoiixRIklSixRlFiixBIllihKLFFiiSNQrG/vfPzR8RsGzyNn5l+I3jHUb4lePP7rmsLr0sH6zTiQ/ZdH/irvGX3NmH9+6Hs7ErN0+72O/H+0o/n5DdnHudJ/Rfa/zmt2P79hMOfND88nP59WGPU/TuRKn2JxZFJiiRJLlFg5IBXGbxNTTaPv/XKg9WzRWC2nxPJFZPqlpnvGhHOtp+s3378Fohf9f1QcmlTUrAWWWJ4xCVGQxouNczZujZ47+dbupaMaoHsM42jJJVYMIRCFKNVZUZfMW3NmVkP9mJchPUSs9Im6hXlv0k+bEc7+uGvmqFull8TK7KYgDRtxnQdmnIil975asjp2EO4pKV4+obzlwgOnbpiaSFSdzK+iB9a3zK2KFrWPe6bytmOfPrSoIxpnHFVjEisjFupYjZCGNmx9rPTP63ttv23zXY6RWCNOLK99avRCFD8kOWrJR5BYVFEIRD3k6oUWpDxLynbbtj2Q8V1x6Wfk3NzqkVg5jVIkqdXN5cvit3yc/9L2vE32npol0nAkrCu4b6BwMbRJEKVslLLqcCnAkYyD1hIiJGLZaz2ksQucue67VLZD1EEjKifabR3PPT/naGvBjlTN2q5E89SWYtoD3yS/25iwZH8r+7xiSFKh1wITb+y1no1Ptv+T9Wsb3tjdV9N1KBU52Nj+RdcL6NVes2nl69W0+ZTtBlcsFfUhFCu9YH/PvIUIQdsVC5lsEiSRWbEQqHfpe0d3Xjx+Y+X5phh0xeKqkxiWmff3XXvv6ZcQoRLrq+6+JdueOrK9d92xi1Ysdp7svhSVk5dYKJKau2HJhyUoRZt+xre3erzmFUNSY/UWV3ZM+Qg5aPOMInWVjVil6dnRZ8ch1vKvp137RNwVC9q4RQ8Ri/FRNpV6sXN65rtELF0bBl4sthVQh/1xCnArlt3etEnQjVgrbrrrp5lbbUK0qRClIHMxPmJBdvPpl14BFovFQx2E+KzqcKT/g7buLU+XjUc7mwqJWESp2vjDZU++b2PYpL9L36lt5FPSHwnOVlf0UGNxvWk/RWtu/kiLwKdCBKKIZoGRBqVYfrvFYEmUmv5aedOqmyGSMUJy8ub82qQlV4iIRWSyCbT/+23zFv1MapYWgY9YqEMyanzw7p7bT5McEY6dcbZPLUmC6GXFgvQTeywZ+bInIy71IOL+spWJgsPuvr8YSLFYWtIQcYWrMz61YrHwtNmdp03cgihFtWSVss9HoBTjEymJW7b2khaBT4UsOQtMjWUv+61YViZXNftQDTLxKUQaK5a73UBtpxvVobpXOFSxaLuq2SjFjhdi2VRokx1iufOKIRGLReXunt0B9yOWV41ln3SgYHfnFUN1E9rS/geOVyqkh0J+Z8+KPYuXoY6Vya23ss8rIUIiFs+qV0xe1/bKvgxnVc+vjNNv7xh61ViU3lRI6GUfX7a01RXHM5el4lZIxPJaYCuWjTFIxrdIYRApabNfxRYGx7u7U+ur35296pHL5mUEZ14xwKkQ8sQBSXAwr6bgdjIbnpZ8N/sIzGgTbi5fGSKxAsbhelOKOILEUiwJ1pmMDFec8P/epsG86crrGPfp+Oxj+j+52Ufwembf//kZ6hnIzVu7/kOsoL9dTm/fuzqpd5CKermtKLFEiaUTIUosUWKJEksnQpRYosQSJZYoSixRYokSSxQl1tVN7vBLLKkgSiwxCPwXO5Dgx3YRLdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ2OjI5LTA1OjAweJLFpgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRVNQLnN2ZwDs7RQAAAAASUVORK5CYII="},"69":{"admin":"Estonia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABAEAIAAAAzLZlgAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA50lEQVR42u3ZQQqCUBhF4X8braNRK8hJG3BV5cxRO3Ed1U6MkAZORFEM3gvB7xy4k0TxcQZFURS36vmyNu2GI7DCssKywnIQoz1318/j7RyEZYVlhWWtsKyw7A5+oAjLCssKywrLQVhhWWHZPf9BJCwrLCssKywHYTOE1X+1nPuCOfx0es300+Vrctz/1/vkfq9/vm+O5665fs1z43A/1peTtWk3AAAAAAAAAAAAAAAAAAAAAADYKmVDpje6lkyvsCgsCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCxSWBQWhUUKi1v1C8pommxuYBvBAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NjozNS0wNTowMHOYr0wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VTVC5zdmf1bEvUAAAAAElFTkSuQmCC"},"70":{"admin":"Ethiopia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGNklEQVR42u2bf2iVVRjH33/c1OaoLM15F7amFLZ+CUtsLW+1aVaylUwZtDIvYcYEdS0dWVtBtCAnNmeSlitMsIZKgRWkNaxgYq2iouHuH+uPfgyDDSIDs6CPfzyX03k793o33/e9zz9fLuc973POe8/3fp/vec57vbz+9vU3xhVTcOTF+PUnstA/3TgRQk9pZCXE2NFiPAknxxrHcZVYwVj+yD2F9z8Du0wrhwX/wqpCINT3vBQrl6mTrWcP5nc4ZrPygsx6xfD+1L00pq7EUi0cE/Oem8TyLyWoluuu0AUnXtXaFN8/aWi992Dz5MHHko2xiw4/cqqlueDehrLnukFauEpP7lJdV2L9B40gzWWz7j71TnHh6UW1B7rcsfiXmp69i6e0LpuxI++G408vWvFZCtWUWGHf4rojZIIKkKngzapfD72SLqUkyghEZhQtN0R854KKZKZM9M9Mzxgx1zQsJ1LhxJef+mRxHyqSmTLV7Wjq2LwdzEzJGJ2ZKLHCr1KCUjYdcqFF29mtBQ/N2T3affSeLe4KZ7bkDr28aBvzktEHkt1HbHa7ccKzvY0n+WyjCBEg1pezPv7+2pJrapfftfsvfyMvI0uNTFGvSCfHyBLLxUvV1q+rbFsICWxaRfo7eF3P7ZVfQyxazJTKWEQjsov3iiyxGuL7niy5I0rIht/dA20sbv/80enVz69JvPCDqS6kv50bXhta+oFEk4KQiWjmPtGGzDZ6q+CdGRnsn5QfDfz5WP/AtCQUcbfVMtnN29/Q1JUv21EpSAPSIpMsyZEItuRrG/223xOnt3zDzKO0FpEi1ltv76uuas5s1wYd0RuSGilP0ggCSarRk7ugSLq7TiIwcyVWQNF0P3z2R9NRQZrO4V0Tan8kFcr+pEKu0jOxffPjGz51GcscV5YzlFiBw28rjvWWfmXu11Aa1MgFJVGO/3ZkYO5y6cBAUh4jYtX9Y86fvHL+tj+ZGy3mPGkhphIrQMiuzdwDsmBoDCkMukg02wcP9E29cphlRpnoI5GrZhyJH3rvttwyIkdH/5iVqVsHn+lpqexUYgUI28s6d9UXmUtFC4RDLXAz0AUtQdUksvwgtSizD0rWurGj8OEFEEX6MCITAQpK92abJ0+hxAoQkpJsxlm2Q6/e2Ptv3FyBokjTDY1QINM5yWhcJY4shEIpInPVZZfKvTyFEitAaCOBbQllikRRiIDHgli2CruMQE+sNxGIRmSZ8lxmpcQKmWL5axiEwAPh1UhbLtFIrHgyIhDNvUCqxAo0ulNB6gcpjFSI6gwlT7w0czo6JI9lzMgkOLwadxGBdvyWS6FBxucplFiBM+/+yiRLD9J6ozHQQiYyabqhIEiLvGomRNplqUImVhu91LwHr9zw70bdLDegRiaNsNhyj8aiktpk5R0bTgSuShrJurncmdLOKDaqmfV3LTcEukAq9QBimTSSfdAhdnCmV6NFli5l2pXeyNwhSpRUY1ZaIA39kY7LKSGLyiE05ECBZMqTSZCr9OQu2s23JGwOz3wzQo90QnwIbWoJRzTcCy3MKpSJshImS68kXPdthNxdKrFC8NqMy9KSniAHS0vJgL2eTKzytRn5Ug090Srq7CiZ+0vPzDaCr838lF+/bOrfUcKu1VV593W6vwslUxtuyXRjJrGkc+IuWQmznQaayGyjtwreQHVhW9GqkOHgpZcUXXEOLVdXnJl3dtNal3dH5Zmg7RRP/kvH1kce5rjU1ZhhX820xJxKp+cKFXopjyEfLJjtznj05IzisuEFa279oyNpehp5LG2jlGmupWLZehLN/3yw/PWK0W17mWEWvgH/nrYfZHbjGO3e+SxeFh57jMd9b1UsVl5Xen/88J7LM/y7qSCWTbHc4zATZnUBvpNxRC+yjycIzUJK9UqXEPLN0syIhUrlAqWiTixLcnTxXrZ3Gfz/UWgrJaR4qUj8UJVY1i9l63ezEzVrURH3NxHcy55EZpTc+elGkVgZOTlUhOVf+mr5obYppQvjH+0p8SeNWQnDORGBaKHXJyVWdhFC7Hzi6tlLZm76Yu7qlXWJ2E0Xr7uTdAbSwlV6ntvf6XeoxFJUYikqsRQVlViKSixFJZaiohJLUYmlGDL8B46CU/TMZu3QAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0Njo0Ni0wNTowMEi1vMgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VUSC5zdmdNeQHvAAAAAElFTkSuQmCC"},"71":{"admin":"Finland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA9EAIAAACEkYd/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABnklEQVR42u3cMUoDQRgG0G3EU4gKYmWj0cI1vUQECxsP4Als7EyKIAqCVoE0UYIHsEhpF7DwCBYWlvYiG4QV2cLGckdm2DfF1wYmj3+G5dvNyujX49zz6uvK/HAn73eybKvV69adrfVu+2p0P3y6LK2aVgYWWGCBBRZYYIEFVoNh7eZFfwkssAJNrB8EdcPaODx7AMtR2AULLBMLLJd3sMACCyywwALLAgsssMACCywLLLDAAgusqGF58g5WMhMLLLAchanA+sg/T2fjOLPYno2/7iYL07eXzdB9rPPJ7ft0UP1izHuSSmbt/eNidBFzrnWO9gbXYUj95vLiwclNGf9upJJZdcOIO0Nc2P+eWynsRioZ/A+TzUxbIMGSYEmwbIQES4IlwZISLAmWBEtKsCRYsqGw/qebEH+7QR+h1tTH0scK0sfSINUgDdIg1XmvUue9qS9T+AYpWKm9VwgWWCYWWCYWWCYWWGCBBRZYYIEFFlhggZUCLJ8xAgsssByFYIEFVtNhefIOlokFFlhggQUWWGCB5TkWWGCBFfv6BuluuD1YhrY6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzowMC0wNTowMEDt7DYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZJTi5zdmdMmf+XAAAAAElFTkSuQmCC"},"72":{"admin":"Fiji","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHR0lEQVR42u2cf2iVVRjHrxCtIY1lEVFQMdxsFjErbNAPUxKDNJjZMKKZ4rZYYv5CQ+0HQtgmRpYNzJylORRNW+FoTilps1Bcy9I5x5Zm7cpKDLU/WlrB/dw/nnE6t/O+7z3vfTfPP18u7z3vOec953Of5znPOe+Ndb/8eFnFB+dXfz7yq+GXFv8159Idqq67e9fV33xXeHlyx4qK2JJRL1aeCq7Fm6eX1cyl/jO51VnvPN82MWfDyMlJPXbdfQX5fOZbShb1TX90VTy9feDpzi7bc3tr/smbSuvnFX7/2g1Lx847VnrrxglHuHKu5uDN335dOWJ/d/zJJ+obd57qKynZvfdk3KlOYwwiyiDqIPulLP7nb7nVr6y/tyknOGRM6pmu85V/LB0AFkhpwOKuIO3Sc56ic1bjWy2/93YtPLFy/dE3R3eNn6gbB/pZvmJfzel9DiwjsOTg8hsNBzKtxRJISbCYWn9g6WDy+rwOLA9g4VyCD7pXyLyC5dVipYZJujmT52KUHnt3y5dHIgHW1LxPCjoPRhosG79sE8i8ukITi5X6R2ICE62o/c9eUNyy+D3ActbIM1hhQpauGCucH4MDK21gmVsCf5P3P64wJVhhWlYHlkWwbMQuDTfurW5f5BWsLeW76w7NCH/B4cDyDBZT5U9PLO/J6/ybaSPTQzbobPP2/MZ4coLv6c2OX9RlyLR5LE3wPqCtRCsoV1K3Qm/JWqnPAui6J+Wu6IfMEQIrOfEZ1SRYynpwAFi+asZKBe+hTDdEbQqjiXuMxGD7q/dnT+s62jNm/l0NXEmzpqwZR/YfYCWuHLpw/cGiZiu9Mu4n4zOzpH5K24/pnQCZvAAR9YpaUl5Jnf5Q6wwJrAExTTiqxlL+avBaj792E3d9MSyv+IHRgJXeSZJwmKsJWDKdm7qeqIIVHJTM1u8RLBvT8Nydbxdt3/TSLSVvzD0cjtKiPTca00U2RtbCRL3W469dHYK6ms1hDQWsijGVy1fOoq0jJddmj/kBlTu5slfde56Zf1uvVJy42n+uU4agglZo0Z7diplHQh7K+IpsdIj4bNH8LoOS9mIsFSz6A0wSHUZDAkf5ZAwqAIq3frR3eL8ETq1BgmXDOVpfFZqsy8hI6fYK+Taz61bbq0KmWS6hJCKotF5qmQvFrTnZY1XlW8qDFCDSIq7QRoAfk/kbXS5Hd13msXSq5pzUBKZ5glSriemnZtmKzG/RW/MsnXxq23ksprljx7ambb+CF9CoiKgYSfukU9WGWXeFQTLvSWukTDOTqh6aw/aQN/eXeecuk3blToBsV55WiE7mnWmWdkgHh4qIvEt+lpGZWjMtZnhLJ10wqXuF/jahbfQnCmAl3Zwm8pM/FRngoyo6aqYwEmDJyWP6g0yeaiH8ncdS6wkHMttgsfjHcdMfqZuXNhzfMEmGExwol2BxRdbQkvXZQ0vy5RU+oy/s2rR6T0EGzmPZtgReTzeYnMey1397YBHlAFb5uLX9s/ukPvzPqouP9D+9bOGapzaiH685tHXnfh1YxJqU4V6U2n5uO978fi4jaR0spipMt+IPrCAnSIM/14N1s7PW9tsGC3RAASs1qnXGpxMuS8i4roLV82HpgqqZLFAoz70o9wKcBMti8B7czVk58y4yLsFfpvAKmRr4H56ze/wBK9u9TC3TjEUBHfrJZ6k4MsAakDIVFouSqsUKFawwYfJ3gpTWg7xMEdyS2d6EZpp5xmQSJ4GIjIpkD7WuUJw8Q4m0qDM52gIsi1s6dLq9o6rp9WEMH9sXXAEmsjgEzkQbwRXnwkP+dE1t7bpxtKuqjLG4K7194OmYBjn0KCfMtj475VzVVHtg4QpleM6YoIyA/JaZGrAqTKz+ZBmUnyW2UF6nRYsxFoM17XRdzYFCPqNkmVFiCxtK/fxuZOuyP3wrS9roiTwRwIupUmXrNsBi8a/bJZSqllHLpy4jt3QsWiyTAxU6T+x1j0m3dRBkr8rkXpOawzxSomrZVYtG1E4iAEgNh6o6+HT10AotWrRY6TpFNNiP0mb2KXBMuH6Jl6oyQaqzWBIgXT3WXaE7nR0FoHH9rObYMZQ7nlJ1MRZXku8cJFTullKnVFp0YA1xsAgSsCLgJdNAcjHBajH1qhAlYJcpDNINbKsTWVqMsdzURkexIuTMdK+vyTyWDixgkicgSDoApe2cuwMrcooVwa6oYAEceKlgkXVTkZKK3Qrn7UgHVkTtljzrJp2gTJByZE9aLAmWPLkFUjLbrmp63aIDK3LKBAOBzJjLs7ipLZZECicIrCbv/Diwrgi3KPEy2dKRu4q4VJ2Vcq5wSNkhf3k17A2rOV2MpYJlYqUcWE6TNoyUBHuXxFgoV/g2Cn8Q58AalJYPa8S2DLt+0j5F4r8b3FQN3q2nKG+sObAGJVjmBwUyDJb75yen6V2COIvl1LlCpw4sp0PmH9sHDVgunnMWy6lTB5ZTB5bTKxcsFwk5dRbLaeT0X5mhki0/uFJaAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzoxMy0wNTowML2v9jUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZKSS5zdmd4LVEpAAAAAElFTkSuQmCC"},"74":{"admin":"France","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3cMRLBQBSA4Zc9CgqcALXC4bThBA5gNFyFM+wBVNFqjYTszpfi1Sm++bOv2ETEdNK2pc/T9ny977rCn+fx8rjtc7dMmybnxXy9KncGWGCBBRZYYIEFFlhggQUWWGCBBRZYYIE1/GzSLB0CLLAUCyywwAILLLDAAgsssGyFlcAqnJRiKRZYYIEFFlhggQUWWGDZCsFSLLDAAgsssP4DazQowVIssMACy1ZYG6wP30qxwFIssMACCyywwOr/HAYWWIplKwQLLLDAAgssZyywwAILLLDAqh6We4VgKZatECywFAsssMACCyywwAILLLBshWCBpVhggQUWWGCBBRZYYIFlKwQLLMUCCyywwAILLLBG+T8ZsMBSLFshWIoFFlhg/fp8BhZYigUWWGB9C+t9ggUWWGD5FA44XxBz7mcwZM9VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODozMS0wNTowMJkeu+wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZSQS5zdmen2JoeAAAAAElFTkSuQmCC"},"77":{"admin":"Gabon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABOElEQVR42u3bsUpCYRjH4fcKmtRN53AVpLwLh2g9F6Fbg+DgXQhB4dosSIsI6qTQ3NYa4SrS4JpInQ891bM80+Hl489vPRExHGYZmVoTUFgUFoVlCAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoVFYZGnD+s62xz1u9//zN9+vzjmyejrm9X5w+PtPZnWeG0sF7UXMq2xe183y/3TuN2uVpVK8W/+pfecy9gPQaZVWBQWhUVhGYLCorAoLENQWBQWhUUKi8KisEhhUVgUFiksCovCIoXFwoa1u1sPShMeNM//Kv94t3i7mH3UR2Rao33Zu3lqkGmNq3HneVYl0xqtVrc7nZJpFRaFRWFRWIagsCgsCssQFBaFRWGRwqKwKCxSWBQWhUUKi8KisEhhUVgUFpnXTyquhRLNf5MSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0OToxMS0wNTowMDT5168AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dBQi5zdmfDTZtPAAAAAElFTkSuQmCC"},"79":{"admin":"United Kingdom","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG3ElEQVR42u1d34vVRRSfl5ICo+yhh8UH+0UIGW0E9WaQL4kk9BAk9lSICmFsRT1kgdCLmBghan/AIi0FrdSK4A+WjTZtt9BetNRda+mHES0iJKHhfgzP7Xxn7pk5Z+bO3b0vhy/f+/1+Z+acz5xz5syZc92Z5asH16/5c+MnY5+PXHvw6sS1vlh65s2pZ/74Z/Pwu/sPbL5rW//ke/3O3bfl7SVWdPHsiq+2TYBafXPd6YHbhp6jo7iy/cLYzIbvf3j00KrfJ4fu3rT8A06/3XfP5RUf8+tTvzy8+KnP8AX6zecfeuXq/ovOLZ1+60tbnsRyL/wMpAYJQpro/99DZ4enRn994v3RvYe/G1za99jaiT13LHvgG8oT3MGv5/tfWjuw49Ls+IXJRxx+xg9nH39h56Yjf702curI8TSQHXPjM+emIbYcILOg18WcBixOwVa8xYGFVvQgsJ1UFEzo4eiOEz9NXaJ8AJgwLjqF+DTzYcZxNoVfkFN0tz6QhYAF3SOBFGV3O2DZa6w0qMnBRPHANZMEG843RxtANnjwwNEP6wcZZzq/ozeFeo2VQw9Fg+n1Xfd+NCsZdSwG3A07Shrwsc/KXNagyTiwwAe5KZT4WFamMLdmyiFrd/zlk1/M3C5pGKyszVzGC+O6YYJbLddYPj9DprFKOO96MJ1ccv+dT+7WyxSIcnJV2VlNVsOqMFVjpQBLvppDK3IFoTdzskWbkT2u2fFvFVL6qtCnt0r6WLlXc2kyahqLx1ikaTJqLmv2ycLA4tGaMM0dx9Ks5hrCSQrNdLTv6/GpW2WyEHgkGnNpGyezAlkNGqvO1ZwdzyOUswBkQVVs6yRqgrFWAVLbONb8msAKd9LWJ9NsK8UwIiVA6hOkXGP5Jm0bB1zAw5y7JgojXih2IphtLZrMeIViqbF4/2NXhXozVymY0jRWGsjg7oUZx7cOcq4uU+JYelOo1+7dtp9bMHAXBhldUeYG2caD79wyPKDZK5QAC60AxGmhAYAJGShWYCqzleRaVZ/vOnwnfP8mxZDAaIkmo/CiINMzembdb4tmr+gj72Fg0VZoCgoNanBqFRo49OLYsz+uBLf/AxOXi0+OYSSE789RoDhM0TnJk+F3cQ2K7B8MngsYIDt/bMOiN1wDncv7mR7ZMrj1U43Ln9sUckih5y2UjOvnvVtPbH9VPyJwlepLuQQ1sqbU6UUyP6hmVehz3hcy7QHLYFUY1lgLFFhgR7dTmBvNF5BQm+a84y18oZ4RdZbzDkzBnKPXlPruS56JfZc/L/kCfz6tXb7v6ctz5zpM3vPwk5LrWB7Gfk3fTxc7O31RFv13NG1J+lCyn3L/rB5qyx83X5ml6SF/t8x4w63Y9iH3iFw+VtqKtjaGdsvUiu2n5PmwS+DVWJLXahB5edHWCco6J7DLh98c88MWfLm1ctr39RPbVjWEv+b71dU54zVPpjEi/J16tHUN3p6ESy63GeqUL1ISCiX9mHy6ylb7OtslaG3Px77r02ol12vdHmi4ASxNEFL+fCyVf18eDpUEDDWsTAtmygOSmpByyf6AzpMtHT2tbUun22lvEzo60a+3Cd3LbiiUj9VLm2kAlj6lKzbpDxRpaDzRL5YixxJJf76UQFCk2mE3PhZYaRmkaRSJfhgR8kh9CY9Ih+TtahL90hIA+a9Oll7sSziWP3kzNRkNayAFMIHptNIcX91A5BxStG5dvnwsngAdMca5xGskYdPTAHykvjE2HYEPJxnrE9PpdcHDFBowYR5TMIX1BxhNhU0ZjcRoqyP2PmChFX4yRz+RfHD3jb3paHzuGjgFS+qkzVoNQ33Hv+TnCiWBUMm5QsoTnJnRg4yfa2qYYMxc4lhs/tJRRpUwzcAUZBwv0MgZ1+5UtE2pSInG8hUFoS4BPa1Ux8SrQGOV1ExyM1f+iH27ajMp5y41PEzTZNYgWxBg6mxREGmdQTNzyXjbBmQK3vrtWDVgaljpsFkVU5+pHLBy1MfK4ZOJQGbmkxUEE8p++GrOxKxoOv8HArYaS+6/2vpkvKCBnZXIDCafvW8BU3Z7/3/hlXHecxS3LSmjMMjaaLIyqzmJmYsFU2pxi5QapPr6WDmKb+c2lzzUEqHJaLEvW5+J5jbl0ExUMLEhkrL1sUpUrq8tTua6f1mborHK18cq+S86ZVaXYZ/MaRrg+Za1gqk7apDm+wOYHJoszA0n35uL1Uxpq7n8ZcHa+1i+kdZQ573OgDbnmNNv9NId9RyhgRwCkGgseSZ7zRord3KAz/F3SKhFxlJYMyEKxVcEp58+t+fiKLJ/ypu5tP/SkYQb5OdwcgMrdoyxz0NqkCBPKIrNQEGqz7/AbUQccgW+ogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDY6MDQtMDU6MDBbYKMbAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HQlIuc3ZnJTl+YwAAAABJRU5ErkJggg=="},"85":{"admin":"Guinea Bissau","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC40lEQVR42u2cvWsUQRjGNyQSjRo9SSzMYcRA/AcEU4QUUYQDg4KVoGBjELWwEBRBYisodiJBBCGFHyBY2AgBFayEwCFBsbCwFGs/CsXisRjZXNy9nXdvdufXPMXdzsxy8+N5P2a4ZOVFY3Rywk7bU41Dk3etV/n65Na1sTe/RtqfdzbQEDSx3vJyFLAAC7AAq3ehLfuK+hawInKsLED4WgWwCIUm+AIWYAFW3GCVn0t1F2QBK1LHsgPUnRmwKgBWyF5Fu8GXfrr59nlzhhwLsOhjARZq5Fh+4csyG2DRbsCxAAuwUCOw/B68ABZg0XlHwwbr8v5Nq6O/ny4Pn9szbeF85YNl3QGqCVgWQUpzLq9uezwxsO9H/5HB07fHNq/smrcLiDhWRO0GwdR3NGkmMyeODZ7avkCOBViFkm6NEkwCS74lDwMsQmEOjFx1g6DAksrDOo0CLELh35RcnpTWww83vNs65CIlPXBv4M7QbKdR8682Xt3xTWl+dtSoCmsF1qNnw8n4XCeA8qqb4IdfFaKGF/3ckCf3aiz1XeqfzYuU0MzrUmn98P3G9fHXagSgIWjiK7tavLLlZXMhnVelVQgKx/Q83b3JyZ8Xz0593H1mqX1wLxqCem43uJXg+oHP77qt4xfOT79PkgcjrS//0/uLrTlLzbJuOW/SU/XbDc/iWFI5nK+WadXAikB9gSVQ3ExLkCnkqR50wVINWAQsd+y/YEXjCnUFy82NBEo6JXefd5/xGxDXAgsNDKy8PSTVhvIkoaNPOs2jtoKel88BFo61hgojN2fKUuXJz9QPK36IBFg1BKs7IPzedACsCoMV2v/MAFYUjtVbBSzAKgEs8AoMrCK3r9b/0w4cC8eKPBSCZi3AsjnSCflHjwZcHAvtGVidfKX4SZ/lITQbHKVjWYLF1hIKKxAKcUHAMtx+wAIsNrXq12YACw3udkOsfSzA4qwQBSwcq/JghZmB2bcbwCWf/gGMEcrk0nVvtAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NTI6MTItMDU6MDA8f+kFAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HTkIuc3ZnMhspmgAAAABJRU5ErkJggg=="},"87":{"admin":"Greece","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADDklEQVR42u2dP0gcQRSHp0sjCMbC1sJOSJ10llYBKxGxESxFMGWaWAhKCGohEiS1IiGIkAMLAwZMCAEFMUElJCioKGoEJWBzFq+5sNzydndm72bmaz6WcXz3buZ3b3fe/FnTsjnU/WHeLnuvJn9/3r6eu2u7n6luVn9UL9Ip9V14AhtF405YGkmJ+BAWwrIcsaRO39Lr91urdAbCsnwrdC2sjrORnrW3dHZ0EYtbIcJCWNC2sPQ3FEaFMNiIJT+D6Terv/a7595Vnh3+TDL9r+UzJH9MqHmsrkejfZXF3dmjsZvnGh+gXQabxxJhnfZfL/zbp5uDElZj81hErKiF5S6PRcQKUFhPP75s3xgnYiGsYB/eiVgNE5ZEF7sc/rbw+Pug3gmpn++znpgXt+tnyXIR66fZvc7zNYlbSe6s/Jn4O6Av94VZ/c/aDhr7RuJKLeVXnizPSr2w0u3k80e+nohMclq1lHhWpDzUElvlJtRQLHKUeEYevAGZ97CFJb8kuhlhEbEQFsKCCIvORljlCSu2laVZv2+R9jGSQxJKBry2JElNHVluoU86SH29fT2XT7Yqx1OwfAa+HovMe4BzhfrUqIu5QoTFXOEFk9AIy5tbIZPQCGuGpckIizXvMO417/LwLkNfWTzztfXgy+Wr5HURhmqnyGdJeURnN9Qu6qiXAEwuCNH8b0h29OXpPkRxdgOM7hgjv3ZCN9sUUDNPSXF2A0RYHGMUubDkmYlDQaIWVr0xQvooI30EUeThPZ8/mlFSkZGU3fKs177489+o0EXmQ7YH6R/epX6+TIwL/5M203M2Gjt6m375U8+OIUcMnWTeaQKIsCDCggiLhoAIC3oiLPaTQIe7dFzvOCtzR5sta0V8sOV/mZNRdvvIMPkAvZkrhBBhQYQFERZk2YzNcyxdnGmpsZ/1jM0iZ3Km+0P7yLXx92xg2Mwk8w6Z0oEICyIsGgIiLIiwYNTCKvONxWLB1puP8aecN0zns0PmHTKlAxEWRFg0BAxaWL6cCdP8frr2UGOfiAW5FRJd/OEDrkySTY3bfGcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjUyOjQ1LTA1OjAwsTjZ7wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1JDLnN2Z3tvwsoAAAAASUVORK5CYII="},"89":{"admin":"Greenland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD7UlEQVR42u2dT0gUYRjGv0NFQXWyOnWsbhIUBHWJOpReMoJi6RAR1GGRLoEReaoMitK6LIGEG1Se0gKDpIiIDlGxolIkYYkkYikaqVjJBvvM4VsGlxVmvn/vc3lYVodhZ37zfu+/7x1V/FMcK05Qqcmq4iWgEiwqwaISLF4IKsGiEiwqwaJSCRaVYFEJVvD678nU3Ew3bznBWgYuv4++7vlwcrKlo7mr8P1K08Yb674OZB6cy3xpr1t/Oju0sPfWiXp8ho70nrp88d7Yu+bHt9tx1HzTwMjQLOETChZu/OzNt4v9nQDo84Fdn47N9V+vWdzd01dYu2pHbd/W1R3bjxTurvxWuwKfdcX3ZX8tHTW4Z/OzfWuA3Xi2pebOfaBGRAIH69e13sY3ueFDh983virDKFGNgNNQg20DysQlELBgM3BrI5g0G2NIS2cEZFg6/54fz/3MEB0vwZqafzjzdMPHLdvy9cd1K2JZS5DBY4NXR4A8AAv+E/wba/aparwAPRx/YuQoWEAKzrhzGFVUPAA/XuYOdm4iTA6BBaTgu/iFVBwvWi+HwMKznl6UZ1Lh4CN6JVjKbvoAN8N3pHRFXm3h6nDb6CTBsrD8IRUZElK6az+6P3v2Uh3BMnrKiZ2tDfkzASIV87okL4tGwcICgUA9bLBgt1AnkFl/VLRVtFseg4WnFjlrR9OeqalMf0uZjAHDSCssV7H0S4sTDYGFQo00W6XrdL679UUDwUpYo+SCTLBKvxrVBYKVmKLJREQkWEWESLAS7qmS6V3pYCEjLyf1oOi2m2yzkePCpw7W9IWuweeP9EucsMZuoWuKRkVUReX00SvEa1RqsqrSflKxDYttJAhi4GkttYPIUa1+NdD+U6XtWyDRQLCkRceK0ZDJHUfh9Z/ZAEtwQWOp6FhOLKxY4Tfa2SGm9qDMnAaRgmSwEMQQLLa8sb3RB4uFBVHmBARsC5NWb1AmyxrYkiptIhestbTODsWWN9ZJvQcLKsFuSbZV1sBCkjDsaS1p+FV+WT5lq40EpZ7wZk0hwx7FgIJbsZXdLqWQlkU8JNHyJ7PzzAmwNPOOrLTvHhUmDxIpJ8DyfcqUv5O9RICl44Xij/s5eix8tFIegKXjhcqamxkv1A+CnZYTKljxWVMI3e3aMNgnWFN/Iz6TCQunwUILLy4Hoi3kss1ABpiANRCnHQoErHh6ApBhGUIsmdSrSvRXp8Aylb3zQnBGKnSw4u+P0Eb7Y44N5rogusSsBFg4eEVQfIM58kATDriOUfyFKFQRYFW2anGvIq6Vj6ISLKqfYIneGk+lxaISLCrBolIJFpVgUQkWlUqwqASLGrj+BzI0XRBdYcwWAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1Njo1Ny0wNTowMOPmaCIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dSTC5zdmf5P1UbAAAAAElFTkSuQmCC"},"94":{"admin":"Heard Island and McDonald Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFyklEQVR42u2cX2gcVRTGN4ogJaVIHzRtqaJBS7RbFGpqQEGkIiy1KpY+9KEvhaQgSIRCSvpQ/0AjtQ9qER9MpJpQiMaqGyoKpg8VYUFMzUO6FUu7MRBfKhafgoaR7G8Xj96dyczOnZm7yXn5WGZm596595tzzv3OmZvzvJ5ST8nzRl8ffW2p9+8nlj4ycXjHB7MzX+Y7H37r499zHblNJ9+Ij7u6dj0+dif397zj54+/4Hlt97fd1wiXz3Kl3T5wN57OK04uTN70vH1X9t1Tb3fLS1u2c2Tulc9yU3fcveHel4fbbbW+yrE+fAxl4VDhufoQz+/5dYek1/yG+fyffW9+OHSx9My2I9tGRtalTyz+Faddes5TQBfvzOGv+04EjwPPrsSKgGEG17Rh8UkGRbhP0sTCMkUlk3ze0tnShYUDHZ929L33k5ImFHYd2d555lT4QQ8mGXdzwWJFtUxJvDxrGuO82XFIJolVKfc/+ern8YlFi+/c9e5T04/E77/deC4JbF9qX3r72RYgVppvvC2LFd/iXitfK988GNXiuoCMxu5Luy99sq5liBVu8uT0hyUZIXAcYnEHW/1pLTcHjXoXexe/OTf2/NnbygMgRxwiWdKrKj8LURwvjl/tCUesgcJAgSsZxKtd4+u/vSXpWNBNR8MC4sfzM4dudMqn44hDywvJ+qgoH8/zpg9OP40eVkOfFZaJwcQiAqtfuXy3Bm1VjwS3Qm+be1LitmynDaL3D/UPXXiIl8R8bTjrxCtRn6pMsYEjE1g7m2UPK/nSYnnMBWIRDGB3JbE4wllHiIUrQX/nd1bo5wqz7dUyVvKbNz+4sPV6e+nEAy44GiIq7BPIEaeUd7/pVBTuOJ/LrR/deqpt52DRnUgLywQ6J94qaVqRWC2RK1TqKLESjLFcwOAJ1hirxYjl/KqwqmPpqjAJlGK1ozpWI3XKR3MyBMzwyvtKbUll6//6VqvrWHYTQXsreyvnfmZFSUUaqr017T6qjiLTO6ZMV5vmmjRa1cGlI6taJvT6L7onfinPRFXe0evDtSvcq2i3VRLMSa8opyamJub+M4Ozj85evjFszXpFzRXGIZPMzTG1zeUK7fYn/cRzttlJbBIVZqZ2v797f3dxpwWJNRaZau4mLJniVDfQB/N9krlLspCukYxJop9YC1wPv4ly0tHKaQXq0Ac5SngDhFYLTj9py5R+PVY6/Y86ndwTO0Hf+M3x9JMwxFgygiTestaKLO5LczKiWKx/Y6zmKkhdIBnTZvYhq7QxL6FU8C2vDVcadHKI9t1HczFWc0F3sCWrrSgDSfbDi++PT37XnIPANsjFiumAHC3Wi4NZxSJJWyy7lqwmkMZQ3rEKrMVAKLVqP9CALpWjhccOXKnkbz258asaVo9QUpdEpSW2hyUu1CFt0gCrERhX2pUJJMm4P1QDK/nLE78dnfvj9F+jM99vzN++53Qc5R0CERrLIHrVEou30ERWK8k9tszJ89uvJ0ynvDI5dYenls+e9DgkgYwb1j0RVT2J0mRF9xFbyKoTJM5TYilaCDOkW88g06DTsFqTNlgpMANXrtOguCaIhdFey0liJVYiOHhs8NjFEVCnR4llYccBjsu1jN+VuuuLEst3MUx2XRZpmGsZ+ZWc+S+dPCVWA8skP7GnttOsECL1IetU+ZfTu6woZhtjyY3XgsuOZRmaBvVKrFBofiruh9QM6YQpsVaQ74LrC/wsFqtFDd6VWL45rDBk8kPuoCE81RmO7u+VZsBOORuEoIBElqz40YizXEm9JXdwaGeV1AVk6uEYDaw49MqslsFNgVSuEKXj4ze1l+pcJLGk1ZcV9EqsBqIowyQ36JFihEoMsjLdtPGWvwpsdWJRRsegmF+JYOQ5qxtiyx39UPjM2nnOOrG4ccGwB394hA1TBcscE4IE0LmN19zfqVxuL6aUkvKyLJ52yAlqPZaiEktRiaWoqMRSVGIpthL+Aw9FiM784caRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyMzoxMS0wNTowMO0Me5MAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0hNRC5zdmfNh959AAAAAElFTkSuQmCC"},"95":{"admin":"Honduras","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACP0lEQVR42u2cTStEURyH78bOTuzEyifwGaxkIaVkVkpNFhY2pCzsKBKlLC2m1ESzkQUxmZDSIOQlImbh3QiTKGPx29wSTeYM5555Nk+6M3P63XOeuf9zzpx4ntdVv7kMoWnSBRCxIGJBxKIjIGJBxIKIBSFiQcSCiJUfK6v6u3czdCjkiQURCyIWhIgFEQsGi02JyczpWstlpO3sxv93IVjo9v+SudzL797jRi952Vh2N5uG0CwdFCvZm4pnpqLh7bF0hc05lVBpEctSnnc8JN46NVTtL9GNi+q60MTocUpXDtLXw68RG3IqiVIpodLOru7XPH5cNTwNvC8glnUcrF5qvNryTyE1bNLOni+AUilh6VxPy/aekiOW1UWwdn0kfLgo2lkQI4lk3f2TP6d7BdFBsUQ9G1RiTD1pTD35lktOXp4b1Zo/M2IVEVWehlrjd9chkT5BLAMTbc2Hyif65ndmRF2xZ0GAWIGkiqmm2KKp8opYgdx6MDVrkUZatakUmhLLPxdErAAopeHvnI7FU0e6ks8y/utn829NqZRQad3Ty6kNUg2VZkIqW/rdzZ41l5Lo1zQlVFolZx/L6u0GHZLWsNm8j6WESst2g9XU/pCGzU87xZL0480rx7dl7i0IPPf2nFiRIRZ0V6zvjpjlfgAt94Npv2v/fw8V/tyOXs3lXkzdrz15fn4/R5MhZ94hYkHEogsgYkHEgohFR0DEKlYG7R9FMWwQsSBiGaOOlzBUiAUhYkHEgogFIV0AC8BPeqaO3wRP5YgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU5OjMxLTA1OjAwt1IPEgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSE5ELnN2Z0sTrNMAAAAASUVORK5CYII="},"96":{"admin":"Croatia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFb0lEQVR42u2cf2hVZRjHD0X9YWS1f6YDDZY/tn9qOopyUsGwVXfgsEy2UbeNqDSa6X40JKJSq621aRPpxoqkNKu70ERWsELrZrYixFkxlqUkjIXNUtE2FjfY5/7xyLt7Onf33nXufZ9/vlze85znPfd9P/d5nvPsnDm/HMk9kHtQ1V1P7phdM+sT9xFVqU6mXOjxP+cdnn9mahCkCqbUzphaNKd/fbIELH/GLf94y8iIZf4aNBEoTNMUsbyHYv8nU3csshWOVK2PT1Ohn8tkf4KV7lpwihEr+cVKx+W6+0wHfHIdzDXh12yOT231kokN5uz+/BE6NtcBcpt7Ku6NVJ7oGyzZvOxmiUt/Z/G3twbaj64r23TH249Vzny09OS+wk2FQblWnIUHrSNTlgr9X13Ftv94+ff3fQUoEh2AmHvP0p3bRm7aU3ihaUXnxRtry0vAZf2rTVduqb/6habAzm9ynnu67s05jHAU1ApGbutqex6VeEk0md3/a6XthgSiLJtadKr15/BMdGvfI9vr8w4fXPNU/fgTjQXnq1eir3++MKf4GcACmuVvbM9Z9/Gc8Zc2hmr4jOa/WzfaVerMKDjb9qRUAOVcEFzZvbV37yjzciUKVgbfQnNVRAg2GCBe27i/OVAGKA+V1T74Sl6kPq90wQaQkmCBWm/egeL5N3DWjl8/+HvxtYzcMlZdsqGwpXp99PYGLEtyr797TS54yQiHfePlXf33/8iV2BC3nOzuGMlYBVhsM2Cx/aQ/qSACOpwFFigjHB38Y+DYdXsADq2YFXirfW7VF42r9j/LvEDJWaBGGlWwMr4wl3WSVDaeeEPckucSt0BBxipGAIsR8EKJYaTC8PKW6l19LR/2XNb30cNbwuVfh7gSqi4FK+OVgnreoZfbQx3AxGajxBhZaRGxiF5gJKsrRoqKlm5rawi+H/ysJoCCFCoTK0o993/VWOlLvvEKIceehgLRgsiByoTIPd2iT+861fEDqFGBcRbFPp9RjjKOPedSqoNs9527Z+y7AqRsiFKW/hGauCXvDelOMc7Gg6DZDo3XioxZTnS2qJy+HKwcejyCZ4myPfeDMbCGgg9EVy38vXn1yOrzNijxg289dFVgb2BzTBkRqzGJjWE5iRqWzHj6mobahlp71tmJDkcPRb+zQccHh88Nn+ZrE2PQ3waWjC4Zu1DVu7Z3LTbo2IsDnQMdICLtwYWjWOIfD3iT9swoLW1Q68ACC7MCu9gR+SnSY9oDirRnxAQFsMykCZoKVtaqjEBmtQQWyYNl3n8pWHZELFKbKMlBwR0sGYfigSLBkv6JkQpWloPF9ptKcW0W4+fC7zm7jpIoUUZMSzxIn9JewcpysEiI5tFJUuRE6W3aMyIfnpGRLF4KVrCsAyteUZ8wWBMeFCxLwTq74Miy/hXJR6xJinQFy2awzhS05rfm01uiKkL/qggFQ0HqIanSRqppiQfZCOUzMypY1rUb0FS1G0zP2m6wqN2Qvj6WNkgVrEviSvKddzxoxLI0FVLxTKdSeylYtt4VxvkbYqJ3hSZAeldohcZLeYn2sby0G9xRVrCyMCGauKQKLBmZ3Hv9WQ5W1XhdsPsf3iqJfZYjUhMdN4+m1D8PAU/tenjNwexXeelvyX6VPIuKSloyy39cj/t3SXSdvaywu413dfXgmK9c2qC8CsFjxF6ebkig3TAR5/DMLHausKVgobz+wKPD3tsN7o/N4M1qpBQslPdzSFuy0PYSsWR5jge86aoqWJcoL3KByLFF75wIN1OSy6fjGeEolpzFf23QNVSwPEUyoDFVk52CpapgqSpYqqoKlqqCpapgqaoqWKoKlqqCpaqqYKkqWKoZrv8CfoNALcv4ejsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU5OjQ1LTA1OjAwSdgiGAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSFJWLnN2ZyUnqtEAAAAASUVORK5CYII="},"99":{"admin":"Indonesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAtklEQVR42u3WsQmAMBRF0URcIKULZBcrV7F3H8cJOIq1ioIjyK/knBEet3i5tVJqTRCqMwHCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBZ806fpXK/dEASHNYzLNh+GIFa+X4bAx0JYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8V8P7lwPhQb9oxAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAwOjE4LTA1OjAwUGem+gAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSUROLnN2ZwZPnKAAAAAASUVORK5CYII="},"101":{"admin":"India","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACvUlEQVR42u2aMUgcQRRAN5WlYKNI0E6xvjSKYMBesLZKQNDy2oCCKUJAUl0ToyjBQjAgwkWOCLFIIylS5CAYOIJVRI4UKVIcKXIRXvNlPDBkwWKewiv+zs7i+Pj7588W3e7GdmVaynJZuARSsaRiScVyIaRiScWSiiWlYknFkoolpWJJxZKKJaViScWSiiWlYknFkoolpWJJxZKZidU6bDySslwW3ebVb1vKcqlYPXk2eTb5o7M/tz/39V6tWWt+egqJcNVVUqxb8ah11Pq2OD86P3rweKw+Vn/1BE6tTq3uvoUxzkjucvUU6xovji+Ofz1YmVmZ+fAbXRYaC436OJnpvP+8/+dOSq4ykruqg9XB9w+ZzVVVrDZCIMfm5ebl53HkgFGmGIljuCvq5apmLRZCDHWGOrU/vM6iLlRRp8Onw9+rKbkaxzMDszGzYmX3Z5N1KluVrZ2T9bX1tY9LMRuhDqKgSCzeiXCVkTGHMRszE1es7HIVL6+Ye6JSKMKrbXlieeLdS0gEyVK9mC2+WBUrI1J0x3I75iqEiDL1InqleYurPEWxMiKNA3aCadeKeJSDyglGsRgZ94/MRpynKFZGzQVqoCgEWScVa3ZkdmRvurj6ebYBiaRixbxFzuMpeTYgMs1Y/MvRIu4BU7HgbTJW7MWbsayxbqixqJzSst0aS7H+eVeYNhqQJpUJ4dgzMjLO4K7QPta1PlaMx3orPYRO+1ixTWofy857G1GonBAovhb/p/POzHbesz7VisfP8awwtiHiiWEs0omgkWeFitVTL/JN/LohzUy9vm6Ie0zXU7H8Hkux7u4LUkpy6BekiiXvTqw3r7/s7jWkLJfFwP3nL/oGpCyXiiUVSyqWVCwXQiqWVCypWC6EVCypWFKxpFQsqVhSsaRULKlYUrGkVCypWFKxpFQsqVgyK/4Fclp79PqRQrsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAxOjEyLTA1OjAwG9WSigAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSU5ELnN2Z+1kp2cAAAAASUVORK5CYII="},"102":{"admin":"Indian Ocean Territories","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFyklEQVR42u2cX2gcVRTGN4ogJaVIHzRtqaJBS7RbFGpqQEGkIiy1KpY+9KEvhaQgSIRCSvpQ/0AjtQ9qER9MpJpQiMaqGyoKpg8VYUFMzUO6FUu7MRBfKhafgoaR7G8Xj96dyczOnZm7yXn5WGZm596595tzzv3OmZvzvJ5ST8nzRl8ffW2p9+8nlj4ycXjHB7MzX+Y7H37r499zHblNJ9+Ij7u6dj0+dif397zj54+/4Hlt97fd1wiXz3Kl3T5wN57OK04uTN70vH1X9t1Tb3fLS1u2c2Tulc9yU3fcveHel4fbbbW+yrE+fAxl4VDhufoQz+/5dYek1/yG+fyffW9+OHSx9My2I9tGRtalTyz+Faddes5TQBfvzOGv+04EjwPPrsSKgGEG17Rh8UkGRbhP0sTCMkUlk3ze0tnShYUDHZ929L33k5ImFHYd2d555lT4QQ8mGXdzwWJFtUxJvDxrGuO82XFIJolVKfc/+ern8YlFi+/c9e5T04/E77/deC4JbF9qX3r72RYgVppvvC2LFd/iXitfK988GNXiuoCMxu5Luy99sq5liBVu8uT0hyUZIXAcYnEHW/1pLTcHjXoXexe/OTf2/NnbygMgRxwiWdKrKj8LURwvjl/tCUesgcJAgSsZxKtd4+u/vSXpWNBNR8MC4sfzM4dudMqn44hDywvJ+qgoH8/zpg9OP40eVkOfFZaJwcQiAqtfuXy3Bm1VjwS3Qm+be1LitmynDaL3D/UPXXiIl8R8bTjrxCtRn6pMsYEjE1g7m2UPK/nSYnnMBWIRDGB3JbE4wllHiIUrQX/nd1bo5wqz7dUyVvKbNz+4sPV6e+nEAy44GiIq7BPIEaeUd7/pVBTuOJ/LrR/deqpt52DRnUgLywQ6J94qaVqRWC2RK1TqKLESjLFcwOAJ1hirxYjl/KqwqmPpqjAJlGK1ozpWI3XKR3MyBMzwyvtKbUll6//6VqvrWHYTQXsreyvnfmZFSUUaqr017T6qjiLTO6ZMV5vmmjRa1cGlI6taJvT6L7onfinPRFXe0evDtSvcq2i3VRLMSa8opyamJub+M4Ozj85evjFszXpFzRXGIZPMzTG1zeUK7fYn/cRzttlJbBIVZqZ2v797f3dxpwWJNRaZau4mLJniVDfQB/N9krlLspCukYxJop9YC1wPv4ly0tHKaQXq0Ac5SngDhFYLTj9py5R+PVY6/Y86ndwTO0Hf+M3x9JMwxFgygiTestaKLO5LczKiWKx/Y6zmKkhdIBnTZvYhq7QxL6FU8C2vDVcadHKI9t1HczFWc0F3sCWrrSgDSfbDi++PT37XnIPANsjFiumAHC3Wi4NZxSJJWyy7lqwmkMZQ3rEKrMVAKLVqP9CALpWjhccOXKnkbz258asaVo9QUpdEpSW2hyUu1CFt0gCrERhX2pUJJMm4P1QDK/nLE78dnfvj9F+jM99vzN++53Qc5R0CERrLIHrVEou30ERWK8k9tszJ89uvJ0ynvDI5dYenls+e9DgkgYwb1j0RVT2J0mRF9xFbyKoTJM5TYilaCDOkW88g06DTsFqTNlgpMANXrtOguCaIhdFey0liJVYiOHhs8NjFEVCnR4llYccBjsu1jN+VuuuLEst3MUx2XRZpmGsZ+ZWc+S+dPCVWA8skP7GnttOsECL1IetU+ZfTu6woZhtjyY3XgsuOZRmaBvVKrFBofiruh9QM6YQpsVaQ74LrC/wsFqtFDd6VWL45rDBk8kPuoCE81RmO7u+VZsBOORuEoIBElqz40YizXEm9JXdwaGeV1AVk6uEYDaw49MqslsFNgVSuEKXj4ze1l+pcJLGk1ZcV9EqsBqIowyQ36JFihEoMsjLdtPGWvwpsdWJRRsegmF+JYOQ5qxtiyx39UPjM2nnOOrG4ccGwB394hA1TBcscE4IE0LmN19zfqVxuL6aUkvKyLJ52yAlqPZaiEktRiaWoqMRSVGIpthL+Aw9FiM784caRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyMzoxMS0wNTowMO0Me5MAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lPQS5zdmfu2PuyAAAAAElFTkSuQmCC"},"104":{"admin":"Ireland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA+klEQVR42u3asQ3CQAxAUV+DIkFx29BkgIDEQMkooc4crMAA2cVsQAeSlZfCEzx9ne8SEet6m6rPbdwvr2uW/96PbcxchtYz5zmi7gywwAILLLD+NVt/nu5nsMBSLLDAAgsssMAC6zus2qTAUiywbIVgKRZYYIEFFlhggQUWWGCBdZSt0D2WYikWWGCBBRZYYIEFFlhgeSsES7HAAss9FliKBRZYYIEFFli2QrAUCyywwAILLLDAAgsssH42l6F1WyFYYIFVgRRYYIHljOXwDhZYYIEFlv+xwAJLsWyFYIGlWGCBBRZYYIEFFlhggWUrBAssxQILrMPC+gDB6+rl3wSe9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDM6NTMtMDU6MDA9HUf5AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JUkwuc3ZnqQAuRgAAAABJRU5ErkJggg=="},"105":{"admin":"Iran","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEaUlEQVR42u1aTUgVURi9q4SIFiVE7lJ3b9nb1CKEDASXFm4Cg3KjRcsMBCFCKDJEKAohy0Ug9MrAjbtW5SIoKgJdJAVZqYhBaURii+PiyOFO896MP+TZHC/f/c79vvnumW/umzHU1Q0NNTQYjflicAmMFpbRwjJaWC6E0cIyWlhGC8totLCMFpbRwjIaLSyjhWW0sCrDQ+He6LH2jU4UUTYnljEHYfGGbQSqLLYbKzs3y5ppbqRkf7Un51DZCmlyY/9wfLr05fSj5vNPD589qojZI7dHOlueMSazmi49+XZmRFnAGAu401jJlcyXlZxnbK8rY4Vrsy+77l4ZOPh69/0hjPtHX10YOsdjtgC7l5+/uXnizsLbXQ+vsz07S8dgcYYxlsZKw4rFivloxXpuvVjqvwGfWERdB6zk+m8tC9VQVpp6BvzhgnJZeTxyYGr/WD/LIsZiH7AefH2/+Pgks1h84ILFsZSlRSk3FvyZBX9mKSazsDJvHrM4urLSx8LVJbN4F5ilt0EsQ1iUxZVkFsflqwu8SZwKwsPCwVSIzOKexCyM+Q6AhbeTU1SWxtJt4AtD/ixZZqmd8+eScYacA3OVpRujNVFZ6K2bzOJaYRzbNe0ufBMi/xiLK5mGhXFYHVu9vNpmBP75NVc/V+86ZEcLy2hhbQX+LI1XjVcpujIWVqYHImQ0M9sy3DIMZGHB0w9QCyuKvzsn+yb7Fi72rvSuQDqwQDSfm5u6m7qBsGAWnot7BqoHqmFxJS2sdZJCN/qxtzRVmoJQuBt93FcoFAqfrhbni/NshydYkJ3lZWGt4WxDR2tHKxAdaP5U10zXDD/sICnIix98zOJ1XNXgXjU9XbtUu4SuA0mhD7GndizY4QkW5AVP960dLazlmYniRBHCwhgPRBYWd6yYsMBimWI1C2tHv0rgHgOJ8KMQyMJiOzz5dIXV/ErCHasIuUAWOCGxgJI7FixgsbDcsYLfV/HbKZy0PvTU1NTUcNfRjoVZeIIFxGo+vPtXYRv/EuS3VtzJMOZHHlvA4iO8q2phrZMXRIMHGR5qkA7GaoEnvyx1JS2sf3zMYXlNvQshBKCeovxJx8Iq41D/vX2wcbARB3PuWLBg1od0CyuH7uUuZWEZt6uwku/I2Oz/zdqcTDYz1satGfCPIjgx4AMFxvjxjDF8gLDzLCN/a8uLhTMN56ZZKfJJKF+W5skZcp5pYul18fqxWLxr2Vmx+sdYaXYtMBlj3k4WBNuzs3RWWTyrUkv2z8JKn1vMp1xWuZWsjKW7pitk3+s1YWmYGKrGYyxWcbks7WoxeVXG4r6SF0trqHdzMou3RHuAdkGVl8ZSll6dxuJdq4yF2YCPEkD+UY2v9Dzm/0lilnoyJrMwy/6YjbH431c4ruafHEuvkS3JGTILs7FYWpP0LK0tZ5Jm12Ks2FXrrqWPpfkHfvVnNOaFLoHRwjJaWEYLy2i0sIwWltHCMhotLKOFZbSwjEYLy2hhGS0so9HCMm5v/Avargl1nz+mlQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6MDctMDU6MDBjbnb3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JUk4uc3Zn08B9JgAAAABJRU5ErkJggg=="},"106":{"admin":"Iraq","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADjUlEQVR42u2aTUhUURiGB2pXC1tEi9oUVJvIRa1yE2gtK11EEGlRUG2KQFAoqIVBZQsJijYhSSVhiopkfxBJZVT+hGGQRdiPUFFGP9BuWjybA5eZZvTOOKMPL7xczhzvOXO/x+8758xNDA4vWrqqVNfj9YSPQBcsXbB0wfJB6IKlC5YuWLouWLpg6YKl64KlC5YuWLouWLpg6YKl64KlC5YuWLouWLpg6XMKrO9NbZXdT3U9Xk8kXyV/JCd0PV6fc2B9Pf+37FdF/kfM/7iCldcA1999XtvW8L7999i3nvyMy1iXTr0+3XcmfR/BKko/MTnU2tm/ZEfr+kO1/VVfDr9pzefoI12TKz/uBe6hhd9qxo9y3dk8fmtw177Rh3+aKwSryByMQIqMlc/CNNb1c+3n6mu9b5NPyoG7sWXkZu9u2jcM9DxruIPPpnI5y8GixBC2LfPulTRdySR4YUaJC6xoluJ6+9b71RdWMEN65j+bClbWTn5a/aL9XP0HQpv5XxHyeLPIuRujVXc7mA8Y7ax8sPjiBGDdOfjp2MtGPgU+wSo4JzCUP8KZajkfzRCAteBky6P9a+PKH2F+WlZ2fduRd4AVZizAYlzmIFgFWv4IW6ryRHGMrr24JvzT2a9xN1ZU5Euuo2BtrrtdfrYkBIs1mWAVkB9Y87j08qaw3ET7gBQBZkdGODkUCAvodEohmZI7A00UrGgppH/H8PjygRrBKggnGGSg9P/xYT4jwGHRjAss5jA1sMxYM+bhHoqCtW5VV/fxq2FpI5Chs+EntFGw6BOWwmzBoj+IhGu1ECzGEqwCwihcRYFFiAIhSeXzNzbX7ekLrymC6TNWtmus8BgWZyzBKtD9Hctb8gElj4CFh41kr8wdaFJlrGgpTJ+9+JSsCS6s2NKXwuiuMFrKi/EHn0SxnEWBEXgRDFoICblnas7dCH+YsWjhUzz92RLhBxqgZG7hPGkPvwsYRXeFAEp7MR49JIrrBxkeOsHLhZNjwrwSgpL+iBWwQIH+mTsZjm8aIogzH8HKCVhh4AlhLjx8xSXaku1LMtl6qju4xooZJgoTOYDSQLApDcV+Nu37WDP27lS4lAY12kEtXM4bSMGK7Ueb2fdynGDp+v/AulqhVPxKJBYqlQP5CJRgKcFSgqWUYCnBUoKllGApwVKCpZRgKcFSgqWUYCnBUoKllGApwVKCpZRgKcFSc0n/ACD6ZYnG6V1CAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNDoyMS0wNTowMEKbRLAAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lSUS5zdmcxcH11AAAAAElFTkSuQmCC"},"107":{"admin":"Iceland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABIEAIAAADffhsNAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACC0lEQVR42u3bPyuFURwH8GexWSULm6uUTLpZyGBReBMyWSWD1TvgBbCYvAsr26WUlJtSSpGUv7k/wy3UFSfn8DnDd7nPc07PeT7n9vQ8v1NVVb2+uZlrju1s7PWfzM9tj5wOnJ9fLz63tfta8655d7w7NTo12lgfXBlcaTSGakO1D7L1axwZZ7X3Ez3HKDFi3nNSSoIFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWFiABRZYYIEFFhxggQUWWGCBJb8HK6Yy3+w/mb3aWk4P63WU/GejmIyVml++3ubI8a6Ftd3Js6OLg5vLFLCi5xilfdxcZ6aMrGK95pxx4x+mH3ufVlPAip5jlPxno5SsnottPwVLS9HA0tLAiokuMW+b+z37Pcd9E90T3R3Bah0ZZ5V71aVkFeu4yGxBOVwanhme+ZRUW8aRcVbBV11IVm9rvdzsgNT7fy+ZOqsv3xgpO0iwJFgSLPnvYf2NB0YP77k9vHvdIJO8bvCCVCZ5QeqTjuZbIVjlwFI2o2wmSdlM3iVjv1Xop1jv24V+SpOVJqdJmyk+gGUrhF06dumABRZYYMEBFlhggQUWWBIssMACCyywJFhggQUWWGBJsMACCyywwJJggQUWWGCBJcECCyywwAJLggUWWGCBBZYEC6xfzRfU59qWrS50MwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6MzUtMDU6MDB6fmA9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JU0wuc3ZnYlz94wAAAABJRU5ErkJggg=="},"109":{"admin":"Italy","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3csQ1BQRjA8XeGEAuwAdEqsINBUCqJRGEBlREUJpAQFiASnWiMcGqd4j1x3u+3gfjnu/suImTZctnrZolbt/ur8XWwa5w6l3Q/xXOxmW6Pt+F4PunGQ6zFerqfpZJ6UqEaRmGfISyEBWUNKz7iLLZ8kcJCWC7vmFh/I/WHBmH96gxuhns4CwtshQgLYdkKMbEQFsICWyHCQli2QkwshIWwwFaIsPjeMpTTj3ZshbyfADn9zNDEwlGIsBCWrZD8r/8mFoVc/22FOAoRFsICYSEshJXycusdS1gIKxnesYTFZ4e7/8eikBns/7HAVoiwEJatEBMLYSEssBXyfkMt7M3MxCq14l75bYU4ChEWwgJh2QqFhbBshQgLYSEsEJatUFiUOqx0dytb4U+H5UDBUYiwXN6FBTl6AXMqeeREiAuGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNDo1Ni0wNTowMI35cycAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lUQS5zdmeHyQnqAAAAAElFTkSuQmCC"},"113":{"admin":"Japan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADEUlEQVR42u2cT4hNURzHr0dRysbGYnZSatTYWMgoC1KMlYSkqMlm7CykbCg1WVCmrExKUyjzyCuUUViYjDAZShopmklpYprXyGCexXf5es/7c8+553fuZ/NZzLx3zz33fTrnd37n3F9S+VMpV+YhTJcJjwAiFkQsiFg8CIhYELEgYkGIWBCxIGJBiFgQsSBi5Zm/188M/Rifq7w59KH36+j10v3Nn1ddWHFt3eTKkx0Xt79f6LtyriTqL/qvPqlv6Qo8ScSan50c65y4+bH39O6B8y+Xbl080v1kePVA961H75bNbhwcGU4KGxbrU5/Ut8YebCoeLEg7XRmxctThmYWHx0f7Jzr3jZw4KyEaEahZ6spqRS0iVoT8eerTpakeTWTuZKovmVrXnSBWJOPT846uo3vLPmWqRd1JHsawaMWamhtcXtz/9O2arm07G//hG4+u2h/DdIeIZUwp/1MeekUrlhb/4StVrZfuHLECTRw8W7L29a4trf3AfibBWtSdKx+GWAElNrWwtzJK1aJ6EUei1bxY0zeunrlzOdvxJi2qF3FEXYbF+rVnuv9bQflunz+861bUI/UOsTJb/cUxVlXT+rhlWKxXf3fcPnYg/BGoNY4Xe+72lRArgzVgummF0CRT7+yuExO7k2B8019ME6JJsbShmwex1FPEMhNdWaF6ilie0qHuTis0Emn5jMbUU4sp08Ri7qrZMwt2qa0ei6e4jImlR9zOniBiIZYnsZqd2up/Pt3JFLHMx1jtR04uYi9iLFaFDleFiJWLPJbPVSF5rEgy79lu7FS3TubdK7WDFuYR5LSCd/YKibScjIicbuA8FudI4xJLayXVXIhJLPWIE6S5G7fctaUr6xQ/Z95z9JaOa315SyfQdaKtPURpKvJeIW9C8yY0tRuo3YBYjl5kDVMv3VUcQTr1scohBO/Ux6KiHxX9EKsZfj/8+N6LLz5rkKpFapDmtGqydh5bqwCob+kKVE1GrP/Uedd6jTrviAURCyIWhIgFEQsiFoSIBRELIhaEiAURCyIWhIgFM+Y/dWVqkJkga9gAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjA4OjA2LTA1OjAw3yWdzQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSlBOLnN2Z6/gxrAAAAAASUVORK5CYII="},"115":{"admin":"Kazakhstan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFdklEQVR42u2dP2hdVRzHO2QLoSLaghAEC4Kx0KHYpdihiDgIrSAZglAKLejgIM3WdrBgLVmyaKbiIIXStOSPJto/qWmaFjEIWcwgKQgO1skh4KI+6vDJ8JHDfbnv5RF7z/ktX+47977z7vnd7/39fuf3+53zdu3qn55aWQsM7DGGCAJ3iFh7r85O3/pk976ZjcXfXrn39RvXfwFp4WwILrBjYg0f//blseHRS7eWzyx9un7nzOn946cW9pyYooWzIbjAjon1weTNtbM/fnnj7g/v9J07dPujD38FaTm5evPFj38PwQV2TCy0FDSa/3vx/NE37/y1+M+RAVo4G4IL7JhYmLxr+7977+1nUuRsCC6wS1P40+j9Pa+uo6VAWjgbggvs0hROXFxYGHkOUwjSEqYwsAc+FubPxzjyIbjALsMNDjGAtES4wUhsr39gZuJ+ixYfB7H+03T4j7lHXzwPjTB/NoKcLZNALw18NTE7Ax67/M3n46+/1ZpvfTZiJJjMNXwriLWJBzfmhq68YIPoQENpxEIa5Buss3EMkAlIC3E+ruRb9BDE2vSxEB9iAmkpwXmHEKCJAjIvRhqQBrTErOO53n0WRyy/l+D3K0t9rw2CTu/kLSBMmL1Mxo6ZwwhaS1l72SCaWFCtUGIhUIcbUuRsri4qugcqgBALokCgP088aD278mTo4c/9p1LkLFe6N/pBnxVhHP0BukAdYlf2IdySnyAYOxrFo4ZS6OwqMlXh6vtLkwcuWHuBuPaZzx/9gQHbBCAakJZcVbrHzhghBBOXTillRHu5f5CW4jQWSptjq3GEktPbxlh4zHbA8ZbaG746SA94Zp4MQdxs9VbahEBNI95gizs/QTAutIvnv+3pUp9e9Aa9+BV+sSBi4VpCKQwBxKIlP8eTR8tj5vEzRh7/dnRVahAdIyyOWLxV0MguJy2cbTqN/i9iEV5uT6xMqFZVj5XqKleQNnHwDqY42WJi1TeFnWJqCjm2JH2HjZ8elVA240xfVXoKV3rnnfc0jQbtGu/ap00Mm8iNc4UUKDex5p3HxovBWBidq2GddXC4YfsG0USpCjdwJ9yV77PBHm2VKUyJlT6MZsWo/Ho8Hlt+OPguI0KfeXGbfcrtB0itjdIAKb9L/9wV9oG7tVYLU/iUhhL8evAIQetgSGCDxYtkd763KR1a+BYIHbnbBgd32i+mcJaw6cu/bOIdSWJcNkxoEUYNLeonofHS6K19Eppr/OpCdIjV+GV2dcINaQVpE51K9ISNO6NwYaNXgYPOQDhQjJTokx7snlu705IaXIq8ITckg1LWdtkGSK2rmh4g9WSeh2f9kRbopYV+JkpVoZ8Lu6GLJeZ+bObsyWWSja2T0gHzSOlAL+tgP0JHsOw4O59og4g0fOzSZOt1u/A2c87Pcj+ZFDSnMV/TyG9kTkloVy6k81yHTDFY1jrp0omq6LmnApDPJE6LlLJKl1VVkNrVxfbnV0Hq+FZVqgod5hCAC7Xxrkyg1KlP6QKlvEdGhqV/VWUzxHussdySXyWWAwHpa+NNnSCTfanUVeCa1KiZUlkZvk5Lk50xLKE0GUpV+Ukmh+lVhxz07JljQYV+DN4EKnMxRftAq4MO7a+HfDad9FCE9Koi754b2v0sc+8G5w29fjBdngoiKwKexNPZUgVSFrE2s86CVcdsylx+6aUWDpwiGUfPQaY7HHMlZCpoAX79Jfa0lLnEPvVE08X1dttjif0Wm4I4ggW9YlOQwB5sY2S0QQzBBXYZeW9fNhOb9QR2WUHqrSKhFC2xa3LgtojlqbV3UI7NbQO7JJbX5MR23IE91limkd35MIWBPfjLkzToEOGGwPiTpsD4W7nAIFZg4Jb4L4xjl6KxNwljAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowOToxMy0wNTowMK512coAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tBWi5zdmdUfS14AAAAAElFTkSuQmCC"},"116":{"admin":"Kenya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFGUlEQVR42u2cW0gVQRjHR3rpTi1dfehG5wgRUS9Bl5eiCwX1EIUFRb5k9dIFD5wgKHoIyywIs6Qeuhwyu4GEQhZhJZWB+GAJUZBEER0yohuRYeXpb/TVNMNuarY7fwf+D7O73+yZ+TnfN7PfrlIxNbijUKndq+wCKsGiEiwqwWJHUAkWlWBRCRaVSrCoBItKsCKm0+t+Ft4bwermwbtblCn/zxD+n3dFsAKol8gUDCFK7FGm9Nb9oHV5P7hDghVKTV5MViYrv37OlNNDMyXocI56pXJUTt4k76P3EYqaoIijddwJ7ooxVugd4ssRbypfHgo6qFPbVMffiTVei9dyf0Lu3twbUNTgaFC4cScRd4LurArlbNG06VmqaZN9aAFNWSKjjQWz82fferx01+FdZVDUVKzLGqBUvDxrR9YOO9bpZ+lP6U9y1uSqMCK66EGmyHnLNMBwc0DqaqqjZtDDlsS1xLWn2SXnS85DUYOjOFN3jrr7Q+u4E4IVKS2Nlb4ofWEf5m0LfyIFfX77ZNHJIgkWauQ5uEqfq9rGp9vT7WgRrXMfK4KKdVnT5kzR463Fs35HCvq6+mbrzVYJFmr0M2FBj6vgfHt3TdoLiknbnYJZCnhhFkG9dH/QuuHZ97LvYdaRYL0f2Ly9uRhH5fmwAGuwjFbQomv9rPDjXSsIqKE1m1fWr6zXZ6DbWfFUPKWDhRoc1a+CNWnfzR5WmK7dLKa5pytgXZk2MndkLiy73LdOgyXXd90FllxLOg0WusA1bWzeEtsSw+zSE2DBMlpxs4eVqWuirdgg2Nj455WgKXh/Mrk4WZxEza25Y7eOfW+6Fpb1zQt31NGfja0B7LDbwdK3G+yRGXbkYdm0hUGwIqjyUYy+z65r65jqkuoSCRZqTOfLvXgoWiRYEdf9l3/fJbc7LLg/CRZq7E5W2reDS7Aiooh+5MDbHRYyGgATFDV2Jyvt2yM5ghWp6ErPZbCvDeV6x74e1NNp3Iy0nPvB+sD7iYQax8+vml8F9RO9+QeXYEUqbLenyugzFtaGUNOMZUqhQYuuhfDOgaUPPGoQ1OtXIblP7imjxgSWKceLYDkHln3thuwrCZaej2WfsQgWwfI1Y5meMHLGYozFGItghW1VqIPFVSH3sbiPRbD+7c67fJjDnXeCZXxWKB1Wzz0rtG9kEKzIrg2DZjcAKf/ZDW6G7czH6syaMg28zF5nPtY/BSu8/4t+MkiRI4p8UekQmUHqCyzmvPvJedfB6rmc97Bkytvvk2/p8C0dvv7VM+8VmpyaDhaeEtrBgjW+V6jwZYGQKd627YIFvKOMT4PU7Fw/ZdV+e4ylz1gmHGENljvfhA5jD3dZlXd2627vrGt64F1N+4G3BXfPNST2jFudt77/siNDhuWrPvpbOv5XhbAAa7CMVtzsYaX65a9Vfd3RmR8KL8/8UDGj/kvFjM4u+F4/b9icfb+u4BCAy9e/4ApNX5uBBViLF25viBeiFbToWj87B9axthsXjrWtKD9yaUW5frSgLOdo8O9j4SrdGlpBixJighUpxTDrc5XU0bWrlqta6RbtX/TDmbhKt4ZW7CgTrBArBti/Y5p8fUnVjx150zdIgRTO/DvnS7BCrzKU9n8VoDl+cOJQLyW/mowaP0hJResb6k7VbqgjWBFRDCcC6qDXws3ljV5wxzsDNTk+u6J1gkWlEiwqwaISLCqVYFEJFpVgUakEi0qwqASLSiVYVIJFJVhUamD9Bgc3K7F3aKTwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMDo0NS0wNTowMLjeFp4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tFTi5zdmdajF4sAAAAAElFTkSuQmCC"},"119":{"admin":"Kiribati","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHyklEQVR42u1afWhXVRg+Q6Hwj5ZLDaZLLbFG4bakzEiDDAxKCyyikVZOCArpS1D6GjGhsb5bxDQVqsFc6DbEFJOVyBzaxj7UlmvT2gSXzpnlHNjCBfe5fzw/zs713K9z7+/jn5cf93fv+5z3fZ/znve854i2w5Pnzc0LTL52Q/mc3bYMVnNGsodjjy5Symz98UQ1zuSdctbIOydO/7lgKApimXeohmbbHZxNVTKEEbbcUlA0c0vLnvsX5Q1Ctr51+5XZbxglmX8s1qChTaT2woGgtre/OnXakrbKJyfl1kC2fvtI7vS/7JBb0g62hnPt9y0N0AzZtaB8wpTnIDvf/azwxnqWeAdf6c/7GFHNZe4X6VCX2KSxKMWB//W9qidyinpa6/uyf8BvSLxjE5G1WU/w/pm79pXkTDr/RdO0nFWX17dsmbICv/Evk2mcLGWmEo102RVpVPCSoxFmO+SUe0AskAaScxKTCQTiXJhAIBnX4OIbBzqKdN5hgQqcyUCs/hPbduSOgjqcn5CZ8NvOZwvvLp51HySWyIRlLuytQ4y3ICKdd0YglpyT/rvaeX7qCC9weILfkHiTyWdXUTq1WhpIkWQdFD9j0PgWVEPGUhGI6yfeEEDGojA371VJg8i0B1X6eaEcp4pKpSawb2IlNHRsYpl3kIwY5zI2c5aQKd6TOEMkO3GTfik0Uz1YX6ENgYWPS3LuoWdOD6PIWHEwWGMMTBq7v2U1DlDC2/SyJCottCTsDj7RDnUGvs1kZZGUpAnqOMKSfNgCiZ4Wt0nxBLtCtCHwhPeG2C0GlsmSfHFM0xqLswt3sJhA8nENP2HaseTzxxSpn5KSWMadzhkFlEKmAV2YRkNvNk3PnixTip9w+5SpKWfBVM1Mqkkr0i1L8R0EhF8mipyZ5J673Ivnf1Gf4fc1iBWHazCZ4j2oOw4gDZODs5FMJpVULZrQbJf/tCHILIWpKK2jYi7MVXTBIsjkYykvms60w1IbSt6KcbMjxYlltwOoWYAwq7KUikzOEl+pshff90o4qE6uG6SZjJXQ5LRCyPs+VaZBlnJLKZleXPKzTl4W02G3KNLh2MS+EEy5irMUF+P+icU6QSy+MBijfn3Ipx3C6CFrRMe6TCzOW3K57V/yUsg5jPv1oS9YUd1WZWId7Zzz4YKzynuP+kPUuIALFCCyDM+5WHqObSz+JL8bi9Hlfe0P5PdyxvK/CLLkLGWTzEKExBhseoUWbPN+xm/GEv/2Dcw4u2xkSUfD8d4LWds3NzzWv3/d6tKsrmfuqVpaOg7hZHpJz+2OkaUB2qAZKEC8cuep433N18DVMZ5wYRLjXnq255ett179aGTn0SNjdaMFA19D4gn+hUTgfS2FlobRGb2bnm7UwVXaq31H3tnP8HAAfnYZXyCKMTE2a2y1LBH+f6p/urmp48wrZc0f1/esX16xaj4PiAOJf/EmvoIGlX63uDwb2LCgcOXAgyKcb7QIpIF1/t6/80Ya9O016Wc5vpBucYXbAfEMYIa6DqQnR4yDqx1OtySTpZyBIrDXuJ9tArm0V1Q+X1fU+kdd9cHG7l3H9p6qHczn+eRfQlt33enbhoZrHm+c3/Xgl6UN5W3FkGZwgWISF5qBAg9DwgNmcL+6tLuu409IM/Z+f+jwzJM58LAQWYvXvj8R8qaNyy5+unfR4NqT1TVvL9/y20Hx44a27/rmDjQPHRiu0ofBV9AAbdB83csP76g4x4h4wrgbXt904cDCsHFle/3jQoMKF7+d7dUPOUYo4+aWrJhdOSxb6myvPq6znxMQVYNgdxQtX/Pitv1r3qk4u6eNZx4knuDfeRNeOLe1UBVIfRktLrSFh6uaYPq48Eys/TwO17QH5M2hsjSJa95e56zploLe0M3jiiMlXafPPIo1uOylbwoPrVy6eF1/bbZzUtWR0ABt0AwUIPrHVRnvB9ePQ8O21y0uFiy3uPoecLZXuSvkchsf31G78sTm7Tz/WCJJFj9V9tCu61EwQoPbYhBfQQO0AVeV7ZDGMUL/uCg8GVdlL/5le1HABmWvjIsnjMtbgbD97Da+LtoNKBh5BmAXgCf6Ba9bybhAZNxg9zjxxOVMEJWf3eKKOJAjtXHjMwlN4grndOctzeIrnTTL6Z2Xs6hwkd7dZiPG1SkbwlhGgYvCQC7Y8QTx9e9nVdmQsMUJoyD1thOR9zLxKYRlXHRxkmWjEwGun423n014BjdsXG/ogSG6bb7pg7ltvkXb9JOzrKqNGTd7IWVcZJqo/Cz02/aqTo98TIGv3O6e8Ca+8nYcxMdQZnCxNKQDLqKvH1/h7YAThRvPDG9lrzfcaA+STeLGx1638RVQ9/sHA59fbAyvNwPN2JQCkXHDuwQCXKBghpnEjdZeGddkfO2zQt4dINGhG+FtKPgKGuSzd065nGDxZrC4vIvRwfXW6WFcuXgwietsrxxf/7iq+Ar9c34UZZj3mAGcCfCv/hm48+aAcdFxkXExM8LDle3FciDby12cMHCjsleFq2Ov8DYgcDOBoUY2wIwb1GUV5684o5uxV+XnoPaPbu31iBvslYnwZDxHFZW93lohYY8w4Um0AdbR5rZzFl7WDCoPxXPaBDyGYN0XnuPMuN7ktPFTG4URC/3oa40hKtcHm7r9OzHayWP+fmnYE/J/4vCcl8AXaHMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjExOjQzLTA1OjAwNMxImgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS0lSLnN2Z4he5NQAAAAASUVORK5CYII="},"121":{"admin":"South Korea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2dfWhXVRjHf0H+I72g4aIiqTBXFEO2cqB/qIhrEUY4g0H2n72tF2oWBXOSLxQOpbdFjLCIXNRKpFZWatnCXEqaGYNlL+hc4SpjzfVX0Or2EXrG6V7u7577cn73Phz2MO65v3vPfc73Pt/nPPec55QmhieG/vlTqTJWWVIVqFRgqVRgqVRgqSJUKrBUKrDKlH9M/L52/OiOh7wyMOQV7Z5giZbQGNpTYP2PREFVD3uleqdXen70igLIlGgGLaGx/navKLAmyZPPeqXxba+c9dF/ZeNUr/Auck6RwYQG0AaakbqqH/WKO1pyAljBaqJW2jDXzH7S7oG0T2gDzaAl81VUYA1jwDHmqIb/oUW/WnyLvMJLPhdPamoAzUjnwawtKLD86O+R570SpvbeE17Jkw2T9omn40l5aqkHNMP5wbWFA1YY+jOV5VfrDgXEq5MwxOdX2/WOVwoErGj0F6a20oEVTG1havHDpF3Pym6V3KE/VBCNHPPkXUUjPvSDTybt+q2DXjl56al1p7/OLbDKpb/wtXly5KMRn3QGTPCl7yqUlP78LMfYy4dqB44jR6fs+uazcaQ8nhyg7Wkx29Fiyf3RXzB12rfwz7qRml8fPVW9vXvXph8+bV345OKv9s0ba77yi/HZnzSMfj4yo7p+bN+Bc86bU4vkCLWcefS7lv1rb+AKXC0ucDM2lOCQLrnLo8VS5dKfPZiG7+tY9eL3gAPQ9N8z9a6aLgkjU3KOKanlalzZHmQ8KX4SYQjpS1FsSLMigWVDcH6BQfuvh9gVut8PHDZSXo27cMe4PjkDMhkodpMWEwQWIJCPlBX98SusCEQWF4zCSO7I3W1oKJjaJGmao0X527he0YypkDdDgiaY4PAh4qI/lI7nlA6Mgm0YLYkGr+BAaJixJL2QjgtfynYgnfTo7/glbdueWpgVpPwkrYqLB4KJL6ugTCn9ACAqwFBLS4b3IN82zHs0dYw8saXvrVpzTJetlBRMC+MNohIIpZYjWX2QLmUbZUb60V+0eaREmAgHuGarJLxoIa2NlxalbgvxSSfYD+MNs3EtUSJRJTchZcKL1trTYrb2yempybyFNjO4sXDdN1/+240/uw8sOWaMZrfkKgHXZtjmapXOjsb1F2z5uPeB88frr68UYMnRoq7Sce/D7b9O690/Le1aPXPn3HN31/1VWcAilBrX5yAFVmxyz4YvXzq2cfFltW0t76cPLGzkayumr5k//MKhGR2LliA5QnuCQ7I2hKjASlB2tvds7V82e+TaZS0r0qFC7nL/mzOn3/Iq95329Jy+1gZkad11ix57nP/rXrn68B17OdMP9MAuWgBCgZWgbD24eai3j47EWiQHqU3bL9y/5CbABIDCS0BGC80vlTaBUwVWInLllPXd25bSec2dVzQ1NwbPQYjmCQEpaZOiSUBpwssm9KDASkTe9kx71eunzxDQUM23D66R3WZvpfCWolmpYOslPTCAlY/ZsDm0WLLbAET4uVZ+vhRXiwtSUuJ7qcVy2scKJp1oVio5SJl2S30sR0eFfp2HV4TvBcjkyJFORVLLmVBqcpAyiVtHhY7GsapGG67Z0BLefcZaIGXIIGkwmZJhAYs1FFjORd7rem6f/9ye9GFhLz/sXT53lUbeK83TcllChXzl1G+Fjs5uOHB84KITTeEJ0QVZvXxBS9tqnd3g9HwsM6blviRQYj8fi0l/Oh9rUn66eBdQYLeumtd05+ZZLkMKy0pry33GwcPHDv6yIMwM0sJlm5Fz3v3SgdjMeScA4SYt0ipaGE2H5mIvVgzIpWAFmvPut0oHkx7vIlVU7KY7T6vSX6WTK2DxwDI/nd+6QoAVLy26NlqkJdFssB/9oc/gWnRLL1T8usJ4V0Lb0KK0XumHQCE+G0j5ZcGQ9Bc+QUjFr4ROIneDfeqid7fuPXvw4nRCqdyFO9ovlUMnwIXMfS6nNHI020xytCgj9R0fdK58b1a8IONqOOb2efTkUl4Zryp0thn5toUnvjOpDUUwIl5a9ANZzxu7px05QlQJcJgjSo5ApvzPmfyKK8SVlDFMfixzBXmB8mOFoUVpn+gYSZdZZfTDHSbOBJ0h+eBNbRKdJOPpfhQmaQ6Nob0wiY1yGG4Ik0dUvmfpJGFz7oN6mTlIzQRPBcpBGoYWbbIm5ym5rU0CSL+syenrJyd53vOxT1i5wU/N855gblKUqztT6M4UMWwmIEdM+dsPbNJeOiLfVZg0kGZtQafN2Oz+NWHslZU/qbt/JU6LHNH9CnW/wkR2WC0OpCbK3GHVBfpzdGqyJD7dEzqMHyb3hHZt/zPdxV53sddVOip1lY5KBZYqQqUCS6UCS6UCSxWhMmb5N0Cme6EwPrvgAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMjozNS0wNTowMLbuz7oAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tPUi5zdmdeBwfJAAAAAElFTkSuQmCC"},"127":{"admin":"Libya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyAQMAAACQ++z9AAAAA1BMVEUAlTCNlXMpAAAACXBIWXMAAABIAAAASABGyWs+AAAADklEQVQYGWMYBaNgiAIAArwAAa44Of4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjU1LTA1OjAwn0OtAwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTEJZLnN2Z1DuG4gAAAAASUVORK5CYII="},"130":{"admin":"Sri Lanka","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAI3ElEQVR42u2dXYhVVRTHj6KR43f6UCmjFnJHnRBkMkSnHKkYKioZsJh6qJeRQhKfHKuRAiuVUZISI6UMIU3BSMKQoqKBIohCGhL6MqNUqIEgnyqbcH536C9rznafj/s1d78sLvueu8+5a/33f6299tr7RP+eem921B5kkPnKKKggyACsIEcLsC6cO/pMdMHns0rbg20vp4y7u/uZ/Vv8/7tbD+4rffTsvm9SaybVpxewuib1FAq3LutYd8OSw9ll51/rvls47Y/m9Z+Pm4AcHHziVBQNf954MoqG5aV2P7mtJxrh+c8u2f3uhMUH21YV5gwcOHbnjllfHTn9yJRpzUha+DbIBPLazqnT37E6RNtewAKPACKK1hTa1meXi1oeaFza/tuezl/H3zw4+NCCS7DILi8BMQ5Yu3rndxU++OLMS49HM/lH5wfe/DPahFK2/9A4d0GLW/YOzu1q6vO/UmW6HtztPr3539FfvryqvX1mG9pDk99seeXeMbe8uHrFmuverztgHVu5rmfiQfvt9z/1Lm/4MQ4QbojkJd2grB7JE37y9ea+qwtWk2gYfSZwhbUOLEg77tukyi0F4JKCu5xAhO97ts0rzPswDjqJgVXrjAVpoxrgxZ+nXb/1UW6cmbs/apoza/fTi2YfvfHL8oOgPCBDAwxFUIEmD+x9+OyUN7Z3LT18/VMJXGHtMJYreFdYYH4UAdSIG9KNYNTKeC2PgdPFednvRRTFP1V96h1HJbCuHLzHQQS+Sap0FA3n7fu4o2d6a1JoWkBYzlPJt9yXOzIwMDYtDJhSQBwtMZws+PhHdRdjuZ2d25xxI/jTc9v+HnsRYGFOH9dpHajOTOE/jKdQozcApDMyneGqPN7fffGqm3iqIkOXmE3rGlg+sZQXsIYMhlFJYQAF5SE1J/Kxzxb2zDh5omnLq1EHPQAjoEB7MQky1I7c++zajePOvHZow7dRxB13Hbpv/NQtOxpuv3vGVsDNNfTA5F9BVnTTwmQ8JwGAPrnPkHDLAKxMwCI9SP9MvxVAuCTlHpwXhlSe05y1tigsAA1wAcRv7V/b2/Dz1nvuen5y/6ZfWlpnngGyTx65o2niYq7XbBN3BHawo6YMeLYKMNZoSpBmVx88pIYhylGWUu6xKVm4h3aVh+/v7o8G+C0tfAY6pxv274zGqPvbM/nBayYV+Jb7Pndb8+uNK+wEZfM/K49MPQH4GBJADXdpY6Z0s+PEwXvtAGvkWaEmFLLDCzPAH/RfDJnFxXANxrOcpA6Lz0iuV3bhGgBk1+O4kmsANxEbYIKxNLS3AX6+sZcmIyrmCumtFhkLk6jbwpWoeTAYZl4/f/mOiYMKF3VzykxI+Ay4aORED8DCQlaBi7QaUCDqakRemqkKV1iLwNJcjrokBRYS90SMRaAN6+DsAJOyl3WCfEsshSOzSQQ4CXemoMG09Kb8pIzL9Zoxz85bVTQrHK5lqCXGYi6mwFKTFx3iEJg0flLQqCRy4jNchcTwwBH+c+f0gYtK/ZUFDaDMVz9V4Qo1xsoPXiPHWLQnVZw1hq7tq+sZIYM1BCxbLwWMFEwKO533IXGF7hybwovg3aZPYT7aNcXqXi1Nx2F1PStMNzo1leAGFq4Qc1quosXCS+d9On9kNgcP0TN3JN0AdDR6c5cxasSmrjCvNGlY0unLC1i6HGQdE1Agw0Qgz/WwkUZdmobQcP6Ftzvbxi2jB82B5VVJq+ukWbRUda6w+oFlk59uxrK/xQEBLJjGxl4Aa+f5R/eN/V2BZdku30JtnFf2dMwoXtLxrW5IByxGNkyjI57IJi4i0QoInJ2dCWp0xWeNxmyuqxQ7BigsThdX6a/qaEnHv+IqTgIdTA4g6JMWmAbYqYqBlK1cVX5SkNmkg5Wl22DCc/ovxgdXmMNaIXAh0aDmV1iQbdKqBOW2uPjG8pMmTsu5c6lYrOfBWFaHKctmwlqh8pY6LxzThmmrW4czT4x7AnZd9qlmWbE8VgCWjkv4CRjxma1s8A13VHeZZb6mfZZOMlRCPVZuwEoKNdycrt8hYSwFgTo1fRJ/oKhzTArEpL9KWpZdh2UzV54VZuEtgKXpAAJwgGV3D9s4SUto3Cb3vxIY2VjNJ0rjV0nLZtyzwpDHSpluIJ+OswNYLAPbiEorORUubmPrfNPNNJqvdwPOPRMMpckVy2NpwQzA0syTRlTW/Wm1gk847+YqLUR28xnAIhkbBz6K/vKtha9rV5jOLXI9aQUgQh0pwTvG1jp01R6showL6hWClvkAAW5L82pxwGIAaB2pfSqbe8vOWHH/bpTPClUm3Xmsy722KkGNhCHVFQI4VgnVwOr+1LVpWZ9m9lXSpzWkbrjQwmWtJFP+o/8skZbmseo6xoo7YMNnF6HOCmEgUotab66K1pVE3eBlJ/yAQPfuuZ9T4WXdIgDVqnYLLL3SP0EaMu8l3EwBvLQQL+kkQA1MlEYojQvzNzCgsVEUYFUeshCEU5UXQ/BeMWBpxZW/MewxQHGVUraC3t1bHLCAkTKf7oVUrrLsGIBVgX2F6RaCABOggZPsXM+H+bTuHmkjLaADp+o+RK1r1elCXic+1HWCtHSntcT1wMzRXacALCxXWZMDI12phOcsD2keSwty7H0t72Y5qinEWCU8Bkgr0LUKFGn31bgn/woveIhAm8M76dPuSSRuY52AaywceRJ/Fre1smER+jJgFaOKoZiDkDmv9TL3/hnbXkwlOGtQ3QcY2eNDML/OLvV0Bvsk/oNKDybRjfnqxOu0bIY0oz14TWdP5Y/DKntIZFJgoU/0hiaLhwwMDY9wVORlee3yg6mazyaNu6MeEBzOIL3C4bbEHNV8sGylni0u2rMrB6P+DFLXrJCYgL+te/fcC7HuWCTLUdhZDuv26ceHg9Md3M1BSBpIpDyOu9QvEFC+Sf7SgP+le6Mm6rAvEKiHY/5L/QIBbU8MLP9XfbhfyJHulSf5bniyz5b0FSA+rxJx95/0JSv+d8/+0hr//v2tGV7SFGR4+1eQAVhB1rn8D0mrs1wVEO5bAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTowMS0wNTowMC7y9zMAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xLQS5zdmcncZYDAAAAAElFTkSuQmCC"},"137":{"admin":"Morocco","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD/UlEQVR42u2dPWtUQRSGJ2AwiBaCWJhkZW8+NkYjwSRgNGqToOkUwQ8ECzGt1aIWqcQmiRaKICJRLFUEW8EmFuofEPEHCIqNKFpooZB3i7OczDVxNxHvPM3LMnd27mbm4bxzz5mrYeF1pWdgAEWbq4EpQPP12efuC/0vAGsVJwsFLBSw0CLGWsBCAQsFLDbIgMVEoICFAhYKWEwEClgoYKGAhaKAhQJWMfTh+uxx5YmU2QCspunUtsqV8vTl0P01u8FsAFbTdHRi6FrWKaVUBVhNM8Et3ydae+ekGCJgNUFlfy3zJ54PjElnD2cXux4wM4DVkE6e3321fC+Ec9WhFqlamBnAaujYT2/rvs1dGy1YavlfDPFf7QgBK6qyPNmfBQtDBKyG9PRc/0L5adhwdnzwjQVLLbrKLAFWQya47sdkX+9tG710ldTDqoBV1Gm98z6b6XkkmIRR+93997NpqUVNPcGIiLWCPHvNBI3x1czRGKJ6MmOA9YfoK1WG3UYmbdWltUi2CBa5+MKCpUWtHu/41fnlyKm+kdKw8kxe/dVYizVBfVb7oZ973mY320YP3irPqr3tw96x0pTa1Sf/N+T/Nv0VxcA0FKnwouWpbbEjT3N17a6lbpO+2G7j0xLfit3FtizjqvAq0o4tFM/OVIRRXS+2wBYdW66xGKlF8cm2eLDsCF5jv0G/sKi7tMLusWaObb+ehVrKwMUbWdjJT9m7zjMCUZ+tCdYW/lV5vnRJfaRCYWt1+GhpkzVEP5ra/d31q4qdYi345n0Ji4wssEC0fWIbc7/B17eElEbzQKuPLC+F8xEhnSc+RRobk2xkKn3b9bL9gLWw/GN9umoNUSN4C9Yddfd0nh9DmhVAv/x2j6V9VX5c0UbbjuOz87qqWEi6IYmcu6Cp2ZnbA2n/lL8H0lX19Ghq5JSPBCadINXCj/RUxtvHYhZpDdE/dfpkhEbjlGkg266NthDxoNgEqX9yrEtSUJwGrFixWdZmn+l8Nsua5o7BnR87ShSnAStebF7EQmXmfItUi5BST4rTgBXNRQkXmwKNIWXVvm9o+1OcDimboE0W6LPikC/FyBx11ac91e5HS9kQEwWrLr1popHPbOnkgn3FXtnz2Fl4a4gpvzkd0jRB4RIrD+fnyvPz+FZTNsSQZu4qdvZhpeXhuspg5PxCmjmtREs63rYayZXbPL4deaUvihUptiUHln2zWUamZIFd1OUvsP+Wt8g035wOqZmgbMuWa1YjTmhk+25PaoaYEFg6ZaC059okAnQX3TG1Mw7JRay1jxyKiEQsFAUsFLBQwEJRwEIBCwUs/kkjlIiFAhYKWCgKWChgoYCFooDF/wcBWChgoejf6G87kZTdBU2ZMAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTg6MjUtMDU6MDBtZl/tAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NQVIuc3ZnB91TgwAAAB90RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgTW9yb2NjbyXwiYAAAAAASUVORK5CYII="},"140":{"admin":"Madagascar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABWUlEQVR42u3ZsW3CQABA0aOhyxIMECEXlnFExwaJhBSULk1YwWAQQ8ASniDpGIEBGIEJrFyqbJDI5vyu+DXWPd0ddyEmMb4fDotq0t5ml8dj287nWabdNoClYIEFFlhggaVggQUWWGDd8WjjU5yCBdb/w8ILLCsWWM5YCpapBQsssMBSh3cFy3UDWLZCBUvBAgsssHSYsPACy3UDWLZCBcvUggUWWGCpw7uC5a0QLFuhgqVggQUWWArWENrLr07kX2HzfnrZfm6K1Thr6vrtmufabROB9frVfOyfQ9gU+TmEui7L4XW3Lpe/7cHvSRHWkHn1pmmcsaxYYIEFFlgKFlhgObyD5bpBbYVggQUWWGApWGCBBRZYfR6jWMXCdQNYViywwFKwwAILLLC8FSpYSIFlKwQLLAULLLDAAgss9VaIF1hWLLDAUrBMLVhggQWWgqVg/TksvLrvDxwkcNOEzggwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxOTowOC0wNTowMKFWUm4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ERy5zdmf/ENrCAAAAAElFTkSuQmCC"},"141":{"admin":"Maldives","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADd0lEQVR42u2dS2gTQRiA96IXRapCQUtBquBBZEUSQ0VpCUYRLz3YUvCqFUHNRUSlFNr0EA/BB1IRkSq04PPiC0TEWmoVD0Wj9FDxIEWkakU99CK2Qn8PI8vGrJvdzOx+l4+SbHeTmS//zPwzO2sVizU1iQSElaVFEUDEgogFEYuCgIgFEQsiFoSIBRELIhaEiAURq0Icb16SSdQJqTa38kEsxIp4+RgpFpLpXz5Wtb4wrC6NF+vR4bpM4lXfVftc8ksgzNq3k18DvMr8+St1tj+fU845z/zWVMOmVvWVAMtK+QwGiyW/DCk4q62rZbMNvXLhSPdU09HSr7sd40apkaDjFmLFjohlcERBLCvoDrv0G8KpVBOrOXyJpUZiF7HMlcOUT05TqJ0ia9efTbW37/l5a0H3jpODI/UDhUvHxpbeGc1tGHrTf6ExeXF/xz794yhiaUHR5cze58uu932wfrR+XjN7YG7j7NOJa9MrJlcdTz18dn5MhFvUk3uSThOxEOsfESu77v7j0w2qTEKJUvXfCrUtPYwKEcuDUtLMqTIJ5XXThwiI5SFJ6L83I/8rTZtTKYlSXtOSeioYcbHCKfTyr5Iu9H86tG26d+bG9yFVqZfNH8ffrlzdeerF7svRGDkSsUJt/u79msiNDjtj1cHFd98Xtkcp/YFYIf2+3WKVjPukkx6l1CtihUTJQjljlaQYmCtELNtrt9p/I2hiFx6xAqekNKV77hRr18zAzSP5qEYs1mMFyOXD+RM7O6Uv5RQrY13Zkm1CrJiK5acxErEqG7H0bxwRK/AqiWqigT6WFqkHt1GhZNsRK1JihdlYuOWxXttTbe86zJ1sjvgK0upO6YSTdECs2I0K4zBXyF06BiwIltUNTr2kvyV5r9JX9PMuESsW67GceskkjykrRRFLu3yPXFd6V84VpIONxdoHk+rktErEIo/lec27mqOXv2X5svTA/C8zRKzY3bAqFS8CSUZesl/l3KXDlA5i+bpRtnyldJjSIWJpNIA3RR3EgogVP7GitxOEfCPEgkQsd7H87LUX+G6ApXfxK72/nnqMx/34/vpeYZ5H2f2QPUghe5BWY79yrwXn53gd9mH/v/JhO+7At6F2HhOOWHF7sACPPDHsiQ888gSxEIuCgIgFEQsiFoSIBRELIhaEiAURCyIWhIgFdeZvdv/8v3xJuaAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE5OjE5LTA1OjAwy4tZRAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTURWLnN2Z6KQZPAAAAAASUVORK5CYII="},"142":{"admin":"Mexico","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE90lEQVR42u3bX2iVdRzH8QUWTqWyG1swpAyNmiip6E3BLkqtySb+IcxMneLQBkt0obvQC1Pntqbz74ZWQlu5OYXNuZCmpRdzE3XaTMdoqDlX0xbkVijoCfY+Fx84LBS9eZ7zufny8DvPec6B58X3+/19z3MSEhKzsqfOCHo8fOPoM4frI92Rzsil4MaeDVXJNaXnWoYnjR4b9JgQDljlo5pTji83LMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyrCce/0m/03GrimhYhvVYsbfgr9FdI04frXmh8PmazKKC7FyNZ3rqiqurnxQ1wwo5LDB1J124XNt6Ne3Eip+mHnqQt3na8F1/L902eUnB6rnnxr2XO3HagjHfcQwyzjQsw4rG2093X78R6Xqts+rSGs1PYPp+Xv6ajJPQYaVievbiSd0fvDu5clTDnPXjR760d+WqtLXj75WMyNk4a8rj8DKsUMFqOtJcVjns6thrn54tI0tBh/ykx+QnVshYi7ZP6ktOWlb+Vsorb0MNZF05V8qaZxpWXMNqzWjZXN9+tu7U9d3v35zY+HpOQ/uOQx3LfuaYjAUsAGkpVFgcE8lej9p7GVaoYHH7tQgSoQYsMJGTOAaT5ipdgR2tvWHFEazevs6+Y0PBxAo91vHKHyP7FlIQta8CSuqQN6aMvE1fpc37QE09eYsezrDiCBaA2q60rWhKgRS8iOQq3Q9CCl7QUUxKimP6rZaGCwvPdBhWHJVCSNG8U/60LAKLVh0i9E+gIRs13S1KzT9RNGRle+ZWSHGmxoffJxpWwGABRVtpjtm7kVGApW177Q81vcVdMNK2HVL0VbwXoPwbG2QaNWP9f1k0rMDA4kZChNsPKYog4HTcAKzzz1Vfnp2o0yzNQDrBUjp8Viwv1rk+34RSa1gBhgWmPbWl05fv5tYC69+Pv924/3xsxoIUEWTAImOBKTYC7tjvjfm1s7k+vL56aueBwmJW4Fv8ztbMuYl8K8MKMCxyEo05DTvrYMp4teRUycyi5PpvjiSTReiHyGew0wEEULSF1+wFPs7h+kpNp2UDzbcMKzCwos14PynNE78s6Hz55sUJSVtWF1YQ4cXkCVKcQ9TCSo9FJBfmpn/YM2MQUYuj9nbAGqgIGlYgm3dIld+tuP9JHSVJeUEKXm/uXDf/82fJZBpXLTkwv+IijCiU7BnJZDAias6DFH2VlmPDCtWukBsMLy1Gd279Oaw1nSuADEZQezFv/dANWcpuzrpNgz5LZEXZscKxji0gxed6VxjCORZliC6HwsTtj8LKa/3t1xyNZCCoEbOrv/zi6z+0dBLhxau8V8nqrtNzrNAOSLnZtPPkMLIIIOatbWy7tpgYbeoHl36UlkskV7GzgwvvotRyZS1/fIquG1bIJ++6+QeBPnYMF4qawtLIqxCkV+O9ZMTYK3vyHhewdKdGAx59XrS/YOk8HSgQOZhamrVpP8c6UNBip6X24X9+NqwQPjajxZEWW/MNaBh4MozQGTpn6o5Pn5jwg35+5j2aw8ABJrIOOSmKrP8nZ3jxauyPRX7m3bAe5Yl4+fFHR6b++5dh+Q+rhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYoYalz52GCdZ/AyERbDpEd1wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE5OjM0LTA1OjAw6Hk/+QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTUVYLnN2Z9b8CTQAAAAASUVORK5CYII="},"145":{"admin":"Mali","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNklEQVR42u3cMQ4BQRSAYWNFoxNH0LqAI2hUKoXCBcQFRKJWqjQKRyDuoVWpdAqFhqxTTNbj+04g8SfzdmYyqdM5HPr9WnCbbbt3242Grdlj836Xi7SP9fvTuTEvj/fLad58XrurSWsa/R+p10BYCKsyRZGW5TjWIiisAGLNWGXvtU4DYYGwEBbCMrxjeEdYCAuEhbAQFvxZWLYbhJWF7QZhISwQViDuYxnes3Afy/COpRBhISwQFsJCWAE4KxRWFrYbhIWwQFjh/N7BjrC+wu8dRQsLYSGsCnkfS1hZeB9LWFgKQVgI69+Hd2EZ3rEUIiyEBcJCWAgrANsNwsrCdoOwEBYIKxD3sQzvWbiPZXjHUoiwEBYIC2EhrACcFQorC9sNwkJY5OQZIxBWHN7H8lWIr0Kq8wHqzHuU9PMQqgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjA6MjQtMDU6MDBBJHKsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NTEkuc3ZnrHPmzgAAAABJRU5ErkJggg=="},"147":{"admin":"Myanmar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAETElEQVR42u2aT0gUURzHp1MkRRQRdIpFMi8FphZZZJChEYRBB0krwzKIDppYiSLoIUNLIhc9ROTNys1KVjRFSIXQ/kBooiZkqIFYKGqabZTBfvfwZNr17ey4uzPzvXyQ+fPeb998mO/zzVP+zr/tUdaSpL5UOAQkxSIpFkmxOBAkxSIpFkmxSJJikRSLpFgkSbGCzaWovuKoXJCjQbH0EytndCS3EeRoUCz9xGqZsb+sBDkaFEu/EBx33fma8ccxUTWZzUCkWPqFYNPSqyW7hwxEiqVbCIpiOaZcjiGODMXSIQSXiYUjDESKpVGs8182XVizTCmR7rMcJYrlv1juyPMqFgORYukWggxEihUQyz4/OeP0qpRAXMkR+49YRbvaHti2kCIHb00kPrXJiIUrOWJqKrv7G7ticmJ/NvbHZqiJsyLVx8Uj3tqRb83fs9pa8FZbyubWyynrxyvn68abZcTClbhLpgaZkZG/Xq/Rk39SvisRqfjuwGq8sfdd8fVyGaVE4i6ZB2YdUqxldHaO5Tuz/BULd3H0LCGW/GsfRJwNP57dN1zkr1i4Cy14iyr5CQPFCuiRy19/uKo5K7EWFI/I/O37SOAhqA5E333JVO7vlRTLb2IQqysHd1Tv7LsymtR9COw5+C2he1Zfyk/YfU/kV6M28bdjNMJfLwNEISKmvmJkod4WyIM3OjECYuBSLJ3/awv87WIUYvaGX22UEDSYWOJk/KStvT41CdFgVqUQgvilnLyHbAbm2dtpdKXc3x/FWZRx/4tUzLGsgLDQtlgQDjKJkWeO5QnFTOtVCA5ti5yhIr42GmVKbtEFUuiFEKk40tdS/jw8p/moChVygdSQvJTwekN2QW/m1IveqHBQCpWgKq68G/4dFg4rYeIqlBU+7FjuIzQCaIXdobpOzM0deRTLw/Shjrb0dcF8V2Ve6yo856BYJidWiYIpFnq02m4ty4kV/Ik8euQby7RTeE8IBmd2pVpcQCByP5Yfu6F97yIPfM+Wtk1z6gqDH4LygSizU97frYursUFSfi+8kr+xpCW+1AoM7Udr9G6d0VYmx6Kj98eZm1NbUz+c3R7Ih+rFyGeFTZ9AbS2gd1SCqr7HxZ86etqsY24JsX7sKYu+26RNhbn20oTbMWJrc5FFF2++0aapujWKZWC6HvWceF8r//hx/XRD2nR2pLc2cVZbyxTLciG4UPOwoe64fEjhStylLRApliGJ2JJ52LPJeQXF9kD6Qgu/Oz5WDLhWCER3VRTLwPxV2l7TOeDtAeOsvu8PtOa730W7M6I1gmIZMwTjj42mJatDEEfmU+9V3T+wer0jItGLtxpQIcUyfAgipGa2XbqaVxLMStCjOiLNHYimFQtxI65ChfYNgd7FlTBzB6Ji1hDEGwIrWOG5roYKzRqIphXL9ypUOBAVUiySpFgkxSIpFklSLJJikRSLJCkWSbFIikWSFIsMEf8BFxxzt4ucGlkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjIwOjUzLTA1OjAwjkZFOwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTU1SLnN2Z3Afk/gAAAAASUVORK5CYII="},"149":{"admin":"Mongolia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwElEQVR42u2cMWgUURRFA1YBSdQY1qggmNZW7dIoBBFsLEQQrWwUxMJC1MrWxkrRwkbEbjGFgqDYiGIV0IiNCgEVIYorhEAkhbBnizu8/bubZGfZmbnNY/g7CezO4b7733t/Rj5/rI3tnS56vHH86vLtfbOz9fr8fBHj0cdzp96v8C3K8URGDJbBMlgGy2AZLINV6C/w9cn0lT1neCQ8HoNVcrB45N8OHds6NToYxTJYFVKsP8/untt5MG+8DFYlwEKrftw5fWLX6Mrrtxe3z4DXYv3w9901gxWRMlg9RQD69eLm6uQ1BQvU8garuHgZrC7x56XzH2pHll89v7xjP2D9XXi0MHGy8fLB2Yk3ViyDtcGIo1o98Ondtpm1L43fY3NrU0tPx7cs3b9+YbJhsAzWpiKJjyQIUngvgzUMYOX3LAZk3kGK5Gjz3gtYeT/yQioWPwoq9e/e4q3xhyRBIq4rD7ysWCUHC2WKSGnEyBssg7Xucqga9hgx9f0VfJcbSg4WtaskWM11ChD5gWXFKglY2hNMFRoULODDafXLbxms0ioWuLATxGmhTPgtUFOkSJr90i2DVVqw0CpgUsg0Unpgb8g9ToXuFfaUENEq9Em1Sldo7Ni8W7E22IRGn8Ao7+EZp8ICgwUWpDCNpDlVLJDiU73WFTXv8R7+j8Eahunc3MFqU0poXlPw1L0hK3qPFhoiiPFOfFjvX8xgFVix0JVYREgNwwAQiKSa0NwTK14Gq9pgNa/VjGs1q3MkCWYUy2BVswndAisUPAGrlSib6+z+NOq6opPqLW4GLO8KCwaWohNToVp7EIwWXiNI5aFYxdUtp8I2qbBzuQGAIihOhVasLoqlZlzLoVSw2A9GyNqY98rvCivnsTqDxc8BQFpcQL24R49XaKsn7jSrBpZTYTIVtpJaQASM9BCYNnbapMJKKpbBaoHV2scJWPFTPfJF1HEa3RWm9oz2WFVKhUGx0CFVLJDSxJcpKESds3l3gTSjWOKx8FJq0qPH0r/NeCyDZcXSjp4qlu4K4+F61S2dxMrDYxULL89jdam8p84VApbOQbiOZcVaB1ipoZrUCE2y8u7pBpcbiHqIPnYJU71CDL4Vy2C1K5CKYum0gh6piJPvrKNwBstgdUmFQMA9WhSldqXX+hK2PFo6nm4oya4wmnfUCDi0vRNb0Rmw+jRBarAKOfMeR2JS7+nTvWHqYGqcedehGqdCn9IZive8l+NcoV9jZLCsWAbLYBksg2WwhhEsz7wbLCuWwTJYgwKrHLvC/+IqLfAqTqLbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMjo0OC0wNTowMEQewWIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ORy5zdmdei/mkAAAAAElFTkSuQmCC"},"151":{"admin":"Mozambique","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGvklEQVR42u2dXWgcVRiGp/WiIbFpbYRAWy9K3DYhZmlxi8TQEpcibiP+1GLToMRIDFrXP0JoYy5MQVqxYIpGoWDBn1asUUkIQRShWvpzUSHWpvUnTWgjdWFbUdpYMJoo7LsXXzmZ6ZmZs9nZ3ffmZZg5e3Yz5+n3fuc7Z6ZWz1R4dMPgLRXPTLa2WfNfan16K5VqQE9uXPJpZPSDf+/YHG2saW6ONXfwplAN6A/XFm+IrIYOLF+ye83bDUcaJh6Y4q2hGgMLihjW/lPdxYYhWiTVGFhSaZFU02DNlN4d2QL9uKFiYP3XtEiqObBokdRMgyXxYpGCeiOwbADSgQwWyQyM6jp51wHuTCT858Z2WCRvKNUEWApktEiqPVg65qgxi6RFEqwbIeU2D0sdyzo+Yxgjli8rtDvPIgVnhaZVWCQyMFokI5ZhsOQsknV8guVaUUR1Ro11/MIGy9ko/Sf7tMgCnRW6zcB8ZGwsUtAKDWBk19vRb8p/rBllHd+tBi6dyHjy7pzOa1tkZf3uZHeUmitqeSt+Ore/kNy6b8Ul/9gdn1i0YE0Ms8iJyr6x/njy0uS1ydulJj5MbE5Mquft1G37YKr+X5Gt+2OZjT1nd678NVx3dfFguKTzlzNru6qL/JcnZJECeOGPH28c33/+tHosz6jn7T5r11LebufvVfvR+VV2bdxe9XPs9qqOWmZXCcfaYutDf09bF363rItj8Zbboh4joqMiza/dG2lc+3Dos9C3ocPUoKllYGVQ1K4uf/Tm8bJHARaObXFR+sTa4q6Xi9etiMEEndu/M11SHipd2XJTtOi8dZe11LqVGiB1UdhUWgKCLxMLk+FlmM1dqf2urngPwMJx2hDtsrTUMRCJjxX1LB1Hb/pwA8EHuxbMKzvC4QweWM4ztZQCHcAECNAFYsbp+tjzq/8AUlLTibyAAJ+V/QCLdJRyW3oVZ9AbY1jgIpazOkeFbXse32ntV8GCIQIawCQ/NQtSvmtptMhggyUGTAUCiggBrf5n1RuWdXCg5/553RIsnCmvL20r2SsjHIZfJ5fyprBUWmQwwFJSbCTUEgvonc/d/MjCJmCBmSAAknghhkmY8CkgpTP70zJERxyl+XKw5xas1MCg/oQCAfTcu63nlp8ACvIDQAdXocOvv9dfNjRV/fOB+R2ASYKIM2iD9vJbpCaiHbXLFqnHs6T/npQWmYWIhVkeEm3M5oCCRATHiEbACC3Hr5wcWfXqvs54e/GLwE6279/1/l9Vn6MNVM4c7RT9pythRlcwYZHqPxhqxmeFiBConks7AzoY+MNPDW1vGjn11akdww+98kV3TfdliSA28UlF3RZVbHzWDi98L+xVziXNLo3TIrNRbhDmCPOSUQpRp29b3/ZPjvZ2vRV7rQJIod6K8wAOMAGsZ3sfO9BSBbBwHnjJ+IT5I743gxumlbVIJvhZmxUifiCWwNqACxZVJFhADXgBIECGq4htWEs6Fj+06YkdwDQdn/T3PvjZK5ZSGCIjVrbrWCKGyRU6IIVohPOya7QBWBIvCR+Wll3MB33vCcM8l1EqG2DZDDBmVRIpwDTSOfz9YBPiEHCR8QxIwQRxVUavg1Wb7nnSH1icFeaiFWK2KIcEuKjoyE0mMkrJjSJQIAW8XIDlLYaljml5AQJLrVwjSsn0HHhJsHAs29gpDBR4wZ7Mbn1m5T1wYNkNCc4g6T677kRv7CoSeRxDMdcDWDiWtSso5oCok6FPVJXS+xq4VpivYCF+yIHB8OOq3HFlV4XCTgedltgxgf7lt3hDCpanLkBRA2SF6h4pFE7tCptqFQrHznih0ID+02uIngoHtLyArhXqDCQWfCQWgOy6RyeUxBnpv6yEqUVRb1kUIisXZ3J+P5aMPdc9LqHztLRYLFL7Scc57V+Cl4Q773k3tSM+ODvr8Usy/XtM9a8FljTBWXaye4o3WGBGn3Jl0K69+pSON1Wfupmblpn4Xm/t5+YXugDL1vJ8P9XjDJZE6r+ZmWPJe6nB12w8Ca2tchvP9Au/Haq8j5orGiSwRMTCRj8k+M47t/JD/fylwbxLVsZf9aFti2pqn083utDU8vNmUV9vyVK25ejsLM2/eFOoYGXshZGFaXmFg6k198ZnyvKouR+xfPx/O1LV+js1X6OpZfjtozZtZDmUw8OI5cvy5CI0s6jCBstQviUfIOMtJlima+W8xQUNlrfEXHk9JC2PaiBiYZcVCwdUEzmWuErLoxpbK4TlsVZONWaFhWx5/CeUkYjFWjnVcMSi5VGNgcVZHtUwWLQ8qtlM1GLhgJoJ/R/N//S6HFnXvgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjM6MzAtMDU6MDCS9u0iAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NT1ouc3ZnDad5MgAAAABJRU5ErkJggg=="},"152":{"admin":"Mauritania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADqElEQVR42u2cP2gUQRSHrwgKCoZoQDQSiRIiKApRELQX4QpRLCIWBm0CgqLRQkGwCIiCBjVwopImJGgjpIgg2BmRQBpLU9iJAREs7LX4NU+GDZu7mz3fzNd8xd3czM7cb9+febNbq3VfHD18BMI2kyWACAsiLIiwWAiIsCDCgggLQoQFERZEWBAirKo58PTqngOTIquBsNrGG88nj/Z9F1kNhNU2vut73dgyLLIaCKsNHPpwbWp/ffXX+66uaVGfsDIIqyWOPXvwsP/snw2L32rDIg4RYZUKyWWBxPATuT8rrPlzc7PdS7ZNyB2XLv88eAxhZTr549O39g2dl3Sss7O0krIsaq/e1DPCIoqqv/o6c7rnSpGMylA9EIEhrH/Yc2bs86Evip/WtlWh3dKv1AMr6UxYilqq2aI88fvOi8GtZSQ1unhvYvdqNTbVWdzmKx7SXx57LMmljMWq5no0irO4zcuF6s9W8h97rDDeKgrn1bKaLY9qrGN2wrp9/fH4zoX72xq925djb0CsLLyd3XhT0rFZns0i9a1axnbQmrVWAGFFWdzYRRU5HVkmjRhGNpKRvlXLkUcT9YGp2AWl2DdV1sKKXVTRKOWdjlrGsyW2oISwIrpCOaB40UZzmVe8jQabRuAKo9fsVFTJYV9NM9Wsq0lcshNWuLeUdtnERntVbm1kJywbbVSZ6neKdsvD5XEdX67h46c3TzbP2eV2dh+X4KmTd1f2jthbSLN2VjLyteiN8ZczvT+sQ1zun9+16UIap9E1C83IztFZPuhRWEVVPAnOe6ge3jYuoyuPwgodovs72+yfhTPS1qjLcxNpHBe29LXfs/ZBHWdbDN6FpW3MMBaxQb1swP95r+uqiqyUjR0dH3H2G5eoQufrVGf506qx648Ia915YkidQZBbqd6GaUSNbs9NFNF7IpKIsJSiF4XzReGwanDxHI16VjZX5LJDahaJnJpP6XmbMvYgtGSyEBKBZFreqqmlfqUeFDk1dyVJFanSq6+t908N/2BZDkVCkp3kIuoTfauWrY+YXv0gwad0dN+Xd0Cdoq4w2VJ62k85t/60YAxm8QRiDqeaymdk8djZzBRhRbdhzQXXrYhJI2b30racXwci+6ENiNalph505lM9Z/0GQB4Gt3viys5UbbTZn33xWpgt6le8tQFhNRmrsQ4ICyIsiLAgRFgQYUGEBSHCgggLIiwIERZEWBBhQYiwIMKCCAtChAURFkRYECIsWCn/AnjY6uinE3twAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMzo0NS0wNTowMMoLy5wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01SVC5zdmcN39YWAAAAAElFTkSuQmCC"},"155":{"admin":"Malawi","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGTklEQVR42u2bbWiVZRjHDxTVhwpc0D4kYUGnonKm5pxU9GKnmqJhs0lqm1YOp2HaC5NSlxAdNRBEy5fUdLWZFS0Uv/UyEyqYhYpWjhAtVjgrKAiCrLBfH/5wdcasnbOz5/x54M/NdV/nvm+e68d13c/9PCf12SdDytJpq7V/NeVbYDVYVoNlNVi+EVaDZTVYVoNltRosq8GyGiyr1WBZDZbVYFmtBstqsAabft5y6bbrKmgfujbdXnUUC4rFd8lg9QkjBeh4/bwDTcfppf3FsHHlE1aiJ15fOHdpSj3jCIqmwSohVQjA5duaxQef34Gl56mNv7WcPFw9OnVXD/avZ04bOucUioUR8ORX2BnNuS1VmgXu6MjqBQ/OJfcQ/lMd22fvbKP93dRsw5pf1Qekjr0we+zjaSz04pkLMnwMVsKVMIOIwhQzEwBFpfDhQ1t7NWMppviXGmSpUih5qoSc8EcgyEC6lwIIBQtLrl/RBqYIGQrKBmvQg0WRIsxgobkKFDRv4anbds12sTdCFiFGWUkp7L1S99593gUXPZckrW2/5PRl89c2znhg9kraszpv6bq9fe+Jlm/aptVVpVdcf2RJ98O756zGZ+H3mYkTrqSNJxYU/8aaUcurVqlFfehlBG0zCxZmZ/y4wuRFIZWal8yr9sLaa2pvWD9lQ/369vSP6dNXn5vZlzmc+Sq7IDs/uxpLw7aGHQ3vVBwYPqbiU3qxly8qf7p8eV9mUX9GqOyo/KCyi5HpXbVt3bEXs9ixsCpWmNT7nyiwFCANNoEktDHw6q+jARzhxx/FQq/664wKLjPqSuIK8TdYRX01jW4qbxqlMBE27AQSLOjV39LbOuzM1bPhzPXnG/9+0Ysnv9JxdF5AZHZgopcVAl/fc6TBKuilAdM2hY+gan7SfIM/nr3D1DtkjKCIaI5UuPFkDfjTTlTeShJYmqseWjbrivojhIrQqkXDT5jPFqZcF6PpqoCG2RUyLKxW85nBKjqwdKdFiSHMFCPsmhUIbX8hpRez69oUJlYVQTdYRZ2raGtu0JKkIew8/8yVD7AYWedidi15FOW4coNV1E+F5AxCpRkr37lKL2aJz5isit5cz6QGa4AvMkFz9dJs8x0xYJohNHg8zeUbLGaJBVEPIFitburjs6rBGrBSqLmKrEDAFDLNWPkrgrEgxmzKClX/2/GswSpQ+Yuh0tPwwuyucoGlO60IVsTLYA3wRbHTk3E9H9cts2aCQoKlQMdSGE/2XQqL9IBU9y6EKmaCwuyxeNbTzNr7CyIfNxRdKWTbiypMunlXsPDJN1h6mqVr0Lyl5216Im+wijRv6XED2Su+xinMOVbMWKxKz9t83DAIPpKJp+26ZS7MaZaeYClSemyrn/HoVw8Gq+gKom6TNWz68iTuZvL9rjC+H1T049beYBXdpd88LapZk1n0AxihsfQoXvT+n68bmDGWP2aML5ri5z0uhUUNlmYv3Rrrl6IxW/TX91jxwFZXpZ/xRE+DNQjKYvyMTh/ytTDlKkPxgCDXm8dYjtVH59WVsMKEPAOWAljxUDTmCdr6YljLVt8/Yom7KB2NN5ha+LDod1oJLIJcZfeMHVN2OqnKHxlQLLoDy4yfuSuz55/238Vo0snGiZMfwz7iy8m/3Dhf2yg+2tZfMZramZHZ6dX1JFVTo0ZsWX/TyOE/vbJpxO+086H5Hj/q+MrWJZmLH8nsGfLofixrd3d2r/sw+/PHc1cOvXXfa5fftrN1xeHFbfPu3/T2vqkT6K0r39VY/xY+2J/Z2HHOs93YGe2Jre9VPvkHvXhOv+rdzTM+YoRJXW++fN9LOjI+9I7buD198wrGwbP3u1SY+3a2s/TFP1XIYA+UEkhCC1K0l03Ze6h5HdBsrjswbMt0hUyxACYUS0SQEZgLHOOM/KoU7nlJgEX2ok12IaMQ8q37D9756vsAAWoocChSUfEBL0ZgNM2IwESu0pUYrEQpYaYMkWkiZJqNNEvFvBUx1SLILIxW+M2AwRoAJcxgQeDJJYoIpY1eRUpLp8LECPQycqnBZLBylkvdA5GTsCtYWOjVLFg6Zc5g9QNqZB3aaNwtlXJmMliD5qDEYFmtBstqsKwGy2o1WFaDZTVYVqvBshosq8GyWg2W1WBZDZbVarCs+de/AGEOk6TV0Z+gAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyNDozNy0wNTowMLWNyNUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01XSS5zdmfFYhSWAAAAAElFTkSuQmCC"},"156":{"admin":"Malaysia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGYUlEQVR42u1dbWiVVRw/I7BAhFhbLzOLnLoyL2wNc2QzDCXMTdOsNbpyW9Zw+XIzzJhGtRAHlQtnkMSMi70w0VIZviSIzmCJzebchxDcbOBtH1pULEuItuD+7of/5ew8nHPPee597vp/+XE59zz/59zn/Pj9386zCfHK3JVfHWD0xqLm8rltbZ3x4pdCkSvHp2+Z9qw39q8quXX63TZzdCwEzQ6dI5g09sRytXlBpp1qPh3JHrHEQ9sOd4wzQnFCKFYuoinJvFFkhkZFjy+e1Fa8aCDy2NuTIz9H+9YfenVp41BdY/0Pm29ZO3nJsRdLt/XNfLcq76N6JQWzioWjlU9/fnPPj1vbd+wbrtw+s6mKETj0d8OGl2/I4z4SCzT6ZM3ug4tGBo+d3lKw66/y7kcnfTHWc2mPuA+Ikct3nZxR1NAV/np3ydTWMy3/Pjl14W+r1zcVYjuDQKx7vqy+6fj9g7OHCq5/ONYz2jP6HaM3Crf6BCrsvP391urrw5u6npqyWKbRqdf2Lw/Ng1ZVPFBT33weWgUM7VpxR8v3ICU+e6sIE2uCEwvO7lBBbPnDcUomYK84mn/vT8+EGyo2p+qQhsuDZZVjlanmB+FArKs7r4z8Moc+vn9647F4ns5nOmI6R+daV+uRx03v64xY2EgVpaBPyfjJNHJKzAeBoG0YgZK9ceKtteHtVCkjd0brNpT4QSwE7+e+XRauXXWtbEV39QXGwWkL3nukSPWtA2Jhg1UqZUopmRaw0H64be/8pfgWVMMI5sCxJqnmg7vUzwrd5la5i1bEgnIgMJdjKTg+U5UCjZA5Jp1gYhw02lHRHF7ZBxoh2IeSgcRJVSOpQ3INXG7IGWIl6KLSKrhFm41EAaLjnc/mlQ+DZCDTtZHOM/m1oBSSg/OXjxyccRtGkEsiD0V26UqxYKe7cM2sjR8jwbZBOAu319rYVFnTsamaI2weNOInmVigglVFKnEV6AVFRElCLlgAMQdInaZ/WaF3kGuKKmtu7xLsrDCx5XBYshOEiniXCWQsmL2wLFYH5wUVBEKlQBSZTDLi7rgWBVhqx3RVXG7IArEQzciVKugKjY1MVRCWEUvBwalUSkbMpLqFGCu99dDfqyJWJhUlt9TLiljyltsQS65UQWlkXfQmFiiFFSYdonWDCMG7fYzlNhLK7hq87Yj0IiG4FVmxQIKUrp+2VtHqFMJ25HqmxMJVCOoRyI9TmOWsMEBZoaQo0Cd5a5GdmdauQCwgbe+AZPoxFi1VwALUy8Yhglhna2aVlVYGs15lf5AmQKcbVGE1IiSbmAbhPPSGNqdBXBmpPkGxkg0fR6ckuPJuWoW3IhZcjBxpYWvlHp++eiGne3NrU23tapAMnymBoJdoeOMzdA6UAhFpydQ+eFf1CvU7aPp9uvS6fv71FjPaK8SWq6pZ2HJTm/SkA6UaHCIqWyA07gv60hyQdg9TQng+3ZBbpxsQUaEmLsdbKcXStBDkoH1AjECTaDIBVeNjMxPq2AxKA7JbRECNb00PzFAa0dCb9hPlmdBRP043pHeCVHXG0nRObt3X8QlS2tGT1Qs9RMRA45BMohooIjsymkVm5gAglxvcZYUW+RSUA1GR6jgyzfXgwuDs8BlqZFoP45cpJsixGR1SQp8QyCPcRgYHVUPxk5IM4bmWO8vgCxcgVteUynVPjCG1ZvRG4eOrXYpxREtQo5SiZYBfAkPw3v/BxXUDz9OUnlGFQkkC723W2H6tWMf7TUM/7HNWGOQmtI+6YmNfvlb1QqzOq7NkDhPLmFiRZXP6o/nAFzaGOqNRRhk3nZ7f+Hre1ZrWJXsW/HEuFv80RPH39lje3otA+VvVTLcYtPUIVTNS9Za+6lvVtd7W9O2rbKr+doDOSnTasfZ/3iPXX5ZPMyt0+wO86ZLdB5QeRbK7ScGktc5TEgMXSqsebGFkdIviRsel53qrGRndouD8hdGXrJAfAaMvxPrz15MnvilmZHSLQk7dGRntUeiXDNJLyN3aN62K2ZQSvCtkptUv/bvr3Dcz69F5PqqrWLEY/VEsjgYYfYmxOH9h5HIDY+4Qi2vEjL5U3rmrxehLrzBbPXNX9u3/aUcw15nrv1fw+ySMvpzH8j5PqH/OUOcEoyv7qrvoWzadb3+S09Xz1F8/HfHv+aj2hbNCRi43MDKxGP/n+B+uxQvMVWFAMwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjQ6NTItMDU6MDAh2u71AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NWVMuc3Zn1Tj6xQAAAABJRU5ErkJggg=="},"157":{"admin":"Namibia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAH5ElEQVR42u2dbWgcRRjHF6XQYrURgjS+FCU0WgRT1KiJLzUGS2tQSWNpLFxCUwVtUZs2JvFLTH2tBmnOi9YGi22MWtqqWBTsQRG1rdKUSqutZzSIwURCMaZaS0MhCve/D0+Ym83s7czs7N58eTj2dmf3bv/3e/7zzOyc4xSWLezoslF/vHBG2UvtS+S2hlhyW21FIpa8rj/+c2zyncnyyUYaz17aX/79Db921m59InF8oKD85hXHn7rkghtbMq+9Rs6xjr3B0YiXjSw+0LmpwenY+PEPqfjoupGfqJjOr/xj2amLxuv2pPatGugsL1wxOyMIcUl5FJ8Tll+k3PMGdW0qIviU+H1Xw+FrWT5NxAePDp397auGhtbuHJkkl1g6v/qw3Gb369T5Kdz5hJiFTwrIxCVWlH67UfVPPD6NfT3+0elSlk/DXza/9UqPXKF4FpYVQViiZz65S0qd4NItO5YEJqdmEf+U4RMVExVN/hCrqLpqS3M7Yj47Nv98Ovnn/IHFn2eRVLDEUiesOWN3HWrbyHt3XVNb75L5r3W/uqi81SY4+mPwwKdAaRSYsOoa1lQ8dMfj7zd3V29iBbfvmr764i3fvf3J+Nxali6QHVowk1uyWkY76N89cN+GRR88eyT1Y9vI8xL8k4LygdD+5NocFV9udXvjeyt3jTUeXD5zAGSCpKp2x1rqz08mjzY57+JdbMG72BPb0YLlUxY+8W68rIJCbpJNHwXpn9qdONdXqYRYpUU1Tz82CIn899mxKieT+EAjEAuRpkXsiaPQQvTElCOfVCcv32dBARaDRfgUjiwbThkGAiHZ7ezoWX39GRAIcrllYvmJR29HXFB2//Dak+AWTZGstVdh+XXafHc+YcglR/+kPy2mt6PrAD6xn8iXsCALSiDIiPok7AMmQTRgEiK24F3IDkdRkmEftI/WwsWqwPyT/3Y418PyicZk8dA/R15wZFl1NvFBKBAEtrtH7AmSscmRZ+fNKS7QK/Hsn/TYah/ycucTyiItVx/a27Om8KZtvTVd0jwWGAO6QBygF8QBw04jlRTdjqPQAlpDy5H1T4HWx0XeFeHTrZfvaVqbcgreiFfdmYlyy57gCjwTZRhPRqzUaG+RJtPwzi/I4p+C8kzCkfKJHZEcK/r3wJnEFD5RSfkRFi0fUCeE2hUYIy4pNqIFtEZ9GEimrmrvNeW58+nvv5I9Bwsk+CfVUiPtYwIgyyeIicsnWcKit5wKAoyhDsmrpBDRAlqj23FG/+VT/ykvM78g/XX74lOwxBLwTyDWNHySJSzaK4TIYL1pzZ1Nc7z0xyMWWkPLaDPYtOiZTxrFkZs0eXyaxj+pFhbrsXD7WY/lzie6D45CykNrXhOf3H7ilPlPrnw6EZ/Xe/ebBg22CPAJ1y+BT+qEheQ1XPLFvRe3sSUDdz7Rd6mXQmtBDVd74JPBNlzEP0ngkwphIW2BNLRkgLSF17wSA33NO4omRz1TfhX6J42Vca188ios97SCEihEwNbc8Zr1YeAQradT/4TXtAXavroxRAl8MqdWrp9PcomFogM7+4qOFdJ+HKQDcSBSMdH9WV/FO5d/PsVn75z37X4hPuU2fqdxIHkaPonUn0wQlsjsBjp5hlbnaVWdTphRN7shx/qTuDj0Oypz+KRHWHQ+FmiEmwpLTgWE/iOtjSFRyq1U0fqTCJ+mTPkN1icJ8Gk09vI3W0d4fGp/8PDc7euV80lcWH467ZAFnayH1ugMUkT2LDjK/0Q/r3z6paRyc/09Oj2Qn7kGxvGJJywV6wi4V+3ZicsK+cSMdmXhk5Ezx0PDJz2pUKSUKteGe+XT4PalTz5y2qD+nWv1HFdrNJ9MEJZyPonUx00oDQjUn9z5tHnVsdIPZxjBp+gJC3ziLdmjiU/+pUkkFUo+hV1Y4v4Jv3VpfFJMKVxnaPxTbsIy+UliaXySKxGvVS6m/oQrZz9R/8zRidS5pZWfznqmJgRiChexItW/S8cI8ilcwqpYv7p1W0Krf1KwaCLbv4sgn8wXlrh/4tbHlT3p6znxRaN/F3ZhKeSTzsEZpn+XF3wyTVjg04bnuvYnZ3ngk4q5A5KGa0T8UwT55F9YsvqM7v07VHEyfFK9vookSuU1n4IiFq0/ZfjEqY9P459MmJ/JRMunwITl7p+4S9qrGzbxfazlUwDCysKn3Pp35jyYQM4uwqcrh3bE6hbmqaRYYfnxT/TYgPnkbu29Ti9OR8zWsnzSSqwc+WT8/Cf071DTt3wKQFg58sng5TEsnwITlgif8KyIVj75FqLlk7Qo7quwp0I+qXigSuCRTjyN486nh/cmh15stXKRTCxpfDLmkXN3/4QIPhUv67siNscKRbKwKJ/YkmYWPpn2ZDDHP2FlPeufAig3eOCTwf+PYPlkhLBE/JP+v1T0syf8kzufrH9SEt39E7aEi0+0f4f1idn+3Y4FqdeTV9n+ncKY8U+MpLLwSVZ9XO46nLZ/Z2YMcf2J4ZOtPxknLOX+Se7UYcInnn/Ckvbo3+XdlBUTYhb/ZOYqmgL+CdH6JyOihFkAWv7LRYRP1j+ZJ6wQ9u8on2z9KVrCUvE8sUf/ZG9hPhFLfMCYcXWWT/knLD9FAc7KKiyf2JKB9U82FQo9xmn5ZIUlm1ge+3e2/hRmYWn8yzIUYC2f8ptYkgRn+WSFJXnJHkwA5PEJSx5aPkU1/g8yoj2cYhAXrwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjU6MDYtMDU6MDByt6+8AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9OQU0uc3Zn1IVJTQAAAABJRU5ErkJggg=="},"159":{"admin":"Niger","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABWEAIAAADmonjmAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACpUlEQVR42u3bPWsUQRjA8RVBlDOFtl4rgvkAdlpYCFpEBBsFCxVF0mpstPED+IKtECzEyph8CDUSQbBQwcLgSxSUKAQFETnBx+KW4y6X3N7eZufX/Lnb3ZuZnfszr89ki4vHjm7ZjFgsM1WAxEJiIbFUBBILiYXEQiQWEguJhUgsJBYSC5FYSCwkFiKxkFhILERiIbGQWIjEQmIhsRCrKtbxiUOb7lXo+eq86bBTixTKrJ+OvLJcIXBwdvuD6/2+nWJ9Wbrx6tQtxGKZtVZa31tLKfPPk5Wby031UCyJtYpwvxsfDrze8WvqzeTCyWBcibukJFZfGv2YeXzhQTMa80/bLs7su/ru3InnO/d3Mu7Gk/ErkhErx9CiXaNV5lNdBrCRws+9T3fPNdRqlnL7tLww/fbSdLFLBqFmpJxyG5alqdTXO7dfnD0y7FWoyCVNvZITK9dKlcLIkVg1H0v9H0WVKFbkGLkTq4bd3+drVz4enBjVRlDknk63mKXTVlVhVzGdOWMSYuWG6iNllIRYNekEY4WpChEQUZIUOsSaixXbL+/nTjea96vQYkVJolTE2sCM3b3yZ4K9Z4jEqkmLVTWxQndi6Qp1hcSq7OC9baPa4N1yg+UGYnVnLEtaICVWvbZ0/uVoS8cm9FBmgqkFACYXNvNt+93zl1+WKVbkKGwmvUC/IQ/VBfolp1e0JX3Fua8xdlRossMUucMUazic7jAFsQY5/hVr5e0Hv+KK41/EKvjAapBGxMJRiHVm17P5qeuIxTLb+ujh4T2TiMUyGxubnR0fjy/xuZ3drvfD3r9d313l2SjlydZXCMTeJBYSC4mFxFIRSCwkFhJLRSCxkFhILERiIbGQWIjEQmIhsRCJhcRCYiESC4mFxEIkFhILiYU4EP8CP3QcVcisNdYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI1OjMyLTA1OjAwCHeMTAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkVSLnN2Z62kCwgAAAAASUVORK5CYII="},"161":{"admin":"Nigeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA10lEQVR42u3YMQ4BQQCG0RmlOIRaRytKrdCKS+jcYJUuwQVUIipRSZRqN9BpxyVmNpnNezeY7JedyR9DaJrVMlTuOF7ctpf1a3Sd/eo9xWnynt/7m+/5eRikT9qlab1n6QUQFsJqSRzGfXz4kMLKrPa3iLD8sfDGQlh08nIXlstdWAgLYYGwEBbCAmF1nR2LIuxYICyEhbBAWAgLYZGZHYsi7FggLISFsEBYCAthkZkdiyLsWCAshIWwQFgIC2GRmR2LIuxYICyEhbBAWAgLYZGZHQuERbv+NLs9EzrgD8YAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI2OjAxLTA1OjAwXCcqMQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkdBLnN2Z8csR1EAAAAASUVORK5CYII="},"162":{"admin":"Nicaragua","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADWUlEQVR42u2bW0gUYRSAFzIMkiIi6UWh8iEi8aWgjCJ9sSQRChSy8vKQDyXZiqVIRmVIpkhmRKktrqmlggZSVkhG6paUUWooiJel26IimaEgusGcfZhhHO1iZM338jGcf3YYZj/O+f8z/1gslsTo1iYIF5o8AohYELEgYvEgIGJBxIKIBSFiQcSCiAUhYkHEgogFIWJBxIKIBSFiQcSCJqN/S/qTVzUQLiwtg8Ejtskx+ENcNxQ44veP3cNfumeL2+2ecHdADR1T8a7twpln47GOiumro8P1K6YrRk7ejVLHPWfyxHRELK1MZRMn3o15pFn/peFRlTqikUlGNw81lEwgGWIZUp+TNOcoGnkEUsU1wikKohdiebKUKGWYdZRI8+pu7+q1QkN1JIcJTa+XqcWS/GSYpRQO5zqv2wKuuEq9UjKEEjEspuriiFjmzFWaEqY/R4l35thnUodrI/Izi2OEEpn7V8y6zCqWumwZnOP0cmbW+hRmtF52Begpo3PnQjPnLVOLZfTHS7y6uSfakZQT2mObqi/eYI9vtQolIqNzXwGxTDphn6WcKaPtAz29tiPn4hqXXAvPSnBsdYwKs1+2dQ/EiVjCTj9nYl3fLCVPrmziibzFzNN2fUb5dMm17MG23MrKewdWpt4s3RS8PMt+v31Lnhwfu1V4NMzXGmSPTJw+dbbGmh5a3Nz2reLM11Wjw00fZhHLaB6GWP8t9Ws3Ja9Uva1zJnTFJlzICdklGiX7F9nCItLcZXl7+hMOZgXt3S08lJG9M2rf4agCe8bGijWNgQVT6sykabEiljlf2kik/3zvG3tkyvs7O0p8T1sfBjXbL+5/3tIdc+Pj65Cuoqe2vqVd4R3Jg9VdjyUiZTFtvOl4t3dSUvnk7XK5gtH1EctMMy3pYCkFS4qgrPXkWE31KlI/qv4V7QYapB3q930LJYHn1ZAsDmiQmrogKmJ5VPg1vdT5j1c6iKVvQKiL4zxyqGZRmixl4pUgYs2XdXw+v8hvmWWrjHrDjMKfEBGxoKYLperRq5sITM/nEYsNx4YbefWbeiWyGDYoL3ryMQX8Mx9T8KES5LtCiFgQsXgQELEgYkHEghCxIGJBxIIQsSBiQcSCELEgYkHEghCxIGJBxILw9/kdY+Ag/wUoK5oAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI2OjUzLTA1OjAwg1g1fAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTklDLnN2Z4fmdUEAAAAASUVORK5CYII="},"164":{"admin":"Netherlands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJUlEQVR42u3aTa7BUACG4VPbQIIJElZgjG0ZYy1txMjEMmzAQhh0JEJKD9p6vsEzuJGmPXnjJ7khTTvt0ZCMa3AEFBaFRWE5CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoX12Cx0k3Hy/esUeX3+mlh32IxzLhTWeZrN9isyruFi9oEJy4RlwjJhmQnLhGXCMhOWCcuEZSYsE5YJy0xYJiwTlpmwTFgmLDNhWWXD2rWOk9OBjGsYJMtke2O/t5hv1vd/f9VY16my1XnG/E7uLX/P7z3jwxuqi8+PtdkWCetXBofr2YXF2igsCsu3MWFRWA6CwqKwKCwHwSaG5feasEhh/avlPwHKX0FY9I5FYVFYDoLCorAoLP/oQu9YrINXxJp2iizafvMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI3OjEzLTA1OjAw6NBQuAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkxELnN2Z2ULOOIAAAAASUVORK5CYII="},"165":{"admin":"Norway","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADa0lEQVR42u2dP2hUMRzHA+rQDkIHLTg5WE97p54KQtEOXeqkg6ODq6f4B6UognQQnFQoTpbaQYodrJ0qLhWu2KEoCEJBlErtYqlSOZWCiH8q5YZ7Nc27JC95l6efDF8euSQvfz4kv/x5OVGp5Nt3FcLUz/t2DxUHf2wpby0/WZbc5MSL/W+PNT/eWzxXFmL7p5M3hNiRK5XUuhKmcOpI/7VHiz2V60tn5DS/Hb356tZiZSjXsbM75JoJX8W/AlZ9pAzAet8+XHgAHIDlGix6LMCqNxTmBkobAAuwPPVYhjYWQyFg6Rnv9FiAZQlWdcgDLMBKCawaUoAFWM57LNaxAAuwAAsbC8XGYuUdsFYrPRZgMRSiGO+ABViABVihgcUmNGB52Stk5R2wMN4BC7BQwOI8FmAxKwQshkIUsGqLDoAFWAyFgMUmNOoeLG9zqICGQlUZk5TdLm41Vjr5SZCa+D4+Kkaf1dF7IyMPL6/xrONjGl7S35vnx+a/pHMe6+fd6bbp2wY5jM25s3oLPz+SiuXMOh93N+Bcuf8ELN0eCwdYXu5uwDkD63Xr7PGFE9nSmfzc14/F+4fGrjzvamreM3X2qT5YbVcPT/YOTN15eWG2/822d50Lv7JYA43Var3F155oXX+w71KTvm7a2PGhZ840VvIU5Fgt5w/MXBzXu7WhpuuW8r2nJ0zz4CrPjQ3vI1bUP/os5HlTqLr2LM8ULHU6vvXvI4cmZQy5Farlkmfllg2Dxq+ZUQ9UAWABFgpYKGBREagHsNKfx7lNP4kllJUZcQZVtQ6R3Cc+pE6YeB/zdayVAkfXsVRl1/k1/lknHVd1YvesX147H6FaRY36JA8T72+afnTl3XQTWl5518mbKj929eC2TvTzY/reJO3OJjSOTWibTWiz81g4wOJ0Q9hgKc8Z6p9FNA1jGFfvBKmboXDVCdIkZTRVV+/y0S5W7SgMzlPLYVThdfw13tXgr3Ts6sGVmraLj/PyCfLDX56oP7HnQ3u+K3RlvPP5F2BxPxZgcXcDYAEW1xgBFnc3ABZgAVYQGhmMsLEAC+MdZSjEeAcseizA4qpIlP8rBCzAwsYCLGwsFLAAC7CwsQCLHguwAAuwAItNaMACLMBiKAQswEpjVsgCKWDRYwEWYAFW9v5WTr4gmr3CRuofeRNcTTFrboUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI4OjM1LTA1OjAwOC45cgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTk9SLnN2Zww/KG4AAAAASUVORK5CYII="},"166":{"admin":"Nepal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAFIAAABkEAIAAADK/Sw/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAISUlEQVR42u1db2hXVRgeBlEQRShZM7PaEpuJWMMkNCMhbJpmYrRgw5kfSlGrqQQzxWYLktRtEVhbSGqsRIzyD2pF5JbUKAhGmnNu6VJnOtOazaYu8Pnywukcz/1z7j3n3PfLy4+zc8+9u8/vec9z3vc955fz1ITFa7ZPby7/5VLXt/3d/Yf6j7H11ebkFI+7tfaTYQ9Nv7Jh6RtT6s//8N2ZjnNtvTP41XgLNrXg+u5t3484Op5fkOdgw96SO6lrfXf50ZqdjVtaTxw78OdOflnegk3tmMWlzQ0VDQe/vL51MDt5r8AeXFa48O2Xb1oydvbaO2k7WuDkf5rz69RTffz6nAd7+YC8+rI9Vf/eO7R0dH7lg4+9OUrsk18ws/ejjTVlny77uZa57jDYK/PzBs2u6Kge+NojjXun3bH2yRUlW0Z0LKwUuQ47rmru81tWfX5l31/tL/ILdZLZAPvwwYGXx98PW//+XfNmTQTXReBZ0HkFNlqaH72tZ1Ih+mB2lzl5FnQOgy3abZVDVkwrmLzqgWeXtsucPAs6T8CGbVs/pOnx3WpBxxE6T8CmFk5+1v6Cka9soFynnyHoOELnPNiU61TQySJ0c9dVNX09hp28w2CLXFcLOo7QpQx2dJiDCjpwnVOuDjNbXLzpCLrb5xctq69gQecw2KLlCJ2HYIPN6j46EToIOo7QOcnscIKOI3RGEiHJgx0uQseCzklmy7iuH6E7UXy6qWcNQ+sk2CLwLOgyAbYo6Ap7Rq9e/qE6QseCznmwg6Zcua7GYbCDCjpaQ5dNQecV2KKgk3GdRuiyI+g8BJuGcfQF3bbh31xoa8w02DqRr3DxsmRmdPpfcITOW2ZHj9D5J+gSAru19+bch584nvvM0EXbO/uK3ntpKVrwGVbsb47x2YzQGQT7t4tj5z63g0J7bmLdP1tbLvTtW/DjHrTjM9op/Lg2GSevE6GDoEOhtLuCTis2jjmv/Z68KZMPB+Vx3w1H6jqnnt1cc/emlotLWm5sfQs3Rjs+ox190I5r9fkd9AnVq3aZoEML6mpcjNBpMRs869o1r6Sy5RqvbNOoF2ZMoJy+XNL99LmZ+g+E/riWjqa+78m6OR2vb4zuD6i0VAs6tLgl6LSYjZf+d/5n87/qV79QfCHATsrjoBbXYhz1lwzP1jN813373jHh/H0SdAFSnHihgFxcXMHlwpFiJo7+cBgHY4ouHc9AdYCJJR8dkwo68b3RCJ2dhdIBBNrpRcsaaq/DZWeOVBV9MIC+CDhe9Okd2bygpTT6w2EcjCkq9j+GlR9Y/QV6Yr63J+Vqp6ALADac5KVBvzecysfMChgAOV590BlafxbH+OK96Bxvc8oVdTWOLb3gNukQYBXmTsyvcUGOcTAmYIZHoeNjWrEn5aoWdCWdK9ftLU5L0AUGG/ymCyc6v8Y1W+uMDB+TFqejCDrU1SQv6EIGVcw5bX0LlvuRck1G0EWKoJ3tf7f640PJw4ypxIZ0i36gRi3o6FEG5gRdJLDxujFnJ8NywBwlUmZ/DR3d+WZpbByOXZzLo1vMzVSNJ5MLNz2+uoaORuji2uUacyIE8o1GuaMADB6bToqk+7Wggk7G9bgidMazXlg4na/evH9HHhQ1vgTUIniCPmCwzQDbsCkinKBLtHiB5qawLoel/DCXyXbLmhB0DleqhHOhblXgBBV0aidvEdh0+y5OZWFLrSxEox+hswhsMBV31zk+l63MyiJ0Vm/ZlTkumlnC7MVWZmkNXWxgx7U2FTf1yMp+UeKPNSgs/iVqzbXLesr6yEYI+iThLBy7dcwW7yuDHKtPPk/Nq7pxOpcz5JnYJKB27Ay5t+tsmWJnyL0NqshYjoUHQ+7hXi927FaDjdg4LVY059iZ5QbB1ikrwNYenQoyuuEWAo0duxVg0+rP41tn9b56UtYTqUxaeSL9WlwdR6xRZ8itYDaAFKvKkbiklee0SAHt6IP+uBbjYEyWb9aBDRbSGwAq5K3Fv9I6UfShvKd/1c9z6wRcswl5bGADDNSlqDfqiX9VbwTU2T3KLE8BbIiveB8RMzfGl23v40VazGBDacOpwtniM5gHmM1t/wHkqFATn4HlmxFm46XL4DS3sU8cGS1q5R80rYIcua+Qh3TjdPtuWhbPYG4u9++o25Bggx+ick7G4r7xRt+y4NgjCTSsj6McpxHu+A0s0uItOs4Cy0OCDZghjmiQxLTFvXBfExsJ1HO565BrgQ0dDr2NAGeSAKuFG1w6ZFpcG/7UaRV3IdcCG1LIBoDVjI8u2ajFQUI+sVwLbEgh25hNN/8he2Zi96VPc3mkORtMMrFNV4fBSW7+82Muj6TGoYr9UONZcOwOr7PNnb+gng7cZXlgsMEnWbIyOwfoqFlu5+8SBAAbLbLYuCyCbTo2jiNqbSt3tJPlIbNe0MCJZr2ujg9oIdBwVhOeId0jdVxheQCwZYfTUqvOiYXMZwtnj9PP9hyQpWa5DZDHXJYkq1TBqSnqShXZ8bbIYbty/AaOx5BBnu4JpjFXqqhr0GSJUbSjj3g2atAaNBusnY7dYHUpGEm/CvrVpbgWfw1XXWo/y5OH3Ezd+NVZVtaTclddN44gaNDCI7dYnqRjT2FHCCAEa9VfC9skmOuOPYW9XtRRm14y2fZFSZflmfvFviwrdgY7QyxnsDPE8syBbafcSwZyZnaGIE9NjTO0QSHHUX9RIGdmOwA5BT4KywP8PCNb1x37NcBmq2/FX+aOy8bl2P8HbPxsASCHS5d9VrfoXBVuZFlL0GdL8n9UW537yn46Rp/lOdk8kRuc8OleOizPsfMFsQ1n1SzPyQ7DGPL/APUV7P6voM+yAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyOToxOS0wNTowMFJpP0UAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05QTC5zdmchb7HDAAAAAElFTkSuQmCC"},"168":{"admin":"New Zealand","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF0ElEQVR42u2cfUhdZRzHbayXkaxRNGVUwq101RouGG4IBRk20X/WtqYTtmaMYq5po0YJhasFc7VKzebsBay2lVQWTUqMXNFgL3YNIyVaZAtvRm83KJh4heB+zh+PnO7ZOT7n7d77++fL4Tnnnufl931+v+f3cm7OcGH5B7Vv/THy7qpj9TOXJ4pmVpnx4NCR3jO5K0erptt7cxbccNUTj2phznXfN9Ws3rGhsGsf7//liv0vdowPvXzltzdfYkbu8iS/uizvlseeWq7TO9fMiNn9emiw7GTV2YU1p7bPo9/hnyLlJVPjPTsiTdVjI33xE7mRrtJ/9+erbxBMiSP1hc2lZSPT11xdHGdZ45/2Lx5MmOl1rjM28ffSloZD938xdNNfFXntJ3U6dkoseudXOqJl5MwCuvx8ePf8p+dZr8N4fOLe+LIla1Y/2NIrxLKFF1jctR8PfBZ1mWSKxuI9TjWWLdEqz8yNTCpGt45OTt5qEEtIYwcxB+ZFxxBEa5ZMrdhiLHqsP3b8E7dIpmMK9TXTrHn5oqGzDt3d2faFAaFdIJaN8bNJtMYv5s8xsea04xH5HM2ljcM7GsXaFHKEd7AZUmhczk+imXwiltdmRefwrk+mzDBzCx8uPrD3fBoTyz7JMDp2NMThiY9mvnnAqSn8sGwgd/SFAByOUOLGZ+sGXl8Ehm6ECHhuiK+E2P7pObUt2opQiYpxnbg2VjdZnSpC5pRYal/0AtJi3QujJWrldKb8Kgxe4dKcO4sOHAXfiGy+bU/pwUW15Xv+pCVEGwbBB4vonlTE4m6wI/yxY/S1c1XBEgvD99DbO3O73xnsue+ixysMbZ0o3nvX17RwNxQmkmEFixjQVMTiDBfsCPvblicqu/JPXP/KI7Ew6IPGTbW3P99nECuJtITIFKYSZ7CoeoVhwGPLFuevLAwPsVomtje8NI0RxCDSEmpicRDONnpZ9wWx8qYKChqHwuAJcmAn4AKqLaKxQq2lwkms9ElCOzxtRHevePXuTUYLcSy1xfJ5rs2oBl3NaCSXLHuxNdoMOmOlAYbTK1S1V3i8Qlz6zBC85x6udfyGgKSdOFYqtBNzchrHMiPiV6No5r4Y7dwidsSxQh3pdlhdwpmscv26BW0DTjOhtk5yTovj1Mg7sWyzmEnsEAc3ggVJY4fuIW6uE3kndm/dr5oJUPvN5pwgNALf/33d6V1n8ShpcXlNnKZxUgo1mcCxJpMqVJ3qBtS4fXLj51qPx08Bs+PtlwC5G7Xv7mo433ZUPR8TqnDZONohU0rhJU2PA+Gh+ZKoQyxVFTvVoNbjnFV+7VmpjCpg/6P5hFK/rNv4XcMY8XrDIHqXhHZKJjuaKZWQtAr9lLIZx+O3Yaa90GSMfGfe1uaOCgKbfG2w78aa5mcupd0f3Unahy20ZnP1+s5hT9LYagWpT8JwqR7rfzSK07OgU42r7YWhLTBARjAlSS+IlSHOARjUout/THEB30TD4TDP9/Tn3fV9a/XNFkYQMwS9OERnYDV9UDvY848pdEimOCKsQ/+WyJt3HNEKkCbHgOnhsIytaL24saizxP+DvE9f6XCIY/lIX7Crvip5r+l4pRdnDpZ1rPWHXb89d+aeJze0z6dfM3KXJ/mVWxmxWUWLyfdDNZAWdFVvQVVR/Tb9yDuaSTV5XGegxmKxQHJhXPNxpncThhy8H6R3dQwq8ozLSVZFk/F+tYyOFq/XIatr3v2P8TiI8HpAslkt8n1OZhBLBOlyaiWo8Yh4BLNDYwkKsQQFhViCQixBIZb4RIKisQR93OSyKIIBa6xMMj2ZMRc1ASWmUGLiriFVXKAQSzSQln6ijJgqD2ouqOhS241/dxZiCdonFvVb1J2qf4FJC3cN4xhs1lUElo7/4kelvFGMmURaQlTcLKJKR2Jh/swoxBLUqnrlSxu1TJKWEH2IK6JKRzfF7KzQIhpLUAKkgoIhJ5akjYVYgoJCLMkHCLEEswT/A9NMKns8VMzkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMDowOS0wNTowMKNYy9EAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05aTC5zdmeA9JKlAAAAAElFTkSuQmCC"},"169":{"admin":"Oman","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADB0lEQVR42u2aQUgUURjHhzxE6EFhKSiUMLoEHVpICgqEDoEgkoFhmtVFukdIBLWB2UUS9KCHCD2VRFAnpdMGgRsUeUkCy81YWYkyzJAoo2D/e3jwdqdnq9tM89vDn8e337x5M+/H933zzXizM9trdsWCo+lP+0cOvFnryrRlbv3iF9qfFzSw3jXEaxt6AStCYL09V3diT4Vtnxvf+3Nfz0Jfy0DrgsbuxwIWYMU+VHWPX1idv3Jw/vC0bf+RmIm/Wvq4u2f48gvzX/kvPu1KnT8KWIBVQAXHSvVo9ViVGYGEwkr7/ckHLRqbUUr+gAVYf0iFq8mJ7GSbGZlUbgsgjWWXj/xJhYDllBDXrk1ffdmkNKfqSpZs/NSW9rTssrjHKsCKNFhmgltO9J/pn9NYKNh2ngoBq4C+Hz0y2zikpKZYpQik8ffHzzpTd6VCwbTIJzN4/E7TFx0l1ZyAFWmwljp6629ktc15zSU4obacGr45cs+ESSq7fORvzqA5ASvSYGmbzSilsRmH9DxoIqVulgp5O2LZDQvAihxYqqjMyKQIpK6VVPZvJ5OHkp2qq4q1SamxACtmNzkVsZTI1ETQ9n9NP6x8VJMv2HPRS/p3eAFWRJ8KleAEk927Ekyyy8dsmQIWYBWNW4pVSo7+bw/lI39VWnzdAFgFXuaoilIqdOmky0fPhjrWpVlKxIoEWOqkr/d9n/88GhfztJNpPu3m6jbbYtptH9vuPvZX/7X5r9P/LKVfr/u1u6/BZR7T4rkkplKe8uwYtlGzbba6v98M/lnKvxIv+BuMhlEBCwUsFLBQwOJGoICFAhYKWNwIFLDQMDRLAQslYqGAhQIWNwIFLDTA30eEDCz3G+f++WF5VrXZW27OX571uJ8lcGA96a5f3PH67NbW65UTzZ9PT227jYZLtXcBBWvnpYvPK455XmLM89BQKmChgIUCFmD9X7ruHQEslIiFAlaYwQJrwEIBC7AA6x++ugEswCJioYAVwU4SYJUOFiACFhELsIIBFpABFhGLyiyvvwGHAWiwJ1sgvgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzA6MjItMDU6MDDjephWAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9PTU4uc3ZnQpD4UgAAAABJRU5ErkJggg=="},"170":{"admin":"Pakistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwklEQVR42u2dL0wcQRTGsW1DSLBYNI7cyaZBIjAIWlVBBQmpgqRJEW1IBYgKakpdEYW0iDpOUEETDEGCa3J3SVPDiUuqqLiKz7xkspvZ3WF2lvuZL5fLsvuy87vvvfnLxKg7Goy66WhvffCltz679PrR7NLE5ItnE5NoIzVNsGb6m/9m+jQPYAEWClgoYNFIgAVYKGCh4wkWww2AhWOhgIWSCkmFgIVjoTgWimPRPIA11mAp2oXW+ycLramzl2+mzgALsCrBtNH+9nWjfXh7cXF4O99/92kelwWs4io3Wu58PFjuXPZ6vy97ilZeBUwU7yWR2jv6Mdw7upn++/BmWrp4+qG1eApGOFZhFeJKdjbOne3Oq53tOAm3kXUbYOU36snK1YOTFRvh+dqv5+drcRpbWMspG9ZTBqysxOe6lFQ1Vky/1DuRR7pA65rV4cHm6jChEoIay1U1oRtbTK+yiKujYFOw3o8+643td38+3u8mlDRxLKsqxlWYu7FpcCG+dwpoG4mNMNG5CsDKb0LbkHPHb9tzxzHjUYLTO8l6Y8I9uQKfVChVE2ZFFScJ2gR3Pfjz9HqQ/66Ee8yaD8cK5lVS9cvuOhIhomcpnqykbFUIxnRTwApQV9VbXVkPc73KxqwCn15hQmDJIfKjqjfd6OkWKf0YNDupJK5eYUL11jiD5Xbms7TeqRvNRfokZYYbkije9RSfOqZesFQ/qepqzBLIcXYs4eITldJNvfOVifb+AKvoEEO9xTurGxoM1tbu989buz5RxRluAKx7AlbWnGAKs4SA1eDiXQnOJyoV+Cw+xrEC11gxF/cBVuMdy79XaEe3SYg4VrBxrHSGHgCrMSPv+dPPWb7FXm3ACjBXyAAEYJWciSuaEHU9aZHiPXBCdNcapNnAtXU1cKxyQw9uzKnN5WnUrbYFgIBlf9nuLsKi7pXCiii7ErW2jXSAVXQ1qY9qT2LMhK43Jpj0DmtO0NRYVeYQfTzM7gQM62S6pzzSbr5IYoYAxyq6E7oKZLqn3bXsg5pd/27TnLstTPdnaXLSZzeoIatUXT6oyWn0FGFhVd9rSDY/QevKhM53IBX61C5h3SusKrbkjgzBsfyTY6jaK2z1lugpNIBVrudYbkC1uiotNmD9O2BVP4PUZwNZdZj0rMackgVYoSCTi6jiUUlebv5Rf6v76J6NPJyc4v2uD+jW4II2bqgq0mCBPut7XaPr78kZ9zgWClgoYAEWYAEWClgoYPFv5QALsFBSIQpYgAVYgIUCFgpYFO+AhWOhofQ/1BYbV2meOR0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMwOjMzLTA1OjAwiaeTfAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUEFLLnN2Z2wkW34AAAAASUVORK5CYII="},"173":{"admin":"Peru","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNElEQVR42u3csQ2DMBBAUeQNwBV4XbYBsQ0NY7CAKagREpWNXwp6lJf7J4ekO46hn8bar+e+rNucK3/dd/GPd6Sr/jZyDCmABRZYb7ByDCmABRZYv4RlxyrsQw4WWCaWFIIFFlhgSSFYYIFleZdCsMACSwrBAgsssKQQLLDAsrxLIVhggSWFYIEFFlhSCBZYYFnepRAssMCSwufnzcECy44FlhSCBRZYlncpBKtNWJ8mqBRKoYkFFlhg2bHAAgssKQQLLLAcN0ghWGCBJYVSCBZYYDluAAsssCzvUggWWGBJoRSCBRZYjhvAAgssy7sUggUWWFIohWCBBZbjBrDAqhJWo78r9JWOiQUWWFIohWCBBRZYUggWWJZ3KQQLLLCkUArBAgsssMAq/u/qnWOZWJb3dmBd0YiNyfSUGOwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMxOjMzLTA1OjAwZmX4QgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUEVSLnN2Z5pF7JsAAAAASUVORK5CYII="},"174":{"admin":"Philippines","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFDUlEQVR42u2daUhUYRSGJaLlR4uJZTsiLQQVEbZClpBBP6KFEqSiAqXSiAptgWgxWpDqh1HZZgtYFi0IZkSiNGgrU1NojqVlUU1pkmUMlo7BPV9wLne+6d65d0Rn3j8vw52ZO8z3PZzz3nPO3Akrqnpw6/PqhJaNeaWfw3pNi71yAwq1QNsd7Rfb4z8tapjh3rsvNTe7wjk2JnH77cVYGqgFYHGtnvo+8+fQtVmHKp4mRexMcN8cgmWCWgAW19JD9nP1o5Ym7agts2OxoP6CtdNdVbmr/d6fO1/miyPK48aeTb9b3uQl342oy4xNX/3rrgsLBzUCVnhTeVFTm6P2xLISz9yPuTvWeDa5JhxI4q/hbmzYvQUbCpKxiFA5WEpk8lyqH3/8cGubwzXwIKnnQ2NrfpUsUdpznamNH+HGoHKwlFjl6VFnS6lpm1KxcMxTUjoiUqRPN0ZlizmnUzNK+mFZoV7Mu0iCFKvIb3HX5VPJjZ0ZWdCv5hvKFgBLjcjM5pVlg/TDJFNyY9uzjr9xNCBRAqyAKLmxFb131zxch0UHWAFRuDGAFUAlN3ak6PJJZxLcGMAKiFITCW4MYOlTpYRh9CIATSSAJVUqVYhKmM8Sq2+lJhLcGMD6V2hVGkGidq9UxUT0MlG2gBsLcrBEBCJQqCLP6vLUFCKYOFh0XNXqJiUQeTFWhxtDEym4wFI2vi3n9fyECJHmlMgkYhJhoRRX6Yh4Da/jE3z0LkVF+yitKnvaEqNuDLOvwRWxlNij6icSKCzqCLAUXAR2PD6xZ/kZzJQtMNITJGCp4GAA8TY2pULezBbvopjH32UCLIz0BIt5ZwM2Ip2x5MhHbmSqQoq8l47pCf+aSOTG+o6LP3Z9Ija7s4LF50uZoyJQ9CClAouXIehsVuGluaQgN7age/p4Wwq2vDOBpWwSN90iYmkGA/Wr6jw8IdJshdEIyq5VxQyZpsCBkZ5OBJaw6uSlKOWRKuPLRmOVNm6J8zDXpbL8ButnPB3L8NKO9ES3xnU7vy3qxeQ9J39DrdIwA9eAHC8GhAVgafAybOc1YNE5ZWD92ewq+pr/aUSm7YizOCp6/6yMQnfk/dhnUKvUeCpkppvXokylQpZkDadCVrYVKolVBFPD2QuPr0ZVLo8tn3fObg8fMHrM87TwyFFb+ONgUvpeWtW+xtrP8te8s1lT/8y7qkUdYPP+Y17xStucmuWJj1LyxZef3XdVTIFs0WXboP+4tdoxn/IfNbhilpUbdCHFUmqgyw3u4lfvXk+vy0jN21bi2Dq8ddI3o0sDNaNWFEgVsAQorJnT8QVS7pxexo2+PL0SG9zFwFLFHqMtHYpz2paOiZ9vkHNyVicUJvZBZOpqYFETmjkqL01o3g2UNaFZlDLThG7uXx755IwZ59Rl/E1IRCxrx2ZoGkL32Aw5pw/NW+2Zz+Gcggqsjh/0I+fksh2NzhksygSAKeTAsnQ0uTH72vqC78I5YcMAlplfVHPnhGQHsExpS/zbuPcx5JxEmQAwASwzzql+xKmcS1m8wQIFWH7q91+3fxYnq5wT4hPA8k/JOaHBEtpgWXQbIy8NFsAUymCZufGal9EUgglIhShYkltFinE5HdMH5kdToMEIluTmtr4r5l5GU7CsUG0q5BOYfMJT12gK4hNU6rEkfyAg/kYADRaoVeWGjhlNgYYEWNLRFCAFNQqWlwYLYIKaAYvKBKqhXsAENa1/AQfYIQ1BB8m5AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMTo0NS0wNTowMA9wxGEAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BITC5zdmf5C9amAAAAAElFTkSuQmCC"},"176":{"admin":"Papua New Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHSklEQVR42u2dS4gcRRjHW5RogmaNLvsIJmvCsggiqBfFCF40KOhFxWgQBFH0pB5EQT2YY/QqqMEHeslF8JqgiCCo8RCJ5mAMohJxhYAQQRRiQGZ/A/Mfaru3+lGv7qKgGHpmJ52uX3//79U1xX3PbHpi611HL525c/fS8a+3za4sD26+Y+snKw8P9P/ubC6K5WKhWLzx6ov/vezH9/ZfsW3HzfmiVCN4Yufs/Ss3RPE98YPFPHfVRd9fcuVLH25ZmjuU7+NsIzsDS+cHDmz6beZxJLLf91aevYJVKpHcbdme5bkNWCqRr/605YP5z/mzbMPy3AFYG0hkDNYrW9DUweptFDmASM3n7doQrHWiyAFblDIc21v0k5tXfl9+cHXfiy/sePf063tv23Wk5xYr0ijSv9VxFsqA0T97jp5YmP/rj8NfLL4FZIMDax2JNC93lhuL+Ze/H/126eT5+WOrc59dWPhu79zl4PXDTbfu2X1moGCVJVqnYMqOdqWVUqSYOXLmy6ff2Xl60GAlEEVGNiNzSJ4ipXMGawOJHLQUlrgHPz//0MFr95u2Suez37w2e81MKlfPE1i1o8hkne5mZ0LcV4YUwOF7pXLDeAWrTCLjSRCEWhKskT1Ysdkt83yCgdXXRCuX2PQmp44bYOE/VYsgn+H1uc3vn99++5//vfnk9nOa5XIOnLWNDwxWdBLZUWQHBCw5KOCYg8JURmptqX796LFPl94us1XVvpfC5zDXVTNVFAVYMbfrNDsHzZibWGBvNC9VLYU2M+DGk52PDqzoJLKug2+EBUR8wKSQ8RobZmOTbGa+B6BrJHccJK4jBau0o1Xm2Bxwzgc7hOVQYeJdJK8uRs0+P7ZegSLfqMFSvEyJjAKstWUDJuSM8gtLy2uOs8x8kuPNoLGfg+W91q5JAmBFlGjVu1+Qqs6YK0CKlOs5bCEoOrDm752MSKPINaSQOTwnP6A0m0MVsKMD66lHJqNNLdLMJ3Vr4apz5bHNnO2gwTp8djKwTA0l0nD2O3BjRf5ci1q3vhexp0/noYhB8hi33DMap56bDI5ct2s0akuks8ixWc5JnXqfSOl3jqNU+xusxQ0ZDCygOfLKaKx+PBkXTk2GHv/q2dG4+8Bo1K5FtodMbJXm03mNPdAYEHcex5l3qfTZuPk2iCh8fCfeHueg1lR7UEl21AWl2XULLIVYI/BSpHTwLiCGTbRyx3Oh9TXAgQ7HtXKnr9tn2MEURBQprRiqWPN5za4NyMcCrzKL1QypOGuRZOHbgAVGGpqAspao9ciUd+UxWRoFWAicCh8+FnjtOz4afWrXaWO3+NuychOWDJiwT4AI0C7AKhPKKMA6eGw0kDysl0rkoetHo0+1SCxKM7zwrtYBRRxt3gUv/pUpHL3UUosYyjVYLDPi4wgWqzoeTEwiJcWqhSAtVFfHhvpAWFmPFz6fOu8+S2GJlXR60tFqlNK1aI1vhNUpy5bZP2mIU8/31E43ZLCcS2S3i2Hmh0qOVNciN6gDGunc8eczWPF0tAYreBuiqUK5Qdfo2t/qs4pTdiuDFV1Hq/8nfAzR1KSrvRRq2tb1DdNDsNTld9euE3Z/Co0uyxBHBLWnlFkd+WQslrsIrm66lRJQt2fS2R6tXVm7kkK7+lVgpJn32oIYtlbIEmouKmzjDclV+9piWolWBUVfm8I37sQSf2uqQ8sCmsC1QpaQ5bTvpupK8rSLC1vFmdB+o+92a1PNKNJ58UTceS3amF6U+ZDZOhbLmb9YtElp6oKxhFo21iV3J5F8s1lnNAdlom59r3X2aPXzoIcRM5YmS40H+f3stlW0WU6t6JUNlhy87Bv3mjXhqK0y+yP8CHSARKtF75TuZjPuJnUc1Rbuml60Wc+Pd0VV0TwTapE9fy6yBCzNgU3ZqpjBYn75jdEwlxNx9JliAGXsFmelR/zHquY25q4L27jkzHhausvDlMMeM1hcOO0CxTZoe7Gf5cTnw2Kp5GEvQdyn7fS/dQBgUWEEKeapzUJS6cdiCc1l0+PuAn77h8Zs+uUDSGS3lqPs4ZEWj5O0uQEK18sZNqeVytYBDqPINt9fBqIFoLlW2Pd2nUA/hVekWw0cRLtOsj8GkxhYCKuLJGc8EuknisxgjS83MBEQEIeSdO2rD5f6NubJWCx9kidU2jPv0dpDsDSFweirIAaOIocGlrnXwzATGWHbdXJr8mKWyAxWnrt7LjIaB7+I+ZJldKKoRaa1jVF19OenwjgEiQzl5kcHFkkEF/s1DE0itaPVv1AWscV99E6F6qDKEpk8WLTZYJ8YZKc0BcoR/UyWyFSiyMB7kNLnafMoBGBlG+ZEIvsqhdVP2gwnw94niYx6q0hG2JbiLJEJg0WfAmAhjiqRvM4QpFWLjAIs0DEljyN4VzllmlYtsogn0TCcftEhSGSuFebZSUdrBivPTiQyg5XndhIZ53bcee5hFJniL6zmOf5EK3MGK89Otg7IYOXZSRT5P9xaBqF0vVdrAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMjoyMS0wNTowMNZnUvYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BORy5zdmdYggSqAAAAAElFTkSuQmCC"},"177":{"admin":"Poland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAsUlEQVR42u3WMRGAMBBEUUJJRZEaH8xECxKQgABEIAcV0YCFw8RV8J6EnV9siYiIGCDVaAKEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8QPl2c/pWgxBclh9m+/1MATZYfVaWzMEPhbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshMWnvSpIDkC2ZYo+AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMzowNy0wNTowMBhQC48AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BPTC5zdmfkDuYeAAAAAElFTkSuQmCC"},"179":{"admin":"North Korea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADF0lEQVR42u2cv0scQRiGBwIpTJfC0iIBq4CpbOwstLMQBO1iE64JClb+A0FIYUrBgHaSOuYfEGx0CYmFYAh4Bo6IBEyahBAwFm8zYZxh9vbH7c0+zcuytze7N/P4zbvfjJ8Zuf9obOUFiparhi5AAQsFLBSw6AgUsFDAGlp98PTxk9Ux+qFI/xhdVI/GP3TbNO+gNl/N29X3rz/dQ9Fy1fx+tn/+7k0T9Nf24e7hbvz5Im0Oo1bRP9WpOb0wy2bZ1s+d0ZPRE/d8Xi2rnSbcJe8d63+qcp8n/nrflaY5Px5NSQELBaz/9cvV+Pz4vPT81eTS5JKOmzYNAdYQYPRtu7PWWZM9/PPxbPNsUyoDax//eLlztHPUe7h4sHgAaoB1hzG8er5+vH4saP59uMluMlt1XsC5n0oF4tfp2ZnZGYa81WApPv3c35vYm/DhIr2e21rYWhCCPvikf7PeZe9SmDLwrQNLSCnGhJGS2nFIkMV86/vpRnejy/C3CCx5oxg4FJ9s/yRHFfNdqa4HgsTByouF4pPrycITooumYiQoJAiWgNDbXPF4Ex/zpLiuZMHqrkxlU5nAkmEPq23Yfa2FWxB8OpafIyWRIFiKGXprq8f3CCNZeN1XKVaASAosN62gIa8iigggd9rFyCcIls9d6bymtuJ3URJV8cm9lz4FiKTACmethEJ/A6+YF2PnAStBsOLf4/Iuy2hKjWkZsJI17+GBV9zKm3MKryHainlPNt0QHngZ/P4WiHy+ynZypBtamiD1TVV6m1Nmyxd1wovZJEhbuqTjToJ2FspdonETB74JkSWdVi9C25OgnaOP307jmxDJXbUILKFgJyAUb8JZqLB/Eo52m2ybafVGP0WvvIvTvslU7YAUW5OjtibHxy22JgNWjn+mUDSyVeflsQQTqQTAyhHJbGNuKxgBFgpYKFo6WPWUwai/harLn/TXfnX9UOfzmFSL+6ADLmNEiTC0ksJrdqnCtIss1lOCMablOnu7v99bvGcMBWcpv1vFk1A1GaUcNwpYKGDREehAwcI+o0Qs/iQG3A+3LATc+rwtfbgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMzOjUxLTA1OjAwM2Aw0QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFJLLnN2Z+lmK0sAAAAASUVORK5CYII="},"181":{"admin":"Paraguay","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwUlEQVR42u2aS0hUURzG70K0WvSSiAoj0LSHopZipUEQPawUC5xShECKivFRupgKlRB1QLNMUlHLAs1HimWTNmo5Eur4IMXJgdBZjM8RIUJKyBZOMN9djIyi4t3db/NbnHvOgTnz4/v/70PoH9i23dOLJKWlwCMgKRZJsUiKxYMgKRZJsUiKRZIUi6RYJMUiSYpFUiySYpEkxSIpFkmx5M64LQEeyYvIM6FYq5JGKrEo3wpiOR4lSa6bwpDWp+dUJGlPY/nhRyenwOWuOs5cbr4czspxXPi3czx00kCS0lKwjlrHrWMkKS0p1hK0uFgOWTzbwnXmds0Xoy5Qp3Kk9kSnSh+LmTwxirUCu+L1Bn0jlDJV6jy+JphLqiKKFTNxRQvZ8ZNm9fGc6OnWSv+K5pHMVn3jJ8zEKp4exVqCTbe04W8fGLd2OetV0MgUdH74aLEx1zvdvduUeu7mfhUojtiuYiZWIcl4khRrUUpBjokP94KThJHoM21ug+N7lEmKfOTTnHZgtP8jiBFcxUysGgj57tPdghLJUxXYS7VH6+bbnZA9olLNWVGqUmTYnV+ZAWoXSINV2h99VRoFRjATq7ADcou9l8CsQlaZE6LqAq8ih8qUtcn5YQFlyuuBjy/9TelPsSqL8nfV/87Oq3Pq8gfjNpX3JTRgJlZhB/Re7LpkLRbSBe05UmcosVB42gSlYt48cU2cv3wwza9lY/Lss293Z0GMZLjV1rzzjT3wYubGNfsdUCjZbwlyLoL4++2L4KCnpqTGALGQUiiF02c7gnY4gxhBbl3JyJ2IdMUqtPa4c2RBlHViodEW7wFtWjiKFVpw/4KmqUFX7XQ6FMRIauXrws8PIRbKH3aw77SYWDL98WjbUbzwEGHK/eVwRS+KYFhB6j5FBJIJMqEIQikUQbEUdpRuKPuJHVgKKZbYvONBKF6mmp5f/BNTrO/pNrXmILeQScg2ECO4ijtErMLLV+zG5p2PG8ROa8qS5Z03j2+JxjYnHUm7jbvFrKD36Wq1PXE/CHUwE6vQXSEF+biBD0iXeECKL4qQQyiOYheFlzy2woerUAqrsAOzimKJxGceyC3IgfRCaYM6Br/dvb5VjsRMvtKhWKtKL5QzpBQSCwKh2ImJZUsvvoSmWGvuvey/dHD8hAZX2UtRrHVxNMSyMJfOc1iDWDgykpSWwt7OiGCNF0lKS0EQjrlXvyJJqckjICkWSbFIisWDICkWSbFIikWSFIukWCTFIkmKRVIskmKRJMUiKRZJsUiSYpEUi5QX/wOLYG3efhMEhQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzQ6NDItMDU6MDAs/jGrAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QUlkuc3Zn80bvqQAAAABJRU5ErkJggg=="},"182":{"admin":"French Polynesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG+ElEQVR42u2df2hVZRjHz1/lTLNFZRZllE2aSK2gFgUVBRsag4StFCRr+yNHba0/RFxkGDUInTMVjbUsRZGtoTl1m2iWsDmz6VjLsInrFxVJymCjP4yd2vm8lz137868270377nn+efL4Zz3vOfd+374Ps95zrlnzqmvMzOz7lVVTaw6OgWqCpaqgqWqYOlEqCpYqgqWqoKlqqpgqSpYqgqWqqqCpapgqSpYqa9dRTNmzanVZUv9+XGCNWW22kdTf/zJgMOemc7O6Tl3b51cn/G3d7j8+CoHmjradlfG4J1v2FOZ+vr/jFnOz0THFv9f55z7qbCspFJVNbHquOfd9e5mWwcbLn375xd+R2M/a/x+JnqVxPaWqHOTNza/sxLbWzLmxIlnKsOgTC76R+X3z/V8Gj+CYVAFy+jQMxeGuh90d/V0bSly236ubbkVjL5qKrr5parGo9lLctcdqni0OW92y9/VQxsvctS0X9Jbuvs2BU7Bilbg6Nj1cNbLrltbMG35wNMHfl/+SeuBhfuKppICH8mbeXHePaffaezf82IUiOIs123MfCQDKHVWnXCGNjzGKGC5dc035qDfna9v+nDq4flz38ut4h6HbeNSEkeDlKdABlidx7aXTjH9K1ih0L6OmpU3RTAa9hjjPUP7t+Uvdd3tq2/PuvzkNxXV90uwbMf6Z1nz5uebJFichUb2D/dmEFSw0jyLigpennruEgFiGLhLn51es+8pCRZFP3KsM03H20/mRBzLC38o/mT37yEbtgwsHGCx5AQptvESqR4o+JAES1aTZb518rqa1TW9tAfKiIeNhFTjglwXpwxNBuaEyaVGwhzodM9Yt7hsdt+iTScKG92Wz6c9tgC8/MBCTV1767JXl84zPgc03lXojZ6Nq9Ez7uVth8G90t2xSKKtFBu/oYhgIMC3vPYk6Rz1A6t9YUXbilnGgQDXU1mesPMwmY0pWMEvJUjP8AACpoPHbqiec8KAJeDAsciorgCWABeMKFIAlvFCec8YmoDopDlSLKQoK7D8NliR5H04DY8lFAKW8SRyKe8qhEjAoh4WVXpgVKK9ghU0lS7luYWBwFtOsCANN97jLbbMwABLPrEfw7EErGBEKARZ6mGRAgf3j6PvRhWsgCbs3KONvvkHC+7yTCg0II5UtvbOXfX22ikERBAEF84yYAlogJJQSPtIKLTGICr16ZrIpy9Y3pJHhSpvIdkzBlgg4qnpQVTqSecpkHIu6T/+Z8Kot41jRYPleZUHrhmPrJypYwXycbJUUc0CDhwIRECH4qdUud+vDcDJh9ZRoVBmV0IBS0KsYAUhbTfhRoah4W2ZvONYgMU7CzsLS54t6waUtR/kFxe8BTpss58QabeUYEU7FmVYUTiVY0vTO0QnbevsY+Q0o3Ms6ViAhQKTvQ1G+JB99Epg2eMZ3q+OFbRQKJLxKPXyG5ljsS1x8VPA8tvD9hjJu99I0voZYtqWG+SCybdAW3M7NvzyF+FMhsJzt/x6R/8AiKysuK/+gS2ozJ9QgiBHZbjkKGDhhZzLUTkGqZq8BwypTeUNJT3HwYjt7OIX8uvfvHbF410fvQIcVKRAAQjYDy7otv0b6hoeooc9O77s7TtlgwWOeB5eRYH0ieKCqprBmT8sWLzjt6LsVa2Hr2c8u3e25p29TJ8KVsCUZWM5bQUXWQIFCKpWUtdsrKvqPJpXXp59cH7pNe8OHKkEI/mysnz4I2Glvd8YgEzBCpgS2lhCsMAn2C+9h4AofxkHIoTL11vW17Ufwu1yzxT/uPd944XixWV5LkEQcLsyzr52YREq+wFWfVYY4IAIWAABXih7WGxZYZc/l8BvQEGqxIvEn7O4H6Q3eS15RXrgugpW4NX2DBYb35JtgEmCiM9J75HBEXCBDJRxRD+f0zdI0zDfmv7fmzAf90tl4aWrsZ8En22ZegOTbIPSRvqTvIpsA5oKVlqFRZJleV8m/Ql02E8b/AzvkdDIO0Tac670J86iDUqf4XnzPXS/0mGZbX9C2Q9wYITTyDbS52wPkz4HXuH8IWvowCIHwnVQcia2QQGw2JZH2UbtNlLZj5/p7wpDrYQqO6j5JeYclYEvPPmTgjXhECnDn0zAZbHALyymd8FTwUpAmo8z2Y9fUJng01LnLWFgJeq7TRP9nlb8I0leKh1Lz8n+61Lo+1j67TnVpHzRz+/blYn6Bmb8X7mMpR+/Pv3axDKGyX2NM/bZS4V5nugYYmlPG8f+eVOyvwo8vtoPgxN73WR/PzjV5mei6xt7z+P37yTqD75a/aT+h7iv7vxMDqz4599JtSXRfxEQrPnxG4+jE6eq//JEVcFSVbB0IlQVLFUFS1XBUlVVsFQVLFUFS1VVwVJVsFRDqf8CyT42OT5oEvIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjM1OjAwLTA1OjAw0OlFRgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFlGLnN2Z3sxHzkAAAAASUVORK5CYII="},"183":{"admin":"Qatar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAnEAIAAAAm3KaCAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACDklEQVR42u2cTyjDYRjHd3JwtFpxcOAgDo4o+X/wpyYucxknpaS25cBKHBx2YLRWDkjiJNQuaJRCO6xExIEiBxSFyEoO5vBe3vo1NXvf2W8+l+99PZ993+d5vr/3tcSv4tH4SeZrrOjZ99p0EzvKO78Pb019Lq4EJtud/WVeW4ml5cOVa7uo7pLV7ckvqKlF/0otZgHLqG8Tj91Pe5c9B97Dd4HaTMSx66mTUaPAgKXAyTZyfH2zreNVlXZHmNICVhL60nFX+uA8i21HInMCI/lAFD8JrwKspJ1pv3h+dM3qH2qO9i4MrhfaG5bpqwBLS3clfCvY2GkdGJF9CzVqOv9+pu+xZNRkP8PDcKxfYvRzp4UCVhI91o4rEFr6StRj0W8BloKpMNQ2dhy8FZAZ16SUFrAUb+TFHgu8ACulTuu6Prp5WiGQ0j0bAmvW9ljiEFydHi73uxMdgpQfsJJWwAKsNHmYWJOKaZEQGrC0N++UFrCUrRuIZQBL44KUuQ+wtITQBDuApfiIFCE0C1LAUuZYhNCApazHkrsiNliZNjSY/tNk41QIXjgWeyzAylSkRAjN5h2wlGWFwpnICgGLrxsAy/whNKUFLC03ofEtwEophDY+DUJRAYub0IBlnkhHAEdpAUtLCA1egIVjARYhNJqVjmV8tYGb0IBFCA1Y3IRG/xVY8lO2+rJCDtPU9RuNp9BGQiNiGgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzU6MjAtMDU6MDCSzEI7AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9RQVQuc3ZnKONQmQAAAB10RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgUWF0YXI0VmY0AAAAAElFTkSuQmCC"},"184":{"admin":"Romania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPUlEQVR42u3aIU4DURCA4dlNg6HhFAiSOg7AIQiiJJyg9dgaFAJUT0AajkAFZ2hANAFEBQ5FQoKoeCwGW7ebdMr3yVWbff8+MZkqYjCYTCK5u9F49vhxMT35fn0qpfTrVa73r557o+bhczkf7q3fz64O90/jJc7jIO+J1AHCQlgIK7W3uI2ZgxQWwgJh7Y7kgwZhbaujuI8vYYGwEBbCAmEhLIRFe8yx6IQ5FggLYSEsEBbCQli0zByLTphjgbAQFsICYSEshAXCQlgIC4SFsBAWCAth/UP2seiEfSwQFsJCWCAshIWwaJk5Fp0wxwJhISyEBcJCWAgLhIWwEBYIC2EhLBAWwiKnnk+wFf/3cXPdjGMZi7j5e7RpKyvJGqAbK4OEO6U7dWOVUvr1KuOb/yyqy2oaEcNYb4wpVV6/I3I8w7RHvwYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjM1OjM1LTA1OjAwDF5tAgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUk9VLnN2Zx5hAsQAAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"187":{"admin":"Western Sahara","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkEAIAAACvEN5AAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF8UlEQVR42u2dbWhVdRzHbxRMikoIezEqCDREgmssrJS9cIqDMbbIpYNFtGYKrkVzhulqkbnyoqZbo5pIK9O2ZJWm6TZ6otBNLV/ERnM+QLqVlY7QTIVqSd+9+I+7zbv7P+fsnnM+jH0Ze+Jwzoff8+9/IhnTM6JXP1HUUY1wC1DAQgELBSxuBApYKGChgIWigIUCFgpYKApYKGChgIWigIUCFgpYKApYKGChgIVGG6L/3Nelr6eXRr+KfiId8jvlVz8qBtX4fRSwhtH53xRMLKgpKirOW/JjxYSKScsux2Kxu9c21M6pza69M1710xVTym4qOaq/zb6Y1TGzfwh8ocQuEmZrJIBiOXXN9VVt+d9e+C6zr+XM1t8+vdR4vuzs7QPvD7QOfJSInrt8YcuVvy6e6M/rW3v6uu6TR55rfKVpctMsoflodc7Xc+oBK7D60GuZZVkP6GHvOfRlRvsXAiJxgMaqAvRYw7G0nokfTN3cWrOhaMb89tzZgBUQlWXyBqbRtW/Vie1dN8qBZj2TeeuD6UF1lJFg2ye5OTm48YJpJD34b9vk5o+DasMCCFZm6ew3su+qv/fDJ1qqUg2meFVMtqyppHXB8iBZr0CBNffFnOKC3h0zWnbsz0h9pOLxWrSvcH3eivjSBmCNs+PzI1KmduUfWN1yRzCyyICAtenU5vztL7mX03Uc6NjYETVV33cjFdj507tTNx5XaA9Y46YLb3uqc+W27q2n036fYP9QhcuW/rorsTVLfy1+snDavJvnrcv+xSx7yjrq+8o3hbVT16B6mKIuwBq3IN0p95dcjmbGQ7nvLbh+8T0qtNpfz96Sxtff/sO/divid1tl74ycimzUwHEqgTi7uHd3T7p/ixG+AUupuFTOyL6goIc3mOr/HV0VXWSfkek/yHrZO8fa9dWrlz/vxzzRZxZLYMkq2D82hcluPDb9T/uUQleIxfIILHsnqNaKe4m9wLK/TkV+irT8Zbd86Qoro+ta6utsHpjblkAQyLLaNJROze2+tKtQOSlguY6XOoA21SlvknmVJJwCC1foOlg2Fks5oDdp/CPpj725pO3MlHOtfz6eHFidt+x/a9cGXKGnMVZyD0x5luvX+X/pwd5lm9UswPJIx1puOF7zw6GDWW534sxq1vcVRzN6J9lkheoBpBpSI+0BhK7yrhzQyyaJfY3NjAVT01aNHk4EpAldfrgysmbazp7dsz6babaKvRwIVtnW3v35PboKFFjmiJxysZF+6obj09fKVe2Rkq3S5o9/Z7NY/7JCSlZKFXanRmj8G7ADlsMD0E4h5U16AVgpaqXcGIA22+HMvIdOhZRTE1cmUoqo2NIJnZXSGMznVYdruu93dscweEgBVkIqpNr3HHmn86SzOzkafWYTOnTnO8jxOWul1KnUmhcr9qFDyo1lMhU8VaoNw/kzgDWMOlU9N4f14su2gBW6iMq+eWyWOv04TQVYrtgq+4aMWT032z6AFdJKun2o7vd9QMByOGCXE7SZ9lTGF7ZYCrCuMbb2bOWrlZvSkgvYU392ystJLMAaUlu3WdAwZ6e4n4A1xBXaLJem5gAxYPkYLDnBl/eW3rDwfDizP8ByxRVqNkETVIAFWMOAVbjy6Yerm8cavBNdAVZCW8uJ17HkBP17GgxgeRppaXd5dLyEVDAOdASscZgUVWVLK2VSvcgktu2Fn8tzuUuAZXWyTeIlQRSwUMBCASswTlCTDnpPmOIqM9LSmwrD8x4vwHLsRCutToxe01Jp1JtDkQDL92MzY50dNatZ3EnAGuaUGJuDh8yjcimWApbDh3vLbtExBKxBsJLrEsarzuLCYgHWoDoFln8P+wcsF1e+bKbdGfcDrBGDd5v9HPNFcIDlAFh6JMG4ETbLFFr5AiYs1jUWVhN5l4QqWOFcnwesJFXvTVUzR1V4oab3rwomhmcAy4HJUlMZoQEsxybiKXsCFgpYKGChKGChgIUCFooCFgpYKGChKGChgIUCFooCFgpYKGChKGChvgIrSGsUKBYL9Y3+B+TF2EckTR7VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMy0wM1QxNzowNzo0OS0wNTowMGRnFbkAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NBSC5zdmcabDszAAAAAElFTkSuQmCC"},"188":{"admin":"Saudi Arabia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAKKklEQVR42u2dX+ifUxzHd0Nu5O8NF0grF1ppF9gKa6lpUmPTlj8pFFPChVxpwwVCzNRMSwprI0sSYhfLSii1ws8FaRe7IK2Y0khR39dz8fp29n1+z/N9nnOezzNuTqfznOdzznM+7/P5fM7nfM55liy5YOuzq9bPTC/Zet41uxepU57CLJrztdW9h81plh/PLi12+YqMH9n2aQ4G5xvc8qCMNjKNgNV8+HLPudzD2oR+Sdb229the+7WRz/Pyg9lF2VUUk53V21dxnlkAjlHzTjWT27Y5QDQrLcKSaY4yqW7WswH7vjmRHPKoU3Cfhkfx/7IMWnzTfj5KHeaAflWW/HXevFtu2H7OTALcwMumrOgpCwfllMZZ0MOF0Z5Z8QY/VgRLNrRf3YOA3yW1TIW9dfXZG5Sf9YoBVJhcWZeF3O4vB8r5oo7qEOyu2puPiPr0+5qse1cH4sjZhHjPTJQ6qXILMa0ZWRzYMVhcL9bPTk2jkaztVnP+LbWgCmcs2XZ1asbwa4e4vN50ss7EfIp+kWAVcbl2HYlmILA+SYqLEnPPvT0fTfsIfVbU+UJhVnlLdK28ng+EA8rJkJ4aGYM4v33fLCw7c9lb+847c6LYeeq5a+d8sC+i7Y8v7BxzR2H3r3iqVNvWrv3tkc/pD55nlawIxW1qv6kFUpc/8rLd63Y/Psjn+w/c+c3r3z01WPvn07rlLjcfaMntLLmjNfff3g5NEmXfvfiS7c+SB767hsl9Apqd9/+3oZnLuN7afH8t57btf6461Ozk6TMbe0Vwv5c8okh9qAf2HD4ukPXU/LRvT+s+/Lm3Uu/Xrl/L8P99eM/f/vjnu2XfvHxvrvMPBhDzSffOPjPmwdhNmyDMuWkgAZq9IH8kYPHHvhlJ/njD/191V+b6A/lgN4QNBCh886rC799ek0l8yZ941ugQA+hT/nRl/9YOLbCUOPrpuiUtws72Vi55dMsCpIxAOKWc99Z9cQ2WM6gw07YZmYc3v7rpp8e+n7z0c+O7LAcggEGFu9aKgAUs5D6SCPYSQkAoi1S3uWppQ5tARrgRc1Kwk36Rv0UslDjWyiHgukgHUMsPnqIbsjsLICpzFfySKbPVx9ZsbCSFHYya1PAkQJK4AWTAAeMpAS5RX1kQ8WqCcupg/RCItITegV0eAs2+6khC33oUL9Sx7IReQodUkp411MCYE1NnvmAlXu3I5DwFLAYRNjJsMI2wESeFKD4KUNv9Wd1CX2D0jRhJ+/CVKjxLuy0zIMyUIAOoEEmUYdy5BySacrSmnw1Ty2NkNOUMCWoQw+ZWpWlZWtyZBIrX4DYDGB5jpIaIrZ4YJ6BZfuDOQ2zYRIlVn/kYR4SBfpIR7cFsGwzXXjtC2dtfNv1DThAQLmtQFuN1KcO8pI8rfjrGB/opPK1hTumL+O90SZ0ML85w8rwMbgw1coCZpPnqVWPDWQkEFLEqgoGIwkACu8CTRQlzAMo0IeOV2qz1JzlnJUpdKhJ//leRoCJZPUKHD1VkFi21cI5eAeIjWwwezybycNsDzesgg2w3zaT1YRZ4regb4VITa8Z7ZKAso1ruzlSS4u3gIWVnWHk9WMKLE8hegVlaFZvtfXn5XZDTG1CR9vIlAvU0gWWwDwGGkCgPpjBVky8BSB4C7bx1DaTrR/beQALWEDZKXKLp0gsUttqVn+UW7VZwdEiSpm+URN40QrjQ/+pH3TrqdCpwLk2YmEGgwg4GHTyKUQ8y0nNbEqgae+UrR8YD6Cxn6hvIz1VXkhHOwXIU27QG+ieMHagkFrOofiqvk1aZDpVHru+Nrn7XUvGPOAAsxm+1MdtYAEaht55m/+wB3XpDRnkAeVWUjA7BavlRLXUn9Chb9SHmqeB15t8F3SscGmR+rYpqWMnC21NqdShIv17iyDtEibW8i3YZgMZGcNQ2gvlhT3lMBUw2V3pZbllibdxDNBU8ll2OrWM8RaNlbJtNcszywmvgqljq9G2XWqrDXzkbhFVOGxwX2p4SiV5QCtrY/IUVtl+MoPtqLTcMixcx+tQb8ikStaAM4xsCdk5ArD4FqAGsEh5CzqWWP46U7NLYrADsfVtBQof01Ov6SwPbK+43MoRqcNct3xCsWKke3cPYFkquBzmecvFIAYKtDUFrKRd14cCNWmXPlNCTYCFzIYCcheA8nQRGyt0BGmO07216pU5aiOa4fN60DIDxjDoDHflCJhQgzG2xoCmoQN9WAs1Ky9bTvaBWU3bEvKU8MY51Ozm8KaN3bD00+4P72B6m6s3YBWKbhgqwlpORWAEw7z6Y6MDlpBacZi1XlEaiDDJO4YwEspWPVZ23rS2oQ1ASSt3pbZovE7kW2gRmHoLyE9dx3umUKZ8ykFa8rhbcwgOdipwRvCad/vtILVZDYw8v70baCDafUqJHRBQqEx79QRp5O0UWgR8qZcLy8nlVp1Wyn7qdV86bvSB1q36GQe+pZKacURDp5j33GHHkxJY6EF31JSX5dWGrtQoMLKqsmLyyqtSW7VLBys+S46pWCgpxDSSwvLY4EsB7RRqADf13k3BsV9F1pdXLLt86rIt0CRNNzTqY9gdC6DYr9aB0V3cj01arw+h7ndPMIewCH1Yvv5QgwMDux/eagvu7pH7sybAfNH0w55OCOHHina5Re4LUeYLvmt+7CLm2cPRH48c4wWQ+U5pD3sLxgkcpEU2lUNf6xP5XHL3Y6jdt+OyXwoyruvIhlKX80mUtkdtI/MiSwMn08VlMRVxnGkzp4N0LDbHydHKyXHhec+HKcYon6LJsC7ujGhTKPSvTf4LMqwvf1vJCdZE0IRe1/TrSo08DeKcB8xuvPd1M1NJv07zC4a6T4Pmvu/m98w0H/n4fruxOw/TK4dOkM8tXWadRU72JR1/scjeX/x7kevhPrAW7zB8jvB0NLoP4PtstFNiEJxPU8fXO8Y8TR2KSD5NfZ6bsBnHaDi2zGF9gX6elWVLp197pVf5kQYD+h4YUhiZ5utrNkmJqZqVT1Of3nZMWHp8w/d4tdhcL+NMboKHUf4apPZoK7FTBN+R1peQz53SN2LFfI7ZUs2Rqz4b3YOKLD/5Q6z7uvjD6g3qerunbWhKk3dr6zjQL72ijfIDNx5ee2jdIndfxXfnZv8D1kC3Aba9j7R1/SYX3bYNUUxjU/OZ87llWOg/I+Sg0yXcr7vl0bxOtEj2wVRhHE90X9EBYwmkGeqXMD1fYxR/uKOpjDK/ThnNbTMlh3ioqKzyC/WSW1s5/ljReksnZvR6hG3poXYVx/Xzpp7vII0TX9AXWMf1F9McocyjuR8rtx+rzNpz2N9tjnFZEC7+MH5oW+R/rg5bs/U/oePPktzsHJdEidDzwQ5QnEyHKfqVZBEi1rvDN7R9kE8+xbef4tzN17O7IYe/O/7RqJLHWfNd6VH+otueY97L+5miHbgY9nfokXcp/j85E1SCjv1bgloG/aqzmN9S8udvxXc8/wVl7ibO/UJF9gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDI6MDYtMDU6MDB43n6GAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TQVUuc3ZnghxoAAAAAABJRU5ErkJggg=="},"189":{"admin":"Sudan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACWElEQVR42u2aPWgTYRiAP3+hEGj8IZQ6dOgQNaT2SIaAQ6EUBKmaajSQtDFSpB0EoaOEpsHJblWHgktFhUjcpXQrIg5msMVBDLEUQYqKHRS6lHM4h5PrhaZ97y45nuUZQu4uvN9DnvtTl39fehYdXqp1v48dXlkNBuNxCPdPpU5MTZ+JaD3Zmcirp6lwn7bAUKCYWAZDtyavnE0W6on+c4vVQ8ffxb4xICgglsGDV+/cD9+93nlxOfocvaCYWGaSSOiIWNZEMjIoJpaZRiK5ioTCYpFI6KBYJBI6KBaJhI6LRSKhg2JxoxU6KJY1keiFWI5wh0QWAzkt0fSP3ttW0PXZuiSWQCKLgZyW+LeVMYJdDML2KHbbWj7/74j7WBjb/dgdsdkl38WeG3/e9KwafsdVsWyvIs2iQF/QM7HMiSxfGH4wcmP90+0jhTz0Bz0Wy3iTwkjk/IG3yfKCvq7X9M+w3emxWGae6i2FBm5OXKs8Kf76+niztqFYHsQS5vnvj0rZ9JuT9S/V1ywSYjnyH2Yk8uePP/rmRxYMsYQ5sVXZnrlHIhGLRCJW+4hlTSRLiFhcRSJW+9A49+LUHrHEnjmSQsTi5B2xSB70/w3S6dL4wAeSh1gkD7aqWNxhRyySB1tWLJXO94yeTg2uabXMy8yLTBn6g96JpYbGuh4qpY6po9CPdP19UaX6osF5Ro9YYslTqnuuY5ahI5Zg8lAKscSUInmIRfJgi4rFVR4UFovkQTGxSB4UFovkQSmxuLEJpcUieVBYLJIHpcQieVBaLJIHpcmzPCjPv0cRG1YFsTmnAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0Mjo0My0wNTowMK6sX9sAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NETi5zdmfF4V8gAAAAAElFTkSuQmCC"},"190":{"admin":"South Sudan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE0klEQVR42u2da0hUQRTHx0TWTBGRBDPNfBRKZU/sQw+z0ErMoISWsqeRKWFUSGVRoT3dqGwjqehFKWokFUUPCoL8UPYgyqKyBz5IiBL6VBAYdL6MTDPM3Ttzd+/u2Q9/5O7s3Ou9P8//nDOzSMJSM15VOIKboyalbyBBJJiEoqIq0IjHOXeupUWcyJp8Ns/RkpLizA5qdDyKnoi3BtWURjpyC1pctIYPnvraNSXkUOyyGTvxBqEqAwt0qHve8KYqNEpUxWCBolGiagGLxguNElUxWAOiFwUZGiWqArDQKFEtAktcUSJkCJYysCCGYUWJqhis/6CGRolg6VasKBEsl9E2hNHxaJQIFholqv4GqRa8/s3PVpSJY0dUJeTGr4kvi9/GUxgjMxLVSiW6cTFaS4LO3lRedvP+tfC2p2+zO6s7qzr3oNpLibdgkunvp+UvXXDnsHtX8+g3zq9bvw/9dasfXzZ5EfOgwJj4vQvXX76QlLho8cXVqjph9JEZ30pfPth3aWVt6e1TPbU1JccTvhcc+OL67F31nSux/ncRjyfmkQKd0FU086TziLvuUuFFgExt5KOXjxaVT27c8eF6UlxvZtb7F1H3Yls7giK3xnSA8o7QyhspHi8eIz6vzBGZMxqdx8z9kRnPu05ixsJYsD61P3mVkLw9u2ZY8TEdrQpak8/N6jnfuLl+bPDa/se7Y9yjPsnfaPEDMDPGzNl1zKP2LPJYE1V5FYDVM/3589iboPPbNsTs7WYBArt0rqgoqjw/zbmm9ygxELE4Rjn19LSyY/nHC1LuL7hNQ4bqLSWebZgRg9Xf/+VKSF/r1bsdGQ8BnVUhlZkVY8Ao29+1jkxZDu/Cp2SuAWBi4xYLHGuUvq/+98dA5K1HrCxYf7Z8dA0uhiM/Yl+/iA6H4/AzRCxxZOKpzJ8BzyhRvQYWHRt4cQJGpv1esvxMG0SjhvqGvjktABOtABONFMQtmaqQjUZGSwqeUVqTwSBYUik21Ho0RpCq0wCxYNHv3k2/sSPzGcyju5vPg9WORml7sMRZFA8s2uZ4eKkCy8ynECyfS955FgNWCNkSoAaQsUjRR2gr1BG3ZKwQH7wPgSVOrunkncbIaPKuCiw2ece8yvZgse0G6Gnx2g1m9nWxZQevL48P21Kw2FqPbj/K95PEDVJ6fpkGqTzWcBzrPp8GS6akZx8qb0lHXAqYX0TimR0i5QsLQcSzJiSv804vQqvNn3iL0AiTt8AS33Nla4X0thkzMYlnebD1D7bN+Mc2Ff9W4q39ouLjuNHPzzf6WdnApD9Lb03uWvyzqDcS1V7q7W/p0Ja3tjDD/ScsYtO3ZYlxfUd7cwYlPzyZOTcV1Y5KrPkGDq9tATokuqSlfL8j9OCu8bNDNh7oHtfEqvhdVF9TogoU+X0HAFP4uqK66o7Qp5WVeQUyFwpgySuLo9EZWJTFZ5G5Bvnxns1j/fXwlOg2uAFtVcrsjMKhQ3XPH8hK9C390pkTmB3EJ0QkgMDS0cykzc4ut0Mc/BFfDyOWgq+bGjQ7ewGHYGmxQjF2dGUXaBoIwBktGkzlWHY0O4TbPFjKqsIBiy1+anYIlqVWSC8wB7LZoSoAa8C/C0CzQ/UoUhI0OzRHjRGLXbNDRbBMgYVmh2Dp0L9y4qRFG3T6ggAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0xMS0yOVQxNzowNzo1Ny0wNTowMLjzwZoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMTEtMjlUMTc6MDc6NTctMDU6MDDJrnkmAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TRFMuc3ZnXZEMEwAAAB10RVh0c3ZnOnRpdGxlAEZsYWcgb2YgU291dGggU3VkYW5wies0AAAAAElFTkSuQmCC"},"191":{"admin":"Senegal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrElEQVR42u3dPyiEcRzH8RtYTRgwELtMRDz3XNksSspmlsVgkFKUMimyXBkMFEaDLGLSpW66QcoZEBmkUKaL4WP4XY/nep6754nfPe/lm+7P73d1rz7f3+93z5NUqnl1zXFsr7tNl67bV/p8fclk7KpfU29DmUXVj9Pd5f7B+4f2ru4e22sKWH9bnyYel9wSsIBFYgELWMCKBJa+HmABC1jAAhawWGMBC1j2JVa0swALWCQWsDggBRaJRWIBC1jAAhawgAUsYAGLA1JgAQtYwAIWsIDFASmwkrl4N3MLWCQWrZDEiq5hqZJYwIrsK9+evHh321S5bAZYkdWJhf18eliVVgisCGph9LbVPW+5WZ9xVlSL03djbhFYwKqpqv3Ft1EAVkJhqf2Zc0XbEIGVOFhqeT3jWwfOoTmXHomjIQLL4nMs8+CgcvU2QbPq2SDjVCZIYlmfWPqCZ2eOntNrSp3KtaNxo8HZ9JtRC/kg42jGILyAZXErVIrMXx0X03vxfR6x0yyVj1X1mfUaYNXJGkvv8q6iaqkaLezOkcSqK1h6vU6qvLu/sFUjaDR2hewKy5pRdS0ySMsjsayHVQuvk1yh053TCin4Wkrv4hyLxPKt2sGFnV3v4hyL67F8d4sD2e0z58kvmfySrHck2+bkaYUk1i9V7cw7sqjlStfn7o6qH77qGqL5mYFVh7C8y3Y1OG8O6RE9a2aYRmCNBayy/aDaWdhTKPMkTElm3hwBrEQnlhpcdadQ3hH0N7CA9fMbYtik8Vv+V3fVA7C4/SvGCixgcc07sGyARWIBizuhgUUrBBaJBSxgkVjAAhawgAUsYLHGAhawgMVPOsACFmssYAELWMACFrBYvAOLxAIWiQUsYAELWP/yv9gDC1gckHKLPbCABSxaIbCAFbR+A4xXV+uLEBwbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0MzoxNC0wNTowMMwpBA8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NFTi5zdmcOvYyFAAAAAElFTkSuQmCC"},"196":{"admin":"Sierra Leone","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3XsY3CMACGUS9CZqAlY2UNJmCA9GnS0NA5FUNQICExAgUUpABBE2xHQXp/8YrTYYH16S6EatX39YajbX+qO/eQbnAFFBaFRWG5CAqLwqKwfOH3/vN+Fn+x6F8hhUVhuQgKi8Ly7UxYLoLCorAoLHp6ExaFRWGRM4f1fKrI+2zxembK+VPPSfn9Ep+99GvnuZ/PsLr1+bCvyLyGu1mBCcuEZcIyYZkJy4RlwjITlgnLhGUmLBOWCctMWCYsE5aZsExYJiwzYdliw2q3x9t1IPMawq4Z4uXN0DQxfvn5VFPOyfUeSruEuyp9b7+dOb7sf516QZxJYVFYFBaF5SIoLAqLwiKFRWFRWKSwKCwKixQWhUVhkcKisCgsUlhcpg+vVbnH5O2QnAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDU6MTUtMDU6MDBnQH/8AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTEUuc3ZnXmLsXAAAAABJRU5ErkJggg=="},"199":{"admin":"Somaliland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFJUlEQVR42u2aW4hNURjHjwcelFseKCSK8kIZl0muIUIeJkkTjQhFokZTck1NITIT5ZZhyKVJlJmUS3gYmkwnmSkJQ3hgzJTGNQ8Me36n5l/bGc1x2M7xn1X/Vt9ee6119vfb37fW2hOL9V27fsiwLNQeqz8PWp7QsD1Z+9RaWsOa6T+g+8nC3iMuqYbtyYDrU1dUNWoK9v77NnUdG1fL4Pi2rROPDj20/d3ke/RDG7WEUfvJiB3jnq2YZvoPmFtzqGLZ0xmjD4xfPHDByGNrVg/AUjr7VvHxLdQLB1+cUjyBOo4EkZ0512Yd3DW6366V8xbljzvxad1m6rTk6uGi233OlNLDtSsPn1XviI990Vhfw9UVtWdXbjwAatR1PqgijiW3bu/5vJE6FhDThrrBilhBBCyAAPfj+Mru9beuvweFC2/v77myRMGiveIFjtyF5eSRuxsu7Acs7LR/++TT3paWxsb3U5vvMHr+1/Id6/O0h+pRDadqXypezA1lPrQsOXfzcVkRaGJnhgYrgvWTvvc4A0c+WPrq2+NqoDlXHK+vvIodC9GFOo5EgYOWwAQK1IGDuxgRIFobWp+3PtpYfunb7mZQAEHwojf6Z85cBUdwZ4b0Rv/Mf9KXkqqFtyNOlL8zeuaCpWkOR+IY3Alq4UgDIjhbUyd3ETk05uF+8KLOKICoENNelbsYhQQHgvTDiMxQ58boIz4U586cn8ErsMwFi8iBkwDryaqm/c+7kaSo43hwoY2mQuDA5YoaoICjrtg0zhHPAAVEdM2kqCk6tCTWUidiMUP65JVIrAidCqPaD/KWhxOiLsBJK1ylpUIAKKBAjMECcBpLuEoi414UlLlLZ4IFaBIot63DiEbMk2SaWP+1/S6dgyNWFBldYg+RAxfiJE1kKEAQw4hn2HEhbuYuooiCCxDaj+7+sJPCsGjUARQOMhhLNxa6CtS9KnHOYEUWsUAKJZHhKl0Iv3nd/LFpH3WNBzgelwMlyVHjDdAQk+gBx7MJACNG1HUVPWicI/3R57bTl7eXTAcsLLqq09RpsCIDCywUDnU8oOB4NGFvi0BECBITbtbVD1FN120a81DWXtoz/QCKHlgwFnPWYwWtMxa98aoYrIjP3DVVaTTSbTx1ENRTKz3wBA4crGsmUAMv3SdqjGQmWHThz+jYNUVyl57AEcnon/ofAevvwPpPTCKlR0OiAQVNQzhGDwL0uAGwiAdEIEWKHsBIoUSJbaAWBlRnRT+MAqC0IZJpzGNjEU7KjliRKWsRVLf9OEbPtMLRRZOdLqU1bQGELvOBADsj6p5Uo2Z4v4nq3lDb0INuBagbrMgiFo7R/RRRAURINPotTz+wYNF4lviELJ+KOXQASo1V+ilaj1gTxwRt89QoCEzhj9AKHzPhVfnFx+wMAEt/anZosv8mSKJhmFLTpP10PJ/w/1ZkhxcSH1Ct1rRqjJNfqzW9arCsBstqsLJM+SiE+mkYrLRp2fygnO0VFD8Ng5U2XVTaXvw0DFanla+Bmvju3whK/5ygDJ8TFCzaxinSYP1Cq+JBGVPRXoCpS1l7wUKhDXf56RmsTuClSGkxUgYrRa3pGRSSoCKFhat+SgYrxbilMClk3iEarBR11fKgkPKIT5RpLUHhqp+Swer0cShnV+EdX8dXrQbLarCsBstqDcBqelVQUVBhtaZXY88exH78Wa3pVT8Cq8GyGiyrwbJaDZbVYFkNltVqsKwGy2qwrFaDZTVYVoNltRosq8Gy/of6HX62pI9K9L2KAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0NzoxNy0wNTowMPQqvugAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NPTC5zdmfV5vyDAAAAAElFTkSuQmCC"},"200":{"admin":"Somalia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC/ElEQVR42u2cMUscQRTH9zMEUqZJLeQTaBDEzsoiINoYxMLWQusgGpLGQkSCgkmaQLqksLGRxMIiTSCBgEdEvOh5nEiUqAhn8W8GJnvs3u56s/t+zb/YO2fGt7997817sxcNDr18tV8LQQfeLzb2p8JZD5pFI0yAAhYKWChgYQgUsFDAQgELRQGrxGqnVgdYIA5YKB4LBSwMgQIWClgoYGEIDvAUA1ZaU2J6FI+Vg/ca+bDc//uhFJsAVm66MPfppPFAijUAKzf9MvHr8nJUijUAK4cgOHm9MXdYP1xtjd00pLqCfQArE1gKf+3h9ut2TaorbF8AK5NuTX9f+vvEBUtXsEzJwArHEzybXR09+KHw54KlK/oUgPBYqeGe3/q49ufaRYqAmNwdAFasmfwgWN6AeP8PAGD95wbEBUE/IKpkit8yAZZuc3L1/6pzEHRV30w7oz87YJWg8aLsZ31z57T1vDv9Nn7Q9+9RErD0ze5mefvi6/nZUFWr+VH1fNXM3ruLo/nkcPRKtUKtFrBKlifJK4SGlFZV7fzMRPKuTOjnm/rx1W6vYFKyr5WQvFew66dG8n0ipcKEZue9wsruExWAVp5uf24eNesXj29XioBJI2sWm6e4Iss14iJCpEbzyxCAVbPmw9yDMdmzKGshr/RgFXGr3PJEXh5Lo/UWrBCwjmjg5FuS0Ghl91jZ128aLKXVReRYvHYRWc6uFLaS7A3do8lJ9oMhBETA6hle6tl1BkV1L6XkySthGpldodEgGNdPlNcRHG7jxa2E6dM4b6eRLQfEqEo7kbQ7QR8Lv/HS+f+Kq4S5ARGwDAVB1cT9xota18kfErfh7Z841SyEQkOqUOU3XrqDIK5ZpFls/tpFZM1XuQm4u3fL98ZrZDfxBywT59nThrwsL5DZfFGMyvtUlVor4fhF3tJBAQsFLBSwMARaMFgcT0PxWPxGMmChgIWigIWGW+wFLHI4PBYKWDzBgIUhUMBCAQsFLAyBAhZaUrDYtVFLw2OhgeodNePDm+0EpMMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ3OjI2LTA1OjAw3NKyvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU09NLnN2Z+iG1TMAAAAASUVORK5CYII="},"202":{"admin":"Republic of Serbia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAI0UlEQVR42u2dXWwUVRTHNybaB5/QgJFowoMxPlU+lKYV1CoBIYggoIKi1CCaiFCQIMXGBNTIh6DF+KAiNQGUBBBJVVbamCBQqApoiomAGIwlRsqDCUj8ijWZ3xj/zeWOs91dOp09L/9M7ty9s53723POPffMNNO6Y/iQEUNMTQurGbsFpgaWqYFlamDZjTDNSfd9ecuz1ctKDqzmc2MqK9bZ9JvFKph+1n/igeG37dtYu3vIM7vKZr8+rKKUbYaBVWBb9eG2Bb8MHZm96b4xNz9llsPAKoBuHVe1alT93GtmXD3pILrrXNX7VQOS//s2sBKhbYOn7n3gzwN1iwctubRt+fIbVmQPn1p7/2t3Ptrxwqz3xjfULr3i1rGA1Tz9lT3z5tAH3X/k8aYnBzQ/PG7x3e/Y9Jc0WPuaJ75xz+/ZJ+bdNfeRr37b8vKWP/bP3P726vNAs3N9bXbCFBSkjrS2VW6oaDrWUD79JT3LCHyKcUCt9fsZg2rOGQoXFazeciLAxMRnT2z95tVrgQn1xU+E7YC1v6Pxqmmj3BUizhHs6Mn44VUCe2ZYpNBigZTaJDAChdaVm0c/PdgHFitBBQuMfGBxlab26ZeUN2PVaMHJGhypAiu0GQFAam+YeKDh2P1s9sSqssEj6HO4blP50KmuxSIlwfiowgdwRGwulKYpsVgkDrqlPbFYAUA68aEdCmwPtsrto+6ScRRQHd8sVmqDd6YW+wEcpBLCHFUADRiFoXegtHCWnqz+GK1blivog0vd++Cov8d2hqMFYNFicKQQLBYNQEDk9HHH6vFrQmgajs+pHBo6Pl0bumdDvII+HHMWpR3lKqGjNDjSnW7ASgFBmFAQaEBBVS0WLdgk3J+e1TEVOAOihPJYISIBEJ9vaegsb0QJz7XF164teqwIGgqllyAN3KJi4YKiqgjG6m+7iqW8pcMWjQKBpcHNKVL0JAvPChGkcIWKF31wuMWoMuBsae5UZnp2K/O5Wb7Puu16LXABHcVIWxQXn+PLByzf3x59T+Lcq57lzHr2fS4SWBMqR2yrOYxOrq/u/1g/t0VVz/rU11Pb3ZHXbZ+yZ3YjG8yuklA4WLFo2b0LUEJvbclVGSH6inyrOH+1qc5v5vIfbq9e9HzZ2ZE1C1/U4+gWV6M/5VN6Xtk1eviSFqbQV8VAbol0AFiwkUyWnHw9fVB+ryEowZ4j/UkuMA52gs3pg8fmr1nYX3/ljFPVPumh+pnx/5aeteSvvu9T7NG0D/OIZgr1h0VfOLqPCxbTfHZQ57bOD868eejMofIQF4moSGZ+V33812N/0fPbXTvrsnPpCUw/TTvacvQLzrLNrE4TvE58su7G9Svpw7GCdcewyfPqa+NPVWFx6buaKSxAIJLrZxUsphOYupq6dnTtwCaFu4HBCo44CciON7YtOnA9PRVBwKIdBUS3cBmrBlj0VBsJWAZKr4GVv7EFrEPzn9uzdKMCEVZNSZCraU/WffQEDrA4VfXp2d1ltP9bHvNf1ko3pwHo9MDTE37+WgFlOwiwevaDSas9i3M3EgGWWqxTA5uva/lIQVE7pHgBh2tvcIjags3zPb3DWfqr4o5zdYWmvQyW+wvYcFnd+RW7NSpSu6UOURXgiLToqbZHXdsFFsbBuk+vpZ/ligoW3zMaMu0Tp7+BVXQFLLUZTDNKC45S4aBFgdD+tGAFdcVHcQ620HctVpGuxYq/SirG6s/AytliUb/w44yTJ0/OxAJxrJPNsZbK0DOOhg4xiJxAzUVKr45LzSfGymdBY2AVxRUqUqoaRb3Vr+bdWS1koVDQVCz0LKp20R2f66I+i2Vg5QBW7/7xClb0ZOuUaxEfDpFjdX8kTvVpHFrUIqqV0usqWBaM93mL5bNVCgGpTsDCSuHmNHdFukGfKAQ71p6uq3W1eGCVgg1LNFg+h8WUgwi4ABaqYNFCH3WIPqT0ilyFLZ3CurxSCOcTtCokZiLG8lkRnXK1WNgkTa7iCsEIvBSs6PFdsHLdJzVNnMVii0Ytihv9EJgz8Xqs2XZsT0f7pvbNazmrx64rdI9B1pfHcrddewusZDrWxIGlCQLXLaKKmiZC42h0DKfX1QRprvUapgkCC1eIxXJhUqUP7o96BCDAGgEQLlVjLxQ75IuuUMDCsboWK9pu9d3dvdSCRTCOa1NrpAG1m27QdV+3GCtwixraawjv2wLSq9BfwXJhirZYlsdKEFhYINeKMPEAgdXRagjfqlATDRq8g6YvbOcq5L1sEzolYAGHzz2BlGbV4+Sx6ANeWneqbletY+hqg7qJ+GAZfHmBVdig1S2bYfrdrBKTjX1Sh0h/8undMu8BWFgd2nXDR/Fyr6WVqHESpPncB58DzScfluvPIH4Jcpz+tGdyXe/E6Znrp+ijYLk1DkDAlOsKDrtFhRYVVAqWvvfB/Sz2TIub3RLn+DXvxbgzfVczvF4xCUq6QavdtXwF4NwsF+181rVJ4RsfAvhATaMoHZkxuSLJVVxhcu5P39KML9/jltoVW5lOnB2VUqBA7VT4dobg/TP6oketcHdLZbA94RtHA7cYtshrQqjTwiLyKb6JOlbTXDWTnK9C5gmLpc8M8iyNvj1G3wkDFq6bU8ViaVmzvg+C2nnFl4iNTL0h0ufBQgGLx+GZcn0m5wLvWQjsUHSdAikMfdqY+nd95gcNX3sUnAV0QyQlYOH+9H9MoLQoZPqKNrdmSzdncJ0Klo7j/j8Lc4IpBEvtFtMcRkLyqCpYAISG5NFbNOGzzs44+nIR96lG01SBxdQS96hF0dd7EIG5tQ8+JTzXcRRQ3B9O07BILVi6TsSKMPH6QiKNrlTdMhiNtHQcdamsRi/+WtjA6mW8sF4aFbFq0ySqrxpCV53uOIZUiYKlzhErhQ3T6Erx0jycqqYzGEHRNBRKFCy3LhREUKwOqz9cntZpaU9aLDw3sP4HMhABNaABMhS8cKaWlzKwTA0sU1MDy9TAMjWwTE0NLFMDy9TAMjU1sEwNLFMDy9RU9B8y2qjgYgPQ0AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDk6NDItMDU6MDDwO6+YAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TUkIuc3Zn1Z5TpwAAAABJRU5ErkJggg=="},"204":{"admin":"Suriname","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADX0lEQVR42u2dPWgUQRiGx8IISuAkQgSb+AexUIJEFGxsxEYsxdpCC60sxM5CSCOmULGIjWhxiK0KIhYWVmKhCSoiBNFCEYJBxP+ckHeLXeZms7szu+vePlM8hL27mc13z803M3uzZ4w5c3xHB8LQJAQQsSBiQcQiEBCxIGJBxIIQsSBiQcSCELEgYsFW8tqtJ4+6VyEMS9OjUEooiEVBLApiURCLQilBrD8PF7qLByEMS/Pm0/51x+5CGJbmxcvO2ORExCPDZ3dfailT4/B2ZO+G8V9iq6OUmUmxbKa/AfYzsx93tZK3/rz1FGr34/TpVZs2fp4593j0cg4pQ51nlXH2+V9izwwnlj9D1R/0POfmRv/u+vZ97PaNtQdEHakoJmHFKhafQiKaSoPSQCr99brP7pgT4vzSoYvbThKZdCLWClQSjIulI0QGsbz49cP1peHfkVi95zeN0REig1jBkqDE+jl7b/uaLdEMkVghln8SJCGWI1b1M6Baufjjyv3OZB+xXAkxHp+WxSq/WHVNqgPx3dDh1Vv3aP0pOxfmpx6MPI1rZIulv/PWLLZhXmkK9lUNUe31zPj5nVN6OxNauOiSKV0yuwb70ZiIOivEGhC+P3V03+YhDb1XkMklVvozHWKpRbVOKqx2vbvC/k+9RWL85Opv0vuqDIlSrbShf8ojVrGF/0aNxhLzPrvvyS6TY84YXfwZuA9nlhZZbpjQUFrXAX16LNVQ4sC8KddqGy9W0EBIiPShd/q4iiVTeizn0L5Y4hOj4Xmj+pXyWjFt66JdX4z58mp6dv2FvDLFn68a+IjW0WP9lxMCzdqiZYgsqdCREFVDO+eApMI+4qYnwfgqVJ+VMOtVWusfjHEnYnmFPrqAY4miVSh7SK4jia/TxERUbW2b+iCWOwlaF17SV6H0aOJi0TIDJ8TGXqU17fw8JWaCy0ppFapYItOr4qO0aDWr3mlNGa1nrtPk/sqH/+n67E5xbe4oVKfSlmZzUR/jsXlENai2GhJi9m1tFfVY/nvx7L8bsotQ/UqfXTceVG2J9fd27itkzy4sZSc0dxmAYcndZijcxoiCWBQKYlEQi9IssbgjOSzlPu/8hgLkJ08gYkHEIhAQsSBiQcSCELEgYkHEghCxIGJBxIIwFP8BJA4soBLvicsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ5OjUxLTA1OjAwDXm1mwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1VSLnN2Z6h79J0AAAAASUVORK5CYII="},"205":{"admin":"Slovakia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHMUlEQVR42u2cYUheVRjHT0Fr1KQPNRsYi8bCxoZE+kWMxia1VCqLpMApWdiKFlODSdRWe5XFHDGTVoSYaw0bVnMLN4xwlvtgGSWjEpYUs6VtEaatNZuJwf2fD0fuztl57zn3vPe+Pl/+vNx77rn3nvO7//Oc5xxlc9Nzf8xNkJLaVUZNQEpgkRJYpAQWNQQpgUVKYJESWKSkBBYpgUVKYJGSElikBBYpgUVKSmCRElikBBYpKYFFSmBJdJrt3NI09k/21m0vDaVWL57ZfldiaCZx6o2RfYRFjMFCF07+l9tVcHSiZunu5cMTo1lzt2dwFY9Ifp/Pf7Sh/AEOx/jmito9FzY+XbD5mj+rV/Sv3nWFOiVHZjYdqjhyE2ERf7Cy1xTktc3rbG2F282rs6fntc+u4mAFqnMm94OqzmLCIs5g9X/53GB7QLA8p4E/iXVeynz7m9YNwZCCogbCwgJYVZ+8fsuJ1vKmpo9PFJYtS/T1visegeIIVCwjO1K9uCnRNya7du9H3V2n1gOsgO7igfVXV8nzjzTOZo6v/W2vGLFdZsjT1n+79mS3nB14bHjZuXbxycV3EVsGivfF24klddrTf9zfnuq7J6vqu+jXIHs2/GbXtTy4an+HS61d81bdwH4dsOBnAAi/xfJTOwqWrO8XwUKkJYKFq6bGC78uWcvjOQ2wOj7vW/fTpPuWSVZvyCtuaWtRH5eVCVabvloAK6OrNP/95MFSxkOTF7I6V14LIIAOYjJchUjIH2jP3Hh89xd38LNeyfNlg78M/YgaEOar/Qye173zq/fOtNtth2S7R12b/2yyvRB2vzP3t3z50oG6k43oeLV/iGCZj/ocLOXwiqQDwFp6sKy843pbn5PdenBVMC9x84RM9j0Fe2idh+BgeX6jdiwxiuK+5Q2g3Jk8NP0TAu5q+C3kpdRg4Y4ogxjLrgOZdKp7NzJXZsuK9cvsqvzwle8fAlhqx0KE9PeSjSVPHkZEJUZaiJlEdFAS3iOWQcYLNagdSwZWqrpWB99gz6b/YaiHXVk9LDwzlKk+WDweElXETgmWtB5tx1IPhe7VvBdcfirM/UtysLwQW9bNQATdjEiLO5YHkzjXEyMwZLZwlpf0FEMq5ozQywzBcCzvrD5Ysq7Cd2zSSm6G2vB6n7n0KijPY8nA8jrYn1WfNzcUVF1GjM/EmSMfKD2XElUHrPC+dT+m5rM/c8SdOpZ6bqJ+DR3H8oNlLeMvpDmCgWXr+3YDaLLpCZ3YWsuxzIN0y44lgIUsFKIxLLbgOJIC+C26EcpgAMVZJDx5ZgvzRO+3bDbqByvZFtCf1qgHO5Ppkcm1kXAso1mhEiweJ4krib5AHmelwbsvVBfnibL7qh0r2eDXZRm76Vbz+zJb1qqvOnksHU1iVqitsgRpGL7lfj7u5klQkoWREba1VhgQrGC7GzwcMZhirTBqiyTmn7rdz8PCko7dJi56tr7q2MOTpacXjb3Kk5aBPAbXTr/wc9loqbljAXGE9vBUk8xQsPjJbjuHkbXXBzoFa4Urb678tLN+uHlk6vd7TDym++Lq5RuKKg7VXH3wPmwCaakrfqL2HRP/A+6oLT2GMJdYizUwEzs1MWQMN7IQHv7xw7bbjuZm+x2o+cU7VzxeKXOFTbP5325pkNWpDtuBe9Ry7lHD2s+Avy9Yqh4OruAfEOFD5VV3t2596t66dfWJ7QAFkOFsTvX9Oc0n1SnBHb15q6qKcRV+ozYo0PQPgpix2moHN+mAMHrKvE6Wqq8KroCJPUJmNTQ4Dix06kd5ACorA+CwRH120WjhuWOI/8irIp3H0lFsZoVvIVpy2cSAD2lYpG3TD5FUocZSy7gYb0HVackwNknDNXO+e6bhcCK+XqWDVBjrhk5nhckqOhWBc+PxfbcO9oaxti82ASK8kaxfa6Yy4Zo0kFl2LEQhfkU0IzsbhrYN1K57czGGRXiJXbxQG6IoQDw+1ZpxYFT9VO7bIbXPY6t+pk4Y6kzU7Sqm/SJetvIrIlKYLvjfiyc4tJMUySZgzWt20xcmd8G1zA0uySq27GG5BimAYLklIIW/dMO8D/sdXH4q0VGXbx1RsLA7CokArCr2zA4MnS6C6+gMkcjvY66HZR9sp1mYSLlXFuWH41vwsNjs5ejhOgjwgY4/NwZ/wpCH/aIAVP+O5t894cui/4jobHQVHyI9DwM6iMMwy4OrYQDFRj++r0sDF/+9FiYQtt6dxezlscXP8zD+H608yDDYISQXF4j0kSJNW8e6wv9x8O1PF3clyP5mkMAisBa0in/cEd+ZYMrAIv/QmqykRSux6LNPukCHQnPg0g9Zu28U5Rw9xVg0QDvFmlEHkKbMsdIpqCSNEFjpFAPRFCQGMVYUQtToJG/j/hYLNHgnp4lX79CskKYslG4gjY+Ls7i/WHiOErZX+f/5WxyRlT05ORZFdaGsbzLqctIweuR/sp02S8QdJg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUwOjA3LTA1OjAwG9J6zwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1ZLLnN2Z0Mfc8AAAAAASUVORK5CYII="},"207":{"admin":"Sweden","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACGklEQVR42u3dv0rDQBwH8CA46ODk4uSggzi76QM4WRUVfBafQAVRdHB0sYhQN6GbgpOouPgIDg6C4ANYKUFMqa1p0kjPfJYvpX+u4fLhd+FyR6IoWl06PA4rh0/Xh/Yrl09nb7ObjcZDNYri/Pi4u+sl41/F7Qyfrl3vn4TYG4Oa/wJWr6TAAgsssMKBlRxAv2A12wQCLBULrDBgqVhggQWWoRAsFUvFAkvFAkvFAkvFAqLUsJoIwAILLLAMhViA1ddlMy7ewTIUggUWWCWHZSgES8UCCyywAoZlMwVYKhZY3XLl+Wjit9fdf5Wt5WYWCev7X/7qBKTpyfTH0+u5yHbuUhzPyPxGfW8+rByrbS7vjte3zudmHvPDituJ2wyxNwbhXLS/H13t1A6m62Hl9fvFwtTL6+3N5Oh2NlLJjNuJ2wyxNwYzWzZChZX5SXXaECbzZ8cu7u9pK0+Wrfcai/eVnxIF2TdYyfd1jSwkdYEES4IlwdIREiwTFmDJssFqmSA1826WvJCZd/cKQ7lXGNbdTKsbAljdEOLKi6j7yqd0n2ZbJ5S9/SI3rLb/b54jT/P9bOu3OmWe48yzTqv9O3ZC26VjabIt9mCpWGCBpWKB5Vk6YKlYYIEFFlgu3sFSscDy9C8VCyxDIViGQrBULCDAMkEKlooFlmssLMACCyywwAJLguXiHSxPpgALLAlWNesG9uS+QrD6m58+1tRKPATTOwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTA6MjgtMDU6MDCvvw1bAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TV0Uuc3ZnN3MeBAAAAABJRU5ErkJggg=="},"211":{"admin":"Syria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADBklEQVR42u2aS0hUURzG79gDF1HZIqwostBatDArichdOBERGYGC1fQQJIJqU7StoEWERESFYg8hjMYQoqKHYoaZZBktGrQkI7Ei0h42oE1ii29zYJhhspm4587v/8HHcO7/3OE7/u6dey46XV1ZM/NycTy57rAE/8nHZ/mW+AALxwELBywcsHAcsHDAwtNhNwpYeEpQBiycOxYOWDhgsRA4YOGAxcYbsFgIHLBwwMIBC8cBCwcsPF3B0iY81lbcPBrdE300fs+/zE28J5EUE+v/21ypzjux742fN/FZcb/XGTodLLn5FMeT6844RaWgAIsCLAqwUlT9deGVQ1vk6fDnsTevZWCd9Ye6m6rk6QCWvXmtAWu0cGxf5PGGovtvqhrlGvEqUrbntQas0LNvxR/WZN+qbzsQkGvEq2DZntdx2zUq/7VnrPf3UfMavbCpu7Il2zftYu/OdrlGzLnmLPdf397O6yKwWvd/KujxldU/LD1/3Z9x7/KpoPlDkBMJbj603MmtfRmYIdeI2aNZOoPO5mawvJ3XdXesYGXf687ipfNvnDzywnFqfpSPm4trXsHmuDo1S2ew5Y6VhLzH+t53lnHHSqh65n0v/NhcuqLl+bnmSVsvTd81x1xo87OOqlOz7H2iipXXTG1LXlc/vA+GRmf/bCjquLPwRH80WHIdVaftD+xeyutqsPRiMPppw/TFaxsyD+/wxitTpVCiWHm1Gu/8w4NfVgPWBKt+/du8juNa0My7V0oqunbfbiuoDcs1omtanbaDpRRKFCuvfgrdn9fVYAUWPVpV3Ti34tr2g+XabI/kj02N5MhrJvdUt2bpHY86bQdLKZRI6cy8WgGthvvzuhSsgSnhpq/L9j5of1W38Unj5zO9A7E6dVSdmmUjUt7L61KwhrdF8kcWJP6Iqk7NshEs7+Xl32YowKIAiwIsikoBWFfXIZR8Ob4MhJIvwEKAhQALARZCgIUACwEWQoCFAAsBFkKAhQALARZCgIUACwEWQoCFAAsBFkKAhQALpZX+AJdtKUiI9nGvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1MToyOC0wNTowMEB9ZmUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NZUi5zdmffuTTmAAAAAElFTkSuQmCC"},"212":{"admin":"Turks and Caicos Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF/0lEQVR42u2bbWiWVRjHn1WCY3NZYyZrLxVjNi2CkVlGSNK+pMgyyyQKeqFaax9GZCMmDjFaHyoG0oehK6eyTZhrqUNZQVkfpG3ahs5eHMU2XZKG+6KWSwN/z4crjufh3M9zvzxu15c/N+e+7/P6u69zneucO3YytuL1Fc+dG28/07H83+zLHVPdpm5bs/XF7w7de7WsaGNpLDeW99qrqeuSqYfvbLyd/CdKG75sqO7/MaMoo9BU7vLkA6vLc94/4m8daN1k+f7MfZODyys7nt1AuYMjmZtnLxzrqep844eB2h0Te//J/y2/c/2IX6VPc6X76Eogo4tNvEZfGa35q+LD1sbBA3mpQwYiXsEChVTKLTu/aOemVlrx06w9mQePg87QUEFVwaP0htkPtF3B8qCyc+lW2bk2S5Y6ZMlZrOTAAmJ3mKYKx9eObZbtPX5y+IOJWgXLgybudBdLdqrydN9ki1fIggaLmpjtcrfQybVLNa6pfNmpWDIQYfBcwOLJxGDZpjnTErvA5K8/N6PBcvdFvPpk5BaExfL3YzDrqeozWO7TSnKWLBWw/LVMOs1FBpYLZF4txBdHusaO1nsFa1db24K+2qCnaVUflKFKTlkrMWx/Fvf/PfQUq0ip5grLVHewUNCRpdhgMld2RK3c28gHAI65o3PmVR9VaJyUgY9Wx3+tW1m30gYWd6OtIZ+Nhhs8KFNJtCpDAKZyN9oafjZSuL7krvnbMxY/H1NonFQOnm1QbUM+E5Qe2Nd90885uQqWZ7BUE6uC5TNYM8FiuVjrMMGSSwTz2lxAmOm2a1uK+liR1XDgoXlZ9zUHBxaRufY1rSf2ruu51JXVXeJVzXdtuZFePLf4cP3sabsqJFBJRCr9V4XBDQb7B6dH+nPrY1cPDDQWvHxl4UDvoqUoKaa6PyNTRl443PzJksA3rIjTpB7HsqlLzMlrHMtUht+MogGEjGPZIlWJW7plWVN217fBhRsI2ALWlcXfP5Jxx8WNu3vLaiaa2/Pu/uXiMwcP3fIYKSjPSD13Yc83mb+jw0NNjVmVqEwnH0oJPEScSuSdWLY5zAwqcXA5kWGZiJsnF3nnLVu5ICvLlYf1KDc9I+/YjwurhnOqd0mwpGK5L93T/Pa6pyVS4CJh6nvinbrbinpaq96be5ZrCVkagSU3el0GFSBMmILYK2QyTb0+0YIlLZa0T2AU14amno9OoNJumVaqs2LtV/nlqA2swLfbXU43uAyezTLZBs8rWLZjMy4W1KWeTHZRQSZ9LNNiARb9YIJlWiwJFnYrYrDkILnDlNx0E8RBP6+QpY8lY5hNiyXBwsGwgSV9KWACLIlUqD6WX1+81/NMYZ4gtX0kLr5gOOe0JFhygsN5TzwVxkEUyvQHWDKd50MCy6XTg/iyw/xLJ/UFR9B/6Zhg/Q8XYbfilkwAhH2Sz2OlzPRQwfLLZ0oOLPejyX79pZOchQ468g6yRJjMFZ95DVKEbdkgJ4VnzKlQvkspgZ/UABf+p6P7UFL4UoPwOUCE2FLf6tplG1bJ0qVylyf9DevJBQr5AzrAkTJ48/Z3W4617b7/TMWc4MBim4WyzBiViRrQ0D+ABUzmOlEihRJ/D3xjh84yFaKDDvyTP4201UTWJ7jukO1FZUo4J7G2nt9y6vPeeMQ8IVgvFXRenr8UpOo/rfy6oJIUEyOp5EwpaXc0WTU4fevBN8u2nZ1qOdb4ZL0Njp0fd+dk7y+d1fp48R+31uxYULJJKndt75IzpShYM0ixjtKFNxXLBFgmXty1vUvOgW8/K1jpqUxV8Q1mYzdQImWzW9eZTK/lFtIkqGClp7KsMe2WnAQTg2VOiCGFGBSsG9HfAhfQSYyXBCtUv0rBSn9lHcqhPCayxG77dcC69hY5RPDjmg5h+uOFb4TtwdPCSTeVuzwJUpH9sqaDd6PgxfkLzmyZ7jkp3A3VSVewppPvxbYMlgklJQJfSsGaTsr6DozYkgopOqVgqSpYqgqWqqqCpapgqSpYqqoKlqqCpapgqaoqWKoKlqqCpaqqYKkqWKrTRP8D5MtY1tf5GxMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUxOjQ2LTA1OjAw1i0UvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVENBLnN2Z58TxscAAAAASUVORK5CYII="},"213":{"admin":"Chad","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABXElEQVR42u3aIQ7CQBBA0a2mpgnhAoRaeie4CxqBRXGWmqZILkE4Al0EBkVICLTbPjOWipcPMyWEUK6223RnlpXtJp6O+8XyEO/nWwhd1zSpzeeTX4vdpZjXMW/Xsa7zWVWlOwNYYIEFFlj9wUqRF1gDnYoFFlhggQUWWOOBlTgpW6FigQUWWL4KwVIssMACCyywwAILLLAmAssdy1aoWIoFFlhggQUWWGCBBRZYYNkKwVIssMCaOKzXCRZYigUWWGCBNaj5wRPaCsFSLMUCCyywwBodrAHQBMsdCyzFAgsssMACC6y3d6wUSYGlWGCBBRZYYIEFljsWWIoFln83gKVYYIEFVn+vrsECS7HAAgsssGyF7lhgKRZYYIEFFlhggQUWWGDZCsECS7HAAivJO9bXnwuWYikWWGCBBZatcGKw/gJXsRQLLLDAAgssv7EGfMcCCyzFAgusn2+ODxFBzpJgiMMKAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1MjowMi0wNTowME0fhVUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1RDRC5zdmdX80m3AAAAAElFTkSuQmCC"},"214":{"admin":"Togo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA+EAIAAAACBfXRAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEu0lEQVR42u2cTUhVQRTHb62UQgIhcxNFQptoEdLHJiqSCKEWLdKMdn0QQgiCi3JR0BcGkZRCLnoWEi7SkjJJssiUgsyIFCKTlNBMyTTxg8IXeITuY95M83Hnvnvf+2/+PMa5c8e5P84598yZ67z/teJS7gYPtCwja1NLnN82lDe+q/3F9aymzRk5S4rK9z51ik9W7NsC9VWtPGZei7inCY7M+AArzGCpoiCDoLGtot8AK/wWK2gKsACWPQVYiQaLF+XIR062w3OtOAxgBclisY/QT5g8vQvACqor5IFlYMO+tp0qOL+259rq+h01cIWpAZbM25aB7fn4MPfOge3Tu9/kf1hJvz17BwRYoQ/e5a0X03Pwy9GDZyLRjvnX8zNkt5ShUUQcYAUPLPNEJdM+frs+q3k9gTVZ1dLdfhGuMLUtljFkvc9yRvKG5wr7GgaiBNbvC992jXZLOUS4wpQDyx3aC1MDA9lF/aXVhJRbYxwibzQD3OkVIdJ6pXjdjaq6urNrtiVWEzUT2/flje9o7sdJ649lkdqGeRasn4ONDa0vNUeWmC2BNfXqVvHy2Wj0Xafj6OrbMbvqvotXc5C5yqv/NP6cHXmrQE6NXJiM9m3d2X/k3OzSnspP0yxY5BA/T+RvPHZcfkyagzxesyfu70nrTBw0qaYyYHGiJbJAhIVY/wyNV06ms0i5lfrIjEbh/2JkBrACr8p7heRiRhovl9Tky6BjojT+2KHq2rsFqraK5hkPLDWTDkT0HK7RJjQF5jxnZ6L0FknZL5MdQ4AVNrBcoTS5J3emykQnSpuetB1WdXm6YAUheA93LGURLHf901BeWWHFqKqLpP7kXhd3Eo2RknOFACtoYHGUIiFV50iO7z9WSqvOIgyukHeXoIGuNh/H20IXSh/oBfUUsXm17R2e4F3VmoYXLHE+Xfg4vw9e7Yo81ouuKJHhbWlyLFheLpz9B5bcFks6600WggpjWGimhjuyu+boLY82odk+5EAV0gpCp0nzmXlwryk9N95CiFtsZ71tgCX+72Qspfgqmfn8a3FktnL1nCAvCyXOhHEdomrJ4YI+P/2oN7MkRtOaBzL3i5UWSKZnHGXv6OfdzeejOmfOyF4c/3I5QQIlThaKcy2bCSMQ/S+bkelpXitho9rCZEzVaxX6myNF1oicILk52iWUsjGuTBhlsJQdolBRNhO2418MFpTB0qxnd7lIGkc5QYoDq8l3rjAmpWmh+hQVpKl9YNX8VA/AAlgefz7EwllFgIUj9rrWDmABLP+/YwOwABaOfyX9R0EAFjSgb4X+j8Z5rwRYKecK5ZOo7gwZ5eLlr7WxpQNVWEk6eUdKtQCkbLu7RfxX1f4m6h6NHTnONqp4Y1W8ZcvbmmXb9a4Vbv22l9/MWDWluqVtNAdVdY2pVEBiUs5hrrbHNylftq1+zkfmLhIHVnVP68pXIAWzXM6fB+nV3PwHS/5pGoEF9fKgQaIrP/2o2QcKKQWWjyehgQIUYEEBFhRgYSGgAAsKsKAAy+aHD/UyK3oHNcOYDgjm+pgcWF1IkCb3p13DMufkWx8Hu/FQK4olgAIsKMCCAiwsBBRgQQEWFGBBoQALCrCgAAsKBVjQoOtfD/eeMzorx/4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUyOjE4LTA1OjAwJcXahQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVEdPLnN2Z7uyOrAAAAAASUVORK5CYII="},"215":{"admin":"Thailand","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZUlEQVR42u3dPUpDQRSG4bODpDNgIyG1jYvQNVhZp7Cw1Swi63AJgqWF7iG4A1tB4rXQ4oImjNw5JDrPVzwW/hDH1y6XidVqPJ5MyLqGI6CwKCwKy0FQWBQWhUW2EdbD6OLgssg2/3h7fz7xi5dIFhvP88PT6QtZ13hdPh4/vZF1jc4sYcIyYZmwTFhmwjJhmbDMhGV/Iqz3dTftbj4t+Yb+V5Z8b/9rhrirn19+Jm26Mazzu6vR7T1Z14g4Ors+IWv79WE2WyxyzP75+2Zrv+8mHQGFRWFRWA6CwqKwKCxSWBQWhUUKi8KisEhhUVgUFiks/u+wvGfSe1B/CMu7sznE/j9DT8+TMOUpnd0+x1f+Wc/0fT+HIc9FZr82T0KbR+xNWCYsM2GZsExYZsIyYVm7Ybn1hSl36biniim3f7lZjyn3FVa429NltS2fT2JY8mr5fKqFRbrFnsKisEhhUVgUFrnVD5SOZrLCehlyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1Mjo0MS0wNTowMPi9kTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1RIQS5zdmf11DYEAAAAAElFTkSuQmCC"},"217":{"admin":"Turkmenistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAJf0lEQVR42u2dbWiVZRjH96GEAl32MjezEW7F5nKytxQj7MWSps6X3BKxxLYg5ypLJClKEjPQKUMJTY1NVLRJhS5YpLCoDNIslZhRtujtQxEW6peIMji/8+Evt8/pOT7P2XZ2X18ubp5zn/vMnl//63qu677uJydnWPOSutoodlRb05KHervO1P5Z0vFJ451bS5Yde3HqrqKrsN1Tx+6rXNHVPHp71RdBdn/V6HcrJ2G5cuj+0g/Gl+o6rMyv8IvR/3KzGbRxgbW3vCavorf3+XkHx1wLCsChAB1oq22c8/DnY54oXnSMMdgxv2v+hBFlh4PwYmV+xcDyAqy8KYsWT395zyO37646dKJ9+qqifeDyUfvKvauPghHjH7f0VBwZ99Pfnct3PqrXfyluyb3h6t+vWTl8+G5XpViNlQGLX7Sb54Vi4aRQGqABoK/fX39283QsYDH+8OL4A2U9C/c05dU/1v3ZmwsXlP1a+Pqr43ahTFiAu0T/zBX6qViApTBhcXxLm+saKr/fvuO25RML3umsvPBAG9/Fbst9vGHBe4qaQsbKqFcUxRqW+9TauTlqDYJBDRa4qFbh5kCKT4EGgLjOGDu3c15J7c+oUd2p1sMr7mI1lAy3CL5xgWW3f5CCxe1RsIiE+o5vm714GFgAE9AwBjgUCx3iCng9W1DRV9OzoXjWPeM3so6uxq9EAeu6Y8+dn399Rc7af5ZNwYb/lxouAwYWuvLNLc8Ujj2ACwMXbufmvppN970CXlhwATssz4Ya2qsz5ZkxCljA9MPBM+1flW3Z/3F+9950cWRs6GQcLJxX55jJS8triIG+fHvGg4WNhN5ABl5ETtwevgWOmoZQmNSlJtMQkYN3sFhT3/1t58x5J3ZsXTeSK6mVjH8p8/mu4dVPigVYKBZWQ2/suRnrqkccUtQABYBQkaDAP64EKX+ziwVXDo461XE0H6vuTyMzPkXtDK+MB++aIMWdYUkTgAUaxhzNcmliAoyIwLCsEFeCVPUJq6ihRkGaxEz07I+W8wVnz7Ucf+vcG0csAst45h0niE1qjFO6SRZ5EtgxBiAt/jBW5WNNdDEKWGCB6gCQYqHApVY7VkBlB7NuDRj0ccVY3HjSnlgSm4xT1wpd666QXCfhCksrnp7VcCFdZQoDVniLK/x358Wqi8OJvUylYgBLYw4N3kEByJIVwDSRClIsVlPFSg0EmTAAcm98amUKY4ESsMI/XRpYkWIs1EVdG06QZ0BU55KsesItJj918GI1ZgJWmHQDMAGWRkJuSB4dLDfYzxqHNfjBUsVSmBjPOV09svxJVKRtQ8tvd5zUwjNX+JQEqathqFd4sNTx6W3jChoDfNFdYXSwTLH+pwitygQEuMWWruYJNatBh6w6OXo+5QqfMlND9U3f5b9WkpvUv8SvANaV3Ug3QZquQyTCYwVzhRkHiyoeesPNQ4fQJHChCK37q8CLK+DFTPSMFViNlbWkc2VgRc+h41hBSoN3U6wMFqFBh+IMSQRcGBFY642TWqv/SsZVbLOReItP1aXyLVaLC6woRWhVO5BiHL7mOFTtZf57xgsWtx9ngQIBTcdN9W239mmuS/HSTXz6Lcaspltu+h8sTVKoVqFepk/95AopMAMTTg0Hh0XDNJUKXjpHg3pWQ8/4lUxs9HP3aWE1baFIWUmnX4N3QmxC+OSez0QqAXdGLAIumm4AJj5lJvCxwiX5+phqhVps1niLsaYqKN0YUgOcbsCpuWUcVTIN6lWZNJZy0w1ugjTK3wwWChDRkosRVzTRakH6gCVItQkiqFyj+yDcfp6g/HsmunSADJcHOmpVz2z36YC5Qo2fgprA0i3poILWVzjEwXJLIgqWqpGWkDULH8ZqR6Fqm4JlyuFdw6pum3G7nEkZaI5er2hkpvqX7Q2r3v1vEJsrTDz9aasWcFCQwRKYUzd0LZ/qfH0eVLCsYdXTGEvhWPVpQVPRetCZNnFa7+RerHtFr/MtXcefGGuIaFtcYJEIIIEJFqpPjNUtUvBRV6i5excvvhVXusFs1oAFFuTHgWPjzLsn3HxSwdKjQdjzrjrHp8ykgOOuEL2v0GxWgsXtx1LSARGuEC3RgEqXDjl3rihYugJjkDWwPD27QbEAKQVLE6Q0gWkbvoI1+4Xae6tfQrF0zczVCs1mQYzlhu3qyPiUrujUMZY+J1qM5aliaYJUUQh6KtRnQP3UngoNrMBaoe5xCEo9pJvH0j1bBpZ3rjAo8+726oBOUOZdGyiGRubdwIqUIHVrhapeqUvOVis0sNLY3RAGrCDUtP3VYixzhZfZj6U599S2//djmc0CsNy2enAhDaExljaE6UHczHR1Lq5DQcxm8Z53UKDHBnTImzPWrhssiQadyTZlxqwZ1553KxVnZZcOFkS4DjSkEvSMZLLqzGQddyYrMEfVzhTLu5IOGqNPbYxpl8D9MR/d4lN2lOscd4VMt3+ZHdSd0Nx+V8+ABqu46Byqh4Dlti0YWF7vbuCpTV2hNnWpDrFzgWKz6wp1JqiBlLlCr195AgRu8A46zNcYiysApDNZId6GVbNZfD6WJgs0qEdvQErTDXzKdeZoeiITDatmM/78G+8576lP9Es3TcoK7qnJttHPoxcI4KTcM0hdvMK8FFNLOnoGqdUKvS5C65nHbid0GLzcFfTUZHOF3pV0dIuLtkhoZw4bkXFqWN196m6b0XNpLMbyNHjn9qtKaU8OL8JMnuuy5vjp9kLaKPRVKHqWqUZa6Z6abDaLg3d3a7KqFH04oANGLli8i5DDjMCL+bzOCYy03d7A8vTtX4qUAoTV1zCRhUfPwEvBUiXDXaJb5gq9e/uXRki8egmM9M1ejLU5jLHipZAxVmT1Dav2VOhREVr3eQaBQj5duwUBC/j0TfdcoVGMp0Ir6XjapcNRttQBwQinppBxRd8JnYzDEocyqusEMmay8tBOkA4pDY433UAYzvMdeGnMBBwaS2kcpmOCd5DieZCVLfPuabpBn+8It0EByABOwVKk0KfkS35l16geg2tPhZ7GWKpJ7v535gAHkROahHWbwIjYWI2VNXi3m+fRRj/6lRWFoLOTw7R/KaasHP3NFGazUrF4dlMgtOk+PFgaV2HJ5tsxRt6BpUUYXJhiEf5tq8zU7+pLCUyxPHWFighAAIe6Rd0Boe+4V/enSKnaWR7L0z3vbgczoKhVNdIr7tjVOXOFWZOHi7evMMz76DWVgGKpbulBIO5qqljmCgez/Q+Iy20007NKmAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTM6MDktMDU6MDCg2rqRAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9US00uc3ZntrCpqwAAAABJRU5ErkJggg=="},"222":{"admin":"Turkey","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD1ElEQVR42u2cS0hUURzG7y6kiFqERLQQImhRQgQhuQgpiJAiaZVbd7UwJCkKCiXIiBZC0KIX5CIIJCMVioSQ2kptRCTKtJcTOWk5PWxs8W1OTE7njnPvnHPvb/Mher3MPec33/9xzj3B5JuqfPUqFC2vBml4yKnBtY0b55lswEIBC0UBCwUs1O88FbAoL3As1B98AQsFLBSwUMBiIFDAQgELBSyWclEcCwWsGNp979u3HNrR/7G5bmJPS6Zh/+XDtabq97omPY5b2pOWd3w8A+vDie1V9bnsxOnejr7c8ODco6cL+151TtTnN8+MZfuli6/zs4uBVL/RNd87hs48yX0Z6tzbNSXgCO6pdqzpgw27G5u+td2+f+daITqlqe4zP35vw4Mjn7Y19TSPgoLTYJXLA95drNm1dfXche7s1YfLh8n0s6Vc7Wvm5oqeWgVNsEigYylI/Vg/fP3Zi9IA0v8q5MmN5HkKo2Y2NrPy6K22c/JChcufrSMHnk/qr8CRELA0/b9ujPWOD4SFSXDoDqW5prwq23Xy7tlRZW+ESO/BkpeERUruEsX0myWCYAUUz8B62149sqlGDmGPlK6POh+S8yks6nOCizdgKROyR0otA6X2cfaEACvsiAWVTdJ/X5l+mVmwQUqBUkHKtaGME3Qc6z+qVoK9V32ua2k91ufmIJpNV5CqGFjKjVTe22dULnfJVZMqTEcdNH1ZLagAWPKeZHiVVM1VfdrZx5fWdO8sjoUCerKbsUFlp6G4ytXcnwDzidRXM5sgwkjNWC0iydvczBcjBCs6s9Wd7bvqmgb3B7Hwq6JSQ0HcLFCEnfse7Jljhc2uiocVF4rqsAtQKllI3iPpsNu3GBQ+XBgmpeTCSB15hTP7ZxF86emHBfH3rux3K7gAllAQTPZe62MJkibHGjh+/tQ61wKfuc0wrGMlqZVaPBd3OsdS4zF5OVYadq4G8U9G2KrQ/WkorAr15TE3Tye1Klxqduhj0cei8+4DWDbhm847a4UhwFr+WmGScq+gUg+s5meSdjew19SJbTOqp+x9i/1YgBX5DtL4sxN2kLLnnT3vgPV3WNRbN6W9pVPeLFABN9mZU9SFQqLeK5S7hB0y83ARc7mG11Z5E/ofb0ILEfmZ7mkqb0Kn9FAQ5TSc3QBYMZ02Y7+bwP60mdICKOo0WGGns3DjinIy04cKfzaXhJN6PpZrz+L9iX5qTgoUeVvhiX66Bk8CLBSwUBSwUP/AIoNBcSwUsFA/2xaAheJYtCUBC005+oAFCoCFAhYKWAwEClgoR0WSdAMWA4GWWf8AK7hyUfoyPfsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU0OjQ2LTA1OjAwMATf+wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVFVSLnN2Z23cyhMAAAAASUVORK5CYII="},"224":{"admin":"United Republic of Tanzania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFG0lEQVR42u2dWUgVYRTHb7m0mOVShi1WpnTbFyVDKrCghGilhYJCozLMgpKgKEGkQNIkHypBSdDSbNHCNLNF0zYv3cAeooyghwqsiCJ6aX84PoxcvXwzd2bunPn+L+flvjje3/y+/znz3W8cMaEN9cmLUKk6K24GLMjl8tcmvW/6ND+gxvl4xuTyH2FPV0YV/Sl9viFqcH+1LqJ41ohlsd3jvgeEOJIcsxxBBlbAxLFuHHCnfu74h69d7pif3pH6uOFJdVRNjjPDNbQ7On5k18A5hiMFsHhV8lNxw4P5U7JE/HS7qPRE2NbFcQn5QZkmwWQdsHgtPVbwk3eY3iXf3Rb5xQ9+grF4pT02fgJYdvKT3/ITwIKfABb8xCc/2Q8se4R9pZ9oORPx07JRyROCx1kaJhiLS37K+5Z1JiSTgZ+MAwuDA+//mfzItn/O3yJ+am0pfxXWycxPitpzG+CLh5/0qvO2T30SuLPKfWLd8CKABT/p4KeMzxtnDwmn9oKuC2D5zU8FO7LPDnNy9xM92PYckTiwHwF+0uanrmsNmZGj+7tSGMsnPzUd6BgzKR5+Ulb6FGDBTzr4ieqLU66OxD2bH9X/PfMLYAnV5a7mjwl71frJpC11Bvd33q+X/HQ2v2XJrkWxq2rc7dEOR2Xw22MASzc/dcRWdYaflNRPBBPV6Mrzb14DLN3yE18/TdsyeULAdcpPGv0EsPT106pdKYHBL/j6KS1y9ZXB6ar9ROgoMfKs2tp1Oz3AUZufTiceaQ4N5NvfkZ98yk/ekZLZWHRjHE67Hz99mwx+oupTfvKOkafD0N95v1/hJ9VgydMVwk+q85PvFX7yzE/c+zsRP1Htw0/cwTIu/iv91NXpPjj2NPzk6af0qIb1xfOE+jsYS1t+soefRH6CYaCf7AeWNj9tKkm9MKiSr5/Uzp8M95OdwFLrp7KveXGh12TIT3S95z60lqbt6/GT0RhxB0utn56X1dZFpMvpp7CcquzOCpMw8rQgLz/RkT0i/Z2d/CSy/6mXn8xZ7PgaS04/0d9P16I6P1mncveTsr+ju5yvn8iyIvMn8tO0xstb76y3HFJWAwt+Up2frLDkWRkspZ9E8gTd2fbwk8j86eKStjWbQ3r8ZGWYrDN5h59U+wlgiRzJKj5/4usn6kxF/ESf9uEnLjCZD5ZaP9HMhuY3MvjpfYm70Hl098vGewURps6f+IIlW34iP1GXatv85C+wyE/7x7fGzGiEn4T8xHfJMwcs8SPtZfOTrfKTOWBpy0+0f4i7n2ivhEY/2Q8mvcBS6yd68sXXT7TzXTw/Xd/S/nPtGxvmJyPAIj9lNbUUzcyCn+AnHcBS+0og2fxE+SnxwdUFN1baPD/pBZba/o67n2j/u09+khkp72AhP4nkpx4/yQyQOFja8hPfn3Sq9VN2ddPNvEKd85P9DKft+R2dTMJ9/kSnxIj8hLWXn7DMiVSaP4kcyWoPP9H5VX72kwxVBj/RbUB+EslPt549PL5iJvxkCFjwE+DQDSxlf0enUHL3k0h+6uUnAKEvWNQDIj8BCN0q/GRqlQdfvvNx8hOdpS7ipyMlzVOPpsJPAEs3Py08VnuhdqJ0zgBYxvlpZEf10mdj8TUDrD78RG/x0+gnVIClm5+w2AEs3/2UklCXe2kPvkiA1a+f6F3t8BPA0s1Pyle+wk8AS2NdHJeQH5Qp7qfcwtvhh9LQ3wGsfv2U48xwDe1GfgJYfntlGfzEvf4HC+MEawCTOAoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU1OjI2LTA1OjAwGam9QgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVFpBLnN2Z7vKlZQAAAAASUVORK5CYII="},"225":{"admin":"Uganda","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKElEQVR42u2cbUhUWRjHjxvVBIXSpoblBluEJUUbBU0frKiYjdqJjAyStnBben/fsIgwUpaMXXa2ZlnTXqAgA0EJ7UNF5GYvIrVS7eaQFfau1pIKQRBWH/59OHG6d8+8NnPP/8uP8Xrm3Huf+c3zPPfM3BGiUpQJF0lGmAwBSbFIikVSLAaCpFgkxSIpFklSLJJikcYy1Z/amrYaf+g8lqluxxb75wa7R3XO8I/E/oxUMj468fmIVw7UPhIkGWGKd13tw0gy0mQISIpFUiySYjEQJMUiKRZJsUiSYpEUizRXrM7ywuS+6fb8r+fnYUMf64yMDV8MLmpI6RebfeHcVcbP2cUyGvoU9/9x93O5WrrHT+zbjcfRIOZ/+IW7xzVAfSxTZx55i/489tsxz4MJnq8yqjuOb/tzUcqd3eWBfUngw5LzKf5JnUXNexv/6OkNBG4l9wys8pQVYCSeJc8W2rnoPFceo567TuTlkaEdpz3b1sxY0f+kiJ5MsSFCBoYzw4vNpdfWl0Kat696t7493fr6bEZd/pGueTkZ9a2TL3WMagLlMSC2YAZIFs7x2L9gVscfvfiExoQXKxxCAuQeWRQw0N4yNfBD3m3vQiHGe77e8eUUSHZz9Z6GTT+q4z9INrDKU1Yg5zAzaahYePdbKfXqyvOmzl8PTizLLM/F942mdE9d4F657pfl3qXz1IwVD3rFPidRrE/w5YnKBf6frLRArjpUUuzZNRO5app7pm+6v7KhcvLJ7+yVkikXR3OUShixdAInj7Ef/6R0+WX3HeQkey1qsiqy/E2zZ40dMmLcBt+2pxt8+krJmQ97ZCmMWFMZn7QqfzLbCtvmtxXmzc07s3iWJ/Pb/Z4RR4uOXD82IVix5LKY6G9dlsL/a9U1OqTtd3c0b78ri4V+K0SxegOBW8mmtfMift4lVhfG4b+fMANKkk7xykmbMS4nDeXPW73wmrcaj2tv1v1b9w06sGD1siqIoS0iRDbmURFLfjmdSpwqWmkdsXANiIYdV4XIYX9lXnhzuQJ6oZFvbL66trFOp2PDUqoJ0QbFxmXDS/p0msDjXetyv9+tk12KB+0ZXTxIvpkJkqHTQgd2rv1UfnUm5vw7a0vN0ntYl7eaEyPNibZBN6yiZ9K/mlu1YuXjVVV4LnIYSiS6LiyW1lRMqk0fjQ890EVZ6YW9805oBxJy6JQtmchSUEq9pVMWC1TX5eW+jWI5kFAhtNYbfRVmyO7OPpz9DD0WFlFlvVAW5efeGNm888ZI+zuVKVbC0zfnt1LfHJ1SCAVBiCVfJ2ILRkImiIVvQ8izYY/8URAjCiIacKulUXRRIBp59FtQCleIEAvfgEDGur3PW5PdIpfajnftBzqqsEeKZQQhhyoWlg+gER7L5QySqSLiwx+1bcde+DNGLtP6LXU93aq1h0CqWPYtv3F9FcWy10suZGjSkXtQHLHFSkF0VEYrRbHUVS6UPFksZCn812q1HVuMW6miWMHmMCiCTIaiBnWgHT7YwX8xkvnpE1xSmztG9AfxNVxQ3q6O0fmvOlJn/nBoNbPVfnXGr7lYkJ6SpFIeE+x+7Y/BKobhxy0a8bEayTvgSN6wSlIskmIxECTFIikWSbFIkmKRFIukWCQZUbFe/15/Juk6SUaWAjdMJi5xb0yin4XzKJz0s0Qm/x6VfWRiHx/Bl5zk72ORpor1efNTsHt3djb9vNEQfAEYAZZCkmIxQ+idl1PPlMsNjhI9fo6TPRbp3IxlprjOzoLvAf+YDWkozYq2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NTozNy0wNTowMHN0tmgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VHQS5zdmei9Y9lAAAAHnRFWHRzdmc6ZGVzY3JpcHRpb24AZmxhZyBvZiBVZ2FuZGFggYa5AAAAAElFTkSuQmCC"},"226":{"admin":"Ukraine","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAxElEQVR42u3SsQmAQBBE0W1TjqvFCszNLUOwCzGzlTU1PjZRXvLyGX5EtLbvZLUuoLAoLArLERQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWKSwKCwKixQWhUVhkaNhTdvRyWLnXNd+k7VG5rUEWWxknue3fU/6+pb/6AIKi8KisBxBYVFYFBYpLAqLwiKFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBY56APt8YgxrmbUbAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTU6NDctMDU6MDB5sb9xAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9VS1Iuc3Zn8neiTAAAAABJRU5ErkJggg=="},"227":{"admin":"Uruguay","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGPklEQVR42u2db2hVdRjHLySxZFdaNy3KXhQjy2Eyy62CIrDeZEapL6KtQTS6olS21vYiYkzEVaxc3W3JhZhOibHRpFRybkwdy02nJZbTVEgtrDaIzchREQv2uS8eOJ3LvWdn6/75vvny47m/85yzcz88z/N7fr9tgcmKyXcnu6RSfzWgVyAVWFKBJRVYs3azke6R535p1UsXWEnr+Oj4trFFbp/uLurc0lHnNkfYCaz/0J+euPz6uY2g4wZcxSev/V6+2gL0W+PVdX/Na729pTTieq00q8H6Jvr1/YPzqwZevfTUJmdMArvqkg3PFo2e2Xc6/+jf2E/sHYx+Ecbub8QC2bplOw4MH32j5KNHT86RzqYGvCU7N3S23PZm85Kh/XM7jlfVW1CAqbHurcLcCWDC3vV4e8G6muZrm+cVHnNLkVfWj45NPBw/ybpdlX927eK9FwOB4sK25dJZVW8FeGQgMnfrt0Qp+ylItS+sPxJc1LO8bdeaauYfO3n4++gw9v7zBy5sfQZ7x3DzfcXd+0LRirs3OCMW/rlXsvFMYKUZWM4U1lfbk799E6mHuAUohzpaHgi2gxGR6culH5fkbu8r3xPc+BB25vRWtfYuLrDo4BP/3movgZWWYKEU3SQ40AGs/rc7w6vKB49/+k7ei0ADakdqdl0XrP7sQtP19+Z8dUPboSUrsDD//LJzK4fL8PN+uKrspju7mj6/0lDt7dkEVhqDRd1DyiMa9azZ2fFgJQnubPGe90I7sQPZqbzOoryI1a7S6JOhOcznWua3l277YOUdtq5iDHyJpEWBldJg8RU6+0+2hKcwBwsiENAwJvYAloWMOLd76MOXgjdiYT4RDp/cnaTJsoBVJ5GS5Cuw0jhiUT5T8RBLbIyhZsIOUoBiASIyWYAscBZHEiU+8U/Jj//ES3jAWnVX5fq+P5cOvFC7/w/pbGog8ZRHxcMXzNdPBAIL0LEA8emvXYfP3Bz+57tTTQsKrGJnvo1wJFDGlPakWqo3b90s6eyrxxrr0tAPjacvEldszVR/rfLn3KeJUuBFZAKyifYT4fktwMQcAIo21D4WupVrmXkwsuOWhQ3494aUNA0670Qs0hDVD+0Gqh+ily3GQeryPd2RUO/LC9Z25oyADrGKMXYbn+y1+KQrxr1s1RW/upKmNFi2bGePj/IZLCi6nSs+LIxtykOJWIzBjjl2/WirNFDjXtyXZ+B5KOG1dZ2WEcu2PemDEz9ixfVU9UOPytZJYAE0wGRrJj7Fzhx7LWPWhvjnXvTueQaeR3ErAzehSY6kQsAi3tgaC3SITFRafMqYKMUc5mMHLNqn+E92l1CalmDFVohT/XEbnwDF2VYAHSw0DmzEsh5IedZChKO1YestfW0ZBRapx24zgxEbMraEBw6UmdiJXoyxW4xsH4sOFnGLiMgJCHppVFeJ9LE4wvF8f01woCW+llXXjg3+aMfW8v9qKjxPsu8nIbAo4TevrskJX6V8JmKxUmObxXbebfQi/dkyHMVukYp1rab8UEvR1ECpqxgnUrar854GnXe+ZvACJlv32JMLxBiiDjCBi13rMQYvEGQ+vatYo3XKZ/zyXFs6GbsJzeEWGgF05KmBWM1ZjOzpBsZghIX54EvEwif+dbohi8ACAioee14UtWmRZgERyG7R2PKf+SQ7PODTeZRZYGUsWCQgtqWdR/AsWCQ4qiK6UKQ/e4KUmdZu74J/7qWjyVlxNNlt/46vn7SI2hOnJDjbMsAPM1kWOD3bSCawsqLGcgMrtnI05z9JZ9idqQ1oOGvlV6eKmMeSeMX4K48cHPamHLyZjgd/1a/nmemfy2ewbGPCeTAQdNxiD00Emhf+PpVz/WgPeDjnuM2PfywnNf24efNrjtunPoMFFm63pFpyeyk6uaC9wmn9trReusCSSgWWVGBJswWs6dT/ydrjr4D88u/t50r2Kn+fx5tl5p7H2/uJgZU6HRppJmlAPWLpzKhegVRgSQWWVGDpRUgFljQ9NBV+60OaearOu1RbOlKBJRVYehFSgSUVWNKsBkt/LVM6I3+DVH/fVzojfzVZPWKptnSkAksqsPQipAJLKrCk2az678XSlPif0FKptnSkAksqsKRSgSUVWFKBJZXG138B8SglSbh5r3EAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU2OjUwLTA1OjAwm4s6YgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVVJZLnN2Z6F+wA4AAAAASUVORK5CYII="},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"},"229":{"admin":"Uzbekistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACgklEQVR42u2azUtUURiHL7VoFX4EKuIHuIpwpTCIpITiQhDLoJa5y/4BoZVCIO5aBC0ciKJBHVy4EAZqkqYRpSQQbIhaWJIwfuQXWEllzLT4bS4cG3TuvXpv82weLnPmvnPnnIf3vOfcY1nXR17FPsCTZEXV8L2n3//z/+VW0OIzj5fj7eUPIsXTYV0jUEHTyc3S6O7N+ecfI2O1S0Xpvb47Mz9T8bpr462JsPOHa+ycrJoN30i92F2ImrJ2NMaa5pOivVXXuksRGObAiCV1kltr9dsHqeqd89/OaoDdfThpKmUlsb31fum73s8bopk77aIz4QZALA3wy/r06Nbm+u/9uV/drfGp6OtVLx5Ov/Wv/KfPc7eaOkKfiqWJL/snm8lmzGwB4bHF0hSjXCWx3MpV+RX7uVM9C4jAiKVp5dPlvcofq6LzIl3F9VDTwtel22Y01W1qNUVR/WRWUfqmMqsXlR/0UCxVV87F0sCr0Daj3QoluhYvPHzzfvhLyBRLwommWLpLERhmpsJDPveiFfqueNfUQ/EOfbHdkDtz5Nea+y42GgK5QXrp0cRgcl96aYNUepkrNVVOqqJMBY9eY5miqDynxirQVzq61qRpvmA5yqpQ8Y+7KlRMVoWBFOtkXkI7mUbhqVGTF4Tu0poua4n2lECTiYkrmbY++iE/WgeRlf70AITu0tKOFITuErEgYkHEgohFR0DEgogFEYuOgIgFEQsiFoSIBRELIhaE7onFAQ/oybEZHWfzD+2H7GBwe8xqePbkaugiLEzWnIu9bW7xIrKl0BC6S8SCiAURCyIWHQERCyIWRCw6AiJWodK+pYlYkIwFIWJBxIKIBSFiQT/zL1RgScNKvfJfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NzoxOS0wNTowMGWbGnUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VaQi5zdmdaHeTwAAAAAElFTkSuQmCC"},"232":{"admin":"Venezuela","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD6ElEQVR42u2aTUhVQRTHH+WqRasiKKIPhARJJBEio4IoaKEELSKigiAqhDIQ+oASKmqRtKpV5CKhsCisKKLvBA2DPkwIBEvIWqRJSJEQScH73+A8xif3xX3vOff+Nj8u8+bOnJnzv2fmzLzU2EjPiznzIYyWKaYAIiyIsCDCYiIgwoIICyIsCBEWRFgQYUGIsCDCgggLQoQFERZEWBAiLIiwIMKCEGFBhAUTJazNA/V7Dk6DMFqmUjVVS6+NBUxVvG/7GNCWu7R1sr1ly9364VsOU3PyUUxeM9f2wzDXmfw/C7PVCVOSbxa0s1wdU/B+ly3fuuJhae2Zht+dc8N8Kuur6us6OvVWxDYXaQbiLqx8OCPEr1uajjzqXvSs99Wa4ZM1G3deeVItkc2evvb6rUZRJav37hp+2qyaeiuHWJsEJmEiJIhsv5ZVbyq919O8snVd35vesf6u0abxkvGLf0pGPo+e/jXj+I4LR9+1zBxa9aH9sKgS/aqaekstqLVsgp7ckljFsyR8Pbu/n6p7OSjK8Qfazy7pWSA3Sy6KQ1ZYny4Nzfs5vvhE7e2756wjVdL/dfDbjwbV1LNaUGtqWb1oobQ2JClixXVnYBY4xRjJ5fz+q639fW79tur7hwa3W7pSUIlb021NMUw9qveMRTPey2W8vxtFEe2EFF1ECUvRRQ7Ws0RjF74gYjlLp41M9i3V0bN6sf3KkozkIH7yCsYVj425U0eu1RZbjteCpcVOMSaIH3k4RFDL6kU9qndZIqusfAsqr8L0FaeNuZwkp2Y4LE3FjyBamPr5cJjtXT0Gi6+xZwJro93gF1dkvp9OyTFym/jgRnfll32usLSJjlhMIageg7MuMw8ql7XW/pjswzxeBNPl+r7dbE4ZWUZOVywnOb3LKlkoa2X588a3j0e2BRHL98XR96VQ+xXrHj0rI9Mh51SzWVbZnNFarhGRFRbjK3H2KGJLxc07Ax2iL5FYy59NJibYI3olqX+We/tNZOyW0g7QQUDOuV5RKWvdY9jC7wWTvRSmp15uyHaK7ZdL3CTDHsO6gvPgYsevcyxNsb2Q0VZXJQVN0fMmL41CI9LoNFKVuEe1RKzINrxKzt0LYHtV7GMMttfb7kW4Rh0kIr7suvxygA4b7SWJtuq+RywbtzQiK6ys/w9DWNH+T0E5lPIpb3LAHIUlZrsIR1h5WRDtLZv+w+l9DmWEZUckTs3TOO+FFQ/RJO6vyRsul5UfWwhhtEy97po1u7wSwmiJsCDCgggLIiwmAiIsiLAgwmIiIMKCCAsiLAgRFkRYEGFBiLAgwoIIC0KEBREWRFgQIiyIsGCi+BdwnhLb54MehQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTg6NDMtMDU6MDB4ABDSAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9WRU4uc3ZnXIWjIgAAAABJRU5ErkJggg=="},"235":{"admin":"Vietnam","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACnUlEQVR42u2cTStEURjHDyXEICSUYqGk7MROaRZk62WtbCxsbNhZWbKULJRk5xPwAXwAO1NIykskJKUUi//maozuHedy731+m1+TGffl9JvzP/d5TuMKhd7eri4I/dIxBBCxIGJBxGIgIGJBxIKIBSFiQcSCiAUhYkHEgogFS/GsYvigc09kNBDLG2+Olxeb+0VGA7G88WXgcKo2/7SzP1mXZzQQy1sIvo/cnlY+iwQiYnkLwY+Hjy3XIBKIiOWBir+gWAQiYnkLwaBYb+uFqqocgYhYZfJqcL6mdSioVJB6l1FCrMh8zO0216+UEkvvMkqIFTkEFXmlxCIQEctzCBKIiBVLCBKIiBVLCBKIiPWNNL5CUFQxIkwgWpPPpX0lpC6eL4afq4rnrd+f/XX0aLt6V6/TXt93aZ+B7tzqeNNScRkzjdRd6I6YsRLBy5bpjbZ9fePTqJSuXHdBFCZ0DlMvLy1zmJ4rs7cCy+ziXWuUZOqlNVm2d0lk/Knwom9irH1Ay+EkKKUr0VXxVJiRiHyY2bzOnfz9HKYz6ux2ig7OZrumvLJCeZFns+1jtPJ+/pTv6eiOLyJ1ZJ2Fyrs53s+tLTTOxiGWjkxLx+jNx1f30pERy9xtqxQZ30JeR7bw9IdYX6i2SdyL92w0ZxArESFIIBoVS/EUPgSDVSi1X6L+r81AdIRg1MZL1EqYzUA0J1aY2lWYxkv4ZpE+g1gZL4qWCjL9PWr9KcyeMJuBaEis4l9h8Nt4+Tkirf3ig7Mcgtq55bfxEtwTZjkQnbUQFP9m/gjuCRPtdA+dnRD8r+2/wW3TdgLR2Wng/O9eKJ09S7vaEQsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELIhZELAgRCyIWTC0/Ae7yUNeiWB9uAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1OTo0Ny0wNTowMGONX/8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1ZOTS5zdmdx4ikxAAAAAElFTkSuQmCC"},"237":{"admin":"West Bank","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACe0lEQVR42u2bMUgbURjHTxSVgMYucahFFBVjSz1JIaKtrg6KgiDhqJQKncWpEEu1glAcXKS0ILg4SCsOrZO6KYIKLQYV1KHaUESwtA4loAgqnsNJCIj5vpjEHwc/MoTk8e7H93/fu3fGu+euqcIdTygjnOU3coyM8wvC+Plj8V5xhTk2nmc8qDFbMk9yD5gUKCbW6lHBhwrfzCd3W2lDeyT7r7uPqYECYkWzt8pV5ZkjIqGwWHYNIyKhsFhEJFQUy6mX/ZkuEoqJRUTCBIlFREJhsegiYYLEctYwIhIKVywiEqqIFauLJCIRy9STjIhELEW9iEjEMolImDJixeoiA8FHvpoSa8FatpZgevAWxLoi2Rv3k/KitV3v9NPRg29jhxPdJyP/Xh5unm6dXxswdXlTsTrzf5VViknm+LXwck/kbf9xaO/L/j635+6JpcztxqbxQOj/+6WW77XcpBQWy46kZFDqciQXNcwZkdwqKpacpkQkYinS1uuCRCRiqYgVHZF0kWkiVlKsxqIiEr2oWGJi2VusiIVYYkrRLSKWsFIs3hFLJfLYbkhJseJZmGss6ok8KhaPdGASikXkIZbGtmeybXheZwxS39Ebc6x/T8yobk2sy4N+rd6vD4OBV6+9HZPWwufNgTaYHjQSeRLLeTQ5OF//u3rQ87G7uW7FyAlaz/JgWlH9EJ/zZYoX908rh9qHW3/69pj6uyeWWuSZrq58f5hJR6y4ws5ekhN5iCX3ij2RBzUir3qyq8H/h8lFrBtWJro8KCGWY3+cyIMqFYsuDwqLReRBAbGIPKgiFpEHBcSiy4PCYhF5UEUsIg8Ksz9SO/t4nciDsjwD+PGwNqWzPpwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ5OjQxLTA1OjAwfBnZywAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvV0VCLnN2Zz8CRZcAAAAASUVORK5CYII="},"240":{"admin":"Yemen","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABBklEQVR42u3XwQnCMBxG8X+cQN1BXcQF3MwlPHlzjB46iivUgxehGGJNRMjvPfgubZGUh9AYx816v7O27oZX8KOdtumQhGWtsKywrLCsFZYVlu3ha1RYtknKwrL+saywrLC8CCssKywf3sLyIqywrLCssKwVlhWW7TWs50f4u0/x16vze+ZX8/d882z5PSWnWHb/p+dqfd5lv5s/b/lT2d+N+/l6ug3W1t2YgAYIC8KCsCAsQFgQFoQFCAvCgrAAYUFYEBYgLAgLwgKEBWFBWICw8LdhXY5kfSOtyPoKi8KisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWKSwKCx25QO7IGoQUExASwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjM6MDI6MjEtMDU6MDCSE+1yAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ZRU0uc3Zn7W2pGwAAAB10RVh0c3ZnOmRlc2NyaXB0aW9uAGZsYWcgb2YgWWVtZW5boPDjAAAAAElFTkSuQmCC"},"241":{"admin":"South Africa","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEmklEQVR42u2da0hTYRjHTxnYTVqLGhKVUQS1yoQKSbMgY0FZliF2oYgMicpArYaZszmwK1MjLbUGQSXzQhfzglSyIrALJVphmV0RNY3MIBJiQa8fDqzs3c452/ue/b/8P21nl/Pzt+d9z3MeBWFcelJMpPQ0tNp6Tca24N7ojn6nyWlxnh46f8a8TH0x+uPFjdfWVrS/CzeELecyzYvHzu9T+St6lIJcYJHUF+RN2XPxUvPTXXcG6PH6XGx5kHUAp0pNn1RmsMS5vancmBtB77Dv4bX91WncOwypNFiuDvta3LW7c/XQeJHHyOAw//EQk7ZTHCySowympXEVcBiM5SWHcVOHKfd3r76a8s/xfQCWzA7zyteE8pwDY8nmsIXmzUf6UVGxCa6Qt6akUl8TctQ8cmWHb/EiDtuZWJmQf5fLOgxwi8FyxtaM0U58GGHXTc1eP+N00ZITHDtMXIdhz8mnrzsIFskex42kSXG8O+ybtbKqrA0OY8JYrsmmw+ylzYfvp6AO4xgslh22P7Qqs+inhw4DXt4B617wqbjxK4fGi02HzT2Wd35vtLsO6/qR3nVwGRymOFjat0G/AjTpUQmZYx3dhaV5Wju9wybqTWtX5bNTh33a3Hem55ZKHKbcu5J+ZIojCIJO0AqBJCPK9AWBh+rbckI17/3UYT49Geq6pCMCi6QUh02emVVvmKuSOgw/lPKCpSaHLUosWJVyzEOHAS8JPv4PWFIcluMsypj3hp06DGtJhowll8MM30/uiyzj3mFYSyoHFkmdoHEGWPl1GHkPxGGS1pKATF6wpDjMUVJqCFnNmsOq17VaH5/xsA4DTEqApY46LMieNRA/nN5hpIWa9FYgXVM2sMS5IiZMNzLMfxyGdE1FwJLuMBb2wzQWszVhurt1GFJxsKQ7LGrY8QlRAyxsVcBhjIIlXkuahW2BQRt4rMPEDmvY0f6suRD5r/QqWK4Oo++tYMdhYpP5Q5JljbvP8hlYYoflTNrREPSNR4ch/5m+BUuKw26nXh03PZMdhyEZBcvVYcRPNA7LyC18tCAbDgNYcBjAYslh9HUYcRgL+2EAi4MkeNHYS4wXTjPAkqHqIlaDsQAWaiyAhVUhEvtYSP8FS8qKD34CWCr0E7mCJk53r755dg1OynO9eUyfdTfQ10/obkB3g2r9RPqxyFeGXisf92Px3rmADlLmwOK3XxQ978yBxfudhu76iTym9qbD8mHL5YTrja9ikeIUcE+Ou356ktaS1D0Q35k8v65O0M2YffYc8i8JP9H7KTfZ9rDJNi1k6YVLcUBHZrD87b5n+ElBsNQ0bYb+Lmf4SUGweJ8tQ2b8wU9MgKWOqaTu7j/BTwqCpQ4/0c++gp8UTHVMHKWfmkzqp3Nbr9haumc1Rm+6nA8IFEnUT0hFki8/iSfx0U8TJX5C/cQEWLzPcH8+9fWIL3PgJybAYrl+ctdPqJ+YAEvsp8EOQPgJKSXZmaAHP6lrH4vD/6cKPwEs+AlgwU9I7sCCnwAW/IRkFSzip+1N5cbcCHo/kV5p+AlgwU9I5cEytNp6TUZ6P5H7W+AnteZv4kIiSSgh79MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIzOjAyOjM2LTA1OjAwmx7TYgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvWkFGLnN2ZzDEwIEAAAAASUVORK5CYII="},"242":{"admin":"Zambia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEHUlEQVR42u2dS0hVURSGdzmQinxRUpMSg8JZED0oGkhQQU2jIjIoQ1PEUKEHJRFmUeRIMoLIEIIG1iQaXOgxiKBBgxAaRA8ocFD0wB7TCv4OLNme2+ler2ff2zf5Oex7HpuzPtZae+11uG7x4vN9zqHo9CqvAAUsFLBQwOJFoID13+iyH321gIVOizZcO/HQLd+2vX2Nc0dGWp44d36oq825daNH785apF+lye+pa+0d8p/h1syRGuc0z53HW09G44AVqK4d7xhz7tLnveecG/50+L5zmfbmOufGBjteRSOXrx+YHQEnA8f5trYHPbejax8fa/kewSrIkuC4p7Z71LmWDYfGorvZWUk1H0EGWIGqzCmwhIJVISK9M9w0Lxq/caZ1o3NDvd1XI+AExOOLXZedmzjYeyU6FrjWh1mAdK3uJmjinm5hvblpV2YqWDFnoNmVPIT8gTXty/Lj9yLDCy+p9W32TIFlr7LnWN8j+PQse2epj5RU/owcqyiDY3+mdWQqLKzhfQ9nwZJ+Hj25ezJMFkodywPZZ+k+UguWZhWXt2G8oknqlSbLnBYmPzhaH2Ox8OHw8fIBtQFXT1dux6qwBAOlUFMYkrFleD8sWvgU7PwgKC/lq5L97EsEwCpZXfitp8nNF2pKorVG0wpOqPk5lvVYFqbOG/tOxQc4wEL/qAKoMLKrRa095ZOkcRUpwEL/Uhuzx8JI3q4QVX5efYlXxZSByUvZ1aXAYksHzXEtaRN8waQMLEn9HbDQRJCxCY3SNoOigIUCFgpYaN5ptdZo6/vP9k/V1hKn9lpVpPJRzUdzyF8xbco6+HXL+NzKZ5UrBiq+PG1sqK0YyU1Xddd8KBuoubig+bdR57nNk9WOZz9Wlcvfuv5XxbQp6/Dpxltzf75duWR31Q7pm4mlF6qWS/0R6euPdfVV1dLn9UteVHYKLB+p5CqwtPljm22yq1pxpHYc0wYBlo9LHEb+OQJr9fvyR2Xv8gGremLO/slgxUFjxwELsGYILB1j2oBCYXaM0gUriQJW0GD9K17hgGUV0wYaCkPzWHYk7hiwSgQsPBZakBwLsFDAQtMDK/xQmAQ46lgk79PmsfzzMW3KXwiGWcfKJwgCFjlWQUIhYFFuoPIOWGmAlc/aENMW2ZaO30JDuQFNlLwn8V4hgOV7NUIh5QY8FmCFB1YS7DAtbTN4rNLNsbJ3tVN5R2mbASxyLHIstnQC9FjZ+0gxbWqqL4ZDBovKO/1YJO9oMedYdDfwXSHJO2CFl2PR6Ecdi34stFTAIhTSj0XlHbCKrR8LsCg3FBAsGv0Aq4BbOngs+rEKHgr5Ejo4j1Us/VisCik3zGiOBVjBdTeEnGPlFhYxcMqq/3LOzVdZpdyATnMo1J8PhPBdIWAFnbyXhsf6BbYQ7ys0lpLUAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMjo1NS0wNTowMGyZwHgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1pNQi5zdmeyhqY6AAAAAElFTkSuQmCC"},"243":{"admin":"Zimbabwe","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGEklEQVR42u2dXWgdRRTHJzS20ZjmUnKTfpASiQqRYkJRq1YtiBhCY63GVqW+FKH3RWmtHwULFmz0QaykRLQW0agPlaapF1uEktY+NPGjKkroh8QQS9s0aUJLmsZCDXiF+78P5zKZ5ezu7Hb35hD4s+zdzM7O/vb8Z8/M7qqOBZ9f6KituVY7t2Z/ck/jiqJnGna+1z3niaV722+Z86+ortI+HFWZ3ZnNmU3HHj/2SU/D8omH1i7vSC5a8ltp+vY1G5cVn/DWiHabPjonMgo1iSbWtFZYzoEFHXpk6IGh2g13bNiSSpePLPyp9ADwcj4kt+tFowNZcOcoDyzo1G1j4yNVH019/NKuTxG9wrfIQo15hXfhmY5iGrCo2rVIOW0zro/lrM4WKaZTGDWxWzILLKqwSHoXKVenqAWwprHILF6dZ7/aV5Q4Oa/37aJOk463/jhfKedtwlSpT3D18QiWbpF3/3XXWFkq3da2TqnMaF+Hull0JqsvsEwWua0oNakUeJcmFrAsKLXI1eOPPq+6Tv2d/l3wErCsqFikqGWwLm/8Y/3BX8UiRa2Bdb395I6+a+ePN6Ye3I5lsUgBi4UOhnpGjxyferUeevXJ4esnPsOvo/2v1KcWDzxWXVH2IZY5Fvnlwebm4p0XVz+3uVSJxl37W589VNWAZRdg5ZA69N26ZN/VA4cfrkxN3N/z1NL5kzd98+be8rPbl+2u6xlcVX1nogXLetzSLbLuXPLpilXru8r+mT0JKFECR7H96U31leUvDv5ZsySxgKqxNGzD3osv1WpFNVfzwPZO2+dMf83Xia3Yb26ZU2dP+8UeFb/nBIz+m9d7qqqR6vDs9qaqe1AcDsY5bpkssuneki9m3de9duHKWw/TcpxPT+DKQERUV8U3QRjfheIfRlItOl4IgxQITtzSLRIxbNfKisUlmbwrbAaAZTpSrI/CZcavg/J230eRgi2a4talI617tv7iNtGqW6Sp0Z1/xXqqbrcPE1PLALEviSCO1CNY6GnR/hbFi0YvxC38FyKf20QrtUhvTeAMk+vtA4thdoHmlEP7W5yLjV9PRc0OtgUILp5Ov9Y+fPn9D3rfHUFvaXjRmramn5FQGPi+8+WKc7ohQgGcjhcU5aBMxDN0/6GoA+pjskgaEfUYY1f5Tcz/dSZoHlg42fpdAz2RMLsr2/YPVDabwDJ17Wk80Lvn2C/qoMc2apGIYTBK0Wiq0jvpiFLU73Ox4Wj1G4l3gAiMj8YnE1gmi6Tg5uwyu19nu6QWqUrlL8J/plMIY4LxASkasWi/ioMXhQx4IUqhfOzL21iknMKYgQVFigH2BKSAhQkmrEEPTO/aU7zOvLVjvHaWPrboVvFcJCZMy+mMDVh594PoyBNc9GgEpKjSX6EoAfMMB1/o2tJyBRHIzzwwASvGYI2t6Jvb2qBHqfOv7+tMfgvVwTIlJrDGW8QSKywosJBzp6AAHYoL1lDI9AiH5Usl3RN1R+kwttv8lpy8QrFCLTWqxyeOYsIMTJA/oETndUm6IWbpBk5Hng5Fm/BCxAJAevTCGsQ/b2OIkooMXzmjBdMkSF1M5ctiAbCQJsUaGskoQCb4nHtXxiGdbOKDM6aWO0iyPWeskDMA4n8sLy8F7bscW/XxVo4JQdd3hUh10n6VfoforIh5pt4Vtby8QWggooHCR8r/GFw4Y3n808xpB//leIPSzUS/bDYLKU1M+sN9IkexPXJX+qAN3/IipJaGoq2hT0YyOObldhoMZ9ieptBdgIX8uNv7OL2XRvPszrMYYgCWf7wICpwpPf7BCm6eFtVAHv/i3+Xp867yBqe1gfBprND3dGGP5QSsnInatrZhtY9hsrJ+1m4AWHYfpoCxFt4jCejFxv2RipDAkse/5PEvyyoPrApY8oi9qLwURFReYyQNLWBFxfIESv+KGW8xA4vzqkjTiwbp+hv1csQw9xuF4w2zffC/ys+MTflEiqi113GH87Z3veQo44u6yQVm4QMC8r51UY9gFYblxfczLWHWIYiaKz9foJD4Ierxs3JiZ/J1MY9gmT6EGbUOaaHiGOan4YJuQ1r+/+0VpDbmjbldAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMzoxMy0wNTowMGTBkIYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1pXRS5zdmei61vXAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/1/0/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/1/0/0.grid.json
new file mode 100644
index 0000000..6fcd565
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/1/0/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," !!!!! "," #### !!!!!!!! "," ####### !!!!!!!!!! "," ########## !!!!!!!!!!!! "," ########## !!!!!!!!!!!!!!! "," ######### !!!!!!!!!!!!!!!!! "," ###########!!!!!!!!!!!!!!!!!!! "," ########## !!!!!!!!!!!!!!!!!! "," ######### !!!!!!!!!!!!!!!!! "," ######## !!!!!!!!!!!!!!!!! "," # ####### !!!!!!!!!!!!!!!!!! "," ## ###########!!!!!!!!!!!!!!!!!!! "," ### # # ####!!!!!!!!!!!!!!!!!!!! "," # # ##### ! !!!!!!!!!!!!!!!!! "," ### # # # ##### !!!!!!!!!!!!!!!!!!! "," ######### ## # ! !!!!!!!!!!!!!!! "," ############## !!!!!!!!!!!!!!! "," # # #### !!!!!!!!!!!!!! "," ### #### # !!!!!!!!!!!!! "," #### # #### #### !!!!!!!!!!!! "," ####### ### ###### !!!!!!!!!!!!! ","$ % ####### ## ####### !!!!!!!!!!!! "," %%%%% ###### ## ####### !!!!!!!!!! ! "," %%%%%%%%# ###### ######### # ##### !!!!!!!!!!! ","$ %%%%%%%%%########### # # ## ## #### !!!!!!!!!! ","$$ %%%%%%%%###################### ##### !!!!!!!! ","$$$$ % %%%%%%%##################### ##### !!!!!!! & & ","$ $ %%%%%%%%%##################### ##### !!!! &&&& "," %%%%%%%###################### #### !!!! &&& "," %%%%%%%%################## # ### !!!! "," %%%%%%%%%################# ## # !! "," %%%%%%% %%################ ### # ! "," %% %%############## ###### ' "," %% %%############### ###### ''"," %% %################ ###### ''"," % ################## ######## ( '"," % ############################ ( '"," ######################## # ''"," %%%%%%%%%%%%########## ### )"," %%%%%%%%%%%%%%#####%## *# "," %%%%%%%%%%%%%%%##%%%%## "," %%%%%%%%%%%%%%%#%%% +++"," %%%%%%%%%%%%%%%%%%% ,++"," %%%%%%%%%%%%%%%%%% , ,++"," %%%%%%%%%%%%%%%%% + "," %%%%%%%%%%%%%%%% --"," .%%%%%%%%%%%% --/"," ....%%%% % % +--//"," .....% %0 -12//"," . .... 00 1223/"," % .... ..4 4 0 112233"," ...... 5 67 8 222233"," ...9: ) ;;2223"," <== > ;;333?"," = @AAAAB CDD33E"," FF@AAAAAG HDIE"," @@@AAAGJ) KIE"," @@@@AALGJ)L "],"keys":["","89","40","185","228","107","79","104","74","201","68","180","137","142","64","27","187","152","145","53","55","97","63","233","90","95","191","197","162","231","23","49","232","220","85","83","82","171","92","196","44","204","126","34"],"data":{"23":{"admin":"Burkina Faso","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACsklEQVR42u2cT0gUYRiHB+lSIBFEiZQSdmi3FS8lka4IbpCdIvCWN08pdBE2IuhUXvSW+QcSzYNWiOihoJAoqIzwIl3qEOFBxYMmURZJdPjtYZbZz751vwVn57k8LDP7veO++/D+vh0GvY2N06dqayF0S48WQMSCiAURi0ZAxIKIBRELQsSCiAURC0LEgogFEQtCxCoKv12u+5LooQ8RFYuvH7EgYkGIWBCxIGJBiFgQsULPzYWWg7GvIt1ALGf8MdldWXNIpBuI5Yy/28eTx3pFbs8ilrMQ3G6YrzvaKRKIiOWAP191Pq9Z395cPHzkgEggIpazEPSL9eve0Pzxz3QGsXYbgu3Jqfjwn8W5hxUf/GIRiIiV45kI6WJDRV6WUoFAtK8WtS2/F7VtuIJMc2hnZjbsBrF01qaOrhi1CedFM+C2xvouVA8apXFEXUVXjFwUfm+L3U+8CS/9HybfVfrFZzWfbOibYaoc9t4WQi+91fGpIRll3ly+c7X+8dOu17GTPYWIpQqqRlc9zxttbp0OActGHrReyvF6d3UCrOgb70qle1Mv9iWW8lVKq1TBVN/4WUqWpkaEhRbS2LPt1nT63PV8xdIqV39DiZAW+AUduDvXGH9i2j+ZxNIqxEIsYxR+vPF+pupsUJ2V8oVrlbfF4Fmt+k8UIlY0Z1VWCPrm07v9b5dPlF1cnbpyvl/UkaBeOks/S10s+2AyhKCOZOaQr5qOmN6PUkysHCGosOvon/17Zs1GR71TqwhExMqiIkx3oeqfPZpoasq3glapAoGIWBnGX06kmscKnzSqoGp0FbEgYkHEgohFIyBiQcSCiEUjIGJBxIKIBSFiQcSCiOX6OSe4d545Y2JBohBCxIKIBUthD0f7IBMLIhZELBoBiyYWtz2hY7Gc/rcWCBELFpH/AJxrG58LvHo3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyNjowMC0wNTowMGH4u/0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JGQS5zdmfL0P6AAAAAAElFTkSuQmCC"},"27":{"admin":"The Bahamas","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAB4UlEQVR42u2dO0jDUBRAryhuDgVRKnEQu4haQQWHDEpBqNCt+AEdxVU6uLqo4OxnMDp0EQqliIgIQkUF8d9BXRxEKQhCt6IOTg5ZIsWCmheb5CxnKSk0OfTk8/KeiHQf6mmRuaChiaST5wUI7WBAQtIsEjipf61qGtmYLIoYl7tv7Bpoh1hWSutM27ZIIrrQwQ6C9on1RTK9c3BJZDGffGFnQfvEIpRQoViEEioXi1BC5WIRSqhQLEIJlYtFKKFysQglYjlEQolY6iUjlIhFKKHLxCKUiEUoofvFKgllfyQVXj6e0I6mjYzJsceD+bVcKb/7tPxW5b/td9v6mZUtloU9q+GputvN8ZXR9oePwtNzJO5PFvfuUwMxZ7b6C10jlpXDp7G7hvWzrp1477WfJatkulIsk8GrRq02NxtKVLcM5aMXWX2fw4lYCkPp/N8+9KxYhBKxCCVieYWEErEcCuXNe7amb4vDj1i2xRGxEIsUIhYn75DbDRCxeJKIWP8aO86cEItbBojFlR30plglA/28NxDPewMJzd8ilSkTQ5MZmszLFJDXv6BvXlhlUl3EInaQSUGgF8Xiyg4SO8hUkZDJbSFi/VAmc+0dYgdZQACy5AlkkSYIWVYOOk2e2UEF/ARzD1bDiEJXIAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjc6MDgtMDU6MDC91Z6kAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CSFMuc3Zn6/pbEgAAAABJRU5ErkJggg=="},"34":{"admin":"Brazil","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABGEAIAAADldHp9AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHTklEQVR42u1dXWwUVRjdGiQYfCiNsmmJlfqzqAGxkbYKwZjGUIk/JBIWMfFBIrpW/GlJfNhYSCQEESNaEBRrmq2pCJXSSLDaNJUEUwMqldTaJaikGqsSElATrFHQh+PDl1zv+M3cO7Mzs9/LyWZm587MnTPnfvfc795JJC57Y+f8BYKCllGqQFCIJSjEEhRiSUUICrEEhViCQixBQSFWAXBRc3f2hplAqQ0hlhGWv96RnteyecfBiYr0qcr84xd/CcQW7JVaEmKxsHRP+3BtLr3/vZHUY59eeezC1Pzf42P1ickUL2RP9iRqsBf/xFFSe0Ks/8AbG9+5aG7Xa6Mfr08uhTJRGtHfdAv+iaNS5Z0D1a9KTQqx/sXM1b3rrnoy/8LIm1OOUupwkCoZSkBpomGJYtanvZ1HystavZFJtxcahpJxFiFWUcRPzQf7ls+chMdvTiNnDcNZcMai07DiMQsOnPysoXS/W32yRT6c/ZYf94zOWSjEirxZsGHyR7fNyPP1yZk0at/QLeJKcFUxNyzi19jd29jTft191Cxw++APL/zg6/Kpb3e016V+bq17paJmxfPZTefqpgGxBXvxT2/EpYaFECvU+kTNAj6N3q/uWlA18OgdTUOL7pr9UkPpQ79cPrtmdPU3iQ2zpjzT4oyX/ll9pKUVR6EElOb2GmJoWERdn6hZwNEnPEjoTf309L7l4xwCuUWUvHtbxyWzPuRrGO4iJqZr1M0C1czU9dG+qPqkftq1D8zL3H93mx9k0ukZzoiz8+M5aFiEDYsomgVUnzgBNZonNFjBUEpFnB1X4jbmi6TpGv5LRBedmgX8kByNET9m8htxJW6byEgaFmEOxp9N9uevGHNrZgKhDeGhlEovt+qlGhah1rCwXRA1C9yakDSWKmzDx28cObGXrh5QS6gxIRbLLPA2kIJjgwzPzRFXa2LeUsMiRKZrODOfvA2kwESICqVozxFXbuLphy5LrLCZT94GenX41Jl1uaaVDdlVE23XLMk0PpJLpjeumXjr7BOVLybeHQCuXrFt065WbKeIo2pPL/tr66HUloahzTfhkQdDL/heJkNPOsOiYKZrmM1MPu3Or/yh5NaSzuvz5/q6DqS+XzW4dMeJ44u7M0BswV4gtg+W/fTtcPPa4c/XtM3B7zMlv23/9Y+eh4f7D9+8M9czMDgDCFKCdpV7b/99Y9YPevHDeX7tFSxLLBizwFvmEx/Ht3+X6+oFgYAgB0VQipJGRyxKO2zB7619x45234kSoHxU50A+E2JhUMhWAo+KeAoBGRZhMAvMc5623LO7qrdJ16glM/NPrJ8EpaGIvXMPLXnw5SSaTtpookyqYaAXflM6grIgK85u0k/01n1xa1j4niXmX+aTSdXwzQXkF1C/Co/WVoMFUoJ8lHCUaiCWjmT8WA3/xB0514At2iHY92Vamx/TpPiDLW73qiWrPcFgQm8QFxoJqtHmEs0xfmMvX8lwRyYvmzfDAk/QWrBv1yzwL4rSEQvZUYW1DKBqaHwRe9GoTiUZ9E9XGu7IFo10L6euVq0ZFrbMTD/eJw6xkHxXWGJBvahGQjXRdFINo6oGtVOVFSG834GE8/8tmK4mxLLb3jvP49PRy4RYUBqTcJsTM4FkNDKjvU5Vw3BH5mnQ5uQLlFi6JGC/tUpXpnlTCHoF47CDxKAXdAskwxbspcTSvU5+1LPaFIY0eDd/bzjpe8EM41BjwlZpiMZoXxL0OjVy/LnFQ/69irrg3fIUj2hNtOLYDVSH8AjLltVWrH3aWz8RR6EEqm3mvU6UgEgLlIJnhlEEP2IpXY6XL/kR/g3gUIPUPMB3ftvUJBkE1LA9TR68qlLYYku9qPeG8YPz/WP7pn9lN3hXLWU8HR+zIaI4pKNWHHpSIBO6/c7NEN8+VbXKlt2qRnjOQzrmGPkhHc4gtF2pR7IvhwR0kKdQjhfVUZDMOafUJGCP7SA0J23G1viX83QuXdPGQaigsxbyEdcAintLm3EOGEKxMEl4Ev3cxli62Ticpo3qB91iqwF1tlLpoBPQeXoFPzalycpFl+jndrTRrW7xU5NpHoSJcUCpSekCZcJeUApbKEElNTnQlRe8TfYCBjmZgtJIpRoUEXtVtaOTKbw5fzKZIqDpX2qzGMz0L11vFIqodhRoqO6WUtQskOlflies8jGYCas6TVIHp3UTVjmRpUxYDZFhYXeKvVu3nQbp3qbYR3hd03isIMpZ4Tj4RUFoeO48MVXtrER+FdO4rsnu3KzQZYz8yDVFyRx9UtddjslSbPFY5gsJtW7nUsvCa0IsC6s/2FoqEmhrqciQmgVCLP7itnaTp/kTRmRx29gi37AwSVF0PlaW45YPCFjLGUcwjjMW3dfC5JMnfsx+kU+eFPVHmqhhQU1Xb9mb1MyUT1/J17+MFn+Tz8oJsYymtanOfsy/KyHECtKwkE/3CrHkY+NCLEEhlqCgEEtQiCUoxBIUFGIJCrEEhViCgv+L/wADAE2QfXfbBwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjk6MTQtMDU6MDCoFsT9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CUkEuc3ZnU5e+DQAAABh0RVh0c3ZnOnRpdGxlAEZsYWcgb2YgQnJhemlsnLDlWgAAAABJRU5ErkJggg=="},"40":{"admin":"Canada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC6klEQVR42u2dMU4rMRBAU3AIGmqOgESNBC0VN0A5AhWIkhtwCQoqOiQKRMEFqLmIKZ4i+ct/FxPbG2/2NaPIduzx+sUej2edVQirw9VhT/Lo/ug+hO+777sQwmf4VA5LnhJPrK9xXAmWYAmWUrAES7AES7AEa1fy4f3hfSMFS7AqyK+zr7MQjtfH643sRzfBmjFYN5c3l/9qSIpgCVbRXJUOGynkCpZg/VmuX9evw3qSK1iC9R/5+Pb4tjHJ4xY/Dj4OfhswcikZDzO1UbNgLRos2j19Pn3eYHF+cn6Spy3fwuriM+mCtWiwXq5frtv0gpoFa6FgtRiYKV0SgtW18Z6/8OVIatN4F6xwdXF1UU9/ahOsRYCFST7keSK3lv5Dxz7xnlGwugaLMuMlYycnc0lavq4Jn5rttMgSmeNczemXYDUEi18/xjLQkMLQMnjpbJQ6Noc87NtpHkNDL9KlNp5B8YSRQkl6lDO3CVYTsMZnmvHHHeNFW7EXajtJDePnjPka5jgsBKvhUsjvu9weKgeLxS62pVo7LASrofFevqcDqVrDU15b/r5SsBqCVXdP14PM3zkKVkOwcg6M5yXjg23B2hlYGLn7ARa9yD9nFKzKYJHLzm6f5qr4CdC7nOcgWJXBqnsU05sccucK1kTuhnFf0RxlzlwlWBOdFRJYN99lEc3/Gh4oWBMdQmP2lrhMp5dou11goGBNGt3AGVzdKKsWEg1L3vYRrB2EzcQ7x/laUYI1g3isHvqVvhEkWLOPIH26fbrdxTDQIq0bQbq3ocnTe7/aRcELVhdglYfZ9PbejmB1AVYaGEhbDDzzCvMZTle8SnyLXCSLGrmU5FsEzFBbOtgt3jQUrC7AImogxoWU/DDo/HBhnAi0Qov5MQuCtaCL15iZ+rnASLAES7AE67crjXq4ukiwBEuwBCvP+yVYgtXk4pAp72UQrEWAhY9KsARLsARLG0uwFgtWn3/BIlj+l45gCZZglcsfHBPAdZqOceQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjUyLTA1OjAwchcLNwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FOLnN2Z/LyUl0AAAAASUVORK5CYII="},"44":{"admin":"Ivory Coast","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABIElEQVR42u3aIRIBYRjH4ZdBFh1BUVRRVzSKZFxA1jmAISkcwA0EM04gusYGxREIPnz2eW5g9jc7/u9spSgWi8heozts7ZfVa2c2muf7Kw6r2/qyGTdP/U07pnGOXb6/pRogLISFsEBYCOsj6jGIrQcpLJ7J/NAgrF+1jV5MhAV/GdY9jjH1IIWFsKxCvLEQFsLi89yxrMIk3LHAKkRYCAuEhbDKvAqFBVYhwkJYICyEZRUKC6xChFVavsciCd9jgbAQFsL6PncsYSGsfLhjCYuXuGORhDsWWIUIC2FZhXhjISyExZu5Y1mFSbhjgVWIsBAWCAthlXkVCgusQoSFsEBYCMsqFBZYhQgLYfmP9X6Zf5XljSWvJGqe3Y/K/APlB6G5QLnEpZn8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMjowNi0wNTowMMpN8X0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NJVi5zdmdOMQxzAAAAAElFTkSuQmCC"},"49":{"admin":"Colombia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3ZsUrDUBSA4Tvo5tCAZHKUOLto+wBdOnXo4lLwCXyD4KA4S2kfK9CWvk1LOujQIgGRY2zkW74h5N7ce/i3pO12ucxzMtZkBBQWhUVhGQSFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWOQPw6rr9e5y3EUPr/FXX2/nnW7d6zOssnw9n2zIWFNKdxfzh2j7g9nid3Y+5dN+7POd3bo4n6YzNz0/WkZGaQQUFoVFYRkEhUVhUViksCgsCosUFoVFYf0rv/6ZZ4uePd9P32/JWNPs7Wo0fCFjTaub3tP1IxlrqqosKwoyVmFRWBQWhWUQFBaFRWEZBIVFYVFYpLAoLAqLFBZP2z3VWG8eJgK6DAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6NTAtMDU6MDD2X1rqAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0wuc3ZnsjhgTQAAAABJRU5ErkJggg=="},"53":{"admin":"Cuba","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFRUlEQVR42u1daUgVURQ2kkgCabHCMC3wGRm0R0UWhGErSUZRgVG2C4VUP4y0iKyoJKsf/qmgjZIWopVSishooY0WbTdbeCgtlCZFBC/wMxiZZrp35p5Z3jt/Ph4z49zr3G/O+eacc++NKj3SIzXj9oifyQmro6M69v1SkhsVSM3ZuV0Icb05ml+pP6s9It6KLBq1Ittb+09A9pnQtWvt+Ri18rAgLiO1/NLFbiuGPc0v6TlrxrT4mj7LN6ZYJJYqKuj/Gbo7qx1OCorQEUjVK6fHB1e6fOwXA3oBj4W6Z6fNnv6pd9qSNq1IZm7PVD0CkX+G2pKpJbEqy2HnOHW7+rN6YgFxHI5yysFegaXHpYllh3D2HaIqEqt1K245OLXWV+RuIJARgmTX47tmDXxSdDAhPnNNYELKkMISixbLO4bdSQ3npEakILS1V86cWHqSQY0tvpXUJbviH2qMTp3IHqejkZ3XyXnL4TzpZYklrca8gxSOzN0hlJXYFC+/iMayRiytozQMWzgvJL1AblmF55bFontWdohlpMY2ViYkZwZb1JjXrBejM6iKWNqz+K1VY73LhpQWt088mV69L44xElCaWNbwatqAtZPrHtTuzjxy503Wq+P1N99ODeY0bNOj+Vm16GRb1O167Z7kxGqlyWoSigYfra2c+3tFUlPxzf13L4Z+hd6Hgozhh38DpM1D7oz1un8ltiYl//GqwPtRoz5k55/f1PBz68u8N215MMKQWO5idWh4+qS6+oJdZ/au/1UVPF2fygMTocSis3Av9k+MnvP56/PzNy53+F35NfnbKR4ktli2dJieuLWdFhSufPQ99l7Z4+E8VEwsxVg1rn9l+pjg6A3PdgxiNRbRxIIwp7jzsw1jDkwr/jh3T5vDH9hRRhCxQCnIcAodhi9K/H7dbUbnRXe//aiYdC2aSRbmxMJgw2FBhpPrs2b6vjuUl7EuC2qsJbh3Ibi5cbwfw57hEbZVTCzYKnAWMSqtjVEl7Y3wTm0gZ/TeLWdGtss9N2zm2IW7Ezm14suUDkgDhOXQfsEhZKB3YfapJoIo6WlV+8rozSQ0wpgIAegRX21arYNQJ+yW9sq3ecsa8/fgN53Mh50Dif9R0uNMzTsTS1aY2xHLIByISBdoNaobw0wkw5IeZ2qVmFhGYhn2RjaqBEcJge9uMBYlPfMmJDXNj2mpffVaqaD3a2vp6rHgHKGlzCn15dWJDufGItTpjMYSJxa7RXJiiX9/aa+v67U9vrTInFgIB7hLKSFXyOiWxdK7RX1llb5CARZLbXBBfOKahHiPTKSwynYCDQiBQsgDIe1xHIkXEAtqDK5T1m5ZoxqHG3xpsUAObWIYdaHas7Bn2lI+OEQ6Z8cBUt8HSLWkQWLY3A7BhiGCpTZviJ7gzvjqRDJHNqXjx4SMW8kfoZSOnUgS/lbEtamV7WiXk9C+TEI7WQUvrrG4bIYL/ZQlZBDx50I/JlaM0Txp2dJkrXLioYo4Yql1mi2TKaaeHVp+iZ0dWyxl07+YTBFELFXRcH2tBE9YZYtla+EQnmLPxCKR8PpFQZyvSWd0NEBKZ6u0pSm8jBGndCx+8fHCa4yG67zbcXZCpSlq1+v1zrKI4bFKqtrnY4dM2sVtPTfA7rborxosunosow0EjJRTq+W4xdfxVbuoP6O/SpP1YQLDDQTo3g/vr2bud9JT9998ypSCRbZlF/IPj90imGpC2wLY74rsAvbsBP2O/1FOqjYF8QtRKPpsf6M2d3e4sNZnkhksdPsyqNrDgm4DTpE+qLXW4tucOEOpZvwDsTW0wvU+jjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjQwOjA1LTA1OjAw1msuXgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1VCLnN2Z69F/9EAAAAASUVORK5CYII="},"55":{"admin":"Cayman Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHRUlEQVR42u2cf0heVRjHldGSbKutH06TGijkypRopBAELaF/XEKO/JUrc21tLR1RSAMh5wYuVuJW6Upj/bHQrK3VloOgYJsL1G3NaFZsCdpPm1mN/kkHgR//eOBwX+77vvfec199/vlyOfe895x7z+c+z3Oec+6b9PW3edvzyy9f+XCod2p66Kdd470zl6Y7Z45JfX+ke/Hgibu+uGfPjitJtyTdurEmfi28qbCoJYPr//JzU0VT9rknlmVdXzR0Jjk3eZVUzv6R98/O/zr5lbd94O54Aj/sL364eIZ26Q8lF6YPpRz/Kr0nvfalHq9an+c6/Fvmuszl8iH+vezozqP1Jl7jG75ffHHL7uaWw31Xcx7NubvxgldgjR/c8t7mURMpCRY14weLnnMX4ELrPAGn5zB2emxo8rH473oBqXy4QMZwRoaMB83wxGbJTIvlBBZ9i8diAcSeoldPHMlxD5PUb0q+G5+4US1WFMpQmW9wtJAxbO7fadoFFyewGPLYLBa4x39f8bw8C1pjGwwikngGw73Fcg+WU//dWybZf2/juQUNlvtBiv+N98piOcVMaplCCpYJWTwxijl40VosEFxT/uDx3du9tUwKkzWw/HA3TPLdgDWSsnVlXbpMfKhlSgBlqGJT5koyGUHsJdUpNybVfYyFgo5sxQkmc2YX252Co91ZIU4fS09P8CGb7n9uV3sWtpzytDVLSjevsAwWA29XsUmR0w12e8hrYwusOyqzzm68BECjp86d6foSmz01OPz5Mx1SKe++7uDadyosW2VciV11yrnLED4M/VzxdvLqqkXBDxKWCaQkRr9W9zUsv4r+1df/1uosyqkJiNbAchpOVamftaTtW1oSPFg4NdyxCZOpErtnN7St7dhnzS0qNOEEi4iqLPWp7Pp3QQQ7BEDna1rHkielSryG3zyQvze3+FTx1GsPhdRiRXZSCpYfSiQHTDkN60urT2YuKqm9c922/uaJ9akcJxXcvi11xzXP5zXeMIo+fVtdx71V1LGMVJAxFnsoEjfGCt5iAQcWS+IFRiZYlKDUJ8ayNpMNw6ww8u6GhTwrJLlgghVZqQmU1sDyKo/lpG5yTtHmsUxl+M22ACL+PFbbJ709g0eCD4RJGeAWwQXIKEFJRsgSWTMhM+/ksp1gIg8uHRm2h1yL+8y7BItfuWlXulHZbvgz7+CLpcEhspwF3PSc42P/Dly+2Jj90dbHe0spmds3NosU5dZSpu4NsnuYIg+qzCDHtrshWri5jgl32CADgq7hsuT6OpTUMbM8lPLOV16ebM2ihDqU86yqKwuuffFke8p95SUjKGcD3ajolWWKPHjmLXm1g1TubjD7KV2kG+jtbpXBrsgJDZMGjk/vX1Wx5IGBlZWNS2s5lkpNzqLyTpk8BXp3/sEU/H6syJA5uenwuMvCP3M/bTgFBBIjoOm+uaY2rY2slTyLclaWoBKsQO8oNjcXv1uJdmtytDtIo4UsDDEZYTjPFnTACETMpKiTSrC4zpzFmgU3ILBsxShuNvp59ZVObBMOEzK/Q2DAAgLTSjmhY9Yx7ZkFsGy9wWzzcA+WV1/pxAYZw+w3WFhZp5hJKmdRM/aSqwVgZwEscOEhyu7K7+n8cAcgQm6JeY1sXSpnqeltH+TXO1wf1FByYMy8mFv5nXnnZZPtyhULXjATerm9kWcl3R/lXDPQ4J2HZSpTX7/ztrIVp55gJ/yeKtMHWkFlSTD5a7JW0o6CBa831h2lDklgjilnFmyWc2xtVqhqV4G4oKv5jYHzKHaUfQ1k1SknNQp8HFNe2dTeevYR6styNFBXqMMZHsXRAwcYkT1n54JcqKFcQiPXE6kva3JNtVgL2mLJOI9tMHKBmW3HLOBk/P7CRP+THGOlZE1+i3Occ4WzFiugPRo6nOFZH2QpRi7wgwsWCAUsIifqcIzjkzU/3nRgrOp16hClsZEmoHVDHVS7Su6KtTxmf8xAsUY4RKCRzhErBTq4OcqpA0wccx2uyTyRdUbaVbDmoTIHZH+6TB/0frA3J/+QxEKqubOUYxlRmfVJQADuj6mHM8rmvvDx8YMLHeAgFWeH4tTACCURzfc2OEEsEzYJO0RGCmfHflHwoj51qM9vAQiYUNki1ov+eAyZDnaQisvDJQEW7oxhNr8TNDcqgguBuYyo5A4toi5+K69mtiXTsECmYCVweM5AYp+kRQER+W0guXigIa2As2PbMeUy9pK2ClfLdbCCXF+ChQtG1WIlsDLhByP5iSkqnRQlWDUQkfGTGUuBHTVRLJC0UjK6onVKyO97nOXSwQ7ebsmP5aXimCQKKCUo9klms1BKcJFOVzBbRFkt1eB9nijrj/JPPlCwMyMw+eWgCZzEyIQJdOSqK1bNx0UeHeAwZ+FxZ0CG2zJdm1Qwoj4wWdtsrUOYWLaNsB3gcIscY+ewQ74nPxUsVQVLVcFSVVWwVBUsVQVLVVXBUlWwVBUsVVUFSzVg/R8WI9snMhO89gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6NDA6MzgtMDU6MDA5M0h9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DWU0uc3ZnWteoewAAAABJRU5ErkJggg=="},"63":{"admin":"Dominican Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE0ElEQVR42u2bbUhWZxjHzxzry2gWRdZYy1a0IiqirL0YwYjZmzh7lkupCaKVZTyjFyshqC0TI9TUhKK3yTIrZb1AWeF64SlbraZGg5qrMZgpi7D2YRVRg35+uCWMkz4nOtz/LxeH+z7nfh7P8+N//a/rvnUcZ9R7aUnWxT4jalP3xmQnv//dxLuP2hbeCzwd+OTUk7ZwxevvfHYzUHD5Yc+cD/64cqV372HDbIuOpWA9i16AxWoCS2AJLIElsASWj8AKr7sywbITKYElxRJY/gFLVaHAkmIJLH96LIElxZJiCSy/gWUnXgLLc/MusASWUqHA8gNYapAKLPWxBJbaDQJLiiWwpFhSLIGlqlBgCSyBpTPvAktgCSyB9ZqD1Rkct979O67ZKWqo+u1AVH7P4n83lxI3xm9t/eFrZl9NVfi6bQ25+T4Or7g9uv5JOo0ve//zT3V/fdefayrW9Yq/ym8+Ap1v1i1r3bBvXOysiCUbP7w0vi7xVvBi2azCh8z2yh2XkFgWF588Jlie3rx4eeYdxpvTWt5qmdnZJrRt0ZlWnFlXeMTOmDWkrHhbPUAwAkaAcqHv8T1nqmsWnA+eazLViJGjoboxtde4M/rN5GVJFV/ezTyU8wmr3fhidf36C03/za5Jr7EzOp0lAjfjL3Yn5qwbH9Od9d1/yvORZIcCkeYAzkx8QNY4t+HBr1/9MmBTZGEC8fT3C+8vDezPKh279SxgHel3eGDVKvdurGv3hMsXvuw67r+zE972oL8iryAtP79iTREAMUISJOWRNKsTTkSej/wzWL274jIeojI3umREYqgxJiN2cEv/xpprhYAIXja/VaKjV4BiTU8pil4Ze3z71asXV6NeIAVelZOrdpe+gUrlrPsoNKFfXMTks7FJBxdNKpjxNio1dEb2juT5MZOW5M1+7MU2kcDyJVgTb+TWLf6JCEzoFmmuNHHF7bzW8vt5oeUTGGF2V9On6fE/Y+2dzEXfTs0a3mPt44xRlAICS2BFAQTuihHsfMPQvUU/rj0xOiU1fThIMZJanj1oSwEjJFPAQrdwYwLL0j+exEcSNBXLBAj/VNI/aU/GP0RGmMVXARNg9Vm/ND4QpGYUWFaDhVaBFJDhnDDmAERnC7CoEA8/nR7xcS3tCWAyUyErCyylwihUxwSLRh/XeCwcFU1RkGqvHJ8hZaZCKZbaDQFgQnXQLSq7knlTmkf+TluBhid4mdWiiRSRFWTepVgdzDt6YyZHIKOhQBI0rboZQZMKUe0GgdXGdjL1IFigVUS8FDF0bE795ye5ZpaeFls6KJ+0SmC1R6o8NmfMfjp9drMpSmSEWSDjqcotKTvn7mI1vVUnXLtIXu9SebfzBVJ0qoDGhIMkSAOCaBpz7uQpVmA19/uk3XkPXdtJ9G62A1i27bpzoIVrLDnnEbxIrDafHHFsPjMEZF6oJie9XJ1FC+95Na+j+/N2Xp9OfDXrd+0pwPLiaHIHsOw8o9udn8frn9/3YFn8H1COnUf9vUuFRIElsKRYAssPiiWwBJbMu8ASWALLcrBk3gWWzLvAElgCS6lQqVBgqd0gsFQVCiwplsASWF56LIElsOSxBJY8lsCSYkmxBJb6WAJLfSyBJY8lsOxBSmAJLM/BUir0Iv4PtC39UeHB87sAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ0OjE3LTA1OjAwotdpJQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRE9NLnN2Z0r/d3MAAAAASUVORK5CYII="},"64":{"admin":"Algeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEqklEQVR42u2dTUgVURzFJ0NxIREYRlD2hSGJPWEetbCFFc8+rDSN0lAiTVpkipliEoQgtWilhdiikIJKS4XQUoO0xMgIUwsz0/xANPArI4qyNOi0uHCdYZ7vKbyZsznIPHU2P8793/P/3/sUxTv5uBpC1dfA2mxHVPXAnvHAkbOz07Njs5NUfVUIDcEiWASLYBEsgkV0CBbBIlgEy3Rg9c0MJw/UERqCRcciWASLYBEsokOwCBbBIlgEi7tCKsGiYxEsgkWwCBaVYDmnfrakQTUi4ETCM/Xg1lP736ohMZkJfg6f3oKqwZr+b1PNjtbVPyN74gdqf1dMJE/5EiOCNYcCIEdd5GN1Q/4h24TacTd4fZD6p6XEv8weLGrHzPIm+xto50DQrchLPT0Rz5PeD1VleBd8/FJZWVrfPd02kjcaQ7AUK8OUciM8Ty1/EL92pd0HuLzzX5aq7hZVhElW+Xe6J8PD4jvHthS33LlpZcgsBxacCZ6kBYf+c9m3oOJftSf7ZoTWA7KprIdtT9MJlmlrJviTvKgZcaOa3KDC7Ut6T2ceKUibbC07+SgXldaPtI5zHw5A8QSfDl/LSbyyA2ABu88VBeuKm61TkylWQCon375RbTK+nEFLyje9VsfhcLZf6bFR953dFQIj1F59WXG2M9sAnBXwUqyAlPFqCX4Gb8N/cFfcAJhQe0EJlkdqnG2nl1oiL3wyZHjyxGtVoj0A4cJC51hYNKEEy2M0OCD2q5oCUIzUUvpILVxAij2jWZdFE4IlV1RaSyH8DN62EMk7oLFmiGoqsOA6wMVIXXV5c+g9tVOspdwL1sTS0uxKxg1m9CotxwJ8WDQXolf4/fyrFR0pXZ/C9kZfRQBBsDx4D4gMXas8F3+GVznbhEavUD9Px8LX+3JfdGoF3oW4gWCZZBHUXwqRTjkLVnvR9b7SBrE/ODVZc7ixUERt5MXFo0XV4tsJlseHC/oZlRgroFc4vwlSICWig4S9v/nYrqxwOeAgWB5fXclLnuxb6BIaKdi1aiz4E3xL7AxqAY1mDsZsCJaHKcZdjLST0ahxfdAP8Sb6gFpgiYpCfnA09faFIZTz5p7oMglYKMZlx5LxcgUs+TAFQDESwwI+EUEU+GZdKE27FGr5lrP7QSOOJb5Xy720HItLoceApVVpQd1bYwEj/X0oaiyrDf2ZfFcoQ4ZdofFoVGtXKDoT3EjcFTJuUKzZzHE9x8JkFdo1ohvBnwiWYqYZdnF6XSt0cCV57ypuWNOYYiR5x1gfk/cQK5Tw4vP59QqNH7Fnr1Ax3ySW8an2RZpu+Nf2IVgmCUuNgMV5LILFCVKC5QkBhFyHceadYLlhTFmr4aN/SseV+7F4Ssfk5wqNTGuJXUXXzxUi6+K5QkuchNaPJORPxZPQ2OvJJ6ERMfAktEXvbkAVpX93g1asavzuBuj/uxsYN1j5tpn53TMjD/rxthnejzXH/VioxuBn2Cdqxa28H4tgzbMmQx4m3uiHXiFqKd7oR7B4BynBIlgEi2BRCRa/mYJg0bEIFsGiEiyCRbAIFsEiWFSCxV0hwaJjESyCRSVYBItgESyCRbCoBItgLYb+BfM3DsS1ovzOAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NDozMC0wNTowMCVVUNYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0RaQS5zdmfcFAlaAAAAH3RFWHRzdmc6ZGVzY3JpcHRpb24ARmxhZyBvZiBBbGdlcmlho8plBgAAAABJRU5ErkJggg=="},"68":{"admin":"Spain","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFlklEQVR42u2cb2hVZRzHL6m0zN2x25y3ICUpsz9UUGkUVkiBK1PDP3OlAzFsZVi6YX+c9qIUq7EXjXm3OUGUuWlp7s+9Llxobk2d3HpRWGBFgUJSLyItwhcW+LkvfvJ0LltnXjxn3zcfHp577vPcnefD7/c7zzk7kf4DY09NnCKKw8uIToEosUSJJUosnQhRYokSS5RYoiixRIklSixRlFiixBIllihKLFFiiSNQrG/vfPzR8RsGzyNn5l+I3jHUb4lePP7rmsLr0sH6zTiQ/ZdH/irvGX3NmH9+6Hs7ErN0+72O/H+0o/n5DdnHudJ/Rfa/zmt2P79hMOfND88nP59WGPU/TuRKn2JxZFJiiRJLlFg5IBXGbxNTTaPv/XKg9WzRWC2nxPJFZPqlpnvGhHOtp+s3378Fohf9f1QcmlTUrAWWWJ4xCVGQxouNczZujZ47+dbupaMaoHsM42jJJVYMIRCFKNVZUZfMW3NmVkP9mJchPUSs9Im6hXlv0k+bEc7+uGvmqFull8TK7KYgDRtxnQdmnIil975asjp2EO4pKV4+obzlwgOnbpiaSFSdzK+iB9a3zK2KFrWPe6bytmOfPrSoIxpnHFVjEisjFupYjZCGNmx9rPTP63ttv23zXY6RWCNOLK99avRCFD8kOWrJR5BYVFEIRD3k6oUWpDxLynbbtj2Q8V1x6Wfk3NzqkVg5jVIkqdXN5cvit3yc/9L2vE32npol0nAkrCu4b6BwMbRJEKVslLLqcCnAkYyD1hIiJGLZaz2ksQucue67VLZD1EEjKifabR3PPT/naGvBjlTN2q5E89SWYtoD3yS/25iwZH8r+7xiSFKh1wITb+y1no1Ptv+T9Wsb3tjdV9N1KBU52Nj+RdcL6NVes2nl69W0+ZTtBlcsFfUhFCu9YH/PvIUIQdsVC5lsEiSRWbEQqHfpe0d3Xjx+Y+X5phh0xeKqkxiWmff3XXvv6ZcQoRLrq+6+JdueOrK9d92xi1Ysdp7svhSVk5dYKJKau2HJhyUoRZt+xre3erzmFUNSY/UWV3ZM+Qg5aPOMInWVjVil6dnRZ8ch1vKvp137RNwVC9q4RQ8Ri/FRNpV6sXN65rtELF0bBl4sthVQh/1xCnArlt3etEnQjVgrbrrrp5lbbUK0qRClIHMxPmJBdvPpl14BFovFQx2E+KzqcKT/g7buLU+XjUc7mwqJWESp2vjDZU++b2PYpL9L36lt5FPSHwnOVlf0UGNxvWk/RWtu/kiLwKdCBKKIZoGRBqVYfrvFYEmUmv5aedOqmyGSMUJy8ub82qQlV4iIRWSyCbT/+23zFv1MapYWgY9YqEMyanzw7p7bT5McEY6dcbZPLUmC6GXFgvQTeywZ+bInIy71IOL+spWJgsPuvr8YSLFYWtIQcYWrMz61YrHwtNmdp03cgihFtWSVss9HoBTjEymJW7b2khaBT4UsOQtMjWUv+61YViZXNftQDTLxKUQaK5a73UBtpxvVobpXOFSxaLuq2SjFjhdi2VRokx1iufOKIRGLReXunt0B9yOWV41ln3SgYHfnFUN1E9rS/geOVyqkh0J+Z8+KPYuXoY6Vya23ss8rIUIiFs+qV0xe1/bKvgxnVc+vjNNv7xh61ViU3lRI6GUfX7a01RXHM5el4lZIxPJaYCuWjTFIxrdIYRApabNfxRYGx7u7U+ur35296pHL5mUEZ14xwKkQ8sQBSXAwr6bgdjIbnpZ8N/sIzGgTbi5fGSKxAsbhelOKOILEUiwJ1pmMDFec8P/epsG86crrGPfp+Oxj+j+52Ufwembf//kZ6hnIzVu7/kOsoL9dTm/fuzqpd5CKermtKLFEiaUTIUosUWKJEksnQpRYosQSJZYoSixRYokSSxQl1tVN7vBLLKkgSiwxCPwXO5Dgx3YRLdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ2OjI5LTA1OjAweJLFpgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRVNQLnN2ZwDs7RQAAAAASUVORK5CYII="},"74":{"admin":"France","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3cMRLBQBSA4Zc9CgqcALXC4bThBA5gNFyFM+wBVNFqjYTszpfi1Sm++bOv2ETEdNK2pc/T9ny977rCn+fx8rjtc7dMmybnxXy9KncGWGCBBRZYYIEFFlhggQUWWGCBBRZYYIE1/GzSLB0CLLAUCyywwAILLLDAAgsssGyFlcAqnJRiKRZYYIEFFlhggQUWWGDZCsFSLLDAAgsssP4DazQowVIssMACy1ZYG6wP30qxwFIssMACCyywwOr/HAYWWIplKwQLLLDAAgssZyywwAILLLDAqh6We4VgKZatECywFAsssMACCyywwAILLLBshWCBpVhggQUWWGCBBRZYYIFlKwQLLMUCCyywwAILLLBG+T8ZsMBSLFshWIoFFlhg/fp8BhZYigUWWGB9C+t9ggUWWGD5FA44XxBz7mcwZM9VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODozMS0wNTowMJkeu+wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZSQS5zdmen2JoeAAAAAElFTkSuQmCC"},"79":{"admin":"United Kingdom","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG3ElEQVR42u1d34vVRRSfl5ICo+yhh8UH+0UIGW0E9WaQL4kk9BAk9lSICmFsRT1kgdCLmBghan/AIi0FrdSK4A+WjTZtt9BetNRda+mHES0iJKHhfgzP7Xxn7pk5Z+bO3b0vhy/f+/1+Z+acz5xz5syZc92Z5asH16/5c+MnY5+PXHvw6sS1vlh65s2pZ/74Z/Pwu/sPbL5rW//ke/3O3bfl7SVWdPHsiq+2TYBafXPd6YHbhp6jo7iy/cLYzIbvf3j00KrfJ4fu3rT8A06/3XfP5RUf8+tTvzy8+KnP8AX6zecfeuXq/ovOLZ1+60tbnsRyL/wMpAYJQpro/99DZ4enRn994v3RvYe/G1za99jaiT13LHvgG8oT3MGv5/tfWjuw49Ls+IXJRxx+xg9nH39h56Yjf702curI8TSQHXPjM+emIbYcILOg18WcBixOwVa8xYGFVvQgsJ1UFEzo4eiOEz9NXaJ8AJgwLjqF+DTzYcZxNoVfkFN0tz6QhYAF3SOBFGV3O2DZa6w0qMnBRPHANZMEG843RxtANnjwwNEP6wcZZzq/ozeFeo2VQw9Fg+n1Xfd+NCsZdSwG3A07Shrwsc/KXNagyTiwwAe5KZT4WFamMLdmyiFrd/zlk1/M3C5pGKyszVzGC+O6YYJbLddYPj9DprFKOO96MJ1ccv+dT+7WyxSIcnJV2VlNVsOqMFVjpQBLvppDK3IFoTdzskWbkT2u2fFvFVL6qtCnt0r6WLlXc2kyahqLx1ikaTJqLmv2ycLA4tGaMM0dx9Ks5hrCSQrNdLTv6/GpW2WyEHgkGnNpGyezAlkNGqvO1ZwdzyOUswBkQVVs6yRqgrFWAVLbONb8msAKd9LWJ9NsK8UwIiVA6hOkXGP5Jm0bB1zAw5y7JgojXih2IphtLZrMeIViqbF4/2NXhXozVymY0jRWGsjg7oUZx7cOcq4uU+JYelOo1+7dtp9bMHAXBhldUeYG2caD79wyPKDZK5QAC60AxGmhAYAJGShWYCqzleRaVZ/vOnwnfP8mxZDAaIkmo/CiINMzembdb4tmr+gj72Fg0VZoCgoNanBqFRo49OLYsz+uBLf/AxOXi0+OYSSE789RoDhM0TnJk+F3cQ2K7B8MngsYIDt/bMOiN1wDncv7mR7ZMrj1U43Ln9sUckih5y2UjOvnvVtPbH9VPyJwlepLuQQ1sqbU6UUyP6hmVehz3hcy7QHLYFUY1lgLFFhgR7dTmBvNF5BQm+a84y18oZ4RdZbzDkzBnKPXlPruS56JfZc/L/kCfz6tXb7v6ctz5zpM3vPwk5LrWB7Gfk3fTxc7O31RFv13NG1J+lCyn3L/rB5qyx83X5ml6SF/t8x4w63Y9iH3iFw+VtqKtjaGdsvUiu2n5PmwS+DVWJLXahB5edHWCco6J7DLh98c88MWfLm1ctr39RPbVjWEv+b71dU54zVPpjEi/J16tHUN3p6ESy63GeqUL1ISCiX9mHy6ylb7OtslaG3Px77r02ol12vdHmi4ASxNEFL+fCyVf18eDpUEDDWsTAtmygOSmpByyf6AzpMtHT2tbUun22lvEzo60a+3Cd3LbiiUj9VLm2kAlj6lKzbpDxRpaDzRL5YixxJJf76UQFCk2mE3PhZYaRmkaRSJfhgR8kh9CY9Ih+TtahL90hIA+a9Oll7sSziWP3kzNRkNayAFMIHptNIcX91A5BxStG5dvnwsngAdMca5xGskYdPTAHykvjE2HYEPJxnrE9PpdcHDFBowYR5TMIX1BxhNhU0ZjcRoqyP2PmChFX4yRz+RfHD3jb3paHzuGjgFS+qkzVoNQ33Hv+TnCiWBUMm5QsoTnJnRg4yfa2qYYMxc4lhs/tJRRpUwzcAUZBwv0MgZ1+5UtE2pSInG8hUFoS4BPa1Ux8SrQGOV1ExyM1f+iH27ajMp5y41PEzTZNYgWxBg6mxREGmdQTNzyXjbBmQK3vrtWDVgaljpsFkVU5+pHLBy1MfK4ZOJQGbmkxUEE8p++GrOxKxoOv8HArYaS+6/2vpkvKCBnZXIDCafvW8BU3Z7/3/hlXHecxS3LSmjMMjaaLIyqzmJmYsFU2pxi5QapPr6WDmKb+c2lzzUEqHJaLEvW5+J5jbl0ExUMLEhkrL1sUpUrq8tTua6f1mborHK18cq+S86ZVaXYZ/MaRrg+Za1gqk7apDm+wOYHJoszA0n35uL1Uxpq7n8ZcHa+1i+kdZQ573OgDbnmNNv9NId9RyhgRwCkGgseSZ7zRord3KAz/F3SKhFxlJYMyEKxVcEp58+t+fiKLJ/ypu5tP/SkYQb5OdwcgMrdoyxz0NqkCBPKIrNQEGqz7/AbUQccgW+ogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDY6MDQtMDU6MDBbYKMbAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HQlIuc3ZnJTl+YwAAAABJRU5ErkJggg=="},"82":{"admin":"Ghana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADA0lEQVR42u2ZP2gTURzHX4PYYrQY0lQkS5VSpIuDe9NFKOjmoHSsdbFaEMWCCg6H+AdLQVwKndQIKl1ERKEgSOqmQkVFcVFBHFpwK4KKw3c5OROfucvl5d5n+Qwvye/d/e6T93vvd+bFy0L/0BCEydKQAohYELEgYpEIiFgQsSBiQYhYELEgYkGIWBCxIGJBiFgQsSBiQYhYELEgYkGIWBCxoFdird6fXSrXOp2fV5ZWi19bF//N3bPB9vdiePzDtYffi6Pu5MGd6zE/R1Z29edhYy6sB/e2XBHJhg0R6x9cH31eKo3v/1hZ3vhA1Ig714ZYHUkVl21fiq9ypR1Pyr9y3zTigxyNr63xpyap6V1OUPwimOvremx2i9U7l8/1zrAyZXbFanW6Ff/Q3rFP3UFYLI2wlqckVvZWLJU8lb+wWBrROdSF7LmZefZYdamSF1aqvQWxsyqD8WGV0urybO7W7cJBe+oMWE8sffq/MZNd59hjOSHW5PED+3pqhcO9k12BDespFaZ9NM2+Nly72HeeFcuhm4m/OuqhXnp6Yjq/pvaBjTpxqFk0Y3NKpV8TkprR+Nnie3R1vri1vGdweGrDqVYopciaxc0Mt65JpJgmnYft5m5M5z4VqfgyhUtec03U9mYp2dlN/ItoRdlKMzX61fUjZ3o2T0SbC/YlTxHS6a65/wc2fvao6jHaDrWhVqksZSn+vZgsqRPnLnRybG7XpV+530pI8ykbH27bJrI22vaNhihd2Kq7k3mT7dOfPU+/nZjdNN64Hdq4ZaoIPr/YCV+D8W0XVa+/FS2CWr2CyrHX+aPKj76pkejapgjtaoG69gR5V/iXImjThYp2whTB/YKYjoKI9UcR1PnOfhse7YTFL4i8K8xIEVSLIX4XShEUrRPfCSYs1s3q4uLgDT85N7BQ3fnu5PKFHwMzScVUNEX2ObfGdE9NV0YgTJikACIWRCyIWCQCIhZELIhYECIWRCyIWBAiFkQsiFgQIhZELIhYECIWRCyIWBAiFkQs6BN/A5SD8vcJxtzQAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MDo1My0wNTowMBq3PHYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dIQS5zdmej4rBXAAAAAElFTkSuQmCC"},"83":{"admin":"Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZUlEQVR42u3aMQ7BYBgG4F5BQhd7R5E4gjuYeyFbB2aj2SAxsYqmEoeQOEKlZgmLED59lv8ET96+35smh7LTzbLo72U5XfV31/I07u3ruqrSNNbbNMdzd7uYbQbDSTIv1vno7k2KIs+Dvf8EKyIpsMD6BqyIpMCSWGC1r2PFzSqwwAILLLDAevUFCyzlHSywwALLjgWWxALLVQgWWGCBBRZYdiywJJbyDhZYYNmxwAJLYv0crIi8XIUSCyywwALLjmXHAkt5BwsssMCyY5kbJJbEchWCBRZYYIEFlh0LLImlvIMFFlh2LLDsWBJLYrkKwQILLLDAAsuOpbwr72CZG8ACy44FlsQCy1UoscACCyywwGrJ3GDHkljKO1hggQWWHQssiQWWqxAssMACCyyw7FhgSSzlHSywwLJjgeXXZLAklqsQLLDAAgsssOxYLYAVl9QDWM9esCTW23YssCTWRzpWwI/jDT/h9u+Sr+gwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MToxOC0wNTowMHM4DUgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dJTi5zdmfq7vQjAAAAAElFTkSuQmCC"},"85":{"admin":"Guinea Bissau","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC40lEQVR42u2cvWsUQRjGNyQSjRo9SSzMYcRA/AcEU4QUUYQDg4KVoGBjELWwEBRBYisodiJBBCGFHyBY2AgBFayEwCFBsbCwFGs/CsXisRjZXNy9nXdvdufXPMXdzsxy8+N5P2a4ZOVFY3Rywk7bU41Dk3etV/n65Na1sTe/RtqfdzbQEDSx3vJyFLAAC7AAq3ehLfuK+hawInKsLED4WgWwCIUm+AIWYAFW3GCVn0t1F2QBK1LHsgPUnRmwKgBWyF5Fu8GXfrr59nlzhhwLsOhjARZq5Fh+4csyG2DRbsCxAAuwUCOw/B68ABZg0XlHwwbr8v5Nq6O/ny4Pn9szbeF85YNl3QGqCVgWQUpzLq9uezwxsO9H/5HB07fHNq/smrcLiDhWRO0GwdR3NGkmMyeODZ7avkCOBViFkm6NEkwCS74lDwMsQmEOjFx1g6DAksrDOo0CLELh35RcnpTWww83vNs65CIlPXBv4M7QbKdR8682Xt3xTWl+dtSoCmsF1qNnw8n4XCeA8qqb4IdfFaKGF/3ckCf3aiz1XeqfzYuU0MzrUmn98P3G9fHXagSgIWjiK7tavLLlZXMhnVelVQgKx/Q83b3JyZ8Xz0593H1mqX1wLxqCem43uJXg+oHP77qt4xfOT79PkgcjrS//0/uLrTlLzbJuOW/SU/XbDc/iWFI5nK+WadXAikB9gSVQ3ExLkCnkqR50wVINWAQsd+y/YEXjCnUFy82NBEo6JXefd5/xGxDXAgsNDKy8PSTVhvIkoaNPOs2jtoKel88BFo61hgojN2fKUuXJz9QPK36IBFg1BKs7IPzedACsCoMV2v/MAFYUjtVbBSzAKgEs8AoMrCK3r9b/0w4cC8eKPBSCZi3AsjnSCflHjwZcHAvtGVidfKX4SZ/lITQbHKVjWYLF1hIKKxAKcUHAMtx+wAIsNrXq12YACw3udkOsfSzA4qwQBSwcq/JghZmB2bcbwCWf/gGMEcrk0nVvtAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NTI6MTItMDU6MDA8f+kFAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HTkIuc3ZnMhspmgAAAABJRU5ErkJggg=="},"89":{"admin":"Greenland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD7UlEQVR42u2dT0gUYRjGv0NFQXWyOnWsbhIUBHWJOpReMoJi6RAR1GGRLoEReaoMitK6LIGEG1Se0gKDpIiIDlGxolIkYYkkYikaqVjJBvvM4VsGlxVmvn/vc3lYVodhZ37zfu+/7x1V/FMcK05Qqcmq4iWgEiwqwaISLF4IKsGiEiwqwaJSCRaVYFEJVvD678nU3Ew3bznBWgYuv4++7vlwcrKlo7mr8P1K08Yb674OZB6cy3xpr1t/Oju0sPfWiXp8ho70nrp88d7Yu+bHt9tx1HzTwMjQLOETChZu/OzNt4v9nQDo84Fdn47N9V+vWdzd01dYu2pHbd/W1R3bjxTurvxWuwKfdcX3ZX8tHTW4Z/OzfWuA3Xi2pebOfaBGRAIH69e13sY3ueFDh983virDKFGNgNNQg20DysQlELBgM3BrI5g0G2NIS2cEZFg6/54fz/3MEB0vwZqafzjzdMPHLdvy9cd1K2JZS5DBY4NXR4A8AAv+E/wba/aparwAPRx/YuQoWEAKzrhzGFVUPAA/XuYOdm4iTA6BBaTgu/iFVBwvWi+HwMKznl6UZ1Lh4CN6JVjKbvoAN8N3pHRFXm3h6nDb6CTBsrD8IRUZElK6az+6P3v2Uh3BMnrKiZ2tDfkzASIV87okL4tGwcICgUA9bLBgt1AnkFl/VLRVtFseg4WnFjlrR9OeqalMf0uZjAHDSCssV7H0S4sTDYGFQo00W6XrdL679UUDwUpYo+SCTLBKvxrVBYKVmKLJREQkWEWESLAS7qmS6V3pYCEjLyf1oOi2m2yzkePCpw7W9IWuweeP9EucsMZuoWuKRkVUReX00SvEa1RqsqrSflKxDYttJAhi4GkttYPIUa1+NdD+U6XtWyDRQLCkRceK0ZDJHUfh9Z/ZAEtwQWOp6FhOLKxY4Tfa2SGm9qDMnAaRgmSwEMQQLLa8sb3RB4uFBVHmBARsC5NWb1AmyxrYkiptIhestbTODsWWN9ZJvQcLKsFuSbZV1sBCkjDsaS1p+FV+WT5lq40EpZ7wZk0hwx7FgIJbsZXdLqWQlkU8JNHyJ7PzzAmwNPOOrLTvHhUmDxIpJ8DyfcqUv5O9RICl44Xij/s5eix8tFIegKXjhcqamxkv1A+CnZYTKljxWVMI3e3aMNgnWFN/Iz6TCQunwUILLy4Hoi3kss1ABpiANRCnHQoErHh6ApBhGUIsmdSrSvRXp8Aylb3zQnBGKnSw4u+P0Eb7Y44N5rogusSsBFg4eEVQfIM58kATDriOUfyFKFQRYFW2anGvIq6Vj6ISLKqfYIneGk+lxaISLCrBolIJFpVgUQkWlUqwqASLGrj+BzI0XRBdYcwWAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1Njo1Ny0wNTowMOPmaCIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dSTC5zdmf5P1UbAAAAAElFTkSuQmCC"},"90":{"admin":"Guatemala","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEnUlEQVR42u3be0jVdxjHcceC/ojFMiiDRRIysqIategyKvoj1/1i5phREWx0GxWzsjAqyD/KotXoWJhERXaQyqFWEhWldheyxtJMJSnrZKfoQl4hR7yNnvpx4sRqcM7v88/D4efP54v8XjzP8/uerxGzknbuqq4J9VhcVRn34n5bWVtF28XQjfwV4fFEIgRLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsATrf4X1OO3lQH+yYAnWJ4uFhZdKDn+7qDW75efIz8dLsMIE1s2Dt5+VZ+T7Kvbl3/swl5yCnKqdpTED0xu65eU8+zsppzFQPSMbmQXLdbBq/vR7qtdTgX7KzPYkJngbS3t7awPdf/3H44tz58z4fVZij73wSuld9NXS3ZYj4BJqslbFJ5CTVQTLRbBAELcxs9PYFiic33Kzc0kDFODSXN1wq34H99fP9/fxdSxLSlmY2nHSmu2psQtifttzKnIivO7UVY8rLyIDpMgcqLYJVhjCgk563Gl/2lNnrQJQ/WxfcmVSy7nm6Mu1xNZXrUf/ieVz3Ux/ZOGD3OHXfRmj4ZX1V2mHrOdkIBu1kFWCr1uCFcKwmIGmNB3In1Q16Icd22MXOOuKZRQo0hwBRDY7Yw2L2v3r9yVcD75uCVYIw0qPK67d/IRWRXRWFJpgO6MRDx+cGdB0trJLQeE7MfHRqHNfVCy5t7ZoDHULUmSz+VlRsFxRsZirbAtzRlu3wNRQdi3myCmo2eo1uZ83plt/5i3bEFklmLdOwQphWNQh6gdN6sOw2uuWqVjAInKFnzphkZlVmLRUscIW1pux/TUsZiAnLDC9M8JTnxy8uFKeXNfsHQIsz8kL+3/ZZisWqwQ/wgtWSMKiGfGYV8XmrV6WHWj6ARZvf8xSdt6yrZB3Q2CR7c0qrz+zyrqXl3pt6KpWGPaw3j5yKoqtJZBin533PudEZSNVClggIxuZLV+7KyZYYTi88/LPg2e7wTbEq71Kvzm/dvOxFRPnNbLtyXU+Xym+m3rlAkSIf6Qc9ET6F/XNuNrvS94QyUZmVtF2gytggcPujxP5mpkIL1vD+O6PyBWmq01NBRGJ8UTmKgZ2crIKKwqWK77S4WHbbdLp0ZNPDL5PEyQbrdNisl9d0/iIy6MzN8SPnz9trmdCHtmAFTwpwQqr0w3MQH23DpjT/TFx6tQ1/u9GMjldW3mjR25n2xCJtDxeAriT37J5yKzTDa6DRTWixvTcFJX5dRonF5iWmJyyRh8aEXWMTQe7GUG7pP3ZGYsMZCOzrXOC5QpYEGFIpwmCA0xwoZ1xZsHWKjuec6eNZGuHZVAKlotaIbwY1ZmTOBIDGrYJqDpUKSYqeNHsuHPeUq+vz1BqGHd+LCnBCtujyTRHxm27rWB37bkHas77//uRZcFy0Zl356E//TOFYOnfvwRLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsATrvfgvOcMijdKPpc8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU3OjIxLTA1OjAwZTE/PwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1RNLnN2ZxIGn7YAAAAASUVORK5CYII="},"92":{"admin":"Guyana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFdklEQVR42u2da2gUVxSAo8SsBk0bqlvx1WJhjab4DC6KoNg/JqUKtRikiFiQWh9JRavxEUmNBBM3SKEKLTESFWJt1Ii2P2SLRSG1S0xqxRjNQoUlmDYPjY9gq9kVevJjYLzDnZk79zXnzyEk7BB2Ps53z7mvtNzNi1vz2tftqQ2XVq9puFoQmTml48e6tY8DJ+o2LK9Iy6j9Jn82Roy24/CqYelppRMiwbbA+1veKy4pCkc723qu99Xtim+MXlz7/FpudeusksYfNs1F1NxEL74962cKfl8AljHO657fNbeltrImcayp98jAjv7yzu8HxvSGfwomymOR7QdifTXF4bsXO7bmBBvrl67ej9BgfE0MZ6Qfypxhxgsi5LAbjc2XbiwCyJJfprIGP4Kff8v/Z9Od8qoDt1Y0TC/Ydrl33xegUfxaMaZd6Mk6/25byahRD4I5U2LDq0Y0mvECUUIOu3PmUX1iAeBljvDX44GO4OUm0Oi0j8+eXP8ZalQVKTt7U6/51M307CWhwxABslXNgelvBkg5DER5eufxlt1f/ZvfW/PztdSVF9GuP0iokTSa9eTUhysf4mvWNhrBgtjyXfaY0KSj3aO3T+ywFmXhtmVnRx6MzT/XEB6fzExEik8n/3s458zqVOXzZLyMhBpJozBiw9ymLVhmyGhEua9kw6+Z3yb6f5k6NjRYeHtvzujB/r9OfhpP7v97WaQnVfisq/keCTWjRo3VqJwaRfSpIqBDiiRRkiDLC+UuTC+v//PQ5Kyip8diA2Pjg5NuPhjXPYTaqXio4HPIbal3HjdFk6hRzcEy5idryOyK8vdL9R9kHxnCyxwr26vCFe41itWoYhmLJtoWJQkyQ25LHr3/9ZoyuxqVrRr1tTTdgwWRpqIkipICNdSowmDRCJGrKK0jI41iNSp1xjLXkvSiLHt7YyjzHJUoaXKbo2oUm7qMG6eswLIGzkNRokZl7mN5DZYxchIlF43yqUaVzJ18MhbpN4wrStSoWmBZw+EeNRDlysMZiTeKuIoSNcoTLGfQsFKqFKLUSKPChv9eq1Ci1quUGpV/blSiwbv7Z9pdzCNAlDQRNAqo+U2jbhCxO8PoDE3pKkoJNKrAonD3QHjdsCA1YJURJQVqGlaj/EdO7itKTURJr9H/UVNMo6z0xzNvkVBTXpQ6zY3KlqvsAkrKYQCZ8qL0bImR59WoqFaC+7KAJodpK0r5m7p8Xr8oNPUQJQPc+Td1vUBE7DhMsTlKyapR69xmQ6PezQCKzWrW/49RlPRzlBriRdIo9YgNcptRo3DSh4BJaFFjMtIwnxVYGo7PKMAyj89sgCWDvFjBR3+kALN1rdpVlFSNWZrpFBlmD521HgSsxJczD3GcShqCTE5EsC/PVmfStRv4ZCm2edFHsjMv17HMQ2adwRmOrBqk8ASHGcu7VQzKbDvzgc4kAksUTBrKzlz2O+qeQ3UmbBLavbBkXjajgOxcb+JgqzPBmylE5S2FZWfS2dCxdXov9JO51rMrO510pu3SZC9GRRquEZVmM4WvV5Di9i9ldCbDaTMayo4wKetMZ77bG81/wC5pZQd5yDR3RrOMBLfYC1OhdG1M1JnqKpToGCN1d7xgxhK8VtP1Hj1jsxHhkK7doOJRkXiOvKRgeXgUkQ+ajXxg4oqssyUuHlZ2rlc2Sjp3hkdFcpUdVmc4xrK99QAUhs1Gf94E68mcHaHZ6OuDyPyGo/s2Jttr5fAlaVJR0m89gMyEF2FiZHx1L9wPDaDg3BlGZpeNo84w2oikW5/hanEACLJUtLOt53of6gyjDbCMsgOAsDoTO6yWuRdP9anczYtb89rX7akNl1Z/8vLKkoq34AwkvPsKo5v4CsFxCiw+wVqGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1NzozNS0wNTowMF3UG7IAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dVWS5zdmdMOn1RAAAAAElFTkSuQmCC"},"95":{"admin":"Honduras","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACP0lEQVR42u2cTStEURyH78bOTuzEyifwGaxkIaVkVkpNFhY2pCzsKBKlLC2m1ESzkQUxmZDSIOQlImbh3QiTKGPx29wSTeYM5555Nk+6M3P63XOeuf9zzpx4ntdVv7kMoWnSBRCxIGJBxKIjIGJBxIKIBSFiQcSCiJUfK6v6u3czdCjkiQURCyIWhIgFEQsGi02JyczpWstlpO3sxv93IVjo9v+SudzL797jRi952Vh2N5uG0CwdFCvZm4pnpqLh7bF0hc05lVBpEctSnnc8JN46NVTtL9GNi+q60MTocUpXDtLXw68RG3IqiVIpodLOru7XPH5cNTwNvC8glnUcrF5qvNryTyE1bNLOni+AUilh6VxPy/aekiOW1UWwdn0kfLgo2lkQI4lk3f2TP6d7BdFBsUQ9G1RiTD1pTD35lktOXp4b1Zo/M2IVEVWehlrjd9chkT5BLAMTbc2Hyif65ndmRF2xZ0GAWIGkiqmm2KKp8opYgdx6MDVrkUZatakUmhLLPxdErAAopeHvnI7FU0e6ks8y/utn829NqZRQad3Ty6kNUg2VZkIqW/rdzZ41l5Lo1zQlVFolZx/L6u0GHZLWsNm8j6WESst2g9XU/pCGzU87xZL0480rx7dl7i0IPPf2nFiRIRZ0V6zvjpjlfgAt94Npv2v/fw8V/tyOXs3lXkzdrz15fn4/R5MhZ94hYkHEogsgYkHEgohFR0DEKlYG7R9FMWwQsSBiGaOOlzBUiAUhYkHEgogFIV0AC8BPeqaO3wRP5YgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU5OjMxLTA1OjAwt1IPEgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSE5ELnN2Z0sTrNMAAAAASUVORK5CYII="},"97":{"admin":"Haiti","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEaElEQVR42u2afUyNURzH70ZjMyKyWitZatGWtkq2suUtkZdiZgqLRA2p5jZZM01vKMvLJKQWRqaEjFb8UWor/jDuZrNeDCuxicVMVtl8Wcee7t3zPHW73Xu//3z27Lz8Tvc8n/s7556TRqNxdS0uJsmRJqeApFgkxSIpFieCpFgkxSIpFklSLJJikRSLJCkWSbFIikWSFIukWCTFMkMGl2gVUWkcikKxKBbFMp1kSuNQFKsXa7zzyctn/tNCWqI8DkVhxhrG0gaZRKoVi0uq1Yv1T6ChxRJr5WS4obIjxTJjaRSJtT5xQ0KB8/HzhUG66fJ50Can+VnuvmNpa+qzlfbFiBTLwsWCIv17+gP7GvTxy67P4zo7Kp0aw8p7T92t6j8f1eT0IKSw/P6a/PzsKJSg1nAcECMqyH8Ua0zvmfRQjlhQJzA5bUXobrsnsS5uyV6RCZO8DoIoQS1ayhJL7S9Zs/1lbT1i/ckZcsRCNtp8O39ibCxkmtwd12e/SpQs5kPBhYRrcvKWarHM/MhmpP8g450DjYJYWATx3B78fuu7V8hJYsZCCWqlvVQsheYllgmWQmNPkJHEErXAXgoU9bqquWS3s06q1Gu3s+lF98T2FGtQLHWpz7TXI2oOPA2KhY15TnXcldwbW1p2+KTU6PyfO9T3glCn1b2u4HDvhyxdSXMhykMnRNQcykMvRPjq9sm+JUCvWPpPy9RdSQ3nqsoYHBzXMcTF6fQN62FmYo5NQw9eOSRoj2jsrvgJReZnLXuprfWYvahNuxJ6NaeXXY+MkhK1aIleKEE0US+MaHkzOeNhwLujD/TVaqq10wYWu5ojX7RNtfXzVtqrIyL1R56vuMwVT4rq2TgTYmGyIEp0il/V6vDateFLZ3c9nXDxu+dHECWohVjoJWYvUSwsl8b+XGONGp3nlCTfDH3Eh5RPw9EMR5Yzrvz4+iiKBZ57k/+4rFSUA2LdynTz8Q5smuu907GgYf/+O/bxIEpQi5bohW8qoon7Nowofz6VzpW6eVP6ZpVSY+wBjE2lLwyvWcwo2D8lJu9esLxJZOms6G6PhfWrNjX5d1Xmpbg7lOEZhGTSXogGsTCKKNbov2BTUWOpH8ywWNiAi8TvPghUpTthM/0Z1MHCh+USRAlq0RK9EEEaWSqW0q8ExVJAcT8x+mIho+D4IObRSdt1A2v3pv7yCY1vTur16hSJvAWZQGgUH36swj9W2h7RcHAqzViWqpH0bVpdxkLWgRZ+2oR6xyKRKBdVk7YBUYuLHX3tEQ0jWrZYXAozdn1bXjSvFVc0OEk/4Hmz/8hdEBc1uMwBIc3f5z+10jagGEe8CEqsCep0z6FYFi4Wfs1BL1B7JOxtQCOetx1fUjdnOyjWQg48G24vEuUY0Ro27FYqltKjDXUHHOpGoVhU0GQnQxSLJCkWSbFIikVSLE6E2Z5uUyySGYskKRZJsUiKRZIUi6RYJMUiSYpFUiySYpEkxSIpFkmxSJJi8R9gxhR/Ax7mO7KDJf8NAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1OTo1Ni0wNTowMLSaOBsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0hUSS5zdmcRzkmfAAAAAElFTkSuQmCC"},"104":{"admin":"Ireland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA+klEQVR42u3asQ3CQAxAUV+DIkFx29BkgIDEQMkooc4crMAA2cVsQAeSlZfCEzx9ne8SEet6m6rPbdwvr2uW/96PbcxchtYz5zmi7gywwAILLLD+NVt/nu5nsMBSLLDAAgsssMAC6zus2qTAUiywbIVgKRZYYIEFFlhggQUWWGCBdZSt0D2WYikWWGCBBRZYYIEFFlhgeSsES7HAAss9FliKBRZYYIEFFli2QrAUCyywwAILLLDAAgsssH42l6F1WyFYYIFVgRRYYIHljOXwDhZYYIEFlv+xwAJLsWyFYIGlWGCBBRZYYIEFFlhggWUrBAssxQILrMPC+gDB6+rl3wSe9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDM6NTMtMDU6MDA9HUf5AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JUkwuc3ZnqQAuRgAAAABJRU5ErkJggg=="},"107":{"admin":"Iceland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABIEAIAAADffhsNAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACC0lEQVR42u3bPyuFURwH8GexWSULm6uUTLpZyGBReBMyWSWD1TvgBbCYvAsr26WUlJtSSpGUv7k/wy3UFSfn8DnDd7nPc07PeT7n9vQ8v1NVVb2+uZlrju1s7PWfzM9tj5wOnJ9fLz63tfta8655d7w7NTo12lgfXBlcaTSGakO1D7L1axwZZ7X3Ez3HKDFi3nNSSoIFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWFiABRZYYIEFFhxggQUWWGCBJb8HK6Yy3+w/mb3aWk4P63WU/GejmIyVml++3ubI8a6Ftd3Js6OLg5vLFLCi5xilfdxcZ6aMrGK95pxx4x+mH3ufVlPAip5jlPxno5SsnottPwVLS9HA0tLAiokuMW+b+z37Pcd9E90T3R3Bah0ZZ5V71aVkFeu4yGxBOVwanhme+ZRUW8aRcVbBV11IVm9rvdzsgNT7fy+ZOqsv3xgpO0iwJFgSLPnvYf2NB0YP77k9vHvdIJO8bvCCVCZ5QeqTjuZbIVjlwFI2o2wmSdlM3iVjv1Xop1jv24V+SpOVJqdJmyk+gGUrhF06dumABRZYYMEBFlhggQUWWBIssMACCyywJFhggQUWWGBJsMACCyywwJJggQUWWGCBJcECCyywwAJLggUWWGCBBZYEC6xfzRfU59qWrS50MwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6MzUtMDU6MDB6fmA9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JU0wuc3ZnYlz94wAAAABJRU5ErkJggg=="},"126":{"admin":"Liberia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEnElEQVR42u2dXUgVQRTHN6JPyaB8CSrJCinqalYEReAXRuFD9AkW3ZKiS9kttIcIhEISLNLKB7OCtIJMI9EHbwSlpWWRaORVVDRMQSswDJWe0qDTw8g629md2dvOveflx7J39uzs2f/dOXNmdlbTFkf/8CQ7gZFVy13uyMpzYUUbIurq57THdNW+mDsjdg2wLmt2icvPkv0Vz7/HTp+1ce17ETuyGKz10UJNWM65hcFdH4WFhbr4P24y/kdOsoN3K1jWk3PeUKuPQ4Wldw1XWDyHIijLjiwGU32kCSssPfa4N07EQtRV1zJP+vPKhJ/p4c0dSc8OjxLVpQRhbelNc1+pAIrYWf0mddHFhwPvBtcNDU/kjXvHnwx1DlePJcA2S8x+3raIHX0Za5ZDoT4ShHWrt/x1Q1Suu3iHr0eusIjqUkhYC89uOpD55eNIR1h/8duYD5c/VcxrX194xisirG95QwMjqXRjQlpY233HRgpb2EeiSINIwgohYa1ITEnJvhC/99DW/HQgG1FBI8iau9F731+bxJZhj13ZsflVVjjvXPCrf0FRY3nB4IN71VUxRHWpYZ4iT7vrfW2PMCEbL7gr667Ja/KATK3lsZRJNuq6645IewbGP2bTDRBLQXhuLCmW0KhlR1w/Wb0fE3tJEJYD6Dgx/ac/lekYa7fHe6m41bjv1tXR+/jrBERgsjLvRLVoWljw7IE+IE9Y0HSaTZlOEhab7dUPPhhnfnnljbcxJY3PZbzHrB28fcy1y/UP4ro0a+lQtkGEJk+/B+IzvGUI3lt8uSN3dvX3XVtVerBvW37a3THYZgn7WerLGx9rvL+vouB2afgU25xjeXXDl4Gz6M/F7sFfyxTn4tgX9w/vukwLC2ImEFDDr2ZXzzSQGjR8TQX+0s958GvGkZz5ZYmUbqB0A6oRhGYOEg0Q1LNllkYn3DyfBH1AIL5BhD4jCSvkhAWigSeTsVzgVwjz9eKjIR0SVkAJTyy1hIVPvqhlJ6iEFVsS13ni6MucnYVed+u+Pd9PZxLVpQoT/YgK0qlTk/FT+jHTcDE5Ibx9w6m9/yjJyyphaiJSH7v9o9t2trDwNwx/m/EuE08wyrrZsupjn3/0woJemxMIMVbN9MjG5KGGhiVJ8aeI6lKDaXrO4WhXW0b3TKLq1CjjQrQl3UAuIJKwiOoIi6IBoi0xFvVfiLb0CoUSj7LKW0sMYiadWUshkh1Matfwrmk0iZYoMredLywa2yLaMVZI4/BEW2Y3WJvH47Tyas1VojwWkUjCIpKwiMEvLP07erLIewdQApm35OyrP1GEmul3ds3OfpRl31rS1ey7yCLvQFu7RpOJR0fUB3FGSpAS7Vm7gVxAdKiweCvDyFoxxpp9/ax5dg9vHXlrCxLJXRtH3J+Ykvb5B47VaO05oi0r+lHHmEh5LCIJixjiwjJeslb/5QKzZawdyyuJ/z6CHeXN1lPW9YrcF7v9wztKo6++EG35lo4dX9UKJpI38F8IY6mZ/g6g+MIVgbFjbYhJ/LujzvSP+BCcoWdkCCuA86Yduoa7uCDw/lHEJ1xh0RAEUe44wW+yNLeTvVGNVAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTM6NDUtMDU6MDBT6a2dAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9MQlIuc3ZnJz4qmQAAAABJRU5ErkJggg=="},"137":{"admin":"Morocco","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD/UlEQVR42u2dPWtUQRSGJ2AwiBaCWJhkZW8+NkYjwSRgNGqToOkUwQ8ECzGt1aIWqcQmiRaKICJRLFUEW8EmFuofEPEHCIqNKFpooZB3i7OczDVxNxHvPM3LMnd27mbm4bxzz5mrYeF1pWdgAEWbq4EpQPP12efuC/0vAGsVJwsFLBSw0CLGWsBCAQsFLDbIgMVEoICFAhYKWEwEClgoYKGAhaKAhQJWMfTh+uxx5YmU2QCspunUtsqV8vTl0P01u8FsAFbTdHRi6FrWKaVUBVhNM8Et3ydae+ekGCJgNUFlfy3zJ54PjElnD2cXux4wM4DVkE6e3321fC+Ec9WhFqlamBnAaujYT2/rvs1dGy1YavlfDPFf7QgBK6qyPNmfBQtDBKyG9PRc/0L5adhwdnzwjQVLLbrKLAFWQya47sdkX+9tG710ldTDqoBV1Gm98z6b6XkkmIRR+93997NpqUVNPcGIiLWCPHvNBI3x1czRGKJ6MmOA9YfoK1WG3UYmbdWltUi2CBa5+MKCpUWtHu/41fnlyKm+kdKw8kxe/dVYizVBfVb7oZ973mY320YP3irPqr3tw96x0pTa1Sf/N+T/Nv0VxcA0FKnwouWpbbEjT3N17a6lbpO+2G7j0xLfit3FtizjqvAq0o4tFM/OVIRRXS+2wBYdW66xGKlF8cm2eLDsCF5jv0G/sKi7tMLusWaObb+ehVrKwMUbWdjJT9m7zjMCUZ+tCdYW/lV5vnRJfaRCYWt1+GhpkzVEP5ra/d31q4qdYi345n0Ji4wssEC0fWIbc7/B17eElEbzQKuPLC+F8xEhnSc+RRobk2xkKn3b9bL9gLWw/GN9umoNUSN4C9Yddfd0nh9DmhVAv/x2j6V9VX5c0UbbjuOz87qqWEi6IYmcu6Cp2ZnbA2n/lL8H0lX19Ghq5JSPBCadINXCj/RUxtvHYhZpDdE/dfpkhEbjlGkg266NthDxoNgEqX9yrEtSUJwGrFixWdZmn+l8Nsua5o7BnR87ShSnAStebF7EQmXmfItUi5BST4rTgBXNRQkXmwKNIWXVvm9o+1OcDimboE0W6LPikC/FyBx11ac91e5HS9kQEwWrLr1popHPbOnkgn3FXtnz2Fl4a4gpvzkd0jRB4RIrD+fnyvPz+FZTNsSQZu4qdvZhpeXhuspg5PxCmjmtREs63rYayZXbPL4deaUvihUptiUHln2zWUamZIFd1OUvsP+Wt8g035wOqZmgbMuWa1YjTmhk+25PaoaYEFg6ZaC059okAnQX3TG1Mw7JRay1jxyKiEQsFAUsFLBQwEJRwEIBCwUs/kkjlIiFAhYKWCgKWChgoYCFooDF/wcBWChgoejf6G87kZTdBU2ZMAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTg6MjUtMDU6MDBtZl/tAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NQVIuc3ZnB91TgwAAAB90RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgTW9yb2NjbyXwiYAAAAAASUVORK5CYII="},"142":{"admin":"Mexico","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE90lEQVR42u3bX2iVdRzH8QUWTqWyG1swpAyNmiip6E3BLkqtySb+IcxMneLQBkt0obvQC1Pntqbz74ZWQlu5OYXNuZCmpRdzE3XaTMdoqDlX0xbkVijoCfY+Fx84LBS9eZ7zufny8DvPec6B58X3+/19z3MSEhKzsqfOCHo8fOPoM4frI92Rzsil4MaeDVXJNaXnWoYnjR4b9JgQDljlo5pTji83LMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyrCce/0m/03GrimhYhvVYsbfgr9FdI04frXmh8PmazKKC7FyNZ3rqiqurnxQ1wwo5LDB1J124XNt6Ne3Eip+mHnqQt3na8F1/L902eUnB6rnnxr2XO3HagjHfcQwyzjQsw4rG2093X78R6Xqts+rSGs1PYPp+Xv6ajJPQYaVievbiSd0fvDu5clTDnPXjR760d+WqtLXj75WMyNk4a8rj8DKsUMFqOtJcVjns6thrn54tI0tBh/ykx+QnVshYi7ZP6ktOWlb+Vsorb0MNZF05V8qaZxpWXMNqzWjZXN9+tu7U9d3v35zY+HpOQ/uOQx3LfuaYjAUsAGkpVFgcE8lej9p7GVaoYHH7tQgSoQYsMJGTOAaT5ipdgR2tvWHFEazevs6+Y0PBxAo91vHKHyP7FlIQta8CSuqQN6aMvE1fpc37QE09eYsezrDiCBaA2q60rWhKgRS8iOQq3Q9CCl7QUUxKimP6rZaGCwvPdBhWHJVCSNG8U/60LAKLVh0i9E+gIRs13S1KzT9RNGRle+ZWSHGmxoffJxpWwGABRVtpjtm7kVGApW177Q81vcVdMNK2HVL0VbwXoPwbG2QaNWP9f1k0rMDA4kZChNsPKYog4HTcAKzzz1Vfnp2o0yzNQDrBUjp8Viwv1rk+34RSa1gBhgWmPbWl05fv5tYC69+Pv924/3xsxoIUEWTAImOBKTYC7tjvjfm1s7k+vL56aueBwmJW4Fv8ztbMuYl8K8MKMCxyEo05DTvrYMp4teRUycyi5PpvjiSTReiHyGew0wEEULSF1+wFPs7h+kpNp2UDzbcMKzCwos14PynNE78s6Hz55sUJSVtWF1YQ4cXkCVKcQ9TCSo9FJBfmpn/YM2MQUYuj9nbAGqgIGlYgm3dIld+tuP9JHSVJeUEKXm/uXDf/82fJZBpXLTkwv+IijCiU7BnJZDAias6DFH2VlmPDCtWukBsMLy1Gd279Oaw1nSuADEZQezFv/dANWcpuzrpNgz5LZEXZscKxji0gxed6VxjCORZliC6HwsTtj8LKa/3t1xyNZCCoEbOrv/zi6z+0dBLhxau8V8nqrtNzrNAOSLnZtPPkMLIIIOatbWy7tpgYbeoHl36UlkskV7GzgwvvotRyZS1/fIquG1bIJ++6+QeBPnYMF4qawtLIqxCkV+O9ZMTYK3vyHhewdKdGAx59XrS/YOk8HSgQOZhamrVpP8c6UNBip6X24X9+NqwQPjajxZEWW/MNaBh4MozQGTpn6o5Pn5jwg35+5j2aw8ABJrIOOSmKrP8nZ3jxauyPRX7m3bAe5Yl4+fFHR6b++5dh+Q+rhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYoYalz52GCdZ/AyERbDpEd1wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE5OjM0LTA1OjAw6Hk/+QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTUVYLnN2Z9b8CTQAAAAASUVORK5CYII="},"145":{"admin":"Mali","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNklEQVR42u3cMQ4BQRSAYWNFoxNH0LqAI2hUKoXCBcQFRKJWqjQKRyDuoVWpdAqFhqxTTNbj+04g8SfzdmYyqdM5HPr9WnCbbbt3242Grdlj836Xi7SP9fvTuTEvj/fLad58XrurSWsa/R+p10BYCKsyRZGW5TjWIiisAGLNWGXvtU4DYYGwEBbCMrxjeEdYCAuEhbAQFvxZWLYbhJWF7QZhISwQViDuYxnes3Afy/COpRBhISwQFsJCWAE4KxRWFrYbhIWwQFjh/N7BjrC+wu8dRQsLYSGsCnkfS1hZeB9LWFgKQVgI69+Hd2EZ3rEUIiyEBcJCWAgrANsNwsrCdoOwEBYIKxD3sQzvWbiPZXjHUoiwEBYIC2EhrACcFQorC9sNwkJY5OQZIxBWHN7H8lWIr0Kq8wHqzHuU9PMQqgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjA6MjQtMDU6MDBBJHKsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NTEkuc3ZnrHPmzgAAAABJRU5ErkJggg=="},"152":{"admin":"Mauritania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADqElEQVR42u2cP2gUQRSHrwgKCoZoQDQSiRIiKApRELQX4QpRLCIWBm0CgqLRQkGwCIiCBjVwopImJGgjpIgg2BmRQBpLU9iJAREs7LX4NU+GDZu7mz3fzNd8xd3czM7cb9+febNbq3VfHD18BMI2kyWACAsiLIiwWAiIsCDCgggLQoQFERZEWBAirKo58PTqngOTIquBsNrGG88nj/Z9F1kNhNU2vut73dgyLLIaCKsNHPpwbWp/ffXX+66uaVGfsDIIqyWOPXvwsP/snw2L32rDIg4RYZUKyWWBxPATuT8rrPlzc7PdS7ZNyB2XLv88eAxhZTr549O39g2dl3Sss7O0krIsaq/e1DPCIoqqv/o6c7rnSpGMylA9EIEhrH/Yc2bs86Evip/WtlWh3dKv1AMr6UxYilqq2aI88fvOi8GtZSQ1unhvYvdqNTbVWdzmKx7SXx57LMmljMWq5no0irO4zcuF6s9W8h97rDDeKgrn1bKaLY9qrGN2wrp9/fH4zoX72xq925djb0CsLLyd3XhT0rFZns0i9a1axnbQmrVWAGFFWdzYRRU5HVkmjRhGNpKRvlXLkUcT9YGp2AWl2DdV1sKKXVTRKOWdjlrGsyW2oISwIrpCOaB40UZzmVe8jQabRuAKo9fsVFTJYV9NM9Wsq0lcshNWuLeUdtnERntVbm1kJywbbVSZ6neKdsvD5XEdX67h46c3TzbP2eV2dh+X4KmTd1f2jthbSLN2VjLyteiN8ZczvT+sQ1zun9+16UIap9E1C83IztFZPuhRWEVVPAnOe6ge3jYuoyuPwgodovs72+yfhTPS1qjLcxNpHBe29LXfs/ZBHWdbDN6FpW3MMBaxQb1swP95r+uqiqyUjR0dH3H2G5eoQufrVGf506qx648Ia915YkidQZBbqd6GaUSNbs9NFNF7IpKIsJSiF4XzReGwanDxHI16VjZX5LJDahaJnJpP6XmbMvYgtGSyEBKBZFreqqmlfqUeFDk1dyVJFanSq6+t908N/2BZDkVCkp3kIuoTfauWrY+YXv0gwad0dN+Xd0Cdoq4w2VJ62k85t/60YAxm8QRiDqeaymdk8djZzBRhRbdhzQXXrYhJI2b30racXwci+6ENiNalph505lM9Z/0GQB4Gt3viys5UbbTZn33xWpgt6le8tQFhNRmrsQ4ICyIsiLAgRFgQYUGEBSHCgggLIiwIERZEWBBhQYiwIMKCCAtChAURFkRYECIsWCn/AnjY6uinE3twAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMzo0NS0wNTowMMoLy5wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01SVC5zdmcN39YWAAAAAElFTkSuQmCC"},"162":{"admin":"Nicaragua","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADWUlEQVR42u2bW0gUYRSAFzIMkiIi6UWh8iEi8aWgjCJ9sSQRChSy8vKQDyXZiqVIRmVIpkhmRKktrqmlggZSVkhG6paUUWooiJel26IimaEgusGcfZhhHO1iZM338jGcf3YYZj/O+f8z/1gslsTo1iYIF5o8AohYELEgYvEgIGJBxIKIBSFiQcSCiAUhYkHEgogFIWJBxIKIBSFiQcSCJqN/S/qTVzUQLiwtg8Ejtskx+ENcNxQ44veP3cNfumeL2+2ecHdADR1T8a7twpln47GOiumro8P1K6YrRk7ejVLHPWfyxHRELK1MZRMn3o15pFn/peFRlTqikUlGNw81lEwgGWIZUp+TNOcoGnkEUsU1wikKohdiebKUKGWYdZRI8+pu7+q1QkN1JIcJTa+XqcWS/GSYpRQO5zqv2wKuuEq9UjKEEjEspuriiFjmzFWaEqY/R4l35thnUodrI/Izi2OEEpn7V8y6zCqWumwZnOP0cmbW+hRmtF52Begpo3PnQjPnLVOLZfTHS7y6uSfakZQT2mObqi/eYI9vtQolIqNzXwGxTDphn6WcKaPtAz29tiPn4hqXXAvPSnBsdYwKs1+2dQ/EiVjCTj9nYl3fLCVPrmziibzFzNN2fUb5dMm17MG23MrKewdWpt4s3RS8PMt+v31Lnhwfu1V4NMzXGmSPTJw+dbbGmh5a3Nz2reLM11Wjw00fZhHLaB6GWP8t9Ws3Ja9Uva1zJnTFJlzICdklGiX7F9nCItLcZXl7+hMOZgXt3S08lJG9M2rf4agCe8bGijWNgQVT6sykabEiljlf2kik/3zvG3tkyvs7O0p8T1sfBjXbL+5/3tIdc+Pj65Cuoqe2vqVd4R3Jg9VdjyUiZTFtvOl4t3dSUvnk7XK5gtH1EctMMy3pYCkFS4qgrPXkWE31KlI/qv4V7QYapB3q930LJYHn1ZAsDmiQmrogKmJ5VPg1vdT5j1c6iKVvQKiL4zxyqGZRmixl4pUgYs2XdXw+v8hvmWWrjHrDjMKfEBGxoKYLperRq5sITM/nEYsNx4YbefWbeiWyGDYoL3ryMQX8Mx9T8KES5LtCiFgQsXgQELEgYkHEghCxIGJBxIIQsSBiQcSCELEgYkHEghCxIGJBxILw9/kdY+Ag/wUoK5oAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI2OjUzLTA1OjAwg1g1fAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTklDLnN2Z4fmdUEAAAAASUVORK5CYII="},"171":{"admin":"Panama","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADyklEQVR42u3dT0gUURzA8YEIIhCC6pRIl4qQiqi0CKJFiCwpCkHpEHRoPRVEhwjcrCBF0A6BggQRJoaXorQCKURC0TAryyRzqdyDmpsiLokaVvS7vBh2fOuu82fni/BD3rx5Fz/+fr9581aN39G/XzGidZxriNwc+TiwkF11tKqvf03W7oNE62iABlhqfPdqde2OamAByxWMgOUxWMv3g6cUkrHosfwBy7tZBFhkLGABi+gJWNGS2Mn5+m9NP0Znc4Dl3adF18G6E+5c+T1PIrDIWCmLRVtvbxjcLBFYwEpBlPKXdeJy8HW5RPcURJ4KPQzr3qWul+P5xtqSws42iTKSThnLPygNO1tyc1SvSvlTYclIvPnmcTKW72A92fj++OSBgp6a3IFYTnFFe9+EOaqkJEpB3HbqWv/bZvN8WU1WpsfydSkUBPEY6cdNF0LX32TYQ8p+WOmREQ2nmvRge0NN+KnkJH1Scpf9TT0ZK9FfDMPZjdCroeaFSJk1JsEnM9luIGMlUBx1cpWdhQ9YyRdrh2FJadMvguxjkbG0SqE88aktuexdSZQRuSozndoyJWN5BpZaBGXj4NO60e6ZAnWOjMhVZwtiMrBSleG8lSkdgxX6+rg1ki3RepNTbfOdauHTI2PZSdMxWOb8tHx3+ROWsxmOg35LhEULDyyeCoHF0WRgAYu+ClhkLGABC1jAIgILWG6F9evZRN1Urj9hzV4M5w9XqtsN1rHvcMaunR1qK/3fVVOjnehMt61j/t6QlyrqCxPziHlc566lzU9+5UTXjDdHvVp15UH5YMvQePWe+u6xMxVddcNE62gkc0TYP9FtH0dzfwQWsIAFLGABC1jQ0YXl1KEdYJGxiMACFrCABSxg2Rd/Husp/xCQCCxgpSyOPC9bf+uGRGABKwVR3tsOtuybK+od2h7oOX3ObW9ygeVJWLHJjkBvpnraQkaABaykorzoVY+pyAiwgLVISz65//6qluJ4UYqgelhFRqzvsrPNB5brYMnZr0hmsDH0MFXH7qTBl5WB5etSKM14dEvt3cbSpX2eUe6SEml/aw8sD/RY0piHx47MBw/pkJKZzrbzwPJM8z7d25bXvUIHlsykeQeW3l8U+1cWdWDJTGABS6vf+tJa2HS+1Fzy1BIpfZjMdHbLFFgegDUz3R/43Kxuh8pTntCRKCNq2y53AQtYixRB2amy7p+m9j6qfHFWZjpbEIHlAViyvamfgdQtVmABK60+KgwsDvoBC1jAAhawoAMsYAELWMACFnS0/vUmsIBFxgIWsNIx/gHU/GRvf4c2+AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDMtMDJUMTg6MTM6NDctMDU6MDB1JVX6AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QQU4uc3ZnpMTUDgAAAB50RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgUGFuYW1h8JV2UAAAAABJRU5ErkJggg=="},"180":{"admin":"Portugal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG0ElEQVR42u2cb2hWVRzHz1wtbdILU8lYUm8KpjWbSCVBEEUF6pyggWnGKKE0MMOCMt+IZPZn1Vriiyhp5ZrNrOaM3hSmYVbqsg1X6dC0mrNlWhixLHg+N/iO033YIOE+8/vmy+Xcc899np3Pfr/f+Z3feUIYt7AmDDsXdPxzc8eHcPiJkTeG4X8fCGNCifXsaTBYVoNlsAyWwTJYBstqsAyWwTJYBstgWQ2WwTJYBstgGSyrwTJYBstgGSyDZTVYBstgGSyDNYSRumb3I12h9aNTU3eEcOy3sZNCqaffYA1Ix8598PSI3tlvrGkoKqurau4JYdOqd1eEzl2Xfn5T0QVo512fHQs3dD7dujncvae+qab40b1v1b0five/M6OvuNXAGaxEsUNg1Fmxf11YeWTfkdrwKtdbJ+14IIzgroIFTODV7ym5+0OYML1okRE5h8DCMsUw0TLt0KptxWUAp08BGTCpZQIgLFYCXG5MdPdtSz4+v9eWbIiDBTRbtnyyYsxVXd3fLRt+GTDFGMWKWwSsE0+NPj3q4rQ/CqgBWWLJck/Zhg1BsIic2ka1X1/ajdKC9cqvk69YuvDK88CRuAqwBqJYLN6Io8S2GZ2CBwsrpQH4p+W1D838o/WVmj9vfzvWjpaq6jtuTbv7xcnFldMvok9+1dF4I2gar4IHCweXuLBcxMPUts2a8+x1UzdPmXZzmNx8allj+JIkAl8pdnMHZ41bHO6lJ0pL2h+F0d7rWL0rlPIWcFR7ieXLP47ByihShNtYKaZTwWLi1/W80BQCuKR9yfa9lVUl2+iJ0pLWn9F0ZMACpiRhkQPdeBUYWI8vf70ihL5b+ub/qycqTi248EUCdqYZsLBnREJpWXUwoiea5sgYgdHoyVt4I+PoXaDPj7XBylCWHFvFigykFC8sFikAHB8241htzeyaeT2j6398qUO1a+v6y1/ujjXuyQjgpeMDFlijrBOT3FiUwrCGbK7+cDTJtOWudVIBK/4yPcvfLG7Y8NfRMxVnulSPb++t/v2ZnR37urp/+bBq52NHR9IS9wSveGTAwlZh7Ug90MI/gJMRmQaLvBRTBVjqEJPweZBggdS1h+ceap4xceqc+o0zwWvgYLE2jJcFwAT65L2MVObAIucETEQwJBpwi7ErzAJYqAb1doiZA4uVILZqScPa+cOC5rHASy0WcRWpAaKi/K5w0a9r6nYcQPO7QkYjVE/irVzsFbtC/oi0Y7fsEDMHlkZXOMRkd09iLAVL81hcK1igowDlb0EBi9FIN2geq1/wnrNPWh/B53TiNNNgxbUJ3GVzprFtwfHyk3EeC7Bq92xo+XpeVdPSjR90cY3jowWlJe7JapHRcMdc4wqBpt92dQ4vracwWJkDC0SIschjpW0tAxYOiAnmGrBwdsRSilEcY3FXe/48YnXJk98yGuByjcVKC97pySenv8HKEFjEVTgazbZjOdRusYsXJzbVYoEL17g5wEJpiXtqjBUH70CD9svCizpZmlFXqJVVWqCnMRbT/B+rwhwWQBPnq1gbohpXaUv+PFYcY2EvtcDGrjCjq0IiFV0VkobgLivE2GKhOLK0dAM2CaUl7snqL3+ClNWiphVoBzivCgsgj5XWkxgrjnh+Wjvx4fLGOJWgMRYa57F4KraFvIWymbhghrtJibPzWNnPvGO30oL3O++5v6XsPgWLnBa2JM5maYIUjS0WTzECoxFpAetXJzYVT6jUJC1Wypn3Atsr1DQpeOnZG4pnsC5aa8W6UleIio66whgpntKaLcDiLbg53VziEyYhvBMN2QcLh6hFKWlnb9affm17xRSSpaAAdlgObA9RF+jg7L5f2V5/8HmUiAqrAyJas8XI2Cp9e1zM46qsgqnHwlbpVrRaL+BTt/jN1dWXTNhLPiku4sNp4tS0nj2u2dIzhsRVWrSjeGm7c1cFVkGq8ZYmIEifskIELNaJ+etCB6IAyhoQvLBGyWGK3CfpZ01diVW4Ne9MoVovdY4oE4/byl+JECs99dAEo+n4cX0Y146rCviUTlz6RwsWCxepm9bEXii44ARjVZiAUjeV1S3i7PQstZEaIucKwSjGKy38j88VghoAabxF8I47080cWgAo2Ro3UkP7JLRWxA/8JDRPYZPyR0VpJ6GN1BD/7QYN7TUS0o1qtVh6QEPDbVUthtExaXF47l+bGfyvzWhNlX9txmClQRb/PpYefNU0AapBumEyWIPO5hOfaW2CHZzB+t+OwnrjxWD5x20NlsGyGiyDZbAMlsEyWFaD5Yk3WAbLYBksq8Hy9Bssg2WwDJbVYFkNljehDZYtltVgWc+S/gNZbVxWRfzdogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzQ6MTgtMDU6MDDAbmCBAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QUlQuc3ZnC9YrGAAAAABJRU5ErkJggg=="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"187":{"admin":"Western Sahara","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkEAIAAACvEN5AAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF8UlEQVR42u2dbWhVdRzHbxRMikoIezEqCDREgmssrJS9cIqDMbbIpYNFtGYKrkVzhulqkbnyoqZbo5pIK9O2ZJWm6TZ6otBNLV/ERnM+QLqVlY7QTIVqSd+9+I+7zbv7P+fsnnM+jH0Ze+Jwzoff8+9/IhnTM6JXP1HUUY1wC1DAQgELBSxuBApYKGChgIWigIUCFgpYKApYKGChgIWigIUCFgpYKApYKGChgIVGG6L/3Nelr6eXRr+KfiId8jvlVz8qBtX4fRSwhtH53xRMLKgpKirOW/JjxYSKScsux2Kxu9c21M6pza69M1710xVTym4qOaq/zb6Y1TGzfwh8ocQuEmZrJIBiOXXN9VVt+d9e+C6zr+XM1t8+vdR4vuzs7QPvD7QOfJSInrt8YcuVvy6e6M/rW3v6uu6TR55rfKVpctMsoflodc7Xc+oBK7D60GuZZVkP6GHvOfRlRvsXAiJxgMaqAvRYw7G0nokfTN3cWrOhaMb89tzZgBUQlWXyBqbRtW/Vie1dN8qBZj2TeeuD6UF1lJFg2ye5OTm48YJpJD34b9vk5o+DasMCCFZm6ew3su+qv/fDJ1qqUg2meFVMtqyppHXB8iBZr0CBNffFnOKC3h0zWnbsz0h9pOLxWrSvcH3eivjSBmCNs+PzI1KmduUfWN1yRzCyyICAtenU5vztL7mX03Uc6NjYETVV33cjFdj507tTNx5XaA9Y46YLb3uqc+W27q2n036fYP9QhcuW/rorsTVLfy1+snDavJvnrcv+xSx7yjrq+8o3hbVT16B6mKIuwBq3IN0p95dcjmbGQ7nvLbh+8T0qtNpfz96Sxtff/sO/divid1tl74ycimzUwHEqgTi7uHd3T7p/ixG+AUupuFTOyL6goIc3mOr/HV0VXWSfkek/yHrZO8fa9dWrlz/vxzzRZxZLYMkq2D82hcluPDb9T/uUQleIxfIILHsnqNaKe4m9wLK/TkV+irT8Zbd86Qoro+ta6utsHpjblkAQyLLaNJROze2+tKtQOSlguY6XOoA21SlvknmVJJwCC1foOlg2Fks5oDdp/CPpj725pO3MlHOtfz6eHFidt+x/a9cGXKGnMVZyD0x5luvX+X/pwd5lm9UswPJIx1puOF7zw6GDWW534sxq1vcVRzN6J9lkheoBpBpSI+0BhK7yrhzQyyaJfY3NjAVT01aNHk4EpAldfrgysmbazp7dsz6babaKvRwIVtnW3v35PboKFFjmiJxysZF+6obj09fKVe2Rkq3S5o9/Z7NY/7JCSlZKFXanRmj8G7ADlsMD0E4h5U16AVgpaqXcGIA22+HMvIdOhZRTE1cmUoqo2NIJnZXSGMznVYdruu93dscweEgBVkIqpNr3HHmn86SzOzkafWYTOnTnO8jxOWul1KnUmhcr9qFDyo1lMhU8VaoNw/kzgDWMOlU9N4f14su2gBW6iMq+eWyWOv04TQVYrtgq+4aMWT032z6AFdJKun2o7vd9QMByOGCXE7SZ9lTGF7ZYCrCuMbb2bOWrlZvSkgvYU392ystJLMAaUlu3WdAwZ6e4n4A1xBXaLJem5gAxYPkYLDnBl/eW3rDwfDizP8ByxRVqNkETVIAFWMOAVbjy6Yerm8cavBNdAVZCW8uJ17HkBP17GgxgeRppaXd5dLyEVDAOdASscZgUVWVLK2VSvcgktu2Fn8tzuUuAZXWyTeIlQRSwUMBCASswTlCTDnpPmOIqM9LSmwrD8x4vwHLsRCutToxe01Jp1JtDkQDL92MzY50dNatZ3EnAGuaUGJuDh8yjcimWApbDh3vLbtExBKxBsJLrEsarzuLCYgHWoDoFln8P+wcsF1e+bKbdGfcDrBGDd5v9HPNFcIDlAFh6JMG4ETbLFFr5AiYs1jUWVhN5l4QqWOFcnwesJFXvTVUzR1V4oab3rwomhmcAy4HJUlMZoQEsxybiKXsCFgpYKGChKGChgIUCFooCFgpYKGChKGChgIUCFooCFgpYKGChKGChvgIrSGsUKBYL9Y3+B+TF2EckTR7VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMy0wM1QxNzowNzo0OS0wNTowMGRnFbkAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NBSC5zdmcabDszAAAAAElFTkSuQmCC"},"191":{"admin":"Senegal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrElEQVR42u3dPyiEcRzH8RtYTRgwELtMRDz3XNksSspmlsVgkFKUMimyXBkMFEaDLGLSpW66QcoZEBmkUKaL4WP4XY/nep6754nfPe/lm+7P73d1rz7f3+93z5NUqnl1zXFsr7tNl67bV/p8fclk7KpfU29DmUXVj9Pd5f7B+4f2ru4e22sKWH9bnyYel9wSsIBFYgELWMCKBJa+HmABC1jAAhawWGMBC1j2JVa0swALWCQWsDggBRaJRWIBC1jAAhawgAUsYAGLA1JgAQtYwAIWsIDFASmwkrl4N3MLWCQWrZDEiq5hqZJYwIrsK9+evHh321S5bAZYkdWJhf18eliVVgisCGph9LbVPW+5WZ9xVlSL03djbhFYwKqpqv3Ft1EAVkJhqf2Zc0XbEIGVOFhqeT3jWwfOoTmXHomjIQLL4nMs8+CgcvU2QbPq2SDjVCZIYlmfWPqCZ2eOntNrSp3KtaNxo8HZ9JtRC/kg42jGILyAZXErVIrMXx0X03vxfR6x0yyVj1X1mfUaYNXJGkvv8q6iaqkaLezOkcSqK1h6vU6qvLu/sFUjaDR2hewKy5pRdS0ySMsjsayHVQuvk1yh053TCin4Wkrv4hyLxPKt2sGFnV3v4hyL67F8d4sD2e0z58kvmfySrHck2+bkaYUk1i9V7cw7sqjlStfn7o6qH77qGqL5mYFVh7C8y3Y1OG8O6RE9a2aYRmCNBayy/aDaWdhTKPMkTElm3hwBrEQnlhpcdadQ3hH0N7CA9fMbYtik8Vv+V3fVA7C4/SvGCixgcc07sGyARWIBizuhgUUrBBaJBSxgkVjAAhawgAUsYLHGAhawgMVPOsACFmssYAELWMACFrBYvAOLxAIWiQUsYAELWP/yv9gDC1gckHKLPbCABSxaIbCAFbR+A4xXV+uLEBwbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0MzoxNC0wNTowMMwpBA8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NFTi5zdmcOvYyFAAAAAElFTkSuQmCC"},"196":{"admin":"Sierra Leone","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3XsY3CMACGUS9CZqAlY2UNJmCA9GnS0NA5FUNQICExAgUUpABBE2xHQXp/8YrTYYH16S6EatX39YajbX+qO/eQbnAFFBaFRWG5CAqLwqKwfOH3/vN+Fn+x6F8hhUVhuQgKi8Ly7UxYLoLCorAoLHp6ExaFRWGRM4f1fKrI+2zxembK+VPPSfn9Ep+99GvnuZ/PsLr1+bCvyLyGu1mBCcuEZcIyYZkJy4RlwjITlgnLhGUmLBOWCctMWCYsE5aZsExYJiwzYdliw2q3x9t1IPMawq4Z4uXN0DQxfvn5VFPOyfUeSruEuyp9b7+dOb7sf516QZxJYVFYFBaF5SIoLAqLwiKFRWFRWKSwKCwKixQWhUVhkcKisCgsUlhcpg+vVbnH5O2QnAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDU6MTUtMDU6MDBnQH/8AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTEUuc3ZnXmLsXAAAAABJRU5ErkJggg=="},"197":{"admin":"El Salvador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA6EAIAAACZlLfHAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEXUlEQVR42u2ca0hTYRjH96VIygq6GFkW3YgiDCsC+2CkwwibGVk2w0uGWSSKZEpJkSFWLouizG4fNLMs7xViKF1FS00qTEX7ouAWmSB2U9yC/U/wyjxjtkmu8//y5/CeZy/bu9/+z/M+O+eoprn6q4uL/q1OD9y8sWTOeHgnzqjjbfXwflT8Yoi4Y1dvhtuWkNKpBIvq4B8kHYvqYLDoWEyvrLGozoM4HYt4jSFeigCLuzymQqrT/0SZCqlFTIVUp2w6ECwqd4VUdt6pdCwuh60tCTYv2G5gr2t8/FfIxaXSsajOUGOJJianlnZnedaWmLGb3/o8cq6MJbDd5OXelfVWofWRsfi8cmdtWR97xoeBVRBYHdp5hEp1rKpMQ6YBUxfVUo3GgRp9CNQU+cPw8bJ0LJzlKskpwRoJpvP9oXWexrdf598NGKbmcQkycYSrR7CsI2X61Le/MkoCS/CkljUtZ+tXAKlh8YBMAI4rSbD+ICX4kxwcBQceujWoDe2G1O7tI8wj4kWwCNYwlzIf6/fpU9qWS/4kRNapnrRf+KFPere0rEMcR6QInJQiFV+BqehVgKD29nuPl1W6xOu67E1B7jHp5Rrg0ufek93gC7A+tNV0XL2I+HqPtxGtYZGNiTPzl2UuyWnNGAKUYgVGsBTtVQAiKzg//fSpKr9Xga8bfeP27H1wC+gApp7kurllg1CMIDki8rlfXe6rNswglf9WEyvB+n8VX7lZK4qflRR3JERnhh/KuJqQM6FCA1wwDpj04eVH85ugGLlzrXByhQGReBVmAGRAVlKCpSDHMjtKf8Tn/kdPd09K9Q8e9FFFxWoHkQR1cVfy72mQ/n55fHGrapbwMtdY8KTClMfzsrci0vvbTs88n5CglM4dauCFBEqwFJoKkQSBgjol9P6J2wALya7bo8bV6/vP7/WZs/1x3N3b5BKdB61e9cI7TQ1XA1gBK2NK4+NRb3GfqOhUCF8RHWt9RPDNZJf0oRvHw2pzux80rg0QNaslr3ldU9rBG7EbWvalntNodwUtPGM8HIRXYQZgKiFFx1LoftCs93srtcUdSGTAxX9iujY5EdDgGG6EY0uNCjw2ZZsWM2B3yXYDd4VRqJbWeUWnrV6AnR2aDqifUKTvL0hquvQmQX1yVrYJIziLSDgcZpDAsujdEywlqRkslOeoltTRcTlqHdIZWg9QtB6gcDjs/pBG4VUYYRIkWF1iMxM9dICChAgFNOhawatE+MR4VGzsvBOsEfCC38C9kNoWz92Zu2gi9oyotKQRM17AUUSKf0UTLFm80N9CtYTEh4QI38IIziKSLkWwRnNxn+2bAK6eJVi8iJY6Jpcmj/ZSeXsu2nfU/HK3SNhz64E9N03Ixdtzs4Y9q/F3n91RMbwTmsr7Cql8PhZV4Y8OIFhUgkUlWFSCRbCoBIvKJ/pR6VgEi+rgtgXBorLzTmWNRVX4o3jpWFQW71SCRSVYBIt1GIt3qlM51m9Pa1fNjb5KagAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDY6MzQtMDU6MDBoJcg2AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTFYuc3ZneSIBDgAAAABJRU5ErkJggg=="},"201":{"admin":"Saint Pierre and Miquelon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAASeUlEQVR42u2da+hlZRXGj04hNKnhJaSraHZhshpFulj0wQzBRkgzMMucRixDMZQpDUrDmYIgnRr+lWaIDuMFQQONJogy1GAiyzKpAdNmmEJw0i8VfWicYD/7w+/lOWudtc/l34z6ZbHZ573s/a5nr/v7ntFBK5euOu2tpK996utbPnD3LY+etfMNN+14z9suO35tTvc8vvXcLXufvXHPtmeWnv/vvsP2HSWqOzs/dsoFq8/0Xo/vessZR/xc12rz0ecuWbn6Qn+e0fs3/njbntGF37li+6tfogcMdUYe+eZNK07793Hv+PhV5/ymAi8ByyH1p1VPrHryRxGwRH/y7mM/ccTVEaREjz7422vu/NdLrDrAqGAUMVW/RvDafs9BrxttdWBVJJYgJekYzf4iBdYh19/+8N4D/i0EmgqDJVdml1iasQLoz3zv/DWvOf3Eiz677fo/vHiAdfRtPzjukb8d8J8EWb5699k/PHlvHV6ykyoSi1ZUBCnddwtvzT/Wr9n8l/6hXxhfc86YF8Y7ugSSnMjhRfZHEktUbXJIcUypSMq5RmK98IClN+ocFH3YRx1+0ZuuPGW+b7riqUtftumTosu0hpFZLXhVoHDzZbcec9shLrGkCs+4/1vr1t5Zl4KuOnuJtT+AoGP/BPZ888vr7/pQD47uWlS9dF/rprcWmOQqffi+933/FUfqzpjxC/Kst0fhR+uOuPmllZ9fd+jvlsnLzkMAFQvs5tUPvfeBkyNVmCs+vbDPvn8CS+wXe3p4BcASOPR21+9Yf+WKk+796g03Hfy1x664+4jR4c9e/st7R6O9Tzzy/Gik66d33L999MoJI3eAWHnCqevPPEZPIpiK6o7AqnFEdUeQVd9lciloA/39p2ufueBa3hG968+f+8qnHpTsOXHVlhM/fTVpBCxJLG8vqjE5o8+uOwsEVvTt6j5kDyXKsQ+8/ZrXb+2lUcAAgUPwOvvQj5x02B2CjoC17VdL+0ZLN1173emH3HPHG7/xz9Ee3RcIcsmk59GYgqz6Cqb7/vP7XeOoZuzl6PJ8im4VMdTp96OWER06prdZ89f7TtjxhTm/dgcLwYVUUNC3ThkjWDx63tLm0Y0vv/icX1y2MR9ZskEQpMoTa3uZ1LXUaGoZQhZQU1+NL6ALNHpayirKSwFxjCysyKHpbLIcFvsDnTOwumV61cMbrrvnSao2sUeKKfruJWNCVWXGeE+dMbC6CCmBTHSMJddd61dXiD0cMa9aUspOGH8REutFBCxIFH3H+rL1xQtYAhltFLURkxrGwDyvGNfqpXE4Js12AYUgCNVuoMTZV+/Ygw8qfgzI6rO8BKxo6bXQAg2pZECkqnp/DfKG7BFYKUtcKhBYpGK/oKw79EA15gRJiaelByr4Nn2hUmk79hYeP5JZ1nnXDRuvuWZdhV66Z8vm7z7pNPcKo171eRduvNM576LeUpThd69FB7zcI/P2hAjVH6kYrNHoOgjQkQVGiOvJNaN6UQryOWV7Sbnrmp/NAsMNun7k/LO2jXbmSeKKV1ipoVDm0YMOyxluIJPIWrKcBr5YIirZoF97CQGpQ5g6ZN0kZ+RJ132wAAq3UZemCikFBSCpe1F5o83TztfeimJI9RxiLrHyESolOgsHlhnFuhaM6FuJGQQTfa7GHoKU4n2qKjHVYeSQ7WUJ5Cufk1KT/qx8WF1zNL2FZpyzXZUDqwIphjfzXCFHy+P4UZJ70apQLGFkSEtPljiz9atWwONDDilBQRChMuL4HlAl++lFCo7qpYAII2QckxKLv06w2OarCisZPa/TynOFCnVWimQ0r55B7RcNLL0LweQySXfEElGxUAxrrC6oIcGILkIk58h4zSVJQ/ZrHN3RMzDcqr4u7QgmhmSXKUxKSOUqT2yolM3k9ViVuagiFw0sxr4Vboio2NmwBxYPLSTCxSVTDlkBqzfhkZyWDOOYkf0Ujdmr1DmFbCbkHCts9lKZocByeFWS3Pp1IYV+3dKIMUyG6Nrv6FrAIstpn7nko0olm8ls3hGlrJKXSonFTIBT/1WfRJ9+Diyq3pcsJKcZwpgg+aYr7iNVUCBXhVHfStHfnIHVQUHgEBsYW/eMG6HWWzzGAAKLmbucRqlo99TEBc8KRJ8EqZ4nAoFmYShYlKknergMLE+w0nI1lMNiOlUYwUvMpqxaCLC6BRJEKkxiErePJBFY3bUzPgeoj8+UEYMIYrBkW5Royp88LJUJnpwQFygldxmfK1VJaGiFDEQVfxJcnKoqQVIq2p9DYLFXNKao5uWTqJ7inU/fvvTY0nwjVVJPdSZplfoFBZPEeJd8EaRykPWxb4tFRUDMZ2kUd6DU9MFEYzrNVWpD9RVGsSin6359yUkXbxCttBdchs6i4kH16i2AOQFLC6pFzyGla6qSJlLVUam/yji5xJKcIAg0l6RFzuwIdnq2MUoQsiqXdqRahwimMbA6RuZlMISI7ADdqUOkAkc9g1pOqHyaiurJ8yoGLq7Yw/AmS24I0IoTEMFOkoNWi38A+QgO08gSqqhXp5SmpdWOCotDoEBdVtpTIVbqt9hLc80rCS3J58CiVSE6Rj1Zvs89yrrd49eyrhi1p0SJLLbInqN0iRI+DNV63ameh6GNwdGvXD4NhUIdcN4rmmsOwIJHQ2AJRowPsbivqeq0cdw6yQGUA67xN1Mlm4/vdWM9sKKSHlaMdddMwEvmTZmczpVRBSgEh3rV60slL3NF3ANrhhSp59QYs2YpHKPhsm/cqtCdig9Yl1guFwnc+miigikhMnQDhd5Rn9mUkXqXUiprkR9HeEWMl8JSewIlByVbimpen6UB1kB4NYVssDAEKa+vkpwgyHpgYXZXUrlhXoEd5aJ7mhV/kGqdMJ0ALFtPrYPWJ7SrKlwI7ZuCOe+grPeinMvtvOmA1fhxBjVBh4Bj3pAZuj7EgCSG5EEOl4pXyPvc8uVuQe5XetCVUrZRiNy+xtpXUD3JULtqTKwxV4UDAgRdy96CKcNRkMq9xT6OVS77b5bS2nidJ6usBBrWkguCrCun9TOLXcU7itgxMxj5rR6p9+tGynbPrLeIDHOm3nV/DkU1lQBBDhQPEFQ8Tckq1kZGEfwBNhaDAkGsmcqRiyv3W5YN09KuNMWeetSqIr2kdChN3YYjpDzDyCR6s+W1e2t6fKJqKfuSOUqCb6bSmojldeNdVLCI7KTpjPcB279gT0TLQQWnawHIF1fWFZeeETWXWHWQRXUTrBEVLCJVyLorUqa03RXwejImaiixCL6ZJNZ0AYI6dAjWodso1KsCrBxSbCNfSS1Zt+n1Ulxo1nBGNRE5pFh34DUI3PIQ2ViUVV5Cw4ouBnVZGsSKLgGRtV/cmzSH8yMqym6MjCnbXqKSZEPDqnq2MblCmJmiTT14uinKa8Y9duXVmLRaPBkSmdi52vJSGVbZe/7RU8KSowyE0l7UrwKc+kr6cqOb4Kv7rA+bQ30pzed6gKA37VNbirt0FIzoQxIpKBvXoRv/+FNvOe+357riY814c06LW1e0vUw5RolYgUO/0piNwgGUJV5wx3osv6P2OlcnimMx6iZgsfLdd0t7ZM7LDL2itSnamR1Y9UBoJaQZ9apH8xmGCKsbsKeZBWisKGKQ0Bcrr3RwOUH7TL3EcjGMe/TIJEKBpX8OPm78oougZyBQxsTWaQ/Bu/QjSfTMXh7Y+JKzZ2aHGukE1lClNp0qrNdjqSU3R4iKtS7PBLsofEB1RvuDe2ya82Fg2nPzJ+UKjwmhVdTIxY61CkCw/pP1Ds3xIbb/h1X2VLWiei+3I+e8Y0ffgZRUPUAwS3VDJXjBXhMO4QiqQ7npgLZOszO4W8qogIRyi1lFhgZY8ibgUrbRaYhOVfC6ddZtcvsX38sTTRpTb8dwSR5QDetLo9O5hkqsijEuljPVU1dqtJmG9hpQjwUVQInl+1t0LYhQFeaxKFpIzSZSVDh5BKhRxBZLoyRrQIN30Sx8WoKP8ol2WyVSP7gYpk6nCx8MpdONr15N5L2S2EHiwtUWZUMe3c6LUrihiuOPYZUd1DHGNIafG53dQAOc9hOttEpdvK4J0GU9bWZonRZV2NBIVS4vZ69uoKriJk/uvMtZ4pS2F+P1hBePBuGpDU2GjudHwCZj0klQoxnufmW9WnUhpnoOLJbX1ZUXIVWphnC/Lw9DzPe0GYZGXfZE1evOJIYoCTJ6fDwlIdr3HG2up2SNIuxRfWkepF2g+psALCtoyY10QqQejFAKqOnVAWtC2cxcK98lP2jqUva4JPPSPNlweZI4Cr1GUS5SOh++94ZbLSKZKiDqjQY7Q7PQfDdOvq9meXrNd5eOn1bFExM8jOknhXo8PbfSooi85h26T4aV+AwfeMLHT6CgQ9Nsu10EsHR40P5JdYLDov+ZgsFVZwl3LTvU/JxSly4OLCZS8vBstNePktWfx08lFbBY0a9x5rbp/sAClujynI/F2BLhEqmqiM2krHzy+548dqCwjVtdtLfcwmPqJtqUscDTlCM5oSPXdN20WXXMzQdt+uNTlz938Ca18V9F9ataiurcB9E6sJbzv3SoHAkmL6nLk9BeGONwzOsdKPncGCeMCDImzh1SXstQ2S3dbLSvR+edkQLE7ls27x59UJRw0cPx1x5eABOv2ZKHWutX0n4Wex4/hKMx5O3wID8Arf/bI4+E2cJ59Cs6RV1vRFtHd/JN9JQT7Euf1CFFBcp8qHxwSVkdU8CiRUKc5/dJA6gvj/f1ZLbWUy1lBzMU0gSufW0jicXvhkBhHY+WUtKIbUj1K3s10i6Xfx1l9NmPndV9LRZ9TMGRFevNv2ph4TQ+d3j7uX5iGMMKrJVlX8bJoo2pmtGrRXhCM31SjSBwqOWtpz3+rqdv/eKxD+3a9SDDQ3oqt6gEO811yb6fbXhihajuNKVHdm6gxtdc/YxYYc8r9J90pIDEZmbK+I3qQSmBKJO8Cluv5xJuDLUncQbolQQmLrECE6K6w+/Mj2x0Jjmr+I2yvoDLzRlVJkQp4gxWG7GWfck2ze5/AqBfKYN1rXH0LoQpPUH1ZQJeHjfn5ftqHN/M4r24Vr2Gye0byRICiLpc11w4QofyjGpC1zw8N5JVtLFUlSVmaPlE9ZJRPlG/9juqOyDqtQkpTxmJVfymBQXmVfWrhzA4o8shAa7vG0TmNAJnZ7C66WuGAUFGxjfPHPTVvFoT9dJ1Xw8X9HK+KK45qjj8ZL/LJ9pMvbHfyR6CifCSDHPbK4KXG++u1EIrKvimx0Aq/VtKsmpCJsBmdAbXQ76EuMap/02mWuodS7FAPLng0qxq4Ujz5tlySJH9Lp8EEXqOEvUOI9phVHlUFr2jMNQrDMA0mM7yD1g+jsG6YTA/hgX/C9fgWSrrU1nJHFgMFjilfIokXK7mCMQxoY2BwKLul1TIJRl70aTVnQksKf+fIIE15nnKLOfbDXjOWcCUP2f+a6gKA1N6vwuQmlSgAqJhrl8l5F0tEo5i25hae4MLmUol4uMrB6rrBvQpyzkOR5AdI8rnd8CVYBeYDXXVX5JYjeSI4JX6cd6mD4pWek0nsYJvSGzWApEZokOtlojlpM5staRpzDYEGT8PtuSH4bCmJPNPJbcj2ddH8NEGrFUELEVN3Dai/dRYRRYgZUtaUR4UzWPx80rpjFFJM6sPyicCKB9fvVz2sNyocS8GvmMja1OFFcknV9ljVHlu80URrMagBrAU+qtH3hkg9V4Va2xASqfywgWlVrd4cnVZn53QdEU55gnLHqWDZsIzT/Vf1ANsLA8WiDI5SvCxjUupKJnj0Sw34ReRK6SKdLEfyQlKGsoVKjWybTorJ4otUSXl4KCaptp19Vq39kLPdyiwxHKPpDMzT+gwAMHwBIGlNrmfOGdVGPhoboW4peLmvIOP6tXbVECQyxhClq5JlG/g7HoeB01uk4Ugq0iyPFeYBx0EFFYpEV400qn41JfpHY6mcSJLK5JYrkQGBwgKjj2/+Hzr7OzqlW80xlazCDvBEZn5+UYu2mSzq/Vm/AqkPKnMghCqMMaxPI3DETimoNk7CuXIuy9rhf0emMgN5KEQGeyu53OVbam5wWLRhX6SH1Rkkc0UBTxJXf0JgmrfVEmgjf63IgKWU/92+S3mlsfsWzai+FmuFv0jqVh7uS3VvIupqumk6WDHqKIK65UIs5Qgz6vQz4OKHlua0sROXQEGJ50BtMAYzHQYRTZQZO1FFpjbkaynKJkN021gqdhY+wOdziv0DN0cYlcz5/I8IDlGUppXGLkUOfsdZGNiVEPzj7N7hQc6sP4PdJavfNkhPrhvPpr9+j84mTe9NS9e6gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDg6MDktMDU6MDCZtJ6mAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TUE0uc3ZnGgZlfQAAAABJRU5ErkJggg=="},"204":{"admin":"Suriname","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADX0lEQVR42u2dPWgUQRiGx8IISuAkQgSb+AexUIJEFGxsxEYsxdpCC60sxM5CSCOmULGIjWhxiK0KIhYWVmKhCSoiBNFCEYJBxP+ckHeLXeZms7szu+vePlM8hL27mc13z803M3uzZ4w5c3xHB8LQJAQQsSBiQcQiEBCxIGJBxIIQsSBiQcSCELEgYsFW8tqtJ4+6VyEMS9OjUEooiEVBLApiURCLQilBrD8PF7qLByEMS/Pm0/51x+5CGJbmxcvO2ORExCPDZ3dfailT4/B2ZO+G8V9iq6OUmUmxbKa/AfYzsx93tZK3/rz1FGr34/TpVZs2fp4593j0cg4pQ51nlXH2+V9izwwnlj9D1R/0POfmRv/u+vZ97PaNtQdEHakoJmHFKhafQiKaSoPSQCr99brP7pgT4vzSoYvbThKZdCLWClQSjIulI0QGsbz49cP1peHfkVi95zeN0REig1jBkqDE+jl7b/uaLdEMkVghln8SJCGWI1b1M6Baufjjyv3OZB+xXAkxHp+WxSq/WHVNqgPx3dDh1Vv3aP0pOxfmpx6MPI1rZIulv/PWLLZhXmkK9lUNUe31zPj5nVN6OxNauOiSKV0yuwb70ZiIOivEGhC+P3V03+YhDb1XkMklVvozHWKpRbVOKqx2vbvC/k+9RWL85Opv0vuqDIlSrbShf8ojVrGF/0aNxhLzPrvvyS6TY84YXfwZuA9nlhZZbpjQUFrXAX16LNVQ4sC8KddqGy9W0EBIiPShd/q4iiVTeizn0L5Y4hOj4Xmj+pXyWjFt66JdX4z58mp6dv2FvDLFn68a+IjW0WP9lxMCzdqiZYgsqdCREFVDO+eApMI+4qYnwfgqVJ+VMOtVWusfjHEnYnmFPrqAY4miVSh7SK4jia/TxERUbW2b+iCWOwlaF17SV6H0aOJi0TIDJ8TGXqU17fw8JWaCy0ppFapYItOr4qO0aDWr3mlNGa1nrtPk/sqH/+n67E5xbe4oVKfSlmZzUR/jsXlENai2GhJi9m1tFfVY/nvx7L8bsotQ/UqfXTceVG2J9fd27itkzy4sZSc0dxmAYcndZijcxoiCWBQKYlEQi9IssbgjOSzlPu/8hgLkJ08gYkHEIhAQsSBiQcSCELEgYkHEghCxIGJBxIIwFP8BJA4soBLvicsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ5OjUxLTA1OjAwDXm1mwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1VSLnN2Z6h79J0AAAAASUVORK5CYII="},"220":{"admin":"Trinidad and Tobago","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFEUlEQVR42u2dTUhVQRiGJ4Q2ZhQYESgEoUSBIEgGFUaLTJA0CRRaKP3Ywn6oNuKiTQRFodAPEeamggqMoiCRcFVIEVl2y0oLUSQRUTOKEEPi+N7FHObOdK92z3xzzsfABC66c5nnzsz7ve+ZI0YamjLOxv48nBqZ3jM3O1c+Vyv393q9trrCa2JXcA2f2DHiNXVUGO3g9MHyk5k9b1Zm56/nnlov8A8maXbnWNb4KnUiMcH5RV4LEi98IuBWR4XRDh1oqGg8yhNJFKye3ctvrpsc6KvcV/vld8vHJf1t6kS+GPZacYnXgl+9rrV4Tbd6fdt/prn5wtu2NQMFG/FdeGppgCXh9am2pLWy5WdBd+urdnUiP6/1Wmm314LEC63xvtd0G/d4Veul28djS/Mubu5lvCiBJeH1PlZQuT32fcWj651H1Ckcy/Va/YTXgscLn4sxqGPDmDF+xosSWBJe+PVjJdBtQ1hFgscLqyZWUHVsWHGx+jqJl+M/iX+AhR5nF5xj1G0If8EZKHj9iDMfzn8qXjgv4uzIqxcNsDS/Hqgwc3nCln7UlSegH33lCYaMBFhK/3VrdV79spltg4XD2bryhF39qFtZUbdj/UgULPT9OaUzNUW/HrwefVdFRz8Cr3ObvKaOKi4+rl7OufG0tyb3cOEhxoscWLJ+/FHaVfdsh04/Vjd4jZp+nHzeXvb4Lpcn6IGl6MeJzFtT7Vk6vGzpR2Ctwwv6se9JcVMZb46kwJLwwtkFG43ugI9Nipp+xIaOzV1WxIyIbbCUnqa9bdaPkCODW+o+HBti/UgUrLi9PT9JZv1Izd6W9SMjQhQsWT/StLd1+hF4jd4533FlQwjLE//ru2j+HxHk18AB2Wxv29KPEBa6Az5EiU8/8jmMwool95geSH1q9rZZP6Kk4rO3GSw6YGEyUJyEfjTb28Ef8M32dgL9yDCRAEvqzfY2epr2NuQIrC3GiBxYcg972xyPtpW+j3Q8ekGrsqD2BUISj+YVi+awcIKhFo8GXsnEo+P2NoNFs4/Ho2c6MrpOuRiPjrK9Lejv7uZ4tF17O+V4dGQgE64cHpPRjzTt7QTx6AjgJdyyDoDXcOeJl6f3Om9vM1g01zBzPJqyfoxCPFq4Xl8xx6OBlwPxaAaLJl6wt3XxaLv2djTj0SJM1WHX7W1fPDq0D6y6aDIo8WjdKceufoyCvS3C6m0BL3M82pa9Df1otrcTxKOdQk1EwTrFM9DU4tHJ2NsorLjoP4romAyU7e3wxaOjAZa0lYQqHs1gEarjS/oxJLd/kczgi4hGO1K8/YtaPBorLmX9KKKcGQrH7V/xeDQdvOZHEm2wUoxH2739K4V4NAHIGCytve1iPNpnbzNYLtrbdm+PRnlCV/ilcPsXg5XU7V/meDRNe9tuPFpYEPwO9pTj0cDarB+Dv/2LV6yUH69FHdzFeHS8PMFgkSu6KvFos36kFo+O3x4dSDyawVqcvU3y9q//HI9e4JPQfK1Fmm//wunHrr2tK/xCP6bD3uYVK6B4tF39GLy9zWBFLh6t048Jbv9isKj18u1f1PSj2d726cdF4MVgpb08QTMejTOfTj/67G0Gi3JP8+XIKcejk059MVgcj07Ly5EZLAsHfJovR5Zv/9LpR9/LkRks0vZ2SF+OLJyxUyIQj9aVMV18OTKvWM7c/uXWy5EZLEIrKPCiHI8229tyPJrBIvfkI3qUJ3SrF+XLyVG3+wtiMzbPq1BfqAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTQ6MDMtMDU6MDDmdv6mAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9UVE8uc3ZnPvBKhQAAAABJRU5ErkJggg=="},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"},"231":{"admin":"Saint Vincent and the Grenadines","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC5ElEQVR42u3cIUgEQRTG8TNcESwGjSeCYFBQMBhMctishxbRoIJB8LBY7Nq1CIKI4UyCQRDB5gkmEUFEk9FuEwzPsLDsMrszs/tm7182rHfj3syPj9nZt1Or1dpbD6/aju9v3eXx9u/o88dQnWPS8XP3aa2xNPx93lw+qfWdHq1uKzpqI9Xo7O9cXQLLHNbIwsVA6wdY3mG5RamZOLBILGABC1jAAhawgAUsYAELWMACFrCABSxgASvoRU5gkVjAAhawgAUsYAFL1zwPWMAisYAFLGABC1jAAhawgAUsYAFLHSz5L4/r3fGxA1dtSmvFXD+wlMLam7zpn/va6Fw351dctSmtScvA6jlY0r4MibyXZ59b0oJ0q7Ts+1cAKzMs6TLfWRXtCPvckhaibfrOLWApSqxoVkU7wia35FvxN5J95xawFMGKZ5V9bsWzqpjcApYKWJIr6cOQNbeSsqqY3AKWCljpWZUvt9KzynduAatkWFkHwCS3TLIqnltub0qAVTIs86wyzy3zrIoeD+u3i7NTwAoelk3XJ+VW1qzyN99if6zSYJ1t3g9OT9h0UDxj5IxNm3JVJFbAsJJWrcpNLFczLWA5g5UPXL6M8THHcntvCKzg7wqlhWjScFcILAfrWHIm6Xzx94PAUrRrsskwxLMqmkzpfy0yq4AV/LPCeCZlzS2eFfYErKQnhuZpZP5Jf1kFLKWFfib1WOk5ZPJ56rGoIM08czKZjVFBSs17znu9pO9S897TsOxXp0xWvIClGlYx1DSspwOrUi+s2jwBdPWeD7AqCMu+DuK4dfcyAyxgZa+IL76qnXqsiuzdkK/WlDkWsBwPle+1dWBVarcZ89wqK6uAFSQsk/lWWfMqYAW/P1Z6bpWbVcAKGJZ5HQSwgOUgtzRkFbCChxVdK9KTVcCqyB6kPvb+Axaw/odQ51UBi12TgQUsYAELWMACFrCABSxg9ebRZi8dYAEIWMAqA5bOCtI/qPSZ+7TSwnIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU4OjI3LTA1OjAwSiA9RgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVkNULnN2Z6CMzxwAAAAASUVORK5CYII="},"232":{"admin":"Venezuela","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD6ElEQVR42u2aTUhVQRTHH+WqRasiKKIPhARJJBEio4IoaKEELSKigiAqhDIQ+oASKmqRtKpV5CKhsCisKKLvBA2DPkwIBEvIWqRJSJEQScH73+A8xif3xX3vOff+Nj8u8+bOnJnzv2fmzLzU2EjPiznzIYyWKaYAIiyIsCDCYiIgwoIICyIsCBEWRFgQYUGIsCDCgggLQoQFERZEWBAiLIiwIMKCEGFBhAUTJazNA/V7Dk6DMFqmUjVVS6+NBUxVvG/7GNCWu7R1sr1ly9364VsOU3PyUUxeM9f2wzDXmfw/C7PVCVOSbxa0s1wdU/B+ly3fuuJhae2Zht+dc8N8Kuur6us6OvVWxDYXaQbiLqx8OCPEr1uajjzqXvSs99Wa4ZM1G3deeVItkc2evvb6rUZRJav37hp+2qyaeiuHWJsEJmEiJIhsv5ZVbyq919O8snVd35vesf6u0abxkvGLf0pGPo+e/jXj+I4LR9+1zBxa9aH9sKgS/aqaekstqLVsgp7ckljFsyR8Pbu/n6p7OSjK8Qfazy7pWSA3Sy6KQ1ZYny4Nzfs5vvhE7e2756wjVdL/dfDbjwbV1LNaUGtqWb1oobQ2JClixXVnYBY4xRjJ5fz+q639fW79tur7hwa3W7pSUIlb021NMUw9qveMRTPey2W8vxtFEe2EFF1ECUvRRQ7Ws0RjF74gYjlLp41M9i3V0bN6sf3KkozkIH7yCsYVj425U0eu1RZbjteCpcVOMSaIH3k4RFDL6kU9qndZIqusfAsqr8L0FaeNuZwkp2Y4LE3FjyBamPr5cJjtXT0Gi6+xZwJro93gF1dkvp9OyTFym/jgRnfll32usLSJjlhMIageg7MuMw8ql7XW/pjswzxeBNPl+r7dbE4ZWUZOVywnOb3LKlkoa2X588a3j0e2BRHL98XR96VQ+xXrHj0rI9Mh51SzWVbZnNFarhGRFRbjK3H2KGJLxc07Ax2iL5FYy59NJibYI3olqX+We/tNZOyW0g7QQUDOuV5RKWvdY9jC7wWTvRSmp15uyHaK7ZdL3CTDHsO6gvPgYsevcyxNsb2Q0VZXJQVN0fMmL41CI9LoNFKVuEe1RKzINrxKzt0LYHtV7GMMttfb7kW4Rh0kIr7suvxygA4b7SWJtuq+RywbtzQiK6ys/w9DWNH+T0E5lPIpb3LAHIUlZrsIR1h5WRDtLZv+w+l9DmWEZUckTs3TOO+FFQ/RJO6vyRsul5UfWwhhtEy97po1u7wSwmiJsCDCgggLIiwmAiIsiLAgwmIiIMKCCAsiLAgRFkRYEGFBiLAgwoIIC0KEBREWRFgQIiyIsGCi+BdwnhLb54MehQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTg6NDMtMDU6MDB4ABDSAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9WRU4uc3ZnXIWjIgAAAABJRU5ErkJggg=="},"233":{"admin":"British Virgin Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHiElEQVR42u2cf2hVZRjHt1aiVtA0N5PFBuWSNCkQM6OE/mmpwZKwTIQttTkrzRSSOcma+WsFW64Cf9QMTLaozVyUhNWwiHCEynAZqYVOIcoY/uEfsx+wz/3jG6/v6b33nnPu3fb+8+Vw7jnPee95Pvd5nvO877k5P26aWT+z/uLs1prW767U9N9zZaqpuwt25R9+5c72KadffT4nPyf/mYr0dcZbM/Zu/g37F8Zu2LOhsutobklusal82lt+vquvmbPCHQPfri+/Y2PHyp/z5tTMeZbrHvt1VOPI+46VlX8x/42eCx/9cfCbktbih2qKw7r6ENfE7RtQbiu32MTr/Ou9v/yet7VuS9tnf5WeLX16/YY4weLI9MFi5HwLcDm7d/me6jPHjxdVFVnvA1h7sJJQ8+biTm6uLZKlDxmI4LCowSLWusBk+77dDScKureMv1j45Op+D42TBt90hcwWyYCjYfSb733yuTtkUUes4Mjk8r3CjdDDTk1nJ5smTMiwEFyTRRGxbDC5p3v38XtNAix3JyX7ized5BKxuFYwWA+U3F+7PjedH0PwOL2GDFZqNYp7JEgnFYaVvj1MGQbLBbJk080Hz+37+shXLmAdqVhVtn4eR3KWh2kQKK5KTU91nrjxzD+4jW2eqlSvFJybf3aTrUOWbMRCQUevYoNJlRGm9k15NPFPhUlowvEZVWKPDSw+Tc0y9VP6IwRKD1YSSirJBrWBlT0jHL8jd9rCPA+Nk+rzl821Nk3trMGon24b0TtmdnRg0dNX5ak8eI+5n21zj/kpVWaEcwnDAYtsBovn3DWLG3LrDzwy7aWdVSfD0sm3vNBXtj94/4qmVaMaejIA1nCISS7fMTqwZt9dPuK1zTj72p2L1k5cN/Hlx94uOjB2zNJ5d/09tW95adGpvAWVhZOuRzmG7XG1T9Td/vBTFdMv3byNs/hU7bCNHT0X++A1TGssHD9UaywFS0Epr3ixeNwh1MQL5dPthctalkzZOPfRBTeNxgIAYYdzOXJiddWfd+zlU8Di6kPwqZCntuieCsNSJqGjqEgULMWlrXh723VlPyxp2TVy7dKKZbeNOwgWGm+qe7fumLWoa0Vz+84WwNIoZdppvLTmcu6D2IkcrGQ7OqxeMvtYNnXpOSXbx7qKDrjfvJbZaUutj8W3jqLdYIKlWKBmrEI5C7CADFxsdkw0IwQrtWKT/jW9bNPNOJU+uCYyYg99c/fOeyJiSefddl1zsZ553WzrvNtSIRDYkiBY7J70/rvv5Gnsp2ZSO1gw7WAhwuLdfRqH7vNVnCrRwsWpPO6mMwmt00qJFmggZDrdlG2QMYmuYJHUmr9verzwXpKXWWkRn0jQ2gQGNeyQQLGDqp2MRSx1nkuEsEUm23qmsNZj/c84k4Q+fsjMVIjjFQgz3tCeULB0So1oR72FBSottXPrwupTs07GBJYulUkfJpf1WOEu9Itz/OH2scx2gy2FcSSRCbBUqbc4JthO5BFLayaXtBLWL94dLF3dkNoKUvdaMP506d5uIA411zd1VzUqWHiNbfYvvmbFx5WrSKmmHZCKHCz3X7btpse55j01B4cVyaJ4KtQaS9sNNAhQ9lBXoRTpfBcFC+yAxmYnphorU2kiG97ScS/84+m8a6rSFAYE1FVaY+mYf5pw+MMvV2rK09692baIvMYCF96e4/ah7Dm6rqO2fXUU6QBEKDlJdnp1VT7lyHDfK9TCH/vEAJQ9VC28fRnPlI52obT1ABwKGT8MlHHaOu9mpRV5xOJmmUqXOeoVSHoV20h0PNGNhDGYawcSY4vsPpjFu8YV4KDpoC0DaikFixCgdrTdYEuFEc4V+pVD2dMg1SlkHJ8o2KVlQI1F4tOXQaixiGRYIHoBk9m2IBV6sIasavFOg0DbBDptnGhGDOwBIy3eUW2QZrjd4F2bDWARP0DBTHwoYIGgNhrMhAg6pELO1e2YJqG9a7NtrlDbBEBmFt3/mcwxEiLWOMtsN/iINYzAImIBFmqbfkYByHztlm0qreBUGOuUjtf4lWdPimhQsPXKgyOW4kWlZS700547Uc2DNQSVFgY1Vt252tNbpzMVQz1E8uJJMLjG0tdxFSzbQj8sENVYNhPJX554B2c2CXa27eufewg18bKlMKZuaD2AiC734xidIEKJi2DHFXtu2F8+oZPrhtyr8w7OLFKX875ty5mMsocoAi4gYjZONamxJ7FUxmhPaKeeFXVACVJcl22uGxpe3s3xI4UjSU8KFmlLo5e+FqYYuShnmUiZ12UPoyJeerAGMVhgRL8KB1MDqZt5AwfIiCgKmUYmrcA4kjlQzsUa9rXq0kpO06IHa1AW7DiP+AEERCkcrNFLY5iuxEL1BVRssp8jdXGzYsQeTbuJ4wc0tELeOzuzkOlUtFY/CoQZyYAmAZzEPI2FmuawkDh34CraaIhkit07OHuUZTwaw2wpzMROayY9Hgtat8W0rt+7M/shI8YAh1ZFwfGMs6jSiIix/g2Td+HgaqWSwog9WtqzzaccmeF/pfdu8+rB8urB8urB8jfCqwfLqwfLqwfLq1cPltdBoP8CMpiEU5pFIl0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU5OjA5LTA1OjAwt/gqWAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVkdCLnN2Z9S97ygAAAAASUVORK5CYII="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/1/0/1.grid.json b/misc/openlayers/examples/utfgrid/geography-class/1/0/1.grid.json
new file mode 100644
index 0000000..612f84b
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/1/0/1.grid.json
@@ -0,0 +1 @@
+{"grid":[" ! !#$$%%%%%%%% "," & ####%%%%%%%%%%% "," ####%%%%%%%%%%%%% "," ' ###%%%%%%%%%%%%% "," & ###(%%%%%%%%%% "," ) ###(((%%%%%%%% ","* ' ' ##(((%%%%%%%% "," +((,,%%%%%% "," +((,,,%%%%% "," +---,,%%% "," + +---,,%% "," +----%%% "," +----.. "," +----. "," +----- "," +---- "," +--- "," +--- "," +-- "," +-- "," ++-- "," ++- "," ++ "," +- / "," + "," "," "," "," 0 "," 0 "," 00 "," 00 "," 00 "," 0 "," 000 "," 000000 "," 0 0000 000"," 000000 0000"," 00 00000 0 0000"," 0 000000 0 0 0000 000000"," 0 0 000000000000000 0 00000"," 000000000 00000000000000 000000"," 000000000000000000000000000000 00000000"," 00000000000000000000000000000 0000000000"," 00000000000000000000000000 00000000000"," 0000000000000000000000000000000 000000000000"," 0000000000000000000000000000 0 00 000000000000"," 0 0000000000000000000000000000 00 000 0000000000000"," 00 00000000000000000000000000 00 000 0000000000000"," 0 00000000000000000000000000 0 000 0 000000000"," 00000000000000000000000000 0 0000 00000000000"," 000000000000000000000000000 00 00 000000000000"," 0000000000000000000000000000000 00000000000000"," 000000000000000000000000000000000 000000000000000"," 00000000000000000000000000000000 00 000000000000000"," 000000000000000000000000000000000 0000000000000000000"," 000000000000000000000000000000000 00000000000000000000"," 0 0 00000000000000000000000000000000000 00000000000000000000"," 000 00000000000000000000000000000000000000000000000000000000"," 000000000000000000000000000000000000000000000000000000000000"," 0000000000000000000000000000000000000000000000000000000000"," 0000000000000000000000000000000000000000000000000000000000","0000 0000000000000000000000000000000000000000000000000000000000","000000 00000000000000000000000000000000000000000000000000000000"],"keys":["","65","173","49","34","119","182","33","239","72","42","181","10","227","193","13"],"data":{"10":{"admin":"Argentina","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADtUlEQVR42u2dT0gUURzHpyA61BIUgtEfNvBSRGHRuVOEdAksRPJsWOBBwouXoEzyEIFUUu6pKIpiIbMQkZAwiKVM+oebtpq0kZbChh0qtsCvh98y7vRcXSHmc/kwzHvzm5m3H37vzZs3rHeq9u6N1DCES0uPJoCIBRELhpVn3nd9GD+JWJCMBRELIhYNARELIhZELBoCIhZELIhYECIWRCyIWBAiFkQsiFgQLpFYWpZll2jZbT/9pcHH+peABS8QW0z8hcZxrx/cMi53VOz7dbkelxYIjuxynXNiZSuyX/+c/pmYKZ2u1bal9vtL7Z7g0nwxXc5bWHyXOIWdN7imS8zC2mShVxt8Pe6R3a/KX+q5/Dxhpm1i9x8MItY8zDxM9yWjYqqyf9W9Z5MTQ7H+pKg9Kp2eHp0aiNNiiPWPzPQl/bq0+7IESu99/vl+dLztSerKiXRL4u3tFaLdo5o6ikyGWDn8Uf3t16dGyWFlEsfijycvbkmVd/9u3jj6ondt6x5t5wg3e5QiKBqt6qGUujPJIVGkznDdg/1N0Tcbridq6sXBgx1TR2qSJfGtDWWi6qR6+jbFHimCchh6eYylbAennPSu+87Ouipp9PT7+fp9vX4OXL3WVLlLetkMpxEYYnmMqKxYVqn+wbOD2zOvyi5UlTSMHejoXNcnao9KB3a3l1ec01GSUtEYdXlhzlVWLHV/6vKUk4Z6Lm1bv2NmpLM60mWZWR1fEzmsUtXUUYpgx1s6C2KFdcA+K5ZGS8pViUMtzZsrJJCy1Ei2/VhkpWTStkpVU92iIihvMZAPqVh2RkpDb2mhrk2dnfLTx+OxukirKMm0rVLV1FGKYEdaOgtihTRjSQUrls1Yfr1sh+jPWIpGxgr1GMtOMdjJBf8Ya+LWzZeRo6L2qEO0YywrlnIVY6yQPhVKLHVbmgjV9IHyltNToW/SwU6Z8lQY6glSiWXnoub0mpUm3zyWnSzVgF1qSizmsRCr0b4Z1EBeEwd2hl0aab5KtM+A9k2i5vERi3eF8+llnhYlmbKRHUVZ8q4QsZxGXXYywqqTs7rB1GSeHbGcqO7MrseSOnYFhF2zRYshFitIl0usfGuZXRrUvU7x4rvUDD5j8Hnd72J57ncxXyEUo33ylXru3+G4fL3jXrMY8d2/LVno1bp85bI895vv2MJafvHtky8C3xVCPliFiAUR6//4Cw1+KsSCELEgYkHEgohFQ0DEgogFEYuGgIgFEQsiFoSIBRELIhaEiAURC4aRfwGxCDVrmSqS1AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTc6NDMtMDU6MDCieqKlAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BUkcuc3Zn7T9RMAAAAABJRU5ErkJggg=="},"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="},"33":{"admin":"Bolivia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABEEAIAAACovNt2AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZ0lEQVR42u3aoU7DUBSA4eNQEzNVCFRxYDENjwFiz4CChOxVJnghMtkgCE+Aw0BmECCalJLbreva9TOfWG633tM/65I11uv5PM/Jbg0joLAoLArLICgsCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCxSWON3Nlvld1uubDo25T3TP3er9fF7wBitbpgDM95Oby7O7sluja/n8iQ75yDclC9Z1uTnR/mQXf+/poVtz6GyJuVMIn1jbH3BJjwHYe0zr0mHJQIKi8KisAyCwqKwKCxSWBQWB2fHfw0Ji76xKCxSWBTWFH/wCosUFoU1kKc6f258R3L7O9CDnDGJmFo+V360YfUYWdRHOV7rD/zXX6+OdZR7rOSyy0z2bdw+ra4uF2S3RsTja/E+ZpfLovjL9PWHOuf+59PnHhsvDLmLRkBhUVgUlkFQWBQWhUUKi8KisEhhUVgUFiksCovCIoVFYVFYpLA4TL8BaXQsNkZuXjAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI5OjAwLTA1OjAwkPPgcAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQk9MLnN2ZxRPa/kAAAAASUVORK5CYII="},"34":{"admin":"Brazil","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABGEAIAAADldHp9AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHTklEQVR42u1dXWwUVRjdGiQYfCiNsmmJlfqzqAGxkbYKwZjGUIk/JBIWMfFBIrpW/GlJfNhYSCQEESNaEBRrmq2pCJXSSLDaNJUEUwMqldTaJaikGqsSElATrFHQh+PDl1zv+M3cO7Mzs9/LyWZm587MnTPnfvfc795JJC57Y+f8BYKCllGqQFCIJSjEEhRiSUUICrEEhViCQixBQSFWAXBRc3f2hplAqQ0hlhGWv96RnteyecfBiYr0qcr84xd/CcQW7JVaEmKxsHRP+3BtLr3/vZHUY59eeezC1Pzf42P1ickUL2RP9iRqsBf/xFFSe0Ks/8AbG9+5aG7Xa6Mfr08uhTJRGtHfdAv+iaNS5Z0D1a9KTQqx/sXM1b3rrnoy/8LIm1OOUupwkCoZSkBpomGJYtanvZ1HystavZFJtxcahpJxFiFWUcRPzQf7ls+chMdvTiNnDcNZcMai07DiMQsOnPysoXS/W32yRT6c/ZYf94zOWSjEirxZsGHyR7fNyPP1yZk0at/QLeJKcFUxNyzi19jd29jTft191Cxw++APL/zg6/Kpb3e016V+bq17paJmxfPZTefqpgGxBXvxT2/EpYaFECvU+kTNAj6N3q/uWlA18OgdTUOL7pr9UkPpQ79cPrtmdPU3iQ2zpjzT4oyX/ll9pKUVR6EElOb2GmJoWERdn6hZwNEnPEjoTf309L7l4xwCuUWUvHtbxyWzPuRrGO4iJqZr1M0C1czU9dG+qPqkftq1D8zL3H93mx9k0ukZzoiz8+M5aFiEDYsomgVUnzgBNZonNFjBUEpFnB1X4jbmi6TpGv5LRBedmgX8kByNET9m8htxJW6byEgaFmEOxp9N9uevGHNrZgKhDeGhlEovt+qlGhah1rCwXRA1C9yakDSWKmzDx28cObGXrh5QS6gxIRbLLPA2kIJjgwzPzRFXa2LeUsMiRKZrODOfvA2kwESICqVozxFXbuLphy5LrLCZT94GenX41Jl1uaaVDdlVE23XLMk0PpJLpjeumXjr7BOVLybeHQCuXrFt065WbKeIo2pPL/tr66HUloahzTfhkQdDL/heJkNPOsOiYKZrmM1MPu3Or/yh5NaSzuvz5/q6DqS+XzW4dMeJ44u7M0BswV4gtg+W/fTtcPPa4c/XtM3B7zMlv23/9Y+eh4f7D9+8M9czMDgDCFKCdpV7b/99Y9YPevHDeX7tFSxLLBizwFvmEx/Ht3+X6+oFgYAgB0VQipJGRyxKO2zB7619x45234kSoHxU50A+E2JhUMhWAo+KeAoBGRZhMAvMc5623LO7qrdJ16glM/NPrJ8EpaGIvXMPLXnw5SSaTtpookyqYaAXflM6grIgK85u0k/01n1xa1j4niXmX+aTSdXwzQXkF1C/Co/WVoMFUoJ8lHCUaiCWjmT8WA3/xB0514At2iHY92Vamx/TpPiDLW73qiWrPcFgQm8QFxoJqtHmEs0xfmMvX8lwRyYvmzfDAk/QWrBv1yzwL4rSEQvZUYW1DKBqaHwRe9GoTiUZ9E9XGu7IFo10L6euVq0ZFrbMTD/eJw6xkHxXWGJBvahGQjXRdFINo6oGtVOVFSG834GE8/8tmK4mxLLb3jvP49PRy4RYUBqTcJsTM4FkNDKjvU5Vw3BH5mnQ5uQLlFi6JGC/tUpXpnlTCHoF47CDxKAXdAskwxbspcTSvU5+1LPaFIY0eDd/bzjpe8EM41BjwlZpiMZoXxL0OjVy/LnFQ/69irrg3fIUj2hNtOLYDVSH8AjLltVWrH3aWz8RR6EEqm3mvU6UgEgLlIJnhlEEP2IpXY6XL/kR/g3gUIPUPMB3ftvUJBkE1LA9TR68qlLYYku9qPeG8YPz/WP7pn9lN3hXLWU8HR+zIaI4pKNWHHpSIBO6/c7NEN8+VbXKlt2qRnjOQzrmGPkhHc4gtF2pR7IvhwR0kKdQjhfVUZDMOafUJGCP7SA0J23G1viX83QuXdPGQaigsxbyEdcAintLm3EOGEKxMEl4Ev3cxli62Ticpo3qB91iqwF1tlLpoBPQeXoFPzalycpFl+jndrTRrW7xU5NpHoSJcUCpSekCZcJeUApbKEElNTnQlRe8TfYCBjmZgtJIpRoUEXtVtaOTKbw5fzKZIqDpX2qzGMz0L11vFIqodhRoqO6WUtQskOlflies8jGYCas6TVIHp3UTVjmRpUxYDZFhYXeKvVu3nQbp3qbYR3hd03isIMpZ4Tj4RUFoeO48MVXtrER+FdO4rsnu3KzQZYz8yDVFyRx9UtddjslSbPFY5gsJtW7nUsvCa0IsC6s/2FoqEmhrqciQmgVCLP7itnaTp/kTRmRx29gi37AwSVF0PlaW45YPCFjLGUcwjjMW3dfC5JMnfsx+kU+eFPVHmqhhQU1Xb9mb1MyUT1/J17+MFn+Tz8oJsYymtanOfsy/KyHECtKwkE/3CrHkY+NCLEEhlqCgEEtQiCUoxBIUFGIJCrEEhViCgv+L/wADAE2QfXfbBwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjk6MTQtMDU6MDCoFsT9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CUkEuc3ZnU5e+DQAAABh0RVh0c3ZnOnRpdGxlAEZsYWcgb2YgQnJhemlsnLDlWgAAAABJRU5ErkJggg=="},"42":{"admin":"Chile","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACcElEQVR42u3aQShkcRzA8XddzR7YJoVcOIjLpJAjtYcNSRIXDvYgZg+THEiNcti0tRFxkJs4bIhEidGSGg6aLcZBkWLmgEREI+04/C6vXm883j/NPN/Ltzm8mf8079P//5/3nqa5y8uml1K9U56VnpPq+EM8Fn+iyVANWBRYwAIWsIAFLAosYAELWMACFk0xWPkVDemLha6xysif/8AClrIOPE76D7zCC1jAUjZXHZ1F/Hfullh/ILgALGApqGCSYeTEAwtYik92uP14/eZzTqj2y3wPsIBlWtmMm1W/COoHkznM7F3u0m9ps5vA+qCwBIGvasi7Gw5G932XbcbK/GQcTKiZvWv068zq4fDbZjVgOWrG6vaM/f43ftl0vRbLs/MlhIWdf47AcuAeq9L3YzuwL7OO9YEj2kXa/YjMfPavcgHLsZt32SHNhf4+nc4lHlIWSuHI5h1Ylir7pMRD7uUeZV7X2NmqA+vDzVjGbbvZDqzG1fVrow5YwHqhAkX/0cs7wYJopyx5cmNHj0zmNmABy9IiKHSEkX5LLq8bsnrrt5rlooPaBRFYDoQlOOTUWlng5LKCHK9qCw8sx8J67YVNmcNU3eQBFg/6cRMaWMCiwAIWsIAFLGBRYHE6gQUsYAGL2oT12ueiEh+vf+zYDhrjrSHjJ6uFZeXRRVXHvOf3ec+xtI6r4pLW76newM/Oxr6y89vRicEMmgzVwodF2bmfKFVbYFFgUWBRYPFDUGBRYFFg8UNQYFFgUWBRCiwKLAosSoFFgUWBRSmwKLAosCgFFgUWBRalwKLAosCiFFgUWBRYlAKLAosCi1Jg0WTqM17OTk6rT8x7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMToxNC0wNTowMHpPW8kAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NITC5zdmevPVD1AAAAAElFTkSuQmCC"},"49":{"admin":"Colombia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3ZsUrDUBSA4Tvo5tCAZHKUOLto+wBdOnXo4lLwCXyD4KA4S2kfK9CWvk1LOujQIgGRY2zkW74h5N7ce/i3pO12ucxzMtZkBBQWhUVhGQSFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWOQPw6rr9e5y3EUPr/FXX2/nnW7d6zOssnw9n2zIWFNKdxfzh2j7g9nid3Y+5dN+7POd3bo4n6YzNz0/WkZGaQQUFoVFYRkEhUVhUViksCgsCosUFoVFYf0rv/6ZZ4uePd9P32/JWNPs7Wo0fCFjTaub3tP1IxlrqqosKwoyVmFRWBQWhWUQFBaFRWEZBIVFYVFYpLAoLAqLFBZP2z3VWG8eJgK6DAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6NTAtMDU6MDD2X1rqAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0wuc3ZnsjhgTQAAAABJRU5ErkJggg=="},"65":{"admin":"Ecuador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2bfWhVZRzHTyt0f0iu5gs5S3S2dPiSrAnpTCnIVWhtLigs51wrt1kWFbGQXJl/pA3Wi2Nh5ksLRAqckbXUP0Zjki+9KZUta1AxlZSCojGQgvu5f3wvj+dy1zZ3zzm/f74cnvuc3zn3eT73+/ye3znX+7fr9Ckv29R0cNWzITA1sEyHWy92/+Z56QRWyjdkao5lampgmRpYpgaWaVSScQPLthrmWKYGlqk5n4EV8GkOBY7mWKYGlqmBZWpg2UCYGlhDoOeWf/3RtCtdvdD9Td/VfclT6YSetiscNrDSYKCBoLOsdV3eqsbW2rEPF635+dGtVYs4bjz+xsi13UfnHDjQUg80Cfcsxz9NPr7xkRH059yGKY0Ti1vQzhWfFJbmdfV05syYFE3IIvFVz1QeOTSyrTWr4Zf8b6uXLPts+j9FFXk5k2fO33jjuzfsQIt75vw1bS7toKZwAGJcY+3AxFkahwi0o1yRc7kTAyvwVauvej9cPerHLb1rn87+gglGdeJBQVWBUA/zg8kFy8WrNGP+tTm7gNt36TSwgqVMJG6h6mIHHEDw1oyq+6f1vF+/40x5JWA1ZG7LefsZP6SIQLR9hxuaxiwiPqrXNccKe92Za8X05KaO2dfsBQvA0iWSxVHb8SRaOCtq6JhjxZVsCb3E9AvQfAouuqhxjDPhSb5xJJpe18AKLVjxXVsslXYh0BYFSxdHkErFC+lJlmZghfbBM5kW6bMuWyyCqOZewKRJN+pCSWSNgP7Qsacma5MfiOFO4b2wVln8FiB8CHTey9hdMuWKinlv9s18hWMUFGjH4VaOaFxe2Yz3cAxeWkTVCJqwg2Mq92aOFZh6Ons6Kk84CtAw/dWvf/z74gcULPoAUP6xzbO3t95Vs3fWkVHuccXu5lXvjCA+ACmmKHhxRSJzJ5wVL8AaWMFa/pi2eDEzBgrOoV7lOhb+BDRghL74QtOYkjL6KGT05Cp44aj9G6YvGI1quUGLF1TtL1HZD9Ezj5AXSON4xZYtMh6mWX1lXHZVe1Xd40cLuson6WJH1YoWziKOLouqRFCwAFHzLSKE26siARaKV7lgMf2zSqv3vdoDHMDU9NiJrF//QHEmt0WVsx6cVzdu80EwJTJXUbDcfMvACrAqWLSoYxXet+DISxsUqdMXz353foWriperRHDB4oqhAsst00QTLNJn3cHNXVxTVtcLUjhNKmAlR03BGrulbGHTsZKzzx164notuiaUKkIOlnYKn8a+pG7+aWHKSy4UHnyy7YPpBX/fuRW8eHTDMYk5uCRXenIW0YgcByt2J1ohC98Iuy0e+x3VZ+/YvKw002330+Hqn3o7ez39PeFYTP/n9xbffXu5pu1oHD5H9VM9JsKXHTcfLng5ASwph3InqX/3/o5b+vTxMj5dmrvrZLgVjBQsppx9HGAxHFS24vWtmA9pS3J1wQI7XQrZKERhzCMElj5CUccCBbBQUMCCqhVKix9Y1Kj8HCs6YGVOXTJ+e7MX1i+mLUynPsVTsE6dyN1500KMXUFRpLTm7gcZrwESjW1BEMFyR88cq9+ONb+2aML6q86fm5gzNZcFUV3quo21q7eVogDhtqMARISgg5U6XqOPltXvbDewEsACIIavbXzuyltv0wVRYQIOdnycxbFCxlkgRTQi44K6K6QMYTnWyXCYdgJYUosnAim8+paLjp+CmnoV0YjMIx0Fy5L3UCnTyfsF+iIy7bgI9ac/9+csnTIBUKhp0cdVPsXnOOu19vx199wCUprV6RXB0cBKC08aeDpJBPcFPZYqrbyDF94DNLyVoC/60cKn9AQpAOVaCZUzeVsreV4yvOl25MAarP6a8ei7D2RIVK1I51vWr/l+TBEAcQxSHLvKUgtY8as4jzhoDwoWA/8BeOlzQ0Pxa9bIOJO7IFJ/QhUm3q/yg0k/1dcJ3fdCcUoWx/+HUXrC12+whm6Ch+uXx6TqMQsT6bz7p3v90wTHKP/PUbxoYeEDU42Jz7lX57i/4+z2T2fgvHSz0MFVlid3k6/TjJNpz/huLuY3QEMWxY5P33ZngSMa5xLNBcjV4LpRRHeFqUyVVqp0mqlaue9BUJ0CLE3McSkFd+DOFKxcKtLlhuSLow4u7fgQSg1dwQI1PqWO5U6Pe5WojXPkwNKiQPIChy6OWlbQT7W/n/a32h6OxdHj95TKAKWDXs67jS+O8qAa38LDwjQmA4ngd6635/n8p4ofGlylzDgUkS+/ApOChbp98LNwfOuBq8czMh5KDK4OXeRwK+MW3NHjzj39Gqamg6UGlqmBZWpgmRpYNhCmBpapgWVqYNlAmBpYpgaWqYEVFXUfm5gaWKYGlqmBZQNhamCZBkP/A0k6122K1m2UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToyMS0wNTowMKBKMMIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VDVS5zdmfL2mD/AAAAAElFTkSuQmCC"},"72":{"admin":"Fiji","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHR0lEQVR42u2cf2iVVRjHrxCtIY1lEVFQMdxsFjErbNAPUxKDNJjZMKKZ4rZYYv5CQ+0HQtgmRpYNzJylORRNW+FoTilps1Bcy9I5x5Zm7cpKDLU/WlrB/dw/nnE6t/O+7z3vfTfPP18u7z3vOec953Of5znPOe+Ndb/8eFnFB+dXfz7yq+GXFv8159Idqq67e9fV33xXeHlyx4qK2JJRL1aeCq7Fm6eX1cyl/jO51VnvPN82MWfDyMlJPXbdfQX5fOZbShb1TX90VTy9feDpzi7bc3tr/smbSuvnFX7/2g1Lx847VnrrxglHuHKu5uDN335dOWJ/d/zJJ+obd57qKynZvfdk3KlOYwwiyiDqIPulLP7nb7nVr6y/tyknOGRM6pmu85V/LB0AFkhpwOKuIO3Sc56ic1bjWy2/93YtPLFy/dE3R3eNn6gbB/pZvmJfzel9DiwjsOTg8hsNBzKtxRJISbCYWn9g6WDy+rwOLA9g4VyCD7pXyLyC5dVipYZJujmT52KUHnt3y5dHIgHW1LxPCjoPRhosG79sE8i8ukITi5X6R2ICE62o/c9eUNyy+D3ActbIM1hhQpauGCucH4MDK21gmVsCf5P3P64wJVhhWlYHlkWwbMQuDTfurW5f5BWsLeW76w7NCH/B4cDyDBZT5U9PLO/J6/ybaSPTQzbobPP2/MZ4coLv6c2OX9RlyLR5LE3wPqCtRCsoV1K3Qm/JWqnPAui6J+Wu6IfMEQIrOfEZ1SRYynpwAFi+asZKBe+hTDdEbQqjiXuMxGD7q/dnT+s62jNm/l0NXEmzpqwZR/YfYCWuHLpw/cGiZiu9Mu4n4zOzpH5K24/pnQCZvAAR9YpaUl5Jnf5Q6wwJrAExTTiqxlL+avBaj792E3d9MSyv+IHRgJXeSZJwmKsJWDKdm7qeqIIVHJTM1u8RLBvT8Nydbxdt3/TSLSVvzD0cjtKiPTca00U2RtbCRL3W469dHYK6ms1hDQWsijGVy1fOoq0jJddmj/kBlTu5slfde56Zf1uvVJy42n+uU4agglZo0Z7diplHQh7K+IpsdIj4bNH8LoOS9mIsFSz6A0wSHUZDAkf5ZAwqAIq3frR3eL8ETq1BgmXDOVpfFZqsy8hI6fYK+Taz61bbq0KmWS6hJCKotF5qmQvFrTnZY1XlW8qDFCDSIq7QRoAfk/kbXS5Hd13msXSq5pzUBKZ5glSriemnZtmKzG/RW/MsnXxq23ksprljx7ambb+CF9CoiKgYSfukU9WGWXeFQTLvSWukTDOTqh6aw/aQN/eXeecuk3blToBsV55WiE7mnWmWdkgHh4qIvEt+lpGZWjMtZnhLJ10wqXuF/jahbfQnCmAl3Zwm8pM/FRngoyo6aqYwEmDJyWP6g0yeaiH8ncdS6wkHMttgsfjHcdMfqZuXNhzfMEmGExwol2BxRdbQkvXZQ0vy5RU+oy/s2rR6T0EGzmPZtgReTzeYnMey1397YBHlAFb5uLX9s/ukPvzPqouP9D+9bOGapzaiH685tHXnfh1YxJqU4V6U2n5uO978fi4jaR0spipMt+IPrCAnSIM/14N1s7PW9tsGC3RAASs1qnXGpxMuS8i4roLV82HpgqqZLFAoz70o9wKcBMti8B7czVk58y4yLsFfpvAKmRr4H56ze/wBK9u9TC3TjEUBHfrJZ6k4MsAakDIVFouSqsUKFawwYfJ3gpTWg7xMEdyS2d6EZpp5xmQSJ4GIjIpkD7WuUJw8Q4m0qDM52gIsi1s6dLq9o6rp9WEMH9sXXAEmsjgEzkQbwRXnwkP+dE1t7bpxtKuqjLG4K7194OmYBjn0KCfMtj475VzVVHtg4QpleM6YoIyA/JaZGrAqTKz+ZBmUnyW2UF6nRYsxFoM17XRdzYFCPqNkmVFiCxtK/fxuZOuyP3wrS9roiTwRwIupUmXrNsBi8a/bJZSqllHLpy4jt3QsWiyTAxU6T+x1j0m3dRBkr8rkXpOawzxSomrZVYtG1E4iAEgNh6o6+HT10AotWrRY6TpFNNiP0mb2KXBMuH6Jl6oyQaqzWBIgXT3WXaE7nR0FoHH9rObYMZQ7nlJ1MRZXku8cJFTullKnVFp0YA1xsAgSsCLgJdNAcjHBajH1qhAlYJcpDNINbKsTWVqMsdzURkexIuTMdK+vyTyWDixgkicgSDoApe2cuwMrcooVwa6oYAEceKlgkXVTkZKK3Qrn7UgHVkTtljzrJp2gTJByZE9aLAmWPLkFUjLbrmp63aIDK3LKBAOBzJjLs7ipLZZECicIrCbv/Diwrgi3KPEy2dKRu4q4VJ2Vcq5wSNkhf3k17A2rOV2MpYJlYqUcWE6TNoyUBHuXxFgoV/g2Cn8Q58AalJYPa8S2DLt+0j5F4r8b3FQN3q2nKG+sObAGJVjmBwUyDJb75yen6V2COIvl1LlCpw4sp0PmH9sHDVgunnMWy6lTB5ZTB5bTKxcsFwk5dRbLaeT0X5mhki0/uFJaAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzoxMy0wNTowML2v9jUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZKSS5zdmd4LVEpAAAAAElFTkSuQmCC"},"119":{"admin":"Kiribati","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHyklEQVR42u1afWhXVRg+Q6Hwj5ZLDaZLLbFG4bakzEiDDAxKCyyikVZOCArpS1D6GjGhsb5bxDQVqsFc6DbEFJOVyBzaxj7UlmvT2gSXzpnlHNjCBfe5fzw/zs713K9z7+/jn5cf93fv+5z3fZ/znve854i2w5Pnzc0LTL52Q/mc3bYMVnNGsodjjy5Symz98UQ1zuSdctbIOydO/7lgKApimXeohmbbHZxNVTKEEbbcUlA0c0vLnvsX5Q1Ctr51+5XZbxglmX8s1qChTaT2woGgtre/OnXakrbKJyfl1kC2fvtI7vS/7JBb0g62hnPt9y0N0AzZtaB8wpTnIDvf/azwxnqWeAdf6c/7GFHNZe4X6VCX2KSxKMWB//W9qidyinpa6/uyf8BvSLxjE5G1WU/w/pm79pXkTDr/RdO0nFWX17dsmbICv/Evk2mcLGWmEo102RVpVPCSoxFmO+SUe0AskAaScxKTCQTiXJhAIBnX4OIbBzqKdN5hgQqcyUCs/hPbduSOgjqcn5CZ8NvOZwvvLp51HySWyIRlLuytQ4y3ICKdd0YglpyT/rvaeX7qCC9weILfkHiTyWdXUTq1WhpIkWQdFD9j0PgWVEPGUhGI6yfeEEDGojA371VJg8i0B1X6eaEcp4pKpSawb2IlNHRsYpl3kIwY5zI2c5aQKd6TOEMkO3GTfik0Uz1YX6ENgYWPS3LuoWdOD6PIWHEwWGMMTBq7v2U1DlDC2/SyJCottCTsDj7RDnUGvs1kZZGUpAnqOMKSfNgCiZ4Wt0nxBLtCtCHwhPeG2C0GlsmSfHFM0xqLswt3sJhA8nENP2HaseTzxxSpn5KSWMadzhkFlEKmAV2YRkNvNk3PnixTip9w+5SpKWfBVM1Mqkkr0i1L8R0EhF8mipyZ5J673Ivnf1Gf4fc1iBWHazCZ4j2oOw4gDZODs5FMJpVULZrQbJf/tCHILIWpKK2jYi7MVXTBIsjkYykvms60w1IbSt6KcbMjxYlltwOoWYAwq7KUikzOEl+pshff90o4qE6uG6SZjJXQ5LRCyPs+VaZBlnJLKZleXPKzTl4W02G3KNLh2MS+EEy5irMUF+P+icU6QSy+MBijfn3Ipx3C6CFrRMe6TCzOW3K57V/yUsg5jPv1oS9YUd1WZWId7Zzz4YKzynuP+kPUuIALFCCyDM+5WHqObSz+JL8bi9Hlfe0P5PdyxvK/CLLkLGWTzEKExBhseoUWbPN+xm/GEv/2Dcw4u2xkSUfD8d4LWds3NzzWv3/d6tKsrmfuqVpaOg7hZHpJz+2OkaUB2qAZKEC8cuep433N18DVMZ5wYRLjXnq255ett179aGTn0SNjdaMFA19D4gn+hUTgfS2FlobRGb2bnm7UwVXaq31H3tnP8HAAfnYZXyCKMTE2a2y1LBH+f6p/urmp48wrZc0f1/esX16xaj4PiAOJf/EmvoIGlX63uDwb2LCgcOXAgyKcb7QIpIF1/t6/80Ya9O016Wc5vpBucYXbAfEMYIa6DqQnR4yDqx1OtySTpZyBIrDXuJ9tArm0V1Q+X1fU+kdd9cHG7l3H9p6qHczn+eRfQlt33enbhoZrHm+c3/Xgl6UN5W3FkGZwgWISF5qBAg9DwgNmcL+6tLuu409IM/Z+f+jwzJM58LAQWYvXvj8R8qaNyy5+unfR4NqT1TVvL9/y20Hx44a27/rmDjQPHRiu0ofBV9AAbdB83csP76g4x4h4wrgbXt904cDCsHFle/3jQoMKF7+d7dUPOUYo4+aWrJhdOSxb6myvPq6znxMQVYNgdxQtX/Pitv1r3qk4u6eNZx4knuDfeRNeOLe1UBVIfRktLrSFh6uaYPq48Eys/TwO17QH5M2hsjSJa95e56zploLe0M3jiiMlXafPPIo1uOylbwoPrVy6eF1/bbZzUtWR0ABt0AwUIPrHVRnvB9ePQ8O21y0uFiy3uPoecLZXuSvkchsf31G78sTm7Tz/WCJJFj9V9tCu61EwQoPbYhBfQQO0AVeV7ZDGMUL/uCg8GVdlL/5le1HABmWvjIsnjMtbgbD97Da+LtoNKBh5BmAXgCf6Ba9bybhAZNxg9zjxxOVMEJWf3eKKOJAjtXHjMwlN4grndOctzeIrnTTL6Z2Xs6hwkd7dZiPG1SkbwlhGgYvCQC7Y8QTx9e9nVdmQsMUJoyD1thOR9zLxKYRlXHRxkmWjEwGun423n014BjdsXG/ogSG6bb7pg7ltvkXb9JOzrKqNGTd7IWVcZJqo/Cz02/aqTo98TIGv3O6e8Ca+8nYcxMdQZnCxNKQDLqKvH1/h7YAThRvPDG9lrzfcaA+STeLGx1638RVQ9/sHA59fbAyvNwPN2JQCkXHDuwQCXKBghpnEjdZeGddkfO2zQt4dINGhG+FtKPgKGuSzd065nGDxZrC4vIvRwfXW6WFcuXgwietsrxxf/7iq+Ar9c34UZZj3mAGcCfCv/hm48+aAcdFxkXExM8LDle3FciDby12cMHCjsleFq2Ov8DYgcDOBoUY2wIwb1GUV5684o5uxV+XnoPaPbu31iBvslYnwZDxHFZW93lohYY8w4Um0AdbR5rZzFl7WDCoPxXPaBDyGYN0XnuPMuN7ktPFTG4URC/3oa40hKtcHm7r9OzHayWP+fmnYE/J/4vCcl8AXaHMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjExOjQzLTA1OjAwNMxImgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS0lSLnN2Z4he5NQAAAAASUVORK5CYII="},"173":{"admin":"Peru","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNElEQVR42u3csQ2DMBBAUeQNwBV4XbYBsQ0NY7CAKagREpWNXwp6lJf7J4ekO46hn8bar+e+rNucK3/dd/GPd6Sr/jZyDCmABRZYb7ByDCmABRZYv4RlxyrsQw4WWCaWFIIFFlhgSSFYYIFleZdCsMACSwrBAgsssKQQLLDAsrxLIVhggSWFYIEFFlhSCBZYYFnepRAssMCSwufnzcECy44FlhSCBRZYlncpBKtNWJ8mqBRKoYkFFlhg2bHAAgssKQQLLLAcN0ghWGCBJYVSCBZYYDluAAsssCzvUggWWGBJoRSCBRZYjhvAAgssy7sUggUWWFIohWCBBZbjBrDAqhJWo78r9JWOiQUWWFIohWCBBRZYUggWWJZ3KQQLLLCkUArBAgsssMAq/u/qnWOZWJb3dmBd0YiNyfSUGOwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMxOjMzLTA1OjAwZmX4QgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUEVSLnN2Z5pF7JsAAAAASUVORK5CYII="},"181":{"admin":"Paraguay","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwUlEQVR42u2aS0hUURzG70K0WvSSiAoj0LSHopZipUEQPawUC5xShECKivFRupgKlRB1QLNMUlHLAs1HimWTNmo5Eur4IMXJgdBZjM8RIUJKyBZOMN9djIyi4t3db/NbnHvOgTnz4/v/70PoH9i23dOLJKWlwCMgKRZJsUiKxYMgKRZJsUiKRZIUi6RYJMUiSYpFUiySYpEkxSIpFkmx5M64LQEeyYvIM6FYq5JGKrEo3wpiOR4lSa6bwpDWp+dUJGlPY/nhRyenwOWuOs5cbr4czspxXPi3czx00kCS0lKwjlrHrWMkKS0p1hK0uFgOWTzbwnXmds0Xoy5Qp3Kk9kSnSh+LmTwxirUCu+L1Bn0jlDJV6jy+JphLqiKKFTNxRQvZ8ZNm9fGc6OnWSv+K5pHMVn3jJ8zEKp4exVqCTbe04W8fGLd2OetV0MgUdH74aLEx1zvdvduUeu7mfhUojtiuYiZWIcl4khRrUUpBjokP94KThJHoM21ug+N7lEmKfOTTnHZgtP8jiBFcxUysGgj57tPdghLJUxXYS7VH6+bbnZA9olLNWVGqUmTYnV+ZAWoXSINV2h99VRoFRjATq7ADcou9l8CsQlaZE6LqAq8ih8qUtcn5YQFlyuuBjy/9TelPsSqL8nfV/87Oq3Pq8gfjNpX3JTRgJlZhB/Re7LpkLRbSBe05UmcosVB42gSlYt48cU2cv3wwza9lY/Lss293Z0GMZLjV1rzzjT3wYubGNfsdUCjZbwlyLoL4++2L4KCnpqTGALGQUiiF02c7gnY4gxhBbl3JyJ2IdMUqtPa4c2RBlHViodEW7wFtWjiKFVpw/4KmqUFX7XQ6FMRIauXrws8PIRbKH3aw77SYWDL98WjbUbzwEGHK/eVwRS+KYFhB6j5FBJIJMqEIQikUQbEUdpRuKPuJHVgKKZbYvONBKF6mmp5f/BNTrO/pNrXmILeQScg2ECO4ijtErMLLV+zG5p2PG8ROa8qS5Z03j2+JxjYnHUm7jbvFrKD36Wq1PXE/CHUwE6vQXSEF+biBD0iXeECKL4qQQyiOYheFlzy2woerUAqrsAOzimKJxGceyC3IgfRCaYM6Br/dvb5VjsRMvtKhWKtKL5QzpBQSCwKh2ImJZUsvvoSmWGvuvey/dHD8hAZX2UtRrHVxNMSyMJfOc1iDWDgykpSWwt7OiGCNF0lKS0EQjrlXvyJJqckjICkWSbFIisWDICkWSbFIikWSFIukWCTFIkmKRVIskmKRJMUiKRZJsUiSYpEUi5QX/wOLYG3efhMEhQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzQ6NDItMDU6MDAs/jGrAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QUlkuc3Zn80bvqQAAAABJRU5ErkJggg=="},"182":{"admin":"French Polynesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG+ElEQVR42u2df2hVZRjHz1/lTLNFZRZllE2aSK2gFgUVBRsag4StFCRr+yNHba0/RFxkGDUInTMVjbUsRZGtoTl1m2iWsDmz6VjLsInrFxVJymCjP4yd2vm8lz137868270377nn+efL4Zz3vOfd+374Ps95zrlnzqmvMzOz7lVVTaw6OgWqCpaqgqWqYOlEqCpYqgqWqoKlqqpgqSpYqgqWqqqCpapgqSpYqa9dRTNmzanVZUv9+XGCNWW22kdTf/zJgMOemc7O6Tl3b51cn/G3d7j8+CoHmjradlfG4J1v2FOZ+vr/jFnOz0THFv9f55z7qbCspFJVNbHquOfd9e5mWwcbLn375xd+R2M/a/x+JnqVxPaWqHOTNza/sxLbWzLmxIlnKsOgTC76R+X3z/V8Gj+CYVAFy+jQMxeGuh90d/V0bSly236ubbkVjL5qKrr5parGo9lLctcdqni0OW92y9/VQxsvctS0X9Jbuvs2BU7Bilbg6Nj1cNbLrltbMG35wNMHfl/+SeuBhfuKppICH8mbeXHePaffaezf82IUiOIs123MfCQDKHVWnXCGNjzGKGC5dc035qDfna9v+nDq4flz38ut4h6HbeNSEkeDlKdABlidx7aXTjH9K1ih0L6OmpU3RTAa9hjjPUP7t+Uvdd3tq2/PuvzkNxXV90uwbMf6Z1nz5uebJFichUb2D/dmEFSw0jyLigpennruEgFiGLhLn51es+8pCRZFP3KsM03H20/mRBzLC38o/mT37yEbtgwsHGCx5AQptvESqR4o+JAES1aTZb518rqa1TW9tAfKiIeNhFTjglwXpwxNBuaEyaVGwhzodM9Yt7hsdt+iTScKG92Wz6c9tgC8/MBCTV1767JXl84zPgc03lXojZ6Nq9Ez7uVth8G90t2xSKKtFBu/oYhgIMC3vPYk6Rz1A6t9YUXbilnGgQDXU1mesPMwmY0pWMEvJUjP8AACpoPHbqiec8KAJeDAsciorgCWABeMKFIAlvFCec8YmoDopDlSLKQoK7D8NliR5H04DY8lFAKW8SRyKe8qhEjAoh4WVXpgVKK9ghU0lS7luYWBwFtOsCANN97jLbbMwABLPrEfw7EErGBEKARZ6mGRAgf3j6PvRhWsgCbs3KONvvkHC+7yTCg0II5UtvbOXfX22ikERBAEF84yYAlogJJQSPtIKLTGICr16ZrIpy9Y3pJHhSpvIdkzBlgg4qnpQVTqSecpkHIu6T/+Z8Kot41jRYPleZUHrhmPrJypYwXycbJUUc0CDhwIRECH4qdUud+vDcDJh9ZRoVBmV0IBS0KsYAUhbTfhRoah4W2ZvONYgMU7CzsLS54t6waUtR/kFxe8BTpss58QabeUYEU7FmVYUTiVY0vTO0QnbevsY+Q0o3Ms6ViAhQKTvQ1G+JB99Epg2eMZ3q+OFbRQKJLxKPXyG5ljsS1x8VPA8tvD9hjJu99I0voZYtqWG+SCybdAW3M7NvzyF+FMhsJzt/x6R/8AiKysuK/+gS2ozJ9QgiBHZbjkKGDhhZzLUTkGqZq8BwypTeUNJT3HwYjt7OIX8uvfvHbF410fvQIcVKRAAQjYDy7otv0b6hoeooc9O77s7TtlgwWOeB5eRYH0ieKCqprBmT8sWLzjt6LsVa2Hr2c8u3e25p29TJ8KVsCUZWM5bQUXWQIFCKpWUtdsrKvqPJpXXp59cH7pNe8OHKkEI/mysnz4I2Glvd8YgEzBCpgS2lhCsMAn2C+9h4AofxkHIoTL11vW17Ufwu1yzxT/uPd944XixWV5LkEQcLsyzr52YREq+wFWfVYY4IAIWAABXih7WGxZYZc/l8BvQEGqxIvEn7O4H6Q3eS15RXrgugpW4NX2DBYb35JtgEmCiM9J75HBEXCBDJRxRD+f0zdI0zDfmv7fmzAf90tl4aWrsZ8En22ZegOTbIPSRvqTvIpsA5oKVlqFRZJleV8m/Ql02E8b/AzvkdDIO0Tac670J86iDUqf4XnzPXS/0mGZbX9C2Q9wYITTyDbS52wPkz4HXuH8IWvowCIHwnVQcia2QQGw2JZH2UbtNlLZj5/p7wpDrYQqO6j5JeYclYEvPPmTgjXhECnDn0zAZbHALyymd8FTwUpAmo8z2Y9fUJng01LnLWFgJeq7TRP9nlb8I0leKh1Lz8n+61Lo+1j67TnVpHzRz+/blYn6Bmb8X7mMpR+/Pv3axDKGyX2NM/bZS4V5nugYYmlPG8f+eVOyvwo8vtoPgxN73WR/PzjV5mei6xt7z+P37yTqD75a/aT+h7iv7vxMDqz4599JtSXRfxEQrPnxG4+jE6eq//JEVcFSVbB0IlQVLFUFS1XBUlVVsFQVLFUFS1VVwVJVsFRDqf8CyT42OT5oEvIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjM1OjAwLTA1OjAw0OlFRgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFlGLnN2Z3sxHzkAAAAASUVORK5CYII="},"193":{"admin":"South Georgia and South Sandwich Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAIG0lEQVR42u2cW2wVRRjH25SbFSJEC2loSEQgRGgFJSHwYLzEkIYqQjSpXJQi0FLC9cXLi5gocjOGm2mlYNLW2Fp4KgmaAIaoUYNQsLUiDwZthcRYLTHYtNRg4u88/JvJnuw5e+bsntN5+WczO2d2dud3vu+bb2Y35+qhxVfK9vxZ2Hzxk6mD+d2Tu0r/zb394eBHqrX9x97/4tbD9z8y7e1dOZNzCtc/H1xpjfZvTNjRsqPqu/O5o3JHmcpZatroA3d3c+LJEyc/4Glw3UtHx50d9xQlncdP1H/aO+lCQdO2Sam6epYrj49HyUPkEZt4/XqtK6/n4K6D75w5tWX248UvvFaViWDRAncBLl37qvduaP/+atHuouUKkz4H7j34XQ8jNR9uopAlN9j+wWo/XN27fWEQsLxg8v+navvs4ls/1jqLlYAGf+jJWTI/YNGH5CxW/Pvyskyp/fME19G/jGxZsXbinfzK566Y5aUnF/SX/myejYSGNRg2XCFYZzpMqqCzrGhJ97LOefOL6xfPmnBiVveKnZRoeaTBSq37iD9IqbJYqYUpalEUYC3fWv7XmgOVbWVTiuv1GLAywGKl060EsVjZ4eb8u0LwAibViCLlH6zUQtb93vUxN6d/3N98+3ylH7Bon5r8KvssU3yd9uK9OcUta3sfKCpcdGTzrAt3r6ME7CLac4YqOW2v6yy58TXD9vdjP5RdnkI+TNUrN6bq32KhoKNX8YJJld6StUr0TvlVuLPCGWfve2XRIcCac6ykdO4tSoCM4wihxsCHqyQU4luscHsYhXQDjk/tFlq19eVLYxsVuEiARe4qXNUJgVcIT82OnpmnZ54Ocq0gLaQHrPhWB7CAyYQsQmB5DadT1daeu46NfdR2sAxSz1QsWbr3Dy+8cHwo/cEJAhnlXpGxRpbWnaaDxj9YNgaDwVYlqttXf+Bc6xbTRoKOV4xlggWmuHKO0dXfbtpfV2sRLwdNuBaLYWbGigIBsR3Db7pCLwUvagJl+51zFaemMnGhZcCNbxdTFmPFjzyCxCV+fus/xrIRRXnVN8vtxVgMM3NPhp+kDHZLg/enj89/86EGUqMkS8lyqYILUOpcmKukaQoShVkh8774i9Bh9Y0BJpmSniEhHlLnlfds3vq2QXVkZOPAhbOa7KWflG/7bXdRw+ecTWsyIrmMjpnH8lI/OadE81imAoF5LYDQPJbXvUQnjzX+jXv66v4BII4VLKwUWn1m85ntI4nMKNFf5RTkFFz/RiHLmMw7MYEXTOTB1ZFhmcibB8m8m9dVsHSznnnd6C/jKBYAgYIIAOEKdYlay2OtSQscp2k+mNxaoX+YUHNQ+YcFWYTW/uCq4vdHW/PqT3RWBofYGIAQRHRZWvc4UBK7F+NXwEr7uhXHYgIlnTDZ2DaT2n6GZcmACdemDg4FC4JxYiZ1iIDFr3TGB1j8VmeOJFcb+g78lFfz0ug9K/ObrGy88dqCYnuQbO/H8tP/+JYsnZCBAnCUFuycW7EGy8Qd6Wxx/0BNX+M4dYKqYAdeaqVAqrzjyS9LmjXFSj6MPV4Wd5AGdyuJ7vBMNMby3346/yTBVW0V0OhgY8/MuMpUPauxFE8DKEFqxJwRT4z5CrVyX+mEKayXKZKzZGbgb2NWaDpBdYUaFeGwFB2ONbOFBUK1t7hRWlakLIKV6MNN1T84Cm/p+Ldk9pZ0TLBwZxxzRfqMiyRCQgs3rlr14FGUaAmlJu2AlKaHwE7B8lphTMFbOjxEHh+q79PZcAe0xq2SAtWro5ogpaaNPnB3tI/9RinhCRy/vGLp7DobcyjskNobDcY5q8CBjobeplKTmExzeCh2zrrd0mkn/x4UU2wvJajXQr16gtqOcryuqCU2rstzBh11ZDFn97/tYfY3792VMw6ON+2TQsZZahLya+bdyxVaAcu9AReuqjXCliheKGABCqgpUgoWZ6mp+xeYdZphuwMrCxVbBVKHm5s2vjpg2i0Fi5rxHaLWwW5pOsMLKQdWlijuXmMp1Iy0tI5CY9otjtX9qcXiWMN2M4RP8dTEDXNYtkoXkgHFBEstGVkoXRpXsKhJeM5mG7VYgEUEBkbAp87RWawMVqyCuUMBpYT8OEo5bxECiqo6QUoAiGMzj0X7wMSckajOSrrBDXb6wSKJgGUyXSFI6XvPWJrpv68cKH9dl3ewc5zVFAPzWXVtlGDndPHHihN0YIUbY5mxlCKFqvVCcXnghWXSdKi6Qkp0+Z9fUceBlbV2i/3piheLM4qXIkUJdTRfhZtTe6bLdKwuYBFJ86Iaz1l5r9oNc1jKoAKK17FpsXQZB5hiuyFEzfVfNkJ2bGpYULMa5SULgHNgZZXFAh0FSJEyz3rhZSrzR+ItbBglqpqS0AVvB1aW4KXxlsKEozRV9zuwtUaRogTVrYK6aKYuVVtzMVYWauyDH+s2tOYfuVbduD9vIfsXdMj1vWfdMIMNUzfKrFN/C2raPsf6HqIDKwvtFjs82aKjw0+grcE7Z0mKchxfAU7b5H1J3UXiLFbWph4YYD7sAUBAAECx78kILjpnNKMx3dWuv6KEmroL3oGV5akHIMNKmVgk6raorzsgFEqLTtCBFTW8TOuC3UITtStEWmr5TJtn8R1DN6jR+YjtkOHnq1cSGyX6khZtaqgec7iCl8UvLrtBjYKSDtAPqQEB7xwAXKIbo6kPWLpz3/wujQNrGIXz+jm1IHZF20nrV5bdQEbZhkX6u8gOrMwN5zP4LoZ84cQNqlNnsZw6sJwOO/0P5UlQG6o0bhAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ0OjE2LTA1OjAwuWoOXwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU0dTLnN2Z9sFfr0AAAAASUVORK5CYII="},"227":{"admin":"Uruguay","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGPklEQVR42u2db2hVdRjHLySxZFdaNy3KXhQjy2Eyy62CIrDeZEapL6KtQTS6olS21vYiYkzEVaxc3W3JhZhOibHRpFRybkwdy02nJZbTVEgtrDaIzchREQv2uS8eOJ3LvWdn6/75vvny47m/85yzcz88z/N7fr9tgcmKyXcnu6RSfzWgVyAVWFKBJRVYs3azke6R535p1UsXWEnr+Oj4trFFbp/uLurc0lHnNkfYCaz/0J+euPz6uY2g4wZcxSev/V6+2gL0W+PVdX/Na729pTTieq00q8H6Jvr1/YPzqwZevfTUJmdMArvqkg3PFo2e2Xc6/+jf2E/sHYx+Ecbub8QC2bplOw4MH32j5KNHT86RzqYGvCU7N3S23PZm85Kh/XM7jlfVW1CAqbHurcLcCWDC3vV4e8G6muZrm+cVHnNLkVfWj45NPBw/ybpdlX927eK9FwOB4sK25dJZVW8FeGQgMnfrt0Qp+ylItS+sPxJc1LO8bdeaauYfO3n4++gw9v7zBy5sfQZ7x3DzfcXd+0LRirs3OCMW/rlXsvFMYKUZWM4U1lfbk799E6mHuAUohzpaHgi2gxGR6culH5fkbu8r3xPc+BB25vRWtfYuLrDo4BP/3movgZWWYKEU3SQ40AGs/rc7w6vKB49/+k7ei0ADakdqdl0XrP7sQtP19+Z8dUPboSUrsDD//LJzK4fL8PN+uKrspju7mj6/0lDt7dkEVhqDRd1DyiMa9azZ2fFgJQnubPGe90I7sQPZqbzOoryI1a7S6JOhOcznWua3l277YOUdtq5iDHyJpEWBldJg8RU6+0+2hKcwBwsiENAwJvYAloWMOLd76MOXgjdiYT4RDp/cnaTJsoBVJ5GS5Cuw0jhiUT5T8RBLbIyhZsIOUoBiASIyWYAscBZHEiU+8U/Jj//ES3jAWnVX5fq+P5cOvFC7/w/pbGog8ZRHxcMXzNdPBAIL0LEA8emvXYfP3Bz+57tTTQsKrGJnvo1wJFDGlPakWqo3b90s6eyrxxrr0tAPjacvEldszVR/rfLn3KeJUuBFZAKyifYT4fktwMQcAIo21D4WupVrmXkwsuOWhQ3494aUNA0670Qs0hDVD+0Gqh+ily3GQeryPd2RUO/LC9Z25oyADrGKMXYbn+y1+KQrxr1s1RW/upKmNFi2bGePj/IZLCi6nSs+LIxtykOJWIzBjjl2/WirNFDjXtyXZ+B5KOG1dZ2WEcu2PemDEz9ixfVU9UOPytZJYAE0wGRrJj7Fzhx7LWPWhvjnXvTueQaeR3ErAzehSY6kQsAi3tgaC3SITFRafMqYKMUc5mMHLNqn+E92l1CalmDFVohT/XEbnwDF2VYAHSw0DmzEsh5IedZChKO1YestfW0ZBRapx24zgxEbMraEBw6UmdiJXoyxW4xsH4sOFnGLiMgJCHppVFeJ9LE4wvF8f01woCW+llXXjg3+aMfW8v9qKjxPsu8nIbAo4TevrskJX6V8JmKxUmObxXbebfQi/dkyHMVukYp1rab8UEvR1ECpqxgnUrar854GnXe+ZvACJlv32JMLxBiiDjCBi13rMQYvEGQ+vatYo3XKZ/zyXFs6GbsJzeEWGgF05KmBWM1ZjOzpBsZghIX54EvEwif+dbohi8ACAioee14UtWmRZgERyG7R2PKf+SQ7PODTeZRZYGUsWCQgtqWdR/AsWCQ4qiK6UKQ/e4KUmdZu74J/7qWjyVlxNNlt/46vn7SI2hOnJDjbMsAPM1kWOD3bSCawsqLGcgMrtnI05z9JZ9idqQ1oOGvlV6eKmMeSeMX4K48cHPamHLyZjgd/1a/nmemfy2ewbGPCeTAQdNxiD00Emhf+PpVz/WgPeDjnuM2PfywnNf24efNrjtunPoMFFm63pFpyeyk6uaC9wmn9trReusCSSgWWVGBJswWs6dT/ydrjr4D88u/t50r2Kn+fx5tl5p7H2/uJgZU6HRppJmlAPWLpzKhegVRgSQWWVGDpRUgFljQ9NBV+60OaearOu1RbOlKBJRVYehFSgSUVWNKsBkt/LVM6I3+DVH/fVzojfzVZPWKptnSkAksqsPQipAJLKrCk2az678XSlPif0FKptnSkAksqsKRSgSUVWFKBJZXG138B8SglSbh5r3EAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU2OjUwLTA1OjAwm4s6YgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVVJZLnN2Z6F+wA4AAAAASUVORK5CYII="},"239":{"admin":"Samoa","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADMElEQVR42u2bTUhUURiGXRRBtshyURHRLEYD+6NSIlCkbSBCpFCbICJCo6LCRTG6mMRgVikWRCgUCmEQgmUrBaeECAukIFILC8GijFqIBBH4tjhy504zc+fM3Jl5Ns/ijF6Y+z18P+ecKSpaXVHaejHNvFsRbWuw8uTMc/m7bPq1Y93l0aH3pVV7v71uXj8dvDQxUVJSVgbdWOS3QO46X7/QXVd+9EjdzTnbuiCWn8SykI02hA5d7ZjZEqn9EbnfPnbnwFiw7U/32dFqrejTzGcpxLIvluXSpvzUf+Vx42T74sxS9e+volaUwyiFlMIUdaw72Ly5b9AUq77x3GL/pD97LJNo5C6WUwI3Laxlr+NLLdsftp3aeq3n0YColWzJZK64ZSzE8nfzvhxIZy+VSHelMpp0H0bznutirY3unw+Pp3fK0zMlU2Sut+F5jYqmVvQpPVY+irUcDAVYrXdXZ9+9F8UrAq+ckVJh3Xfj2M7bU4NPR0bfVaobmw58WvW9VbNkmrMXYmVNLEdgFNrQra4zI8XPHrwKzQ4r/FLhX+fkQSxRfZie/GZhes2XWSu7X4jlt1KoMCvk5vbBiozl4fkSS7NkUyBcNXRYmSzNMyxi+VMsyaRtTxXEFT2QB71MQRV49rEKQixzP91ULTUJXFtyzyUVsQrirNBN0AuBjtNPxlX4YkiGWIiVLGtPnBzoefn5+nz5z0q1/zHmvkTESk0+xMpXsVRM1Z5rovS0U5XA5gJi2TzSsTBbeWrSOYTOk4yVmct6LntgWbgqg1j5XQrNQ2jEQixPE582Ns2DmuFwtHNqm7n+n4LopQTH/1/EykWxpIvO+DT3mfextKKjoawVR+5jWRErgz+L0D0FU6wYO/WZ/6FH3IyFWDnQY+nwR0VQ1IrrJUTLm6KUQm9iOV5i2piwBCpz2qMyp0JzJX7gbROxkhZLrwzG5+6PwdqmGsRKQiy9LHsc/rDx7Z5e73/jB6JLEmI5pxvn1APJUkmLletfIDPhR6yCEwsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELZp1/AdfAFB/2no59AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMjowMi0wNTowMOHe8JIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1dTTS5zdmdo3TPAAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/1/1/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/1/1/0.grid.json
new file mode 100644
index 0000000..3bb1360
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/1/1/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," "," "," "," "," ! "," ! "," ! ! ! !!! "," !!!!!!! !! "," ### # !! !! !! "," ###### ! !!! "," #### !!! "," #### !! "," ## # ! "," ## # !!! "," # ! !!!! ! "," !! !!!!!!!! !! ! "," !!! ! !!!!!!!!! !!!!! "," !! !!!!!!!!! ! ! "," !! !!!!!!!!!! ! ! "," !! ! !!!!!!!!!!!!!!! !! "," ! ! !!!!!!!!!!!!!!!!!!! !!!! "," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "," ### !! !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "," ####$! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ##%%$!!!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," #%%%$$!!!! !!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," #%%%$$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," #%%% $$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," #%%%$$$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ###%% $$$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ###%% $$$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!! "," ###%%&$ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! "," ##%%% ''!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! "," ( %% ))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! "," ((% **+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! ",", --....*+!!!!!!!!!!!!///!!!!!!!!!!!!!!!!!!!!!!!! ! ",", 0--....++11!!!!!!!!!//////!!!!!!!!!!!!!!!22!!!!!! ! 3"," 4---5...11111!!!!///!///////!!!!!!66!!!!!!222!!!! ! ! ","444--5771111111!!//////////////6666666666622222!!! ","44489:;;<<<111!!!!/////////////26666666666622222!! ! ! ","44499=>??<< 1!!!! //@@///////222266666666222222!! ","44 499=??A B!! /@@@@//CCC22222666666222222D! EE ","F 9 99GHHIIIIIIJJ KKK@@@@@C22222222222222222DD E ","F HHHIIIIIILL KKKK@@MM222222222222222 2 D EE ","NNNO P HHH IIQRRLLLLLKSSSTT222222222222222 UU EE ","NNNO QQRRLLLLLSSSTTVV22222222222222 UEEE ","NNNOWW WWX XYZZRRRLLLLSSTTTVV222222222222222 E ","NNNNWWWWWXXXXZZZZ[LLLLTTTTTVV]2222V222222222 ","NNNNWWWWWXXXXZZZZZ LLLTTTVVVVV]]VV^^2222222 ","NNNNWWWWWXXXX ZZZZZ_` TTVVVVVVa^^^222222bbE ","cNNdddeeWfffffZZZZZZ`` VVVVVVVa^^^ghh22 ","ccddddeeefffff ZZZi`` VVVVV ^^jjh 2 k ","ccdddeeeefffffliiii VVVV ^jjjh k ","ddmdmeeefffffnnii VV V jjoh kk ","pqmmmreeeffffnnssst VV j hh k ","pmmmmreuuvvvnnnnnt w V j k "," mmrruxuxvvvnnntt y z zz "," {||xxxx}}~~tt yyzz zy "],"keys":["","185","165","71","207","7","69","62","134","132","30","79","59","177","115","164","226","43","228","74","58","149","205","41","109","18","98","184","96","28","202","229","25","80","117","179","113","68","6","87","222","19","217","105","216","64","221","146","211","106","3","170","121","101","127","66","108","188","123","166","147","9","169","24","223","145","159","213","189","124","235","240","215","174","67","161","70","118","214","22","45","199","200","39","190","130","46","99","156","86","47","225","116"],"data":{"3":{"admin":"Afghanistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG70lEQVR42u2cX2iWVRzH3/fmzSgYi0V0sS66mUTscipm2FphRhbEfJUuwqK1wgZd6C5WrNFYrsz+YTlMsSB1A6ELXU2h2GbiZssczrXwzSnOWrVw6kAprDyfc/F7ObyrFOM97/vdxeHhPOc55xnnw/f3Pb9znjeRuDFx5S/ysqnzStnXnvwiWf/lZ4nSRCqukjdfs79qy9//RrLjvQIoBVY+ICWwIgArXrwEVpRg5TNwAkuh8CqhEVgCSx5LYGlVKLCiBut6IyuwtCoUWAIr/5RJHkuK9b9CKbBk3hUKpVgCS2AVLVjyWAJLiiWwBJbAknkXWAJLHktgXXPZ31VSWbKFPgd67uqYe2F/z21nbk1ej2M5AiuvwboWjIDm21tq5t73+khreuvycVuOtdWnnk0dObP0ySWXvzpRsfD29qG6+fOqMrQHO0CkFFgy7ykQoQSgTHnj4jXrLVKnHnijvH0rYFmkbBtK7oKpwCpSsFAaUAAaQOEaUEJ0whIQLVjUcC2PVURggRSqY7EAKe7Ojl3Yg707Xtn+clsD9f9GvQRWxOa974WyV27aa6GxmoRzsg6JNtZp2fDHXUbkKRDkLkhZTyawClaxhltf/PSRd050ba9o+fnwxfS91atGHqu7nH48l6JQDxwgZdHJZdItjkMlS2aqmo93b7rQWPt1W+2yBU8JrIJSLCb1ZF3ninWtlEw2pQWLa7vKo8YiZdvbNITtgZ6/37FxTkPqh9KPNzftAujBV2sO3X1OHit6xSIMMc0WKSYbDctStbKVi5dWnTzywf3ti6jJLGj7ae2lmaHjXaODM8cmnv+ue6z8pTfrDhBYaYmq2X6O7m5ckU4AE3hxzehhcBRY0YDFxH9TumrfwhkwGlv32ubn7rRlGAQBa+KOnX++P4RKTQ0fLP18+veV50d+S1JOru2t2HGOu7TkqTCMhiMCFm/FGwqsyEIhU3t48Onx6rfQD6YTvRmonPdRRVkYNLHzZ0sPJnsbCHMhWNRwd7ppYNmBt3kq7I3AB1K4uuFF9dMPn6a0WAusaBQLdJhaFIWgdurQnuYNx8AiF1iTyztXb7+Bld2lvl87fxy1YBEWbctcYDHKxEN7dm3KMDp4Yep5Q3msaMDCwdgAhK5Mvrs7s7MZ9UItaMk15en6jkc3zifYTf9ydMPAg95dBSV3p57Z29FdTV7e7y26AGd7ZkTwQgvtu9FSYEUAFiqFWSbwWSDQDBSFdAOByXosnBDWnhUlGkPP1HAX98ZderCJDL9l5O5aHHkrFhP0KbAiAIsQA1ioBeGPoEbKAF3BLZ2dM1zT24NyAJk37w4Ia7FtohW86BmYfJ9Ow+iZGkYkjAITb8W1H0Vg5T9YTDlaYjNJhCGck91+Rl3AkWkmSNEPihKafWpoyTUlXsquE9neAV9gsm/loRRY+Q8WoKA6TCGhjSnnroUp1KG+fc2f1D5BjorcFQpkg6b3Rq4lT9nslD9a4zBlLEbnTcAR1+UVVGDlP1hsv+BpUCnUgiDFCg5o0JIwR99/c8tQehvqAlgWL3Dxe4uuZbhdA0asGQl8jO7fxPXDGyoURrYqBAs7hT5Z4BABvjADTmBC5+gBF8U6kWdRGtSOlnZBYA07WsVTf0zN3HN+3ALq+zGb2QIrgjwWOkHoYZqpIelgV222tGHUrhD9tsy2D/tbRgHC2vxcvflcl4OS0ekTd0WN8liRgUXOHb0hNUDmHR3C5YRrPUBB4UAQFEgQoEyEUcBCe+zeYhZYbhSbkuBN/FrSvaEy75GBZbef0RurYdSw4qMlSU7vyUyJTwKyrGtX+uSCK3Fs1raHG952MWEDscCK7HSDnWD0A6XxQc3pE0jZc6ThgWOuwzZhe/CiZ6ttoXrpdEP0x2ZwRUw26oKVxs6z4WPzW7kOMdtvcuxWtw98LlySW886DeG0jc0iAnG4/SywIgOL/TtUhMw7JXiRQyI82Z07e8LdnmQPPwuz599JMVCiYTgwu40dJjikWNEfTWbt5jFy6mJ9j8UCZbIn4mf/0pDASukDruuZa+rDM1sCq6C+0iGZaW0114RLAhm2He+V6yMwtIcAx1P0wB6l3bqZ/bS7wCoQsEgrZFl4B4TNsJNuIFziwAhn/viNWxuClA95riVt6A2w0K1wG1tn3gv2u0LasAmNuba5K0DxyQVX2hp/Zstdo23gCFj0kOsgoRSriD6xx+DjlmzqAYAobbrB1tOea3r4rx/XC6wC/1EQmz4IPwILQbQffl0dTAJLv4+l38eSYsX2W35SLIElsGTe9eO2AqvIwZLHknmXYgksgSWw5LEElsCSxxJYUiyBJbAElsy7wBJY8lgCS4olsASWwJJ5F1gCS2AJLIXCf0BEYP0F7SYyPlPSiAIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE1OjEzLTA1OjAw7m98/AAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUZHLnN2Z3V4Eb0AAAAASUVORK5CYII="},"6":{"admin":"Albania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABHEAIAAAAuKKnYAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHXElEQVR42u2du6omRRSF6zDDGW/jcRhwBEXOOHIUTAx9BBVRzEREzEwn9AGEyQ00VhAzGSbRaDAzExXxKTTxCQxW8sFiN/Vfqv6q7p0Uh/77VFd1rdqXtfeuLr89unVxdXeE9o+fb31w9XCc8axjXqd6elk3OA4ZT/+5rGlrlS1AZK3tqd5JzXNLgiPn1WL8Jfd9ti0AV3KvJ1Ba9FmO9fh2EBnHAO+zDUZ7/6kKU36k8Z7AWnubEivl3LZ5rJSLc73Pkks4MrjHf8M7eIXbebnqme1x+5wFIs0J0vVJoGhGP717cXn39y/eefLf59/85s9nHrx09vjVi0f3bu/6Bni/elBv6llP2WYUssy4Px68+PTVC/+ojfr/8fLZTy8vec/nrzzx5e3zt964/t1Tn9z56uy/6x+d/VDulff19+vl2ns3Pvv4tRv3n/tWdwocgos/Rb/qTrXqwXvWE9Wz/lfg0wj5Nijt1H80x1V5hSPIMy0zF0+LFN3JhSew9L9v/31+7eZ9LbmuqBVEvv/r5sOXv45Gol85ErXqTT3rip7oI5E8c3WsGRGa+0nT04qDcqzuWk+b/RMcAoEWI7JvqPgcClH74a/nv1zccYmlK/q1ph89UU+XlIrmqFloRgTljEpzSh5L0oLw4uI5FGjraPGkYqjCHBDq3+0kXeHT/b+oTCn5vDeN1kGvflxdJkHafE9Q2bllI5WkBRN0tFSCFJUL+3Gg0DZS66pTrXogrAUjPVFP10g0KvXDketX9nNcj7Xn+k4psSR1JAk0DSo72TeRHHLw8XU4vNQP3QXvWf/l4FtWu+pHo6UXSUuLKn4u37DMsgPcxtKyuXVFORRJF7ZaVPqSDgiBwCGlO6mw1NvyEzWqSCZpRvQoM1bYfCdFi6d9757XstyKlF1kP0UWVY2idJiS2lBLX5Kgn5FWnTgITduF8kMtoVYDrMjrrAEWpU6Nt0gYccyufOelrPcMQp92wvTsZJdIGamViqknBbSo6q1GkUXKVD3U0BmCFF0QjVxzockfsXSHS5fWK1hOJTYPmRhVBv0sAq5e6pyqpZfKQJBTuJuwsSJA9Ezb1f6mKnEJJFlVY/H0b8nORxJOv9JPnKX8dWK6gdNjTFD7XoshFePm8AitRqURarQkF8jO9+GxOpV/jZ/goQWQCUxeW0s1oyokI68ZaXbLoaqkG44G2WUak9wP2e3RICU1TYuKGRZUjprpjHU7U2aQan9reZy/Zl5Ufci5Z0tq16WsZqTZuY01C/++wmIKuv2efTUCpDiqMTOuDgdfGU0m1fBnjNmRCKWpLiXI3KZxWo2fapo5EZTEDDdNTzeMP42IxqQMWE5uGcFsr8kSWxWw5lJ5zHyioz4m0RDx7wSZZnQsFbmSg9d6BhCWMxokA+pD0X1aZlyRyPU05dlL3MoI+2DX3rSbmbHuLDbjd55T4EHrFkY6rT2C2+OS9AQ5oygUfSzipt2dZa6dQeM9yppijihtLF/OFlQqac8I4rqHREOU6XV4jsNwqnB8/l0MdVQKRsnEjCstqlpW2pBD2k/BkURgVj5hRGqXo/KaxOUaoczH6ppf6ovHqByva/GkemiN7acW3Z4jt04OXbYUHQumNR9emT3OqaRlRhgRKNr3aqnU6NJTetEXY+javcgIZJFlpicydcf9U43E4UX7T6D0PPrJ6IYZK0CWCQVmlJP34qISoBGkpIw82qgrUcEqqxHdV2WqscMrIiOSxzqB3KKEYFHD8mIsm+00mT3gzcAwTfLIjahRVRo5k2ei0v4EVvPzZyQz6HPVFEstB6c9Y9PB5wUUkdTxOqLlElxSvpHx3ues1+FihT3zSJm3RLuH9YY+nuXMdMoY9RMZ7Fz4KCLpYWY3z6lSWc99iFc4guqckiCl4vCccXpkupOZT35EB4PZ9YVcUdEYwcFjSJglxlEx4Ycz8gTAKVXhLOYh/UGmxdGT8rIqJ045X9b5UKrV1/awHy9DdSI0KlMj/cENMMJpMyunGzzdj7ucEouSjEESMlsEq3rbj82Kjlmj18lTsjhmylddkRJM4/3EBz16qCQ6KsgVJU1vLxmt59yjQgnSHFFNs0ZLF6FF9XN/02WyEntvWai+fOiPH+tI05vJKjWqkCBgDywx5VOWR6WRk/JdeQbp+O3yUWb1KtWDPJHc8vRiSsH9zG0vaMsPYU6fnU1mi8cbRYwX2SlPwjm8KH4dRwyXnks7cvoHfcPlo9g8cMQzF/KM+ykPt+2TTU8lSGqD+Z9+ckR+v6NTEHqcg3F3VYuSRpEhz+tUmvnFjfxI0w6nB3ocUFfafdViHVArKbR3PT3wkJKs7Xx9qGussGcJwLEOZnL2PL9klsb7kX3GKGY3b7LkNDzWusG3na2VzPtwSz7jIWlNjPfxjdB1yImtGRIlRXrO/QRf/0oArRvK7UZb8tXnJklVmJDd0qd7513OnnTr1nzPkvs+25UY7zNmPYyQA5I21kDH4G7TKpqmYHVrbHK7kW+HFslYYfqYTd7Y/8HPfi0xPlKPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToxNjoxMi0wNTowMKMvzEsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0FMQi5zdmccA72rAAAAAElFTkSuQmCC"},"7":{"admin":"Aland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABBEAIAAAD4cUrFAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKklEQVR42u2dTUhVQRTHB7JN5VdiBGEQgW0kRIWgReEiiijQ3NRCQQnpY2dFhZtoFSRCRJBBSIFQQauihRFBRlCCleU2KioeCSkokW4s3zxjHnPnvnPmw/fue//N4TF37tyZub935szMmXOFEB0dIyPJke3d91r3DZ9NXRhaWD9+uuzqUufEJXEySy5NCiF+zp8f3dw+2V9xp2VIlx82VTc2/5C/P26o3dv0eu7JrV8VTfJevcwvO5/P1Mxtfd/TeO1x0nosXzLpYOkoGMCSMKlIyd85wEqnAKzSA4ugsVSYdMigsQAWC6xz89lg6dJ2KFyuA9ApbbAOl39qmXC3sb5Pv9hd9RkaC2Blg/W2am3LLpOlZQRL+Q2NBbCijHcJVlqyjXfYWJgVqlK1sVSw2ENhxKwQGgtDoToU6lJCNlxzpnk/NBbA8geWHBzZYEFjwcZyBwsLpLCxKMZ7lgkPjQWwfM0KdbxWwLp99D9YGl7QWACLDRZmhQArF1iKvolYefdmvAMazAr1LR0Pxjs0FobCWIcZbEIDLG+OfqQtHQ0vbEIDLONQaOnoB40FsCL2CtNgmRz94JoMsCLAWqwb/7NmQndx8ayxsPJuC5aq2E1KXqarknuV8lsvITpPBFiBHf1WwOquu17PrS2nvZSrdr1q96boPGj37ujqbRtoSJbsOXD5aW85CSz3wxSK8d7a1/et/1ASe2z1pUh1jZ2oPJiRo2M3KisiJCWPmtPu3lgpX62UM5Wv7q77bbKubJcblC0dQ8nTV17OlO+RdXBpC0PG9ydF+iqTUk9FCtOLMVkbxhRKfr/lk8GK11sUjUVqEbeN9H6I7w2HXvLwHg0ptEokS5r2Ch1sLEiuFMWBUWifd7Y+CNe6/JZJvldkKeQikVMNucDKklluM8v3FmOfrLYUs+8GH1UPzB4beLaxliQLM78mv9488mDbYjxY6tVU+6mpLZ0RT+fWx1e7El6OMJ0SToqMdzuOP6WjD4uQvqSIn4rHRz2I39ylTPXdy88BlskpmVw3bs3zVQ43DyX4AL0+erqgNDi+CPcOdSmfC1YILPz2gF3OEH8Su/clfws60fQ8do20+7eFA4v+mn31iak+vmro8izucwW3I+yUs7syN6bkdSh0+ePZ/eW4xgN3NHAZFo1DIVdjhUCK+1w7sCidbtded/vJ7lkhLGAXXVvss0LCUIgZXJBZYcLWq8hrWpl1LIqNZVrH8rWmFX8XvUx6fShPtLuXnLPYV94JNla2dwNW3j2tvOdtByrcvpvujxVrY3nbhKb7BRTOvmGwJ4oCbVIwsErIu2F12mJ4irDztrG8i9tsOx3APQmdhswJLDsvKLs83P7k9qG73pVDofSHlL6RDJ9GXx6nindopg4ED0aaB6nP+Fi6B6mptsarhPZaeqgq5UeU7OkdkdqipCfM5337wvGHg13S590lPhYjjFE6BT7vbJ93zukL+lkOejrlHIia3lZ/P/XvlE7ZxTcu0Wai/LG4p3TiT8hwz9L46k+X8zZ2+U1XSzLwWo74WDnCcePMIGI3ID4WwAoexgjxsQBWkMBriI8FsBAfC2AVY3wsfKQJYCE+FsAquPhY+EgTwMoF1qrGx4LGwlDoOT4WNBbA8hofCxoLYPlYbtAGVmgsgBUkPhY0Fr7+ZQTLcoEUGgsaizIUwsYCWN6Md8R5B1jBlxvYnzzBUFgaYC1Ly29CQ2MBrHCOfvqwCBsLYI2QvkyR2Sv0GdwWGgsai+02Q/EghdsMV/4FWkOpeRgUuWYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE2OjMyLTA1OjAw4QrLNgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUxELnN2Z5NDSAsAAAAASUVORK5CYII="},"9":{"admin":"United Arab Emirates","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACnklEQVR42u2avUoEMRDHF7XV48BCS0Gv0kp7fQMRbNQXEBQLLX0DQSwVfAKLK6+xUMHG6rAQROz8wMJCLLVQOVQ8WHaZZCZLcv7+C2HZzd0mk18mk4+s3a7XG40Y0su12kdjs/U8dji2N3K/Mziwn2UHV1n2mx42/9L886Kc3W/z90V5yp+Xl0r+D5pv+f2zJL+fVXNP4gZLUj0/I8qRLW8wefOUdwk9HOGg9KpXCmAF6E/GXkpeKnmDhfagErgV9UoZLL2rD+EJystW3uM19bIaUo3qlc5Q6DrEWBnO77lrw8t9idXw59oVHT1iah7LKobwC//9GtI1xvKrqd9b21AhSbBcDRQiOLUKgW1jrBB+UR1TpjYU6t11OC8liaLk4Fo1vP5bXkCn5rH08ZDVXNJ1icFvMUK/XFJ9fZP3WPoGDrGEoQnJqx8i9UsVBW/T9Fh+w4TV1NpqvUczmfCbperrK7ZhrwyF+tWgomYwnSup/I1m50A/BDvGlGlu6YQIhF1ncLaIuPotq/0GW1tFClZ92mfl3dUQIfYQJX09HND6fU8r+JIEK99IVfZO+ZzU6le2e4WhbRX1ckMerCKYSKNP4wHrOz27nnid3F49Pmoubizdnj6tTHany+cnu8vn3feh03wZikrVS2m5tSU2iQms9drM+NZVa6o9t/Q+fD/7ePFZoI+X6q78d6svQ2zXp0BJgoXiF2AhwEKABViABVgIsBBgARZgARYCLARYgAVYgIUACwEWYAEWYKFeB+tt/G7+4UZSAflxDvJUkyc+sH4O+o2+dA76LTwubnQOlnGleUV4NLnW3zma3DfUOZqMkhVgIcBCgAVYgAVYCLAQYAEWYAEWAiwEWIAFWICFAAsBFmABFmAhwEKABVj/T1+gjm/JSCOymgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTc6MjQtMDU6MDChspWsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BUkUuc3Znl/8CUAAAAABJRU5ErkJggg=="},"18":{"admin":"Austria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABEklEQVR42u3dsW2DQBiG4WMb2xPYnpHeg1B5Fs/AAFRQuKE5Cc7/YRDPV7xVgpzTI0VxdEnq++vlcVeNbXIECpaCpWA5CAVLwVKwVMFSsBQsVbAO1fHWPBuwVMFSsHzzAksVLAVLwVIFS8FSsCr/+P195rxbPm35x0e9wqhn/vJZNc4nB2t4dZ93qxrbNJpVGFgGloFlYJmBZWAZWGZgGVgGlhlYBpaBZQaWgWVgmYFlYBlYZmDZbmG5T6JVbukU3jjL3T5b0rU32sqelnudsR+/5ddb9pw/nY+b0OqKvYLlr+aB5SAULAVLwXIQCpaCpWCpf2IAlvfGwFKwVMFSsBQsVbAULAVLFSwFy3vlp/o11ATvdjw6QaUN+AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MjQ6MTMtMDU6MDCYT3HDAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVVQuc3Zn13qM2gAAAABJRU5ErkJggg=="},"19":{"admin":"Azerbaijan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACw0lEQVR42u2aPWgUQRiGp7KysJADK3+wsbCRWBmL+I9gjpB0iohyNiLKWYhgRIJVIERJcyBamKCIEjgLUXKKkkJFLQQtREKEJBqjBHL+K4cWbwIjyx6zu3PnXfZpHvb29r7ZnX3u+2Zn1hhTGB39CKFv0gUQsSBiQcSiIyBiQcSCiAUhYkHEgogFIWJBxIKIBSFiQcSC6WN25ODp829gtm3/QN/9eXYfONu/c572/lq0q1aq76k17ev1dLwZ/2xutuTd+XbKrGlpDdL+NmrMJPHDjq8eJ6zdicHMnR27353ZsHffxpnOPaV8Zqa36+HJgrYnL6y+0f4j2G6S621kxutbbZuoP07SmMvNrs/5BCl1vvfczT4qVb7OPi93/LlWWVEpir9fjF2cHPxZeJp5NTH3oHfzlW1T4+uWdT1Lfg6N0P+1IGLl55ace33pm61RZen7wqf+8pOB7uunPqzdPnRkqzQSpeDsiWNDfcuV2+p/2xqfJm0XXF2pX7df9ozlJJNLHKmmAopMiJWfXrlp+tBVZSY7S7koVV1WJEu1WCpzdq7Snqjl2M5bklVZUHppsI9YqaBuuYbhtljSIp6mX1Zdfnxrl3KeBv6KHzX//a+BNmJ5oLKInvLsIhjvKU/qBDUV7exFxkKsCNGU54JiKW8hFqWwmGTYrlIoWe1SqIkJxlgpYtjgPZ6myoIM3hGrVROb9gy7tu285bLE5GsxBLEWFZVdbL3sCVIXsZggRazQQqa1v3+yV8iSjkobSzqIFWEAXs9FaMRqsrU/X9GCr80ob4W9NrOY/mC+pnBNduT4vc5y07P96JaO9Z6j2TH9xk8BjTHF0uE2CH3TDA/ncgsftO3CsON97Xdv1z1C9SPDOiheHF/n0yxxgt9GuKkQupMugIgFEQsiFh0BEQsiFkQsCBELIhZELAgRCyIWRCwIPfEv4VStsKuTQYgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI0OjQ1LTA1OjAws39KnQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQVpFLnN2Z3usgD0AAAAASUVORK5CYII="},"22":{"admin":"Benin","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABo0lEQVR42u3br0sDYRzA4TMYHS5sQZwiCBYPsS0Z1KQzXFldMhoGCy4IwsQZBZNt/gVms0GDZeKKYU0YFmEilvkjrGxG3YF6T/mEi3cP3/e997ggCOr1KEpCZ6rHextL7dp1O5d/f7ndzjR6vWYzm9U4GoClYIEFFlhggaVggQUWWGCBpWB9o4fP0RVYYMUOy4MHy8QCCywFCyywwALLcYOaWB4/WGCBBZaCBQFYYIEFFlhgKVjOscAyscACS8ECCyywwAJLwQILLLDAAkvBAgusXwnrY6WVyxTe1u9qmUsdbfv3NkGwcmsH7c2T1nllaqHU7ZbLc2caXxP0rXB6fPdpa+zmfra4XH8sT+yE+U4jVQr3O510OgyHruiPm6CJNQTrYnJ+8aFPSuNoUicWWGCNokeV6BQssMACCywF6wuswaIAlokFFlhg/UtMgwULLBMLLLAULLDAAgusP7KFBwsssMCyFCpYYIEFFlhgaaJhpaqrhVewwBr1WyFYYFkKwQJLwQILLCfvYJlYChZYYFkKwQJLLYXD/xVCAJY9FlhgJbufplbALlZTXuoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI1OjM1LTA1OjAwVngougAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkVOLnN2Z88UG/8AAAAASUVORK5CYII="},"24":{"admin":"Bangladesh","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACn0lEQVR42u2dPS9EQRSGtxalaIhGSFYk66tQEcuusGyyFY0NiYpOIhK1hlqpQjZR+An4AVQKttSpVEKikChexSTiI+7dufPxNE/Bunfu5smZM+fOHLlc+8ZGtQphyuQrgIgFEQsiFl8ERCyIWBCxIEQsiFhus/N5fW+x62eiCGL9ItD0yPLd/MXOUqVv9vW4d/KxdH65Nb49k79ujr1Mb933DL8Xm6J+IuqT+itdAeGiFquQr7dVbvavyrXymxR5GRjonppIwqeDwa6phq6mKw9trjQrV2gUuFimTA8dhUaxnlymv6imO/bfrq4tzKBUUGKt9tVKc6OayFot03fU3TUSxMr5njkpWmQl088xLOpszF+llFa7o9RXaoSR6oVS6IVY1f9NfJqestVLI0csR9NzX6LUd3JHlNq7P0Qt47Nd8aW7coyiMBHq9Ofy5BjFtOjy4FTXtlPqtCmfrhx41T6MWOVCek7c8kAsLc7TesfnJvV0wZYh3ByW9hG0Ig65E9s0Ej0pYlmitqmEGqtM6kkRyxL9qq0nr8sjliX6nl39fcLVZsMAMy0303YXyqF2srFgU/hQxfKlACGxAqzFE7EoOiAWYiFWMrGU0saTvLMqtMSTs8nd0iHlBsQKqkBqM/GnQOr0Kx0fX0LzSoeX0KTtsW6b8XGiZNtMxiebs9ro1zrqifR0iOV93DIjDVuTEevzMEUY+RaHKRw9/uXj6s9k/ah2Olfm+BfpPNNfTEfs/arLc8SeDg4plxVoCuJ9GyPXjkjQxojGaymv+CJKz+NpFallvM1WkboLrSIjam5rSqY9T8mny6/NbZGJdtyf7bglhNmO22zEbVK/pR03YqXwbwQUgVAHsSBiQcSCELEgYkHEghCxIGJBxILw3/wAd3GLQCDAyg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI2OjE3LTA1OjAwaPWF7QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkdELnN2Z8hsolUAAAAASUVORK5CYII="},"25":{"admin":"Bulgaria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABOklEQVR42u3aIVICcRjG4S94AbcZsLHJRqNRTEYS1Sw2K8yQOAB4AJt3oOEFDIyVA2DxABo06CzMLMzHiPK84ZEh6PDnN8uMS7yb7WHhCExYJiwTlpmwTFgmLDNhmbBMWGbCMmGZsMyEZcIyYZkJy4RlxxbWsv3WfR2Sucb500NnsCJzjYjJ/OZqe6fTfn/dM1V3+/38LTe9a9u+v18/7u9uy83W+QPkd3+EVX1cfcaRsVZY1SvT52NHw7QrlisThUUfhTzasBwEk8NyBBQWhUVhOQgKi8KisEhhrfnahjsHwkp2f3cO/Os47ZbOX7fO98b+0+s9YM+ex8V1l8w1Zietx8YFmWu8jJqXpz0y11gsyrIoyFyFRWFRWBSWg6CwKCwKy0FQWBQWhUUKi8KisEhhUVgUFiksHrAfuwGIY4Xi14MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI2OjQwLTA1OjAw5bK1BwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkdSLnN2ZyfMwHcAAAAASUVORK5CYII="},"28":{"admin":"Bosnia and Herzegovina","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE40lEQVR42u2cT0hUURTGZyH92QQhCVFE5qJaZBS4qECJIkippKBFkQThogJ36RDIJG0yIkSLahOkjEETFRFIGriwkUgUIoUKpaIoMaQmKEiwgvne4jzO3Dfv6dx5f+7ZHIb7nuM48/Pcc757vonFNlWUX0/piE0X2maGauYr5zv/jtN4bvOlJcNb3TzDhos7frTVjw6mOtb++lc6MheLSVTFRKKlpXZE36fpOep76vs1fe1Tq8ZXvCub7U9c7Rh72YTHWOf315Wcev6kf83czs93lsZi60a7vmJ9W9+eK/G04OUKLPK+LQ4Lx+dx81t0ILWxc+9scrL9yK2bY714jJdC19cfq37cPYn7y1ZXxW+fAXCHS06/6uvmf4bgJRkrRXHhdGPduppd2b7lQOZe1XTXt6Hf1dde9LS+Pmt7BvKzgpeApYzAqPv9w+TbfUPNI+kvE9giUYF9n/pZ+mc/1gc+pCs/3UWtJngJWK7i7svHDz1KAiBe4AMvbJo8bwleAlae8nDXm6OtD+qBEQUL+cxNqSh4CVg5YuON8z2DzwATivepZR8nMs2jjeN1Mxmeq1DyW4W/dI4RAatQrSyJKNWhbAEjdI6ADNul9XuzEfVWDiXM+M7Rs9yg4dMMUMayxAhFR2lpWmQd5Tyi6g0yE68C61hhB0sVgRQyE0p45DZUY4hYwVXcSUE0DS8By0OEFk/FCB5xFXeaXNpL8e65c8R2yfHCClX2bdEwvASsBdZh0OV5tFVpWaS4sm/CkbaA5TljQZJAwQ7dC9o9VnCV5ipcRe1lzuYoYC1Qo0e+4Z0jUKMgQpKwKWFsi4weXgJWwSJQA3aYiUCkx0QY16FXaU0WJbykKyxwBC7OnSMi5ImoTkxIxtKi/CIP8SNtKF58PsL2GiKRvSRjaTxMwEEQRQqPVSpXlLKXZCyNkgQOsNEPAiaU8LxDBFLoKPlAYhjxErA0do62seYsKJiDsCQJsg5JwlliBV7DDb395QcFLEPBco7ACwDhMbpF2jnye8IlqwpYvp05orSHXk8rMDzGOrZOW02WzV7AK8jZK6hgBaeb0DxrrxqGho6Pe8I4rSpdoc8HRCobrVKSCAleshX6HLmNFr2ks402+MKEgOWzJOFso+UTrTYbbYAHcmQr9C2qbGTONlo6rZrjzsCo9gKWf8dHjm86t9GiQ6SdI524txk6AtA5ylYYAqGVIpXHRquQVYufvSRjhSDmsdHyMegAqPaSsUKwaVIbLTY+DzZan0p7Acs/C6VHGy1VtlCBoUO0ZlaZjda636fOMbpgRUK7RzbiuYfaNPhXw+Ww0RbdpS0ZK6g9o4sVIIXMFDQbrYAVkSNt52FoVGM5vp1QG15tFfGB2uUCVugznMpGizLfNuNVlDNHyViROiByZaMtyjC0gBX+8p9IEtDo89hoSV+pz0YrYEVKo+f6u8pGi0IeW6cO1V7AMgg7ZxstmoBC2WjlSMeITRa4oJBXdY5UnrApZwvCS8AyrsBXDUPnsdF6rL1kKzQuqmy0tm9YXXTnKGAZpHWhE6Q2WmyROWy0BC/6Vb/u8RKwzDh/zL5yNzZa2iEqbbQu8BKwJCpttMhVbmy0HC/tYHn9J5cPOPg2Wtxj2WgVupd1VihdoUQ6raoahsY6nwCjWeTEypNPGzKI3vOKLhD/AzBi5B+T++PRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyNzoyNS0wNTowMJ4n+BkAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JJSC5zdmc3li4kAAAAAElFTkSuQmCC"},"30":{"admin":"Belarus","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADcUlEQVR42u2dMWhUQRCGX62FEkyr2BmCYGXEQiyvtrGxSyFpbERSiW0KRSvhGhsb0Ua7tBYRDBoMIhIEScBCOQhaHEYlaPFd8cPy7r0NpPJrfoa5bQ4+ZmZ3Zvc1f+/tr+2v/fk2ujG6iX49sXJ/ZZie9JfrPxyfez336s1ac6w5oqpoAyi7K08uPBn8eLc6Xv318cXCeGGMnX4ASr9gqR1g7S1uzWyd+/J0+e3y5qezg73B3s77pY2lDfzY+Fnz+8P25Z2jgqX2Auvzmav7V/e/n3y++nwVm8hU+lkvWGoHWKNbw8HwCpFpfGd9Z/0nFRVpkSiFH5v1gqV2gAUuwERkAh2gwcbPGtYLltoBFtEo0xw2AKWfSov1gqVWgEVCxGYPmP5Ml4Kl9jpuQDP9pX/z9Oz67Hp6BEvtFbGAifQHOnjSj836H9vjX7szgqV27Aoz/ZHygAY706K7QrUXWOz1sn7KQ9HSn20fwVJbweIoAWhIguUpPP6MXoKl9qqxwAWYQA0PNn487grVil0hUYofsoRv8wuW2gEWTWUaOPyQswzpx7YJrVbUWLg4rwIX9oDY+FnDesFSO8CiJM+YVKa/jFusFyy1AyxSHjEJBabSRlnvAanaCyxiUk6QlpVW/mrEUitaOhx+lmMz6RcstWIeq5x8p5Yqp+Cdx1IrwGIPiHIcCkaAlb8KllqRCkEEJfGl5q/OvKsVxTtxKNvMeTMHP7Y1lnpAsLIJDWQkQWzBUg8lFdqEViuK9xySoWCneMdv8a4eEKwclfG4QT3EA1IaOKXfVKhWt3TKNk7Z0nG6QT3EJrRgqRVjM1nCJ1iOzdRqTrD9b/+rY9APyNoG/Ribuf7g9uP5i4OHi6eaRlXRXqPJGZNyNHl09+ej0bX588+OzV9qmuHLplHVidZepmBvOLknLVjqdLA4PshrXm3Xv7AnxxOCpU4Ha/qF1YxerJm8oSVY6nSwaq/YTx4LESx1OlgHexREsNReNVYmwXzAqPQz6CdYaq9dYT6wlkhl3zBXCpbaK2LlY5BtT0ViuytUK8CiVJ/+uK1gqRVg5XPcRKm8V5jPdOdz3IKl9toV5rFCOfPucYNaDRa9v/IzJ22fPLFXqPYCq/xIU37mpO0jTYKl9jpuaPt8XPrzxEuw1A6wGNnL9k7bhzDTI1hqm/4DcvO4XhRykw8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI3OjUxLTA1OjAwYK3VEwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkxSLnN2Z00LMLQAAAAASUVORK5CYII="},"39":{"admin":"Central African Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADs0lEQVR42u3dTUgUYRzH8YHIBfUQ2otGHTQhA6UCMygvRRYVeAgihKgIMUI6FL1TVIJJSgkhBZ0yKshL0SGRQFEoCiNRumRgdQmDQBIC7W2D+W/wyLPPMPsy+/Z8Lz9kdmaemWc/83+emV1cx8lbV9vaYVvuGqx41Nw7/npRec2m4FJasbOHHWABK9tgjVUfu/TnTOjcVP1k2alt/cfvAAtYScgVn7c8PVk2GnpyumR234vmtr0fgJVQLqhuuHLTblhuFwim8Le3407+rbnrbRumgEXFiuUaUq8k5W/BFA6P/XYcqVuLl9WFzr7MhGsRWOp0JeNgbZ1s7Dp0Y8fd/S0HStSUWvW+tG9N8YRULEmZb+nrS6ZyHgasjK5YAuVL52Be4ZzQmXo2VFTwScWkp75Oz0x3+9rvKYLlVkdgZcEcS+qTDHbepCLpDo7CS2jmh2r+XqhkKARWlJR6I7XHG5YQlGGUyXvQtTlH7gqFi16fIuku6Zi92rR5IL2nneOw9JuqZEH0v8/kqhc0xhmVy+vN4ccblx+M3CGmiRewsqxiCRoB9Lz4fvmqMrnj08FFhkL/J692VsJdlmWw/JyvPDhQM+thuScggKQ+CSP9qZX6GCLmATGpXZaDFUuHFdwzqhhg6W+V/9qgPGeP8tGNtp/1ww0TR7fPux+M74pUO87wkNZ03WcQrPguEn0r/7CCmHvp60cZYuJK45zJsH6ElFqB9JpkelXruHlAvVtMPSzvS9R01t5L9DT1j3eatk3kHZHc2dgy/rArXSkfVKe+3fMVu+sv/gwalrSS3h6OL6VMJLIHJ/wg3Bd+Z1tOD/UO9t0LGpa0YmcPAwtYwAIWsIAFLGABC1jAAhawgAUsYAELWMACFrCABSxgAQtYwAIWsIAFLGABC1g2wuouHPnYvdK27N1zre7EkqBhSSt29rBTWXW7Z/VX27Kp/0h7VX3QsKQVO3sYWMACFrCABSxgAQtYwAIWsIAFLGABC1jAAhawgAUsYAELWMACFrCABSxgAQtYwLIR1vDSgdmCNkn5P8fqEj2HRoZD+aN+1vFeM+j9mLaVJa8WXv5ROhM0LPk5BfVI9MzM/onveNR0/PzWTZQfBDAt0V/1sx/TVt5t+V+ivTr9q7O2qDXwrya7rRiP0Mdxxtw/yXqPYu1VLWNYNUcyE2BZkMACFrASw6QmsICV/bBsGxMYClNasSyrW8BiKAQWsIAFLGABC1jAAhawgAUsYAELWMlIYAGLigUsYAHrf/4D+/vF6KjUa1UAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjM1LTA1OjAwcd88PgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FGLnN2Z8KCGZwAAAAASUVORK5CYII="},"41":{"admin":"Switzerland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkEAIAAACvEN5AAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABlElEQVR42u3bsQ2CUBSGUcMQVizEGCQOxAjMwBZsQ03zNPkbOxtzMc9zi6+9EU/57q2122tUv1ufQMFSsBQsVbAULAVLFSwFS8FSBUvBUrBUwVKwFCxVsBSsX+q4j3tr0zZtVc1GsDrvvMxLa+f9vFc1G8HqvI/hMbTSyUawwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAqvDN+9gdQUr9yr5a/O5K5u967EetbCyMdvrf3s2Vl8KFcPKOVRuV0zN5Gvny4NlwAILLLDAAsuABRZYYIEFlgELLLDAAgssAxZYYIEFFlgGLLDAAgsssAxYYIH1P0+T358FX/s0+aoH2Z0/TXZM4ZjC+RdYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQXWpzfvuV2paTaC1Xlzr5JzqJpWX8iApWCpgqVgKViqYClYCpYqWAqWgqUKloKlYKmCpWApWKpgaX2fUJVDxRBlOpcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMxOjA1LTA1OjAwEJJQ4wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0hFLnN2Z6ItMoQAAAAASUVORK5CYII="},"43":{"admin":"China","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADC0lEQVR42u2cv0ocURSHL674L/gvr2AVLaIi6CsoadIYfANJuqRQ2yVgZSk+gIXaaCWChVgmD5AqKAqCIEgIEhESWMHfFAduZh135u7MznzNx7DizO7cb88599w7687P3oyNjMIi8KL29tNwvRyfxTGcELEgYuXBy+O5H0P7DCpitVqLzE1uDi9bjW4OP04PvL9e/LDz6g+DilipItNdbeuk55349+LbQZe7XVj93P+aQS3CVMO1J7qEOLPiU2P37IsbbzQul5yTXr9nN/71TpAWSx6xlKRCDPOvta/rfbVIrCcqel19n98enAonNMxZLMn08PPorrYivbKNgjqnNJJS2V4FFlQsm6qUnkJLLMkY1JKLJZlU/Shu5ZWe0K4kYtkkaGugvNoBUup+Zq/efapjSyToGLH8+ZoYOiE2r8Y0ZxQlGTPHQoil77cGqTlVSttYZdsBSc4gNdOkTv2vOlu6br6KI1aitqQfjf7zin3d/tW+4lEJNNukqbNJL53fpkUkKFAqtMklVpckepnjcI1N+5XQMeV8oWssWxS/OEp5ybGda4uwA4p3DVvUXIiLW96xUhIxA7ESRa/m9ZPVSwsyDABiJVu5i0uFHpVAWdFDrGdSYdQO9cSKKrCYequdqVDXotLqGLE0YHaGaHdHSTtN+P1efDt3UOla7NkqkFjNt3RpqOyaYFwXyu5EsCV86E07mljYzrvdDUE6LmjEUrKze6GSx4/Qc8O4hojtafHgV4j349I3HqPvfUtvRQMfenHaLuyIzEnZ856ZWNJXKkf1X4keEEWs3J7qKeszx4gVvJVAHx+xMlhUtglO5XmINUdiW+UiVtTm8J7SSb+XC1ZaLMUnK5ZtzNJhR6xU+ynEEA+ZweqJ9VT38NsNiJVbWwEiFkQsCBELIhZELH5kDGYlFjcXErEgYkHEghCxIGJBxIIQseic5XZ+xKLRilgIhFiQGosbARELIhZELG4EpTdiQcSCiAUhYsFqikXxCx0zLEgqhB2zAwKxIBELIhZELG4EExfEgogFq8xH2u1W8NlacbkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMxOjI1LTA1OjAwUrdXngAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0hOLnN2Z9X9A5UAAAAASUVORK5CYII="},"45":{"admin":"Cameroon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFQUlEQVR42u2dTUhUURTHZ6WbyIQaNb9txiIigjIigiKCFiER0SZrtNoFCRJFCG0KFxW1iBJdJCZSESR90M5N4EKC/IxIneyLJIwikgpslGDOCE/evDf3vnPvzPv4bw6PN/fd4N1f5/zPuec+Q6FQa2ss5nXb1r791M6VYxcKtkbXDw0VFtbWesY2FeyrrSc7/eJwvKpnYePYjXCe0Sbqhu6GG8XvW1nzeMuZx0aj4ajVtf2vdB0CWO4B6/2GIwerzsviYg+NNHwWoIgDRxZguQgvI1jiXkotiLJeymo8wHIdWJyQZz9SZB5ZgBAKPQCWlcaSBUVCS5lH2gY4ccgAlqs1lrMwx7f2MIl4NYDlUo1l5V34GaLVPKn7AhmfiJAHWK7WWM40E1/go9wQCz1trYxtNoLl3YAoW8figCICR7DFuwksf2gsVb7KCgtxpFBu8K3GylBzsr1OA4HVfYF8EJV3X9Wxspn98X0YPJZL9wqFypjiOSBDXXFGAizXaSz+NrN42RPiPaAaK4N+0rBFA/FuCZZ3/ZYqjQWPBY/lsI7F78RSfB97hf6oY8lqLLX+T6RsAbA8Vsfib1Gr6ke113MAKxgaS1soDFK5gYKLZzWWuE/KfpepeNaJrNADhynUts04g8++zQYF0sB1kKoCSHZrCGC5tPKezd5RTk89skKWHekuvhO9TFaxhmOcK9Qn6vkFWIAltPDvGuoT1UVk9YElfq5QY++obOhE5d2ZpTm/Xb2ytixlc3uuULyaxZ+BgxeywgyLPT4Y/Ro5/Wfb89biFrLj3dEVkYs6/JYbzhVKQwmPlSEYWVxT+Evkv3obbia7LCCardK2GT4iCipV9jkmwLJfVCs7u+rS57KehUevn6yZoFdGd0SeFUJN6blC/uaPs5EQ70vKKVqRiMxPfd4/Xd1LHijef6Cl+qjRUo5G4c/4QukO/Wp+iizNTP+K2nOF/M1m87Mc/wePlaZ88KnhZF/lib+z/QNFcWOwW2YTo2PhcBprWB6jpdlo5lR5Qtu5Qn1lBX5uGNRQaAhSk3m7P9Ts+dV/b7HkocT/+CReqfvJQEkz0Gyq9gr5nyvS8X0H83swW4j3pewvmeuRfvq392Vn+LEIZPMDg9fCcXqK8kd9e4Vuq8KnxgMsETt8f3VHdHLmzNmu8p8iL5RGKuiqEN6EdhtwAQKLhVdygWemzh0vH8nwipPhj0bq2IRW9pEPyfvSdSx4LPFsca7yQUdJm7HEQCGPrPEV00iJ7M+RxuLU2ZVpKXgsztKS9E4BlATrd9ez/OJeWnKydId+pZETm3btqOnkF0ideazcdjoALKGlNaqr73M3F0sb3hza0rTuunEM3aFfaSQ9pVtjudMCrAyLSuHsx63bw6VlX+qa2yuOpWmSMeBF42kkQcYKiIxQKKulZD80AvHuVLxTuaGo5mOkLxXUJLGgp2gGTkB0VsdSK/algYbHkt6Q1v2UojqWvuNcCIWB6HnPbR0LYHkeLIdaR5uWyjAeYHnx+BenC1T79xqgsfx0rhChEGB5so4l7bd8C5ZPP8etWzOhbSbQ4p2zl2e/8LotwPKMxlKrgQAWNBbAAlg6NZazPiqABbBYGktWXAOsgGosZUdSARY8lu46FsBCKGxU1dueurY/IwmwggyWvdcxAgSPBbCk61j2YEFj4Q9hOuzHEgcr++EPe4Ve3iu0XU54LIRCa43F10mGGQAWwFrusUx4pQltFsEOHgtgSdexrPxZrgACWD4JhW7L/gCWl/9eocugCShYXs8K3aOTAJa/wMpp/UmV/Q9UzC80rC9rUwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MzI6MzktMDU6MDCyioZ3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DTVIuc3ZnICDopQAAACB0RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgQ2FtZXJvb26Jr9hlAAAAAElFTkSuQmCC"},"46":{"admin":"Democratic Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGgklEQVR42u1dXYhVVRQ+pFD0kL1I2sSoIwSBCkEDPkX4EDOIBVr0A0FFBUUUWUERNAzGUCkSDSkNFf0YvqRDZtFEUkyp1MAVBmHGQRLtpWCgFwmloqCvh3057T3rnL3W/rvrZTGce+ecvc/9zvrW+tba+1TVFSPP//2XWtgrV46OXn5Q70Pd3nvgvZcPrZ7fO3Ni8IVL03OPXPdY3f72xKF9qy8tTA1P9o9V+pOotdmbqze+m90wMf755aefWtwxe2zgjzqYLk6evHrVqZ+Hn73qhg9m59fvX3tr58Vrnlt3S6W3T32haVcuG/ts8b6dfQePvDXk9k+/nnjzob7BuYHNd63Z1pm/dsu6NYCUAksB2nXOrZ9ObJwaPDYyfdsdxylkVweTAkstA9l1WYDMsIGAddOevX1nBmD158yS7JwwigYsTANWiSmWpZPd2R93fNy/oovs3GCqfacKc7O+OP3N2buXw6rPyIDsCNCJ7LEwsfM7O4sb18PiiAbgYcju3Gznnk2fSJBdY4/Fe4MwPXMyj585cPTdZepLkiM7G6Tqxwngq6Sfy8Orvj7+wCZzYkqIuWR2PraSzgRBf+YklRB5UyJmsvP51PiOILAe/fKjDe9fsD09+FSjJcr5TYuaHUNmxxRLicRYbnvw4lcTDy/aJg+KVK9DARZ8f0uy84GRHBWawqZp1w69/tP5ju04Qki3i8anW67fd2r6RtvZbNcCjeJ4L8iY9XDCBBOJ7Hj9E8ELVu6JvXLy8NCuGeizpmQAWMDajtggZd6U+v/azmz+jVGV6p8EZUyfyIlgAe5fntz9fd9oRQ8SKXCRs7h6qVIFQ2YnEYY7y8ywGAlGhRFitA1irM3D43fO3A+xICSk8Ozi6iWF5ww1OzGxYAlg/Xsc/hK+sz7mxsE7IpvXbp+cG/nQ9my1sybecWZcBT+AypgtIyefeKsGJoAbQHdHz15Z4fYf3nn7yLjtaTOBQrc4G5JqJbsGnonXbxHIzrSY0f7fj/75zIX/FEouIbSusJuXpEAK8kQZrTUgUOaaHRfxkQHqJjvYqVe/Xdj+EnxwV9jA22zvQ45leCncXPhy5pqdHOXVMjs32YFV8MBYAxWuGwon7xNj4Qy5kx3owCuzCy5m0skOv5GtHMfssRDOu/MaenSVl+xJyewayJjS+lPDzM7MykF29AIUA7Dg9t2gMWVP9zcpE4hLc14yZixrQMqUMb3Izm39bzecv22I0L2gQlGUMJyt2JqdR1nXRwhtmtkxpFASJIihQ4Wy0Qc+rU8SZ0tHu2KTMSU8kO0IV2YXC1gYivns4taDHH2UsBQIMUJmx1QwZsvsYgHLJEEfFQoUaSphNm+XZc1OTsBsRXaYnbhe6EMQeJp5F3XhbIjDwhAinewQ8NoWlYuH3h4yJj2ziwwsRFdyhWGzTyu5mh2XUEnXrmoRWyyyaxCB6dKDlpldGBmzVc0uENkpsOg1uy6y4+rG9A7Go2V2YYCV4w5YXpldmA5MCyEmkdmpxxIhO17Kc3crxJIxFVj+ZPc/NTsJuLRqwcuS7MoGVuSanUcZOHuyKw9Y2ciYHjW7JDK7XgAWw96YXOtVxDI7U8aMS3YMVy88swvZNFe7VqAGFfVYhSw9aLXOLsvMrgxg0bsxrTW7MIF5q3V2qZFdTwCrKdmRGuikl256yJilQmqJnvcw02bbG7Pd0k0PmaCHMruUPZYJU3QrNF564LPql5UQKWSHhp/yyK7xXJLeQSUBm7KMmTRwC5cx6X7O+CbXOrue3oM+s3V2EluKpd+NmSMEM2hQ4VWkkunGLNz6oJWZ7KSreP47qChc5DwWczdmkpldeWSXNLCCbhfGtR1PD5BdorlhtMxOuMyiNbtYULZuCiLYoBKks6CpjKlAEfRY4jKmxPIEzexSpk627cL863c9XLPLvq2vbt27twfdQYVs88rs5ECTdEmncWbH+1KNVm89kCC7wrujYgGrcTdmkG0wKGRn7l+qmV1CdgkZM+Q+TypjlmQF95UTrtnpj5czsBJYZ2fuTVp2za6oOC9Wfqdkp1TI1sRikp3NP6UpY9Z9Sb7ehVSQiRxj6dIDJb7GwKJ7JqZ1dgqIokbl1SHu8T47rdkV7oP9l5Yr2anlkBtUxkw/cE6BarN864Ha7IN3XXqgVoIKyyM77WIIdGfcDSq21j8lO7Wkko5mdiX5uSRGuzA1PNk/5iY7+KcyXgauNgxY/wHtNammNY8UKQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6MDktMDU6MDArJxFdAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0Quc3ZngkgrjAAAAABJRU5ErkJggg=="},"47":{"admin":"Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrklEQVR42u3dv0tbYRTG8Qt1dCyCIBgkiKijtEgNhEaMOvUfEPwLSku3IrVbXRwUhWopLg4OLgpZBAlWEEUo0qEFKZrNoYg/IKW0JTi8S6D1F7nvveec97uc1SEfn/PkwL2Jos75xUKBme7sqC2NDfZsH3x+O7z+59fR4eiIxlltKo3ny6ezU1uPXkR8qBJIra3vPR7u0UvKTUfqqLv3YeY3sFKbLc8+rBa+WCL1/TDb2t7mJrBSTqm/ucrL0RVLpIBFSnkhBSxIxdClgKWelISleXNKAYuU8kgKWBwRYlt8wEoopVZGdl4XT5JHEO+6vG9KAYvF55EUsIyklITFByxSymNKAYuU8pJSwIKUR1LAgpQXUsAKiFT9GcI3KWCJIHXfy1Mjl6pkSAHLbEo5fMmnFLCCIOXm2fu54/5ckqSAZbaeO1LJpxSw+MYHrPBINVLP01p8wPoPqYXdT/niJEcEYMVGamai/LVY0E5KTkoFDYsu1ci8+9+KICUzh7R0qaBhsfiART1XnFJBwNKeUnpJmYVFSgELUgZJmYLF4gMWpG4hdTzd96rjh15S6mGRUsCiS107z6sfm59kLJFSDMtSPbex+BTD4hsfsEip4BafMliW6nk4pBTAghSw6FLXktJVz+P6B4hIKe0pJTMLI7oUKWUQliP1bn/zwVCNlAIWpEgpqbAgBSy6FKRkw9JOyj3UACkRsCx1qcuny98GTiElAhaLD1iQIqVkw4IUM6JLQUooLE6dTC+wbial5TePraaUyldFak8ph14yqeAeWPVHynfC1b9R2JGq1HJd2QuWV8qw9KbUv6ToUiJgsfiYMcOCFDNmWG9+bjwf2rRBii4lApalUyekRLzclnrOjC2x3PVc++IjpcTBskSKlBIBy0aXqjaVxvNlnpCRM68Am91UDENA4fcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjM4OjE4LTA1OjAwQfoadwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ09HLnN2Z8XoUVwAAAAASUVORK5CYII="},"58":{"admin":"Czech Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADnklEQVR42u2cS0hUURjHhxZZJpVUlho1NggtUrKCUCsYUiFqelGLaBXE3UiCLuyJpKRSoK3sBQpGrSx6LCxIJCGhRRBEohUKQZE0RdEDLStbnM2F68iM9zvX+/ht/sxi5g733h+/853vnHtDRkbznVutA/kjDaP9k2OTvyYnSNJ+hhZ2lp2o21WaZeS2lXcN9Ba/OPKx42v85w8uDWkLrMw15VvrqhVeubFY2bn0qlOtFffCOIwUAMuMl9VhXCbSFlhWyNRn5bCRrPdnP3dwyUhbYFkhw2GkMFjWpA4jtYClEoeRWsCyziWpw8gp2g32IcNhpICxknHY24bRdV9OcqEBSzhxGGBpSRwGWA7l9olji64tv7uk7/zL29wAwNLisOPdbWe683BYoDvvOvAyzyVxWKCNpQ8yHObz3Q3Jm0bfQKnqsIeXnxa92slNCpCx7ICV/G9xWIDAUjfb+WIfh/lko5/zs8VUHfbh5qfxb8+4hYClpR+GwzwAlvKBFFiJjiO14K1ydfbeLU3ZOIwGKQ5jz7tTA6KsI5XD6v+0Nz66z6NsGAuH0cfyToGPwzxmLDNezne27AygO8aqe9oP4jCGwoRAzwwsHOaZhylmd+ON/aPhME8ay9wbc3Nllle/f2Wz0XSps6R3Nw4Te9uMmwcv51M5rK/zeXSkCERmbSj0Ci7O12FOmi+Z/3LyOyF3ekj3slKqvz392njTeCM+fGFuSwaZTIakqig/GUudUTg/2lL7rmZ+wcZYzZOCFV2Rx4ODS+fkpJHJpM877zPLzbWl/ZW516/mRTf9BRHAsjVc4ictYE1fi0htdHFbP0xldEGxYZThJ2Gw/D2zS3Re+MlRsOyU4W5D03pe6jN+cl2N5UWrmed3Vb8L0/Ysxk8BKt71PUym/HQlEhkqqeBmMysUmN/hJ8AS7j/hJxcV77JlspMwKT9VLiv8t28efvKhsZwp7c3/Qn88QGBZ8ZICzuxC/ESNRf0EWG6d3+GnQICl+81YKvETxqqWrZ+Orlr/4EBLkP3kj3MPuaHbjp8wlvCOcuWnnsyc72vD3AzfgkX9RGrZ6Gd/Mdh6HOqnQINl/7FSK1jqmPiJoVD4jciq/0T9xFA4xUJyqm9+V366OJ4/vM3g4gKWwMog8zsyZbCmr5/wE5kCWIn2Kaj1u8OlG+KH0vETOUOwqJ9IYbDMDQj8RIq1G/ATKdYgVZ/xEyn2MAV+IoXBUo904idSNv8DQU9eyPkl1PwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjQxOjI3LTA1OjAw7BNTNAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1pFLnN2Z+wzkRQAAAAqdEVYdHN2ZzpkZXNjcmlwdGlvbgBGbGFnIG9mIHRoZSBDemVjaCBSZXB1YmxpY9YC5UcAAAAASUVORK5CYII="},"59":{"admin":"Germany","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA60lEQVR42u3VMQ4BQRiG4f8Y4hqO5ULOoHcC/UYrIqJwAoVCMS5gWPyTjM3zFk9lN0w+uxGSJEmSJEmSJEmSJEmSJEmS9HvzNZlvbBdkvnFake897p9b+3z1gtcXj7lqvFn3//QO4w+u3e/97tp+zqdm8pFNzxbDmtL3qQ3RdNhER0DD8pI1LHpikYblRfMn5+OI/QHaD6v1j+z/ED3nPLFoWDQs0rBoWDQs0rDY67DOt9mBzDbul82VzDZK2S3JbKOUYSCzdQQ0LBoWDctB0LBoWDQs0rBoWDQs0rBoWDQs0rBoWDQs0rDYsQ+EqqV97vWrJgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6NDE6NDEtMDU6MDBJrG+JAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ERVUuc3Znu/SIVgAAAB90RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgR2VybWFuecjsIlEAAAAASUVORK5CYII="},"62":{"admin":"Denmark","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABMEAIAAABE71kbAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAByUlEQVR42u3bMUoDURQF0FGIomQRLkBRyAKs0ggJrkBEbIRgFV2DaCq1tAyptLOxE6xDUtrYiCJoGsFCFBnB32g7Zob55LzilpMQDnd+8ibJcFit1mpRZnOutZK+1E9ve/Nptpn+uP+8vPtaX2xth6tF/GmULBOwAqxBuzK1tAYEWBoLLLDAAgsLsMACy+EdLI0lwdJYYGkssCaxsd5nL5YPgAALLLDcCsFyeMcCLI0FFlhggSXBAgssh3ewNJYES2OBpbHAstKRYIEFllshWA7vEiyNBRZYYIElwcoKa6sxs3PtjAWWxooBVvj9Jsr86Zjnp+NRd3dcjVXMOw+Ii3zF4jN53Nzf6OzFmA+v7YXDk7fmzVF/Nf3HjAZn/fOrcLWQ8X4m5ckkNSaHAcuAZcAyYBkDlgHLgGUMWAYsM7mwrHR+r3SsYsa20rGE/rOEDg/PhCVxDu82lyuXcwntsRmPzXgeCyywPEEKlsbSWGD5JzRYGgsssJyxwNJYYGkssDSWxgILLLD83AAWWECA5VYIlm+FYGksLMDSWGBpLLDAkmCBBZYzFlgaS4IFFlhWOmCBJcFyKwTLt0KwNJYES2OBpbHAAktmy2/hVcWm46JcXgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDM6NTEtMDU6MDCnkUmcAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ETksuc3ZnDuNRdgAAAABJRU5ErkJggg=="},"64":{"admin":"Algeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEqklEQVR42u2dTUgVURzFJ0NxIREYRlD2hSGJPWEetbCFFc8+rDSN0lAiTVpkipliEoQgtWilhdiikIJKS4XQUoO0xMgIUwsz0/xANPArI4qyNOi0uHCdYZ7vKbyZsznIPHU2P8793/P/3/sUxTv5uBpC1dfA2mxHVPXAnvHAkbOz07Njs5NUfVUIDcEiWASLYBEsgkV0CBbBIlgEy3Rg9c0MJw/UERqCRcciWASLYBEsokOwCBbBIlgEi7tCKsGiYxEsgkWwCBaVYDmnfrakQTUi4ETCM/Xg1lP736ohMZkJfg6f3oKqwZr+b1PNjtbVPyN74gdqf1dMJE/5EiOCNYcCIEdd5GN1Q/4h24TacTd4fZD6p6XEv8weLGrHzPIm+xto50DQrchLPT0Rz5PeD1VleBd8/FJZWVrfPd02kjcaQ7AUK8OUciM8Ty1/EL92pd0HuLzzX5aq7hZVhElW+Xe6J8PD4jvHthS33LlpZcgsBxacCZ6kBYf+c9m3oOJftSf7ZoTWA7KprIdtT9MJlmlrJviTvKgZcaOa3KDC7Ut6T2ceKUibbC07+SgXldaPtI5zHw5A8QSfDl/LSbyyA2ABu88VBeuKm61TkylWQCon375RbTK+nEFLyje9VsfhcLZf6bFR953dFQIj1F59WXG2M9sAnBXwUqyAlPFqCX4Gb8N/cFfcAJhQe0EJlkdqnG2nl1oiL3wyZHjyxGtVoj0A4cJC51hYNKEEy2M0OCD2q5oCUIzUUvpILVxAij2jWZdFE4IlV1RaSyH8DN62EMk7oLFmiGoqsOA6wMVIXXV5c+g9tVOspdwL1sTS0uxKxg1m9CotxwJ8WDQXolf4/fyrFR0pXZ/C9kZfRQBBsDx4D4gMXas8F3+GVznbhEavUD9Px8LX+3JfdGoF3oW4gWCZZBHUXwqRTjkLVnvR9b7SBrE/ODVZc7ixUERt5MXFo0XV4tsJlseHC/oZlRgroFc4vwlSICWig4S9v/nYrqxwOeAgWB5fXclLnuxb6BIaKdi1aiz4E3xL7AxqAY1mDsZsCJaHKcZdjLST0ahxfdAP8Sb6gFpgiYpCfnA09faFIZTz5p7oMglYKMZlx5LxcgUs+TAFQDESwwI+EUEU+GZdKE27FGr5lrP7QSOOJb5Xy720HItLoceApVVpQd1bYwEj/X0oaiyrDf2ZfFcoQ4ZdofFoVGtXKDoT3EjcFTJuUKzZzHE9x8JkFdo1ohvBnwiWYqYZdnF6XSt0cCV57ypuWNOYYiR5x1gfk/cQK5Tw4vP59QqNH7Fnr1Ax3ySW8an2RZpu+Nf2IVgmCUuNgMV5LILFCVKC5QkBhFyHceadYLlhTFmr4aN/SseV+7F4Ssfk5wqNTGuJXUXXzxUi6+K5QkuchNaPJORPxZPQ2OvJJ6ERMfAktEXvbkAVpX93g1asavzuBuj/uxsYN1j5tpn53TMjD/rxthnejzXH/VioxuBn2Cdqxa28H4tgzbMmQx4m3uiHXiFqKd7oR7B4BynBIlgEi2BRCRa/mYJg0bEIFsGiEiyCRbAIFsEiWFSCxV0hwaJjESyCRSVYBItgESyCRbCoBItgLYb+BfM3DsS1ovzOAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NDozMC0wNTowMCVVUNYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0RaQS5zdmfcFAlaAAAAH3RFWHRzdmc6ZGVzY3JpcHRpb24ARmxhZyBvZiBBbGdlcmlho8plBgAAAABJRU5ErkJggg=="},"66":{"admin":"Egypt","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADJUlEQVR42u2aPUhVYRiAz5ItFhTRUARCYEVL0w2CApeGIlwKCqIlpCFBSppqCLKoiAoiQhTRyCT7gZKgGqIfbZASSQzCLIzsh4uSgQkut+G5wyunazdwO8/ycHi/Hzifj+/7ne+7ycDgstXVm6RcWCYugVQsqVhSsVwIqVhSsaRiSalYUrGkYkmpWFKxpGJJqVhSsaRiSalYUrGkYkmpWFKxZKbEmrx4e9eDF7IUx1e0VFw4AL+/7WhsOeOalMOkMFuYKeRlmrMnfp//sX+s9/W60zdHtj4/cvjU1w1D09caibs+86+bYv2DMy+nxj52/6r+1t83ESOuzPxUrLL+/xBr+uREYfi4YinWf6iDLukyR3xy9HPTk7UQvcqfQbEyRxSZSD4d6qlEiKgIrVGpn7vHq55diXpFmWA+NzJ4q8mslmmxUGpgcfee3KLR4b76Y70x3/CMQJRCmJaGCDMwGzMrVkZfnuyCCkO1PQd3NiAHcciXIHpBIrEPo5iB2YgrlmIVxXp39FHv3urBrnsfah6+yrVOrjxHa8xhRGilJ6MUS7GKJPekxeK5lFj9d25cWn+f1vQo+jOzYinWHLEgcRi35zEe+yuWYhXJqXopsYjAdCmMrWmxmFmxFOsvYpF16BO/Ftmq0/r+7tPauuWKpVhzGA8IoljcDHJexcFBPHRgY86ZVimxmFmxMvryCJQubcTjASkCwXhASiSOVSzFyqNFFIsIZY78RMbiSxASoTUWxCgWEcVSrGKOocDFK2fUiWLFI9N4Ih8LK8VRsTL68vz50Sve/cWbwagLYrExRyZ6MgpSRhXLc6zGeBUN01c6yASJpC92pgpf3jzuQi/PsfzZTJ7MhFI8owi6xJ1W3F3FnrGA8uyqKlaechZlirkKmdJ9ECjmrVgcXVXFmvMbUYhAMF0cY+GLz4z1h35FsTprOrd1bpbw+o72jc1bOra31V9d1b6mteFyXVtVc8XZfWnSChnl6kUmSWWyJFkq5QLTJZCKJRVLKpYLIRVLKpZULCkVSyqWVCwpFUsqllQsKRVLKpZULCkVSyqWVCwpFUsqlswS/wBhPLGpf7jDnwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDU6MDYtMDU6MDAnyAkxAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9FR1kuc3ZnlbvP6AAAAABJRU5ErkJggg=="},"67":{"admin":"Eritrea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGV0lEQVR42u2cT2hdRRTGLxQqIraQgqhgW/yTaJ5pmz8miqg0inRjRWmlXUgbBKspiFA0qRshQjQ8N6K0KEVBRetC2k3FgiihQlwYQottBJUqunioaKsVbSmNi8/FJ4cZzr0zc+/c92ZzuNx339ybN7/3ne+cmZds8b1Va4ev/+DykXcHp/cMjB3bMHPv/PRXfa21Z9/ualzsWvHh4UZj5eOHPm8sR8SZFFO0x+yXVcvOjXRz/HbfytHhnZ+dumluaH7f3vt7BsZ2PTN+Zn3rroXmVN9rErj0IaaoAssUoW164BJ2Caxul6hXuBQTWN2+gJs9f+PVA8dMwEHVVrx4eE/vP4hpShJYwRUuoZbA8hBPb71yzeibCbgEVqQKl7BLYDlFfZWaYgKrpKIhKVwC67/4a+uq6dvecFc4Cdzwfa88PdJMHq72YP3x4KbZ/jsQgcvvS4P9g9f+Of/IgQ3LcF6+6++xiZN9q02vhlA409JWgiY6sM41H31u/fiFE/t/7n0YoOD44uT7O2++cP66l65oHME1Z765c+PALqCGM7j+0kMff9rzbAi8UuO3BmD9dqjnsqFeTl5ABFgsLcy1um/AMWDCq9AtIAXUcCUiziCWiZeLh+tkhctCOCHoEPACKIwU69NfR57Ysm6SkyCOWc9wJSLO8DicTKFtdWmLJLByRGCBKQdYQAFg4TygAXAAAkgBMsZFqiAnRODFEOMYY8ZfpZqKhvbALtN8KPoPEbrCmgQUMNnAhaEBUuy0cCzBwhlGEBHjs8LhvHsVGU9KrWNizXylP0w5YGL/hGm2j8BaxSDiGGpkcnKMr/Rteb8YMQNXrw2Yma/2AasOJpgdkt3gs09i7WEFknjhXayO8r5IuJ3Z+K1W5zK/voqrOUytSW+AC6sagyIrR1Nqg2GX9SPeW62dr7ZosCtcaOAyX90pTCQiVMfucjh5AQUon2xJ6EdjvBA1ibi9gdOk1BCJ1ZtisVrYO0yyvmO8OAkiAlw7WFLtEKvqdSUPl/FeKHewNAmIe10yhcEVcUGgr+/kmAmsqhq/TooFCJBuGCyNZcY13PZkfeK+lMmlSVgxGmOKZ4uts9WuwHkDC3rAZlmTCu3jcEK023bp84AR14YYDZqaECmnaEDj14NimZZrTEAg7fKrGIdTGKdU1jC7eedKk91bUqxwW8m/+HL14rbjB2+9fWryxxe2bJ060Nr25MTo0YXMvTUqGwSIJhTwLqAALWFAOS1yy9SuW6x5nBBNffwUXQB6effm7a9eeuyap374ZP/m5c9PHn9g48TMju8O3tPfvOX0T4iZC1K89Cv1xtTSBCisK3yGITAdy4gnYe1EBLixdeFdSqVw8eu5rtFNS1KBJEB3720e/f51xgjnOTopFm++44pM09I0bYzBuwAlrsT49lTIBQQiJ8dOaJO6pzAGiKExocNRXuPNvEvjLKeTazfe9SArSl7M1qQzTn/cr+d9FjGrRVUAyRRmAsWOkXzVQ4OUkZJRVohIW+yiOPJorH/SgAMXXsCW6bgT+ljuAGk0KW/09r8beGFHTrDsbPHyM3svU4pk1eEWq7xjsZZHewPkK+rH9LbRDxMPmyw74Jp0xj6J6zsJpSwX+C51by7gi5oXoBAYRQEWb3rhik+/JMym3t6qMC0HaRoTscW8JlpOrd5c+4XPPlrAH1OwoZb1mkSHf41jampIpBgszXJ1VSls7p01s9sXY1MgiUix+8pnDv7zL7u55v1YcoeWfBe/F5Ah+cYAk72RyB0gjvqJzDvlZaKpUqwQjUToEww1p0uTAZd7QYGapqdVrYnmDxfouH/7w+FS7C6aZ6j4J/ZIc6ZKsFobrgco/li+elUMFm9rlquE5XTM9WV8DCkmZtRK9Vj6f/UR2ifF0AcKMcGhn7bYyFnqRNdFgVye06VJUcx7ZXUHqFgZH2JZw8Wqx++Z8j5J1h6NRLmdI9yUFAOoTODc+1K1N+/FFMjUB4rnY+0EU19B591FgeKxwzFPc7ULzMH3Y2kWU+0KJH1POYlG/7EWex6Xv6I9VDaLYUdiMfucd0nE7xTG9gXwm8hKBUu/J1qzJl8MrPKVoEyAQk+2r4KgIFjFFEh/47xTFW5qq1Id+32rSoXu25H/BxZ06KOZvqHxt+w/69H8NiOGGCcu1UJQ/nz9C9XYLU2nyDoEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToxNy0wNTowME0VAhsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VSSS5zdmemQMtCAAAAAElFTkSuQmCC"},"68":{"admin":"Spain","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFlklEQVR42u2cb2hVZRzHL6m0zN2x25y3ICUpsz9UUGkUVkiBK1PDP3OlAzFsZVi6YX+c9qIUq7EXjXm3OUGUuWlp7s+9Llxobk2d3HpRWGBFgUJSLyItwhcW+LkvfvJ0LltnXjxn3zcfHp577vPcnefD7/c7zzk7kf4DY09NnCKKw8uIToEosUSJJUosnQhRYokSS5RYoiixRIklSixRlFiixBIllihKLFFiiSNQrG/vfPzR8RsGzyNn5l+I3jHUb4lePP7rmsLr0sH6zTiQ/ZdH/irvGX3NmH9+6Hs7ErN0+72O/H+0o/n5DdnHudJ/Rfa/zmt2P79hMOfND88nP59WGPU/TuRKn2JxZFJiiRJLlFg5IBXGbxNTTaPv/XKg9WzRWC2nxPJFZPqlpnvGhHOtp+s3378Fohf9f1QcmlTUrAWWWJ4xCVGQxouNczZujZ47+dbupaMaoHsM42jJJVYMIRCFKNVZUZfMW3NmVkP9mJchPUSs9Im6hXlv0k+bEc7+uGvmqFull8TK7KYgDRtxnQdmnIil975asjp2EO4pKV4+obzlwgOnbpiaSFSdzK+iB9a3zK2KFrWPe6bytmOfPrSoIxpnHFVjEisjFupYjZCGNmx9rPTP63ttv23zXY6RWCNOLK99avRCFD8kOWrJR5BYVFEIRD3k6oUWpDxLynbbtj2Q8V1x6Wfk3NzqkVg5jVIkqdXN5cvit3yc/9L2vE32npol0nAkrCu4b6BwMbRJEKVslLLqcCnAkYyD1hIiJGLZaz2ksQucue67VLZD1EEjKifabR3PPT/naGvBjlTN2q5E89SWYtoD3yS/25iwZH8r+7xiSFKh1wITb+y1no1Ptv+T9Wsb3tjdV9N1KBU52Nj+RdcL6NVes2nl69W0+ZTtBlcsFfUhFCu9YH/PvIUIQdsVC5lsEiSRWbEQqHfpe0d3Xjx+Y+X5phh0xeKqkxiWmff3XXvv6ZcQoRLrq+6+JdueOrK9d92xi1Ysdp7svhSVk5dYKJKau2HJhyUoRZt+xre3erzmFUNSY/UWV3ZM+Qg5aPOMInWVjVil6dnRZ8ch1vKvp137RNwVC9q4RQ8Ri/FRNpV6sXN65rtELF0bBl4sthVQh/1xCnArlt3etEnQjVgrbrrrp5lbbUK0qRClIHMxPmJBdvPpl14BFovFQx2E+KzqcKT/g7buLU+XjUc7mwqJWESp2vjDZU++b2PYpL9L36lt5FPSHwnOVlf0UGNxvWk/RWtu/kiLwKdCBKKIZoGRBqVYfrvFYEmUmv5aedOqmyGSMUJy8ub82qQlV4iIRWSyCbT/+23zFv1MapYWgY9YqEMyanzw7p7bT5McEY6dcbZPLUmC6GXFgvQTeywZ+bInIy71IOL+spWJgsPuvr8YSLFYWtIQcYWrMz61YrHwtNmdp03cgihFtWSVss9HoBTjEymJW7b2khaBT4UsOQtMjWUv+61YViZXNftQDTLxKUQaK5a73UBtpxvVobpXOFSxaLuq2SjFjhdi2VRokx1iufOKIRGLReXunt0B9yOWV41ln3SgYHfnFUN1E9rS/geOVyqkh0J+Z8+KPYuXoY6Vya23ss8rIUIiFs+qV0xe1/bKvgxnVc+vjNNv7xh61ViU3lRI6GUfX7a01RXHM5el4lZIxPJaYCuWjTFIxrdIYRApabNfxRYGx7u7U+ur35296pHL5mUEZ14xwKkQ8sQBSXAwr6bgdjIbnpZ8N/sIzGgTbi5fGSKxAsbhelOKOILEUiwJ1pmMDFec8P/epsG86crrGPfp+Oxj+j+52Ufwembf//kZ6hnIzVu7/kOsoL9dTm/fuzqpd5CKermtKLFEiaUTIUosUWKJEksnQpRYosQSJZYoSixRYokSSxQl1tVN7vBLLKkgSiwxCPwXO5Dgx3YRLdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ2OjI5LTA1OjAweJLFpgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRVNQLnN2ZwDs7RQAAAAASUVORK5CYII="},"69":{"admin":"Estonia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABAEAIAAAAzLZlgAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA50lEQVR42u3ZQQqCUBhF4X8braNRK8hJG3BV5cxRO3Ed1U6MkAZORFEM3gvB7xy4k0TxcQZFURS36vmyNu2GI7DCssKywnIQoz1318/j7RyEZYVlhWWtsKyw7A5+oAjLCssKywrLQVhhWWHZPf9BJCwrLCssKywHYTOE1X+1nPuCOfx0es300+Vrctz/1/vkfq9/vm+O5665fs1z43A/1peTtWk3AAAAAAAAAAAAAAAAAAAAAADYKmVDpje6lkyvsCgsCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCxSWBQWhUUKi1v1C8pommxuYBvBAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NjozNS0wNTowMHOYr0wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VTVC5zdmf1bEvUAAAAAElFTkSuQmCC"},"70":{"admin":"Ethiopia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGNklEQVR42u2bf2iVVRjH33/c1OaoLM15F7amFLZ+CUtsLW+1aVaylUwZtDIvYcYEdS0dWVtBtCAnNmeSlitMsIZKgRWkNaxgYq2iouHuH+uPfgyDDSIDs6CPfzyX03k793o33/e9zz9fLuc973POe8/3fp/vec57vbz+9vU3xhVTcOTF+PUnstA/3TgRQk9pZCXE2NFiPAknxxrHcZVYwVj+yD2F9z8Du0wrhwX/wqpCINT3vBQrl6mTrWcP5nc4ZrPygsx6xfD+1L00pq7EUi0cE/Oem8TyLyWoluuu0AUnXtXaFN8/aWi992Dz5MHHko2xiw4/cqqlueDehrLnukFauEpP7lJdV2L9B40gzWWz7j71TnHh6UW1B7rcsfiXmp69i6e0LpuxI++G408vWvFZCtWUWGHf4rojZIIKkKngzapfD72SLqUkyghEZhQtN0R854KKZKZM9M9Mzxgx1zQsJ1LhxJef+mRxHyqSmTLV7Wjq2LwdzEzJGJ2ZKLHCr1KCUjYdcqFF29mtBQ/N2T3affSeLe4KZ7bkDr28aBvzktEHkt1HbHa7ccKzvY0n+WyjCBEg1pezPv7+2pJrapfftfsvfyMvI0uNTFGvSCfHyBLLxUvV1q+rbFsICWxaRfo7eF3P7ZVfQyxazJTKWEQjsov3iiyxGuL7niy5I0rIht/dA20sbv/80enVz69JvPCDqS6kv50bXhta+oFEk4KQiWjmPtGGzDZ6q+CdGRnsn5QfDfz5WP/AtCQUcbfVMtnN29/Q1JUv21EpSAPSIpMsyZEItuRrG/223xOnt3zDzKO0FpEi1ltv76uuas5s1wYd0RuSGilP0ggCSarRk7ugSLq7TiIwcyVWQNF0P3z2R9NRQZrO4V0Tan8kFcr+pEKu0jOxffPjGz51GcscV5YzlFiBw28rjvWWfmXu11Aa1MgFJVGO/3ZkYO5y6cBAUh4jYtX9Y86fvHL+tj+ZGy3mPGkhphIrQMiuzdwDsmBoDCkMukg02wcP9E29cphlRpnoI5GrZhyJH3rvttwyIkdH/5iVqVsHn+lpqexUYgUI28s6d9UXmUtFC4RDLXAz0AUtQdUksvwgtSizD0rWurGj8OEFEEX6MCITAQpK92abJ0+hxAoQkpJsxlm2Q6/e2Ptv3FyBokjTDY1QINM5yWhcJY4shEIpInPVZZfKvTyFEitAaCOBbQllikRRiIDHgli2CruMQE+sNxGIRmSZ8lxmpcQKmWL5axiEwAPh1UhbLtFIrHgyIhDNvUCqxAo0ulNB6gcpjFSI6gwlT7w0czo6JI9lzMgkOLwadxGBdvyWS6FBxucplFiBM+/+yiRLD9J6ozHQQiYyabqhIEiLvGomRNplqUImVhu91LwHr9zw70bdLDegRiaNsNhyj8aiktpk5R0bTgSuShrJurncmdLOKDaqmfV3LTcEukAq9QBimTSSfdAhdnCmV6NFli5l2pXeyNwhSpRUY1ZaIA39kY7LKSGLyiE05ECBZMqTSZCr9OQu2s23JGwOz3wzQo90QnwIbWoJRzTcCy3MKpSJshImS68kXPdthNxdKrFC8NqMy9KSniAHS0vJgL2eTKzytRn5Ug090Srq7CiZ+0vPzDaCr838lF+/bOrfUcKu1VV593W6vwslUxtuyXRjJrGkc+IuWQmznQaayGyjtwreQHVhW9GqkOHgpZcUXXEOLVdXnJl3dtNal3dH5Zmg7RRP/kvH1kce5rjU1ZhhX820xJxKp+cKFXopjyEfLJjtznj05IzisuEFa279oyNpehp5LG2jlGmupWLZehLN/3yw/PWK0W17mWEWvgH/nrYfZHbjGO3e+SxeFh57jMd9b1UsVl5Xen/88J7LM/y7qSCWTbHc4zATZnUBvpNxRC+yjycIzUJK9UqXEPLN0syIhUrlAqWiTixLcnTxXrZ3Gfz/UWgrJaR4qUj8UJVY1i9l63ezEzVrURH3NxHcy55EZpTc+elGkVgZOTlUhOVf+mr5obYppQvjH+0p8SeNWQnDORGBaKHXJyVWdhFC7Hzi6tlLZm76Yu7qlXWJ2E0Xr7uTdAbSwlV6ntvf6XeoxFJUYikqsRQVlViKSixFJZaiohJLUYmlGDL8B46CU/TMZu3QAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0Njo0Ni0wNTowMEi1vMgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VUSC5zdmdNeQHvAAAAAElFTkSuQmCC"},"71":{"admin":"Finland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA9EAIAAACEkYd/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABnklEQVR42u3cMUoDQRgG0G3EU4gKYmWj0cI1vUQECxsP4Als7EyKIAqCVoE0UYIHsEhpF7DwCBYWlvYiG4QV2cLGckdm2DfF1wYmj3+G5dvNyujX49zz6uvK/HAn73eybKvV69adrfVu+2p0P3y6LK2aVgYWWGCBBRZYYIEFVoNh7eZFfwkssAJNrB8EdcPaODx7AMtR2AULLBMLLJd3sMACCyywwALLAgsssMACCywLLLDAAgusqGF58g5WMhMLLLAchanA+sg/T2fjOLPYno2/7iYL07eXzdB9rPPJ7ft0UP1izHuSSmbt/eNidBFzrnWO9gbXYUj95vLiwclNGf9upJJZdcOIO0Nc2P+eWynsRioZ/A+TzUxbIMGSYEmwbIQES4IlwZISLAmWBEtKsCRYsqGw/qebEH+7QR+h1tTH0scK0sfSINUgDdIg1XmvUue9qS9T+AYpWKm9VwgWWCYWWCYWWCYWWGCBBRZYYIEFFlhggZUCLJ8xAgsssByFYIEFVtNhefIOlokFFlhggQUWWGCB5TkWWGCBFfv6BuluuD1YhrY6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzowMC0wNTowMEDt7DYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZJTi5zdmdMmf+XAAAAAElFTkSuQmCC"},"74":{"admin":"France","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3cMRLBQBSA4Zc9CgqcALXC4bThBA5gNFyFM+wBVNFqjYTszpfi1Sm++bOv2ETEdNK2pc/T9ny977rCn+fx8rjtc7dMmybnxXy9KncGWGCBBRZYYIEFFlhggQUWWGCBBRZYYIE1/GzSLB0CLLAUCyywwAILLLDAAgsssGyFlcAqnJRiKRZYYIEFFlhggQUWWGDZCsFSLLDAAgsssP4DazQowVIssMACy1ZYG6wP30qxwFIssMACCyywwOr/HAYWWIplKwQLLLDAAgssZyywwAILLLDAqh6We4VgKZatECywFAsssMACCyywwAILLLBshWCBpVhggQUWWGCBBRZYYIFlKwQLLMUCCyywwAILLLBG+T8ZsMBSLFshWIoFFlhg/fp8BhZYigUWWGB9C+t9ggUWWGD5FA44XxBz7mcwZM9VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODozMS0wNTowMJkeu+wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZSQS5zdmen2JoeAAAAAElFTkSuQmCC"},"79":{"admin":"United Kingdom","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG3ElEQVR42u1d34vVRRSfl5ICo+yhh8UH+0UIGW0E9WaQL4kk9BAk9lSICmFsRT1kgdCLmBghan/AIi0FrdSK4A+WjTZtt9BetNRda+mHES0iJKHhfgzP7Xxn7pk5Z+bO3b0vhy/f+/1+Z+acz5xz5syZc92Z5asH16/5c+MnY5+PXHvw6sS1vlh65s2pZ/74Z/Pwu/sPbL5rW//ke/3O3bfl7SVWdPHsiq+2TYBafXPd6YHbhp6jo7iy/cLYzIbvf3j00KrfJ4fu3rT8A06/3XfP5RUf8+tTvzy8+KnP8AX6zecfeuXq/ovOLZ1+60tbnsRyL/wMpAYJQpro/99DZ4enRn994v3RvYe/G1za99jaiT13LHvgG8oT3MGv5/tfWjuw49Ls+IXJRxx+xg9nH39h56Yjf702curI8TSQHXPjM+emIbYcILOg18WcBixOwVa8xYGFVvQgsJ1UFEzo4eiOEz9NXaJ8AJgwLjqF+DTzYcZxNoVfkFN0tz6QhYAF3SOBFGV3O2DZa6w0qMnBRPHANZMEG843RxtANnjwwNEP6wcZZzq/ozeFeo2VQw9Fg+n1Xfd+NCsZdSwG3A07Shrwsc/KXNagyTiwwAe5KZT4WFamMLdmyiFrd/zlk1/M3C5pGKyszVzGC+O6YYJbLddYPj9DprFKOO96MJ1ccv+dT+7WyxSIcnJV2VlNVsOqMFVjpQBLvppDK3IFoTdzskWbkT2u2fFvFVL6qtCnt0r6WLlXc2kyahqLx1ikaTJqLmv2ycLA4tGaMM0dx9Ks5hrCSQrNdLTv6/GpW2WyEHgkGnNpGyezAlkNGqvO1ZwdzyOUswBkQVVs6yRqgrFWAVLbONb8msAKd9LWJ9NsK8UwIiVA6hOkXGP5Jm0bB1zAw5y7JgojXih2IphtLZrMeIViqbF4/2NXhXozVymY0jRWGsjg7oUZx7cOcq4uU+JYelOo1+7dtp9bMHAXBhldUeYG2caD79wyPKDZK5QAC60AxGmhAYAJGShWYCqzleRaVZ/vOnwnfP8mxZDAaIkmo/CiINMzembdb4tmr+gj72Fg0VZoCgoNanBqFRo49OLYsz+uBLf/AxOXi0+OYSSE789RoDhM0TnJk+F3cQ2K7B8MngsYIDt/bMOiN1wDncv7mR7ZMrj1U43Ln9sUckih5y2UjOvnvVtPbH9VPyJwlepLuQQ1sqbU6UUyP6hmVehz3hcy7QHLYFUY1lgLFFhgR7dTmBvNF5BQm+a84y18oZ4RdZbzDkzBnKPXlPruS56JfZc/L/kCfz6tXb7v6ctz5zpM3vPwk5LrWB7Gfk3fTxc7O31RFv13NG1J+lCyn3L/rB5qyx83X5ml6SF/t8x4w63Y9iH3iFw+VtqKtjaGdsvUiu2n5PmwS+DVWJLXahB5edHWCco6J7DLh98c88MWfLm1ctr39RPbVjWEv+b71dU54zVPpjEi/J16tHUN3p6ESy63GeqUL1ISCiX9mHy6ylb7OtslaG3Px77r02ol12vdHmi4ASxNEFL+fCyVf18eDpUEDDWsTAtmygOSmpByyf6AzpMtHT2tbUun22lvEzo60a+3Cd3LbiiUj9VLm2kAlj6lKzbpDxRpaDzRL5YixxJJf76UQFCk2mE3PhZYaRmkaRSJfhgR8kh9CY9Ih+TtahL90hIA+a9Oll7sSziWP3kzNRkNayAFMIHptNIcX91A5BxStG5dvnwsngAdMca5xGskYdPTAHykvjE2HYEPJxnrE9PpdcHDFBowYR5TMIX1BxhNhU0ZjcRoqyP2PmChFX4yRz+RfHD3jb3paHzuGjgFS+qkzVoNQ33Hv+TnCiWBUMm5QsoTnJnRg4yfa2qYYMxc4lhs/tJRRpUwzcAUZBwv0MgZ1+5UtE2pSInG8hUFoS4BPa1Ux8SrQGOV1ExyM1f+iH27ajMp5y41PEzTZNYgWxBg6mxREGmdQTNzyXjbBmQK3vrtWDVgaljpsFkVU5+pHLBy1MfK4ZOJQGbmkxUEE8p++GrOxKxoOv8HArYaS+6/2vpkvKCBnZXIDCafvW8BU3Z7/3/hlXHecxS3LSmjMMjaaLIyqzmJmYsFU2pxi5QapPr6WDmKb+c2lzzUEqHJaLEvW5+J5jbl0ExUMLEhkrL1sUpUrq8tTua6f1mborHK18cq+S86ZVaXYZ/MaRrg+Za1gqk7apDm+wOYHJoszA0n35uL1Uxpq7n8ZcHa+1i+kdZQ573OgDbnmNNv9NId9RyhgRwCkGgseSZ7zRord3KAz/F3SKhFxlJYMyEKxVcEp58+t+fiKLJ/ypu5tP/SkYQb5OdwcgMrdoyxz0NqkCBPKIrNQEGqz7/AbUQccgW+ogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDY6MDQtMDU6MDBbYKMbAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HQlIuc3ZnJTl+YwAAAABJRU5ErkJggg=="},"80":{"admin":"Georgia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC50lEQVR42u2dsa3CMBRFPQAD0FJRUbNCZmACSlpmQKJBrEBHS0+HmCETsAJFfnGFZP3gyE5iEsenuULBMfblxEls6z1TVVVZlXnrptgUVWVKU/ahqi13Vw0WABZgARZgARZgARZgARZgAZZT79f71a2ANQVXzdB/5Hw2n30+b+/bO2C11f1uvxuPq2ZoC2w9LA9LwGqrcq/eHrmdBVjn0/n0zYLL8/IErLYq9+rtkduTAutVvIoQC27v2zuknjzBkht1T+Sez+XqqiEBsNTo4lgcq2q9WC8+dut+7/oj7TL2ufocz4hUwJIDqj/UVd0Q7XPjuhoVLHWg+1+lehixpH25unqsHoC1YMTq21WBleStUE23X4BtrXfVVQawYria5IhlP6TroVLTdOqG6+Fdx1VG5XVu7LfFtG6FabhqxvRizHTDdFw1Y5rKG2bGZSpguWYHh5l2HgQsvR6PZY54iks6tma0pKNrSB2252AYsbqPWHVXMxqx2DbDthnAAizAAizAAizAAizAAizAGg9Y9f3R9nKBj/qXD63Z/yy7jKt8/biOaANJX2CpNp/2d+mXPrt65Doerz3/y5hvi5f2keZvx1DeVSa05njq0x7/fvnU6fNbcX028W1Fc1TAQgELBSwUsDACBSwUsFDAwggUsFDAQgELRSOCleK6Ybs1wd+sEnZpj79X7dYT+/r172WCdjeErpCHrqK7Vuab9yY0/6JPbWPY3dCuTPN+hL78abMDgv1Y7Mdiox9gARZgARZgARZgARZgARZgEbuB2A1EmwEsos0QH4v4WET0Sx0s/zjvCYNlR8UkBmmMy1Lq46pGMperSYbjJmpy7HDc2UVNJoHA+BMIxL1cSSCQbQKBhMHqkkvnd1lfUgMrDVd/kP3LP09VPfvXL/JUkf2LfIWA1WISZ4L5CkMnSMmwOh1XB1/SISd0bFeHaQ9Z7Euy2LNtBrDYNgNYgAVYgAVYgAVYgAVYgAVY/eoffW/ASfIPUTIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjUwOjI5LTA1OjAwtAJqIQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR0VPLnN2Z6BMHegAAAAASUVORK5CYII="},"86":{"admin":"Equatorial Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE9UlEQVR42u2cf0jUZxzHD4P9tUJbSRSMFbMfJmjJwGCFOpijSBnsj5bYoA2LQiHnVMhK2KitwqGJcRs1BurKKwlbksj164+sy4pbadClblhaCRaNWgw2R7z3xyeevsd59zx3z3VvhBdfnvv6eD738vO8v5/vF10LC+sH1u5aVfDDT65kktRFV0rdtj1dX2d6G2qyfudykNrESiquvO2flZy+Y4f7VI7vYMZbS7gopDaxwHlF33xfWcVFITWL5Zr95dZrXqYuUoNYkEnqxdRFahNLkqmLNCKWJFIX9SI1i8XURRoRS6auZUONvvTTXDJSW8UCmbpII2LJ1MWFIzWLxdRFGhSLqYs0IhZTF2lQLKYu0qBYMnXlHvxxzOXispKu4B2s8FIX9SK1VSyZurispGaxmLpIg2LFY68L23fwTVyeE8r5FCuidBV5r0t+PKF8VKF//JGIpc4Qiljh/dzwzpnqeHjrE956GqxYYNr4dynH/+jJv/NG68J4pGf8VlbX4fh9/7GicbHA2pzTqWP+yfbJY5MemznR8ec/T/bKkYeb7h3zF2IcxIj9v0tsqa3dEHyGGRXVjVcnPZn+LY9O2qaR5HDdbxv2+UEIdOeL8zVZz+U4RvAqRgab+tLKS9XZKJY3OlxU8e2ugYxbT0czJ0ps+OVHCgJNne9BFBwPfna5csN6iDK6eXTdaJ6UTMqEV3EmKGcb9vS7PMspljeaXPvJoY2DtTb8Td+dH6jr+7/e4BgMdAcuBtZAneDEmaCcDZJRLG/0aVvqgijqRobjM7OH2n0NoPongRHoxXQVY7FsS10QSx13T/e3dFyYkeFOza/PfPLLByXv4joRlJI5zUCxIornUz0fx/akLtQbKQrUSVrdXL7iTSduWtrj2fMh3j/FeoVYupqi8Zu6pBYQBfUJAuEuAuqWk14Uy4qt0LbUBS0uld7ruDFUfKhtb9nbcvvDSPPS8s6iudAIIzjGd1GsMMVSq5quOod5Ypu65LUeREFUx/GFBUcCB9pAp1YqxbKoYsn/GQFiW7QnvIOX6rv+PloEOjVaKZZ1W6E9Fcsp51GsOBYrmhlL7TwF70KpW2HwmqdeYyYO8VtbIVb0rwqlWLLeOL0HxPa2X1vcTUmhNCyCz8aK9drePZRiSTqdD7FAJ10ww+2ZN3299xO5YsW4jxXbRIWPvLe6t6q3Sl4PqirgDmBz++d52a2gqqCsUiBmplgJ17VSK5a8qSwVwRMNEEUVUf0ueSbF0hbA4+vpBgiB5NTW8uJLagGq2yWonokZ5Dy8KkzQ57Fk3eqc9uILWuBYiqVWKSeZ2HSIqlh2PkEavP8uKQVSt04+LxrVR5Nj1aPSW8/UB/rYSohBxlKZlbxx3ZGxkZ9b/zq64lnKicfHu3XRxJzxS9tWw6BYUKrLt6z7/eaRB/PT57xjM4fzF/jmXXEad3o1vDkTgUbEQqJqeLSq59OBxFxW0kh4L91duLO2n4tLsbQptbKsZPvhOYm8BZAviRX5NSD+U0O8JCrS6orFREUa3AqZqEgNYsmrPyYqUrNYTFSkNrGYqEgNYjldJyJRcfsjtVUsJKrr44s/XjyTC0dqEIuJijQi1v5zue6Sf7lYpDaxmKhIbWLJHhUTFalBLPaoSIM3odmjIjWLxbt+pDax5GPETFSkNrGQqE58lH027ysuB6lNLCYq0gT/A0LeSt+yXmjJAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MjoyNS0wNTowMHdX0GgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dOUS5zdmcVW8TIAAAAAElFTkSuQmCC"},"87":{"admin":"Greece","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADDklEQVR42u2dP0gcQRSHp0sjCMbC1sJOSJ10llYBKxGxESxFMGWaWAhKCGohEiS1IiGIkAMLAwZMCAEFMUElJCioKGoEJWBzFq+5sNzydndm72bmaz6WcXz3buZ3b3fe/FnTsjnU/WHeLnuvJn9/3r6eu2u7n6luVn9UL9Ip9V14AhtF405YGkmJ+BAWwrIcsaRO39Lr91urdAbCsnwrdC2sjrORnrW3dHZ0EYtbIcJCWNC2sPQ3FEaFMNiIJT+D6Terv/a7595Vnh3+TDL9r+UzJH9MqHmsrkejfZXF3dmjsZvnGh+gXQabxxJhnfZfL/zbp5uDElZj81hErKiF5S6PRcQKUFhPP75s3xgnYiGsYB/eiVgNE5ZEF7sc/rbw+Pug3gmpn++znpgXt+tnyXIR66fZvc7zNYlbSe6s/Jn4O6Av94VZ/c/aDhr7RuJKLeVXnizPSr2w0u3k80e+nohMclq1lHhWpDzUElvlJtRQLHKUeEYevAGZ97CFJb8kuhlhEbEQFsKCCIvORljlCSu2laVZv2+R9jGSQxJKBry2JElNHVluoU86SH29fT2XT7Yqx1OwfAa+HovMe4BzhfrUqIu5QoTFXOEFk9AIy5tbIZPQCGuGpckIizXvMO417/LwLkNfWTzztfXgy+Wr5HURhmqnyGdJeURnN9Qu6qiXAEwuCNH8b0h29OXpPkRxdgOM7hgjv3ZCN9sUUDNPSXF2A0RYHGMUubDkmYlDQaIWVr0xQvooI30EUeThPZ8/mlFSkZGU3fKs177489+o0EXmQ7YH6R/epX6+TIwL/5M203M2Gjt6m375U8+OIUcMnWTeaQKIsCDCggiLhoAIC3oiLPaTQIe7dFzvOCtzR5sta0V8sOV/mZNRdvvIMPkAvZkrhBBhQYQFERZk2YzNcyxdnGmpsZ/1jM0iZ3Km+0P7yLXx92xg2Mwk8w6Z0oEICyIsGgIiLIiwYNTCKvONxWLB1puP8aecN0zns0PmHTKlAxEWRFg0BAxaWL6cCdP8frr2UGOfiAW5FRJd/OEDrkySTY3bfGcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjUyOjQ1LTA1OjAwsTjZ7wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1JDLnN2Z3tvwsoAAAAASUVORK5CYII="},"96":{"admin":"Croatia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFb0lEQVR42u2cf2hVZRjHD0X9YWS1f6YDDZY/tn9qOopyUsGwVXfgsEy2UbeNqDSa6X40JKJSq621aRPpxoqkNKu70ERWsELrZrYixFkxlqUkjIXNUtE2FjfY5/7xyLt7Onf33nXufZ9/vlze85znPfd9P/d5nvPsnDm/HMk9kHtQ1V1P7phdM+sT9xFVqU6mXOjxP+cdnn9mahCkCqbUzphaNKd/fbIELH/GLf94y8iIZf4aNBEoTNMUsbyHYv8nU3csshWOVK2PT1Ohn8tkf4KV7lpwihEr+cVKx+W6+0wHfHIdzDXh12yOT231kokN5uz+/BE6NtcBcpt7Ku6NVJ7oGyzZvOxmiUt/Z/G3twbaj64r23TH249Vzny09OS+wk2FQblWnIUHrSNTlgr9X13Ftv94+ff3fQUoEh2AmHvP0p3bRm7aU3ihaUXnxRtry0vAZf2rTVduqb/6habAzm9ynnu67s05jHAU1ApGbutqex6VeEk0md3/a6XthgSiLJtadKr15/BMdGvfI9vr8w4fXPNU/fgTjQXnq1eir3++MKf4GcACmuVvbM9Z9/Gc8Zc2hmr4jOa/WzfaVerMKDjb9qRUAOVcEFzZvbV37yjzciUKVgbfQnNVRAg2GCBe27i/OVAGKA+V1T74Sl6kPq90wQaQkmCBWm/egeL5N3DWjl8/+HvxtYzcMlZdsqGwpXp99PYGLEtyr797TS54yQiHfePlXf33/8iV2BC3nOzuGMlYBVhsM2Cx/aQ/qSACOpwFFigjHB38Y+DYdXsADq2YFXirfW7VF42r9j/LvEDJWaBGGlWwMr4wl3WSVDaeeEPckucSt0BBxipGAIsR8EKJYaTC8PKW6l19LR/2XNb30cNbwuVfh7gSqi4FK+OVgnreoZfbQx3AxGajxBhZaRGxiF5gJKsrRoqKlm5rawi+H/ysJoCCFCoTK0o993/VWOlLvvEKIceehgLRgsiByoTIPd2iT+861fEDqFGBcRbFPp9RjjKOPedSqoNs9527Z+y7AqRsiFKW/hGauCXvDelOMc7Gg6DZDo3XioxZTnS2qJy+HKwcejyCZ4myPfeDMbCGgg9EVy38vXn1yOrzNijxg289dFVgb2BzTBkRqzGJjWE5iRqWzHj6mobahlp71tmJDkcPRb+zQccHh88Nn+ZrE2PQ3waWjC4Zu1DVu7Z3LTbo2IsDnQMdICLtwYWjWOIfD3iT9swoLW1Q68ACC7MCu9gR+SnSY9oDirRnxAQFsMykCZoKVtaqjEBmtQQWyYNl3n8pWHZELFKbKMlBwR0sGYfigSLBkv6JkQpWloPF9ptKcW0W4+fC7zm7jpIoUUZMSzxIn9JewcpysEiI5tFJUuRE6W3aMyIfnpGRLF4KVrCsAyteUZ8wWBMeFCxLwTq74Miy/hXJR6xJinQFy2awzhS05rfm01uiKkL/qggFQ0HqIanSRqppiQfZCOUzMypY1rUb0FS1G0zP2m6wqN2Qvj6WNkgVrEviSvKddzxoxLI0FVLxTKdSeylYtt4VxvkbYqJ3hSZAeldohcZLeYn2sby0G9xRVrCyMCGauKQKLBmZ3Hv9WQ5W1XhdsPsf3iqJfZYjUhMdN4+m1D8PAU/tenjNwexXeelvyX6VPIuKSloyy39cj/t3SXSdvaywu413dfXgmK9c2qC8CsFjxF6ebkig3TAR5/DMLHausKVgobz+wKPD3tsN7o/N4M1qpBQslPdzSFuy0PYSsWR5jge86aoqWJcoL3KByLFF75wIN1OSy6fjGeEolpzFf23QNVSwPEUyoDFVk52CpapgqSpYqqoKlqqCpapgqaoqWKoKlqqCpaqqYKkqWKoZrv8CfoNALcv4ejsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU5OjQ1LTA1OjAwSdgiGAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSFJWLnN2ZyUnqtEAAAAASUVORK5CYII="},"98":{"admin":"Hungary","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAwUlEQVR42u3WsQnCUBiF0ZeXlCIEG8FWgp1YiyOksBSXcgoXcAAHEFLoCg4hKDyH8C8CnjPC5StuNQxt23UJQmUTICyEhbBAWAgLYYGwEBbCAmEhLIQFwmLUmrJ83z8bQxAc1qw/PPcTQxCrKreSy9wQ+FgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBb8qDm+LtvTyhAEh3XePdbX2hAEh5VSXtRTQ+BjISyEBcJCWAgLhIWwEBYIC2EhLBAWwuKffAHRWBI+T4tO4QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDA6MDYtMDU6MDDM8t05AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9IVU4uc3ZnaLJGKgAAAABJRU5ErkJggg=="},"99":{"admin":"Indonesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAtklEQVR42u3WsQmAMBRF0URcIKULZBcrV7F3H8cJOIq1ioIjyK/knBEet3i5tVJqTRCqMwHCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBZ806fpXK/dEASHNYzLNh+GIFa+X4bAx0JYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8V8P7lwPhQb9oxAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAwOjE4LTA1OjAwUGem+gAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSUROLnN2ZwZPnKAAAAAASUVORK5CYII="},"101":{"admin":"India","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACvUlEQVR42u2aMUgcQRRAN5WlYKNI0E6xvjSKYMBesLZKQNDy2oCCKUJAUl0ToyjBQjAgwkWOCLFIIylS5CAYOIJVRI4UKVIcKXIRXvNlPDBkwWKewiv+zs7i+Pj7588W3e7GdmVaynJZuARSsaRiScVyIaRiScWSiiWlYknFkoolpWJJxZKKJaViScWSiiWlYknFkoolpWJJxZKZidU6bDySslwW3ebVb1vKcqlYPXk2eTb5o7M/tz/39V6tWWt+egqJcNVVUqxb8ah11Pq2OD86P3rweKw+Vn/1BE6tTq3uvoUxzkjucvUU6xovji+Ofz1YmVmZ+fAbXRYaC436OJnpvP+8/+dOSq4ykruqg9XB9w+ZzVVVrDZCIMfm5ebl53HkgFGmGIljuCvq5apmLRZCDHWGOrU/vM6iLlRRp8Onw9+rKbkaxzMDszGzYmX3Z5N1KluVrZ2T9bX1tY9LMRuhDqKgSCzeiXCVkTGHMRszE1es7HIVL6+Ye6JSKMKrbXlieeLdS0gEyVK9mC2+WBUrI1J0x3I75iqEiDL1InqleYurPEWxMiKNA3aCadeKeJSDyglGsRgZ94/MRpynKFZGzQVqoCgEWScVa3ZkdmRvurj6ebYBiaRixbxFzuMpeTYgMs1Y/MvRIu4BU7HgbTJW7MWbsayxbqixqJzSst0aS7H+eVeYNhqQJpUJ4dgzMjLO4K7QPta1PlaMx3orPYRO+1ixTWofy857G1GonBAovhb/p/POzHbesz7VisfP8awwtiHiiWEs0omgkWeFitVTL/JN/LohzUy9vm6Ie0zXU7H8Hkux7u4LUkpy6BekiiXvTqw3r7/s7jWkLJfFwP3nL/oGpCyXiiUVSyqWVCwXQiqWVCypWC6EVCypWFKxpFQsqVhSsaRULKlYUrGkVCypWFKxpFQsqVgyK/4Fclp79PqRQrsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAxOjEyLTA1OjAwG9WSigAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSU5ELnN2Z+1kp2cAAAAASUVORK5CYII="},"105":{"admin":"Iran","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEaUlEQVR42u1aTUgVURi9q4SIFiVE7lJ3b9nb1CKEDASXFm4Cg3KjRcsMBCFCKDJEKAohy0Ug9MrAjbtW5SIoKgJdJAVZqYhBaURii+PiyOFO896MP+TZHC/f/c79vvnumW/umzHU1Q0NNTQYjflicAmMFpbRwjJaWC6E0cIyWlhGC8totLCMFpbRwjIaLSyjhWW0sCrDQ+He6LH2jU4UUTYnljEHYfGGbQSqLLYbKzs3y5ppbqRkf7Un51DZCmlyY/9wfLr05fSj5vNPD589qojZI7dHOlueMSazmi49+XZmRFnAGAu401jJlcyXlZxnbK8rY4Vrsy+77l4ZOPh69/0hjPtHX10YOsdjtgC7l5+/uXnizsLbXQ+vsz07S8dgcYYxlsZKw4rFivloxXpuvVjqvwGfWERdB6zk+m8tC9VQVpp6BvzhgnJZeTxyYGr/WD/LIsZiH7AefH2/+Pgks1h84ILFsZSlRSk3FvyZBX9mKSazsDJvHrM4urLSx8LVJbN4F5ilt0EsQ1iUxZVkFsflqwu8SZwKwsPCwVSIzOKexCyM+Q6AhbeTU1SWxtJt4AtD/ixZZqmd8+eScYacA3OVpRujNVFZ6K2bzOJaYRzbNe0ufBMi/xiLK5mGhXFYHVu9vNpmBP75NVc/V+86ZEcLy2hhbQX+LI1XjVcpujIWVqYHImQ0M9sy3DIMZGHB0w9QCyuKvzsn+yb7Fi72rvSuQDqwQDSfm5u6m7qBsGAWnot7BqoHqmFxJS2sdZJCN/qxtzRVmoJQuBt93FcoFAqfrhbni/NshydYkJ3lZWGt4WxDR2tHKxAdaP5U10zXDD/sICnIix98zOJ1XNXgXjU9XbtUu4SuA0mhD7GndizY4QkW5AVP960dLazlmYniRBHCwhgPRBYWd6yYsMBimWI1C2tHv0rgHgOJ8KMQyMJiOzz5dIXV/ErCHasIuUAWOCGxgJI7FixgsbDcsYLfV/HbKZy0PvTU1NTUcNfRjoVZeIIFxGo+vPtXYRv/EuS3VtzJMOZHHlvA4iO8q2phrZMXRIMHGR5qkA7GaoEnvyx1JS2sf3zMYXlNvQshBKCeovxJx8Iq41D/vX2wcbARB3PuWLBg1od0CyuH7uUuZWEZt6uwku/I2Oz/zdqcTDYz1satGfCPIjgx4AMFxvjxjDF8gLDzLCN/a8uLhTMN56ZZKfJJKF+W5skZcp5pYul18fqxWLxr2Vmx+sdYaXYtMBlj3k4WBNuzs3RWWTyrUkv2z8JKn1vMp1xWuZWsjKW7pitk3+s1YWmYGKrGYyxWcbks7WoxeVXG4r6SF0trqHdzMou3RHuAdkGVl8ZSll6dxuJdq4yF2YCPEkD+UY2v9Dzm/0lilnoyJrMwy/6YjbH431c4ruafHEuvkS3JGTILs7FYWpP0LK0tZ5Jm12Ks2FXrrqWPpfkHfvVnNOaFLoHRwjJaWEYLy2i0sIwWltHCMhotLKOFZbSwjEYLy2hhGS0so9HCMm5v/Avargl1nz+mlQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6MDctMDU6MDBjbnb3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JUk4uc3Zn08B9JgAAAABJRU5ErkJggg=="},"106":{"admin":"Iraq","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADjUlEQVR42u2aTUhUURiGB2pXC1tEi9oUVJvIRa1yE2gtK11EEGlRUG2KQFAoqIVBZQsJijYhSSVhiopkfxBJZVT+hGGQRdiPUFFGP9BuWjybA5eZZvTOOKMPL7xczhzvOXO/x+8758xNDA4vWrqqVNfj9YSPQBcsXbB0wfJB6IKlC5YuWLouWLpg6YKl64KlC5YuWLouWLpg6YKl64KlC5YuWLouWLpg6XMKrO9NbZXdT3U9Xk8kXyV/JCd0PV6fc2B9Pf+37FdF/kfM/7iCldcA1999XtvW8L7999i3nvyMy1iXTr0+3XcmfR/BKko/MTnU2tm/ZEfr+kO1/VVfDr9pzefoI12TKz/uBe6hhd9qxo9y3dk8fmtw177Rh3+aKwSryByMQIqMlc/CNNb1c+3n6mu9b5NPyoG7sWXkZu9u2jcM9DxruIPPpnI5y8GixBC2LfPulTRdySR4YUaJC6xoluJ6+9b71RdWMEN65j+bClbWTn5a/aL9XP0HQpv5XxHyeLPIuRujVXc7mA8Y7ax8sPjiBGDdOfjp2MtGPgU+wSo4JzCUP8KZajkfzRCAteBky6P9a+PKH2F+WlZ2fduRd4AVZizAYlzmIFgFWv4IW6ryRHGMrr24JvzT2a9xN1ZU5Euuo2BtrrtdfrYkBIs1mWAVkB9Y87j08qaw3ET7gBQBZkdGODkUCAvodEohmZI7A00UrGgppH/H8PjygRrBKggnGGSg9P/xYT4jwGHRjAss5jA1sMxYM+bhHoqCtW5VV/fxq2FpI5Chs+EntFGw6BOWwmzBoj+IhGu1ECzGEqwCwihcRYFFiAIhSeXzNzbX7ekLrymC6TNWtmus8BgWZyzBKtD9Hctb8gElj4CFh41kr8wdaFJlrGgpTJ+9+JSsCS6s2NKXwuiuMFrKi/EHn0SxnEWBEXgRDFoICblnas7dCH+YsWjhUzz92RLhBxqgZG7hPGkPvwsYRXeFAEp7MR49JIrrBxkeOsHLhZNjwrwSgpL+iBWwQIH+mTsZjm8aIogzH8HKCVhh4AlhLjx8xSXaku1LMtl6qju4xooZJgoTOYDSQLApDcV+Nu37WDP27lS4lAY12kEtXM4bSMGK7Ueb2fdynGDp+v/AulqhVPxKJBYqlQP5CJRgKcFSgqWUYCnBUoKllGApwVKCpZRgKcFSgqWUYCnBUoKllGApwVKCpZRgKcFSc0n/ACD6ZYnG6V1CAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNDoyMS0wNTowMEKbRLAAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lSUS5zdmcxcH11AAAAAElFTkSuQmCC"},"108":{"admin":"Israel","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEA0lEQVR42u2dO2hUQRSGV0FsrHzESmxVsLEQCwMxooUGUqYJiZ2FWohBFBFEooiFNhoQArFIoxiRBMXCQESIkSWghhQWiRKNGHyFiFqkWIu/OTDMde5jH3fv13wsc2fnzj37M/fMmTOzpcpK5XdlFcJsWcIEEGFBhAURFoaACAvmRFhT32fXLd/z8eWxmenl1ug6cWva+j6Gt5Nt/9369Cduf1RS2jDVvvPZoXqxdGffxadv7GdxU9vhD+M/3ZpueTX647L2/VHLjdOf2PbxVYUwFTEBRFgQYUGEhSEgwoIICyIsCBEWzL+wQqKu0Vd9daIj7+F1wluI28NkDOlnmueNWyeZfdLbs/TwysSppaPik7HJ8tcB3+fi0H3qEDsU01Y+kt0ASZupH3+sX1m7ukbEGggrM/Z3D52ZuyliDYSVAWda57b+urrtbefuiSVRJVgGYaXi8bZrl2Z77XxHJVgGYSWkZje+qbuuYiWEFdtVP9Jz+uN0hy/+pKu48wgrBgdaRjoX+u0odXb81u13I6Itv/vt8fDiOSyGsP7D+YHFlT9jezb2DE2ekHR2POo6/2Je5Qubv9z/+8Be1WeVY71CCEs7RjT2hLOr5cLo61d2TDrYe7Kv3G3rqMTW6SlfbpnZEvde6iHCyqWwove61IvqlbZJIaxc0vWK7E9rGVIn2bfcOuoVr8Lc+0zyk/Sjbr/R+fn5LgULFOq83ju89/1++8PrhairosY/WyK6r061pqu6i5WX9dgQVhPO8rQ444YV4r6k3BeuPDC1rLvY+6onOO9NFZeyTrfGLcXQbXQq2WqgKyC97HQXV3AIqxCR9PQvKTdI4XpdytYi3NC045a79icO9o22fzqQpn214LasYARxrCanRo6sxqroiYJYtLGqoMLS+OGOK+ndaneKYEesoq0qFkhYWtfz+VjykNL4WO5YVeRxq9CzQjd8mix06WuHWSFxrA4ruPBVPNVUZqlNp/GFIYhjNW3kXZP/8Mi7jbm7ByiGRN7tuKWeFCG5mbXCKq4V+vw51grJbiC7AWFlkY/lBiNsPpZCoG4+loKu5GORQeqlmyMqr0jlovXYyCBFWLEjXuS8I6yq79JxfTV26SAs9hUirDxkQ7ATGmFlfHaDQp2c3YCwMqPi6SLWQFicj4WwYJMJqxHO+bTnoNbmJE8tp/juVcueNKZ9MjiDNP1ZudHn+IaffJzmROT6npqcR/uEn6ycsCecSA75AwGIsCDCwhAQYUGEBREWhAgLIixYWNpoqc2TtCeb23I3l9JudXKZpk5If3xt+uqE9KeW7aR5lkZ+3pJWzaJpt2iGlIQzzXerwaz602jPVfvnJbsBkjYDERZEWBgCIiyIsGCR+Q/Uod3rvtSw9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6NDUtMDU6MDBwu2kkAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JU1Iuc3ZnvYzUAAAAAB50RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgSXNyYWVsYsv/cwAAAABJRU5ErkJggg=="},"109":{"admin":"Italy","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3csQ1BQRjA8XeGEAuwAdEqsINBUCqJRGEBlREUJpAQFiASnWiMcGqd4j1x3u+3gfjnu/suImTZctnrZolbt/ur8XWwa5w6l3Q/xXOxmW6Pt+F4PunGQ6zFerqfpZJ6UqEaRmGfISyEBWUNKz7iLLZ8kcJCWC7vmFh/I/WHBmH96gxuhns4CwtshQgLYdkKMbEQFsICWyHCQli2QkwshIWwwFaIsPjeMpTTj3ZshbyfADn9zNDEwlGIsBCWrZD8r/8mFoVc/22FOAoRFsICYSEshJXycusdS1gIKxnesYTFZ4e7/8eikBns/7HAVoiwEJatEBMLYSEssBXyfkMt7M3MxCq14l75bYU4ChEWwgJh2QqFhbBshQgLYSEsEJatUFiUOqx0dytb4U+H5UDBUYiwXN6FBTl6AXMqeeREiAuGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNDo1Ni0wNTowMI35cycAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lUQS5zdmeHyQnqAAAAAElFTkSuQmCC"},"113":{"admin":"Japan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADEUlEQVR42u2cT4hNURzHr0dRysbGYnZSatTYWMgoC1KMlYSkqMlm7CykbCg1WVCmrExKUyjzyCuUUViYjDAZShopmklpYprXyGCexXf5es/7c8+553fuZ/NZzLx3zz33fTrnd37n3F9S+VMpV+YhTJcJjwAiFkQsiFg8CIhYELEgYkGIWBCxIGJBiFgQsSBi5Zm/188M/Rifq7w59KH36+j10v3Nn1ddWHFt3eTKkx0Xt79f6LtyriTqL/qvPqlv6Qo8ScSan50c65y4+bH39O6B8y+Xbl080v1kePVA961H75bNbhwcGU4KGxbrU5/Ut8YebCoeLEg7XRmxctThmYWHx0f7Jzr3jZw4KyEaEahZ6spqRS0iVoT8eerTpakeTWTuZKovmVrXnSBWJOPT846uo3vLPmWqRd1JHsawaMWamhtcXtz/9O2arm07G//hG4+u2h/DdIeIZUwp/1MeekUrlhb/4StVrZfuHLECTRw8W7L29a4trf3AfibBWtSdKx+GWAElNrWwtzJK1aJ6EUei1bxY0zeunrlzOdvxJi2qF3FEXYbF+rVnuv9bQflunz+861bUI/UOsTJb/cUxVlXT+rhlWKxXf3fcPnYg/BGoNY4Xe+72lRArgzVgummF0CRT7+yuExO7k2B8019ME6JJsbShmwex1FPEMhNdWaF6ilie0qHuTis0Emn5jMbUU4sp08Ri7qrZMwt2qa0ei6e4jImlR9zOniBiIZYnsZqd2up/Pt3JFLHMx1jtR04uYi9iLFaFDleFiJWLPJbPVSF5rEgy79lu7FS3TubdK7WDFuYR5LSCd/YKibScjIicbuA8FudI4xJLayXVXIhJLPWIE6S5G7fctaUr6xQ/Z95z9JaOa315SyfQdaKtPURpKvJeIW9C8yY0tRuo3YBYjl5kDVMv3VUcQTr1scohBO/Ux6KiHxX9EKsZfj/8+N6LLz5rkKpFapDmtGqydh5bqwCob+kKVE1GrP/Uedd6jTrviAURCyIWhIgFEQsiFoSIBRELIhaEiAURCyIWhIgFM+Y/dWVqkJkga9gAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjA4OjA2LTA1OjAw3yWdzQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSlBOLnN2Z6/gxrAAAAAASUVORK5CYII="},"115":{"admin":"Kazakhstan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFdklEQVR42u2dP2hdVRzHO2QLoSLaghAEC4Kx0KHYpdihiDgIrSAZglAKLejgIM3WdrBgLVmyaKbiIIXStOSPJto/qWmaFjEIWcwgKQgO1skh4KI+6vDJ8JHDfbnv5RF7z/ktX+47977z7vnd7/39fuf3+53zdu3qn55aWQsM7DGGCAJ3iFh7r85O3/pk976ZjcXfXrn39RvXfwFp4WwILrBjYg0f//blseHRS7eWzyx9un7nzOn946cW9pyYooWzIbjAjon1weTNtbM/fnnj7g/v9J07dPujD38FaTm5evPFj38PwQV2TCy0FDSa/3vx/NE37/y1+M+RAVo4G4IL7JhYmLxr+7977+1nUuRsCC6wS1P40+j9Pa+uo6VAWjgbggvs0hROXFxYGHkOUwjSEqYwsAc+FubPxzjyIbjALsMNDjGAtES4wUhsr39gZuJ+ixYfB7H+03T4j7lHXzwPjTB/NoKcLZNALw18NTE7Ax67/M3n46+/1ZpvfTZiJJjMNXwriLWJBzfmhq68YIPoQENpxEIa5Buss3EMkAlIC3E+ruRb9BDE2vSxEB9iAmkpwXmHEKCJAjIvRhqQBrTErOO53n0WRyy/l+D3K0t9rw2CTu/kLSBMmL1Mxo6ZwwhaS1l72SCaWFCtUGIhUIcbUuRsri4qugcqgBALokCgP088aD278mTo4c/9p1LkLFe6N/pBnxVhHP0BukAdYlf2IdySnyAYOxrFo4ZS6OwqMlXh6vtLkwcuWHuBuPaZzx/9gQHbBCAakJZcVbrHzhghBBOXTillRHu5f5CW4jQWSptjq3GEktPbxlh4zHbA8ZbaG746SA94Zp4MQdxs9VbahEBNI95gizs/QTAutIvnv+3pUp9e9Aa9+BV+sSBi4VpCKQwBxKIlP8eTR8tj5vEzRh7/dnRVahAdIyyOWLxV0MguJy2cbTqN/i9iEV5uT6xMqFZVj5XqKleQNnHwDqY42WJi1TeFnWJqCjm2JH2HjZ8elVA240xfVXoKV3rnnfc0jQbtGu/ap00Mm8iNc4UUKDex5p3HxovBWBidq2GddXC4YfsG0USpCjdwJ9yV77PBHm2VKUyJlT6MZsWo/Ho8Hlt+OPguI0KfeXGbfcrtB0itjdIAKb9L/9wV9oG7tVYLU/iUhhL8evAIQetgSGCDxYtkd763KR1a+BYIHbnbBgd32i+mcJaw6cu/bOIdSWJcNkxoEUYNLeonofHS6K19Eppr/OpCdIjV+GV2dcINaQVpE51K9ISNO6NwYaNXgYPOQDhQjJTokx7snlu705IaXIq8ITckg1LWdtkGSK2rmh4g9WSeh2f9kRbopYV+JkpVoZ8Lu6GLJeZ+bObsyWWSja2T0gHzSOlAL+tgP0JHsOw4O59og4g0fOzSZOt1u/A2c87Pcj+ZFDSnMV/TyG9kTkloVy6k81yHTDFY1jrp0omq6LmnApDPJE6LlLJKl1VVkNrVxfbnV0Hq+FZVqgod5hCAC7Xxrkyg1KlP6QKlvEdGhqV/VWUzxHussdySXyWWAwHpa+NNnSCTfanUVeCa1KiZUlkZvk5Lk50xLKE0GUpV+Ukmh+lVhxz07JljQYV+DN4EKnMxRftAq4MO7a+HfDad9FCE9Koi754b2v0sc+8G5w29fjBdngoiKwKexNPZUgVSFrE2s86CVcdsylx+6aUWDpwiGUfPQaY7HHMlZCpoAX79Jfa0lLnEPvVE08X1dttjif0Wm4I4ggW9YlOQwB5sY2S0QQzBBXYZeW9fNhOb9QR2WUHqrSKhFC2xa3LgtojlqbV3UI7NbQO7JJbX5MR23IE91limkd35MIWBPfjLkzToEOGGwPiTpsD4W7nAIFZg4Jb4L4xjl6KxNwljAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowOToxMy0wNTowMK512coAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tBWi5zdmdUfS14AAAAAElFTkSuQmCC"},"116":{"admin":"Kenya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFGUlEQVR42u2cW0gVQRjHR3rpTi1dfehG5wgRUS9Bl5eiCwX1EIUFRb5k9dIFD5wgKHoIyywIs6Qeuhwyu4GEQhZhJZWB+GAJUZBEER0yohuRYeXpb/TVNMNuarY7fwf+D7O73+yZ+TnfN7PfrlIxNbijUKndq+wCKsGiEiwqwWJHUAkWlWBRCRaVSrCoBItKsCKm0+t+Ft4bwermwbtblCn/zxD+n3dFsAKol8gUDCFK7FGm9Nb9oHV5P7hDghVKTV5MViYrv37OlNNDMyXocI56pXJUTt4k76P3EYqaoIijddwJ7ooxVugd4ssRbypfHgo6qFPbVMffiTVei9dyf0Lu3twbUNTgaFC4cScRd4LurArlbNG06VmqaZN9aAFNWSKjjQWz82fferx01+FdZVDUVKzLGqBUvDxrR9YOO9bpZ+lP6U9y1uSqMCK66EGmyHnLNMBwc0DqaqqjZtDDlsS1xLWn2SXnS85DUYOjOFN3jrr7Q+u4E4IVKS2Nlb4ofWEf5m0LfyIFfX77ZNHJIgkWauQ5uEqfq9rGp9vT7WgRrXMfK4KKdVnT5kzR463Fs35HCvq6+mbrzVYJFmr0M2FBj6vgfHt3TdoLiknbnYJZCnhhFkG9dH/QuuHZ97LvYdaRYL0f2Ly9uRhH5fmwAGuwjFbQomv9rPDjXSsIqKE1m1fWr6zXZ6DbWfFUPKWDhRoc1a+CNWnfzR5WmK7dLKa5pytgXZk2MndkLiy73LdOgyXXd90FllxLOg0WusA1bWzeEtsSw+zSE2DBMlpxs4eVqWuirdgg2Nj455WgKXh/Mrk4WZxEza25Y7eOfW+6Fpb1zQt31NGfja0B7LDbwdK3G+yRGXbkYdm0hUGwIqjyUYy+z65r65jqkuoSCRZqTOfLvXgoWiRYEdf9l3/fJbc7LLg/CRZq7E5W2reDS7Aiooh+5MDbHRYyGgATFDV2Jyvt2yM5ghWp6ErPZbCvDeV6x74e1NNp3Iy0nPvB+sD7iYQax8+vml8F9RO9+QeXYEUqbLenyugzFtaGUNOMZUqhQYuuhfDOgaUPPGoQ1OtXIblP7imjxgSWKceLYDkHln3thuwrCZaej2WfsQgWwfI1Y5meMHLGYozFGItghW1VqIPFVSH3sbiPRbD+7c67fJjDnXeCZXxWKB1Wzz0rtG9kEKzIrg2DZjcAKf/ZDW6G7czH6syaMg28zF5nPtY/BSu8/4t+MkiRI4p8UekQmUHqCyzmvPvJedfB6rmc97Bkytvvk2/p8C0dvv7VM+8VmpyaDhaeEtrBgjW+V6jwZYGQKd627YIFvKOMT4PU7Fw/ZdV+e4ylz1gmHGENljvfhA5jD3dZlXd2627vrGt64F1N+4G3BXfPNST2jFudt77/siNDhuWrPvpbOv5XhbAAa7CMVtzsYaX65a9Vfd3RmR8KL8/8UDGj/kvFjM4u+F4/b9icfb+u4BCAy9e/4ApNX5uBBViLF25viBeiFbToWj87B9axthsXjrWtKD9yaUW5frSgLOdo8O9j4SrdGlpBixJighUpxTDrc5XU0bWrlqta6RbtX/TDmbhKt4ZW7CgTrBArBti/Y5p8fUnVjx150zdIgRTO/DvnS7BCrzKU9n8VoDl+cOJQLyW/mowaP0hJResb6k7VbqgjWBFRDCcC6qDXws3ljV5wxzsDNTk+u6J1gkWlEiwqwaISLCqVYFEJFpVgUakEi0qwqASLSiVYVIJFJVhUamD9Bgc3K7F3aKTwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMDo0NS0wNTowMLjeFp4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tFTi5zdmdajF4sAAAAAElFTkSuQmCC"},"117":{"admin":"Kyrgyzstan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7klEQVR42u2dPcsdRRiGx4/wCkJERQiIBBWJgoGAiGUq0VIsrBTbgD/AfyB+FJaCVpYigmCRHyAWiVUa0d4i8MYkL8FvgsJep7iW52w473v2Y2Z2mmGZ3Z3Zc+Y+93PPPc/sSdcPH71w/tyqyy9Ov3j++1nv3afHQr6ltGpYtHKyMo0wePP/4huIc37yrq/Uwlx7/uUYa07lsf9XuQIFk/9nTO1XWO1n2eXZJnv+1NTGKnrfvd+RnjA1Kd0+b/mhsFkDzW5oAGoMt0/LqfL5VIP4nBBUO6lpi0meLdbvfmVmVmc5dkMhYL3x2yu/PveGa25+/vqZZx7i+PDlc689/w3lJINdONemFkT4pMDo1tdvXjp7RP3R4aWnn/wEMAGgP658duGxUwbZ7Wffunn2gBZ8ZQuvJcwKfdeogQPomHuADsd3Pn7/pzPvUlLz13dffXv6VY5/v/jh208c0AKt+V7KyHnryeNIIw98xmVvsLtnvv3DOw889SMQMXTgHiDFWZjsnxcuHz38Hi38e+PK/Qd/cyXwMp9Rs+G/rq9VMNmxxXu5GkLrj2YahpwawwVgASmAQsldXMlZA4tjwiJg5UpD2dzWQmHJBC7lxGBbGxlMhgvA+vOXLz945BbHnKV0ja+khl42IOuODVaz1yQhctzZ5d4/+1TtWphqzEAOefAQ9QCC+rsf/Xz5wWsu/3vp+tX7PnXJ9RwDL2o4hp+osUqjfg3sleqL8R5Cc4PBxFlAA7B60Ll75/F06hil7gVYtExfcCT1lNZbtWqvVJWxqR4ZTsBkKe0hN+sYKFwTgUgJAxlAEWS0DGQNX2DEs1ntzZTiMu6I3LO1VJOVBz/ZxrS4duDbwk/dsTkmzg25lxoAQY/U9ECm9rkLUFqlWfnVFzeqMkjtP1kyO/AxqIaU53eeOW5ho8Bq8Bbw8lQgspd5iyvpy09Y06JZynT2d6KPar1i+cxwAgKDg7OGRQx5gMxMQw1nfb3hEuFF7zwJrZlHs7NSiwHWxPwH39hzsnU5NL+zm2WIuMSdsp9ujomlYRqDo8FkjrTjNeH644yxKI3LGcvC1KHQ9kFPV3XDbClt18pS3cNsuLjG3r1ZzdorTghiyfN4pbIx1sK/J8+zLKg5ti9l5jCfReVk8W5IufTZzexS7XiZyL0bXsCut3Rd0U6CVLpgZEhgDn739sptfjK0Xmyx6mLIaccaK8LLodDX7N4LZ6m3YqM8tt7KMkOrwrQZBmbIqbJFaSgwqCgqGwp2m2xAAEFLeGroCwB5jhmBZUXoHuuwTKfc/jVWzuTOMIqqJdqYDlIAy6qIuzwV8Noi1xtwQ3fRo2eaQ/apVxWzg9Qe64mp9FjuWZWVk9fvomzn3o1RKZbqHUuYWz/RsuFlJ91h0ZMJA2tIwve8+Cbe8wl/VkgMUmQshtBeV2QdQ2Ro0QYIDgHLhmcPmt299sPMVcdmrHFn8aNGklT60oEn9gaTJ/8Gh7nE8Ipl5Kqo1WCXqLrMpjyJe7dBamcrI421QNpM9qLeyibOCs03fBYPqlmn556H0gtBXqhxqB2ySb3c5KcFmoWJ9wE8pNJh5CG03WBhHnnLAdFWggPi4FqhvHsHQbfjLPgIKT+hs7Vq2kg3707oCZJDLNujM+6cgpjLEMOWfal4fQSZ3axeNn3HiIYgMAVMTuNxAnRz3rMmZIbZrDC0sLMxRQmLAaa7ZDf0gpc2gUUv3s/AvRb4EzLWQjEnlbvXdmg7V1wM9uphb0FaEImJK+Y8gp3NTGd9UcI3Uc85ENvUsMWwgK6aeJSL97G2pM2IOWKCSi/XKgQ4rndGQ+89g2HPj9OOt4ROJcx4JcDrg/b0a9rMsoR4n/i3Yi/eocopNLYlt+gn+eMOYfb3753W7LQ+jr3C6N2Lte44ryoUbnmrgixKJwQ7mbiXAXGiLRUOrHEzhXv3xGLW+WAT7+OCz/64Q4+n+g6OFvjmJMMubg6Lew89gfBStOehxXDVifz9yl+8ZlfJOekAzrM/772JKTcOf/ai7Jx5iuC+Yp7qXonIs79NdBqDtCIzovc+ha4eGMWcKsKl1ZVz1R3aaDPuq3YyoLNV2xb70XYh57aHJwp86zB76J7BGYLmNoMpvqNmy+uNSvwXj0lmhZWWcfnFkDLTeBf15p1Yylb10o19/722c824rXTp7V+F7xjZKblZ/nvc62f57zmd9wjVbR+sbjPF5G/SEnQcTKOluep3+VWymWJq8K3h33hmYqz1/KFjTRDJeF1kra/jLnx1If8RSS0oNHZs2Q1tOIv5UaUWwhqgS85uWErTLDUpWf1f3rVQuFZFmF0GaQtwzaqoSryPFdTyAXF1fzA+1/8VNh5qa4XNVWqSYFzW/x8WNffSy7WyHAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTA6NTgtMDU6MDAVo3fAAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9LR1ouc3ZngiTOZQAAAABJRU5ErkJggg=="},"118":{"admin":"Cambodia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFhklEQVR42u2cv4tdRRzFb5fCwtVqCQhapbDTIiBYWGVBbEQF21QJaJMiAUsjCME/IKK2QrrELoWIpgjBYFaQkIABBQPRhawSFNlClD1bfORkxnn33ix33JPi8Jh393vnvfnc8/3Oj7xh+Offh19Go/NrvoJowIoGrGjAikYDVjRgRQNWNBqwogErGrCi0YAVDVjRgBWNBqxowIoeNH3hg2F46+1odF4drr8/PDW81qJfH3v88JPvznVle7T6364aZ9x9b/5ydP3opdufHftm4/u57jX9W5ryHW4+s7a9tj3v9ezPMK5bB01/unrmwZntX5+9uHVxa8pwHhwNWE1edf/ShbMXzgqsVX0rYEUfYv63b7763uuHf3/i2olrJ6Ryr/jWAQLru4+PXDlyZa4hV7StN8/fOH/jt3uXP7n8kcBSy7y+pXsFrEVXQvKYGbxqFx0lwT8fu/X8reekavnhleMbxzemQ0x8vz21fmj9UMBanFdpeO6dOnf63OnpQ6449Ko/rm6+s/myWoTXqnMrVwGqaP+n6q17sASQhkRDLrxUdM+VBHe+uvvF3c+lalEhP/0uxPfHByfvnLwzHdaANZtXKQlyyKeU2MKFBbs7ll7Lb8b1nKlW8QXZFFgD1mzK4SFY8ptxRbFiEil51V8/7zy9s0bI5DHjXFYYqbeKqdfjYgasmVXDwBJbg7RXtaxYyCsNacgVR7CyePcZ4qrJi6lWMHFaoLv3nhCH3teZ+Nw7WKs+/ZqXacjpTPIqqVp4l1Vnc+6y6rk+hdp7X4AY+kWKa+Issf3pbx8kXaloXrZ7CS+82qsiIcgNIoLL+abg69e3hh6RUsmsgSEE9BLWLi14aSDlVYojgBSBqdBb9uq5T1+8/9Ib7XNA9plJlpWiPmmPeHUGlmomJj4Og557AcHZFvEqKZGSMgK9iklQfeD19bswvTICHYtORrwC1iNUfcWlxOc1lq6hA7FaotKBiGZ9HYvxvRqj8kq1eI3lKOvdgLVPYJXWwTmELMCpDhBxJEAceCLCEtudjPE9gRJB9ZCOpcg+EelxAaIzsFiwM1nQCTTMrLHoYY4a32WV46mK3ubXsyes9rhswZKf6dsjM132uGTaZfGuWRU9wJcZS2vl7iVMUh6HGzvcMiKmvlFdUsLnvXWv0ifNrHBfE6KvhrOc57stQ84ynM7n802PT2SZXksJly7FaEy7aumxuuoYLC0NcGh9eBysUvHOOomJj5h6avMrfa2L8Ymgp+wSWP2ed+geLNYiLLpZwTAh1pUVEot0X21nUvP6qdRCx+JdfIkkYC0CLNU9pdlcO1h0IweIKHi11BKZjhWwunesloEvgeWO5WC1R45jdQAWcdG6dgkIolAaeF7DkwtckWc7IWiJz7t4P4WRH6QJWPu68czlBg0DN0y4RVOqeFxL60w83MIWglXar/T4hI8HqdlzRtYn1afua8ewA7B4LI61jhfUXCvylW7/q5Z3Ga0EUD1mvZ0997TL132tvw+9IOVTerb4tnT9+vq7bCn5UOk4TT0mlfWZLz2UrpfPLf9/NS4ULB0y0TNa34OjS/mSZvvSaGlJs76ONUW9eOfjUd9z1Dez5K2ehYIllyqdFHCtL5BOUU9evns4Tr23vuFd1yVv+CwILJ2u9DPs7Xtwvv08xUtKjuWn4EvuWHfNFseqR+Dx66X9Z9dhaScXxjkNB3tex/Iieq7IdKnSTmJ7nKWlxaE+vW+Z4pauWXV6rGdOZ0T/Q3dXd9iiY8H/em3XPKSl1K6WXVU01/a+1d/lL2/tvV415q6O+/Uy/6uWliaw8ttz0Ufyi375tcxoftw2GrCi0XwF0YAVDVjRgBWNBqxowIoGrGg0YEUDVjRgRaMBKxqwogErGp1F/waSC59MEZqyugAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTE6MzQtMDU6MDD7rn8NAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9LSE0uc3ZnobI3IgAAAABJRU5ErkJggg=="},"121":{"admin":"South Korea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2dfWhXVRjHf0H+I72g4aIiqTBXFEO2cqB/qIhrEUY4g0H2n72tF2oWBXOSLxQOpbdFjLCIXNRKpFZWatnCXEqaGYNlL+hc4SpjzfVX0Or2EXrG6V7u7577cn73Phz2MO65v3vPfc73Pt/nPPec55QmhieG/vlTqTJWWVIVqFRgqVRgqVRgqSJUKrBUKrDKlH9M/L52/OiOh7wyMOQV7Z5giZbQGNpTYP2PREFVD3uleqdXen70igLIlGgGLaGx/navKLAmyZPPeqXxba+c9dF/ZeNUr/Auck6RwYQG0AaakbqqH/WKO1pyAljBaqJW2jDXzH7S7oG0T2gDzaAl81VUYA1jwDHmqIb/oUW/WnyLvMJLPhdPamoAzUjnwawtKLD86O+R570SpvbeE17Jkw2T9omn40l5aqkHNMP5wbWFA1YY+jOV5VfrDgXEq5MwxOdX2/WOVwoErGj0F6a20oEVTG1havHDpF3Pym6V3KE/VBCNHPPkXUUjPvSDTybt+q2DXjl56al1p7/OLbDKpb/wtXly5KMRn3QGTPCl7yqUlP78LMfYy4dqB44jR6fs+uazcaQ8nhyg7Wkx29Fiyf3RXzB12rfwz7qRml8fPVW9vXvXph8+bV345OKv9s0ba77yi/HZnzSMfj4yo7p+bN+Bc86bU4vkCLWcefS7lv1rb+AKXC0ucDM2lOCQLrnLo8VS5dKfPZiG7+tY9eL3gAPQ9N8z9a6aLgkjU3KOKanlalzZHmQ8KX4SYQjpS1FsSLMigWVDcH6BQfuvh9gVut8PHDZSXo27cMe4PjkDMhkodpMWEwQWIJCPlBX98SusCEQWF4zCSO7I3W1oKJjaJGmao0X527he0YypkDdDgiaY4PAh4qI/lI7nlA6Mgm0YLYkGr+BAaJixJL2QjgtfynYgnfTo7/glbdueWpgVpPwkrYqLB4KJL6ugTCn9ACAqwFBLS4b3IN82zHs0dYw8saXvrVpzTJetlBRMC+MNohIIpZYjWX2QLmUbZUb60V+0eaREmAgHuGarJLxoIa2NlxalbgvxSSfYD+MNs3EtUSJRJTchZcKL1trTYrb2yempybyFNjO4sXDdN1/+240/uw8sOWaMZrfkKgHXZtjmapXOjsb1F2z5uPeB88frr68UYMnRoq7Sce/D7b9O690/Le1aPXPn3HN31/1VWcAilBrX5yAFVmxyz4YvXzq2cfFltW0t76cPLGzkayumr5k//MKhGR2LliA5QnuCQ7I2hKjASlB2tvds7V82e+TaZS0r0qFC7nL/mzOn3/Iq95329Jy+1gZkad11ix57nP/rXrn68B17OdMP9MAuWgBCgZWgbD24eai3j47EWiQHqU3bL9y/5CbABIDCS0BGC80vlTaBUwVWInLllPXd25bSec2dVzQ1NwbPQYjmCQEpaZOiSUBpwssm9KDASkTe9kx71eunzxDQUM23D66R3WZvpfCWolmpYOslPTCAlY/ZsDm0WLLbAET4uVZ+vhRXiwtSUuJ7qcVy2scKJp1oVio5SJl2S30sR0eFfp2HV4TvBcjkyJFORVLLmVBqcpAyiVtHhY7GsapGG67Z0BLefcZaIGXIIGkwmZJhAYs1FFjORd7rem6f/9ye9GFhLz/sXT53lUbeK83TcllChXzl1G+Fjs5uOHB84KITTeEJ0QVZvXxBS9tqnd3g9HwsM6blviRQYj8fi0l/Oh9rUn66eBdQYLeumtd05+ZZLkMKy0pry33GwcPHDv6yIMwM0sJlm5Fz3v3SgdjMeScA4SYt0ipaGE2H5mIvVgzIpWAFmvPut0oHkx7vIlVU7KY7T6vSX6WTK2DxwDI/nd+6QoAVLy26NlqkJdFssB/9oc/gWnRLL1T8usJ4V0Lb0KK0XumHQCE+G0j5ZcGQ9Bc+QUjFr4ROIneDfeqid7fuPXvw4nRCqdyFO9ovlUMnwIXMfS6nNHI020xytCgj9R0fdK58b1a8IONqOOb2efTkUl4Zryp0thn5toUnvjOpDUUwIl5a9ANZzxu7px05QlQJcJgjSo5ApvzPmfyKK8SVlDFMfixzBXmB8mOFoUVpn+gYSZdZZfTDHSbOBJ0h+eBNbRKdJOPpfhQmaQ6Nob0wiY1yGG4Ik0dUvmfpJGFz7oN6mTlIzQRPBcpBGoYWbbIm5ym5rU0CSL+syenrJyd53vOxT1i5wU/N855gblKUqztT6M4UMWwmIEdM+dsPbNJeOiLfVZg0kGZtQafN2Oz+NWHslZU/qbt/JU6LHNH9CnW/wkR2WC0OpCbK3GHVBfpzdGqyJD7dEzqMHyb3hHZt/zPdxV53sddVOip1lY5KBZYqQqUCS6UCS6UCSxWhMmb5N0Cme6EwPrvgAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMjozNS0wNTowMLbuz7oAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tPUi5zdmdeBwfJAAAAAElFTkSuQmCC"},"123":{"admin":"Kuwait","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACmUlEQVR42u2aTWgTQRiGP6ONmDShS0Vy2tSi8QdbWj2oSKpIQRBF/EOIIIgW61GkP1gFqSB4EC+KB1EET+3FS45eVKpCaqAg9CREizdBEE+irpbpIWXdddXZ3Vl93sBz2GxmZicPb5Z2RWR9ZeUmkdMnN/eKXMz1pUUujJXLEP4dF9J+dkVVZP9I6ZHI8Ocdl9gaqEUslWV9qaJI15FVkyJnprbM0WFQi1jNocNgKGLRYTBEsegwGLpYdBgMUSz/DkMyqEEsOgyGKBYdBkMXy6vD2GjECr3D2HTECrHD+IlELO7DYBLE+rMOG123fXbxOerI//aFqR0Icu3R7I/XeoyIu8PUQt1U7/qfkxTqugr3OPHuj5rdoMx3WOb91oHVGw7M3useWXp8dPLOlYyblacTM+N7o2Fc8yaFXvsjpsWetmt2rbG7UW2MOR759sE55/REQ/e80a/BTDq+MVSsN73zL4ckNohFEIsgFmIhFmIRxCKIhViIhVgEsQhiIRZiIRZBLIJYiIVYiEX+dbHcj814Parh/yDHondrTsop/GQP1PFmeowT5JxfzOWe0T/mjBN8LtPEWr5Rbi85dPSLZRemXhUqjwfLjZ0Dz8+fgkmkEUp1PEtV09nxPZknhcyLE23ZNVfrb3NDnZ9efmyd6zwWhF7n/+44JjCJa25euWLM/bTvWvpg3p64nPta7J/JWrtKN+p1yyqVPKmW7n8k+GcNHmfhqwowjv+ZEY3juhYj+imQUjBRjKGfHm7LD3ccRibE0tdP79purs2jFGJp7ieUQiz6CcYtVvrWDzzg/glqE4t+gtrEop+gZrHoJ6hNLP7+BDWLRT9BbWJx/wQ1i1WcTt1vec3/76A2sfqtlrut11U/sR1QF78DzKEz7C4xic0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjA0LTA1OjAwcdSo0wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS1dULnN2Zz7Ccp8AAAAASUVORK5CYII="},"124":{"admin":"Laos","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC50lEQVR42u2dT0gUURyAJ6JTLKWEHgQxMN1DHbZDoDdFjS6Bl0BkodgOBaJIdCgRT4pFoODFg4aUFGkEHroUBRJRsGGS4EHUy55CaCPKk7AefpcV3W3GeTvz/nyXD1ln3p/ffPvem3lv3norK1VVTU0QqqVHCCBiQcSCiEUgIGJBxIKIBSFiQcSCiAUhYkHEUsQfl84uXajmgpkSH8PEQi9T4mOkWEimf3y8UgWCMAy9qWe1r1vqIFRLz+tN1t655oenE6mT/XPhjwl6pNpzo2T4cqqqaQzp6H95oJEkBH6+ozUXW9P3U/K3Ke0lYmkk0JXfN0bG/w2kxxIL6ZeLbx9kXyyns6mNn9+b17/lEkL5RP6beTP06fl5OYsYItYBdiRvvZrMiig7W/lTf88VvhRWC9v+KWdJCpIaUXVULOnaJk7Mff3QkN/4k9kdDCpTKUpqkrLkglhOsD7Xtviw+93M50frv1TJVEovyaVxs+vx8EfEQinFlBwld8SycGAuY6AolSqm5O7QHaULlZR7N7VjqeN1jlISxLKk+5PHBHEpVUwpiemDel/trgttlQ5KFdOJdsvu6sU7rio/3kIsgzvB7cu5/p28bmJJqSx/DGFrxWSaJd4Be/mBvOUTQbZWrHu+b296WTeliiklRCzEUsyeznt3Z7sQC7FosRCLMZbRz7F0nqDQ/67Q8tlD5geZN0Qsnrwjlk4L+pgrtEosfZp6HVY3ONRWsR6L+UHEUtYtxrWC1Ln1766txZap32j0cnRRcnmx7L4ZlvbjyZmn19/frsRbOpIyb+k4/QZc++jNqxNrqt4rlNSIKmIdMRHEm9CIFdEd5eEdHIgMYsE4qHbXKxh99DRNn73nYEV29GO3TFiRPUh127U36LlB8w1zPPHxXx7EQqz/pH+8cnq6bWYf5gIEzTGundDVxkfP+lollgu/ImFKZPiRJohYELEgYhEIiFgQsSBiEQiIWBCxIGJBiFhQe+4DtSrzYBJ8V2EAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjE3LTA1OjAwjJay0AAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTEFPLnN2ZznaCwQAAAAASUVORK5CYII="},"127":{"admin":"Libya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyAQMAAACQ++z9AAAAA1BMVEUAlTCNlXMpAAAACXBIWXMAAABIAAAASABGyWs+AAAADklEQVQYGWMYBaNgiAIAArwAAa44Of4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjU1LTA1OjAwn0OtAwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTEJZLnN2Z1DuG4gAAAAASUVORK5CYII="},"130":{"admin":"Sri Lanka","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAI3ElEQVR42u2dXYhVVRTHj6KR43f6UCmjFnJHnRBkMkSnHKkYKioZsJh6qJeRQhKfHKuRAiuVUZISI6UMIU3BSMKQoqKBIohCGhL6MqNUqIEgnyqbcH536C9rznafj/s1d78sLvueu8+5a/33f6299tr7RP+eem921B5kkPnKKKggyACsIEcLsC6cO/pMdMHns0rbg20vp4y7u/uZ/Vv8/7tbD+4rffTsvm9SaybVpxewuib1FAq3LutYd8OSw9ll51/rvls47Y/m9Z+Pm4AcHHziVBQNf954MoqG5aV2P7mtJxrh+c8u2f3uhMUH21YV5gwcOHbnjllfHTn9yJRpzUha+DbIBPLazqnT37E6RNtewAKPACKK1hTa1meXi1oeaFza/tuezl/H3zw4+NCCS7DILi8BMQ5Yu3rndxU++OLMS49HM/lH5wfe/DPahFK2/9A4d0GLW/YOzu1q6vO/UmW6HtztPr3539FfvryqvX1mG9pDk99seeXeMbe8uHrFmuverztgHVu5rmfiQfvt9z/1Lm/4MQ4QbojkJd2grB7JE37y9ea+qwtWk2gYfSZwhbUOLEg77tukyi0F4JKCu5xAhO97ts0rzPswDjqJgVXrjAVpoxrgxZ+nXb/1UW6cmbs/apoza/fTi2YfvfHL8oOgPCBDAwxFUIEmD+x9+OyUN7Z3LT18/VMJXGHtMJYreFdYYH4UAdSIG9KNYNTKeC2PgdPFednvRRTFP1V96h1HJbCuHLzHQQS+Sap0FA3n7fu4o2d6a1JoWkBYzlPJt9yXOzIwMDYtDJhSQBwtMZws+PhHdRdjuZ2d25xxI/jTc9v+HnsRYGFOH9dpHajOTOE/jKdQozcApDMyneGqPN7fffGqm3iqIkOXmE3rGlg+sZQXsIYMhlFJYQAF5SE1J/Kxzxb2zDh5omnLq1EHPQAjoEB7MQky1I7c++zajePOvHZow7dRxB13Hbpv/NQtOxpuv3vGVsDNNfTA5F9BVnTTwmQ8JwGAPrnPkHDLAKxMwCI9SP9MvxVAuCTlHpwXhlSe05y1tigsAA1wAcRv7V/b2/Dz1nvuen5y/6ZfWlpnngGyTx65o2niYq7XbBN3BHawo6YMeLYKMNZoSpBmVx88pIYhylGWUu6xKVm4h3aVh+/v7o8G+C0tfAY6pxv274zGqPvbM/nBayYV+Jb7Pndb8+uNK+wEZfM/K49MPQH4GBJADXdpY6Z0s+PEwXvtAGvkWaEmFLLDCzPAH/RfDJnFxXANxrOcpA6Lz0iuV3bhGgBk1+O4kmsANxEbYIKxNLS3AX6+sZcmIyrmCumtFhkLk6jbwpWoeTAYZl4/f/mOiYMKF3VzykxI+Ay4aORED8DCQlaBi7QaUCDqakRemqkKV1iLwNJcjrokBRYS90SMRaAN6+DsAJOyl3WCfEsshSOzSQQ4CXemoMG09Kb8pIzL9Zoxz85bVTQrHK5lqCXGYi6mwFKTFx3iEJg0flLQqCRy4jNchcTwwBH+c+f0gYtK/ZUFDaDMVz9V4Qo1xsoPXiPHWLQnVZw1hq7tq+sZIYM1BCxbLwWMFEwKO533IXGF7hybwovg3aZPYT7aNcXqXi1Nx2F1PStMNzo1leAGFq4Qc1quosXCS+d9On9kNgcP0TN3JN0AdDR6c5cxasSmrjCvNGlY0unLC1i6HGQdE1Agw0Qgz/WwkUZdmobQcP6Ftzvbxi2jB82B5VVJq+ukWbRUda6w+oFlk59uxrK/xQEBLJjGxl4Aa+f5R/eN/V2BZdku30JtnFf2dMwoXtLxrW5IByxGNkyjI57IJi4i0QoInJ2dCWp0xWeNxmyuqxQ7BigsThdX6a/qaEnHv+IqTgIdTA4g6JMWmAbYqYqBlK1cVX5SkNmkg5Wl22DCc/ovxgdXmMNaIXAh0aDmV1iQbdKqBOW2uPjG8pMmTsu5c6lYrOfBWFaHKctmwlqh8pY6LxzThmmrW4czT4x7AnZd9qlmWbE8VgCWjkv4CRjxma1s8A13VHeZZb6mfZZOMlRCPVZuwEoKNdycrt8hYSwFgTo1fRJ/oKhzTArEpL9KWpZdh2UzV54VZuEtgKXpAAJwgGV3D9s4SUto3Cb3vxIY2VjNJ0rjV0nLZtyzwpDHSpluIJ+OswNYLAPbiEorORUubmPrfNPNNJqvdwPOPRMMpckVy2NpwQzA0syTRlTW/Wm1gk847+YqLUR28xnAIhkbBz6K/vKtha9rV5jOLXI9aQUgQh0pwTvG1jp01R6showL6hWClvkAAW5L82pxwGIAaB2pfSqbe8vOWHH/bpTPClUm3Xmsy722KkGNhCHVFQI4VgnVwOr+1LVpWZ9m9lXSpzWkbrjQwmWtJFP+o/8skZbmseo6xoo7YMNnF6HOCmEgUotab66K1pVE3eBlJ/yAQPfuuZ9T4WXdIgDVqnYLLL3SP0EaMu8l3EwBvLQQL+kkQA1MlEYojQvzNzCgsVEUYFUeshCEU5UXQ/BeMWBpxZW/MewxQHGVUraC3t1bHLCAkTKf7oVUrrLsGIBVgX2F6RaCABOggZPsXM+H+bTuHmkjLaADp+o+RK1r1elCXic+1HWCtHSntcT1wMzRXacALCxXWZMDI12phOcsD2keSwty7H0t72Y5qinEWCU8Bkgr0LUKFGn31bgn/woveIhAm8M76dPuSSRuY52AaywceRJ/Fre1smER+jJgFaOKoZiDkDmv9TL3/hnbXkwlOGtQ3QcY2eNDML/OLvV0Bvsk/oNKDybRjfnqxOu0bIY0oz14TWdP5Y/DKntIZFJgoU/0hiaLhwwMDY9wVORlee3yg6mazyaNu6MeEBzOIL3C4bbEHNV8sGylni0u2rMrB6P+DFLXrJCYgL+te/fcC7HuWCTLUdhZDuv26ceHg9Md3M1BSBpIpDyOu9QvEFC+Sf7SgP+le6Mm6rAvEKiHY/5L/QIBbU8MLP9XfbhfyJHulSf5bniyz5b0FSA+rxJx95/0JSv+d8/+0hr//v2tGV7SFGR4+1eQAVhB1rn8D0mrs1wVEO5bAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTowMS0wNTowMC7y9zMAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xLQS5zdmcncZYDAAAAAElFTkSuQmCC"},"132":{"admin":"Lithuania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABj0lEQVR42u3asUpCURzA4fMMOdTUmEsiPUBBcKfmIGhsy7Ggttaeo6XBqUbbpLV36BWilhaxwQpFT4b3nNtNv+VD5HLuuX9/XkUMg0Gv12iQaQ1GQGFRWBSWQVBYFBaFRQqLwqKwSGFRWBQWKSwKi8JaYofvD0dr9/mON59SYY2Wyzf0fOunHVyqfdZnnYrCim10/HkyZni+uNvZGJBpDZvX5697x2RaQwinT8XtLDsvxdm3naL4ehw7nhx3IpfpxzMcRZbUiaArXOc3x5ffVdrrLbOfHPOJORHNnKSyhVVnc4S1TPuJhRi/V61YQExr/KPQgFgqrOlPU3cspgprxjctA2KCsEhhUVgUFiksCovCIoXF2jn2a6hxzBuT/Sy0nyy15n43eCH/Q1g//0mGXMT1g5PD/SsyraF72d5tv5FpDY/F1s32kEzrZ1j9frPZauUw9/rT56ryjH97vXWYT2y1pQqrPi+e6w2rMwJWqbAoLAqLwjIICovCorAMgsKisCgsUlgUFoVFCovC4gr6AfbDZvCvuz1hAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTozMC0wNTowMAYK+2QAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xUVS5zdmdAkRcPAAAAAElFTkSuQmCC"},"134":{"admin":"Latvia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAwElEQVR42u3asQ3CMBCG0RuCEagjiwZlgoiShr0YAA8QZaTUUQZIi2CJOwmJ9xdvAOurbEfvrY0jmWs4AgqLwqKwHASFRWFRWKSwKCwKixQWhUVhkcKisCgsUlj84bBe6/C8LmSuMT9ul/uZzDWOaXvvJzLX+JgVTFgmLBOWCctMWCYsE5ZZdliu8lhyQerxgSVPOp5LWfII7YMH/ceisCgsB0FhUVgUFiksCovCIoVFYVFYpLAoLAqLFBaFxT/zC7rlujN+sttfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTo1NS0wNTowMJJd3UQAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xWQS5zdmeYOYdGAAAAAElFTkSuQmCC"},"145":{"admin":"Mali","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNklEQVR42u3cMQ4BQRSAYWNFoxNH0LqAI2hUKoXCBcQFRKJWqjQKRyDuoVWpdAqFhqxTTNbj+04g8SfzdmYyqdM5HPr9WnCbbbt3242Grdlj836Xi7SP9fvTuTEvj/fLad58XrurSWsa/R+p10BYCKsyRZGW5TjWIiisAGLNWGXvtU4DYYGwEBbCMrxjeEdYCAuEhbAQFvxZWLYbhJWF7QZhISwQViDuYxnes3Afy/COpRBhISwQFsJCWAE4KxRWFrYbhIWwQFjh/N7BjrC+wu8dRQsLYSGsCnkfS1hZeB9LWFgKQVgI69+Hd2EZ3rEUIiyEBcJCWAgrANsNwsrCdoOwEBYIKxD3sQzvWbiPZXjHUoiwEBYIC2EhrACcFQorC9sNwkJY5OQZIxBWHN7H8lWIr0Kq8wHqzHuU9PMQqgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjA6MjQtMDU6MDBBJHKsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NTEkuc3ZnrHPmzgAAAABJRU5ErkJggg=="},"146":{"admin":"Malta","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADJ0lEQVR42u3cT0jTYRzH8Z2Egi7hpVPUJUgCT/2jS9GtkxBYnSwpuhUYUhhB6SWXIf1QttFGhdNlSbXS4TQKKpPN5YpCC6NaMiISB5KRRQv22eGJ8RsM09r2vnz4sY3nMF77fr/P89vmSP9Kz6RTZP5cOPLxQzL4ylV9dPfgeKxyZdV6Mn86QAOs/wjW7PNgcmhfMubf3/c1ORA4cPtcNjOPLLx4+yOxAVjAKuQt9s+OpL69fmm5L28beRPeG06YOXbd0+I7MRd63Bj5VFKwMh8VYC05rMm0Z93Vn9Gt0TPRPcqxXfGp+I7o2kDljbMlCIuKBSxgFTGs96PeCX8TsMi/Buvzl9Ge6LvxWqfH2mIHS+xEEFjA+qMmmSx0LVK9cct1cToYDrj9DRrYB1eF6ge2m9fe3s7YpRohs1sNWGUES4cI2vGp6miW0rWqlEgJ0M2qnkiXT4z6a4IVtybNx+93d+zs9JqrmWsm1lgrvA3Fgoxd4aJg6SxKtcdsc2bqWdHpqGs933yw5cnJY8c3u0+1n249LFhCZreCUuBUCahYZQorOz9lrk063Ztc96yn7XXNQ021QiZweo3SDpZOvIBVHrAy5+bZo04DUy4ss+WJlFJt0Y6U1lGqIQKrjGBp6I5c6Zu/U5Gb5pCuuiVMZsUyX2O3jiY2YJUFrO/WxMzUcCoZqn9QnXo2vPHRITPVvNT+cutW7vD+8K7T0daoDUHuajrrKprhHVhLd44lBLHVXt+1ee34zOaoKqVq1D93ocvZpuMJ7hUCq+Cb0LmTk9qoah4n78DiXiGwuFdIK6RiAQtYhcHSDRntEzXOK3WIQCskHYt5o3U8YaY+2aX0vYblh1UafPkxBRULWMACFrCABR12hcCiYgELWMCCDrCABSxgMbwDiwQWsGiFwAIWsMh/Bqv0yAKLGQtYtEJgAQtY0AEWsIAFLGABCzrsCoFFxQIWsIBFAgtYwAIWsIBFsisEFhULWMACFgksYC1j6r90gAUsKhawgAUsYAELWMACFrCAxa4QNMCiYgELWMACFnSABSxgAQtYfB+LBBYVC1jAAhawyDz5G2cTMNB2jMTYAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMDo0My0wNTowMELsRaUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01MVC5zdmc0A7X9AAAAAElFTkSuQmCC"},"147":{"admin":"Myanmar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAETElEQVR42u2aT0gUURzHp1MkRRQRdIpFMi8FphZZZJChEYRBB0krwzKIDppYiSLoIUNLIhc9ROTNys1KVjRFSIXQ/kBooiZkqIFYKGqabZTBfvfwZNr17ey4uzPzvXyQ+fPeb998mO/zzVP+zr/tUdaSpL5UOAQkxSIpFkmxOBAkxSIpFkmxSJJikRSLpFgkSbGCzaWovuKoXJCjQbH0EytndCS3EeRoUCz9xGqZsb+sBDkaFEu/EBx33fma8ccxUTWZzUCkWPqFYNPSqyW7hwxEiqVbCIpiOaZcjiGODMXSIQSXiYUjDESKpVGs8182XVizTCmR7rMcJYrlv1juyPMqFgORYukWggxEihUQyz4/OeP0qpRAXMkR+49YRbvaHti2kCIHb00kPrXJiIUrOWJqKrv7G7ticmJ/NvbHZqiJsyLVx8Uj3tqRb83fs9pa8FZbyubWyynrxyvn68abZcTClbhLpgaZkZG/Xq/Rk39SvisRqfjuwGq8sfdd8fVyGaVE4i6ZB2YdUqxldHaO5Tuz/BULd3H0LCGW/GsfRJwNP57dN1zkr1i4Cy14iyr5CQPFCuiRy19/uKo5K7EWFI/I/O37SOAhqA5E333JVO7vlRTLb2IQqysHd1Tv7LsymtR9COw5+C2he1Zfyk/YfU/kV6M28bdjNMJfLwNEISKmvmJkod4WyIM3OjECYuBSLJ3/awv87WIUYvaGX22UEDSYWOJk/KStvT41CdFgVqUQgvilnLyHbAbm2dtpdKXc3x/FWZRx/4tUzLGsgLDQtlgQDjKJkWeO5QnFTOtVCA5ti5yhIr42GmVKbtEFUuiFEKk40tdS/jw8p/moChVygdSQvJTwekN2QW/m1IveqHBQCpWgKq68G/4dFg4rYeIqlBU+7FjuIzQCaIXdobpOzM0deRTLw/Shjrb0dcF8V2Ve6yo856BYJidWiYIpFnq02m4ty4kV/Ik8euQby7RTeE8IBmd2pVpcQCByP5Yfu6F97yIPfM+Wtk1z6gqDH4LygSizU97frYursUFSfi+8kr+xpCW+1AoM7Udr9G6d0VYmx6Kj98eZm1NbUz+c3R7Ih+rFyGeFTZ9AbS2gd1SCqr7HxZ86etqsY24JsX7sKYu+26RNhbn20oTbMWJrc5FFF2++0aapujWKZWC6HvWceF8r//hx/XRD2nR2pLc2cVZbyxTLciG4UPOwoe64fEjhStylLRApliGJ2JJ52LPJeQXF9kD6Qgu/Oz5WDLhWCER3VRTLwPxV2l7TOeDtAeOsvu8PtOa730W7M6I1gmIZMwTjj42mJatDEEfmU+9V3T+wer0jItGLtxpQIcUyfAgipGa2XbqaVxLMStCjOiLNHYimFQtxI65ChfYNgd7FlTBzB6Ji1hDEGwIrWOG5roYKzRqIphXL9ypUOBAVUiySpFgkxSIpFklSLJJikRSLJCkWSbFIikWSFIsMEf8BFxxzt4ucGlkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjIwOjUzLTA1OjAwjkZFOwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTU1SLnN2Z3Afk/gAAAAASUVORK5CYII="},"149":{"admin":"Mongolia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwElEQVR42u2cMWgUURRFA1YBSdQY1qggmNZW7dIoBBFsLEQQrWwUxMJC1MrWxkrRwkbEbjGFgqDYiGIV0IiNCgEVIYorhEAkhbBnizu8/bubZGfZmbnNY/g7CezO4b7733t/Rj5/rI3tnS56vHH86vLtfbOz9fr8fBHj0cdzp96v8C3K8URGDJbBMlgGy2AZLINV6C/w9cn0lT1neCQ8HoNVcrB45N8OHds6NToYxTJYFVKsP8/untt5MG+8DFYlwEKrftw5fWLX6Mrrtxe3z4DXYv3w9901gxWRMlg9RQD69eLm6uQ1BQvU8garuHgZrC7x56XzH2pHll89v7xjP2D9XXi0MHGy8fLB2Yk3ViyDtcGIo1o98Ondtpm1L43fY3NrU0tPx7cs3b9+YbJhsAzWpiKJjyQIUngvgzUMYOX3LAZk3kGK5Gjz3gtYeT/yQioWPwoq9e/e4q3xhyRBIq4rD7ysWCUHC2WKSGnEyBssg7Xucqga9hgx9f0VfJcbSg4WtaskWM11ChD5gWXFKglY2hNMFRoULODDafXLbxms0ioWuLATxGmhTPgtUFOkSJr90i2DVVqw0CpgUsg0Unpgb8g9ToXuFfaUENEq9Em1Sldo7Ni8W7E22IRGn8Ao7+EZp8ICgwUWpDCNpDlVLJDiU73WFTXv8R7+j8Eahunc3MFqU0poXlPw1L0hK3qPFhoiiPFOfFjvX8xgFVix0JVYREgNwwAQiKSa0NwTK14Gq9pgNa/VjGs1q3MkCWYUy2BVswndAisUPAGrlSib6+z+NOq6opPqLW4GLO8KCwaWohNToVp7EIwWXiNI5aFYxdUtp8I2qbBzuQGAIihOhVasLoqlZlzLoVSw2A9GyNqY98rvCivnsTqDxc8BQFpcQL24R49XaKsn7jSrBpZTYTIVtpJaQASM9BCYNnbapMJKKpbBaoHV2scJWPFTPfJF1HEa3RWm9oz2WFVKhUGx0CFVLJDSxJcpKESds3l3gTSjWOKx8FJq0qPH0r/NeCyDZcXSjp4qlu4K4+F61S2dxMrDYxULL89jdam8p84VApbOQbiOZcVaB1ipoZrUCE2y8u7pBpcbiHqIPnYJU71CDL4Vy2C1K5CKYum0gh6piJPvrKNwBstgdUmFQMA9WhSldqXX+hK2PFo6nm4oya4wmnfUCDi0vRNb0Rmw+jRBarAKOfMeR2JS7+nTvWHqYGqcedehGqdCn9IZive8l+NcoV9jZLCsWAbLYBksg2WwhhEsz7wbLCuWwTJYgwKrHLvC/+IqLfAqTqLbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMjo0OC0wNTowMEQewWIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ORy5zdmdei/mkAAAAAElFTkSuQmCC"},"156":{"admin":"Malaysia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGYUlEQVR42u1dbWiVVRw/I7BAhFhbLzOLnLoyL2wNc2QzDCXMTdOsNbpyW9Zw+XIzzJhGtRAHlQtnkMSMi70w0VIZviSIzmCJzebchxDcbOBtH1pULEuItuD+7of/5ew8nHPPee597vp/+XE59zz/59zn/Pj9386zCfHK3JVfHWD0xqLm8rltbZ3x4pdCkSvHp2+Z9qw39q8quXX63TZzdCwEzQ6dI5g09sRytXlBpp1qPh3JHrHEQ9sOd4wzQnFCKFYuoinJvFFkhkZFjy+e1Fa8aCDy2NuTIz9H+9YfenVp41BdY/0Pm29ZO3nJsRdLt/XNfLcq76N6JQWzioWjlU9/fnPPj1vbd+wbrtw+s6mKETj0d8OGl2/I4z4SCzT6ZM3ug4tGBo+d3lKw66/y7kcnfTHWc2mPuA+Ikct3nZxR1NAV/np3ydTWMy3/Pjl14W+r1zcVYjuDQKx7vqy+6fj9g7OHCq5/ONYz2jP6HaM3Crf6BCrsvP391urrw5u6npqyWKbRqdf2Lw/Ng1ZVPFBT33weWgUM7VpxR8v3ICU+e6sIE2uCEwvO7lBBbPnDcUomYK84mn/vT8+EGyo2p+qQhsuDZZVjlanmB+FArKs7r4z8Moc+vn9647F4ns5nOmI6R+daV+uRx03v64xY2EgVpaBPyfjJNHJKzAeBoG0YgZK9ceKtteHtVCkjd0brNpT4QSwE7+e+XRauXXWtbEV39QXGwWkL3nukSPWtA2Jhg1UqZUopmRaw0H64be/8pfgWVMMI5sCxJqnmg7vUzwrd5la5i1bEgnIgMJdjKTg+U5UCjZA5Jp1gYhw02lHRHF7ZBxoh2IeSgcRJVSOpQ3INXG7IGWIl6KLSKrhFm41EAaLjnc/mlQ+DZCDTtZHOM/m1oBSSg/OXjxyccRtGkEsiD0V26UqxYKe7cM2sjR8jwbZBOAu319rYVFnTsamaI2weNOInmVigglVFKnEV6AVFRElCLlgAMQdInaZ/WaF3kGuKKmtu7xLsrDCx5XBYshOEiniXCWQsmL2wLFYH5wUVBEKlQBSZTDLi7rgWBVhqx3RVXG7IArEQzciVKugKjY1MVRCWEUvBwalUSkbMpLqFGCu99dDfqyJWJhUlt9TLiljyltsQS65UQWlkXfQmFiiFFSYdonWDCMG7fYzlNhLK7hq87Yj0IiG4FVmxQIKUrp+2VtHqFMJ25HqmxMJVCOoRyI9TmOWsMEBZoaQo0Cd5a5GdmdauQCwgbe+AZPoxFi1VwALUy8Yhglhna2aVlVYGs15lf5AmQKcbVGE1IiSbmAbhPPSGNqdBXBmpPkGxkg0fR6ckuPJuWoW3IhZcjBxpYWvlHp++eiGne3NrU23tapAMnymBoJdoeOMzdA6UAhFpydQ+eFf1CvU7aPp9uvS6fv71FjPaK8SWq6pZ2HJTm/SkA6UaHCIqWyA07gv60hyQdg9TQng+3ZBbpxsQUaEmLsdbKcXStBDkoH1AjECTaDIBVeNjMxPq2AxKA7JbRECNb00PzFAa0dCb9hPlmdBRP043pHeCVHXG0nRObt3X8QlS2tGT1Qs9RMRA45BMohooIjsymkVm5gAglxvcZYUW+RSUA1GR6jgyzfXgwuDs8BlqZFoP45cpJsixGR1SQp8QyCPcRgYHVUPxk5IM4bmWO8vgCxcgVteUynVPjCG1ZvRG4eOrXYpxREtQo5SiZYBfAkPw3v/BxXUDz9OUnlGFQkkC723W2H6tWMf7TUM/7HNWGOQmtI+6YmNfvlb1QqzOq7NkDhPLmFiRZXP6o/nAFzaGOqNRRhk3nZ7f+Hre1ZrWJXsW/HEuFv80RPH39lje3otA+VvVTLcYtPUIVTNS9Za+6lvVtd7W9O2rbKr+doDOSnTasfZ/3iPXX5ZPMyt0+wO86ZLdB5QeRbK7ScGktc5TEgMXSqsebGFkdIviRsel53qrGRndouD8hdGXrJAfAaMvxPrz15MnvilmZHSLQk7dGRntUeiXDNJLyN3aN62K2ZQSvCtkptUv/bvr3Dcz69F5PqqrWLEY/VEsjgYYfYmxOH9h5HIDY+4Qi2vEjL5U3rmrxehLrzBbPXNX9u3/aUcw15nrv1fw+ySMvpzH8j5PqH/OUOcEoyv7qrvoWzadb3+S09Xz1F8/HfHv+aj2hbNCRi43MDKxGP/n+B+uxQvMVWFAMwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjQ6NTItMDU6MDAh2u71AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NWVMuc3Zn1Tj6xQAAAABJRU5ErkJggg=="},"159":{"admin":"Niger","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABWEAIAAADmonjmAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACpUlEQVR42u3bPWsUQRjA8RVBlDOFtl4rgvkAdlpYCFpEBBsFCxVF0mpstPED+IKtECzEyph8CDUSQbBQwcLgSxSUKAQFETnBx+KW4y6X3N7eZufX/Lnb3ZuZnfszr89ki4vHjm7ZjFgsM1WAxEJiIbFUBBILiYXEQiQWEguJhUgsJBYSC5FYSCwkFiKxkFhILERiIbGQWIjEQmIhsRCrKtbxiUOb7lXo+eq86bBTixTKrJ+OvLJcIXBwdvuD6/2+nWJ9Wbrx6tQtxGKZtVZa31tLKfPPk5Wby031UCyJtYpwvxsfDrze8WvqzeTCyWBcibukJFZfGv2YeXzhQTMa80/bLs7su/ru3InnO/d3Mu7Gk/ErkhErx9CiXaNV5lNdBrCRws+9T3fPNdRqlnL7tLww/fbSdLFLBqFmpJxyG5alqdTXO7dfnD0y7FWoyCVNvZITK9dKlcLIkVg1H0v9H0WVKFbkGLkTq4bd3+drVz4enBjVRlDknk63mKXTVlVhVzGdOWMSYuWG6iNllIRYNekEY4WpChEQUZIUOsSaixXbL+/nTjea96vQYkVJolTE2sCM3b3yZ4K9Z4jEqkmLVTWxQndi6Qp1hcSq7OC9baPa4N1yg+UGYnVnLEtaICVWvbZ0/uVoS8cm9FBmgqkFACYXNvNt+93zl1+WKVbkKGwmvUC/IQ/VBfolp1e0JX3Fua8xdlRossMUucMUazic7jAFsQY5/hVr5e0Hv+KK41/EKvjAapBGxMJRiHVm17P5qeuIxTLb+ujh4T2TiMUyGxubnR0fjy/xuZ3drvfD3r9d313l2SjlydZXCMTeJBYSC4mFxFIRSCwkFhJLRSCxkFhILERiIbGQWIjEQmIhsRCJhcRCYiESC4mFxEIkFhILiYU4EP8CP3QcVcisNdYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI1OjMyLTA1OjAwCHeMTAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkVSLnN2Z62kCwgAAAAASUVORK5CYII="},"161":{"admin":"Nigeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA10lEQVR42u3YMQ4BQQCG0RmlOIRaRytKrdCKS+jcYJUuwQVUIipRSZRqN9BpxyVmNpnNezeY7JedyR9DaJrVMlTuOF7ctpf1a3Sd/eo9xWnynt/7m+/5eRikT9qlab1n6QUQFsJqSRzGfXz4kMLKrPa3iLD8sfDGQlh08nIXlstdWAgLYYGwEBbCAmF1nR2LIuxYICyEhbBAWAgLYZGZHYsi7FggLISFsEBYCAthkZkdiyLsWCAshIWwQFgIC2GRmR2LIuxYICyEhbBAWAgLYZGZHQuERbv+NLs9EzrgD8YAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI2OjAxLTA1OjAwXCcqMQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkdBLnN2Z8csR1EAAAAASUVORK5CYII="},"164":{"admin":"Netherlands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJUlEQVR42u3aTa7BUACG4VPbQIIJElZgjG0ZYy1txMjEMmzAQhh0JEJKD9p6vsEzuJGmPXnjJ7khTTvt0ZCMa3AEFBaFRWE5CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoX12Cx0k3Hy/esUeX3+mlh32IxzLhTWeZrN9isyruFi9oEJy4RlwjJhmQnLhGXCMhOWCcuEZSYsE5YJy0xYJiwTlpmwTFgmLDNhWWXD2rWOk9OBjGsYJMtke2O/t5hv1vd/f9VY16my1XnG/E7uLX/P7z3jwxuqi8+PtdkWCetXBofr2YXF2igsCsu3MWFRWA6CwqKwKCwHwSaG5feasEhh/avlPwHKX0FY9I5FYVFYDoLCorAoLP/oQu9YrINXxJp2iizafvMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI3OjEzLTA1OjAw6NBQuAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkxELnN2Z2ULOOIAAAAASUVORK5CYII="},"165":{"admin":"Norway","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADa0lEQVR42u2dP2hUMRzHA+rQDkIHLTg5WE97p54KQtEOXeqkg6ODq6f4B6UognQQnFQoTpbaQYodrJ0qLhWu2KEoCEJBlErtYqlSOZWCiH8q5YZ7Nc27JC95l6efDF8euSQvfz4kv/x5OVGp5Nt3FcLUz/t2DxUHf2wpby0/WZbc5MSL/W+PNT/eWzxXFmL7p5M3hNiRK5XUuhKmcOpI/7VHiz2V60tn5DS/Hb356tZiZSjXsbM75JoJX8W/AlZ9pAzAet8+XHgAHIDlGix6LMCqNxTmBkobAAuwPPVYhjYWQyFg6Rnv9FiAZQlWdcgDLMBKCawaUoAFWM57LNaxAAuwAAsbC8XGYuUdsFYrPRZgMRSiGO+ABViABVihgcUmNGB52Stk5R2wMN4BC7BQwOI8FmAxKwQshkIUsGqLDoAFWAyFgMUmNOoeLG9zqICGQlUZk5TdLm41Vjr5SZCa+D4+Kkaf1dF7IyMPL6/xrONjGl7S35vnx+a/pHMe6+fd6bbp2wY5jM25s3oLPz+SiuXMOh93N+Bcuf8ELN0eCwdYXu5uwDkD63Xr7PGFE9nSmfzc14/F+4fGrjzvamreM3X2qT5YbVcPT/YOTN15eWG2/822d50Lv7JYA43Var3F155oXX+w71KTvm7a2PGhZ840VvIU5Fgt5w/MXBzXu7WhpuuW8r2nJ0zz4CrPjQ3vI1bUP/os5HlTqLr2LM8ULHU6vvXvI4cmZQy5Farlkmfllg2Dxq+ZUQ9UAWABFgpYKGBREagHsNKfx7lNP4kllJUZcQZVtQ6R3Cc+pE6YeB/zdayVAkfXsVRl1/k1/lknHVd1YvesX147H6FaRY36JA8T72+afnTl3XQTWl5518mbKj929eC2TvTzY/reJO3OJjSOTWibTWiz81g4wOJ0Q9hgKc8Z6p9FNA1jGFfvBKmboXDVCdIkZTRVV+/y0S5W7SgMzlPLYVThdfw13tXgr3Ts6sGVmraLj/PyCfLDX56oP7HnQ3u+K3RlvPP5F2BxPxZgcXcDYAEW1xgBFnc3ABZgAVYQGhmMsLEAC+MdZSjEeAcseizA4qpIlP8rBCzAwsYCLGwsFLAAC7CwsQCLHguwAAuwAItNaMACLMBiKAQswEpjVsgCKWDRYwEWYAFW9v5WTr4gmr3CRuofeRNcTTFrboUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI4OjM1LTA1OjAwOC45cgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTk9SLnN2Zww/KG4AAAAASUVORK5CYII="},"166":{"admin":"Nepal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAFIAAABkEAIAAADK/Sw/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAISUlEQVR42u1db2hXVRgeBlEQRShZM7PaEpuJWMMkNCMhbJpmYrRgw5kfSlGrqQQzxWYLktRtEVhbSGqsRIzyD2pF5JbUKAhGmnNu6VJnOtOazaYu8Pnywukcz/1z7j3n3PfLy4+zc8+9u8/vec9z3vc955fz1ITFa7ZPby7/5VLXt/3d/Yf6j7H11ebkFI+7tfaTYQ9Nv7Jh6RtT6s//8N2ZjnNtvTP41XgLNrXg+u5t3484Op5fkOdgw96SO6lrfXf50ZqdjVtaTxw78OdOflnegk3tmMWlzQ0VDQe/vL51MDt5r8AeXFa48O2Xb1oydvbaO2k7WuDkf5rz69RTffz6nAd7+YC8+rI9Vf/eO7R0dH7lg4+9OUrsk18ws/ejjTVlny77uZa57jDYK/PzBs2u6Kge+NojjXun3bH2yRUlW0Z0LKwUuQ47rmru81tWfX5l31/tL/ILdZLZAPvwwYGXx98PW//+XfNmTQTXReBZ0HkFNlqaH72tZ1Ih+mB2lzl5FnQOgy3abZVDVkwrmLzqgWeXtsucPAs6T8CGbVs/pOnx3WpBxxE6T8CmFk5+1v6Cka9soFynnyHoOELnPNiU61TQySJ0c9dVNX09hp28w2CLXFcLOo7QpQx2dJiDCjpwnVOuDjNbXLzpCLrb5xctq69gQecw2KLlCJ2HYIPN6j46EToIOo7QOcnscIKOI3RGEiHJgx0uQseCzklmy7iuH6E7UXy6qWcNQ+sk2CLwLOgyAbYo6Ap7Rq9e/qE6QseCznmwg6Zcua7GYbCDCjpaQ5dNQecV2KKgk3GdRuiyI+g8BJuGcfQF3bbh31xoa8w02DqRr3DxsmRmdPpfcITOW2ZHj9D5J+gSAru19+bch584nvvM0EXbO/uK3ntpKVrwGVbsb47x2YzQGQT7t4tj5z63g0J7bmLdP1tbLvTtW/DjHrTjM9op/Lg2GSevE6GDoEOhtLuCTis2jjmv/Z68KZMPB+Vx3w1H6jqnnt1cc/emlotLWm5sfQs3Rjs+ox190I5r9fkd9AnVq3aZoEML6mpcjNBpMRs869o1r6Sy5RqvbNOoF2ZMoJy+XNL99LmZ+g+E/riWjqa+78m6OR2vb4zuD6i0VAs6tLgl6LSYjZf+d/5n87/qV79QfCHATsrjoBbXYhz1lwzP1jN813373jHh/H0SdAFSnHihgFxcXMHlwpFiJo7+cBgHY4ouHc9AdYCJJR8dkwo68b3RCJ2dhdIBBNrpRcsaaq/DZWeOVBV9MIC+CDhe9Okd2bygpTT6w2EcjCkq9j+GlR9Y/QV6Yr63J+Vqp6ALADac5KVBvzecysfMChgAOV590BlafxbH+OK96Bxvc8oVdTWOLb3gNukQYBXmTsyvcUGOcTAmYIZHoeNjWrEn5aoWdCWdK9ftLU5L0AUGG/ymCyc6v8Y1W+uMDB+TFqejCDrU1SQv6EIGVcw5bX0LlvuRck1G0EWKoJ3tf7f640PJw4ypxIZ0i36gRi3o6FEG5gRdJLDxujFnJ8NywBwlUmZ/DR3d+WZpbByOXZzLo1vMzVSNJ5MLNz2+uoaORuji2uUacyIE8o1GuaMADB6bToqk+7Wggk7G9bgidMazXlg4na/evH9HHhQ1vgTUIniCPmCwzQDbsCkinKBLtHiB5qawLoel/DCXyXbLmhB0DleqhHOhblXgBBV0aidvEdh0+y5OZWFLrSxEox+hswhsMBV31zk+l63MyiJ0Vm/ZlTkumlnC7MVWZmkNXWxgx7U2FTf1yMp+UeKPNSgs/iVqzbXLesr6yEYI+iThLBy7dcwW7yuDHKtPPk/Nq7pxOpcz5JnYJKB27Ay5t+tsmWJnyL0NqshYjoUHQ+7hXi927FaDjdg4LVY059iZ5QbB1ikrwNYenQoyuuEWAo0duxVg0+rP41tn9b56UtYTqUxaeSL9WlwdR6xRZ8itYDaAFKvKkbiklee0SAHt6IP+uBbjYEyWb9aBDRbSGwAq5K3Fv9I6UfShvKd/1c9z6wRcswl5bGADDNSlqDfqiX9VbwTU2T3KLE8BbIiveB8RMzfGl23v40VazGBDacOpwtniM5gHmM1t/wHkqFATn4HlmxFm46XL4DS3sU8cGS1q5R80rYIcua+Qh3TjdPtuWhbPYG4u9++o25Bggx+ick7G4r7xRt+y4NgjCTSsj6McpxHu+A0s0uItOs4Cy0OCDZghjmiQxLTFvXBfExsJ1HO565BrgQ0dDr2NAGeSAKuFG1w6ZFpcG/7UaRV3IdcCG1LIBoDVjI8u2ajFQUI+sVwLbEgh25hNN/8he2Zi96VPc3mkORtMMrFNV4fBSW7+82Muj6TGoYr9UONZcOwOr7PNnb+gng7cZXlgsMEnWbIyOwfoqFlu5+8SBAAbLbLYuCyCbTo2jiNqbSt3tJPlIbNe0MCJZr2ujg9oIdBwVhOeId0jdVxheQCwZYfTUqvOiYXMZwtnj9PP9hyQpWa5DZDHXJYkq1TBqSnqShXZ8bbIYbty/AaOx5BBnu4JpjFXqqhr0GSJUbSjj3g2atAaNBusnY7dYHUpGEm/CvrVpbgWfw1XXWo/y5OH3Ezd+NVZVtaTclddN44gaNDCI7dYnqRjT2FHCCAEa9VfC9skmOuOPYW9XtRRm14y2fZFSZflmfvFviwrdgY7QyxnsDPE8syBbafcSwZyZnaGIE9NjTO0QSHHUX9RIGdmOwA5BT4KywP8PCNb1x37NcBmq2/FX+aOy8bl2P8HbPxsASCHS5d9VrfoXBVuZFlL0GdL8n9UW537yn46Rp/lOdk8kRuc8OleOizPsfMFsQ1n1SzPyQ7DGPL/APUV7P6voM+yAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyOToxOS0wNTowMFJpP0UAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05QTC5zdmchb7HDAAAAAElFTkSuQmCC"},"169":{"admin":"Oman","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADB0lEQVR42u2aQUgUURjHhzxE6EFhKSiUMLoEHVpICgqEDoEgkoFhmtVFukdIBLWB2UUS9KCHCD2VRFAnpdMGgRsUeUkCy81YWYkyzJAoo2D/e3jwdqdnq9tM89vDn8e337x5M+/H933zzXizM9trdsWCo+lP+0cOvFnryrRlbv3iF9qfFzSw3jXEaxt6AStCYL09V3diT4Vtnxvf+3Nfz0Jfy0DrgsbuxwIWYMU+VHWPX1idv3Jw/vC0bf+RmIm/Wvq4u2f48gvzX/kvPu1KnT8KWIBVQAXHSvVo9ViVGYGEwkr7/ckHLRqbUUr+gAVYf0iFq8mJ7GSbGZlUbgsgjWWXj/xJhYDllBDXrk1ffdmkNKfqSpZs/NSW9rTssrjHKsCKNFhmgltO9J/pn9NYKNh2ngoBq4C+Hz0y2zikpKZYpQik8ffHzzpTd6VCwbTIJzN4/E7TFx0l1ZyAFWmwljp6629ktc15zSU4obacGr45cs+ESSq7fORvzqA5ASvSYGmbzSilsRmH9DxoIqVulgp5O2LZDQvAihxYqqjMyKQIpK6VVPZvJ5OHkp2qq4q1SamxACtmNzkVsZTI1ETQ9n9NP6x8VJMv2HPRS/p3eAFWRJ8KleAEk927Ekyyy8dsmQIWYBWNW4pVSo7+bw/lI39VWnzdAFgFXuaoilIqdOmky0fPhjrWpVlKxIoEWOqkr/d9n/88GhfztJNpPu3m6jbbYtptH9vuPvZX/7X5r9P/LKVfr/u1u6/BZR7T4rkkplKe8uwYtlGzbba6v98M/lnKvxIv+BuMhlEBCwUsFLBQwOJGoICFAhYKWNwIFLDQMDRLAQslYqGAhQIWNwIFLDTA30eEDCz3G+f++WF5VrXZW27OX571uJ8lcGA96a5f3PH67NbW65UTzZ9PT227jYZLtXcBBWvnpYvPK455XmLM89BQKmChgIUCFmD9X7ruHQEslIiFAlaYwQJrwEIBC7AA6x++ugEswCJioYAVwU4SYJUOFiACFhELsIIBFpABFhGLyiyvvwGHAWiwJ1sgvgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzA6MjItMDU6MDDjephWAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9PTU4uc3ZnQpD4UgAAAABJRU5ErkJggg=="},"170":{"admin":"Pakistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwklEQVR42u2dL0wcQRTGsW1DSLBYNI7cyaZBIjAIWlVBBQmpgqRJEW1IBYgKakpdEYW0iDpOUEETDEGCa3J3SVPDiUuqqLiKz7xkspvZ3WF2lvuZL5fLsvuy87vvvfnLxKg7Goy66WhvffCltz679PrR7NLE5ItnE5NoIzVNsGb6m/9m+jQPYAEWClgoYNFIgAVYKGCh4wkWww2AhWOhgIWSCkmFgIVjoTgWimPRPIA11mAp2oXW+ycLramzl2+mzgALsCrBtNH+9nWjfXh7cXF4O99/92kelwWs4io3Wu58PFjuXPZ6vy97ilZeBUwU7yWR2jv6Mdw7upn++/BmWrp4+qG1eApGOFZhFeJKdjbOne3Oq53tOAm3kXUbYOU36snK1YOTFRvh+dqv5+drcRpbWMspG9ZTBqysxOe6lFQ1Vky/1DuRR7pA65rV4cHm6jChEoIay1U1oRtbTK+yiKujYFOw3o8+643td38+3u8mlDRxLKsqxlWYu7FpcCG+dwpoG4mNMNG5CsDKb0LbkHPHb9tzxzHjUYLTO8l6Y8I9uQKfVChVE2ZFFScJ2gR3Pfjz9HqQ/66Ee8yaD8cK5lVS9cvuOhIhomcpnqykbFUIxnRTwApQV9VbXVkPc73KxqwCn15hQmDJIfKjqjfd6OkWKf0YNDupJK5eYUL11jiD5Xbms7TeqRvNRfokZYYbkije9RSfOqZesFQ/qepqzBLIcXYs4eITldJNvfOVifb+AKvoEEO9xTurGxoM1tbu989buz5RxRluAKx7AlbWnGAKs4SA1eDiXQnOJyoV+Cw+xrEC11gxF/cBVuMdy79XaEe3SYg4VrBxrHSGHgCrMSPv+dPPWb7FXm3ACjBXyAAEYJWciSuaEHU9aZHiPXBCdNcapNnAtXU1cKxyQw9uzKnN5WnUrbYFgIBlf9nuLsKi7pXCiii7ErW2jXSAVXQ1qY9qT2LMhK43Jpj0DmtO0NRYVeYQfTzM7gQM62S6pzzSbr5IYoYAxyq6E7oKZLqn3bXsg5pd/27TnLstTPdnaXLSZzeoIatUXT6oyWn0FGFhVd9rSDY/QevKhM53IBX61C5h3SusKrbkjgzBsfyTY6jaK2z1lugpNIBVrudYbkC1uiotNmD9O2BVP4PUZwNZdZj0rMackgVYoSCTi6jiUUlebv5Rf6v76J6NPJyc4v2uD+jW4II2bqgq0mCBPut7XaPr78kZ9zgWClgoYAEWYAEWClgoYPFv5QALsFBSIQpYgAVYgIUCFgpYFO+AhWOhofQ/1BYbV2meOR0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMwOjMzLTA1OjAwiaeTfAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUEFLLnN2Z2wkW34AAAAASUVORK5CYII="},"174":{"admin":"Philippines","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFDUlEQVR42u2daUhUYRSGJaLlR4uJZTsiLQQVEbZClpBBP6KFEqSiAqXSiAptgWgxWpDqh1HZZgtYFi0IZkSiNGgrU1NojqVlUU1pkmUMlo7BPV9wLne+6d65d0Rn3j8vw52ZO8z3PZzz3nPO3Akrqnpw6/PqhJaNeaWfw3pNi71yAwq1QNsd7Rfb4z8tapjh3rsvNTe7wjk2JnH77cVYGqgFYHGtnvo+8+fQtVmHKp4mRexMcN8cgmWCWgAW19JD9nP1o5Ym7agts2OxoP6CtdNdVbmr/d6fO1/miyPK48aeTb9b3uQl342oy4xNX/3rrgsLBzUCVnhTeVFTm6P2xLISz9yPuTvWeDa5JhxI4q/hbmzYvQUbCpKxiFA5WEpk8lyqH3/8cGubwzXwIKnnQ2NrfpUsUdpznamNH+HGoHKwlFjl6VFnS6lpm1KxcMxTUjoiUqRPN0ZlizmnUzNK+mFZoV7Mu0iCFKvIb3HX5VPJjZ0ZWdCv5hvKFgBLjcjM5pVlg/TDJFNyY9uzjr9xNCBRAqyAKLmxFb131zxch0UHWAFRuDGAFUAlN3ak6PJJZxLcGMAKiFITCW4MYOlTpYRh9CIATSSAJVUqVYhKmM8Sq2+lJhLcGMD6V2hVGkGidq9UxUT0MlG2gBsLcrBEBCJQqCLP6vLUFCKYOFh0XNXqJiUQeTFWhxtDEym4wFI2vi3n9fyECJHmlMgkYhJhoRRX6Yh4Da/jE3z0LkVF+yitKnvaEqNuDLOvwRWxlNij6icSKCzqCLAUXAR2PD6xZ/kZzJQtMNITJGCp4GAA8TY2pULezBbvopjH32UCLIz0BIt5ZwM2Ip2x5MhHbmSqQoq8l47pCf+aSOTG+o6LP3Z9Ija7s4LF50uZoyJQ9CClAouXIehsVuGluaQgN7age/p4Wwq2vDOBpWwSN90iYmkGA/Wr6jw8IdJshdEIyq5VxQyZpsCBkZ5OBJaw6uSlKOWRKuPLRmOVNm6J8zDXpbL8ButnPB3L8NKO9ES3xnU7vy3qxeQ9J39DrdIwA9eAHC8GhAVgafAybOc1YNE5ZWD92ewq+pr/aUSm7YizOCp6/6yMQnfk/dhnUKvUeCpkppvXokylQpZkDadCVrYVKolVBFPD2QuPr0ZVLo8tn3fObg8fMHrM87TwyFFb+ONgUvpeWtW+xtrP8te8s1lT/8y7qkUdYPP+Y17xStucmuWJj1LyxZef3XdVTIFs0WXboP+4tdoxn/IfNbhilpUbdCHFUmqgyw3u4lfvXk+vy0jN21bi2Dq8ddI3o0sDNaNWFEgVsAQorJnT8QVS7pxexo2+PL0SG9zFwFLFHqMtHYpz2paOiZ9vkHNyVicUJvZBZOpqYFETmjkqL01o3g2UNaFZlDLThG7uXx755IwZ59Rl/E1IRCxrx2ZoGkL32Aw5pw/NW+2Zz+Gcggqsjh/0I+fksh2NzhksygSAKeTAsnQ0uTH72vqC78I5YcMAlplfVHPnhGQHsExpS/zbuPcx5JxEmQAwASwzzql+xKmcS1m8wQIFWH7q91+3fxYnq5wT4hPA8k/JOaHBEtpgWXQbIy8NFsAUymCZufGal9EUgglIhShYkltFinE5HdMH5kdToMEIluTmtr4r5l5GU7CsUG0q5BOYfMJT12gK4hNU6rEkfyAg/kYADRaoVeWGjhlNgYYEWNLRFCAFNQqWlwYLYIKaAYvKBKqhXsAENa1/AQfYIQ1BB8m5AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMTo0NS0wNTowMA9wxGEAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BITC5zdmf5C9amAAAAAElFTkSuQmCC"},"177":{"admin":"Poland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAsUlEQVR42u3WMRGAMBBEUUJJRZEaH8xECxKQgABEIAcV0YCFw8RV8J6EnV9siYiIGCDVaAKEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8QPl2c/pWgxBclh9m+/1MATZYfVaWzMEPhbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshMWnvSpIDkC2ZYo+AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMzowNy0wNTowMBhQC48AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BPTC5zdmfkDuYeAAAAAElFTkSuQmCC"},"179":{"admin":"North Korea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADF0lEQVR42u2cv0scQRiGBwIpTJfC0iIBq4CpbOwstLMQBO1iE64JClb+A0FIYUrBgHaSOuYfEGx0CYmFYAh4Bo6IBEyahBAwFm8zYZxh9vbH7c0+zcuytze7N/P4zbvfjJ8Zuf9obOUFiparhi5AAQsFLBSw6AgUsFDAGlp98PTxk9Ux+qFI/xhdVI/GP3TbNO+gNl/N29X3rz/dQ9Fy1fx+tn/+7k0T9Nf24e7hbvz5Im0Oo1bRP9WpOb0wy2bZ1s+d0ZPRE/d8Xi2rnSbcJe8d63+qcp8n/nrflaY5Px5NSQELBaz/9cvV+Pz4vPT81eTS5JKOmzYNAdYQYPRtu7PWWZM9/PPxbPNsUyoDax//eLlztHPUe7h4sHgAaoB1hzG8er5+vH4saP59uMluMlt1XsC5n0oF4tfp2ZnZGYa81WApPv3c35vYm/DhIr2e21rYWhCCPvikf7PeZe9SmDLwrQNLSCnGhJGS2nFIkMV86/vpRnejy/C3CCx5oxg4FJ9s/yRHFfNdqa4HgsTByouF4pPrycITooumYiQoJAiWgNDbXPF4Ex/zpLiuZMHqrkxlU5nAkmEPq23Yfa2FWxB8OpafIyWRIFiKGXprq8f3CCNZeN1XKVaASAosN62gIa8iigggd9rFyCcIls9d6bymtuJ3URJV8cm9lz4FiKTACmethEJ/A6+YF2PnAStBsOLf4/Iuy2hKjWkZsJI17+GBV9zKm3MKryHainlPNt0QHngZ/P4WiHy+ynZypBtamiD1TVV6m1Nmyxd1wovZJEhbuqTjToJ2FspdonETB74JkSWdVi9C25OgnaOP307jmxDJXbUILKFgJyAUb8JZqLB/Eo52m2ybafVGP0WvvIvTvslU7YAUW5OjtibHxy22JgNWjn+mUDSyVeflsQQTqQTAyhHJbGNuKxgBFgpYKFo6WPWUwai/harLn/TXfnX9UOfzmFSL+6ADLmNEiTC0ksJrdqnCtIss1lOCMablOnu7v99bvGcMBWcpv1vFk1A1GaUcNwpYKGDREehAwcI+o0Qs/iQG3A+3LATc+rwtfbgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMzOjUxLTA1OjAwM2Aw0QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFJLLnN2Z+lmK0sAAAAASUVORK5CYII="},"184":{"admin":"Romania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPUlEQVR42u3aIU4DURCA4dlNg6HhFAiSOg7AIQiiJJyg9dgaFAJUT0AajkAFZ2hANAFEBQ5FQoKoeCwGW7ebdMr3yVWbff8+MZkqYjCYTCK5u9F49vhxMT35fn0qpfTrVa73r557o+bhczkf7q3fz64O90/jJc7jIO+J1AHCQlgIK7W3uI2ZgxQWwgJh7Y7kgwZhbaujuI8vYYGwEBbCAmEhLIRFe8yx6IQ5FggLYSEsEBbCQli0zByLTphjgbAQFsICYSEshAXCQlgIC4SFsBAWCAth/UP2seiEfSwQFsJCWCAshIWwaJk5Fp0wxwJhISyEBcJCWAgLhIWwEBYIC2EhLBAWwiKnnk+wFf/3cXPdjGMZi7j5e7RpKyvJGqAbK4OEO6U7dWOVUvr1KuOb/yyqy2oaEcNYb4wpVV6/I3I8w7RHvwYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjM1OjM1LTA1OjAwDF5tAgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUk9VLnN2Zx5hAsQAAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"188":{"admin":"Saudi Arabia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAKKklEQVR42u2dX+ifUxzHd0Nu5O8NF0grF1ppF9gKa6lpUmPTlj8pFFPChVxpwwVCzNRMSwprI0sSYhfLSii1ws8FaRe7IK2Y0khR39dz8fp29n1+z/N9nnOezzNuTqfznOdzznM+7/P5fM7nfM55liy5YOuzq9bPTC/Zet41uxepU57CLJrztdW9h81plh/PLi12+YqMH9n2aQ4G5xvc8qCMNjKNgNV8+HLPudzD2oR+Sdb229the+7WRz/Pyg9lF2VUUk53V21dxnlkAjlHzTjWT27Y5QDQrLcKSaY4yqW7WswH7vjmRHPKoU3Cfhkfx/7IMWnzTfj5KHeaAflWW/HXevFtu2H7OTALcwMumrOgpCwfllMZZ0MOF0Z5Z8QY/VgRLNrRf3YOA3yW1TIW9dfXZG5Sf9YoBVJhcWZeF3O4vB8r5oo7qEOyu2puPiPr0+5qse1cH4sjZhHjPTJQ6qXILMa0ZWRzYMVhcL9bPTk2jkaztVnP+LbWgCmcs2XZ1asbwa4e4vN50ss7EfIp+kWAVcbl2HYlmILA+SYqLEnPPvT0fTfsIfVbU+UJhVnlLdK28ng+EA8rJkJ4aGYM4v33fLCw7c9lb+847c6LYeeq5a+d8sC+i7Y8v7BxzR2H3r3iqVNvWrv3tkc/pD55nlawIxW1qv6kFUpc/8rLd63Y/Psjn+w/c+c3r3z01WPvn07rlLjcfaMntLLmjNfff3g5NEmXfvfiS7c+SB767hsl9Apqd9/+3oZnLuN7afH8t57btf6461Ozk6TMbe0Vwv5c8okh9qAf2HD4ukPXU/LRvT+s+/Lm3Uu/Xrl/L8P99eM/f/vjnu2XfvHxvrvMPBhDzSffOPjPmwdhNmyDMuWkgAZq9IH8kYPHHvhlJ/njD/191V+b6A/lgN4QNBCh886rC799ek0l8yZ941ugQA+hT/nRl/9YOLbCUOPrpuiUtws72Vi55dMsCpIxAOKWc99Z9cQ2WM6gw07YZmYc3v7rpp8e+n7z0c+O7LAcggEGFu9aKgAUs5D6SCPYSQkAoi1S3uWppQ5tARrgRc1Kwk36Rv0UslDjWyiHgukgHUMsPnqIbsjsLICpzFfySKbPVx9ZsbCSFHYya1PAkQJK4AWTAAeMpAS5RX1kQ8WqCcupg/RCItITegV0eAs2+6khC33oUL9Sx7IReQodUkp411MCYE1NnvmAlXu3I5DwFLAYRNjJsMI2wESeFKD4KUNv9Wd1CX2D0jRhJ+/CVKjxLuy0zIMyUIAOoEEmUYdy5BySacrSmnw1Ty2NkNOUMCWoQw+ZWpWlZWtyZBIrX4DYDGB5jpIaIrZ4YJ6BZfuDOQ2zYRIlVn/kYR4SBfpIR7cFsGwzXXjtC2dtfNv1DThAQLmtQFuN1KcO8pI8rfjrGB/opPK1hTumL+O90SZ0ML85w8rwMbgw1coCZpPnqVWPDWQkEFLEqgoGIwkACu8CTRQlzAMo0IeOV2qz1JzlnJUpdKhJ//leRoCJZPUKHD1VkFi21cI5eAeIjWwwezybycNsDzesgg2w3zaT1YRZ4regb4VITa8Z7ZKAso1ruzlSS4u3gIWVnWHk9WMKLE8hegVlaFZvtfXn5XZDTG1CR9vIlAvU0gWWwDwGGkCgPpjBVky8BSB4C7bx1DaTrR/beQALWEDZKXKLp0gsUttqVn+UW7VZwdEiSpm+URN40QrjQ/+pH3TrqdCpwLk2YmEGgwg4GHTyKUQ8y0nNbEqgae+UrR8YD6Cxn6hvIz1VXkhHOwXIU27QG+ieMHagkFrOofiqvk1aZDpVHru+Nrn7XUvGPOAAsxm+1MdtYAEaht55m/+wB3XpDRnkAeVWUjA7BavlRLXUn9Chb9SHmqeB15t8F3SscGmR+rYpqWMnC21NqdShIv17iyDtEibW8i3YZgMZGcNQ2gvlhT3lMBUw2V3pZbllibdxDNBU8ll2OrWM8RaNlbJtNcszywmvgqljq9G2XWqrDXzkbhFVOGxwX2p4SiV5QCtrY/IUVtl+MoPtqLTcMixcx+tQb8ikStaAM4xsCdk5ArD4FqAGsEh5CzqWWP46U7NLYrADsfVtBQof01Ov6SwPbK+43MoRqcNct3xCsWKke3cPYFkquBzmecvFIAYKtDUFrKRd14cCNWmXPlNCTYCFzIYCcheA8nQRGyt0BGmO07216pU5aiOa4fN60DIDxjDoDHflCJhQgzG2xoCmoQN9WAs1Ky9bTvaBWU3bEvKU8MY51Ozm8KaN3bD00+4P72B6m6s3YBWKbhgqwlpORWAEw7z6Y6MDlpBacZi1XlEaiDDJO4YwEspWPVZ23rS2oQ1ASSt3pbZovE7kW2gRmHoLyE9dx3umUKZ8ykFa8rhbcwgOdipwRvCad/vtILVZDYw8v70baCDafUqJHRBQqEx79QRp5O0UWgR8qZcLy8nlVp1Wyn7qdV86bvSB1q36GQe+pZKacURDp5j33GHHkxJY6EF31JSX5dWGrtQoMLKqsmLyyqtSW7VLBys+S46pWCgpxDSSwvLY4EsB7RRqADf13k3BsV9F1pdXLLt86rIt0CRNNzTqY9gdC6DYr9aB0V3cj01arw+h7ndPMIewCH1Yvv5QgwMDux/eagvu7pH7sybAfNH0w55OCOHHina5Re4LUeYLvmt+7CLm2cPRH48c4wWQ+U5pD3sLxgkcpEU2lUNf6xP5XHL3Y6jdt+OyXwoyruvIhlKX80mUtkdtI/MiSwMn08VlMRVxnGkzp4N0LDbHydHKyXHhec+HKcYon6LJsC7ujGhTKPSvTf4LMqwvf1vJCdZE0IRe1/TrSo08DeKcB8xuvPd1M1NJv07zC4a6T4Pmvu/m98w0H/n4fruxOw/TK4dOkM8tXWadRU72JR1/scjeX/x7kevhPrAW7zB8jvB0NLoP4PtstFNiEJxPU8fXO8Y8TR2KSD5NfZ6bsBnHaDi2zGF9gX6elWVLp197pVf5kQYD+h4YUhiZ5utrNkmJqZqVT1Of3nZMWHp8w/d4tdhcL+NMboKHUf4apPZoK7FTBN+R1peQz53SN2LFfI7ZUs2Rqz4b3YOKLD/5Q6z7uvjD6g3qerunbWhKk3dr6zjQL72ijfIDNx5ee2jdIndfxXfnZv8D1kC3Aba9j7R1/SYX3bYNUUxjU/OZ87llWOg/I+Sg0yXcr7vl0bxOtEj2wVRhHE90X9EBYwmkGeqXMD1fYxR/uKOpjDK/ThnNbTMlh3ioqKzyC/WSW1s5/ljReksnZvR6hG3poXYVx/Xzpp7vII0TX9AXWMf1F9McocyjuR8rtx+rzNpz2N9tjnFZEC7+MH5oW+R/rg5bs/U/oePPktzsHJdEidDzwQ5QnEyHKfqVZBEi1rvDN7R9kE8+xbef4tzN17O7IYe/O/7RqJLHWfNd6VH+otueY97L+5miHbgY9nfokXcp/j85E1SCjv1bgloG/aqzmN9S8udvxXc8/wVl7ibO/UJF9gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDI6MDYtMDU6MDB43n6GAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TQVUuc3ZnghxoAAAAAABJRU5ErkJggg=="},"189":{"admin":"Sudan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACWElEQVR42u2aPWgTYRiAP3+hEGj8IZQ6dOgQNaT2SIaAQ6EUBKmaajSQtDFSpB0EoaOEpsHJblWHgktFhUjcpXQrIg5msMVBDLEUQYqKHRS6lHM4h5PrhaZ97y45nuUZQu4uvN9DnvtTl39fehYdXqp1v48dXlkNBuNxCPdPpU5MTZ+JaD3Zmcirp6lwn7bAUKCYWAZDtyavnE0W6on+c4vVQ8ffxb4xICgglsGDV+/cD9+93nlxOfocvaCYWGaSSOiIWNZEMjIoJpaZRiK5ioTCYpFI6KBYJBI6KBaJhI6LRSKhg2JxoxU6KJY1keiFWI5wh0QWAzkt0fSP3ttW0PXZuiSWQCKLgZyW+LeVMYJdDML2KHbbWj7/74j7WBjb/dgdsdkl38WeG3/e9KwafsdVsWyvIs2iQF/QM7HMiSxfGH4wcmP90+0jhTz0Bz0Wy3iTwkjk/IG3yfKCvq7X9M+w3emxWGae6i2FBm5OXKs8Kf76+niztqFYHsQS5vnvj0rZ9JuT9S/V1ywSYjnyH2Yk8uePP/rmRxYMsYQ5sVXZnrlHIhGLRCJW+4hlTSRLiFhcRSJW+9A49+LUHrHEnjmSQsTi5B2xSB70/w3S6dL4wAeSh1gkD7aqWNxhRyySB1tWLJXO94yeTg2uabXMy8yLTBn6g96JpYbGuh4qpY6po9CPdP19UaX6osF5Ro9YYslTqnuuY5ahI5Zg8lAKscSUInmIRfJgi4rFVR4UFovkQTGxSB4UFovkQSmxuLEJpcUieVBYLJIHpcQieVBaLJIHpcmzPCjPv0cRG1YFsTmnAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0Mjo0My0wNTowMK6sX9sAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NETi5zdmfF4V8gAAAAAElFTkSuQmCC"},"190":{"admin":"South Sudan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE0klEQVR42u2da0hUQRTHx0TWTBGRBDPNfBRKZU/sQw+z0ErMoISWsqeRKWFUSGVRoT3dqGwjqehFKWokFUUPCoL8UPYgyqKyBz5IiBL6VBAYdL6MTDPM3Ttzd+/u2Q9/5O7s3Ou9P8//nDOzSMJSM15VOIKboyalbyBBJJiEoqIq0IjHOXeupUWcyJp8Ns/RkpLizA5qdDyKnoi3BtWURjpyC1pctIYPnvraNSXkUOyyGTvxBqEqAwt0qHve8KYqNEpUxWCBolGiagGLxguNElUxWAOiFwUZGiWqArDQKFEtAktcUSJkCJYysCCGYUWJqhis/6CGRolg6VasKBEsl9E2hNHxaJQIFholqv4GqRa8/s3PVpSJY0dUJeTGr4kvi9/GUxgjMxLVSiW6cTFaS4LO3lRedvP+tfC2p2+zO6s7qzr3oNpLibdgkunvp+UvXXDnsHtX8+g3zq9bvw/9dasfXzZ5EfOgwJj4vQvXX76QlLho8cXVqjph9JEZ30pfPth3aWVt6e1TPbU1JccTvhcc+OL67F31nSux/ncRjyfmkQKd0FU086TziLvuUuFFgExt5KOXjxaVT27c8eF6UlxvZtb7F1H3Yls7giK3xnSA8o7QyhspHi8eIz6vzBGZMxqdx8z9kRnPu05ixsJYsD61P3mVkLw9u2ZY8TEdrQpak8/N6jnfuLl+bPDa/se7Y9yjPsnfaPEDMDPGzNl1zKP2LPJYE1V5FYDVM/3589iboPPbNsTs7WYBArt0rqgoqjw/zbmm9ygxELE4Rjn19LSyY/nHC1LuL7hNQ4bqLSWebZgRg9Xf/+VKSF/r1bsdGQ8BnVUhlZkVY8Ao29+1jkxZDu/Cp2SuAWBi4xYLHGuUvq/+98dA5K1HrCxYf7Z8dA0uhiM/Yl+/iA6H4/AzRCxxZOKpzJ8BzyhRvQYWHRt4cQJGpv1esvxMG0SjhvqGvjktABOtABONFMQtmaqQjUZGSwqeUVqTwSBYUik21Ho0RpCq0wCxYNHv3k2/sSPzGcyju5vPg9WORml7sMRZFA8s2uZ4eKkCy8ynECyfS955FgNWCNkSoAaQsUjRR2gr1BG3ZKwQH7wPgSVOrunkncbIaPKuCiw2ece8yvZgse0G6Gnx2g1m9nWxZQevL48P21Kw2FqPbj/K95PEDVJ6fpkGqTzWcBzrPp8GS6akZx8qb0lHXAqYX0TimR0i5QsLQcSzJiSv804vQqvNn3iL0AiTt8AS33Nla4X0thkzMYlnebD1D7bN+Mc2Ff9W4q39ouLjuNHPzzf6WdnApD9Lb03uWvyzqDcS1V7q7W/p0Ja3tjDD/ScsYtO3ZYlxfUd7cwYlPzyZOTcV1Y5KrPkGDq9tATokuqSlfL8j9OCu8bNDNh7oHtfEqvhdVF9TogoU+X0HAFP4uqK66o7Qp5WVeQUyFwpgySuLo9EZWJTFZ5G5Bvnxns1j/fXwlOg2uAFtVcrsjMKhQ3XPH8hK9C390pkTmB3EJ0QkgMDS0cykzc4ut0Mc/BFfDyOWgq+bGjQ7ewGHYGmxQjF2dGUXaBoIwBktGkzlWHY0O4TbPFjKqsIBiy1+anYIlqVWSC8wB7LZoSoAa8C/C0CzQ/UoUhI0OzRHjRGLXbNDRbBMgYVmh2Dp0L9y4qRFG3T6ggAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0xMS0yOVQxNzowNzo1Ny0wNTowMLjzwZoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMTEtMjlUMTc6MDc6NTctMDU6MDDJrnkmAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TRFMuc3ZnXZEMEwAAAB10RVh0c3ZnOnRpdGxlAEZsYWcgb2YgU291dGggU3VkYW5wies0AAAAAElFTkSuQmCC"},"199":{"admin":"Somaliland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFJUlEQVR42u2aW4hNURjHjwcelFseKCSK8kIZl0muIUIeJkkTjQhFokZTck1NITIT5ZZhyKVJlJmUS3gYmkwnmSkJQ3hgzJTGNQ8Me36n5l/bGc1x2M7xn1X/Vt9ee6119vfb37fW2hOL9V27fsiwLNQeqz8PWp7QsD1Z+9RaWsOa6T+g+8nC3iMuqYbtyYDrU1dUNWoK9v77NnUdG1fL4Pi2rROPDj20/d3ke/RDG7WEUfvJiB3jnq2YZvoPmFtzqGLZ0xmjD4xfPHDByGNrVg/AUjr7VvHxLdQLB1+cUjyBOo4EkZ0512Yd3DW6366V8xbljzvxad1m6rTk6uGi233OlNLDtSsPn1XviI990Vhfw9UVtWdXbjwAatR1PqgijiW3bu/5vJE6FhDThrrBilhBBCyAAPfj+Mru9beuvweFC2/v77myRMGiveIFjtyF5eSRuxsu7Acs7LR/++TT3paWxsb3U5vvMHr+1/Id6/O0h+pRDadqXypezA1lPrQsOXfzcVkRaGJnhgYrgvWTvvc4A0c+WPrq2+NqoDlXHK+vvIodC9GFOo5EgYOWwAQK1IGDuxgRIFobWp+3PtpYfunb7mZQAEHwojf6Z85cBUdwZ4b0Rv/Mf9KXkqqFtyNOlL8zeuaCpWkOR+IY3Alq4UgDIjhbUyd3ETk05uF+8KLOKICoENNelbsYhQQHgvTDiMxQ58boIz4U586cn8ErsMwFi8iBkwDryaqm/c+7kaSo43hwoY2mQuDA5YoaoICjrtg0zhHPAAVEdM2kqCk6tCTWUidiMUP65JVIrAidCqPaD/KWhxOiLsBJK1ylpUIAKKBAjMECcBpLuEoi414UlLlLZ4IFaBIot63DiEbMk2SaWP+1/S6dgyNWFBldYg+RAxfiJE1kKEAQw4hn2HEhbuYuooiCCxDaj+7+sJPCsGjUARQOMhhLNxa6CtS9KnHOYEUWsUAKJZHhKl0Iv3nd/LFpH3WNBzgelwMlyVHjDdAQk+gBx7MJACNG1HUVPWicI/3R57bTl7eXTAcsLLqq09RpsCIDCywUDnU8oOB4NGFvi0BECBITbtbVD1FN120a81DWXtoz/QCKHlgwFnPWYwWtMxa98aoYrIjP3DVVaTTSbTx1ENRTKz3wBA4crGsmUAMv3SdqjGQmWHThz+jYNUVyl57AEcnon/ofAevvwPpPTCKlR0OiAQVNQzhGDwL0uAGwiAdEIEWKHsBIoUSJbaAWBlRnRT+MAqC0IZJpzGNjEU7KjliRKWsRVLf9OEbPtMLRRZOdLqU1bQGELvOBADsj6p5Uo2Z4v4nq3lDb0INuBagbrMgiFo7R/RRRAURINPotTz+wYNF4lviELJ+KOXQASo1V+ilaj1gTxwRt89QoCEzhj9AKHzPhVfnFx+wMAEt/anZosv8mSKJhmFLTpP10PJ/w/1ZkhxcSH1Ct1rRqjJNfqzW9arCsBstqsLJM+SiE+mkYrLRp2fygnO0VFD8Ng5U2XVTaXvw0DFanla+Bmvju3whK/5ygDJ8TFCzaxinSYP1Cq+JBGVPRXoCpS1l7wUKhDXf56RmsTuClSGkxUgYrRa3pGRSSoCKFhat+SgYrxbilMClk3iEarBR11fKgkPKIT5RpLUHhqp+Swer0cShnV+EdX8dXrQbLarCsBstqDcBqelVQUVBhtaZXY88exH78Wa3pVT8Cq8GyGiyrwbJaDZbVYFkNltVqsKwGy2qwrFaDZTVYVoNltRosq8Gy/of6HX62pI9K9L2KAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0NzoxNy0wNTowMPQqvugAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NPTC5zdmfV5vyDAAAAAElFTkSuQmCC"},"200":{"admin":"Somalia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC/ElEQVR42u2cMUscQRTH9zMEUqZJLeQTaBDEzsoiINoYxMLWQusgGpLGQkSCgkmaQLqksLGRxMIiTSCBgEdEvOh5nEiUqAhn8W8GJnvs3u56s/t+zb/YO2fGt7997817sxcNDr18tV8LQQfeLzb2p8JZD5pFI0yAAhYKWChgYQgUsFDAQgELRQGrxGqnVgdYIA5YKB4LBSwMgQIWClgoYGEIDvAUA1ZaU2J6FI+Vg/ca+bDc//uhFJsAVm66MPfppPFAijUAKzf9MvHr8nJUijUAK4cgOHm9MXdYP1xtjd00pLqCfQArE1gKf+3h9ut2TaorbF8AK5NuTX9f+vvEBUtXsEzJwArHEzybXR09+KHw54KlK/oUgPBYqeGe3/q49ufaRYqAmNwdAFasmfwgWN6AeP8PAGD95wbEBUE/IKpkit8yAZZuc3L1/6pzEHRV30w7oz87YJWg8aLsZ31z57T1vDv9Nn7Q9+9RErD0ze5mefvi6/nZUFWr+VH1fNXM3ruLo/nkcPRKtUKtFrBKlifJK4SGlFZV7fzMRPKuTOjnm/rx1W6vYFKyr5WQvFew66dG8n0ipcKEZue9wsruExWAVp5uf24eNesXj29XioBJI2sWm6e4Iss14iJCpEbzyxCAVbPmw9yDMdmzKGshr/RgFXGr3PJEXh5Lo/UWrBCwjmjg5FuS0Ghl91jZ128aLKXVReRYvHYRWc6uFLaS7A3do8lJ9oMhBETA6hle6tl1BkV1L6XkySthGpldodEgGNdPlNcRHG7jxa2E6dM4b6eRLQfEqEo7kbQ7QR8Lv/HS+f+Kq4S5ARGwDAVB1cT9xota18kfErfh7Z841SyEQkOqUOU3XrqDIK5ZpFls/tpFZM1XuQm4u3fL98ZrZDfxBywT59nThrwsL5DZfFGMyvtUlVor4fhF3tJBAQsFLBSwMARaMFgcT0PxWPxGMmChgIWigIWGW+wFLHI4PBYKWDzBgIUhUMBCAQsFLAyBAhZaUrDYtVFLw2OhgeodNePDm+0EpMMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ3OjI2LTA1OjAw3NKyvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU09NLnN2Z+iG1TMAAAAASUVORK5CYII="},"202":{"admin":"Republic of Serbia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAI0UlEQVR42u2dXWwUVRTHNybaB5/QgJFowoMxPlU+lKYV1CoBIYggoIKi1CCaiFCQIMXGBNTIh6DF+KAiNQGUBBBJVVbamCBQqApoiomAGIwlRsqDCUj8ijWZ3xj/zeWOs91dOp09L/9M7ty9s53723POPffMNNO6Y/iQEUNMTQurGbsFpgaWqYFlamDZjTDNSfd9ecuz1ctKDqzmc2MqK9bZ9JvFKph+1n/igeG37dtYu3vIM7vKZr8+rKKUbYaBVWBb9eG2Bb8MHZm96b4xNz9llsPAKoBuHVe1alT93GtmXD3pILrrXNX7VQOS//s2sBKhbYOn7n3gzwN1iwctubRt+fIbVmQPn1p7/2t3Ptrxwqz3xjfULr3i1rGA1Tz9lT3z5tAH3X/k8aYnBzQ/PG7x3e/Y9Jc0WPuaJ75xz+/ZJ+bdNfeRr37b8vKWP/bP3P726vNAs3N9bXbCFBSkjrS2VW6oaDrWUD79JT3LCHyKcUCt9fsZg2rOGQoXFazeciLAxMRnT2z95tVrgQn1xU+E7YC1v6Pxqmmj3BUizhHs6Mn44VUCe2ZYpNBigZTaJDAChdaVm0c/PdgHFitBBQuMfGBxlab26ZeUN2PVaMHJGhypAiu0GQFAam+YeKDh2P1s9sSqssEj6HO4blP50KmuxSIlwfiowgdwRGwulKYpsVgkDrqlPbFYAUA68aEdCmwPtsrto+6ScRRQHd8sVmqDd6YW+wEcpBLCHFUADRiFoXegtHCWnqz+GK1blivog0vd++Cov8d2hqMFYNFicKQQLBYNQEDk9HHH6vFrQmgajs+pHBo6Pl0bumdDvII+HHMWpR3lKqGjNDjSnW7ASgFBmFAQaEBBVS0WLdgk3J+e1TEVOAOihPJYISIBEJ9vaegsb0QJz7XF164teqwIGgqllyAN3KJi4YKiqgjG6m+7iqW8pcMWjQKBpcHNKVL0JAvPChGkcIWKF31wuMWoMuBsae5UZnp2K/O5Wb7Puu16LXABHcVIWxQXn+PLByzf3x59T+Lcq57lzHr2fS4SWBMqR2yrOYxOrq/u/1g/t0VVz/rU11Pb3ZHXbZ+yZ3YjG8yuklA4WLFo2b0LUEJvbclVGSH6inyrOH+1qc5v5vIfbq9e9HzZ2ZE1C1/U4+gWV6M/5VN6Xtk1eviSFqbQV8VAbol0AFiwkUyWnHw9fVB+ryEowZ4j/UkuMA52gs3pg8fmr1nYX3/ljFPVPumh+pnx/5aeteSvvu9T7NG0D/OIZgr1h0VfOLqPCxbTfHZQ57bOD868eejMofIQF4moSGZ+V33812N/0fPbXTvrsnPpCUw/TTvacvQLzrLNrE4TvE58su7G9Svpw7GCdcewyfPqa+NPVWFx6buaKSxAIJLrZxUsphOYupq6dnTtwCaFu4HBCo44CciON7YtOnA9PRVBwKIdBUS3cBmrBlj0VBsJWAZKr4GVv7EFrEPzn9uzdKMCEVZNSZCraU/WffQEDrA4VfXp2d1ltP9bHvNf1ko3pwHo9MDTE37+WgFlOwiwevaDSas9i3M3EgGWWqxTA5uva/lIQVE7pHgBh2tvcIjags3zPb3DWfqr4o5zdYWmvQyW+wvYcFnd+RW7NSpSu6UOURXgiLToqbZHXdsFFsbBuk+vpZ/ligoW3zMaMu0Tp7+BVXQFLLUZTDNKC45S4aBFgdD+tGAFdcVHcQ620HctVpGuxYq/SirG6s/AytliUb/w44yTJ0/OxAJxrJPNsZbK0DOOhg4xiJxAzUVKr45LzSfGymdBY2AVxRUqUqoaRb3Vr+bdWS1koVDQVCz0LKp20R2f66I+i2Vg5QBW7/7xClb0ZOuUaxEfDpFjdX8kTvVpHFrUIqqV0usqWBaM93mL5bNVCgGpTsDCSuHmNHdFukGfKAQ71p6uq3W1eGCVgg1LNFg+h8WUgwi4ABaqYNFCH3WIPqT0ilyFLZ3CurxSCOcTtCokZiLG8lkRnXK1WNgkTa7iCsEIvBSs6PFdsHLdJzVNnMVii0Ytihv9EJgz8Xqs2XZsT0f7pvbNazmrx64rdI9B1pfHcrddewusZDrWxIGlCQLXLaKKmiZC42h0DKfX1QRprvUapgkCC1eIxXJhUqUP7o96BCDAGgEQLlVjLxQ75IuuUMDCsboWK9pu9d3dvdSCRTCOa1NrpAG1m27QdV+3GCtwixraawjv2wLSq9BfwXJhirZYlsdKEFhYINeKMPEAgdXRagjfqlATDRq8g6YvbOcq5L1sEzolYAGHzz2BlGbV4+Sx6ANeWneqbletY+hqg7qJ+GAZfHmBVdig1S2bYfrdrBKTjX1Sh0h/8undMu8BWFgd2nXDR/Fyr6WVqHESpPncB58DzScfluvPIH4Jcpz+tGdyXe/E6Znrp+ijYLk1DkDAlOsKDrtFhRYVVAqWvvfB/Sz2TIub3RLn+DXvxbgzfVczvF4xCUq6QavdtXwF4NwsF+181rVJ4RsfAvhATaMoHZkxuSLJVVxhcu5P39KML9/jltoVW5lOnB2VUqBA7VT4dobg/TP6oketcHdLZbA94RtHA7cYtshrQqjTwiLyKb6JOlbTXDWTnK9C5gmLpc8M8iyNvj1G3wkDFq6bU8ViaVmzvg+C2nnFl4iNTL0h0ufBQgGLx+GZcn0m5wLvWQjsUHSdAikMfdqY+nd95gcNX3sUnAV0QyQlYOH+9H9MoLQoZPqKNrdmSzdncJ0Klo7j/j8Lc4IpBEvtFtMcRkLyqCpYAISG5NFbNOGzzs44+nIR96lG01SBxdQS96hF0dd7EIG5tQ8+JTzXcRRQ3B9O07BILVi6TsSKMPH6QiKNrlTdMhiNtHQcdamsRi/+WtjA6mW8sF4aFbFq0ySqrxpCV53uOIZUiYKlzhErhQ3T6Erx0jycqqYzGEHRNBRKFCy3LhREUKwOqz9cntZpaU9aLDw3sP4HMhABNaABMhS8cKaWlzKwTA0sU1MDy9TAMjWwTE0NLFMDy9TAMjU1sEwNLFMDy9RU9B8y2qjgYgPQ0AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDk6NDItMDU6MDDwO6+YAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TUkIuc3Zn1Z5TpwAAAABJRU5ErkJggg=="},"205":{"admin":"Slovakia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHMUlEQVR42u2cYUheVRjHT0Fr1KQPNRsYi8bCxoZE+kWMxia1VCqLpMApWdiKFlODSdRWe5XFHDGTVoSYaw0bVnMLN4xwlvtgGSWjEpYUs6VtEaatNZuJwf2fD0fuztl57zn3vPe+Pl/+vNx77rn3nvO7//Oc5xxlc9Nzf8xNkJLaVUZNQEpgkRJYpAQWNQQpgUVKYJESWKSkBBYpgUVKYJGSElikBBYpgUVKSmCRElikBBYpKYFFSmBJdJrt3NI09k/21m0vDaVWL57ZfldiaCZx6o2RfYRFjMFCF07+l9tVcHSiZunu5cMTo1lzt2dwFY9Ifp/Pf7Sh/AEOx/jmito9FzY+XbD5mj+rV/Sv3nWFOiVHZjYdqjhyE2ERf7Cy1xTktc3rbG2F282rs6fntc+u4mAFqnMm94OqzmLCIs5g9X/53GB7QLA8p4E/iXVeynz7m9YNwZCCogbCwgJYVZ+8fsuJ1vKmpo9PFJYtS/T1visegeIIVCwjO1K9uCnRNya7du9H3V2n1gOsgO7igfVXV8nzjzTOZo6v/W2vGLFdZsjT1n+79mS3nB14bHjZuXbxycV3EVsGivfF24klddrTf9zfnuq7J6vqu+jXIHs2/GbXtTy4an+HS61d81bdwH4dsOBnAAi/xfJTOwqWrO8XwUKkJYKFq6bGC78uWcvjOQ2wOj7vW/fTpPuWSVZvyCtuaWtRH5eVCVabvloAK6OrNP/95MFSxkOTF7I6V14LIIAOYjJchUjIH2jP3Hh89xd38LNeyfNlg78M/YgaEOar/Qye173zq/fOtNtth2S7R12b/2yyvRB2vzP3t3z50oG6k43oeLV/iGCZj/ocLOXwiqQDwFp6sKy843pbn5PdenBVMC9x84RM9j0Fe2idh+BgeX6jdiwxiuK+5Q2g3Jk8NP0TAu5q+C3kpdRg4Y4ogxjLrgOZdKp7NzJXZsuK9cvsqvzwle8fAlhqx0KE9PeSjSVPHkZEJUZaiJlEdFAS3iOWQcYLNagdSwZWqrpWB99gz6b/YaiHXVk9LDwzlKk+WDweElXETgmWtB5tx1IPhe7VvBdcfirM/UtysLwQW9bNQATdjEiLO5YHkzjXEyMwZLZwlpf0FEMq5ozQywzBcCzvrD5Ysq7Cd2zSSm6G2vB6n7n0KijPY8nA8jrYn1WfNzcUVF1GjM/EmSMfKD2XElUHrPC+dT+m5rM/c8SdOpZ6bqJ+DR3H8oNlLeMvpDmCgWXr+3YDaLLpCZ3YWsuxzIN0y44lgIUsFKIxLLbgOJIC+C26EcpgAMVZJDx5ZgvzRO+3bDbqByvZFtCf1qgHO5Ppkcm1kXAso1mhEiweJ4krib5AHmelwbsvVBfnibL7qh0r2eDXZRm76Vbz+zJb1qqvOnksHU1iVqitsgRpGL7lfj7u5klQkoWREba1VhgQrGC7GzwcMZhirTBqiyTmn7rdz8PCko7dJi56tr7q2MOTpacXjb3Kk5aBPAbXTr/wc9loqbljAXGE9vBUk8xQsPjJbjuHkbXXBzoFa4Urb678tLN+uHlk6vd7TDym++Lq5RuKKg7VXH3wPmwCaakrfqL2HRP/A+6oLT2GMJdYizUwEzs1MWQMN7IQHv7xw7bbjuZm+x2o+cU7VzxeKXOFTbP5325pkNWpDtuBe9Ry7lHD2s+Avy9Yqh4OruAfEOFD5VV3t2596t66dfWJ7QAFkOFsTvX9Oc0n1SnBHb15q6qKcRV+ozYo0PQPgpix2moHN+mAMHrKvE6Wqq8KroCJPUJmNTQ4Dix06kd5ACorA+CwRH120WjhuWOI/8irIp3H0lFsZoVvIVpy2cSAD2lYpG3TD5FUocZSy7gYb0HVackwNknDNXO+e6bhcCK+XqWDVBjrhk5nhckqOhWBc+PxfbcO9oaxti82ASK8kaxfa6Yy4Zo0kFl2LEQhfkU0IzsbhrYN1K57czGGRXiJXbxQG6IoQDw+1ZpxYFT9VO7bIbXPY6t+pk4Y6kzU7Sqm/SJetvIrIlKYLvjfiyc4tJMUySZgzWt20xcmd8G1zA0uySq27GG5BimAYLklIIW/dMO8D/sdXH4q0VGXbx1RsLA7CokArCr2zA4MnS6C6+gMkcjvY66HZR9sp1mYSLlXFuWH41vwsNjs5ejhOgjwgY4/NwZ/wpCH/aIAVP+O5t894cui/4jobHQVHyI9DwM6iMMwy4OrYQDFRj++r0sDF/+9FiYQtt6dxezlscXP8zD+H608yDDYISQXF4j0kSJNW8e6wv9x8O1PF3clyP5mkMAisBa0in/cEd+ZYMrAIv/QmqykRSux6LNPukCHQnPg0g9Zu28U5Rw9xVg0QDvFmlEHkKbMsdIpqCSNEFjpFAPRFCQGMVYUQtToJG/j/hYLNHgnp4lX79CskKYslG4gjY+Ls7i/WHiOErZX+f/5WxyRlT05ORZFdaGsbzLqctIweuR/sp02S8QdJg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUwOjA3LTA1OjAwG9J6zwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1ZLLnN2Z0Mfc8AAAAAASUVORK5CYII="},"207":{"admin":"Sweden","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACGklEQVR42u3dv0rDQBwH8CA46ODk4uSggzi76QM4WRUVfBafQAVRdHB0sYhQN6GbgpOouPgIDg6C4ANYKUFMqa1p0kjPfJYvpX+u4fLhd+FyR6IoWl06PA4rh0/Xh/Yrl09nb7ObjcZDNYri/Pi4u+sl41/F7Qyfrl3vn4TYG4Oa/wJWr6TAAgsssMKBlRxAv2A12wQCLBULrDBgqVhggQWWoRAsFUvFAkvFAkvFAkvFAqLUsJoIwAILLLAMhViA1ddlMy7ewTIUggUWWCWHZSgES8UCCyywAoZlMwVYKhZY3XLl+Wjit9fdf5Wt5WYWCev7X/7qBKTpyfTH0+u5yHbuUhzPyPxGfW8+rByrbS7vjte3zudmHvPDituJ2wyxNwbhXLS/H13t1A6m62Hl9fvFwtTL6+3N5Oh2NlLJjNuJ2wyxNwYzWzZChZX5SXXaECbzZ8cu7u9pK0+Wrfcai/eVnxIF2TdYyfd1jSwkdYEES4IlwdIREiwTFmDJssFqmSA1826WvJCZd/cKQ7lXGNbdTKsbAljdEOLKi6j7yqd0n2ZbJ5S9/SI3rLb/b54jT/P9bOu3OmWe48yzTqv9O3ZC26VjabIt9mCpWGCBpWKB5Vk6YKlYYIEFFlgu3sFSscDy9C8VCyxDIViGQrBULCDAMkEKlooFlmssLMACCyywwAJLguXiHSxPpgALLAlWNesG9uS+QrD6m58+1tRKPATTOwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTA6MjgtMDU6MDCvvw1bAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TV0Uuc3ZnN3MeBAAAAABJRU5ErkJggg=="},"211":{"admin":"Syria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADBklEQVR42u2aS0hUURzG79gDF1HZIqwostBatDArichdOBERGYGC1fQQJIJqU7StoEWERESFYg8hjMYQoqKHYoaZZBktGrQkI7Ei0h42oE1ii29zYJhhspm4587v/8HHcO7/3OE7/u6dey46XV1ZM/NycTy57rAE/8nHZ/mW+AALxwELBywcsHAcsHDAwtNhNwpYeEpQBiycOxYOWDhgsRA4YOGAxcYbsFgIHLBwwMIBC8cBCwcsPF3B0iY81lbcPBrdE300fs+/zE28J5EUE+v/21ypzjux742fN/FZcb/XGTodLLn5FMeT6844RaWgAIsCLAqwUlT9deGVQ1vk6fDnsTevZWCd9Ye6m6rk6QCWvXmtAWu0cGxf5PGGovtvqhrlGvEqUrbntQas0LNvxR/WZN+qbzsQkGvEq2DZntdx2zUq/7VnrPf3UfMavbCpu7Il2zftYu/OdrlGzLnmLPdf397O6yKwWvd/KujxldU/LD1/3Z9x7/KpoPlDkBMJbj603MmtfRmYIdeI2aNZOoPO5mawvJ3XdXesYGXf687ipfNvnDzywnFqfpSPm4trXsHmuDo1S2ew5Y6VhLzH+t53lnHHSqh65n0v/NhcuqLl+bnmSVsvTd81x1xo87OOqlOz7H2iipXXTG1LXlc/vA+GRmf/bCjquLPwRH80WHIdVaftD+xeyutqsPRiMPppw/TFaxsyD+/wxitTpVCiWHm1Gu/8w4NfVgPWBKt+/du8juNa0My7V0oqunbfbiuoDcs1omtanbaDpRRKFCuvfgrdn9fVYAUWPVpV3Ti34tr2g+XabI/kj02N5MhrJvdUt2bpHY86bQdLKZRI6cy8WgGthvvzuhSsgSnhpq/L9j5of1W38Unj5zO9A7E6dVSdmmUjUt7L61KwhrdF8kcWJP6Iqk7NshEs7+Xl32YowKIAiwIsikoBWFfXIZR8Ob4MhJIvwEKAhQALARZCgIUACwEWQoCFAAsBFkKAhQALARZCgIUACwEWQoCFAAsBFkKAhQALpZX+AJdtKUiI9nGvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1MToyOC0wNTowMEB9ZmUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NZUi5zdmffuTTmAAAAAElFTkSuQmCC"},"213":{"admin":"Chad","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABXElEQVR42u3aIQ7CQBBA0a2mpgnhAoRaeie4CxqBRXGWmqZILkE4Al0EBkVICLTbPjOWipcPMyWEUK6223RnlpXtJp6O+8XyEO/nWwhd1zSpzeeTX4vdpZjXMW/Xsa7zWVWlOwNYYIEFFlj9wUqRF1gDnYoFFlhggQUWWOOBlTgpW6FigQUWWL4KwVIssMACCyywwAILLLAmAssdy1aoWIoFFlhggQUWWGCBBRZYYNkKwVIssMCaOKzXCRZYigUWWGCBNaj5wRPaCsFSLMUCCyywwBodrAHQBMsdCyzFAgsssMACC6y3d6wUSYGlWGCBBRZYYIEFljsWWIoFln83gKVYYIEFVn+vrsECS7HAAgsssGyF7lhgKRZYYIEFFlhggQUWWGDZCsECS7HAAivJO9bXnwuWYikWWGCBBZatcGKw/gJXsRQLLLDAAgssv7EGfMcCCyzFAgusn2+ODxFBzpJgiMMKAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1MjowMi0wNTowME0fhVUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1RDRC5zdmdX80m3AAAAAElFTkSuQmCC"},"214":{"admin":"Togo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA+EAIAAAACBfXRAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEu0lEQVR42u2cTUhVQRTHb62UQgIhcxNFQptoEdLHJiqSCKEWLdKMdn0QQgiCi3JR0BcGkZRCLnoWEi7SkjJJssiUgsyIFCKTlNBMyTTxg8IXeITuY95M83Hnvnvf+2/+PMa5c8e5P84598yZ67z/teJS7gYPtCwja1NLnN82lDe+q/3F9aymzRk5S4rK9z51ik9W7NsC9VWtPGZei7inCY7M+AArzGCpoiCDoLGtot8AK/wWK2gKsACWPQVYiQaLF+XIR062w3OtOAxgBclisY/QT5g8vQvACqor5IFlYMO+tp0qOL+259rq+h01cIWpAZbM25aB7fn4MPfOge3Tu9/kf1hJvz17BwRYoQ/e5a0X03Pwy9GDZyLRjvnX8zNkt5ShUUQcYAUPLPNEJdM+frs+q3k9gTVZ1dLdfhGuMLUtljFkvc9yRvKG5wr7GgaiBNbvC992jXZLOUS4wpQDyx3aC1MDA9lF/aXVhJRbYxwibzQD3OkVIdJ6pXjdjaq6urNrtiVWEzUT2/flje9o7sdJ649lkdqGeRasn4ONDa0vNUeWmC2BNfXqVvHy2Wj0Xafj6OrbMbvqvotXc5C5yqv/NP6cHXmrQE6NXJiM9m3d2X/k3OzSnspP0yxY5BA/T+RvPHZcfkyagzxesyfu70nrTBw0qaYyYHGiJbJAhIVY/wyNV06ms0i5lfrIjEbh/2JkBrACr8p7heRiRhovl9Tky6BjojT+2KHq2rsFqraK5hkPLDWTDkT0HK7RJjQF5jxnZ6L0FknZL5MdQ4AVNrBcoTS5J3emykQnSpuetB1WdXm6YAUheA93LGURLHf901BeWWHFqKqLpP7kXhd3Eo2RknOFACtoYHGUIiFV50iO7z9WSqvOIgyukHeXoIGuNh/H20IXSh/oBfUUsXm17R2e4F3VmoYXLHE+Xfg4vw9e7Yo81ouuKJHhbWlyLFheLpz9B5bcFks6600WggpjWGimhjuyu+boLY82odk+5EAV0gpCp0nzmXlwryk9N95CiFtsZ71tgCX+72Qspfgqmfn8a3FktnL1nCAvCyXOhHEdomrJ4YI+P/2oN7MkRtOaBzL3i5UWSKZnHGXv6OfdzeejOmfOyF4c/3I5QQIlThaKcy2bCSMQ/S+bkelpXitho9rCZEzVaxX6myNF1oicILk52iWUsjGuTBhlsJQdolBRNhO2418MFpTB0qxnd7lIGkc5QYoDq8l3rjAmpWmh+hQVpKl9YNX8VA/AAlgefz7EwllFgIUj9rrWDmABLP+/YwOwABaOfyX9R0EAFjSgb4X+j8Z5rwRYKecK5ZOo7gwZ5eLlr7WxpQNVWEk6eUdKtQCkbLu7RfxX1f4m6h6NHTnONqp4Y1W8ZcvbmmXb9a4Vbv22l9/MWDWluqVtNAdVdY2pVEBiUs5hrrbHNylftq1+zkfmLhIHVnVP68pXIAWzXM6fB+nV3PwHS/5pGoEF9fKgQaIrP/2o2QcKKQWWjyehgQIUYEEBFhRgYSGgAAsKsKAAy+aHD/UyK3oHNcOYDgjm+pgcWF1IkCb3p13DMufkWx8Hu/FQK4olgAIsKMCCAiwsBBRgQQEWFGBBoQALCrCgAAsKBVjQoOtfD/eeMzorx/4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUyOjE4LTA1OjAwJcXahQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVEdPLnN2Z7uyOrAAAAAASUVORK5CYII="},"215":{"admin":"Thailand","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZUlEQVR42u3dPUpDQRSG4bODpDNgIyG1jYvQNVhZp7Cw1Swi63AJgqWF7iG4A1tB4rXQ4oImjNw5JDrPVzwW/hDH1y6XidVqPJ5MyLqGI6CwKCwKy0FQWBQWhUW2EdbD6OLgssg2/3h7fz7xi5dIFhvP88PT6QtZ13hdPh4/vZF1jc4sYcIyYZmwTFhmwjJhmbDMhGV/Iqz3dTftbj4t+Yb+V5Z8b/9rhrirn19+Jm26Mazzu6vR7T1Z14g4Ors+IWv79WE2WyxyzP75+2Zrv+8mHQGFRWFRWA6CwqKwKCxSWBQWhUUKi8KisEhhUVgUFiks/u+wvGfSe1B/CMu7sznE/j9DT8+TMOUpnd0+x1f+Wc/0fT+HIc9FZr82T0KbR+xNWCYsM2GZsExYZsIyYVm7Ybn1hSl36biniim3f7lZjyn3FVa429NltS2fT2JY8mr5fKqFRbrFnsKisEhhUVgUFrnVD5SOZrLCehlyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1Mjo0MS0wNTowMPi9kTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1RIQS5zdmf11DYEAAAAAElFTkSuQmCC"},"216":{"admin":"Tajikistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACt0lEQVR42u2bP2gUQRSHt7O2EAVBECs7sVPLA2NncSrYqqSw1c4gCIIK2thoZSciCKcIWka0EpQkEohIEP+AIopeQFEvuZw4v1e8MHu52WQF2fmaj+N2ZzbsffzmzdtN8exl0SpaENbLglsAEQsiFkQsbgRELIhYELEgRCyIWBCxIEQsiFgQsSCsTaxvM50tna0Q1sti0B90B5NwBE/3up/aRu5GAhErSanFD/NvTr4Q0QuxKqjT//F57vZ5MT7aW5i9O/ZdjMVaMRbtEMu46eexV897h6bO7D7auzZzc9+OFXLoczjHGI8NozRDyTmIlXVinfh45/o2sWrqrGcsYjWxGG/GVRDrf+DykYXHT86qDO//+nrrwYERP39cgSXoopkX23NfDj/SFRGr4ZQcv288Pbj5qi1eCUvk8t7u7OQl6TJiVODSw/fzF6/YVeINAWI1tVS3rFq90A5HLdtcYlnTIWGsZVWW5TzFe5KISiCR8hyxaiuoh3a5KOERS8ufpU7F5WmNYintjr/dMDFuy2I2kmUk1tL21+9OjVlBrRorfaxfCiuqrCvq6iRWYxPLUidOLN9hj0ZZbz1QO8Q4mUrSSBuFcMWSUYjV8EorfNaC5Z8Jir4x4ZsIOqpOlT5rBqqrrIt3/7aC18g3FCSNPa5RjeX6WDoqmXTU5gmpxtsQmSaWdcbDD68c0lJVkj3u7QbJ5N/NUuWkoyrPNZtmzrPnTh9raDvU55Dlk74PGlmGaY8ZvklqmSJWbsuiMsYvZJ5KqZjxmZZ2FXeOiNXcZdFVUUP3d6tvAtxjojyfDJaI1dk5daGzawT3T5+73zamnL9neuO9iQrn/+v5h80Qz6Nv1jbnev6exs1TFK3xy3//XQfCWsktgIgFEQsiFjcCIhZELIhYECIWRCyIWBAiFkQsiFgQ1sU/YXON6H04CdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUyOjU1LTA1OjAwwFi1vwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVEpLLnN2Z/Ksj64AAAAASUVORK5CYII="},"217":{"admin":"Turkmenistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAJf0lEQVR42u2dbWiVZRjH96GEAl32MjezEW7F5nKytxQj7MWSps6X3BKxxLYg5ypLJClKEjPQKUMJTY1NVLRJhS5YpLCoDNIslZhRtujtQxEW6peIMji/8+Evt8/pOT7P2XZ2X18ubp5zn/vMnl//63qu677uJydnWPOSutoodlRb05KHervO1P5Z0vFJ451bS5Yde3HqrqKrsN1Tx+6rXNHVPHp71RdBdn/V6HcrJ2G5cuj+0g/Gl+o6rMyv8IvR/3KzGbRxgbW3vCavorf3+XkHx1wLCsChAB1oq22c8/DnY54oXnSMMdgxv2v+hBFlh4PwYmV+xcDyAqy8KYsWT395zyO37646dKJ9+qqifeDyUfvKvauPghHjH7f0VBwZ99Pfnct3PqrXfyluyb3h6t+vWTl8+G5XpViNlQGLX7Sb54Vi4aRQGqABoK/fX39283QsYDH+8OL4A2U9C/c05dU/1v3ZmwsXlP1a+Pqr43ahTFiAu0T/zBX6qViApTBhcXxLm+saKr/fvuO25RML3umsvPBAG9/Fbst9vGHBe4qaQsbKqFcUxRqW+9TauTlqDYJBDRa4qFbh5kCKT4EGgLjOGDu3c15J7c+oUd2p1sMr7mI1lAy3CL5xgWW3f5CCxe1RsIiE+o5vm714GFgAE9AwBjgUCx3iCng9W1DRV9OzoXjWPeM3so6uxq9EAeu6Y8+dn399Rc7af5ZNwYb/lxouAwYWuvLNLc8Ujj2ACwMXbufmvppN970CXlhwATssz4Ya2qsz5ZkxCljA9MPBM+1flW3Z/3F+9950cWRs6GQcLJxX55jJS8triIG+fHvGg4WNhN5ABl5ETtwevgWOmoZQmNSlJtMQkYN3sFhT3/1t58x5J3ZsXTeSK6mVjH8p8/mu4dVPigVYKBZWQ2/suRnrqkccUtQABYBQkaDAP64EKX+ziwVXDo461XE0H6vuTyMzPkXtDK+MB++aIMWdYUkTgAUaxhzNcmliAoyIwLCsEFeCVPUJq6ihRkGaxEz07I+W8wVnz7Ucf+vcG0csAst45h0niE1qjFO6SRZ5EtgxBiAt/jBW5WNNdDEKWGCB6gCQYqHApVY7VkBlB7NuDRj0ccVY3HjSnlgSm4xT1wpd666QXCfhCksrnp7VcCFdZQoDVniLK/x358Wqi8OJvUylYgBLYw4N3kEByJIVwDSRClIsVlPFSg0EmTAAcm98amUKY4ESsMI/XRpYkWIs1EVdG06QZ0BU55KsesItJj918GI1ZgJWmHQDMAGWRkJuSB4dLDfYzxqHNfjBUsVSmBjPOV09svxJVKRtQ8tvd5zUwjNX+JQEqathqFd4sNTx6W3jChoDfNFdYXSwTLH+pwitygQEuMWWruYJNatBh6w6OXo+5QqfMlND9U3f5b9WkpvUv8SvANaV3Ug3QZquQyTCYwVzhRkHiyoeesPNQ4fQJHChCK37q8CLK+DFTPSMFViNlbWkc2VgRc+h41hBSoN3U6wMFqFBh+IMSQRcGBFY642TWqv/SsZVbLOReItP1aXyLVaLC6woRWhVO5BiHL7mOFTtZf57xgsWtx9ngQIBTcdN9W239mmuS/HSTXz6Lcaspltu+h8sTVKoVqFepk/95AopMAMTTg0Hh0XDNJUKXjpHg3pWQ8/4lUxs9HP3aWE1baFIWUmnX4N3QmxC+OSez0QqAXdGLAIumm4AJj5lJvCxwiX5+phqhVps1niLsaYqKN0YUgOcbsCpuWUcVTIN6lWZNJZy0w1ugjTK3wwWChDRkosRVzTRakH6gCVItQkiqFyj+yDcfp6g/HsmunSADJcHOmpVz2z36YC5Qo2fgprA0i3poILWVzjEwXJLIgqWqpGWkDULH8ZqR6Fqm4JlyuFdw6pum3G7nEkZaI5er2hkpvqX7Q2r3v1vEJsrTDz9aasWcFCQwRKYUzd0LZ/qfH0eVLCsYdXTGEvhWPVpQVPRetCZNnFa7+RerHtFr/MtXcefGGuIaFtcYJEIIIEJFqpPjNUtUvBRV6i5excvvhVXusFs1oAFFuTHgWPjzLsn3HxSwdKjQdjzrjrHp8ykgOOuEL2v0GxWgsXtx1LSARGuEC3RgEqXDjl3rihYugJjkDWwPD27QbEAKQVLE6Q0gWkbvoI1+4Xae6tfQrF0zczVCs1mQYzlhu3qyPiUrujUMZY+J1qM5aliaYJUUQh6KtRnQP3UngoNrMBaoe5xCEo9pJvH0j1bBpZ3rjAo8+726oBOUOZdGyiGRubdwIqUIHVrhapeqUvOVis0sNLY3RAGrCDUtP3VYixzhZfZj6U599S2//djmc0CsNy2enAhDaExljaE6UHczHR1Lq5DQcxm8Z53UKDHBnTImzPWrhssiQadyTZlxqwZ1553KxVnZZcOFkS4DjSkEvSMZLLqzGQddyYrMEfVzhTLu5IOGqNPbYxpl8D9MR/d4lN2lOscd4VMt3+ZHdSd0Nx+V8+ABqu46Byqh4Dlti0YWF7vbuCpTV2hNnWpDrFzgWKz6wp1JqiBlLlCr195AgRu8A46zNcYiysApDNZId6GVbNZfD6WJgs0qEdvQErTDXzKdeZoeiITDatmM/78G+8576lP9Es3TcoK7qnJttHPoxcI4KTcM0hdvMK8FFNLOnoGqdUKvS5C65nHbid0GLzcFfTUZHOF3pV0dIuLtkhoZw4bkXFqWN196m6b0XNpLMbyNHjn9qtKaU8OL8JMnuuy5vjp9kLaKPRVKHqWqUZa6Z6abDaLg3d3a7KqFH04oANGLli8i5DDjMCL+bzOCYy03d7A8vTtX4qUAoTV1zCRhUfPwEvBUiXDXaJb5gq9e/uXRki8egmM9M1ejLU5jLHipZAxVmT1Dav2VOhREVr3eQaBQj5duwUBC/j0TfdcoVGMp0Ir6XjapcNRttQBwQinppBxRd8JnYzDEocyqusEMmay8tBOkA4pDY433UAYzvMdeGnMBBwaS2kcpmOCd5DieZCVLfPuabpBn+8It0EByABOwVKk0KfkS35l16geg2tPhZ7GWKpJ7v535gAHkROahHWbwIjYWI2VNXi3m+fRRj/6lRWFoLOTw7R/KaasHP3NFGazUrF4dlMgtOk+PFgaV2HJ5tsxRt6BpUUYXJhiEf5tq8zU7+pLCUyxPHWFighAAIe6Rd0Boe+4V/enSKnaWR7L0z3vbgczoKhVNdIr7tjVOXOFWZOHi7evMMz76DWVgGKpbulBIO5qqljmCgez/Q+Iy20007NKmAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTM6MDktMDU6MDCg2rqRAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9US00uc3ZntrCpqwAAAABJRU5ErkJggg=="},"221":{"admin":"Tunisia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEWklEQVR42u2cP2gTYRjGj06CWMUIbuqg4ORShXaRosRugkMX0Q51EUERddBFEBeNQ5GMoVJREBzq6NIhQZAK/sGlEhBFBOu/mlT7J9YahTwZvuPyfn3vcqGX757lGS53lyP3y/O+3/u93+fNznqbMxkqNV71+BNQCRaVYFEJFn8IKsGiEiwqwaJSCRaVYFEJFpVKsKgEi0qwqFSCRSVYDugFz+vNt1D+MgRLr183bt+yd8e383tqB57MZQcOD5V/3B3KDWehOIJPcSZ/MYLVwocAR6U+XBmdWBorPLvX9+fIi5nXN//e+TT5eax+uVKsLv17X39Un4biCD7FmbgKdyBqaQXLgOnX6JWV68urW2ey5ZMmOu0o7vazfOnQ1ckmZKkMmqkDq3LrWN/IAJwmLpgkxbfAyXy5GsFyw5++9Pd+39UDfwqGNknNkLf69t34h51wIylE2u+zsOlaJncDT5IGvBwHCy9y8WW+WHilef2/9z2+PVVDIJurDU4cfWom6b6kvvEpzsRVGtTwJE28CFb3IrVQzT3IH7S/bGCBEZ/PUUxfkcoNxjm4A+5m/0Y8FcHqSp0vnn5z8aPkIjiO4LhGeDKCKQoN1dJI/5kNCG1ABPdBLoVz7GEXx/GEBKtrFKEKmZD0UgGHxvOAiz7YIRtDyINKT4Iz8bQEqwsUVaWIPmEUI6T76LWZ+FvLGfgWgpXo0R/CkOQrmlcIpKQ8CXeG08RVksA98eQEK6Eqpeq+oCPkUgh8y8fv73+4aA9zCKNSgIum7qXznkuze1LQ0bw24KIJbQDUPjiIVq93aTrIEbCkIOgLNFavWilNn3h+VgMWXr/kcHA1HMfYUONtmuckWOuQXcE/pEkVsSCJzKxR6tR4jwkWroV7YfQHz8MRfWHW1PnBU+Pn6nSsBIElZVdwDvu18BXNi4ertQhYgZIpEElzpuW5XWKAZ9iDS/Bac/QXoiXGGJlGGzm6VHpwBCxpNIf6uB0s81oghRfsKwGo856w4S+EvxKs5DhWM7hYsZBQwEjNdCxNlZyO5RRYEXOshkbMsYKwGjkWEvmwxQjN34BgOTsqNAutMY8KHZqWdqWOJcChmTCJt46Fc9qqYxGspFXepYbjNYbxRvAKW3mPt4mZlfeEBkQxDVfPFdo7Gjo+V+hQy3KKuhs0bcHr3N1AsLqx9KCfNjH7sdqZZtb0Y/nGrQQryb6FkCf5iqqD1GhHbnaQFqa2lXbrO0gBJTtI2fPeY9+Rwex5h+e16HlvrFgM0fPu0JQzV+noVukEp5YVW4Og5MFVOq6vKzTCWbR1hUDNvq4QzoQaGNcVpm6JfRJWQvvCLpfYO7W7lbF3g6bOHtveDY1v5N4NKdptBiEv3t1mABP8Kc1bGnF/rM7sj5X6/f64o1+LcIlMSLOjX3pyJoLVmX1HgxkS9yAlWFSCRSVYVCrBohIsKsGiUgkWlWBRCRaVSrCoBItKsKhUgkUlWFSCRaX69T8dsauhR7BBrQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTQ6MjAtMDU6MDCVu+NGAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9UVU4uc3ZnyMywkAAAAABJRU5ErkJggg=="},"222":{"admin":"Turkey","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD1ElEQVR42u2cS0hUURzG7y6kiFqERLQQImhRQgQhuQgpiJAiaZVbd7UwJCkKCiXIiBZC0KIX5CIIJCMVioSQ2kptRCTKtJcTOWk5PWxs8W1OTE7njnPvnHPvb/Mher3MPec33/9xzj3B5JuqfPUqFC2vBml4yKnBtY0b55lswEIBC0UBCwUs1O88FbAoL3As1B98AQsFLBSwUMBiIFDAQgELBSyWclEcCwWsGNp979u3HNrR/7G5bmJPS6Zh/+XDtabq97omPY5b2pOWd3w8A+vDie1V9bnsxOnejr7c8ODco6cL+151TtTnN8+MZfuli6/zs4uBVL/RNd87hs48yX0Z6tzbNSXgCO6pdqzpgw27G5u+td2+f+daITqlqe4zP35vw4Mjn7Y19TSPgoLTYJXLA95drNm1dfXche7s1YfLh8n0s6Vc7Wvm5oqeWgVNsEigYylI/Vg/fP3Zi9IA0v8q5MmN5HkKo2Y2NrPy6K22c/JChcufrSMHnk/qr8CRELA0/b9ujPWOD4SFSXDoDqW5prwq23Xy7tlRZW+ESO/BkpeERUruEsX0myWCYAUUz8B62149sqlGDmGPlK6POh+S8yks6nOCizdgKROyR0otA6X2cfaEACvsiAWVTdJ/X5l+mVmwQUqBUkHKtaGME3Qc6z+qVoK9V32ua2k91ufmIJpNV5CqGFjKjVTe22dULnfJVZMqTEcdNH1ZLagAWPKeZHiVVM1VfdrZx5fWdO8sjoUCerKbsUFlp6G4ytXcnwDzidRXM5sgwkjNWC0iydvczBcjBCs6s9Wd7bvqmgb3B7Hwq6JSQ0HcLFCEnfse7Jljhc2uiocVF4rqsAtQKllI3iPpsNu3GBQ+XBgmpeTCSB15hTP7ZxF86emHBfH3rux3K7gAllAQTPZe62MJkibHGjh+/tQ61wKfuc0wrGMlqZVaPBd3OsdS4zF5OVYadq4G8U9G2KrQ/WkorAr15TE3Tye1Klxqduhj0cei8+4DWDbhm847a4UhwFr+WmGScq+gUg+s5meSdjew19SJbTOqp+x9i/1YgBX5DtL4sxN2kLLnnT3vgPV3WNRbN6W9pVPeLFABN9mZU9SFQqLeK5S7hB0y83ARc7mG11Z5E/ofb0ILEfmZ7mkqb0Kn9FAQ5TSc3QBYMZ02Y7+bwP60mdICKOo0WGGns3DjinIy04cKfzaXhJN6PpZrz+L9iX5qTgoUeVvhiX66Bk8CLBSwUBSwUP/AIoNBcSwUsFA/2xaAheJYtCUBC005+oAFCoCFAhYKWAwEClgoR0WSdAMWA4GWWf8AK7hyUfoyPfsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU0OjQ2LTA1OjAwMATf+wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVFVSLnN2Z23cyhMAAAAASUVORK5CYII="},"223":{"admin":"Taiwan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADnUlEQVR42u2csUtVURyAD02BEMiDlgeBFIGLENTgGE0m5Wpbg5v/QKTREE4P20IQikDiTRIiDrYEhZhBPoeccpC2HIqmgggTvuUnt3vx+bxy3/Nz+Hicc+7vwLsf5/zu755nSkNp6NmSLOalFyk9WfzxOqXzf/bPpYM/WcykNEcXa/d+Sv2/lUaxFEuxFEuxSmW9VW+9/AgVS7FOjOO18dqbOlQsxWpjNSqWZq411/rchMXyneaqpliVFqs2WBt8/mh1bXXt6/vJ9cn1d3eyvduN7cb3e5CWOIariJDtVawzvRXONmebWw/2VvZWfl2ZqE/U316jfbgx3Fi8S/vfg7/9QVroZSTtRHArVKz/bGQoAmlBo7gV0kJvFG6kf6R/eU2xzpxYVweuDry6gRbZDYvejb6Nvm+XWXsQhTVpZn5m/tMFSAu9qMZV2eyKWZiR+IrVg2Jx45ujzdEvF8mW0AJRonZTY1NjH9YZE9ewSHoZGdUhWpyFz2Uk9YpVoa2QG7y8sLywewtFYl7FmpQnUx65KruZMkt5z4mKVbkcK+rFihLT8OMxrlVlK6VYlU7eufFsZORJnYhFuYFop1PNUqxKiEUqTSbEtkWGRBoen++ORyIQjcjMwoxIoFg9IhYysUmxokSBSK6zuVEnJFpM/JkxFmBPqoh6lsU63im0VEahgVtOKYGNj5tdhlhEjsWLuHq5YvVsjhW3RbgzvTP983onShEhxiz79Y5iVTR5ZxVhe6Km1YlYRCDa6ZyDUKzKiUXWRfbDSkPSXVwULS6WEoFoRC5bL8WqkFhRKUgmFN8MHr30wMj49jBeS/zsiQnF6imxuP2k0tzs+N4QxjIBnxGFdQjSQrWdVYrPMQ6RmYUZ45kIxeopsYorSfF4TNzI4hNltiWOL1anjEResbrg2Ex8pUOxAIFi+TSWQOllJFeVt+UpVheLxTu+qFR87RPT/Ngb9SKCYinWoTeGFAvyCpioA/MKsETwzLtiHcp+ioUgkYfFgnrmXbHafqIs48lOsfwl9FIZJxQUS7H8ib1iKZZUrA4fOxSrDbEeP0zp9mbk082Ubm5Vvz2PRxnf7hg++4/X2hCLLyv7leV9icXjT6q9W85Jylyx/AqkYknFkoolpWJJxZKKJaViScWSiiWlYknFkoolpWJJxZK99U/VFEu6YknFklKxpGJJxZJSsaRiScWSUrG6u4qtWFIqllQsqVhSKpZULJ/dFEvK9A8j9Yu4TFwBigAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTU6MTEtMDU6MDBSgYQvAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9UV04uc3ZnhQQRmwAAAABJRU5ErkJggg=="},"225":{"admin":"Uganda","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKElEQVR42u2cbUhUWRjHjxvVBIXSpoblBluEJUUbBU0frKiYjdqJjAyStnBben/fsIgwUpaMXXa2ZlnTXqAgA0EJ7UNF5GYvIrVS7eaQFfau1pIKQRBWH/59OHG6d8+8NnPP/8uP8Xrm3Huf+c3zPPfM3BGiUpQJF0lGmAwBSbFIikVSLAaCpFgkxSIpFklSLJJikcYy1Z/amrYaf+g8lqluxxb75wa7R3XO8I/E/oxUMj468fmIVw7UPhIkGWGKd13tw0gy0mQISIpFUiySYjEQJMUiKRZJsUiSYpEUizRXrM7ywuS+6fb8r+fnYUMf64yMDV8MLmpI6RebfeHcVcbP2cUyGvoU9/9x93O5WrrHT+zbjcfRIOZ/+IW7xzVAfSxTZx55i/489tsxz4MJnq8yqjuOb/tzUcqd3eWBfUngw5LzKf5JnUXNexv/6OkNBG4l9wys8pQVYCSeJc8W2rnoPFceo567TuTlkaEdpz3b1sxY0f+kiJ5MsSFCBoYzw4vNpdfWl0Kat696t7493fr6bEZd/pGueTkZ9a2TL3WMagLlMSC2YAZIFs7x2L9gVscfvfiExoQXKxxCAuQeWRQw0N4yNfBD3m3vQiHGe77e8eUUSHZz9Z6GTT+q4z9INrDKU1Yg5zAzaahYePdbKfXqyvOmzl8PTizLLM/F942mdE9d4F657pfl3qXz1IwVD3rFPidRrE/w5YnKBf6frLRArjpUUuzZNRO5app7pm+6v7KhcvLJ7+yVkikXR3OUShixdAInj7Ef/6R0+WX3HeQkey1qsiqy/E2zZ40dMmLcBt+2pxt8+krJmQ97ZCmMWFMZn7QqfzLbCtvmtxXmzc07s3iWJ/Pb/Z4RR4uOXD82IVix5LKY6G9dlsL/a9U1OqTtd3c0b78ri4V+K0SxegOBW8mmtfMift4lVhfG4b+fMANKkk7xykmbMS4nDeXPW73wmrcaj2tv1v1b9w06sGD1siqIoS0iRDbmURFLfjmdSpwqWmkdsXANiIYdV4XIYX9lXnhzuQJ6oZFvbL66trFOp2PDUqoJ0QbFxmXDS/p0msDjXetyv9+tk12KB+0ZXTxIvpkJkqHTQgd2rv1UfnUm5vw7a0vN0ntYl7eaEyPNibZBN6yiZ9K/mlu1YuXjVVV4LnIYSiS6LiyW1lRMqk0fjQ890EVZ6YW9805oBxJy6JQtmchSUEq9pVMWC1TX5eW+jWI5kFAhtNYbfRVmyO7OPpz9DD0WFlFlvVAW5efeGNm888ZI+zuVKVbC0zfnt1LfHJ1SCAVBiCVfJ2ILRkImiIVvQ8izYY/8URAjCiIacKulUXRRIBp59FtQCleIEAvfgEDGur3PW5PdIpfajnftBzqqsEeKZQQhhyoWlg+gER7L5QySqSLiwx+1bcde+DNGLtP6LXU93aq1h0CqWPYtv3F9FcWy10suZGjSkXtQHLHFSkF0VEYrRbHUVS6UPFksZCn812q1HVuMW6miWMHmMCiCTIaiBnWgHT7YwX8xkvnpE1xSmztG9AfxNVxQ3q6O0fmvOlJn/nBoNbPVfnXGr7lYkJ6SpFIeE+x+7Y/BKobhxy0a8bEayTvgSN6wSlIskmIxECTFIikWSbFIkmKRFIukWCQZUbFe/15/Juk6SUaWAjdMJi5xb0yin4XzKJz0s0Qm/x6VfWRiHx/Bl5zk72ORpor1efNTsHt3djb9vNEQfAEYAZZCkmIxQ+idl1PPlMsNjhI9fo6TPRbp3IxlprjOzoLvAf+YDWkozYq2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NTozNy0wNTowMHN0tmgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VHQS5zdmei9Y9lAAAAHnRFWHRzdmc6ZGVzY3JpcHRpb24AZmxhZyBvZiBVZ2FuZGFggYa5AAAAAElFTkSuQmCC"},"226":{"admin":"Ukraine","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAxElEQVR42u3SsQmAQBBE0W1TjqvFCszNLUOwCzGzlTU1PjZRXvLyGX5EtLbvZLUuoLAoLArLERQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWKSwKCwKixQWhUVhkaNhTdvRyWLnXNd+k7VG5rUEWWxknue3fU/6+pb/6AIKi8KisBxBYVFYFBYpLAqLwiKFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBY56APt8YgxrmbUbAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTU6NDctMDU6MDB5sb9xAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9VS1Iuc3Zn8neiTAAAAABJRU5ErkJggg=="},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"},"229":{"admin":"Uzbekistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACgklEQVR42u2azUtUURiHL7VoFX4EKuIHuIpwpTCIpITiQhDLoJa5y/4BoZVCIO5aBC0ciKJBHVy4EAZqkqYRpSQQbIhaWJIwfuQXWEllzLT4bS4cG3TuvXpv82weLnPmvnPnnIf3vOfcY1nXR17FPsCTZEXV8L2n3//z/+VW0OIzj5fj7eUPIsXTYV0jUEHTyc3S6O7N+ecfI2O1S0Xpvb47Mz9T8bpr462JsPOHa+ycrJoN30i92F2ImrJ2NMaa5pOivVXXuksRGObAiCV1kltr9dsHqeqd89/OaoDdfThpKmUlsb31fum73s8bopk77aIz4QZALA3wy/r06Nbm+u/9uV/drfGp6OtVLx5Ov/Wv/KfPc7eaOkKfiqWJL/snm8lmzGwB4bHF0hSjXCWx3MpV+RX7uVM9C4jAiKVp5dPlvcofq6LzIl3F9VDTwtel22Y01W1qNUVR/WRWUfqmMqsXlR/0UCxVV87F0sCr0Daj3QoluhYvPHzzfvhLyBRLwommWLpLERhmpsJDPveiFfqueNfUQ/EOfbHdkDtz5Nea+y42GgK5QXrp0cRgcl96aYNUepkrNVVOqqJMBY9eY5miqDynxirQVzq61qRpvmA5yqpQ8Y+7KlRMVoWBFOtkXkI7mUbhqVGTF4Tu0poua4n2lECTiYkrmbY++iE/WgeRlf70AITu0tKOFITuErEgYkHEgohFR0DEgogFEYuOgIgFEQsiFoSIBRELIhaE7onFAQ/oybEZHWfzD+2H7GBwe8xqePbkaugiLEzWnIu9bW7xIrKl0BC6S8SCiAURCyIWHQERCyIWRCw6AiJWodK+pYlYkIwFIWJBxIKIBSFiQT/zL1RgScNKvfJfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NzoxOS0wNTowMGWbGnUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VaQi5zdmdaHeTwAAAAAElFTkSuQmCC"},"235":{"admin":"Vietnam","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACnUlEQVR42u2cTStEURjHDyXEICSUYqGk7MROaRZk62WtbCxsbNhZWbKULJRk5xPwAXwAO1NIykskJKUUi//maozuHedy731+m1+TGffl9JvzP/d5TuMKhd7eri4I/dIxBBCxIGJBxGIgIGJBxIKIBSFiQcSCiAUhYkHEgogFS/GsYvigc09kNBDLG2+Olxeb+0VGA7G88WXgcKo2/7SzP1mXZzQQy1sIvo/cnlY+iwQiYnkLwY+Hjy3XIBKIiOWBir+gWAQiYnkLwaBYb+uFqqocgYhYZfJqcL6mdSioVJB6l1FCrMh8zO0216+UEkvvMkqIFTkEFXmlxCIQEctzCBKIiBVLCBKIiBVLCBKIiPWNNL5CUFQxIkwgWpPPpX0lpC6eL4afq4rnrd+f/XX0aLt6V6/TXt93aZ+B7tzqeNNScRkzjdRd6I6YsRLBy5bpjbZ9fePTqJSuXHdBFCZ0DlMvLy1zmJ4rs7cCy+ziXWuUZOqlNVm2d0lk/Knwom9irH1Ay+EkKKUr0VXxVJiRiHyY2bzOnfz9HKYz6ux2ig7OZrumvLJCeZFns+1jtPJ+/pTv6eiOLyJ1ZJ2Fyrs53s+tLTTOxiGWjkxLx+jNx1f30pERy9xtqxQZ30JeR7bw9IdYX6i2SdyL92w0ZxArESFIIBoVS/EUPgSDVSi1X6L+r81AdIRg1MZL1EqYzUA0J1aY2lWYxkv4ZpE+g1gZL4qWCjL9PWr9KcyeMJuBaEis4l9h8Nt4+Tkirf3ig7Mcgtq55bfxEtwTZjkQnbUQFP9m/gjuCRPtdA+dnRD8r+2/wW3TdgLR2Wng/O9eKJ09S7vaEQsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELIhZELAgRCyIWTC0/Ae7yUNeiWB9uAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1OTo0Ny0wNTowMGONX/8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1ZOTS5zdmdx4ikxAAAAAElFTkSuQmCC"},"240":{"admin":"Yemen","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABBklEQVR42u3XwQnCMBxG8X+cQN1BXcQF3MwlPHlzjB46iivUgxehGGJNRMjvPfgubZGUh9AYx816v7O27oZX8KOdtumQhGWtsKywrLCsFZYVlu3ha1RYtknKwrL+saywrLC8CCssKywf3sLyIqywrLCssKwVlhWW7TWs50f4u0/x16vze+ZX8/d882z5PSWnWHb/p+dqfd5lv5s/b/lT2d+N+/l6ug3W1t2YgAYIC8KCsCAsQFgQFoQFCAvCgrAAYUFYEBYgLAgLwgKEBWFBWICw8LdhXY5kfSOtyPoKi8KisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWKSwKCx25QO7IGoQUExASwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjM6MDI6MjEtMDU6MDCSE+1yAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ZRU0uc3Zn7W2pGwAAAB10RVh0c3ZnOmRlc2NyaXB0aW9uAGZsYWcgb2YgWWVtZW5boPDjAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/1/1/1.grid.json b/misc/openlayers/examples/utfgrid/geography-class/1/1/1.grid.json
new file mode 100644
index 0000000..c11f785
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/1/1/1.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!#$$$$%%&&' ((( ((( (((( ) "," !#$$$$$***& (((((((( (((((++ + "," $$$$$$**** (( ( ( ( (++++ + "," ,,,$$--*** (((((. +++++ // "," ,,,,---0*1 22 / "," ,,,----011 3 2222 2 "," 4,,,--55111 33 22222222 6"," 44477551 33 2222222222 8 "," 44477951 33 2222222222222 : "," 44979991 3 22222222222222 "," 499999 22222222222222 "," 999999 22222222222222 "," 9999 22222 22222222 "," 9 22 222222 "," 2222 ; "," 2 ;;"," 2 ;; "," 2 ; "," ; "," "," < "," ; "," ; "," "," "," "," "," "," "," "," "," = "," ==== = ========== ======= = "," ========= ======================== "," = =========== ============================ "," ============= =============================== "," ====== = =============== ================================== ","========================= =================================== ","======================== ==================================== ","============================================================ ","=========================================================== ","=========================================================== ","========================================================== ","========================================================== ","========================================================== ","============================================================= ","============================================================ ","============================================================ ","========================================================== ","========================================================== ","========================================================= ","========================================================== ","========================================================== ","========================================================== ","=========================================================== ","========================================================== ","============================================================ ","============================================================ ","============================================================= ","============================================================== ","================================================================","================================================================","================================================================","================================================================"],"keys":["","77","47","46","225","116","200","99","119","224","176","4","242","218","195","155","151","17","140","157","243","72","38","236","241","158","168","15","13"],"data":{"4":{"admin":"Angola","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFP0lEQVR42u2bTWgdVRiGr1HTGmOav4YELWmT9EbiwkWQIooLQYlBwUpttI1iRZqqiNiq0FKVCmL9qVYFBQNabKRokaqLWFq0FYTSTbAUm2IhxoUYaKHGv4U0KtxnFt/l5Exmmrlw78m7ebmcOTMnzDy833u+meTGxhoa8nmpNFvN6RZIBZZUYEkFlm6EVGBJBZZUYEmlAksqsKQCSyoVWFKBJRVYUqnAkgosqcCSzq3X192Y31CkhfGTF7oHujbp/gisi1QAmthzf/fye/h9prPv9IpPpus/+7ftkakzOweu2cZIEYgCSxqvP383dKB95T/j4z+0dPx97fGZlmmUERTIIg8TWNIk+kvts2eXTVqMXAWs8f5V5zpmBJY0kU6+vmFf+4HzH+3ra/v9jx8PnW59wIcXRyma+BxQCixplJAoaicmGo/mX3EDO+ic/e3dl6++1y2LtmjymxwWXU1gLWSwQAf1lTZAYQ6l0OdkU2u/uWXpaNiFUmDN1VYo/MaN0DlQMA6HM80CVmFcpXBB96hAhLSEpt3ruXhRFotaEgIr7O4UwdyqxQIgGOEokZzfszRIDZq4ncWL+E/pDA8ygVUEFrs2XwD3RXLO8obxAl4kKt8ukkwWUpwXWLOo7aTHd6rSdtjje2DxmwOBFUjG4jH7XKoIgiQomLLo69RHmAqssMFy05Xbi/K9kE4b56PGaUCtB4HlbxYUuk1EbBISjsIIR1PsEGO9MGpkBPTSWmBFjU3brgQgXKQIHYMdR1MXL3N963+2FIbx+U3uxJq6kfw0NzdS34gdjz/Lzi/x9SM40q5o1L6QYcS+wIn/26KZvtU9fzMQu0nL9rdmuWaS+5P8Hmb7LJxzc0MPLd7c1LyQ9ciH22eWTvFovx167PvWJ0q94ranOt9seppi6m4O3t9792jzykq/q7lcW64lV7WQdf/gO2uWrLCe8eCO1W8sXl26FTs6l62r+s8F69fdx9qbt9x1w62D1Scr/t4KrJG/dh2ve8t9wKXD6+ba3uHL+1mFFcfu+OLOxlWMtw42n6/qFlgVr1u3b9pY87VbknjwGwcG/ryipn60bv0lW7NaEWTtWrhmUPdWYPXWXDdx2YvWPyxYn3/13vSSXcCXFV7D9730wlWH7VpcX2AFqDsefrLnys2ub1GkwOvtx587VXtuPqWqp7fr9ksPnTp8cHfjBbvKsdf2P9+w57ZHb/q0ernACkpxI9Bx8QIFChZ+g89lVXbRQGK7wPLhRa5yfWWy6+jepnHcC13b09+0aHg+XmUdK9skJ7DKVEEBf7IJjJbEwS0fvFrflySBMW6bGhYmgnxQXiWw0jYIgMN2zIHDJjBwjC+vQfWrBJbrRjQq0xZKPAakbAIDL7pixHDU3W8yJ6jCJ7BACizY8eEcaR8zUFIEyV7Ww0CHoz4QLy7+C6wyDeZu1qG0gQKQJW8oMJOzuAL+ZHeRlELm8DvbrpjAKgvFP3y7M9u7AoudPc9U1y4CBascZSbKXpLrMwJkRz4e+bL+J7DjaOBIKWPZ8jR/BSOujJMBJeOsBY6gmTbhCawKUB68CxZpyY3b8f+lY+fbzhbORITHt5iJe9HrD6rbLrBQHiqdKhvkaS64odvtPzGTs7iC75sIULZllHJsy6gapMF6mPtoQQEIUPeFMWclj/y4Gi1WPAznCyrUC6kke0lXs7oykNmwj4NWPF5Cp3yKcrwvCixpBp8cglcFf02qx1men+7wgkhgSUuS6lQKpVKBJRVYUoEllQosqcCSCiypVGBJBZZUYEmlAktaMv0fswCmUwz2euEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE1OjMzLTA1OjAwrEp7gQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUdPLnN2Z45UidkAAAAASUVORK5CYII="},"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="},"15":{"admin":"French Southern and Antarctic Lands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFd0lEQVR42u2cbWhWZRjHn8h9KaPEqOiLOWN+cJSShZKYOQXTUKgRRoKEwgJXKIXZBwcaI2iQ+DYksAQzjJHBFmOViNF8KWyJjAbORKZB08039PPC/Q5yPdzdx/N0ztl5zp7/lz/jPOfc5xn37/lf133d1zmFwqM1T9YvTUq/Pv/91O4XRqpGakfejK9XW9vWtvf39E16oqY2lk5+aEL1hgu/rmvctOetpveHtu8pFJ6qXtGa7P8uLVKBJRVYyYGliRdYciyBJceSyrHkWAJLYAkshUKpwJKO6sTTM4+tahdYCoVyLDmWwJJjSQWWwBJYAktgCSypwFLyLrAElsBSKJQKLIElsBQKBZYcSyqwBJbAElgCSzmWVGAJrLLs6EqO0DvBRaFQGujW+3a1tX2YlP42dKb3XMO1fTcfvn1/fB1c3FPXO79/zad/tR6LrwP97RN/3Lly5fqBbbM18anr8w31Szb9kbbO7FpR98EP0Y+4nyb1HR5vmHtwTYcmPoscy4aJOH+XOmZokI10bfhoCn/5Td6l/y83/Y8fjO9I+M/Jd47AGn9KIH770kfLd/enoSxH3ONP1y5qblwksMatMvEsR5JaL4frnzvOHbk0R2BVRI1n365DJ44+mDZS4ItjZRwQNfFjozO+WTZtQ9PpL/oOXXiR6b98cvj6jZOu+rwtylXgm8oDqAIrK6WQET6pUyYsePWdG0DmU1+RGR/yXRWlmPJI73Ndq6vGqOAiIJLSluG9c9qnM82FwpT65bcireCcTxnHBeuV62sPNx9wg2wkfxodmRGKAmV64VJAxPEnVnx1i1fP2rKQlHnzte1VBz/BPwLIShx590sHHuh63ZeSdz/7+2N9Ayj7HIDoG40Unm9CoOz86edvewbsN8zBuxsqRwlqQGCzIv5m4l2PieIrPrB8WVdwF8cLwQV/Irez154fvHh28BQbcYRIgVV2vnV43vE3znx2j8mO/HaX6GBxZhS/2djdMn3/FffaFPckBEdSYDFVeFWQx3iuwideO9v4XktHdMfi+PrPm49/2fnuko+r936Fa3pHc/I2vmfH0iO/nAr6UFJcPwqOOFsx5CjkWDZE+oIgZ+JqZEjuGs0HVnAXsyzgLjb4BqMZBwIdejrsutUeEVg5RtANmqibevvAsuGV0YDJnsPSoYRNfZUb8q5uloNS3mR1GR4KLVgA5J5z8eo/1cOz7GgqkI5bZWHPKozpJ7+x7kUJgNWZW8eyCwKgAUd7LUhxhCwq4/p7Bm+nrLRdwqHvao4us/4Eai93rnqm6W+LCIm5z7HIioDGVrbI80jh3Up9Zr6Vyxen5iSvAgU38OFYqAULV3MzJxwLpGzNjPNd/yuLHgf5SnrlU7csOfZaVK8SWHn3OTec4TS4C8ER5Uip3Vq4kR2H7RrXtxjZW+WSY+XLq5hsghcaXgjwbTzfYxvHd3ezJwhq3H1Mf65CIT3HjT6R0UNnZqFNYOU32Q9vYi6LhmOBVc71d99GCg4X3ugXp81QYI3bB7yAI3ga29Nq5xZUvQVPcy11rLKouQustGGikm6f6iaVxpPCW+3YAiraonGcj+MUWgmULAI4nmITn8DKVgGLFZl1IJsz4WHu9IOOr0zAp2BkN3NQ+iZKbjMUWPntiLfTjyfFeS0AONpNnqKqlTahK0HtNg4ohPeqR28zpF5FGYJQSxugcqyK6DIlMOExhEh7JDw/C69+2ZDn3ktgSe+uH+/0iJKSp96ZLrAqoVJP8zHpPEGTxJx2Go5n9X6vkv1P7S7l9l4a30YyyX4qj2rJsSohFNoHLmzHqV68Jo2lBD46FAh/rCJJ1QWWNFaPfJCKjHoYG885e3uqJlIqsKQCS+WDyl5T/wu/C/ZEKz4GwAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MjI6MzktMDU6MDBzBFm3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEYuc3ZnBgabnQAAACl0RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgV2FsbGlzIGFuZCBGdXR1bmGg6A9zAAAAAElFTkSuQmCC"},"17":{"admin":"Australia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFyklEQVR42u2cX2gcVRTGN4ogJaVIHzRtqaJBS7RbFGpqQEGkIiy1KpY+9KEvhaQgSIRCSvpQ/0AjtQ9qER9MpJpQiMaqGyoKpg8VYUFMzUO6FUu7MRBfKhafgoaR7G8Xj96dyczOnZm7yXn5WGZm596595tzzv3OmZvzvJ5ST8nzRl8ffW2p9+8nlj4ycXjHB7MzX+Y7H37r499zHblNJ9+Ij7u6dj0+dif397zj54+/4Hlt97fd1wiXz3Kl3T5wN57OK04uTN70vH1X9t1Tb3fLS1u2c2Tulc9yU3fcveHel4fbbbW+yrE+fAxl4VDhufoQz+/5dYek1/yG+fyffW9+OHSx9My2I9tGRtalTyz+Faddes5TQBfvzOGv+04EjwPPrsSKgGEG17Rh8UkGRbhP0sTCMkUlk3ze0tnShYUDHZ929L33k5ImFHYd2d555lT4QQ8mGXdzwWJFtUxJvDxrGuO82XFIJolVKfc/+ern8YlFi+/c9e5T04/E77/deC4JbF9qX3r72RYgVppvvC2LFd/iXitfK988GNXiuoCMxu5Luy99sq5liBVu8uT0hyUZIXAcYnEHW/1pLTcHjXoXexe/OTf2/NnbygMgRxwiWdKrKj8LURwvjl/tCUesgcJAgSsZxKtd4+u/vSXpWNBNR8MC4sfzM4dudMqn44hDywvJ+qgoH8/zpg9OP40eVkOfFZaJwcQiAqtfuXy3Bm1VjwS3Qm+be1LitmynDaL3D/UPXXiIl8R8bTjrxCtRn6pMsYEjE1g7m2UPK/nSYnnMBWIRDGB3JbE4wllHiIUrQX/nd1bo5wqz7dUyVvKbNz+4sPV6e+nEAy44GiIq7BPIEaeUd7/pVBTuOJ/LrR/deqpt52DRnUgLywQ6J94qaVqRWC2RK1TqKLESjLFcwOAJ1hirxYjl/KqwqmPpqjAJlGK1ozpWI3XKR3MyBMzwyvtKbUll6//6VqvrWHYTQXsreyvnfmZFSUUaqr017T6qjiLTO6ZMV5vmmjRa1cGlI6taJvT6L7onfinPRFXe0evDtSvcq2i3VRLMSa8opyamJub+M4Ozj85evjFszXpFzRXGIZPMzTG1zeUK7fYn/cRzttlJbBIVZqZ2v797f3dxpwWJNRaZau4mLJniVDfQB/N9krlLspCukYxJop9YC1wPv4ly0tHKaQXq0Ac5SngDhFYLTj9py5R+PVY6/Y86ndwTO0Hf+M3x9JMwxFgygiTestaKLO5LczKiWKx/Y6zmKkhdIBnTZvYhq7QxL6FU8C2vDVcadHKI9t1HczFWc0F3sCWrrSgDSfbDi++PT37XnIPANsjFiumAHC3Wi4NZxSJJWyy7lqwmkMZQ3rEKrMVAKLVqP9CALpWjhccOXKnkbz258asaVo9QUpdEpSW2hyUu1CFt0gCrERhX2pUJJMm4P1QDK/nLE78dnfvj9F+jM99vzN++53Qc5R0CERrLIHrVEou30ERWK8k9tszJ89uvJ0ynvDI5dYenls+e9DgkgYwb1j0RVT2J0mRF9xFbyKoTJM5TYilaCDOkW88g06DTsFqTNlgpMANXrtOguCaIhdFey0liJVYiOHhs8NjFEVCnR4llYccBjsu1jN+VuuuLEst3MUx2XRZpmGsZ+ZWc+S+dPCVWA8skP7GnttOsECL1IetU+ZfTu6woZhtjyY3XgsuOZRmaBvVKrFBofiruh9QM6YQpsVaQ74LrC/wsFqtFDd6VWL45rDBk8kPuoCE81RmO7u+VZsBOORuEoIBElqz40YizXEm9JXdwaGeV1AVk6uEYDaw49MqslsFNgVSuEKXj4ze1l+pcJLGk1ZcV9EqsBqIowyQ36JFihEoMsjLdtPGWvwpsdWJRRsegmF+JYOQ5qxtiyx39UPjM2nnOOrG4ccGwB394hA1TBcscE4IE0LmN19zfqVxuL6aUkvKyLJ52yAlqPZaiEktRiaWoqMRSVGIpthL+Aw9FiM784caRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyMzoxMS0wNTowMO0Me5MAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0FVUy5zdmdlWlDKAAAAAElFTkSuQmCC"},"38":{"admin":"Botswana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABAklEQVR42u3bsalCQRCG0SnCRLAjYzvxpsaWYA2bGNqI2YIYGIlgaiBoAWIgzoByz79wIp88Ll+kawxDa72TuYZHQGFRWBSWB0FhUVgUFiksCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCzy27CW57bou1ff/UHW6z/1399/bP9PrKbb+2FG5hqn43V+u5C5xsOsYLFfO07+icnGcfJPmJmZmZmZmZmZmY12PiN2Sj55962WU/Jdoe/hreR2g5tDLLmP5a4jS26QuqvurnrF+/uVDv38i8KisDwICovCorA8CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFn/SJxiv5sAOieSHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMDoyNC0wNTowMBsCNxQAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JXQS5zdmcDWi++AAAAAElFTkSuQmCC"},"46":{"admin":"Democratic Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGgklEQVR42u1dXYhVVRQ+pFD0kL1I2sSoIwSBCkEDPkX4EDOIBVr0A0FFBUUUWUERNAzGUCkSDSkNFf0YvqRDZtFEUkyp1MAVBmHGQRLtpWCgFwmloqCvh3057T3rnL3W/rvrZTGce+ecvc/9zvrW+tba+1TVFSPP//2XWtgrV46OXn5Q70Pd3nvgvZcPrZ7fO3Ni8IVL03OPXPdY3f72xKF9qy8tTA1P9o9V+pOotdmbqze+m90wMf755aefWtwxe2zgjzqYLk6evHrVqZ+Hn73qhg9m59fvX3tr58Vrnlt3S6W3T32haVcuG/ts8b6dfQePvDXk9k+/nnjzob7BuYHNd63Z1pm/dsu6NYCUAksB2nXOrZ9ObJwaPDYyfdsdxylkVweTAkstA9l1WYDMsIGAddOevX1nBmD158yS7JwwigYsTANWiSmWpZPd2R93fNy/oovs3GCqfacKc7O+OP3N2buXw6rPyIDsCNCJ7LEwsfM7O4sb18PiiAbgYcju3Gznnk2fSJBdY4/Fe4MwPXMyj585cPTdZepLkiM7G6Tqxwngq6Sfy8Orvj7+wCZzYkqIuWR2PraSzgRBf+YklRB5UyJmsvP51PiOILAe/fKjDe9fsD09+FSjJcr5TYuaHUNmxxRLicRYbnvw4lcTDy/aJg+KVK9DARZ8f0uy84GRHBWawqZp1w69/tP5ju04Qki3i8anW67fd2r6RtvZbNcCjeJ4L8iY9XDCBBOJ7Hj9E8ELVu6JvXLy8NCuGeizpmQAWMDajtggZd6U+v/azmz+jVGV6p8EZUyfyIlgAe5fntz9fd9oRQ8SKXCRs7h6qVIFQ2YnEYY7y8ywGAlGhRFitA1irM3D43fO3A+xICSk8Ozi6iWF5ww1OzGxYAlg/Xsc/hK+sz7mxsE7IpvXbp+cG/nQ9my1sybecWZcBT+AypgtIyefeKsGJoAbQHdHz15Z4fYf3nn7yLjtaTOBQrc4G5JqJbsGnonXbxHIzrSY0f7fj/75zIX/FEouIbSusJuXpEAK8kQZrTUgUOaaHRfxkQHqJjvYqVe/Xdj+EnxwV9jA22zvQ45leCncXPhy5pqdHOXVMjs32YFV8MBYAxWuGwon7xNj4Qy5kx3owCuzCy5m0skOv5GtHMfssRDOu/MaenSVl+xJyewayJjS+lPDzM7MykF29AIUA7Dg9t2gMWVP9zcpE4hLc14yZixrQMqUMb3Izm39bzecv22I0L2gQlGUMJyt2JqdR1nXRwhtmtkxpFASJIihQ4Wy0Qc+rU8SZ0tHu2KTMSU8kO0IV2YXC1gYivns4taDHH2UsBQIMUJmx1QwZsvsYgHLJEEfFQoUaSphNm+XZc1OTsBsRXaYnbhe6EMQeJp5F3XhbIjDwhAinewQ8NoWlYuH3h4yJj2ziwwsRFdyhWGzTyu5mh2XUEnXrmoRWyyyaxCB6dKDlpldGBmzVc0uENkpsOg1uy6y4+rG9A7Go2V2YYCV4w5YXpldmA5MCyEmkdmpxxIhO17Kc3crxJIxFVj+ZPc/NTsJuLRqwcuS7MoGVuSanUcZOHuyKw9Y2ciYHjW7JDK7XgAWw96YXOtVxDI7U8aMS3YMVy88swvZNFe7VqAGFfVYhSw9aLXOLsvMrgxg0bsxrTW7MIF5q3V2qZFdTwCrKdmRGuikl256yJilQmqJnvcw02bbG7Pd0k0PmaCHMruUPZYJU3QrNF564LPql5UQKWSHhp/yyK7xXJLeQSUBm7KMmTRwC5cx6X7O+CbXOrue3oM+s3V2EluKpd+NmSMEM2hQ4VWkkunGLNz6oJWZ7KSreP47qChc5DwWczdmkpldeWSXNLCCbhfGtR1PD5BdorlhtMxOuMyiNbtYULZuCiLYoBKks6CpjKlAEfRY4jKmxPIEzexSpk627cL863c9XLPLvq2vbt27twfdQYVs88rs5ECTdEmncWbH+1KNVm89kCC7wrujYgGrcTdmkG0wKGRn7l+qmV1CdgkZM+Q+TypjlmQF95UTrtnpj5czsBJYZ2fuTVp2za6oOC9Wfqdkp1TI1sRikp3NP6UpY9Z9Sb7ehVSQiRxj6dIDJb7GwKJ7JqZ1dgqIokbl1SHu8T47rdkV7oP9l5Yr2anlkBtUxkw/cE6BarN864Ha7IN3XXqgVoIKyyM77WIIdGfcDSq21j8lO7Wkko5mdiX5uSRGuzA1PNk/5iY7+KcyXgauNgxY/wHtNammNY8UKQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6MDktMDU6MDArJxFdAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0Quc3ZngkgrjAAAAABJRU5ErkJggg=="},"47":{"admin":"Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrklEQVR42u3dv0tbYRTG8Qt1dCyCIBgkiKijtEgNhEaMOvUfEPwLSku3IrVbXRwUhWopLg4OLgpZBAlWEEUo0qEFKZrNoYg/IKW0JTi8S6D1F7nvveec97uc1SEfn/PkwL2Jos75xUKBme7sqC2NDfZsH3x+O7z+59fR4eiIxlltKo3ny6ezU1uPXkR8qBJIra3vPR7u0UvKTUfqqLv3YeY3sFKbLc8+rBa+WCL1/TDb2t7mJrBSTqm/ucrL0RVLpIBFSnkhBSxIxdClgKWelISleXNKAYuU8kgKWBwRYlt8wEoopVZGdl4XT5JHEO+6vG9KAYvF55EUsIyklITFByxSymNKAYuU8pJSwIKUR1LAgpQXUsAKiFT9GcI3KWCJIHXfy1Mjl6pkSAHLbEo5fMmnFLCCIOXm2fu54/5ckqSAZbaeO1LJpxSw+MYHrPBINVLP01p8wPoPqYXdT/niJEcEYMVGamai/LVY0E5KTkoFDYsu1ci8+9+KICUzh7R0qaBhsfiART1XnFJBwNKeUnpJmYVFSgELUgZJmYLF4gMWpG4hdTzd96rjh15S6mGRUsCiS107z6sfm59kLJFSDMtSPbex+BTD4hsfsEip4BafMliW6nk4pBTAghSw6FLXktJVz+P6B4hIKe0pJTMLI7oUKWUQliP1bn/zwVCNlAIWpEgpqbAgBSy6FKRkw9JOyj3UACkRsCx1qcuny98GTiElAhaLD1iQIqVkw4IUM6JLQUooLE6dTC+wbial5TePraaUyldFak8ph14yqeAeWPVHynfC1b9R2JGq1HJd2QuWV8qw9KbUv6ToUiJgsfiYMcOCFDNmWG9+bjwf2rRBii4lApalUyekRLzclnrOjC2x3PVc++IjpcTBskSKlBIBy0aXqjaVxvNlnpCRM68Am91UDENA4fcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjM4OjE4LTA1OjAwQfoadwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ09HLnN2Z8XoUVwAAAAASUVORK5CYII="},"72":{"admin":"Fiji","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHR0lEQVR42u2cf2iVVRjHrxCtIY1lEVFQMdxsFjErbNAPUxKDNJjZMKKZ4rZYYv5CQ+0HQtgmRpYNzJylORRNW+FoTilps1Bcy9I5x5Zm7cpKDLU/WlrB/dw/nnE6t/O+7z3vfTfPP18u7z3vOec953Of5znPOe+Ndb/8eFnFB+dXfz7yq+GXFv8159Idqq67e9fV33xXeHlyx4qK2JJRL1aeCq7Fm6eX1cyl/jO51VnvPN82MWfDyMlJPXbdfQX5fOZbShb1TX90VTy9feDpzi7bc3tr/smbSuvnFX7/2g1Lx847VnrrxglHuHKu5uDN335dOWJ/d/zJJ+obd57qKynZvfdk3KlOYwwiyiDqIPulLP7nb7nVr6y/tyknOGRM6pmu85V/LB0AFkhpwOKuIO3Sc56ic1bjWy2/93YtPLFy/dE3R3eNn6gbB/pZvmJfzel9DiwjsOTg8hsNBzKtxRJISbCYWn9g6WDy+rwOLA9g4VyCD7pXyLyC5dVipYZJujmT52KUHnt3y5dHIgHW1LxPCjoPRhosG79sE8i8ukITi5X6R2ICE62o/c9eUNyy+D3ActbIM1hhQpauGCucH4MDK21gmVsCf5P3P64wJVhhWlYHlkWwbMQuDTfurW5f5BWsLeW76w7NCH/B4cDyDBZT5U9PLO/J6/ybaSPTQzbobPP2/MZ4coLv6c2OX9RlyLR5LE3wPqCtRCsoV1K3Qm/JWqnPAui6J+Wu6IfMEQIrOfEZ1SRYynpwAFi+asZKBe+hTDdEbQqjiXuMxGD7q/dnT+s62jNm/l0NXEmzpqwZR/YfYCWuHLpw/cGiZiu9Mu4n4zOzpH5K24/pnQCZvAAR9YpaUl5Jnf5Q6wwJrAExTTiqxlL+avBaj792E3d9MSyv+IHRgJXeSZJwmKsJWDKdm7qeqIIVHJTM1u8RLBvT8Nydbxdt3/TSLSVvzD0cjtKiPTca00U2RtbCRL3W469dHYK6ms1hDQWsijGVy1fOoq0jJddmj/kBlTu5slfde56Zf1uvVJy42n+uU4agglZo0Z7diplHQh7K+IpsdIj4bNH8LoOS9mIsFSz6A0wSHUZDAkf5ZAwqAIq3frR3eL8ETq1BgmXDOVpfFZqsy8hI6fYK+Taz61bbq0KmWS6hJCKotF5qmQvFrTnZY1XlW8qDFCDSIq7QRoAfk/kbXS5Hd13msXSq5pzUBKZ5glSriemnZtmKzG/RW/MsnXxq23ksprljx7ambb+CF9CoiKgYSfukU9WGWXeFQTLvSWukTDOTqh6aw/aQN/eXeecuk3blToBsV55WiE7mnWmWdkgHh4qIvEt+lpGZWjMtZnhLJ10wqXuF/jahbfQnCmAl3Zwm8pM/FRngoyo6aqYwEmDJyWP6g0yeaiH8ncdS6wkHMttgsfjHcdMfqZuXNhzfMEmGExwol2BxRdbQkvXZQ0vy5RU+oy/s2rR6T0EGzmPZtgReTzeYnMey1397YBHlAFb5uLX9s/ukPvzPqouP9D+9bOGapzaiH685tHXnfh1YxJqU4V6U2n5uO978fi4jaR0spipMt+IPrCAnSIM/14N1s7PW9tsGC3RAASs1qnXGpxMuS8i4roLV82HpgqqZLFAoz70o9wKcBMti8B7czVk58y4yLsFfpvAKmRr4H56ze/wBK9u9TC3TjEUBHfrJZ6k4MsAakDIVFouSqsUKFawwYfJ3gpTWg7xMEdyS2d6EZpp5xmQSJ4GIjIpkD7WuUJw8Q4m0qDM52gIsi1s6dLq9o6rp9WEMH9sXXAEmsjgEzkQbwRXnwkP+dE1t7bpxtKuqjLG4K7194OmYBjn0KCfMtj475VzVVHtg4QpleM6YoIyA/JaZGrAqTKz+ZBmUnyW2UF6nRYsxFoM17XRdzYFCPqNkmVFiCxtK/fxuZOuyP3wrS9roiTwRwIupUmXrNsBi8a/bJZSqllHLpy4jt3QsWiyTAxU6T+x1j0m3dRBkr8rkXpOawzxSomrZVYtG1E4iAEgNh6o6+HT10AotWrRY6TpFNNiP0mb2KXBMuH6Jl6oyQaqzWBIgXT3WXaE7nR0FoHH9rObYMZQ7nlJ1MRZXku8cJFTullKnVFp0YA1xsAgSsCLgJdNAcjHBajH1qhAlYJcpDNINbKsTWVqMsdzURkexIuTMdK+vyTyWDixgkicgSDoApe2cuwMrcooVwa6oYAEceKlgkXVTkZKK3Qrn7UgHVkTtljzrJp2gTJByZE9aLAmWPLkFUjLbrmp63aIDK3LKBAOBzJjLs7ipLZZECicIrCbv/Diwrgi3KPEy2dKRu4q4VJ2Vcq5wSNkhf3k17A2rOV2MpYJlYqUcWE6TNoyUBHuXxFgoV/g2Cn8Q58AalJYPa8S2DLt+0j5F4r8b3FQN3q2nKG+sObAGJVjmBwUyDJb75yen6V2COIvl1LlCpw4sp0PmH9sHDVgunnMWy6lTB5ZTB5bTKxcsFwk5dRbLaeT0X5mhki0/uFJaAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzoxMy0wNTowML2v9jUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZKSS5zdmd4LVEpAAAAAElFTkSuQmCC"},"77":{"admin":"Gabon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABOElEQVR42u3bsUpCYRjH4fcKmtRN53AVpLwLh2g9F6Fbg+DgXQhB4dosSIsI6qTQ3NYa4SrS4JpInQ891bM80+Hl489vPRExHGYZmVoTUFgUFoVlCAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoVFYZGnD+s62xz1u9//zN9+vzjmyejrm9X5w+PtPZnWeG0sF7UXMq2xe183y/3TuN2uVpVK8W/+pfecy9gPQaZVWBQWhUVhGYLCorAoLENQWBQWhUUKi8KisEhhUVgUFiksCovCIoXFwoa1u1sPShMeNM//Kv94t3i7mH3UR2Rao33Zu3lqkGmNq3HneVYl0xqtVrc7nZJpFRaFRWFRWIagsCgsCssQFBaFRWGRwqKwKCxSWBQWhUUKi8KisEhhUVgUFpnXTyquhRLNf5MSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0OToxMS0wNTowMDT5168AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dBQi5zdmfDTZtPAAAAAElFTkSuQmCC"},"99":{"admin":"Indonesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAtklEQVR42u3WsQmAMBRF0URcIKULZBcrV7F3H8cJOIq1ioIjyK/knBEet3i5tVJqTRCqMwHCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBZ806fpXK/dEASHNYzLNh+GIFa+X4bAx0JYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8V8P7lwPhQb9oxAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAwOjE4LTA1OjAwUGem+gAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSUROLnN2ZwZPnKAAAAAASUVORK5CYII="},"116":{"admin":"Kenya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFGUlEQVR42u2cW0gVQRjHR3rpTi1dfehG5wgRUS9Bl5eiCwX1EIUFRb5k9dIFD5wgKHoIyywIs6Qeuhwyu4GEQhZhJZWB+GAJUZBEER0yohuRYeXpb/TVNMNuarY7fwf+D7O73+yZ+TnfN7PfrlIxNbijUKndq+wCKsGiEiwqwWJHUAkWlWBRCRaVSrCoBItKsCKm0+t+Ft4bwermwbtblCn/zxD+n3dFsAKol8gUDCFK7FGm9Nb9oHV5P7hDghVKTV5MViYrv37OlNNDMyXocI56pXJUTt4k76P3EYqaoIijddwJ7ooxVugd4ssRbypfHgo6qFPbVMffiTVei9dyf0Lu3twbUNTgaFC4cScRd4LurArlbNG06VmqaZN9aAFNWSKjjQWz82fferx01+FdZVDUVKzLGqBUvDxrR9YOO9bpZ+lP6U9y1uSqMCK66EGmyHnLNMBwc0DqaqqjZtDDlsS1xLWn2SXnS85DUYOjOFN3jrr7Q+u4E4IVKS2Nlb4ofWEf5m0LfyIFfX77ZNHJIgkWauQ5uEqfq9rGp9vT7WgRrXMfK4KKdVnT5kzR463Fs35HCvq6+mbrzVYJFmr0M2FBj6vgfHt3TdoLiknbnYJZCnhhFkG9dH/QuuHZ97LvYdaRYL0f2Ly9uRhH5fmwAGuwjFbQomv9rPDjXSsIqKE1m1fWr6zXZ6DbWfFUPKWDhRoc1a+CNWnfzR5WmK7dLKa5pytgXZk2MndkLiy73LdOgyXXd90FllxLOg0WusA1bWzeEtsSw+zSE2DBMlpxs4eVqWuirdgg2Nj455WgKXh/Mrk4WZxEza25Y7eOfW+6Fpb1zQt31NGfja0B7LDbwdK3G+yRGXbkYdm0hUGwIqjyUYy+z65r65jqkuoSCRZqTOfLvXgoWiRYEdf9l3/fJbc7LLg/CRZq7E5W2reDS7Aiooh+5MDbHRYyGgATFDV2Jyvt2yM5ghWp6ErPZbCvDeV6x74e1NNp3Iy0nPvB+sD7iYQax8+vml8F9RO9+QeXYEUqbLenyugzFtaGUNOMZUqhQYuuhfDOgaUPPGoQ1OtXIblP7imjxgSWKceLYDkHln3thuwrCZaej2WfsQgWwfI1Y5meMHLGYozFGItghW1VqIPFVSH3sbiPRbD+7c67fJjDnXeCZXxWKB1Wzz0rtG9kEKzIrg2DZjcAKf/ZDW6G7czH6syaMg28zF5nPtY/BSu8/4t+MkiRI4p8UekQmUHqCyzmvPvJedfB6rmc97Bkytvvk2/p8C0dvv7VM+8VmpyaDhaeEtrBgjW+V6jwZYGQKd627YIFvKOMT4PU7Fw/ZdV+e4ylz1gmHGENljvfhA5jD3dZlXd2627vrGt64F1N+4G3BXfPNST2jFudt77/siNDhuWrPvpbOv5XhbAAa7CMVtzsYaX65a9Vfd3RmR8KL8/8UDGj/kvFjM4u+F4/b9icfb+u4BCAy9e/4ApNX5uBBViLF25viBeiFbToWj87B9axthsXjrWtKD9yaUW5frSgLOdo8O9j4SrdGlpBixJighUpxTDrc5XU0bWrlqta6RbtX/TDmbhKt4ZW7CgTrBArBti/Y5p8fUnVjx150zdIgRTO/DvnS7BCrzKU9n8VoDl+cOJQLyW/mowaP0hJResb6k7VbqgjWBFRDCcC6qDXws3ljV5wxzsDNTk+u6J1gkWlEiwqwaISLCqVYFEJFpVgUakEi0qwqASLSiVYVIJFJVhUamD9Bgc3K7F3aKTwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMDo0NS0wNTowMLjeFp4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tFTi5zdmdajF4sAAAAAElFTkSuQmCC"},"119":{"admin":"Kiribati","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHyklEQVR42u1afWhXVRg+Q6Hwj5ZLDaZLLbFG4bakzEiDDAxKCyyikVZOCArpS1D6GjGhsb5bxDQVqsFc6DbEFJOVyBzaxj7UlmvT2gSXzpnlHNjCBfe5fzw/zs713K9z7+/jn5cf93fv+5z3fZ/znve854i2w5Pnzc0LTL52Q/mc3bYMVnNGsodjjy5Symz98UQ1zuSdctbIOydO/7lgKApimXeohmbbHZxNVTKEEbbcUlA0c0vLnvsX5Q1Ctr51+5XZbxglmX8s1qChTaT2woGgtre/OnXakrbKJyfl1kC2fvtI7vS/7JBb0g62hnPt9y0N0AzZtaB8wpTnIDvf/azwxnqWeAdf6c/7GFHNZe4X6VCX2KSxKMWB//W9qidyinpa6/uyf8BvSLxjE5G1WU/w/pm79pXkTDr/RdO0nFWX17dsmbICv/Evk2mcLGWmEo102RVpVPCSoxFmO+SUe0AskAaScxKTCQTiXJhAIBnX4OIbBzqKdN5hgQqcyUCs/hPbduSOgjqcn5CZ8NvOZwvvLp51HySWyIRlLuytQ4y3ICKdd0YglpyT/rvaeX7qCC9weILfkHiTyWdXUTq1WhpIkWQdFD9j0PgWVEPGUhGI6yfeEEDGojA371VJg8i0B1X6eaEcp4pKpSawb2IlNHRsYpl3kIwY5zI2c5aQKd6TOEMkO3GTfik0Uz1YX6ENgYWPS3LuoWdOD6PIWHEwWGMMTBq7v2U1DlDC2/SyJCottCTsDj7RDnUGvs1kZZGUpAnqOMKSfNgCiZ4Wt0nxBLtCtCHwhPeG2C0GlsmSfHFM0xqLswt3sJhA8nENP2HaseTzxxSpn5KSWMadzhkFlEKmAV2YRkNvNk3PnixTip9w+5SpKWfBVM1Mqkkr0i1L8R0EhF8mipyZ5J673Ivnf1Gf4fc1iBWHazCZ4j2oOw4gDZODs5FMJpVULZrQbJf/tCHILIWpKK2jYi7MVXTBIsjkYykvms60w1IbSt6KcbMjxYlltwOoWYAwq7KUikzOEl+pshff90o4qE6uG6SZjJXQ5LRCyPs+VaZBlnJLKZleXPKzTl4W02G3KNLh2MS+EEy5irMUF+P+icU6QSy+MBijfn3Ipx3C6CFrRMe6TCzOW3K57V/yUsg5jPv1oS9YUd1WZWId7Zzz4YKzynuP+kPUuIALFCCyDM+5WHqObSz+JL8bi9Hlfe0P5PdyxvK/CLLkLGWTzEKExBhseoUWbPN+xm/GEv/2Dcw4u2xkSUfD8d4LWds3NzzWv3/d6tKsrmfuqVpaOg7hZHpJz+2OkaUB2qAZKEC8cuep433N18DVMZ5wYRLjXnq255ett179aGTn0SNjdaMFA19D4gn+hUTgfS2FlobRGb2bnm7UwVXaq31H3tnP8HAAfnYZXyCKMTE2a2y1LBH+f6p/urmp48wrZc0f1/esX16xaj4PiAOJf/EmvoIGlX63uDwb2LCgcOXAgyKcb7QIpIF1/t6/80Ya9O016Wc5vpBucYXbAfEMYIa6DqQnR4yDqx1OtySTpZyBIrDXuJ9tArm0V1Q+X1fU+kdd9cHG7l3H9p6qHczn+eRfQlt33enbhoZrHm+c3/Xgl6UN5W3FkGZwgWISF5qBAg9DwgNmcL+6tLuu409IM/Z+f+jwzJM58LAQWYvXvj8R8qaNyy5+unfR4NqT1TVvL9/y20Hx44a27/rmDjQPHRiu0ofBV9AAbdB83csP76g4x4h4wrgbXt904cDCsHFle/3jQoMKF7+d7dUPOUYo4+aWrJhdOSxb6myvPq6znxMQVYNgdxQtX/Pitv1r3qk4u6eNZx4knuDfeRNeOLe1UBVIfRktLrSFh6uaYPq48Eys/TwO17QH5M2hsjSJa95e56zploLe0M3jiiMlXafPPIo1uOylbwoPrVy6eF1/bbZzUtWR0ABt0AwUIPrHVRnvB9ePQ8O21y0uFiy3uPoecLZXuSvkchsf31G78sTm7Tz/WCJJFj9V9tCu61EwQoPbYhBfQQO0AVeV7ZDGMUL/uCg8GVdlL/5le1HABmWvjIsnjMtbgbD97Da+LtoNKBh5BmAXgCf6Ba9bybhAZNxg9zjxxOVMEJWf3eKKOJAjtXHjMwlN4grndOctzeIrnTTL6Z2Xs6hwkd7dZiPG1SkbwlhGgYvCQC7Y8QTx9e9nVdmQsMUJoyD1thOR9zLxKYRlXHRxkmWjEwGun423n014BjdsXG/ogSG6bb7pg7ltvkXb9JOzrKqNGTd7IWVcZJqo/Cz02/aqTo98TIGv3O6e8Ca+8nYcxMdQZnCxNKQDLqKvH1/h7YAThRvPDG9lrzfcaA+STeLGx1638RVQ9/sHA59fbAyvNwPN2JQCkXHDuwQCXKBghpnEjdZeGddkfO2zQt4dINGhG+FtKPgKGuSzd065nGDxZrC4vIvRwfXW6WFcuXgwietsrxxf/7iq+Ar9c34UZZj3mAGcCfCv/hm48+aAcdFxkXExM8LDle3FciDby12cMHCjsleFq2Ov8DYgcDOBoUY2wIwb1GUV5684o5uxV+XnoPaPbu31iBvslYnwZDxHFZW93lohYY8w4Um0AdbR5rZzFl7WDCoPxXPaBDyGYN0XnuPMuN7ktPFTG4URC/3oa40hKtcHm7r9OzHayWP+fmnYE/J/4vCcl8AXaHMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjExOjQzLTA1OjAwNMxImgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS0lSLnN2Z4he5NQAAAAASUVORK5CYII="},"140":{"admin":"Madagascar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABWUlEQVR42u3ZsW3CQABA0aOhyxIMECEXlnFExwaJhBSULk1YwWAQQ8ASniDpGIEBGIEJrFyqbJDI5vyu+DXWPd0ddyEmMb4fDotq0t5ml8dj287nWabdNoClYIEFFlhggaVggQUWWGDd8WjjU5yCBdb/w8ILLCsWWM5YCpapBQsssMBSh3cFy3UDWLZCBUvBAgsssHSYsPACy3UDWLZCBcvUggUWWGCpw7uC5a0QLFuhgqVggQUWWArWENrLr07kX2HzfnrZfm6K1Thr6vrtmufabROB9frVfOyfQ9gU+TmEui7L4XW3Lpe/7cHvSRHWkHn1pmmcsaxYYIEFFlgKFlhgObyD5bpBbYVggQUWWGApWGCBBRZYfR6jWMXCdQNYViywwFKwwAILLLC8FSpYSIFlKwQLLAULLLDAAgss9VaIF1hWLLDAUrBMLVhggQWWgqVg/TksvLrvDxwkcNOEzggwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxOTowOC0wNTowMKFWUm4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ERy5zdmf/ENrCAAAAAElFTkSuQmCC"},"151":{"admin":"Mozambique","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGvklEQVR42u2dXWgcVRiGp/WiIbFpbYRAWy9K3DYhZmlxi8TQEpcibiP+1GLToMRIDFrXP0JoYy5MQVqxYIpGoWDBn1asUUkIQRShWvpzUSHWpvUnTWgjdWFbUdpYMJoo7LsXXzmZ6ZmZs9nZ3ffmZZg5e3Yz5+n3fuc7Z6ZWz1R4dMPgLRXPTLa2WfNfan16K5VqQE9uXPJpZPSDf+/YHG2saW6ONXfwplAN6A/XFm+IrIYOLF+ye83bDUcaJh6Y4q2hGgMLihjW/lPdxYYhWiTVGFhSaZFU02DNlN4d2QL9uKFiYP3XtEiqObBokdRMgyXxYpGCeiOwbADSgQwWyQyM6jp51wHuTCT858Z2WCRvKNUEWApktEiqPVg65qgxi6RFEqwbIeU2D0sdyzo+Yxgjli8rtDvPIgVnhaZVWCQyMFokI5ZhsOQsknV8guVaUUR1Ro11/MIGy9ko/Sf7tMgCnRW6zcB8ZGwsUtAKDWBk19vRb8p/rBllHd+tBi6dyHjy7pzOa1tkZf3uZHeUmitqeSt+Ore/kNy6b8Ul/9gdn1i0YE0Ms8iJyr6x/njy0uS1ydulJj5MbE5Mquft1G37YKr+X5Gt+2OZjT1nd678NVx3dfFguKTzlzNru6qL/JcnZJECeOGPH28c33/+tHosz6jn7T5r11LebufvVfvR+VV2bdxe9XPs9qqOWmZXCcfaYutDf09bF363rItj8Zbboh4joqMiza/dG2lc+3Dos9C3ocPUoKllYGVQ1K4uf/Tm8bJHARaObXFR+sTa4q6Xi9etiMEEndu/M11SHipd2XJTtOi8dZe11LqVGiB1UdhUWgKCLxMLk+FlmM1dqf2urngPwMJx2hDtsrTUMRCJjxX1LB1Hb/pwA8EHuxbMKzvC4QweWM4ztZQCHcAECNAFYsbp+tjzq/8AUlLTibyAAJ+V/QCLdJRyW3oVZ9AbY1jgIpazOkeFbXse32ntV8GCIQIawCQ/NQtSvmtptMhggyUGTAUCiggBrf5n1RuWdXCg5/553RIsnCmvL20r2SsjHIZfJ5fyprBUWmQwwFJSbCTUEgvonc/d/MjCJmCBmSAAknghhkmY8CkgpTP70zJERxyl+XKw5xas1MCg/oQCAfTcu63nlp8ACvIDQAdXocOvv9dfNjRV/fOB+R2ASYKIM2iD9vJbpCaiHbXLFqnHs6T/npQWmYWIhVkeEm3M5oCCRATHiEbACC3Hr5wcWfXqvs54e/GLwE6279/1/l9Vn6MNVM4c7RT9pythRlcwYZHqPxhqxmeFiBConks7AzoY+MNPDW1vGjn11akdww+98kV3TfdliSA28UlF3RZVbHzWDi98L+xVziXNLo3TIrNRbhDmCPOSUQpRp29b3/ZPjvZ2vRV7rQJIod6K8wAOMAGsZ3sfO9BSBbBwHnjJ+IT5I743gxumlbVIJvhZmxUifiCWwNqACxZVJFhADXgBIECGq4htWEs6Fj+06YkdwDQdn/T3PvjZK5ZSGCIjVrbrWCKGyRU6IIVohPOya7QBWBIvCR+Wll3MB33vCcM8l1EqG2DZDDBmVRIpwDTSOfz9YBPiEHCR8QxIwQRxVUavg1Wb7nnSH1icFeaiFWK2KIcEuKjoyE0mMkrJjSJQIAW8XIDlLYaljml5AQJLrVwjSsn0HHhJsHAs29gpDBR4wZ7Mbn1m5T1wYNkNCc4g6T677kRv7CoSeRxDMdcDWDiWtSso5oCok6FPVJXS+xq4VpivYCF+yIHB8OOq3HFlV4XCTgedltgxgf7lt3hDCpanLkBRA2SF6h4pFE7tCptqFQrHznih0ID+02uIngoHtLyArhXqDCQWfCQWgOy6RyeUxBnpv6yEqUVRb1kUIisXZ3J+P5aMPdc9LqHztLRYLFL7Scc57V+Cl4Q773k3tSM+ODvr8Usy/XtM9a8FljTBWXaye4o3WGBGn3Jl0K69+pSON1Wfupmblpn4Xm/t5+YXugDL1vJ8P9XjDJZE6r+ZmWPJe6nB12w8Ca2tchvP9Au/Haq8j5orGiSwRMTCRj8k+M47t/JD/fylwbxLVsZf9aFti2pqn083utDU8vNmUV9vyVK25ejsLM2/eFOoYGXshZGFaXmFg6k198ZnyvKouR+xfPx/O1LV+js1X6OpZfjtozZtZDmUw8OI5cvy5CI0s6jCBstQviUfIOMtJlima+W8xQUNlrfEXHk9JC2PaiBiYZcVCwdUEzmWuErLoxpbK4TlsVZONWaFhWx5/CeUkYjFWjnVcMSi5VGNgcVZHtUwWLQ8qtlM1GLhgJoJ/R/N//S6HFnXvgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjM6MzAtMDU6MDCS9u0iAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NT1ouc3ZnDad5MgAAAABJRU5ErkJggg=="},"155":{"admin":"Malawi","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGTklEQVR42u2bbWiVZRjHDxTVhwpc0D4kYUGnonKm5pxU9GKnmqJhs0lqm1YOp2HaC5NSlxAdNRBEy5fUdLWZFS0Uv/UyEyqYhYpWjhAtVjgrKAiCrLBfH/5wdcasnbOz5/x54M/NdV/nvm+e68d13c/9PCf12SdDytJpq7V/NeVbYDVYVoNlNVi+EVaDZTVYVoNltRosq8GyGiyr1WBZDZbVYFmtBstqsAabft5y6bbrKmgfujbdXnUUC4rFd8lg9QkjBeh4/bwDTcfppf3FsHHlE1aiJ15fOHdpSj3jCIqmwSohVQjA5duaxQef34Gl56mNv7WcPFw9OnVXD/avZ04bOucUioUR8ORX2BnNuS1VmgXu6MjqBQ/OJfcQ/lMd22fvbKP93dRsw5pf1Qekjr0we+zjaSz04pkLMnwMVsKVMIOIwhQzEwBFpfDhQ1t7NWMppviXGmSpUih5qoSc8EcgyEC6lwIIBQtLrl/RBqYIGQrKBmvQg0WRIsxgobkKFDRv4anbds12sTdCFiFGWUkp7L1S99593gUXPZckrW2/5PRl89c2znhg9kraszpv6bq9fe+Jlm/aptVVpVdcf2RJ98O756zGZ+H3mYkTrqSNJxYU/8aaUcurVqlFfehlBG0zCxZmZ/y4wuRFIZWal8yr9sLaa2pvWD9lQ/369vSP6dNXn5vZlzmc+Sq7IDs/uxpLw7aGHQ3vVBwYPqbiU3qxly8qf7p8eV9mUX9GqOyo/KCyi5HpXbVt3bEXs9ixsCpWmNT7nyiwFCANNoEktDHw6q+jARzhxx/FQq/664wKLjPqSuIK8TdYRX01jW4qbxqlMBE27AQSLOjV39LbOuzM1bPhzPXnG/9+0Ysnv9JxdF5AZHZgopcVAl/fc6TBKuilAdM2hY+gan7SfIM/nr3D1DtkjKCIaI5UuPFkDfjTTlTeShJYmqseWjbrivojhIrQqkXDT5jPFqZcF6PpqoCG2RUyLKxW85nBKjqwdKdFiSHMFCPsmhUIbX8hpRez69oUJlYVQTdYRZ2raGtu0JKkIew8/8yVD7AYWedidi15FOW4coNV1E+F5AxCpRkr37lKL2aJz5isit5cz6QGa4AvMkFz9dJs8x0xYJohNHg8zeUbLGaJBVEPIFitburjs6rBGrBSqLmKrEDAFDLNWPkrgrEgxmzKClX/2/GswSpQ+Yuh0tPwwuyucoGlO60IVsTLYA3wRbHTk3E9H9cts2aCQoKlQMdSGE/2XQqL9IBU9y6EKmaCwuyxeNbTzNr7CyIfNxRdKWTbiypMunlXsPDJN1h6mqVr0Lyl5216Im+wijRv6XED2Su+xinMOVbMWKxKz9t83DAIPpKJp+26ZS7MaZaeYClSemyrn/HoVw8Gq+gKom6TNWz68iTuZvL9rjC+H1T049beYBXdpd88LapZk1n0AxihsfQoXvT+n68bmDGWP2aML5ri5z0uhUUNlmYv3Rrrl6IxW/TX91jxwFZXpZ/xRE+DNQjKYvyMTh/ytTDlKkPxgCDXm8dYjtVH59WVsMKEPAOWAljxUDTmCdr6YljLVt8/Yom7KB2NN5ha+LDod1oJLIJcZfeMHVN2OqnKHxlQLLoDy4yfuSuz55/238Vo0snGiZMfwz7iy8m/3Dhf2yg+2tZfMZramZHZ6dX1JFVTo0ZsWX/TyOE/vbJpxO+086H5Hj/q+MrWJZmLH8nsGfLofixrd3d2r/sw+/PHc1cOvXXfa5fftrN1xeHFbfPu3/T2vqkT6K0r39VY/xY+2J/Z2HHOs93YGe2Jre9VPvkHvXhOv+rdzTM+YoRJXW++fN9LOjI+9I7buD198wrGwbP3u1SY+3a2s/TFP1XIYA+UEkhCC1K0l03Ze6h5HdBsrjswbMt0hUyxACYUS0SQEZgLHOOM/KoU7nlJgEX2ok12IaMQ8q37D9756vsAAWoocChSUfEBL0ZgNM2IwESu0pUYrEQpYaYMkWkiZJqNNEvFvBUx1SLILIxW+M2AwRoAJcxgQeDJJYoIpY1eRUpLp8LECPQycqnBZLBylkvdA5GTsCtYWOjVLFg6Zc5g9QNqZB3aaNwtlXJmMliD5qDEYFmtBstqsKwGy2o1WFaDZTVYVqvBshosq8GyWg2W1WBZDZbVarCs+de/AGEOk6TV0Z+gAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyNDozNy0wNTowMLWNyNUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01XSS5zdmfFYhSWAAAAAElFTkSuQmCC"},"157":{"admin":"Namibia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAH5ElEQVR42u2dbWgcRRjHF6XQYrURgjS+FCU0WgRT1KiJLzUGS2tQSWNpLFxCUwVtUZs2JvFLTH2tBmnOi9YGi22MWtqqWBTsQRG1rdKUSqutZzSIwURCMaZaS0MhCve/D0+Ym83s7czs7N58eTj2dmf3bv/3e/7zzOyc4xSWLezoslF/vHBG2UvtS+S2hlhyW21FIpa8rj/+c2zyncnyyUYaz17aX/79Db921m59InF8oKD85hXHn7rkghtbMq+9Rs6xjr3B0YiXjSw+0LmpwenY+PEPqfjoupGfqJjOr/xj2amLxuv2pPatGugsL1wxOyMIcUl5FJ8Tll+k3PMGdW0qIviU+H1Xw+FrWT5NxAePDp397auGhtbuHJkkl1g6v/qw3Gb369T5Kdz5hJiFTwrIxCVWlH67UfVPPD6NfT3+0elSlk/DXza/9UqPXKF4FpYVQViiZz65S0qd4NItO5YEJqdmEf+U4RMVExVN/hCrqLpqS3M7Yj47Nv98Ovnn/IHFn2eRVLDEUiesOWN3HWrbyHt3XVNb75L5r3W/uqi81SY4+mPwwKdAaRSYsOoa1lQ8dMfj7zd3V29iBbfvmr764i3fvf3J+Nxali6QHVowk1uyWkY76N89cN+GRR88eyT1Y9vI8xL8k4LygdD+5NocFV9udXvjeyt3jTUeXD5zAGSCpKp2x1rqz08mjzY57+JdbMG72BPb0YLlUxY+8W68rIJCbpJNHwXpn9qdONdXqYRYpUU1Tz82CIn899mxKieT+EAjEAuRpkXsiaPQQvTElCOfVCcv32dBARaDRfgUjiwbThkGAiHZ7ezoWX39GRAIcrllYvmJR29HXFB2//Dak+AWTZGstVdh+XXafHc+YcglR/+kPy2mt6PrAD6xn8iXsCALSiDIiPok7AMmQTRgEiK24F3IDkdRkmEftI/WwsWqwPyT/3Y418PyicZk8dA/R15wZFl1NvFBKBAEtrtH7AmSscmRZ+fNKS7QK/Hsn/TYah/ycucTyiItVx/a27Om8KZtvTVd0jwWGAO6QBygF8QBw04jlRTdjqPQAlpDy5H1T4HWx0XeFeHTrZfvaVqbcgreiFfdmYlyy57gCjwTZRhPRqzUaG+RJtPwzi/I4p+C8kzCkfKJHZEcK/r3wJnEFD5RSfkRFi0fUCeE2hUYIy4pNqIFtEZ9GEimrmrvNeW58+nvv5I9Bwsk+CfVUiPtYwIgyyeIicsnWcKit5wKAoyhDsmrpBDRAlqj23FG/+VT/ykvM78g/XX74lOwxBLwTyDWNHySJSzaK4TIYL1pzZ1Nc7z0xyMWWkPLaDPYtOiZTxrFkZs0eXyaxj+pFhbrsXD7WY/lzie6D45CykNrXhOf3H7ilPlPrnw6EZ/Xe/ebBg22CPAJ1y+BT+qEheQ1XPLFvRe3sSUDdz7Rd6mXQmtBDVd74JPBNlzEP0ngkwphIW2BNLRkgLSF17wSA33NO4omRz1TfhX6J42Vca188ios97SCEihEwNbc8Zr1YeAQradT/4TXtAXavroxRAl8MqdWrp9PcomFogM7+4qOFdJ+HKQDcSBSMdH9WV/FO5d/PsVn75z37X4hPuU2fqdxIHkaPonUn0wQlsjsBjp5hlbnaVWdTphRN7shx/qTuDj0Oypz+KRHWHQ+FmiEmwpLTgWE/iOtjSFRyq1U0fqTCJ+mTPkN1icJ8Gk09vI3W0d4fGp/8PDc7euV80lcWH467ZAFnayH1ugMUkT2LDjK/0Q/r3z6paRyc/09Oj2Qn7kGxvGJJywV6wi4V+3ZicsK+cSMdmXhk5Ezx0PDJz2pUKSUKteGe+XT4PalTz5y2qD+nWv1HFdrNJ9MEJZyPonUx00oDQjUn9z5tHnVsdIPZxjBp+gJC3ziLdmjiU/+pUkkFUo+hV1Y4v4Jv3VpfFJMKVxnaPxTbsIy+UliaXySKxGvVS6m/oQrZz9R/8zRidS5pZWfznqmJgRiChexItW/S8cI8ilcwqpYv7p1W0Krf1KwaCLbv4sgn8wXlrh/4tbHlT3p6znxRaN/F3ZhKeSTzsEZpn+XF3wyTVjg04bnuvYnZ3ngk4q5A5KGa0T8UwT55F9YsvqM7v07VHEyfFK9vookSuU1n4IiFq0/ZfjEqY9P459MmJ/JRMunwITl7p+4S9qrGzbxfazlUwDCysKn3Pp35jyYQM4uwqcrh3bE6hbmqaRYYfnxT/TYgPnkbu29Ti9OR8zWsnzSSqwc+WT8/Cf071DTt3wKQFg58sng5TEsnwITlgif8KyIVj75FqLlk7Qo7quwp0I+qXigSuCRTjyN486nh/cmh15stXKRTCxpfDLmkXN3/4QIPhUv67siNscKRbKwKJ/YkmYWPpn2ZDDHP2FlPeufAig3eOCTwf+PYPlkhLBE/JP+v1T0syf8kzufrH9SEt39E7aEi0+0f4f1idn+3Y4FqdeTV9n+ncKY8U+MpLLwSVZ9XO46nLZ/Z2YMcf2J4ZOtPxknLOX+Se7UYcInnn/Ckvbo3+XdlBUTYhb/ZOYqmgL+CdH6JyOihFkAWv7LRYRP1j+ZJ6wQ9u8on2z9KVrCUvE8sUf/ZG9hPhFLfMCYcXWWT/knLD9FAc7KKiyf2JKB9U82FQo9xmn5ZIUlm1ge+3e2/hRmYWn8yzIUYC2f8ptYkgRn+WSFJXnJHkwA5PEJSx5aPkU1/g8yoj2cYhAXrwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjU6MDYtMDU6MDByt6+8AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9OQU0uc3Zn1IVJTQAAAABJRU5ErkJggg=="},"158":{"admin":"New Caledonia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFeUlEQVR42u2bW2xUVRSGT4O1MNZhWmiJaNUAtgVkBBpraYhkEKtQJAVapYgkEiqSKtpA0weRJo3XpjyoIQQ0PFgDasVLiJGkIYRbI7HEhEsMJkiCCRctVxOflCj92rjkOOM5s8+czmW9/GnOnJ6Z7v3Nv/699q5lWWVlXb2ZquHSzp2ZPQKJUx0CVQVLVcFSVbB0IFQVLNVMAEtXVTpu6liqClZUnV76/Ts5OddKdr1UVTjj/r2LvrIrr6KD3qDOqmDZMAKXhZvr+oprWnvavg2Fuz7pLA+Fe4/suxL65rOmzqbgu43DGh4f3sGVPUt2FY4Mv9/z3uncK00Fa9vHtIdHLKqYFPg3ajrxmQaWgAksegq6Pw2NOdf44x/5T/0eufDg6FFSgemVi81PB+7hyqmZRy/nHby080xR/gGu8LsgCKA8XyHLCLBwFCYeXOwYRQNrXeWavBGtuNTzE+obAyvBy34/wAFZ+bUpwWfmgrKikGZg3fAM/AMsnMAkfWhj3VstI63CjoKlWSdR58/5YdZ31/OWAOJgoVQgUh6sf5AiMzlHCiVXZe3OOm8tQPEtt8/Bwyi7ilfKg2WCFE5Terb4w2FzQapm+fyiWzui5TC3eCW+OGofK2FZihIWHwSARWCn8MnwbqKgSc7T7GWktfXT1rQd8EcXlDwwqq2baTNxF6nEcHKSV8889HG3FfyJaM9n9nOU0kOti73hqRURf/T1x6bNGXuQPpP59MvYLgtitPWgW32zunnb7WM/bx6/emLg6pTwazNypfo5bqmoCQeLaeidPeHS1CNvrGgoCi02meyjH/UcDl6lxSAzllSKI/eYeBgFly+D/Fv8GTEFy5FuXXbfxnF9lC3nUdp+Hbdj9Vc+qaz6lhclUoHj+fOyN/OqV6mL5/DFUK9KOrBaJ868u3C/eQbCRUCKdgO+BVL1w2trcqoihx6uyl7O5o85WKxb+WIoLkkElldFUDoZAJGu5lVX3ZHdB14Ax/Wvv+jaEjxn/o4kNlkQ/dfkcUrnWdNK9Icg/G569e0/c382n2ZZEKVjSch4r2jFND6UcVz1oSF2LEk0YO2464MTwVq3iSqagg5gsRIEL66Qioj5XqHc1vLk+vzIsZLiO6evV2iGuBRKsMxju/0e2g0yY+FYnIbwCimUUp5osOIrecm5pEg4WMRerxKPzD32VaFsN3gLFj0t8mLmuI4Jslaiv0luHcs5WAAkAzuhnr65ea7y37G0FDpCTbZGvQ3vIEW/Sq4KZcZy3n+PXXzt4V27WR6AZTKI3vbc7f13/ElmLNzL21UhgKbKqjB5oLfcrvJMdgnNG6Syj0Wikkf8UCDzqvjS2tAG6ZCF99jYeRXh5Uks6VhAJq9QFtMvtqdKIfZpSydaQXSSb+wHWti0IWPJ9SBIkbGAzBxiP4tgOqU3X4/NMEkmqzYKHCnKDpbczOFVk+LLu9iPzWiZcwRW7dK17U9s80crKxpmPbLdpEiReAjUNEjtjoXTcCc/x7c44L8Rq0+9vGP+bD9HKT3076PJW76sf9ZPDbzQ0F3+S3xtTFkc5UkHAjt5SN7pdm3I/XWnV58pOGv92vLco4f9H5900SF6Y/AyOU1KmWMXkh1Dk/3BgX5V/2EbRSqFwbKs9sjCVZQb53gBE/FcnmuQpRDI3O4YUljBXbFIabBuxiv2CQhylexgxVYgi42sxFSRSjOwbi6OTLMM3SCFDzlBygleXBn4Ny8tfOkN1oD2TzMeRnliN9AtUna8cETaEIP+dMMvFYLMAMsGmVVaeX3cOtZ98YEVmnzvQ7dtyPmtbkN4svqTgvUfaQwsBvymH7hoqp6kYKkqWKqqCpaqgqWqYKmqKliqCpaqgqWq+n/6FwbRnu6ejHKxAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyNToyMC0wNTowMFNCnfsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05DTC5zdmekLcH2AAAAAElFTkSuQmCC"},"168":{"admin":"New Zealand","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF0ElEQVR42u2cfUhdZRzHbayXkaxRNGVUwq101RouGG4IBRk20X/WtqYTtmaMYq5po0YJhasFc7VKzebsBay2lVQWTUqMXNFgL3YNIyVaZAtvRm83KJh4heB+zh+PnO7ZOT7n7d77++fL4Tnnnufl931+v+f3cm7OcGH5B7Vv/THy7qpj9TOXJ4pmVpnx4NCR3jO5K0erptt7cxbccNUTj2phznXfN9Ws3rGhsGsf7//liv0vdowPvXzltzdfYkbu8iS/uizvlseeWq7TO9fMiNn9emiw7GTV2YU1p7bPo9/hnyLlJVPjPTsiTdVjI33xE7mRrtJ/9+erbxBMiSP1hc2lZSPT11xdHGdZ45/2Lx5MmOl1rjM28ffSloZD938xdNNfFXntJ3U6dkoseudXOqJl5MwCuvx8ePf8p+dZr8N4fOLe+LIla1Y/2NIrxLKFF1jctR8PfBZ1mWSKxuI9TjWWLdEqz8yNTCpGt45OTt5qEEtIYwcxB+ZFxxBEa5ZMrdhiLHqsP3b8E7dIpmMK9TXTrHn5oqGzDt3d2faFAaFdIJaN8bNJtMYv5s8xsea04xH5HM2ljcM7GsXaFHKEd7AZUmhczk+imXwiltdmRefwrk+mzDBzCx8uPrD3fBoTyz7JMDp2NMThiY9mvnnAqSn8sGwgd/SFAByOUOLGZ+sGXl8Ehm6ECHhuiK+E2P7pObUt2opQiYpxnbg2VjdZnSpC5pRYal/0AtJi3QujJWrldKb8Kgxe4dKcO4sOHAXfiGy+bU/pwUW15Xv+pCVEGwbBB4vonlTE4m6wI/yxY/S1c1XBEgvD99DbO3O73xnsue+ixysMbZ0o3nvX17RwNxQmkmEFixjQVMTiDBfsCPvblicqu/JPXP/KI7Ew6IPGTbW3P99nECuJtITIFKYSZ7CoeoVhwGPLFuevLAwPsVomtje8NI0RxCDSEmpicRDONnpZ9wWx8qYKChqHwuAJcmAn4AKqLaKxQq2lwkms9ElCOzxtRHevePXuTUYLcSy1xfJ5rs2oBl3NaCSXLHuxNdoMOmOlAYbTK1S1V3i8Qlz6zBC85x6udfyGgKSdOFYqtBNzchrHMiPiV6No5r4Y7dwidsSxQh3pdlhdwpmscv26BW0DTjOhtk5yTovj1Mg7sWyzmEnsEAc3ggVJY4fuIW6uE3kndm/dr5oJUPvN5pwgNALf/33d6V1n8ShpcXlNnKZxUgo1mcCxJpMqVJ3qBtS4fXLj51qPx08Bs+PtlwC5G7Xv7mo433ZUPR8TqnDZONohU0rhJU2PA+Gh+ZKoQyxVFTvVoNbjnFV+7VmpjCpg/6P5hFK/rNv4XcMY8XrDIHqXhHZKJjuaKZWQtAr9lLIZx+O3Yaa90GSMfGfe1uaOCgKbfG2w78aa5mcupd0f3Unahy20ZnP1+s5hT9LYagWpT8JwqR7rfzSK07OgU42r7YWhLTBARjAlSS+IlSHOARjUout/THEB30TD4TDP9/Tn3fV9a/XNFkYQMwS9OERnYDV9UDvY848pdEimOCKsQ/+WyJt3HNEKkCbHgOnhsIytaL24saizxP+DvE9f6XCIY/lIX7Crvip5r+l4pRdnDpZ1rPWHXb89d+aeJze0z6dfM3KXJ/mVWxmxWUWLyfdDNZAWdFVvQVVR/Tb9yDuaSTV5XGegxmKxQHJhXPNxpncThhy8H6R3dQwq8ozLSVZFk/F+tYyOFq/XIatr3v2P8TiI8HpAslkt8n1OZhBLBOlyaiWo8Yh4BLNDYwkKsQQFhViCQixBIZb4RIKisQR93OSyKIIBa6xMMj2ZMRc1ASWmUGLiriFVXKAQSzSQln6ijJgqD2ouqOhS241/dxZiCdonFvVb1J2qf4FJC3cN4xhs1lUElo7/4kelvFGMmURaQlTcLKJKR2Jh/swoxBLUqnrlSxu1TJKWEH2IK6JKRzfF7KzQIhpLUAKkgoIhJ5akjYVYgoJCLMkHCLEEswT/A9NMKns8VMzkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMDowOS0wNTowMKNYy9EAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05aTC5zdmeA9JKlAAAAAElFTkSuQmCC"},"176":{"admin":"Papua New Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHSklEQVR42u2dS4gcRRjHW5RogmaNLvsIJmvCsggiqBfFCF40KOhFxWgQBFH0pB5EQT2YY/QqqMEHeslF8JqgiCCo8RCJ5mAMohJxhYAQQRRiQGZ/A/Mfaru3+lGv7qKgGHpmJ52uX3//79U1xX3PbHpi611HL525c/fS8a+3za4sD26+Y+snKw8P9P/ubC6K5WKhWLzx6ov/vezH9/ZfsW3HzfmiVCN4Yufs/Ss3RPE98YPFPHfVRd9fcuVLH25ZmjuU7+NsIzsDS+cHDmz6beZxJLLf91aevYJVKpHcbdme5bkNWCqRr/605YP5z/mzbMPy3AFYG0hkDNYrW9DUweptFDmASM3n7doQrHWiyAFblDIc21v0k5tXfl9+cHXfiy/sePf063tv23Wk5xYr0ijSv9VxFsqA0T97jp5YmP/rj8NfLL4FZIMDax2JNC93lhuL+Ze/H/126eT5+WOrc59dWPhu79zl4PXDTbfu2X1moGCVJVqnYMqOdqWVUqSYOXLmy6ff2Xl60GAlEEVGNiNzSJ4ipXMGawOJHLQUlrgHPz//0MFr95u2Suez37w2e81MKlfPE1i1o8hkne5mZ0LcV4YUwOF7pXLDeAWrTCLjSRCEWhKskT1Ysdkt83yCgdXXRCuX2PQmp44bYOE/VYsgn+H1uc3vn99++5//vfnk9nOa5XIOnLWNDwxWdBLZUWQHBCw5KOCYg8JURmptqX796LFPl94us1XVvpfC5zDXVTNVFAVYMbfrNDsHzZibWGBvNC9VLYU2M+DGk52PDqzoJLKug2+EBUR8wKSQ8RobZmOTbGa+B6BrJHccJK4jBau0o1Xm2Bxwzgc7hOVQYeJdJK8uRs0+P7ZegSLfqMFSvEyJjAKstWUDJuSM8gtLy2uOs8x8kuPNoLGfg+W91q5JAmBFlGjVu1+Qqs6YK0CKlOs5bCEoOrDm752MSKPINaSQOTwnP6A0m0MVsKMD66lHJqNNLdLMJ3Vr4apz5bHNnO2gwTp8djKwTA0l0nD2O3BjRf5ci1q3vhexp0/noYhB8hi33DMap56bDI5ct2s0akuks8ixWc5JnXqfSOl3jqNU+xusxQ0ZDCygOfLKaKx+PBkXTk2GHv/q2dG4+8Bo1K5FtodMbJXm03mNPdAYEHcex5l3qfTZuPk2iCh8fCfeHueg1lR7UEl21AWl2XULLIVYI/BSpHTwLiCGTbRyx3Oh9TXAgQ7HtXKnr9tn2MEURBQprRiqWPN5za4NyMcCrzKL1QypOGuRZOHbgAVGGpqAspao9ciUd+UxWRoFWAicCh8+FnjtOz4afWrXaWO3+NuychOWDJiwT4AI0C7AKhPKKMA6eGw0kDysl0rkoetHo0+1SCxKM7zwrtYBRRxt3gUv/pUpHL3UUosYyjVYLDPi4wgWqzoeTEwiJcWqhSAtVFfHhvpAWFmPFz6fOu8+S2GJlXR60tFqlNK1aI1vhNUpy5bZP2mIU8/31E43ZLCcS2S3i2Hmh0qOVNciN6gDGunc8eczWPF0tAYreBuiqUK5Qdfo2t/qs4pTdiuDFV1Hq/8nfAzR1KSrvRRq2tb1DdNDsNTld9euE3Z/Co0uyxBHBLWnlFkd+WQslrsIrm66lRJQt2fS2R6tXVm7kkK7+lVgpJn32oIYtlbIEmouKmzjDclV+9piWolWBUVfm8I37sQSf2uqQ8sCmsC1QpaQ5bTvpupK8rSLC1vFmdB+o+92a1PNKNJ58UTceS3amF6U+ZDZOhbLmb9YtElp6oKxhFo21iV3J5F8s1lnNAdlom59r3X2aPXzoIcRM5YmS40H+f3stlW0WU6t6JUNlhy87Bv3mjXhqK0y+yP8CHSARKtF75TuZjPuJnUc1Rbuml60Wc+Pd0VV0TwTapE9fy6yBCzNgU3ZqpjBYn75jdEwlxNx9JliAGXsFmelR/zHquY25q4L27jkzHhausvDlMMeM1hcOO0CxTZoe7Gf5cTnw2Kp5GEvQdyn7fS/dQBgUWEEKeapzUJS6cdiCc1l0+PuAn77h8Zs+uUDSGS3lqPs4ZEWj5O0uQEK18sZNqeVytYBDqPINt9fBqIFoLlW2Pd2nUA/hVekWw0cRLtOsj8GkxhYCKuLJGc8EuknisxgjS83MBEQEIeSdO2rD5f6NubJWCx9kidU2jPv0dpDsDSFweirIAaOIocGlrnXwzATGWHbdXJr8mKWyAxWnrt7LjIaB7+I+ZJldKKoRaa1jVF19OenwjgEiQzl5kcHFkkEF/s1DE0itaPVv1AWscV99E6F6qDKEpk8WLTZYJ8YZKc0BcoR/UyWyFSiyMB7kNLnafMoBGBlG+ZEIvsqhdVP2gwnw94niYx6q0hG2JbiLJEJg0WfAmAhjiqRvM4QpFWLjAIs0DEljyN4VzllmlYtsogn0TCcftEhSGSuFebZSUdrBivPTiQyg5XndhIZ53bcee5hFJniL6zmOf5EK3MGK89Otg7IYOXZSRT5P9xaBqF0vVdrAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMjoyMS0wNTowMNZnUvYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BORy5zdmdYggSqAAAAAElFTkSuQmCC"},"195":{"admin":"Solomon Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFcUlEQVR42u1cTUgVURR+tmkTRFREiEGLWgi10GoRWoss2tirhVpJEZgQFRT9LJ5QGlEWUWGERFESBNWioqIww4wQIlCwH4SMqOiHFikhCVFgwftcnMd13rsz99yZO++dzWGYN++eO3O+Oefc75w7iURqw9Gu81oykZzxuCXHmezng0mT0fTnHO08XZbkvuqnNp9t3t039Gjz0obxkldNs8+pcqyo/+HcioS+gpmrt/zteRr+4/ahN0zTBtOl8y8XoK+A6UHTrVTVJS8wqTKhr+DE19vnPtwv3t/w9tmscIy3YOPOs71PUo3X5rw7HWOPwqXXnvfFOB5gggfSAROu7E7eaZ3XnsihJi1h4KHN39aNDazf03pgoMzGm6HeHnRBL+bgI/hG5b1MfFKkwTRZl7rcOs0ETI3VLSWllaV122rKpkwCLPgkGBLHjTXtiwenjs8b7/x36OKurmNf9qnXmD9WjENHhi7oxRw89cbL90Q7w/Q1SDCSf1Kjxzv8gglSBVPJ8Kar5R3zS+o3lZdPAqyaf6e6X37tG3w/Y3TB59M/LvxeN3Ll18K/1TAwJM7DlyBEemZCGreK/2IcjInxqUbMAecxN8zTyPDZr7cXwiIFbjAwUc+0va25s7QWYAKMVJnwupk1a4+U9T+ECamBKbDgRRiMSo4xpgosSMwHc4vA8C4AyGdMwEtrnjNRMFHP5A2srJOjQZBKBCl7j54GQSozoCx0QNZ75PVMAJMOpHJ4LMibyd5X31sRhnBMg9Ek4Y+JXKCBGHoDAtoGmHRSdd6gzLqaU89zgUnLYyE1hlFpNrPk48HaF0U4v6q6eUXfT16zIcxh/EWv9156vpBmfjhvlLaHk1fZDnms1AAXmLSABc/htcjP+FUbLvrclRd08GtAqtZeVmSb0lTGDz9n4gAWazILKHSVDVQOT8+Aqe2Mx/3xdcCq5EzdJ++2rdziJph85FhG5Yj0MUIYsrSty9tuvKlwiLp0bV0ZKMz5zZnsgcnbYxk8IIQnQOdMzb0dn5aBl6KEBY5xHtcYERbxpTqzQtz9MOcHWExvM4IdXcd5STU9jw2LnT2UGxd64wsmPR7LwBjwXl5cFM6HmoC7w2DRcopxbc41MHEAS+NtpsCiRaEcXJSNdpRwWKXQyymugSkosLQfN8gCZFSAEdgvHPsmV11jzAMVeuOymosAWPq0JDIntfxCE3xAjVcvcxOLY+UU98EUcFWI1dwkpKiN1mHCgUEvG8lpL8ylc6ZC80wBi9B0rYfK3UR5R39lFCy/SUvPRj8HyAKTfqZ4gSl7e4wPYMGECGe0OZim3viVSvPOdHVMSltgDurcQuLHaZhjXc3F1yf5BhZt9MNqTm30o2135j3pACXG8WotpDPxbPSzUKErBGog1FCI5Lqn6vWbkZTKRSE8ZZiWiSbAmBhf1Yv56Cf+Jp4s/0hLh3KsgI1+xjlWjka/SGtzAiYGugEGRgDCMXwJghHbdgZiWsqBQRf00qY/qc3FE1hkqQ9D0m4qun9motGPlQeCLoxP03Pownz0+8AETM55LCStEz5JWRn5bfTTL7xktPIp12M+OVagHlqEtHSJxwpW4XegFMPFM3ntmxPJ1+gXk83pAqY4dzc4s/OOa9+cgMk9YNn77opGD7iAKR+BFc6GBVnN5TmwIupJFzDFGVj2vjug3xElYCoEgjQkn2QBTLgxAVN+9byLZxIZKt1grZ9JwBRPYDF9+FDadgVYbFupCnN3ikg+j8XaHEdJSzFPQSfvvAm4mKSgCVJZzYlkI0hlNSeSrdFPmuNEsgFLwCSSAVhRfQdcZJ4DS3gmkVaAlf1rluF8ullkngNLlS+vdx0u/iKeSaRf+R+wRTkSd7OJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDQ6NDItMDU6MDAFxSQoAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTEIuc3Zn7EIwTAAAAABJRU5ErkJggg=="},"200":{"admin":"Somalia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC/ElEQVR42u2cMUscQRTH9zMEUqZJLeQTaBDEzsoiINoYxMLWQusgGpLGQkSCgkmaQLqksLGRxMIiTSCBgEdEvOh5nEiUqAhn8W8GJnvs3u56s/t+zb/YO2fGt7997817sxcNDr18tV8LQQfeLzb2p8JZD5pFI0yAAhYKWChgYQgUsFDAQgELRQGrxGqnVgdYIA5YKB4LBSwMgQIWClgoYGEIDvAUA1ZaU2J6FI+Vg/ca+bDc//uhFJsAVm66MPfppPFAijUAKzf9MvHr8nJUijUAK4cgOHm9MXdYP1xtjd00pLqCfQArE1gKf+3h9ut2TaorbF8AK5NuTX9f+vvEBUtXsEzJwArHEzybXR09+KHw54KlK/oUgPBYqeGe3/q49ufaRYqAmNwdAFasmfwgWN6AeP8PAGD95wbEBUE/IKpkit8yAZZuc3L1/6pzEHRV30w7oz87YJWg8aLsZ31z57T1vDv9Nn7Q9+9RErD0ze5mefvi6/nZUFWr+VH1fNXM3ruLo/nkcPRKtUKtFrBKlifJK4SGlFZV7fzMRPKuTOjnm/rx1W6vYFKyr5WQvFew66dG8n0ipcKEZue9wsruExWAVp5uf24eNesXj29XioBJI2sWm6e4Iss14iJCpEbzyxCAVbPmw9yDMdmzKGshr/RgFXGr3PJEXh5Lo/UWrBCwjmjg5FuS0Ghl91jZ128aLKXVReRYvHYRWc6uFLaS7A3do8lJ9oMhBETA6hle6tl1BkV1L6XkySthGpldodEgGNdPlNcRHG7jxa2E6dM4b6eRLQfEqEo7kbQ7QR8Lv/HS+f+Kq4S5ARGwDAVB1cT9xota18kfErfh7Z841SyEQkOqUOU3XrqDIK5ZpFls/tpFZM1XuQm4u3fL98ZrZDfxBywT59nThrwsL5DZfFGMyvtUlVor4fhF3tJBAQsFLBSwMARaMFgcT0PxWPxGMmChgIWigIWGW+wFLHI4PBYKWDzBgIUhUMBCAQsFLAyBAhZaUrDYtVFLw2OhgeodNePDm+0EpMMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ3OjI2LTA1OjAw3NKyvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU09NLnN2Z+iG1TMAAAAASUVORK5CYII="},"218":{"admin":"East Timor","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADBUlEQVR42u2dMWgUURRFp7AQEZegUSGbrFkXBUEtlCCmsBBktVBUJGhpsxYKFqJoYxOxE1LFFGphYEFsBBstUkXEwmCnBBERiZDCRqyEVT638MMww+zO+7szy+HBLcKkmT3c+/77M3+imfeNB5ufvWscX62ufKseGK/9+fylcbBWQ9E8GkW3oqtRc/rR/pWNX18faX0cO/xj9kJ98hiQoQZgScemR29vqDys3ty2vf3zw9yT3VuADDUAy9cr18/frSx8P/G8M/nr94v5kcZWQcYtQ3OB5Ufk8uWF9vipv9feXtxTF2Rrh860d3VwMrRHsOIRKbx8yIhLtEewfG0tnR2p3FBExiGTk3Fb0a7BSopIIEMNwEqPSOISzQVWPCLX9r2cqreSIPNHGNx6wDKLSJwMsJqhIzIJMn4SwDKLSJwMsIJHJE4GWMEjMh0y/AywDCISJwOs4BEJZEMI1t5zrooTkUA2JGDdn3F1Z9RV9v/a8fR/pV+px3V6i0hWl2XehF531bnnSpClw3Sp6ap92tXRTa76E5E4WcnAEhwCS/V4ypXvRid3unq16sq/ZlARiZOVpsf6NOHKx0sYyZn8v69HrrJ7VYhVZDpkPIVRCLDkTL4bpVdvXtWfiCQuC+dYbxZdpSNl5VX9iUicbABgCSa16sIli1fpSvVb4eb4ISISyAKCJZg0Voh3VNlLeGltGAIvq/FEOli0+WZgaaygZlzBJ7wESnbf8vHqdvpVhCgEqeA9ltp2OZk6p7iTCSC19kk45sGrP807MA14r1ArvqQ1oD9t93FU15VlCh868lgJFhQseU+4NWCIyOPogBKAJe8RWOrGijmvwplKBpY/hbcaK1iND+I9E/5UGrDUJ2n9yGMzqPGDft224TzoB1gBNc8qTw04cybAMnuZgq0VwDJ7/QuYAMsg8thOASzjV+xxJsAyPhSEnwGwckUe2ymAFeTgNW43YBkcFYkzAZbBXh7OhJodx83Rj6jxBwSACeWTJygfaUKH/rNy3Bo0F1h8CBMNof8AiPYW3AG3OeoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUzOjIyLTA1OjAw4PjpFgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVExTLnN2Z3RlsPAAAAAASUVORK5CYII="},"224":{"admin":"United Republic of Tanzania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFG0lEQVR42u2dWUgVYRTHb7m0mOVShi1WpnTbFyVDKrCghGilhYJCozLMgpKgKEGkQNIkHypBSdDSbNHCNLNF0zYv3cAeooyghwqsiCJ6aX84PoxcvXwzd2bunPn+L+flvjje3/y+/znz3W8cMaEN9cmLUKk6K24GLMjl8tcmvW/6ND+gxvl4xuTyH2FPV0YV/Sl9viFqcH+1LqJ41ohlsd3jvgeEOJIcsxxBBlbAxLFuHHCnfu74h69d7pif3pH6uOFJdVRNjjPDNbQ7On5k18A5hiMFsHhV8lNxw4P5U7JE/HS7qPRE2NbFcQn5QZkmwWQdsHgtPVbwk3eY3iXf3Rb5xQ9+grF4pT02fgJYdvKT3/ITwIKfABb8xCc/2Q8se4R9pZ9oORPx07JRyROCx1kaJhiLS37K+5Z1JiSTgZ+MAwuDA+//mfzItn/O3yJ+am0pfxXWycxPitpzG+CLh5/0qvO2T30SuLPKfWLd8CKABT/p4KeMzxtnDwmn9oKuC2D5zU8FO7LPDnNy9xM92PYckTiwHwF+0uanrmsNmZGj+7tSGMsnPzUd6BgzKR5+Ulb6FGDBTzr4ieqLU66OxD2bH9X/PfMLYAnV5a7mjwl71frJpC11Bvd33q+X/HQ2v2XJrkWxq2rc7dEOR2Xw22MASzc/dcRWdYaflNRPBBPV6Mrzb14DLN3yE18/TdsyeULAdcpPGv0EsPT106pdKYHBL/j6KS1y9ZXB6ar9ROgoMfKs2tp1Oz3AUZufTiceaQ4N5NvfkZ98yk/ekZLZWHRjHE67Hz99mwx+oupTfvKOkafD0N95v1/hJ9VgydMVwk+q85PvFX7yzE/c+zsRP1Htw0/cwTIu/iv91NXpPjj2NPzk6af0qIb1xfOE+jsYS1t+soefRH6CYaCf7AeWNj9tKkm9MKiSr5/Uzp8M95OdwFLrp7KveXGh12TIT3S95z60lqbt6/GT0RhxB0utn56X1dZFpMvpp7CcquzOCpMw8rQgLz/RkT0i/Z2d/CSy/6mXn8xZ7PgaS04/0d9P16I6P1mncveTsr+ju5yvn8iyIvMn8tO0xstb76y3HFJWAwt+Up2frLDkWRkspZ9E8gTd2fbwk8j86eKStjWbQ3r8ZGWYrDN5h59U+wlgiRzJKj5/4usn6kxF/ESf9uEnLjCZD5ZaP9HMhuY3MvjpfYm70Hl098vGewURps6f+IIlW34iP1GXatv85C+wyE/7x7fGzGiEn4T8xHfJMwcs8SPtZfOTrfKTOWBpy0+0f4i7n2ivhEY/2Q8mvcBS6yd68sXXT7TzXTw/Xd/S/nPtGxvmJyPAIj9lNbUUzcyCn+AnHcBS+0og2fxE+SnxwdUFN1baPD/pBZba/o67n2j/u09+khkp72AhP4nkpx4/yQyQOFja8hPfn3Sq9VN2ddPNvEKd85P9DKft+R2dTMJ9/kSnxIj8hLWXn7DMiVSaP4kcyWoPP9H5VX72kwxVBj/RbUB+EslPt549PL5iJvxkCFjwE+DQDSxlf0enUHL3k0h+6uUnAKEvWNQDIj8BCN0q/GRqlQdfvvNx8hOdpS7ipyMlzVOPpsJPAEs3Py08VnuhdqJ0zgBYxvlpZEf10mdj8TUDrD78RG/x0+gnVIClm5+w2AEs3/2UklCXe2kPvkiA1a+f6F3t8BPA0s1Pyle+wk8AS2NdHJeQH5Qp7qfcwtvhh9LQ3wGsfv2U48xwDe1GfgJYfntlGfzEvf4HC+MEawCTOAoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU1OjI2LTA1OjAwGam9QgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVFpBLnN2Z7vKlZQAAAAASUVORK5CYII="},"225":{"admin":"Uganda","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKElEQVR42u2cbUhUWRjHjxvVBIXSpoblBluEJUUbBU0frKiYjdqJjAyStnBben/fsIgwUpaMXXa2ZlnTXqAgA0EJ7UNF5GYvIrVS7eaQFfau1pIKQRBWH/59OHG6d8+8NnPP/8uP8Xrm3Huf+c3zPPfM3BGiUpQJF0lGmAwBSbFIikVSLAaCpFgkxSIpFklSLJJikcYy1Z/amrYaf+g8lqluxxb75wa7R3XO8I/E/oxUMj468fmIVw7UPhIkGWGKd13tw0gy0mQISIpFUiySYjEQJMUiKRZJsUiSYpEUizRXrM7ywuS+6fb8r+fnYUMf64yMDV8MLmpI6RebfeHcVcbP2cUyGvoU9/9x93O5WrrHT+zbjcfRIOZ/+IW7xzVAfSxTZx55i/489tsxz4MJnq8yqjuOb/tzUcqd3eWBfUngw5LzKf5JnUXNexv/6OkNBG4l9wys8pQVYCSeJc8W2rnoPFceo567TuTlkaEdpz3b1sxY0f+kiJ5MsSFCBoYzw4vNpdfWl0Kat696t7493fr6bEZd/pGueTkZ9a2TL3WMagLlMSC2YAZIFs7x2L9gVscfvfiExoQXKxxCAuQeWRQw0N4yNfBD3m3vQiHGe77e8eUUSHZz9Z6GTT+q4z9INrDKU1Yg5zAzaahYePdbKfXqyvOmzl8PTizLLM/F942mdE9d4F657pfl3qXz1IwVD3rFPidRrE/w5YnKBf6frLRArjpUUuzZNRO5app7pm+6v7KhcvLJ7+yVkikXR3OUShixdAInj7Ef/6R0+WX3HeQkey1qsiqy/E2zZ40dMmLcBt+2pxt8+krJmQ97ZCmMWFMZn7QqfzLbCtvmtxXmzc07s3iWJ/Pb/Z4RR4uOXD82IVix5LKY6G9dlsL/a9U1OqTtd3c0b78ri4V+K0SxegOBW8mmtfMift4lVhfG4b+fMANKkk7xykmbMS4nDeXPW73wmrcaj2tv1v1b9w06sGD1siqIoS0iRDbmURFLfjmdSpwqWmkdsXANiIYdV4XIYX9lXnhzuQJ6oZFvbL66trFOp2PDUqoJ0QbFxmXDS/p0msDjXetyv9+tk12KB+0ZXTxIvpkJkqHTQgd2rv1UfnUm5vw7a0vN0ntYl7eaEyPNibZBN6yiZ9K/mlu1YuXjVVV4LnIYSiS6LiyW1lRMqk0fjQ890EVZ6YW9805oBxJy6JQtmchSUEq9pVMWC1TX5eW+jWI5kFAhtNYbfRVmyO7OPpz9DD0WFlFlvVAW5efeGNm888ZI+zuVKVbC0zfnt1LfHJ1SCAVBiCVfJ2ILRkImiIVvQ8izYY/8URAjCiIacKulUXRRIBp59FtQCleIEAvfgEDGur3PW5PdIpfajnftBzqqsEeKZQQhhyoWlg+gER7L5QySqSLiwx+1bcde+DNGLtP6LXU93aq1h0CqWPYtv3F9FcWy10suZGjSkXtQHLHFSkF0VEYrRbHUVS6UPFksZCn812q1HVuMW6miWMHmMCiCTIaiBnWgHT7YwX8xkvnpE1xSmztG9AfxNVxQ3q6O0fmvOlJn/nBoNbPVfnXGr7lYkJ6SpFIeE+x+7Y/BKobhxy0a8bEayTvgSN6wSlIskmIxECTFIikWSbFIkmKRFIukWCQZUbFe/15/Juk6SUaWAjdMJi5xb0yin4XzKJz0s0Qm/x6VfWRiHx/Bl5zk72ORpor1efNTsHt3djb9vNEQfAEYAZZCkmIxQ+idl1PPlMsNjhI9fo6TPRbp3IxlprjOzoLvAf+YDWkozYq2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NTozNy0wNTowMHN0tmgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VHQS5zdmei9Y9lAAAAHnRFWHRzdmc6ZGVzY3JpcHRpb24AZmxhZyBvZiBVZ2FuZGFggYa5AAAAAElFTkSuQmCC"},"236":{"admin":"Vanuatu","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGpklEQVR42u2dXWgcVRTH7ya7yqamSRMrBELRSBsSbCCYfjxYKMQWRQ3WVvG7bfyuIvhZyIOI1lZUEhAqolgfDFptClWhIJpWq1iQtGJL+xIh9sXGgilVUBqFKvntwgm3M7kzO7PZzJyXw7A7Xzv3N/977v+emTUrVma7aj9+58i8kSXrD3fUdXTeduzv+sauVRqJRw/WfX1tZ7hv0xyNGTAvmIXEG8ZyAw01QKbNqTEysIiNC/Kbqh6VkGlDlvO2SSxYXpDtXnfZ8bahJDWn3jDx3RjmmUfu21a95OotzXvMPhfI7rz3kq8WjpUTsvI3f7RHnLv4lnLm5t+bjxhjTtQNfZh7G8iadl4+bA67Q7Z/S+2vS9vT0LmowgUA68LkT/+TRUwGZBorAqzxu748Y6bhJSH74czghuzIYx/dviMz6g7Zw8cvPdn0rAtkqgHJGEzYezBdE+2jmcY3x54/mKmVkAGWDdnm6p7Bqj8ByB+y1o3VJn/AHTIdwSXJw5s2KvSCzFaybxfvqs/NDweZlw2bhiZMD6aedsOqGzsHM0+/X/Pi+dyes/O/ec0bsi9eeev+3IN3/La2NzPhDlnf2vyti64HMi63dotJgnsGH0tCtrv11e7ssD9kny4baDamZ3L18uzycJCpfqTCIHWHDLBkBLI1J1a+W7XYHbKXtubPXdkTLWQKUNxX8iLJe1Cw5OgPaABIIiVRAz4Jmcv+mRqPAzJVvgpVLC/I6P5syCRqQIbaJRUyjZGBZUNGIk9Sb2uYhIzBAd2rO2RxF/mkZ0onvnOLGCwvyLAnJFgyYm1gc2B5uOxf1l+okiVcsfwhw/fCaI0PMs2lUgSWDRnTREBm4yUhe6P3qd9zL7cfavkks7lyINMYP1jmipr8eLhtmX8EMia/vXKy0V8+a8x+AGTuRT7JqCRLB1hgJGDCf6LbQldcXCsvyKiqACMvC4Nvt+16/PXs3QpZYsBq6ajbRLaEcfDz+s+fqNs7sf3Quvp/Tp8fbq7fwSeM+NxtBRuyvqMPDFSfAiMvr59vwxX5KGQVARaahIkweXLkvQWnLvx1bGNDr38EOJJxl4a3I5qEPhUgm9qzl5IpZHMGLJBChyQ0UpnIfljmcxs+oAyHl4SMYxXOx9IwWa4YXyWZxggepgAI4AAXmpbsyk7eaUg0hvUlZGxb+qCBfI69xVeuiOPvHhmNBt0qvjhb52P8k3QmaujOJBY0qj3BDGp8zjp0TBIs9ubutrtrqku5YtBKMo1hvQLfr8FIdnygQ5MADeuAIMsk3VLPpOYR0bM4flJQyNwryTRGAdYUEIz7QIH8yTYdZMZT6OYsSwLspG6BWikemEt35l/kU0q5osaSFItqBVBAn7yqTFEglsHIzocwI6T+lbMJ6aBdyhWBDM3TGC7OABYr2Z0XoNAABeCmtIfchc/BDnRYX44ryw+WDVnhtrE0LKpYyj6jOh+vmYy4z2EGsBhJgQIZiexi0KeC9z0FFnmVzLpYk4aUg4DZAkuevyzs8b+ILpc4qnVctooDu2iPZVx8I+lLkZhLmC4yYyjUi/VRNdvTii/Hcu8KvYqqtTuLsSuUqTcZkvSx6ODkOFFO+KBwbFuoYhBgFQ5fFmWSMHndkZq8lzV5t/GS6iVnBsGIZaI0IOR4kGU7wY/DbrCTdKlMClNFgGUbp6Tz6AH5Fl0eTevlvNOc0TYk2hnHo7YaywqWHclgAIvGs5FC4cLVO7gboV5TOsDkNFNZ+F3dBxY9Z8w9420b3OJD3y/9Lsj6c2U/T87r7Au+VUSFfsAksygbKZo2qvlB/3KaoPODRZhuOt1ylTFbf1w2ZszOoe5bNIaNEaXJdnoOWHxeSkZll83Y3ky4FzAVYVrxR1OrwlShYJE5oUwsB1CLUIV+smSZNRWmRIElOymXQmH30mQvg07C5HREhWnughXHwxSl1LkXkVKYUgOWhMnlGcOgj38VYWIsow2caLDkA6vuT0UHgaltTUO/wpQKsGyYpAFhv8ch6NPPClOKwIr7pSBFmLD++vevvkabMOF2A06VC0xMASlMCpZTuZx/4W/Qd2JRJVGcHFCYUgCW/RY///eRKkzxxMq/PpFWiwd9ua3ClFwcA76O2+v5liAwyene7Xuv69c7PuFdYdA/EAhQIlcRMKmulDGiTNNgokKh9BI5hSnN0eX/c5h+UZhU+QJEr7+VC14iR71lJUz3av5UATHcm6W0EEXjDFFh0jh7BqnCpDEysLSqSTO2yMBSmBT9yGKZqpr0vk9X/A/7QFjXjDFyyQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjM6MDA6MDAtMDU6MDBytDGGAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9WVVQuc3ZndQMumgAAAABJRU5ErkJggg=="},"241":{"admin":"South Africa","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEmklEQVR42u2da0hTYRjHTxnYTVqLGhKVUQS1yoQKSbMgY0FZliF2oYgMicpArYaZszmwK1MjLbUGQSXzQhfzglSyIrALJVphmV0RNY3MIBJiQa8fDqzs3c452/ue/b/8P21nl/Pzt+d9z3MeBWFcelJMpPQ0tNp6Tca24N7ojn6nyWlxnh46f8a8TH0x+uPFjdfWVrS/CzeELecyzYvHzu9T+St6lIJcYJHUF+RN2XPxUvPTXXcG6PH6XGx5kHUAp0pNn1RmsMS5vancmBtB77Dv4bX91WncOwypNFiuDvta3LW7c/XQeJHHyOAw//EQk7ZTHCySowympXEVcBiM5SWHcVOHKfd3r76a8s/xfQCWzA7zyteE8pwDY8nmsIXmzUf6UVGxCa6Qt6akUl8TctQ8cmWHb/EiDtuZWJmQf5fLOgxwi8FyxtaM0U58GGHXTc1eP+N00ZITHDtMXIdhz8mnrzsIFskex42kSXG8O+ybtbKqrA0OY8JYrsmmw+ylzYfvp6AO4xgslh22P7Qqs+inhw4DXt4B617wqbjxK4fGi02HzT2Wd35vtLsO6/qR3nVwGRymOFjat0G/AjTpUQmZYx3dhaV5Wju9wybqTWtX5bNTh33a3Hem55ZKHKbcu5J+ZIojCIJO0AqBJCPK9AWBh+rbckI17/3UYT49Geq6pCMCi6QUh02emVVvmKuSOgw/lPKCpSaHLUosWJVyzEOHAS8JPv4PWFIcluMsypj3hp06DGtJhowll8MM30/uiyzj3mFYSyoHFkmdoHEGWPl1GHkPxGGS1pKATF6wpDjMUVJqCFnNmsOq17VaH5/xsA4DTEqApY46LMieNRA/nN5hpIWa9FYgXVM2sMS5IiZMNzLMfxyGdE1FwJLuMBb2wzQWszVhurt1GFJxsKQ7LGrY8QlRAyxsVcBhjIIlXkuahW2BQRt4rMPEDmvY0f6suRD5r/QqWK4Oo++tYMdhYpP5Q5JljbvP8hlYYoflTNrREPSNR4ch/5m+BUuKw26nXh03PZMdhyEZBcvVYcRPNA7LyC18tCAbDgNYcBjAYslh9HUYcRgL+2EAi4MkeNHYS4wXTjPAkqHqIlaDsQAWaiyAhVUhEvtYSP8FS8qKD34CWCr0E7mCJk53r755dg1OynO9eUyfdTfQ10/obkB3g2r9RPqxyFeGXisf92Px3rmADlLmwOK3XxQ978yBxfudhu76iTym9qbD8mHL5YTrja9ikeIUcE+Ou356ktaS1D0Q35k8v65O0M2YffYc8i8JP9H7KTfZ9rDJNi1k6YVLcUBHZrD87b5n+ElBsNQ0bYb+Lmf4SUGweJ8tQ2b8wU9MgKWOqaTu7j/BTwqCpQ4/0c++gp8UTHVMHKWfmkzqp3Nbr9haumc1Rm+6nA8IFEnUT0hFki8/iSfx0U8TJX5C/cQEWLzPcH8+9fWIL3PgJybAYrl+ctdPqJ+YAEvsp8EOQPgJKSXZmaAHP6lrH4vD/6cKPwEs+AlgwU9I7sCCnwAW/IRkFSzip+1N5cbcCHo/kV5p+AlgwU9I5cEytNp6TUZ6P5H7W+AnteZv4kIiSSgh79MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIzOjAyOjM2LTA1OjAwmx7TYgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvWkFGLnN2ZzDEwIEAAAAASUVORK5CYII="},"242":{"admin":"Zambia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEHUlEQVR42u2dS0hVURSGdzmQinxRUpMSg8JZED0oGkhQQU2jIjIoQ1PEUKEHJRFmUeRIMoLIEIIG1iQaXOgxiKBBgxAaRA8ocFD0wB7TCv4OLNme2+ler2ff2zf5Oex7HpuzPtZae+11uG7x4vN9zqHo9CqvAAUsFLBQwOJFoID13+iyH321gIVOizZcO/HQLd+2vX2Nc0dGWp44d36oq825daNH785apF+lye+pa+0d8p/h1syRGuc0z53HW09G44AVqK4d7xhz7tLnveecG/50+L5zmfbmOufGBjteRSOXrx+YHQEnA8f5trYHPbejax8fa/kewSrIkuC4p7Z71LmWDYfGorvZWUk1H0EGWIGqzCmwhIJVISK9M9w0Lxq/caZ1o3NDvd1XI+AExOOLXZedmzjYeyU6FrjWh1mAdK3uJmjinm5hvblpV2YqWDFnoNmVPIT8gTXty/Lj9yLDCy+p9W32TIFlr7LnWN8j+PQse2epj5RU/owcqyiDY3+mdWQqLKzhfQ9nwZJ+Hj25ezJMFkodywPZZ+k+UguWZhWXt2G8oknqlSbLnBYmPzhaH2Ox8OHw8fIBtQFXT1dux6qwBAOlUFMYkrFleD8sWvgU7PwgKC/lq5L97EsEwCpZXfitp8nNF2pKorVG0wpOqPk5lvVYFqbOG/tOxQc4wEL/qAKoMLKrRa095ZOkcRUpwEL/Uhuzx8JI3q4QVX5efYlXxZSByUvZ1aXAYksHzXEtaRN8waQMLEn9HbDQRJCxCY3SNoOigIUCFgpYaN5ptdZo6/vP9k/V1hKn9lpVpPJRzUdzyF8xbco6+HXL+NzKZ5UrBiq+PG1sqK0YyU1Xddd8KBuoubig+bdR57nNk9WOZz9Wlcvfuv5XxbQp6/Dpxltzf75duWR31Q7pm4mlF6qWS/0R6euPdfVV1dLn9UteVHYKLB+p5CqwtPljm22yq1pxpHYc0wYBlo9LHEb+OQJr9fvyR2Xv8gGremLO/slgxUFjxwELsGYILB1j2oBCYXaM0gUriQJW0GD9K17hgGUV0wYaCkPzWHYk7hiwSgQsPBZakBwLsFDAQtMDK/xQmAQ46lgk79PmsfzzMW3KXwiGWcfKJwgCFjlWQUIhYFFuoPIOWGmAlc/aENMW2ZaO30JDuQFNlLwn8V4hgOV7NUIh5QY8FmCFB1YS7DAtbTN4rNLNsbJ3tVN5R2mbASxyLHIstnQC9FjZ+0gxbWqqL4ZDBovKO/1YJO9oMedYdDfwXSHJO2CFl2PR6Ecdi34stFTAIhTSj0XlHbCKrR8LsCg3FBAsGv0Aq4BbOngs+rEKHgr5Ejo4j1Us/VisCik3zGiOBVjBdTeEnGPlFhYxcMqq/3LOzVdZpdyATnMo1J8PhPBdIWAFnbyXhsf6BbYQ7ys0lpLUAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMjo1NS0wNTowMGyZwHgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1pNQi5zdmeyhqY6AAAAAElFTkSuQmCC"},"243":{"admin":"Zimbabwe","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGEklEQVR42u2dXWgdRRTHJzS20ZjmUnKTfpASiQqRYkJRq1YtiBhCY63GVqW+FKH3RWmtHwULFmz0QaykRLQW0agPlaapF1uEktY+NPGjKkroh8QQS9s0aUJLmsZCDXiF+78P5zKZ5ezu7Hb35hD4s+zdzM7O/vb8Z8/M7qqOBZ9f6KituVY7t2Z/ck/jiqJnGna+1z3niaV722+Z86+ortI+HFWZ3ZnNmU3HHj/2SU/D8omH1i7vSC5a8ltp+vY1G5cVn/DWiHabPjonMgo1iSbWtFZYzoEFHXpk6IGh2g13bNiSSpePLPyp9ADwcj4kt+tFowNZcOcoDyzo1G1j4yNVH019/NKuTxG9wrfIQo15hXfhmY5iGrCo2rVIOW0zro/lrM4WKaZTGDWxWzILLKqwSHoXKVenqAWwprHILF6dZ7/aV5Q4Oa/37aJOk463/jhfKedtwlSpT3D18QiWbpF3/3XXWFkq3da2TqnMaF+Hull0JqsvsEwWua0oNakUeJcmFrAsKLXI1eOPPq+6Tv2d/l3wErCsqFikqGWwLm/8Y/3BX8UiRa2Bdb395I6+a+ePN6Ye3I5lsUgBi4UOhnpGjxyferUeevXJ4esnPsOvo/2v1KcWDzxWXVH2IZY5Fvnlwebm4p0XVz+3uVSJxl37W589VNWAZRdg5ZA69N26ZN/VA4cfrkxN3N/z1NL5kzd98+be8rPbl+2u6xlcVX1nogXLetzSLbLuXPLpilXru8r+mT0JKFECR7H96U31leUvDv5ZsySxgKqxNGzD3osv1WpFNVfzwPZO2+dMf83Xia3Yb26ZU2dP+8UeFb/nBIz+m9d7qqqR6vDs9qaqe1AcDsY5bpkssuneki9m3de9duHKWw/TcpxPT+DKQERUV8U3QRjfheIfRlItOl4IgxQITtzSLRIxbNfKisUlmbwrbAaAZTpSrI/CZcavg/J230eRgi2a4talI617tv7iNtGqW6Sp0Z1/xXqqbrcPE1PLALEviSCO1CNY6GnR/hbFi0YvxC38FyKf20QrtUhvTeAMk+vtA4thdoHmlEP7W5yLjV9PRc0OtgUILp5Ov9Y+fPn9D3rfHUFvaXjRmramn5FQGPi+8+WKc7ohQgGcjhcU5aBMxDN0/6GoA+pjskgaEfUYY1f5Tcz/dSZoHlg42fpdAz2RMLsr2/YPVDabwDJ17Wk80Lvn2C/qoMc2apGIYTBK0Wiq0jvpiFLU73Ox4Wj1G4l3gAiMj8YnE1gmi6Tg5uwyu19nu6QWqUrlL8J/plMIY4LxASkasWi/ioMXhQx4IUqhfOzL21iknMKYgQVFigH2BKSAhQkmrEEPTO/aU7zOvLVjvHaWPrboVvFcJCZMy+mMDVh594PoyBNc9GgEpKjSX6EoAfMMB1/o2tJyBRHIzzwwASvGYI2t6Jvb2qBHqfOv7+tMfgvVwTIlJrDGW8QSKywosJBzp6AAHYoL1lDI9AiH5Usl3RN1R+kwttv8lpy8QrFCLTWqxyeOYsIMTJA/oETndUm6IWbpBk5Hng5Fm/BCxAJAevTCGsQ/b2OIkooMXzmjBdMkSF1M5ctiAbCQJsUaGskoQCb4nHtXxiGdbOKDM6aWO0iyPWeskDMA4n8sLy8F7bscW/XxVo4JQdd3hUh10n6VfoforIh5pt4Vtby8QWggooHCR8r/GFw4Y3n808xpB//leIPSzUS/bDYLKU1M+sN9IkexPXJX+qAN3/IipJaGoq2hT0YyOObldhoMZ9ieptBdgIX8uNv7OL2XRvPszrMYYgCWf7wICpwpPf7BCm6eFtVAHv/i3+Xp867yBqe1gfBprND3dGGP5QSsnInatrZhtY9hsrJ+1m4AWHYfpoCxFt4jCejFxv2RipDAkse/5PEvyyoPrApY8oi9qLwURFReYyQNLWBFxfIESv+KGW8xA4vzqkjTiwbp+hv1csQw9xuF4w2zffC/ys+MTflEiqi113GH87Z3veQo44u6yQVm4QMC8r51UY9gFYblxfczLWHWIYiaKz9foJD4Ierxs3JiZ/J1MY9gmT6EGbUOaaHiGOan4YJuQ1r+/+0VpDbmjbldAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMzoxMy0wNTowMGTBkIYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1pXRS5zdmei61vXAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/0/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/0/0.grid.json
new file mode 100644
index 0000000..17113f6
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/0/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," !"," "," !! "," !! "," !!!!"," !!!!"," !!!!"," ! !!!!!"," ! !!!!"," !!!!"," !! !"," !! !!"," !!! !!!"," !! !!!!!! !!"," !!! !!!! !! !!"," !!! ! !! "," !!! ! !! !! "," ! !! ! ! !"," !!! "," !!!! ! ! !!! "," !!! ! ! ! !! !!!!"," !!!!!!!! !! !!!!! !!"," ! !!!!! !!! !!!! !"," !!!!!!!! ! !!!!! !"," !!!!!!!! ! !! !! !"," !!! ! !"," !!! "," !!!!!! ! !!!"," !!!!!! !! !!! !!!!"," !!!!!!!! !!! !!!!!! "," !!!!!!!! ! !!!! !!!!!!!! "," !!!!!!!!!!!! !! !!!! ! "," !!!! !!!!!!!!!! !!!! ","## !!! !!!!!!!!!! ! !! ","## $$$ !!!!!!!!!! !! "," $$$$$$$ ! !! !!!!!!! !!! "," $$$$$$$$$$$$ $ ! ! ! !!!!!!!!!!!! !!! "," $$$$$$$$$$$$$$$$!! !!!!!! !! !!!!!!!!!!!! !!!!!!"," $$$$$$$$$$$$$$$$!!! !!!!!!!!!!!!! !!!!! !!! !!! !!!","## $$$$$$$$$$$$$$$$$$!!!!!!!!!!!!!!!!!!! !!!!!! ! !!","### $$$$$$$$$$$$$$$$$$!!!!!!!!!!!!!!!!!!! ! !!! !!!!!!","#### $$$$$$$$$$$$$$$$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","###### $$$$$$$$$$$$$$$$!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["","40","185","228"],"data":{"40":{"admin":"Canada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC6klEQVR42u2dMU4rMRBAU3AIGmqOgESNBC0VN0A5AhWIkhtwCQoqOiQKRMEFqLmIKZ4i+ct/FxPbG2/2NaPIduzx+sUej2edVQirw9VhT/Lo/ug+hO+777sQwmf4VA5LnhJPrK9xXAmWYAmWUrAES7AES7AEa1fy4f3hfSMFS7AqyK+zr7MQjtfH643sRzfBmjFYN5c3l/9qSIpgCVbRXJUOGynkCpZg/VmuX9evw3qSK1iC9R/5+Pb4tjHJ4xY/Dj4OfhswcikZDzO1UbNgLRos2j19Pn3eYHF+cn6Spy3fwuriM+mCtWiwXq5frtv0gpoFa6FgtRiYKV0SgtW18Z6/8OVIatN4F6xwdXF1UU9/ahOsRYCFST7keSK3lv5Dxz7xnlGwugaLMuMlYycnc0lavq4Jn5rttMgSmeNczemXYDUEi18/xjLQkMLQMnjpbJQ6Noc87NtpHkNDL9KlNp5B8YSRQkl6lDO3CVYTsMZnmvHHHeNFW7EXajtJDePnjPka5jgsBKvhUsjvu9weKgeLxS62pVo7LASrofFevqcDqVrDU15b/r5SsBqCVXdP14PM3zkKVkOwcg6M5yXjg23B2hlYGLn7ARa9yD9nFKzKYJHLzm6f5qr4CdC7nOcgWJXBqnsU05sccucK1kTuhnFf0RxlzlwlWBOdFRJYN99lEc3/Gh4oWBMdQmP2lrhMp5dou11goGBNGt3AGVzdKKsWEg1L3vYRrB2EzcQ7x/laUYI1g3isHvqVvhEkWLOPIH26fbrdxTDQIq0bQbq3ocnTe7/aRcELVhdglYfZ9PbejmB1AVYaGEhbDDzzCvMZTle8SnyLXCSLGrmU5FsEzFBbOtgt3jQUrC7AImogxoWU/DDo/HBhnAi0Qov5MQuCtaCL15iZ+rnASLAES7AE67crjXq4ukiwBEuwBCvP+yVYgtXk4pAp72UQrEWAhY9KsARLsARLG0uwFgtWn3/BIlj+l45gCZZglcsfHBPAdZqOceQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjUyLTA1OjAwchcLNwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FOLnN2Z/LyUl0AAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/0/1.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/0/1.grid.json
new file mode 100644
index 0000000..eb2aeda
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/0/1.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!!#!###############$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"," !!###################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"," !! !!!!##################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!! !!!!###!##############$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!!!!##############$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!##!!!!!###############$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!#################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!##################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!","!!!!!!!!!!##################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!!","!!!!!!!!!!!#################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!!!","!!!!!!!!!###############!#####$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!!!","!!!!!!!!!!!!!######!#!!!!!!!!#$#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!!!","!!!!!!!!!!!!!#!####!!!!!!!!!!!###$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!!","!!!!!!!!!!!!!!!!####!!!!!!!!!!!###$$$$$$$$$$$$$$$$$$$$$$$$$$$$$!","!!!!!!!#!!!!!!!##!##!!!!!!!!!!!!##$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!!!!##!!#!!!!!!!!!!!!!###$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!!!##!!!!!!!!!!!!!!!!!!###$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!##!#!!!!!!!!!!!!!!!!!!!##$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!##!!!!!!!!!!!!!!!!!!!!!!!$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!#!!!!!!!!!!!!!!!!!!!!!!!!!$!$$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!##!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$$$$$$$$$$$$$$$$$$$$$$$$$$","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!$$####################$$$","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!########################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#######################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!######################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!######################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!#####################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!###################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%################","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%!%%%%%###########","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%%%#%#####!!#","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%!%%%%%%%%###!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%!%%%%%%%%#!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%%%%%!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%!!%%%%%%%!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%!!%%%%%%!!!!!","!!!!!!!!!!!!!!#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%%!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%%!!!!","!!!!!!!!!!!!!!!!!#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%!!!%","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%%%","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%%%%%%","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!%&","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["185","","228","40","142","90"],"data":{"40":{"admin":"Canada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC6klEQVR42u2dMU4rMRBAU3AIGmqOgESNBC0VN0A5AhWIkhtwCQoqOiQKRMEFqLmIKZ4i+ct/FxPbG2/2NaPIduzx+sUej2edVQirw9VhT/Lo/ug+hO+777sQwmf4VA5LnhJPrK9xXAmWYAmWUrAES7AES7AEa1fy4f3hfSMFS7AqyK+zr7MQjtfH643sRzfBmjFYN5c3l/9qSIpgCVbRXJUOGynkCpZg/VmuX9evw3qSK1iC9R/5+Pb4tjHJ4xY/Dj4OfhswcikZDzO1UbNgLRos2j19Pn3eYHF+cn6Spy3fwuriM+mCtWiwXq5frtv0gpoFa6FgtRiYKV0SgtW18Z6/8OVIatN4F6xwdXF1UU9/ahOsRYCFST7keSK3lv5Dxz7xnlGwugaLMuMlYycnc0lavq4Jn5rttMgSmeNczemXYDUEi18/xjLQkMLQMnjpbJQ6Noc87NtpHkNDL9KlNp5B8YSRQkl6lDO3CVYTsMZnmvHHHeNFW7EXajtJDePnjPka5jgsBKvhUsjvu9weKgeLxS62pVo7LASrofFevqcDqVrDU15b/r5SsBqCVXdP14PM3zkKVkOwcg6M5yXjg23B2hlYGLn7ARa9yD9nFKzKYJHLzm6f5qr4CdC7nOcgWJXBqnsU05sccucK1kTuhnFf0RxlzlwlWBOdFRJYN99lEc3/Gh4oWBMdQmP2lrhMp5dou11goGBNGt3AGVzdKKsWEg1L3vYRrB2EzcQ7x/laUYI1g3isHvqVvhEkWLOPIH26fbrdxTDQIq0bQbq3ocnTe7/aRcELVhdglYfZ9PbejmB1AVYaGEhbDDzzCvMZTle8SnyLXCSLGrmU5FsEzFBbOtgt3jQUrC7AImogxoWU/DDo/HBhnAi0Qov5MQuCtaCL15iZ+rnASLAES7AE67crjXq4ukiwBEuwBCvP+yVYgtXk4pAp72UQrEWAhY9KsARLsARLG0uwFgtWn3/BIlj+l45gCZZglcsfHBPAdZqOceQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjUyLTA1OjAwchcLNwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FOLnN2Z/LyUl0AAAAASUVORK5CYII="},"90":{"admin":"Guatemala","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEnUlEQVR42u3be0jVdxjHcceC/ojFMiiDRRIysqIategyKvoj1/1i5phREWx0GxWzsjAqyD/KotXoWJhERXaQyqFWEhWldheyxtJMJSnrZKfoQl4hR7yNnvpx4sRqcM7v88/D4efP54v8XjzP8/uerxGzknbuqq4J9VhcVRn34n5bWVtF28XQjfwV4fFEIgRLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsATrf4X1OO3lQH+yYAnWJ4uFhZdKDn+7qDW75efIz8dLsMIE1s2Dt5+VZ+T7Kvbl3/swl5yCnKqdpTED0xu65eU8+zsppzFQPSMbmQXLdbBq/vR7qtdTgX7KzPYkJngbS3t7awPdf/3H44tz58z4fVZij73wSuld9NXS3ZYj4BJqslbFJ5CTVQTLRbBAELcxs9PYFiic33Kzc0kDFODSXN1wq34H99fP9/fxdSxLSlmY2nHSmu2psQtifttzKnIivO7UVY8rLyIDpMgcqLYJVhjCgk563Gl/2lNnrQJQ/WxfcmVSy7nm6Mu1xNZXrUf/ieVz3Ux/ZOGD3OHXfRmj4ZX1V2mHrOdkIBu1kFWCr1uCFcKwmIGmNB3In1Q16Icd22MXOOuKZRQo0hwBRDY7Yw2L2v3r9yVcD75uCVYIw0qPK67d/IRWRXRWFJpgO6MRDx+cGdB0trJLQeE7MfHRqHNfVCy5t7ZoDHULUmSz+VlRsFxRsZirbAtzRlu3wNRQdi3myCmo2eo1uZ83plt/5i3bEFklmLdOwQphWNQh6gdN6sOw2uuWqVjAInKFnzphkZlVmLRUscIW1pux/TUsZiAnLDC9M8JTnxy8uFKeXNfsHQIsz8kL+3/ZZisWqwQ/wgtWSMKiGfGYV8XmrV6WHWj6ARZvf8xSdt6yrZB3Q2CR7c0qrz+zyrqXl3pt6KpWGPaw3j5yKoqtJZBin533PudEZSNVClggIxuZLV+7KyZYYTi88/LPg2e7wTbEq71Kvzm/dvOxFRPnNbLtyXU+Xym+m3rlAkSIf6Qc9ET6F/XNuNrvS94QyUZmVtF2gytggcPujxP5mpkIL1vD+O6PyBWmq01NBRGJ8UTmKgZ2crIKKwqWK77S4WHbbdLp0ZNPDL5PEyQbrdNisl9d0/iIy6MzN8SPnz9trmdCHtmAFTwpwQqr0w3MQH23DpjT/TFx6tQ1/u9GMjldW3mjR25n2xCJtDxeAriT37J5yKzTDa6DRTWixvTcFJX5dRonF5iWmJyyRh8aEXWMTQe7GUG7pP3ZGYsMZCOzrXOC5QpYEGFIpwmCA0xwoZ1xZsHWKjuec6eNZGuHZVAKlotaIbwY1ZmTOBIDGrYJqDpUKSYqeNHsuHPeUq+vz1BqGHd+LCnBCtujyTRHxm27rWB37bkHas77//uRZcFy0Zl356E//TOFYOnfvwRLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsATrvfgvOcMijdKPpc8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU3OjIxLTA1OjAwZTE/PwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1RNLnN2ZxIGn7YAAAAASUVORK5CYII="},"142":{"admin":"Mexico","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE90lEQVR42u3bX2iVdRzH8QUWTqWyG1swpAyNmiip6E3BLkqtySb+IcxMneLQBkt0obvQC1Pntqbz74ZWQlu5OYXNuZCmpRdzE3XaTMdoqDlX0xbkVijoCfY+Fx84LBS9eZ7zufny8DvPec6B58X3+/19z3MSEhKzsqfOCHo8fOPoM4frI92Rzsil4MaeDVXJNaXnWoYnjR4b9JgQDljlo5pTji83LMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyrCce/0m/03GrimhYhvVYsbfgr9FdI04frXmh8PmazKKC7FyNZ3rqiqurnxQ1wwo5LDB1J124XNt6Ne3Eip+mHnqQt3na8F1/L902eUnB6rnnxr2XO3HagjHfcQwyzjQsw4rG2093X78R6Xqts+rSGs1PYPp+Xv6ajJPQYaVievbiSd0fvDu5clTDnPXjR760d+WqtLXj75WMyNk4a8rj8DKsUMFqOtJcVjns6thrn54tI0tBh/ykx+QnVshYi7ZP6ktOWlb+Vsorb0MNZF05V8qaZxpWXMNqzWjZXN9+tu7U9d3v35zY+HpOQ/uOQx3LfuaYjAUsAGkpVFgcE8lej9p7GVaoYHH7tQgSoQYsMJGTOAaT5ipdgR2tvWHFEazevs6+Y0PBxAo91vHKHyP7FlIQta8CSuqQN6aMvE1fpc37QE09eYsezrDiCBaA2q60rWhKgRS8iOQq3Q9CCl7QUUxKimP6rZaGCwvPdBhWHJVCSNG8U/60LAKLVh0i9E+gIRs13S1KzT9RNGRle+ZWSHGmxoffJxpWwGABRVtpjtm7kVGApW177Q81vcVdMNK2HVL0VbwXoPwbG2QaNWP9f1k0rMDA4kZChNsPKYog4HTcAKzzz1Vfnp2o0yzNQDrBUjp8Viwv1rk+34RSa1gBhgWmPbWl05fv5tYC69+Pv924/3xsxoIUEWTAImOBKTYC7tjvjfm1s7k+vL56aueBwmJW4Fv8ztbMuYl8K8MKMCxyEo05DTvrYMp4teRUycyi5PpvjiSTReiHyGew0wEEULSF1+wFPs7h+kpNp2UDzbcMKzCwos14PynNE78s6Hz55sUJSVtWF1YQ4cXkCVKcQ9TCSo9FJBfmpn/YM2MQUYuj9nbAGqgIGlYgm3dIld+tuP9JHSVJeUEKXm/uXDf/82fJZBpXLTkwv+IijCiU7BnJZDAias6DFH2VlmPDCtWukBsMLy1Gd279Oaw1nSuADEZQezFv/dANWcpuzrpNgz5LZEXZscKxji0gxed6VxjCORZliC6HwsTtj8LKa/3t1xyNZCCoEbOrv/zi6z+0dBLhxau8V8nqrtNzrNAOSLnZtPPkMLIIIOatbWy7tpgYbeoHl36UlkskV7GzgwvvotRyZS1/fIquG1bIJ++6+QeBPnYMF4qawtLIqxCkV+O9ZMTYK3vyHhewdKdGAx59XrS/YOk8HSgQOZhamrVpP8c6UNBip6X24X9+NqwQPjajxZEWW/MNaBh4MozQGTpn6o5Pn5jwg35+5j2aw8ABJrIOOSmKrP8nZ3jxauyPRX7m3bAe5Yl4+fFHR6b++5dh+Q+rhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYoYalz52GCdZ/AyERbDpEd1wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE5OjM0LTA1OjAw6Hk/+QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTUVYLnN2Z9b8CTQAAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/0/2.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/0/2.grid.json
new file mode 100644
index 0000000..ae6dd26
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/0/2.grid.json
@@ -0,0 +1 @@
+{"grid":[" !"," "," "," # "," # "," "," "," $ "," # "," "," % "," ","& $ "," "," "," "," "," "," "," "," ' "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "],"keys":["","65","119","182","239","72","42"],"data":{"42":{"admin":"Chile","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACcElEQVR42u3aQShkcRzA8XddzR7YJoVcOIjLpJAjtYcNSRIXDvYgZg+THEiNcti0tRFxkJs4bIhEidGSGg6aLcZBkWLmgEREI+04/C6vXm883j/NPN/Ltzm8mf8079P//5/3nqa5y8uml1K9U56VnpPq+EM8Fn+iyVANWBRYwAIWsIAFLAosYAELWMACFk0xWPkVDemLha6xysif/8AClrIOPE76D7zCC1jAUjZXHZ1F/Hfullh/ILgALGApqGCSYeTEAwtYik92uP14/eZzTqj2y3wPsIBlWtmMm1W/COoHkznM7F3u0m9ps5vA+qCwBIGvasi7Gw5G932XbcbK/GQcTKiZvWv068zq4fDbZjVgOWrG6vaM/f43ftl0vRbLs/MlhIWdf47AcuAeq9L3YzuwL7OO9YEj2kXa/YjMfPavcgHLsZt32SHNhf4+nc4lHlIWSuHI5h1Ylir7pMRD7uUeZV7X2NmqA+vDzVjGbbvZDqzG1fVrow5YwHqhAkX/0cs7wYJopyx5cmNHj0zmNmABy9IiKHSEkX5LLq8bsnrrt5rlooPaBRFYDoQlOOTUWlng5LKCHK9qCw8sx8J67YVNmcNU3eQBFg/6cRMaWMCiwAIWsIAFLGBRYHE6gQUsYAGL2oT12ueiEh+vf+zYDhrjrSHjJ6uFZeXRRVXHvOf3ec+xtI6r4pLW76newM/Oxr6y89vRicEMmgzVwodF2bmfKFVbYFFgUWBRYPFDUGBRYFFg8UNQYFFgUWBRCiwKLAosSoFFgUWBRSmwKLAosCgFFgUWBRalwKLAosCiFFgUWBRYlAKLAosCi1Jg0WTqM17OTk6rT8x7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMToxNC0wNTowMHpPW8kAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NITC5zdmevPVD1AAAAAElFTkSuQmCC"},"65":{"admin":"Ecuador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2bfWhVZRzHTyt0f0iu5gs5S3S2dPiSrAnpTCnIVWhtLigs51wrt1kWFbGQXJl/pA3Wi2Nh5ksLRAqckbXUP0Zjki+9KZUta1AxlZSCojGQgvu5f3wvj+dy1zZ3zzm/f74cnvuc3zn3eT73+/ye3znX+7fr9Ckv29R0cNWzITA1sEyHWy92/+Z56QRWyjdkao5lampgmRpYpgaWaVSScQPLthrmWKYGlqk5n4EV8GkOBY7mWKYGlqmBZWpg2UCYGlhDoOeWf/3RtCtdvdD9Td/VfclT6YSetiscNrDSYKCBoLOsdV3eqsbW2rEPF635+dGtVYs4bjz+xsi13UfnHDjQUg80Cfcsxz9NPr7xkRH059yGKY0Ti1vQzhWfFJbmdfV05syYFE3IIvFVz1QeOTSyrTWr4Zf8b6uXLPts+j9FFXk5k2fO33jjuzfsQIt75vw1bS7toKZwAGJcY+3AxFkahwi0o1yRc7kTAyvwVauvej9cPerHLb1rn87+gglGdeJBQVWBUA/zg8kFy8WrNGP+tTm7gNt36TSwgqVMJG6h6mIHHEDw1oyq+6f1vF+/40x5JWA1ZG7LefsZP6SIQLR9hxuaxiwiPqrXNccKe92Za8X05KaO2dfsBQvA0iWSxVHb8SRaOCtq6JhjxZVsCb3E9AvQfAouuqhxjDPhSb5xJJpe18AKLVjxXVsslXYh0BYFSxdHkErFC+lJlmZghfbBM5kW6bMuWyyCqOZewKRJN+pCSWSNgP7Qsacma5MfiOFO4b2wVln8FiB8CHTey9hdMuWKinlv9s18hWMUFGjH4VaOaFxe2Yz3cAxeWkTVCJqwg2Mq92aOFZh6Ons6Kk84CtAw/dWvf/z74gcULPoAUP6xzbO3t95Vs3fWkVHuccXu5lXvjCA+ACmmKHhxRSJzJ5wVL8AaWMFa/pi2eDEzBgrOoV7lOhb+BDRghL74QtOYkjL6KGT05Cp44aj9G6YvGI1quUGLF1TtL1HZD9Ezj5AXSON4xZYtMh6mWX1lXHZVe1Xd40cLuson6WJH1YoWziKOLouqRFCwAFHzLSKE26siARaKV7lgMf2zSqv3vdoDHMDU9NiJrF//QHEmt0WVsx6cVzdu80EwJTJXUbDcfMvACrAqWLSoYxXet+DISxsUqdMXz353foWriperRHDB4oqhAsst00QTLNJn3cHNXVxTVtcLUjhNKmAlR03BGrulbGHTsZKzzx164notuiaUKkIOlnYKn8a+pG7+aWHKSy4UHnyy7YPpBX/fuRW8eHTDMYk5uCRXenIW0YgcByt2J1ohC98Iuy0e+x3VZ+/YvKw002330+Hqn3o7ez39PeFYTP/n9xbffXu5pu1oHD5H9VM9JsKXHTcfLng5ASwph3InqX/3/o5b+vTxMj5dmrvrZLgVjBQsppx9HGAxHFS24vWtmA9pS3J1wQI7XQrZKERhzCMElj5CUccCBbBQUMCCqhVKix9Y1Kj8HCs6YGVOXTJ+e7MX1i+mLUynPsVTsE6dyN1500KMXUFRpLTm7gcZrwESjW1BEMFyR88cq9+ONb+2aML6q86fm5gzNZcFUV3quo21q7eVogDhtqMARISgg5U6XqOPltXvbDewEsACIIavbXzuyltv0wVRYQIOdnycxbFCxlkgRTQi44K6K6QMYTnWyXCYdgJYUosnAim8+paLjp+CmnoV0YjMIx0Fy5L3UCnTyfsF+iIy7bgI9ac/9+csnTIBUKhp0cdVPsXnOOu19vx199wCUprV6RXB0cBKC08aeDpJBPcFPZYqrbyDF94DNLyVoC/60cKn9AQpAOVaCZUzeVsreV4yvOl25MAarP6a8ei7D2RIVK1I51vWr/l+TBEAcQxSHLvKUgtY8as4jzhoDwoWA/8BeOlzQ0Pxa9bIOJO7IFJ/QhUm3q/yg0k/1dcJ3fdCcUoWx/+HUXrC12+whm6Ch+uXx6TqMQsT6bz7p3v90wTHKP/PUbxoYeEDU42Jz7lX57i/4+z2T2fgvHSz0MFVlid3k6/TjJNpz/huLuY3QEMWxY5P33ZngSMa5xLNBcjV4LpRRHeFqUyVVqp0mqlaue9BUJ0CLE3McSkFd+DOFKxcKtLlhuSLow4u7fgQSg1dwQI1PqWO5U6Pe5WojXPkwNKiQPIChy6OWlbQT7W/n/a32h6OxdHj95TKAKWDXs67jS+O8qAa38LDwjQmA4ngd6635/n8p4ofGlylzDgUkS+/ApOChbp98LNwfOuBq8czMh5KDK4OXeRwK+MW3NHjzj39Gqamg6UGlqmBZWpgmRpYNhCmBpapgWVqYNlAmBpYpgaWqYEVFXUfm5gaWKYGlqmBZQNhamCZBkP/A0k6122K1m2UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToyMS0wNTowMKBKMMIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VDVS5zdmfL2mD/AAAAAElFTkSuQmCC"},"72":{"admin":"Fiji","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHR0lEQVR42u2cf2iVVRjHrxCtIY1lEVFQMdxsFjErbNAPUxKDNJjZMKKZ4rZYYv5CQ+0HQtgmRpYNzJylORRNW+FoTilps1Bcy9I5x5Zm7cpKDLU/WlrB/dw/nnE6t/O+7z3vfTfPP18u7z3vOec953Of5znPOe+Ndb/8eFnFB+dXfz7yq+GXFv8159Idqq67e9fV33xXeHlyx4qK2JJRL1aeCq7Fm6eX1cyl/jO51VnvPN82MWfDyMlJPXbdfQX5fOZbShb1TX90VTy9feDpzi7bc3tr/smbSuvnFX7/2g1Lx847VnrrxglHuHKu5uDN335dOWJ/d/zJJ+obd57qKynZvfdk3KlOYwwiyiDqIPulLP7nb7nVr6y/tyknOGRM6pmu85V/LB0AFkhpwOKuIO3Sc56ic1bjWy2/93YtPLFy/dE3R3eNn6gbB/pZvmJfzel9DiwjsOTg8hsNBzKtxRJISbCYWn9g6WDy+rwOLA9g4VyCD7pXyLyC5dVipYZJujmT52KUHnt3y5dHIgHW1LxPCjoPRhosG79sE8i8ukITi5X6R2ICE62o/c9eUNyy+D3ActbIM1hhQpauGCucH4MDK21gmVsCf5P3P64wJVhhWlYHlkWwbMQuDTfurW5f5BWsLeW76w7NCH/B4cDyDBZT5U9PLO/J6/ybaSPTQzbobPP2/MZ4coLv6c2OX9RlyLR5LE3wPqCtRCsoV1K3Qm/JWqnPAui6J+Wu6IfMEQIrOfEZ1SRYynpwAFi+asZKBe+hTDdEbQqjiXuMxGD7q/dnT+s62jNm/l0NXEmzpqwZR/YfYCWuHLpw/cGiZiu9Mu4n4zOzpH5K24/pnQCZvAAR9YpaUl5Jnf5Q6wwJrAExTTiqxlL+avBaj792E3d9MSyv+IHRgJXeSZJwmKsJWDKdm7qeqIIVHJTM1u8RLBvT8Nydbxdt3/TSLSVvzD0cjtKiPTca00U2RtbCRL3W469dHYK6ms1hDQWsijGVy1fOoq0jJddmj/kBlTu5slfde56Zf1uvVJy42n+uU4agglZo0Z7diplHQh7K+IpsdIj4bNH8LoOS9mIsFSz6A0wSHUZDAkf5ZAwqAIq3frR3eL8ETq1BgmXDOVpfFZqsy8hI6fYK+Taz61bbq0KmWS6hJCKotF5qmQvFrTnZY1XlW8qDFCDSIq7QRoAfk/kbXS5Hd13msXSq5pzUBKZ5glSriemnZtmKzG/RW/MsnXxq23ksprljx7ambb+CF9CoiKgYSfukU9WGWXeFQTLvSWukTDOTqh6aw/aQN/eXeecuk3blToBsV55WiE7mnWmWdkgHh4qIvEt+lpGZWjMtZnhLJ10wqXuF/jahbfQnCmAl3Zwm8pM/FRngoyo6aqYwEmDJyWP6g0yeaiH8ncdS6wkHMttgsfjHcdMfqZuXNhzfMEmGExwol2BxRdbQkvXZQ0vy5RU+oy/s2rR6T0EGzmPZtgReTzeYnMey1397YBHlAFb5uLX9s/ukPvzPqouP9D+9bOGapzaiH685tHXnfh1YxJqU4V6U2n5uO978fi4jaR0spipMt+IPrCAnSIM/14N1s7PW9tsGC3RAASs1qnXGpxMuS8i4roLV82HpgqqZLFAoz70o9wKcBMti8B7czVk58y4yLsFfpvAKmRr4H56ze/wBK9u9TC3TjEUBHfrJZ6k4MsAakDIVFouSqsUKFawwYfJ3gpTWg7xMEdyS2d6EZpp5xmQSJ4GIjIpkD7WuUJw8Q4m0qDM52gIsi1s6dLq9o6rp9WEMH9sXXAEmsjgEzkQbwRXnwkP+dE1t7bpxtKuqjLG4K7194OmYBjn0KCfMtj475VzVVHtg4QpleM6YoIyA/JaZGrAqTKz+ZBmUnyW2UF6nRYsxFoM17XRdzYFCPqNkmVFiCxtK/fxuZOuyP3wrS9roiTwRwIupUmXrNsBi8a/bJZSqllHLpy4jt3QsWiyTAxU6T+x1j0m3dRBkr8rkXpOawzxSomrZVYtG1E4iAEgNh6o6+HT10AotWrRY6TpFNNiP0mb2KXBMuH6Jl6oyQaqzWBIgXT3WXaE7nR0FoHH9rObYMZQ7nlJ1MRZXku8cJFTullKnVFp0YA1xsAgSsCLgJdNAcjHBajH1qhAlYJcpDNINbKsTWVqMsdzURkexIuTMdK+vyTyWDixgkicgSDoApe2cuwMrcooVwa6oYAEceKlgkXVTkZKK3Qrn7UgHVkTtljzrJp2gTJByZE9aLAmWPLkFUjLbrmp63aIDK3LKBAOBzJjLs7ipLZZECicIrCbv/Diwrgi3KPEy2dKRu4q4VJ2Vcq5wSNkhf3k17A2rOV2MpYJlYqUcWE6TNoyUBHuXxFgoV/g2Cn8Q58AalJYPa8S2DLt+0j5F4r8b3FQN3q2nKG+sObAGJVjmBwUyDJb75yen6V2COIvl1LlCpw4sp0PmH9sHDVgunnMWy6lTB5ZTB5bTKxcsFwk5dRbLaeT0X5mhki0/uFJaAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzoxMy0wNTowML2v9jUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZKSS5zdmd4LVEpAAAAAElFTkSuQmCC"},"119":{"admin":"Kiribati","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHyklEQVR42u1afWhXVRg+Q6Hwj5ZLDaZLLbFG4bakzEiDDAxKCyyikVZOCArpS1D6GjGhsb5bxDQVqsFc6DbEFJOVyBzaxj7UlmvT2gSXzpnlHNjCBfe5fzw/zs713K9z7+/jn5cf93fv+5z3fZ/znve854i2w5Pnzc0LTL52Q/mc3bYMVnNGsodjjy5Symz98UQ1zuSdctbIOydO/7lgKApimXeohmbbHZxNVTKEEbbcUlA0c0vLnvsX5Q1Ctr51+5XZbxglmX8s1qChTaT2woGgtre/OnXakrbKJyfl1kC2fvtI7vS/7JBb0g62hnPt9y0N0AzZtaB8wpTnIDvf/azwxnqWeAdf6c/7GFHNZe4X6VCX2KSxKMWB//W9qidyinpa6/uyf8BvSLxjE5G1WU/w/pm79pXkTDr/RdO0nFWX17dsmbICv/Evk2mcLGWmEo102RVpVPCSoxFmO+SUe0AskAaScxKTCQTiXJhAIBnX4OIbBzqKdN5hgQqcyUCs/hPbduSOgjqcn5CZ8NvOZwvvLp51HySWyIRlLuytQ4y3ICKdd0YglpyT/rvaeX7qCC9weILfkHiTyWdXUTq1WhpIkWQdFD9j0PgWVEPGUhGI6yfeEEDGojA371VJg8i0B1X6eaEcp4pKpSawb2IlNHRsYpl3kIwY5zI2c5aQKd6TOEMkO3GTfik0Uz1YX6ENgYWPS3LuoWdOD6PIWHEwWGMMTBq7v2U1DlDC2/SyJCottCTsDj7RDnUGvs1kZZGUpAnqOMKSfNgCiZ4Wt0nxBLtCtCHwhPeG2C0GlsmSfHFM0xqLswt3sJhA8nENP2HaseTzxxSpn5KSWMadzhkFlEKmAV2YRkNvNk3PnixTip9w+5SpKWfBVM1Mqkkr0i1L8R0EhF8mipyZ5J673Ivnf1Gf4fc1iBWHazCZ4j2oOw4gDZODs5FMJpVULZrQbJf/tCHILIWpKK2jYi7MVXTBIsjkYykvms60w1IbSt6KcbMjxYlltwOoWYAwq7KUikzOEl+pshff90o4qE6uG6SZjJXQ5LRCyPs+VaZBlnJLKZleXPKzTl4W02G3KNLh2MS+EEy5irMUF+P+icU6QSy+MBijfn3Ipx3C6CFrRMe6TCzOW3K57V/yUsg5jPv1oS9YUd1WZWId7Zzz4YKzynuP+kPUuIALFCCyDM+5WHqObSz+JL8bi9Hlfe0P5PdyxvK/CLLkLGWTzEKExBhseoUWbPN+xm/GEv/2Dcw4u2xkSUfD8d4LWds3NzzWv3/d6tKsrmfuqVpaOg7hZHpJz+2OkaUB2qAZKEC8cuep433N18DVMZ5wYRLjXnq255ett179aGTn0SNjdaMFA19D4gn+hUTgfS2FlobRGb2bnm7UwVXaq31H3tnP8HAAfnYZXyCKMTE2a2y1LBH+f6p/urmp48wrZc0f1/esX16xaj4PiAOJf/EmvoIGlX63uDwb2LCgcOXAgyKcb7QIpIF1/t6/80Ya9O016Wc5vpBucYXbAfEMYIa6DqQnR4yDqx1OtySTpZyBIrDXuJ9tArm0V1Q+X1fU+kdd9cHG7l3H9p6qHczn+eRfQlt33enbhoZrHm+c3/Xgl6UN5W3FkGZwgWISF5qBAg9DwgNmcL+6tLuu409IM/Z+f+jwzJM58LAQWYvXvj8R8qaNyy5+unfR4NqT1TVvL9/y20Hx44a27/rmDjQPHRiu0ofBV9AAbdB83csP76g4x4h4wrgbXt904cDCsHFle/3jQoMKF7+d7dUPOUYo4+aWrJhdOSxb6myvPq6znxMQVYNgdxQtX/Pitv1r3qk4u6eNZx4knuDfeRNeOLe1UBVIfRktLrSFh6uaYPq48Eys/TwO17QH5M2hsjSJa95e56zploLe0M3jiiMlXafPPIo1uOylbwoPrVy6eF1/bbZzUtWR0ABt0AwUIPrHVRnvB9ePQ8O21y0uFiy3uPoecLZXuSvkchsf31G78sTm7Tz/WCJJFj9V9tCu61EwQoPbYhBfQQO0AVeV7ZDGMUL/uCg8GVdlL/5le1HABmWvjIsnjMtbgbD97Da+LtoNKBh5BmAXgCf6Ba9bybhAZNxg9zjxxOVMEJWf3eKKOJAjtXHjMwlN4grndOctzeIrnTTL6Z2Xs6hwkd7dZiPG1SkbwlhGgYvCQC7Y8QTx9e9nVdmQsMUJoyD1thOR9zLxKYRlXHRxkmWjEwGun423n014BjdsXG/ogSG6bb7pg7ltvkXb9JOzrKqNGTd7IWVcZJqo/Cz02/aqTo98TIGv3O6e8Ca+8nYcxMdQZnCxNKQDLqKvH1/h7YAThRvPDG9lrzfcaA+STeLGx1638RVQ9/sHA59fbAyvNwPN2JQCkXHDuwQCXKBghpnEjdZeGddkfO2zQt4dINGhG+FtKPgKGuSzd065nGDxZrC4vIvRwfXW6WFcuXgwietsrxxf/7iq+Ar9c34UZZj3mAGcCfCv/hm48+aAcdFxkXExM8LDle3FciDby12cMHCjsleFq2Ov8DYgcDOBoUY2wIwb1GUV5684o5uxV+XnoPaPbu31iBvslYnwZDxHFZW93lohYY8w4Um0AdbR5rZzFl7WDCoPxXPaBDyGYN0XnuPMuN7ktPFTG4URC/3oa40hKtcHm7r9OzHayWP+fmnYE/J/4vCcl8AXaHMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjExOjQzLTA1OjAwNMxImgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS0lSLnN2Z4he5NQAAAAASUVORK5CYII="},"182":{"admin":"French Polynesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG+ElEQVR42u2df2hVZRjHz1/lTLNFZRZllE2aSK2gFgUVBRsag4StFCRr+yNHba0/RFxkGDUInTMVjbUsRZGtoTl1m2iWsDmz6VjLsInrFxVJymCjP4yd2vm8lz137868270377nn+efL4Zz3vOfd+374Ps95zrlnzqmvMzOz7lVVTaw6OgWqCpaqgqWqYOlEqCpYqgqWqoKlqqpgqSpYqgqWqqqCpapgqSpYqa9dRTNmzanVZUv9+XGCNWW22kdTf/zJgMOemc7O6Tl3b51cn/G3d7j8+CoHmjradlfG4J1v2FOZ+vr/jFnOz0THFv9f55z7qbCspFJVNbHquOfd9e5mWwcbLn375xd+R2M/a/x+JnqVxPaWqHOTNza/sxLbWzLmxIlnKsOgTC76R+X3z/V8Gj+CYVAFy+jQMxeGuh90d/V0bSly236ubbkVjL5qKrr5parGo9lLctcdqni0OW92y9/VQxsvctS0X9Jbuvs2BU7Bilbg6Nj1cNbLrltbMG35wNMHfl/+SeuBhfuKppICH8mbeXHePaffaezf82IUiOIs123MfCQDKHVWnXCGNjzGKGC5dc035qDfna9v+nDq4flz38ut4h6HbeNSEkeDlKdABlidx7aXTjH9K1ih0L6OmpU3RTAa9hjjPUP7t+Uvdd3tq2/PuvzkNxXV90uwbMf6Z1nz5uebJFichUb2D/dmEFSw0jyLigpennruEgFiGLhLn51es+8pCRZFP3KsM03H20/mRBzLC38o/mT37yEbtgwsHGCx5AQptvESqR4o+JAES1aTZb518rqa1TW9tAfKiIeNhFTjglwXpwxNBuaEyaVGwhzodM9Yt7hsdt+iTScKG92Wz6c9tgC8/MBCTV1767JXl84zPgc03lXojZ6Nq9Ez7uVth8G90t2xSKKtFBu/oYhgIMC3vPYk6Rz1A6t9YUXbilnGgQDXU1mesPMwmY0pWMEvJUjP8AACpoPHbqiec8KAJeDAsciorgCWABeMKFIAlvFCec8YmoDopDlSLKQoK7D8NliR5H04DY8lFAKW8SRyKe8qhEjAoh4WVXpgVKK9ghU0lS7luYWBwFtOsCANN97jLbbMwABLPrEfw7EErGBEKARZ6mGRAgf3j6PvRhWsgCbs3KONvvkHC+7yTCg0II5UtvbOXfX22ikERBAEF84yYAlogJJQSPtIKLTGICr16ZrIpy9Y3pJHhSpvIdkzBlgg4qnpQVTqSecpkHIu6T/+Z8Kot41jRYPleZUHrhmPrJypYwXycbJUUc0CDhwIRECH4qdUud+vDcDJh9ZRoVBmV0IBS0KsYAUhbTfhRoah4W2ZvONYgMU7CzsLS54t6waUtR/kFxe8BTpss58QabeUYEU7FmVYUTiVY0vTO0QnbevsY+Q0o3Ms6ViAhQKTvQ1G+JB99Epg2eMZ3q+OFbRQKJLxKPXyG5ljsS1x8VPA8tvD9hjJu99I0voZYtqWG+SCybdAW3M7NvzyF+FMhsJzt/x6R/8AiKysuK/+gS2ozJ9QgiBHZbjkKGDhhZzLUTkGqZq8BwypTeUNJT3HwYjt7OIX8uvfvHbF410fvQIcVKRAAQjYDy7otv0b6hoeooc9O77s7TtlgwWOeB5eRYH0ieKCqprBmT8sWLzjt6LsVa2Hr2c8u3e25p29TJ8KVsCUZWM5bQUXWQIFCKpWUtdsrKvqPJpXXp59cH7pNe8OHKkEI/mysnz4I2Glvd8YgEzBCpgS2lhCsMAn2C+9h4AofxkHIoTL11vW17Ufwu1yzxT/uPd944XixWV5LkEQcLsyzr52YREq+wFWfVYY4IAIWAABXih7WGxZYZc/l8BvQEGqxIvEn7O4H6Q3eS15RXrgugpW4NX2DBYb35JtgEmCiM9J75HBEXCBDJRxRD+f0zdI0zDfmv7fmzAf90tl4aWrsZ8En22ZegOTbIPSRvqTvIpsA5oKVlqFRZJleV8m/Ql02E8b/AzvkdDIO0Tac670J86iDUqf4XnzPXS/0mGZbX9C2Q9wYITTyDbS52wPkz4HXuH8IWvowCIHwnVQcia2QQGw2JZH2UbtNlLZj5/p7wpDrYQqO6j5JeYclYEvPPmTgjXhECnDn0zAZbHALyymd8FTwUpAmo8z2Y9fUJng01LnLWFgJeq7TRP9nlb8I0leKh1Lz8n+61Lo+1j67TnVpHzRz+/blYn6Bmb8X7mMpR+/Pv3axDKGyX2NM/bZS4V5nugYYmlPG8f+eVOyvwo8vtoPgxN73WR/PzjV5mei6xt7z+P37yTqD75a/aT+h7iv7vxMDqz4599JtSXRfxEQrPnxG4+jE6eq//JEVcFSVbB0IlQVLFUFS1XBUlVVsFQVLFUFS1VVwVJVsFRDqf8CyT42OT5oEvIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjM1OjAwLTA1OjAw0OlFRgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFlGLnN2Z3sxHzkAAAAASUVORK5CYII="},"239":{"admin":"Samoa","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADMElEQVR42u2bTUhUURiGXRRBtshyURHRLEYD+6NSIlCkbSBCpFCbICJCo6LCRTG6mMRgVikWRCgUCmEQgmUrBaeECAukIFILC8GijFqIBBH4tjhy504zc+fM3Jl5Ns/ijF6Y+z18P+ecKSpaXVHaejHNvFsRbWuw8uTMc/m7bPq1Y93l0aH3pVV7v71uXj8dvDQxUVJSVgbdWOS3QO46X7/QXVd+9EjdzTnbuiCWn8SykI02hA5d7ZjZEqn9EbnfPnbnwFiw7U/32dFqrejTzGcpxLIvluXSpvzUf+Vx42T74sxS9e+volaUwyiFlMIUdaw72Ly5b9AUq77x3GL/pD97LJNo5C6WUwI3Laxlr+NLLdsftp3aeq3n0YColWzJZK64ZSzE8nfzvhxIZy+VSHelMpp0H0bznutirY3unw+Pp3fK0zMlU2Sut+F5jYqmVvQpPVY+irUcDAVYrXdXZ9+9F8UrAq+ckVJh3Xfj2M7bU4NPR0bfVaobmw58WvW9VbNkmrMXYmVNLEdgFNrQra4zI8XPHrwKzQ4r/FLhX+fkQSxRfZie/GZhes2XWSu7X4jlt1KoMCvk5vbBiozl4fkSS7NkUyBcNXRYmSzNMyxi+VMsyaRtTxXEFT2QB71MQRV49rEKQixzP91ULTUJXFtyzyUVsQrirNBN0AuBjtNPxlX4YkiGWIiVLGtPnBzoefn5+nz5z0q1/zHmvkTESk0+xMpXsVRM1Z5rovS0U5XA5gJi2TzSsTBbeWrSOYTOk4yVmct6LntgWbgqg1j5XQrNQ2jEQixPE582Ns2DmuFwtHNqm7n+n4LopQTH/1/EykWxpIvO+DT3mfextKKjoawVR+5jWRErgz+L0D0FU6wYO/WZ/6FH3IyFWDnQY+nwR0VQ1IrrJUTLm6KUQm9iOV5i2piwBCpz2qMyp0JzJX7gbROxkhZLrwzG5+6PwdqmGsRKQiy9LHsc/rDx7Z5e73/jB6JLEmI5pxvn1APJUkmLletfIDPhR6yCEwsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELZp1/AdfAFB/2no59AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMjowMi0wNTowMOHe8JIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1dTTS5zdmdo3TPAAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/0/3.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/0/3.grid.json
new file mode 100644
index 0000000..187916d
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/0/3.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," "," "," "," "," "," !!!!!! "," !!!! "," ! ! !"," ! !!!!!!!!!"," ! ! !!!!!!!!!"," ! !!! ! ! !!!!!!!!"," ! ! ! !!!! ! !!!!!!!!"," !!!!!!!!!!!!!!!! !! !!!!!!!"," !!!!!!!!!!!!!!!!!!!!!! !!! !!!!!!"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!! !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["","13"],"data":{"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/1/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/1/0.grid.json
new file mode 100644
index 0000000..37da089
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/1/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," "," "," "," "," "," !!!!!!! "," ! !!!!!!!!! "," !!!!!!!!!!!! ! "," ####### !!!!!!!!!!!!!!!! "," ############ !!!!!!!!!!!!!!! "," ############# !!!!!!!!!!!!!!!!!! "," ############### ! !!!!!!!!!!!!!!!!!! "," ## ############### !! !! !!!!!!!!!!!!!!!!! "," # ################# !!!!!!!!! !!!!!!!!!!! ! ","#################### !!!!! !!!!! !!!!!!!!!!!!!! ! ! ","################## !!!!!!!!!!!!!!!!!!!!!!!!!!!! !!! ","################## !!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!! "," ################# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","################# !!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!! "," ####### ####### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "," ############# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","## ########## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","## ########### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! ","############## !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! ","############ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","########### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","############ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","########### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","########### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","# ######### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "," ####### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "," # ##### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","# ## #### ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! "," ####### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","######### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","######### !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","# !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","# ### !!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","######## !!!!!!!!!!!!!!!!!!!!!!!!!!!! ","######## !!!!!!!!!!!!!!!!!!!!!!!!!!! ! ","####### !!!!!!!!!!!!!!!!!!!!!!!!!!! "," !!!!!!!!!!!!!!!!!!!!!!!!!!! "," !!!!!!!!!!!!!!!!!!!!!!!!!! "," ### #### !!!!!!!!!!!!!!!!!!!!!!!!!! "," ## ###### !!!!!!!!!!!!!!!!!!!!!!! "," ###### # !!!!!!!!!!!!!!!!!!!!!!!!! ","### ####### !!!!!!!!!!!!!!!!!!!!!!!! ","############ !!!!!!!!!!!!!!!!!!!!!!!! ","############## ! !!!!!!!!!!!!!!!!!!!!!! "," ############# !!!!!!!!!!!!!!!!!!! !! $ "," ############### !!!!!!!!!!!!!!!!!!!! !! "," ###### ######## !!!!!!!!!!!!!!!!!!!!!!!! "," ### ######### !!!!!!!!!!!!!!!!!!!!!!! "," # ### ######## !!!!!!!!!!!!!!!!!!! ","## #### ##### !!!!!!!!!!!!!!!!!!! ","###### ######### !!!!!!!!!!!!!!!!! ","## #### ## ###### !!!!!!!!!!!!!!! ","####### ######### !!!!!!!!!!!!!!! "],"keys":["","89","40","165"],"data":{"40":{"admin":"Canada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC6klEQVR42u2dMU4rMRBAU3AIGmqOgESNBC0VN0A5AhWIkhtwCQoqOiQKRMEFqLmIKZ4i+ct/FxPbG2/2NaPIduzx+sUej2edVQirw9VhT/Lo/ug+hO+777sQwmf4VA5LnhJPrK9xXAmWYAmWUrAES7AES7AEa1fy4f3hfSMFS7AqyK+zr7MQjtfH643sRzfBmjFYN5c3l/9qSIpgCVbRXJUOGynkCpZg/VmuX9evw3qSK1iC9R/5+Pb4tjHJ4xY/Dj4OfhswcikZDzO1UbNgLRos2j19Pn3eYHF+cn6Spy3fwuriM+mCtWiwXq5frtv0gpoFa6FgtRiYKV0SgtW18Z6/8OVIatN4F6xwdXF1UU9/ahOsRYCFST7keSK3lv5Dxz7xnlGwugaLMuMlYycnc0lavq4Jn5rttMgSmeNczemXYDUEi18/xjLQkMLQMnjpbJQ6Noc87NtpHkNDL9KlNp5B8YSRQkl6lDO3CVYTsMZnmvHHHeNFW7EXajtJDePnjPka5jgsBKvhUsjvu9weKgeLxS62pVo7LASrofFevqcDqVrDU15b/r5SsBqCVXdP14PM3zkKVkOwcg6M5yXjg23B2hlYGLn7ARa9yD9nFKzKYJHLzm6f5qr4CdC7nOcgWJXBqnsU05sccucK1kTuhnFf0RxlzlwlWBOdFRJYN99lEc3/Gh4oWBMdQmP2lrhMp5dou11goGBNGt3AGVzdKKsWEg1L3vYRrB2EzcQ7x/laUYI1g3isHvqVvhEkWLOPIH26fbrdxTDQIq0bQbq3ocnTe7/aRcELVhdglYfZ9PbejmB1AVYaGEhbDDzzCvMZTle8SnyLXCSLGrmU5FsEzFBbOtgt3jQUrC7AImogxoWU/DDo/HBhnAi0Qov5MQuCtaCL15iZ+rnASLAES7AE67crjXq4ukiwBEuwBCvP+yVYgtXk4pAp72UQrEWAhY9KsARLsARLG0uwFgtWn3/BIlj+l45gCZZglcsfHBPAdZqOceQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjUyLTA1OjAwchcLNwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FOLnN2Z/LyUl0AAAAASUVORK5CYII="},"89":{"admin":"Greenland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD7UlEQVR42u2dT0gUYRjGv0NFQXWyOnWsbhIUBHWJOpReMoJi6RAR1GGRLoEReaoMitK6LIGEG1Se0gKDpIiIDlGxolIkYYkkYikaqVjJBvvM4VsGlxVmvn/vc3lYVodhZ37zfu+/7x1V/FMcK05Qqcmq4iWgEiwqwaISLF4IKsGiEiwqwaJSCRaVYFEJVvD678nU3Ew3bznBWgYuv4++7vlwcrKlo7mr8P1K08Yb674OZB6cy3xpr1t/Oju0sPfWiXp8ho70nrp88d7Yu+bHt9tx1HzTwMjQLOETChZu/OzNt4v9nQDo84Fdn47N9V+vWdzd01dYu2pHbd/W1R3bjxTurvxWuwKfdcX3ZX8tHTW4Z/OzfWuA3Xi2pebOfaBGRAIH69e13sY3ueFDh983virDKFGNgNNQg20DysQlELBgM3BrI5g0G2NIS2cEZFg6/54fz/3MEB0vwZqafzjzdMPHLdvy9cd1K2JZS5DBY4NXR4A8AAv+E/wba/aparwAPRx/YuQoWEAKzrhzGFVUPAA/XuYOdm4iTA6BBaTgu/iFVBwvWi+HwMKznl6UZ1Lh4CN6JVjKbvoAN8N3pHRFXm3h6nDb6CTBsrD8IRUZElK6az+6P3v2Uh3BMnrKiZ2tDfkzASIV87okL4tGwcICgUA9bLBgt1AnkFl/VLRVtFseg4WnFjlrR9OeqalMf0uZjAHDSCssV7H0S4sTDYGFQo00W6XrdL679UUDwUpYo+SCTLBKvxrVBYKVmKLJREQkWEWESLAS7qmS6V3pYCEjLyf1oOi2m2yzkePCpw7W9IWuweeP9EucsMZuoWuKRkVUReX00SvEa1RqsqrSflKxDYttJAhi4GkttYPIUa1+NdD+U6XtWyDRQLCkRceK0ZDJHUfh9Z/ZAEtwQWOp6FhOLKxY4Tfa2SGm9qDMnAaRgmSwEMQQLLa8sb3RB4uFBVHmBARsC5NWb1AmyxrYkiptIhestbTODsWWN9ZJvQcLKsFuSbZV1sBCkjDsaS1p+FV+WT5lq40EpZ7wZk0hwx7FgIJbsZXdLqWQlkU8JNHyJ7PzzAmwNPOOrLTvHhUmDxIpJ8DyfcqUv5O9RICl44Xij/s5eix8tFIegKXjhcqamxkv1A+CnZYTKljxWVMI3e3aMNgnWFN/Iz6TCQunwUILLy4Hoi3kss1ABpiANRCnHQoErHh6ApBhGUIsmdSrSvRXp8Aylb3zQnBGKnSw4u+P0Eb7Y44N5rogusSsBFg4eEVQfIM58kATDriOUfyFKFQRYFW2anGvIq6Vj6ISLKqfYIneGk+lxaISLCrBolIJFpVgUQkWlUqwqASLGrj+BzI0XRBdYcwWAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1Njo1Ny0wNTowMOPmaCIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dSTC5zdmf5P1UbAAAAAElFTkSuQmCC"},"165":{"admin":"Norway","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADa0lEQVR42u2dP2hUMRzHA+rQDkIHLTg5WE97p54KQtEOXeqkg6ODq6f4B6UognQQnFQoTpbaQYodrJ0qLhWu2KEoCEJBlErtYqlSOZWCiH8q5YZ7Nc27JC95l6efDF8euSQvfz4kv/x5OVGp5Nt3FcLUz/t2DxUHf2wpby0/WZbc5MSL/W+PNT/eWzxXFmL7p5M3hNiRK5XUuhKmcOpI/7VHiz2V60tn5DS/Hb356tZiZSjXsbM75JoJX8W/AlZ9pAzAet8+XHgAHIDlGix6LMCqNxTmBkobAAuwPPVYhjYWQyFg6Rnv9FiAZQlWdcgDLMBKCawaUoAFWM57LNaxAAuwAAsbC8XGYuUdsFYrPRZgMRSiGO+ABViABVihgcUmNGB52Stk5R2wMN4BC7BQwOI8FmAxKwQshkIUsGqLDoAFWAyFgMUmNOoeLG9zqICGQlUZk5TdLm41Vjr5SZCa+D4+Kkaf1dF7IyMPL6/xrONjGl7S35vnx+a/pHMe6+fd6bbp2wY5jM25s3oLPz+SiuXMOh93N+Bcuf8ELN0eCwdYXu5uwDkD63Xr7PGFE9nSmfzc14/F+4fGrjzvamreM3X2qT5YbVcPT/YOTN15eWG2/822d50Lv7JYA43Var3F155oXX+w71KTvm7a2PGhZ840VvIU5Fgt5w/MXBzXu7WhpuuW8r2nJ0zz4CrPjQ3vI1bUP/os5HlTqLr2LM8ULHU6vvXvI4cmZQy5Farlkmfllg2Dxq+ZUQ9UAWABFgpYKGBREagHsNKfx7lNP4kllJUZcQZVtQ6R3Cc+pE6YeB/zdayVAkfXsVRl1/k1/lknHVd1YvesX147H6FaRY36JA8T72+afnTl3XQTWl5518mbKj929eC2TvTzY/reJO3OJjSOTWibTWiz81g4wOJ0Q9hgKc8Z6p9FNA1jGFfvBKmboXDVCdIkZTRVV+/y0S5W7SgMzlPLYVThdfw13tXgr3Ts6sGVmraLj/PyCfLDX56oP7HnQ3u+K3RlvPP5F2BxPxZgcXcDYAEW1xgBFnc3ABZgAVYQGhmMsLEAC+MdZSjEeAcseizA4qpIlP8rBCzAwsYCLGwsFLAAC7CwsQCLHguwAAuwAItNaMACLMBiKAQswEpjVsgCKWDRYwEWYAFW9v5WTr4gmr3CRuofeRNcTTFrboUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI4OjM1LTA1OjAwOC45cgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTk9SLnN2Zww/KG4AAAAASUVORK5CYII="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/1/1.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/1/1.grid.json
new file mode 100644
index 0000000..91d4291
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/1/1.grid.json
@@ -0,0 +1 @@
+{"grid":[" ! !!!!!!! !!!!!##############!!!!!!!!$!!!$!!!!!!!!!!!"," !!!!!!! ! !!!!!!#############!!!!!!!!$$!$$$$!!!!!!!!!!"," !!!! ! !!!!!!!!#########!!!!!!!!!!!!$$$$$$$!!!!!!!!!"," ! !!! !!!!!!!!!#########!!!!!!!!!!!$$$$$$$$!!!!!!!!!"," ! !!!!! !!!!!!!!#########!!!!!!!!!!!!$$$$$!!!!!!!!!!!","!!! ! ! !!! !!!!!!!!!########!!!!!!!!!!!!!!$$!!!!!!!!!!!!","!!!!! !!!!!!!! !!!!!!!!!#######!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!! ! ! !! !!!!!!!!!!!#####!!!!!!!!!!!!!!!!!!!!!!!!!%!!!!","!!!!!!! ! !!!! !!!!!!!!!!#####!!!!!!!!!!!!!!!!!!!!!!!!!%!!!!","!!!!!!!! !!!!!!!!!!!!!!!####!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!! !!! !!!!!!!!!!!!!##!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&","!!!!!!!!! !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&!!","!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&!"," !!!!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&!"," !!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&!"," !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'&!&&&"," !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'''!!&&"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'''!&&&"," !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'''!&&&"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'!!!&&&"," ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!&&&"," ! !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!("," !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(!(","))) !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(((",")))) ) !!! !!*! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(","))))) )) ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(",")))))) ))))))! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(",")))))) )))))!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!(",")))))) ))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+++++(",")))))))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,,++++",")))))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,,++++","))))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,,++++",")))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,!!!!!!!!!!!!!,+++++",")))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!,+++++",")))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+!!!",")))))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!--!.",")))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!----.","))))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!-----.",")))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!-----..",")))))))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!----...",")!!!!))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!--.....","!!!!!))!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!+++---......","!!!!!!))/!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!-001.....","!!!!!!)!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!--11111...","!!!!!!!!!!/!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!--0111122..","!!!!!333!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!-011111222.","!!4!33!333!!!5!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!--0111112222","444!!!!!!333!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!111111112222","44!!!!6!!!!!7788!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11111112222","49!!!!!!!:!!!8!!;;<=>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11111112222","?9!!!!!!!!!!!!!!!!!!(!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1@@111111222","?AAAA!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B!!!!@@@@12222222","CCADD!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!@@@@222222EE","!!DDD!!!!!!!!FG!!!!!H!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!@@I@22222EEE","!!!DD!!!!!!FFJJJ!!J!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!IKKKK222ELL","!!!!MN!!!!FFJ!JJJJJJJ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!OOKKPPPLL","!!!!!NNNNNFFFJJJJJJJJJ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!OOQKPPPLL","!!!!!!!!!FFFFJJJJJJJJJR!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!QQQPPPLL","!!!!!!!!!FFFFFFFJJJJJRRSSS(!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!QQPPPLL","!!!!!!!!!FFFFFFFJJJJJTRSSS((!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!FFFFFFFFJJJTTTRRSS(T!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!UFFFFFFTTJJTTTRTTTTTT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["40","","89","107","75","79","104","74","228","201","68","180","137","64","27","187","152","145","53","142","212","55","97","63","31","110","178","234","164","16","90","191","95","51","197","162","23","49","2","88","85","232","83","82","52","171","196","44","126","92","204","34","65"],"data":{"2":{"admin":"Aruba","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEi0lEQVR42u2dW0gVQRjHpxtFJF2oh4oICYrUx0MZBUY3KKKEUCNIRDJ9iKIyFCJEKLQiulBERUkZYZic7DyoFCWFJ1IxpSJfIrDsopHiBUIIgv49TCx7mD27e3bPzv/l/7Dumd355jff9823O6vYXVJ55nE91UxzTlSMP82mHayq4DDoAG7ix0XQ6Oydnfs0+6srYBXsrZ75rIKQMRQ6PPzXlz4cfbutbMb18va5BIJgOYAUfNXbKW9S+2bfnN4w0J3mLVhBzWC081jwUsPHbzXcqe5cFi5ujeSnVJ9s7aShCZYtvX+hse31pbGr+Z0FMwfOny+5sgSoMXH22/27bQHhbMLeW/RgdcuskUhm1ZqXwOv2p/CT7lUEiB7LVhCElxr6nhbKCAEvNwKibrjEt+APCFgIguMidyhvEGBB3Q6I9En+9PfCziWhRVvP7XuxUg6CMlgIiHWNTanvculF9IHMMlgIavBAyJ+6CsM5z1pGJh3afvgFYBr+kL4z47ccEOG3olsi06KlVx81TO1JO7Dk0kS0iF5B6xwLGKEuhZwJoMAbyV4KSMn6n/cKbdy1KWv01dE5xyZ/ra1Ze6+kKdyc17PciBoDXGDBwmBHd3Yf+ZyNupSMkREaWc3AMp4jo/blYPPH1t4nvzrq+/q9KlK4AbTfJkli7scUrJPptYu6FiI3Qv70s//UntO5SM+NuZQ6WDJSYwX7JxWXfSu8/OPGWXgvPA7SuawajBqbUihEeg7UsPozombEyJhpyRjJ+ZY/i6hUD1aFKIcCNSACyIz+afRN1rz1cwEisrQgYcRc0JU6lvzgGUk9MPovYf/rpRDgOPB+7q+zthJOdQbeCBjJoRCBL9m9lJ8BDWzlHR0DOsBIDoIIlBwG3bypsN9VOSACI6TqKJkiPWfOoRtwDr/oB4yAVDCCINXjUCgHxIHBxsVNK/wWBPUMT171yJXNFKjXe7USDBJqyQu6K5spUN/iY2bd3r6Xry445FTtNqwyQ0peOwuWCjltEuqxOPupDIX0bb6zG5N3fvAj2wOPxZlNiAMbCvnYWIsci8YNxlRMmi32evoDHaZZfH3kqpAIutI7QYB4h0kcCpmZMXknTFT9Ku/qsFrFGltk5ZcWVT77oX6+bpNNBDXzUB/s+ECJDyxNH+lYNY0bBnVqwFTakY/Yua5TdvBbO7HtGfszCMLMoFTdNPYEs3q+uFhaU1cZonql19rvppR3JNc9qJwvJtreb1iwGdpb27Yuc4d8hKqPqo++ypmCBqW6ocIPM4AaPFsJxEuqmcpZBVXdPsJqYVBlSa9eB3KqHaslSvVrJWYtlkj72OmvevvC2AG3C2j75ldFws/tV1acasd4z3LLKvUbHDfeT3z2dKodpyypAq7snv7VsdCon1XuPDVZRkcgbaRSnVWWG6jJVm6gag0WnTbVlVBoloKZrRdip2+x24l9vtlVYp9v9cxE3o/b9lH/rfoRq781Td75rqPOG0lUalTcYs8NEdz+ReWryVQqwaISLGqgsz2CRSVYXOUF/v8VUjkltAOL33X2g/4B5wJm5mz7tscAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjEzOjUyLTA1OjAwwUwJ9QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUJXLnN2Z44JxCkAAAAASUVORK5CYII="},"16":{"admin":"Antigua and Barbuda","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHWElEQVR42u2bXWxURRTH11K3H3bB2pZKrWClRSsI+2DEWE2x1iCRFIwW8SskECghSsRI+Gj0gaTWSMBgQ6AhENSSGsEEFB+MlNisSDDaBoimElFRUcsDMZJUJeKa9L8PZzPMdu7eO3NnZuflZHPv3bkzZ373nP+cuTfyyKLo7Nj5SHPk3kiVs876tymiDk8ff6bm7vhIfnFRgXOKs34sKAJRkZP1139Zd2Jnd8mO6robGq89lTfgHOSsVwtyQBGISoEFu7a76HDFQecmZ70iBXIoS2lgDRwrrZg2/dE90SMTYs5lrPucH1gLWgaWjU/UFoIf2DSwYJ3qYu3ceMND0R5Y5w1WUVGk0sCih3JBdVUP3jg17wqsyPVrGp/9vWgIVuT62rKbh8dNFm/fdEXFBYt32lbVhYnffOylrbEGkYdn9+RNi2IHYUXaR8u4i33eAxUsLWlgcU+TrGmf6gJMR4f3dpYfXfLXgncKV4hceXzfvp7StswgojVcb1+8TymqjEiNBRaxtqouRKChgg/3l73Piy5IZz81f/JeWT8sL8GhBbS2/Y72j2P5Nvmq/rdxPxa2pCkqRrCng5XxtImqS7yH0ExX2k8tn9gAyNj/QrBfrPy8pGJwZN1XrZUxVsLjX2gBrYmrMf1XnVRRiYQhDxGL4qWz6kIsQTIS0TdABLjAPrHr4d6CvfSaVe2LzxS3ABdYHKHX4F+0HZH1I3qI3uos80UUlS+waADUeYcRkwo9hCnnxQNMLaIRoGHTIo1DNLax6Q9nkS4zYw2Y0EOdSxi8GpUcsIjqQt7V0ylYlyF+QPGwkw3gMMEsOogikOH0LI7gLIsdT+Dj7rgevUIP9VdUvIqBFLBgdVZdmHgKDeIKm+xYOBDD2qesmFqcoNGItoOzNNqx8QyewR1pO+iVnulPpEYlHSz9a11UemNSaQzD1FIJz+LFHqftsMfRGlrGXWg7+K1z+kspqqzSX8Bg6a+6kHRYCBA5MP08UMQtRZZNr7A6pz/MIF2ohQ2WctWFeCCegtm0yAIRFFi8s17TH0anJl1epUbl2wYGlkrVNWvabZPyqxAboGmQgJBiIJMzV6T8AOQVLKwTedUv9BZnMQqMCKPDSPVXVNLBgl29p6imvE3Nfh9NZLCYSEQIil1L8oGO6K+H5nXVTnhFBlg8izvi7hQd9BC9pf2na1LZPsRMBZX+pIOlXnVh2njJjkYUeRGLF7do1T5zosQoVCoqWlAQAmt0ZkWulAiW+loXG8OyQ4H3X/86jAWOrk/D2vXzAFbIEUuDHUaRGOZfkmdnUdNCD1V6JrtdP03BQvRSo7p4MYzWu9UIdp5FT8J6TyulqHzXqMaYcdlgpb1cIVl1ZV5hIW7JAMsrguySIvNKNqi3IeD5/r8rJ8Wr6IwYGbHYt3ZY1cU6iHeERQdvGWCSsH/HrrB49arMVrzKJd4mbYH9jZ5jFBgRRoeR8hQYDy96nFujMh4sxr61ccpNDb1lHQs7X4tHDy1e2bWdZ0uSz53te4MembF07Qfb7pq3ZMvzb54O1j7YuOOLry/KaNm/xahvnb/0owODrE94Fh6Gt9VEqTDAIgP79JrKSLyn7fWmGetmRwpenHs86qwMCw+rTH8hRyyKV9Odj3V0JRwEwVp4NYSZDRksgteBrTUz5xxBkHdA+LfwJLwabF3Ka3U+ZLDQ0S23z/rvyXqoBwdHdhbegydlIGVYxKK/neryr6gCmxGDwWICLGSmiaor3FgLj8F7tCgdbtzSAiyKl1Nd/hWVjL0/g8GiTpGnuuxQcropqsDAUjMYfVSXbjimKSrhT47VBAVNI5YdqkuNolJZ8PQKa0TPQEqtU11qalSWayz1qsuU5KizojISLDW1Lnmg+G85gBqV/mCF9cRQ1fXUzOUv7N6E9xHstlBU2Fe1HCwd4tY3797X19L6x5yhprPfJecnn0nu9GMv3/Lz+vP/Znc22HtRi9FhpOGK9NwAi+D1w+Vlj68pD3bidbAYEUbnBylebhHJORp9CR2WHW7Zltj1sk1gYURq3vOUt29oMlijgz+dV7fhnv4/7++rS1xnOlIYBUZkYvobAyz18tzXHSWoLvX2n+rvO89tNFdRGVzH4p2lNWjoEhPB8q+oHFgS36Y3UXWht6aUPXMJLGNV11UUleERy4BNaP8WegXaRU9F9e3K5s2tE21Kfx4ilsEhWmPVZZ+isj0Vch4JfVTXhZHugbdP2urznABLN9Vlh6LS7vOvkFPq6BRC06jfAmJrVPSTBxOhcRFLC9XlVVGp2cvTOhVKefKUfLoErWOwotI+jeZSxGJU16Xez6InFspACi2Hu+vnPv8KzcpQXWhNYo0qoDZlYxexbz3i9b/nFqy6sL40KLDQWi4/ri5iBay6cqdG5cBSpLp0UFRag2XfHrts1cVTVLZ6UmmBNDAnavOsi6su2YrKXEBzo/IuQXU5RWXh51/hqq40ReUwcuLd/w5jmqJy/nGp0P/df3l6w/6OV2H1T+469O1/EWniqHRifXwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjIyOjUyLTA1OjAwt2wEygAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQVRHLnN2Zztmsi0AAAAldEVYdHN2Zzp0aXRsZQBGbGFnIG9mIEFudGlndWEgYW5kIEJhcmJ1ZGH2xoQOAAAAAElFTkSuQmCC"},"23":{"admin":"Burkina Faso","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACsklEQVR42u2cT0gUYRiHB+lSIBFEiZQSdmi3FS8lka4IbpCdIvCWN08pdBE2IuhUXvSW+QcSzYNWiOihoJAoqIzwIl3qEOFBxYMmURZJdPjtYZbZz751vwVn57k8LDP7veO++/D+vh0GvY2N06dqayF0S48WQMSCiAURi0ZAxIKIBRELQsSCiAURC0LEgogFEQtCxCoKv12u+5LooQ8RFYuvH7EgYkGIWBCxIGJBiFgQsULPzYWWg7GvIt1ALGf8MdldWXNIpBuI5Yy/28eTx3pFbs8ilrMQ3G6YrzvaKRKIiOWAP191Pq9Z395cPHzkgEggIpazEPSL9eve0Pzxz3QGsXYbgu3Jqfjwn8W5hxUf/GIRiIiV45kI6WJDRV6WUoFAtK8WtS2/F7VtuIJMc2hnZjbsBrF01qaOrhi1CedFM+C2xvouVA8apXFEXUVXjFwUfm+L3U+8CS/9HybfVfrFZzWfbOibYaoc9t4WQi+91fGpIRll3ly+c7X+8dOu17GTPYWIpQqqRlc9zxttbp0OActGHrReyvF6d3UCrOgb70qle1Mv9iWW8lVKq1TBVN/4WUqWpkaEhRbS2LPt1nT63PV8xdIqV39DiZAW+AUduDvXGH9i2j+ZxNIqxEIsYxR+vPF+pupsUJ2V8oVrlbfF4Fmt+k8UIlY0Z1VWCPrm07v9b5dPlF1cnbpyvl/UkaBeOks/S10s+2AyhKCOZOaQr5qOmN6PUkysHCGosOvon/17Zs1GR71TqwhExMqiIkx3oeqfPZpoasq3glapAoGIWBnGX06kmscKnzSqoGp0FbEgYkHEgohFIyBiQcSCiEUjIGJBxIKIBSFiQcSCiOX6OSe4d545Y2JBohBCxIKIBUthD0f7IBMLIhZELBoBiyYWtz2hY7Gc/rcWCBELFpH/AJxrG58LvHo3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyNjowMC0wNTowMGH4u/0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JGQS5zdmfL0P6AAAAAAElFTkSuQmCC"},"27":{"admin":"The Bahamas","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAB4UlEQVR42u2dO0jDUBRAryhuDgVRKnEQu4haQQWHDEpBqNCt+AEdxVU6uLqo4OxnMDp0EQqliIgIQkUF8d9BXRxEKQhCt6IOTg5ZIsWCmheb5CxnKSk0OfTk8/KeiHQf6mmRuaChiaST5wUI7WBAQtIsEjipf61qGtmYLIoYl7tv7Bpoh1hWSutM27ZIIrrQwQ6C9on1RTK9c3BJZDGffGFnQfvEIpRQoViEEioXi1BC5WIRSqhQLEIJlYtFKKFysQglYjlEQolY6iUjlIhFKKHLxCKUiEUoofvFKgllfyQVXj6e0I6mjYzJsceD+bVcKb/7tPxW5b/td9v6mZUtloU9q+GputvN8ZXR9oePwtNzJO5PFvfuUwMxZ7b6C10jlpXDp7G7hvWzrp1477WfJatkulIsk8GrRq02NxtKVLcM5aMXWX2fw4lYCkPp/N8+9KxYhBKxCCVieYWEErEcCuXNe7amb4vDj1i2xRGxEIsUIhYn75DbDRCxeJKIWP8aO86cEItbBojFlR30plglA/28NxDPewMJzd8ilSkTQ5MZmszLFJDXv6BvXlhlUl3EInaQSUGgF8Xiyg4SO8hUkZDJbSFi/VAmc+0dYgdZQACy5AlkkSYIWVYOOk2e2UEF/ARzD1bDiEJXIAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjc6MDgtMDU6MDC91Z6kAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CSFMuc3Zn6/pbEgAAAABJRU5ErkJggg=="},"31":{"admin":"Belize","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAANSklEQVR42u2dYYwdVRXHn4YoGrTdDwUtaUrsulSkmsIHo19qVMSEuGCJoARjQcE2NNJqUPniF6JEJaGsCdRFwLSWKDHF0tBtjIkJImQp3cq2RQi1iUFJd7UEiiENgWjyfvPh15zeYd6b997Oe2++nEzu3LlzZ85//ufcc8+905iZGRkZG4vymfnF60YfjjJVv3g7rbaQ359W28xvp3hr3Wunyu+neDuNfgdWZ9spD6zOtlO191NpYNXtDAWwilB6LWvZqmxsnV+2ZM0dtaxlZ2WjsX7NObePR/muA5995ac/RMby1FWpdvKP25OpHrbXt/bql3+izr6f8vrq2NOVUe1gy5SSoqzfVaWB1XslcccPn3fNy1sPXbLnu9f9ZtmGFXfu3rv6543fjc+8svvsJ9Y+/zhy//hzm16aQlKy/Y9Tl8y+Sk2uogVaqwHXGDa+QfFAAYi8sPqf/z1+7M35t65983/l5bGL/jX79xdombsMKdQGm6Vo/1N33nTBtjdgl3+/8+WVJz5RHCi7tsx+aOdf797+5N6JI3PHX5ue29kq1Lgjd4fVhgJk1Wea9iQ8gcGKYKLkwV/sv/HeL95x5Z6Hbt/43Dfmpv72D9cBRtS56Ikt569edNWiHY+ufXcKXoZgEZAB9350ISoErN48/NlHrzhr4kwMUD4zGTRr9v5ycs2rQGffOVPLd35uzy2/Hb/7e8hd4/fM/WTbPY9Nvr7pC6tWbt6+5FyOKac+EmBRB7DmMxkmmN4OIIcNxmOYnyKLoGZcb8pffPL5kwcfBxAGDTU5i7cEQOEhwPf0n188tO99c8/Of+XYa24HIN50xpaPrr2UmpzljjAi/YkgwyfjKWpgVUJiUA5uOrhh5iGrCvVjvMxJwAgQcBXK5uyB0acXTX3erAbUOBt5iDoGzWU7th699PyNB3Yuvfo4rMa9aJmefGd698abJyO8gH6+iewbbutfSOEIp8Z0MA2sg4LhJCsVKFjZNqCAhjoZSzVLOAZMSGAE+GgHPjM70gfzWcpY80Q8Xc1YC8BShhR8gFLxnChBnWYLO9fAAqBYtdShPqChDser/jNx25nfv2Ll/UfO+xV3NOwinyFhRwPLLBtBxtNF9qoZq4u+lL0lwIEBQsJMHMMcQI0Sj/4ABAaOOkigY2kYYV4p4SzmNcIr8hlghQUxkfhn1OHu0Tja9xoQYPXyMfLvxYgPJ/c0rNAEEEqCFVChfSCrHBCsfuC+ZY2TBhAKBjS0A5go4Sww4iygBLLUQVJuxoqwy0ytTCR9juzFU/MGuhE0rhxj9bKjxH5SQ3ePyxxESPkxNmowEFBA/Rgp2uTYYDIQIzQtU0EHA51PIn4M8SreQO1jddijAiIe68ETMAqQQgH2tKJZyaJTTWNkz8xcZe/KUSuzFwb3+s+sm1jxMY4jsDB/ux57ZP2uDzj04EGDTSTwoibw8ofB8UI59RXKbkh1pXgXqWnzh2otUarjQ6dxopuscPmPvvz+y6Y3bF7+7Mg2js1bKXCgbIBlJps841vvvWbzjqu+dvHoOBKQ0bK9Pd/RAw4+EnruMSb3opweGl68jV4mC7V5bZWndIhKR48q++4VRMiPdBN0OHzj5nM/eAsgMN/AWzZz8Ie9JWAEezFmROWAO4KMmkDKMsbbbBwBkI2vQ7Kuz5upTWGb8IquOjILbDaBBTdEwxdjWke+PXnDqhNI1E8L8BkmEoj4mHtxbFaL3hU9ARDUB7hA6sc3X/iXpW85vhUDHLTAsQMT8aryvNX14VoZI9U9SOFJpOb4HJ3Kn/TFX0HNqBbemp14dOyuiw07lIepymJgTWPEtQYKyna4IQYm4BiuMrB83wh92BG+NExTT1fpIGo1u+UxYHR1U68bk2cfiPxrQ8q8dfSsfZff9TOuAoL2usxJ8EcEGVCwcQRYhpSle0I/81Nx4E765jnKXo4T2ySRqkGKaA3MgfTozwbCngfgMJgwdgDo5Ojsez55GPnSn6YXf/VLHHMWtUUGisACHDaIDpyaTQ0jS5fTQ465KnpdGGLgBYjtTfJ+yse3ugK1qlloG0HP1vGlogCAZb8qZezMT0AqAgvDB98AJpRHuVkKSbADaSACdHgoslRkL7Max4e2/Hrp10fcH9qEoT293QcGscpG0JLXzbd7ygxdE3AohjoAyzLCK2UQTxMfb7ZvFjRHYgSBuKEfYZQv7Qs6usYnBEemRoi3rn1w/+HXK5cSWAWH3TLmVNnbwDTEl4vzi+S7B1IcA6AIOMOOmo5XAR0bWSseaX8Or8sjwSiJZkWucv/9RJ7NjObSI8Sasd4GuJ5gxodwRlT+GNAjQYBiJkhByqDJot5N7vFokfLosdmkcmzPKQIL5nOvHJFPpdDgb/FpuVeeUei9p7XAwCrOc8zh+6u1ObDbnh+1ikyGv5VFtCWd/wkgHA5AeR4zYkaBrH01SjCpp6QQKq3ZSX9FFnRgjnkDbg0Oc1YZrZUBVldsUXUGqJ4TNLBw3nmtziYoAixzDxChJJYDHeCV5bArqx11Ajs71EAKxQMspHnFAI18ky89UOCOLjGwKrc0ozopsLya+NWicr51+xzFGSsyUASWhwjAC1NlV93jSgCEkeIY9nJ5hJd7UqT/zjZLufCdBVaHGasK6WORsaxU5y8UX99npzhCylBz+g2ekJ1latoI0gdgBBANLOcmRMZqdW0jrcFYcZkasqLAqk56TCrQwFebWgNYxBRazXEax3OC9r0wf9zRXBXjYcDLwHLGlVmqOLD8pJ7ZHEBgdY/bImOhYMgfCbyY3mkBXuKPCDIvdgBMBiXlBEHIrIqjQiQjTepn7BiAVZyxopPudBqnCdFab5aOdSxtppcm0pM5vDKvQvb3aqe+VR8rFUpILWe4/+oD0yeWWhI3MmNZZowV+OkUf6uAKeezcZDFzru9zF4Cqy9NIcByHCuGB62YIoyVMoI+GyNJEVLMByBdHuHlUWHKeS9uCh3Dw3mPbN0HcawqROFTyyUwQ15QWtzHSinVwOI4QgojSK/oA8cpeAGs2Ifo8xV/Ck9Cx3WU9LByWVlVmwpg5ivlwmMOMJGtuvDRDLk8wsV+lYF13w8eeOr3qyJ7eXl+eVPoOk6eKb/IokfuTdUyD1Mpfo7C48JnvlfC0zqNdyVX2oMDYAqgbfgAEJByfjoy+l60EEEcl3zlG0S77U7FSdXv4+yGhXXhebme2CE0gEsb90FwWNXAyvZTEBBRITlVtA84yChnwvj66775jtuutGT0aiByjPdj3jJ8nYuRHyD1HhCe6o6ZF+XzsfI1W2p5X/UzSPlSDQuHLh2D9vwa9VGhAYT0vB4lZGIBr+VLbvj4vSssUV4sRwJBr/bx4gvfF6DEwGxqPOhlsQDXz1vplYZV3vAjjtQcW4o+B8CyIomVT3zkD+s//QjQ8VlCmgaWpWumSrLxmjPftf4n1qcc2AGalL8FmDyNE7m5b3Leq7MvQJFVOijGy7AcnjAUXBOQcews9RSwqB9lXKUTwed056y+Vhp6IpnPJsuVlV/Fk0bwEWLo7PbmQ7QSOq4rtBeCkgCN10Z7DxlMj/0bjpFWJ3D0WucUvAwaoOlsdK+xdjnS9T2qpRyvkbGn5wDyHfbeQGqg9nlPbQRin8Nm0XBJhUlRJ4rP8pyaisRfgQk8OEAaWPhnGZdottGjyGwBWbNls0502z00OSUUnDCR3VtROHR7N/B1erANK8BSXpaeP9oCfI6hO1LFWScl57dGHa9DBFi0RsuMFjnOzxSFa/HACH8A6Jge0z2/qsO/ROiX7WvjIgteupelAy8Ug1FDPYAP1aJspDmGFpCxxC0AOJ914BQTRm99L3OqQx4A1AvqUzs0F1800ceM1XsqTpnF1Egq7kgT1z372JsWARTDxRNK1LHRtMmDwzxB5Ht54zVnVdhcxhmFTu2PVZvCFnb0i4m8gMnL8M1tMZvU/5Iw00TeMpg4S02ustcVU5Dpgzc2SgVN8nf0GyhgVc1VjHuQmrG8vayXMFDifIGMP5pq9n9yiKen4GuVe1bRhtIbLXnFIve1L5Xt/Rf8udQepEPHWAuVIx8nf+LsoTcq8jof70qVv+9eLPeuDa7jHeG9n5YXg3gfipi/3/eQGqR93vPZJXpgWTamxoNOofF2tBw7NmbnGoB6cgb4RnMcN/qOk8pF9nmvgbUAvle+a29YwCtetu/dtrynjd12QAakPD3sFrIN3LSnvOPpqb5hTAfqzxSd+pNndSaCiNenfizgv1HEICrAAhwROjamjBap4xU4MdKWWr1N/fpfOn38d532UoHjDhGwjnd9KZ5saDANID9FuhmG3+/6f4WpZJU4F5mfQuglqUX+8uVfyQ3Fb3/7/fe77f2o139YzY8ktSqBUfzDav3r3qH7nXh7/4R2QJWryC+t/wldA6vlqVkmVWrQlEr0y/cD8rOhy7/6Ii2k+lC8b+V/cVCkneKZ4519b62+jeJ1Cp1lA4xqypmZkZGxsSr3sH4/Kdl4Zn7xutGHa1nLzsq3ARbfRC1r2aqsgVXL3gKrfjW1LCP/DxR+YNThwx4XAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyODowNC0wNTowMIt+r10AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JMWi5zdmd9e3t1AAAAAElFTkSuQmCC"},"34":{"admin":"Brazil","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABGEAIAAADldHp9AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHTklEQVR42u1dXWwUVRjdGiQYfCiNsmmJlfqzqAGxkbYKwZjGUIk/JBIWMfFBIrpW/GlJfNhYSCQEESNaEBRrmq2pCJXSSLDaNJUEUwMqldTaJaikGqsSElATrFHQh+PDl1zv+M3cO7Mzs9/LyWZm587MnTPnfvfc795JJC57Y+f8BYKCllGqQFCIJSjEEhRiSUUICrEEhViCQixBQSFWAXBRc3f2hplAqQ0hlhGWv96RnteyecfBiYr0qcr84xd/CcQW7JVaEmKxsHRP+3BtLr3/vZHUY59eeezC1Pzf42P1ickUL2RP9iRqsBf/xFFSe0Ks/8AbG9+5aG7Xa6Mfr08uhTJRGtHfdAv+iaNS5Z0D1a9KTQqx/sXM1b3rrnoy/8LIm1OOUupwkCoZSkBpomGJYtanvZ1HystavZFJtxcahpJxFiFWUcRPzQf7ls+chMdvTiNnDcNZcMai07DiMQsOnPysoXS/W32yRT6c/ZYf94zOWSjEirxZsGHyR7fNyPP1yZk0at/QLeJKcFUxNyzi19jd29jTft191Cxw++APL/zg6/Kpb3e016V+bq17paJmxfPZTefqpgGxBXvxT2/EpYaFECvU+kTNAj6N3q/uWlA18OgdTUOL7pr9UkPpQ79cPrtmdPU3iQ2zpjzT4oyX/ll9pKUVR6EElOb2GmJoWERdn6hZwNEnPEjoTf309L7l4xwCuUWUvHtbxyWzPuRrGO4iJqZr1M0C1czU9dG+qPqkftq1D8zL3H93mx9k0ukZzoiz8+M5aFiEDYsomgVUnzgBNZonNFjBUEpFnB1X4jbmi6TpGv5LRBedmgX8kByNET9m8htxJW6byEgaFmEOxp9N9uevGHNrZgKhDeGhlEovt+qlGhah1rCwXRA1C9yakDSWKmzDx28cObGXrh5QS6gxIRbLLPA2kIJjgwzPzRFXa2LeUsMiRKZrODOfvA2kwESICqVozxFXbuLphy5LrLCZT94GenX41Jl1uaaVDdlVE23XLMk0PpJLpjeumXjr7BOVLybeHQCuXrFt065WbKeIo2pPL/tr66HUloahzTfhkQdDL/heJkNPOsOiYKZrmM1MPu3Or/yh5NaSzuvz5/q6DqS+XzW4dMeJ44u7M0BswV4gtg+W/fTtcPPa4c/XtM3B7zMlv23/9Y+eh4f7D9+8M9czMDgDCFKCdpV7b/99Y9YPevHDeX7tFSxLLBizwFvmEx/Ht3+X6+oFgYAgB0VQipJGRyxKO2zB7619x45234kSoHxU50A+E2JhUMhWAo+KeAoBGRZhMAvMc5623LO7qrdJ16glM/NPrJ8EpaGIvXMPLXnw5SSaTtpookyqYaAXflM6grIgK85u0k/01n1xa1j4niXmX+aTSdXwzQXkF1C/Co/WVoMFUoJ8lHCUaiCWjmT8WA3/xB0514At2iHY92Vamx/TpPiDLW73qiWrPcFgQm8QFxoJqtHmEs0xfmMvX8lwRyYvmzfDAk/QWrBv1yzwL4rSEQvZUYW1DKBqaHwRe9GoTiUZ9E9XGu7IFo10L6euVq0ZFrbMTD/eJw6xkHxXWGJBvahGQjXRdFINo6oGtVOVFSG834GE8/8tmK4mxLLb3jvP49PRy4RYUBqTcJsTM4FkNDKjvU5Vw3BH5mnQ5uQLlFi6JGC/tUpXpnlTCHoF47CDxKAXdAskwxbspcTSvU5+1LPaFIY0eDd/bzjpe8EM41BjwlZpiMZoXxL0OjVy/LnFQ/69irrg3fIUj2hNtOLYDVSH8AjLltVWrH3aWz8RR6EEqm3mvU6UgEgLlIJnhlEEP2IpXY6XL/kR/g3gUIPUPMB3ftvUJBkE1LA9TR68qlLYYku9qPeG8YPz/WP7pn9lN3hXLWU8HR+zIaI4pKNWHHpSIBO6/c7NEN8+VbXKlt2qRnjOQzrmGPkhHc4gtF2pR7IvhwR0kKdQjhfVUZDMOafUJGCP7SA0J23G1viX83QuXdPGQaigsxbyEdcAintLm3EOGEKxMEl4Ev3cxli62Ticpo3qB91iqwF1tlLpoBPQeXoFPzalycpFl+jndrTRrW7xU5NpHoSJcUCpSekCZcJeUApbKEElNTnQlRe8TfYCBjmZgtJIpRoUEXtVtaOTKbw5fzKZIqDpX2qzGMz0L11vFIqodhRoqO6WUtQskOlflies8jGYCas6TVIHp3UTVjmRpUxYDZFhYXeKvVu3nQbp3qbYR3hd03isIMpZ4Tj4RUFoeO48MVXtrER+FdO4rsnu3KzQZYz8yDVFyRx9UtddjslSbPFY5gsJtW7nUsvCa0IsC6s/2FoqEmhrqciQmgVCLP7itnaTp/kTRmRx29gi37AwSVF0PlaW45YPCFjLGUcwjjMW3dfC5JMnfsx+kU+eFPVHmqhhQU1Xb9mb1MyUT1/J17+MFn+Tz8oJsYymtanOfsy/KyHECtKwkE/3CrHkY+NCLEEhlqCgEEtQiCUoxBIUFGIJCrEEhViCgv+L/wADAE2QfXfbBwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjk6MTQtMDU6MDCoFsT9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CUkEuc3ZnU5e+DQAAABh0RVh0c3ZnOnRpdGxlAEZsYWcgb2YgQnJhemlsnLDlWgAAAABJRU5ErkJggg=="},"40":{"admin":"Canada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC6klEQVR42u2dMU4rMRBAU3AIGmqOgESNBC0VN0A5AhWIkhtwCQoqOiQKRMEFqLmIKZ4i+ct/FxPbG2/2NaPIduzx+sUej2edVQirw9VhT/Lo/ug+hO+777sQwmf4VA5LnhJPrK9xXAmWYAmWUrAES7AES7AEa1fy4f3hfSMFS7AqyK+zr7MQjtfH643sRzfBmjFYN5c3l/9qSIpgCVbRXJUOGynkCpZg/VmuX9evw3qSK1iC9R/5+Pb4tjHJ4xY/Dj4OfhswcikZDzO1UbNgLRos2j19Pn3eYHF+cn6Spy3fwuriM+mCtWiwXq5frtv0gpoFa6FgtRiYKV0SgtW18Z6/8OVIatN4F6xwdXF1UU9/ahOsRYCFST7keSK3lv5Dxz7xnlGwugaLMuMlYycnc0lavq4Jn5rttMgSmeNczemXYDUEi18/xjLQkMLQMnjpbJQ6Noc87NtpHkNDL9KlNp5B8YSRQkl6lDO3CVYTsMZnmvHHHeNFW7EXajtJDePnjPka5jgsBKvhUsjvu9weKgeLxS62pVo7LASrofFevqcDqVrDU15b/r5SsBqCVXdP14PM3zkKVkOwcg6M5yXjg23B2hlYGLn7ARa9yD9nFKzKYJHLzm6f5qr4CdC7nOcgWJXBqnsU05sccucK1kTuhnFf0RxlzlwlWBOdFRJYN99lEc3/Gh4oWBMdQmP2lrhMp5dou11goGBNGt3AGVzdKKsWEg1L3vYRrB2EzcQ7x/laUYI1g3isHvqVvhEkWLOPIH26fbrdxTDQIq0bQbq3ocnTe7/aRcELVhdglYfZ9PbejmB1AVYaGEhbDDzzCvMZTle8SnyLXCSLGrmU5FsEzFBbOtgt3jQUrC7AImogxoWU/DDo/HBhnAi0Qov5MQuCtaCL15iZ+rnASLAES7AE67crjXq4ukiwBEuwBCvP+yVYgtXk4pAp72UQrEWAhY9KsARLsARLG0uwFgtWn3/BIlj+l45gCZZglcsfHBPAdZqOceQAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjUyLTA1OjAwchcLNwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FOLnN2Z/LyUl0AAAAASUVORK5CYII="},"44":{"admin":"Ivory Coast","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABIElEQVR42u3aIRIBYRjH4ZdBFh1BUVRRVzSKZFxA1jmAISkcwA0EM04gusYGxREIPnz2eW5g9jc7/u9spSgWi8heozts7ZfVa2c2muf7Kw6r2/qyGTdP/U07pnGOXb6/pRogLISFsEBYCOsj6jGIrQcpLJ7J/NAgrF+1jV5MhAV/GdY9jjH1IIWFsKxCvLEQFsLi89yxrMIk3LHAKkRYCAuEhbDKvAqFBVYhwkJYICyEZRUKC6xChFVavsciCd9jgbAQFsL6PncsYSGsfLhjCYuXuGORhDsWWIUIC2FZhXhjISyExZu5Y1mFSbhjgVWIsBAWCAthlXkVCgusQoSFsEBYCMsqFBZYhQgLYfmP9X6Zf5XljSWvJGqe3Y/K/APlB6G5QLnEpZn8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMjowNi0wNTowMMpN8X0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NJVi5zdmdOMQxzAAAAAElFTkSuQmCC"},"49":{"admin":"Colombia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3ZsUrDUBSA4Tvo5tCAZHKUOLto+wBdOnXo4lLwCXyD4KA4S2kfK9CWvk1LOujQIgGRY2zkW74h5N7ce/i3pO12ucxzMtZkBBQWhUVhGQSFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWOQPw6rr9e5y3EUPr/FXX2/nnW7d6zOssnw9n2zIWFNKdxfzh2j7g9nid3Y+5dN+7POd3bo4n6YzNz0/WkZGaQQUFoVFYRkEhUVhUViksCgsCosUFoVFYf0rv/6ZZ4uePd9P32/JWNPs7Wo0fCFjTaub3tP1IxlrqqosKwoyVmFRWBQWhWUQFBaFRWEZBIVFYVFYpLAoLAqLFBZP2z3VWG8eJgK6DAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6NTAtMDU6MDD2X1rqAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0wuc3ZnsjhgTQAAAABJRU5ErkJggg=="},"51":{"admin":"Cape Verde","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA7EAIAAABSyGRiAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEHUlEQVR42u2cXUgUURiGRyov+tGM0rpLEw3KCqPSqJAIY9OijLabMIigrAwLWbIwVkO6kMKQsgtNUKwwCVGxpT8wENmooCyCjOhHpRA1QZCMdWJ9V5plmWHEnZ2z43vz8nFmPOf1nMfvzB7nW0mKTIuuLKVSg6ycAirBogqn80u2F197QbCozFhUgkWlEiwqwaISLCqVYFEJlilnMFSCRSVYVIJFteLmG9LRudhUgkUlWFSCxYmgEiwqwRJAN7ccflW4m8tMsILwcRoaH5uV6mx/uKGmY/1qxMqrXHiCNW2wzsdePHqgr6u9KXPl5fFMd/OcbMRoJ1gEy093zsstz7+O3KN9Z9xgZlRZtxKspxEN7uR0tGv/LPrnBmpxsKIiM+zlvsWur6i6sSn7VJsjy74CiOCqGljV/ZXb0hOdg1cSbDsQq4GFfnAVWQ33Y1xb4rn9LXeY5ywFFrIU8g1yz8DNDveCLix8Uu5eT/FfbRyVeUgNDiWI6F+Z5+CBoFhwK0SWwmL3JTz7HV1kxFaFPpVgYVwiYlmwsD1hmbG1GZFFbHOPFebVon+MhXGJiGXBCnw20vMYLtoos+4pjX9bVEPUGVG93N0sgpak3Uq7v0wcP+Hu01yV5EPyWbnAXPV8G7f9ypXlvqv5Rb5YAFfh61MElcxepIGM2zZZ/vLTfkSW39XFVUzF3nYRFs/f5+dH++rE9Emw/FUey3i/VZY/1m/xfchH7GsXZ7LCxSfB8s8H+Ov/r6LlADWfBEgVrB/PHS9LR8zS3tOOHofUW1awMcfla5mM0W6uNzWfgbE4PkWYJcTS69GFwwkOc/XNpxhbkl0EJ9b2HEqVMEH6VTmtRqj+/rXvnInPQIDMVTWsRfYjmYVF6Cci3H+X8PIjGUfxdCei25WYsqrRuP6DlbG0fYbej7kZSxWsoaoHl1oXmavDsY1f764ZtTUt2fUHsQiuwtenCGr6AelIw709stz/tmBi6uAR8WS7QOdY8Ok9c5flDydSchCPtw1FuRp4QCrgAemovbPa/+DRu2xYMHGmyecH3hQ+ff4Jk3Bg+fDCv0pwlu2NRT0gFd2npcBSm1w9k457lBuKcZuLfj9qsbbPYHmeyXwaNz+BV7Xvl1AsQKUGV/miH3V2v0GKd9L11BXORFH5g7EIh2XBUpaPtqbWTqx1othhXd7B+AudwXonHW+jAyn0j7GCOwrBEu4rPdTqCvUsuVpRq/66QlZFW7z8C0uO0yPkEv3f46CnQgYAfR974ln8mHWFs6hgNefMySXHYxCrPQMBIFwFUj01ruSlHsSoRlSDDFcDxyIilgUrcDvTzkDAAkgh9yBGu/5R9Gyj1FlUVwjs8ACOrQ0xF5tgBaGmGRsZPlcqv6OGS06wqASLSiVYVIJFJVhUqkL/AUGVaXIDVCwKAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozOToxNy0wNTowMFhwAaAAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NQVi5zdmdq6F8gAAAAAElFTkSuQmCC"},"52":{"admin":"Costa Rica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAERklEQVR42u2dW0gUURjHp4tGQUYXIQNTQ7ICMQhKrEiwhzK7UJS5RJlWBllhtWgPkWjRQ+YWlFHbSm7pYiVqChWZlt0kwl2zpKuhlmF0UYmMygz893CW2bXZnPXS/F9+LHO+8zF75secMzPf7EqSNOPn/niSVJscApJikRSLpFgcCJJikRSLpFgkSbHIQcDJCQuTMitJUl1KDSXN1tYKklSXUtenrpauZpJUlxSLpFgkxSIpFgeCpFgkxSIpFgeCVFus79OaTM1+JKkupce5wUnhHnLWpge9Dmt31uoqe5PNWV9s77m1f/fcHd+3v46Iq/FS9aYxowNXuJHVY8dNDfpDF+Nte33CgrNezlsZvfFcy5ZjhcbEVuny8msBILagFZF2GZTsg/K96kuKe/Vvo+dqq5LxcXGvJLugAcO6ijkXFq+FQB8Pf9jcdKznGR2R6OXgy/eG6mbTDAeSWN2HEHJ8TahJfWIU1aktfOb3VFfaWVVddxTEFjEGvdyiFzkoxeqWALN4u7m84851URdopPfIqr80Qk60ivHIgGx/Oe2T/6FYskP+ZliyIT0HcmD6s7y4YXyw6lDaxeMZE8D8ItPw8OUGn/zSaLO4HZEo2EAGZKNMmhZLfq5qC605ZW2DNHKNRCbrznqv/2TRWfak2E2OmBYdLOpJ7Yj1/FtkmO6mOJ3lhRTfzSoVBUps6Cizxcq5t/TVRMvWvLhsW0IjeuE+CvIgM8XS3hqr+5A3WncW7IuFCm+Tm9bYrBBl+9yCyiTfNP2PoXdKwA1xv5bZRoGiXohEL2RANmTmGkujYr3zPOBpqBfFun2+3Du7AtPc6Ud1L407Ch5cDPD3wfkJzPF99CXGC58RiV6iWHYrLYqlTbEgBJbnu98nLZg2P/TkkaptLRDovn/uFo/AlBrT6TlmxEA18Gh+hmlWW2p8infIErQiGzJTLE1PhdbMh/riXVAEYkEjy5X7fiN9wRNRWZM8LeJnkeiFDMjGqVDTYmGJjXPMqdWGIRHboMiigpLQqEIwduatqJhKLM8x5WGLGINeyIDrSrvFO8XS2g1S3BSACpAGiky/t6dKVyJJ2UE542fMNpaZjmNFhetEr6WGiIOdaEUkeiGD09ukPPBau/MuXhtiIlvXqP8caRb1gkwi0YpI9HK6bKdYfKQj3n/PWWvWJzRi5QSBQGyR32JwcK7iweZDaPlDaOh1LbzoTMoUXPFBJmwRax8cPISmWBRLrlerT/GUq/OVFMI6KJuhWANOLGfFXAoeJKtWkibE9Fzoh+s+u2eCrhYeKimOU16npVbJXl+WE/ZmfJwV+ikpQu25LNXdJbNKSpP/bR+U9HJfoXDfFz335R7yZQrSPS9T8EUlku8VkhSLpFgcCJJikRSLpFgkSbHIwSAWfziadMvPcfOn7km3/IEA/5yD5H/pkBSLpFgcCJJikRSLpFgkSbFIikVqlb8B7v10S8piMgYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjM5OjI4LTA1OjAwILd2qgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1JJLnN2Z8WQ/ngAAAAASUVORK5CYII="},"53":{"admin":"Cuba","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFRUlEQVR42u1daUgVURQ2kkgCabHCMC3wGRm0R0UWhGErSUZRgVG2C4VUP4y0iKyoJKsf/qmgjZIWopVSishooY0WbTdbeCgtlCZFBC/wMxiZZrp35p5Z3jt/Ph4z49zr3G/O+eacc++NKj3SIzXj9oifyQmro6M69v1SkhsVSM3ZuV0Icb05ml+pP6s9It6KLBq1Ittb+09A9pnQtWvt+Ri18rAgLiO1/NLFbiuGPc0v6TlrxrT4mj7LN6ZYJJYqKuj/Gbo7qx1OCorQEUjVK6fHB1e6fOwXA3oBj4W6Z6fNnv6pd9qSNq1IZm7PVD0CkX+G2pKpJbEqy2HnOHW7+rN6YgFxHI5yysFegaXHpYllh3D2HaIqEqt1K245OLXWV+RuIJARgmTX47tmDXxSdDAhPnNNYELKkMISixbLO4bdSQ3npEakILS1V86cWHqSQY0tvpXUJbviH2qMTp3IHqejkZ3XyXnL4TzpZYklrca8gxSOzN0hlJXYFC+/iMayRiytozQMWzgvJL1AblmF55bFontWdohlpMY2ViYkZwZb1JjXrBejM6iKWNqz+K1VY73LhpQWt088mV69L44xElCaWNbwatqAtZPrHtTuzjxy503Wq+P1N99ODeY0bNOj+Vm16GRb1O167Z7kxGqlyWoSigYfra2c+3tFUlPxzf13L4Z+hd6Hgozhh38DpM1D7oz1un8ltiYl//GqwPtRoz5k55/f1PBz68u8N215MMKQWO5idWh4+qS6+oJdZ/au/1UVPF2fygMTocSis3Av9k+MnvP56/PzNy53+F35NfnbKR4ktli2dJieuLWdFhSufPQ99l7Z4+E8VEwsxVg1rn9l+pjg6A3PdgxiNRbRxIIwp7jzsw1jDkwr/jh3T5vDH9hRRhCxQCnIcAodhi9K/H7dbUbnRXe//aiYdC2aSRbmxMJgw2FBhpPrs2b6vjuUl7EuC2qsJbh3Ibi5cbwfw57hEbZVTCzYKnAWMSqtjVEl7Y3wTm0gZ/TeLWdGtss9N2zm2IW7Ezm14suUDkgDhOXQfsEhZKB3YfapJoIo6WlV+8rozSQ0wpgIAegRX21arYNQJ+yW9sq3ecsa8/fgN53Mh50Dif9R0uNMzTsTS1aY2xHLIByISBdoNaobw0wkw5IeZ2qVmFhGYhn2RjaqBEcJge9uMBYlPfMmJDXNj2mpffVaqaD3a2vp6rHgHKGlzCn15dWJDufGItTpjMYSJxa7RXJiiX9/aa+v67U9vrTInFgIB7hLKSFXyOiWxdK7RX1llb5CARZLbXBBfOKahHiPTKSwynYCDQiBQsgDIe1xHIkXEAtqDK5T1m5ZoxqHG3xpsUAObWIYdaHas7Bn2lI+OEQ6Z8cBUt8HSLWkQWLY3A7BhiGCpTZviJ7gzvjqRDJHNqXjx4SMW8kfoZSOnUgS/lbEtamV7WiXk9C+TEI7WQUvrrG4bIYL/ZQlZBDx50I/JlaM0Txp2dJkrXLioYo4Yql1mi2TKaaeHVp+iZ0dWyxl07+YTBFELFXRcH2tBE9YZYtla+EQnmLPxCKR8PpFQZyvSWd0NEBKZ6u0pSm8jBGndCx+8fHCa4yG67zbcXZCpSlq1+v1zrKI4bFKqtrnY4dM2sVtPTfA7rborxosunosow0EjJRTq+W4xdfxVbuoP6O/SpP1YQLDDQTo3g/vr2bud9JT9998ypSCRbZlF/IPj90imGpC2wLY74rsAvbsBP2O/1FOqjYF8QtRKPpsf6M2d3e4sNZnkhksdPsyqNrDgm4DTpE+qLXW4tucOEOpZvwDsTW0wvU+jjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjQwOjA1LTA1OjAw1msuXgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1VCLnN2Z69F/9EAAAAASUVORK5CYII="},"55":{"admin":"Cayman Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHRUlEQVR42u2cf0heVRjHldGSbKutH06TGijkypRopBAELaF/XEKO/JUrc21tLR1RSAMh5wYuVuJW6Upj/bHQrK3VloOgYJsL1G3NaFZsCdpPm1mN/kkHgR//eOBwX+77vvfec199/vlyOfe895x7z+c+z3Oec+6b9PW3edvzyy9f+XCod2p66Kdd470zl6Y7Z45JfX+ke/Hgibu+uGfPjitJtyTdurEmfi28qbCoJYPr//JzU0VT9rknlmVdXzR0Jjk3eZVUzv6R98/O/zr5lbd94O54Aj/sL364eIZ26Q8lF6YPpRz/Kr0nvfalHq9an+c6/Fvmuszl8iH+vezozqP1Jl7jG75ffHHL7uaWw31Xcx7NubvxgldgjR/c8t7mURMpCRY14weLnnMX4ELrPAGn5zB2emxo8rH473oBqXy4QMZwRoaMB83wxGbJTIvlBBZ9i8diAcSeoldPHMlxD5PUb0q+G5+4US1WFMpQmW9wtJAxbO7fadoFFyewGPLYLBa4x39f8bw8C1pjGwwikngGw73Fcg+WU//dWybZf2/juQUNlvtBiv+N98piOcVMaplCCpYJWTwxijl40VosEFxT/uDx3du9tUwKkzWw/HA3TPLdgDWSsnVlXbpMfKhlSgBlqGJT5koyGUHsJdUpNybVfYyFgo5sxQkmc2YX252Co91ZIU4fS09P8CGb7n9uV3sWtpzytDVLSjevsAwWA29XsUmR0w12e8hrYwusOyqzzm68BECjp86d6foSmz01OPz5Mx1SKe++7uDadyosW2VciV11yrnLED4M/VzxdvLqqkXBDxKWCaQkRr9W9zUsv4r+1df/1uosyqkJiNbAchpOVamftaTtW1oSPFg4NdyxCZOpErtnN7St7dhnzS0qNOEEi4iqLPWp7Pp3QQQ7BEDna1rHkielSryG3zyQvze3+FTx1GsPhdRiRXZSCpYfSiQHTDkN60urT2YuKqm9c922/uaJ9akcJxXcvi11xzXP5zXeMIo+fVtdx71V1LGMVJAxFnsoEjfGCt5iAQcWS+IFRiZYlKDUJ8ayNpMNw6ww8u6GhTwrJLlgghVZqQmU1sDyKo/lpG5yTtHmsUxl+M22ACL+PFbbJ709g0eCD4RJGeAWwQXIKEFJRsgSWTMhM+/ksp1gIg8uHRm2h1yL+8y7BItfuWlXulHZbvgz7+CLpcEhspwF3PSc42P/Dly+2Jj90dbHe0spmds3NosU5dZSpu4NsnuYIg+qzCDHtrshWri5jgl32CADgq7hsuT6OpTUMbM8lPLOV16ebM2ihDqU86yqKwuuffFke8p95SUjKGcD3ajolWWKPHjmLXm1g1TubjD7KV2kG+jtbpXBrsgJDZMGjk/vX1Wx5IGBlZWNS2s5lkpNzqLyTpk8BXp3/sEU/H6syJA5uenwuMvCP3M/bTgFBBIjoOm+uaY2rY2slTyLclaWoBKsQO8oNjcXv1uJdmtytDtIo4UsDDEZYTjPFnTACETMpKiTSrC4zpzFmgU3ILBsxShuNvp59ZVObBMOEzK/Q2DAAgLTSjmhY9Yx7ZkFsGy9wWzzcA+WV1/pxAYZw+w3WFhZp5hJKmdRM/aSqwVgZwEscOEhyu7K7+n8cAcgQm6JeY1sXSpnqeltH+TXO1wf1FByYMy8mFv5nXnnZZPtyhULXjATerm9kWcl3R/lXDPQ4J2HZSpTX7/ztrIVp55gJ/yeKtMHWkFlSTD5a7JW0o6CBa831h2lDklgjilnFmyWc2xtVqhqV4G4oKv5jYHzKHaUfQ1k1SknNQp8HFNe2dTeevYR6styNFBXqMMZHsXRAwcYkT1n54JcqKFcQiPXE6kva3JNtVgL2mLJOI9tMHKBmW3HLOBk/P7CRP+THGOlZE1+i3Occ4WzFiugPRo6nOFZH2QpRi7wgwsWCAUsIifqcIzjkzU/3nRgrOp16hClsZEmoHVDHVS7Su6KtTxmf8xAsUY4RKCRzhErBTq4OcqpA0wccx2uyTyRdUbaVbDmoTIHZH+6TB/0frA3J/+QxEKqubOUYxlRmfVJQADuj6mHM8rmvvDx8YMLHeAgFWeH4tTACCURzfc2OEEsEzYJO0RGCmfHflHwoj51qM9vAQiYUNki1ov+eAyZDnaQisvDJQEW7oxhNr8TNDcqgguBuYyo5A4toi5+K69mtiXTsECmYCVweM5AYp+kRQER+W0guXigIa2As2PbMeUy9pK2ClfLdbCCXF+ChQtG1WIlsDLhByP5iSkqnRQlWDUQkfGTGUuBHTVRLJC0UjK6onVKyO97nOXSwQ7ebsmP5aXimCQKKCUo9klms1BKcJFOVzBbRFkt1eB9nijrj/JPPlCwMyMw+eWgCZzEyIQJdOSqK1bNx0UeHeAwZ+FxZ0CG2zJdm1Qwoj4wWdtsrUOYWLaNsB3gcIscY+ewQ74nPxUsVQVLVcFSVVWwVBUsVQVLVVXBUlWwVBUsVVUFSzVg/R8WI9snMhO89gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6NDA6MzgtMDU6MDA5M0h9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DWU0uc3ZnWteoewAAAABJRU5ErkJggg=="},"63":{"admin":"Dominican Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE0ElEQVR42u2bbUhWZxjHzxzry2gWRdZYy1a0IiqirL0YwYjZmzh7lkupCaKVZTyjFyshqC0TI9TUhKK3yTIrZb1AWeF64SlbraZGg5qrMZgpi7D2YRVRg35+uCWMkz4nOtz/LxeH+z7nfh7P8+N//a/rvnUcZ9R7aUnWxT4jalP3xmQnv//dxLuP2hbeCzwd+OTUk7ZwxevvfHYzUHD5Yc+cD/64cqV372HDbIuOpWA9i16AxWoCS2AJLIElsASWj8AKr7sywbITKYElxRJY/gFLVaHAkmIJLH96LIElxZJiCSy/gWUnXgLLc/MusASWUqHA8gNYapAKLPWxBJbaDQJLiiWwpFhSLIGlqlBgCSyBpTPvAktgCSyB9ZqD1Rkct979O67ZKWqo+u1AVH7P4n83lxI3xm9t/eFrZl9NVfi6bQ25+T4Or7g9uv5JOo0ve//zT3V/fdefayrW9Yq/ym8+Ap1v1i1r3bBvXOysiCUbP7w0vi7xVvBi2azCh8z2yh2XkFgWF588Jlie3rx4eeYdxpvTWt5qmdnZJrRt0ZlWnFlXeMTOmDWkrHhbPUAwAkaAcqHv8T1nqmsWnA+eazLViJGjoboxtde4M/rN5GVJFV/ezTyU8wmr3fhidf36C03/za5Jr7EzOp0lAjfjL3Yn5qwbH9Od9d1/yvORZIcCkeYAzkx8QNY4t+HBr1/9MmBTZGEC8fT3C+8vDezPKh279SxgHel3eGDVKvdurGv3hMsXvuw67r+zE972oL8iryAtP79iTREAMUISJOWRNKsTTkSej/wzWL274jIeojI3umREYqgxJiN2cEv/xpprhYAIXja/VaKjV4BiTU8pil4Ze3z71asXV6NeIAVelZOrdpe+gUrlrPsoNKFfXMTks7FJBxdNKpjxNio1dEb2juT5MZOW5M1+7MU2kcDyJVgTb+TWLf6JCEzoFmmuNHHF7bzW8vt5oeUTGGF2V9On6fE/Y+2dzEXfTs0a3mPt44xRlAICS2BFAQTuihHsfMPQvUU/rj0xOiU1fThIMZJanj1oSwEjJFPAQrdwYwLL0j+exEcSNBXLBAj/VNI/aU/GP0RGmMVXARNg9Vm/ND4QpGYUWFaDhVaBFJDhnDDmAERnC7CoEA8/nR7xcS3tCWAyUyErCyylwihUxwSLRh/XeCwcFU1RkGqvHJ8hZaZCKZbaDQFgQnXQLSq7knlTmkf+TluBhid4mdWiiRSRFWTepVgdzDt6YyZHIKOhQBI0rboZQZMKUe0GgdXGdjL1IFigVUS8FDF0bE795ye5ZpaeFls6KJ+0SmC1R6o8NmfMfjp9drMpSmSEWSDjqcotKTvn7mI1vVUnXLtIXu9SebfzBVJ0qoDGhIMkSAOCaBpz7uQpVmA19/uk3XkPXdtJ9G62A1i27bpzoIVrLDnnEbxIrDafHHFsPjMEZF6oJie9XJ1FC+95Na+j+/N2Xp9OfDXrd+0pwPLiaHIHsOw8o9udn8frn9/3YFn8H1COnUf9vUuFRIElsKRYAssPiiWwBJbMu8ASWALLcrBk3gWWzLvAElgCS6lQqVBgqd0gsFQVCiwplsASWF56LIElsOSxBJY8lsCSYkmxBJb6WAJLfSyBJY8lsOxBSmAJLM/BUir0Iv4PtC39UeHB87sAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ0OjE3LTA1OjAwotdpJQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRE9NLnN2Z0r/d3MAAAAASUVORK5CYII="},"64":{"admin":"Algeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEqklEQVR42u2dTUgVURzFJ0NxIREYRlD2hSGJPWEetbCFFc8+rDSN0lAiTVpkipliEoQgtWilhdiikIJKS4XQUoO0xMgIUwsz0/xANPArI4qyNOi0uHCdYZ7vKbyZsznIPHU2P8793/P/3/sUxTv5uBpC1dfA2mxHVPXAnvHAkbOz07Njs5NUfVUIDcEiWASLYBEsgkV0CBbBIlgEy3Rg9c0MJw/UERqCRcciWASLYBEsokOwCBbBIlgEi7tCKsGiYxEsgkWwCBaVYDmnfrakQTUi4ETCM/Xg1lP736ohMZkJfg6f3oKqwZr+b1PNjtbVPyN74gdqf1dMJE/5EiOCNYcCIEdd5GN1Q/4h24TacTd4fZD6p6XEv8weLGrHzPIm+xto50DQrchLPT0Rz5PeD1VleBd8/FJZWVrfPd02kjcaQ7AUK8OUciM8Ty1/EL92pd0HuLzzX5aq7hZVhElW+Xe6J8PD4jvHthS33LlpZcgsBxacCZ6kBYf+c9m3oOJftSf7ZoTWA7KprIdtT9MJlmlrJviTvKgZcaOa3KDC7Ut6T2ceKUibbC07+SgXldaPtI5zHw5A8QSfDl/LSbyyA2ABu88VBeuKm61TkylWQCon375RbTK+nEFLyje9VsfhcLZf6bFR953dFQIj1F59WXG2M9sAnBXwUqyAlPFqCX4Gb8N/cFfcAJhQe0EJlkdqnG2nl1oiL3wyZHjyxGtVoj0A4cJC51hYNKEEy2M0OCD2q5oCUIzUUvpILVxAij2jWZdFE4IlV1RaSyH8DN62EMk7oLFmiGoqsOA6wMVIXXV5c+g9tVOspdwL1sTS0uxKxg1m9CotxwJ8WDQXolf4/fyrFR0pXZ/C9kZfRQBBsDx4D4gMXas8F3+GVznbhEavUD9Px8LX+3JfdGoF3oW4gWCZZBHUXwqRTjkLVnvR9b7SBrE/ODVZc7ixUERt5MXFo0XV4tsJlseHC/oZlRgroFc4vwlSICWig4S9v/nYrqxwOeAgWB5fXclLnuxb6BIaKdi1aiz4E3xL7AxqAY1mDsZsCJaHKcZdjLST0ahxfdAP8Sb6gFpgiYpCfnA09faFIZTz5p7oMglYKMZlx5LxcgUs+TAFQDESwwI+EUEU+GZdKE27FGr5lrP7QSOOJb5Xy720HItLoceApVVpQd1bYwEj/X0oaiyrDf2ZfFcoQ4ZdofFoVGtXKDoT3EjcFTJuUKzZzHE9x8JkFdo1ohvBnwiWYqYZdnF6XSt0cCV57ypuWNOYYiR5x1gfk/cQK5Tw4vP59QqNH7Fnr1Ax3ySW8an2RZpu+Nf2IVgmCUuNgMV5LILFCVKC5QkBhFyHceadYLlhTFmr4aN/SseV+7F4Ssfk5wqNTGuJXUXXzxUi6+K5QkuchNaPJORPxZPQ2OvJJ6ERMfAktEXvbkAVpX93g1asavzuBuj/uxsYN1j5tpn53TMjD/rxthnejzXH/VioxuBn2Cdqxa28H4tgzbMmQx4m3uiHXiFqKd7oR7B4BynBIlgEi2BRCRa/mYJg0bEIFsGiEiyCRbAIFsEiWFSCxV0hwaJjESyCRSVYBItgESyCRbCoBItgLYb+BfM3DsS1ovzOAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NDozMC0wNTowMCVVUNYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0RaQS5zdmfcFAlaAAAAH3RFWHRzdmc6ZGVzY3JpcHRpb24ARmxhZyBvZiBBbGdlcmlho8plBgAAAABJRU5ErkJggg=="},"65":{"admin":"Ecuador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2bfWhVZRzHTyt0f0iu5gs5S3S2dPiSrAnpTCnIVWhtLigs51wrt1kWFbGQXJl/pA3Wi2Nh5ksLRAqckbXUP0Zjki+9KZUta1AxlZSCojGQgvu5f3wvj+dy1zZ3zzm/f74cnvuc3zn3eT73+/ye3znX+7fr9Ckv29R0cNWzITA1sEyHWy92/+Z56QRWyjdkao5lampgmRpYpgaWaVSScQPLthrmWKYGlqk5n4EV8GkOBY7mWKYGlqmBZWpg2UCYGlhDoOeWf/3RtCtdvdD9Td/VfclT6YSetiscNrDSYKCBoLOsdV3eqsbW2rEPF635+dGtVYs4bjz+xsi13UfnHDjQUg80Cfcsxz9NPr7xkRH059yGKY0Ti1vQzhWfFJbmdfV05syYFE3IIvFVz1QeOTSyrTWr4Zf8b6uXLPts+j9FFXk5k2fO33jjuzfsQIt75vw1bS7toKZwAGJcY+3AxFkahwi0o1yRc7kTAyvwVauvej9cPerHLb1rn87+gglGdeJBQVWBUA/zg8kFy8WrNGP+tTm7gNt36TSwgqVMJG6h6mIHHEDw1oyq+6f1vF+/40x5JWA1ZG7LefsZP6SIQLR9hxuaxiwiPqrXNccKe92Za8X05KaO2dfsBQvA0iWSxVHb8SRaOCtq6JhjxZVsCb3E9AvQfAouuqhxjDPhSb5xJJpe18AKLVjxXVsslXYh0BYFSxdHkErFC+lJlmZghfbBM5kW6bMuWyyCqOZewKRJN+pCSWSNgP7Qsacma5MfiOFO4b2wVln8FiB8CHTey9hdMuWKinlv9s18hWMUFGjH4VaOaFxe2Yz3cAxeWkTVCJqwg2Mq92aOFZh6Ons6Kk84CtAw/dWvf/z74gcULPoAUP6xzbO3t95Vs3fWkVHuccXu5lXvjCA+ACmmKHhxRSJzJ5wVL8AaWMFa/pi2eDEzBgrOoV7lOhb+BDRghL74QtOYkjL6KGT05Cp44aj9G6YvGI1quUGLF1TtL1HZD9Ezj5AXSON4xZYtMh6mWX1lXHZVe1Xd40cLuson6WJH1YoWziKOLouqRFCwAFHzLSKE26siARaKV7lgMf2zSqv3vdoDHMDU9NiJrF//QHEmt0WVsx6cVzdu80EwJTJXUbDcfMvACrAqWLSoYxXet+DISxsUqdMXz353foWriperRHDB4oqhAsst00QTLNJn3cHNXVxTVtcLUjhNKmAlR03BGrulbGHTsZKzzx164notuiaUKkIOlnYKn8a+pG7+aWHKSy4UHnyy7YPpBX/fuRW8eHTDMYk5uCRXenIW0YgcByt2J1ohC98Iuy0e+x3VZ+/YvKw002330+Hqn3o7ez39PeFYTP/n9xbffXu5pu1oHD5H9VM9JsKXHTcfLng5ASwph3InqX/3/o5b+vTxMj5dmrvrZLgVjBQsppx9HGAxHFS24vWtmA9pS3J1wQI7XQrZKERhzCMElj5CUccCBbBQUMCCqhVKix9Y1Kj8HCs6YGVOXTJ+e7MX1i+mLUynPsVTsE6dyN1500KMXUFRpLTm7gcZrwESjW1BEMFyR88cq9+ONb+2aML6q86fm5gzNZcFUV3quo21q7eVogDhtqMARISgg5U6XqOPltXvbDewEsACIIavbXzuyltv0wVRYQIOdnycxbFCxlkgRTQi44K6K6QMYTnWyXCYdgJYUosnAim8+paLjp+CmnoV0YjMIx0Fy5L3UCnTyfsF+iIy7bgI9ac/9+csnTIBUKhp0cdVPsXnOOu19vx199wCUprV6RXB0cBKC08aeDpJBPcFPZYqrbyDF94DNLyVoC/60cKn9AQpAOVaCZUzeVsreV4yvOl25MAarP6a8ei7D2RIVK1I51vWr/l+TBEAcQxSHLvKUgtY8as4jzhoDwoWA/8BeOlzQ0Pxa9bIOJO7IFJ/QhUm3q/yg0k/1dcJ3fdCcUoWx/+HUXrC12+whm6Ch+uXx6TqMQsT6bz7p3v90wTHKP/PUbxoYeEDU42Jz7lX57i/4+z2T2fgvHSz0MFVlid3k6/TjJNpz/huLuY3QEMWxY5P33ZngSMa5xLNBcjV4LpRRHeFqUyVVqp0mqlaue9BUJ0CLE3McSkFd+DOFKxcKtLlhuSLow4u7fgQSg1dwQI1PqWO5U6Pe5WojXPkwNKiQPIChy6OWlbQT7W/n/a32h6OxdHj95TKAKWDXs67jS+O8qAa38LDwjQmA4ngd6635/n8p4ofGlylzDgUkS+/ApOChbp98LNwfOuBq8czMh5KDK4OXeRwK+MW3NHjzj39Gqamg6UGlqmBZWpgmRpYNhCmBpapgWVqYNlAmBpYpgaWqYEVFXUfm5gaWKYGlqmBZQNhamCZBkP/A0k6122K1m2UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToyMS0wNTowMKBKMMIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VDVS5zdmfL2mD/AAAAAElFTkSuQmCC"},"68":{"admin":"Spain","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFlklEQVR42u2cb2hVZRzHL6m0zN2x25y3ICUpsz9UUGkUVkiBK1PDP3OlAzFsZVi6YX+c9qIUq7EXjXm3OUGUuWlp7s+9Llxobk2d3HpRWGBFgUJSLyItwhcW+LkvfvJ0LltnXjxn3zcfHp577vPcnefD7/c7zzk7kf4DY09NnCKKw8uIToEosUSJJUosnQhRYokSS5RYoiixRIklSixRlFiixBIllihKLFFiiSNQrG/vfPzR8RsGzyNn5l+I3jHUb4lePP7rmsLr0sH6zTiQ/ZdH/irvGX3NmH9+6Hs7ErN0+72O/H+0o/n5DdnHudJ/Rfa/zmt2P79hMOfND88nP59WGPU/TuRKn2JxZFJiiRJLlFg5IBXGbxNTTaPv/XKg9WzRWC2nxPJFZPqlpnvGhHOtp+s3378Fohf9f1QcmlTUrAWWWJ4xCVGQxouNczZujZ47+dbupaMaoHsM42jJJVYMIRCFKNVZUZfMW3NmVkP9mJchPUSs9Im6hXlv0k+bEc7+uGvmqFull8TK7KYgDRtxnQdmnIil975asjp2EO4pKV4+obzlwgOnbpiaSFSdzK+iB9a3zK2KFrWPe6bytmOfPrSoIxpnHFVjEisjFupYjZCGNmx9rPTP63ttv23zXY6RWCNOLK99avRCFD8kOWrJR5BYVFEIRD3k6oUWpDxLynbbtj2Q8V1x6Wfk3NzqkVg5jVIkqdXN5cvit3yc/9L2vE32npol0nAkrCu4b6BwMbRJEKVslLLqcCnAkYyD1hIiJGLZaz2ksQucue67VLZD1EEjKifabR3PPT/naGvBjlTN2q5E89SWYtoD3yS/25iwZH8r+7xiSFKh1wITb+y1no1Ptv+T9Wsb3tjdV9N1KBU52Nj+RdcL6NVes2nl69W0+ZTtBlcsFfUhFCu9YH/PvIUIQdsVC5lsEiSRWbEQqHfpe0d3Xjx+Y+X5phh0xeKqkxiWmff3XXvv6ZcQoRLrq+6+JdueOrK9d92xi1Ysdp7svhSVk5dYKJKau2HJhyUoRZt+xre3erzmFUNSY/UWV3ZM+Qg5aPOMInWVjVil6dnRZ8ch1vKvp137RNwVC9q4RQ8Ri/FRNpV6sXN65rtELF0bBl4sthVQh/1xCnArlt3etEnQjVgrbrrrp5lbbUK0qRClIHMxPmJBdvPpl14BFovFQx2E+KzqcKT/g7buLU+XjUc7mwqJWESp2vjDZU++b2PYpL9L36lt5FPSHwnOVlf0UGNxvWk/RWtu/kiLwKdCBKKIZoGRBqVYfrvFYEmUmv5aedOqmyGSMUJy8ub82qQlV4iIRWSyCbT/+23zFv1MapYWgY9YqEMyanzw7p7bT5McEY6dcbZPLUmC6GXFgvQTeywZ+bInIy71IOL+spWJgsPuvr8YSLFYWtIQcYWrMz61YrHwtNmdp03cgihFtWSVss9HoBTjEymJW7b2khaBT4UsOQtMjWUv+61YViZXNftQDTLxKUQaK5a73UBtpxvVobpXOFSxaLuq2SjFjhdi2VRokx1iufOKIRGLReXunt0B9yOWV41ln3SgYHfnFUN1E9rS/geOVyqkh0J+Z8+KPYuXoY6Vya23ss8rIUIiFs+qV0xe1/bKvgxnVc+vjNNv7xh61ViU3lRI6GUfX7a01RXHM5el4lZIxPJaYCuWjTFIxrdIYRApabNfxRYGx7u7U+ur35296pHL5mUEZ14xwKkQ8sQBSXAwr6bgdjIbnpZ8N/sIzGgTbi5fGSKxAsbhelOKOILEUiwJ1pmMDFec8P/epsG86crrGPfp+Oxj+j+52Ufwembf//kZ6hnIzVu7/kOsoL9dTm/fuzqpd5CKermtKLFEiaUTIUosUWKJEksnQpRYosQSJZYoSixRYokSSxQl1tVN7vBLLKkgSiwxCPwXO5Dgx3YRLdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ2OjI5LTA1OjAweJLFpgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRVNQLnN2ZwDs7RQAAAAASUVORK5CYII="},"74":{"admin":"France","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3cMRLBQBSA4Zc9CgqcALXC4bThBA5gNFyFM+wBVNFqjYTszpfi1Sm++bOv2ETEdNK2pc/T9ny977rCn+fx8rjtc7dMmybnxXy9KncGWGCBBRZYYIEFFlhggQUWWGCBBRZYYIE1/GzSLB0CLLAUCyywwAILLLDAAgsssGyFlcAqnJRiKRZYYIEFFlhggQUWWGDZCsFSLLDAAgsssP4DazQowVIssMACy1ZYG6wP30qxwFIssMACCyywwOr/HAYWWIplKwQLLLDAAgssZyywwAILLLDAqh6We4VgKZatECywFAsssMACCyywwAILLLBshWCBpVhggQUWWGCBBRZYYIFlKwQLLMUCCyywwAILLLBG+T8ZsMBSLFshWIoFFlhg/fp8BhZYigUWWGB9C+t9ggUWWGD5FA44XxBz7mcwZM9VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODozMS0wNTowMJkeu+wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZSQS5zdmen2JoeAAAAAElFTkSuQmCC"},"75":{"admin":"Faroe Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEAUlEQVR42u2dPWgUQRSAR0GElIKmUxAEE1EQf6IEVJR0IlcIEZJCI2eliI12NqKCAQkSSKESzyYoaAoDp5LDH5IqaQQhIiiolYJRsTLBix54ZMmyc2/nZ28nflu8Ym9vbn6+e+/NmzezaiHH1/z07/PVz0dWDV569U2pvvL4kFLFYqWi1Kn3z+c6qt2jwzc+/drycN/E7NW2lXs+RuXXlra+veX4/YmRHeXCuTXtx6cfDddLW5TXuh//+NC/wGV9qTDBqsk4WHGY4neSwaqVD1iAVdFrrCSJxgIsx2B9H9h8seNFHawTPWNv0ViAlQKsqPnDFAIWphCwwtFYyaYQjQVYKcMNMrDwsQDLKNyQPo4FWICV0nmP3gEswMrYFOK8A5Y48o6PBVgONJZ+SafRrBCNBVgCHysOGeEGwLICS5/p8BesW4XdaCzAcr+kkwAWGguwHETecd4By8gUNvKxCDcAFmuFgNXccIPexwIswDLUWElIsaQDWB5NIflYgIUpBKy8mkLCDYDlUWPhYwGWYx+LRD/AcgyWqSnEeXcK1snXpdGZwTzL9WcuTE7e9r+kU5O7fl6emxornr07MlNNUc/Z0rOZm/+kfavty3FbHyOp6gOWZ7l0+M3ysSThhqhZRNrJxM7Nu/RxKAjSnYxpguQ7+k99PJ/0jKuc98XSZL+rb4vku/kvZ2k/G47UptbeFffWhiU3dvY+vf/gaE+h5frLtDuhp9Ztnz9c2nn62IY7/SG2PRSp3oxvu9K1OkT57snWQwf369cH49rry4H21s6ucFsdilTy/3roUh+XR7qVSj4Avu/LgdAnH0v0lj6ImrZuNuVIZrIhlqN8/NezQVAPkBy+PGhBt9pUUpq8bz1qLPtKSIZZjoUN0GbaS1I3fTn6NsafSdsWmz+SpM5pR0HZdK5v6u1rIh8kt63w7c+5NXw+XAKVZ/fZTFfp/aps6uZWx2fT5277Ry3veR8zwabNCpdTHEui3oljZRTH+h8i71HIiLxnFHnPak1K/iuS8qXZDbK1QrN2pV1hTOoN+zU+eX18PJ/0rSBXzn0dvIZ0mN0QSn5P47QZuzNIycdyKkVZiJlnJEZzOOsZpLb5WJKD1wwzSJHxDFJ26bCZgs0UQ75NIWCxr5AXCABWM7Z/cT4WYHnZsMr5WIDlESwOBQEsj847s0LA8mgKmRUCFqYQsPIUbuB8LMAyAEu6pEPkHbA8mkIi74DlGCyZKURjAVZGrzxBYwGW+HDbNOdjobEAS7sIzcvGAauJphDnHbCIYwFWHnwsIu+ARbgBsEIzhY2cd8INgGWhseLGER8LsJgVAlaenHfTfYVoLMByEG5AYwGWo0Q/Iu+A5SXcAFhZXn8Ayd9Xxn4ERDkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ4OjQ0LTA1OjAwweOdUgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRlJPLnN2ZxjoJH8AAAAASUVORK5CYII="},"79":{"admin":"United Kingdom","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG3ElEQVR42u1d34vVRRSfl5ICo+yhh8UH+0UIGW0E9WaQL4kk9BAk9lSICmFsRT1kgdCLmBghan/AIi0FrdSK4A+WjTZtt9BetNRda+mHES0iJKHhfgzP7Xxn7pk5Z+bO3b0vhy/f+/1+Z+acz5xz5syZc92Z5asH16/5c+MnY5+PXHvw6sS1vlh65s2pZ/74Z/Pwu/sPbL5rW//ke/3O3bfl7SVWdPHsiq+2TYBafXPd6YHbhp6jo7iy/cLYzIbvf3j00KrfJ4fu3rT8A06/3XfP5RUf8+tTvzy8+KnP8AX6zecfeuXq/ovOLZ1+60tbnsRyL/wMpAYJQpro/99DZ4enRn994v3RvYe/G1za99jaiT13LHvgG8oT3MGv5/tfWjuw49Ls+IXJRxx+xg9nH39h56Yjf702curI8TSQHXPjM+emIbYcILOg18WcBixOwVa8xYGFVvQgsJ1UFEzo4eiOEz9NXaJ8AJgwLjqF+DTzYcZxNoVfkFN0tz6QhYAF3SOBFGV3O2DZa6w0qMnBRPHANZMEG843RxtANnjwwNEP6wcZZzq/ozeFeo2VQw9Fg+n1Xfd+NCsZdSwG3A07Shrwsc/KXNagyTiwwAe5KZT4WFamMLdmyiFrd/zlk1/M3C5pGKyszVzGC+O6YYJbLddYPj9DprFKOO96MJ1ccv+dT+7WyxSIcnJV2VlNVsOqMFVjpQBLvppDK3IFoTdzskWbkT2u2fFvFVL6qtCnt0r6WLlXc2kyahqLx1ikaTJqLmv2ycLA4tGaMM0dx9Ks5hrCSQrNdLTv6/GpW2WyEHgkGnNpGyezAlkNGqvO1ZwdzyOUswBkQVVs6yRqgrFWAVLbONb8msAKd9LWJ9NsK8UwIiVA6hOkXGP5Jm0bB1zAw5y7JgojXih2IphtLZrMeIViqbF4/2NXhXozVymY0jRWGsjg7oUZx7cOcq4uU+JYelOo1+7dtp9bMHAXBhldUeYG2caD79wyPKDZK5QAC60AxGmhAYAJGShWYCqzleRaVZ/vOnwnfP8mxZDAaIkmo/CiINMzembdb4tmr+gj72Fg0VZoCgoNanBqFRo49OLYsz+uBLf/AxOXi0+OYSSE789RoDhM0TnJk+F3cQ2K7B8MngsYIDt/bMOiN1wDncv7mR7ZMrj1U43Ln9sUckih5y2UjOvnvVtPbH9VPyJwlepLuQQ1sqbU6UUyP6hmVehz3hcy7QHLYFUY1lgLFFhgR7dTmBvNF5BQm+a84y18oZ4RdZbzDkzBnKPXlPruS56JfZc/L/kCfz6tXb7v6ctz5zpM3vPwk5LrWB7Gfk3fTxc7O31RFv13NG1J+lCyn3L/rB5qyx83X5ml6SF/t8x4w63Y9iH3iFw+VtqKtjaGdsvUiu2n5PmwS+DVWJLXahB5edHWCco6J7DLh98c88MWfLm1ctr39RPbVjWEv+b71dU54zVPpjEi/J16tHUN3p6ESy63GeqUL1ISCiX9mHy6ylb7OtslaG3Px77r02ol12vdHmi4ASxNEFL+fCyVf18eDpUEDDWsTAtmygOSmpByyf6AzpMtHT2tbUun22lvEzo60a+3Cd3LbiiUj9VLm2kAlj6lKzbpDxRpaDzRL5YixxJJf76UQFCk2mE3PhZYaRmkaRSJfhgR8kh9CY9Ih+TtahL90hIA+a9Oll7sSziWP3kzNRkNayAFMIHptNIcX91A5BxStG5dvnwsngAdMca5xGskYdPTAHykvjE2HYEPJxnrE9PpdcHDFBowYR5TMIX1BxhNhU0ZjcRoqyP2PmChFX4yRz+RfHD3jb3paHzuGjgFS+qkzVoNQ33Hv+TnCiWBUMm5QsoTnJnRg4yfa2qYYMxc4lhs/tJRRpUwzcAUZBwv0MgZ1+5UtE2pSInG8hUFoS4BPa1Ux8SrQGOV1ExyM1f+iH27ajMp5y41PEzTZNYgWxBg6mxREGmdQTNzyXjbBmQK3vrtWDVgaljpsFkVU5+pHLBy1MfK4ZOJQGbmkxUEE8p++GrOxKxoOv8HArYaS+6/2vpkvKCBnZXIDCafvW8BU3Z7/3/hlXHecxS3LSmjMMjaaLIyqzmJmYsFU2pxi5QapPr6WDmKb+c2lzzUEqHJaLEvW5+J5jbl0ExUMLEhkrL1sUpUrq8tTua6f1mborHK18cq+S86ZVaXYZ/MaRrg+Za1gqk7apDm+wOYHJoszA0n35uL1Uxpq7n8ZcHa+1i+kdZQ573OgDbnmNNv9NId9RyhgRwCkGgseSZ7zRord3KAz/F3SKhFxlJYMyEKxVcEp58+t+fiKLJ/ypu5tP/SkYQb5OdwcgMrdoyxz0NqkCBPKIrNQEGqz7/AbUQccgW+ogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDY6MDQtMDU6MDBbYKMbAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HQlIuc3ZnJTl+YwAAAABJRU5ErkJggg=="},"82":{"admin":"Ghana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADA0lEQVR42u2ZP2gTURzHX4PYYrQY0lQkS5VSpIuDe9NFKOjmoHSsdbFaEMWCCg6H+AdLQVwKndQIKl1ERKEgSOqmQkVFcVFBHFpwK4KKw3c5OROfucvl5d5n+Qwvye/d/e6T93vvd+bFy0L/0BCEydKQAohYELEgYpEIiFgQsSBiQYhYELEgYkGIWBCxIGJBiFgQsSBiQYhYELEgYkGIWBCxoFdird6fXSrXOp2fV5ZWi19bF//N3bPB9vdiePzDtYffi6Pu5MGd6zE/R1Z29edhYy6sB/e2XBHJhg0R6x9cH31eKo3v/1hZ3vhA1Ig714ZYHUkVl21fiq9ypR1Pyr9y3zTigxyNr63xpyap6V1OUPwimOvremx2i9U7l8/1zrAyZXbFanW6Ff/Q3rFP3UFYLI2wlqckVvZWLJU8lb+wWBrROdSF7LmZefZYdamSF1aqvQWxsyqD8WGV0urybO7W7cJBe+oMWE8sffq/MZNd59hjOSHW5PED+3pqhcO9k12BDespFaZ9NM2+Nly72HeeFcuhm4m/OuqhXnp6Yjq/pvaBjTpxqFk0Y3NKpV8TkprR+Nnie3R1vri1vGdweGrDqVYopciaxc0Mt65JpJgmnYft5m5M5z4VqfgyhUtec03U9mYp2dlN/ItoRdlKMzX61fUjZ3o2T0SbC/YlTxHS6a65/wc2fvao6jHaDrWhVqksZSn+vZgsqRPnLnRybG7XpV+530pI8ykbH27bJrI22vaNhihd2Kq7k3mT7dOfPU+/nZjdNN64Hdq4ZaoIPr/YCV+D8W0XVa+/FS2CWr2CyrHX+aPKj76pkejapgjtaoG69gR5V/iXImjThYp2whTB/YKYjoKI9UcR1PnOfhse7YTFL4i8K8xIEVSLIX4XShEUrRPfCSYs1s3q4uLgDT85N7BQ3fnu5PKFHwMzScVUNEX2ObfGdE9NV0YgTJikACIWRCyIWCQCIhZELIhYECIWRCyIWBAiFkQsiFgQIhZELIhYECIWRCyIWBAiFkQs6BN/A5SD8vcJxtzQAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MDo1My0wNTowMBq3PHYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dIQS5zdmej4rBXAAAAAElFTkSuQmCC"},"83":{"admin":"Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZUlEQVR42u3aMQ7BYBgG4F5BQhd7R5E4gjuYeyFbB2aj2SAxsYqmEoeQOEKlZgmLED59lv8ET96+35smh7LTzbLo72U5XfV31/I07u3ruqrSNNbbNMdzd7uYbQbDSTIv1vno7k2KIs+Dvf8EKyIpsMD6BqyIpMCSWGC1r2PFzSqwwAILLLDAevUFCyzlHSywwALLjgWWxALLVQgWWGCBBRZYdiywJJbyDhZYYNmxwAJLYv0crIi8XIUSCyywwALLjmXHAkt5BwsssMCyY5kbJJbEchWCBRZYYIEFlh0LLImlvIMFFlh2LLDsWBJLYrkKwQILLLDAAsuOpbwr72CZG8ACy44FlsQCy1UoscACCyywwGrJ3GDHkljKO1hggQWWHQssiQWWqxAssMACCyyw7FhgSSzlHSywwLJjgeXXZLAklqsQLLDAAgsssOxYLYAVl9QDWM9esCTW23YssCTWRzpWwI/jDT/h9u+Sr+gwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MToxOC0wNTowMHM4DUgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dJTi5zdmfq7vQjAAAAAElFTkSuQmCC"},"85":{"admin":"Guinea Bissau","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC40lEQVR42u2cvWsUQRjGNyQSjRo9SSzMYcRA/AcEU4QUUYQDg4KVoGBjELWwEBRBYisodiJBBCGFHyBY2AgBFayEwCFBsbCwFGs/CsXisRjZXNy9nXdvdufXPMXdzsxy8+N5P2a4ZOVFY3Rywk7bU41Dk3etV/n65Na1sTe/RtqfdzbQEDSx3vJyFLAAC7AAq3ehLfuK+hawInKsLED4WgWwCIUm+AIWYAFW3GCVn0t1F2QBK1LHsgPUnRmwKgBWyF5Fu8GXfrr59nlzhhwLsOhjARZq5Fh+4csyG2DRbsCxAAuwUCOw/B68ABZg0XlHwwbr8v5Nq6O/ny4Pn9szbeF85YNl3QGqCVgWQUpzLq9uezwxsO9H/5HB07fHNq/smrcLiDhWRO0GwdR3NGkmMyeODZ7avkCOBViFkm6NEkwCS74lDwMsQmEOjFx1g6DAksrDOo0CLELh35RcnpTWww83vNs65CIlPXBv4M7QbKdR8682Xt3xTWl+dtSoCmsF1qNnw8n4XCeA8qqb4IdfFaKGF/3ckCf3aiz1XeqfzYuU0MzrUmn98P3G9fHXagSgIWjiK7tavLLlZXMhnVelVQgKx/Q83b3JyZ8Xz0593H1mqX1wLxqCem43uJXg+oHP77qt4xfOT79PkgcjrS//0/uLrTlLzbJuOW/SU/XbDc/iWFI5nK+WadXAikB9gSVQ3ExLkCnkqR50wVINWAQsd+y/YEXjCnUFy82NBEo6JXefd5/xGxDXAgsNDKy8PSTVhvIkoaNPOs2jtoKel88BFo61hgojN2fKUuXJz9QPK36IBFg1BKs7IPzedACsCoMV2v/MAFYUjtVbBSzAKgEs8AoMrCK3r9b/0w4cC8eKPBSCZi3AsjnSCflHjwZcHAvtGVidfKX4SZ/lITQbHKVjWYLF1hIKKxAKcUHAMtx+wAIsNrXq12YACw3udkOsfSzA4qwQBSwcq/JghZmB2bcbwCWf/gGMEcrk0nVvtAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NTI6MTItMDU6MDA8f+kFAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HTkIuc3ZnMhspmgAAAABJRU5ErkJggg=="},"88":{"admin":"Grenada","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAH90lEQVR42u1dfWhWVRhfaBAFZf+Uxmyb+7TiLSe09bW1QAch7k1YYpaWkG6+QQYVmkWQUItqjMg1KXVkSRMSC00zzRmaZF+uj0mUSTlo9E/0sZFQELy/94/f5dxzd+977/m4973/PFzOe9/7nHOe33nOc57zPOeUffnNjH/qamNAL770krrub0/NHa65CBQlDqqMuwtfLa128I2LpPK0LF7A+rn8vv6KT0BVA2ukY1ZTbXYKvlra+8vlK4crlnF5CqwIRMuj9s+dO7fM2vX7tFe7rprk8gIIlPEFR1N80Wp1fEsOWNy5k+MH1s/M/Nf8dccVF4CihIUdFd/T39/4bPW035o3NZVvA5fzC07uvvIm0PH5J98vn66fr7r2luhUyPBiYDm6ONoJguw5rXxNtbeUbSzWWzx21dk3mG7ARdQZqi0e6C39fEsDWKQ5fs08PjF7ByYLnjJUa44p+CrT0OCis70lp7GgORzmKmkUXruJoHQ8iyWezy7f9+brXYcw7fUsD8AxYH1GXrussXaVJmANVre0N2480v3YvoZRjKGUhqfQi7bVClKGxBUCC/jtPJz9t/22mYc2/bh0xYa+t+65ZduZvSferJjOZmZK40shTUi2oacnt+QLSFy5xgKbsrKNgyvPg4J9z/qhgab7U5DFF0yQIKQJyUJ9dF7YUdPepkljMbCYzjv04mfZOagi/D02dBzWVimAmEI6DjC1PtG34iFRpsqB5aKxUBWuEJUAZJuP7F44f3O0ILMBKLI62AxiSAESgXQcEhQpgKV6KnRoLO8KCVBrPtG7bnHV1uo9w/MesUeTlY5mQs9DCr5kx8DSpLHybFw0lm+K5r0x+t6e60+nIFMHprdf33s8kwkMJmPA8q+xPKsLw3Dh/pfH76xDF6Q2Wfg6oyfRq6FkJAArqE+rWI0VvtICRXcc6D5YP/eduIjWbD3BHT2G3pvRue7BZX9HKxcT7gYF8AJd+vzAvgX3xgtkOil6Br2kTgpsvOvSWIqBBYrxxyBLEjiCDhgGEwwJ1f2vaVUYmY3lZy1JdhjK8Yxu/fyrozdUn41KnP4FXJzuDPOvY6Mf5eoqtYJJMhXG2MYKqslyDdt7W99F1ydJk2HYoHUqbCZ1NhZvkNunsQJSjOOHlw+ea+n/7uyxsao74ggm1BytMKWZ7LCxrAGWCDJsoIYHGQfZIeoclIOGw4MJtZ1dmVtz96N29qouz7vFwGIKUQXdGgdcEKt5pn7RRFUNpzBwIgN+5fjSoLEDFmkmK/xYwlSIDnJsZFpG/cRfABxItBKD+37aseSHyiEx/Qtv4l8yeLnEDlg/LM35sQT28PkWuSelHWTi1ngBUuK4zAOI079coivz7zC8eLs3jmAy524Q/FjsZ+K9qsi2FxRQ7PAPjT+ZzRx2wIV0EqY/TuKQ5WFDe+FrNg8t66ZCh41FwMJUKDOWMYIxdm1YPDvijfIlvc81vtL0KeCCtAW2qKCHuEXntqw5fvUYv8P/wtdiDykDGksAFlS9ny1kwMthtGrx4Mu6DAb+B4vn3HztGOwnXvHJVn/8K1NAcP/B8pHrum1e5dnqbhCmQqh9Bhbn74oiaV3bt3xRqw0aKzO5qv+uAbaTsNYTs65lFG/iX/wdfDkZesuYuwFAYRjhGd2NfA8WBtyAdgILFFObmIss5iXjTTGtKiEaC8BSYWOxk15mY4lTIYDFXp+Pn37mr/oP8Su8ODYAC+LH5IXWMRWHBFP8KvZYoqZCrRrL093AXX909IHVDRvw36e6bl97a8GEN6yxhD1HmNti7jVPiOx550mQV4joH3ytsEwxZUEmyY+FpTtvCSP4uCvX/lLbBIAFs92e0czuBnaKYhKH3kUqKa8B+VSFgnUluBscqQrxhZfWZArPLR3eEgawMFFa4WiQOEgBETgR0FI8F/zskiR9/Mr/kjlIfeXDpFs6fqIbAC8HmIyOWt43lLlFeEvH18EbNGkCXgnc0jGQ/uUfKKohJTg8/e8P+t+E5gM5UCLbhPbekGaQxWYT2pSD1J6AGWimqE6UUB02E5dIB1s1lpbw5aCBfjZk1wQI9JNo5ZKzsfTop9XZrafaqsLEv9sfmlwAmSlgkYM0/lOh5FQILAWKA5Mf/WRnkhncN2i1wzemEWR6NZYWk5wzc2xI/zJ7EAhn7OgEmV6NpUw/cep9shNWw7SLs59VG/6mjzEKYS1h/PE5DklKjffmFSarkVPsXbKiI5JRHGwsQXUzmNJU+qgOBUG8SVSaTO+hIEGBRe+j2djwiRZMemwgP+5Qs4NEPMYozJaa1cACmOw8eM0UCPTwDXXwmnXGO51HWtxRkTYfBhlfWBe3NW7OQSoE/dl2uG1KZRtKLiATD7fVn/7Fqaqlcxx38hYZsuO4DUQ3gH16gUCyLxAonPMuAZb3FSzplScpneLKk+1/tFzT+ILVlzQh0cA7gE68jImf+TYsDggOfCGvn6uLqNyFL+dM+7gCKUA9vfkG/I6f90XDPMz1TEVOhfqvlWOKCHRQPbcy45n5imn46lqttb2mrpXTfw8xJ7kj/FfMPEbsebS3B+JrHNUu8gWV5RWq4IsSB9/0vsIwNxVivIr+bk6CUHHptym+sgh6dXwVAsv+iorpoyhRXXMZX1PtTeBUaFZvId4cncunVakDllm+zIu5268CYnaLPc7U465Hieq7mcGFfTYOvsruop6Cbwqs2FN1q78Q+sz+fvsfCOO8aNDPrygAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjUzOjA2LTA1OjAw61imtgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1JELnN2Z8lPHtoAAAAASUVORK5CYII="},"89":{"admin":"Greenland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD7UlEQVR42u2dT0gUYRjGv0NFQXWyOnWsbhIUBHWJOpReMoJi6RAR1GGRLoEReaoMitK6LIGEG1Se0gKDpIiIDlGxolIkYYkkYikaqVjJBvvM4VsGlxVmvn/vc3lYVodhZ37zfu+/7x1V/FMcK05Qqcmq4iWgEiwqwaISLF4IKsGiEiwqwaJSCRaVYFEJVvD678nU3Ew3bznBWgYuv4++7vlwcrKlo7mr8P1K08Yb674OZB6cy3xpr1t/Oju0sPfWiXp8ho70nrp88d7Yu+bHt9tx1HzTwMjQLOETChZu/OzNt4v9nQDo84Fdn47N9V+vWdzd01dYu2pHbd/W1R3bjxTurvxWuwKfdcX3ZX8tHTW4Z/OzfWuA3Xi2pebOfaBGRAIH69e13sY3ueFDh983virDKFGNgNNQg20DysQlELBgM3BrI5g0G2NIS2cEZFg6/54fz/3MEB0vwZqafzjzdMPHLdvy9cd1K2JZS5DBY4NXR4A8AAv+E/wba/aparwAPRx/YuQoWEAKzrhzGFVUPAA/XuYOdm4iTA6BBaTgu/iFVBwvWi+HwMKznl6UZ1Lh4CN6JVjKbvoAN8N3pHRFXm3h6nDb6CTBsrD8IRUZElK6az+6P3v2Uh3BMnrKiZ2tDfkzASIV87okL4tGwcICgUA9bLBgt1AnkFl/VLRVtFseg4WnFjlrR9OeqalMf0uZjAHDSCssV7H0S4sTDYGFQo00W6XrdL679UUDwUpYo+SCTLBKvxrVBYKVmKLJREQkWEWESLAS7qmS6V3pYCEjLyf1oOi2m2yzkePCpw7W9IWuweeP9EucsMZuoWuKRkVUReX00SvEa1RqsqrSflKxDYttJAhi4GkttYPIUa1+NdD+U6XtWyDRQLCkRceK0ZDJHUfh9Z/ZAEtwQWOp6FhOLKxY4Tfa2SGm9qDMnAaRgmSwEMQQLLa8sb3RB4uFBVHmBARsC5NWb1AmyxrYkiptIhestbTODsWWN9ZJvQcLKsFuSbZV1sBCkjDsaS1p+FV+WT5lq40EpZ7wZk0hwx7FgIJbsZXdLqWQlkU8JNHyJ7PzzAmwNPOOrLTvHhUmDxIpJ8DyfcqUv5O9RICl44Xij/s5eix8tFIegKXjhcqamxkv1A+CnZYTKljxWVMI3e3aMNgnWFN/Iz6TCQunwUILLy4Hoi3kss1ABpiANRCnHQoErHh6ApBhGUIsmdSrSvRXp8Aylb3zQnBGKnSw4u+P0Eb7Y44N5rogusSsBFg4eEVQfIM58kATDriOUfyFKFQRYFW2anGvIq6Vj6ISLKqfYIneGk+lxaISLCrBolIJFpVgUQkWlUqwqASLGrj+BzI0XRBdYcwWAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1Njo1Ny0wNTowMOPmaCIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dSTC5zdmf5P1UbAAAAAElFTkSuQmCC"},"90":{"admin":"Guatemala","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEnUlEQVR42u3be0jVdxjHcceC/ojFMiiDRRIysqIategyKvoj1/1i5phREWx0GxWzsjAqyD/KotXoWJhERXaQyqFWEhWldheyxtJMJSnrZKfoQl4hR7yNnvpx4sRqcM7v88/D4efP54v8XjzP8/uerxGzknbuqq4J9VhcVRn34n5bWVtF28XQjfwV4fFEIgRLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsATrf4X1OO3lQH+yYAnWJ4uFhZdKDn+7qDW75efIz8dLsMIE1s2Dt5+VZ+T7Kvbl3/swl5yCnKqdpTED0xu65eU8+zsppzFQPSMbmQXLdbBq/vR7qtdTgX7KzPYkJngbS3t7awPdf/3H44tz58z4fVZij73wSuld9NXS3ZYj4BJqslbFJ5CTVQTLRbBAELcxs9PYFiic33Kzc0kDFODSXN1wq34H99fP9/fxdSxLSlmY2nHSmu2psQtifttzKnIivO7UVY8rLyIDpMgcqLYJVhjCgk563Gl/2lNnrQJQ/WxfcmVSy7nm6Mu1xNZXrUf/ieVz3Ux/ZOGD3OHXfRmj4ZX1V2mHrOdkIBu1kFWCr1uCFcKwmIGmNB3In1Q16Icd22MXOOuKZRQo0hwBRDY7Yw2L2v3r9yVcD75uCVYIw0qPK67d/IRWRXRWFJpgO6MRDx+cGdB0trJLQeE7MfHRqHNfVCy5t7ZoDHULUmSz+VlRsFxRsZirbAtzRlu3wNRQdi3myCmo2eo1uZ83plt/5i3bEFklmLdOwQphWNQh6gdN6sOw2uuWqVjAInKFnzphkZlVmLRUscIW1pux/TUsZiAnLDC9M8JTnxy8uFKeXNfsHQIsz8kL+3/ZZisWqwQ/wgtWSMKiGfGYV8XmrV6WHWj6ARZvf8xSdt6yrZB3Q2CR7c0qrz+zyrqXl3pt6KpWGPaw3j5yKoqtJZBin533PudEZSNVClggIxuZLV+7KyZYYTi88/LPg2e7wTbEq71Kvzm/dvOxFRPnNbLtyXU+Xym+m3rlAkSIf6Qc9ET6F/XNuNrvS94QyUZmVtF2gytggcPujxP5mpkIL1vD+O6PyBWmq01NBRGJ8UTmKgZ2crIKKwqWK77S4WHbbdLp0ZNPDL5PEyQbrdNisl9d0/iIy6MzN8SPnz9trmdCHtmAFTwpwQqr0w3MQH23DpjT/TFx6tQ1/u9GMjldW3mjR25n2xCJtDxeAriT37J5yKzTDa6DRTWixvTcFJX5dRonF5iWmJyyRh8aEXWMTQe7GUG7pP3ZGYsMZCOzrXOC5QpYEGFIpwmCA0xwoZ1xZsHWKjuec6eNZGuHZVAKlotaIbwY1ZmTOBIDGrYJqDpUKSYqeNHsuHPeUq+vz1BqGHd+LCnBCtujyTRHxm27rWB37bkHas77//uRZcFy0Zl356E//TOFYOnfvwRLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsARLsATrvfgvOcMijdKPpc8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU3OjIxLTA1OjAwZTE/PwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1RNLnN2ZxIGn7YAAAAASUVORK5CYII="},"92":{"admin":"Guyana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFdklEQVR42u2da2gUVxSAo8SsBk0bqlvx1WJhjab4DC6KoNg/JqUKtRikiFiQWh9JRavxEUmNBBM3SKEKLTESFWJt1Ii2P2SLRSG1S0xqxRjNQoUlmDYPjY9gq9kVevJjYLzDnZk79zXnzyEk7BB2Ps53z7mvtNzNi1vz2tftqQ2XVq9puFoQmTml48e6tY8DJ+o2LK9Iy6j9Jn82Roy24/CqYelppRMiwbbA+1veKy4pCkc723qu99Xtim+MXlz7/FpudeusksYfNs1F1NxEL74962cKfl8AljHO657fNbeltrImcayp98jAjv7yzu8HxvSGfwomymOR7QdifTXF4bsXO7bmBBvrl67ej9BgfE0MZ6Qfypxhxgsi5LAbjc2XbiwCyJJfprIGP4Kff8v/Z9Od8qoDt1Y0TC/Ydrl33xegUfxaMaZd6Mk6/25byahRD4I5U2LDq0Y0mvECUUIOu3PmUX1iAeBljvDX44GO4OUm0Oi0j8+eXP8ZalQVKTt7U6/51M307CWhwxABslXNgelvBkg5DER5eufxlt1f/ZvfW/PztdSVF9GuP0iokTSa9eTUhysf4mvWNhrBgtjyXfaY0KSj3aO3T+ywFmXhtmVnRx6MzT/XEB6fzExEik8n/3s458zqVOXzZLyMhBpJozBiw9ymLVhmyGhEua9kw6+Z3yb6f5k6NjRYeHtvzujB/r9OfhpP7v97WaQnVfisq/keCTWjRo3VqJwaRfSpIqBDiiRRkiDLC+UuTC+v//PQ5Kyip8diA2Pjg5NuPhjXPYTaqXio4HPIbal3HjdFk6hRzcEy5idryOyK8vdL9R9kHxnCyxwr26vCFe41itWoYhmLJtoWJQkyQ25LHr3/9ZoyuxqVrRr1tTTdgwWRpqIkipICNdSowmDRCJGrKK0jI41iNSp1xjLXkvSiLHt7YyjzHJUoaXKbo2oUm7qMG6eswLIGzkNRokZl7mN5DZYxchIlF43yqUaVzJ18MhbpN4wrStSoWmBZw+EeNRDlysMZiTeKuIoSNcoTLGfQsFKqFKLUSKPChv9eq1Ci1quUGpV/blSiwbv7Z9pdzCNAlDQRNAqo+U2jbhCxO8PoDE3pKkoJNKrAonD3QHjdsCA1YJURJQVqGlaj/EdO7itKTURJr9H/UVNMo6z0xzNvkVBTXpQ6zY3KlqvsAkrKYQCZ8qL0bImR59WoqFaC+7KAJodpK0r5m7p8Xr8oNPUQJQPc+Td1vUBE7DhMsTlKyapR69xmQ6PezQCKzWrW/49RlPRzlBriRdIo9YgNcptRo3DSh4BJaFFjMtIwnxVYGo7PKMAyj89sgCWDvFjBR3+kALN1rdpVlFSNWZrpFBlmD521HgSsxJczD3GcShqCTE5EsC/PVmfStRv4ZCm2edFHsjMv17HMQ2adwRmOrBqk8ASHGcu7VQzKbDvzgc4kAksUTBrKzlz2O+qeQ3UmbBLavbBkXjajgOxcb+JgqzPBmylE5S2FZWfS2dCxdXov9JO51rMrO510pu3SZC9GRRquEZVmM4WvV5Di9i9ldCbDaTMayo4wKetMZ77bG81/wC5pZQd5yDR3RrOMBLfYC1OhdG1M1JnqKpToGCN1d7xgxhK8VtP1Hj1jsxHhkK7doOJRkXiOvKRgeXgUkQ+ajXxg4oqssyUuHlZ2rlc2Sjp3hkdFcpUdVmc4xrK99QAUhs1Gf94E68mcHaHZ6OuDyPyGo/s2Jttr5fAlaVJR0m89gMyEF2FiZHx1L9wPDaDg3BlGZpeNo84w2oikW5/hanEACLJUtLOt53of6gyjDbCMsgOAsDoTO6yWuRdP9anczYtb89rX7akNl1Z/8vLKkoq34AwkvPsKo5v4CsFxCiw+wVqGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1NzozNS0wNTowMF3UG7IAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dVWS5zdmdMOn1RAAAAAElFTkSuQmCC"},"95":{"admin":"Honduras","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACP0lEQVR42u2cTStEURyH78bOTuzEyifwGaxkIaVkVkpNFhY2pCzsKBKlLC2m1ESzkQUxmZDSIOQlImbh3QiTKGPx29wSTeYM5555Nk+6M3P63XOeuf9zzpx4ntdVv7kMoWnSBRCxIGJBxKIjIGJBxIKIBSFiQcSCiJUfK6v6u3czdCjkiQURCyIWhIgFEQsGi02JyczpWstlpO3sxv93IVjo9v+SudzL797jRi952Vh2N5uG0CwdFCvZm4pnpqLh7bF0hc05lVBpEctSnnc8JN46NVTtL9GNi+q60MTocUpXDtLXw68RG3IqiVIpodLOru7XPH5cNTwNvC8glnUcrF5qvNryTyE1bNLOni+AUilh6VxPy/aekiOW1UWwdn0kfLgo2lkQI4lk3f2TP6d7BdFBsUQ9G1RiTD1pTD35lktOXp4b1Zo/M2IVEVWehlrjd9chkT5BLAMTbc2Hyif65ndmRF2xZ0GAWIGkiqmm2KKp8opYgdx6MDVrkUZatakUmhLLPxdErAAopeHvnI7FU0e6ks8y/utn829NqZRQad3Ty6kNUg2VZkIqW/rdzZ41l5Lo1zQlVFolZx/L6u0GHZLWsNm8j6WESst2g9XU/pCGzU87xZL0480rx7dl7i0IPPf2nFiRIRZ0V6zvjpjlfgAt94Npv2v/fw8V/tyOXs3lXkzdrz15fn4/R5MhZ94hYkHEogsgYkHEgohFR0DEKlYG7R9FMWwQsSBiGaOOlzBUiAUhYkHEgogFIV0AC8BPeqaO3wRP5YgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU5OjMxLTA1OjAwt1IPEgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSE5ELnN2Z0sTrNMAAAAASUVORK5CYII="},"97":{"admin":"Haiti","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEaElEQVR42u2afUyNURzH70ZjMyKyWitZatGWtkq2suUtkZdiZgqLRA2p5jZZM01vKMvLJKQWRqaEjFb8UWor/jDuZrNeDCuxicVMVtl8Wcee7t3zPHW73Xu//3z27Lz8Tvc8n/s7556TRqNxdS0uJsmRJqeApFgkxSIpFieCpFgkxSIpFklSLJJikRSLJCkWSbFIikWSFIukWCTFMkMGl2gVUWkcikKxKBbFMp1kSuNQFKsXa7zzyctn/tNCWqI8DkVhxhrG0gaZRKoVi0uq1Yv1T6ChxRJr5WS4obIjxTJjaRSJtT5xQ0KB8/HzhUG66fJ50Can+VnuvmNpa+qzlfbFiBTLwsWCIv17+gP7GvTxy67P4zo7Kp0aw8p7T92t6j8f1eT0IKSw/P6a/PzsKJSg1nAcECMqyH8Ua0zvmfRQjlhQJzA5bUXobrsnsS5uyV6RCZO8DoIoQS1ayhJL7S9Zs/1lbT1i/ckZcsRCNtp8O39ibCxkmtwd12e/SpQs5kPBhYRrcvKWarHM/MhmpP8g450DjYJYWATx3B78fuu7V8hJYsZCCWqlvVQsheYllgmWQmNPkJHEErXAXgoU9bqquWS3s06q1Gu3s+lF98T2FGtQLHWpz7TXI2oOPA2KhY15TnXcldwbW1p2+KTU6PyfO9T3glCn1b2u4HDvhyxdSXMhykMnRNQcykMvRPjq9sm+JUCvWPpPy9RdSQ3nqsoYHBzXMcTF6fQN62FmYo5NQw9eOSRoj2jsrvgJReZnLXuprfWYvahNuxJ6NaeXXY+MkhK1aIleKEE0US+MaHkzOeNhwLujD/TVaqq10wYWu5ojX7RNtfXzVtqrIyL1R56vuMwVT4rq2TgTYmGyIEp0il/V6vDateFLZ3c9nXDxu+dHECWohVjoJWYvUSwsl8b+XGONGp3nlCTfDH3Eh5RPw9EMR5Yzrvz4+iiKBZ57k/+4rFSUA2LdynTz8Q5smuu907GgYf/+O/bxIEpQi5bohW8qoon7Nowofz6VzpW6eVP6ZpVSY+wBjE2lLwyvWcwo2D8lJu9esLxJZOms6G6PhfWrNjX5d1Xmpbg7lOEZhGTSXogGsTCKKNbov2BTUWOpH8ywWNiAi8TvPghUpTthM/0Z1MHCh+USRAlq0RK9EEEaWSqW0q8ExVJAcT8x+mIho+D4IObRSdt1A2v3pv7yCY1vTur16hSJvAWZQGgUH36swj9W2h7RcHAqzViWqpH0bVpdxkLWgRZ+2oR6xyKRKBdVk7YBUYuLHX3tEQ0jWrZYXAozdn1bXjSvFVc0OEk/4Hmz/8hdEBc1uMwBIc3f5z+10jagGEe8CEqsCep0z6FYFi4Wfs1BL1B7JOxtQCOetx1fUjdnOyjWQg48G24vEuUY0Ro27FYqltKjDXUHHOpGoVhU0GQnQxSLJCkWSbFIikVSLE6E2Z5uUyySGYskKRZJsUiKRZIUi6RYJMUiSYpFUiySYpEkxSIpFkmxSJJi8R9gxhR/Ax7mO7KDJf8NAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1OTo1Ni0wNTowMLSaOBsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0hUSS5zdmcRzkmfAAAAAElFTkSuQmCC"},"104":{"admin":"Ireland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA+klEQVR42u3asQ3CQAxAUV+DIkFx29BkgIDEQMkooc4crMAA2cVsQAeSlZfCEzx9ne8SEet6m6rPbdwvr2uW/96PbcxchtYz5zmi7gywwAILLLD+NVt/nu5nsMBSLLDAAgsssMAC6zus2qTAUiywbIVgKRZYYIEFFlhggQUWWGCBdZSt0D2WYikWWGCBBRZYYIEFFlhgeSsES7HAAss9FliKBRZYYIEFFli2QrAUCyywwAILLLDAAgsssH42l6F1WyFYYIFVgRRYYIHljOXwDhZYYIEFlv+xwAJLsWyFYIGlWGCBBRZYYIEFFlhggWUrBAssxQILrMPC+gDB6+rl3wSe9wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDM6NTMtMDU6MDA9HUf5AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JUkwuc3ZnqQAuRgAAAABJRU5ErkJggg=="},"107":{"admin":"Iceland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABIEAIAAADffhsNAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACC0lEQVR42u3bPyuFURwH8GexWSULm6uUTLpZyGBReBMyWSWD1TvgBbCYvAsr26WUlJtSSpGUv7k/wy3UFSfn8DnDd7nPc07PeT7n9vQ8v1NVVb2+uZlrju1s7PWfzM9tj5wOnJ9fLz63tfta8655d7w7NTo12lgfXBlcaTSGakO1D7L1axwZZ7X3Ez3HKDFi3nNSSoIFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWFiABRZYYIEFFhxggQUWWGCBJb8HK6Yy3+w/mb3aWk4P63WU/GejmIyVml++3ubI8a6Ftd3Js6OLg5vLFLCi5xilfdxcZ6aMrGK95pxx4x+mH3ufVlPAip5jlPxno5SsnottPwVLS9HA0tLAiokuMW+b+z37Pcd9E90T3R3Bah0ZZ5V71aVkFeu4yGxBOVwanhme+ZRUW8aRcVbBV11IVm9rvdzsgNT7fy+ZOqsv3xgpO0iwJFgSLPnvYf2NB0YP77k9vHvdIJO8bvCCVCZ5QeqTjuZbIVjlwFI2o2wmSdlM3iVjv1Xop1jv24V+SpOVJqdJmyk+gGUrhF06dumABRZYYMEBFlhggQUWWBIssMACCyywJFhggQUWWGBJsMACCyywwJJggQUWWGCBJcECCyywwAJLggUWWGCBBZYEC6xfzRfU59qWrS50MwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6MzUtMDU6MDB6fmA9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JU0wuc3ZnYlz94wAAAABJRU5ErkJggg=="},"110":{"admin":"Jamaica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEk0lEQVR42u2dQUhUQRzGB5K0DgZb6hJdlVhMhLpEh0jC6ujFQ4EQBYV2MDp2KQzL7ZJ58BJYdClUKikoyOgSRSsWm5hZ4qHwEuSplDAI9lthZJpt9r35z8577/8Ow7Lq7ts3v/2+b+Y/bxS/xfyiEOW2S1vzy1W7h25NNNWdPrJt6H7jqT0rA/cyd9C2tmazmQy3/rctC9lNmfNye7jj5mDjL/QsejkYISLYn6Fd6Z0bEWLmem6t+v3V/vHOhu84LRUynDR3pJ9ItU0PLje19S+Mp9K1H47mLtbUrS7Od4tra+2fXwVlIxRYajvbOfWouleGTP4A3J3+wITeQU/NpHKT1V8Bky0SxFrzl9e28WLIfIYJygSfKSrT0kK3ELbAwquJd2NPptZNDU9RQKbaJUPmBqkNyiTDFMLmdDCBIhAldu6r/yHEmZ7jfUK8GX4wTAMZPgY+EkPm1OaIYcJjkAOKQNSGoyG1o1WIs4dOHBQid3uigxSyzfPbxQhDRhHAEUKolQkwgRaQY3TUnUw9Xv8zvITKKZ1d4jIxZObKVISp8HUFUnYzk6xMZcOkO2QlcwMZB3+TzOS1MgWDjDqT6SBLjpL9R5msahK5MoVXMrpMhseYvou3ksmZSadMEbA5Osg4k9kazVFkJtnm4D8eweSDkuFy65QsKrU5l/NMniqTz0pWep7M/8xk92pEwOYoICOfJzOejKXDTgeTrUKv+aRlDGEqe3RpqXapRl03k7E+2FziYCrbLgkK5BSQGRV67cJUuDKxykzxC/7BMpn8O5WqzTFMXiuZyeiyYqM5Vqa4KhlMDRiZ2Fz48R0rk0+Q1X4aF88sq4UCGWFtjpXJt0NdT6YWguyWlTgzJe5Ql/oUlYysrMQwJVTJqDNZua0ME74GfEReyWCXcwcmO93q0+zP5y3r784wxRCsnr6ujFuw0OId8e4MVkysEN2JuiRdDDcP/jiTc/u7pv950wEfPsOEbtsAE9m9k2FanCHQZ8giAJPdYrasOnQlGobMb5uzOuKT122i1EO43EX6GjBkfsBEUTcs3ESVz7xdrWm/8nFsb7q4Ah0tnsFPqYvNDFn0YSogAlz6Xoy+TI+q6+jVG0RVyCwHf0XJOPjbDuCWbE5dAKgqk4qRyV3IwLEImXTjKJ2SMWQBYaIY9utsztZCPx1kbJexCuByi+hdrjJZhowik/E8mUuY5NFc6czkctMO8uCfHMhUmOiKwRQ25wgyskyGqx0Tu3SjTOqSYp0y+bYPagXsMrqjS6cweaxM5mCZQ5a40SX11IDO5korU/w2UpPtkmJhjxd2qVMmu6E7/DxT/CDT2SXFzjOOlMyNMsXD5ioV/KnnyaxB5gYmOUMkU5kolIyiuBRqMlY7NUB2q7sMk+4GUYbJZDskN5lMhUyrZLsup//QFHrVTcNkm8MlYJuLrpLpykogSuQvPD1Go0zmNsf/b8f96NJyPpZGlyDKGkzyGktVmXjn4wSNLgtEkcyA82guKvud0tUuedKSIdMu9akAWN/a881Vl25seXi3Pl16ux/uwqiMK2UlQ8+il4MR8heVjEq3jdVQzwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDc6MzItMDU6MDBU7uWwAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9KQU0uc3ZnIMptXgAAAABJRU5ErkJggg=="},"126":{"admin":"Liberia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEnElEQVR42u2dXUgVQRTHN6JPyaB8CSrJCinqalYEReAXRuFD9AkW3ZKiS9kttIcIhEISLNLKB7OCtIJMI9EHbwSlpWWRaORVVDRMQSswDJWe0qDTw8g629md2dvOveflx7J39uzs2f/dOXNmdlbTFkf/8CQ7gZFVy13uyMpzYUUbIurq57THdNW+mDsjdg2wLmt2icvPkv0Vz7/HTp+1ce17ETuyGKz10UJNWM65hcFdH4WFhbr4P24y/kdOsoN3K1jWk3PeUKuPQ4Wldw1XWDyHIijLjiwGU32kCSssPfa4N07EQtRV1zJP+vPKhJ/p4c0dSc8OjxLVpQRhbelNc1+pAIrYWf0mddHFhwPvBtcNDU/kjXvHnwx1DlePJcA2S8x+3raIHX0Za5ZDoT4ShHWrt/x1Q1Suu3iHr0eusIjqUkhYC89uOpD55eNIR1h/8duYD5c/VcxrX194xisirG95QwMjqXRjQlpY233HRgpb2EeiSINIwgohYa1ITEnJvhC/99DW/HQgG1FBI8iau9F731+bxJZhj13ZsflVVjjvXPCrf0FRY3nB4IN71VUxRHWpYZ4iT7vrfW2PMCEbL7gr667Ja/KATK3lsZRJNuq6645IewbGP2bTDRBLQXhuLCmW0KhlR1w/Wb0fE3tJEJYD6Dgx/ac/lekYa7fHe6m41bjv1tXR+/jrBERgsjLvRLVoWljw7IE+IE9Y0HSaTZlOEhab7dUPPhhnfnnljbcxJY3PZbzHrB28fcy1y/UP4ro0a+lQtkGEJk+/B+IzvGUI3lt8uSN3dvX3XVtVerBvW37a3THYZgn7WerLGx9rvL+vouB2afgU25xjeXXDl4Gz6M/F7sFfyxTn4tgX9w/vukwLC2ImEFDDr2ZXzzSQGjR8TQX+0s958GvGkZz5ZYmUbqB0A6oRhGYOEg0Q1LNllkYn3DyfBH1AIL5BhD4jCSvkhAWigSeTsVzgVwjz9eKjIR0SVkAJTyy1hIVPvqhlJ6iEFVsS13ni6MucnYVed+u+Pd9PZxLVpQoT/YgK0qlTk/FT+jHTcDE5Ibx9w6m9/yjJyyphaiJSH7v9o9t2trDwNwx/m/EuE08wyrrZsupjn3/0woJemxMIMVbN9MjG5KGGhiVJ8aeI6lKDaXrO4WhXW0b3TKLq1CjjQrQl3UAuIJKwiOoIi6IBoi0xFvVfiLb0CoUSj7LKW0sMYiadWUshkh1Matfwrmk0iZYoMredLywa2yLaMVZI4/BEW2Y3WJvH47Tyas1VojwWkUjCIpKwiMEvLP07erLIewdQApm35OyrP1GEmul3ds3OfpRl31rS1ey7yCLvQFu7RpOJR0fUB3FGSpAS7Vm7gVxAdKiweCvDyFoxxpp9/ax5dg9vHXlrCxLJXRtH3J+Ykvb5B47VaO05oi0r+lHHmEh5LCIJixjiwjJeslb/5QKzZawdyyuJ/z6CHeXN1lPW9YrcF7v9wztKo6++EG35lo4dX9UKJpI38F8IY6mZ/g6g+MIVgbFjbYhJ/LujzvSP+BCcoWdkCCuA86Yduoa7uCDw/lHEJ1xh0RAEUe44wW+yNLeTvVGNVAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTM6NDUtMDU6MDBT6a2dAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9MQlIuc3ZnJz4qmQAAAABJRU5ErkJggg=="},"137":{"admin":"Morocco","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD/UlEQVR42u2dPWtUQRSGJ2AwiBaCWJhkZW8+NkYjwSRgNGqToOkUwQ8ECzGt1aIWqcQmiRaKICJRLFUEW8EmFuofEPEHCIqNKFpooZB3i7OczDVxNxHvPM3LMnd27mbm4bxzz5mrYeF1pWdgAEWbq4EpQPP12efuC/0vAGsVJwsFLBSw0CLGWsBCAQsFLDbIgMVEoICFAhYKWEwEClgoYKGAhaKAhQJWMfTh+uxx5YmU2QCspunUtsqV8vTl0P01u8FsAFbTdHRi6FrWKaVUBVhNM8Et3ydae+ekGCJgNUFlfy3zJ54PjElnD2cXux4wM4DVkE6e3321fC+Ec9WhFqlamBnAaujYT2/rvs1dGy1YavlfDPFf7QgBK6qyPNmfBQtDBKyG9PRc/0L5adhwdnzwjQVLLbrKLAFWQya47sdkX+9tG710ldTDqoBV1Gm98z6b6XkkmIRR+93997NpqUVNPcGIiLWCPHvNBI3x1czRGKJ6MmOA9YfoK1WG3UYmbdWltUi2CBa5+MKCpUWtHu/41fnlyKm+kdKw8kxe/dVYizVBfVb7oZ973mY320YP3irPqr3tw96x0pTa1Sf/N+T/Nv0VxcA0FKnwouWpbbEjT3N17a6lbpO+2G7j0xLfit3FtizjqvAq0o4tFM/OVIRRXS+2wBYdW66xGKlF8cm2eLDsCF5jv0G/sKi7tMLusWaObb+ehVrKwMUbWdjJT9m7zjMCUZ+tCdYW/lV5vnRJfaRCYWt1+GhpkzVEP5ra/d31q4qdYi345n0Ji4wssEC0fWIbc7/B17eElEbzQKuPLC+F8xEhnSc+RRobk2xkKn3b9bL9gLWw/GN9umoNUSN4C9Yddfd0nh9DmhVAv/x2j6V9VX5c0UbbjuOz87qqWEi6IYmcu6Cp2ZnbA2n/lL8H0lX19Ghq5JSPBCadINXCj/RUxtvHYhZpDdE/dfpkhEbjlGkg266NthDxoNgEqX9yrEtSUJwGrFixWdZmn+l8Nsua5o7BnR87ShSnAStebF7EQmXmfItUi5BST4rTgBXNRQkXmwKNIWXVvm9o+1OcDimboE0W6LPikC/FyBx11ac91e5HS9kQEwWrLr1popHPbOnkgn3FXtnz2Fl4a4gpvzkd0jRB4RIrD+fnyvPz+FZTNsSQZu4qdvZhpeXhuspg5PxCmjmtREs63rYayZXbPL4deaUvihUptiUHln2zWUamZIFd1OUvsP+Wt8g035wOqZmgbMuWa1YjTmhk+25PaoaYEFg6ZaC059okAnQX3TG1Mw7JRay1jxyKiEQsFAUsFLBQwEJRwEIBCwUs/kkjlIiFAhYKWCgKWChgoYCFooDF/wcBWChgoejf6G87kZTdBU2ZMAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTg6MjUtMDU6MDBtZl/tAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NQVIuc3ZnB91TgwAAAB90RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgTW9yb2NjbyXwiYAAAAAASUVORK5CYII="},"142":{"admin":"Mexico","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE90lEQVR42u3bX2iVdRzH8QUWTqWyG1swpAyNmiip6E3BLkqtySb+IcxMneLQBkt0obvQC1Pntqbz74ZWQlu5OYXNuZCmpRdzE3XaTMdoqDlX0xbkVijoCfY+Fx84LBS9eZ7zufny8DvPec6B58X3+/19z3MSEhKzsqfOCHo8fOPoM4frI92Rzsil4MaeDVXJNaXnWoYnjR4b9JgQDljlo5pTji83LMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyLMMyrCce/0m/03GrimhYhvVYsbfgr9FdI04frXmh8PmazKKC7FyNZ3rqiqurnxQ1wwo5LDB1J124XNt6Ne3Eip+mHnqQt3na8F1/L902eUnB6rnnxr2XO3HagjHfcQwyzjQsw4rG2093X78R6Xqts+rSGs1PYPp+Xv6ajJPQYaVievbiSd0fvDu5clTDnPXjR760d+WqtLXj75WMyNk4a8rj8DKsUMFqOtJcVjns6thrn54tI0tBh/ykx+QnVshYi7ZP6ktOWlb+Vsorb0MNZF05V8qaZxpWXMNqzWjZXN9+tu7U9d3v35zY+HpOQ/uOQx3LfuaYjAUsAGkpVFgcE8lej9p7GVaoYHH7tQgSoQYsMJGTOAaT5ipdgR2tvWHFEazevs6+Y0PBxAo91vHKHyP7FlIQta8CSuqQN6aMvE1fpc37QE09eYsezrDiCBaA2q60rWhKgRS8iOQq3Q9CCl7QUUxKimP6rZaGCwvPdBhWHJVCSNG8U/60LAKLVh0i9E+gIRs13S1KzT9RNGRle+ZWSHGmxoffJxpWwGABRVtpjtm7kVGApW177Q81vcVdMNK2HVL0VbwXoPwbG2QaNWP9f1k0rMDA4kZChNsPKYog4HTcAKzzz1Vfnp2o0yzNQDrBUjp8Viwv1rk+34RSa1gBhgWmPbWl05fv5tYC69+Pv924/3xsxoIUEWTAImOBKTYC7tjvjfm1s7k+vL56aueBwmJW4Fv8ztbMuYl8K8MKMCxyEo05DTvrYMp4teRUycyi5PpvjiSTReiHyGew0wEEULSF1+wFPs7h+kpNp2UDzbcMKzCwos14PynNE78s6Hz55sUJSVtWF1YQ4cXkCVKcQ9TCSo9FJBfmpn/YM2MQUYuj9nbAGqgIGlYgm3dIld+tuP9JHSVJeUEKXm/uXDf/82fJZBpXLTkwv+IijCiU7BnJZDAias6DFH2VlmPDCtWukBsMLy1Gd279Oaw1nSuADEZQezFv/dANWcpuzrpNgz5LZEXZscKxji0gxed6VxjCORZliC6HwsTtj8LKa/3t1xyNZCCoEbOrv/zi6z+0dBLhxau8V8nqrtNzrNAOSLnZtPPkMLIIIOatbWy7tpgYbeoHl36UlkskV7GzgwvvotRyZS1/fIquG1bIJ++6+QeBPnYMF4qawtLIqxCkV+O9ZMTYK3vyHhewdKdGAx59XrS/YOk8HSgQOZhamrVpP8c6UNBip6X24X9+NqwQPjajxZEWW/MNaBh4MozQGTpn6o5Pn5jwg35+5j2aw8ABJrIOOSmKrP8nZ3jxauyPRX7m3bAe5Yl4+fFHR6b++5dh+Q+rhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYhmVYoYalz52GCdZ/AyERbDpEd1wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE5OjM0LTA1OjAw6Hk/+QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTUVYLnN2Z9b8CTQAAAAASUVORK5CYII="},"145":{"admin":"Mali","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNklEQVR42u3cMQ4BQRSAYWNFoxNH0LqAI2hUKoXCBcQFRKJWqjQKRyDuoVWpdAqFhqxTTNbj+04g8SfzdmYyqdM5HPr9WnCbbbt3242Grdlj836Xi7SP9fvTuTEvj/fLad58XrurSWsa/R+p10BYCKsyRZGW5TjWIiisAGLNWGXvtU4DYYGwEBbCMrxjeEdYCAuEhbAQFvxZWLYbhJWF7QZhISwQViDuYxnes3Afy/COpRBhISwQFsJCWAE4KxRWFrYbhIWwQFjh/N7BjrC+wu8dRQsLYSGsCnkfS1hZeB9LWFgKQVgI69+Hd2EZ3rEUIiyEBcJCWAgrANsNwsrCdoOwEBYIKxD3sQzvWbiPZXjHUoiwEBYIC2EhrACcFQorC9sNwkJY5OQZIxBWHN7H8lWIr0Kq8wHqzHuU9PMQqgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjA6MjQtMDU6MDBBJHKsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NTEkuc3ZnrHPmzgAAAABJRU5ErkJggg=="},"152":{"admin":"Mauritania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADqElEQVR42u2cP2gUQRSHrwgKCoZoQDQSiRIiKApRELQX4QpRLCIWBm0CgqLRQkGwCIiCBjVwopImJGgjpIgg2BmRQBpLU9iJAREs7LX4NU+GDZu7mz3fzNd8xd3czM7cb9+febNbq3VfHD18BMI2kyWACAsiLIiwWAiIsCDCgggLQoQFERZEWBAirKo58PTqngOTIquBsNrGG88nj/Z9F1kNhNU2vut73dgyLLIaCKsNHPpwbWp/ffXX+66uaVGfsDIIqyWOPXvwsP/snw2L32rDIg4RYZUKyWWBxPATuT8rrPlzc7PdS7ZNyB2XLv88eAxhZTr549O39g2dl3Sss7O0krIsaq/e1DPCIoqqv/o6c7rnSpGMylA9EIEhrH/Yc2bs86Evip/WtlWh3dKv1AMr6UxYilqq2aI88fvOi8GtZSQ1unhvYvdqNTbVWdzmKx7SXx57LMmljMWq5no0irO4zcuF6s9W8h97rDDeKgrn1bKaLY9qrGN2wrp9/fH4zoX72xq925djb0CsLLyd3XhT0rFZns0i9a1axnbQmrVWAGFFWdzYRRU5HVkmjRhGNpKRvlXLkUcT9YGp2AWl2DdV1sKKXVTRKOWdjlrGsyW2oISwIrpCOaB40UZzmVe8jQabRuAKo9fsVFTJYV9NM9Wsq0lcshNWuLeUdtnERntVbm1kJywbbVSZ6neKdsvD5XEdX67h46c3TzbP2eV2dh+X4KmTd1f2jthbSLN2VjLyteiN8ZczvT+sQ1zun9+16UIap9E1C83IztFZPuhRWEVVPAnOe6ge3jYuoyuPwgodovs72+yfhTPS1qjLcxNpHBe29LXfs/ZBHWdbDN6FpW3MMBaxQb1swP95r+uqiqyUjR0dH3H2G5eoQufrVGf506qx648Ia915YkidQZBbqd6GaUSNbs9NFNF7IpKIsJSiF4XzReGwanDxHI16VjZX5LJDahaJnJpP6XmbMvYgtGSyEBKBZFreqqmlfqUeFDk1dyVJFanSq6+t908N/2BZDkVCkp3kIuoTfauWrY+YXv0gwad0dN+Xd0Cdoq4w2VJ62k85t/60YAxm8QRiDqeaymdk8djZzBRhRbdhzQXXrYhJI2b30racXwci+6ENiNalph505lM9Z/0GQB4Gt3viys5UbbTZn33xWpgt6le8tQFhNRmrsQ4ICyIsiLAgRFgQYUGEBSHCgggLIiwIERZEWBBhQYiwIMKCCAtChAURFkRYECIsWCn/AnjY6uinE3twAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMzo0NS0wNTowMMoLy5wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01SVC5zdmcN39YWAAAAAElFTkSuQmCC"},"162":{"admin":"Nicaragua","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADWUlEQVR42u2bW0gUYRSAFzIMkiIi6UWh8iEi8aWgjCJ9sSQRChSy8vKQDyXZiqVIRmVIpkhmRKktrqmlggZSVkhG6paUUWooiJel26IimaEgusGcfZhhHO1iZM338jGcf3YYZj/O+f8z/1gslsTo1iYIF5o8AohYELEgYvEgIGJBxIKIBSFiQcSCiAUhYkHEgogFIWJBxIKIBSFiQcSCJqN/S/qTVzUQLiwtg8Ejtskx+ENcNxQ44veP3cNfumeL2+2ecHdADR1T8a7twpln47GOiumro8P1K6YrRk7ejVLHPWfyxHRELK1MZRMn3o15pFn/peFRlTqikUlGNw81lEwgGWIZUp+TNOcoGnkEUsU1wikKohdiebKUKGWYdZRI8+pu7+q1QkN1JIcJTa+XqcWS/GSYpRQO5zqv2wKuuEq9UjKEEjEspuriiFjmzFWaEqY/R4l35thnUodrI/Izi2OEEpn7V8y6zCqWumwZnOP0cmbW+hRmtF52Begpo3PnQjPnLVOLZfTHS7y6uSfakZQT2mObqi/eYI9vtQolIqNzXwGxTDphn6WcKaPtAz29tiPn4hqXXAvPSnBsdYwKs1+2dQ/EiVjCTj9nYl3fLCVPrmziibzFzNN2fUb5dMm17MG23MrKewdWpt4s3RS8PMt+v31Lnhwfu1V4NMzXGmSPTJw+dbbGmh5a3Nz2reLM11Wjw00fZhHLaB6GWP8t9Ws3Ja9Uva1zJnTFJlzICdklGiX7F9nCItLcZXl7+hMOZgXt3S08lJG9M2rf4agCe8bGijWNgQVT6sykabEiljlf2kik/3zvG3tkyvs7O0p8T1sfBjXbL+5/3tIdc+Pj65Cuoqe2vqVd4R3Jg9VdjyUiZTFtvOl4t3dSUvnk7XK5gtH1EctMMy3pYCkFS4qgrPXkWE31KlI/qv4V7QYapB3q930LJYHn1ZAsDmiQmrogKmJ5VPg1vdT5j1c6iKVvQKiL4zxyqGZRmixl4pUgYs2XdXw+v8hvmWWrjHrDjMKfEBGxoKYLperRq5sITM/nEYsNx4YbefWbeiWyGDYoL3ryMQX8Mx9T8KES5LtCiFgQsXgQELEgYkHEghCxIGJBxIIQsSBiQcSCELEgYkHEghCxIGJBxILw9/kdY+Ag/wUoK5oAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI2OjUzLTA1OjAwg1g1fAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTklDLnN2Z4fmdUEAAAAASUVORK5CYII="},"164":{"admin":"Netherlands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJUlEQVR42u3aTa7BUACG4VPbQIIJElZgjG0ZYy1txMjEMmzAQhh0JEJKD9p6vsEzuJGmPXnjJ7khTTvt0ZCMa3AEFBaFRWE5CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoX12Cx0k3Hy/esUeX3+mlh32IxzLhTWeZrN9isyruFi9oEJy4RlwjJhmQnLhGXCMhOWCcuEZSYsE5YJy0xYJiwTlpmwTFgmLDNhWWXD2rWOk9OBjGsYJMtke2O/t5hv1vd/f9VY16my1XnG/E7uLX/P7z3jwxuqi8+PtdkWCetXBofr2YXF2igsCsu3MWFRWA6CwqKwKCwHwSaG5feasEhh/avlPwHKX0FY9I5FYVFYDoLCorAoLP/oQu9YrINXxJp2iizafvMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI3OjEzLTA1OjAw6NBQuAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkxELnN2Z2ULOOIAAAAASUVORK5CYII="},"171":{"admin":"Panama","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADyklEQVR42u3dT0gUURzA8YEIIhCC6pRIl4qQiqi0CKJFiCwpCkHpEHRoPRVEhwjcrCBF0A6BggQRJoaXorQCKURC0TAryyRzqdyDmpsiLokaVvS7vBh2fOuu82fni/BD3rx5Fz/+fr9581aN39G/XzGidZxriNwc+TiwkF11tKqvf03W7oNE62iABlhqfPdqde2OamAByxWMgOUxWMv3g6cUkrHosfwBy7tZBFhkLGABi+gJWNGS2Mn5+m9NP0Znc4Dl3adF18G6E+5c+T1PIrDIWCmLRVtvbxjcLBFYwEpBlPKXdeJy8HW5RPcURJ4KPQzr3qWul+P5xtqSws42iTKSThnLPygNO1tyc1SvSvlTYclIvPnmcTKW72A92fj++OSBgp6a3IFYTnFFe9+EOaqkJEpB3HbqWv/bZvN8WU1WpsfydSkUBPEY6cdNF0LX32TYQ8p+WOmREQ2nmvRge0NN+KnkJH1Scpf9TT0ZK9FfDMPZjdCroeaFSJk1JsEnM9luIGMlUBx1cpWdhQ9YyRdrh2FJadMvguxjkbG0SqE88aktuexdSZQRuSozndoyJWN5BpZaBGXj4NO60e6ZAnWOjMhVZwtiMrBSleG8lSkdgxX6+rg1ki3RepNTbfOdauHTI2PZSdMxWOb8tHx3+ROWsxmOg35LhEULDyyeCoHF0WRgAYu+ClhkLGABC1jAIgILWG6F9evZRN1Urj9hzV4M5w9XqtsN1rHvcMaunR1qK/3fVVOjnehMt61j/t6QlyrqCxPziHlc566lzU9+5UTXjDdHvVp15UH5YMvQePWe+u6xMxVddcNE62gkc0TYP9FtH0dzfwQWsIAFLGABC1jQ0YXl1KEdYJGxiMACFrCABSxg2Rd/Husp/xCQCCxgpSyOPC9bf+uGRGABKwVR3tsOtuybK+od2h7oOX3ObW9ygeVJWLHJjkBvpnraQkaABaykorzoVY+pyAiwgLVISz65//6qluJ4UYqgelhFRqzvsrPNB5brYMnZr0hmsDH0MFXH7qTBl5WB5etSKM14dEvt3cbSpX2eUe6SEml/aw8sD/RY0piHx47MBw/pkJKZzrbzwPJM8z7d25bXvUIHlsykeQeW3l8U+1cWdWDJTGABS6vf+tJa2HS+1Fzy1BIpfZjMdHbLFFgegDUz3R/43Kxuh8pTntCRKCNq2y53AQtYixRB2amy7p+m9j6qfHFWZjpbEIHlAViyvamfgdQtVmABK60+KgwsDvoBC1jAAhawoAMsYAELWMACFnS0/vUmsIBFxgIWsNIx/gHU/GRvf4c2+AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDMtMDJUMTg6MTM6NDctMDU6MDB1JVX6AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QQU4uc3ZnpMTUDgAAAB50RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgUGFuYW1h8JV2UAAAAABJRU5ErkJggg=="},"178":{"admin":"Puerto Rico","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGRElEQVR42u1db2hWVRi/9FcMc1YskSn2RffBgloKK8vQhLVKgxhCJIrkhxZmUENRK8pFNQbRH60kCmOVZIQRuqB83xWUthVBRZaDSmIflFa2ZG66vQve3z6ccXZO557z3Pe9d+/vy8O4f87u7vnteX7n9zznudHt+fqr9698t252VcPDpwvRp9HjsP1/RU3R/bS0fjaKau/Z9M+06lxDz/ENDzUsqm5d/t2H01fM+5YgoxUAlmprG5fnjta1dV6z9YEHf/v14ujyAkFGKwAs2AuXrv7577ZlvTflD9QhUOIGwos2CFiqvfTGVQ2nRppW1D2957mvWmfcu3CQL45WAFiqBRt77J3a/m1Hjj077bPZ8/kSaQWApdr695Z2HLrz1ZNzL1ozXWVjfK20QcACD0OghGxxYNFVP966nWyMNghYJjYG2UJlYwRZZQNr1qru099LgUyVLcDGCC96LDGLcAk2BtmCbIzAEoaXKlt8MueKhUu2UHqtJGCJBkS7bKEnkTgl9FgCPgwWbOyVN5sWvFw4ta+9d9dLQ2/s/mB3B212bZmBpR9v3NJ63+Cmw1U/3DwyPPbH2J9jo7RZtGUDlt1e+drajQNV6y558YXBnb/09H0+mudUVQawrDzM5I1ij1+0c/ZuaBl4or3w0bzhW/pq+p8vNHPasg8sByLvB6MYdynPsGRxy9ozc9/f+uXqc0P9MwZGCyc4hWkFVtJrQJfxDdfo4FMp/5p97esGq49+cXzWSI4TSY4lDFmwsUcXv7Xx7EGysakCrGBvN0lA9BuzeNeCPc1P/nvdrpOdNcN5Bsq0AksoRE6AjkLJkw6pYGMfL+tpPL+D0zzFQyHCFlZ5peFwqmxBNlYOYKnTE+KrTPcWj8OLQDgQTh9Znx9eE4De1tmxeaiWskUWgGUFk3oWkwqKPe63/IKjfr1XoNz7SH7muc1kY9mUGxSfoSZqkLoJYl3B7BBPxSRS8hzLzxM4TDM8hOobsHYTC8HBiw+wsea3X287u/Kn+iPdv3eOXt91Qe4E7PmnutZ3rad1t8mXzRRHw4SpiEZAxHQa/VZJvKlu539z92XHRnbede32lr7eG2YequlW68Zg9a286hH9Z/tZ9zFD7vV7HrW43H2cKLav0kGgHtGOo7jPtOy/7fCOr8/kJhnB5MmSCJ3OO5FC6vd1aEqN41cvlcTzBNVjqSssu4U3QhA0rcIQEF1GU8csvVfTa1/TNpFZBpaSQlHXd3ZrX3PhrMs48HmAaSlzA/Ytu34NVNJWKyvr+XwrSItTBb8F5pS0JoRaBqRr0pavNDVQqZwSZDeP5fX/DS8iu1yHJ0NqeQLBT2U63NRAhTXvAqFELccLkRyReBlXuZIQQZJInGsrSsIrkpUV8NKRm4sLL4S8IEVeClJeGlh6PFYaNtUlUpoMPhS3OgoLAilJNrscSwoQ5R0nvkDqEJjgsfyC4CSSacwUjXDVl3Ilns3eVjPuMt7lepfj6tm44yQhQ/jWYxnOQu9BctdEyU2rSJwdFxTs8mzJ6blJx3KZWvvE6Iq56Xo/wKUPWF4BCAxJD4I4gip1BEowqgQDotAeIXvXCXdPI3VNCKBln0dYebdXQQE6KlAgbI6rUMqEmZQwyBZiEkPckFe09lyhe5bNPa9nz9PFzTPqz+xyBCO4/L327OHEXKGQVSucnrljf+tQ3ggRZSIR/sCxJgmIXkE5LpimanVDuf6KSKrSHEEQAQ4qFHiJOzgwArKHkEaFRQfNv7IeKxWFfkZhUPmPd6pntxJz/Ba/BM7/bILVfCQ3vqZoM4UdXol4l/Dwp3hE1rynuzS59Ks2LzBxl04WPFZiKRHZ0cDtuK8wFcAK6Q8T+16/PTkOFa3cCZ0xj5Uc7JzothWO7N0wFbfYl0kfZ7eZit5iL9B+zVBUaEoH0WaSY6m9qUrpnyATQMenTECPJdD1CgkWMqcpAqwYdFuUYzHBQoFUjM6zz3ul9nn3gp0LD+OXKbiZIpE6THx0jt/SIbAEOiDw618EViI7WPi6CSyBb3rxe/cElgBz0r8JTUtgBX03lV+xpxUAFnawhDTxoa1sYCkKli4T8Nv0tJ7AQrADc2LvFNogYKkJlkpuJkYrBix7owu+JtrYwEJXJ1UmIJhow+1/mrBccoSWTwsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMzOjM0LTA1OjAwpzcW8QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFJJLnN2Z5OmeCsAAAAASUVORK5CYII="},"180":{"admin":"Portugal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG0ElEQVR42u2cb2hWVRzHz1wtbdILU8lYUm8KpjWbSCVBEEUF6pyggWnGKKE0MMOCMt+IZPZn1Vriiyhp5ZrNrOaM3hSmYVbqsg1X6dC0mrNlWhixLHg+N/iO033YIOE+8/vmy+Xcc899np3Pfr/f+Z3feUIYt7AmDDsXdPxzc8eHcPiJkTeG4X8fCGNCifXsaTBYVoNlsAyWwTJYBstqsAyWwTJYBstgWQ2WwTJYBstgGSyrwTJYBstgGSyDZTVYBstgGSyDNYSRumb3I12h9aNTU3eEcOy3sZNCqaffYA1Ix8598PSI3tlvrGkoKqurau4JYdOqd1eEzl2Xfn5T0QVo512fHQs3dD7dujncvae+qab40b1v1b0five/M6OvuNXAGaxEsUNg1Fmxf11YeWTfkdrwKtdbJ+14IIzgroIFTODV7ym5+0OYML1okRE5h8DCMsUw0TLt0KptxWUAp08BGTCpZQIgLFYCXG5MdPdtSz4+v9eWbIiDBTRbtnyyYsxVXd3fLRt+GTDFGMWKWwSsE0+NPj3q4rQ/CqgBWWLJck/Zhg1BsIic2ka1X1/ajdKC9cqvk69YuvDK88CRuAqwBqJYLN6Io8S2GZ2CBwsrpQH4p+W1D838o/WVmj9vfzvWjpaq6jtuTbv7xcnFldMvok9+1dF4I2gar4IHCweXuLBcxMPUts2a8+x1UzdPmXZzmNx8allj+JIkAl8pdnMHZ41bHO6lJ0pL2h+F0d7rWL0rlPIWcFR7ieXLP47ByihShNtYKaZTwWLi1/W80BQCuKR9yfa9lVUl2+iJ0pLWn9F0ZMACpiRhkQPdeBUYWI8vf70ihL5b+ub/qycqTi248EUCdqYZsLBnREJpWXUwoiea5sgYgdHoyVt4I+PoXaDPj7XBylCWHFvFigykFC8sFikAHB8241htzeyaeT2j6398qUO1a+v6y1/ujjXuyQjgpeMDFlijrBOT3FiUwrCGbK7+cDTJtOWudVIBK/4yPcvfLG7Y8NfRMxVnulSPb++t/v2ZnR37urp/+bBq52NHR9IS9wSveGTAwlZh7Ug90MI/gJMRmQaLvBRTBVjqEJPweZBggdS1h+ceap4xceqc+o0zwWvgYLE2jJcFwAT65L2MVObAIucETEQwJBpwi7ErzAJYqAb1doiZA4uVILZqScPa+cOC5rHASy0WcRWpAaKi/K5w0a9r6nYcQPO7QkYjVE/irVzsFbtC/oi0Y7fsEDMHlkZXOMRkd09iLAVL81hcK1igowDlb0EBi9FIN2geq1/wnrNPWh/B53TiNNNgxbUJ3GVzprFtwfHyk3EeC7Bq92xo+XpeVdPSjR90cY3jowWlJe7JapHRcMdc4wqBpt92dQ4vracwWJkDC0SIschjpW0tAxYOiAnmGrBwdsRSilEcY3FXe/48YnXJk98yGuByjcVKC97pySenv8HKEFjEVTgazbZjOdRusYsXJzbVYoEL17g5wEJpiXtqjBUH70CD9svCizpZmlFXqJVVWqCnMRbT/B+rwhwWQBPnq1gbohpXaUv+PFYcY2EvtcDGrjCjq0IiFV0VkobgLivE2GKhOLK0dAM2CaUl7snqL3+ClNWiphVoBzivCgsgj5XWkxgrjnh+Wjvx4fLGOJWgMRYa57F4KraFvIWymbhghrtJibPzWNnPvGO30oL3O++5v6XsPgWLnBa2JM5maYIUjS0WTzECoxFpAetXJzYVT6jUJC1Wypn3Atsr1DQpeOnZG4pnsC5aa8W6UleIio66whgpntKaLcDiLbg53VziEyYhvBMN2QcLh6hFKWlnb9affm17xRSSpaAAdlgObA9RF+jg7L5f2V5/8HmUiAqrAyJas8XI2Cp9e1zM46qsgqnHwlbpVrRaL+BTt/jN1dWXTNhLPiku4sNp4tS0nj2u2dIzhsRVWrSjeGm7c1cFVkGq8ZYmIEifskIELNaJ+etCB6IAyhoQvLBGyWGK3CfpZ01diVW4Ne9MoVovdY4oE4/byl+JECs99dAEo+n4cX0Y146rCviUTlz6RwsWCxepm9bEXii44ARjVZiAUjeV1S3i7PQstZEaIucKwSjGKy38j88VghoAabxF8I47080cWgAo2Ro3UkP7JLRWxA/8JDRPYZPyR0VpJ6GN1BD/7QYN7TUS0o1qtVh6QEPDbVUthtExaXF47l+bGfyvzWhNlX9txmClQRb/PpYefNU0AapBumEyWIPO5hOfaW2CHZzB+t+OwnrjxWD5x20NlsGyGiyDZbAMlsEyWFaD5Yk3WAbLYBksq8Hy9Bssg2WwDJbVYFkNljehDZYtltVgWc+S/gNZbVxWRfzdogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzQ6MTgtMDU6MDDAbmCBAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QUlQuc3ZnC9YrGAAAAABJRU5ErkJggg=="},"187":{"admin":"Western Sahara","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkEAIAAACvEN5AAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF8UlEQVR42u2dbWhVdRzHbxRMikoIezEqCDREgmssrJS9cIqDMbbIpYNFtGYKrkVzhulqkbnyoqZbo5pIK9O2ZJWm6TZ6otBNLV/ERnM+QLqVlY7QTIVqSd+9+I+7zbv7P+fsnnM+jH0Ze+Jwzoff8+9/IhnTM6JXP1HUUY1wC1DAQgELBSxuBApYKGChgIWigIUCFgpYKApYKGChgIWigIUCFgpYKApYKGChgIVGG6L/3Nelr6eXRr+KfiId8jvlVz8qBtX4fRSwhtH53xRMLKgpKirOW/JjxYSKScsux2Kxu9c21M6pza69M1710xVTym4qOaq/zb6Y1TGzfwh8ocQuEmZrJIBiOXXN9VVt+d9e+C6zr+XM1t8+vdR4vuzs7QPvD7QOfJSInrt8YcuVvy6e6M/rW3v6uu6TR55rfKVpctMsoflodc7Xc+oBK7D60GuZZVkP6GHvOfRlRvsXAiJxgMaqAvRYw7G0nokfTN3cWrOhaMb89tzZgBUQlWXyBqbRtW/Vie1dN8qBZj2TeeuD6UF1lJFg2ye5OTm48YJpJD34b9vk5o+DasMCCFZm6ew3su+qv/fDJ1qqUg2meFVMtqyppHXB8iBZr0CBNffFnOKC3h0zWnbsz0h9pOLxWrSvcH3eivjSBmCNs+PzI1KmduUfWN1yRzCyyICAtenU5vztL7mX03Uc6NjYETVV33cjFdj507tTNx5XaA9Y46YLb3uqc+W27q2n036fYP9QhcuW/rorsTVLfy1+snDavJvnrcv+xSx7yjrq+8o3hbVT16B6mKIuwBq3IN0p95dcjmbGQ7nvLbh+8T0qtNpfz96Sxtff/sO/divid1tl74ycimzUwHEqgTi7uHd3T7p/ixG+AUupuFTOyL6goIc3mOr/HV0VXWSfkek/yHrZO8fa9dWrlz/vxzzRZxZLYMkq2D82hcluPDb9T/uUQleIxfIILHsnqNaKe4m9wLK/TkV+irT8Zbd86Qoro+ta6utsHpjblkAQyLLaNJROze2+tKtQOSlguY6XOoA21SlvknmVJJwCC1foOlg2Fks5oDdp/CPpj725pO3MlHOtfz6eHFidt+x/a9cGXKGnMVZyD0x5luvX+X/pwd5lm9UswPJIx1puOF7zw6GDWW534sxq1vcVRzN6J9lkheoBpBpSI+0BhK7yrhzQyyaJfY3NjAVT01aNHk4EpAldfrgysmbazp7dsz6babaKvRwIVtnW3v35PboKFFjmiJxysZF+6obj09fKVe2Rkq3S5o9/Z7NY/7JCSlZKFXanRmj8G7ADlsMD0E4h5U16AVgpaqXcGIA22+HMvIdOhZRTE1cmUoqo2NIJnZXSGMznVYdruu93dscweEgBVkIqpNr3HHmn86SzOzkafWYTOnTnO8jxOWul1KnUmhcr9qFDyo1lMhU8VaoNw/kzgDWMOlU9N4f14su2gBW6iMq+eWyWOv04TQVYrtgq+4aMWT032z6AFdJKun2o7vd9QMByOGCXE7SZ9lTGF7ZYCrCuMbb2bOWrlZvSkgvYU392ystJLMAaUlu3WdAwZ6e4n4A1xBXaLJem5gAxYPkYLDnBl/eW3rDwfDizP8ByxRVqNkETVIAFWMOAVbjy6Yerm8cavBNdAVZCW8uJ17HkBP17GgxgeRppaXd5dLyEVDAOdASscZgUVWVLK2VSvcgktu2Fn8tzuUuAZXWyTeIlQRSwUMBCASswTlCTDnpPmOIqM9LSmwrD8x4vwHLsRCutToxe01Jp1JtDkQDL92MzY50dNatZ3EnAGuaUGJuDh8yjcimWApbDh3vLbtExBKxBsJLrEsarzuLCYgHWoDoFln8P+wcsF1e+bKbdGfcDrBGDd5v9HPNFcIDlAFh6JMG4ETbLFFr5AiYs1jUWVhN5l4QqWOFcnwesJFXvTVUzR1V4oab3rwomhmcAy4HJUlMZoQEsxybiKXsCFgpYKGChKGChgIUCFooCFgpYKGChKGChgIUCFooCFgpYKGChKGChvgIrSGsUKBYL9Y3+B+TF2EckTR7VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMy0wM1QxNzowNzo0OS0wNTowMGRnFbkAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NBSC5zdmcabDszAAAAAElFTkSuQmCC"},"191":{"admin":"Senegal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrElEQVR42u3dPyiEcRzH8RtYTRgwELtMRDz3XNksSspmlsVgkFKUMimyXBkMFEaDLGLSpW66QcoZEBmkUKaL4WP4XY/nep6754nfPe/lm+7P73d1rz7f3+93z5NUqnl1zXFsr7tNl67bV/p8fclk7KpfU29DmUXVj9Pd5f7B+4f2ru4e22sKWH9bnyYel9wSsIBFYgELWMCKBJa+HmABC1jAAhawWGMBC1j2JVa0swALWCQWsDggBRaJRWIBC1jAAhawgAUsYAGLA1JgAQtYwAIWsIDFASmwkrl4N3MLWCQWrZDEiq5hqZJYwIrsK9+evHh321S5bAZYkdWJhf18eliVVgisCGph9LbVPW+5WZ9xVlSL03djbhFYwKqpqv3Ft1EAVkJhqf2Zc0XbEIGVOFhqeT3jWwfOoTmXHomjIQLL4nMs8+CgcvU2QbPq2SDjVCZIYlmfWPqCZ2eOntNrSp3KtaNxo8HZ9JtRC/kg42jGILyAZXErVIrMXx0X03vxfR6x0yyVj1X1mfUaYNXJGkvv8q6iaqkaLezOkcSqK1h6vU6qvLu/sFUjaDR2hewKy5pRdS0ySMsjsayHVQuvk1yh053TCin4Wkrv4hyLxPKt2sGFnV3v4hyL67F8d4sD2e0z58kvmfySrHck2+bkaYUk1i9V7cw7sqjlStfn7o6qH77qGqL5mYFVh7C8y3Y1OG8O6RE9a2aYRmCNBayy/aDaWdhTKPMkTElm3hwBrEQnlhpcdadQ3hH0N7CA9fMbYtik8Vv+V3fVA7C4/SvGCixgcc07sGyARWIBizuhgUUrBBaJBSxgkVjAAhawgAUsYLHGAhawgMVPOsACFmssYAELWMACFrBYvAOLxAIWiQUsYAELWP/yv9gDC1gckHKLPbCABSxaIbCAFbR+A4xXV+uLEBwbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0MzoxNC0wNTowMMwpBA8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NFTi5zdmcOvYyFAAAAAElFTkSuQmCC"},"196":{"admin":"Sierra Leone","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3XsY3CMACGUS9CZqAlY2UNJmCA9GnS0NA5FUNQICExAgUUpABBE2xHQXp/8YrTYYH16S6EatX39YajbX+qO/eQbnAFFBaFRWG5CAqLwqKwfOH3/vN+Fn+x6F8hhUVhuQgKi8Ly7UxYLoLCorAoLHp6ExaFRWGRM4f1fKrI+2zxembK+VPPSfn9Ep+99GvnuZ/PsLr1+bCvyLyGu1mBCcuEZcIyYZkJy4RlwjITlgnLhGUmLBOWCctMWCYsE5aZsExYJiwzYdliw2q3x9t1IPMawq4Z4uXN0DQxfvn5VFPOyfUeSruEuyp9b7+dOb7sf516QZxJYVFYFBaF5SIoLAqLwiKFRWFRWKSwKCwKixQWhUVhkcKisCgsUlhcpg+vVbnH5O2QnAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDU6MTUtMDU6MDBnQH/8AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTEUuc3ZnXmLsXAAAAABJRU5ErkJggg=="},"197":{"admin":"El Salvador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA6EAIAAACZlLfHAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEXUlEQVR42u2ca0hTYRjH96VIygq6GFkW3YgiDCsC+2CkwwibGVk2w0uGWSSKZEpJkSFWLouizG4fNLMs7xViKF1FS00qTEX7ouAWmSB2U9yC/U/wyjxjtkmu8//y5/CeZy/bu9/+z/M+O+eoprn6q4uL/q1OD9y8sWTOeHgnzqjjbfXwflT8Yoi4Y1dvhtuWkNKpBIvq4B8kHYvqYLDoWEyvrLGozoM4HYt4jSFeigCLuzymQqrT/0SZCqlFTIVUp2w6ECwqd4VUdt6pdCwuh60tCTYv2G5gr2t8/FfIxaXSsajOUGOJJianlnZnedaWmLGb3/o8cq6MJbDd5OXelfVWofWRsfi8cmdtWR97xoeBVRBYHdp5hEp1rKpMQ6YBUxfVUo3GgRp9CNQU+cPw8bJ0LJzlKskpwRoJpvP9oXWexrdf598NGKbmcQkycYSrR7CsI2X61Le/MkoCS/CkljUtZ+tXAKlh8YBMAI4rSbD+ICX4kxwcBQceujWoDe2G1O7tI8wj4kWwCNYwlzIf6/fpU9qWS/4kRNapnrRf+KFPere0rEMcR6QInJQiFV+BqehVgKD29nuPl1W6xOu67E1B7jHp5Rrg0ufek93gC7A+tNV0XL2I+HqPtxGtYZGNiTPzl2UuyWnNGAKUYgVGsBTtVQAiKzg//fSpKr9Xga8bfeP27H1wC+gApp7kurllg1CMIDki8rlfXe6rNswglf9WEyvB+n8VX7lZK4qflRR3JERnhh/KuJqQM6FCA1wwDpj04eVH85ugGLlzrXByhQGReBVmAGRAVlKCpSDHMjtKf8Tn/kdPd09K9Q8e9FFFxWoHkQR1cVfy72mQ/n55fHGrapbwMtdY8KTClMfzsrci0vvbTs88n5CglM4dauCFBEqwFJoKkQSBgjol9P6J2wALya7bo8bV6/vP7/WZs/1x3N3b5BKdB61e9cI7TQ1XA1gBK2NK4+NRb3GfqOhUCF8RHWt9RPDNZJf0oRvHw2pzux80rg0QNaslr3ldU9rBG7EbWvalntNodwUtPGM8HIRXYQZgKiFFx1LoftCs93srtcUdSGTAxX9iujY5EdDgGG6EY0uNCjw2ZZsWM2B3yXYDd4VRqJbWeUWnrV6AnR2aDqifUKTvL0hquvQmQX1yVrYJIziLSDgcZpDAsujdEywlqRkslOeoltTRcTlqHdIZWg9QtB6gcDjs/pBG4VUYYRIkWF1iMxM9dICChAgFNOhawatE+MR4VGzsvBOsEfCC38C9kNoWz92Zu2gi9oyotKQRM17AUUSKf0UTLFm80N9CtYTEh4QI38IIziKSLkWwRnNxn+2bAK6eJVi8iJY6Jpcmj/ZSeXsu2nfU/HK3SNhz64E9N03Ixdtzs4Y9q/F3n91RMbwTmsr7Cql8PhZV4Y8OIFhUgkUlWFSCRbCoBIvKJ/pR6VgEi+rgtgXBorLzTmWNRVX4o3jpWFQW71SCRSVYBIt1GIt3qlM51m9Pa1fNjb5KagAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDY6MzQtMDU6MDBoJcg2AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTFYuc3ZneSIBDgAAAABJRU5ErkJggg=="},"201":{"admin":"Saint Pierre and Miquelon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAASeUlEQVR42u2da+hlZRXGj04hNKnhJaSraHZhshpFulj0wQzBRkgzMMucRixDMZQpDUrDmYIgnRr+lWaIDuMFQQONJogy1GAiyzKpAdNmmEJw0i8VfWicYD/7w+/lOWudtc/l34z6ZbHZ573s/a5nr/v7ntFBK5euOu2tpK996utbPnD3LY+etfMNN+14z9suO35tTvc8vvXcLXufvXHPtmeWnv/vvsP2HSWqOzs/dsoFq8/0Xo/vessZR/xc12rz0ecuWbn6Qn+e0fs3/njbntGF37li+6tfogcMdUYe+eZNK07793Hv+PhV5/ymAi8ByyH1p1VPrHryRxGwRH/y7mM/ccTVEaREjz7422vu/NdLrDrAqGAUMVW/RvDafs9BrxttdWBVJJYgJekYzf4iBdYh19/+8N4D/i0EmgqDJVdml1iasQLoz3zv/DWvOf3Eiz677fo/vHiAdfRtPzjukb8d8J8EWb5699k/PHlvHV6ykyoSi1ZUBCnddwtvzT/Wr9n8l/6hXxhfc86YF8Y7ugSSnMjhRfZHEktUbXJIcUypSMq5RmK98IClN+ocFH3YRx1+0ZuuPGW+b7riqUtftumTosu0hpFZLXhVoHDzZbcec9shLrGkCs+4/1vr1t5Zl4KuOnuJtT+AoGP/BPZ888vr7/pQD47uWlS9dF/rprcWmOQqffi+933/FUfqzpjxC/Kst0fhR+uOuPmllZ9fd+jvlsnLzkMAFQvs5tUPvfeBkyNVmCs+vbDPvn8CS+wXe3p4BcASOPR21+9Yf+WKk+796g03Hfy1x664+4jR4c9e/st7R6O9Tzzy/Gik66d33L999MoJI3eAWHnCqevPPEZPIpiK6o7AqnFEdUeQVd9lciloA/39p2ufueBa3hG968+f+8qnHpTsOXHVlhM/fTVpBCxJLG8vqjE5o8+uOwsEVvTt6j5kDyXKsQ+8/ZrXb+2lUcAAgUPwOvvQj5x02B2CjoC17VdL+0ZLN1173emH3HPHG7/xz9Ee3RcIcsmk59GYgqz6Cqb7/vP7XeOoZuzl6PJ8im4VMdTp96OWER06prdZ89f7TtjxhTm/dgcLwYVUUNC3ThkjWDx63tLm0Y0vv/icX1y2MR9ZskEQpMoTa3uZ1LXUaGoZQhZQU1+NL6ALNHpayirKSwFxjCysyKHpbLIcFvsDnTOwumV61cMbrrvnSao2sUeKKfruJWNCVWXGeE+dMbC6CCmBTHSMJddd61dXiD0cMa9aUspOGH8REutFBCxIFH3H+rL1xQtYAhltFLURkxrGwDyvGNfqpXE4Js12AYUgCNVuoMTZV+/Ygw8qfgzI6rO8BKxo6bXQAg2pZECkqnp/DfKG7BFYKUtcKhBYpGK/oKw79EA15gRJiaelByr4Nn2hUmk79hYeP5JZ1nnXDRuvuWZdhV66Z8vm7z7pNPcKo171eRduvNM576LeUpThd69FB7zcI/P2hAjVH6kYrNHoOgjQkQVGiOvJNaN6UQryOWV7Sbnrmp/NAsMNun7k/LO2jXbmSeKKV1ipoVDm0YMOyxluIJPIWrKcBr5YIirZoF97CQGpQ5g6ZN0kZ+RJ132wAAq3UZemCikFBSCpe1F5o83TztfeimJI9RxiLrHyESolOgsHlhnFuhaM6FuJGQQTfa7GHoKU4n2qKjHVYeSQ7WUJ5Cufk1KT/qx8WF1zNL2FZpyzXZUDqwIphjfzXCFHy+P4UZJ70apQLGFkSEtPljiz9atWwONDDilBQRChMuL4HlAl++lFCo7qpYAII2QckxKLv06w2OarCisZPa/TynOFCnVWimQ0r55B7RcNLL0LweQySXfEElGxUAxrrC6oIcGILkIk58h4zSVJQ/ZrHN3RMzDcqr4u7QgmhmSXKUxKSOUqT2yolM3k9ViVuagiFw0sxr4Vboio2NmwBxYPLSTCxSVTDlkBqzfhkZyWDOOYkf0Ujdmr1DmFbCbkHCts9lKZocByeFWS3Pp1IYV+3dKIMUyG6Nrv6FrAIstpn7nko0olm8ls3hGlrJKXSonFTIBT/1WfRJ9+Diyq3pcsJKcZwpgg+aYr7iNVUCBXhVHfStHfnIHVQUHgEBsYW/eMG6HWWzzGAAKLmbucRqlo99TEBc8KRJ8EqZ4nAoFmYShYlKknergMLE+w0nI1lMNiOlUYwUvMpqxaCLC6BRJEKkxiErePJBFY3bUzPgeoj8+UEYMIYrBkW5Royp88LJUJnpwQFygldxmfK1VJaGiFDEQVfxJcnKoqQVIq2p9DYLFXNKao5uWTqJ7inU/fvvTY0nwjVVJPdSZplfoFBZPEeJd8EaRykPWxb4tFRUDMZ2kUd6DU9MFEYzrNVWpD9RVGsSin6359yUkXbxCttBdchs6i4kH16i2AOQFLC6pFzyGla6qSJlLVUam/yji5xJKcIAg0l6RFzuwIdnq2MUoQsiqXdqRahwimMbA6RuZlMISI7ADdqUOkAkc9g1pOqHyaiurJ8yoGLq7Yw/AmS24I0IoTEMFOkoNWi38A+QgO08gSqqhXp5SmpdWOCotDoEBdVtpTIVbqt9hLc80rCS3J58CiVSE6Rj1Zvs89yrrd49eyrhi1p0SJLLbInqN0iRI+DNV63ameh6GNwdGvXD4NhUIdcN4rmmsOwIJHQ2AJRowPsbivqeq0cdw6yQGUA67xN1Mlm4/vdWM9sKKSHlaMdddMwEvmTZmczpVRBSgEh3rV60slL3NF3ANrhhSp59QYs2YpHKPhsm/cqtCdig9Yl1guFwnc+miigikhMnQDhd5Rn9mUkXqXUiprkR9HeEWMl8JSewIlByVbimpen6UB1kB4NYVssDAEKa+vkpwgyHpgYXZXUrlhXoEd5aJ7mhV/kGqdMJ0ALFtPrYPWJ7SrKlwI7ZuCOe+grPeinMvtvOmA1fhxBjVBh4Bj3pAZuj7EgCSG5EEOl4pXyPvc8uVuQe5XetCVUrZRiNy+xtpXUD3JULtqTKwxV4UDAgRdy96CKcNRkMq9xT6OVS77b5bS2nidJ6usBBrWkguCrCun9TOLXcU7itgxMxj5rR6p9+tGynbPrLeIDHOm3nV/DkU1lQBBDhQPEFQ8Tckq1kZGEfwBNhaDAkGsmcqRiyv3W5YN09KuNMWeetSqIr2kdChN3YYjpDzDyCR6s+W1e2t6fKJqKfuSOUqCb6bSmojldeNdVLCI7KTpjPcB279gT0TLQQWnawHIF1fWFZeeETWXWHWQRXUTrBEVLCJVyLorUqa03RXwejImaiixCL6ZJNZ0AYI6dAjWodso1KsCrBxSbCNfSS1Zt+n1Ulxo1nBGNRE5pFh34DUI3PIQ2ViUVV5Cw4ouBnVZGsSKLgGRtV/cmzSH8yMqym6MjCnbXqKSZEPDqnq2MblCmJmiTT14uinKa8Y9duXVmLRaPBkSmdi52vJSGVbZe/7RU8KSowyE0l7UrwKc+kr6cqOb4Kv7rA+bQ30pzed6gKA37VNbirt0FIzoQxIpKBvXoRv/+FNvOe+357riY814c06LW1e0vUw5RolYgUO/0piNwgGUJV5wx3osv6P2OlcnimMx6iZgsfLdd0t7ZM7LDL2itSnamR1Y9UBoJaQZ9apH8xmGCKsbsKeZBWisKGKQ0Bcrr3RwOUH7TL3EcjGMe/TIJEKBpX8OPm78oougZyBQxsTWaQ/Bu/QjSfTMXh7Y+JKzZ2aHGukE1lClNp0qrNdjqSU3R4iKtS7PBLsofEB1RvuDe2ya82Fg2nPzJ+UKjwmhVdTIxY61CkCw/pP1Ds3xIbb/h1X2VLWiei+3I+e8Y0ffgZRUPUAwS3VDJXjBXhMO4QiqQ7npgLZOszO4W8qogIRyi1lFhgZY8ibgUrbRaYhOVfC6ddZtcvsX38sTTRpTb8dwSR5QDetLo9O5hkqsijEuljPVU1dqtJmG9hpQjwUVQInl+1t0LYhQFeaxKFpIzSZSVDh5BKhRxBZLoyRrQIN30Sx8WoKP8ol2WyVSP7gYpk6nCx8MpdONr15N5L2S2EHiwtUWZUMe3c6LUrihiuOPYZUd1DHGNIafG53dQAOc9hOttEpdvK4J0GU9bWZonRZV2NBIVS4vZ69uoKriJk/uvMtZ4pS2F+P1hBePBuGpDU2GjudHwCZj0klQoxnufmW9WnUhpnoOLJbX1ZUXIVWphnC/Lw9DzPe0GYZGXfZE1evOJIYoCTJ6fDwlIdr3HG2up2SNIuxRfWkepF2g+psALCtoyY10QqQejFAKqOnVAWtC2cxcK98lP2jqUva4JPPSPNlweZI4Cr1GUS5SOh++94ZbLSKZKiDqjQY7Q7PQfDdOvq9meXrNd5eOn1bFExM8jOknhXo8PbfSooi85h26T4aV+AwfeMLHT6CgQ9Nsu10EsHR40P5JdYLDov+ZgsFVZwl3LTvU/JxSly4OLCZS8vBstNePktWfx08lFbBY0a9x5rbp/sAClujynI/F2BLhEqmqiM2krHzy+548dqCwjVtdtLfcwmPqJtqUscDTlCM5oSPXdN20WXXMzQdt+uNTlz938Ca18V9F9ataiurcB9E6sJbzv3SoHAkmL6nLk9BeGONwzOsdKPncGCeMCDImzh1SXstQ2S3dbLSvR+edkQLE7ls27x59UJRw0cPx1x5eABOv2ZKHWutX0n4Wex4/hKMx5O3wID8Arf/bI4+E2cJ59Cs6RV1vRFtHd/JN9JQT7Euf1CFFBcp8qHxwSVkdU8CiRUKc5/dJA6gvj/f1ZLbWUy1lBzMU0gSufW0jicXvhkBhHY+WUtKIbUj1K3s10i6Xfx1l9NmPndV9LRZ9TMGRFevNv2ph4TQ+d3j7uX5iGMMKrJVlX8bJoo2pmtGrRXhCM31SjSBwqOWtpz3+rqdv/eKxD+3a9SDDQ3oqt6gEO811yb6fbXhihajuNKVHdm6gxtdc/YxYYc8r9J90pIDEZmbK+I3qQSmBKJO8Cluv5xJuDLUncQbolQQmLrECE6K6w+/Mj2x0Jjmr+I2yvoDLzRlVJkQp4gxWG7GWfck2ze5/AqBfKYN1rXH0LoQpPUH1ZQJeHjfn5ftqHN/M4r24Vr2Gye0byRICiLpc11w4QofyjGpC1zw8N5JVtLFUlSVmaPlE9ZJRPlG/9juqOyDqtQkpTxmJVfymBQXmVfWrhzA4o8shAa7vG0TmNAJnZ7C66WuGAUFGxjfPHPTVvFoT9dJ1Xw8X9HK+KK45qjj8ZL/LJ9pMvbHfyR6CifCSDHPbK4KXG++u1EIrKvimx0Aq/VtKsmpCJsBmdAbXQ76EuMap/02mWuodS7FAPLng0qxq4Ujz5tlySJH9Lp8EEXqOEvUOI9phVHlUFr2jMNQrDMA0mM7yD1g+jsG6YTA/hgX/C9fgWSrrU1nJHFgMFjilfIokXK7mCMQxoY2BwKLul1TIJRl70aTVnQksKf+fIIE15nnKLOfbDXjOWcCUP2f+a6gKA1N6vwuQmlSgAqJhrl8l5F0tEo5i25hae4MLmUol4uMrB6rrBvQpyzkOR5AdI8rnd8CVYBeYDXXVX5JYjeSI4JX6cd6mD4pWek0nsYJvSGzWApEZokOtlojlpM5staRpzDYEGT8PtuSH4bCmJPNPJbcj2ddH8NEGrFUELEVN3Dai/dRYRRYgZUtaUR4UzWPx80rpjFFJM6sPyicCKB9fvVz2sNyocS8GvmMja1OFFcknV9ljVHlu80URrMagBrAU+qtH3hkg9V4Va2xASqfywgWlVrd4cnVZn53QdEU55gnLHqWDZsIzT/Vf1ANsLA8WiDI5SvCxjUupKJnj0Sw34ReRK6SKdLEfyQlKGsoVKjWybTorJ4otUSXl4KCaptp19Vq39kLPdyiwxHKPpDMzT+gwAMHwBIGlNrmfOGdVGPhoboW4peLmvIOP6tXbVECQyxhClq5JlG/g7HoeB01uk4Ugq0iyPFeYBx0EFFYpEV400qn41JfpHY6mcSJLK5JYrkQGBwgKjj2/+Hzr7OzqlW80xlazCDvBEZn5+UYu2mSzq/Vm/AqkPKnMghCqMMaxPI3DETimoNk7CuXIuy9rhf0emMgN5KEQGeyu53OVbam5wWLRhX6SH1Rkkc0UBTxJXf0JgmrfVEmgjf63IgKWU/92+S3mlsfsWzai+FmuFv0jqVh7uS3VvIupqumk6WDHqKIK65UIs5Qgz6vQz4OKHlua0sROXQEGJ50BtMAYzHQYRTZQZO1FFpjbkaynKJkN021gqdhY+wOdziv0DN0cYlcz5/I8IDlGUppXGLkUOfsdZGNiVEPzj7N7hQc6sP4PdJavfNkhPrhvPpr9+j84mTe9NS9e6gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDg6MDktMDU6MDCZtJ6mAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TUE0uc3ZnGgZlfQAAAABJRU5ErkJggg=="},"204":{"admin":"Suriname","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADX0lEQVR42u2dPWgUQRiGx8IISuAkQgSb+AexUIJEFGxsxEYsxdpCC60sxM5CSCOmULGIjWhxiK0KIhYWVmKhCSoiBNFCEYJBxP+ckHeLXeZms7szu+vePlM8hL27mc13z803M3uzZ4w5c3xHB8LQJAQQsSBiQcQiEBCxIGJBxIIQsSBiQcSCELEgYsFW8tqtJ4+6VyEMS9OjUEooiEVBLApiURCLQilBrD8PF7qLByEMS/Pm0/51x+5CGJbmxcvO2ORExCPDZ3dfailT4/B2ZO+G8V9iq6OUmUmxbKa/AfYzsx93tZK3/rz1FGr34/TpVZs2fp4593j0cg4pQ51nlXH2+V9izwwnlj9D1R/0POfmRv/u+vZ97PaNtQdEHakoJmHFKhafQiKaSoPSQCr99brP7pgT4vzSoYvbThKZdCLWClQSjIulI0QGsbz49cP1peHfkVi95zeN0REig1jBkqDE+jl7b/uaLdEMkVghln8SJCGWI1b1M6Baufjjyv3OZB+xXAkxHp+WxSq/WHVNqgPx3dDh1Vv3aP0pOxfmpx6MPI1rZIulv/PWLLZhXmkK9lUNUe31zPj5nVN6OxNauOiSKV0yuwb70ZiIOivEGhC+P3V03+YhDb1XkMklVvozHWKpRbVOKqx2vbvC/k+9RWL85Opv0vuqDIlSrbShf8ojVrGF/0aNxhLzPrvvyS6TY84YXfwZuA9nlhZZbpjQUFrXAX16LNVQ4sC8KddqGy9W0EBIiPShd/q4iiVTeizn0L5Y4hOj4Xmj+pXyWjFt66JdX4z58mp6dv2FvDLFn68a+IjW0WP9lxMCzdqiZYgsqdCREFVDO+eApMI+4qYnwfgqVJ+VMOtVWusfjHEnYnmFPrqAY4miVSh7SK4jia/TxERUbW2b+iCWOwlaF17SV6H0aOJi0TIDJ8TGXqU17fw8JWaCy0ppFapYItOr4qO0aDWr3mlNGa1nrtPk/sqH/+n67E5xbe4oVKfSlmZzUR/jsXlENai2GhJi9m1tFfVY/nvx7L8bsotQ/UqfXTceVG2J9fd27itkzy4sZSc0dxmAYcndZijcxoiCWBQKYlEQi9IssbgjOSzlPu/8hgLkJ08gYkHEIhAQsSBiQcSCELEgYkHEghCxIGJBxIIwFP8BJA4soBLvicsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ5OjUxLTA1OjAwDXm1mwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1VSLnN2Z6h79J0AAAAASUVORK5CYII="},"212":{"admin":"Turks and Caicos Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF/0lEQVR42u2bbWiWVRjHn1WCY3NZYyZrLxVjNi2CkVlGSNK+pMgyyyQKeqFaax9GZCMmDjFaHyoG0oehK6eyTZhrqUNZQVkfpG3ahs5eHMU2XZKG+6KWSwN/z4crjufh3M9zvzxu15c/N+e+7/P6u69zneucO3YytuL1Fc+dG28/07H83+zLHVPdpm5bs/XF7w7de7WsaGNpLDeW99qrqeuSqYfvbLyd/CdKG75sqO7/MaMoo9BU7vLkA6vLc94/4m8daN1k+f7MfZODyys7nt1AuYMjmZtnLxzrqep844eB2h0Te//J/y2/c/2IX6VPc6X76Eogo4tNvEZfGa35q+LD1sbBA3mpQwYiXsEChVTKLTu/aOemVlrx06w9mQePg87QUEFVwaP0htkPtF3B8qCyc+lW2bk2S5Y6ZMlZrOTAAmJ3mKYKx9eObZbtPX5y+IOJWgXLgybudBdLdqrydN9ki1fIggaLmpjtcrfQybVLNa6pfNmpWDIQYfBcwOLJxGDZpjnTErvA5K8/N6PBcvdFvPpk5BaExfL3YzDrqeozWO7TSnKWLBWw/LVMOs1FBpYLZF4txBdHusaO1nsFa1db24K+2qCnaVUflKFKTlkrMWx/Fvf/PfQUq0ip5grLVHewUNCRpdhgMld2RK3c28gHAI65o3PmVR9VaJyUgY9Wx3+tW1m30gYWd6OtIZ+Nhhs8KFNJtCpDAKZyN9oafjZSuL7krvnbMxY/H1NonFQOnm1QbUM+E5Qe2Nd90885uQqWZ7BUE6uC5TNYM8FiuVjrMMGSSwTz2lxAmOm2a1uK+liR1XDgoXlZ9zUHBxaRufY1rSf2ruu51JXVXeJVzXdtuZFePLf4cP3sabsqJFBJRCr9V4XBDQb7B6dH+nPrY1cPDDQWvHxl4UDvoqUoKaa6PyNTRl443PzJksA3rIjTpB7HsqlLzMlrHMtUht+MogGEjGPZIlWJW7plWVN217fBhRsI2ALWlcXfP5Jxx8WNu3vLaiaa2/Pu/uXiMwcP3fIYKSjPSD13Yc83mb+jw0NNjVmVqEwnH0oJPEScSuSdWLY5zAwqcXA5kWGZiJsnF3nnLVu5ICvLlYf1KDc9I+/YjwurhnOqd0mwpGK5L93T/Pa6pyVS4CJh6nvinbrbinpaq96be5ZrCVkagSU3el0GFSBMmILYK2QyTb0+0YIlLZa0T2AU14amno9OoNJumVaqs2LtV/nlqA2swLfbXU43uAyezTLZBs8rWLZjMy4W1KWeTHZRQSZ9LNNiARb9YIJlWiwJFnYrYrDkILnDlNx0E8RBP6+QpY8lY5hNiyXBwsGwgSV9KWACLIlUqD6WX1+81/NMYZ4gtX0kLr5gOOe0JFhygsN5TzwVxkEUyvQHWDKd50MCy6XTg/iyw/xLJ/UFR9B/6Zhg/Q8XYbfilkwAhH2Sz2OlzPRQwfLLZ0oOLPejyX79pZOchQ468g6yRJjMFZ95DVKEbdkgJ4VnzKlQvkspgZ/UABf+p6P7UFL4UoPwOUCE2FLf6tplG1bJ0qVylyf9DevJBQr5AzrAkTJ48/Z3W4617b7/TMWc4MBim4WyzBiViRrQ0D+ABUzmOlEihRJ/D3xjh84yFaKDDvyTP4201UTWJ7jukO1FZUo4J7G2nt9y6vPeeMQ8IVgvFXRenr8UpOo/rfy6oJIUEyOp5EwpaXc0WTU4fevBN8u2nZ1qOdb4ZL0Njp0fd+dk7y+d1fp48R+31uxYULJJKndt75IzpShYM0ixjtKFNxXLBFgmXty1vUvOgW8/K1jpqUxV8Q1mYzdQImWzW9eZTK/lFtIkqGClp7KsMe2WnAQTg2VOiCGFGBSsG9HfAhfQSYyXBCtUv0rBSn9lHcqhPCayxG77dcC69hY5RPDjmg5h+uOFb4TtwdPCSTeVuzwJUpH9sqaDd6PgxfkLzmyZ7jkp3A3VSVewppPvxbYMlgklJQJfSsGaTsr6DozYkgopOqVgqSpYqgqWqqqCpapgqSpYqqoKlqqCpapgqaoqWKoKlqqCpaqqYKkqWKrTRP8D5MtY1tf5GxMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUxOjQ2LTA1OjAw1i0UvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVENBLnN2Z58TxscAAAAASUVORK5CYII="},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"},"232":{"admin":"Venezuela","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD6ElEQVR42u2aTUhVQRTHH+WqRasiKKIPhARJJBEio4IoaKEELSKigiAqhDIQ+oASKmqRtKpV5CKhsCisKKLvBA2DPkwIBEvIWqRJSJEQScH73+A8xif3xX3vOff+Nj8u8+bOnJnzv2fmzLzU2EjPiznzIYyWKaYAIiyIsCDCYiIgwoIICyIsCBEWRFgQYUGIsCDCgggLQoQFERZEWBAiLIiwIMKCEGFBhAUTJazNA/V7Dk6DMFqmUjVVS6+NBUxVvG/7GNCWu7R1sr1ly9364VsOU3PyUUxeM9f2wzDXmfw/C7PVCVOSbxa0s1wdU/B+ly3fuuJhae2Zht+dc8N8Kuur6us6OvVWxDYXaQbiLqx8OCPEr1uajjzqXvSs99Wa4ZM1G3deeVItkc2evvb6rUZRJav37hp+2qyaeiuHWJsEJmEiJIhsv5ZVbyq919O8snVd35vesf6u0abxkvGLf0pGPo+e/jXj+I4LR9+1zBxa9aH9sKgS/aqaekstqLVsgp7ckljFsyR8Pbu/n6p7OSjK8Qfazy7pWSA3Sy6KQ1ZYny4Nzfs5vvhE7e2756wjVdL/dfDbjwbV1LNaUGtqWb1oobQ2JClixXVnYBY4xRjJ5fz+q639fW79tur7hwa3W7pSUIlb021NMUw9qveMRTPey2W8vxtFEe2EFF1ECUvRRQ7Ws0RjF74gYjlLp41M9i3V0bN6sf3KkozkIH7yCsYVj425U0eu1RZbjteCpcVOMSaIH3k4RFDL6kU9qndZIqusfAsqr8L0FaeNuZwkp2Y4LE3FjyBamPr5cJjtXT0Gi6+xZwJro93gF1dkvp9OyTFym/jgRnfll32usLSJjlhMIageg7MuMw8ql7XW/pjswzxeBNPl+r7dbE4ZWUZOVywnOb3LKlkoa2X588a3j0e2BRHL98XR96VQ+xXrHj0rI9Mh51SzWVbZnNFarhGRFRbjK3H2KGJLxc07Ax2iL5FYy59NJibYI3olqX+We/tNZOyW0g7QQUDOuV5RKWvdY9jC7wWTvRSmp15uyHaK7ZdL3CTDHsO6gvPgYsevcyxNsb2Q0VZXJQVN0fMmL41CI9LoNFKVuEe1RKzINrxKzt0LYHtV7GMMttfb7kW4Rh0kIr7suvxygA4b7SWJtuq+RywbtzQiK6ys/w9DWNH+T0E5lPIpb3LAHIUlZrsIR1h5WRDtLZv+w+l9DmWEZUckTs3TOO+FFQ/RJO6vyRsul5UfWwhhtEy97po1u7wSwmiJsCDCgggLIiwmAiIsiLAgwmIiIMKCCAsiLAgRFkRYEGFBiLAgwoIIC0KEBREWRFgQIiyIsGCi+BdwnhLb54MehQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTg6NDMtMDU6MDB4ABDSAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9WRU4uc3ZnXIWjIgAAAABJRU5ErkJggg=="},"234":{"admin":"United States Virgin Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAMdElEQVR42u2da4heVxWGvzYkf6KItVpIIIyGjkXBsZZiEBWqlXgJBCmUVi3WWrWibcTU2FqoVsXihY5S6V0EGykmIThMY9MJTc2kkprSRCc6tbF4SYuYFEwQ+08T4Tznx/vxdp2sM99lZtL9Z3HYc8539t7r3e9699p7n+mc2nFqy6nJYovtr+2ULii2AKvYAqxiC7BKRxRbgFVsAVaxBVjFFluAVWwBVrEFWHO2J2ZO/Pf4T49+48UVR6/j+szrpn/e8NzqP4zMXDx96Mvfru3Wfcd/eAHlZzZE8KnaIQGLLn7+B9O3nPvVmfftvGjsCwcvmdq+/iuUz/759xsm3/2PS59/8JlFCTvqf+ieyd2v2fSfO55e1/nMyV2zn+gs4bpuddXSMwNADBVazRDCm7R0/227Otc/kPFjH4DFi+lutXT9set/c/7Z5yjsqO7CB5xC6rff/cXlZ52twFJL67h/sQCohk41JGoiqLyDv6KWcg/RaeDAqsNE9cojN+4e6+ymcl4thx0uoWE0NVPpQXc60AdMtOiZ/+24uTP+y3/ftXbprRHIeGoh81AdxAVAGR9xJz2QHzx9AxYQoSpUAgdwjXuicYClATR7ctnPl165h/E0TFZ76o4djzywTrtegaXW28LwmF/2ZVjOfHN2yYHHCFu0Be/kAaS+oxw7VGDRjKjqVDpyjxOvNq9LxwwljOIYfhlY8146unmUc+dwgFUza9XzUTjTPqcnFUDaz+6RyI95Ndm3dIO6IYKOA8gB16xm9FkNo9h6jMqsDYj03i7qmQkcvQt510A6G1UAZXjIA3o0pJ0IsG1l+0DyWLyYxjeHElVj2kiunYqboaad0q9Zm7al+b36xl4CYs1AFWi8LfkeoG8VUs5Y0ZBW79RRoqrVgkiQMtpUdUURXUeGNxKxnCFqrP6Oz9ry8KqVSsUQGXdqKESltQWW9pj3lbcouscnGRFomlVvrXd7mI50hpRUlBCZZ6BIJmeecgjW8EqMv8jNGWDVIax6SwZetVqqno0C2dx6IPOURgwSKzpD70UvDmlJR5WKy0Ylbb2H8oxwzozyvAbSkJR3pwZfnAR7zQ3EyuhtIaU95oGPv1JDHYSoqEW2VlgHl8BVUXBUrYDNO9hdUgMroHdGJ1CIgnje5pUWwPJho7DIQ8rFgw7UYSZ4O8PPZeuMJhMWewkK3n1kyK5efcPm27+3/dfT+x/9Ee5/6fznDhwZA1iuP5SHHrzgc+9Y+fj6zRtm3/ok1w7iSNUhD3gvdcBG6qp3AZBaERnAklRn3rLbVbjZuu3uV62YiOYmkZxvGyBUiuLIzujo+HUjqz6y5nWffwhL+U/eM3He/QeoFe8COmPbNn78jTedc+noo1ds4lm1q7520fjl40CNZ7HXLht/+GMn+eW1Jz75pmu/6L/AX6lbnp8UItRTuSqaiWOp26CTz522K9uZte78U8ph3mVR9qt5lhQJat6LIyOIUB79ta3N/A6AQypQz3yCw+d3zTbDT+7TucHuNMD69LotP554w9v+uHNy781uoXSvRPTUmy/72bKtm+9cvm/6qReb54/OW75ml4GXdiLOg596B02/LGync1Wum1mZvzpLuTbV/SbNcz3Kb7p3z759t0Q2v5J7GmD96eSRZ19YASw6a/avPnxWp/O7q//28Kv/PnXnkyP8NZpVcQ/38+yVL+zauveKqHLKZ8xQ0DQqY9VmVFed4qvghbrCkf3ipH4BS9dGaXtzuyIw6fADTBrsMrCYeO3BbTMra9/h8cpCFn3OvMMxCqyla/eMHHyCSpymcon7sSgSVI6vyTuHqepqVicqV3HkQmMs5admSLnWVH5yZgJMwCKTw+N+953HmT4AC2ZyBoIe/X7KFYhwXsRwCqzXH9/wlyVHgReNxDJf8yVVTUZkVhgdWPPLXtQnI9t9LVUTNMpP6jWiBP2fYazI1wMBFtinigoXSpQeua5DpwXB5lHCzAtgfejx7zy25p1ATXlOF2WZ3WieRpcyIt5CLKO0VKrrDDFfrtA8zS9ICNZy6hMByzUl7aXtgIkwqnoXZsIL8E1z/7sv3IMDAVYUEJ2HAAGNaQ6COnq4BkwKLIWXR3dAFm3/iJY1UDDUef9Lay/71OzU7cufffv6Y1/afuvUX3EJJfyVO+FLyg9/eNPE95dTzrX+Am3h2b1Przz63q9Hv0wJsPB6+mK8Zqo0AUt8wDv0s8KibSDrCoWisQYILCdJB00EPocRcFHoRMDS4OhpCxyjIz4KkVjud/eTIMVScuDwB3Zc81neAjgUWJQ7sCjn2cwve3JBWUrbpSt6gJKeX3bXlot/dQgV1QWIxDQro7EGDqyugGivjP4a6TCoG9C4Hd14493nblQLsFTg17wlqUWfLaouwRJE6D4c/MTUqn9d8q62wOLtDizKM8CiRNMrPhh82R4gKjMBLFI5WGWaSK5EemuoGssDogY7mucpBq6jmaCHv8gqqyl7hdviZD+TS12YAFhngMVb8sDKMxZ1djDptW6yq1sn+gmX08+0CBhRAuAY2NQKX8BtEVDmQWNFiKYSNMAB1zwToZsQsDATEp7r897/rQs79134wdvuGblP/6rACrfr4AaBmk7LcVXkfqA2aMaiDqqfHEa6MBxNofCFhkJ8QS9RzlMq5CO/DDXd0DxD5MVdJFxVKAqC/mvKRkAHllJ4Uc645K+e948sk3DdyY7SAkDNwIoYa24aizsppw7ACAAxJKJFFVzbFaQqCzOpped1Vqjc1pxN7EqJD5OxooDoY6i5Ac5bwCgKiIBPlRklvZzGeWhs9N633I+znVeQ9m1DoTJWFGR5L8DKyGqARdsBjeoqrulzShx8eCoz1OdNY4W4lkpQnk//AywNgspVHhAVWL1sAkZvDR9YOuNru2VFeUvDHzEE8LmEzySoQ41V2SEBC9s1B0zMBCOLGlCprnNAyoGXQspniPnDEapp2BgTAautxtJQ6MCCq3QpJn+2WEWIzgEBFo5X8a5/zUePechjZVKmbZsRhUK9dt4CUtzDdfNY9IVtn4UBi8Exlh7Y93yVL8g0BylVVA4yBja92nbxeJ41VhdjCa7nNoNQ3lIeYu8UHKbwcu2FdcfokXk/OquJ0/qoZwWOXhjLgUW5HomLDpQ2b2imV12qO2951irvBd9AEOXDBgIsHQ2gWxc7KW/7FQYqDYzQW8pMCiysBk3uQWHo0rUe/9fdAb5bFS5BUOvCSy95LMqpoSZCdW+CZ9pcdekKLGBiAKtsn9tgjvbSqTe5zu+P6ANjtd1Z2lZveS4+kvNsC+ZZDZdwgAIoOlFNqCIQTO786PRVx3phLJ7ld/TYfrQ7VIHO2oDvU4CZFFJYZ6l+bR/Ply+CL/rpJhndM+4BMZLzlAA1X9jRjbwRQ9TwqtgLzssDi/uZEPj49qO8vsxMuR5p1yCowrytln1FfyoSZ2iCVHlLWSpSXUBKt85p0NGpPvzhIRu20GMRcA/aKwIWQNSnosCB5ou2F3cdAKlAz4BRRZVPDhdgvcwStTKQrhgCLyAFyHQnVngGmm3K8tUahVQt8+XLLXr6BUuyQIEF4Cj3+3VjtAtqXW7SI3F+7ExDpO7OKMCa7CUNq/BS3qKc7o4OtRJQYCAV9dHM0YOmw0UdrAfF1Gp9dAd6tFyDVR2mM1Y/wdz7V3TKV5MnyQMhzOsZloQYHKxhTs/3ZcSmHv/XbSq+s15PGkYlCikNwfU+qsZz2Awk5l/K0Jp7aw6yBVh91mTKHwRHWK3ZAZqL1zDqu7jc+vF/P96uyknhpV+YRphrKsEtuz/0oOnC/0zwGfKddx3xugMCeDVLXUAZbW72oKZpC//Yhn8wSP+qC8++d0pzVJ5WaLtQVoA1QCYDWLqLy8c6ekXDTfNBdT+qFR2sdUsddHeUQkfXMDwRmj8QUYA1pM+QaJJC8/UKMjJn/mFFP6zh++v9+Khznh7iAPQEQd36ovurdB9VYazJhZy80ONliGJPNxCqHFi+rpc5l0w5vxmtY+oWF+Un3ZpHyUJOh76i/5cO8NKjsJl9EHlgKXvpiaAoNeDLvVFAzB83LcBaDCpNjvb7R8IjYOlR92b4RuLdA+LiUlcFWG0+iy17ExxYlOtidnM6AA5DY7nS0oCY+eZFAdainwTohyR1RU8PRLTNMGlSFAvIlM/yx00LsBY9yPR/ZPSSrnyZNcRqzqiAW4z/Na0AaxGE4/IfVosttgCr2AKsYguwii22AKvYAqxiC7CKLbYAq9jB2P8DXOdai+ReHDAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU5OjMzLTA1OjAwnQdy9QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVklSLnN2Z45XGdoAAAAASUVORK5CYII="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/1/2.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/1/2.grid.json
new file mode 100644
index 0000000..8dafb56
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/1/2.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!#$$$$%%%%%%%%%%%%%%% "," !!!##$$$%%%%%%%%%%%%%%%%%% "," !!######%%%%%%%%%%%%%%%%%%%%% "," #########%%%%%%%%%%%%%%%%%%%%%%% "," ######%%%%%%%%%%%%%%%%%%%%%%%%%%% "," #####%%%%%%%%%%%%%%%%%%%%%%%%%%%% "," ####%%%%%%%%%%%%%%%%%%%%%%%%%%%% "," #####%%%%%%%%%%%%%%%%%%%%%%%%% "," ######&&&%%%%%%%%%%%%%%%%%%%% "," ######&&&%%%%%%%%%%%%%%%%%% "," ######&&&&&%%%%%%%%%%%%%%%% "," ####&&&&&&%%%%%%%%%%%%%%%% "," ##&&&&&&&&%%%%%%%%%%%%%% "," #&&&&&&&&%%%%%%%%%%%%% "," ''&&&&(((%%%%%%%%%%%%% "," ''&&&&(((%%%%%%%%%%%%% "," ''&&&&(((((%%%%%%%%%% "," ''')))(((((%%%%%%%% "," ''))))))((((%%%%% "," '')))))))(((%%%% "," '')))))))(()%%%% "," ')))))))))%%%%%% "," ')))))))))%%%%% "," ''))))))))**%%%% "," ''))))))))***%% "," ''))))))))***% "," ''))))))))*** "," ')))))))))) "," '')))))))))) "," '')))))))))) "," '')))))))) "," '')))))) "," '))))) ) "," ')))))) "," '')))))) "," '))))) "," '''))) "," ''))) "," ''))))) "," ''))))) "," ''')))) "," ')))) "," '')) "," ''))) ++ "," '''' "," '''' "," ''') ,, "," ''' "," ' "," "," "," "," "," "," "," "," "," - "," - "," --- "," - --- "," -- "," - "," --- "],"keys":["","65","173","49","34","33","42","181","10","227","73","193","13"],"data":{"10":{"admin":"Argentina","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADtUlEQVR42u2dT0gUURzHpyA61BIUgtEfNvBSRGHRuVOEdAksRPJsWOBBwouXoEzyEIFUUu6pKIpiIbMQkZAwiKVM+oebtpq0kZbChh0qtsCvh98y7vRcXSHmc/kwzHvzm5m3H37vzZs3rHeq9u6N1DCES0uPJoCIBRELhpVn3nd9GD+JWJCMBRELIhYNARELIhZELBoCIhZELIhYECIWRCyIWBAiFkQsiFgQLpFYWpZll2jZbT/9pcHH+peABS8QW0z8hcZxrx/cMi53VOz7dbkelxYIjuxynXNiZSuyX/+c/pmYKZ2u1bal9vtL7Z7g0nwxXc5bWHyXOIWdN7imS8zC2mShVxt8Pe6R3a/KX+q5/Dxhpm1i9x8MItY8zDxM9yWjYqqyf9W9Z5MTQ7H+pKg9Kp2eHp0aiNNiiPWPzPQl/bq0+7IESu99/vl+dLztSerKiXRL4u3tFaLdo5o6ikyGWDn8Uf3t16dGyWFlEsfijycvbkmVd/9u3jj6ondt6x5t5wg3e5QiKBqt6qGUujPJIVGkznDdg/1N0Tcbridq6sXBgx1TR2qSJfGtDWWi6qR6+jbFHimCchh6eYylbAennPSu+87Ouipp9PT7+fp9vX4OXL3WVLlLetkMpxEYYnmMqKxYVqn+wbOD2zOvyi5UlTSMHejoXNcnao9KB3a3l1ec01GSUtEYdXlhzlVWLHV/6vKUk4Z6Lm1bv2NmpLM60mWZWR1fEzmsUtXUUYpgx1s6C2KFdcA+K5ZGS8pViUMtzZsrJJCy1Ei2/VhkpWTStkpVU92iIihvMZAPqVh2RkpDb2mhrk2dnfLTx+OxukirKMm0rVLV1FGKYEdaOgtihTRjSQUrls1Yfr1sh+jPWIpGxgr1GMtOMdjJBf8Ya+LWzZeRo6L2qEO0YywrlnIVY6yQPhVKLHVbmgjV9IHyltNToW/SwU6Z8lQY6glSiWXnoub0mpUm3zyWnSzVgF1qSizmsRCr0b4Z1EBeEwd2hl0aab5KtM+A9k2i5vERi3eF8+llnhYlmbKRHUVZ8q4QsZxGXXYywqqTs7rB1GSeHbGcqO7MrseSOnYFhF2zRYshFitIl0usfGuZXRrUvU7x4rvUDD5j8Hnd72J57ncxXyEUo33ylXru3+G4fL3jXrMY8d2/LVno1bp85bI895vv2MJafvHtky8C3xVCPliFiAUR6//4Cw1+KsSCELEgYkHEgohFQ0DEgogFEYuGgIgFEQsiFoSIBRELIhaEiAURC4aRfwGxCDVrmSqS1AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTc6NDMtMDU6MDCieqKlAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BUkcuc3Zn7T9RMAAAAABJRU5ErkJggg=="},"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="},"33":{"admin":"Bolivia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABEEAIAAACovNt2AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZ0lEQVR42u3aoU7DUBSA4eNQEzNVCFRxYDENjwFiz4CChOxVJnghMtkgCE+Aw0BmECCalJLbreva9TOfWG633tM/65I11uv5PM/Jbg0joLAoLArLICgsCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCxSWON3Nlvld1uubDo25T3TP3er9fF7wBitbpgDM95Oby7O7sluja/n8iQ75yDclC9Z1uTnR/mQXf+/poVtz6GyJuVMIn1jbH3BJjwHYe0zr0mHJQIKi8KisAyCwqKwKCxSWBQWB2fHfw0Ji76xKCxSWBTWFH/wCosUFoU1kKc6f258R3L7O9CDnDGJmFo+V360YfUYWdRHOV7rD/zXX6+OdZR7rOSyy0z2bdw+ra4uF2S3RsTja/E+ZpfLovjL9PWHOuf+59PnHhsvDLmLRkBhUVgUlkFQWBQWhUUKi8KisEhhUVgUFiksCovCIoVFYVFYpLA4TL8BaXQsNkZuXjAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI5OjAwLTA1OjAwkPPgcAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQk9MLnN2ZxRPa/kAAAAASUVORK5CYII="},"34":{"admin":"Brazil","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABGEAIAAADldHp9AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHTklEQVR42u1dXWwUVRjdGiQYfCiNsmmJlfqzqAGxkbYKwZjGUIk/JBIWMfFBIrpW/GlJfNhYSCQEESNaEBRrmq2pCJXSSLDaNJUEUwMqldTaJaikGqsSElATrFHQh+PDl1zv+M3cO7Mzs9/LyWZm587MnTPnfvfc795JJC57Y+f8BYKCllGqQFCIJSjEEhRiSUUICrEEhViCQixBQSFWAXBRc3f2hplAqQ0hlhGWv96RnteyecfBiYr0qcr84xd/CcQW7JVaEmKxsHRP+3BtLr3/vZHUY59eeezC1Pzf42P1ickUL2RP9iRqsBf/xFFSe0Ks/8AbG9+5aG7Xa6Mfr08uhTJRGtHfdAv+iaNS5Z0D1a9KTQqx/sXM1b3rrnoy/8LIm1OOUupwkCoZSkBpomGJYtanvZ1HystavZFJtxcahpJxFiFWUcRPzQf7ls+chMdvTiNnDcNZcMai07DiMQsOnPysoXS/W32yRT6c/ZYf94zOWSjEirxZsGHyR7fNyPP1yZk0at/QLeJKcFUxNyzi19jd29jTft191Cxw++APL/zg6/Kpb3e016V+bq17paJmxfPZTefqpgGxBXvxT2/EpYaFECvU+kTNAj6N3q/uWlA18OgdTUOL7pr9UkPpQ79cPrtmdPU3iQ2zpjzT4oyX/ll9pKUVR6EElOb2GmJoWERdn6hZwNEnPEjoTf309L7l4xwCuUWUvHtbxyWzPuRrGO4iJqZr1M0C1czU9dG+qPqkftq1D8zL3H93mx9k0ukZzoiz8+M5aFiEDYsomgVUnzgBNZonNFjBUEpFnB1X4jbmi6TpGv5LRBedmgX8kByNET9m8htxJW6byEgaFmEOxp9N9uevGHNrZgKhDeGhlEovt+qlGhah1rCwXRA1C9yakDSWKmzDx28cObGXrh5QS6gxIRbLLPA2kIJjgwzPzRFXa2LeUsMiRKZrODOfvA2kwESICqVozxFXbuLphy5LrLCZT94GenX41Jl1uaaVDdlVE23XLMk0PpJLpjeumXjr7BOVLybeHQCuXrFt065WbKeIo2pPL/tr66HUloahzTfhkQdDL/heJkNPOsOiYKZrmM1MPu3Or/yh5NaSzuvz5/q6DqS+XzW4dMeJ44u7M0BswV4gtg+W/fTtcPPa4c/XtM3B7zMlv23/9Y+eh4f7D9+8M9czMDgDCFKCdpV7b/99Y9YPevHDeX7tFSxLLBizwFvmEx/Ht3+X6+oFgYAgB0VQipJGRyxKO2zB7619x45234kSoHxU50A+E2JhUMhWAo+KeAoBGRZhMAvMc5623LO7qrdJ16glM/NPrJ8EpaGIvXMPLXnw5SSaTtpookyqYaAXflM6grIgK85u0k/01n1xa1j4niXmX+aTSdXwzQXkF1C/Co/WVoMFUoJ8lHCUaiCWjmT8WA3/xB0514At2iHY92Vamx/TpPiDLW73qiWrPcFgQm8QFxoJqtHmEs0xfmMvX8lwRyYvmzfDAk/QWrBv1yzwL4rSEQvZUYW1DKBqaHwRe9GoTiUZ9E9XGu7IFo10L6euVq0ZFrbMTD/eJw6xkHxXWGJBvahGQjXRdFINo6oGtVOVFSG834GE8/8tmK4mxLLb3jvP49PRy4RYUBqTcJsTM4FkNDKjvU5Vw3BH5mnQ5uQLlFi6JGC/tUpXpnlTCHoF47CDxKAXdAskwxbspcTSvU5+1LPaFIY0eDd/bzjpe8EM41BjwlZpiMZoXxL0OjVy/LnFQ/69irrg3fIUj2hNtOLYDVSH8AjLltVWrH3aWz8RR6EEqm3mvU6UgEgLlIJnhlEEP2IpXY6XL/kR/g3gUIPUPMB3ftvUJBkE1LA9TR68qlLYYku9qPeG8YPz/WP7pn9lN3hXLWU8HR+zIaI4pKNWHHpSIBO6/c7NEN8+VbXKlt2qRnjOQzrmGPkhHc4gtF2pR7IvhwR0kKdQjhfVUZDMOafUJGCP7SA0J23G1viX83QuXdPGQaigsxbyEdcAintLm3EOGEKxMEl4Ev3cxli62Ticpo3qB91iqwF1tlLpoBPQeXoFPzalycpFl+jndrTRrW7xU5NpHoSJcUCpSekCZcJeUApbKEElNTnQlRe8TfYCBjmZgtJIpRoUEXtVtaOTKbw5fzKZIqDpX2qzGMz0L11vFIqodhRoqO6WUtQskOlflies8jGYCas6TVIHp3UTVjmRpUxYDZFhYXeKvVu3nQbp3qbYR3hd03isIMpZ4Tj4RUFoeO48MVXtrER+FdO4rsnu3KzQZYz8yDVFyRx9UtddjslSbPFY5gsJtW7nUsvCa0IsC6s/2FoqEmhrqciQmgVCLP7itnaTp/kTRmRx29gi37AwSVF0PlaW45YPCFjLGUcwjjMW3dfC5JMnfsx+kU+eFPVHmqhhQU1Xb9mb1MyUT1/J17+MFn+Tz8oJsYymtanOfsy/KyHECtKwkE/3CrHkY+NCLEEhlqCgEEtQiCUoxBIUFGIJCrEEhViCgv+L/wADAE2QfXfbBwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mjk6MTQtMDU6MDCoFsT9AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9CUkEuc3ZnU5e+DQAAABh0RVh0c3ZnOnRpdGxlAEZsYWcgb2YgQnJhemlsnLDlWgAAAABJRU5ErkJggg=="},"42":{"admin":"Chile","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACcElEQVR42u3aQShkcRzA8XddzR7YJoVcOIjLpJAjtYcNSRIXDvYgZg+THEiNcti0tRFxkJs4bIhEidGSGg6aLcZBkWLmgEREI+04/C6vXm883j/NPN/Ltzm8mf8079P//5/3nqa5y8uml1K9U56VnpPq+EM8Fn+iyVANWBRYwAIWsIAFLAosYAELWMACFk0xWPkVDemLha6xysif/8AClrIOPE76D7zCC1jAUjZXHZ1F/Hfullh/ILgALGApqGCSYeTEAwtYik92uP14/eZzTqj2y3wPsIBlWtmMm1W/COoHkznM7F3u0m9ps5vA+qCwBIGvasi7Gw5G932XbcbK/GQcTKiZvWv068zq4fDbZjVgOWrG6vaM/f43ftl0vRbLs/MlhIWdf47AcuAeq9L3YzuwL7OO9YEj2kXa/YjMfPavcgHLsZt32SHNhf4+nc4lHlIWSuHI5h1Ylir7pMRD7uUeZV7X2NmqA+vDzVjGbbvZDqzG1fVrow5YwHqhAkX/0cs7wYJopyx5cmNHj0zmNmABy9IiKHSEkX5LLq8bsnrrt5rlooPaBRFYDoQlOOTUWlng5LKCHK9qCw8sx8J67YVNmcNU3eQBFg/6cRMaWMCiwAIWsIAFLGBRYHE6gQUsYAGL2oT12ueiEh+vf+zYDhrjrSHjJ6uFZeXRRVXHvOf3ec+xtI6r4pLW76newM/Oxr6y89vRicEMmgzVwodF2bmfKFVbYFFgUWBRYPFDUGBRYFFg8UNQYFFgUWBRCiwKLAosSoFFgUWBRSmwKLAosCgFFgUWBRalwKLAosCiFFgUWBRYlAKLAosCi1Jg0WTqM17OTk6rT8x7AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMToxNC0wNTowMHpPW8kAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0NITC5zdmevPVD1AAAAAElFTkSuQmCC"},"49":{"admin":"Colombia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJElEQVR42u3ZsUrDUBSA4Tvo5tCAZHKUOLto+wBdOnXo4lLwCXyD4KA4S2kfK9CWvk1LOujQIgGRY2zkW74h5N7ce/i3pO12ucxzMtZkBBQWhUVhGQSFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWOQPw6rr9e5y3EUPr/FXX2/nnW7d6zOssnw9n2zIWFNKdxfzh2j7g9nid3Y+5dN+7POd3bo4n6YzNz0/WkZGaQQUFoVFYRkEhUVhUViksCgsCosUFoVFYf0rv/6ZZ4uePd9P32/JWNPs7Wo0fCFjTaub3tP1IxlrqqosKwoyVmFRWBQWhWUQFBaFRWEZBIVFYVFYpLAoLAqLFBZP2z3VWG8eJgK6DAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6NTAtMDU6MDD2X1rqAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0wuc3ZnsjhgTQAAAABJRU5ErkJggg=="},"65":{"admin":"Ecuador","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2bfWhVZRzHTyt0f0iu5gs5S3S2dPiSrAnpTCnIVWhtLigs51wrt1kWFbGQXJl/pA3Wi2Nh5ksLRAqckbXUP0Zjki+9KZUta1AxlZSCojGQgvu5f3wvj+dy1zZ3zzm/f74cnvuc3zn3eT73+/ye3znX+7fr9Ckv29R0cNWzITA1sEyHWy92/+Z56QRWyjdkao5lampgmRpYpgaWaVSScQPLthrmWKYGlqk5n4EV8GkOBY7mWKYGlqmBZWpg2UCYGlhDoOeWf/3RtCtdvdD9Td/VfclT6YSetiscNrDSYKCBoLOsdV3eqsbW2rEPF635+dGtVYs4bjz+xsi13UfnHDjQUg80Cfcsxz9NPr7xkRH059yGKY0Ti1vQzhWfFJbmdfV05syYFE3IIvFVz1QeOTSyrTWr4Zf8b6uXLPts+j9FFXk5k2fO33jjuzfsQIt75vw1bS7toKZwAGJcY+3AxFkahwi0o1yRc7kTAyvwVauvej9cPerHLb1rn87+gglGdeJBQVWBUA/zg8kFy8WrNGP+tTm7gNt36TSwgqVMJG6h6mIHHEDw1oyq+6f1vF+/40x5JWA1ZG7LefsZP6SIQLR9hxuaxiwiPqrXNccKe92Za8X05KaO2dfsBQvA0iWSxVHb8SRaOCtq6JhjxZVsCb3E9AvQfAouuqhxjDPhSb5xJJpe18AKLVjxXVsslXYh0BYFSxdHkErFC+lJlmZghfbBM5kW6bMuWyyCqOZewKRJN+pCSWSNgP7Qsacma5MfiOFO4b2wVln8FiB8CHTey9hdMuWKinlv9s18hWMUFGjH4VaOaFxe2Yz3cAxeWkTVCJqwg2Mq92aOFZh6Ons6Kk84CtAw/dWvf/z74gcULPoAUP6xzbO3t95Vs3fWkVHuccXu5lXvjCA+ACmmKHhxRSJzJ5wVL8AaWMFa/pi2eDEzBgrOoV7lOhb+BDRghL74QtOYkjL6KGT05Cp44aj9G6YvGI1quUGLF1TtL1HZD9Ezj5AXSON4xZYtMh6mWX1lXHZVe1Xd40cLuson6WJH1YoWziKOLouqRFCwAFHzLSKE26siARaKV7lgMf2zSqv3vdoDHMDU9NiJrF//QHEmt0WVsx6cVzdu80EwJTJXUbDcfMvACrAqWLSoYxXet+DISxsUqdMXz353foWriperRHDB4oqhAsst00QTLNJn3cHNXVxTVtcLUjhNKmAlR03BGrulbGHTsZKzzx164notuiaUKkIOlnYKn8a+pG7+aWHKSy4UHnyy7YPpBX/fuRW8eHTDMYk5uCRXenIW0YgcByt2J1ohC98Iuy0e+x3VZ+/YvKw002330+Hqn3o7ez39PeFYTP/n9xbffXu5pu1oHD5H9VM9JsKXHTcfLng5ASwph3InqX/3/o5b+vTxMj5dmrvrZLgVjBQsppx9HGAxHFS24vWtmA9pS3J1wQI7XQrZKERhzCMElj5CUccCBbBQUMCCqhVKix9Y1Kj8HCs6YGVOXTJ+e7MX1i+mLUynPsVTsE6dyN1500KMXUFRpLTm7gcZrwESjW1BEMFyR88cq9+ONb+2aML6q86fm5gzNZcFUV3quo21q7eVogDhtqMARISgg5U6XqOPltXvbDewEsACIIavbXzuyltv0wVRYQIOdnycxbFCxlkgRTQi44K6K6QMYTnWyXCYdgJYUosnAim8+paLjp+CmnoV0YjMIx0Fy5L3UCnTyfsF+iIy7bgI9ac/9+csnTIBUKhp0cdVPsXnOOu19vx199wCUprV6RXB0cBKC08aeDpJBPcFPZYqrbyDF94DNLyVoC/60cKn9AQpAOVaCZUzeVsreV4yvOl25MAarP6a8ei7D2RIVK1I51vWr/l+TBEAcQxSHLvKUgtY8as4jzhoDwoWA/8BeOlzQ0Pxa9bIOJO7IFJ/QhUm3q/yg0k/1dcJ3fdCcUoWx/+HUXrC12+whm6Ch+uXx6TqMQsT6bz7p3v90wTHKP/PUbxoYeEDU42Jz7lX57i/4+z2T2fgvHSz0MFVlid3k6/TjJNpz/huLuY3QEMWxY5P33ZngSMa5xLNBcjV4LpRRHeFqUyVVqp0mqlaue9BUJ0CLE3McSkFd+DOFKxcKtLlhuSLow4u7fgQSg1dwQI1PqWO5U6Pe5WojXPkwNKiQPIChy6OWlbQT7W/n/a32h6OxdHj95TKAKWDXs67jS+O8qAa38LDwjQmA4ngd6635/n8p4ofGlylzDgUkS+/ApOChbp98LNwfOuBq8czMh5KDK4OXeRwK+MW3NHjzj39Gqamg6UGlqmBZWpgmRpYNhCmBpapgWVqYNlAmBpYpgaWqYEVFXUfm5gaWKYGlqmBZQNhamCZBkP/A0k6122K1m2UAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToyMS0wNTowMKBKMMIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VDVS5zdmfL2mD/AAAAAElFTkSuQmCC"},"73":{"admin":"Falkland Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHUElEQVR42u2cf2iVVRjHt6SZUQaLMmtgFEIuXSAZS6W/ksRsCzLKflDmmLiItUz6gQyTYTpzaTVn1sBky5k5l78WVvSDKasNcnO0moXYMgtZEGERGgb3c//4yunc3rv3nvu+uvPPl5fznnvOe8/zeZ/znOece3O+rZveN71vaKDls23jz7x8uvrMPFObf9k6s2te4QeTe1Y8ljMhp6C8LLwW7y7uWlVO+yeuXP7O8gXdh3Kvz51gKndPFgwN/fEVn8rsM/Dtfp+696o9/wzsmDtl7hH67Tk2Zv0ltw82Vby+uKfv+7bL96y8Nm/cz0vaMtX7Ba4MH8qwMsQmXn1r+o+eKF/90Kr69rM39U4aU90f3qjgEgQsniE8WLwefAtwAZ3enoKygtvMcTj9509LB7cMLhusOjk7/LceQWoOLuZkcG2ejIHGPMPzZOl6rDBgpQuT7aXyHisNxVS2QVfIbIM+PMhcg2WDCYzM74XvzOzLM6I1s292cGMoWPQSPsZK/fzuXhKv/wNW8Dc+PGSZ8lgepvMMLNeQhVkVuobea5bAchEI7/it/bKujnQ9Fp9yPU17zYCSxRmegkgSjqMDhd/UsIpUZbluy5ClBkszSVofdLQXG0wm0MP7puDoV4VpKIaPVoME79E+Yd+0np7eFndglUwqmbzuu4pjtfkfH160/ZkjTTNQLUl9N90SenQIFlNJHNQGFn4rDk94zabcWx8e5cIMGPuWxlXLOjsnjK+p7K5GzRLVK9577kBHrVl+w4rqU19cnbocH+wQLDWezbRe99bmHc+f4xosQLn47Wc3HChb8OPmfb1jmb6bTh8c/cMugOBuGKUXenQOltc4gAU6INU+59P6T2azFmbZQQk+DDgUxHQ1YrBGsifT754dsNRLEdWhgMU162KWFOv+3n7pR/euPfj+2K5c2zUgRuCxfIwVRLPpsUAByPrvOdC5/3H8lnovIGMlriWq3GUaJbpSP+ccrIjXg4lBSb0q5G5UT6jJFHerQsysfgUUmP4UFE2gmPubtuyd+q0sgaXZGnWz6eaxbBok5xQ8QWpTIDCzaGqMeOaxaNMM3gErmRAWsFLDZN7lW0cAVpjMO2+D+fUwKnlwncj00FyYzLutX/OwntkvT57ZA4MuVoVcJ19gy8SXPM2WKKdEPRx3uTZbjgVYQWAKblSOywU56AeU5iZ0us+je4jm88Rhe8ecCnXtppET6KimLudVNNePtOxwL8G1Z8rU6QbbsZnhQRY3T2YDCyA0AYGy7rOp1kmNbJY8Fr6EIbbN5cE9U/DzWJk66Dc8zxoHT2ZOhejoO54q2vUX15pKoCZ3TaXOjW9WlTQtQrWdLIGlJ0jDv/EuTpDSS7o/pjhfpksN3k2k8lpL3yrKQ++vLd1Z/CGq5cGVNjXGcjgVRjXo2fwxRfjpcve7u0v2TXSdbtA8FvmnxevrJjbMt4GCZ6oqqju79HlbHe5SU7P2zj2Wa88Uhx9TZAoydwlSoihg0iCE69YHmy/atAdfBSjJ8HzrloWL7iKJii5ZWJnzxJ3UpI6ZjKAX55vQ4MIgkl9GKeFNdTEdgAhZFtDR3lW/XPlC+QM7qZnZ4PqcQ4uJ9s/JbidKuo83VdSd2rp/yqlZ49yBpckXG+hghCZTDInrXyvbbp62GbB4WkrMhKoqPToEi8EylUDe9dE22kdtT6ILi+w8CUqP2RkH8lWYHI+CD9ONHQULH2aCZdahBZ1e9UfIMUqQenW9CW2G8Ex/AAQ0WtJy6JWGJxu1BJ356Ev5zf9xzIZeYpd59+pCOduJXwEmPZoHNAoQ2nrd05+X1mmJKokGXRAAFtfOT5B6o8ZBmXBZu73W+MZ9raP0wAwlBOZAg9cBNXwY0TB10GTWnkM4cmCQzzr/uwBv1PgocGgYTgCOgpEeAAQpLWnb1/DI2rMoLWjUxVa98+jKgxU3ZXrCS+kWDViACxhRZ2PHmrl1MwjSgQ80WUczUaIb7q46vGAWn3I+CXqw4qYkU8yjeUx/4AJeJE5BhFiKiY+7ChYhvB7CydJ+qDdnHJSMGv6GzCI7g4TboKMeSGMpVe4CFiWgCay0jJ9zvhPqjRqfVSGG14kPvIAD36PoqGfSEiY+cKQF4jZOoxCrrd744tevbvNgXeBxFUgRboMUuLAnoas5vBo1k3sSCRAp0aQonyKu0k9p1OUw3vKmjTbFoMkF/AqREH4FCEDNPAhu+/uCZIoB/5doWbGrLyytKZqK4recTIvewNFOf5hckwuYH48CWJg/9QEn/csTaoKsCROqERtPkuGdUG/gaCdBkEr6pwQEBNeqZrn6OZQSEERBk5MObOdzrWDRr5MJ0Rs4KmVjWyFANckJRrY6WqIKOoqRwqQ1adnJFrs3cBz8FgCBC17E9FiKGn7OhMmEjIyX2b4P3kdQvIX/wNjqRQiuKdea6s9Q7lJTdwNpjXJbLx4srxfiX0V69erB8urB8urB8urVg+XVg+XVg+XVqwfLawT6L3cSIOlxCsQ8AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODoxNi0wNTowMB6cgh8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZMSy5zdmfUtOFUAAAAAElFTkSuQmCC"},"173":{"admin":"Peru","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNElEQVR42u3csQ2DMBBAUeQNwBV4XbYBsQ0NY7CAKagREpWNXwp6lJf7J4ekO46hn8bar+e+rNucK3/dd/GPd6Sr/jZyDCmABRZYb7ByDCmABRZYv4RlxyrsQw4WWCaWFIIFFlhgSSFYYIFleZdCsMACSwrBAgsssKQQLLDAsrxLIVhggSWFYIEFFlhSCBZYYFnepRAssMCSwufnzcECy44FlhSCBRZYlncpBKtNWJ8mqBRKoYkFFlhg2bHAAgssKQQLLLAcN0ghWGCBJYVSCBZYYDluAAsssCzvUggWWGBJoRSCBRZYjhvAAgssy7sUggUWWFIohWCBBZbjBrDAqhJWo78r9JWOiQUWWFIohWCBBRZYUggWWJZ3KQQLLLCkUArBAgsssMAq/u/qnWOZWJb3dmBd0YiNyfSUGOwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMxOjMzLTA1OjAwZmX4QgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUEVSLnN2Z5pF7JsAAAAASUVORK5CYII="},"181":{"admin":"Paraguay","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwUlEQVR42u2aS0hUURzG70K0WvSSiAoj0LSHopZipUEQPawUC5xShECKivFRupgKlRB1QLNMUlHLAs1HimWTNmo5Eur4IMXJgdBZjM8RIUJKyBZOMN9djIyi4t3db/NbnHvOgTnz4/v/70PoH9i23dOLJKWlwCMgKRZJsUiKxYMgKRZJsUiKRZIUi6RYJMUiSYpFUiySYpEkxSIpFkmx5M64LQEeyYvIM6FYq5JGKrEo3wpiOR4lSa6bwpDWp+dUJGlPY/nhRyenwOWuOs5cbr4czspxXPi3czx00kCS0lKwjlrHrWMkKS0p1hK0uFgOWTzbwnXmds0Xoy5Qp3Kk9kSnSh+LmTwxirUCu+L1Bn0jlDJV6jy+JphLqiKKFTNxRQvZ8ZNm9fGc6OnWSv+K5pHMVn3jJ8zEKp4exVqCTbe04W8fGLd2OetV0MgUdH74aLEx1zvdvduUeu7mfhUojtiuYiZWIcl4khRrUUpBjokP94KThJHoM21ug+N7lEmKfOTTnHZgtP8jiBFcxUysGgj57tPdghLJUxXYS7VH6+bbnZA9olLNWVGqUmTYnV+ZAWoXSINV2h99VRoFRjATq7ADcou9l8CsQlaZE6LqAq8ih8qUtcn5YQFlyuuBjy/9TelPsSqL8nfV/87Oq3Pq8gfjNpX3JTRgJlZhB/Re7LpkLRbSBe05UmcosVB42gSlYt48cU2cv3wwza9lY/Lss293Z0GMZLjV1rzzjT3wYubGNfsdUCjZbwlyLoL4++2L4KCnpqTGALGQUiiF02c7gnY4gxhBbl3JyJ2IdMUqtPa4c2RBlHViodEW7wFtWjiKFVpw/4KmqUFX7XQ6FMRIauXrws8PIRbKH3aw77SYWDL98WjbUbzwEGHK/eVwRS+KYFhB6j5FBJIJMqEIQikUQbEUdpRuKPuJHVgKKZbYvONBKF6mmp5f/BNTrO/pNrXmILeQScg2ECO4ijtErMLLV+zG5p2PG8ROa8qS5Z03j2+JxjYnHUm7jbvFrKD36Wq1PXE/CHUwE6vQXSEF+biBD0iXeECKL4qQQyiOYheFlzy2woerUAqrsAOzimKJxGceyC3IgfRCaYM6Br/dvb5VjsRMvtKhWKtKL5QzpBQSCwKh2ImJZUsvvoSmWGvuvey/dHD8hAZX2UtRrHVxNMSyMJfOc1iDWDgykpSWwt7OiGCNF0lKS0EQjrlXvyJJqckjICkWSbFIisWDICkWSbFIikWSFIukWCTFIkmKRVIskmKRJMUiKRZJsUiSYpEUi5QX/wOLYG3efhMEhQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzQ6NDItMDU6MDAs/jGrAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9QUlkuc3Zn80bvqQAAAABJRU5ErkJggg=="},"193":{"admin":"South Georgia and South Sandwich Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAIG0lEQVR42u2cW2wVRRjH25SbFSJEC2loSEQgRGgFJSHwYLzEkIYqQjSpXJQi0FLC9cXLi5gocjOGm2mlYNLW2Fp4KgmaAIaoUYNQsLUiDwZthcRYLTHYtNRg4u88/JvJnuw5e+bsntN5+WczO2d2dud3vu+bb2Y35+qhxVfK9vxZ2Hzxk6mD+d2Tu0r/zb394eBHqrX9x97/4tbD9z8y7e1dOZNzCtc/H1xpjfZvTNjRsqPqu/O5o3JHmcpZatroA3d3c+LJEyc/4Glw3UtHx50d9xQlncdP1H/aO+lCQdO2Sam6epYrj49HyUPkEZt4/XqtK6/n4K6D75w5tWX248UvvFaViWDRAncBLl37qvduaP/+atHuouUKkz4H7j34XQ8jNR9uopAlN9j+wWo/XN27fWEQsLxg8v+navvs4ls/1jqLlYAGf+jJWTI/YNGH5CxW/Pvyskyp/fME19G/jGxZsXbinfzK566Y5aUnF/SX/myejYSGNRg2XCFYZzpMqqCzrGhJ97LOefOL6xfPmnBiVveKnZRoeaTBSq37iD9IqbJYqYUpalEUYC3fWv7XmgOVbWVTiuv1GLAywGKl060EsVjZ4eb8u0LwAibViCLlH6zUQtb93vUxN6d/3N98+3ylH7Bon5r8KvssU3yd9uK9OcUta3sfKCpcdGTzrAt3r6ME7CLac4YqOW2v6yy58TXD9vdjP5RdnkI+TNUrN6bq32KhoKNX8YJJld6StUr0TvlVuLPCGWfve2XRIcCac6ykdO4tSoCM4wihxsCHqyQU4luscHsYhXQDjk/tFlq19eVLYxsVuEiARe4qXNUJgVcIT82OnpmnZ54Ocq0gLaQHrPhWB7CAyYQsQmB5DadT1daeu46NfdR2sAxSz1QsWbr3Dy+8cHwo/cEJAhnlXpGxRpbWnaaDxj9YNgaDwVYlqttXf+Bc6xbTRoKOV4xlggWmuHKO0dXfbtpfV2sRLwdNuBaLYWbGigIBsR3Db7pCLwUvagJl+51zFaemMnGhZcCNbxdTFmPFjzyCxCV+fus/xrIRRXnVN8vtxVgMM3NPhp+kDHZLg/enj89/86EGUqMkS8lyqYILUOpcmKukaQoShVkh8774i9Bh9Y0BJpmSniEhHlLnlfds3vq2QXVkZOPAhbOa7KWflG/7bXdRw+ecTWsyIrmMjpnH8lI/OadE81imAoF5LYDQPJbXvUQnjzX+jXv66v4BII4VLKwUWn1m85ntI4nMKNFf5RTkFFz/RiHLmMw7MYEXTOTB1ZFhmcibB8m8m9dVsHSznnnd6C/jKBYAgYIIAOEKdYlay2OtSQscp2k+mNxaoX+YUHNQ+YcFWYTW/uCq4vdHW/PqT3RWBofYGIAQRHRZWvc4UBK7F+NXwEr7uhXHYgIlnTDZ2DaT2n6GZcmACdemDg4FC4JxYiZ1iIDFr3TGB1j8VmeOJFcb+g78lFfz0ug9K/ObrGy88dqCYnuQbO/H8tP/+JYsnZCBAnCUFuycW7EGy8Qd6Wxx/0BNX+M4dYKqYAdeaqVAqrzjyS9LmjXFSj6MPV4Wd5AGdyuJ7vBMNMby3346/yTBVW0V0OhgY8/MuMpUPauxFE8DKEFqxJwRT4z5CrVyX+mEKayXKZKzZGbgb2NWaDpBdYUaFeGwFB2ONbOFBUK1t7hRWlakLIKV6MNN1T84Cm/p+Ldk9pZ0TLBwZxxzRfqMiyRCQgs3rlr14FGUaAmlJu2AlKaHwE7B8lphTMFbOjxEHh+q79PZcAe0xq2SAtWro5ogpaaNPnB3tI/9RinhCRy/vGLp7DobcyjskNobDcY5q8CBjobeplKTmExzeCh2zrrd0mkn/x4UU2wvJajXQr16gtqOcryuqCU2rstzBh11ZDFn97/tYfY3792VMw6ON+2TQsZZahLya+bdyxVaAcu9AReuqjXCliheKGABCqgpUgoWZ6mp+xeYdZphuwMrCxVbBVKHm5s2vjpg2i0Fi5rxHaLWwW5pOsMLKQdWlijuXmMp1Iy0tI5CY9otjtX9qcXiWMN2M4RP8dTEDXNYtkoXkgHFBEstGVkoXRpXsKhJeM5mG7VYgEUEBkbAp87RWawMVqyCuUMBpYT8OEo5bxECiqo6QUoAiGMzj0X7wMSckajOSrrBDXb6wSKJgGUyXSFI6XvPWJrpv68cKH9dl3ewc5zVFAPzWXVtlGDndPHHihN0YIUbY5mxlCKFqvVCcXnghWXSdKi6Qkp0+Z9fUceBlbV2i/3piheLM4qXIkUJdTRfhZtTe6bLdKwuYBFJ86Iaz1l5r9oNc1jKoAKK17FpsXQZB5hiuyFEzfVfNkJ2bGpYULMa5SULgHNgZZXFAh0FSJEyz3rhZSrzR+ItbBglqpqS0AVvB1aW4KXxlsKEozRV9zuwtUaRogTVrYK6aKYuVVtzMVYWauyDH+s2tOYfuVbduD9vIfsXdMj1vWfdMIMNUzfKrFN/C2raPsf6HqIDKwvtFjs82aKjw0+grcE7Z0mKchxfAU7b5H1J3UXiLFbWph4YYD7sAUBAAECx78kILjpnNKMx3dWuv6KEmroL3oGV5akHIMNKmVgk6raorzsgFEqLTtCBFTW8TOuC3UITtStEWmr5TJtn8R1DN6jR+YjtkOHnq1cSGyX6khZtaqgec7iCl8UvLrtBjYKSDtAPqQEB7xwAXKIbo6kPWLpz3/wujQNrGIXz+jm1IHZF20nrV5bdQEbZhkX6u8gOrMwN5zP4LoZ84cQNqlNnsZw6sJwOO/0P5UlQG6o0bhAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ0OjE2LTA1OjAwuWoOXwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU0dTLnN2Z9sFfr0AAAAASUVORK5CYII="},"227":{"admin":"Uruguay","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGPklEQVR42u2db2hVdRjHLySxZFdaNy3KXhQjy2Eyy62CIrDeZEapL6KtQTS6olS21vYiYkzEVaxc3W3JhZhOibHRpFRybkwdy02nJZbTVEgtrDaIzchREQv2uS8eOJ3LvWdn6/75vvny47m/85yzcz88z/N7fr9tgcmKyXcnu6RSfzWgVyAVWFKBJRVYs3azke6R535p1UsXWEnr+Oj4trFFbp/uLurc0lHnNkfYCaz/0J+euPz6uY2g4wZcxSev/V6+2gL0W+PVdX/Na729pTTieq00q8H6Jvr1/YPzqwZevfTUJmdMArvqkg3PFo2e2Xc6/+jf2E/sHYx+Ecbub8QC2bplOw4MH32j5KNHT86RzqYGvCU7N3S23PZm85Kh/XM7jlfVW1CAqbHurcLcCWDC3vV4e8G6muZrm+cVHnNLkVfWj45NPBw/ybpdlX927eK9FwOB4sK25dJZVW8FeGQgMnfrt0Qp+ylItS+sPxJc1LO8bdeaauYfO3n4++gw9v7zBy5sfQZ7x3DzfcXd+0LRirs3OCMW/rlXsvFMYKUZWM4U1lfbk799E6mHuAUohzpaHgi2gxGR6culH5fkbu8r3xPc+BB25vRWtfYuLrDo4BP/3movgZWWYKEU3SQ40AGs/rc7w6vKB49/+k7ei0ADakdqdl0XrP7sQtP19+Z8dUPboSUrsDD//LJzK4fL8PN+uKrspju7mj6/0lDt7dkEVhqDRd1DyiMa9azZ2fFgJQnubPGe90I7sQPZqbzOoryI1a7S6JOhOcznWua3l277YOUdtq5iDHyJpEWBldJg8RU6+0+2hKcwBwsiENAwJvYAloWMOLd76MOXgjdiYT4RDp/cnaTJsoBVJ5GS5Cuw0jhiUT5T8RBLbIyhZsIOUoBiASIyWYAscBZHEiU+8U/Jj//ES3jAWnVX5fq+P5cOvFC7/w/pbGog8ZRHxcMXzNdPBAIL0LEA8emvXYfP3Bz+57tTTQsKrGJnvo1wJFDGlPakWqo3b90s6eyrxxrr0tAPjacvEldszVR/rfLn3KeJUuBFZAKyifYT4fktwMQcAIo21D4WupVrmXkwsuOWhQ3494aUNA0670Qs0hDVD+0Gqh+ily3GQeryPd2RUO/LC9Z25oyADrGKMXYbn+y1+KQrxr1s1RW/upKmNFi2bGePj/IZLCi6nSs+LIxtykOJWIzBjjl2/WirNFDjXtyXZ+B5KOG1dZ2WEcu2PemDEz9ixfVU9UOPytZJYAE0wGRrJj7Fzhx7LWPWhvjnXvTueQaeR3ErAzehSY6kQsAi3tgaC3SITFRafMqYKMUc5mMHLNqn+E92l1CalmDFVohT/XEbnwDF2VYAHSw0DmzEsh5IedZChKO1YestfW0ZBRapx24zgxEbMraEBw6UmdiJXoyxW4xsH4sOFnGLiMgJCHppVFeJ9LE4wvF8f01woCW+llXXjg3+aMfW8v9qKjxPsu8nIbAo4TevrskJX6V8JmKxUmObxXbebfQi/dkyHMVukYp1rab8UEvR1ECpqxgnUrar854GnXe+ZvACJlv32JMLxBiiDjCBi13rMQYvEGQ+vatYo3XKZ/zyXFs6GbsJzeEWGgF05KmBWM1ZjOzpBsZghIX54EvEwif+dbohi8ACAioee14UtWmRZgERyG7R2PKf+SQ7PODTeZRZYGUsWCQgtqWdR/AsWCQ4qiK6UKQ/e4KUmdZu74J/7qWjyVlxNNlt/46vn7SI2hOnJDjbMsAPM1kWOD3bSCawsqLGcgMrtnI05z9JZ9idqQ1oOGvlV6eKmMeSeMX4K48cHPamHLyZjgd/1a/nmemfy2ewbGPCeTAQdNxiD00Emhf+PpVz/WgPeDjnuM2PfywnNf24efNrjtunPoMFFm63pFpyeyk6uaC9wmn9trReusCSSgWWVGBJswWs6dT/ydrjr4D88u/t50r2Kn+fx5tl5p7H2/uJgZU6HRppJmlAPWLpzKhegVRgSQWWVGDpRUgFljQ9NBV+60OaearOu1RbOlKBJRVYehFSgSUVWNKsBkt/LVM6I3+DVH/fVzojfzVZPWKptnSkAksqsPQipAJLKrCk2az678XSlPif0FKptnSkAksqsKRSgSUVWFKBJZXG138B8SglSbh5r3EAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU2OjUwLTA1OjAwm4s6YgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVVJZLnN2Z6F+wA4AAAAASUVORK5CYII="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/1/3.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/1/3.grid.json
new file mode 100644
index 0000000..6a41219
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/1/3.grid.json
@@ -0,0 +1 @@
+{"grid":[" !! "," !!! "," !!! "," ! "," !!! "," !! !!!! "," !! !!!! "," !!!!!!! "," ! !!!!!!!! ! "," ! !!!!!!!!! ! ! ! "," !!!!!!!!!! !! !!!!!"," ! !!! !!!! !!!!!!!!!"," !!! !!!!! !!!!!!!!","!! !! !!!!!! !!!!!!!!!!","!!!! !! !! !!!!!!!! !!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! ! !!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! ! !!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! ! !!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!! !! !!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!! !! !!!! !!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!! ! !!!! !!!!!!!!!!!!!!!!!!!!!!!!!","!!!!! !!! !! !!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!! !! !!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!! !! ! !!!!!! !!!! !!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!! !! ! !!!!!! !!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!! !! ! !!!!!! !! !!!!!!!!!!!!!!!!!!!","!!!!!!!!!! ! ! !!!!!!! ! !!!!!!!!!!!!!!!!!","!!!!!!!!!! !! !!!!!!!! !!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!! !!!!! !!!!!! !!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!! ! !!!! !!! !!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!! !! ! !!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!! !!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!! !!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!! !! !!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["","13"],"data":{"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/2/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/2/0.grid.json
new file mode 100644
index 0000000..14abaf6
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/2/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," ! !! "," ! "," !!! "," ! ! ! ! ! "," !!! !!! !!!! !! "," !! !! ! !!! "," ## # !! !! "," ####### # !! !! "," ######## ! ! "," ##### ##### ! "," ###### #### "," ####### "," ######## ## "," ######## "," ##### # "," #### ## "," ### ### "," ### ## "," ## "," ## !!! "," !!! "," !!!!! "," !!!!! "," !!!! ! "," !!!! !!"," !!! !!!"," !!! !!!!"," !!! !!"," !! ! !!!!!!"," !!! ! !!!!!!!"," !! !!! ! ! !!!!!!"," !! !!! !! !!!!!!"," !!! !!! !!!!! !!!!!"," !!! !!!!!! !!!!!!!!!"," # # !!! !!!! !!!!!!!!!!!"," ###### ! !!!!! !!!!!!!!!!!"," # ###### ! !!!! !!!!!!!!!!!"," ######$$#!! !!! !!!! !!!!!!!!!!!"," ########$$!!!!! ! !!!! !!! ! !!!!!!!!!"," ##%%%$$$$$!!!!!!! !! !! !!!!!!!!!!!!!!! !!!!!!!!"," # #%%%%%$$$$!!!!!!!! !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!"," #%%%%%$$$$!!!!!!!!! !! !!!!!!!!!!!!!!!!!! !!!!!!!!!!!"," ##%%%%%$$$$!!!!!!!!! ! !!!!!!!!!!!!!!!!!! !!!!!!!!!!!"],"keys":["","185","165","71","207"],"data":{"71":{"admin":"Finland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA9EAIAAACEkYd/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABnklEQVR42u3cMUoDQRgG0G3EU4gKYmWj0cI1vUQECxsP4Als7EyKIAqCVoE0UYIHsEhpF7DwCBYWlvYiG4QV2cLGckdm2DfF1wYmj3+G5dvNyujX49zz6uvK/HAn73eybKvV69adrfVu+2p0P3y6LK2aVgYWWGCBBRZYYIEFVoNh7eZFfwkssAJNrB8EdcPaODx7AMtR2AULLBMLLJd3sMACCyywwALLAgsssMACCywLLLDAAgusqGF58g5WMhMLLLAchanA+sg/T2fjOLPYno2/7iYL07eXzdB9rPPJ7ft0UP1izHuSSmbt/eNidBFzrnWO9gbXYUj95vLiwclNGf9upJJZdcOIO0Nc2P+eWynsRioZ/A+TzUxbIMGSYEmwbIQES4IlwZISLAmWBEtKsCRYsqGw/qebEH+7QR+h1tTH0scK0sfSINUgDdIg1XmvUue9qS9T+AYpWKm9VwgWWCYWWCYWWCYWWGCBBRZYYIEFFlhggZUCLJ8xAgsssByFYIEFVtNhefIOlokFFlhggQUWWGCB5TkWWGCBFfv6BuluuD1YhrY6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzowMC0wNTowMEDt7DYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZJTi5zdmdMmf+XAAAAAElFTkSuQmCC"},"165":{"admin":"Norway","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADa0lEQVR42u2dP2hUMRzHA+rQDkIHLTg5WE97p54KQtEOXeqkg6ODq6f4B6UognQQnFQoTpbaQYodrJ0qLhWu2KEoCEJBlErtYqlSOZWCiH8q5YZ7Nc27JC95l6efDF8euSQvfz4kv/x5OVGp5Nt3FcLUz/t2DxUHf2wpby0/WZbc5MSL/W+PNT/eWzxXFmL7p5M3hNiRK5XUuhKmcOpI/7VHiz2V60tn5DS/Hb356tZiZSjXsbM75JoJX8W/AlZ9pAzAet8+XHgAHIDlGix6LMCqNxTmBkobAAuwPPVYhjYWQyFg6Rnv9FiAZQlWdcgDLMBKCawaUoAFWM57LNaxAAuwAAsbC8XGYuUdsFYrPRZgMRSiGO+ABViABVihgcUmNGB52Stk5R2wMN4BC7BQwOI8FmAxKwQshkIUsGqLDoAFWAyFgMUmNOoeLG9zqICGQlUZk5TdLm41Vjr5SZCa+D4+Kkaf1dF7IyMPL6/xrONjGl7S35vnx+a/pHMe6+fd6bbp2wY5jM25s3oLPz+SiuXMOh93N+Bcuf8ELN0eCwdYXu5uwDkD63Xr7PGFE9nSmfzc14/F+4fGrjzvamreM3X2qT5YbVcPT/YOTN15eWG2/822d50Lv7JYA43Var3F155oXX+w71KTvm7a2PGhZ840VvIU5Fgt5w/MXBzXu7WhpuuW8r2nJ0zz4CrPjQ3vI1bUP/os5HlTqLr2LM8ULHU6vvXvI4cmZQy5Farlkmfllg2Dxq+ZUQ9UAWABFgpYKGBREagHsNKfx7lNP4kllJUZcQZVtQ6R3Cc+pE6YeB/zdayVAkfXsVRl1/k1/lknHVd1YvesX147H6FaRY36JA8T72+afnTl3XQTWl5518mbKj929eC2TvTzY/reJO3OJjSOTWibTWiz81g4wOJ0Q9hgKc8Z6p9FNA1jGFfvBKmboXDVCdIkZTRVV+/y0S5W7SgMzlPLYVThdfw13tXgr3Ts6sGVmraLj/PyCfLDX56oP7HnQ3u+K3RlvPP5F2BxPxZgcXcDYAEW1xgBFnc3ABZgAVYQGhmMsLEAC+MdZSjEeAcseizA4qpIlP8rBCzAwsYCLGwsFLAAC7CwsQCLHguwAAuwAItNaMACLMBiKAQswEpjVsgCKWDRYwEWYAFW9v5WTr4gmr3CRuofeRNcTTFrboUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI4OjM1LTA1OjAwOC45cgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTk9SLnN2Zww/KG4AAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"207":{"admin":"Sweden","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACGklEQVR42u3dv0rDQBwH8CA46ODk4uSggzi76QM4WRUVfBafQAVRdHB0sYhQN6GbgpOouPgIDg6C4ANYKUFMqa1p0kjPfJYvpX+u4fLhd+FyR6IoWl06PA4rh0/Xh/Yrl09nb7ObjcZDNYri/Pi4u+sl41/F7Qyfrl3vn4TYG4Oa/wJWr6TAAgsssMKBlRxAv2A12wQCLBULrDBgqVhggQWWoRAsFUvFAkvFAkvFAkvFAqLUsJoIwAILLLAMhViA1ddlMy7ewTIUggUWWCWHZSgES8UCCyywAoZlMwVYKhZY3XLl+Wjit9fdf5Wt5WYWCev7X/7qBKTpyfTH0+u5yHbuUhzPyPxGfW8+rByrbS7vjte3zudmHvPDituJ2wyxNwbhXLS/H13t1A6m62Hl9fvFwtTL6+3N5Oh2NlLJjNuJ2wyxNwYzWzZChZX5SXXaECbzZ8cu7u9pK0+Wrfcai/eVnxIF2TdYyfd1jSwkdYEES4IlwdIREiwTFmDJssFqmSA1826WvJCZd/cKQ7lXGNbdTKsbAljdEOLKi6j7yqd0n2ZbJ5S9/SI3rLb/b54jT/P9bOu3OmWe48yzTqv9O3ZC26VjabIt9mCpWGCBpWKB5Vk6YKlYYIEFFlgu3sFSscDy9C8VCyxDIViGQrBULCDAMkEKlooFlmssLMACCyywwAJLguXiHSxPpgALLAlWNesG9uS+QrD6m58+1tRKPATTOwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTA6MjgtMDU6MDCvvw1bAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TV0Uuc3ZnN3MeBAAAAABJRU5ErkJggg=="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/2/1.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/2/1.grid.json
new file mode 100644
index 0000000..bd799d1
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/2/1.grid.json
@@ -0,0 +1 @@
+{"grid":[" !######$$$$%%% %%%% % %%%%%%%%%%%%%%%%%% %%%%%%%%%%%%"," !!#######$$$$%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !###### $$$%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!###### $$$$%%% %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!###### $$$$$%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!###### $$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!!##### $$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!!!#### $$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!!!#### $$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!!!#### $$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!!!#####& $$ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!!!!##### '''%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," !!! ##### ''''%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," ! #### ('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," ) ##### (((((%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," )) ### (((((%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," )))## ****(+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%"," ))) ,%%%**+++%%%%%%%%%%%%%%%%%%%%%%%%%%%--%%%%%%%%%%%%%"," ....,,,,,,**++++%%%%%%%%%%%%%%%%%%%%%%-------%%-%%%%%%%%%","/ 00....,,,,,,,++++++%%%%%%%%%%%%%%%%%%%%%%-----------%%%%%%%%","// 000.....,,,,,,++++++11%%%%%%%%%%%%%%%%%%%------------%%%%%%%%","/ 00......,,,,,,11111111%%%%%%%%%%%%-%%%%%%-------------%%%%%%%"," 2233.....4,,,,,,111111111%%%%%%%%%-------%%----------------%%%%","22225....4444,,,1111111111111%%%%%---------------------------%%%","222222....4466661111111111111%%%%%----------------------------78","222222....99:6::111;<11111111%%%%%%--------------------------777","22222===>999::::;;;;;111111 %%%%%%%--------------------------777","22222>>>>>??@:A;;;;;<1 1 %%%%%%%% ---------------------77777","22222>>>> @@B@AA;;;;;; 11%%%%%%%% ---CCC---------------777777","222222 >>> @BBAAA;;D; %%%%%% ----CCCC--------------777777","222 >>> @EAAADDD FF%%% ---CCCCCCCC---G-GGG--777777","HHH 2 >>> IJJDDK KKK FFF%% LCLLCCCCC--CGGGGGGG777777","H > >>> IJMMMK KKKKKKKKKKKNOOO LLLLLLCCCCCCCCCGG777777777"," HH > > >MMM MKKKKKKKKKKKKKOOPO LLLLLLLCCCCQQCG77777777777","HH MMM KKKKKKKKKKKKKPPP LLLLLLLCCCQQQQQ7777777777"," >> MMMMKKKKKKKKKKKKKPPPP LPPLLLLLLCQRQQQ7777777777"," SSSSSTT M M MK KKKKUUUVVVPPPPPPPPPPPLLRRRRRWWWW777777777","SSSSSSTT MM XY UUUUVVVPPPPPPPPPPPLRRRRRRWWWWZ77777777","SSSSSSTT UUUVVVVPPPPPPPPPPPRRRRRRRWW[[[[7777777","SSSSSSTTT] ^UUVVVVVVPPPPPPPPPPRRRRRRWWW[[[[7777777","SSSSSSST]]]] ]]]] _`aaaVVVVVPPPPPPPPPRRRRRWWWWW[[[7777777","SSSSSSST]]]]]]]]]]bbbbbbb``aaaaVVVVPPPPPPPPPRRRWWWWWW[[[[[777777","SSSSSSS]]]]]]]]]]]bbbbbbbaaaaaaaVcc PPPPPPPPWWWWWWWWW[[[[ddd7777","SSSSSSS]]]]]]]]]]]bbbbbbbaaaaaaaaaa PPPPPPPPWWWWWWW[[[[[[dddd7[","SSSSSSS]]]]]]]]]]]bbbbbbb aaaaaaaaaa PPPPPPPWWWWW[[[[[[[[[[ddd[","SSSSSSS]]]]]]]]]]]bbbbbbb aaaaaaaaaae fPPPWWWWWWW[[[[[[[[[[[[[","SSSSSSSS]]]]]]]]]]bbbbbbbb aaaaaaaaaa ggf WWW[[[[[[[[[[[[[","SSSSSSSShh]ii]]]]]bbbbbbbb aaaaaaaaaagffff [[[[[[[[[[[[[[[","jSSSSShhhhhiii]]]]kkkkkkkkk aaaaaaaaaaaafff [[[[[[[[[[[[[[[","jjSSShhhhhhiiiii]]kkkkkkkkk aaaaaaaaaaaaff [[[[[[[[[[ ","jjjhhhhhhhhiiiiiiikkkkkkkkk aaaaaaaaaffff [[[[[[[[[ ","jjjhhhhhhhhiiiiiiikkkkkkkkkk aaaallllfff [[[[[[[[ ","jjjhhhhhhhhiiiiiiikkkkkkkkkm lllllll [[[[[[[ ","hhhhhhhhhhiiiiiiikkkkkkkkkmmm llllll [[[[ ","nhhoohhhhhiiiiiikkkkkkkkkkppppmlll [[[[[ ","nnhooooooooiiiiikkkkkkkkkkppppp l [[[[ ","qrrooooooosiiiiiikkkkkktkppppppu uvv [[[ ","qrooooooooiiiiiwwkttkttttppppppuuuuvv [[[x ","yqooooooossiiiwwwtttttttpppppppppuvv [ x ","yqooooooosswwwwwwwwttttttppppppppvvv xx "," ooosssswwwwwwwzwttttttppppppvvv "," sssswwzzzzzzzzztt{||pp|vvvv } "," sssss~~zzzzzzzz{{{||||vvvv "," €€~~zzzzzzzzz{{{||||vvv "],"keys":["","165","207","71","185","7","69","134","62","132","30","177","115","59","79","164","226","74","21","58","133","205","43","149","18","98","184","139","41","109","206","96","202","28","229","25","148","80","117","68","6","144","222","217","87","11","19","105","216","3","64","221","211","106","170","57","56","114","101","127","125","237","112","188","66","123","166","183","169","9","159","213","145","189","240","67","23","161","70","214","22","45","190","199","200","39","130","82","46","225","116","141","47","86","77"],"data":{"3":{"admin":"Afghanistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG70lEQVR42u2cX2iWVRzH3/fmzSgYi0V0sS66mUTscipm2FphRhbEfJUuwqK1wgZd6C5WrNFYrsz+YTlMsSB1A6ELXU2h2GbiZssczrXwzSnOWrVw6kAprDyfc/F7ObyrFOM97/vdxeHhPOc55xnnw/f3Pb9znjeRuDFx5S/ysqnzStnXnvwiWf/lZ4nSRCqukjdfs79qy9//RrLjvQIoBVY+ICWwIgArXrwEVpRg5TNwAkuh8CqhEVgCSx5LYGlVKLCiBut6IyuwtCoUWAIr/5RJHkuK9b9CKbBk3hUKpVgCS2AVLVjyWAJLiiWwBJbAknkXWAJLHktgXXPZ31VSWbKFPgd67uqYe2F/z21nbk1ej2M5AiuvwboWjIDm21tq5t73+khreuvycVuOtdWnnk0dObP0ySWXvzpRsfD29qG6+fOqMrQHO0CkFFgy7ykQoQSgTHnj4jXrLVKnHnijvH0rYFmkbBtK7oKpwCpSsFAaUAAaQOEaUEJ0whIQLVjUcC2PVURggRSqY7EAKe7Ojl3Yg707Xtn+clsD9f9GvQRWxOa974WyV27aa6GxmoRzsg6JNtZp2fDHXUbkKRDkLkhZTyawClaxhltf/PSRd050ba9o+fnwxfS91atGHqu7nH48l6JQDxwgZdHJZdItjkMlS2aqmo93b7rQWPt1W+2yBU8JrIJSLCb1ZF3ninWtlEw2pQWLa7vKo8YiZdvbNITtgZ6/37FxTkPqh9KPNzftAujBV2sO3X1OHit6xSIMMc0WKSYbDctStbKVi5dWnTzywf3ti6jJLGj7ae2lmaHjXaODM8cmnv+ue6z8pTfrDhBYaYmq2X6O7m5ckU4AE3hxzehhcBRY0YDFxH9TumrfwhkwGlv32ubn7rRlGAQBa+KOnX++P4RKTQ0fLP18+veV50d+S1JOru2t2HGOu7TkqTCMhiMCFm/FGwqsyEIhU3t48Onx6rfQD6YTvRmonPdRRVkYNLHzZ0sPJnsbCHMhWNRwd7ppYNmBt3kq7I3AB1K4uuFF9dMPn6a0WAusaBQLdJhaFIWgdurQnuYNx8AiF1iTyztXb7+Bld2lvl87fxy1YBEWbctcYDHKxEN7dm3KMDp4Yep5Q3msaMDCwdgAhK5Mvrs7s7MZ9UItaMk15en6jkc3zifYTf9ydMPAg95dBSV3p57Z29FdTV7e7y26AGd7ZkTwQgvtu9FSYEUAFiqFWSbwWSDQDBSFdAOByXosnBDWnhUlGkPP1HAX98ZderCJDL9l5O5aHHkrFhP0KbAiAIsQA1ioBeGPoEbKAF3BLZ2dM1zT24NyAJk37w4Ia7FtohW86BmYfJ9Ow+iZGkYkjAITb8W1H0Vg5T9YTDlaYjNJhCGck91+Rl3AkWkmSNEPihKafWpoyTUlXsquE9neAV9gsm/loRRY+Q8WoKA6TCGhjSnnroUp1KG+fc2f1D5BjorcFQpkg6b3Rq4lT9nslD9a4zBlLEbnTcAR1+UVVGDlP1hsv+BpUCnUgiDFCg5o0JIwR99/c8tQehvqAlgWL3Dxe4uuZbhdA0asGQl8jO7fxPXDGyoURrYqBAs7hT5Z4BABvjADTmBC5+gBF8U6kWdRGtSOlnZBYA07WsVTf0zN3HN+3ALq+zGb2QIrgjwWOkHoYZqpIelgV222tGHUrhD9tsy2D/tbRgHC2vxcvflcl4OS0ekTd0WN8liRgUXOHb0hNUDmHR3C5YRrPUBB4UAQFEgQoEyEUcBCe+zeYhZYbhSbkuBN/FrSvaEy75GBZbef0RurYdSw4qMlSU7vyUyJTwKyrGtX+uSCK3Fs1raHG952MWEDscCK7HSDnWD0A6XxQc3pE0jZc6ThgWOuwzZhe/CiZ6ttoXrpdEP0x2ZwRUw26oKVxs6z4WPzW7kOMdtvcuxWtw98LlySW886DeG0jc0iAnG4/SywIgOL/TtUhMw7JXiRQyI82Z07e8LdnmQPPwuz599JMVCiYTgwu40dJjikWNEfTWbt5jFy6mJ9j8UCZbIn4mf/0pDASukDruuZa+rDM1sCq6C+0iGZaW0114RLAhm2He+V6yMwtIcAx1P0wB6l3bqZ/bS7wCoQsEgrZFl4B4TNsJNuIFziwAhn/viNWxuClA95riVt6A2w0K1wG1tn3gv2u0LasAmNuba5K0DxyQVX2hp/Zstdo23gCFj0kOsgoRSriD6xx+DjlmzqAYAobbrB1tOea3r4rx/XC6wC/1EQmz4IPwILQbQffl0dTAJLv4+l38eSYsX2W35SLIElsGTe9eO2AqvIwZLHknmXYgksgSWw5LEElsCSxxJYUiyBJbAElsy7wBJY8lgCS4olsASWwJJ5F1gCS2AJLIXCf0BEYP0F7SYyPlPSiAIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE1OjEzLTA1OjAw7m98/AAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUZHLnN2Z3V4Eb0AAAAASUVORK5CYII="},"6":{"admin":"Albania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABHEAIAAAAuKKnYAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHXElEQVR42u2du6omRRSF6zDDGW/jcRhwBEXOOHIUTAx9BBVRzEREzEwn9AGEyQ00VhAzGSbRaDAzExXxKTTxCQxW8sFiN/Vfqv6q7p0Uh/77VFd1rdqXtfeuLr89unVxdXeE9o+fb31w9XCc8axjXqd6elk3OA4ZT/+5rGlrlS1AZK3tqd5JzXNLgiPn1WL8Jfd9ti0AV3KvJ1Ba9FmO9fh2EBnHAO+zDUZ7/6kKU36k8Z7AWnubEivl3LZ5rJSLc73Pkks4MrjHf8M7eIXbebnqme1x+5wFIs0J0vVJoGhGP717cXn39y/eefLf59/85s9nHrx09vjVi0f3bu/6Bni/elBv6llP2WYUssy4Px68+PTVC/+ojfr/8fLZTy8vec/nrzzx5e3zt964/t1Tn9z56uy/6x+d/VDulff19+vl2ns3Pvv4tRv3n/tWdwocgos/Rb/qTrXqwXvWE9Wz/lfg0wj5Nijt1H80x1V5hSPIMy0zF0+LFN3JhSew9L9v/31+7eZ9LbmuqBVEvv/r5sOXv45Gol85ErXqTT3rip7oI5E8c3WsGRGa+0nT04qDcqzuWk+b/RMcAoEWI7JvqPgcClH74a/nv1zccYmlK/q1ph89UU+XlIrmqFloRgTljEpzSh5L0oLw4uI5FGjraPGkYqjCHBDq3+0kXeHT/b+oTCn5vDeN1kGvflxdJkHafE9Q2bllI5WkBRN0tFSCFJUL+3Gg0DZS66pTrXogrAUjPVFP10g0KvXDketX9nNcj7Xn+k4psSR1JAk0DSo72TeRHHLw8XU4vNQP3QXvWf/l4FtWu+pHo6UXSUuLKn4u37DMsgPcxtKyuXVFORRJF7ZaVPqSDgiBwCGlO6mw1NvyEzWqSCZpRvQoM1bYfCdFi6d9757XstyKlF1kP0UWVY2idJiS2lBLX5Kgn5FWnTgITduF8kMtoVYDrMjrrAEWpU6Nt0gYccyufOelrPcMQp92wvTsZJdIGamViqknBbSo6q1GkUXKVD3U0BmCFF0QjVxzockfsXSHS5fWK1hOJTYPmRhVBv0sAq5e6pyqpZfKQJBTuJuwsSJA9Ezb1f6mKnEJJFlVY/H0b8nORxJOv9JPnKX8dWK6gdNjTFD7XoshFePm8AitRqURarQkF8jO9+GxOpV/jZ/goQWQCUxeW0s1oyokI68ZaXbLoaqkG44G2WUak9wP2e3RICU1TYuKGRZUjprpjHU7U2aQan9reZy/Zl5Ufci5Z0tq16WsZqTZuY01C/++wmIKuv2efTUCpDiqMTOuDgdfGU0m1fBnjNmRCKWpLiXI3KZxWo2fapo5EZTEDDdNTzeMP42IxqQMWE5uGcFsr8kSWxWw5lJ5zHyioz4m0RDx7wSZZnQsFbmSg9d6BhCWMxokA+pD0X1aZlyRyPU05dlL3MoI+2DX3rSbmbHuLDbjd55T4EHrFkY6rT2C2+OS9AQ5oygUfSzipt2dZa6dQeM9yppijihtLF/OFlQqac8I4rqHREOU6XV4jsNwqnB8/l0MdVQKRsnEjCstqlpW2pBD2k/BkURgVj5hRGqXo/KaxOUaoczH6ppf6ovHqByva/GkemiN7acW3Z4jt04OXbYUHQumNR9emT3OqaRlRhgRKNr3aqnU6NJTetEXY+javcgIZJFlpicydcf9U43E4UX7T6D0PPrJ6IYZK0CWCQVmlJP34qISoBGkpIw82qgrUcEqqxHdV2WqscMrIiOSxzqB3KKEYFHD8mIsm+00mT3gzcAwTfLIjahRVRo5k2ei0v4EVvPzZyQz6HPVFEstB6c9Y9PB5wUUkdTxOqLlElxSvpHx3ues1+FihT3zSJm3RLuH9YY+nuXMdMoY9RMZ7Fz4KCLpYWY3z6lSWc99iFc4guqckiCl4vCccXpkupOZT35EB4PZ9YVcUdEYwcFjSJglxlEx4Ycz8gTAKVXhLOYh/UGmxdGT8rIqJ045X9b5UKrV1/awHy9DdSI0KlMj/cENMMJpMyunGzzdj7ucEouSjEESMlsEq3rbj82Kjlmj18lTsjhmylddkRJM4/3EBz16qCQ6KsgVJU1vLxmt59yjQgnSHFFNs0ZLF6FF9XN/02WyEntvWai+fOiPH+tI05vJKjWqkCBgDywx5VOWR6WRk/JdeQbp+O3yUWb1KtWDPJHc8vRiSsH9zG0vaMsPYU6fnU1mi8cbRYwX2SlPwjm8KH4dRwyXnks7cvoHfcPlo9g8cMQzF/KM+ykPt+2TTU8lSGqD+Z9+ckR+v6NTEHqcg3F3VYuSRpEhz+tUmvnFjfxI0w6nB3ocUFfafdViHVArKbR3PT3wkJKs7Xx9qGussGcJwLEOZnL2PL9klsb7kX3GKGY3b7LkNDzWusG3na2VzPtwSz7jIWlNjPfxjdB1yImtGRIlRXrO/QRf/0oArRvK7UZb8tXnJklVmJDd0qd7513OnnTr1nzPkvs+25UY7zNmPYyQA5I21kDH4G7TKpqmYHVrbHK7kW+HFslYYfqYTd7Y/8HPfi0xPlKPAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToxNjoxMi0wNTowMKMvzEsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0FMQi5zdmccA72rAAAAAElFTkSuQmCC"},"7":{"admin":"Aland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABBEAIAAAD4cUrFAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKklEQVR42u2dTUhVQRTHB7JN5VdiBGEQgW0kRIWgReEiiijQ3NRCQQnpY2dFhZtoFSRCRJBBSIFQQauihRFBRlCCleU2KioeCSkokW4s3zxjHnPnvnPmw/fue//N4TF37tyZub935szMmXOFEB0dIyPJke3d91r3DZ9NXRhaWD9+uuzqUufEJXEySy5NCiF+zp8f3dw+2V9xp2VIlx82VTc2/5C/P26o3dv0eu7JrV8VTfJevcwvO5/P1Mxtfd/TeO1x0nosXzLpYOkoGMCSMKlIyd85wEqnAKzSA4ugsVSYdMigsQAWC6xz89lg6dJ2KFyuA9ApbbAOl39qmXC3sb5Pv9hd9RkaC2Blg/W2am3LLpOlZQRL+Q2NBbCijHcJVlqyjXfYWJgVqlK1sVSw2ENhxKwQGgtDoToU6lJCNlxzpnk/NBbA8geWHBzZYEFjwcZyBwsLpLCxKMZ7lgkPjQWwfM0KdbxWwLp99D9YGl7QWACLDRZmhQArF1iKvolYefdmvAMazAr1LR0Pxjs0FobCWIcZbEIDLG+OfqQtHQ0vbEIDLONQaOnoB40FsCL2CtNgmRz94JoMsCLAWqwb/7NmQndx8ayxsPJuC5aq2E1KXqarknuV8lsvITpPBFiBHf1WwOquu17PrS2nvZSrdr1q96boPGj37ujqbRtoSJbsOXD5aW85CSz3wxSK8d7a1/et/1ASe2z1pUh1jZ2oPJiRo2M3KisiJCWPmtPu3lgpX62UM5Wv7q77bbKubJcblC0dQ8nTV17OlO+RdXBpC0PG9ydF+iqTUk9FCtOLMVkbxhRKfr/lk8GK11sUjUVqEbeN9H6I7w2HXvLwHg0ptEokS5r2Ch1sLEiuFMWBUWifd7Y+CNe6/JZJvldkKeQikVMNucDKklluM8v3FmOfrLYUs+8GH1UPzB4beLaxliQLM78mv9488mDbYjxY6tVU+6mpLZ0RT+fWx1e7El6OMJ0SToqMdzuOP6WjD4uQvqSIn4rHRz2I39ylTPXdy88BlskpmVw3bs3zVQ43DyX4AL0+erqgNDi+CPcOdSmfC1YILPz2gF3OEH8Su/clfws60fQ8do20+7eFA4v+mn31iak+vmro8izucwW3I+yUs7syN6bkdSh0+ePZ/eW4xgN3NHAZFo1DIVdjhUCK+1w7sCidbtded/vJ7lkhLGAXXVvss0LCUIgZXJBZYcLWq8hrWpl1LIqNZVrH8rWmFX8XvUx6fShPtLuXnLPYV94JNla2dwNW3j2tvOdtByrcvpvujxVrY3nbhKb7BRTOvmGwJ4oCbVIwsErIu2F12mJ4irDztrG8i9tsOx3APQmdhswJLDsvKLs83P7k9qG73pVDofSHlL6RDJ9GXx6nindopg4ED0aaB6nP+Fi6B6mptsarhPZaeqgq5UeU7OkdkdqipCfM5337wvGHg13S590lPhYjjFE6BT7vbJ93zukL+lkOejrlHIia3lZ/P/XvlE7ZxTcu0Wai/LG4p3TiT8hwz9L46k+X8zZ2+U1XSzLwWo74WDnCcePMIGI3ID4WwAoexgjxsQBWkMBriI8FsBAfC2AVY3wsfKQJYCE+FsAquPhY+EgTwMoF1qrGx4LGwlDoOT4WNBbA8hofCxoLYPlYbtAGVmgsgBUkPhY0Fr7+ZQTLcoEUGgsaizIUwsYCWN6Md8R5B1jBlxvYnzzBUFgaYC1Ly29CQ2MBrHCOfvqwCBsLYI2QvkyR2Sv0GdwWGgsai+02Q/EghdsMV/4FWkOpeRgUuWYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE2OjMyLTA1OjAw4QrLNgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUxELnN2Z5NDSAsAAAAASUVORK5CYII="},"9":{"admin":"United Arab Emirates","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACnklEQVR42u2avUoEMRDHF7XV48BCS0Gv0kp7fQMRbNQXEBQLLX0DQSwVfAKLK6+xUMHG6rAQROz8wMJCLLVQOVQ8WHaZZCZLcv7+C2HZzd0mk18mk4+s3a7XG40Y0su12kdjs/U8dji2N3K/Mziwn2UHV1n2mx42/9L886Kc3W/z90V5yp+Xl0r+D5pv+f2zJL+fVXNP4gZLUj0/I8qRLW8wefOUdwk9HOGg9KpXCmAF6E/GXkpeKnmDhfagErgV9UoZLL2rD+EJystW3uM19bIaUo3qlc5Q6DrEWBnO77lrw8t9idXw59oVHT1iah7LKobwC//9GtI1xvKrqd9b21AhSbBcDRQiOLUKgW1jrBB+UR1TpjYU6t11OC8liaLk4Fo1vP5bXkCn5rH08ZDVXNJ1icFvMUK/XFJ9fZP3WPoGDrGEoQnJqx8i9UsVBW/T9Fh+w4TV1NpqvUczmfCbperrK7ZhrwyF+tWgomYwnSup/I1m50A/BDvGlGlu6YQIhF1ncLaIuPotq/0GW1tFClZ92mfl3dUQIfYQJX09HND6fU8r+JIEK99IVfZO+ZzU6le2e4WhbRX1ckMerCKYSKNP4wHrOz27nnid3F49Pmoubizdnj6tTHany+cnu8vn3feh03wZikrVS2m5tSU2iQms9drM+NZVa6o9t/Q+fD/7ePFZoI+X6q78d6svQ2zXp0BJgoXiF2AhwEKABViABVgIsBBgARZgARYCLARYgAVYgIUACwEWYAEWYKFeB+tt/G7+4UZSAflxDvJUkyc+sH4O+o2+dA76LTwubnQOlnGleUV4NLnW3zma3DfUOZqMkhVgIcBCgAVYgAVYCLAQYAEWYAEWAiwEWIAFWICFAAsBFmABFmAhwEKABVj/T1+gjm/JSCOymgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTc6MjQtMDU6MDChspWsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BUkUuc3Znl/8CUAAAAABJRU5ErkJggg=="},"11":{"admin":"Armenia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA8ElEQVR42u3ayw1BURSG0V8H+tCCElSjJwXcxFALhiTa0MAxMCDE47KJx5qsyHFzcu72kQhpLUnIWo2AwqKwKCxSWBQWhUUKi8KisEhhUVgUFiksCov/GNZsnExGZK0ZDJPVlq8zSdabz9nnTXd9fGiyzPP3BFmjEVBYFBaFRQqL3/Ot0CDoE4vCorBIYVFYFBb5kh+hT1yc+dt/cbnnTquuSdE17zxPrxIyG0+7yYisNa3NlyGLTWtdd1jaPz720vol++5Ttf7M2frubJ/bzz78IpHXNAIKi8KisAyCwqKwKCxSWBQWhUUKi8KisMgydzKRziwqxFonAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToxODowNi0wNTowMIUD2HUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0FSTS5zdmenj0mRAAAAAElFTkSuQmCC"},"18":{"admin":"Austria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABEklEQVR42u3dsW2DQBiG4WMb2xPYnpHeg1B5Fs/AAFRQuKE5Cc7/YRDPV7xVgpzTI0VxdEnq++vlcVeNbXIECpaCpWA5CAVLwVKwVMFSsBQsVbAO1fHWPBuwVMFSsHzzAksVLAVLwVIFS8FSsCr/+P195rxbPm35x0e9wqhn/vJZNc4nB2t4dZ93qxrbNJpVGFgGloFlYJmBZWAZWGZgGVgGlhlYBpaBZQaWgWVgmYFlYBlYZmDZbmG5T6JVbukU3jjL3T5b0rU32sqelnudsR+/5ddb9pw/nY+b0OqKvYLlr+aB5SAULAVLwXIQCpaCpWCpf2IAlvfGwFKwVMFSsBQsVbAULAVLFSwFy3vlp/o11ATvdjw6QaUN+AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MjQ6MTMtMDU6MDCYT3HDAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVVQuc3Zn13qM2gAAAABJRU5ErkJggg=="},"19":{"admin":"Azerbaijan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACw0lEQVR42u2aPWgUQRiGp7KysJADK3+wsbCRWBmL+I9gjpB0iohyNiLKWYhgRIJVIERJcyBamKCIEjgLUXKKkkJFLQQtREKEJBqjBHL+K4cWbwIjyx6zu3PnXfZpHvb29r7ZnX3u+2Zn1hhTGB39CKFv0gUQsSBiQcSiIyBiQcSCiAUhYkHEgogFIWJBxIKIBSFiQcSC6WN25ODp829gtm3/QN/9eXYfONu/c572/lq0q1aq76k17ev1dLwZ/2xutuTd+XbKrGlpDdL+NmrMJPHDjq8eJ6zdicHMnR27353ZsHffxpnOPaV8Zqa36+HJgrYnL6y+0f4j2G6S621kxutbbZuoP07SmMvNrs/5BCl1vvfczT4qVb7OPi93/LlWWVEpir9fjF2cHPxZeJp5NTH3oHfzlW1T4+uWdT1Lfg6N0P+1IGLl55ace33pm61RZen7wqf+8pOB7uunPqzdPnRkqzQSpeDsiWNDfcuV2+p/2xqfJm0XXF2pX7df9ozlJJNLHKmmAopMiJWfXrlp+tBVZSY7S7koVV1WJEu1WCpzdq7Snqjl2M5bklVZUHppsI9YqaBuuYbhtljSIp6mX1Zdfnxrl3KeBv6KHzX//a+BNmJ5oLKInvLsIhjvKU/qBDUV7exFxkKsCNGU54JiKW8hFqWwmGTYrlIoWe1SqIkJxlgpYtjgPZ6myoIM3hGrVROb9gy7tu285bLE5GsxBLEWFZVdbL3sCVIXsZggRazQQqa1v3+yV8iSjkobSzqIFWEAXs9FaMRqsrU/X9GCr80ob4W9NrOY/mC+pnBNduT4vc5y07P96JaO9Z6j2TH9xk8BjTHF0uE2CH3TDA/ncgsftO3CsON97Xdv1z1C9SPDOiheHF/n0yxxgt9GuKkQupMugIgFEQsiFh0BEQsiFkQsCBELIhZELAgRCyIWRCwIPfEv4VStsKuTQYgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI0OjQ1LTA1OjAws39KnQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQVpFLnN2Z3usgD0AAAAASUVORK5CYII="},"21":{"admin":"Belgium","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABXEAIAAAAt/qtDAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNUlEQVR42u3dMWoCQRSA4TdJjmARsPYO9rbRC3gUSw8h2AkRJGVS21l4hBwghRcQCRjGI0hgBx3n+06wsD8sOzv7JsVDeP+Z7yOm/bdNRM7naW3XnybPh4jfj92qNzyeFt+D6u/IU4CwEBb/eAgKi47lz79XYYGwEBbCAmEhLIRF977SVlh0b5xHwrovy5jpUlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2FRkq3JlFDj6CVhVeDxRoMIC2EhLIQFwkJYCIubMYOUIswgBWEhLIQFwkJYCAuEhbAQFggLYSEsEBbCapcfVinCWTogLISFsEBY3gqF1TBjjEBY9UjpZS0sEBbCQlggLISFsEBYCKtJPulQhJMpQFgIC2GBsBBW02xNpgTrWBRhHQuEhbAQFlx3AUFCNWPfLd84AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyNToyMS0wNTowMG6dDDcAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JFTC5zdme11EifAAAAAElFTkSuQmCC"},"22":{"admin":"Benin","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABo0lEQVR42u3br0sDYRzA4TMYHS5sQZwiCBYPsS0Z1KQzXFldMhoGCy4IwsQZBZNt/gVms0GDZeKKYU0YFmEilvkjrGxG3YF6T/mEi3cP3/e997ggCOr1KEpCZ6rHextL7dp1O5d/f7ndzjR6vWYzm9U4GoClYIEFFlhggaVggQUWWGCBpWB9o4fP0RVYYMUOy4MHy8QCCywFCyywwALLcYOaWB4/WGCBBZaCBQFYYIEFFlhgKVjOscAyscACS8ECCyywwAJLwQILLLDAAkvBAgusXwnrY6WVyxTe1u9qmUsdbfv3NkGwcmsH7c2T1nllaqHU7ZbLc2caXxP0rXB6fPdpa+zmfra4XH8sT+yE+U4jVQr3O510OgyHruiPm6CJNQTrYnJ+8aFPSuNoUicWWGCNokeV6BQssMACCywF6wuswaIAlokFFlhg/UtMgwULLBMLLLAULLDAAgusP7KFBwsssMCyFCpYYIEFFlhgaaJhpaqrhVewwBr1WyFYYFkKwQJLwQILLCfvYJlYChZYYFkKwQJLLYXD/xVCAJY9FlhgJbufplbALlZTXuoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI1OjM1LTA1OjAwVngougAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkVOLnN2Z88UG/8AAAAASUVORK5CYII="},"23":{"admin":"Burkina Faso","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACsklEQVR42u2cT0gUYRiHB+lSIBFEiZQSdmi3FS8lka4IbpCdIvCWN08pdBE2IuhUXvSW+QcSzYNWiOihoJAoqIzwIl3qEOFBxYMmURZJdPjtYZbZz751vwVn57k8LDP7veO++/D+vh0GvY2N06dqayF0S48WQMSCiAURi0ZAxIKIBRELQsSCiAURC0LEgogFEQtCxCoKv12u+5LooQ8RFYuvH7EgYkGIWBCxIGJBiFgQsULPzYWWg7GvIt1ALGf8MdldWXNIpBuI5Yy/28eTx3pFbs8ilrMQ3G6YrzvaKRKIiOWAP191Pq9Z395cPHzkgEggIpazEPSL9eve0Pzxz3QGsXYbgu3Jqfjwn8W5hxUf/GIRiIiV45kI6WJDRV6WUoFAtK8WtS2/F7VtuIJMc2hnZjbsBrF01qaOrhi1CedFM+C2xvouVA8apXFEXUVXjFwUfm+L3U+8CS/9HybfVfrFZzWfbOibYaoc9t4WQi+91fGpIRll3ly+c7X+8dOu17GTPYWIpQqqRlc9zxttbp0OActGHrReyvF6d3UCrOgb70qle1Mv9iWW8lVKq1TBVN/4WUqWpkaEhRbS2LPt1nT63PV8xdIqV39DiZAW+AUduDvXGH9i2j+ZxNIqxEIsYxR+vPF+pupsUJ2V8oVrlbfF4Fmt+k8UIlY0Z1VWCPrm07v9b5dPlF1cnbpyvl/UkaBeOks/S10s+2AyhKCOZOaQr5qOmN6PUkysHCGosOvon/17Zs1GR71TqwhExMqiIkx3oeqfPZpoasq3glapAoGIWBnGX06kmscKnzSqoGp0FbEgYkHEgohFIyBiQcSCiEUjIGJBxIKIBSFiQcSCiOX6OSe4d545Y2JBohBCxIKIBUthD0f7IBMLIhZELBoBiyYWtz2hY7Gc/rcWCBELFpH/AJxrG58LvHo3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyNjowMC0wNTowMGH4u/0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JGQS5zdmfL0P6AAAAAAElFTkSuQmCC"},"25":{"admin":"Bulgaria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABOklEQVR42u3aIVICcRjG4S94AbcZsLHJRqNRTEYS1Sw2K8yQOAB4AJt3oOEFDIyVA2DxABo06CzMLMzHiPK84ZEh6PDnN8uMS7yb7WHhCExYJiwTlpmwTFgmLDNhmbBMWGbCMmGZsMyEZcIyYZkJy4RlxxbWsv3WfR2Sucb500NnsCJzjYjJ/OZqe6fTfn/dM1V3+/38LTe9a9u+v18/7u9uy83W+QPkd3+EVX1cfcaRsVZY1SvT52NHw7QrlisThUUfhTzasBwEk8NyBBQWhUVhOQgKi8KisEhhrfnahjsHwkp2f3cO/Os47ZbOX7fO98b+0+s9YM+ex8V1l8w1Zietx8YFmWu8jJqXpz0y11gsyrIoyFyFRWFRWBSWg6CwKCwKy0FQWBQWhUUKi8KisEhhUVgUFiksHrAfuwGIY4Xi14MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI2OjQwLTA1OjAw5bK1BwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkdSLnN2ZyfMwHcAAAAASUVORK5CYII="},"28":{"admin":"Bosnia and Herzegovina","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE40lEQVR42u2cT0hUURTGZyH92QQhCVFE5qJaZBS4qECJIkippKBFkQThogJ36RDIJG0yIkSLahOkjEETFRFIGriwkUgUIoUKpaIoMaQmKEiwgvne4jzO3Dfv6dx5f+7ZHIb7nuM48/Pcc757vonFNlWUX0/piE0X2maGauYr5zv/jtN4bvOlJcNb3TzDhos7frTVjw6mOtb++lc6MheLSVTFRKKlpXZE36fpOep76vs1fe1Tq8ZXvCub7U9c7Rh72YTHWOf315Wcev6kf83czs93lsZi60a7vmJ9W9+eK/G04OUKLPK+LQ4Lx+dx81t0ILWxc+9scrL9yK2bY714jJdC19cfq37cPYn7y1ZXxW+fAXCHS06/6uvmf4bgJRkrRXHhdGPduppd2b7lQOZe1XTXt6Hf1dde9LS+Pmt7BvKzgpeApYzAqPv9w+TbfUPNI+kvE9giUYF9n/pZ+mc/1gc+pCs/3UWtJngJWK7i7svHDz1KAiBe4AMvbJo8bwleAlae8nDXm6OtD+qBEQUL+cxNqSh4CVg5YuON8z2DzwATivepZR8nMs2jjeN1Mxmeq1DyW4W/dI4RAatQrSyJKNWhbAEjdI6ADNul9XuzEfVWDiXM+M7Rs9yg4dMMUMayxAhFR2lpWmQd5Tyi6g0yE68C61hhB0sVgRQyE0p45DZUY4hYwVXcSUE0DS8By0OEFk/FCB5xFXeaXNpL8e65c8R2yfHCClX2bdEwvASsBdZh0OV5tFVpWaS4sm/CkbaA5TljQZJAwQ7dC9o9VnCV5ipcRe1lzuYoYC1Qo0e+4Z0jUKMgQpKwKWFsi4weXgJWwSJQA3aYiUCkx0QY16FXaU0WJbykKyxwBC7OnSMi5ImoTkxIxtKi/CIP8SNtKF58PsL2GiKRvSRjaTxMwEEQRQqPVSpXlLKXZCyNkgQOsNEPAiaU8LxDBFLoKPlAYhjxErA0do62seYsKJiDsCQJsg5JwlliBV7DDb395QcFLEPBco7ACwDhMbpF2jnye8IlqwpYvp05orSHXk8rMDzGOrZOW02WzV7AK8jZK6hgBaeb0DxrrxqGho6Pe8I4rSpdoc8HRCobrVKSCAleshX6HLmNFr2ks402+MKEgOWzJOFso+UTrTYbbYAHcmQr9C2qbGTONlo6rZrjzsCo9gKWf8dHjm86t9GiQ6SdI524txk6AtA5ylYYAqGVIpXHRquQVYufvSRjhSDmsdHyMegAqPaSsUKwaVIbLTY+DzZan0p7Acs/C6VHGy1VtlCBoUO0ZlaZjda636fOMbpgRUK7RzbiuYfaNPhXw+Ww0RbdpS0ZK6g9o4sVIIXMFDQbrYAVkSNt52FoVGM5vp1QG15tFfGB2uUCVugznMpGizLfNuNVlDNHyViROiByZaMtyjC0gBX+8p9IEtDo89hoSV+pz0YrYEVKo+f6u8pGi0IeW6cO1V7AMgg7ZxstmoBC2WjlSMeITRa4oJBXdY5UnrApZwvCS8AyrsBXDUPnsdF6rL1kKzQuqmy0tm9YXXTnKGAZpHWhE6Q2WmyROWy0BC/6Vb/u8RKwzDh/zL5yNzZa2iEqbbQu8BKwJCpttMhVbmy0HC/tYHn9J5cPOPg2Wtxj2WgVupd1VihdoUQ6raoahsY6nwCjWeTEypNPGzKI3vOKLhD/AzBi5B+T++PRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyNzoyNS0wNTowMJ4n+BkAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JJSC5zdmc3li4kAAAAAElFTkSuQmCC"},"30":{"admin":"Belarus","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADcUlEQVR42u2dMWhUQRCGX62FEkyr2BmCYGXEQiyvtrGxSyFpbERSiW0KRSvhGhsb0Ua7tBYRDBoMIhIEScBCOQhaHEYlaPFd8cPy7r0NpPJrfoa5bQ4+ZmZ3Zvc1f+/tr+2v/fk2ujG6iX49sXJ/ZZie9JfrPxyfez336s1ac6w5oqpoAyi7K08uPBn8eLc6Xv318cXCeGGMnX4ASr9gqR1g7S1uzWyd+/J0+e3y5qezg73B3s77pY2lDfzY+Fnz+8P25Z2jgqX2Auvzmav7V/e/n3y++nwVm8hU+lkvWGoHWKNbw8HwCpFpfGd9Z/0nFRVpkSiFH5v1gqV2gAUuwERkAh2gwcbPGtYLltoBFtEo0xw2AKWfSov1gqVWgEVCxGYPmP5Ml4Kl9jpuQDP9pX/z9Oz67Hp6BEvtFbGAifQHOnjSj836H9vjX7szgqV27Aoz/ZHygAY706K7QrUXWOz1sn7KQ9HSn20fwVJbweIoAWhIguUpPP6MXoKl9qqxwAWYQA0PNn487grVil0hUYofsoRv8wuW2gEWTWUaOPyQswzpx7YJrVbUWLg4rwIX9oDY+FnDesFSO8CiJM+YVKa/jFusFyy1AyxSHjEJBabSRlnvAanaCyxiUk6QlpVW/mrEUitaOhx+lmMz6RcstWIeq5x8p5Yqp+Cdx1IrwGIPiHIcCkaAlb8KllqRCkEEJfGl5q/OvKsVxTtxKNvMeTMHP7Y1lnpAsLIJDWQkQWzBUg8lFdqEViuK9xySoWCneMdv8a4eEKwclfG4QT3EA1IaOKXfVKhWt3TKNk7Z0nG6QT3EJrRgqRVjM1nCJ1iOzdRqTrD9b/+rY9APyNoG/Ribuf7g9uP5i4OHi6eaRlXRXqPJGZNyNHl09+ej0bX588+OzV9qmuHLplHVidZepmBvOLknLVjqdLA4PshrXm3Xv7AnxxOCpU4Ha/qF1YxerJm8oSVY6nSwaq/YTx4LESx1OlgHexREsNReNVYmwXzAqPQz6CdYaq9dYT6wlkhl3zBXCpbaK2LlY5BtT0ViuytUK8CiVJ/+uK1gqRVg5XPcRKm8V5jPdOdz3IKl9toV5rFCOfPucYNaDRa9v/IzJ22fPLFXqPYCq/xIU37mpO0jTYKl9jpuaPt8XPrzxEuw1A6wGNnL9k7bhzDTI1hqm/4DcvO4XhRykw8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI3OjUxLTA1OjAwYK3VEwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkxSLnN2Z00LMLQAAAAASUVORK5CYII="},"39":{"admin":"Central African Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADs0lEQVR42u3dTUgUYRzH8YHIBfUQ2otGHTQhA6UCMygvRRYVeAgihKgIMUI6FL1TVIJJSgkhBZ0yKshL0SGRQFEoCiNRumRgdQmDQBIC7W2D+W/wyLPPMPsy+/Z8Lz9kdmaemWc/83+emV1cx8lbV9vaYVvuGqx41Nw7/npRec2m4FJasbOHHWABK9tgjVUfu/TnTOjcVP1k2alt/cfvAAtYScgVn7c8PVk2GnpyumR234vmtr0fgJVQLqhuuHLTblhuFwim8Le3407+rbnrbRumgEXFiuUaUq8k5W/BFA6P/XYcqVuLl9WFzr7MhGsRWOp0JeNgbZ1s7Dp0Y8fd/S0HStSUWvW+tG9N8YRULEmZb+nrS6ZyHgasjK5YAuVL52Be4ZzQmXo2VFTwScWkp75Oz0x3+9rvKYLlVkdgZcEcS+qTDHbepCLpDo7CS2jmh2r+XqhkKARWlJR6I7XHG5YQlGGUyXvQtTlH7gqFi16fIuku6Zi92rR5IL2nneOw9JuqZEH0v8/kqhc0xhmVy+vN4ccblx+M3CGmiRewsqxiCRoB9Lz4fvmqMrnj08FFhkL/J692VsJdlmWw/JyvPDhQM+thuScggKQ+CSP9qZX6GCLmATGpXZaDFUuHFdwzqhhg6W+V/9qgPGeP8tGNtp/1ww0TR7fPux+M74pUO87wkNZ03WcQrPguEn0r/7CCmHvp60cZYuJK45zJsH6ElFqB9JpkelXruHlAvVtMPSzvS9R01t5L9DT1j3eatk3kHZHc2dgy/rArXSkfVKe+3fMVu+sv/gwalrSS3h6OL6VMJLIHJ/wg3Bd+Z1tOD/UO9t0LGpa0YmcPAwtYwAIWsIAFLGABC1jAAhawgAUsYAELWMACFrCABSxgAQtYwAIWsIAFLGABC1g2wuouHPnYvdK27N1zre7EkqBhSSt29rBTWXW7Z/VX27Kp/0h7VX3QsKQVO3sYWMACFrCABSxgAQtYwAIWsIAFLGABC1jAAhawgAUsYAELWMACFrCABSxgAQtYwLIR1vDSgdmCNkn5P8fqEj2HRoZD+aN+1vFeM+j9mLaVJa8WXv5ROhM0LPk5BfVI9MzM/onveNR0/PzWTZQfBDAt0V/1sx/TVt5t+V+ivTr9q7O2qDXwrya7rRiP0Mdxxtw/yXqPYu1VLWNYNUcyE2BZkMACFrASw6QmsICV/bBsGxMYClNasSyrW8BiKAQWsIAFLGABC1jAAhawgAUsYAELWMlIYAGLigUsYAHrf/4D+/vF6KjUa1UAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMwOjM1LTA1OjAwcd88PgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0FGLnN2Z8KCGZwAAAAASUVORK5CYII="},"41":{"admin":"Switzerland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkEAIAAACvEN5AAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABlElEQVR42u3bsQ2CUBSGUcMQVizEGCQOxAjMwBZsQ03zNPkbOxtzMc9zi6+9EU/57q2122tUv1ufQMFSsBQsVbAULAVLFSwFS8FSBUvBUrBUwVKwFCxVsBSsX+q4j3tr0zZtVc1GsDrvvMxLa+f9vFc1G8HqvI/hMbTSyUawwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAqvDN+9gdQUr9yr5a/O5K5u967EetbCyMdvrf3s2Vl8KFcPKOVRuV0zN5Gvny4NlwAILLLDAAsuABRZYYIEFlgELLLDAAgssAxZYYIEFFlgGLLDAAgsssAxYYIH1P0+T358FX/s0+aoH2Z0/TXZM4ZjC+RdYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIEFFlhggQXWpzfvuV2paTaC1Xlzr5JzqJpWX8iApWCpgqVgKViqYClYCpYqWAqWgqUKloKlYKmCpWApWKpgaX2fUJVDxRBlOpcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMxOjA1LTA1OjAwEJJQ4wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0hFLnN2Z6ItMoQAAAAASUVORK5CYII="},"43":{"admin":"China","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADC0lEQVR42u2cv0ocURSHL674L/gvr2AVLaIi6CsoadIYfANJuqRQ2yVgZSk+gIXaaCWChVgmD5AqKAqCIEgIEhESWMHfFAduZh135u7MznzNx7DizO7cb88599w7687P3oyNjMIi8KL29tNwvRyfxTGcELEgYuXBy+O5H0P7DCpitVqLzE1uDi9bjW4OP04PvL9e/LDz6g+DilipItNdbeuk55349+LbQZe7XVj93P+aQS3CVMO1J7qEOLPiU2P37IsbbzQul5yTXr9nN/71TpAWSx6xlKRCDPOvta/rfbVIrCcqel19n98enAonNMxZLMn08PPorrYivbKNgjqnNJJS2V4FFlQsm6qUnkJLLMkY1JKLJZlU/Shu5ZWe0K4kYtkkaGugvNoBUup+Zq/efapjSyToGLH8+ZoYOiE2r8Y0ZxQlGTPHQoil77cGqTlVSttYZdsBSc4gNdOkTv2vOlu6br6KI1aitqQfjf7zin3d/tW+4lEJNNukqbNJL53fpkUkKFAqtMklVpckepnjcI1N+5XQMeV8oWssWxS/OEp5ybGda4uwA4p3DVvUXIiLW96xUhIxA7ESRa/m9ZPVSwsyDABiJVu5i0uFHpVAWdFDrGdSYdQO9cSKKrCYequdqVDXotLqGLE0YHaGaHdHSTtN+P1efDt3UOla7NkqkFjNt3RpqOyaYFwXyu5EsCV86E07mljYzrvdDUE6LmjEUrKze6GSx4/Qc8O4hojtafHgV4j349I3HqPvfUtvRQMfenHaLuyIzEnZ856ZWNJXKkf1X4keEEWs3J7qKeszx4gVvJVAHx+xMlhUtglO5XmINUdiW+UiVtTm8J7SSb+XC1ZaLMUnK5ZtzNJhR6xU+ynEEA+ZweqJ9VT38NsNiJVbWwEiFkQsCBELIhZELH5kDGYlFjcXErEgYkHEghCxIGJBxIIQseic5XZ+xKLRilgIhFiQGosbARELIhZELG4EpTdiQcSCiAUhYsFqikXxCx0zLEgqhB2zAwKxIBELIhZELG4EExfEgogFq8xH2u1W8NlacbkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMxOjI1LTA1OjAwUrdXngAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0hOLnN2Z9X9A5UAAAAASUVORK5CYII="},"45":{"admin":"Cameroon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFQUlEQVR42u2dTUhUURTHZ6WbyIQaNb9txiIigjIigiKCFiER0SZrtNoFCRJFCG0KFxW1iBJdJCZSESR90M5N4EKC/IxIneyLJIwikgpslGDOCE/evDf3vnPvzPv4bw6PN/fd4N1f5/zPuec+Q6FQa2ss5nXb1r791M6VYxcKtkbXDw0VFtbWesY2FeyrrSc7/eJwvKpnYePYjXCe0Sbqhu6GG8XvW1nzeMuZx0aj4ajVtf2vdB0CWO4B6/2GIwerzsviYg+NNHwWoIgDRxZguQgvI1jiXkotiLJeymo8wHIdWJyQZz9SZB5ZgBAKPQCWlcaSBUVCS5lH2gY4ccgAlqs1lrMwx7f2MIl4NYDlUo1l5V34GaLVPKn7AhmfiJAHWK7WWM40E1/go9wQCz1trYxtNoLl3YAoW8figCICR7DFuwksf2gsVb7KCgtxpFBu8K3GylBzsr1OA4HVfYF8EJV3X9Wxspn98X0YPJZL9wqFypjiOSBDXXFGAizXaSz+NrN42RPiPaAaK4N+0rBFA/FuCZZ3/ZYqjQWPBY/lsI7F78RSfB97hf6oY8lqLLX+T6RsAbA8Vsfib1Gr6ke113MAKxgaS1soDFK5gYKLZzWWuE/KfpepeNaJrNADhynUts04g8++zQYF0sB1kKoCSHZrCGC5tPKezd5RTk89skKWHekuvhO9TFaxhmOcK9Qn6vkFWIAltPDvGuoT1UVk9YElfq5QY++obOhE5d2ZpTm/Xb2ytixlc3uuULyaxZ+BgxeywgyLPT4Y/Ro5/Wfb89biFrLj3dEVkYs6/JYbzhVKQwmPlSEYWVxT+Evkv3obbia7LCCardK2GT4iCipV9jkmwLJfVCs7u+rS57KehUevn6yZoFdGd0SeFUJN6blC/uaPs5EQ70vKKVqRiMxPfd4/Xd1LHijef6Cl+qjRUo5G4c/4QukO/Wp+iizNTP+K2nOF/M1m87Mc/wePlaZ88KnhZF/lib+z/QNFcWOwW2YTo2PhcBprWB6jpdlo5lR5Qtu5Qn1lBX5uGNRQaAhSk3m7P9Ts+dV/b7HkocT/+CReqfvJQEkz0Gyq9gr5nyvS8X0H83swW4j3pewvmeuRfvq392Vn+LEIZPMDg9fCcXqK8kd9e4Vuq8KnxgMsETt8f3VHdHLmzNmu8p8iL5RGKuiqEN6EdhtwAQKLhVdygWemzh0vH8nwipPhj0bq2IRW9pEPyfvSdSx4LPFsca7yQUdJm7HEQCGPrPEV00iJ7M+RxuLU2ZVpKXgsztKS9E4BlATrd9ez/OJeWnKydId+pZETm3btqOnkF0ideazcdjoALKGlNaqr73M3F0sb3hza0rTuunEM3aFfaSQ9pVtjudMCrAyLSuHsx63bw6VlX+qa2yuOpWmSMeBF42kkQcYKiIxQKKulZD80AvHuVLxTuaGo5mOkLxXUJLGgp2gGTkB0VsdSK/algYbHkt6Q1v2UojqWvuNcCIWB6HnPbR0LYHkeLIdaR5uWyjAeYHnx+BenC1T79xqgsfx0rhChEGB5so4l7bd8C5ZPP8etWzOhbSbQ4p2zl2e/8LotwPKMxlKrgQAWNBbAAlg6NZazPiqABbBYGktWXAOsgGosZUdSARY8lu46FsBCKGxU1dueurY/IwmwggyWvdcxAgSPBbCk61j2YEFj4Q9hOuzHEgcr++EPe4Ve3iu0XU54LIRCa43F10mGGQAWwFrusUx4pQltFsEOHgtgSdexrPxZrgACWD4JhW7L/gCWl/9eocugCShYXs8K3aOTAJa/wMpp/UmV/Q9UzC80rC9rUwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MzI6MzktMDU6MDCyioZ3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DTVIuc3ZnICDopQAAACB0RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgQ2FtZXJvb26Jr9hlAAAAAElFTkSuQmCC"},"46":{"admin":"Democratic Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGgklEQVR42u1dXYhVVRQ+pFD0kL1I2sSoIwSBCkEDPkX4EDOIBVr0A0FFBUUUWUERNAzGUCkSDSkNFf0YvqRDZtFEUkyp1MAVBmHGQRLtpWCgFwmloqCvh3057T3rnL3W/rvrZTGce+ecvc/9zvrW+tba+1TVFSPP//2XWtgrV46OXn5Q70Pd3nvgvZcPrZ7fO3Ni8IVL03OPXPdY3f72xKF9qy8tTA1P9o9V+pOotdmbqze+m90wMf755aefWtwxe2zgjzqYLk6evHrVqZ+Hn73qhg9m59fvX3tr58Vrnlt3S6W3T32haVcuG/ts8b6dfQePvDXk9k+/nnjzob7BuYHNd63Z1pm/dsu6NYCUAksB2nXOrZ9ObJwaPDYyfdsdxylkVweTAkstA9l1WYDMsIGAddOevX1nBmD158yS7JwwigYsTANWiSmWpZPd2R93fNy/oovs3GCqfacKc7O+OP3N2buXw6rPyIDsCNCJ7LEwsfM7O4sb18PiiAbgYcju3Gznnk2fSJBdY4/Fe4MwPXMyj585cPTdZepLkiM7G6Tqxwngq6Sfy8Orvj7+wCZzYkqIuWR2PraSzgRBf+YklRB5UyJmsvP51PiOILAe/fKjDe9fsD09+FSjJcr5TYuaHUNmxxRLicRYbnvw4lcTDy/aJg+KVK9DARZ8f0uy84GRHBWawqZp1w69/tP5ju04Qki3i8anW67fd2r6RtvZbNcCjeJ4L8iY9XDCBBOJ7Hj9E8ELVu6JvXLy8NCuGeizpmQAWMDajtggZd6U+v/azmz+jVGV6p8EZUyfyIlgAe5fntz9fd9oRQ8SKXCRs7h6qVIFQ2YnEYY7y8ywGAlGhRFitA1irM3D43fO3A+xICSk8Ozi6iWF5ww1OzGxYAlg/Xsc/hK+sz7mxsE7IpvXbp+cG/nQ9my1sybecWZcBT+AypgtIyefeKsGJoAbQHdHz15Z4fYf3nn7yLjtaTOBQrc4G5JqJbsGnonXbxHIzrSY0f7fj/75zIX/FEouIbSusJuXpEAK8kQZrTUgUOaaHRfxkQHqJjvYqVe/Xdj+EnxwV9jA22zvQ45leCncXPhy5pqdHOXVMjs32YFV8MBYAxWuGwon7xNj4Qy5kx3owCuzCy5m0skOv5GtHMfssRDOu/MaenSVl+xJyewayJjS+lPDzM7MykF29AIUA7Dg9t2gMWVP9zcpE4hLc14yZixrQMqUMb3Izm39bzecv22I0L2gQlGUMJyt2JqdR1nXRwhtmtkxpFASJIihQ4Wy0Qc+rU8SZ0tHu2KTMSU8kO0IV2YXC1gYivns4taDHH2UsBQIMUJmx1QwZsvsYgHLJEEfFQoUaSphNm+XZc1OTsBsRXaYnbhe6EMQeJp5F3XhbIjDwhAinewQ8NoWlYuH3h4yJj2ziwwsRFdyhWGzTyu5mh2XUEnXrmoRWyyyaxCB6dKDlpldGBmzVc0uENkpsOg1uy6y4+rG9A7Go2V2YYCV4w5YXpldmA5MCyEmkdmpxxIhO17Kc3crxJIxFVj+ZPc/NTsJuLRqwcuS7MoGVuSanUcZOHuyKw9Y2ciYHjW7JDK7XgAWw96YXOtVxDI7U8aMS3YMVy88swvZNFe7VqAGFfVYhSw9aLXOLsvMrgxg0bsxrTW7MIF5q3V2qZFdTwCrKdmRGuikl256yJilQmqJnvcw02bbG7Pd0k0PmaCHMruUPZYJU3QrNF564LPql5UQKWSHhp/yyK7xXJLeQSUBm7KMmTRwC5cx6X7O+CbXOrue3oM+s3V2EluKpd+NmSMEM2hQ4VWkkunGLNz6oJWZ7KSreP47qChc5DwWczdmkpldeWSXNLCCbhfGtR1PD5BdorlhtMxOuMyiNbtYULZuCiLYoBKks6CpjKlAEfRY4jKmxPIEzexSpk627cL863c9XLPLvq2vbt27twfdQYVs88rs5ECTdEmncWbH+1KNVm89kCC7wrujYgGrcTdmkG0wKGRn7l+qmV1CdgkZM+Q+TypjlmQF95UTrtnpj5czsBJYZ2fuTVp2za6oOC9Wfqdkp1TI1sRikp3NP6UpY9Z9Sb7ehVSQiRxj6dIDJb7GwKJ7JqZ1dgqIokbl1SHu8T47rdkV7oP9l5Yr2anlkBtUxkw/cE6BarN864Ha7IN3XXqgVoIKyyM77WIIdGfcDSq21j8lO7Wkko5mdiX5uSRGuzA1PNk/5iY7+KcyXgauNgxY/wHtNammNY8UKQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6MDktMDU6MDArJxFdAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0Quc3ZngkgrjAAAAABJRU5ErkJggg=="},"47":{"admin":"Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrklEQVR42u3dv0tbYRTG8Qt1dCyCIBgkiKijtEgNhEaMOvUfEPwLSku3IrVbXRwUhWopLg4OLgpZBAlWEEUo0qEFKZrNoYg/IKW0JTi8S6D1F7nvveec97uc1SEfn/PkwL2Jos75xUKBme7sqC2NDfZsH3x+O7z+59fR4eiIxlltKo3ny6ezU1uPXkR8qBJIra3vPR7u0UvKTUfqqLv3YeY3sFKbLc8+rBa+WCL1/TDb2t7mJrBSTqm/ucrL0RVLpIBFSnkhBSxIxdClgKWelISleXNKAYuU8kgKWBwRYlt8wEoopVZGdl4XT5JHEO+6vG9KAYvF55EUsIyklITFByxSymNKAYuU8pJSwIKUR1LAgpQXUsAKiFT9GcI3KWCJIHXfy1Mjl6pkSAHLbEo5fMmnFLCCIOXm2fu54/5ckqSAZbaeO1LJpxSw+MYHrPBINVLP01p8wPoPqYXdT/niJEcEYMVGamai/LVY0E5KTkoFDYsu1ci8+9+KICUzh7R0qaBhsfiART1XnFJBwNKeUnpJmYVFSgELUgZJmYLF4gMWpG4hdTzd96rjh15S6mGRUsCiS107z6sfm59kLJFSDMtSPbex+BTD4hsfsEip4BafMliW6nk4pBTAghSw6FLXktJVz+P6B4hIKe0pJTMLI7oUKWUQliP1bn/zwVCNlAIWpEgpqbAgBSy6FKRkw9JOyj3UACkRsCx1qcuny98GTiElAhaLD1iQIqVkw4IUM6JLQUooLE6dTC+wbial5TePraaUyldFak8ph14yqeAeWPVHynfC1b9R2JGq1HJd2QuWV8qw9KbUv6ToUiJgsfiYMcOCFDNmWG9+bjwf2rRBii4lApalUyekRLzclnrOjC2x3PVc++IjpcTBskSKlBIBy0aXqjaVxvNlnpCRM68Am91UDENA4fcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjM4OjE4LTA1OjAwQfoadwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ09HLnN2Z8XoUVwAAAAASUVORK5CYII="},"56":{"admin":"Northern Cyprus","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD+0lEQVR42u2dTUgVURTH3zL6WBSkREQfCzcV2KYWQVBtilxEGWG0EFxkYLgoIgKT4O1CN0GBBQatCrJFX0SQJETgRlpEQtDCr7QyM8TKEgP/CjcG6867c3XuzG/z5zFv3nsz5/7eOeee+zGFmd8zfTMDKJqsFjABClgoYKGAhSFQwEIDAWvy5f2aB7dRNFkt9A8sX1O+UTrwcHXlhnfmkX8fj75rf6aN+rse+2vGPqXZp2B/Gyhqr4CFAhYKWChgYQjUA1jfxlqeX+2ZqGk73d7lpJdv1N/qnFP3bzN07goX+mbzd51/PWoN7FOaNSiQolTeUcDyqtPHhztHpn42v1rfvUn6/emT1men9Hqqsefw6yGdQwMD1n8AGl/WVF5c8fHYvsaq4tC1zXe3n1edN1ojHny8brpix4febR27Bj/vPbr/5BlF/V9lb1a+rabJcw3WZNmdg/f6Ph05tLa6Q6C491OE41hFQ+u5WsFK8+cCLHmU0S0nJuoeuQBkejJBGUVTR9THIWhmFiyNhw9XVbbtbouLkRBRyBMoyreUaZmq4zpH58uH6TWBMlNgKeTFDXYCQlmXCxACTt8jT6kjYBEwWPJScZFS1uWj+ZV1ERwDBks+Jm7gG/tytuFiv+8mN/uhwBEYWMpp7JEafVFbV/9+8b0IfisYsJRR2c9IVOBLcwMD3xKDpQYQKPZ9vTSHJN2RewcCsJxUXX17X6WMKv3m00iAiq7AtARgCZRs+KooWLpyBXqbjkseAmhhcUKG2QA2eVUoA+HmfWl00iyF6LVKGDpT1TLASqy4oJKmDVjjfcVLV1aFYr6R3j07DzSZ168j0VFOWSA/hQzvYMmU9oVQm4CSBl+lrNHmD6PMUrM3ybESrrDbDx6n7T+90NQd+7+K6nAk715qVyGCJaTU7bAP5eF2RDLusX5c79zadTN7HitvpVTvYAmU7OVYui97T0aORa+QXmE4dSz7wZz0jw+WVsfSfeUnLFJ5T6DyruyQyjtjhQl7rFBGNpndENjsBibPMB+L+VjZnUEad4GXJqUwgxSwrNbGxK1lM+cdsGKk86Wt0vHR8KzSYV3h/GL5WR/mshRMnf+vxQvtzd2sK8zgSmh5LxUYF3MltD4rpJi3nvW9G2Zr03F3PI8OZpu7NrB3A7vNzKXPmhNh+pVkd5sh5LE/1vyUldnBaSXvQkTAmXvLRPfHUphjfyzAskLNBE7TV5Q/SfMzHgdYKGChaCJg+dh5PIH90BP6VAK7tKfNPh72ZPdhH55MgfLIExSwUMDCEChgoSGCFR2hc39essuzh5O9HvdnIWfbPu7X8xdYPG8d9fIUe0p5KJV3FLBQwMIQKGChgIXmWP8AEUhjyNXLsS0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjQwOjUwLTA1OjAwzLMPnQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1lOLnN2Zx130qsAAAAASUVORK5CYII="},"57":{"admin":"Cyprus","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEtElEQVR42u2bbWiNYRjHTz5IPvhCUiiRqVkSX1Yi5YNSStlE04SI8lLIijJqRsS85KUmzEteRl5DiA8sb1sWYmoaZrU4Y7Z5f5na76jr9Dhn59k5s3Oe5//l3+l57ue+78712/+67us8C7Q2tza1vpNKE6sBfQVSgSUVWFKBpS9CKrCkAksqsKRSgSUVWFKBJZUKLP/qz7Q3z+8V/J7c0qu+h8CSdlB/jQqOr5rxZeftrRtLP6ddLVxRxGeBJe2gfqt8knN8TmO/oqeDprQMPZUzfRCQKRVK3TjT3cbclyXABEYfVm7O7jvkY92+9ZkTUgspgZVEya7px5ERE083XM7vFujd8HXtw0D5p/4X0uc1q3iXuoaJmolkB0zghW8lfxUlsLpYQcSW4Y0Nu06krwImUl6qlOQCy0XIOcAnduav2x/s3f2aCgkNORPJzqQ8RqqP5cGeUGPW9kWDMwk/lQ1KqiLwpCfGO5UxPMU8QBNSB0yMoUj3kksJrLBah2QUFn6nGlAYH3oqKkDOu95LeQKrPbBcIvKP65FGtiku5QekBNa778HqzCsZHYQmOkwOsDjx6bdCrxzm285fBJVKKHRGa2tIUuX8A6b4vcrxFDtJxVanwAppGC4OJcD2wO/ah+LwMPYmsJL6NEcic/6+FlMxHh0Ut1VULAnR3KWEF1hJmuxwHVIeR/2IpXfUMLtGKnrqjBlor3awUj4VhmBy60ZuYYrlrk215n0ElD4ZPsoV2y1THyvpFK9yndo6zbFAx5mg8VfStLPvL8dKOiWVuDjTdZ4ar/LPuc+zYPEXH7FlkCBc2qmrHJ0q/7RAPd5u4IQY1j3v0EnNdSo0zQu8U17lKbBwiLAX5eIv3qOe6SjGaRkIJo83SAmwfam3nUZAVNRsExXlBCdn0k86f1+pM+9nApzFxSLISPuzjx/ObgIrLsjs21QWlFDDwgftSoHVBf8bwwstzj6TVGAlAC+lPIHVjgbPtCxseoRy5fb86rHPstCu2hWrV8x8te/F8kj7FFhJrTeCVc2P92QGNpzLG3Hxw6PP5XdKpt0ZfXNB9uC9TZsq/n84a5e9rw1uYvWTaeXTysaxK7tDgZUyjpU38PStQ6uHFa9JXzS94P6lPqXvCeT8LYfP7elOgDt7D6zCiqzOTtjV6llnFx/Nl2OlmOIT1iGKx9zacm388ubSkoM9JxXuHFAwnDDb9BQ/TJVltSdryoqeXD9/YSWrsCKrsxN2xQ5VY6WYWpciwKTIqpr6wrrJuMXs/APHdizJnbt/6ra3jOcucMSyCiN5ihmYjZlZhbusTkLMOL5u5NK7jBdYKZkQcQiCjXPgTzZVEWzrLlyJXv1wl5H2KVtLWQ9jDDvhWa8mQV+0Gwge3gA6uAh42dMiwcZpLASRZraw8hmY7JysQrVnk6+3kfJdH4twUv2QjHAXUhUVj/0cCQILq/Mp/MmmPFb0A0y+bpDiJVQ8hNzWYfYufhNJAciOZwZmo1TnOqoGqe8gAwISFh5Dg8BWaYyxZ0zuMpKnSLKM7NpmrMBKIrWNgNjTlj0EdHZvTGBJpQJLKrCkAksqsPRFSAWWVGBJBZa+CKnAkgosqcCSShOqfwBIiFA4LVBp2QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6NDE6MDctMDU6MDCuNlRJAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DWVAuc3Znwqf7SAAAAABJRU5ErkJggg=="},"58":{"admin":"Czech Republic","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADnklEQVR42u2cS0hUURjHhxZZJpVUlho1NggtUrKCUCsYUiFqelGLaBXE3UiCLuyJpKRSoK3sBQpGrSx6LCxIJCGhRRBEohUKQZE0RdEDLStbnM2F68iM9zvX+/ht/sxi5g733h+/853vnHtDRkbznVutA/kjDaP9k2OTvyYnSNJ+hhZ2lp2o21WaZeS2lXcN9Ba/OPKx42v85w8uDWkLrMw15VvrqhVeubFY2bn0qlOtFffCOIwUAMuMl9VhXCbSFlhWyNRn5bCRrPdnP3dwyUhbYFkhw2GkMFjWpA4jtYClEoeRWsCyziWpw8gp2g32IcNhpICxknHY24bRdV9OcqEBSzhxGGBpSRwGWA7l9olji64tv7uk7/zL29wAwNLisOPdbWe683BYoDvvOvAyzyVxWKCNpQ8yHObz3Q3Jm0bfQKnqsIeXnxa92slNCpCx7ICV/G9xWIDAUjfb+WIfh/lko5/zs8VUHfbh5qfxb8+4hYClpR+GwzwAlvKBFFiJjiO14K1ydfbeLU3ZOIwGKQ5jz7tTA6KsI5XD6v+0Nz66z6NsGAuH0cfyToGPwzxmLDNezne27AygO8aqe9oP4jCGwoRAzwwsHOaZhylmd+ON/aPhME8ay9wbc3Nllle/f2Wz0XSps6R3Nw4Te9uMmwcv51M5rK/zeXSkCERmbSj0Ci7O12FOmi+Z/3LyOyF3ekj3slKqvz392njTeCM+fGFuSwaZTIakqig/GUudUTg/2lL7rmZ+wcZYzZOCFV2Rx4ODS+fkpJHJpM877zPLzbWl/ZW516/mRTf9BRHAsjVc4ictYE1fi0htdHFbP0xldEGxYZThJ2Gw/D2zS3Re+MlRsOyU4W5D03pe6jN+cl2N5UWrmed3Vb8L0/Ysxk8BKt71PUym/HQlEhkqqeBmMysUmN/hJ8AS7j/hJxcV77JlspMwKT9VLiv8t28efvKhsZwp7c3/Qn88QGBZ8ZICzuxC/ESNRf0EWG6d3+GnQICl+81YKvETxqqWrZ+Orlr/4EBLkP3kj3MPuaHbjp8wlvCOcuWnnsyc72vD3AzfgkX9RGrZ6Gd/Mdh6HOqnQINl/7FSK1jqmPiJoVD4jciq/0T9xFA4xUJyqm9+V366OJ4/vM3g4gKWwMog8zsyZbCmr5/wE5kCWIn2Kaj1u8OlG+KH0vETOUOwqJ9IYbDMDQj8RIq1G/ATKdYgVZ/xEyn2MAV+IoXBUo904idSNv8DQU9eyPkl1PwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjQxOjI3LTA1OjAw7BNTNAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ1pFLnN2Z+wzkRQAAAAqdEVYdHN2ZzpkZXNjcmlwdGlvbgBGbGFnIG9mIHRoZSBDemVjaCBSZXB1YmxpY9YC5UcAAAAASUVORK5CYII="},"59":{"admin":"Germany","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA60lEQVR42u3VMQ4BQRiG4f8Y4hqO5ULOoHcC/UYrIqJwAoVCMS5gWPyTjM3zFk9lN0w+uxGSJEmSJEmSJEmSJEmSJEmS9HvzNZlvbBdkvnFake897p9b+3z1gtcXj7lqvFn3//QO4w+u3e/97tp+zqdm8pFNzxbDmtL3qQ3RdNhER0DD8pI1LHpikYblRfMn5+OI/QHaD6v1j+z/ED3nPLFoWDQs0rBoWDQs0rDY67DOt9mBzDbul82VzDZK2S3JbKOUYSCzdQQ0LBoWDctB0LBoWDQs0rBoWDQs0rBoWDQs0rBoWDQs0rDYsQ+EqqV97vWrJgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6NDE6NDEtMDU6MDBJrG+JAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ERVUuc3Znu/SIVgAAAB90RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgR2VybWFuecjsIlEAAAAASUVORK5CYII="},"62":{"admin":"Denmark","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABMEAIAAABE71kbAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAByUlEQVR42u3bMUoDURQF0FGIomQRLkBRyAKs0ggJrkBEbIRgFV2DaCq1tAyptLOxE6xDUtrYiCJoGsFCFBnB32g7Zob55LzilpMQDnd+8ibJcFit1mpRZnOutZK+1E9ve/Nptpn+uP+8vPtaX2xth6tF/GmULBOwAqxBuzK1tAYEWBoLLLDAAgsLsMACy+EdLI0lwdJYYGkssCaxsd5nL5YPgAALLLDcCsFyeMcCLI0FFlhggSXBAgssh3ewNJYES2OBpbHAstKRYIEFllshWA7vEiyNBRZYYIElwcoKa6sxs3PtjAWWxooBVvj9Jsr86Zjnp+NRd3dcjVXMOw+Ii3zF4jN53Nzf6OzFmA+v7YXDk7fmzVF/Nf3HjAZn/fOrcLWQ8X4m5ckkNSaHAcuAZcAyYBkDlgHLgGUMWAYsM7mwrHR+r3SsYsa20rGE/rOEDg/PhCVxDu82lyuXcwntsRmPzXgeCyywPEEKlsbSWGD5JzRYGgsssJyxwNJYYGkssDSWxgILLLD83AAWWECA5VYIlm+FYGksLMDSWGBpLLDAkmCBBZYzFlgaS4IFFlhWOmCBJcFyKwTLt0KwNJYES2OBpbHAAktmy2/hVcWm46JcXgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDM6NTEtMDU6MDCnkUmcAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ETksuc3ZnDuNRdgAAAABJRU5ErkJggg=="},"64":{"admin":"Algeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEqklEQVR42u2dTUgVURzFJ0NxIREYRlD2hSGJPWEetbCFFc8+rDSN0lAiTVpkipliEoQgtWilhdiikIJKS4XQUoO0xMgIUwsz0/xANPArI4qyNOi0uHCdYZ7vKbyZsznIPHU2P8793/P/3/sUxTv5uBpC1dfA2mxHVPXAnvHAkbOz07Njs5NUfVUIDcEiWASLYBEsgkV0CBbBIlgEy3Rg9c0MJw/UERqCRcciWASLYBEsokOwCBbBIlgEi7tCKsGiYxEsgkWwCBaVYDmnfrakQTUi4ETCM/Xg1lP736ohMZkJfg6f3oKqwZr+b1PNjtbVPyN74gdqf1dMJE/5EiOCNYcCIEdd5GN1Q/4h24TacTd4fZD6p6XEv8weLGrHzPIm+xto50DQrchLPT0Rz5PeD1VleBd8/FJZWVrfPd02kjcaQ7AUK8OUciM8Ty1/EL92pd0HuLzzX5aq7hZVhElW+Xe6J8PD4jvHthS33LlpZcgsBxacCZ6kBYf+c9m3oOJftSf7ZoTWA7KprIdtT9MJlmlrJviTvKgZcaOa3KDC7Ut6T2ceKUibbC07+SgXldaPtI5zHw5A8QSfDl/LSbyyA2ABu88VBeuKm61TkylWQCon375RbTK+nEFLyje9VsfhcLZf6bFR953dFQIj1F59WXG2M9sAnBXwUqyAlPFqCX4Gb8N/cFfcAJhQe0EJlkdqnG2nl1oiL3wyZHjyxGtVoj0A4cJC51hYNKEEy2M0OCD2q5oCUIzUUvpILVxAij2jWZdFE4IlV1RaSyH8DN62EMk7oLFmiGoqsOA6wMVIXXV5c+g9tVOspdwL1sTS0uxKxg1m9CotxwJ8WDQXolf4/fyrFR0pXZ/C9kZfRQBBsDx4D4gMXas8F3+GVznbhEavUD9Px8LX+3JfdGoF3oW4gWCZZBHUXwqRTjkLVnvR9b7SBrE/ODVZc7ixUERt5MXFo0XV4tsJlseHC/oZlRgroFc4vwlSICWig4S9v/nYrqxwOeAgWB5fXclLnuxb6BIaKdi1aiz4E3xL7AxqAY1mDsZsCJaHKcZdjLST0ahxfdAP8Sb6gFpgiYpCfnA09faFIZTz5p7oMglYKMZlx5LxcgUs+TAFQDESwwI+EUEU+GZdKE27FGr5lrP7QSOOJb5Xy720HItLoceApVVpQd1bYwEj/X0oaiyrDf2ZfFcoQ4ZdofFoVGtXKDoT3EjcFTJuUKzZzHE9x8JkFdo1ohvBnwiWYqYZdnF6XSt0cCV57ypuWNOYYiR5x1gfk/cQK5Tw4vP59QqNH7Fnr1Ax3ySW8an2RZpu+Nf2IVgmCUuNgMV5LILFCVKC5QkBhFyHceadYLlhTFmr4aN/SseV+7F4Ssfk5wqNTGuJXUXXzxUi6+K5QkuchNaPJORPxZPQ2OvJJ6ERMfAktEXvbkAVpX93g1asavzuBuj/uxsYN1j5tpn53TMjD/rxthnejzXH/VioxuBn2Cdqxa28H4tgzbMmQx4m3uiHXiFqKd7oR7B4BynBIlgEi2BRCRa/mYJg0bEIFsGiEiyCRbAIFsEiWFSCxV0hwaJjESyCRSVYBItgESyCRbCoBItgLYb+BfM3DsS1ovzOAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NDozMC0wNTowMCVVUNYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0RaQS5zdmfcFAlaAAAAH3RFWHRzdmc6ZGVzY3JpcHRpb24ARmxhZyBvZiBBbGdlcmlho8plBgAAAABJRU5ErkJggg=="},"66":{"admin":"Egypt","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADJUlEQVR42u2aPUhVYRiAz5ItFhTRUARCYEVL0w2CApeGIlwKCqIlpCFBSppqCLKoiAoiQhTRyCT7gZKgGqIfbZASSQzCLIzsh4uSgQkut+G5wyunazdwO8/ycHi/Hzifj+/7ne+7ycDgstXVm6RcWCYugVQsqVhSsVwIqVhSsaRiSalYUrGkYkmpWFKxpGJJqVhSsaRiSalYUrGkYkmpWFKxZKbEmrx4e9eDF7IUx1e0VFw4AL+/7WhsOeOalMOkMFuYKeRlmrMnfp//sX+s9/W60zdHtj4/cvjU1w1D09caibs+86+bYv2DMy+nxj52/6r+1t83ESOuzPxUrLL+/xBr+uREYfi4YinWf6iDLukyR3xy9HPTk7UQvcqfQbEyRxSZSD4d6qlEiKgIrVGpn7vHq55diXpFmWA+NzJ4q8mslmmxUGpgcfee3KLR4b76Y70x3/CMQJRCmJaGCDMwGzMrVkZfnuyCCkO1PQd3NiAHcciXIHpBIrEPo5iB2YgrlmIVxXp39FHv3urBrnsfah6+yrVOrjxHa8xhRGilJ6MUS7GKJPekxeK5lFj9d25cWn+f1vQo+jOzYinWHLEgcRi35zEe+yuWYhXJqXopsYjAdCmMrWmxmFmxFOsvYpF16BO/Ftmq0/r+7tPauuWKpVhzGA8IoljcDHJexcFBPHRgY86ZVimxmFmxMvryCJQubcTjASkCwXhASiSOVSzFyqNFFIsIZY78RMbiSxASoTUWxCgWEcVSrGKOocDFK2fUiWLFI9N4Ih8LK8VRsTL68vz50Sve/cWbwagLYrExRyZ6MgpSRhXLc6zGeBUN01c6yASJpC92pgpf3jzuQi/PsfzZTJ7MhFI8owi6xJ1W3F3FnrGA8uyqKlaechZlirkKmdJ9ECjmrVgcXVXFmvMbUYhAMF0cY+GLz4z1h35FsTprOrd1bpbw+o72jc1bOra31V9d1b6mteFyXVtVc8XZfWnSChnl6kUmSWWyJFkq5QLTJZCKJRVLKpYLIRVLKpZULCkVSyqWVCwpFUsqllQsKRVLKpZULCkVSyqWVCwpFUsqlswS/wBhPLGpf7jDnwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDU6MDYtMDU6MDAnyAkxAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9FR1kuc3ZnlbvP6AAAAABJRU5ErkJggg=="},"67":{"admin":"Eritrea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGV0lEQVR42u2cT2hdRRTGLxQqIraQgqhgW/yTaJ5pmz8miqg0inRjRWmlXUgbBKspiFA0qRshQjQ8N6K0KEVBRetC2k3FgiihQlwYQottBJUqunioaKsVbSmNi8/FJ4cZzr0zc+/c92ZzuNx339ybN7/3ne+cmZds8b1Va4ev/+DykXcHp/cMjB3bMHPv/PRXfa21Z9/ualzsWvHh4UZj5eOHPm8sR8SZFFO0x+yXVcvOjXRz/HbfytHhnZ+dumluaH7f3vt7BsZ2PTN+Zn3rroXmVN9rErj0IaaoAssUoW164BJ2Caxul6hXuBQTWN2+gJs9f+PVA8dMwEHVVrx4eE/vP4hpShJYwRUuoZbA8hBPb71yzeibCbgEVqQKl7BLYDlFfZWaYgKrpKIhKVwC67/4a+uq6dvecFc4Cdzwfa88PdJMHq72YP3x4KbZ/jsQgcvvS4P9g9f+Of/IgQ3LcF6+6++xiZN9q02vhlA409JWgiY6sM41H31u/fiFE/t/7n0YoOD44uT7O2++cP66l65oHME1Z765c+PALqCGM7j+0kMff9rzbAi8UuO3BmD9dqjnsqFeTl5ABFgsLcy1um/AMWDCq9AtIAXUcCUiziCWiZeLh+tkhctCOCHoEPACKIwU69NfR57Ysm6SkyCOWc9wJSLO8DicTKFtdWmLJLByRGCBKQdYQAFg4TygAXAAAkgBMsZFqiAnRODFEOMYY8ZfpZqKhvbALtN8KPoPEbrCmgQUMNnAhaEBUuy0cCzBwhlGEBHjs8LhvHsVGU9KrWNizXylP0w5YGL/hGm2j8BaxSDiGGpkcnKMr/Rteb8YMQNXrw2Yma/2AasOJpgdkt3gs09i7WEFknjhXayO8r5IuJ3Z+K1W5zK/voqrOUytSW+AC6sagyIrR1Nqg2GX9SPeW62dr7ZosCtcaOAyX90pTCQiVMfucjh5AQUon2xJ6EdjvBA1ibi9gdOk1BCJ1ZtisVrYO0yyvmO8OAkiAlw7WFLtEKvqdSUPl/FeKHewNAmIe10yhcEVcUGgr+/kmAmsqhq/TooFCJBuGCyNZcY13PZkfeK+lMmlSVgxGmOKZ4uts9WuwHkDC3rAZlmTCu3jcEK023bp84AR14YYDZqaECmnaEDj14NimZZrTEAg7fKrGIdTGKdU1jC7eedKk91bUqxwW8m/+HL14rbjB2+9fWryxxe2bJ060Nr25MTo0YXMvTUqGwSIJhTwLqAALWFAOS1yy9SuW6x5nBBNffwUXQB6effm7a9eeuyap374ZP/m5c9PHn9g48TMju8O3tPfvOX0T4iZC1K89Cv1xtTSBCisK3yGITAdy4gnYe1EBLixdeFdSqVw8eu5rtFNS1KBJEB3720e/f51xgjnOTopFm++44pM09I0bYzBuwAlrsT49lTIBQQiJ8dOaJO6pzAGiKExocNRXuPNvEvjLKeTazfe9SArSl7M1qQzTn/cr+d9FjGrRVUAyRRmAsWOkXzVQ4OUkZJRVohIW+yiOPJorH/SgAMXXsCW6bgT+ljuAGk0KW/09r8beGFHTrDsbPHyM3svU4pk1eEWq7xjsZZHewPkK+rH9LbRDxMPmyw74Jp0xj6J6zsJpSwX+C51by7gi5oXoBAYRQEWb3rhik+/JMym3t6qMC0HaRoTscW8JlpOrd5c+4XPPlrAH1OwoZb1mkSHf41jampIpBgszXJ1VSls7p01s9sXY1MgiUix+8pnDv7zL7u55v1YcoeWfBe/F5Ah+cYAk72RyB0gjvqJzDvlZaKpUqwQjUToEww1p0uTAZd7QYGapqdVrYnmDxfouH/7w+FS7C6aZ6j4J/ZIc6ZKsFobrgco/li+elUMFm9rlquE5XTM9WV8DCkmZtRK9Vj6f/UR2ifF0AcKMcGhn7bYyFnqRNdFgVye06VJUcx7ZXUHqFgZH2JZw8Wqx++Z8j5J1h6NRLmdI9yUFAOoTODc+1K1N+/FFMjUB4rnY+0EU19B591FgeKxwzFPc7ULzMH3Y2kWU+0KJH1POYlG/7EWex6Xv6I9VDaLYUdiMfucd0nE7xTG9gXwm8hKBUu/J1qzJl8MrPKVoEyAQk+2r4KgIFjFFEh/47xTFW5qq1Id+32rSoXu25H/BxZ06KOZvqHxt+w/69H8NiOGGCcu1UJQ/nz9C9XYLU2nyDoEAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NToxNy0wNTowME0VAhsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VSSS5zdmemQMtCAAAAAElFTkSuQmCC"},"68":{"admin":"Spain","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFlklEQVR42u2cb2hVZRzHL6m0zN2x25y3ICUpsz9UUGkUVkiBK1PDP3OlAzFsZVi6YX+c9qIUq7EXjXm3OUGUuWlp7s+9Llxobk2d3HpRWGBFgUJSLyItwhcW+LkvfvJ0LltnXjxn3zcfHp577vPcnefD7/c7zzk7kf4DY09NnCKKw8uIToEosUSJJUosnQhRYokSS5RYoiixRIklSixRlFiixBIllihKLFFiiSNQrG/vfPzR8RsGzyNn5l+I3jHUb4lePP7rmsLr0sH6zTiQ/ZdH/irvGX3NmH9+6Hs7ErN0+72O/H+0o/n5DdnHudJ/Rfa/zmt2P79hMOfND88nP59WGPU/TuRKn2JxZFJiiRJLlFg5IBXGbxNTTaPv/XKg9WzRWC2nxPJFZPqlpnvGhHOtp+s3378Fohf9f1QcmlTUrAWWWJ4xCVGQxouNczZujZ47+dbupaMaoHsM42jJJVYMIRCFKNVZUZfMW3NmVkP9mJchPUSs9Im6hXlv0k+bEc7+uGvmqFull8TK7KYgDRtxnQdmnIil975asjp2EO4pKV4+obzlwgOnbpiaSFSdzK+iB9a3zK2KFrWPe6bytmOfPrSoIxpnHFVjEisjFupYjZCGNmx9rPTP63ttv23zXY6RWCNOLK99avRCFD8kOWrJR5BYVFEIRD3k6oUWpDxLynbbtj2Q8V1x6Wfk3NzqkVg5jVIkqdXN5cvit3yc/9L2vE32npol0nAkrCu4b6BwMbRJEKVslLLqcCnAkYyD1hIiJGLZaz2ksQucue67VLZD1EEjKifabR3PPT/naGvBjlTN2q5E89SWYtoD3yS/25iwZH8r+7xiSFKh1wITb+y1no1Ptv+T9Wsb3tjdV9N1KBU52Nj+RdcL6NVes2nl69W0+ZTtBlcsFfUhFCu9YH/PvIUIQdsVC5lsEiSRWbEQqHfpe0d3Xjx+Y+X5phh0xeKqkxiWmff3XXvv6ZcQoRLrq+6+JdueOrK9d92xi1Ysdp7svhSVk5dYKJKau2HJhyUoRZt+xre3erzmFUNSY/UWV3ZM+Qg5aPOMInWVjVil6dnRZ8ch1vKvp137RNwVC9q4RQ8Ri/FRNpV6sXN65rtELF0bBl4sthVQh/1xCnArlt3etEnQjVgrbrrrp5lbbUK0qRClIHMxPmJBdvPpl14BFovFQx2E+KzqcKT/g7buLU+XjUc7mwqJWESp2vjDZU++b2PYpL9L36lt5FPSHwnOVlf0UGNxvWk/RWtu/kiLwKdCBKKIZoGRBqVYfrvFYEmUmv5aedOqmyGSMUJy8ub82qQlV4iIRWSyCbT/+23zFv1MapYWgY9YqEMyanzw7p7bT5McEY6dcbZPLUmC6GXFgvQTeywZ+bInIy71IOL+spWJgsPuvr8YSLFYWtIQcYWrMz61YrHwtNmdp03cgihFtWSVss9HoBTjEymJW7b2khaBT4UsOQtMjWUv+61YViZXNftQDTLxKUQaK5a73UBtpxvVobpXOFSxaLuq2SjFjhdi2VRokx1iufOKIRGLReXunt0B9yOWV41ln3SgYHfnFUN1E9rS/geOVyqkh0J+Z8+KPYuXoY6Vya23ss8rIUIiFs+qV0xe1/bKvgxnVc+vjNNv7xh61ViU3lRI6GUfX7a01RXHM5el4lZIxPJaYCuWjTFIxrdIYRApabNfxRYGx7u7U+ur35296pHL5mUEZ14xwKkQ8sQBSXAwr6bgdjIbnpZ8N/sIzGgTbi5fGSKxAsbhelOKOILEUiwJ1pmMDFec8P/epsG86crrGPfp+Oxj+j+52Ufwembf//kZ6hnIzVu7/kOsoL9dTm/fuzqpd5CKermtKLFEiaUTIUosUWKJEksnQpRYosQSJZYoSixRYokSSxQl1tVN7vBLLKkgSiwxCPwXO5Dgx3YRLdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ2OjI5LTA1OjAweJLFpgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRVNQLnN2ZwDs7RQAAAAASUVORK5CYII="},"69":{"admin":"Estonia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABAEAIAAAAzLZlgAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA50lEQVR42u3ZQQqCUBhF4X8braNRK8hJG3BV5cxRO3Ed1U6MkAZORFEM3gvB7xy4k0TxcQZFURS36vmyNu2GI7DCssKywnIQoz1318/j7RyEZYVlhWWtsKyw7A5+oAjLCssKywrLQVhhWWHZPf9BJCwrLCssKywHYTOE1X+1nPuCOfx0es300+Vrctz/1/vkfq9/vm+O5665fs1z43A/1peTtWk3AAAAAAAAAAAAAAAAAAAAAADYKmVDpje6lkyvsCgsCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCxSWBQWhUUKi1v1C8pommxuYBvBAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NjozNS0wNTowMHOYr0wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VTVC5zdmf1bEvUAAAAAElFTkSuQmCC"},"70":{"admin":"Ethiopia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGNklEQVR42u2bf2iVVRjH33/c1OaoLM15F7amFLZ+CUtsLW+1aVaylUwZtDIvYcYEdS0dWVtBtCAnNmeSlitMsIZKgRWkNaxgYq2iouHuH+uPfgyDDSIDs6CPfzyX03k793o33/e9zz9fLuc973POe8/3fp/vec57vbz+9vU3xhVTcOTF+PUnstA/3TgRQk9pZCXE2NFiPAknxxrHcZVYwVj+yD2F9z8Du0wrhwX/wqpCINT3vBQrl6mTrWcP5nc4ZrPygsx6xfD+1L00pq7EUi0cE/Oem8TyLyWoluuu0AUnXtXaFN8/aWi992Dz5MHHko2xiw4/cqqlueDehrLnukFauEpP7lJdV2L9B40gzWWz7j71TnHh6UW1B7rcsfiXmp69i6e0LpuxI++G408vWvFZCtWUWGHf4rojZIIKkKngzapfD72SLqUkyghEZhQtN0R854KKZKZM9M9Mzxgx1zQsJ1LhxJef+mRxHyqSmTLV7Wjq2LwdzEzJGJ2ZKLHCr1KCUjYdcqFF29mtBQ/N2T3affSeLe4KZ7bkDr28aBvzktEHkt1HbHa7ccKzvY0n+WyjCBEg1pezPv7+2pJrapfftfsvfyMvI0uNTFGvSCfHyBLLxUvV1q+rbFsICWxaRfo7eF3P7ZVfQyxazJTKWEQjsov3iiyxGuL7niy5I0rIht/dA20sbv/80enVz69JvPCDqS6kv50bXhta+oFEk4KQiWjmPtGGzDZ6q+CdGRnsn5QfDfz5WP/AtCQUcbfVMtnN29/Q1JUv21EpSAPSIpMsyZEItuRrG/223xOnt3zDzKO0FpEi1ltv76uuas5s1wYd0RuSGilP0ggCSarRk7ugSLq7TiIwcyVWQNF0P3z2R9NRQZrO4V0Tan8kFcr+pEKu0jOxffPjGz51GcscV5YzlFiBw28rjvWWfmXu11Aa1MgFJVGO/3ZkYO5y6cBAUh4jYtX9Y86fvHL+tj+ZGy3mPGkhphIrQMiuzdwDsmBoDCkMukg02wcP9E29cphlRpnoI5GrZhyJH3rvttwyIkdH/5iVqVsHn+lpqexUYgUI28s6d9UXmUtFC4RDLXAz0AUtQdUksvwgtSizD0rWurGj8OEFEEX6MCITAQpK92abJ0+hxAoQkpJsxlm2Q6/e2Ptv3FyBokjTDY1QINM5yWhcJY4shEIpInPVZZfKvTyFEitAaCOBbQllikRRiIDHgli2CruMQE+sNxGIRmSZ8lxmpcQKmWL5axiEwAPh1UhbLtFIrHgyIhDNvUCqxAo0ulNB6gcpjFSI6gwlT7w0czo6JI9lzMgkOLwadxGBdvyWS6FBxucplFiBM+/+yiRLD9J6ozHQQiYyabqhIEiLvGomRNplqUImVhu91LwHr9zw70bdLDegRiaNsNhyj8aiktpk5R0bTgSuShrJurncmdLOKDaqmfV3LTcEukAq9QBimTSSfdAhdnCmV6NFli5l2pXeyNwhSpRUY1ZaIA39kY7LKSGLyiE05ECBZMqTSZCr9OQu2s23JGwOz3wzQo90QnwIbWoJRzTcCy3MKpSJshImS68kXPdthNxdKrFC8NqMy9KSniAHS0vJgL2eTKzytRn5Ug090Srq7CiZ+0vPzDaCr838lF+/bOrfUcKu1VV593W6vwslUxtuyXRjJrGkc+IuWQmznQaayGyjtwreQHVhW9GqkOHgpZcUXXEOLVdXnJl3dtNal3dH5Zmg7RRP/kvH1kce5rjU1ZhhX820xJxKp+cKFXopjyEfLJjtznj05IzisuEFa279oyNpehp5LG2jlGmupWLZehLN/3yw/PWK0W17mWEWvgH/nrYfZHbjGO3e+SxeFh57jMd9b1UsVl5Xen/88J7LM/y7qSCWTbHc4zATZnUBvpNxRC+yjycIzUJK9UqXEPLN0syIhUrlAqWiTixLcnTxXrZ3Gfz/UWgrJaR4qUj8UJVY1i9l63ezEzVrURH3NxHcy55EZpTc+elGkVgZOTlUhOVf+mr5obYppQvjH+0p8SeNWQnDORGBaKHXJyVWdhFC7Hzi6tlLZm76Yu7qlXWJ2E0Xr7uTdAbSwlV6ntvf6XeoxFJUYikqsRQVlViKSixFJZaiohJLUYmlGDL8B46CU/TMZu3QAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0Njo0Ni0wNTowMEi1vMgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0VUSC5zdmdNeQHvAAAAAElFTkSuQmCC"},"71":{"admin":"Finland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA9EAIAAACEkYd/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABnklEQVR42u3cMUoDQRgG0G3EU4gKYmWj0cI1vUQECxsP4Als7EyKIAqCVoE0UYIHsEhpF7DwCBYWlvYiG4QV2cLGckdm2DfF1wYmj3+G5dvNyujX49zz6uvK/HAn73eybKvV69adrfVu+2p0P3y6LK2aVgYWWGCBBRZYYIEFVoNh7eZFfwkssAJNrB8EdcPaODx7AMtR2AULLBMLLJd3sMACCyywwALLAgsssMACCywLLLDAAgusqGF58g5WMhMLLLAchanA+sg/T2fjOLPYno2/7iYL07eXzdB9rPPJ7ft0UP1izHuSSmbt/eNidBFzrnWO9gbXYUj95vLiwclNGf9upJJZdcOIO0Nc2P+eWynsRioZ/A+TzUxbIMGSYEmwbIQES4IlwZISLAmWBEtKsCRYsqGw/qebEH+7QR+h1tTH0scK0sfSINUgDdIg1XmvUue9qS9T+AYpWKm9VwgWWCYWWCYWWCYWWGCBBRZYYIEFFlhggZUCLJ8xAgsssByFYIEFVtNhefIOlokFFlhggQUWWGCB5TkWWGCBFfv6BuluuD1YhrY6AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzowMC0wNTowMEDt7DYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZJTi5zdmdMmf+XAAAAAElFTkSuQmCC"},"74":{"admin":"France","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3cMRLBQBSA4Zc9CgqcALXC4bThBA5gNFyFM+wBVNFqjYTszpfi1Sm++bOv2ETEdNK2pc/T9ny977rCn+fx8rjtc7dMmybnxXy9KncGWGCBBRZYYIEFFlhggQUWWGCBBRZYYIE1/GzSLB0CLLAUCyywwAILLLDAAgsssGyFlcAqnJRiKRZYYIEFFlhggQUWWGDZCsFSLLDAAgsssP4DazQowVIssMACy1ZYG6wP30qxwFIssMACCyywwOr/HAYWWIplKwQLLLDAAgssZyywwAILLLDAqh6We4VgKZatECywFAsssMACCyywwAILLLBshWCBpVhggQUWWGCBBRZYYIFlKwQLLMUCCyywwAILLLBG+T8ZsMBSLFshWIoFFlhg/fp8BhZYigUWWGB9C+t9ggUWWGD5FA44XxBz7mcwZM9VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODozMS0wNTowMJkeu+wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZSQS5zdmen2JoeAAAAAElFTkSuQmCC"},"77":{"admin":"Gabon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABOElEQVR42u3bsUpCYRjH4fcKmtRN53AVpLwLh2g9F6Fbg+DgXQhB4dosSIsI6qTQ3NYa4SrS4JpInQ891bM80+Hl489vPRExHGYZmVoTUFgUFoVlCAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoVFYZGnD+s62xz1u9//zN9+vzjmyejrm9X5w+PtPZnWeG0sF7UXMq2xe183y/3TuN2uVpVK8W/+pfecy9gPQaZVWBQWhUVhGYLCorAoLENQWBQWhUUKi8KisEhhUVgUFiksCovCIoXFwoa1u1sPShMeNM//Kv94t3i7mH3UR2Rao33Zu3lqkGmNq3HneVYl0xqtVrc7nZJpFRaFRWFRWIagsCgsCssQFBaFRWGRwqKwKCxSWBQWhUUKi8KisEhhUVgUFpnXTyquhRLNf5MSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0OToxMS0wNTowMDT5168AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dBQi5zdmfDTZtPAAAAAElFTkSuQmCC"},"79":{"admin":"United Kingdom","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG3ElEQVR42u1d34vVRRSfl5ICo+yhh8UH+0UIGW0E9WaQL4kk9BAk9lSICmFsRT1kgdCLmBghan/AIi0FrdSK4A+WjTZtt9BetNRda+mHES0iJKHhfgzP7Xxn7pk5Z+bO3b0vhy/f+/1+Z+acz5xz5syZc92Z5asH16/5c+MnY5+PXHvw6sS1vlh65s2pZ/74Z/Pwu/sPbL5rW//ke/3O3bfl7SVWdPHsiq+2TYBafXPd6YHbhp6jo7iy/cLYzIbvf3j00KrfJ4fu3rT8A06/3XfP5RUf8+tTvzy8+KnP8AX6zecfeuXq/ovOLZ1+60tbnsRyL/wMpAYJQpro/99DZ4enRn994v3RvYe/G1za99jaiT13LHvgG8oT3MGv5/tfWjuw49Ls+IXJRxx+xg9nH39h56Yjf702curI8TSQHXPjM+emIbYcILOg18WcBixOwVa8xYGFVvQgsJ1UFEzo4eiOEz9NXaJ8AJgwLjqF+DTzYcZxNoVfkFN0tz6QhYAF3SOBFGV3O2DZa6w0qMnBRPHANZMEG843RxtANnjwwNEP6wcZZzq/ozeFeo2VQw9Fg+n1Xfd+NCsZdSwG3A07Shrwsc/KXNagyTiwwAe5KZT4WFamMLdmyiFrd/zlk1/M3C5pGKyszVzGC+O6YYJbLddYPj9DprFKOO96MJ1ccv+dT+7WyxSIcnJV2VlNVsOqMFVjpQBLvppDK3IFoTdzskWbkT2u2fFvFVL6qtCnt0r6WLlXc2kyahqLx1ikaTJqLmv2ycLA4tGaMM0dx9Ks5hrCSQrNdLTv6/GpW2WyEHgkGnNpGyezAlkNGqvO1ZwdzyOUswBkQVVs6yRqgrFWAVLbONb8msAKd9LWJ9NsK8UwIiVA6hOkXGP5Jm0bB1zAw5y7JgojXih2IphtLZrMeIViqbF4/2NXhXozVymY0jRWGsjg7oUZx7cOcq4uU+JYelOo1+7dtp9bMHAXBhldUeYG2caD79wyPKDZK5QAC60AxGmhAYAJGShWYCqzleRaVZ/vOnwnfP8mxZDAaIkmo/CiINMzembdb4tmr+gj72Fg0VZoCgoNanBqFRo49OLYsz+uBLf/AxOXi0+OYSSE789RoDhM0TnJk+F3cQ2K7B8MngsYIDt/bMOiN1wDncv7mR7ZMrj1U43Ln9sUckih5y2UjOvnvVtPbH9VPyJwlepLuQQ1sqbU6UUyP6hmVehz3hcy7QHLYFUY1lgLFFhgR7dTmBvNF5BQm+a84y18oZ4RdZbzDkzBnKPXlPruS56JfZc/L/kCfz6tXb7v6ctz5zpM3vPwk5LrWB7Gfk3fTxc7O31RFv13NG1J+lCyn3L/rB5qyx83X5ml6SF/t8x4w63Y9iH3iFw+VtqKtjaGdsvUiu2n5PmwS+DVWJLXahB5edHWCco6J7DLh98c88MWfLm1ctr39RPbVjWEv+b71dU54zVPpjEi/J16tHUN3p6ESy63GeqUL1ISCiX9mHy6ylb7OtslaG3Px77r02ol12vdHmi4ASxNEFL+fCyVf18eDpUEDDWsTAtmygOSmpByyf6AzpMtHT2tbUun22lvEzo60a+3Cd3LbiiUj9VLm2kAlj6lKzbpDxRpaDzRL5YixxJJf76UQFCk2mE3PhZYaRmkaRSJfhgR8kh9CY9Ih+TtahL90hIA+a9Oll7sSziWP3kzNRkNayAFMIHptNIcX91A5BxStG5dvnwsngAdMca5xGskYdPTAHykvjE2HYEPJxnrE9PpdcHDFBowYR5TMIX1BxhNhU0ZjcRoqyP2PmChFX4yRz+RfHD3jb3paHzuGjgFS+qkzVoNQ33Hv+TnCiWBUMm5QsoTnJnRg4yfa2qYYMxc4lhs/tJRRpUwzcAUZBwv0MgZ1+5UtE2pSInG8hUFoS4BPa1Ux8SrQGOV1ExyM1f+iH27ajMp5y41PEzTZNYgWxBg6mxREGmdQTNzyXjbBmQK3vrtWDVgaljpsFkVU5+pHLBy1MfK4ZOJQGbmkxUEE8p++GrOxKxoOv8HArYaS+6/2vpkvKCBnZXIDCafvW8BU3Z7/3/hlXHecxS3LSmjMMjaaLIyqzmJmYsFU2pxi5QapPr6WDmKb+c2lzzUEqHJaLEvW5+J5jbl0ExUMLEhkrL1sUpUrq8tTua6f1mborHK18cq+S86ZVaXYZ/MaRrg+Za1gqk7apDm+wOYHJoszA0n35uL1Uxpq7n8ZcHa+1i+kdZQ573OgDbnmNNv9NId9RyhgRwCkGgseSZ7zRord3KAz/F3SKhFxlJYMyEKxVcEp58+t+fiKLJ/ypu5tP/SkYQb5OdwcgMrdoyxz0NqkCBPKIrNQEGqz7/AbUQccgW+ogAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjE6NDY6MDQtMDU6MDBbYKMbAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9HQlIuc3ZnJTl+YwAAAABJRU5ErkJggg=="},"80":{"admin":"Georgia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC50lEQVR42u2dsa3CMBRFPQAD0FJRUbNCZmACSlpmQKJBrEBHS0+HmCETsAJFfnGFZP3gyE5iEsenuULBMfblxEls6z1TVVVZlXnrptgUVWVKU/ahqi13Vw0WABZgARZgARZgARZgARZgAZZT79f71a2ANQVXzdB/5Hw2n30+b+/bO2C11f1uvxuPq2ZoC2w9LA9LwGqrcq/eHrmdBVjn0/n0zYLL8/IErLYq9+rtkduTAutVvIoQC27v2zuknjzBkht1T+Sez+XqqiEBsNTo4lgcq2q9WC8+dut+7/oj7TL2ufocz4hUwJIDqj/UVd0Q7XPjuhoVLHWg+1+lehixpH25unqsHoC1YMTq21WBleStUE23X4BtrXfVVQawYria5IhlP6TroVLTdOqG6+Fdx1VG5XVu7LfFtG6FabhqxvRizHTDdFw1Y5rKG2bGZSpguWYHh5l2HgQsvR6PZY54iks6tma0pKNrSB2252AYsbqPWHVXMxqx2DbDthnAAizAAizAAizAAizAAizAGg9Y9f3R9nKBj/qXD63Z/yy7jKt8/biOaANJX2CpNp/2d+mXPrt65Doerz3/y5hvi5f2keZvx1DeVSa05njq0x7/fvnU6fNbcX028W1Fc1TAQgELBSwUsDACBSwUsFDAwggUsFDAQgELRSOCleK6Ybs1wd+sEnZpj79X7dYT+/r172WCdjeErpCHrqK7Vuab9yY0/6JPbWPY3dCuTPN+hL78abMDgv1Y7Mdiox9gARZgARZgARZgARZgARZgEbuB2A1EmwEsos0QH4v4WET0Sx0s/zjvCYNlR8UkBmmMy1Lq46pGMperSYbjJmpy7HDc2UVNJoHA+BMIxL1cSSCQbQKBhMHqkkvnd1lfUgMrDVd/kP3LP09VPfvXL/JUkf2LfIWA1WISZ4L5CkMnSMmwOh1XB1/SISd0bFeHaQ9Z7Euy2LNtBrDYNgNYgAVYgAVYgAVYgAVYgAVY/eoffW/ASfIPUTIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjUwOjI5LTA1OjAwtAJqIQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR0VPLnN2Z6BMHegAAAAASUVORK5CYII="},"82":{"admin":"Ghana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADA0lEQVR42u2ZP2gTURzHX4PYYrQY0lQkS5VSpIuDe9NFKOjmoHSsdbFaEMWCCg6H+AdLQVwKndQIKl1ERKEgSOqmQkVFcVFBHFpwK4KKw3c5OROfucvl5d5n+Qwvye/d/e6T93vvd+bFy0L/0BCEydKQAohYELEgYpEIiFgQsSBiQYhYELEgYkGIWBCxIGJBiFgQsSBiQYhYELEgYkGIWBCxoFdird6fXSrXOp2fV5ZWi19bF//N3bPB9vdiePzDtYffi6Pu5MGd6zE/R1Z29edhYy6sB/e2XBHJhg0R6x9cH31eKo3v/1hZ3vhA1Ig714ZYHUkVl21fiq9ypR1Pyr9y3zTigxyNr63xpyap6V1OUPwimOvremx2i9U7l8/1zrAyZXbFanW6Ff/Q3rFP3UFYLI2wlqckVvZWLJU8lb+wWBrROdSF7LmZefZYdamSF1aqvQWxsyqD8WGV0urybO7W7cJBe+oMWE8sffq/MZNd59hjOSHW5PED+3pqhcO9k12BDespFaZ9NM2+Nly72HeeFcuhm4m/OuqhXnp6Yjq/pvaBjTpxqFk0Y3NKpV8TkprR+Nnie3R1vri1vGdweGrDqVYopciaxc0Mt65JpJgmnYft5m5M5z4VqfgyhUtec03U9mYp2dlN/ItoRdlKMzX61fUjZ3o2T0SbC/YlTxHS6a65/wc2fvao6jHaDrWhVqksZSn+vZgsqRPnLnRybG7XpV+530pI8ykbH27bJrI22vaNhihd2Kq7k3mT7dOfPU+/nZjdNN64Hdq4ZaoIPr/YCV+D8W0XVa+/FS2CWr2CyrHX+aPKj76pkejapgjtaoG69gR5V/iXImjThYp2whTB/YKYjoKI9UcR1PnOfhse7YTFL4i8K8xIEVSLIX4XShEUrRPfCSYs1s3q4uLgDT85N7BQ3fnu5PKFHwMzScVUNEX2ObfGdE9NV0YgTJikACIWRCyIWCQCIhZELIhYECIWRCyIWBAiFkQsiFgQIhZELIhYECIWRCyIWBAiFkQs6BN/A5SD8vcJxtzQAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MDo1My0wNTowMBq3PHYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dIQS5zdmej4rBXAAAAAElFTkSuQmCC"},"86":{"admin":"Equatorial Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE9UlEQVR42u2cf0jUZxzHD4P9tUJbSRSMFbMfJmjJwGCFOpijSBnsj5bYoA2LQiHnVMhK2KitwqGJcRs1BurKKwlbksj164+sy4pbadClblhaCRaNWgw2R7z3xyeevsd59zx3z3VvhBdfnvv6eD738vO8v5/vF10LC+sH1u5aVfDDT65kktRFV0rdtj1dX2d6G2qyfudykNrESiquvO2flZy+Y4f7VI7vYMZbS7gopDaxwHlF33xfWcVFITWL5Zr95dZrXqYuUoNYkEnqxdRFahNLkqmLNCKWJFIX9SI1i8XURRoRS6auZUONvvTTXDJSW8UCmbpII2LJ1MWFIzWLxdRFGhSLqYs0IhZTF2lQLKYu0qBYMnXlHvxxzOXispKu4B2s8FIX9SK1VSyZurispGaxmLpIg2LFY68L23fwTVyeE8r5FCuidBV5r0t+PKF8VKF//JGIpc4Qiljh/dzwzpnqeHjrE956GqxYYNr4dynH/+jJv/NG68J4pGf8VlbX4fh9/7GicbHA2pzTqWP+yfbJY5MemznR8ec/T/bKkYeb7h3zF2IcxIj9v0tsqa3dEHyGGRXVjVcnPZn+LY9O2qaR5HDdbxv2+UEIdOeL8zVZz+U4RvAqRgab+tLKS9XZKJY3OlxU8e2ugYxbT0czJ0ps+OVHCgJNne9BFBwPfna5csN6iDK6eXTdaJ6UTMqEV3EmKGcb9vS7PMspljeaXPvJoY2DtTb8Td+dH6jr+7/e4BgMdAcuBtZAneDEmaCcDZJRLG/0aVvqgijqRobjM7OH2n0NoPongRHoxXQVY7FsS10QSx13T/e3dFyYkeFOza/PfPLLByXv4joRlJI5zUCxIornUz0fx/akLtQbKQrUSVrdXL7iTSduWtrj2fMh3j/FeoVYupqi8Zu6pBYQBfUJAuEuAuqWk14Uy4qt0LbUBS0uld7ruDFUfKhtb9nbcvvDSPPS8s6iudAIIzjGd1GsMMVSq5quOod5Ypu65LUeREFUx/GFBUcCB9pAp1YqxbKoYsn/GQFiW7QnvIOX6rv+PloEOjVaKZZ1W6E9Fcsp51GsOBYrmhlL7TwF70KpW2HwmqdeYyYO8VtbIVb0rwqlWLLeOL0HxPa2X1vcTUmhNCyCz8aK9drePZRiSTqdD7FAJ10ww+2ZN3299xO5YsW4jxXbRIWPvLe6t6q3Sl4PqirgDmBz++d52a2gqqCsUiBmplgJ17VSK5a8qSwVwRMNEEUVUf0ueSbF0hbA4+vpBgiB5NTW8uJLagGq2yWonokZ5Dy8KkzQ57Fk3eqc9uILWuBYiqVWKSeZ2HSIqlh2PkEavP8uKQVSt04+LxrVR5Nj1aPSW8/UB/rYSohBxlKZlbxx3ZGxkZ9b/zq64lnKicfHu3XRxJzxS9tWw6BYUKrLt6z7/eaRB/PT57xjM4fzF/jmXXEad3o1vDkTgUbEQqJqeLSq59OBxFxW0kh4L91duLO2n4tLsbQptbKsZPvhOYm8BZAviRX5NSD+U0O8JCrS6orFREUa3AqZqEgNYsmrPyYqUrNYTFSkNrGYqEgNYjldJyJRcfsjtVUsJKrr44s/XjyTC0dqEIuJijQi1v5zue6Sf7lYpDaxmKhIbWLJHhUTFalBLPaoSIM3odmjIjWLxbt+pDax5GPETFSkNrGQqE58lH027ysuB6lNLCYq0gT/A0LeSt+yXmjJAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1MjoyNS0wNTowMHdX0GgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dOUS5zdmcVW8TIAAAAAElFTkSuQmCC"},"87":{"admin":"Greece","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADDklEQVR42u2dP0gcQRSHp0sjCMbC1sJOSJ10llYBKxGxESxFMGWaWAhKCGohEiS1IiGIkAMLAwZMCAEFMUElJCioKGoEJWBzFq+5sNzydndm72bmaz6WcXz3buZ3b3fe/FnTsjnU/WHeLnuvJn9/3r6eu2u7n6luVn9UL9Ip9V14AhtF405YGkmJ+BAWwrIcsaRO39Lr91urdAbCsnwrdC2sjrORnrW3dHZ0EYtbIcJCWNC2sPQ3FEaFMNiIJT+D6Terv/a7595Vnh3+TDL9r+UzJH9MqHmsrkejfZXF3dmjsZvnGh+gXQabxxJhnfZfL/zbp5uDElZj81hErKiF5S6PRcQKUFhPP75s3xgnYiGsYB/eiVgNE5ZEF7sc/rbw+Pug3gmpn++znpgXt+tnyXIR66fZvc7zNYlbSe6s/Jn4O6Av94VZ/c/aDhr7RuJKLeVXnizPSr2w0u3k80e+nohMclq1lHhWpDzUElvlJtRQLHKUeEYevAGZ97CFJb8kuhlhEbEQFsKCCIvORljlCSu2laVZv2+R9jGSQxJKBry2JElNHVluoU86SH29fT2XT7Yqx1OwfAa+HovMe4BzhfrUqIu5QoTFXOEFk9AIy5tbIZPQCGuGpckIizXvMO417/LwLkNfWTzztfXgy+Wr5HURhmqnyGdJeURnN9Qu6qiXAEwuCNH8b0h29OXpPkRxdgOM7hgjv3ZCN9sUUDNPSXF2A0RYHGMUubDkmYlDQaIWVr0xQvooI30EUeThPZ8/mlFSkZGU3fKs177489+o0EXmQ7YH6R/epX6+TIwL/5M203M2Gjt6m375U8+OIUcMnWTeaQKIsCDCggiLhoAIC3oiLPaTQIe7dFzvOCtzR5sta0V8sOV/mZNRdvvIMPkAvZkrhBBhQYQFERZk2YzNcyxdnGmpsZ/1jM0iZ3Km+0P7yLXx92xg2Mwk8w6Z0oEICyIsGgIiLIiwYNTCKvONxWLB1puP8aecN0zns0PmHTKlAxEWRFg0BAxaWL6cCdP8frr2UGOfiAW5FRJd/OEDrkySTY3bfGcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjUyOjQ1LTA1OjAwsTjZ7wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvR1JDLnN2Z3tvwsoAAAAASUVORK5CYII="},"96":{"admin":"Croatia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFb0lEQVR42u2cf2hVZRjHD0X9YWS1f6YDDZY/tn9qOopyUsGwVXfgsEy2UbeNqDSa6X40JKJSq621aRPpxoqkNKu70ERWsELrZrYixFkxlqUkjIXNUtE2FjfY5/7xyLt7Onf33nXufZ9/vlze85znPfd9P/d5nvPsnDm/HMk9kHtQ1V1P7phdM+sT9xFVqU6mXOjxP+cdnn9mahCkCqbUzphaNKd/fbIELH/GLf94y8iIZf4aNBEoTNMUsbyHYv8nU3csshWOVK2PT1Ohn8tkf4KV7lpwihEr+cVKx+W6+0wHfHIdzDXh12yOT231kokN5uz+/BE6NtcBcpt7Ku6NVJ7oGyzZvOxmiUt/Z/G3twbaj64r23TH249Vzny09OS+wk2FQblWnIUHrSNTlgr9X13Ftv94+ff3fQUoEh2AmHvP0p3bRm7aU3ihaUXnxRtry0vAZf2rTVduqb/6habAzm9ynnu67s05jHAU1ApGbutqex6VeEk0md3/a6XthgSiLJtadKr15/BMdGvfI9vr8w4fXPNU/fgTjQXnq1eir3++MKf4GcACmuVvbM9Z9/Gc8Zc2hmr4jOa/WzfaVerMKDjb9qRUAOVcEFzZvbV37yjzciUKVgbfQnNVRAg2GCBe27i/OVAGKA+V1T74Sl6kPq90wQaQkmCBWm/egeL5N3DWjl8/+HvxtYzcMlZdsqGwpXp99PYGLEtyr797TS54yQiHfePlXf33/8iV2BC3nOzuGMlYBVhsM2Cx/aQ/qSACOpwFFigjHB38Y+DYdXsADq2YFXirfW7VF42r9j/LvEDJWaBGGlWwMr4wl3WSVDaeeEPckucSt0BBxipGAIsR8EKJYaTC8PKW6l19LR/2XNb30cNbwuVfh7gSqi4FK+OVgnreoZfbQx3AxGajxBhZaRGxiF5gJKsrRoqKlm5rawi+H/ysJoCCFCoTK0o993/VWOlLvvEKIceehgLRgsiByoTIPd2iT+861fEDqFGBcRbFPp9RjjKOPedSqoNs9527Z+y7AqRsiFKW/hGauCXvDelOMc7Gg6DZDo3XioxZTnS2qJy+HKwcejyCZ4myPfeDMbCGgg9EVy38vXn1yOrzNijxg289dFVgb2BzTBkRqzGJjWE5iRqWzHj6mobahlp71tmJDkcPRb+zQccHh88Nn+ZrE2PQ3waWjC4Zu1DVu7Z3LTbo2IsDnQMdICLtwYWjWOIfD3iT9swoLW1Q68ACC7MCu9gR+SnSY9oDirRnxAQFsMykCZoKVtaqjEBmtQQWyYNl3n8pWHZELFKbKMlBwR0sGYfigSLBkv6JkQpWloPF9ptKcW0W4+fC7zm7jpIoUUZMSzxIn9JewcpysEiI5tFJUuRE6W3aMyIfnpGRLF4KVrCsAyteUZ8wWBMeFCxLwTq74Miy/hXJR6xJinQFy2awzhS05rfm01uiKkL/qggFQ0HqIanSRqppiQfZCOUzMypY1rUb0FS1G0zP2m6wqN2Qvj6WNkgVrEviSvKddzxoxLI0FVLxTKdSeylYtt4VxvkbYqJ3hSZAeldohcZLeYn2sby0G9xRVrCyMCGauKQKLBmZ3Hv9WQ5W1XhdsPsf3iqJfZYjUhMdN4+m1D8PAU/tenjNwexXeelvyX6VPIuKSloyy39cj/t3SXSdvaywu413dfXgmK9c2qC8CsFjxF6ebkig3TAR5/DMLHausKVgobz+wKPD3tsN7o/N4M1qpBQslPdzSFuy0PYSsWR5jge86aoqWJcoL3KByLFF75wIN1OSy6fjGeEolpzFf23QNVSwPEUyoDFVk52CpapgqSpYqqoKlqqCpapgqaoqWKoKlqqCpaqqYKkqWKoZrv8CfoNALcv4ejsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjU5OjQ1LTA1OjAwSdgiGAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSFJWLnN2ZyUnqtEAAAAASUVORK5CYII="},"98":{"admin":"Hungary","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAwUlEQVR42u3WsQnCUBiF0ZeXlCIEG8FWgp1YiyOksBSXcgoXcAAHEFLoCg4hKDyH8C8CnjPC5StuNQxt23UJQmUTICyEhbBAWAgLYYGwEBbCAmEhLIQFwmLUmrJ83z8bQxAc1qw/PPcTQxCrKreSy9wQ+FgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBb8qDm+LtvTyhAEh3XePdbX2hAEh5VSXtRTQ+BjISyEBcJCWAgLhIWwEBYIC2EhLBAWwuKffAHRWBI+T4tO4QAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDA6MDYtMDU6MDDM8t05AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9IVU4uc3ZnaLJGKgAAAABJRU5ErkJggg=="},"101":{"admin":"India","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACvUlEQVR42u2aMUgcQRRAN5WlYKNI0E6xvjSKYMBesLZKQNDy2oCCKUJAUl0ToyjBQjAgwkWOCLFIIylS5CAYOIJVRI4UKVIcKXIRXvNlPDBkwWKewiv+zs7i+Pj7588W3e7GdmVaynJZuARSsaRiScVyIaRiScWSiiWlYknFkoolpWJJxZKKJaViScWSiiWlYknFkoolpWJJxZKZidU6bDySslwW3ebVb1vKcqlYPXk2eTb5o7M/tz/39V6tWWt+egqJcNVVUqxb8ah11Pq2OD86P3rweKw+Vn/1BE6tTq3uvoUxzkjucvUU6xovji+Ofz1YmVmZ+fAbXRYaC436OJnpvP+8/+dOSq4ykruqg9XB9w+ZzVVVrDZCIMfm5ebl53HkgFGmGIljuCvq5apmLRZCDHWGOrU/vM6iLlRRp8Onw9+rKbkaxzMDszGzYmX3Z5N1KluVrZ2T9bX1tY9LMRuhDqKgSCzeiXCVkTGHMRszE1es7HIVL6+Ye6JSKMKrbXlieeLdS0gEyVK9mC2+WBUrI1J0x3I75iqEiDL1InqleYurPEWxMiKNA3aCadeKeJSDyglGsRgZ94/MRpynKFZGzQVqoCgEWScVa3ZkdmRvurj6ebYBiaRixbxFzuMpeTYgMs1Y/MvRIu4BU7HgbTJW7MWbsayxbqixqJzSst0aS7H+eVeYNhqQJpUJ4dgzMjLO4K7QPta1PlaMx3orPYRO+1ixTWofy857G1GonBAovhb/p/POzHbesz7VisfP8awwtiHiiWEs0omgkWeFitVTL/JN/LohzUy9vm6Ie0zXU7H8Hkux7u4LUkpy6BekiiXvTqw3r7/s7jWkLJfFwP3nL/oGpCyXiiUVSyqWVCwXQiqWVCypWC6EVCypWFKxpFQsqVhSsaRULKlYUrGkVCypWFKxpFQsqVgyK/4Fclp79PqRQrsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAxOjEyLTA1OjAwG9WSigAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSU5ELnN2Z+1kp2cAAAAASUVORK5CYII="},"105":{"admin":"Iran","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA5EAIAAAAfAMVpAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEaUlEQVR42u1aTUgVURi9q4SIFiVE7lJ3b9nb1CKEDASXFm4Cg3KjRcsMBCFCKDJEKAohy0Ug9MrAjbtW5SIoKgJdJAVZqYhBaURii+PiyOFO896MP+TZHC/f/c79vvnumW/umzHU1Q0NNTQYjflicAmMFpbRwjJaWC6E0cIyWlhGC8totLCMFpbRwjIaLSyjhWW0sCrDQ+He6LH2jU4UUTYnljEHYfGGbQSqLLYbKzs3y5ppbqRkf7Un51DZCmlyY/9wfLr05fSj5vNPD589qojZI7dHOlueMSazmi49+XZmRFnAGAu401jJlcyXlZxnbK8rY4Vrsy+77l4ZOPh69/0hjPtHX10YOsdjtgC7l5+/uXnizsLbXQ+vsz07S8dgcYYxlsZKw4rFivloxXpuvVjqvwGfWERdB6zk+m8tC9VQVpp6BvzhgnJZeTxyYGr/WD/LIsZiH7AefH2/+Pgks1h84ILFsZSlRSk3FvyZBX9mKSazsDJvHrM4urLSx8LVJbN4F5ilt0EsQ1iUxZVkFsflqwu8SZwKwsPCwVSIzOKexCyM+Q6AhbeTU1SWxtJt4AtD/ixZZqmd8+eScYacA3OVpRujNVFZ6K2bzOJaYRzbNe0ufBMi/xiLK5mGhXFYHVu9vNpmBP75NVc/V+86ZEcLy2hhbQX+LI1XjVcpujIWVqYHImQ0M9sy3DIMZGHB0w9QCyuKvzsn+yb7Fi72rvSuQDqwQDSfm5u6m7qBsGAWnot7BqoHqmFxJS2sdZJCN/qxtzRVmoJQuBt93FcoFAqfrhbni/NshydYkJ3lZWGt4WxDR2tHKxAdaP5U10zXDD/sICnIix98zOJ1XNXgXjU9XbtUu4SuA0mhD7GndizY4QkW5AVP960dLazlmYniRBHCwhgPRBYWd6yYsMBimWI1C2tHv0rgHgOJ8KMQyMJiOzz5dIXV/ErCHasIuUAWOCGxgJI7FixgsbDcsYLfV/HbKZy0PvTU1NTUcNfRjoVZeIIFxGo+vPtXYRv/EuS3VtzJMOZHHlvA4iO8q2phrZMXRIMHGR5qkA7GaoEnvyx1JS2sf3zMYXlNvQshBKCeovxJx8Iq41D/vX2wcbARB3PuWLBg1od0CyuH7uUuZWEZt6uwku/I2Oz/zdqcTDYz1satGfCPIjgx4AMFxvjxjDF8gLDzLCN/a8uLhTMN56ZZKfJJKF+W5skZcp5pYul18fqxWLxr2Vmx+sdYaXYtMBlj3k4WBNuzs3RWWTyrUkv2z8JKn1vMp1xWuZWsjKW7pitk3+s1YWmYGKrGYyxWcbks7WoxeVXG4r6SF0trqHdzMou3RHuAdkGVl8ZSll6dxuJdq4yF2YCPEkD+UY2v9Dzm/0lilnoyJrMwy/6YjbH431c4ruafHEuvkS3JGTILs7FYWpP0LK0tZ5Jm12Ks2FXrrqWPpfkHfvVnNOaFLoHRwjJaWEYLy2i0sIwWltHCMhotLKOFZbSwjEYLy2hhGS0so9HCMm5v/Avargl1nz+mlQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MDQ6MDctMDU6MDBjbnb3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9JUk4uc3Zn08B9JgAAAABJRU5ErkJggg=="},"106":{"admin":"Iraq","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADjUlEQVR42u2aTUhUURiGB2pXC1tEi9oUVJvIRa1yE2gtK11EEGlRUG2KQFAoqIVBZQsJijYhSSVhiopkfxBJZVT+hGGQRdiPUFFGP9BuWjybA5eZZvTOOKMPL7xczhzvOXO/x+8758xNDA4vWrqqVNfj9YSPQBcsXbB0wfJB6IKlC5YuWLouWLpg6YKl64KlC5YuWLouWLpg6YKl64KlC5YuWLouWLpg6XMKrO9NbZXdT3U9Xk8kXyV/JCd0PV6fc2B9Pf+37FdF/kfM/7iCldcA1999XtvW8L7999i3nvyMy1iXTr0+3XcmfR/BKko/MTnU2tm/ZEfr+kO1/VVfDr9pzefoI12TKz/uBe6hhd9qxo9y3dk8fmtw177Rh3+aKwSryByMQIqMlc/CNNb1c+3n6mu9b5NPyoG7sWXkZu9u2jcM9DxruIPPpnI5y8GixBC2LfPulTRdySR4YUaJC6xoluJ6+9b71RdWMEN65j+bClbWTn5a/aL9XP0HQpv5XxHyeLPIuRujVXc7mA8Y7ax8sPjiBGDdOfjp2MtGPgU+wSo4JzCUP8KZajkfzRCAteBky6P9a+PKH2F+WlZ2fduRd4AVZizAYlzmIFgFWv4IW6ryRHGMrr24JvzT2a9xN1ZU5Euuo2BtrrtdfrYkBIs1mWAVkB9Y87j08qaw3ET7gBQBZkdGODkUCAvodEohmZI7A00UrGgppH/H8PjygRrBKggnGGSg9P/xYT4jwGHRjAss5jA1sMxYM+bhHoqCtW5VV/fxq2FpI5Chs+EntFGw6BOWwmzBoj+IhGu1ECzGEqwCwihcRYFFiAIhSeXzNzbX7ekLrymC6TNWtmus8BgWZyzBKtD9Hctb8gElj4CFh41kr8wdaFJlrGgpTJ+9+JSsCS6s2NKXwuiuMFrKi/EHn0SxnEWBEXgRDFoICblnas7dCH+YsWjhUzz92RLhBxqgZG7hPGkPvwsYRXeFAEp7MR49JIrrBxkeOsHLhZNjwrwSgpL+iBWwQIH+mTsZjm8aIogzH8HKCVhh4AlhLjx8xSXaku1LMtl6qju4xooZJgoTOYDSQLApDcV+Nu37WDP27lS4lAY12kEtXM4bSMGK7Ueb2fdynGDp+v/AulqhVPxKJBYqlQP5CJRgKcFSgqWUYCnBUoKllGApwVKCpZRgKcFSgqWUYCnBUoKllGApwVKCpZRgKcFSc0n/ACD6ZYnG6V1CAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNDoyMS0wNTowMEKbRLAAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lSUS5zdmcxcH11AAAAAElFTkSuQmCC"},"109":{"admin":"Italy","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3csQ1BQRjA8XeGEAuwAdEqsINBUCqJRGEBlREUJpAQFiASnWiMcGqd4j1x3u+3gfjnu/suImTZctnrZolbt/ur8XWwa5w6l3Q/xXOxmW6Pt+F4PunGQ6zFerqfpZJ6UqEaRmGfISyEBWUNKz7iLLZ8kcJCWC7vmFh/I/WHBmH96gxuhns4CwtshQgLYdkKMbEQFsICWyHCQli2QkwshIWwwFaIsPjeMpTTj3ZshbyfADn9zNDEwlGIsBCWrZD8r/8mFoVc/22FOAoRFsICYSEshJXycusdS1gIKxnesYTFZ4e7/8eikBns/7HAVoiwEJatEBMLYSEssBXyfkMt7M3MxCq14l75bYU4ChEWwgJh2QqFhbBshQgLYSEsEJatUFiUOqx0dytb4U+H5UDBUYiwXN6FBTl6AXMqeeREiAuGAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNDo1Ni0wNTowMI35cycAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0lUQS5zdmeHyQnqAAAAAElFTkSuQmCC"},"112":{"admin":"Jordan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADUUlEQVR42u2bS0hUYRiGTwOzSJNsEcF0oTInu1jTTSKwFhEWUkgwYhOuiloUJG1CIYigdGW2sbAkNy0GCimJWjTRxohZ1EgoKUhOV2ohWYQRhYXfLH45nGEu/7lM8zDwMhwPZwb/Z97v/b7/HOPqxwW+wHhdv3+yrMZYavgMP4pq0KHBhe2V8RfXF60KVnXNK/0Q6AQyVIMKUqJmyHaX+FeXnuHfhOYFlpVKuQQyVDNYOBlqC1gqXkCGagYrvZPRXaLawJoDWXDxufVhQQ3IUG1gWSnlErAsy5xeyHAyHCuLjJWtMsIALFuU4F+kk3cXtLIsUbEZJ8OxbAFRuksyGeHdRuDYICdjOaqUSzJW1n6W7XcQJwvHlyUqVkZikbuRftQLWpCOZY78kszGDzXNPzH8ddvDgdjEr4lPKz43zCRm7v97oY6r4aQb2QeWqkPHlt/c0i2QfRt9unXwJctc8GC5W0bTO5lAhpM5CpYtTiMu4gXnsyiXLL9DYKkzpByXXylJrw/uCTVsf3Vh7dtd3+cULFfTmLlcAplrjpWJfwiUox37qsPRkXc7Dx+Ymhy+0zPw831L25pLP+SIoGZ1Nb3dIk72X2UswWi6c+T3WK98gGQaOSLYubxxZC7TqpMBmZNgZY5CsuO0r7Xc/DEClviW5/rKDMol3aULjiUoiBtZzZDkyJfz17r7nqdSl+NDjYwKupxj0VGCiwulMHVr8uyvXNKVwCQqTpY6J8sWgUEr44YUXpK0JKq/mT7ecrZKlip9V2h+Csj28YQJpqm9j049eQwWHh2Qqg9WqE6mC5e8fgaEdHfBsjFEOz/NMsEkxZrl95BjeWJYkMeWDkvOXmGuYwLKHGDpTXIEcMDSAdZsQ9B1L/Sgekk4erJ9/42m+r7Lrbcjz6LJi0fU97mpXMHqOun/mvk5hf59rI4Ydo8cbXm6WoHJCLQ11pajnlPvR3Upc1diofjGsdqeoyU1zSxbwYPlLmTAVBRgOYMXMBUpWNq6OUUlM9VtagzuqGdJKIUanAmYAEtbNwdMgJXXnVJ0c4ClGSlgQi0HpFncb6mUOWBCcx03KPdXkZlQbWDhTKiOLR1lzgRMqAawmDOh2sCim0O1aW9y3Z8Nt3AmVK/+Be+XqsC/4qgsAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowNzo1NC0wNTowMPFR2Q0AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0pPUi5zdmf4cAx9AAAAAElFTkSuQmCC"},"114":{"admin":"Siachen Glacier","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABkEAIAAACvEN5AAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF8UlEQVR42u2dbWhVdRzHbxRMikoIezEqCDREgmssrJS9cIqDMbbIpYNFtGYKrkVzhulqkbnyoqZbo5pIK9O2ZJWm6TZ6otBNLV/ERnM+QLqVlY7QTIVqSd+9+I+7zbv7P+fsnnM+jH0Ze+Jwzoff8+9/IhnTM6JXP1HUUY1wC1DAQgELBSxuBApYKGChgIWigIUCFgpYKApYKGChgIWigIUCFgpYKApYKGChgIVGG6L/3Nelr6eXRr+KfiId8jvlVz8qBtX4fRSwhtH53xRMLKgpKirOW/JjxYSKScsux2Kxu9c21M6pza69M1710xVTym4qOaq/zb6Y1TGzfwh8ocQuEmZrJIBiOXXN9VVt+d9e+C6zr+XM1t8+vdR4vuzs7QPvD7QOfJSInrt8YcuVvy6e6M/rW3v6uu6TR55rfKVpctMsoflodc7Xc+oBK7D60GuZZVkP6GHvOfRlRvsXAiJxgMaqAvRYw7G0nokfTN3cWrOhaMb89tzZgBUQlWXyBqbRtW/Vie1dN8qBZj2TeeuD6UF1lJFg2ye5OTm48YJpJD34b9vk5o+DasMCCFZm6ew3su+qv/fDJ1qqUg2meFVMtqyppHXB8iBZr0CBNffFnOKC3h0zWnbsz0h9pOLxWrSvcH3eivjSBmCNs+PzI1KmduUfWN1yRzCyyICAtenU5vztL7mX03Uc6NjYETVV33cjFdj507tTNx5XaA9Y46YLb3uqc+W27q2n036fYP9QhcuW/rorsTVLfy1+snDavJvnrcv+xSx7yjrq+8o3hbVT16B6mKIuwBq3IN0p95dcjmbGQ7nvLbh+8T0qtNpfz96Sxtff/sO/divid1tl74ycimzUwHEqgTi7uHd3T7p/ixG+AUupuFTOyL6goIc3mOr/HV0VXWSfkek/yHrZO8fa9dWrlz/vxzzRZxZLYMkq2D82hcluPDb9T/uUQleIxfIILHsnqNaKe4m9wLK/TkV+irT8Zbd86Qoro+ta6utsHpjblkAQyLLaNJROze2+tKtQOSlguY6XOoA21SlvknmVJJwCC1foOlg2Fks5oDdp/CPpj725pO3MlHOtfz6eHFidt+x/a9cGXKGnMVZyD0x5luvX+X/pwd5lm9UswPJIx1puOF7zw6GDWW534sxq1vcVRzN6J9lkheoBpBpSI+0BhK7yrhzQyyaJfY3NjAVT01aNHk4EpAldfrgysmbazp7dsz6babaKvRwIVtnW3v35PboKFFjmiJxysZF+6obj09fKVe2Rkq3S5o9/Z7NY/7JCSlZKFXanRmj8G7ADlsMD0E4h5U16AVgpaqXcGIA22+HMvIdOhZRTE1cmUoqo2NIJnZXSGMznVYdruu93dscweEgBVkIqpNr3HHmn86SzOzkafWYTOnTnO8jxOWul1KnUmhcr9qFDyo1lMhU8VaoNw/kzgDWMOlU9N4f14su2gBW6iMq+eWyWOv04TQVYrtgq+4aMWT032z6AFdJKun2o7vd9QMByOGCXE7SZ9lTGF7ZYCrCuMbb2bOWrlZvSkgvYU392ystJLMAaUlu3WdAwZ6e4n4A1xBXaLJem5gAxYPkYLDnBl/eW3rDwfDizP8ByxRVqNkETVIAFWMOAVbjy6Yerm8cavBNdAVZCW8uJ17HkBP17GgxgeRppaXd5dLyEVDAOdASscZgUVWVLK2VSvcgktu2Fn8tzuUuAZXWyTeIlQRSwUMBCASswTlCTDnpPmOIqM9LSmwrD8x4vwHLsRCutToxe01Jp1JtDkQDL92MzY50dNatZ3EnAGuaUGJuDh8yjcimWApbDh3vLbtExBKxBsJLrEsarzuLCYgHWoDoFln8P+wcsF1e+bKbdGfcDrBGDd5v9HPNFcIDlAFh6JMG4ETbLFFr5AiYs1jUWVhN5l4QqWOFcnwesJFXvTVUzR1V4oab3rwomhmcAy4HJUlMZoQEsxybiKXsCFgpYKGChKGChgIUCFooCFgpYKGChKGChgIUCFooCFgpYKGChKGChvgIrSGsUKBYL9Y3+B+TF2EckTR7VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMy0wM1QxNzowNzoyNy0wNTowMPI3Z2MAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tBUy5zdmdZbU8JAAAAAElFTkSuQmCC"},"115":{"admin":"Kazakhstan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFdklEQVR42u2dP2hdVRzHO2QLoSLaghAEC4Kx0KHYpdihiDgIrSAZglAKLejgIM3WdrBgLVmyaKbiIIXStOSPJto/qWmaFjEIWcwgKQgO1skh4KI+6vDJ8JHDfbnv5RF7z/ktX+47977z7vnd7/39fuf3+53zdu3qn55aWQsM7DGGCAJ3iFh7r85O3/pk976ZjcXfXrn39RvXfwFp4WwILrBjYg0f//blseHRS7eWzyx9un7nzOn946cW9pyYooWzIbjAjon1weTNtbM/fnnj7g/v9J07dPujD38FaTm5evPFj38PwQV2TCy0FDSa/3vx/NE37/y1+M+RAVo4G4IL7JhYmLxr+7977+1nUuRsCC6wS1P40+j9Pa+uo6VAWjgbggvs0hROXFxYGHkOUwjSEqYwsAc+FubPxzjyIbjALsMNDjGAtES4wUhsr39gZuJ+ixYfB7H+03T4j7lHXzwPjTB/NoKcLZNALw18NTE7Ax67/M3n46+/1ZpvfTZiJJjMNXwriLWJBzfmhq68YIPoQENpxEIa5Buss3EMkAlIC3E+ruRb9BDE2vSxEB9iAmkpwXmHEKCJAjIvRhqQBrTErOO53n0WRyy/l+D3K0t9rw2CTu/kLSBMmL1Mxo6ZwwhaS1l72SCaWFCtUGIhUIcbUuRsri4qugcqgBALokCgP088aD278mTo4c/9p1LkLFe6N/pBnxVhHP0BukAdYlf2IdySnyAYOxrFo4ZS6OwqMlXh6vtLkwcuWHuBuPaZzx/9gQHbBCAakJZcVbrHzhghBBOXTillRHu5f5CW4jQWSptjq3GEktPbxlh4zHbA8ZbaG746SA94Zp4MQdxs9VbahEBNI95gizs/QTAutIvnv+3pUp9e9Aa9+BV+sSBi4VpCKQwBxKIlP8eTR8tj5vEzRh7/dnRVahAdIyyOWLxV0MguJy2cbTqN/i9iEV5uT6xMqFZVj5XqKleQNnHwDqY42WJi1TeFnWJqCjm2JH2HjZ8elVA240xfVXoKV3rnnfc0jQbtGu/ap00Mm8iNc4UUKDex5p3HxovBWBidq2GddXC4YfsG0USpCjdwJ9yV77PBHm2VKUyJlT6MZsWo/Ho8Hlt+OPguI0KfeXGbfcrtB0itjdIAKb9L/9wV9oG7tVYLU/iUhhL8evAIQetgSGCDxYtkd763KR1a+BYIHbnbBgd32i+mcJaw6cu/bOIdSWJcNkxoEUYNLeonofHS6K19Eppr/OpCdIjV+GV2dcINaQVpE51K9ISNO6NwYaNXgYPOQDhQjJTokx7snlu705IaXIq8ITckg1LWdtkGSK2rmh4g9WSeh2f9kRbopYV+JkpVoZ8Lu6GLJeZ+bObsyWWSja2T0gHzSOlAL+tgP0JHsOw4O59og4g0fOzSZOt1u/A2c87Pcj+ZFDSnMV/TyG9kTkloVy6k81yHTDFY1jrp0omq6LmnApDPJE6LlLJKl1VVkNrVxfbnV0Hq+FZVqgod5hCAC7Xxrkyg1KlP6QKlvEdGhqV/VWUzxHussdySXyWWAwHpa+NNnSCTfanUVeCa1KiZUlkZvk5Lk50xLKE0GUpV+Ukmh+lVhxz07JljQYV+DN4EKnMxRftAq4MO7a+HfDad9FCE9Koi754b2v0sc+8G5w29fjBdngoiKwKexNPZUgVSFrE2s86CVcdsylx+6aUWDpwiGUfPQaY7HHMlZCpoAX79Jfa0lLnEPvVE08X1dttjif0Wm4I4ggW9YlOQwB5sY2S0QQzBBXYZeW9fNhOb9QR2WUHqrSKhFC2xa3LgtojlqbV3UI7NbQO7JJbX5MR23IE91limkd35MIWBPfjLkzToEOGGwPiTpsD4W7nAIFZg4Jb4L4xjl6KxNwljAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjowOToxMy0wNTowMK512coAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tBWi5zdmdUfS14AAAAAElFTkSuQmCC"},"116":{"admin":"Kenya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFGUlEQVR42u2cW0gVQRjHR3rpTi1dfehG5wgRUS9Bl5eiCwX1EIUFRb5k9dIFD5wgKHoIyywIs6Qeuhwyu4GEQhZhJZWB+GAJUZBEER0yohuRYeXpb/TVNMNuarY7fwf+D7O73+yZ+TnfN7PfrlIxNbijUKndq+wCKsGiEiwqwWJHUAkWlWBRCRaVSrCoBItKsCKm0+t+Ft4bwermwbtblCn/zxD+n3dFsAKol8gUDCFK7FGm9Nb9oHV5P7hDghVKTV5MViYrv37OlNNDMyXocI56pXJUTt4k76P3EYqaoIijddwJ7ooxVugd4ssRbypfHgo6qFPbVMffiTVei9dyf0Lu3twbUNTgaFC4cScRd4LurArlbNG06VmqaZN9aAFNWSKjjQWz82fferx01+FdZVDUVKzLGqBUvDxrR9YOO9bpZ+lP6U9y1uSqMCK66EGmyHnLNMBwc0DqaqqjZtDDlsS1xLWn2SXnS85DUYOjOFN3jrr7Q+u4E4IVKS2Nlb4ofWEf5m0LfyIFfX77ZNHJIgkWauQ5uEqfq9rGp9vT7WgRrXMfK4KKdVnT5kzR463Fs35HCvq6+mbrzVYJFmr0M2FBj6vgfHt3TdoLiknbnYJZCnhhFkG9dH/QuuHZ97LvYdaRYL0f2Ly9uRhH5fmwAGuwjFbQomv9rPDjXSsIqKE1m1fWr6zXZ6DbWfFUPKWDhRoc1a+CNWnfzR5WmK7dLKa5pytgXZk2MndkLiy73LdOgyXXd90FllxLOg0WusA1bWzeEtsSw+zSE2DBMlpxs4eVqWuirdgg2Nj455WgKXh/Mrk4WZxEza25Y7eOfW+6Fpb1zQt31NGfja0B7LDbwdK3G+yRGXbkYdm0hUGwIqjyUYy+z65r65jqkuoSCRZqTOfLvXgoWiRYEdf9l3/fJbc7LLg/CRZq7E5W2reDS7Aiooh+5MDbHRYyGgATFDV2Jyvt2yM5ghWp6ErPZbCvDeV6x74e1NNp3Iy0nPvB+sD7iYQax8+vml8F9RO9+QeXYEUqbLenyugzFtaGUNOMZUqhQYuuhfDOgaUPPGoQ1OtXIblP7imjxgSWKceLYDkHln3thuwrCZaej2WfsQgWwfI1Y5meMHLGYozFGItghW1VqIPFVSH3sbiPRbD+7c67fJjDnXeCZXxWKB1Wzz0rtG9kEKzIrg2DZjcAKf/ZDW6G7czH6syaMg28zF5nPtY/BSu8/4t+MkiRI4p8UekQmUHqCyzmvPvJedfB6rmc97Bkytvvk2/p8C0dvv7VM+8VmpyaDhaeEtrBgjW+V6jwZYGQKd627YIFvKOMT4PU7Fw/ZdV+e4ylz1gmHGENljvfhA5jD3dZlXd2627vrGt64F1N+4G3BXfPNST2jFudt77/siNDhuWrPvpbOv5XhbAAa7CMVtzsYaX65a9Vfd3RmR8KL8/8UDGj/kvFjM4u+F4/b9icfb+u4BCAy9e/4ApNX5uBBViLF25viBeiFbToWj87B9axthsXjrWtKD9yaUW5frSgLOdo8O9j4SrdGlpBixJighUpxTDrc5XU0bWrlqta6RbtX/TDmbhKt4ZW7CgTrBArBti/Y5p8fUnVjx150zdIgRTO/DvnS7BCrzKU9n8VoDl+cOJQLyW/mowaP0hJResb6k7VbqgjWBFRDCcC6qDXws3ljV5wxzsDNTk+u6J1gkWlEiwqwaISLCqVYFEJFpVgUakEi0qwqASLSiVYVIJFJVhUamD9Bgc3K7F3aKTwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMDo0NS0wNTowMLjeFp4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tFTi5zdmdajF4sAAAAAElFTkSuQmCC"},"117":{"admin":"Kyrgyzstan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7klEQVR42u2dPcsdRRiGx4/wCkJERQiIBBWJgoGAiGUq0VIsrBTbgD/AfyB+FJaCVpYigmCRHyAWiVUa0d4i8MYkL8FvgsJep7iW52w473v2Y2Z2mmGZ3Z3Zc+Y+93PPPc/sSdcPH71w/tyqyy9Ov3j++1nv3afHQr6ltGpYtHKyMo0wePP/4huIc37yrq/Uwlx7/uUYa07lsf9XuQIFk/9nTO1XWO1n2eXZJnv+1NTGKnrfvd+RnjA1Kd0+b/mhsFkDzW5oAGoMt0/LqfL5VIP4nBBUO6lpi0meLdbvfmVmVmc5dkMhYL3x2yu/PveGa25+/vqZZx7i+PDlc689/w3lJINdONemFkT4pMDo1tdvXjp7RP3R4aWnn/wEMAGgP658duGxUwbZ7Wffunn2gBZ8ZQuvJcwKfdeogQPomHuADsd3Pn7/pzPvUlLz13dffXv6VY5/v/jh208c0AKt+V7KyHnryeNIIw98xmVvsLtnvv3DOw889SMQMXTgHiDFWZjsnxcuHz38Hi38e+PK/Qd/cyXwMp9Rs+G/rq9VMNmxxXu5GkLrj2YahpwawwVgASmAQsldXMlZA4tjwiJg5UpD2dzWQmHJBC7lxGBbGxlMhgvA+vOXLz945BbHnKV0ja+khl42IOuODVaz1yQhctzZ5d4/+1TtWphqzEAOefAQ9QCC+rsf/Xz5wWsu/3vp+tX7PnXJ9RwDL2o4hp+osUqjfg3sleqL8R5Cc4PBxFlAA7B60Ll75/F06hil7gVYtExfcCT1lNZbtWqvVJWxqR4ZTsBkKe0hN+sYKFwTgUgJAxlAEWS0DGQNX2DEs1ntzZTiMu6I3LO1VJOVBz/ZxrS4duDbwk/dsTkmzg25lxoAQY/U9ECm9rkLUFqlWfnVFzeqMkjtP1kyO/AxqIaU53eeOW5ho8Bq8Bbw8lQgspd5iyvpy09Y06JZynT2d6KPar1i+cxwAgKDg7OGRQx5gMxMQw1nfb3hEuFF7zwJrZlHs7NSiwHWxPwH39hzsnU5NL+zm2WIuMSdsp9ujomlYRqDo8FkjrTjNeH644yxKI3LGcvC1KHQ9kFPV3XDbClt18pS3cNsuLjG3r1ZzdorTghiyfN4pbIx1sK/J8+zLKg5ti9l5jCfReVk8W5IufTZzexS7XiZyL0bXsCut3Rd0U6CVLpgZEhgDn739sptfjK0Xmyx6mLIaccaK8LLodDX7N4LZ6m3YqM8tt7KMkOrwrQZBmbIqbJFaSgwqCgqGwp2m2xAAEFLeGroCwB5jhmBZUXoHuuwTKfc/jVWzuTOMIqqJdqYDlIAy6qIuzwV8Noi1xtwQ3fRo2eaQ/apVxWzg9Qe64mp9FjuWZWVk9fvomzn3o1RKZbqHUuYWz/RsuFlJ91h0ZMJA2tIwve8+Cbe8wl/VkgMUmQshtBeV2QdQ2Ro0QYIDgHLhmcPmt299sPMVcdmrHFn8aNGklT60oEn9gaTJ/8Gh7nE8Ipl5Kqo1WCXqLrMpjyJe7dBamcrI421QNpM9qLeyibOCs03fBYPqlmn556H0gtBXqhxqB2ySb3c5KcFmoWJ9wE8pNJh5CG03WBhHnnLAdFWggPi4FqhvHsHQbfjLPgIKT+hs7Vq2kg3707oCZJDLNujM+6cgpjLEMOWfal4fQSZ3axeNn3HiIYgMAVMTuNxAnRz3rMmZIbZrDC0sLMxRQmLAaa7ZDf0gpc2gUUv3s/AvRb4EzLWQjEnlbvXdmg7V1wM9uphb0FaEImJK+Y8gp3NTGd9UcI3Uc85ENvUsMWwgK6aeJSL97G2pM2IOWKCSi/XKgQ4rndGQ+89g2HPj9OOt4ROJcx4JcDrg/b0a9rMsoR4n/i3Yi/eocopNLYlt+gn+eMOYfb3753W7LQ+jr3C6N2Lte44ryoUbnmrgixKJwQ7mbiXAXGiLRUOrHEzhXv3xGLW+WAT7+OCz/64Q4+n+g6OFvjmJMMubg6Lew89gfBStOehxXDVifz9yl+8ZlfJOekAzrM/772JKTcOf/ai7Jx5iuC+Yp7qXonIs79NdBqDtCIzovc+ha4eGMWcKsKl1ZVz1R3aaDPuq3YyoLNV2xb70XYh57aHJwp86zB76J7BGYLmNoMpvqNmy+uNSvwXj0lmhZWWcfnFkDLTeBf15p1Yylb10o19/722c824rXTp7V+F7xjZKblZ/nvc62f57zmd9wjVbR+sbjPF5G/SEnQcTKOluep3+VWymWJq8K3h33hmYqz1/KFjTRDJeF1kra/jLnx1If8RSS0oNHZs2Q1tOIv5UaUWwhqgS85uWErTLDUpWf1f3rVQuFZFmF0GaQtwzaqoSryPFdTyAXF1fzA+1/8VNh5qa4XNVWqSYFzW/x8WNffSy7WyHAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTA6NTgtMDU6MDAVo3fAAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9LR1ouc3ZngiTOZQAAAABJRU5ErkJggg=="},"123":{"admin":"Kuwait","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACmUlEQVR42u2aTWgTQRiGP6ONmDShS0Vy2tSi8QdbWj2oSKpIQRBF/EOIIIgW61GkP1gFqSB4EC+KB1EET+3FS45eVKpCaqAg9CREizdBEE+irpbpIWXdddXZ3Vl93sBz2GxmZicPb5Z2RWR9ZeUmkdMnN/eKXMz1pUUujJXLEP4dF9J+dkVVZP9I6ZHI8Ocdl9gaqEUslWV9qaJI15FVkyJnprbM0WFQi1jNocNgKGLRYTBEsegwGLpYdBgMUSz/DkMyqEEsOgyGKBYdBkMXy6vD2GjECr3D2HTECrHD+IlELO7DYBLE+rMOG123fXbxOerI//aFqR0Icu3R7I/XeoyIu8PUQt1U7/qfkxTqugr3OPHuj5rdoMx3WOb91oHVGw7M3useWXp8dPLOlYyblacTM+N7o2Fc8yaFXvsjpsWetmt2rbG7UW2MOR759sE55/REQ/e80a/BTDq+MVSsN73zL4ckNohFEIsgFmIhFmIRxCKIhViIhVgEsQhiIRZiIRZBLIJYiIVYiEX+dbHcj814Parh/yDHondrTsop/GQP1PFmeowT5JxfzOWe0T/mjBN8LtPEWr5Rbi85dPSLZRemXhUqjwfLjZ0Dz8+fgkmkEUp1PEtV09nxPZknhcyLE23ZNVfrb3NDnZ9efmyd6zwWhF7n/+44JjCJa25euWLM/bTvWvpg3p64nPta7J/JWrtKN+p1yyqVPKmW7n8k+GcNHmfhqwowjv+ZEY3juhYj+imQUjBRjKGfHm7LD3ccRibE0tdP79purs2jFGJp7ieUQiz6CcYtVvrWDzzg/glqE4t+gtrEop+gZrHoJ6hNLP7+BDWLRT9BbWJx/wQ1i1WcTt1vec3/76A2sfqtlrut11U/sR1QF78DzKEz7C4xic0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjA0LTA1OjAwcdSo0wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS1dULnN2Zz7Ccp8AAAAASUVORK5CYII="},"125":{"admin":"Lebanon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFb0lEQVR42u2cS2hdVRSGD4JUoaPioFWqUB8VBw4K4kCLRAQRqYovYiADsREVxA4snfhCagbFig8oIipcIahgqC8KKjVEi1GoSiuF0Ic2SEpjW9GIgijEwefgD4t9um/vvdG7zz/5Oeycu8/N3l/W+vfaO6c6NXfBzMUnrNbuauUhsBosq8GyGiwPhNVgWQ2W1WBZrQbLarCsBstqNVhWg2U1WFarwbIaLGsDwfpt7fAD961N6exrg+P3zNXf061Pdav/nKe3+w3/23GI/XTyO/ZudrTnauH4whsLrc715I4/r53/vN17cj6Vc2d+P+3q9Ke/3PDj6P0rv/prx87Zx/9YderupfmeZzae3Rqfzsez6sVklKEM7l3nTGwf/b46e2zkliOj8wceHT/fI2OwzlAPXje//9j6O5/fc9G2ubM2vjlw+1OAteKl8WXDX27fOn3uezM50ctgeSAWIbVhYPLVZxYUqXh95RW7vti0j3j28jWH9n50lVEzWKdxVJsOfbP89UGFKaXcg149+fF5W27+8PrZV/YOeSQN1r+6e/nxt/cPYdJJeTlIRbwuPfjB1EN/j40cHfzscoPVaKSIMcSb4W1TQy9cxrX6KnBZ/ey7J0ceHNi1+7knVq18cueae4/RrmBtfvjbVusRIp/BajRYeCMsObgAE8pKUOHDgaGPLdv369jvIAV2KHg12XVVtuogAkZgQSJjVYhJpz2mQkXwrfdnXtyzZWrjifXTm8G0yaa+anKNCkeluAAZ5l2dliY72rVFIxZI9a5ga7D+pzARRUhqasBJgsSnWLsi5fEprnFXtGhvKMg2Ga+qOUUEak4adfBPQAAurU9+WD3xToxVRCPawQiwWEt+t+Hnw0cv1HTJp4h//NRgFaj4HtDBhpOwtAVcIhz1ZVLuj/FPtZkpsqEeC9TUeqfqUjmQKUDAyrVWtmghhvF0g1Wg02LFl6qhK2pEo2jYI14amRQm0iswkTqbUOWqmgMTE3zj2ETr6UtS9XQAwnVh59Wea60rrgpTEU5LrADNAoJih8HqS8U4x0o6qz/auY54paIR1XlNdjEtars+i1RID9S9DFZfuigmNcYMYhhKOTRVtYpxKJUWU7adtSdJ0Oa97xOf7voRG7TOTgkUyOKWcyqppSJTjIWpPnliE2rxVdm+SiMEMYy4lX9aIed0Axjh4YAGgHBjODZ9LnDbY/X9BjMlgFSyo4XYpqdGNfHFtWHsRzenuROTruvQWGgttfRQuMdS214fjQCCpMn0a6ShPR6SqU+Ruu1Db0Qp/JaCWF70KhAsKulaQydCcJSFnzKd0ZJrDV0xSiGVc7JU62ExPpXqtwoBi794ygF6Loo4odNJcZLDeqkaer73Uuxyzp3qlrY9Vp+BRdoiEuBvqGYRsbS8yQqOaa4/KpOCT4sXerhZK2Rax1IEucbyl1eGqEotiupBFzXdxKrUdIJmfcFTFXtevyaNSjrmm9APxZGS0mKBYDHZ8QBxTomSe+orW6opsPILIrpNVNI/xPrM+yJlanNiVedgxRNjJW1OG6xFLi1VPk3FLRyVR89gZR2k0foTq0stN8TdwGaeETVYp6nL6z+psjbUFSWRLG4HxTNb/l/CRoPF9GOZOQWPUqTIsfZ6Nov1Hc6MlV0TTod2BFbcxK1fTnfrnUypd2jVPyvVg16TsICA6Qeydr8n/QBZ3EQCU/1p/e+1NOOTGpN2728DrM7f8rY0b7vLeT9dfZ8Hfrpj3a3reveWPfTrydsGb2qhvXtrYXfHp/N3/8U7/Q5Sq19uazVYVoPlgbAaLKvBshosD4TVYFkNltVgWa0Gy2qwrAbLajVYVoNlNVjW0pSTUkv/3H8AlEZcWcxLLAcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjMxLTA1OjAwrWOAlwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTEJOLnN2Z4IuUBoAAAAASUVORK5CYII="},"127":{"admin":"Libya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyAQMAAACQ++z9AAAAA1BMVEUAlTCNlXMpAAAACXBIWXMAAABIAAAASABGyWs+AAAADklEQVQYGWMYBaNgiAIAArwAAa44Of4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjU1LTA1OjAwn0OtAwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTEJZLnN2Z1DuG4gAAAAASUVORK5CYII="},"130":{"admin":"Sri Lanka","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAI3ElEQVR42u2dXYhVVRTHj6KR43f6UCmjFnJHnRBkMkSnHKkYKioZsJh6qJeRQhKfHKuRAiuVUZISI6UMIU3BSMKQoqKBIohCGhL6MqNUqIEgnyqbcH536C9rznafj/s1d78sLvueu8+5a/33f6299tr7RP+eem921B5kkPnKKKggyACsIEcLsC6cO/pMdMHns0rbg20vp4y7u/uZ/Vv8/7tbD+4rffTsvm9SaybVpxewuib1FAq3LutYd8OSw9ll51/rvls47Y/m9Z+Pm4AcHHziVBQNf954MoqG5aV2P7mtJxrh+c8u2f3uhMUH21YV5gwcOHbnjllfHTn9yJRpzUha+DbIBPLazqnT37E6RNtewAKPACKK1hTa1meXi1oeaFza/tuezl/H3zw4+NCCS7DILi8BMQ5Yu3rndxU++OLMS49HM/lH5wfe/DPahFK2/9A4d0GLW/YOzu1q6vO/UmW6HtztPr3539FfvryqvX1mG9pDk99seeXeMbe8uHrFmuverztgHVu5rmfiQfvt9z/1Lm/4MQ4QbojkJd2grB7JE37y9ea+qwtWk2gYfSZwhbUOLEg77tukyi0F4JKCu5xAhO97ts0rzPswDjqJgVXrjAVpoxrgxZ+nXb/1UW6cmbs/apoza/fTi2YfvfHL8oOgPCBDAwxFUIEmD+x9+OyUN7Z3LT18/VMJXGHtMJYreFdYYH4UAdSIG9KNYNTKeC2PgdPFednvRRTFP1V96h1HJbCuHLzHQQS+Sap0FA3n7fu4o2d6a1JoWkBYzlPJt9yXOzIwMDYtDJhSQBwtMZws+PhHdRdjuZ2d25xxI/jTc9v+HnsRYGFOH9dpHajOTOE/jKdQozcApDMyneGqPN7fffGqm3iqIkOXmE3rGlg+sZQXsIYMhlFJYQAF5SE1J/Kxzxb2zDh5omnLq1EHPQAjoEB7MQky1I7c++zajePOvHZow7dRxB13Hbpv/NQtOxpuv3vGVsDNNfTA5F9BVnTTwmQ8JwGAPrnPkHDLAKxMwCI9SP9MvxVAuCTlHpwXhlSe05y1tigsAA1wAcRv7V/b2/Dz1nvuen5y/6ZfWlpnngGyTx65o2niYq7XbBN3BHawo6YMeLYKMNZoSpBmVx88pIYhylGWUu6xKVm4h3aVh+/v7o8G+C0tfAY6pxv274zGqPvbM/nBayYV+Jb7Pndb8+uNK+wEZfM/K49MPQH4GBJADXdpY6Z0s+PEwXvtAGvkWaEmFLLDCzPAH/RfDJnFxXANxrOcpA6Lz0iuV3bhGgBk1+O4kmsANxEbYIKxNLS3AX6+sZcmIyrmCumtFhkLk6jbwpWoeTAYZl4/f/mOiYMKF3VzykxI+Ay4aORED8DCQlaBi7QaUCDqakRemqkKV1iLwNJcjrokBRYS90SMRaAN6+DsAJOyl3WCfEsshSOzSQQ4CXemoMG09Kb8pIzL9Zoxz85bVTQrHK5lqCXGYi6mwFKTFx3iEJg0flLQqCRy4jNchcTwwBH+c+f0gYtK/ZUFDaDMVz9V4Qo1xsoPXiPHWLQnVZw1hq7tq+sZIYM1BCxbLwWMFEwKO533IXGF7hybwovg3aZPYT7aNcXqXi1Nx2F1PStMNzo1leAGFq4Qc1quosXCS+d9On9kNgcP0TN3JN0AdDR6c5cxasSmrjCvNGlY0unLC1i6HGQdE1Agw0Qgz/WwkUZdmobQcP6Ftzvbxi2jB82B5VVJq+ukWbRUda6w+oFlk59uxrK/xQEBLJjGxl4Aa+f5R/eN/V2BZdku30JtnFf2dMwoXtLxrW5IByxGNkyjI57IJi4i0QoInJ2dCWp0xWeNxmyuqxQ7BigsThdX6a/qaEnHv+IqTgIdTA4g6JMWmAbYqYqBlK1cVX5SkNmkg5Wl22DCc/ovxgdXmMNaIXAh0aDmV1iQbdKqBOW2uPjG8pMmTsu5c6lYrOfBWFaHKctmwlqh8pY6LxzThmmrW4czT4x7AnZd9qlmWbE8VgCWjkv4CRjxma1s8A13VHeZZb6mfZZOMlRCPVZuwEoKNdycrt8hYSwFgTo1fRJ/oKhzTArEpL9KWpZdh2UzV54VZuEtgKXpAAJwgGV3D9s4SUto3Cb3vxIY2VjNJ0rjV0nLZtyzwpDHSpluIJ+OswNYLAPbiEorORUubmPrfNPNNJqvdwPOPRMMpckVy2NpwQzA0syTRlTW/Wm1gk847+YqLUR28xnAIhkbBz6K/vKtha9rV5jOLXI9aQUgQh0pwTvG1jp01R6showL6hWClvkAAW5L82pxwGIAaB2pfSqbe8vOWHH/bpTPClUm3Xmsy722KkGNhCHVFQI4VgnVwOr+1LVpWZ9m9lXSpzWkbrjQwmWtJFP+o/8skZbmseo6xoo7YMNnF6HOCmEgUotab66K1pVE3eBlJ/yAQPfuuZ9T4WXdIgDVqnYLLL3SP0EaMu8l3EwBvLQQL+kkQA1MlEYojQvzNzCgsVEUYFUeshCEU5UXQ/BeMWBpxZW/MewxQHGVUraC3t1bHLCAkTKf7oVUrrLsGIBVgX2F6RaCABOggZPsXM+H+bTuHmkjLaADp+o+RK1r1elCXic+1HWCtHSntcT1wMzRXacALCxXWZMDI12phOcsD2keSwty7H0t72Y5qinEWCU8Bkgr0LUKFGn31bgn/woveIhAm8M76dPuSSRuY52AaywceRJ/Fre1smER+jJgFaOKoZiDkDmv9TL3/hnbXkwlOGtQ3QcY2eNDML/OLvV0Bvsk/oNKDybRjfnqxOu0bIY0oz14TWdP5Y/DKntIZFJgoU/0hiaLhwwMDY9wVORlee3yg6mazyaNu6MeEBzOIL3C4bbEHNV8sGylni0u2rMrB6P+DFLXrJCYgL+te/fcC7HuWCTLUdhZDuv26ceHg9Md3M1BSBpIpDyOu9QvEFC+Sf7SgP+le6Mm6rAvEKiHY/5L/QIBbU8MLP9XfbhfyJHulSf5bniyz5b0FSA+rxJx95/0JSv+d8/+0hr//v2tGV7SFGR4+1eQAVhB1rn8D0mrs1wVEO5bAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTowMS0wNTowMC7y9zMAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xLQS5zdmcncZYDAAAAAElFTkSuQmCC"},"132":{"admin":"Lithuania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABj0lEQVR42u3asUpCURzA4fMMOdTUmEsiPUBBcKfmIGhsy7Ggttaeo6XBqUbbpLV36BWilhaxwQpFT4b3nNtNv+VD5HLuuX9/XkUMg0Gv12iQaQ1GQGFRWBSWQVBYFBaFRQqLwqKwSGFRWBQWKSwKi8JaYofvD0dr9/mON59SYY2Wyzf0fOunHVyqfdZnnYrCim10/HkyZni+uNvZGJBpDZvX5697x2RaQwinT8XtLDsvxdm3naL4ehw7nhx3IpfpxzMcRZbUiaArXOc3x5ffVdrrLbOfHPOJORHNnKSyhVVnc4S1TPuJhRi/V61YQExr/KPQgFgqrOlPU3cspgprxjctA2KCsEhhUVgUFiksCovCIoXF2jn2a6hxzBuT/Sy0nyy15n43eCH/Q1g//0mGXMT1g5PD/SsyraF72d5tv5FpDY/F1s32kEzrZ1j9frPZauUw9/rT56ryjH97vXWYT2y1pQqrPi+e6w2rMwJWqbAoLAqLwjIICovCorAMgsKisCgsUlgUFoVFCovC4gr6AfbDZvCvuz1hAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTozMC0wNTowMAYK+2QAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xUVS5zdmdAkRcPAAAAAElFTkSuQmCC"},"133":{"admin":"Luxembourg","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABAklEQVR42u3UMQ7BcBjG4W9wCRFWMZiF0Q3cx1EMTJZew2A2WZqQEIvEYBGEC+j2L8XzDs9gaNMvv4jjsdPp98m0hhNQWBQWheUQFBaFRWGRwqKwKCxSWBQWhUUKi8KisEhhscphLdvjXkamNU714XXUJNMa99W+e2iQaY2HWQkTlgnLhGXCMhOWCcuEZSYsE5YJy0xYJiwTlpmwTFgmLDNhWZXD2gzO81uNTGu0FtlluybTGhHTXT4hU/vip9ksz9P5/ueX8ZbqfG/V7lP05A+8+Bvz/d7Qy75P4T/WZz+Yv6oTUFgUFoXlEBQWhUVhkcKisCgsUlgUFoVFCovC4t/5BO+3nIrZC7rtAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTo0MS0wNTowMKq4+ckAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xVWC5zdmdzXQAbAAAAAElFTkSuQmCC"},"134":{"admin":"Latvia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAwElEQVR42u3asQ3CMBCG0RuCEagjiwZlgoiShr0YAA8QZaTUUQZIi2CJOwmJ9xdvAOurbEfvrY0jmWs4AgqLwqKwHASFRWFRWKSwKCwKixQWhUVhkcKisCgsUlj84bBe6/C8LmSuMT9ul/uZzDWOaXvvJzLX+JgVTFgmLBOWCctMWCYsE5ZZdliu8lhyQerxgSVPOp5LWfII7YMH/ceisCgsB0FhUVgUFiksCovCIoVFYVFYpLAoLAqLFBaFxT/zC7rlujN+sttfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxNTo1NS0wNTowMJJd3UQAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0xWQS5zdmeYOYdGAAAAAElFTkSuQmCC"},"139":{"admin":"Moldova","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFWUlEQVR42u2dW2xUVRSGp164VoS2hgDGYpFLneKUVHhA0ZQoFrBgA20kSKMgOiKmmLRASkStxUC0cmkJJNAXhwbQOrSAoMKDYqIhUkzgQeML3hoSSWwMSb0kWB8+HpbZzDCdkpZzzv/y52SffdZJer78a+21z5mGQiPmPBnf73Xd3/D+oUfae/4+Pz0USkW7e868m9rM9DS9+L/Pa3g1a++Zs0OzI9O8rqFggjVQ8CWPCVgdk7NbwkMElm/B6k+Hk2MFFKz+UYHlAbASeUxfvCf5talEViqUYw1AGhVYAkupUGAJLIElsASWwLrxVZdqLJ+DdenUl2+Gir4vPPJWxpT0VpHqvAusayhIHczdsmvwrj1j6z8ZvOPQozUfTe04fGzbwTuv/HL4s7pbtikVCqzr+IrrLox80FPz5/C2qnDF5KyOzc+tuDyjc29u1QtPvb2janVeQQOuJscSWP9TXKfhlTXxUU14kkWB4+9WxjdlZONVIPXh6drWxecAKxGORCNyb71NNZaHweLx8+Drj1f+/Pjs2pNL9hRPBAh3/qnK5q4RzwAWSkJM5HDERLmL623Ji3c5lifBAotFnQ8vuz/y4hNzf5tevbatfNWsr0h57iNnPvChoOaCQgSiEZm7fLyh6d9BRUqFPgeLktyChc57b+bxcedsCgMXEh+4oDYV2pRKBIsU6jqcwPItWCUFDxZP7EZ5/ODyzutLH5pymbrqavFeuPDCA99ylhIeZ+IsMxlHLVLE546qsQIHFgo6538MX8y/wKM9Fs9/esIyq+44I1y1+9lFBzI3uJF7C5Ycy8Ng2dSGfn1kffesLosOoLSOH7580jqO3RGLFxFsNUZkgRUIsCilWbVtGba8fE4xasFqzVi4Imv7VU0Alp1jwbIxuYuKd5+DxbLfNhpcsPijuGBdQ9MCSy/6+RCsdbevjg3bPP7EY9l332Yfv+tYW5+P3JHTTnn++e7qaGatVVZ5nN33T+SbMWeTg0UqpN3KZpEcy1dg0QKdsHNGzrgc1noWArrqjAMNpffpMS1dGTEbhxGaC0BDi4EILljcl5nJe/ECy5Ng4RY0C/CtaGX0UuQ+IGh+qf5KRWxUZkl8e7vVisbo2vL5AIEywtnwy5Wjt5ZxTATXC0m+LqACyydggQVI4Vus2ixYI+9dcDQWBRSO89fMfqOsjfSHMmLvzsxEYKmPFaB2A4msYGXJ3MgJ8AKLqb8uLq0J4zH42ZJ7Sj8t2kgBjjKCAlkisGiZultAciwfgkXFA1K4F4g0/1GXW7yJNFd9sW5nTgtgMROkQIQRkLIJkS1qCxZpN1G7wV0hCizPOxYw4Vtlf5UOuqsRaL4Y0rovb6OtogCICgm1SDGTq4hAwW63gLRXGKAGKZ1x8MKBcCnWg2ARm9b4Q+FQ0HGVs6RRrqKGw6WIoAZpQMECKZQuF30paixSFX5D/YQyYt/B4qqfmuKdtxbhiBYsOVaAwLLvYK16LW/0yCqUTjrQ0J6wLyUDFiM0PJnJVbRVgdK+myXH8qSyIuttjcWDBx2AACyqKPuWqW112o0gztpGBhGIZntm2oQOhGNRYtutFbBgtQhYoIAzUYfZtR7vWnGWmexCkhZtZI5TaY2qj+Xbz7+AA7zY2AEd1oxs16AWLGaS7JJ/tyPHCvR3hbiLrZNAx4LFCGdT364RWD6vsXr72ar7iQQjthrTB6tyrDRTJDDRkuh7ylONJbCu8820PrEXWPoZI4EVbLCUCgWWwBJYSoUC66YFq39+jltgybHkWGqQ6l+eCCwfO5ZSocAaAG8TWD4Eq++FuVJhoP9J083mXnazyE9g/QdXgm6XrBlEJwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTg6NTUtMDU6MDBno1b0AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NREEuc3ZncFAvYgAAAABJRU5ErkJggg=="},"141":{"admin":"Maldives","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADd0lEQVR42u2dS2gTQRiA96IXRapCQUtBquBBZEUSQ0VpCUYRLz3YUvCqFUHNRUSlFNr0EA/BB1IRkSq04PPiC0TEWmoVD0Wj9FDxIEWkakU99CK2Qn8PI8vGrJvdzOx+l4+SbHeTmS//zPwzO2sVizU1iQSElaVFEUDEgogFEYuCgIgFEQsiFoSIBRELIhaEiAURq0Icb16SSdQJqTa38kEsxIp4+RgpFpLpXz5Wtb4wrC6NF+vR4bpM4lXfVftc8ksgzNq3k18DvMr8+St1tj+fU845z/zWVMOmVvWVAMtK+QwGiyW/DCk4q62rZbMNvXLhSPdU09HSr7sd40apkaDjFmLFjohlcERBLCvoDrv0G8KpVBOrOXyJpUZiF7HMlcOUT05TqJ0ia9efTbW37/l5a0H3jpODI/UDhUvHxpbeGc1tGHrTf6ExeXF/xz794yhiaUHR5cze58uu932wfrR+XjN7YG7j7NOJa9MrJlcdTz18dn5MhFvUk3uSThOxEOsfESu77v7j0w2qTEKJUvXfCrUtPYwKEcuDUtLMqTIJ5XXThwiI5SFJ6L83I/8rTZtTKYlSXtOSeioYcbHCKfTyr5Iu9H86tG26d+bG9yFVqZfNH8ffrlzdeerF7svRGDkSsUJt/u79msiNDjtj1cHFd98Xtkcp/YFYIf2+3WKVjPukkx6l1CtihUTJQjljlaQYmCtELNtrt9p/I2hiFx6xAqekNKV77hRr18zAzSP5qEYs1mMFyOXD+RM7O6Uv5RQrY13Zkm1CrJiK5acxErEqG7H0bxwRK/AqiWqigT6WFqkHt1GhZNsRK1JihdlYuOWxXttTbe86zJ1sjvgK0upO6YSTdECs2I0K4zBXyF06BiwIltUNTr2kvyV5r9JX9PMuESsW67GceskkjykrRRFLu3yPXFd6V84VpIONxdoHk+rktErEIo/lec27mqOXv2X5svTA/C8zRKzY3bAqFS8CSUZesl/l3KXDlA5i+bpRtnyldJjSIWJpNIA3RR3EgogVP7GitxOEfCPEgkQsd7H87LUX+G6ApXfxK72/nnqMx/34/vpeYZ5H2f2QPUghe5BWY79yrwXn53gd9mH/v/JhO+7At6F2HhOOWHF7sACPPDHsiQ888gSxEIuCgIgFEQsiFoSIBRELIhaEiAURCyIWhIgFdeZvdv/8v3xJuaAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE5OjE5LTA1OjAwy4tZRAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTURWLnN2Z6KQZPAAAAAASUVORK5CYII="},"144":{"admin":"Macedonia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFJElEQVR42u2dLY9WMRCFy69gHYog1yDQoCA4BH+BoBEoBAIMjiAwkKwjQYPD4jckwCYkEMQmiCWL2ARBQDyQDGluM+30Y3rvmor34952enrmdNpOw6edi09DON5/9iKEX/cO3oTw+/jwSY3y+8vHz0N4f/Xsbgj7H8L9GcqTvde7+S3lX7O09ODBhYf/erxWX4Mcngmi/nvll+vXdkL48WrvpN4rMfrnc1dOZjA6g6FsCPlvHb1QNniWSsAEcuS7FitRF2Qg+vD8rTO+RzY1zG0d//LZIqxNDe0eSTITCFnqzQykAzJ75XgOhOmtG6iVvo380mdbNC5P31Keo/c82dWtxWQ/P759F8LXo5t3PHEYNaFums7gl944GKvaKWDJzTUBVl0m419Hlx/d9tQ9+mGD6f0MCSxZS4Bb6lOtYZLJykDmR+brlZYHdQUIyiR5rJlq1arh7KPMXXqQ+dRfU9uxw+DbjbuXSocxvVMXTM2BVYvJRsl8jdLiWwRyf37KHbTtmGkYsOwgkzLfT7CUVvSX5Gm4jwXTMGDZZ5eI0z48gaNJ16QPd+aGbXNDA6sClmV22UfmwxBLdWjNAXpJLpnJzwqHuxix3l3yLbzSLsAY14FPWrAmFtBIcr7FSj6Xy1yvbelB1k7mx5zBJy34SbMwLGdznhfHptl3oNFkLWR+HHKsq67Sktw/M00PrNw4Wa1ovux4utkO3HSUfOxsbtPA0jOZXeYDAhSVXVctbVyZl5lWCywNyKTMz+WwGFhlT1jauDKLZto0sDQhDFxMmnv4L9Ej6QQp+UQzvectMdDXxEyLwFrTKNEzGeCQCkY/1deEPHiyhGPrtTlvYaO/Jsbr03hEJcaCwJGrGIWJcZlT8MNk8I2U4ek4u6aU8OLJ3oKWFgFAK2gXqKC98DotBUUgqnD3gXQKEpS8BmhKUAJHn6DEZLW27XqAEbbVgIOeotfoQQmOWAborVFtU70dlKA+ZkrMASilyWpBs+wARZ+DFRIccnDGzIH16oLDUnYFlh2OaaaUoMRxS1DGTKnZHlO25Tp+CyU1SbsVP+DI3SkvP2/yAs/QlGcA00cnLJutJYh5Yzw8/Fhmo4zVDlhLy8wW00tgUXoG1qZdoeww6RroMMrYFUp9thTe9OYKkdKoJdlS6QpPgZUBDine+88ofYr3eGZXps9GcaRpTqcR0TE46s7p2oUbypygn6iVHZSmcIN0KGnmSM+wTgOk6QDpXLaKQcksmzINSsqVL+nQOgkmOf5aL+lI9baFJR3piza3CM0n9kVovk07vviols/96ae7GwrBZNkdb982s3S4dN2L06sCU7vzPHZg6Tf6rQNkK9FMcSfVyicoM7fU2poMQNMJPGYH2ZSzubSUXtNhinlBNjEzxaave9ZPrvT1Of6VPh4y1yGLicFk2cnu+cCqJq2j/9nlZAdTJWe0zkIjtxfHdWidpCQ3Ea03dzkNmOqeFixTV0tx9j55+vRW8gAydwLcT7bStOJpobTqpjEam3nGKZjGJl5DP2kSr/VfENNneRgbJxsswNOpIseGNjyniixb2ewJsmqNtF+dcprcdtR9Ey0O0A7OlNxfkmtKvaPxmY7bslu/FpMVgsl+qdOozKL2tLYelFYLmZ8GWW4bm2umLVx50j93sn4Kos/ymj6tlBvxV83map0S9n9JUzrv6LyXNFkywpe5yyaaaWvXyvXJoOztWrl4WUlSxl/yr3tSZcaLMOMlZ/3QmvEizLo9Lrcqgag/3ULByVnpRu8AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjIwOjEwLTA1OjAwO+RRXAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTUtELnN2Z0nmEscAAAAASUVORK5CYII="},"145":{"admin":"Mali","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABNklEQVR42u3cMQ4BQRSAYWNFoxNH0LqAI2hUKoXCBcQFRKJWqjQKRyDuoVWpdAqFhqxTTNbj+04g8SfzdmYyqdM5HPr9WnCbbbt3242Grdlj836Xi7SP9fvTuTEvj/fLad58XrurSWsa/R+p10BYCKsyRZGW5TjWIiisAGLNWGXvtU4DYYGwEBbCMrxjeEdYCAuEhbAQFvxZWLYbhJWF7QZhISwQViDuYxnes3Afy/COpRBhISwQFsJCWAE4KxRWFrYbhIWwQFjh/N7BjrC+wu8dRQsLYSGsCnkfS1hZeB9LWFgKQVgI69+Hd2EZ3rEUIiyEBcJCWAgrANsNwsrCdoOwEBYIKxD3sQzvWbiPZXjHUoiwEBYIC2EhrACcFQorC9sNwkJY5OQZIxBWHN7H8lWIr0Kq8wHqzHuU9PMQqgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjA6MjQtMDU6MDBBJHKsAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NTEkuc3ZnrHPmzgAAAABJRU5ErkJggg=="},"148":{"admin":"Montenegro","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFzUlEQVR42u1dXWhURxRONWi0CsUoWqrEBFobCgaJBruxpQ/+IKbSREKt1pDIQoKJJhrUxP5QzT5IDESDIRURRNLWihYarSKE5EGjIf5UqKUvQQqFPvRFS33Qh4Bwv3n4Lmfn5m421uzc8/KxzMy9c3fm2++cOXNmb9ZvZy/9Gfvj4ZyfC0qf4rMNg9vYalEucWK9pNpStuGS4Nr0+wr/PMH4qp4nnRnJCr5peHKEGSDFKGASYt3NS3yxvO7Oya3P868rKoZHMMdKLDS6OTDtn5zXFBXDI5jjIxaLmBJLMX1iAbPYQ1JiKU6aYimxFNUUKiqxHMP1C36ce1/HQU3hJOPQpdUzFu0D6miEct5VscJg/55tTwrPmvGZmur1Sp9KFSsFvJ2zcfmS6cP7an9/Z8HIskPT3+uRAWQdJSVWCr9sUApkklFmqBdq0VKJFWliGT+p+u17uQVhhgkIxQJySXjdQo9u+2dWYrm9pYNJBSHCEIsHa+Srjp4VuYx3V52atvKn8GOFHqFwrtIrosQCpeAhWY3X6JLjb+SiFlTAaAzEm/4r6mViocSMlbjKRmv2z9wLXkRuSweTyq43SCY9KtRCjYCmpUcdH3q0MLv61B60u5G9uXPpKUlrQ01HdcuVcEOYX7zXxremIxIYv4e8HxDiwcMzOave4iQiQyahbSCKaUnGcfCX2MLFeXx/H/m8JzGUdUi3IuS8s2cDioA0v9afyyt54DNJHrL5Q3u0lCTAPVELeuEqYwq9lldr5r4539wNLdE7rgUmoawSK7OMIKaf1QifjdkSygHjyITgyDu0B7U2jw0jyVQ2xHLUIEbIeWeXGdOJCZaYhBwe1XgNyEEHY9pgKANJyQhi8bVKrAyOm7NiYVJZtzAQmHhe00HJmI68KuQSdtVxB9TyqKJHqVguBVcjtFdoW+sB5akSduHbyw+PfHK5arTpWUNX7aPmK7W38Lny3u5bezdxCVqacIN3B3l2RRJrnMCH+lgZE2ggSklFYUce2zWffd94pfF+WdeusQNte1sOfRD/9IeajsJ1n4NYJX3xmUfaUQvEVRyGsBGLXfhImELHiSVWhUwvDl2yYrWeb8neseXb4bb2j8vRHiXxRGN/3WIg1ItzHzgMwX1xrMsQK/QegCb6TbloO/tYNheef1qYfigW6NV/prsoNoyWXTsTWyvqvsz/prg6CyQDsnmVlPJ5VxSe0C2djMwf4g1jVixpFhn7H59u+6gIxg6G79pfx+Kr94NefBUbR9RKSrFWmd6xHpwKWjWp4x+lvULaxWMjFUws0GXZUHXs2Gb4T0ff/Tq/0vhVrFLfDZ64vKkPphBXWYMLhK6m2URur5C3iuE/yclmkkF7GhMtJVVjJxcdr3g/AYOIEhAIVMNn1ELVbHd2m1IRTU2WWVPYy5OrRXauQamD/3b0rlkXjGhpIxN6QY++bWxN9Mt0xKQmiZJjf9BbOcr/IIBudf7d+XpsHghU39ex5cMGIEpQa3wvkbJsyEQ9okTmViixMv5rW11mb/o5u8E43R5dYOZAIyBK5Bozyc6j9/lww9o1GzaemN28sqznQve21uIRbFErsRyJaSX5dpRaI/0tXlH6zBzldfG6z5fFRWSFNwZtQ1TsfGXx9sJZGm5wZIXoS1ahVBleLfIOoIyhc0CB28g7wFVHrikcf1DKBC8QqVfFcgNlsJQ1xmfIhJLJwITPW6LQhk/nyGMDXqxpLijNcW+FGNUDqx5ROIVmnBAApc1IY8fKZA0zelSDMplDHNgZdPSofqQVi5NhrKszUIpyRIPjXr68eD1XGOUDq75lvyCEzBENRrQ0wQUllp6EDtYY6ZLb1EsP2o+zpaP/QWo7jsHnCtnZ56MTOlaqWBNUOP0jEN2EVlTFUlRiKSrqlo6irgoV1RQqKk6QWPqSJsWX8pImfSVa+HcFKtreaejLbkj1xZYv7+WUqfY1Wc+f6pP/n8+W6lXpj3A6owR8AWFH9A0C9M33AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMTowOS0wNTowMI0Ufy8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ORS5zdmckS6rEAAAAAElFTkSuQmCC"},"149":{"admin":"Mongolia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwElEQVR42u2cMWgUURRFA1YBSdQY1qggmNZW7dIoBBFsLEQQrWwUxMJC1MrWxkrRwkbEbjGFgqDYiGIV0IiNCgEVIYorhEAkhbBnizu8/bubZGfZmbnNY/g7CezO4b7733t/Rj5/rI3tnS56vHH86vLtfbOz9fr8fBHj0cdzp96v8C3K8URGDJbBMlgGy2AZLINV6C/w9cn0lT1neCQ8HoNVcrB45N8OHds6NToYxTJYFVKsP8/untt5MG+8DFYlwEKrftw5fWLX6Mrrtxe3z4DXYv3w9901gxWRMlg9RQD69eLm6uQ1BQvU8garuHgZrC7x56XzH2pHll89v7xjP2D9XXi0MHGy8fLB2Yk3ViyDtcGIo1o98Ondtpm1L43fY3NrU0tPx7cs3b9+YbJhsAzWpiKJjyQIUngvgzUMYOX3LAZk3kGK5Gjz3gtYeT/yQioWPwoq9e/e4q3xhyRBIq4rD7ysWCUHC2WKSGnEyBssg7Xucqga9hgx9f0VfJcbSg4WtaskWM11ChD5gWXFKglY2hNMFRoULODDafXLbxms0ioWuLATxGmhTPgtUFOkSJr90i2DVVqw0CpgUsg0Unpgb8g9ToXuFfaUENEq9Em1Sldo7Ni8W7E22IRGn8Ao7+EZp8ICgwUWpDCNpDlVLJDiU73WFTXv8R7+j8Eahunc3MFqU0poXlPw1L0hK3qPFhoiiPFOfFjvX8xgFVix0JVYREgNwwAQiKSa0NwTK14Gq9pgNa/VjGs1q3MkCWYUy2BVswndAisUPAGrlSib6+z+NOq6opPqLW4GLO8KCwaWohNToVp7EIwWXiNI5aFYxdUtp8I2qbBzuQGAIihOhVasLoqlZlzLoVSw2A9GyNqY98rvCivnsTqDxc8BQFpcQL24R49XaKsn7jSrBpZTYTIVtpJaQASM9BCYNnbapMJKKpbBaoHV2scJWPFTPfJF1HEa3RWm9oz2WFVKhUGx0CFVLJDSxJcpKESds3l3gTSjWOKx8FJq0qPH0r/NeCyDZcXSjp4qlu4K4+F61S2dxMrDYxULL89jdam8p84VApbOQbiOZcVaB1ipoZrUCE2y8u7pBpcbiHqIPnYJU71CDL4Vy2C1K5CKYum0gh6piJPvrKNwBstgdUmFQMA9WhSldqXX+hK2PFo6nm4oya4wmnfUCDi0vRNb0Rmw+jRBarAKOfMeR2JS7+nTvWHqYGqcedehGqdCn9IZive8l+NcoV9jZLCsWAbLYBksg2WwhhEsz7wbLCuWwTJYgwKrHLvC/+IqLfAqTqLbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMjo0OC0wNTowMEQewWIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ORy5zdmdei/mkAAAAAElFTkSuQmCC"},"159":{"admin":"Niger","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABWEAIAAADmonjmAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACpUlEQVR42u3bPWsUQRjA8RVBlDOFtl4rgvkAdlpYCFpEBBsFCxVF0mpstPED+IKtECzEyph8CDUSQbBQwcLgSxSUKAQFETnBx+KW4y6X3N7eZufX/Lnb3ZuZnfszr89ki4vHjm7ZjFgsM1WAxEJiIbFUBBILiYXEQiQWEguJhUgsJBYSC5FYSCwkFiKxkFhILERiIbGQWIjEQmIhsRCrKtbxiUOb7lXo+eq86bBTixTKrJ+OvLJcIXBwdvuD6/2+nWJ9Wbrx6tQtxGKZtVZa31tLKfPPk5Wby031UCyJtYpwvxsfDrze8WvqzeTCyWBcibukJFZfGv2YeXzhQTMa80/bLs7su/ru3InnO/d3Mu7Gk/ErkhErx9CiXaNV5lNdBrCRws+9T3fPNdRqlnL7tLww/fbSdLFLBqFmpJxyG5alqdTXO7dfnD0y7FWoyCVNvZITK9dKlcLIkVg1H0v9H0WVKFbkGLkTq4bd3+drVz4enBjVRlDknk63mKXTVlVhVzGdOWMSYuWG6iNllIRYNekEY4WpChEQUZIUOsSaixXbL+/nTjea96vQYkVJolTE2sCM3b3yZ4K9Z4jEqkmLVTWxQndi6Qp1hcSq7OC9baPa4N1yg+UGYnVnLEtaICVWvbZ0/uVoS8cm9FBmgqkFACYXNvNt+93zl1+WKVbkKGwmvUC/IQ/VBfolp1e0JX3Fua8xdlRossMUucMUazic7jAFsQY5/hVr5e0Hv+KK41/EKvjAapBGxMJRiHVm17P5qeuIxTLb+ujh4T2TiMUyGxubnR0fjy/xuZ3drvfD3r9d313l2SjlydZXCMTeJBYSC4mFxFIRSCwkFhJLRSCxkFhILERiIbGQWIjEQmIhsRCJhcRCYiESC4mFxEIkFhILiYU4EP8CP3QcVcisNdYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI1OjMyLTA1OjAwCHeMTAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkVSLnN2Z62kCwgAAAAASUVORK5CYII="},"161":{"admin":"Nigeria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA10lEQVR42u3YMQ4BQQCG0RmlOIRaRytKrdCKS+jcYJUuwQVUIipRSZRqN9BpxyVmNpnNezeY7JedyR9DaJrVMlTuOF7ctpf1a3Sd/eo9xWnynt/7m+/5eRikT9qlab1n6QUQFsJqSRzGfXz4kMLKrPa3iLD8sfDGQlh08nIXlstdWAgLYYGwEBbCAmF1nR2LIuxYICyEhbBAWAgLYZGZHYsi7FggLISFsEBYCAthkZkdiyLsWCAshIWwQFgIC2GRmR2LIuxYICyEhbBAWAgLYZGZHQuERbv+NLs9EzrgD8YAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI2OjAxLTA1OjAwXCcqMQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkdBLnN2Z8csR1EAAAAASUVORK5CYII="},"164":{"admin":"Netherlands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABJUlEQVR42u3aTa7BUACG4VPbQIIJElZgjG0ZYy1txMjEMmzAQhh0JEJKD9p6vsEzuJGmPXnjJ7khTTvt0ZCMa3AEFBaFRWE5CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoX12Cx0k3Hy/esUeX3+mlh32IxzLhTWeZrN9isyruFi9oEJy4RlwjJhmQnLhGXCMhOWCcuEZSYsE5YJy0xYJiwTlpmwTFgmLDNhWWXD2rWOk9OBjGsYJMtke2O/t5hv1vd/f9VY16my1XnG/E7uLX/P7z3jwxuqi8+PtdkWCetXBofr2YXF2igsCsu3MWFRWA6CwqKwKCwHwSaG5feasEhh/avlPwHKX0FY9I5FYVFYDoLCorAoLP/oQu9YrINXxJp2iizafvMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI3OjEzLTA1OjAw6NBQuAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTkxELnN2Z2ULOOIAAAAASUVORK5CYII="},"165":{"admin":"Norway","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABJEAIAAAAUIsioAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADa0lEQVR42u2dP2hUMRzHA+rQDkIHLTg5WE97p54KQtEOXeqkg6ODq6f4B6UognQQnFQoTpbaQYodrJ0qLhWu2KEoCEJBlErtYqlSOZWCiH8q5YZ7Nc27JC95l6efDF8euSQvfz4kv/x5OVGp5Nt3FcLUz/t2DxUHf2wpby0/WZbc5MSL/W+PNT/eWzxXFmL7p5M3hNiRK5XUuhKmcOpI/7VHiz2V60tn5DS/Hb356tZiZSjXsbM75JoJX8W/AlZ9pAzAet8+XHgAHIDlGix6LMCqNxTmBkobAAuwPPVYhjYWQyFg6Rnv9FiAZQlWdcgDLMBKCawaUoAFWM57LNaxAAuwAAsbC8XGYuUdsFYrPRZgMRSiGO+ABViABVihgcUmNGB52Stk5R2wMN4BC7BQwOI8FmAxKwQshkIUsGqLDoAFWAyFgMUmNOoeLG9zqICGQlUZk5TdLm41Vjr5SZCa+D4+Kkaf1dF7IyMPL6/xrONjGl7S35vnx+a/pHMe6+fd6bbp2wY5jM25s3oLPz+SiuXMOh93N+Bcuf8ELN0eCwdYXu5uwDkD63Xr7PGFE9nSmfzc14/F+4fGrjzvamreM3X2qT5YbVcPT/YOTN15eWG2/822d50Lv7JYA43Var3F155oXX+w71KTvm7a2PGhZ840VvIU5Fgt5w/MXBzXu7WhpuuW8r2nJ0zz4CrPjQ3vI1bUP/os5HlTqLr2LM8ULHU6vvXvI4cmZQy5Farlkmfllg2Dxq+ZUQ9UAWABFgpYKGBREagHsNKfx7lNP4kllJUZcQZVtQ6R3Cc+pE6YeB/zdayVAkfXsVRl1/k1/lknHVd1YvesX147H6FaRY36JA8T72+afnTl3XQTWl5518mbKj929eC2TvTzY/reJO3OJjSOTWibTWiz81g4wOJ0Q9hgKc8Z6p9FNA1jGFfvBKmboXDVCdIkZTRVV+/y0S5W7SgMzlPLYVThdfw13tXgr3Ts6sGVmraLj/PyCfLDX56oP7HnQ3u+K3RlvPP5F2BxPxZgcXcDYAEW1xgBFnc3ABZgAVYQGhmMsLEAC+MdZSjEeAcseizA4qpIlP8rBCzAwsYCLGwsFLAAC7CwsQCLHguwAAuwAItNaMACLMBiKAQswEpjVsgCKWDRYwEWYAFW9v5WTr4gmr3CRuofeRNcTTFrboUAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjI4OjM1LTA1OjAwOC45cgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTk9SLnN2Zww/KG4AAAAASUVORK5CYII="},"166":{"admin":"Nepal","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAFIAAABkEAIAAADK/Sw/AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAISUlEQVR42u1db2hXVRgeBlEQRShZM7PaEpuJWMMkNCMhbJpmYrRgw5kfSlGrqQQzxWYLktRtEVhbSGqsRIzyD2pF5JbUKAhGmnNu6VJnOtOazaYu8Pnywukcz/1z7j3n3PfLy4+zc8+9u8/vec9z3vc955fz1ITFa7ZPby7/5VLXt/3d/Yf6j7H11ebkFI+7tfaTYQ9Nv7Jh6RtT6s//8N2ZjnNtvTP41XgLNrXg+u5t3484Op5fkOdgw96SO6lrfXf50ZqdjVtaTxw78OdOflnegk3tmMWlzQ0VDQe/vL51MDt5r8AeXFa48O2Xb1oydvbaO2k7WuDkf5rz69RTffz6nAd7+YC8+rI9Vf/eO7R0dH7lg4+9OUrsk18ws/ejjTVlny77uZa57jDYK/PzBs2u6Kge+NojjXun3bH2yRUlW0Z0LKwUuQ47rmru81tWfX5l31/tL/ILdZLZAPvwwYGXx98PW//+XfNmTQTXReBZ0HkFNlqaH72tZ1Ih+mB2lzl5FnQOgy3abZVDVkwrmLzqgWeXtsucPAs6T8CGbVs/pOnx3WpBxxE6T8CmFk5+1v6Cka9soFynnyHoOELnPNiU61TQySJ0c9dVNX09hp28w2CLXFcLOo7QpQx2dJiDCjpwnVOuDjNbXLzpCLrb5xctq69gQecw2KLlCJ2HYIPN6j46EToIOo7QOcnscIKOI3RGEiHJgx0uQseCzklmy7iuH6E7UXy6qWcNQ+sk2CLwLOgyAbYo6Ap7Rq9e/qE6QseCznmwg6Zcua7GYbCDCjpaQ5dNQecV2KKgk3GdRuiyI+g8BJuGcfQF3bbh31xoa8w02DqRr3DxsmRmdPpfcITOW2ZHj9D5J+gSAru19+bch584nvvM0EXbO/uK3ntpKVrwGVbsb47x2YzQGQT7t4tj5z63g0J7bmLdP1tbLvTtW/DjHrTjM9op/Lg2GSevE6GDoEOhtLuCTis2jjmv/Z68KZMPB+Vx3w1H6jqnnt1cc/emlotLWm5sfQs3Rjs+ox190I5r9fkd9AnVq3aZoEML6mpcjNBpMRs869o1r6Sy5RqvbNOoF2ZMoJy+XNL99LmZ+g+E/riWjqa+78m6OR2vb4zuD6i0VAs6tLgl6LSYjZf+d/5n87/qV79QfCHATsrjoBbXYhz1lwzP1jN813373jHh/H0SdAFSnHihgFxcXMHlwpFiJo7+cBgHY4ouHc9AdYCJJR8dkwo68b3RCJ2dhdIBBNrpRcsaaq/DZWeOVBV9MIC+CDhe9Okd2bygpTT6w2EcjCkq9j+GlR9Y/QV6Yr63J+Vqp6ALADac5KVBvzecysfMChgAOV590BlafxbH+OK96Bxvc8oVdTWOLb3gNukQYBXmTsyvcUGOcTAmYIZHoeNjWrEn5aoWdCWdK9ftLU5L0AUGG/ymCyc6v8Y1W+uMDB+TFqejCDrU1SQv6EIGVcw5bX0LlvuRck1G0EWKoJ3tf7f640PJw4ypxIZ0i36gRi3o6FEG5gRdJLDxujFnJ8NywBwlUmZ/DR3d+WZpbByOXZzLo1vMzVSNJ5MLNz2+uoaORuji2uUacyIE8o1GuaMADB6bToqk+7Wggk7G9bgidMazXlg4na/evH9HHhQ1vgTUIniCPmCwzQDbsCkinKBLtHiB5qawLoel/DCXyXbLmhB0DleqhHOhblXgBBV0aidvEdh0+y5OZWFLrSxEox+hswhsMBV31zk+l63MyiJ0Vm/ZlTkumlnC7MVWZmkNXWxgx7U2FTf1yMp+UeKPNSgs/iVqzbXLesr6yEYI+iThLBy7dcwW7yuDHKtPPk/Nq7pxOpcz5JnYJKB27Ay5t+tsmWJnyL0NqshYjoUHQ+7hXi927FaDjdg4LVY059iZ5QbB1ikrwNYenQoyuuEWAo0duxVg0+rP41tn9b56UtYTqUxaeSL9WlwdR6xRZ8itYDaAFKvKkbiklee0SAHt6IP+uBbjYEyWb9aBDRbSGwAq5K3Fv9I6UfShvKd/1c9z6wRcswl5bGADDNSlqDfqiX9VbwTU2T3KLE8BbIiveB8RMzfGl23v40VazGBDacOpwtniM5gHmM1t/wHkqFATn4HlmxFm46XL4DS3sU8cGS1q5R80rYIcua+Qh3TjdPtuWhbPYG4u9++o25Bggx+ick7G4r7xRt+y4NgjCTSsj6McpxHu+A0s0uItOs4Cy0OCDZghjmiQxLTFvXBfExsJ1HO565BrgQ0dDr2NAGeSAKuFG1w6ZFpcG/7UaRV3IdcCG1LIBoDVjI8u2ajFQUI+sVwLbEgh25hNN/8he2Zi96VPc3mkORtMMrFNV4fBSW7+82Muj6TGoYr9UONZcOwOr7PNnb+gng7cZXlgsMEnWbIyOwfoqFlu5+8SBAAbLbLYuCyCbTo2jiNqbSt3tJPlIbNe0MCJZr2ujg9oIdBwVhOeId0jdVxheQCwZYfTUqvOiYXMZwtnj9PP9hyQpWa5DZDHXJYkq1TBqSnqShXZ8bbIYbty/AaOx5BBnu4JpjFXqqhr0GSJUbSjj3g2atAaNBusnY7dYHUpGEm/CvrVpbgWfw1XXWo/y5OH3Ezd+NVZVtaTclddN44gaNDCI7dYnqRjT2FHCCAEa9VfC9skmOuOPYW9XtRRm14y2fZFSZflmfvFviwrdgY7QyxnsDPE8syBbafcSwZyZnaGIE9NjTO0QSHHUX9RIGdmOwA5BT4KywP8PCNb1x37NcBmq2/FX+aOy8bl2P8HbPxsASCHS5d9VrfoXBVuZFlL0GdL8n9UW537yn46Rp/lOdk8kRuc8OleOizPsfMFsQ1n1SzPyQ7DGPL/APUV7P6voM+yAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyOToxOS0wNTowMFJpP0UAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05QTC5zdmchb7HDAAAAAElFTkSuQmCC"},"169":{"admin":"Oman","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADB0lEQVR42u2aQUgUURjHhzxE6EFhKSiUMLoEHVpICgqEDoEgkoFhmtVFukdIBLWB2UUS9KCHCD2VRFAnpdMGgRsUeUkCy81YWYkyzJAoo2D/e3jwdqdnq9tM89vDn8e337x5M+/H933zzXizM9trdsWCo+lP+0cOvFnryrRlbv3iF9qfFzSw3jXEaxt6AStCYL09V3diT4Vtnxvf+3Nfz0Jfy0DrgsbuxwIWYMU+VHWPX1idv3Jw/vC0bf+RmIm/Wvq4u2f48gvzX/kvPu1KnT8KWIBVQAXHSvVo9ViVGYGEwkr7/ckHLRqbUUr+gAVYf0iFq8mJ7GSbGZlUbgsgjWWXj/xJhYDllBDXrk1ffdmkNKfqSpZs/NSW9rTssrjHKsCKNFhmgltO9J/pn9NYKNh2ngoBq4C+Hz0y2zikpKZYpQik8ffHzzpTd6VCwbTIJzN4/E7TFx0l1ZyAFWmwljp6629ktc15zSU4obacGr45cs+ESSq7fORvzqA5ASvSYGmbzSilsRmH9DxoIqVulgp5O2LZDQvAihxYqqjMyKQIpK6VVPZvJ5OHkp2qq4q1SamxACtmNzkVsZTI1ETQ9n9NP6x8VJMv2HPRS/p3eAFWRJ8KleAEk927Ekyyy8dsmQIWYBWNW4pVSo7+bw/lI39VWnzdAFgFXuaoilIqdOmky0fPhjrWpVlKxIoEWOqkr/d9n/88GhfztJNpPu3m6jbbYtptH9vuPvZX/7X5r9P/LKVfr/u1u6/BZR7T4rkkplKe8uwYtlGzbba6v98M/lnKvxIv+BuMhlEBCwUsFLBQwOJGoICFAhYKWNwIFLDQMDRLAQslYqGAhQIWNwIFLDTA30eEDCz3G+f++WF5VrXZW27OX571uJ8lcGA96a5f3PH67NbW65UTzZ9PT227jYZLtXcBBWvnpYvPK455XmLM89BQKmChgIUCFmD9X7ruHQEslIiFAlaYwQJrwEIBC7AA6x++ugEswCJioYAVwU4SYJUOFiACFhELsIIBFpABFhGLyiyvvwGHAWiwJ1sgvgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzA6MjItMDU6MDDjephWAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9PTU4uc3ZnQpD4UgAAAABJRU5ErkJggg=="},"170":{"admin":"Pakistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwklEQVR42u2dL0wcQRTGsW1DSLBYNI7cyaZBIjAIWlVBBQmpgqRJEW1IBYgKakpdEYW0iDpOUEETDEGCa3J3SVPDiUuqqLiKz7xkspvZ3WF2lvuZL5fLsvuy87vvvfnLxKg7Goy66WhvffCltz679PrR7NLE5ItnE5NoIzVNsGb6m/9m+jQPYAEWClgoYNFIgAVYKGCh4wkWww2AhWOhgIWSCkmFgIVjoTgWimPRPIA11mAp2oXW+ycLramzl2+mzgALsCrBtNH+9nWjfXh7cXF4O99/92kelwWs4io3Wu58PFjuXPZ6vy97ilZeBUwU7yWR2jv6Mdw7upn++/BmWrp4+qG1eApGOFZhFeJKdjbOne3Oq53tOAm3kXUbYOU36snK1YOTFRvh+dqv5+drcRpbWMspG9ZTBqysxOe6lFQ1Vky/1DuRR7pA65rV4cHm6jChEoIay1U1oRtbTK+yiKujYFOw3o8+643td38+3u8mlDRxLKsqxlWYu7FpcCG+dwpoG4mNMNG5CsDKb0LbkHPHb9tzxzHjUYLTO8l6Y8I9uQKfVChVE2ZFFScJ2gR3Pfjz9HqQ/66Ee8yaD8cK5lVS9cvuOhIhomcpnqykbFUIxnRTwApQV9VbXVkPc73KxqwCn15hQmDJIfKjqjfd6OkWKf0YNDupJK5eYUL11jiD5Xbms7TeqRvNRfokZYYbkije9RSfOqZesFQ/qepqzBLIcXYs4eITldJNvfOVifb+AKvoEEO9xTurGxoM1tbu989buz5RxRluAKx7AlbWnGAKs4SA1eDiXQnOJyoV+Cw+xrEC11gxF/cBVuMdy79XaEe3SYg4VrBxrHSGHgCrMSPv+dPPWb7FXm3ACjBXyAAEYJWciSuaEHU9aZHiPXBCdNcapNnAtXU1cKxyQw9uzKnN5WnUrbYFgIBlf9nuLsKi7pXCiii7ErW2jXSAVXQ1qY9qT2LMhK43Jpj0DmtO0NRYVeYQfTzM7gQM62S6pzzSbr5IYoYAxyq6E7oKZLqn3bXsg5pd/27TnLstTPdnaXLSZzeoIatUXT6oyWn0FGFhVd9rSDY/QevKhM53IBX61C5h3SusKrbkjgzBsfyTY6jaK2z1lugpNIBVrudYbkC1uiotNmD9O2BVP4PUZwNZdZj0rMackgVYoSCTi6jiUUlebv5Rf6v76J6NPJyc4v2uD+jW4II2bqgq0mCBPut7XaPr78kZ9zgWClgoYAEWYAEWClgoYPFv5QALsFBSIQpYgAVYgIUCFgpYFO+AhWOhofQ/1BYbV2meOR0AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMwOjMzLTA1OjAwiaeTfAAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUEFLLnN2Z2wkW34AAAAASUVORK5CYII="},"177":{"admin":"Poland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAsUlEQVR42u3WMRGAMBBEUUJJRZEaH8xECxKQgABEIAcV0YCFw8RV8J6EnV9siYiIGCDVaAKEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8QPl2c/pWgxBclh9m+/1MATZYfVaWzMEPhbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshMWnvSpIDkC2ZYo+AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMzowNy0wNTowMBhQC48AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BPTC5zdmfkDuYeAAAAAElFTkSuQmCC"},"183":{"admin":"Qatar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAnEAIAAAAm3KaCAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACDklEQVR42u2cTyjDYRjHd3JwtFpxcOAgDo4o+X/wpyYucxknpaS25cBKHBx2YLRWDkjiJNQuaJRCO6xExIEiBxSFyEoO5vBe3vo1NXvf2W8+l+99PZ993+d5vr/3tcSv4tH4SeZrrOjZ99p0EzvKO78Pb019Lq4EJtud/WVeW4ml5cOVa7uo7pLV7ckvqKlF/0otZgHLqG8Tj91Pe5c9B97Dd4HaTMSx66mTUaPAgKXAyTZyfH2zreNVlXZHmNICVhL60nFX+uA8i21HInMCI/lAFD8JrwKspJ1pv3h+dM3qH2qO9i4MrhfaG5bpqwBLS3clfCvY2GkdGJF9CzVqOv9+pu+xZNRkP8PDcKxfYvRzp4UCVhI91o4rEFr6StRj0W8BloKpMNQ2dhy8FZAZ16SUFrAUb+TFHgu8ACulTuu6Prp5WiGQ0j0bAmvW9ljiEFydHi73uxMdgpQfsJJWwAKsNHmYWJOKaZEQGrC0N++UFrCUrRuIZQBL44KUuQ+wtITQBDuApfiIFCE0C1LAUuZYhNCApazHkrsiNliZNjSY/tNk41QIXjgWeyzAylSkRAjN5h2wlGWFwpnICgGLrxsAy/whNKUFLC03ofEtwEophDY+DUJRAYub0IBlnkhHAEdpAUtLCA1egIVjARYhNJqVjmV8tYGb0IBFCA1Y3IRG/xVY8lO2+rJCDtPU9RuNp9BGQiNiGgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MzU6MjAtMDU6MDCSzEI7AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9RQVQuc3ZnKONQmQAAAB10RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgUWF0YXI0VmY0AAAAAElFTkSuQmCC"},"184":{"admin":"Romania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPUlEQVR42u3aIU4DURCA4dlNg6HhFAiSOg7AIQiiJJyg9dgaFAJUT0AajkAFZ2hANAFEBQ5FQoKoeCwGW7ebdMr3yVWbff8+MZkqYjCYTCK5u9F49vhxMT35fn0qpfTrVa73r557o+bhczkf7q3fz64O90/jJc7jIO+J1AHCQlgIK7W3uI2ZgxQWwgJh7Y7kgwZhbaujuI8vYYGwEBbCAmEhLIRFe8yx6IQ5FggLYSEsEBbCQli0zByLTphjgbAQFsICYSEshAXCQlgIC4SFsBAWCAth/UP2seiEfSwQFsJCWCAshIWwaJk5Fp0wxwJhISyEBcJCWAgLhIWwEBYIC2EhLBAWwiKnnk+wFf/3cXPdjGMZi7j5e7RpKyvJGqAbK4OEO6U7dWOVUvr1KuOb/yyqy2oaEcNYb4wpVV6/I3I8w7RHvwYAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjM1OjM1LTA1OjAwDF5tAgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUk9VLnN2Zx5hAsQAAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"188":{"admin":"Saudi Arabia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAKKklEQVR42u2dX+ifUxzHd0Nu5O8NF0grF1ppF9gKa6lpUmPTlj8pFFPChVxpwwVCzNRMSwprI0sSYhfLSii1ws8FaRe7IK2Y0khR39dz8fp29n1+z/N9nnOezzNuTqfznOdzznM+7/P5fM7nfM55liy5YOuzq9bPTC/Zet41uxepU57CLJrztdW9h81plh/PLi12+YqMH9n2aQ4G5xvc8qCMNjKNgNV8+HLPudzD2oR+Sdb229the+7WRz/Pyg9lF2VUUk53V21dxnlkAjlHzTjWT27Y5QDQrLcKSaY4yqW7WswH7vjmRHPKoU3Cfhkfx/7IMWnzTfj5KHeaAflWW/HXevFtu2H7OTALcwMumrOgpCwfllMZZ0MOF0Z5Z8QY/VgRLNrRf3YOA3yW1TIW9dfXZG5Sf9YoBVJhcWZeF3O4vB8r5oo7qEOyu2puPiPr0+5qse1cH4sjZhHjPTJQ6qXILMa0ZWRzYMVhcL9bPTk2jkaztVnP+LbWgCmcs2XZ1asbwa4e4vN50ss7EfIp+kWAVcbl2HYlmILA+SYqLEnPPvT0fTfsIfVbU+UJhVnlLdK28ng+EA8rJkJ4aGYM4v33fLCw7c9lb+847c6LYeeq5a+d8sC+i7Y8v7BxzR2H3r3iqVNvWrv3tkc/pD55nlawIxW1qv6kFUpc/8rLd63Y/Psjn+w/c+c3r3z01WPvn07rlLjcfaMntLLmjNfff3g5NEmXfvfiS7c+SB767hsl9Apqd9/+3oZnLuN7afH8t57btf6461Ozk6TMbe0Vwv5c8okh9qAf2HD4ukPXU/LRvT+s+/Lm3Uu/Xrl/L8P99eM/f/vjnu2XfvHxvrvMPBhDzSffOPjPmwdhNmyDMuWkgAZq9IH8kYPHHvhlJ/njD/191V+b6A/lgN4QNBCh886rC799ek0l8yZ941ugQA+hT/nRl/9YOLbCUOPrpuiUtws72Vi55dMsCpIxAOKWc99Z9cQ2WM6gw07YZmYc3v7rpp8e+n7z0c+O7LAcggEGFu9aKgAUs5D6SCPYSQkAoi1S3uWppQ5tARrgRc1Kwk36Rv0UslDjWyiHgukgHUMsPnqIbsjsLICpzFfySKbPVx9ZsbCSFHYya1PAkQJK4AWTAAeMpAS5RX1kQ8WqCcupg/RCItITegV0eAs2+6khC33oUL9Sx7IReQodUkp411MCYE1NnvmAlXu3I5DwFLAYRNjJsMI2wESeFKD4KUNv9Wd1CX2D0jRhJ+/CVKjxLuy0zIMyUIAOoEEmUYdy5BySacrSmnw1Ty2NkNOUMCWoQw+ZWpWlZWtyZBIrX4DYDGB5jpIaIrZ4YJ6BZfuDOQ2zYRIlVn/kYR4SBfpIR7cFsGwzXXjtC2dtfNv1DThAQLmtQFuN1KcO8pI8rfjrGB/opPK1hTumL+O90SZ0ML85w8rwMbgw1coCZpPnqVWPDWQkEFLEqgoGIwkACu8CTRQlzAMo0IeOV2qz1JzlnJUpdKhJ//leRoCJZPUKHD1VkFi21cI5eAeIjWwwezybycNsDzesgg2w3zaT1YRZ4regb4VITa8Z7ZKAso1ruzlSS4u3gIWVnWHk9WMKLE8hegVlaFZvtfXn5XZDTG1CR9vIlAvU0gWWwDwGGkCgPpjBVky8BSB4C7bx1DaTrR/beQALWEDZKXKLp0gsUttqVn+UW7VZwdEiSpm+URN40QrjQ/+pH3TrqdCpwLk2YmEGgwg4GHTyKUQ8y0nNbEqgae+UrR8YD6Cxn6hvIz1VXkhHOwXIU27QG+ieMHagkFrOofiqvk1aZDpVHru+Nrn7XUvGPOAAsxm+1MdtYAEaht55m/+wB3XpDRnkAeVWUjA7BavlRLXUn9Chb9SHmqeB15t8F3SscGmR+rYpqWMnC21NqdShIv17iyDtEibW8i3YZgMZGcNQ2gvlhT3lMBUw2V3pZbllibdxDNBU8ll2OrWM8RaNlbJtNcszywmvgqljq9G2XWqrDXzkbhFVOGxwX2p4SiV5QCtrY/IUVtl+MoPtqLTcMixcx+tQb8ikStaAM4xsCdk5ArD4FqAGsEh5CzqWWP46U7NLYrADsfVtBQof01Ov6SwPbK+43MoRqcNct3xCsWKke3cPYFkquBzmecvFIAYKtDUFrKRd14cCNWmXPlNCTYCFzIYCcheA8nQRGyt0BGmO07216pU5aiOa4fN60DIDxjDoDHflCJhQgzG2xoCmoQN9WAs1Ky9bTvaBWU3bEvKU8MY51Ozm8KaN3bD00+4P72B6m6s3YBWKbhgqwlpORWAEw7z6Y6MDlpBacZi1XlEaiDDJO4YwEspWPVZ23rS2oQ1ASSt3pbZovE7kW2gRmHoLyE9dx3umUKZ8ykFa8rhbcwgOdipwRvCad/vtILVZDYw8v70baCDafUqJHRBQqEx79QRp5O0UWgR8qZcLy8nlVp1Wyn7qdV86bvSB1q36GQe+pZKacURDp5j33GHHkxJY6EF31JSX5dWGrtQoMLKqsmLyyqtSW7VLBys+S46pWCgpxDSSwvLY4EsB7RRqADf13k3BsV9F1pdXLLt86rIt0CRNNzTqY9gdC6DYr9aB0V3cj01arw+h7ndPMIewCH1Yvv5QgwMDux/eagvu7pH7sybAfNH0w55OCOHHina5Re4LUeYLvmt+7CLm2cPRH48c4wWQ+U5pD3sLxgkcpEU2lUNf6xP5XHL3Y6jdt+OyXwoyruvIhlKX80mUtkdtI/MiSwMn08VlMRVxnGkzp4N0LDbHydHKyXHhec+HKcYon6LJsC7ujGhTKPSvTf4LMqwvf1vJCdZE0IRe1/TrSo08DeKcB8xuvPd1M1NJv07zC4a6T4Pmvu/m98w0H/n4fruxOw/TK4dOkM8tXWadRU72JR1/scjeX/x7kevhPrAW7zB8jvB0NLoP4PtstFNiEJxPU8fXO8Y8TR2KSD5NfZ6bsBnHaDi2zGF9gX6elWVLp197pVf5kQYD+h4YUhiZ5utrNkmJqZqVT1Of3nZMWHp8w/d4tdhcL+NMboKHUf4apPZoK7FTBN+R1peQz53SN2LFfI7ZUs2Rqz4b3YOKLD/5Q6z7uvjD6g3qerunbWhKk3dr6zjQL72ijfIDNx5ee2jdIndfxXfnZv8D1kC3Aba9j7R1/SYX3bYNUUxjU/OZ87llWOg/I+Sg0yXcr7vl0bxOtEj2wVRhHE90X9EBYwmkGeqXMD1fYxR/uKOpjDK/ThnNbTMlh3ioqKzyC/WSW1s5/ljReksnZvR6hG3poXYVx/Xzpp7vII0TX9AXWMf1F9McocyjuR8rtx+rzNpz2N9tjnFZEC7+MH5oW+R/rg5bs/U/oePPktzsHJdEidDzwQ5QnEyHKfqVZBEi1rvDN7R9kE8+xbef4tzN17O7IYe/O/7RqJLHWfNd6VH+otueY97L+5miHbgY9nfokXcp/j85E1SCjv1bgloG/aqzmN9S8udvxXc8/wVl7ibO/UJF9gAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDI6MDYtMDU6MDB43n6GAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TQVUuc3ZnghxoAAAAAABJRU5ErkJggg=="},"189":{"admin":"Sudan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACWElEQVR42u2aPWgTYRiAP3+hEGj8IZQ6dOgQNaT2SIaAQ6EUBKmaajSQtDFSpB0EoaOEpsHJblWHgktFhUjcpXQrIg5msMVBDLEUQYqKHRS6lHM4h5PrhaZ97y45nuUZQu4uvN9DnvtTl39fehYdXqp1v48dXlkNBuNxCPdPpU5MTZ+JaD3Zmcirp6lwn7bAUKCYWAZDtyavnE0W6on+c4vVQ8ffxb4xICgglsGDV+/cD9+93nlxOfocvaCYWGaSSOiIWNZEMjIoJpaZRiK5ioTCYpFI6KBYJBI6KBaJhI6LRSKhg2JxoxU6KJY1keiFWI5wh0QWAzkt0fSP3ttW0PXZuiSWQCKLgZyW+LeVMYJdDML2KHbbWj7/74j7WBjb/dgdsdkl38WeG3/e9KwafsdVsWyvIs2iQF/QM7HMiSxfGH4wcmP90+0jhTz0Bz0Wy3iTwkjk/IG3yfKCvq7X9M+w3emxWGae6i2FBm5OXKs8Kf76+niztqFYHsQS5vnvj0rZ9JuT9S/V1ywSYjnyH2Yk8uePP/rmRxYMsYQ5sVXZnrlHIhGLRCJW+4hlTSRLiFhcRSJW+9A49+LUHrHEnjmSQsTi5B2xSB70/w3S6dL4wAeSh1gkD7aqWNxhRyySB1tWLJXO94yeTg2uabXMy8yLTBn6g96JpYbGuh4qpY6po9CPdP19UaX6osF5Ro9YYslTqnuuY5ahI5Zg8lAKscSUInmIRfJgi4rFVR4UFovkQTGxSB4UFovkQSmxuLEJpcUieVBYLJIHpcQieVBaLJIHpcmzPCjPv0cRG1YFsTmnAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0Mjo0My0wNTowMK6sX9sAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NETi5zdmfF4V8gAAAAAElFTkSuQmCC"},"190":{"admin":"South Sudan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAE0klEQVR42u2da0hUQRTHx0TWTBGRBDPNfBRKZU/sQw+z0ErMoISWsqeRKWFUSGVRoT3dqGwjqehFKWokFUUPCoL8UPYgyqKyBz5IiBL6VBAYdL6MTDPM3Ttzd+/u2Q9/5O7s3Ou9P8//nDOzSMJSM15VOIKboyalbyBBJJiEoqIq0IjHOXeupUWcyJp8Ns/RkpLizA5qdDyKnoi3BtWURjpyC1pctIYPnvraNSXkUOyyGTvxBqEqAwt0qHve8KYqNEpUxWCBolGiagGLxguNElUxWAOiFwUZGiWqArDQKFEtAktcUSJkCJYysCCGYUWJqhis/6CGRolg6VasKBEsl9E2hNHxaJQIFholqv4GqRa8/s3PVpSJY0dUJeTGr4kvi9/GUxgjMxLVSiW6cTFaS4LO3lRedvP+tfC2p2+zO6s7qzr3oNpLibdgkunvp+UvXXDnsHtX8+g3zq9bvw/9dasfXzZ5EfOgwJj4vQvXX76QlLho8cXVqjph9JEZ30pfPth3aWVt6e1TPbU1JccTvhcc+OL67F31nSux/ncRjyfmkQKd0FU086TziLvuUuFFgExt5KOXjxaVT27c8eF6UlxvZtb7F1H3Yls7giK3xnSA8o7QyhspHi8eIz6vzBGZMxqdx8z9kRnPu05ixsJYsD61P3mVkLw9u2ZY8TEdrQpak8/N6jnfuLl+bPDa/se7Y9yjPsnfaPEDMDPGzNl1zKP2LPJYE1V5FYDVM/3589iboPPbNsTs7WYBArt0rqgoqjw/zbmm9ygxELE4Rjn19LSyY/nHC1LuL7hNQ4bqLSWebZgRg9Xf/+VKSF/r1bsdGQ8BnVUhlZkVY8Ao29+1jkxZDu/Cp2SuAWBi4xYLHGuUvq/+98dA5K1HrCxYf7Z8dA0uhiM/Yl+/iA6H4/AzRCxxZOKpzJ8BzyhRvQYWHRt4cQJGpv1esvxMG0SjhvqGvjktABOtABONFMQtmaqQjUZGSwqeUVqTwSBYUik21Ho0RpCq0wCxYNHv3k2/sSPzGcyju5vPg9WORml7sMRZFA8s2uZ4eKkCy8ynECyfS955FgNWCNkSoAaQsUjRR2gr1BG3ZKwQH7wPgSVOrunkncbIaPKuCiw2ece8yvZgse0G6Gnx2g1m9nWxZQevL48P21Kw2FqPbj/K95PEDVJ6fpkGqTzWcBzrPp8GS6akZx8qb0lHXAqYX0TimR0i5QsLQcSzJiSv804vQqvNn3iL0AiTt8AS33Nla4X0thkzMYlnebD1D7bN+Mc2Ff9W4q39ouLjuNHPzzf6WdnApD9Lb03uWvyzqDcS1V7q7W/p0Ja3tjDD/ScsYtO3ZYlxfUd7cwYlPzyZOTcV1Y5KrPkGDq9tATokuqSlfL8j9OCu8bNDNh7oHtfEqvhdVF9TogoU+X0HAFP4uqK66o7Qp5WVeQUyFwpgySuLo9EZWJTFZ5G5Bvnxns1j/fXwlOg2uAFtVcrsjMKhQ3XPH8hK9C390pkTmB3EJ0QkgMDS0cykzc4ut0Mc/BFfDyOWgq+bGjQ7ewGHYGmxQjF2dGUXaBoIwBktGkzlWHY0O4TbPFjKqsIBiy1+anYIlqVWSC8wB7LZoSoAa8C/C0CzQ/UoUhI0OzRHjRGLXbNDRbBMgYVmh2Dp0L9y4qRFG3T6ggAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0xMS0yOVQxNzowNzo1Ny0wNTowMLjzwZoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMTEtMjlUMTc6MDc6NTctMDU6MDDJrnkmAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TRFMuc3ZnXZEMEwAAAB10RVh0c3ZnOnRpdGxlAEZsYWcgb2YgU291dGggU3VkYW5wies0AAAAAElFTkSuQmCC"},"199":{"admin":"Somaliland","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFJUlEQVR42u2aW4hNURjHjwcelFseKCSK8kIZl0muIUIeJkkTjQhFokZTck1NITIT5ZZhyKVJlJmUS3gYmkwnmSkJQ3hgzJTGNQ8Me36n5l/bGc1x2M7xn1X/Vt9ee6119vfb37fW2hOL9V27fsiwLNQeqz8PWp7QsD1Z+9RaWsOa6T+g+8nC3iMuqYbtyYDrU1dUNWoK9v77NnUdG1fL4Pi2rROPDj20/d3ke/RDG7WEUfvJiB3jnq2YZvoPmFtzqGLZ0xmjD4xfPHDByGNrVg/AUjr7VvHxLdQLB1+cUjyBOo4EkZ0512Yd3DW6366V8xbljzvxad1m6rTk6uGi233OlNLDtSsPn1XviI990Vhfw9UVtWdXbjwAatR1PqgijiW3bu/5vJE6FhDThrrBilhBBCyAAPfj+Mru9beuvweFC2/v77myRMGiveIFjtyF5eSRuxsu7Acs7LR/++TT3paWxsb3U5vvMHr+1/Id6/O0h+pRDadqXypezA1lPrQsOXfzcVkRaGJnhgYrgvWTvvc4A0c+WPrq2+NqoDlXHK+vvIodC9GFOo5EgYOWwAQK1IGDuxgRIFobWp+3PtpYfunb7mZQAEHwojf6Z85cBUdwZ4b0Rv/Mf9KXkqqFtyNOlL8zeuaCpWkOR+IY3Alq4UgDIjhbUyd3ETk05uF+8KLOKICoENNelbsYhQQHgvTDiMxQ58boIz4U586cn8ErsMwFi8iBkwDryaqm/c+7kaSo43hwoY2mQuDA5YoaoICjrtg0zhHPAAVEdM2kqCk6tCTWUidiMUP65JVIrAidCqPaD/KWhxOiLsBJK1ylpUIAKKBAjMECcBpLuEoi414UlLlLZ4IFaBIot63DiEbMk2SaWP+1/S6dgyNWFBldYg+RAxfiJE1kKEAQw4hn2HEhbuYuooiCCxDaj+7+sJPCsGjUARQOMhhLNxa6CtS9KnHOYEUWsUAKJZHhKl0Iv3nd/LFpH3WNBzgelwMlyVHjDdAQk+gBx7MJACNG1HUVPWicI/3R57bTl7eXTAcsLLqq09RpsCIDCywUDnU8oOB4NGFvi0BECBITbtbVD1FN120a81DWXtoz/QCKHlgwFnPWYwWtMxa98aoYrIjP3DVVaTTSbTx1ENRTKz3wBA4crGsmUAMv3SdqjGQmWHThz+jYNUVyl57AEcnon/ofAevvwPpPTCKlR0OiAQVNQzhGDwL0uAGwiAdEIEWKHsBIoUSJbaAWBlRnRT+MAqC0IZJpzGNjEU7KjliRKWsRVLf9OEbPtMLRRZOdLqU1bQGELvOBADsj6p5Uo2Z4v4nq3lDb0INuBagbrMgiFo7R/RRRAURINPotTz+wYNF4lviELJ+KOXQASo1V+ilaj1gTxwRt89QoCEzhj9AKHzPhVfnFx+wMAEt/anZosv8mSKJhmFLTpP10PJ/w/1ZkhxcSH1Ct1rRqjJNfqzW9arCsBstqsLJM+SiE+mkYrLRp2fygnO0VFD8Ng5U2XVTaXvw0DFanla+Bmvju3whK/5ygDJ8TFCzaxinSYP1Cq+JBGVPRXoCpS1l7wUKhDXf56RmsTuClSGkxUgYrRa3pGRSSoCKFhat+SgYrxbilMClk3iEarBR11fKgkPKIT5RpLUHhqp+Swer0cShnV+EdX8dXrQbLarCsBstqDcBqelVQUVBhtaZXY88exH78Wa3pVT8Cq8GyGiyrwbJaDZbVYFkNltVqsKwGy2qwrFaDZTVYVoNltRosq8Gy/of6HX62pI9K9L2KAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo0NzoxNy0wNTowMPQqvugAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NPTC5zdmfV5vyDAAAAAElFTkSuQmCC"},"200":{"admin":"Somalia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC/ElEQVR42u2cMUscQRTH9zMEUqZJLeQTaBDEzsoiINoYxMLWQusgGpLGQkSCgkmaQLqksLGRxMIiTSCBgEdEvOh5nEiUqAhn8W8GJnvs3u56s/t+zb/YO2fGt7997817sxcNDr18tV8LQQfeLzb2p8JZD5pFI0yAAhYKWChgYQgUsFDAQgELRQGrxGqnVgdYIA5YKB4LBSwMgQIWClgoYGEIDvAUA1ZaU2J6FI+Vg/ca+bDc//uhFJsAVm66MPfppPFAijUAKzf9MvHr8nJUijUAK4cgOHm9MXdYP1xtjd00pLqCfQArE1gKf+3h9ut2TaorbF8AK5NuTX9f+vvEBUtXsEzJwArHEzybXR09+KHw54KlK/oUgPBYqeGe3/q49ufaRYqAmNwdAFasmfwgWN6AeP8PAGD95wbEBUE/IKpkit8yAZZuc3L1/6pzEHRV30w7oz87YJWg8aLsZ31z57T1vDv9Nn7Q9+9RErD0ze5mefvi6/nZUFWr+VH1fNXM3ruLo/nkcPRKtUKtFrBKlifJK4SGlFZV7fzMRPKuTOjnm/rx1W6vYFKyr5WQvFew66dG8n0ipcKEZue9wsruExWAVp5uf24eNesXj29XioBJI2sWm6e4Iss14iJCpEbzyxCAVbPmw9yDMdmzKGshr/RgFXGr3PJEXh5Lo/UWrBCwjmjg5FuS0Ghl91jZ128aLKXVReRYvHYRWc6uFLaS7A3do8lJ9oMhBETA6hle6tl1BkV1L6XkySthGpldodEgGNdPlNcRHG7jxa2E6dM4b6eRLQfEqEo7kbQ7QR8Lv/HS+f+Kq4S5ARGwDAVB1cT9xota18kfErfh7Z841SyEQkOqUOU3XrqDIK5ZpFls/tpFZM1XuQm4u3fL98ZrZDfxBywT59nThrwsL5DZfFGMyvtUlVor4fhF3tJBAQsFLBSwMARaMFgcT0PxWPxGMmChgIWigIWGW+wFLHI4PBYKWDzBgIUhUMBCAQsFLAyBAhZaUrDYtVFLw2OhgeodNePDm+0EpMMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ3OjI2LTA1OjAw3NKyvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU09NLnN2Z+iG1TMAAAAASUVORK5CYII="},"202":{"admin":"Republic of Serbia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAI0UlEQVR42u2dXWwUVRTHNybaB5/QgJFowoMxPlU+lKYV1CoBIYggoIKi1CCaiFCQIMXGBNTIh6DF+KAiNQGUBBBJVVbamCBQqApoiomAGIwlRsqDCUj8ijWZ3xj/zeWOs91dOp09L/9M7ty9s53723POPffMNNO6Y/iQEUNMTQurGbsFpgaWqYFlamDZjTDNSfd9ecuz1ctKDqzmc2MqK9bZ9JvFKph+1n/igeG37dtYu3vIM7vKZr8+rKKUbYaBVWBb9eG2Bb8MHZm96b4xNz9llsPAKoBuHVe1alT93GtmXD3pILrrXNX7VQOS//s2sBKhbYOn7n3gzwN1iwctubRt+fIbVmQPn1p7/2t3Ptrxwqz3xjfULr3i1rGA1Tz9lT3z5tAH3X/k8aYnBzQ/PG7x3e/Y9Jc0WPuaJ75xz+/ZJ+bdNfeRr37b8vKWP/bP3P726vNAs3N9bXbCFBSkjrS2VW6oaDrWUD79JT3LCHyKcUCt9fsZg2rOGQoXFazeciLAxMRnT2z95tVrgQn1xU+E7YC1v6Pxqmmj3BUizhHs6Mn44VUCe2ZYpNBigZTaJDAChdaVm0c/PdgHFitBBQuMfGBxlab26ZeUN2PVaMHJGhypAiu0GQFAam+YeKDh2P1s9sSqssEj6HO4blP50KmuxSIlwfiowgdwRGwulKYpsVgkDrqlPbFYAUA68aEdCmwPtsrto+6ScRRQHd8sVmqDd6YW+wEcpBLCHFUADRiFoXegtHCWnqz+GK1blivog0vd++Cov8d2hqMFYNFicKQQLBYNQEDk9HHH6vFrQmgajs+pHBo6Pl0bumdDvII+HHMWpR3lKqGjNDjSnW7ASgFBmFAQaEBBVS0WLdgk3J+e1TEVOAOihPJYISIBEJ9vaegsb0QJz7XF164teqwIGgqllyAN3KJi4YKiqgjG6m+7iqW8pcMWjQKBpcHNKVL0JAvPChGkcIWKF31wuMWoMuBsae5UZnp2K/O5Wb7Puu16LXABHcVIWxQXn+PLByzf3x59T+Lcq57lzHr2fS4SWBMqR2yrOYxOrq/u/1g/t0VVz/rU11Pb3ZHXbZ+yZ3YjG8yuklA4WLFo2b0LUEJvbclVGSH6inyrOH+1qc5v5vIfbq9e9HzZ2ZE1C1/U4+gWV6M/5VN6Xtk1eviSFqbQV8VAbol0AFiwkUyWnHw9fVB+ryEowZ4j/UkuMA52gs3pg8fmr1nYX3/ljFPVPumh+pnx/5aeteSvvu9T7NG0D/OIZgr1h0VfOLqPCxbTfHZQ57bOD868eejMofIQF4moSGZ+V33812N/0fPbXTvrsnPpCUw/TTvacvQLzrLNrE4TvE58su7G9Svpw7GCdcewyfPqa+NPVWFx6buaKSxAIJLrZxUsphOYupq6dnTtwCaFu4HBCo44CciON7YtOnA9PRVBwKIdBUS3cBmrBlj0VBsJWAZKr4GVv7EFrEPzn9uzdKMCEVZNSZCraU/WffQEDrA4VfXp2d1ltP9bHvNf1ko3pwHo9MDTE37+WgFlOwiwevaDSas9i3M3EgGWWqxTA5uva/lIQVE7pHgBh2tvcIjags3zPb3DWfqr4o5zdYWmvQyW+wvYcFnd+RW7NSpSu6UOURXgiLToqbZHXdsFFsbBuk+vpZ/ligoW3zMaMu0Tp7+BVXQFLLUZTDNKC45S4aBFgdD+tGAFdcVHcQ620HctVpGuxYq/SirG6s/AytliUb/w44yTJ0/OxAJxrJPNsZbK0DOOhg4xiJxAzUVKr45LzSfGymdBY2AVxRUqUqoaRb3Vr+bdWS1koVDQVCz0LKp20R2f66I+i2Vg5QBW7/7xClb0ZOuUaxEfDpFjdX8kTvVpHFrUIqqV0usqWBaM93mL5bNVCgGpTsDCSuHmNHdFukGfKAQ71p6uq3W1eGCVgg1LNFg+h8WUgwi4ABaqYNFCH3WIPqT0ilyFLZ3CurxSCOcTtCokZiLG8lkRnXK1WNgkTa7iCsEIvBSs6PFdsHLdJzVNnMVii0Ytihv9EJgz8Xqs2XZsT0f7pvbNazmrx64rdI9B1pfHcrddewusZDrWxIGlCQLXLaKKmiZC42h0DKfX1QRprvUapgkCC1eIxXJhUqUP7o96BCDAGgEQLlVjLxQ75IuuUMDCsboWK9pu9d3dvdSCRTCOa1NrpAG1m27QdV+3GCtwixraawjv2wLSq9BfwXJhirZYlsdKEFhYINeKMPEAgdXRagjfqlATDRq8g6YvbOcq5L1sEzolYAGHzz2BlGbV4+Sx6ANeWneqbletY+hqg7qJ+GAZfHmBVdig1S2bYfrdrBKTjX1Sh0h/8undMu8BWFgd2nXDR/Fyr6WVqHESpPncB58DzScfluvPIH4Jcpz+tGdyXe/E6Znrp+ijYLk1DkDAlOsKDrtFhRYVVAqWvvfB/Sz2TIub3RLn+DXvxbgzfVczvF4xCUq6QavdtXwF4NwsF+181rVJ4RsfAvhATaMoHZkxuSLJVVxhcu5P39KML9/jltoVW5lOnB2VUqBA7VT4dobg/TP6oketcHdLZbA94RtHA7cYtshrQqjTwiLyKb6JOlbTXDWTnK9C5gmLpc8M8iyNvj1G3wkDFq6bU8ViaVmzvg+C2nnFl4iNTL0h0ufBQgGLx+GZcn0m5wLvWQjsUHSdAikMfdqY+nd95gcNX3sUnAV0QyQlYOH+9H9MoLQoZPqKNrdmSzdncJ0Klo7j/j8Lc4IpBEvtFtMcRkLyqCpYAISG5NFbNOGzzs44+nIR96lG01SBxdQS96hF0dd7EIG5tQ8+JTzXcRRQ3B9O07BILVi6TsSKMPH6QiKNrlTdMhiNtHQcdamsRi/+WtjA6mW8sF4aFbFq0ySqrxpCV53uOIZUiYKlzhErhQ3T6Erx0jycqqYzGEHRNBRKFCy3LhREUKwOqz9cntZpaU9aLDw3sP4HMhABNaABMhS8cKaWlzKwTA0sU1MDy9TAMjWwTE0NLFMDy9TAMjU1sEwNLFMDy9RU9B8y2qjgYgPQ0AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDk6NDItMDU6MDDwO6+YAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TUkIuc3Zn1Z5TpwAAAABJRU5ErkJggg=="},"205":{"admin":"Slovakia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHMUlEQVR42u2cYUheVRjHT0Fr1KQPNRsYi8bCxoZE+kWMxia1VCqLpMApWdiKFlODSdRWe5XFHDGTVoSYaw0bVnMLN4xwlvtgGSWjEpYUs6VtEaatNZuJwf2fD0fuztl57zn3vPe+Pl/+vNx77rn3nvO7//Oc5xxlc9Nzf8xNkJLaVUZNQEpgkRJYpAQWNQQpgUVKYJESWKSkBBYpgUVKYJGSElikBBYpgUVKSmCRElikBBYpKYFFSmBJdJrt3NI09k/21m0vDaVWL57ZfldiaCZx6o2RfYRFjMFCF07+l9tVcHSiZunu5cMTo1lzt2dwFY9Ifp/Pf7Sh/AEOx/jmito9FzY+XbD5mj+rV/Sv3nWFOiVHZjYdqjhyE2ERf7Cy1xTktc3rbG2F282rs6fntc+u4mAFqnMm94OqzmLCIs5g9X/53GB7QLA8p4E/iXVeynz7m9YNwZCCogbCwgJYVZ+8fsuJ1vKmpo9PFJYtS/T1visegeIIVCwjO1K9uCnRNya7du9H3V2n1gOsgO7igfVXV8nzjzTOZo6v/W2vGLFdZsjT1n+79mS3nB14bHjZuXbxycV3EVsGivfF24klddrTf9zfnuq7J6vqu+jXIHs2/GbXtTy4an+HS61d81bdwH4dsOBnAAi/xfJTOwqWrO8XwUKkJYKFq6bGC78uWcvjOQ2wOj7vW/fTpPuWSVZvyCtuaWtRH5eVCVabvloAK6OrNP/95MFSxkOTF7I6V14LIIAOYjJchUjIH2jP3Hh89xd38LNeyfNlg78M/YgaEOar/Qye173zq/fOtNtth2S7R12b/2yyvRB2vzP3t3z50oG6k43oeLV/iGCZj/ocLOXwiqQDwFp6sKy843pbn5PdenBVMC9x84RM9j0Fe2idh+BgeX6jdiwxiuK+5Q2g3Jk8NP0TAu5q+C3kpdRg4Y4ogxjLrgOZdKp7NzJXZsuK9cvsqvzwle8fAlhqx0KE9PeSjSVPHkZEJUZaiJlEdFAS3iOWQcYLNagdSwZWqrpWB99gz6b/YaiHXVk9LDwzlKk+WDweElXETgmWtB5tx1IPhe7VvBdcfirM/UtysLwQW9bNQATdjEiLO5YHkzjXEyMwZLZwlpf0FEMq5ozQywzBcCzvrD5Ysq7Cd2zSSm6G2vB6n7n0KijPY8nA8jrYn1WfNzcUVF1GjM/EmSMfKD2XElUHrPC+dT+m5rM/c8SdOpZ6bqJ+DR3H8oNlLeMvpDmCgWXr+3YDaLLpCZ3YWsuxzIN0y44lgIUsFKIxLLbgOJIC+C26EcpgAMVZJDx5ZgvzRO+3bDbqByvZFtCf1qgHO5Ppkcm1kXAso1mhEiweJ4krib5AHmelwbsvVBfnibL7qh0r2eDXZRm76Vbz+zJb1qqvOnksHU1iVqitsgRpGL7lfj7u5klQkoWREba1VhgQrGC7GzwcMZhirTBqiyTmn7rdz8PCko7dJi56tr7q2MOTpacXjb3Kk5aBPAbXTr/wc9loqbljAXGE9vBUk8xQsPjJbjuHkbXXBzoFa4Urb678tLN+uHlk6vd7TDym++Lq5RuKKg7VXH3wPmwCaakrfqL2HRP/A+6oLT2GMJdYizUwEzs1MWQMN7IQHv7xw7bbjuZm+x2o+cU7VzxeKXOFTbP5325pkNWpDtuBe9Ry7lHD2s+Avy9Yqh4OruAfEOFD5VV3t2596t66dfWJ7QAFkOFsTvX9Oc0n1SnBHb15q6qKcRV+ozYo0PQPgpix2moHN+mAMHrKvE6Wqq8KroCJPUJmNTQ4Dix06kd5ACorA+CwRH120WjhuWOI/8irIp3H0lFsZoVvIVpy2cSAD2lYpG3TD5FUocZSy7gYb0HVackwNknDNXO+e6bhcCK+XqWDVBjrhk5nhckqOhWBc+PxfbcO9oaxti82ASK8kaxfa6Yy4Zo0kFl2LEQhfkU0IzsbhrYN1K57czGGRXiJXbxQG6IoQDw+1ZpxYFT9VO7bIbXPY6t+pk4Y6kzU7Sqm/SJetvIrIlKYLvjfiyc4tJMUySZgzWt20xcmd8G1zA0uySq27GG5BimAYLklIIW/dMO8D/sdXH4q0VGXbx1RsLA7CokArCr2zA4MnS6C6+gMkcjvY66HZR9sp1mYSLlXFuWH41vwsNjs5ejhOgjwgY4/NwZ/wpCH/aIAVP+O5t894cui/4jobHQVHyI9DwM6iMMwy4OrYQDFRj++r0sDF/+9FiYQtt6dxezlscXP8zD+H608yDDYISQXF4j0kSJNW8e6wv9x8O1PF3clyP5mkMAisBa0in/cEd+ZYMrAIv/QmqykRSux6LNPukCHQnPg0g9Zu28U5Rw9xVg0QDvFmlEHkKbMsdIpqCSNEFjpFAPRFCQGMVYUQtToJG/j/hYLNHgnp4lX79CskKYslG4gjY+Ls7i/WHiOErZX+f/5WxyRlT05ORZFdaGsbzLqctIweuR/sp02S8QdJg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUwOjA3LTA1OjAwG9J6zwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1ZLLnN2Z0Mfc8AAAAAASUVORK5CYII="},"206":{"admin":"Slovenia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADbklEQVR42u2ZW0iTYRjHR2dKQi9SKjFKIZLKjghpRBIKGkTOLDwhiaYlmWkqVkYoZrEVFgTiAdJc2cyyWYY5L2wO06W5FiIS1tTqwkNa2OlDu/h38Yl9Y+rSqP/Nj/G87/t83973t/c02djnsW9jAknaljJ2AUmxSIpFUix2BEmxSIpFUiyS/EfE6gkZqBqJrkpr2dAdqPR59NxkOFV3q8WgiYjNk+s9wGPqG4qmYZSiZufyD8phE4eNYo1jX31n49vUxAWF83UfXQ3JisoO2ZrwyJsq16LjgtI17k5ucljXkqKQlYWvEEdk3bnE5Auji72j4ss2ohUyIBuHkGIJHQsLnt3Wui6VV+TWQh3QzhDVWBALvcRxRFAqjjtpD8Rfb0Y2DiHFEnp1agdNkHurPPSKj1gUMTcnp9hnmr3cT8/NUEnVgZpdl4vN5YMcQool9OkeZjyptkYsUKoOMkBTDiHFEoaytAfrPXYkBJgya6WksQ894qA2glJ1thfL/RS7oSmHkGIJI6n6N01evs1Be3OKpKTB2TBdVb6+zd+yWMjGIaRYwnfhxSdjz6E5wdeyvaAIznrRi/Jd9Pu2RWckVHsa73dvGWzFtQIiKBXPYVCTYv3VYpV664O78m3LMk3TYXPMxHilo6pEu1YsFm6q3puHVn0xTryjgmQoxUyGVsiAbJafOLu01VtNP8/M949Marn5E8Rpzr/TtyTdaWdOdnBtGq5JrfkFQC+fe5fs6mqQwfIhgJxlzuTDcPkZ89ovMMkTN+mTnWAfexlH3v1ABtxmcQgp1q8dVcS8PWdOtio2lYY1tGPGwiIIafAZcUTECyJaIQMHj2KNI64bsE8SnwGtIVpZvrAg/1OxxAtieMrZOLU9lkXMTPiMzaY4ElFz3qUiEq3E/yeSFOs3G3mIUqy929boIrWvKh19sKz56IkAf2WSaeL/jCTFktQLe6aLDVlDqhzsq562vxzo0SEC+agUxZri4ohLBMxMICJc+CiWzSSjTBSLJCkWSbFIikWSFIukWCTFIsnxzLu6ddd+DUnalrJhxxVfV/eSpG0p6+93dnZzkyIqzXx8su8z/bbWZ7YmjzXZpv8+1jzLtt/L+lLZ1AaJJC2TYpEUi6RYJMViR5AUi6RYJMViR5AUi6RYJMUiSYpFUiySYpGkzfgTZbkUK0H4BRwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUwOjE2LTA1OjAwcQ9x5QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU1ZOLnN2Z4v//LAAAAAASUVORK5CYII="},"207":{"admin":"Sweden","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA/EAIAAADJWSZ0AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACGklEQVR42u3dv0rDQBwH8CA46ODk4uSggzi76QM4WRUVfBafQAVRdHB0sYhQN6GbgpOouPgIDg6C4ANYKUFMqa1p0kjPfJYvpX+u4fLhd+FyR6IoWl06PA4rh0/Xh/Yrl09nb7ObjcZDNYri/Pi4u+sl41/F7Qyfrl3vn4TYG4Oa/wJWr6TAAgsssMKBlRxAv2A12wQCLBULrDBgqVhggQWWoRAsFUvFAkvFAkvFAkvFAqLUsJoIwAILLLAMhViA1ddlMy7ewTIUggUWWCWHZSgES8UCCyywAoZlMwVYKhZY3XLl+Wjit9fdf5Wt5WYWCev7X/7qBKTpyfTH0+u5yHbuUhzPyPxGfW8+rByrbS7vjte3zudmHvPDituJ2wyxNwbhXLS/H13t1A6m62Hl9fvFwtTL6+3N5Oh2NlLJjNuJ2wyxNwYzWzZChZX5SXXaECbzZ8cu7u9pK0+Wrfcai/eVnxIF2TdYyfd1jSwkdYEES4IlwdIREiwTFmDJssFqmSA1826WvJCZd/cKQ7lXGNbdTKsbAljdEOLKi6j7yqd0n2ZbJ5S9/SI3rLb/b54jT/P9bOu3OmWe48yzTqv9O3ZC26VjabIt9mCpWGCBpWKB5Vk6YKlYYIEFFlgu3sFSscDy9C8VCyxDIViGQrBULCDAMkEKlooFlmssLMACCyywwAJLguXiHSxPpgALLAlWNesG9uS+QrD6m58+1tRKPATTOwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTA6MjgtMDU6MDCvvw1bAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TV0Uuc3ZnN3MeBAAAAABJRU5ErkJggg=="},"211":{"admin":"Syria","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADBklEQVR42u2aS0hUURzG79gDF1HZIqwostBatDArichdOBERGYGC1fQQJIJqU7StoEWERESFYg8hjMYQoqKHYoaZZBktGrQkI7Ei0h42oE1ii29zYJhhspm4587v/8HHcO7/3OE7/u6dey46XV1ZM/NycTy57rAE/8nHZ/mW+AALxwELBywcsHAcsHDAwtNhNwpYeEpQBiycOxYOWDhgsRA4YOGAxcYbsFgIHLBwwMIBC8cBCwcsPF3B0iY81lbcPBrdE300fs+/zE28J5EUE+v/21ypzjux742fN/FZcb/XGTodLLn5FMeT6844RaWgAIsCLAqwUlT9deGVQ1vk6fDnsTevZWCd9Ye6m6rk6QCWvXmtAWu0cGxf5PGGovtvqhrlGvEqUrbntQas0LNvxR/WZN+qbzsQkGvEq2DZntdx2zUq/7VnrPf3UfMavbCpu7Il2zftYu/OdrlGzLnmLPdf397O6yKwWvd/KujxldU/LD1/3Z9x7/KpoPlDkBMJbj603MmtfRmYIdeI2aNZOoPO5mawvJ3XdXesYGXf687ipfNvnDzywnFqfpSPm4trXsHmuDo1S2ew5Y6VhLzH+t53lnHHSqh65n0v/NhcuqLl+bnmSVsvTd81x1xo87OOqlOz7H2iipXXTG1LXlc/vA+GRmf/bCjquLPwRH80WHIdVaftD+xeyutqsPRiMPppw/TFaxsyD+/wxitTpVCiWHm1Gu/8w4NfVgPWBKt+/du8juNa0My7V0oqunbfbiuoDcs1omtanbaDpRRKFCuvfgrdn9fVYAUWPVpV3Ti34tr2g+XabI/kj02N5MhrJvdUt2bpHY86bQdLKZRI6cy8WgGthvvzuhSsgSnhpq/L9j5of1W38Unj5zO9A7E6dVSdmmUjUt7L61KwhrdF8kcWJP6Iqk7NshEs7+Xl32YowKIAiwIsikoBWFfXIZR8Ob4MhJIvwEKAhQALARZCgIUACwEWQoCFAAsBFkKAhQALARZCgIUACwEWQoCFAAsBFkKAhQALpZX+AJdtKUiI9nGvAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1MToyOC0wNTowMEB9ZmUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1NZUi5zdmffuTTmAAAAAElFTkSuQmCC"},"213":{"admin":"Chad","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABXElEQVR42u3aIQ7CQBBA0a2mpgnhAoRaeie4CxqBRXGWmqZILkE4Al0EBkVICLTbPjOWipcPMyWEUK6223RnlpXtJp6O+8XyEO/nWwhd1zSpzeeTX4vdpZjXMW/Xsa7zWVWlOwNYYIEFFlj9wUqRF1gDnYoFFlhggQUWWOOBlTgpW6FigQUWWL4KwVIssMACCyywwAILLLAmAssdy1aoWIoFFlhggQUWWGCBBRZYYNkKwVIssMCaOKzXCRZYigUWWGCBNaj5wRPaCsFSLMUCCyywwBodrAHQBMsdCyzFAgsssMACC6y3d6wUSYGlWGCBBRZYYIEFljsWWIoFln83gKVYYIEFVn+vrsECS7HAAgsssGyF7lhgKRZYYIEFFlhggQUWWGDZCsECS7HAAivJO9bXnwuWYikWWGCBBZatcGKw/gJXsRQLLLDAAgssv7EGfMcCCyzFAgusn2+ODxFBzpJgiMMKAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1MjowMi0wNTowME0fhVUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1RDRC5zdmdX80m3AAAAAElFTkSuQmCC"},"214":{"admin":"Togo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA+EAIAAAACBfXRAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEu0lEQVR42u2cTUhVQRTHb62UQgIhcxNFQptoEdLHJiqSCKEWLdKMdn0QQgiCi3JR0BcGkZRCLnoWEi7SkjJJssiUgsyIFCKTlNBMyTTxg8IXeITuY95M83Hnvnvf+2/+PMa5c8e5P84598yZ67z/teJS7gYPtCwja1NLnN82lDe+q/3F9aymzRk5S4rK9z51ik9W7NsC9VWtPGZei7inCY7M+AArzGCpoiCDoLGtot8AK/wWK2gKsACWPQVYiQaLF+XIR062w3OtOAxgBclisY/QT5g8vQvACqor5IFlYMO+tp0qOL+259rq+h01cIWpAZbM25aB7fn4MPfOge3Tu9/kf1hJvz17BwRYoQ/e5a0X03Pwy9GDZyLRjvnX8zNkt5ShUUQcYAUPLPNEJdM+frs+q3k9gTVZ1dLdfhGuMLUtljFkvc9yRvKG5wr7GgaiBNbvC992jXZLOUS4wpQDyx3aC1MDA9lF/aXVhJRbYxwibzQD3OkVIdJ6pXjdjaq6urNrtiVWEzUT2/flje9o7sdJ649lkdqGeRasn4ONDa0vNUeWmC2BNfXqVvHy2Wj0Xafj6OrbMbvqvotXc5C5yqv/NP6cHXmrQE6NXJiM9m3d2X/k3OzSnspP0yxY5BA/T+RvPHZcfkyagzxesyfu70nrTBw0qaYyYHGiJbJAhIVY/wyNV06ms0i5lfrIjEbh/2JkBrACr8p7heRiRhovl9Tky6BjojT+2KHq2rsFqraK5hkPLDWTDkT0HK7RJjQF5jxnZ6L0FknZL5MdQ4AVNrBcoTS5J3emykQnSpuetB1WdXm6YAUheA93LGURLHf901BeWWHFqKqLpP7kXhd3Eo2RknOFACtoYHGUIiFV50iO7z9WSqvOIgyukHeXoIGuNh/H20IXSh/oBfUUsXm17R2e4F3VmoYXLHE+Xfg4vw9e7Yo81ouuKJHhbWlyLFheLpz9B5bcFks6600WggpjWGimhjuyu+boLY82odk+5EAV0gpCp0nzmXlwryk9N95CiFtsZ71tgCX+72Qspfgqmfn8a3FktnL1nCAvCyXOhHEdomrJ4YI+P/2oN7MkRtOaBzL3i5UWSKZnHGXv6OfdzeejOmfOyF4c/3I5QQIlThaKcy2bCSMQ/S+bkelpXitho9rCZEzVaxX6myNF1oicILk52iWUsjGuTBhlsJQdolBRNhO2418MFpTB0qxnd7lIGkc5QYoDq8l3rjAmpWmh+hQVpKl9YNX8VA/AAlgefz7EwllFgIUj9rrWDmABLP+/YwOwABaOfyX9R0EAFjSgb4X+j8Z5rwRYKecK5ZOo7gwZ5eLlr7WxpQNVWEk6eUdKtQCkbLu7RfxX1f4m6h6NHTnONqp4Y1W8ZcvbmmXb9a4Vbv22l9/MWDWluqVtNAdVdY2pVEBiUs5hrrbHNylftq1+zkfmLhIHVnVP68pXIAWzXM6fB+nV3PwHS/5pGoEF9fKgQaIrP/2o2QcKKQWWjyehgQIUYEEBFhRgYSGgAAsKsKAAy+aHD/UyK3oHNcOYDgjm+pgcWF1IkCb3p13DMufkWx8Hu/FQK4olgAIsKMCCAiwsBBRgQQEWFGBBoQALCrCgAAsKBVjQoOtfD/eeMzorx/4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUyOjE4LTA1OjAwJcXahQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVEdPLnN2Z7uyOrAAAAAASUVORK5CYII="},"216":{"admin":"Tajikistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACt0lEQVR42u2bP2gUQRSHt7O2EAVBECs7sVPLA2NncSrYqqSw1c4gCIIK2thoZSciCKcIWka0EpQkEohIEP+AIopeQFEvuZw4v1e8MHu52WQF2fmaj+N2ZzbsffzmzdtN8exl0SpaENbLglsAEQsiFkQsbgRELIhYELEgRCyIWBCxIEQsiFgQsSCsTaxvM50tna0Q1sti0B90B5NwBE/3up/aRu5GAhErSanFD/NvTr4Q0QuxKqjT//F57vZ5MT7aW5i9O/ZdjMVaMRbtEMu46eexV897h6bO7D7auzZzc9+OFXLoczjHGI8NozRDyTmIlXVinfh45/o2sWrqrGcsYjWxGG/GVRDrf+DykYXHT86qDO//+nrrwYERP39cgSXoopkX23NfDj/SFRGr4ZQcv288Pbj5qi1eCUvk8t7u7OQl6TJiVODSw/fzF6/YVeINAWI1tVS3rFq90A5HLdtcYlnTIWGsZVWW5TzFe5KISiCR8hyxaiuoh3a5KOERS8ufpU7F5WmNYintjr/dMDFuy2I2kmUk1tL21+9OjVlBrRorfaxfCiuqrCvq6iRWYxPLUidOLN9hj0ZZbz1QO8Q4mUrSSBuFcMWSUYjV8EorfNaC5Z8Jir4x4ZsIOqpOlT5rBqqrrIt3/7aC18g3FCSNPa5RjeX6WDoqmXTU5gmpxtsQmSaWdcbDD68c0lJVkj3u7QbJ5N/NUuWkoyrPNZtmzrPnTh9raDvU55Dlk74PGlmGaY8ZvklqmSJWbsuiMsYvZJ5KqZjxmZZ2FXeOiNXcZdFVUUP3d6tvAtxjojyfDJaI1dk5daGzawT3T5+73zamnL9neuO9iQrn/+v5h80Qz6Nv1jbnev6exs1TFK3xy3//XQfCWsktgIgFEQsiFjcCIhZELIhYECIWRCyIWBAiFkQsiFgQ1sU/YXON6H04CdkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUyOjU1LTA1OjAwwFi1vwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVEpLLnN2Z/Ksj64AAAAASUVORK5CYII="},"217":{"admin":"Turkmenistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAJf0lEQVR42u2dbWiVZRjH96GEAl32MjezEW7F5nKytxQj7MWSps6X3BKxxLYg5ypLJClKEjPQKUMJTY1NVLRJhS5YpLCoDNIslZhRtujtQxEW6peIMji/8+Evt8/pOT7P2XZ2X18ubp5zn/vMnl//63qu677uJydnWPOSutoodlRb05KHervO1P5Z0vFJ451bS5Yde3HqrqKrsN1Tx+6rXNHVPHp71RdBdn/V6HcrJ2G5cuj+0g/Gl+o6rMyv8IvR/3KzGbRxgbW3vCavorf3+XkHx1wLCsChAB1oq22c8/DnY54oXnSMMdgxv2v+hBFlh4PwYmV+xcDyAqy8KYsWT395zyO37646dKJ9+qqifeDyUfvKvauPghHjH7f0VBwZ99Pfnct3PqrXfyluyb3h6t+vWTl8+G5XpViNlQGLX7Sb54Vi4aRQGqABoK/fX39283QsYDH+8OL4A2U9C/c05dU/1v3ZmwsXlP1a+Pqr43ahTFiAu0T/zBX6qViApTBhcXxLm+saKr/fvuO25RML3umsvPBAG9/Fbst9vGHBe4qaQsbKqFcUxRqW+9TauTlqDYJBDRa4qFbh5kCKT4EGgLjOGDu3c15J7c+oUd2p1sMr7mI1lAy3CL5xgWW3f5CCxe1RsIiE+o5vm714GFgAE9AwBjgUCx3iCng9W1DRV9OzoXjWPeM3so6uxq9EAeu6Y8+dn399Rc7af5ZNwYb/lxouAwYWuvLNLc8Ujj2ACwMXbufmvppN970CXlhwATssz4Ya2qsz5ZkxCljA9MPBM+1flW3Z/3F+9950cWRs6GQcLJxX55jJS8triIG+fHvGg4WNhN5ABl5ETtwevgWOmoZQmNSlJtMQkYN3sFhT3/1t58x5J3ZsXTeSK6mVjH8p8/mu4dVPigVYKBZWQ2/suRnrqkccUtQABYBQkaDAP64EKX+ziwVXDo461XE0H6vuTyMzPkXtDK+MB++aIMWdYUkTgAUaxhzNcmliAoyIwLCsEFeCVPUJq6ihRkGaxEz07I+W8wVnz7Ucf+vcG0csAst45h0niE1qjFO6SRZ5EtgxBiAt/jBW5WNNdDEKWGCB6gCQYqHApVY7VkBlB7NuDRj0ccVY3HjSnlgSm4xT1wpd666QXCfhCksrnp7VcCFdZQoDVniLK/x358Wqi8OJvUylYgBLYw4N3kEByJIVwDSRClIsVlPFSg0EmTAAcm98amUKY4ESsMI/XRpYkWIs1EVdG06QZ0BU55KsesItJj918GI1ZgJWmHQDMAGWRkJuSB4dLDfYzxqHNfjBUsVSmBjPOV09svxJVKRtQ8tvd5zUwjNX+JQEqathqFd4sNTx6W3jChoDfNFdYXSwTLH+pwitygQEuMWWruYJNatBh6w6OXo+5QqfMlND9U3f5b9WkpvUv8SvANaV3Ug3QZquQyTCYwVzhRkHiyoeesPNQ4fQJHChCK37q8CLK+DFTPSMFViNlbWkc2VgRc+h41hBSoN3U6wMFqFBh+IMSQRcGBFY642TWqv/SsZVbLOReItP1aXyLVaLC6woRWhVO5BiHL7mOFTtZf57xgsWtx9ngQIBTcdN9W239mmuS/HSTXz6Lcaspltu+h8sTVKoVqFepk/95AopMAMTTg0Hh0XDNJUKXjpHg3pWQ8/4lUxs9HP3aWE1baFIWUmnX4N3QmxC+OSez0QqAXdGLAIumm4AJj5lJvCxwiX5+phqhVps1niLsaYqKN0YUgOcbsCpuWUcVTIN6lWZNJZy0w1ugjTK3wwWChDRkosRVzTRakH6gCVItQkiqFyj+yDcfp6g/HsmunSADJcHOmpVz2z36YC5Qo2fgprA0i3poILWVzjEwXJLIgqWqpGWkDULH8ZqR6Fqm4JlyuFdw6pum3G7nEkZaI5er2hkpvqX7Q2r3v1vEJsrTDz9aasWcFCQwRKYUzd0LZ/qfH0eVLCsYdXTGEvhWPVpQVPRetCZNnFa7+RerHtFr/MtXcefGGuIaFtcYJEIIIEJFqpPjNUtUvBRV6i5excvvhVXusFs1oAFFuTHgWPjzLsn3HxSwdKjQdjzrjrHp8ykgOOuEL2v0GxWgsXtx1LSARGuEC3RgEqXDjl3rihYugJjkDWwPD27QbEAKQVLE6Q0gWkbvoI1+4Xae6tfQrF0zczVCs1mQYzlhu3qyPiUrujUMZY+J1qM5aliaYJUUQh6KtRnQP3UngoNrMBaoe5xCEo9pJvH0j1bBpZ3rjAo8+726oBOUOZdGyiGRubdwIqUIHVrhapeqUvOVis0sNLY3RAGrCDUtP3VYixzhZfZj6U599S2//djmc0CsNy2enAhDaExljaE6UHczHR1Lq5DQcxm8Z53UKDHBnTImzPWrhssiQadyTZlxqwZ1553KxVnZZcOFkS4DjSkEvSMZLLqzGQddyYrMEfVzhTLu5IOGqNPbYxpl8D9MR/d4lN2lOscd4VMt3+ZHdSd0Nx+V8+ABqu46Byqh4Dlti0YWF7vbuCpTV2hNnWpDrFzgWKz6wp1JqiBlLlCr195AgRu8A46zNcYiysApDNZId6GVbNZfD6WJgs0qEdvQErTDXzKdeZoeiITDatmM/78G+8576lP9Es3TcoK7qnJttHPoxcI4KTcM0hdvMK8FFNLOnoGqdUKvS5C65nHbid0GLzcFfTUZHOF3pV0dIuLtkhoZw4bkXFqWN196m6b0XNpLMbyNHjn9qtKaU8OL8JMnuuy5vjp9kLaKPRVKHqWqUZa6Z6abDaLg3d3a7KqFH04oANGLli8i5DDjMCL+bzOCYy03d7A8vTtX4qUAoTV1zCRhUfPwEvBUiXDXaJb5gq9e/uXRki8egmM9M1ejLU5jLHipZAxVmT1Dav2VOhREVr3eQaBQj5duwUBC/j0TfdcoVGMp0Ir6XjapcNRttQBwQinppBxRd8JnYzDEocyqusEMmay8tBOkA4pDY433UAYzvMdeGnMBBwaS2kcpmOCd5DieZCVLfPuabpBn+8It0EByABOwVKk0KfkS35l16geg2tPhZ7GWKpJ7v535gAHkROahHWbwIjYWI2VNXi3m+fRRj/6lRWFoLOTw7R/KaasHP3NFGazUrF4dlMgtOk+PFgaV2HJ5tsxRt6BpUUYXJhiEf5tq8zU7+pLCUyxPHWFighAAIe6Rd0Boe+4V/enSKnaWR7L0z3vbgczoKhVNdIr7tjVOXOFWZOHi7evMMz76DWVgGKpbulBIO5qqljmCgez/Q+Iy20007NKmAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTM6MDktMDU6MDCg2rqRAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9US00uc3ZntrCpqwAAAABJRU5ErkJggg=="},"221":{"admin":"Tunisia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEWklEQVR42u2cP2gTYRjGj06CWMUIbuqg4ORShXaRosRugkMX0Q51EUERddBFEBeNQ5GMoVJREBzq6NIhQZAK/sGlEhBFBOu/mlT7J9YahTwZvuPyfn3vcqGX757lGS53lyP3y/O+3/u93+fNznqbMxkqNV71+BNQCRaVYFEJFn8IKsGiEiwqwaJSCRaVYFEJFpVKsKgEi0qwqFSCRSVYDugFz+vNt1D+MgRLr183bt+yd8e383tqB57MZQcOD5V/3B3KDWehOIJPcSZ/MYLVwocAR6U+XBmdWBorPLvX9+fIi5nXN//e+TT5eax+uVKsLv17X39Un4biCD7FmbgKdyBqaQXLgOnX6JWV68urW2ey5ZMmOu0o7vazfOnQ1ckmZKkMmqkDq3LrWN/IAJwmLpgkxbfAyXy5GsFyw5++9Pd+39UDfwqGNknNkLf69t34h51wIylE2u+zsOlaJncDT5IGvBwHCy9y8WW+WHilef2/9z2+PVVDIJurDU4cfWom6b6kvvEpzsRVGtTwJE28CFb3IrVQzT3IH7S/bGCBEZ/PUUxfkcoNxjm4A+5m/0Y8FcHqSp0vnn5z8aPkIjiO4LhGeDKCKQoN1dJI/5kNCG1ABPdBLoVz7GEXx/GEBKtrFKEKmZD0UgGHxvOAiz7YIRtDyINKT4Iz8bQEqwsUVaWIPmEUI6T76LWZ+FvLGfgWgpXo0R/CkOQrmlcIpKQ8CXeG08RVksA98eQEK6Eqpeq+oCPkUgh8y8fv73+4aA9zCKNSgIum7qXznkuze1LQ0bw24KIJbQDUPjiIVq93aTrIEbCkIOgLNFavWilNn3h+VgMWXr/kcHA1HMfYUONtmuckWOuQXcE/pEkVsSCJzKxR6tR4jwkWroV7YfQHz8MRfWHW1PnBU+Pn6nSsBIElZVdwDvu18BXNi4ertQhYgZIpEElzpuW5XWKAZ9iDS/Bac/QXoiXGGJlGGzm6VHpwBCxpNIf6uB0s81oghRfsKwGo856w4S+EvxKs5DhWM7hYsZBQwEjNdCxNlZyO5RRYEXOshkbMsYKwGjkWEvmwxQjN34BgOTsqNAutMY8KHZqWdqWOJcChmTCJt46Fc9qqYxGspFXepYbjNYbxRvAKW3mPt4mZlfeEBkQxDVfPFdo7Gjo+V+hQy3KKuhs0bcHr3N1AsLqx9KCfNjH7sdqZZtb0Y/nGrQQryb6FkCf5iqqD1GhHbnaQFqa2lXbrO0gBJTtI2fPeY9+Rwex5h+e16HlvrFgM0fPu0JQzV+noVukEp5YVW4Og5MFVOq6vKzTCWbR1hUDNvq4QzoQaGNcVpm6JfRJWQvvCLpfYO7W7lbF3g6bOHtveDY1v5N4NKdptBiEv3t1mABP8Kc1bGnF/rM7sj5X6/f64o1+LcIlMSLOjX3pyJoLVmX1HgxkS9yAlWFSCRSVYVCrBohIsKsGiUgkWlWBRCRaVSrCoBItKsKhUgkUlWFSCRaX69T8dsauhR7BBrQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTQ6MjAtMDU6MDCVu+NGAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9UVU4uc3ZnyMywkAAAAABJRU5ErkJggg=="},"222":{"admin":"Turkey","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAD1ElEQVR42u2cS0hUURzG7y6kiFqERLQQImhRQgQhuQgpiJAiaZVbd7UwJCkKCiXIiBZC0KIX5CIIJCMVioSQ2kptRCTKtJcTOWk5PWxs8W1OTE7njnPvnHPvb/Mher3MPec33/9xzj3B5JuqfPUqFC2vBml4yKnBtY0b55lswEIBC0UBCwUs1O88FbAoL3As1B98AQsFLBSwUMBiIFDAQgELBSyWclEcCwWsGNp979u3HNrR/7G5bmJPS6Zh/+XDtabq97omPY5b2pOWd3w8A+vDie1V9bnsxOnejr7c8ODco6cL+151TtTnN8+MZfuli6/zs4uBVL/RNd87hs48yX0Z6tzbNSXgCO6pdqzpgw27G5u+td2+f+daITqlqe4zP35vw4Mjn7Y19TSPgoLTYJXLA95drNm1dfXche7s1YfLh8n0s6Vc7Wvm5oqeWgVNsEigYylI/Vg/fP3Zi9IA0v8q5MmN5HkKo2Y2NrPy6K22c/JChcufrSMHnk/qr8CRELA0/b9ujPWOD4SFSXDoDqW5prwq23Xy7tlRZW+ESO/BkpeERUruEsX0myWCYAUUz8B62149sqlGDmGPlK6POh+S8yks6nOCizdgKROyR0otA6X2cfaEACvsiAWVTdJ/X5l+mVmwQUqBUkHKtaGME3Qc6z+qVoK9V32ua2k91ufmIJpNV5CqGFjKjVTe22dULnfJVZMqTEcdNH1ZLagAWPKeZHiVVM1VfdrZx5fWdO8sjoUCerKbsUFlp6G4ytXcnwDzidRXM5sgwkjNWC0iydvczBcjBCs6s9Wd7bvqmgb3B7Hwq6JSQ0HcLFCEnfse7Jljhc2uiocVF4rqsAtQKllI3iPpsNu3GBQ+XBgmpeTCSB15hTP7ZxF86emHBfH3rux3K7gAllAQTPZe62MJkibHGjh+/tQ61wKfuc0wrGMlqZVaPBd3OsdS4zF5OVYadq4G8U9G2KrQ/WkorAr15TE3Tye1Klxqduhj0cei8+4DWDbhm847a4UhwFr+WmGScq+gUg+s5meSdjew19SJbTOqp+x9i/1YgBX5DtL4sxN2kLLnnT3vgPV3WNRbN6W9pVPeLFABN9mZU9SFQqLeK5S7hB0y83ARc7mG11Z5E/ofb0ILEfmZ7mkqb0Kn9FAQ5TSc3QBYMZ02Y7+bwP60mdICKOo0WGGns3DjinIy04cKfzaXhJN6PpZrz+L9iX5qTgoUeVvhiX66Bk8CLBSwUBSwUP/AIoNBcSwUsFA/2xaAheJYtCUBC005+oAFCoCFAhYKWAwEClgoR0WSdAMWA4GWWf8AK7hyUfoyPfsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU0OjQ2LTA1OjAwMATf+wAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVFVSLnN2Z23cyhMAAAAASUVORK5CYII="},"225":{"admin":"Uganda","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKElEQVR42u2cbUhUWRjHjxvVBIXSpoblBluEJUUbBU0frKiYjdqJjAyStnBben/fsIgwUpaMXXa2ZlnTXqAgA0EJ7UNF5GYvIrVS7eaQFfau1pIKQRBWH/59OHG6d8+8NnPP/8uP8Xrm3Huf+c3zPPfM3BGiUpQJF0lGmAwBSbFIikVSLAaCpFgkxSIpFklSLJJikcYy1Z/amrYaf+g8lqluxxb75wa7R3XO8I/E/oxUMj468fmIVw7UPhIkGWGKd13tw0gy0mQISIpFUiySYjEQJMUiKRZJsUiSYpEUizRXrM7ywuS+6fb8r+fnYUMf64yMDV8MLmpI6RebfeHcVcbP2cUyGvoU9/9x93O5WrrHT+zbjcfRIOZ/+IW7xzVAfSxTZx55i/489tsxz4MJnq8yqjuOb/tzUcqd3eWBfUngw5LzKf5JnUXNexv/6OkNBG4l9wys8pQVYCSeJc8W2rnoPFceo567TuTlkaEdpz3b1sxY0f+kiJ5MsSFCBoYzw4vNpdfWl0Kat696t7493fr6bEZd/pGueTkZ9a2TL3WMagLlMSC2YAZIFs7x2L9gVscfvfiExoQXKxxCAuQeWRQw0N4yNfBD3m3vQiHGe77e8eUUSHZz9Z6GTT+q4z9INrDKU1Yg5zAzaahYePdbKfXqyvOmzl8PTizLLM/F942mdE9d4F657pfl3qXz1IwVD3rFPidRrE/w5YnKBf6frLRArjpUUuzZNRO5app7pm+6v7KhcvLJ7+yVkikXR3OUShixdAInj7Ef/6R0+WX3HeQkey1qsiqy/E2zZ40dMmLcBt+2pxt8+krJmQ97ZCmMWFMZn7QqfzLbCtvmtxXmzc07s3iWJ/Pb/Z4RR4uOXD82IVix5LKY6G9dlsL/a9U1OqTtd3c0b78ri4V+K0SxegOBW8mmtfMift4lVhfG4b+fMANKkk7xykmbMS4nDeXPW73wmrcaj2tv1v1b9w06sGD1siqIoS0iRDbmURFLfjmdSpwqWmkdsXANiIYdV4XIYX9lXnhzuQJ6oZFvbL66trFOp2PDUqoJ0QbFxmXDS/p0msDjXetyv9+tk12KB+0ZXTxIvpkJkqHTQgd2rv1UfnUm5vw7a0vN0ntYl7eaEyPNibZBN6yiZ9K/mlu1YuXjVVV4LnIYSiS6LiyW1lRMqk0fjQ890EVZ6YW9805oBxJy6JQtmchSUEq9pVMWC1TX5eW+jWI5kFAhtNYbfRVmyO7OPpz9DD0WFlFlvVAW5efeGNm888ZI+zuVKVbC0zfnt1LfHJ1SCAVBiCVfJ2ILRkImiIVvQ8izYY/8URAjCiIacKulUXRRIBp59FtQCleIEAvfgEDGur3PW5PdIpfajnftBzqqsEeKZQQhhyoWlg+gER7L5QySqSLiwx+1bcde+DNGLtP6LXU93aq1h0CqWPYtv3F9FcWy10suZGjSkXtQHLHFSkF0VEYrRbHUVS6UPFksZCn812q1HVuMW6miWMHmMCiCTIaiBnWgHT7YwX8xkvnpE1xSmztG9AfxNVxQ3q6O0fmvOlJn/nBoNbPVfnXGr7lYkJ6SpFIeE+x+7Y/BKobhxy0a8bEayTvgSN6wSlIskmIxECTFIikWSbFIkmKRFIukWCQZUbFe/15/Juk6SUaWAjdMJi5xb0yin4XzKJz0s0Qm/x6VfWRiHx/Bl5zk72ORpor1efNTsHt3djb9vNEQfAEYAZZCkmIxQ+idl1PPlMsNjhI9fo6TPRbp3IxlprjOzoLvAf+YDWkozYq2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NTozNy0wNTowMHN0tmgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VHQS5zdmei9Y9lAAAAHnRFWHRzdmc6ZGVzY3JpcHRpb24AZmxhZyBvZiBVZ2FuZGFggYa5AAAAAElFTkSuQmCC"},"226":{"admin":"Ukraine","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAxElEQVR42u3SsQmAQBBE0W1TjqvFCszNLUOwCzGzlTU1PjZRXvLyGX5EtLbvZLUuoLAoLArLERQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWKSwKCwKixQWhUVhkaNhTdvRyWLnXNd+k7VG5rUEWWxknue3fU/6+pb/6AIKi8KisBxBYVFYFBYpLAqLwiKFRWFRWKSwKCwKixQWhUVhkcKisCgsUlgUFoVFCovCorBIYVFYFBY56APt8YgxrmbUbAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTU6NDctMDU6MDB5sb9xAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9VS1Iuc3Zn8neiTAAAAABJRU5ErkJggg=="},"229":{"admin":"Uzbekistan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACgklEQVR42u2azUtUURiHL7VoFX4EKuIHuIpwpTCIpITiQhDLoJa5y/4BoZVCIO5aBC0ciKJBHVy4EAZqkqYRpSQQbIhaWJIwfuQXWEllzLT4bS4cG3TuvXpv82weLnPmvnPnnIf3vOfcY1nXR17FPsCTZEXV8L2n3//z/+VW0OIzj5fj7eUPIsXTYV0jUEHTyc3S6O7N+ecfI2O1S0Xpvb47Mz9T8bpr462JsPOHa+ycrJoN30i92F2ImrJ2NMaa5pOivVXXuksRGObAiCV1kltr9dsHqeqd89/OaoDdfThpKmUlsb31fum73s8bopk77aIz4QZALA3wy/r06Nbm+u/9uV/drfGp6OtVLx5Ov/Wv/KfPc7eaOkKfiqWJL/snm8lmzGwB4bHF0hSjXCWx3MpV+RX7uVM9C4jAiKVp5dPlvcofq6LzIl3F9VDTwtel22Y01W1qNUVR/WRWUfqmMqsXlR/0UCxVV87F0sCr0Daj3QoluhYvPHzzfvhLyBRLwommWLpLERhmpsJDPveiFfqueNfUQ/EOfbHdkDtz5Nea+y42GgK5QXrp0cRgcl96aYNUepkrNVVOqqJMBY9eY5miqDynxirQVzq61qRpvmA5yqpQ8Y+7KlRMVoWBFOtkXkI7mUbhqVGTF4Tu0poua4n2lECTiYkrmbY++iE/WgeRlf70AITu0tKOFITuErEgYkHEgohFR0DEgogFEYuOgIgFEQsiFoSIBRELIhaE7onFAQ/oybEZHWfzD+2H7GBwe8xqePbkaugiLEzWnIu9bW7xIrKl0BC6S8SCiAURCyIWHQERCyIWRCw6AiJWodK+pYlYkIwFIWJBxIKIBSFiQT/zL1RgScNKvfJfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NzoxOS0wNTowMGWbGnUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VaQi5zdmdaHeTwAAAAAElFTkSuQmCC"},"237":{"admin":"West Bank","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACe0lEQVR42u2bMUgbURjHTxSVgMYucahFFBVjSz1JIaKtrg6KgiDhqJQKncWpEEu1glAcXKS0ILg4SCsOrZO6KYIKLQYV1KHaUESwtA4loAgqnsNJCIj5vpjEHwc/MoTk8e7H93/fu3fGu+euqcIdTygjnOU3coyM8wvC+Plj8V5xhTk2nmc8qDFbMk9yD5gUKCbW6lHBhwrfzCd3W2lDeyT7r7uPqYECYkWzt8pV5ZkjIqGwWHYNIyKhsFhEJFQUy6mX/ZkuEoqJRUTCBIlFREJhsegiYYLEctYwIhIKVywiEqqIFauLJCIRy9STjIhELEW9iEjEMolImDJixeoiA8FHvpoSa8FatpZgevAWxLoi2Rv3k/KitV3v9NPRg29jhxPdJyP/Xh5unm6dXxswdXlTsTrzf5VViknm+LXwck/kbf9xaO/L/j635+6JpcztxqbxQOj/+6WW77XcpBQWy46kZFDqciQXNcwZkdwqKpacpkQkYinS1uuCRCRiqYgVHZF0kWkiVlKsxqIiEr2oWGJi2VusiIVYYkrRLSKWsFIs3hFLJfLYbkhJseJZmGss6ok8KhaPdGASikXkIZbGtmeybXheZwxS39Ebc6x/T8yobk2sy4N+rd6vD4OBV6+9HZPWwufNgTaYHjQSeRLLeTQ5OF//u3rQ87G7uW7FyAlaz/JgWlH9EJ/zZYoX908rh9qHW3/69pj6uyeWWuSZrq58f5hJR6y4ws5ekhN5iCX3ij2RBzUir3qyq8H/h8lFrBtWJro8KCGWY3+cyIMqFYsuDwqLReRBAbGIPKgiFpEHBcSiy4PCYhF5UEUsIg8Ksz9SO/t4nciDsjwD+PGwNqWzPpwAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ5OjQxLTA1OjAwfBnZywAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvV0VCLnN2Zz8CRZcAAAAASUVORK5CYII="},"240":{"admin":"Yemen","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABBklEQVR42u3XwQnCMBxG8X+cQN1BXcQF3MwlPHlzjB46iivUgxehGGJNRMjvPfgubZGUh9AYx816v7O27oZX8KOdtumQhGWtsKywrLCsFZYVlu3ha1RYtknKwrL+saywrLC8CCssKywf3sLyIqywrLCssKwVlhWW7TWs50f4u0/x16vze+ZX8/d882z5PSWnWHb/p+dqfd5lv5s/b/lT2d+N+/l6ug3W1t2YgAYIC8KCsCAsQFgQFoQFCAvCgrAAYUFYEBYgLAgLwgKEBWFBWICw8LdhXY5kfSOtyPoKi8KisCgsUlgUFoVFCovCorBIYVFYFBYpLAqLwiKFRWFRWKSwKCx25QO7IGoQUExASwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjM6MDI6MjEtMDU6MDCSE+1yAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9ZRU0uc3Zn7W2pGwAAAB10RVh0c3ZnOmRlc2NyaXB0aW9uAGZsYWcgb2YgWWVtZW5boPDjAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/2/2.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/2/2.grid.json
new file mode 100644
index 0000000..fbd3598
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/2/2.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!!##$$$$$$$$%%%%&&&&'' "," !!!!#$$$$$$$$$%((((&&&& "," !!###$$$$$$$$$)(((((&& "," ###$$$$$$$$$$(((((((& "," $$$$$$$$$$$$((((((( "," ***$$$$$$$$$$((((((( "," ******$$$$$++(((((( "," ******$$$$$+++,(((( "," *******++$++++,(((- "," ********+++++++,---- . / "," *******++++++++,,--- // "," *******++++++---,--- /// "," *******++++0000-,--- //// "," 111111111000000--- //// "," 111111222200000-- /// "," 11111222220000- //// "," 1111122222200--- //// "," 111122222333--- /// "," 111322223333--- /// "," 111323333333- "," 1113333333333 "," 313333333333 "," 33333333433 "," 333333333 "," 33333333 "," 3333333 "," 333 "," "," "," "," "," "," "," "," "," "," "," "," "," "," 55 "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," 666 "],"keys":["","77","47","46","225","116","200","224","20","4","242","155","151","74","140","243","157","38","241","131","15","13"],"data":{"4":{"admin":"Angola","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFP0lEQVR42u2bTWgdVRiGr1HTGmOav4YELWmT9EbiwkWQIooLQYlBwUpttI1iRZqqiNiq0FKVCmL9qVYFBQNabKRokaqLWFq0FYTSTbAUm2IhxoUYaKHGv4U0KtxnFt/l5Exmmrlw78m7ebmcOTMnzDy833u+meTGxhoa8nmpNFvN6RZIBZZUYEkFlm6EVGBJBZZUYEmlAksqsKQCSyoVWFKBJRVYUqnAkgosqcCSzq3X192Y31CkhfGTF7oHujbp/gisi1QAmthzf/fye/h9prPv9IpPpus/+7ftkakzOweu2cZIEYgCSxqvP383dKB95T/j4z+0dPx97fGZlmmUERTIIg8TWNIk+kvts2eXTVqMXAWs8f5V5zpmBJY0kU6+vmFf+4HzH+3ra/v9jx8PnW59wIcXRyma+BxQCixplJAoaicmGo/mX3EDO+ic/e3dl6++1y2LtmjymxwWXU1gLWSwQAf1lTZAYQ6l0OdkU2u/uWXpaNiFUmDN1VYo/MaN0DlQMA6HM80CVmFcpXBB96hAhLSEpt3ruXhRFotaEgIr7O4UwdyqxQIgGOEokZzfszRIDZq4ncWL+E/pDA8ygVUEFrs2XwD3RXLO8obxAl4kKt8ukkwWUpwXWLOo7aTHd6rSdtjje2DxmwOBFUjG4jH7XKoIgiQomLLo69RHmAqssMFy05Xbi/K9kE4b56PGaUCtB4HlbxYUuk1EbBISjsIIR1PsEGO9MGpkBPTSWmBFjU3brgQgXKQIHYMdR1MXL3N963+2FIbx+U3uxJq6kfw0NzdS34gdjz/Lzi/x9SM40q5o1L6QYcS+wIn/26KZvtU9fzMQu0nL9rdmuWaS+5P8Hmb7LJxzc0MPLd7c1LyQ9ciH22eWTvFovx167PvWJ0q94ranOt9seppi6m4O3t9792jzykq/q7lcW64lV7WQdf/gO2uWrLCe8eCO1W8sXl26FTs6l62r+s8F69fdx9qbt9x1w62D1Scr/t4KrJG/dh2ve8t9wKXD6+ba3uHL+1mFFcfu+OLOxlWMtw42n6/qFlgVr1u3b9pY87VbknjwGwcG/ryipn60bv0lW7NaEWTtWrhmUPdWYPXWXDdx2YvWPyxYn3/13vSSXcCXFV7D9730wlWH7VpcX2AFqDsefrLnys2ub1GkwOvtx587VXtuPqWqp7fr9ksPnTp8cHfjBbvKsdf2P9+w57ZHb/q0ernACkpxI9Bx8QIFChZ+g89lVXbRQGK7wPLhRa5yfWWy6+jepnHcC13b09+0aHg+XmUdK9skJ7DKVEEBf7IJjJbEwS0fvFrflySBMW6bGhYmgnxQXiWw0jYIgMN2zIHDJjBwjC+vQfWrBJbrRjQq0xZKPAakbAIDL7pixHDU3W8yJ6jCJ7BACizY8eEcaR8zUFIEyV7Ww0CHoz4QLy7+C6wyDeZu1qG0gQKQJW8oMJOzuAL+ZHeRlELm8DvbrpjAKgvFP3y7M9u7AoudPc9U1y4CBascZSbKXpLrMwJkRz4e+bL+J7DjaOBIKWPZ8jR/BSOujJMBJeOsBY6gmTbhCawKUB68CxZpyY3b8f+lY+fbzhbORITHt5iJe9HrD6rbLrBQHiqdKhvkaS64odvtPzGTs7iC75sIULZllHJsy6gapMF6mPtoQQEIUPeFMWclj/y4Gi1WPAznCyrUC6kke0lXs7oykNmwj4NWPF5Cp3yKcrwvCixpBp8cglcFf02qx1men+7wgkhgSUuS6lQKpVKBJRVYUoEllQosqcCSCiypVGBJBZZUYEmlAktaMv0fswCmUwz2euEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjE1OjMzLTA1OjAwrEp7gQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQUdPLnN2Z45UidkAAAAASUVORK5CYII="},"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="},"15":{"admin":"French Southern and Antarctic Lands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFd0lEQVR42u2cbWhWZRjHn8h9KaPEqOiLOWN+cJSShZKYOQXTUKgRRoKEwgJXKIXZBwcaI2iQ+DYksAQzjJHBFmOViNF8KWyJjAbORKZB08039PPC/Q5yPdzdx/N0ztl5zp7/lz/jPOfc5xn37/lf133d1zmFwqM1T9YvTUq/Pv/91O4XRqpGakfejK9XW9vWtvf39E16oqY2lk5+aEL1hgu/rmvctOetpveHtu8pFJ6qXtGa7P8uLVKBJRVYyYGliRdYciyBJceSyrHkWAJLYAkshUKpwJKO6sTTM4+tahdYCoVyLDmWwJJjSQWWwBJYAktgCSypwFLyLrAElsBSKJQKLIElsBQKBZYcSyqwBJbAElgCSzmWVGAJrLLs6EqO0DvBRaFQGujW+3a1tX2YlP42dKb3XMO1fTcfvn1/fB1c3FPXO79/zad/tR6LrwP97RN/3Lly5fqBbbM18anr8w31Szb9kbbO7FpR98EP0Y+4nyb1HR5vmHtwTYcmPoscy4aJOH+XOmZokI10bfhoCn/5Td6l/y83/Y8fjO9I+M/Jd47AGn9KIH770kfLd/enoSxH3ONP1y5qblwksMatMvEsR5JaL4frnzvOHbk0R2BVRI1n365DJ44+mDZS4ItjZRwQNfFjozO+WTZtQ9PpL/oOXXiR6b98cvj6jZOu+rwtylXgm8oDqAIrK6WQET6pUyYsePWdG0DmU1+RGR/yXRWlmPJI73Ndq6vGqOAiIJLSluG9c9qnM82FwpT65bcireCcTxnHBeuV62sPNx9wg2wkfxodmRGKAmV64VJAxPEnVnx1i1fP2rKQlHnzte1VBz/BPwLIShx590sHHuh63ZeSdz/7+2N9Ayj7HIDoG40Unm9CoOz86edvewbsN8zBuxsqRwlqQGCzIv5m4l2PieIrPrB8WVdwF8cLwQV/Irez154fvHh28BQbcYRIgVV2vnV43vE3znx2j8mO/HaX6GBxZhS/2djdMn3/FffaFPckBEdSYDFVeFWQx3iuwideO9v4XktHdMfi+PrPm49/2fnuko+r936Fa3pHc/I2vmfH0iO/nAr6UFJcPwqOOFsx5CjkWDZE+oIgZ+JqZEjuGs0HVnAXsyzgLjb4BqMZBwIdejrsutUeEVg5RtANmqibevvAsuGV0YDJnsPSoYRNfZUb8q5uloNS3mR1GR4KLVgA5J5z8eo/1cOz7GgqkI5bZWHPKozpJ7+x7kUJgNWZW8eyCwKgAUd7LUhxhCwq4/p7Bm+nrLRdwqHvao4us/4Eai93rnqm6W+LCIm5z7HIioDGVrbI80jh3Up9Zr6Vyxen5iSvAgU38OFYqAULV3MzJxwLpGzNjPNd/yuLHgf5SnrlU7csOfZaVK8SWHn3OTec4TS4C8ER5Uip3Vq4kR2H7RrXtxjZW+WSY+XLq5hsghcaXgjwbTzfYxvHd3ezJwhq3H1Mf65CIT3HjT6R0UNnZqFNYOU32Q9vYi6LhmOBVc71d99GCg4X3ugXp81QYI3bB7yAI3ga29Nq5xZUvQVPcy11rLKouQustGGikm6f6iaVxpPCW+3YAiraonGcj+MUWgmULAI4nmITn8DKVgGLFZl1IJsz4WHu9IOOr0zAp2BkN3NQ+iZKbjMUWPntiLfTjyfFeS0AONpNnqKqlTahK0HtNg4ohPeqR28zpF5FGYJQSxugcqyK6DIlMOExhEh7JDw/C69+2ZDn3ktgSe+uH+/0iJKSp96ZLrAqoVJP8zHpPEGTxJx2Go5n9X6vkv1P7S7l9l4a30YyyX4qj2rJsSohFNoHLmzHqV68Jo2lBD46FAh/rCJJ1QWWNFaPfJCKjHoYG885e3uqJlIqsKQCS+WDyl5T/wu/C/ZEKz4GwAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MjI6MzktMDU6MDBzBFm3AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEYuc3ZnBgabnQAAACl0RVh0c3ZnOmRlc2NyaXB0aW9uAEZsYWcgb2YgV2FsbGlzIGFuZCBGdXR1bmGg6A9zAAAAAElFTkSuQmCC"},"20":{"admin":"Burundi","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHOklEQVR42u1db2hWZRR/MIg0VoygD4UT1JUrnAyzxFU0bCUaK5i+1fog64+ErRpEGYJBsTAhCrIZGLk0t4nORGdr+rrpWMtisZpjohsOzUx9zdctw4GrLbi/98N9ubvX++c553nu+94vh3Hv7nmee8/vPb9zzvPcc8XE0YkfJva4l5dnbI81VfVPzB8r/bfnkduWzarracvNuyc/khkiYVNDwsqwuFecCK8XQI72nVg62H3m6hs5ay/1lk6/pej2CGRhl71zc9bPPg5rwrKwsj+E+ASWWY6UtW5vvzQYf3pgZWUa6iODhUoObFxaXPH4SE98Wses4KiQACzIsYbE0F/xxM21JVua+qfMe6hka3bCK/W7N6T+8wTZwWqwoCw8iD/Gr41dWCxLnZkof19XvWtdOT9R6m9UZfduWEEW2dnJgbKrNUNJsWjocEH5qs3DQx2NNcn3rreP5ModBq41jSgjyR+MG3/DCrLIziyBHKAIiBLTx7/buSgPcsWBo+OrE23xxPyuMbkgSyPKKKNklHRkB4QALUCOGUtpwIIsuO/As4v/XLOtL/+jL+HWMokos4IQicmuP5Y8deJlIARosaJoEmCZJTlRRhklBdkZT5Wa7JyRcwNg2RGl3EnbEmVoQcaUQKCMaeTg/GQnDVhWooRLjIiS3z/xZHZ2ZDejpaW4uE4ysFiJ0ppRBgZZIF+iCuKKMjv3MJIMLAUZJV3p1UK+v+2+a05hdV+8QBRfx7iQOIKz5JRtWrPTjeyYgKUqowzugQARGA+asez6z61dI913YESrxFn8J65KRYeS/CLuDpr5yc6rtHo1ycDiJ0qscHmlQoDpVG5sxSvPXOnb+3ZrZXBPAA3QBs0pr6Y92VFIQmApyyhdLLUmk7vv3P+qXFqZxJDGKHbQT3k14swOkoLsnCMwQTGA3WA8RGndzAPPAVqhBpMd9DErsw9Tm9m5h4jWHksVUYKSzu/bMPr5S0EghcJKkPIKRsdM6MgOWwp4yM4ZmsqAxZlRBolIILfd/cnUqtovzm16veq0+Tj/rIKTnbNnkuW3BAW/BtFGR5ReSQQw2vrCmqKKhc0X759WcAESR3BWh3nKyuxkBTwaeSyrLGo4NHNZOR1RuvFV8FLWoLvt77yc/ATOqpobngyekp4WFNS41pkonWXjlZYHP20HjKzwwllOMNFldlpnhdSSOqO0Usymue+uXv7knnsf+KzwHbOvwhGc5ZkJ7lruz5XCBQQCFt1U9MkokVsdO3tu+NdvO286PbNj+dfHV7U8NQp5cOpgWesUnKXY2M1ZxvRnccJFaE54OY8Favhp48XDv5TTeY7mwmMrmzoh6UbBXcgiO7UuQOjvVN1IkAVPrEOnH3cRluBE0wKpXDjueOvsguYKVWm/LIm70OEn7U/nJAVSfzyqT1BPTYU8EnfBU5GihpqgK5Hx3BhqOWoLlXJzQLnVKa820qLy7m+KcpcUkDdRZGf8EnfhnAnyLMiQA0t/QsTv2yuwEIZT7Nk3L1p7DfZxF84eS5VFAlGhWhj5G90fFXZdPvl+e2zD2g/Xx/bKpVFog2aMopYKVdlOhLG44DV4h+doONn9Y91w/c7G/R+cR6kTlfSv+mu+f7EWx1H89AomXAUN0AbNGAXHMbqzDwtL8E7+lo4+N+ym3NA6++fNO67tqpxXP6fEui24fsmSMwufQJ3dK7BwFTRYNWNEjO6m3BCWfFwasHQOG90USBH3pJnf9I4NNsP4K4HiKmiwvrGDEd3EcxlYIFVVRJC198ENFWI/gtVjgba29JYuKHotCBVCg1UzRnSzG8K8pKODFYLMQYSx2u51ERr/A8ICJSESwsY9xEA47q/QiqugAdqgGaPgOMDnfrY8i9AUWzW120HKuW0GexM+fv6bh9/skrsCiJIBNGOUcO0RlWV3ERayo3h1jK6sSqHZvNFP/3BF07XCRzuPVMd6VG1N1lmaiRJPSW6ZIARLOjrvEc0MqTNRCupYys211O9J6wYI6te/+DfSsPZu0KcFUvAXVmWBKe2FVQ367rEWSOmiq2x+xR6j69M8LTSVdy2agth1z7J8HwY9YajB5NwUhLPdI3/pNcxtjAwqSZnNYwM0ndsYpT49EnKilEyFnGSX1lMvcAtG58ZrAA2kc+M1WbOCnrAQJWHwzkN2aNNI0pPYplUkIiFzk8hUJ0HEcHYdSoM3jzTTN2P3LGl1rCCKqMkOtOK1Zx91e1m1XZzDQpQiLJld1Ijb2upSZ6L0UCBVkNlFkHJuJG48HxC0bm0mhUaZnVKiCeXH6Mw/P0OqJcobbJvhz+yy+QuDFNJcEFZFlEIZ2TH6m6wDLir7+Iaqon7M4rGqI/89V8pJdpF/yuyMEpt5CD/dK62MGclQfeAJiNLuY+ORP+MM+emIUhBmdr6A5WF9LQBkI/jafWpF1vK8iMgukhRE6RlYWHbNjO+gqqVmTb2mZYsRLO4VJ/8DjUtlZ+AhkDoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI0OjU1LTA1OjAwf9VKAwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkRJLnN2Z7ZoFEoAAAAASUVORK5CYII="},"38":{"admin":"Botswana","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABAklEQVR42u3bsalCQRCG0SnCRLAjYzvxpsaWYA2bGNqI2YIYGIlgaiBoAWIgzoByz79wIp88Ll+kawxDa72TuYZHQGFRWBSWB0FhUVgUFiksCovCIoVFYVFYpLAoLAqLFBaFRWGRwqKwKCzy27CW57bou1ff/UHW6z/1399/bP9PrKbb+2FG5hqn43V+u5C5xsOsYLFfO07+icnGcfJPmJmZmZmZmZmZmY12PiN2Sj55962WU/Jdoe/hreR2g5tDLLmP5a4jS26QuqvurnrF+/uVDv38i8KisDwICovCorA8CAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFn/SJxiv5sAOieSHAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOTozMDoyNC0wNTowMBsCNxQAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0JXQS5zdmcDWi++AAAAAElFTkSuQmCC"},"46":{"admin":"Democratic Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGgklEQVR42u1dXYhVVRQ+pFD0kL1I2sSoIwSBCkEDPkX4EDOIBVr0A0FFBUUUWUERNAzGUCkSDSkNFf0YvqRDZtFEUkyp1MAVBmHGQRLtpWCgFwmloqCvh3057T3rnL3W/rvrZTGce+ecvc/9zvrW+tba+1TVFSPP//2XWtgrV46OXn5Q70Pd3nvgvZcPrZ7fO3Ni8IVL03OPXPdY3f72xKF9qy8tTA1P9o9V+pOotdmbqze+m90wMf755aefWtwxe2zgjzqYLk6evHrVqZ+Hn73qhg9m59fvX3tr58Vrnlt3S6W3T32haVcuG/ts8b6dfQePvDXk9k+/nnjzob7BuYHNd63Z1pm/dsu6NYCUAksB2nXOrZ9ObJwaPDYyfdsdxylkVweTAkstA9l1WYDMsIGAddOevX1nBmD158yS7JwwigYsTANWiSmWpZPd2R93fNy/oovs3GCqfacKc7O+OP3N2buXw6rPyIDsCNCJ7LEwsfM7O4sb18PiiAbgYcju3Gznnk2fSJBdY4/Fe4MwPXMyj585cPTdZepLkiM7G6Tqxwngq6Sfy8Orvj7+wCZzYkqIuWR2PraSzgRBf+YklRB5UyJmsvP51PiOILAe/fKjDe9fsD09+FSjJcr5TYuaHUNmxxRLicRYbnvw4lcTDy/aJg+KVK9DARZ8f0uy84GRHBWawqZp1w69/tP5ju04Qki3i8anW67fd2r6RtvZbNcCjeJ4L8iY9XDCBBOJ7Hj9E8ELVu6JvXLy8NCuGeizpmQAWMDajtggZd6U+v/azmz+jVGV6p8EZUyfyIlgAe5fntz9fd9oRQ8SKXCRs7h6qVIFQ2YnEYY7y8ywGAlGhRFitA1irM3D43fO3A+xICSk8Ozi6iWF5ww1OzGxYAlg/Xsc/hK+sz7mxsE7IpvXbp+cG/nQ9my1sybecWZcBT+AypgtIyefeKsGJoAbQHdHz15Z4fYf3nn7yLjtaTOBQrc4G5JqJbsGnonXbxHIzrSY0f7fj/75zIX/FEouIbSusJuXpEAK8kQZrTUgUOaaHRfxkQHqJjvYqVe/Xdj+EnxwV9jA22zvQ45leCncXPhy5pqdHOXVMjs32YFV8MBYAxWuGwon7xNj4Qy5kx3owCuzCy5m0skOv5GtHMfssRDOu/MaenSVl+xJyewayJjS+lPDzM7MykF29AIUA7Dg9t2gMWVP9zcpE4hLc14yZixrQMqUMb3Izm39bzecv22I0L2gQlGUMJyt2JqdR1nXRwhtmtkxpFASJIihQ4Wy0Qc+rU8SZ0tHu2KTMSU8kO0IV2YXC1gYivns4taDHH2UsBQIMUJmx1QwZsvsYgHLJEEfFQoUaSphNm+XZc1OTsBsRXaYnbhe6EMQeJp5F3XhbIjDwhAinewQ8NoWlYuH3h4yJj2ziwwsRFdyhWGzTyu5mh2XUEnXrmoRWyyyaxCB6dKDlpldGBmzVc0uENkpsOg1uy6y4+rG9A7Go2V2YYCV4w5YXpldmA5MCyEmkdmpxxIhO17Kc3crxJIxFVj+ZPc/NTsJuLRqwcuS7MoGVuSanUcZOHuyKw9Y2ciYHjW7JDK7XgAWw96YXOtVxDI7U8aMS3YMVy88swvZNFe7VqAGFfVYhSw9aLXOLsvMrgxg0bsxrTW7MIF5q3V2qZFdTwCrKdmRGuikl256yJilQmqJnvcw02bbG7Pd0k0PmaCHMruUPZYJU3QrNF564LPql5UQKWSHhp/yyK7xXJLeQSUBm7KMmTRwC5cx6X7O+CbXOrue3oM+s3V2EluKpd+NmSMEM2hQ4VWkkunGLNz6oJWZ7KSreP47qChc5DwWczdmkpldeWSXNLCCbhfGtR1PD5BdorlhtMxOuMyiNbtYULZuCiLYoBKks6CpjKlAEfRY4jKmxPIEzexSpk627cL863c9XLPLvq2vbt27twfdQYVs88rs5ECTdEmncWbH+1KNVm89kCC7wrujYgGrcTdmkG0wKGRn7l+qmV1CdgkZM+Q+TypjlmQF95UTrtnpj5czsBJYZ2fuTVp2za6oOC9Wfqdkp1TI1sRikp3NP6UpY9Z9Sb7ehVSQiRxj6dIDJb7GwKJ7JqZ1dgqIokbl1SHu8T47rdkV7oP9l5Yr2anlkBtUxkw/cE6BarN864Ha7IN3XXqgVoIKyyM77WIIdGfcDSq21j8lO7Wkko5mdiX5uSRGuzA1PNk/5iY7+KcyXgauNgxY/wHtNammNY8UKQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6Mzg6MDktMDU6MDArJxFdAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9DT0Quc3ZngkgrjAAAAABJRU5ErkJggg=="},"47":{"admin":"Republic of the Congo","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACrklEQVR42u3dv0tbYRTG8Qt1dCyCIBgkiKijtEgNhEaMOvUfEPwLSku3IrVbXRwUhWopLg4OLgpZBAlWEEUo0qEFKZrNoYg/IKW0JTi8S6D1F7nvveec97uc1SEfn/PkwL2Jos75xUKBme7sqC2NDfZsH3x+O7z+59fR4eiIxlltKo3ny6ezU1uPXkR8qBJIra3vPR7u0UvKTUfqqLv3YeY3sFKbLc8+rBa+WCL1/TDb2t7mJrBSTqm/ucrL0RVLpIBFSnkhBSxIxdClgKWelISleXNKAYuU8kgKWBwRYlt8wEoopVZGdl4XT5JHEO+6vG9KAYvF55EUsIyklITFByxSymNKAYuU8pJSwIKUR1LAgpQXUsAKiFT9GcI3KWCJIHXfy1Mjl6pkSAHLbEo5fMmnFLCCIOXm2fu54/5ckqSAZbaeO1LJpxSw+MYHrPBINVLP01p8wPoPqYXdT/niJEcEYMVGamai/LVY0E5KTkoFDYsu1ci8+9+KICUzh7R0qaBhsfiART1XnFJBwNKeUnpJmYVFSgELUgZJmYLF4gMWpG4hdTzd96rjh15S6mGRUsCiS107z6sfm59kLJFSDMtSPbex+BTD4hsfsEip4BafMliW6nk4pBTAghSw6FLXktJVz+P6B4hIKe0pJTMLI7oUKWUQliP1bn/zwVCNlAIWpEgpqbAgBSy6FKRkw9JOyj3UACkRsCx1qcuny98GTiElAhaLD1iQIqVkw4IUM6JLQUooLE6dTC+wbial5TePraaUyldFak8ph14yqeAeWPVHynfC1b9R2JGq1HJd2QuWV8qw9KbUv6ToUiJgsfiYMcOCFDNmWG9+bjwf2rRBii4lApalUyekRLzclnrOjC2x3PVc++IjpcTBskSKlBIBy0aXqjaVxvNlnpCRM68Am91UDENA4fcAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjM4OjE4LTA1OjAwQfoadwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ09HLnN2Z8XoUVwAAAAASUVORK5CYII="},"74":{"admin":"France","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABPklEQVR42u3cMRLBQBSA4Zc9CgqcALXC4bThBA5gNFyFM+wBVNFqjYTszpfi1Sm++bOv2ETEdNK2pc/T9ny977rCn+fx8rjtc7dMmybnxXy9KncGWGCBBRZYYIEFFlhggQUWWGCBBRZYYIE1/GzSLB0CLLAUCyywwAILLLDAAgsssGyFlcAqnJRiKRZYYIEFFlhggQUWWGDZCsFSLLDAAgsssP4DazQowVIssMACy1ZYG6wP30qxwFIssMACCyywwOr/HAYWWIplKwQLLLDAAgssZyywwAILLLDAqh6We4VgKZatECywFAsssMACCyywwAILLLBshWCBpVhggQUWWGCBBRZYYIFlKwQLLMUCCyywwAILLLBG+T8ZsMBSLFshWIoFFlhg/fp8BhZYigUWWGB9C+t9ggUWWGD5FA44XxBz7mcwZM9VAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0ODozMS0wNTowMJkeu+wAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZSQS5zdmen2JoeAAAAAElFTkSuQmCC"},"77":{"admin":"Gabon","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABOElEQVR42u3bsUpCYRjH4fcKmtRN53AVpLwLh2g9F6Fbg+DgXQhB4dosSIsI6qTQ3NYa4SrS4JpInQ891bM80+Hl489vPRExHGYZmVoTUFgUFoVlCAqLwqKwSGFRWBQWKSwKi8IihUVhUViksCgsCosUFoVFYZGnD+s62xz1u9//zN9+vzjmyejrm9X5w+PtPZnWeG0sF7UXMq2xe183y/3TuN2uVpVK8W/+pfecy9gPQaZVWBQWhUVhGYLCorAoLENQWBQWhUUKi8KisEhhUVgUFiksCovCIoXFwoa1u1sPShMeNM//Kv94t3i7mH3UR2Rao33Zu3lqkGmNq3HneVYl0xqtVrc7nZJpFRaFRWFRWIagsCgsCssQFBaFRWGRwqKwKCxSWBQWhUUKi8KisEhhUVgUFpnXTyquhRLNf5MSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0OToxMS0wNTowMDT5168AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dBQi5zdmfDTZtPAAAAAElFTkSuQmCC"},"116":{"admin":"Kenya","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFGUlEQVR42u2cW0gVQRjHR3rpTi1dfehG5wgRUS9Bl5eiCwX1EIUFRb5k9dIFD5wgKHoIyywIs6Qeuhwyu4GEQhZhJZWB+GAJUZBEER0yohuRYeXpb/TVNMNuarY7fwf+D7O73+yZ+TnfN7PfrlIxNbijUKndq+wCKsGiEiwqwWJHUAkWlWBRCRaVSrCoBItKsCKm0+t+Ft4bwermwbtblCn/zxD+n3dFsAKol8gUDCFK7FGm9Nb9oHV5P7hDghVKTV5MViYrv37OlNNDMyXocI56pXJUTt4k76P3EYqaoIijddwJ7ooxVugd4ssRbypfHgo6qFPbVMffiTVei9dyf0Lu3twbUNTgaFC4cScRd4LurArlbNG06VmqaZN9aAFNWSKjjQWz82fferx01+FdZVDUVKzLGqBUvDxrR9YOO9bpZ+lP6U9y1uSqMCK66EGmyHnLNMBwc0DqaqqjZtDDlsS1xLWn2SXnS85DUYOjOFN3jrr7Q+u4E4IVKS2Nlb4ofWEf5m0LfyIFfX77ZNHJIgkWauQ5uEqfq9rGp9vT7WgRrXMfK4KKdVnT5kzR463Fs35HCvq6+mbrzVYJFmr0M2FBj6vgfHt3TdoLiknbnYJZCnhhFkG9dH/QuuHZ97LvYdaRYL0f2Ly9uRhH5fmwAGuwjFbQomv9rPDjXSsIqKE1m1fWr6zXZ6DbWfFUPKWDhRoc1a+CNWnfzR5WmK7dLKa5pytgXZk2MndkLiy73LdOgyXXd90FllxLOg0WusA1bWzeEtsSw+zSE2DBMlpxs4eVqWuirdgg2Nj455WgKXh/Mrk4WZxEza25Y7eOfW+6Fpb1zQt31NGfja0B7LDbwdK3G+yRGXbkYdm0hUGwIqjyUYy+z65r65jqkuoSCRZqTOfLvXgoWiRYEdf9l3/fJbc7LLg/CRZq7E5W2reDS7Aiooh+5MDbHRYyGgATFDV2Jyvt2yM5ghWp6ErPZbCvDeV6x74e1NNp3Iy0nPvB+sD7iYQax8+vml8F9RO9+QeXYEUqbLenyugzFtaGUNOMZUqhQYuuhfDOgaUPPGoQ1OtXIblP7imjxgSWKceLYDkHln3thuwrCZaej2WfsQgWwfI1Y5meMHLGYozFGItghW1VqIPFVSH3sbiPRbD+7c67fJjDnXeCZXxWKB1Wzz0rtG9kEKzIrg2DZjcAKf/ZDW6G7czH6syaMg28zF5nPtY/BSu8/4t+MkiRI4p8UekQmUHqCyzmvPvJedfB6rmc97Bkytvvk2/p8C0dvv7VM+8VmpyaDhaeEtrBgjW+V6jwZYGQKd627YIFvKOMT4PU7Fw/ZdV+e4ylz1gmHGENljvfhA5jD3dZlXd2627vrGt64F1N+4G3BXfPNST2jFudt77/siNDhuWrPvpbOv5XhbAAa7CMVtzsYaX65a9Vfd3RmR8KL8/8UDGj/kvFjM4u+F4/b9icfb+u4BCAy9e/4ApNX5uBBViLF25viBeiFbToWj87B9axthsXjrWtKD9yaUW5frSgLOdo8O9j4SrdGlpBixJighUpxTDrc5XU0bWrlqta6RbtX/TDmbhKt4ZW7CgTrBArBti/Y5p8fUnVjx150zdIgRTO/DvnS7BCrzKU9n8VoDl+cOJQLyW/mowaP0hJResb6k7VbqgjWBFRDCcC6qDXws3ljV5wxzsDNTk+u6J1gkWlEiwqwaISLCqVYFEJFpVgUakEi0qwqASLSiVYVIJFJVhUamD9Bgc3K7F3aKTwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMDo0NS0wNTowMLjeFp4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tFTi5zdmdajF4sAAAAAElFTkSuQmCC"},"131":{"admin":"Lesotho","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADIElEQVR42u2cPWsUURRAR/wBQootUuiCaFBBRdFYKzZjkU21nVgm/2A7EbZIZSGIWITtNH9AsQjWpgi4gSgusqLVSkSjiGKlDifCldnvbCQ4hwuHx8ubTXg53HfnziRJkpTLjYaUk6ZbIBVLKpZULDdCKpZULKlYUiqWVCypWFIqllQsqVhSKpZULKlYMuOR6cbpP3Q3FEux9pNYcROlnBCnbp2de3BPysky2Zh52fn4VPbi5sXW98/r/WdknsnPLaNfrF1eO//sR221tlS7Dpuvnx9tltyZ/qFYXeL9UhaLBxfaC19m2lkcWM2iNJ8FM3yVle6YYg0VZKZL5SyerGeBTHGGMSvdMcUaEK0brTOtFI04BMlJUSxWRr24yt1TrJ6xkj7srBxGl3jMRbGYJ2IOc/cUq2fcL2VReTN3sjIdMxPVFcEMYqWL6fH0Gjq6e4o1QCx04YBjTPFOMMNBqViKNUJzIR555KcoFjP5aszdU6wBjQZ0QSCk4RCMYsXi3aaDYg0V9Kggh2PMWMzENe6YYo1wb0jeig3SfJvU+0HFGvlAjMdfPhDLDpZijdx576VUDI9CxRoqYtdqGLFi1eXuKdaAhznDKxWrLpsOitWloqo2q3eqj0ZVKoatB8X6K+qP6zfry7tRKl91qVehxRqvorLqUqwuwfufPGYer6IavuriSWIxe12FE4ujKv769yJjUbEhLmPFKsR9Hxkrvk1FdonvXfWSL3beGbOeWo15xigVX7ZRrP+8SEcjhEAyMhljKiQUoVka5Ys6RkFZyXdBpihW0VqpBRKLPhMqxAyEQFEmJIhPDNElygTzwnFtzHPoVbQuV0HvCuMfdSFE/rCL84xRJM7HAzGu5JOL3DK1j7XTIKUCI0vFConMRE6CzJOZWMlV3G/awdoRa+PUh2/t2f3P9audQ6/m8+PdfE7k5rFP22/PvTix/fXdBcZxfZ6siVdFjvcz7N2O/XsmU/XltLIl5WT5+7/N3L19ZVbKSdMtkIolFUsqlhshFUsqllQsKRVLKpZULCkVSyqWVCwpFUsqllQsKRVLKpYsFn8BDhVEebWytPIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjE1OjE0LTA1OjAwsGDYCgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTFNPLnN2Z3fEqJQAAAAASUVORK5CYII="},"140":{"admin":"Madagascar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABWUlEQVR42u3ZsW3CQABA0aOhyxIMECEXlnFExwaJhBSULk1YwWAQQ8ASniDpGIEBGIEJrFyqbJDI5vyu+DXWPd0ddyEmMb4fDotq0t5ml8dj287nWabdNoClYIEFFlhggaVggQUWWGDd8WjjU5yCBdb/w8ILLCsWWM5YCpapBQsssMBSh3cFy3UDWLZCBUvBAgsssHSYsPACy3UDWLZCBcvUggUWWGCpw7uC5a0QLFuhgqVggQUWWArWENrLr07kX2HzfnrZfm6K1Thr6vrtmufabROB9frVfOyfQ9gU+TmEui7L4XW3Lpe/7cHvSRHWkHn1pmmcsaxYYIEFFlgKFlhgObyD5bpBbYVggQUWWGApWGCBBRZYfR6jWMXCdQNYViywwFKwwAILLLC8FSpYSIFlKwQLLAULLLDAAgss9VaIF1hWLLDAUrBMLVhggQWWgqVg/TksvLrvDxwkcNOEzggwAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxOTowOC0wNTowMKFWUm4AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ERy5zdmf/ENrCAAAAAElFTkSuQmCC"},"151":{"admin":"Mozambique","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGvklEQVR42u2dXWgcVRiGp/WiIbFpbYRAWy9K3DYhZmlxi8TQEpcibiP+1GLToMRIDFrXP0JoYy5MQVqxYIpGoWDBn1asUUkIQRShWvpzUSHWpvUnTWgjdWFbUdpYMJoo7LsXXzmZ6ZmZs9nZ3ffmZZg5e3Yz5+n3fuc7Z6ZWz1R4dMPgLRXPTLa2WfNfan16K5VqQE9uXPJpZPSDf+/YHG2saW6ONXfwplAN6A/XFm+IrIYOLF+ye83bDUcaJh6Y4q2hGgMLihjW/lPdxYYhWiTVGFhSaZFU02DNlN4d2QL9uKFiYP3XtEiqObBokdRMgyXxYpGCeiOwbADSgQwWyQyM6jp51wHuTCT858Z2WCRvKNUEWApktEiqPVg65qgxi6RFEqwbIeU2D0sdyzo+Yxgjli8rtDvPIgVnhaZVWCQyMFokI5ZhsOQsknV8guVaUUR1Ro11/MIGy9ko/Sf7tMgCnRW6zcB8ZGwsUtAKDWBk19vRb8p/rBllHd+tBi6dyHjy7pzOa1tkZf3uZHeUmitqeSt+Ore/kNy6b8Ul/9gdn1i0YE0Ms8iJyr6x/njy0uS1ydulJj5MbE5Mquft1G37YKr+X5Gt+2OZjT1nd678NVx3dfFguKTzlzNru6qL/JcnZJECeOGPH28c33/+tHosz6jn7T5r11LebufvVfvR+VV2bdxe9XPs9qqOWmZXCcfaYutDf09bF363rItj8Zbboh4joqMiza/dG2lc+3Dos9C3ocPUoKllYGVQ1K4uf/Tm8bJHARaObXFR+sTa4q6Xi9etiMEEndu/M11SHipd2XJTtOi8dZe11LqVGiB1UdhUWgKCLxMLk+FlmM1dqf2urngPwMJx2hDtsrTUMRCJjxX1LB1Hb/pwA8EHuxbMKzvC4QweWM4ztZQCHcAECNAFYsbp+tjzq/8AUlLTibyAAJ+V/QCLdJRyW3oVZ9AbY1jgIpazOkeFbXse32ntV8GCIQIawCQ/NQtSvmtptMhggyUGTAUCiggBrf5n1RuWdXCg5/553RIsnCmvL20r2SsjHIZfJ5fyprBUWmQwwFJSbCTUEgvonc/d/MjCJmCBmSAAknghhkmY8CkgpTP70zJERxyl+XKw5xas1MCg/oQCAfTcu63nlp8ACvIDQAdXocOvv9dfNjRV/fOB+R2ASYKIM2iD9vJbpCaiHbXLFqnHs6T/npQWmYWIhVkeEm3M5oCCRATHiEbACC3Hr5wcWfXqvs54e/GLwE6279/1/l9Vn6MNVM4c7RT9pythRlcwYZHqPxhqxmeFiBConks7AzoY+MNPDW1vGjn11akdww+98kV3TfdliSA28UlF3RZVbHzWDi98L+xVziXNLo3TIrNRbhDmCPOSUQpRp29b3/ZPjvZ2vRV7rQJIod6K8wAOMAGsZ3sfO9BSBbBwHnjJ+IT5I743gxumlbVIJvhZmxUifiCWwNqACxZVJFhADXgBIECGq4htWEs6Fj+06YkdwDQdn/T3PvjZK5ZSGCIjVrbrWCKGyRU6IIVohPOya7QBWBIvCR+Wll3MB33vCcM8l1EqG2DZDDBmVRIpwDTSOfz9YBPiEHCR8QxIwQRxVUavg1Wb7nnSH1icFeaiFWK2KIcEuKjoyE0mMkrJjSJQIAW8XIDlLYaljml5AQJLrVwjSsn0HHhJsHAs29gpDBR4wZ7Mbn1m5T1wYNkNCc4g6T677kRv7CoSeRxDMdcDWDiWtSso5oCok6FPVJXS+xq4VpivYCF+yIHB8OOq3HFlV4XCTgedltgxgf7lt3hDCpanLkBRA2SF6h4pFE7tCptqFQrHznih0ID+02uIngoHtLyArhXqDCQWfCQWgOy6RyeUxBnpv6yEqUVRb1kUIisXZ3J+P5aMPdc9LqHztLRYLFL7Scc57V+Cl4Q773k3tSM+ODvr8Usy/XtM9a8FljTBWXaye4o3WGBGn3Jl0K69+pSON1Wfupmblpn4Xm/t5+YXugDL1vJ8P9XjDJZE6r+ZmWPJe6nB12w8Ca2tchvP9Au/Haq8j5orGiSwRMTCRj8k+M47t/JD/fylwbxLVsZf9aFti2pqn083utDU8vNmUV9vyVK25ejsLM2/eFOoYGXshZGFaXmFg6k198ZnyvKouR+xfPx/O1LV+js1X6OpZfjtozZtZDmUw8OI5cvy5CI0s6jCBstQviUfIOMtJlima+W8xQUNlrfEXHk9JC2PaiBiYZcVCwdUEzmWuErLoxpbK4TlsVZONWaFhWx5/CeUkYjFWjnVcMSi5VGNgcVZHtUwWLQ8qtlM1GLhgJoJ/R/N//S6HFnXvgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjM6MzAtMDU6MDCS9u0iAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NT1ouc3ZnDad5MgAAAABJRU5ErkJggg=="},"155":{"admin":"Malawi","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGTklEQVR42u2bbWiVZRjHDxTVhwpc0D4kYUGnonKm5pxU9GKnmqJhs0lqm1YOp2HaC5NSlxAdNRBEy5fUdLWZFS0Uv/UyEyqYhYpWjhAtVjgrKAiCrLBfH/5wdcasnbOz5/x54M/NdV/nvm+e68d13c/9PCf12SdDytJpq7V/NeVbYDVYVoNlNVi+EVaDZTVYVoNltRosq8GyGiyr1WBZDZbVYFmtBstqsAabft5y6bbrKmgfujbdXnUUC4rFd8lg9QkjBeh4/bwDTcfppf3FsHHlE1aiJ15fOHdpSj3jCIqmwSohVQjA5duaxQef34Gl56mNv7WcPFw9OnVXD/avZ04bOucUioUR8ORX2BnNuS1VmgXu6MjqBQ/OJfcQ/lMd22fvbKP93dRsw5pf1Qekjr0we+zjaSz04pkLMnwMVsKVMIOIwhQzEwBFpfDhQ1t7NWMppviXGmSpUih5qoSc8EcgyEC6lwIIBQtLrl/RBqYIGQrKBmvQg0WRIsxgobkKFDRv4anbds12sTdCFiFGWUkp7L1S99593gUXPZckrW2/5PRl89c2znhg9kraszpv6bq9fe+Jlm/aptVVpVdcf2RJ98O756zGZ+H3mYkTrqSNJxYU/8aaUcurVqlFfehlBG0zCxZmZ/y4wuRFIZWal8yr9sLaa2pvWD9lQ/369vSP6dNXn5vZlzmc+Sq7IDs/uxpLw7aGHQ3vVBwYPqbiU3qxly8qf7p8eV9mUX9GqOyo/KCyi5HpXbVt3bEXs9ixsCpWmNT7nyiwFCANNoEktDHw6q+jARzhxx/FQq/664wKLjPqSuIK8TdYRX01jW4qbxqlMBE27AQSLOjV39LbOuzM1bPhzPXnG/9+0Ysnv9JxdF5AZHZgopcVAl/fc6TBKuilAdM2hY+gan7SfIM/nr3D1DtkjKCIaI5UuPFkDfjTTlTeShJYmqseWjbrivojhIrQqkXDT5jPFqZcF6PpqoCG2RUyLKxW85nBKjqwdKdFiSHMFCPsmhUIbX8hpRez69oUJlYVQTdYRZ2raGtu0JKkIew8/8yVD7AYWedidi15FOW4coNV1E+F5AxCpRkr37lKL2aJz5isit5cz6QGa4AvMkFz9dJs8x0xYJohNHg8zeUbLGaJBVEPIFitburjs6rBGrBSqLmKrEDAFDLNWPkrgrEgxmzKClX/2/GswSpQ+Yuh0tPwwuyucoGlO60IVsTLYA3wRbHTk3E9H9cts2aCQoKlQMdSGE/2XQqL9IBU9y6EKmaCwuyxeNbTzNr7CyIfNxRdKWTbiypMunlXsPDJN1h6mqVr0Lyl5216Im+wijRv6XED2Su+xinMOVbMWKxKz9t83DAIPpKJp+26ZS7MaZaeYClSemyrn/HoVw8Gq+gKom6TNWz68iTuZvL9rjC+H1T049beYBXdpd88LapZk1n0AxihsfQoXvT+n68bmDGWP2aML5ri5z0uhUUNlmYv3Rrrl6IxW/TX91jxwFZXpZ/xRE+DNQjKYvyMTh/ytTDlKkPxgCDXm8dYjtVH59WVsMKEPAOWAljxUDTmCdr6YljLVt8/Yom7KB2NN5ha+LDod1oJLIJcZfeMHVN2OqnKHxlQLLoDy4yfuSuz55/238Vo0snGiZMfwz7iy8m/3Dhf2yg+2tZfMZramZHZ6dX1JFVTo0ZsWX/TyOE/vbJpxO+086H5Hj/q+MrWJZmLH8nsGfLofixrd3d2r/sw+/PHc1cOvXXfa5fftrN1xeHFbfPu3/T2vqkT6K0r39VY/xY+2J/Z2HHOs93YGe2Jre9VPvkHvXhOv+rdzTM+YoRJXW++fN9LOjI+9I7buD198wrGwbP3u1SY+3a2s/TFP1XIYA+UEkhCC1K0l03Ze6h5HdBsrjswbMt0hUyxACYUS0SQEZgLHOOM/KoU7nlJgEX2ok12IaMQ8q37D9756vsAAWoocChSUfEBL0ZgNM2IwESu0pUYrEQpYaYMkWkiZJqNNEvFvBUx1SLILIxW+M2AwRoAJcxgQeDJJYoIpY1eRUpLp8LECPQycqnBZLBylkvdA5GTsCtYWOjVLFg6Zc5g9QNqZB3aaNwtlXJmMliD5qDEYFmtBstqsKwGy2o1WFaDZTVYVqvBshosq8GyWg2W1WBZDZbVarCs+de/AGEOk6TV0Z+gAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyNDozNy0wNTowMLWNyNUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01XSS5zdmfFYhSWAAAAAElFTkSuQmCC"},"157":{"admin":"Namibia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAH5ElEQVR42u2dbWgcRRjHF6XQYrURgjS+FCU0WgRT1KiJLzUGS2tQSWNpLFxCUwVtUZs2JvFLTH2tBmnOi9YGi22MWtqqWBTsQRG1rdKUSqutZzSIwURCMaZaS0MhCve/D0+Ym83s7czs7N58eTj2dmf3bv/3e/7zzOyc4xSWLezoslF/vHBG2UvtS+S2hlhyW21FIpa8rj/+c2zyncnyyUYaz17aX/79Db921m59InF8oKD85hXHn7rkghtbMq+9Rs6xjr3B0YiXjSw+0LmpwenY+PEPqfjoupGfqJjOr/xj2amLxuv2pPatGugsL1wxOyMIcUl5FJ8Tll+k3PMGdW0qIviU+H1Xw+FrWT5NxAePDp397auGhtbuHJkkl1g6v/qw3Gb369T5Kdz5hJiFTwrIxCVWlH67UfVPPD6NfT3+0elSlk/DXza/9UqPXKF4FpYVQViiZz65S0qd4NItO5YEJqdmEf+U4RMVExVN/hCrqLpqS3M7Yj47Nv98Ovnn/IHFn2eRVLDEUiesOWN3HWrbyHt3XVNb75L5r3W/uqi81SY4+mPwwKdAaRSYsOoa1lQ8dMfj7zd3V29iBbfvmr764i3fvf3J+Nxali6QHVowk1uyWkY76N89cN+GRR88eyT1Y9vI8xL8k4LygdD+5NocFV9udXvjeyt3jTUeXD5zAGSCpKp2x1rqz08mjzY57+JdbMG72BPb0YLlUxY+8W68rIJCbpJNHwXpn9qdONdXqYRYpUU1Tz82CIn899mxKieT+EAjEAuRpkXsiaPQQvTElCOfVCcv32dBARaDRfgUjiwbThkGAiHZ7ezoWX39GRAIcrllYvmJR29HXFB2//Dak+AWTZGstVdh+XXafHc+YcglR/+kPy2mt6PrAD6xn8iXsCALSiDIiPok7AMmQTRgEiK24F3IDkdRkmEftI/WwsWqwPyT/3Y418PyicZk8dA/R15wZFl1NvFBKBAEtrtH7AmSscmRZ+fNKS7QK/Hsn/TYah/ycucTyiItVx/a27Om8KZtvTVd0jwWGAO6QBygF8QBw04jlRTdjqPQAlpDy5H1T4HWx0XeFeHTrZfvaVqbcgreiFfdmYlyy57gCjwTZRhPRqzUaG+RJtPwzi/I4p+C8kzCkfKJHZEcK/r3wJnEFD5RSfkRFi0fUCeE2hUYIy4pNqIFtEZ9GEimrmrvNeW58+nvv5I9Bwsk+CfVUiPtYwIgyyeIicsnWcKit5wKAoyhDsmrpBDRAlqj23FG/+VT/ykvM78g/XX74lOwxBLwTyDWNHySJSzaK4TIYL1pzZ1Nc7z0xyMWWkPLaDPYtOiZTxrFkZs0eXyaxj+pFhbrsXD7WY/lzie6D45CykNrXhOf3H7ilPlPrnw6EZ/Xe/ebBg22CPAJ1y+BT+qEheQ1XPLFvRe3sSUDdz7Rd6mXQmtBDVd74JPBNlzEP0ngkwphIW2BNLRkgLSF17wSA33NO4omRz1TfhX6J42Vca188ios97SCEihEwNbc8Zr1YeAQradT/4TXtAXavroxRAl8MqdWrp9PcomFogM7+4qOFdJ+HKQDcSBSMdH9WV/FO5d/PsVn75z37X4hPuU2fqdxIHkaPonUn0wQlsjsBjp5hlbnaVWdTphRN7shx/qTuDj0Oypz+KRHWHQ+FmiEmwpLTgWE/iOtjSFRyq1U0fqTCJ+mTPkN1icJ8Gk09vI3W0d4fGp/8PDc7euV80lcWH467ZAFnayH1ugMUkT2LDjK/0Q/r3z6paRyc/09Oj2Qn7kGxvGJJywV6wi4V+3ZicsK+cSMdmXhk5Ezx0PDJz2pUKSUKteGe+XT4PalTz5y2qD+nWv1HFdrNJ9MEJZyPonUx00oDQjUn9z5tHnVsdIPZxjBp+gJC3ziLdmjiU/+pUkkFUo+hV1Y4v4Jv3VpfFJMKVxnaPxTbsIy+UliaXySKxGvVS6m/oQrZz9R/8zRidS5pZWfznqmJgRiChexItW/S8cI8ilcwqpYv7p1W0Krf1KwaCLbv4sgn8wXlrh/4tbHlT3p6znxRaN/F3ZhKeSTzsEZpn+XF3wyTVjg04bnuvYnZ3ngk4q5A5KGa0T8UwT55F9YsvqM7v07VHEyfFK9vookSuU1n4IiFq0/ZfjEqY9P459MmJ/JRMunwITl7p+4S9qrGzbxfazlUwDCysKn3Pp35jyYQM4uwqcrh3bE6hbmqaRYYfnxT/TYgPnkbu29Ti9OR8zWsnzSSqwc+WT8/Cf071DTt3wKQFg58sng5TEsnwITlgif8KyIVj75FqLlk7Qo7quwp0I+qXigSuCRTjyN486nh/cmh15stXKRTCxpfDLmkXN3/4QIPhUv67siNscKRbKwKJ/YkmYWPpn2ZDDHP2FlPeufAig3eOCTwf+PYPlkhLBE/JP+v1T0syf8kzufrH9SEt39E7aEi0+0f4f1idn+3Y4FqdeTV9n+ncKY8U+MpLLwSVZ9XO46nLZ/Z2YMcf2J4ZOtPxknLOX+Se7UYcInnn/Ckvbo3+XdlBUTYhb/ZOYqmgL+CdH6JyOihFkAWv7LRYRP1j+ZJ6wQ9u8on2z9KVrCUvE8sUf/ZG9hPhFLfMCYcXWWT/knLD9FAc7KKiyf2JKB9U82FQo9xmn5ZIUlm1ge+3e2/hRmYWn8yzIUYC2f8ptYkgRn+WSFJXnJHkwA5PEJSx5aPkU1/g8yoj2cYhAXrwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjU6MDYtMDU6MDByt6+8AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9OQU0uc3Zn1IVJTQAAAABJRU5ErkJggg=="},"200":{"admin":"Somalia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC/ElEQVR42u2cMUscQRTH9zMEUqZJLeQTaBDEzsoiINoYxMLWQusgGpLGQkSCgkmaQLqksLGRxMIiTSCBgEdEvOh5nEiUqAhn8W8GJnvs3u56s/t+zb/YO2fGt7997817sxcNDr18tV8LQQfeLzb2p8JZD5pFI0yAAhYKWChgYQgUsFDAQgELRQGrxGqnVgdYIA5YKB4LBSwMgQIWClgoYGEIDvAUA1ZaU2J6FI+Vg/ca+bDc//uhFJsAVm66MPfppPFAijUAKzf9MvHr8nJUijUAK4cgOHm9MXdYP1xtjd00pLqCfQArE1gKf+3h9ut2TaorbF8AK5NuTX9f+vvEBUtXsEzJwArHEzybXR09+KHw54KlK/oUgPBYqeGe3/q49ufaRYqAmNwdAFasmfwgWN6AeP8PAGD95wbEBUE/IKpkit8yAZZuc3L1/6pzEHRV30w7oz87YJWg8aLsZ31z57T1vDv9Nn7Q9+9RErD0ze5mefvi6/nZUFWr+VH1fNXM3ruLo/nkcPRKtUKtFrBKlifJK4SGlFZV7fzMRPKuTOjnm/rx1W6vYFKyr5WQvFew66dG8n0ipcKEZue9wsruExWAVp5uf24eNesXj29XioBJI2sWm6e4Iss14iJCpEbzyxCAVbPmw9yDMdmzKGshr/RgFXGr3PJEXh5Lo/UWrBCwjmjg5FuS0Ghl91jZ128aLKXVReRYvHYRWc6uFLaS7A3do8lJ9oMhBETA6hle6tl1BkV1L6XkySthGpldodEgGNdPlNcRHG7jxa2E6dM4b6eRLQfEqEo7kbQ7QR8Lv/HS+f+Kq4S5ARGwDAVB1cT9xota18kfErfh7Z841SyEQkOqUOU3XrqDIK5ZpFls/tpFZM1XuQm4u3fL98ZrZDfxBywT59nThrwsL5DZfFGMyvtUlVor4fhF3tJBAQsFLBSwMARaMFgcT0PxWPxGMmChgIWigIWGW+wFLHI4PBYKWDzBgIUhUMBCAQsFLAyBAhZaUrDYtVFLw2OhgeodNePDm+0EpMMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjQ3OjI2LTA1OjAw3NKyvwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvU09NLnN2Z+iG1TMAAAAASUVORK5CYII="},"224":{"admin":"United Republic of Tanzania","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFG0lEQVR42u2dWUgVYRTHb7m0mOVShi1WpnTbFyVDKrCghGilhYJCozLMgpKgKEGkQNIkHypBSdDSbNHCNLNF0zYv3cAeooyghwqsiCJ6aX84PoxcvXwzd2bunPn+L+flvjje3/y+/znz3W8cMaEN9cmLUKk6K24GLMjl8tcmvW/6ND+gxvl4xuTyH2FPV0YV/Sl9viFqcH+1LqJ41ohlsd3jvgeEOJIcsxxBBlbAxLFuHHCnfu74h69d7pif3pH6uOFJdVRNjjPDNbQ7On5k18A5hiMFsHhV8lNxw4P5U7JE/HS7qPRE2NbFcQn5QZkmwWQdsHgtPVbwk3eY3iXf3Rb5xQ9+grF4pT02fgJYdvKT3/ITwIKfABb8xCc/2Q8se4R9pZ9oORPx07JRyROCx1kaJhiLS37K+5Z1JiSTgZ+MAwuDA+//mfzItn/O3yJ+am0pfxXWycxPitpzG+CLh5/0qvO2T30SuLPKfWLd8CKABT/p4KeMzxtnDwmn9oKuC2D5zU8FO7LPDnNy9xM92PYckTiwHwF+0uanrmsNmZGj+7tSGMsnPzUd6BgzKR5+Ulb6FGDBTzr4ieqLU66OxD2bH9X/PfMLYAnV5a7mjwl71frJpC11Bvd33q+X/HQ2v2XJrkWxq2rc7dEOR2Xw22MASzc/dcRWdYaflNRPBBPV6Mrzb14DLN3yE18/TdsyeULAdcpPGv0EsPT106pdKYHBL/j6KS1y9ZXB6ar9ROgoMfKs2tp1Oz3AUZufTiceaQ4N5NvfkZ98yk/ekZLZWHRjHE67Hz99mwx+oupTfvKOkafD0N95v1/hJ9VgydMVwk+q85PvFX7yzE/c+zsRP1Htw0/cwTIu/iv91NXpPjj2NPzk6af0qIb1xfOE+jsYS1t+soefRH6CYaCf7AeWNj9tKkm9MKiSr5/Uzp8M95OdwFLrp7KveXGh12TIT3S95z60lqbt6/GT0RhxB0utn56X1dZFpMvpp7CcquzOCpMw8rQgLz/RkT0i/Z2d/CSy/6mXn8xZ7PgaS04/0d9P16I6P1mncveTsr+ju5yvn8iyIvMn8tO0xstb76y3HFJWAwt+Up2frLDkWRkspZ9E8gTd2fbwk8j86eKStjWbQ3r8ZGWYrDN5h59U+wlgiRzJKj5/4usn6kxF/ESf9uEnLjCZD5ZaP9HMhuY3MvjpfYm70Hl098vGewURps6f+IIlW34iP1GXatv85C+wyE/7x7fGzGiEn4T8xHfJMwcs8SPtZfOTrfKTOWBpy0+0f4i7n2ivhEY/2Q8mvcBS6yd68sXXT7TzXTw/Xd/S/nPtGxvmJyPAIj9lNbUUzcyCn+AnHcBS+0og2fxE+SnxwdUFN1baPD/pBZba/o67n2j/u09+khkp72AhP4nkpx4/yQyQOFja8hPfn3Sq9VN2ddPNvEKd85P9DKft+R2dTMJ9/kSnxIj8hLWXn7DMiVSaP4kcyWoPP9H5VX72kwxVBj/RbUB+EslPt549PL5iJvxkCFjwE+DQDSxlf0enUHL3k0h+6uUnAKEvWNQDIj8BCN0q/GRqlQdfvvNx8hOdpS7ipyMlzVOPpsJPAEs3Py08VnuhdqJ0zgBYxvlpZEf10mdj8TUDrD78RG/x0+gnVIClm5+w2AEs3/2UklCXe2kPvkiA1a+f6F3t8BPA0s1Pyle+wk8AS2NdHJeQH5Qp7qfcwtvhh9LQ3wGsfv2U48xwDe1GfgJYfntlGfzEvf4HC+MEawCTOAoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjU1OjI2LTA1OjAwGam9QgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVFpBLnN2Z7vKlZQAAAAASUVORK5CYII="},"225":{"admin":"Uganda","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFKElEQVR42u2cbUhUWRjHjxvVBIXSpoblBluEJUUbBU0frKiYjdqJjAyStnBben/fsIgwUpaMXXa2ZlnTXqAgA0EJ7UNF5GYvIrVS7eaQFfau1pIKQRBWH/59OHG6d8+8NnPP/8uP8Xrm3Huf+c3zPPfM3BGiUpQJF0lGmAwBSbFIikVSLAaCpFgkxSIpFklSLJJikcYy1Z/amrYaf+g8lqluxxb75wa7R3XO8I/E/oxUMj468fmIVw7UPhIkGWGKd13tw0gy0mQISIpFUiySYjEQJMUiKRZJsUiSYpEUizRXrM7ywuS+6fb8r+fnYUMf64yMDV8MLmpI6RebfeHcVcbP2cUyGvoU9/9x93O5WrrHT+zbjcfRIOZ/+IW7xzVAfSxTZx55i/489tsxz4MJnq8yqjuOb/tzUcqd3eWBfUngw5LzKf5JnUXNexv/6OkNBG4l9wys8pQVYCSeJc8W2rnoPFceo567TuTlkaEdpz3b1sxY0f+kiJ5MsSFCBoYzw4vNpdfWl0Kat696t7493fr6bEZd/pGueTkZ9a2TL3WMagLlMSC2YAZIFs7x2L9gVscfvfiExoQXKxxCAuQeWRQw0N4yNfBD3m3vQiHGe77e8eUUSHZz9Z6GTT+q4z9INrDKU1Yg5zAzaahYePdbKfXqyvOmzl8PTizLLM/F942mdE9d4F657pfl3qXz1IwVD3rFPidRrE/w5YnKBf6frLRArjpUUuzZNRO5app7pm+6v7KhcvLJ7+yVkikXR3OUShixdAInj7Ef/6R0+WX3HeQkey1qsiqy/E2zZ40dMmLcBt+2pxt8+krJmQ97ZCmMWFMZn7QqfzLbCtvmtxXmzc07s3iWJ/Pb/Z4RR4uOXD82IVix5LKY6G9dlsL/a9U1OqTtd3c0b78ri4V+K0SxegOBW8mmtfMift4lVhfG4b+fMANKkk7xykmbMS4nDeXPW73wmrcaj2tv1v1b9w06sGD1siqIoS0iRDbmURFLfjmdSpwqWmkdsXANiIYdV4XIYX9lXnhzuQJ6oZFvbL66trFOp2PDUqoJ0QbFxmXDS/p0msDjXetyv9+tk12KB+0ZXTxIvpkJkqHTQgd2rv1UfnUm5vw7a0vN0ntYl7eaEyPNibZBN6yiZ9K/mlu1YuXjVVV4LnIYSiS6LiyW1lRMqk0fjQ890EVZ6YW9805oBxJy6JQtmchSUEq9pVMWC1TX5eW+jWI5kFAhtNYbfRVmyO7OPpz9DD0WFlFlvVAW5efeGNm888ZI+zuVKVbC0zfnt1LfHJ1SCAVBiCVfJ2ILRkImiIVvQ8izYY/8URAjCiIacKulUXRRIBp59FtQCleIEAvfgEDGur3PW5PdIpfajnftBzqqsEeKZQQhhyoWlg+gER7L5QySqSLiwx+1bcde+DNGLtP6LXU93aq1h0CqWPYtv3F9FcWy10suZGjSkXtQHLHFSkF0VEYrRbHUVS6UPFksZCn812q1HVuMW6miWMHmMCiCTIaiBnWgHT7YwX8xkvnpE1xSmztG9AfxNVxQ3q6O0fmvOlJn/nBoNbPVfnXGr7lYkJ6SpFIeE+x+7Y/BKobhxy0a8bEayTvgSN6wSlIskmIxECTFIikWSbFIkmKRFIukWCQZUbFe/15/Juk6SUaWAjdMJi5xb0yin4XzKJz0s0Qm/x6VfWRiHx/Bl5zk72ORpor1efNTsHt3djb9vNEQfAEYAZZCkmIxQ+idl1PPlMsNjhI9fo6TPRbp3IxlprjOzoLvAf+YDWkozYq2AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NTozNy0wNTowMHN0tmgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VHQS5zdmei9Y9lAAAAHnRFWHRzdmc6ZGVzY3JpcHRpb24AZmxhZyBvZiBVZ2FuZGFggYa5AAAAAElFTkSuQmCC"},"241":{"admin":"South Africa","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEmklEQVR42u2da0hTYRjHTxnYTVqLGhKVUQS1yoQKSbMgY0FZliF2oYgMicpArYaZszmwK1MjLbUGQSXzQhfzglSyIrALJVphmV0RNY3MIBJiQa8fDqzs3c452/ue/b/8P21nl/Pzt+d9z3MeBWFcelJMpPQ0tNp6Tca24N7ojn6nyWlxnh46f8a8TH0x+uPFjdfWVrS/CzeELecyzYvHzu9T+St6lIJcYJHUF+RN2XPxUvPTXXcG6PH6XGx5kHUAp0pNn1RmsMS5vancmBtB77Dv4bX91WncOwypNFiuDvta3LW7c/XQeJHHyOAw//EQk7ZTHCySowympXEVcBiM5SWHcVOHKfd3r76a8s/xfQCWzA7zyteE8pwDY8nmsIXmzUf6UVGxCa6Qt6akUl8TctQ8cmWHb/EiDtuZWJmQf5fLOgxwi8FyxtaM0U58GGHXTc1eP+N00ZITHDtMXIdhz8mnrzsIFskex42kSXG8O+ybtbKqrA0OY8JYrsmmw+ylzYfvp6AO4xgslh22P7Qqs+inhw4DXt4B617wqbjxK4fGi02HzT2Wd35vtLsO6/qR3nVwGRymOFjat0G/AjTpUQmZYx3dhaV5Wju9wybqTWtX5bNTh33a3Hem55ZKHKbcu5J+ZIojCIJO0AqBJCPK9AWBh+rbckI17/3UYT49Geq6pCMCi6QUh02emVVvmKuSOgw/lPKCpSaHLUosWJVyzEOHAS8JPv4PWFIcluMsypj3hp06DGtJhowll8MM30/uiyzj3mFYSyoHFkmdoHEGWPl1GHkPxGGS1pKATF6wpDjMUVJqCFnNmsOq17VaH5/xsA4DTEqApY46LMieNRA/nN5hpIWa9FYgXVM2sMS5IiZMNzLMfxyGdE1FwJLuMBb2wzQWszVhurt1GFJxsKQ7LGrY8QlRAyxsVcBhjIIlXkuahW2BQRt4rMPEDmvY0f6suRD5r/QqWK4Oo++tYMdhYpP5Q5JljbvP8hlYYoflTNrREPSNR4ch/5m+BUuKw26nXh03PZMdhyEZBcvVYcRPNA7LyC18tCAbDgNYcBjAYslh9HUYcRgL+2EAi4MkeNHYS4wXTjPAkqHqIlaDsQAWaiyAhVUhEvtYSP8FS8qKD34CWCr0E7mCJk53r755dg1OynO9eUyfdTfQ10/obkB3g2r9RPqxyFeGXisf92Px3rmADlLmwOK3XxQ978yBxfudhu76iTym9qbD8mHL5YTrja9ikeIUcE+Ou356ktaS1D0Q35k8v65O0M2YffYc8i8JP9H7KTfZ9rDJNi1k6YVLcUBHZrD87b5n+ElBsNQ0bYb+Lmf4SUGweJ8tQ2b8wU9MgKWOqaTu7j/BTwqCpQ4/0c++gp8UTHVMHKWfmkzqp3Nbr9haumc1Rm+6nA8IFEnUT0hFki8/iSfx0U8TJX5C/cQEWLzPcH8+9fWIL3PgJybAYrl+ctdPqJ+YAEvsp8EOQPgJKSXZmaAHP6lrH4vD/6cKPwEs+AlgwU9I7sCCnwAW/IRkFSzip+1N5cbcCHo/kV5p+AlgwU9I5cEytNp6TUZ6P5H7W+AnteZv4kIiSSgh79MAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIzOjAyOjM2LTA1OjAwmx7TYgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvWkFGLnN2ZzDEwIEAAAAASUVORK5CYII="},"242":{"admin":"Zambia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEHUlEQVR42u2dS0hVURSGdzmQinxRUpMSg8JZED0oGkhQQU2jIjIoQ1PEUKEHJRFmUeRIMoLIEIIG1iQaXOgxiKBBgxAaRA8ocFD0wB7TCv4OLNme2+ler2ff2zf5Oex7HpuzPtZae+11uG7x4vN9zqHo9CqvAAUsFLBQwOJFoID13+iyH321gIVOizZcO/HQLd+2vX2Nc0dGWp44d36oq825daNH785apF+lye+pa+0d8p/h1syRGuc0z53HW09G44AVqK4d7xhz7tLnveecG/50+L5zmfbmOufGBjteRSOXrx+YHQEnA8f5trYHPbejax8fa/kewSrIkuC4p7Z71LmWDYfGorvZWUk1H0EGWIGqzCmwhIJVISK9M9w0Lxq/caZ1o3NDvd1XI+AExOOLXZedmzjYeyU6FrjWh1mAdK3uJmjinm5hvblpV2YqWDFnoNmVPIT8gTXty/Lj9yLDCy+p9W32TIFlr7LnWN8j+PQse2epj5RU/owcqyiDY3+mdWQqLKzhfQ9nwZJ+Hj25ezJMFkodywPZZ+k+UguWZhWXt2G8oknqlSbLnBYmPzhaH2Ox8OHw8fIBtQFXT1dux6qwBAOlUFMYkrFleD8sWvgU7PwgKC/lq5L97EsEwCpZXfitp8nNF2pKorVG0wpOqPk5lvVYFqbOG/tOxQc4wEL/qAKoMLKrRa095ZOkcRUpwEL/Uhuzx8JI3q4QVX5efYlXxZSByUvZ1aXAYksHzXEtaRN8waQMLEn9HbDQRJCxCY3SNoOigIUCFgpYaN5ptdZo6/vP9k/V1hKn9lpVpPJRzUdzyF8xbco6+HXL+NzKZ5UrBiq+PG1sqK0YyU1Xddd8KBuoubig+bdR57nNk9WOZz9Wlcvfuv5XxbQp6/Dpxltzf75duWR31Q7pm4mlF6qWS/0R6euPdfVV1dLn9UteVHYKLB+p5CqwtPljm22yq1pxpHYc0wYBlo9LHEb+OQJr9fvyR2Xv8gGremLO/slgxUFjxwELsGYILB1j2oBCYXaM0gUriQJW0GD9K17hgGUV0wYaCkPzWHYk7hiwSgQsPBZakBwLsFDAQtMDK/xQmAQ46lgk79PmsfzzMW3KXwiGWcfKJwgCFjlWQUIhYFFuoPIOWGmAlc/aENMW2ZaO30JDuQFNlLwn8V4hgOV7NUIh5QY8FmCFB1YS7DAtbTN4rNLNsbJ3tVN5R2mbASxyLHIstnQC9FjZ+0gxbWqqL4ZDBovKO/1YJO9oMedYdDfwXSHJO2CFl2PR6Ecdi34stFTAIhTSj0XlHbCKrR8LsCg3FBAsGv0Aq4BbOngs+rEKHgr5Ejo4j1Us/VisCik3zGiOBVjBdTeEnGPlFhYxcMqq/3LOzVdZpdyATnMo1J8PhPBdIWAFnbyXhsf6BbYQ7ys0lpLUAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMjo1NS0wNTowMGyZwHgAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1pNQi5zdmeyhqY6AAAAAElFTkSuQmCC"},"243":{"admin":"Zimbabwe","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGEklEQVR42u2dXWgdRRTHJzS20ZjmUnKTfpASiQqRYkJRq1YtiBhCY63GVqW+FKH3RWmtHwULFmz0QaykRLQW0agPlaapF1uEktY+NPGjKkroh8QQS9s0aUJLmsZCDXiF+78P5zKZ5ezu7Hb35hD4s+zdzM7O/vb8Z8/M7qqOBZ9f6KituVY7t2Z/ck/jiqJnGna+1z3niaV722+Z86+ortI+HFWZ3ZnNmU3HHj/2SU/D8omH1i7vSC5a8ltp+vY1G5cVn/DWiHabPjonMgo1iSbWtFZYzoEFHXpk6IGh2g13bNiSSpePLPyp9ADwcj4kt+tFowNZcOcoDyzo1G1j4yNVH019/NKuTxG9wrfIQo15hXfhmY5iGrCo2rVIOW0zro/lrM4WKaZTGDWxWzILLKqwSHoXKVenqAWwprHILF6dZ7/aV5Q4Oa/37aJOk463/jhfKedtwlSpT3D18QiWbpF3/3XXWFkq3da2TqnMaF+Hull0JqsvsEwWua0oNakUeJcmFrAsKLXI1eOPPq+6Tv2d/l3wErCsqFikqGWwLm/8Y/3BX8UiRa2Bdb395I6+a+ePN6Ye3I5lsUgBi4UOhnpGjxyferUeevXJ4esnPsOvo/2v1KcWDzxWXVH2IZY5Fvnlwebm4p0XVz+3uVSJxl37W589VNWAZRdg5ZA69N26ZN/VA4cfrkxN3N/z1NL5kzd98+be8rPbl+2u6xlcVX1nogXLetzSLbLuXPLpilXru8r+mT0JKFECR7H96U31leUvDv5ZsySxgKqxNGzD3osv1WpFNVfzwPZO2+dMf83Xia3Yb26ZU2dP+8UeFb/nBIz+m9d7qqqR6vDs9qaqe1AcDsY5bpkssuneki9m3de9duHKWw/TcpxPT+DKQERUV8U3QRjfheIfRlItOl4IgxQITtzSLRIxbNfKisUlmbwrbAaAZTpSrI/CZcavg/J230eRgi2a4talI617tv7iNtGqW6Sp0Z1/xXqqbrcPE1PLALEviSCO1CNY6GnR/hbFi0YvxC38FyKf20QrtUhvTeAMk+vtA4thdoHmlEP7W5yLjV9PRc0OtgUILp5Ov9Y+fPn9D3rfHUFvaXjRmramn5FQGPi+8+WKc7ohQgGcjhcU5aBMxDN0/6GoA+pjskgaEfUYY1f5Tcz/dSZoHlg42fpdAz2RMLsr2/YPVDabwDJ17Wk80Lvn2C/qoMc2apGIYTBK0Wiq0jvpiFLU73Ox4Wj1G4l3gAiMj8YnE1gmi6Tg5uwyu19nu6QWqUrlL8J/plMIY4LxASkasWi/ioMXhQx4IUqhfOzL21iknMKYgQVFigH2BKSAhQkmrEEPTO/aU7zOvLVjvHaWPrboVvFcJCZMy+mMDVh594PoyBNc9GgEpKjSX6EoAfMMB1/o2tJyBRHIzzwwASvGYI2t6Jvb2qBHqfOv7+tMfgvVwTIlJrDGW8QSKywosJBzp6AAHYoL1lDI9AiH5Usl3RN1R+kwttv8lpy8QrFCLTWqxyeOYsIMTJA/oETndUm6IWbpBk5Hng5Fm/BCxAJAevTCGsQ/b2OIkooMXzmjBdMkSF1M5ctiAbCQJsUaGskoQCb4nHtXxiGdbOKDM6aWO0iyPWeskDMA4n8sLy8F7bscW/XxVo4JQdd3hUh10n6VfoforIh5pt4Vtby8QWggooHCR8r/GFw4Y3n808xpB//leIPSzUS/bDYLKU1M+sN9IkexPXJX+qAN3/IipJaGoq2hT0YyOObldhoMZ9ieptBdgIX8uNv7OL2XRvPszrMYYgCWf7wICpwpPf7BCm6eFtVAHv/i3+Xp867yBqe1gfBprND3dGGP5QSsnInatrZhtY9hsrJ+1m4AWHYfpoCxFt4jCejFxv2RipDAkse/5PEvyyoPrApY8oi9qLwURFReYyQNLWBFxfIESv+KGW8xA4vzqkjTiwbp+hv1csQw9xuF4w2zffC/ys+MTflEiqi113GH87Z3veQo44u6yQVm4QMC8r51UY9gFYblxfczLWHWIYiaKz9foJD4Ierxs3JiZ/J1MY9gmT6EGbUOaaHiGOan4YJuQ1r+/+0VpDbmjbldAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMzowMzoxMy0wNTowMGTBkIYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1pXRS5zdmei61vXAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/2/3.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/2/3.grid.json
new file mode 100644
index 0000000..6e989ec
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/2/3.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!!! "," !!!!!!! !!!!"," !!!!!!!!!!!!!!!! !!!!!!"," !!!!!!!!!!!!!!!!!!! !!!!!!!!"," ! !!!!!!!!!!!!!!!!!!!!! !!!!!!!!"," !! !!!!!!!!!!!!!!!!!!!!! !!!!!!!!!"," ! !!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!"," ! ! ! ! !!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!"," !!!!!! !!!!! ! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!"," !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["","13"],"data":{"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/3/0.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/3/0.grid.json
new file mode 100644
index 0000000..db1cb87
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/3/0.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," ! "," !! "," !!!! "," !!!! "," !!!!! "," !!!! "," !!!!!!! "," !!!!! "," !!!! ! "," !!! !! "," !! !!! "," !!! "," !!!! "," ! "," ! "," !!! "," ! !!!! "," !!!!! ! "," !!!!!!!!!! "," !! !!!!!!!!!!! ! "," !!!!!!!!!!!!!!! !!!!! ","!!!!!!!!!!!!!!!!! !!!!!!!! ","!!!!!!!!!!!!!!!!! !!!!! !!!! ","!!!!!!!!!!!!!!!! ! ! ","!!!!!!!!!!!!!!! ! ","!!!!!!!!!!!!!!!!! ! ","!!!!!!!!!!!!! !!!!!! !! !! ","!!!!!!!!!!!!!!!!!!!!!! !!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! !!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!! !!! !","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["","185"],"data":{"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/3/1.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/3/1.grid.json
new file mode 100644
index 0000000..153a10c
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/3/1.grid.json
@@ -0,0 +1 @@
+{"grid":[" "," "," "," "," !"," "," "," !!"," !! !!!!"," !!! ! !!!!!"," !!!! !! !!!!!!"," !!! !!!!!!!!!!!"," !!!!!!!!!!!!! !!!!!!!!!!!!"," !!!!!!!!!!!! !!!!!!!!!!!!"," !!!!!!!!!!!! !!!!!!!!!!!!"," !!!!!!!!!!!! !!!!!!!!!!!"," !!!!!!!!!!!!! !!!!!!!!!!!!"," !!!!!!!!!!!! !! !!!!!!!!"," ! !!!!!!!!!!! !!!!!!!!!!!!!!"," ## !!!!!!!!! !!!!!!!!!!!!!!"," #### !!!!!!!!! !!!!!!!!!!!!!!!"," $$ #### !!!!!!!!! !!!!!!!!!!!!!!%"," $$$ ###### ! !!!!!!!! !!!!!!!!!!!!!!!!","$$$$$ $$$$$$$ $$ ###### ! !!!!!!! !!!!!!!!!!!!!!!!!","$$$$$$$$$$$$$$$$$$$########## ! !!!!!!!!!!!!!!!!!!!!!!!!!!","$$$$$$$$$$$$$$$$$$$$$######## # ! !!!!!!!!!!!!!!!!!!!!!!!!!!","#$$$$$$$$$$$$$$$$$$$$$########## !! !!!!!! !!!!!!!!!!!!!!!!!!!","#$$$$$$$$$$$$$$$$$$############ !! !!!!!!!!!!!!!!!!!!!!!!!!!","#$$$$$$$$$$$$$$$$$########### !!!&!!! !!!!!!!!!!!!!!!!!!!!!!","####$$$$$$$$$$$$############## !!!!&&&!!!!!!!!!!!!!!!!!!!!!!!!","#####$$$$$$$$$$############## !!!!&&&&!!!!!!!!!!!!!!!!!!!!!!!!","#########$$$##############'''!!!!!!!&!!!!!!!!!!!!!!!!!!!!!!!!!!!","#########################''''!!!!!!!&!!!!!!!!!!!!!!!!!!!!!!!!!!!","######################!##''!!!!!!!!!&!!!!!!!!!!!!!!!!!!!!!!!!!!!","####################!!#!!'''!!!!!!!&&!!!!!!!!!!!!!!!!!!!!!!!!!!!","#####################!#!!!((!(!!!!!&&!!!!!!!!!!!!!!!!!!!!!!!!!!!","######################!!!!(((!!!!&&&&!!!!!!!!!!!!!!!!!!!!!!!!!!!","######################!!!!(((!&&&&&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!","######################!!!!(!!&&&&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","######################!!!!!!&&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","#######################!!!!!&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","#######################!!!!!!&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","###))##################!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","##)))*#################!!!!!&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","))))***###############!!!!!&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","))))***###############!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","+))***##############,!,!&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","+)*****####-#######!!!,!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","+!******#----####!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!*****.//--!!#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!***.../--!!#!!!!!!!0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!***.....-!!!!!!!!!!0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!***...../-!!!!!!!!00!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!....../-!!!!!!!!0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!*...111-!!!!!!!!000!!!!!!!!!!!!!!2!!!!!!!!!!!!!!!!!!!!!!!!","!!)!!!*.!.11--!!!!!!!00000!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!*!!!1---!!!!!!!0!000!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!..!!!-!!!!!!!!0!!000!!!!!!!!3!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!)!!!..!!!!!!!!!!!0!!!000!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!..!!!!!!!!!!4!!!!00!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!5!!!!!","!!!!6!!444!!!!!!!!444!!!!0!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!66!44!!!6!!!446!!!!!!6!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!6!66444!!!!44466!!!!!6!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!6!6664!!!4466666!6!!!!6!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!7!!!!"],"keys":["185","","43","149","228","113","179","121","101","147","24","223","235","215","124","174","118","91","76","156","143","99","119"],"data":{"24":{"admin":"Bangladesh","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACn0lEQVR42u2dPS9EQRSGtxalaIhGSFYk66tQEcuusGyyFY0NiYpOIhK1hlqpQjZR+An4AVQKttSpVEKikChexSTiI+7dufPxNE/Bunfu5smZM+fOHLlc+8ZGtQphyuQrgIgFEQsiFl8ERCyIWBCxIEQsiFhus/N5fW+x62eiCGL9ItD0yPLd/MXOUqVv9vW4d/KxdH65Nb49k79ujr1Mb933DL8Xm6J+IuqT+itdAeGiFquQr7dVbvavyrXymxR5GRjonppIwqeDwa6phq6mKw9trjQrV2gUuFimTA8dhUaxnlymv6imO/bfrq4tzKBUUGKt9tVKc6OayFot03fU3TUSxMr5njkpWmQl088xLOpszF+llFa7o9RXaoSR6oVS6IVY1f9NfJqestVLI0csR9NzX6LUd3JHlNq7P0Qt47Nd8aW7coyiMBHq9Ofy5BjFtOjy4FTXtlPqtCmfrhx41T6MWOVCek7c8kAsLc7TesfnJvV0wZYh3ByW9hG0Ig65E9s0Ej0pYlmitqmEGqtM6kkRyxL9qq0nr8sjliX6nl39fcLVZsMAMy0303YXyqF2srFgU/hQxfKlACGxAqzFE7EoOiAWYiFWMrGU0saTvLMqtMSTs8nd0iHlBsQKqkBqM/GnQOr0Kx0fX0LzSoeX0KTtsW6b8XGiZNtMxiebs9ro1zrqifR0iOV93DIjDVuTEevzMEUY+RaHKRw9/uXj6s9k/ah2Olfm+BfpPNNfTEfs/arLc8SeDg4plxVoCuJ9GyPXjkjQxojGaymv+CJKz+NpFallvM1WkboLrSIjam5rSqY9T8mny6/NbZGJdtyf7bglhNmO22zEbVK/pR03YqXwbwQUgVAHsSBiQcSCELEgYkHEghCxIGJBxILw3/wAd3GLQCDAyg4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjI2OjE3LTA1OjAwaPWF7QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQkdELnN2Z8hsolUAAAAASUVORK5CYII="},"43":{"admin":"China","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADC0lEQVR42u2cv0ocURSHL674L/gvr2AVLaIi6CsoadIYfANJuqRQ2yVgZSk+gIXaaCWChVgmD5AqKAqCIEgIEhESWMHfFAduZh135u7MznzNx7DizO7cb88599w7687P3oyNjMIi8KL29tNwvRyfxTGcELEgYuXBy+O5H0P7DCpitVqLzE1uDi9bjW4OP04PvL9e/LDz6g+DilipItNdbeuk55349+LbQZe7XVj93P+aQS3CVMO1J7qEOLPiU2P37IsbbzQul5yTXr9nN/71TpAWSx6xlKRCDPOvta/rfbVIrCcqel19n98enAonNMxZLMn08PPorrYivbKNgjqnNJJS2V4FFlQsm6qUnkJLLMkY1JKLJZlU/Shu5ZWe0K4kYtkkaGugvNoBUup+Zq/efapjSyToGLH8+ZoYOiE2r8Y0ZxQlGTPHQoil77cGqTlVSttYZdsBSc4gNdOkTv2vOlu6br6KI1aitqQfjf7zin3d/tW+4lEJNNukqbNJL53fpkUkKFAqtMklVpckepnjcI1N+5XQMeV8oWssWxS/OEp5ybGda4uwA4p3DVvUXIiLW96xUhIxA7ESRa/m9ZPVSwsyDABiJVu5i0uFHpVAWdFDrGdSYdQO9cSKKrCYequdqVDXotLqGLE0YHaGaHdHSTtN+P1efDt3UOla7NkqkFjNt3RpqOyaYFwXyu5EsCV86E07mljYzrvdDUE6LmjEUrKze6GSx4/Qc8O4hojtafHgV4j349I3HqPvfUtvRQMfenHaLuyIzEnZ856ZWNJXKkf1X4keEEWs3J7qKeszx4gVvJVAHx+xMlhUtglO5XmINUdiW+UiVtTm8J7SSb+XC1ZaLMUnK5ZtzNJhR6xU+ynEEA+ZweqJ9VT38NsNiJVbWwEiFkQsCBELIhZELH5kDGYlFjcXErEgYkHEghCxIGJBxIIQseic5XZ+xKLRilgIhFiQGosbARELIhZELG4EpTdiQcSCiAUhYsFqikXxCx0zLEgqhB2zAwKxIBELIhZELG4EExfEgogFq8xH2u1W8NlacbkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDE5OjMxOjI1LTA1OjAwUrdXngAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvQ0hOLnN2Z9X9A5UAAAAASUVORK5CYII="},"76":{"admin":"Federated States of Micronesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACCUlEQVR42u2dsUoDQRCGg/gMlr6CpLGysfYZfAU7tRVELS1sfADLFHJ1mjQWAUFFEIQgBBE0qUKwEFGLvxk4dhNzpzu3fM1HuEtgb++72bm9uU1rd68oBgMI62WLLoCIBRELIhYdARErDU+WuwfDtkhvIFZt7ExvO6Mjkd5ArNrYPxu2Jw+P3dH6+x29gViVhjzxfPtq7WX/c+dr6Xv1bTLd/LjRFu09PextPG8xRCLWDEoaRSbJFKe+qV/Re4g1g4pDcb20V9+kxxDr13qFxGL4Q6wFedG7Xnk9ttmVqC3cISJWpTtB0abzGgS1nV5CrAUjVmjII2FHLIhYELEgRCyIWBCxIMxWrPxmwPM4osaLlVOlVE7HkolYethir3V9jk9ypopGtvDGPpfUURSX90/jMWIlpk6DrTgQdZJ8PnhRq2wL7VNIxEp83SsahYpbyjHMW/utTOWCHEXi5uZbDRMrLpOl/+vextp41ZeOGrGIWHO1P9RyKxMRy1GOVc5g/OdYVjJyLEdi6STZQuHQ/ZefWGtbpXZyV8jczx9mkE3MqJh5h4gFEQtCxIKIBRELQsSCiOX1vcI8ZpsQK/GaWLwJjVj/tHYDEQuxWG0GsVgfC7EyHv5scXOuhXiI5WINUptj2VIclulGrEq0r2nQG4hV8xDJKn6IxT9TIBZELAgRCyIWRCwI5+EPpAZW8PQSpoIAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIxOjQ4OjU2LTA1OjAwmtaM5QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvRlNNLnN2Z6l0pLoAAAAASUVORK5CYII="},"91":{"admin":"Guam","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA2EAIAAADuVne8AAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFZElEQVR42u2db2hVZRzHr1qQ0CyiVQb1QqPi2jLLZIZbtZzlYi3806ZZ1mS01ZTbFJO9sCjNam5rGxW3rO0O8xrrUhtTSCKihjVWjoRCM6U/Ug7StkiIJAj67MVvHI7b3d29O/ee75svh+c85zxnZ5/7/f2ef/cGvjudO/PeNql0YjWgVyBNOljhQH539cG1ywubG5+1Wl5aOLd+rz32vjqfP/G7ja91r723iX0e7gY5rmCVPV/Y2vJ3YOqDZV8OSaVjV/ByBYvTqX+s6edK8j5f5s9/SWb87ViS58CSyrGkUoElFVhSgSWVehIsP/cBM/VdybGkAstLWrx/yc7wSUZr5LgCKyG9pPqB6Ke312QX1G5b1ft+cdeyA/teX/5rKPLSq3cGN0/Jbi/+5uM8LwfHVH4ANEA6JgWa+oV3lW0cAqbaT96841AIpSQ6J7+o4uqc64u+iPXpjaWNY01WuAEpplTfe6Oqpm7m+k3v7jg+veKDzvCZNWjo0mjDT39ydqLwSvfwqlA4ClKEOaABoEeviu3/OTa/OtL74ZMcP3EqFjr1lMULD/OzewksV7dwQwoN1rdO2/3i/YPRY31b8C2OH5rdcc+RHutek5V7CSzPKeuKcB0b+IAGpNBZ1+0qDx+3JdThKvAC0FQGQS+EUYE1QgleNj3Hn3AjlCB4ZSg8d+euGSeaLtrUhAIWZ6nJHeg/Mjzhnzc5Sq/QPwv9+JSTpL/279Ybop3AgSdZBaMF4dCR1TehlICaDYj4XGNw+/zYb34Li3KsEQOeeBUZFXCgFjLrVYBlA6LFy/YZuTNjYH7oWWcUWON7cdarcBeAoK9n+4A2x1r5Q1Ow4xqUEhs0qW+HJJLhW14ekpBjDd29577a9tXWq5xg2WML1sZZLTWdi23azrXUtPchnacVWszsSXGBNdwHpAfnHPYECwsWngRS4AVSFkGnck9aSU1AnFw/GwWsZCTvXjNwJmre/mNLyTsl1mMIXi+/Vbf2QBYldhoHsFCSfcqpSQlqByxoJd4BCG2mSOOeIABZrwIsm3VxDC4WLHyIcsACIOcUUOSf5oWRVbSY2WsiiAO+DoXOtB0FNRTU8CRKLFjWyQDLea11LFrM7HlDrW4YIjDZUGi164JDHw2sa+jsuKV/ERkV5d05vbedeBoH4tgqV9mSX14YWD847+i0HfuaG/wQCpW8D3+2cJGbuyofb6+yCiJgAV7WqyqLHusr/8wGTXvWKmfpFRImMnvNiMAansZpm5LbU5p9Rf6SHzdsDZTnztv8LeXWb1DqXPxc7ukNrYv/Kvq9oJRjrrJqy7mKVpIx3OC1cCmwRqTwwwMB/6Mwp2XFwLqq0GBdpKACxZ8op04we1FOSQR1AwvlWgZI/bCUWWCNGCbFUSwQFh3UeRbfytm29MY1X4MdUz27u/Yunf1Vf+DgM9l5TEV7c+41GaAnEazzP67XPrU8D2Nake5Hala8Yr0KUGwQBCDQOXxr7OzlR7efrL7wsl4wOtd4JmvqtShn8SpWzWsSus07aWYq8y3cxYnI2Rn9D2d9D2qA5YaXrc+bzKRpHIGV0EqHY4crexY0AgfoWA+zmRZ+5kSQO5C3+W2LmHIs17AIEM7eIsfABGRgZ8ECKToE/gl/AiuOzRQWL1zKGSKdSPl5tbtG3uPeqwM0Tphs4Es2Ut4Mqc6nkmPFsQfa4gVMFikCH0hp073ASmiLPTq5W+zTEix9a7Kb7etLQeRY+r4ugSVVr1AqlWMpRAosqcCS60gFllRfCiJVr1AqHc++QrcfwkzlzzrG28r4nspZP9l/3Vju7Fbn/OWJ3Dnx90MJm3X1071S/Sa0ND31P+Zh4Os2F7vbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo1NzoyNy0wNTowMAbhCgUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0dVTS5zdmfZWkwTAAAAAElFTkSuQmCC"},"99":{"admin":"Indonesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAtklEQVR42u3WsQmAMBRF0URcIKULZBcrV7F3H8cJOIq1ioIjyK/knBEet3i5tVJqTRCqMwHCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBZ806fpXK/dEASHNYzLNh+GIFa+X4bAx0JYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8V8P7lwPhQb9oxAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAwOjE4LTA1OjAwUGem+gAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSUROLnN2ZwZPnKAAAAAASUVORK5CYII="},"101":{"admin":"India","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACvUlEQVR42u2aMUgcQRRAN5WlYKNI0E6xvjSKYMBesLZKQNDy2oCCKUJAUl0ToyjBQjAgwkWOCLFIIylS5CAYOIJVRI4UKVIcKXIRXvNlPDBkwWKewiv+zs7i+Pj7588W3e7GdmVaynJZuARSsaRiScVyIaRiScWSiiWlYknFkoolpWJJxZKKJaViScWSiiWlYknFkoolpWJJxZKZidU6bDySslwW3ebVb1vKcqlYPXk2eTb5o7M/tz/39V6tWWt+egqJcNVVUqxb8ah11Pq2OD86P3rweKw+Vn/1BE6tTq3uvoUxzkjucvUU6xovji+Ofz1YmVmZ+fAbXRYaC436OJnpvP+8/+dOSq4ykruqg9XB9w+ZzVVVrDZCIMfm5ebl53HkgFGmGIljuCvq5apmLRZCDHWGOrU/vM6iLlRRp8Onw9+rKbkaxzMDszGzYmX3Z5N1KluVrZ2T9bX1tY9LMRuhDqKgSCzeiXCVkTGHMRszE1es7HIVL6+Ye6JSKMKrbXlieeLdS0gEyVK9mC2+WBUrI1J0x3I75iqEiDL1InqleYurPEWxMiKNA3aCadeKeJSDyglGsRgZ94/MRpynKFZGzQVqoCgEWScVa3ZkdmRvurj6ebYBiaRixbxFzuMpeTYgMs1Y/MvRIu4BU7HgbTJW7MWbsayxbqixqJzSst0aS7H+eVeYNhqQJpUJ4dgzMjLO4K7QPta1PlaMx3orPYRO+1ixTWofy857G1GonBAovhb/p/POzHbesz7VisfP8awwtiHiiWEs0omgkWeFitVTL/JN/LohzUy9vm6Ie0zXU7H8Hkux7u4LUkpy6BekiiXvTqw3r7/s7jWkLJfFwP3nL/oGpCyXiiUVSyqWVCwXQiqWVCypWC6EVCypWFKxpFQsqVhSsaRULKlYUrGkVCypWFKxpFQsqVgyK/4Fclp79PqRQrsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAxOjEyLTA1OjAwG9WSigAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSU5ELnN2Z+1kp2cAAAAASUVORK5CYII="},"113":{"admin":"Japan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADEUlEQVR42u2cT4hNURzHr0dRysbGYnZSatTYWMgoC1KMlYSkqMlm7CykbCg1WVCmrExKUyjzyCuUUViYjDAZShopmklpYprXyGCexXf5es/7c8+553fuZ/NZzLx3zz33fTrnd37n3F9S+VMpV+YhTJcJjwAiFkQsiFg8CIhYELEgYkGIWBCxIGJBiFgQsSBi5Zm/188M/Rifq7w59KH36+j10v3Nn1ddWHFt3eTKkx0Xt79f6LtyriTqL/qvPqlv6Qo8ScSan50c65y4+bH39O6B8y+Xbl080v1kePVA961H75bNbhwcGU4KGxbrU5/Ut8YebCoeLEg7XRmxctThmYWHx0f7Jzr3jZw4KyEaEahZ6spqRS0iVoT8eerTpakeTWTuZKovmVrXnSBWJOPT846uo3vLPmWqRd1JHsawaMWamhtcXtz/9O2arm07G//hG4+u2h/DdIeIZUwp/1MeekUrlhb/4StVrZfuHLECTRw8W7L29a4trf3AfibBWtSdKx+GWAElNrWwtzJK1aJ6EUei1bxY0zeunrlzOdvxJi2qF3FEXYbF+rVnuv9bQflunz+861bUI/UOsTJb/cUxVlXT+rhlWKxXf3fcPnYg/BGoNY4Xe+72lRArgzVgummF0CRT7+yuExO7k2B8019ME6JJsbShmwex1FPEMhNdWaF6ilie0qHuTis0Emn5jMbUU4sp08Ri7qrZMwt2qa0ei6e4jImlR9zOniBiIZYnsZqd2up/Pt3JFLHMx1jtR04uYi9iLFaFDleFiJWLPJbPVSF5rEgy79lu7FS3TubdK7WDFuYR5LSCd/YKibScjIicbuA8FudI4xJLayXVXIhJLPWIE6S5G7fctaUr6xQ/Z95z9JaOa315SyfQdaKtPURpKvJeIW9C8yY0tRuo3YBYjl5kDVMv3VUcQTr1scohBO/Ux6KiHxX9EKsZfj/8+N6LLz5rkKpFapDmtGqydh5bqwCob+kKVE1GrP/Uedd6jTrviAURCyIWhIgFEQsiFoSIBRELIhaEiAURCyIWhIgFM+Y/dWVqkJkga9gAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjA4OjA2LTA1OjAw3yWdzQAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSlBOLnN2Z6/gxrAAAAAASUVORK5CYII="},"118":{"admin":"Cambodia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFhklEQVR42u2cv4tdRRzFb5fCwtVqCQhapbDTIiBYWGVBbEQF21QJaJMiAUsjCME/IKK2QrrELoWIpgjBYFaQkIABBQPRhawSFNlClD1bfORkxnn33ix33JPi8Jh393vnvfnc8/3Oj7xh+Offh19Go/NrvoJowIoGrGjAikYDVjRgRQNWNBqwogErGrCi0YAVDVjRgBWNBqxowIoeNH3hg2F46+1odF4drr8/PDW81qJfH3v88JPvznVle7T6364aZ9x9b/5ydP3opdufHftm4/u57jX9W5ryHW4+s7a9tj3v9ezPMK5bB01/unrmwZntX5+9uHVxa8pwHhwNWE1edf/ShbMXzgqsVX0rYEUfYv63b7763uuHf3/i2olrJ6Ryr/jWAQLru4+PXDlyZa4hV7StN8/fOH/jt3uXP7n8kcBSy7y+pXsFrEVXQvKYGbxqFx0lwT8fu/X8reekavnhleMbxzemQ0x8vz21fmj9UMBanFdpeO6dOnf63OnpQ6449Ko/rm6+s/myWoTXqnMrVwGqaP+n6q17sASQhkRDLrxUdM+VBHe+uvvF3c+lalEhP/0uxPfHByfvnLwzHdaANZtXKQlyyKeU2MKFBbs7ll7Lb8b1nKlW8QXZFFgD1mzK4SFY8ptxRbFiEil51V8/7zy9s0bI5DHjXFYYqbeKqdfjYgasmVXDwBJbg7RXtaxYyCsNacgVR7CyePcZ4qrJi6lWMHFaoLv3nhCH3teZ+Nw7WKs+/ZqXacjpTPIqqVp4l1Vnc+6y6rk+hdp7X4AY+kWKa+Issf3pbx8kXaloXrZ7CS+82qsiIcgNIoLL+abg69e3hh6RUsmsgSEE9BLWLi14aSDlVYojgBSBqdBb9uq5T1+8/9Ib7XNA9plJlpWiPmmPeHUGlmomJj4Og557AcHZFvEqKZGSMgK9iklQfeD19bswvTICHYtORrwC1iNUfcWlxOc1lq6hA7FaotKBiGZ9HYvxvRqj8kq1eI3lKOvdgLVPYJXWwTmELMCpDhBxJEAceCLCEtudjPE9gRJB9ZCOpcg+EelxAaIzsFiwM1nQCTTMrLHoYY4a32WV46mK3ubXsyes9rhswZKf6dsjM132uGTaZfGuWRU9wJcZS2vl7iVMUh6HGzvcMiKmvlFdUsLnvXWv0ifNrHBfE6KvhrOc57stQ84ynM7n802PT2SZXksJly7FaEy7aumxuuoYLC0NcGh9eBysUvHOOomJj5h6avMrfa2L8Ymgp+wSWP2ed+geLNYiLLpZwTAh1pUVEot0X21nUvP6qdRCx+JdfIkkYC0CLNU9pdlcO1h0IweIKHi11BKZjhWwunesloEvgeWO5WC1R45jdQAWcdG6dgkIolAaeF7DkwtckWc7IWiJz7t4P4WRH6QJWPu68czlBg0DN0y4RVOqeFxL60w83MIWglXar/T4hI8HqdlzRtYn1afua8ewA7B4LI61jhfUXCvylW7/q5Z3Ga0EUD1mvZ0997TL132tvw+9IOVTerb4tnT9+vq7bCn5UOk4TT0mlfWZLz2UrpfPLf9/NS4ULB0y0TNa34OjS/mSZvvSaGlJs76ONUW9eOfjUd9z1Dez5K2ehYIllyqdFHCtL5BOUU9evns4Tr23vuFd1yVv+CwILJ2u9DPs7Xtwvv08xUtKjuWn4EvuWHfNFseqR+Dx66X9Z9dhaScXxjkNB3tex/Iieq7IdKnSTmJ7nKWlxaE+vW+Z4pauWXV6rGdOZ0T/Q3dXd9iiY8H/em3XPKSl1K6WXVU01/a+1d/lL2/tvV415q6O+/Uy/6uWliaw8ttz0Ufyi375tcxoftw2GrCi0XwF0YAVDVjRgBWNBqxowIoGrGg0YEUDVjRgRaMBKxqwogErGp1F/waSC59MEZqyugAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTE6MzQtMDU6MDD7rn8NAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9LSE0uc3ZnobI3IgAAAABJRU5ErkJggg=="},"119":{"admin":"Kiribati","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHyklEQVR42u1afWhXVRg+Q6Hwj5ZLDaZLLbFG4bakzEiDDAxKCyyikVZOCArpS1D6GjGhsb5bxDQVqsFc6DbEFJOVyBzaxj7UlmvT2gSXzpnlHNjCBfe5fzw/zs713K9z7+/jn5cf93fv+5z3fZ/znve854i2w5Pnzc0LTL52Q/mc3bYMVnNGsodjjy5Symz98UQ1zuSdctbIOydO/7lgKApimXeohmbbHZxNVTKEEbbcUlA0c0vLnvsX5Q1Ctr51+5XZbxglmX8s1qChTaT2woGgtre/OnXakrbKJyfl1kC2fvtI7vS/7JBb0g62hnPt9y0N0AzZtaB8wpTnIDvf/azwxnqWeAdf6c/7GFHNZe4X6VCX2KSxKMWB//W9qidyinpa6/uyf8BvSLxjE5G1WU/w/pm79pXkTDr/RdO0nFWX17dsmbICv/Evk2mcLGWmEo102RVpVPCSoxFmO+SUe0AskAaScxKTCQTiXJhAIBnX4OIbBzqKdN5hgQqcyUCs/hPbduSOgjqcn5CZ8NvOZwvvLp51HySWyIRlLuytQ4y3ICKdd0YglpyT/rvaeX7qCC9weILfkHiTyWdXUTq1WhpIkWQdFD9j0PgWVEPGUhGI6yfeEEDGojA371VJg8i0B1X6eaEcp4pKpSawb2IlNHRsYpl3kIwY5zI2c5aQKd6TOEMkO3GTfik0Uz1YX6ENgYWPS3LuoWdOD6PIWHEwWGMMTBq7v2U1DlDC2/SyJCottCTsDj7RDnUGvs1kZZGUpAnqOMKSfNgCiZ4Wt0nxBLtCtCHwhPeG2C0GlsmSfHFM0xqLswt3sJhA8nENP2HaseTzxxSpn5KSWMadzhkFlEKmAV2YRkNvNk3PnixTip9w+5SpKWfBVM1Mqkkr0i1L8R0EhF8mipyZ5J673Ivnf1Gf4fc1iBWHazCZ4j2oOw4gDZODs5FMJpVULZrQbJf/tCHILIWpKK2jYi7MVXTBIsjkYykvms60w1IbSt6KcbMjxYlltwOoWYAwq7KUikzOEl+pshff90o4qE6uG6SZjJXQ5LRCyPs+VaZBlnJLKZleXPKzTl4W02G3KNLh2MS+EEy5irMUF+P+icU6QSy+MBijfn3Ipx3C6CFrRMe6TCzOW3K57V/yUsg5jPv1oS9YUd1WZWId7Zzz4YKzynuP+kPUuIALFCCyDM+5WHqObSz+JL8bi9Hlfe0P5PdyxvK/CLLkLGWTzEKExBhseoUWbPN+xm/GEv/2Dcw4u2xkSUfD8d4LWds3NzzWv3/d6tKsrmfuqVpaOg7hZHpJz+2OkaUB2qAZKEC8cuep433N18DVMZ5wYRLjXnq255ett179aGTn0SNjdaMFA19D4gn+hUTgfS2FlobRGb2bnm7UwVXaq31H3tnP8HAAfnYZXyCKMTE2a2y1LBH+f6p/urmp48wrZc0f1/esX16xaj4PiAOJf/EmvoIGlX63uDwb2LCgcOXAgyKcb7QIpIF1/t6/80Ya9O016Wc5vpBucYXbAfEMYIa6DqQnR4yDqx1OtySTpZyBIrDXuJ9tArm0V1Q+X1fU+kdd9cHG7l3H9p6qHczn+eRfQlt33enbhoZrHm+c3/Xgl6UN5W3FkGZwgWISF5qBAg9DwgNmcL+6tLuu409IM/Z+f+jwzJM58LAQWYvXvj8R8qaNyy5+unfR4NqT1TVvL9/y20Hx44a27/rmDjQPHRiu0ofBV9AAbdB83csP76g4x4h4wrgbXt904cDCsHFle/3jQoMKF7+d7dUPOUYo4+aWrJhdOSxb6myvPq6znxMQVYNgdxQtX/Pitv1r3qk4u6eNZx4knuDfeRNeOLe1UBVIfRktLrSFh6uaYPq48Eys/TwO17QH5M2hsjSJa95e56zploLe0M3jiiMlXafPPIo1uOylbwoPrVy6eF1/bbZzUtWR0ABt0AwUIPrHVRnvB9ePQ8O21y0uFiy3uPoecLZXuSvkchsf31G78sTm7Tz/WCJJFj9V9tCu61EwQoPbYhBfQQO0AVeV7ZDGMUL/uCg8GVdlL/5le1HABmWvjIsnjMtbgbD97Da+LtoNKBh5BmAXgCf6Ba9bybhAZNxg9zjxxOVMEJWf3eKKOJAjtXHjMwlN4grndOctzeIrnTTL6Z2Xs6hwkd7dZiPG1SkbwlhGgYvCQC7Y8QTx9e9nVdmQsMUJoyD1thOR9zLxKYRlXHRxkmWjEwGun423n014BjdsXG/ogSG6bb7pg7ltvkXb9JOzrKqNGTd7IWVcZJqo/Cz02/aqTo98TIGv3O6e8Ca+8nYcxMdQZnCxNKQDLqKvH1/h7YAThRvPDG9lrzfcaA+STeLGx1638RVQ9/sHA59fbAyvNwPN2JQCkXHDuwQCXKBghpnEjdZeGddkfO2zQt4dINGhG+FtKPgKGuSzd065nGDxZrC4vIvRwfXW6WFcuXgwietsrxxf/7iq+Ar9c34UZZj3mAGcCfCv/hm48+aAcdFxkXExM8LDle3FciDby12cMHCjsleFq2Ov8DYgcDOBoUY2wIwb1GUV5684o5uxV+XnoPaPbu31iBvslYnwZDxHFZW93lohYY8w4Um0AdbR5rZzFl7WDCoPxXPaBDyGYN0XnuPMuN7ktPFTG4URC/3oa40hKtcHm7r9OzHayWP+fmnYE/J/4vCcl8AXaHMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjExOjQzLTA1OjAwNMxImgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvS0lSLnN2Z4he5NQAAAAASUVORK5CYII="},"121":{"admin":"South Korea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG7UlEQVR42u2dfWhXVRjHf0H+I72g4aIiqTBXFEO2cqB/qIhrEUY4g0H2n72tF2oWBXOSLxQOpbdFjLCIXNRKpFZWatnCXEqaGYNlL+hc4SpjzfVX0Or2EXrG6V7u7577cn73Phz2MO65v3vPfc73Pt/nPPec55QmhieG/vlTqTJWWVIVqFRgqVRgqVRgqSJUKrBUKrDKlH9M/L52/OiOh7wyMOQV7Z5giZbQGNpTYP2PREFVD3uleqdXen70igLIlGgGLaGx/navKLAmyZPPeqXxba+c9dF/ZeNUr/Auck6RwYQG0AaakbqqH/WKO1pyAljBaqJW2jDXzH7S7oG0T2gDzaAl81VUYA1jwDHmqIb/oUW/WnyLvMJLPhdPamoAzUjnwawtKLD86O+R570SpvbeE17Jkw2T9omn40l5aqkHNMP5wbWFA1YY+jOV5VfrDgXEq5MwxOdX2/WOVwoErGj0F6a20oEVTG1havHDpF3Pym6V3KE/VBCNHPPkXUUjPvSDTybt+q2DXjl56al1p7/OLbDKpb/wtXly5KMRn3QGTPCl7yqUlP78LMfYy4dqB44jR6fs+uazcaQ8nhyg7Wkx29Fiyf3RXzB12rfwz7qRml8fPVW9vXvXph8+bV345OKv9s0ba77yi/HZnzSMfj4yo7p+bN+Bc86bU4vkCLWcefS7lv1rb+AKXC0ucDM2lOCQLrnLo8VS5dKfPZiG7+tY9eL3gAPQ9N8z9a6aLgkjU3KOKanlalzZHmQ8KX4SYQjpS1FsSLMigWVDcH6BQfuvh9gVut8PHDZSXo27cMe4PjkDMhkodpMWEwQWIJCPlBX98SusCEQWF4zCSO7I3W1oKJjaJGmao0X527he0YypkDdDgiaY4PAh4qI/lI7nlA6Mgm0YLYkGr+BAaJixJL2QjgtfynYgnfTo7/glbdueWpgVpPwkrYqLB4KJL6ugTCn9ACAqwFBLS4b3IN82zHs0dYw8saXvrVpzTJetlBRMC+MNohIIpZYjWX2QLmUbZUb60V+0eaREmAgHuGarJLxoIa2NlxalbgvxSSfYD+MNs3EtUSJRJTchZcKL1trTYrb2yempybyFNjO4sXDdN1/+240/uw8sOWaMZrfkKgHXZtjmapXOjsb1F2z5uPeB88frr68UYMnRoq7Sce/D7b9O690/Le1aPXPn3HN31/1VWcAilBrX5yAFVmxyz4YvXzq2cfFltW0t76cPLGzkayumr5k//MKhGR2LliA5QnuCQ7I2hKjASlB2tvds7V82e+TaZS0r0qFC7nL/mzOn3/Iq95329Jy+1gZkad11ix57nP/rXrn68B17OdMP9MAuWgBCgZWgbD24eai3j47EWiQHqU3bL9y/5CbABIDCS0BGC80vlTaBUwVWInLllPXd25bSec2dVzQ1NwbPQYjmCQEpaZOiSUBpwssm9KDASkTe9kx71eunzxDQUM23D66R3WZvpfCWolmpYOslPTCAlY/ZsDm0WLLbAET4uVZ+vhRXiwtSUuJ7qcVy2scKJp1oVio5SJl2S30sR0eFfp2HV4TvBcjkyJFORVLLmVBqcpAyiVtHhY7GsapGG67Z0BLefcZaIGXIIGkwmZJhAYs1FFjORd7rem6f/9ye9GFhLz/sXT53lUbeK83TcllChXzl1G+Fjs5uOHB84KITTeEJ0QVZvXxBS9tqnd3g9HwsM6blviRQYj8fi0l/Oh9rUn66eBdQYLeumtd05+ZZLkMKy0pry33GwcPHDv6yIMwM0sJlm5Fz3v3SgdjMeScA4SYt0ipaGE2H5mIvVgzIpWAFmvPut0oHkx7vIlVU7KY7T6vSX6WTK2DxwDI/nd+6QoAVLy26NlqkJdFssB/9oc/gWnRLL1T8usJ4V0Lb0KK0XumHQCE+G0j5ZcGQ9Bc+QUjFr4ROIneDfeqid7fuPXvw4nRCqdyFO9ovlUMnwIXMfS6nNHI020xytCgj9R0fdK58b1a8IONqOOb2efTkUl4Zryp0thn5toUnvjOpDUUwIl5a9ANZzxu7px05QlQJcJgjSo5ApvzPmfyKK8SVlDFMfixzBXmB8mOFoUVpn+gYSZdZZfTDHSbOBJ0h+eBNbRKdJOPpfhQmaQ6Nob0wiY1yGG4Ik0dUvmfpJGFz7oN6mTlIzQRPBcpBGoYWbbIm5ym5rU0CSL+syenrJyd53vOxT1i5wU/N855gblKUqztT6M4UMWwmIEdM+dsPbNJeOiLfVZg0kGZtQafN2Oz+NWHslZU/qbt/JU6LHNH9CnW/wkR2WC0OpCbK3GHVBfpzdGqyJD7dEzqMHyb3hHZt/zPdxV53sddVOip1lY5KBZYqQqUCS6UCS6UCSxWhMmb5N0Cme6EwPrvgAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoxMjozNS0wNTowMLbuz7oAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0tPUi5zdmdeBwfJAAAAAElFTkSuQmCC"},"124":{"admin":"Laos","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAC50lEQVR42u2dT0gUURyAJ6JTLKWEHgQxMN1DHbZDoDdFjS6Bl0BkodgOBaJIdCgRT4pFoODFg4aUFGkEHroUBRJRsGGS4EHUy55CaCPKk7AefpcV3W3GeTvz/nyXD1ln3p/ffPvem3lv3norK1VVTU0QqqVHCCBiQcSCiEUgIGJBxIKIBSFiQcSCiAUhYkHEUsQfl84uXajmgpkSH8PEQi9T4mOkWEimf3y8UgWCMAy9qWe1r1vqIFRLz+tN1t655oenE6mT/XPhjwl6pNpzo2T4cqqqaQzp6H95oJEkBH6+ozUXW9P3U/K3Ke0lYmkk0JXfN0bG/w2kxxIL6ZeLbx9kXyyns6mNn9+b17/lEkL5RP6beTP06fl5OYsYItYBdiRvvZrMiig7W/lTf88VvhRWC9v+KWdJCpIaUXVULOnaJk7Mff3QkN/4k9kdDCpTKUpqkrLkglhOsD7Xtviw+93M50frv1TJVEovyaVxs+vx8EfEQinFlBwld8SycGAuY6AolSqm5O7QHaULlZR7N7VjqeN1jlISxLKk+5PHBHEpVUwpiemDel/trgttlQ5KFdOJdsvu6sU7rio/3kIsgzvB7cu5/p28bmJJqSx/DGFrxWSaJd4Be/mBvOUTQbZWrHu+b296WTeliiklRCzEUsyeznt3Z7sQC7FosRCLMZbRz7F0nqDQ/67Q8tlD5geZN0Qsnrwjlk4L+pgrtEosfZp6HVY3ONRWsR6L+UHEUtYtxrWC1Ln1766txZap32j0cnRRcnmx7L4ZlvbjyZmn19/frsRbOpIyb+k4/QZc++jNqxNrqt4rlNSIKmIdMRHEm9CIFdEd5eEdHIgMYsE4qHbXKxh99DRNn73nYEV29GO3TFiRPUh127U36LlB8w1zPPHxXx7EQqz/pH+8cnq6bWYf5gIEzTGundDVxkfP+lollgu/ImFKZPiRJohYELEgYhEIiFgQsSBiEQiIWBCxIGJBiFhQe+4DtSrzYBJ8V2EAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjEtMDQ6MDBxBsFaAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjEzOjE3LTA1OjAwjJay0AAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTEFPLnN2ZznaCwQAAAAASUVORK5CYII="},"143":{"admin":"Marshall Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAG4klEQVR42u2df2hVZRjHL/hXFlZEK7ICRZLqL1eywVy1lhv7gWlmZYhOQ9JlS83MVllqOpojKZcJuSVj4lpmtVXKKmkNpjgZo02LbDUTy4amjWaR2vrj0x+PvLy3c+75fe77z8Ph3Ht+3L2f+32+z/s+5y6RmJCdtXmriSa6Fa96Kbe8OiNh/hAmOo9jn8vbs/7l8kOzJzx42Y6WRZ/eXmTAMtEFmD4fqDgwNnNgYOX9iQKir2Bd3jWl8rVtV6zP69m63wxMPGDqf/+ZisRRiVQAYIHUxMUPHW/MNYMUD2UCLDX6ClZGd1F/3cTsux479V6pGbAowqTDKGCwbv5q+ozt/8yaUdmw5x0zeFGHKUSKRRJcWFx1x76nzECGIY6/Nb93TY3dNBcKxSL9sU0SXPXXlrrOAem6jJ0PCqb99ywrvKbaCUA+gQVGVH/smdpS8fZHGezJm/LE6A9u2TTYtKFnNa+yR8Int00MvzL5BBYuCpjYg6NCq9hf90DrqCN38yrqhWKpx5rorjJ5DZPLYOGcZDpbsqzmRHvfpI657zYtBZR199VXdV0PZDsb2n75bppUL44FMjkZoZ7ZRCcweQcW5zzx6Oau7FFnhvZ9W1WfcCv9YclRHfQJZQKgliMde38sBK+28wczf/qDbSLHsi11jmMNLlZgWvXqnAvFw3Y9kw4yK/Ada1h3/obawWPN+fNrh4e+yfy47mLPuZW/XTsyc2TeyOqEE5hUYw5MbKNMbzbuuvD10IExfUtOVna+2Lv4ZElva/+Tp3OIvMp+qW3S4MskazDSweRPmkOTfi9vL9s4+e8tv2YdbgAjNSacLM6UtD1d2LodvSFtoTSAAmQ/zzyVGL7z9L1nLg5frV6eV3n/7k++3Nj/OkeBLGeTWmhg8hOm5JqUPLqQClEa9AnPxLYOJjXyTjQMxQIydCud60RdmvPaJ1nRJJfBkrUbioVJxw8BhHWkdHihUtL+y2vF285LmA5tWv78lWu9UyYnmuSJYqEiDDYqhT0nqaUGFvHo+OM5Z+dj8FE+mWrjCtNtjxTMemGb1zC5q0kpgoWLkiYdrSIyzFJRSFuA5eSGcF2YejwWVyFyXe5B3puckjUw+aNJtsGSzS1AwwAzO1Vd3VjYPRWLjUqhLiAFFk5uSCZEzinVC3UEYpRMpsjwG3xg2nBT2Yd5h6UBT22h13nt5itYdvurGFQGGyCcgwVGQBN1824XpqhokstgkXTkcjJpiwgQTnQLleJs6CIaiWpGZdJBhcnJdGVQPik1G0OGSaRm26XXQbfkDFZqusVtcSwwSS/FFcO8yCNhUj2TE5UKpyZRZiElc3esve6z5ktGx/lijtQt8GLSwbpu8U5uFOckz0mqDb8yWTHg1iGT627BapJ0vXhcRvl/LIrz6QbSolxIlkYbXNT5d9WkcywFgVREac/DAFPWs8WlKxpUmOLhkxgXuZ6rtjZZik5su9wDxdwKQIAItSToABzrhvLWeSdnACP2y6sEm/68gCkMPokvvBwpvsAuTN+41TYjO9klHHJBmluXHwMvpTYA8vE4T3Rhku/nDINlO5fPGReUJpEfKIkYLw+nnd1tPoZ0oJELyZg72Y/FR5Iw8R75DI//k5/AVFOy4OHcWp1nso5XUD6JdEZm4C8cwMSNFx2k0g/JbwbTrVhycMGq84FJdv4/HJYcJusYBeWTpCvCp/I3DHj+z4tHUuUeCQrbpD+ZRuXslNdeCqB1MIXfJ8kiXy7Vh25Ry5+nn3VP6YQhzdnumfTFJ8mqWbqiyPSl+f9coT8PrEpleqN90d78nNQWev3xSWBE7UzRg22IcHuj/09Ce13rAdNbnQsXTG62C5M/tZuuyI9Vn1k8frvBCkw6sLz2Sbgi0pnsLYtiq09IwZLPD7qV5nQw6TC6xCeN9BXtznNLk6QrkgtcadpaHa3bLWiatrR8RXKYVKTQpLP1X+x6pcgtTdItfZinICMDlg4mS7WbS5qEK5LrB+nQfR9DsJLDpNMkt2o3XZEfc1cUV7CSpzkvNEkufTBtS5FvfpgkwmDx7Zcw6X7NUvZMOvFJsr+RSVrjimIClgpT8qmB/9bdUtKkyCx9GLDcmhpQfZITTdIV+eaR/BiCJZWJn5PXKZNdn6QufaTY32hiVMDSeSYnmqQufcg2aDN4sQWLAZbKJDGyW7vJIl8+9WEGKS3A0sFkfY5b199oXFHagaV6JuvzSWqRH4r+RhODAkunTGgSEwGqJski3+WnPkyMLlj8p7nS0dPHPD4JmL4ft+aHG//UaZJZ+jBRG1WY1P4ks/Rhou0ITFKT5E+fSVdkinwTbcS+cwe7O+bJ/kbjikx0IRqMTPQi/gu6Bo/LVYhnLwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MTk6NTMtMDU6MDDrsQjwAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NSEwuc3Zn/wIrqAAAAABJRU5ErkJggg=="},"147":{"admin":"Myanmar","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAETElEQVR42u2aT0gUURzHp1MkRRQRdIpFMi8FphZZZJChEYRBB0krwzKIDppYiSLoIUNLIhc9ROTNys1KVjRFSIXQ/kBooiZkqIFYKGqabZTBfvfwZNr17ey4uzPzvXyQ+fPeb998mO/zzVP+zr/tUdaSpL5UOAQkxSIpFkmxOBAkxSIpFkmxSJJikRSLpFgkSbGCzaWovuKoXJCjQbH0EytndCS3EeRoUCz9xGqZsb+sBDkaFEu/EBx33fma8ccxUTWZzUCkWPqFYNPSqyW7hwxEiqVbCIpiOaZcjiGODMXSIQSXiYUjDESKpVGs8182XVizTCmR7rMcJYrlv1juyPMqFgORYukWggxEihUQyz4/OeP0qpRAXMkR+49YRbvaHti2kCIHb00kPrXJiIUrOWJqKrv7G7ticmJ/NvbHZqiJsyLVx8Uj3tqRb83fs9pa8FZbyubWyynrxyvn68abZcTClbhLpgaZkZG/Xq/Rk39SvisRqfjuwGq8sfdd8fVyGaVE4i6ZB2YdUqxldHaO5Tuz/BULd3H0LCGW/GsfRJwNP57dN1zkr1i4Cy14iyr5CQPFCuiRy19/uKo5K7EWFI/I/O37SOAhqA5E333JVO7vlRTLb2IQqysHd1Tv7LsymtR9COw5+C2he1Zfyk/YfU/kV6M28bdjNMJfLwNEISKmvmJkod4WyIM3OjECYuBSLJ3/awv87WIUYvaGX22UEDSYWOJk/KStvT41CdFgVqUQgvilnLyHbAbm2dtpdKXc3x/FWZRx/4tUzLGsgLDQtlgQDjKJkWeO5QnFTOtVCA5ti5yhIr42GmVKbtEFUuiFEKk40tdS/jw8p/moChVygdSQvJTwekN2QW/m1IveqHBQCpWgKq68G/4dFg4rYeIqlBU+7FjuIzQCaIXdobpOzM0deRTLw/Shjrb0dcF8V2Ve6yo856BYJidWiYIpFnq02m4ty4kV/Ik8euQby7RTeE8IBmd2pVpcQCByP5Yfu6F97yIPfM+Wtk1z6gqDH4LygSizU97frYursUFSfi+8kr+xpCW+1AoM7Udr9G6d0VYmx6Kj98eZm1NbUz+c3R7Ih+rFyGeFTZ9AbS2gd1SCqr7HxZ86etqsY24JsX7sKYu+26RNhbn20oTbMWJrc5FFF2++0aapujWKZWC6HvWceF8r//hx/XRD2nR2pLc2cVZbyxTLciG4UPOwoe64fEjhStylLRApliGJ2JJ52LPJeQXF9kD6Qgu/Oz5WDLhWCER3VRTLwPxV2l7TOeDtAeOsvu8PtOa730W7M6I1gmIZMwTjj42mJatDEEfmU+9V3T+wer0jItGLtxpQIcUyfAgipGa2XbqaVxLMStCjOiLNHYimFQtxI65ChfYNgd7FlTBzB6Ji1hDEGwIrWOG5roYKzRqIphXL9ypUOBAVUiySpFgkxSIpFklSLJJikRSLJCkWSbFIikWSFIsMEf8BFxxzt4ucGlkAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjIwOjUzLTA1OjAwjkZFOwAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvTU1SLnN2Z3Afk/gAAAAASUVORK5CYII="},"149":{"admin":"Mongolia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADwElEQVR42u2cMWgUURRFA1YBSdQY1qggmNZW7dIoBBFsLEQQrWwUxMJC1MrWxkrRwkbEbjGFgqDYiGIV0IiNCgEVIYorhEAkhbBnizu8/bubZGfZmbnNY/g7CezO4b7733t/Rj5/rI3tnS56vHH86vLtfbOz9fr8fBHj0cdzp96v8C3K8URGDJbBMlgGy2AZLINV6C/w9cn0lT1neCQ8HoNVcrB45N8OHds6NToYxTJYFVKsP8/untt5MG+8DFYlwEKrftw5fWLX6Mrrtxe3z4DXYv3w9901gxWRMlg9RQD69eLm6uQ1BQvU8garuHgZrC7x56XzH2pHll89v7xjP2D9XXi0MHGy8fLB2Yk3ViyDtcGIo1o98Ondtpm1L43fY3NrU0tPx7cs3b9+YbJhsAzWpiKJjyQIUngvgzUMYOX3LAZk3kGK5Gjz3gtYeT/yQioWPwoq9e/e4q3xhyRBIq4rD7ysWCUHC2WKSGnEyBssg7Xucqga9hgx9f0VfJcbSg4WtaskWM11ChD5gWXFKglY2hNMFRoULODDafXLbxms0ioWuLATxGmhTPgtUFOkSJr90i2DVVqw0CpgUsg0Unpgb8g9ToXuFfaUENEq9Em1Sldo7Ni8W7E22IRGn8Ao7+EZp8ICgwUWpDCNpDlVLJDiU73WFTXv8R7+j8Eahunc3MFqU0poXlPw1L0hK3qPFhoiiPFOfFjvX8xgFVix0JVYREgNwwAQiKSa0NwTK14Gq9pgNa/VjGs1q3MkCWYUy2BVswndAisUPAGrlSib6+z+NOq6opPqLW4GLO8KCwaWohNToVp7EIwWXiNI5aFYxdUtp8I2qbBzuQGAIihOhVasLoqlZlzLoVSw2A9GyNqY98rvCivnsTqDxc8BQFpcQL24R49XaKsn7jSrBpZTYTIVtpJaQASM9BCYNnbapMJKKpbBaoHV2scJWPFTPfJF1HEa3RWm9oz2WFVKhUGx0CFVLJDSxJcpKESds3l3gTSjWOKx8FJq0qPH0r/NeCyDZcXSjp4qlu4K4+F61S2dxMrDYxULL89jdam8p84VApbOQbiOZcVaB1ipoZrUCE2y8u7pBpcbiHqIPnYJU71CDL4Vy2C1K5CKYum0gh6piJPvrKNwBstgdUmFQMA9WhSldqXX+hK2PFo6nm4oya4wmnfUCDi0vRNb0Rmw+jRBarAKOfMeR2JS7+nTvWHqYGqcedehGqdCn9IZive8l+NcoV9jZLCsWAbLYBksg2WwhhEsz7wbLCuWwTJYgwKrHLvC/+IqLfAqTqLbAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyMjo0OC0wNTowMEQewWIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL01ORy5zdmdei/mkAAAAAElFTkSuQmCC"},"156":{"admin":"Malaysia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGYUlEQVR42u1dbWiVVRw/I7BAhFhbLzOLnLoyL2wNc2QzDCXMTdOsNbpyW9Zw+XIzzJhGtRAHlQtnkMSMi70w0VIZviSIzmCJzebchxDcbOBtH1pULEuItuD+7of/5ew8nHPPee597vp/+XE59zz/59zn/Pj9386zCfHK3JVfHWD0xqLm8rltbZ3x4pdCkSvHp2+Z9qw39q8quXX63TZzdCwEzQ6dI5g09sRytXlBpp1qPh3JHrHEQ9sOd4wzQnFCKFYuoinJvFFkhkZFjy+e1Fa8aCDy2NuTIz9H+9YfenVp41BdY/0Pm29ZO3nJsRdLt/XNfLcq76N6JQWzioWjlU9/fnPPj1vbd+wbrtw+s6mKETj0d8OGl2/I4z4SCzT6ZM3ug4tGBo+d3lKw66/y7kcnfTHWc2mPuA+Ikct3nZxR1NAV/np3ydTWMy3/Pjl14W+r1zcVYjuDQKx7vqy+6fj9g7OHCq5/ONYz2jP6HaM3Crf6BCrsvP391urrw5u6npqyWKbRqdf2Lw/Ng1ZVPFBT33weWgUM7VpxR8v3ICU+e6sIE2uCEwvO7lBBbPnDcUomYK84mn/vT8+EGyo2p+qQhsuDZZVjlanmB+FArKs7r4z8Moc+vn9647F4ns5nOmI6R+daV+uRx03v64xY2EgVpaBPyfjJNHJKzAeBoG0YgZK9ceKtteHtVCkjd0brNpT4QSwE7+e+XRauXXWtbEV39QXGwWkL3nukSPWtA2Jhg1UqZUopmRaw0H64be/8pfgWVMMI5sCxJqnmg7vUzwrd5la5i1bEgnIgMJdjKTg+U5UCjZA5Jp1gYhw02lHRHF7ZBxoh2IeSgcRJVSOpQ3INXG7IGWIl6KLSKrhFm41EAaLjnc/mlQ+DZCDTtZHOM/m1oBSSg/OXjxyccRtGkEsiD0V26UqxYKe7cM2sjR8jwbZBOAu319rYVFnTsamaI2weNOInmVigglVFKnEV6AVFRElCLlgAMQdInaZ/WaF3kGuKKmtu7xLsrDCx5XBYshOEiniXCWQsmL2wLFYH5wUVBEKlQBSZTDLi7rgWBVhqx3RVXG7IArEQzciVKugKjY1MVRCWEUvBwalUSkbMpLqFGCu99dDfqyJWJhUlt9TLiljyltsQS65UQWlkXfQmFiiFFSYdonWDCMG7fYzlNhLK7hq87Yj0IiG4FVmxQIKUrp+2VtHqFMJ25HqmxMJVCOoRyI9TmOWsMEBZoaQo0Cd5a5GdmdauQCwgbe+AZPoxFi1VwALUy8Yhglhna2aVlVYGs15lf5AmQKcbVGE1IiSbmAbhPPSGNqdBXBmpPkGxkg0fR6ckuPJuWoW3IhZcjBxpYWvlHp++eiGne3NrU23tapAMnymBoJdoeOMzdA6UAhFpydQ+eFf1CvU7aPp9uvS6fv71FjPaK8SWq6pZ2HJTm/SkA6UaHCIqWyA07gv60hyQdg9TQng+3ZBbpxsQUaEmLsdbKcXStBDkoH1AjECTaDIBVeNjMxPq2AxKA7JbRECNb00PzFAa0dCb9hPlmdBRP043pHeCVHXG0nRObt3X8QlS2tGT1Qs9RMRA45BMohooIjsymkVm5gAglxvcZYUW+RSUA1GR6jgyzfXgwuDs8BlqZFoP45cpJsixGR1SQp8QyCPcRgYHVUPxk5IM4bmWO8vgCxcgVteUynVPjCG1ZvRG4eOrXYpxREtQo5SiZYBfAkPw3v/BxXUDz9OUnlGFQkkC723W2H6tWMf7TUM/7HNWGOQmtI+6YmNfvlb1QqzOq7NkDhPLmFiRZXP6o/nAFzaGOqNRRhk3nZ7f+Hre1ZrWJXsW/HEuFv80RPH39lje3otA+VvVTLcYtPUIVTNS9Za+6lvVtd7W9O2rbKr+doDOSnTasfZ/3iPXX5ZPMyt0+wO86ZLdB5QeRbK7ScGktc5TEgMXSqsebGFkdIviRsel53qrGRndouD8hdGXrJAfAaMvxPrz15MnvilmZHSLQk7dGRntUeiXDNJLyN3aN62K2ZQSvCtkptUv/bvr3Dcz69F5PqqrWLEY/VEsjgYYfYmxOH9h5HIDY+4Qi2vEjL5U3rmrxehLrzBbPXNX9u3/aUcw15nrv1fw+ySMvpzH8j5PqH/OUOcEoyv7qrvoWzadb3+S09Xz1F8/HfHv+aj2hbNCRi43MDKxGP/n+B+uxQvMVWFAMwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMi0wNDowMEDu28cAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6MjQ6NTItMDU6MDAh2u71AAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9NWVMuc3Zn1Tj6xQAAAABJRU5ErkJggg=="},"174":{"admin":"Philippines","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFDUlEQVR42u2daUhUYRSGJaLlR4uJZTsiLQQVEbZClpBBP6KFEqSiAqXSiAptgWgxWpDqh1HZZgtYFi0IZkSiNGgrU1NojqVlUU1pkmUMlo7BPV9wLne+6d65d0Rn3j8vw52ZO8z3PZzz3nPO3Akrqnpw6/PqhJaNeaWfw3pNi71yAwq1QNsd7Rfb4z8tapjh3rsvNTe7wjk2JnH77cVYGqgFYHGtnvo+8+fQtVmHKp4mRexMcN8cgmWCWgAW19JD9nP1o5Ym7agts2OxoP6CtdNdVbmr/d6fO1/miyPK48aeTb9b3uQl342oy4xNX/3rrgsLBzUCVnhTeVFTm6P2xLISz9yPuTvWeDa5JhxI4q/hbmzYvQUbCpKxiFA5WEpk8lyqH3/8cGubwzXwIKnnQ2NrfpUsUdpznamNH+HGoHKwlFjl6VFnS6lpm1KxcMxTUjoiUqRPN0ZlizmnUzNK+mFZoV7Mu0iCFKvIb3HX5VPJjZ0ZWdCv5hvKFgBLjcjM5pVlg/TDJFNyY9uzjr9xNCBRAqyAKLmxFb131zxch0UHWAFRuDGAFUAlN3ak6PJJZxLcGMAKiFITCW4MYOlTpYRh9CIATSSAJVUqVYhKmM8Sq2+lJhLcGMD6V2hVGkGidq9UxUT0MlG2gBsLcrBEBCJQqCLP6vLUFCKYOFh0XNXqJiUQeTFWhxtDEym4wFI2vi3n9fyECJHmlMgkYhJhoRRX6Yh4Da/jE3z0LkVF+yitKnvaEqNuDLOvwRWxlNij6icSKCzqCLAUXAR2PD6xZ/kZzJQtMNITJGCp4GAA8TY2pULezBbvopjH32UCLIz0BIt5ZwM2Ip2x5MhHbmSqQoq8l47pCf+aSOTG+o6LP3Z9Ija7s4LF50uZoyJQ9CClAouXIehsVuGluaQgN7age/p4Wwq2vDOBpWwSN90iYmkGA/Wr6jw8IdJshdEIyq5VxQyZpsCBkZ5OBJaw6uSlKOWRKuPLRmOVNm6J8zDXpbL8ButnPB3L8NKO9ES3xnU7vy3qxeQ9J39DrdIwA9eAHC8GhAVgafAybOc1YNE5ZWD92ewq+pr/aUSm7YizOCp6/6yMQnfk/dhnUKvUeCpkppvXokylQpZkDadCVrYVKolVBFPD2QuPr0ZVLo8tn3fObg8fMHrM87TwyFFb+ONgUvpeWtW+xtrP8te8s1lT/8y7qkUdYPP+Y17xStucmuWJj1LyxZef3XdVTIFs0WXboP+4tdoxn/IfNbhilpUbdCHFUmqgyw3u4lfvXk+vy0jN21bi2Dq8ddI3o0sDNaNWFEgVsAQorJnT8QVS7pxexo2+PL0SG9zFwFLFHqMtHYpz2paOiZ9vkHNyVicUJvZBZOpqYFETmjkqL01o3g2UNaFZlDLThG7uXx755IwZ59Rl/E1IRCxrx2ZoGkL32Aw5pw/NW+2Zz+Gcggqsjh/0I+fksh2NzhksygSAKeTAsnQ0uTH72vqC78I5YcMAlplfVHPnhGQHsExpS/zbuPcx5JxEmQAwASwzzql+xKmcS1m8wQIFWH7q91+3fxYnq5wT4hPA8k/JOaHBEtpgWXQbIy8NFsAUymCZufGal9EUgglIhShYkltFinE5HdMH5kdToMEIluTmtr4r5l5GU7CsUG0q5BOYfMJT12gK4hNU6rEkfyAg/kYADRaoVeWGjhlNgYYEWNLRFCAFNQqWlwYLYIKaAYvKBKqhXsAENa1/AQfYIQ1BB8m5AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMTo0NS0wNTowMA9wxGEAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BITC5zdmf5C9amAAAAAElFTkSuQmCC"},"179":{"admin":"North Korea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADF0lEQVR42u2cv0scQRiGBwIpTJfC0iIBq4CpbOwstLMQBO1iE64JClb+A0FIYUrBgHaSOuYfEGx0CYmFYAh4Bo6IBEyahBAwFm8zYZxh9vbH7c0+zcuytze7N/P4zbvfjJ8Zuf9obOUFiparhi5AAQsFLBSw6AgUsFDAGlp98PTxk9Ux+qFI/xhdVI/GP3TbNO+gNl/N29X3rz/dQ9Fy1fx+tn/+7k0T9Nf24e7hbvz5Im0Oo1bRP9WpOb0wy2bZ1s+d0ZPRE/d8Xi2rnSbcJe8d63+qcp8n/nrflaY5Px5NSQELBaz/9cvV+Pz4vPT81eTS5JKOmzYNAdYQYPRtu7PWWZM9/PPxbPNsUyoDax//eLlztHPUe7h4sHgAaoB1hzG8er5+vH4saP59uMluMlt1XsC5n0oF4tfp2ZnZGYa81WApPv3c35vYm/DhIr2e21rYWhCCPvikf7PeZe9SmDLwrQNLSCnGhJGS2nFIkMV86/vpRnejy/C3CCx5oxg4FJ9s/yRHFfNdqa4HgsTByouF4pPrycITooumYiQoJAiWgNDbXPF4Ex/zpLiuZMHqrkxlU5nAkmEPq23Yfa2FWxB8OpafIyWRIFiKGXprq8f3CCNZeN1XKVaASAosN62gIa8iigggd9rFyCcIls9d6bymtuJ3URJV8cm9lz4FiKTACmethEJ/A6+YF2PnAStBsOLf4/Iuy2hKjWkZsJI17+GBV9zKm3MKryHainlPNt0QHngZ/P4WiHy+ynZypBtamiD1TVV6m1Nmyxd1wovZJEhbuqTjToJ2FspdonETB74JkSWdVi9C25OgnaOP307jmxDJXbUILKFgJyAUb8JZqLB/Eo52m2ybafVGP0WvvIvTvslU7YAUW5OjtibHxy22JgNWjn+mUDSyVeflsQQTqQTAyhHJbGNuKxgBFgpYKFo6WPWUwai/harLn/TXfnX9UOfzmFSL+6ADLmNEiTC0ksJrdqnCtIss1lOCMablOnu7v99bvGcMBWcpv1vFk1A1GaUcNwpYKGDREehAwcI+o0Qs/iQG3A+3LATc+rwtfbgAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjMtMDQ6MDDmmdBzAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjMzOjUxLTA1OjAwM2Aw0QAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvUFJLLnN2Z+lmK0sAAAAASUVORK5CYII="},"185":{"admin":"Russia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAA20lEQVR42u3bwQmDMBSA4bziEi7iMO7XYVykbmFs01MPgg0RBb/vKJKExw96SeQiQVMPI0BYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFhfVnbt9nvP8+yT66I9fZ//73zfrTrVn939XrjvPcfPZEuM0Tm7p0FqUPoWFfyyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYXET71s6KT0Hg6BxWMvrc2fMIPApRFgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwoJqK8XrMLz36KVyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIwLTA0OjAw13HK7gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozNTo0Ny0wNTowMJEEdTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1JVUy5zdmczbNaZAAAAAElFTkSuQmCC"},"215":{"admin":"Thailand","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAABZUlEQVR42u3dPUpDQRSG4bODpDNgIyG1jYvQNVhZp7Cw1Swi63AJgqWF7iG4A1tB4rXQ4oImjNw5JDrPVzwW/hDH1y6XidVqPJ5MyLqGI6CwKCwKy0FQWBQWhUW2EdbD6OLgssg2/3h7fz7xi5dIFhvP88PT6QtZ13hdPh4/vZF1jc4sYcIyYZmwTFhmwjJhmbDMhGV/Iqz3dTftbj4t+Yb+V5Z8b/9rhrirn19+Jm26Mazzu6vR7T1Z14g4Ors+IWv79WE2WyxyzP75+2Zrv+8mHQGFRWFRWA6CwqKwKCxSWBQWhUUKi8KisEhhUVgUFiks/u+wvGfSe1B/CMu7sznE/j9DT8+TMOUpnd0+x1f+Wc/0fT+HIc9FZr82T0KbR+xNWCYsM2GZsExYZsIyYVm7Ybn1hSl36biniim3f7lZjyn3FVa429NltS2fT2JY8mr5fKqFRbrFnsKisEhhUVgUFrnVD5SOZrLCehlyAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1Mjo0MS0wNTowMPi9kTIAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1RIQS5zdmf11DYEAAAAAElFTkSuQmCC"},"223":{"admin":"Taiwan","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADnUlEQVR42u2csUtVURyAD02BEMiDlgeBFIGLENTgGE0m5Wpbg5v/QKTREE4P20IQikDiTRIiDrYEhZhBPoeccpC2HIqmgggTvuUnt3vx+bxy3/Nz+Hicc+7vwLsf5/zu755nSkNp6NmSLOalFyk9WfzxOqXzf/bPpYM/WcykNEcXa/d+Sv2/lUaxFEuxFEuxSmW9VW+9/AgVS7FOjOO18dqbOlQsxWpjNSqWZq411/rchMXyneaqpliVFqs2WBt8/mh1bXXt6/vJ9cn1d3eyvduN7cb3e5CWOIariJDtVawzvRXONmebWw/2VvZWfl2ZqE/U316jfbgx3Fi8S/vfg7/9QVroZSTtRHArVKz/bGQoAmlBo7gV0kJvFG6kf6R/eU2xzpxYVweuDry6gRbZDYvejb6Nvm+XWXsQhTVpZn5m/tMFSAu9qMZV2eyKWZiR+IrVg2Jx45ujzdEvF8mW0AJRonZTY1NjH9YZE9ewSHoZGdUhWpyFz2Uk9YpVoa2QG7y8sLywewtFYl7FmpQnUx65KruZMkt5z4mKVbkcK+rFihLT8OMxrlVlK6VYlU7eufFsZORJnYhFuYFop1PNUqxKiEUqTSbEtkWGRBoen++ORyIQjcjMwoxIoFg9IhYysUmxokSBSK6zuVEnJFpM/JkxFmBPqoh6lsU63im0VEahgVtOKYGNj5tdhlhEjsWLuHq5YvVsjhW3RbgzvTP983onShEhxiz79Y5iVTR5ZxVhe6Km1YlYRCDa6ZyDUKzKiUXWRfbDSkPSXVwULS6WEoFoRC5bL8WqkFhRKUgmFN8MHr30wMj49jBeS/zsiQnF6imxuP2k0tzs+N4QxjIBnxGFdQjSQrWdVYrPMQ6RmYUZ45kIxeopsYorSfF4TNzI4hNltiWOL1anjEResbrg2Ex8pUOxAIFi+TSWQOllJFeVt+UpVheLxTu+qFR87RPT/Ngb9SKCYinWoTeGFAvyCpioA/MKsETwzLtiHcp+ioUgkYfFgnrmXbHafqIs48lOsfwl9FIZJxQUS7H8ib1iKZZUrA4fOxSrDbEeP0zp9mbk082Ubm5Vvz2PRxnf7hg++4/X2hCLLyv7leV9icXjT6q9W85Jylyx/AqkYknFkoolpWJJxZKKJaViScWSiiWlYknFkoolpWJJxZK99U/VFEu6YknFklKxpGJJxZJSsaRiScWSUrG6u4qtWFIqllQsqVhSKpZULJ/dFEvK9A8j9Yu4TFwBigAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMS0wNDowMHEGwVoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NTU6MTEtMDU6MDBSgYQvAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9UV04uc3ZnhQQRmwAAAABJRU5ErkJggg=="},"228":{"admin":"United States of America","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA1EAIAAABowgUSAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHU0lEQVR42u2dX4hWVRDAL7QUEaFimFAW2EuEEJKWKKlBKyVIbQ8VgaQY/UGlrGWJfFjJHlqLCjc1DWrNaEtUSCEtEyQLttD+WJqwKAYmmBml9RZrD7+XWcYZz3fvd77v25yX4XLP3Jk5d+Y78+fMd24xY8n0ZUsHGwNntc287plefW3h+Ji1wtn3zpq3bNmmv2/6aXqx/fjEvin9VeDHK2/4ZPKoHBTKUW41eQqpvDmr2l/uuvKx/YvOv7qWa0tJD3c+tG7lk0BfkQumPrqwZ5JPjVEwfTNK4QsFPYu5i6YtfuKjWg2rWWoe6fIMM6yeLT2jP7xssHOw/cQPXWe6pm0Yo9V2/1fzDixfv2vSzv3frPm6beDzn6+31Lx0/pK3V/8DtbduW7d1+0FtNBgfoweHDu499hJPWSYFR7hbfJEcvm889frhrW3w1YbFS9Gvxrrj/6blqL5OGdUwZS1Jkbbx8hRS2SgVtbF+WOsQptD34Ls/7jpqrUYofvPOzXP2vtl9untW3xeWg2PUNxe4wBHuSKIxkRxq0kwxrM1XT22f2/bpuCmd7VcEzAcLrWwgakOdqAeVc19C8BkFk6ckDnfARPHScOWoxVdLqPlKU5POXa61R2cM3Pz9n+fuHnz22OKA+WDhB7xEKif+OHbXr3e+37Zp1e711vrUe2r15dvmnHzn5NNnxlpuVLoz341CAWqWG0USVkQkRFprLhgWNM+/MPTi0MaA+WDhB8soA9Va5iLdKK5KrzHymriHeM5yZ1CAmnRnWlqkQkI/qGc0DKtBhiUVxqtH5ZiUNgtMAWWjVOnItBta0dHdv/EqKGtHJvkSafnU4CsdnzY1JJd85X3WNiZ/+vfT3/412r9Ox+RaP5tC//8nT6HXEgb8rBBHRh7nZ4WsEKw9Vo2KUTD9rBCOcEcSjYmBMgudFe4Yf9/tC4/sm99xdsGNAfPBQudTrFi+O8Ps5BpjmQLU/OgHY0rhC0e4SzcqryVfmSLIAqmfVPsJfK2lB7/2YyXz5aAvT3opoRz+BepYVpkRJfG7R/1WLQrTIYS31hJMEOMA+jEW1KBs8UUqJLT4VimQtk4pcmRJeJHgnV88Toqs0DIFwmccUEqRM6W4CjUoWxU1pEJCq/YmDeuDe255bvZYai3yReg7Gqbg1ArTaZaTuRzf6rwKa12R17geqTYZekujAVOai6QGvgzAJQVdcwLH58uoTCM0NXnn0NkdM/d89tu/+7YMrAmYDxZa5VSGrF8/6iGCYS3xYyxWFBRvBe+MgmmtYZgazhHuFl8kZxZy7YSylQEFzFJuAKIwv8iJOlPcmXSjljvDsBgF0wrz9V6hZViyuMqMokDaZMNCebofQda0ZFCv+xFk+Cx7FrRzlPR9vrpXAuOTdSz9lOYbBdKmGZa/oWs5IOnOwLTyOFRO1cp3fFCAmpU/cgep/A1sWcfaPf7xW7vO7e9aMub5CQHzwYsYlixLWnt20o3SrGK5M4wSI/DdGRSgJt2ZlY0iYUpWmF7HClgFFjrb0iVK3V6n3RmrETjazUm3JTF1YVPS5452rz5fKbP8GVh1rJQCY44qUXoRtb51qZQSbhX8CzT6sb9GgGzVolAkOZfvgFh1oGa5UenOwPSDdzjCXZdDZW4LNWYE35QCacrr8xsDax1NeSqdTjq1fPeHrVhyl803LJREhOS7MxyTNCzLSUnD8ssccPQbeKRBywZD8A88smFN/zW/3NH/3rZDAfPBQivPytqko9GYMgeU1FhXNDXLsUpMqMmGQUlNSiIdn0XtgSMdp7onR1bYQlkhobHViIzK2a3z+9Zlv3x63zpFUZ0VIokM3v2tbgwrCqRN6MfyIxvLnelyg+/OMFCgvwkNNb/BMKXcEHWsJhuWbKaTfeuWk5KuSuaV0lVBTZZYJU29MyhHrX557fi0E5SzkGb95YQVo17beXhXz/LeiQHzwQs0+vlbOuX+/mWtT3JLh97O9M4If0sHaswIvlHHakIdS5qCXxOXERXRj78JjUn57oxR3S+v3SgcZV9oer98uXJDfbuXqv/dtNXkqaFtpvGwXn+i92GsWA1dsXBDErIq6PspoynQpyDXwipcLPjduFeuXXs8YqDsMVbkLwGzZIXxCgKGYQUcOYYVu1oBs+wV1rpr3Zr45XoNqv8rMN9MR6I88n7RmN6jZvU2ValLlVNq7r6rVpPHNKyouATMUseK7uyAWXreI38JGOWGgGFYAS9xw4pTBgJmObuhXmeSlDv5JAf9Wk9KScevfgZLfedbrzNzcpw8U4yUU5fyfXmhNU+6ajz3+r6folzFttwZczno5ziZrgrNxsy3kc+mn2k4rEAap2UGzHIGacppuFVwmkW/Xtf1krPVcOr7fvT9KDcEjDpWwBFkWPHVl4BZvqUT36kKWAXKytawr3/5KWWtTWEpB8ynf0cv/auC6VLVOq/c76cK5RwzLTevjAXSS+2A/HhjdTCsHKfLtcInaOv7v+fcZ/C1wgd50zn+B8F5a0r2DlDkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1NjoyMS0wNTowMDc5OM8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1VTQS5zdmc6ss/oAAAAAElFTkSuQmCC"},"235":{"admin":"Vietnam","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAACnUlEQVR42u2cTStEURjHDyXEICSUYqGk7MROaRZk62WtbCxsbNhZWbKULJRk5xPwAXwAO1NIykskJKUUi//maozuHedy731+m1+TGffl9JvzP/d5TuMKhd7eri4I/dIxBBCxIGJBxGIgIGJBxIKIBSFiQcSCiAUhYkHEgogFS/GsYvigc09kNBDLG2+Olxeb+0VGA7G88WXgcKo2/7SzP1mXZzQQy1sIvo/cnlY+iwQiYnkLwY+Hjy3XIBKIiOWBir+gWAQiYnkLwaBYb+uFqqocgYhYZfJqcL6mdSioVJB6l1FCrMh8zO0216+UEkvvMkqIFTkEFXmlxCIQEctzCBKIiBVLCBKIiBVLCBKIiPWNNL5CUFQxIkwgWpPPpX0lpC6eL4afq4rnrd+f/XX0aLt6V6/TXt93aZ+B7tzqeNNScRkzjdRd6I6YsRLBy5bpjbZ9fePTqJSuXHdBFCZ0DlMvLy1zmJ4rs7cCy+ziXWuUZOqlNVm2d0lk/Knwom9irH1Ay+EkKKUr0VXxVJiRiHyY2bzOnfz9HKYz6ux2ig7OZrumvLJCeZFns+1jtPJ+/pTv6eiOLyJ1ZJ2Fyrs53s+tLTTOxiGWjkxLx+jNx1f30pERy9xtqxQZ30JeR7bw9IdYX6i2SdyL92w0ZxArESFIIBoVS/EUPgSDVSi1X6L+r81AdIRg1MZL1EqYzUA0J1aY2lWYxkv4ZpE+g1gZL4qWCjL9PWr9KcyeMJuBaEis4l9h8Nt4+Tkirf3ig7Mcgtq55bfxEtwTZjkQnbUQFP9m/gjuCRPtdA+dnRD8r+2/wW3TdgLR2Wng/O9eKJ09S7vaEQsiFkQsCBELIhZELAgRCyIWRCwIEQsiFkQsCBELIhZELAgRCyIWTC0/Ae7yUNeiWB9uAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjo1OTo0Ny0wNTowMGONX/8AAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1ZOTS5zdmdx4ikxAAAAAElFTkSuQmCC"}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/3/2.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/3/2.grid.json
new file mode 100644
index 0000000..64eb142
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/3/2.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!! !!!!!! ! !!!! "," ! !!!! !!!!! !!! ! ! !!! !! "," !!!!!!! !!!! !!! !!! !! !!!## # "," !!! ! ! !!!!!#### # "," !!! ! ! !! !!######## # "," !!!!!! !##### $ "," !!!!!!!!!%% ### ## $ $ "," ! ! & ### $ "," && & & # "," &&&&& && "," & &&&&&& && ' "," &&&&&&&&& &&& ' "," &&&&&&&&&&&&& &&& ("," &&&&&&&&&&&&&&&&&& (("," &&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&& ) ) "," &&&&&&&&&&&&&&&&&&&&&&&&&&& ) "," &&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&&&&&&&&&&&&&&&&&&&& "," &&&&&&&&& &&&&&&&&&&&&&&& "," &&&&&&& &&&&&&&&&&&& "," &&& & &&&&&&&&&& * "," &&&&&&&&&& * "," &&&&&&& ** "," &&&&&&& ** "," & *** "," & * "," &&& *** "," && ** "," && *** "," *** "," *** "," *** "," * "," "," "," "," * "," "," * "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," "," +++ +++ "],"keys":["","99","176","195","218","17","236","72","158","168","13"],"data":{"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="},"17":{"admin":"Australia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFyklEQVR42u2cX2gcVRTGN4ogJaVIHzRtqaJBS7RbFGpqQEGkIiy1KpY+9KEvhaQgSIRCSvpQ/0AjtQ9qER9MpJpQiMaqGyoKpg8VYUFMzUO6FUu7MRBfKhafgoaR7G8Xj96dyczOnZm7yXn5WGZm596595tzzv3OmZvzvJ5ST8nzRl8ffW2p9+8nlj4ycXjHB7MzX+Y7H37r499zHblNJ9+Ij7u6dj0+dif397zj54+/4Hlt97fd1wiXz3Kl3T5wN57OK04uTN70vH1X9t1Tb3fLS1u2c2Tulc9yU3fcveHel4fbbbW+yrE+fAxl4VDhufoQz+/5dYek1/yG+fyffW9+OHSx9My2I9tGRtalTyz+Faddes5TQBfvzOGv+04EjwPPrsSKgGEG17Rh8UkGRbhP0sTCMkUlk3ze0tnShYUDHZ929L33k5ImFHYd2d555lT4QQ8mGXdzwWJFtUxJvDxrGuO82XFIJolVKfc/+ern8YlFi+/c9e5T04/E77/deC4JbF9qX3r72RYgVppvvC2LFd/iXitfK988GNXiuoCMxu5Luy99sq5liBVu8uT0hyUZIXAcYnEHW/1pLTcHjXoXexe/OTf2/NnbygMgRxwiWdKrKj8LURwvjl/tCUesgcJAgSsZxKtd4+u/vSXpWNBNR8MC4sfzM4dudMqn44hDywvJ+qgoH8/zpg9OP40eVkOfFZaJwcQiAqtfuXy3Bm1VjwS3Qm+be1LitmynDaL3D/UPXXiIl8R8bTjrxCtRn6pMsYEjE1g7m2UPK/nSYnnMBWIRDGB3JbE4wllHiIUrQX/nd1bo5wqz7dUyVvKbNz+4sPV6e+nEAy44GiIq7BPIEaeUd7/pVBTuOJ/LrR/deqpt52DRnUgLywQ6J94qaVqRWC2RK1TqKLESjLFcwOAJ1hirxYjl/KqwqmPpqjAJlGK1ozpWI3XKR3MyBMzwyvtKbUll6//6VqvrWHYTQXsreyvnfmZFSUUaqr017T6qjiLTO6ZMV5vmmjRa1cGlI6taJvT6L7onfinPRFXe0evDtSvcq2i3VRLMSa8opyamJub+M4Ozj85evjFszXpFzRXGIZPMzTG1zeUK7fYn/cRzttlJbBIVZqZ2v797f3dxpwWJNRaZau4mLJniVDfQB/N9krlLspCukYxJop9YC1wPv4ly0tHKaQXq0Ac5SngDhFYLTj9py5R+PVY6/Y86ndwTO0Hf+M3x9JMwxFgygiTestaKLO5LczKiWKx/Y6zmKkhdIBnTZvYhq7QxL6FU8C2vDVcadHKI9t1HczFWc0F3sCWrrSgDSfbDi++PT37XnIPANsjFiumAHC3Wi4NZxSJJWyy7lqwmkMZQ3rEKrMVAKLVqP9CALpWjhccOXKnkbz258asaVo9QUpdEpSW2hyUu1CFt0gCrERhX2pUJJMm4P1QDK/nLE78dnfvj9F+jM99vzN++53Qc5R0CERrLIHrVEou30ERWK8k9tszJ89uvJ0ynvDI5dYenls+e9DgkgYwb1j0RVT2J0mRF9xFbyKoTJM5TYilaCDOkW88g06DTsFqTNlgpMANXrtOguCaIhdFey0liJVYiOHhs8NjFEVCnR4llYccBjsu1jN+VuuuLEst3MUx2XRZpmGsZ+ZWc+S+dPCVWA8skP7GnttOsECL1IetU+ZfTu6woZhtjyY3XgsuOZRmaBvVKrFBofiruh9QM6YQpsVaQ74LrC/wsFqtFDd6VWL45rDBk8kPuoCE81RmO7u+VZsBOORuEoIBElqz40YizXEm9JXdwaGeV1AVk6uEYDaw49MqslsFNgVSuEKXj4ze1l+pcJLGk1ZcV9EqsBqIowyQ36JFihEoMsjLdtPGWvwpsdWJRRsegmF+JYOQ5qxtiyx39UPjM2nnOOrG4ccGwB394hA1TBcscE4IE0LmN19zfqVxuL6aUkvKyLJ52yAlqPZaiEktRiaWoqMRSVGIpthL+Aw9FiM784caRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQxOToyMzoxMS0wNTowMO0Me5MAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0FVUy5zdmdlWlDKAAAAAElFTkSuQmCC"},"72":{"admin":"Fiji","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHR0lEQVR42u2cf2iVVRjHrxCtIY1lEVFQMdxsFjErbNAPUxKDNJjZMKKZ4rZYYv5CQ+0HQtgmRpYNzJylORRNW+FoTilps1Bcy9I5x5Zm7cpKDLU/WlrB/dw/nnE6t/O+7z3vfTfPP18u7z3vOec953Of5znPOe+Ndb/8eFnFB+dXfz7yq+GXFv8159Idqq67e9fV33xXeHlyx4qK2JJRL1aeCq7Fm6eX1cyl/jO51VnvPN82MWfDyMlJPXbdfQX5fOZbShb1TX90VTy9feDpzi7bc3tr/smbSuvnFX7/2g1Lx847VnrrxglHuHKu5uDN335dOWJ/d/zJJ+obd57qKynZvfdk3KlOYwwiyiDqIPulLP7nb7nVr6y/tyknOGRM6pmu85V/LB0AFkhpwOKuIO3Sc56ic1bjWy2/93YtPLFy/dE3R3eNn6gbB/pZvmJfzel9DiwjsOTg8hsNBzKtxRJISbCYWn9g6WDy+rwOLA9g4VyCD7pXyLyC5dVipYZJujmT52KUHnt3y5dHIgHW1LxPCjoPRhosG79sE8i8ukITi5X6R2ICE62o/c9eUNyy+D3ActbIM1hhQpauGCucH4MDK21gmVsCf5P3P64wJVhhWlYHlkWwbMQuDTfurW5f5BWsLeW76w7NCH/B4cDyDBZT5U9PLO/J6/ybaSPTQzbobPP2/MZ4coLv6c2OX9RlyLR5LE3wPqCtRCsoV1K3Qm/JWqnPAui6J+Wu6IfMEQIrOfEZ1SRYynpwAFi+asZKBe+hTDdEbQqjiXuMxGD7q/dnT+s62jNm/l0NXEmzpqwZR/YfYCWuHLpw/cGiZiu9Mu4n4zOzpH5K24/pnQCZvAAR9YpaUl5Jnf5Q6wwJrAExTTiqxlL+avBaj792E3d9MSyv+IHRgJXeSZJwmKsJWDKdm7qeqIIVHJTM1u8RLBvT8Nydbxdt3/TSLSVvzD0cjtKiPTca00U2RtbCRL3W469dHYK6ms1hDQWsijGVy1fOoq0jJddmj/kBlTu5slfde56Zf1uvVJy42n+uU4agglZo0Z7diplHQh7K+IpsdIj4bNH8LoOS9mIsFSz6A0wSHUZDAkf5ZAwqAIq3frR3eL8ETq1BgmXDOVpfFZqsy8hI6fYK+Taz61bbq0KmWS6hJCKotF5qmQvFrTnZY1XlW8qDFCDSIq7QRoAfk/kbXS5Hd13msXSq5pzUBKZ5glSriemnZtmKzG/RW/MsnXxq23ksprljx7ambb+CF9CoiKgYSfukU9WGWXeFQTLvSWukTDOTqh6aw/aQN/eXeecuk3blToBsV55WiE7mnWmWdkgHh4qIvEt+lpGZWjMtZnhLJ10wqXuF/jahbfQnCmAl3Zwm8pM/FRngoyo6aqYwEmDJyWP6g0yeaiH8ncdS6wkHMttgsfjHcdMfqZuXNhzfMEmGExwol2BxRdbQkvXZQ0vy5RU+oy/s2rR6T0EGzmPZtgReTzeYnMey1397YBHlAFb5uLX9s/ukPvzPqouP9D+9bOGapzaiH685tHXnfh1YxJqU4V6U2n5uO978fi4jaR0spipMt+IPrCAnSIM/14N1s7PW9tsGC3RAASs1qnXGpxMuS8i4roLV82HpgqqZLFAoz70o9wKcBMti8B7czVk58y4yLsFfpvAKmRr4H56ze/wBK9u9TC3TjEUBHfrJZ6k4MsAakDIVFouSqsUKFawwYfJ3gpTWg7xMEdyS2d6EZpp5xmQSJ4GIjIpkD7WuUJw8Q4m0qDM52gIsi1s6dLq9o6rp9WEMH9sXXAEmsjgEzkQbwRXnwkP+dE1t7bpxtKuqjLG4K7194OmYBjn0KCfMtj475VzVVHtg4QpleM6YoIyA/JaZGrAqTKz+ZBmUnyW2UF6nRYsxFoM17XRdzYFCPqNkmVFiCxtK/fxuZOuyP3wrS9roiTwRwIupUmXrNsBi8a/bJZSqllHLpy4jt3QsWiyTAxU6T+x1j0m3dRBkr8rkXpOawzxSomrZVYtG1E4iAEgNh6o6+HT10AotWrRY6TpFNNiP0mb2KXBMuH6Jl6oyQaqzWBIgXT3WXaE7nR0FoHH9rObYMZQ7nlJ1MRZXku8cJFTullKnVFp0YA1xsAgSsCLgJdNAcjHBajH1qhAlYJcpDNINbKsTWVqMsdzURkexIuTMdK+vyTyWDixgkicgSDoApe2cuwMrcooVwa6oYAEceKlgkXVTkZKK3Qrn7UgHVkTtljzrJp2gTJByZE9aLAmWPLkFUjLbrmp63aIDK3LKBAOBzJjLs7ipLZZECicIrCbv/Diwrgi3KPEy2dKRu4q4VJ2Vcq5wSNkhf3k17A2rOV2MpYJlYqUcWE6TNoyUBHuXxFgoV/g2Cn8Q58AalJYPa8S2DLt+0j5F4r8b3FQN3q2nKG+sObAGJVjmBwUyDJb75yen6V2COIvl1LlCpw4sp0PmH9sHDVgunnMWy6lTB5ZTB5bTKxcsFwk5dRbLaeT0X5mhki0/uFJaAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMTo0NzoxMy0wNTowML2v9jUAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL0ZKSS5zdmd4LVEpAAAAAElFTkSuQmCC"},"99":{"admin":"Indonesia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAAtklEQVR42u3WsQmAMBRF0URcIKULZBcrV7F3H8cJOIq1ioIjyK/knBEet3i5tVJqTRCqMwHCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBZ806fpXK/dEASHNYzLNh+GIFa+X4bAx0JYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAth8V8P7lwPhQb9oxAAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjAtMDQ6MDDXccruAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjAwOjE4LTA1OjAwUGem+gAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvSUROLnN2ZwZPnKAAAAAASUVORK5CYII="},"158":{"admin":"New Caledonia","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFeUlEQVR42u2bW2xUVRSGT4O1MNZhWmiJaNUAtgVkBBpraYhkEKtQJAVapYgkEiqSKtpA0weRJo3XpjyoIQQ0PFgDasVLiJGkIYRbI7HEhEsMJkiCCRctVxOflCj92rjkOOM5s8+czmW9/GnOnJ6Z7v3Nv/699q5lWWVlXb2ZquHSzp2ZPQKJUx0CVQVLVcFSVbB0IFQVLNVMAEtXVTpu6liqClZUnV76/Ts5OddKdr1UVTjj/r2LvrIrr6KD3qDOqmDZMAKXhZvr+oprWnvavg2Fuz7pLA+Fe4/suxL65rOmzqbgu43DGh4f3sGVPUt2FY4Mv9/z3uncK00Fa9vHtIdHLKqYFPg3ajrxmQaWgAksegq6Pw2NOdf44x/5T/0eufDg6FFSgemVi81PB+7hyqmZRy/nHby080xR/gGu8LsgCKA8XyHLCLBwFCYeXOwYRQNrXeWavBGtuNTzE+obAyvBy34/wAFZ+bUpwWfmgrKikGZg3fAM/AMsnMAkfWhj3VstI63CjoKlWSdR58/5YdZ31/OWAOJgoVQgUh6sf5AiMzlHCiVXZe3OOm8tQPEtt8/Bwyi7ilfKg2WCFE5Terb4w2FzQapm+fyiWzui5TC3eCW+OGofK2FZihIWHwSARWCn8MnwbqKgSc7T7GWktfXT1rQd8EcXlDwwqq2baTNxF6nEcHKSV8889HG3FfyJaM9n9nOU0kOti73hqRURf/T1x6bNGXuQPpP59MvYLgtitPWgW32zunnb7WM/bx6/emLg6pTwazNypfo5bqmoCQeLaeidPeHS1CNvrGgoCi02meyjH/UcDl6lxSAzllSKI/eYeBgFly+D/Fv8GTEFy5FuXXbfxnF9lC3nUdp+Hbdj9Vc+qaz6lhclUoHj+fOyN/OqV6mL5/DFUK9KOrBaJ868u3C/eQbCRUCKdgO+BVL1w2trcqoihx6uyl7O5o85WKxb+WIoLkkElldFUDoZAJGu5lVX3ZHdB14Ax/Wvv+jaEjxn/o4kNlkQ/dfkcUrnWdNK9Icg/G569e0/c382n2ZZEKVjSch4r2jFND6UcVz1oSF2LEk0YO2464MTwVq3iSqagg5gsRIEL66Qioj5XqHc1vLk+vzIsZLiO6evV2iGuBRKsMxju/0e2g0yY+FYnIbwCimUUp5osOIrecm5pEg4WMRerxKPzD32VaFsN3gLFj0t8mLmuI4Jslaiv0luHcs5WAAkAzuhnr65ea7y37G0FDpCTbZGvQ3vIEW/Sq4KZcZy3n+PXXzt4V27WR6AZTKI3vbc7f13/ElmLNzL21UhgKbKqjB5oLfcrvJMdgnNG6Syj0Wikkf8UCDzqvjS2tAG6ZCF99jYeRXh5Uks6VhAJq9QFtMvtqdKIfZpSydaQXSSb+wHWti0IWPJ9SBIkbGAzBxiP4tgOqU3X4/NMEkmqzYKHCnKDpbczOFVk+LLu9iPzWiZcwRW7dK17U9s80crKxpmPbLdpEiReAjUNEjtjoXTcCc/x7c44L8Rq0+9vGP+bD9HKT3076PJW76sf9ZPDbzQ0F3+S3xtTFkc5UkHAjt5SN7pdm3I/XWnV58pOGv92vLco4f9H5900SF6Y/AyOU1KmWMXkh1Dk/3BgX5V/2EbRSqFwbKs9sjCVZQb53gBE/FcnmuQpRDI3O4YUljBXbFIabBuxiv2CQhylexgxVYgi42sxFSRSjOwbi6OTLMM3SCFDzlBygleXBn4Ny8tfOkN1oD2TzMeRnliN9AtUna8cETaEIP+dMMvFYLMAMsGmVVaeX3cOtZ98YEVmnzvQ7dtyPmtbkN4svqTgvUfaQwsBvymH7hoqp6kYKkqWKqqCpaqgqWqYKmqKliqCpaqgqWq+n/6FwbRnu6ejHKxAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIxLTA0OjAwcQbBWgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjoyNToyMC0wNTowMFNCnfsAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05DTC5zdmekLcH2AAAAAElFTkSuQmCC"},"168":{"admin":"New Zealand","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAF0ElEQVR42u2cfUhdZRzHbayXkaxRNGVUwq101RouGG4IBRk20X/WtqYTtmaMYq5po0YJhasFc7VKzebsBay2lVQWTUqMXNFgL3YNIyVaZAtvRm83KJh4heB+zh+PnO7ZOT7n7d77++fL4Tnnnufl931+v+f3cm7OcGH5B7Vv/THy7qpj9TOXJ4pmVpnx4NCR3jO5K0erptt7cxbccNUTj2phznXfN9Ws3rGhsGsf7//liv0vdowPvXzltzdfYkbu8iS/uizvlseeWq7TO9fMiNn9emiw7GTV2YU1p7bPo9/hnyLlJVPjPTsiTdVjI33xE7mRrtJ/9+erbxBMiSP1hc2lZSPT11xdHGdZ45/2Lx5MmOl1rjM28ffSloZD938xdNNfFXntJ3U6dkoseudXOqJl5MwCuvx8ePf8p+dZr8N4fOLe+LIla1Y/2NIrxLKFF1jctR8PfBZ1mWSKxuI9TjWWLdEqz8yNTCpGt45OTt5qEEtIYwcxB+ZFxxBEa5ZMrdhiLHqsP3b8E7dIpmMK9TXTrHn5oqGzDt3d2faFAaFdIJaN8bNJtMYv5s8xsea04xH5HM2ljcM7GsXaFHKEd7AZUmhczk+imXwiltdmRefwrk+mzDBzCx8uPrD3fBoTyz7JMDp2NMThiY9mvnnAqSn8sGwgd/SFAByOUOLGZ+sGXl8Ehm6ECHhuiK+E2P7pObUt2opQiYpxnbg2VjdZnSpC5pRYal/0AtJi3QujJWrldKb8Kgxe4dKcO4sOHAXfiGy+bU/pwUW15Xv+pCVEGwbBB4vonlTE4m6wI/yxY/S1c1XBEgvD99DbO3O73xnsue+ixysMbZ0o3nvX17RwNxQmkmEFixjQVMTiDBfsCPvblicqu/JPXP/KI7Ew6IPGTbW3P99nECuJtITIFKYSZ7CoeoVhwGPLFuevLAwPsVomtje8NI0RxCDSEmpicRDONnpZ9wWx8qYKChqHwuAJcmAn4AKqLaKxQq2lwkms9ElCOzxtRHevePXuTUYLcSy1xfJ5rs2oBl3NaCSXLHuxNdoMOmOlAYbTK1S1V3i8Qlz6zBC85x6udfyGgKSdOFYqtBNzchrHMiPiV6No5r4Y7dwidsSxQh3pdlhdwpmscv26BW0DTjOhtk5yTovj1Mg7sWyzmEnsEAc3ggVJY4fuIW6uE3kndm/dr5oJUPvN5pwgNALf/33d6V1n8ShpcXlNnKZxUgo1mcCxJpMqVJ3qBtS4fXLj51qPx08Bs+PtlwC5G7Xv7mo433ZUPR8TqnDZONohU0rhJU2PA+Gh+ZKoQyxVFTvVoNbjnFV+7VmpjCpg/6P5hFK/rNv4XcMY8XrDIHqXhHZKJjuaKZWQtAr9lLIZx+O3Yaa90GSMfGfe1uaOCgKbfG2w78aa5mcupd0f3Unahy20ZnP1+s5hT9LYagWpT8JwqR7rfzSK07OgU42r7YWhLTBARjAlSS+IlSHOARjUout/THEB30TD4TDP9/Tn3fV9a/XNFkYQMwS9OERnYDV9UDvY848pdEimOCKsQ/+WyJt3HNEKkCbHgOnhsIytaL24saizxP+DvE9f6XCIY/lIX7Crvip5r+l4pRdnDpZ1rPWHXb89d+aeJze0z6dfM3KXJ/mVWxmxWUWLyfdDNZAWdFVvQVVR/Tb9yDuaSTV5XGegxmKxQHJhXPNxpncThhy8H6R3dQwq8ozLSVZFk/F+tYyOFq/XIatr3v2P8TiI8HpAslkt8n1OZhBLBOlyaiWo8Yh4BLNDYwkKsQQFhViCQixBIZb4RIKisQR93OSyKIIBa6xMMj2ZMRc1ASWmUGLiriFVXKAQSzSQln6ijJgqD2ouqOhS241/dxZiCdonFvVb1J2qf4FJC3cN4xhs1lUElo7/4kelvFGMmURaQlTcLKJKR2Jh/swoxBLUqnrlSxu1TJKWEH2IK6JKRzfF7KzQIhpLUAKkgoIhJ5akjYVYgoJCLMkHCLEEswT/A9NMKns8VMzkAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIyLTA0OjAwQO7bxwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMDowOS0wNTowMKNYy9EAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL05aTC5zdmeA9JKlAAAAAElFTkSuQmCC"},"176":{"admin":"Papua New Guinea","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABLEAIAAABZ6mmjAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAHSklEQVR42u2dS4gcRRjHW5RogmaNLvsIJmvCsggiqBfFCF40KOhFxWgQBFH0pB5EQT2YY/QqqMEHeslF8JqgiCCo8RCJ5mAMohJxhYAQQRRiQGZ/A/Mfaru3+lGv7qKgGHpmJ52uX3//79U1xX3PbHpi611HL525c/fS8a+3za4sD26+Y+snKw8P9P/ubC6K5WKhWLzx6ov/vezH9/ZfsW3HzfmiVCN4Yufs/Ss3RPE98YPFPHfVRd9fcuVLH25ZmjuU7+NsIzsDS+cHDmz6beZxJLLf91aevYJVKpHcbdme5bkNWCqRr/605YP5z/mzbMPy3AFYG0hkDNYrW9DUweptFDmASM3n7doQrHWiyAFblDIc21v0k5tXfl9+cHXfiy/sePf063tv23Wk5xYr0ijSv9VxFsqA0T97jp5YmP/rj8NfLL4FZIMDax2JNC93lhuL+Ze/H/126eT5+WOrc59dWPhu79zl4PXDTbfu2X1moGCVJVqnYMqOdqWVUqSYOXLmy6ff2Xl60GAlEEVGNiNzSJ4ipXMGawOJHLQUlrgHPz//0MFr95u2Suez37w2e81MKlfPE1i1o8hkne5mZ0LcV4YUwOF7pXLDeAWrTCLjSRCEWhKskT1Ysdkt83yCgdXXRCuX2PQmp44bYOE/VYsgn+H1uc3vn99++5//vfnk9nOa5XIOnLWNDwxWdBLZUWQHBCw5KOCYg8JURmptqX796LFPl94us1XVvpfC5zDXVTNVFAVYMbfrNDsHzZibWGBvNC9VLYU2M+DGk52PDqzoJLKug2+EBUR8wKSQ8RobZmOTbGa+B6BrJHccJK4jBau0o1Xm2Bxwzgc7hOVQYeJdJK8uRs0+P7ZegSLfqMFSvEyJjAKstWUDJuSM8gtLy2uOs8x8kuPNoLGfg+W91q5JAmBFlGjVu1+Qqs6YK0CKlOs5bCEoOrDm752MSKPINaSQOTwnP6A0m0MVsKMD66lHJqNNLdLMJ3Vr4apz5bHNnO2gwTp8djKwTA0l0nD2O3BjRf5ci1q3vhexp0/noYhB8hi33DMap56bDI5ct2s0akuks8ixWc5JnXqfSOl3jqNU+xusxQ0ZDCygOfLKaKx+PBkXTk2GHv/q2dG4+8Bo1K5FtodMbJXm03mNPdAYEHcex5l3qfTZuPk2iCh8fCfeHueg1lR7UEl21AWl2XULLIVYI/BSpHTwLiCGTbRyx3Oh9TXAgQ7HtXKnr9tn2MEURBQprRiqWPN5za4NyMcCrzKL1QypOGuRZOHbgAVGGpqAspao9ciUd+UxWRoFWAicCh8+FnjtOz4afWrXaWO3+NuychOWDJiwT4AI0C7AKhPKKMA6eGw0kDysl0rkoetHo0+1SCxKM7zwrtYBRRxt3gUv/pUpHL3UUosYyjVYLDPi4wgWqzoeTEwiJcWqhSAtVFfHhvpAWFmPFz6fOu8+S2GJlXR60tFqlNK1aI1vhNUpy5bZP2mIU8/31E43ZLCcS2S3i2Hmh0qOVNciN6gDGunc8eczWPF0tAYreBuiqUK5Qdfo2t/qs4pTdiuDFV1Hq/8nfAzR1KSrvRRq2tb1DdNDsNTld9euE3Z/Co0uyxBHBLWnlFkd+WQslrsIrm66lRJQt2fS2R6tXVm7kkK7+lVgpJn32oIYtlbIEmouKmzjDclV+9piWolWBUVfm8I37sQSf2uqQ8sCmsC1QpaQ5bTvpupK8rSLC1vFmdB+o+92a1PNKNJ58UTceS3amF6U+ZDZOhbLmb9YtElp6oKxhFo21iV3J5F8s1lnNAdlom59r3X2aPXzoIcRM5YmS40H+f3stlW0WU6t6JUNlhy87Bv3mjXhqK0y+yP8CHSARKtF75TuZjPuJnUc1Rbuml60Wc+Pd0VV0TwTapE9fy6yBCzNgU3ZqpjBYn75jdEwlxNx9JliAGXsFmelR/zHquY25q4L27jkzHhausvDlMMeM1hcOO0CxTZoe7Gf5cTnw2Kp5GEvQdyn7fS/dQBgUWEEKeapzUJS6cdiCc1l0+PuAn77h8Zs+uUDSGS3lqPs4ZEWj5O0uQEK18sZNqeVytYBDqPINt9fBqIFoLlW2Pd2nUA/hVekWw0cRLtOsj8GkxhYCKuLJGc8EuknisxgjS83MBEQEIeSdO2rD5f6NubJWCx9kidU2jPv0dpDsDSFweirIAaOIocGlrnXwzATGWHbdXJr8mKWyAxWnrt7LjIaB7+I+ZJldKKoRaa1jVF19OenwjgEiQzl5kcHFkkEF/s1DE0itaPVv1AWscV99E6F6qDKEpk8WLTZYJ8YZKc0BcoR/UyWyFSiyMB7kNLnafMoBGBlG+ZEIvsqhdVP2gwnw94niYx6q0hG2JbiLJEJg0WfAmAhjiqRvM4QpFWLjAIs0DEljyN4VzllmlYtsogn0TCcftEhSGSuFebZSUdrBivPTiQyg5XndhIZ53bcee5hFJniL6zmOf5EK3MGK89Otg7IYOXZSRT5P9xaBqF0vVdrAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDExLTA5LTAyVDIzOjI5OjIzLTA0OjAw5pnQcwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxMS0wMi0yOFQyMjozMjoyMS0wNTowMNZnUvYAAABSdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWovQ29kZS90bS1tYXN0ZXIvZXhhbXBsZXMvZ2VvZ3JhcGh5LWNsYXNzL2ZsYWdzL1BORy5zdmdYggSqAAAAAElFTkSuQmCC"},"195":{"admin":"Solomon Islands","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAFcUlEQVR42u1cTUgVURR+tmkTRFREiEGLWgi10GoRWoss2tirhVpJEZgQFRT9LJ5QGlEWUWGERFESBNWioqIww4wQIlCwH4SMqOiHFikhCVFgwftcnMd13rsz99yZO++dzWGYN++eO3O+Oefc75w7iURqw9Gu81oykZzxuCXHmezng0mT0fTnHO08XZbkvuqnNp9t3t039Gjz0obxkldNs8+pcqyo/+HcioS+gpmrt/zteRr+4/ahN0zTBtOl8y8XoK+A6UHTrVTVJS8wqTKhr+DE19vnPtwv3t/w9tmscIy3YOPOs71PUo3X5rw7HWOPwqXXnvfFOB5gggfSAROu7E7eaZ3XnsihJi1h4KHN39aNDazf03pgoMzGm6HeHnRBL+bgI/hG5b1MfFKkwTRZl7rcOs0ETI3VLSWllaV122rKpkwCLPgkGBLHjTXtiwenjs8b7/x36OKurmNf9qnXmD9WjENHhi7oxRw89cbL90Q7w/Q1SDCSf1Kjxzv8gglSBVPJ8Kar5R3zS+o3lZdPAqyaf6e6X37tG3w/Y3TB59M/LvxeN3Ll18K/1TAwJM7DlyBEemZCGreK/2IcjInxqUbMAecxN8zTyPDZr7cXwiIFbjAwUc+0va25s7QWYAKMVJnwupk1a4+U9T+ECamBKbDgRRiMSo4xpgosSMwHc4vA8C4AyGdMwEtrnjNRMFHP5A2srJOjQZBKBCl7j54GQSozoCx0QNZ75PVMAJMOpHJ4LMibyd5X31sRhnBMg9Ek4Y+JXKCBGHoDAtoGmHRSdd6gzLqaU89zgUnLYyE1hlFpNrPk48HaF0U4v6q6eUXfT16zIcxh/EWv9156vpBmfjhvlLaHk1fZDnms1AAXmLSABc/htcjP+FUbLvrclRd08GtAqtZeVmSb0lTGDz9n4gAWazILKHSVDVQOT8+Aqe2Mx/3xdcCq5EzdJ++2rdziJph85FhG5Yj0MUIYsrSty9tuvKlwiLp0bV0ZKMz5zZnsgcnbYxk8IIQnQOdMzb0dn5aBl6KEBY5xHtcYERbxpTqzQtz9MOcHWExvM4IdXcd5STU9jw2LnT2UGxd64wsmPR7LwBjwXl5cFM6HmoC7w2DRcopxbc41MHEAS+NtpsCiRaEcXJSNdpRwWKXQyymugSkosLQfN8gCZFSAEdgvHPsmV11jzAMVeuOymosAWPq0JDIntfxCE3xAjVcvcxOLY+UU98EUcFWI1dwkpKiN1mHCgUEvG8lpL8ylc6ZC80wBi9B0rYfK3UR5R39lFCy/SUvPRj8HyAKTfqZ4gSl7e4wPYMGECGe0OZim3viVSvPOdHVMSltgDurcQuLHaZhjXc3F1yf5BhZt9MNqTm30o2135j3pACXG8WotpDPxbPSzUKErBGog1FCI5Lqn6vWbkZTKRSE8ZZiWiSbAmBhf1Yv56Cf+Jp4s/0hLh3KsgI1+xjlWjka/SGtzAiYGugEGRgDCMXwJghHbdgZiWsqBQRf00qY/qc3FE1hkqQ9D0m4qun9motGPlQeCLoxP03Pownz0+8AETM55LCStEz5JWRn5bfTTL7xktPIp12M+OVagHlqEtHSJxwpW4XegFMPFM3ntmxPJ1+gXk83pAqY4dzc4s/OOa9+cgMk9YNn77opGD7iAKR+BFc6GBVnN5TmwIupJFzDFGVj2vjug3xElYCoEgjQkn2QBTLgxAVN+9byLZxIZKt1grZ9JwBRPYDF9+FDadgVYbFupCnN3ikg+j8XaHEdJSzFPQSfvvAm4mKSgCVJZzYlkI0hlNSeSrdFPmuNEsgFLwCSSAVhRfQdcZJ4DS3gmkVaAlf1rluF8ullkngNLlS+vdx0u/iKeSaRf+R+wRTkSd7OJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMC0wNDowMNdxyu4AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjI6NDQ6NDItMDU6MDAFxSQoAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9TTEIuc3Zn7EIwTAAAAABJRU5ErkJggg=="},"218":{"admin":"East Timor","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyEAIAAAB1xzWqAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAADBUlEQVR42u2dMWgUURRFp7AQEZegUSGbrFkXBUEtlCCmsBBktVBUJGhpsxYKFqJoYxOxE1LFFGphYEFsBBstUkXEwmCnBBERiZDCRqyEVT638MMww+zO+7szy+HBLcKkmT3c+/77M3+imfeNB5ufvWscX62ufKseGK/9+fylcbBWQ9E8GkW3oqtRc/rR/pWNX18faX0cO/xj9kJ98hiQoQZgScemR29vqDys3ty2vf3zw9yT3VuADDUAy9cr18/frSx8P/G8M/nr94v5kcZWQcYtQ3OB5Ufk8uWF9vipv9feXtxTF2Rrh860d3VwMrRHsOIRKbx8yIhLtEewfG0tnR2p3FBExiGTk3Fb0a7BSopIIEMNwEqPSOISzQVWPCLX9r2cqreSIPNHGNx6wDKLSJwMsJqhIzIJMn4SwDKLSJwMsIJHJE4GWMEjMh0y/AywDCISJwOs4BEJZEMI1t5zrooTkUA2JGDdn3F1Z9RV9v/a8fR/pV+px3V6i0hWl2XehF531bnnSpClw3Sp6ap92tXRTa76E5E4WcnAEhwCS/V4ypXvRid3unq16sq/ZlARiZOVpsf6NOHKx0sYyZn8v69HrrJ7VYhVZDpkPIVRCLDkTL4bpVdvXtWfiCQuC+dYbxZdpSNl5VX9iUicbABgCSa16sIli1fpSvVb4eb4ISISyAKCJZg0Voh3VNlLeGltGAIvq/FEOli0+WZgaaygZlzBJ7wESnbf8vHqdvpVhCgEqeA9ltp2OZk6p7iTCSC19kk45sGrP807MA14r1ArvqQ1oD9t93FU15VlCh868lgJFhQseU+4NWCIyOPogBKAJe8RWOrGijmvwplKBpY/hbcaK1iND+I9E/5UGrDUJ2n9yGMzqPGDft224TzoB1gBNc8qTw04cybAMnuZgq0VwDJ7/QuYAMsg8thOASzjV+xxJsAyPhSEnwGwckUe2ymAFeTgNW43YBkcFYkzAZbBXh7OhJodx83Rj6jxBwSACeWTJygfaUKH/rNy3Bo0F1h8CBMNof8AiPYW3AG3OeoAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTEtMDktMDJUMjM6Mjk6MjItMDQ6MDBA7tvHAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDExLTAyLTI4VDIyOjUzOjIyLTA1OjAw4PjpFgAAAFJ0RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hai9Db2RlL3RtLW1hc3Rlci9leGFtcGxlcy9nZW9ncmFwaHktY2xhc3MvZmxhZ3MvVExTLnN2Z3RlsPAAAAAASUVORK5CYII="},"236":{"admin":"Vanuatu","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8EAIAAABPzVTaAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAGpklEQVR42u2dXWgcVRTH7ya7yqamSRMrBELRSBsSbCCYfjxYKMQWRQ3WVvG7bfyuIvhZyIOI1lZUEhAqolgfDFptClWhIJpWq1iQtGJL+xIh9sXGgilVUBqFKvntwgm3M7kzO7PZzJyXw7A7Xzv3N/977v+emTUrVma7aj9+58i8kSXrD3fUdXTeduzv+sauVRqJRw/WfX1tZ7hv0xyNGTAvmIXEG8ZyAw01QKbNqTEysIiNC/Kbqh6VkGlDlvO2SSxYXpDtXnfZ8bahJDWn3jDx3RjmmUfu21a95OotzXvMPhfI7rz3kq8WjpUTsvI3f7RHnLv4lnLm5t+bjxhjTtQNfZh7G8iadl4+bA67Q7Z/S+2vS9vT0LmowgUA68LkT/+TRUwGZBorAqzxu748Y6bhJSH74czghuzIYx/dviMz6g7Zw8cvPdn0rAtkqgHJGEzYezBdE+2jmcY3x54/mKmVkAGWDdnm6p7Bqj8ByB+y1o3VJn/AHTIdwSXJw5s2KvSCzFaybxfvqs/NDweZlw2bhiZMD6aedsOqGzsHM0+/X/Pi+dyes/O/ec0bsi9eeev+3IN3/La2NzPhDlnf2vyti64HMi63dotJgnsGH0tCtrv11e7ssD9kny4baDamZ3L18uzycJCpfqTCIHWHDLBkBLI1J1a+W7XYHbKXtubPXdkTLWQKUNxX8iLJe1Cw5OgPaABIIiVRAz4Jmcv+mRqPAzJVvgpVLC/I6P5syCRqQIbaJRUyjZGBZUNGIk9Sb2uYhIzBAd2rO2RxF/mkZ0onvnOLGCwvyLAnJFgyYm1gc2B5uOxf1l+okiVcsfwhw/fCaI0PMs2lUgSWDRnTREBm4yUhe6P3qd9zL7cfavkks7lyINMYP1jmipr8eLhtmX8EMia/vXKy0V8+a8x+AGTuRT7JqCRLB1hgJGDCf6LbQldcXCsvyKiqACMvC4Nvt+16/PXs3QpZYsBq6ajbRLaEcfDz+s+fqNs7sf3Quvp/Tp8fbq7fwSeM+NxtBRuyvqMPDFSfAiMvr59vwxX5KGQVARaahIkweXLkvQWnLvx1bGNDr38EOJJxl4a3I5qEPhUgm9qzl5IpZHMGLJBChyQ0UpnIfljmcxs+oAyHl4SMYxXOx9IwWa4YXyWZxggepgAI4AAXmpbsyk7eaUg0hvUlZGxb+qCBfI69xVeuiOPvHhmNBt0qvjhb52P8k3QmaujOJBY0qj3BDGp8zjp0TBIs9ubutrtrqku5YtBKMo1hvQLfr8FIdnygQ5MADeuAIMsk3VLPpOYR0bM4flJQyNwryTRGAdYUEIz7QIH8yTYdZMZT6OYsSwLspG6BWikemEt35l/kU0q5osaSFItqBVBAn7yqTFEglsHIzocwI6T+lbMJ6aBdyhWBDM3TGC7OABYr2Z0XoNAABeCmtIfchc/BDnRYX44ryw+WDVnhtrE0LKpYyj6jOh+vmYy4z2EGsBhJgQIZiexi0KeC9z0FFnmVzLpYk4aUg4DZAkuevyzs8b+ILpc4qnVctooDu2iPZVx8I+lLkZhLmC4yYyjUi/VRNdvTii/Hcu8KvYqqtTuLsSuUqTcZkvSx6ODkOFFO+KBwbFuoYhBgFQ5fFmWSMHndkZq8lzV5t/GS6iVnBsGIZaI0IOR4kGU7wY/DbrCTdKlMClNFgGUbp6Tz6AH5Fl0eTevlvNOc0TYk2hnHo7YaywqWHclgAIvGs5FC4cLVO7gboV5TOsDkNFNZ+F3dBxY9Z8w9420b3OJD3y/9Lsj6c2U/T87r7Au+VUSFfsAksygbKZo2qvlB/3KaoPODRZhuOt1ylTFbf1w2ZszOoe5bNIaNEaXJdnoOWHxeSkZll83Y3ky4FzAVYVrxR1OrwlShYJE5oUwsB1CLUIV+smSZNRWmRIElOymXQmH30mQvg07C5HREhWnughXHwxSl1LkXkVKYUgOWhMnlGcOgj38VYWIsow2caLDkA6vuT0UHgaltTUO/wpQKsGyYpAFhv8ch6NPPClOKwIr7pSBFmLD++vevvkabMOF2A06VC0xMASlMCpZTuZx/4W/Qd2JRJVGcHFCYUgCW/RY///eRKkzxxMq/PpFWiwd9ua3ClFwcA76O2+v5liAwyene7Xuv69c7PuFdYdA/EAhQIlcRMKmulDGiTNNgokKh9BI5hSnN0eX/c5h+UZhU+QJEr7+VC14iR71lJUz3av5UATHcm6W0EEXjDFFh0jh7BqnCpDEysLSqSTO2yMBSmBT9yGKZqpr0vk9X/A/7QFjXjDFyyQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMjM6MDA6MDAtMDU6MDBytDGGAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9WVVQuc3ZndQMumgAAAABJRU5ErkJggg=="}}}
diff --git a/misc/openlayers/examples/utfgrid/geography-class/2/3/3.grid.json b/misc/openlayers/examples/utfgrid/geography-class/2/3/3.grid.json
new file mode 100644
index 0000000..f803145
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/geography-class/2/3/3.grid.json
@@ -0,0 +1 @@
+{"grid":[" !!!!!!!!!!!! !!!! !!! !!!!!!!! ","!!!!!!!!!!!!!!!!!! !!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!","!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"],"keys":["","13"],"data":{"13":{"admin":"Antarctica","flag_png":"iVBORw0KGgoAAAANSUhEUgAAAGQAAABDEAIAAAC1uevOAAAACXBIWXMAAABIAAAASABGyWs+AAAABmJLR0T///////8JWPfcAAAEhElEQVR42u2dO2gUURSGL0IgtmohRLCxELQQ7FIYsNDCUmwEwS4iWClaKabQQsEmWARrC5sIBgRREJuA76AYUFSUoIJETKKIRCQWX3Ng3GV2d+bOuXf+5rDMbmbD3W/O4z/3EUZ3nD357JGsbH92z/L5qeerxeuhqS/2fGfZwW3QEMgKLFmBJSuwNBCyAitGqXHw+JUtrzZoNARWZXZ89dr0m133hl5u+76Z14eXJjfOL4Pa2N+JA3Nj9vPFK7IDgZVHqQ8uN4Zmp74eubDv5rqPZx7efrtz5dzak7X3ayuLh34M/RnBLqxfXPj9eG73h1M/R2buP53+NozlytXZO18+TQqynsHKTzE6PXp907u7YAQ6wNSf5Q6XJ2a2Lgzj4QRW60IhfsV6pmrt6xOfL/06Rhjdf+vi0RfbQQ2U25O9tQgsG/jqQKrow4CMMMr1B3vnp5bGgUxgZZKS2x+4Wct/krf3CjnJBJ3eJfvxgJS1pPwUDfhRPBkBVGAlYPnxvIFlq86iPwO1qrxaHeVX93uGNuRViALewCpfCljfloo/C3F+2mZlhRSR6mTxZK0Gi8IeIbGpOig/sBBK/IuxNYKF02YgcOnxvVcccSE+WFXlTPVJ3yFO341BwXvFwQusATonsMgXW+2xAKhYkVH1kJDW1wAB68HbNd4so+e/cRQpee/kORimOjIwwM0JqaIG5kcpLH4yktxQRqIkH7JdtkGQys9XWUuC4XlyQIiT3OG3wKtM3kMmAWRl7mwbvfU1mL3pW56bQiFOHWEnypUXLQmU4MX3WoDwTAxx3v6pk/XczG5AeSfMlc+BwIt5Ae0EqFM7yHMK3wBY+WlLCoUuwLIChDzQIB5LYP0nSyM/IH8izAmXnDqGLmY3AJm8V6/6O77fp98Kfia3MEz5NWHiQIbvZyQ9TK1xNx/L9hZle826uncyYgqqjsCieJbHGhyvMsJyK8DCdctXVWXpcAiszBvGedSMvYZRF2D5XOyQrk0+FFaVDOY3gVitnuAhYZfHym8pbMNgqW9Yt7LVUrBsEJTynpPo4KilQ20o0aGqlTwExKbCojvlXTJpHi3q4G2DNbv4QsExE7A8LHq005cVFvtDSlVhBWt7ZO2c0vrmNSSpvEs4zaPxLLCktrcbLJ5CdnwQQN0t3Qs/OyomsPGaFSBUJ8bcpiDzrSJtnYiVD+MBowb0ubowya0iqX14Rhnc8oIqz3en/T/T2rvB23YgGe5BiiejKQRqSBW8xvKu3euBHA5Ai8v2O7VKPHQFtPwrYUxJhwm7VnhsdpIPHrfXVD1+TyXorOX+sGvqOAKmxPjfO1keq+eHis/EbDfZM8ZSOQQq0tTknLwgVWrMveMJxzqZQqGwFrDSOgpFYLle/8h3pXi6jsByt0zNnqWT7gF0AsvFvFakWvK2PE4zFFg9JOzgRcZTVPC76/i8y9+yHxiY2qMxczpVWmD1mWlZBd/q+IQwciMw4rXdZtwe6ZvrcZgCq3Y9LF0/NIhIFPwoT1L8cxrJoCHW/ymwZJN5AIKeM9k67D/5FMR6T3YJsAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxMS0wOS0wMlQyMzoyOToyMy0wNDowMOaZ0HMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTEtMDItMjhUMTk6MTg6MzYtMDU6MDALjN+WAAAAUnRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FqL0NvZGUvdG0tbWFzdGVyL2V4YW1wbGVzL2dlb2dyYXBoeS1jbGFzcy9mbGFncy9BVEEuc3ZntCZHjQAAAABJRU5ErkJggg=="}}}
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/0.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/0.json
new file mode 100644
index 0000000..7ad337b
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/0.json
@@ -0,0 +1 @@
+{"keys": ["", "71", "24", "245", "207", "238", "82", "132", "205", "51", "65", "242", "231", "186", "165", "114", "120", "2", "13", "235", "116", "113", "41", "234", "34", "90", "78", "48", "173", "215", "150", "75", "79", "224", "42", "181", "158", "38", "225", "211", "11", "208", "212", "167", "76", "39", "164", "77", "183", "104", "87", "95", "157", "59", "49", "21"], "data": {"150": {"NAME": "United States Minor Outlying Islands", "POP2005": 0}, "215": {"NAME": "United States Virgin Islands", "POP2005": 111408}, "212": {"NAME": "Venezuela", "POP2005": 26725573}, "157": {"NAME": "Suriname", "POP2005": 452468}, "211": {"NAME": "Saint Vincent and the Grenadines", "POP2005": 119137}, "158": {"NAME": "Nicaragua", "POP2005": 5462539}, "132": {"NAME": "Faroe Islands", "POP2005": 48205}, "116": {"NAME": "Mauritania", "POP2005": 2963105}, "238": {"NAME": "Svalbard", "POP2005": 0}, "65": {"NAME": "France", "POP2005": 60990544}, "113": {"NAME": "Mali", "POP2005": 1161109}, "90": {"NAME": "Jamaica", "POP2005": 2682469}, "234": {"NAME": "Turks and Caicos Islands", "POP2005": 24459}, "235": {"NAME": "Western Sahara", "POP2005": 440428}, "173": {"NAME": "Puerto Rico", "POP2005": 3946779}, "231": {"NAME": "Saint Pierre and Miquelon", "POP2005": 6346}, "24": {"NAME": "Canada", "POP2005": 32270507}, "224": {"NAME": "Guadeloupe", "POP2005": 438403}, "21": {"NAME": "Brazil", "POP2005": 186830759}, "48": {"NAME": "Dominican Republic", "POP2005": 9469601}, "49": {"NAME": "Ecuador", "POP2005": 13060993}, "82": {"NAME": "Iceland", "POP2005": 295732}, "42": {"NAME": "Cape Verde", "POP2005": 506807}, "41": {"NAME": "Cuba", "POP2005": 11259905}, "183": {"NAME": "Sierra Leone", "POP2005": 5586403}, "181": {"NAME": "Senegal", "POP2005": 1177034}, "186": {"NAME": "Spain", "POP2005": 43397491}, "79": {"NAME": "Honduras", "POP2005": 683411}, "87": {"NAME": "Cote d'Ivoire", "POP2005": 18584701}, "205": {"NAME": "United Kingdom", "POP2005": 60244834}, "207": {"NAME": "United States", "POP2005": 299846449}, "208": {"NAME": "Burkina Faso", "POP2005": 13933363}, "39": {"NAME": "Costa Rica", "POP2005": 4327228}, "120": {"NAME": "Mexico", "POP2005": 104266392}, "76": {"NAME": "Guinea", "POP2005": 9002656}, "2": {"NAME": "Algeria", "POP2005": 32854159}, "71": {"NAME": "Greenland", "POP2005": 57475}, "242": {"NAME": "Jersey", "POP2005": 0}, "164": {"NAME": "Panama", "POP2005": 3231502}, "165": {"NAME": "Portugal", "POP2005": 10528226}, "225": {"NAME": "Netherlands Antilles", "POP2005": 186392}, "167": {"NAME": "Guinea-Bissau", "POP2005": 1596929}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "104": {"NAME": "Liberia", "POP2005": 3441796}, "78": {"NAME": "Haiti", "POP2005": 9296291}, "11": {"NAME": "Barbados", "POP2005": 291933}, "245": {"NAME": "Russia", "POP2005": 143953092}, "13": {"NAME": "Bahamas", "POP2005": 323295}, "38": {"NAME": "Colombia", "POP2005": 4494579}, "59": {"NAME": "French Guiana", "POP2005": 192099}, "114": {"NAME": "Morocco", "POP2005": 30494991}, "51": {"NAME": "Ireland", "POP2005": 4143294}, "75": {"NAME": "Guatemala", "POP2005": 12709564}, "34": {"NAME": "Cayman Islands", "POP2005": 45591}, "77": {"NAME": "Guyana", "POP2005": 739472}}, "grid": [" ", " ", " ", " ", " ", " ", " !!!!!!! ", " ###### !!!!!!!!! ", " ####### !!!!!!!!!! ", " ########## !!!!!!!!!!!! ! ", " ##########!!!!!!!!!!!!!!!!! ", " # ##########!!!!!!!!!!!!!!!!!! ", " ##########!!!!!!!!!!!!!!!!!!! ", " ##########!!!!!!!!!!!!!!!!!!! ", " # ##########!!!!!!!!!!!!!!!!!!! ", " ########## !!!!!!!!!!!!!!!!! ", " ### ####### !!!!!!!!!!!!!!!!!! ", " ## ###########!!!!!!!!!!!!!!!!!!!! ", " #### ### #####!!!!!!!!!!!!!!!!!!!!! ", " ### # ##### !!!!!!!!!!!!!!!!!!!! ", " ### # ######### !!!!!!!!!!!!!!!!!!! ", " ############# ## !!!!!!!!!!!!!!!!!! ", " ############## !!!!!!!!!!!!!!! ", " ## ######## !!!!!!!!!!!!!!! ", " #### ##### # !!!!!!!!!!!!!! ", " ####### ########## !!!!!!!!!!!!! ", " ######## ### ###### !!!!!!!!!!!!! ", " $ % ######## ########## !!!!!!!!!!!!! & ", " %%%%% # ####### ## ######## !!!!!!!!!!!! ", " %%%%%%%%######## ########## ####### !!!!!!!!!!!! ", " %%%%%%%%%########################### !!!!!!!!!! ", " %%%%%%%%###################### ###### !!!!!!!! ", " %%%%%%%%%###################### ##### !!!!!!! ''''' ", " %%%%%%%%%##################### ####### !!!!!! ''''' ", " % %%%%%%%%############################ !!!!! ''' ", " %%%%%%%%%################## # ###### !!!! ( ", " %%%%%%%%%################# #### # !!!! ", " %%%%%%%%%%%%################ #### # !!! )", " %%%%% %%############### ####### ))", " % %%% %%############### ######## )))", " %% %%%######################### *))", " % %################# ######### *)))", "%%%% ############################ **))", " ########################### ))", " #%%%%%%%%%%%%############## +,", " %%%%%%%%%%%%%%%####%%## -# +", " %%%%%%%%%%%%%%%##%%%%## ", " %%%%%%%%%%%%%%%#%%%% ...", " %%%%%%%%%%%%%%%%%%%% /..", " %%%%%%%%%%%%%%%%%% / ...", " %%%%%%%%%%%%%%%%% ..", " %%%%%%%%%%%%%%%% 000", " 1%%%%%%%%%%%% 000", " 11111%%%%%%%%% .0022", " % 111111% %3 44422", " 111111 %33 445566", " %% 1111 1177778 445566", " % 1 1111111 9:%;<=> 555556", " ? 111@AA B CC DD5556", " 1@EEF G HI DDD66J", " E FKKKKK LMM6JJ", " NOOFKKKKKP QQRSS", " T FFFKKKPUV RSS", " T WFFFKKXPUVX "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/1.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/1.json
new file mode 100644
index 0000000..549b5e6
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/1.json
@@ -0,0 +1 @@
+{"keys": ["", "150", "49", "161", "38", "21", "95", "195", "64", "43", "17", "218", "61", "196", "33", "160", "8", "209", "159", "62", "243"], "data": {"150": {"NAME": "United States Minor Outlying Islands", "POP2005": 0}, "38": {"NAME": "Colombia", "POP2005": 4494579}, "21": {"NAME": "Brazil", "POP2005": 186830759}, "17": {"NAME": "Bolivia", "POP2005": 9182015}, "49": {"NAME": "Ecuador", "POP2005": 13060993}, "159": {"NAME": "New Zealand", "POP2005": 4097112}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "196": {"NAME": "Tonga", "POP2005": 99361}, "61": {"NAME": "Fiji", "POP2005": 828046}, "43": {"NAME": "Cook Islands", "POP2005": 13984}, "218": {"NAME": "Samoa", "POP2005": 183845}, "195": {"NAME": "Tokelau", "POP2005": 1401}, "62": {"NAME": "Falkland Islands (Malvinas)", "POP2005": 2975}, "209": {"NAME": "Uruguay", "POP2005": 3325727}, "243": {"NAME": "South Georgia South Sandwich Islands", "POP2005": 0}, "8": {"NAME": "Argentina", "POP2005": 38747148}, "64": {"NAME": "French Polynesia", "POP2005": 255632}, "160": {"NAME": "Paraguay", "POP2005": 5904342}, "161": {"NAME": "Peru", "POP2005": 27274266}, "33": {"NAME": "Chile", "POP2005": 16295102}}, "grid": [" ! ! ## ##$%%&&&&&&&&& ", " ' $$$$$&&&&&&&&&&& ", " ' $$$$&&&&&&&&&&&&& ", " ( ) $$$$&&&&&&&&&&&& ", " * ' $$$$+&&&&&&&&&& ", " , $$$+++&&&&&&&& ", "- * ) $$+++&&&&&&&& ", "- . * /++00&&&&&& & ", " ) ) //+000&&&&& ", " /11100&&& ", " ) / /11100&& ", " /1111&&& ", " /111222 ", " //111222 ", " /11111 ", " /11111 ", " /111 ", " 3 //111 ", " 3 //11 ", " //11 ", " //11 ", " //1 44 ", " //// 4 ", " //11 55 ", " / 5 ", " ", " 5 ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/2.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/0/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/0.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/0.json
new file mode 100644
index 0000000..1be321e
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/0.json
@@ -0,0 +1 @@
+{"keys": ["", "245", "238", "154", "189", "60", "142", "53", "45", "101", "103", "102", "205", "153", "72", "163", "98", "206", "30", "207", "129", "65", "105", "110", "191", "86", "182", "81", "170", "80", "236", "171", "210", "89", "186", "22", "202", "112", "200", "5", "3", "93", "94", "74", "84", "194", "96", "2", "199", "117", "88", "162", "190", "31", "83", "107", "50", "139", "175", "155", "168", "226", "244", "14", "18", "113", "126", "36", "188", "118", "99", "214", "220", "193", "172", "150", "54", "131", "208", "152", "56", "25", "185", "230", "197", "35", "40", "119", "26", "229", "63", "69", "223", "121", "67", "27", "28", "204", "92", "95"], "data": {"214": {"NAME": "Viet Nam", "POP2005": 85028643}, "210": {"NAME": "Uzbekistan", "POP2005": 26593123}, "131": {"NAME": "Northern Mariana Islands", "POP2005": 80258}, "139": {"NAME": "Palestine", "POP2005": 3762005}, "25": {"NAME": "Cambodia", "POP2005": 13955507}, "26": {"NAME": "Sri Lanka", "POP2005": 19120763}, "27": {"NAME": "Congo", "POP2005": 3609851}, "22": {"NAME": "Bulgaria", "POP2005": 7744591}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "28": {"NAME": "Democratic Republic of the Congo", "POP2005": 58740547}, "220": {"NAME": "Yemen", "POP2005": 21095679}, "121": {"NAME": "Malaysia", "POP2005": 25652985}, "126": {"NAME": "Niger", "POP2005": 1326419}, "129": {"NAME": "Belgium", "POP2005": 10398049}, "54": {"NAME": "Eritrea", "POP2005": 4526722}, "56": {"NAME": "Ethiopia", "POP2005": 78985857}, "50": {"NAME": "Egypt", "POP2005": 72849793}, "53": {"NAME": "Estonia", "POP2005": 1344312}, "199": {"NAME": "Tunisia", "POP2005": 10104685}, "194": {"NAME": "Tajikistan", "POP2005": 6550213}, "197": {"NAME": "Togo", "POP2005": 6238572}, "191": {"NAME": "Switzerland", "POP2005": 7424389}, "190": {"NAME": "Syrian Arab Republic", "POP2005": 18893881}, "193": {"NAME": "Thailand", "POP2005": 63002911}, "117": {"NAME": "Malta", "POP2005": 402617}, "89": {"NAME": "Japan", "POP2005": 127896740}, "110": {"NAME": "Mongolia", "POP2005": 2580704}, "113": {"NAME": "Mali", "POP2005": 1161109}, "112": {"NAME": "The former Yugoslav Republic of Macedonia", "POP2005": 2033655}, "205": {"NAME": "United Kingdom", "POP2005": 60244834}, "80": {"NAME": "Croatia", "POP2005": 455149}, "81": {"NAME": "Hungary", "POP2005": 10086387}, "119": {"NAME": "Maldives", "POP2005": 295297}, "118": {"NAME": "Oman", "POP2005": 2507042}, "84": {"NAME": "Iran (Islamic Republic of)", "POP2005": 69420607}, "3": {"NAME": "Azerbaijan", "POP2005": 8352021}, "245": {"NAME": "Russia", "POP2005": 143953092}, "244": {"NAME": "Taiwan", "POP2005": 0}, "102": {"NAME": "Belarus", "POP2005": 9795287}, "103": {"NAME": "Lithuania", "POP2005": 3425077}, "101": {"NAME": "Latvia", "POP2005": 2301793}, "107": {"NAME": "Libyan Arab Jamahiriya", "POP2005": 5918217}, "105": {"NAME": "Slovakia", "POP2005": 5386995}, "31": {"NAME": "Afghanistan", "POP2005": 25067407}, "30": {"NAME": "China", "POP2005": 1312978855}, "36": {"NAME": "Chad", "POP2005": 10145609}, "35": {"NAME": "Cameroon", "POP2005": 17795149}, "60": {"NAME": "Finland", "POP2005": 5246004}, "63": {"NAME": "Micronesia, Federated States of", "POP2005": 110058}, "65": {"NAME": "France", "POP2005": 60990544}, "67": {"NAME": "Gabon", "POP2005": 1290693}, "69": {"NAME": "Ghana", "POP2005": 2253501}, "175": {"NAME": "Saudi Arabia", "POP2005": 2361236}, "172": {"NAME": "Philippines", "POP2005": 84566163}, "171": {"NAME": "Republic of Moldova", "POP2005": 3876661}, "170": {"NAME": "Romania", "POP2005": 21627557}, "182": {"NAME": "Slovenia", "POP2005": 1999425}, "96": {"NAME": "Korea, Republic of", "POP2005": 47869837}, "2": {"NAME": "Algeria", "POP2005": 32854159}, "186": {"NAME": "Spain", "POP2005": 43397491}, "185": {"NAME": "Somalia", "POP2005": 8196395}, "188": {"NAME": "Sudan", "POP2005": 36899747}, "189": {"NAME": "Sweden", "POP2005": 9038049}, "99": {"NAME": "Lao People's Democratic Republic", "POP2005": 566391}, "98": {"NAME": "Kazakhstan", "POP2005": 15210609}, "168": {"NAME": "Qatar", "POP2005": 796186}, "229": {"NAME": "Palau", "POP2005": 20127}, "226": {"NAME": "United Arab Emirates", "POP2005": 4104291}, "93": {"NAME": "Kyrgyzstan", "POP2005": 5203547}, "92": {"NAME": "Kenya", "POP2005": 35598952}, "223": {"NAME": "Indonesia", "POP2005": 226063044}, "94": {"NAME": "Korea, Democratic People's Republic of", "POP2005": 23615611}, "162": {"NAME": "Pakistan", "POP2005": 158080591}, "163": {"NAME": "Poland", "POP2005": 38195558}, "14": {"NAME": "Bangladesh", "POP2005": 15328112}, "18": {"NAME": "Burma", "POP2005": 47967266}, "88": {"NAME": "Iraq", "POP2005": 27995984}, "150": {"NAME": "United States Minor Outlying Islands", "POP2005": 0}, "153": {"NAME": "Netherlands", "POP2005": 1632769}, "152": {"NAME": "Nigeria", "POP2005": 141356083}, "155": {"NAME": "Nepal", "POP2005": 27093656}, "154": {"NAME": "Norway", "POP2005": 4638836}, "238": {"NAME": "Svalbard", "POP2005": 0}, "83": {"NAME": "India", "POP2005": 1134403141}, "236": {"NAME": "Serbia", "POP2005": 9863026}, "230": {"NAME": "Marshall Islands", "POP2005": 5672}, "86": {"NAME": "Italy", "POP2005": 5864636}, "45": {"NAME": "Denmark", "POP2005": 5416945}, "40": {"NAME": "Central African Republic", "POP2005": 4191429}, "5": {"NAME": "Armenia", "POP2005": 3017661}, "200": {"NAME": "Turkey", "POP2005": 72969723}, "202": {"NAME": "Turkmenistan", "POP2005": 4833266}, "142": {"NAME": "\ufffdland Islands", "POP2005": 0}, "204": {"NAME": "Uganda", "POP2005": 28947181}, "207": {"NAME": "United States", "POP2005": 299846449}, "206": {"NAME": "Ukraine", "POP2005": 46917544}, "208": {"NAME": "Burkina Faso", "POP2005": 13933363}, "74": {"NAME": "Greece", "POP2005": 11099737}, "72": {"NAME": "Germany", "POP2005": 82652369}}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ! ", " !! ! ", " !!!!!! !!! ", " ## !!!!!!! !! ", " ######## !!!!! !!!! ", " ###### ! !!!! ", " ##### # !!!!! ", " #### # !! ", " ## # ! ! ", " #### ! !!! ", " # # ! ! !!!! ! ", " !!! ! !!!!!!!! !!! ! ", " !!! ! !!!!!!!!!! !!!!!! ", " !! ! !!!!!!!!!!! ! ! ", " !! !!!!!!!!!!!! ! ! ! ", " !! !!!!!!!!!!!!!!!!!!!!!! !! ", " ! !!!!!!!!!!!!!!!!!!!!!!! !!!!! ", " !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ", " $$$$$ !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ", " $$$$$$!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%$!!!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $%%%&&!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " %%%%&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $%%%%&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%%&&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%% &&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%% &&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ", " $$%%%'&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!! ", " $$%%%%((!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! !! ", " )%%% **!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!! ", " ))% ++,!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!! ! ", "- .//000!+,!!!!!!!!!!!!!1!!!!!!!!!!!!!!!!!!!!!!!!!! !! ! ", "- .//00002,!!!!!!!!!!!111111!!!!!!!!!!!!!!!!3!!!!!!! ! 444", "-5.//00002222!!!!!11!!1111111!!!!!!!!!!!!!!33!!!!!!! ! ", "666///70222222!!!11111111111111!88888888883333!!!! ! ! ", "6699:;;<==222!!!!!111111111111138888888888833333!! ! ! ", "66:::>>??=@ 2!!!!!11AA1111111133888888888333333!! BB! ", "CC6:::>??DD !!!!1EAAAA1111133333888888333333!! BB ", "C : ::FFGGGGGGGH!IEEEAAAAAJ3333333333333333KKK B ", "C : ::LLGGGGGGGGM EEEEAANN33333333333333333KO BB ", "PPPQ:R LGGGGGGSSMMMMMEENTTT3333333333333333 OOBBBB ", "PPPQQ UUSSMMMMMVVVTTWW33333333333333 OBBBB ", "PPPQQXXXXXYYZ[[[SSMMMMMVTTTWW333333333333333 B ", "PPPPXXXXXXYYY[[[[[MMMMTTTTTWW]]33WW333333333 B ", "PPPPXXXXXXYYY[[[[[^M_MTTTTWWWW]]WWW33333333` B B ", "PPPPXXXXXXYYY[[[[[___ TTWWWWWWaWb3333333``BB ", "cPPdddeXXfffff[[[[[[gg WWWWWWWaWb3hii333 ` ", "ccddddeeefffff [[[j[g WWWWW bbkki 3 l m ", "cdddddeeefffffnjjjjj WWWW bkkki l o ", "pdqqqqeeffffffrjjj WW W kksii lll o ", "pqqqqqeefffffrrtttt WWW W k iii lll uu ", "vqqqqwxxfffffrrrttt yWzz W k i ll { |||| uu ", "} qqwxxxxxfffrrrtt y ~ k \u007flll~ { | ", " \u0080w\u0081\u0082\u0082\u0082\u0082\u0083\u0083\u0084\u0084tt y ~~~\u007f ~\u007f~~ ~ \u0085 "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/1.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/1.json
new file mode 100644
index 0000000..66a2faf
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/1.json
@@ -0,0 +1 @@
+{"keys": ["", "52", "67", "28", "204", "92", "185", "119", "223", "156", "95", "174", "203", "166", "177", "148", "201", "6", "221", "227", "20", "123", "37", "9", "122", "108", "151", "216", "222", "146", "61", "180", "115", "124", "178", "219", "179", "159", "147", "145"], "data": {"151": {"NAME": "Vanuatu", "POP2005": 215366}, "201": {"NAME": "Tuvalu", "POP2005": 10441}, "156": {"NAME": "Nauru", "POP2005": 10111}, "159": {"NAME": "New Zealand", "POP2005": 4097112}, "67": {"NAME": "Gabon", "POP2005": 1290693}, "219": {"NAME": "Swaziland", "POP2005": 1124529}, "115": {"NAME": "Mauritius", "POP2005": 1241173}, "61": {"NAME": "Fiji", "POP2005": 828046}, "179": {"NAME": "Lesotho", "POP2005": 1980831}, "178": {"NAME": "South Africa", "POP2005": 47938663}, "177": {"NAME": "Seychelles", "POP2005": 85532}, "174": {"NAME": "Rwanda", "POP2005": 9233793}, "119": {"NAME": "Maldives", "POP2005": 295297}, "20": {"NAME": "Solomon Islands", "POP2005": 472419}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "28": {"NAME": "Democratic Republic of the Congo", "POP2005": 58740547}, "180": {"NAME": "Botswana", "POP2005": 1835938}, "185": {"NAME": "Somalia", "POP2005": 8196395}, "9": {"NAME": "Australia", "POP2005": 20310208}, "146": {"NAME": "French Southern and Antarctic Lands", "POP2005": 0}, "147": {"NAME": "Heard Island and McDonald Islands", "POP2005": 0}, "203": {"NAME": "United Republic of Tanzania", "POP2005": 38477873}, "145": {"NAME": "Bouvet Island", "POP2005": 0}, "204": {"NAME": "Uganda", "POP2005": 28947181}, "6": {"NAME": "Angola", "POP2005": 16095214}, "148": {"NAME": "British Indian Ocean Territory", "POP2005": 0}, "122": {"NAME": "Mozambique", "POP2005": 20532675}, "123": {"NAME": "Malawi", "POP2005": 13226091}, "124": {"NAME": "New Caledonia", "POP2005": 234185}, "227": {"NAME": "Timor-Leste", "POP2005": 1067285}, "166": {"NAME": "Papua New Guinea", "POP2005": 6069715}, "92": {"NAME": "Kenya", "POP2005": 35598952}, "223": {"NAME": "Indonesia", "POP2005": 226063044}, "222": {"NAME": "Zimbabwe", "POP2005": 13119679}, "221": {"NAME": "Zambia", "POP2005": 11478317}, "37": {"NAME": "Comoros", "POP2005": 797902}, "108": {"NAME": "Madagascar", "POP2005": 18642586}, "52": {"NAME": "Equatorial Guinea", "POP2005": 484098}, "216": {"NAME": "Namibia", "POP2005": 2019677}}, "grid": [" !###$$$$$%%&&' ( ))) ))) )))))) * + ", " ##$$$$,---& )))))))) ))))))..... ", " $$$$$$$---- / 0 ))) ) ) ))))..... 11", " 2$2$$33--- / ))))44 )).... 555 ", " 222$3336--7 888 . 555 ", " 22223333399 : 8888888 ;; ", " <<<233==699>:: 888888888 ; ?", " <<<@===9 :: A 8888888888888 B ; ?", " <<<@@==9 ::: 8888888888888 BB ", " <<<@@CC9 : 888888888888888 ", " <<CCCD 88888888888888 ", " CCCCEC 88888888888888 ", " CCCC 88888888888888 ", " CCC 88 888888 FF ", " 8888 F ", " 88 FF", " 8 FF ", " 8 FF ", " FFF ", " C ", " >> ", " F ", " G F ", " H ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/2.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/1/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/0.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/0.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/0.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/1.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/1.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/1.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/2.json b/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid/world_utfgrid/1/2/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/examples/utfgrid_twogrids.html b/misc/openlayers/examples/utfgrid_twogrids.html
new file mode 100644
index 0000000..57adb88
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid_twogrids.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Multiple UTFGrid Demo</title>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ #controlToggle li { list-style: none; }
+ </style>
+</head>
+<body>
+ <h1 id="title">OpenLayers Multiple UTFGrid Demo</h1>
+
+ <div id="shortdesc">
+ This page demonstrates the use of the OpenLayers UTFGrid Controls with
+ more than one UTFGrid Layer.
+ </div>
+ <div id="map" class="smallmap"></div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="move_pop" id="moveHandler"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="moveHandler">View population stats</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="move_bio" id="hoverHandler"
+ onclick="toggleControl(this);" />
+ <label for="hoverHandler">View bioregion stats</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="move_both" id="clickHandler"
+ onclick="toggleControl(this);" />
+ <label for="clickHandler">View all stats</label>
+ </li>
+ </ul>
+ <div id="docs">
+ <p>
+ This example demonstrates the use of two separate UTFGrid layers.
+ See the <a href="utfgrid_twogrids.js">utfgrid_twogrids.js source</a>
+ for detail on how this is done.
+ </p>
+ </div>
+ <div id="attrsdiv"></div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="utfgrid_twogrids.js"></script>
+ <script>
+ </script>
+</body>
+</html>
diff --git a/misc/openlayers/examples/utfgrid_twogrids.js b/misc/openlayers/examples/utfgrid_twogrids.js
new file mode 100644
index 0000000..c9cb498
--- /dev/null
+++ b/misc/openlayers/examples/utfgrid_twogrids.js
@@ -0,0 +1,70 @@
+var osm = new OpenLayers.Layer.OSM();
+
+var population = new OpenLayers.Layer.UTFGrid({
+ name: "World Population",
+ url: "utfgrid/world_utfgrid/${z}/${x}/${y}.json",
+ utfgridResolution: 4 // default is 2
+});
+var bioregions = new OpenLayers.Layer.UTFGrid({
+ name: "World Bioregions",
+ url: "utfgrid/bio_utfgrid/${z}/${x}/${y}.json",
+ utfgridResolution: 4 // default is 2
+});
+
+var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ controls: [],
+ layers: [osm, population, bioregions],
+ center: [0, 0],
+ zoom: 1
+});
+
+var callback = function(infoLookup) {
+ var msg = "";
+ if (infoLookup) {
+ var layer, info;
+ for (var idx in infoLookup) {
+ layer = map.layers[idx];
+ info = infoLookup[idx];
+ if (info && info.data) {
+ msg += "<strong>" + layer.name + "</strong><br>";
+ msg += "feature id: " + info.id + "<br>";
+ for (var key in info.data) {
+ msg += key + ": " + info.data[key] + "<br>";
+ }
+ }
+ }
+ }
+ document.getElementById("attrsdiv").innerHTML = msg;
+};
+
+var controls = {
+ move_pop: new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ layers: [population],
+ handlerMode: "move"
+ }),
+ move_bio: new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ layers: [bioregions],
+ handlerMode: "move"
+ }),
+ move_both: new OpenLayers.Control.UTFGrid({
+ callback: callback,
+ layers: null, // same as all map.layers
+ handlerMode: "move"
+ })
+};
+
+for (var key in controls) {
+ map.addControl(controls[key]);
+}
+
+function toggleControl(el) {
+ for (var c in controls) {
+ controls[c].deactivate();
+ }
+ controls[el.value].activate();
+}
+toggleControl({value: "move_pop"});
diff --git a/misc/openlayers/examples/vector-features-with-text.html b/misc/openlayers/examples/vector-features-with-text.html
new file mode 100644
index 0000000..c3e9e02
--- /dev/null
+++ b/misc/openlayers/examples/vector-features-with-text.html
@@ -0,0 +1,138 @@
+<!DOCTYPE HTML>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Labeled Features Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", {
+ styleMap: new OpenLayers.StyleMap({'default':{
+ strokeColor: "#00FF00",
+ strokeOpacity: 1,
+ strokeWidth: 3,
+ fillColor: "#FF5500",
+ fillOpacity: 0.5,
+ pointRadius: 6,
+ pointerEvents: "visiblePainted",
+ // label with \n linebreaks
+ label : "name: ${name}\n\nage: ${age}",
+
+ fontColor: "${favColor}",
+ fontSize: "12px",
+ fontFamily: "Courier New, monospace",
+ fontWeight: "bold",
+ labelAlign: "${align}",
+ labelXOffset: "${xOffset}",
+ labelYOffset: "${yOffset}",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+ }}),
+ renderers: renderer
+ });
+
+ // create a point feature
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointFeature = new OpenLayers.Feature.Vector(point);
+ pointFeature.attributes = {
+ name: "toto",
+ age: 20,
+ favColor: 'red',
+ align: "cm"
+ };
+
+ // create a polygon feature from a linear ring of points
+ var pointList = [];
+ for(var p=0; p<6; ++p) {
+ var a = p * (2 * Math.PI) / 7;
+ var r = Math.random(1) + 1;
+ var newPoint = new OpenLayers.Geometry.Point(point.x + 5 + (r * Math.cos(a)),
+ point.y + 5 + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+
+ var linearRing = new OpenLayers.Geometry.LinearRing(pointList);
+ var polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([linearRing]));
+ polygonFeature.attributes = {
+ name: "dude",
+ age: 21,
+ favColor: 'purple',
+ align: 'lb'
+ };
+
+ multiFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Collection([
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-105,40),
+ new OpenLayers.Geometry.Point(-95,45)
+ ]),
+ new OpenLayers.Geometry.Point(-105, 40)
+ ]),
+ {
+ name: "ball-and-chain",
+ age: 30,
+ favColor: 'black',
+ align: 'rt'
+ });
+
+ // Create a point feature to show the label offset options
+ var labelOffsetPoint = new OpenLayers.Geometry.Point(-101.04, 35.68);
+ var labelOffsetFeature = new OpenLayers.Feature.Vector(labelOffsetPoint);
+ labelOffsetFeature.attributes = {
+ name: "offset",
+ age: 22,
+ favColor: 'blue',
+ align: "cm",
+ // positive value moves the label to the right
+ xOffset: 50,
+ // negative value moves the label down
+ yOffset: -15
+ };
+
+
+ var nullFeature = new OpenLayers.Feature.Vector(null);
+ nullFeature.attributes = {
+ name: "toto is some text about the world",
+ age: 20,
+ favColor: 'red',
+ align: "cm"
+ };
+
+ map.addLayer(vectorLayer);
+ vectorLayer.drawFeature(multiFeature);
+ map.setCenter(new OpenLayers.LonLat(-109.370078125, 43.39484375), 4);
+ vectorLayer.addFeatures([pointFeature, polygonFeature, multiFeature, labelOffsetFeature, nullFeature ]);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Labeled features example</h1>
+ <div id="tags">
+ vector, feature, labeling, symbolizer, light
+ </div>
+ <p id="shortdesc">
+ Label vector features with a text symbolizer.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>This example shows drawing simple vector features with a label.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/vector-features.html b/misc/openlayers/examples/vector-features.html
new file mode 100644
index 0000000..62fe8e9
--- /dev/null
+++ b/misc/openlayers/examples/vector-features.html
@@ -0,0 +1,149 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Vector Features</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ /*
+ * Layer style
+ */
+ // we want opaque external graphics and non-opaque internal graphics
+ var layer_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
+ layer_style.fillOpacity = 0.2;
+ layer_style.graphicOpacity = 1;
+
+ /*
+ * Blue style
+ */
+ var style_blue = OpenLayers.Util.extend({}, layer_style);
+ style_blue.strokeColor = "blue";
+ style_blue.fillColor = "blue";
+ style_blue.graphicName = "star";
+ style_blue.pointRadius = 10;
+ style_blue.strokeWidth = 3;
+ style_blue.rotation = 45;
+ style_blue.strokeLinecap = "butt";
+
+ /*
+ * Green style
+ */
+ var style_green = {
+ strokeColor: "#00FF00",
+ strokeWidth: 3,
+ strokeDashstyle: "dashdot",
+ pointRadius: 6,
+ pointerEvents: "visiblePainted",
+ title: "this is a green line"
+ };
+
+ /*
+ * Mark style
+ */
+ var style_mark = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style['default']);
+ // each of the three lines below means the same, if only one of
+ // them is active: the image will have a size of 24px, and the
+ // aspect ratio will be kept
+ // style_mark.pointRadius = 12;
+ // style_mark.graphicHeight = 24;
+ // style_mark.graphicWidth = 24;
+
+ // if graphicWidth and graphicHeight are both set, the aspect ratio
+ // of the image will be ignored
+ style_mark.graphicWidth = 24;
+ style_mark.graphicHeight = 20;
+ style_mark.graphicXOffset = 10; // default is -(style_mark.graphicWidth/2);
+ style_mark.graphicYOffset = -style_mark.graphicHeight;
+ style_mark.externalGraphic = "../img/marker.png";
+ // title only works in Firefox and Internet Explorer
+ style_mark.title = "this is a test tooltip";
+
+ var vectorLayer = new OpenLayers.Layer.Vector("Simple Geometry", {
+ style: layer_style,
+ renderers: renderer
+ });
+
+ // create a point feature
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointFeature = new OpenLayers.Feature.Vector(point,null,style_blue);
+ var point2 = new OpenLayers.Geometry.Point(-105.04, 49.68);
+ var pointFeature2 = new OpenLayers.Feature.Vector(point2,null,style_green);
+ var point3 = new OpenLayers.Geometry.Point(-105.04, 49.68);
+ var pointFeature3 = new OpenLayers.Feature.Vector(point3,null,style_mark);
+
+ // create a line feature from a list of points
+ var pointList = [];
+ var newPoint = point;
+ for(var p=0; p<15; ++p) {
+ newPoint = new OpenLayers.Geometry.Point(newPoint.x + Math.random(1),
+ newPoint.y + Math.random(1));
+ pointList.push(newPoint);
+ }
+ var lineFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(pointList),null,style_green);
+
+ // create a polygon feature from a linear ring of points
+ var pointList = [];
+ for(var p=0; p<6; ++p) {
+ var a = p * (2 * Math.PI) / 7;
+ var r = Math.random(1) + 1;
+ var newPoint = new OpenLayers.Geometry.Point(point.x + (r * Math.cos(a)),
+ point.y + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+
+ var linearRing = new OpenLayers.Geometry.LinearRing(pointList);
+ var polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([linearRing]));
+
+
+ map.addLayer(vectorLayer);
+ map.setCenter(new OpenLayers.LonLat(point.x, point.y), 5);
+ vectorLayer.addFeatures([pointFeature, pointFeature3, pointFeature2, lineFeature, polygonFeature]);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+<h1 id="title">Drawing Simple Vector Features Example</h1>
+
+<div id="tags">
+ vector, feature, light
+</div>
+<p id="shortdesc">
+ Shows the use of the shows drawing simple vector features, in different styles.
+</p>
+<div id="map" class="smallmap"></div>
+<div id="docs">
+ <p>This example shows drawing simple vector features -- point, line, polygon
+ in different styles, created 'manually', by constructing the entire style
+ object, via 'copy', extending the default style object, and by
+ inheriting the default style from the layer.</p>
+ <p>It also shows how to use external graphic files for point features
+ and how to set their size: If either graphicWidth or graphicHeight is set,
+ the aspect ratio of the image will be respected. If both graphicWidth and
+ graphicHeight are set, it will be ignored. Alternatively, if graphicWidth
+ and graphicHeight are omitted, pointRadius will be used to set the size
+ of the image, which will then be twice the value of pointRadius with the
+ original aspect ratio.</p>
+</div>
+
+ </body>
+</html>
+
diff --git a/misc/openlayers/examples/vector-formats.html b/misc/openlayers/examples/vector-formats.html
new file mode 100644
index 0000000..2b45b54
--- /dev/null
+++ b/misc/openlayers/examples/vector-formats.html
@@ -0,0 +1,240 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>Vector Formats</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <!--[if lte IE 6]>
+ <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ <![endif]-->
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ input, select, textarea {
+ font: 0.9em Verdana, Arial, sans-serif;
+ }
+ #leftcol {
+ position: absolute;
+ top: 0;
+ left: 1em;
+ padding: 0;
+ width: 517px;
+ }
+ #map {
+ width: 512px;
+ height: 225px;
+ border: 1px solid #ccc;
+ }
+ #input {
+ width: 512px;
+ }
+ #text {
+ font-size: 0.85em;
+ margin: 1em 0 1em 0;
+ width: 100%;
+ height: 10em;
+ }
+ #info {
+ position: relative;
+ padding: 2em 0;
+ margin-left: 540px;
+ }
+ #output {
+ font-size: 0.8em;
+ width: 100%;
+ height: 512px;
+ border: 0;
+ }
+ p {
+ margin: 0;
+ padding: 0.75em 0 0.75em 0;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectors, formats;
+ function updateFormats() {
+ var in_options = {
+ 'internalProjection': map.baseLayer.projection,
+ 'externalProjection': new OpenLayers.Projection(OpenLayers.Util.getElement("inproj").value)
+ };
+ var out_options = {
+ 'internalProjection': map.baseLayer.projection,
+ 'externalProjection': new OpenLayers.Projection(OpenLayers.Util.getElement("outproj").value)
+ };
+ var gmlOptions = {
+ featureType: "feature",
+ featureNS: "http://example.com/feature"
+ };
+ var gmlOptionsIn = OpenLayers.Util.extend(
+ OpenLayers.Util.extend({}, gmlOptions),
+ in_options
+ );
+ var gmlOptionsOut = OpenLayers.Util.extend(
+ OpenLayers.Util.extend({}, gmlOptions),
+ out_options
+ );
+ var kmlOptionsIn = OpenLayers.Util.extend(
+ {extractStyles: true}, in_options);
+ formats = {
+ 'in': {
+ wkt: new OpenLayers.Format.WKT(in_options),
+ geojson: new OpenLayers.Format.GeoJSON(in_options),
+ georss: new OpenLayers.Format.GeoRSS(in_options),
+ gml2: new OpenLayers.Format.GML.v2(gmlOptionsIn),
+ gml3: new OpenLayers.Format.GML.v3(gmlOptionsIn),
+ kml: new OpenLayers.Format.KML(kmlOptionsIn),
+ atom: new OpenLayers.Format.Atom(in_options),
+ gpx: new OpenLayers.Format.GPX(in_options),
+ encoded_polyline: new OpenLayers.Format.EncodedPolyline(in_options)
+ },
+ 'out': {
+ wkt: new OpenLayers.Format.WKT(out_options),
+ geojson: new OpenLayers.Format.GeoJSON(out_options),
+ georss: new OpenLayers.Format.GeoRSS(out_options),
+ gml2: new OpenLayers.Format.GML.v2(gmlOptionsOut),
+ gml3: new OpenLayers.Format.GML.v3(gmlOptionsOut),
+ kml: new OpenLayers.Format.KML(out_options),
+ atom: new OpenLayers.Format.Atom(out_options),
+ gpx: new OpenLayers.Format.GPX(out_options),
+ encoded_polyline: new OpenLayers.Format.EncodedPolyline(out_options)
+ }
+ };
+ }
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ vectors = new OpenLayers.Layer.Vector("Vector Layer");
+
+ map.addLayers([wms, vectors]);
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.addControl(new OpenLayers.Control.EditingToolbar(vectors));
+
+ var options = {
+ hover: true,
+ onSelect: serialize
+ };
+ var select = new OpenLayers.Control.SelectFeature(vectors, options);
+ map.addControl(select);
+ select.activate();
+
+ updateFormats();
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ }
+
+ function serialize(feature) {
+ var type = document.getElementById("formatType").value;
+ // second argument for pretty printing (geojson only)
+ var pretty = document.getElementById("prettyPrint").checked;
+ var str = formats['out'][type].write(feature, pretty);
+ // not a good idea in general, just for this demo
+ str = str.replace(/,/g, ', ');
+ document.getElementById('output').value = str;
+ }
+
+ function deserialize() {
+ var element = document.getElementById('text');
+ var type = document.getElementById("formatType").value;
+ var features = formats['in'][type].read(element.value);
+ var bounds;
+ if(features) {
+ if(features.constructor != Array) {
+ features = [features];
+ }
+ for(var i=0; i<features.length; ++i) {
+ if (!bounds) {
+ bounds = features[i].geometry.getBounds();
+ } else {
+ bounds.extend(features[i].geometry.getBounds());
+ }
+
+ }
+ vectors.addFeatures(features);
+ map.zoomToExtent(bounds);
+ var plural = (features.length > 1) ? 's' : '';
+ element.value = features.length + ' feature' + plural + ' added';
+ } else {
+ element.value = 'Bad input ' + type;
+ }
+ }
+
+ // preload images
+ (function() {
+ var roots = ["draw_point", "draw_line", "draw_polygon", "pan"];
+ var onImages = [];
+ var offImages = [];
+ for(var i=0; i<roots.length; ++i) {
+ onImages[i] = new Image();
+ onImages[i].src = "../theme/default/img/" + roots[i] + "_on.png";
+ offImages[i] = new Image();
+ offImages[i].src = "../theme/default/img/" + roots[i] + "_on.png";
+ }
+ })();
+
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="leftcol">
+ <h1 id="title">Vector Formats Example</h1>
+
+ <div id="tags">
+ vector, geojson, atom, kml, georss, gml, wkt, advanced, spherical, mercator
+ </div>
+ <p id="shortdesc">
+ Shows the wide variety of vector formats that open layers supports.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <div id="input">
+ <p>Use the drop-down below to select the input/output format
+ for vector features. New features can be added by using the drawing
+ tools above or by pasting their text representation below.</p>
+ <label for="formatType">Format</label>
+ <select name="formatType" id="formatType">
+ <option value="geojson" selected="selected">GeoJSON</option>
+ <option value="atom">Atom</option>
+ <option value="kml">KML</option>
+ <option value="georss">GeoRSS</option>
+ <option value="gml2">GML (v2)</option>
+ <option value="gml3">GML (v3)</option>
+ <option value="wkt">Well-Known Text (WKT)</option>
+ <option value="gpx">GPX</option>
+ <option value="encoded_polyline">Encoded Polyline</option>
+ </select>
+ &nbsp;
+ <label for="prettyPrint">Pretty print</label>
+ <input id="prettyPrint" type="checkbox"
+ name="prettyPrint" value="1" />
+ <br>
+ Input Projection: <select id="inproj" onchange='updateFormats()'>
+ <option value="EPSG:4326" selected="selected">EPSG:4326</option>
+ <option value="EPSG:900913">Spherical Mercator</option>
+ </select> <br>
+ Output Projection: <select id="outproj" onchange='updateFormats()'>
+ <option value="EPSG:4326" selected="selected">EPSG:4326</option>
+ <option value="EPSG:900913">Spherical Mercator</option>
+ </select>
+ <br>
+ <textarea id="text">paste text here...</textarea>
+ <br>
+ <input type="button" value="add feature" onclick="deserialize();" />
+ </div>
+
+ <div id="docs">
+ </div>
+
+ </div>
+ <div id="info">
+ <p>Use the tools to the left to draw new polygons, lines, and points.
+ After drawing some new features, hover over a feature to see the
+ serialized version below.</p>
+ <textarea id="output"></textarea>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/web-mercator.html b/misc/openlayers/examples/web-mercator.html
new file mode 100644
index 0000000..cfa307f
--- /dev/null
+++ b/misc/openlayers/examples/web-mercator.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Web Mercator</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="web-mercator.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Spherical Mercator Example</h1>
+
+ <div id="tags">
+ sperical, mercator, epsg, projection
+ </div>
+ <p id="shortdesc">
+ Shows the use of layers in spherical or web mercator using different
+ EPSG codes to indicate the same projection.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ A number of mapping services support the spherical or web
+ mercator but use different EPSG codes to identify it. ArcGIS
+ server version 9.3 uses EPSG:102113 to represent the same SRS
+ that OpenLayers typically refers to by EPSG:900913.
+ </p><p>
+ To configure a map with a WMS layer overlaid on a Google layer
+ where the WMS uses EPSG:102113 to refer to the web mercator
+ projection, the Google layer must be constructed with this
+ projection code in its options (it is not sufficient to
+ construct the map with this projection).
+ <p>
+ If your application needs to transform coordinates to and from
+ EPSG:102113, you must add custom transforms as well.
+ </p><p>
+ See the <a href="web-mercator.js" target="_blank">web-mercator.js</a>
+ source for details.
+ </p>
+ </div>
+ </body>
+</html>
+
+
+
diff --git a/misc/openlayers/examples/web-mercator.js b/misc/openlayers/examples/web-mercator.js
new file mode 100644
index 0000000..7a25d37
--- /dev/null
+++ b/misc/openlayers/examples/web-mercator.js
@@ -0,0 +1,37 @@
+// make map available for easy debugging
+var map;
+
+function init() {
+
+ var options = {
+ projection: new OpenLayers.Projection("EPSG:102113"),
+ units: "m",
+ numZoomLevels: 18,
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+ 20037508, 20037508.34)
+ };
+ map = new OpenLayers.Map('map', options);
+
+ // create Google layer with EPSG:102113 code
+ var gsat = new OpenLayers.Layer.Google("Google Imagery", {
+ type: G_SATELLITE_MAP,
+ sphericalMercator: true,
+ projection: "EPSG:102113"
+ });
+
+ // create WMS layer
+ var wms = new OpenLayers.Layer.WMS(
+ "Highways",
+ "http://sampleserver1.arcgisonline.com/arcgis/services/Specialty/ESRI_StateCityHighway_USA/MapServer/WMSServer",
+ {layers: "2", format: "image/gif", transparent: "true"},
+ {
+ isBaseLayer: false,
+ wrapDateLine: true
+ }
+ );
+
+ map.addLayers([gsat, wms]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(-10723197, 4500612), 3);
+}
diff --git a/misc/openlayers/examples/wfs-filter.html b/misc/openlayers/examples/wfs-filter.html
new file mode 100644
index 0000000..be256c0
--- /dev/null
+++ b/misc/openlayers/examples/wfs-filter.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WFS Protocol with Filter</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wfs-filter.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WFS Protocol and Filter</h1>
+ <div id="tags">
+ filter, wfs, comparison
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of a filter in making GetFeature requests using the WFS protocol.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ If a vector layer has a filter and the protocol supports server-side filtering,
+ the filter will be serialized in requests for features. The WFS protocol can be
+ used with a vector layer to serialize a filter using OGC Filter Encoding. This
+ example requests all features that are <code>TYPE</code> "highway" or "road".
+ </p><p>
+ See the <a href="wfs-filter.js" target="_blank">wfs-filter source</a>
+ for details on how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wfs-filter.js b/misc/openlayers/examples/wfs-filter.js
new file mode 100644
index 0000000..18d517c
--- /dev/null
+++ b/misc/openlayers/examples/wfs-filter.js
@@ -0,0 +1,48 @@
+var map;
+
+// use proxy if requesting features cross-domain
+OpenLayers.ProxyHost= "proxy.cgi?url=";
+
+function init() {
+
+ map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "Natural Earth",
+ "http://demo.opengeo.org/geoserver/wms",
+ {layers: "topp:naturalearth"}
+ ),
+ new OpenLayers.Layer.Vector("WFS", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureType: "tasmania_roads",
+ featureNS: "http://www.openplans.org/topp"
+ }),
+ styleMap: new OpenLayers.StyleMap({
+ strokeWidth: 3,
+ strokeColor: "#333333"
+ }),
+ filter: new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "TYPE",
+ value: "highway"
+ }),
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "TYPE",
+ value: "road"
+ })
+ ]
+ })
+ })
+ ],
+ center: new OpenLayers.LonLat(146.7, -41.8),
+ zoom: 6
+ });
+
+}
diff --git a/misc/openlayers/examples/wfs-protocol-transactions.html b/misc/openlayers/examples/wfs-protocol-transactions.html
new file mode 100644
index 0000000..390c62a
--- /dev/null
+++ b/misc/openlayers/examples/wfs-protocol-transactions.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <!--[if lte IE 6]>
+ <style>
+ .customEditingToolbar {
+ width: 200px;
+ }
+ </style>
+ <![endif]-->
+ <style>
+ .customEditingToolbar {
+ float: right;
+ right: 0px;
+ height: 30px;
+ }
+ .customEditingToolbar div {
+ float: right;
+ margin: 5px;
+ width: 24px;
+ height: 24px;
+ }
+ .olControlNavigationItemActive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -103px -23px;
+ }
+ .olControlNavigationItemInactive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -103px -0px;
+ }
+ .olControlDrawFeaturePolygonItemInactive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -26px 0px;
+ }
+ .olControlDrawFeaturePolygonItemActive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -26px -23px ;
+ }
+ .olControlModifyFeatureItemActive {
+ background-image: url(../theme/default/img/move_feature_on.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ .olControlModifyFeatureItemInactive {
+ background-image: url(../theme/default/img/move_feature_off.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ .olControlDeleteFeatureItemActive {
+ background-image: url(../theme/default/img/remove_point_on.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ .olControlDeleteFeatureItemInactive {
+ background-image: url(../theme/default/img/remove_point_off.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ </style>
+ <script src="wfs-protocol-transactions.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WFS Transaction Example</h1>
+ <div id="tags">
+ wfs, wfst, wfs-t, advanced
+ </div>
+ <p id="shortdesc">
+ Shows the use of the WFS Transactions (WFS-T).
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ The WFS protocol allows for creation of new features and
+ reading, updating, or deleting of existing features.
+ </p>
+ <p>
+ Use the tools to create, modify, and delete (in order from left
+ to right) features. Use the save tool (picture of a disk) to
+ save your changes.
+ </p>
+ <p>
+ To deactivate "drawing" or "modifying" depress the
+ corresponding button.
+ </p>
+ <p>
+ See the <a href="wfs-protocol-transactions.js" target="_blank">
+ wfs-protocol-transactions.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
+
+
diff --git a/misc/openlayers/examples/wfs-protocol-transactions.js b/misc/openlayers/examples/wfs-protocol-transactions.js
new file mode 100644
index 0000000..6b1044e
--- /dev/null
+++ b/misc/openlayers/examples/wfs-protocol-transactions.js
@@ -0,0 +1,106 @@
+var map, wfs;
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+var DeleteFeature = OpenLayers.Class(OpenLayers.Control, {
+ initialize: function(layer, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.layer = layer;
+ this.handler = new OpenLayers.Handler.Feature(
+ this, layer, {click: this.clickFeature}
+ );
+ },
+ clickFeature: function(feature) {
+ // if feature doesn't have a fid, destroy it
+ if(feature.fid == undefined) {
+ this.layer.destroyFeatures([feature]);
+ } else {
+ feature.state = OpenLayers.State.DELETE;
+ this.layer.events.triggerEvent("afterfeaturemodified",
+ {feature: feature});
+ feature.renderIntent = "select";
+ this.layer.drawFeature(feature);
+ }
+ },
+ setMap: function(map) {
+ this.handler.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+ CLASS_NAME: "OpenLayers.Control.DeleteFeature"
+});
+
+function init() {
+
+ var extent = new OpenLayers.Bounds(
+ -11593508, 5509847, -11505759, 5557774
+ );
+
+
+ map = new OpenLayers.Map('map', {
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326"),
+ restrictedExtent: extent,
+ controls: [
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.Navigation()
+ ]
+ });
+ var gphy = new OpenLayers.Layer.Google(
+ "Google Physical",
+ {type: G_PHYSICAL_MAP, sphericalMercator: true}
+ );
+
+ var saveStrategy = new OpenLayers.Strategy.Save();
+
+ wfs = new OpenLayers.Layer.Vector("Editable Features", {
+ strategies: [new OpenLayers.Strategy.BBOX(), saveStrategy],
+ projection: new OpenLayers.Projection("EPSG:4326"),
+ protocol: new OpenLayers.Protocol.WFS({
+ version: "1.1.0",
+ srsName: "EPSG:4326",
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureNS : "http://opengeo.org",
+ featureType: "restricted",
+ geometryName: "the_geom",
+ schema: "http://demo.opengeo.org/geoserver/wfs/DescribeFeatureType?version=1.1.0&typename=og:restricted"
+ })
+ });
+
+ map.addLayers([gphy, wfs]);
+
+ var panel = new OpenLayers.Control.Panel({
+ displayClass: 'customEditingToolbar',
+ allowDepress: true
+ });
+
+ var draw = new OpenLayers.Control.DrawFeature(
+ wfs, OpenLayers.Handler.Polygon,
+ {
+ title: "Draw Feature",
+ displayClass: "olControlDrawFeaturePolygon",
+ multi: true
+ }
+ );
+
+ var edit = new OpenLayers.Control.ModifyFeature(wfs, {
+ title: "Modify Feature",
+ displayClass: "olControlModifyFeature"
+ });
+
+ var del = new DeleteFeature(wfs, {title: "Delete Feature"});
+
+ var save = new OpenLayers.Control.Button({
+ title: "Save Changes",
+ trigger: function() {
+ if(edit.feature) {
+ edit.selectControl.unselectAll();
+ }
+ saveStrategy.save();
+ },
+ displayClass: "olControlSaveFeatures"
+ });
+
+ panel.addControls([save, del, edit, draw]);
+ map.addControl(panel);
+ map.zoomToExtent(extent, true);
+}
+
diff --git a/misc/openlayers/examples/wfs-protocol.html b/misc/openlayers/examples/wfs-protocol.html
new file mode 100644
index 0000000..97752a5
--- /dev/null
+++ b/misc/openlayers/examples/wfs-protocol.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Vector Behavior Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ OpenLayers.ProxyHost= "proxy.cgi?url=";
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var layer = new OpenLayers.Layer.Vector("WFS", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureType: "tasmania_roads",
+ featureNS: "http://www.openplans.org/topp"
+ })
+ });
+
+ map.addLayers([wms, layer]);
+ map.setCenter(new OpenLayers.LonLat(146.7, -41.8), 6);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Vector Behavior Example</h1>
+ <div id="tags">
+ wfs, vector
+ </div>
+ <p id="shortdesc">
+ Uses a BBOX strategy, WFS protocol, and GML format.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>The vector layer shown uses the BBOX strategy, the WFS protocol,
+ and the GML format. The BBOX strategy fetches features within a
+ bounding box. When the map bounds invalidate the data bounds,
+ another request is triggered. The WFS protocol gets features
+ through a WFS request. The GML format is used to serialize
+ features.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wfs-reprojection.html b/misc/openlayers/examples/wfs-reprojection.html
new file mode 100644
index 0000000..ceae053
--- /dev/null
+++ b/misc/openlayers/examples/wfs-reprojection.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>WFS Reprojection Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ"></script>
+ <script src="wfs-reprojection.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WFS Reprojection Example</h1>
+ <div id="tags">
+ reprojection, styling, stylemap, wfs, vector, advanced
+ </div>
+ <p id="shortdesc">
+ Shows the use of the client side reprojection support.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This example shows client side reprojection. In the case where
+ the projection of a vector layer differs from the projection of
+ the map, features are requested in the layer projection and
+ transformed during parsing. It is assumed that the layer
+ projection is "native" projection of the data (the coordinate
+ reference system of the data on the server).
+ </p>
+ <p>
+ Also shown is styleMap for the layer with unique value rules.
+ Colors are assigned based on the STATE_FIPS attribute.
+ </p>
+ <p>
+ See the <a href="wfs-reprojection.js" target="_blank">
+ wfs-reprojection.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wfs-reprojection.js b/misc/openlayers/examples/wfs-reprojection.js
new file mode 100644
index 0000000..64a0736
--- /dev/null
+++ b/misc/openlayers/examples/wfs-reprojection.js
@@ -0,0 +1,60 @@
+var map, layer, styleMap;
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+function init() {
+
+ var geographic = new OpenLayers.Projection("EPSG:4326");
+ var mercator = new OpenLayers.Projection("EPSG:900913");
+
+ map = new OpenLayers.Map('map', {
+ projection: mercator
+ });
+
+ var g = new OpenLayers.Layer.Google("Google Layer", {
+ sphericalMercator: true
+ });
+ map.addLayers([g]);
+
+ // prepare to style the data
+ styleMap = new OpenLayers.StyleMap({
+ strokeColor: "black",
+ strokeWidth: 2,
+ strokeOpacity: 0.5,
+ fillOpacity: 0.2
+ });
+ // create a color table for state FIPS code
+ var colors = ["red", "orange", "yellow", "green", "blue", "purple"];
+ var code, fips = {};
+ for(var i=1; i<=66; ++i) {
+ code = "0" + i;
+ code = code.substring(code.length - 2);
+ fips[code] = {fillColor: colors[i % colors.length]};
+ }
+ // add unique value rules with your color lookup
+ styleMap.addUniqueValueRules("default", "STATE_FIPS", fips);
+
+ // This server supports server-side reprojection, but we're using WFS 1.0
+ // here (which doesn't support reprojection) to illustrate client-side
+ // reprojection.
+ var wfs = new OpenLayers.Layer.Vector("States", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ version: "1.0.0",
+ srsName: "EPSG:4326", // this is the default
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp"
+ }),
+ projection: geographic, // specified because it is different than the map
+ styleMap: styleMap
+ });
+ map.addLayer(wfs);
+
+ // if you want to use Geographic coords, transform to ESPG:900913
+ var ddBounds = new OpenLayers.Bounds(
+ -73.839111,40.287907,-68.214111,44.441624
+ );
+ map.zoomToExtent(
+ ddBounds.transform(geographic, mercator)
+ );
+}
diff --git a/misc/openlayers/examples/wfs-snap-split.html b/misc/openlayers/examples/wfs-snap-split.html
new file mode 100644
index 0000000..0b71da0
--- /dev/null
+++ b/misc/openlayers/examples/wfs-snap-split.html
@@ -0,0 +1,292 @@
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <!--[if lte IE 6]>
+ <style>
+ .customEditingToolbar {
+ width: 200px;
+ }
+ </style>
+ <![endif]-->
+ <style>
+ .customEditingToolbar {
+ float: right;
+ right: 0px;
+ height: 30px;
+ }
+ .customEditingToolbar div {
+ float: right;
+ margin: 5px;
+ width: 24px;
+ height: 24px;
+ }
+ .olControlNavigationItemActive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -103px -23px;
+ }
+ .olControlNavigationItemInactive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -103px -0px;
+ }
+ .olControlDrawFeaturePointItemInactive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -77px 0px;
+ }
+ .olControlDrawFeaturePointItemActive {
+ background-image: url("../theme/default/img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ background-position: -77px -23px ;
+ }
+ .olControlModifyFeatureItemActive {
+ background-image: url(../theme/default/img/move_feature_on.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ .olControlModifyFeatureItemInactive {
+ background-image: url(../theme/default/img/move_feature_off.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ .olControlDeleteFeatureItemActive {
+ background-image: url(../theme/default/img/remove_point_on.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ .olControlDeleteFeatureItemInactive {
+ background-image: url(../theme/default/img/remove_point_off.png);
+ background-repeat: no-repeat;
+ background-position: 0px 1px;
+ }
+ </style>
+ <script type="text/javascript">
+ var map, wfs;
+
+ var DeleteFeature = OpenLayers.Class(OpenLayers.Control, {
+ initialize: function(layer, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.layer = layer;
+ this.handler = new OpenLayers.Handler.Feature(
+ this, layer, {click: this.clickFeature}
+ );
+ },
+ clickFeature: function(feature) {
+ // if feature doesn't have a fid, destroy it
+ if(feature.fid == undefined) {
+ this.layer.destroyFeatures([feature]);
+ } else {
+ feature.state = OpenLayers.State.DELETE;
+ this.layer.events.triggerEvent("afterfeaturemodified",
+ {feature: feature});
+ feature.renderIntent = "select";
+ this.layer.drawFeature(feature);
+ }
+ },
+ setMap: function(map) {
+ this.handler.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+ CLASS_NAME: "OpenLayers.Control.DeleteFeature"
+ });
+
+ function init() {
+ OpenLayers.ProxyHost = "proxy.cgi?url=";
+ map = new OpenLayers.Map({
+ div: "map",
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508),
+ restrictedExtent: new OpenLayers.Bounds(
+ -11563906, 5540550, -11559015, 5542996
+ ),
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ units: "m",
+ controls: [
+ new OpenLayers.Control.PanZoom(),
+ new OpenLayers.Control.Navigation()
+ ]
+ });
+
+ var osm = new OpenLayers.Layer.OSM();
+ var styles = new OpenLayers.StyleMap({
+ "default": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ pointRadius: 5,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#333333"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#666666"
+ }
+ }
+ })
+ ]
+ }),
+ "select": new OpenLayers.Style({
+ strokeColor: "#00ccff",
+ strokeWidth: 4
+ }),
+ "temporary": new OpenLayers.Style(null, {
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ "Point": {
+ pointRadius: 5,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 0.25,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#333333"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#00ccff"
+ }
+ }
+ })
+ ]
+ })
+ });
+
+ var saveStrategy = new OpenLayers.Strategy.Save();
+ wfs = new OpenLayers.Layer.Vector("Editable Features", {
+ strategies: [new OpenLayers.Strategy.BBOX(), saveStrategy],
+ projection: new OpenLayers.Projection("EPSG:4326"),
+ styleMap: styles,
+ protocol: new OpenLayers.Protocol.WFS({
+ version: "1.1.0",
+ srsName: "EPSG:4326",
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureNS : "http://opengeo.org",
+ featureType: "roads",
+ geometryName: "the_geom",
+ schema: "http://demo.opengeo.org/geoserver/wfs/DescribeFeatureType?version=1.1.0&typename=og:roads"
+ })
+ });
+
+ map.addLayers([osm, wfs]);
+
+ // configure the snapping agent
+ var snap = new OpenLayers.Control.Snapping({layer: wfs});
+ map.addControl(snap);
+ snap.activate();
+
+ // configure split agent
+ var split = new OpenLayers.Control.Split({
+ layer: wfs,
+ source: wfs,
+ tolerance: 0.0001,
+ deferDelete: true,
+ eventListeners: {
+ aftersplit: function(event) {
+ var msg = "Split resulted in " + event.features.length + " features.";
+ flashFeatures(event.features);
+ }
+ }
+ });
+ map.addControl(split);
+ split.activate();
+
+ // add some editing tools to a panel
+ var panel = new OpenLayers.Control.Panel({
+ displayClass: 'customEditingToolbar',
+ allowDepress: true
+ });
+ var draw = new OpenLayers.Control.DrawFeature(
+ wfs, OpenLayers.Handler.Path,
+ {
+ title: "Draw Feature",
+ displayClass: "olControlDrawFeaturePoint",
+ handlerOptions: {multi: true}
+ }
+ );
+ modify = new OpenLayers.Control.ModifyFeature(
+ wfs, {displayClass: "olControlModifyFeature"}
+ );
+ var del = new DeleteFeature(wfs, {title: "Delete Feature"});
+
+ var save = new OpenLayers.Control.Button({
+ title: "Save Changes",
+ trigger: function() {
+ if(modify.feature) {
+ modify.selectControl.unselectAll();
+ }
+ saveStrategy.save();
+ },
+ displayClass: "olControlSaveFeatures"
+ });
+
+
+
+ panel.addControls([
+ save, del, modify, draw
+ ]);
+
+ map.addControl(panel);
+ map.setCenter(new OpenLayers.LonLat(-11561460.5, 5541773), 15);
+ }
+
+ function flashFeatures(features, index) {
+ if(!index) {
+ index = 0;
+ }
+ var current = features[index];
+ if(current && current.layer === wfs) {
+ wfs.drawFeature(features[index], "select");
+ }
+ var prev = features[index-1];
+ if(prev && prev.layer === wfs) {
+ wfs.drawFeature(prev, "default");
+ }
+ ++index;
+ if(index <= features.length) {
+ window.setTimeout(function() {flashFeatures(features, index)}, 100);
+ }
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+
+ <h1 id="title">Snap/Split and Persist via WFS</h1>
+
+ <div id="tags">
+ snapping, splitting, wfs, wfst, wfs-t, advanced
+ </div>
+ <p id="shortdesc">
+ Shows snapping, splitting, and use of the WFS Transactions (WFS-T).
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>The WFS protocol allows for creation of new features and reading,
+ updating, or deleting of existing features.</p>
+ <p>Use the tools to create, modify, and delete (in order from left
+ to right) features. Use the save tool (picture of a disk) to
+ save your changes.</p>
+ <p>To deactivate "drawing" or "modifying" depress the corresponding
+ button.</p>
+ </div>
+
+</body>
+</html>
+
+
diff --git a/misc/openlayers/examples/wfs-spatial-filter.html b/misc/openlayers/examples/wfs-spatial-filter.html
new file mode 100644
index 0000000..d6acead
--- /dev/null
+++ b/misc/openlayers/examples/wfs-spatial-filter.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WFS Protocol with Filter</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">WFS Protocol and Filter</h1>
+ <div id="tags">
+ filter, wfs, spatial
+ </div>
+ <p id="shortdesc">
+ Demonstrates the use of a spatial filter in making GetFeature requests using the WFS protocol.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ If a vector layer has a filter and the protocol supports server-side filtering,
+ the filter will be serialized in requests for features. The WFS protocol can be
+ used with a vector layer to serialize a filter using OGC Filter Encoding.
+ </p><p>
+ This example has a draw control that is always active. When you draw a polygon
+ on the map, the filter for the main vector layer will be updated, and features
+ that intersect your drawn polygon will be requested.
+ </p><p>
+ See the <a href="wfs-spatial-filter.js" target="_blank">source</a>
+ for details on how this is done.
+ </p>
+ </div>
+ <script src="wfs-spatial-filter.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wfs-spatial-filter.js b/misc/openlayers/examples/wfs-spatial-filter.js
new file mode 100644
index 0000000..4ca9fd5
--- /dev/null
+++ b/misc/openlayers/examples/wfs-spatial-filter.js
@@ -0,0 +1,36 @@
+OpenLayers.ProxyHost= "proxy.cgi?url=";
+var map = new OpenLayers.Map('map');
+var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+);
+
+var layer = new OpenLayers.Layer.Vector("WFS", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureType: "tasmania_roads",
+ featureNS: "http://www.openplans.org/topp"
+ })
+});
+
+map.addLayers([wms, layer]);
+map.setCenter(new OpenLayers.LonLat(146.7, -41.8), 6);
+
+var drawings = new OpenLayers.Layer.Vector();
+map.addLayer(drawings);
+var draw = new OpenLayers.Control.DrawFeature(drawings, OpenLayers.Handler.Polygon);
+map.addControl(draw);
+draw.activate();
+
+drawings.events.on({
+ beforefeatureadded: function(event) {
+ var geometry = event.feature.geometry;
+ layer.filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ value: event.feature.geometry
+ });
+ layer.refresh({force: true});
+ return false;
+ }
+});
diff --git a/misc/openlayers/examples/wfs-states.html b/misc/openlayers/examples/wfs-states.html
new file mode 100644
index 0000000..ffe6c87
--- /dev/null
+++ b/misc/openlayers/examples/wfs-states.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <title>WFS: United States (GeoServer)</title>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wfs-states.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WFS United States (GeoServer) Example</h1>
+ <div id="tags">
+ wfs, vector
+ </div>
+ <p id="shortdesc">
+ Shows the use of the WFS United States (GeoServer).
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This example shows the basic use of a vector layer with the
+ WFS protocol, and shows how to switch between a WMS and a vector
+ layer at a certain scale.
+ </p>
+ <p>
+ See the <a href="wfs-states.js" target="_blank">wfs-states.js
+ source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wfs-states.js b/misc/openlayers/examples/wfs-states.js
new file mode 100644
index 0000000..c4e801e
--- /dev/null
+++ b/misc/openlayers/examples/wfs-states.js
@@ -0,0 +1,35 @@
+var map;
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+function init() {
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS("OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: "basic"}
+ ),
+ new OpenLayers.Layer.WMS("States WMS",
+ "http://demo.opengeo.org/geoserver/wms",
+ {layers: "topp:states", format: "image/png", transparent: true},
+ {maxScale: 15000000}
+ ),
+ new OpenLayers.Layer.Vector("States", {
+ minScale: 15000000,
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ url: "http://demo.opengeo.org/geoserver/wfs",
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp"
+ }),
+ renderers: renderer
+ })
+ ],
+ center: [-95.8506355, 37.163851],
+ zoom: 3
+ });
+}
diff --git a/misc/openlayers/examples/wmc.html b/misc/openlayers/examples/wmc.html
new file mode 100644
index 0000000..a8c1fda
--- /dev/null
+++ b/misc/openlayers/examples/wmc.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #wmc {
+ width: 90%;
+ height: 300px;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+ </style>
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ // increase reload attempts
+ OpenLayers.IMAGE_RELOAD_ATTEMPTS = 2;
+
+ var format = new OpenLayers.Format.WMC({'layerOptions': {buffer: 0}});
+ var doc, context, map;
+
+ function init() {
+ map = new OpenLayers.Map("map");
+
+ var gwc = new OpenLayers.Layer.WMS(
+ "Global Imagery",
+ "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "bluemarble"}
+ );
+
+ var vmap = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'},
+ {
+ maxExtent: new OpenLayers.Bounds(-130, 14, -60, 55),
+ maxResolution: 0.1,
+ numZoomLevels: 4,
+ minResolution: 0.02
+ }
+ );
+
+ var roads = new OpenLayers.Layer.WMS(
+ "Transportation Network",
+ "http://lioapp.lrc.gov.on.ca/cubeserv/cubeserv.pl",
+ {layers: "na_road:CCRS", transparent: "TRUE"},
+ {
+ isBaseLayer: false,
+ maxExtent: new OpenLayers.Bounds(
+ -166.532, 4.05046, -0.206818, 70.287
+ ),
+ displayInLayerSwitcher: false,
+ opacity: 0.6,
+ minScale: 32000000,
+ numZoomLevels: 4,
+ maxScale: 6200000
+ }
+ );
+
+ var nexrad = new OpenLayers.Layer.WMS(
+ "Radar 3:1",
+ "http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet/AccuWeather_Maps.wms",
+ {layers: "3:1", transparent: "TRUE"},
+ {
+ isBaseLayer: false,
+ maxExtent: new OpenLayers.Bounds(
+ -131.029495239, 14.5628967285,
+ -61.0295028687, 54.562896728
+ ),
+ opacity: 0.8,
+ singleTile: true,
+ maxResolution: 0.1,
+ numZoomLevels: 4,
+ minResolution: 0.02
+ }
+ );
+
+ map.addLayers([gwc, vmap, roads, nexrad]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(-95, 34.5), 4);
+ };
+
+ function readWMC(merge) {
+ var text = document.getElementById("wmc").value;
+
+ if(merge) {
+ try {
+ map = format.read(text, {map: map});
+ } catch(err) {
+ document.getElementById("wmc").value = err;
+ }
+ } else {
+ map.destroy();
+ try {
+ var jsonFormat = new OpenLayers.Format.JSON();
+ var mapOptions = jsonFormat.read(OpenLayers.Util.getElement('mapOptions').value);
+ map = format.read(text, {map: mapOptions});
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ } catch(err) {
+ document.getElementById("wmc").value = err;
+ }
+ }
+ }
+
+ function writeWMC(merge) {
+ try {
+ var text = format.write(map);
+ document.getElementById("wmc").value = text;
+ } catch(err) {
+ document.getElementById("wmc").value = err;
+ }
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WMC Example</h1>
+
+ <div id="tags">
+ wmc, parser, advanced, cleanup
+ </div>
+ <p id="shortdesc">
+ Shows parsing of Web Map Context documents.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <button onclick="writeWMC();">write</button><br>
+ <button onclick="readWMC();">read as new map</button> with the following extra map options : <input type="text" id="mapOptions" value='{"div": "map", "allOverlays": true}'/><br>
+ <button onclick="readWMC(true);">read and merge</button><br>
+ <button onclick="pasteWMC();">try with another WMC document</button><br>
+ <textarea id="wmc">paste WMC doc here</textarea>
+ <div id="docs">
+ <p>This is an example of parsing WMC documents. <br>
+ The format class has a layerOptions property, which can be used
+ to control the default options of the layer when it is created
+ by the parser.</p>
+ </div>
+ </body>
+</html>
+
+
+
+
diff --git a/misc/openlayers/examples/wms-long-url.html b/misc/openlayers/examples/wms-long-url.html
new file mode 100644
index 0000000..023345b
--- /dev/null
+++ b/misc/openlayers/examples/wms-long-url.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>WMS with POST Requests to Avoid Long URLs</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ </head>
+ <body>
+ <h1 id="title">WMS with POST Requests to Avoid Long URLs</h1>
+
+ <div id="tags">
+ sld, sld_body, post, iframe, advanced
+ </div>
+
+ <div id="shortdesc">Render tiles in IMG or IFRAME elements, depending on
+ the complexity of the GetMap request</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>The <code>maxGetUrlLength</code> property of the layer's
+ <code>tileOptions</code> option causes tiles to be requested using
+ HTTP POST when the length of the GET url would exceed the specified
+ length (2048 characters is recommended). In real life applications,
+ this happens often when using the SLD_BODY request parameter for
+ inline styling.
+ </p><p>
+ <input type="radio" name="group" id="longurl" checked="checked">
+ Long URL - POST requests
+ <br>
+ <input type="radio" name="group" id="shorturl">
+ Short URL - GET requests
+ </p><p>
+ View the <a href="wms-long-url.js" target="_blank">wms-long-url.js</a>
+ source to see how this is done.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wms-long-url.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wms-long-url.js b/misc/openlayers/examples/wms-long-url.js
new file mode 100644
index 0000000..ef95177
--- /dev/null
+++ b/misc/openlayers/examples/wms-long-url.js
@@ -0,0 +1,26 @@
+// a long text that we set as dummy param (makeTheUrlLong) to make the url long
+var longText = new Array(205).join("1234567890");
+
+var map = new OpenLayers.Map( 'map' );
+var base = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic', makeTheUrlLong: longText},
+ {tileOptions: {maxGetUrlLength: 2048}, transitionEffect: 'resize'}
+);
+var overlay = new OpenLayers.Layer.WMS("Overlay",
+ "http://suite.opengeo.org/geoserver/wms",
+ {layers: "usa:states", transparent: true, makeTheUrlLong: longText},
+ {ratio: 1, singleTile: true, tileOptions: {maxGetUrlLength: 2048}, transitionEffect: 'resize'}
+);
+map.addLayers([base, overlay]);
+map.zoomToMaxExtent();
+
+// add behavior to dom elements
+document.getElementById("longurl").onclick = function() {
+ base.mergeNewParams({makeTheUrlLong: longText});
+ overlay.mergeNewParams({makeTheUrlLong: longText});
+};
+document.getElementById("shorturl").onclick = function() {
+ base.mergeNewParams({makeTheUrlLong: null});
+ overlay.mergeNewParams({makeTheUrlLong: null});
+};
diff --git a/misc/openlayers/examples/wms-untiled.html b/misc/openlayers/examples/wms-untiled.html
new file mode 100644
index 0000000..9651eac
--- /dev/null
+++ b/misc/openlayers/examples/wms-untiled.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'},
+ {singleTile: true}
+ );
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title"> WMS Untiled Example</h1>
+
+ <div id="tags">
+ singletile, tile, light
+ </div>
+ <p id="shortdesc">
+ Shows an example of an "untiled" WMS layer, which requests a single
+ image for the entire map view.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ An untiled (with singleTile: true) layer will only request a single image at a time.
+ </div>
+ </body>
+</html>
+
+
diff --git a/misc/openlayers/examples/wms-v13.html b/misc/openlayers/examples/wms-v13.html
new file mode 100644
index 0000000..427a829
--- /dev/null
+++ b/misc/openlayers/examples/wms-v13.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, map2;
+
+ function init(){
+ // clear array to simulate a wrong axis order request
+ map = new OpenLayers.Map( 'map' );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://demo.cubewerx.com/demo/cubeserv/cubeserv.cgi?",
+ {layers: 'Foundation.GTOPO30', version: '1.3.0'},
+ {singleTile: true, yx: []}
+ );
+ map.addLayer(layer);
+
+ map.zoomToMaxExtent();
+
+ map2 = new OpenLayers.Map( 'map2' );
+ var layer2 = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://demo.cubewerx.com/demo/cubeserv/cubeserv.cgi?",
+ {layers: 'Foundation.GTOPO30', version: '1.3.0'},
+ {singleTile: true}
+ );
+ map2.addLayer(layer2);
+
+ map2.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title"> WMS version 1.3 (axis order) Example</h1>
+
+ <div id="tags">
+ axis order, wms 1.3, light
+ </div>
+ <p id="shortdesc">
+ Shows an example of the influence of axis order on WMS 1.3 GetMap requests.
+ </p>
+ <div id="map" class="smallmap"></div>
+ <div id="map2" class="smallmap"></div>
+ <div id="docs">
+ <p>WMS version 1.3 introduced the axis order sequence, so that for e.g. EPSG:4326 the bbox coordinate
+ values need to be flipped (LatLon instead of LonLat). The first map uses the incorrect (WMS 1.1) axis
+ order against a WMS 1.3 service, resulting in corrupted maps. The second map shows how to correctly
+ request a map in EPSG:4326 against a WMS 1.3 service.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wms.html b/misc/openlayers/examples/wms.html
new file mode 100644
index 0000000..1f0668f
--- /dev/null
+++ b/misc/openlayers/examples/wms.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WMS Example</h1>
+
+ <div id="tags">
+ wms, layer, singletile
+ </div>
+ <p id="shortdesc">
+ Shows the basic use of openlayers using a WMS layer
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This is an example of how to add an WMS layer to the OpenLayers
+ window. The images are tiled in this instance. If you wanted to not use
+ a tiled WMS, "singleTile" option to true like shown in this example.</p>
+ </div>
+ </body>
+</html>
+
+
+
+
diff --git a/misc/openlayers/examples/wmst.html b/misc/openlayers/examples/wmst.html
new file mode 100644
index 0000000..8d1e891
--- /dev/null
+++ b/misc/openlayers/examples/wmst.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: WMS + Time</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, ia_wms;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'} );
+
+ var jpl_wms = new OpenLayers.Layer.WMS( "NASA Global Mosaic",
+ "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ {layers: "landsat7"});
+
+ ia_wms = new OpenLayers.Layer.WMS("Nexrad","http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r-t.cgi?",{layers:"nexrad-n0r-wmst",transparent:true,format:'image/png',time:"2005-08-29T13:00:00Z"});
+
+ jpl_wms.setVisibility(false);
+
+ map.addLayers([ol_wms, jpl_wms, ia_wms]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToExtent(new OpenLayers.Bounds(-100.898437,22.148438,-78.398437,39.726563));
+ }
+ function update_date() {
+ var string = OpenLayers.Util.getElement('year').value + "-" +
+ OpenLayers.Util.getElement('month').value + "-" +
+ OpenLayers.Util.getElement('day').value + "T" +
+ OpenLayers.Util.getElement('hour').value + ":" +
+ OpenLayers.Util.getElement('minute').value + ":00";
+ ia_wms.mergeNewParams({'time':string});
+
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title"> WMS Time Example</h1>
+ <div id="tags">
+ wmst, wms-t
+ </div>
+ <p id="shortdesc">
+ Shows the use of the layer WMS-T (time) layer</a>
+ </p>
+ <input size="4" type='text' id='year' value="2005" onchange="update_date()"/>-<input size="2" type="text" id="month" value="08" onchange="update_date()"/>-<input size="2" type="text" id="day" value="29" onchange="update_date()" />T<input type="text" size="2" id="hour" value="13" onchange="update_date()" />:<input type="text" size="2" id="minute" value="00" onchange="update_date()" />:00
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>WMS-T example: update the times, and the radar image will change.
+ Uses Layer.mergeNewParams to update the date element with the strings
+ from the input fields every time one of them is changed. The inputs
+ above describe a timestamp: The minute increments can only be updated
+ in units of 5.</p>
+ </div>
+ </body>
+</html>
+
+
+
diff --git a/misc/openlayers/examples/wmts-capabilities.html b/misc/openlayers/examples/wmts-capabilities.html
new file mode 100644
index 0000000..58aabd5
--- /dev/null
+++ b/misc/openlayers/examples/wmts-capabilities.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WMTS Capabilities Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wmts-capabilities.js"></script>
+ <style>
+ .olControlAttribution {
+ bottom: 5px;
+ }
+ </style>
+ </head>
+ <body onload="init();">
+ <h1 id="title">Web Map Tile Service (WMTS) Capabilities Parsing</h1>
+ <div id="tags">
+ wmts, capabilities, getcapabilities
+ </div>
+ <p id="shortdesc">
+ The WMTS Capabilities format allows for parsing of capabilities
+ documents from OGC Web Map Tile Service (WMTS) version 1.0.0
+ implementations.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ This example creates an OpenLayers.Layer.WMTS layer to based
+ on the results of parsing a capabilities doc with the
+ OpenLayers.Format.WMTSCapabilities parser.
+ </p><p>
+ See the <a href="wmts-capabilities.js" target="_blank">
+ wmts-capabilities.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wmts-capabilities.js b/misc/openlayers/examples/wmts-capabilities.js
new file mode 100644
index 0000000..103f5f5
--- /dev/null
+++ b/misc/openlayers/examples/wmts-capabilities.js
@@ -0,0 +1,58 @@
+OpenLayers.ProxyHost = "proxy.cgi/?url=";
+
+var map, format;
+
+function init() {
+
+ format = new OpenLayers.Format.WMTSCapabilities({
+ /**
+ * This particular service is not in compliance with the WMTS spec and
+ * is providing coordinates in y, x order regardless of the CRS. To
+ * work around this, we can provide the format a table of CRS URN that
+ * should be considered y, x order. These will extend the defaults on
+ * the format.
+ */
+ yx: {
+ "urn:ogc:def:crs:EPSG::900913": true
+ }
+ });
+
+ OpenLayers.Request.GET({
+ url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts",
+ params: {
+ SERVICE: "WMTS",
+ VERSION: "1.0.0",
+ REQUEST: "GetCapabilities"
+ },
+ success: function(request) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var capabilities = format.read(doc);
+ var layer = format.createLayer(capabilities, {
+ layer: "medford:buildings",
+ matrixSet: "EPSG:900913",
+ format: "image/png",
+ opacity: 0.7,
+ isBaseLayer: false
+ });
+ map.addLayer(layer);
+ },
+ failure: function() {
+ alert("Trouble getting capabilities doc");
+ OpenLayers.Console.error.apply(OpenLayers.Console, arguments);
+ }
+ });
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913"
+ });
+
+ var osm = new OpenLayers.Layer.OSM();
+
+ map.addLayer(osm);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(-13677832, 5213272), 13);
+}
diff --git a/misc/openlayers/examples/wmts-getfeatureinfo.html b/misc/openlayers/examples/wmts-getfeatureinfo.html
new file mode 100644
index 0000000..453eb5f
--- /dev/null
+++ b/misc/openlayers/examples/wmts-getfeatureinfo.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WMTS GetFeatureInfo Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wmts-getfeatureinfo.js"></script>
+ <style>
+ .olControlAttribution {
+ bottom: 5px;
+ }
+
+ table.featureInfo, table.featureInfo td, table.featureInfo th {
+ border: 1px solid #ddd;
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ font-size: 80%;
+ padding: .1em .1em;
+ }
+ table.featureInfo th {
+ padding: .2em .2em;
+ font-weight: bold;
+ background: #eee;
+ }
+ table.featureInfo td {
+ background: #fff;
+ }
+ table.featureInfo tr.odd td {
+ background: #eee;
+ }
+ table.featureInfo caption {
+ text-align: left;
+ font-size: 100%;
+ font-weight: bold;
+ text-transform: uppercase;
+ padding: .1em .2em;
+ }
+
+ </style>
+ </head>
+ <body onload="init();">
+ <h1 id="title">WMTS GetFeatureInfo Control</h1>
+ <div id="tags">
+ wmts, tile, cache, getfeatureinfo
+ </div>
+ <p id="shortdesc">
+ The WMTSGetFeatureInfo control allows retrieval of information about
+ features displayed in a WMTS layer.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <input id="drill" type="checkbox" checked="checked">
+ <label for="drill">drill down</label>
+ <div id="docs">
+ <p>
+ This example uses an OpenLayers.Control.WMTSGetFeatureInfo
+ control layer to access information from WMTS layers. The
+ control is activated and configured to request feature
+ information when you click on the map. If the control's
+ drillDown property is set to true, multiple layers can be
+ queried.
+ </p><p>
+ See the <a href="wmts-getfeatureinfo.js" target="_blank">
+ wmts-getfeatureinfo.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wmts-getfeatureinfo.js b/misc/openlayers/examples/wmts-getfeatureinfo.js
new file mode 100644
index 0000000..0b8cd8a
--- /dev/null
+++ b/misc/openlayers/examples/wmts-getfeatureinfo.js
@@ -0,0 +1,94 @@
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+var map, control, popups = {};
+
+function init() {
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913"
+ });
+
+ var osm = new OpenLayers.Layer.OSM();
+
+ // If tile matrix identifiers differ from zoom levels (0, 1, 2, ...)
+ // then they must be explicitly provided.
+ var matrixIds = new Array(26);
+ for (var i=0; i<26; ++i) {
+ matrixIds[i] = "EPSG:900913:" + i;
+ }
+
+ var zoning = new OpenLayers.Layer.WMTS({
+ name: "zoning",
+ url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts/",
+ layer: "medford:zoning",
+ matrixSet: "EPSG:900913",
+ matrixIds: matrixIds,
+ format: "image/png",
+ style: "_null",
+ opacity: 0.7,
+ isBaseLayer: false
+ });
+ var buildings = new OpenLayers.Layer.WMTS({
+ name: "building",
+ url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts/",
+ layer: "medford:buildings",
+ matrixSet: "EPSG:900913",
+ matrixIds: matrixIds,
+ format: "image/png",
+ style: "_null",
+ isBaseLayer: false
+ });
+
+ map.addLayers([osm, zoning, buildings]);
+
+ // create WMTS GetFeatureInfo control
+ control = new OpenLayers.Control.WMTSGetFeatureInfo({
+ drillDown: true,
+ queryVisible: true,
+ eventListeners: {
+ getfeatureinfo: function(evt) {
+ var text;
+ var match = evt.text.match(/<body[^>]*>([\s\S]*)<\/body>/);
+ if (match && !match[1].match(/^\s*$/)) {
+ text = match[1];
+ } else {
+ text = "No " + evt.layer.name + " features in that area.<br>";
+ }
+ var popupId = evt.xy.x + "," + evt.xy.y;
+ var popup = popups[popupId];
+ if (!popup || !popup.map) {
+ popup = new OpenLayers.Popup.FramedCloud(
+ popupId,
+ map.getLonLatFromPixel(evt.xy),
+ null,
+ " ",
+ null,
+ true,
+ function(evt) {
+ delete popups[this.id];
+ this.hide();
+ OpenLayers.Event.stop(evt);
+ }
+ );
+ popups[popupId] = popup;
+ map.addPopup(popup, true);
+ }
+ popup.setContentHTML(popup.contentHTML + text);
+ popup.show();
+ }
+ }
+ });
+ map.addControl(control);
+ control.activate();
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(-13678519, 5212803), 15);
+
+ var drill = document.getElementById("drill");
+ drill.checked = true;
+ drill.onchange = function() {
+ control.drillDown = drill.checked;
+ };
+}
+
+OpenLayers.Popup.FramedCloud.prototype.maxSize = new OpenLayers.Size(350, 200);
diff --git a/misc/openlayers/examples/wmts.html b/misc/openlayers/examples/wmts.html
new file mode 100644
index 0000000..922df8c
--- /dev/null
+++ b/misc/openlayers/examples/wmts.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WMTS Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/Firebug/firebug.js"></script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wmts.js"></script>
+ <style>
+ .olControlAttribution {
+ bottom: 5px;
+ }
+ </style>
+ </head>
+ <body onload="init();">
+ <h1 id="title">Web Map Tile Service (WMTS) Layer</h1>
+ <div id="tags">
+ wmts
+ </div>
+ <p id="shortdesc">
+ The WMTS layer allows viewing of tiles from a server implementing
+ the OGC Web Map Tile Service (WMTS) standard version 1.0.0.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ This example uses an OpenLayers.Layer.WMTS layer to display
+ cached tiles over an OSM layer in spherical mercator coordinates.
+ </p><p>
+ See the <a href="wmts.js" target="_blank">
+ wmts.js source</a> to see how this is done.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wmts.js b/misc/openlayers/examples/wmts.js
new file mode 100644
index 0000000..391a200
--- /dev/null
+++ b/misc/openlayers/examples/wmts.js
@@ -0,0 +1,35 @@
+var map;
+
+function init() {
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913"
+ });
+
+ var osm = new OpenLayers.Layer.OSM();
+
+ // If tile matrix identifiers differ from zoom levels (0, 1, 2, ...)
+ // then they must be explicitly provided.
+ var matrixIds = new Array(26);
+ for (var i=0; i<26; ++i) {
+ matrixIds[i] = "EPSG:900913:" + i;
+ }
+
+ var wmts = new OpenLayers.Layer.WMTS({
+ name: "Medford Buildings",
+ url: "http://v2.suite.opengeo.org/geoserver/gwc/service/wmts/",
+ layer: "medford:buildings",
+ matrixSet: "EPSG:900913",
+ matrixIds: matrixIds,
+ format: "image/png",
+ style: "_null",
+ opacity: 0.7,
+ isBaseLayer: false
+ });
+
+ map.addLayers([osm, wmts]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.setCenter(new OpenLayers.LonLat(-13677832, 5213272), 13);
+
+}
diff --git a/misc/openlayers/examples/wps-client.html b/misc/openlayers/examples/wps-client.html
new file mode 100644
index 0000000..379f1bb
--- /dev/null
+++ b/misc/openlayers/examples/wps-client.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WPS Client Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wps-client.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WPS Client Example</h1>
+
+ <div id="tags">
+ wps
+ </div>
+
+ <div id="shortdesc">Shows the usage of the WPS Client</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>This example shows how simple it is to use the WPS Client. It
+ buffers an intersection of a geometry and a feature, which is
+ accomplished by chaining two processes. See
+ <a href="wps-client.js">wps-client.js</a> to see how this is done.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wps-client.js b/misc/openlayers/examples/wps-client.js
new file mode 100644
index 0000000..511d491
--- /dev/null
+++ b/misc/openlayers/examples/wps-client.js
@@ -0,0 +1,75 @@
+OpenLayers.ProxyHost = 'proxy.cgi?url=';
+
+var map, client, intersect, buffer;
+
+function init() {
+
+ map = new OpenLayers.Map('map', {
+ allOverlays: true,
+ center: [114, 16],
+ zoom: 4,
+ layers: [new OpenLayers.Layer.Vector()]
+ });
+
+ var features = [new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ 'LINESTRING(117 22,112 18,118 13, 115 8)'
+ ))];
+ var geometry = OpenLayers.Geometry.fromWKT(
+ 'POLYGON((110 20,120 20,120 10,110 10,110 20),(112 17,118 18,118 16,112 15,112 17))'
+ );
+
+ map.baseLayer.addFeatures(features);
+ map.baseLayer.addFeatures([new OpenLayers.Feature.Vector(geometry)]);
+
+ client = new OpenLayers.WPSClient({
+ servers: {
+ opengeo: 'http://demo.opengeo.org/geoserver/wps'
+ }
+ });
+
+ // Create a process and configure it
+ intersect = client.getProcess('opengeo', 'JTS:intersection');
+ intersect.configure({
+ // spatial input can be a feature or a geometry or an array of
+ // features or geometries
+ inputs: {
+ a: features,
+ b: geometry
+ }
+ });
+
+ // Create another process which chains the previous one and execute it
+ buffer = client.getProcess('opengeo', 'JTS:buffer');
+ buffer.execute({
+ inputs: {
+ geom: intersect.output(),
+ distance: 1
+ },
+ success: function(outputs) {
+ // outputs.result is a feature or an array of features for spatial
+ // processes.
+ map.baseLayer.addFeatures(outputs.result);
+ }
+ });
+
+ // Instead of creating a process and executing it, we could call execute on
+ // the client directly if we are only dealing with a single process:
+ /*
+ client.execute({
+ server: "opengeo",
+ process: "JTS:intersection",
+ // spatial input can be a feature or a geometry or an array of
+ // features or geometries
+ inputs: {
+ a: features,
+ b: geometry
+ },
+ success: function(outputs) {
+ // outputs.result is a feature or an array of features for spatial
+ // processes.
+ map.baseLayer.addFeatures(outputs.result);
+ }
+ });
+ */
+
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/wps.html b/misc/openlayers/examples/wps.html
new file mode 100644
index 0000000..b136e3a
--- /dev/null
+++ b/misc/openlayers/examples/wps.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers WPS Builder Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ .olControlEditingToolbar .olControlModifyFeatureItemInactive {
+ background-image: url(../theme/default/img/draw_point_off.png);
+ }
+ .olControlEditingToolbar .olControlModifyFeatureItemActive {
+ background-image: url(../theme/default/img/draw_point_on.png);
+ }
+ textarea {
+ display: block;
+ width: 100%;
+ height: 3em;
+ }
+ label {
+ display: block;
+ }
+ .notsupported {
+ color: red;
+ }
+ button {
+ display: block;
+ margin-top: 10px;
+ }
+ #docs {
+ top: 6em;
+ left: 550px;
+ position: absolute;
+ margin-right: 10px;
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="title">WPS Builder Example</h1>
+
+ <div id="tags">
+ wps, process, advanced
+ </div>
+
+ <div id="shortdesc">Using WPS formats to interact with WPS</div>
+
+ <div id="docs">
+ <p>This example shows WPS in action by using the WPSCapabilities,
+ WPSDescribeProcess and WPSExecute formats. See
+ <a target="_blank" href="wps.js">wps.js</a> for the
+ source code. <b>Note: For applications using WPS, the high level
+ approach shown in the <a href="wps-client.html">wps-client</a> example
+ is recommended instead.</b></p>
+ <ol>
+ <li>Select a process from the list below the map. The list is
+ populated with the result of a WPS GetCapabilities request, parsed
+ using <code>OpenLayers.Format.WPSCapabilities::read</code>.</li>
+ <li>Fill out the Input form. Hover over fields to get a description.
+ Required fields are marked with a "*".
+ To use a geometry from the map as input, select the geometry on the
+ map (using the pen symbol on the left of the toolbar) and just
+ click the field. The form is generated from the object returned by
+ <code>OpenLayers.Format.WPSDescribeProcess::read</code></li>
+ <li>Click "Execute" and examine the result in the result text area.
+ If the result can be parsed as features, it will be displayed on
+ the map as well. The process data is sent to the server with the
+ serialized XML from <code>OpenLayers.Format.WPSExecute::write</code>,
+ which can use a modified
+ <code>OpenLayers.Format.WPSDescribeProcess</code> result object as
+ input.</li>
+ </ol>
+ </div>
+
+ <div id="example" style="width:520px">
+ <div id="map" class="smallmap"></div>
+
+ <div>
+ <select id="processes"><option>Select a process</option></select>
+ <p id="abstract"></p>
+ <div id="input"></div>
+ <div id="output"></div>
+ </div>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="wps.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/wps.js b/misc/openlayers/examples/wps.js
new file mode 100644
index 0000000..e54f35d
--- /dev/null
+++ b/misc/openlayers/examples/wps.js
@@ -0,0 +1,353 @@
+OpenLayers.ProxyHost = "proxy.cgi?url=";
+
+var wps = "http://demo.opengeo.org/geoserver/wps",
+ capabilities, // the capabilities, read by Format.WPSCapabilities::read
+ process; // the process description from Format.WPSDescribeProcess::read
+
+// get some capabilities
+getCapabilities();
+
+// create the UI
+var layer = new OpenLayers.Layer.Vector("Scratchpad");
+var toolbar = new OpenLayers.Control.EditingToolbar(layer);
+toolbar.addControls([new OpenLayers.Control.ModifyFeature(layer, {
+ title: "Select feature"
+})]);
+var map = new OpenLayers.Map('map', {
+ controls: [
+ toolbar,
+ new OpenLayers.Control.ZoomPanel(),
+ new OpenLayers.Control.PanPanel()
+ ],
+ layers: [
+ new OpenLayers.Layer.WMS(
+ "OSM", "http://maps.opengeo.org/geowebcache/service/wms",
+ {layers: "openstreetmap", format: "image/png"}
+ ), layer
+ ]
+});
+map.zoomToMaxExtent();
+
+// add behavior to html elements
+document.getElementById("processes").onchange = describeProcess;
+
+// using OpenLayers.Format.WPSCapabilities to read the capabilities
+function getCapabilities() {
+ OpenLayers.Request.GET({
+ url: wps,
+ params: {
+ "SERVICE": "WPS",
+ "REQUEST": "GetCapabilities"
+ },
+ success: function(response){
+ capabilities = new OpenLayers.Format.WPSCapabilities().read(
+ response.responseText
+ );
+ var dropdown = document.getElementById("processes");
+ var offerings = capabilities.processOfferings, option;
+ // populate the dropdown
+ for (var p in offerings) {
+ option = document.createElement("option");
+ option.innerHTML = offerings[p].identifier;
+ option.value = p;
+ dropdown.appendChild(option);
+ }
+ }
+ });
+}
+
+// using OpenLayers.Format.WPSDescribeProcess to get information about a
+// process
+function describeProcess() {
+ var selection = this.options[this.selectedIndex].value;
+ OpenLayers.Request.GET({
+ url: wps,
+ params: {
+ "SERVICE": "WPS",
+ "REQUEST": "DescribeProcess",
+ "VERSION": capabilities.version,
+ "IDENTIFIER": selection
+ },
+ success: function(response) {
+ process = new OpenLayers.Format.WPSDescribeProcess().read(
+ response.responseText
+ ).processDescriptions[selection];
+ buildForm();
+ }
+ });
+}
+
+// dynamically create a form from the process description
+function buildForm() {
+ document.getElementById("abstract").innerHTML = process["abstract"];
+ document.getElementById("input").innerHTML = "<h3>Input:</h3>";
+ document.getElementById("output").innerHTML = "";
+
+ var inputs = process.dataInputs, supported = true,
+ sld = "text/xml; subtype=sld/1.0.0",
+ input;
+ for (var i=0,ii=inputs.length; i<ii; ++i) {
+ input = inputs[i];
+ if (input.complexData) {
+ var formats = input.complexData.supported.formats;
+ if (formats["application/wkt"]) {
+ addWKTInput(input);
+ } else if (formats["text/xml; subtype=wfs-collection/1.0"]) {
+ addWFSCollectionInput(input);
+ } else if (formats["image/tiff"]) {
+ addRasterInput(input);
+ } else if (formats[sld]) {
+ addXMLInput(input, sld);
+ } else {
+ supported = false;
+ }
+ } else if (input.boundingBoxData) {
+ addBoundingBoxInput(input);
+ } else if (input.literalData) {
+ addLiteralInput(input);
+ } else {
+ supported = false;
+ }
+ if (input.minOccurs > 0) {
+ document.getElementById("input").appendChild(document.createTextNode("* "));
+ }
+ }
+
+ if (supported) {
+ var executeButton = document.createElement("button");
+ executeButton.innerHTML = "Execute";
+ document.getElementById("input").appendChild(executeButton);
+ executeButton.onclick = execute;
+ } else {
+ document.getElementById("input").innerHTML = '<span class="notsupported">' +
+ "Sorry, the WPS builder does not support the selected process." +
+ "</span>";
+ }
+}
+
+// helper function to dynamically create a textarea for geometry (WKT) data
+// input
+function addWKTInput(input, previousSibling) {
+ var name = input.identifier;
+ var container = document.getElementById("input");
+ var label = document.createElement("label");
+ label["for"] = name;
+ label.title = input["abstract"];
+ label.innerHTML = name + " (select feature, then click field):";
+ previousSibling && previousSibling.nextSibling ?
+ container.insertBefore(label, previousSibling.nextSibling) :
+ container.appendChild(label);
+ var field = document.createElement("textarea");
+ field.onclick = function () {
+ if (layer.selectedFeatures.length) {
+ this.innerHTML = new OpenLayers.Format.WKT().write(
+ layer.selectedFeatures[0]
+ );
+ }
+ createCopy(input, this, addWKTInput);
+ };
+ field.onblur = function() {
+ input.data = field.value ? {
+ complexData: {
+ mimeType: "application/wkt",
+ value: this.value
+ }
+ } : undefined;
+ };
+ field.title = input["abstract"];
+ field.id = name;
+ previousSibling && previousSibling.nextSibling ?
+ container.insertBefore(field, previousSibling.nextSibling.nextSibling) :
+ container.appendChild(field);
+}
+
+// helper function for xml input
+function addXMLInput(input, type) {
+ var name = input.identifier;
+ var field = document.createElement("input");
+ field.title = input["abstract"];
+ field.value = name + " (" + type + ")";
+ field.onblur = function() {
+ input.data = field.value ? {
+ complexData: {
+ mimeType: type,
+ value: this.value
+ }
+ } : undefined;
+ };
+ document.getElementById("input").appendChild(field);
+}
+
+// helper function to dynamically create a WFS collection reference input
+function addWFSCollectionInput(input) {
+ var name = input.identifier;
+ var field = document.createElement("input");
+ field.title = input["abstract"];
+ field.value = name + " (layer on demo server)";
+ addValueHandlers(field, function() {
+ input.reference = field.value ? {
+ mimeType: "text/xml; subtype=wfs-collection/1.0",
+ href: "http://geoserver/wfs",
+ method: "POST",
+ body: {
+ wfs: {
+ version: "1.0.0",
+ outputFormat: "GML2",
+ featureType: field.value
+ }
+ }
+ } : undefined;
+ });
+ document.getElementById("input").appendChild(field);
+}
+
+// helper function to dynamically create a raster (GeoTIFF) url input
+function addRasterInput(input) {
+ var name = input.identifier;
+ var field = document.createElement("input");
+ field.title = input["abstract"];
+ var url = window.location.href.split("?")[0];
+ field.value = url.substr(0, url.lastIndexOf("/")+1) + "data/tazdem.tiff";
+ document.getElementById("input").appendChild(field);
+ (field.onblur = function() {
+ input.reference = {
+ mimeType: "image/tiff",
+ href: field.value,
+ method: "GET"
+ };
+ })();
+}
+
+// helper function to dynamically create a bounding box input
+function addBoundingBoxInput(input) {
+ var name = input.identifier;
+ var field = document.createElement("input");
+ field.title = input["abstract"];
+ field.value = "left,bottom,right,top (EPSG:4326)";
+ document.getElementById("input").appendChild(field);
+ addValueHandlers(field, function() {
+ input.boundingBoxData = {
+ projection: "EPSG:4326",
+ bounds: OpenLayers.Bounds.fromString(field.value)
+ };
+ });
+}
+
+// helper function to create a literal input textfield or dropdown
+function addLiteralInput(input, previousSibling) {
+ var name = input.identifier;
+ var container = document.getElementById("input");
+ var anyValue = input.literalData.anyValue;
+ // anyValue means textfield, otherwise we create a dropdown
+ var field = document.createElement(anyValue ? "input" : "select");
+ field.id = name;
+ field.title = input["abstract"];
+ previousSibling && previousSibling.nextSibling ?
+ container.insertBefore(field, previousSibling.nextSibling) :
+ container.appendChild(field);
+ if (anyValue) {
+ var dataType = input.literalData.dataType;
+ field.value = name + (dataType ? " (" + dataType + ")" : "");
+ addValueHandlers(field, function() {
+ input.data = field.value ? {
+ literalData: {
+ value: field.value
+ }
+ } : undefined;
+ createCopy(input, field, addLiteralInput);
+ });
+ } else {
+ var option;
+ option = document.createElement("option");
+ option.innerHTML = name;
+ field.appendChild(option);
+ for (var v in input.literalData.allowedValues) {
+ option = document.createElement("option");
+ option.value = v;
+ option.innerHTML = v;
+ field.appendChild(option);
+ }
+ field.onchange = function() {
+ createCopy(input, field, addLiteralInput);
+ input.data = this.selectedIndex ? {
+ literalData: {
+ value: this.options[this.selectedIndex].value
+ }
+ } : undefined;
+ };
+ }
+}
+
+// if maxOccurs is > 1, this will add a copy of the field
+function createCopy(input, field, fn) {
+ if (input.maxOccurs && input.maxOccurs > 1 && !field.userSelected) {
+ // add another copy of the field - we don't check maxOccurs
+ field.userSelected = true;
+ var newInput = OpenLayers.Util.extend({}, input);
+ // we recognize copies by the occurrence property
+ newInput.occurrence = (input.occurrence || 0) + 1;
+ process.dataInputs.push(newInput);
+ fn(newInput, field);
+ }
+}
+
+// helper function for adding events to form fields
+function addValueHandlers(field, onblur) {
+ field.onclick = function() {
+ if (!this.initialValue) {
+ this.initialValue = this.value;
+ this.value = "";
+ }
+ };
+ field.onblur = function() {
+ if (!this.value) {
+ this.value = this.initialValue;
+ delete this.initialValue;
+ }
+ onblur.apply(this, arguments);
+ };
+}
+
+// execute the process
+function execute() {
+ var output = process.processOutputs[0];
+ var input;
+ // remove occurrences that the user has not filled out
+ for (var i=process.dataInputs.length-1; i>=0; --i) {
+ input = process.dataInputs[i];
+ if ((input.minOccurs === 0 || input.occurrence) && !input.data && !input.reference) {
+ OpenLayers.Util.removeItem(process.dataInputs, input);
+ }
+ }
+ process.responseForm = {
+ rawDataOutput: {
+ identifier: output.identifier
+ }
+ };
+ if (output.complexOutput && output.complexOutput.supported.formats["application/wkt"]) {
+ process.responseForm.rawDataOutput.mimeType = "application/wkt";
+ }
+ OpenLayers.Request.POST({
+ url: wps,
+ data: new OpenLayers.Format.WPSExecute().write(process),
+ success: showOutput
+ });
+}
+
+// add the process's output to the page
+function showOutput(response) {
+ var result = document.getElementById("output");
+ result.innerHTML = "<h3>Output:</h3>";
+ var features;
+ var contentType = response.getResponseHeader("Content-Type");
+ if (contentType == "application/wkt") {
+ features = new OpenLayers.Format.WKT().read(response.responseText);
+ } else if (contentType == "text/xml; subtype=wfs-collection/1.0") {
+ features = new OpenLayers.Format.WFST.v1_0_0().read(response.responseText);
+ }
+ if (features && (features instanceof OpenLayers.Feature.Vector || features.length)) {
+ layer.addFeatures(features);
+ result.innerHTML += "The result should also be visible on the map.";
+ }
+ result.innerHTML += "<textarea>" + response.responseText + "</textarea>";
+} \ No newline at end of file
diff --git a/misc/openlayers/examples/wrapDateLine.html b/misc/openlayers/examples/wrapDateLine.html
new file mode 100644
index 0000000..52a39a6
--- /dev/null
+++ b/misc/openlayers/examples/wrapDateLine.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Wrap Date Line</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ function init(){
+ map = new OpenLayers.Map( 'map', {maxResolution: 1.40625} );
+ var mapserv = new OpenLayers.Layer.MapServer( "OpenLayers Basic",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'},
+ {wrapDateLine: true} );
+
+ var kamap = new OpenLayers.Layer.KaMap( "Blue Marble NG",
+ "http://www.openlayers.org/world/index.php",
+ {g: "satellite", map: "world"},
+ {wrapDateLine: true} );
+
+ var wms = new OpenLayers.Layer.WMS( "DM Solutions Demo",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap",
+ {layers: "bathymetry,land_fn,park,drain_fn,drainage," +
+ "prov_bound,fedlimit,rail,road,popplace",
+ transparent: "true", format: "image/png"},
+ {wrapDateLine: true});
+
+ /* WW doesn't quite work yet */
+ ww = new OpenLayers.Layer.WorldWind( "LANDSAT",
+ "http://worldwind25.arc.nasa.gov/tile/tile.aspx", 2.25, 4,
+ {T:"105"},
+ {'maxResolution': .28125,
+ tileSize: new OpenLayers.Size(512, 512),
+ wrapDateLine: true});
+
+ map.addLayers([mapserv, kamap, wms]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Wrapping the Date Line</h1>
+ <div id="tags">
+ WMS,
+ MapServer,
+ wrapDateLine
+ </div>
+
+ <p id="shortdesc">Shows how to work around dateline issues, by wrapping the dateline on a number of layer types.</p>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ This is an example that shows wrapping the date line. Wrapping the
+ date line is an option on the layer.
+ </p>
+ <p>
+ You can do it with a 'Layer.WMS' or a 'Layer.MapServer' layer.
+ </p>
+ <pre id="code">
+ var mapserv = new OpenLayers.Layer.MapServer( "OpenLayers Basic",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'},
+ <b>{wrapDateLine: true}</b> );
+ </pre>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/xhtml.html b/misc/openlayers/examples/xhtml.html
new file mode 100644
index 0000000..b4decfb
--- /dev/null
+++ b/misc/openlayers/examples/xhtml.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
+ <meta name="apple-mobile-web-app-capable" content="yes"/>
+<title>XHTML Example</title>
+<link rel="stylesheet" href="../theme/default/style.css" type="text/css"/>
+ <link rel="stylesheet" href="style.css" type="text/css"/>
+<script src="../lib/OpenLayers.js" type="text/javascript"></script>
+</head>
+<body style="width:100%">
+ <h1 id="title">XHTML Example</h1>
+
+ <div id="tags">
+ xhtml
+ </div>
+ <p id="shortdesc">
+ Shows openlayers running in a XHTML 1.0 Strict Doctype
+ </p>
+
+ <div id="map" class="smallmap"></div>
+ <script defer="defer" type="text/javascript">
+ var map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(wms);
+ map.zoomToMaxExtent();
+ </script>
+ <div id="docs">This example shows openlayers using a XHTML 1.0 Strict Doctype click on the below image to validate.
+ <p>
+ <a href="http://validator.w3.org/check/referer"><img
+ src="http://www.w3.org/Icons/valid-xhtml10"
+ alt="Valid XHTML 1.0!" height="31" width="88" /></a>
+ </p>
+ </div>
+
+</body>
+</html>
+
+
diff --git a/misc/openlayers/examples/xml.html b/misc/openlayers/examples/xml.html
new file mode 100644
index 0000000..696229b
--- /dev/null
+++ b/misc/openlayers/examples/xml.html
@@ -0,0 +1,161 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>XML Parsing Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ #output {
+ font-family: monospace;
+ background-color: #efefef;
+ font-size: 0.9em;
+ padding: 1em;
+ }
+ span.code {
+ font-family: monospace;
+ background-color: #efefef;
+ font-size: 0.9em;
+ padding: 0.25em;
+ line-height: 1.5em;
+ }
+ ul {
+ margin: 0;
+ padding: 0 0 1em 1.5em;
+ }
+ ul li {
+ padding-left: 0;
+ }
+
+ </style>
+ <script src="../lib/Firebug/firebug.js" type="text/javascript"></script>
+ <script src="../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+ //<![CDATA[
+
+ var format = new OpenLayers.Format.XML();
+ var doc = null;
+
+ function init() {
+ OpenLayers.Request.GET({
+ url: "xml/features.xml",
+ success: loadSuccess,
+ failure: loadFailure
+ });
+ }
+
+ function loadSuccess(request) {
+ updateStatus("loaded");
+ if(!request.responseXML.documentElement) {
+ doc = format.read(request.responseText);
+ } else {
+ doc = request.responseXML;
+ }
+ }
+
+ function loadFailure(request) {
+ updateStatus("failed to load");
+ }
+
+ function updateStatus(msg) {
+ document.getElementById("loadStatus").firstChild.nodeValue = msg;
+ }
+
+ function updateOutput(text) {
+ document.getElementById("output").firstChild.nodeValue = text;
+ }
+
+ function write() {
+ var text = format.write(doc);
+ updateOutput(text);
+ }
+
+ function getElementsByTagNameNS(node, uri, name) {
+ var nodes = format.getElementsByTagNameNS(node, uri, name);
+ var pieces = [];
+ for(var i=0; i<nodes.length; ++i) {
+ pieces.push(format.write(nodes[i]));
+ }
+ updateOutput(pieces.join(' '));
+ }
+
+ function hasAttributeNS(node, uri, name) {
+ updateOutput(format.hasAttributeNS(node, uri, name))
+ }
+
+ function getAttributeNodeNS(node, uri, name) {
+ var attributeNode = format.getAttributeNodeNS(node, uri, name);
+ updateOutput(attributeNode.nodeName + ' = "' +
+ attributeNode.nodeValue + '"');
+ }
+
+ function getAttributeNS(node, uri, name) {
+ var attributeValue = format.getAttributeNS(node, uri, name);
+ updateOutput('"' + attributeValue + '"')
+ }
+
+ function createElementNS(uri, name) {
+ var node = format.createElementNS(uri, name);
+ doc.documentElement.appendChild(node);
+ write();
+ }
+
+ function createTextNode(text) {
+ var node = format.createTextNode(text);
+ doc.documentElement.appendChild(node);
+ write();
+ }
+
+ window.onload = init;
+
+ //]]>
+ </script>
+ </head>
+ <body>
+ <h1 id="title">XML Format Example</h1>
+
+ <div id="tags">
+ xml
+ </div>
+
+ <p id="shortdesc">
+ Shows the use of the OpenLayers XML format class
+ </p>
+
+ <div id="docs">
+ <p>OpenLayers has a very simple XML format class (OpenLayers.Format.XML)
+ that can be used to read/write XML docs. The methods available on the
+ XML format (or parser if you like) allow for reading and writing of the
+ various XML flavors used by the library - in particular the vector data
+ formats. It is by no means intended to be a full-fledged XML toolset.
+ Additional methods will be added only as needed elsewhere in the
+ library.</p>
+ <p>This page loads an XML document and demonstrates a few of the methods
+ available in the parser.</p>
+ <p>Status: <b>XML document <span id="loadStatus">loading..</span>.</b></p>
+ <p>After the XML document loads, see the result of a few of the methods
+ below. Assume that you start with the following code:
+ <br>
+ <span class="code">
+ var format = new OpenLayers.Format.XML();
+ </span>
+ </p>
+ Sample methods
+ <ul>
+ <li><a href="javascript:void write();">format.write()</a> - write the XML doc as text</li>
+ <li><a href="javascript:void getElementsByTagNameNS(doc, 'http://www.opengis.net/gml', 'MultiPolygon');">format.getElementsByTagNameNS()</a> - get all gml:MultiPolygon</li>
+ <li><a href="javascript:void hasAttributeNS(doc.documentElement, 'http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation');">format.hasAttributeNS()</a> - test to see schemaLocation attribute exists in the http://www.w3.org/2001/XMLSchema-instance namespace</li>
+ <li><a href="javascript:void getAttributeNodeNS(doc.documentElement, 'http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation');">format.getAttributeNodeNS()</a> - get schemaLocation attribute in the http://www.w3.org/2001/XMLSchema-instance namespace</li>
+ <li><a href="javascript:void getAttributeNS(doc.documentElement, 'http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation');">format.getAttributeNS()</a> - get schemaLocation attribute value in the http://www.w3.org/2001/XMLSchema-instance namespace</li>
+ <li><a href="javascript:void createElementNS('http://bar.com/foo', 'foo:TestNode');">format.createElementNS()</a> - create a foo:TestNode element (and append it to the doc)</li>
+ <li><a href="javascript:void createTextNode('test text ');">format.createTextNode()</a> - create a text node (and append it to the doc)</li>
+ </ul>
+ Output:
+ <div id="output">&nbsp;</div>
+ </div>
+ </body>
+</html>
+
+
diff --git a/misc/openlayers/examples/xml/features.xml b/misc/openlayers/examples/xml/features.xml
new file mode 100644
index 0000000..b213ce5
--- /dev/null
+++ b/misc/openlayers/examples/xml/features.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection numberOfFeatures="2" timeStamp="2007-08-03T13:10:00.071-06:00" xsi:schemaLocation="http://www.openplans.org/topp http://localhost:8080/geoserver/wfs?service=WFS&amp;version=1.1.0&amp;request=DescribeFeatureType&amp;typeName=topp:leases http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:topp="http://www.openplans.org/topp" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ows="http://www.opengis.net/ows" xmlns:wfs="http://www.opengis.net/wfs"><gml:featureMembers><topp:leases gml:id="leases.1"><gml:boundedBy><gml:Envelope srsName="urn:x-ogc:def:crs:EPSG:6.11.2:4267"><gml:lowerCorner>-107.7912454726602 43.649560413854424</gml:lowerCorner><gml:upperCorner>-107.75539905577847 43.66774946861892</gml:upperCorner></gml:Envelope></gml:boundedBy><topp:meridian>6</topp:meridian><topp:township>0430N</topp:township><topp:range>0910W</topp:range><topp:section>27</topp:section><topp:surveytype>A</topp:surveytype><topp:aliquot>w2sw;</topp:aliquot><topp:serialnumb>WYB 0016999A</topp:serialnumb><topp:adminagenc>BUREAU OF LAND MGMT</topp:adminagenc><topp:price>0.0</topp:price><topp:acres>614.3</topp:acres><topp:dispositio>Authorized</topp:dispositio><topp:casetypeco>310781</topp:casetypeco><topp:casetype>O&amp;g renewal lease - pd</topp:casetype><topp:commodity>Oil &amp; gas</topp:commodity><topp:expireyear>0</topp:expireyear><topp:effectdate>6/5/1926</topp:effectdate><topp:royaltyrt>Rlty rate - 5%</topp:royaltyrt><topp:hbp>HBP</topp:hbp><topp:or>OR</topp:or><topp:name1>GAS VENTURES LLC</topp:name1><topp:perint1>100.0</topp:perint1><topp:perint2>0.0</topp:perint2><topp:perint3>0.0</topp:perint3><topp:perint4>0.0</topp:perint4><topp:updatedate>6/1/2006</topp:updatedate><topp:the_geom><gml:MultiPolygon><gml:polygonMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>-107.75540341813374 43.65318043604783 -107.75540766903033 43.649560413854424 -107.76039213131902 43.64957232716459 -107.76537647481773 43.649584044882054 -107.76600694778301 43.649585553307226 -107.76600544447962 43.65320449790224 -107.76600393275089 43.65682260581091 -107.77035309969853 43.6568319555119 -107.77533746205971 43.65684246461631 -107.77533369030677 43.66046005010295 -107.78032119967183 43.66047517767307 -107.78114989067903 43.660477553258325 -107.7811491411714 43.66409732386495 -107.78530636850998 43.66411137468226 -107.78619730956676 43.664114220754314 -107.79029430779957 43.6641274142625 -107.7912454726602 43.66413046978637 -107.79124472581245 43.66774946861892 -107.79029254907311 43.667746432392896 -107.78530411910795 43.66773049422058 -107.7803154837038 43.66771429284182 -107.77532694645721 43.66769786251535 -107.77034201441859 43.66768723301139 -107.76599111151326 43.667677746482155 -107.76599928176243 43.66406177993355 -107.76600204937104 43.66044527933786 -107.76536482605789 43.660441720601554 -107.76095267723535 43.66043320984291 -107.76037976752744 43.6604312952967 -107.76038385503145 43.656811633534815 -107.75539905577847 43.65680054792165 -107.75540341813374 43.65318043604783</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:polygonMember></gml:MultiPolygon></topp:the_geom></topp:leases><topp:leases gml:id="leases.2"><gml:boundedBy><gml:Envelope srsName="urn:x-ogc:def:crs:EPSG:6.11.2:4267"><gml:lowerCorner>-107.76038385503497 43.65314461898675</gml:lowerCorner><gml:upperCorner>-107.74044949722713 43.66043129530163</gml:upperCorner></gml:Envelope></gml:boundedBy><topp:meridian>6</topp:meridian><topp:township>0430N</topp:township><topp:range>0910W</topp:range><topp:section>34</topp:section><topp:surveytype>A</topp:surveytype><topp:aliquot>nene;</topp:aliquot><topp:serialnumb>WYB 0017060A</topp:serialnumb><topp:adminagenc>BUREAU OF LAND MGMT</topp:adminagenc><topp:price>0.0</topp:price><topp:acres>190.0</topp:acres><topp:dispositio>Authorized</topp:dispositio><topp:casetypeco>310781</topp:casetypeco><topp:casetype>O&amp;g renewal lease - pd</topp:casetype><topp:commodity>Oil &amp; gas</topp:commodity><topp:expireyear>0</topp:expireyear><topp:effectdate>8/14/1929</topp:effectdate><topp:royaltyrt>Rlty rate - 5%</topp:royaltyrt><topp:hbp>HBP</topp:hbp><topp:or>OR</topp:or><topp:name1>TEXACO EXPL &amp; PROD INC</topp:name1><topp:perint1>100.0</topp:perint1><topp:perint2>0.0</topp:perint2><topp:perint3>0.0</topp:perint3><topp:perint4>0.0</topp:perint4><topp:updatedate>6/1/2006</topp:updatedate><topp:the_geom><gml:MultiPolygon><gml:polygonMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>-107.74605488318316 43.65994411135142 -107.74543221894442 43.659942507723265 -107.74543182097408 43.66039495347534 -107.74044949722713 43.66038434024628 -107.74045205662398 43.65676451042827 -107.74045468122058 43.65314461898675 -107.74543785843247 43.65315677493463 -107.74543483251206 43.656775865277204 -107.74792589467117 43.656782141688055 -107.7504169506792 43.65678836105594 -107.75290800688019 43.65679449548188 -107.75539905578172 43.65680054791882 -107.76038385503497 43.6568116335444 -107.76037976752876 43.66043129530163 -107.75590467181928 43.66042401057107 -107.75539470030401 43.66042058014666 -107.75539522492454 43.65996803160492 -107.75477258519014 43.65996648396323 -107.75414984843758 43.659964934969224 -107.75352723875065 43.65996338002915 -107.7529045032101 43.65996182230788 -107.7522817671415 43.65996026343829 -107.75165902657075 43.65995861118674 -107.75103641630865 43.65995704018709 -107.75041368089654 43.65995547101946 -107.74979106335141 43.659953806253434 -107.74916845036381 43.659952225696536 -107.74854571394238 43.659950645819315 -107.74792297797997 43.659949063070066 -107.74730023769644 43.65994738546058 -107.74667762361214 43.6599457929788 -107.74605488318316 43.65994411135142</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:polygonMember></gml:MultiPolygon></topp:the_geom></topp:leases></gml:featureMembers></wfs:FeatureCollection> \ No newline at end of file
diff --git a/misc/openlayers/examples/xml/georss-flickr.xml b/misc/openlayers/examples/xml/georss-flickr.xml
new file mode 100644
index 0000000..23ec199
--- /dev/null
+++ b/misc/openlayers/examples/xml/georss-flickr.xml
@@ -0,0 +1,730 @@
+<?xml version="1.0" encoding="utf-8"?>
+<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:georss="http://www.georss.org/georss">
+ <channel>
+ <title>Photos from everyone tagged snowboard and powder, with geodata</title>
+ <link>http://www.flickr.com/photos/</link>
+ <description><![CDATA[]]></description>
+ <pubDate>Sat, 24 Nov 2007 16:25:03 -0800</pubDate>
+ <lastBuildDate>Sat, 24 Nov 2007 16:25:03 -0800</lastBuildDate>
+
+ <generator>http://www.flickr.com/</generator>
+ <image>
+ <url>http://www.flickr.com/images/buddyicon.jpg</url>
+ <title>Photos from everyone tagged snowboard and powder, with geodata</title>
+ <link>http://www.flickr.com/photos/</link>
+ </image>
+
+ <item>
+ <title>hofü07_5259</title>
+ <link>http://www.flickr.com/photos/-mo-/2061136492/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/-mo-/">I - mo - I</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/-mo-/2061136492/" title="hofü07_5259"><img src="http://farm3.static.flickr.com/2303/2061136492_b529e65785_m.jpg" width="240" height="184"
+ alt="hofü07_5259" /></a></p>
+
+ ]]></description>
+ <pubDate>Sat, 24 Nov 2007 16:25:03 -0800</pubDate>
+ <dc:date.Taken>2007-11-17T12:01:18-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (I - mo - I)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/2061136492</guid>
+ <georss:point>47.25162 11.754426</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.25162</geo:lat>
+ <geo:long>11.754426</geo:long>
+ </geo:Point>
+ <media:content url="http://farm3.static.flickr.com/2303/2061136492_7da2c08df7_o.jpg" type="image/jpeg" height="613" width="800" />
+ <media:title>hofü07_5259</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/-mo-/">I - mo - I</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/-mo-/2061136492/" title="hofü07_5259"><img src="http://farm3.static.flickr.com/2303/2061136492_b529e65785_m.jpg" width="240" height="184"
+ alt="hofü07_5259" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm3.static.flickr.com/2303/2061136492_b529e65785_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">I - mo - I</media:credit>
+ <media:category scheme="urn:flickr:tags">sun snow fall fun austria yeah powder snowboard tyrol 2007 hochfügen powderahyeahturn</media:category>
+
+ </item>
+ <item>
+ <title>First tracks</title>
+
+ <link>http://www.flickr.com/photos/ruthanddave/2046899659/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/ruthanddave/">Ruth and Dave</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/ruthanddave/2046899659/" title="First tracks"><img src="http://farm3.static.flickr.com/2207/2046899659_95fa37ae23_m.jpg" width="180"
+ height="240" alt="First tracks" /></a></p>
+
+ <p>Not mine, alas.</p>
+ ]]></description>
+ <pubDate>Mon, 19 Nov 2007 08:20:11 -0800</pubDate>
+ <dc:date.Taken>2007-11-18T11:19:50-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (Ruth and Dave)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/2046899659</guid>
+
+ <georss:point>50.107423 -122.899847</georss:point>
+ <geo:Point>
+ <geo:lat>50.107423</geo:lat>
+ <geo:long>-122.899847</geo:long>
+ </geo:Point>
+ <media:content url="http://farm3.static.flickr.com/2207/2046899659_b4f5485d50_o.jpg" type="image/jpeg" height="3648" width="2736" />
+ <media:title>First tracks</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/ruthanddave/">Ruth and Dave</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/ruthanddave/2046899659/" title="First tracks"><img src="http://farm3.static.flickr.com/2207/2046899659_95fa37ae23_m.jpg" width="180"
+ height="240" alt="First tracks" /></a></p>
+
+ <p>Not mine, alas.</p>
+ ]]></media:text>
+ <media:thumbnail url="http://farm3.static.flickr.com/2207/2046899659_95fa37ae23_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">Ruth and Dave</media:credit>
+ <media:category scheme="urn:flickr:tags">november lines whistler snowboarding tracks peak powder snowboard turns freshies</media:category>
+
+ </item>
+ <item>
+
+ <title>Powder turns</title>
+ <link>http://www.flickr.com/photos/ruthanddave/2047707124/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/ruthanddave/">Ruth and Dave</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/ruthanddave/2047707124/" title="Powder turns"><img src="http://farm3.static.flickr.com/2126/2047707124_2c0e32e014_m.jpg" width="180"
+ height="240" alt="Powder turns" /></a></p>
+
+ ]]></description>
+ <pubDate>Mon, 19 Nov 2007 08:26:04 -0800</pubDate>
+ <dc:date.Taken>2007-11-18T11:19:57-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (Ruth and Dave)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/2047707124</guid>
+ <georss:point>50.107423 -122.899847</georss:point>
+
+ <geo:Point>
+ <geo:lat>50.107423</geo:lat>
+ <geo:long>-122.899847</geo:long>
+ </geo:Point>
+ <media:content url="http://farm3.static.flickr.com/2126/2047707124_0d458f4c19_o.jpg" type="image/jpeg" height="3648" width="2736" />
+ <media:title>Powder turns</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/ruthanddave/">Ruth and Dave</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/ruthanddave/2047707124/" title="Powder turns"><img src="http://farm3.static.flickr.com/2126/2047707124_2c0e32e014_m.jpg" width="180"
+ height="240" alt="Powder turns" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm3.static.flickr.com/2126/2047707124_2c0e32e014_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">Ruth and Dave</media:credit>
+ <media:category scheme="urn:flickr:tags">november face lines whistler snowboarding tracks first peak powder fresh snowboard turns</media:category>
+
+ </item>
+ <item>
+ <title>Splitboard</title>
+
+ <link>http://www.flickr.com/photos/hamsgod/1330385819/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/hamsgod/">@nt!x</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/hamsgod/1330385819/" title="Splitboard"><img src="http://farm2.static.flickr.com/1111/1330385819_8bf66f33be_m.jpg" width="162" height="240"
+ alt="Splitboard" /></a></p>
+
+ ]]></description>
+ <pubDate>Wed, 5 Sep 2007 09:00:56 -0800</pubDate>
+ <dc:date.Taken>2007-03-04T04:40:12-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (@nt!x)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/1330385819</guid>
+ <georss:point>41.931831 -111.646049</georss:point>
+
+ <geo:Point>
+ <geo:lat>41.931831</geo:lat>
+ <geo:long>-111.646049</geo:long>
+ </geo:Point>
+ <media:content url="http://farm2.static.flickr.com/1111/1330385819_8bf66f33be_m.jpg" type="image/jpeg" height="240" width="162" />
+ <media:title>Splitboard</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/hamsgod/">@nt!x</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/hamsgod/1330385819/" title="Splitboard"><img src="http://farm2.static.flickr.com/1111/1330385819_8bf66f33be_m.jpg" width="162" height="240"
+ alt="Splitboard" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm2.static.flickr.com/1111/1330385819_8bf66f33be_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">@nt!x</media:credit>
+ <media:category scheme="urn:flickr:tags">snow mountains snowboarding utah powder yurt snowboard splitboard supershot</media:category>
+
+ </item>
+ <item>
+ <title>Fresh powder</title>
+
+ <link>http://www.flickr.com/photos/oliver80/1266854486/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/oliver80/">Oliver Astrologo</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/oliver80/1266854486/" title="Fresh powder"><img src="http://farm2.static.flickr.com/1310/1266854486_990ef8d68a_m.jpg" width="240" height="160"
+ alt="Fresh powder" /></a></p>
+
+ ]]></description>
+ <pubDate>Wed, 29 Aug 2007 05:19:36 -0800</pubDate>
+ <dc:date.Taken>2007-08-24T10:05:48-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (Oliver Astrologo)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/1266854486</guid>
+ <georss:point>46.528236 10.449789</georss:point>
+
+ <geo:Point>
+ <geo:lat>46.528236</geo:lat>
+ <geo:long>10.449789</geo:long>
+ </geo:Point>
+ <media:content url="http://farm2.static.flickr.com/1310/1266854486_990ef8d68a_m.jpg" type="image/jpeg" height="160" width="240" />
+ <media:title>Fresh powder</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/oliver80/">Oliver Astrologo</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/oliver80/1266854486/" title="Fresh powder"><img src="http://farm2.static.flickr.com/1310/1266854486_990ef8d68a_m.jpg" width="240" height="160"
+ alt="Fresh powder" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm2.static.flickr.com/1310/1266854486_990ef8d68a_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">Oliver Astrologo</media:credit>
+ <media:category scheme="urn:flickr:tags">sky people italy panorama mountain snow alps landscape powder snowboard rider snowboarder passo ghiacciaio stelvio</media:category>
+
+ </item>
+ <item>
+ <title>Sawatch Range From Vail Pano</title>
+
+ <link>http://www.flickr.com/photos/dman861/498747983/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/dman861/">dman861</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/dman861/498747983/" title="Sawatch Range From Vail Pano"><img src="http://farm1.static.flickr.com/202/498747983_0831eb06b6_m.jpg" width="240"
+ height="64" alt="Sawatch Range From Vail Pano" /></a></p>
+
+ <p>View near the Eagle's Nest at the top of Eagle Bahn Gondola. Mount of the Holy Cross mountain is visible in this picture but you can't make out the cross shape. I have no idea who the lady is. Handheld panorama, stitch of 5
+ photos.</p>
+ ]]></description>
+ <pubDate>Mon, 14 May 2007 17:05:11 -0800</pubDate>
+ <dc:date.Taken>2007-05-14T15:00:27-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (dman861)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/498747983</guid>
+
+ <georss:point>39.617598 -106.386773</georss:point>
+ <geo:Point>
+ <geo:lat>39.617598</geo:lat>
+ <geo:long>-106.386773</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/202/498747983_2e2ad022e0_o.jpg" type="image/jpeg" height="768" width="2889" />
+ <media:title>Sawatch Range From Vail Pano</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/dman861/">dman861</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/dman861/498747983/" title="Sawatch Range From Vail Pano"><img src="http://farm1.static.flickr.com/202/498747983_0831eb06b6_m.jpg" width="240"
+ height="64" alt="Sawatch Range From Vail Pano" /></a></p>
+
+ <p>View near the Eagle's Nest at the top of Eagle Bahn Gondola. Mount of the Holy Cross mountain is visible in this picture but you can't make out the cross shape. I have no idea who the lady is. Handheld panorama, stitch of 5
+ photos.</p>
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/202/498747983_0831eb06b6_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">dman861</media:credit>
+ <media:category scheme="urn:flickr:tags">
+ trees sky panorama mountain snow ski mountains sport pinetree person photo colorado peak wideangle panoramic powder resort panasonic trail vail snowboard gondola beavercreek range avon mountainrange mountoftheholycross sawatch dmctz3 tz3
+ </media:category>
+
+ </item>
+ <item>
+
+ <title>Flash Powder</title>
+ <link>http://www.flickr.com/photos/eckan/488776800/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/eckan/">Erik Eckerström</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/eckan/488776800/" title="Flash Powder"><img src="http://farm1.static.flickr.com/228/488776800_a2b7484210_m.jpg" width="240" height="160"
+ alt="Flash Powder" /></a></p>
+
+ <p>Some late night powder in Ã…re</p>
+ ]]></description>
+ <pubDate>Mon, 7 May 2007 13:17:48 -0800</pubDate>
+ <dc:date.Taken>2007-01-18T15:37:17-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (Erik Eckerström)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/488776800</guid>
+
+ <georss:point>63.409238 13.079953</georss:point>
+ <geo:Point>
+ <geo:lat>63.409238</geo:lat>
+ <geo:long>13.079953</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/228/488776800_38966d0741_o.jpg" type="image/jpeg" height="2336" width="3504" />
+ <media:title>Flash Powder</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/eckan/">Erik Eckerström</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/eckan/488776800/" title="Flash Powder"><img src="http://farm1.static.flickr.com/228/488776800_a2b7484210_m.jpg" width="240" height="160"
+ alt="Flash Powder" /></a></p>
+
+ <p>Some late night powder in Ã…re</p>
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/228/488776800_a2b7484210_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">Erik Eckerström</media:credit>
+ <media:category scheme="urn:flickr:tags">snow forest canon eos board flash powder snowboard portfolio Ã…re 30d canon30d canoneos30d are</media:category>
+
+ </item>
+ <item>
+
+ <title>Hannes and Reini</title>
+ <link>http://www.flickr.com/photos/moschitz/483974969/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483974969/" title="Hannes and Reini"><img src="http://farm1.static.flickr.com/230/483974969_6645a1267c_m.jpg" width="240"
+ height="180" alt="Hannes and Reini" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:56:02 -0800</pubDate>
+ <dc:date.Taken>2007-03-26T12:17:53-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483974969</guid>
+ <georss:point>47.252086 14.365139</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.252086</geo:lat>
+ <geo:long>14.365139</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/230/483974969_ceb3fbf711_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Hannes and Reini</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483974969/" title="Hannes and Reini"><img src="http://farm1.static.flickr.com/230/483974969_6645a1267c_m.jpg" width="240"
+ height="180" alt="Hannes and Reini" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/230/483974969_6645a1267c_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Zehnerkar, Obertauern</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483975805/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483975805/" title="Zehnerkar, Obertauern"><img src="http://farm1.static.flickr.com/202/483975805_aae74c0e2d_m.jpg" width="240"
+ height="180" alt="Zehnerkar, Obertauern" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:56:46 -0800</pubDate>
+ <dc:date.Taken>2007-03-21T15:56:56-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483975805</guid>
+ <georss:point>47.249698 13.557643</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.249698</geo:lat>
+ <geo:long>13.557643</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/202/483975805_b8749393b9_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Zehnerkar, Obertauern</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483975805/" title="Zehnerkar, Obertauern"><img src="http://farm1.static.flickr.com/202/483975805_aae74c0e2d_m.jpg" width="240"
+ height="180" alt="Zehnerkar, Obertauern" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/202/483975805_aae74c0e2d_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Uhh, thats high</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483941044/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483941044/" title="Uhh, thats high"><img src="http://farm1.static.flickr.com/179/483941044_11806b381b_m.jpg" width="240" height="180"
+ alt="Uhh, thats high" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:55:25 -0800</pubDate>
+ <dc:date.Taken>2007-03-23T11:38:54-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483941044</guid>
+ <georss:point>47.258669 14.359989</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.258669</geo:lat>
+ <geo:long>14.359989</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/179/483941044_7b13748500_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Uhh, thats high</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483941044/" title="Uhh, thats high"><img src="http://farm1.static.flickr.com/179/483941044_11806b381b_m.jpg" width="240" height="180"
+ alt="Uhh, thats high" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/179/483941044_11806b381b_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Powder days</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483973863/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483973863/" title="Powder days"><img src="http://farm1.static.flickr.com/185/483973863_fdef927258_m.jpg" width="240" height="180"
+ alt="Powder days" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:55:02 -0800</pubDate>
+ <dc:date.Taken>2007-03-26T11:25:01-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483973863</guid>
+ <georss:point>47.258669 14.359989</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.258669</geo:lat>
+ <geo:long>14.359989</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/185/483973863_3d460e16ac_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Powder days</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483973863/" title="Powder days"><img src="http://farm1.static.flickr.com/185/483973863_fdef927258_m.jpg" width="240" height="180"
+ alt="Powder days" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/185/483973863_fdef927258_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Getting ready</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483940792/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483940792/" title="Getting ready"><img src="http://farm1.static.flickr.com/186/483940792_03aa2476e5_m.jpg" width="240" height="180"
+ alt="Getting ready" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:55:14 -0800</pubDate>
+ <dc:date.Taken>2007-03-23T11:38:53-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483940792</guid>
+ <georss:point>47.258669 14.359989</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.258669</geo:lat>
+ <geo:long>14.359989</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/186/483940792_c6d43a3f38_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Getting ready</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483940792/" title="Getting ready"><img src="http://farm1.static.flickr.com/186/483940792_03aa2476e5_m.jpg" width="240" height="180"
+ alt="Getting ready" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/186/483940792_03aa2476e5_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Going up and getting ready for the descent</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483941528/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483941528/" title="Going up and getting ready for the descent"><img src="http://farm1.static.flickr.com/224/483941528_9fba721265_m.jpg"
+ width="240" height="180" alt="Going up and getting ready for the descent" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:55:50 -0800</pubDate>
+ <dc:date.Taken>2007-03-26T12:17:17-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483941528</guid>
+ <georss:point>47.258669 14.359989</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.258669</geo:lat>
+ <geo:long>14.359989</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/224/483941528_c92ea73dea_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Going up and getting ready for the descent</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483941528/" title="Going up and getting ready for the descent"><img src="http://farm1.static.flickr.com/224/483941528_9fba721265_m.jpg"
+ width="240" height="180" alt="Going up and getting ready for the descent" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/224/483941528_9fba721265_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>I made it!</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483941314/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483941314/" title="I made it!"><img src="http://farm1.static.flickr.com/196/483941314_84884b47de_m.jpg" width="240" height="180"
+ alt="I made it!" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:55:39 -0800</pubDate>
+ <dc:date.Taken>2007-03-23T11:38:54-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483941314</guid>
+ <georss:point>47.258669 14.359989</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.258669</geo:lat>
+ <geo:long>14.359989</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/196/483941314_6d08f6e97b_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>I made it!</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483941314/" title="I made it!"><img src="http://farm1.static.flickr.com/196/483941314_84884b47de_m.jpg" width="240" height="180"
+ alt="I made it!" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/196/483941314_84884b47de_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Reini and me</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483942166/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483942166/" title="Reini and me"><img src="http://farm1.static.flickr.com/167/483942166_ab3470e411_m.jpg" width="240" height="180"
+ alt="Reini and me" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:56:24 -0800</pubDate>
+ <dc:date.Taken>2007-03-21T15:45:38-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483942166</guid>
+ <georss:point>47.249698 13.557643</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.249698</geo:lat>
+ <geo:long>13.557643</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/167/483942166_ea09729354_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Reini and me</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483942166/" title="Reini and me"><img src="http://farm1.static.flickr.com/167/483942166_ab3470e411_m.jpg" width="240" height="180"
+ alt="Reini and me" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/167/483942166_ab3470e411_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Obertauern</title>
+
+ <link>http://www.flickr.com/photos/moschitz/483976145/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483976145/" title="Obertauern"><img src="http://farm1.static.flickr.com/174/483976145_3817dac9dc_m.jpg" width="240" height="180"
+ alt="Obertauern" /></a></p>
+
+ ]]></description>
+ <pubDate>Fri, 4 May 2007 08:57:04 -0800</pubDate>
+ <dc:date.Taken>2007-03-21T15:48:01-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (martinom)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/483976145</guid>
+ <georss:point>47.249698 13.557643</georss:point>
+
+ <geo:Point>
+ <geo:lat>47.249698</geo:lat>
+ <geo:long>13.557643</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/174/483976145_a40e67f8e4_o.jpg" type="image/jpeg" height="1200" width="1600" />
+ <media:title>Obertauern</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/moschitz/">martinom</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/moschitz/483976145/" title="Obertauern"><img src="http://farm1.static.flickr.com/174/483976145_3817dac9dc_m.jpg" width="240" height="180"
+ alt="Obertauern" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/174/483976145_3817dac9dc_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">martinom</media:credit>
+ <media:category scheme="urn:flickr:tags">powder snowboard autoupload obertauern lachtal</media:category>
+
+ </item>
+ <item>
+ <title>Ross Fall Line 2</title>
+
+ <link>http://www.flickr.com/photos/pwadsworth/480290173/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/pwadsworth/">phwadsworth</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/pwadsworth/480290173/" title="Ross Fall Line 2"><img src="http://farm1.static.flickr.com/230/480290173_92696d207c_m.jpg" width="182"
+ height="240" alt="Ross Fall Line 2" /></a></p>
+
+ ]]></description>
+ <pubDate>Tue, 1 May 2007 12:02:23 -0800</pubDate>
+ <dc:date.Taken>2007-05-01T15:02:23-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (phwadsworth)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/480290173</guid>
+ <georss:point>44.196728 -72.926688</georss:point>
+
+ <geo:Point>
+ <geo:lat>44.196728</geo:lat>
+ <geo:long>-72.926688</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/230/480290173_5b8ebaf718_o.jpg" type="image/jpeg" height="793" width="600" />
+ <media:title>Ross Fall Line 2</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/pwadsworth/">phwadsworth</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/pwadsworth/480290173/" title="Ross Fall Line 2"><img src="http://farm1.static.flickr.com/230/480290173_92696d207c_m.jpg" width="182"
+ height="240" alt="Ross Fall Line 2" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/230/480290173_92696d207c_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">phwadsworth</media:credit>
+ <media:category scheme="urn:flickr:tags">ski river geotagged powder glen snowboard mad vt mrg vemont</media:category>
+
+ </item>
+ <item>
+ <title>Ross Fall Line 1</title>
+
+ <link>http://www.flickr.com/photos/pwadsworth/480290169/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/pwadsworth/">phwadsworth</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/pwadsworth/480290169/" title="Ross Fall Line 1"><img src="http://farm1.static.flickr.com/218/480290169_35b0301cb4_m.jpg" width="150"
+ height="240" alt="Ross Fall Line 1" /></a></p>
+
+ ]]></description>
+ <pubDate>Tue, 1 May 2007 12:02:23 -0800</pubDate>
+ <dc:date.Taken>2007-05-01T15:02:23-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (phwadsworth)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/480290169</guid>
+ <georss:point>44.196728 -72.926688</georss:point>
+
+ <geo:Point>
+ <geo:lat>44.196728</geo:lat>
+ <geo:long>-72.926688</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/218/480290169_bd54859899_o.jpg" type="image/jpeg" height="959" width="600" />
+ <media:title>Ross Fall Line 1</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/pwadsworth/">phwadsworth</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/pwadsworth/480290169/" title="Ross Fall Line 1"><img src="http://farm1.static.flickr.com/218/480290169_35b0301cb4_m.jpg" width="150"
+ height="240" alt="Ross Fall Line 1" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/218/480290169_35b0301cb4_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">phwadsworth</media:credit>
+ <media:category scheme="urn:flickr:tags">ski river geotagged powder glen snowboard mad vt mrg vemont</media:category>
+
+ </item>
+ <item>
+ <title>IMGP0575</title>
+
+ <link>http://www.flickr.com/photos/beppoegeppa/471446918/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/beppoegeppa/">beppovox</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/beppoegeppa/471446918/" title="IMGP0575"><img src="http://farm1.static.flickr.com/169/471446918_fa54874333_m.jpg" width="240" height="180"
+ alt="IMGP0575" /></a></p>
+
+ ]]></description>
+ <pubDate>Tue, 24 Apr 2007 10:32:41 -0800</pubDate>
+ <dc:date.Taken>2003-12-29T11:07:29-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (beppovox)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/471446918</guid>
+ <georss:point>45.082308 6.761913</georss:point>
+
+ <geo:Point>
+ <geo:lat>45.082308</geo:lat>
+ <geo:long>6.761913</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/169/471446918_f199685d82_o.jpg" type="image/jpeg" height="1944" width="2592" />
+ <media:title>IMGP0575</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/beppoegeppa/">beppovox</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/beppoegeppa/471446918/" title="IMGP0575"><img src="http://farm1.static.flickr.com/169/471446918_fa54874333_m.jpg" width="240" height="180"
+ alt="IMGP0575" /></a></p>
+
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/169/471446918_fa54874333_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">beppovox</media:credit>
+ <media:category scheme="urn:flickr:tags">geotagged free powder snowboard freeride jafferau</media:category>
+
+ </item>
+ <item>
+ <title>Slopes</title>
+
+ <link>http://www.flickr.com/photos/blupic/469735045/</link>
+ <description><![CDATA[
+ <p><a href="http://www.flickr.com/people/blupic/">blupic.com</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/blupic/469735045/" title="Slopes"><img src="http://farm1.static.flickr.com/203/469735045_e035c04ab2_m.jpg" width="240" height="160"
+ alt="Slopes" /></a></p>
+
+ <p>One day...</p>
+ ]]></description>
+ <pubDate>Mon, 23 Apr 2007 02:48:17 -0800</pubDate>
+ <dc:date.Taken>2007-03-31T17:31:13-08:00</dc:date.Taken>
+ <author>nobody@flickr.com (blupic.com)</author>
+ <guid isPermaLink="false">tag:flickr.com,2004:/photo/469735045</guid>
+
+ <georss:point>50.111882 -122.939436</georss:point>
+ <geo:Point>
+ <geo:lat>50.111882</geo:lat>
+ <geo:long>-122.939436</geo:long>
+ </geo:Point>
+ <media:content url="http://farm1.static.flickr.com/203/469735045_e035c04ab2_m.jpg" type="image/jpeg" height="160" width="240" />
+ <media:title>Slopes</media:title>
+ <media:text type="html"><![CDATA[
+ <p><a href="http://www.flickr.com/people/blupic/">blupic.com</a> posted a photo:</p>
+
+ <p><a href="http://www.flickr.com/photos/blupic/469735045/" title="Slopes"><img src="http://farm1.static.flickr.com/203/469735045_e035c04ab2_m.jpg" width="240" height="160"
+ alt="Slopes" /></a></p>
+
+ <p>One day...</p>
+ ]]></media:text>
+ <media:thumbnail url="http://farm1.static.flickr.com/203/469735045_e035c04ab2_s.jpg" height="75" width="75" />
+ <media:credit role="photographer">blupic.com</media:credit>
+ <media:category scheme="urn:flickr:tags">blue winter sky mountain snow canada ski nature vancouver whistler nikon skiing d70 peak columbia powder 1870mmf3545g snowboard british blackcomb mogul</media:category>
+
+ </item>
+
+ </channel>
+</rss> \ No newline at end of file
diff --git a/misc/openlayers/examples/xml/track1.xml b/misc/openlayers/examples/xml/track1.xml
new file mode 100644
index 0000000..69036a4
--- /dev/null
+++ b/misc/openlayers/examples/xml/track1.xml
@@ -0,0 +1,98 @@
+<rss version="2.0" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
+<channel>
+
+<title>Title for First trial track</title>
+<description>Description for first track</description>
+<category>Nelson</category>
+<item><pubDate>1995-12-12T05:00:00Z</pubDate><geo:lat>22.18628611</geo:lat><geo:long>20.30211944</geo:long><title>Phase Change</title><description>Start Phase A</description></item>
+<item><pubDate>1995-12-12T05:05:00Z</pubDate><geo:lat>22.18621944</geo:lat><geo:long>20.28514722</geo:long></item>
+<item><pubDate>1995-12-12T05:10:00Z</pubDate><geo:lat>22.18609722</geo:lat><geo:long>20.266425</geo:long></item>
+<item><pubDate>1995-12-12T05:15:00Z</pubDate><geo:lat>22.18279722</geo:lat><geo:long>20.24935</geo:long></item>
+<item><pubDate>1995-12-12T05:16:00Z</pubDate><geo:lat>22.18153889</geo:lat><geo:long>20.24605556</geo:long><title>Course Change</title><description>220 degs 4kts</description></item>
+<item><pubDate>1995-12-12T05:20:00Z</pubDate><geo:lat>22.1764</geo:lat><geo:long>20.23299444</geo:long></item>
+<item><pubDate>1995-12-12T05:25:00Z</pubDate><geo:lat>22.16996111</geo:lat><geo:long>20.21654167</geo:long></item>
+<item><pubDate>1995-12-12T05:30:00Z</pubDate><geo:lat>22.16351944</geo:lat><geo:long>20.20017222</geo:long></item>
+<item><pubDate>1995-12-12T05:35:00Z</pubDate><geo:lat>22.15710556</geo:lat><geo:long>20.18375278</geo:long></item>
+<item><pubDate>1995-12-12T05:40:00Z</pubDate><geo:lat>22.15062778</geo:lat><geo:long>20.16738889</geo:long></item>
+<item><pubDate>1995-12-12T05:42:00Z</pubDate><geo:lat>22.14803333</geo:lat><geo:long>20.16077222</geo:long><title>Comment</title><description>XO has bridge</description></item>
+<item><pubDate>1995-12-12T05:45:00Z</pubDate><geo:lat>22.14416389</geo:lat><geo:long>20.15091944</geo:long></item>
+<item><pubDate>1995-12-12T05:50:00Z</pubDate><geo:lat>22.13757778</geo:lat><geo:long>20.13483333</geo:long></item>
+<item><pubDate>1995-12-12T05:55:00Z</pubDate><geo:lat>22.12541667</geo:lat><geo:long>20.134125</geo:long></item>
+<item><pubDate>1995-12-12T06:00:00Z</pubDate><geo:lat>22.11250556</geo:lat><geo:long>20.13402778</geo:long></item>
+<item><pubDate>1995-12-12T06:05:00Z</pubDate><geo:lat>22.09960278</geo:lat><geo:long>20.13395</geo:long></item>
+<item><pubDate>1995-12-12T06:10:00Z</pubDate><geo:lat>22.08812222</geo:lat><geo:long>20.14054722</geo:long><title>Comment</title><description></description></item>
+<item><pubDate>1995-12-12T06:13:00Z</pubDate><geo:lat>22.08141389</geo:lat><geo:long>20.14618333</geo:long><title>Course Change</title><description>220 degs 4kts</description></item>
+<item><pubDate>1995-12-12T06:15:00Z</pubDate><geo:lat>22.07695278</geo:lat><geo:long>20.14992778</geo:long></item>
+<item><pubDate>1995-12-12T06:20:00Z</pubDate><geo:lat>22.06584167</geo:lat><geo:long>20.15931111</geo:long></item>
+<item><pubDate>1995-12-12T06:25:00Z</pubDate><geo:lat>22.05460278</geo:lat><geo:long>20.16871944</geo:long></item>
+<item><pubDate>1995-12-12T06:30:00Z</pubDate><geo:lat>22.04315833</geo:lat><geo:long>20.16791667</geo:long></item>
+<item><pubDate>1995-12-12T06:35:00Z</pubDate><geo:lat>22.03118611</geo:lat><geo:long>20.16143056</geo:long></item>
+<item><pubDate>1995-12-12T06:40:00Z</pubDate><geo:lat>22.01912222</geo:lat><geo:long>20.15486389</geo:long></item>
+<item><pubDate>1995-12-12T06:45:00Z</pubDate><geo:lat>22.00708333</geo:lat><geo:long>20.14833056</geo:long></item>
+<item><pubDate>1995-12-12T06:50:00Z</pubDate><geo:lat>21.99504444</geo:lat><geo:long>20.14181111</geo:long></item>
+<item><pubDate>1995-12-12T06:55:00Z</pubDate><geo:lat>21.98423889</geo:lat><geo:long>20.14733611</geo:long></item>
+<item><pubDate>1995-12-12T07:00:00Z</pubDate><geo:lat>21.97367222</geo:lat><geo:long>20.15803333</geo:long></item>
+<item><pubDate>1995-12-12T07:05:00Z</pubDate><geo:lat>21.96306111</geo:lat><geo:long>20.16874444</geo:long></item>
+<item><pubDate>1995-12-12T07:10:00Z</pubDate><geo:lat>21.95407222</geo:lat><geo:long>20.17233611</geo:long></item>
+<item><pubDate>1995-12-12T07:15:00Z</pubDate><geo:lat>21.95885556</geo:lat><geo:long>20.15766944</geo:long></item>
+<item><pubDate>1995-12-12T07:20:00Z</pubDate><geo:lat>21.96630833</geo:lat><geo:long>20.16956944</geo:long></item>
+<item><pubDate>1995-12-12T07:25:00Z</pubDate><geo:lat>21.97450556</geo:lat><geo:long>20.18388056</geo:long></item>
+<item><pubDate>1995-12-12T07:30:00Z</pubDate><geo:lat>21.98276389</geo:lat><geo:long>20.19838333</geo:long></item>
+<item><pubDate>1995-12-12T07:32:00Z</pubDate><geo:lat>21.98605278</geo:lat><geo:long>20.20413056</geo:long><title>Comment</title><description>Suspect opponent to North, slowing down</description></item>
+<item><pubDate>1995-12-12T07:33:00Z</pubDate><geo:lat>21.98691667</geo:lat><geo:long>20.20727778</geo:long><title>Comment</title><description>VIP visitors due. Helo retrieved.</description></item>
+<item><pubDate>1995-12-12T07:34:00Z</pubDate><geo:lat>21.98566944</geo:lat><geo:long>20.20985556</geo:long><title>Comment</title><description>Wind picked up. Switching off sensor Delta</description></item>
+<item><pubDate>1995-12-12T07:35:00Z</pubDate><geo:lat>21.9842</geo:lat><geo:long>20.21243611</geo:long><title>Comment</title><description>Heavenly dusk</description></item>
+<item><pubDate>1995-12-12T07:40:00Z</pubDate><geo:lat>21.97609444</geo:lat><geo:long>20.226425</geo:long></item>
+<item><pubDate>1995-12-12T07:45:00Z</pubDate><geo:lat>21.96786111</geo:lat><geo:long>20.240725</geo:long></item>
+<item><pubDate>1995-12-12T07:50:00Z</pubDate><geo:lat>21.9595</geo:lat><geo:long>20.25507778</geo:long></item>
+<item><pubDate>1995-12-12T07:55:00Z</pubDate><geo:lat>21.95118056</geo:lat><geo:long>20.26941389</geo:long></item>
+<item><pubDate>1995-12-12T08:00:00Z</pubDate><geo:lat>21.94862778</geo:lat><geo:long>20.28483056</geo:long></item>
+<item><pubDate>1995-12-12T08:05:00Z</pubDate><geo:lat>21.95295</geo:lat><geo:long>20.30239444</geo:long></item>
+<item><pubDate>1995-12-12T08:10:00Z</pubDate><geo:lat>21.957325</geo:lat><geo:long>20.32020278</geo:long></item>
+<item><pubDate>1995-12-12T08:15:00Z</pubDate><geo:lat>21.96172222</geo:lat><geo:long>20.33795278</geo:long></item>
+<item><pubDate>1995-12-12T08:20:00Z</pubDate><geo:lat>21.96616111</geo:lat><geo:long>20.35568611</geo:long></item>
+<item><pubDate>1995-12-12T08:25:00Z</pubDate><geo:lat>21.96355556</geo:lat><geo:long>20.371925</geo:long></item>
+<item><pubDate>1995-12-12T08:30:00Z</pubDate><geo:lat>21.95877778</geo:lat><geo:long>20.38858333</geo:long></item>
+<item><pubDate>1995-12-12T08:35:00Z</pubDate><geo:lat>21.95988889</geo:lat><geo:long>20.40708333</geo:long></item>
+<item><pubDate>1995-12-12T08:40:00Z</pubDate><geo:lat>21.96361944</geo:lat><geo:long>20.41884722</geo:long></item>
+<item><pubDate>1995-12-12T08:45:00Z</pubDate><geo:lat>21.96945556</geo:lat><geo:long>20.40425278</geo:long><title>Course Change</title><description>220 degs 4kts</description></item>
+<item><pubDate>1995-12-12T08:50:00Z</pubDate><geo:lat>21.9759</geo:lat><geo:long>20.38807778</geo:long></item>
+<item><pubDate>1995-12-12T08:55:00Z</pubDate><geo:lat>21.98237222</geo:lat><geo:long>20.37183889</geo:long></item>
+<item><pubDate>1995-12-12T09:00:00Z</pubDate><geo:lat>21.98880278</geo:lat><geo:long>20.35546667</geo:long></item>
+<item><pubDate>1995-12-12T09:05:00Z</pubDate><geo:lat>21.99968611</geo:lat><geo:long>20.34794722</geo:long></item>
+<item><pubDate>1995-12-12T09:10:00Z</pubDate><geo:lat>22.01179167</geo:lat><geo:long>20.34156389</geo:long></item>
+<item><pubDate>1995-12-12T09:15:00Z</pubDate><geo:lat>22.02168611</geo:lat><geo:long>20.34614444</geo:long></item>
+<item><pubDate>1995-12-12T09:20:00Z</pubDate><geo:lat>22.02811111</geo:lat><geo:long>20.36220833</geo:long></item>
+<item><pubDate>1995-12-12T09:25:00Z</pubDate><geo:lat>22.03456111</geo:lat><geo:long>20.37856389</geo:long><title>Phase Change</title><description>End Phase A</description></item>
+<item><pubDate>1995-12-12T09:30:00Z</pubDate><geo:lat>22.04145</geo:lat><geo:long>20.36952222</geo:long></item>
+<item><pubDate>1995-12-12T09:35:00Z</pubDate><geo:lat>22.04782222</geo:lat><geo:long>20.35348611</geo:long></item>
+<item><pubDate>1995-12-12T09:40:00Z</pubDate><geo:lat>22.05426389</geo:lat><geo:long>20.33713056</geo:long></item>
+<item><pubDate>1995-12-12T09:45:00Z</pubDate><geo:lat>22.06069444</geo:lat><geo:long>20.32085</geo:long></item>
+<item><pubDate>1995-12-12T09:46:00Z</pubDate><geo:lat>22.06198611</geo:lat><geo:long>20.31761111</geo:long><title>Phase Change</title><description>Start Phase B</description></item>
+<item><pubDate>1995-12-12T09:50:00Z</pubDate><geo:lat>22.06471944</geo:lat><geo:long>20.30389444</geo:long></item>
+<item><pubDate>1995-12-12T09:55:00Z</pubDate><geo:lat>22.07586667</geo:lat><geo:long>20.29758333</geo:long></item>
+<item><pubDate>1995-12-12T10:00:00Z</pubDate><geo:lat>22.08793889</geo:lat><geo:long>20.29113889</geo:long></item>
+<item><pubDate>1995-12-12T10:05:00Z</pubDate><geo:lat>22.10003333</geo:lat><geo:long>20.2848</geo:long></item>
+<item><pubDate>1995-12-12T10:10:00Z</pubDate><geo:lat>22.11219722</geo:lat><geo:long>20.27842222</geo:long></item>
+<item><pubDate>1995-12-12T10:15:00Z</pubDate><geo:lat>22.11530556</geo:lat><geo:long>20.26194167</geo:long></item>
+<item><pubDate>1995-12-12T10:20:00Z</pubDate><geo:lat>22.11756944</geo:lat><geo:long>20.24336667</geo:long></item>
+<item><pubDate>1995-12-12T10:25:00Z</pubDate><geo:lat>22.11986111</geo:lat><geo:long>20.22466944</geo:long></item>
+<item><pubDate>1995-12-12T10:30:00Z</pubDate><geo:lat>22.12238333</geo:lat><geo:long>20.20620833</geo:long></item>
+<item><pubDate>1995-12-12T10:35:00Z</pubDate><geo:lat>22.12890278</geo:lat><geo:long>20.216475</geo:long></item>
+<item><pubDate>1995-12-12T10:40:00Z</pubDate><geo:lat>22.12351944</geo:lat><geo:long>20.22643333</geo:long></item>
+<item><pubDate>1995-12-12T10:45:00Z</pubDate><geo:lat>22.12099722</geo:lat><geo:long>20.23426667</geo:long></item>
+<item><pubDate>1995-12-12T10:50:00Z</pubDate><geo:lat>22.12194167</geo:lat><geo:long>20.23018333</geo:long></item>
+<item><pubDate>1995-12-12T10:55:00Z</pubDate><geo:lat>22.11872778</geo:lat><geo:long>20.23548056</geo:long></item>
+<item><pubDate>1995-12-12T11:00:00Z</pubDate><geo:lat>22.11994167</geo:lat><geo:long>20.23541111</geo:long></item>
+<item><pubDate>1995-12-12T11:05:00Z</pubDate><geo:lat>22.11992778</geo:lat><geo:long>20.23948056</geo:long></item>
+<item><pubDate>1995-12-12T11:10:00Z</pubDate><geo:lat>22.11802222</geo:lat><geo:long>20.24473889</geo:long></item>
+<item><pubDate>1995-12-12T11:15:00Z</pubDate><geo:lat>22.11764444</geo:lat><geo:long>20.24835278</geo:long></item>
+<item><pubDate>1995-12-12T11:20:00Z</pubDate><geo:lat>22.12215556</geo:lat><geo:long>20.24788889</geo:long></item>
+<item><pubDate>1995-12-12T11:25:00Z</pubDate><geo:lat>22.11725278</geo:lat><geo:long>20.25047778</geo:long></item>
+<item><pubDate>1995-12-12T11:30:00Z</pubDate><geo:lat>22.12259722</geo:lat><geo:long>20.24290278</geo:long></item>
+<item><pubDate>1995-12-12T11:35:00Z</pubDate><geo:lat>22.12921944</geo:lat><geo:long>20.24653889</geo:long></item>
+<item><pubDate>1995-12-12T11:40:00Z</pubDate><geo:lat>22.13970833</geo:lat><geo:long>20.25221389</geo:long></item>
+<item><pubDate>1995-12-12T11:41:00Z</pubDate><geo:lat>22.14173889</geo:lat><geo:long>20.25330556</geo:long><title>Phase Change</title><description>End Phase B</description></item>
+</channel>
+</rss>
+
diff --git a/misc/openlayers/examples/xml/wmsdescribelayer.xml b/misc/openlayers/examples/xml/wmsdescribelayer.xml
new file mode 100644
index 0000000..d9bb811
--- /dev/null
+++ b/misc/openlayers/examples/xml/wmsdescribelayer.xml
@@ -0,0 +1,5 @@
+<WMS_DescribeLayerResponse version="1.1.1">
+ <LayerDescription name="topp:states" wfs="http://demo.opengeo.org:80/geoserver/wfs/WfsDispatcher?">
+ <Query typeName="topp:states"/>
+ </LayerDescription>
+</WMS_DescribeLayerResponse>
diff --git a/misc/openlayers/examples/xyz-esri.html b/misc/openlayers/examples/xyz-esri.html
new file mode 100644
index 0000000..71ad270
--- /dev/null
+++ b/misc/openlayers/examples/xyz-esri.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Basic ESRI Map Cache Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ .olImageLoadError {
+ display: none;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ var layerExtent = new OpenLayers.Bounds( -13758743.4295939, 5591455.28887228, -13531302.3472101 , 5757360.4178881);
+ map = new OpenLayers.Map( 'map', {'restrictedExtent': layerExtent} );
+ layer = new OpenLayers.Layer.XYZ( "ESRI",
+ "http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/${z}/${y}/${x}",
+ {sphericalMercator: true} );
+ map.addLayer(layer);
+ map.zoomToExtent(map.restrictedExtent);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Basic ESRI Map Cache Example</h1>
+
+ <div id="tags">
+ XYZ, layer, tile
+ </div>
+
+ <div id="shortdesc">Show a Simple ESRI map using the layer from <a href="http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer">ESRI's server</a>. </div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>Show the use of the XYZ layer to access a map cache provided in
+ spherical mercator by ESRI.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/yelp-georss.xml b/misc/openlayers/examples/yelp-georss.xml
new file mode 100644
index 0000000..3981069
--- /dev/null
+++ b/misc/openlayers/examples/yelp-georss.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet href="http://yelp.com/css/atom.css" type="text/css" media="screen"?>
+<feed xmlns="http://www.w3.org/2005/Atom" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">
+ <rights>Copyright 2007 Yelp, Inc. All rights reserved.</rights>
+ <title>Yelp - Recent Reviews Near Ann Arbor, MI</title>
+ <link href="http://www.yelp.com/?location=Ann+Arbor%2C+MI"/>
+ <updated>2007-05-29T22:58:24-08:00</updated>
+ <author>
+ <name>Recent Reviews Near Ann Arbor, MI</name>
+ </author>
+
+ <entry>
+ <title>Sam S.&#39;s Review Of State Theatre - Ann Arbor (4/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/r-dZCCNtld2ik0QRoTwuUQ?hrid=pve0e49KrVsvgX_wXpZHYA" />
+ <id>http://www.yelp.com/biz/r-dZCCNtld2ik0QRoTwuUQ?hrid=pve0e49KrVsvgX_wXpZHYA</id>
+ <updated>2007-05-29T22:58:24-08:00</updated>
+ <summary type="html">I gotta give this place props for hosting independent movies. Man, I really tire of that Hollywood bullshit; I actually feel retarded afterwards.&lt;br /&gt;&lt;br /&gt;My main gripe is that the seats are not ergonomic at&amp;#8230;</summary>
+ <geo:long>-83.7406005859</geo:long>
+ <geo:lat>42.2790985107</geo:lat>
+ </entry>
+ <entry>
+ <title>Sean M.&#39;s Review Of Maize N Blue Deli - Ann Arbor (5/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/GqqBWV8ysYB48_jLPMl2bA?hrid=ZZIVErEJIo-FMsxOBVX1GQ" />
+ <id>http://www.yelp.com/biz/GqqBWV8ysYB48_jLPMl2bA?hrid=ZZIVErEJIo-FMsxOBVX1GQ</id>
+ <updated>2007-05-29T16:04:19-08:00</updated>
+ <summary type="html">Maize N Blue is synonymous with huge sandwiches (lunch+dinner sized) and good but not showy ingredients. It's a much better value than certain other Ann Arbor delis, and eschews the boutique feel that&amp;#8230;</summary>
+ <geo:long>-83.7255020142</geo:long>
+ <geo:lat>42.266998291</geo:lat>
+ </entry>
+ <entry>
+ <title>Coco C.&#39;s Review Of Tom Thompson Flowers - Ann Arbor (5/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/B_lu4c0HgyHlxOjdKxTW7w?hrid=KNa-bOGA0xltz6bHEu3-AQ" />
+ <id>http://www.yelp.com/biz/B_lu4c0HgyHlxOjdKxTW7w?hrid=KNa-bOGA0xltz6bHEu3-AQ</id>
+ <updated>2007-05-29T10:49:06-08:00</updated>
+ <summary type="html">This place isn't much to look at - you go in and there are buckets upon buckets of loose flowers laying around, and there isn't much room to walk around. However, they make kick-ass arrangements! And&amp;#8230;</summary>
+ <geo:long>-83.7490005493</geo:long>
+ <geo:lat>42.2747993469</geo:lat>
+ </entry>
+ <entry>
+ <title>Sam S.&#39;s Review Of Mitch&#39;s Place - Ann Arbor (3/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/zlJ37GVxuy-9wfvWqpPm1Q?hrid=qK6d7BOgLoK3gLf2u2OXAQ" />
+ <id>http://www.yelp.com/biz/zlJ37GVxuy-9wfvWqpPm1Q?hrid=qK6d7BOgLoK3gLf2u2OXAQ</id>
+ <updated>2007-05-28T21:28:33-08:00</updated>
+ <summary type="html">Another generic sports bar with the generic stripe-o crowd. Cheap pitchers, but constant covers.&lt;br /&gt;&lt;br /&gt;This is where I met Dave, a.k.a. Future Guy. He's skinny, has tall, straggly hair, and wears these&amp;#8230;</summary>
+ <geo:long>-83.7329025269</geo:long>
+ <geo:lat>42.275100708</geo:lat>
+ </entry>
+ <entry>
+ <title>Jane S.&#39;s Review Of Special Moments Photography - Plymouth (1/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/IEsTnmfhN7vFfzt724qojw?hrid=ZvALNYOlSodoOciFsmeLZA" />
+ <id>http://www.yelp.com/biz/IEsTnmfhN7vFfzt724qojw?hrid=ZvALNYOlSodoOciFsmeLZA</id>
+ <updated>2007-05-28T10:18:35-08:00</updated>
+ <summary type="html">I was very unsatisfied for several reasons. First, the photographer did not have the lighting for family portraits right at all! Two faces are almost completely obscured by shadows. Also, the backdrop&amp;#8230;</summary>
+ <geo:long>-83.4609985352</geo:long>
+ <geo:lat>42.3588981628</geo:lat>
+ </entry>
+ <entry>
+ <title>Jacqueline D.&#39;s Review Of Embassy Hotel - Ann Arbor (4/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/Vz0hW6UhF6w4JuvgsqDmMQ?hrid=eMKcIyREDk-pFGdH0EC6jg" />
+ <id>http://www.yelp.com/biz/Vz0hW6UhF6w4JuvgsqDmMQ?hrid=eMKcIyREDk-pFGdH0EC6jg</id>
+ <updated>2007-05-27T09:23:06-08:00</updated>
+ <summary type="html">The Embassy Hotel does what it does well: It is VERY cheap ($40-50/night) and located in the heart of downtown. It is relatively clean. What to expect: Your bed won't be made, your sheets won't be&amp;#8230;</summary>
+ <geo:long>-83.7469024658</geo:long>
+ <geo:lat>42.2812004089</geo:lat>
+ </entry>
+ <entry>
+ <title>Tony C.&#39;s Review Of Cafe Zola - Ann Arbor (5/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/BlUEgCOGzDwpOWnkQn3odw?hrid=X3rR29JnKIqAjEzo9nC2VQ" />
+ <id>http://www.yelp.com/biz/BlUEgCOGzDwpOWnkQn3odw?hrid=X3rR29JnKIqAjEzo9nC2VQ</id>
+ <updated>2007-05-26T07:48:01-08:00</updated>
+ <summary type="html">Zola...this is easily one of the best experiences to be had in Ann Arbor. I'll get the negatives for this place right out in the open. It's popular, really popular. It's expensive, but not break&amp;#8230;</summary>
+ <geo:long>-83.7489013672</geo:long>
+ <geo:lat>42.2806015015</geo:lat>
+ </entry>
+ <entry>
+ <title>Tony C.&#39;s Review Of Melange - Ann Arbor (4/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/aPdz29vOWj4fBlLlCBM7UQ?hrid=ETs0aMnHTjSLy1VTsGxCBg" />
+ <id>http://www.yelp.com/biz/aPdz29vOWj4fBlLlCBM7UQ?hrid=ETs0aMnHTjSLy1VTsGxCBg</id>
+ <updated>2007-05-26T07:30:59-08:00</updated>
+ <summary type="html">Melange has an excellent menu. I've tried the scallops, the perch, and the seabass. All were excellent. The two dishes I'd steer clear of are the rock beef thing and the squid appetizers.&lt;br /&gt;&lt;br /&gt;The rock&amp;#8230;</summary>
+ <geo:long>-83.7481918335</geo:long>
+ <geo:lat>42.283493042</geo:lat>
+ </entry>
+ <entry>
+ <title>Tony C.&#39;s Review Of Bennett Optometry - Ann Arbor (4/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/Oa5c1Zzr6RlkGjx-0KYr1A?hrid=vUiuwPLri6D5LTPrC76UlA" />
+ <id>http://www.yelp.com/biz/Oa5c1Zzr6RlkGjx-0KYr1A?hrid=vUiuwPLri6D5LTPrC76UlA</id>
+ <updated>2007-05-26T07:09:43-08:00</updated>
+ <summary type="html">I just got my eyes checked out here about two months ago and overall, I was satisfied with my experience. The optometrist I got was young, but very knowledgeable and didn't seem to be in a hurry to&amp;#8230;</summary>
+ <geo:long>-83.6924972534</geo:long>
+ <geo:lat>42.3031005859</geo:lat>
+ </entry>
+ <entry>
+ <title>Nedra B.&#39;s Review Of Star&#39;s Cafe - Ann Arbor (4/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/aNeaXyQWZ0LGH3FoNnYzmA?hrid=XUd-5ehybDuujOTinekhWA" />
+ <id>http://www.yelp.com/biz/aNeaXyQWZ0LGH3FoNnYzmA?hrid=XUd-5ehybDuujOTinekhWA</id>
+ <updated>2007-05-26T02:53:51-08:00</updated>
+ <summary type="html">As you walk in, you hear arabic music playing the background, and about 4 or 5 tables in front of the main window. You walk up to the menu, choose from many middle eastern foods, give your order, and&amp;#8230;</summary>
+ <geo:long>-83.782539</geo:long>
+ <geo:lat>42.281079</geo:lat>
+ </entry>
+ <entry>
+ <title>Nedra B.&#39;s Review Of Vinology - Ann Arbor (3/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/qkw4xWWgTufvBs1NcxsFnw?hrid=lFMHzQ0GwJ93Ns0kCoPWPQ" />
+ <id>http://www.yelp.com/biz/qkw4xWWgTufvBs1NcxsFnw?hrid=lFMHzQ0GwJ93Ns0kCoPWPQ</id>
+ <updated>2007-05-26T02:45:15-08:00</updated>
+ <summary type="html">I knew to expect a pricey "high class" restaurant... but in my opinion they went over the top. I went with my sister and some friends, and she ordered the little "mini burgers" and I ordered the only&amp;#8230;</summary>
+ <geo:long>-83.7487030029</geo:long>
+ <geo:lat>42.2812004089</geo:lat>
+ </entry>
+ <entry>
+ <title>Coco C.&#39;s Review Of Eastern Accents - Ann Arbor (4/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/AztY39QdGAkoKrLy9Di2yw?hrid=HL2_XnWdZP1FO95e1q-Xjw" />
+ <id>http://www.yelp.com/biz/AztY39QdGAkoKrLy9Di2yw?hrid=HL2_XnWdZP1FO95e1q-Xjw</id>
+ <updated>2007-05-25T15:43:30-08:00</updated>
+ <summary type="html">Oooh baby, this is my dream food come to life - sweet buns with tasty meat inside of them. I'm already a big fan of WowBao in Chicago, so to find this place in Ann Arbor makes me living here a&amp;#8230;</summary>
+ <geo:long>-83.7472991943</geo:long>
+ <geo:lat>42.2803001404</geo:lat>
+ </entry>
+ <entry>
+ <title>Liam C.&#39;s Review Of The Dartmoor - Plymouth (1/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/Q32V2uxVKKv8_fp-hGxyZQ?hrid=7zFDEW0THJGYTUPPHLZucQ" />
+ <id>http://www.yelp.com/biz/Q32V2uxVKKv8_fp-hGxyZQ?hrid=7zFDEW0THJGYTUPPHLZucQ</id>
+ <updated>2007-05-24T14:36:00-08:00</updated>
+ <summary type="html">Okay, so here is how fawked up it is in Plymouth.&lt;br /&gt;&lt;br /&gt;This place was a hotel, back in the day. Dude converts it to offices and retail, and leases the bar and restaurant out to someone else.&lt;br /&gt;&lt;br /&gt;the tenant gets&amp;#8230;</summary>
+ <geo:long>-83.4705963135</geo:long>
+ <geo:lat>42.3686981201</geo:lat>
+ </entry>
+ <entry>
+ <title>Liam C.&#39;s Review Of Omelette &amp; Waffle Cafe - Plymouth (2/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/a09i0TiPG4_yhl-fnzPzDA?hrid=I4ENTNhbAK8pLP_qJmivTQ" />
+ <id>http://www.yelp.com/biz/a09i0TiPG4_yhl-fnzPzDA?hrid=I4ENTNhbAK8pLP_qJmivTQ</id>
+ <updated>2007-05-24T14:32:05-08:00</updated>
+ <summary type="html">Who does this guy pay off? Every year, he wins the Chii cook off but when you order the chili here in the restaurant, it's Hormel right out of a can!&lt;br /&gt;&lt;br /&gt;tired and uninspired boring diner egg dishes,&amp;#8230;</summary>
+ <geo:long>-83.4709014893</geo:long>
+ <geo:lat>42.3681983948</geo:lat>
+ </entry>
+ <entry>
+ <title>Liam C.&#39;s Review Of Zack&#39;s of Plymouth - Plymouth (2/5)</title>
+ <link rel="alternate" type="text/html" href="http://www.yelp.com/biz/F0iuCrXUd_fEJ-LZh8wMFw?hrid=2VLIUcC_oI8b_aLTMjYx8w" />
+ <id>http://www.yelp.com/biz/F0iuCrXUd_fEJ-LZh8wMFw?hrid=2VLIUcC_oI8b_aLTMjYx8w</id>
+ <updated>2007-05-24T14:30:12-08:00</updated>
+ <summary type="html">If you like greasy diners with vaguely ethnic workers yelling in orgy-borgy talk back int he kitchen... you're gonna love this place.&lt;br /&gt;&lt;br /&gt;It's cleaner than it's competitors....gotta give them props on&amp;#8230;</summary>
+ <geo:long>-83.4692993164</geo:long>
+ <geo:lat>42.3581008911</geo:lat>
+ </entry>
+</feed>
diff --git a/misc/openlayers/examples/zoom.html b/misc/openlayers/examples/zoom.html
new file mode 100644
index 0000000..8ca11bf
--- /dev/null
+++ b/misc/openlayers/examples/zoom.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Zoom Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style>
+ .olControlAttribution {
+ bottom: 5px;
+ font-size: 9px;
+ }
+ #customZoom {
+ z-index: 1001;
+ position: relative;
+ top: 10px;
+ left: 10px;
+ }
+ #customZoom a {
+ text-decoration: none;
+ position: absolute;
+ display: block;
+ width: 50px;
+ text-align: center;
+ font-weight: bold;
+ color: #fff;
+ background: #369;
+ border: 1px solid #ccc;
+ border-radius: 3px;
+ }
+ #customZoom a:hover {
+ background: #036;
+ }
+ #customZoomOut {
+ top: 25px;
+ }
+
+ </style>
+ </head>
+ <body>
+ <h1 id="title">Zoom Control Example</h1>
+ <div id="tags">zoom control light</div>
+
+ <div id="shortdesc">Shows how to use a simple zoom control.</div>
+
+ <div id="map" class="smallmap"></div>
+ <p>The map above uses the default control configuration and style.</p>
+ <p>The map below uses the custom zoom elements and styling.</p>
+ <div id="map2" class="smallmap">
+ <div id="customZoom">
+ <a href="#customZoomIn" id="customZoomIn">in</a>
+ <a href="#customZoomOut" id="customZoomOut">out</a>
+ </div>
+ </div>
+
+ <div id="docs">
+ <p>This example demonstrates the use of a Zoom control.</p>
+ <p>
+ See the <a href="zoom.js" target="_blank">zoom.js</a> source
+ for details.
+ </p>
+ </div>
+ <script src="../lib/OpenLayers.js"></script>
+ <script src="zoom.js"></script>
+ </body>
+</html>
diff --git a/misc/openlayers/examples/zoom.js b/misc/openlayers/examples/zoom.js
new file mode 100644
index 0000000..08694cc
--- /dev/null
+++ b/misc/openlayers/examples/zoom.js
@@ -0,0 +1,34 @@
+var map = new OpenLayers.Map({
+ div: "map",
+ layers: [new OpenLayers.Layer.OSM()],
+ controls: [
+ new OpenLayers.Control.Navigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.Zoom()
+ ],
+ center: [0, 0],
+ zoom: 1
+});
+
+var map2 = new OpenLayers.Map({
+ div: "map2",
+ layers: [new OpenLayers.Layer.OSM()],
+ controls: [
+ new OpenLayers.Control.Navigation({
+ dragPanOptions: {
+ enableKinetic: true
+ }
+ }),
+ new OpenLayers.Control.Attribution(),
+ new OpenLayers.Control.Zoom({
+ zoomInId: "customZoomIn",
+ zoomOutId: "customZoomOut"
+ })
+ ],
+ center: [0, 0],
+ zoom: 1
+});
diff --git a/misc/openlayers/examples/zoomLevels.html b/misc/openlayers/examples/zoomLevels.html
new file mode 100644
index 0000000..d4eb1b1
--- /dev/null
+++ b/misc/openlayers/examples/zoomLevels.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 50;
+ var lat = 0;
+ var zoom = 0;
+ var map, layer;
+
+ function init(){
+ OpenLayers.DOTS_PER_INCH = 72;
+ var options = {
+
+// various ways of specifying similar things
+// resolutions: [1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.00137329101],
+// scales: [50000000, 10000000],
+// maxResolution: 0.17578125,
+// minResolution: 0.0439453125,
+// maxScale: 10000000,
+// minScale: 50000000,
+// numZoomLevels: 5,
+// units: "dd",
+ minResolution: "auto",
+ minExtent: new OpenLayers.Bounds(-1, -1, 1, 1),
+ maxResolution: "auto",
+ maxExtent: new OpenLayers.Bounds(-180, -90, 180, 90)
+ };
+
+ map = new OpenLayers.Map( 'map' , options);
+
+
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'});
+ map.addLayer(layer);
+
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Zoom Level</h1>
+
+ <div id="tags">
+ zoom, zoomlevel, resolution, scale, cleanup
+ </div>
+
+ <p id="shortdesc">
+ This example shows the use of the resolutions layer option on a number of layer types.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ Set the extent of the viewable map using preset levels of scale available
+ to the user via the zoom slider bar. You can set the minimum, maximum
+ scales or resolutions, the number of levels in between and the minimum
+ and maximum geographic extents in your map's units.
+ </p>
+ <p>
+ Default units for Scale are in inches. Resolution is specified in map units
+ per pixel where the default map units are decimal degrees(dd).<br>
+ scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
+ OpenLayers.DOTS_PER_INCH <br>
+ You can either set the scale or the resolution, there is no need to set both.
+ </p>
+ <p>
+ You can do it with a ...
+ </p>
+ </div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/examples/zoomify.html b/misc/openlayers/examples/zoomify.html
new file mode 100644
index 0000000..6b610aa
--- /dev/null
+++ b/misc/openlayers/examples/zoomify.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Zoomify Example</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var zoomify_width = 8001;
+ var zoomify_height = 6943;
+ var zoomify_url = "http://almor.mzk.cz/moll/AA22/0103/";
+
+ var map, zoomify;
+
+ function init(){
+ /* First we initialize the zoomify pyramid (to get number of tiers) */
+ var zoomify = new OpenLayers.Layer.Zoomify( "Zoomify", zoomify_url,
+ new OpenLayers.Size( zoomify_width, zoomify_height ) );
+
+ /* Map with raster coordinates (pixels) from Zoomify image */
+ var options = {
+ maxExtent: new OpenLayers.Bounds(0, 0, zoomify_width, zoomify_height),
+ maxResolution: Math.pow(2, zoomify.numberOfTiers-1 ),
+ numZoomLevels: zoomify.numberOfTiers,
+ units: 'pixels'
+ };
+
+ map = new OpenLayers.Map("map", options);
+ map.addLayer(zoomify);
+
+ map.setBaseLayer(zoomify);
+ map.zoomToMaxExtent();
+ };
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Zoomify Layer Example</h1>
+
+ <div id="tags">
+ zoomify, layer
+ </div>
+
+ <p id="shortdesc">
+ Demo of a layer with Zoomify tiles.
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ Demonstration of the Zoomify layer in OpenLayers.<br>
+ You can have a look at <a href="http://almor.mzk.cz/moll/AA22/103.html">Zoomify viewer for this picture</a>, which is using the same <a href="http://almor.mzk.cz/moll/AA22/0103/">tiles</a>.
+ </p>
+ <p>
+ For change to our own image you have to specify 'url' (zoomifyImagePath in Zoomify terminology) and 'size' ('width' and 'height' from ImageProperty.xml file).<br>
+ Custom tiles can be easily generated with original <a href="http://www.zoomify.com/">Zoomify software</a> like with freely available <a href="http://www.zoomify.com/express.htm">ZoomifyerEZ</a> or with Adobe PhotoShop CS3 (it has built in support for export into Zoomify tiles).<br>
+ There is also a <a href="http://sourceforge.net/projects/zoomifyimage/">ZoomifyImage SourceForge Project</a>, a tile cutter available under GPL license.<br>
+ Zoomify tiles can be also served dynamically on the server side from JPEG2000 masters using <a href="http://dltj.org/article/introducing-j2ktilerenderer/">J2KTileRender</a> with available integration for DSpace and soon for Fedora Digital Repository.<br>
+ <a href="http://iipimage.sourceforge.net/">IIPImage server</a> can serve Zoomify tiles dynamically from TIFF files.
+ </p>
+ <p>
+ Development of the Zoomify support for OpenLayers was supported from the grant <a href="http://www.oldmapsonline.org/">Old Maps Online</a>.
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/img/blank.gif b/misc/openlayers/img/blank.gif
new file mode 100644
index 0000000..4bcc753
--- /dev/null
+++ b/misc/openlayers/img/blank.gif
Binary files differ
diff --git a/misc/openlayers/img/cloud-popup-relative.png b/misc/openlayers/img/cloud-popup-relative.png
new file mode 100755
index 0000000..c9fd4c4
--- /dev/null
+++ b/misc/openlayers/img/cloud-popup-relative.png
Binary files differ
diff --git a/misc/openlayers/img/drag-rectangle-off.png b/misc/openlayers/img/drag-rectangle-off.png
new file mode 100644
index 0000000..382a81d
--- /dev/null
+++ b/misc/openlayers/img/drag-rectangle-off.png
Binary files differ
diff --git a/misc/openlayers/img/drag-rectangle-on.png b/misc/openlayers/img/drag-rectangle-on.png
new file mode 100644
index 0000000..2ed2d5b
--- /dev/null
+++ b/misc/openlayers/img/drag-rectangle-on.png
Binary files differ
diff --git a/misc/openlayers/img/east-mini.png b/misc/openlayers/img/east-mini.png
new file mode 100644
index 0000000..ecedc5e
--- /dev/null
+++ b/misc/openlayers/img/east-mini.png
Binary files differ
diff --git a/misc/openlayers/img/layer-switcher-maximize.png b/misc/openlayers/img/layer-switcher-maximize.png
new file mode 100644
index 0000000..f346086
--- /dev/null
+++ b/misc/openlayers/img/layer-switcher-maximize.png
Binary files differ
diff --git a/misc/openlayers/img/layer-switcher-minimize.png b/misc/openlayers/img/layer-switcher-minimize.png
new file mode 100644
index 0000000..b4aab0b
--- /dev/null
+++ b/misc/openlayers/img/layer-switcher-minimize.png
Binary files differ
diff --git a/misc/openlayers/img/marker-blue.png b/misc/openlayers/img/marker-blue.png
new file mode 100644
index 0000000..f5b4efc
--- /dev/null
+++ b/misc/openlayers/img/marker-blue.png
Binary files differ
diff --git a/misc/openlayers/img/marker-gold.png b/misc/openlayers/img/marker-gold.png
new file mode 100644
index 0000000..0b62f96
--- /dev/null
+++ b/misc/openlayers/img/marker-gold.png
Binary files differ
diff --git a/misc/openlayers/img/marker-green.png b/misc/openlayers/img/marker-green.png
new file mode 100644
index 0000000..c36b164
--- /dev/null
+++ b/misc/openlayers/img/marker-green.png
Binary files differ
diff --git a/misc/openlayers/img/marker.png b/misc/openlayers/img/marker.png
new file mode 100644
index 0000000..ea3e59a
--- /dev/null
+++ b/misc/openlayers/img/marker.png
Binary files differ
diff --git a/misc/openlayers/img/measuring-stick-off.png b/misc/openlayers/img/measuring-stick-off.png
new file mode 100644
index 0000000..efbf63f
--- /dev/null
+++ b/misc/openlayers/img/measuring-stick-off.png
Binary files differ
diff --git a/misc/openlayers/img/measuring-stick-on.png b/misc/openlayers/img/measuring-stick-on.png
new file mode 100644
index 0000000..2d41c84
--- /dev/null
+++ b/misc/openlayers/img/measuring-stick-on.png
Binary files differ
diff --git a/misc/openlayers/img/north-mini.png b/misc/openlayers/img/north-mini.png
new file mode 100644
index 0000000..dfd7211
--- /dev/null
+++ b/misc/openlayers/img/north-mini.png
Binary files differ
diff --git a/misc/openlayers/img/panning-hand-off.png b/misc/openlayers/img/panning-hand-off.png
new file mode 100644
index 0000000..d1c593e
--- /dev/null
+++ b/misc/openlayers/img/panning-hand-off.png
Binary files differ
diff --git a/misc/openlayers/img/panning-hand-on.png b/misc/openlayers/img/panning-hand-on.png
new file mode 100644
index 0000000..9b7e064
--- /dev/null
+++ b/misc/openlayers/img/panning-hand-on.png
Binary files differ
diff --git a/misc/openlayers/img/slider.png b/misc/openlayers/img/slider.png
new file mode 100644
index 0000000..4335364
--- /dev/null
+++ b/misc/openlayers/img/slider.png
Binary files differ
diff --git a/misc/openlayers/img/south-mini.png b/misc/openlayers/img/south-mini.png
new file mode 100644
index 0000000..2970875
--- /dev/null
+++ b/misc/openlayers/img/south-mini.png
Binary files differ
diff --git a/misc/openlayers/img/west-mini.png b/misc/openlayers/img/west-mini.png
new file mode 100644
index 0000000..363cd3d
--- /dev/null
+++ b/misc/openlayers/img/west-mini.png
Binary files differ
diff --git a/misc/openlayers/img/zoom-minus-mini.png b/misc/openlayers/img/zoom-minus-mini.png
new file mode 100644
index 0000000..8f0d77f
--- /dev/null
+++ b/misc/openlayers/img/zoom-minus-mini.png
Binary files differ
diff --git a/misc/openlayers/img/zoom-plus-mini.png b/misc/openlayers/img/zoom-plus-mini.png
new file mode 100644
index 0000000..a73ab4e
--- /dev/null
+++ b/misc/openlayers/img/zoom-plus-mini.png
Binary files differ
diff --git a/misc/openlayers/img/zoom-world-mini.png b/misc/openlayers/img/zoom-world-mini.png
new file mode 100644
index 0000000..aebf22d
--- /dev/null
+++ b/misc/openlayers/img/zoom-world-mini.png
Binary files differ
diff --git a/misc/openlayers/img/zoombar.png b/misc/openlayers/img/zoombar.png
new file mode 100644
index 0000000..47110ab
--- /dev/null
+++ b/misc/openlayers/img/zoombar.png
Binary files differ
diff --git a/misc/openlayers/lib/Firebug/errorIcon.png b/misc/openlayers/lib/Firebug/errorIcon.png
new file mode 100644
index 0000000..2d75261
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/errorIcon.png
Binary files differ
diff --git a/misc/openlayers/lib/Firebug/firebug.css b/misc/openlayers/lib/Firebug/firebug.css
new file mode 100644
index 0000000..1f041c4
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/firebug.css
@@ -0,0 +1,209 @@
+
+html, body {
+ margin: 0;
+ background: #FFFFFF;
+ font-family: Lucida Grande, Tahoma, sans-serif;
+ font-size: 11px;
+ overflow: hidden;
+}
+
+a {
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.toolbar {
+ height: 14px;
+ border-top: 1px solid ThreeDHighlight;
+ border-bottom: 1px solid ThreeDShadow;
+ padding: 2px 6px;
+ background: ThreeDFace;
+}
+
+.toolbarRight {
+ position: absolute;
+ top: 4px;
+ right: 6px;
+}
+
+#log {
+ overflow: auto;
+ position: absolute;
+ left: 0;
+ width: 100%;
+}
+
+#commandLine {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 18px;
+ border: none;
+ border-top: 1px solid ThreeDShadow;
+}
+
+/************************************************************************************************/
+
+.logRow {
+ position: relative;
+ border-bottom: 1px solid #D7D7D7;
+ padding: 2px 4px 1px 6px;
+ background-color: #FFFFFF;
+}
+
+.logRow-command {
+ font-family: Monaco, monospace;
+ color: blue;
+}
+
+.objectBox-null {
+ padding: 0 2px;
+ border: 1px solid #666666;
+ background-color: #888888;
+ color: #FFFFFF;
+}
+
+.objectBox-string {
+ font-family: Monaco, monospace;
+ color: red;
+ white-space: pre;
+}
+
+.objectBox-number {
+ color: #000088;
+}
+
+.objectBox-function {
+ font-family: Monaco, monospace;
+ color: DarkGreen;
+}
+
+.objectBox-object {
+ color: DarkGreen;
+ font-weight: bold;
+}
+
+/************************************************************************************************/
+
+.logRow-info,
+.logRow-error,
+.logRow-warning {
+ background: #FFFFFF no-repeat 2px 2px;
+ padding-left: 20px;
+ padding-bottom: 3px;
+}
+
+.logRow-info {
+ background-image: url(infoIcon.png);
+}
+
+.logRow-warning {
+ background-color: cyan;
+ background-image: url(warningIcon.png);
+}
+
+.logRow-error {
+ background-color: LightYellow;
+ background-image: url(errorIcon.png);
+}
+
+.errorMessage {
+ vertical-align: top;
+ color: #FF0000;
+}
+
+.objectBox-sourceLink {
+ position: absolute;
+ right: 4px;
+ top: 2px;
+ padding-left: 8px;
+ font-family: Lucida Grande, sans-serif;
+ font-weight: bold;
+ color: #0000FF;
+}
+
+/************************************************************************************************/
+
+.logRow-group {
+ background: #EEEEEE;
+ border-bottom: none;
+}
+
+.logGroup {
+ background: #EEEEEE;
+}
+
+.logGroupBox {
+ margin-left: 24px;
+ border-top: 1px solid #D7D7D7;
+ border-left: 1px solid #D7D7D7;
+}
+
+/************************************************************************************************/
+
+.selectorTag,
+.selectorId,
+.selectorClass {
+ font-family: Monaco, monospace;
+ font-weight: normal;
+}
+
+.selectorTag {
+ color: #0000FF;
+}
+
+.selectorId {
+ color: DarkBlue;
+}
+
+.selectorClass {
+ color: red;
+}
+
+/************************************************************************************************/
+
+.objectBox-element {
+ font-family: Monaco, monospace;
+ color: #000088;
+}
+
+.nodeChildren {
+ margin-left: 16px;
+}
+
+.nodeTag {
+ color: blue;
+}
+
+.nodeValue {
+ color: #FF0000;
+ font-weight: normal;
+}
+
+.nodeText,
+.nodeComment {
+ margin: 0 2px;
+ vertical-align: top;
+}
+
+.nodeText {
+ color: #333333;
+}
+
+.nodeComment {
+ color: DarkGreen;
+}
+
+/************************************************************************************************/
+
+.propertyNameCell {
+ vertical-align: top;
+}
+
+.propertyName {
+ font-weight: bold;
+}
diff --git a/misc/openlayers/lib/Firebug/firebug.html b/misc/openlayers/lib/Firebug/firebug.html
new file mode 100644
index 0000000..861e639
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/firebug.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+ <title>Firebug</title>
+ <link rel="stylesheet" type="text/css" href="firebug.css">
+</head>
+
+<body>
+ <div id="toolbar" class="toolbar">
+ <a href="#" onclick="parent.console.clear()">Clear</a>
+ <span class="toolbarRight">
+ <a href="#" onclick="parent.console.close()">Close</a>
+ </span>
+ </div>
+ <div id="log"></div>
+ <input type="text" id="commandLine">
+
+ <script>parent.onFirebugReady(document);</script>
+</body>
+</html>
diff --git a/misc/openlayers/lib/Firebug/firebug.js b/misc/openlayers/lib/Firebug/firebug.js
new file mode 100644
index 0000000..f07825e
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/firebug.js
@@ -0,0 +1,674 @@
+
+if (!window.console || !console.firebug) { (function()
+{
+ window.console =
+ {
+ log: function()
+ {
+ logFormatted(arguments, "");
+ },
+
+ debug: function()
+ {
+ logFormatted(arguments, "debug");
+ },
+
+ info: function()
+ {
+ logFormatted(arguments, "info");
+ },
+
+ warn: function()
+ {
+ logFormatted(arguments, "warning");
+ },
+
+ error: function()
+ {
+ logFormatted(arguments, "error");
+ },
+
+ assert: function(truth, message)
+ {
+ if (!truth)
+ {
+ var args = [];
+ for (var i = 1; i < arguments.length; ++i)
+ args.push(arguments[i]);
+
+ logFormatted(args.length ? args : ["Assertion Failure"], "error");
+ throw message ? message : "Assertion Failure";
+ }
+ },
+
+ dir: function(object)
+ {
+ var html = [];
+
+ var pairs = [];
+ for (var name in object)
+ {
+ try
+ {
+ pairs.push([name, object[name]]);
+ }
+ catch (exc)
+ {
+ }
+ }
+
+ pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; });
+
+ html.push('<table>');
+ for (var i = 0; i < pairs.length; ++i)
+ {
+ var name = pairs[i][0], value = pairs[i][1];
+
+ html.push('<tr>',
+ '<td class="propertyNameCell"><span class="propertyName">',
+ escapeHTML(name), '</span></td>', '<td><span class="propertyValue">');
+ appendObject(value, html);
+ html.push('</span></td></tr>');
+ }
+ html.push('</table>');
+
+ logRow(html, "dir");
+ },
+
+ dirxml: function(node)
+ {
+ var html = [];
+
+ appendNode(node, html);
+ logRow(html, "dirxml");
+ },
+
+ group: function()
+ {
+ logRow(arguments, "group", pushGroup);
+ },
+
+ groupEnd: function()
+ {
+ logRow(arguments, "", popGroup);
+ },
+
+ time: function(name)
+ {
+ timeMap[name] = (new Date()).getTime();
+ },
+
+ timeEnd: function(name)
+ {
+ if (name in timeMap)
+ {
+ var delta = (new Date()).getTime() - timeMap[name];
+ logFormatted([name+ ":", delta+"ms"]);
+ delete timeMap[name];
+ }
+ },
+
+ count: function()
+ {
+ this.warn(["count() not supported."]);
+ },
+
+ trace: function()
+ {
+ this.warn(["trace() not supported."]);
+ },
+
+ profile: function()
+ {
+ this.warn(["profile() not supported."]);
+ },
+
+ profileEnd: function()
+ {
+ },
+
+ clear: function()
+ {
+ consoleBody.innerHTML = "";
+ },
+
+ open: function()
+ {
+ toggleConsole(true);
+ },
+
+ close: function()
+ {
+ if (frameVisible)
+ toggleConsole();
+ }
+ };
+
+ // ********************************************************************************************
+
+ var consoleFrame = null;
+ var consoleBody = null;
+ var commandLine = null;
+
+ var frameVisible = false;
+ var messageQueue = [];
+ var groupStack = [];
+ var timeMap = {};
+
+ var clPrefix = ">>> ";
+
+ var isFirefox = navigator.userAgent.indexOf("Firefox") != -1;
+ var isIE = navigator.userAgent.indexOf("MSIE") != -1;
+ var isOpera = navigator.userAgent.indexOf("Opera") != -1;
+ var isSafari = navigator.userAgent.indexOf("AppleWebKit") != -1;
+
+ // ********************************************************************************************
+
+ function toggleConsole(forceOpen)
+ {
+ frameVisible = forceOpen || !frameVisible;
+ if (consoleFrame)
+ consoleFrame.style.visibility = frameVisible ? "visible" : "hidden";
+ else
+ waitForBody();
+ }
+
+ function focusCommandLine()
+ {
+ toggleConsole(true);
+ if (commandLine)
+ commandLine.focus();
+ }
+
+ function waitForBody()
+ {
+ if (document.body)
+ createFrame();
+ else
+ setTimeout(waitForBody, 200);
+ }
+
+ function createFrame()
+ {
+ if (consoleFrame)
+ return;
+
+ window.onFirebugReady = function(doc)
+ {
+ window.onFirebugReady = null;
+
+ var toolbar = doc.getElementById("toolbar");
+ toolbar.onmousedown = onSplitterMouseDown;
+
+ commandLine = doc.getElementById("commandLine");
+ addEvent(commandLine, "keydown", onCommandLineKeyDown);
+
+ addEvent(doc, isIE || isSafari ? "keydown" : "keypress", onKeyDown);
+
+ consoleBody = doc.getElementById("log");
+ layout();
+ flush();
+ };
+
+ var baseURL = getFirebugURL();
+
+ consoleFrame = document.createElement("iframe");
+ consoleFrame.setAttribute("src", baseURL+"/firebug.html");
+ consoleFrame.setAttribute("frameBorder", "0");
+ consoleFrame.style.visibility = (frameVisible ? "visible" : "hidden");
+ consoleFrame.style.zIndex = "2147483583";
+ consoleFrame.style.position = document.all ? "absolute" : "fixed";
+ consoleFrame.style.width = "100%";
+ consoleFrame.style.left = "0";
+ consoleFrame.style.bottom = "0";
+ consoleFrame.style.height = "200px";
+ document.body.appendChild(consoleFrame);
+ }
+
+ function getFirebugURL()
+ {
+ var scripts = document.getElementsByTagName("script");
+ for (var i = 0; i < scripts.length; ++i)
+ {
+ if (scripts[i].src.indexOf("firebug.js") != -1)
+ {
+ var lastSlash = scripts[i].src.lastIndexOf("/");
+ return scripts[i].src.substr(0, lastSlash);
+ }
+ }
+ }
+
+ function evalCommandLine()
+ {
+ var text = commandLine.value;
+ commandLine.value = "";
+
+ logRow([clPrefix, text], "command");
+
+ var value;
+ try
+ {
+ value = eval(text);
+ }
+ catch (exc)
+ {
+ }
+
+ console.log(value);
+ }
+
+ function layout()
+ {
+ var toolbar = consoleBody.ownerDocument.getElementById("toolbar");
+ var height = consoleFrame.offsetHeight - (toolbar.offsetHeight + commandLine.offsetHeight);
+ height = Math.max(height, 0);
+ consoleBody.style.top = toolbar.offsetHeight + "px";
+ consoleBody.style.height = height + "px";
+
+ commandLine.style.top = (consoleFrame.offsetHeight - commandLine.offsetHeight) + "px";
+ }
+
+ function logRow(message, className, handler)
+ {
+ if (consoleBody)
+ writeMessage(message, className, handler);
+ else
+ {
+ messageQueue.push([message, className, handler]);
+ waitForBody();
+ }
+ }
+
+ function flush()
+ {
+ var queue = messageQueue;
+ messageQueue = [];
+
+ for (var i = 0; i < queue.length; ++i)
+ writeMessage(queue[i][0], queue[i][1], queue[i][2]);
+ }
+
+ function writeMessage(message, className, handler)
+ {
+ var isScrolledToBottom =
+ consoleBody.scrollTop + consoleBody.offsetHeight >= consoleBody.scrollHeight;
+
+ if (!handler)
+ handler = writeRow;
+
+ handler(message, className);
+
+ if (isScrolledToBottom)
+ consoleBody.scrollTop = consoleBody.scrollHeight - consoleBody.offsetHeight;
+ }
+
+ function appendRow(row)
+ {
+ var container = groupStack.length ? groupStack[groupStack.length-1] : consoleBody;
+ container.appendChild(row);
+ }
+
+ function writeRow(message, className)
+ {
+ var row = consoleBody.ownerDocument.createElement("div");
+ row.className = "logRow" + (className ? " logRow-"+className : "");
+ row.innerHTML = message.join("");
+ appendRow(row);
+ }
+
+ function pushGroup(message, className)
+ {
+ logFormatted(message, className);
+
+ var groupRow = consoleBody.ownerDocument.createElement("div");
+ groupRow.className = "logGroup";
+ var groupRowBox = consoleBody.ownerDocument.createElement("div");
+ groupRowBox.className = "logGroupBox";
+ groupRow.appendChild(groupRowBox);
+ appendRow(groupRowBox);
+ groupStack.push(groupRowBox);
+ }
+
+ function popGroup()
+ {
+ groupStack.pop();
+ }
+
+ // ********************************************************************************************
+
+ function logFormatted(objects, className)
+ {
+ var html = [];
+
+ var format = objects[0];
+ var objIndex = 0;
+
+ if (typeof(format) != "string")
+ {
+ format = "";
+ objIndex = -1;
+ }
+
+ var parts = parseFormat(format);
+ for (var i = 0; i < parts.length; ++i)
+ {
+ var part = parts[i];
+ if (part && typeof(part) == "object")
+ {
+ var object = objects[++objIndex];
+ part.appender(object, html);
+ }
+ else
+ appendText(part, html);
+ }
+
+ for (var i = objIndex+1; i < objects.length; ++i)
+ {
+ appendText(" ", html);
+
+ var object = objects[i];
+ if (typeof(object) == "string")
+ appendText(object, html);
+ else
+ appendObject(object, html);
+ }
+
+ logRow(html, className);
+ }
+
+ function parseFormat(format)
+ {
+ var parts = [];
+
+ var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/;
+ var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat};
+
+ for (var m = reg.exec(format); m; m = reg.exec(format))
+ {
+ var type = m[8] ? m[8] : m[5];
+ var appender = type in appenderMap ? appenderMap[type] : appendObject;
+ var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);
+
+ parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
+ parts.push({appender: appender, precision: precision});
+
+ format = format.substr(m.index+m[0].length);
+ }
+
+ parts.push(format);
+
+ return parts;
+ }
+
+ function escapeHTML(value)
+ {
+ function replaceChars(ch)
+ {
+ switch (ch)
+ {
+ case "<":
+ return "&lt;";
+ case ">":
+ return "&gt;";
+ case "&":
+ return "&amp;";
+ case "'":
+ return "&#39;";
+ case '"':
+ return "&quot;";
+ }
+ return "?";
+ };
+ return String(value).replace(/[<>&"']/g, replaceChars);
+ }
+
+ function objectToString(object)
+ {
+ try
+ {
+ return object+"";
+ }
+ catch (exc)
+ {
+ return null;
+ }
+ }
+
+ // ********************************************************************************************
+
+ function appendText(object, html)
+ {
+ html.push(escapeHTML(objectToString(object)));
+ }
+
+ function appendNull(object, html)
+ {
+ html.push('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>');
+ }
+
+ function appendString(object, html)
+ {
+ html.push('<span class="objectBox-string">&quot;', escapeHTML(objectToString(object)),
+ '&quot;</span>');
+ }
+
+ function appendInteger(object, html)
+ {
+ html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
+ }
+
+ function appendFloat(object, html)
+ {
+ html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
+ }
+
+ function appendFunction(object, html)
+ {
+ var reName = /function ?(.*?)\(/;
+ var m = reName.exec(objectToString(object));
+ var name = m ? m[1] : "function";
+ html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>');
+ }
+
+ function appendObject(object, html)
+ {
+ try
+ {
+ if (object == undefined)
+ appendNull("undefined", html);
+ else if (object == null)
+ appendNull("null", html);
+ else if (typeof object == "string")
+ appendString(object, html);
+ else if (typeof object == "number")
+ appendInteger(object, html);
+ else if (typeof object == "function")
+ appendFunction(object, html);
+ else if (object.nodeType == 1)
+ appendSelector(object, html);
+ else if (typeof object == "object")
+ appendObjectFormatted(object, html);
+ else
+ appendText(object, html);
+ }
+ catch (exc)
+ {
+ }
+ }
+
+ function appendObjectFormatted(object, html)
+ {
+ var text = objectToString(object);
+ var reObject = /\[object (.*?)\]/;
+
+ var m = reObject.exec(text);
+ html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>')
+ }
+
+ function appendSelector(object, html)
+ {
+ html.push('<span class="objectBox-selector">');
+
+ html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>');
+ if (object.id)
+ html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>');
+ if (object.className)
+ html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>');
+
+ html.push('</span>');
+ }
+
+ function appendNode(node, html)
+ {
+ if (node.nodeType == 1)
+ {
+ html.push(
+ '<div class="objectBox-element">',
+ '&lt;<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>');
+
+ for (var i = 0; i < node.attributes.length; ++i)
+ {
+ var attr = node.attributes[i];
+ if (!attr.specified)
+ continue;
+
+ html.push('&nbsp;<span class="nodeName">', attr.nodeName.toLowerCase(),
+ '</span>=&quot;<span class="nodeValue">', escapeHTML(attr.nodeValue),
+ '</span>&quot;')
+ }
+
+ if (node.firstChild)
+ {
+ html.push('&gt;</div><div class="nodeChildren">');
+
+ for (var child = node.firstChild; child; child = child.nextSibling)
+ appendNode(child, html);
+
+ html.push('</div><div class="objectBox-element">&lt;/<span class="nodeTag">',
+ node.nodeName.toLowerCase(), '&gt;</span></div>');
+ }
+ else
+ html.push('/&gt;</div>');
+ }
+ else if (node.nodeType == 3)
+ {
+ html.push('<div class="nodeText">', escapeHTML(node.nodeValue),
+ '</div>');
+ }
+ }
+
+ // ********************************************************************************************
+
+ function addEvent(object, name, handler)
+ {
+ if (document.all)
+ object.attachEvent("on"+name, handler);
+ else
+ object.addEventListener(name, handler, false);
+ }
+
+ function removeEvent(object, name, handler)
+ {
+ if (document.all)
+ object.detachEvent("on"+name, handler);
+ else
+ object.removeEventListener(name, handler, false);
+ }
+
+ function cancelEvent(event)
+ {
+ if (document.all)
+ event.cancelBubble = true;
+ else
+ event.stopPropagation();
+ }
+
+ function onError(msg, href, lineNo)
+ {
+ var html = [];
+
+ var lastSlash = href.lastIndexOf("/");
+ var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1);
+
+ html.push(
+ '<span class="errorMessage">', msg, '</span>',
+ '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>'
+ );
+
+ logRow(html, "error");
+ };
+
+ function onKeyDown(event)
+ {
+ if (event.keyCode == 123)
+ toggleConsole();
+ else if ((event.keyCode == 108 || event.keyCode == 76) && event.shiftKey
+ && (event.metaKey || event.ctrlKey))
+ focusCommandLine();
+ else
+ return;
+
+ cancelEvent(event);
+ }
+
+ function onSplitterMouseDown(event)
+ {
+ if (isSafari || isOpera)
+ return;
+
+ addEvent(document, "mousemove", onSplitterMouseMove);
+ addEvent(document, "mouseup", onSplitterMouseUp);
+
+ for (var i = 0; i < frames.length; ++i)
+ {
+ addEvent(frames[i].document, "mousemove", onSplitterMouseMove);
+ addEvent(frames[i].document, "mouseup", onSplitterMouseUp);
+ }
+ }
+
+ function onSplitterMouseMove(event)
+ {
+ var win = document.all
+ ? event.srcElement.ownerDocument.parentWindow
+ : event.target.ownerDocument.defaultView;
+
+ var clientY = event.clientY;
+ if (win != win.parent)
+ clientY += win.frameElement ? win.frameElement.offsetTop : 0;
+
+ var height = consoleFrame.offsetTop + consoleFrame.clientHeight;
+ var toolbar = consoleBody.ownerDocument.getElementById("toolbar");
+ var y = Math.max(height - clientY,
+ toolbar.offsetHeight + commandLine.offsetHeight);
+
+ consoleFrame.style.height = y + "px";
+ layout();
+ }
+
+ function onSplitterMouseUp(event)
+ {
+ removeEvent(document, "mousemove", onSplitterMouseMove);
+ removeEvent(document, "mouseup", onSplitterMouseUp);
+
+ for (var i = 0; i < frames.length; ++i)
+ {
+ removeEvent(frames[i].document, "mousemove", onSplitterMouseMove);
+ removeEvent(frames[i].document, "mouseup", onSplitterMouseUp);
+ }
+ }
+
+ function onCommandLineKeyDown(event)
+ {
+ if (event.keyCode == 13)
+ evalCommandLine();
+ else if (event.keyCode == 27)
+ commandLine.value = "";
+ }
+
+ window.onerror = onError;
+ addEvent(document, isIE || isSafari ? "keydown" : "keypress", onKeyDown);
+
+ if (document.documentElement.getAttribute("debug") == "true")
+ toggleConsole(true);
+})();
+}
diff --git a/misc/openlayers/lib/Firebug/firebugx.js b/misc/openlayers/lib/Firebug/firebugx.js
new file mode 100644
index 0000000..f0a34df
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/firebugx.js
@@ -0,0 +1,10 @@
+(function() {
+ if (!window.console || !console.firebug) {
+ var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml",
+ "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"];
+
+ window.console = {};
+ for (var i = 0; i < names.length; ++i)
+ window.console[names[i]] = function() {}
+ }
+})();
diff --git a/misc/openlayers/lib/Firebug/infoIcon.png b/misc/openlayers/lib/Firebug/infoIcon.png
new file mode 100644
index 0000000..da1e533
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/infoIcon.png
Binary files differ
diff --git a/misc/openlayers/lib/Firebug/license.txt b/misc/openlayers/lib/Firebug/license.txt
new file mode 100644
index 0000000..ba43b75
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/license.txt
@@ -0,0 +1,30 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2007, Parakey Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Parakey Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Parakey Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/misc/openlayers/lib/Firebug/readme.txt b/misc/openlayers/lib/Firebug/readme.txt
new file mode 100644
index 0000000..1edebf5
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/readme.txt
@@ -0,0 +1,13 @@
+This directory contains the source for Firebug Lite
+(http://www.getfirebug.com/lite.html). This code is distributed with a
+BSD License, Copyright (c) 2007, Parakey Inc. See the included license.txt
+for the full text of the license.
+
+This is a patched version of the trunk from
+http://fbug.googlecode.com/svn/trunk.
+
+Revision 36 was patched to resolve the issue described here
+http://code.google.com/p/fbug/issues/detail?id=85
+
+When this issue is resolved, Firebug Lite can be used directly - no further
+modifications are needed for OpenLayers. \ No newline at end of file
diff --git a/misc/openlayers/lib/Firebug/warningIcon.png b/misc/openlayers/lib/Firebug/warningIcon.png
new file mode 100644
index 0000000..de51084
--- /dev/null
+++ b/misc/openlayers/lib/Firebug/warningIcon.png
Binary files differ
diff --git a/misc/openlayers/lib/OpenLayers.js b/misc/openlayers/lib/OpenLayers.js
new file mode 100644
index 0000000..03530c2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers.js
@@ -0,0 +1,429 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/*
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Lang/en.js
+ * @requires OpenLayers/Console.js
+ */
+
+/*
+ * TODO: In 3.0, we will stop supporting build profiles that include
+ * OpenLayers.js. This means we will not need the singleFile and scriptFile
+ * variables, because we don't have to handle the singleFile case any more.
+ */
+
+(function() {
+ /**
+ * Before creating the OpenLayers namespace, check to see if
+ * OpenLayers.singleFile is true. This occurs if the
+ * OpenLayers/SingleFile.js script is included before this one - as is the
+ * case with old single file build profiles that included both
+ * OpenLayers.js and OpenLayers/SingleFile.js.
+ */
+ var singleFile = (typeof OpenLayers == "object" && OpenLayers.singleFile);
+
+ /**
+ * Relative path of this script.
+ */
+ var scriptName = (!singleFile) ? "lib/OpenLayers.js" : "OpenLayers.js";
+
+ /*
+ * If window.OpenLayers isn't set when this script (OpenLayers.js) is
+ * evaluated (and if singleFile is false) then this script will load
+ * *all* OpenLayers scripts. If window.OpenLayers is set to an array
+ * then this script will attempt to load scripts for each string of
+ * the array, using the string as the src of the script.
+ *
+ * Example:
+ * (code)
+ * <script type="text/javascript">
+ * window.OpenLayers = [
+ * "OpenLayers/Util.js",
+ * "OpenLayers/BaseTypes.js"
+ * ];
+ * </script>
+ * <script type="text/javascript" src="../lib/OpenLayers.js"></script>
+ * (end)
+ * In this example OpenLayers.js will load Util.js and BaseTypes.js only.
+ */
+ var jsFiles = window.OpenLayers;
+
+ /**
+ * Namespace: OpenLayers
+ * The OpenLayers object provides a namespace for all things OpenLayers
+ */
+ window.OpenLayers = {
+ /**
+ * Method: _getScriptLocation
+ * Return the path to this script. This is also implemented in
+ * OpenLayers/SingleFile.js
+ *
+ * Returns:
+ * {String} Path to this script
+ */
+ _getScriptLocation: (function() {
+ var r = new RegExp("(^|(.*?\\/))(" + scriptName + ")(\\?|$)"),
+ s = document.getElementsByTagName('script'),
+ src, m, l = "";
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+ return (function() { return l; });
+ })(),
+
+ /**
+ * APIProperty: ImgPath
+ * {String} Set this to the path where control images are stored, a path
+ * given here must end with a slash. If set to '' (which is the default)
+ * OpenLayers will use its script location + "img/".
+ *
+ * You will need to set this property when you have a singlefile build of
+ * OpenLayers that either is not named "OpenLayers.js" or if you move
+ * the file in a way such that the image directory cannot be derived from
+ * the script location.
+ *
+ * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+ * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+ * way of including OpenLayers in your HTML would be:
+ *
+ * (code)
+ * <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+ * <script type="text/javascript">
+ * // tell OpenLayers where the control images are
+ * // remember the trailing slash
+ * OpenLayers.ImgPath = "/resources/external/images/ol/";
+ * </script>
+ * (end code)
+ *
+ * Please remember that when your OpenLayers script is not named
+ * "OpenLayers.js" you will have to make sure that the default theme is
+ * loaded into the page by including an appropriate <link>-tag,
+ * e.g.:
+ *
+ * (code)
+ * <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
+ * (end code)
+ */
+ ImgPath : ''
+ };
+
+ /**
+ * OpenLayers.singleFile is a flag indicating this file is being included
+ * in a Single File Library build of the OpenLayers Library.
+ *
+ * When we are *not* part of a SFL build we dynamically include the
+ * OpenLayers library code.
+ *
+ * When we *are* part of a SFL build we do not dynamically include the
+ * OpenLayers library code as it will be appended at the end of this file.
+ */
+ if(!singleFile) {
+ if (!jsFiles) {
+ jsFiles = [
+ "OpenLayers/BaseTypes/Class.js",
+ "OpenLayers/Util.js",
+ "OpenLayers/Util/vendorPrefix.js",
+ "OpenLayers/Animation.js",
+ "OpenLayers/BaseTypes.js",
+ "OpenLayers/BaseTypes/Bounds.js",
+ "OpenLayers/BaseTypes/Date.js",
+ "OpenLayers/BaseTypes/Element.js",
+ "OpenLayers/BaseTypes/LonLat.js",
+ "OpenLayers/BaseTypes/Pixel.js",
+ "OpenLayers/BaseTypes/Size.js",
+ "OpenLayers/Console.js",
+ "OpenLayers/Tween.js",
+ "OpenLayers/Kinetic.js",
+ "OpenLayers/Events.js",
+ "OpenLayers/Events/buttonclick.js",
+ "OpenLayers/Events/featureclick.js",
+ "OpenLayers/Request.js",
+ "OpenLayers/Request/XMLHttpRequest.js",
+ "OpenLayers/Projection.js",
+ "OpenLayers/Map.js",
+ "OpenLayers/Layer.js",
+ "OpenLayers/Icon.js",
+ "OpenLayers/Marker.js",
+ "OpenLayers/Marker/Box.js",
+ "OpenLayers/Popup.js",
+ "OpenLayers/Tile.js",
+ "OpenLayers/Tile/Image.js",
+ "OpenLayers/Tile/Image/IFrame.js",
+ "OpenLayers/Tile/UTFGrid.js",
+ "OpenLayers/Layer/Image.js",
+ "OpenLayers/Layer/SphericalMercator.js",
+ "OpenLayers/Layer/EventPane.js",
+ "OpenLayers/Layer/FixedZoomLevels.js",
+ "OpenLayers/Layer/Google.js",
+ "OpenLayers/Layer/Google/v3.js",
+ "OpenLayers/Layer/HTTPRequest.js",
+ "OpenLayers/Layer/Grid.js",
+ "OpenLayers/Layer/MapGuide.js",
+ "OpenLayers/Layer/MapServer.js",
+ "OpenLayers/Layer/KaMap.js",
+ "OpenLayers/Layer/KaMapCache.js",
+ "OpenLayers/Layer/Markers.js",
+ "OpenLayers/Layer/Text.js",
+ "OpenLayers/Layer/WorldWind.js",
+ "OpenLayers/Layer/ArcGIS93Rest.js",
+ "OpenLayers/Layer/WMS.js",
+ "OpenLayers/Layer/WMTS.js",
+ "OpenLayers/Layer/ArcIMS.js",
+ "OpenLayers/Layer/GeoRSS.js",
+ "OpenLayers/Layer/Boxes.js",
+ "OpenLayers/Layer/XYZ.js",
+ "OpenLayers/Layer/UTFGrid.js",
+ "OpenLayers/Layer/OSM.js",
+ "OpenLayers/Layer/Bing.js",
+ "OpenLayers/Layer/TMS.js",
+ "OpenLayers/Layer/TileCache.js",
+ "OpenLayers/Layer/Zoomify.js",
+ "OpenLayers/Layer/ArcGISCache.js",
+ "OpenLayers/Popup/Anchored.js",
+ "OpenLayers/Popup/Framed.js",
+ "OpenLayers/Popup/FramedCloud.js",
+ "OpenLayers/Feature.js",
+ "OpenLayers/Feature/Vector.js",
+ "OpenLayers/Handler.js",
+ "OpenLayers/Handler/Click.js",
+ "OpenLayers/Handler/Hover.js",
+ "OpenLayers/Handler/Point.js",
+ "OpenLayers/Handler/Path.js",
+ "OpenLayers/Handler/Polygon.js",
+ "OpenLayers/Handler/Feature.js",
+ "OpenLayers/Handler/Drag.js",
+ "OpenLayers/Handler/Pinch.js",
+ "OpenLayers/Handler/RegularPolygon.js",
+ "OpenLayers/Handler/Box.js",
+ "OpenLayers/Handler/MouseWheel.js",
+ "OpenLayers/Handler/Keyboard.js",
+ "OpenLayers/Control.js",
+ "OpenLayers/Control/Attribution.js",
+ "OpenLayers/Control/Button.js",
+ "OpenLayers/Control/CacheRead.js",
+ "OpenLayers/Control/CacheWrite.js",
+ "OpenLayers/Control/ZoomBox.js",
+ "OpenLayers/Control/ZoomToMaxExtent.js",
+ "OpenLayers/Control/DragPan.js",
+ "OpenLayers/Control/Navigation.js",
+ "OpenLayers/Control/PinchZoom.js",
+ "OpenLayers/Control/TouchNavigation.js",
+ "OpenLayers/Control/MousePosition.js",
+ "OpenLayers/Control/OverviewMap.js",
+ "OpenLayers/Control/KeyboardDefaults.js",
+ "OpenLayers/Control/PanZoom.js",
+ "OpenLayers/Control/PanZoomBar.js",
+ "OpenLayers/Control/ArgParser.js",
+ "OpenLayers/Control/Permalink.js",
+ "OpenLayers/Control/Scale.js",
+ "OpenLayers/Control/ScaleLine.js",
+ "OpenLayers/Control/Snapping.js",
+ "OpenLayers/Control/Split.js",
+ "OpenLayers/Control/LayerSwitcher.js",
+ "OpenLayers/Control/DrawFeature.js",
+ "OpenLayers/Control/DragFeature.js",
+ "OpenLayers/Control/ModifyFeature.js",
+ "OpenLayers/Control/Panel.js",
+ "OpenLayers/Control/SelectFeature.js",
+ "OpenLayers/Control/NavigationHistory.js",
+ "OpenLayers/Control/Measure.js",
+ "OpenLayers/Control/WMSGetFeatureInfo.js",
+ "OpenLayers/Control/WMTSGetFeatureInfo.js",
+ "OpenLayers/Control/Graticule.js",
+ "OpenLayers/Control/TransformFeature.js",
+ "OpenLayers/Control/UTFGrid.js",
+ "OpenLayers/Control/SLDSelect.js",
+ "OpenLayers/Control/Zoom.js",
+ "OpenLayers/Geometry.js",
+ "OpenLayers/Geometry/Collection.js",
+ "OpenLayers/Geometry/Point.js",
+ "OpenLayers/Geometry/MultiPoint.js",
+ "OpenLayers/Geometry/Curve.js",
+ "OpenLayers/Geometry/LineString.js",
+ "OpenLayers/Geometry/LinearRing.js",
+ "OpenLayers/Geometry/Polygon.js",
+ "OpenLayers/Geometry/MultiLineString.js",
+ "OpenLayers/Geometry/MultiPolygon.js",
+ "OpenLayers/Renderer.js",
+ "OpenLayers/Renderer/Elements.js",
+ "OpenLayers/Renderer/SVG.js",
+ "OpenLayers/Renderer/Canvas.js",
+ "OpenLayers/Renderer/VML.js",
+ "OpenLayers/Layer/Vector.js",
+ "OpenLayers/Layer/PointGrid.js",
+ "OpenLayers/Layer/Vector/RootContainer.js",
+ "OpenLayers/Strategy.js",
+ "OpenLayers/Strategy/Filter.js",
+ "OpenLayers/Strategy/Fixed.js",
+ "OpenLayers/Strategy/Cluster.js",
+ "OpenLayers/Strategy/Paging.js",
+ "OpenLayers/Strategy/BBOX.js",
+ "OpenLayers/Strategy/Save.js",
+ "OpenLayers/Strategy/Refresh.js",
+ "OpenLayers/Filter.js",
+ "OpenLayers/Filter/FeatureId.js",
+ "OpenLayers/Filter/Logical.js",
+ "OpenLayers/Filter/Comparison.js",
+ "OpenLayers/Filter/Spatial.js",
+ "OpenLayers/Filter/Function.js",
+ "OpenLayers/Protocol.js",
+ "OpenLayers/Protocol/HTTP.js",
+ "OpenLayers/Protocol/WFS.js",
+ "OpenLayers/Protocol/WFS/v1.js",
+ "OpenLayers/Protocol/WFS/v1_0_0.js",
+ "OpenLayers/Protocol/WFS/v1_1_0.js",
+ "OpenLayers/Protocol/CSW.js",
+ "OpenLayers/Protocol/CSW/v2_0_2.js",
+ "OpenLayers/Protocol/Script.js",
+ "OpenLayers/Protocol/SOS.js",
+ "OpenLayers/Protocol/SOS/v1_0_0.js",
+ "OpenLayers/Layer/PointTrack.js",
+ "OpenLayers/Style.js",
+ "OpenLayers/Style2.js",
+ "OpenLayers/StyleMap.js",
+ "OpenLayers/Rule.js",
+ "OpenLayers/Format.js",
+ "OpenLayers/Format/QueryStringFilter.js",
+ "OpenLayers/Format/XML.js",
+ "OpenLayers/Format/XML/VersionedOGC.js",
+ "OpenLayers/Format/Context.js",
+ "OpenLayers/Format/ArcXML.js",
+ "OpenLayers/Format/ArcXML/Features.js",
+ "OpenLayers/Format/GML.js",
+ "OpenLayers/Format/GML/Base.js",
+ "OpenLayers/Format/GML/v2.js",
+ "OpenLayers/Format/GML/v3.js",
+ "OpenLayers/Format/Atom.js",
+ "OpenLayers/Format/EncodedPolyline.js",
+ "OpenLayers/Format/KML.js",
+ "OpenLayers/Format/GeoRSS.js",
+ "OpenLayers/Format/WFS.js",
+ "OpenLayers/Format/OWSCommon.js",
+ "OpenLayers/Format/OWSCommon/v1.js",
+ "OpenLayers/Format/OWSCommon/v1_0_0.js",
+ "OpenLayers/Format/OWSCommon/v1_1_0.js",
+ "OpenLayers/Format/WCSCapabilities.js",
+ "OpenLayers/Format/WCSCapabilities/v1.js",
+ "OpenLayers/Format/WCSCapabilities/v1_0_0.js",
+ "OpenLayers/Format/WCSCapabilities/v1_1_0.js",
+ "OpenLayers/Format/WFSCapabilities.js",
+ "OpenLayers/Format/WFSCapabilities/v1.js",
+ "OpenLayers/Format/WFSCapabilities/v1_0_0.js",
+ "OpenLayers/Format/WFSCapabilities/v1_1_0.js",
+ "OpenLayers/Format/WFSDescribeFeatureType.js",
+ "OpenLayers/Format/WMSDescribeLayer.js",
+ "OpenLayers/Format/WMSDescribeLayer/v1_1.js",
+ "OpenLayers/Format/WKT.js",
+ "OpenLayers/Format/CQL.js",
+ "OpenLayers/Format/OSM.js",
+ "OpenLayers/Format/GPX.js",
+ "OpenLayers/Format/Filter.js",
+ "OpenLayers/Format/Filter/v1.js",
+ "OpenLayers/Format/Filter/v1_0_0.js",
+ "OpenLayers/Format/Filter/v1_1_0.js",
+ "OpenLayers/Format/SLD.js",
+ "OpenLayers/Format/SLD/v1.js",
+ "OpenLayers/Format/SLD/v1_0_0.js",
+ "OpenLayers/Format/SLD/v1_0_0_GeoServer.js",
+ "OpenLayers/Format/OWSCommon.js",
+ "OpenLayers/Format/OWSCommon/v1.js",
+ "OpenLayers/Format/OWSCommon/v1_0_0.js",
+ "OpenLayers/Format/OWSCommon/v1_1_0.js",
+ "OpenLayers/Format/CSWGetDomain.js",
+ "OpenLayers/Format/CSWGetDomain/v2_0_2.js",
+ "OpenLayers/Format/CSWGetRecords.js",
+ "OpenLayers/Format/CSWGetRecords/v2_0_2.js",
+ "OpenLayers/Format/WFST.js",
+ "OpenLayers/Format/WFST/v1.js",
+ "OpenLayers/Format/WFST/v1_0_0.js",
+ "OpenLayers/Format/WFST/v1_1_0.js",
+ "OpenLayers/Format/Text.js",
+ "OpenLayers/Format/JSON.js",
+ "OpenLayers/Format/GeoJSON.js",
+ "OpenLayers/Format/WMC.js",
+ "OpenLayers/Format/WMC/v1.js",
+ "OpenLayers/Format/WMC/v1_0_0.js",
+ "OpenLayers/Format/WMC/v1_1_0.js",
+ "OpenLayers/Format/WCSGetCoverage.js",
+ "OpenLayers/Format/WMSCapabilities.js",
+ "OpenLayers/Format/WMSCapabilities/v1.js",
+ "OpenLayers/Format/WMSCapabilities/v1_1.js",
+ "OpenLayers/Format/WMSCapabilities/v1_1_0.js",
+ "OpenLayers/Format/WMSCapabilities/v1_1_1.js",
+ "OpenLayers/Format/WMSCapabilities/v1_3.js",
+ "OpenLayers/Format/WMSCapabilities/v1_3_0.js",
+ "OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js",
+ "OpenLayers/Format/WMSGetFeatureInfo.js",
+ "OpenLayers/Format/SOSCapabilities.js",
+ "OpenLayers/Format/SOSCapabilities/v1_0_0.js",
+ "OpenLayers/Format/SOSGetFeatureOfInterest.js",
+ "OpenLayers/Format/SOSGetObservation.js",
+ "OpenLayers/Format/OWSContext.js",
+ "OpenLayers/Format/OWSContext/v0_3_1.js",
+ "OpenLayers/Format/WMTSCapabilities.js",
+ "OpenLayers/Format/WMTSCapabilities/v1_0_0.js",
+ "OpenLayers/Format/WPSCapabilities.js",
+ "OpenLayers/Format/WPSCapabilities/v1_0_0.js",
+ "OpenLayers/Format/WPSDescribeProcess.js",
+ "OpenLayers/Format/WPSExecute.js",
+ "OpenLayers/Format/XLS.js",
+ "OpenLayers/Format/XLS/v1.js",
+ "OpenLayers/Format/XLS/v1_1_0.js",
+ "OpenLayers/Format/OGCExceptionReport.js",
+ "OpenLayers/Control/GetFeature.js",
+ "OpenLayers/Control/NavToolbar.js",
+ "OpenLayers/Control/PanPanel.js",
+ "OpenLayers/Control/Pan.js",
+ "OpenLayers/Control/ZoomIn.js",
+ "OpenLayers/Control/ZoomOut.js",
+ "OpenLayers/Control/ZoomPanel.js",
+ "OpenLayers/Control/EditingToolbar.js",
+ "OpenLayers/Control/Geolocate.js",
+ "OpenLayers/Symbolizer.js",
+ "OpenLayers/Symbolizer/Point.js",
+ "OpenLayers/Symbolizer/Line.js",
+ "OpenLayers/Symbolizer/Polygon.js",
+ "OpenLayers/Symbolizer/Text.js",
+ "OpenLayers/Symbolizer/Raster.js",
+ "OpenLayers/Lang.js",
+ "OpenLayers/Lang/en.js",
+ "OpenLayers/Spherical.js",
+ "OpenLayers/TileManager.js",
+ "OpenLayers/WPSClient.js",
+ "OpenLayers/WPSProcess.js"
+ ]; // etc.
+ }
+
+ // use "parser-inserted scripts" for guaranteed execution order
+ // http://hsivonen.iki.fi/script-execution/
+ var scriptTags = new Array(jsFiles.length);
+ var host = OpenLayers._getScriptLocation() + "lib/";
+ for (var i=0, len=jsFiles.length; i<len; i++) {
+ scriptTags[i] = "<script src='" + host + jsFiles[i] +
+ "'></script>";
+ }
+ if (scriptTags.length > 0) {
+ document.write(scriptTags.join(""));
+ }
+ }
+})();
+
+/**
+ * Constant: VERSION_NUMBER
+ *
+ * This constant identifies the version of OpenLayers.
+ *
+ * When asking questions or reporting issues, make sure to include the output of
+ * OpenLayers.VERSION_NUMBER in the question or issue-description.
+ */
+OpenLayers.VERSION_NUMBER="Release 2.13.1";
diff --git a/misc/openlayers/lib/OpenLayers/Animation.js b/misc/openlayers/lib/OpenLayers/Animation.js
new file mode 100644
index 0000000..7b47a08
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Animation.js
@@ -0,0 +1,102 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ */
+
+/**
+ * Namespace: OpenLayers.Animation
+ * A collection of utility functions for executing methods that repaint a
+ * portion of the browser window. These methods take advantage of the
+ * browser's scheduled repaints where requestAnimationFrame is available.
+ */
+OpenLayers.Animation = (function(window) {
+
+ /**
+ * Property: isNative
+ * {Boolean} true if a native requestAnimationFrame function is available
+ */
+ var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
+ var isNative = !!(requestAnimationFrame);
+
+ /**
+ * Function: requestFrame
+ * Schedule a function to be called at the next available animation frame.
+ * Uses the native method where available. Where requestAnimationFrame is
+ * not available, setTimeout will be called with a 16ms delay.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ */
+ var requestFrame = (function() {
+ var request = window[requestAnimationFrame] ||
+ function(callback, element) {
+ window.setTimeout(callback, 16);
+ };
+ // bind to window to avoid illegal invocation of native function
+ return function(callback, element) {
+ request.apply(window, [callback, element]);
+ };
+ })();
+
+ // private variables for animation loops
+ var counter = 0;
+ var loops = {};
+
+ /**
+ * Function: start
+ * Executes a method with <requestFrame> in series for some
+ * duration.
+ *
+ * Parameters:
+ * callback - {Function} The function to be called at the next animation frame.
+ * duration - {Number} Optional duration for the loop. If not provided, the
+ * animation loop will execute indefinitely.
+ * element - {DOMElement} Optional element that visually bounds the animation.
+ *
+ * Returns:
+ * {Number} Identifier for the animation loop. Used to stop animations with
+ * <stop>.
+ */
+ function start(callback, duration, element) {
+ duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
+ var id = ++counter;
+ var start = +new Date;
+ loops[id] = function() {
+ if (loops[id] && +new Date - start <= duration) {
+ callback();
+ if (loops[id]) {
+ requestFrame(loops[id], element);
+ }
+ } else {
+ delete loops[id];
+ }
+ };
+ requestFrame(loops[id], element);
+ return id;
+ }
+
+ /**
+ * Function: stop
+ * Terminates an animation loop started with <start>.
+ *
+ * Parameters:
+ * id - {Number} Identifier returned from <start>.
+ */
+ function stop(id) {
+ delete loops[id];
+ }
+
+ return {
+ isNative: isNative,
+ requestFrame: requestFrame,
+ start: start,
+ stop: stop
+ };
+
+})(window);
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes.js b/misc/openlayers/lib/OpenLayers/BaseTypes.js
new file mode 100644
index 0000000..d416b8f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes.js
@@ -0,0 +1,463 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Header: OpenLayers Base Types
+ * OpenLayers custom string, number and function functions are described here.
+ */
+
+/**
+ * Namespace: OpenLayers.String
+ * Contains convenience functions for string manipulation.
+ */
+OpenLayers.String = {
+
+ /**
+ * APIFunction: startsWith
+ * Test whether a string starts with another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string starts with the second.
+ */
+ startsWith: function(str, sub) {
+ return (str.indexOf(sub) == 0);
+ },
+
+ /**
+ * APIFunction: contains
+ * Test whether a string contains another string.
+ *
+ * Parameters:
+ * str - {String} The string to test.
+ * sub - {String} The substring to look for.
+ *
+ * Returns:
+ * {Boolean} The first string contains the second.
+ */
+ contains: function(str, sub) {
+ return (str.indexOf(sub) != -1);
+ },
+
+ /**
+ * APIFunction: trim
+ * Removes leading and trailing whitespace characters from a string.
+ *
+ * Parameters:
+ * str - {String} The (potentially) space padded string. This string is not
+ * modified.
+ *
+ * Returns:
+ * {String} A trimmed version of the string with all leading and
+ * trailing spaces removed.
+ */
+ trim: function(str) {
+ return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+ },
+
+ /**
+ * APIFunction: camelize
+ * Camel-case a hyphenated string.
+ * Ex. "chicken-head" becomes "chickenHead", and
+ * "-chicken-head" becomes "ChickenHead".
+ *
+ * Parameters:
+ * str - {String} The string to be camelized. The original is not modified.
+ *
+ * Returns:
+ * {String} The string, camelized
+ */
+ camelize: function(str) {
+ var oStringList = str.split('-');
+ var camelizedString = oStringList[0];
+ for (var i=1, len=oStringList.length; i<len; i++) {
+ var s = oStringList[i];
+ camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+ }
+ return camelizedString;
+ },
+
+ /**
+ * APIFunction: format
+ * Given a string with tokens in the form ${token}, return a string
+ * with tokens replaced with properties from the given context
+ * object. Represent a literal "${" by doubling it, e.g. "${${".
+ *
+ * Parameters:
+ * template - {String} A string with tokens to be replaced. A template
+ * has the form "literal ${token}" where the token will be replaced
+ * by the value of context["token"].
+ * context - {Object} An optional object with properties corresponding
+ * to the tokens in the format string. If no context is sent, the
+ * window object will be used.
+ * args - {Array} Optional arguments to pass to any functions found in
+ * the context. If a context property is a function, the token
+ * will be replaced by the return from the function called with
+ * these arguments.
+ *
+ * Returns:
+ * {String} A string with tokens replaced from the context object.
+ */
+ format: function(template, context, args) {
+ if(!context) {
+ context = window;
+ }
+
+ // Example matching:
+ // str = ${foo.bar}
+ // match = foo.bar
+ var replacer = function(str, match) {
+ var replacement;
+
+ // Loop through all subs. Example: ${a.b.c}
+ // 0 -> replacement = context[a];
+ // 1 -> replacement = context[a][b];
+ // 2 -> replacement = context[a][b][c];
+ var subs = match.split(/\.+/);
+ for (var i=0; i< subs.length; i++) {
+ if (i == 0) {
+ replacement = context;
+ }
+ if (replacement === undefined) {
+ break;
+ }
+ replacement = replacement[subs[i]];
+ }
+
+ if(typeof replacement == "function") {
+ replacement = args ?
+ replacement.apply(null, args) :
+ replacement();
+ }
+
+ // If replacement is undefined, return the string 'undefined'.
+ // This is a workaround for a bugs in browsers not properly
+ // dealing with non-participating groups in regular expressions:
+ // http://blog.stevenlevithan.com/archives/npcg-javascript
+ if (typeof replacement == 'undefined') {
+ return 'undefined';
+ } else {
+ return replacement;
+ }
+ };
+
+ return template.replace(OpenLayers.String.tokenRegEx, replacer);
+ },
+
+ /**
+ * Property: tokenRegEx
+ * Used to find tokens in a string.
+ * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
+ */
+ tokenRegEx: /\$\{([\w.]+?)\}/g,
+
+ /**
+ * Property: numberRegEx
+ * Used to test strings as numbers.
+ */
+ numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+
+ /**
+ * APIFunction: isNumeric
+ * Determine whether a string contains only a numeric value.
+ *
+ * Examples:
+ * (code)
+ * OpenLayers.String.isNumeric("6.02e23") // true
+ * OpenLayers.String.isNumeric("12 dozen") // false
+ * OpenLayers.String.isNumeric("4") // true
+ * OpenLayers.String.isNumeric(" 4 ") // false
+ * (end)
+ *
+ * Returns:
+ * {Boolean} String contains only a number.
+ */
+ isNumeric: function(value) {
+ return OpenLayers.String.numberRegEx.test(value);
+ },
+
+ /**
+ * APIFunction: numericIf
+ * Converts a string that appears to be a numeric value into a number.
+ *
+ * Parameters:
+ * value - {String}
+ * trimWhitespace - {Boolean}
+ *
+ * Returns:
+ * {Number|String} a Number if the passed value is a number, a String
+ * otherwise.
+ */
+ numericIf: function(value, trimWhitespace) {
+ var originalValue = value;
+ if (trimWhitespace === true && value != null && value.replace) {
+ value = value.replace(/^\s*|\s*$/g, "");
+ }
+ return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
+ }
+
+};
+
+/**
+ * Namespace: OpenLayers.Number
+ * Contains convenience functions for manipulating numbers.
+ */
+OpenLayers.Number = {
+
+ /**
+ * Property: decimalSeparator
+ * Decimal separator to use when formatting numbers.
+ */
+ decimalSeparator: ".",
+
+ /**
+ * Property: thousandsSeparator
+ * Thousands separator to use when formatting numbers.
+ */
+ thousandsSeparator: ",",
+
+ /**
+ * APIFunction: limitSigDigs
+ * Limit the number of significant digits on a float.
+ *
+ * Parameters:
+ * num - {Float}
+ * sig - {Integer}
+ *
+ * Returns:
+ * {Float} The number, rounded to the specified number of significant
+ * digits.
+ */
+ limitSigDigs: function(num, sig) {
+ var fig = 0;
+ if (sig > 0) {
+ fig = parseFloat(num.toPrecision(sig));
+ }
+ return fig;
+ },
+
+ /**
+ * APIFunction: format
+ * Formats a number for output.
+ *
+ * Parameters:
+ * num - {Float}
+ * dec - {Integer} Number of decimal places to round to.
+ * Defaults to 0. Set to null to leave decimal places unchanged.
+ * tsep - {String} Thousands separator.
+ * Default is ",".
+ * dsep - {String} Decimal separator.
+ * Default is ".".
+ *
+ * Returns:
+ * {String} A string representing the formatted number.
+ */
+ format: function(num, dec, tsep, dsep) {
+ dec = (typeof dec != "undefined") ? dec : 0;
+ tsep = (typeof tsep != "undefined") ? tsep :
+ OpenLayers.Number.thousandsSeparator;
+ dsep = (typeof dsep != "undefined") ? dsep :
+ OpenLayers.Number.decimalSeparator;
+
+ if (dec != null) {
+ num = parseFloat(num.toFixed(dec));
+ }
+
+ var parts = num.toString().split(".");
+ if (parts.length == 1 && dec == null) {
+ // integer where we do not want to touch the decimals
+ dec = 0;
+ }
+
+ var integer = parts[0];
+ if (tsep) {
+ var thousands = /(-?[0-9]+)([0-9]{3})/;
+ while(thousands.test(integer)) {
+ integer = integer.replace(thousands, "$1" + tsep + "$2");
+ }
+ }
+
+ var str;
+ if (dec == 0) {
+ str = integer;
+ } else {
+ var rem = parts.length > 1 ? parts[1] : "0";
+ if (dec != null) {
+ rem = rem + new Array(dec - rem.length + 1).join("0");
+ }
+ str = integer + dsep + rem;
+ }
+ return str;
+ },
+
+ /**
+ * Method: zeroPad
+ * Create a zero padded string optionally with a radix for casting numbers.
+ *
+ * Parameters:
+ * num - {Number} The number to be zero padded.
+ * len - {Number} The length of the string to be returned.
+ * radix - {Number} An integer between 2 and 36 specifying the base to use
+ * for representing numeric values.
+ */
+ zeroPad: function(num, len, radix) {
+ var str = num.toString(radix || 10);
+ while (str.length < len) {
+ str = "0" + str;
+ }
+ return str;
+ }
+};
+
+/**
+ * Namespace: OpenLayers.Function
+ * Contains convenience functions for function manipulation.
+ */
+OpenLayers.Function = {
+ /**
+ * APIFunction: bind
+ * Bind a function to an object. Method to easily create closures with
+ * 'this' altered.
+ *
+ * Parameters:
+ * func - {Function} Input function.
+ * object - {Object} The object to bind to the input function (as this).
+ *
+ * Returns:
+ * {Function} A closure with 'this' set to the passed in object.
+ */
+ bind: function(func, object) {
+ // create a reference to all arguments past the second one
+ var args = Array.prototype.slice.apply(arguments, [2]);
+ return function() {
+ // Push on any additional arguments from the actual function call.
+ // These will come after those sent to the bind call.
+ var newArgs = args.concat(
+ Array.prototype.slice.apply(arguments, [0])
+ );
+ return func.apply(object, newArgs);
+ };
+ },
+
+ /**
+ * APIFunction: bindAsEventListener
+ * Bind a function to an object, and configure it to receive the event
+ * object as first parameter when called.
+ *
+ * Parameters:
+ * func - {Function} Input function to serve as an event listener.
+ * object - {Object} A reference to this.
+ *
+ * Returns:
+ * {Function}
+ */
+ bindAsEventListener: function(func, object) {
+ return function(event) {
+ return func.call(object, event || window.event);
+ };
+ },
+
+ /**
+ * APIFunction: False
+ * A simple function to that just does "return false". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.False;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ False : function() {
+ return false;
+ },
+
+ /**
+ * APIFunction: True
+ * A simple function to that just does "return true". We use this to
+ * avoid attaching anonymous functions to DOM event handlers, which
+ * causes "issues" on IE<8.
+ *
+ * Usage:
+ * document.onclick = OpenLayers.Function.True;
+ *
+ * Returns:
+ * {Boolean}
+ */
+ True : function() {
+ return true;
+ },
+
+ /**
+ * APIFunction: Void
+ * A reusable function that returns ``undefined``.
+ *
+ * Returns:
+ * {undefined}
+ */
+ Void: function() {}
+
+};
+
+/**
+ * Namespace: OpenLayers.Array
+ * Contains convenience functions for array manipulation.
+ */
+OpenLayers.Array = {
+
+ /**
+ * APIMethod: filter
+ * Filter an array. Provides the functionality of the
+ * Array.prototype.filter extension to the ECMA-262 standard. Where
+ * available, Array.prototype.filter will be used.
+ *
+ * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
+ *
+ * Parameters:
+ * array - {Array} The array to be filtered. This array is not mutated.
+ * Elements added to this array by the callback will not be visited.
+ * callback - {Function} A function that is called for each element in
+ * the array. If this function returns true, the element will be
+ * included in the return. The function will be called with three
+ * arguments: the element in the array, the index of that element, and
+ * the array itself. If the optional caller parameter is specified
+ * the callback will be called with this set to caller.
+ * caller - {Object} Optional object to be set as this when the callback
+ * is called.
+ *
+ * Returns:
+ * {Array} An array of elements from the passed in array for which the
+ * callback returns true.
+ */
+ filter: function(array, callback, caller) {
+ var selected = [];
+ if (Array.prototype.filter) {
+ selected = array.filter(callback, caller);
+ } else {
+ var len = array.length;
+ if (typeof callback != "function") {
+ throw new TypeError();
+ }
+ for(var i=0; i<len; i++) {
+ if (i in array) {
+ var val = array[i];
+ if (callback.call(caller, val, i, array)) {
+ selected.push(val);
+ }
+ }
+ }
+ }
+ return selected;
+ }
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/Bounds.js b/misc/openlayers/lib/OpenLayers/BaseTypes/Bounds.js
new file mode 100644
index 0000000..a828eb0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/Bounds.js
@@ -0,0 +1,837 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Bounds
+ * Instances of this class represent bounding boxes. Data stored as left,
+ * bottom, right, top floats. All values are initialized to null, however,
+ * you should make sure you set them before using the bounds for anything.
+ *
+ * Possible use case:
+ * (code)
+ * bounds = new OpenLayers.Bounds();
+ * bounds.extend(new OpenLayers.LonLat(4,5));
+ * bounds.extend(new OpenLayers.LonLat(5,6));
+ * bounds.toBBOX(); // returns 4,5,5,6
+ * (end)
+ */
+OpenLayers.Bounds = OpenLayers.Class({
+
+ /**
+ * Property: left
+ * {Number} Minimum horizontal coordinate.
+ */
+ left: null,
+
+ /**
+ * Property: bottom
+ * {Number} Minimum vertical coordinate.
+ */
+ bottom: null,
+
+ /**
+ * Property: right
+ * {Number} Maximum horizontal coordinate.
+ */
+ right: null,
+
+ /**
+ * Property: top
+ * {Number} Maximum vertical coordinate.
+ */
+ top: null,
+
+ /**
+ * Property: centerLonLat
+ * {<OpenLayers.LonLat>} A cached center location. This should not be
+ * accessed directly. Use <getCenterLonLat> instead.
+ */
+ centerLonLat: null,
+
+ /**
+ * Constructor: OpenLayers.Bounds
+ * Construct a new bounds object. Coordinates can either be passed as four
+ * arguments, or as a single argument.
+ *
+ * Parameters (four arguments):
+ * left - {Number} The left bounds of the box. Note that for width
+ * calculations, this is assumed to be less than the right value.
+ * bottom - {Number} The bottom bounds of the box. Note that for height
+ * calculations, this is assumed to be less than the top value.
+ * right - {Number} The right bounds.
+ * top - {Number} The top bounds.
+ *
+ * Parameters (single argument):
+ * bounds - {Array(Number)} [left, bottom, right, top]
+ */
+ initialize: function(left, bottom, right, top) {
+ if (OpenLayers.Util.isArray(left)) {
+ top = left[3];
+ right = left[2];
+ bottom = left[1];
+ left = left[0];
+ }
+ if (left != null) {
+ this.left = OpenLayers.Util.toFloat(left);
+ }
+ if (bottom != null) {
+ this.bottom = OpenLayers.Util.toFloat(bottom);
+ }
+ if (right != null) {
+ this.right = OpenLayers.Util.toFloat(right);
+ }
+ if (top != null) {
+ this.top = OpenLayers.Util.toFloat(top);
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a cloned instance of this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A fresh copy of the bounds
+ */
+ clone:function() {
+ return new OpenLayers.Bounds(this.left, this.bottom,
+ this.right, this.top);
+ },
+
+ /**
+ * Method: equals
+ * Test a two bounds for equivalence.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object has the same left,
+ * right, top, bottom components as this. Note that if bounds
+ * passed in is null, returns false.
+ */
+ equals:function(bounds) {
+ var equals = false;
+ if (bounds != null) {
+ equals = ((this.left == bounds.left) &&
+ (this.right == bounds.right) &&
+ (this.top == bounds.top) &&
+ (this.bottom == bounds.bottom));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: toString
+ * Returns a string representation of the bounds object.
+ *
+ * Returns:
+ * {String} String representation of bounds object.
+ */
+ toString:function() {
+ return [this.left, this.bottom, this.right, this.top].join(",");
+ },
+
+ /**
+ * APIMethod: toArray
+ * Returns an array representation of the bounds object.
+ *
+ * Returns an array of left, bottom, right, top properties, or -- when the
+ * optional parameter is true -- an array of the bottom, left, top,
+ * right properties.
+ *
+ * Parameters:
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {Array} array of left, bottom, right, top
+ */
+ toArray: function(reverseAxisOrder) {
+ if (reverseAxisOrder === true) {
+ return [this.bottom, this.left, this.top, this.right];
+ } else {
+ return [this.left, this.bottom, this.right, this.top];
+ }
+ },
+
+ /**
+ * APIMethod: toBBOX
+ * Returns a boundingbox-string representation of the bounds object.
+ *
+ * Parameters:
+ * decimal - {Integer} How many significant digits in the bbox coords?
+ * Default is 6
+ * reverseAxisOrder - {Boolean} Should we reverse the axis order?
+ *
+ * Returns:
+ * {String} Simple String representation of bounds object.
+ * (e.g. "5,42,10,45")
+ */
+ toBBOX:function(decimal, reverseAxisOrder) {
+ if (decimal== null) {
+ decimal = 6;
+ }
+ var mult = Math.pow(10, decimal);
+ var xmin = Math.round(this.left * mult) / mult;
+ var ymin = Math.round(this.bottom * mult) / mult;
+ var xmax = Math.round(this.right * mult) / mult;
+ var ymax = Math.round(this.top * mult) / mult;
+ if (reverseAxisOrder === true) {
+ return ymin + "," + xmin + "," + ymax + "," + xmax;
+ } else {
+ return xmin + "," + ymin + "," + xmax + "," + ymax;
+ }
+ },
+
+ /**
+ * APIMethod: toGeometry
+ * Create a new polygon geometry based on this bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
+ * of this bounds.
+ */
+ toGeometry: function() {
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(this.left, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.bottom),
+ new OpenLayers.Geometry.Point(this.right, this.top),
+ new OpenLayers.Geometry.Point(this.left, this.top)
+ ])
+ ]);
+ },
+
+ /**
+ * APIMethod: getWidth
+ * Returns the width of the bounds.
+ *
+ * Returns:
+ * {Float} The width of the bounds (right minus left).
+ */
+ getWidth:function() {
+ return (this.right - this.left);
+ },
+
+ /**
+ * APIMethod: getHeight
+ * Returns the height of the bounds.
+ *
+ * Returns:
+ * {Float} The height of the bounds (top minus bottom).
+ */
+ getHeight:function() {
+ return (this.top - this.bottom);
+ },
+
+ /**
+ * APIMethod: getSize
+ * Returns an <OpenLayers.Size> object of the bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size of the bounds.
+ */
+ getSize:function() {
+ return new OpenLayers.Size(this.getWidth(), this.getHeight());
+ },
+
+ /**
+ * APIMethod: getCenterPixel
+ * Returns the <OpenLayers.Pixel> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
+ */
+ getCenterPixel:function() {
+ return new OpenLayers.Pixel( (this.left + this.right) / 2,
+ (this.bottom + this.top) / 2);
+ },
+
+ /**
+ * APIMethod: getCenterLonLat
+ * Returns the <OpenLayers.LonLat> object which represents the center of the
+ * bounds.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The center of the bounds in map space.
+ */
+ getCenterLonLat:function() {
+ if(!this.centerLonLat) {
+ this.centerLonLat = new OpenLayers.LonLat(
+ (this.left + this.right) / 2, (this.bottom + this.top) / 2
+ );
+ }
+ return this.centerLonLat;
+ },
+
+ /**
+ * APIMethod: scale
+ * Scales the bounds around a pixel or lonlat. Note that the new
+ * bounds may return non-integer properties, even if a pixel
+ * is passed.
+ *
+ * Parameters:
+ * ratio - {Float}
+ * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+ * Default is center.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds that is scaled by ratio
+ * from origin.
+ */
+ scale: function(ratio, origin){
+ if(origin == null){
+ origin = this.getCenterLonLat();
+ }
+
+ var origx,origy;
+
+ // get origin coordinates
+ if(origin.CLASS_NAME == "OpenLayers.LonLat"){
+ origx = origin.lon;
+ origy = origin.lat;
+ } else {
+ origx = origin.x;
+ origy = origin.y;
+ }
+
+ var left = (this.left - origx) * ratio + origx;
+ var bottom = (this.bottom - origy) * ratio + origy;
+ var right = (this.right - origx) * ratio + origx;
+ var top = (this.top - origy) * ratio + origy;
+
+ return new OpenLayers.Bounds(left, bottom, right, top);
+ },
+
+ /**
+ * APIMethod: add
+ * Shifts the coordinates of the bound by the given horizontal and vertical
+ * deltas.
+ *
+ * (start code)
+ * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+ * bounds.toString();
+ * // => "0,0,10,10"
+ *
+ * bounds.add(-1.5, 4).toString();
+ * // => "-1.5,4,8.5,14"
+ * (end)
+ *
+ * This method will throw a TypeError if it is passed null as an argument.
+ *
+ * Parameters:
+ * x - {Float} horizontal delta
+ * y - {Float} vertical delta
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
+ * this, but shifted by the passed-in x and y values.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Bounds.add cannot receive null values');
+ }
+ return new OpenLayers.Bounds(this.left + x, this.bottom + y,
+ this.right + x, this.top + y);
+ },
+
+ /**
+ * APIMethod: extend
+ * Extend the bounds to include the <OpenLayers.LonLat>,
+ * <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
+ *
+ * Please note that this function assumes that left < right and
+ * bottom < top.
+ *
+ * Parameters:
+ * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
+ * <OpenLayers.Bounds>} The object to be included in the new bounds
+ * object.
+ */
+ extend:function(object) {
+ if (object) {
+ switch(object.CLASS_NAME) {
+ case "OpenLayers.LonLat":
+ this.extendXY(object.lon, object.lat);
+ break;
+ case "OpenLayers.Geometry.Point":
+ this.extendXY(object.x, object.y);
+ break;
+
+ case "OpenLayers.Bounds":
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ( (this.left == null) || (object.left < this.left)) {
+ this.left = object.left;
+ }
+ if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
+ this.bottom = object.bottom;
+ }
+ if ( (this.right == null) || (object.right > this.right) ) {
+ this.right = object.right;
+ }
+ if ( (this.top == null) || (object.top > this.top) ) {
+ this.top = object.top;
+ }
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: extendXY
+ * Extend the bounds to include the XY coordinate specified.
+ *
+ * Parameters:
+ * x - {number} The X part of the the coordinate.
+ * y - {number} The Y part of the the coordinate.
+ */
+ extendXY:function(x, y) {
+ // clear cached center location
+ this.centerLonLat = null;
+
+ if ((this.left == null) || (x < this.left)) {
+ this.left = x;
+ }
+ if ((this.bottom == null) || (y < this.bottom)) {
+ this.bottom = y;
+ }
+ if ((this.right == null) || (x > this.right)) {
+ this.right = x;
+ }
+ if ((this.top == null) || (y > this.top)) {
+ this.top = y;
+ }
+ },
+
+ /**
+ * APIMethod: containsLonLat
+ * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * options - {Object} Optional parameters
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Whether or not to include the border.
+ * Default is true.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
+ * ll will be considered as contained if it exceeds the world bounds,
+ * but can be wrapped around the dateline so it is contained by this
+ * bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in lonlat is within this bounds.
+ */
+ containsLonLat: function(ll, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ var contains = this.contains(ll.lon, ll.lat, options.inclusive),
+ worldBounds = options.worldBounds;
+ if (worldBounds && !contains) {
+ var worldWidth = worldBounds.getWidth();
+ var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
+ var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
+ contains = this.containsLonLat({
+ lon: ll.lon - worldsAway * worldWidth,
+ lat: ll.lat
+ }, {inclusive: options.inclusive});
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: containsPixel
+ * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in pixel is within this bounds.
+ */
+ containsPixel:function(px, inclusive) {
+ return this.contains(px.x, px.y, inclusive);
+ },
+
+ /**
+ * APIMethod: contains
+ * Returns whether the bounds object contains the given x and y.
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ * inclusive - {Boolean} Whether or not to include the border. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the passed-in coordinates are within this
+ * bounds.
+ */
+ contains:function(x, y, inclusive) {
+ //set default
+ if (inclusive == null) {
+ inclusive = true;
+ }
+
+ if (x == null || y == null) {
+ return false;
+ }
+
+ x = OpenLayers.Util.toFloat(x);
+ y = OpenLayers.Util.toFloat(y);
+
+ var contains = false;
+ if (inclusive) {
+ contains = ((x >= this.left) && (x <= this.right) &&
+ (y >= this.bottom) && (y <= this.top));
+ } else {
+ contains = ((x > this.left) && (x < this.right) &&
+ (y > this.bottom) && (y < this.top));
+ }
+ return contains;
+ },
+
+ /**
+ * APIMethod: intersectsBounds
+ * Determine whether the target bounds intersects this bounds. Bounds are
+ * considered intersecting if any of their edges intersect or if one
+ * bounds contains the other.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * options - {Object} Optional parameters.
+ *
+ * Acceptable options:
+ * inclusive - {Boolean} Treat coincident borders as intersecting. Default
+ * is true. If false, bounds that do not overlap but only touch at the
+ * border will not be considered as intersecting.
+ * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
+ * bounds will be considered as intersecting if they intersect when
+ * shifted to within the world bounds. This applies only to bounds that
+ * cross or are completely outside the world bounds.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object intersects this bounds.
+ */
+ intersectsBounds:function(bounds, options) {
+ if (typeof options === "boolean") {
+ options = {inclusive: options};
+ }
+ options = options || {};
+ if (options.worldBounds) {
+ var self = this.wrapDateLine(options.worldBounds);
+ bounds = bounds.wrapDateLine(options.worldBounds);
+ } else {
+ self = this;
+ }
+ if (options.inclusive == null) {
+ options.inclusive = true;
+ }
+ var intersects = false;
+ var mightTouch = (
+ self.left == bounds.right ||
+ self.right == bounds.left ||
+ self.top == bounds.bottom ||
+ self.bottom == bounds.top
+ );
+
+ // if the two bounds only touch at an edge, and inclusive is false,
+ // then the bounds don't *really* intersect.
+ if (options.inclusive || !mightTouch) {
+ // otherwise, if one of the boundaries even partially contains another,
+ // inclusive of the edges, then they do intersect.
+ var inBottom = (
+ ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
+ ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
+ );
+ var inTop = (
+ ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
+ ((self.top > bounds.bottom) && (self.top < bounds.top))
+ );
+ var inLeft = (
+ ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
+ ((self.left >= bounds.left) && (self.left <= bounds.right))
+ );
+ var inRight = (
+ ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
+ ((self.right >= bounds.left) && (self.right <= bounds.right))
+ );
+ intersects = ((inBottom || inTop) && (inLeft || inRight));
+ }
+ // document me
+ if (options.worldBounds && !intersects) {
+ var world = options.worldBounds;
+ var width = world.getWidth();
+ var selfCrosses = !world.containsBounds(self);
+ var boundsCrosses = !world.containsBounds(bounds);
+ if (selfCrosses && !boundsCrosses) {
+ bounds = bounds.add(-width, 0);
+ intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
+ } else if (boundsCrosses && !selfCrosses) {
+ self = self.add(-width, 0);
+ intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});
+ }
+ }
+ return intersects;
+ },
+
+ /**
+ * APIMethod: containsBounds
+ * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
+ *
+ * bounds - {<OpenLayers.Bounds>} The target bounds.
+ * partial - {Boolean} If any of the target corners is within this bounds
+ * consider the bounds contained. Default is false. If false, the
+ * entire target bounds must be contained within this bounds.
+ * inclusive - {Boolean} Treat shared edges as contained. Default is
+ * true.
+ *
+ * Returns:
+ * {Boolean} The passed-in bounds object is contained within this bounds.
+ */
+ containsBounds:function(bounds, partial, inclusive) {
+ if (partial == null) {
+ partial = false;
+ }
+ if (inclusive == null) {
+ inclusive = true;
+ }
+ var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
+ var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
+ var topLeft = this.contains(bounds.left, bounds.top, inclusive);
+ var topRight = this.contains(bounds.right, bounds.top, inclusive);
+
+ return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
+ : (bottomLeft && bottomRight && topLeft && topRight);
+ },
+
+ /**
+ * APIMethod: determineQuadrant
+ * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
+ * <OpenLayers.LonLat> lies.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
+ * coordinate lies.
+ */
+ determineQuadrant: function(lonlat) {
+
+ var quadrant = "";
+ var center = this.getCenterLonLat();
+
+ quadrant += (lonlat.lat < center.lat) ? "b" : "t";
+ quadrant += (lonlat.lon < center.lon) ? "l" : "r";
+
+ return quadrant;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the Bounds object from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ // clear cached center location
+ this.centerLonLat = null;
+ var ll = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.bottom}, source, dest);
+ var lr = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.bottom}, source, dest);
+ var ul = OpenLayers.Projection.transform(
+ {'x': this.left, 'y': this.top}, source, dest);
+ var ur = OpenLayers.Projection.transform(
+ {'x': this.right, 'y': this.top}, source, dest);
+ this.left = Math.min(ll.x, ul.x);
+ this.bottom = Math.min(ll.y, lr.y);
+ this.right = Math.max(lr.x, ur.x);
+ this.top = Math.max(ul.y, ur.y);
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ * Wraps the bounds object around the dateline.
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ * options - {Object} Some possible options are:
+ *
+ * Allowed Options:
+ * leftTolerance - {float} Allow for a margin of error
+ * with the 'left' value of this
+ * bound.
+ * Default is 0.
+ * rightTolerance - {float} Allow for a margin of error
+ * with the 'right' value of
+ * this bound.
+ * Default is 0.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent). Note that this function only returns
+ * a different bounds value if this bounds is
+ * *entirely* outside of the maxExtent. If this
+ * bounds straddles the dateline (is part in/part
+ * out of maxExtent), the returned bounds will always
+ * cross the left edge of the given maxExtent.
+ *.
+ */
+ wrapDateLine: function(maxExtent, options) {
+ options = options || {};
+
+ var leftTolerance = options.leftTolerance || 0;
+ var rightTolerance = options.rightTolerance || 0;
+
+ var newBounds = this.clone();
+
+ if (maxExtent) {
+ var width = maxExtent.getWidth();
+
+ //shift right?
+ while (newBounds.left < maxExtent.left &&
+ newBounds.right - rightTolerance <= maxExtent.left ) {
+ newBounds = newBounds.add(width, 0);
+ }
+
+ //shift left?
+ while (newBounds.left + leftTolerance >= maxExtent.right &&
+ newBounds.right > maxExtent.right ) {
+ newBounds = newBounds.add(-width, 0);
+ }
+
+ // crosses right only? force left
+ var newLeft = newBounds.left + leftTolerance;
+ if (newLeft < maxExtent.right && newLeft > maxExtent.left &&
+ newBounds.right - rightTolerance > maxExtent.right) {
+ newBounds = newBounds.add(-width, 0);
+ }
+ }
+
+ return newBounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Bounds"
+});
+
+/**
+ * APIFunction: fromString
+ * Alternative constructor that builds a new OpenLayers.Bounds from a
+ * parameter string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromString("5,42,10,45");
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
+ * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the
+ * passed-in String.
+ */
+OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
+ var bounds = str.split(",");
+ return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
+};
+
+/**
+ * APIFunction: fromArray
+ * Alternative constructor that builds a new OpenLayers.Bounds from an array.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
+ * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
+ */
+OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
+ return reverseAxisOrder === true ?
+ new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
+ new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
+};
+
+/**
+ * APIFunction: fromSize
+ * Alternative constructor that builds a new OpenLayers.Bounds from a size.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(0, 20, 10, 0);
+ * (end)
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
+ * both 'w' and 'h' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
+ */
+OpenLayers.Bounds.fromSize = function(size) {
+ return new OpenLayers.Bounds(0,
+ size.h,
+ size.w,
+ 0);
+};
+
+/**
+ * Function: oppositeQuadrant
+ * Get the opposite quadrant for a given quadrant string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.oppositeQuadrant( "tl" );
+ * // => "br"
+ *
+ * OpenLayers.Bounds.oppositeQuadrant( "tr" );
+ * // => "bl"
+ * (end)
+ *
+ * Parameters:
+ * quadrant - {String} two character quadrant shortstring
+ *
+ * Returns:
+ * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if
+ * you pass in "bl" it returns "tr", if you pass in "br" it
+ * returns "tl", etc.
+ */
+OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
+ var opp = "";
+
+ opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
+ opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
+
+ return opp;
+};
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/Class.js b/misc/openlayers/lib/OpenLayers/BaseTypes/Class.js
new file mode 100644
index 0000000..2be7212
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/Class.js
@@ -0,0 +1,121 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Constructor: OpenLayers.Class
+ * Base class used to construct all other classes. Includes support for
+ * multiple inheritance.
+ *
+ * This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old
+ * syntax for creating classes and dealing with inheritance
+ * will be removed.
+ *
+ * To create a new OpenLayers-style class, use the following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(prototype);
+ * (end)
+ *
+ * To create a new OpenLayers-style class with multiple inheritance, use the
+ * following syntax:
+ * (code)
+ * var MyClass = OpenLayers.Class(Class1, Class2, prototype);
+ * (end)
+ *
+ * Note that instanceof reflection will only reveal Class1 as superclass.
+ *
+ */
+OpenLayers.Class = function() {
+ var len = arguments.length;
+ var P = arguments[0];
+ var F = arguments[len-1];
+
+ var C = typeof F.initialize == "function" ?
+ F.initialize :
+ function(){ P.prototype.initialize.apply(this, arguments); };
+
+ if (len > 1) {
+ var newArgs = [C, P].concat(
+ Array.prototype.slice.call(arguments).slice(1, len-1), F);
+ OpenLayers.inherit.apply(null, newArgs);
+ } else {
+ C.prototype = F;
+ }
+ return C;
+};
+
+/**
+ * Function: OpenLayers.inherit
+ *
+ * Parameters:
+ * C - {Object} the class that inherits
+ * P - {Object} the superclass to inherit from
+ *
+ * In addition to the mandatory C and P parameters, an arbitrary number of
+ * objects can be passed, which will extend C.
+ */
+OpenLayers.inherit = function(C, P) {
+ var F = function() {};
+ F.prototype = P.prototype;
+ C.prototype = new F;
+ var i, l, o;
+ for(i=2, l=arguments.length; i<l; i++) {
+ o = arguments[i];
+ if(typeof o === "function") {
+ o = o.prototype;
+ }
+ OpenLayers.Util.extend(C.prototype, o);
+ }
+};
+
+/**
+ * APIFunction: extend
+ * Copy all properties of a source object to a destination object. Modifies
+ * the passed in destination object. Any properties on the source object
+ * that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+OpenLayers.Util.extend = function(destination, source) {
+ destination = destination || {};
+ if (source) {
+ for (var property in source) {
+ var value = source[property];
+ if (value !== undefined) {
+ destination[property] = value;
+ }
+ }
+
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object
+ * is an instance of window.Event.
+ */
+
+ var sourceIsEvt = typeof window.Event == "function"
+ && source instanceof window.Event;
+
+ if (!sourceIsEvt
+ && source.hasOwnProperty && source.hasOwnProperty("toString")) {
+ destination.toString = source.toString;
+ }
+ }
+ return destination;
+};
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/Date.js b/misc/openlayers/lib/OpenLayers/BaseTypes/Date.js
new file mode 100644
index 0000000..2646f24
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/Date.js
@@ -0,0 +1,123 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Namespace: OpenLayers.Date
+ * Contains implementations of Date.parse and date.toISOString that match the
+ * ECMAScript 5 specification for parsing RFC 3339 dates.
+ * http://tools.ietf.org/html/rfc3339
+ */
+OpenLayers.Date = {
+
+ /**
+ * APIProperty: dateRegEx
+ * The regex to be used for validating dates. You can provide your own
+ * regex for instance for adding support for years before BC. Default
+ * value is: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/
+ */
+ dateRegEx: /^(?:(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/,
+
+ /**
+ * APIMethod: toISOString
+ * Generates a string representing a date. The format of the string follows
+ * the profile of ISO 8601 for date and time on the Internet (see
+ * http://tools.ietf.org/html/rfc3339). If the toISOString method is
+ * available on the Date prototype, that is used. The toISOString
+ * method for Date instances is defined in ECMA-262.
+ *
+ * Parameters:
+ * date - {Date} A date object.
+ *
+ * Returns:
+ * {String} A string representing the date (e.g.
+ * "2010-08-07T16:58:23.123Z"). If the date does not have a valid time
+ * (i.e. isNaN(date.getTime())) this method returns the string "Invalid
+ * Date". The ECMA standard says the toISOString method should throw
+ * RangeError in this case, but Firefox returns a string instead. For
+ * best results, use isNaN(date.getTime()) to determine date validity
+ * before generating date strings.
+ */
+ toISOString: (function() {
+ if ("toISOString" in Date.prototype) {
+ return function(date) {
+ return date.toISOString();
+ };
+ } else {
+ return function(date) {
+ var str;
+ if (isNaN(date.getTime())) {
+ // ECMA-262 says throw RangeError, Firefox returns
+ // "Invalid Date"
+ str = "Invalid Date";
+ } else {
+ str =
+ date.getUTCFullYear() + "-" +
+ OpenLayers.Number.zeroPad(date.getUTCMonth() + 1, 2) + "-" +
+ OpenLayers.Number.zeroPad(date.getUTCDate(), 2) + "T" +
+ OpenLayers.Number.zeroPad(date.getUTCHours(), 2) + ":" +
+ OpenLayers.Number.zeroPad(date.getUTCMinutes(), 2) + ":" +
+ OpenLayers.Number.zeroPad(date.getUTCSeconds(), 2) + "." +
+ OpenLayers.Number.zeroPad(date.getUTCMilliseconds(), 3) + "Z";
+ }
+ return str;
+ };
+ }
+
+ })(),
+
+ /**
+ * APIMethod: parse
+ * Generate a date object from a string. The format for the string follows
+ * the profile of ISO 8601 for date and time on the Internet (see
+ * http://tools.ietf.org/html/rfc3339). We don't call the native
+ * Date.parse because of inconsistency between implmentations. In
+ * Chrome, calling Date.parse with a string that doesn't contain any
+ * indication of the timezone (e.g. "2011"), the date is interpreted
+ * in local time. On Firefox, the assumption is UTC.
+ *
+ * Parameters:
+ * str - {String} A string representing the date (e.g.
+ * "2010", "2010-08", "2010-08-07", "2010-08-07T16:58:23.123Z",
+ * "2010-08-07T11:58:23.123-06").
+ *
+ * Returns:
+ * {Date} A date object. If the string could not be parsed, an invalid
+ * date is returned (i.e. isNaN(date.getTime())).
+ */
+ parse: function(str) {
+ var date;
+ var match = str.match(this.dateRegEx);
+ if (match && (match[1] || match[7])) { // must have at least year or time
+ var year = parseInt(match[1], 10) || 0;
+ var month = (parseInt(match[2], 10) - 1) || 0;
+ var day = parseInt(match[3], 10) || 1;
+ date = new Date(Date.UTC(year, month, day));
+ // optional time
+ var type = match[7];
+ if (type) {
+ var hours = parseInt(match[4], 10);
+ var minutes = parseInt(match[5], 10);
+ var secFrac = parseFloat(match[6]);
+ var seconds = secFrac | 0;
+ var milliseconds = Math.round(1000 * (secFrac - seconds));
+ date.setUTCHours(hours, minutes, seconds, milliseconds);
+ // check offset
+ if (type !== "Z") {
+ var hoursOffset = parseInt(type, 10);
+ var minutesOffset = parseInt(match[8], 10) || 0;
+ var offset = -1000 * (60 * (hoursOffset * 60) + minutesOffset * 60);
+ date = new Date(date.getTime() + offset);
+ }
+ }
+ } else {
+ date = new Date("invalid");
+ }
+ return date;
+ }
+};
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/Element.js b/misc/openlayers/lib/OpenLayers/BaseTypes/Element.js
new file mode 100644
index 0000000..dc71b5c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/Element.js
@@ -0,0 +1,189 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ */
+
+/**
+ * Namespace: OpenLayers.Element
+ */
+OpenLayers.Element = {
+
+ /**
+ * APIFunction: visible
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Boolean} Is the element visible?
+ */
+ visible: function(element) {
+ return OpenLayers.Util.getElement(element).style.display != 'none';
+ },
+
+ /**
+ * APIFunction: toggle
+ * Toggle the visibility of element(s) passed in
+ *
+ * Parameters:
+ * element - {DOMElement} Actually user can pass any number of elements
+ */
+ toggle: function() {
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ var display = OpenLayers.Element.visible(element) ? 'none'
+ : '';
+ element.style.display = display;
+ }
+ },
+
+ /**
+ * APIFunction: remove
+ * Remove the specified element from the DOM.
+ *
+ * Parameters:
+ * element - {DOMElement}
+ */
+ remove: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ element.parentNode.removeChild(element);
+ },
+
+ /**
+ * APIFunction: getHeight
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Integer} The offset height of the element passed in
+ */
+ getHeight: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ return element.offsetHeight;
+ },
+
+ /**
+ * Function: hasClass
+ * Tests if an element has the given CSS class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to search for.
+ *
+ * Returns:
+ * {Boolean} The element has the given class name.
+ */
+ hasClass: function(element, name) {
+ var names = element.className;
+ return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
+ },
+
+ /**
+ * Function: addClass
+ * Add a CSS class name to an element. Safe where element already has
+ * the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to add.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ addClass: function(element, name) {
+ if(!OpenLayers.Element.hasClass(element, name)) {
+ element.className += (element.className ? " " : "") + name;
+ }
+ return element;
+ },
+
+ /**
+ * Function: removeClass
+ * Remove a CSS class name from an element. Safe where element does not
+ * have the class name.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to remove.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ removeClass: function(element, name) {
+ var names = element.className;
+ if(names) {
+ element.className = OpenLayers.String.trim(
+ names.replace(
+ new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
+ )
+ );
+ }
+ return element;
+ },
+
+ /**
+ * Function: toggleClass
+ * Remove a CSS class name from an element if it exists. Add the class name
+ * if it doesn't exist.
+ *
+ * Parameters:
+ * element - {DOMElement} A DOM element node.
+ * name - {String} The CSS class name to toggle.
+ *
+ * Returns:
+ * {DOMElement} The element.
+ */
+ toggleClass: function(element, name) {
+ if(OpenLayers.Element.hasClass(element, name)) {
+ OpenLayers.Element.removeClass(element, name);
+ } else {
+ OpenLayers.Element.addClass(element, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIFunction: getStyle
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * style - {?}
+ *
+ * Returns:
+ * {?}
+ */
+ getStyle: function(element, style) {
+ element = OpenLayers.Util.getElement(element);
+
+ var value = null;
+ if (element && element.style) {
+ value = element.style[OpenLayers.String.camelize(style)];
+ if (!value) {
+ if (document.defaultView &&
+ document.defaultView.getComputedStyle) {
+
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = css ? css.getPropertyValue(style) : null;
+ } else if (element.currentStyle) {
+ value = element.currentStyle[OpenLayers.String.camelize(style)];
+ }
+ }
+
+ var positions = ['left', 'top', 'right', 'bottom'];
+ if (window.opera &&
+ (OpenLayers.Util.indexOf(positions,style) != -1) &&
+ (OpenLayers.Element.getStyle(element, 'position') == 'static')) {
+ value = 'auto';
+ }
+ }
+
+ return value == 'auto' ? null : value;
+ }
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/LonLat.js b/misc/openlayers/lib/OpenLayers/BaseTypes/LonLat.js
new file mode 100644
index 0000000..0780dc3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/LonLat.js
@@ -0,0 +1,215 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.LonLat
+ * This class represents a longitude and latitude pair
+ */
+OpenLayers.LonLat = OpenLayers.Class({
+
+ /**
+ * APIProperty: lon
+ * {Float} The x-axis coodinate in map units
+ */
+ lon: 0.0,
+
+ /**
+ * APIProperty: lat
+ * {Float} The y-axis coordinate in map units
+ */
+ lat: 0.0,
+
+ /**
+ * Constructor: OpenLayers.LonLat
+ * Create a new map location. Coordinates can be passed either as two
+ * arguments, or as a single argument.
+ *
+ * Parameters (two arguments):
+ * lon - {Number} The x-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Longitude. Otherwise,
+ * it will be the x coordinate of the map location in your map units.
+ * lat - {Number} The y-axis coordinate in map units. If your map is in
+ * a geographic projection, this will be the Latitude. Otherwise,
+ * it will be the y coordinate of the map location in your map units.
+ *
+ * Parameters (single argument):
+ * location - {Array(Float)} [lon, lat]
+ */
+ initialize: function(lon, lat) {
+ if (OpenLayers.Util.isArray(lon)) {
+ lat = lon[1];
+ lon = lon[0];
+ }
+ this.lon = OpenLayers.Util.toFloat(lon);
+ this.lat = OpenLayers.Util.toFloat(lat);
+ },
+
+ /**
+ * Method: toString
+ * Return a readable string version of the lonlat
+ *
+ * Returns:
+ * {String} String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"lon=5,lat=42"</i>)
+ */
+ toString:function() {
+ return ("lon=" + this.lon + ",lat=" + this.lat);
+ },
+
+ /**
+ * APIMethod: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of OpenLayers.LonLat object.
+ * (e.g. <i>"5, 42"</i>)
+ */
+ toShortString:function() {
+ return (this.lon + ", " + this.lat);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon
+ * and lat values
+ */
+ clone:function() {
+ return new OpenLayers.LonLat(this.lon, this.lat);
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and
+ * lat passed-in added to this's.
+ */
+ add:function(lon, lat) {
+ if ( (lon == null) || (lat == null) ) {
+ throw new TypeError('LonLat.add cannot receive null values');
+ }
+ return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon),
+ this.lat + OpenLayers.Util.toFloat(lat));
+ },
+
+ /**
+ * APIMethod: equals
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Boolean value indicating whether the passed-in
+ * <OpenLayers.LonLat> object has the same lon and lat
+ * components as this.
+ * Note: if ll passed in is null, returns false
+ */
+ equals:function(ll) {
+ var equals = false;
+ if (ll != null) {
+ equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
+ (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: transform
+ * Transform the LonLat object from source to dest. This transformation is
+ * *in place*: if you want a *new* lonlat, use .clone() first.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>} Source projection.
+ * dest - {<OpenLayers.Projection>} Destination projection.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
+ */
+ transform: function(source, dest) {
+ var point = OpenLayers.Projection.transform(
+ {'x': this.lon, 'y': this.lat}, source, dest);
+ this.lon = point.x;
+ this.lat = point.y;
+ return this;
+ },
+
+ /**
+ * APIMethod: wrapDateLine
+ *
+ * Parameters:
+ * maxExtent - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the
+ * "dateline" (as specified by the borders of
+ * maxExtent)
+ */
+ wrapDateLine: function(maxExtent) {
+
+ var newLonLat = this.clone();
+
+ if (maxExtent) {
+ //shift right?
+ while (newLonLat.lon < maxExtent.left) {
+ newLonLat.lon += maxExtent.getWidth();
+ }
+
+ //shift left?
+ while (newLonLat.lon > maxExtent.right) {
+ newLonLat.lon -= maxExtent.getWidth();
+ }
+ }
+
+ return newLonLat;
+ },
+
+ CLASS_NAME: "OpenLayers.LonLat"
+});
+
+/**
+ * Function: fromString
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from a
+ * parameter string
+ *
+ * Parameters:
+ * str - {String} Comma-separated Lon,Lat coordinate string.
+ * (e.g. <i>"5,40"</i>)
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in String.
+ */
+OpenLayers.LonLat.fromString = function(str) {
+ var pair = str.split(",");
+ return new OpenLayers.LonLat(pair[0], pair[1]);
+};
+
+/**
+ * Function: fromArray
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from an
+ * array of two numbers that represent lon- and lat-values.
+ *
+ * Parameters:
+ * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the
+ * passed-in array.
+ */
+OpenLayers.LonLat.fromArray = function(arr) {
+ var gotArr = OpenLayers.Util.isArray(arr),
+ lon = gotArr && arr[0],
+ lat = gotArr && arr[1];
+ return new OpenLayers.LonLat(lon, lat);
+};
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/Pixel.js b/misc/openlayers/lib/OpenLayers/BaseTypes/Pixel.js
new file mode 100644
index 0000000..d6ac60a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/Pixel.js
@@ -0,0 +1,143 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Pixel
+ * This class represents a screen coordinate, in x and y coordinates
+ */
+OpenLayers.Pixel = OpenLayers.Class({
+
+ /**
+ * APIProperty: x
+ * {Number} The x coordinate
+ */
+ x: 0.0,
+
+ /**
+ * APIProperty: y
+ * {Number} The y coordinate
+ */
+ y: 0.0,
+
+ /**
+ * Constructor: OpenLayers.Pixel
+ * Create a new OpenLayers.Pixel instance
+ *
+ * Parameters:
+ * x - {Number} The x coordinate
+ * y - {Number} The y coordinate
+ *
+ * Returns:
+ * An instance of OpenLayers.Pixel
+ */
+ initialize: function(x, y) {
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * Method: toString
+ * Cast this object into a string
+ *
+ * Returns:
+ * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
+ */
+ toString:function() {
+ return ("x=" + this.x + ",y=" + this.y);
+ },
+
+ /**
+ * APIMethod: clone
+ * Return a clone of this pixel object
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A clone pixel
+ */
+ clone:function() {
+ return new OpenLayers.Pixel(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether one pixel is equivalent to another
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {Boolean} The point passed in as parameter is equal to this. Note that
+ * if px passed in is null, returns false.
+ */
+ equals:function(px) {
+ var equals = false;
+ if (px != null) {
+ equals = ((this.x == px.x && this.y == px.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Returns the distance to the pixel point passed in as a parameter.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Float} The pixel point passed in as parameter to calculate the
+ * distance to.
+ */
+ distanceTo:function(px) {
+ return Math.sqrt(
+ Math.pow(this.x - px.x, 2) +
+ Math.pow(this.y - px.y, 2)
+ );
+ },
+
+ /**
+ * APIMethod: add
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * values passed in.
+ */
+ add:function(x, y) {
+ if ( (x == null) || (y == null) ) {
+ throw new TypeError('Pixel.add cannot receive null values');
+ }
+ return new OpenLayers.Pixel(this.x + x, this.y + y);
+ },
+
+ /**
+ * APIMethod: offset
+ *
+ * Parameters
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the
+ * x&y values of the pixel passed in.
+ */
+ offset:function(px) {
+ var newPx = this.clone();
+ if (px) {
+ newPx = this.add(px.x, px.y);
+ }
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Pixel"
+});
diff --git a/misc/openlayers/lib/OpenLayers/BaseTypes/Size.js b/misc/openlayers/lib/OpenLayers/BaseTypes/Size.js
new file mode 100644
index 0000000..34c7a6c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/BaseTypes/Size.js
@@ -0,0 +1,89 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Size
+ * Instances of this class represent a width/height pair
+ */
+OpenLayers.Size = OpenLayers.Class({
+
+ /**
+ * APIProperty: w
+ * {Number} width
+ */
+ w: 0.0,
+
+ /**
+ * APIProperty: h
+ * {Number} height
+ */
+ h: 0.0,
+
+
+ /**
+ * Constructor: OpenLayers.Size
+ * Create an instance of OpenLayers.Size
+ *
+ * Parameters:
+ * w - {Number} width
+ * h - {Number} height
+ */
+ initialize: function(w, h) {
+ this.w = parseFloat(w);
+ this.h = parseFloat(h);
+ },
+
+ /**
+ * Method: toString
+ * Return the string representation of a size object
+ *
+ * Returns:
+ * {String} The string representation of OpenLayers.Size object.
+ * (e.g. <i>"w=55,h=66"</i>)
+ */
+ toString:function() {
+ return ("w=" + this.w + ",h=" + this.h);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this size object
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
+ * values
+ */
+ clone:function() {
+ return new OpenLayers.Size(this.w, this.h);
+ },
+
+ /**
+ *
+ * APIMethod: equals
+ * Determine where this size is equal to another
+ *
+ * Parameters:
+ * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ *
+ * Returns:
+ * {Boolean} The passed in size has the same h and w properties as this one.
+ * Note that if sz passed in is null, returns false.
+ */
+ equals:function(sz) {
+ var equals = false;
+ if (sz != null) {
+ equals = ((this.w == sz.w && this.h == sz.h) ||
+ (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
+ }
+ return equals;
+ },
+
+ CLASS_NAME: "OpenLayers.Size"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Console.js b/misc/openlayers/lib/OpenLayers/Console.js
new file mode 100644
index 0000000..ef5029a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Console.js
@@ -0,0 +1,250 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Namespace: OpenLayers.Console
+ * The OpenLayers.Console namespace is used for debugging and error logging.
+ * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
+ * calls to OpenLayers.Console methods will get redirected to window.console.
+ * This makes use of the Firebug extension where available and allows for
+ * cross-browser debugging Firebug style.
+ *
+ * Note:
+ * Note that behavior will differ with the Firebug extention and Firebug Lite.
+ * Most notably, the Firebug Lite console does not currently allow for
+ * hyperlinks to code or for clicking on object to explore their properties.
+ *
+ */
+OpenLayers.Console = {
+ /**
+ * Create empty functions for all console methods. The real value of these
+ * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
+ * included. We explicitly require the Firebug Lite script to trigger
+ * functionality of the OpenLayers.Console methods.
+ */
+
+ /**
+ * APIFunction: log
+ * Log an object in the console. The Firebug Lite console logs string
+ * representation of objects. Given multiple arguments, they will
+ * be cast to strings and logged with a space delimiter. If the first
+ * argument is a string with printf-like formatting, subsequent arguments
+ * will be used in string substitution. Any additional arguments (beyond
+ * the number substituted in a format string) will be appended in a space-
+ * delimited line.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ log: function() {},
+
+ /**
+ * APIFunction: debug
+ * Writes a message to the console, including a hyperlink to the line
+ * where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ debug: function() {},
+
+ /**
+ * APIFunction: info
+ * Writes a message to the console with the visual "info" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ info: function() {},
+
+ /**
+ * APIFunction: warn
+ * Writes a message to the console with the visual "warning" icon and
+ * color coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ warn: function() {},
+
+ /**
+ * APIFunction: error
+ * Writes a message to the console with the visual "error" icon and color
+ * coding and a hyperlink to the line where it was called.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ error: function() {},
+
+ /**
+ * APIFunction: userError
+ * A single interface for showing error messages to the user. The default
+ * behavior is a Javascript alert, though this can be overridden by
+ * reassigning OpenLayers.Console.userError to a different function.
+ *
+ * Expects a single error message
+ *
+ * Parameters:
+ * error - {Object}
+ */
+ userError: function(error) {
+ alert(error);
+ },
+
+ /**
+ * APIFunction: assert
+ * Tests that an expression is true. If not, it will write a message to
+ * the console and throw an exception.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ assert: function() {},
+
+ /**
+ * APIFunction: dir
+ * Prints an interactive listing of all properties of the object. This
+ * looks identical to the view that you would see in the DOM tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dir: function() {},
+
+ /**
+ * APIFunction: dirxml
+ * Prints the XML source tree of an HTML or XML element. This looks
+ * identical to the view that you would see in the HTML tab. You can click
+ * on any node to inspect it in the HTML tab.
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ dirxml: function() {},
+
+ /**
+ * APIFunction: trace
+ * Prints an interactive stack trace of JavaScript execution at the point
+ * where it is called. The stack trace details the functions on the stack,
+ * as well as the values that were passed as arguments to each function.
+ * You can click each function to take you to its source in the Script tab,
+ * and click each argument value to inspect it in the DOM or HTML tabs.
+ *
+ */
+ trace: function() {},
+
+ /**
+ * APIFunction: group
+ * Writes a message to the console and opens a nested block to indent all
+ * future messages sent to the console. Call OpenLayers.Console.groupEnd()
+ * to close the block.
+ *
+ * May be called with multiple arguments as with OpenLayers.Console.log().
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ group: function() {},
+
+ /**
+ * APIFunction: groupEnd
+ * Closes the most recently opened block created by a call to
+ * OpenLayers.Console.group
+ */
+ groupEnd: function() {},
+
+ /**
+ * APIFunction: time
+ * Creates a new timer under the given name. Call
+ * OpenLayers.Console.timeEnd(name)
+ * with the same name to stop the timer and print the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ time: function() {},
+
+ /**
+ * APIFunction: timeEnd
+ * Stops a timer created by a call to OpenLayers.Console.time(name) and
+ * writes the time elapsed.
+ *
+ * Parameters:
+ * name - {String}
+ */
+ timeEnd: function() {},
+
+ /**
+ * APIFunction: profile
+ * Turns on the JavaScript profiler. The optional argument title would
+ * contain the text to be printed in the header of the profile report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title for the profiler
+ */
+ profile: function() {},
+
+ /**
+ * APIFunction: profileEnd
+ * Turns off the JavaScript profiler and prints its report.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ */
+ profileEnd: function() {},
+
+ /**
+ * APIFunction: count
+ * Writes the number of times that the line of code where count was called
+ * was executed. The optional argument title will print a message in
+ * addition to the number of the count.
+ *
+ * This function is not currently implemented in Firebug Lite.
+ *
+ * Parameters:
+ * title - {String} Optional title to be printed with count
+ */
+ count: function() {},
+
+ CLASS_NAME: "OpenLayers.Console"
+};
+
+/**
+ * Execute an anonymous function to extend the OpenLayers.Console namespace
+ * if the firebug.js script is included. This closure is used so that the
+ * "scripts" and "i" variables don't pollute the global namespace.
+ */
+(function() {
+ /**
+ * If Firebug Lite is included (before this script), re-route all
+ * OpenLayers.Console calls to the console object.
+ */
+ var scripts = document.getElementsByTagName("script");
+ for(var i=0, len=scripts.length; i<len; ++i) {
+ if(scripts[i].src.indexOf("firebug.js") != -1) {
+ if(console) {
+ OpenLayers.Util.extend(OpenLayers.Console, console);
+ break;
+ }
+ }
+ }
+})();
diff --git a/misc/openlayers/lib/OpenLayers/Control.js b/misc/openlayers/lib/OpenLayers/Control.js
new file mode 100644
index 0000000..472a4e6
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control.js
@@ -0,0 +1,371 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Control
+ * Controls affect the display or behavior of the map. They allow everything
+ * from panning and zooming to displaying a scale indicator. Controls by
+ * default are added to the map they are contained within however it is
+ * possible to add a control to an external div by passing the div in the
+ * options parameter.
+ *
+ * Example:
+ * The following example shows how to add many of the common controls
+ * to a map.
+ *
+ * > var map = new OpenLayers.Map('map', { controls: [] });
+ * >
+ * > map.addControl(new OpenLayers.Control.PanZoomBar());
+ * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false}));
+ * > map.addControl(new OpenLayers.Control.Permalink());
+ * > map.addControl(new OpenLayers.Control.Permalink('permalink'));
+ * > map.addControl(new OpenLayers.Control.MousePosition());
+ * > map.addControl(new OpenLayers.Control.OverviewMap());
+ * > map.addControl(new OpenLayers.Control.KeyboardDefaults());
+ *
+ * The next code fragment is a quick example of how to intercept
+ * shift-mouse click to display the extent of the bounding box
+ * dragged out by the user. Usually controls are not created
+ * in exactly this manner. See the source for a more complete
+ * example:
+ *
+ * > var control = new OpenLayers.Control();
+ * > OpenLayers.Util.extend(control, {
+ * > draw: function () {
+ * > // this Handler.Box will intercept the shift-mousedown
+ * > // before Control.MouseDefault gets to see it
+ * > this.box = new OpenLayers.Handler.Box( control,
+ * > {"done": this.notice},
+ * > {keyMask: OpenLayers.Handler.MOD_SHIFT});
+ * > this.box.activate();
+ * > },
+ * >
+ * > notice: function (bounds) {
+ * > OpenLayers.Console.userError(bounds);
+ * > }
+ * > });
+ * > map.addControl(control);
+ *
+ */
+OpenLayers.Control = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in the addControl() function in
+ * OpenLayers.Map
+ */
+ map: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement} The element that contains the control, if not present the
+ * control is placed inside the map.
+ */
+ div: null,
+
+ /**
+ * APIProperty: type
+ * {Number} Controls can have a 'type'. The type determines the type of
+ * interactions which are possible with them when they are placed in an
+ * <OpenLayers.Control.Panel>.
+ */
+ type: null,
+
+ /**
+ * Property: allowSelection
+ * {Boolean} By default, controls do not allow selection, because
+ * it may interfere with map dragging. If this is true, OpenLayers
+ * will not prevent selection of the control.
+ * Default is false.
+ */
+ allowSelection: false,
+
+ /**
+ * Property: displayClass
+ * {string} This property is used for CSS related to the drawing of the
+ * Control.
+ */
+ displayClass: "",
+
+ /**
+ * APIProperty: title
+ * {string} This property is used for showing a tooltip over the
+ * Control.
+ */
+ title: "",
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * false.
+ */
+ autoActivate: false,
+
+ /**
+ * APIProperty: active
+ * {Boolean} The control is active (read-only). Use <activate> and
+ * <deactivate> to change control state.
+ */
+ active: null,
+
+ /**
+ * Property: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+ handlerOptions: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler>} null
+ */
+ handler: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to control.events.object (a reference
+ * to the control).
+ * element - {DOMElement} A reference to control.events.element (which
+ * will be null unless documented otherwise).
+ *
+ * Supported map event types:
+ * activate - Triggered when activated.
+ * deactivate - Triggered when deactivated.
+ */
+ events: null,
+
+ /**
+ * Constructor: OpenLayers.Control
+ * Create an OpenLayers Control. The options passed as a parameter
+ * directly extend the control. For example passing the following:
+ *
+ * > var control = new OpenLayers.Control({div: myDiv});
+ *
+ * Overrides the default div attribute value of null.
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function (options) {
+ // We do this before the extend so that instances can override
+ // className in options.
+ this.displayClass =
+ this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, "");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ if (this.id == null) {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function () {
+ if(this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.events = null;
+ }
+ this.eventListeners = null;
+
+ // eliminate circular references
+ if (this.handler) {
+ this.handler.destroy();
+ this.handler = null;
+ }
+ if(this.handlers) {
+ for(var key in this.handlers) {
+ if(this.handlers.hasOwnProperty(key) &&
+ typeof this.handlers[key].destroy == "function") {
+ this.handlers[key].destroy();
+ }
+ }
+ this.handlers = null;
+ }
+ if (this.map) {
+ this.map.removeControl(this);
+ this.map = null;
+ }
+ this.div = null;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.map = map;
+ if (this.handler) {
+ this.handler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: draw
+ * The draw method is called when the control is ready to be displayed
+ * on the page. If a div has not been created one is created. Controls
+ * with a visual component will almost always want to override this method
+ * to customize the look of control.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The top-left pixel position of the control
+ * or null.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function (px) {
+ if (this.div == null) {
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.className = this.displayClass;
+ if (!this.allowSelection) {
+ this.div.className += " olControlNoSelect";
+ this.div.setAttribute("unselectable", "on", 0);
+ this.div.onselectstart = OpenLayers.Function.False;
+ }
+ if (this.title != "") {
+ this.div.title = this.title;
+ }
+ }
+ if (px != null) {
+ this.position = px.clone();
+ }
+ this.moveTo(this.position);
+ return this.div;
+ },
+
+ /**
+ * Method: moveTo
+ * Sets the left and top style attributes to the passed in pixel
+ * coordinates.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Explicitly activates a control and it's associated
+ * handler if one has been set. Controls can be
+ * deactivated by calling the deactivate() method.
+ *
+ * Returns:
+ * {Boolean} True if the control was successfully activated or
+ * false if the control was already active.
+ */
+ activate: function () {
+ if (this.active) {
+ return false;
+ }
+ if (this.handler) {
+ this.handler.activate();
+ }
+ this.active = true;
+ if(this.map) {
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("activate");
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates a control and it's associated handler if any. The exact
+ * effect of this depends on the control itself.
+ *
+ * Returns:
+ * {Boolean} True if the control was effectively deactivated or false
+ * if the control was already inactive.
+ */
+ deactivate: function () {
+ if (this.active) {
+ if (this.handler) {
+ this.handler.deactivate();
+ }
+ this.active = false;
+ if(this.map) {
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv,
+ this.displayClass.replace(/ /g, "") + "Active"
+ );
+ }
+ this.events.triggerEvent("deactivate");
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Control"
+});
+
+/**
+ * Constant: OpenLayers.Control.TYPE_BUTTON
+ */
+OpenLayers.Control.TYPE_BUTTON = 1;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOGGLE
+ */
+OpenLayers.Control.TYPE_TOGGLE = 2;
+
+/**
+ * Constant: OpenLayers.Control.TYPE_TOOL
+ */
+OpenLayers.Control.TYPE_TOOL = 3;
diff --git a/misc/openlayers/lib/OpenLayers/Control/ArgParser.js b/misc/openlayers/lib/OpenLayers/Control/ArgParser.js
new file mode 100644
index 0000000..6b076f5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ArgParser.js
@@ -0,0 +1,182 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ArgParser
+ * The ArgParser control adds location bar query string parsing functionality
+ * to an OpenLayers Map.
+ * When added to a Map control, on a page load/refresh, the Map will
+ * automatically take the href string and parse it for lon, lat, zoom, and
+ * layers information.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ArgParser = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>}
+ */
+ center: null,
+
+ /**
+ * Property: zoom
+ * {int}
+ */
+ zoom: null,
+
+ /**
+ * Property: layers
+ * {String} Each character represents the state of the corresponding layer
+ * on the map.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support.
+ * Projection used when reading the coordinates from the URL. This will
+ * reproject the map coordinates from the URL into the map's
+ * projection.
+ *
+ * If you are using this functionality, be aware that any permalink
+ * which is added to the map will determine the coordinate type which
+ * is read from the URL, which means you should not add permalinks with
+ * different displayProjections to the same map.
+ */
+ displayProjection: null,
+
+ /**
+ * Constructor: OpenLayers.Control.ArgParser
+ *
+ * Parameters:
+ * options - {Object}
+ */
+
+ /**
+ * Method: getParameters
+ */
+ getParameters: function(url) {
+ url = url || window.location.href;
+ var parameters = OpenLayers.Util.getParameters(url);
+
+ // If we have an anchor in the url use it to split the url
+ var index = url.indexOf('#');
+ if (index > 0) {
+ // create an url to parse on the getParameters
+ url = '?' + url.substring(index + 1, url.length);
+
+ OpenLayers.Util.extend(parameters,
+ OpenLayers.Util.getParameters(url));
+ }
+ return parameters;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ //make sure we dont already have an arg parser attached
+ for(var i=0, len=this.map.controls.length; i<len; i++) {
+ var control = this.map.controls[i];
+ if ( (control != this) &&
+ (control.CLASS_NAME == "OpenLayers.Control.ArgParser") ) {
+
+ // If a second argparser is added to the map, then we
+ // override the displayProjection to be the one added to the
+ // map.
+ if (control.displayProjection != this.displayProjection) {
+ this.displayProjection = control.displayProjection;
+ }
+
+ break;
+ }
+ }
+ if (i == this.map.controls.length) {
+
+ var args = this.getParameters();
+ // Be careful to set layer first, to not trigger unnecessary layer loads
+ if (args.layers) {
+ this.layers = args.layers;
+
+ // when we add a new layer, set its visibility
+ this.map.events.register('addlayer', this,
+ this.configureLayers);
+ this.configureLayers();
+ }
+ if (args.lat && args.lon) {
+ this.center = new OpenLayers.LonLat(parseFloat(args.lon),
+ parseFloat(args.lat));
+ if (args.zoom) {
+ this.zoom = parseFloat(args.zoom);
+ }
+
+ // when we add a new baselayer to see when we can set the center
+ this.map.events.register('changebaselayer', this,
+ this.setCenter);
+ this.setCenter();
+ }
+ }
+ },
+
+ /**
+ * Method: setCenter
+ * As soon as a baseLayer has been loaded, we center and zoom
+ * ...and remove the handler.
+ */
+ setCenter: function() {
+
+ if (this.map.baseLayer) {
+ //dont need to listen for this one anymore
+ this.map.events.unregister('changebaselayer', this,
+ this.setCenter);
+
+ if (this.displayProjection) {
+ this.center.transform(this.displayProjection,
+ this.map.getProjectionObject());
+ }
+
+ this.map.setCenter(this.center, this.zoom);
+ }
+ },
+
+ /**
+ * Method: configureLayers
+ * As soon as all the layers are loaded, cycle through them and
+ * hide or show them.
+ */
+ configureLayers: function() {
+
+ if (this.layers.length == this.map.layers.length) {
+ this.map.events.unregister('addlayer', this, this.configureLayers);
+
+ for(var i=0, len=this.layers.length; i<len; i++) {
+
+ var layer = this.map.layers[i];
+ var c = this.layers.charAt(i);
+
+ if (c == "B") {
+ this.map.setBaseLayer(layer);
+ } else if ( (c == "T") || (c == "F") ) {
+ layer.setVisibility(c == "T");
+ }
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ArgParser"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Attribution.js b/misc/openlayers/lib/OpenLayers/Control/Attribution.js
new file mode 100644
index 0000000..e5ea1ce
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Attribution.js
@@ -0,0 +1,104 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Attribution
+ * The attribution control adds attribution from layers to the map display.
+ * It uses 'attribution' property of each layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Attribution =
+ OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: separator
+ * {String} String used to separate layers.
+ */
+ separator: ", ",
+
+ /**
+ * APIProperty: template
+ * {String} Template for the attribution. This has to include the substring
+ * "${layers}", which will be replaced by the layer specific
+ * attributions, separated by <separator>. The default is "${layers}".
+ */
+ template: "${layers}",
+
+ /**
+ * Constructor: OpenLayers.Control.Attribution
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ * Destroy control.
+ */
+ destroy: function() {
+ this.map.events.un({
+ "removelayer": this.updateAttribution,
+ "addlayer": this.updateAttribution,
+ "changelayer": this.updateAttribution,
+ "changebaselayer": this.updateAttribution,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Initialize control.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ this.map.events.on({
+ 'changebaselayer': this.updateAttribution,
+ 'changelayer': this.updateAttribution,
+ 'addlayer': this.updateAttribution,
+ 'removelayer': this.updateAttribution,
+ scope: this
+ });
+ this.updateAttribution();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateAttribution
+ * Update attribution string.
+ */
+ updateAttribution: function() {
+ var attributions = [];
+ if (this.map && this.map.layers) {
+ for(var i=0, len=this.map.layers.length; i<len; i++) {
+ var layer = this.map.layers[i];
+ if (layer.attribution && layer.getVisibility()) {
+ // add attribution only if attribution text is unique
+ if (OpenLayers.Util.indexOf(
+ attributions, layer.attribution) === -1) {
+ attributions.push( layer.attribution );
+ }
+ }
+ }
+ this.div.innerHTML = OpenLayers.String.format(this.template, {
+ layers: attributions.join(this.separator)
+ });
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Attribution"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Button.js b/misc/openlayers/lib/OpenLayers/Control/Button.js
new file mode 100644
index 0000000..830df6d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Button.js
@@ -0,0 +1,44 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Button
+ * The Button control is a very simple push-button, for use with
+ * <OpenLayers.Control.Panel>.
+ * When clicked, the function trigger() is executed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ *
+ * Use:
+ * (code)
+ * var button = new OpenLayers.Control.Button({
+ * displayClass: "MyButton", trigger: myFunction
+ * });
+ * panel.addControls([button]);
+ * (end)
+ *
+ * Will create a button with CSS class MyButtonItemInactive, that
+ * will call the function MyFunction() when clicked.
+ */
+OpenLayers.Control.Button = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: type
+ * {Integer} OpenLayers.Control.TYPE_BUTTON.
+ */
+ type: OpenLayers.Control.TYPE_BUTTON,
+
+ /**
+ * Method: trigger
+ * Called by a control panel when the button is clicked.
+ */
+ trigger: function() {},
+
+ CLASS_NAME: "OpenLayers.Control.Button"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/CacheRead.js b/misc/openlayers/lib/OpenLayers/Control/CacheRead.js
new file mode 100644
index 0000000..7768bce
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/CacheRead.js
@@ -0,0 +1,156 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.CacheRead
+ * A control for using image tiles cached with <OpenLayers.Control.CacheWrite>
+ * from the browser's local storage.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.CacheRead = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: fetchEvent
+ * {String} The layer event to listen to for replacing remote resource tile
+ * URLs with cached data URIs. Supported values are "tileerror" (try
+ * remote first, fall back to cached) and "tileloadstart" (try cache
+ * first, fall back to remote). Default is "tileloadstart".
+ *
+ * Note that "tileerror" will not work for CORS enabled images (see
+ * https://developer.mozilla.org/en/CORS_Enabled_Image), i.e. layers
+ * configured with a <OpenLayers.Tile.Image.crossOriginKeyword> in
+ * <OpenLayers.Layer.Grid.tileOptions>.
+ */
+ fetchEvent: "tileloadstart",
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, only these
+ * layers will receive tiles from the cache.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.CacheRead
+ *
+ * Parameters:
+ * options - {Object} Object with API properties for this control
+ */
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ var i, layers = this.layers || map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.addLayer({layer: layers[i]});
+ }
+ if (!this.layers) {
+ map.events.on({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Adds a layer to the control. Once added, tiles requested for this layer
+ * will be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ addLayer: function(evt) {
+ evt.layer.events.register(this.fetchEvent, this, this.fetch);
+ },
+
+ /**
+ * Method: removeLayer
+ * Removes a layer from the control. Once removed, tiles requested for this
+ * layer will no longer be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ removeLayer: function(evt) {
+ evt.layer.events.unregister(this.fetchEvent, this, this.fetch);
+ },
+
+ /**
+ * Method: fetch
+ * Listener to the <fetchEvent> event. Replaces a tile's url with a data
+ * URI from the cache.
+ *
+ * Parameters:
+ * evt - {Object} Event object with a tile property.
+ */
+ fetch: function(evt) {
+ if (this.active && window.localStorage &&
+ evt.tile instanceof OpenLayers.Tile.Image) {
+ var tile = evt.tile,
+ url = tile.url;
+ // deal with modified tile urls when both CacheWrite and CacheRead
+ // are active
+ if (!tile.layer.crossOriginKeyword && OpenLayers.ProxyHost &&
+ url.indexOf(OpenLayers.ProxyHost) === 0) {
+ url = OpenLayers.Control.CacheWrite.urlMap[url];
+ }
+ var dataURI = window.localStorage.getItem("olCache_" + url);
+ if (dataURI) {
+ tile.url = dataURI;
+ if (evt.type === "tileerror") {
+ tile.setImgSrc(dataURI);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ if (this.layers || this.map) {
+ var i, layers = this.layers || this.map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.removeLayer({layer: layers[i]});
+ }
+ }
+ if (this.map) {
+ this.map.events.un({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.CacheRead"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/CacheWrite.js b/misc/openlayers/lib/OpenLayers/Control/CacheWrite.js
new file mode 100644
index 0000000..3d4ecf5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/CacheWrite.js
@@ -0,0 +1,257 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Control.CacheWrite
+ * A control for caching image tiles in the browser's local storage. The
+ * <OpenLayers.Control.CacheRead> control is used to fetch and use the cached
+ * tile images.
+ *
+ * Note: Before using this control on any layer that is not your own, make sure
+ * that the terms of service of the tile provider allow local storage of tiles.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.CacheWrite = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * To register events in the constructor, configure <eventListeners>.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * cachefull - Triggered when the cache is full. Listeners receive an
+ * object with a tile property as first argument. The tile references
+ * the tile that couldn't be cached.
+ */
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} Object with event listeners, keyed by event name. An optional
+ * scope property defines the scope that listeners will be executed in.
+ */
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.Grid>)}. Optional. If provided, caching
+ * will be enabled for these layers only, otherwise for all cacheable
+ * layers.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: imageFormat
+ * {String} The image format used for caching. The default is "image/png".
+ * Supported formats depend on the user agent. If an unsupported
+ * <imageFormat> is provided, "image/png" will be used. For aerial
+ * imagery, "image/jpeg" is recommended.
+ */
+ imageFormat: "image/png",
+
+ /**
+ * Property: quotaRegEx
+ * {RegExp}
+ */
+ quotaRegEx: (/quota/i),
+
+ /**
+ * Constructor: OpenLayers.Control.CacheWrite
+ *
+ * Parameters:
+ * options - {Object} Object with API properties for this control.
+ */
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ var i, layers = this.layers || map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.addLayer({layer: layers[i]});
+ }
+ if (!this.layers) {
+ map.events.on({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Adds a layer to the control. Once added, tiles requested for this layer
+ * will be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ addLayer: function(evt) {
+ evt.layer.events.on({
+ tileloadstart: this.makeSameOrigin,
+ tileloaded: this.onTileLoaded,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeLayer
+ * Removes a layer from the control. Once removed, tiles requested for this
+ * layer will no longer be cached.
+ *
+ * Parameters:
+ * evt - {Object} Object with a layer property referencing an
+ * <OpenLayers.Layer> instance
+ */
+ removeLayer: function(evt) {
+ evt.layer.events.un({
+ tileloadstart: this.makeSameOrigin,
+ tileloaded: this.onTileLoaded,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: makeSameOrigin
+ * If the tile does not have CORS image loading enabled and is from a
+ * different origin, use OpenLayers.ProxyHost to make it a same origin url.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ makeSameOrigin: function(evt) {
+ if (this.active) {
+ var tile = evt.tile;
+ if (tile instanceof OpenLayers.Tile.Image &&
+ !tile.crossOriginKeyword &&
+ tile.url.substr(0, 5) !== "data:") {
+ var sameOriginUrl = OpenLayers.Request.makeSameOrigin(
+ tile.url, OpenLayers.ProxyHost
+ );
+ OpenLayers.Control.CacheWrite.urlMap[sameOriginUrl] = tile.url;
+ tile.url = sameOriginUrl;
+ }
+ }
+ },
+
+ /**
+ * Method: onTileLoaded
+ * Decides whether a tile can be cached and calls the cache method.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onTileLoaded: function(evt) {
+ if (this.active && !evt.aborted &&
+ evt.tile instanceof OpenLayers.Tile.Image &&
+ evt.tile.url.substr(0, 5) !== 'data:') {
+ this.cache({tile: evt.tile});
+ delete OpenLayers.Control.CacheWrite.urlMap[evt.tile.url];
+ }
+ },
+
+ /**
+ * Method: cache
+ * Adds a tile to the cache. When the cache is full, the "cachefull" event
+ * is triggered.
+ *
+ * Parameters:
+ * obj - {Object} Object with a tile property, tile being the
+ * <OpenLayers.Tile.Image> with the data to add to the cache
+ */
+ cache: function(obj) {
+ if (window.localStorage) {
+ var tile = obj.tile;
+ try {
+ var canvasContext = tile.getCanvasContext();
+ if (canvasContext) {
+ var urlMap = OpenLayers.Control.CacheWrite.urlMap;
+ var url = urlMap[tile.url] || tile.url;
+ window.localStorage.setItem(
+ "olCache_" + url,
+ canvasContext.canvas.toDataURL(this.imageFormat)
+ );
+ }
+ } catch(e) {
+ // local storage full or CORS violation
+ var reason = e.name || e.message;
+ if (reason && this.quotaRegEx.test(reason)) {
+ this.events.triggerEvent("cachefull", {tile: tile});
+ } else {
+ OpenLayers.Console.error(e.toString());
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ if (this.layers || this.map) {
+ var i, layers = this.layers || this.map.layers;
+ for (i=layers.length-1; i>=0; --i) {
+ this.removeLayer({layer: layers[i]});
+ }
+ }
+ if (this.map) {
+ this.map.events.un({
+ addlayer: this.addLayer,
+ removeLayer: this.removeLayer,
+ scope: this
+ });
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.CacheWrite"
+});
+
+/**
+ * APIFunction: OpenLayers.Control.CacheWrite.clearCache
+ * Clears all tiles cached with <OpenLayers.Control.CacheWrite> from the cache.
+ */
+OpenLayers.Control.CacheWrite.clearCache = function() {
+ if (!window.localStorage) { return; }
+ var i, key;
+ for (i=window.localStorage.length-1; i>=0; --i) {
+ key = window.localStorage.key(i);
+ if (key.substr(0, 8) === "olCache_") {
+ window.localStorage.removeItem(key);
+ }
+ }
+};
+
+/**
+ * Property: OpenLayers.Control.CacheWrite.urlMap
+ * {Object} Mapping of same origin urls to cache url keys. Entries will be
+ * deleted as soon as a tile was cached.
+ */
+OpenLayers.Control.CacheWrite.urlMap = {};
+
+
diff --git a/misc/openlayers/lib/OpenLayers/Control/DragFeature.js b/misc/openlayers/lib/OpenLayers/Control/DragFeature.js
new file mode 100644
index 0000000..d8fb15f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/DragFeature.js
@@ -0,0 +1,366 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragFeature
+ * The DragFeature control moves a feature with a drag of the mouse. Create a
+ * new control with the <OpenLayers.Control.DragFeature> constructor.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict dragging to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * APIProperty: onStart
+ * {Function} Define this function if you want to know when a drag starts.
+ * The function should expect to receive two arguments: the feature
+ * that is about to be dragged and the pixel location of the mouse.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that is about to be
+ * dragged.
+ * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+ */
+ onStart: function(feature, pixel) {},
+
+ /**
+ * APIProperty: onDrag
+ * {Function} Define this function if you want to know about each move of a
+ * feature. The function should expect to receive two arguments: the
+ * feature that is being dragged and the pixel location of the mouse.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+ * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+ */
+ onDrag: function(feature, pixel) {},
+
+ /**
+ * APIProperty: onComplete
+ * {Function} Define this function if you want to know when a feature is
+ * done dragging. The function should expect to receive two arguments:
+ * the feature that is being dragged and the pixel location of the
+ * mouse.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+ * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+ */
+ onComplete: function(feature, pixel) {},
+
+ /**
+ * APIProperty: onEnter
+ * {Function} Define this function if you want to know when the mouse
+ * goes over a feature and thereby makes this feature a candidate
+ * for dragging.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that is ready
+ * to be dragged.
+ */
+ onEnter: function(feature) {},
+
+ /**
+ * APIProperty: onLeave
+ * {Function} Define this function if you want to know when the mouse
+ * goes out of the feature that was dragged.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+ */
+ onLeave: function(feature) {},
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>}
+ */
+ feature: null,
+
+ /**
+ * Property: dragCallbacks
+ * {Object} The functions that are sent to the drag handler for callback.
+ */
+ dragCallbacks: {},
+
+ /**
+ * Property: featureCallbacks
+ * {Object} The functions that are sent to the feature handler for callback.
+ */
+ featureCallbacks: {},
+
+ /**
+ * Property: lastPixel
+ * {<OpenLayers.Pixel>}
+ */
+ lastPixel: null,
+
+ /**
+ * Constructor: OpenLayers.Control.DragFeature
+ * Create a new control to drag features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The layer containing features to be
+ * dragged.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.layer = layer;
+ this.handlers = {
+ drag: new OpenLayers.Handler.Drag(
+ this, OpenLayers.Util.extend({
+ down: this.downFeature,
+ move: this.moveFeature,
+ up: this.upFeature,
+ out: this.cancel,
+ done: this.doneDragging
+ }, this.dragCallbacks), {
+ documentDrag: this.documentDrag
+ }
+ ),
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, OpenLayers.Util.extend({
+ // 'click' and 'clickout' callback are for the mobile
+ // support: no 'over' or 'out' in touch based browsers.
+ click: this.clickFeature,
+ clickout: this.clickoutFeature,
+ over: this.overFeature,
+ out: this.outFeature
+ }, this.featureCallbacks),
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+ },
+
+ /**
+ * Method: clickFeature
+ * Called when the feature handler detects a click-in on a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if (this.handlers.feature.touch && !this.over && this.overFeature(feature)) {
+ this.handlers.drag.dragstart(this.handlers.feature.evt);
+ // to let the events propagate to the feature handler (click callback)
+ this.handlers.drag.stopDown = false;
+ }
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called when the feature handler detects a click-out on a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickoutFeature: function(feature) {
+ if (this.handlers.feature.touch && this.over) {
+ this.outFeature(feature);
+ this.handlers.drag.stopDown = true;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass
+ */
+ destroy: function() {
+ this.layer = null;
+ OpenLayers.Control.prototype.destroy.apply(this, []);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control and the feature handler.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the control and feature handler.
+ */
+ activate: function() {
+ return (this.handlers.feature.activate() &&
+ OpenLayers.Control.prototype.activate.apply(this, arguments));
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control and all handlers.
+ *
+ * Returns:
+ * {Boolean} Successfully deactivated the control.
+ */
+ deactivate: function() {
+ // the return from the handlers is unimportant in this case
+ this.handlers.drag.deactivate();
+ this.handlers.feature.deactivate();
+ this.feature = null;
+ this.dragging = false;
+ this.lastPixel = null;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, this.displayClass + "Over"
+ );
+ return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
+ },
+
+ /**
+ * Method: overFeature
+ * Called when the feature handler detects a mouse-over on a feature.
+ * This activates the drag handler.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The selected feature.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the drag handler.
+ */
+ overFeature: function(feature) {
+ var activated = false;
+ if(!this.handlers.drag.dragging) {
+ this.feature = feature;
+ this.handlers.drag.activate();
+ activated = true;
+ this.over = true;
+ OpenLayers.Element.addClass(this.map.viewPortDiv, this.displayClass + "Over");
+ this.onEnter(feature);
+ } else {
+ if(this.feature.id == feature.id) {
+ this.over = true;
+ } else {
+ this.over = false;
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: downFeature
+ * Called when the drag handler detects a mouse-down.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+ */
+ downFeature: function(pixel) {
+ this.lastPixel = pixel;
+ this.onStart(this.feature, pixel);
+ },
+
+ /**
+ * Method: moveFeature
+ * Called when the drag handler detects a mouse-move. Also calls the
+ * optional onDrag method.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+ */
+ moveFeature: function(pixel) {
+ var res = this.map.getResolution();
+ this.feature.geometry.move(res * (pixel.x - this.lastPixel.x),
+ res * (this.lastPixel.y - pixel.y));
+ this.layer.drawFeature(this.feature);
+ this.lastPixel = pixel;
+ this.onDrag(this.feature, pixel);
+ },
+
+ /**
+ * Method: upFeature
+ * Called when the drag handler detects a mouse-up.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+ */
+ upFeature: function(pixel) {
+ if(!this.over) {
+ this.handlers.drag.deactivate();
+ }
+ },
+
+ /**
+ * Method: doneDragging
+ * Called when the drag handler is done dragging.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The last event pixel location. If this event
+ * came from a mouseout, this may not be in the map viewport.
+ */
+ doneDragging: function(pixel) {
+ this.onComplete(this.feature, pixel);
+ },
+
+ /**
+ * Method: outFeature
+ * Called when the feature handler detects a mouse-out on a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature that the mouse left.
+ */
+ outFeature: function(feature) {
+ if(!this.handlers.drag.dragging) {
+ this.over = false;
+ this.handlers.drag.deactivate();
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, this.displayClass + "Over"
+ );
+ this.onLeave(feature);
+ this.feature = null;
+ } else {
+ if(this.feature.id == feature.id) {
+ this.over = false;
+ }
+ }
+ },
+
+ /**
+ * Method: cancel
+ * Called when the drag handler detects a mouse-out (from the map viewport).
+ */
+ cancel: function() {
+ this.handlers.drag.deactivate();
+ this.over = false;
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and all handlers.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The control's map.
+ */
+ setMap: function(map) {
+ this.handlers.drag.setMap(map);
+ this.handlers.feature.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragFeature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/DragPan.js b/misc/openlayers/lib/OpenLayers/Control/DragPan.js
new file mode 100644
index 0000000..981a649
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/DragPan.js
@@ -0,0 +1,156 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragPan
+ * The DragPan control pans the map with a drag of the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: panned
+ * {Boolean} The map moved.
+ */
+ panned: false,
+
+ /**
+ * Property: interval
+ * {Integer} The number of milliseconds that should ellapse before
+ * panning the map again. Defaults to 0 milliseconds, which means that
+ * no separate cycle is used for panning. In most cases you won't want
+ * to change this value. For slow machines/devices larger values can be
+ * tried out.
+ */
+ interval: 0,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, mouse dragging will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: kinetic
+ * {<OpenLayers.Kinetic>} The OpenLayers.Kinetic object.
+ */
+ kinetic: null,
+
+ /**
+ * APIProperty: enableKinetic
+ * {Boolean} Set this option to enable "kinetic dragging". Can be
+ * set to true or to an object. If set to an object this
+ * object will be passed to the {<OpenLayers.Kinetic>}
+ * constructor. Defaults to true.
+ * To get kinetic dragging, ensure that OpenLayers/Kinetic.js is
+ * included in your build config.
+ */
+ enableKinetic: true,
+
+ /**
+ * APIProperty: kineticInterval
+ * {Integer} Interval in milliseconds between 2 steps in the "kinetic
+ * scrolling". Applies only if enableKinetic is set. Defaults
+ * to 10 milliseconds.
+ */
+ kineticInterval: 10,
+
+
+ /**
+ * Method: draw
+ * Creates a Drag handler, using <panMap> and
+ * <panMapDone> as callbacks.
+ */
+ draw: function() {
+ if (this.enableKinetic && OpenLayers.Kinetic) {
+ var config = {interval: this.kineticInterval};
+ if(typeof this.enableKinetic === "object") {
+ config = OpenLayers.Util.extend(config, this.enableKinetic);
+ }
+ this.kinetic = new OpenLayers.Kinetic(config);
+ }
+ this.handler = new OpenLayers.Handler.Drag(this, {
+ "move": this.panMap,
+ "done": this.panMapDone,
+ "down": this.panMapStart
+ }, {
+ interval: this.interval,
+ documentDrag: this.documentDrag
+ }
+ );
+ },
+
+ /**
+ * Method: panMapStart
+ */
+ panMapStart: function() {
+ if(this.kinetic) {
+ this.kinetic.begin();
+ }
+ },
+
+ /**
+ * Method: panMap
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMap: function(xy) {
+ if(this.kinetic) {
+ this.kinetic.update(xy);
+ }
+ this.panned = true;
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: true, animate: false}
+ );
+ },
+
+ /**
+ * Method: panMapDone
+ * Finish the panning operation. Only call setCenter (through <panMap>)
+ * if the map has actually been moved.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} Pixel of the mouse position
+ */
+ panMapDone: function(xy) {
+ if(this.panned) {
+ var res = null;
+ if (this.kinetic) {
+ res = this.kinetic.end(xy);
+ }
+ this.map.pan(
+ this.handler.last.x - xy.x,
+ this.handler.last.y - xy.y,
+ {dragging: !!res, animate: false}
+ );
+ if (res) {
+ var self = this;
+ this.kinetic.move(res, function(x, y, end) {
+ self.map.pan(x, y, {dragging: !end, animate: false});
+ });
+ }
+ this.panned = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DragPan"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/DrawFeature.js b/misc/openlayers/lib/OpenLayers/Control/DrawFeature.js
new file mode 100644
index 0000000..b0afc71
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/DrawFeature.js
@@ -0,0 +1,229 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DrawFeature
+ * The DrawFeature control draws point, line or polygon features on a vector
+ * layer when active.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * featureadded - Triggered when a feature is added
+ */
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: featureAdded
+ * {Function} Called after each feature is added
+ */
+ featureAdded: function() {},
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.DrawFeature
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ * handler - {<OpenLayers.Handler>}
+ * options - {Object}
+ */
+ initialize: function(layer, handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.callbacks = OpenLayers.Util.extend(
+ {
+ done: this.drawFeature,
+ modify: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchmodified", {vertex: vertex, feature: feature}
+ );
+ },
+ create: function(vertex, feature) {
+ this.layer.events.triggerEvent(
+ "sketchstarted", {vertex: vertex, feature: feature}
+ );
+ }
+ },
+ this.callbacks
+ );
+ this.layer = layer;
+ this.handlerOptions = this.handlerOptions || {};
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions, {
+ renderers: layer.renderers, rendererOptions: layer.rendererOptions
+ }
+ );
+ if (!("multi" in this.handlerOptions)) {
+ this.handlerOptions.multi = this.multi;
+ }
+ var sketchStyle = this.layer.styleMap && this.layer.styleMap.styles.temporary;
+ if(sketchStyle) {
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions,
+ {styleMap: new OpenLayers.StyleMap({"default": sketchStyle})}
+ );
+ }
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * Method: drawFeature
+ */
+ drawFeature: function(geometry) {
+ var feature = new OpenLayers.Feature.Vector(geometry);
+ var proceed = this.layer.events.triggerEvent(
+ "sketchcomplete", {feature: feature}
+ );
+ if(proceed !== false) {
+ feature.state = OpenLayers.State.INSERT;
+ this.layer.addFeatures([feature]);
+ this.featureAdded(feature);
+ this.events.triggerEvent("featureadded",{feature : feature});
+ }
+ },
+
+ /**
+ * APIMethod: insertXY
+ * Insert a point in the current sketch given x & y coordinates.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertXY(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeltaXY(dx, dy);
+ }
+ },
+
+ /**
+ * APIMethod: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDirectionLength(direction, length);
+ }
+ },
+
+ /**
+ * APIMethod: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ if (this.handler && this.handler.line) {
+ this.handler.insertDeflectionLength(deflection, length);
+ }
+ },
+
+ /**
+ * APIMethod: undo
+ * Remove the most recently added point in the current sketch geometry.
+ *
+ * Returns:
+ * {Boolean} An edit was undone.
+ */
+ undo: function() {
+ return this.handler.undo && this.handler.undo();
+ },
+
+ /**
+ * APIMethod: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} An edit was redone.
+ */
+ redo: function() {
+ return this.handler.redo && this.handler.redo();
+ },
+
+ /**
+ * APIMethod: finishSketch
+ * Finishes the sketch without including the currently drawn point.
+ * This method can be called to terminate drawing programmatically
+ * instead of waiting for the user to end the sketch.
+ */
+ finishSketch: function() {
+ this.handler.finishGeometry();
+ },
+
+ /**
+ * APIMethod: cancel
+ * Cancel the current sketch. This removes the current sketch and keeps
+ * the drawing control active.
+ */
+ cancel: function() {
+ this.handler.cancel();
+ },
+
+ CLASS_NAME: "OpenLayers.Control.DrawFeature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/EditingToolbar.js b/misc/openlayers/lib/OpenLayers/Control/EditingToolbar.js
new file mode 100644
index 0000000..ba7ca40
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/EditingToolbar.js
@@ -0,0 +1,81 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/DrawFeature.js
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Handler/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Control.EditingToolbar
+ * The EditingToolbar is a panel of 4 controls to draw polygons, lines,
+ * points, or to navigate the map by panning. By default it appears in the
+ * upper right corner of the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.EditingToolbar = OpenLayers.Class(
+ OpenLayers.Control.Panel, {
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Constructor: OpenLayers.Control.EditingToolbar
+ * Create an editing toolbar for a given layer.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ * options - {Object}
+ */
+ initialize: function(layer, options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+
+ this.addControls(
+ [ new OpenLayers.Control.Navigation() ]
+ );
+ var controls = [
+ new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {
+ displayClass: 'olControlDrawFeaturePoint',
+ handlerOptions: {citeCompliant: this.citeCompliant}
+ }),
+ new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {
+ displayClass: 'olControlDrawFeaturePath',
+ handlerOptions: {citeCompliant: this.citeCompliant}
+ }),
+ new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {
+ displayClass: 'olControlDrawFeaturePolygon',
+ handlerOptions: {citeCompliant: this.citeCompliant}
+ })
+ ];
+ this.addControls(controls);
+ },
+
+ /**
+ * Method: draw
+ * calls the default draw, and then activates mouse defaults.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+ if (this.defaultControl === null) {
+ this.defaultControl = this.controls[0];
+ }
+ return div;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.EditingToolbar"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Geolocate.js b/misc/openlayers/lib/OpenLayers/Control/Geolocate.js
new file mode 100644
index 0000000..4b5b439
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Geolocate.js
@@ -0,0 +1,192 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Geolocate
+ * The Geolocate control wraps w3c geolocation API into control that can be
+ * bound to a map, and generate events on location update
+ *
+ * To use this control requires to load the proj4js library if the projection
+ * of the map is not EPSG:4326 or EPSG:900913.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Geolocate = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * locationupdated - Triggered when browser return a new position. Listeners will
+ * receive an object with a 'position' property which is the browser.geolocation.position
+ * native object, as well as a 'point' property which is the location transformed in the
+ * current map projection.
+ * locationfailed - Triggered when geolocation has failed
+ * locationuncapable - Triggered when control is activated on a browser
+ * which doesn't support geolocation
+ */
+
+ /**
+ * Property: geolocation
+ * {Object} The geolocation engine, as a property to be possibly mocked.
+ * This is set lazily to avoid a memory leak in IE9.
+ */
+ geolocation: null,
+
+ /**
+ * Property: available
+ * {Boolean} The navigator.geolocation object is available.
+ */
+ available: ('geolocation' in navigator),
+
+ /**
+ * APIProperty: bind
+ * {Boolean} If true, map center will be set on location update.
+ */
+ bind: true,
+
+ /**
+ * APIProperty: watch
+ * {Boolean} If true, position will be update regularly.
+ */
+ watch: false,
+
+ /**
+ * APIProperty: geolocationOptions
+ * {Object} Options to pass to the navigator's geolocation API. See
+ * <http://dev.w3.org/geo/api/spec-source.html>. No specific
+ * option is passed to the geolocation API by default.
+ */
+ geolocationOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Geolocate
+ * Create a new control to deal with browser geolocation API
+ *
+ */
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (this.available && !this.geolocation) {
+ // set lazily to avoid IE9 memory leak
+ this.geolocation = navigator.geolocation;
+ }
+ if (!this.geolocation) {
+ this.events.triggerEvent("locationuncapable");
+ return false;
+ }
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ if (this.watch) {
+ this.watchId = this.geolocation.watchPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ } else {
+ this.getCurrentLocation();
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active && this.watchId !== null) {
+ this.geolocation.clearWatch(this.watchId);
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: geolocate
+ * Activates the control.
+ *
+ */
+ geolocate: function (position) {
+ var center = new OpenLayers.LonLat(
+ position.coords.longitude,
+ position.coords.latitude
+ ).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ this.map.getProjectionObject()
+ );
+ if (this.bind) {
+ this.map.setCenter(center);
+ }
+ this.events.triggerEvent("locationupdated", {
+ position: position,
+ point: new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ )
+ });
+ },
+
+ /**
+ * APIMethod: getCurrentLocation
+ *
+ * Returns:
+ * {Boolean} Returns true if a event will be fired (successfull
+ * registration)
+ */
+ getCurrentLocation: function() {
+ if (!this.active || this.watch) {
+ return false;
+ }
+ this.geolocation.getCurrentPosition(
+ OpenLayers.Function.bind(this.geolocate, this),
+ OpenLayers.Function.bind(this.failure, this),
+ this.geolocationOptions
+ );
+ return true;
+ },
+
+ /**
+ * Method: failure
+ * method called on browser's geolocation failure
+ *
+ */
+ failure: function (error) {
+ this.events.triggerEvent("locationfailed", {error: error});
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Geolocate"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/GetFeature.js b/misc/openlayers/lib/OpenLayers/Control/GetFeature.js
new file mode 100644
index 0000000..144e87f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/GetFeature.js
@@ -0,0 +1,597 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Box.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Control.GetFeature
+ * Gets vector features for locations underneath the mouse cursor. Can be
+ * configured to act on click, hover or dragged boxes. Uses an
+ * <OpenLayers.Protocol> that supports spatial filters to retrieve
+ * features from a server and fires events that notify applications of the
+ * selected features.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.GetFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: protocol
+ * {<OpenLayers.Protocol>} Required. The protocol used for fetching
+ * features.
+ */
+ protocol: null,
+
+ /**
+ * APIProperty: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * APIProperty: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * Property: modifiers
+ * {Object} The event modifiers to use, according to the current event
+ * being handled by this control's handlers
+ */
+ modifiers: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: click
+ * {Boolean} Use a click handler for selecting/unselecting features. If
+ * both <click> and <box> are set to true, the click handler takes
+ * precedence over the box handler if a box with zero extent was
+ * selected. Default is true.
+ */
+ click: true,
+
+ /**
+ * APIProperty: single
+ * {Boolean} Tells whether select by click should select a single
+ * feature. If set to false, all matching features are selected.
+ * If set to true, only the best matching feature is selected.
+ * This option has an effect only of the <click> option is set
+ * to true. Default is true.
+ */
+ single: true,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Applies only if <click> is true. Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Applies only if
+ * <click> is true. Default is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: clickTolerance
+ * {Integer} Tolerance for the filter query in pixels. This has the
+ * same effect as the tolerance parameter on WMS GetFeatureInfo
+ * requests. Will be ignored for box selections. Applies only if
+ * <click> or <hover> is true. Default is 5. Note that this not
+ * only affects requests on click, but also on hover.
+ */
+ clickTolerance: 5,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send feature requests on mouse moves. Default is false.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box. If set to
+ * true set <click> to false to disable the click handler and
+ * rely on the box handler only, even for "zero extent" boxes.
+ * See the description of the <click> option for additional
+ * information. Default is false.
+ */
+ box: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a query in single mode
+ * if supported by the <protocol>. This set of features is then used to
+ * determine the best match client-side. Default is 10.
+ */
+ maxFeatures: 10,
+
+ /**
+ * Property: features
+ * {Object} Hash of {<OpenLayers.Feature.Vector>}, keyed by fid, holding
+ * the currently selected features
+ */
+ features: null,
+
+ /**
+ * Proeprty: hoverFeature
+ * {<OpenLayers.Feature.Vector>} The feature currently selected by the
+ * hover handler
+ */
+ hoverFeature: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control. This
+ * is a hash with the keys "click", "box" and "hover".
+ */
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Property: hoverResponse
+ * {<OpenLayers.Protocol.Response>} The response object associated with
+ * the currently running hover request (if any).
+ */
+ hoverResponse: null,
+
+ /**
+ * Property: filterType
+ * {<String>} The type of filter to use when sending off a request.
+ * Possible values:
+ * OpenLayers.Filter.Spatial.<BBOX|INTERSECTS|WITHIN|CONTAINS>
+ * Defaults to: OpenLayers.Filter.Spatial.BBOX
+ */
+ filterType: OpenLayers.Filter.Spatial.BBOX,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforefeatureselected - Triggered when <click> is true before a
+ * feature is selected. The event object has a feature property with
+ * the feature about to select
+ * featureselected - Triggered when <click> is true and a feature is
+ * selected. The event object has a feature property with the
+ * selected feature
+ * beforefeaturesselected - Triggered when <click> is true before a
+ * set of features is selected. The event object is an array of
+ * feature properties with the features about to be selected.
+ * Return false after receiving this event to discontinue processing
+ * of all featureselected events and the featuresselected event.
+ * featuresselected - Triggered when <click> is true and a set of
+ * features is selected. The event object is an array of feature
+ * properties of the selected features
+ * featureunselected - Triggered when <click> is true and a feature is
+ * unselected. The event object has a feature property with the
+ * unselected feature
+ * clickout - Triggered when when <click> is true and no feature was
+ * selected.
+ * hoverfeature - Triggered when <hover> is true and the mouse has
+ * stopped over a feature
+ * outfeature - Triggered when <hover> is true and the mouse moves
+ * moved away from a hover-selected feature
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.GetFeature
+ * Create a new control for fetching remote features.
+ *
+ * Parameters:
+ * options - {Object} A configuration object which at least has to contain
+ * a <protocol> property (if not, it has to be set before a request is
+ * made)
+ */
+ initialize: function(options) {
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.features = {};
+
+ this.handlers = {};
+
+ if(this.click) {
+ this.handlers.click = new OpenLayers.Handler.Click(this,
+ {click: this.selectClick}, this.handlerOptions.click || {});
+ }
+
+ if(this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ OpenLayers.Util.extend(this.handlerOptions.box, {
+ boxDivClassName: "olHandlerBoxSelectFeature"
+ })
+ );
+ }
+
+ if(this.hover) {
+ this.handlers.hover = new OpenLayers.Handler.Hover(
+ this, {'move': this.cancelHover, 'pause': this.selectHover},
+ OpenLayers.Util.extend(this.handlerOptions.hover, {
+ 'delay': 250,
+ 'pixelTolerance': 2
+ })
+ );
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ for(var i in this.handlers) {
+ this.handlers[i].activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ for(var i in this.handlers) {
+ this.handlers[i].deactivate();
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: selectClick
+ * Called on click
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ selectClick: function(evt) {
+ var bounds = this.pixelToBounds(evt.xy);
+
+ this.setModifiers(evt);
+ this.request(bounds, {single: this.single});
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is on
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds>|Object} An OpenLayers.Bounds or
+ * an object with a 'left', 'bottom', 'right' and 'top' properties.
+ */
+ selectBox: function(position) {
+ var bounds;
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ } else {
+ if(this.click) {
+ // box without extent - let the click handler take care of it
+ return;
+ }
+ bounds = this.pixelToBounds(position);
+ }
+ this.setModifiers(this.handlers.box.dragHandler.evt);
+ this.request(bounds);
+ },
+
+ /**
+ * Method: selectHover
+ * Callback from the handlers.hover set up when <hover> selection is on
+ *
+ * Parameters:
+ * evt - {Object} event object with an xy property
+ */
+ selectHover: function(evt) {
+ var bounds = this.pixelToBounds(evt.xy);
+ this.request(bounds, {single: true, hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Callback from the handlers.hover set up when <hover> selection is on
+ */
+ cancelHover: function() {
+ if (this.hoverResponse) {
+ this.protocol.abort(this.hoverResponse);
+ this.hoverResponse = null;
+
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ }
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeature request to the WFS
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} bounds for the request's BBOX filter
+ * options - {Object} additional options for this method.
+ *
+ * Supported options include:
+ * single - {Boolean} A single feature should be returned.
+ * Note that this will be ignored if the protocol does not
+ * return the geometries of the features.
+ * hover - {Boolean} Do the request for the hover handler.
+ */
+ request: function(bounds, options) {
+ options = options || {};
+ var filter = new OpenLayers.Filter.Spatial({
+ type: this.filterType,
+ value: bounds
+ });
+
+ // Set the cursor to "wait" to tell the user we're working.
+ OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
+
+ var response = this.protocol.read({
+ maxFeatures: options.single == true ? this.maxFeatures : undefined,
+ filter: filter,
+ callback: function(result) {
+ if(result.success()) {
+ if(result.features.length) {
+ if(options.single == true) {
+ this.selectBestFeature(result.features,
+ bounds.getCenterLonLat(), options);
+ } else {
+ this.select(result.features);
+ }
+ } else if(options.hover) {
+ this.hoverSelect();
+ } else {
+ this.events.triggerEvent("clickout");
+ if(this.clickout) {
+ this.unselectAll();
+ }
+ }
+ }
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ },
+ scope: this
+ });
+ if(options.hover == true) {
+ this.hoverResponse = response;
+ }
+ },
+
+ /**
+ * Method: selectBestFeature
+ * Selects the feature from an array of features that is the best match
+ * for the click position.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * clickPosition - {<OpenLayers.LonLat>}
+ * options - {Object} additional options for this method
+ *
+ * Supported options include:
+ * hover - {Boolean} Do the selection for the hover handler.
+ */
+ selectBestFeature: function(features, clickPosition, options) {
+ options = options || {};
+ if(features.length) {
+ var point = new OpenLayers.Geometry.Point(clickPosition.lon,
+ clickPosition.lat);
+ var feature, resultFeature, dist;
+ var minDist = Number.MAX_VALUE;
+ for(var i=0; i<features.length; ++i) {
+ feature = features[i];
+ if(feature.geometry) {
+ dist = point.distanceTo(feature.geometry, {edge: false});
+ if(dist < minDist) {
+ minDist = dist;
+ resultFeature = feature;
+ if(minDist == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ if(options.hover == true) {
+ this.hoverSelect(resultFeature);
+ } else {
+ this.select(resultFeature || features);
+ }
+ }
+ },
+
+ /**
+ * Method: setModifiers
+ * Sets the multiple and toggle modifiers according to the current event
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ setModifiers: function(evt) {
+ this.modifiers = {
+ multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]),
+ toggle: this.toggle || (this.toggleKey && evt[this.toggleKey])
+ };
+ },
+
+ /**
+ * Method: select
+ * Add feature to the hash of selected features and trigger the
+ * featureselected and featuresselected events.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>} or an array of features
+ */
+ select: function(features) {
+ if(!this.modifiers.multiple && !this.modifiers.toggle) {
+ this.unselectAll();
+ }
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var cont = this.events.triggerEvent("beforefeaturesselected", {
+ features: features
+ });
+ if(cont !== false) {
+ var selectedFeatures = [];
+ var feature;
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ if(this.features[feature.fid || feature.id]) {
+ if(this.modifiers.toggle) {
+ this.unselect(this.features[feature.fid || feature.id]);
+ }
+ } else {
+ cont = this.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ this.features[feature.fid || feature.id] = feature;
+ selectedFeatures.push(feature);
+
+ this.events.triggerEvent("featureselected",
+ {feature: feature});
+ }
+ }
+ }
+ this.events.triggerEvent("featuresselected", {
+ features: selectedFeatures
+ });
+ }
+ },
+
+ /**
+ * Method: hoverSelect
+ * Sets/unsets the <hoverFeature>
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} the feature to hover-select.
+ * If none is provided, the current <hoverFeature> will be nulled and
+ * the outfeature event will be triggered.
+ */
+ hoverSelect: function(feature) {
+ var fid = feature ? feature.fid || feature.id : null;
+ var hfid = this.hoverFeature ?
+ this.hoverFeature.fid || this.hoverFeature.id : null;
+
+ if(hfid && hfid != fid) {
+ this.events.triggerEvent("outfeature",
+ {feature: this.hoverFeature});
+ this.hoverFeature = null;
+ }
+ if(fid && fid != hfid) {
+ this.events.triggerEvent("hoverfeature", {feature: feature});
+ this.hoverFeature = feature;
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the hash of selected features and trigger the
+ * featureunselected event.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ delete this.features[feature.fid || feature.id];
+ this.events.triggerEvent("featureunselected", {feature: feature});
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features.
+ */
+ unselectAll: function() {
+ // we'll want an option to supress notification here
+ for(var fid in this.features) {
+ this.unselect(this.features[fid]);
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ for(var i in this.handlers) {
+ this.handlers[i].setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: pixelToBounds
+ * Takes a pixel as argument and creates bounds after adding the
+ * <clickTolerance>.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>}
+ */
+ pixelToBounds: function(pixel) {
+ var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2);
+ var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2);
+ var ll = this.map.getLonLatFromPixel(llPx);
+ var ur = this.map.getLonLatFromPixel(urPx);
+ return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.GetFeature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Graticule.js b/misc/openlayers/lib/OpenLayers/Control/Graticule.js
new file mode 100644
index 0000000..2fce50d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Graticule.js
@@ -0,0 +1,377 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Graticule
+ * The Graticule displays a grid of latitude/longitude lines reprojected on
+ * the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ *
+ */
+OpenLayers.Control.Graticule = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: intervals
+ * {Array(Float)} A list of possible graticule widths in degrees.
+ */
+ intervals: [ 45, 30, 20, 10, 5, 2, 1,
+ 0.5, 0.2, 0.1, 0.05, 0.01,
+ 0.005, 0.002, 0.001 ],
+
+ /**
+ * APIProperty: displayInLayerSwitcher
+ * {Boolean} Allows the Graticule control to be switched on and off by
+ * LayerSwitcher control. Defaults is true.
+ */
+ displayInLayerSwitcher: true,
+
+ /**
+ * APIProperty: visible
+ * {Boolean} should the graticule be initially visible (default=true)
+ */
+ visible: true,
+
+ /**
+ * APIProperty: numPoints
+ * {Integer} The number of points to use in each graticule line. Higher
+ * numbers result in a smoother curve for projected maps
+ */
+ numPoints: 50,
+
+ /**
+ * APIProperty: targetSize
+ * {Integer} The maximum size of the grid in pixels on the map
+ */
+ targetSize: 200,
+
+ /**
+ * APIProperty: layerName
+ * {String} The name to be displayed in the layer switcher, default is set
+ * by {<OpenLayers.Lang>}.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: labelled
+ * {Boolean} Should the graticule lines be labelled?. default=true
+ */
+ labelled: true,
+
+ /**
+ * APIProperty: labelFormat
+ * {String} the format of the labels, default = 'dm'. See
+ * <OpenLayers.Util.getFormattedLonLat> for other options.
+ */
+ labelFormat: 'dm',
+
+ /**
+ * APIProperty: lineSymbolizer
+ * {symbolizer} the symbolizer used to render lines
+ */
+ lineSymbolizer: {
+ strokeColor: "#333",
+ strokeWidth: 1,
+ strokeOpacity: 0.5
+ },
+
+ /**
+ * APIProperty: labelSymbolizer
+ * {symbolizer} the symbolizer used to render labels
+ */
+ labelSymbolizer: {},
+
+ /**
+ * Property: gratLayer
+ * {<OpenLayers.Layer.Vector>} vector layer used to draw the graticule on
+ */
+ gratLayer: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Graticule
+ * Create a new graticule control to display a grid of latitude longitude
+ * lines.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.layerName = options.layerName || OpenLayers.i18n("Graticule");
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.labelSymbolizer.stroke = false;
+ this.labelSymbolizer.fill = false;
+ this.labelSymbolizer.label = "${label}";
+ this.labelSymbolizer.labelAlign = "${labelAlign}";
+ this.labelSymbolizer.labelXOffset = "${xOffset}";
+ this.labelSymbolizer.labelYOffset = "${yOffset}";
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if (this.gratLayer) {
+ this.gratLayer.destroy();
+ this.gratLayer = null;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * initializes the graticule layer and does the initial update
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (!this.gratLayer) {
+ var gratStyle = new OpenLayers.Style({},{
+ rules: [new OpenLayers.Rule({'symbolizer':
+ {"Point":this.labelSymbolizer,
+ "Line":this.lineSymbolizer}
+ })]
+ });
+ this.gratLayer = new OpenLayers.Layer.Vector(this.layerName, {
+ styleMap: new OpenLayers.StyleMap({'default':gratStyle}),
+ visibility: this.visible,
+ displayInLayerSwitcher: this.displayInLayerSwitcher
+ });
+ }
+ return this.div;
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ this.map.addLayer(this.gratLayer);
+ this.map.events.register('moveend', this, this.update);
+ this.update();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.map.events.unregister('moveend', this, this.update);
+ this.map.removeLayer(this.gratLayer);
+ return true;
+ } else {
+ return false;
+ }
+ },
+ /**
+ * Method: update
+ *
+ * calculates the grid to be displayed and actually draws it
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ update: function() {
+ //wait for the map to be initialized before proceeding
+ var mapBounds = this.map.getExtent();
+ if (!mapBounds) {
+ return;
+ }
+
+ //clear out the old grid
+ this.gratLayer.destroyFeatures();
+
+ //get the projection objects required
+ var llProj = new OpenLayers.Projection("EPSG:4326");
+ var mapProj = this.map.getProjectionObject();
+ var mapRes = this.map.getResolution();
+
+ //if the map is in lon/lat, then the lines are straight and only one
+ //point is required
+ if (mapProj.proj && mapProj.proj.projName == "longlat") {
+ this.numPoints = 1;
+ }
+
+ //get the map center in EPSG:4326
+ var mapCenter = this.map.getCenter(); //lon and lat here are really map x and y
+ var mapCenterLL = new OpenLayers.Pixel(mapCenter.lon, mapCenter.lat);
+ OpenLayers.Projection.transform(mapCenterLL, mapProj, llProj);
+
+ /* This block of code determines the lon/lat interval to use for the
+ * grid by calculating the diagonal size of one grid cell at the map
+ * center. Iterates through the intervals array until the diagonal
+ * length is less than the targetSize option.
+ */
+ //find lat/lon interval that results in a grid of less than the target size
+ var testSq = this.targetSize*mapRes;
+ testSq *= testSq; //compare squares rather than doing a square root to save time
+ var llInterval;
+ for (var i=0; i<this.intervals.length; ++i) {
+ llInterval = this.intervals[i]; //could do this for both x and y??
+ var delta = llInterval/2;
+ var p1 = mapCenterLL.offset({x: -delta, y: -delta}); //test coords in EPSG:4326 space
+ var p2 = mapCenterLL.offset({x: delta, y: delta});
+ OpenLayers.Projection.transform(p1, llProj, mapProj); // convert them back to map projection
+ OpenLayers.Projection.transform(p2, llProj, mapProj);
+ var distSq = (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y);
+ if (distSq <= testSq) {
+ break;
+ }
+ }
+ //alert(llInterval);
+
+ //round the LL center to an even number based on the interval
+ mapCenterLL.x = Math.floor(mapCenterLL.x/llInterval)*llInterval;
+ mapCenterLL.y = Math.floor(mapCenterLL.y/llInterval)*llInterval;
+ //TODO adjust for minutses/seconds?
+
+ /* The following 2 blocks calculate the nodes of the grid along a
+ * line of constant longitude (then latitiude) running through the
+ * center of the map until it reaches the map edge. The calculation
+ * goes from the center in both directions to the edge.
+ */
+ //get the central longitude line, increment the latitude
+ var iter = 0;
+ var centerLonPoints = [mapCenterLL.clone()];
+ var newPoint = mapCenterLL.clone();
+ var mapXY;
+ do {
+ newPoint = newPoint.offset({x: 0, y: llInterval});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLonPoints.unshift(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+ newPoint = mapCenterLL.clone();
+ do {
+ newPoint = newPoint.offset({x: 0, y: -llInterval});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLonPoints.push(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+
+ //get the central latitude line, increment the longitude
+ iter = 0;
+ var centerLatPoints = [mapCenterLL.clone()];
+ newPoint = mapCenterLL.clone();
+ do {
+ newPoint = newPoint.offset({x: -llInterval, y: 0});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLatPoints.unshift(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+ newPoint = mapCenterLL.clone();
+ do {
+ newPoint = newPoint.offset({x: llInterval, y: 0});
+ mapXY = OpenLayers.Projection.transform(newPoint.clone(), llProj, mapProj);
+ centerLatPoints.push(newPoint);
+ } while (mapBounds.containsPixel(mapXY) && ++iter<1000);
+
+ //now generate a line for each node in the central lat and lon lines
+ //first loop over constant longitude
+ var lines = [];
+ for(var i=0; i < centerLatPoints.length; ++i) {
+ var lon = centerLatPoints[i].x;
+ var pointList = [];
+ var labelPoint = null;
+ var latEnd = Math.min(centerLonPoints[0].y, 90);
+ var latStart = Math.max(centerLonPoints[centerLonPoints.length - 1].y, -90);
+ var latDelta = (latEnd - latStart)/this.numPoints;
+ var lat = latStart;
+ for(var j=0; j<= this.numPoints; ++j) {
+ var gridPoint = new OpenLayers.Geometry.Point(lon,lat);
+ gridPoint.transform(llProj, mapProj);
+ pointList.push(gridPoint);
+ lat += latDelta;
+ if (gridPoint.y >= mapBounds.bottom && !labelPoint) {
+ labelPoint = gridPoint;
+ }
+ }
+ if (this.labelled) {
+ //keep track of when this grid line crosses the map bounds to set
+ //the label position
+ //labels along the bottom, add 10 pixel offset up into the map
+ //TODO add option for labels on top
+ var labelPos = new OpenLayers.Geometry.Point(labelPoint.x,mapBounds.bottom);
+ var labelAttrs = {
+ value: lon,
+ label: this.labelled?OpenLayers.Util.getFormattedLonLat(lon, "lon", this.labelFormat):"",
+ labelAlign: "cb",
+ xOffset: 0,
+ yOffset: 2
+ };
+ this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));
+ }
+ var geom = new OpenLayers.Geometry.LineString(pointList);
+ lines.push(new OpenLayers.Feature.Vector(geom));
+ }
+
+ //now draw the lines of constant latitude
+ for (var j=0; j < centerLonPoints.length; ++j) {
+ lat = centerLonPoints[j].y;
+ if (lat<-90 || lat>90) { //latitudes only valid between -90 and 90
+ continue;
+ }
+ var pointList = [];
+ var lonStart = centerLatPoints[0].x;
+ var lonEnd = centerLatPoints[centerLatPoints.length - 1].x;
+ var lonDelta = (lonEnd - lonStart)/this.numPoints;
+ var lon = lonStart;
+ var labelPoint = null;
+ for(var i=0; i <= this.numPoints ; ++i) {
+ var gridPoint = new OpenLayers.Geometry.Point(lon,lat);
+ gridPoint.transform(llProj, mapProj);
+ pointList.push(gridPoint);
+ lon += lonDelta;
+ if (gridPoint.x < mapBounds.right) {
+ labelPoint = gridPoint;
+ }
+ }
+ if (this.labelled) {
+ //keep track of when this grid line crosses the map bounds to set
+ //the label position
+ //labels along the right, 30 pixel offset left into the map
+ //TODO add option for labels on left
+ var labelPos = new OpenLayers.Geometry.Point(mapBounds.right, labelPoint.y);
+ var labelAttrs = {
+ value: lat,
+ label: this.labelled?OpenLayers.Util.getFormattedLonLat(lat, "lat", this.labelFormat):"",
+ labelAlign: "rb",
+ xOffset: -2,
+ yOffset: 2
+ };
+ this.gratLayer.addFeatures(new OpenLayers.Feature.Vector(labelPos,labelAttrs));
+ }
+ var geom = new OpenLayers.Geometry.LineString(pointList);
+ lines.push(new OpenLayers.Feature.Vector(geom));
+ }
+ this.gratLayer.addFeatures(lines);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Graticule"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Control/KeyboardDefaults.js b/misc/openlayers/lib/OpenLayers/Control/KeyboardDefaults.js
new file mode 100644
index 0000000..3af8831
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/KeyboardDefaults.js
@@ -0,0 +1,142 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Control.KeyboardDefaults
+ * The KeyboardDefaults control adds panning and zooming functions, controlled
+ * with the keyboard. By default arrow keys pan, +/- keys zoom & Page Up/Page
+ * Down/Home/End scroll by three quarters of a page.
+ *
+ * This control has no visible appearance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: slideFactor
+ * Pixels to slide by.
+ */
+ slideFactor: 75,
+
+ /**
+ * APIProperty: observeElement
+ * {DOMelement|String} The DOM element to handle keys for. You
+ * can use the map div here, to have the navigation keys
+ * work when the map div has the focus. If undefined the
+ * document is used.
+ */
+ observeElement: null,
+
+ /**
+ * Constructor: OpenLayers.Control.KeyboardDefaults
+ */
+
+ /**
+ * Method: draw
+ * Create handler.
+ */
+ draw: function() {
+ var observeElement = this.observeElement || document;
+ this.handler = new OpenLayers.Handler.Keyboard( this,
+ {"keydown": this.defaultKeyPress},
+ {observeElement: observeElement}
+ );
+ },
+
+ /**
+ * Method: defaultKeyPress
+ * When handling the key event, we only use evt.keyCode. This holds
+ * some drawbacks, though we get around them below. When interpretting
+ * the keycodes below (including the comments associated with them),
+ * consult the URL below. For instance, the Safari browser returns
+ * "IE keycodes", and so is supported by any keycode labeled "IE".
+ *
+ * Very informative URL:
+ * http://unixpapa.com/js/key.html
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultKeyPress: function (evt) {
+ var size, handled = true;
+
+ var target = OpenLayers.Event.element(evt);
+ if (target &&
+ (target.tagName == 'INPUT' ||
+ target.tagName == 'TEXTAREA' ||
+ target.tagName == 'SELECT')) {
+ return;
+ }
+
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_LEFT:
+ this.map.pan(-this.slideFactor, 0);
+ break;
+ case OpenLayers.Event.KEY_RIGHT:
+ this.map.pan(this.slideFactor, 0);
+ break;
+ case OpenLayers.Event.KEY_UP:
+ this.map.pan(0, -this.slideFactor);
+ break;
+ case OpenLayers.Event.KEY_DOWN:
+ this.map.pan(0, this.slideFactor);
+ break;
+
+ case 33: // Page Up. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(0, -0.75*size.h);
+ break;
+ case 34: // Page Down. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(0, 0.75*size.h);
+ break;
+ case 35: // End. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(0.75*size.w, 0);
+ break;
+ case 36: // Home. Same in all browsers.
+ size = this.map.getSize();
+ this.map.pan(-0.75*size.w, 0);
+ break;
+
+ case 43: // +/= (ASCII), keypad + (ASCII, Opera)
+ case 61: // +/= (Mozilla, Opera, some ASCII)
+ case 187: // +/= (IE)
+ case 107: // keypad + (IE, Mozilla)
+ this.map.zoomIn();
+ break;
+ case 45: // -/_ (ASCII, Opera), keypad - (ASCII, Opera)
+ case 109: // -/_ (Mozilla), keypad - (Mozilla, IE)
+ case 189: // -/_ (IE)
+ case 95: // -/_ (some ASCII)
+ this.map.zoomOut();
+ break;
+ default:
+ handled = false;
+ }
+ if (handled) {
+ // prevent browser default not to move the page
+ // when moving the page with the keyboard
+ OpenLayers.Event.stop(evt);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.KeyboardDefaults"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/LayerSwitcher.js b/misc/openlayers/lib/OpenLayers/Control/LayerSwitcher.js
new file mode 100644
index 0000000..668f5c3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/LayerSwitcher.js
@@ -0,0 +1,521 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.LayerSwitcher
+ * The LayerSwitcher control displays a table of contents for the map. This
+ * allows the user interface to switch between BaseLasyers and to show or hide
+ * Overlays. By default the switcher is shown minimized on the right edge of
+ * the map, the user may expand it by clicking on the handle.
+ *
+ * To create the LayerSwitcher outside of the map, pass the Id of a html div
+ * as the first argument to the constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.LayerSwitcher = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: layerStates
+ * {Array(Object)} Basically a copy of the "state" of the map's layers
+ * the last time the control was drawn. We have this in order to avoid
+ * unnecessarily redrawing the control.
+ */
+ layerStates: null,
+
+ // DOM Elements
+
+ /**
+ * Property: layersDiv
+ * {DOMElement}
+ */
+ layersDiv: null,
+
+ /**
+ * Property: baseLayersDiv
+ * {DOMElement}
+ */
+ baseLayersDiv: null,
+
+ /**
+ * Property: baseLayers
+ * {Array(Object)}
+ */
+ baseLayers: null,
+
+
+ /**
+ * Property: dataLbl
+ * {DOMElement}
+ */
+ dataLbl: null,
+
+ /**
+ * Property: dataLayersDiv
+ * {DOMElement}
+ */
+ dataLayersDiv: null,
+
+ /**
+ * Property: dataLayers
+ * {Array(Object)}
+ */
+ dataLayers: null,
+
+
+ /**
+ * Property: minimizeDiv
+ * {DOMElement}
+ */
+ minimizeDiv: null,
+
+ /**
+ * Property: maximizeDiv
+ * {DOMElement}
+ */
+ maximizeDiv: null,
+
+ /**
+ * APIProperty: ascending
+ * {Boolean}
+ */
+ ascending: true,
+
+ /**
+ * Constructor: OpenLayers.Control.LayerSwitcher
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.layerStates = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+
+ //clear out layers info and unregister their events
+ this.clearLayersArray("base");
+ this.clearLayersArray("data");
+
+ this.map.events.un({
+ buttonclick: this.onButtonClick,
+ addlayer: this.redraw,
+ changelayer: this.redraw,
+ removelayer: this.redraw,
+ changebaselayer: this.redraw,
+ scope: this
+ });
+ this.events.unregister("buttonclick", this, this.onButtonClick);
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Properties:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ this.map.events.on({
+ addlayer: this.redraw,
+ changelayer: this.redraw,
+ removelayer: this.redraw,
+ changebaselayer: this.redraw,
+ scope: this
+ });
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the
+ * switcher tabs.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this);
+
+ // create layout divs
+ this.loadContents();
+
+ // set mode to minimize
+ if(!this.outsideViewport) {
+ this.minimizeControl();
+ }
+
+ // populate div with current info
+ this.redraw();
+
+ return this.div;
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.minimizeDiv) {
+ this.minimizeControl();
+ } else if (button === this.maximizeDiv) {
+ this.maximizeControl();
+ } else if (button._layerSwitcher === this.id) {
+ if (button["for"]) {
+ button = document.getElementById(button["for"]);
+ }
+ if (!button.disabled) {
+ if (button.type == "radio") {
+ button.checked = true;
+ this.map.setBaseLayer(this.map.getLayer(button._layer));
+ } else {
+ button.checked = !button.checked;
+ this.updateMap();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clearLayersArray
+ * User specifies either "base" or "data". we then clear all the
+ * corresponding listeners, the div, and reinitialize a new array.
+ *
+ * Parameters:
+ * layersType - {String}
+ */
+ clearLayersArray: function(layersType) {
+ this[layersType + "LayersDiv"].innerHTML = "";
+ this[layersType + "Layers"] = [];
+ },
+
+
+ /**
+ * Method: checkRedraw
+ * Checks if the layer state has changed since the last redraw() call.
+ *
+ * Returns:
+ * {Boolean} The layer state changed since the last redraw() call.
+ */
+ checkRedraw: function() {
+ if ( !this.layerStates.length ||
+ (this.map.layers.length != this.layerStates.length) ) {
+ return true;
+ }
+
+ for (var i = 0, len = this.layerStates.length; i < len; i++) {
+ var layerState = this.layerStates[i];
+ var layer = this.map.layers[i];
+ if ( (layerState.name != layer.name) ||
+ (layerState.inRange != layer.inRange) ||
+ (layerState.id != layer.id) ||
+ (layerState.visibility != layer.visibility) ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Method: redraw
+ * Goes through and takes the current state of the Map and rebuilds the
+ * control to display that state. Groups base layers into a
+ * radio-button group and lists each data layer with a checkbox.
+ *
+ * Returns:
+ * {DOMElement} A reference to the DIV DOMElement containing the control
+ */
+ redraw: function() {
+ //if the state hasn't changed since last redraw, no need
+ // to do anything. Just return the existing div.
+ if (!this.checkRedraw()) {
+ return this.div;
+ }
+
+ //clear out previous layers
+ this.clearLayersArray("base");
+ this.clearLayersArray("data");
+
+ var containsOverlays = false;
+ var containsBaseLayers = false;
+
+ // Save state -- for checking layer if the map state changed.
+ // We save this before redrawing, because in the process of redrawing
+ // we will trigger more visibility changes, and we want to not redraw
+ // and enter an infinite loop.
+ var len = this.map.layers.length;
+ this.layerStates = new Array(len);
+ for (var i=0; i <len; i++) {
+ var layer = this.map.layers[i];
+ this.layerStates[i] = {
+ 'name': layer.name,
+ 'visibility': layer.visibility,
+ 'inRange': layer.inRange,
+ 'id': layer.id
+ };
+ }
+
+ var layers = this.map.layers.slice();
+ if (!this.ascending) { layers.reverse(); }
+ for(var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ var baseLayer = layer.isBaseLayer;
+
+ if (layer.displayInLayerSwitcher) {
+
+ if (baseLayer) {
+ containsBaseLayers = true;
+ } else {
+ containsOverlays = true;
+ }
+
+ // only check a baselayer if it is *the* baselayer, check data
+ // layers if they are visible
+ var checked = (baseLayer) ? (layer == this.map.baseLayer)
+ : layer.getVisibility();
+
+ // create input element
+ var inputElem = document.createElement("input"),
+ // The input shall have an id attribute so we can use
+ // labels to interact with them.
+ inputId = OpenLayers.Util.createUniqueID(
+ this.id + "_input_"
+ );
+
+ inputElem.id = inputId;
+ inputElem.name = (baseLayer) ? this.id + "_baseLayers" : layer.name;
+ inputElem.type = (baseLayer) ? "radio" : "checkbox";
+ inputElem.value = layer.name;
+ inputElem.checked = checked;
+ inputElem.defaultChecked = checked;
+ inputElem.className = "olButton";
+ inputElem._layer = layer.id;
+ inputElem._layerSwitcher = this.id;
+
+ if (!baseLayer && !layer.inRange) {
+ inputElem.disabled = true;
+ }
+
+ // create span
+ var labelSpan = document.createElement("label");
+ // this isn't the DOM attribute 'for', but an arbitrary name we
+ // use to find the appropriate input element in <onButtonClick>
+ labelSpan["for"] = inputElem.id;
+ OpenLayers.Element.addClass(labelSpan, "labelSpan olButton");
+ labelSpan._layer = layer.id;
+ labelSpan._layerSwitcher = this.id;
+ if (!baseLayer && !layer.inRange) {
+ labelSpan.style.color = "gray";
+ }
+ labelSpan.innerHTML = layer.name;
+ labelSpan.style.verticalAlign = (baseLayer) ? "bottom"
+ : "baseline";
+ // create line break
+ var br = document.createElement("br");
+
+
+ var groupArray = (baseLayer) ? this.baseLayers
+ : this.dataLayers;
+ groupArray.push({
+ 'layer': layer,
+ 'inputElem': inputElem,
+ 'labelSpan': labelSpan
+ });
+
+
+ var groupDiv = (baseLayer) ? this.baseLayersDiv
+ : this.dataLayersDiv;
+ groupDiv.appendChild(inputElem);
+ groupDiv.appendChild(labelSpan);
+ groupDiv.appendChild(br);
+ }
+ }
+
+ // if no overlays, dont display the overlay label
+ this.dataLbl.style.display = (containsOverlays) ? "" : "none";
+
+ // if no baselayers, dont display the baselayer label
+ this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateMap
+ * Cycles through the loaded data and base layer input arrays and makes
+ * the necessary calls to the Map object such that that the map's
+ * visual state corresponds to what the user has selected in
+ * the control.
+ */
+ updateMap: function() {
+
+ // set the newly selected base layer
+ for(var i=0, len=this.baseLayers.length; i<len; i++) {
+ var layerEntry = this.baseLayers[i];
+ if (layerEntry.inputElem.checked) {
+ this.map.setBaseLayer(layerEntry.layer, false);
+ }
+ }
+
+ // set the correct visibilities for the overlays
+ for(var i=0, len=this.dataLayers.length; i<len; i++) {
+ var layerEntry = this.dataLayers[i];
+ layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
+ }
+
+ },
+
+ /**
+ * Method: maximizeControl
+ * Set up the labels and divs for the control
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ maximizeControl: function(e) {
+
+ // set the div's width and height to empty values, so
+ // the div dimensions can be controlled by CSS
+ this.div.style.width = "";
+ this.div.style.height = "";
+
+ this.showControls(false);
+
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: minimizeControl
+ * Hide all the contents of the control, shrink the size,
+ * add the maximize icon
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ minimizeControl: function(e) {
+
+ // to minimize the control we set its div's width
+ // and height to 0px, we cannot just set "display"
+ // to "none" because it would hide the maximize
+ // div
+ this.div.style.width = "0px";
+ this.div.style.height = "0px";
+
+ this.showControls(true);
+
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: showControls
+ * Hide/Show all LayerSwitcher controls depending on whether we are
+ * minimized or not
+ *
+ * Parameters:
+ * minimize - {Boolean}
+ */
+ showControls: function(minimize) {
+
+ this.maximizeDiv.style.display = minimize ? "" : "none";
+ this.minimizeDiv.style.display = minimize ? "none" : "";
+
+ this.layersDiv.style.display = minimize ? "none" : "";
+ },
+
+ /**
+ * Method: loadContents
+ * Set up the labels and divs for the control
+ */
+ loadContents: function() {
+
+ // layers list div
+ this.layersDiv = document.createElement("div");
+ this.layersDiv.id = this.id + "_layersDiv";
+ OpenLayers.Element.addClass(this.layersDiv, "layersDiv");
+
+ this.baseLbl = document.createElement("div");
+ this.baseLbl.innerHTML = OpenLayers.i18n("Base Layer");
+ OpenLayers.Element.addClass(this.baseLbl, "baseLbl");
+
+ this.baseLayersDiv = document.createElement("div");
+ OpenLayers.Element.addClass(this.baseLayersDiv, "baseLayersDiv");
+
+ this.dataLbl = document.createElement("div");
+ this.dataLbl.innerHTML = OpenLayers.i18n("Overlays");
+ OpenLayers.Element.addClass(this.dataLbl, "dataLbl");
+
+ this.dataLayersDiv = document.createElement("div");
+ OpenLayers.Element.addClass(this.dataLayersDiv, "dataLayersDiv");
+
+ if (this.ascending) {
+ this.layersDiv.appendChild(this.baseLbl);
+ this.layersDiv.appendChild(this.baseLayersDiv);
+ this.layersDiv.appendChild(this.dataLbl);
+ this.layersDiv.appendChild(this.dataLayersDiv);
+ } else {
+ this.layersDiv.appendChild(this.dataLbl);
+ this.layersDiv.appendChild(this.dataLayersDiv);
+ this.layersDiv.appendChild(this.baseLbl);
+ this.layersDiv.appendChild(this.baseLayersDiv);
+ }
+
+ this.div.appendChild(this.layersDiv);
+
+ // maximize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
+ this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MaximizeDiv",
+ null,
+ null,
+ img,
+ "absolute");
+ OpenLayers.Element.addClass(this.maximizeDiv, "maximizeDiv olButton");
+ this.maximizeDiv.style.display = "none";
+
+ this.div.appendChild(this.maximizeDiv);
+
+ // minimize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
+ this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MinimizeDiv",
+ null,
+ null,
+ img,
+ "absolute");
+ OpenLayers.Element.addClass(this.minimizeDiv, "minimizeDiv olButton");
+ this.minimizeDiv.style.display = "none";
+
+ this.div.appendChild(this.minimizeDiv);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Measure.js b/misc/openlayers/lib/OpenLayers/Control/Measure.js
new file mode 100644
index 0000000..03c22b8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Measure.js
@@ -0,0 +1,379 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Measure
+ * Allows for drawing of features for measurements.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Measure = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * measure - Triggered when a measurement sketch is complete. Listeners
+ * will receive an event with measure, units, order, and geometry
+ * properties.
+ * measurepartial - Triggered when a new point is added to the
+ * measurement sketch or if the <immediate> property is true and the
+ * measurement sketch is modified. Listeners receive an event with measure,
+ * units, order, and geometry.
+ */
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: displaySystem
+ * {String} Display system for output measurements. Supported values
+ * are 'english', 'metric', and 'geographic'. Default is 'metric'.
+ */
+ displaySystem: 'metric',
+
+ /**
+ * APIProperty: geodesic
+ * {Boolean} Calculate geodesic metrics instead of planar metrics. This
+ * requires that geometries can be transformed into Geographic/WGS84
+ * (if that is not already the map projection). Default is false.
+ */
+ geodesic: false,
+
+ /**
+ * Property: displaySystemUnits
+ * {Object} Units for various measurement systems. Values are arrays
+ * of unit abbreviations (from OpenLayers.INCHES_PER_UNIT) in decreasing
+ * order of length.
+ */
+ displaySystemUnits: {
+ geographic: ['dd'],
+ english: ['mi', 'ft', 'in'],
+ metric: ['km', 'm']
+ },
+
+ /**
+ * Property: delay
+ * {Number} Number of milliseconds between clicks before the event is
+ * considered a double-click. The "measurepartial" event will not
+ * be triggered if the sketch is completed within this time. This
+ * is required for IE where creating a browser reflow (if a listener
+ * is modifying the DOM by displaying the measurement values) messes
+ * with the dblclick listener in the sketch handler.
+ */
+ partialDelay: 300,
+
+ /**
+ * Property: delayedTrigger
+ * {Number} Timeout id of trigger for measurepartial.
+ */
+ delayedTrigger: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Keep the temporary measurement sketch drawn after the
+ * measurement is complete. The geometry will persist until a new
+ * measurement is started, the control is deactivated, or <cancel> is
+ * called.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: immediate
+ * {Boolean} Activates the immediate measurement so that the "measurepartial"
+ * event is also fired once the measurement sketch is modified.
+ * Default is false.
+ */
+ immediate : false,
+
+ /**
+ * Constructor: OpenLayers.Control.Measure
+ *
+ * Parameters:
+ * handler - {<OpenLayers.Handler>}
+ * options - {Object}
+ */
+ initialize: function(handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ var callbacks = {done: this.measureComplete,
+ point: this.measurePartial};
+ if (this.immediate){
+ callbacks.modify = this.measureImmediate;
+ }
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+
+ // let the handler options override, so old code that passes 'persist'
+ // directly to the handler does not need an update
+ this.handlerOptions = OpenLayers.Util.extend(
+ {persist: this.persist}, this.handlerOptions
+ );
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ this.cancelDelay();
+ return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: cancel
+ * Stop the control from measuring. If <persist> is true, the temporary
+ * sketch will be erased.
+ */
+ cancel: function() {
+ this.cancelDelay();
+ this.handler.cancel();
+ },
+
+ /**
+ * APIMethod: setImmediate
+ * Sets the <immediate> property. Changes the activity of immediate
+ * measurement.
+ */
+ setImmediate: function(immediate) {
+ this.immediate = immediate;
+ if (this.immediate){
+ this.callbacks.modify = this.measureImmediate;
+ } else {
+ delete this.callbacks.modify;
+ }
+ },
+
+ /**
+ * Method: updateHandler
+ *
+ * Parameters:
+ * handler - {Function} One of the sketch handler constructors.
+ * options - {Object} Options for the handler.
+ */
+ updateHandler: function(handler, options) {
+ var active = this.active;
+ if(active) {
+ this.deactivate();
+ }
+ this.handler = new handler(this, this.callbacks, options);
+ if(active) {
+ this.activate();
+ }
+ },
+
+ /**
+ * Method: measureComplete
+ * Called when the measurement sketch is done.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ measureComplete: function(geometry) {
+ this.cancelDelay();
+ this.measure(geometry, "measure");
+ },
+
+ /**
+ * Method: measurePartial
+ * Called each time a new point is added to the measurement sketch.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The last point added.
+ * geometry - {<OpenLayers.Geometry>} The sketch geometry.
+ */
+ measurePartial: function(point, geometry) {
+ this.cancelDelay();
+ geometry = geometry.clone();
+ // when we're wating for a dblclick, we have to trigger measurepartial
+ // after some delay to deal with reflow issues in IE
+ if (this.handler.freehandMode(this.handler.evt)) {
+ // no dblclick in freehand mode
+ this.measure(geometry, "measurepartial");
+ } else {
+ this.delayedTrigger = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.delayedTrigger = null;
+ this.measure(geometry, "measurepartial");
+ }, this),
+ this.partialDelay
+ );
+ }
+ },
+
+ /**
+ * Method: measureImmediate
+ * Called each time the measurement sketch is modified.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The point at the mouse position.
+ * feature - {<OpenLayers.Feature.Vector>} The sketch feature.
+ * drawing - {Boolean} Indicates whether we're currently drawing.
+ */
+ measureImmediate : function(point, feature, drawing) {
+ if (drawing && !this.handler.freehandMode(this.handler.evt)) {
+ this.cancelDelay();
+ this.measure(feature.geometry, "measurepartial");
+ }
+ },
+
+ /**
+ * Method: cancelDelay
+ * Cancels the delay measurement that measurePartial began.
+ */
+ cancelDelay: function() {
+ if (this.delayedTrigger !== null) {
+ window.clearTimeout(this.delayedTrigger);
+ this.delayedTrigger = null;
+ }
+ },
+
+ /**
+ * Method: measure
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * eventType - {String}
+ */
+ measure: function(geometry, eventType) {
+ var stat, order;
+ if(geometry.CLASS_NAME.indexOf('LineString') > -1) {
+ stat = this.getBestLength(geometry);
+ order = 1;
+ } else {
+ stat = this.getBestArea(geometry);
+ order = 2;
+ }
+ this.events.triggerEvent(eventType, {
+ measure: stat[0],
+ units: stat[1],
+ order: order,
+ geometry: geometry
+ });
+ },
+
+ /**
+ * Method: getBestArea
+ * Based on the <displaySystem> returns the area of a geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Array([Float, String])} Returns a two item array containing the
+ * area and the units abbreviation.
+ */
+ getBestArea: function(geometry) {
+ var units = this.displaySystemUnits[this.displaySystem];
+ var unit, area;
+ for(var i=0, len=units.length; i<len; ++i) {
+ unit = units[i];
+ area = this.getArea(geometry, unit);
+ if(area > 1) {
+ break;
+ }
+ }
+ return [area, unit];
+ },
+
+ /**
+ * Method: getArea
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * units - {String} Unit abbreviation
+ *
+ * Returns:
+ * {Float} The geometry area in the given units.
+ */
+ getArea: function(geometry, units) {
+ var area, geomUnits;
+ if(this.geodesic) {
+ area = geometry.getGeodesicArea(this.map.getProjectionObject());
+ geomUnits = "m";
+ } else {
+ area = geometry.getArea();
+ geomUnits = this.map.getUnits();
+ }
+ var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
+ if(inPerDisplayUnit) {
+ var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
+ area *= Math.pow((inPerMapUnit / inPerDisplayUnit), 2);
+ }
+ return area;
+ },
+
+ /**
+ * Method: getBestLength
+ * Based on the <displaySystem> returns the length of a geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Array([Float, String])} Returns a two item array containing the
+ * length and the units abbreviation.
+ */
+ getBestLength: function(geometry) {
+ var units = this.displaySystemUnits[this.displaySystem];
+ var unit, length;
+ for(var i=0, len=units.length; i<len; ++i) {
+ unit = units[i];
+ length = this.getLength(geometry, unit);
+ if(length > 1) {
+ break;
+ }
+ }
+ return [length, unit];
+ },
+
+ /**
+ * Method: getLength
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * units - {String} Unit abbreviation
+ *
+ * Returns:
+ * {Float} The geometry length in the given units.
+ */
+ getLength: function(geometry, units) {
+ var length, geomUnits;
+ if(this.geodesic) {
+ length = geometry.getGeodesicLength(this.map.getProjectionObject());
+ geomUnits = "m";
+ } else {
+ length = geometry.getLength();
+ geomUnits = this.map.getUnits();
+ }
+ var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units];
+ if(inPerDisplayUnit) {
+ var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits];
+ length *= (inPerMapUnit / inPerDisplayUnit);
+ }
+ return length;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Measure"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/ModifyFeature.js b/misc/openlayers/lib/OpenLayers/Control/ModifyFeature.js
new file mode 100644
index 0000000..e574608
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ModifyFeature.js
@@ -0,0 +1,835 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ModifyFeature
+ * Control to modify features. When activated, a click renders the vertices
+ * of a feature - these vertices can then be dragged. By default, the
+ * delete key will delete the vertex under the mouse. New features are
+ * added by dragging "virtual vertices" between vertices. Create a new
+ * control with the <OpenLayers.Control.ModifyFeature> constructor.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, dragging vertices will continue even if the
+ * mouse cursor leaves the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict modification to a limited set of geometry
+ * types, send a list of strings corresponding to the geometry class
+ * names.
+ */
+ geometryTypes: null,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click.
+ * Default is true.
+ */
+ toggle: true,
+
+ /**
+ * APIProperty: standalone
+ * {Boolean} Set to true to create a control without SelectFeature
+ * capabilities. Default is false. If standalone is true, to modify
+ * a feature, call the <selectFeature> method with the target feature.
+ * Note that you must call the <unselectFeature> method to finish
+ * feature modification in standalone mode (before starting to modify
+ * another feature).
+ */
+ standalone: false,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} Feature currently available for modification.
+ */
+ feature: null,
+
+ /**
+ * Property: vertex
+ * {<OpenLayers.Feature.Vector>} Vertex currently being modified.
+ */
+ vertex: null,
+
+ /**
+ * Property: vertices
+ * {Array(<OpenLayers.Feature.Vector>)} Verticies currently available
+ * for dragging.
+ */
+ vertices: null,
+
+ /**
+ * Property: virtualVertices
+ * {Array(<OpenLayers.Feature.Vector>)} Virtual vertices in the middle
+ * of each edge.
+ */
+ virtualVertices: null,
+
+ /**
+ * Property: handlers
+ * {Object}
+ */
+ handlers: null,
+
+ /**
+ * APIProperty: deleteCodes
+ * {Array(Integer)} Keycodes for deleting verticies. Set to null to disable
+ * vertex deltion by keypress. If non-null, keypresses with codes
+ * in this array will delete vertices under the mouse. Default
+ * is 46 and 68, the 'delete' and lowercase 'd' keys.
+ */
+ deleteCodes: null,
+
+ /**
+ * APIProperty: virtualStyle
+ * {Object} A symbolizer to be used for virtual vertices.
+ */
+ virtualStyle: null,
+
+ /**
+ * APIProperty: vertexRenderIntent
+ * {String} The renderIntent to use for vertices. If no <virtualStyle> is
+ * provided, this renderIntent will also be used for virtual vertices, with
+ * a fillOpacity and strokeOpacity of 0.3. Default is null, which means
+ * that the layer's default style will be used for vertices.
+ */
+ vertexRenderIntent: null,
+
+ /**
+ * APIProperty: mode
+ * {Integer} Bitfields specifying the modification mode. Defaults to
+ * OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a
+ * combination of options, use the | operator. For example, to allow
+ * the control to both resize and rotate features, use the following
+ * syntax
+ * (code)
+ * control.mode = OpenLayers.Control.ModifyFeature.RESIZE |
+ * OpenLayers.Control.ModifyFeature.ROTATE;
+ * (end)
+ */
+ mode: null,
+
+ /**
+ * APIProperty: createVertices
+ * {Boolean} Create new vertices by dragging the virtual vertices
+ * in the middle of each edge. Default is true.
+ */
+ createVertices: true,
+
+ /**
+ * Property: modified
+ * {Boolean} The currently selected feature has been modified.
+ */
+ modified: false,
+
+ /**
+ * Property: radiusHandle
+ * {<OpenLayers.Feature.Vector>} A handle for rotating/resizing a feature.
+ */
+ radiusHandle: null,
+
+ /**
+ * Property: dragHandle
+ * {<OpenLayers.Feature.Vector>} A handle for dragging a feature.
+ */
+ dragHandle: null,
+
+ /**
+ * APIProperty: onModificationStart
+ * {Function} *Deprecated*. Register for "beforefeaturemodified" instead.
+ * The "beforefeaturemodified" event is triggered on the layer before
+ * any modification begins.
+ *
+ * Optional function to be called when a feature is selected
+ * to be modified. The function should expect to be called with a
+ * feature. This could be used for example to allow to lock the
+ * feature on server-side.
+ */
+ onModificationStart: function() {},
+
+ /**
+ * APIProperty: onModification
+ * {Function} *Deprecated*. Register for "featuremodified" instead.
+ * The "featuremodified" event is triggered on the layer with each
+ * feature modification.
+ *
+ * Optional function to be called when a feature has been
+ * modified. The function should expect to be called with a feature.
+ */
+ onModification: function() {},
+
+ /**
+ * APIProperty: onModificationEnd
+ * {Function} *Deprecated*. Register for "afterfeaturemodified" instead.
+ * The "afterfeaturemodified" event is triggered on the layer after
+ * a feature has been modified.
+ *
+ * Optional function to be called when a feature is finished
+ * being modified. The function should expect to be called with a
+ * feature.
+ */
+ onModificationEnd: function() {},
+
+ /**
+ * Constructor: OpenLayers.Control.ModifyFeature
+ * Create a new modify feature control.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+ * will be modified.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ options = options || {};
+ this.layer = layer;
+ this.vertices = [];
+ this.virtualVertices = [];
+ this.virtualStyle = OpenLayers.Util.extend({},
+ this.layer.style ||
+ this.layer.styleMap.createSymbolizer(null, options.vertexRenderIntent)
+ );
+ this.virtualStyle.fillOpacity = 0.3;
+ this.virtualStyle.strokeOpacity = 0.3;
+ this.deleteCodes = [46, 68];
+ this.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ if(!(OpenLayers.Util.isArray(this.deleteCodes))) {
+ this.deleteCodes = [this.deleteCodes];
+ }
+
+ // configure the drag handler
+ var dragCallbacks = {
+ down: function(pixel) {
+ this.vertex = null;
+ var feature = this.layer.getFeatureFromEvent(
+ this.handlers.drag.evt);
+ if (feature) {
+ this.dragStart(feature);
+ } else if (this.clickout) {
+ this._unselect = this.feature;
+ }
+ },
+ move: function(pixel) {
+ delete this._unselect;
+ if (this.vertex) {
+ this.dragVertex(this.vertex, pixel);
+ }
+ },
+ up: function() {
+ this.handlers.drag.stopDown = false;
+ if (this._unselect) {
+ this.unselectFeature(this._unselect);
+ delete this._unselect;
+ }
+ },
+ done: function(pixel) {
+ if (this.vertex) {
+ this.dragComplete(this.vertex);
+ }
+ }
+ };
+ var dragOptions = {
+ documentDrag: this.documentDrag,
+ stopDown: false
+ };
+
+ // configure the keyboard handler
+ var keyboardOptions = {
+ keydown: this.handleKeypress
+ };
+ this.handlers = {
+ keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions),
+ drag: new OpenLayers.Handler.Drag(this, dragCallbacks, dragOptions)
+ };
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ }
+ this.layer = null;
+ OpenLayers.Control.prototype.destroy.apply(this, []);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control.
+ *
+ * Returns:
+ * {Boolean} Successfully activated the control.
+ */
+ activate: function() {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ return (this.handlers.keyboard.activate() &&
+ this.handlers.drag.activate() &&
+ OpenLayers.Control.prototype.activate.apply(this, arguments));
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control.
+ *
+ * Returns:
+ * {Boolean} Successfully deactivated the control.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ // the return from the controls is unimportant in this case
+ if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.layer.removeFeatures(this.virtualVertices, {silent: true});
+ this.vertices = [];
+ this.handlers.drag.deactivate();
+ this.handlers.keyboard.deactivate();
+ var feature = this.feature;
+ if (feature && feature.geometry && feature.layer) {
+ this.unselectFeature(feature);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: beforeSelectFeature
+ * Called before a feature is selected.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature about to be selected.
+ */
+ beforeSelectFeature: function(feature) {
+ return this.layer.events.triggerEvent(
+ "beforefeaturemodified", {feature: feature}
+ );
+ },
+
+ /**
+ * APIMethod: selectFeature
+ * Select a feature for modification in standalone mode. In non-standalone
+ * mode, this method is called when a feature is selected by clicking.
+ * Register a listener to the beforefeaturemodified event and return false
+ * to prevent feature modification.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} the selected feature.
+ */
+ selectFeature: function(feature) {
+ if (this.feature === feature ||
+ (this.geometryTypes && OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) == -1)) {
+ return;
+ }
+ if (this.beforeSelectFeature(feature) !== false) {
+ if (this.feature) {
+ this.unselectFeature(this.feature);
+ }
+ this.feature = feature;
+ this.layer.selectedFeatures.push(feature);
+ this.layer.drawFeature(feature, 'select');
+ this.modified = false;
+ this.resetVertices();
+ this.onModificationStart(this.feature);
+ }
+ // keep track of geometry modifications
+ var modified = feature.modified;
+ if (feature.geometry && !(modified && modified.geometry)) {
+ this._originalGeometry = feature.geometry.clone();
+ }
+ },
+
+ /**
+ * APIMethod: unselectFeature
+ * Called when the select feature control unselects a feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The unselected feature.
+ */
+ unselectFeature: function(feature) {
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ if(this.dragHandle) {
+ this.layer.destroyFeatures([this.dragHandle], {silent: true});
+ delete this.dragHandle;
+ }
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ delete this.radiusHandle;
+ }
+ this.layer.drawFeature(this.feature, 'default');
+ this.feature = null;
+ OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature);
+ this.onModificationEnd(feature);
+ this.layer.events.triggerEvent("afterfeaturemodified", {
+ feature: feature,
+ modified: this.modified
+ });
+ this.modified = false;
+ },
+
+
+ /**
+ * Method: dragStart
+ * Called by the drag handler before a feature is dragged. This method is
+ * used to differentiate between points and vertices
+ * of higher order geometries.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
+ * dragged.
+ */
+ dragStart: function(feature) {
+ var isPoint = feature.geometry.CLASS_NAME ==
+ 'OpenLayers.Geometry.Point';
+ if (!this.standalone &&
+ ((!feature._sketch && isPoint) || !feature._sketch)) {
+ if (this.toggle && this.feature === feature) {
+ // mark feature for unselection
+ this._unselect = feature;
+ }
+ this.selectFeature(feature);
+ }
+ if (feature._sketch || isPoint) {
+ // feature is a drag or virtual handle or point
+ this.vertex = feature;
+ this.handlers.drag.stopDown = true;
+ }
+ },
+
+ /**
+ * Method: dragVertex
+ * Called by the drag handler with each drag move of a vertex.
+ *
+ * Parameters:
+ * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
+ */
+ dragVertex: function(vertex, pixel) {
+ var pos = this.map.getLonLatFromViewPortPx(pixel);
+ var geom = vertex.geometry;
+ geom.move(pos.lon - geom.x, pos.lat - geom.y);
+ this.modified = true;
+ /**
+ * Five cases:
+ * 1) dragging a simple point
+ * 2) dragging a virtual vertex
+ * 3) dragging a drag handle
+ * 4) dragging a real vertex
+ * 5) dragging a radius handle
+ */
+ if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ // dragging a simple point
+ this.layer.events.triggerEvent("vertexmodified", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: pixel
+ });
+ } else {
+ if(vertex._index) {
+ // dragging a virtual vertex
+ vertex.geometry.parent.addComponent(vertex.geometry,
+ vertex._index);
+ // move from virtual to real vertex
+ delete vertex._index;
+ OpenLayers.Util.removeItem(this.virtualVertices, vertex);
+ this.vertices.push(vertex);
+ } else if(vertex == this.dragHandle) {
+ // dragging a drag handle
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ this.radiusHandle = null;
+ }
+ } else if(vertex !== this.radiusHandle) {
+ // dragging a real vertex
+ this.layer.events.triggerEvent("vertexmodified", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: pixel
+ });
+ }
+ // dragging a radius handle - no special treatment
+ if(this.virtualVertices.length > 0) {
+ this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ }
+ this.layer.drawFeature(this.feature, this.standalone ? undefined :
+ 'select');
+ }
+ // keep the vertex on top so it gets the mouseout after dragging
+ // this should be removed in favor of an option to draw under or
+ // maintain node z-index
+ this.layer.drawFeature(vertex);
+ },
+
+ /**
+ * Method: dragComplete
+ * Called by the drag handler when the feature dragging is complete.
+ *
+ * Parameters:
+ * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+ */
+ dragComplete: function(vertex) {
+ this.resetVertices();
+ this.setFeatureState();
+ this.onModification(this.feature);
+ this.layer.events.triggerEvent("featuremodified",
+ {feature: this.feature});
+ },
+
+ /**
+ * Method: setFeatureState
+ * Called when the feature is modified. If the current state is not
+ * INSERT or DELETE, the state is set to UPDATE.
+ */
+ setFeatureState: function() {
+ if(this.feature.state != OpenLayers.State.INSERT &&
+ this.feature.state != OpenLayers.State.DELETE) {
+ this.feature.state = OpenLayers.State.UPDATE;
+ if (this.modified && this._originalGeometry) {
+ var feature = this.feature;
+ feature.modified = OpenLayers.Util.extend(feature.modified, {
+ geometry: this._originalGeometry
+ });
+ delete this._originalGeometry;
+ }
+ }
+ },
+
+ /**
+ * Method: resetVertices
+ */
+ resetVertices: function() {
+ if(this.vertices.length > 0) {
+ this.layer.removeFeatures(this.vertices, {silent: true});
+ this.vertices = [];
+ }
+ if(this.virtualVertices.length > 0) {
+ this.layer.removeFeatures(this.virtualVertices, {silent: true});
+ this.virtualVertices = [];
+ }
+ if(this.dragHandle) {
+ this.layer.destroyFeatures([this.dragHandle], {silent: true});
+ this.dragHandle = null;
+ }
+ if(this.radiusHandle) {
+ this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+ this.radiusHandle = null;
+ }
+ if(this.feature &&
+ this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
+ if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
+ this.collectDragHandle();
+ }
+ if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
+ OpenLayers.Control.ModifyFeature.RESIZE))) {
+ this.collectRadiusHandle();
+ }
+ if(this.mode & OpenLayers.Control.ModifyFeature.RESHAPE){
+ // Don't collect vertices when we're resizing
+ if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)){
+ this.collectVertices();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: handleKeypress
+ * Called by the feature handler on keypress. This is used to delete
+ * vertices. If the <deleteCode> property is set, vertices will
+ * be deleted when a feature is selected for modification and
+ * the mouse is over a vertex.
+ *
+ * Parameters:
+ * evt - {Event} Keypress event.
+ */
+ handleKeypress: function(evt) {
+ var code = evt.keyCode;
+
+ // check for delete key
+ if(this.feature &&
+ OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
+ var vertex = this.layer.getFeatureFromEvent(this.handlers.drag.evt);
+ if (vertex &&
+ OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
+ !this.handlers.drag.dragging && vertex.geometry.parent) {
+ // remove the vertex
+ vertex.geometry.parent.removeComponent(vertex.geometry);
+ this.layer.events.triggerEvent("vertexremoved", {
+ vertex: vertex.geometry,
+ feature: this.feature,
+ pixel: evt.xy
+ });
+ this.layer.drawFeature(this.feature, this.standalone ?
+ undefined : 'select');
+ this.modified = true;
+ this.resetVertices();
+ this.setFeatureState();
+ this.onModification(this.feature);
+ this.layer.events.triggerEvent("featuremodified",
+ {feature: this.feature});
+ }
+ }
+ },
+
+ /**
+ * Method: collectVertices
+ * Collect the vertices from the modifiable feature's geometry and push
+ * them on to the control's vertices array.
+ */
+ collectVertices: function() {
+ this.vertices = [];
+ this.virtualVertices = [];
+ var control = this;
+ function collectComponentVertices(geometry) {
+ var i, vertex, component, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(geometry);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ var numVert = geometry.components.length;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ numVert -= 1;
+ }
+ for(i=0; i<numVert; ++i) {
+ component = geometry.components[i];
+ if(component.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ vertex = new OpenLayers.Feature.Vector(component);
+ vertex._sketch = true;
+ vertex.renderIntent = control.vertexRenderIntent;
+ control.vertices.push(vertex);
+ } else {
+ collectComponentVertices(component);
+ }
+ }
+
+ // add virtual vertices in the middle of each edge
+ if (control.createVertices && geometry.CLASS_NAME != "OpenLayers.Geometry.MultiPoint") {
+ for(i=0, len=geometry.components.length; i<len-1; ++i) {
+ var prevVertex = geometry.components[i];
+ var nextVertex = geometry.components[i + 1];
+ if(prevVertex.CLASS_NAME == "OpenLayers.Geometry.Point" &&
+ nextVertex.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ var x = (prevVertex.x + nextVertex.x) / 2;
+ var y = (prevVertex.y + nextVertex.y) / 2;
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y),
+ null, control.virtualStyle
+ );
+ // set the virtual parent and intended index
+ point.geometry.parent = geometry;
+ point._index = i + 1;
+ point._sketch = true;
+ control.virtualVertices.push(point);
+ }
+ }
+ }
+ }
+ }
+ collectComponentVertices.call(this, this.feature.geometry);
+ this.layer.addFeatures(this.virtualVertices, {silent: true});
+ this.layer.addFeatures(this.vertices, {silent: true});
+ },
+
+ /**
+ * Method: collectDragHandle
+ * Collect the drag handle for the selected geometry.
+ */
+ collectDragHandle: function() {
+ var geometry = this.feature.geometry;
+ var center = geometry.getBounds().getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var origin = new OpenLayers.Feature.Vector(originGeometry);
+ originGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ geometry.move(x, y);
+ };
+ origin._sketch = true;
+ this.dragHandle = origin;
+ this.dragHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.dragHandle], {silent: true});
+ },
+
+ /**
+ * Method: collectRadiusHandle
+ * Collect the radius handle for the selected geometry.
+ */
+ collectRadiusHandle: function() {
+ var geometry = this.feature.geometry;
+ var bounds = geometry.getBounds();
+ var center = bounds.getCenterLonLat();
+ var originGeometry = new OpenLayers.Geometry.Point(
+ center.lon, center.lat
+ );
+ var radiusGeometry = new OpenLayers.Geometry.Point(
+ bounds.right, bounds.bottom
+ );
+ var radius = new OpenLayers.Feature.Vector(radiusGeometry);
+ var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
+ var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
+ var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
+
+ radiusGeometry.move = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ var dx1 = this.x - originGeometry.x;
+ var dy1 = this.y - originGeometry.y;
+ var dx0 = dx1 - x;
+ var dy0 = dy1 - y;
+ if(rotate) {
+ var a0 = Math.atan2(dy0, dx0);
+ var a1 = Math.atan2(dy1, dx1);
+ var angle = a1 - a0;
+ angle *= 180 / Math.PI;
+ geometry.rotate(angle, originGeometry);
+ }
+ if(resize) {
+ var scale, ratio;
+ // 'resize' together with 'reshape' implies that the aspect
+ // ratio of the geometry will not be preserved whilst resizing
+ if (reshape) {
+ scale = dy1 / dy0;
+ ratio = (dx1 / dx0) / scale;
+ } else {
+ var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+ var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+ scale = l1 / l0;
+ }
+ geometry.resize(scale, originGeometry, ratio);
+ }
+ };
+ radius._sketch = true;
+ this.radiusHandle = radius;
+ this.radiusHandle.renderIntent = this.vertexRenderIntent;
+ this.layer.addFeatures([this.radiusHandle], {silent: true});
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and all handlers.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The control's map.
+ */
+ setMap: function(map) {
+ this.handlers.drag.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ModifyFeature"
+});
+
+/**
+ * Constant: RESHAPE
+ * {Integer} Constant used to make the control work in reshape mode
+ */
+OpenLayers.Control.ModifyFeature.RESHAPE = 1;
+/**
+ * Constant: RESIZE
+ * {Integer} Constant used to make the control work in resize mode
+ */
+OpenLayers.Control.ModifyFeature.RESIZE = 2;
+/**
+ * Constant: ROTATE
+ * {Integer} Constant used to make the control work in rotate mode
+ */
+OpenLayers.Control.ModifyFeature.ROTATE = 4;
+/**
+ * Constant: DRAG
+ * {Integer} Constant used to make the control work in drag mode
+ */
+OpenLayers.Control.ModifyFeature.DRAG = 8;
diff --git a/misc/openlayers/lib/OpenLayers/Control/MousePosition.js b/misc/openlayers/lib/OpenLayers/Control/MousePosition.js
new file mode 100644
index 0000000..0c88fcf
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/MousePosition.js
@@ -0,0 +1,227 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.MousePosition
+ * The MousePosition control displays geographic coordinates of the mouse
+ * pointer, as it is moved about the map.
+ *
+ * You can use the <prefix>- or <suffix>-properties to provide more information
+ * about the displayed coordinates to the user:
+ *
+ * (code)
+ * var mousePositionCtrl = new OpenLayers.Control.MousePosition({
+ * prefix: '<a target="_blank" ' +
+ * 'href="http://spatialreference.org/ref/epsg/4326/">' +
+ * 'EPSG:4326</a> coordinates: '
+ * }
+ * );
+ * (end code)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.MousePosition = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: element
+ * {DOMElement}
+ */
+ element: null,
+
+ /**
+ * APIProperty: prefix
+ * {String} A string to be prepended to the current pointers coordinates
+ * when it is rendered. Defaults to the empty string ''.
+ */
+ prefix: '',
+
+ /**
+ * APIProperty: separator
+ * {String} A string to be used to seperate the two coordinates from each
+ * other. Defaults to the string ', ', which will result in a
+ * rendered coordinate of e.g. '42.12, 21.22'.
+ */
+ separator: ', ',
+
+ /**
+ * APIProperty: suffix
+ * {String} A string to be appended to the current pointers coordinates
+ * when it is rendered. Defaults to the empty string ''.
+ */
+ suffix: '',
+
+ /**
+ * APIProperty: numDigits
+ * {Integer} The number of digits each coordinate shall have when being
+ * rendered, Defaults to 5.
+ */
+ numDigits: 5,
+
+ /**
+ * APIProperty: granularity
+ * {Integer}
+ */
+ granularity: 10,
+
+ /**
+ * APIProperty: emptyString
+ * {String} Set this to some value to set when the mouse is outside the
+ * map.
+ */
+ emptyString: null,
+
+ /**
+ * Property: lastXy
+ * {<OpenLayers.Pixel>}
+ */
+ lastXy: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} The projection in which the mouse position is
+ * displayed.
+ */
+ displayProjection: null,
+
+ /**
+ * Constructor: OpenLayers.Control.MousePosition
+ *
+ * Parameters:
+ * options - {Object} Options for control.
+ */
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ this.map.events.register('mousemove', this, this.redraw);
+ this.map.events.register('mouseout', this, this.reset);
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.map.events.unregister('mousemove', this, this.redraw);
+ this.map.events.unregister('mouseout', this, this.reset);
+ this.element.innerHTML = "";
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ if (!this.element) {
+ this.div.left = "";
+ this.div.top = "";
+ this.element = this.div;
+ }
+
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function(evt) {
+
+ var lonLat;
+
+ if (evt == null) {
+ this.reset();
+ return;
+ } else {
+ if (this.lastXy == null ||
+ Math.abs(evt.xy.x - this.lastXy.x) > this.granularity ||
+ Math.abs(evt.xy.y - this.lastXy.y) > this.granularity)
+ {
+ this.lastXy = evt.xy;
+ return;
+ }
+
+ lonLat = this.map.getLonLatFromPixel(evt.xy);
+ if (!lonLat) {
+ // map has not yet been properly initialized
+ return;
+ }
+ if (this.displayProjection) {
+ lonLat.transform(this.map.getProjectionObject(),
+ this.displayProjection );
+ }
+ this.lastXy = evt.xy;
+
+ }
+
+ var newHtml = this.formatOutput(lonLat);
+
+ if (newHtml != this.element.innerHTML) {
+ this.element.innerHTML = newHtml;
+ }
+ },
+
+ /**
+ * Method: reset
+ */
+ reset: function(evt) {
+ if (this.emptyString != null) {
+ this.element.innerHTML = this.emptyString;
+ }
+ },
+
+ /**
+ * Method: formatOutput
+ * Override to provide custom display output
+ *
+ * Parameters:
+ * lonLat - {<OpenLayers.LonLat>} Location to display
+ */
+ formatOutput: function(lonLat) {
+ var digits = parseInt(this.numDigits);
+ var newHtml =
+ this.prefix +
+ lonLat.lon.toFixed(digits) +
+ this.separator +
+ lonLat.lat.toFixed(digits) +
+ this.suffix;
+ return newHtml;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.MousePosition"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/NavToolbar.js b/misc/openlayers/lib/OpenLayers/Control/NavToolbar.js
new file mode 100644
index 0000000..b6bc2aa
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/NavToolbar.js
@@ -0,0 +1,57 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/ZoomBox.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavToolbar
+ * This Toolbar is an alternative to the Navigation control that displays
+ * the state of the control, and provides a UI for changing state to
+ * use the zoomBox via a Panel control.
+ *
+ * If you wish to change the properties of the Navigation control used
+ * in the NavToolbar, see:
+ * http://trac.openlayers.org/wiki/Toolbars#SubclassingNavToolbar
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.NavToolbar = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * Constructor: OpenLayers.Control.NavToolbar
+ * Add our two mousedefaults controls.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ this.addControls([
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.ZoomBox()
+ ]);
+ },
+
+ /**
+ * Method: draw
+ * calls the default draw, and then activates mouse defaults.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+ if (this.defaultControl === null) {
+ this.defaultControl = this.controls[0];
+ }
+ return div;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.NavToolbar"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Navigation.js b/misc/openlayers/lib/OpenLayers/Control/Navigation.js
new file mode 100644
index 0000000..d50e131
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Navigation.js
@@ -0,0 +1,345 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/ZoomBox.js
+ * @requires OpenLayers/Control/DragPan.js
+ * @requires OpenLayers/Handler/MouseWheel.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Navigation
+ * The navigation control handles map browsing with mouse events (dragging,
+ * double-clicking, and scrolling the wheel). Create a new navigation
+ * control with the <OpenLayers.Control.Navigation> control.
+ *
+ * Note that this control is added to the map by default (if no controls
+ * array is sent in the options object to the <OpenLayers.Map>
+ * constructor).
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: zoomBox
+ * {<OpenLayers.Control.ZoomBox>}
+ */
+ zoomBox: null,
+
+ /**
+ * APIProperty: zoomBoxEnabled
+ * {Boolean} Whether the user can draw a box to zoom
+ */
+ zoomBoxEnabled: true,
+
+ /**
+ * APIProperty: zoomWheelEnabled
+ * {Boolean} Whether the mousewheel should zoom the map
+ */
+ zoomWheelEnabled: true,
+
+ /**
+ * Property: mouseWheelOptions
+ * {Object} Options passed to the MouseWheel control (only useful if
+ * <zoomWheelEnabled> is set to true). Default is no options for maps
+ * with fractionalZoom set to true, otherwise
+ * {cumulative: false, interval: 50, maxDelta: 6}
+ */
+ mouseWheelOptions: null,
+
+ /**
+ * APIProperty: handleRightClicks
+ * {Boolean} Whether or not to handle right clicks. Default is false.
+ */
+ handleRightClicks: false,
+
+ /**
+ * APIProperty: zoomBoxKeyMask
+ * {Integer} <OpenLayers.Handler> key code of the key, which has to be
+ * pressed, while drawing the zoom box with the mouse on the screen.
+ * You should probably set handleRightClicks to true if you use this
+ * with MOD_CTRL, to disable the context menu for machines which use
+ * CTRL-Click as a right click.
+ * Default: <OpenLayers.Handler.MOD_SHIFT>
+ */
+ zoomBoxKeyMask: OpenLayers.Handler.MOD_SHIFT,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.Navigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ this.deactivate();
+
+ if (this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+
+ if (this.zoomBox) {
+ this.zoomBox.destroy();
+ }
+ this.zoomBox = null;
+
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ }
+ this.pinchZoom = null;
+
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ this.dragPan.activate();
+ if (this.zoomWheelEnabled) {
+ this.handlers.wheel.activate();
+ }
+ this.handlers.click.activate();
+ if (this.zoomBoxEnabled) {
+ this.zoomBox.activate();
+ }
+ if (this.pinchZoom) {
+ this.pinchZoom.activate();
+ }
+ return OpenLayers.Control.prototype.activate.apply(this,arguments);
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if (this.pinchZoom) {
+ this.pinchZoom.deactivate();
+ }
+ this.zoomBox.deactivate();
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.handlers.wheel.deactivate();
+ return OpenLayers.Control.prototype.deactivate.apply(this,arguments);
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ // disable right mouse context menu for support of right click events
+ if (this.handleRightClicks) {
+ this.map.viewPortDiv.oncontextmenu = OpenLayers.Function.False;
+ }
+
+ var clickCallbacks = {
+ 'click': this.defaultClick,
+ 'dblclick': this.defaultDblClick,
+ 'dblrightclick': this.defaultDblRightClick
+ };
+ var clickOptions = {
+ 'double': true,
+ 'stopDouble': true
+ };
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.zoomBox = new OpenLayers.Control.ZoomBox(
+ {map: this.map, keyMask: this.zoomBoxKeyMask});
+ this.dragPan.draw();
+ this.zoomBox.draw();
+ var wheelOptions = this.map.fractionalZoom ? {} : {
+ cumulative: false,
+ interval: 50,
+ maxDelta: 6
+ };
+ this.handlers.wheel = new OpenLayers.Handler.MouseWheel(
+ this, {up : this.wheelUp, down: this.wheelDown},
+ OpenLayers.Util.extend(wheelOptions, this.mouseWheelOptions)
+ );
+ if (OpenLayers.Control.PinchZoom) {
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend(
+ {map: this.map}, this.pinchZoomOptions));
+ }
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if (evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ /**
+ * Method: defaultDblRightClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblRightClick: function (evt) {
+ this.map.zoomTo(this.map.zoom - 1, evt.xy);
+ },
+
+ /**
+ * Method: wheelChange
+ *
+ * Parameters:
+ * evt - {Event}
+ * deltaZ - {Integer}
+ */
+ wheelChange: function(evt, deltaZ) {
+ if (!this.map.fractionalZoom) {
+ deltaZ = Math.round(deltaZ);
+ }
+ var currentZoom = this.map.getZoom(),
+ newZoom = currentZoom + deltaZ;
+ newZoom = Math.max(newZoom, 0);
+ newZoom = Math.min(newZoom, this.map.getNumZoomLevels());
+ if (newZoom === currentZoom) {
+ return;
+ }
+ this.map.zoomTo(newZoom, evt.xy);
+ },
+
+ /**
+ * Method: wheelUp
+ * User spun scroll wheel up
+ *
+ * Parameters:
+ * evt - {Event}
+ * delta - {Integer}
+ */
+ wheelUp: function(evt, delta) {
+ this.wheelChange(evt, delta || 1);
+ },
+
+ /**
+ * Method: wheelDown
+ * User spun scroll wheel down
+ *
+ * Parameters:
+ * evt - {Event}
+ * delta - {Integer}
+ */
+ wheelDown: function(evt, delta) {
+ this.wheelChange(evt, delta || -1);
+ },
+
+ /**
+ * Method: disableZoomBox
+ */
+ disableZoomBox : function() {
+ this.zoomBoxEnabled = false;
+ this.zoomBox.deactivate();
+ },
+
+ /**
+ * Method: enableZoomBox
+ */
+ enableZoomBox : function() {
+ this.zoomBoxEnabled = true;
+ if (this.active) {
+ this.zoomBox.activate();
+ }
+ },
+
+ /**
+ * Method: disableZoomWheel
+ */
+
+ disableZoomWheel : function() {
+ this.zoomWheelEnabled = false;
+ this.handlers.wheel.deactivate();
+ },
+
+ /**
+ * Method: enableZoomWheel
+ */
+
+ enableZoomWheel : function() {
+ this.zoomWheelEnabled = true;
+ if (this.active) {
+ this.handlers.wheel.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Navigation"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/NavigationHistory.js b/misc/openlayers/lib/OpenLayers/Control/NavigationHistory.js
new file mode 100644
index 0000000..bf2f95a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/NavigationHistory.js
@@ -0,0 +1,423 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavigationHistory
+ * A navigation history control. This is a meta-control, that creates two
+ * dependent controls: <previous> and <next>. Call the trigger method
+ * on the <previous> and <next> controls to restore previous and next
+ * history states. The previous and next controls will become active
+ * when there are available states to restore and will become deactive
+ * when there are no states to restore.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.NavigationHistory = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {String} Note that this control is not intended to be added directly
+ * to a control panel. Instead, add the sub-controls previous and
+ * next. These sub-controls are button type controls that activate
+ * and deactivate themselves. If this parent control is added to
+ * a panel, it will act as a toggle.
+ */
+ type: OpenLayers.Control.TYPE_TOGGLE,
+
+ /**
+ * APIProperty: previous
+ * {<OpenLayers.Control>} A button type control whose trigger method restores
+ * the previous state managed by this control.
+ */
+ previous: null,
+
+ /**
+ * APIProperty: previousOptions
+ * {Object} Set this property on the options argument of the constructor
+ * to set optional properties on the <previous> control.
+ */
+ previousOptions: null,
+
+ /**
+ * APIProperty: next
+ * {<OpenLayers.Control>} A button type control whose trigger method restores
+ * the next state managed by this control.
+ */
+ next: null,
+
+ /**
+ * APIProperty: nextOptions
+ * {Object} Set this property on the options argument of the constructor
+ * to set optional properties on the <next> control.
+ */
+ nextOptions: null,
+
+ /**
+ * APIProperty: limit
+ * {Integer} Optional limit on the number of history items to retain. If
+ * null, there is no limit. Default is 50.
+ */
+ limit: 50,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: clearOnDeactivate
+ * {Boolean} Clear the history when the control is deactivated. Default
+ * is false.
+ */
+ clearOnDeactivate: false,
+
+ /**
+ * Property: registry
+ * {Object} An object with keys corresponding to event types. Values
+ * are functions that return an object representing the current state.
+ */
+ registry: null,
+
+ /**
+ * Property: nextStack
+ * {Array} Array of items in the history.
+ */
+ nextStack: null,
+
+ /**
+ * Property: previousStack
+ * {Array} List of items in the history. First item represents the current
+ * state.
+ */
+ previousStack: null,
+
+ /**
+ * Property: listeners
+ * {Object} An object containing properties corresponding to event types.
+ * This object is used to configure the control and is modified on
+ * construction.
+ */
+ listeners: null,
+
+ /**
+ * Property: restoring
+ * {Boolean} Currently restoring a history state. This is set to true
+ * before calling restore and set to false after restore returns.
+ */
+ restoring: false,
+
+ /**
+ * Constructor: OpenLayers.Control.NavigationHistory
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.registry = OpenLayers.Util.extend({
+ "moveend": this.getState
+ }, this.registry);
+
+ var previousOptions = {
+ trigger: OpenLayers.Function.bind(this.previousTrigger, this),
+ displayClass: this.displayClass + " " + this.displayClass + "Previous"
+ };
+ OpenLayers.Util.extend(previousOptions, this.previousOptions);
+ this.previous = new OpenLayers.Control.Button(previousOptions);
+
+ var nextOptions = {
+ trigger: OpenLayers.Function.bind(this.nextTrigger, this),
+ displayClass: this.displayClass + " " + this.displayClass + "Next"
+ };
+ OpenLayers.Util.extend(nextOptions, this.nextOptions);
+ this.next = new OpenLayers.Control.Button(nextOptions);
+
+ this.clear();
+ },
+
+ /**
+ * Method: onPreviousChange
+ * Called when the previous history stack changes.
+ *
+ * Parameters:
+ * state - {Object} An object representing the state to be restored
+ * if previous is triggered again or null if no previous states remain.
+ * length - {Integer} The number of remaining previous states that can
+ * be restored.
+ */
+ onPreviousChange: function(state, length) {
+ if(state && !this.previous.active) {
+ this.previous.activate();
+ } else if(!state && this.previous.active) {
+ this.previous.deactivate();
+ }
+ },
+
+ /**
+ * Method: onNextChange
+ * Called when the next history stack changes.
+ *
+ * Parameters:
+ * state - {Object} An object representing the state to be restored
+ * if next is triggered again or null if no next states remain.
+ * length - {Integer} The number of remaining next states that can
+ * be restored.
+ */
+ onNextChange: function(state, length) {
+ if(state && !this.next.active) {
+ this.next.activate();
+ } else if(!state && this.next.active) {
+ this.next.deactivate();
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy the control.
+ */
+ destroy: function() {
+ OpenLayers.Control.prototype.destroy.apply(this);
+ this.previous.destroy();
+ this.next.destroy();
+ this.deactivate();
+ for(var prop in this) {
+ this[prop] = null;
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control and <previous> and <next> child
+ * controls.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.map = map;
+ this.next.setMap(map);
+ this.previous.setMap(map);
+ },
+
+ /**
+ * Method: draw
+ * Called when the control is added to the map.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ this.next.draw();
+ this.previous.draw();
+ },
+
+ /**
+ * Method: previousTrigger
+ * Restore the previous state. If no items are in the previous history
+ * stack, this has no effect.
+ *
+ * Returns:
+ * {Object} Item representing state that was restored. Undefined if no
+ * items are in the previous history stack.
+ */
+ previousTrigger: function() {
+ var current = this.previousStack.shift();
+ var state = this.previousStack.shift();
+ if(state != undefined) {
+ this.nextStack.unshift(current);
+ this.previousStack.unshift(state);
+ this.restoring = true;
+ this.restore(state);
+ this.restoring = false;
+ this.onNextChange(this.nextStack[0], this.nextStack.length);
+ this.onPreviousChange(
+ this.previousStack[1], this.previousStack.length - 1
+ );
+ } else {
+ this.previousStack.unshift(current);
+ }
+ return state;
+ },
+
+ /**
+ * APIMethod: nextTrigger
+ * Restore the next state. If no items are in the next history
+ * stack, this has no effect. The next history stack is populated
+ * as states are restored from the previous history stack.
+ *
+ * Returns:
+ * {Object} Item representing state that was restored. Undefined if no
+ * items are in the next history stack.
+ */
+ nextTrigger: function() {
+ var state = this.nextStack.shift();
+ if(state != undefined) {
+ this.previousStack.unshift(state);
+ this.restoring = true;
+ this.restore(state);
+ this.restoring = false;
+ this.onNextChange(this.nextStack[0], this.nextStack.length);
+ this.onPreviousChange(
+ this.previousStack[1], this.previousStack.length - 1
+ );
+ }
+ return state;
+ },
+
+ /**
+ * APIMethod: clear
+ * Clear history.
+ */
+ clear: function() {
+ this.previousStack = [];
+ this.previous.deactivate();
+ this.nextStack = [];
+ this.next.deactivate();
+ },
+
+ /**
+ * Method: getState
+ * Get the current state and return it.
+ *
+ * Returns:
+ * {Object} An object representing the current state.
+ */
+ getState: function() {
+ return {
+ center: this.map.getCenter(),
+ resolution: this.map.getResolution(),
+ projection: this.map.getProjectionObject(),
+ units: this.map.getProjectionObject().getUnits() ||
+ this.map.units || this.map.baseLayer.units
+ };
+ },
+
+ /**
+ * Method: restore
+ * Update the state with the given object.
+ *
+ * Parameters:
+ * state - {Object} An object representing the state to restore.
+ */
+ restore: function(state) {
+ var center, zoom;
+ if (this.map.getProjectionObject() == state.projection) {
+ zoom = this.map.getZoomForResolution(state.resolution);
+ center = state.center;
+ } else {
+ center = state.center.clone();
+ center.transform(state.projection, this.map.getProjectionObject());
+ var sourceUnits = state.units;
+ var targetUnits = this.map.getProjectionObject().getUnits() ||
+ this.map.units || this.map.baseLayer.units;
+ var resolutionFactor = sourceUnits && targetUnits ?
+ OpenLayers.INCHES_PER_UNIT[sourceUnits] / OpenLayers.INCHES_PER_UNIT[targetUnits] : 1;
+ zoom = this.map.getZoomForResolution(resolutionFactor*state.resolution);
+ }
+ this.map.setCenter(center, zoom);
+ },
+
+ /**
+ * Method: setListeners
+ * Sets functions to be registered in the listeners object.
+ */
+ setListeners: function() {
+ this.listeners = {};
+ for(var type in this.registry) {
+ this.listeners[type] = OpenLayers.Function.bind(function() {
+ if(!this.restoring) {
+ var state = this.registry[type].apply(this, arguments);
+ this.previousStack.unshift(state);
+ if(this.previousStack.length > 1) {
+ this.onPreviousChange(
+ this.previousStack[1], this.previousStack.length - 1
+ );
+ }
+ if(this.previousStack.length > (this.limit + 1)) {
+ this.previousStack.pop();
+ }
+ if(this.nextStack.length > 0) {
+ this.nextStack = [];
+ this.onNextChange(null, 0);
+ }
+ }
+ return true;
+ }, this);
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. This registers any listeners.
+ *
+ * Returns:
+ * {Boolean} Control successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(this.map) {
+ if(OpenLayers.Control.prototype.activate.apply(this)) {
+ if(this.listeners == null) {
+ this.setListeners();
+ }
+ for(var type in this.listeners) {
+ this.map.events.register(type, this, this.listeners[type]);
+ }
+ activated = true;
+ if(this.previousStack.length == 0) {
+ this.initStack();
+ }
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: initStack
+ * Called after the control is activated if the previous history stack is
+ * empty.
+ */
+ initStack: function() {
+ if(this.map.getCenter()) {
+ this.listeners.moveend();
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. This unregisters any listeners.
+ *
+ * Returns:
+ * {Boolean} Control successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(this.map) {
+ if(OpenLayers.Control.prototype.deactivate.apply(this)) {
+ for(var type in this.listeners) {
+ this.map.events.unregister(
+ type, this, this.listeners[type]
+ );
+ }
+ if(this.clearOnDeactivate) {
+ this.clear();
+ }
+ deactivated = true;
+ }
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.NavigationHistory"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Control/OverviewMap.js b/misc/openlayers/lib/OpenLayers/Control/OverviewMap.js
new file mode 100644
index 0000000..50b9300
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/OverviewMap.js
@@ -0,0 +1,750 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Events/buttonclick.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Control.OverviewMap
+ * The OverMap control creates a small overview map, useful to display the
+ * extent of a zoomed map and your main map and provide additional
+ * navigation options to the User. By default the overview map is drawn in
+ * the lower right corner of the main map. Create a new overview map with the
+ * <OpenLayers.Control.OverviewMap> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.OverviewMap = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: element
+ * {DOMElement} The DOM element that contains the overview map
+ */
+ element: null,
+
+ /**
+ * APIProperty: ovmap
+ * {<OpenLayers.Map>} A reference to the overview map itself.
+ */
+ ovmap: null,
+
+ /**
+ * APIProperty: size
+ * {<OpenLayers.Size>} The overvew map size in pixels. Note that this is
+ * the size of the map itself - the element that contains the map (default
+ * class name olControlOverviewMapElement) may have padding or other style
+ * attributes added via CSS.
+ */
+ size: {w: 180, h: 90},
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the overview map.
+ * If none are sent at construction, the base layer for the main map is used.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: minRectSize
+ * {Integer} The minimum width or height (in pixels) of the extent
+ * rectangle on the overview map. When the extent rectangle reaches
+ * this size, it will be replaced depending on the value of the
+ * <minRectDisplayClass> property. Default is 15 pixels.
+ */
+ minRectSize: 15,
+
+ /**
+ * APIProperty: minRectDisplayClass
+ * {String} Replacement style class name for the extent rectangle when
+ * <minRectSize> is reached. This string will be suffixed on to the
+ * displayClass. Default is "RectReplacement".
+ *
+ * Example CSS declaration:
+ * (code)
+ * .olControlOverviewMapRectReplacement {
+ * overflow: hidden;
+ * cursor: move;
+ * background-image: url("img/overview_replacement.gif");
+ * background-repeat: no-repeat;
+ * background-position: center;
+ * }
+ * (end)
+ */
+ minRectDisplayClass: "RectReplacement",
+
+ /**
+ * APIProperty: minRatio
+ * {Float} The ratio of the overview map resolution to the main map
+ * resolution at which to zoom farther out on the overview map.
+ */
+ minRatio: 8,
+
+ /**
+ * APIProperty: maxRatio
+ * {Float} The ratio of the overview map resolution to the main map
+ * resolution at which to zoom farther in on the overview map.
+ */
+ maxRatio: 32,
+
+ /**
+ * APIProperty: mapOptions
+ * {Object} An object containing any non-default properties to be sent to
+ * the overview map's map constructor. These should include any
+ * non-default options that the main map was constructed with.
+ */
+ mapOptions: null,
+
+ /**
+ * APIProperty: autoPan
+ * {Boolean} Always pan the overview map, so the extent marker remains in
+ * the center. Default is false. If true, when you drag the extent
+ * marker, the overview map will update itself so the marker returns
+ * to the center.
+ */
+ autoPan: false,
+
+ /**
+ * Property: handlers
+ * {Object}
+ */
+ handlers: null,
+
+ /**
+ * Property: resolutionFactor
+ * {Object}
+ */
+ resolutionFactor: 1,
+
+ /**
+ * APIProperty: maximized
+ * {Boolean} Start as maximized (visible). Defaults to false.
+ */
+ maximized: false,
+
+ /**
+ * APIProperty: maximizeTitle
+ * {String} This property is used for showing a tooltip over the
+ * maximize div. Defaults to "" (no title).
+ */
+ maximizeTitle: "",
+
+ /**
+ * APIProperty: minimizeTitle
+ * {String} This property is used for showing a tooltip over the
+ * minimize div. Defaults to "" (no title).
+ */
+ minimizeTitle: "",
+
+ /**
+ * Constructor: OpenLayers.Control.OverviewMap
+ * Create a new overview map
+ *
+ * Parameters:
+ * options - {Object} Properties of this object will be set on the overview
+ * map object. Note, to set options on the map object contained in this
+ * control, set <mapOptions> as one of the options properties.
+ */
+ initialize: function(options) {
+ this.layers = [];
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct the control
+ */
+ destroy: function() {
+ if (!this.mapDiv) { // we've already been destroyed
+ return;
+ }
+ if (this.handlers.click) {
+ this.handlers.click.destroy();
+ }
+ if (this.handlers.drag) {
+ this.handlers.drag.destroy();
+ }
+
+ this.ovmap && this.ovmap.viewPortDiv.removeChild(this.extentRectangle);
+ this.extentRectangle = null;
+
+ if (this.rectEvents) {
+ this.rectEvents.destroy();
+ this.rectEvents = null;
+ }
+
+ if (this.ovmap) {
+ this.ovmap.destroy();
+ this.ovmap = null;
+ }
+
+ this.element.removeChild(this.mapDiv);
+ this.mapDiv = null;
+
+ this.div.removeChild(this.element);
+ this.element = null;
+
+ if (this.maximizeDiv) {
+ this.div.removeChild(this.maximizeDiv);
+ this.maximizeDiv = null;
+ }
+
+ if (this.minimizeDiv) {
+ this.div.removeChild(this.minimizeDiv);
+ this.minimizeDiv = null;
+ }
+
+ this.map.events.un({
+ buttonclick: this.onButtonClick,
+ moveend: this.update,
+ changebaselayer: this.baseLayerDraw,
+ scope: this
+ });
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Render the control in the browser.
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.layers.length === 0) {
+ if (this.map.baseLayer) {
+ var layer = this.map.baseLayer.clone();
+ this.layers = [layer];
+ } else {
+ this.map.events.register("changebaselayer", this, this.baseLayerDraw);
+ return this.div;
+ }
+ }
+
+ // create overview map DOM elements
+ this.element = document.createElement('div');
+ this.element.className = this.displayClass + 'Element';
+ this.element.style.display = 'none';
+
+ this.mapDiv = document.createElement('div');
+ this.mapDiv.style.width = this.size.w + 'px';
+ this.mapDiv.style.height = this.size.h + 'px';
+ this.mapDiv.style.position = 'relative';
+ this.mapDiv.style.overflow = 'hidden';
+ this.mapDiv.id = OpenLayers.Util.createUniqueID('overviewMap');
+
+ this.extentRectangle = document.createElement('div');
+ this.extentRectangle.style.position = 'absolute';
+ this.extentRectangle.style.zIndex = 1000; //HACK
+ this.extentRectangle.className = this.displayClass+'ExtentRectangle';
+
+ this.element.appendChild(this.mapDiv);
+
+ this.div.appendChild(this.element);
+
+ // Optionally add min/max buttons if the control will go in the
+ // map viewport.
+ if(!this.outsideViewport) {
+ this.div.className += " " + this.displayClass + 'Container';
+ // maximize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-maximize.png');
+ this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ this.displayClass + 'MaximizeButton',
+ null,
+ null,
+ img,
+ 'absolute');
+ this.maximizeDiv.style.display = 'none';
+ this.maximizeDiv.className = this.displayClass + 'MaximizeButton olButton';
+ if (this.maximizeTitle) {
+ this.maximizeDiv.title = this.maximizeTitle;
+ }
+ this.div.appendChild(this.maximizeDiv);
+
+ // minimize button div
+ var img = OpenLayers.Util.getImageLocation('layer-switcher-minimize.png');
+ this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+ 'OpenLayers_Control_minimizeDiv',
+ null,
+ null,
+ img,
+ 'absolute');
+ this.minimizeDiv.style.display = 'none';
+ this.minimizeDiv.className = this.displayClass + 'MinimizeButton olButton';
+ if (this.minimizeTitle) {
+ this.minimizeDiv.title = this.minimizeTitle;
+ }
+ this.div.appendChild(this.minimizeDiv);
+ this.minimizeControl();
+ } else {
+ // show the overview map
+ this.element.style.display = '';
+ }
+ if(this.map.getExtent()) {
+ this.update();
+ }
+
+ this.map.events.on({
+ buttonclick: this.onButtonClick,
+ moveend: this.update,
+ scope: this
+ });
+
+ if (this.maximized) {
+ this.maximizeControl();
+ }
+ return this.div;
+ },
+
+ /**
+ * Method: baseLayerDraw
+ * Draw the base layer - called if unable to complete in the initial draw
+ */
+ baseLayerDraw: function() {
+ this.draw();
+ this.map.events.unregister("changebaselayer", this, this.baseLayerDraw);
+ },
+
+ /**
+ * Method: rectDrag
+ * Handle extent rectangle drag
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel location of the drag.
+ */
+ rectDrag: function(px) {
+ var deltaX = this.handlers.drag.last.x - px.x;
+ var deltaY = this.handlers.drag.last.y - px.y;
+ if(deltaX != 0 || deltaY != 0) {
+ var rectTop = this.rectPxBounds.top;
+ var rectLeft = this.rectPxBounds.left;
+ var rectHeight = Math.abs(this.rectPxBounds.getHeight());
+ var rectWidth = this.rectPxBounds.getWidth();
+ // don't allow dragging off of parent element
+ var newTop = Math.max(0, (rectTop - deltaY));
+ newTop = Math.min(newTop,
+ this.ovmap.size.h - this.hComp - rectHeight);
+ var newLeft = Math.max(0, (rectLeft - deltaX));
+ newLeft = Math.min(newLeft,
+ this.ovmap.size.w - this.wComp - rectWidth);
+ this.setRectPxBounds(new OpenLayers.Bounds(newLeft,
+ newTop + rectHeight,
+ newLeft + rectWidth,
+ newTop));
+ }
+ },
+
+ /**
+ * Method: mapDivClick
+ * Handle browser events
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>} evt
+ */
+ mapDivClick: function(evt) {
+ var pxCenter = this.rectPxBounds.getCenterPixel();
+ var deltaX = evt.xy.x - pxCenter.x;
+ var deltaY = evt.xy.y - pxCenter.y;
+ var top = this.rectPxBounds.top;
+ var left = this.rectPxBounds.left;
+ var height = Math.abs(this.rectPxBounds.getHeight());
+ var width = this.rectPxBounds.getWidth();
+ var newTop = Math.max(0, (top + deltaY));
+ newTop = Math.min(newTop, this.ovmap.size.h - height);
+ var newLeft = Math.max(0, (left + deltaX));
+ newLeft = Math.min(newLeft, this.ovmap.size.w - width);
+ this.setRectPxBounds(new OpenLayers.Bounds(newLeft,
+ newTop + height,
+ newLeft + width,
+ newTop));
+ this.updateMapToRect();
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ if (evt.buttonElement === this.minimizeDiv) {
+ this.minimizeControl();
+ } else if (evt.buttonElement === this.maximizeDiv) {
+ this.maximizeControl();
+ }
+ },
+
+ /**
+ * Method: maximizeControl
+ * Unhide the control. Called when the control is in the map viewport.
+ *
+ * Parameters:
+ * e - {<OpenLayers.Event>}
+ */
+ maximizeControl: function(e) {
+ this.element.style.display = '';
+ this.showToggle(false);
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: minimizeControl
+ * Hide all the contents of the control, shrink the size,
+ * add the maximize icon
+ *
+ * Parameters:
+ * e - {<OpenLayers.Event>}
+ */
+ minimizeControl: function(e) {
+ this.element.style.display = 'none';
+ this.showToggle(true);
+ if (e != null) {
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: showToggle
+ * Hide/Show the toggle depending on whether the control is minimized
+ *
+ * Parameters:
+ * minimize - {Boolean}
+ */
+ showToggle: function(minimize) {
+ if (this.maximizeDiv) {
+ this.maximizeDiv.style.display = minimize ? '' : 'none';
+ }
+ if (this.minimizeDiv) {
+ this.minimizeDiv.style.display = minimize ? 'none' : '';
+ }
+ },
+
+ /**
+ * Method: update
+ * Update the overview map after layers move.
+ */
+ update: function() {
+ if(this.ovmap == null) {
+ this.createMap();
+ }
+
+ if(this.autoPan || !this.isSuitableOverview()) {
+ this.updateOverview();
+ }
+
+ // update extent rectangle
+ this.updateRectToMap();
+ },
+
+ /**
+ * Method: isSuitableOverview
+ * Determines if the overview map is suitable given the extent and
+ * resolution of the main map.
+ */
+ isSuitableOverview: function() {
+ var mapExtent = this.map.getExtent();
+ var maxExtent = this.map.getMaxExtent();
+ var testExtent = new OpenLayers.Bounds(
+ Math.max(mapExtent.left, maxExtent.left),
+ Math.max(mapExtent.bottom, maxExtent.bottom),
+ Math.min(mapExtent.right, maxExtent.right),
+ Math.min(mapExtent.top, maxExtent.top));
+
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ testExtent = testExtent.transform(
+ this.map.getProjectionObject(),
+ this.ovmap.getProjectionObject() );
+ }
+
+ var resRatio = this.ovmap.getResolution() / this.map.getResolution();
+ return ((resRatio > this.minRatio) &&
+ (resRatio <= this.maxRatio) &&
+ (this.ovmap.getExtent().containsBounds(testExtent)));
+ },
+
+ /**
+ * Method updateOverview
+ * Called by <update> if <isSuitableOverview> returns true
+ */
+ updateOverview: function() {
+ var mapRes = this.map.getResolution();
+ var targetRes = this.ovmap.getResolution();
+ var resRatio = targetRes / mapRes;
+ if(resRatio > this.maxRatio) {
+ // zoom in overview map
+ targetRes = this.minRatio * mapRes;
+ } else if(resRatio <= this.minRatio) {
+ // zoom out overview map
+ targetRes = this.maxRatio * mapRes;
+ }
+ var center;
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ center = this.map.center.clone();
+ center.transform(this.map.getProjectionObject(),
+ this.ovmap.getProjectionObject() );
+ } else {
+ center = this.map.center;
+ }
+ this.ovmap.setCenter(center, this.ovmap.getZoomForResolution(
+ targetRes * this.resolutionFactor));
+ this.updateRectToMap();
+ },
+
+ /**
+ * Method: createMap
+ * Construct the map that this control contains
+ */
+ createMap: function() {
+ // create the overview map
+ var options = OpenLayers.Util.extend(
+ {controls: [], maxResolution: 'auto',
+ fallThrough: false}, this.mapOptions);
+ this.ovmap = new OpenLayers.Map(this.mapDiv, options);
+ this.ovmap.viewPortDiv.appendChild(this.extentRectangle);
+
+ // prevent ovmap from being destroyed when the page unloads, because
+ // the OverviewMap control has to do this (and does it).
+ OpenLayers.Event.stopObserving(window, 'unload', this.ovmap.unloadDestroy);
+
+ this.ovmap.addLayers(this.layers);
+ this.ovmap.zoomToMaxExtent();
+ // check extent rectangle border width
+ this.wComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-left-width')) +
+ parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-right-width'));
+ this.wComp = (this.wComp) ? this.wComp : 2;
+ this.hComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-top-width')) +
+ parseInt(OpenLayers.Element.getStyle(this.extentRectangle,
+ 'border-bottom-width'));
+ this.hComp = (this.hComp) ? this.hComp : 2;
+
+ this.handlers.drag = new OpenLayers.Handler.Drag(
+ this, {move: this.rectDrag, done: this.updateMapToRect},
+ {map: this.ovmap}
+ );
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, {
+ "click": this.mapDivClick
+ },{
+ "single": true, "double": false,
+ "stopSingle": true, "stopDouble": true,
+ "pixelTolerance": 1,
+ map: this.ovmap
+ }
+ );
+ this.handlers.click.activate();
+
+ this.rectEvents = new OpenLayers.Events(this, this.extentRectangle,
+ null, true);
+ this.rectEvents.register("mouseover", this, function(e) {
+ if(!this.handlers.drag.active && !this.map.dragging) {
+ this.handlers.drag.activate();
+ }
+ });
+ this.rectEvents.register("mouseout", this, function(e) {
+ if(!this.handlers.drag.dragging) {
+ this.handlers.drag.deactivate();
+ }
+ });
+
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ var sourceUnits = this.map.getProjectionObject().getUnits() ||
+ this.map.units || this.map.baseLayer.units;
+ var targetUnits = this.ovmap.getProjectionObject().getUnits() ||
+ this.ovmap.units || this.ovmap.baseLayer.units;
+ this.resolutionFactor = sourceUnits && targetUnits ?
+ OpenLayers.INCHES_PER_UNIT[sourceUnits] /
+ OpenLayers.INCHES_PER_UNIT[targetUnits] : 1;
+ }
+ },
+
+ /**
+ * Method: updateRectToMap
+ * Updates the extent rectangle position and size to match the map extent
+ */
+ updateRectToMap: function() {
+ // If the projections differ we need to reproject
+ var bounds;
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ bounds = this.map.getExtent().transform(
+ this.map.getProjectionObject(),
+ this.ovmap.getProjectionObject() );
+ } else {
+ bounds = this.map.getExtent();
+ }
+ var pxBounds = this.getRectBoundsFromMapBounds(bounds);
+ if (pxBounds) {
+ this.setRectPxBounds(pxBounds);
+ }
+ },
+
+ /**
+ * Method: updateMapToRect
+ * Updates the map extent to match the extent rectangle position and size
+ */
+ updateMapToRect: function() {
+ var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds);
+ if (this.ovmap.getProjection() != this.map.getProjection()) {
+ lonLatBounds = lonLatBounds.transform(
+ this.ovmap.getProjectionObject(),
+ this.map.getProjectionObject() );
+ }
+ this.map.panTo(lonLatBounds.getCenterLonLat());
+ },
+
+ /**
+ * Method: setRectPxBounds
+ * Set extent rectangle pixel bounds.
+ *
+ * Parameters:
+ * pxBounds - {<OpenLayers.Bounds>}
+ */
+ setRectPxBounds: function(pxBounds) {
+ var top = Math.max(pxBounds.top, 0);
+ var left = Math.max(pxBounds.left, 0);
+ var bottom = Math.min(pxBounds.top + Math.abs(pxBounds.getHeight()),
+ this.ovmap.size.h - this.hComp);
+ var right = Math.min(pxBounds.left + pxBounds.getWidth(),
+ this.ovmap.size.w - this.wComp);
+ var width = Math.max(right - left, 0);
+ var height = Math.max(bottom - top, 0);
+ if(width < this.minRectSize || height < this.minRectSize) {
+ this.extentRectangle.className = this.displayClass +
+ this.minRectDisplayClass;
+ var rLeft = left + (width / 2) - (this.minRectSize / 2);
+ var rTop = top + (height / 2) - (this.minRectSize / 2);
+ this.extentRectangle.style.top = Math.round(rTop) + 'px';
+ this.extentRectangle.style.left = Math.round(rLeft) + 'px';
+ this.extentRectangle.style.height = this.minRectSize + 'px';
+ this.extentRectangle.style.width = this.minRectSize + 'px';
+ } else {
+ this.extentRectangle.className = this.displayClass +
+ 'ExtentRectangle';
+ this.extentRectangle.style.top = Math.round(top) + 'px';
+ this.extentRectangle.style.left = Math.round(left) + 'px';
+ this.extentRectangle.style.height = Math.round(height) + 'px';
+ this.extentRectangle.style.width = Math.round(width) + 'px';
+ }
+ this.rectPxBounds = new OpenLayers.Bounds(
+ Math.round(left), Math.round(bottom),
+ Math.round(right), Math.round(top)
+ );
+ },
+
+ /**
+ * Method: getRectBoundsFromMapBounds
+ * Get the rect bounds from the map bounds.
+ *
+ * Parameters:
+ * lonLatBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}A bounds which is the passed-in map lon/lat extent
+ * translated into pixel bounds for the overview map
+ */
+ getRectBoundsFromMapBounds: function(lonLatBounds) {
+ var leftBottomPx = this.getOverviewPxFromLonLat({
+ lon: lonLatBounds.left,
+ lat: lonLatBounds.bottom
+ });
+ var rightTopPx = this.getOverviewPxFromLonLat({
+ lon: lonLatBounds.right,
+ lat: lonLatBounds.top
+ });
+ var bounds = null;
+ if (leftBottomPx && rightTopPx) {
+ bounds = new OpenLayers.Bounds(leftBottomPx.x, leftBottomPx.y,
+ rightTopPx.x, rightTopPx.y);
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: getMapBoundsFromRectBounds
+ * Get the map bounds from the rect bounds.
+ *
+ * Parameters:
+ * pxBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds which is the passed-in overview rect bounds
+ * translated into lon/lat bounds for the overview map
+ */
+ getMapBoundsFromRectBounds: function(pxBounds) {
+ var leftBottomLonLat = this.getLonLatFromOverviewPx({
+ x: pxBounds.left,
+ y: pxBounds.bottom
+ });
+ var rightTopLonLat = this.getLonLatFromOverviewPx({
+ x: pxBounds.right,
+ y: pxBounds.top
+ });
+ return new OpenLayers.Bounds(leftBottomLonLat.lon, leftBottomLonLat.lat,
+ rightTopLonLat.lon, rightTopLonLat.lat);
+ },
+
+ /**
+ * Method: getLonLatFromOverviewPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * overviewMapPx - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or
+ * an object with a
+ * 'x' and 'y' properties.
+ *
+ * Returns:
+ * {Object} Location which is the passed-in overview map
+ * OpenLayers.Pixel, translated into lon/lat by the overview
+ * map. An object with a 'lon' and 'lat' properties.
+ */
+ getLonLatFromOverviewPx: function(overviewMapPx) {
+ var size = this.ovmap.size;
+ var res = this.ovmap.getResolution();
+ var center = this.ovmap.getExtent().getCenterLonLat();
+
+ var deltaX = overviewMapPx.x - (size.w / 2);
+ var deltaY = overviewMapPx.y - (size.h / 2);
+
+ return {
+ lon: center.lon + deltaX * res,
+ lat: center.lat - deltaY * res
+ };
+ },
+
+ /**
+ * Method: getOverviewPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ *
+ * Returns:
+ * {Object} Location which is the passed-in OpenLayers.LonLat,
+ * translated into overview map pixels
+ */
+ getOverviewPxFromLonLat: function(lonlat) {
+ var res = this.ovmap.getResolution();
+ var extent = this.ovmap.getExtent();
+ if (extent) {
+ return {
+ x: Math.round(1/res * (lonlat.lon - extent.left)),
+ y: Math.round(1/res * (extent.top - lonlat.lat))
+ };
+ }
+ },
+
+ CLASS_NAME: 'OpenLayers.Control.OverviewMap'
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Pan.js b/misc/openlayers/lib/OpenLayers/Control/Pan.js
new file mode 100644
index 0000000..d7fcc07
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Pan.js
@@ -0,0 +1,95 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Pan
+ * The Pan control is a single button to pan the map in one direction. For
+ * a more complete control see <OpenLayers.Control.PanPanel>.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Pan = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * APIProperty: slideFactor
+ * {Integer} Number of pixels by which we'll pan the map in any direction
+ * on clicking the arrow buttons, defaults to 50. If you want to pan
+ * by some ratio of the map dimensions, use <slideRatio> instead.
+ */
+ slideFactor: 50,
+
+ /**
+ * APIProperty: slideRatio
+ * {Number} The fraction of map width/height by which we'll pan the map
+ * on clicking the arrow buttons. Default is null. If set, will
+ * override <slideFactor>. E.g. if slideRatio is .5, then Pan Up will
+ * pan up half the map height.
+ */
+ slideRatio: null,
+
+ /**
+ * Property: direction
+ * {String} in {'North', 'South', 'East', 'West'}
+ */
+ direction: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Pan
+ * Control which handles the panning (in any of the cardinal directions)
+ * of the map by a set px distance.
+ *
+ * Parameters:
+ * direction - {String} The direction this button should pan.
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(direction, options) {
+
+ this.direction = direction;
+ this.CLASS_NAME += this.direction;
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: trigger
+ */
+ trigger: function(){
+ if (this.map) {
+ var getSlideFactor = OpenLayers.Function.bind(function (dim) {
+ return this.slideRatio ?
+ this.map.getSize()[dim] * this.slideRatio :
+ this.slideFactor;
+ }, this);
+
+ switch (this.direction) {
+ case OpenLayers.Control.Pan.NORTH:
+ this.map.pan(0, -getSlideFactor("h"));
+ break;
+ case OpenLayers.Control.Pan.SOUTH:
+ this.map.pan(0, getSlideFactor("h"));
+ break;
+ case OpenLayers.Control.Pan.WEST:
+ this.map.pan(-getSlideFactor("w"), 0);
+ break;
+ case OpenLayers.Control.Pan.EAST:
+ this.map.pan(getSlideFactor("w"), 0);
+ break;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Pan"
+});
+
+OpenLayers.Control.Pan.NORTH = "North";
+OpenLayers.Control.Pan.SOUTH = "South";
+OpenLayers.Control.Pan.EAST = "East";
+OpenLayers.Control.Pan.WEST = "West";
diff --git a/misc/openlayers/lib/OpenLayers/Control/PanPanel.js b/misc/openlayers/lib/OpenLayers/Control/PanPanel.js
new file mode 100644
index 0000000..eeedbd0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/PanPanel.js
@@ -0,0 +1,73 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Pan.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanPanel
+ * The PanPanel is visible control for panning the map North, South, East or
+ * West in small steps. By default it is drawn in the top left corner of the
+ * map.
+ *
+ * Note:
+ * If you wish to use this class with the default images and you want
+ * it to look nice in ie6, you should add the following, conditionally
+ * added css stylesheet to your HTML file:
+ *
+ * (code)
+ * <!--[if lte IE 6]>
+ * <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ * <![endif]-->
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.PanPanel = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * APIProperty: slideFactor
+ * {Integer} Number of pixels by which we'll pan the map in any direction
+ * on clicking the arrow buttons, defaults to 50. If you want to pan
+ * by some ratio of the map dimensions, use <slideRatio> instead.
+ */
+ slideFactor: 50,
+
+ /**
+ * APIProperty: slideRatio
+ * {Number} The fraction of map width/height by which we'll pan the map
+ * on clicking the arrow buttons. Default is null. If set, will
+ * override <slideFactor>. E.g. if slideRatio is .5, then Pan Up will
+ * pan up half the map height.
+ */
+ slideRatio: null,
+
+ /**
+ * Constructor: OpenLayers.Control.PanPanel
+ * Add the four directional pan buttons.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ var options = {
+ slideFactor: this.slideFactor,
+ slideRatio: this.slideRatio
+ };
+ this.addControls([
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH, options),
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH, options),
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST, options),
+ new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST, options)
+ ]);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PanPanel"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/PanZoom.js b/misc/openlayers/lib/OpenLayers/Control/PanZoom.js
new file mode 100644
index 0000000..dd007cf
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/PanZoom.js
@@ -0,0 +1,233 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanZoom
+ * The PanZoom is a visible control, composed of a
+ * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomPanel>. By
+ * default it is drawn in the upper left corner of the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.PanZoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: slideFactor
+ * {Integer} Number of pixels by which we'll pan the map in any direction
+ * on clicking the arrow buttons. If you want to pan by some ratio
+ * of the map dimensions, use <slideRatio> instead.
+ */
+ slideFactor: 50,
+
+ /**
+ * APIProperty: slideRatio
+ * {Number} The fraction of map width/height by which we'll pan the map
+ * on clicking the arrow buttons. Default is null. If set, will
+ * override <slideFactor>. E.g. if slideRatio is .5, then the Pan Up
+ * button will pan up half the map height.
+ */
+ slideRatio: null,
+
+ /**
+ * Property: buttons
+ * {Array(DOMElement)} Array of Button Divs
+ */
+ buttons: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>}
+ */
+ position: null,
+
+ /**
+ * Constructor: OpenLayers.Control.PanZoom
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ this.position = new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,
+ OpenLayers.Control.PanZoom.Y);
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onButtonClick);
+ }
+ this.removeButtons();
+ this.buttons = null;
+ this.position = null;
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Properties:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {DOMElement} A reference to the container div for the PanZoom control.
+ */
+ draw: function(px) {
+ // initialize our internal div
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ px = this.position;
+
+ // place the controls
+ this.buttons = [];
+
+ var sz = {w: 18, h: 18};
+ var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
+
+ this._addButton("panup", "north-mini.png", centered, sz);
+ px.y = centered.y+sz.h;
+ this._addButton("panleft", "west-mini.png", px, sz);
+ this._addButton("panright", "east-mini.png", px.add(sz.w, 0), sz);
+ this._addButton("pandown", "south-mini.png",
+ centered.add(0, sz.h*2), sz);
+ this._addButton("zoomin", "zoom-plus-mini.png",
+ centered.add(0, sz.h*3+5), sz);
+ this._addButton("zoomworld", "zoom-world-mini.png",
+ centered.add(0, sz.h*4+5), sz);
+ this._addButton("zoomout", "zoom-minus-mini.png",
+ centered.add(0, sz.h*5+5), sz);
+ return this.div;
+ },
+
+ /**
+ * Method: _addButton
+ *
+ * Parameters:
+ * id - {String}
+ * img - {String}
+ * xy - {<OpenLayers.Pixel>}
+ * sz - {<OpenLayers.Size>}
+ *
+ * Returns:
+ * {DOMElement} A Div (an alphaImageDiv, to be precise) that contains the
+ * image of the button, and has all the proper event handlers set.
+ */
+ _addButton:function(id, img, xy, sz) {
+ var imgLocation = OpenLayers.Util.getImageLocation(img);
+ var btn = OpenLayers.Util.createAlphaImageDiv(
+ this.id + "_" + id,
+ xy, sz, imgLocation, "absolute");
+ btn.style.cursor = "pointer";
+ //we want to add the outer div
+ this.div.appendChild(btn);
+ btn.action = id;
+ btn.className = "olButton";
+
+ //we want to remember/reference the outer div
+ this.buttons.push(btn);
+ return btn;
+ },
+
+ /**
+ * Method: _removeButton
+ *
+ * Parameters:
+ * btn - {Object}
+ */
+ _removeButton: function(btn) {
+ this.div.removeChild(btn);
+ OpenLayers.Util.removeItem(this.buttons, btn);
+ },
+
+ /**
+ * Method: removeButtons
+ */
+ removeButtons: function() {
+ for(var i=this.buttons.length-1; i>=0; --i) {
+ this._removeButton(this.buttons[i]);
+ }
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ var btn = evt.buttonElement;
+ switch (btn.action) {
+ case "panup":
+ this.map.pan(0, -this.getSlideFactor("h"));
+ break;
+ case "pandown":
+ this.map.pan(0, this.getSlideFactor("h"));
+ break;
+ case "panleft":
+ this.map.pan(-this.getSlideFactor("w"), 0);
+ break;
+ case "panright":
+ this.map.pan(this.getSlideFactor("w"), 0);
+ break;
+ case "zoomin":
+ this.map.zoomIn();
+ break;
+ case "zoomout":
+ this.map.zoomOut();
+ break;
+ case "zoomworld":
+ this.map.zoomToMaxExtent();
+ break;
+ }
+ },
+
+ /**
+ * Method: getSlideFactor
+ *
+ * Parameters:
+ * dim - {String} "w" or "h" (for width or height).
+ *
+ * Returns:
+ * {Number} The slide factor for panning in the requested direction.
+ */
+ getSlideFactor: function(dim) {
+ return this.slideRatio ?
+ this.map.getSize()[dim] * this.slideRatio :
+ this.slideFactor;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PanZoom"
+});
+
+/**
+ * Constant: X
+ * {Integer}
+ */
+OpenLayers.Control.PanZoom.X = 4;
+
+/**
+ * Constant: Y
+ * {Integer}
+ */
+OpenLayers.Control.PanZoom.Y = 4;
diff --git a/misc/openlayers/lib/OpenLayers/Control/PanZoomBar.js b/misc/openlayers/lib/OpenLayers/Control/PanZoomBar.js
new file mode 100644
index 0000000..ebf2964
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/PanZoomBar.js
@@ -0,0 +1,408 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control/PanZoom.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanZoomBar
+ * The PanZoomBar is a visible control composed of a
+ * <OpenLayers.Control.PanPanel> and a <OpenLayers.Control.ZoomBar>.
+ * By default it is displayed in the upper left corner of the map as 4
+ * directional arrows above a vertical slider.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.PanZoom>
+ */
+OpenLayers.Control.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoom, {
+
+ /**
+ * APIProperty: zoomStopWidth
+ */
+ zoomStopWidth: 18,
+
+ /**
+ * APIProperty: zoomStopHeight
+ */
+ zoomStopHeight: 11,
+
+ /**
+ * Property: slider
+ */
+ slider: null,
+
+ /**
+ * Property: sliderEvents
+ * {<OpenLayers.Events>}
+ */
+ sliderEvents: null,
+
+ /**
+ * Property: zoombarDiv
+ * {DOMElement}
+ */
+ zoombarDiv: null,
+
+ /**
+ * APIProperty: zoomWorldIcon
+ * {Boolean}
+ */
+ zoomWorldIcon: false,
+
+ /**
+ * APIProperty: panIcons
+ * {Boolean} Set this property to false not to display the pan icons. If
+ * false the zoom world icon is placed under the zoom bar. Defaults to
+ * true.
+ */
+ panIcons: true,
+
+ /**
+ * APIProperty: forceFixedZoomLevel
+ * {Boolean} Force a fixed zoom level even though the map has
+ * fractionalZoom
+ */
+ forceFixedZoomLevel: false,
+
+ /**
+ * Property: mouseDragStart
+ * {<OpenLayers.Pixel>}
+ */
+ mouseDragStart: null,
+
+ /**
+ * Property: deltaY
+ * {Number} The cumulative vertical pixel offset during a zoom bar drag.
+ */
+ deltaY: null,
+
+ /**
+ * Property: zoomStart
+ * {<OpenLayers.Pixel>}
+ */
+ zoomStart: null,
+
+ /**
+ * Constructor: OpenLayers.Control.PanZoomBar
+ */
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+
+ this._removeZoomBar();
+
+ this.map.events.un({
+ "changebaselayer": this.redraw,
+ "updatesize": this.redraw,
+ scope: this
+ });
+
+ OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments);
+
+ delete this.mouseDragStart;
+ delete this.zoomStart;
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments);
+ this.map.events.on({
+ "changebaselayer": this.redraw,
+ "updatesize": this.redraw,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: redraw
+ * clear the div and start over.
+ */
+ redraw: function() {
+ if (this.div != null) {
+ this.removeButtons();
+ this._removeZoomBar();
+ }
+ this.draw();
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ draw: function(px) {
+ // initialize our internal div
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ px = this.position.clone();
+
+ // place the controls
+ this.buttons = [];
+
+ var sz = {w: 18, h: 18};
+ if (this.panIcons) {
+ var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
+ var wposition = sz.w;
+
+ if (this.zoomWorldIcon) {
+ centered = new OpenLayers.Pixel(px.x+sz.w, px.y);
+ }
+
+ this._addButton("panup", "north-mini.png", centered, sz);
+ px.y = centered.y+sz.h;
+ this._addButton("panleft", "west-mini.png", px, sz);
+ if (this.zoomWorldIcon) {
+ this._addButton("zoomworld", "zoom-world-mini.png", px.add(sz.w, 0), sz);
+
+ wposition *= 2;
+ }
+ this._addButton("panright", "east-mini.png", px.add(wposition, 0), sz);
+ this._addButton("pandown", "south-mini.png", centered.add(0, sz.h*2), sz);
+ this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, sz.h*3+5), sz);
+ centered = this._addZoomBar(centered.add(0, sz.h*4 + 5));
+ this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
+ }
+ else {
+ this._addButton("zoomin", "zoom-plus-mini.png", px, sz);
+ centered = this._addZoomBar(px.add(0, sz.h));
+ this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
+ if (this.zoomWorldIcon) {
+ centered = centered.add(0, sz.h+3);
+ this._addButton("zoomworld", "zoom-world-mini.png", centered, sz);
+ }
+ }
+ return this.div;
+ },
+
+ /**
+ * Method: _addZoomBar
+ *
+ * Parameters:
+ * centered - {<OpenLayers.Pixel>} where zoombar drawing is to start.
+ */
+ _addZoomBar:function(centered) {
+ var imgLocation = OpenLayers.Util.getImageLocation("slider.png");
+ var id = this.id + "_" + this.map.id;
+ var minZoom = this.map.getMinZoom();
+ var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom();
+ var slider = OpenLayers.Util.createAlphaImageDiv(id,
+ centered.add(-1, zoomsToEnd * this.zoomStopHeight),
+ {w: 20, h: 9},
+ imgLocation,
+ "absolute");
+ slider.style.cursor = "move";
+ this.slider = slider;
+
+ this.sliderEvents = new OpenLayers.Events(this, slider, null, true,
+ {includeXY: true});
+ this.sliderEvents.on({
+ "touchstart": this.zoomBarDown,
+ "touchmove": this.zoomBarDrag,
+ "touchend": this.zoomBarUp,
+ "mousedown": this.zoomBarDown,
+ "mousemove": this.zoomBarDrag,
+ "mouseup": this.zoomBarUp
+ });
+
+ var sz = {
+ w: this.zoomStopWidth,
+ h: this.zoomStopHeight * (this.map.getNumZoomLevels() - minZoom)
+ };
+ var imgLocation = OpenLayers.Util.getImageLocation("zoombar.png");
+ var div = null;
+
+ if (OpenLayers.Util.alphaHack()) {
+ var id = this.id + "_" + this.map.id;
+ div = OpenLayers.Util.createAlphaImageDiv(id, centered,
+ {w: sz.w, h: this.zoomStopHeight},
+ imgLocation,
+ "absolute", null, "crop");
+ div.style.height = sz.h + "px";
+ } else {
+ div = OpenLayers.Util.createDiv(
+ 'OpenLayers_Control_PanZoomBar_Zoombar' + this.map.id,
+ centered,
+ sz,
+ imgLocation);
+ }
+ div.style.cursor = "pointer";
+ div.className = "olButton";
+ this.zoombarDiv = div;
+
+ this.div.appendChild(div);
+
+ this.startTop = parseInt(div.style.top);
+ this.div.appendChild(slider);
+
+ this.map.events.register("zoomend", this, this.moveZoomBar);
+
+ centered = centered.add(0,
+ this.zoomStopHeight * (this.map.getNumZoomLevels() - minZoom));
+ return centered;
+ },
+
+ /**
+ * Method: _removeZoomBar
+ */
+ _removeZoomBar: function() {
+ this.sliderEvents.un({
+ "touchstart": this.zoomBarDown,
+ "touchmove": this.zoomBarDrag,
+ "touchend": this.zoomBarUp,
+ "mousedown": this.zoomBarDown,
+ "mousemove": this.zoomBarDrag,
+ "mouseup": this.zoomBarUp
+ });
+ this.sliderEvents.destroy();
+
+ this.div.removeChild(this.zoombarDiv);
+ this.zoombarDiv = null;
+ this.div.removeChild(this.slider);
+ this.slider = null;
+
+ this.map.events.unregister("zoomend", this, this.moveZoomBar);
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function(evt) {
+ OpenLayers.Control.PanZoom.prototype.onButtonClick.apply(this, arguments);
+ if (evt.buttonElement === this.zoombarDiv) {
+ var levels = evt.buttonXY.y / this.zoomStopHeight;
+ if(this.forceFixedZoomLevel || !this.map.fractionalZoom) {
+ levels = Math.floor(levels);
+ }
+ var zoom = (this.map.getNumZoomLevels() - 1) - levels;
+ zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1);
+ this.map.zoomTo(zoom);
+ }
+ },
+
+ /**
+ * Method: passEventToSlider
+ * This function is used to pass events that happen on the div, or the map,
+ * through to the slider, which then does its moving thing.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ passEventToSlider:function(evt) {
+ this.sliderEvents.handleBrowserEvent(evt);
+ },
+
+ /*
+ * Method: zoomBarDown
+ * event listener for clicks on the slider
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ zoomBarDown:function(evt) {
+ if (!OpenLayers.Event.isLeftClick(evt) && !OpenLayers.Event.isSingleTouch(evt)) {
+ return;
+ }
+ this.map.events.on({
+ "touchmove": this.passEventToSlider,
+ "mousemove": this.passEventToSlider,
+ "mouseup": this.passEventToSlider,
+ scope: this
+ });
+ this.mouseDragStart = evt.xy.clone();
+ this.zoomStart = evt.xy.clone();
+ this.div.style.cursor = "move";
+ // reset the div offsets just in case the div moved
+ this.zoombarDiv.offsets = null;
+ OpenLayers.Event.stop(evt);
+ },
+
+ /*
+ * Method: zoomBarDrag
+ * This is what happens when a click has occurred, and the client is
+ * dragging. Here we must ensure that the slider doesn't go beyond the
+ * bottom/top of the zoombar div, as well as moving the slider to its new
+ * visual location
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ zoomBarDrag:function(evt) {
+ if (this.mouseDragStart != null) {
+ var deltaY = this.mouseDragStart.y - evt.xy.y;
+ var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv);
+ if ((evt.clientY - offsets[1]) > 0 &&
+ (evt.clientY - offsets[1]) < parseInt(this.zoombarDiv.style.height) - 2) {
+ var newTop = parseInt(this.slider.style.top) - deltaY;
+ this.slider.style.top = newTop+"px";
+ this.mouseDragStart = evt.xy.clone();
+ }
+ // set cumulative displacement
+ this.deltaY = this.zoomStart.y - evt.xy.y;
+ OpenLayers.Event.stop(evt);
+ }
+ },
+
+ /*
+ * Method: zoomBarUp
+ * Perform cleanup when a mouseup event is received -- discover new zoom
+ * level and switch to it.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ zoomBarUp:function(evt) {
+ if (!OpenLayers.Event.isLeftClick(evt) && evt.type !== "touchend") {
+ return;
+ }
+ if (this.mouseDragStart) {
+ this.div.style.cursor="";
+ this.map.events.un({
+ "touchmove": this.passEventToSlider,
+ "mouseup": this.passEventToSlider,
+ "mousemove": this.passEventToSlider,
+ scope: this
+ });
+ var zoomLevel = this.map.zoom;
+ if (!this.forceFixedZoomLevel && this.map.fractionalZoom) {
+ zoomLevel += this.deltaY/this.zoomStopHeight;
+ zoomLevel = Math.min(Math.max(zoomLevel, 0),
+ this.map.getNumZoomLevels() - 1);
+ } else {
+ zoomLevel += this.deltaY/this.zoomStopHeight;
+ zoomLevel = Math.max(Math.round(zoomLevel), 0);
+ }
+ this.map.zoomTo(zoomLevel);
+ this.mouseDragStart = null;
+ this.zoomStart = null;
+ this.deltaY = 0;
+ OpenLayers.Event.stop(evt);
+ }
+ },
+
+ /*
+ * Method: moveZoomBar
+ * Change the location of the slider to match the current zoom level.
+ */
+ moveZoomBar:function() {
+ var newTop =
+ ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) *
+ this.zoomStopHeight + this.startTop + 1;
+ this.slider.style.top = newTop + "px";
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PanZoomBar"
+}); \ No newline at end of file
diff --git a/misc/openlayers/lib/OpenLayers/Control/Panel.js b/misc/openlayers/lib/OpenLayers/Control/Panel.js
new file mode 100644
index 0000000..150afa7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Panel.js
@@ -0,0 +1,431 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ * The Panel control is a container for other controls. With it toolbars
+ * may be composed.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: controls
+ * {Array(<OpenLayers.Control>)}
+ */
+ controls: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: defaultControl
+ * {<OpenLayers.Control>} The control which is activated when the control is
+ * activated (turned on), which also happens at instantiation.
+ * If <saveState> is true, <defaultControl> will be nullified after the
+ * first activation of the panel.
+ */
+ defaultControl: null,
+
+ /**
+ * APIProperty: saveState
+ * {Boolean} If set to true, the active state of this panel's controls will
+ * be stored on panel deactivation, and restored on reactivation. Default
+ * is false.
+ */
+ saveState: false,
+
+ /**
+ * APIProperty: allowDepress
+ * {Boolean} If is true the <OpenLayers.Control.TYPE_TOOL> controls can
+ * be deactivated by clicking the icon that represents them. Default
+ * is false.
+ */
+ allowDepress: false,
+
+ /**
+ * Property: activeState
+ * {Object} stores the active state of this panel's controls.
+ */
+ activeState: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Panel
+ * Create a new control panel.
+ *
+ * Each control in the panel is represented by an icon. When clicking
+ * on an icon, the <activateControl> method is called.
+ *
+ * Specific properties for controls on a panel:
+ * type - {Number} One of <OpenLayers.Control.TYPE_TOOL>,
+ * <OpenLayers.Control.TYPE_TOGGLE>, <OpenLayers.Control.TYPE_BUTTON>.
+ * If not provided, <OpenLayers.Control.TYPE_TOOL> is assumed.
+ * title - {string} Text displayed when mouse is over the icon that
+ * represents the control.
+ *
+ * The <OpenLayers.Control.type> of a control determines the behavior when
+ * clicking its icon:
+ * <OpenLayers.Control.TYPE_TOOL> - The control is activated and other
+ * controls of this type in the same panel are deactivated. This is
+ * the default type.
+ * <OpenLayers.Control.TYPE_TOGGLE> - The active state of the control is
+ * toggled.
+ * <OpenLayers.Control.TYPE_BUTTON> - The
+ * <OpenLayers.Control.Button.trigger> method of the control is called,
+ * but its active state is not changed.
+ *
+ * If a control is <OpenLayers.Control.active>, it will be drawn with the
+ * olControl[Name]ItemActive class, otherwise with the
+ * olControl[Name]ItemInactive class.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.controls = [];
+ this.activeState = {};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onButtonClick);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ for (var ctl, i = this.controls.length - 1; i >= 0; i--) {
+ ctl = this.controls[i];
+ if (ctl.events) {
+ ctl.events.un({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ ctl.panel_div = null;
+ }
+ this.activeState = null;
+ },
+
+ /**
+ * APIMethod: activate
+ */
+ activate: function() {
+ if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ if (control === this.defaultControl ||
+ (this.saveState && this.activeState[control.id])) {
+ control.activate();
+ }
+ }
+ if (this.saveState === true) {
+ this.defaultControl = null;
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * APIMethod: deactivate
+ */
+ deactivate: function() {
+ if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ var control;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ control = this.controls[i];
+ this.activeState[control.id] = control.deactivate();
+ }
+ this.redraw();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (this.outsideViewport) {
+ this.events.attachToElement(this.div);
+ this.events.register("buttonclick", this, this.onButtonClick);
+ } else {
+ this.map.events.register("buttonclick", this, this.onButtonClick);
+ }
+ this.addControlsToMap(this.controls);
+ return this.div;
+ },
+
+ /**
+ * Method: redraw
+ */
+ redraw: function() {
+ for (var l=this.div.childNodes.length, i=l-1; i>=0; i--) {
+ this.div.removeChild(this.div.childNodes[i]);
+ }
+ this.div.innerHTML = "";
+ if (this.active) {
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ this.div.appendChild(this.controls[i].panel_div);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: activateControl
+ * This method is called when the user click on the icon representing a
+ * control in the panel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ */
+ activateControl: function (control) {
+ if (!this.active) { return false; }
+ if (control.type == OpenLayers.Control.TYPE_BUTTON) {
+ control.trigger();
+ return;
+ }
+ if (control.type == OpenLayers.Control.TYPE_TOGGLE) {
+ if (control.active) {
+ control.deactivate();
+ } else {
+ control.activate();
+ }
+ return;
+ }
+ if (this.allowDepress && control.active) {
+ control.deactivate();
+ } else {
+ var c;
+ for (var i=0, len=this.controls.length; i<len; i++) {
+ c = this.controls[i];
+ if (c != control &&
+ (c.type === OpenLayers.Control.TYPE_TOOL || c.type == null)) {
+ c.deactivate();
+ }
+ }
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: addControls
+ * To build a toolbar, you add a set of controls to it. addControls
+ * lets you add a single control or a list of controls to the
+ * Control Panel.
+ *
+ * Parameters:
+ * controls - {<OpenLayers.Control>} Controls to add in the panel.
+ */
+ addControls: function(controls) {
+ if (!(OpenLayers.Util.isArray(controls))) {
+ controls = [controls];
+ }
+ this.controls = this.controls.concat(controls);
+
+ for (var i=0, len=controls.length; i<len; i++) {
+ var control = controls[i],
+ element = this.createControlMarkup(control);
+ OpenLayers.Element.addClass(element,
+ control.displayClass + "ItemInactive");
+ OpenLayers.Element.addClass(element, "olButton");
+ if (control.title != "" && !element.title) {
+ element.title = control.title;
+ }
+ control.panel_div = element;
+ }
+
+ if (this.map) { // map.addControl() has already been called on the panel
+ this.addControlsToMap(controls);
+ this.redraw();
+ }
+ },
+
+ /**
+ * APIMethod: createControlMarkup
+ * This function just creates a div for the control. If specific HTML
+ * markup is needed this function can be overridden in specific classes,
+ * or at panel instantiation time:
+ *
+ * Example:
+ * (code)
+ * var panel = new OpenLayers.Control.Panel({
+ * defaultControl: control,
+ * // ovverride createControlMarkup to create actual buttons
+ * // including texts wrapped into span elements.
+ * createControlMarkup: function(control) {
+ * var button = document.createElement('button'),
+ * span = document.createElement('span');
+ * if (control.text) {
+ * span.innerHTML = control.text;
+ * }
+ * return button;
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to create the HTML
+ * markup for.
+ *
+ * Returns:
+ * {DOMElement} The markup.
+ */
+ createControlMarkup: function(control) {
+ return document.createElement("div");
+ },
+
+ /**
+ * Method: addControlsToMap
+ * Only for internal use in draw() and addControls() methods.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)} Controls to add into map.
+ */
+ addControlsToMap: function (controls) {
+ var control;
+ for (var i=0, len=controls.length; i<len; i++) {
+ control = controls[i];
+ if (control.autoActivate === true) {
+ control.autoActivate = false;
+ this.map.addControl(control);
+ control.autoActivate = true;
+ } else {
+ this.map.addControl(control);
+ control.deactivate();
+ }
+ control.events.on({
+ activate: this.iconOn,
+ deactivate: this.iconOff
+ });
+ }
+ },
+
+ /**
+ * Method: iconOn
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOn: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Inactive\\b");
+ d.className = d.className.replace(re, "$1Active");
+ },
+
+ /**
+ * Method: iconOff
+ * Internal use, for use only with "controls[i].events.on/un".
+ */
+ iconOff: function() {
+ var d = this.panel_div; // "this" refers to a control on panel!
+ var re = new RegExp("\\b(" + this.displayClass + "Item)Active\\b");
+ d.className = d.className.replace(re, "$1Inactive");
+ },
+
+ /**
+ * Method: onButtonClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onButtonClick: function (evt) {
+ var controls = this.controls,
+ button = evt.buttonElement;
+ for (var i=controls.length-1; i>=0; --i) {
+ if (controls[i].panel_div === button) {
+ this.activateControl(controls[i]);
+ break;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(control[property]) evaluates to true, the control will be
+ * included in the array returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this.controls, function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getControlsByName
+ * Get a list of contorls with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A control name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(control.name) evaluates to true, the control will be included
+ * in the list of controls returned. If no controls are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByName: function(match) {
+ return this.getControlsBy("name", match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given type (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The type can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given type.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Control/Permalink.js b/misc/openlayers/lib/OpenLayers/Control/Permalink.js
new file mode 100644
index 0000000..3d5d7a2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Permalink.js
@@ -0,0 +1,257 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/ArgParser.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Permalink
+ * The Permalink control is hyperlink that will return the user to the
+ * current map view. By default it is drawn in the lower right corner of the
+ * map. The href is updated as the map is zoomed, panned and whilst layers
+ * are switched.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: argParserClass
+ * {Class} The ArgParser control class (not instance) to use with this
+ * control.
+ */
+ argParserClass: OpenLayers.Control.ArgParser,
+
+ /**
+ * Property: element
+ * {DOMElement}
+ */
+ element: null,
+
+ /**
+ * APIProperty: anchor
+ * {Boolean} This option changes 3 things:
+ * the character '#' is used in place of the character '?',
+ * the window.href is updated if no element is provided.
+ * When this option is set to true it's not recommend to provide
+ * a base without provide an element.
+ */
+ anchor: false,
+
+ /**
+ * APIProperty: base
+ * {String}
+ */
+ base: '',
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support. Projection used
+ * when creating the coordinates in the link. This will reproject the
+ * map coordinates into display coordinates. If you are using this
+ * functionality, the permalink which is last added to the map will
+ * determine the coordinate type which is read from the URL, which
+ * means you should not add permalinks with different
+ * displayProjections to the same map.
+ */
+ displayProjection: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Permalink
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * base - {String}
+ * options - {Object} options to the control.
+ *
+ * Or for anchor:
+ * options - {Object} options to the control.
+ */
+ initialize: function(element, base, options) {
+ if (element !== null && typeof element == 'object' && !OpenLayers.Util.isElement(element)) {
+ options = element;
+ this.base = document.location.href;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ if (this.element != null) {
+ this.element = OpenLayers.Util.getElement(this.element);
+ }
+ }
+ else {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.element = OpenLayers.Util.getElement(element);
+ this.base = base || document.location.href;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.element && this.element.parentNode == this.div) {
+ this.div.removeChild(this.element);
+ this.element = null;
+ }
+ if (this.map) {
+ this.map.events.unregister('moveend', this, this.updateLink);
+ }
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+ //make sure we have an arg parser attached
+ for(var i=0, len=this.map.controls.length; i<len; i++) {
+ var control = this.map.controls[i];
+ if (control.CLASS_NAME == this.argParserClass.CLASS_NAME) {
+
+ // If a permalink is added to the map, and an ArgParser already
+ // exists, we override the displayProjection to be the one
+ // on the permalink.
+ if (control.displayProjection != this.displayProjection) {
+ this.displayProjection = control.displayProjection;
+ }
+
+ break;
+ }
+ }
+ if (i == this.map.controls.length) {
+ this.map.addControl(new this.argParserClass(
+ { 'displayProjection': this.displayProjection }));
+ }
+
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+
+ if (!this.element && !this.anchor) {
+ this.element = document.createElement("a");
+ this.element.innerHTML = OpenLayers.i18n("Permalink");
+ this.element.href="";
+ this.div.appendChild(this.element);
+ }
+ this.map.events.on({
+ 'moveend': this.updateLink,
+ 'changelayer': this.updateLink,
+ 'changebaselayer': this.updateLink,
+ scope: this
+ });
+
+ // Make it so there is at least a link even though the map may not have
+ // moved yet.
+ this.updateLink();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateLink
+ */
+ updateLink: function() {
+ var separator = this.anchor ? '#' : '?';
+ var href = this.base;
+ var anchor = null;
+ if (href.indexOf("#") != -1 && this.anchor == false) {
+ anchor = href.substring( href.indexOf("#"), href.length);
+ }
+ if (href.indexOf(separator) != -1) {
+ href = href.substring( 0, href.indexOf(separator) );
+ }
+ var splits = href.split("#");
+ href = splits[0] + separator+ OpenLayers.Util.getParameterString(this.createParams());
+ if (anchor) {
+ href += anchor;
+ }
+ if (this.anchor && !this.element) {
+ window.location.href = href;
+ }
+ else {
+ this.element.href = href;
+ }
+ },
+
+ /**
+ * APIMethod: createParams
+ * Creates the parameters that need to be encoded into the permalink url.
+ *
+ * Parameters:
+ * center - {<OpenLayers.LonLat>} center to encode in the permalink.
+ * Defaults to the current map center.
+ * zoom - {Integer} zoom level to encode in the permalink. Defaults to the
+ * current map zoom level.
+ * layers - {Array(<OpenLayers.Layer>)} layers to encode in the permalink.
+ * Defaults to the current map layers.
+ *
+ * Returns:
+ * {Object} Hash of parameters that will be url-encoded into the
+ * permalink.
+ */
+ createParams: function(center, zoom, layers) {
+ center = center || this.map.getCenter();
+
+ var params = OpenLayers.Util.getParameters(this.base);
+
+ // If there's still no center, map is not initialized yet.
+ // Break out of this function, and simply return the params from the
+ // base link.
+ if (center) {
+
+ //zoom
+ params.zoom = zoom || this.map.getZoom();
+
+ //lon,lat
+ var lat = center.lat;
+ var lon = center.lon;
+
+ if (this.displayProjection) {
+ var mapPosition = OpenLayers.Projection.transform(
+ { x: lon, y: lat },
+ this.map.getProjectionObject(),
+ this.displayProjection );
+ lon = mapPosition.x;
+ lat = mapPosition.y;
+ }
+ params.lat = Math.round(lat*100000)/100000;
+ params.lon = Math.round(lon*100000)/100000;
+
+ //layers
+ layers = layers || this.map.layers;
+ params.layers = '';
+ for (var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+
+ if (layer.isBaseLayer) {
+ params.layers += (layer == this.map.baseLayer) ? "B" : "0";
+ } else {
+ params.layers += (layer.getVisibility()) ? "T" : "F";
+ }
+ }
+ }
+
+ return params;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Permalink"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/PinchZoom.js b/misc/openlayers/lib/OpenLayers/Control/PinchZoom.js
new file mode 100644
index 0000000..13c1104
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/PinchZoom.js
@@ -0,0 +1,157 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler/Pinch.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PinchZoom
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.PinchZoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPES}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: pinchOrigin
+ * {Object} Cached object representing the pinch start (in pixels).
+ */
+ pinchOrigin: null,
+
+ /**
+ * Property: currentCenter
+ * {Object} Cached object representing the latest pinch center (in pixels).
+ */
+ currentCenter: null,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: preserveCenter
+ * {Boolean} Set this to true if you don't want the map center to change
+ * while pinching. For example you may want to set preserveCenter to
+ * true when the user location is being watched and you want to preserve
+ * the user location at the center of the map even if he zooms in or
+ * out using pinch. This property's value can be changed any time on an
+ * existing instance. Default is false.
+ */
+ preserveCenter: false,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the pinch handler
+ */
+
+ /**
+ * Constructor: OpenLayers.Control.PinchZoom
+ * Create a control for zooming with pinch gestures. This works on devices
+ * with multi-touch support.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.handler = new OpenLayers.Handler.Pinch(this, {
+ start: this.pinchStart,
+ move: this.pinchMove,
+ done: this.pinchDone
+ }, this.handlerOptions);
+ },
+
+ /**
+ * Method: pinchStart
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchStart: function(evt, pinchData) {
+ var xy = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+ this.pinchOrigin = xy;
+ this.currentCenter = xy;
+ },
+
+ /**
+ * Method: pinchMove
+ *
+ * Parameters:
+ * evt - {Event}
+ * pinchData - {Object} pinch data object related to the current touchmove
+ * of the pinch gesture. This give us the current scale of the pinch.
+ */
+ pinchMove: function(evt, pinchData) {
+ var scale = pinchData.scale;
+ var containerOrigin = this.map.layerContainerOriginPx;
+ var pinchOrigin = this.pinchOrigin;
+ var current = (this.preserveCenter) ?
+ this.map.getPixelFromLonLat(this.map.getCenter()) : evt.xy;
+
+ var dx = Math.round((containerOrigin.x + current.x - pinchOrigin.x) + (scale - 1) * (containerOrigin.x - pinchOrigin.x));
+ var dy = Math.round((containerOrigin.y + current.y - pinchOrigin.y) + (scale - 1) * (containerOrigin.y - pinchOrigin.y));
+
+ this.map.applyTransform(dx, dy, scale);
+ this.currentCenter = current;
+ },
+
+ /**
+ * Method: pinchDone
+ *
+ * Parameters:
+ * evt - {Event}
+ * start - {Object} pinch data object related to the touchstart event that
+ * started the pinch gesture.
+ * last - {Object} pinch data object related to the last touchmove event
+ * of the pinch gesture. This give us the final scale of the pinch.
+ */
+ pinchDone: function(evt, start, last) {
+ this.map.applyTransform();
+ var zoom = this.map.getZoomForResolution(this.map.getResolution() / last.scale, true);
+ if (zoom !== this.map.getZoom() || !this.currentCenter.equals(this.pinchOrigin)) {
+ var resolution = this.map.getResolutionForZoom(zoom);
+
+ var location = this.map.getLonLatFromPixel(this.pinchOrigin);
+ var zoomPixel = this.currentCenter;
+ var size = this.map.getSize();
+
+ location.lon += resolution * ((size.w / 2) - zoomPixel.x);
+ location.lat -= resolution * ((size.h / 2) - zoomPixel.y);
+
+ // Force a reflow before calling setCenter. This is to work
+ // around an issue occuring in iOS.
+ //
+ // See https://github.com/openlayers/openlayers/pull/351.
+ //
+ // Without a reflow setting the layer container div's top left
+ // style properties to "0px" - as done in Map.moveTo when zoom
+ // is changed - won't actually correctly reposition the layer
+ // container div.
+ //
+ // Also, we need to use a statement that the Google Closure
+ // compiler won't optimize away.
+ this.map.div.clientWidth = this.map.div.clientWidth;
+
+ this.map.setCenter(location, zoom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.PinchZoom"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/SLDSelect.js b/misc/openlayers/lib/OpenLayers/Control/SLDSelect.js
new file mode 100644
index 0000000..cd348a7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/SLDSelect.js
@@ -0,0 +1,567 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Layer/WMS.js
+ * @requires OpenLayers/Handler/RegularPolygon.js
+ * @requires OpenLayers/Handler/Polygon.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Format/SLD/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SLDSelect
+ * Perform selections on WMS layers using Styled Layer Descriptor (SLD)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SLDSelect = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * selected - Triggered when a selection occurs. Listeners receive an
+ * event with *filters* and *layer* properties. Filters will be an
+ * array of OpenLayers.Filter objects created in order to perform
+ * the particular selection.
+ */
+
+ /**
+ * APIProperty: clearOnDeactivate
+ * {Boolean} Should the selection be cleared when the control is
+ * deactivated. Default value is false.
+ */
+ clearOnDeactivate: false,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.WMS>)} The WMS layers this control will work
+ * on.
+ */
+ layers: null,
+
+ /**
+ * Property: callbacks
+ * {Object} The functions that are sent to the handler for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectionSymbolizer
+ * {Object} Determines the styling of the selected objects. Default is
+ * a selection in red.
+ */
+ selectionSymbolizer: {
+ 'Polygon': {fillColor: '#FF0000', stroke: false},
+ 'Line': {strokeColor: '#FF0000', strokeWidth: 2},
+ 'Point': {graphicName: 'square', fillColor: '#FF0000', pointRadius: 5}
+ },
+
+ /**
+ * APIProperty: layerOptions
+ * {Object} The options to apply to the selection layer, by default the
+ * selection layer will be kept out of the layer switcher.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Used to set non-default properties on the control's handler
+ */
+
+ /**
+ * APIProperty: sketchStyle
+ * {<OpenLayers.Style>|Object} Style or symbolizer to use for the sketch
+ * handler. The recommended way of styling the sketch layer, however, is
+ * to configure an <OpenLayers.StyleMap> in the layerOptions of the
+ * <handlerOptions>:
+ *
+ * (code)
+ * new OpenLayers.Control.SLDSelect(OpenLayers.Handler.Path, {
+ * handlerOptions: {
+ * layerOptions: {
+ * styleMap: new OpenLayers.StyleMap({
+ * "default": {strokeColor: "yellow"}
+ * })
+ * }
+ * }
+ * });
+ * (end)
+ */
+ sketchStyle: null,
+
+ /**
+ * APIProperty: wfsCache
+ * {Object} Cache to use for storing parsed results from
+ * <OpenLayers.Format.WFSDescribeFeatureType.read>. If not provided,
+ * these will be cached on the prototype.
+ */
+ wfsCache: {},
+
+ /**
+ * APIProperty: layerCache
+ * {Object} Cache to use for storing references to the selection layers.
+ * Normally each source layer will have exactly 1 selection layer of
+ * type OpenLayers.Layer.WMS. If not provided, layers will
+ * be cached on the prototype. Note that if <clearOnDeactivate> is
+ * true, the layer will no longer be cached after deactivating the
+ * control.
+ */
+ layerCache: {},
+
+ /**
+ * Constructor: OpenLayers.Control.SLDSelect
+ * Create a new control for selecting features in WMS layers using
+ * Styled Layer Descriptor (SLD).
+ *
+ * Parameters:
+ * handler - {<OpenLayers.Class>} A sketch handler class. This determines
+ * the type of selection, e.g. box (<OpenLayers.Handler.Box>), point
+ * (<OpenLayers.Handler.Point>), path (<OpenLayers.Handler.Path>) or
+ * polygon (<OpenLayers.Handler.Polygon>) selection. To use circle
+ * type selection, use <OpenLayers.Handler.RegularPolygon> and pass
+ * the number of desired sides (e.g. 40) as "sides" property to the
+ * <handlerOptions>.
+ * options - {Object} An object containing all configuration properties for
+ * the control.
+ *
+ * Valid options:
+ * layers - Array({<OpenLayers.Layer.WMS>}) The layers to perform the
+ * selection on.
+ */
+ initialize: function(handler, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.callbacks = OpenLayers.Util.extend({done: this.select,
+ click: this.select}, this.callbacks);
+ this.handlerOptions = this.handlerOptions || {};
+ this.layerOptions = OpenLayers.Util.applyDefaults(this.layerOptions, {
+ displayInLayerSwitcher: false,
+ tileOptions: {maxGetUrlLength: 2048}
+ });
+ if (this.sketchStyle) {
+ this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults(
+ this.handlerOptions.layerOptions,
+ {styleMap: new OpenLayers.StyleMap({"default": this.sketchStyle})}
+ );
+ }
+ this.handler = new handler(this, this.callbacks, this.handlerOptions);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ for (var key in this.layerCache) {
+ delete this.layerCache[key];
+ }
+ for (var key in this.wfsCache) {
+ delete this.wfsCache[key];
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: coupleLayerVisiblity
+ * Couple the selection layer and the source layer with respect to
+ * layer visibility. So if the source layer is turned off, the
+ * selection layer is also turned off.
+ *
+ * Context:
+ * - {<OpenLayers.Layer>}
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ coupleLayerVisiblity: function(evt) {
+ this.setVisibility(evt.object.getVisibility());
+ },
+
+ /**
+ * Method: createSelectionLayer
+ * Creates a "clone" from the source layer in which the selection can
+ * be drawn. This ensures both the source layer and the selection are
+ * visible and not only the selection.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Layer.WMS>} The source layer on which the selection
+ * is performed.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} A WMS layer with maxGetUrlLength configured to 2048
+ * since SLD selections can easily get quite long.
+ */
+ createSelectionLayer: function(source) {
+ // check if we already have a selection layer for the source layer
+ var selectionLayer;
+ if (!this.layerCache[source.id]) {
+ selectionLayer = new OpenLayers.Layer.WMS(source.name,
+ source.url, source.params,
+ OpenLayers.Util.applyDefaults(
+ this.layerOptions,
+ source.getOptions())
+ );
+ this.layerCache[source.id] = selectionLayer;
+ // make sure the layers are coupled wrt visibility, but only
+ // if they are not displayed in the layer switcher, because in
+ // that case the user cannot control visibility.
+ if (this.layerOptions.displayInLayerSwitcher === false) {
+ source.events.on({
+ "visibilitychanged": this.coupleLayerVisiblity,
+ scope: selectionLayer});
+ }
+ this.map.addLayer(selectionLayer);
+ } else {
+ selectionLayer = this.layerCache[source.id];
+ }
+ return selectionLayer;
+ },
+
+ /**
+ * Method: createSLD
+ * Create the SLD document for the layer using the supplied filters.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>}
+ * filters - Array({<OpenLayers.Filter>}) The filters to be applied.
+ * geometryAttributes - Array({Object}) The geometry attributes of the
+ * layer.
+ *
+ * Returns:
+ * {String} The SLD document generated as a string.
+ */
+ createSLD: function(layer, filters, geometryAttributes) {
+ var sld = {version: "1.0.0", namedLayers: {}};
+ var layerNames = [layer.params.LAYERS].join(",").split(",");
+ for (var i=0, len=layerNames.length; i<len; i++) {
+ var name = layerNames[i];
+ sld.namedLayers[name] = {name: name, userStyles: []};
+ var symbolizer = this.selectionSymbolizer;
+ var geometryAttribute = geometryAttributes[i];
+ if (geometryAttribute.type.indexOf('Polygon') >= 0) {
+ symbolizer = {Polygon: this.selectionSymbolizer['Polygon']};
+ } else if (geometryAttribute.type.indexOf('LineString') >= 0) {
+ symbolizer = {Line: this.selectionSymbolizer['Line']};
+ } else if (geometryAttribute.type.indexOf('Point') >= 0) {
+ symbolizer = {Point: this.selectionSymbolizer['Point']};
+ }
+ var filter = filters[i];
+ sld.namedLayers[name].userStyles.push({name: 'default', rules: [
+ new OpenLayers.Rule({symbolizer: symbolizer,
+ filter: filter,
+ maxScaleDenominator: layer.options.minScale})
+ ]});
+ }
+ return new OpenLayers.Format.SLD({srsName: this.map.getProjection()}).write(sld);
+ },
+
+ /**
+ * Method: parseDescribeLayer
+ * Parse the SLD WMS DescribeLayer response and issue the corresponding
+ * WFS DescribeFeatureType request
+ *
+ * request - {XMLHttpRequest} The request object.
+ */
+ parseDescribeLayer: function(request) {
+ var format = new OpenLayers.Format.WMSDescribeLayer();
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var describeLayer = format.read(doc);
+ var typeNames = [];
+ var url = null;
+ for (var i=0, len=describeLayer.length; i<len; i++) {
+ // perform a WFS DescribeFeatureType request
+ if (describeLayer[i].owsType == "WFS") {
+ typeNames.push(describeLayer[i].typeName);
+ url = describeLayer[i].owsURL;
+ }
+ }
+ var options = {
+ url: url,
+ params: {
+ SERVICE: "WFS",
+ TYPENAME: typeNames.toString(),
+ REQUEST: "DescribeFeatureType",
+ VERSION: "1.0.0"
+ },
+ callback: function(request) {
+ var format = new OpenLayers.Format.WFSDescribeFeatureType();
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var describeFeatureType = format.read(doc);
+ this.control.wfsCache[this.layer.id] = describeFeatureType;
+ this.control._queue && this.control.applySelection();
+ },
+ scope: this
+ };
+ OpenLayers.Request.GET(options);
+ },
+
+ /**
+ * Method: getGeometryAttributes
+ * Look up the geometry attributes from the WFS DescribeFeatureType response
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} The layer for which to look up the
+ * geometry attributes.
+ *
+ * Returns:
+ * Array({Object}) Array of geometry attributes
+ */
+ getGeometryAttributes: function(layer) {
+ var result = [];
+ var cache = this.wfsCache[layer.id];
+ for (var i=0, len=cache.featureTypes.length; i<len; i++) {
+ var typeName = cache.featureTypes[i];
+ var properties = typeName.properties;
+ for (var j=0, lenj=properties.length; j < lenj; j++) {
+ var property = properties[j];
+ var type = property.type;
+ if ((type.indexOf('LineString') >= 0) ||
+ (type.indexOf('GeometryAssociationType') >=0) ||
+ (type.indexOf('GeometryPropertyType') >= 0) ||
+ (type.indexOf('Point') >= 0) ||
+ (type.indexOf('Polygon') >= 0) ) {
+ result.push(property);
+ }
+ }
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. Activating the control will perform a SLD WMS
+ * DescribeLayer request followed by a WFS DescribeFeatureType request
+ * so that the proper symbolizers can be chosen based on the geometry
+ * type.
+ */
+ activate: function() {
+ var activated = OpenLayers.Control.prototype.activate.call(this);
+ if(activated) {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer && !this.wfsCache[layer.id]) {
+ var options = {
+ url: layer.url,
+ params: {
+ SERVICE: "WMS",
+ VERSION: layer.params.VERSION,
+ LAYERS: layer.params.LAYERS,
+ REQUEST: "DescribeLayer"
+ },
+ callback: this.parseDescribeLayer,
+ scope: {layer: layer, control: this}
+ };
+ OpenLayers.Request.GET(options);
+ }
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. If clearOnDeactivate is true, remove the
+ * selection layer(s).
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
+ if(deactivated) {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer && this.clearOnDeactivate === true) {
+ var layerCache = this.layerCache;
+ var selectionLayer = layerCache[layer.id];
+ if (selectionLayer) {
+ layer.events.un({
+ "visibilitychanged": this.coupleLayerVisiblity,
+ scope: selectionLayer});
+ selectionLayer.destroy();
+ delete layerCache[layer.id];
+ }
+ }
+ }
+ }
+ return deactivated;
+ },
+
+ /**
+ * APIMethod: setLayers
+ * Set the layers on which the selection should be performed. Call the
+ * setLayers method if the layer(s) to be used change and the same
+ * control should be used on a new set of layers.
+ * If the control is already active, it will be active after the new
+ * set of layers is set.
+ *
+ * Parameters:
+ * layers - {Array(<OpenLayers.Layer.WMS>)} The new set of layers on which
+ * the selection should be performed.
+ */
+ setLayers: function(layers) {
+ if(this.active) {
+ this.deactivate();
+ this.layers = layers;
+ this.activate();
+ } else {
+ this.layers = layers;
+ }
+ },
+
+ /**
+ * Function: createFilter
+ * Create the filter to be used in the SLD.
+ *
+ * Parameters:
+ * geometryAttribute - {Object} Used to get the name of the geometry
+ * attribute which is needed for constructing the spatial filter.
+ * geometry - {<OpenLayers.Geometry>} The geometry to use.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} The spatial filter created.
+ */
+ createFilter: function(geometryAttribute, geometry) {
+ var filter = null;
+ if (this.handler instanceof OpenLayers.Handler.RegularPolygon) {
+ // box
+ if (this.handler.irregular === true) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ property: geometryAttribute.name,
+ value: geometry.getBounds()}
+ );
+ } else {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ }
+ } else if (this.handler instanceof OpenLayers.Handler.Polygon) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ } else if (this.handler instanceof OpenLayers.Handler.Path) {
+ // if source layer is point based, use DWITHIN instead
+ if (geometryAttribute.type.indexOf('Point') >= 0) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ property: geometryAttribute.name,
+ distance: this.map.getExtent().getWidth()*0.01 ,
+ distanceUnits: this.map.getUnits(),
+ value: geometry}
+ );
+ } else {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ }
+ } else if (this.handler instanceof OpenLayers.Handler.Click) {
+ if (geometryAttribute.type.indexOf('Polygon') >= 0) {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: geometryAttribute.name,
+ value: geometry}
+ );
+ } else {
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ property: geometryAttribute.name,
+ distance: this.map.getExtent().getWidth()*0.01 ,
+ distanceUnits: this.map.getUnits(),
+ value: geometry}
+ );
+ }
+ }
+ return filter;
+ },
+
+ /**
+ * Method: select
+ * When the handler is done, use SLD_BODY on the selection layer to
+ * display the selection in the map.
+ *
+ * Parameters:
+ * geometry - {Object} or {<OpenLayers.Geometry>}
+ */
+ select: function(geometry) {
+ this._queue = function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ var geometryAttributes = this.getGeometryAttributes(layer);
+ var filters = [];
+ for (var j=0, lenj=geometryAttributes.length; j<lenj; j++) {
+ var geometryAttribute = geometryAttributes[j];
+ if (geometryAttribute !== null) {
+ // from the click handler we will not get an actual
+ // geometry so transform
+ if (!(geometry instanceof OpenLayers.Geometry)) {
+ var point = this.map.getLonLatFromPixel(
+ geometry.xy);
+ geometry = new OpenLayers.Geometry.Point(
+ point.lon, point.lat);
+ }
+ var filter = this.createFilter(geometryAttribute,
+ geometry);
+ if (filter !== null) {
+ filters.push(filter);
+ }
+ }
+ }
+
+ var selectionLayer = this.createSelectionLayer(layer);
+
+ this.events.triggerEvent("selected", {
+ layer: layer,
+ filters: filters
+ });
+
+ var sld = this.createSLD(layer, filters, geometryAttributes);
+
+ selectionLayer.mergeNewParams({SLD_BODY: sld});
+ delete this._queue;
+ }
+ };
+ this.applySelection();
+ },
+
+ /**
+ * Method: applySelection
+ * Checks if all required wfs data is cached, and applies the selection
+ */
+ applySelection: function() {
+ var canApply = true;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ if(!this.wfsCache[this.layers[i].id]) {
+ canApply = false;
+ break;
+ }
+ }
+ canApply && this._queue.call(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SLDSelect"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Scale.js b/misc/openlayers/lib/OpenLayers/Control/Scale.js
new file mode 100644
index 0000000..c9f2d2b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Scale.js
@@ -0,0 +1,100 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Scale
+ * The Scale control displays the current map scale as a ratio (e.g. Scale =
+ * 1:1M). By default it is displayed in the lower right corner of the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Scale = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: element
+ * {DOMElement}
+ */
+ element: null,
+
+ /**
+ * APIProperty: geodesic
+ * {Boolean} Use geodesic measurement. Default is false. The recommended
+ * setting for maps in EPSG:4326 is false, and true EPSG:900913. If set to
+ * true, the scale will be calculated based on the horizontal size of the
+ * pixel in the center of the map viewport.
+ */
+ geodesic: false,
+
+ /**
+ * Constructor: OpenLayers.Control.Scale
+ *
+ * Parameters:
+ * element - {DOMElement}
+ * options - {Object}
+ */
+ initialize: function(element, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.element = OpenLayers.Util.getElement(element);
+ },
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (!this.element) {
+ this.element = document.createElement("div");
+ this.div.appendChild(this.element);
+ }
+ this.map.events.register( 'moveend', this, this.updateScale);
+ this.updateScale();
+ return this.div;
+ },
+
+ /**
+ * Method: updateScale
+ */
+ updateScale: function() {
+ var scale;
+ if(this.geodesic === true) {
+ var units = this.map.getUnits();
+ if(!units) {
+ return;
+ }
+ var inches = OpenLayers.INCHES_PER_UNIT;
+ scale = (this.map.getGeodesicPixelSize().w || 0.000001) *
+ inches["km"] * OpenLayers.DOTS_PER_INCH;
+ } else {
+ scale = this.map.getScale();
+ }
+
+ if (!scale) {
+ return;
+ }
+
+ if (scale >= 9500 && scale <= 950000) {
+ scale = Math.round(scale / 1000) + "K";
+ } else if (scale >= 950000) {
+ scale = Math.round(scale / 1000000) + "M";
+ } else {
+ scale = Math.round(scale);
+ }
+
+ this.element.innerHTML = OpenLayers.i18n("Scale = 1 : ${scaleDenom}", {'scaleDenom':scale});
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Scale"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Control/ScaleLine.js b/misc/openlayers/lib/OpenLayers/Control/ScaleLine.js
new file mode 100644
index 0000000..9262414
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ScaleLine.js
@@ -0,0 +1,220 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ScaleLine
+ * The ScaleLine displays a small line indicator representing the current
+ * map scale on the map. By default it is drawn in the lower left corner of
+ * the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ *
+ * Is a very close copy of:
+ * - <OpenLayers.Control.Scale>
+ */
+OpenLayers.Control.ScaleLine = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: maxWidth
+ * {Integer} Maximum width of the scale line in pixels. Default is 100.
+ */
+ maxWidth: 100,
+
+ /**
+ * Property: topOutUnits
+ * {String} Units for zoomed out on top bar. Default is km.
+ */
+ topOutUnits: "km",
+
+ /**
+ * Property: topInUnits
+ * {String} Units for zoomed in on top bar. Default is m.
+ */
+ topInUnits: "m",
+
+ /**
+ * Property: bottomOutUnits
+ * {String} Units for zoomed out on bottom bar. Default is mi.
+ */
+ bottomOutUnits: "mi",
+
+ /**
+ * Property: bottomInUnits
+ * {String} Units for zoomed in on bottom bar. Default is ft.
+ */
+ bottomInUnits: "ft",
+
+ /**
+ * Property: eTop
+ * {DOMElement}
+ */
+ eTop: null,
+
+ /**
+ * Property: eBottom
+ * {DOMElement}
+ */
+ eBottom:null,
+
+ /**
+ * APIProperty: geodesic
+ * {Boolean} Use geodesic measurement. Default is false. The recommended
+ * setting for maps in EPSG:4326 is false, and true EPSG:900913. If set to
+ * true, the scale will be calculated based on the horizontal size of the
+ * pixel in the center of the map viewport.
+ */
+ geodesic: false,
+
+ /**
+ * Constructor: OpenLayers.Control.ScaleLine
+ * Create a new scale line control.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ if (!this.eTop) {
+ // stick in the top bar
+ this.eTop = document.createElement("div");
+ this.eTop.className = this.displayClass + "Top";
+ var theLen = this.topInUnits.length;
+ this.div.appendChild(this.eTop);
+ if((this.topOutUnits == "") || (this.topInUnits == "")) {
+ this.eTop.style.visibility = "hidden";
+ } else {
+ this.eTop.style.visibility = "visible";
+ }
+
+ // and the bottom bar
+ this.eBottom = document.createElement("div");
+ this.eBottom.className = this.displayClass + "Bottom";
+ this.div.appendChild(this.eBottom);
+ if((this.bottomOutUnits == "") || (this.bottomInUnits == "")) {
+ this.eBottom.style.visibility = "hidden";
+ } else {
+ this.eBottom.style.visibility = "visible";
+ }
+ }
+ this.map.events.register('moveend', this, this.update);
+ this.update();
+ return this.div;
+ },
+
+ /**
+ * Method: getBarLen
+ * Given a number, round it down to the nearest 1,2,5 times a power of 10.
+ * That seems a fairly useful set of number groups to use.
+ *
+ * Parameters:
+ * maxLen - {float} the number we're rounding down from
+ *
+ * Returns:
+ * {Float} the rounded number (less than or equal to maxLen)
+ */
+ getBarLen: function(maxLen) {
+ // nearest power of 10 lower than maxLen
+ var digits = parseInt(Math.log(maxLen) / Math.log(10));
+ var pow10 = Math.pow(10, digits);
+
+ // ok, find first character
+ var firstChar = parseInt(maxLen / pow10);
+
+ // right, put it into the correct bracket
+ var barLen;
+ if(firstChar > 5) {
+ barLen = 5;
+ } else if(firstChar > 2) {
+ barLen = 2;
+ } else {
+ barLen = 1;
+ }
+
+ // scale it up the correct power of 10
+ return barLen * pow10;
+ },
+
+ /**
+ * Method: update
+ * Update the size of the bars, and the labels they contain.
+ */
+ update: function() {
+ var res = this.map.getResolution();
+ if (!res) {
+ return;
+ }
+
+ var curMapUnits = this.map.getUnits();
+ var inches = OpenLayers.INCHES_PER_UNIT;
+
+ // convert maxWidth to map units
+ var maxSizeData = this.maxWidth * res * inches[curMapUnits];
+ var geodesicRatio = 1;
+ if(this.geodesic === true) {
+ var maxSizeGeodesic = (this.map.getGeodesicPixelSize().w ||
+ 0.000001) * this.maxWidth;
+ var maxSizeKilometers = maxSizeData / inches["km"];
+ geodesicRatio = maxSizeGeodesic / maxSizeKilometers;
+ maxSizeData *= geodesicRatio;
+ }
+
+ // decide whether to use large or small scale units
+ var topUnits;
+ var bottomUnits;
+ if(maxSizeData > 100000) {
+ topUnits = this.topOutUnits;
+ bottomUnits = this.bottomOutUnits;
+ } else {
+ topUnits = this.topInUnits;
+ bottomUnits = this.bottomInUnits;
+ }
+
+ // and to map units units
+ var topMax = maxSizeData / inches[topUnits];
+ var bottomMax = maxSizeData / inches[bottomUnits];
+
+ // now trim this down to useful block length
+ var topRounded = this.getBarLen(topMax);
+ var bottomRounded = this.getBarLen(bottomMax);
+
+ // and back to display units
+ topMax = topRounded / inches[curMapUnits] * inches[topUnits];
+ bottomMax = bottomRounded / inches[curMapUnits] * inches[bottomUnits];
+
+ // and to pixel units
+ var topPx = topMax / res / geodesicRatio;
+ var bottomPx = bottomMax / res / geodesicRatio;
+
+ // now set the pixel widths
+ // and the values inside them
+
+ if (this.eBottom.style.visibility == "visible"){
+ this.eBottom.style.width = Math.round(bottomPx) + "px";
+ this.eBottom.innerHTML = bottomRounded + " " + bottomUnits ;
+ }
+
+ if (this.eTop.style.visibility == "visible"){
+ this.eTop.style.width = Math.round(topPx) + "px";
+ this.eTop.innerHTML = topRounded + " " + topUnits;
+ }
+
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ScaleLine"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Control/SelectFeature.js b/misc/openlayers/lib/OpenLayers/Control/SelectFeature.js
new file mode 100644
index 0000000..5467267
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/SelectFeature.js
@@ -0,0 +1,643 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ * @requires OpenLayers/Layer/Vector/RootContainer.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * The SelectFeature control selects vector features from a given layer on
+ * click or hover.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforefeaturehighlighted - Triggered before a feature is highlighted
+ * featurehighlighted - Triggered when a feature is highlighted
+ * featureunhighlighted - Triggered when a feature is unhighlighted
+ * boxselectionstart - Triggered before box selection starts
+ * boxselectionend - Triggered after box selection ends
+ */
+
+ /**
+ * Property: multipleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <multiple> property to true. Default is null.
+ */
+ multipleKey: null,
+
+ /**
+ * Property: toggleKey
+ * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+ * the <toggle> property to true. Default is null.
+ */
+ toggleKey: null,
+
+ /**
+ * APIProperty: multiple
+ * {Boolean} Allow selection of multiple geometries. Default is false.
+ */
+ multiple: false,
+
+ /**
+ * APIProperty: clickout
+ * {Boolean} Unselect features when clicking outside any feature.
+ * Default is true.
+ */
+ clickout: true,
+
+ /**
+ * APIProperty: toggle
+ * {Boolean} Unselect a selected feature on click. Default is false. Only
+ * has meaning if hover is false.
+ */
+ toggle: false,
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Select on mouse over and deselect on mouse out. If true, this
+ * ignores clicks and only listens to mouse moves.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: highlightOnly
+ * {Boolean} If true do not actually select features (that is place them in
+ * the layer's selected features array), just highlight them. This property
+ * has no effect if hover is false. Defaults to false.
+ */
+ highlightOnly: false,
+
+ /**
+ * APIProperty: box
+ * {Boolean} Allow feature selection by drawing a box.
+ */
+ box: false,
+
+ /**
+ * Property: onBeforeSelect
+ * {Function} Optional function to be called before a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onBeforeSelect: function() {},
+
+ /**
+ * APIProperty: onSelect
+ * {Function} Optional function to be called when a feature is selected.
+ * The function should expect to be called with a feature.
+ */
+ onSelect: function() {},
+
+ /**
+ * APIProperty: onUnselect
+ * {Function} Optional function to be called when a feature is unselected.
+ * The function should expect to be called with a feature.
+ */
+ onUnselect: function() {},
+
+ /**
+ * Property: scope
+ * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect
+ * callbacks. If null the scope will be this control.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict selecting to a limited set of geometry types,
+ * send a list of strings corresponding to the geometry class names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The vector layer with a common renderer
+ * root for all layers this control is configured with (if an array of
+ * layers was passed to the constructor), or the vector layer the control
+ * was configured with (if a single layer was passed to the constructor).
+ */
+ layer: null,
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.Vector>)} The layers this control will work on,
+ * or null if the control was configured with a single layer
+ */
+ layers: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} The functions that are sent to the handlers.feature for callback
+ */
+ callbacks: null,
+
+ /**
+ * APIProperty: selectStyle
+ * {Object} Hash of styles
+ */
+ selectStyle: null,
+
+ /**
+ * Property: renderIntent
+ * {String} key used to retrieve the select style from the layer's
+ * style map.
+ */
+ renderIntent: "select",
+
+ /**
+ * Property: handlers
+ * {Object} Object with references to multiple <OpenLayers.Handler>
+ * instances.
+ */
+ handlers: null,
+
+ /**
+ * Constructor: OpenLayers.Control.SelectFeature
+ * Create a new control for selecting features.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers. The
+ * layer(s) this control will select features from.
+ * options - {Object}
+ */
+ initialize: function(layers, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(this.scope === null) {
+ this.scope = this;
+ }
+ this.initLayer(layers);
+ var callbacks = {
+ click: this.clickFeature,
+ clickout: this.clickoutFeature
+ };
+ if (this.hover) {
+ callbacks.over = this.overFeature;
+ callbacks.out = this.outFeature;
+ }
+
+ this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+ this.handlers = {
+ feature: new OpenLayers.Handler.Feature(
+ this, this.layer, this.callbacks,
+ {geometryTypes: this.geometryTypes}
+ )
+ };
+
+ if (this.box) {
+ this.handlers.box = new OpenLayers.Handler.Box(
+ this, {done: this.selectBox},
+ {boxDivClassName: "olHandlerBoxSelectFeature"}
+ );
+ }
+ },
+
+ /**
+ * Method: initLayer
+ * Assign the layer property. If layers is an array, we need to use
+ * a RootContainer.
+ *
+ * Parameters:
+ * layers - {<OpenLayers.Layer.Vector>}, or an array of vector layers.
+ */
+ initLayer: function(layers) {
+ if(OpenLayers.Util.isArray(layers)) {
+ this.layers = layers;
+ this.layer = new OpenLayers.Layer.Vector.RootContainer(
+ this.id + "_container", {
+ layers: layers
+ }
+ );
+ } else {
+ this.layer = layers;
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ if(this.active && this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ if(this.layers) {
+ this.layer.destroy();
+ }
+ },
+
+ /**
+ * Method: activate
+ * Activates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively activated.
+ */
+ activate: function () {
+ if (!this.active) {
+ if(this.layers) {
+ this.map.addLayer(this.layer);
+ }
+ this.handlers.feature.activate();
+ if(this.box && this.handlers.box) {
+ this.handlers.box.activate();
+ }
+ }
+ return OpenLayers.Control.prototype.activate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivates the control.
+ *
+ * Returns:
+ * {Boolean} The control was effectively deactivated.
+ */
+ deactivate: function () {
+ if (this.active) {
+ this.handlers.feature.deactivate();
+ if(this.handlers.box) {
+ this.handlers.box.deactivate();
+ }
+ if(this.layers) {
+ this.map.removeLayer(this.layer);
+ }
+ }
+ return OpenLayers.Control.prototype.deactivate.apply(
+ this, arguments
+ );
+ },
+
+ /**
+ * Method: unselectAll
+ * Unselect all selected features. To unselect all except for a single
+ * feature, set the options.except property to the feature.
+ *
+ * Parameters:
+ * options - {Object} Optional configuration object.
+ */
+ unselectAll: function(options) {
+ // we'll want an option to supress notification here
+ var layers = this.layers || [this.layer],
+ layer, feature, l, numExcept;
+ for(l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ numExcept = 0;
+ //layer.selectedFeatures is null when layer is destroyed and
+ //one of it's preremovelayer listener calls setLayer
+ //with another layer on this control
+ if(layer.selectedFeatures != null) {
+ while(layer.selectedFeatures.length > numExcept) {
+ feature = layer.selectedFeatures[numExcept];
+ if(!options || options.except != feature) {
+ this.unselect(feature);
+ } else {
+ ++numExcept;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: clickFeature
+ * Called on click in a feature
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ clickFeature: function(feature) {
+ if(!this.hover) {
+ var selected = (OpenLayers.Util.indexOf(
+ feature.layer.selectedFeatures, feature) > -1);
+ if(selected) {
+ if(this.toggleSelect()) {
+ this.unselect(feature);
+ } else if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ } else {
+ if(!this.multipleSelect()) {
+ this.unselectAll({except: feature});
+ }
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: multipleSelect
+ * Allow for multiple selected features based on <multiple> property and
+ * <multipleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Allow for multiple selected features.
+ */
+ multipleSelect: function() {
+ return this.multiple || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.multipleKey]);
+ },
+
+ /**
+ * Method: toggleSelect
+ * Event should toggle the selected state of a feature based on <toggle>
+ * property and <toggleKey> event modifier.
+ *
+ * Returns:
+ * {Boolean} Toggle the selected state of a feature.
+ */
+ toggleSelect: function() {
+ return this.toggle || (this.handlers.feature.evt &&
+ this.handlers.feature.evt[this.toggleKey]);
+ },
+
+ /**
+ * Method: clickoutFeature
+ * Called on click outside a previously clicked (selected) feature.
+ * Only responds if this.hover is false.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ */
+ clickoutFeature: function(feature) {
+ if(!this.hover && this.clickout) {
+ this.unselectAll();
+ }
+ },
+
+ /**
+ * Method: overFeature
+ * Called on over a feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ overFeature: function(feature) {
+ var layer = feature.layer;
+ if(this.hover) {
+ if(this.highlightOnly) {
+ this.highlight(feature);
+ } else if(OpenLayers.Util.indexOf(
+ layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: outFeature
+ * Called on out of a selected feature.
+ * Only responds if this.hover is true.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ outFeature: function(feature) {
+ if(this.hover) {
+ if(this.highlightOnly) {
+ // we do nothing if we're not the last highlighter of the
+ // feature
+ if(feature._lastHighlighter == this.id) {
+ // if another select control had highlighted the feature before
+ // we did it ourself then we use that control to highlight the
+ // feature as it was before we highlighted it, else we just
+ // unhighlight it
+ if(feature._prevHighlighter &&
+ feature._prevHighlighter != this.id) {
+ delete feature._lastHighlighter;
+ var control = this.map.getControl(
+ feature._prevHighlighter);
+ if(control) {
+ control.highlight(feature);
+ }
+ } else {
+ this.unhighlight(feature);
+ }
+ }
+ } else {
+ this.unselect(feature);
+ }
+ }
+ },
+
+ /**
+ * Method: highlight
+ * Redraw feature with the select style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ highlight: function(feature) {
+ var layer = feature.layer;
+ var cont = this.events.triggerEvent("beforefeaturehighlighted", {
+ feature : feature
+ });
+ if(cont !== false) {
+ feature._prevHighlighter = feature._lastHighlighter;
+ feature._lastHighlighter = this.id;
+ var style = this.selectStyle || this.renderIntent;
+ layer.drawFeature(feature, style);
+ this.events.triggerEvent("featurehighlighted", {feature : feature});
+ }
+ },
+
+ /**
+ * Method: unhighlight
+ * Redraw feature with the "default" style
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unhighlight: function(feature) {
+ var layer = feature.layer;
+ // three cases:
+ // 1. there's no other highlighter, in that case _prev is undefined,
+ // and we just need to undef _last
+ // 2. another control highlighted the feature after we did it, in
+ // that case _last references this other control, and we just
+ // need to undef _prev
+ // 3. another control highlighted the feature before we did it, in
+ // that case _prev references this other control, and we need to
+ // set _last to _prev and undef _prev
+ if(feature._prevHighlighter == undefined) {
+ delete feature._lastHighlighter;
+ } else if(feature._prevHighlighter == this.id) {
+ delete feature._prevHighlighter;
+ } else {
+ feature._lastHighlighter = feature._prevHighlighter;
+ delete feature._prevHighlighter;
+ }
+ layer.drawFeature(feature, feature.style || feature.layer.style ||
+ "default");
+ this.events.triggerEvent("featureunhighlighted", {feature : feature});
+ },
+
+ /**
+ * Method: select
+ * Add feature to the layer's selectedFeature array, render the feature as
+ * selected, and call the onSelect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ select: function(feature) {
+ var cont = this.onBeforeSelect.call(this.scope, feature);
+ var layer = feature.layer;
+ if(cont !== false) {
+ cont = layer.events.triggerEvent("beforefeatureselected", {
+ feature: feature
+ });
+ if(cont !== false) {
+ layer.selectedFeatures.push(feature);
+ this.highlight(feature);
+ // if the feature handler isn't involved in the feature
+ // selection (because the box handler is used or the
+ // feature is selected programatically) we fake the
+ // feature handler to allow unselecting on click
+ if(!this.handlers.feature.lastFeature) {
+ this.handlers.feature.lastFeature = layer.selectedFeatures[0];
+ }
+ layer.events.triggerEvent("featureselected", {feature: feature});
+ this.onSelect.call(this.scope, feature);
+ }
+ }
+ },
+
+ /**
+ * Method: unselect
+ * Remove feature from the layer's selectedFeature array, render the feature as
+ * normal, and call the onUnselect function.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ unselect: function(feature) {
+ var layer = feature.layer;
+ // Store feature style for restoration later
+ this.unhighlight(feature);
+ OpenLayers.Util.removeItem(layer.selectedFeatures, feature);
+ layer.events.triggerEvent("featureunselected", {feature: feature});
+ this.onUnselect.call(this.scope, feature);
+ },
+
+ /**
+ * Method: selectBox
+ * Callback from the handlers.box set up when <box> selection is true
+ * on.
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }
+ */
+ selectBox: function(position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ var bounds = new OpenLayers.Bounds(
+ minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+ );
+
+ // if multiple is false, first deselect currently selected features
+ if (!this.multipleSelect()) {
+ this.unselectAll();
+ }
+
+ // because we're using a box, we consider we want multiple selection
+ var prevMultiple = this.multiple;
+ this.multiple = true;
+ var layers = this.layers || [this.layer];
+ this.events.triggerEvent("boxselectionstart", {layers: layers});
+ var layer;
+ for(var l=0; l<layers.length; ++l) {
+ layer = layers[l];
+ for(var i=0, len = layer.features.length; i<len; ++i) {
+ var feature = layer.features[i];
+ // check if the feature is displayed
+ if (!feature.getVisibility()) {
+ continue;
+ }
+
+ if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+ this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+ if (bounds.toGeometry().intersects(feature.geometry)) {
+ if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) {
+ this.select(feature);
+ }
+ }
+ }
+ }
+ }
+ this.multiple = prevMultiple;
+ this.events.triggerEvent("boxselectionend", {layers: layers});
+ }
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the control.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.handlers.feature.setMap(map);
+ if (this.box) {
+ this.handlers.box.setMap(map);
+ }
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Attach a new layer to the control, overriding any existing layers.
+ *
+ * Parameters:
+ * layers - Array of {<OpenLayers.Layer.Vector>} or a single
+ * {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layers) {
+ var isActive = this.active;
+ this.unselectAll();
+ this.deactivate();
+ if(this.layers) {
+ this.layer.destroy();
+ this.layers = null;
+ }
+ this.initLayer(layers);
+ this.handlers.feature.layer = this.layer;
+ if (isActive) {
+ this.activate();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Snapping.js b/misc/openlayers/lib/OpenLayers/Control/Snapping.js
new file mode 100644
index 0000000..2173114
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Snapping.js
@@ -0,0 +1,560 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Snapping
+ * Acts as a snapping agent while editing vector features.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Snapping = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforesnap - Triggered before a snap occurs. Listeners receive an
+ * event object with *point*, *x*, *y*, *distance*, *layer*, and
+ * *snapType* properties. The point property will be original point
+ * geometry considered for snapping. The x and y properties represent
+ * coordinates the point will receive. The distance is the distance
+ * of the snap. The layer is the target layer. The snapType property
+ * will be one of "node", "vertex", or "edge". Return false to stop
+ * snapping from occurring.
+ * snap - Triggered when a snap occurs. Listeners receive an event with
+ * *point*, *snapType*, *layer*, and *distance* properties. The point
+ * will be the location snapped to. The snapType will be one of "node",
+ * "vertex", or "edge". The layer will be the target layer. The
+ * distance will be the distance of the snap in map units.
+ * unsnap - Triggered when a vertex is unsnapped. Listeners receive an
+ * event with a *point* property.
+ */
+
+ /**
+ * CONSTANT: DEFAULTS
+ * Default target properties.
+ */
+ DEFAULTS: {
+ tolerance: 10,
+ node: true,
+ edge: true,
+ vertex: true
+ },
+
+ /**
+ * Property: greedy
+ * {Boolean} Snap to closest feature in first layer with an eligible
+ * feature. Default is true.
+ */
+ greedy: true,
+
+ /**
+ * Property: precedence
+ * {Array} List representing precedence of different snapping types.
+ * Default is "node", "vertex", "edge".
+ */
+ precedence: ["node", "vertex", "edge"],
+
+ /**
+ * Property: resolution
+ * {Float} The map resolution for the previously considered snap.
+ */
+ resolution: null,
+
+ /**
+ * Property: geoToleranceCache
+ * {Object} A cache of geo-tolerances. Tolerance values (in map units) are
+ * calculated when the map resolution changes.
+ */
+ geoToleranceCache: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The current editable layer. Set at
+ * construction or after construction with <setLayer>.
+ */
+ layer: null,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The current editable feature.
+ */
+ feature: null,
+
+ /**
+ * Property: point
+ * {<OpenLayers.Geometry.Point>} The currently snapped vertex.
+ */
+ point: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Snapping
+ * Creates a new snapping control. A control is constructed with an editable
+ * layer and a set of configuration objects for target layers. While the
+ * control is active, dragging vertices while drawing new features or
+ * modifying existing features on the editable layer will engage
+ * snapping to features on the target layers. Whether a vertex snaps to
+ * a feature on a target layer depends on the target layer configuration.
+ *
+ * Parameters:
+ * options - {Object} An object containing all configuration properties for
+ * the control.
+ *
+ * Valid options:
+ * layer - {<OpenLayers.Layer.Vector>} The editable layer. Features from this
+ * layer that are digitized or modified may have vertices snapped to
+ * features from any of the target layers.
+ * targets - {Array(Object | OpenLayers.Layer.Vector)} A list of objects for
+ * configuring target layers. See valid properties of the target
+ * objects below. If the items in the targets list are vector layers
+ * (instead of configuration objects), the defaults from the <defaults>
+ * property will apply. The editable layer itself may be a target
+ * layer, allowing newly created or edited features to be snapped to
+ * existing features from the same layer. If no targets are provided
+ * the layer given in the constructor (as <layer>) will become the
+ * initial target.
+ * defaults - {Object} An object with default properties to be applied
+ * to all target objects.
+ * greedy - {Boolean} Snap to closest feature in first target layer that
+ * applies. Default is true. If false, all features in all target
+ * layers will be checked and the closest feature in all target layers
+ * will be chosen. The greedy property determines if the order of the
+ * target layers is significant. By default, the order of the target
+ * layers is significant where layers earlier in the target layer list
+ * have precedence over layers later in the list. Within a single
+ * layer, the closest feature is always chosen for snapping. This
+ * property only determines whether the search for a closer feature
+ * continues after an eligible feature is found in a target layer.
+ *
+ * Valid target properties:
+ * layer - {<OpenLayers.Layer.Vector>} A target layer. Features from this
+ * layer will be eligible to act as snapping target for the editable
+ * layer.
+ * tolerance - {Float} The distance (in pixels) at which snapping may occur.
+ * Default is 10.
+ * node - {Boolean} Snap to nodes (first or last point in a geometry) in
+ * target layer. Default is true.
+ * nodeTolerance - {Float} Optional distance at which snapping may occur
+ * for nodes specifically. If none is provided, <tolerance> will be
+ * used.
+ * vertex - {Boolean} Snap to vertices in target layer. Default is true.
+ * vertexTolerance - {Float} Optional distance at which snapping may occur
+ * for vertices specifically. If none is provided, <tolerance> will be
+ * used.
+ * edge - {Boolean} Snap to edges in target layer. Default is true.
+ * edgeTolerance - {Float} Optional distance at which snapping may occur
+ * for edges specifically. If none is provided, <tolerance> will be
+ * used.
+ * filter - {<OpenLayers.Filter>} Optional filter to evaluate to determine if
+ * feature is eligible for snapping. If filter evaluates to true for a
+ * target feature a vertex may be snapped to the feature.
+ * minResolution - {Number} If a minResolution is provided, snapping to this
+ * target will only be considered if the map resolution is greater than
+ * or equal to this value (the minResolution is inclusive). Default is
+ * no minimum resolution limit.
+ * maxResolution - {Number} If a maxResolution is provided, snapping to this
+ * target will only be considered if the map resolution is strictly
+ * less than this value (the maxResolution is exclusive). Default is
+ * no maximum resolution limit.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.options = options || {}; // TODO: this could be done by the super
+
+ // set the editable layer if provided
+ if(this.options.layer) {
+ this.setLayer(this.options.layer);
+ }
+ // configure target layers
+ var defaults = OpenLayers.Util.extend({}, this.options.defaults);
+ this.defaults = OpenLayers.Util.applyDefaults(defaults, this.DEFAULTS);
+ this.setTargets(this.options.targets);
+ if(this.targets.length === 0 && this.layer) {
+ this.addTargetLayer(this.layer);
+ }
+
+ this.geoToleranceCache = {};
+ },
+
+ /**
+ * APIMethod: setLayer
+ * Set the editable layer. Call the setLayer method if the editable layer
+ * changes and the same control should be used on a new editable layer.
+ * If the control is already active, it will be active after the new
+ * layer is set.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The new editable layer.
+ */
+ setLayer: function(layer) {
+ if(this.active) {
+ this.deactivate();
+ this.layer = layer;
+ this.activate();
+ } else {
+ this.layer = layer;
+ }
+ },
+
+ /**
+ * Method: setTargets
+ * Set the targets for the snapping agent.
+ *
+ * Parameters:
+ * targets - {Array} An array of target configs or target layers.
+ */
+ setTargets: function(targets) {
+ this.targets = [];
+ if(targets && targets.length) {
+ var target;
+ for(var i=0, len=targets.length; i<len; ++i) {
+ target = targets[i];
+ if(target instanceof OpenLayers.Layer.Vector) {
+ this.addTargetLayer(target);
+ } else {
+ this.addTarget(target);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: addTargetLayer
+ * Add a target layer with the default target config.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} A target layer.
+ */
+ addTargetLayer: function(layer) {
+ this.addTarget({layer: layer});
+ },
+
+ /**
+ * Method: addTarget
+ * Add a configured target layer.
+ *
+ * Parameters:
+ * target - {Object} A target config.
+ */
+ addTarget: function(target) {
+ target = OpenLayers.Util.applyDefaults(target, this.defaults);
+ target.nodeTolerance = target.nodeTolerance || target.tolerance;
+ target.vertexTolerance = target.vertexTolerance || target.tolerance;
+ target.edgeTolerance = target.edgeTolerance || target.tolerance;
+ this.targets.push(target);
+ },
+
+ /**
+ * Method: removeTargetLayer
+ * Remove a target layer.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The target layer to remove.
+ */
+ removeTargetLayer: function(layer) {
+ var target;
+ for(var i=this.targets.length-1; i>=0; --i) {
+ target = this.targets[i];
+ if(target.layer === layer) {
+ this.removeTarget(target);
+ }
+ }
+ },
+
+ /**
+ * Method: removeTarget
+ * Remove a target.
+ *
+ * Parameters:
+ * target - {Object} A target config.
+ *
+ * Returns:
+ * {Array} The targets array.
+ */
+ removeTarget: function(target) {
+ return OpenLayers.Util.removeItem(this.targets, target);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. Activating the control registers listeners for
+ * editing related events so that during feature creation and
+ * modification, moving vertices will trigger snapping.
+ */
+ activate: function() {
+ var activated = OpenLayers.Control.prototype.activate.call(this);
+ if(activated) {
+ if(this.layer && this.layer.events) {
+ this.layer.events.on({
+ sketchstarted: this.onSketchModified,
+ sketchmodified: this.onSketchModified,
+ vertexmodified: this.onVertexModified,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. Deactivating the control unregisters listeners
+ * so feature editing may proceed without engaging the snapping agent.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
+ if(deactivated) {
+ if(this.layer && this.layer.events) {
+ this.layer.events.un({
+ sketchstarted: this.onSketchModified,
+ sketchmodified: this.onSketchModified,
+ vertexmodified: this.onVertexModified,
+ scope: this
+ });
+ }
+ }
+ this.feature = null;
+ this.point = null;
+ return deactivated;
+ },
+
+ /**
+ * Method: onSketchModified
+ * Registered as a listener for the sketchmodified event on the editable
+ * layer.
+ *
+ * Parameters:
+ * event - {Object} The sketch modified event.
+ */
+ onSketchModified: function(event) {
+ this.feature = event.feature;
+ this.considerSnapping(event.vertex, event.vertex);
+ },
+
+ /**
+ * Method: onVertexModified
+ * Registered as a listener for the vertexmodified event on the editable
+ * layer.
+ *
+ * Parameters:
+ * event - {Object} The vertex modified event.
+ */
+ onVertexModified: function(event) {
+ this.feature = event.feature;
+ var loc = this.layer.map.getLonLatFromViewPortPx(event.pixel);
+ this.considerSnapping(
+ event.vertex, new OpenLayers.Geometry.Point(loc.lon, loc.lat)
+ );
+ },
+
+ /**
+ * Method: considerSnapping
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The vertex to be snapped (or
+ * unsnapped).
+ * loc - {<OpenLayers.Geometry.Point>} The location of the mouse in map
+ * coords.
+ */
+ considerSnapping: function(point, loc) {
+ var best = {
+ rank: Number.POSITIVE_INFINITY,
+ dist: Number.POSITIVE_INFINITY,
+ x: null, y: null
+ };
+ var snapped = false;
+ var result, target;
+ for(var i=0, len=this.targets.length; i<len; ++i) {
+ target = this.targets[i];
+ result = this.testTarget(target, loc);
+ if(result) {
+ if(this.greedy) {
+ best = result;
+ best.target = target;
+ snapped = true;
+ break;
+ } else {
+ if((result.rank < best.rank) ||
+ (result.rank === best.rank && result.dist < best.dist)) {
+ best = result;
+ best.target = target;
+ snapped = true;
+ }
+ }
+ }
+ }
+ if(snapped) {
+ var proceed = this.events.triggerEvent("beforesnap", {
+ point: point, x: best.x, y: best.y, distance: best.dist,
+ layer: best.target.layer, snapType: this.precedence[best.rank]
+ });
+ if(proceed !== false) {
+ point.x = best.x;
+ point.y = best.y;
+ this.point = point;
+ this.events.triggerEvent("snap", {
+ point: point,
+ snapType: this.precedence[best.rank],
+ layer: best.target.layer,
+ distance: best.dist
+ });
+ } else {
+ snapped = false;
+ }
+ }
+ if(this.point && !snapped) {
+ point.x = loc.x;
+ point.y = loc.y;
+ this.point = null;
+ this.events.triggerEvent("unsnap", {point: point});
+ }
+ },
+
+ /**
+ * Method: testTarget
+ *
+ * Parameters:
+ * target - {Object} Object with target layer configuration.
+ * loc - {<OpenLayers.Geometry.Point>} The location of the mouse in map
+ * coords.
+ *
+ * Returns:
+ * {Object} A result object with rank, dist, x, and y properties.
+ * Returns null if candidate is not eligible for snapping.
+ */
+ testTarget: function(target, loc) {
+ var resolution = this.layer.map.getResolution();
+ if ("minResolution" in target) {
+ if (resolution < target.minResolution) {
+ return null;
+ }
+ }
+ if ("maxResolution" in target) {
+ if (resolution >= target.maxResolution) {
+ return null;
+ }
+ }
+ var tolerance = {
+ node: this.getGeoTolerance(target.nodeTolerance, resolution),
+ vertex: this.getGeoTolerance(target.vertexTolerance, resolution),
+ edge: this.getGeoTolerance(target.edgeTolerance, resolution)
+ };
+ // this could be cached if we don't support setting tolerance values directly
+ var maxTolerance = Math.max(
+ tolerance.node, tolerance.vertex, tolerance.edge
+ );
+ var result = {
+ rank: Number.POSITIVE_INFINITY, dist: Number.POSITIVE_INFINITY
+ };
+ var eligible = false;
+ var features = target.layer.features;
+ var feature, type, vertices, vertex, closest, dist, found;
+ var numTypes = this.precedence.length;
+ var ll = new OpenLayers.LonLat(loc.x, loc.y);
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ if(feature !== this.feature && !feature._sketch &&
+ feature.state !== OpenLayers.State.DELETE &&
+ (!target.filter || target.filter.evaluate(feature))) {
+ if(feature.atPoint(ll, maxTolerance, maxTolerance)) {
+ for(var j=0, stop=Math.min(result.rank+1, numTypes); j<stop; ++j) {
+ type = this.precedence[j];
+ if(target[type]) {
+ if(type === "edge") {
+ closest = feature.geometry.distanceTo(loc, {details: true});
+ dist = closest.distance;
+ if(dist <= tolerance[type] && dist < result.dist) {
+ result = {
+ rank: j, dist: dist,
+ x: closest.x0, y: closest.y0 // closest coords on feature
+ };
+ eligible = true;
+ // don't look for lower precedence types for this feature
+ break;
+ }
+ } else {
+ // look for nodes or vertices
+ vertices = feature.geometry.getVertices(type === "node");
+ found = false;
+ for(var k=0, klen=vertices.length; k<klen; ++k) {
+ vertex = vertices[k];
+ dist = vertex.distanceTo(loc);
+ if(dist <= tolerance[type] &&
+ (j < result.rank || (j === result.rank && dist < result.dist))) {
+ result = {
+ rank: j, dist: dist,
+ x: vertex.x, y: vertex.y
+ };
+ eligible = true;
+ found = true;
+ }
+ }
+ if(found) {
+ // don't look for lower precedence types for this feature
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return eligible ? result : null;
+ },
+
+ /**
+ * Method: getGeoTolerance
+ * Calculate a tolerance in map units given a tolerance in pixels. This
+ * takes advantage of the <geoToleranceCache> when the map resolution
+ * has not changed.
+ *
+ * Parameters:
+ * tolerance - {Number} A tolerance value in pixels.
+ * resolution - {Number} Map resolution.
+ *
+ * Returns:
+ * {Number} A tolerance value in map units.
+ */
+ getGeoTolerance: function(tolerance, resolution) {
+ if(resolution !== this.resolution) {
+ this.resolution = resolution;
+ this.geoToleranceCache = {};
+ }
+ var geoTolerance = this.geoToleranceCache[tolerance];
+ if(geoTolerance === undefined) {
+ geoTolerance = tolerance * resolution;
+ this.geoToleranceCache[tolerance] = geoTolerance;
+ }
+ return geoTolerance;
+ },
+
+ /**
+ * Method: destroy
+ * Clean up the control.
+ */
+ destroy: function() {
+ if(this.active) {
+ this.deactivate(); // TODO: this should be handled by the super
+ }
+ delete this.layer;
+ delete this.targets;
+ OpenLayers.Control.prototype.destroy.call(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Snapping"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Split.js b/misc/openlayers/lib/OpenLayers/Control/Split.js
new file mode 100644
index 0000000..de19eb7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Split.js
@@ -0,0 +1,494 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Split
+ * Acts as a split feature agent while editing vector features.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforesplit - Triggered before a split occurs. Listeners receive an
+ * event object with *source* and *target* properties.
+ * split - Triggered when a split occurs. Listeners receive an event with
+ * an *original* property and a *features* property. The original
+ * is a reference to the target feature that the sketch or modified
+ * feature intersects. The features property is a list of all features
+ * that result from this single split. This event is triggered before
+ * the resulting features are added to the layer (while the layer still
+ * has a reference to the original).
+ * aftersplit - Triggered after all splits resulting from a single sketch
+ * or feature modification have occurred. The original features
+ * have been destroyed and features that result from the split
+ * have already been added to the layer. Listeners receive an event
+ * with a *source* and *features* property. The source references the
+ * sketch or modified feature used as a splitter. The features
+ * property is a list of all resulting features.
+ */
+
+ /**
+ * APIProperty: layer
+ * {<OpenLayers.Layer.Vector>} The target layer with features to be split.
+ * Set at construction or after construction with <setLayer>.
+ */
+ layer: null,
+
+ /**
+ * Property: source
+ * {<OpenLayers.Layer.Vector>} Optional source layer. Any newly created
+ * or modified features from this layer will be used to split features
+ * on the target layer. If not provided, a temporary sketch layer will
+ * be created.
+ */
+ source: null,
+
+ /**
+ * Property: sourceOptions
+ * {Options} If a temporary sketch layer is created, these layer options
+ * will be applied.
+ */
+ sourceOptions: null,
+
+ /**
+ * APIProperty: tolerance
+ * {Number} Distance between the calculated intersection and a vertex on
+ * the source geometry below which the existing vertex will be used
+ * for the split. Default is null.
+ */
+ tolerance: null,
+
+ /**
+ * APIProperty: edge
+ * {Boolean} Allow splits given intersection of edges only. Default is
+ * true. If false, a vertex on the source must be within the
+ * <tolerance> distance of the calculated intersection for a split
+ * to occur.
+ */
+ edge: true,
+
+ /**
+ * APIProperty: deferDelete
+ * {Boolean} Instead of removing features from the layer, set feature
+ * states of split features to DELETE. This assumes a save strategy
+ * or other component is in charge of removing features from the
+ * layer. Default is false. If false, split features will be
+ * immediately deleted from the layer.
+ */
+ deferDelete: false,
+
+ /**
+ * APIProperty: mutual
+ * {Boolean} If source and target layers are the same, split source
+ * features and target features where they intersect. Default is
+ * true. If false, only target features will be split.
+ */
+ mutual: true,
+
+ /**
+ * APIProperty: targetFilter
+ * {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the target layer is eligible for
+ * splitting.
+ */
+ targetFilter: null,
+
+ /**
+ * APIProperty: sourceFilter
+ * {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the source layer is eligible for
+ * splitting.
+ */
+ sourceFilter: null,
+
+ /**
+ * Property: handler
+ * {<OpenLayers.Handler.Path>} The temporary sketch handler created if
+ * no source layer is provided.
+ */
+ handler: null,
+
+ /**
+ * Constructor: OpenLayers.Control.Split
+ * Creates a new split control. A control is constructed with a target
+ * layer and an optional source layer. While the control is active,
+ * creating new features or modifying existing features on the source
+ * layer will result in splitting any eligible features on the target
+ * layer. If no source layer is provided, a temporary sketch layer will
+ * be created to create lines for splitting features on the target.
+ *
+ * Parameters:
+ * options - {Object} An object containing all configuration properties for
+ * the control.
+ *
+ * Valid options:
+ * layer - {<OpenLayers.Layer.Vector>} The target layer. Features from this
+ * layer will be split by new or modified features on the source layer
+ * or temporary sketch layer.
+ * source - {<OpenLayers.Layer.Vector>} Optional source layer. If provided
+ * newly created features or modified features will be used to split
+ * features on the target layer. If not provided, a temporary sketch
+ * layer will be created for drawing lines.
+ * tolerance - {Number} Optional value for the distance between a source
+ * vertex and the calculated intersection below which the split will
+ * occur at the vertex.
+ * edge - {Boolean} Allow splits given intersection of edges only. Default
+ * is true. If false, a vertex on the source must be within the
+ * <tolerance> distance of the calculated intersection for a split
+ * to occur.
+ * mutual - {Boolean} If source and target are the same, split source
+ * features and target features where they intersect. Default is
+ * true. If false, only target features will be split.
+ * targetFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the target layer is eligible for
+ * splitting.
+ * sourceFilter - {<OpenLayers.Filter>} Optional filter that will be evaluated
+ * to determine if a feature from the target layer is eligible for
+ * splitting.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.options = options || {}; // TODO: this could be done by the super
+
+ // set the source layer if provided
+ if(this.options.source) {
+ this.setSource(this.options.source);
+ }
+ },
+
+ /**
+ * APIMethod: setSource
+ * Set the source layer for edits layer.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} The new source layer layer. If
+ * null, a temporary sketch layer will be created.
+ */
+ setSource: function(layer) {
+ if(this.active) {
+ this.deactivate();
+ if(this.handler) {
+ this.handler.destroy();
+ delete this.handler;
+ }
+ this.source = layer;
+ this.activate();
+ } else {
+ this.source = layer;
+ }
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the control. Activating the control registers listeners for
+ * editing related events so that during feature creation and
+ * modification, features in the target will be considered for
+ * splitting.
+ */
+ activate: function() {
+ var activated = OpenLayers.Control.prototype.activate.call(this);
+ if(activated) {
+ if(!this.source) {
+ if(!this.handler) {
+ this.handler = new OpenLayers.Handler.Path(this,
+ {done: function(geometry) {
+ this.onSketchComplete({
+ feature: new OpenLayers.Feature.Vector(geometry)
+ });
+ }},
+ {layerOptions: this.sourceOptions}
+ );
+ }
+ this.handler.activate();
+ } else if(this.source.events) {
+ this.source.events.on({
+ sketchcomplete: this.onSketchComplete,
+ afterfeaturemodified: this.afterFeatureModified,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the control. Deactivating the control unregisters listeners
+ * so feature editing may proceed without engaging the split agent.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
+ if(deactivated) {
+ if(this.source && this.source.events) {
+ this.source.events.un({
+ sketchcomplete: this.onSketchComplete,
+ afterfeaturemodified: this.afterFeatureModified,
+ scope: this
+ });
+ }
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: onSketchComplete
+ * Registered as a listener for the sketchcomplete event on the editable
+ * layer.
+ *
+ * Parameters:
+ * event - {Object} The sketch complete event.
+ *
+ * Returns:
+ * {Boolean} Stop the sketch from being added to the layer (it has been
+ * split).
+ */
+ onSketchComplete: function(event) {
+ this.feature = null;
+ return !this.considerSplit(event.feature);
+ },
+
+ /**
+ * Method: afterFeatureModified
+ * Registered as a listener for the afterfeaturemodified event on the
+ * editable layer.
+ *
+ * Parameters:
+ * event - {Object} The after feature modified event.
+ */
+ afterFeatureModified: function(event) {
+ if(event.modified) {
+ var feature = event.feature;
+ if (typeof feature.geometry.split === "function") {
+ this.feature = event.feature;
+ this.considerSplit(event.feature);
+ }
+ }
+ },
+
+ /**
+ * Method: removeByGeometry
+ * Remove a feature from a list based on the given geometry.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} A list of features.
+ * geometry - {<OpenLayers.Geometry>} A geometry.
+ */
+ removeByGeometry: function(features, geometry) {
+ for(var i=0, len=features.length; i<len; ++i) {
+ if(features[i].geometry === geometry) {
+ features.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: isEligible
+ * Test if a target feature is eligible for splitting.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Feature.Vector>} The target feature.
+ *
+ * Returns:
+ * {Boolean} The target is eligible for splitting.
+ */
+ isEligible: function(target) {
+ if (!target.geometry) {
+ return false;
+ } else {
+ return (
+ target.state !== OpenLayers.State.DELETE
+ ) && (
+ typeof target.geometry.split === "function"
+ ) && (
+ this.feature !== target
+ ) && (
+ !this.targetFilter ||
+ this.targetFilter.evaluate(target.attributes)
+ );
+ }
+ },
+
+ /**
+ * Method: considerSplit
+ * Decide whether or not to split target features with the supplied
+ * feature. If <mutual> is true, both the source and target features
+ * will be split if eligible.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The newly created or modified
+ * feature.
+ *
+ * Returns:
+ * {Boolean} The supplied feature was split (and destroyed).
+ */
+ considerSplit: function(feature) {
+ var sourceSplit = false;
+ var targetSplit = false;
+ if(!this.sourceFilter ||
+ this.sourceFilter.evaluate(feature.attributes)) {
+ var features = this.layer && this.layer.features || [];
+ var target, results, proceed;
+ var additions = [], removals = [];
+ var mutual = (this.layer === this.source) && this.mutual;
+ var options = {
+ edge: this.edge,
+ tolerance: this.tolerance,
+ mutual: mutual
+ };
+ var sourceParts = [feature.geometry];
+ var targetFeature, targetParts;
+ var source, parts;
+ for(var i=0, len=features.length; i<len; ++i) {
+ targetFeature = features[i];
+ if(this.isEligible(targetFeature)) {
+ targetParts = [targetFeature.geometry];
+ // work through source geoms - this array may change
+ for(var j=0; j<sourceParts.length; ++j) {
+ source = sourceParts[j];
+ // work through target parts - this array may change
+ for(var k=0; k<targetParts.length; ++k) {
+ target = targetParts[k];
+ if(source.getBounds().intersectsBounds(target.getBounds())) {
+ results = source.split(target, options);
+ if(results) {
+ proceed = this.events.triggerEvent(
+ "beforesplit", {source: feature, target: targetFeature}
+ );
+ if(proceed !== false) {
+ if(mutual) {
+ parts = results[0];
+ // handle parts that result from source splitting
+ if(parts.length > 1) {
+ // splice in new source parts
+ parts.unshift(j, 1); // add args for splice below
+ Array.prototype.splice.apply(sourceParts, parts);
+ j += parts.length - 3;
+ }
+ results = results[1];
+ }
+ // handle parts that result from target splitting
+ if(results.length > 1) {
+ // splice in new target parts
+ results.unshift(k, 1); // add args for splice below
+ Array.prototype.splice.apply(targetParts, results);
+ k += results.length - 3;
+ }
+ }
+ }
+ }
+ }
+ }
+ if(targetParts && targetParts.length > 1) {
+ this.geomsToFeatures(targetFeature, targetParts);
+ this.events.triggerEvent("split", {
+ original: targetFeature,
+ features: targetParts
+ });
+ Array.prototype.push.apply(additions, targetParts);
+ removals.push(targetFeature);
+ targetSplit = true;
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ this.geomsToFeatures(feature, sourceParts);
+ this.events.triggerEvent("split", {
+ original: feature,
+ features: sourceParts
+ });
+ Array.prototype.push.apply(additions, sourceParts);
+ removals.push(feature);
+ sourceSplit = true;
+ }
+ if(sourceSplit || targetSplit) {
+ // remove and add feature events are suppressed
+ // listen for split event on this control instead
+ if(this.deferDelete) {
+ // Set state instead of removing. Take care to avoid
+ // setting delete for features that have not yet been
+ // inserted - those should be destroyed immediately.
+ var feat, destroys = [];
+ for(var i=0, len=removals.length; i<len; ++i) {
+ feat = removals[i];
+ if(feat.state === OpenLayers.State.INSERT) {
+ destroys.push(feat);
+ } else {
+ feat.state = OpenLayers.State.DELETE;
+ this.layer.drawFeature(feat);
+ }
+ }
+ this.layer.destroyFeatures(destroys, {silent: true});
+ for(var i=0, len=additions.length; i<len; ++i) {
+ additions[i].state = OpenLayers.State.INSERT;
+ }
+ } else {
+ this.layer.destroyFeatures(removals, {silent: true});
+ }
+ this.layer.addFeatures(additions, {silent: true});
+ this.events.triggerEvent("aftersplit", {
+ source: feature,
+ features: additions
+ });
+ }
+ }
+ return sourceSplit;
+ },
+
+ /**
+ * Method: geomsToFeatures
+ * Create new features given a template feature and a list of geometries.
+ * The list of geometries is modified in place. The result will be
+ * a list of new features.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature to be cloned.
+ * geoms - {Array(<OpenLayers.Geometry>)} List of goemetries. This will
+ * become a list of new features.
+ */
+ geomsToFeatures: function(feature, geoms) {
+ var clone = feature.clone();
+ delete clone.geometry;
+ var newFeature;
+ for(var i=0, len=geoms.length; i<len; ++i) {
+ // turn results list from geoms to features
+ newFeature = clone.clone();
+ newFeature.geometry = geoms[i];
+ newFeature.state = OpenLayers.State.INSERT;
+ geoms[i] = newFeature;
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up the control.
+ */
+ destroy: function() {
+ if(this.active) {
+ this.deactivate(); // TODO: this should be handled by the super
+ }
+ OpenLayers.Control.prototype.destroy.call(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Split"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/TouchNavigation.js b/misc/openlayers/lib/OpenLayers/Control/TouchNavigation.js
new file mode 100644
index 0000000..cd5f926
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/TouchNavigation.js
@@ -0,0 +1,182 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/DragPan.js
+ * @requires OpenLayers/Control/PinchZoom.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.TouchNavigation
+ * The navigation control handles map browsing with touch events (dragging,
+ * double-tapping, tap with two fingers, and pinch zoom). Create a new
+ * control with the <OpenLayers.Control.TouchNavigation> constructor.
+ *
+ * If you’re only targeting touch enabled devices with your mapping application,
+ * you can create a map with only a TouchNavigation control. The
+ * <OpenLayers.Control.Navigation> control is mobile ready by default, but
+ * you can generate a smaller build of the library by only including this
+ * touch navigation control if you aren't concerned about mouse interaction.
+ *
+ * Inherits:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.TouchNavigation = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * Property: dragPan
+ * {<OpenLayers.Control.DragPan>}
+ */
+ dragPan: null,
+
+ /**
+ * APIProperty: dragPanOptions
+ * {Object} Options passed to the DragPan control.
+ */
+ dragPanOptions: null,
+
+ /**
+ * Property: pinchZoom
+ * {<OpenLayers.Control.PinchZoom>}
+ */
+ pinchZoom: null,
+
+ /**
+ * APIProperty: pinchZoomOptions
+ * {Object} Options passed to the PinchZoom control.
+ */
+ pinchZoomOptions: null,
+
+ /**
+ * APIProperty: clickHandlerOptions
+ * {Object} Options passed to the Click handler.
+ */
+ clickHandlerOptions: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} Allow panning of the map by dragging outside map viewport.
+ * Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * Constructor: OpenLayers.Control.TouchNavigation
+ * Create a new navigation control
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * the control
+ */
+ initialize: function(options) {
+ this.handlers = {};
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: destroy
+ * The destroy method is used to perform any clean up before the control
+ * is dereferenced. Typically this is where event listeners are removed
+ * to prevent memory leaks.
+ */
+ destroy: function() {
+ this.deactivate();
+ if(this.dragPan) {
+ this.dragPan.destroy();
+ }
+ this.dragPan = null;
+ if (this.pinchZoom) {
+ this.pinchZoom.destroy();
+ delete this.pinchZoom;
+ }
+ OpenLayers.Control.prototype.destroy.apply(this,arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if(OpenLayers.Control.prototype.activate.apply(this,arguments)) {
+ this.dragPan.activate();
+ this.handlers.click.activate();
+ this.pinchZoom.activate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ if(OpenLayers.Control.prototype.deactivate.apply(this,arguments)) {
+ this.dragPan.deactivate();
+ this.handlers.click.deactivate();
+ this.pinchZoom.deactivate();
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ var clickCallbacks = {
+ click: this.defaultClick,
+ dblclick: this.defaultDblClick
+ };
+ var clickOptions = OpenLayers.Util.extend({
+ "double": true,
+ stopDouble: true,
+ pixelTolerance: 2
+ }, this.clickHandlerOptions);
+ this.handlers.click = new OpenLayers.Handler.Click(
+ this, clickCallbacks, clickOptions
+ );
+ this.dragPan = new OpenLayers.Control.DragPan(
+ OpenLayers.Util.extend({
+ map: this.map,
+ documentDrag: this.documentDrag
+ }, this.dragPanOptions)
+ );
+ this.dragPan.draw();
+ this.pinchZoom = new OpenLayers.Control.PinchZoom(
+ OpenLayers.Util.extend({map: this.map}, this.pinchZoomOptions)
+ );
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if(evt.lastTouches && evt.lastTouches.length == 2) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.map.zoomTo(this.map.zoom + 1, evt.xy);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.TouchNavigation"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/TransformFeature.js b/misc/openlayers/lib/OpenLayers/Control/TransformFeature.js
new file mode 100644
index 0000000..8c21456
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/TransformFeature.js
@@ -0,0 +1,624 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/DragFeature.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Control.TransformFeature
+ * Control to transform features with a standard transformation box.
+ *
+ * Inherits From:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.TransformFeature = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforesetfeature - Triggered before a feature is set for
+ * tranformation. The feature will not be set if a listener returns
+ * false. Listeners receive a *feature* property, with the feature
+ * that will be set for transformation. Listeners are allowed to
+ * set the control's *scale*, *ratio* and *rotation* properties,
+ * which will set the initial scale, ratio and rotation of the
+ * feature, like the <setFeature> method's initialParams argument.
+ * setfeature - Triggered when a feature is set for tranformation.
+ * Listeners receive a *feature* property, with the feature that
+ * is now set for transformation.
+ * beforetransform - Triggered while dragging, before a feature is
+ * transformed. The feature will not be transformed if a listener
+ * returns false (but the box still will). Listeners receive one or
+ * more of *center*, *scale*, *ratio* and *rotation*. The *center*
+ * property is an <OpenLayers.Geometry.Point> object with the new
+ * center of the transformed feature, the others are Floats with the
+ * scale, ratio or rotation change since the last transformation.
+ * transform - Triggered while dragging, when a feature is transformed.
+ * Listeners receive an event object with one or more of *center*,
+ * scale*, *ratio* and *rotation*. The *center* property is an
+ * <OpenLayers.Geometry.Point> object with the new center of the
+ * transformed feature, the others are Floats with the scale, ratio
+ * or rotation change of the feature since the last transformation.
+ * transformcomplete - Triggered after dragging. Listeners receive
+ * an event object with the transformed *feature*.
+ */
+
+ /**
+ * APIProperty: geometryTypes
+ * {Array(String)} To restrict transformation to a limited set of geometry
+ * types, send a list of strings corresponding to the geometry class
+ * names.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>}
+ */
+ layer: null,
+
+ /**
+ * APIProperty: preserveAspectRatio
+ * {Boolean} set to true to not change the feature's aspect ratio.
+ */
+ preserveAspectRatio: false,
+
+ /**
+ * APIProperty: rotate
+ * {Boolean} set to false if rotation should be disabled. Default is true.
+ * To be passed with the constructor or set when the control is not
+ * active.
+ */
+ rotate: true,
+
+ /**
+ * APIProperty: feature
+ * {<OpenLayers.Feature.Vector>} Feature currently available for
+ * transformation. Read-only, use <setFeature> to set it manually.
+ */
+ feature: null,
+
+ /**
+ * APIProperty: renderIntent
+ * {String|Object} Render intent for the transformation box and
+ * handles. A symbolizer object can also be provided here.
+ */
+ renderIntent: "temporary",
+
+ /**
+ * APIProperty: rotationHandleSymbolizer
+ * {Object|String} Optional. A custom symbolizer for the rotation handles.
+ * A render intent can also be provided here. Defaults to
+ * (code)
+ * {
+ * stroke: false,
+ * pointRadius: 10,
+ * fillOpacity: 0,
+ * cursor: "pointer"
+ * }
+ * (end)
+ */
+ rotationHandleSymbolizer: null,
+
+ /**
+ * APIProperty: box
+ * {<OpenLayers.Feature.Vector>} The transformation box rectangle.
+ * Read-only.
+ */
+ box: null,
+
+ /**
+ * APIProperty: center
+ * {<OpenLayers.Geometry.Point>} The center of the feature bounds.
+ * Read-only.
+ */
+ center: null,
+
+ /**
+ * APIProperty: scale
+ * {Float} The scale of the feature, relative to the scale the time the
+ * feature was set. Read-only, except for *beforesetfeature*
+ * listeners.
+ */
+ scale: 1,
+
+ /**
+ * APIProperty: ratio
+ * {Float} The ratio of the feature relative to the ratio the time the
+ * feature was set. Read-only, except for *beforesetfeature*
+ * listeners.
+ */
+ ratio: 1,
+
+ /**
+ * Property: rotation
+ * {Integer} the current rotation angle of the box. Read-only, except for
+ * *beforesetfeature* listeners.
+ */
+ rotation: 0,
+
+ /**
+ * APIProperty: handles
+ * {Array(<OpenLayers.Feature.Vector>)} The 8 handles currently available
+ * for scaling/resizing. Numbered counterclockwise, starting from the
+ * southwest corner. Read-only.
+ */
+ handles: null,
+
+ /**
+ * APIProperty: rotationHandles
+ * {Array(<OpenLayers.Feature.Vector>)} The 4 rotation handles currently
+ * available for rotating. Numbered counterclockwise, starting from
+ * the southwest corner. Read-only.
+ */
+ rotationHandles: null,
+
+ /**
+ * Property: dragControl
+ * {<OpenLayers.Control.DragFeature>}
+ */
+ dragControl: null,
+
+ /**
+ * APIProperty: irregular
+ * {Boolean} Make scaling/resizing work irregularly. If true then
+ * dragging a handle causes the feature to resize in the direction
+ * of movement. If false then the feature resizes symetrically
+ * about it's center.
+ */
+ irregular: false,
+
+ /**
+ * Constructor: OpenLayers.Control.TransformFeature
+ * Create a new transform feature control.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+ * will be transformed.
+ * options - {Object} Optional object whose properties will be set on the
+ * control.
+ */
+ initialize: function(layer, options) {
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ this.layer = layer;
+
+ if(!this.rotationHandleSymbolizer) {
+ this.rotationHandleSymbolizer = {
+ stroke: false,
+ pointRadius: 10,
+ fillOpacity: 0,
+ cursor: "pointer"
+ };
+ }
+
+ this.createBox();
+ this.createControl();
+ },
+
+ /**
+ * APIMethod: activate
+ * Activates the control.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+ this.dragControl.activate();
+ this.layer.addFeatures([this.box]);
+ this.rotate && this.layer.addFeatures(this.rotationHandles);
+ this.layer.addFeatures(this.handles);
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivates the control.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+ this.layer.removeFeatures(this.handles);
+ this.rotate && this.layer.removeFeatures(this.rotationHandles);
+ this.layer.removeFeatures([this.box]);
+ this.dragControl.deactivate();
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ this.dragControl.setMap(map);
+ OpenLayers.Control.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setFeature
+ * Place the transformation box on a feature and start transforming it.
+ * If the control is not active, it will be activated.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * initialParams - {Object} Initial values for rotation, scale or ratio.
+ * Setting a rotation value here will cause the transformation box to
+ * start rotated. Setting a scale or ratio will not affect the
+ * transormation box, but applications may use this to keep track of
+ * scale and ratio of a feature across multiple transforms.
+ */
+ setFeature: function(feature, initialParams) {
+ initialParams = OpenLayers.Util.applyDefaults(initialParams, {
+ rotation: 0,
+ scale: 1,
+ ratio: 1
+ });
+
+ var oldRotation = this.rotation;
+ var oldCenter = this.center;
+ OpenLayers.Util.extend(this, initialParams);
+
+ var cont = this.events.triggerEvent("beforesetfeature",
+ {feature: feature}
+ );
+ if (cont === false) {
+ return;
+ }
+
+ this.feature = feature;
+ this.activate();
+
+ this._setfeature = true;
+
+ var featureBounds = this.feature.geometry.getBounds();
+ this.box.move(featureBounds.getCenterLonLat());
+ this.box.geometry.rotate(-oldRotation, oldCenter);
+ this._angle = 0;
+
+ var ll;
+ if(this.rotation) {
+ var geom = feature.geometry.clone();
+ geom.rotate(-this.rotation, this.center);
+ var box = new OpenLayers.Feature.Vector(
+ geom.getBounds().toGeometry());
+ box.geometry.rotate(this.rotation, this.center);
+ this.box.geometry.rotate(this.rotation, this.center);
+ this.box.move(box.geometry.getBounds().getCenterLonLat());
+ var llGeom = box.geometry.components[0].components[0];
+ ll = llGeom.getBounds().getCenterLonLat();
+ } else {
+ ll = new OpenLayers.LonLat(featureBounds.left, featureBounds.bottom);
+ }
+ this.handles[0].move(ll);
+
+ delete this._setfeature;
+
+ this.events.triggerEvent("setfeature", {feature: feature});
+ },
+
+ /**
+ * APIMethod: unsetFeature
+ * Remove the transformation box off any feature.
+ * If the control is active, it will be deactivated first.
+ */
+ unsetFeature: function() {
+ if (this.active) {
+ this.deactivate();
+ } else {
+ this.feature = null;
+ this.rotation = 0;
+ this.scale = 1;
+ this.ratio = 1;
+ }
+ },
+
+ /**
+ * Method: createBox
+ * Creates the box with all handles and transformation handles.
+ */
+ createBox: function() {
+ var control = this;
+
+ this.center = new OpenLayers.Geometry.Point(0, 0);
+ this.box = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-1, -1),
+ new OpenLayers.Geometry.Point(0, -1),
+ new OpenLayers.Geometry.Point(1, -1),
+ new OpenLayers.Geometry.Point(1, 0),
+ new OpenLayers.Geometry.Point(1, 1),
+ new OpenLayers.Geometry.Point(0, 1),
+ new OpenLayers.Geometry.Point(-1, 1),
+ new OpenLayers.Geometry.Point(-1, 0),
+ new OpenLayers.Geometry.Point(-1, -1)
+ ]), null,
+ typeof this.renderIntent == "string" ? null : this.renderIntent
+ );
+
+ // Override for box move - make sure that the center gets updated
+ this.box.geometry.move = function(x, y) {
+ control._moving = true;
+ OpenLayers.Geometry.LineString.prototype.move.apply(this, arguments);
+ control.center.move(x, y);
+ delete control._moving;
+ };
+
+ // Overrides for vertex move, resize and rotate - make sure that
+ // handle and rotationHandle geometries are also moved, resized and
+ // rotated.
+ var vertexMoveFn = function(x, y) {
+ OpenLayers.Geometry.Point.prototype.move.apply(this, arguments);
+ this._rotationHandle && this._rotationHandle.geometry.move(x, y);
+ this._handle.geometry.move(x, y);
+ };
+ var vertexResizeFn = function(scale, center, ratio) {
+ OpenLayers.Geometry.Point.prototype.resize.apply(this, arguments);
+ this._rotationHandle && this._rotationHandle.geometry.resize(
+ scale, center, ratio);
+ this._handle.geometry.resize(scale, center, ratio);
+ };
+ var vertexRotateFn = function(angle, center) {
+ OpenLayers.Geometry.Point.prototype.rotate.apply(this, arguments);
+ this._rotationHandle && this._rotationHandle.geometry.rotate(
+ angle, center);
+ this._handle.geometry.rotate(angle, center);
+ };
+
+ // Override for handle move - make sure that the box and other handles
+ // are updated, and finally transform the feature.
+ var handleMoveFn = function(x, y) {
+ var oldX = this.x, oldY = this.y;
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ if(control._moving) {
+ return;
+ }
+ var evt = control.dragControl.handlers.drag.evt;
+ var preserveAspectRatio = !control._setfeature &&
+ control.preserveAspectRatio;
+ var reshape = !preserveAspectRatio && !(evt && evt.shiftKey);
+ var oldGeom = new OpenLayers.Geometry.Point(oldX, oldY);
+ var centerGeometry = control.center;
+ this.rotate(-control.rotation, centerGeometry);
+ oldGeom.rotate(-control.rotation, centerGeometry);
+ var dx1 = this.x - centerGeometry.x;
+ var dy1 = this.y - centerGeometry.y;
+ var dx0 = dx1 - (this.x - oldGeom.x);
+ var dy0 = dy1 - (this.y - oldGeom.y);
+ if (control.irregular && !control._setfeature) {
+ dx1 -= (this.x - oldGeom.x) / 2;
+ dy1 -= (this.y - oldGeom.y) / 2;
+ }
+ this.x = oldX;
+ this.y = oldY;
+ var scale, ratio = 1;
+ if (reshape) {
+ scale = Math.abs(dy0) < 0.00001 ? 1 : dy1 / dy0;
+ ratio = (Math.abs(dx0) < 0.00001 ? 1 : (dx1 / dx0)) / scale;
+ } else {
+ var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+ var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+ scale = l1 / l0;
+ }
+
+ // rotate the box to 0 before resizing - saves us some
+ // calculations and is inexpensive because we don't drawFeature.
+ control._moving = true;
+ control.box.geometry.rotate(-control.rotation, centerGeometry);
+ delete control._moving;
+
+ control.box.geometry.resize(scale, centerGeometry, ratio);
+ control.box.geometry.rotate(control.rotation, centerGeometry);
+ control.transformFeature({scale: scale, ratio: ratio});
+ if (control.irregular && !control._setfeature) {
+ var newCenter = centerGeometry.clone();
+ newCenter.x += Math.abs(oldX - centerGeometry.x) < 0.00001 ? 0 : (this.x - oldX);
+ newCenter.y += Math.abs(oldY - centerGeometry.y) < 0.00001 ? 0 : (this.y - oldY);
+ control.box.geometry.move(this.x - oldX, this.y - oldY);
+ control.transformFeature({center: newCenter});
+ }
+ };
+
+ // Override for rotation handle move - make sure that the box and
+ // other handles are updated, and finally transform the feature.
+ var rotationHandleMoveFn = function(x, y){
+ var oldX = this.x, oldY = this.y;
+ OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+ if(control._moving) {
+ return;
+ }
+ var evt = control.dragControl.handlers.drag.evt;
+ var constrain = (evt && evt.shiftKey) ? 45 : 1;
+ var centerGeometry = control.center;
+ var dx1 = this.x - centerGeometry.x;
+ var dy1 = this.y - centerGeometry.y;
+ var dx0 = dx1 - x;
+ var dy0 = dy1 - y;
+ this.x = oldX;
+ this.y = oldY;
+ var a0 = Math.atan2(dy0, dx0);
+ var a1 = Math.atan2(dy1, dx1);
+ var angle = a1 - a0;
+ angle *= 180 / Math.PI;
+ control._angle = (control._angle + angle) % 360;
+ var diff = control.rotation % constrain;
+ if(Math.abs(control._angle) >= constrain || diff !== 0) {
+ angle = Math.round(control._angle / constrain) * constrain -
+ diff;
+ control._angle = 0;
+ control.box.geometry.rotate(angle, centerGeometry);
+ control.transformFeature({rotation: angle});
+ }
+ };
+
+ var handles = new Array(8);
+ var rotationHandles = new Array(4);
+ var geom, handle, rotationHandle;
+ var positions = ["sw", "s", "se", "e", "ne", "n", "nw", "w"];
+ for(var i=0; i<8; ++i) {
+ geom = this.box.geometry.components[i];
+ handle = new OpenLayers.Feature.Vector(geom.clone(), {
+ role: positions[i] + "-resize"
+ }, typeof this.renderIntent == "string" ? null :
+ this.renderIntent);
+ if(i % 2 == 0) {
+ rotationHandle = new OpenLayers.Feature.Vector(geom.clone(), {
+ role: positions[i] + "-rotate"
+ }, typeof this.rotationHandleSymbolizer == "string" ?
+ null : this.rotationHandleSymbolizer);
+ rotationHandle.geometry.move = rotationHandleMoveFn;
+ geom._rotationHandle = rotationHandle;
+ rotationHandles[i/2] = rotationHandle;
+ }
+ geom.move = vertexMoveFn;
+ geom.resize = vertexResizeFn;
+ geom.rotate = vertexRotateFn;
+ handle.geometry.move = handleMoveFn;
+ geom._handle = handle;
+ handles[i] = handle;
+ }
+
+ this.rotationHandles = rotationHandles;
+ this.handles = handles;
+ },
+
+ /**
+ * Method: createControl
+ * Creates a DragFeature control for this control.
+ */
+ createControl: function() {
+ var control = this;
+ this.dragControl = new OpenLayers.Control.DragFeature(this.layer, {
+ documentDrag: true,
+ // avoid moving the feature itself - move the box instead
+ moveFeature: function(pixel) {
+ if(this.feature === control.feature) {
+ this.feature = control.box;
+ }
+ OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this,
+ arguments);
+ },
+ // transform while dragging
+ onDrag: function(feature, pixel) {
+ if(feature === control.box) {
+ control.transformFeature({center: control.center});
+ }
+ },
+ // set a new feature
+ onStart: function(feature, pixel) {
+ var eligible = !control.geometryTypes ||
+ OpenLayers.Util.indexOf(control.geometryTypes,
+ feature.geometry.CLASS_NAME) !== -1;
+ var i = OpenLayers.Util.indexOf(control.handles, feature);
+ i += OpenLayers.Util.indexOf(control.rotationHandles,
+ feature);
+ if(feature !== control.feature && feature !== control.box &&
+ i == -2 && eligible) {
+ control.setFeature(feature);
+ }
+ },
+ onComplete: function(feature, pixel) {
+ control.events.triggerEvent("transformcomplete",
+ {feature: control.feature});
+ }
+ });
+ },
+
+ /**
+ * Method: drawHandles
+ * Draws the handles to match the box.
+ */
+ drawHandles: function() {
+ var layer = this.layer;
+ for(var i=0; i<8; ++i) {
+ if(this.rotate && i % 2 === 0) {
+ layer.drawFeature(this.rotationHandles[i/2],
+ this.rotationHandleSymbolizer);
+ }
+ layer.drawFeature(this.handles[i], this.renderIntent);
+ }
+ },
+
+ /**
+ * Method: transformFeature
+ * Transforms the feature.
+ *
+ * Parameters:
+ * mods - {Object} An object with optional scale, ratio, rotation and
+ * center properties.
+ */
+ transformFeature: function(mods) {
+ if(!this._setfeature) {
+ this.scale *= (mods.scale || 1);
+ this.ratio *= (mods.ratio || 1);
+ var oldRotation = this.rotation;
+ this.rotation = (this.rotation + (mods.rotation || 0)) % 360;
+
+ if(this.events.triggerEvent("beforetransform", mods) !== false) {
+ var feature = this.feature;
+ var geom = feature.geometry;
+ var center = this.center;
+ geom.rotate(-oldRotation, center);
+ if(mods.scale || mods.ratio) {
+ geom.resize(mods.scale, center, mods.ratio);
+ } else if(mods.center) {
+ feature.move(mods.center.getBounds().getCenterLonLat());
+ }
+ geom.rotate(this.rotation, center);
+ this.layer.drawFeature(feature);
+ feature.toState(OpenLayers.State.UPDATE);
+ this.events.triggerEvent("transform", mods);
+ }
+ }
+ this.layer.drawFeature(this.box, this.renderIntent);
+ this.drawHandles();
+ },
+
+ /**
+ * APIMethod: destroy
+ * Take care of things that are not handled in superclass.
+ */
+ destroy: function() {
+ var geom;
+ for(var i=0; i<8; ++i) {
+ geom = this.box.geometry.components[i];
+ geom._handle.destroy();
+ geom._handle = null;
+ geom._rotationHandle && geom._rotationHandle.destroy();
+ geom._rotationHandle = null;
+ }
+ this.center = null;
+ this.feature = null;
+ this.handles = null;
+ this.rotationHandleSymbolizer = null;
+ this.rotationHandles = null;
+ this.box.destroy();
+ this.box = null;
+ this.layer = null;
+ this.dragControl.destroy();
+ this.dragControl = null;
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.TransformFeature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/UTFGrid.js b/misc/openlayers/lib/OpenLayers/Control/UTFGrid.js
new file mode 100644
index 0000000..7993201
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/UTFGrid.js
@@ -0,0 +1,240 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Handler/Click.js
+ */
+
+/**
+ * Class: OpenLayers.Control.UTFGrid
+ *
+ * This Control provides behavior associated with UTFGrid Layers.
+ * These 'hit grids' provide underlying feature attributes without
+ * calling the server (again). This control allows Mousemove, Hovering
+ * and Click events to trigger callbacks that use the attributes in
+ * whatever way you need.
+ *
+ * The most common example may be a UTFGrid layer containing feature
+ * attributes that are displayed in a div as you mouseover.
+ *
+ * Example Code:
+ *
+ * (start code)
+ * var world_utfgrid = new OpenLayers.Layer.UTFGrid(
+ * 'UTFGrid Layer',
+ * "http://tiles/world_utfgrid/${z}/${x}/${y}.json"
+ * );
+ * map.addLayer(world_utfgrid);
+ *
+ * var control = new OpenLayers.Control.UTFGrid({
+ * layers: [world_utfgrid],
+ * handlerMode: 'move',
+ * callback: function(infoLookup) {
+ * // do something with returned data
+ *
+ * }
+ * })
+ * (end code)
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.UTFGrid = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: autoActivate
+ * {Boolean} Activate the control when it is added to a map. Default is
+ * true.
+ */
+ autoActivate: true,
+
+ /**
+ * APIProperty: Layers
+ * List of layers to consider. Must be Layer.UTFGrids
+ * `null` is the default indicating all UTFGrid Layers are queried.
+ * {Array} <OpenLayers.Layer.UTFGrid>
+ */
+ layers: null,
+
+ /* Property: defaultHandlerOptions
+ * The default opts passed to the handler constructors
+ */
+ defaultHandlerOptions: {
+ 'delay': 300,
+ 'pixelTolerance': 4,
+ 'stopMove': false,
+ 'single': true,
+ 'double': false,
+ 'stopSingle': false,
+ 'stopDouble': false
+ },
+
+ /* APIProperty: handlerMode
+ * Defaults to 'click'. Can be 'hover' or 'move'.
+ */
+ handlerMode: 'click',
+
+ /**
+ * APIMethod: setHandler
+ * sets this.handlerMode and calls resetHandler()
+ *
+ * Parameters:
+ * hm - {String} Handler Mode string; 'click', 'hover' or 'move'.
+ */
+ setHandler: function(hm) {
+ this.handlerMode = hm;
+ this.resetHandler();
+ },
+
+ /**
+ * Method: resetHandler
+ * Deactivates the old hanlder and creates a new
+ * <OpenLayers.Handler> based on the mode specified in
+ * this.handlerMode
+ *
+ */
+ resetHandler: function() {
+ if (this.handler) {
+ this.handler.deactivate();
+ this.handler.destroy();
+ this.handler = null;
+ }
+
+ if (this.handlerMode == 'hover') {
+ // Handle this event on hover
+ this.handler = new OpenLayers.Handler.Hover(
+ this,
+ {'pause': this.handleEvent, 'move': this.reset},
+ this.handlerOptions
+ );
+ } else if (this.handlerMode == 'click') {
+ // Handle this event on click
+ this.handler = new OpenLayers.Handler.Click(
+ this, {
+ 'click': this.handleEvent
+ }, this.handlerOptions
+ );
+ } else if (this.handlerMode == 'move') {
+ this.handler = new OpenLayers.Handler.Hover(
+ this,
+ // Handle this event while hovering OR moving
+ {'pause': this.handleEvent, 'move': this.handleEvent},
+ this.handlerOptions
+ );
+ }
+ if (this.handler) {
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Constructor: <OpenLayers.Control.UTFGrid>
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.handlerOptions = options.handlerOptions || this.defaultHandlerOptions;
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+ this.resetHandler();
+ },
+
+ /**
+ * Method: handleEvent
+ * Internal method called when specified event is triggered.
+ *
+ * This method does several things:
+ *
+ * Gets the lonLat of the event.
+ *
+ * Loops through the appropriate hit grid layers and gathers the attributes.
+ *
+ * Passes the attributes to the callback
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ handleEvent: function(evt) {
+ if (evt == null) {
+ this.reset();
+ return;
+ }
+
+ var lonLat = this.map.getLonLatFromPixel(evt.xy);
+ if (!lonLat) {
+ return;
+ }
+
+ var layers = this.findLayers();
+ if (layers.length > 0) {
+ var infoLookup = {};
+ var layer, idx;
+ for (var i=0, len=layers.length; i<len; i++) {
+ layer = layers[i];
+ idx = OpenLayers.Util.indexOf(this.map.layers, layer);
+ infoLookup[idx] = layer.getFeatureInfo(lonLat);
+ }
+ this.callback(infoLookup, lonLat, evt.xy);
+ }
+ },
+
+ /**
+ * APIMethod: callback
+ * Function to be called when a mouse event corresponds with a location that
+ * includes data in one of the configured UTFGrid layers.
+ *
+ * Parameters:
+ * infoLookup - {Object} Keys of this object are layer indexes and can be
+ * used to resolve a layer in the map.layers array. The structure of
+ * the property values depend on the data included in the underlying
+ * UTFGrid and may be any valid JSON type.
+ */
+ callback: function(infoLookup) {
+ // to be provided in the constructor
+ },
+
+ /**
+ * Method: reset
+ * Calls the callback with null.
+ */
+ reset: function(evt) {
+ this.callback(null);
+ },
+
+ /**
+ * Method: findLayers
+ * Internal method to get the layers, independent of whether we are
+ * inspecting the map or using a client-provided array
+ *
+ * The default value of this.layers is null; this causes the
+ * findLayers method to return ALL UTFGrid layers encountered.
+ *
+ * Parameters:
+ * None
+ *
+ * Returns:
+ * {Array} Layers to handle on each event
+ */
+ findLayers: function() {
+ var candidates = this.layers || this.map.layers;
+ var layers = [];
+ var layer;
+ for (var i=candidates.length-1; i>=0; --i) {
+ layer = candidates[i];
+ if (layer instanceof OpenLayers.Layer.UTFGrid ) {
+ layers.push(layer);
+ }
+ }
+ return layers;
+ },
+
+ CLASS_NAME: "OpenLayers.Control.UTFGrid"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/WMSGetFeatureInfo.js b/misc/openlayers/lib/OpenLayers/Control/WMSGetFeatureInfo.js
new file mode 100644
index 0000000..c9242f6
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/WMSGetFeatureInfo.js
@@ -0,0 +1,532 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Format/WMSGetFeatureInfo.js
+ */
+
+/**
+ * Class: OpenLayers.Control.WMSGetFeatureInfo
+ * The WMSGetFeatureInfo control uses a WMS query to get information about a point on the map. The
+ * information may be in a display-friendly format such as HTML, or a machine-friendly format such
+ * as GML, depending on the server's capabilities and the client's configuration. This control
+ * handles click or hover events, attempts to parse the results using an OpenLayers.Format, and
+ * fires a 'getfeatureinfo' event with the click position, the raw body of the response, and an
+ * array of features if it successfully read the response.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send GetFeatureInfo requests when mouse stops moving.
+ * Default is false.
+ */
+ hover: false,
+
+ /**
+ * APIProperty: drillDown
+ * {Boolean} Drill down over all WMS layers in the map. When
+ * using drillDown mode, hover is not possible, and an infoFormat that
+ * returns parseable features is required. Default is false.
+ */
+ drillDown: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a WMS query. This
+ * sets the feature_count parameter on WMS GetFeatureInfo
+ * requests.
+ */
+ maxFeatures: 10,
+
+ /**
+ * APIProperty: clickCallback
+ * {String} The click callback to register in the
+ * {<OpenLayers.Handler.Click>} object created when the hover
+ * option is set to false. Default is "click".
+ */
+ clickCallback: "click",
+
+ /**
+ * APIProperty: output
+ * {String} Either "features" or "object". When triggering a getfeatureinfo
+ * request should we pass on an array of features or an object with with
+ * a "features" property and other properties (such as the url of the
+ * WMS). Default is "features".
+ */
+ output: "features",
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer.WMS>)} The layers to query for feature info.
+ * If omitted, all map WMS layers with a url that matches this <url> or
+ * <layerUrls> will be considered.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: queryVisible
+ * {Boolean} If true, filter out hidden layers when searching the map for
+ * layers to query. Default is false.
+ */
+ queryVisible: false,
+
+ /**
+ * APIProperty: url
+ * {String} The URL of the WMS service to use. If not provided, the url
+ * of the first eligible layer will be used.
+ */
+ url: null,
+
+ /**
+ * APIProperty: layerUrls
+ * {Array(String)} Optional list of urls for layers that should be queried.
+ * This can be used when the layer url differs from the url used for
+ * making GetFeatureInfo requests (in the case of a layer using cached
+ * tiles).
+ */
+ layerUrls: null,
+
+ /**
+ * APIProperty: infoFormat
+ * {String} The mimetype to request from the server. If you are using
+ * drillDown mode and have multiple servers that do not share a common
+ * infoFormat, you can override the control's infoFormat by providing an
+ * INFO_FORMAT parameter in your <OpenLayers.Layer.WMS> instance(s).
+ */
+ infoFormat: 'text/html',
+
+ /**
+ * APIProperty: vendorParams
+ * {Object} Additional parameters that will be added to the request, for
+ * WMS implementations that support them. This could e.g. look like
+ * (start code)
+ * {
+ * radius: 5
+ * }
+ * (end)
+ */
+ vendorParams: {},
+
+ /**
+ * APIProperty: format
+ * {<OpenLayers.Format>} A format for parsing GetFeatureInfo responses.
+ * Default is <OpenLayers.Format.WMSGetFeatureInfo>.
+ */
+ format: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Optional properties to set on the format (if one is not provided
+ * in the <format> property.
+ */
+ formatOptions: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control, e.g.
+ * (start code)
+ * {
+ * "click": {delay: 100},
+ * "hover": {delay: 300}
+ * }
+ * (end)
+ */
+
+ /**
+ * Property: handler
+ * {Object} Reference to the <OpenLayers.Handler> for this control
+ */
+ handler: null,
+
+ /**
+ * Property: hoverRequest
+ * {<OpenLayers.Request>} contains the currently running hover request
+ * (if any).
+ */
+ hoverRequest: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforegetfeatureinfo - Triggered before the request is sent.
+ * The event object has an *xy* property with the position of the
+ * mouse click or hover event that triggers the request.
+ * nogetfeatureinfo - no queryable layers were found.
+ * getfeatureinfo - Triggered when a GetFeatureInfo response is received.
+ * The event object has a *text* property with the body of the
+ * response (String), a *features* property with an array of the
+ * parsed features, an *xy* property with the position of the mouse
+ * click or hover event that triggered the request, and a *request*
+ * property with the request itself. If drillDown is set to true and
+ * multiple requests were issued to collect feature info from all
+ * layers, *text* and *request* will only contain the response body
+ * and request object of the last request.
+ */
+
+ /**
+ * Constructor: <OpenLayers.Control.WMSGetFeatureInfo>
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if(!this.format) {
+ this.format = new OpenLayers.Format.WMSGetFeatureInfo(
+ options.formatOptions
+ );
+ }
+
+ if(this.drillDown === true) {
+ this.hover = false;
+ }
+
+ if(this.hover) {
+ this.handler = new OpenLayers.Handler.Hover(
+ this, {
+ 'move': this.cancelHover,
+ 'pause': this.getInfoForHover
+ },
+ OpenLayers.Util.extend(this.handlerOptions.hover || {}, {
+ 'delay': 250
+ }));
+ } else {
+ var callbacks = {};
+ callbacks[this.clickCallback] = this.getInfoForClick;
+ this.handler = new OpenLayers.Handler.Click(
+ this, callbacks, this.handlerOptions.click || {});
+ }
+ },
+
+ /**
+ * Method: getInfoForClick
+ * Called on click
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ getInfoForClick: function(evt) {
+ this.events.triggerEvent("beforegetfeatureinfo", {xy: evt.xy});
+ // Set the cursor to "wait" to tell the user we're working on their
+ // click.
+ OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
+ this.request(evt.xy, {});
+ },
+
+ /**
+ * Method: getInfoForHover
+ * Pause callback for the hover handler
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ getInfoForHover: function(evt) {
+ this.events.triggerEvent("beforegetfeatureinfo", {xy: evt.xy});
+ this.request(evt.xy, {hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Cancel callback for the hover handler
+ */
+ cancelHover: function() {
+ if (this.hoverRequest) {
+ this.hoverRequest.abort();
+ this.hoverRequest = null;
+ }
+ },
+
+ /**
+ * Method: findLayers
+ * Internal method to get the layers, independent of whether we are
+ * inspecting the map or using a client-provided array
+ */
+ findLayers: function() {
+
+ var candidates = this.layers || this.map.layers;
+ var layers = [];
+ var layer, url;
+ for(var i = candidates.length - 1; i >= 0; --i) {
+ layer = candidates[i];
+ if(layer instanceof OpenLayers.Layer.WMS &&
+ (!this.queryVisible || layer.getVisibility())) {
+ url = OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url;
+ // if the control was not configured with a url, set it
+ // to the first layer url
+ if(this.drillDown === false && !this.url) {
+ this.url = url;
+ }
+ if(this.drillDown === true || this.urlMatches(url)) {
+ layers.push(layer);
+ }
+ }
+ }
+ return layers;
+ },
+
+ /**
+ * Method: urlMatches
+ * Test to see if the provided url matches either the control <url> or one
+ * of the <layerUrls>.
+ *
+ * Parameters:
+ * url - {String} The url to test.
+ *
+ * Returns:
+ * {Boolean} The provided url matches the control <url> or one of the
+ * <layerUrls>.
+ */
+ urlMatches: function(url) {
+ var matches = OpenLayers.Util.isEquivalentUrl(this.url, url);
+ if(!matches && this.layerUrls) {
+ for(var i=0, len=this.layerUrls.length; i<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(this.layerUrls[i], url)) {
+ matches = true;
+ break;
+ }
+ }
+ }
+ return matches;
+ },
+
+ /**
+ * Method: buildWMSOptions
+ * Build an object with the relevant WMS options for the GetFeatureInfo request
+ *
+ * Parameters:
+ * url - {String} The url to be used for sending the request
+ * layers - {Array(<OpenLayers.Layer.WMS)} An array of layers
+ * clickPosition - {<OpenLayers.Pixel>} The position on the map where the mouse
+ * event occurred.
+ * format - {String} The format from the corresponding GetMap request
+ */
+ buildWMSOptions: function(url, layers, clickPosition, format) {
+ var layerNames = [], styleNames = [];
+ for (var i = 0, len = layers.length; i < len; i++) {
+ if (layers[i].params.LAYERS != null) {
+ layerNames = layerNames.concat(layers[i].params.LAYERS);
+ styleNames = styleNames.concat(this.getStyleNames(layers[i]));
+ }
+ }
+ var firstLayer = layers[0];
+ // use the firstLayer's projection if it matches the map projection -
+ // this assumes that all layers will be available in this projection
+ var projection = this.map.getProjection();
+ var layerProj = firstLayer.projection;
+ if (layerProj && layerProj.equals(this.map.getProjectionObject())) {
+ projection = layerProj.getCode();
+ }
+ var params = OpenLayers.Util.extend({
+ service: "WMS",
+ version: firstLayer.params.VERSION,
+ request: "GetFeatureInfo",
+ exceptions: firstLayer.params.EXCEPTIONS,
+ bbox: this.map.getExtent().toBBOX(null,
+ firstLayer.reverseAxisOrder()),
+ feature_count: this.maxFeatures,
+ height: this.map.getSize().h,
+ width: this.map.getSize().w,
+ format: format,
+ info_format: firstLayer.params.INFO_FORMAT || this.infoFormat
+ }, (parseFloat(firstLayer.params.VERSION) >= 1.3) ?
+ {
+ crs: projection,
+ i: parseInt(clickPosition.x),
+ j: parseInt(clickPosition.y)
+ } :
+ {
+ srs: projection,
+ x: parseInt(clickPosition.x),
+ y: parseInt(clickPosition.y)
+ }
+ );
+ if (layerNames.length != 0) {
+ params = OpenLayers.Util.extend({
+ layers: layerNames,
+ query_layers: layerNames,
+ styles: styleNames
+ }, params);
+ }
+ OpenLayers.Util.applyDefaults(params, this.vendorParams);
+ return {
+ url: url,
+ params: OpenLayers.Util.upperCaseObject(params),
+ callback: function(request) {
+ this.handleResponse(clickPosition, request, url);
+ },
+ scope: this
+ };
+ },
+
+ /**
+ * Method: getStyleNames
+ * Gets the STYLES parameter for the layer. Make sure the STYLES parameter
+ * matches the LAYERS parameter
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>}
+ *
+ * Returns:
+ * {Array(String)} The STYLES parameter
+ */
+ getStyleNames: function(layer) {
+ // in the event of a WMS layer bundling multiple layers but not
+ // specifying styles,we need the same number of commas to specify
+ // the default style for each of the layers. We can't just leave it
+ // blank as we may be including other layers that do specify styles.
+ var styleNames;
+ if (layer.params.STYLES) {
+ styleNames = layer.params.STYLES;
+ } else {
+ if (OpenLayers.Util.isArray(layer.params.LAYERS)) {
+ styleNames = new Array(layer.params.LAYERS.length);
+ } else { // Assume it's a String
+ styleNames = layer.params.LAYERS.replace(/[^,]/g, "");
+ }
+ }
+ return styleNames;
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeatureInfo request to the WMS
+ *
+ * Parameters:
+ * clickPosition - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ * options - {Object} additional options for this method.
+ *
+ * Valid options:
+ * - *hover* {Boolean} true if we do the request for the hover handler
+ */
+ request: function(clickPosition, options) {
+ var layers = this.findLayers();
+ if(layers.length == 0) {
+ this.events.triggerEvent("nogetfeatureinfo");
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ return;
+ }
+
+ options = options || {};
+ if(this.drillDown === false) {
+ var wmsOptions = this.buildWMSOptions(this.url, layers,
+ clickPosition, layers[0].params.FORMAT);
+ var request = OpenLayers.Request.GET(wmsOptions);
+
+ if (options.hover === true) {
+ this.hoverRequest = request;
+ }
+ } else {
+ this._requestCount = 0;
+ this._numRequests = 0;
+ this.features = [];
+ // group according to service url to combine requests
+ var services = {}, url;
+ for(var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ var service, found = false;
+ url = OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url;
+ if(url in services) {
+ services[url].push(layer);
+ } else {
+ this._numRequests++;
+ services[url] = [layer];
+ }
+ }
+ var layers;
+ for (var url in services) {
+ layers = services[url];
+ var wmsOptions = this.buildWMSOptions(url, layers,
+ clickPosition, layers[0].params.FORMAT);
+ OpenLayers.Request.GET(wmsOptions);
+ }
+ }
+ },
+
+ /**
+ * Method: triggerGetFeatureInfo
+ * Trigger the getfeatureinfo event when all is done
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ * xy - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ * features - {Array(<OpenLayers.Feature.Vector>)} or
+ * {Array({Object}) when output is "object". The object has a url and a
+ * features property which contains an array of features.
+ */
+ triggerGetFeatureInfo: function(request, xy, features) {
+ this.events.triggerEvent("getfeatureinfo", {
+ text: request.responseText,
+ features: features,
+ request: request,
+ xy: xy
+ });
+
+ // Reset the cursor.
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ },
+
+ /**
+ * Method: handleResponse
+ * Handler for the GetFeatureInfo response.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ * request - {XMLHttpRequest} The request object.
+ * url - {String} The url which was used for this request.
+ */
+ handleResponse: function(xy, request, url) {
+
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var features = this.format.read(doc);
+ if (this.drillDown === false) {
+ this.triggerGetFeatureInfo(request, xy, features);
+ } else {
+ this._requestCount++;
+ if (this.output === "object") {
+ this._features = (this._features || []).concat(
+ {url: url, features: features}
+ );
+ } else {
+ this._features = (this._features || []).concat(features);
+ }
+ if (this._requestCount === this._numRequests) {
+ this.triggerGetFeatureInfo(request, xy, this._features.concat());
+ delete this._features;
+ delete this._requestCount;
+ delete this._numRequests;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.WMSGetFeatureInfo"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/WMTSGetFeatureInfo.js b/misc/openlayers/lib/OpenLayers/Control/WMTSGetFeatureInfo.js
new file mode 100644
index 0000000..c26f8f3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/WMTSGetFeatureInfo.js
@@ -0,0 +1,400 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Click.js
+ * @requires OpenLayers/Handler/Hover.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Format/WMSGetFeatureInfo.js
+ */
+
+/**
+ * Class: OpenLayers.Control.WMTSGetFeatureInfo
+ * The WMTSGetFeatureInfo control uses a WMTS query to get information about a
+ * point on the map. The information may be in a display-friendly format
+ * such as HTML, or a machine-friendly format such as GML, depending on the
+ * server's capabilities and the client's configuration. This control
+ * handles click or hover events, attempts to parse the results using an
+ * OpenLayers.Format, and fires a 'getfeatureinfo' event for each layer
+ * queried.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.WMTSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: hover
+ * {Boolean} Send GetFeatureInfo requests when mouse stops moving.
+ * Default is false.
+ */
+ hover: false,
+
+ /**
+ * Property: requestEncoding
+ * {String} One of "KVP" or "REST". Only KVP encoding is supported at this
+ * time.
+ */
+ requestEncoding: "KVP",
+
+ /**
+ * APIProperty: drillDown
+ * {Boolean} Drill down over all WMTS layers in the map. When
+ * using drillDown mode, hover is not possible. A getfeatureinfo event
+ * will be fired for each layer queried.
+ */
+ drillDown: false,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Integer} Maximum number of features to return from a WMTS query. This
+ * sets the feature_count parameter on WMTS GetFeatureInfo
+ * requests.
+ */
+ maxFeatures: 10,
+
+ /** APIProperty: clickCallback
+ * {String} The click callback to register in the
+ * {<OpenLayers.Handler.Click>} object created when the hover
+ * option is set to false. Default is "click".
+ */
+ clickCallback: "click",
+
+ /**
+ * Property: layers
+ * {Array(<OpenLayers.Layer.WMTS>)} The layers to query for feature info.
+ * If omitted, all map WMTS layers will be considered.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: queryVisible
+ * {Boolean} Filter out hidden layers when searching the map for layers to
+ * query. Default is true.
+ */
+ queryVisible: true,
+
+ /**
+ * Property: infoFormat
+ * {String} The mimetype to request from the server
+ */
+ infoFormat: 'text/html',
+
+ /**
+ * Property: vendorParams
+ * {Object} Additional parameters that will be added to the request, for
+ * WMTS implementations that support them. This could e.g. look like
+ * (start code)
+ * {
+ * radius: 5
+ * }
+ * (end)
+ */
+ vendorParams: {},
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} A format for parsing GetFeatureInfo responses.
+ * Default is <OpenLayers.Format.WMSGetFeatureInfo>.
+ */
+ format: null,
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional properties to set on the format (if one is not provided
+ * in the <format> property.
+ */
+ formatOptions: null,
+
+ /**
+ * APIProperty: handlerOptions
+ * {Object} Additional options for the handlers used by this control, e.g.
+ * (start code)
+ * {
+ * "click": {delay: 100},
+ * "hover": {delay: 300}
+ * }
+ * (end)
+ */
+
+ /**
+ * Property: handler
+ * {Object} Reference to the <OpenLayers.Handler> for this control
+ */
+ handler: null,
+
+ /**
+ * Property: hoverRequest
+ * {<OpenLayers.Request>} contains the currently running hover request
+ * (if any).
+ */
+ hoverRequest: null,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} Events instance for listeners and triggering
+ * control specific events.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * control.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to those from <OpenLayers.Control.events>):
+ * beforegetfeatureinfo - Triggered before each request is sent.
+ * The event object has an *xy* property with the position of the
+ * mouse click or hover event that triggers the request and a *layer*
+ * property referencing the layer about to be queried. If a listener
+ * returns false, the request will not be issued.
+ * getfeatureinfo - Triggered when a GetFeatureInfo response is received.
+ * The event object has a *text* property with the body of the
+ * response (String), a *features* property with an array of the
+ * parsed features, an *xy* property with the position of the mouse
+ * click or hover event that triggered the request, a *layer* property
+ * referencing the layer queried and a *request* property with the
+ * request itself. If drillDown is set to true, one event will be fired
+ * for each layer queried.
+ * exception - Triggered when a GetFeatureInfo request fails (with a
+ * status other than 200) or whenparsing fails. Listeners will receive
+ * an event with *request*, *xy*, and *layer* properties. In the case
+ * of a parsing error, the event will also contain an *error* property.
+ */
+
+ /**
+ * Property: pending
+ * {Number} The number of pending requests.
+ */
+ pending: 0,
+
+ /**
+ * Constructor: <OpenLayers.Control.WMTSGetFeatureInfo>
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ options.handlerOptions = options.handlerOptions || {};
+
+ OpenLayers.Control.prototype.initialize.apply(this, [options]);
+
+ if (!this.format) {
+ this.format = new OpenLayers.Format.WMSGetFeatureInfo(
+ options.formatOptions
+ );
+ }
+
+ if (this.drillDown === true) {
+ this.hover = false;
+ }
+
+ if (this.hover) {
+ this.handler = new OpenLayers.Handler.Hover(
+ this, {
+ move: this.cancelHover,
+ pause: this.getInfoForHover
+ },
+ OpenLayers.Util.extend(
+ this.handlerOptions.hover || {}, {delay: 250}
+ )
+ );
+ } else {
+ var callbacks = {};
+ callbacks[this.clickCallback] = this.getInfoForClick;
+ this.handler = new OpenLayers.Handler.Click(
+ this, callbacks, this.handlerOptions.click || {}
+ );
+ }
+ },
+
+ /**
+ * Method: getInfoForClick
+ * Called on click
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ getInfoForClick: function(evt) {
+ this.request(evt.xy, {});
+ },
+
+ /**
+ * Method: getInfoForHover
+ * Pause callback for the hover handler
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ getInfoForHover: function(evt) {
+ this.request(evt.xy, {hover: true});
+ },
+
+ /**
+ * Method: cancelHover
+ * Cancel callback for the hover handler
+ */
+ cancelHover: function() {
+ if (this.hoverRequest) {
+ --this.pending;
+ if (this.pending <= 0) {
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ this.pending = 0;
+ }
+ this.hoverRequest.abort();
+ this.hoverRequest = null;
+ }
+ },
+
+ /**
+ * Method: findLayers
+ * Internal method to get the layers, independent of whether we are
+ * inspecting the map or using a client-provided array
+ */
+ findLayers: function() {
+ var candidates = this.layers || this.map.layers;
+ var layers = [];
+ var layer;
+ for (var i=candidates.length-1; i>=0; --i) {
+ layer = candidates[i];
+ if (layer instanceof OpenLayers.Layer.WMTS &&
+ layer.requestEncoding === this.requestEncoding &&
+ (!this.queryVisible || layer.getVisibility())) {
+ layers.push(layer);
+ if (!this.drillDown || this.hover) {
+ break;
+ }
+ }
+ }
+ return layers;
+ },
+
+ /**
+ * Method: buildRequestOptions
+ * Build an object with the relevant options for the GetFeatureInfo request.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMTS>} A WMTS layer.
+ * xy - {<OpenLayers.Pixel>} The position on the map where the
+ * mouse event occurred.
+ */
+ buildRequestOptions: function(layer, xy) {
+ var loc = this.map.getLonLatFromPixel(xy);
+ var getTileUrl = layer.getURL(
+ new OpenLayers.Bounds(loc.lon, loc.lat, loc.lon, loc.lat)
+ );
+ var params = OpenLayers.Util.getParameters(getTileUrl);
+ var tileInfo = layer.getTileInfo(loc);
+ OpenLayers.Util.extend(params, {
+ service: "WMTS",
+ version: layer.version,
+ request: "GetFeatureInfo",
+ infoFormat: this.infoFormat,
+ i: tileInfo.i,
+ j: tileInfo.j
+ });
+ OpenLayers.Util.applyDefaults(params, this.vendorParams);
+ return {
+ url: OpenLayers.Util.isArray(layer.url) ? layer.url[0] : layer.url,
+ params: OpenLayers.Util.upperCaseObject(params),
+ callback: function(request) {
+ this.handleResponse(xy, request, layer);
+ },
+ scope: this
+ };
+ },
+
+ /**
+ * Method: request
+ * Sends a GetFeatureInfo request to the WMTS
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The position on the map where the mouse event
+ * occurred.
+ * options - {Object} additional options for this method.
+ *
+ * Valid options:
+ * - *hover* {Boolean} true if we do the request for the hover handler
+ */
+ request: function(xy, options) {
+ options = options || {};
+ var layers = this.findLayers();
+ if (layers.length > 0) {
+ var issue, layer;
+ for (var i=0, len=layers.length; i<len; i++) {
+ layer = layers[i];
+ issue = this.events.triggerEvent("beforegetfeatureinfo", {
+ xy: xy,
+ layer: layer
+ });
+ if (issue !== false) {
+ ++this.pending;
+ var requestOptions = this.buildRequestOptions(layer, xy);
+ var request = OpenLayers.Request.GET(requestOptions);
+ if (options.hover === true) {
+ this.hoverRequest = request;
+ }
+ }
+ }
+ if (this.pending > 0) {
+ OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait");
+ }
+ }
+ },
+
+ /**
+ * Method: handleResponse
+ * Handler for the GetFeatureInfo response.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The position on the map where the mouse event
+ * occurred.
+ * request - {XMLHttpRequest} The request object.
+ * layer - {<OpenLayers.Layer.WMTS>} The queried layer.
+ */
+ handleResponse: function(xy, request, layer) {
+ --this.pending;
+ if (this.pending <= 0) {
+ OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait");
+ this.pending = 0;
+ }
+ if (request.status && (request.status < 200 || request.status >= 300)) {
+ this.events.triggerEvent("exception", {
+ xy: xy,
+ request: request,
+ layer: layer
+ });
+ } else {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ var features, except;
+ try {
+ features = this.format.read(doc);
+ } catch (error) {
+ except = true;
+ this.events.triggerEvent("exception", {
+ xy: xy,
+ request: request,
+ error: error,
+ layer: layer
+ });
+ }
+ if (!except) {
+ this.events.triggerEvent("getfeatureinfo", {
+ text: request.responseText,
+ features: features,
+ request: request,
+ xy: xy,
+ layer: layer
+ });
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.WMTSGetFeatureInfo"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/Zoom.js b/misc/openlayers/lib/OpenLayers/Control/Zoom.js
new file mode 100644
index 0000000..70140f4
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/Zoom.js
@@ -0,0 +1,138 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Events/buttonclick.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Zoom
+ * The Zoom control is a pair of +/- links for zooming in and out.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, {
+
+ /**
+ * APIProperty: zoomInText
+ * {String}
+ * Text for zoom-in link. Default is "+".
+ */
+ zoomInText: "+",
+
+ /**
+ * APIProperty: zoomInId
+ * {String}
+ * Instead of having the control create a zoom in link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomInLink" will be searched for
+ * and used if it exists.
+ */
+ zoomInId: "olZoomInLink",
+
+ /**
+ * APIProperty: zoomOutText
+ * {String}
+ * Text for zoom-out link. Default is "\u2212".
+ */
+ zoomOutText: "\u2212",
+
+ /**
+ * APIProperty: zoomOutId
+ * {String}
+ * Instead of having the control create a zoom out link, you can provide
+ * the identifier for an anchor element already added to the document.
+ * By default, an element with id "olZoomOutLink" will be searched for
+ * and used if it exists.
+ */
+ zoomOutId: "olZoomOutLink",
+
+ /**
+ * Method: draw
+ *
+ * Returns:
+ * {DOMElement} A reference to the DOMElement containing the zoom links.
+ */
+ draw: function() {
+ var div = OpenLayers.Control.prototype.draw.apply(this),
+ links = this.getOrCreateLinks(div),
+ zoomIn = links.zoomIn,
+ zoomOut = links.zoomOut,
+ eventsInstance = this.map.events;
+
+ if (zoomOut.parentNode !== div) {
+ eventsInstance = this.events;
+ eventsInstance.attachToElement(zoomOut.parentNode);
+ }
+ eventsInstance.register("buttonclick", this, this.onZoomClick);
+
+ this.zoomInLink = zoomIn;
+ this.zoomOutLink = zoomOut;
+ return div;
+ },
+
+ /**
+ * Method: getOrCreateLinks
+ *
+ * Parameters:
+ * el - {DOMElement}
+ *
+ * Return:
+ * {Object} Object with zoomIn and zoomOut properties referencing links.
+ */
+ getOrCreateLinks: function(el) {
+ var zoomIn = document.getElementById(this.zoomInId),
+ zoomOut = document.getElementById(this.zoomOutId);
+ if (!zoomIn) {
+ zoomIn = document.createElement("a");
+ zoomIn.href = "#zoomIn";
+ zoomIn.appendChild(document.createTextNode(this.zoomInText));
+ zoomIn.className = "olControlZoomIn";
+ el.appendChild(zoomIn);
+ }
+ OpenLayers.Element.addClass(zoomIn, "olButton");
+ if (!zoomOut) {
+ zoomOut = document.createElement("a");
+ zoomOut.href = "#zoomOut";
+ zoomOut.appendChild(document.createTextNode(this.zoomOutText));
+ zoomOut.className = "olControlZoomOut";
+ el.appendChild(zoomOut);
+ }
+ OpenLayers.Element.addClass(zoomOut, "olButton");
+ return {
+ zoomIn: zoomIn, zoomOut: zoomOut
+ };
+ },
+
+ /**
+ * Method: onZoomClick
+ * Called when zoomin/out link is clicked.
+ */
+ onZoomClick: function(evt) {
+ var button = evt.buttonElement;
+ if (button === this.zoomInLink) {
+ this.map.zoomIn();
+ } else if (button === this.zoomOutLink) {
+ this.map.zoomOut();
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ if (this.map) {
+ this.map.events.unregister("buttonclick", this, this.onZoomClick);
+ }
+ delete this.zoomInLink;
+ delete this.zoomOutLink;
+ OpenLayers.Control.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.Zoom"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/ZoomBox.js b/misc/openlayers/lib/OpenLayers/Control/ZoomBox.js
new file mode 100644
index 0000000..9d4b2da
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ZoomBox.js
@@ -0,0 +1,129 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Box.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomBox
+ * The ZoomBox control enables zooming directly to a given extent, by drawing
+ * a box on the map. The box is drawn by holding down shift, whilst dragging
+ * the mouse.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, {
+ /**
+ * Property: type
+ * {OpenLayers.Control.TYPE}
+ */
+ type: OpenLayers.Control.TYPE_TOOL,
+
+ /**
+ * Property: out
+ * {Boolean} Should the control be used for zooming out?
+ */
+ out: false,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Zoom only occurs if the keyMask matches the combination of
+ * keys down. Use bitwise operators and one or more of the
+ * <OpenLayers.Handler> constants to construct a keyMask. Leave null if
+ * not used mask. Default is null.
+ */
+ keyMask: null,
+
+ /**
+ * APIProperty: alwaysZoom
+ * {Boolean} Always zoom in/out when box drawn, even if the zoom level does
+ * not change.
+ */
+ alwaysZoom: false,
+
+ /**
+ * APIProperty: zoomOnClick
+ * {Boolean} Should we zoom when no box was dragged, i.e. the user only
+ * clicked? Default is true.
+ */
+ zoomOnClick: true,
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ this.handler = new OpenLayers.Handler.Box( this,
+ {done: this.zoomBox}, {keyMask: this.keyMask} );
+ },
+
+ /**
+ * Method: zoomBox
+ *
+ * Parameters:
+ * position - {<OpenLayers.Bounds>} or {<OpenLayers.Pixel>}
+ */
+ zoomBox: function (position) {
+ if (position instanceof OpenLayers.Bounds) {
+ var bounds,
+ targetCenterPx = position.getCenterPixel();
+ if (!this.out) {
+ var minXY = this.map.getLonLatFromPixel({
+ x: position.left,
+ y: position.bottom
+ });
+ var maxXY = this.map.getLonLatFromPixel({
+ x: position.right,
+ y: position.top
+ });
+ bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat,
+ maxXY.lon, maxXY.lat);
+ } else {
+ var pixWidth = position.right - position.left;
+ var pixHeight = position.bottom - position.top;
+ var zoomFactor = Math.min((this.map.size.h / pixHeight),
+ (this.map.size.w / pixWidth));
+ var extent = this.map.getExtent();
+ var center = this.map.getLonLatFromPixel(targetCenterPx);
+ var xmin = center.lon - (extent.getWidth()/2)*zoomFactor;
+ var xmax = center.lon + (extent.getWidth()/2)*zoomFactor;
+ var ymin = center.lat - (extent.getHeight()/2)*zoomFactor;
+ var ymax = center.lat + (extent.getHeight()/2)*zoomFactor;
+ bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax);
+ }
+ // always zoom in/out
+ var lastZoom = this.map.getZoom(),
+ size = this.map.getSize(),
+ centerPx = {x: size.w / 2, y: size.h / 2},
+ zoom = this.map.getZoomForExtent(bounds),
+ oldRes = this.map.getResolution(),
+ newRes = this.map.getResolutionForZoom(zoom);
+ if (oldRes == newRes) {
+ this.map.setCenter(this.map.getLonLatFromPixel(targetCenterPx));
+ } else {
+ var zoomOriginPx = {
+ x: (oldRes * targetCenterPx.x - newRes * centerPx.x) /
+ (oldRes - newRes),
+ y: (oldRes * targetCenterPx.y - newRes * centerPx.y) /
+ (oldRes - newRes)
+ };
+ this.map.zoomTo(zoom, zoomOriginPx);
+ }
+ if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){
+ this.map.zoomTo(lastZoom + (this.out ? -1 : 1));
+ }
+ } else if (this.zoomOnClick) { // it's a pixel
+ if (!this.out) {
+ this.map.zoomTo(this.map.getZoom() + 1, position);
+ } else {
+ this.map.zoomTo(this.map.getZoom() - 1, position);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomBox"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/ZoomIn.js b/misc/openlayers/lib/OpenLayers/Control/ZoomIn.js
new file mode 100644
index 0000000..8da1e1c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ZoomIn.js
@@ -0,0 +1,29 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomIn
+ * The ZoomIn control is a button to increase the zoom level of a map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomIn = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * Method: trigger
+ */
+ trigger: function(){
+ if (this.map) {
+ this.map.zoomIn();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomIn"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/ZoomOut.js b/misc/openlayers/lib/OpenLayers/Control/ZoomOut.js
new file mode 100644
index 0000000..72a657b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ZoomOut.js
@@ -0,0 +1,29 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomOut
+ * The ZoomOut control is a button to decrease the zoom level of a map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomOut = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * Method: trigger
+ */
+ trigger: function(){
+ if (this.map) {
+ this.map.zoomOut();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomOut"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/ZoomPanel.js b/misc/openlayers/lib/OpenLayers/Control/ZoomPanel.js
new file mode 100644
index 0000000..147f6cb
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ZoomPanel.js
@@ -0,0 +1,54 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/ZoomIn.js
+ * @requires OpenLayers/Control/ZoomOut.js
+ * @requires OpenLayers/Control/ZoomToMaxExtent.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomPanel
+ * The ZoomPanel control is a compact collecton of 3 zoom controls: a
+ * <OpenLayers.Control.ZoomIn>, a <OpenLayers.Control.ZoomToMaxExtent>, and a
+ * <OpenLayers.Control.ZoomOut>. By default it is drawn in the upper left
+ * corner of the map.
+ *
+ * Note:
+ * If you wish to use this class with the default images and you want
+ * it to look nice in ie6, you should add the following, conditionally
+ * added css stylesheet to your HTML file:
+ *
+ * (code)
+ * <!--[if lte IE 6]>
+ * <link rel="stylesheet" href="../theme/default/ie6-style.css" type="text/css" />
+ * <![endif]-->
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.ZoomPanel = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+ /**
+ * Constructor: OpenLayers.Control.ZoomPanel
+ * Add the three zooming controls.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be used
+ * to extend the control.
+ */
+ initialize: function(options) {
+ OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+ this.addControls([
+ new OpenLayers.Control.ZoomIn(),
+ new OpenLayers.Control.ZoomToMaxExtent(),
+ new OpenLayers.Control.ZoomOut()
+ ]);
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomPanel"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Control/ZoomToMaxExtent.js b/misc/openlayers/lib/OpenLayers/Control/ZoomToMaxExtent.js
new file mode 100644
index 0000000..bc2e754
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Control/ZoomToMaxExtent.js
@@ -0,0 +1,35 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Button.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomToMaxExtent
+ * The ZoomToMaxExtent control is a button that zooms out to the maximum
+ * extent of the map. It is designed to be used with a
+ * <OpenLayers.Control.Panel>.
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomToMaxExtent = OpenLayers.Class(OpenLayers.Control.Button, {
+
+ /**
+ * Method: trigger
+ *
+ * Called whenever this control is being rendered inside of a panel and a
+ * click occurs on this controls element. Actually zooms to the maximum
+ * extent of this controls map.
+ */
+ trigger: function() {
+ if (this.map) {
+ this.map.zoomToMaxExtent();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.ZoomToMaxExtent"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Events.js b/misc/openlayers/lib/OpenLayers/Events.js
new file mode 100644
index 0000000..6a4a129
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Events.js
@@ -0,0 +1,1170 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Event
+ * Utility functions for event handling.
+ */
+OpenLayers.Event = {
+
+ /**
+ * Property: observers
+ * {Object} A hashtable cache of the event observers. Keyed by
+ * element._eventCacheID
+ */
+ observers: false,
+
+ /**
+ * Constant: KEY_SPACE
+ * {int}
+ */
+ KEY_SPACE: 32,
+
+ /**
+ * Constant: KEY_BACKSPACE
+ * {int}
+ */
+ KEY_BACKSPACE: 8,
+
+ /**
+ * Constant: KEY_TAB
+ * {int}
+ */
+ KEY_TAB: 9,
+
+ /**
+ * Constant: KEY_RETURN
+ * {int}
+ */
+ KEY_RETURN: 13,
+
+ /**
+ * Constant: KEY_ESC
+ * {int}
+ */
+ KEY_ESC: 27,
+
+ /**
+ * Constant: KEY_LEFT
+ * {int}
+ */
+ KEY_LEFT: 37,
+
+ /**
+ * Constant: KEY_UP
+ * {int}
+ */
+ KEY_UP: 38,
+
+ /**
+ * Constant: KEY_RIGHT
+ * {int}
+ */
+ KEY_RIGHT: 39,
+
+ /**
+ * Constant: KEY_DOWN
+ * {int}
+ */
+ KEY_DOWN: 40,
+
+ /**
+ * Constant: KEY_DELETE
+ * {int}
+ */
+ KEY_DELETE: 46,
+
+
+ /**
+ * Method: element
+ * Cross browser event element detection.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {DOMElement} The element that caused the event
+ */
+ element: function(event) {
+ return event.target || event.srcElement;
+ },
+
+ /**
+ * Method: isSingleTouch
+ * Determine whether event was caused by a single touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isSingleTouch: function(event) {
+ return event.touches && event.touches.length == 1;
+ },
+
+ /**
+ * Method: isMultiTouch
+ * Determine whether event was caused by a multi touch
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isMultiTouch: function(event) {
+ return event.touches && event.touches.length > 1;
+ },
+
+ /**
+ * Method: isLeftClick
+ * Determine whether event was caused by a left click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isLeftClick: function(event) {
+ return (((event.which) && (event.which == 1)) ||
+ ((event.button) && (event.button == 1)));
+ },
+
+ /**
+ * Method: isRightClick
+ * Determine whether event was caused by a right mouse click.
+ *
+ * Parameters:
+ * event - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isRightClick: function(event) {
+ return (((event.which) && (event.which == 3)) ||
+ ((event.button) && (event.button == 2)));
+ },
+
+ /**
+ * Method: stop
+ * Stops an event from propagating.
+ *
+ * Parameters:
+ * event - {Event}
+ * allowDefault - {Boolean} If true, we stop the event chain but
+ * still allow the default browser behaviour (text selection,
+ * radio-button clicking, etc). Default is false.
+ */
+ stop: function(event, allowDefault) {
+
+ if (!allowDefault) {
+ OpenLayers.Event.preventDefault(event);
+ }
+
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * Method: preventDefault
+ * Cancels the event if it is cancelable, without stopping further
+ * propagation of the event.
+ *
+ * Parameters:
+ * event - {Event}
+ */
+ preventDefault: function(event) {
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ }
+ },
+
+ /**
+ * Method: findElement
+ *
+ * Parameters:
+ * event - {Event}
+ * tagName - {String}
+ *
+ * Returns:
+ * {DOMElement} The first node with the given tagName, starting from the
+ * node the event was triggered on and traversing the DOM upwards
+ */
+ findElement: function(event, tagName) {
+ var element = OpenLayers.Event.element(event);
+ while (element.parentNode && (!element.tagName ||
+ (element.tagName.toUpperCase() != tagName.toUpperCase()))){
+ element = element.parentNode;
+ }
+ return element;
+ },
+
+ /**
+ * Method: observe
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ */
+ observe: function(elementParam, name, observer, useCapture) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ useCapture = useCapture || false;
+
+ if (name == 'keypress' &&
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+ || element.attachEvent)) {
+ name = 'keydown';
+ }
+
+ //if observers cache has not yet been created, create it
+ if (!this.observers) {
+ this.observers = {};
+ }
+
+ //if not already assigned, make a new unique cache ID
+ if (!element._eventCacheID) {
+ var idPrefix = "eventCacheID_";
+ if (element.id) {
+ idPrefix = element.id + "_" + idPrefix;
+ }
+ element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
+ }
+
+ var cacheID = element._eventCacheID;
+
+ //if there is not yet a hash entry for this element, add one
+ if (!this.observers[cacheID]) {
+ this.observers[cacheID] = [];
+ }
+
+ //add a new observer to this element's list
+ this.observers[cacheID].push({
+ 'element': element,
+ 'name': name,
+ 'observer': observer,
+ 'useCapture': useCapture
+ });
+
+ //add the actual browser event listener
+ if (element.addEventListener) {
+ element.addEventListener(name, observer, useCapture);
+ } else if (element.attachEvent) {
+ element.attachEvent('on' + name, observer);
+ }
+ },
+
+ /**
+ * Method: stopObservingElement
+ * Given the id of an element to stop observing, cycle through the
+ * element's cached observers, calling stopObserving on each one,
+ * skipping those entries which can no longer be removed.
+ *
+ * parameters:
+ * elementParam - {DOMElement || String}
+ */
+ stopObservingElement: function(elementParam) {
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
+ },
+
+ /**
+ * Method: _removeElementObservers
+ *
+ * Parameters:
+ * elementObservers - {Array(Object)} Array of (element, name,
+ * observer, usecapture) objects,
+ * taken directly from hashtable
+ */
+ _removeElementObservers: function(elementObservers) {
+ if (elementObservers) {
+ for(var i = elementObservers.length-1; i >= 0; i--) {
+ var entry = elementObservers[i];
+ OpenLayers.Event.stopObserving.apply(this, [
+ entry.element, entry.name, entry.observer, entry.useCapture
+ ]);
+ }
+ }
+ },
+
+ /**
+ * Method: stopObserving
+ *
+ * Parameters:
+ * elementParam - {DOMElement || String}
+ * name - {String}
+ * observer - {function}
+ * useCapture - {Boolean}
+ *
+ * Returns:
+ * {Boolean} Whether or not the event observer was removed
+ */
+ stopObserving: function(elementParam, name, observer, useCapture) {
+ useCapture = useCapture || false;
+
+ var element = OpenLayers.Util.getElement(elementParam);
+ var cacheID = element._eventCacheID;
+
+ if (name == 'keypress') {
+ if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) ||
+ element.detachEvent) {
+ name = 'keydown';
+ }
+ }
+
+ // find element's entry in this.observers cache and remove it
+ var foundEntry = false;
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ if (elementObservers) {
+
+ // find the specific event type in the element's list
+ var i=0;
+ while(!foundEntry && i < elementObservers.length) {
+ var cacheEntry = elementObservers[i];
+
+ if ((cacheEntry.name == name) &&
+ (cacheEntry.observer == observer) &&
+ (cacheEntry.useCapture == useCapture)) {
+
+ elementObservers.splice(i, 1);
+ if (elementObservers.length == 0) {
+ delete OpenLayers.Event.observers[cacheID];
+ }
+ foundEntry = true;
+ break;
+ }
+ i++;
+ }
+ }
+
+ //actually remove the event listener from browser
+ if (foundEntry) {
+ if (element.removeEventListener) {
+ element.removeEventListener(name, observer, useCapture);
+ } else if (element && element.detachEvent) {
+ element.detachEvent('on' + name, observer);
+ }
+ }
+ return foundEntry;
+ },
+
+ /**
+ * Method: unloadCache
+ * Cycle through all the element entries in the events cache and call
+ * stopObservingElement on each.
+ */
+ unloadCache: function() {
+ // check for OpenLayers.Event before checking for observers, because
+ // OpenLayers.Event may be undefined in IE if no map instance was
+ // created
+ if (OpenLayers.Event && OpenLayers.Event.observers) {
+ for (var cacheID in OpenLayers.Event.observers) {
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+ OpenLayers.Event._removeElementObservers.apply(this,
+ [elementObservers]);
+ }
+ OpenLayers.Event.observers = false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Event"
+};
+
+/* prevent memory leaks in IE */
+OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
+
+/**
+ * Class: OpenLayers.Events
+ */
+OpenLayers.Events = OpenLayers.Class({
+
+ /**
+ * Constant: BROWSER_EVENTS
+ * {Array(String)} supported events
+ */
+ BROWSER_EVENTS: [
+ "mouseover", "mouseout",
+ "mousedown", "mouseup", "mousemove",
+ "click", "dblclick", "rightclick", "dblrightclick",
+ "resize", "focus", "blur",
+ "touchstart", "touchmove", "touchend",
+ "keydown"
+ ],
+
+ /**
+ * Property: listeners
+ * {Object} Hashtable of Array(Function): events listener functions
+ */
+ listeners: null,
+
+ /**
+ * Property: object
+ * {Object} the code object issuing application events
+ */
+ object: null,
+
+ /**
+ * Property: element
+ * {DOMElement} the DOM element receiving browser events
+ */
+ element: null,
+
+ /**
+ * Property: eventHandler
+ * {Function} bound event handler attached to elements
+ */
+ eventHandler: null,
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean}
+ */
+ fallThrough: null,
+
+ /**
+ * APIProperty: includeXY
+ * {Boolean} Should the .xy property automatically be created for browser
+ * mouse events? In general, this should be false. If it is true, then
+ * mouse events will automatically generate a '.xy' property on the
+ * event object that is passed. (Prior to OpenLayers 2.7, this was true
+ * by default.) Otherwise, you can call the getMousePosition on the
+ * relevant events handler on the object available via the 'evt.object'
+ * property of the evt object. So, for most events, you can call:
+ * function named(evt) {
+ * this.xy = this.object.events.getMousePosition(evt)
+ * }
+ *
+ * This option typically defaults to false for performance reasons:
+ * when creating an events object whose primary purpose is to manage
+ * relatively positioned mouse events within a div, it may make
+ * sense to set it to true.
+ *
+ * This option is also used to control whether the events object caches
+ * offsets. If this is false, it will not: the reason for this is that
+ * it is only expected to be called many times if the includeXY property
+ * is set to true. If you set this to true, you are expected to clear
+ * the offset cache manually (using this.clearMouseCache()) if:
+ * the border of the element changes
+ * the location of the element in the page changes
+ */
+ includeXY: false,
+
+ /**
+ * APIProperty: extensions
+ * {Object} Event extensions registered with this instance. Keys are
+ * event types, values are {OpenLayers.Events.*} extension instances or
+ * {Boolean} for events that an instantiated extension provides in
+ * addition to the one it was created for.
+ *
+ * Extensions create an event in addition to browser events, which usually
+ * fires when a sequence of browser events is completed. Extensions are
+ * automatically instantiated when a listener is registered for an event
+ * provided by an extension.
+ *
+ * Extensions are created in the <OpenLayers.Events> namespace using
+ * <OpenLayers.Class>, and named after the event they provide.
+ * The constructor receives the target <OpenLayers.Events> instance as
+ * argument. Extensions that need to capture browser events before they
+ * propagate can register their listeners events using <register>, with
+ * {extension: true} as 4th argument.
+ *
+ * If an extension creates more than one event, an alias for each event
+ * type should be created and reference the same class. The constructor
+ * should set a reference in the target's extensions registry to itself.
+ *
+ * Below is a minimal extension that provides the "foostart" and "fooend"
+ * event types, which replace the native "click" event type if clicked on
+ * an element with the css class "foo":
+ *
+ * (code)
+ * OpenLayers.Events.foostart = OpenLayers.Class({
+ * initialize: function(target) {
+ * this.target = target;
+ * this.target.register("click", this, this.doStuff, {extension: true});
+ * // only required if extension provides more than one event type
+ * this.target.extensions["foostart"] = true;
+ * this.target.extensions["fooend"] = true;
+ * },
+ * destroy: function() {
+ * var target = this.target;
+ * target.unregister("click", this, this.doStuff);
+ * delete this.target;
+ * // only required if extension provides more than one event type
+ * delete target.extensions["foostart"];
+ * delete target.extensions["fooend"];
+ * },
+ * doStuff: function(evt) {
+ * var propagate = true;
+ * if (OpenLayers.Event.element(evt).className === "foo") {
+ * propagate = false;
+ * var target = this.target;
+ * target.triggerEvent("foostart");
+ * window.setTimeout(function() {
+ * target.triggerEvent("fooend");
+ * }, 1000);
+ * }
+ * return propagate;
+ * }
+ * });
+ * // only required if extension provides more than one event type
+ * OpenLayers.Events.fooend = OpenLayers.Events.foostart;
+ * (end)
+ *
+ */
+ extensions: null,
+
+ /**
+ * Property: extensionCount
+ * {Object} Keys are event types (like in <listeners>), values are the
+ * number of extension listeners for each event type.
+ */
+ extensionCount: null,
+
+ /**
+ * Method: clearMouseListener
+ * A version of <clearMouseCache> that is bound to this instance so that
+ * it can be used with <OpenLayers.Event.observe> and
+ * <OpenLayers.Event.stopObserving>.
+ */
+ clearMouseListener: null,
+
+ /**
+ * Constructor: OpenLayers.Events
+ * Construct an OpenLayers.Events object.
+ *
+ * Parameters:
+ * object - {Object} The js object to which this Events object is being added
+ * element - {DOMElement} A dom element to respond to browser events
+ * eventTypes - {Array(String)} Deprecated. Array of custom application
+ * events. A listener may be registered for any named event, regardless
+ * of the values provided here.
+ * fallThrough - {Boolean} Allow events to fall through after these have
+ * been handled?
+ * options - {Object} Options for the events object.
+ */
+ initialize: function (object, element, eventTypes, fallThrough, options) {
+ OpenLayers.Util.extend(this, options);
+ this.object = object;
+ this.fallThrough = fallThrough;
+ this.listeners = {};
+ this.extensions = {};
+ this.extensionCount = {};
+ this._msTouches = [];
+
+ // if a dom element is specified, add a listeners list
+ // for browser events on the element and register them
+ if (element != null) {
+ this.attachToElement(element);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function () {
+ for (var e in this.extensions) {
+ if (typeof this.extensions[e] !== "boolean") {
+ this.extensions[e].destroy();
+ }
+ }
+ this.extensions = null;
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ if(this.element.hasScrollEvent) {
+ OpenLayers.Event.stopObserving(
+ window, "scroll", this.clearMouseListener
+ );
+ }
+ }
+ this.element = null;
+
+ this.listeners = null;
+ this.object = null;
+ this.fallThrough = null;
+ this.eventHandler = null;
+ },
+
+ /**
+ * APIMethod: addEventType
+ * Deprecated. Any event can be triggered without adding it first.
+ *
+ * Parameters:
+ * eventName - {String}
+ */
+ addEventType: function(eventName) {
+ },
+
+ /**
+ * Method: attachToElement
+ *
+ * Parameters:
+ * element - {HTMLDOMElement} a DOM element to attach browser events to
+ */
+ attachToElement: function (element) {
+ if (this.element) {
+ OpenLayers.Event.stopObservingElement(this.element);
+ } else {
+ // keep a bound copy of handleBrowserEvent() so that we can
+ // pass the same function to both Event.observe() and .stopObserving()
+ this.eventHandler = OpenLayers.Function.bindAsEventListener(
+ this.handleBrowserEvent, this
+ );
+
+ // to be used with observe and stopObserving
+ this.clearMouseListener = OpenLayers.Function.bind(
+ this.clearMouseCache, this
+ );
+ }
+ this.element = element;
+ var msTouch = !!window.navigator.msMaxTouchPoints;
+ var type;
+ for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
+ type = this.BROWSER_EVENTS[i];
+ // register the event cross-browser
+ OpenLayers.Event.observe(element, type, this.eventHandler
+ );
+ if (msTouch && type.indexOf('touch') === 0) {
+ this.addMsTouchListener(element, type, this.eventHandler);
+ }
+ }
+ // disable dragstart in IE so that mousedown/move/up works normally
+ OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
+ },
+
+ /**
+ * APIMethod: on
+ * Convenience method for registering listeners with a common scope.
+ * Internally, this method calls <register> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // register a single listener for the "loadstart" event
+ * events.on({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", undefined, loadStartListener);
+ *
+ * // register multiple listeners to be called with the same `this` object
+ * events.on({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.register("loadstart", object, loadStartListener);
+ * events.register("loadend", object, loadEndListener);
+ * (end)
+ *
+ * Parameters:
+ * object - {Object}
+ */
+ on: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.register(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: register
+ * Register an event on the events object.
+ *
+ * When the event is triggered, the 'func' function will be called, in the
+ * context of 'obj'. Imagine we were to register an event, specifying an
+ * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the
+ * context in the callback function will be our Bounds object. This means
+ * that within our callback function, we can access the properties and
+ * methods of the Bounds object through the "this" variable. So our
+ * callback could execute something like:
+ * : leftStr = "Left: " + this.left;
+ *
+ * or
+ *
+ * : centerStr = "Center: " + this.getCenterLonLat();
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ * priority - {Boolean|Object} If true, adds the new listener to the
+ * *front* of the events queue instead of to the end.
+ *
+ * Valid options for priority:
+ * extension - {Boolean} If true, then the event will be registered as
+ * extension event. Extension events are handled before all other
+ * events.
+ */
+ register: function (type, obj, func, priority) {
+ if (type in OpenLayers.Events && !this.extensions[type]) {
+ this.extensions[type] = new OpenLayers.Events[type](this);
+ }
+ if (func != null) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (!listeners) {
+ listeners = [];
+ this.listeners[type] = listeners;
+ this.extensionCount[type] = 0;
+ }
+ var listener = {obj: obj, func: func};
+ if (priority) {
+ listeners.splice(this.extensionCount[type], 0, listener);
+ if (typeof priority === "object" && priority.extension) {
+ this.extensionCount[type]++;
+ }
+ } else {
+ listeners.push(listener);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: registerPriority
+ * Same as register() but adds the new listener to the *front* of the
+ * events queue instead of to the end.
+ *
+ * TODO: get rid of this in 3.0 - Decide whether listeners should be
+ * called in the order they were registered or in reverse order.
+ *
+ *
+ * Parameters:
+ * type - {String} Name of the event to register
+ * obj - {Object} The object to bind the context to for the callback#.
+ * If no object is specified, default is the Events's
+ * 'object' property.
+ * func - {Function} The callback function. If no callback is
+ * specified, this function does nothing.
+ */
+ registerPriority: function (type, obj, func) {
+ this.register(type, obj, func, true);
+ },
+
+ /**
+ * APIMethod: un
+ * Convenience method for unregistering listeners with a common scope.
+ * Internally, this method calls <unregister> as shown in the examples
+ * below.
+ *
+ * Example use:
+ * (code)
+ * // unregister a single listener for the "loadstart" event
+ * events.un({"loadstart": loadStartListener});
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", undefined, loadStartListener);
+ *
+ * // unregister multiple listeners with the same `this` object
+ * events.un({
+ * "loadstart": loadStartListener,
+ * "loadend": loadEndListener,
+ * scope: object
+ * });
+ *
+ * // this is equivalent to the following
+ * events.unregister("loadstart", object, loadStartListener);
+ * events.unregister("loadend", object, loadEndListener);
+ * (end)
+ */
+ un: function(object) {
+ for(var type in object) {
+ if(type != "scope" && object.hasOwnProperty(type)) {
+ this.unregister(type, object.scope, object[type]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: unregister
+ *
+ * Parameters:
+ * type - {String}
+ * obj - {Object} If none specified, defaults to this.object
+ * func - {Function}
+ */
+ unregister: function (type, obj, func) {
+ if (obj == null) {
+ obj = this.object;
+ }
+ var listeners = this.listeners[type];
+ if (listeners != null) {
+ for (var i=0, len=listeners.length; i<len; i++) {
+ if (listeners[i].obj == obj && listeners[i].func == func) {
+ listeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: remove
+ * Remove all listeners for a given event type. If type is not registered,
+ * does nothing.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ remove: function(type) {
+ if (this.listeners[type] != null) {
+ this.listeners[type] = [];
+ }
+ },
+
+ /**
+ * APIMethod: triggerEvent
+ * Trigger a specified registered event.
+ *
+ * Parameters:
+ * type - {String}
+ * evt - {Event || Object} will be passed to the listeners.
+ *
+ * Returns:
+ * {Boolean} The last listener return. If a listener returns false, the
+ * chain of listeners will stop getting called.
+ */
+ triggerEvent: function (type, evt) {
+ var listeners = this.listeners[type];
+
+ // fast path
+ if(!listeners || listeners.length == 0) {
+ return undefined;
+ }
+
+ // prep evt object with object & div references
+ if (evt == null) {
+ evt = {};
+ }
+ evt.object = this.object;
+ evt.element = this.element;
+ if(!evt.type) {
+ evt.type = type;
+ }
+
+ // execute all callbacks registered for specified type
+ // get a clone of the listeners array to
+ // allow for splicing during callbacks
+ listeners = listeners.slice();
+ var continueChain;
+ for (var i=0, len=listeners.length; i<len; i++) {
+ var callback = listeners[i];
+ // bind the context to callback.obj
+ continueChain = callback.func.apply(callback.obj, [evt]);
+
+ if ((continueChain != undefined) && (continueChain == false)) {
+ // if callback returns false, execute no more callbacks.
+ break;
+ }
+ }
+ // don't fall through to other DOM elements
+ if (!this.fallThrough) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ return continueChain;
+ },
+
+ /**
+ * Method: handleBrowserEvent
+ * Basically just a wrapper to the triggerEvent() function, but takes
+ * care to set a property 'xy' on the event with the current mouse
+ * position.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ handleBrowserEvent: function (evt) {
+ var type = evt.type, listeners = this.listeners[type];
+ if(!listeners || listeners.length == 0) {
+ // noone's listening, bail out
+ return;
+ }
+ // add clientX & clientY to all events - corresponds to average x, y
+ var touches = evt.touches;
+ if (touches && touches[0]) {
+ var x = 0;
+ var y = 0;
+ var num = touches.length;
+ var touch;
+ for (var i=0; i<num; ++i) {
+ touch = this.getTouchClientXY(touches[i]);
+ x += touch.clientX;
+ y += touch.clientY;
+ }
+ evt.clientX = x / num;
+ evt.clientY = y / num;
+ }
+ if (this.includeXY) {
+ evt.xy = this.getMousePosition(evt);
+ }
+ this.triggerEvent(type, evt);
+ },
+
+ /**
+ * Method: getTouchClientXY
+ * WebKit has a few bugs for clientX/clientY. This method detects them
+ * and calculate the correct values.
+ *
+ * Parameters:
+ * evt - {Touch} a Touch object from a TouchEvent
+ *
+ * Returns:
+ * {Object} An object with only clientX and clientY properties with the
+ * calculated values.
+ */
+ getTouchClientXY: function (evt) {
+ // olMochWin is to override window, used for testing
+ var win = window.olMockWin || window,
+ winPageX = win.pageXOffset,
+ winPageY = win.pageYOffset,
+ x = evt.clientX,
+ y = evt.clientY;
+
+ if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
+ evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
+ // iOS4 include scroll offset in clientX/Y
+ x = x - winPageX;
+ y = y - winPageY;
+ } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
+ // Some Android browsers have totally bogus values for clientX/Y
+ // when scrolling/zooming a page
+ x = evt.pageX - winPageX;
+ y = evt.pageY - winPageY;
+ }
+
+ evt.olClientX = x;
+ evt.olClientY = y;
+
+ return {
+ clientX: x,
+ clientY: y
+ };
+ },
+
+ /**
+ * APIMethod: clearMouseCache
+ * Clear cached data about the mouse position. This should be called any
+ * time the element that events are registered on changes position
+ * within the page.
+ */
+ clearMouseCache: function() {
+ this.element.scrolls = null;
+ this.element.lefttop = null;
+ this.element.offsets = null;
+ },
+
+ /**
+ * Method: getMousePosition
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
+ * for offsets
+ */
+ getMousePosition: function (evt) {
+ if (!this.includeXY) {
+ this.clearMouseCache();
+ } else if (!this.element.hasScrollEvent) {
+ OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
+ this.element.hasScrollEvent = true;
+ }
+
+ if (!this.element.scrolls) {
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ this.element.scrolls = [
+ window.pageXOffset || viewportElement.scrollLeft,
+ window.pageYOffset || viewportElement.scrollTop
+ ];
+ }
+
+ if (!this.element.lefttop) {
+ this.element.lefttop = [
+ (document.documentElement.clientLeft || 0),
+ (document.documentElement.clientTop || 0)
+ ];
+ }
+
+ if (!this.element.offsets) {
+ this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+ }
+
+ return new OpenLayers.Pixel(
+ (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
+ - this.element.lefttop[0],
+ (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
+ - this.element.lefttop[1]
+ );
+ },
+
+ /**
+ * Method: addMsTouchListener
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListener: function (element, type, handler) {
+ var eventHandler = this.eventHandler;
+ var touches = this._msTouches;
+
+ function msHandler(evt) {
+ handler(OpenLayers.Util.applyDefaults({
+ stopPropagation: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].stopPropagation();
+ }
+ },
+ preventDefault: function() {
+ for (var i=touches.length-1; i>=0; --i) {
+ touches[i].preventDefault();
+ }
+ },
+ type: type
+ }, evt));
+ }
+
+ switch (type) {
+ case 'touchstart':
+ return this.addMsTouchListenerStart(element, type, msHandler);
+ case 'touchend':
+ return this.addMsTouchListenerEnd(element, type, msHandler);
+ case 'touchmove':
+ return this.addMsTouchListenerMove(element, type, msHandler);
+ default:
+ throw 'Unknown touch event type';
+ }
+ },
+
+ /**
+ * Method: addMsTouchListenerStart
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerStart: function(element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ var alreadyInArray = false;
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ alreadyInArray = true;
+ break;
+ }
+ }
+ if (!alreadyInArray) {
+ touches.push(e);
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerDown', cb);
+
+ // Need to also listen for end events to keep the _msTouches list
+ // accurate
+ var internalCb = function(e) {
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+ };
+ OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
+ },
+
+ /**
+ * Method: addMsTouchListenerMove
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerMove: function (element, type, handler) {
+ var touches = this._msTouches;
+ var cb = function(e) {
+
+ //Don't fire touch moves when mouse isn't down
+ if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
+ return;
+ }
+
+ if (touches.length == 1 && touches[0].pageX == e.pageX &&
+ touches[0].pageY == e.pageY) {
+ // don't trigger event when pointer has not moved
+ return;
+ }
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches[i] = e;
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerMove', cb);
+ },
+
+ /**
+ * Method: addMsTouchListenerEnd
+ *
+ * Parameters:
+ * element - {DOMElement} The DOM element to register the listener on
+ * type - {String} The event type
+ * handler - {Function} the handler
+ */
+ addMsTouchListenerEnd: function (element, type, handler) {
+ var touches = this._msTouches;
+
+ var cb = function(e) {
+
+ for (var i=0, ii=touches.length; i<ii; ++i) {
+ if (touches[i].pointerId == e.pointerId) {
+ touches.splice(i, 1);
+ break;
+ }
+ }
+
+ e.touches = touches.slice();
+ handler(e);
+ };
+
+ OpenLayers.Event.observe(element, 'MSPointerUp', cb);
+ },
+
+ CLASS_NAME: "OpenLayers.Events"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Events/buttonclick.js b/misc/openlayers/lib/OpenLayers/Events/buttonclick.js
new file mode 100644
index 0000000..ae9094d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Events/buttonclick.js
@@ -0,0 +1,206 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.buttonclick
+ * Extension event type for handling buttons on top of a dom element. This
+ * event type fires "buttonclick" on its <target> when a button was
+ * clicked. Buttons are detected by the "olButton" class.
+ *
+ * This event type makes sure that button clicks do not interfere with other
+ * events that are registered on the same <element>.
+ *
+ * Event types provided by this extension:
+ * - *buttonclick* Triggered when a button is clicked. Listeners receive an
+ * object with a *buttonElement* property referencing the dom element of
+ * the clicked button, and an *buttonXY* property with the click position
+ * relative to the button.
+ */
+OpenLayers.Events.buttonclick = OpenLayers.Class({
+
+ /**
+ * Property: target
+ * {<OpenLayers.Events>} The events instance that the buttonclick event will
+ * be triggered on.
+ */
+ target: null,
+
+ /**
+ * Property: events
+ * {Array} Events to observe and conditionally stop from propagating when
+ * an element with the olButton class (or its olAlphaImg child) is
+ * clicked.
+ */
+ events: [
+ 'mousedown', 'mouseup', 'click', 'dblclick',
+ 'touchstart', 'touchmove', 'touchend', 'keydown'
+ ],
+
+ /**
+ * Property: startRegEx
+ * {RegExp} Regular expression to test Event.type for events that start
+ * a buttonclick sequence.
+ */
+ startRegEx: /^mousedown|touchstart$/,
+
+ /**
+ * Property: cancelRegEx
+ * {RegExp} Regular expression to test Event.type for events that cancel
+ * a buttonclick sequence.
+ */
+ cancelRegEx: /^touchmove$/,
+
+ /**
+ * Property: completeRegEx
+ * {RegExp} Regular expression to test Event.type for events that complete
+ * a buttonclick sequence.
+ */
+ completeRegEx: /^mouseup|touchend$/,
+
+ /**
+ * Property: startEvt
+ * {Event} The event that started the click sequence
+ */
+
+ /**
+ * Constructor: OpenLayers.Events.buttonclick
+ * Construct a buttonclick event type. Applications are not supposed to
+ * create instances of this class - they are created on demand by
+ * <OpenLayers.Events> instances.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance that the buttonclick
+ * event will be triggered on.
+ */
+ initialize: function(target) {
+ this.target = target;
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.register(this.events[i], this, this.buttonClick, {
+ extension: true
+ });
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.events.length-1; i>=0; --i) {
+ this.target.unregister(this.events[i], this, this.buttonClick);
+ }
+ delete this.target;
+ },
+
+ /**
+ * Method: getPressedButton
+ * Get the pressed button, if any. Returns undefined if no button
+ * was pressed.
+ *
+ * Arguments:
+ * element - {DOMElement} The event target.
+ *
+ * Returns:
+ * {DOMElement} The button element, or undefined.
+ */
+ getPressedButton: function(element) {
+ var depth = 3, // limit the search depth
+ button;
+ do {
+ if(OpenLayers.Element.hasClass(element, "olButton")) {
+ // hit!
+ button = element;
+ break;
+ }
+ element = element.parentNode;
+ } while(--depth > 0 && element);
+ return button;
+ },
+
+ /**
+ * Method: ignore
+ * Check for event target elements that should be ignored by OpenLayers.
+ *
+ * Parameters:
+ * element - {DOMElement} The event target.
+ */
+ ignore: function(element) {
+ var depth = 3,
+ ignore = false;
+ do {
+ if (element.nodeName.toLowerCase() === 'a') {
+ ignore = true;
+ break;
+ }
+ element = element.parentNode;
+ } while (--depth > 0 && element);
+ return ignore;
+ },
+
+ /**
+ * Method: buttonClick
+ * Check if a button was clicked, and fire the buttonclick event
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonClick: function(evt) {
+ var propagate = true,
+ element = OpenLayers.Event.element(evt);
+ if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
+ // was a button pressed?
+ var button = this.getPressedButton(element);
+ if (button) {
+ if (evt.type === "keydown") {
+ switch (evt.keyCode) {
+ case OpenLayers.Event.KEY_RETURN:
+ case OpenLayers.Event.KEY_SPACE:
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button
+ });
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ break;
+ }
+ } else if (this.startEvt) {
+ if (this.completeRegEx.test(evt.type)) {
+ var pos = OpenLayers.Util.pagePosition(button);
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+ pos[0] = pos[0] - scrollLeft;
+ pos[1] = pos[1] - scrollTop;
+
+ this.target.triggerEvent("buttonclick", {
+ buttonElement: button,
+ buttonXY: {
+ x: this.startEvt.clientX - pos[0],
+ y: this.startEvt.clientY - pos[1]
+ }
+ });
+ }
+ if (this.cancelRegEx.test(evt.type)) {
+ delete this.startEvt;
+ }
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ if (this.startRegEx.test(evt.type)) {
+ this.startEvt = evt;
+ OpenLayers.Event.stop(evt);
+ propagate = false;
+ }
+ } else {
+ propagate = !this.ignore(OpenLayers.Event.element(evt));
+ delete this.startEvt;
+ }
+ }
+ return propagate;
+ }
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Events/featureclick.js b/misc/openlayers/lib/OpenLayers/Events/featureclick.js
new file mode 100644
index 0000000..9ae6ec5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Events/featureclick.js
@@ -0,0 +1,321 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.featureclick
+ *
+ * Extension event type for handling feature click events, including overlapping
+ * features.
+ *
+ * Event types provided by this extension:
+ * - featureclick
+ */
+OpenLayers.Events.featureclick = OpenLayers.Class({
+
+ /**
+ * Property: cache
+ * {Object} A cache of features under the mouse.
+ */
+ cache: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} The map to register browser events on.
+ */
+ map: null,
+
+ /**
+ * Property: provides
+ * {Array(String)} The event types provided by this extension.
+ */
+ provides: ["featureclick", "nofeatureclick", "featureover", "featureout"],
+
+ /**
+ * Constructor: OpenLayers.Events.featureclick
+ * Create a new featureclick event type.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Events>} The events instance to create the events
+ * for.
+ */
+ initialize: function(target) {
+ this.target = target;
+ if (target.object instanceof OpenLayers.Map) {
+ this.setMap(target.object);
+ } else if (target.object instanceof OpenLayers.Layer.Vector) {
+ if (target.object.map) {
+ this.setMap(target.object.map);
+ } else {
+ target.object.events.register("added", this, function(evt) {
+ this.setMap(target.object.map);
+ });
+ }
+ } else {
+ throw("Listeners for '" + this.provides.join("', '") +
+ "' events can only be registered for OpenLayers.Layer.Vector " +
+ "or OpenLayers.Map instances");
+ }
+ for (var i=this.provides.length-1; i>=0; --i) {
+ target.extensions[this.provides[i]] = true;
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map to register browser events on.
+ */
+ setMap: function(map) {
+ this.map = map;
+ this.cache = {};
+ map.events.register("mousedown", this, this.start, {extension: true});
+ map.events.register("mouseup", this, this.onClick, {extension: true});
+ map.events.register("touchstart", this, this.start, {extension: true});
+ map.events.register("touchmove", this, this.cancel, {extension: true});
+ map.events.register("touchend", this, this.onClick, {extension: true});
+ map.events.register("mousemove", this, this.onMousemove, {extension: true});
+ },
+
+ /**
+ * Method: start
+ * Sets startEvt = evt.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ start: function(evt) {
+ this.startEvt = evt;
+ },
+
+ /**
+ * Method: cancel
+ * Deletes the start event.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ cancel: function(evt) {
+ delete this.startEvt;
+ },
+
+ /**
+ * Method: onClick
+ * Listener for the click event.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ onClick: function(evt) {
+ if (!this.startEvt || evt.type !== "touchend" &&
+ !OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ var features = this.getFeatures(this.startEvt);
+ delete this.startEvt;
+ // fire featureclick events
+ var feature, layer, more, clicked = {};
+ for (var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ layer = feature.layer;
+ clicked[layer.id] = true;
+ more = this.triggerEvent("featureclick", {feature: feature});
+ if (more === false) {
+ break;
+ }
+ }
+ // fire nofeatureclick events on all vector layers with no targets
+ for (i=0, len=this.map.layers.length; i<len; ++i) {
+ layer = this.map.layers[i];
+ if (layer instanceof OpenLayers.Layer.Vector && !clicked[layer.id]) {
+ this.triggerEvent("nofeatureclick", {layer: layer});
+ }
+ }
+ },
+
+ /**
+ * Method: onMousemove
+ * Listener for the mousemove event.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ onMousemove: function(evt) {
+ delete this.startEvt;
+ var features = this.getFeatures(evt);
+ var over = {}, newly = [], feature;
+ for (var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ over[feature.id] = feature;
+ if (!this.cache[feature.id]) {
+ newly.push(feature);
+ }
+ }
+ // check if already over features
+ var out = [];
+ for (var id in this.cache) {
+ feature = this.cache[id];
+ if (feature.layer && feature.layer.map) {
+ if (!over[feature.id]) {
+ out.push(feature);
+ }
+ } else {
+ // removed
+ delete this.cache[id];
+ }
+ }
+ // fire featureover events
+ var more;
+ for (i=0, len=newly.length; i<len; ++i) {
+ feature = newly[i];
+ this.cache[feature.id] = feature;
+ more = this.triggerEvent("featureover", {feature: feature});
+ if (more === false) {
+ break;
+ }
+ }
+ // fire featureout events
+ for (i=0, len=out.length; i<len; ++i) {
+ feature = out[i];
+ delete this.cache[feature.id];
+ more = this.triggerEvent("featureout", {feature: feature});
+ if (more === false) {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: triggerEvent
+ * Determines where to trigger the event and triggers it.
+ *
+ * Parameters:
+ * type - {String} The event type to trigger
+ * evt - {Object} The listener argument
+ *
+ * Returns:
+ * {Boolean} The last listener return.
+ */
+ triggerEvent: function(type, evt) {
+ var layer = evt.feature ? evt.feature.layer : evt.layer,
+ object = this.target.object;
+ if (object instanceof OpenLayers.Map || object === layer) {
+ return this.target.triggerEvent(type, evt);
+ }
+ },
+
+ /**
+ * Method: getFeatures
+ * Get all features at the given screen location.
+ *
+ * Parameters:
+ * evt - {Object} Event object.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features at the given point.
+ */
+ getFeatures: function(evt) {
+ var x = evt.clientX, y = evt.clientY,
+ features = [], targets = [], layers = [],
+ layer, target, feature, i, len;
+ // go through all layers looking for targets
+ for (i=this.map.layers.length-1; i>=0; --i) {
+ layer = this.map.layers[i];
+ if (layer.div.style.display !== "none") {
+ if (layer.renderer instanceof OpenLayers.Renderer.Elements) {
+ if (layer instanceof OpenLayers.Layer.Vector) {
+ target = document.elementFromPoint(x, y);
+ while (target && target._featureId) {
+ feature = layer.getFeatureById(target._featureId);
+ if (feature) {
+ features.push(feature);
+ target.style.display = "none";
+ targets.push(target);
+ target = document.elementFromPoint(x, y);
+ } else {
+ // sketch, all bets off
+ target = false;
+ }
+ }
+ }
+ layers.push(layer);
+ layer.div.style.display = "none";
+ } else if (layer.renderer instanceof OpenLayers.Renderer.Canvas) {
+ feature = layer.renderer.getFeatureIdFromEvent(evt);
+ if (feature) {
+ features.push(feature);
+ layers.push(layer);
+ }
+ }
+ }
+ }
+ // restore feature visibility
+ for (i=0, len=targets.length; i<len; ++i) {
+ targets[i].style.display = "";
+ }
+ // restore layer visibility
+ for (i=layers.length-1; i>=0; --i) {
+ layers[i].div.style.display = "block";
+ }
+ return features;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ for (var i=this.provides.length-1; i>=0; --i) {
+ delete this.target.extensions[this.provides[i]];
+ }
+ this.map.events.un({
+ mousemove: this.onMousemove,
+ mousedown: this.start,
+ mouseup: this.onClick,
+ touchstart: this.start,
+ touchmove: this.cancel,
+ touchend: this.onClick,
+ scope: this
+ });
+ delete this.cache;
+ delete this.map;
+ delete this.target;
+ }
+
+});
+
+/**
+ * Class: OpenLayers.Events.nofeatureclick
+ *
+ * Extension event type for handling click events that do not hit a feature.
+ *
+ * Event types provided by this extension:
+ * - nofeatureclick
+ */
+OpenLayers.Events.nofeatureclick = OpenLayers.Events.featureclick;
+
+/**
+ * Class: OpenLayers.Events.featureover
+ *
+ * Extension event type for handling hovering over a feature.
+ *
+ * Event types provided by this extension:
+ * - featureover
+ */
+OpenLayers.Events.featureover = OpenLayers.Events.featureclick;
+
+/**
+ * Class: OpenLayers.Events.featureout
+ *
+ * Extension event type for handling leaving a feature.
+ *
+ * Event types provided by this extension:
+ * - featureout
+ */
+OpenLayers.Events.featureout = OpenLayers.Events.featureclick;
diff --git a/misc/openlayers/lib/OpenLayers/Feature.js b/misc/openlayers/lib/OpenLayers/Feature.js
new file mode 100644
index 0000000..ed7fd16
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Feature.js
@@ -0,0 +1,225 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ * class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>}
+ */
+ lonlat: null,
+
+ /**
+ * Property: data
+ * {Object}
+ */
+ data: null,
+
+ /**
+ * Property: marker
+ * {<OpenLayers.Marker>}
+ */
+ marker: null,
+
+ /**
+ * APIProperty: popupClass
+ * {<OpenLayers.Class>} The class which will be used to instantiate
+ * a new Popup. Default is <OpenLayers.Popup.Anchored>.
+ */
+ popupClass: null,
+
+ /**
+ * Property: popup
+ * {<OpenLayers.Popup>}
+ */
+ popup: null,
+
+ /**
+ * Constructor: OpenLayers.Feature
+ * Constructor for features.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * lonlat - {<OpenLayers.LonLat>}
+ * data - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Feature>}
+ */
+ initialize: function(layer, lonlat, data) {
+ this.layer = layer;
+ this.lonlat = lonlat;
+ this.data = (data != null) ? data : {};
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ //remove the popup from the map
+ if ((this.layer != null) && (this.layer.map != null)) {
+ if (this.popup != null) {
+ this.layer.map.removePopup(this.popup);
+ }
+ }
+ // remove the marker from the layer
+ if (this.layer != null && this.marker != null) {
+ this.layer.removeMarker(this.marker);
+ }
+
+ this.layer = null;
+ this.id = null;
+ this.lonlat = null;
+ this.data = null;
+ if (this.marker != null) {
+ this.destroyMarker(this.marker);
+ this.marker = null;
+ }
+ if (this.popup != null) {
+ this.destroyPopup(this.popup);
+ this.popup = null;
+ }
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is currently visible on screen
+ * (based on its 'lonlat' property)
+ */
+ onScreen:function() {
+
+ var onScreen = false;
+ if ((this.layer != null) && (this.layer.map != null)) {
+ var screenBounds = this.layer.map.getExtent();
+ onScreen = screenBounds.containsLonLat(this.lonlat);
+ }
+ return onScreen;
+ },
+
+
+ /**
+ * Method: createMarker
+ * Based on the data associated with the Feature, create and return a marker object.
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
+ * set in this.data. If no 'lonlat' is set, returns null. If no
+ * 'icon' is set, OpenLayers.Marker() will load the default image.
+ *
+ * Note - this.marker is set to return value
+ *
+ */
+ createMarker: function() {
+
+ if (this.lonlat != null) {
+ this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
+ }
+ return this.marker;
+ },
+
+ /**
+ * Method: destroyMarker
+ * Destroys marker.
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ this.marker.destroy();
+ },
+
+ /**
+ * Method: createPopup
+ * Creates a popup object created from the 'lonlat', 'popupSize',
+ * and 'popupContentHTML' properties set in this.data. It uses
+ * this.marker.icon as default anchor.
+ *
+ * If no 'lonlat' is set, returns null.
+ * If no this.marker has been created, no anchor is sent.
+ *
+ * Note - the returned popup object is 'owned' by the feature, so you
+ * cannot use the popup's destroy method to discard the popup.
+ * Instead, you must use the feature's destroyPopup
+ *
+ * Note - this.popup is set to return value
+ *
+ * Parameters:
+ * closeBox - {Boolean} create popup with closebox or not
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} Returns the created popup, which is also set
+ * as 'popup' property of this feature. Will be of whatever type
+ * specified by this feature's 'popupClass' property, but must be
+ * of type <OpenLayers.Popup>.
+ *
+ */
+ createPopup: function(closeBox) {
+
+ if (this.lonlat != null) {
+ if (!this.popup) {
+ var anchor = (this.marker) ? this.marker.icon : null;
+ var popupClass = this.popupClass ?
+ this.popupClass : OpenLayers.Popup.Anchored;
+ this.popup = new popupClass(this.id + "_popup",
+ this.lonlat,
+ this.data.popupSize,
+ this.data.popupContentHTML,
+ anchor,
+ closeBox);
+ }
+ if (this.data.overflow != null) {
+ this.popup.contentDiv.style.overflow = this.data.overflow;
+ }
+
+ this.popup.feature = this;
+ }
+ return this.popup;
+ },
+
+
+ /**
+ * Method: destroyPopup
+ * Destroys the popup created via createPopup.
+ *
+ * As with the marker, if user overrides the createPopup() function, s/he
+ * should also be able to override the destruction
+ */
+ destroyPopup: function() {
+ if (this.popup) {
+ this.popup.feature = null;
+ this.popup.destroy();
+ this.popup = null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Feature/Vector.js b/misc/openlayers/lib/OpenLayers/Feature/Vector.js
new file mode 100644
index 0000000..a6c8c70
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Feature/Vector.js
@@ -0,0 +1,510 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+// TRASH THIS
+OpenLayers.State = {
+ /** states */
+ UNKNOWN: 'Unknown',
+ INSERT: 'Insert',
+ UPDATE: 'Update',
+ DELETE: 'Delete'
+};
+
+/**
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.Vector
+ * Vector features use the OpenLayers.Geometry classes as geometry description.
+ * They have an 'attributes' property, which is the data object, and a 'style'
+ * property, the default values of which are defined in the
+ * <OpenLayers.Feature.Vector.style> objects.
+ *
+ * Inherits from:
+ * - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+ /**
+ * Property: fid
+ * {String}
+ */
+ fid: null,
+
+ /**
+ * APIProperty: geometry
+ * {<OpenLayers.Geometry>}
+ */
+ geometry: null,
+
+ /**
+ * APIProperty: attributes
+ * {Object} This object holds arbitrary, serializable properties that
+ * describe the feature.
+ */
+ attributes: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The box bounding that feature's geometry, that
+ * property can be set by an <OpenLayers.Format> object when
+ * deserializing the feature, so in most cases it represents an
+ * information set by the server.
+ */
+ bounds: null,
+
+ /**
+ * Property: state
+ * {String}
+ */
+ state: null,
+
+ /**
+ * APIProperty: style
+ * {Object}
+ */
+ style: null,
+
+ /**
+ * APIProperty: url
+ * {String} If this property is set it will be taken into account by
+ * {<OpenLayers.HTTP>} when upadting or deleting the feature.
+ */
+ url: null,
+
+ /**
+ * Property: renderIntent
+ * {String} rendering intent currently being used
+ */
+ renderIntent: "default",
+
+ /**
+ * APIProperty: modified
+ * {Object} An object with the originals of the geometry and attributes of
+ * the feature, if they were changed. Currently this property is only read
+ * by <OpenLayers.Format.WFST.v1>, and written by
+ * <OpenLayers.Control.ModifyFeature>, which sets the geometry property.
+ * Applications can set the originals of modified attributes in the
+ * attributes property. Note that applications have to check if this
+ * object and the attributes property is already created before using it.
+ * After a change made with ModifyFeature, this object could look like
+ *
+ * (code)
+ * {
+ * geometry: >Object
+ * }
+ * (end)
+ *
+ * When an application has made changes to feature attributes, it could
+ * have set the attributes to something like this:
+ *
+ * (code)
+ * {
+ * attributes: {
+ * myAttribute: "original"
+ * }
+ * }
+ * (end)
+ *
+ * Note that <OpenLayers.Format.WFST.v1> only checks for truthy values in
+ * *modified.geometry* and the attribute names in *modified.attributes*,
+ * but it is recommended to set the original values (and not just true) as
+ * attribute value, so applications could use this information to undo
+ * changes.
+ */
+ modified: null,
+
+ /**
+ * Constructor: OpenLayers.Feature.Vector
+ * Create a vector feature.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry that this feature
+ * represents.
+ * attributes - {Object} An optional object that will be mapped to the
+ * <attributes> property.
+ * style - {Object} An optional style object.
+ */
+ initialize: function(geometry, attributes, style) {
+ OpenLayers.Feature.prototype.initialize.apply(this,
+ [null, null, attributes]);
+ this.lonlat = null;
+ this.geometry = geometry ? geometry : null;
+ this.state = null;
+ this.attributes = {};
+ if (attributes) {
+ this.attributes = OpenLayers.Util.extend(this.attributes,
+ attributes);
+ }
+ this.style = style ? style : null;
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.layer) {
+ this.layer.removeFeatures(this);
+ this.layer = null;
+ }
+
+ this.geometry = null;
+ this.modified = null;
+ OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this vector feature. Does not set any non-standard
+ * properties.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
+ */
+ clone: function () {
+ return new OpenLayers.Feature.Vector(
+ this.geometry ? this.geometry.clone() : null,
+ this.attributes,
+ this.style);
+ },
+
+ /**
+ * Method: onScreen
+ * Determine whether the feature is within the map viewport. This method
+ * tests for an intersection between the geometry and the viewport
+ * bounds. If a more effecient but less precise geometry bounds
+ * intersection is desired, call the method with the boundsOnly
+ * parameter true.
+ *
+ * Parameters:
+ * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
+ * the viewport bounds. Default is false. If false, the feature's
+ * geometry must intersect the viewport for onScreen to return true.
+ *
+ * Returns:
+ * {Boolean} The feature is currently visible on screen (optionally
+ * based on its bounds if boundsOnly is true).
+ */
+ onScreen:function(boundsOnly) {
+ var onScreen = false;
+ if(this.layer && this.layer.map) {
+ var screenBounds = this.layer.map.getExtent();
+ if(boundsOnly) {
+ var featureBounds = this.geometry.getBounds();
+ onScreen = screenBounds.intersectsBounds(featureBounds);
+ } else {
+ var screenPoly = screenBounds.toGeometry();
+ onScreen = screenPoly.intersects(this.geometry);
+ }
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: getVisibility
+ * Determine whether the feature is displayed or not. It may not displayed
+ * because:
+ * - its style display property is set to 'none',
+ * - it doesn't belong to any layer,
+ * - the styleMap creates a symbolizer with display property set to 'none'
+ * for it,
+ * - the layer which it belongs to is not visible.
+ *
+ * Returns:
+ * {Boolean} The feature is currently displayed.
+ */
+ getVisibility: function() {
+ return !(this.style && this.style.display == 'none' ||
+ !this.layer ||
+ this.layer && this.layer.styleMap &&
+ this.layer.styleMap.createSymbolizer(this, this.renderIntent).display == 'none' ||
+ this.layer && !this.layer.getVisibility());
+ },
+
+ /**
+ * Method: createMarker
+ * HACK - we need to decide if all vector features should be able to
+ * create markers
+ *
+ * Returns:
+ * {<OpenLayers.Marker>} For now just returns null
+ */
+ createMarker: function() {
+ return null;
+ },
+
+ /**
+ * Method: destroyMarker
+ * HACK - we need to decide if all vector features should be able to
+ * delete markers
+ *
+ * If user overrides the createMarker() function, s/he should be able
+ * to also specify an alternative function for destroying it
+ */
+ destroyMarker: function() {
+ // pass
+ },
+
+ /**
+ * Method: createPopup
+ * HACK - we need to decide if all vector features should be able to
+ * create popups
+ *
+ * Returns:
+ * {<OpenLayers.Popup>} For now just returns null
+ */
+ createPopup: function() {
+ return null;
+ },
+
+ /**
+ * Method: atPoint
+ * Determins whether the feature intersects with the specified location.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the feature is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ if(this.geometry) {
+ atPoint = this.geometry.atPoint(lonlat, toleranceLon,
+ toleranceLat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: destroyPopup
+ * HACK - we need to decide if all vector features should be able to
+ * delete popups
+ */
+ destroyPopup: function() {
+ // pass
+ },
+
+ /**
+ * Method: move
+ * Moves the feature and redraws it at its new location
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat> or <OpenLayers.Pixel>} the
+ * location to which to move the feature.
+ */
+ move: function(location) {
+
+ if(!this.layer || !this.geometry.move){
+ //do nothing if no layer or immoveable geometry
+ return undefined;
+ }
+
+ var pixel;
+ if (location.CLASS_NAME == "OpenLayers.LonLat") {
+ pixel = this.layer.getViewPortPxFromLonLat(location);
+ } else {
+ pixel = location;
+ }
+
+ var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
+ var res = this.layer.map.getResolution();
+ this.geometry.move(res * (pixel.x - lastPixel.x),
+ res * (lastPixel.y - pixel.y));
+ this.layer.drawFeature(this);
+ return lastPixel;
+ },
+
+ /**
+ * Method: toState
+ * Sets the new state
+ *
+ * Parameters:
+ * state - {String}
+ */
+ toState: function(state) {
+ if (state == OpenLayers.State.UPDATE) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.DELETE:
+ this.state = state;
+ break;
+ case OpenLayers.State.UPDATE:
+ case OpenLayers.State.INSERT:
+ break;
+ }
+ } else if (state == OpenLayers.State.INSERT) {
+ switch (this.state) {
+ case OpenLayers.State.UNKNOWN:
+ break;
+ default:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.DELETE) {
+ switch (this.state) {
+ case OpenLayers.State.INSERT:
+ // the feature should be destroyed
+ break;
+ case OpenLayers.State.DELETE:
+ break;
+ case OpenLayers.State.UNKNOWN:
+ case OpenLayers.State.UPDATE:
+ this.state = state;
+ break;
+ }
+ } else if (state == OpenLayers.State.UNKNOWN) {
+ this.state = state;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Feature.Vector"
+});
+
+
+/**
+ * Constant: OpenLayers.Feature.Vector.style
+ * OpenLayers features can have a number of style attributes. The 'default'
+ * style will typically be used if no other style is specified. These
+ * styles correspond for the most part, to the styling properties defined
+ * by the SVG standard.
+ * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties
+ * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties
+ *
+ * Symbolizer properties:
+ * fill - {Boolean} Set to false if no fill is desired.
+ * fillColor - {String} Hex fill color. Default is "#ee9900".
+ * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4
+ * stroke - {Boolean} Set to false if no stroke is desired.
+ * strokeColor - {String} Hex stroke color. Default is "#ee9900".
+ * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1.
+ * strokeWidth - {Number} Pixel stroke width. Default is 1.
+ * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square]
+ * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid]
+ * graphic - {Boolean} Set to false if no graphic is desired.
+ * pointRadius - {Number} Pixel point radius. Default is 6.
+ * pointerEvents - {String} Default is "visiblePainted".
+ * cursor - {String} Default is "".
+ * externalGraphic - {String} Url to an external graphic that will be used for rendering points.
+ * graphicWidth - {Number} Pixel width for sizing an external graphic.
+ * graphicHeight - {Number} Pixel height for sizing an external graphic.
+ * graphicOpacity - {Number} Opacity (0-1) for an external graphic.
+ * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic.
+ * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic.
+ * rotation - {Number} For point symbolizers, this is the rotation of a graphic in the clockwise direction about its center point (or any point off center as specified by graphicXOffset and graphicYOffset).
+ * graphicZIndex - {Number} The integer z-index value to use in rendering.
+ * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default),
+ * "square", "star", "x", "cross", "triangle".
+ * graphicTitle - {String} Tooltip when hovering over a feature. *deprecated*, use title instead
+ * title - {String} Tooltip when hovering over a feature. Not supported by the canvas renderer.
+ * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic.
+ * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic.
+ * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic.
+ * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic.
+ * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used.
+ * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used.
+ * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either
+ * fillText or mozDrawText to be available.
+ * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string
+ * composed of two characters. The first character is for the horizontal alignment, the second for the vertical
+ * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical
+ * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". Default is "cm".
+ * labelXOffset - {Number} Pixel offset along the positive x axis for displacing the label. Not supported by the canvas renderer.
+ * labelYOffset - {Number} Pixel offset along the positive y axis for displacing the label. Not supported by the canvas renderer.
+ * labelSelect - {Boolean} If set to true, labels will be selectable using SelectFeature or similar controls.
+ * Default is false.
+ * labelOutlineColor - {String} The color of the label outline. Default is 'white'. Only supported by the canvas & SVG renderers.
+ * labelOutlineWidth - {Number} The width of the label outline. Default is 3, set to 0 or null to disable. Only supported by the SVG renderers.
+ * labelOutlineOpacity - {Number} The opacity (0-1) of the label outline. Default is fontOpacity. Only supported by the canvas & SVG renderers.
+ * fontColor - {String} The font color for the label, to be provided like CSS.
+ * fontOpacity - {Number} Opacity (0-1) for the label
+ * fontFamily - {String} The font family for the label, to be provided like in CSS.
+ * fontSize - {String} The font size for the label, to be provided like in CSS.
+ * fontStyle - {String} The font style for the label, to be provided like in CSS.
+ * fontWeight - {String} The font weight for the label, to be provided like in CSS.
+ * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect.
+ */
+OpenLayers.Feature.Vector.style = {
+ 'default': {
+ fillColor: "#ee9900",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#ee9900",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+ },
+ 'select': {
+ fillColor: "blue",
+ fillOpacity: 0.4,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "blue",
+ strokeOpacity: 1,
+ strokeWidth: 2,
+ strokeLinecap: "round",
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "pointer",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'temporary': {
+ fillColor: "#66cccc",
+ fillOpacity: 0.2,
+ hoverFillColor: "white",
+ hoverFillOpacity: 0.8,
+ strokeColor: "#66cccc",
+ strokeOpacity: 1,
+ strokeLinecap: "round",
+ strokeWidth: 2,
+ strokeDashstyle: "solid",
+ hoverStrokeColor: "red",
+ hoverStrokeOpacity: 1,
+ hoverStrokeWidth: 0.2,
+ pointRadius: 6,
+ hoverPointRadius: 1,
+ hoverPointUnit: "%",
+ pointerEvents: "visiblePainted",
+ cursor: "inherit",
+ fontColor: "#000000",
+ labelAlign: "cm",
+ labelOutlineColor: "white",
+ labelOutlineWidth: 3
+
+ },
+ 'delete': {
+ display: "none"
+ }
+};
diff --git a/misc/openlayers/lib/OpenLayers/Filter.js b/misc/openlayers/lib/OpenLayers/Filter.js
new file mode 100644
index 0000000..99c0448
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Filter.js
@@ -0,0 +1,87 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Filter
+ * This class represents an OGC Filter.
+ */
+OpenLayers.Filter = OpenLayers.Class({
+
+ /**
+ * Constructor: OpenLayers.Filter
+ * This class represents a generic filter.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to anything added.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context. Instances or subclasses
+ * are supposed to override this method.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ return true;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter. Should be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} Clone of this filter.
+ */
+ clone: function() {
+ return null;
+ },
+
+ /**
+ * APIMethod: toString
+ *
+ * Returns:
+ * {String} Include <OpenLayers.Format.CQL> in your build to get a CQL
+ * representation of the filter returned. Otherwise "[Object object]"
+ * will be returned.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.CQL) {
+ string = OpenLayers.Format.CQL.prototype.write(this);
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Filter/Comparison.js b/misc/openlayers/lib/OpenLayers/Filter/Comparison.js
new file mode 100644
index 0000000..6863ea8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Filter/Comparison.js
@@ -0,0 +1,267 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Comparison
+ * This class represents a comparison filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} type: type of the comparison. This is one of
+ * - OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+ * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+ * - OpenLayers.Filter.Comparison.LESS_THAN = "<";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+ * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+ * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+ * - OpenLayers.Filter.Comparison.BETWEEN = "..";
+ * - OpenLayers.Filter.Comparison.LIKE = "~";
+ * - OpenLayers.Filter.Comparison.IS_NULL = "NULL";
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String}
+ * name of the context property to compare
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {Number} or {String}
+ * comparison value for binary comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ value: null,
+
+ /**
+ * Property: matchCase
+ * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO
+ * comparisons. The Filter Encoding 1.1 specification added a matchCase
+ * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo
+ * elements. This property will be serialized with those elements only
+ * if using the v1.1.0 filter format. However, when evaluating filters
+ * here, the matchCase property will always be respected (for EQUAL_TO
+ * and NOT_EQUAL_TO). Default is true.
+ */
+ matchCase: true,
+
+ /**
+ * APIProperty: lowerBoundary
+ * {Number} or {String}
+ * lower boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ lowerBoundary: null,
+
+ /**
+ * APIProperty: upperBoundary
+ * {Number} or {String}
+ * upper boundary for between comparisons. In the case of a String, this
+ * can be a combination of text and propertyNames in the form
+ * "literal ${propertyName}"
+ */
+ upperBoundary: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Comparison
+ * Creates a comparison rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>}
+ */
+ initialize: function(options) {
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ // since matchCase on PropertyIsLike is not schema compliant, we only
+ // want to use this if explicitly asked for
+ if (this.type === OpenLayers.Filter.Comparison.LIKE
+ && options.matchCase === undefined) {
+ this.matchCase = null;
+ }
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. If a vector
+ * feature is provided, the feature.attributes will be used as context.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ if (context instanceof OpenLayers.Feature.Vector) {
+ context = context.attributes;
+ }
+ var result = false;
+ var got = context[this.property];
+ var exp;
+ switch(this.type) {
+ case OpenLayers.Filter.Comparison.EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() == exp.toUpperCase());
+ } else {
+ result = (got == exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+ exp = this.value;
+ if(!this.matchCase &&
+ typeof got == "string" && typeof exp == "string") {
+ result = (got.toUpperCase() != exp.toUpperCase());
+ } else {
+ result = (got != exp);
+ }
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN:
+ result = got < this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN:
+ result = got > this.value;
+ break;
+ case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+ result = got <= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+ result = got >= this.value;
+ break;
+ case OpenLayers.Filter.Comparison.BETWEEN:
+ result = (got >= this.lowerBoundary) &&
+ (got <= this.upperBoundary);
+ break;
+ case OpenLayers.Filter.Comparison.LIKE:
+ var regexp = new RegExp(this.value, "gi");
+ result = regexp.test(got);
+ break;
+ case OpenLayers.Filter.Comparison.IS_NULL:
+ result = (got === null);
+ break;
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: value2regex
+ * Converts the value of this rule into a regular expression string,
+ * according to the wildcard characters specified. This method has to
+ * be called after instantiation of this class, if the value is not a
+ * regular expression already.
+ *
+ * Parameters:
+ * wildCard - {Char} wildcard character in the above value, default
+ * is "*"
+ * singleChar - {Char} single-character wildcard in the above value
+ * default is "."
+ * escapeChar - {Char} escape character in the above value, default is
+ * "!"
+ *
+ * Returns:
+ * {String} regular expression string
+ */
+ value2regex: function(wildCard, singleChar, escapeChar) {
+ if (wildCard == ".") {
+ throw new Error("'.' is an unsupported wildCard character for " +
+ "OpenLayers.Filter.Comparison");
+ }
+
+
+ // set UMN MapServer defaults for unspecified parameters
+ wildCard = wildCard ? wildCard : "*";
+ singleChar = singleChar ? singleChar : ".";
+ escapeChar = escapeChar ? escapeChar : "!";
+
+ this.value = this.value.replace(
+ new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1");
+ this.value = this.value.replace(
+ new RegExp("\\"+singleChar, "g"), ".");
+ this.value = this.value.replace(
+ new RegExp("\\"+wildCard, "g"), ".*");
+ this.value = this.value.replace(
+ new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
+ this.value = this.value.replace(
+ new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+
+ return this.value;
+ },
+
+ /**
+ * Method: regex2value
+ * Convert the value of this rule from a regular expression string into an
+ * ogc literal string using a wildCard of *, a singleChar of ., and an
+ * escape of !. Leaves the <value> property unmodified.
+ *
+ * Returns:
+ * {String} A string value.
+ */
+ regex2value: function() {
+
+ var value = this.value;
+
+ // replace ! with !!
+ value = value.replace(/!/g, "!!");
+
+ // replace \. with !. (watching out for \\.)
+ value = value.replace(/(\\)?\\\./g, function($0, $1) {
+ return $1 ? $0 : "!.";
+ });
+
+ // replace \* with #* (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "!*";
+ });
+
+ // replace \\ with \
+ value = value.replace(/\\\\/g, "\\");
+
+ // convert .* to * (the sequence #.* is not allowed)
+ value = value.replace(/\.\*/g, "*");
+
+ return value;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Comparison>} Clone of this filter.
+ */
+ clone: function() {
+ return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this);
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Comparison"
+});
+
+
+OpenLayers.Filter.Comparison.EQUAL_TO = "==";
+OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!=";
+OpenLayers.Filter.Comparison.LESS_THAN = "<";
+OpenLayers.Filter.Comparison.GREATER_THAN = ">";
+OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<=";
+OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+OpenLayers.Filter.Comparison.BETWEEN = "..";
+OpenLayers.Filter.Comparison.LIKE = "~";
+OpenLayers.Filter.Comparison.IS_NULL = "NULL";
diff --git a/misc/openlayers/lib/OpenLayers/Filter/FeatureId.js b/misc/openlayers/lib/OpenLayers/Filter/FeatureId.js
new file mode 100644
index 0000000..2927651
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Filter/FeatureId.js
@@ -0,0 +1,87 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.FeatureId
+ * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
+ * styling
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: fids
+ * {Array(String)} Feature Ids to evaluate this rule against.
+ * To be passed inside the params object.
+ */
+ fids: null,
+
+ /**
+ * Property: type
+ * {String} Type to identify this filter.
+ */
+ type: "FID",
+
+ /**
+ * Constructor: OpenLayers.Filter.FeatureId
+ * Creates an ogc:FeatureId rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>}
+ */
+ initialize: function(options) {
+ this.fids = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+ * For vector features, the check is run against the fid,
+ * for plain features against the id.
+ *
+ * Returns:
+ * {Boolean} true if the rule applies, false if it does not
+ */
+ evaluate: function(feature) {
+ for (var i=0, len=this.fids.length; i<len; i++) {
+ var fid = feature.fid || feature.id;
+ if (fid == this.fids[i]) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.FeatureId>} Clone of this filter.
+ */
+ clone: function() {
+ var filter = new OpenLayers.Filter.FeatureId();
+ OpenLayers.Util.extend(filter, this);
+ filter.fids = this.fids.slice();
+ return filter;
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.FeatureId"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Filter/Function.js b/misc/openlayers/lib/OpenLayers/Filter/Function.js
new file mode 100644
index 0000000..0d5a7a9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Filter/Function.js
@@ -0,0 +1,49 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Function
+ * This class represents a filter function.
+ * We are using this class for creation of complex
+ * filters that can contain filter functions as values.
+ * Nesting function as other functions parameter is supported.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Function = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: name
+ * {String} Name of the function.
+ */
+ name: null,
+
+ /**
+ * APIProperty: params
+ * {Array(<OpenLayers.Filter.Function> || String || Number)} Function parameters
+ * For now support only other Functions, String or Number
+ */
+ params: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Function
+ * Creates a filter function.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * function.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Function>}
+ */
+
+ CLASS_NAME: "OpenLayers.Filter.Function"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Filter/Logical.js b/misc/openlayers/lib/OpenLayers/Filter/Logical.js
new file mode 100644
index 0000000..4eac579
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Filter/Logical.js
@@ -0,0 +1,121 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: filters
+ * {Array(<OpenLayers.Filter>)} Child filters for this filter.
+ */
+ filters: null,
+
+ /**
+ * APIProperty: type
+ * {String} type of logical operator. Available types are:
+ * - OpenLayers.Filter.Logical.AND = "&&";
+ * - OpenLayers.Filter.Logical.OR = "||";
+ * - OpenLayers.Filter.Logical.NOT = "!";
+ */
+ type: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Logical
+ * Creates a logical filter (And, Or, Not).
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>}
+ */
+ initialize: function(options) {
+ this.filters = [];
+ OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove reference to child filters.
+ */
+ destroy: function() {
+ this.filters = null;
+ OpenLayers.Filter.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: evaluate
+ * Evaluates this filter in a specific context.
+ *
+ * Parameters:
+ * context - {Object} Context to use in evaluating the filter. A vector
+ * feature may also be provided to evaluate feature attributes in
+ * comparison filters or geometries in spatial filters.
+ *
+ * Returns:
+ * {Boolean} The filter applies.
+ */
+ evaluate: function(context) {
+ var i, len;
+ switch(this.type) {
+ case OpenLayers.Filter.Logical.AND:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == false) {
+ return false;
+ }
+ }
+ return true;
+
+ case OpenLayers.Filter.Logical.OR:
+ for (i=0, len=this.filters.length; i<len; i++) {
+ if (this.filters[i].evaluate(context) == true) {
+ return true;
+ }
+ }
+ return false;
+
+ case OpenLayers.Filter.Logical.NOT:
+ return (!this.filters[0].evaluate(context));
+ }
+ return undefined;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Logical>} Clone of this filter.
+ */
+ clone: function() {
+ var filters = [];
+ for(var i=0, len=this.filters.length; i<len; ++i) {
+ filters.push(this.filters[i].clone());
+ }
+ return new OpenLayers.Filter.Logical({
+ type: this.type,
+ filters: filters
+ });
+ },
+
+ CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR = "||";
+OpenLayers.Filter.Logical.NOT = "!";
diff --git a/misc/openlayers/lib/OpenLayers/Filter/Spatial.js b/misc/openlayers/lib/OpenLayers/Filter/Spatial.js
new file mode 100644
index 0000000..dd9e2a7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Filter/Spatial.js
@@ -0,0 +1,122 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Spatial
+ * This class represents a spatial filter.
+ * Currently implemented: BBOX, DWithin and Intersects
+ *
+ * Inherits from:
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, {
+
+ /**
+ * APIProperty: type
+ * {String} Type of spatial filter.
+ *
+ * The type should be one of:
+ * - OpenLayers.Filter.Spatial.BBOX
+ * - OpenLayers.Filter.Spatial.INTERSECTS
+ * - OpenLayers.Filter.Spatial.DWITHIN
+ * - OpenLayers.Filter.Spatial.WITHIN
+ * - OpenLayers.Filter.Spatial.CONTAINS
+ */
+ type: null,
+
+ /**
+ * APIProperty: property
+ * {String} Name of the context property to compare.
+ */
+ property: null,
+
+ /**
+ * APIProperty: value
+ * {<OpenLayers.Bounds> || <OpenLayers.Geometry>} The bounds or geometry
+ * to be used by the filter. Use bounds for BBOX filters and geometry
+ * for INTERSECTS or DWITHIN filters.
+ */
+ value: null,
+
+ /**
+ * APIProperty: distance
+ * {Number} The distance to use in a DWithin spatial filter.
+ */
+ distance: null,
+
+ /**
+ * APIProperty: distanceUnits
+ * {String} The units to use for the distance, e.g. 'm'.
+ */
+ distanceUnits: null,
+
+ /**
+ * Constructor: OpenLayers.Filter.Spatial
+ * Creates a spatial filter.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>}
+ */
+
+ /**
+ * Method: evaluate
+ * Evaluates this filter for a specific feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} feature to apply the filter to.
+ *
+ * Returns:
+ * {Boolean} The feature meets filter criteria.
+ */
+ evaluate: function(feature) {
+ var intersect = false;
+ switch(this.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ case OpenLayers.Filter.Spatial.INTERSECTS:
+ if(feature.geometry) {
+ var geom = this.value;
+ if(this.value.CLASS_NAME == "OpenLayers.Bounds") {
+ geom = this.value.toGeometry();
+ }
+ if(feature.geometry.intersects(geom)) {
+ intersect = true;
+ }
+ }
+ break;
+ default:
+ throw new Error('evaluate is not implemented for this filter type.');
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this filter.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} Clone of this filter.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.applyDefaults({
+ value: this.value && this.value.clone && this.value.clone()
+ }, this);
+ return new OpenLayers.Filter.Spatial(options);
+ },
+ CLASS_NAME: "OpenLayers.Filter.Spatial"
+});
+
+OpenLayers.Filter.Spatial.BBOX = "BBOX";
+OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS";
+OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN";
+OpenLayers.Filter.Spatial.WITHIN = "WITHIN";
+OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS";
diff --git a/misc/openlayers/lib/OpenLayers/Format.js b/misc/openlayers/lib/OpenLayers/Format.js
new file mode 100644
index 0000000..620ecc7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format.js
@@ -0,0 +1,123 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats. Subclasses
+ * of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+
+ /**
+ * Property: options
+ * {Object} A reference to options passed to the constructor.
+ */
+ options: null,
+
+ /**
+ * APIProperty: externalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The externalProjection is the projection used by
+ * the content which is passed into read or which comes out of write.
+ * In order to reproject, a projection transformation function for the
+ * specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ externalProjection: null,
+
+ /**
+ * APIProperty: internalProjection
+ * {<OpenLayers.Projection>} When passed a externalProjection and
+ * internalProjection, the format will reproject the geometries it
+ * reads or writes. The internalProjection is the projection used by
+ * the geometries which are returned by read or which are passed into
+ * write. In order to reproject, a projection transformation function
+ * for the specified projections must be available. This support may be
+ * provided via proj4js or via a custom transformation function. See
+ * {<OpenLayers.Projection.addTransform>} for more information on
+ * custom transformations.
+ */
+ internalProjection: null,
+
+ /**
+ * APIProperty: data
+ * {Object} When <keepData> is true, this is the parsed string sent to
+ * <read>.
+ */
+ data: null,
+
+ /**
+ * APIProperty: keepData
+ * {Object} Maintain a reference (<data>) to the most recently read data.
+ * Default is false.
+ */
+ keepData: false,
+
+ /**
+ * Constructor: OpenLayers.Format
+ * Instances of this class are not useful. See one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * format
+ *
+ * Valid options:
+ * keepData - {Boolean} If true, upon <read>, the data property will be
+ * set to the parsed object (e.g. the json or xml object).
+ *
+ * Returns:
+ * An instance of OpenLayers.Format
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ },
+
+ /**
+ * Method: read
+ * Read data from a string, and return an object whose type depends on the
+ * subclass.
+ *
+ * Parameters:
+ * data - {string} Data to read/parse.
+ *
+ * Returns:
+ * Depends on the subclass
+ */
+ read: function(data) {
+ throw new Error('Read not implemented.');
+ },
+
+ /**
+ * Method: write
+ * Accept an object, and return a string.
+ *
+ * Parameters:
+ * object - {Object} Object to be serialized
+ *
+ * Returns:
+ * {String} A string representation of the object.
+ */
+ write: function(object) {
+ throw new Error('Write not implemented.');
+ },
+
+ CLASS_NAME: "OpenLayers.Format"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/ArcXML.js b/misc/openlayers/lib/OpenLayers/Format/ArcXML.js
new file mode 100644
index 0000000..9d523d1
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/ArcXML.js
@@ -0,0 +1,1028 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Format.ArcXML
+ * Read/Write ArcXML. Create a new instance with the <OpenLayers.Format.ArcXML>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.ArcXML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: fontStyleKeys
+ * {Array} List of keys used in font styling.
+ */
+ fontStyleKeys: [
+ 'antialiasing', 'blockout', 'font', 'fontcolor','fontsize', 'fontstyle',
+ 'glowing', 'interval', 'outline', 'printmode', 'shadow', 'transparency'
+ ],
+
+ /**
+ * Property: request
+ * A get_image request destined for an ArcIMS server.
+ */
+ request: null,
+
+ /**
+ * Property: response
+ * A parsed response from an ArcIMS server.
+ */
+ response: null,
+
+ /**
+ * Constructor: OpenLayers.Format.ArcXML
+ * Create a new parser/writer for ArcXML. Create an instance of this class
+ * to begin authoring a request to an ArcIMS service. This is used
+ * primarily by the ArcIMS layer, but could be used to do other wild
+ * stuff, like geocoding.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ this.request = new OpenLayers.Format.ArcXML.Request();
+ this.response = new OpenLayers.Format.ArcXML.Response();
+
+ if (options) {
+ if (options.requesttype == "feature") {
+ this.request.get_image = null;
+
+ var qry = this.request.get_feature.query;
+ this.addCoordSys(qry.featurecoordsys, options.featureCoordSys);
+ this.addCoordSys(qry.filtercoordsys, options.filterCoordSys);
+
+ if (options.polygon) {
+ qry.isspatial = true;
+ qry.spatialfilter.polygon = options.polygon;
+ } else if (options.envelope) {
+ qry.isspatial = true;
+ qry.spatialfilter.envelope = {minx:0, miny:0, maxx:0, maxy:0};
+ this.parseEnvelope(qry.spatialfilter.envelope, options.envelope);
+ }
+ } else if (options.requesttype == "image") {
+ this.request.get_feature = null;
+
+ var props = this.request.get_image.properties;
+ this.parseEnvelope(props.envelope, options.envelope);
+
+ this.addLayers(props.layerlist, options.layers);
+ this.addImageSize(props.imagesize, options.tileSize);
+ this.addCoordSys(props.featurecoordsys, options.featureCoordSys);
+ this.addCoordSys(props.filtercoordsys, options.filterCoordSys);
+ } else {
+ // if an arcxml object is being created with no request type, it is
+ // probably going to consume a response, so do not throw an error if
+ // the requesttype is not defined
+ this.request = null;
+ }
+ }
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: parseEnvelope
+ * Parse an array of coordinates into an ArcXML envelope structure.
+ *
+ * Parameters:
+ * env - {Object} An envelope object that will contain the parsed coordinates.
+ * arr - {Array(double)} An array of coordinates in the order: [ minx, miny, maxx, maxy ]
+ */
+ parseEnvelope: function(env, arr) {
+ if (arr && arr.length == 4) {
+ env.minx = arr[0];
+ env.miny = arr[1];
+ env.maxx = arr[2];
+ env.maxy = arr[3];
+ }
+ },
+
+ /**
+ * Method: addLayers
+ * Add a collection of layers to another collection of layers. Each layer in the list is tuple of
+ * { id, visible }. These layer collections represent the
+ * /ARCXML/REQUEST/get_image/PROPERTIES/LAYERLIST/LAYERDEF items in ArcXML
+ *
+ * TODO: Add support for dynamic layer rendering.
+ *
+ * Parameters:
+ * ll - {Array({id,visible})} A list of layer definitions.
+ * lyrs - {Array({id,visible})} A list of layer definitions.
+ */
+ addLayers: function(ll, lyrs) {
+ for(var lind = 0, len=lyrs.length; lind < len; lind++) {
+ ll.push(lyrs[lind]);
+ }
+ },
+
+ /**
+ * Method: addImageSize
+ * Set the size of the requested image.
+ *
+ * Parameters:
+ * imsize - {Object} An ArcXML imagesize object.
+ * olsize - {<OpenLayers.Size>} The image size to set.
+ */
+ addImageSize: function(imsize, olsize) {
+ if (olsize !== null) {
+ imsize.width = olsize.w;
+ imsize.height = olsize.h;
+ imsize.printwidth = olsize.w;
+ imsize.printheight = olsize.h;
+ }
+ },
+
+ /**
+ * Method: addCoordSys
+ * Add the coordinate system information to an object. The object may be
+ *
+ * Parameters:
+ * featOrFilt - {Object} A featurecoordsys or filtercoordsys ArcXML structure.
+ * fsys - {String} or {<OpenLayers.Projection>} or {filtercoordsys} or
+ * {featurecoordsys} A projection representation. If it's a {String},
+ * the value is assumed to be the SRID. If it's a {OpenLayers.Projection}
+ * AND Proj4js is available, the projection number and name are extracted
+ * from there. If it's a filter or feature ArcXML structure, it is copied.
+ */
+ addCoordSys: function(featOrFilt, fsys) {
+ if (typeof fsys == "string") {
+ featOrFilt.id = parseInt(fsys);
+ featOrFilt.string = fsys;
+ }
+ // is this a proj4js instance?
+ else if (typeof fsys == "object" && fsys.proj !== null){
+ featOrFilt.id = fsys.proj.srsProjNumber;
+ featOrFilt.string = fsys.proj.srsCode;
+ } else {
+ featOrFilt = fsys;
+ }
+ },
+
+ /**
+ * APIMethod: iserror
+ * Check to see if the response from the server was an error.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse. If nothing is supplied,
+ * the current response is examined.
+ *
+ * Returns:
+ * {Boolean} true if the response was an error.
+ */
+ iserror: function(data) {
+ var ret = null;
+
+ if (!data) {
+ ret = (this.response.error !== '');
+ } else {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ var errorNodes = data.documentElement.getElementsByTagName("ERROR");
+ ret = (errorNodes !== null && errorNodes.length > 0);
+ }
+
+ return ret;
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return an response.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {<OpenLayers.Format.ArcXML.Response>} An ArcXML response. Note that this response
+ * data may change in the future.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+
+ var arcNode = null;
+ if (data && data.documentElement) {
+ if(data.documentElement.nodeName == "ARCXML") {
+ arcNode = data.documentElement;
+ } else {
+ arcNode = data.documentElement.getElementsByTagName("ARCXML")[0];
+ }
+ }
+
+ // in Safari, arcNode will be there but will have a child named
+ // parsererror
+ if (!arcNode || arcNode.firstChild.nodeName === 'parsererror') {
+ var error, source;
+ try {
+ error = data.firstChild.nodeValue;
+ source = data.firstChild.childNodes[1].firstChild.nodeValue;
+ } catch (err) {
+ // pass
+ }
+ throw {
+ message: "Error parsing the ArcXML request",
+ error: error,
+ source: source
+ };
+ }
+
+ var response = this.parseResponse(arcNode);
+ return response;
+ },
+
+ /**
+ * APIMethod: write
+ * Generate an ArcXml document string for sending to an ArcIMS server.
+ *
+ * Returns:
+ * {String} A string representing the ArcXML document request.
+ */
+ write: function(request) {
+ if (!request) {
+ request = this.request;
+ }
+ var root = this.createElementNS("", "ARCXML");
+ root.setAttribute("version","1.1");
+
+ var reqElem = this.createElementNS("", "REQUEST");
+
+ if (request.get_image != null) {
+ var getElem = this.createElementNS("", "GET_IMAGE");
+ reqElem.appendChild(getElem);
+
+ var propElem = this.createElementNS("", "PROPERTIES");
+ getElem.appendChild(propElem);
+
+ var props = request.get_image.properties;
+ if (props.featurecoordsys != null) {
+ var feat = this.createElementNS("", "FEATURECOORDSYS");
+ propElem.appendChild(feat);
+
+ if (props.featurecoordsys.id === 0) {
+ feat.setAttribute("string", props.featurecoordsys['string']);
+ }
+ else {
+ feat.setAttribute("id", props.featurecoordsys.id);
+ }
+ }
+
+ if (props.filtercoordsys != null) {
+ var filt = this.createElementNS("", "FILTERCOORDSYS");
+ propElem.appendChild(filt);
+
+ if (props.filtercoordsys.id === 0) {
+ filt.setAttribute("string", props.filtercoordsys.string);
+ }
+ else {
+ filt.setAttribute("id", props.filtercoordsys.id);
+ }
+ }
+
+ if (props.envelope != null) {
+ var env = this.createElementNS("", "ENVELOPE");
+ propElem.appendChild(env);
+
+ env.setAttribute("minx", props.envelope.minx);
+ env.setAttribute("miny", props.envelope.miny);
+ env.setAttribute("maxx", props.envelope.maxx);
+ env.setAttribute("maxy", props.envelope.maxy);
+ }
+
+ var imagesz = this.createElementNS("", "IMAGESIZE");
+ propElem.appendChild(imagesz);
+
+ imagesz.setAttribute("height", props.imagesize.height);
+ imagesz.setAttribute("width", props.imagesize.width);
+
+ if (props.imagesize.height != props.imagesize.printheight ||
+ props.imagesize.width != props.imagesize.printwidth) {
+ imagesz.setAttribute("printheight", props.imagesize.printheight);
+ imagesz.setArrtibute("printwidth", props.imagesize.printwidth);
+ }
+
+ if (props.background != null) {
+ var backgrnd = this.createElementNS("", "BACKGROUND");
+ propElem.appendChild(backgrnd);
+
+ backgrnd.setAttribute("color",
+ props.background.color.r + "," +
+ props.background.color.g + "," +
+ props.background.color.b);
+
+ if (props.background.transcolor !== null) {
+ backgrnd.setAttribute("transcolor",
+ props.background.transcolor.r + "," +
+ props.background.transcolor.g + "," +
+ props.background.transcolor.b);
+ }
+ }
+
+ if (props.layerlist != null && props.layerlist.length > 0) {
+ var layerlst = this.createElementNS("", "LAYERLIST");
+ propElem.appendChild(layerlst);
+
+ for (var ld = 0; ld < props.layerlist.length; ld++) {
+ var ldef = this.createElementNS("", "LAYERDEF");
+ layerlst.appendChild(ldef);
+
+ ldef.setAttribute("id", props.layerlist[ld].id);
+ ldef.setAttribute("visible", props.layerlist[ld].visible);
+
+ if (typeof props.layerlist[ld].query == "object") {
+ var query = props.layerlist[ld].query;
+
+ if (query.where.length < 0) {
+ continue;
+ }
+
+ var queryElem = null;
+ if (typeof query.spatialfilter == "boolean" && query.spatialfilter) {
+ // handle spatial filter madness
+ queryElem = this.createElementNS("", "SPATIALQUERY");
+ }
+ else {
+ queryElem = this.createElementNS("", "QUERY");
+ }
+
+ queryElem.setAttribute("where", query.where);
+
+ if (typeof query.accuracy == "number" && query.accuracy > 0) {
+ queryElem.setAttribute("accuracy", query.accuracy);
+ }
+ if (typeof query.featurelimit == "number" && query.featurelimit < 2000) {
+ queryElem.setAttribute("featurelimit", query.featurelimit);
+ }
+ if (typeof query.subfields == "string" && query.subfields != "#ALL#") {
+ queryElem.setAttribute("subfields", query.subfields);
+ }
+ if (typeof query.joinexpression == "string" && query.joinexpression.length > 0) {
+ queryElem.setAttribute("joinexpression", query.joinexpression);
+ }
+ if (typeof query.jointables == "string" && query.jointables.length > 0) {
+ queryElem.setAttribute("jointables", query.jointables);
+ }
+
+ ldef.appendChild(queryElem);
+ }
+
+ if (typeof props.layerlist[ld].renderer == "object") {
+ this.addRenderer(ldef, props.layerlist[ld].renderer);
+ }
+ }
+ }
+ } else if (request.get_feature != null) {
+ var getElem = this.createElementNS("", "GET_FEATURES");
+ getElem.setAttribute("outputmode", "newxml");
+ getElem.setAttribute("checkesc", "true");
+
+ if (request.get_feature.geometry) {
+ getElem.setAttribute("geometry", request.get_feature.geometry);
+ }
+ else {
+ getElem.setAttribute("geometry", "false");
+ }
+
+ if (request.get_feature.compact) {
+ getElem.setAttribute("compact", request.get_feature.compact);
+ }
+
+ if (request.get_feature.featurelimit == "number") {
+ getElem.setAttribute("featurelimit", request.get_feature.featurelimit);
+ }
+
+ getElem.setAttribute("globalenvelope", "true");
+ reqElem.appendChild(getElem);
+
+ if (request.get_feature.layer != null && request.get_feature.layer.length > 0) {
+ var lyrElem = this.createElementNS("", "LAYER");
+ lyrElem.setAttribute("id", request.get_feature.layer);
+ getElem.appendChild(lyrElem);
+ }
+
+ var fquery = request.get_feature.query;
+ if (fquery != null) {
+ var qElem = null;
+ if (fquery.isspatial) {
+ qElem = this.createElementNS("", "SPATIALQUERY");
+ } else {
+ qElem = this.createElementNS("", "QUERY");
+ }
+ getElem.appendChild(qElem);
+
+ if (typeof fquery.accuracy == "number") {
+ qElem.setAttribute("accuracy", fquery.accuracy);
+ }
+ //qElem.setAttribute("featurelimit", "5");
+
+ if (fquery.featurecoordsys != null) {
+ var fcsElem1 = this.createElementNS("", "FEATURECOORDSYS");
+
+ if (fquery.featurecoordsys.id == 0) {
+ fcsElem1.setAttribute("string", fquery.featurecoordsys.string);
+ } else {
+ fcsElem1.setAttribute("id", fquery.featurecoordsys.id);
+ }
+ qElem.appendChild(fcsElem1);
+ }
+
+ if (fquery.filtercoordsys != null) {
+ var fcsElem2 = this.createElementNS("", "FILTERCOORDSYS");
+
+ if (fquery.filtercoordsys.id === 0) {
+ fcsElem2.setAttribute("string", fquery.filtercoordsys.string);
+ } else {
+ fcsElem2.setAttribute("id", fquery.filtercoordsys.id);
+ }
+ qElem.appendChild(fcsElem2);
+ }
+
+ if (fquery.buffer > 0) {
+ var bufElem = this.createElementNS("", "BUFFER");
+ bufElem.setAttribute("distance", fquery.buffer);
+ qElem.appendChild(bufElem);
+ }
+
+ if (fquery.isspatial) {
+ var spfElem = this.createElementNS("", "SPATIALFILTER");
+ spfElem.setAttribute("relation", fquery.spatialfilter.relation);
+ qElem.appendChild(spfElem);
+
+ if (fquery.spatialfilter.envelope) {
+ var envElem = this.createElementNS("", "ENVELOPE");
+ envElem.setAttribute("minx", fquery.spatialfilter.envelope.minx);
+ envElem.setAttribute("miny", fquery.spatialfilter.envelope.miny);
+ envElem.setAttribute("maxx", fquery.spatialfilter.envelope.maxx);
+ envElem.setAttribute("maxy", fquery.spatialfilter.envelope.maxy);
+ spfElem.appendChild(envElem);
+ } else if(typeof fquery.spatialfilter.polygon == "object") {
+ spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon));
+ }
+ }
+
+ if (fquery.where != null && fquery.where.length > 0) {
+ qElem.setAttribute("where", fquery.where);
+ }
+ }
+ }
+
+ root.appendChild(reqElem);
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+
+ addGroupRenderer: function(ldef, toprenderer) {
+ var topRelem = this.createElementNS("", "GROUPRENDERER");
+ ldef.appendChild(topRelem);
+
+ for (var rind = 0; rind < toprenderer.length; rind++) {
+ var renderer = toprenderer[rind];
+ this.addRenderer(topRelem, renderer);
+ }
+ },
+
+
+ addRenderer: function(topRelem, renderer) {
+ if (OpenLayers.Util.isArray(renderer)) {
+ this.addGroupRenderer(topRelem, renderer);
+ } else {
+ var renderElem = this.createElementNS("", renderer.type.toUpperCase() + "RENDERER");
+ topRelem.appendChild(renderElem);
+
+ if (renderElem.tagName == "VALUEMAPRENDERER") {
+ this.addValueMapRenderer(renderElem, renderer);
+ } else if (renderElem.tagName == "VALUEMAPLABELRENDERER") {
+ this.addValueMapLabelRenderer(renderElem, renderer);
+ } else if (renderElem.tagName == "SIMPLELABELRENDERER") {
+ this.addSimpleLabelRenderer(renderElem, renderer);
+ } else if (renderElem.tagName == "SCALEDEPENDENTRENDERER") {
+ this.addScaleDependentRenderer(renderElem, renderer);
+ }
+ }
+ },
+
+
+ addScaleDependentRenderer: function(renderElem, renderer) {
+ if (typeof renderer.lower == "string" || typeof renderer.lower == "number") {
+ renderElem.setAttribute("lower", renderer.lower);
+ }
+ if (typeof renderer.upper == "string" || typeof renderer.upper == "number") {
+ renderElem.setAttribute("upper", renderer.upper);
+ }
+
+ this.addRenderer(renderElem, renderer.renderer);
+ },
+
+
+ addValueMapLabelRenderer: function(renderElem, renderer) {
+ renderElem.setAttribute("lookupfield", renderer.lookupfield);
+ renderElem.setAttribute("labelfield", renderer.labelfield);
+
+ if (typeof renderer.exacts == "object") {
+ for (var ext=0, extlen=renderer.exacts.length; ext<extlen; ext++) {
+ var exact = renderer.exacts[ext];
+
+ var eelem = this.createElementNS("", "EXACT");
+
+ if (typeof exact.value == "string") {
+ eelem.setAttribute("value", exact.value);
+ }
+ if (typeof exact.label == "string") {
+ eelem.setAttribute("label", exact.label);
+ }
+ if (typeof exact.method == "string") {
+ eelem.setAttribute("method", exact.method);
+ }
+
+ renderElem.appendChild(eelem);
+
+ if (typeof exact.symbol == "object") {
+ var selem = null;
+
+ if (exact.symbol.type == "text") {
+ selem = this.createElementNS("", "TEXTSYMBOL");
+ }
+
+ if (selem != null) {
+ var keys = this.fontStyleKeys;
+ for (var i = 0, len = keys.length; i < len; i++) {
+ var key = keys[i];
+ if (exact.symbol[key]) {
+ selem.setAttribute(key, exact.symbol[key]);
+ }
+ }
+ eelem.appendChild(selem);
+ }
+ }
+ } // for each exact
+ }
+ },
+
+ addValueMapRenderer: function(renderElem, renderer) {
+ renderElem.setAttribute("lookupfield", renderer.lookupfield);
+
+ if (typeof renderer.ranges == "object") {
+ for(var rng=0, rnglen=renderer.ranges.length; rng<rnglen; rng++) {
+ var range = renderer.ranges[rng];
+
+ var relem = this.createElementNS("", "RANGE");
+ relem.setAttribute("lower", range.lower);
+ relem.setAttribute("upper", range.upper);
+
+ renderElem.appendChild(relem);
+
+ if (typeof range.symbol == "object") {
+ var selem = null;
+
+ if (range.symbol.type == "simplepolygon") {
+ selem = this.createElementNS("", "SIMPLEPOLYGONSYMBOL");
+ }
+
+ if (selem != null) {
+ if (typeof range.symbol.boundarycolor == "string") {
+ selem.setAttribute("boundarycolor", range.symbol.boundarycolor);
+ }
+ if (typeof range.symbol.fillcolor == "string") {
+ selem.setAttribute("fillcolor", range.symbol.fillcolor);
+ }
+ if (typeof range.symbol.filltransparency == "number") {
+ selem.setAttribute("filltransparency", range.symbol.filltransparency);
+ }
+ relem.appendChild(selem);
+ }
+ }
+ } // for each range
+ } else if (typeof renderer.exacts == "object") {
+ for (var ext=0, extlen=renderer.exacts.length; ext<extlen; ext++) {
+ var exact = renderer.exacts[ext];
+
+ var eelem = this.createElementNS("", "EXACT");
+ if (typeof exact.value == "string") {
+ eelem.setAttribute("value", exact.value);
+ }
+ if (typeof exact.label == "string") {
+ eelem.setAttribute("label", exact.label);
+ }
+ if (typeof exact.method == "string") {
+ eelem.setAttribute("method", exact.method);
+ }
+
+ renderElem.appendChild(eelem);
+
+ if (typeof exact.symbol == "object") {
+ var selem = null;
+
+ if (exact.symbol.type == "simplemarker") {
+ selem = this.createElementNS("", "SIMPLEMARKERSYMBOL");
+ }
+
+ if (selem != null) {
+ if (typeof exact.symbol.antialiasing == "string") {
+ selem.setAttribute("antialiasing", exact.symbol.antialiasing);
+ }
+ if (typeof exact.symbol.color == "string") {
+ selem.setAttribute("color", exact.symbol.color);
+ }
+ if (typeof exact.symbol.outline == "string") {
+ selem.setAttribute("outline", exact.symbol.outline);
+ }
+ if (typeof exact.symbol.overlap == "string") {
+ selem.setAttribute("overlap", exact.symbol.overlap);
+ }
+ if (typeof exact.symbol.shadow == "string") {
+ selem.setAttribute("shadow", exact.symbol.shadow);
+ }
+ if (typeof exact.symbol.transparency == "number") {
+ selem.setAttribute("transparency", exact.symbol.transparency);
+ }
+ //if (typeof exact.symbol.type == "string")
+ // selem.setAttribute("type", exact.symbol.type);
+ if (typeof exact.symbol.usecentroid == "string") {
+ selem.setAttribute("usecentroid", exact.symbol.usecentroid);
+ }
+ if (typeof exact.symbol.width == "number") {
+ selem.setAttribute("width", exact.symbol.width);
+ }
+
+ eelem.appendChild(selem);
+ }
+ }
+ } // for each exact
+ }
+ },
+
+
+ addSimpleLabelRenderer: function(renderElem, renderer) {
+ renderElem.setAttribute("field", renderer.field);
+ var keys = ['featureweight', 'howmanylabels', 'labelbufferratio',
+ 'labelpriorities', 'labelweight', 'linelabelposition',
+ 'rotationalangles'];
+ for (var i=0, len=keys.length; i<len; i++) {
+ var key = keys[i];
+ if (renderer[key]) {
+ renderElem.setAttribute(key, renderer[key]);
+ }
+ }
+
+ if (renderer.symbol.type == "text") {
+ var symbol = renderer.symbol;
+ var selem = this.createElementNS("", "TEXTSYMBOL");
+ renderElem.appendChild(selem);
+
+ var keys = this.fontStyleKeys;
+ for (var i=0, len=keys.length; i<len; i++) {
+ var key = keys[i];
+ if (symbol[key]) {
+ selem.setAttribute(key, renderer[key]);
+ }
+ }
+ }
+ },
+
+ writePolygonGeometry: function(polygon) {
+ if (!(polygon instanceof OpenLayers.Geometry.Polygon)) {
+ throw {
+ message:'Cannot write polygon geometry to ArcXML with an ' +
+ polygon.CLASS_NAME + ' object.',
+ geometry: polygon
+ };
+ }
+
+ var polyElem = this.createElementNS("", "POLYGON");
+
+ for (var ln=0, lnlen=polygon.components.length; ln<lnlen; ln++) {
+ var ring = polygon.components[ln];
+ var ringElem = this.createElementNS("", "RING");
+
+ for (var rn=0, rnlen=ring.components.length; rn<rnlen; rn++) {
+ var point = ring.components[rn];
+ var pointElem = this.createElementNS("", "POINT");
+
+ pointElem.setAttribute("x", point.x);
+ pointElem.setAttribute("y", point.y);
+
+ ringElem.appendChild(pointElem);
+ }
+
+ polyElem.appendChild(ringElem);
+ }
+
+ return polyElem;
+ },
+
+ /**
+ * Method: parseResponse
+ * Take an ArcXML response, and parse in into this object's internal properties.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} The ArcXML response, as either a string or the
+ * top level DOMElement of the response.
+ */
+ parseResponse: function(data) {
+ if(typeof data == "string") {
+ var newData = new OpenLayers.Format.XML();
+ data = newData.read(data);
+ }
+ var response = new OpenLayers.Format.ArcXML.Response();
+
+ var errorNode = data.getElementsByTagName("ERROR");
+
+ if (errorNode != null && errorNode.length > 0) {
+ response.error = this.getChildValue(errorNode, "Unknown error.");
+ } else {
+ var responseNode = data.getElementsByTagName("RESPONSE");
+
+ if (responseNode == null || responseNode.length == 0) {
+ response.error = "No RESPONSE tag found in ArcXML response.";
+ return response;
+ }
+
+ var rtype = responseNode[0].firstChild.nodeName;
+ if (rtype == "#text") {
+ rtype = responseNode[0].firstChild.nextSibling.nodeName;
+ }
+
+ if (rtype == "IMAGE") {
+ var envelopeNode = data.getElementsByTagName("ENVELOPE");
+ var outputNode = data.getElementsByTagName("OUTPUT");
+
+ if (envelopeNode == null || envelopeNode.length == 0) {
+ response.error = "No ENVELOPE tag found in ArcXML response.";
+ } else if (outputNode == null || outputNode.length == 0) {
+ response.error = "No OUTPUT tag found in ArcXML response.";
+ } else {
+ var envAttr = this.parseAttributes(envelopeNode[0]);
+ var outputAttr = this.parseAttributes(outputNode[0]);
+
+ if (typeof outputAttr.type == "string") {
+ response.image = {
+ envelope: envAttr,
+ output: {
+ type: outputAttr.type,
+ data: this.getChildValue(outputNode[0])
+ }
+ };
+ } else {
+ response.image = { envelope: envAttr, output: outputAttr };
+ }
+ }
+ } else if (rtype == "FEATURES") {
+ var features = responseNode[0].getElementsByTagName("FEATURES");
+
+ // get the feature count
+ var featureCount = features[0].getElementsByTagName("FEATURECOUNT");
+ response.features.featurecount = featureCount[0].getAttribute("count");
+
+ if (response.features.featurecount > 0) {
+ // get the feature envelope
+ var envelope = features[0].getElementsByTagName("ENVELOPE");
+ response.features.envelope = this.parseAttributes(envelope[0], typeof(0));
+
+ // get the field values per feature
+ var featureList = features[0].getElementsByTagName("FEATURE");
+ for (var fn = 0; fn < featureList.length; fn++) {
+ var feature = new OpenLayers.Feature.Vector();
+ var fields = featureList[fn].getElementsByTagName("FIELD");
+
+ for (var fdn = 0; fdn < fields.length; fdn++) {
+ var fieldName = fields[fdn].getAttribute("name");
+ var fieldValue = fields[fdn].getAttribute("value");
+ feature.attributes[ fieldName ] = fieldValue;
+ }
+
+ var geom = featureList[fn].getElementsByTagName("POLYGON");
+
+ if (geom.length > 0) {
+ // if there is a polygon, create an openlayers polygon, and assign
+ // it to the .geometry property of the feature
+ var ring = geom[0].getElementsByTagName("RING");
+
+ var polys = [];
+ for (var rn = 0; rn < ring.length; rn++) {
+ var linearRings = [];
+ linearRings.push(this.parsePointGeometry(ring[rn]));
+
+ var holes = ring[rn].getElementsByTagName("HOLE");
+ for (var hn = 0; hn < holes.length; hn++) {
+ linearRings.push(this.parsePointGeometry(holes[hn]));
+ }
+ holes = null;
+ polys.push(new OpenLayers.Geometry.Polygon(linearRings));
+ linearRings = null;
+ }
+ ring = null;
+
+ if (polys.length == 1) {
+ feature.geometry = polys[0];
+ } else
+ {
+ feature.geometry = new OpenLayers.Geometry.MultiPolygon(polys);
+ }
+ }
+
+ response.features.feature.push(feature);
+ }
+ }
+ } else {
+ response.error = "Unidentified response type.";
+ }
+ }
+ return response;
+ },
+
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {<DOMElement>} An element to parse attributes from.
+ *
+ * Returns:
+ * {Object} An attributes object, with properties set to attribute values.
+ */
+ parseAttributes: function(node,type) {
+ var attributes = {};
+ for(var attr = 0; attr < node.attributes.length; attr++) {
+ if (type == "number") {
+ attributes[node.attributes[attr].nodeName] = parseFloat(node.attributes[attr].nodeValue);
+ } else {
+ attributes[node.attributes[attr].nodeName] = node.attributes[attr].nodeValue;
+ }
+ }
+ return attributes;
+ },
+
+
+ /**
+ * Method: parsePointGeometry
+ *
+ * Parameters:
+ * node - {<DOMElement>} An element to parse <COORDS> or <POINT> arcxml data from.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LinearRing>} A linear ring represented by the node's points.
+ */
+ parsePointGeometry: function(node) {
+ var ringPoints = [];
+ var coords = node.getElementsByTagName("COORDS");
+
+ if (coords.length > 0) {
+ // if coords is present, it's the only coords item
+ var coordArr = this.getChildValue(coords[0]);
+ coordArr = coordArr.split(/;/);
+ for (var cn = 0; cn < coordArr.length; cn++) {
+ var coordItems = coordArr[cn].split(/ /);
+ ringPoints.push(new OpenLayers.Geometry.Point(coordItems[0], coordItems[1]));
+ }
+ coords = null;
+ } else {
+ var point = node.getElementsByTagName("POINT");
+ if (point.length > 0) {
+ for (var pn = 0; pn < point.length; pn++) {
+ ringPoints.push(
+ new OpenLayers.Geometry.Point(
+ parseFloat(point[pn].getAttribute("x")),
+ parseFloat(point[pn].getAttribute("y"))
+ )
+ );
+ }
+ }
+ point = null;
+ }
+
+ return new OpenLayers.Geometry.LinearRing(ringPoints);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.ArcXML"
+});
+
+OpenLayers.Format.ArcXML.Request = OpenLayers.Class({
+ initialize: function(params) {
+ var defaults = {
+ get_image: {
+ properties: {
+ background: null,
+ /*{
+ color: { r:255, g:255, b:255 },
+ transcolor: null
+ },*/
+ draw: true,
+ envelope: {
+ minx: 0,
+ miny: 0,
+ maxx: 0,
+ maxy: 0
+ },
+ featurecoordsys: {
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ filtercoordsys:{
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ imagesize:{
+ height:0,
+ width:0,
+ dpi:96,
+ printheight:0,
+ printwidth:0,
+ scalesymbols:false
+ },
+ layerlist:[],
+ /* no support for legends */
+ output:{
+ baseurl:"",
+ legendbaseurl:"",
+ legendname:"",
+ legendpath:"",
+ legendurl:"",
+ name:"",
+ path:"",
+ type:"jpg",
+ url:""
+ }
+ }
+ },
+
+ get_feature: {
+ layer: "",
+ query: {
+ isspatial: false,
+ featurecoordsys: {
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ filtercoordsys: {
+ id:0,
+ string:"",
+ datumtransformid:0,
+ datumtransformstring:""
+ },
+ buffer:0,
+ where:"",
+ spatialfilter: {
+ relation: "envelope_intersection",
+ envelope: null
+ }
+ }
+ },
+
+ environment: {
+ separators: {
+ cs:" ",
+ ts:";"
+ }
+ },
+
+ layer: [],
+ workspaces: []
+ };
+
+ return OpenLayers.Util.extend(this, defaults);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.ArcXML.Request"
+});
+
+OpenLayers.Format.ArcXML.Response = OpenLayers.Class({
+ initialize: function(params) {
+ var defaults = {
+ image: {
+ envelope:null,
+ output:''
+ },
+
+ features: {
+ featurecount: 0,
+ envelope: null,
+ feature: []
+ },
+
+ error:''
+ };
+
+ return OpenLayers.Util.extend(this, defaults);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.ArcXML.Response"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/ArcXML/Features.js b/misc/openlayers/lib/OpenLayers/Format/ArcXML/Features.js
new file mode 100644
index 0000000..5b8730d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/ArcXML/Features.js
@@ -0,0 +1,46 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/ArcXML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.ArcXML.Features
+ * Read/Write ArcXML features. Create a new instance with the
+ * <OpenLayers.Format.ArcXML.Features> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.ArcXML.Features = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Constructor: OpenLayers.Format.ArcXML.Features
+ * Create a new parser/writer for ArcXML Features. Create an instance of this class
+ * to get a set of features from an ArcXML response.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read data from a string of ArcXML, and return a set of OpenLayers features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} A collection of features.
+ */
+ read: function(data) {
+ var axl = new OpenLayers.Format.ArcXML();
+ var parsed = axl.read(data);
+
+ return parsed.features.feature;
+ }
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/Atom.js b/misc/openlayers/lib/OpenLayers/Format/Atom.js
new file mode 100644
index 0000000..8eb5792
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Atom.js
@@ -0,0 +1,712 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML/v3.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Atom
+ * Read/write Atom feeds. Create a new instance with the
+ * <OpenLayers.Format.AtomFeed> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Atom = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs. Properties
+ * of this object should not be set individually. Read-only. All
+ * XML subclasses should have their own namespaces object. Use
+ * <setNamespace> to add or set a namespace alias after construction.
+ */
+ namespaces: {
+ atom: "http://www.w3.org/2005/Atom",
+ georss: "http://www.georss.org/georss"
+ },
+
+ /**
+ * APIProperty: feedTitle
+ * {String} Atom feed elements require a title. Default is "untitled".
+ */
+ feedTitle: "untitled",
+
+ /**
+ * APIProperty: defaultEntryTitle
+ * {String} Atom entry elements require a title. In cases where one is
+ * not provided in the feature attributes, this will be used. Default
+ * is "untitled".
+ */
+ defaultEntryTitle: "untitled",
+
+ /**
+ * Property: gmlParse
+ * {Object} GML Format object for parsing features
+ * Non-API and only created if necessary
+ */
+ gmlParser: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+ * For GeoRSS the default is (y,x), therefore: false
+ */
+ xy: false,
+
+ /**
+ * Constructor: OpenLayers.Format.AtomEntry
+ * Create a new parser for Atom.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Return a list of features from an Atom feed or entry document.
+
+ * Parameters:
+ * doc - {Element} or {String}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+ return this.parseFeatures(doc);
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize or more feature nodes to Atom documents.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>} or Array({<OpenLayers.Feature.Vector>})
+ *
+ * Returns:
+ * {String} an Atom entry document if passed one feature node, or a feed
+ * document if passed an array of feature nodes.
+ */
+ write: function(features) {
+ var doc;
+ if (OpenLayers.Util.isArray(features)) {
+ doc = this.createElementNSPlus("atom:feed");
+ doc.appendChild(
+ this.createElementNSPlus("atom:title", {
+ value: this.feedTitle
+ })
+ );
+ for (var i=0, ii=features.length; i<ii; i++) {
+ doc.appendChild(this.buildEntryNode(features[i]));
+ }
+ }
+ else {
+ doc = this.buildEntryNode(features);
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [doc]);
+ },
+
+ /**
+ * Method: buildContentNode
+ *
+ * Parameters:
+ * content - {Object}
+ *
+ * Returns:
+ * {DOMElement} an Atom content node.
+ *
+ * TODO: types other than text.
+ */
+ buildContentNode: function(content) {
+ var node = this.createElementNSPlus("atom:content", {
+ attributes: {
+ type: content.type || null
+ }
+ });
+ if (content.src) {
+ node.setAttribute("src", content.src);
+ } else {
+ if (content.type == "text" || content.type == null) {
+ node.appendChild(
+ this.createTextNode(content.value)
+ );
+ } else if (content.type == "html") {
+ if (typeof content.value != "string") {
+ throw "HTML content must be in form of an escaped string";
+ }
+ node.appendChild(
+ this.createTextNode(content.value)
+ );
+ } else if (content.type == "xhtml") {
+ node.appendChild(content.value);
+ } else if (content.type == "xhtml" ||
+ content.type.match(/(\+|\/)xml$/)) {
+ node.appendChild(content.value);
+ }
+ else { // MUST be a valid Base64 encoding
+ node.appendChild(
+ this.createTextNode(content.value)
+ );
+ }
+ }
+ return node;
+ },
+
+ /**
+ * Method: buildEntryNode
+ * Build an Atom entry node from a feature object.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement} an Atom entry node.
+ *
+ * These entries are geared for publication using AtomPub.
+ *
+ * TODO: support extension elements
+ */
+ buildEntryNode: function(feature) {
+ var attrib = feature.attributes;
+ var atomAttrib = attrib.atom || {};
+ var entryNode = this.createElementNSPlus("atom:entry");
+
+ // atom:author
+ if (atomAttrib.authors) {
+ var authors = OpenLayers.Util.isArray(atomAttrib.authors) ?
+ atomAttrib.authors : [atomAttrib.authors];
+ for (var i=0, ii=authors.length; i<ii; i++) {
+ entryNode.appendChild(
+ this.buildPersonConstructNode(
+ "author", authors[i]
+ )
+ );
+ }
+ }
+
+ // atom:category
+ if (atomAttrib.categories) {
+ var categories = OpenLayers.Util.isArray(atomAttrib.categories) ?
+ atomAttrib.categories : [atomAttrib.categories];
+ var category;
+ for (var i=0, ii=categories.length; i<ii; i++) {
+ category = categories[i];
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:category", {
+ attributes: {
+ term: category.term,
+ scheme: category.scheme || null,
+ label: category.label || null
+ }
+ })
+ );
+ }
+ }
+
+ // atom:content
+ if (atomAttrib.content) {
+ entryNode.appendChild(this.buildContentNode(atomAttrib.content));
+ }
+
+ // atom:contributor
+ if (atomAttrib.contributors) {
+ var contributors = OpenLayers.Util.isArray(atomAttrib.contributors) ?
+ atomAttrib.contributors : [atomAttrib.contributors];
+ for (var i=0, ii=contributors.length; i<ii; i++) {
+ entryNode.appendChild(
+ this.buildPersonConstructNode(
+ "contributor",
+ contributors[i]
+ )
+ );
+ }
+ }
+
+ // atom:id
+ if (feature.fid) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:id", {
+ value: feature.fid
+ })
+ );
+ }
+
+ // atom:link
+ if (atomAttrib.links) {
+ var links = OpenLayers.Util.isArray(atomAttrib.links) ?
+ atomAttrib.links : [atomAttrib.links];
+ var link;
+ for (var i=0, ii=links.length; i<ii; i++) {
+ link = links[i];
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:link", {
+ attributes: {
+ href: link.href,
+ rel: link.rel || null,
+ type: link.type || null,
+ hreflang: link.hreflang || null,
+ title: link.title || null,
+ length: link.length || null
+ }
+ })
+ );
+ }
+ }
+
+ // atom:published
+ if (atomAttrib.published) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:published", {
+ value: atomAttrib.published
+ })
+ );
+ }
+
+ // atom:rights
+ if (atomAttrib.rights) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:rights", {
+ value: atomAttrib.rights
+ })
+ );
+ }
+
+ // atom:source not implemented
+
+ // atom:summary
+ if (atomAttrib.summary || attrib.description) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:summary", {
+ value: atomAttrib.summary || attrib.description
+ })
+ );
+ }
+
+ // atom:title
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:title", {
+ value: atomAttrib.title || attrib.title || this.defaultEntryTitle
+ })
+ );
+
+ // atom:updated
+ if (atomAttrib.updated) {
+ entryNode.appendChild(
+ this.createElementNSPlus("atom:updated", {
+ value: atomAttrib.updated
+ })
+ );
+ }
+
+ // georss:where
+ if (feature.geometry) {
+ var whereNode = this.createElementNSPlus("georss:where");
+ whereNode.appendChild(
+ this.buildGeometryNode(feature.geometry)
+ );
+ entryNode.appendChild(whereNode);
+ }
+
+ return entryNode;
+ },
+
+ /**
+ * Method: initGmlParser
+ * Creates a GML parser.
+ */
+ initGmlParser: function() {
+ this.gmlParser = new OpenLayers.Format.GML.v3({
+ xy: this.xy,
+ featureNS: "http://example.com#feature",
+ internalProjection: this.internalProjection,
+ externalProjection: this.externalProjection
+ });
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * builds a GeoRSS node with a given geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} A gml node.
+ */
+ buildGeometryNode: function(geometry) {
+ if (!this.gmlParser) {
+ this.initGmlParser();
+ }
+ var node = this.gmlParser.writeNode("feature:_geometry", geometry);
+ return node.firstChild;
+ },
+
+ /**
+ * Method: buildPersonConstructNode
+ *
+ * Parameters:
+ * name - {String}
+ * value - {Object}
+ *
+ * Returns:
+ * {DOMElement} an Atom person construct node.
+ *
+ * Example:
+ * >>> buildPersonConstructNode("author", {name: "John Smith"})
+ * {<author><name>John Smith</name></author>}
+ *
+ * TODO: how to specify extension elements? Add to the oNames array?
+ */
+ buildPersonConstructNode: function(name, value) {
+ var oNames = ["uri", "email"];
+ var personNode = this.createElementNSPlus("atom:" + name);
+ personNode.appendChild(
+ this.createElementNSPlus("atom:name", {
+ value: value.name
+ })
+ );
+ for (var i=0, ii=oNames.length; i<ii; i++) {
+ if (value[oNames[i]]) {
+ personNode.appendChild(
+ this.createElementNSPlus("atom:" + oNames[i], {
+ value: value[oNames[i]]
+ })
+ );
+ }
+ }
+ return personNode;
+ },
+
+ /**
+ * Method: getFirstChildValue
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * nsuri - {String} Child node namespace uri ("*" for any).
+ * name - {String} Child node name.
+ * def - {String} Optional string default to return if no child found.
+ *
+ * Returns:
+ * {String} The value of the first child with the given tag name. Returns
+ * default value or empty string if none found.
+ */
+ getFirstChildValue: function(node, nsuri, name, def) {
+ var value;
+ var nodes = this.getElementsByTagNameNS(node, nsuri, name);
+ if (nodes && nodes.length > 0) {
+ value = this.getChildValue(nodes[0], def);
+ } else {
+ value = def;
+ }
+ return value;
+ },
+
+ /**
+ * Method: parseFeature
+ * Parse feature from an Atom entry node..
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ parseFeature: function(node) {
+ var atomAttrib = {};
+ var value = null;
+ var nodes = null;
+ var attval = null;
+ var atomns = this.namespaces.atom;
+
+ // atomAuthor*
+ this.parsePersonConstructs(node, "author", atomAttrib);
+
+ // atomCategory*
+ nodes = this.getElementsByTagNameNS(node, atomns, "category");
+ if (nodes.length > 0) {
+ atomAttrib.categories = [];
+ }
+ for (var i=0, ii=nodes.length; i<ii; i++) {
+ value = {};
+ value.term = nodes[i].getAttribute("term");
+ attval = nodes[i].getAttribute("scheme");
+ if (attval) { value.scheme = attval; }
+ attval = nodes[i].getAttribute("label");
+ if (attval) { value.label = attval; }
+ atomAttrib.categories.push(value);
+ }
+
+ // atomContent?
+ nodes = this.getElementsByTagNameNS(node, atomns, "content");
+ if (nodes.length > 0) {
+ value = {};
+ attval = nodes[0].getAttribute("type");
+ if (attval) {
+ value.type = attval;
+ }
+ attval = nodes[0].getAttribute("src");
+ if (attval) {
+ value.src = attval;
+ } else {
+ if (value.type == "text" ||
+ value.type == "html" ||
+ value.type == null ) {
+ value.value = this.getFirstChildValue(
+ node,
+ atomns,
+ "content",
+ null
+ );
+ } else if (value.type == "xhtml" ||
+ value.type.match(/(\+|\/)xml$/)) {
+ value.value = this.getChildEl(nodes[0]);
+ } else { // MUST be base64 encoded
+ value.value = this.getFirstChildValue(
+ node,
+ atomns,
+ "content",
+ null
+ );
+ }
+ atomAttrib.content = value;
+ }
+ }
+
+ // atomContributor*
+ this.parsePersonConstructs(node, "contributor", atomAttrib);
+
+ // atomId
+ atomAttrib.id = this.getFirstChildValue(node, atomns, "id", null);
+
+ // atomLink*
+ nodes = this.getElementsByTagNameNS(node, atomns, "link");
+ if (nodes.length > 0) {
+ atomAttrib.links = new Array(nodes.length);
+ }
+ var oAtts = ["rel", "type", "hreflang", "title", "length"];
+ for (var i=0, ii=nodes.length; i<ii; i++) {
+ value = {};
+ value.href = nodes[i].getAttribute("href");
+ for (var j=0, jj=oAtts.length; j<jj; j++) {
+ attval = nodes[i].getAttribute(oAtts[j]);
+ if (attval) {
+ value[oAtts[j]] = attval;
+ }
+ }
+ atomAttrib.links[i] = value;
+ }
+
+ // atomPublished?
+ value = this.getFirstChildValue(node, atomns, "published", null);
+ if (value) {
+ atomAttrib.published = value;
+ }
+
+ // atomRights?
+ value = this.getFirstChildValue(node, atomns, "rights", null);
+ if (value) {
+ atomAttrib.rights = value;
+ }
+
+ // atomSource? -- not implemented
+
+ // atomSummary?
+ value = this.getFirstChildValue(node, atomns, "summary", null);
+ if (value) {
+ atomAttrib.summary = value;
+ }
+
+ // atomTitle
+ atomAttrib.title = this.getFirstChildValue(
+ node, atomns, "title", null
+ );
+
+ // atomUpdated
+ atomAttrib.updated = this.getFirstChildValue(
+ node, atomns, "updated", null
+ );
+
+ var featureAttrib = {
+ title: atomAttrib.title,
+ description: atomAttrib.summary,
+ atom: atomAttrib
+ };
+ var geometry = this.parseLocations(node)[0];
+ var feature = new OpenLayers.Feature.Vector(geometry, featureAttrib);
+ feature.fid = atomAttrib.id;
+ return feature;
+ },
+
+ /**
+ * Method: parseFeatures
+ * Return features from an Atom entry or feed.
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ parseFeatures: function(node) {
+ var features = [];
+ var entries = this.getElementsByTagNameNS(
+ node, this.namespaces.atom, "entry"
+ );
+ if (entries.length == 0) {
+ entries = [node];
+ }
+ for (var i=0, ii=entries.length; i<ii; i++) {
+ features.push(this.parseFeature(entries[i]));
+ }
+ return features;
+ },
+
+ /**
+ * Method: parseLocations
+ * Parse the locations from an Atom entry or feed.
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ *
+ * Returns:
+ * Array({<OpenLayers.Geometry>})
+ */
+ parseLocations: function(node) {
+ var georssns = this.namespaces.georss;
+
+ var locations = {components: []};
+ var where = this.getElementsByTagNameNS(node, georssns, "where");
+ if (where && where.length > 0) {
+ if (!this.gmlParser) {
+ this.initGmlParser();
+ }
+ for (var i=0, ii=where.length; i<ii; i++) {
+ this.gmlParser.readChildNodes(where[i], locations);
+ }
+ }
+
+ var components = locations.components;
+ var point = this.getElementsByTagNameNS(node, georssns, "point");
+ if (point && point.length > 0) {
+ for (var i=0, ii=point.length; i<ii; i++) {
+ var xy = OpenLayers.String.trim(
+ point[i].firstChild.nodeValue
+ ).split(/\s+/);
+ if (xy.length !=2) {
+ xy = OpenLayers.String.trim(
+ point[i].firstChild.nodeValue
+ ).split(/\s*,\s*/);
+ }
+ components.push(new OpenLayers.Geometry.Point(xy[1], xy[0]));
+ }
+ }
+
+ var line = this.getElementsByTagNameNS(node, georssns, "line");
+ if (line && line.length > 0) {
+ var coords;
+ var p;
+ var points;
+ for (var i=0, ii=line.length; i<ii; i++) {
+ coords = OpenLayers.String.trim(
+ line[i].firstChild.nodeValue
+ ).split(/\s+/);
+ points = [];
+ for (var j=0, jj=coords.length; j<jj; j+=2) {
+ p = new OpenLayers.Geometry.Point(coords[j+1], coords[j]);
+ points.push(p);
+ }
+ components.push(
+ new OpenLayers.Geometry.LineString(points)
+ );
+ }
+ }
+
+ var polygon = this.getElementsByTagNameNS(node, georssns, "polygon");
+ if (polygon && polygon.length > 0) {
+ var coords;
+ var p;
+ var points;
+ for (var i=0, ii=polygon.length; i<ii; i++) {
+ coords = OpenLayers.String.trim(
+ polygon[i].firstChild.nodeValue
+ ).split(/\s+/);
+ points = [];
+ for (var j=0, jj=coords.length; j<jj; j+=2) {
+ p = new OpenLayers.Geometry.Point(coords[j+1], coords[j]);
+ points.push(p);
+ }
+ components.push(
+ new OpenLayers.Geometry.Polygon(
+ [new OpenLayers.Geometry.LinearRing(points)]
+ )
+ );
+ }
+ }
+
+ if (this.internalProjection && this.externalProjection) {
+ for (var i=0, ii=components.length; i<ii; i++) {
+ if (components[i]) {
+ components[i].transform(
+ this.externalProjection,
+ this.internalProjection
+ );
+ }
+ }
+ }
+
+ return components;
+ },
+
+ /**
+ * Method: parsePersonConstruct
+ * Parse Atom person constructs from an Atom entry node.
+ *
+ * Parameters:
+ * node - {DOMElement} An Atom entry or feed node.
+ * name - {String} Construcy name ("author" or "contributor")
+ * data = {Object} Object in which to put parsed persons.
+ *
+ * Returns:
+ * An {Object}.
+ */
+ parsePersonConstructs: function(node, name, data) {
+ var persons = [];
+ var atomns = this.namespaces.atom;
+ var nodes = this.getElementsByTagNameNS(node, atomns, name);
+ var oAtts = ["uri", "email"];
+ for (var i=0, ii=nodes.length; i<ii; i++) {
+ var value = {};
+ value.name = this.getFirstChildValue(
+ nodes[i],
+ atomns,
+ "name",
+ null
+ );
+ for (var j=0, jj=oAtts.length; j<jj; j++) {
+ var attval = this.getFirstChildValue(
+ nodes[i],
+ atomns,
+ oAtts[j],
+ null);
+ if (attval) {
+ value[oAtts[j]] = attval;
+ }
+ }
+ persons.push(value);
+ }
+ if (persons.length > 0) {
+ data[name + "s"] = persons;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Atom"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/CQL.js b/misc/openlayers/lib/OpenLayers/Format/CQL.js
new file mode 100644
index 0000000..8430a8b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/CQL.js
@@ -0,0 +1,452 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CQL
+ * Read CQL strings to get <OpenLayers.Filter> objects. Write
+ * <OpenLayers.Filter> objects to get CQL strings. Create a new parser with
+ * the <OpenLayers.Format.CQL> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.CQL = (function() {
+
+ var tokens = [
+ "PROPERTY", "COMPARISON", "VALUE", "LOGICAL"
+ ],
+
+ patterns = {
+ PROPERTY: /^[_a-zA-Z]\w*/,
+ COMPARISON: /^(=|<>|<=|<|>=|>|LIKE)/i,
+ IS_NULL: /^IS NULL/i,
+ COMMA: /^,/,
+ LOGICAL: /^(AND|OR)/i,
+ VALUE: /^('([^']|'')*'|\d+(\.\d*)?|\.\d+)/,
+ LPAREN: /^\(/,
+ RPAREN: /^\)/,
+ SPATIAL: /^(BBOX|INTERSECTS|DWITHIN|WITHIN|CONTAINS)/i,
+ NOT: /^NOT/i,
+ BETWEEN: /^BETWEEN/i,
+ GEOMETRY: function(text) {
+ var type = /^(POINT|LINESTRING|POLYGON|MULTIPOINT|MULTILINESTRING|MULTIPOLYGON|GEOMETRYCOLLECTION)/.exec(text);
+ if (type) {
+ var len = text.length;
+ var idx = text.indexOf("(", type[0].length);
+ if (idx > -1) {
+ var depth = 1;
+ while (idx < len && depth > 0) {
+ idx++;
+ switch(text.charAt(idx)) {
+ case '(':
+ depth++;
+ break;
+ case ')':
+ depth--;
+ break;
+ default:
+ // in default case, do nothing
+ }
+ }
+ }
+ return [text.substr(0, idx+1)];
+ }
+ },
+ END: /^$/
+ },
+
+ follows = {
+ LPAREN: ['GEOMETRY', 'SPATIAL', 'PROPERTY', 'VALUE', 'LPAREN'],
+ RPAREN: ['NOT', 'LOGICAL', 'END', 'RPAREN'],
+ PROPERTY: ['COMPARISON', 'BETWEEN', 'COMMA', 'IS_NULL'],
+ BETWEEN: ['VALUE'],
+ IS_NULL: ['END'],
+ COMPARISON: ['VALUE'],
+ COMMA: ['GEOMETRY', 'VALUE', 'PROPERTY'],
+ VALUE: ['LOGICAL', 'COMMA', 'RPAREN', 'END'],
+ SPATIAL: ['LPAREN'],
+ LOGICAL: ['NOT', 'VALUE', 'SPATIAL', 'PROPERTY', 'LPAREN'],
+ NOT: ['PROPERTY', 'LPAREN'],
+ GEOMETRY: ['COMMA', 'RPAREN']
+ },
+
+ operators = {
+ '=': OpenLayers.Filter.Comparison.EQUAL_TO,
+ '<>': OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ '<': OpenLayers.Filter.Comparison.LESS_THAN,
+ '<=': OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
+ '>': OpenLayers.Filter.Comparison.GREATER_THAN,
+ '>=': OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+ 'LIKE': OpenLayers.Filter.Comparison.LIKE,
+ 'BETWEEN': OpenLayers.Filter.Comparison.BETWEEN,
+ 'IS NULL': OpenLayers.Filter.Comparison.IS_NULL
+ },
+
+ operatorReverse = {},
+
+ logicals = {
+ 'AND': OpenLayers.Filter.Logical.AND,
+ 'OR': OpenLayers.Filter.Logical.OR
+ },
+
+ logicalReverse = {},
+
+ precedence = {
+ 'RPAREN': 3,
+ 'LOGICAL': 2,
+ 'COMPARISON': 1
+ };
+
+ var i;
+ for (i in operators) {
+ if (operators.hasOwnProperty(i)) {
+ operatorReverse[operators[i]] = i;
+ }
+ }
+
+ for (i in logicals) {
+ if (logicals.hasOwnProperty(i)) {
+ logicalReverse[logicals[i]] = i;
+ }
+ }
+
+ function tryToken(text, pattern) {
+ if (pattern instanceof RegExp) {
+ return pattern.exec(text);
+ } else {
+ return pattern(text);
+ }
+ }
+
+ function nextToken(text, tokens) {
+ var i, token, len = tokens.length;
+ for (i=0; i<len; i++) {
+ token = tokens[i];
+ var pat = patterns[token];
+ var matches = tryToken(text, pat);
+ if (matches) {
+ var match = matches[0];
+ var remainder = text.substr(match.length).replace(/^\s*/, "");
+ return {
+ type: token,
+ text: match,
+ remainder: remainder
+ };
+ }
+ }
+
+ var msg = "ERROR: In parsing: [" + text + "], expected one of: ";
+ for (i=0; i<len; i++) {
+ token = tokens[i];
+ msg += "\n " + token + ": " + patterns[token];
+ }
+
+ throw new Error(msg);
+ }
+
+ function tokenize(text) {
+ var results = [];
+ var token, expect = ["NOT", "GEOMETRY", "SPATIAL", "PROPERTY", "LPAREN"];
+
+ do {
+ token = nextToken(text, expect);
+ text = token.remainder;
+ expect = follows[token.type];
+ if (token.type != "END" && !expect) {
+ throw new Error("No follows list for " + token.type);
+ }
+ results.push(token);
+ } while (token.type != "END");
+
+ return results;
+ }
+
+ function buildAst(tokens) {
+ var operatorStack = [],
+ postfix = [];
+
+ while (tokens.length) {
+ var tok = tokens.shift();
+ switch (tok.type) {
+ case "PROPERTY":
+ case "GEOMETRY":
+ case "VALUE":
+ postfix.push(tok);
+ break;
+ case "COMPARISON":
+ case "BETWEEN":
+ case "IS_NULL":
+ case "LOGICAL":
+ var p = precedence[tok.type];
+
+ while (operatorStack.length > 0 &&
+ (precedence[operatorStack[operatorStack.length - 1].type] <= p)
+ ) {
+ postfix.push(operatorStack.pop());
+ }
+
+ operatorStack.push(tok);
+ break;
+ case "SPATIAL":
+ case "NOT":
+ case "LPAREN":
+ operatorStack.push(tok);
+ break;
+ case "RPAREN":
+ while (operatorStack.length > 0 &&
+ (operatorStack[operatorStack.length - 1].type != "LPAREN")
+ ) {
+ postfix.push(operatorStack.pop());
+ }
+ operatorStack.pop(); // toss out the LPAREN
+
+ if (operatorStack.length > 0 &&
+ operatorStack[operatorStack.length-1].type == "SPATIAL") {
+ postfix.push(operatorStack.pop());
+ }
+ case "COMMA":
+ case "END":
+ break;
+ default:
+ throw new Error("Unknown token type " + tok.type);
+ }
+ }
+
+ while (operatorStack.length > 0) {
+ postfix.push(operatorStack.pop());
+ }
+
+ function buildTree() {
+ var tok = postfix.pop();
+ switch (tok.type) {
+ case "LOGICAL":
+ var rhs = buildTree(),
+ lhs = buildTree();
+ return new OpenLayers.Filter.Logical({
+ filters: [lhs, rhs],
+ type: logicals[tok.text.toUpperCase()]
+ });
+ case "NOT":
+ var operand = buildTree();
+ return new OpenLayers.Filter.Logical({
+ filters: [operand],
+ type: OpenLayers.Filter.Logical.NOT
+ });
+ case "BETWEEN":
+ var min, max, property;
+ postfix.pop(); // unneeded AND token here
+ max = buildTree();
+ min = buildTree();
+ property = buildTree();
+ return new OpenLayers.Filter.Comparison({
+ property: property,
+ lowerBoundary: min,
+ upperBoundary: max,
+ type: OpenLayers.Filter.Comparison.BETWEEN
+ });
+ case "COMPARISON":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Comparison({
+ property: property,
+ value: value,
+ type: operators[tok.text.toUpperCase()]
+ });
+ case "IS_NULL":
+ var property = buildTree();
+ return new OpenLayers.Filter.Comparison({
+ property: property,
+ type: operators[tok.text.toUpperCase()]
+ });
+ case "VALUE":
+ var match = tok.text.match(/^'(.*)'$/);
+ if (match) {
+ return match[1].replace(/''/g, "'");
+ } else {
+ return Number(tok.text);
+ }
+ case "SPATIAL":
+ switch(tok.text.toUpperCase()) {
+ case "BBOX":
+ var maxy = buildTree(),
+ maxx = buildTree(),
+ miny = buildTree(),
+ minx = buildTree(),
+ prop = buildTree();
+
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ property: prop,
+ value: OpenLayers.Bounds.fromArray(
+ [minx, miny, maxx, maxy]
+ )
+ });
+ case "INTERSECTS":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: property,
+ value: value
+ });
+ case "WITHIN":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.WITHIN,
+ property: property,
+ value: value
+ });
+ case "CONTAINS":
+ var value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.CONTAINS,
+ property: property,
+ value: value
+ });
+ case "DWITHIN":
+ var distance = buildTree(),
+ value = buildTree(),
+ property = buildTree();
+ return new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ value: value,
+ property: property,
+ distance: Number(distance)
+ });
+ }
+ case "GEOMETRY":
+ return OpenLayers.Geometry.fromWKT(tok.text);
+ default:
+ return tok.text;
+ }
+ }
+
+ var result = buildTree();
+ if (postfix.length > 0) {
+ var msg = "Remaining tokens after building AST: \n";
+ for (var i = postfix.length - 1; i >= 0; i--) {
+ msg += postfix[i].type + ": " + postfix[i].text + "\n";
+ }
+ throw new Error(msg);
+ }
+
+ return result;
+ }
+
+ return OpenLayers.Class(OpenLayers.Format, {
+ /**
+ * APIMethod: read
+ * Generate a filter from a CQL string.
+
+ * Parameters:
+ * text - {String} The CQL text.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter based on the CQL text.
+ */
+ read: function(text) {
+ var result = buildAst(tokenize(text));
+ if (this.keepData) {
+ this.data = result;
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: write
+ * Convert a filter into a CQL string.
+
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} The filter.
+ *
+ * Returns:
+ * {String} A CQL string based on the filter.
+ */
+ write: function(filter) {
+ if (filter instanceof OpenLayers.Geometry) {
+ return filter.toString();
+ }
+ switch (filter.CLASS_NAME) {
+ case "OpenLayers.Filter.Spatial":
+ switch(filter.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ return "BBOX(" +
+ filter.property + "," +
+ filter.value.toBBOX() +
+ ")";
+ case OpenLayers.Filter.Spatial.DWITHIN:
+ return "DWITHIN(" +
+ filter.property + ", " +
+ this.write(filter.value) + ", " +
+ filter.distance + ")";
+ case OpenLayers.Filter.Spatial.WITHIN:
+ return "WITHIN(" +
+ filter.property + ", " +
+ this.write(filter.value) + ")";
+ case OpenLayers.Filter.Spatial.INTERSECTS:
+ return "INTERSECTS(" +
+ filter.property + ", " +
+ this.write(filter.value) + ")";
+ case OpenLayers.Filter.Spatial.CONTAINS:
+ return "CONTAINS(" +
+ filter.property + ", " +
+ this.write(filter.value) + ")";
+ default:
+ throw new Error("Unknown spatial filter type: " + filter.type);
+ }
+ case "OpenLayers.Filter.Logical":
+ if (filter.type == OpenLayers.Filter.Logical.NOT) {
+ // TODO: deal with precedence of logical operators to
+ // avoid extra parentheses (not urgent)
+ return "NOT (" + this.write(filter.filters[0]) + ")";
+ } else {
+ var res = "(";
+ var first = true;
+ for (var i = 0; i < filter.filters.length; i++) {
+ if (first) {
+ first = false;
+ } else {
+ res += ") " + logicalReverse[filter.type] + " (";
+ }
+ res += this.write(filter.filters[i]);
+ }
+ return res + ")";
+ }
+ case "OpenLayers.Filter.Comparison":
+ if (filter.type == OpenLayers.Filter.Comparison.BETWEEN) {
+ return filter.property + " BETWEEN " +
+ this.write(filter.lowerBoundary) + " AND " +
+ this.write(filter.upperBoundary);
+ } else {
+ return (filter.value !== null) ? filter.property +
+ " " + operatorReverse[filter.type] + " " +
+ this.write(filter.value) : filter.property +
+ " " + operatorReverse[filter.type];
+ }
+ case undefined:
+ if (typeof filter === "string") {
+ return "'" + filter.replace(/'/g, "''") + "'";
+ } else if (typeof filter === "number") {
+ return String(filter);
+ }
+ default:
+ throw new Error("Can't encode: " + filter.CLASS_NAME + " " + filter);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.CQL"
+
+ });
+})();
+
diff --git a/misc/openlayers/lib/OpenLayers/Format/CSWGetDomain.js b/misc/openlayers/lib/OpenLayers/Format/CSWGetDomain.js
new file mode 100644
index 0000000..18d5328
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/CSWGetDomain.js
@@ -0,0 +1,34 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetDomain
+ * Default version is 2.0.2.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A CSWGetDomain format of the given version.
+ */
+OpenLayers.Format.CSWGetDomain = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.CSWGetDomain.DEFAULTS
+ );
+ var cls = OpenLayers.Format.CSWGetDomain["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported CSWGetDomain version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: DEFAULTS
+ * {Object} Default properties for the CSWGetDomain format.
+ */
+OpenLayers.Format.CSWGetDomain.DEFAULTS = {
+ "version": "2.0.2"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Format/CSWGetDomain/v2_0_2.js b/misc/openlayers/lib/OpenLayers/Format/CSWGetDomain/v2_0_2.js
new file mode 100644
index 0000000..78200ea
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/CSWGetDomain/v2_0_2.js
@@ -0,0 +1,240 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/CSWGetDomain.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetDomain.v2_0_2
+ * A format for creating CSWGetDomain v2.0.2 transactions.
+ * Create a new instance with the
+ * <OpenLayers.Format.CSWGetDomain.v2_0_2> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.CSWGetDomain.v2_0_2 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ csw: "http://www.opengis.net/cat/csw/2.0.2"
+ },
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default prefix (used by Format.XML).
+ */
+ defaultPrefix: "csw",
+
+ /**
+ * Property: version
+ * {String} CSW version number.
+ */
+ version: "2.0.2",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/cat/csw/2.0.2
+ * http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd
+ */
+ schemaLocation: "http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",
+
+ /**
+ * APIProperty: PropertyName
+ * {String} Value of the csw:PropertyName element, used when
+ * writing a GetDomain document.
+ */
+ PropertyName: null,
+
+ /**
+ * APIProperty: ParameterName
+ * {String} Value of the csw:ParameterName element, used when
+ * writing a GetDomain document.
+ */
+ ParameterName: null,
+
+ /**
+ * Constructor: OpenLayers.Format.CSWGetDomain.v2_0_2
+ * A class for parsing and generating CSWGetDomain v2.0.2 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * - PropertyName
+ * - ParameterName
+ */
+
+ /**
+ * APIMethod: read
+ * Parse the response from a GetDomain request.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ this.readNode(data, obj);
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "csw": {
+ "GetDomainResponse": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "DomainValues": function(node, obj) {
+ if (!(OpenLayers.Util.isArray(obj.DomainValues))) {
+ obj.DomainValues = [];
+ }
+ var attrs = node.attributes;
+ var domainValue = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ domainValue[attrs[i].name] = attrs[i].nodeValue;
+ }
+ this.readChildNodes(node, domainValue);
+ obj.DomainValues.push(domainValue);
+ },
+ "PropertyName": function(node, obj) {
+ obj.PropertyName = this.getChildValue(node);
+ },
+ "ParameterName": function(node, obj) {
+ obj.ParameterName = this.getChildValue(node);
+ },
+ "ListOfValues": function(node, obj) {
+ if (!(OpenLayers.Util.isArray(obj.ListOfValues))) {
+ obj.ListOfValues = [];
+ }
+ this.readChildNodes(node, obj.ListOfValues);
+ },
+ "Value": function(node, obj) {
+ var attrs = node.attributes;
+ var value = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ value[attrs[i].name] = attrs[i].nodeValue;
+ }
+ value.value = this.getChildValue(node);
+ obj.push({Value: value});
+ },
+ "ConceptualScheme": function(node, obj) {
+ obj.ConceptualScheme = {};
+ this.readChildNodes(node, obj.ConceptualScheme);
+ },
+ "Name": function(node, obj) {
+ obj.Name = this.getChildValue(node);
+ },
+ "Document": function(node, obj) {
+ obj.Document = this.getChildValue(node);
+ },
+ "Authority": function(node, obj) {
+ obj.Authority = this.getChildValue(node);
+ },
+ "RangeOfValues": function(node, obj) {
+ obj.RangeOfValues = {};
+ this.readChildNodes(node, obj.RangeOfValues);
+ },
+ "MinValue": function(node, obj) {
+ var attrs = node.attributes;
+ var value = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ value[attrs[i].name] = attrs[i].nodeValue;
+ }
+ value.value = this.getChildValue(node);
+ obj.MinValue = value;
+ },
+ "MaxValue": function(node, obj) {
+ var attrs = node.attributes;
+ var value = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ value[attrs[i].name] = attrs[i].nodeValue;
+ }
+ value.value = this.getChildValue(node);
+ obj.MaxValue = value;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: write
+ * Given an configuration js object, write a CSWGetDomain request.
+ *
+ * Parameters:
+ * options - {Object} A object mapping the request.
+ *
+ * Returns:
+ * {String} A serialized CSWGetDomain request.
+ */
+ write: function(options) {
+ var node = this.writeNode("csw:GetDomain", options);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "csw": {
+ "GetDomain": function(options) {
+ var node = this.createElementNSPlus("csw:GetDomain", {
+ attributes: {
+ service: "CSW",
+ version: this.version
+ }
+ });
+ if (options.PropertyName || this.PropertyName) {
+ this.writeNode(
+ "csw:PropertyName",
+ options.PropertyName || this.PropertyName,
+ node
+ );
+ } else if (options.ParameterName || this.ParameterName) {
+ this.writeNode(
+ "csw:ParameterName",
+ options.ParameterName || this.ParameterName,
+ node
+ );
+ }
+ this.readChildNodes(node, options);
+ return node;
+ },
+ "PropertyName": function(value) {
+ var node = this.createElementNSPlus("csw:PropertyName", {
+ value: value
+ });
+ return node;
+ },
+ "ParameterName": function(value) {
+ var node = this.createElementNSPlus("csw:ParameterName", {
+ value: value
+ });
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.CSWGetDomain.v2_0_2"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/CSWGetRecords.js b/misc/openlayers/lib/OpenLayers/Format/CSWGetRecords.js
new file mode 100644
index 0000000..923c548
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/CSWGetRecords.js
@@ -0,0 +1,34 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetRecords
+ * Default version is 2.0.2.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A CSWGetRecords format of the given version.
+ */
+OpenLayers.Format.CSWGetRecords = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.CSWGetRecords.DEFAULTS
+ );
+ var cls = OpenLayers.Format.CSWGetRecords["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported CSWGetRecords version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: DEFAULTS
+ * {Object} Default properties for the CSWGetRecords format.
+ */
+OpenLayers.Format.CSWGetRecords.DEFAULTS = {
+ "version": "2.0.2"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Format/CSWGetRecords/v2_0_2.js b/misc/openlayers/lib/OpenLayers/Format/CSWGetRecords/v2_0_2.js
new file mode 100644
index 0000000..3c87612
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/CSWGetRecords/v2_0_2.js
@@ -0,0 +1,457 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/CSWGetRecords.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ * @requires OpenLayers/Format/Filter/v1_1_0.js
+ * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.CSWGetRecords.v2_0_2
+ * A format for creating CSWGetRecords v2.0.2 transactions.
+ * Create a new instance with the
+ * <OpenLayers.Format.CSWGetRecords.v2_0_2> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.CSWGetRecords.v2_0_2 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ csw: "http://www.opengis.net/cat/csw/2.0.2",
+ dc: "http://purl.org/dc/elements/1.1/",
+ dct: "http://purl.org/dc/terms/",
+ gmd: "http://www.isotc211.org/2005/gmd",
+ geonet: "http://www.fao.org/geonetwork",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default prefix (used by Format.XML).
+ */
+ defaultPrefix: "csw",
+
+ /**
+ * Property: version
+ * {String} CSW version number.
+ */
+ version: "2.0.2",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/cat/csw/2.0.2
+ * http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd
+ */
+ schemaLocation: "http://www.opengis.net/cat/csw/2.0.2 http://schemas.opengis.net/csw/2.0.2/CSW-discovery.xsd",
+
+ /**
+ * APIProperty: requestId
+ * {String} Value of the requestId attribute of the GetRecords element.
+ */
+ requestId: null,
+
+ /**
+ * APIProperty: resultType
+ * {String} Value of the resultType attribute of the GetRecords element,
+ * specifies the result type in the GetRecords response, "hits" is
+ * the default.
+ */
+ resultType: null,
+
+ /**
+ * APIProperty: outputFormat
+ * {String} Value of the outputFormat attribute of the GetRecords element,
+ * specifies the format of the GetRecords response,
+ * "application/xml" is the default.
+ */
+ outputFormat: null,
+
+ /**
+ * APIProperty: outputSchema
+ * {String} Value of the outputSchema attribute of the GetRecords element,
+ * specifies the schema of the GetRecords response.
+ */
+ outputSchema: null,
+
+ /**
+ * APIProperty: startPosition
+ * {String} Value of the startPosition attribute of the GetRecords element,
+ * specifies the start position (offset+1) for the GetRecords response,
+ * 1 is the default.
+ */
+ startPosition: null,
+
+ /**
+ * APIProperty: maxRecords
+ * {String} Value of the maxRecords attribute of the GetRecords element,
+ * specifies the maximum number of records in the GetRecords response,
+ * 10 is the default.
+ */
+ maxRecords: null,
+
+ /**
+ * APIProperty: DistributedSearch
+ * {String} Value of the csw:DistributedSearch element, used when writing
+ * a csw:GetRecords document.
+ */
+ DistributedSearch: null,
+
+ /**
+ * APIProperty: ResponseHandler
+ * {Array({String})} Values of the csw:ResponseHandler elements, used when
+ * writting a csw:GetRecords document.
+ */
+ ResponseHandler: null,
+
+ /**
+ * APIProperty: Query
+ * {String} Value of the csw:Query element, used when writing a csw:GetRecords
+ * document.
+ */
+ Query: null,
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.CSWGetRecords.v2_0_2
+ * A class for parsing and generating CSWGetRecords v2.0.2 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties (documented as class properties):
+ * - requestId
+ * - resultType
+ * - outputFormat
+ * - outputSchema
+ * - startPosition
+ * - maxRecords
+ * - DistributedSearch
+ * - ResponseHandler
+ * - Query
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Parse the response from a GetRecords request.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ this.readNode(data, obj);
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "csw": {
+ "GetRecordsResponse": function(node, obj) {
+ obj.records = [];
+ this.readChildNodes(node, obj);
+ var version = this.getAttributeNS(node, "", 'version');
+ if (version != "") {
+ obj.version = version;
+ }
+ },
+ "RequestId": function(node, obj) {
+ obj.RequestId = this.getChildValue(node);
+ },
+ "SearchStatus": function(node, obj) {
+ obj.SearchStatus = {};
+ var timestamp = this.getAttributeNS(node, "", 'timestamp');
+ if (timestamp != "") {
+ obj.SearchStatus.timestamp = timestamp;
+ }
+ },
+ "SearchResults": function(node, obj) {
+ this.readChildNodes(node, obj);
+ var attrs = node.attributes;
+ var SearchResults = {};
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ if ((attrs[i].name == "numberOfRecordsMatched") ||
+ (attrs[i].name == "numberOfRecordsReturned") ||
+ (attrs[i].name == "nextRecord")) {
+ SearchResults[attrs[i].name] = parseInt(attrs[i].nodeValue);
+ } else {
+ SearchResults[attrs[i].name] = attrs[i].nodeValue;
+ }
+ }
+ obj.SearchResults = SearchResults;
+ },
+ "SummaryRecord": function(node, obj) {
+ var record = {type: "SummaryRecord"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "BriefRecord": function(node, obj) {
+ var record = {type: "BriefRecord"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "DCMIRecord": function(node, obj) {
+ var record = {type: "DCMIRecord"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "Record": function(node, obj) {
+ var record = {type: "Record"};
+ this.readChildNodes(node, record);
+ obj.records.push(record);
+ },
+ "*": function(node, obj) {
+ var name = node.localName || node.nodeName.split(":").pop();
+ obj[name] = this.getChildValue(node);
+ }
+ },
+ "geonet": {
+ "info": function(node, obj) {
+ var gninfo = {};
+ this.readChildNodes(node, gninfo);
+ obj.gninfo = gninfo;
+ }
+ },
+ "dc": {
+ // audience, contributor, coverage, creator, date, description, format,
+ // identifier, language, provenance, publisher, relation, rights,
+ // rightsHolder, source, subject, title, type, URI
+ "*": function(node, obj) {
+ var name = node.localName || node.nodeName.split(":").pop();
+ if (!(OpenLayers.Util.isArray(obj[name]))) {
+ obj[name] = [];
+ }
+ var dc_element = {};
+ var attrs = node.attributes;
+ for(var i=0, len=attrs.length; i<len; ++i) {
+ dc_element[attrs[i].name] = attrs[i].nodeValue;
+ }
+ dc_element.value = this.getChildValue(node);
+ if (dc_element.value != "") {
+ obj[name].push(dc_element);
+ }
+ }
+ },
+ "dct": {
+ // abstract, modified, spatial
+ "*": function(node, obj) {
+ var name = node.localName || node.nodeName.split(":").pop();
+ if (!(OpenLayers.Util.isArray(obj[name]))) {
+ obj[name] = [];
+ }
+ obj[name].push(this.getChildValue(node));
+ }
+ },
+ "ows": OpenLayers.Util.applyDefaults({
+ "BoundingBox": function(node, obj) {
+ if (obj.bounds) {
+ obj.BoundingBox = [{crs: obj.projection, value:
+ [
+ obj.bounds.left,
+ obj.bounds.bottom,
+ obj.bounds.right,
+ obj.bounds.top
+ ]
+ }];
+ delete obj.projection;
+ delete obj.bounds;
+ }
+ OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"]["BoundingBox"].apply(
+ this, arguments);
+ }
+ }, OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"])
+ },
+
+ /**
+ * Method: write
+ * Given an configuration js object, write a CSWGetRecords request.
+ *
+ * Parameters:
+ * options - {Object} A object mapping the request.
+ *
+ * Returns:
+ * {String} A serialized CSWGetRecords request.
+ */
+ write: function(options) {
+ var node = this.writeNode("csw:GetRecords", options);
+ node.setAttribute("xmlns:gmd", this.namespaces.gmd);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "csw": {
+ "GetRecords": function(options) {
+ if (!options) {
+ options = {};
+ }
+ var node = this.createElementNSPlus("csw:GetRecords", {
+ attributes: {
+ service: "CSW",
+ version: this.version,
+ requestId: options.requestId || this.requestId,
+ resultType: options.resultType || this.resultType,
+ outputFormat: options.outputFormat || this.outputFormat,
+ outputSchema: options.outputSchema || this.outputSchema,
+ startPosition: options.startPosition || this.startPosition,
+ maxRecords: options.maxRecords || this.maxRecords
+ }
+ });
+ if (options.DistributedSearch || this.DistributedSearch) {
+ this.writeNode(
+ "csw:DistributedSearch",
+ options.DistributedSearch || this.DistributedSearch,
+ node
+ );
+ }
+ var ResponseHandler = options.ResponseHandler || this.ResponseHandler;
+ if (OpenLayers.Util.isArray(ResponseHandler) && ResponseHandler.length > 0) {
+ // ResponseHandler must be a non-empty array
+ for(var i=0, len=ResponseHandler.length; i<len; i++) {
+ this.writeNode(
+ "csw:ResponseHandler",
+ ResponseHandler[i],
+ node
+ );
+ }
+ }
+ this.writeNode("Query", options.Query || this.Query, node);
+ return node;
+ },
+ "DistributedSearch": function(options) {
+ var node = this.createElementNSPlus("csw:DistributedSearch", {
+ attributes: {
+ hopCount: options.hopCount
+ }
+ });
+ return node;
+ },
+ "ResponseHandler": function(options) {
+ var node = this.createElementNSPlus("csw:ResponseHandler", {
+ value: options.value
+ });
+ return node;
+ },
+ "Query": function(options) {
+ if (!options) {
+ options = {};
+ }
+ var node = this.createElementNSPlus("csw:Query", {
+ attributes: {
+ typeNames: options.typeNames || "csw:Record"
+ }
+ });
+ var ElementName = options.ElementName;
+ if (OpenLayers.Util.isArray(ElementName) && ElementName.length > 0) {
+ // ElementName must be a non-empty array
+ for(var i=0, len=ElementName.length; i<len; i++) {
+ this.writeNode(
+ "csw:ElementName",
+ ElementName[i],
+ node
+ );
+ }
+ } else {
+ this.writeNode(
+ "csw:ElementSetName",
+ options.ElementSetName || {value: 'summary'},
+ node
+ );
+ }
+ if (options.Constraint) {
+ this.writeNode(
+ "csw:Constraint",
+ options.Constraint,
+ node
+ );
+ }
+ if (options.SortBy) {
+ this.writeNode(
+ "ogc:SortBy",
+ options.SortBy,
+ node
+ );
+ }
+ return node;
+ },
+ "ElementName": function(options) {
+ var node = this.createElementNSPlus("csw:ElementName", {
+ value: options.value
+ });
+ return node;
+ },
+ "ElementSetName": function(options) {
+ var node = this.createElementNSPlus("csw:ElementSetName", {
+ attributes: {
+ typeNames: options.typeNames
+ },
+ value: options.value
+ });
+ return node;
+ },
+ "Constraint": function(options) {
+ var node = this.createElementNSPlus("csw:Constraint", {
+ attributes: {
+ version: options.version
+ }
+ });
+ if (options.Filter) {
+ var format = new OpenLayers.Format.Filter({
+ version: options.version
+ });
+ node.appendChild(format.write(options.Filter));
+ } else if (options.CqlText) {
+ var child = this.createElementNSPlus("CqlText", {
+ value: options.CqlText.value
+ });
+ node.appendChild(child);
+ }
+ return node;
+ }
+ },
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.CSWGetRecords.v2_0_2"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/Context.js b/misc/openlayers/lib/OpenLayers/Format/Context.js
new file mode 100644
index 0000000..73d6203
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Context.js
@@ -0,0 +1,334 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Context
+ * Base class for both Format.WMC and Format.OWSContext
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.Context = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * Property: layerOptions
+ * {Object} Default options for layers created by the parser. These
+ * options are overridden by the options which are read from the
+ * capabilities document.
+ */
+ layerOptions: null,
+
+ /**
+ * Property: layerParams
+ * {Object} Default parameters for layers created by the parser. This
+ * can be used e.g. to override DEFAULT_PARAMS for
+ * OpenLayers.Layer.WMS.
+ */
+ layerParams: null,
+
+ /**
+ * Constructor: OpenLayers.Format.Context
+ * Create a new parser for Context documents.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read Context data from a string, and return an object with map
+ * properties and a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ * options - {Object} The options object must contain a map property. If
+ * the map property is a string, it must be the id of a dom element
+ * where the new map will be placed. If the map property is an
+ * <OpenLayers.Map>, the layers from the context document will be added
+ * to the map.
+ *
+ * Returns:
+ * {<OpenLayers.Map>} A map based on the context.
+ */
+ read: function(data, options) {
+ var context = OpenLayers.Format.XML.VersionedOGC.prototype.read.apply(this,
+ arguments);
+ var map;
+ if(options && options.map) {
+ this.context = context;
+ if(options.map instanceof OpenLayers.Map) {
+ map = this.mergeContextToMap(context, options.map);
+ } else {
+ var mapOptions = options.map;
+ if(OpenLayers.Util.isElement(mapOptions) ||
+ typeof mapOptions == "string") {
+ // we assume mapOptions references a div
+ // element
+ mapOptions = {div: mapOptions};
+ }
+ map = this.contextToMap(context, mapOptions);
+ }
+ } else {
+ // not documented as part of the API, provided as a non-API option
+ map = context;
+ }
+ return map;
+ },
+
+ /**
+ * Method: getLayerFromContext
+ * Create a WMS layer from a layerContext object.
+ *
+ * Parameters:
+ * layerContext - {Object} An object representing a WMS layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} A WMS layer.
+ */
+ getLayerFromContext: function(layerContext) {
+ var i, len;
+ // fill initial options object from layerContext
+ var options = {
+ queryable: layerContext.queryable, //keep queryable for api compatibility
+ visibility: layerContext.visibility,
+ maxExtent: layerContext.maxExtent,
+ metadata: OpenLayers.Util.applyDefaults(layerContext.metadata,
+ {styles: layerContext.styles,
+ formats: layerContext.formats,
+ "abstract": layerContext["abstract"],
+ dataURL: layerContext.dataURL
+ }),
+ numZoomLevels: layerContext.numZoomLevels,
+ units: layerContext.units,
+ isBaseLayer: layerContext.isBaseLayer,
+ opacity: layerContext.opacity,
+ displayInLayerSwitcher: layerContext.displayInLayerSwitcher,
+ singleTile: layerContext.singleTile,
+ tileSize: (layerContext.tileSize) ?
+ new OpenLayers.Size(
+ layerContext.tileSize.width,
+ layerContext.tileSize.height
+ ) : undefined,
+ minScale: layerContext.minScale || layerContext.maxScaleDenominator,
+ maxScale: layerContext.maxScale || layerContext.minScaleDenominator,
+ srs: layerContext.srs,
+ dimensions: layerContext.dimensions,
+ metadataURL: layerContext.metadataURL
+ };
+ if (this.layerOptions) {
+ OpenLayers.Util.applyDefaults(options, this.layerOptions);
+ }
+
+ var params = {
+ layers: layerContext.name,
+ transparent: layerContext.transparent,
+ version: layerContext.version
+ };
+ if (layerContext.formats && layerContext.formats.length>0) {
+ // set default value for params if current attribute is not positionned
+ params.format = layerContext.formats[0].value;
+ for (i=0, len=layerContext.formats.length; i<len; i++) {
+ var format = layerContext.formats[i];
+ if (format.current == true) {
+ params.format = format.value;
+ break;
+ }
+ }
+ }
+ if (layerContext.styles && layerContext.styles.length>0) {
+ for (i=0, len=layerContext.styles.length; i<len; i++) {
+ var style = layerContext.styles[i];
+ if (style.current == true) {
+ // three style types to consider
+ // 1) linked SLD
+ // 2) inline SLD
+ // 3) named style
+ if(style.href) {
+ params.sld = style.href;
+ } else if(style.body) {
+ params.sld_body = style.body;
+ } else {
+ params.styles = style.name;
+ }
+ break;
+ }
+ }
+ }
+ if (this.layerParams) {
+ OpenLayers.Util.applyDefaults(params, this.layerParams);
+ }
+
+ var layer = null;
+ var service = layerContext.service;
+ if (service == OpenLayers.Format.Context.serviceTypes.WFS) {
+ options.strategies = [new OpenLayers.Strategy.BBOX()];
+ options.protocol = new OpenLayers.Protocol.WFS({
+ url: layerContext.url,
+ // since we do not know featureNS, let the protocol
+ // determine it automagically using featurePrefix
+ featurePrefix: layerContext.name.split(":")[0],
+ featureType: layerContext.name.split(":").pop()
+ });
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ } else if (service == OpenLayers.Format.Context.serviceTypes.KML) {
+ // use a vector layer with an HTTP Protcol and a Fixed strategy
+ options.strategies = [new OpenLayers.Strategy.Fixed()];
+ options.protocol = new OpenLayers.Protocol.HTTP({
+ url: layerContext.url,
+ format: new OpenLayers.Format.KML()
+ });
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ } else if (service == OpenLayers.Format.Context.serviceTypes.GML) {
+ // use a vector layer with a HTTP Protocol and a Fixed strategy
+ options.strategies = [new OpenLayers.Strategy.Fixed()];
+ options.protocol = new OpenLayers.Protocol.HTTP({
+ url: layerContext.url,
+ format: new OpenLayers.Format.GML()
+ });
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ } else if (layerContext.features) {
+ // inline GML or KML features
+ layer = new OpenLayers.Layer.Vector(
+ layerContext.title || layerContext.name,
+ options
+ );
+ layer.addFeatures(layerContext.features);
+ } else if (layerContext.categoryLayer !== true) {
+ layer = new OpenLayers.Layer.WMS(
+ layerContext.title || layerContext.name,
+ layerContext.url,
+ params,
+ options
+ );
+ }
+ return layer;
+ },
+
+ /**
+ * Method: getLayersFromContext
+ * Create an array of layers from an array of layerContext objects.
+ *
+ * Parameters:
+ * layersContext - {Array(Object)} An array of objects representing layers.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} An array of layers.
+ */
+ getLayersFromContext: function(layersContext) {
+ var layers = [];
+ for (var i=0, len=layersContext.length; i<len; i++) {
+ var layer = this.getLayerFromContext(layersContext[i]);
+ if (layer !== null) {
+ layers.push(layer);
+ }
+ }
+ return layers;
+ },
+
+ /**
+ * Method: contextToMap
+ * Create a map given a context object.
+ *
+ * Parameters:
+ * context - {Object} The context object.
+ * options - {Object} Default map options.
+ *
+ * Returns:
+ * {<OpenLayers.Map>} A map based on the context object.
+ */
+ contextToMap: function(context, options) {
+ options = OpenLayers.Util.applyDefaults({
+ maxExtent: context.maxExtent,
+ projection: context.projection,
+ units: context.units
+ }, options);
+
+ if (options.maxExtent) {
+ options.maxResolution =
+ options.maxExtent.getWidth() / OpenLayers.Map.TILE_WIDTH;
+ }
+
+ var metadata = {
+ contactInformation: context.contactInformation,
+ "abstract": context["abstract"],
+ keywords: context.keywords,
+ logo: context.logo,
+ descriptionURL: context.descriptionURL
+ };
+
+ options.metadata = metadata;
+
+ var map = new OpenLayers.Map(options);
+ map.addLayers(this.getLayersFromContext(context.layersContext));
+ map.setCenter(
+ context.bounds.getCenterLonLat(),
+ map.getZoomForExtent(context.bounds, true)
+ );
+ return map;
+ },
+
+ /**
+ * Method: mergeContextToMap
+ * Add layers from a context object to a map.
+ *
+ * Parameters:
+ * context - {Object} The context object.
+ * map - {<OpenLayers.Map>} The map.
+ *
+ * Returns:
+ * {<OpenLayers.Map>} The same map with layers added.
+ */
+ mergeContextToMap: function(context, map) {
+ map.addLayers(this.getLayersFromContext(context.layersContext));
+ return map;
+ },
+
+ /**
+ * APIMethod: write
+ * Write a context document given a map.
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Map> | Object} A map or context object.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} A context document string.
+ */
+ write: function(obj, options) {
+ obj = this.toContext(obj);
+ return OpenLayers.Format.XML.VersionedOGC.prototype.write.apply(this,
+ arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Context"
+});
+
+/**
+ * Constant: OpenLayers.Format.Context.serviceTypes
+ * Enumeration for service types
+ */
+OpenLayers.Format.Context.serviceTypes = {
+ "WMS": "urn:ogc:serviceType:WMS",
+ "WFS": "urn:ogc:serviceType:WFS",
+ "WCS": "urn:ogc:serviceType:WCS",
+ "GML": "urn:ogc:serviceType:GML",
+ "SLD": "urn:ogc:serviceType:SLD",
+ "FES": "urn:ogc:serviceType:FES",
+ "KML": "urn:ogc:serviceType:KML"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Format/EncodedPolyline.js b/misc/openlayers/lib/OpenLayers/Format/EncodedPolyline.js
new file mode 100644
index 0000000..e10e8b2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/EncodedPolyline.js
@@ -0,0 +1,557 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.EncodedPolyline
+ * Class for reading and writing encoded polylines. Create a new instance
+ * with the <OpenLayers.Format.EncodedPolyline> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.EncodedPolyline = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: geometryType
+ * {String} Geometry type to output. One of: linestring (default),
+ * linearring, point, multipoint or polygon. If the geometryType is
+ * point, only the first point of the string is returned.
+ */
+ geometryType: "linestring",
+
+ /**
+ * Constructor: OpenLayers.Format.EncodedPolyline
+ * Create a new parser for encoded polylines
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance
+ *
+ * Returns:
+ * {<OpenLayers.Format.EncodedPolyline>} A new encoded polylines parser.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize an encoded polyline string and return a vector feature.
+ *
+ * Parameters:
+ * encoded - {String} An encoded polyline string
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A vector feature with a linestring.
+ */
+ read: function(encoded) {
+ var geomType;
+ if (this.geometryType == "linestring")
+ geomType = OpenLayers.Geometry.LineString;
+ else if (this.geometryType == "linearring")
+ geomType = OpenLayers.Geometry.LinearRing;
+ else if (this.geometryType == "multipoint")
+ geomType = OpenLayers.Geometry.MultiPoint;
+ else if (this.geometryType != "point" && this.geometryType != "polygon")
+ return null;
+
+ var flatPoints = this.decodeDeltas(encoded, 2);
+ var flatPointsLength = flatPoints.length;
+
+ var pointGeometries = [];
+ for (var i = 0; i + 1 < flatPointsLength;) {
+ var y = flatPoints[i++], x = flatPoints[i++];
+ pointGeometries.push(new OpenLayers.Geometry.Point(x, y));
+ }
+
+
+ if (this.geometryType == "point")
+ return new OpenLayers.Feature.Vector(
+ pointGeometries[0]
+ );
+
+ if (this.geometryType == "polygon")
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing(pointGeometries)
+ ])
+ );
+
+ return new OpenLayers.Feature.Vector(
+ new geomType(pointGeometries)
+ );
+ },
+
+ /**
+ * APIMethod: decode
+ * Deserialize an encoded string and return an array of n-dimensional
+ * points.
+ *
+ * Parameters:
+ * encoded - {String} An encoded string
+ * dims - {int} The dimension of the points that are returned
+ *
+ * Returns:
+ * {Array(Array(int))} An array containing n-dimensional arrays of
+ * coordinates.
+ */
+ decode: function(encoded, dims, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var flatPoints = this.decodeDeltas(encoded, dims, factor);
+ var flatPointsLength = flatPoints.length;
+
+ var points = [];
+ for (var i = 0; i + (dims - 1) < flatPointsLength;) {
+ var point = [];
+
+ for (var dim = 0; dim < dims; ++dim) {
+ point.push(flatPoints[i++])
+ }
+
+ points.push(point);
+ }
+
+ return points;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature or array of features into a WKT string.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>|Array} A feature or array of
+ * features
+ *
+ * Returns:
+ * {String} The WKT string representation of the input geometries
+ */
+ write: function(features) {
+ var feature;
+ if (features.constructor == Array)
+ feature = features[0];
+ else
+ feature = features;
+
+ var geometry = feature.geometry;
+ var type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
+
+ var pointGeometries;
+ if (type == "point")
+ pointGeometries = new Array(geometry);
+ else if (type == "linestring" ||
+ type == "linearring" ||
+ type == "multipoint")
+ pointGeometries = geometry.components;
+ else if (type == "polygon")
+ pointGeometries = geometry.components[0].components;
+ else
+ return null;
+
+ var flatPoints = [];
+
+ var pointGeometriesLength = pointGeometries.length;
+ for (var i = 0; i < pointGeometriesLength; ++i) {
+ var pointGeometry = pointGeometries[i];
+ flatPoints.push(pointGeometry.y);
+ flatPoints.push(pointGeometry.x);
+ }
+
+ return this.encodeDeltas(flatPoints, 2);
+ },
+
+ /**
+ * APIMethod: encode
+ * Serialize an array of n-dimensional points and return an encoded string
+ *
+ * Parameters:
+ * points - {Array(Array(int))} An array containing n-dimensional
+ * arrays of coordinates
+ * dims - {int} The dimension of the points that should be read
+ *
+ * Returns:
+ * {String} An encoded string
+ */
+ encode: function (points, dims, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var flatPoints = [];
+
+ var pointsLength = points.length;
+ for (var i = 0; i < pointsLength; ++i) {
+ var point = points[i];
+
+ for (var dim = 0; dim < dims; ++dim) {
+ flatPoints.push(point[dim]);
+ }
+ }
+
+ return this.encodeDeltas(flatPoints, dims, factor);
+ },
+
+ /**
+ * APIMethod: encodeDeltas
+ * Encode a list of n-dimensional points and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of n-dimensional points.
+ * dimension - {number} The dimension of the points in the list.
+ * opt_factor - {number=} The factor by which the numbers will be
+ * multiplied. The remaining decimal places will get rounded away.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeDeltas: function(numbers, dimension, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var d;
+
+ var lastNumbers = new Array(dimension);
+ for (d = 0; d < dimension; ++d) {
+ lastNumbers[d] = 0;
+ }
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength;) {
+ for (d = 0; d < dimension; ++d, ++i) {
+ var num = numbers[i];
+ var delta = num - lastNumbers[d];
+ lastNumbers[d] = num;
+
+ numbers[i] = delta;
+ }
+ }
+
+ return this.encodeFloats(numbers, factor);
+ },
+
+
+ /**
+ * APIMethod: decodeDeltas
+ * Decode a list of n-dimensional points from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ * dimension - {number} The dimension of the points in the encoded string.
+ * opt_factor - {number=} The factor by which the resulting numbers will
+ * be divided.
+ *
+ * Returns:
+ * {Array.<number>} A list of n-dimensional points.
+ */
+ decodeDeltas: function(encoded, dimension, opt_factor) {
+ var factor = opt_factor || 1e5;
+ var d;
+
+ var lastNumbers = new Array(dimension);
+ for (d = 0; d < dimension; ++d) {
+ lastNumbers[d] = 0;
+ }
+
+ var numbers = this.decodeFloats(encoded, factor);
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength;) {
+ for (d = 0; d < dimension; ++d, ++i) {
+ lastNumbers[d] += numbers[i];
+
+ numbers[i] = lastNumbers[d];
+ }
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * APIMethod: encodeFloats
+ * Encode a list of floating point numbers and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of floating point numbers.
+ * opt_factor - {number=} The factor by which the numbers will be
+ * multiplied. The remaining decimal places will get rounded away.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeFloats: function(numbers, opt_factor) {
+ var factor = opt_factor || 1e5;
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ numbers[i] = Math.round(numbers[i] * factor);
+ }
+
+ return this.encodeSignedIntegers(numbers);
+ },
+
+
+ /**
+ * APIMethod: decodeFloats
+ * Decode a list of floating point numbers from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ * opt_factor - {number=} The factor by which the result will be divided.
+ *
+ * Returns:
+ * {Array.<number>} A list of floating point numbers.
+ */
+ decodeFloats: function(encoded, opt_factor) {
+ var factor = opt_factor || 1e5;
+
+ var numbers = this.decodeSignedIntegers(encoded);
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ numbers[i] /= factor;
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * APIMethod: encodeSignedIntegers
+ * Encode a list of signed integers and return an encoded string
+ *
+ * Attention: This function will modify the passed array!
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of signed integers.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeSignedIntegers: function(numbers) {
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ var num = numbers[i];
+
+ var signedNum = num << 1;
+ if (num < 0) {
+ signedNum = ~(signedNum);
+ }
+
+ numbers[i] = signedNum;
+ }
+
+ return this.encodeUnsignedIntegers(numbers);
+ },
+
+
+ /**
+ * APIMethod: decodeSignedIntegers
+ * Decode a list of signed integers from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {Array.<number>} A list of signed integers.
+ */
+ decodeSignedIntegers: function(encoded) {
+ var numbers = this.decodeUnsignedIntegers(encoded);
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ var num = numbers[i];
+ numbers[i] = (num & 1) ? ~(num >> 1) : (num >> 1);
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * APIMethod: encodeUnsignedIntegers
+ * Encode a list of unsigned integers and return an encoded string
+ *
+ * Parameters:
+ * numbers - {Array.<number>} A list of unsigned integers.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeUnsignedIntegers: function(numbers) {
+ var encoded = '';
+
+ var numbersLength = numbers.length;
+ for (var i = 0; i < numbersLength; ++i) {
+ encoded += this.encodeUnsignedInteger(numbers[i]);
+ }
+
+ return encoded;
+ },
+
+
+ /**
+ * APIMethod: decodeUnsignedIntegers
+ * Decode a list of unsigned integers from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {Array.<number>} A list of unsigned integers.
+ */
+ decodeUnsignedIntegers: function(encoded) {
+ var numbers = [];
+
+ var current = 0;
+ var shift = 0;
+
+ var encodedLength = encoded.length;
+ for (var i = 0; i < encodedLength; ++i) {
+ var b = encoded.charCodeAt(i) - 63;
+
+ current |= (b & 0x1f) << shift;
+
+ if (b < 0x20) {
+ numbers.push(current);
+ current = 0;
+ shift = 0;
+ } else {
+ shift += 5;
+ }
+ }
+
+ return numbers;
+ },
+
+
+ /**
+ * Method: encodeFloat
+ * Encode one single floating point number and return an encoded string
+ *
+ * Parameters:
+ * num - {number} Floating point number that should be encoded.
+ * opt_factor - {number=} The factor by which num will be multiplied.
+ * The remaining decimal places will get rounded away.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeFloat: function(num, opt_factor) {
+ num = Math.round(num * (opt_factor || 1e5));
+ return this.encodeSignedInteger(num);
+ },
+
+
+ /**
+ * Method: decodeFloat
+ * Decode one single floating point number from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ * opt_factor - {number=} The factor by which the result will be divided.
+ *
+ * Returns:
+ * {number} The decoded floating point number.
+ */
+ decodeFloat: function(encoded, opt_factor) {
+ var result = this.decodeSignedInteger(encoded);
+ return result / (opt_factor || 1e5);
+ },
+
+
+ /**
+ * Method: encodeSignedInteger
+ * Encode one single signed integer and return an encoded string
+ *
+ * Parameters:
+ * num - {number} Signed integer that should be encoded.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeSignedInteger: function(num) {
+ var signedNum = num << 1;
+ if (num < 0) {
+ signedNum = ~(signedNum);
+ }
+
+ return this.encodeUnsignedInteger(signedNum);
+ },
+
+
+ /**
+ * Method: decodeSignedInteger
+ * Decode one single signed integer from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {number} The decoded signed integer.
+ */
+ decodeSignedInteger: function(encoded) {
+ var result = this.decodeUnsignedInteger(encoded);
+ return ((result & 1) ? ~(result >> 1) : (result >> 1));
+ },
+
+
+ /**
+ * Method: encodeUnsignedInteger
+ * Encode one single unsigned integer and return an encoded string
+ *
+ * Parameters:
+ * num - {number} Unsigned integer that should be encoded.
+ *
+ * Returns:
+ * {string} The encoded string.
+ */
+ encodeUnsignedInteger: function(num) {
+ var value, encoded = '';
+ while (num >= 0x20) {
+ value = (0x20 | (num & 0x1f)) + 63;
+ encoded += (String.fromCharCode(value));
+ num >>= 5;
+ }
+ value = num + 63;
+ encoded += (String.fromCharCode(value));
+ return encoded;
+ },
+
+
+ /**
+ * Method: decodeUnsignedInteger
+ * Decode one single unsigned integer from an encoded string
+ *
+ * Parameters:
+ * encoded - {string} An encoded string.
+ *
+ * Returns:
+ * {number} The decoded unsigned integer.
+ */
+ decodeUnsignedInteger: function(encoded) {
+ var result = 0;
+ var shift = 0;
+
+ var encodedLength = encoded.length;
+ for (var i = 0; i < encodedLength; ++i) {
+ var b = encoded.charCodeAt(i) - 63;
+
+ result |= (b & 0x1f) << shift;
+
+ if (b < 0x20)
+ break;
+
+ shift += 5;
+ }
+
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.EncodedPolyline"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/Filter.js b/misc/openlayers/lib/OpenLayers/Format/Filter.js
new file mode 100644
index 0000000..59c06a8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Filter.js
@@ -0,0 +1,53 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter
+ * Read/Write ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIMethod: write
+ * Write an ogc:Filter given a filter object.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} An filter.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {Elment} An ogc:Filter element node.
+ */
+
+ /**
+ * APIMethod: read
+ * Read and Filter doc and return an object representing the Filter.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.Filter"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/Filter/v1.js b/misc/openlayers/lib/OpenLayers/Format/Filter/v1.js
new file mode 100644
index 0000000..539ec0f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Filter/v1.js
@@ -0,0 +1,504 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+/**
+ * @requires OpenLayers/Format/Filter.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Filter/Function.js
+ * @requires OpenLayers/BaseTypes/Date.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1
+ * Superclass for Filter version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ogc: "http://www.opengis.net/ogc",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "ogc",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} A Filter document element.
+ *
+ * Returns:
+ * {<OpenLayers.Filter>} A filter object.
+ */
+ read: function(data) {
+ var obj = {};
+ this.readers.ogc["Filter"].apply(this, [data, obj]);
+ return obj.filter;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": {
+ "_expression": function(node) {
+ // only the simplest of ogc:expression handled
+ // "some text and an <PropertyName>attribute</PropertyName>"}
+ var obj, value = "";
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1:
+ obj = this.readNode(child);
+ if (obj.property) {
+ value += "${" + obj.property + "}";
+ } else if (obj.value !== undefined) {
+ value += obj.value;
+ }
+ break;
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ return value;
+ },
+ "Filter": function(node, parent) {
+ // Filters correspond to subclasses of OpenLayers.Filter.
+ // Since they contain information we don't persist, we
+ // create a temporary object and then pass on the filter
+ // (ogc:Filter) to the parent obj.
+ var obj = {
+ fids: [],
+ filters: []
+ };
+ this.readChildNodes(node, obj);
+ if(obj.fids.length > 0) {
+ parent.filter = new OpenLayers.Filter.FeatureId({
+ fids: obj.fids
+ });
+ } else if(obj.filters.length > 0) {
+ parent.filter = obj.filters[0];
+ }
+ },
+ "FeatureId": function(node, obj) {
+ var fid = node.getAttribute("fid");
+ if(fid) {
+ obj.fids.push(fid);
+ }
+ },
+ "And": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Or": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Not": function(node, obj) {
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLessThan": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsGreaterThan": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLessThanOrEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsBetween": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "Literal": function(node, obj) {
+ obj.value = OpenLayers.String.numericIf(
+ this.getChildValue(node), true);
+ },
+ "PropertyName": function(node, filter) {
+ filter.property = this.getChildValue(node);
+ },
+ "LowerBoundary": function(node, filter) {
+ filter.lowerBoundary = OpenLayers.String.numericIf(
+ this.readers.ogc._expression.call(this, node), true);
+ },
+ "UpperBoundary": function(node, filter) {
+ filter.upperBoundary = OpenLayers.String.numericIf(
+ this.readers.ogc._expression.call(this, node), true);
+ },
+ "Intersects": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS);
+ },
+ "Within": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN);
+ },
+ "Contains": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS);
+ },
+ "DWithin": function(node, obj) {
+ this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN);
+ },
+ "Distance": function(node, obj) {
+ obj.distance = parseInt(this.getChildValue(node));
+ obj.distanceUnits = node.getAttribute("units");
+ },
+ "Function": function(node, obj) {
+ //TODO write decoder for it
+ return;
+ },
+ "PropertyIsNull": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ }
+ }
+ },
+
+ /**
+ * Method: readSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM element that contains an ogc:expression.
+ * obj - {Object} The target object.
+ * type - {String} One of the OpenLayers.Filter.Spatial.* constants.
+ *
+ * Returns:
+ * {<OpenLayers.Filter.Spatial>} The created filter.
+ */
+ readSpatial: function(node, obj, type) {
+ var filter = new OpenLayers.Filter.Spatial({
+ type: type
+ });
+ this.readChildNodes(node, filter);
+ filter.value = filter.components[0];
+ delete filter.components;
+ obj.filters.push(filter);
+ },
+
+ /**
+ * APIMethod: encodeLiteral
+ * Generates the string representation of a value for use in <Literal>
+ * elements. The default encoder writes Date values as ISO 8601
+ * strings.
+ *
+ * Parameters:
+ * value - {Object} Literal value to encode
+ *
+ * Returns:
+ * {String} String representation of the provided value.
+ */
+ encodeLiteral: function(value) {
+ if (value instanceof Date) {
+ value = OpenLayers.Date.toISOString(value);
+ }
+ return value;
+ },
+
+ /**
+ * Method: writeOgcExpression
+ * Limited support for writing OGC expressions. Currently it supports
+ * (<OpenLayers.Filter.Function> || String || Number)
+ *
+ * Parameters:
+ * value - (<OpenLayers.Filter.Function> || String || Number)
+ * node - {DOMElement} A parent DOM element
+ *
+ * Returns:
+ * {DOMElement} Updated node element.
+ */
+ writeOgcExpression: function(value, node) {
+ if (value instanceof OpenLayers.Filter.Function){
+ this.writeNode("Function", value, node);
+ } else {
+ this.writeNode("Literal", value, node);
+ }
+ return node;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} A filter object.
+ *
+ * Returns:
+ * {DOMElement} An ogc:Filter element.
+ */
+ write: function(filter) {
+ return this.writers.ogc["Filter"].apply(this, [filter]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": {
+ "Filter": function(filter) {
+ var node = this.createElementNSPlus("ogc:Filter");
+ this.writeNode(this.getFilterType(filter), filter, node);
+ return node;
+ },
+ "_featureIds": function(filter) {
+ var node = this.createDocumentFragment();
+ for (var i=0, ii=filter.fids.length; i<ii; ++i) {
+ this.writeNode("ogc:FeatureId", filter.fids[i], node);
+ }
+ return node;
+ },
+ "FeatureId": function(fid) {
+ return this.createElementNSPlus("ogc:FeatureId", {
+ attributes: {fid: fid}
+ });
+ },
+ "And": function(filter) {
+ var node = this.createElementNSPlus("ogc:And");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Or": function(filter) {
+ var node = this.createElementNSPlus("ogc:Or");
+ var childFilter;
+ for (var i=0, ii=filter.filters.length; i<ii; ++i) {
+ childFilter = filter.filters[i];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ }
+ return node;
+ },
+ "Not": function(filter) {
+ var node = this.createElementNSPlus("ogc:Not");
+ var childFilter = filter.filters[0];
+ this.writeNode(
+ this.getFilterType(childFilter), childFilter, node
+ );
+ return node;
+ },
+ "PropertyIsLessThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsGreaterThan": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLessThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsGreaterThanOrEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsBetween": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsBetween");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ this.writeNode("LowerBoundary", filter, node);
+ this.writeNode("UpperBoundary", filter, node);
+ return node;
+ },
+ "PropertyName": function(filter) {
+ // no ogc:expression handling for now
+ return this.createElementNSPlus("ogc:PropertyName", {
+ value: filter.property
+ });
+ },
+ "Literal": function(value) {
+ var encode = this.encodeLiteral ||
+ OpenLayers.Format.Filter.v1.prototype.encodeLiteral;
+ return this.createElementNSPlus("ogc:Literal", {
+ value: encode(value)
+ });
+ },
+ "LowerBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:LowerBoundary");
+ this.writeOgcExpression(filter.lowerBoundary, node);
+ return node;
+ },
+ "UpperBoundary": function(filter) {
+ // handle Literals or Functions for now
+ var node = this.createElementNSPlus("ogc:UpperBoundary");
+ this.writeNode("Literal", filter.upperBoundary, node);
+ return node;
+ },
+ "INTERSECTS": function(filter) {
+ return this.writeSpatial(filter, "Intersects");
+ },
+ "WITHIN": function(filter) {
+ return this.writeSpatial(filter, "Within");
+ },
+ "CONTAINS": function(filter) {
+ return this.writeSpatial(filter, "Contains");
+ },
+ "DWITHIN": function(filter) {
+ var node = this.writeSpatial(filter, "DWithin");
+ this.writeNode("Distance", filter, node);
+ return node;
+ },
+ "Distance": function(filter) {
+ return this.createElementNSPlus("ogc:Distance", {
+ attributes: {
+ units: filter.distanceUnits
+ },
+ value: filter.distance
+ });
+ },
+ "Function": function(filter) {
+ var node = this.createElementNSPlus("ogc:Function", {
+ attributes: {
+ name: filter.name
+ }
+ });
+ var params = filter.params;
+ for(var i=0, len=params.length; i<len; i++){
+ this.writeOgcExpression(params[i], node);
+ }
+ return node;
+ },
+ "PropertyIsNull": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNull");
+ this.writeNode("PropertyName", filter, node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: getFilterType
+ */
+ getFilterType: function(filter) {
+ var filterType = this.filterMap[filter.type];
+ if(!filterType) {
+ throw "Filter writing not supported for rule type: " + filter.type;
+ }
+ return filterType;
+ },
+
+ /**
+ * Property: filterMap
+ * {Object} Contains a member for each filter type. Values are node names
+ * for corresponding OGC Filter child elements.
+ */
+ filterMap: {
+ "&&": "And",
+ "||": "Or",
+ "!": "Not",
+ "==": "PropertyIsEqualTo",
+ "!=": "PropertyIsNotEqualTo",
+ "<": "PropertyIsLessThan",
+ ">": "PropertyIsGreaterThan",
+ "<=": "PropertyIsLessThanOrEqualTo",
+ ">=": "PropertyIsGreaterThanOrEqualTo",
+ "..": "PropertyIsBetween",
+ "~": "PropertyIsLike",
+ "NULL": "PropertyIsNull",
+ "BBOX": "BBOX",
+ "DWITHIN": "DWITHIN",
+ "WITHIN": "WITHIN",
+ "CONTAINS": "CONTAINS",
+ "INTERSECTS": "INTERSECTS",
+ "FID": "_featureIds"
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/Filter/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/Filter/v1_0_0.js
new file mode 100644
index 0000000..52e650e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Filter/v1_0_0.js
@@ -0,0 +1,184 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/v2.js
+ * @requires OpenLayers/Format/Filter/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_0_0
+ * Write ogc:Filter version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.v2>
+ * - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
+ */
+ schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.v2.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsNotEqualTo": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLike": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE
+ });
+ this.readChildNodes(node, filter);
+ var wildCard = node.getAttribute("wildCard");
+ var singleChar = node.getAttribute("singleChar");
+ var esc = node.getAttribute("escape");
+ filter.value2regex(wildCard, singleChar, esc);
+ obj.filters.push(filter);
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsNotEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLike": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+ attributes: {
+ wildCard: "*", singleChar: ".", escape: "!"
+ }
+ });
+ // no ogc:expression handling for now
+ this.writeNode("PropertyName", filter, node);
+ // convert regex string to ogc string
+ this.writeNode("Literal", filter.regex2value(), node);
+ return node;
+ },
+ "BBOX": function(filter) {
+ var node = this.createElementNSPlus("ogc:BBOX");
+ // PropertyName is mandatory in 1.0.0, but e.g. GeoServer also
+ // accepts filters without it. When this is used with
+ // OpenLayers.Protocol.WFS, OpenLayers.Format.WFST will set a
+ // missing filter.property to the geometryName that is
+ // configured with the protocol, which defaults to "the_geom".
+ // So the only way to omit this mandatory property is to not
+ // set the property on the filter and to set the geometryName
+ // on the WFS protocol to null. The latter also happens when
+ // the protocol is configured without a geometryName and a
+ // featureNS.
+ filter.property && this.writeNode("PropertyName", filter, node);
+ var box = this.writeNode("gml:Box", filter.value, node);
+ if(filter.projection) {
+ box.setAttribute("srsName", filter.projection);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"]
+ },
+
+ /**
+ * Method: writeSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter.Spatial>} The filter.
+ * name - {String} Name of the generated XML element.
+ *
+ * Returns:
+ * {DOMElement} The created XML element.
+ */
+ writeSpatial: function(filter, name) {
+ var node = this.createElementNSPlus("ogc:"+name);
+ this.writeNode("PropertyName", filter, node);
+ if(filter.value instanceof OpenLayers.Filter.Function) {
+ this.writeNode("Function", filter.value, node);
+ } else {
+ var child;
+ if(filter.value instanceof OpenLayers.Geometry) {
+ child = this.writeNode("feature:_geometry", filter.value).firstChild;
+ } else {
+ child = this.writeNode("gml:Box", filter.value);
+ }
+ if(filter.projection) {
+ child.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(child);
+ }
+ return node;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0"
+
+}); \ No newline at end of file
diff --git a/misc/openlayers/lib/OpenLayers/Format/Filter/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/Filter/v1_1_0.js
new file mode 100644
index 0000000..628c489
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Filter/v1_1_0.js
@@ -0,0 +1,222 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Filter/v1.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_1_0
+ * Write ogc:Filter version 1.1.0.
+ *
+ * Differences from the v1.0.0 parser:
+ * - uses GML v3 instead of GML v2
+ * - reads matchCase attribute on ogc:PropertyIsEqual and
+ * ogc:PropertyIsNotEqual elements.
+ * - writes matchCase attribute from comparison filters of type EQUAL_TO,
+ * NOT_EQUAL_TO and LIKE.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.v3>
+ * - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.GML.v3, OpenLayers.Format.Filter.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1.0
+ */
+ VERSION: "1.1.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/ogc/filter/1.1.0/filter.xsd
+ */
+ schemaLocation: "http://www.opengis.net/ogc/filter/1.1.0/filter.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.Filter.v1_1_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.Filter> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.v3.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(node, obj) {
+ var matchCase = node.getAttribute("matchCase");
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ matchCase: !(matchCase === "false" || matchCase === "0")
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsNotEqualTo": function(node, obj) {
+ var matchCase = node.getAttribute("matchCase");
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: !(matchCase === "false" || matchCase === "0")
+ });
+ this.readChildNodes(node, filter);
+ obj.filters.push(filter);
+ },
+ "PropertyIsLike": function(node, obj) {
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE
+ });
+ this.readChildNodes(node, filter);
+ var wildCard = node.getAttribute("wildCard");
+ var singleChar = node.getAttribute("singleChar");
+ var esc = node.getAttribute("escapeChar");
+ filter.value2regex(wildCard, singleChar, esc);
+ obj.filters.push(filter);
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ogc": OpenLayers.Util.applyDefaults({
+ "PropertyIsEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsEqualTo", {
+ attributes: {matchCase: filter.matchCase}
+ });
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsNotEqualTo": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo", {
+ attributes: {matchCase: filter.matchCase}
+ });
+ // no ogc:expression handling for PropertyName for now
+ this.writeNode("PropertyName", filter, node);
+ // handle Literals or Functions for now
+ this.writeOgcExpression(filter.value, node);
+ return node;
+ },
+ "PropertyIsLike": function(filter) {
+ var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+ attributes: {
+ matchCase: filter.matchCase,
+ wildCard: "*", singleChar: ".", escapeChar: "!"
+ }
+ });
+ // no ogc:expression handling for now
+ this.writeNode("PropertyName", filter, node);
+ // convert regex string to ogc string
+ this.writeNode("Literal", filter.regex2value(), node);
+ return node;
+ },
+ "BBOX": function(filter) {
+ var node = this.createElementNSPlus("ogc:BBOX");
+ // PropertyName is optional in 1.1.0
+ filter.property && this.writeNode("PropertyName", filter, node);
+ var box = this.writeNode("gml:Envelope", filter.value);
+ if(filter.projection) {
+ box.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(box);
+ return node;
+ },
+ "SortBy": function(sortProperties) {
+ var node = this.createElementNSPlus("ogc:SortBy");
+ for (var i=0,l=sortProperties.length;i<l;i++) {
+ this.writeNode(
+ "ogc:SortProperty",
+ sortProperties[i],
+ node
+ );
+ }
+ return node;
+ },
+ "SortProperty": function(sortProperty) {
+ var node = this.createElementNSPlus("ogc:SortProperty");
+ this.writeNode(
+ "ogc:PropertyName",
+ sortProperty,
+ node
+ );
+ this.writeNode(
+ "ogc:SortOrder",
+ (sortProperty.order == 'DESC') ? 'DESC' : 'ASC',
+ node
+ );
+ return node;
+ },
+ "SortOrder": function(value) {
+ var node = this.createElementNSPlus("ogc:SortOrder", {
+ value: value
+ });
+ return node;
+ }
+ }, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"]
+ },
+
+ /**
+ * Method: writeSpatial
+ *
+ * Read a {<OpenLayers.Filter.Spatial>} filter and converts it into XML.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter.Spatial>} The filter.
+ * name - {String} Name of the generated XML element.
+ *
+ * Returns:
+ * {DOMElement} The created XML element.
+ */
+ writeSpatial: function(filter, name) {
+ var node = this.createElementNSPlus("ogc:"+name);
+ this.writeNode("PropertyName", filter, node);
+ if(filter.value instanceof OpenLayers.Filter.Function) {
+ this.writeNode("Function", filter.value, node);
+ } else {
+ var child;
+ if(filter.value instanceof OpenLayers.Geometry) {
+ child = this.writeNode("feature:_geometry", filter.value).firstChild;
+ } else {
+ child = this.writeNode("gml:Envelope", filter.value);
+ }
+ if(filter.projection) {
+ child.setAttribute("srsName", filter.projection);
+ }
+ node.appendChild(child);
+ }
+ return node;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Filter.v1_1_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GML.js b/misc/openlayers/lib/OpenLayers/Format/GML.js
new file mode 100644
index 0000000..467f875
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GML.js
@@ -0,0 +1,923 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML
+ * Read/Write GML. Create a new instance with the <OpenLayers.Format.GML>
+ * constructor. Supports the GML simple features profile.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: featureNS
+ * {String} Namespace used for feature attributes. Default is
+ * "http://mapserver.gis.umn.edu/mapserver".
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * APIProperty: featurePrefix
+ * {String} Namespace alias (or prefix) for feature nodes. Default is
+ * "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * APIProperty: featureName
+ * {String} Element name for features. Default is "featureMember".
+ */
+ featureName: "featureMember",
+
+ /**
+ * APIProperty: layerName
+ * {String} Name of data layer. Default is "features".
+ */
+ layerName: "features",
+
+ /**
+ * APIProperty: geometryName
+ * {String} Name of geometry element. Defaults to "geometry".
+ */
+ geometryName: "geometry",
+
+ /**
+ * APIProperty: collectionName
+ * {String} Name of featureCollection element.
+ */
+ collectionName: "FeatureCollection",
+
+ /**
+ * APIProperty: gmlns
+ * {String} GML Namespace.
+ */
+ gmlns: "http://www.opengis.net/gml",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Constructor: OpenLayers.Format.GML
+ * Create a new parser for GML.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // compile regular expressions once instead of every time they are used
+ this.regExes = {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ };
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var featureNodes = this.getElementsByTagNameNS(data.documentElement,
+ this.gmlns,
+ this.featureName);
+ var features = [];
+ for(var i=0; i<featureNodes.length; i++) {
+ var feature = this.parseFeature(featureNodes[i]);
+ if(feature) {
+ features.push(feature);
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the GML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML feature node.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiPolygon", "Polygon",
+ "MultiLineString", "LineString",
+ "MultiPoint", "Point", "Envelope"];
+ // FIXME: In case we parse a feature with no geometry, but boundedBy an Envelope,
+ // this code creates a geometry derived from the Envelope. This is not correct.
+ var type, nodeList, geometry, parser;
+ for(var i=0; i<order.length; ++i) {
+ type = order[i];
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ geometry = parser.apply(this, [nodeList[0]]);
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ } else {
+ throw new TypeError("Unsupported geometry type: " + type);
+ }
+ // stop looking for different geometry types
+ break;
+ }
+ }
+
+ var bounds;
+ var boxNodes = this.getElementsByTagNameNS(node, this.gmlns, "Box");
+ for(i=0; i<boxNodes.length; ++i) {
+ var boxNode = boxNodes[i];
+ var box = this.parseGeometry["box"].apply(this, [boxNode]);
+ var parentNode = boxNode.parentNode;
+ var parentName = parentNode.localName ||
+ parentNode.nodeName.split(":").pop();
+ if(parentName === "boundedBy") {
+ bounds = box;
+ } else {
+ geometry = box.toGeometry();
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ feature.bounds = bounds;
+
+ feature.gml = {
+ featureType: node.firstChild.nodeName.split(":")[1],
+ featureNS: node.firstChild.namespaceURI,
+ featureNSPrefix: node.firstChild.prefix
+ };
+
+ // assign fid - this can come from a "fid" or "id" attribute
+ var childNode = node.firstChild;
+ var fid;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ fid = childNode.getAttribute("fid") ||
+ childNode.getAttribute("id");
+ if(fid) {
+ break;
+ }
+ }
+ childNode = childNode.nextSibling;
+ }
+ feature.fid = fid;
+ return feature;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a GML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ /**
+ * Three coordinate variations to consider:
+ * 1) <gml:pos>x y z</gml:pos>
+ * 2) <gml:coordinates>x, y, z</gml:coordinates>
+ * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
+ */
+ var nodeList, coordString;
+ var coords = [];
+
+ // look for <gml:pos>
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
+ if(nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ // look for <gml:coordinates>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ if(nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace,
+ "");
+ coords = coordString.split(",");
+ }
+ }
+
+ // look for <gml:coord>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coord");
+ if(nodeList.length > 0) {
+ var xList = this.getElementsByTagNameNS(nodeList[0],
+ this.gmlns, "X");
+ var yList = this.getElementsByTagNameNS(nodeList[0],
+ this.gmlns, "Y");
+ if(xList.length > 0 && yList.length > 0) {
+ coords = [xList[0].firstChild.nodeValue,
+ yList[0].firstChild.nodeValue];
+ }
+ }
+ }
+
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+
+ if (this.xy) {
+ return new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ }
+ else{
+ return new OpenLayers.Geometry.Point(coords[1], coords[0],
+ coords[2]);
+ }
+ },
+
+ /**
+ * Method: parseGeometry.multipoint
+ * Given a GML node representing a multipoint geometry, create an
+ * OpenLayers multipoint geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+ */
+ multipoint: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "Point");
+ var components = [];
+ if(nodeList.length > 0) {
+ var point;
+ for(var i=0; i<nodeList.length; ++i) {
+ point = this.parseGeometry.point.apply(this, [nodeList[i]]);
+ if(point) {
+ components.push(point);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPoint(components);
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a GML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ /**
+ * Two coordinate variations to consider:
+ * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
+ * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
+ */
+ var nodeList, coordString;
+ var coords = [];
+ var points = [];
+
+ // look for <gml:posList>
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
+ if(nodeList.length > 0) {
+ coordString = this.getChildValue(nodeList[0]);
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ var dim = parseInt(nodeList[0].getAttribute("dimension"));
+ var j, x, y, z;
+ for(var i=0; i<coords.length/dim; ++i) {
+ j = i * dim;
+ x = coords[j];
+ y = coords[j+1];
+ z = (dim == 2) ? null : coords[j+2];
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(x, y, z));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(y, x, z));
+ }
+ }
+ }
+
+ // look for <gml:coordinates>
+ if(coords.length == 0) {
+ nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ if(nodeList.length > 0) {
+ coordString = this.getChildValue(nodeList[0]);
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ for(var i=0; i<pointList.length; ++i) {
+ coords = pointList[i].split(",");
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ points.push(new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]));
+ } else {
+ points.push(new OpenLayers.Geometry.Point(coords[1],
+ coords[0],
+ coords[2]));
+ }
+ }
+ }
+ }
+
+ var line = null;
+ if(points.length != 0) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ }
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.multilinestring
+ * Given a GML node representing a multilinestring geometry, create an
+ * OpenLayers multilinestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
+ */
+ multilinestring: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "LineString");
+ var components = [];
+ if(nodeList.length > 0) {
+ var line;
+ for(var i=0; i<nodeList.length; ++i) {
+ line = this.parseGeometry.linestring.apply(this,
+ [nodeList[i]]);
+ if(line) {
+ components.push(line);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiLineString(components);
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a GML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "LinearRing");
+ var components = [];
+ if(nodeList.length > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0; i<nodeList.length; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components.push(ring);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multipolygon
+ * Given a GML node representing a multipolygon geometry, create an
+ * OpenLayers multipolygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
+ */
+ multipolygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "Polygon");
+ var components = [];
+ if(nodeList.length > 0) {
+ var polygon;
+ for(var i=0; i<nodeList.length; ++i) {
+ polygon = this.parseGeometry.polygon.apply(this,
+ [nodeList[i]]);
+ if(polygon) {
+ components.push(polygon);
+ }
+ }
+ }
+ return new OpenLayers.Geometry.MultiPolygon(components);
+ },
+
+ envelope: function(node) {
+ var components = [];
+ var coordString;
+ var envelope;
+
+ var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
+ if (lpoint.length > 0) {
+ var coords = [];
+
+ if(lpoint.length > 0) {
+ coordString = lpoint[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+ } else {
+ var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+ }
+ }
+
+ var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
+ if (upoint.length > 0) {
+ var coords = [];
+
+ if(upoint.length > 0) {
+ coordString = upoint[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.trimSpace, "");
+ coords = coordString.split(this.regExes.splitSpace);
+ }
+
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ if (this.xy) {
+ var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+ } else {
+ var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+ }
+ }
+
+ if (lowerPoint && upperPoint) {
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+ components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
+ components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
+ components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+ envelope = new OpenLayers.Geometry.Polygon([ring]);
+ }
+ return envelope;
+ },
+
+ /**
+ * Method: parseGeometry.box
+ * Given a GML node representing a box geometry, create an
+ * OpenLayers.Bounds.
+ *
+ * Parameters:
+ * node - {DOMElement} A GML node.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds representing the box.
+ */
+ box: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+ "coordinates");
+ var coordString;
+ var coords, beginPoint = null, endPoint = null;
+ if (nodeList.length > 0) {
+ coordString = nodeList[0].firstChild.nodeValue;
+ coords = coordString.split(" ");
+ if (coords.length == 2) {
+ beginPoint = coords[0].split(",");
+ endPoint = coords[1].split(",");
+ }
+ }
+ if (beginPoint !== null && endPoint !== null) {
+ return new OpenLayers.Bounds(parseFloat(beginPoint[0]),
+ parseFloat(beginPoint[1]),
+ parseFloat(endPoint[0]),
+ parseFloat(endPoint[1]) );
+ }
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+ // assume attributes are children of the first type 1 child
+ var childNode = node.firstChild;
+ var children, i, child, grandchildren, grandchild, name, value;
+ while(childNode) {
+ if(childNode.nodeType == 1) {
+ // attributes are type 1 children with one type 3 child
+ children = childNode.childNodes;
+ for(i=0; i<children.length; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length == 1) {
+ grandchild = grandchildren[0];
+ if(grandchild.nodeType == 3 ||
+ grandchild.nodeType == 4) {
+ name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ value = grandchild.nodeValue.replace(
+ this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ } else {
+ // If child has no childNodes (grandchildren),
+ // set an attribute with null value.
+ // e.g. <prefix:fieldname/> becomes
+ // {fieldname: null}
+ attributes[child.nodeName.split(":").pop()] = null;
+ }
+ }
+ }
+ break;
+ }
+ childNode = childNode.nextSibling;
+ }
+ return attributes;
+ },
+
+ /**
+ * APIMethod: write
+ * Generate a GML document string given a list of features.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
+ * serialize into a string.
+ *
+ * Returns:
+ * {String} A string representing the GML document.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var gml = this.createElementNS("http://www.opengis.net/wfs",
+ "wfs:" + this.collectionName);
+ for(var i=0; i<features.length; i++) {
+ gml.appendChild(this.createFeatureXML(features[i]));
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
+ *
+ * Returns:
+ * {DOMElement} A node reprensting the feature in GML.
+ */
+ createFeatureXML: function(feature) {
+ var geometry = feature.geometry;
+ var geometryNode = this.buildGeometryNode(geometry);
+ var geomContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ this.geometryName);
+ geomContainer.appendChild(geometryNode);
+ var featureNode = this.createElementNS(this.gmlns,
+ "gml:" + this.featureName);
+ var featureContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ this.layerName);
+ var fid = feature.fid || feature.id;
+ featureContainer.setAttribute("fid", fid);
+ featureContainer.appendChild(geomContainer);
+ for(var attr in feature.attributes) {
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr.substring(attr.lastIndexOf(":") + 1);
+ var attrContainer = this.createElementNS(this.featureNS,
+ this.featurePrefix + ":" +
+ nodename);
+ attrContainer.appendChild(attrText);
+ featureContainer.appendChild(attrContainer);
+ }
+ featureNode.appendChild(featureContainer);
+ return featureNode;
+ },
+
+ /**
+ * APIMethod: buildGeometryNode
+ */
+ buildGeometryNode: function(geometry) {
+ if (this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ return builder.apply(this, [geometry]);
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD retrieve the srs from layer
+ // srsName is non-standard, so not including it until it's right.
+ // gml.setAttribute("srsName",
+ // "http://www.opengis.net/gml/srs/epsg.xml#4326");
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a GML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML point node.
+ */
+ point: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:Point");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a GML multipoint.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multipoint node.
+ */
+ multipoint: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
+ var points = geometry.components;
+ var pointMember, pointGeom;
+ for(var i=0; i<points.length; i++) {
+ pointMember = this.createElementNS(this.gmlns,
+ "gml:pointMember");
+ pointGeom = this.buildGeometry.point.apply(this,
+ [points[i]]);
+ pointMember.appendChild(pointGeom);
+ gml.appendChild(pointMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a GML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML linestring node.
+ */
+ linestring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:LineString");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a GML
+ * multilinestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
+ * geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multilinestring node.
+ */
+ multilinestring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
+ var lines = geometry.components;
+ var lineMember, lineGeom;
+ for(var i=0; i<lines.length; ++i) {
+ lineMember = this.createElementNS(this.gmlns,
+ "gml:lineStringMember");
+ lineGeom = this.buildGeometry.linestring.apply(this,
+ [lines[i]]);
+ lineMember.appendChild(lineGeom);
+ gml.appendChild(lineMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a GML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML linearring node.
+ */
+ linearring: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
+ gml.appendChild(this.buildCoordinatesNode(geometry));
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a GML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML polygon node.
+ */
+ polygon: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0; i<rings.length; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.gmlns,
+ "gml:" + type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ gml.appendChild(ringMember);
+ }
+ return gml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
+ * geometry.
+ *
+ * Returns:
+ * {DOMElement} A GML multipolygon node.
+ */
+ multipolygon: function(geometry) {
+ var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
+ var polys = geometry.components;
+ var polyMember, polyGeom;
+ for(var i=0; i<polys.length; ++i) {
+ polyMember = this.createElementNS(this.gmlns,
+ "gml:polygonMember");
+ polyGeom = this.buildGeometry.polygon.apply(this,
+ [polys[i]]);
+ polyMember.appendChild(polyGeom);
+ gml.appendChild(polyMember);
+ }
+ return gml;
+
+ },
+
+ /**
+ * Method: buildGeometry.bounds
+ * Given an OpenLayers bounds, create a GML box.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
+ *
+ * Returns:
+ * {DOMElement} A GML box node.
+ */
+ bounds: function(bounds) {
+ var gml = this.createElementNS(this.gmlns, "gml:Box");
+ gml.appendChild(this.buildCoordinatesNode(bounds));
+ return gml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinates
+ * builds the coordinates XmlNode
+ * (code)
+ * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
+ * (end)
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {XmlNode} created xmlNode
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.gmlns,
+ "gml:coordinates");
+ coordinatesNode.setAttribute("decimal", ".");
+ coordinatesNode.setAttribute("cs", ",");
+ coordinatesNode.setAttribute("ts", " ");
+
+ var parts = [];
+
+ if(geometry instanceof OpenLayers.Bounds){
+ parts.push(geometry.left + "," + geometry.bottom);
+ parts.push(geometry.right + "," + geometry.top);
+ } else {
+ var points = (geometry.components) ? geometry.components : [geometry];
+ for(var i=0; i<points.length; i++) {
+ parts.push(points[i].x + "," + points[i].y);
+ }
+ }
+
+ var txtNode = this.createTextNode(parts.join(" "));
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GML/Base.js b/misc/openlayers/lib/OpenLayers/Format/GML/Base.js
new file mode 100644
index 0000000..6c49969
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GML/Base.js
@@ -0,0 +1,645 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML.js
+ */
+
+/**
+ * Though required in the full build, if the GML format is excluded, we set
+ * the namespace here.
+ */
+if(!OpenLayers.Format.GML) {
+ OpenLayers.Format.GML = {};
+}
+
+/**
+ * Class: OpenLayers.Format.GML.Base
+ * Superclass for GML parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "gml",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * APIProperty: featureType
+ * {Array(String) or String} The local (without prefix) feature typeName(s).
+ */
+ featureType: null,
+
+ /**
+ * APIProperty: featureNS
+ * {String} The feature namespace. Must be set in the options at
+ * construction.
+ */
+ featureNS: null,
+
+ /**
+ * APIProperty: geometry
+ * {String} Name of geometry element. Defaults to "geometry". If null, it
+ * will be set on <read> when the first geometry is parsed.
+ */
+ geometryName: "geometry",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: srsName
+ * {String} URI for spatial reference system. This is optional for
+ * single part geometries and mandatory for collections and multis.
+ * If set, the srsName attribute will be written for all geometries.
+ * Default is null.
+ */
+ srsName: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: geometryTypes
+ * {Object} Maps OpenLayers geometry class names to GML element names.
+ * Use <setGeometryTypes> before accessing this property.
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: singleFeatureType
+ * {Boolean} True if there is only 1 featureType, and not an array
+ * of featuretypes.
+ */
+ singleFeatureType: null,
+
+ /**
+ * Property: autoConfig
+ * {Boolean} Indicates if the format was configured without a <featureNS>,
+ * but auto-configured <featureNS> and <featureType> during read.
+ * Subclasses making use of <featureType> auto-configuration should make
+ * the first call to the <readNode> method (usually in the read method)
+ * with true as 3rd argument, so the auto-configured featureType can be
+ * reset and the format can be reused for subsequent reads with data from
+ * different featureTypes. Set to false after read if you want to keep the
+ * auto-configured values.
+ */
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g),
+ featureMember: (/^(.*:)?featureMembers?$/)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.GML.Base
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.GML.v2> or <OpenLayers.Format.GML.v3> constructor
+ * instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {Array(String) or String} Local (without prefix) feature
+ * typeName(s) (required for write).
+ * featureNS - {String} Feature namespace (required for write).
+ * geometryName - {String} Geometry element name (required for write).
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.setGeometryTypes();
+ if(options && options.featureNS) {
+ this.setNamespace("feature", options.featureNS);
+ }
+ this.singleFeatureType = !options || (typeof options.featureType === "string");
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} A gml:featureMember element, a gml:featureMembers
+ * element, or an element containing either of the above at any level.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var features = [];
+ this.readNode(data, {features: features}, true);
+ if(features.length == 0) {
+ // look for gml:featureMember elements
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMember"
+ );
+ if(elements.length) {
+ for(var i=0, len=elements.length; i<len; ++i) {
+ this.readNode(elements[i], {features: features}, true);
+ }
+ } else {
+ // look for gml:featureMembers elements (this is v3, but does no harm here)
+ var elements = this.getElementsByTagNameNS(
+ data, this.namespaces.gml, "featureMembers"
+ );
+ if(elements.length) {
+ // there can be only one
+ this.readNode(elements[0], {features: features}, true);
+ }
+ }
+ }
+ return features;
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // on subsequent calls of format.read(), we want to reset auto-
+ // configured properties and auto-configure again.
+ if (first === true && this.autoConfig === true) {
+ this.featureType = null;
+ delete this.namespaceAlias[this.featureNS];
+ delete this.namespaces["feature"];
+ this.featureNS = null;
+ }
+ // featureType auto-configuration
+ if (!this.featureNS && (!(node.prefix in this.namespaces) &&
+ node.parentNode.namespaceURI == this.namespaces["gml"] &&
+ this.regExes.featureMember.test(node.parentNode.nodeName))) {
+ this.featureType = node.nodeName.split(":").pop();
+ this.setNamespace("feature", node.namespaceURI);
+ this.featureNS = node.namespaceURI;
+ this.autoConfig = true;
+ }
+ return OpenLayers.Format.XML.prototype.readNode.apply(this, [node, obj]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": {
+ "_inherit": function(node, obj, container) {
+ // To be implemented by version specific parsers
+ },
+ "featureMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "featureMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "boundedBy": function(node, obj) {
+ var container = {};
+ this.readChildNodes(node, container);
+ if(container.components && container.components.length > 0) {
+ obj.bounds = container.components[0];
+ }
+ },
+ "Point": function(node, container) {
+ var obj = {points: []};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(obj.points[0]);
+ },
+ "coordinates": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ var coords;
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ if (this.xy) {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[0], coords[1], coords[2]
+ );
+ } else {
+ points[i] = new OpenLayers.Geometry.Point(
+ coords[1], coords[0], coords[2]
+ );
+ }
+ }
+ obj.points = points;
+ },
+ "coord": function(node, obj) {
+ var coord = {};
+ this.readChildNodes(node, coord);
+ if(!obj.points) {
+ obj.points = [];
+ }
+ obj.points.push(new OpenLayers.Geometry.Point(
+ coord.x, coord.y, coord.z
+ ));
+ },
+ "X": function(node, coord) {
+ coord.x = this.getChildValue(node);
+ },
+ "Y": function(node, coord) {
+ coord.y = this.getChildValue(node);
+ },
+ "Z": function(node, coord) {
+ coord.z = this.getChildValue(node);
+ },
+ "MultiPoint": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPoint(obj.components)
+ ];
+ },
+ "pointMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LineString": function(node, container) {
+ var obj = {};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.LineString(obj.points)
+ );
+ },
+ "MultiLineString": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiLineString(obj.components)
+ ];
+ },
+ "lineStringMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Polygon": function(node, container) {
+ var obj = {outer: null, inner: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ obj.inner.unshift(obj.outer);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.Polygon(obj.inner)
+ );
+ },
+ "LinearRing": function(node, obj) {
+ var container = {};
+ this.readers.gml._inherit.apply(this, [node, container]);
+ this.readChildNodes(node, container);
+ obj.components = [new OpenLayers.Geometry.LinearRing(
+ container.points
+ )];
+ },
+ "MultiPolygon": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.MultiPolygon(obj.components)
+ ];
+ },
+ "polygonMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "GeometryCollection": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ container.components = [
+ new OpenLayers.Geometry.Collection(obj.components)
+ ];
+ },
+ "geometryMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ },
+ "feature": {
+ "*": function(node, obj) {
+ // The node can either be named like the featureType, or it
+ // can be a child of the feature:featureType. Children can be
+ // geometry or attributes.
+ var name;
+ var local = node.localName || node.nodeName.split(":").pop();
+ // Since an attribute can have the same name as the feature type
+ // we only want to read the node as a feature if the parent
+ // node can have feature nodes as children. In this case, the
+ // obj.features property is set.
+ if (obj.features) {
+ if (!this.singleFeatureType &&
+ (OpenLayers.Util.indexOf(this.featureType, local) !== -1)) {
+ name = "_typeName";
+ } else if(local === this.featureType) {
+ name = "_typeName";
+ }
+ } else {
+ // Assume attribute elements have one child node and that the child
+ // is a text node. Otherwise assume it is a geometry node.
+ if(node.childNodes.length == 0 ||
+ (node.childNodes.length == 1 && node.firstChild.nodeType == 3)) {
+ if(this.extractAttributes) {
+ name = "_attribute";
+ }
+ } else {
+ name = "_geometry";
+ }
+ }
+ if(name) {
+ this.readers.feature[name].apply(this, [node, obj]);
+ }
+ },
+ "_typeName": function(node, obj) {
+ var container = {components: [], attributes: {}};
+ this.readChildNodes(node, container);
+ // look for common gml namespaced elements
+ if(container.name) {
+ container.attributes.name = container.name;
+ }
+ var feature = new OpenLayers.Feature.Vector(
+ container.components[0], container.attributes
+ );
+ if (!this.singleFeatureType) {
+ feature.type = node.nodeName.split(":").pop();
+ feature.namespace = node.namespaceURI;
+ }
+ var fid = node.getAttribute("fid") ||
+ this.getAttributeNS(node, this.namespaces["gml"], "id");
+ if(fid) {
+ feature.fid = fid;
+ }
+ if(this.internalProjection && this.externalProjection &&
+ feature.geometry) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if(container.bounds) {
+ feature.bounds = container.bounds;
+ }
+ obj.features.push(feature);
+ },
+ "_geometry": function(node, obj) {
+ if (!this.geometryName) {
+ this.geometryName = node.nodeName.split(":").pop();
+ }
+ this.readChildNodes(node, obj);
+ },
+ "_attribute": function(node, obj) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var value = this.getChildValue(node);
+ obj.attributes[local] = value;
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ name = "featureMembers";
+ } else {
+ name = "featureMember";
+ }
+ var root = this.writeNode("gml:" + name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": {
+ "featureMember": function(feature) {
+ var node = this.createElementNSPlus("gml:featureMember");
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "MultiPoint": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPoint");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("pointMember", components[i], node);
+ }
+ return node;
+ },
+ "pointMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:pointMember");
+ this.writeNode("Point", geometry, node);
+ return node;
+ },
+ "MultiLineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiLineString");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode("lineStringMember", components[i], node);
+ }
+ return node;
+ },
+ "lineStringMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:lineStringMember");
+ this.writeNode("LineString", geometry, node);
+ return node;
+ },
+ "MultiPolygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiPolygon");
+ var components = geometry.components || [geometry];
+ for(var i=0, ii=components.length; i<ii; ++i) {
+ this.writeNode(
+ "polygonMember", components[i], node
+ );
+ }
+ return node;
+ },
+ "polygonMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:polygonMember");
+ this.writeNode("Polygon", geometry, node);
+ return node;
+ },
+ "GeometryCollection": function(geometry) {
+ var node = this.createElementNSPlus("gml:GeometryCollection");
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ this.writeNode("geometryMember", geometry.components[i], node);
+ }
+ return node;
+ },
+ "geometryMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:geometryMember");
+ var child = this.writeNode("feature:_geometry", geometry);
+ node.appendChild(child.firstChild);
+ return node;
+ }
+ },
+ "feature": {
+ "_typeName": function(feature) {
+ var node = this.createElementNSPlus("feature:" + this.featureType, {
+ attributes: {fid: feature.fid}
+ });
+ if(feature.geometry) {
+ this.writeNode("feature:_geometry", feature.geometry, node);
+ }
+ for(var name in feature.attributes) {
+ var value = feature.attributes[name];
+ if(value != null) {
+ this.writeNode(
+ "feature:_attribute",
+ {name: name, value: value}, node
+ );
+ }
+ }
+ return node;
+ },
+ "_geometry": function(geometry) {
+ if(this.externalProjection && this.internalProjection) {
+ geometry = geometry.clone().transform(
+ this.internalProjection, this.externalProjection
+ );
+ }
+ var node = this.createElementNSPlus(
+ "feature:" + this.geometryName
+ );
+ var type = this.geometryTypes[geometry.CLASS_NAME];
+ var child = this.writeNode("gml:" + type, geometry, node);
+ if(this.srsName) {
+ child.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ },
+ "_attribute": function(obj) {
+ return this.createElementNSPlus("feature:" + obj.name, {
+ value: obj.value
+ });
+ }
+ },
+ "wfs": {
+ "FeatureCollection": function(features) {
+ /**
+ * This is only here because GML2 only describes abstract
+ * feature collections. Typically, you would not be using
+ * the GML format to write wfs elements. This just provides
+ * some way to write out lists of features. GML3 defines the
+ * featureMembers element, so that is used by default instead.
+ */
+ var node = this.createElementNSPlus("wfs:FeatureCollection");
+ for(var i=0, len=features.length; i<len; ++i) {
+ this.writeNode("gml:featureMember", features[i], node);
+ }
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: setGeometryTypes
+ * Sets the <geometryTypes> mapping.
+ */
+ setGeometryTypes: function() {
+ this.geometryTypes = {
+ "OpenLayers.Geometry.Point": "Point",
+ "OpenLayers.Geometry.MultiPoint": "MultiPoint",
+ "OpenLayers.Geometry.LineString": "LineString",
+ "OpenLayers.Geometry.MultiLineString": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": "Polygon",
+ "OpenLayers.Geometry.MultiPolygon": "MultiPolygon",
+ "OpenLayers.Geometry.Collection": "GeometryCollection"
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.Base"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GML/v2.js b/misc/openlayers/lib/OpenLayers/Format/GML/v2.js
new file mode 100644
index 0000000..bd26b99
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GML/v2.js
@@ -0,0 +1,193 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/Base.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML.v2
+ * Parses GML version 2.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.Base>
+ */
+OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.GML.v2
+ * Create a parser for GML v2.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required).
+ * geometryName - {String} Geometry element name.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "outerBoundaryIs": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.outer = obj.components[0];
+ },
+ "innerBoundaryIs": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.inner.push(obj.components[0]);
+ },
+ "Box": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ var min = obj.points[0];
+ var max = obj.points[1];
+ container.components.push(
+ new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
+ );
+ }
+ }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ // GML2 only has abstract feature collections
+ // wfs provides a feature collection from a well-known schema
+ name = "wfs:FeatureCollection";
+ } else {
+ name = "gml:featureMember";
+ }
+ var root = this.writeNode(name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "Point": function(geometry) {
+ var node = this.createElementNSPlus("gml:Point");
+ this.writeNode("coordinates", [geometry], node);
+ return node;
+ },
+ "coordinates": function(points) {
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ var point;
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ if(this.xy) {
+ parts[i] = point.x + "," + point.y;
+ } else {
+ parts[i] = point.y + "," + point.x;
+ }
+ if(point.z != undefined) { // allow null or undefined
+ parts[i] += "," + point.z;
+ }
+ }
+ return this.createElementNSPlus("gml:coordinates", {
+ attributes: {
+ decimal: ".", cs: ",", ts: " "
+ },
+ value: (numPoints == 1) ? parts[0] : parts.join(" ")
+ });
+ },
+ "LineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineString");
+ this.writeNode("coordinates", geometry.components, node);
+ return node;
+ },
+ "Polygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:Polygon");
+ this.writeNode("outerBoundaryIs", geometry.components[0], node);
+ for(var i=1; i<geometry.components.length; ++i) {
+ this.writeNode(
+ "innerBoundaryIs", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "outerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:outerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "innerBoundaryIs": function(ring) {
+ var node = this.createElementNSPlus("gml:innerBoundaryIs");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "LinearRing": function(ring) {
+ var node = this.createElementNSPlus("gml:LinearRing");
+ this.writeNode("coordinates", ring.components, node);
+ return node;
+ },
+ "Box": function(bounds) {
+ var node = this.createElementNSPlus("gml:Box");
+ this.writeNode("coordinates", [
+ {x: bounds.left, y: bounds.bottom},
+ {x: bounds.right, y: bounds.top}
+ ], node);
+ // srsName attribute is optional for gml:Box
+ if(this.srsName) {
+ node.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.v2"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GML/v3.js b/misc/openlayers/lib/OpenLayers/Format/GML/v3.js
new file mode 100644
index 0000000..82c7b1e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GML/v3.js
@@ -0,0 +1,477 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML/Base.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML.v3
+ * Parses GML version 3.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML.Base>
+ */
+OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, {
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version. The writers
+ * conform with the Simple Features Profile for GML.
+ */
+ schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd",
+
+ /**
+ * Property: curve
+ * {Boolean} Write gml:Curve instead of gml:LineString elements. This also
+ * affects the elements in multi-part geometries. Default is false.
+ * To write gml:Curve elements instead of gml:LineString, set curve
+ * to true in the options to the contstructor (cannot be changed after
+ * instantiation).
+ */
+ curve: false,
+
+ /**
+ * Property: multiCurve
+ * {Boolean} Write gml:MultiCurve instead of gml:MultiLineString. Since
+ * the latter is deprecated in GML 3, the default is true. To write
+ * gml:MultiLineString instead of gml:MultiCurve, set multiCurve to
+ * false in the options to the constructor (cannot be changed after
+ * instantiation).
+ */
+ multiCurve: true,
+
+ /**
+ * Property: surface
+ * {Boolean} Write gml:Surface instead of gml:Polygon elements. This also
+ * affects the elements in multi-part geometries. Default is false.
+ * To write gml:Surface elements instead of gml:Polygon, set surface
+ * to true in the options to the contstructor (cannot be changed after
+ * instantiation).
+ */
+ surface: false,
+
+ /**
+ * Property: multiSurface
+ * {Boolean} Write gml:multiSurface instead of gml:MultiPolygon. Since
+ * the latter is deprecated in GML 3, the default is true. To write
+ * gml:MultiPolygon instead of gml:multiSurface, set multiSurface to
+ * false in the options to the constructor (cannot be changed after
+ * instantiation).
+ */
+ multiSurface: true,
+
+ /**
+ * Constructor: OpenLayers.Format.GML.v3
+ * Create a parser for GML v3.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required).
+ * geometryName - {String} Geometry element name.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "_inherit": function(node, obj, container) {
+ // SRSReferenceGroup attributes
+ var dim = parseInt(node.getAttribute("srsDimension"), 10) ||
+ (container && container.srsDimension);
+ if (dim) {
+ obj.srsDimension = dim;
+ }
+ },
+ "featureMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Curve": function(node, container) {
+ var obj = {points: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ container.components.push(
+ new OpenLayers.Geometry.LineString(obj.points)
+ );
+ },
+ "segments": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LineStringSegment": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ if(obj.points) {
+ Array.prototype.push.apply(container.points, obj.points);
+ }
+ },
+ "pos": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ var coords = str.split(this.regExes.splitSpace);
+ var point;
+ if(this.xy) {
+ point = new OpenLayers.Geometry.Point(
+ coords[0], coords[1], coords[2]
+ );
+ } else {
+ point = new OpenLayers.Geometry.Point(
+ coords[1], coords[0], coords[2]
+ );
+ }
+ obj.points = [point];
+ },
+ "posList": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, ""
+ );
+ var coords = str.split(this.regExes.splitSpace);
+ // The "dimension" attribute is from the GML 3.0.1 spec.
+ var dim = obj.srsDimension ||
+ parseInt(node.getAttribute("srsDimension") || node.getAttribute("dimension"), 10) || 2;
+ var j, x, y, z;
+ var numPoints = coords.length / dim;
+ var points = new Array(numPoints);
+ for(var i=0, len=coords.length; i<len; i += dim) {
+ x = coords[i];
+ y = coords[i+1];
+ z = (dim == 2) ? undefined : coords[i+2];
+ if (this.xy) {
+ points[i/dim] = new OpenLayers.Geometry.Point(x, y, z);
+ } else {
+ points[i/dim] = new OpenLayers.Geometry.Point(y, x, z);
+ }
+ }
+ obj.points = points;
+ },
+ "Surface": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "patches": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "PolygonPatch": function(node, obj) {
+ this.readers.gml.Polygon.apply(this, [node, obj]);
+ },
+ "exterior": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.outer = obj.components[0];
+ },
+ "interior": function(node, container) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ container.inner.push(obj.components[0]);
+ },
+ "MultiCurve": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(obj.components.length > 0) {
+ container.components = [
+ new OpenLayers.Geometry.MultiLineString(obj.components)
+ ];
+ }
+ },
+ "curveMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "MultiSurface": function(node, container) {
+ var obj = {components: []};
+ this.readers.gml._inherit.apply(this, [node, obj, container]);
+ this.readChildNodes(node, obj);
+ if(obj.components.length > 0) {
+ container.components = [
+ new OpenLayers.Geometry.MultiPolygon(obj.components)
+ ];
+ }
+ },
+ "surfaceMember": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "surfaceMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "pointMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "lineStringMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "polygonMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "geometryMembers": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Envelope": function(node, container) {
+ var obj = {points: new Array(2)};
+ this.readChildNodes(node, obj);
+ if(!container.components) {
+ container.components = [];
+ }
+ var min = obj.points[0];
+ var max = obj.points[1];
+ container.components.push(
+ new OpenLayers.Bounds(min.x, min.y, max.x, max.y)
+ );
+ },
+ "lowerCorner": function(node, container) {
+ var obj = {};
+ this.readers.gml.pos.apply(this, [node, obj]);
+ container.points[0] = obj.points[0];
+ },
+ "upperCorner": function(node, container) {
+ var obj = {};
+ this.readers.gml.pos.apply(this, [node, obj]);
+ container.points[1] = obj.points[0];
+ }
+ }, OpenLayers.Format.GML.Base.prototype.readers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"]
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>) | OpenLayers.Feature.Vector}
+ * An array of features or a single feature.
+ *
+ * Returns:
+ * {String} Given an array of features, a doc with a gml:featureMembers
+ * element will be returned. Given a single feature, a doc with a
+ * gml:featureMember element will be returned.
+ */
+ write: function(features) {
+ var name;
+ if(OpenLayers.Util.isArray(features)) {
+ name = "featureMembers";
+ } else {
+ name = "featureMember";
+ }
+ var root = this.writeNode("gml:" + name, features);
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "featureMembers": function(features) {
+ var node = this.createElementNSPlus("gml:featureMembers");
+ for(var i=0, len=features.length; i<len; ++i) {
+ this.writeNode("feature:_typeName", features[i], node);
+ }
+ return node;
+ },
+ "Point": function(geometry) {
+ var node = this.createElementNSPlus("gml:Point");
+ this.writeNode("pos", geometry, node);
+ return node;
+ },
+ "pos": function(point) {
+ // only 2d for simple features profile
+ var pos = (this.xy) ?
+ (point.x + " " + point.y) : (point.y + " " + point.x);
+ return this.createElementNSPlus("gml:pos", {
+ value: pos
+ });
+ },
+ "LineString": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineString");
+ this.writeNode("posList", geometry.components, node);
+ return node;
+ },
+ "Curve": function(geometry) {
+ var node = this.createElementNSPlus("gml:Curve");
+ this.writeNode("segments", geometry, node);
+ return node;
+ },
+ "segments": function(geometry) {
+ var node = this.createElementNSPlus("gml:segments");
+ this.writeNode("LineStringSegment", geometry, node);
+ return node;
+ },
+ "LineStringSegment": function(geometry) {
+ var node = this.createElementNSPlus("gml:LineStringSegment");
+ this.writeNode("posList", geometry.components, node);
+ return node;
+ },
+ "posList": function(points) {
+ // only 2d for simple features profile
+ var len = points.length;
+ var parts = new Array(len);
+ var point;
+ for(var i=0; i<len; ++i) {
+ point = points[i];
+ if(this.xy) {
+ parts[i] = point.x + " " + point.y;
+ } else {
+ parts[i] = point.y + " " + point.x;
+ }
+ }
+ return this.createElementNSPlus("gml:posList", {
+ value: parts.join(" ")
+ });
+ },
+ "Surface": function(geometry) {
+ var node = this.createElementNSPlus("gml:Surface");
+ this.writeNode("patches", geometry, node);
+ return node;
+ },
+ "patches": function(geometry) {
+ var node = this.createElementNSPlus("gml:patches");
+ this.writeNode("PolygonPatch", geometry, node);
+ return node;
+ },
+ "PolygonPatch": function(geometry) {
+ var node = this.createElementNSPlus("gml:PolygonPatch", {
+ attributes: {interpolation: "planar"}
+ });
+ this.writeNode("exterior", geometry.components[0], node);
+ for(var i=1, len=geometry.components.length; i<len; ++i) {
+ this.writeNode(
+ "interior", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "Polygon": function(geometry) {
+ var node = this.createElementNSPlus("gml:Polygon");
+ this.writeNode("exterior", geometry.components[0], node);
+ for(var i=1, len=geometry.components.length; i<len; ++i) {
+ this.writeNode(
+ "interior", geometry.components[i], node
+ );
+ }
+ return node;
+ },
+ "exterior": function(ring) {
+ var node = this.createElementNSPlus("gml:exterior");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "interior": function(ring) {
+ var node = this.createElementNSPlus("gml:interior");
+ this.writeNode("LinearRing", ring, node);
+ return node;
+ },
+ "LinearRing": function(ring) {
+ var node = this.createElementNSPlus("gml:LinearRing");
+ this.writeNode("posList", ring.components, node);
+ return node;
+ },
+ "MultiCurve": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiCurve");
+ var components = geometry.components || [geometry];
+ for(var i=0, len=components.length; i<len; ++i) {
+ this.writeNode("curveMember", components[i], node);
+ }
+ return node;
+ },
+ "curveMember": function(geometry) {
+ var node = this.createElementNSPlus("gml:curveMember");
+ if(this.curve) {
+ this.writeNode("Curve", geometry, node);
+ } else {
+ this.writeNode("LineString", geometry, node);
+ }
+ return node;
+ },
+ "MultiSurface": function(geometry) {
+ var node = this.createElementNSPlus("gml:MultiSurface");
+ var components = geometry.components || [geometry];
+ for(var i=0, len=components.length; i<len; ++i) {
+ this.writeNode("surfaceMember", components[i], node);
+ }
+ return node;
+ },
+ "surfaceMember": function(polygon) {
+ var node = this.createElementNSPlus("gml:surfaceMember");
+ if(this.surface) {
+ this.writeNode("Surface", polygon, node);
+ } else {
+ this.writeNode("Polygon", polygon, node);
+ }
+ return node;
+ },
+ "Envelope": function(bounds) {
+ var node = this.createElementNSPlus("gml:Envelope");
+ this.writeNode("lowerCorner", bounds, node);
+ this.writeNode("upperCorner", bounds, node);
+ // srsName attribute is required for gml:Envelope
+ if(this.srsName) {
+ node.setAttribute("srsName", this.srsName);
+ }
+ return node;
+ },
+ "lowerCorner": function(bounds) {
+ // only 2d for simple features profile
+ var pos = (this.xy) ?
+ (bounds.left + " " + bounds.bottom) :
+ (bounds.bottom + " " + bounds.left);
+ return this.createElementNSPlus("gml:lowerCorner", {
+ value: pos
+ });
+ },
+ "upperCorner": function(bounds) {
+ // only 2d for simple features profile
+ var pos = (this.xy) ?
+ (bounds.right + " " + bounds.top) :
+ (bounds.top + " " + bounds.right);
+ return this.createElementNSPlus("gml:upperCorner", {
+ value: pos
+ });
+ }
+ }, OpenLayers.Format.GML.Base.prototype.writers["gml"]),
+ "feature": OpenLayers.Format.GML.Base.prototype.writers["feature"],
+ "wfs": OpenLayers.Format.GML.Base.prototype.writers["wfs"]
+ },
+
+ /**
+ * Method: setGeometryTypes
+ * Sets the <geometryTypes> mapping.
+ */
+ setGeometryTypes: function() {
+ this.geometryTypes = {
+ "OpenLayers.Geometry.Point": "Point",
+ "OpenLayers.Geometry.MultiPoint": "MultiPoint",
+ "OpenLayers.Geometry.LineString": (this.curve === true) ? "Curve": "LineString",
+ "OpenLayers.Geometry.MultiLineString": (this.multiCurve === false) ? "MultiLineString" : "MultiCurve",
+ "OpenLayers.Geometry.Polygon": (this.surface === true) ? "Surface" : "Polygon",
+ "OpenLayers.Geometry.MultiPolygon": (this.multiSurface === false) ? "MultiPolygon" : "MultiSurface",
+ "OpenLayers.Geometry.Collection": "GeometryCollection"
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GML.v3"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GPX.js b/misc/openlayers/lib/OpenLayers/Format/GPX.js
new file mode 100644
index 0000000..16a8056
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GPX.js
@@ -0,0 +1,385 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GPX
+ * Read/write GPX parser. Create a new instance with the
+ * <OpenLayers.Format.GPX> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GPX = OpenLayers.Class(OpenLayers.Format.XML, {
+
+
+ /**
+ * APIProperty: defaultDesc
+ * {String} Default description for the waypoints/tracks in the case
+ * where the feature has no "description" attribute.
+ * Default is "No description available".
+ */
+ defaultDesc: "No description available",
+
+ /**
+ * APIProperty: extractWaypoints
+ * {Boolean} Extract waypoints from GPX. (default: true)
+ */
+ extractWaypoints: true,
+
+ /**
+ * APIProperty: extractTracks
+ * {Boolean} Extract tracks from GPX. (default: true)
+ */
+ extractTracks: true,
+
+ /**
+ * APIProperty: extractRoutes
+ * {Boolean} Extract routes from GPX. (default: true)
+ */
+ extractRoutes: true,
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract feature attributes from GPX. (default: true)
+ * NOTE: Attributes as part of extensions to the GPX standard may not
+ * be extracted.
+ */
+ extractAttributes: true,
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ gpx: "http://www.topografix.com/GPX/1/1",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location. Defaults to
+ * "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
+ */
+ schemaLocation: "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd",
+
+ /**
+ * APIProperty: creator
+ * {String} The creator attribute to be added to the written GPX files.
+ * Defaults to "OpenLayers"
+ */
+ creator: "OpenLayers",
+
+ /**
+ * Constructor: OpenLayers.Format.GPX
+ * Create a new parser for GPX.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // GPX coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a GPX doc
+ *
+ * Parameters:
+ * doc - {Element}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+ var features = [];
+
+ if(this.extractTracks) {
+ var tracks = doc.getElementsByTagName("trk");
+ for (var i=0, len=tracks.length; i<len; i++) {
+ // Attributes are only in trk nodes, not trkseg nodes
+ var attrs = {};
+ if(this.extractAttributes) {
+ attrs = this.parseAttributes(tracks[i]);
+ }
+
+ var segs = this.getElementsByTagNameNS(tracks[i], tracks[i].namespaceURI, "trkseg");
+ for (var j = 0, seglen = segs.length; j < seglen; j++) {
+ // We don't yet support extraction of trkpt attributes
+ // All trksegs of a trk get that trk's attributes
+ var track = this.extractSegment(segs[j], "trkpt");
+ features.push(new OpenLayers.Feature.Vector(track, attrs));
+ }
+ }
+ }
+
+ if(this.extractRoutes) {
+ var routes = doc.getElementsByTagName("rte");
+ for (var k=0, klen=routes.length; k<klen; k++) {
+ var attrs = {};
+ if(this.extractAttributes) {
+ attrs = this.parseAttributes(routes[k]);
+ }
+ var route = this.extractSegment(routes[k], "rtept");
+ features.push(new OpenLayers.Feature.Vector(route, attrs));
+ }
+ }
+
+ if(this.extractWaypoints) {
+ var waypoints = doc.getElementsByTagName("wpt");
+ for (var l = 0, len = waypoints.length; l < len; l++) {
+ var attrs = {};
+ if(this.extractAttributes) {
+ attrs = this.parseAttributes(waypoints[l]);
+ }
+ var wpt = new OpenLayers.Geometry.Point(waypoints[l].getAttribute("lon"), waypoints[l].getAttribute("lat"));
+ features.push(new OpenLayers.Feature.Vector(wpt, attrs));
+ }
+ }
+
+ if (this.internalProjection && this.externalProjection) {
+ for (var g = 0, featLength = features.length; g < featLength; g++) {
+ features[g].geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ }
+
+ return features;
+ },
+
+ /**
+ * Method: extractSegment
+ *
+ * Parameters:
+ * segment - {DOMElement} a trkseg or rte node to parse
+ * segmentType - {String} nodeName of waypoints that form the line
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry
+ */
+ extractSegment: function(segment, segmentType) {
+ var points = this.getElementsByTagNameNS(segment, segment.namespaceURI, segmentType);
+ var point_features = [];
+ for (var i = 0, len = points.length; i < len; i++) {
+ point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"), points[i].getAttribute("lat")));
+ }
+ return new OpenLayers.Geometry.LineString(point_features);
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {<DOMElement>}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ // node is either a wpt, trk or rte
+ // attributes are children of the form <attr>value</attr>
+ var attributes = {};
+ var attrNode = node.firstChild, value, name;
+ while(attrNode) {
+ if(attrNode.nodeType == 1 && attrNode.firstChild) {
+ value = attrNode.firstChild;
+ if(value.nodeType == 3 || value.nodeType == 4) {
+ name = (attrNode.prefix) ?
+ attrNode.nodeName.split(":")[1] :
+ attrNode.nodeName;
+ if(name != "trkseg" && name != "rtept") {
+ attributes[name] = value.nodeValue;
+ }
+ }
+ }
+ attrNode = attrNode.nextSibling;
+ }
+ return attributes;
+ },
+
+ /**
+ * APIMethod: write
+ * Accepts Feature Collection, and returns a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to serialize into a string.
+ * metadata - {Object} A key/value pairs object to build a metadata node to
+ * add to the gpx. Supported keys are 'name', 'desc', 'author'.
+ */
+ write: function(features, metadata) {
+ features = OpenLayers.Util.isArray(features) ?
+ features : [features];
+ var gpx = this.createElementNS(this.namespaces.gpx, "gpx");
+ gpx.setAttribute("version", "1.1");
+ gpx.setAttribute("creator", this.creator);
+ this.setAttributes(gpx, {
+ "xsi:schemaLocation": this.schemaLocation
+ });
+
+ if (metadata && typeof metadata == 'object') {
+ gpx.appendChild(this.buildMetadataNode(metadata));
+ }
+ for(var i=0, len=features.length; i<len; i++) {
+ gpx.appendChild(this.buildFeatureNode(features[i]));
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [gpx]);
+ },
+
+ /**
+ * Method: buildMetadataNode
+ * Creates a "metadata" node.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildMetadataNode: function(metadata) {
+ var types = ['name', 'desc', 'author'],
+ node = this.createElementNS(this.namespaces.gpx, 'metadata');
+ for (var i=0; i < types.length; i++) {
+ var type = types[i];
+ if (metadata[type]) {
+ var n = this.createElementNS(this.namespaces.gpx, type);
+ n.appendChild(this.createTextNode(metadata[type]));
+ node.appendChild(n);
+ }
+ }
+ return node;
+ },
+
+ /**
+ * Method: buildFeatureNode
+ * Accepts an <OpenLayers.Feature.Vector>, and builds a node for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement} - The created node, either a 'wpt' or a 'trk'.
+ */
+ buildFeatureNode: function(feature) {
+ var geometry = feature.geometry;
+ geometry = geometry.clone();
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ var wpt = this.buildWptNode(geometry);
+ this.appendAttributesNode(wpt, feature);
+ return wpt;
+ } else {
+ var trkNode = this.createElementNS(this.namespaces.gpx, "trk");
+ this.appendAttributesNode(trkNode, feature);
+ var trkSegNodes = this.buildTrkSegNode(geometry);
+ trkSegNodes = OpenLayers.Util.isArray(trkSegNodes) ?
+ trkSegNodes : [trkSegNodes];
+ for (var i = 0, len = trkSegNodes.length; i < len; i++) {
+ trkNode.appendChild(trkSegNodes[i]);
+ }
+ return trkNode;
+ }
+ },
+
+ /**
+ * Method: buildTrkSegNode
+ * Builds trkseg node(s) given a geometry
+ *
+ * Parameters:
+ * trknode
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ buildTrkSegNode: function(geometry) {
+ var node,
+ i,
+ len,
+ point,
+ nodes;
+ if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+ geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ node = this.createElementNS(this.namespaces.gpx, "trkseg");
+ for (i = 0, len=geometry.components.length; i < len; i++) {
+ point = geometry.components[i];
+ node.appendChild(this.buildTrkPtNode(point));
+ }
+ return node;
+ } else {
+ nodes = [];
+ for (i = 0, len = geometry.components.length; i < len; i++) {
+ nodes.push(this.buildTrkSegNode(geometry.components[i]));
+ }
+ return nodes;
+ }
+ },
+
+ /**
+ * Method: buildTrkPtNode
+ * Builds a trkpt node given a point
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {DOMElement} A trkpt node
+ */
+ buildTrkPtNode: function(point) {
+ var node = this.createElementNS(this.namespaces.gpx, "trkpt");
+ node.setAttribute("lon", point.x);
+ node.setAttribute("lat", point.y);
+ return node;
+ },
+
+ /**
+ * Method: buildWptNode
+ * Builds a wpt node given a point
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {DOMElement} A wpt node
+ */
+ buildWptNode: function(geometry) {
+ var node = this.createElementNS(this.namespaces.gpx, "wpt");
+ node.setAttribute("lon", geometry.x);
+ node.setAttribute("lat", geometry.y);
+ return node;
+ },
+
+ /**
+ * Method: appendAttributesNode
+ * Adds some attributes node.
+ *
+ * Parameters:
+ * node - {DOMElement} the node to append the attribute nodes to.
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ appendAttributesNode: function(node, feature) {
+ var name = this.createElementNS(this.namespaces.gpx, 'name');
+ name.appendChild(this.createTextNode(
+ feature.attributes.name || feature.id));
+ node.appendChild(name);
+ var desc = this.createElementNS(this.namespaces.gpx, 'desc');
+ desc.appendChild(this.createTextNode(
+ feature.attributes.description || this.defaultDesc));
+ node.appendChild(desc);
+ // TBD - deal with remaining (non name/description) attributes.
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GPX"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GeoJSON.js b/misc/openlayers/lib/OpenLayers/Format/GeoJSON.js
new file mode 100644
index 0000000..0e02377
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GeoJSON.js
@@ -0,0 +1,716 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ * <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.JSON>
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+ /**
+ * APIProperty: ignoreExtraDims
+ * {Boolean} Ignore dimensions higher than 2 when reading geometry
+ * coordinates.
+ */
+ ignoreExtraDims: false,
+
+ /**
+ * Constructor: OpenLayers.Format.GeoJSON
+ * Create a new parser for GeoJSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a GeoJSON string.
+ *
+ * Parameters:
+ * json - {String} A GeoJSON string
+ * type - {String} Optional string that determines the structure of
+ * the output. Supported values are "Geometry", "Feature", and
+ * "FeatureCollection". If absent or null, a default of
+ * "FeatureCollection" is assumed.
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} The return depends on the value of the type argument. If type
+ * is "FeatureCollection" (the default), the return will be an array
+ * of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+ * must represent a single geometry, and the return will be an
+ * <OpenLayers.Geometry>. If type is "Feature", the input json must
+ * represent a single feature, and the return will be an
+ * <OpenLayers.Feature.Vector>.
+ */
+ read: function(json, type, filter) {
+ type = (type) ? type : "FeatureCollection";
+ var results = null;
+ var obj = null;
+ if (typeof json == "string") {
+ obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+ [json, filter]);
+ } else {
+ obj = json;
+ }
+ if(!obj) {
+ OpenLayers.Console.error("Bad JSON: " + json);
+ } else if(typeof(obj.type) != "string") {
+ OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+ } else if(this.isValidType(obj, type)) {
+ switch(type) {
+ case "Geometry":
+ try {
+ results = this.parseGeometry(obj);
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "Feature":
+ try {
+ results = this.parseFeature(obj);
+ results.type = "Feature";
+ } catch(err) {
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ // for type FeatureCollection, we allow input to be any type
+ results = [];
+ switch(obj.type) {
+ case "Feature":
+ try {
+ results.push(this.parseFeature(obj));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ break;
+ case "FeatureCollection":
+ for(var i=0, len=obj.features.length; i<len; ++i) {
+ try {
+ results.push(this.parseFeature(obj.features[i]));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ default:
+ try {
+ var geom = this.parseGeometry(obj);
+ results.push(new OpenLayers.Feature.Vector(geom));
+ } catch(err) {
+ results = null;
+ OpenLayers.Console.error(err);
+ }
+ }
+ break;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: isValidType
+ * Check if a GeoJSON object is a valid representative of the given type.
+ *
+ * Returns:
+ * {Boolean} The object is valid GeoJSON object of the given type.
+ */
+ isValidType: function(obj, type) {
+ var valid = false;
+ switch(type) {
+ case "Geometry":
+ if(OpenLayers.Util.indexOf(
+ ["Point", "MultiPoint", "LineString", "MultiLineString",
+ "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+ obj.type) == -1) {
+ // unsupported geometry type
+ OpenLayers.Console.error("Unsupported geometry type: " +
+ obj.type);
+ } else {
+ valid = true;
+ }
+ break;
+ case "FeatureCollection":
+ // allow for any type to be converted to a feature collection
+ valid = true;
+ break;
+ default:
+ // for Feature types must match
+ if(obj.type == type) {
+ valid = true;
+ } else {
+ OpenLayers.Console.error("Cannot convert types from " +
+ obj.type + " to " + type);
+ }
+ }
+ return valid;
+ },
+
+ /**
+ * Method: parseFeature
+ * Convert a feature object from GeoJSON into an
+ * <OpenLayers.Feature.Vector>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature.
+ */
+ parseFeature: function(obj) {
+ var feature, geometry, attributes, bbox;
+ attributes = (obj.properties) ? obj.properties : {};
+ bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox;
+ try {
+ geometry = this.parseGeometry(obj.geometry);
+ } catch(err) {
+ // deal with bad geometries
+ throw err;
+ }
+ feature = new OpenLayers.Feature.Vector(geometry, attributes);
+ if(bbox) {
+ feature.bounds = OpenLayers.Bounds.fromArray(bbox);
+ }
+ if(obj.id) {
+ feature.fid = obj.id;
+ }
+ return feature;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * obj - {Object} An object created from a GeoJSON object
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ parseGeometry: function(obj) {
+ if (obj == null) {
+ return null;
+ }
+ var geometry, collection = false;
+ if(obj.type == "GeometryCollection") {
+ if(!(OpenLayers.Util.isArray(obj.geometries))) {
+ throw "GeometryCollection must have geometries array: " + obj;
+ }
+ var numGeom = obj.geometries.length;
+ var components = new Array(numGeom);
+ for(var i=0; i<numGeom; ++i) {
+ components[i] = this.parseGeometry.apply(
+ this, [obj.geometries[i]]
+ );
+ }
+ geometry = new OpenLayers.Geometry.Collection(components);
+ collection = true;
+ } else {
+ if(!(OpenLayers.Util.isArray(obj.coordinates))) {
+ throw "Geometry must have coordinates array: " + obj;
+ }
+ if(!this.parseCoords[obj.type.toLowerCase()]) {
+ throw "Unsupported geometry type: " + obj.type;
+ }
+ try {
+ geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+ this, [obj.coordinates]
+ );
+ } catch(err) {
+ // deal with bad coordinates
+ throw err;
+ }
+ }
+ // We don't reproject collections because the children are reprojected
+ // for us when they are created.
+ if (this.internalProjection && this.externalProjection && !collection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ return geometry;
+ },
+
+ /**
+ * Property: parseCoords
+ * Object with properties corresponding to the GeoJSON geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parseCoords: {
+ /**
+ * Method: parseCoords.point
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "point": function(array) {
+ if (this.ignoreExtraDims == false &&
+ array.length != 2) {
+ throw "Only 2D points are supported: " + array;
+ }
+ return new OpenLayers.Geometry.Point(array[0], array[1]);
+ },
+
+ /**
+ * Method: parseCoords.multipoint
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipoint": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPoint(points);
+ },
+
+ /**
+ * Method: parseCoords.linestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "linestring": function(array) {
+ var points = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["point"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ points.push(p);
+ }
+ return new OpenLayers.Geometry.LineString(points);
+ },
+
+ /**
+ * Method: parseCoords.multilinestring
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multilinestring": function(array) {
+ var lines = [];
+ var l = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ lines.push(l);
+ }
+ return new OpenLayers.Geometry.MultiLineString(lines);
+ },
+
+ /**
+ * Method: parseCoords.polygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "polygon": function(array) {
+ var rings = [];
+ var r, l;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ l = this.parseCoords["linestring"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ r = new OpenLayers.Geometry.LinearRing(l.components);
+ rings.push(r);
+ }
+ return new OpenLayers.Geometry.Polygon(rings);
+ },
+
+ /**
+ * Method: parseCoords.multipolygon
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "multipolygon": function(array) {
+ var polys = [];
+ var p = null;
+ for(var i=0, len=array.length; i<len; ++i) {
+ try {
+ p = this.parseCoords["polygon"].apply(this, [array[i]]);
+ } catch(err) {
+ throw err;
+ }
+ polys.push(p);
+ }
+ return new OpenLayers.Geometry.MultiPolygon(polys);
+ },
+
+ /**
+ * Method: parseCoords.box
+ * Convert a coordinate array from GeoJSON into an
+ * <OpenLayers.Geometry>.
+ *
+ * Parameters:
+ * array - {Object} The coordinates array from the GeoJSON fragment.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry.
+ */
+ "box": function(array) {
+ if(array.length != 2) {
+ throw "GeoJSON box coordinates must have 2 elements";
+ }
+ return new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+ new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+ new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+ ])
+ ]);
+ }
+
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature, geometry, array of features into a GeoJSON string.
+ *
+ * Parameters:
+ * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+ * or an array of features.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The GeoJSON string representation of the input geometry,
+ * features, or array of features.
+ */
+ write: function(obj, pretty) {
+ var geojson = {
+ "type": null
+ };
+ if(OpenLayers.Util.isArray(obj)) {
+ geojson.type = "FeatureCollection";
+ var numFeatures = obj.length;
+ geojson.features = new Array(numFeatures);
+ for(var i=0; i<numFeatures; ++i) {
+ var element = obj[i];
+ if(!element instanceof OpenLayers.Feature.Vector) {
+ var msg = "FeatureCollection only supports collections " +
+ "of features: " + element;
+ throw msg;
+ }
+ geojson.features[i] = this.extract.feature.apply(
+ this, [element]
+ );
+ }
+ } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+ geojson = this.extract.geometry.apply(this, [obj]);
+ } else if (obj instanceof OpenLayers.Feature.Vector) {
+ geojson = this.extract.feature.apply(this, [obj]);
+ if(obj.layer && obj.layer.projection) {
+ geojson.crs = this.createCRSObject(obj);
+ }
+ }
+ return OpenLayers.Format.JSON.prototype.write.apply(this,
+ [geojson, pretty]);
+ },
+
+ /**
+ * Method: createCRSObject
+ * Create the CRS object for an object.
+ *
+ * Parameters:
+ * object - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object which can be assigned to the crs property
+ * of a GeoJSON object.
+ */
+ createCRSObject: function(object) {
+ var proj = object.layer.projection.toString();
+ var crs = {};
+ if (proj.match(/epsg:/i)) {
+ var code = parseInt(proj.substring(proj.indexOf(":") + 1));
+ if (code == 4326) {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "urn:ogc:def:crs:OGC:1.3:CRS84"
+ }
+ };
+ } else {
+ crs = {
+ "type": "name",
+ "properties": {
+ "name": "EPSG:" + code
+ }
+ };
+ }
+ }
+ return crs;
+ },
+
+ /**
+ * Property: extract
+ * Object with properties corresponding to the GeoJSON types.
+ * Property values are functions that do the actual value extraction.
+ */
+ extract: {
+ /**
+ * Method: extract.feature
+ * Return a partial GeoJSON object representing a single feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} An object representing the point.
+ */
+ 'feature': function(feature) {
+ var geom = this.extract.geometry.apply(this, [feature.geometry]);
+ var json = {
+ "type": "Feature",
+ "properties": feature.attributes,
+ "geometry": geom
+ };
+ if (feature.fid != null) {
+ json.id = feature.fid;
+ }
+ return json;
+ },
+
+ /**
+ * Method: extract.geometry
+ * Return a GeoJSON object representing a single geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Object} An object representing the geometry.
+ */
+ 'geometry': function(geometry) {
+ if (geometry == null) {
+ return null;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var geometryType = geometry.CLASS_NAME.split('.')[2];
+ var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+ var json;
+ if(geometryType == "Collection") {
+ json = {
+ "type": "GeometryCollection",
+ "geometries": data
+ };
+ } else {
+ json = {
+ "type": geometryType,
+ "coordinates": data
+ };
+ }
+
+ return json;
+ },
+
+ /**
+ * Method: extract.point
+ * Return an array of coordinates from a point.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Array} An array of coordinates representing the point.
+ */
+ 'point': function(point) {
+ return [point.x, point.y];
+ },
+
+ /**
+ * Method: extract.multipoint
+ * Return an array of point coordinates from a multipoint.
+ *
+ * Parameters:
+ * multipoint - {<OpenLayers.Geometry.MultiPoint>}
+ *
+ * Returns:
+ * {Array} An array of point coordinate arrays representing
+ * the multipoint.
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0, len=multipoint.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.linestring
+ * Return an array of coordinate arrays from a linestring.
+ *
+ * Parameters:
+ * linestring - {<OpenLayers.Geometry.LineString>}
+ *
+ * Returns:
+ * {Array} An array of coordinate arrays representing
+ * the linestring.
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multilinestring
+ * Return an array of linestring arrays from a linestring.
+ *
+ * Parameters:
+ * multilinestring - {<OpenLayers.Geometry.MultiLineString>}
+ *
+ * Returns:
+ * {Array} An array of linestring arrays representing
+ * the multilinestring.
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.polygon
+ * Return an array of linear ring arrays from a polygon.
+ *
+ * Parameters:
+ * polygon - {<OpenLayers.Geometry.Polygon>}
+ *
+ * Returns:
+ * {Array} An array of linear ring arrays representing the polygon.
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0, len=polygon.components.length; i<len; ++i) {
+ array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.multipolygon
+ * Return an array of polygon arrays from a multipolygon.
+ *
+ * Parameters:
+ * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+ *
+ * Returns:
+ * {Array} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+ }
+ return array;
+ },
+
+ /**
+ * Method: extract.collection
+ * Return an array of geometries from a geometry collection.
+ *
+ * Parameters:
+ * collection - {<OpenLayers.Geometry.Collection>}
+ *
+ * Returns:
+ * {Array} An array of geometry objects representing the geometry
+ * collection.
+ */
+ 'collection': function(collection) {
+ var len = collection.components.length;
+ var array = new Array(len);
+ for(var i=0; i<len; ++i) {
+ array[i] = this.extract.geometry.apply(
+ this, [collection.components[i]]
+ );
+ }
+ return array;
+ }
+
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoJSON"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/GeoRSS.js b/misc/openlayers/lib/OpenLayers/Format/GeoRSS.js
new file mode 100644
index 0000000..cbbb4d8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/GeoRSS.js
@@ -0,0 +1,409 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoRSS
+ * Read/write GeoRSS parser. Create a new instance with the
+ * <OpenLayers.Format.GeoRSS> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GeoRSS = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: rssns
+ * {String} RSS namespace to use. Defaults to
+ * "http://backend.userland.com/rss2"
+ */
+ rssns: "http://backend.userland.com/rss2",
+
+ /**
+ * APIProperty: featurens
+ * {String} Feature Attributes namespace. Defaults to
+ * "http://mapserver.gis.umn.edu/mapserver"
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * APIProperty: georssns
+ * {String} GeoRSS namespace to use. Defaults to
+ * "http://www.georss.org/georss"
+ */
+ georssns: "http://www.georss.org/georss",
+
+ /**
+ * APIProperty: geons
+ * {String} W3C Geo namespace to use. Defaults to
+ * "http://www.w3.org/2003/01/geo/wgs84_pos#"
+ */
+ geons: "http://www.w3.org/2003/01/geo/wgs84_pos#",
+
+ /**
+ * APIProperty: featureTitle
+ * {String} Default title for features. Defaults to "Untitled"
+ */
+ featureTitle: "Untitled",
+
+ /**
+ * APIProperty: featureDescription
+ * {String} Default description for features. Defaults to "No Description"
+ */
+ featureDescription: "No Description",
+
+ /**
+ * Property: gmlParse
+ * {Object} GML Format object for parsing features
+ * Non-API and only created if necessary
+ */
+ gmlParser: null,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+ * For GeoRSS the default is (y,x), therefore: false
+ */
+ xy: false,
+
+ /**
+ * Constructor: OpenLayers.Format.GeoRSS
+ * Create a new parser for GeoRSS.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: createGeometryFromItem
+ * Return a geometry from a GeoRSS Item.
+ *
+ * Parameters:
+ * item - {DOMElement} A GeoRSS item node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry representing the node.
+ */
+ createGeometryFromItem: function(item) {
+ var point = this.getElementsByTagNameNS(item, this.georssns, "point");
+ var lat = this.getElementsByTagNameNS(item, this.geons, 'lat');
+ var lon = this.getElementsByTagNameNS(item, this.geons, 'long');
+
+ var line = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "line");
+ var polygon = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "polygon");
+ var where = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "where");
+ var box = this.getElementsByTagNameNS(item,
+ this.georssns,
+ "box");
+
+ if (point.length > 0 || (lat.length > 0 && lon.length > 0)) {
+ var location;
+ if (point.length > 0) {
+ location = OpenLayers.String.trim(
+ point[0].firstChild.nodeValue).split(/\s+/);
+ if (location.length !=2) {
+ location = OpenLayers.String.trim(
+ point[0].firstChild.nodeValue).split(/\s*,\s*/);
+ }
+ } else {
+ location = [parseFloat(lat[0].firstChild.nodeValue),
+ parseFloat(lon[0].firstChild.nodeValue)];
+ }
+
+ var geometry = new OpenLayers.Geometry.Point(location[1], location[0]);
+
+ } else if (line.length > 0) {
+ var coords = OpenLayers.String.trim(this.getChildValue(line[0])).split(/\s+/);
+ var components = [];
+ var point;
+ for (var i=0, len=coords.length; i<len; i+=2) {
+ point = new OpenLayers.Geometry.Point(coords[i+1], coords[i]);
+ components.push(point);
+ }
+ geometry = new OpenLayers.Geometry.LineString(components);
+ } else if (polygon.length > 0) {
+ var coords = OpenLayers.String.trim(this.getChildValue(polygon[0])).split(/\s+/);
+ var components = [];
+ var point;
+ for (var i=0, len=coords.length; i<len; i+=2) {
+ point = new OpenLayers.Geometry.Point(coords[i+1], coords[i]);
+ components.push(point);
+ }
+ geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);
+ } else if (where.length > 0) {
+ if (!this.gmlParser) {
+ this.gmlParser = new OpenLayers.Format.GML({'xy': this.xy});
+ }
+ var feature = this.gmlParser.parseFeature(where[0]);
+ geometry = feature.geometry;
+ } else if (box.length > 0) {
+ var coords = OpenLayers.String.trim(box[0].firstChild.nodeValue).split(/\s+/);
+ var components = [];
+ var point;
+ if (coords.length > 3) {
+ point = new OpenLayers.Geometry.Point(coords[1], coords[0]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[1], coords[2]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[3], coords[2]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[3], coords[0]);
+ components.push(point);
+ point = new OpenLayers.Geometry.Point(coords[1], coords[0]);
+ components.push(point);
+ }
+ geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);
+ }
+
+ if (geometry && this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+
+ return geometry;
+ },
+
+ /**
+ * Method: createFeatureFromItem
+ * Return a feature from a GeoRSS Item.
+ *
+ * Parameters:
+ * item - {DOMElement} A GeoRSS item node.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature representing the item.
+ */
+ createFeatureFromItem: function(item) {
+ var geometry = this.createGeometryFromItem(item);
+
+ /* Provide defaults for title and description */
+ var title = this._getChildValue(item, "*", "title", this.featureTitle);
+
+ /* First try RSS descriptions, then Atom summaries */
+ var description = this._getChildValue(
+ item, "*", "description",
+ this._getChildValue(item, "*", "content",
+ this._getChildValue(item, "*", "summary", this.featureDescription)));
+
+ /* If no link URL is found in the first child node, try the
+ href attribute */
+ var link = this._getChildValue(item, "*", "link");
+ if(!link) {
+ try {
+ link = this.getElementsByTagNameNS(item, "*", "link")[0].getAttribute("href");
+ } catch(e) {
+ link = null;
+ }
+ }
+
+ var id = this._getChildValue(item, "*", "id", null);
+
+ var data = {
+ "title": title,
+ "description": description,
+ "link": link
+ };
+ var feature = new OpenLayers.Feature.Vector(geometry, data);
+ feature.fid = id;
+ return feature;
+ },
+
+ /**
+ * Method: _getChildValue
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * nsuri - {String} Child node namespace uri ("*" for any).
+ * name - {String} Child node name.
+ * def - {String} Optional string default to return if no child found.
+ *
+ * Returns:
+ * {String} The value of the first child with the given tag name. Returns
+ * default value or empty string if none found.
+ */
+ _getChildValue: function(node, nsuri, name, def) {
+ var value;
+ var eles = this.getElementsByTagNameNS(node, nsuri, name);
+ if(eles && eles[0] && eles[0].firstChild
+ && eles[0].firstChild.nodeValue) {
+ value = this.getChildValue(eles[0]);
+ } else {
+ value = (def == undefined) ? "" : def;
+ }
+ return value;
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a GeoRSS doc
+ *
+ * Parameters:
+ * doc - {Element}
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+
+ /* Try RSS items first, then Atom entries */
+ var itemlist = null;
+ itemlist = this.getElementsByTagNameNS(doc, '*', 'item');
+ if (itemlist.length == 0) {
+ itemlist = this.getElementsByTagNameNS(doc, '*', 'entry');
+ }
+
+ var numItems = itemlist.length;
+ var features = new Array(numItems);
+ for(var i=0; i<numItems; i++) {
+ features[i] = this.createFeatureFromItem(itemlist[i]);
+ }
+ return features;
+ },
+
+
+ /**
+ * APIMethod: write
+ * Accept Feature Collection, and return a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to serialize into a string.
+ */
+ write: function(features) {
+ var georss;
+ if(OpenLayers.Util.isArray(features)) {
+ georss = this.createElementNS(this.rssns, "rss");
+ for(var i=0, len=features.length; i<len; i++) {
+ georss.appendChild(this.createFeatureXML(features[i]));
+ }
+ } else {
+ georss = this.createFeatureXML(features);
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [georss]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ * Accept an <OpenLayers.Feature.Vector>, and build a geometry for it.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createFeatureXML: function(feature) {
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ var featureNode = this.createElementNS(this.rssns, "item");
+ var titleNode = this.createElementNS(this.rssns, "title");
+ titleNode.appendChild(this.createTextNode(feature.attributes.title ? feature.attributes.title : ""));
+ var descNode = this.createElementNS(this.rssns, "description");
+ descNode.appendChild(this.createTextNode(feature.attributes.description ? feature.attributes.description : ""));
+ featureNode.appendChild(titleNode);
+ featureNode.appendChild(descNode);
+ if (feature.attributes.link) {
+ var linkNode = this.createElementNS(this.rssns, "link");
+ linkNode.appendChild(this.createTextNode(feature.attributes.link));
+ featureNode.appendChild(linkNode);
+ }
+ for(var attr in feature.attributes) {
+ if (attr == "link" || attr == "title" || attr == "description") { continue; }
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr;
+ if (attr.search(":") != -1) {
+ nodename = attr.split(":")[1];
+ }
+ var attrContainer = this.createElementNS(this.featureNS, "feature:"+nodename);
+ attrContainer.appendChild(attrText);
+ featureNode.appendChild(attrContainer);
+ }
+ featureNode.appendChild(geometryNode);
+ return featureNode;
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * builds a GeoRSS node with a given geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} A gml node.
+ */
+ buildGeometryNode: function(geometry) {
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ var node;
+ // match Polygon
+ if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ node = this.createElementNS(this.georssns, 'georss:polygon');
+
+ node.appendChild(this.buildCoordinatesNode(geometry.components[0]));
+ }
+ // match LineString
+ else if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+ node = this.createElementNS(this.georssns, 'georss:line');
+
+ node.appendChild(this.buildCoordinatesNode(geometry));
+ }
+ // match Point
+ else if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ node = this.createElementNS(this.georssns, 'georss:point');
+ node.appendChild(this.buildCoordinatesNode(geometry));
+ } else {
+ throw "Couldn't parse " + geometry.CLASS_NAME;
+ }
+ return node;
+ },
+
+ /**
+ * Method: buildCoordinatesNode
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ buildCoordinatesNode: function(geometry) {
+ var points = null;
+
+ if (geometry.components) {
+ points = geometry.components;
+ }
+
+ var path;
+ if (points) {
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ for (var i = 0; i < numPoints; i++) {
+ parts[i] = points[i].y + " " + points[i].x;
+ }
+ path = parts.join(" ");
+ } else {
+ path = geometry.y + " " + geometry.x;
+ }
+ return this.createTextNode(path);
+ },
+
+ CLASS_NAME: "OpenLayers.Format.GeoRSS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/JSON.js b/misc/openlayers/lib/OpenLayers/Format/JSON.js
new file mode 100644
index 0000000..5b25e6a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/JSON.js
@@ -0,0 +1,398 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * Note:
+ * This work draws heavily from the public domain JSON serializer/deserializer
+ * at http://www.json.org/json.js. Rewritten so that it doesn't modify
+ * basic data prototypes.
+ */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.JSON
+ * A parser to read/write JSON safely. Create a new instance with the
+ * <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: indent
+ * {String} For "pretty" printing, the indent string will be used once for
+ * each indentation level.
+ */
+ indent: " ",
+
+ /**
+ * APIProperty: space
+ * {String} For "pretty" printing, the space string will be used after
+ * the ":" separating a name/value pair.
+ */
+ space: " ",
+
+ /**
+ * APIProperty: newline
+ * {String} For "pretty" printing, the newline string will be used at the
+ * end of each name/value pair or array item.
+ */
+ newline: "\n",
+
+ /**
+ * Property: level
+ * {Integer} For "pretty" printing, this is incremented/decremented during
+ * serialization.
+ */
+ level: 0,
+
+ /**
+ * Property: pretty
+ * {Boolean} Serialize with extra whitespace for structure. This is set
+ * by the <write> method.
+ */
+ pretty: false,
+
+ /**
+ * Property: nativeJSON
+ * {Boolean} Does the browser support native json?
+ */
+ nativeJSON: (function() {
+ return !!(window.JSON && typeof JSON.parse == "function" && typeof JSON.stringify == "function");
+ })(),
+
+ /**
+ * Constructor: OpenLayers.Format.JSON
+ * Create a new parser for JSON.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Deserialize a json string.
+ *
+ * Parameters:
+ * json - {String} A JSON string
+ * filter - {Function} A function which will be called for every key and
+ * value at every level of the final result. Each value will be
+ * replaced by the result of the filter function. This can be used to
+ * reform generic objects into instances of classes, or to transform
+ * date strings into Date objects.
+ *
+ * Returns:
+ * {Object} An object, array, string, or number .
+ */
+ read: function(json, filter) {
+ var object;
+ if (this.nativeJSON) {
+ object = JSON.parse(json, filter);
+ } else try {
+ /**
+ * Parsing happens in three stages. In the first stage, we run the
+ * text against a regular expression which looks for non-JSON
+ * characters. We are especially concerned with '()' and 'new'
+ * because they can cause invocation, and '=' because it can
+ * cause mutation. But just to be safe, we will reject all
+ * unexpected characters.
+ */
+ if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+ replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+ /**
+ * In the second stage we use the eval function to compile the
+ * text into a JavaScript structure. The '{' operator is
+ * subject to a syntactic ambiguity in JavaScript - it can
+ * begin a block or an object literal. We wrap the text in
+ * parens to eliminate the ambiguity.
+ */
+ object = eval('(' + json + ')');
+
+ /**
+ * In the optional third stage, we recursively walk the new
+ * structure, passing each name/value pair to a filter
+ * function for possible transformation.
+ */
+ if(typeof filter === 'function') {
+ function walk(k, v) {
+ if(v && typeof v === 'object') {
+ for(var i in v) {
+ if(v.hasOwnProperty(i)) {
+ v[i] = walk(i, v[i]);
+ }
+ }
+ }
+ return filter(k, v);
+ }
+ object = walk('', object);
+ }
+ }
+ } catch(e) {
+ // Fall through if the regexp test fails.
+ }
+
+ if(this.keepData) {
+ this.data = object;
+ }
+
+ return object;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize an object into a JSON string.
+ *
+ * Parameters:
+ * value - {String} The object, array, string, number, boolean or date
+ * to be serialized.
+ * pretty - {Boolean} Structure the output with newlines and indentation.
+ * Default is false.
+ *
+ * Returns:
+ * {String} The JSON string representation of the input value.
+ */
+ write: function(value, pretty) {
+ this.pretty = !!pretty;
+ var json = null;
+ var type = typeof value;
+ if(this.serialize[type]) {
+ try {
+ json = (!this.pretty && this.nativeJSON) ?
+ JSON.stringify(value) :
+ this.serialize[type].apply(this, [value]);
+ } catch(err) {
+ OpenLayers.Console.error("Trouble serializing: " + err);
+ }
+ }
+ return json;
+ },
+
+ /**
+ * Method: writeIndent
+ * Output an indentation string depending on the indentation level.
+ *
+ * Returns:
+ * {String} An appropriate indentation string.
+ */
+ writeIndent: function() {
+ var pieces = [];
+ if(this.pretty) {
+ for(var i=0; i<this.level; ++i) {
+ pieces.push(this.indent);
+ }
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: writeNewline
+ * Output a string representing a newline if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A string representing a new line.
+ */
+ writeNewline: function() {
+ return (this.pretty) ? this.newline : '';
+ },
+
+ /**
+ * Method: writeSpace
+ * Output a string representing a space if in pretty printing mode.
+ *
+ * Returns:
+ * {String} A space.
+ */
+ writeSpace: function() {
+ return (this.pretty) ? this.space : '';
+ },
+
+ /**
+ * Property: serialize
+ * Object with properties corresponding to the serializable data types.
+ * Property values are functions that do the actual serializing.
+ */
+ serialize: {
+ /**
+ * Method: serialize.object
+ * Transform an object into a JSON string.
+ *
+ * Parameters:
+ * object - {Object} The object to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the object.
+ */
+ 'object': function(object) {
+ // three special objects that we want to treat differently
+ if(object == null) {
+ return "null";
+ }
+ if(object.constructor == Date) {
+ return this.serialize.date.apply(this, [object]);
+ }
+ if(object.constructor == Array) {
+ return this.serialize.array.apply(this, [object]);
+ }
+ var pieces = ['{'];
+ this.level += 1;
+ var key, keyJSON, valueJSON;
+
+ var addComma = false;
+ for(key in object) {
+ if(object.hasOwnProperty(key)) {
+ // recursive calls need to allow for sub-classing
+ keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [key, this.pretty]);
+ valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [object[key], this.pretty]);
+ if(keyJSON != null && valueJSON != null) {
+ if(addComma) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(),
+ keyJSON, ':', this.writeSpace(), valueJSON);
+ addComma = true;
+ }
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), '}');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.array
+ * Transform an array into a JSON string.
+ *
+ * Parameters:
+ * array - {Array} The array to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the array.
+ */
+ 'array': function(array) {
+ var json;
+ var pieces = ['['];
+ this.level += 1;
+
+ for(var i=0, len=array.length; i<len; ++i) {
+ // recursive calls need to allow for sub-classing
+ json = OpenLayers.Format.JSON.prototype.write.apply(this,
+ [array[i], this.pretty]);
+ if(json != null) {
+ if(i > 0) {
+ pieces.push(',');
+ }
+ pieces.push(this.writeNewline(), this.writeIndent(), json);
+ }
+ }
+
+ this.level -= 1;
+ pieces.push(this.writeNewline(), this.writeIndent(), ']');
+ return pieces.join('');
+ },
+
+ /**
+ * Method: serialize.string
+ * Transform a string into a JSON string.
+ *
+ * Parameters:
+ * string - {String} The string to be serialized
+ *
+ * Returns:
+ * {String} A JSON string representing the string.
+ */
+ 'string': function(string) {
+ // If the string contains no control characters, no quote characters, and no
+ // backslash characters, then we can simply slap some quotes around it.
+ // Otherwise we must also replace the offending characters with safe
+ // sequences.
+ var m = {
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ };
+ if(/["\\\x00-\x1f]/.test(string)) {
+ return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c) {
+ return c;
+ }
+ c = b.charCodeAt();
+ return '\\u00' +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + string + '"';
+ },
+
+ /**
+ * Method: serialize.number
+ * Transform a number into a JSON string.
+ *
+ * Parameters:
+ * number - {Number} The number to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the number.
+ */
+ 'number': function(number) {
+ return isFinite(number) ? String(number) : "null";
+ },
+
+ /**
+ * Method: serialize.boolean
+ * Transform a boolean into a JSON string.
+ *
+ * Parameters:
+ * bool - {Boolean} The boolean to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the boolean.
+ */
+ 'boolean': function(bool) {
+ return String(bool);
+ },
+
+ /**
+ * Method: serialize.object
+ * Transform a date into a JSON string.
+ *
+ * Parameters:
+ * date - {Date} The date to be serialized.
+ *
+ * Returns:
+ * {String} A JSON string representing the date.
+ */
+ 'date': function(date) {
+ function format(number) {
+ // Format integers to have at least two digits.
+ return (number < 10) ? '0' + number : number;
+ }
+ return '"' + date.getFullYear() + '-' +
+ format(date.getMonth() + 1) + '-' +
+ format(date.getDate()) + 'T' +
+ format(date.getHours()) + ':' +
+ format(date.getMinutes()) + ':' +
+ format(date.getSeconds()) + '"';
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.JSON"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/KML.js b/misc/openlayers/lib/OpenLayers/Format/KML.js
new file mode 100644
index 0000000..e10bce7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/KML.js
@@ -0,0 +1,1517 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Date.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.KML
+ * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ kml: "http://www.opengis.net/kml/2.2",
+ gx: "http://www.google.com/kml/ext/2.2"
+ },
+
+ /**
+ * APIProperty: kmlns
+ * {String} KML Namespace to use. Defaults to 2.0 namespace.
+ */
+ kmlns: "http://earth.google.com/kml/2.0",
+
+ /**
+ * APIProperty: placemarksDesc
+ * {String} Name of the placemarks. Default is "No description available".
+ */
+ placemarksDesc: "No description available",
+
+ /**
+ * APIProperty: foldersName
+ * {String} Name of the folders. Default is "OpenLayers export".
+ * If set to null, no name element will be created.
+ */
+ foldersName: "OpenLayers export",
+
+ /**
+ * APIProperty: foldersDesc
+ * {String} Description of the folders. Default is "Exported on [date]."
+ * If set to null, no description element will be created.
+ */
+ foldersDesc: "Exported on " + new Date(),
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from KML. Default is true.
+ * Extracting styleUrls requires this to be set to true
+ * Note that currently only Data and SimpleData
+ * elements are handled.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: kvpAttributes
+ * {Boolean} Only used if extractAttributes is true.
+ * If set to true, attributes will be simple
+ * key-value pairs, compatible with other formats,
+ * Any displayName elements will be ignored.
+ * If set to false, attributes will be objects,
+ * retaining any displayName elements, but not
+ * compatible with other formats. Any CDATA in
+ * displayName will be read in as a string value.
+ * Default is false.
+ */
+ kvpAttributes: false,
+
+ /**
+ * Property: extractStyles
+ * {Boolean} Extract styles from KML. Default is false.
+ * Extracting styleUrls also requires extractAttributes to be
+ * set to true
+ */
+ extractStyles: false,
+
+ /**
+ * APIProperty: extractTracks
+ * {Boolean} Extract gx:Track elements from Placemark elements. Default
+ * is false. If true, features will be generated for all points in
+ * all gx:Track elements. Features will have a when (Date) attribute
+ * based on when elements in the track. If tracks include angle
+ * elements, features will have heading, tilt, and roll attributes.
+ * If track point coordinates have three values, features will have
+ * an altitude attribute with the third coordinate value.
+ */
+ extractTracks: false,
+
+ /**
+ * APIProperty: trackAttributes
+ * {Array} If <extractTracks> is true, points within gx:Track elements will
+ * be parsed as features with when, heading, tilt, and roll attributes.
+ * Any additional attribute names can be provided in <trackAttributes>.
+ */
+ trackAttributes: null,
+
+ /**
+ * Property: internalns
+ * {String} KML Namespace to use -- defaults to the namespace of the
+ * Placemark node being parsed, but falls back to kmlns.
+ */
+ internalns: null,
+
+ /**
+ * Property: features
+ * {Array} Array of features
+ *
+ */
+ features: null,
+
+ /**
+ * Property: styles
+ * {Object} Storage of style objects
+ *
+ */
+ styles: null,
+
+ /**
+ * Property: styleBaseUrl
+ * {String}
+ */
+ styleBaseUrl: "",
+
+ /**
+ * Property: fetched
+ * {Object} Storage of KML URLs that have been fetched before
+ * in order to prevent reloading them.
+ */
+ fetched: null,
+
+ /**
+ * APIProperty: maxDepth
+ * {Integer} Maximum depth for recursive loading external KML URLs
+ * Defaults to 0: do no external fetching
+ */
+ maxDepth: 0,
+
+ /**
+ * Constructor: OpenLayers.Format.KML
+ * Create a new parser for KML.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // compile regular expressions once instead of every time they are used
+ this.regExes = {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g),
+ kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
+ kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
+ straightBracket: (/\$\[(.*?)\]/g)
+ };
+ // KML coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features.
+ */
+ read: function(data) {
+ this.features = [];
+ this.styles = {};
+ this.fetched = {};
+
+ // Set default options
+ var options = {
+ depth: 0,
+ styleBaseUrl: this.styleBaseUrl
+ };
+
+ return this.parseData(data, options);
+ },
+
+ /**
+ * Method: parseData
+ * Read data from a string, and return a list of features.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} List of features.
+ */
+ parseData: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+
+ // Loop throught the following node types in this order and
+ // process the nodes found
+ var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
+ for(var i=0, len=types.length; i<len; ++i) {
+ var type = types[i];
+
+ var nodes = this.getElementsByTagNameNS(data, "*", type);
+
+ // skip to next type if no nodes are found
+ if(nodes.length == 0) {
+ continue;
+ }
+
+ switch (type.toLowerCase()) {
+
+ // Fetch external links
+ case "link":
+ case "networklink":
+ this.parseLinks(nodes, options);
+ break;
+
+ // parse style information
+ case "style":
+ if (this.extractStyles) {
+ this.parseStyles(nodes, options);
+ }
+ break;
+ case "stylemap":
+ if (this.extractStyles) {
+ this.parseStyleMaps(nodes, options);
+ }
+ break;
+
+ // parse features
+ case "placemark":
+ this.parseFeatures(nodes, options);
+ break;
+ }
+ }
+
+ return this.features;
+ },
+
+ /**
+ * Method: parseLinks
+ * Finds URLs of linked KML documents and fetches them
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseLinks: function(nodes, options) {
+
+ // Fetch external links <NetworkLink> and <Link>
+ // Don't do anything if we have reached our maximum depth for recursion
+ if (options.depth >= this.maxDepth) {
+ return false;
+ }
+
+ // increase depth
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var href = this.parseProperty(nodes[i], "*", "href");
+ if(href && !this.fetched[href]) {
+ this.fetched[href] = true; // prevent reloading the same urls
+ var data = this.fetchLink(href);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Method: fetchLink
+ * Fetches a URL and returns the result
+ *
+ * Parameters:
+ * href - {String} url to be fetched
+ *
+ */
+ fetchLink: function(href) {
+ var request = OpenLayers.Request.GET({url: href, async: false});
+ if (request) {
+ return request.responseText;
+ }
+ },
+
+ /**
+ * Method: parseStyles
+ * Parses <Style> nodes
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyles: function(nodes, options) {
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var style = this.parseStyle(nodes[i]);
+ if(style) {
+ var styleName = (options.styleBaseUrl || "") + "#" + style.id;
+
+ this.styles[styleName] = style;
+ }
+ }
+ },
+
+ /**
+ * Method: parseKmlColor
+ * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
+ * color and opacity or null if the color is invalid.
+ *
+ * Parameters:
+ * kmlColor - {String} a kml formated color
+ *
+ * Returns:
+ * {Object}
+ */
+ parseKmlColor: function(kmlColor) {
+ var color = null;
+ if (kmlColor) {
+ var matches = kmlColor.match(this.regExes.kmlColor);
+ if (matches) {
+ color = {
+ color: '#' + matches[4] + matches[3] + matches[2],
+ opacity: parseInt(matches[1], 16) / 255
+ };
+ }
+ }
+ return color;
+ },
+
+ /**
+ * Method: parseStyle
+ * Parses the children of a <Style> node and builds the style hash
+ * accordingly
+ *
+ * Parameters:
+ * node - {DOMElement} <Style> node
+ *
+ */
+ parseStyle: function(node) {
+ var style = {};
+
+ var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle",
+ "LabelStyle"];
+ var type, styleTypeNode, nodeList, geometry, parser;
+ for(var i=0, len=types.length; i<len; ++i) {
+ type = types[i];
+ styleTypeNode = this.getElementsByTagNameNS(node, "*", type)[0];
+ if(!styleTypeNode) {
+ continue;
+ }
+
+ // only deal with first geometry of this type
+ switch (type.toLowerCase()) {
+ case "linestyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["strokeColor"] = color.color;
+ style["strokeOpacity"] = color.opacity;
+ }
+
+ var width = this.parseProperty(styleTypeNode, "*", "width");
+ if (width) {
+ style["strokeWidth"] = width;
+ }
+ break;
+
+ case "polystyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fillOpacity"] = color.opacity;
+ style["fillColor"] = color.color;
+ }
+ // Check if fill is disabled
+ var fill = this.parseProperty(styleTypeNode, "*", "fill");
+ if (fill == "0") {
+ style["fillColor"] = "none";
+ }
+ // Check if outline is disabled
+ var outline = this.parseProperty(styleTypeNode, "*", "outline");
+ if (outline == "0") {
+ style["strokeWidth"] = "0";
+ }
+
+ break;
+
+ case "iconstyle":
+ // set scale
+ var scale = parseFloat(this.parseProperty(styleTypeNode,
+ "*", "scale") || 1);
+
+ // set default width and height of icon
+ var width = 32 * scale;
+ var height = 32 * scale;
+
+ var iconNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "Icon")[0];
+ if (iconNode) {
+ var href = this.parseProperty(iconNode, "*", "href");
+ if (href) {
+
+ var w = this.parseProperty(iconNode, "*", "w");
+ var h = this.parseProperty(iconNode, "*", "h");
+
+ // Settings for Google specific icons that are 64x64
+ // We set the width and height to 64 and halve the
+ // scale to prevent icons from being too big
+ var google = "http://maps.google.com/mapfiles/kml";
+ if (OpenLayers.String.startsWith(
+ href, google) && !w && !h) {
+ w = 64;
+ h = 64;
+ scale = scale / 2;
+ }
+
+ // if only dimension is defined, make sure the
+ // other one has the same value
+ w = w || h;
+ h = h || w;
+
+ if (w) {
+ width = parseInt(w) * scale;
+ }
+
+ if (h) {
+ height = parseInt(h) * scale;
+ }
+
+ // support for internal icons
+ // (/root://icons/palette-x.png)
+ // x and y tell the position on the palette:
+ // - in pixels
+ // - starting from the left bottom
+ // We translate that to a position in the list
+ // and request the appropriate icon from the
+ // google maps website
+ var matches = href.match(this.regExes.kmlIconPalette);
+ if (matches) {
+ var palette = matches[1];
+ var file_extension = matches[2];
+
+ var x = this.parseProperty(iconNode, "*", "x");
+ var y = this.parseProperty(iconNode, "*", "y");
+
+ var posX = x ? x/32 : 0;
+ var posY = y ? (7 - y/32) : 7;
+
+ var pos = posY * 8 + posX;
+ href = "http://maps.google.com/mapfiles/kml/pal"
+ + palette + "/icon" + pos + file_extension;
+ }
+
+ style["graphicOpacity"] = 1; // fully opaque
+ style["externalGraphic"] = href;
+ }
+
+ }
+
+
+ // hotSpots define the offset for an Icon
+ var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode,
+ "*",
+ "hotSpot")[0];
+ if (hotSpotNode) {
+ var x = parseFloat(hotSpotNode.getAttribute("x"));
+ var y = parseFloat(hotSpotNode.getAttribute("y"));
+
+ var xUnits = hotSpotNode.getAttribute("xunits");
+ if (xUnits == "pixels") {
+ style["graphicXOffset"] = -x * scale;
+ }
+ else if (xUnits == "insetPixels") {
+ style["graphicXOffset"] = -width + (x * scale);
+ }
+ else if (xUnits == "fraction") {
+ style["graphicXOffset"] = -width * x;
+ }
+
+ var yUnits = hotSpotNode.getAttribute("yunits");
+ if (yUnits == "pixels") {
+ style["graphicYOffset"] = -height + (y * scale) + 1;
+ }
+ else if (yUnits == "insetPixels") {
+ style["graphicYOffset"] = -(y * scale) + 1;
+ }
+ else if (yUnits == "fraction") {
+ style["graphicYOffset"] = -height * (1 - y) + 1;
+ }
+ }
+
+ style["graphicWidth"] = width;
+ style["graphicHeight"] = height;
+ break;
+
+ case "balloonstyle":
+ var balloonStyle = OpenLayers.Util.getXmlNodeValue(
+ styleTypeNode);
+ if (balloonStyle) {
+ style["balloonStyle"] = balloonStyle.replace(
+ this.regExes.straightBracket, "${$1}");
+ }
+ break;
+ case "labelstyle":
+ var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
+ var color = this.parseKmlColor(kmlColor);
+ if (color) {
+ style["fontColor"] = color.color;
+ style["fontOpacity"] = color.opacity;
+ }
+ break;
+
+ default:
+ }
+ }
+
+ // Some polygons have no line color, so we use the fillColor for that
+ if (!style["strokeColor"] && style["fillColor"]) {
+ style["strokeColor"] = style["fillColor"];
+ }
+
+ var id = node.getAttribute("id");
+ if (id && style) {
+ style.id = id;
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: parseStyleMaps
+ * Parses <StyleMap> nodes, but only uses the 'normal' key
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseStyleMaps: function(nodes, options) {
+ // Only the default or "normal" part of the StyleMap is processed now
+ // To do the select or "highlight" bit, we'd need to change lots more
+
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var node = nodes[i];
+ var pairs = this.getElementsByTagNameNS(node, "*",
+ "Pair");
+
+ var id = node.getAttribute("id");
+ for (var j=0, jlen=pairs.length; j<jlen; j++) {
+ var pair = pairs[j];
+ // Use the shortcut in the SLD format to quickly retrieve the
+ // value of a node. Maybe it's good to have a method in
+ // Format.XML to do this
+ var key = this.parseProperty(pair, "*", "key");
+ var styleUrl = this.parseProperty(pair, "*", "styleUrl");
+
+ if (styleUrl && key == "normal") {
+ this.styles[(options.styleBaseUrl || "") + "#" + id] =
+ this.styles[(options.styleBaseUrl || "") + styleUrl];
+ }
+
+ // TODO: implement the "select" part
+ //if (styleUrl && key == "highlight") {
+ //}
+
+ }
+ }
+
+ },
+
+
+ /**
+ * Method: parseFeatures
+ * Loop through all Placemark nodes and parse them.
+ * Will create a list of features
+ *
+ * Parameters:
+ * nodes - {Array} of {DOMElement} data to read/parse.
+ * options - {Object} Hash of options
+ *
+ */
+ parseFeatures: function(nodes, options) {
+ var features = [];
+ for(var i=0, len=nodes.length; i<len; i++) {
+ var featureNode = nodes[i];
+ var feature = this.parseFeature.apply(this,[featureNode]) ;
+ if(feature) {
+
+ // Create reference to styleUrl
+ if (this.extractStyles && feature.attributes &&
+ feature.attributes.styleUrl) {
+ feature.style = this.getStyle(feature.attributes.styleUrl, options);
+ }
+
+ if (this.extractStyles) {
+ // Make sure that <Style> nodes within a placemark are
+ // processed as well
+ var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
+ "*",
+ "Style")[0];
+ if (inlineStyleNode) {
+ var inlineStyle= this.parseStyle(inlineStyleNode);
+ if (inlineStyle) {
+ feature.style = OpenLayers.Util.extend(
+ feature.style, inlineStyle
+ );
+ }
+ }
+ }
+
+ // check if gx:Track elements should be parsed
+ if (this.extractTracks) {
+ var tracks = this.getElementsByTagNameNS(
+ featureNode, this.namespaces.gx, "Track"
+ );
+ if (tracks && tracks.length > 0) {
+ var track = tracks[0];
+ var container = {
+ features: [],
+ feature: feature
+ };
+ this.readNode(track, container);
+ if (container.features.length > 0) {
+ features.push.apply(features, container.features);
+ }
+ }
+ } else {
+ // add feature to list of features
+ features.push(feature);
+ }
+ } else {
+ throw "Bad Placemark: " + i;
+ }
+ }
+
+ // add new features to existing feature list
+ this.features = this.features.concat(features);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "kml": {
+ "when": function(node, container) {
+ container.whens.push(OpenLayers.Date.parse(
+ this.getChildValue(node)
+ ));
+ },
+ "_trackPointAttribute": function(node, container) {
+ var name = node.nodeName.split(":").pop();
+ container.attributes[name].push(this.getChildValue(node));
+ }
+ },
+ "gx": {
+ "Track": function(node, container) {
+ var obj = {
+ whens: [],
+ points: [],
+ angles: []
+ };
+ if (this.trackAttributes) {
+ var name;
+ obj.attributes = {};
+ for (var i=0, ii=this.trackAttributes.length; i<ii; ++i) {
+ name = this.trackAttributes[i];
+ obj.attributes[name] = [];
+ if (!(name in this.readers.kml)) {
+ this.readers.kml[name] = this.readers.kml._trackPointAttribute;
+ }
+ }
+ }
+ this.readChildNodes(node, obj);
+ if (obj.whens.length !== obj.points.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:coord (" +
+ obj.points.length + ") elements.");
+ }
+ var hasAngles = obj.angles.length > 0;
+ if (hasAngles && obj.whens.length !== obj.angles.length) {
+ throw new Error("gx:Track with unequal number of when (" +
+ obj.whens.length + ") and gx:angles (" +
+ obj.angles.length + ") elements.");
+ }
+ var feature, point, angles;
+ for (var i=0, ii=obj.whens.length; i<ii; ++i) {
+ feature = container.feature.clone();
+ feature.fid = container.feature.fid || container.feature.id;
+ point = obj.points[i];
+ feature.geometry = point;
+ if ("z" in point) {
+ feature.attributes.altitude = point.z;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ feature.geometry.transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ if (this.trackAttributes) {
+ for (var j=0, jj=this.trackAttributes.length; j<jj; ++j) {
+ var name = this.trackAttributes[j];
+ feature.attributes[name] = obj.attributes[name][i];
+ }
+ }
+ feature.attributes.when = obj.whens[i];
+ feature.attributes.trackId = container.feature.id;
+ if (hasAngles) {
+ angles = obj.angles[i];
+ feature.attributes.heading = parseFloat(angles[0]);
+ feature.attributes.tilt = parseFloat(angles[1]);
+ feature.attributes.roll = parseFloat(angles[2]);
+ }
+ container.features.push(feature);
+ }
+ },
+ "coord": function(node, container) {
+ var str = this.getChildValue(node);
+ var coords = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ var point = new OpenLayers.Geometry.Point(coords[0], coords[1]);
+ if (coords.length > 2) {
+ point.z = parseFloat(coords[2]);
+ }
+ container.points.push(point);
+ },
+ "angles": function(node, container) {
+ var str = this.getChildValue(node);
+ var parts = str.replace(this.regExes.trimSpace, "").split(/\s+/);
+ container.angles.push(parts);
+ }
+ }
+ },
+
+ /**
+ * Method: parseFeature
+ * This function is the core of the KML parsing code in OpenLayers.
+ * It creates the geometries that are then attached to the returned
+ * feature, and calls parseAttributes() to get attribute data out.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A vector feature.
+ */
+ parseFeature: function(node) {
+ // only accept one geometry per feature - look for highest "order"
+ var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+ var type, nodeList, geometry, parser;
+ for(var i=0, len=order.length; i<len; ++i) {
+ type = order[i];
+ this.internalns = node.namespaceURI ?
+ node.namespaceURI : this.kmlns;
+ nodeList = this.getElementsByTagNameNS(node,
+ this.internalns, type);
+ if(nodeList.length > 0) {
+ // only deal with first geometry of this type
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ geometry = parser.apply(this, [nodeList[0]]);
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ } else {
+ throw new TypeError("Unsupported geometry type: " + type);
+ }
+ // stop looking for different geometry types
+ break;
+ }
+ }
+
+ // construct feature (optionally with attributes)
+ var attributes;
+ if(this.extractAttributes) {
+ attributes = this.parseAttributes(node);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+
+ var fid = node.getAttribute("id") || node.getAttribute("name");
+ if(fid != null) {
+ feature.fid = fid;
+ }
+
+ return feature;
+ },
+
+ /**
+ * Method: getStyle
+ * Retrieves a style from a style hash using styleUrl as the key
+ * If the styleUrl doesn't exist yet, we try to fetch it
+ * Internet
+ *
+ * Parameters:
+ * styleUrl - {String} URL of style
+ * options - {Object} Hash of options
+ *
+ * Returns:
+ * {Object} - (reference to) Style hash
+ */
+ getStyle: function(styleUrl, options) {
+
+ var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
+
+ var newOptions = OpenLayers.Util.extend({}, options);
+ newOptions.depth++;
+ newOptions.styleBaseUrl = styleBaseUrl;
+
+ // Fetch remote Style URLs (if not fetched before)
+ if (!this.styles[styleUrl]
+ && !OpenLayers.String.startsWith(styleUrl, "#")
+ && newOptions.depth <= this.maxDepth
+ && !this.fetched[styleBaseUrl] ) {
+
+ var data = this.fetchLink(styleBaseUrl);
+ if (data) {
+ this.parseData(data, newOptions);
+ }
+
+ }
+
+ // return requested style
+ var style = OpenLayers.Util.extend({}, this.styles[styleUrl]);
+ return style;
+ },
+
+ /**
+ * Property: parseGeometry
+ * Properties of this object are the functions that parse geometries based
+ * on their type.
+ */
+ parseGeometry: {
+
+ /**
+ * Method: parseGeometry.point
+ * Given a KML node representing a point geometry, create an OpenLayers
+ * point geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Point node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} A point geometry.
+ */
+ point: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var coords = [];
+ if(nodeList.length > 0) {
+ var coordString = nodeList[0].firstChild.nodeValue;
+ coordString = coordString.replace(this.regExes.removeSpace, "");
+ coords = coordString.split(",");
+ }
+
+ var point = null;
+ if(coords.length > 1) {
+ // preserve third dimension
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+ coords[2]);
+ } else {
+ throw "Bad coordinate string: " + coordString;
+ }
+ return point;
+ },
+
+ /**
+ * Method: parseGeometry.linestring
+ * Given a KML node representing a linestring geometry, create an
+ * OpenLayers linestring geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML LineString node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ */
+ linestring: function(node, ring) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "coordinates");
+ var line = null;
+ if(nodeList.length > 0) {
+ var coordString = this.getChildValue(nodeList[0]);
+
+ coordString = coordString.replace(this.regExes.trimSpace,
+ "");
+ coordString = coordString.replace(this.regExes.trimComma,
+ ",");
+ var pointList = coordString.split(this.regExes.splitSpace);
+ var numPoints = pointList.length;
+ var points = new Array(numPoints);
+ var coords, numCoords;
+ for(var i=0; i<numPoints; ++i) {
+ coords = pointList[i].split(",");
+ numCoords = coords.length;
+ if(numCoords > 1) {
+ if(coords.length == 2) {
+ coords[2] = null;
+ }
+ points[i] = new OpenLayers.Geometry.Point(coords[0],
+ coords[1],
+ coords[2]);
+ } else {
+ throw "Bad LineString point coordinates: " +
+ pointList[i];
+ }
+ }
+ if(numPoints) {
+ if(ring) {
+ line = new OpenLayers.Geometry.LinearRing(points);
+ } else {
+ line = new OpenLayers.Geometry.LineString(points);
+ }
+ } else {
+ throw "Bad LineString coordinates: " + coordString;
+ }
+ }
+
+ return line;
+ },
+
+ /**
+ * Method: parseGeometry.polygon
+ * Given a KML node representing a polygon geometry, create an
+ * OpenLayers polygon geometry.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML Polygon node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ */
+ polygon: function(node) {
+ var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+ "LinearRing");
+ var numRings = nodeList.length;
+ var components = new Array(numRings);
+ if(numRings > 0) {
+ // this assumes exterior ring first, inner rings after
+ var ring;
+ for(var i=0, len=nodeList.length; i<len; ++i) {
+ ring = this.parseGeometry.linestring.apply(this,
+ [nodeList[i], true]);
+ if(ring) {
+ components[i] = ring;
+ } else {
+ throw "Bad LinearRing geometry: " + i;
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Polygon(components);
+ },
+
+ /**
+ * Method: parseGeometry.multigeometry
+ * Given a KML node representing a multigeometry, create an
+ * OpenLayers geometry collection.
+ *
+ * Parameters:
+ * node - {DOMElement} A KML MultiGeometry node.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} A geometry collection.
+ */
+ multigeometry: function(node) {
+ var child, parser;
+ var parts = [];
+ var children = node.childNodes;
+ for(var i=0, len=children.length; i<len; ++i ) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ var type = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var parser = this.parseGeometry[type.toLowerCase()];
+ if(parser) {
+ parts.push(parser.apply(this, [child]));
+ }
+ }
+ }
+ return new OpenLayers.Geometry.Collection(parts);
+ }
+
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ */
+ parseAttributes: function(node) {
+ var attributes = {};
+
+ // Extended Data is parsed first.
+ var edNodes = node.getElementsByTagName("ExtendedData");
+ if (edNodes.length) {
+ attributes = this.parseExtendedData(edNodes[0]);
+ }
+
+ // assume attribute nodes are type 1 children with a type 3 or 4 child
+ var child, grandchildren, grandchild;
+ var children = node.childNodes;
+
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ grandchildren = child.childNodes;
+ if(grandchildren.length >= 1 && grandchildren.length <= 3) {
+ var grandchild;
+ switch (grandchildren.length) {
+ case 1:
+ grandchild = grandchildren[0];
+ break;
+ case 2:
+ var c1 = grandchildren[0];
+ var c2 = grandchildren[1];
+ grandchild = (c1.nodeType == 3 || c1.nodeType == 4) ?
+ c1 : c2;
+ break;
+ case 3:
+ default:
+ grandchild = grandchildren[1];
+ break;
+ }
+ if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+ var name = (child.prefix) ?
+ child.nodeName.split(":")[1] :
+ child.nodeName;
+ var value = OpenLayers.Util.getXmlNodeValue(grandchild);
+ if (value) {
+ value = value.replace(this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ }
+ }
+ }
+ }
+ return attributes;
+ },
+
+ /**
+ * Method: parseExtendedData
+ * Parse ExtendedData from KML. Limited support for schemas/datatypes.
+ * See http://code.google.com/apis/kml/documentation/kmlreference.html#extendeddata
+ * for more information on extendeddata.
+ */
+ parseExtendedData: function(node) {
+ var attributes = {};
+ var i, len, data, key;
+ var dataNodes = node.getElementsByTagName("Data");
+ for (i = 0, len = dataNodes.length; i < len; i++) {
+ data = dataNodes[i];
+ key = data.getAttribute("name");
+ var ed = {};
+ var valueNode = data.getElementsByTagName("value");
+ if (valueNode.length) {
+ ed['value'] = this.getChildValue(valueNode[0]);
+ }
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ var nameNode = data.getElementsByTagName("displayName");
+ if (nameNode.length) {
+ ed['displayName'] = this.getChildValue(nameNode[0]);
+ }
+ attributes[key] = ed;
+ }
+ }
+ var simpleDataNodes = node.getElementsByTagName("SimpleData");
+ for (i = 0, len = simpleDataNodes.length; i < len; i++) {
+ var ed = {};
+ data = simpleDataNodes[i];
+ key = data.getAttribute("name");
+ ed['value'] = this.getChildValue(data);
+ if (this.kvpAttributes) {
+ attributes[key] = ed['value'];
+ } else {
+ ed['displayName'] = key;
+ attributes[key] = ed;
+ }
+ }
+
+ return attributes;
+ },
+
+ /**
+ * Method: parseProperty
+ * Convenience method to find a node and return its value
+ *
+ * Parameters:
+ * xmlNode - {<DOMElement>}
+ * namespace - {String} namespace of the node to find
+ * tagName - {String} name of the property to parse
+ *
+ * Returns:
+ * {String} The value for the requested property (defaults to null)
+ */
+ parseProperty: function(xmlNode, namespace, tagName) {
+ var value;
+ var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
+ try {
+ value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
+ } catch(e) {
+ value = null;
+ }
+
+ return value;
+ },
+
+ /**
+ * APIMethod: write
+ * Accept Feature Collection, and return a string.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ *
+ * Returns:
+ * {String} A KML string.
+ */
+ write: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ var kml = this.createElementNS(this.kmlns, "kml");
+ var folder = this.createFolderXML();
+ for(var i=0, len=features.length; i<len; ++i) {
+ folder.appendChild(this.createPlacemarkXML(features[i]));
+ }
+ kml.appendChild(folder);
+ return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+ },
+
+ /**
+ * Method: createFolderXML
+ * Creates and returns a KML folder node
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createFolderXML: function() {
+ // Folder
+ var folder = this.createElementNS(this.kmlns, "Folder");
+
+ // Folder name
+ if (this.foldersName) {
+ var folderName = this.createElementNS(this.kmlns, "name");
+ var folderNameText = this.createTextNode(this.foldersName);
+ folderName.appendChild(folderNameText);
+ folder.appendChild(folderName);
+ }
+
+ // Folder description
+ if (this.foldersDesc) {
+ var folderDesc = this.createElementNS(this.kmlns, "description");
+ var folderDescText = this.createTextNode(this.foldersDesc);
+ folderDesc.appendChild(folderDescText);
+ folder.appendChild(folderDesc);
+ }
+
+ return folder;
+ },
+
+ /**
+ * Method: createPlacemarkXML
+ * Creates and returns a KML placemark node representing the given feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createPlacemarkXML: function(feature) {
+ // Placemark name
+ var placemarkName = this.createElementNS(this.kmlns, "name");
+ var label = (feature.style && feature.style.label) ? feature.style.label : feature.id;
+ var name = feature.attributes.name || label;
+ placemarkName.appendChild(this.createTextNode(name));
+
+ // Placemark description
+ var placemarkDesc = this.createElementNS(this.kmlns, "description");
+ var desc = feature.attributes.description || this.placemarksDesc;
+ placemarkDesc.appendChild(this.createTextNode(desc));
+
+ // Placemark
+ var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+ if(feature.fid != null) {
+ placemarkNode.setAttribute("id", feature.fid);
+ }
+ placemarkNode.appendChild(placemarkName);
+ placemarkNode.appendChild(placemarkDesc);
+
+ // Geometry node (Point, LineString, etc. nodes)
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ placemarkNode.appendChild(geometryNode);
+
+ // output attributes as extendedData
+ if (feature.attributes) {
+ var edNode = this.buildExtendedData(feature.attributes);
+ if (edNode) {
+ placemarkNode.appendChild(edNode);
+ }
+ }
+
+ return placemarkNode;
+ },
+
+ /**
+ * Method: buildGeometryNode
+ * Builds and returns a KML geometry node with the given geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildGeometryNode: function(geometry) {
+ var className = geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ var builder = this.buildGeometry[type.toLowerCase()];
+ var node = null;
+ if(builder) {
+ node = builder.apply(this, [geometry]);
+ }
+ return node;
+ },
+
+ /**
+ * Property: buildGeometry
+ * Object containing methods to do the actual geometry node building
+ * based on geometry type.
+ */
+ buildGeometry: {
+ // TBD: Anybody care about namespace aliases here (these nodes have
+ // no prefixes)?
+
+ /**
+ * Method: buildGeometry.point
+ * Given an OpenLayers point geometry, create a KML point.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML point node.
+ */
+ point: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Point");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipoint
+ * Given an OpenLayers multipoint geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipoint: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linestring
+ * Given an OpenLayers linestring geometry, create a KML linestring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linestring node.
+ */
+ linestring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LineString");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multilinestring
+ * Given an OpenLayers multilinestring geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multilinestring: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.linearring
+ * Given an OpenLayers linearring geometry, create a KML linearring.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML linearring node.
+ */
+ linearring: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "LinearRing");
+ kml.appendChild(this.buildCoordinatesNode(geometry));
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.polygon
+ * Given an OpenLayers polygon geometry, create a KML polygon.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML polygon node.
+ */
+ polygon: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "Polygon");
+ var rings = geometry.components;
+ var ringMember, ringGeom, type;
+ for(var i=0, len=rings.length; i<len; ++i) {
+ type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+ ringMember = this.createElementNS(this.kmlns, type);
+ ringGeom = this.buildGeometry.linearring.apply(this,
+ [rings[i]]);
+ ringMember.appendChild(ringGeom);
+ kml.appendChild(ringMember);
+ }
+ return kml;
+ },
+
+ /**
+ * Method: buildGeometry.multipolygon
+ * Given an OpenLayers multipolygon geometry, create a KML
+ * GeometryCollection.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+ *
+ * Returns:
+ * {DOMElement} A KML GeometryCollection node.
+ */
+ multipolygon: function(geometry) {
+ return this.buildGeometry.collection.apply(this, [geometry]);
+ },
+
+ /**
+ * Method: buildGeometry.collection
+ * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+ *
+ * Returns:
+ * {DOMElement} A KML MultiGeometry node.
+ */
+ collection: function(geometry) {
+ var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+ var child;
+ for(var i=0, len=geometry.components.length; i<len; ++i) {
+ child = this.buildGeometryNode.apply(this,
+ [geometry.components[i]]);
+ if(child) {
+ kml.appendChild(child);
+ }
+ }
+ return kml;
+ }
+ },
+
+ /**
+ * Method: buildCoordinatesNode
+ * Builds and returns the KML coordinates node with the given geometry
+ * <coordinates>...</coordinates>
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ buildCoordinatesNode: function(geometry) {
+ var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+
+ var path;
+ var points = geometry.components;
+ if(points) {
+ // LineString or LinearRing
+ var point;
+ var numPoints = points.length;
+ var parts = new Array(numPoints);
+ for(var i=0; i<numPoints; ++i) {
+ point = points[i];
+ parts[i] = this.buildCoordinates(point);
+ }
+ path = parts.join(" ");
+ } else {
+ // Point
+ path = this.buildCoordinates(geometry);
+ }
+
+ var txtNode = this.createTextNode(path);
+ coordinatesNode.appendChild(txtNode);
+
+ return coordinatesNode;
+ },
+
+ /**
+ * Method: buildCoordinates
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns
+ * {String} a coordinate pair
+ */
+ buildCoordinates: function(point) {
+ if (this.internalProjection && this.externalProjection) {
+ point = point.clone();
+ point.transform(this.internalProjection,
+ this.externalProjection);
+ }
+ return point.x + "," + point.y;
+ },
+
+ /**
+ * Method: buildExtendedData
+ *
+ * Parameters:
+ * attributes - {Object}
+ *
+ * Returns
+ * {DOMElement} A KML ExtendedData node or {null} if no attributes.
+ */
+ buildExtendedData: function(attributes) {
+ var extendedData = this.createElementNS(this.kmlns, "ExtendedData");
+ for (var attributeName in attributes) {
+ // empty, name, description, styleUrl attributes ignored
+ if (attributes[attributeName] && attributeName != "name" && attributeName != "description" && attributeName != "styleUrl") {
+ var data = this.createElementNS(this.kmlns, "Data");
+ data.setAttribute("name", attributeName);
+ var value = this.createElementNS(this.kmlns, "value");
+ if (typeof attributes[attributeName] == "object") {
+ // cater for object attributes with 'value' properties
+ // other object properties will output an empty node
+ if (attributes[attributeName].value) {
+ value.appendChild(this.createTextNode(attributes[attributeName].value));
+ }
+ if (attributes[attributeName].displayName) {
+ var displayName = this.createElementNS(this.kmlns, "displayName");
+ // displayName always written as CDATA
+ displayName.appendChild(this.getXMLDoc().createCDATASection(attributes[attributeName].displayName));
+ data.appendChild(displayName);
+ }
+ } else {
+ value.appendChild(this.createTextNode(attributes[attributeName]));
+ }
+ data.appendChild(value);
+ extendedData.appendChild(data);
+ }
+ }
+ if (this.isSimpleContent(extendedData)) {
+ return null;
+ } else {
+ return extendedData;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.KML"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OGCExceptionReport.js b/misc/openlayers/lib/OpenLayers/Format/OGCExceptionReport.js
new file mode 100644
index 0000000..61a9da5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OGCExceptionReport.js
@@ -0,0 +1,108 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OGCExceptionReport
+ * Class to read exception reports for various OGC services and versions.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OGCExceptionReport = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ogc: "http://www.opengis.net/ogc"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "ogc",
+
+ /**
+ * Constructor: OpenLayers.Format.OGCExceptionReport
+ * Create a new parser for OGC exception reports.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read OGC exception report data from a string, and return an object with
+ * information about the exceptions.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the exceptions that occurred.
+ */
+ read: function(data) {
+ var result;
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var exceptionInfo = {exceptionReport: null};
+ if (root) {
+ this.readChildNodes(data, exceptionInfo);
+ if (exceptionInfo.exceptionReport === null) {
+ // fall-back to OWSCommon since this is a common output format for exceptions
+ // we cannot easily use the ows readers directly since they differ for 1.0 and 1.1
+ exceptionInfo = new OpenLayers.Format.OWSCommon().read(data);
+ }
+ }
+ return exceptionInfo;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ogc": {
+ "ServiceExceptionReport": function(node, obj) {
+ obj.exceptionReport = {exceptions: []};
+ this.readChildNodes(node, obj.exceptionReport);
+ },
+ "ServiceException": function(node, exceptionReport) {
+ var exception = {
+ code: node.getAttribute("code"),
+ locator: node.getAttribute("locator"),
+ text: this.getChildValue(node)
+ };
+ exceptionReport.exceptions.push(exception);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OGCExceptionReport"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OSM.js b/misc/openlayers/lib/OpenLayers/Format/OSM.js
new file mode 100644
index 0000000..7283348
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OSM.js
@@ -0,0 +1,465 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OSM
+ * OSM parser. Create a new instance with the
+ * <OpenLayers.Format.OSM> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: checkTags
+ * {Boolean} Should tags be checked to determine whether something
+ * should be treated as a seperate node. Will slow down parsing.
+ * Default is false.
+ */
+ checkTags: false,
+
+ /**
+ * Property: interestingTagsExclude
+ * {Array} List of tags to exclude from 'interesting' checks on nodes.
+ * Must be set when creating the format. Will only be used if checkTags
+ * is set.
+ */
+ interestingTagsExclude: null,
+
+ /**
+ * APIProperty: areaTags
+ * {Array} List of tags indicating that something is an area.
+ * Must be set when creating the format. Will only be used if
+ * checkTags is true.
+ */
+ areaTags: null,
+
+ /**
+ * Constructor: OpenLayers.Format.OSM
+ * Create a new parser for OSM.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ var layer_defaults = {
+ 'interestingTagsExclude': ['source', 'source_ref',
+ 'source:ref', 'history', 'attribution', 'created_by'],
+ 'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
+ 'historic', 'landuse', 'military', 'natural', 'sport']
+ };
+
+ layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
+
+ var interesting = {};
+ for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
+ interesting[layer_defaults.interestingTagsExclude[i]] = true;
+ }
+ layer_defaults.interestingTagsExclude = interesting;
+
+ var area = {};
+ for (var i = 0; i < layer_defaults.areaTags.length; i++) {
+ area[layer_defaults.areaTags[i]] = true;
+ }
+ layer_defaults.areaTags = area;
+
+ // OSM coordinates are always in longlat WGS84
+ this.externalProjection = new OpenLayers.Projection("EPSG:4326");
+
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a OSM doc
+
+ * Parameters:
+ * doc - {Element}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(doc) {
+ if (typeof doc == "string") {
+ doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+ }
+
+ var nodes = this.getNodes(doc);
+ var ways = this.getWays(doc);
+
+ // Geoms will contain at least ways.length entries.
+ var feat_list = new Array(ways.length);
+
+ for (var i = 0; i < ways.length; i++) {
+ // We know the minimal of this one ahead of time. (Could be -1
+ // due to areas/polygons)
+ var point_list = new Array(ways[i].nodes.length);
+
+ var poly = this.isWayArea(ways[i]) ? 1 : 0;
+ for (var j = 0; j < ways[i].nodes.length; j++) {
+ var node = nodes[ways[i].nodes[j]];
+
+ var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
+
+ // Since OSM is topological, we stash the node ID internally.
+ point.osm_id = parseInt(ways[i].nodes[j]);
+ point_list[j] = point;
+
+ // We don't display nodes if they're used inside other
+ // elements.
+ node.used = true;
+ }
+ var geometry = null;
+ if (poly) {
+ geometry = new OpenLayers.Geometry.Polygon(
+ new OpenLayers.Geometry.LinearRing(point_list));
+ } else {
+ geometry = new OpenLayers.Geometry.LineString(point_list);
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ var feat = new OpenLayers.Feature.Vector(geometry,
+ ways[i].tags);
+ feat.osm_id = parseInt(ways[i].id);
+ feat.fid = "way." + feat.osm_id;
+ feat_list[i] = feat;
+ }
+ for (var node_id in nodes) {
+ var node = nodes[node_id];
+ if (!node.used || this.checkTags) {
+ var tags = null;
+
+ if (this.checkTags) {
+ var result = this.getTags(node.node, true);
+ if (node.used && !result[1]) {
+ continue;
+ }
+ tags = result[0];
+ } else {
+ tags = this.getTags(node.node);
+ }
+
+ var feat = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(node['lon'], node['lat']),
+ tags);
+ if (this.internalProjection && this.externalProjection) {
+ feat.geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ feat.osm_id = parseInt(node_id);
+ feat.fid = "node." + feat.osm_id;
+ feat_list.push(feat);
+ }
+ // Memory cleanup
+ node.node = null;
+ }
+ return feat_list;
+ },
+
+ /**
+ * Method: getNodes
+ * Return the node items from a doc.
+ *
+ * Parameters:
+ * doc - {DOMElement} node to parse tags from
+ */
+ getNodes: function(doc) {
+ var node_list = doc.getElementsByTagName("node");
+ var nodes = {};
+ for (var i = 0; i < node_list.length; i++) {
+ var node = node_list[i];
+ var id = node.getAttribute("id");
+ nodes[id] = {
+ 'lat': node.getAttribute("lat"),
+ 'lon': node.getAttribute("lon"),
+ 'node': node
+ };
+ }
+ return nodes;
+ },
+
+ /**
+ * Method: getWays
+ * Return the way items from a doc.
+ *
+ * Parameters:
+ * doc - {DOMElement} node to parse tags from
+ */
+ getWays: function(doc) {
+ var way_list = doc.getElementsByTagName("way");
+ var return_ways = [];
+ for (var i = 0; i < way_list.length; i++) {
+ var way = way_list[i];
+ var way_object = {
+ id: way.getAttribute("id")
+ };
+
+ way_object.tags = this.getTags(way);
+
+ var node_list = way.getElementsByTagName("nd");
+
+ way_object.nodes = new Array(node_list.length);
+
+ for (var j = 0; j < node_list.length; j++) {
+ way_object.nodes[j] = node_list[j].getAttribute("ref");
+ }
+ return_ways.push(way_object);
+ }
+ return return_ways;
+
+ },
+
+ /**
+ * Method: getTags
+ * Return the tags list attached to a specific DOM element.
+ *
+ * Parameters:
+ * dom_node - {DOMElement} node to parse tags from
+ * interesting_tags - {Boolean} whether the return from this function should
+ * return a boolean indicating that it has 'interesting tags' --
+ * tags like attribution and source are ignored. (To change the list
+ * of tags, see interestingTagsExclude)
+ *
+ * Returns:
+ * tags - {Object} hash of tags
+ * interesting - {Boolean} if interesting_tags is passed, returns
+ * whether there are any interesting tags on this element.
+ */
+ getTags: function(dom_node, interesting_tags) {
+ var tag_list = dom_node.getElementsByTagName("tag");
+ var tags = {};
+ var interesting = false;
+ for (var j = 0; j < tag_list.length; j++) {
+ var key = tag_list[j].getAttribute("k");
+ tags[key] = tag_list[j].getAttribute("v");
+ if (interesting_tags) {
+ if (!this.interestingTagsExclude[key]) {
+ interesting = true;
+ }
+ }
+ }
+ return interesting_tags ? [tags, interesting] : tags;
+ },
+
+ /**
+ * Method: isWayArea
+ * Given a way object from getWays, check whether the tags and geometry
+ * indicate something is an area.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ isWayArea: function(way) {
+ var poly_shaped = false;
+ var poly_tags = false;
+
+ if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
+ poly_shaped = true;
+ }
+ if (this.checkTags) {
+ for(var key in way.tags) {
+ if (this.areaTags[key]) {
+ poly_tags = true;
+ break;
+ }
+ }
+ }
+ return poly_shaped && (this.checkTags ? poly_tags : true);
+ },
+
+ /**
+ * APIMethod: write
+ * Takes a list of features, returns a serialized OSM format file for use
+ * in tools like JOSM.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ write: function(features) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ this.osm_id = 1;
+ this.created_nodes = {};
+ var root_node = this.createElementNS(null, "osm");
+ root_node.setAttribute("version", "0.5");
+ root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
+
+ // Loop backwards, because the deserializer puts nodes last, and
+ // we want them first if possible
+ for(var i = features.length - 1; i >= 0; i--) {
+ var nodes = this.createFeatureNodes(features[i]);
+ for (var j = 0; j < nodes.length; j++) {
+ root_node.appendChild(nodes[j]);
+ }
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
+ },
+
+ /**
+ * Method: createFeatureNodes
+ * Takes a feature, returns a list of nodes from size 0->n.
+ * Will include all pieces of the serialization that are required which
+ * have not already been created. Calls out to createXML based on geometry
+ * type.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ createFeatureNodes: function(feature) {
+ var nodes = [];
+ var className = feature.geometry.CLASS_NAME;
+ var type = className.substring(className.lastIndexOf(".") + 1);
+ type = type.toLowerCase();
+ var builder = this.createXML[type];
+ if (builder) {
+ nodes = builder.apply(this, [feature]);
+ }
+ return nodes;
+ },
+
+ /**
+ * Method: createXML
+ * Takes a feature, returns a list of nodes from size 0->n.
+ * Will include all pieces of the serialization that are required which
+ * have not already been created.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ createXML: {
+ 'point': function(point) {
+ var id = null;
+ var geometry = point.geometry ? point.geometry : point;
+
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection,
+ this.externalProjection);
+ }
+
+ var already_exists = false; // We don't return anything if the node
+ // has already been created
+ if (point.osm_id) {
+ id = point.osm_id;
+ if (this.created_nodes[id]) {
+ already_exists = true;
+ }
+ } else {
+ id = -this.osm_id;
+ this.osm_id++;
+ }
+ if (already_exists) {
+ node = this.created_nodes[id];
+ } else {
+ var node = this.createElementNS(null, "node");
+ }
+ this.created_nodes[id] = node;
+ node.setAttribute("id", id);
+ node.setAttribute("lon", geometry.x);
+ node.setAttribute("lat", geometry.y);
+ if (point.attributes) {
+ this.serializeTags(point, node);
+ }
+ this.setState(point, node);
+ return already_exists ? [] : [node];
+ },
+ linestring: function(feature) {
+ var id;
+ var nodes = [];
+ var geometry = feature.geometry;
+ if (feature.osm_id) {
+ id = feature.osm_id;
+ } else {
+ id = -this.osm_id;
+ this.osm_id++;
+ }
+ var way = this.createElementNS(null, "way");
+ way.setAttribute("id", id);
+ for (var i = 0; i < geometry.components.length; i++) {
+ var node = this.createXML['point'].apply(this, [geometry.components[i]]);
+ if (node.length) {
+ node = node[0];
+ var node_ref = node.getAttribute("id");
+ nodes.push(node);
+ } else {
+ node_ref = geometry.components[i].osm_id;
+ node = this.created_nodes[node_ref];
+ }
+ this.setState(feature, node);
+ var nd_dom = this.createElementNS(null, "nd");
+ nd_dom.setAttribute("ref", node_ref);
+ way.appendChild(nd_dom);
+ }
+ this.serializeTags(feature, way);
+ nodes.push(way);
+
+ return nodes;
+ },
+ polygon: function(feature) {
+ var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes);
+ var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs);
+ feat.osm_id = feature.osm_id;
+ return this.createXML['linestring'].apply(this, [feat]);
+ }
+ },
+
+ /**
+ * Method: serializeTags
+ * Given a feature, serialize the attributes onto the given node.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * node - {DOMNode}
+ */
+ serializeTags: function(feature, node) {
+ for (var key in feature.attributes) {
+ var tag = this.createElementNS(null, "tag");
+ tag.setAttribute("k", key);
+ tag.setAttribute("v", feature.attributes[key]);
+ node.appendChild(tag);
+ }
+ },
+
+ /**
+ * Method: setState
+ * OpenStreetMap has a convention that 'state' is stored for modification or deletion.
+ * This allows the file to be uploaded via JOSM or the bulk uploader tool.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * node - {DOMNode}
+ */
+ setState: function(feature, node) {
+ if (feature.state) {
+ var state = null;
+ switch(feature.state) {
+ case OpenLayers.State.UPDATE:
+ state = "modify";
+ case OpenLayers.State.DELETE:
+ state = "delete";
+ }
+ if (state) {
+ node.setAttribute("action", state);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OSM"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OWSCommon.js b/misc/openlayers/lib/OpenLayers/Format/OWSCommon.js
new file mode 100644
index 0000000..fd71820
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OWSCommon.js
@@ -0,0 +1,78 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon
+ * Read OWSCommon. Create a new instance with the <OpenLayers.Format.OWSCommon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.OWSCommon = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Format.OWSCommon
+ * Create a new parser for OWSCommon.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version = this.version;
+ if(!version) {
+ // remember version does not correspond to the OWS version
+ // it corresponds to the WMS/WFS/WCS etc. request version
+ var uri = root.getAttribute("xmlns:ows");
+ // the above will fail if the namespace prefix is different than
+ // ows and if the namespace is declared on a different element
+ if (uri && uri.substring(uri.lastIndexOf("/")+1) === "1.1") {
+ version ="1.1.0";
+ }
+ if(!version) {
+ version = this.defaultVersion;
+ }
+ }
+ return version;
+ },
+
+ /**
+ * APIMethod: read
+ * Read an OWSCommon document and return an object.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the structure of the document.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1.js b/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1.js
new file mode 100644
index 0000000..57ae9d2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1.js
@@ -0,0 +1,318 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/OWSCommon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon.v1
+ * Common readers and writers for OWSCommon v1.X formats
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OWSCommon.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An OWSCommon document element.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the OWSCommon document.
+ */
+ read: function(data, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var ows = {};
+ this.readChildNodes(data, ows);
+ return ows;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ows": {
+ "Exception": function(node, exceptionReport) {
+ var exception = {
+ code: node.getAttribute('exceptionCode'),
+ locator: node.getAttribute('locator'),
+ texts: []
+ };
+ exceptionReport.exceptions.push(exception);
+ this.readChildNodes(node, exception);
+ },
+ "ExceptionText": function(node, exception) {
+ var text = this.getChildValue(node);
+ exception.texts.push(text);
+ },
+ "ServiceIdentification": function(node, obj) {
+ obj.serviceIdentification = {};
+ this.readChildNodes(node, obj.serviceIdentification);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, serviceIdentification) {
+ serviceIdentification["abstract"] = this.getChildValue(node);
+ },
+ "Keywords": function(node, serviceIdentification) {
+ serviceIdentification.keywords = {};
+ this.readChildNodes(node, serviceIdentification.keywords);
+ },
+ "Keyword": function(node, keywords) {
+ keywords[this.getChildValue(node)] = true;
+ },
+ "ServiceType": function(node, serviceIdentification) {
+ serviceIdentification.serviceType = {
+ codeSpace: node.getAttribute('codeSpace'),
+ value: this.getChildValue(node)};
+ },
+ "ServiceTypeVersion": function(node, serviceIdentification) {
+ serviceIdentification.serviceTypeVersion = this.getChildValue(node);
+ },
+ "Fees": function(node, serviceIdentification) {
+ serviceIdentification.fees = this.getChildValue(node);
+ },
+ "AccessConstraints": function(node, serviceIdentification) {
+ serviceIdentification.accessConstraints =
+ this.getChildValue(node);
+ },
+ "ServiceProvider": function(node, obj) {
+ obj.serviceProvider = {};
+ this.readChildNodes(node, obj.serviceProvider);
+ },
+ "ProviderName": function(node, serviceProvider) {
+ serviceProvider.providerName = this.getChildValue(node);
+ },
+ "ProviderSite": function(node, serviceProvider) {
+ serviceProvider.providerSite = this.getAttributeNS(node,
+ this.namespaces.xlink, "href");
+ },
+ "ServiceContact": function(node, serviceProvider) {
+ serviceProvider.serviceContact = {};
+ this.readChildNodes(node, serviceProvider.serviceContact);
+ },
+ "IndividualName": function(node, serviceContact) {
+ serviceContact.individualName = this.getChildValue(node);
+ },
+ "PositionName": function(node, serviceContact) {
+ serviceContact.positionName = this.getChildValue(node);
+ },
+ "ContactInfo": function(node, serviceContact) {
+ serviceContact.contactInfo = {};
+ this.readChildNodes(node, serviceContact.contactInfo);
+ },
+ "Phone": function(node, contactInfo) {
+ contactInfo.phone = {};
+ this.readChildNodes(node, contactInfo.phone);
+ },
+ "Voice": function(node, phone) {
+ phone.voice = this.getChildValue(node);
+ },
+ "Address": function(node, contactInfo) {
+ contactInfo.address = {};
+ this.readChildNodes(node, contactInfo.address);
+ },
+ "DeliveryPoint": function(node, address) {
+ address.deliveryPoint = this.getChildValue(node);
+ },
+ "City": function(node, address) {
+ address.city = this.getChildValue(node);
+ },
+ "AdministrativeArea": function(node, address) {
+ address.administrativeArea = this.getChildValue(node);
+ },
+ "PostalCode": function(node, address) {
+ address.postalCode = this.getChildValue(node);
+ },
+ "Country": function(node, address) {
+ address.country = this.getChildValue(node);
+ },
+ "ElectronicMailAddress": function(node, address) {
+ address.electronicMailAddress = this.getChildValue(node);
+ },
+ "Role": function(node, serviceContact) {
+ serviceContact.role = this.getChildValue(node);
+ },
+ "OperationsMetadata": function(node, obj) {
+ obj.operationsMetadata = {};
+ this.readChildNodes(node, obj.operationsMetadata);
+ },
+ "Operation": function(node, operationsMetadata) {
+ var name = node.getAttribute("name");
+ operationsMetadata[name] = {};
+ this.readChildNodes(node, operationsMetadata[name]);
+ },
+ "DCP": function(node, operation) {
+ operation.dcp = {};
+ this.readChildNodes(node, operation.dcp);
+ },
+ "HTTP": function(node, dcp) {
+ dcp.http = {};
+ this.readChildNodes(node, dcp.http);
+ },
+ "Get": function(node, http) {
+ if (!http.get) {
+ http.get = [];
+ }
+ var obj = {
+ url: this.getAttributeNS(node, this.namespaces.xlink, "href")
+ };
+ this.readChildNodes(node, obj);
+ http.get.push(obj);
+ },
+ "Post": function(node, http) {
+ if (!http.post) {
+ http.post = [];
+ }
+ var obj = {
+ url: this.getAttributeNS(node, this.namespaces.xlink, "href")
+ };
+ this.readChildNodes(node, obj);
+ http.post.push(obj);
+ },
+ "Parameter": function(node, operation) {
+ if (!operation.parameters) {
+ operation.parameters = {};
+ }
+ var name = node.getAttribute("name");
+ operation.parameters[name] = {};
+ this.readChildNodes(node, operation.parameters[name]);
+ },
+ "Constraint": function(node, obj) {
+ if (!obj.constraints) {
+ obj.constraints = {};
+ }
+ var name = node.getAttribute("name");
+ obj.constraints[name] = {};
+ this.readChildNodes(node, obj.constraints[name]);
+ },
+ "Value": function(node, allowedValues) {
+ allowedValues[this.getChildValue(node)] = true;
+ },
+ "OutputFormat": function(node, obj) {
+ obj.formats.push({value: this.getChildValue(node)});
+ this.readChildNodes(node, obj);
+ },
+ "WGS84BoundingBox": function(node, obj) {
+ var boundingBox = {};
+ boundingBox.crs = node.getAttribute("crs");
+ if (obj.BoundingBox) {
+ obj.BoundingBox.push(boundingBox);
+ } else {
+ obj.projection = boundingBox.crs;
+ boundingBox = obj;
+ }
+ this.readChildNodes(node, boundingBox);
+ },
+ "BoundingBox": function(node, obj) {
+ // FIXME: We consider that BoundingBox is the same as WGS84BoundingBox
+ // LowerCorner = "min_x min_y"
+ // UpperCorner = "max_x max_y"
+ // It should normally depend on the projection
+ this.readers['ows']['WGS84BoundingBox'].apply(this, [node, obj]);
+ },
+ "LowerCorner": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, "");
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ obj.left = pointList[0];
+ obj.bottom = pointList[1];
+ },
+ "UpperCorner": function(node, obj) {
+ var str = this.getChildValue(node).replace(
+ this.regExes.trimSpace, "");
+ str = str.replace(this.regExes.trimComma, ",");
+ var pointList = str.split(this.regExes.splitSpace);
+ obj.right = pointList[0];
+ obj.top = pointList[1];
+ obj.bounds = new OpenLayers.Bounds(obj.left, obj.bottom,
+ obj.right, obj.top);
+ delete obj.left;
+ delete obj.bottom;
+ delete obj.right;
+ delete obj.top;
+ },
+ "Language": function(node, obj) {
+ obj.language = this.getChildValue(node);
+ }
+ }
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ows": {
+ "BoundingBox": function(options, nodeName) {
+ var node = this.createElementNSPlus(nodeName || "ows:BoundingBox", {
+ attributes: {
+ crs: options.projection
+ }
+ });
+ this.writeNode("ows:LowerCorner", options, node);
+ this.writeNode("ows:UpperCorner", options, node);
+ return node;
+ },
+ "LowerCorner": function(options) {
+ var node = this.createElementNSPlus("ows:LowerCorner", {
+ value: options.bounds.left + " " + options.bounds.bottom });
+ return node;
+ },
+ "UpperCorner": function(options) {
+ var node = this.createElementNSPlus("ows:UpperCorner", {
+ value: options.bounds.right + " " + options.bounds.top });
+ return node;
+ },
+ "Identifier": function(identifier) {
+ var node = this.createElementNSPlus("ows:Identifier", {
+ value: identifier });
+ return node;
+ },
+ "Title": function(title) {
+ var node = this.createElementNSPlus("ows:Title", {
+ value: title });
+ return node;
+ },
+ "Abstract": function(abstractValue) {
+ var node = this.createElementNSPlus("ows:Abstract", {
+ value: abstractValue });
+ return node;
+ },
+ "OutputFormat": function(format) {
+ var node = this.createElementNSPlus("ows:OutputFormat", {
+ value: format });
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_0_0.js
new file mode 100644
index 0000000..bc9852d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_0_0.js
@@ -0,0 +1,62 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/OWSCommon/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon.v1_0_0
+ * Parser for OWS Common version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.OWSCommon.v1>
+ */
+OpenLayers.Format.OWSCommon.v1_0_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ows": OpenLayers.Util.applyDefaults({
+ "ExceptionReport": function(node, obj) {
+ obj.success = false;
+ obj.exceptionReport = {
+ version: node.getAttribute('version'),
+ language: node.getAttribute('language'),
+ exceptions: []
+ };
+ this.readChildNodes(node, obj.exceptionReport);
+ }
+ }, OpenLayers.Format.OWSCommon.v1.prototype.readers.ows)
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ows": OpenLayers.Format.OWSCommon.v1.prototype.writers.ows
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_1_0.js
new file mode 100644
index 0000000..9da216c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OWSCommon/v1_1_0.js
@@ -0,0 +1,116 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/OWSCommon/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSCommon.v1_1_0
+ * Parser for OWS Common version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.OWSCommon.v1>
+ */
+OpenLayers.Format.OWSCommon.v1_1_0 = OpenLayers.Class(OpenLayers.Format.OWSCommon.v1, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "ows": OpenLayers.Util.applyDefaults({
+ "ExceptionReport": function(node, obj) {
+ obj.exceptionReport = {
+ version: node.getAttribute('version'),
+ language: node.getAttribute('xml:lang'),
+ exceptions: []
+ };
+ this.readChildNodes(node, obj.exceptionReport);
+ },
+ "AllowedValues": function(node, parameter) {
+ parameter.allowedValues = {};
+ this.readChildNodes(node, parameter.allowedValues);
+ },
+ "AnyValue": function(node, parameter) {
+ parameter.anyValue = true;
+ },
+ "DataType": function(node, parameter) {
+ parameter.dataType = this.getChildValue(node);
+ },
+ "Range": function(node, allowedValues) {
+ allowedValues.range = {};
+ this.readChildNodes(node, allowedValues.range);
+ },
+ "MinimumValue": function(node, range) {
+ range.minValue = this.getChildValue(node);
+ },
+ "MaximumValue": function(node, range) {
+ range.maxValue = this.getChildValue(node);
+ },
+ "Identifier": function(node, obj) {
+ obj.identifier = this.getChildValue(node);
+ },
+ "SupportedCRS": function(node, obj) {
+ obj.supportedCRS = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.OWSCommon.v1.prototype.readers["ows"])
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "ows": OpenLayers.Util.applyDefaults({
+ "Range": function(range) {
+ var node = this.createElementNSPlus("ows:Range", {
+ attributes: {
+ 'ows:rangeClosure': range.closure
+ }
+ });
+ this.writeNode("ows:MinimumValue", range.minValue, node);
+ this.writeNode("ows:MaximumValue", range.maxValue, node);
+ return node;
+ },
+ "MinimumValue": function(minValue) {
+ var node = this.createElementNSPlus("ows:MinimumValue", {
+ value: minValue
+ });
+ return node;
+ },
+ "MaximumValue": function(maxValue) {
+ var node = this.createElementNSPlus("ows:MaximumValue", {
+ value: maxValue
+ });
+ return node;
+ },
+ "Value": function(value) {
+ var node = this.createElementNSPlus("ows:Value", {
+ value: value
+ });
+ return node;
+ }
+ }, OpenLayers.Format.OWSCommon.v1.prototype.writers["ows"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSCommon.v1_1_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OWSContext.js b/misc/openlayers/lib/OpenLayers/Format/OWSContext.js
new file mode 100644
index 0000000..ab38734
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OWSContext.js
@@ -0,0 +1,86 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Context.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSContext
+ * Read and write OWS Context documents. OWS Context documents are a
+ * preliminary OGC (Open Geospatial Consortium) standard for storing the
+ * state of a web mapping application. In a way it is the successor to
+ * Web Map Context (WMC), since it is more generic and more types of layers
+ * can be stored. Also, nesting of layers is supported since version 0.3.1.
+ * For more information see: http://www.ogcnetwork.net/context
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Context>
+ */
+OpenLayers.Format.OWSContext = OpenLayers.Class(OpenLayers.Format.Context,{
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "0.3.1".
+ */
+ defaultVersion: "0.3.1",
+
+ /**
+ * Constructor: OpenLayers.Format.OWSContext
+ * Create a new parser for OWS Context documents.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version = OpenLayers.Format.XML.VersionedOGC.prototype.getVersion.apply(
+ this, arguments);
+ // 0.3.1 is backwards compatible with 0.3.0
+ if (version === "0.3.0") {
+ version = this.defaultVersion;
+ }
+ return version;
+ },
+
+ /**
+ * Method: toContext
+ * Create a context object free from layer given a map or a
+ * context object.
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Map> | Object} The map or context.
+ *
+ * Returns:
+ * {Object} A context object.
+ */
+ toContext: function(obj) {
+ var context = {};
+ if(obj.CLASS_NAME == "OpenLayers.Map") {
+ context.bounds = obj.getExtent();
+ context.maxExtent = obj.maxExtent;
+ context.projection = obj.projection;
+ context.size = obj.getSize();
+ context.layers = obj.layers;
+ }
+ return context;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSContext"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/OWSContext/v0_3_1.js b/misc/openlayers/lib/OpenLayers/Format/OWSContext/v0_3_1.js
new file mode 100644
index 0000000..d6487e8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/OWSContext/v0_3_1.js
@@ -0,0 +1,595 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/KML.js
+ * @requires OpenLayers/Format/GML.js
+ * @requires OpenLayers/Format/GML/v2.js
+ * @requires OpenLayers/Format/SLD/v1_0_0.js
+ * @requires OpenLayers/Format/OWSContext.js
+ * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.OWSContext.v0_3_1
+ * Read and write OWSContext version 0.3.1.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OWSContext.v0_3_1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ owc: "http://www.opengis.net/ows-context",
+ gml: "http://www.opengis.net/gml",
+ kml: "http://www.opengis.net/kml/2.2",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows",
+ sld: "http://www.opengis.net/sld",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 0.3.1
+ */
+ VERSION: "0.3.1",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd",
+
+ /**
+ * Property: defaultPrefix
+ * {String} Default namespace prefix to use.
+ */
+ defaultPrefix: "owc",
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: featureNS
+ * {String} The namespace uri to use for writing InlineGeometry
+ */
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+
+ /**
+ * Property: featureType
+ * {String} The name to use as the feature type when writing out
+ * InlineGeometry
+ */
+ featureType: 'vector',
+
+ /**
+ * Property: geometryName
+ * {String} The name to use for the geometry attribute when writing out
+ * InlineGeometry
+ */
+ geometryName: 'geometry',
+
+ /**
+ * Property: nestingLayerLookup
+ * {Object} Hashtable lookup for nesting layer nodes. Used while writing
+ * the OWS context document. It is necessary to keep track of the
+ * nestingPaths for which nesting layer nodes have already been
+ * created, so (nesting) layer nodes are added to those nodes.
+ *
+ * For example:
+ *
+ * If there are three layers with nestingPaths:
+ * layer1.metadata.nestingPath = "a/b/"
+ * layer2.metadata.nestingPath = "a/b/"
+ * layer2.metadata.nestingPath = "a/c"
+ *
+ * then a nesting layer node "a" should be created once and added
+ * to the resource list, a nesting layer node "b" should be created
+ * once and added under "a", and a nesting layer node "c" should be
+ * created and added under "a". The lookup paths for these nodes
+ * will be "a", "a/b", and "a/c" respectively.
+ */
+ nestingLayerLookup: null,
+
+ /**
+ * Constructor: OpenLayers.Format.OWSContext.v0_3_1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.OWSContext> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.GML.v2.prototype.setGeometryTypes.call(this);
+ },
+
+ /**
+ * Method: setNestingPath
+ * Set the nestingPath property of the layer depending on the position
+ * of the layer in hierarchy of layers.
+ *
+ * Parameters:
+ * l - {Object} An object that may have a layersContext array property.
+ *
+ */
+ setNestingPath : function(l){
+ if(l.layersContext){
+ for (var i = 0, len = l.layersContext.length; i < len; i++) {
+ var layerContext = l.layersContext[i];
+ var nPath = [];
+ var nTitle = l.title || "";
+ if(l.metadata && l.metadata.nestingPath){
+ nPath = l.metadata.nestingPath.slice();
+ }
+ if (nTitle != "") {
+ nPath.push(nTitle);
+ }
+ layerContext.metadata.nestingPath = nPath;
+ if(layerContext.layersContext){
+ this.setNestingPath(layerContext);
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: decomposeNestingPath
+ * Takes a nestingPath like "a/b/c" and decomposes it into subpaths:
+ * "a", "a/b", "a/b/c"
+ *
+ * Parameters:
+ * nPath - {Array} the nesting path
+ *
+ * Returns:
+ * Array({String}) Array with subpaths, or empty array if there is nothing
+ * to decompose
+ */
+ decomposeNestingPath: function(nPath){
+ var a = [];
+ if (OpenLayers.Util.isArray(nPath)) {
+ var path = nPath.slice();
+ while (path.length > 0) {
+ a.push(path.slice());
+ path.pop();
+ }
+ a.reverse();
+ }
+ return a;
+ },
+
+ /**
+ * APIMethod: read
+ * Read OWS context data from a string or DOMElement, and return a list
+ * of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} The context object with a flat layer list as a property named
+ * layersContext.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var context = {};
+ this.readNode(data, context);
+ // since an OWSContext can be nested we need to go through this
+ // structure recursively
+ this.setNestingPath({layersContext : context.layersContext});
+ // after nesting path has been set, create a flat list of layers
+ var layers = [];
+ this.processLayer(layers, context);
+ delete context.layersContext;
+ context.layersContext = layers;
+ return context;
+ },
+
+ /**
+ * Method: processLayer
+ * Recursive function to get back a flat list of layers from the hierarchic
+ * layer structure.
+ *
+ * Parameters:
+ * layerArray - {Array({Object})} Array of layerContext objects
+ * layer - {Object} layerContext object
+ */
+ processLayer: function(layerArray, layer) {
+ if (layer.layersContext) {
+ for (var i=0, len = layer.layersContext.length; i<len; i++) {
+ var l = layer.layersContext[i];
+ layerArray.push(l);
+ if (l.layersContext) {
+ this.processLayer(layerArray, l);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: write
+ *
+ * Parameters:
+ * context - {Object} An object representing the map context.
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} An OWS Context document string.
+ */
+ write: function(context, options) {
+ var name = "OWSContext";
+ this.nestingLayerLookup = {}; //start with empty lookup
+ options = options || {};
+ OpenLayers.Util.applyDefaults(options, context);
+ var root = this.writeNode(name, options);
+ this.nestingLayerLookup = null; //clear lookup
+ this.setAttributeNS(
+ root, this.namespaces["xsi"],
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "kml": {
+ "Document": function(node, obj) {
+ obj.features = new OpenLayers.Format.KML(
+ {kmlns: this.namespaces.kml,
+ extractStyles: true}).read(node);
+ }
+ },
+ "owc": {
+ "OWSContext": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "General": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "ResourceList": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Layer": function(node, obj) {
+ var layerContext = {
+ metadata: {},
+ visibility: (node.getAttribute("hidden") != "1"),
+ queryable: (node.getAttribute("queryable") == "1"),
+ opacity: ((node.getAttribute("opacity") != null) ?
+ parseFloat(node.getAttribute("opacity")) : null),
+ name: node.getAttribute("name"),
+ /* A category layer is a dummy layer meant for creating
+ hierarchies. It is not a physical layer in the
+ OpenLayers sense. The assumption we make here is that
+ category layers do not have a name attribute */
+ categoryLayer: (node.getAttribute("name") == null),
+ formats: [],
+ styles: []
+ };
+ if (!obj.layersContext) {
+ obj.layersContext = [];
+ }
+ obj.layersContext.push(layerContext);
+ this.readChildNodes(node, layerContext);
+ },
+ "InlineGeometry": function(node, obj) {
+ obj.features = [];
+ var elements = this.getElementsByTagNameNS(node,
+ this.namespaces.gml, "featureMember");
+ var el;
+ if (elements.length >= 1) {
+ el = elements[0];
+ }
+ if (el && el.firstChild) {
+ var featurenode = (el.firstChild.nextSibling) ?
+ el.firstChild.nextSibling : el.firstChild;
+ this.setNamespace("feature", featurenode.namespaceURI);
+ this.featureType = featurenode.localName ||
+ featurenode.nodeName.split(":").pop();
+ this.readChildNodes(node, obj);
+ }
+ },
+ "Server": function(node, obj) {
+ // when having multiple Server types, we prefer WMS
+ if ((!obj.service && !obj.version) ||
+ (obj.service !=
+ OpenLayers.Format.Context.serviceTypes.WMS)) {
+ obj.service = node.getAttribute("service");
+ obj.version = node.getAttribute("version");
+ this.readChildNodes(node, obj);
+ }
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ this.readChildNodes(node, obj);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ this.readChildNodes(node, obj);
+ },
+ "StyleList": function(node, obj) {
+ this.readChildNodes(node, obj.styles);
+ },
+ "Style": function(node, obj) {
+ var style = {};
+ obj.push(style);
+ this.readChildNodes(node, style);
+ },
+ "LegendURL": function(node, obj) {
+ var legend = {};
+ obj.legend = legend;
+ this.readChildNodes(node, legend);
+ },
+ "OnlineResource": function(node, obj) {
+ obj.url = this.getAttributeNS(node, this.namespaces.xlink,
+ "href");
+ this.readChildNodes(node, obj);
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers.ows,
+ "gml": OpenLayers.Format.GML.v2.prototype.readers.gml,
+ "sld": OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld,
+ "feature": OpenLayers.Format.GML.v2.prototype.readers.feature
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "owc": {
+ "OWSContext": function(options) {
+ var node = this.createElementNSPlus("OWSContext", {
+ attributes: {
+ version: this.VERSION,
+ id: options.id || OpenLayers.Util.createUniqueID("OpenLayers_OWSContext_")
+ }
+ });
+ this.writeNode("General", options, node);
+ this.writeNode("ResourceList", options, node);
+ return node;
+ },
+ "General": function(options) {
+ var node = this.createElementNSPlus("General");
+ this.writeNode("ows:BoundingBox", options, node);
+ this.writeNode("ows:Title", options.title || 'OpenLayers OWSContext', node);
+ return node;
+ },
+ "ResourceList": function(options) {
+ var node = this.createElementNSPlus("ResourceList");
+ for (var i=0, len=options.layers.length; i<len; i++) {
+ var layer = options.layers[i];
+ var decomposedPath = this.decomposeNestingPath(layer.metadata.nestingPath);
+ this.writeNode("_Layer", {layer: layer, subPaths: decomposedPath}, node);
+ }
+ return node;
+ },
+ "Server": function(options) {
+ var node = this.createElementNSPlus("Server", {attributes: {
+ version: options.version,
+ service: options.service }
+ });
+ this.writeNode("OnlineResource", options, node);
+ return node;
+ },
+ "OnlineResource": function(options) {
+ var node = this.createElementNSPlus("OnlineResource", {attributes: {
+ "xlink:href": options.url }
+ });
+ return node;
+ },
+ "InlineGeometry": function(layer) {
+ var node = this.createElementNSPlus("InlineGeometry"),
+ dataExtent = layer.getDataExtent();
+ if (dataExtent !== null) {
+ this.writeNode("gml:boundedBy", dataExtent, node);
+ }
+ for (var i=0, len=layer.features.length; i<len; i++) {
+ this.writeNode("gml:featureMember", layer.features[i], node);
+ }
+ return node;
+ },
+ "StyleList": function(styles) {
+ var node = this.createElementNSPlus("StyleList");
+ for (var i=0, len=styles.length; i<len; i++) {
+ this.writeNode("Style", styles[i], node);
+ }
+ return node;
+ },
+ "Style": function(style) {
+ var node = this.createElementNSPlus("Style");
+ this.writeNode("Name", style, node);
+ this.writeNode("Title", style, node);
+ if (style.legend) {
+ this.writeNode("LegendURL", style, node);
+ }
+ return node;
+ },
+ "Name": function(obj) {
+ var node = this.createElementNSPlus("Name", {
+ value: obj.name });
+ return node;
+ },
+ "Title": function(obj) {
+ var node = this.createElementNSPlus("Title", {
+ value: obj.title });
+ return node;
+ },
+ "LegendURL": function(style) {
+ var node = this.createElementNSPlus("LegendURL");
+ this.writeNode("OnlineResource", style.legend, node);
+ return node;
+ },
+ "_WMS": function(layer) {
+ var node = this.createElementNSPlus("Layer", {attributes: {
+ name: layer.params.LAYERS,
+ queryable: layer.queryable ? "1" : "0",
+ hidden: layer.visibility ? "0" : "1",
+ opacity: layer.hasOwnProperty("opacity") ? layer.opacity : null}
+ });
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("ows:OutputFormat", layer.params.FORMAT, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.WMS,
+ version: layer.params.VERSION, url: layer.url}, node);
+ if (layer.metadata.styles && layer.metadata.styles.length > 0) {
+ this.writeNode("StyleList", layer.metadata.styles, node);
+ }
+ return node;
+ },
+ "_Layer": function(options) {
+ var layer, subPaths, node, title;
+ layer = options.layer;
+ subPaths = options.subPaths;
+ node = null;
+ title = null;
+ // subPaths is an array of an array
+ // recursively calling _Layer writer eats up subPaths, until a
+ // real writer is called and nodes are returned.
+ if(subPaths.length > 0){
+ var path = subPaths[0].join("/");
+ var index = path.lastIndexOf("/");
+ node = this.nestingLayerLookup[path];
+ title = (index > 0)?path.substring(index + 1, path.length):path;
+ if(!node){
+ // category layer
+ node = this.createElementNSPlus("Layer");
+ this.writeNode("ows:Title", title, node);
+ this.nestingLayerLookup[path] = node;
+ }
+ options.subPaths.shift();//remove a path after each call
+ this.writeNode("_Layer", options, node);
+ return node;
+ } else {
+ // write out the actual layer
+ if (layer instanceof OpenLayers.Layer.WMS) {
+ node = this.writeNode("_WMS", layer);
+ } else if (layer instanceof OpenLayers.Layer.Vector) {
+ if (layer.protocol instanceof OpenLayers.Protocol.WFS.v1) {
+ node = this.writeNode("_WFS", layer);
+ } else if (layer.protocol instanceof OpenLayers.Protocol.HTTP) {
+ if (layer.protocol.format instanceof OpenLayers.Format.GML) {
+ layer.protocol.format.version = "2.1.2";
+ node = this.writeNode("_GML", layer);
+ } else if (layer.protocol.format instanceof OpenLayers.Format.KML) {
+ layer.protocol.format.version = "2.2";
+ node = this.writeNode("_KML", layer);
+ }
+ } else {
+ // write out as inline GML since we have no idea
+ // about the original Format
+ this.setNamespace("feature", this.featureNS);
+ node = this.writeNode("_InlineGeometry", layer);
+ }
+ }
+ if (layer.options.maxScale) {
+ this.writeNode("sld:MinScaleDenominator",
+ layer.options.maxScale, node);
+ }
+ if (layer.options.minScale) {
+ this.writeNode("sld:MaxScaleDenominator",
+ layer.options.minScale, node);
+ }
+ this.nestingLayerLookup[layer.name] = node;
+ return node;
+ }
+ },
+ "_WFS": function(layer) {
+ var node = this.createElementNSPlus("Layer", {attributes: {
+ name: layer.protocol.featurePrefix + ":" + layer.protocol.featureType,
+ hidden: layer.visibility ? "0" : "1" }
+ });
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.WFS,
+ version: layer.protocol.version,
+ url: layer.protocol.url}, node);
+ return node;
+ },
+ "_InlineGeometry": function(layer) {
+ var node = this.createElementNSPlus("Layer", {attributes: {
+ name: this.featureType,
+ hidden: layer.visibility ? "0" : "1" }
+ });
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("InlineGeometry", layer, node);
+ return node;
+ },
+ "_GML": function(layer) {
+ var node = this.createElementNSPlus("Layer");
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.GML,
+ url: layer.protocol.url, version:
+ layer.protocol.format.version}, node);
+ return node;
+ },
+ "_KML": function(layer) {
+ var node = this.createElementNSPlus("Layer");
+ this.writeNode("ows:Title", layer.name, node);
+ this.writeNode("Server", {service:
+ OpenLayers.Format.Context.serviceTypes.KML,
+ version: layer.protocol.format.version, url:
+ layer.protocol.url}, node);
+ return node;
+ }
+ },
+ "gml": OpenLayers.Util.applyDefaults({
+ "boundedBy": function(bounds) {
+ var node = this.createElementNSPlus("gml:boundedBy");
+ this.writeNode("gml:Box", bounds, node);
+ return node;
+ }
+ }, OpenLayers.Format.GML.v2.prototype.writers.gml),
+ "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.writers.ows,
+ "sld": OpenLayers.Format.SLD.v1_0_0.prototype.writers.sld,
+ "feature": OpenLayers.Format.GML.v2.prototype.writers.feature
+ },
+
+ CLASS_NAME: "OpenLayers.Format.OWSContext.v0_3_1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/QueryStringFilter.js b/misc/openlayers/lib/OpenLayers/Format/QueryStringFilter.js
new file mode 100644
index 0000000..e33f722
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/QueryStringFilter.js
@@ -0,0 +1,183 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Filter/Logical.js
+ */
+
+/**
+ * Class: OpenLayers.Format.QueryStringFilter
+ * Parser for reading a query string and creating a simple filter.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.QueryStringFilter = (function() {
+
+ /**
+ * Map the OpenLayers.Filter.Comparison types to the operation strings of
+ * the protocol.
+ */
+ var cmpToStr = {};
+ cmpToStr[OpenLayers.Filter.Comparison.EQUAL_TO] = "eq";
+ cmpToStr[OpenLayers.Filter.Comparison.NOT_EQUAL_TO] = "ne";
+ cmpToStr[OpenLayers.Filter.Comparison.LESS_THAN] = "lt";
+ cmpToStr[OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO] = "lte";
+ cmpToStr[OpenLayers.Filter.Comparison.GREATER_THAN] = "gt";
+ cmpToStr[OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO] = "gte";
+ cmpToStr[OpenLayers.Filter.Comparison.LIKE] = "ilike";
+
+ /**
+ * Function: regex2value
+ * Convert the value from a regular expression string to a LIKE/ILIKE
+ * string known to the web service.
+ *
+ * Parameters:
+ * value - {String} The regex string.
+ *
+ * Returns:
+ * {String} The converted string.
+ */
+ function regex2value(value) {
+
+ // highly sensitive!! Do not change this without running the
+ // Protocol/HTTP.html unit tests
+
+ // convert % to \%
+ value = value.replace(/%/g, "\\%");
+
+ // convert \\. to \\_ (\\.* occurences converted later)
+ value = value.replace(/\\\\\.(\*)?/g, function($0, $1) {
+ return $1 ? $0 : "\\\\_";
+ });
+
+ // convert \\.* to \\%
+ value = value.replace(/\\\\\.\*/g, "\\\\%");
+
+ // convert . to _ (\. and .* occurences converted later)
+ value = value.replace(/(\\)?\.(\*)?/g, function($0, $1, $2) {
+ return $1 || $2 ? $0 : "_";
+ });
+
+ // convert .* to % (\.* occurnces converted later)
+ value = value.replace(/(\\)?\.\*/g, function($0, $1) {
+ return $1 ? $0 : "%";
+ });
+
+ // convert \. to .
+ value = value.replace(/\\\./g, ".");
+
+ // replace \* with * (watching out for \\*)
+ value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+ return $1 ? $0 : "*";
+ });
+
+ return value;
+ }
+
+ return OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Default is false. If true and the layer has a projection object set,
+ * any BBOX filter will be serialized with a fifth item identifying the
+ * projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * APIMethod: write
+ * Serialize an <OpenLayers.Filter> objects using the "simple" filter syntax for
+ * query string parameters. This function must be called as a method of
+ * a protocol instance.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+ write: function(filter, params) {
+ params = params || {};
+ var className = filter.CLASS_NAME;
+ var filterType = className.substring(className.lastIndexOf(".") + 1);
+ switch (filterType) {
+ case "Spatial":
+ switch (filter.type) {
+ case OpenLayers.Filter.Spatial.BBOX:
+ params.bbox = filter.value.toArray();
+ if (this.srsInBBOX && filter.projection) {
+ params.bbox.push(filter.projection.getCode());
+ }
+ break;
+ case OpenLayers.Filter.Spatial.DWITHIN:
+ params.tolerance = filter.distance;
+ // no break here
+ case OpenLayers.Filter.Spatial.WITHIN:
+ params.lon = filter.value.x;
+ params.lat = filter.value.y;
+ break;
+ default:
+ OpenLayers.Console.warn(
+ "Unknown spatial filter type " + filter.type);
+ }
+ break;
+ case "Comparison":
+ var op = cmpToStr[filter.type];
+ if (op !== undefined) {
+ var value = filter.value;
+ if (filter.type == OpenLayers.Filter.Comparison.LIKE) {
+ value = regex2value(value);
+ if (this.wildcarded) {
+ value = "%" + value + "%";
+ }
+ }
+ params[filter.property + "__" + op] = value;
+ params.queryable = params.queryable || [];
+ params.queryable.push(filter.property);
+ } else {
+ OpenLayers.Console.warn(
+ "Unknown comparison filter type " + filter.type);
+ }
+ break;
+ case "Logical":
+ if (filter.type === OpenLayers.Filter.Logical.AND) {
+ for (var i=0,len=filter.filters.length; i<len; i++) {
+ params = this.write(filter.filters[i], params);
+ }
+ } else {
+ OpenLayers.Console.warn(
+ "Unsupported logical filter type " + filter.type);
+ }
+ break;
+ default:
+ OpenLayers.Console.warn("Unknown filter type " + filterType);
+ }
+ return params;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.QueryStringFilter"
+
+ });
+
+
+})();
diff --git a/misc/openlayers/lib/OpenLayers/Format/SLD.js b/misc/openlayers/lib/OpenLayers/Format/SLD.js
new file mode 100644
index 0000000..56e59d0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SLD.js
@@ -0,0 +1,81 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD
+ * Read/Write SLD. Create a new instance with the <OpenLayers.Format.SLD>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ *
+ * Currently supported profiles:
+ * - GeoServer - parses GeoServer vendor specific capabilities for SLD.
+ */
+ profile: null,
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is true.
+ */
+ stringifyOutput: true,
+
+ /**
+ * APIProperty: namedLayersAsArray
+ * {Boolean} Generate a namedLayers array. If false, the namedLayers
+ * property value will be an object keyed by layer name. Default is
+ * false.
+ */
+ namedLayersAsArray: false,
+
+ /**
+ * APIMethod: write
+ * Write a SLD document given a list of styles.
+ *
+ * Parameters:
+ * sld - {Object} An object representing the SLD.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} An SLD document string.
+ */
+
+ /**
+ * APIMethod: read
+ * Read and SLD doc and return an object representing the SLD.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the SLD.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.SLD"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SLD/v1.js b/misc/openlayers/lib/OpenLayers/Format/SLD/v1.js
new file mode 100644
index 0000000..c43bac4
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SLD/v1.js
@@ -0,0 +1,1309 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Format/SLD.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ * @requires OpenLayers/Symbolizer/Point.js
+ * @requires OpenLayers/Symbolizer/Line.js
+ * @requires OpenLayers/Symbolizer/Polygon.js
+ * @requires OpenLayers/Symbolizer/Text.js
+ * @requires OpenLayers/Symbolizer/Raster.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1
+ * Superclass for SLD version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_0_0>
+ */
+OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ sld: "http://www.opengis.net/sld",
+ ogc: "http://www.opengis.net/ogc",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sld",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * APIProperty: multipleSymbolizers
+ * {Boolean} Support multiple symbolizers per rule. Default is false. if
+ * true, an OpenLayers.Style2 instance will be created to represent
+ * user styles instead of an OpenLayers.Style instace. The
+ * OpenLayers.Style2 class allows collections of rules with multiple
+ * symbolizers, but is not currently useful for client side rendering.
+ * If multiple symbolizers is true, multiple FeatureTypeStyle elements
+ * are preserved in reading/writing by setting symbolizer zIndex values.
+ * In addition, the <defaultSymbolizer> property is ignored if
+ * multiple symbolizers are supported (defaults should be applied
+ * when rendering).
+ */
+ multipleSymbolizers: false,
+
+ /**
+ * Property: featureTypeCounter
+ * {Number} Private counter for multiple feature type styles.
+ */
+ featureTypeCounter: null,
+
+ /**
+ * APIProperty: defaultSymbolizer.
+ * {Object} A symbolizer with the SLD defaults.
+ */
+ defaultSymbolizer: {
+ fillColor: "#808080",
+ fillOpacity: 1,
+ strokeColor: "#000000",
+ strokeOpacity: 1,
+ strokeWidth: 1,
+ strokeDashstyle: "solid",
+ pointRadius: 3,
+ graphicName: "square"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.SLD> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An SLD document element.
+ * options - {Object} Options for the reader.
+ *
+ * Valid options:
+ * namedLayersAsArray - {Boolean} Generate a namedLayers array. If false,
+ * the namedLayers property value will be an object keyed by layer name.
+ * Default is false.
+ *
+ * Returns:
+ * {Object} An object representing the SLD.
+ */
+ read: function(data, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var sld = {
+ namedLayers: options.namedLayersAsArray === true ? [] : {}
+ };
+ this.readChildNodes(data, sld);
+ return sld;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: OpenLayers.Util.applyDefaults({
+ "sld": {
+ "StyledLayerDescriptor": function(node, sld) {
+ sld.version = node.getAttribute("version");
+ this.readChildNodes(node, sld);
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, obj) {
+ obj.description = this.getChildValue(node);
+ },
+ "NamedLayer": function(node, sld) {
+ var layer = {
+ userStyles: [],
+ namedStyles: []
+ };
+ this.readChildNodes(node, layer);
+ // give each of the user styles this layer name
+ for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+ layer.userStyles[i].layerName = layer.name;
+ }
+ if(OpenLayers.Util.isArray(sld.namedLayers)) {
+ sld.namedLayers.push(layer);
+ } else {
+ sld.namedLayers[layer.name] = layer;
+ }
+ },
+ "NamedStyle": function(node, layer) {
+ layer.namedStyles.push(
+ this.getChildName(node.firstChild)
+ );
+ },
+ "UserStyle": function(node, layer) {
+ var obj = {defaultsPerSymbolizer: true, rules: []};
+ this.featureTypeCounter = -1;
+ this.readChildNodes(node, obj);
+ var style;
+ if (this.multipleSymbolizers) {
+ delete obj.defaultsPerSymbolizer;
+ style = new OpenLayers.Style2(obj);
+ } else {
+ style = new OpenLayers.Style(this.defaultSymbolizer, obj);
+ }
+ layer.userStyles.push(style);
+ },
+ "IsDefault": function(node, style) {
+ if(this.getChildValue(node) == "1") {
+ style.isDefault = true;
+ }
+ },
+ "FeatureTypeStyle": function(node, style) {
+ ++this.featureTypeCounter;
+ var obj = {
+ rules: this.multipleSymbolizers ? style.rules : []
+ };
+ this.readChildNodes(node, obj);
+ if (!this.multipleSymbolizers) {
+ style.rules = obj.rules;
+ }
+ },
+ "Rule": function(node, obj) {
+ var config;
+ if (this.multipleSymbolizers) {
+ config = {symbolizers: []};
+ }
+ var rule = new OpenLayers.Rule(config);
+ this.readChildNodes(node, rule);
+ obj.rules.push(rule);
+ },
+ "ElseFilter": function(node, rule) {
+ rule.elseFilter = true;
+ },
+ "MinScaleDenominator": function(node, rule) {
+ rule.minScaleDenominator = parseFloat(this.getChildValue(node));
+ },
+ "MaxScaleDenominator": function(node, rule) {
+ rule.maxScaleDenominator = parseFloat(this.getChildValue(node));
+ },
+ "TextSymbolizer": function(node, rule) {
+ var config = {};
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Text(config)
+ );
+ } else {
+ rule.symbolizer["Text"] = OpenLayers.Util.applyDefaults(
+ config, rule.symbolizer["Text"]
+ );
+ }
+ },
+ "LabelPlacement": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "PointPlacement": function(node, symbolizer) {
+ var config = {};
+ this.readChildNodes(node, config);
+ config.labelRotation = config.rotation;
+ delete config.rotation;
+ var labelAlign,
+ x = symbolizer.labelAnchorPointX,
+ y = symbolizer.labelAnchorPointY;
+ if (x <= 1/3) {
+ labelAlign = 'l';
+ } else if (x > 1/3 && x < 2/3) {
+ labelAlign = 'c';
+ } else if (x >= 2/3) {
+ labelAlign = 'r';
+ }
+ if (y <= 1/3) {
+ labelAlign += 'b';
+ } else if (y > 1/3 && y < 2/3) {
+ labelAlign += 'm';
+ } else if (y >= 2/3) {
+ labelAlign += 't';
+ }
+ config.labelAlign = labelAlign;
+ OpenLayers.Util.applyDefaults(symbolizer, config);
+ },
+ "AnchorPoint": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "AnchorPointX": function(node, symbolizer) {
+ var labelAnchorPointX = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelAnchorPointX) {
+ symbolizer.labelAnchorPointX = labelAnchorPointX;
+ }
+ },
+ "AnchorPointY": function(node, symbolizer) {
+ var labelAnchorPointY = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelAnchorPointY) {
+ symbolizer.labelAnchorPointY = labelAnchorPointY;
+ }
+ },
+ "Displacement": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "DisplacementX": function(node, symbolizer) {
+ var labelXOffset = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelXOffset) {
+ symbolizer.labelXOffset = labelXOffset;
+ }
+ },
+ "DisplacementY": function(node, symbolizer) {
+ var labelYOffset = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelYOffset) {
+ symbolizer.labelYOffset = labelYOffset;
+ }
+ },
+ "LinePlacement": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "PerpendicularOffset": function(node, symbolizer) {
+ var labelPerpendicularOffset = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(labelPerpendicularOffset) {
+ symbolizer.labelPerpendicularOffset = labelPerpendicularOffset;
+ }
+ },
+ "Label": function(node, symbolizer) {
+ var value = this.readers.ogc._expression.call(this, node);
+ if (value) {
+ symbolizer.label = value;
+ }
+ },
+ "Font": function(node, symbolizer) {
+ this.readChildNodes(node, symbolizer);
+ },
+ "Halo": function(node, symbolizer) {
+ // halo has a fill, so send fresh object
+ var obj = {};
+ this.readChildNodes(node, obj);
+ symbolizer.haloRadius = obj.haloRadius;
+ symbolizer.haloColor = obj.fillColor;
+ symbolizer.haloOpacity = obj.fillOpacity;
+ },
+ "Radius": function(node, symbolizer) {
+ var radius = this.readers.ogc._expression.call(this, node);
+ if(radius != null) {
+ // radius is only used for halo
+ symbolizer.haloRadius = radius;
+ }
+ },
+ "RasterSymbolizer": function(node, rule) {
+ var config = {};
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Raster(config)
+ );
+ } else {
+ rule.symbolizer["Raster"] = OpenLayers.Util.applyDefaults(
+ config, rule.symbolizer["Raster"]
+ );
+ }
+ },
+ "Geometry": function(node, obj) {
+ obj.geometry = {};
+ this.readChildNodes(node, obj.geometry);
+ },
+ "ColorMap": function(node, symbolizer) {
+ symbolizer.colorMap = [];
+ this.readChildNodes(node, symbolizer.colorMap);
+ },
+ "ColorMapEntry": function(node, colorMap) {
+ var q = node.getAttribute("quantity");
+ var o = node.getAttribute("opacity");
+ colorMap.push({
+ color: node.getAttribute("color"),
+ quantity: q !== null ? parseFloat(q) : undefined,
+ label: node.getAttribute("label") || undefined,
+ opacity: o !== null ? parseFloat(o) : undefined
+ });
+ },
+ "LineSymbolizer": function(node, rule) {
+ var config = {};
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Line(config)
+ );
+ } else {
+ rule.symbolizer["Line"] = OpenLayers.Util.applyDefaults(
+ config, rule.symbolizer["Line"]
+ );
+ }
+ },
+ "PolygonSymbolizer": function(node, rule) {
+ var config = {
+ fill: false,
+ stroke: false
+ };
+ if (!this.multipleSymbolizers) {
+ config = rule.symbolizer["Polygon"] || config;
+ }
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Polygon(config)
+ );
+ } else {
+ rule.symbolizer["Polygon"] = config;
+ }
+ },
+ "PointSymbolizer": function(node, rule) {
+ var config = {
+ fill: false,
+ stroke: false,
+ graphic: false
+ };
+ if (!this.multipleSymbolizers) {
+ config = rule.symbolizer["Point"] || config;
+ }
+ this.readChildNodes(node, config);
+ if (this.multipleSymbolizers) {
+ config.zIndex = this.featureTypeCounter;
+ rule.symbolizers.push(
+ new OpenLayers.Symbolizer.Point(config)
+ );
+ } else {
+ rule.symbolizer["Point"] = config;
+ }
+ },
+ "Stroke": function(node, symbolizer) {
+ symbolizer.stroke = true;
+ this.readChildNodes(node, symbolizer);
+ },
+ "Fill": function(node, symbolizer) {
+ symbolizer.fill = true;
+ this.readChildNodes(node, symbolizer);
+ },
+ "CssParameter": function(node, symbolizer) {
+ var cssProperty = node.getAttribute("name");
+ var symProperty = this.cssMap[cssProperty];
+ // for labels, fill should map to fontColor and fill-opacity
+ // to fontOpacity
+ if (symbolizer.label) {
+ if (cssProperty === 'fill') {
+ symProperty = "fontColor";
+ } else if (cssProperty === 'fill-opacity') {
+ symProperty = "fontOpacity";
+ }
+ }
+ if(symProperty) {
+ // Limited support for parsing of OGC expressions
+ var value = this.readers.ogc._expression.call(this, node);
+ // always string, could be an empty string
+ if(value) {
+ symbolizer[symProperty] = value;
+ }
+ }
+ },
+ "Graphic": function(node, symbolizer) {
+ symbolizer.graphic = true;
+ var graphic = {};
+ // painter's order not respected here, clobber previous with next
+ this.readChildNodes(node, graphic);
+ // directly properties with names that match symbolizer properties
+ var properties = [
+ "stroke", "strokeColor", "strokeWidth", "strokeOpacity",
+ "strokeLinecap", "fill", "fillColor", "fillOpacity",
+ "graphicName", "rotation", "graphicFormat"
+ ];
+ var prop, value;
+ for(var i=0, len=properties.length; i<len; ++i) {
+ prop = properties[i];
+ value = graphic[prop];
+ if(value != undefined) {
+ symbolizer[prop] = value;
+ }
+ }
+ // set other generic properties with specific graphic property names
+ if(graphic.opacity != undefined) {
+ symbolizer.graphicOpacity = graphic.opacity;
+ }
+ if(graphic.size != undefined) {
+ var pointRadius = graphic.size / 2;
+ if (isNaN(pointRadius)) {
+ // likely a property name
+ symbolizer.graphicWidth = graphic.size;
+ } else {
+ symbolizer.pointRadius = graphic.size / 2;
+ }
+ }
+ if(graphic.href != undefined) {
+ symbolizer.externalGraphic = graphic.href;
+ }
+ if(graphic.rotation != undefined) {
+ symbolizer.rotation = graphic.rotation;
+ }
+ },
+ "ExternalGraphic": function(node, graphic) {
+ this.readChildNodes(node, graphic);
+ },
+ "Mark": function(node, graphic) {
+ this.readChildNodes(node, graphic);
+ },
+ "WellKnownName": function(node, graphic) {
+ graphic.graphicName = this.getChildValue(node);
+ },
+ "Opacity": function(node, obj) {
+ var opacity = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(opacity) {
+ obj.opacity = opacity;
+ }
+ },
+ "Size": function(node, obj) {
+ var size = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(size) {
+ obj.size = size;
+ }
+ },
+ "Rotation": function(node, obj) {
+ var rotation = this.readers.ogc._expression.call(this, node);
+ // always string, could be empty string
+ if(rotation) {
+ obj.rotation = rotation;
+ }
+ },
+ "OnlineResource": function(node, obj) {
+ obj.href = this.getAttributeNS(
+ node, this.namespaces.xlink, "href"
+ );
+ },
+ "Format": function(node, graphic) {
+ graphic.graphicFormat = this.getChildValue(node);
+ }
+ }
+ }, OpenLayers.Format.Filter.v1_0_0.prototype.readers),
+
+ /**
+ * Property: cssMap
+ * {Object} Object mapping supported css property names to OpenLayers
+ * symbolizer property names.
+ */
+ cssMap: {
+ "stroke": "strokeColor",
+ "stroke-opacity": "strokeOpacity",
+ "stroke-width": "strokeWidth",
+ "stroke-linecap": "strokeLinecap",
+ "stroke-dasharray": "strokeDashstyle",
+ "fill": "fillColor",
+ "fill-opacity": "fillOpacity",
+ "font-family": "fontFamily",
+ "font-size": "fontSize",
+ "font-weight": "fontWeight",
+ "font-style": "fontStyle"
+ },
+
+ /**
+ * Method: getCssProperty
+ * Given a symbolizer property, get the corresponding CSS property
+ * from the <cssMap>.
+ *
+ * Parameters:
+ * sym - {String} A symbolizer property name.
+ *
+ * Returns:
+ * {String} A CSS property name or null if none found.
+ */
+ getCssProperty: function(sym) {
+ var css = null;
+ for(var prop in this.cssMap) {
+ if(this.cssMap[prop] == sym) {
+ css = prop;
+ break;
+ }
+ }
+ return css;
+ },
+
+ /**
+ * Method: getGraphicFormat
+ * Given a href for an external graphic, try to determine the mime-type.
+ * This method doesn't try too hard, and will fall back to
+ * <defaultGraphicFormat> if one of the known <graphicFormats> is not
+ * the file extension of the provided href.
+ *
+ * Parameters:
+ * href - {String}
+ *
+ * Returns:
+ * {String} The graphic format.
+ */
+ getGraphicFormat: function(href) {
+ var format, regex;
+ for(var key in this.graphicFormats) {
+ if(this.graphicFormats[key].test(href)) {
+ format = key;
+ break;
+ }
+ }
+ return format || this.defaultGraphicFormat;
+ },
+
+ /**
+ * Property: defaultGraphicFormat
+ * {String} If none other can be determined from <getGraphicFormat>, this
+ * default will be returned.
+ */
+ defaultGraphicFormat: "image/png",
+
+ /**
+ * Property: graphicFormats
+ * {Object} Mapping of image mime-types to regular extensions matching
+ * well-known file extensions.
+ */
+ graphicFormats: {
+ "image/jpeg": /\.jpe?g$/i,
+ "image/gif": /\.gif$/i,
+ "image/png": /\.png$/i
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * sld - {Object} An object representing the SLD.
+ *
+ * Returns:
+ * {DOMElement} The root of an SLD document.
+ */
+ write: function(sld) {
+ return this.writers.sld.StyledLayerDescriptor.apply(this, [sld]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: OpenLayers.Util.applyDefaults({
+ "sld": {
+ "_OGCExpression": function(nodeName, value) {
+ // only the simplest of ogc:expression handled
+ // {label: "some text and a ${propertyName}"}
+ var node = this.createElementNSPlus(nodeName);
+ var tokens = typeof value == "string" ?
+ value.split("${") :
+ [value];
+ node.appendChild(this.createTextNode(tokens[0]));
+ var item, last;
+ for(var i=1, len=tokens.length; i<len; i++) {
+ item = tokens[i];
+ last = item.indexOf("}");
+ if(last > 0) {
+ this.writeNode(
+ "ogc:PropertyName",
+ {property: item.substring(0, last)},
+ node
+ );
+ node.appendChild(
+ this.createTextNode(item.substring(++last))
+ );
+ } else {
+ // no ending }, so this is a literal ${
+ node.appendChild(
+ this.createTextNode("${" + item)
+ );
+ }
+ }
+ return node;
+ },
+ "StyledLayerDescriptor": function(sld) {
+ var root = this.createElementNSPlus(
+ "sld:StyledLayerDescriptor",
+ {attributes: {
+ "version": this.VERSION,
+ "xsi:schemaLocation": this.schemaLocation
+ }}
+ );
+
+ // For ArcGIS Server it is necessary to define this
+ // at the root level (see ticket:2166).
+ root.setAttribute("xmlns:ogc", this.namespaces.ogc);
+ root.setAttribute("xmlns:gml", this.namespaces.gml);
+
+ // add in optional name
+ if(sld.name) {
+ this.writeNode("Name", sld.name, root);
+ }
+ // add in optional title
+ if(sld.title) {
+ this.writeNode("Title", sld.title, root);
+ }
+ // add in optional description
+ if(sld.description) {
+ this.writeNode("Abstract", sld.description, root);
+ }
+ // add in named layers
+ // allow namedLayers to be an array
+ if(OpenLayers.Util.isArray(sld.namedLayers)) {
+ for(var i=0, len=sld.namedLayers.length; i<len; ++i) {
+ this.writeNode("NamedLayer", sld.namedLayers[i], root);
+ }
+ } else {
+ for(var name in sld.namedLayers) {
+ this.writeNode("NamedLayer", sld.namedLayers[name], root);
+ }
+ }
+ return root;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("sld:Name", {value: name});
+ },
+ "Title": function(title) {
+ return this.createElementNSPlus("sld:Title", {value: title});
+ },
+ "Abstract": function(description) {
+ return this.createElementNSPlus(
+ "sld:Abstract", {value: description}
+ );
+ },
+ "NamedLayer": function(layer) {
+ var node = this.createElementNSPlus("sld:NamedLayer");
+
+ // add in required name
+ this.writeNode("Name", layer.name, node);
+
+ // optional sld:LayerFeatureConstraints here
+
+ // add in named styles
+ if(layer.namedStyles) {
+ for(var i=0, len=layer.namedStyles.length; i<len; ++i) {
+ this.writeNode(
+ "NamedStyle", layer.namedStyles[i], node
+ );
+ }
+ }
+
+ // add in user styles
+ if(layer.userStyles) {
+ for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+ this.writeNode(
+ "UserStyle", layer.userStyles[i], node
+ );
+ }
+ }
+
+ return node;
+ },
+ "NamedStyle": function(name) {
+ var node = this.createElementNSPlus("sld:NamedStyle");
+ this.writeNode("Name", name, node);
+ return node;
+ },
+ "UserStyle": function(style) {
+ var node = this.createElementNSPlus("sld:UserStyle");
+
+ // add in optional name
+ if(style.name) {
+ this.writeNode("Name", style.name, node);
+ }
+ // add in optional title
+ if(style.title) {
+ this.writeNode("Title", style.title, node);
+ }
+ // add in optional description
+ if(style.description) {
+ this.writeNode("Abstract", style.description, node);
+ }
+
+ // add isdefault
+ if(style.isDefault) {
+ this.writeNode("IsDefault", style.isDefault, node);
+ }
+
+ // add FeatureTypeStyles
+ if (this.multipleSymbolizers && style.rules) {
+ // group style objects by symbolizer zIndex
+ var rulesByZ = {
+ 0: []
+ };
+ var zValues = [0];
+ var rule, ruleMap, symbolizer, zIndex, clone;
+ for (var i=0, ii=style.rules.length; i<ii; ++i) {
+ rule = style.rules[i];
+ if (rule.symbolizers) {
+ ruleMap = {};
+ for (var j=0, jj=rule.symbolizers.length; j<jj; ++j) {
+ symbolizer = rule.symbolizers[j];
+ zIndex = symbolizer.zIndex;
+ if (!(zIndex in ruleMap)) {
+ clone = rule.clone();
+ clone.symbolizers = [];
+ ruleMap[zIndex] = clone;
+ }
+ ruleMap[zIndex].symbolizers.push(symbolizer.clone());
+ }
+ for (zIndex in ruleMap) {
+ if (!(zIndex in rulesByZ)) {
+ zValues.push(zIndex);
+ rulesByZ[zIndex] = [];
+ }
+ rulesByZ[zIndex].push(ruleMap[zIndex]);
+ }
+ } else {
+ // no symbolizers in rule
+ rulesByZ[0].push(rule.clone());
+ }
+ }
+ // write one FeatureTypeStyle per zIndex
+ zValues.sort();
+ var rules;
+ for (var i=0, ii=zValues.length; i<ii; ++i) {
+ rules = rulesByZ[zValues[i]];
+ if (rules.length > 0) {
+ clone = style.clone();
+ clone.rules = rulesByZ[zValues[i]];
+ this.writeNode("FeatureTypeStyle", clone, node);
+ }
+ }
+ } else {
+ this.writeNode("FeatureTypeStyle", style, node);
+ }
+
+ return node;
+ },
+ "IsDefault": function(bool) {
+ return this.createElementNSPlus(
+ "sld:IsDefault", {value: (bool) ? "1" : "0"}
+ );
+ },
+ "FeatureTypeStyle": function(style) {
+ var node = this.createElementNSPlus("sld:FeatureTypeStyle");
+
+ // OpenLayers currently stores no Name, Title, Abstract,
+ // FeatureTypeName, or SemanticTypeIdentifier information
+ // related to FeatureTypeStyle
+
+ // add in rules
+ for(var i=0, len=style.rules.length; i<len; ++i) {
+ this.writeNode("Rule", style.rules[i], node);
+ }
+
+ return node;
+ },
+ "Rule": function(rule) {
+ var node = this.createElementNSPlus("sld:Rule");
+
+ // add in optional name
+ if(rule.name) {
+ this.writeNode("Name", rule.name, node);
+ }
+ // add in optional title
+ if(rule.title) {
+ this.writeNode("Title", rule.title, node);
+ }
+ // add in optional description
+ if(rule.description) {
+ this.writeNode("Abstract", rule.description, node);
+ }
+
+ // add in LegendGraphic here
+
+ // add in optional filters
+ if(rule.elseFilter) {
+ this.writeNode("ElseFilter", null, node);
+ } else if(rule.filter) {
+ this.writeNode("ogc:Filter", rule.filter, node);
+ }
+
+ // add in scale limits
+ if(rule.minScaleDenominator != undefined) {
+ this.writeNode(
+ "MinScaleDenominator", rule.minScaleDenominator, node
+ );
+ }
+ if(rule.maxScaleDenominator != undefined) {
+ this.writeNode(
+ "MaxScaleDenominator", rule.maxScaleDenominator, node
+ );
+ }
+
+ var type, symbolizer;
+ if (this.multipleSymbolizers && rule.symbolizers) {
+ var symbolizer;
+ for (var i=0, ii=rule.symbolizers.length; i<ii; ++i) {
+ symbolizer = rule.symbolizers[i];
+ type = symbolizer.CLASS_NAME.split(".").pop();
+ this.writeNode(
+ type + "Symbolizer", symbolizer, node
+ );
+ }
+ } else {
+ // add in symbolizers (relies on geometry type keys)
+ var types = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+ for(var i=0, len=types.length; i<len; ++i) {
+ type = types[i];
+ symbolizer = rule.symbolizer[type];
+ if(symbolizer) {
+ this.writeNode(
+ type + "Symbolizer", symbolizer, node
+ );
+ }
+ }
+ }
+ return node;
+
+ },
+ "ElseFilter": function() {
+ return this.createElementNSPlus("sld:ElseFilter");
+ },
+ "MinScaleDenominator": function(scale) {
+ return this.createElementNSPlus(
+ "sld:MinScaleDenominator", {value: scale}
+ );
+ },
+ "MaxScaleDenominator": function(scale) {
+ return this.createElementNSPlus(
+ "sld:MaxScaleDenominator", {value: scale}
+ );
+ },
+ "LineSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:LineSymbolizer");
+ this.writeNode("Stroke", symbolizer, node);
+ return node;
+ },
+ "Stroke": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Stroke");
+
+ // GraphicFill here
+ // GraphicStroke here
+
+ // add in CssParameters
+ if(symbolizer.strokeColor != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeColor"},
+ node
+ );
+ }
+ if(symbolizer.strokeOpacity != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeOpacity"},
+ node
+ );
+ }
+ if(symbolizer.strokeWidth != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeWidth"},
+ node
+ );
+ }
+ if(symbolizer.strokeDashstyle != undefined && symbolizer.strokeDashstyle !== "solid") {
+ // assumes valid stroke-dasharray value
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeDashstyle"},
+ node
+ );
+ }
+ if(symbolizer.strokeLinecap != undefined) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "strokeLinecap"},
+ node
+ );
+ }
+ return node;
+ },
+ "CssParameter": function(obj) {
+ // not handling ogc:expressions for now
+ return this.createElementNSPlus("sld:CssParameter", {
+ attributes: {name: this.getCssProperty(obj.key)},
+ value: obj.symbolizer[obj.key]
+ });
+ },
+ "TextSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:TextSymbolizer");
+ // add in optional Label
+ if(symbolizer.label != null) {
+ this.writeNode("Label", symbolizer.label, node);
+ }
+ // add in optional Font
+ if(symbolizer.fontFamily != null ||
+ symbolizer.fontSize != null ||
+ symbolizer.fontWeight != null ||
+ symbolizer.fontStyle != null) {
+ this.writeNode("Font", symbolizer, node);
+ }
+ // add in optional LabelPlacement
+ if (symbolizer.labelAnchorPointX != null ||
+ symbolizer.labelAnchorPointY != null ||
+ symbolizer.labelAlign != null ||
+ symbolizer.labelXOffset != null ||
+ symbolizer.labelYOffset != null ||
+ symbolizer.labelRotation != null ||
+ symbolizer.labelPerpendicularOffset != null) {
+ this.writeNode("LabelPlacement", symbolizer, node);
+ }
+ // add in optional Halo
+ if(symbolizer.haloRadius != null ||
+ symbolizer.haloColor != null ||
+ symbolizer.haloOpacity != null) {
+ this.writeNode("Halo", symbolizer, node);
+ }
+ // add in optional Fill
+ if(symbolizer.fontColor != null ||
+ symbolizer.fontOpacity != null) {
+ this.writeNode("Fill", {
+ fillColor: symbolizer.fontColor,
+ fillOpacity: symbolizer.fontOpacity
+ }, node);
+ }
+ return node;
+ },
+ "LabelPlacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:LabelPlacement");
+ // PointPlacement and LinePlacement are choices, so don't output both
+ if ((symbolizer.labelAnchorPointX != null ||
+ symbolizer.labelAnchorPointY != null ||
+ symbolizer.labelAlign != null ||
+ symbolizer.labelXOffset != null ||
+ symbolizer.labelYOffset != null ||
+ symbolizer.labelRotation != null) &&
+ symbolizer.labelPerpendicularOffset == null) {
+ this.writeNode("PointPlacement", symbolizer, node);
+ }
+ if (symbolizer.labelPerpendicularOffset != null) {
+ this.writeNode("LinePlacement", symbolizer, node);
+ }
+ return node;
+ },
+ "LinePlacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:LinePlacement");
+ this.writeNode("PerpendicularOffset", symbolizer.labelPerpendicularOffset, node);
+ return node;
+ },
+ "PerpendicularOffset": function(value) {
+ return this.createElementNSPlus("sld:PerpendicularOffset", {
+ value: value
+ });
+ },
+ "PointPlacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:PointPlacement");
+ if (symbolizer.labelAnchorPointX != null ||
+ symbolizer.labelAnchorPointY != null ||
+ symbolizer.labelAlign != null) {
+ this.writeNode("AnchorPoint", symbolizer, node);
+ }
+ if (symbolizer.labelXOffset != null ||
+ symbolizer.labelYOffset != null) {
+ this.writeNode("Displacement", symbolizer, node);
+ }
+ if (symbolizer.labelRotation != null) {
+ this.writeNode("Rotation", symbolizer.labelRotation, node);
+ }
+ return node;
+ },
+ "AnchorPoint": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:AnchorPoint");
+ var x = symbolizer.labelAnchorPointX,
+ y = symbolizer.labelAnchorPointY;
+ if (x != null) {
+ this.writeNode("AnchorPointX", x, node);
+ }
+ if (y != null) {
+ this.writeNode("AnchorPointY", y, node);
+ }
+ if (x == null && y == null) {
+ var xAlign = symbolizer.labelAlign.substr(0, 1),
+ yAlign = symbolizer.labelAlign.substr(1, 1);
+ if (xAlign === "l") {
+ x = 0;
+ } else if (xAlign === "c") {
+ x = 0.5;
+ } else if (xAlign === "r") {
+ x = 1;
+ }
+ if (yAlign === "b") {
+ y = 0;
+ } else if (yAlign === "m") {
+ y = 0.5;
+ } else if (yAlign === "t") {
+ y = 1;
+ }
+ this.writeNode("AnchorPointX", x, node);
+ this.writeNode("AnchorPointY", y, node);
+ }
+ return node;
+ },
+ "AnchorPointX": function(value) {
+ return this.createElementNSPlus("sld:AnchorPointX", {
+ value: value
+ });
+ },
+ "AnchorPointY": function(value) {
+ return this.createElementNSPlus("sld:AnchorPointY", {
+ value: value
+ });
+ },
+ "Displacement": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Displacement");
+ if (symbolizer.labelXOffset != null) {
+ this.writeNode("DisplacementX", symbolizer.labelXOffset, node);
+ }
+ if (symbolizer.labelYOffset != null) {
+ this.writeNode("DisplacementY", symbolizer.labelYOffset, node);
+ }
+ return node;
+ },
+ "DisplacementX": function(value) {
+ return this.createElementNSPlus("sld:DisplacementX", {
+ value: value
+ });
+ },
+ "DisplacementY": function(value) {
+ return this.createElementNSPlus("sld:DisplacementY", {
+ value: value
+ });
+ },
+ "Font": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Font");
+ // add in CssParameters
+ if(symbolizer.fontFamily) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontFamily"},
+ node
+ );
+ }
+ if(symbolizer.fontSize) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontSize"},
+ node
+ );
+ }
+ if(symbolizer.fontWeight) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontWeight"},
+ node
+ );
+ }
+ if(symbolizer.fontStyle) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fontStyle"},
+ node
+ );
+ }
+ return node;
+ },
+ "Label": function(label) {
+ return this.writers.sld._OGCExpression.call(
+ this, "sld:Label", label
+ );
+ },
+ "Halo": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Halo");
+ if(symbolizer.haloRadius) {
+ this.writeNode("Radius", symbolizer.haloRadius, node);
+ }
+ if(symbolizer.haloColor || symbolizer.haloOpacity) {
+ this.writeNode("Fill", {
+ fillColor: symbolizer.haloColor,
+ fillOpacity: symbolizer.haloOpacity
+ }, node);
+ }
+ return node;
+ },
+ "Radius": function(value) {
+ return this.createElementNSPlus("sld:Radius", {
+ value: value
+ });
+ },
+ "RasterSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:RasterSymbolizer");
+ if (symbolizer.geometry) {
+ this.writeNode("Geometry", symbolizer.geometry, node);
+ }
+ if (symbolizer.opacity) {
+ this.writeNode("Opacity", symbolizer.opacity, node);
+ }
+ if (symbolizer.colorMap) {
+ this.writeNode("ColorMap", symbolizer.colorMap, node);
+ }
+ return node;
+ },
+ "Geometry": function(geometry) {
+ var node = this.createElementNSPlus("sld:Geometry");
+ if (geometry.property) {
+ this.writeNode("ogc:PropertyName", geometry, node);
+ }
+ return node;
+ },
+ "ColorMap": function(colorMap) {
+ var node = this.createElementNSPlus("sld:ColorMap");
+ for (var i=0, len=colorMap.length; i<len; ++i) {
+ this.writeNode("ColorMapEntry", colorMap[i], node);
+ }
+ return node;
+ },
+ "ColorMapEntry": function(colorMapEntry) {
+ var node = this.createElementNSPlus("sld:ColorMapEntry");
+ var a = colorMapEntry;
+ node.setAttribute("color", a.color);
+ a.opacity !== undefined && node.setAttribute("opacity",
+ parseFloat(a.opacity));
+ a.quantity !== undefined && node.setAttribute("quantity",
+ parseFloat(a.quantity));
+ a.label !== undefined && node.setAttribute("label", a.label);
+ return node;
+ },
+ "PolygonSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:PolygonSymbolizer");
+ if(symbolizer.fill !== false) {
+ this.writeNode("Fill", symbolizer, node);
+ }
+ if(symbolizer.stroke !== false) {
+ this.writeNode("Stroke", symbolizer, node);
+ }
+ return node;
+ },
+ "Fill": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Fill");
+
+ // GraphicFill here
+
+ // add in CssParameters
+ if(symbolizer.fillColor) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fillColor"},
+ node
+ );
+ }
+ if(symbolizer.fillOpacity != null) {
+ this.writeNode(
+ "CssParameter",
+ {symbolizer: symbolizer, key: "fillOpacity"},
+ node
+ );
+ }
+ return node;
+ },
+ "PointSymbolizer": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:PointSymbolizer");
+ this.writeNode("Graphic", symbolizer, node);
+ return node;
+ },
+ "Graphic": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Graphic");
+ if(symbolizer.externalGraphic != undefined) {
+ this.writeNode("ExternalGraphic", symbolizer, node);
+ } else {
+ this.writeNode("Mark", symbolizer, node);
+ }
+
+ if(symbolizer.graphicOpacity != undefined) {
+ this.writeNode("Opacity", symbolizer.graphicOpacity, node);
+ }
+ if(symbolizer.pointRadius != undefined) {
+ this.writeNode("Size", symbolizer.pointRadius * 2, node);
+ } else if (symbolizer.graphicWidth != undefined) {
+ this.writeNode("Size", symbolizer.graphicWidth, node);
+ }
+ if(symbolizer.rotation != undefined) {
+ this.writeNode("Rotation", symbolizer.rotation, node);
+ }
+ return node;
+ },
+ "ExternalGraphic": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:ExternalGraphic");
+ this.writeNode(
+ "OnlineResource", symbolizer.externalGraphic, node
+ );
+ var format = symbolizer.graphicFormat ||
+ this.getGraphicFormat(symbolizer.externalGraphic);
+ this.writeNode("Format", format, node);
+ return node;
+ },
+ "Mark": function(symbolizer) {
+ var node = this.createElementNSPlus("sld:Mark");
+ if(symbolizer.graphicName) {
+ this.writeNode("WellKnownName", symbolizer.graphicName, node);
+ }
+ if (symbolizer.fill !== false) {
+ this.writeNode("Fill", symbolizer, node);
+ }
+ if (symbolizer.stroke !== false) {
+ this.writeNode("Stroke", symbolizer, node);
+ }
+ return node;
+ },
+ "WellKnownName": function(name) {
+ return this.createElementNSPlus("sld:WellKnownName", {
+ value: name
+ });
+ },
+ "Opacity": function(value) {
+ return this.createElementNSPlus("sld:Opacity", {
+ value: value
+ });
+ },
+ "Size": function(value) {
+ return this.writers.sld._OGCExpression.call(
+ this, "sld:Size", value
+ );
+ },
+ "Rotation": function(value) {
+ return this.createElementNSPlus("sld:Rotation", {
+ value: value
+ });
+ },
+ "OnlineResource": function(href) {
+ return this.createElementNSPlus("sld:OnlineResource", {
+ attributes: {
+ "xlink:type": "simple",
+ "xlink:href": href
+ }
+ });
+ },
+ "Format": function(format) {
+ return this.createElementNSPlus("sld:Format", {
+ value: format
+ });
+ }
+ }
+ }, OpenLayers.Format.Filter.v1_0_0.prototype.writers),
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0.js
new file mode 100644
index 0000000..e920b50
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0.js
@@ -0,0 +1,46 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1_0_0
+ * Write SLD version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.SLD.v1>
+ */
+OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.SLD.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/sld
+ * http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd
+ */
+ schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.SLD> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js b/misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js
new file mode 100644
index 0000000..902da67
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js
@@ -0,0 +1,149 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD/v1_0_0_GeoServer
+ * Read and write SLD version 1.0.0 with GeoServer-specific enhanced options.
+ * See http://svn.osgeo.org/geotools/trunk/modules/extension/xsd/xsd-sld/src/main/resources/org/geotools/sld/bindings/StyledLayerDescriptor.xsd
+ * for more information.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.SLD.v1_0_0>
+ */
+OpenLayers.Format.SLD.v1_0_0_GeoServer = OpenLayers.Class(
+ OpenLayers.Format.SLD.v1_0_0, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.0.0",
+
+ /**
+ * Property: profile
+ * {String} The specific profile
+ */
+ profile: "GeoServer",
+
+ /**
+ * Constructor: OpenLayers.Format.SLD.v1_0_0_GeoServer
+ * Create a new parser for GeoServer-enhanced SLD version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: OpenLayers.Util.applyDefaults({
+ "sld": OpenLayers.Util.applyDefaults({
+ "Priority": function(node, obj) {
+ var value = this.readers.ogc._expression.call(this, node);
+ if (value) {
+ obj.priority = value;
+ }
+ },
+ "VendorOption": function(node, obj) {
+ if (!obj.vendorOptions) {
+ obj.vendorOptions = {};
+ }
+ obj.vendorOptions[node.getAttribute("name")] = this.getChildValue(node);
+ },
+ "TextSymbolizer": function(node, rule) {
+ OpenLayers.Format.SLD.v1_0_0.prototype.readers.sld.TextSymbolizer.apply(this, arguments);
+ var symbolizer = this.multipleSymbolizers ? rule.symbolizers[rule.symbolizers.length-1] : rule.symbolizer["Text"];
+ if (symbolizer.graphic === undefined) {
+ symbolizer.graphic = false;
+ }
+ }
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.readers["sld"])
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.readers),
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: OpenLayers.Util.applyDefaults({
+ "sld": OpenLayers.Util.applyDefaults({
+ "Priority": function(priority) {
+ return this.writers.sld._OGCExpression.call(
+ this, "sld:Priority", priority
+ );
+ },
+ "VendorOption": function(option) {
+ return this.createElementNSPlus("sld:VendorOption", {
+ attributes: {name: option.name},
+ value: option.value
+ });
+ },
+ "TextSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["TextSymbolizer"].apply(this, arguments);
+ if (symbolizer.graphic !== false && (symbolizer.externalGraphic || symbolizer.graphicName)) {
+ this.writeNode("Graphic", symbolizer, node);
+ }
+ if ("priority" in symbolizer) {
+ this.writeNode("Priority", symbolizer.priority, node);
+ }
+ return this.addVendorOptions(node, symbolizer);
+ },
+ "PointSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["PointSymbolizer"].apply(this, arguments);
+ return this.addVendorOptions(node, symbolizer);
+ },
+ "LineSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["LineSymbolizer"].apply(this, arguments);
+ return this.addVendorOptions(node, symbolizer);
+ },
+ "PolygonSymbolizer": function(symbolizer) {
+ var writers = OpenLayers.Format.SLD.v1_0_0.prototype.writers;
+ var node = writers["sld"]["PolygonSymbolizer"].apply(this, arguments);
+ return this.addVendorOptions(node, symbolizer);
+ }
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.writers["sld"])
+ }, OpenLayers.Format.SLD.v1_0_0.prototype.writers),
+
+ /**
+ * Method: addVendorOptions
+ * Add in the VendorOption tags and return the node again.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM node.
+ * symbolizer - {Object}
+ *
+ * Returns:
+ * {DOMElement} A DOM node.
+ */
+ addVendorOptions: function(node, symbolizer) {
+ var options = symbolizer.vendorOptions;
+ if (options) {
+ for (var key in symbolizer.vendorOptions) {
+ this.writeNode("VendorOption", {
+ name: key,
+ value: symbolizer.vendorOptions[key]
+ }, node);
+ }
+ }
+ return node;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0_GeoServer"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SOSCapabilities.js b/misc/openlayers/lib/OpenLayers/Format/SOSCapabilities.js
new file mode 100644
index 0000000..1abb1c8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SOSCapabilities.js
@@ -0,0 +1,48 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSCapabilities
+ * Read SOS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.SOSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Format.SOSCapabilities
+ * Create a new parser for SOS Capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return information about
+ * the service (offering and observedProperty mostly).
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Info about the SOS
+ */
+
+ CLASS_NAME: "OpenLayers.Format.SOSCapabilities"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SOSCapabilities/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/SOSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..89c0e91
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SOSCapabilities/v1_0_0.js
@@ -0,0 +1,158 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SOSCapabilities.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSCapabilities.v1_0_0
+ * Read SOS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.SOSCapabilities>
+ */
+OpenLayers.Format.SOSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.SOSCapabilities, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ sos: "http://www.opengis.net/sos/1.0",
+ gml: "http://www.opengis.net/gml",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SOSCapabilities.v1_0_0
+ * Create a new parser for SOS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.options = options;
+ },
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return info about the SOS.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the SOS service.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "gml": OpenLayers.Util.applyDefaults({
+ "name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "TimePeriod": function(node, obj) {
+ obj.timePeriod = {};
+ this.readChildNodes(node, obj.timePeriod);
+ },
+ "beginPosition": function(node, timePeriod) {
+ timePeriod.beginPosition = this.getChildValue(node);
+ },
+ "endPosition": function(node, timePeriod) {
+ timePeriod.endPosition = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.GML.v3.prototype.readers["gml"]),
+ "sos": {
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Contents": function(node, obj) {
+ obj.contents = {};
+ this.readChildNodes(node, obj.contents);
+ },
+ "ObservationOfferingList": function(node, contents) {
+ contents.offeringList = {};
+ this.readChildNodes(node, contents.offeringList);
+ },
+ "ObservationOffering": function(node, offeringList) {
+ var id = this.getAttributeNS(node, this.namespaces.gml, "id");
+ offeringList[id] = {
+ procedures: [],
+ observedProperties: [],
+ featureOfInterestIds: [],
+ responseFormats: [],
+ resultModels: [],
+ responseModes: []
+ };
+ this.readChildNodes(node, offeringList[id]);
+ },
+ "time": function(node, offering) {
+ offering.time = {};
+ this.readChildNodes(node, offering.time);
+ },
+ "procedure": function(node, offering) {
+ offering.procedures.push(this.getAttributeNS(node,
+ this.namespaces.xlink, "href"));
+ },
+ "observedProperty": function(node, offering) {
+ offering.observedProperties.push(this.getAttributeNS(node,
+ this.namespaces.xlink, "href"));
+ },
+ "featureOfInterest": function(node, offering) {
+ offering.featureOfInterestIds.push(this.getAttributeNS(node,
+ this.namespaces.xlink, "href"));
+ },
+ "responseFormat": function(node, offering) {
+ offering.responseFormats.push(this.getChildValue(node));
+ },
+ "resultModel": function(node, offering) {
+ offering.resultModels.push(this.getChildValue(node));
+ },
+ "responseMode": function(node, offering) {
+ offering.responseModes.push(this.getChildValue(node));
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SOSCapabilities.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SOSGetFeatureOfInterest.js b/misc/openlayers/lib/OpenLayers/Format/SOSGetFeatureOfInterest.js
new file mode 100644
index 0000000..aac2030
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SOSGetFeatureOfInterest.js
@@ -0,0 +1,190 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSGetFeatureOfInterest
+ * Read and write SOS GetFeatureOfInterest. This is used to get to
+ * the location of the features (stations). The stations can have 1 or more
+ * sensors.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SOSGetFeatureOfInterest = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ sos: "http://www.opengis.net/sos/1.0",
+ gml: "http://www.opengis.net/gml",
+ sa: "http://www.opengis.net/sampling/1.0",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sos",
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.SOSGetFeatureOfInterest
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Parse a GetFeatureOfInterest response and return an array of features
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+
+ var info = {features: []};
+ this.readNode(data, info);
+
+ var features = [];
+ for (var i=0, len=info.features.length; i<len; i++) {
+ var container = info.features[i];
+ // reproject features if needed
+ if(this.internalProjection && this.externalProjection &&
+ container.components[0]) {
+ container.components[0].transform(
+ this.externalProjection, this.internalProjection
+ );
+ }
+ var feature = new OpenLayers.Feature.Vector(
+ container.components[0], container.attributes);
+ features.push(feature);
+ }
+ return features;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "sa": {
+ "SamplingPoint": function(node, obj) {
+ // sampling point can also be without a featureMember if
+ // there is only 1
+ if (!obj.attributes) {
+ var feature = {attributes: {}};
+ obj.features.push(feature);
+ obj = feature;
+ }
+ obj.attributes.id = this.getAttributeNS(node,
+ this.namespaces.gml, "id");
+ this.readChildNodes(node, obj);
+ },
+ "position": function (node, obj) {
+ this.readChildNodes(node, obj);
+ }
+ },
+ "gml": OpenLayers.Util.applyDefaults({
+ "FeatureCollection": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "featureMember": function(node, obj) {
+ var feature = {attributes: {}};
+ obj.features.push(feature);
+ this.readChildNodes(node, feature);
+ },
+ "name": function(node, obj) {
+ obj.attributes.name = this.getChildValue(node);
+ },
+ "pos": function(node, obj) {
+ // we need to parse the srsName to get to the
+ // externalProjection, that's why we cannot use
+ // GML v3 for this
+ if (!this.externalProjection) {
+ this.externalProjection = new OpenLayers.Projection(
+ node.getAttribute("srsName"));
+ }
+ OpenLayers.Format.GML.v3.prototype.readers.gml.pos.apply(
+ this, [node, obj]);
+ }
+ }, OpenLayers.Format.GML.v3.prototype.readers.gml)
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "sos": {
+ "GetFeatureOfInterest": function(options) {
+ var node = this.createElementNSPlus("GetFeatureOfInterest", {
+ attributes: {
+ version: this.VERSION,
+ service: 'SOS',
+ "xsi:schemaLocation": this.schemaLocation
+ }
+ });
+ for (var i=0, len=options.fois.length; i<len; i++) {
+ this.writeNode("FeatureOfInterestId", {foi: options.fois[i]}, node);
+ }
+ return node;
+ },
+ "FeatureOfInterestId": function(options) {
+ var node = this.createElementNSPlus("FeatureOfInterestId", {value: options.foi});
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SOSGetFeatureOfInterest"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/SOSGetObservation.js b/misc/openlayers/lib/OpenLayers/Format/SOSGetObservation.js
new file mode 100644
index 0000000..9a6e2d7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/SOSGetObservation.js
@@ -0,0 +1,302 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/SOSGetFeatureOfInterest.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SOSGetObservation
+ * Read and write SOS GetObersation (to get the actual values from a sensor)
+ * version 1.0.0
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SOSGetObservation = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows",
+ gml: "http://www.opengis.net/gml",
+ sos: "http://www.opengis.net/sos/1.0",
+ ogc: "http://www.opengis.net/ogc",
+ om: "http://www.opengis.net/om/1.0",
+ sa: "http://www.opengis.net/sampling/1.0",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "sos",
+
+ /**
+ * Constructor: OpenLayers.Format.SOSGetObservation
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} An object containing the measurements
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var info = {measurements: [], observations: []};
+ this.readNode(data, info);
+ return info;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} An SOS GetObservation request XML string.
+ */
+ write: function(options) {
+ var node = this.writeNode("sos:GetObservation", options);
+ node.setAttribute("xmlns:om", this.namespaces.om);
+ node.setAttribute("xmlns:ogc", this.namespaces.ogc);
+ this.setAttributeNS(
+ node, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "om": {
+ "ObservationCollection": function(node, obj) {
+ obj.id = this.getAttributeNS(node, this.namespaces.gml, "id");
+ this.readChildNodes(node, obj);
+ },
+ "member": function(node, observationCollection) {
+ this.readChildNodes(node, observationCollection);
+ },
+ "Measurement": function(node, observationCollection) {
+ var measurement = {};
+ observationCollection.measurements.push(measurement);
+ this.readChildNodes(node, measurement);
+ },
+ "Observation": function(node, observationCollection) {
+ var observation = {};
+ observationCollection.observations.push(observation);
+ this.readChildNodes(node, observation);
+ },
+ "samplingTime": function(node, measurement) {
+ var samplingTime = {};
+ measurement.samplingTime = samplingTime;
+ this.readChildNodes(node, samplingTime);
+ },
+ "observedProperty": function(node, measurement) {
+ measurement.observedProperty =
+ this.getAttributeNS(node, this.namespaces.xlink, "href");
+ this.readChildNodes(node, measurement);
+ },
+ "procedure": function(node, measurement) {
+ measurement.procedure =
+ this.getAttributeNS(node, this.namespaces.xlink, "href");
+ this.readChildNodes(node, measurement);
+ },
+ "featureOfInterest": function(node, observation) {
+ var foi = {features: []};
+ observation.fois = [];
+ observation.fois.push(foi);
+ this.readChildNodes(node, foi);
+ // postprocessing to get actual features
+ var features = [];
+ for (var i=0, len=foi.features.length; i<len; i++) {
+ var feature = foi.features[i];
+ features.push(new OpenLayers.Feature.Vector(
+ feature.components[0], feature.attributes));
+ }
+ foi.features = features;
+ },
+ "result": function(node, measurement) {
+ var result = {};
+ measurement.result = result;
+ if (this.getChildValue(node) !== '') {
+ result.value = this.getChildValue(node);
+ result.uom = node.getAttribute("uom");
+ } else {
+ this.readChildNodes(node, result);
+ }
+ }
+ },
+ "sa": OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.sa,
+ "gml": OpenLayers.Util.applyDefaults({
+ "TimeInstant": function(node, samplingTime) {
+ var timeInstant = {};
+ samplingTime.timeInstant = timeInstant;
+ this.readChildNodes(node, timeInstant);
+ },
+ "timePosition": function(node, timeInstant) {
+ timeInstant.timePosition = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.SOSGetFeatureOfInterest.prototype.readers.gml)
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "sos": {
+ "GetObservation": function(options) {
+ var node = this.createElementNSPlus("GetObservation", {
+ attributes: {
+ version: this.VERSION,
+ service: 'SOS'
+ }
+ });
+ this.writeNode("offering", options, node);
+ if (options.eventTime) {
+ this.writeNode("eventTime", options, node);
+ }
+ for (var procedure in options.procedures) {
+ this.writeNode("procedure", options.procedures[procedure], node);
+ }
+ for (var observedProperty in options.observedProperties) {
+ this.writeNode("observedProperty", options.observedProperties[observedProperty], node);
+ }
+ if (options.foi) {
+ this.writeNode("featureOfInterest", options.foi, node);
+ }
+ this.writeNode("responseFormat", options, node);
+ if (options.resultModel) {
+ this.writeNode("resultModel", options, node);
+ }
+ if (options.responseMode) {
+ this.writeNode("responseMode", options, node);
+ }
+ return node;
+ },
+ "featureOfInterest": function(foi) {
+ var node = this.createElementNSPlus("featureOfInterest");
+ this.writeNode("ObjectID", foi.objectId, node);
+ return node;
+ },
+ "ObjectID": function(options) {
+ return this.createElementNSPlus("ObjectID",
+ {value: options});
+ },
+ "responseFormat": function(options) {
+ return this.createElementNSPlus("responseFormat",
+ {value: options.responseFormat});
+ },
+ "procedure": function(procedure) {
+ return this.createElementNSPlus("procedure",
+ {value: procedure});
+ },
+ "offering": function(options) {
+ return this.createElementNSPlus("offering", {value:
+ options.offering});
+ },
+ "observedProperty": function(observedProperty) {
+ return this.createElementNSPlus("observedProperty",
+ {value: observedProperty});
+ },
+ "eventTime": function(options) {
+ var node = this.createElementNSPlus("eventTime");
+ if (options.eventTime === 'latest') {
+ this.writeNode("ogc:TM_Equals", options, node);
+ }
+ return node;
+ },
+ "resultModel": function(options) {
+ return this.createElementNSPlus("resultModel", {value:
+ options.resultModel});
+ },
+ "responseMode": function(options) {
+ return this.createElementNSPlus("responseMode", {value:
+ options.responseMode});
+ }
+ },
+ "ogc": {
+ "TM_Equals": function(options) {
+ var node = this.createElementNSPlus("ogc:TM_Equals");
+ this.writeNode("ogc:PropertyName", {property:
+ "urn:ogc:data:time:iso8601"}, node);
+ if (options.eventTime === 'latest') {
+ this.writeNode("gml:TimeInstant", {value: 'latest'}, node);
+ }
+ return node;
+ },
+ "PropertyName": function(options) {
+ return this.createElementNSPlus("ogc:PropertyName",
+ {value: options.property});
+ }
+ },
+ "gml": {
+ "TimeInstant": function(options) {
+ var node = this.createElementNSPlus("gml:TimeInstant");
+ this.writeNode("gml:timePosition", options, node);
+ return node;
+ },
+ "timePosition": function(options) {
+ var node = this.createElementNSPlus("gml:timePosition",
+ {value: options.value});
+ return node;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.SOSGetObservation"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/Text.js b/misc/openlayers/lib/OpenLayers/Format/Text.js
new file mode 100644
index 0000000..bf9bcd5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/Text.js
@@ -0,0 +1,151 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Text
+ * Read Text format. Create a new instance with the <OpenLayers.Format.Text>
+ * constructor. This reads text which is formatted like CSV text, using
+ * tabs as the seperator by default. It provides parsing of data originally
+ * used in the MapViewerService, described on the wiki. This Format is used
+ * by the <OpenLayers.Layer.Text> class.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.Text = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * APIProperty: defaultStyle
+ * defaultStyle allows one to control the default styling of the features.
+ * It should be a symbolizer hash. By default, this is set to match the
+ * Layer.Text behavior, which is to use the default OpenLayers Icon.
+ */
+ defaultStyle: null,
+
+ /**
+ * APIProperty: extractStyles
+ * set to true to extract styles from the TSV files, using information
+ * from the image or icon, iconSize and iconOffset fields. This will result
+ * in features with a symbolizer (style) property set, using the
+ * default symbolizer specified in <defaultStyle>. Set to false if you
+ * wish to use a styleMap or OpenLayers.Style options to style your
+ * layer instead.
+ */
+ extractStyles: true,
+
+ /**
+ * Constructor: OpenLayers.Format.Text
+ * Create a new parser for TSV Text.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ options = options || {};
+
+ if(options.extractStyles !== false) {
+ options.defaultStyle = {
+ 'externalGraphic': OpenLayers.Util.getImageLocation("marker.png"),
+ 'graphicWidth': 21,
+ 'graphicHeight': 25,
+ 'graphicXOffset': -10.5,
+ 'graphicYOffset': -12.5
+ };
+ }
+
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Return a list of features from a Tab Seperated Values text string.
+ *
+ * Parameters:
+ * text - {String}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>})
+ */
+ read: function(text) {
+ var lines = text.split('\n');
+ var columns;
+ var features = [];
+ // length - 1 to allow for trailing new line
+ for (var lcv = 0; lcv < (lines.length - 1); lcv++) {
+ var currLine = lines[lcv].replace(/^\s*/,'').replace(/\s*$/,'');
+
+ if (currLine.charAt(0) != '#') { /* not a comment */
+
+ if (!columns) {
+ //First line is columns
+ columns = currLine.split('\t');
+ } else {
+ var vals = currLine.split('\t');
+ var geometry = new OpenLayers.Geometry.Point(0,0);
+ var attributes = {};
+ var style = this.defaultStyle ?
+ OpenLayers.Util.applyDefaults({}, this.defaultStyle) :
+ null;
+ var icon, iconSize, iconOffset, overflow;
+ var set = false;
+ for (var valIndex = 0; valIndex < vals.length; valIndex++) {
+ if (vals[valIndex]) {
+ if (columns[valIndex] == 'point') {
+ var coords = vals[valIndex].split(',');
+ geometry.y = parseFloat(coords[0]);
+ geometry.x = parseFloat(coords[1]);
+ set = true;
+ } else if (columns[valIndex] == 'lat') {
+ geometry.y = parseFloat(vals[valIndex]);
+ set = true;
+ } else if (columns[valIndex] == 'lon') {
+ geometry.x = parseFloat(vals[valIndex]);
+ set = true;
+ } else if (columns[valIndex] == 'title')
+ attributes['title'] = vals[valIndex];
+ else if (columns[valIndex] == 'image' ||
+ columns[valIndex] == 'icon' && style) {
+ style['externalGraphic'] = vals[valIndex];
+ } else if (columns[valIndex] == 'iconSize' && style) {
+ var size = vals[valIndex].split(',');
+ style['graphicWidth'] = parseFloat(size[0]);
+ style['graphicHeight'] = parseFloat(size[1]);
+ } else if (columns[valIndex] == 'iconOffset' && style) {
+ var offset = vals[valIndex].split(',');
+ style['graphicXOffset'] = parseFloat(offset[0]);
+ style['graphicYOffset'] = parseFloat(offset[1]);
+ } else if (columns[valIndex] == 'description') {
+ attributes['description'] = vals[valIndex];
+ } else if (columns[valIndex] == 'overflow') {
+ attributes['overflow'] = vals[valIndex];
+ } else {
+ // For StyleMap filtering, allow additional
+ // columns to be stored as attributes.
+ attributes[columns[valIndex]] = vals[valIndex];
+ }
+ }
+ }
+ if (set) {
+ if (this.internalProjection && this.externalProjection) {
+ geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ var feature = new OpenLayers.Feature.Vector(geometry, attributes, style);
+ features.push(feature);
+ }
+ }
+ }
+ }
+ return features;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.Text"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities.js b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities.js
new file mode 100644
index 0000000..934aaa5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities.js
@@ -0,0 +1,47 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities
+ * Read WCS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WCSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WCSCapabilities
+ * Create a new parser for WCS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of coverages.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named coverages.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1.js b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1.js
new file mode 100644
index 0000000..bf8da3b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1.js
@@ -0,0 +1,55 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WCSCapabilities.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities.v1
+ * Abstract class not to be instantiated directly.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WCSCapabilities.v1 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ splitSpace: (/\s+/)
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wcs",
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of coverages.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named coverages.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var raw = data;
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1"
+
+}); \ No newline at end of file
diff --git a/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..4dfa0b8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_0_0.js
@@ -0,0 +1,170 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WCSCapabilities/v1.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities/v1_0_0
+ * Read WCS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WCSCapabilities.v1>
+ */
+OpenLayers.Format.WCSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.WCSCapabilities.v1, {
+
+ /**
+ * Constructor: OpenLayers.Format.WCSCapabilities.v1_0_0
+ * Create a new parser for WCS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wcs: "http://www.opengis.net/wcs",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ ows: "http://www.opengis.net/ows"
+ },
+
+ /**
+ * Property: errorProperty
+ * {String} Which property of the returned object to check for in order to
+ * determine whether or not parsing has failed. In the case that the
+ * errorProperty is undefined on the returned object, the document will be
+ * run through an OGCExceptionReport parser.
+ */
+ errorProperty: "service",
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wcs": {
+ "WCS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Service": function(node, obj) {
+ obj.service = {};
+ this.readChildNodes(node, obj.service);
+ },
+ "name": function(node, service) {
+ service.name = this.getChildValue(node);
+ },
+ "label": function(node, service) {
+ service.label = this.getChildValue(node);
+ },
+ "keywords": function(node, service) {
+ service.keywords = [];
+ this.readChildNodes(node, service.keywords);
+ },
+ "keyword": function(node, keywords) {
+ // Append the keyword to the keywords list
+ keywords.push(this.getChildValue(node));
+ },
+ "responsibleParty": function(node, service) {
+ service.responsibleParty = {};
+ this.readChildNodes(node, service.responsibleParty);
+ },
+ "individualName": function(node, responsibleParty) {
+ responsibleParty.individualName = this.getChildValue(node);
+ },
+ "organisationName": function(node, responsibleParty) {
+ responsibleParty.organisationName = this.getChildValue(node);
+ },
+ "positionName": function(node, responsibleParty) {
+ responsibleParty.positionName = this.getChildValue(node);
+ },
+ "contactInfo": function(node, responsibleParty) {
+ responsibleParty.contactInfo = {};
+ this.readChildNodes(node, responsibleParty.contactInfo);
+ },
+ "phone": function(node, contactInfo) {
+ contactInfo.phone = {};
+ this.readChildNodes(node, contactInfo.phone);
+ },
+ "voice": function(node, phone) {
+ phone.voice = this.getChildValue(node);
+ },
+ "facsimile": function(node, phone) {
+ phone.facsimile = this.getChildValue(node);
+ },
+ "address": function(node, contactInfo) {
+ contactInfo.address = {};
+ this.readChildNodes(node, contactInfo.address);
+ },
+ "deliveryPoint": function(node, address) {
+ address.deliveryPoint = this.getChildValue(node);
+ },
+ "city": function(node, address) {
+ address.city = this.getChildValue(node);
+ },
+ "postalCode": function(node, address) {
+ address.postalCode = this.getChildValue(node);
+ },
+ "country": function(node, address) {
+ address.country = this.getChildValue(node);
+ },
+ "electronicMailAddress": function(node, address) {
+ address.electronicMailAddress = this.getChildValue(node);
+ },
+ "fees": function(node, service) {
+ service.fees = this.getChildValue(node);
+ },
+ "accessConstraints": function(node, service) {
+ service.accessConstraints = this.getChildValue(node);
+ },
+ "ContentMetadata": function(node, obj) {
+ obj.contentMetadata = [];
+ this.readChildNodes(node, obj.contentMetadata);
+ },
+ "CoverageOfferingBrief": function(node, contentMetadata) {
+ var coverageOfferingBrief = {};
+ this.readChildNodes(node, coverageOfferingBrief);
+ contentMetadata.push(coverageOfferingBrief);
+ },
+ "name": function(node, coverageOfferingBrief) {
+ coverageOfferingBrief.name = this.getChildValue(node);
+ },
+ "label": function(node, coverageOfferingBrief) {
+ coverageOfferingBrief.label = this.getChildValue(node);
+ },
+ "lonLatEnvelope": function(node, coverageOfferingBrief) {
+ var nodeList = this.getElementsByTagNameNS(node, "http://www.opengis.net/gml", "pos");
+
+ // We expect two nodes here, to create the corners of a bounding box
+ if(nodeList.length == 2) {
+ var min = {};
+ var max = {};
+
+ OpenLayers.Format.GML.v3.prototype.readers["gml"].pos.apply(this, [nodeList[0], min]);
+ OpenLayers.Format.GML.v3.prototype.readers["gml"].pos.apply(this, [nodeList[1], max]);
+
+ coverageOfferingBrief.lonLatEnvelope = {};
+ coverageOfferingBrief.lonLatEnvelope.srsName = node.getAttribute("srsName");
+ coverageOfferingBrief.lonLatEnvelope.min = min.points[0];
+ coverageOfferingBrief.lonLatEnvelope.max = max.points[0];
+ }
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_1_0.js
new file mode 100644
index 0000000..1753c51
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WCSCapabilities/v1_1_0.js
@@ -0,0 +1,109 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WCSCapabilities/v1.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSCapabilities/v1_1_0
+ * Read WCS Capabilities version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WCSCapabilities.v1>
+ */
+OpenLayers.Format.WCSCapabilities.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WCSCapabilities.v1, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wcs: "http://www.opengis.net/wcs/1.1",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ ows: "http://www.opengis.net/ows/1.1"
+ },
+
+ /**
+ * APIProperty: errorProperty
+ * {String} Which property of the returned object to check for in order to
+ * determine whether or not parsing has failed. In the case that the
+ * errorProperty is undefined on the returned object, the document will be
+ * run through an OGCExceptionReport parser.
+ */
+ errorProperty: "operationsMetadata",
+
+ /**
+ * Constructor: OpenLayers.Format.WCSCapabilities.v1_1_0
+ * Create a new parser for WCS capabilities version 1.1.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wcs": OpenLayers.Util.applyDefaults({
+ // In 1.0.0, this was WCS_Capabilties, in 1.1.0, it's Capabilities
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Contents": function(node, request) {
+ request.contentMetadata = [];
+ this.readChildNodes(node, request.contentMetadata);
+ },
+ "CoverageSummary": function(node, contentMetadata) {
+ var coverageSummary = {};
+ // Read the summary:
+ this.readChildNodes(node, coverageSummary);
+
+ // Add it to the contentMetadata array:
+ contentMetadata.push(coverageSummary);
+ },
+ "Identifier": function(node, coverageSummary) {
+ coverageSummary.identifier = this.getChildValue(node);
+ },
+ "Title": function(node, coverageSummary) {
+ coverageSummary.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, coverageSummary) {
+ coverageSummary["abstract"] = this.getChildValue(node);
+ },
+ "SupportedCRS": function(node, coverageSummary) {
+ var crs = this.getChildValue(node);
+ if(crs) {
+ if(!coverageSummary.supportedCRS) {
+ coverageSummary.supportedCRS = [];
+ }
+ coverageSummary.supportedCRS.push(crs);
+ }
+ },
+ "SupportedFormat": function(node, coverageSummary) {
+ var format = this.getChildValue(node);
+ if(format) {
+ if(!coverageSummary.supportedFormat) {
+ coverageSummary.supportedFormat = [];
+ }
+ coverageSummary.supportedFormat.push(format);
+ }
+ }
+ }, OpenLayers.Format.WCSCapabilities.v1.prototype.readers["wcs"]),
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSCapabilities.v1_1_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WCSGetCoverage.js b/misc/openlayers/lib/OpenLayers/Format/WCSGetCoverage.js
new file mode 100644
index 0000000..2817f28
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WCSGetCoverage.js
@@ -0,0 +1,199 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WCSGetCoverage version 1.1.0
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WCSGetCoverage = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ wcs: "http://www.opengis.net/wcs/1.1",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1.2
+ */
+ VERSION: "1.1.2",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/wcs/1.1 http://schemas.opengis.net/wcs/1.1/wcsGetCoverage.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.WCSGetCoverage
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} A WCS GetCoverage request XML string.
+ */
+ write: function(options) {
+ var node = this.writeNode("wcs:GetCoverage", options);
+ this.setAttributeNS(
+ node, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wcs": {
+ "GetCoverage": function(options) {
+ var node = this.createElementNSPlus("wcs:GetCoverage", {
+ attributes: {
+ version: options.version || this.VERSION,
+ service: 'WCS'
+ }
+ });
+ this.writeNode("ows:Identifier", options.identifier, node);
+ this.writeNode("wcs:DomainSubset", options.domainSubset, node);
+ this.writeNode("wcs:Output", options.output, node);
+ return node;
+ },
+ "DomainSubset": function(domainSubset) {
+ var node = this.createElementNSPlus("wcs:DomainSubset", {});
+ this.writeNode("ows:BoundingBox", domainSubset.boundingBox, node);
+ if (domainSubset.temporalSubset) {
+ this.writeNode("wcs:TemporalSubset", domainSubset.temporalSubset, node);
+ }
+ return node;
+ },
+ "TemporalSubset": function(temporalSubset) {
+ var node = this.createElementNSPlus("wcs:TemporalSubset", {});
+ for (var i=0, len=temporalSubset.timePeriods.length; i<len; ++i) {
+ this.writeNode("wcs:TimePeriod", temporalSubset.timePeriods[i], node);
+ }
+ return node;
+ },
+ "TimePeriod": function(timePeriod) {
+ var node = this.createElementNSPlus("wcs:TimePeriod", {});
+ this.writeNode("wcs:BeginPosition", timePeriod.begin, node);
+ this.writeNode("wcs:EndPosition", timePeriod.end, node);
+ if (timePeriod.resolution) {
+ this.writeNode("wcs:TimeResolution", timePeriod.resolution, node);
+ }
+ return node;
+ },
+ "BeginPosition": function(begin) {
+ var node = this.createElementNSPlus("wcs:BeginPosition", {
+ value: begin
+ });
+ return node;
+ },
+ "EndPosition": function(end) {
+ var node = this.createElementNSPlus("wcs:EndPosition", {
+ value: end
+ });
+ return node;
+ },
+ "TimeResolution": function(resolution) {
+ var node = this.createElementNSPlus("wcs:TimeResolution", {
+ value: resolution
+ });
+ return node;
+ },
+ "Output": function(output) {
+ var node = this.createElementNSPlus("wcs:Output", {
+ attributes: {
+ format: output.format,
+ store: output.store
+ }
+ });
+ if (output.gridCRS) {
+ this.writeNode("wcs:GridCRS", output.gridCRS, node);
+ }
+ return node;
+ },
+ "GridCRS": function(gridCRS) {
+ var node = this.createElementNSPlus("wcs:GridCRS", {});
+ this.writeNode("wcs:GridBaseCRS", gridCRS.baseCRS, node);
+ if (gridCRS.type) {
+ this.writeNode("wcs:GridType", gridCRS.type, node);
+ }
+ if (gridCRS.origin) {
+ this.writeNode("wcs:GridOrigin", gridCRS.origin, node);
+ }
+ this.writeNode("wcs:GridOffsets", gridCRS.offsets, node);
+ if (gridCRS.CS) {
+ this.writeNode("wcs:GridCS", gridCRS.CS, node);
+ }
+ return node;
+ },
+ "GridBaseCRS": function(baseCRS) {
+ return this.createElementNSPlus("wcs:GridBaseCRS", {
+ value: baseCRS
+ });
+ },
+ "GridOrigin": function(origin) {
+ return this.createElementNSPlus("wcs:GridOrigin", {
+ value: origin
+ });
+ },
+ "GridType": function(type) {
+ return this.createElementNSPlus("wcs:GridType", {
+ value: type
+ });
+ },
+ "GridOffsets": function(offsets) {
+ return this.createElementNSPlus("wcs:GridOffsets", {
+ value: offsets
+ });
+ },
+ "GridCS": function(CS) {
+ return this.createElementNSPlus("wcs:GridCS", {
+ value: CS
+ });
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WCSGetCoverage"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFS.js b/misc/openlayers/lib/OpenLayers/Format/WFS.js
new file mode 100644
index 0000000..44b03a3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFS.js
@@ -0,0 +1,223 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFS
+ * Read/Write WFS.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.GML>
+ */
+OpenLayers.Format.WFS = OpenLayers.Class(OpenLayers.Format.GML, {
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>}
+ */
+ layer: null,
+
+ /**
+ * APIProperty: wfsns
+ * {String}
+ */
+ wfsns: "http://www.opengis.net/wfs",
+
+ /**
+ * Property: ogcns
+ * {String}
+ */
+ ogcns: "http://www.opengis.net/ogc",
+
+ /**
+ * Constructor: OpenLayers.Format.WFS
+ * Create a WFS-T formatter. This requires a layer: that layer should
+ * have two properties: geometry_column and typename. The parser
+ * for this format is subclassed entirely from GML: There is a writer
+ * only, which uses most of the code from the GML layer, and wraps
+ * it in transactional elements.
+ *
+ * Parameters:
+ * options - {Object}
+ * layer - {<OpenLayers.Layer>}
+ */
+ initialize: function(options, layer) {
+ OpenLayers.Format.GML.prototype.initialize.apply(this, [options]);
+ this.layer = layer;
+ if (this.layer.featureNS) {
+ this.featureNS = this.layer.featureNS;
+ }
+ if (this.layer.options.geometry_column) {
+ this.geometryName = this.layer.options.geometry_column;
+ }
+ if (this.layer.options.typename) {
+ this.featureName = this.layer.options.typename;
+ }
+ },
+
+ /**
+ * Method: write
+ * Takes a feature list, and generates a WFS-T Transaction
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ write: function(features) {
+
+ var transaction = this.createElementNS(this.wfsns, 'wfs:Transaction');
+ transaction.setAttribute("version","1.0.0");
+ transaction.setAttribute("service","WFS");
+ for (var i=0; i < features.length; i++) {
+ switch (features[i].state) {
+ case OpenLayers.State.INSERT:
+ transaction.appendChild(this.insert(features[i]));
+ break;
+ case OpenLayers.State.UPDATE:
+ transaction.appendChild(this.update(features[i]));
+ break;
+ case OpenLayers.State.DELETE:
+ transaction.appendChild(this.remove(features[i]));
+ break;
+ }
+ }
+
+ return OpenLayers.Format.XML.prototype.write.apply(this,[transaction]);
+ },
+
+ /**
+ * Method: createFeatureXML
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ createFeatureXML: function(feature) {
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+ var geomContainer = this.createElementNS(this.featureNS, "feature:" + this.geometryName);
+ geomContainer.appendChild(geometryNode);
+ var featureContainer = this.createElementNS(this.featureNS, "feature:" + this.featureName);
+ featureContainer.appendChild(geomContainer);
+ for(var attr in feature.attributes) {
+ var attrText = this.createTextNode(feature.attributes[attr]);
+ var nodename = attr;
+ if (attr.search(":") != -1) {
+ nodename = attr.split(":")[1];
+ }
+ var attrContainer = this.createElementNS(this.featureNS, "feature:" + nodename);
+ attrContainer.appendChild(attrText);
+ featureContainer.appendChild(attrContainer);
+ }
+ return featureContainer;
+ },
+
+ /**
+ * Method: insert
+ * Takes a feature, and generates a WFS-T Transaction "Insert"
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ insert: function(feature) {
+ var insertNode = this.createElementNS(this.wfsns, 'wfs:Insert');
+ insertNode.appendChild(this.createFeatureXML(feature));
+ return insertNode;
+ },
+
+ /**
+ * Method: update
+ * Takes a feature, and generates a WFS-T Transaction "Update"
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ update: function(feature) {
+ if (!feature.fid) { OpenLayers.Console.userError(OpenLayers.i18n("noFID")); }
+ var updateNode = this.createElementNS(this.wfsns, 'wfs:Update');
+ updateNode.setAttribute("typeName", this.featurePrefix + ':' + this.featureName);
+ updateNode.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+
+ var propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+ var nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+
+ var txtNode = this.createTextNode(this.geometryName);
+ nameNode.appendChild(txtNode);
+ propertyNode.appendChild(nameNode);
+
+ var valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+
+ var geometryNode = this.buildGeometryNode(feature.geometry);
+
+ if(feature.layer){
+ geometryNode.setAttribute(
+ "srsName", feature.layer.projection.getCode()
+ );
+ }
+
+ valueNode.appendChild(geometryNode);
+
+ propertyNode.appendChild(valueNode);
+ updateNode.appendChild(propertyNode);
+
+ // add in attributes
+ for(var propName in feature.attributes) {
+ propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+ nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+ nameNode.appendChild(this.createTextNode(propName));
+ propertyNode.appendChild(nameNode);
+ valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+ valueNode.appendChild(this.createTextNode(feature.attributes[propName]));
+ propertyNode.appendChild(valueNode);
+ updateNode.appendChild(propertyNode);
+ }
+
+
+ var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+ var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+ filterIdNode.setAttribute("fid", feature.fid);
+ filterNode.appendChild(filterIdNode);
+ updateNode.appendChild(filterNode);
+
+ return updateNode;
+ },
+
+ /**
+ * Method: remove
+ * Takes a feature, and generates a WFS-T Transaction "Delete"
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ remove: function(feature) {
+ if (!feature.fid) {
+ OpenLayers.Console.userError(OpenLayers.i18n("noFID"));
+ return false;
+ }
+ var deleteNode = this.createElementNS(this.wfsns, 'wfs:Delete');
+ deleteNode.setAttribute("typeName", this.featurePrefix + ':' + this.featureName);
+ deleteNode.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+
+ var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+ var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+ filterIdNode.setAttribute("fid", feature.fid);
+ filterNode.appendChild(filterIdNode);
+ deleteNode.appendChild(filterNode);
+
+ return deleteNode;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Remove ciruclar ref to layer
+ */
+ destroy: function() {
+ this.layer = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities.js b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities.js
new file mode 100644
index 0000000..61af085
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities.js
@@ -0,0 +1,47 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities
+ * Read WFS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WFSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities
+ * Create a new parser for WFS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1.js b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1.js
new file mode 100644
index 0000000..c4ec517
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1.js
@@ -0,0 +1,129 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFSCapabilities.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities.v1
+ * Abstract class not to be instantiated directly.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wfs: "http://www.opengis.net/wfs",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ ows: "http://www.opengis.net/ows"
+ },
+
+
+ /**
+ * APIProperty: errorProperty
+ * {String} Which property of the returned object to check for in order to
+ * determine whether or not parsing has failed. In the case that the
+ * errorProperty is undefined on the returned object, the document will be
+ * run through an OGCExceptionReport parser.
+ */
+ errorProperty: "featureTypeList",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wfs",
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities.v1_1
+ * Create an instance of one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var raw = data;
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": {
+ "WFS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "FeatureTypeList": function(node, request) {
+ request.featureTypeList = {
+ featureTypes: []
+ };
+ this.readChildNodes(node, request.featureTypeList);
+ },
+ "FeatureType": function(node, featureTypeList) {
+ var featureType = {};
+ this.readChildNodes(node, featureType);
+ featureTypeList.featureTypes.push(featureType);
+ },
+ "Name": function(node, obj) {
+ var name = this.getChildValue(node);
+ if(name) {
+ var parts = name.split(":");
+ obj.name = parts.pop();
+ if(parts.length > 0) {
+ obj.featureNS = this.lookupNamespaceURI(node, parts[0]);
+ }
+ }
+ },
+ "Title": function(node, obj) {
+ var title = this.getChildValue(node);
+ if(title) {
+ obj.title = title;
+ }
+ },
+ "Abstract": function(node, obj) {
+ var abst = this.getChildValue(node);
+ if(abst) {
+ obj["abstract"] = abst;
+ }
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..6b202c7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js
@@ -0,0 +1,115 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFSCapabilities/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities/v1_0_0
+ * Read WFS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WFSCapabilities.v1>
+ */
+OpenLayers.Format.WFSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.WFSCapabilities.v1, {
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities.v1_0_0
+ * Create a new parser for WFS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "Service": function(node, capabilities) {
+ capabilities.service = {};
+ this.readChildNodes(node, capabilities.service);
+ },
+ "Fees": function(node, service) {
+ var fees = this.getChildValue(node);
+ if (fees && fees.toLowerCase() != "none") {
+ service.fees = fees;
+ }
+ },
+ "AccessConstraints": function(node, service) {
+ var constraints = this.getChildValue(node);
+ if (constraints && constraints.toLowerCase() != "none") {
+ service.accessConstraints = constraints;
+ }
+ },
+ "OnlineResource": function(node, service) {
+ var onlineResource = this.getChildValue(node);
+ if (onlineResource && onlineResource.toLowerCase() != "none") {
+ service.onlineResource = onlineResource;
+ }
+ },
+ "Keywords": function(node, service) {
+ var keywords = this.getChildValue(node);
+ if (keywords && keywords.toLowerCase() != "none") {
+ service.keywords = keywords.split(', ');
+ }
+ },
+ "Capability": function(node, capabilities) {
+ capabilities.capability = {};
+ this.readChildNodes(node, capabilities.capability);
+ },
+ "Request": function(node, obj) {
+ obj.request = {};
+ this.readChildNodes(node, obj.request);
+ },
+ "GetFeature": function(node, request) {
+ request.getfeature = {
+ href: {}, // DCPType
+ formats: [] // ResultFormat
+ };
+ this.readChildNodes(node, request.getfeature);
+ },
+ "ResultFormat": function(node, obj) {
+ var children = node.childNodes;
+ var childNode;
+ for(var i=0; i<children.length; i++) {
+ childNode = children[i];
+ if(childNode.nodeType == 1) {
+ obj.formats.push(childNode.nodeName);
+ }
+ }
+ },
+ "DCPType": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "HTTP": function(node, obj) {
+ this.readChildNodes(node, obj.href);
+ },
+ "Get": function(node, obj) {
+ obj.get = node.getAttribute("onlineResource");
+ },
+ "Post": function(node, obj) {
+ obj.post = node.getAttribute("onlineResource");
+ },
+ "SRS": function(node, obj) {
+ var srs = this.getChildValue(node);
+ if (srs) {
+ obj.srs = srs;
+ }
+ }
+ }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_1_0.js
new file mode 100644
index 0000000..84f6b4b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFSCapabilities/v1_1_0.js
@@ -0,0 +1,63 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFSCapabilities/v1.js
+ * @requires OpenLayers/Format/OWSCommon/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSCapabilities/v1_1_0
+ * Read WFS Capabilities version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WFSCapabilities>
+ */
+OpenLayers.Format.WFSCapabilities.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WFSCapabilities.v1, {
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFSCapabilities.v1_1_0
+ * Create a new parser for WFS capabilities version 1.1.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "DefaultSRS": function(node, obj) {
+ var defaultSRS = this.getChildValue(node);
+ if (defaultSRS) {
+ obj.srs = defaultSRS;
+ }
+ }
+ }, OpenLayers.Format.WFSCapabilities.v1.prototype.readers["wfs"]),
+ "ows": OpenLayers.Format.OWSCommon.v1.prototype.readers.ows
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1_1_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFSDescribeFeatureType.js b/misc/openlayers/lib/OpenLayers/Format/WFSDescribeFeatureType.js
new file mode 100644
index 0000000..416e845
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFSDescribeFeatureType.js
@@ -0,0 +1,234 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFSDescribeFeatureType
+ * Read WFS DescribeFeatureType response
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFSDescribeFeatureType = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g)
+ },
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xsd: "http://www.w3.org/2001/XMLSchema"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFSDescribeFeatureType
+ * Create a new parser for WFS DescribeFeatureType responses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "xsd": {
+ "schema": function(node, obj) {
+ var complexTypes = [];
+ var customTypes = {};
+ var schema = {
+ complexTypes: complexTypes,
+ customTypes: customTypes
+ };
+ var i, len;
+
+ this.readChildNodes(node, schema);
+
+ var attributes = node.attributes;
+ var attr, name;
+ for(i=0, len=attributes.length; i<len; ++i) {
+ attr = attributes[i];
+ name = attr.name;
+ if(name.indexOf("xmlns") === 0) {
+ this.setNamespace(name.split(":")[1] || "", attr.value);
+ } else {
+ obj[name] = attr.value;
+ }
+ }
+ obj.featureTypes = complexTypes;
+ obj.targetPrefix = this.namespaceAlias[obj.targetNamespace];
+
+ // map complexTypes to names of customTypes
+ var complexType, customType;
+ for(i=0, len=complexTypes.length; i<len; ++i) {
+ complexType = complexTypes[i];
+ customType = customTypes[complexType.typeName];
+ if(customTypes[complexType.typeName]) {
+ complexType.typeName = customType.name;
+ }
+ }
+ },
+ "complexType": function(node, obj) {
+ var complexType = {
+ // this is a temporary typeName, it will be overwritten by
+ // the schema reader with the metadata found in the
+ // customTypes hash
+ "typeName": node.getAttribute("name")
+ };
+ this.readChildNodes(node, complexType);
+ obj.complexTypes.push(complexType);
+ },
+ "complexContent": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "extension": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "sequence": function(node, obj) {
+ var sequence = {
+ elements: []
+ };
+ this.readChildNodes(node, sequence);
+ obj.properties = sequence.elements;
+ },
+ "element": function(node, obj) {
+ var type;
+ if(obj.elements) {
+ var element = {};
+ var attributes = node.attributes;
+ var attr;
+ for(var i=0, len=attributes.length; i<len; ++i) {
+ attr = attributes[i];
+ element[attr.name] = attr.value;
+ }
+
+ type = element.type;
+ if(!type) {
+ type = {};
+ this.readChildNodes(node, type);
+ element.restriction = type;
+ element.type = type.base;
+ }
+ var fullType = type.base || type;
+ element.localType = fullType.split(":").pop();
+ obj.elements.push(element);
+ this.readChildNodes(node, element);
+ }
+
+ if(obj.complexTypes) {
+ type = node.getAttribute("type");
+ var localType = type.split(":").pop();
+ obj.customTypes[localType] = {
+ "name": node.getAttribute("name"),
+ "type": type
+ };
+ }
+ },
+ "annotation": function(node, obj) {
+ obj.annotation = {};
+ this.readChildNodes(node, obj.annotation);
+ },
+ "appinfo": function(node, obj) {
+ if (!obj.appinfo) {
+ obj.appinfo = [];
+ }
+ obj.appinfo.push(this.getChildValue(node));
+ },
+ "documentation": function(node, obj) {
+ if (!obj.documentation) {
+ obj.documentation = [];
+ }
+ var value = this.getChildValue(node);
+ obj.documentation.push({
+ lang: node.getAttribute("xml:lang"),
+ textContent: value.replace(this.regExes.trimSpace, "")
+ });
+ },
+ "simpleType": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "restriction": function(node, obj) {
+ obj.base = node.getAttribute("base");
+ this.readRestriction(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: readRestriction
+ * Reads restriction defined in the child nodes of a restriction element
+ *
+ * Parameters:
+ * node - {DOMElement} the node to parse
+ * obj - {Object} the object that receives the read result
+ */
+ readRestriction: function(node, obj) {
+ var children = node.childNodes;
+ var child, nodeName, value;
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ nodeName = child.nodeName.split(":").pop();
+ value = child.getAttribute("value");
+ if(!obj[nodeName]) {
+ obj[nodeName] = value;
+ } else {
+ if(typeof obj[nodeName] == "string") {
+ obj[nodeName] = [obj[nodeName]];
+ }
+ obj[nodeName].push(value);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement|String} A WFS DescribeFeatureType document.
+ *
+ * Returns:
+ * {Object} An object representing the WFS DescribeFeatureType response.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var schema = {};
+ if (data.nodeName.split(":").pop() === 'ExceptionReport') {
+ // an exception must have occurred, so parse it
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ schema.error = parser.read(data);
+ } else {
+ this.readNode(data, schema);
+ }
+ return schema;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFSDescribeFeatureType"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFST.js b/misc/openlayers/lib/OpenLayers/Format/WFST.js
new file mode 100644
index 0000000..eb3d9d9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFST.js
@@ -0,0 +1,34 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Function: OpenLayers.Format.WFST
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Format>} A WFST format of the given version.
+ */
+OpenLayers.Format.WFST = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Format.WFST.DEFAULTS
+ );
+ var cls = OpenLayers.Format.WFST["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFST version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Format.WFST.DEFAULTS
+ * {Object} Default properties for the WFST format.
+ */
+OpenLayers.Format.WFST.DEFAULTS = {
+ "version": "1.0.0"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFST/v1.js b/misc/openlayers/lib/OpenLayers/Format/WFST/v1.js
new file mode 100644
index 0000000..306ba6f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFST/v1.js
@@ -0,0 +1,446 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/WFST.js
+ * @requires OpenLayers/Filter/Spatial.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1
+ * Superclass for WFST parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WFST.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance",
+ wfs: "http://www.opengis.net/wfs",
+ gml: "http://www.opengis.net/gml",
+ ogc: "http://www.opengis.net/ogc",
+ ows: "http://www.opengis.net/ows"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wfs",
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocations: null,
+
+ /**
+ * APIProperty: srsName
+ * {String} URI for spatial reference system.
+ */
+ srsName: null,
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Extract attributes from GML. Default is true.
+ */
+ extractAttributes: true,
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: stateName
+ * {Object} Maps feature states to node names.
+ */
+ stateName: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WFST.v1_0_0> or <OpenLayers.Format.WFST.v1_1_0>
+ * constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ // set state name mapping
+ this.stateName = {};
+ this.stateName[OpenLayers.State.INSERT] = "wfs:Insert";
+ this.stateName[OpenLayers.State.UPDATE] = "wfs:Update";
+ this.stateName[OpenLayers.State.DELETE] = "wfs:Delete";
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: getSrsName
+ */
+ getSrsName: function(feature, options) {
+ var srsName = options && options.srsName;
+ if(!srsName) {
+ if(feature && feature.layer) {
+ srsName = feature.layer.projection.getCode();
+ } else {
+ srsName = this.srsName;
+ }
+ }
+ return srsName;
+ },
+
+ /**
+ * APIMethod: read
+ * Parse the response from a transaction. Because WFS is split into
+ * Transaction requests (create, update, and delete) and GetFeature
+ * requests (read), this method handles parsing of both types of
+ * responses.
+ *
+ * Parameters:
+ * data - {String | Document} The WFST document to read
+ * options - {Object} Options for the reader
+ *
+ * Valid options properties:
+ * output - {String} either "features" or "object". The default is
+ * "features", which means that the method will return an array of
+ * features. If set to "object", an object with a "features" property
+ * and other properties read by the parser will be returned.
+ *
+ * Returns:
+ * {Array | Object} Output depending on the output option.
+ */
+ read: function(data, options) {
+ options = options || {};
+ OpenLayers.Util.applyDefaults(options, {
+ output: "features"
+ });
+
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var obj = {};
+ if(data) {
+ this.readNode(data, obj, true);
+ }
+ if(obj.features && options.output === "features") {
+ obj = obj.features;
+ }
+ return obj;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": {
+ "FeatureCollection": function(node, obj) {
+ obj.features = [];
+ this.readChildNodes(node, obj);
+ }
+ }
+ },
+
+ /**
+ * Method: write
+ * Given an array of features, write a WFS transaction. This assumes
+ * the features have a state property that determines the operation
+ * type - insert, update, or delete.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} A list of features. See
+ * below for a more detailed description of the influence of the
+ * feature's *modified* property.
+ * options - {Object}
+ *
+ * feature.modified rules:
+ * If a feature has a modified property set, the following checks will be
+ * made before a feature's geometry or attribute is included in an Update
+ * transaction:
+ * - *modified* is not set at all: The geometry and all attributes will be
+ * included.
+ * - *modified.geometry* is set (null or a geometry): The geometry will be
+ * included. If *modified.attributes* is not set, all attributes will
+ * be included.
+ * - *modified.attributes* is set: Only the attributes set (i.e. to null or
+ * a value) in *modified.attributes* will be included.
+ * If *modified.geometry* is not set, the geometry will not be included.
+ *
+ * Valid options include:
+ * - *multi* {Boolean} If set to true, geometries will be casted to
+ * Multi geometries before writing.
+ *
+ * Returns:
+ * {String} A serialized WFS transaction.
+ */
+ write: function(features, options) {
+ var node = this.writeNode("wfs:Transaction", {
+ features:features,
+ options: options
+ });
+ var value = this.schemaLocationAttr();
+ if(value) {
+ this.setAttributeNS(
+ node, this.namespaces["xsi"], "xsi:schemaLocation", value
+ );
+ }
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": {
+ "GetFeature": function(options) {
+ var node = this.createElementNSPlus("wfs:GetFeature", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options && options.handle,
+ outputFormat: options && options.outputFormat,
+ maxFeatures: options && options.maxFeatures,
+ "xsi:schemaLocation": this.schemaLocationAttr(options)
+ }
+ });
+ if (typeof this.featureType == "string") {
+ this.writeNode("Query", options, node);
+ } else {
+ for (var i=0,len = this.featureType.length; i<len; i++) {
+ options.featureType = this.featureType[i];
+ this.writeNode("Query", options, node);
+ }
+ }
+ return node;
+ },
+ "Transaction": function(obj) {
+ obj = obj || {};
+ var options = obj.options || {};
+ var node = this.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version,
+ handle: options.handle
+ }
+ });
+ var i, len;
+ var features = obj.features;
+ if(features) {
+ // temporarily re-assigning geometry types
+ if (options.multi === true) {
+ OpenLayers.Util.extend(this.geometryTypes, {
+ "OpenLayers.Geometry.Point": "MultiPoint",
+ "OpenLayers.Geometry.LineString": (this.multiCurve === true) ? "MultiCurve": "MultiLineString",
+ "OpenLayers.Geometry.Polygon": (this.multiSurface === true) ? "MultiSurface" : "MultiPolygon"
+ });
+ }
+ var name, feature;
+ for(i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ name = this.stateName[feature.state];
+ if(name) {
+ this.writeNode(name, {
+ feature: feature,
+ options: options
+ }, node);
+ }
+ }
+ // switch back to original geometry types assignment
+ if (options.multi === true) {
+ this.setGeometryTypes();
+ }
+ }
+ if (options.nativeElements) {
+ for (i=0, len=options.nativeElements.length; i<len; ++i) {
+ this.writeNode("wfs:Native",
+ options.nativeElements[i], node);
+ }
+ }
+ return node;
+ },
+ "Native": function(nativeElement) {
+ var node = this.createElementNSPlus("wfs:Native", {
+ attributes: {
+ vendorId: nativeElement.vendorId,
+ safeToIgnore: nativeElement.safeToIgnore
+ },
+ value: nativeElement.value
+ });
+ return node;
+ },
+ "Insert": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Insert", {
+ attributes: {
+ handle: options && options.handle
+ }
+ });
+ this.srsName = this.getSrsName(feature);
+ this.writeNode("feature:_typeName", feature, node);
+ return node;
+ },
+ "Update": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Update", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+
+ // add in geometry
+ var modified = feature.modified;
+ if (this.geometryName !== null && (!modified || modified.geometry !== undefined)) {
+ this.srsName = this.getSrsName(feature);
+ this.writeNode(
+ "Property", {name: this.geometryName, value: feature.geometry}, node
+ );
+ }
+
+ // add in attributes
+ for(var key in feature.attributes) {
+ if(feature.attributes[key] !== undefined &&
+ (!modified || !modified.attributes ||
+ (modified.attributes && modified.attributes[key] !== undefined))) {
+ this.writeNode(
+ "Property", {name: key, value: feature.attributes[key]}, node
+ );
+ }
+ }
+
+ // add feature id filter
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+
+ return node;
+ },
+ "Property": function(obj) {
+ var node = this.createElementNSPlus("wfs:Property");
+ this.writeNode("Name", obj.name, node);
+ if(obj.value !== null) {
+ this.writeNode("Value", obj.value, node);
+ }
+ return node;
+ },
+ "Name": function(name) {
+ return this.createElementNSPlus("wfs:Name", {value: name});
+ },
+ "Value": function(obj) {
+ var node;
+ if(obj instanceof OpenLayers.Geometry) {
+ node = this.createElementNSPlus("wfs:Value");
+ var geom = this.writeNode("feature:_geometry", obj).firstChild;
+ node.appendChild(geom);
+ } else {
+ node = this.createElementNSPlus("wfs:Value", {value: obj});
+ }
+ return node;
+ },
+ "Delete": function(obj) {
+ var feature = obj.feature;
+ var options = obj.options;
+ var node = this.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ handle: options && options.handle,
+ typeName: (this.featureNS ? this.featurePrefix + ":" : "") +
+ this.featureType
+ }
+ });
+ if(this.featureNS) {
+ node.setAttribute("xmlns:" + this.featurePrefix, this.featureNS);
+ }
+ this.writeNode("ogc:Filter", new OpenLayers.Filter.FeatureId({
+ fids: [feature.fid]
+ }), node);
+ return node;
+ }
+ }
+ },
+
+ /**
+ * Method: schemaLocationAttr
+ * Generate the xsi:schemaLocation attribute value.
+ *
+ * Returns:
+ * {String} The xsi:schemaLocation attribute or undefined if none.
+ */
+ schemaLocationAttr: function(options) {
+ options = OpenLayers.Util.extend({
+ featurePrefix: this.featurePrefix,
+ schema: this.schema
+ }, options);
+ var schemaLocations = OpenLayers.Util.extend({}, this.schemaLocations);
+ if(options.schema) {
+ schemaLocations[options.featurePrefix] = options.schema;
+ }
+ var parts = [];
+ var uri;
+ for(var key in schemaLocations) {
+ uri = this.namespaces[key];
+ if(uri) {
+ parts.push(uri + " " + schemaLocations[key]);
+ }
+ }
+ var value = parts.join(" ") || undefined;
+ return value;
+ },
+
+ /**
+ * Method: setFilterProperty
+ * Set the property of each spatial filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ setFilterProperty: function(filter) {
+ if(filter.filters) {
+ for(var i=0, len=filter.filters.length; i<len; ++i) {
+ OpenLayers.Format.WFST.v1.prototype.setFilterProperty.call(this, filter.filters[i]);
+ }
+ } else {
+ if(filter instanceof OpenLayers.Filter.Spatial && !filter.property) {
+ // got a spatial filter without property, so set it
+ filter.property = this.geometryName;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFST/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/WFST/v1_0_0.js
new file mode 100644
index 0000000..ed81a2d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFST/v1_0_0.js
@@ -0,0 +1,174 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFST/v1.js
+ * @requires OpenLayers/Format/Filter/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1_0_0
+ * A format for creating WFS v1.0.0 transactions. Create a new instance with the
+ * <OpenLayers.Format.WFST.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_0_0>
+ * - <OpenLayers.Format.WFST.v1>
+ */
+OpenLayers.Format.WFST.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.Filter.v1_0_0, OpenLayers.Format.WFST.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * APIProperty: srsNameInQuery
+ * {Boolean} If true the reference system is passed in Query requests
+ * via the "srsName" attribute to the "wfs:Query" element, this
+ * property defaults to false as it isn't WFS 1.0.0 compliant.
+ */
+ srsNameInQuery: false,
+
+ /**
+ * Property: schemaLocations
+ * {Object} Properties are namespace aliases, values are schema locations.
+ */
+ schemaLocations: {
+ "wfs": "http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1_0_0
+ * A class for parsing and generating WFS v1.0.0 transactions.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // Not the superclass, only the mixin classes inherit from
+ // Format.GML.v2. We need this because we don't want to get readNode
+ // from the superclass's superclass, which is OpenLayers.Format.XML.
+ return OpenLayers.Format.GML.v2.prototype.readNode.apply(this, arguments);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "WFS_TransactionResponse": function(node, obj) {
+ obj.insertIds = [];
+ obj.success = false;
+ this.readChildNodes(node, obj);
+ },
+ "InsertResult": function(node, container) {
+ var obj = {fids: []};
+ this.readChildNodes(node, obj);
+ container.insertIds = container.insertIds.concat(obj.fids);
+ },
+ "TransactionResult": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Status": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "SUCCESS": function(node, obj) {
+ obj.success = true;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "Query": function(options) {
+ options = OpenLayers.Util.extend({
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ featureType: this.featureType,
+ srsName: this.srsName,
+ srsNameInQuery: this.srsNameInQuery
+ }, options);
+ var prefix = options.featurePrefix;
+ var node = this.createElementNSPlus("wfs:Query", {
+ attributes: {
+ typeName: (prefix ? prefix + ":" : "") +
+ options.featureType
+ }
+ });
+ if(options.srsNameInQuery && options.srsName) {
+ node.setAttribute("srsName", options.srsName);
+ }
+ if(options.featureNS) {
+ node.setAttribute("xmlns:" + prefix, options.featureNS);
+ }
+ if(options.propertyNames) {
+ for(var i=0,len = options.propertyNames.length; i<len; i++) {
+ this.writeNode(
+ "ogc:PropertyName",
+ {property: options.propertyNames[i]},
+ node
+ );
+ }
+ }
+ if(options.filter) {
+ this.setFilterProperty(options.filter);
+ this.writeNode("ogc:Filter", options.filter, node);
+ }
+ return node;
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
+ "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_0_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1_0_0"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WFST/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/WFST/v1_1_0.js
new file mode 100644
index 0000000..ff2a88d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WFST/v1_1_0.js
@@ -0,0 +1,189 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WFST/v1.js
+ * @requires OpenLayers/Format/Filter/v1_1_0.js
+ * @requires OpenLayers/Format/OWSCommon/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFST.v1_1_0
+ * A format for creating WFS v1.1.0 transactions. Create a new instance with the
+ * <OpenLayers.Format.WFST.v1_1_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Filter.v1_1_0>
+ * - <OpenLayers.Format.WFST.v1>
+ */
+OpenLayers.Format.WFST.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.Filter.v1_1_0, OpenLayers.Format.WFST.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.1.0",
+
+ /**
+ * Property: schemaLocations
+ * {Object} Properties are namespace aliases, values are schema locations.
+ */
+ schemaLocations: {
+ "wfs": "http://schemas.opengis.net/wfs/1.1.0/wfs.xsd"
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WFST.v1_1_0
+ * A class for parsing and generating WFS v1.1.0 transactions.
+ *
+ * To read additional information like hit count (numberOfFeatures) from
+ * the FeatureCollection, call the <OpenLayers.Format.WFST.v1.read> method
+ * with {output: "object"} as 2nd argument. Note that it is possible to
+ * just request the hit count from a WFS 1.1.0 server with the
+ * resultType="hits" request parameter.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.Filter.v1_1_0.prototype.initialize.apply(this, [options]);
+ OpenLayers.Format.WFST.v1.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ * first - {Boolean} Should be set to true for the first node read. This
+ * is usually the readNode call in the read method. Without this being
+ * set, auto-configured properties will stick on subsequent reads.
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj, first) {
+ // Not the superclass, only the mixin classes inherit from
+ // Format.GML.v3. We need this because we don't want to get readNode
+ // from the superclass's superclass, which is OpenLayers.Format.XML.
+ return OpenLayers.Format.GML.v3.prototype.readNode.apply(this, arguments);
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "FeatureCollection": function(node, obj) {
+ obj.numberOfFeatures = parseInt(node.getAttribute(
+ "numberOfFeatures"));
+ OpenLayers.Format.WFST.v1.prototype.readers["wfs"]["FeatureCollection"].apply(
+ this, arguments);
+ },
+ "TransactionResponse": function(node, obj) {
+ obj.insertIds = [];
+ obj.success = false;
+ this.readChildNodes(node, obj);
+ },
+ "TransactionSummary": function(node, obj) {
+ // this is a limited test of success
+ obj.success = true;
+ },
+ "InsertResults": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Feature": function(node, container) {
+ var obj = {fids: []};
+ this.readChildNodes(node, obj);
+ container.insertIds.push(obj.fids[0]);
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.readers["ogc"],
+ "ows": OpenLayers.Format.OWSCommon.v1_0_0.prototype.readers["ows"]
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wfs": OpenLayers.Util.applyDefaults({
+ "GetFeature": function(options) {
+ var node = OpenLayers.Format.WFST.v1.prototype.writers["wfs"]["GetFeature"].apply(this, arguments);
+ options && this.setAttributes(node, {
+ resultType: options.resultType,
+ startIndex: options.startIndex,
+ count: options.count
+ });
+ return node;
+ },
+ "Query": function(options) {
+ options = OpenLayers.Util.extend({
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ featureType: this.featureType,
+ srsName: this.srsName
+ }, options);
+ var prefix = options.featurePrefix;
+ var node = this.createElementNSPlus("wfs:Query", {
+ attributes: {
+ typeName: (prefix ? prefix + ":" : "") +
+ options.featureType,
+ srsName: options.srsName
+ }
+ });
+ if(options.featureNS) {
+ node.setAttribute("xmlns:" + prefix, options.featureNS);
+ }
+ if(options.propertyNames) {
+ for(var i=0,len = options.propertyNames.length; i<len; i++) {
+ this.writeNode(
+ "wfs:PropertyName",
+ {property: options.propertyNames[i]},
+ node
+ );
+ }
+ }
+ if(options.filter) {
+ OpenLayers.Format.WFST.v1_1_0.prototype.setFilterProperty.call(this, options.filter);
+ this.writeNode("ogc:Filter", options.filter, node);
+ }
+ return node;
+ },
+ "PropertyName": function(obj) {
+ return this.createElementNSPlus("wfs:PropertyName", {
+ value: obj.property
+ });
+ }
+ }, OpenLayers.Format.WFST.v1.prototype.writers["wfs"]),
+ "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"],
+ "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"],
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers["ogc"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WFST.v1_1_0"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WKT.js b/misc/openlayers/lib/OpenLayers/Format/WKT.js
new file mode 100644
index 0000000..a7a7b2e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WKT.js
@@ -0,0 +1,392 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WKT
+ * Class for reading and writing Well-Known Text. Create a new instance
+ * with the <OpenLayers.Format.WKT> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.WKT = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Constructor: OpenLayers.Format.WKT
+ * Create a new parser for WKT
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance
+ *
+ * Returns:
+ * {<OpenLayers.Format.WKT>} A new WKT parser.
+ */
+ initialize: function(options) {
+ this.regExes = {
+ 'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
+ 'spaces': /\s+/,
+ 'parenComma': /\)\s*,\s*\(/,
+ 'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/, // can't use {2} here
+ 'trimParens': /^\s*\(?(.*?)\)?\s*$/
+ };
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a WKT string and return a vector feature or an
+ * array of vector features. Supports WKT for POINT, MULTIPOINT,
+ * LINESTRING, MULTILINESTRING, POLYGON, MULTIPOLYGON, and
+ * GEOMETRYCOLLECTION.
+ *
+ * Parameters:
+ * wkt - {String} A WKT string
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>|Array} A feature or array of features for
+ * GEOMETRYCOLLECTION WKT.
+ */
+ read: function(wkt) {
+ var features, type, str;
+ wkt = wkt.replace(/[\n\r]/g, " ");
+ var matches = this.regExes.typeStr.exec(wkt);
+ if(matches) {
+ type = matches[1].toLowerCase();
+ str = matches[2];
+ if(this.parse[type]) {
+ features = this.parse[type].apply(this, [str]);
+ }
+ if (this.internalProjection && this.externalProjection) {
+ if (features &&
+ features.CLASS_NAME == "OpenLayers.Feature.Vector") {
+ features.geometry.transform(this.externalProjection,
+ this.internalProjection);
+ } else if (features &&
+ type != "geometrycollection" &&
+ typeof features == "object") {
+ for (var i=0, len=features.length; i<len; i++) {
+ var component = features[i];
+ component.geometry.transform(this.externalProjection,
+ this.internalProjection);
+ }
+ }
+ }
+ }
+ return features;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a feature or array of features into a WKT string.
+ *
+ * Parameters:
+ * features - {<OpenLayers.Feature.Vector>|Array} A feature or array of
+ * features
+ *
+ * Returns:
+ * {String} The WKT string representation of the input geometries
+ */
+ write: function(features) {
+ var collection, geometry, isCollection;
+ if (features.constructor == Array) {
+ collection = features;
+ isCollection = true;
+ } else {
+ collection = [features];
+ isCollection = false;
+ }
+ var pieces = [];
+ if (isCollection) {
+ pieces.push('GEOMETRYCOLLECTION(');
+ }
+ for (var i=0, len=collection.length; i<len; ++i) {
+ if (isCollection && i>0) {
+ pieces.push(',');
+ }
+ geometry = collection[i].geometry;
+ pieces.push(this.extractGeometry(geometry));
+ }
+ if (isCollection) {
+ pieces.push(')');
+ }
+ return pieces.join('');
+ },
+
+ /**
+ * Method: extractGeometry
+ * Entry point to construct the WKT for a single Geometry object.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry.Geometry>}
+ *
+ * Returns:
+ * {String} A WKT string of representing the geometry
+ */
+ extractGeometry: function(geometry) {
+ var type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
+ if (!this.extract[type]) {
+ return null;
+ }
+ if (this.internalProjection && this.externalProjection) {
+ geometry = geometry.clone();
+ geometry.transform(this.internalProjection, this.externalProjection);
+ }
+ var wktType = type == 'collection' ? 'GEOMETRYCOLLECTION' : type.toUpperCase();
+ var data = wktType + '(' + this.extract[type].apply(this, [geometry]) + ')';
+ return data;
+ },
+
+ /**
+ * Object with properties corresponding to the geometry types.
+ * Property values are functions that do the actual data extraction.
+ */
+ extract: {
+ /**
+ * Return a space delimited string of point coordinates.
+ * @param {OpenLayers.Geometry.Point} point
+ * @returns {String} A string of coordinates representing the point
+ */
+ 'point': function(point) {
+ return point.x + ' ' + point.y;
+ },
+
+ /**
+ * Return a comma delimited string of point coordinates from a multipoint.
+ * @param {OpenLayers.Geometry.MultiPoint} multipoint
+ * @returns {String} A string of point coordinate strings representing
+ * the multipoint
+ */
+ 'multipoint': function(multipoint) {
+ var array = [];
+ for(var i=0, len=multipoint.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.point.apply(this, [multipoint.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return a comma delimited string of point coordinates from a line.
+ * @param {OpenLayers.Geometry.LineString} linestring
+ * @returns {String} A string of point coordinate strings representing
+ * the linestring
+ */
+ 'linestring': function(linestring) {
+ var array = [];
+ for(var i=0, len=linestring.components.length; i<len; ++i) {
+ array.push(this.extract.point.apply(this, [linestring.components[i]]));
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return a comma delimited string of linestring strings from a multilinestring.
+ * @param {OpenLayers.Geometry.MultiLineString} multilinestring
+ * @returns {String} A string of of linestring strings representing
+ * the multilinestring
+ */
+ 'multilinestring': function(multilinestring) {
+ var array = [];
+ for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.linestring.apply(this, [multilinestring.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return a comma delimited string of linear ring arrays from a polygon.
+ * @param {OpenLayers.Geometry.Polygon} polygon
+ * @returns {String} An array of linear ring arrays representing the polygon
+ */
+ 'polygon': function(polygon) {
+ var array = [];
+ for(var i=0, len=polygon.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.linestring.apply(this, [polygon.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return an array of polygon arrays from a multipolygon.
+ * @param {OpenLayers.Geometry.MultiPolygon} multipolygon
+ * @returns {String} An array of polygon arrays representing
+ * the multipolygon
+ */
+ 'multipolygon': function(multipolygon) {
+ var array = [];
+ for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+ array.push('(' +
+ this.extract.polygon.apply(this, [multipolygon.components[i]]) +
+ ')');
+ }
+ return array.join(',');
+ },
+
+ /**
+ * Return the WKT portion between 'GEOMETRYCOLLECTION(' and ')' for an <OpenLayers.Geometry.Collection>
+ * @param {OpenLayers.Geometry.Collection} collection
+ * @returns {String} internal WKT representation of the collection
+ */
+ 'collection': function(collection) {
+ var array = [];
+ for(var i=0, len=collection.components.length; i<len; ++i) {
+ array.push(this.extractGeometry.apply(this, [collection.components[i]]));
+ }
+ return array.join(',');
+ }
+
+ },
+
+ /**
+ * Object with properties corresponding to the geometry types.
+ * Property values are functions that do the actual parsing.
+ */
+ parse: {
+ /**
+ * Return point feature given a point WKT fragment.
+ * @param {String} str A WKT fragment representing the point
+ * @returns {OpenLayers.Feature.Vector} A point feature
+ * @private
+ */
+ 'point': function(str) {
+ var coords = OpenLayers.String.trim(str).split(this.regExes.spaces);
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(coords[0], coords[1])
+ );
+ },
+
+ /**
+ * Return a multipoint feature given a multipoint WKT fragment.
+ * @param {String} str A WKT fragment representing the multipoint
+ * @returns {OpenLayers.Feature.Vector} A multipoint feature
+ * @private
+ */
+ 'multipoint': function(str) {
+ var point;
+ var points = OpenLayers.String.trim(str).split(',');
+ var components = [];
+ for(var i=0, len=points.length; i<len; ++i) {
+ point = points[i].replace(this.regExes.trimParens, '$1');
+ components.push(this.parse.point.apply(this, [point]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPoint(components)
+ );
+ },
+
+ /**
+ * Return a linestring feature given a linestring WKT fragment.
+ * @param {String} str A WKT fragment representing the linestring
+ * @returns {OpenLayers.Feature.Vector} A linestring feature
+ * @private
+ */
+ 'linestring': function(str) {
+ var points = OpenLayers.String.trim(str).split(',');
+ var components = [];
+ for(var i=0, len=points.length; i<len; ++i) {
+ components.push(this.parse.point.apply(this, [points[i]]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(components)
+ );
+ },
+
+ /**
+ * Return a multilinestring feature given a multilinestring WKT fragment.
+ * @param {String} str A WKT fragment representing the multilinestring
+ * @returns {OpenLayers.Feature.Vector} A multilinestring feature
+ * @private
+ */
+ 'multilinestring': function(str) {
+ var line;
+ var lines = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+ var components = [];
+ for(var i=0, len=lines.length; i<len; ++i) {
+ line = lines[i].replace(this.regExes.trimParens, '$1');
+ components.push(this.parse.linestring.apply(this, [line]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiLineString(components)
+ );
+ },
+
+ /**
+ * Return a polygon feature given a polygon WKT fragment.
+ * @param {String} str A WKT fragment representing the polygon
+ * @returns {OpenLayers.Feature.Vector} A polygon feature
+ * @private
+ */
+ 'polygon': function(str) {
+ var ring, linestring, linearring;
+ var rings = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+ var components = [];
+ for(var i=0, len=rings.length; i<len; ++i) {
+ ring = rings[i].replace(this.regExes.trimParens, '$1');
+ linestring = this.parse.linestring.apply(this, [ring]).geometry;
+ linearring = new OpenLayers.Geometry.LinearRing(linestring.components);
+ components.push(linearring);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon(components)
+ );
+ },
+
+ /**
+ * Return a multipolygon feature given a multipolygon WKT fragment.
+ * @param {String} str A WKT fragment representing the multipolygon
+ * @returns {OpenLayers.Feature.Vector} A multipolygon feature
+ * @private
+ */
+ 'multipolygon': function(str) {
+ var polygon;
+ var polygons = OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);
+ var components = [];
+ for(var i=0, len=polygons.length; i<len; ++i) {
+ polygon = polygons[i].replace(this.regExes.trimParens, '$1');
+ components.push(this.parse.polygon.apply(this, [polygon]).geometry);
+ }
+ return new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPolygon(components)
+ );
+ },
+
+ /**
+ * Return an array of features given a geometrycollection WKT fragment.
+ * @param {String} str A WKT fragment representing the geometrycollection
+ * @returns {Array} An array of OpenLayers.Feature.Vector
+ * @private
+ */
+ 'geometrycollection': function(str) {
+ // separate components of the collection with |
+ str = str.replace(/,\s*([A-Za-z])/g, '|$1');
+ var wktArray = OpenLayers.String.trim(str).split('|');
+ var components = [];
+ for(var i=0, len=wktArray.length; i<len; ++i) {
+ components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
+ }
+ return components;
+ }
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WKT"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMC.js b/misc/openlayers/lib/OpenLayers/Format/WMC.js
new file mode 100644
index 0000000..ded1b3a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMC.js
@@ -0,0 +1,182 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/Context.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC
+ * Read and write Web Map Context documents.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.Context>
+ */
+OpenLayers.Format.WMC = OpenLayers.Class(OpenLayers.Format.Context, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC
+ * Create a new parser for Web Map Context documents.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: layerToContext
+ * Create a layer context object given a wms layer object.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} The layer.
+ *
+ * Returns:
+ * {Object} A layer context object.
+ */
+ layerToContext: function(layer) {
+ var parser = this.getParser();
+ var layerContext = {
+ queryable: layer.queryable,
+ visibility: layer.visibility,
+ name: layer.params["LAYERS"],
+ title: layer.name,
+ "abstract": layer.metadata["abstract"],
+ dataURL: layer.metadata.dataURL,
+ metadataURL: layer.metadataURL,
+ server: {
+ version: layer.params["VERSION"],
+ url: layer.url
+ },
+ maxExtent: layer.maxExtent,
+ transparent: layer.params["TRANSPARENT"],
+ numZoomLevels: layer.numZoomLevels,
+ units: layer.units,
+ isBaseLayer: layer.isBaseLayer,
+ opacity: layer.opacity == 1 ? undefined : layer.opacity,
+ displayInLayerSwitcher: layer.displayInLayerSwitcher,
+ singleTile: layer.singleTile,
+ tileSize: (layer.singleTile || !layer.tileSize) ?
+ undefined : {width: layer.tileSize.w, height: layer.tileSize.h},
+ minScale : (layer.options.resolutions ||
+ layer.options.scales ||
+ layer.options.maxResolution ||
+ layer.options.minScale) ?
+ layer.minScale : undefined,
+ maxScale : (layer.options.resolutions ||
+ layer.options.scales ||
+ layer.options.minResolution ||
+ layer.options.maxScale) ?
+ layer.maxScale : undefined,
+ formats: [],
+ styles: [],
+ srs: layer.srs,
+ dimensions: layer.dimensions
+ };
+
+
+ if (layer.metadata.servertitle) {
+ layerContext.server.title = layer.metadata.servertitle;
+ }
+
+ if (layer.metadata.formats && layer.metadata.formats.length > 0) {
+ for (var i=0, len=layer.metadata.formats.length; i<len; i++) {
+ var format = layer.metadata.formats[i];
+ layerContext.formats.push({
+ value: format.value,
+ current: (format.value == layer.params["FORMAT"])
+ });
+ }
+ } else {
+ layerContext.formats.push({
+ value: layer.params["FORMAT"],
+ current: true
+ });
+ }
+
+ if (layer.metadata.styles && layer.metadata.styles.length > 0) {
+ for (var i=0, len=layer.metadata.styles.length; i<len; i++) {
+ var style = layer.metadata.styles[i];
+ if ((style.href == layer.params["SLD"]) ||
+ (style.body == layer.params["SLD_BODY"]) ||
+ (style.name == layer.params["STYLES"])) {
+ style.current = true;
+ } else {
+ style.current = false;
+ }
+ layerContext.styles.push(style);
+ }
+ } else {
+ layerContext.styles.push({
+ href: layer.params["SLD"],
+ body: layer.params["SLD_BODY"],
+ name: layer.params["STYLES"] || parser.defaultStyleName,
+ title: parser.defaultStyleTitle,
+ current: true
+ });
+ }
+
+ return layerContext;
+ },
+
+ /**
+ * Method: toContext
+ * Create a context object free from layer given a map or a
+ * context object.
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Map> | Object} The map or context.
+ *
+ * Returns:
+ * {Object} A context object.
+ */
+ toContext: function(obj) {
+ var context = {};
+ var layers = obj.layers;
+ if (obj.CLASS_NAME == "OpenLayers.Map") {
+ var metadata = obj.metadata || {};
+ context.size = obj.getSize();
+ context.bounds = obj.getExtent();
+ context.projection = obj.projection;
+ context.title = obj.title;
+ context.keywords = metadata.keywords;
+ context["abstract"] = metadata["abstract"];
+ context.logo = metadata.logo;
+ context.descriptionURL = metadata.descriptionURL;
+ context.contactInformation = metadata.contactInformation;
+ context.maxExtent = obj.maxExtent;
+ } else {
+ // copy all obj properties except the "layers" property
+ OpenLayers.Util.applyDefaults(context, obj);
+ if (context.layers != undefined) {
+ delete(context.layers);
+ }
+ }
+
+ if (context.layersContext == undefined) {
+ context.layersContext = [];
+ }
+
+ // let's convert layers into layersContext object (if any)
+ if (layers != undefined && OpenLayers.Util.isArray(layers)) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ var layer = layers[i];
+ if (layer instanceof OpenLayers.Layer.WMS) {
+ context.layersContext.push(this.layerToContext(layer));
+ }
+ }
+ }
+ return context;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMC"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMC/v1.js b/misc/openlayers/lib/OpenLayers/Format/WMC/v1.js
new file mode 100644
index 0000000..6c9a5c3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMC/v1.js
@@ -0,0 +1,1267 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC.js
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1
+ * Superclass for WMC version 1 parsers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMC.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ol: "http://openlayers.org/context",
+ wmc: "http://www.opengis.net/context",
+ sld: "http://www.opengis.net/sld",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: "",
+
+ /**
+ * Method: getNamespacePrefix
+ * Get the namespace prefix for a given uri from the <namespaces> object.
+ *
+ * Returns:
+ * {String} A namespace prefix or null if none found.
+ */
+ getNamespacePrefix: function(uri) {
+ var prefix = null;
+ if(uri == null) {
+ prefix = this.namespaces[this.defaultPrefix];
+ } else {
+ for(prefix in this.namespaces) {
+ if(this.namespaces[prefix] == uri) {
+ break;
+ }
+ }
+ }
+ return prefix;
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wmc",
+
+ /**
+ * Property: rootPrefix
+ * {String} Prefix on the root node that maps to the context namespace URI.
+ */
+ rootPrefix: null,
+
+ /**
+ * Property: defaultStyleName
+ * {String} Style name used if layer has no style param. Default is "".
+ */
+ defaultStyleName: "",
+
+ /**
+ * Property: defaultStyleTitle
+ * {String} Default style title. Default is "Default".
+ */
+ defaultStyleTitle: "Default",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WMC> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * Method: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ this.rootPrefix = root.prefix;
+ var context = {
+ version: root.getAttribute("version")
+ };
+ this.runChildNodes(context, root);
+ return context;
+ },
+
+ /**
+ * Method: runChildNodes
+ */
+ runChildNodes: function(obj, node) {
+ var children = node.childNodes;
+ var childNode, processor, prefix, local;
+ for(var i=0, len=children.length; i<len; ++i) {
+ childNode = children[i];
+ if(childNode.nodeType == 1) {
+ prefix = this.getNamespacePrefix(childNode.namespaceURI);
+ local = childNode.nodeName.split(":").pop();
+ processor = this["read_" + prefix + "_" + local];
+ if(processor) {
+ processor.apply(this, [obj, childNode]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: read_wmc_General
+ */
+ read_wmc_General: function(context, node) {
+ this.runChildNodes(context, node);
+ },
+
+ /**
+ * Method: read_wmc_BoundingBox
+ */
+ read_wmc_BoundingBox: function(context, node) {
+ context.projection = node.getAttribute("SRS");
+ context.bounds = new OpenLayers.Bounds(
+ node.getAttribute("minx"), node.getAttribute("miny"),
+ node.getAttribute("maxx"), node.getAttribute("maxy")
+ );
+ },
+
+ /**
+ * Method: read_wmc_LayerList
+ */
+ read_wmc_LayerList: function(context, node) {
+ // layersContext is an array containing info for each layer
+ context.layersContext = [];
+ this.runChildNodes(context, node);
+ },
+
+ /**
+ * Method: read_wmc_Layer
+ */
+ read_wmc_Layer: function(context, node) {
+ var layerContext = {
+ visibility: (node.getAttribute("hidden") != "1"),
+ queryable: (node.getAttribute("queryable") == "1"),
+ formats: [],
+ styles: [],
+ metadata: {}
+ };
+
+ this.runChildNodes(layerContext, node);
+ // set properties common to multiple objects on layer options/params
+ context.layersContext.push(layerContext);
+ },
+
+ /**
+ * Method: read_wmc_Extension
+ */
+ read_wmc_Extension: function(obj, node) {
+ this.runChildNodes(obj, node);
+ },
+
+ /**
+ * Method: read_ol_units
+ */
+ read_ol_units: function(layerContext, node) {
+ layerContext.units = this.getChildValue(node);
+ },
+
+ /**
+ * Method: read_ol_maxExtent
+ */
+ read_ol_maxExtent: function(obj, node) {
+ var bounds = new OpenLayers.Bounds(
+ node.getAttribute("minx"), node.getAttribute("miny"),
+ node.getAttribute("maxx"), node.getAttribute("maxy")
+ );
+ obj.maxExtent = bounds;
+ },
+
+ /**
+ * Method: read_ol_transparent
+ */
+ read_ol_transparent: function(layerContext, node) {
+ layerContext.transparent = this.getChildValue(node);
+ },
+
+ /**
+ * Method: read_ol_numZoomLevels
+ */
+ read_ol_numZoomLevels: function(layerContext, node) {
+ layerContext.numZoomLevels = parseInt(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_ol_opacity
+ */
+ read_ol_opacity: function(layerContext, node) {
+ layerContext.opacity = parseFloat(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_ol_singleTile
+ */
+ read_ol_singleTile: function(layerContext, node) {
+ layerContext.singleTile = (this.getChildValue(node) == "true");
+ },
+
+ /**
+ * Method: read_ol_tileSize
+ */
+ read_ol_tileSize: function(layerContext, node) {
+ var obj = {"width": node.getAttribute("width"), "height": node.getAttribute("height")};
+ layerContext.tileSize = obj;
+ },
+
+ /**
+ * Method: read_ol_isBaseLayer
+ */
+ read_ol_isBaseLayer: function(layerContext, node) {
+ layerContext.isBaseLayer = (this.getChildValue(node) == "true");
+ },
+
+ /**
+ * Method: read_ol_displayInLayerSwitcher
+ */
+ read_ol_displayInLayerSwitcher: function(layerContext, node) {
+ layerContext.displayInLayerSwitcher = (this.getChildValue(node) == "true");
+ },
+
+ /**
+ * Method: read_wmc_Server
+ */
+ read_wmc_Server: function(layerContext, node) {
+ layerContext.version = node.getAttribute("version");
+ layerContext.url = this.getOnlineResource_href(node);
+ layerContext.metadata.servertitle = node.getAttribute("title");
+ },
+
+ /**
+ * Method: read_wmc_FormatList
+ */
+ read_wmc_FormatList: function(layerContext, node) {
+ this.runChildNodes(layerContext, node);
+ },
+
+ /**
+ * Method: read_wmc_Format
+ */
+ read_wmc_Format: function(layerContext, node) {
+ var format = {
+ value: this.getChildValue(node)
+ };
+ if(node.getAttribute("current") == "1") {
+ format.current = true;
+ }
+ layerContext.formats.push(format);
+ },
+
+ /**
+ * Method: read_wmc_StyleList
+ */
+ read_wmc_StyleList: function(layerContext, node) {
+ this.runChildNodes(layerContext, node);
+ },
+
+ /**
+ * Method: read_wmc_Style
+ */
+ read_wmc_Style: function(layerContext, node) {
+ var style = {};
+ this.runChildNodes(style, node);
+ if(node.getAttribute("current") == "1") {
+ style.current = true;
+ }
+ layerContext.styles.push(style);
+ },
+
+ /**
+ * Method: read_wmc_SLD
+ */
+ read_wmc_SLD: function(style, node) {
+ this.runChildNodes(style, node);
+ // style either comes back with an href or a body property
+ },
+
+ /**
+ * Method: read_sld_StyledLayerDescriptor
+ */
+ read_sld_StyledLayerDescriptor: function(sld, node) {
+ var xml = OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ sld.body = xml;
+ },
+
+ /**
+ * Method: read_sld_FeatureTypeStyle
+ */
+ read_sld_FeatureTypeStyle: function(sld, node) {
+ var xml = OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ sld.body = xml;
+ },
+
+ /**
+ * Method: read_wmc_OnlineResource
+ */
+ read_wmc_OnlineResource: function(obj, node) {
+ obj.href = this.getAttributeNS(
+ node, this.namespaces.xlink, "href"
+ );
+ },
+
+ /**
+ * Method: read_wmc_Name
+ */
+ read_wmc_Name: function(obj, node) {
+ var name = this.getChildValue(node);
+ if(name) {
+ obj.name = name;
+ }
+ },
+
+ /**
+ * Method: read_wmc_Title
+ */
+ read_wmc_Title: function(obj, node) {
+ var title = this.getChildValue(node);
+ if(title) {
+ obj.title = title;
+ }
+ },
+
+ /**
+ * Method: read_wmc_MetadataURL
+ */
+ read_wmc_MetadataURL: function(layerContext, node) {
+ layerContext.metadataURL = this.getOnlineResource_href(node);
+ },
+
+ /**
+ * Method: read_wmc_KeywordList
+ */
+ read_wmc_KeywordList: function(context, node) {
+ context.keywords = [];
+ this.runChildNodes(context.keywords, node);
+ },
+
+ /**
+ * Method: read_wmc_Keyword
+ */
+ read_wmc_Keyword: function(keywords, node) {
+ keywords.push(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_wmc_Abstract
+ */
+ read_wmc_Abstract: function(obj, node) {
+ var abst = this.getChildValue(node);
+ if(abst) {
+ obj["abstract"] = abst;
+ }
+ },
+
+ /**
+ * Method: read_wmc_LogoURL
+ */
+ read_wmc_LogoURL: function(context, node) {
+ context.logo = {
+ width: node.getAttribute("width"),
+ height: node.getAttribute("height"),
+ format: node.getAttribute("format"),
+ href: this.getOnlineResource_href(node)
+ };
+ },
+
+ /**
+ * Method: read_wmc_DescriptionURL
+ */
+ read_wmc_DescriptionURL: function(context, node) {
+ context.descriptionURL = this.getOnlineResource_href(node);
+ },
+
+ /**
+ * Method: read_wmc_ContactInformation
+ */
+ read_wmc_ContactInformation: function(obj, node) {
+ var contact = {};
+ this.runChildNodes(contact, node);
+ obj.contactInformation = contact;
+ },
+
+ /**
+ * Method: read_wmc_ContactPersonPrimary
+ */
+ read_wmc_ContactPersonPrimary: function(contact, node) {
+ var personPrimary = {};
+ this.runChildNodes(personPrimary, node);
+ contact.personPrimary = personPrimary;
+ },
+
+ /**
+ * Method: read_wmc_ContactPerson
+ */
+ read_wmc_ContactPerson: function(primaryPerson, node) {
+ var person = this.getChildValue(node);
+ if (person) {
+ primaryPerson.person = person;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactOrganization
+ */
+ read_wmc_ContactOrganization: function(primaryPerson, node) {
+ var organization = this.getChildValue(node);
+ if (organization) {
+ primaryPerson.organization = organization;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactPosition
+ */
+ read_wmc_ContactPosition: function(contact, node) {
+ var position = this.getChildValue(node);
+ if (position) {
+ contact.position = position;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactAddress
+ */
+ read_wmc_ContactAddress: function(contact, node) {
+ var contactAddress = {};
+ this.runChildNodes(contactAddress, node);
+ contact.contactAddress = contactAddress;
+ },
+
+ /**
+ * Method: read_wmc_AddressType
+ */
+ read_wmc_AddressType: function(contactAddress, node) {
+ var type = this.getChildValue(node);
+ if (type) {
+ contactAddress.type = type;
+ }
+ },
+
+ /**
+ * Method: read_wmc_Address
+ */
+ read_wmc_Address: function(contactAddress, node) {
+ var address = this.getChildValue(node);
+ if (address) {
+ contactAddress.address = address;
+ }
+ },
+
+ /**
+ * Method: read_wmc_City
+ */
+ read_wmc_City: function(contactAddress, node) {
+ var city = this.getChildValue(node);
+ if (city) {
+ contactAddress.city = city;
+ }
+ },
+
+ /**
+ * Method: read_wmc_StateOrProvince
+ */
+ read_wmc_StateOrProvince: function(contactAddress, node) {
+ var stateOrProvince = this.getChildValue(node);
+ if (stateOrProvince) {
+ contactAddress.stateOrProvince = stateOrProvince;
+ }
+ },
+
+ /**
+ * Method: read_wmc_PostCode
+ */
+ read_wmc_PostCode: function(contactAddress, node) {
+ var postcode = this.getChildValue(node);
+ if (postcode) {
+ contactAddress.postcode = postcode;
+ }
+ },
+
+ /**
+ * Method: read_wmc_Country
+ */
+ read_wmc_Country: function(contactAddress, node) {
+ var country = this.getChildValue(node);
+ if (country) {
+ contactAddress.country = country;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactVoiceTelephone
+ */
+ read_wmc_ContactVoiceTelephone: function(contact, node) {
+ var phone = this.getChildValue(node);
+ if (phone) {
+ contact.phone = phone;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactFacsimileTelephone
+ */
+ read_wmc_ContactFacsimileTelephone: function(contact, node) {
+ var fax = this.getChildValue(node);
+ if (fax) {
+ contact.fax = fax;
+ }
+ },
+
+ /**
+ * Method: read_wmc_ContactElectronicMailAddress
+ */
+ read_wmc_ContactElectronicMailAddress: function(contact, node) {
+ var email = this.getChildValue(node);
+ if (email) {
+ contact.email = email;
+ }
+ },
+
+ /**
+ * Method: read_wmc_DataURL
+ */
+ read_wmc_DataURL: function(layerContext, node) {
+ layerContext.dataURL = this.getOnlineResource_href(node);
+ },
+
+ /**
+ * Method: read_wmc_LegendURL
+ */
+ read_wmc_LegendURL: function(style, node) {
+ var legend = {
+ width: node.getAttribute('width'),
+ height: node.getAttribute('height'),
+ format: node.getAttribute('format'),
+ href: this.getOnlineResource_href(node)
+ };
+ style.legend = legend;
+ },
+
+ /**
+ * Method: read_wmc_DimensionList
+ */
+ read_wmc_DimensionList: function(layerContext, node) {
+ layerContext.dimensions = {};
+ this.runChildNodes(layerContext.dimensions, node);
+ },
+ /**
+ * Method: read_wmc_Dimension
+ */
+ read_wmc_Dimension: function(dimensions, node) {
+ var name = node.getAttribute("name").toLowerCase();
+
+ var dim = {
+ name: name,
+ units: node.getAttribute("units") || "",
+ unitSymbol: node.getAttribute("unitSymbol") || "",
+ userValue: node.getAttribute("userValue") || "",
+ nearestValue: node.getAttribute("nearestValue") === "1",
+ multipleValues: node.getAttribute("multipleValues") === "1",
+ current: node.getAttribute("current") === "1",
+ "default": node.getAttribute("default") || ""
+ };
+ var values = this.getChildValue(node);
+ dim.values = values.split(",");
+
+ dimensions[dim.name] = dim;
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * context - {Object} An object representing the map context.
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} A WMC document string.
+ */
+ write: function(context, options) {
+ var root = this.createElementDefaultNS("ViewContext");
+ this.setAttributes(root, {
+ version: this.VERSION,
+ id: (options && typeof options.id == "string") ?
+ options.id :
+ OpenLayers.Util.createUniqueID("OpenLayers_Context_")
+ });
+
+ // add schemaLocation attribute
+ this.setAttributeNS(
+ root, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+
+ // required General element
+ root.appendChild(this.write_wmc_General(context));
+
+ // required LayerList element
+ root.appendChild(this.write_wmc_LayerList(context));
+
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ },
+
+ /**
+ * Method: createElementDefaultNS
+ * Shorthand for createElementNS with namespace from <defaultPrefix>.
+ * Can optionally be used to set attributes and a text child value.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * childValue - {String} Optional value for text child node.
+ * attributes - {Object} Optional object representing attributes.
+ *
+ * Returns:
+ * {Element} An element node.
+ */
+ createElementDefaultNS: function(name, childValue, attributes) {
+ var node = this.createElementNS(
+ this.namespaces[this.defaultPrefix],
+ name
+ );
+ if(childValue) {
+ node.appendChild(this.createTextNode(childValue));
+ }
+ if(attributes) {
+ this.setAttributes(node, attributes);
+ }
+ return node;
+ },
+
+ /**
+ * Method: setAttributes
+ * Set multiple attributes given key value pairs from an object.
+ *
+ * Parameters:
+ * node - {Element} An element node.
+ * obj - {Object} An object whose properties represent attribute names and
+ * values represent attribute values.
+ */
+ setAttributes: function(node, obj) {
+ var value;
+ for(var name in obj) {
+ value = obj[name].toString();
+ if(value.match(/[A-Z]/)) {
+ // safari lowercases attributes with setAttribute
+ this.setAttributeNS(node, null, name, value);
+ } else {
+ node.setAttribute(name, value);
+ }
+ }
+ },
+
+ /**
+ * Method: write_wmc_General
+ * Create a General node given an context object.
+ *
+ * Parameters:
+ * context - {Object} Context object.
+ *
+ * Returns:
+ * {Element} A WMC General element node.
+ */
+ write_wmc_General: function(context) {
+ var node = this.createElementDefaultNS("General");
+
+ // optional Window element
+ if(context.size) {
+ node.appendChild(this.createElementDefaultNS(
+ "Window", null,
+ {
+ width: context.size.w,
+ height: context.size.h
+ }
+ ));
+ }
+
+ // required BoundingBox element
+ var bounds = context.bounds;
+ node.appendChild(this.createElementDefaultNS(
+ "BoundingBox", null,
+ {
+ minx: bounds.left.toPrecision(18),
+ miny: bounds.bottom.toPrecision(18),
+ maxx: bounds.right.toPrecision(18),
+ maxy: bounds.top.toPrecision(18),
+ SRS: context.projection
+ }
+ ));
+
+ // required Title element
+ node.appendChild(this.createElementDefaultNS(
+ "Title", context.title
+ ));
+
+ // optional KeywordList element
+ if (context.keywords) {
+ node.appendChild(this.write_wmc_KeywordList(context.keywords));
+ }
+
+ // optional Abstract element
+ if (context["abstract"]) {
+ node.appendChild(this.createElementDefaultNS(
+ "Abstract", context["abstract"]
+ ));
+ }
+
+ // Optional LogoURL element
+ if (context.logo) {
+ node.appendChild(this.write_wmc_URLType("LogoURL", context.logo.href, context.logo));
+ }
+
+ // Optional DescriptionURL element
+ if (context.descriptionURL) {
+ node.appendChild(this.write_wmc_URLType("DescriptionURL", context.descriptionURL));
+ }
+
+ // Optional ContactInformation element
+ if (context.contactInformation) {
+ node.appendChild(this.write_wmc_ContactInformation(context.contactInformation));
+ }
+
+ // OpenLayers specific map properties
+ node.appendChild(this.write_ol_MapExtension(context));
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_KeywordList
+ */
+ write_wmc_KeywordList: function(keywords) {
+ var node = this.createElementDefaultNS("KeywordList");
+
+ for (var i=0, len=keywords.length; i<len; i++) {
+ node.appendChild(this.createElementDefaultNS(
+ "Keyword", keywords[i]
+ ));
+ }
+ return node;
+ },
+ /**
+ * Method: write_wmc_ContactInformation
+ */
+ write_wmc_ContactInformation: function(contact) {
+ var node = this.createElementDefaultNS("ContactInformation");
+
+ if (contact.personPrimary) {
+ node.appendChild(this.write_wmc_ContactPersonPrimary(contact.personPrimary));
+ }
+ if (contact.position) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactPosition", contact.position
+ ));
+ }
+ if (contact.contactAddress) {
+ node.appendChild(this.write_wmc_ContactAddress(contact.contactAddress));
+ }
+ if (contact.phone) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactVoiceTelephone", contact.phone
+ ));
+ }
+ if (contact.fax) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactFacsimileTelephone", contact.fax
+ ));
+ }
+ if (contact.email) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactElectronicMailAddress", contact.email
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_ContactPersonPrimary
+ */
+ write_wmc_ContactPersonPrimary: function(personPrimary) {
+ var node = this.createElementDefaultNS("ContactPersonPrimary");
+ if (personPrimary.person) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactPerson", personPrimary.person
+ ));
+ }
+ if (personPrimary.organization) {
+ node.appendChild(this.createElementDefaultNS(
+ "ContactOrganization", personPrimary.organization
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_ContactAddress
+ */
+ write_wmc_ContactAddress: function(contactAddress) {
+ var node = this.createElementDefaultNS("ContactAddress");
+ if (contactAddress.type) {
+ node.appendChild(this.createElementDefaultNS(
+ "AddressType", contactAddress.type
+ ));
+ }
+ if (contactAddress.address) {
+ node.appendChild(this.createElementDefaultNS(
+ "Address", contactAddress.address
+ ));
+ }
+ if (contactAddress.city) {
+ node.appendChild(this.createElementDefaultNS(
+ "City", contactAddress.city
+ ));
+ }
+ if (contactAddress.stateOrProvince) {
+ node.appendChild(this.createElementDefaultNS(
+ "StateOrProvince", contactAddress.stateOrProvince
+ ));
+ }
+ if (contactAddress.postcode) {
+ node.appendChild(this.createElementDefaultNS(
+ "PostCode", contactAddress.postcode
+ ));
+ }
+ if (contactAddress.country) {
+ node.appendChild(this.createElementDefaultNS(
+ "Country", contactAddress.country
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_ol_MapExtension
+ */
+ write_ol_MapExtension: function(context) {
+ var node = this.createElementDefaultNS("Extension");
+
+ var bounds = context.maxExtent;
+ if(bounds) {
+ var maxExtent = this.createElementNS(
+ this.namespaces.ol, "ol:maxExtent"
+ );
+ this.setAttributes(maxExtent, {
+ minx: bounds.left.toPrecision(18),
+ miny: bounds.bottom.toPrecision(18),
+ maxx: bounds.right.toPrecision(18),
+ maxy: bounds.top.toPrecision(18)
+ });
+ node.appendChild(maxExtent);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_LayerList
+ * Create a LayerList node given an context object.
+ *
+ * Parameters:
+ * context - {Object} Context object.
+ *
+ * Returns:
+ * {Element} A WMC LayerList element node.
+ */
+ write_wmc_LayerList: function(context) {
+ var list = this.createElementDefaultNS("LayerList");
+
+ for(var i=0, len=context.layersContext.length; i<len; ++i) {
+ list.appendChild(this.write_wmc_Layer(context.layersContext[i]));
+ }
+
+ return list;
+ },
+
+ /**
+ * Method: write_wmc_Layer
+ * Create a Layer node given a layer context object.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.}
+ *
+ * Returns:
+ * {Element} A WMC Layer element node.
+ */
+ write_wmc_Layer: function(context) {
+ var node = this.createElementDefaultNS(
+ "Layer", null, {
+ queryable: context.queryable ? "1" : "0",
+ hidden: context.visibility ? "0" : "1"
+ }
+ );
+
+ // required Server element
+ node.appendChild(this.write_wmc_Server(context));
+
+ // required Name element
+ node.appendChild(this.createElementDefaultNS(
+ "Name", context.name
+ ));
+
+ // required Title element
+ node.appendChild(this.createElementDefaultNS(
+ "Title", context.title
+ ));
+
+ // optional Abstract element
+ if (context["abstract"]) {
+ node.appendChild(this.createElementDefaultNS(
+ "Abstract", context["abstract"]
+ ));
+ }
+
+ // optional DataURL element
+ if (context.dataURL) {
+ node.appendChild(this.write_wmc_URLType("DataURL", context.dataURL));
+ }
+
+ // optional MetadataURL element
+ if (context.metadataURL) {
+ node.appendChild(this.write_wmc_URLType("MetadataURL", context.metadataURL));
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_LayerExtension
+ * Add OpenLayers specific layer parameters to an Extension element.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.
+ *
+ * Returns:
+ * {Element} A WMC Extension element (for a layer).
+ */
+ write_wmc_LayerExtension: function(context) {
+ var node = this.createElementDefaultNS("Extension");
+
+ var bounds = context.maxExtent;
+ var maxExtent = this.createElementNS(
+ this.namespaces.ol, "ol:maxExtent"
+ );
+ this.setAttributes(maxExtent, {
+ minx: bounds.left.toPrecision(18),
+ miny: bounds.bottom.toPrecision(18),
+ maxx: bounds.right.toPrecision(18),
+ maxy: bounds.top.toPrecision(18)
+ });
+ node.appendChild(maxExtent);
+
+ if (context.tileSize && !context.singleTile) {
+ var size = this.createElementNS(
+ this.namespaces.ol, "ol:tileSize"
+ );
+ this.setAttributes(size, context.tileSize);
+ node.appendChild(size);
+ }
+
+ var properties = [
+ "transparent", "numZoomLevels", "units", "isBaseLayer",
+ "opacity", "displayInLayerSwitcher", "singleTile"
+ ];
+ var child;
+ for(var i=0, len=properties.length; i<len; ++i) {
+ child = this.createOLPropertyNode(context, properties[i]);
+ if(child) {
+ node.appendChild(child);
+ }
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: createOLPropertyNode
+ * Create a node representing an OpenLayers property. If the property is
+ * null or undefined, null will be returned.
+ *
+ * Parameters:
+ * obj - {Object} An object.
+ * prop - {String} A property.
+ *
+ * Returns:
+ * {Element} A property node.
+ */
+ createOLPropertyNode: function(obj, prop) {
+ var node = null;
+ if(obj[prop] != null) {
+ node = this.createElementNS(this.namespaces.ol, "ol:" + prop);
+ node.appendChild(this.createTextNode(obj[prop].toString()));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_Server
+ * Create a Server node given a layer context object.
+ *
+ * Parameters:
+ * context - {Object} Layer context object.
+ *
+ * Returns:
+ * {Element} A WMC Server element node.
+ */
+ write_wmc_Server: function(context) {
+ var server = context.server;
+ var node = this.createElementDefaultNS("Server");
+ var attributes = {
+ service: "OGC:WMS",
+ version: server.version
+ };
+ if (server.title) {
+ attributes.title = server.title;
+ }
+ this.setAttributes(node, attributes);
+
+ // required OnlineResource element
+ node.appendChild(this.write_wmc_OnlineResource(server.url));
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_URLType
+ * Create a LogoURL/DescriptionURL/MetadataURL/DataURL/LegendURL node given a object and elementName.
+ *
+ * Parameters:
+ * elName - {String} Name of element (LogoURL/DescriptionURL/MetadataURL/LegendURL)
+ * url - {String} URL string value
+ * attr - {Object} Optional attributes (width, height, format)
+ *
+ * Returns:
+ * {Element} A WMC element node.
+ */
+ write_wmc_URLType: function(elName, url, attr) {
+ var node = this.createElementDefaultNS(elName);
+ node.appendChild(this.write_wmc_OnlineResource(url));
+ if (attr) {
+ var optionalAttributes = ["width", "height", "format"];
+ for (var i=0; i<optionalAttributes.length; i++) {
+ if (optionalAttributes[i] in attr) {
+ node.setAttribute(optionalAttributes[i], attr[optionalAttributes[i]]);
+ }
+ }
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_DimensionList
+ */
+ write_wmc_DimensionList: function(context) {
+ var node = this.createElementDefaultNS("DimensionList");
+ var required_attributes = {
+ name: true,
+ units: true,
+ unitSymbol: true,
+ userValue: true
+ };
+ for (var dim in context.dimensions) {
+ var attributes = {};
+ var dimension = context.dimensions[dim];
+ for (var name in dimension) {
+ if (typeof dimension[name] == "boolean") {
+ attributes[name] = Number(dimension[name]);
+ } else {
+ attributes[name] = dimension[name];
+ }
+ }
+ var values = "";
+ if (attributes.values) {
+ values = attributes.values.join(",");
+ delete attributes.values;
+ }
+
+ node.appendChild(this.createElementDefaultNS(
+ "Dimension", values, attributes
+ ));
+ }
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_FormatList
+ * Create a FormatList node given a layer context.
+ *
+ * Parameters:
+ * context - {Object} Layer context object.
+ *
+ * Returns:
+ * {Element} A WMC FormatList element node.
+ */
+ write_wmc_FormatList: function(context) {
+ var node = this.createElementDefaultNS("FormatList");
+ for (var i=0, len=context.formats.length; i<len; i++) {
+ var format = context.formats[i];
+ node.appendChild(this.createElementDefaultNS(
+ "Format",
+ format.value,
+ (format.current && format.current == true) ?
+ {current: "1"} : null
+ ));
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_StyleList
+ * Create a StyleList node given a layer context.
+ *
+ * Parameters:
+ * layer - {Object} Layer context object.
+ *
+ * Returns:
+ * {Element} A WMC StyleList element node.
+ */
+ write_wmc_StyleList: function(layer) {
+ var node = this.createElementDefaultNS("StyleList");
+
+ var styles = layer.styles;
+ if (styles && OpenLayers.Util.isArray(styles)) {
+ var sld;
+ for (var i=0, len=styles.length; i<len; i++) {
+ var s = styles[i];
+ // three style types to consider
+ // [1] linked SLD
+ // [2] inline SLD
+ // [3] named style
+ // running child nodes always gets name, optionally gets href or body
+ var style = this.createElementDefaultNS(
+ "Style",
+ null,
+ (s.current && s.current == true) ?
+ {current: "1"} : null
+ );
+ if(s.href) { // [1]
+ sld = this.createElementDefaultNS("SLD");
+ // Name is optional.
+ if (s.name) {
+ sld.appendChild(this.createElementDefaultNS("Name", s.name));
+ }
+ // Title is optional.
+ if (s.title) {
+ sld.appendChild(this.createElementDefaultNS("Title", s.title));
+ }
+ // LegendURL is optional
+ if (s.legend) {
+ sld.appendChild(this.write_wmc_URLType("LegendURL", s.legend.href, s.legend));
+ }
+
+ var link = this.write_wmc_OnlineResource(s.href);
+ sld.appendChild(link);
+ style.appendChild(sld);
+ } else if(s.body) { // [2]
+ sld = this.createElementDefaultNS("SLD");
+ // Name is optional.
+ if (s.name) {
+ sld.appendChild(this.createElementDefaultNS("Name", s.name));
+ }
+ // Title is optional.
+ if (s.title) {
+ sld.appendChild(this.createElementDefaultNS("Title", s.title));
+ }
+ // LegendURL is optional
+ if (s.legend) {
+ sld.appendChild(this.write_wmc_URLType("LegendURL", s.legend.href, s.legend));
+ }
+
+ // read in body as xml doc - assume proper namespace declarations
+ var doc = OpenLayers.Format.XML.prototype.read.apply(this, [s.body]);
+ // append to StyledLayerDescriptor node
+ var imported = doc.documentElement;
+ if(sld.ownerDocument && sld.ownerDocument.importNode) {
+ imported = sld.ownerDocument.importNode(imported, true);
+ }
+ sld.appendChild(imported);
+ style.appendChild(sld);
+ } else { // [3]
+ // both Name and Title are required.
+ style.appendChild(this.createElementDefaultNS("Name", s.name));
+ style.appendChild(this.createElementDefaultNS("Title", s.title));
+ // Abstract is optional
+ if (s['abstract']) { // abstract is a js keyword
+ style.appendChild(this.createElementDefaultNS(
+ "Abstract", s['abstract']
+ ));
+ }
+ // LegendURL is optional
+ if (s.legend) {
+ style.appendChild(this.write_wmc_URLType("LegendURL", s.legend.href, s.legend));
+ }
+ }
+ node.appendChild(style);
+ }
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: write_wmc_OnlineResource
+ * Create an OnlineResource node given a URL.
+ *
+ * Parameters:
+ * href - {String} URL for the resource.
+ *
+ * Returns:
+ * {Element} A WMC OnlineResource element node.
+ */
+ write_wmc_OnlineResource: function(href) {
+ var node = this.createElementDefaultNS("OnlineResource");
+ this.setAttributeNS(node, this.namespaces.xlink, "xlink:type", "simple");
+ this.setAttributeNS(node, this.namespaces.xlink, "xlink:href", href);
+ return node;
+ },
+
+ /**
+ * Method: getOnlineResource_href
+ */
+ getOnlineResource_href: function(node) {
+ var object = {};
+ var links = node.getElementsByTagName("OnlineResource");
+ if(links.length > 0) {
+ this.read_wmc_OnlineResource(object, links[0]);
+ }
+ return object.href;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Format.WMC.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMC/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/WMC/v1_0_0.js
new file mode 100644
index 0000000..ace0d95
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMC/v1_0_0.js
@@ -0,0 +1,104 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_0_0
+ * Read and write WMC version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.WMC.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/context
+ * http://schemas.opengis.net/context/1.0.0/context.xsd
+ */
+ schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC.v1_0_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WMC> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Method: read_wmc_SRS
+ */
+ read_wmc_SRS: function(layerContext, node) {
+ var srs = this.getChildValue(node);
+ if (typeof layerContext.projections != "object") {
+ layerContext.projections = {};
+ }
+ var values = srs.split(/ +/);
+ for (var i=0, len=values.length; i<len; i++) {
+ layerContext.projections[values[i]] = true;
+ }
+ },
+
+ /**
+ * Method: write_wmc_Layer
+ * Create a Layer node given a layer context object. This method adds
+ * elements specific to version 1.0.0.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.}
+ *
+ * Returns:
+ * {Element} A WMC Layer element node.
+ */
+ write_wmc_Layer: function(context) {
+ var node = OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(
+ this, [context]
+ );
+
+ // optional SRS element(s)
+ if (context.srs) {
+ var projections = [];
+ for(var name in context.srs) {
+ projections.push(name);
+ }
+ node.appendChild(this.createElementDefaultNS("SRS", projections.join(" ")));
+ }
+
+ // optional FormatList element
+ node.appendChild(this.write_wmc_FormatList(context));
+
+ // optional StyleList element
+ node.appendChild(this.write_wmc_StyleList(context));
+
+ // optional DimensionList element
+ if (context.dimensions) {
+ node.appendChild(this.write_wmc_DimensionList(context));
+ }
+
+ // OpenLayers specific properties go in an Extension element
+ node.appendChild(this.write_wmc_LayerExtension(context));
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMC.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMC/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/WMC/v1_1_0.js
new file mode 100644
index 0000000..e5efc3e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMC/v1_1_0.js
@@ -0,0 +1,149 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_1_0
+ * Read and write WMC version 1.1.0.
+ *
+ * Differences between 1.1.0 and 1.0.0:
+ * - 1.1.0 Layers have optional sld:MinScaleDenominator and
+ * sld:MaxScaleDenominator
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WMC.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1.0
+ */
+ VERSION: "1.1.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/context
+ * http://schemas.opengis.net/context/1.1.0/context.xsd
+ */
+ schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.WMC.v1_1_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.WMC> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+ this, [options]
+ );
+ },
+
+ /**
+ * Method: read_sld_MinScaleDenominator
+ * Read a sld:MinScaleDenominator node.
+ *
+ * Parameters:
+ * layerContext - {Object} An object representing a layer.
+ * node - {Element} An element node.
+ */
+ read_sld_MinScaleDenominator: function(layerContext, node) {
+ var minScaleDenominator = parseFloat(this.getChildValue(node));
+ if (minScaleDenominator > 0) {
+ layerContext.maxScale = minScaleDenominator;
+ }
+ },
+
+ /**
+ * Method: read_sld_MaxScaleDenominator
+ * Read a sld:MaxScaleDenominator node.
+ *
+ * Parameters:
+ * layerContext - {Object} An object representing a layer.
+ * node - {Element} An element node.
+ */
+ read_sld_MaxScaleDenominator: function(layerContext, node) {
+ layerContext.minScale = parseFloat(this.getChildValue(node));
+ },
+
+ /**
+ * Method: read_wmc_SRS
+ */
+ read_wmc_SRS: function(layerContext, node) {
+ if (! ("srs" in layerContext)) {
+ layerContext.srs = {};
+ }
+ layerContext.srs[this.getChildValue(node)] = true;
+ },
+
+ /**
+ * Method: write_wmc_Layer
+ * Create a Layer node given a layer context object. This method adds
+ * elements specific to version 1.1.0.
+ *
+ * Parameters:
+ * context - {Object} A layer context object.}
+ *
+ * Returns:
+ * {Element} A WMC Layer element node.
+ */
+ write_wmc_Layer: function(context) {
+ var node = OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(
+ this, [context]
+ );
+
+ // min/max scale denominator elements go before the 4th element in v1
+ if(context.maxScale) {
+ var minSD = this.createElementNS(
+ this.namespaces.sld, "sld:MinScaleDenominator"
+ );
+ minSD.appendChild(this.createTextNode(context.maxScale.toPrecision(16)));
+ node.appendChild(minSD);
+ }
+
+ if(context.minScale) {
+ var maxSD = this.createElementNS(
+ this.namespaces.sld, "sld:MaxScaleDenominator"
+ );
+ maxSD.appendChild(this.createTextNode(context.minScale.toPrecision(16)));
+ node.appendChild(maxSD);
+ }
+
+ // optional SRS element(s)
+ if (context.srs) {
+ for(var name in context.srs) {
+ node.appendChild(this.createElementDefaultNS("SRS", name));
+ }
+ }
+
+ // optional FormatList element
+ node.appendChild(this.write_wmc_FormatList(context));
+
+ // optional StyleList element
+ node.appendChild(this.write_wmc_StyleList(context));
+
+ // optional DimensionList element
+ if (context.dimensions) {
+ node.appendChild(this.write_wmc_DimensionList(context));
+ }
+
+ // OpenLayers specific properties go in an Extension element
+ node.appendChild(this.write_wmc_LayerExtension(context));
+
+ return node;
+
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMC.v1_1_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities.js
new file mode 100644
index 0000000..2bf3cef
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities.js
@@ -0,0 +1,56 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities
+ * Read WMS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WMSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.1".
+ */
+ defaultVersion: "1.1.1",
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ *
+ * Currently supported profiles:
+ * - WMSC - parses vendor specific capabilities for WMS-C.
+ */
+ profile: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities
+ * Create a new parser for WMS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1.js
new file mode 100644
index 0000000..ef5c133
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1.js
@@ -0,0 +1,368 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities.v1
+ * Abstract class not to be instantiated directly. Creates
+ * the common parts for both WMS 1.1.X and WMS 1.3.X.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMSCapabilities.v1 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wms: "http://www.opengis.net/wms",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wms",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1
+ * Create an instance of one of the subclasses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return a list of layers.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} List of named layers.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var raw = data;
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ if (capabilities.service === undefined) {
+ // an exception must have occurred, so parse it
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ capabilities.error = parser.read(raw);
+ }
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": {
+ "Service": function(node, obj) {
+ obj.service = {};
+ this.readChildNodes(node, obj.service);
+ },
+ "Name": function(node, obj) {
+ obj.name = this.getChildValue(node);
+ },
+ "Title": function(node, obj) {
+ obj.title = this.getChildValue(node);
+ },
+ "Abstract": function(node, obj) {
+ obj["abstract"] = this.getChildValue(node);
+ },
+ "BoundingBox": function(node, obj) {
+ var bbox = {};
+ bbox.bbox = [
+ parseFloat(node.getAttribute("minx")),
+ parseFloat(node.getAttribute("miny")),
+ parseFloat(node.getAttribute("maxx")),
+ parseFloat(node.getAttribute("maxy"))
+ ];
+ var res = {
+ x: parseFloat(node.getAttribute("resx")),
+ y: parseFloat(node.getAttribute("resy"))
+ };
+
+ if (! (isNaN(res.x) && isNaN(res.y))) {
+ bbox.res = res;
+ }
+ // return the bbox so that descendant classes can set the
+ // CRS and SRS and add it to the obj
+ return bbox;
+ },
+ "OnlineResource": function(node, obj) {
+ obj.href = this.getAttributeNS(node, this.namespaces.xlink,
+ "href");
+ },
+ "ContactInformation": function(node, obj) {
+ obj.contactInformation = {};
+ this.readChildNodes(node, obj.contactInformation);
+ },
+ "ContactPersonPrimary": function(node, obj) {
+ obj.personPrimary = {};
+ this.readChildNodes(node, obj.personPrimary);
+ },
+ "ContactPerson": function(node, obj) {
+ obj.person = this.getChildValue(node);
+ },
+ "ContactOrganization": function(node, obj) {
+ obj.organization = this.getChildValue(node);
+ },
+ "ContactPosition": function(node, obj) {
+ obj.position = this.getChildValue(node);
+ },
+ "ContactAddress": function(node, obj) {
+ obj.contactAddress = {};
+ this.readChildNodes(node, obj.contactAddress);
+ },
+ "AddressType": function(node, obj) {
+ obj.type = this.getChildValue(node);
+ },
+ "Address": function(node, obj) {
+ obj.address = this.getChildValue(node);
+ },
+ "City": function(node, obj) {
+ obj.city = this.getChildValue(node);
+ },
+ "StateOrProvince": function(node, obj) {
+ obj.stateOrProvince = this.getChildValue(node);
+ },
+ "PostCode": function(node, obj) {
+ obj.postcode = this.getChildValue(node);
+ },
+ "Country": function(node, obj) {
+ obj.country = this.getChildValue(node);
+ },
+ "ContactVoiceTelephone": function(node, obj) {
+ obj.phone = this.getChildValue(node);
+ },
+ "ContactFacsimileTelephone": function(node, obj) {
+ obj.fax = this.getChildValue(node);
+ },
+ "ContactElectronicMailAddress": function(node, obj) {
+ obj.email = this.getChildValue(node);
+ },
+ "Fees": function(node, obj) {
+ var fees = this.getChildValue(node);
+ if (fees && fees.toLowerCase() != "none") {
+ obj.fees = fees;
+ }
+ },
+ "AccessConstraints": function(node, obj) {
+ var constraints = this.getChildValue(node);
+ if (constraints && constraints.toLowerCase() != "none") {
+ obj.accessConstraints = constraints;
+ }
+ },
+ "Capability": function(node, obj) {
+ obj.capability = {
+ nestedLayers: [],
+ layers: []
+ };
+ this.readChildNodes(node, obj.capability);
+ },
+ "Request": function(node, obj) {
+ obj.request = {};
+ this.readChildNodes(node, obj.request);
+ },
+ "GetCapabilities": function(node, obj) {
+ obj.getcapabilities = {formats: []};
+ this.readChildNodes(node, obj.getcapabilities);
+ },
+ "Format": function(node, obj) {
+ if (OpenLayers.Util.isArray(obj.formats)) {
+ obj.formats.push(this.getChildValue(node));
+ } else {
+ obj.format = this.getChildValue(node);
+ }
+ },
+ "DCPType": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "HTTP": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Get": function(node, obj) {
+ obj.get = {};
+ this.readChildNodes(node, obj.get);
+ // backwards compatibility
+ if (!obj.href) {
+ obj.href = obj.get.href;
+ }
+ },
+ "Post": function(node, obj) {
+ obj.post = {};
+ this.readChildNodes(node, obj.post);
+ // backwards compatibility
+ if (!obj.href) {
+ obj.href = obj.get.href;
+ }
+ },
+ "GetMap": function(node, obj) {
+ obj.getmap = {formats: []};
+ this.readChildNodes(node, obj.getmap);
+ },
+ "GetFeatureInfo": function(node, obj) {
+ obj.getfeatureinfo = {formats: []};
+ this.readChildNodes(node, obj.getfeatureinfo);
+ },
+ "Exception": function(node, obj) {
+ obj.exception = {formats: []};
+ this.readChildNodes(node, obj.exception);
+ },
+ "Layer": function(node, obj) {
+ var parentLayer, capability;
+ if (obj.capability) {
+ capability = obj.capability;
+ parentLayer = obj;
+ } else {
+ capability = obj;
+ }
+ var attrNode = node.getAttributeNode("queryable");
+ var queryable = (attrNode && attrNode.specified) ?
+ node.getAttribute("queryable") : null;
+ attrNode = node.getAttributeNode("cascaded");
+ var cascaded = (attrNode && attrNode.specified) ?
+ node.getAttribute("cascaded") : null;
+ attrNode = node.getAttributeNode("opaque");
+ var opaque = (attrNode && attrNode.specified) ?
+ node.getAttribute('opaque') : null;
+ var noSubsets = node.getAttribute('noSubsets');
+ var fixedWidth = node.getAttribute('fixedWidth');
+ var fixedHeight = node.getAttribute('fixedHeight');
+ var parent = parentLayer || {},
+ extend = OpenLayers.Util.extend;
+ var layer = {
+ nestedLayers: [],
+ styles: parentLayer ? [].concat(parentLayer.styles) : [],
+ srs: parentLayer ? extend({}, parent.srs) : {},
+ metadataURLs: [],
+ bbox: parentLayer ? extend({}, parent.bbox) : {},
+ llbbox: parent.llbbox,
+ dimensions: parentLayer ? extend({}, parent.dimensions) : {},
+ authorityURLs: parentLayer ? extend({}, parent.authorityURLs) : {},
+ identifiers: {},
+ keywords: [],
+ queryable: (queryable && queryable !== "") ?
+ (queryable === "1" || queryable === "true" ) :
+ (parent.queryable || false),
+ cascaded: (cascaded !== null) ? parseInt(cascaded) :
+ (parent.cascaded || 0),
+ opaque: opaque ?
+ (opaque === "1" || opaque === "true" ) :
+ (parent.opaque || false),
+ noSubsets: (noSubsets !== null) ?
+ (noSubsets === "1" || noSubsets === "true" ) :
+ (parent.noSubsets || false),
+ fixedWidth: (fixedWidth != null) ?
+ parseInt(fixedWidth) : (parent.fixedWidth || 0),
+ fixedHeight: (fixedHeight != null) ?
+ parseInt(fixedHeight) : (parent.fixedHeight || 0),
+ minScale: parent.minScale,
+ maxScale: parent.maxScale,
+ attribution: parent.attribution
+ };
+ obj.nestedLayers.push(layer);
+ layer.capability = capability;
+ this.readChildNodes(node, layer);
+ delete layer.capability;
+ if(layer.name) {
+ var parts = layer.name.split(":"),
+ request = capability.request,
+ gfi = request.getfeatureinfo;
+ if(parts.length > 0) {
+ layer.prefix = parts[0];
+ }
+ capability.layers.push(layer);
+ if (layer.formats === undefined) {
+ layer.formats = request.getmap.formats;
+ }
+ if (layer.infoFormats === undefined && gfi) {
+ layer.infoFormats = gfi.formats;
+ }
+ }
+ },
+ "Attribution": function(node, obj) {
+ obj.attribution = {};
+ this.readChildNodes(node, obj.attribution);
+ },
+ "LogoURL": function(node, obj) {
+ obj.logo = {
+ width: node.getAttribute("width"),
+ height: node.getAttribute("height")
+ };
+ this.readChildNodes(node, obj.logo);
+ },
+ "Style": function(node, obj) {
+ var style = {};
+ obj.styles.push(style);
+ this.readChildNodes(node, style);
+ },
+ "LegendURL": function(node, obj) {
+ var legend = {
+ width: node.getAttribute("width"),
+ height: node.getAttribute("height")
+ };
+ obj.legend = legend;
+ this.readChildNodes(node, legend);
+ },
+ "MetadataURL": function(node, obj) {
+ var metadataURL = {type: node.getAttribute("type")};
+ obj.metadataURLs.push(metadataURL);
+ this.readChildNodes(node, metadataURL);
+ },
+ "DataURL": function(node, obj) {
+ obj.dataURL = {};
+ this.readChildNodes(node, obj.dataURL);
+ },
+ "FeatureListURL": function(node, obj) {
+ obj.featureListURL = {};
+ this.readChildNodes(node, obj.featureListURL);
+ },
+ "AuthorityURL": function(node, obj) {
+ var name = node.getAttribute("name");
+ var authority = {};
+ this.readChildNodes(node, authority);
+ obj.authorityURLs[name] = authority.href;
+ },
+ "Identifier": function(node, obj) {
+ var authority = node.getAttribute("authority");
+ obj.identifiers[authority] = this.getChildValue(node);
+ },
+ "KeywordList": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "SRS": function(node, obj) {
+ obj.srs[this.getChildValue(node)] = true;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1.js
new file mode 100644
index 0000000..0e15d38
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1.js
@@ -0,0 +1,122 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities.v1_1
+ * Abstract class not to be instantiated directly.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1, {
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "WMT_MS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Keyword": function(node, obj) {
+ if (obj.keywords) {
+ obj.keywords.push(this.getChildValue(node));
+ }
+ },
+ "DescribeLayer": function(node, obj) {
+ obj.describelayer = {formats: []};
+ this.readChildNodes(node, obj.describelayer);
+ },
+ "GetLegendGraphic": function(node, obj) {
+ obj.getlegendgraphic = {formats: []};
+ this.readChildNodes(node, obj.getlegendgraphic);
+ },
+ "GetStyles": function(node, obj) {
+ obj.getstyles = {formats: []};
+ this.readChildNodes(node, obj.getstyles);
+ },
+ "PutStyles": function(node, obj) {
+ obj.putstyles = {formats: []};
+ this.readChildNodes(node, obj.putstyles);
+ },
+ "UserDefinedSymbolization": function(node, obj) {
+ var userSymbols = {
+ supportSLD: parseInt(node.getAttribute("SupportSLD")) == 1,
+ userLayer: parseInt(node.getAttribute("UserLayer")) == 1,
+ userStyle: parseInt(node.getAttribute("UserStyle")) == 1,
+ remoteWFS: parseInt(node.getAttribute("RemoteWFS")) == 1
+ };
+ obj.userSymbols = userSymbols;
+ },
+ "LatLonBoundingBox": function(node, obj) {
+ obj.llbbox = [
+ parseFloat(node.getAttribute("minx")),
+ parseFloat(node.getAttribute("miny")),
+ parseFloat(node.getAttribute("maxx")),
+ parseFloat(node.getAttribute("maxy"))
+ ];
+ },
+ "BoundingBox": function(node, obj) {
+ var bbox = OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"].BoundingBox.apply(this, [node, obj]);
+ bbox.srs = node.getAttribute("SRS");
+ obj.bbox[bbox.srs] = bbox;
+ },
+ "ScaleHint": function(node, obj) {
+ var min = node.getAttribute("min");
+ var max = node.getAttribute("max");
+ var rad2 = Math.pow(2, 0.5);
+ var ipm = OpenLayers.INCHES_PER_UNIT["m"];
+ if (min != 0) {
+ obj.maxScale = parseFloat(
+ ((min / rad2) * ipm *
+ OpenLayers.DOTS_PER_INCH).toPrecision(13)
+ );
+ }
+ if (max != Number.POSITIVE_INFINITY) {
+ obj.minScale = parseFloat(
+ ((max / rad2) * ipm *
+ OpenLayers.DOTS_PER_INCH).toPrecision(13)
+ );
+ }
+ },
+ "Dimension": function(node, obj) {
+ var name = node.getAttribute("name").toLowerCase();
+ var dim = {
+ name: name,
+ units: node.getAttribute("units"),
+ unitsymbol: node.getAttribute("unitSymbol")
+ };
+ obj.dimensions[dim.name] = dim;
+ },
+ "Extent": function(node, obj) {
+ var name = node.getAttribute("name").toLowerCase();
+ if (name in obj["dimensions"]) {
+ var extent = obj.dimensions[name];
+ extent.nearestVal =
+ node.getAttribute("nearestValue") === "1";
+ extent.multipleVal =
+ node.getAttribute("multipleValues") === "1";
+ extent.current = node.getAttribute("current") === "1";
+ extent["default"] = node.getAttribute("default") || "";
+ var values = this.getChildValue(node);
+ extent.values = values.split(",");
+ }
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_0.js
new file mode 100644
index 0000000..a1c6279
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_0.js
@@ -0,0 +1,57 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_0
+ * Read WMS Capabilities version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_1, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_0
+ * Create a new parser for WMS capabilities version 1.1.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "SRS": function(node, obj) {
+ var srs = this.getChildValue(node);
+ var values = srs.split(/ +/);
+ for (var i=0, len=values.length; i<len; i++) {
+ obj.srs[values[i]] = true;
+ }
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1.js
new file mode 100644
index 0000000..459572b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1.js
@@ -0,0 +1,60 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_1
+ * Read WMS Capabilities version 1.1.1.
+ *
+ * Note on <ScaleHint> parsing: If the 'min' attribute is set to "0", no
+ * maxScale will be set on the layer object. If the 'max' attribute is set to
+ * "Infinity", no minScale will be set. This makes it easy to create proper
+ * {<OpenLayers.Layer.WMS>} configurations directly from the layer object
+ * literals returned by this format, because no minScale/maxScale modifications
+ * need to be made.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_1 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_1, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.1.1",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_1
+ * Create a new parser for WMS capabilities version 1.1.1.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "SRS": function(node, obj) {
+ obj.srs[this.getChildValue(node)] = true;
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1_1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
new file mode 100644
index 0000000..e58e4f7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_1_1_WMSC.js
@@ -0,0 +1,85 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_1_1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_1_1_WMSC
+ * Read WMS-C Capabilities version 1.1.1.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_1_1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_1_1, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.1.1",
+
+ /**
+ * Property: profile
+ * {String} The specific profile
+ */
+ profile: "WMSC",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSCapabilities.v1_1_1
+ * Create a new parser for WMS-C capabilities version 1.1.1.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "VendorSpecificCapabilities": function(node, obj) {
+ obj.vendorSpecific = {tileSets: []};
+ this.readChildNodes(node, obj.vendorSpecific);
+ },
+ "TileSet": function(node, vendorSpecific) {
+ var tileset = {srs: {}, bbox: {}, resolutions: []};
+ this.readChildNodes(node, tileset);
+ vendorSpecific.tileSets.push(tileset);
+ },
+ "Resolutions": function(node, tileset) {
+ var res = this.getChildValue(node).split(" ");
+ for (var i=0, len=res.length; i<len; i++) {
+ if (res[i] != "") {
+ tileset.resolutions.push(parseFloat(res[i]));
+ }
+ }
+ },
+ "Width": function(node, tileset) {
+ tileset.width = parseInt(this.getChildValue(node));
+ },
+ "Height": function(node, tileset) {
+ tileset.height = parseInt(this.getChildValue(node));
+ },
+ "Layers": function(node, tileset) {
+ tileset.layers = this.getChildValue(node);
+ },
+ "Styles": function(node, tileset) {
+ tileset.styles = this.getChildValue(node);
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1_1_1.prototype.readers["wms"])
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_1_1_WMSC"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3.js
new file mode 100644
index 0000000..57aee1a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3.js
@@ -0,0 +1,128 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_3
+ * Abstract base class for WMS Capabilities version 1.3.X.
+ * SLD 1.1.0 adds in the extra operations DescribeLayer and GetLegendGraphic,
+ * see: http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1>
+ */
+OpenLayers.Format.WMSCapabilities.v1_3 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1, {
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wms": OpenLayers.Util.applyDefaults({
+ "WMS_Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "LayerLimit": function(node, obj) {
+ obj.layerLimit = parseInt(this.getChildValue(node));
+ },
+ "MaxWidth": function(node, obj) {
+ obj.maxWidth = parseInt(this.getChildValue(node));
+ },
+ "MaxHeight": function(node, obj) {
+ obj.maxHeight = parseInt(this.getChildValue(node));
+ },
+ "BoundingBox": function(node, obj) {
+ var bbox = OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"].BoundingBox.apply(this, [node, obj]);
+ bbox.srs = node.getAttribute("CRS");
+ obj.bbox[bbox.srs] = bbox;
+ },
+ "CRS": function(node, obj) {
+ // CRS is the synonym of SRS
+ this.readers.wms.SRS.apply(this, [node, obj]);
+ },
+ "EX_GeographicBoundingBox": function(node, obj) {
+ // replacement of LatLonBoundingBox
+ obj.llbbox = [];
+ this.readChildNodes(node, obj.llbbox);
+
+ },
+ "westBoundLongitude": function(node, obj) {
+ obj[0] = this.getChildValue(node);
+ },
+ "eastBoundLongitude": function(node, obj) {
+ obj[2] = this.getChildValue(node);
+ },
+ "southBoundLatitude": function(node, obj) {
+ obj[1] = this.getChildValue(node);
+ },
+ "northBoundLatitude": function(node, obj) {
+ obj[3] = this.getChildValue(node);
+ },
+ "MinScaleDenominator": function(node, obj) {
+ obj.maxScale = parseFloat(this.getChildValue(node)).toPrecision(16);
+ },
+ "MaxScaleDenominator": function(node, obj) {
+ obj.minScale = parseFloat(this.getChildValue(node)).toPrecision(16);
+ },
+ "Dimension": function(node, obj) {
+ // dimension has extra attributes: default, multipleValues,
+ // nearestValue, current which used to be part of Extent. It now
+ // also contains the values.
+ var name = node.getAttribute("name").toLowerCase();
+ var dim = {
+ name: name,
+ units: node.getAttribute("units"),
+ unitsymbol: node.getAttribute("unitSymbol"),
+ nearestVal: node.getAttribute("nearestValue") === "1",
+ multipleVal: node.getAttribute("multipleValues") === "1",
+ "default": node.getAttribute("default") || "",
+ current: node.getAttribute("current") === "1",
+ values: this.getChildValue(node).split(",")
+
+ };
+ // Theoretically there can be more dimensions with the same
+ // name, but with a different unit. Until we meet such a case,
+ // let's just keep the same structure as the WMS 1.1
+ // GetCapabilities parser uses. We will store the last
+ // one encountered.
+ obj.dimensions[dim.name] = dim;
+ },
+ "Keyword": function(node, obj) {
+ // TODO: should we change the structure of keyword in v1.js?
+ // Make it an object with a value instead of a string?
+ var keyword = {value: this.getChildValue(node),
+ vocabulary: node.getAttribute("vocabulary")};
+ if (obj.keywords) {
+ obj.keywords.push(keyword);
+ }
+ }
+ }, OpenLayers.Format.WMSCapabilities.v1.prototype.readers["wms"]),
+ "sld": {
+ "UserDefinedSymbolization": function(node, obj) {
+ this.readers.wms.UserDefinedSymbolization.apply(this, [node, obj]);
+ // add the two extra attributes
+ obj.userSymbols.inlineFeature = parseInt(node.getAttribute("InlineFeature")) == 1;
+ obj.userSymbols.remoteWCS = parseInt(node.getAttribute("RemoteWCS")) == 1;
+ },
+ "DescribeLayer": function(node, obj) {
+ this.readers.wms.DescribeLayer.apply(this, [node, obj]);
+ },
+ "GetLegendGraphic": function(node, obj) {
+ this.readers.wms.GetLegendGraphic.apply(this, [node, obj]);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_3"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3_0.js b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3_0.js
new file mode 100644
index 0000000..c2c4ca4
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSCapabilities/v1_3_0.js
@@ -0,0 +1,30 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSCapabilities/v1_3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSCapabilities/v1_3_0
+ * Read WMS Capabilities version 1.3.0.
+ * SLD 1.1.0 adds in the extra operations DescribeLayer and GetLegendGraphic,
+ * see: http://schemas.opengis.net/sld/1.1.0/sld_capabilities.xsd
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSCapabilities.v1_3>
+ */
+OpenLayers.Format.WMSCapabilities.v1_3_0 = OpenLayers.Class(
+ OpenLayers.Format.WMSCapabilities.v1_3, {
+
+ /**
+ * Property: version
+ * {String} The specific parser version.
+ */
+ version: "1.3.0",
+
+ CLASS_NAME: "OpenLayers.Format.WMSCapabilities.v1_3_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer.js b/misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer.js
new file mode 100644
index 0000000..296262c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer.js
@@ -0,0 +1,53 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSDescribeLayer
+ * Read SLD WMS DescribeLayer response
+ * DescribeLayer is meant to couple WMS to WFS and WCS
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WMSDescribeLayer = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.1".
+ */
+ defaultVersion: "1.1.1",
+
+ /**
+ * Constructor: OpenLayers.Format.WMSDescribeLayer
+ * Create a new parser for WMS DescribeLayer responses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read DescribeLayer data from a string, and return the response.
+ * The OGC currently defines 2 formats which are allowed for output,
+ * so we need to parse these 2 types
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array} Array of {<LayerDescription>} objects which have:
+ * - {String} owsType: WFS/WCS
+ * - {String} owsURL: the online resource
+ * - {String} typeName: the name of the typename on the service
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WMSDescribeLayer"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer/v1_1.js b/misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer/v1_1.js
new file mode 100644
index 0000000..3929d4b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSDescribeLayer/v1_1.js
@@ -0,0 +1,122 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMSDescribeLayer.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSDescribeLayer.v1_1_1
+ * Read SLD WMS DescribeLayer response for WMS 1.1.X
+ * WMS 1.1.X is tightly coupled to SLD 1.0.0
+ *
+ * Example DescribeLayer request:
+ * http://demo.opengeo.org/geoserver/wms?request=DescribeLayer&version=1.1.1&layers=topp:states
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMSDescribeLayer>
+ */
+OpenLayers.Format.WMSDescribeLayer.v1_1_1 = OpenLayers.Class(
+ OpenLayers.Format.WMSDescribeLayer, {
+
+ /**
+ * Constructor: OpenLayers.Format.WMSDescribeLayer
+ * Create a new parser for WMS DescribeLayer responses.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.WMSDescribeLayer.prototype.initialize.apply(this,
+ [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read DescribeLayer data from a string, and return the response.
+ * The OGC defines 2 formats which are allowed for output,
+ * so we need to parse these 2 types for version 1.1.X
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Object with a layerDescriptions property, which holds an Array
+ * of {<LayerDescription>} objects which have:
+ * - {String} owsType: WFS/WCS
+ * - {String} owsURL: the online resource
+ * - {String} typeName: the name of the typename on the owsType service
+ * - {String} layerName: the name of the WMS layer we did a lookup for
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var children = root.childNodes;
+ var describelayer = {layerDescriptions: []};
+ var childNode, nodeName;
+ for(var i=0; i<children.length; ++i) {
+ childNode = children[i];
+ nodeName = childNode.nodeName;
+ if (nodeName == 'LayerDescription') {
+ var layerName = childNode.getAttribute('name');
+ var owsType = '';
+ var owsURL = '';
+ var typeName = '';
+ // check for owsType and owsURL attributes
+ if (childNode.getAttribute('owsType')) {
+ owsType = childNode.getAttribute('owsType');
+ owsURL = childNode.getAttribute('owsURL');
+ } else {
+ // look for wfs or wcs attribute
+ if (childNode.getAttribute('wfs') != '') {
+ owsType = 'WFS';
+ owsURL = childNode.getAttribute('wfs');
+ } else if (childNode.getAttribute('wcs') != '') {
+ owsType = 'WCS';
+ owsURL = childNode.getAttribute('wcs');
+ }
+ }
+ // look for Query child
+ var query = childNode.getElementsByTagName('Query');
+ if(query.length > 0) {
+ typeName = query[0].getAttribute('typeName');
+ if (!typeName) {
+ // because of Ionic bug
+ typeName = query[0].getAttribute('typename');
+ }
+ }
+ var layerDescription = {
+ layerName: layerName, owsType: owsType,
+ owsURL: owsURL, typeName: typeName
+ };
+ describelayer.layerDescriptions.push(layerDescription);
+
+ //TODO do this in deprecated.js instead:
+ // array style index for backwards compatibility
+ describelayer.length = describelayer.layerDescriptions.length;
+ describelayer[describelayer.length - 1] = layerDescription;
+
+ } else if (nodeName == 'ServiceException') {
+ // an exception must have occurred, so parse it
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ return {
+ error: parser.read(data)
+ };
+ }
+ }
+ return describelayer;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSDescribeLayer.v1_1_1"
+
+});
+
+// Version alias - workaround for http://trac.osgeo.org/mapserver/ticket/2257
+OpenLayers.Format.WMSDescribeLayer.v1_1_0 =
+ OpenLayers.Format.WMSDescribeLayer.v1_1_1;
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMSGetFeatureInfo.js b/misc/openlayers/lib/OpenLayers/Format/WMSGetFeatureInfo.js
new file mode 100644
index 0000000..57eb219
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMSGetFeatureInfo.js
@@ -0,0 +1,296 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMSGetFeatureInfo
+ * Class to read GetFeatureInfo responses from Web Mapping Services
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: layerIdentifier
+ * {String} All xml nodes containing this search criteria will populate an
+ * internal array of layer nodes.
+ */
+ layerIdentifier: '_layer',
+
+ /**
+ * APIProperty: featureIdentifier
+ * {String} All xml nodes containing this search criteria will populate an
+ * internal array of feature nodes for each layer node found.
+ */
+ featureIdentifier: '_feature',
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Property: gmlFormat
+ * {<OpenLayers.Format.GML>} internal GML format for parsing geometries
+ * in msGMLOutput
+ */
+ gmlFormat: null,
+
+ /**
+ * Constructor: OpenLayers.Format.WMSGetFeatureInfo
+ * Create a new parser for WMS GetFeatureInfo responses
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read WMS GetFeatureInfo data from a string, and return an array of features
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+ */
+ read: function(data) {
+ var result;
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ if(root) {
+ var scope = this;
+ var read = this["read_" + root.nodeName];
+ if(read) {
+ result = read.call(this, root);
+ } else {
+ // fall-back to GML since this is a common output format for WMS
+ // GetFeatureInfo responses
+ result = new OpenLayers.Format.GML((this.options ? this.options : {})).read(data);
+ }
+ } else {
+ result = data;
+ }
+ return result;
+ },
+
+
+ /**
+ * Method: read_msGMLOutput
+ * Parse msGMLOutput nodes.
+ *
+ * Parameters:
+ * data - {DOMElement}
+ *
+ * Returns:
+ * {Array}
+ */
+ read_msGMLOutput: function(data) {
+ var response = [];
+ var layerNodes = this.getSiblingNodesByTagCriteria(data,
+ this.layerIdentifier);
+ if (layerNodes) {
+ for (var i=0, len=layerNodes.length; i<len; ++i) {
+ var node = layerNodes[i];
+ var layerName = node.nodeName;
+ if (node.prefix) {
+ layerName = layerName.split(':')[1];
+ }
+ var layerName = layerName.replace(this.layerIdentifier, '');
+ var featureNodes = this.getSiblingNodesByTagCriteria(node,
+ this.featureIdentifier);
+ if (featureNodes) {
+ for (var j = 0; j < featureNodes.length; j++) {
+ var featureNode = featureNodes[j];
+ var geomInfo = this.parseGeometry(featureNode);
+ var attributes = this.parseAttributes(featureNode);
+ var feature = new OpenLayers.Feature.Vector(geomInfo.geometry,
+ attributes, null);
+ feature.bounds = geomInfo.bounds;
+ feature.type = layerName;
+ response.push(feature);
+ }
+ }
+ }
+ }
+ return response;
+ },
+
+ /**
+ * Method: read_FeatureInfoResponse
+ * Parse FeatureInfoResponse nodes.
+ *
+ * Parameters:
+ * data - {DOMElement}
+ *
+ * Returns:
+ * {Array}
+ */
+ read_FeatureInfoResponse: function(data) {
+ var response = [];
+ var featureNodes = this.getElementsByTagNameNS(data, '*',
+ 'FIELDS');
+
+ for(var i=0, len=featureNodes.length;i<len;i++) {
+ var featureNode = featureNodes[i];
+ var geom = null;
+
+ // attributes can be actual attributes on the FIELDS tag,
+ // or FIELD children
+ var attributes = {};
+ var j;
+ var jlen = featureNode.attributes.length;
+ if (jlen > 0) {
+ for(j=0; j<jlen; j++) {
+ var attribute = featureNode.attributes[j];
+ attributes[attribute.nodeName] = attribute.nodeValue;
+ }
+ } else {
+ var nodes = featureNode.childNodes;
+ for (j=0, jlen=nodes.length; j<jlen; ++j) {
+ var node = nodes[j];
+ if (node.nodeType != 3) {
+ attributes[node.getAttribute("name")] =
+ node.getAttribute("value");
+ }
+ }
+ }
+
+ response.push(
+ new OpenLayers.Feature.Vector(geom, attributes, null)
+ );
+ }
+ return response;
+ },
+
+ /**
+ * Method: getSiblingNodesByTagCriteria
+ * Recursively searches passed xml node and all it's descendant levels for
+ * nodes whose tagName contains the passed search string. This returns an
+ * array of all sibling nodes which match the criteria from the highest
+ * hierarchial level from which a match is found.
+ *
+ * Parameters:
+ * node - {DOMElement} An xml node
+ * criteria - {String} Search string which will match some part of a tagName
+ *
+ * Returns:
+ * Array({DOMElement}) An array of sibling xml nodes
+ */
+ getSiblingNodesByTagCriteria: function(node, criteria){
+ var nodes = [];
+ var children, tagName, n, matchNodes, child;
+ if (node && node.hasChildNodes()) {
+ children = node.childNodes;
+ n = children.length;
+
+ for(var k=0; k<n; k++){
+ child = children[k];
+ while (child && child.nodeType != 1) {
+ child = child.nextSibling;
+ k++;
+ }
+ tagName = (child ? child.nodeName : '');
+ if (tagName.length > 0 && tagName.indexOf(criteria) > -1) {
+ nodes.push(child);
+ } else {
+ matchNodes = this.getSiblingNodesByTagCriteria(
+ child, criteria);
+
+ if(matchNodes.length > 0){
+ (nodes.length == 0) ?
+ nodes = matchNodes : nodes.push(matchNodes);
+ }
+ }
+ }
+
+ }
+ return nodes;
+ },
+
+ /**
+ * Method: parseAttributes
+ *
+ * Parameters:
+ * node - {<DOMElement>}
+ *
+ * Returns:
+ * {Object} An attributes object.
+ *
+ * Notes:
+ * Assumes that attributes are direct child xml nodes of the passed node
+ * and contain only a single text node.
+ */
+ parseAttributes: function(node){
+ var attributes = {};
+ if (node.nodeType == 1) {
+ var children = node.childNodes;
+ var n = children.length;
+ for (var i = 0; i < n; ++i) {
+ var child = children[i];
+ if (child.nodeType == 1) {
+ var grandchildren = child.childNodes;
+ var name = (child.prefix) ?
+ child.nodeName.split(":")[1] : child.nodeName;
+ if (grandchildren.length == 0) {
+ attributes[name] = null;
+ } else if (grandchildren.length == 1) {
+ var grandchild = grandchildren[0];
+ if (grandchild.nodeType == 3 ||
+ grandchild.nodeType == 4) {
+ var value = grandchild.nodeValue.replace(
+ this.regExes.trimSpace, "");
+ attributes[name] = value;
+ }
+ }
+ }
+ }
+ }
+ return attributes;
+ },
+
+ /**
+ * Method: parseGeometry
+ * Parse the geometry and the feature bounds out of the node using
+ * Format.GML
+ *
+ * Parameters:
+ * node - {<DOMElement>}
+ *
+ * Returns:
+ * {Object} An object containing the geometry and the feature bounds
+ */
+ parseGeometry: function(node) {
+ // we need to use the old Format.GML parser since we do not know the
+ // geometry name
+ if (!this.gmlFormat) {
+ this.gmlFormat = new OpenLayers.Format.GML();
+ }
+ var feature = this.gmlFormat.parseFeature(node);
+ var geometry, bounds = null;
+ if (feature) {
+ geometry = feature.geometry && feature.geometry.clone();
+ bounds = feature.bounds && feature.bounds.clone();
+ feature.destroy();
+ }
+ return {geometry: geometry, bounds: bounds};
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMSGetFeatureInfo"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities.js b/misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities.js
new file mode 100644
index 0000000..9cff69c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities.js
@@ -0,0 +1,230 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMTSCapabilities
+ * Read WMTS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WMTSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * APIProperty: yx
+ * {Object} Members in the yx object are used to determine if a CRS URN
+ * corresponds to a CRS with y,x axis order. Member names are CRS URNs
+ * and values are boolean. By default, the following CRS URN are
+ * assumed to correspond to a CRS with y,x axis order:
+ *
+ * * urn:ogc:def:crs:EPSG::4326
+ */
+ yx: {
+ "urn:ogc:def:crs:EPSG::4326": true
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WMTSCapabilities
+ * Create a new parser for WMTS capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return information about
+ * the service (offering and observedProperty mostly).
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Info about the WMTS Capabilities
+ */
+
+ /**
+ * APIMethod: createLayer
+ * Create a WMTS layer given a capabilities object.
+ *
+ * Parameters:
+ * capabilities - {Object} The object returned from a <read> call to this
+ * format.
+ * config - {Object} Configuration properties for the layer. Defaults for
+ * the layer will apply if not provided.
+ *
+ * Required config properties:
+ * layer - {String} The layer identifier.
+ *
+ * Optional config properties:
+ * matrixSet - {String} The matrix set identifier, required if there is
+ * more than one matrix set in the layer capabilities.
+ * style - {String} The name of the style
+ * format - {String} Image format for the layer. Default is the first
+ * format returned in the GetCapabilities response.
+ * param - {Object} The dimensions values eg: {"Year": "2012"}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMTS>} A properly configured WMTS layer. Throws an
+ * error if an incomplete config is provided. Returns undefined if no
+ * layer could be created with the provided config.
+ */
+ createLayer: function(capabilities, config) {
+ var layer;
+
+ // confirm required properties are supplied in config
+ if (!('layer' in config)) {
+ throw new Error("Missing property 'layer' in configuration.");
+ }
+
+ var contents = capabilities.contents;
+
+ // find the layer definition with the given identifier
+ var layers = contents.layers;
+ var layerDef;
+ for (var i=0, ii=contents.layers.length; i<ii; ++i) {
+ if (contents.layers[i].identifier === config.layer) {
+ layerDef = contents.layers[i];
+ break;
+ }
+ }
+ if (!layerDef) {
+ throw new Error("Layer not found");
+ }
+
+ var format = config.format;
+ if (!format && layerDef.formats && layerDef.formats.length) {
+ format = layerDef.formats[0];
+ }
+
+ // find the matrixSet definition
+ var matrixSet;
+ if (config.matrixSet) {
+ matrixSet = contents.tileMatrixSets[config.matrixSet];
+ } else if (layerDef.tileMatrixSetLinks.length >= 1) {
+ matrixSet = contents.tileMatrixSets[
+ layerDef.tileMatrixSetLinks[0].tileMatrixSet];
+ }
+ if (!matrixSet) {
+ throw new Error("matrixSet not found");
+ }
+
+ // get the default style for the layer
+ var style;
+ for (var i=0, ii=layerDef.styles.length; i<ii; ++i) {
+ style = layerDef.styles[i];
+ if (style.isDefault) {
+ break;
+ }
+ }
+
+ var requestEncoding = config.requestEncoding;
+ if (!requestEncoding) {
+ requestEncoding = "KVP";
+ if (capabilities.operationsMetadata.GetTile.dcp.http) {
+ var http = capabilities.operationsMetadata.GetTile.dcp.http;
+ // Get first get method
+ if (http.get[0].constraints) {
+ var constraints = http.get[0].constraints;
+ var allowedValues = constraints.GetEncoding.allowedValues;
+
+ // The OGC documentation is not clear if we should use
+ // REST or RESTful, ArcGis use RESTful,
+ // and OpenLayers use REST.
+ if (!allowedValues.KVP &&
+ (allowedValues.REST || allowedValues.RESTful)) {
+ requestEncoding = "REST";
+ }
+ }
+ }
+ }
+
+ var dimensions = [];
+ var params = config.params || {};
+ // to don't overwrite the changes in the applyDefaults
+ delete config.params;
+ for (var id = 0, ld = layerDef.dimensions.length ; id < ld ; id++) {
+ var dimension = layerDef.dimensions[id];
+ dimensions.push(dimension.identifier);
+ if (!params.hasOwnProperty(dimension.identifier)) {
+ params[dimension.identifier] = dimension['default'];
+ }
+ }
+
+ var projection = config.projection || matrixSet.supportedCRS.replace(
+ /urn:ogc:def:crs:(\w+):(.*:)?(\w+)$/, "$1:$3");
+ var units = config.units ||
+ (projection === "EPSG:4326" ? "degrees" : "m");
+
+ var resolutions = [];
+ for (var mid in matrixSet.matrixIds) {
+ if (matrixSet.matrixIds.hasOwnProperty(mid)) {
+ resolutions.push(
+ matrixSet.matrixIds[mid].scaleDenominator * 0.28E-3 /
+ OpenLayers.METERS_PER_INCH /
+ OpenLayers.INCHES_PER_UNIT[units]);
+ }
+ }
+
+ var url;
+ if (requestEncoding === "REST" && layerDef.resourceUrls) {
+ url = [];
+ var resourceUrls = layerDef.resourceUrls,
+ resourceUrl;
+ for (var t = 0, tt = layerDef.resourceUrls.length; t < tt; ++t) {
+ resourceUrl = layerDef.resourceUrls[t];
+ if (resourceUrl.format === format && resourceUrl.resourceType === "tile") {
+ url.push(resourceUrl.template);
+ }
+ }
+ }
+ else {
+ var httpGet = capabilities.operationsMetadata.GetTile.dcp.http.get;
+ url = [];
+ var constraint;
+ for (var i = 0, ii = httpGet.length; i < ii; i++) {
+ constraint = httpGet[i].constraints;
+ if (!constraint || (constraint && constraint.
+ GetEncoding.allowedValues[requestEncoding])) {
+ url.push(httpGet[i].url);
+ }
+ }
+ }
+
+ return new OpenLayers.Layer.WMTS(
+ OpenLayers.Util.applyDefaults(config, {
+ url: url,
+ requestEncoding: requestEncoding,
+ name: layerDef.title,
+ style: style.identifier,
+ format: format,
+ matrixIds: matrixSet.matrixIds,
+ matrixSet: matrixSet.identifier,
+ projection: projection,
+ units: units,
+ resolutions: config.isBaseLayer === false ? undefined :
+ resolutions,
+ serverResolutions: resolutions,
+ tileFullExtent: matrixSet.bounds,
+ dimensions: dimensions,
+ params: params
+ })
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMTSCapabilities"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..fda2584
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WMTSCapabilities/v1_0_0.js
@@ -0,0 +1,251 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMTSCapabilities.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMTSCapabilities.v1_0_0
+ * Read WMTS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.WMTSCapabilities>
+ */
+OpenLayers.Format.WMTSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.OWSCommon.v1_1_0, {
+
+ /**
+ * Property: version
+ * {String} The parser version ("1.0.0").
+ */
+ version: "1.0.0",
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ wmts: "http://www.opengis.net/wmts/1.0",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: yx
+ * {Object} Members in the yx object are used to determine if a CRS URN
+ * corresponds to a CRS with y,x axis order. Member names are CRS URNs
+ * and values are boolean. Defaults come from the
+ * <OpenLayers.Format.WMTSCapabilities> prototype.
+ */
+ yx: null,
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default namespace alias for creating element nodes.
+ */
+ defaultPrefix: "wmts",
+
+ /**
+ * Constructor: OpenLayers.Format.WMTSCapabilities.v1_0_0
+ * Create a new parser for WMTS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ this.options = options;
+ var yx = OpenLayers.Util.extend(
+ {}, OpenLayers.Format.WMTSCapabilities.prototype.yx
+ );
+ this.yx = OpenLayers.Util.extend(yx, this.yx);
+ },
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return info about the WMTS.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the SOS service.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ capabilities.version = this.version;
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wmts": {
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "Contents": function(node, obj) {
+ obj.contents = {};
+ obj.contents.layers = [];
+ obj.contents.tileMatrixSets = {};
+ this.readChildNodes(node, obj.contents);
+ },
+ "Layer": function(node, obj) {
+ var layer = {
+ styles: [],
+ formats: [],
+ dimensions: [],
+ tileMatrixSetLinks: []
+ };
+ layer.layers = [];
+ this.readChildNodes(node, layer);
+ obj.layers.push(layer);
+ },
+ "Style": function(node, obj) {
+ var style = {};
+ style.isDefault = (node.getAttribute("isDefault") === "true");
+ this.readChildNodes(node, style);
+ obj.styles.push(style);
+ },
+ "Format": function(node, obj) {
+ obj.formats.push(this.getChildValue(node));
+ },
+ "TileMatrixSetLink": function(node, obj) {
+ var tileMatrixSetLink = {};
+ this.readChildNodes(node, tileMatrixSetLink);
+ obj.tileMatrixSetLinks.push(tileMatrixSetLink);
+ },
+ "TileMatrixSet": function(node, obj) {
+ // node could be child of wmts:Contents or wmts:TileMatrixSetLink
+ // duck type wmts:Contents by looking for layers
+ if (obj.layers) {
+ // TileMatrixSet as object type in schema
+ var tileMatrixSet = {
+ matrixIds: []
+ };
+ this.readChildNodes(node, tileMatrixSet);
+ obj.tileMatrixSets[tileMatrixSet.identifier] = tileMatrixSet;
+ } else {
+ // TileMatrixSet as string type in schema
+ obj.tileMatrixSet = this.getChildValue(node);
+ }
+ },
+ "TileMatrix": function(node, obj) {
+ var tileMatrix = {
+ supportedCRS: obj.supportedCRS
+ };
+ this.readChildNodes(node, tileMatrix);
+ obj.matrixIds.push(tileMatrix);
+ },
+ "ScaleDenominator": function(node, obj) {
+ obj.scaleDenominator = parseFloat(this.getChildValue(node));
+ },
+ "TopLeftCorner": function(node, obj) {
+ var topLeftCorner = this.getChildValue(node);
+ var coords = topLeftCorner.split(" ");
+ // decide on axis order for the given CRS
+ var yx;
+ if (obj.supportedCRS) {
+ // extract out version from URN
+ var crs = obj.supportedCRS.replace(
+ /urn:ogc:def:crs:(\w+):.+:(\w+)$/,
+ "urn:ogc:def:crs:$1::$2"
+ );
+ yx = !!this.yx[crs];
+ }
+ if (yx) {
+ obj.topLeftCorner = new OpenLayers.LonLat(
+ coords[1], coords[0]
+ );
+ } else {
+ obj.topLeftCorner = new OpenLayers.LonLat(
+ coords[0], coords[1]
+ );
+ }
+ },
+ "TileWidth": function(node, obj) {
+ obj.tileWidth = parseInt(this.getChildValue(node));
+ },
+ "TileHeight": function(node, obj) {
+ obj.tileHeight = parseInt(this.getChildValue(node));
+ },
+ "MatrixWidth": function(node, obj) {
+ obj.matrixWidth = parseInt(this.getChildValue(node));
+ },
+ "MatrixHeight": function(node, obj) {
+ obj.matrixHeight = parseInt(this.getChildValue(node));
+ },
+ "ResourceURL": function(node, obj) {
+ obj.resourceUrl = obj.resourceUrl || {};
+ var resourceType = node.getAttribute("resourceType");
+ if (!obj.resourceUrls) {
+ obj.resourceUrls = [];
+ }
+ var resourceUrl = obj.resourceUrl[resourceType] = {
+ format: node.getAttribute("format"),
+ template: node.getAttribute("template"),
+ resourceType: resourceType
+ };
+ obj.resourceUrls.push(resourceUrl);
+ },
+ // not used for now, can be added in the future though
+ /*"Themes": function(node, obj) {
+ obj.themes = [];
+ this.readChildNodes(node, obj.themes);
+ },
+ "Theme": function(node, obj) {
+ var theme = {};
+ this.readChildNodes(node, theme);
+ obj.push(theme);
+ },*/
+ "WSDL": function(node, obj) {
+ obj.wsdl = {};
+ obj.wsdl.href = node.getAttribute("xlink:href");
+ // TODO: other attributes of <WSDL> element
+ },
+ "ServiceMetadataURL": function(node, obj) {
+ obj.serviceMetadataUrl = {};
+ obj.serviceMetadataUrl.href = node.getAttribute("xlink:href");
+ // TODO: other attributes of <ServiceMetadataURL> element
+ },
+ "LegendURL": function(node, obj) {
+ obj.legend = {};
+ obj.legend.href = node.getAttribute("xlink:href");
+ obj.legend.format = node.getAttribute("format");
+ },
+ "Dimension": function(node, obj) {
+ var dimension = {values: []};
+ this.readChildNodes(node, dimension);
+ obj.dimensions.push(dimension);
+ },
+ "Default": function(node, obj) {
+ obj["default"] = this.getChildValue(node);
+ },
+ "Value": function(node, obj) {
+ obj.values.push(this.getChildValue(node));
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WMTSCapabilities.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WPSCapabilities.js b/misc/openlayers/lib/OpenLayers/Format/WPSCapabilities.js
new file mode 100644
index 0000000..f0d74db
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WPSCapabilities.js
@@ -0,0 +1,48 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSCapabilities
+ * Read WPS Capabilities.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.WPSCapabilities = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.0.0".
+ */
+ defaultVersion: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Format.WPSCapabilities
+ * Create a new parser for WPS Capabilities.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return information about
+ * the service.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Info about the WPS
+ */
+
+ CLASS_NAME: "OpenLayers.Format.WPSCapabilities"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WPSCapabilities/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Format/WPSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..e6762a9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WPSCapabilities/v1_0_0.js
@@ -0,0 +1,119 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WPSCapabilities.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSCapabilities.v1_0_0
+ * Read WPS Capabilities version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WPSCapabilities.v1_0_0 = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ wps: "http://www.opengis.net/wps/1.0.0",
+ xlink: "http://www.w3.org/1999/xlink"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WPSCapabilities.v1_0_0
+ * Create a new parser for WPS capabilities version 1.0.0.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: read
+ * Read capabilities data from a string, and return info about the WPS.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object} Information about the WPS service.
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var capabilities = {};
+ this.readNode(data, capabilities);
+ return capabilities;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wps": {
+ "Capabilities": function(node, obj) {
+ this.readChildNodes(node, obj);
+ },
+ "ProcessOfferings": function(node, obj) {
+ obj.processOfferings = {};
+ this.readChildNodes(node, obj.processOfferings);
+ },
+ "Process": function(node, processOfferings) {
+ var processVersion = this.getAttributeNS(node, this.namespaces.wps, "processVersion");
+ var process = {processVersion: processVersion};
+ this.readChildNodes(node, process);
+ processOfferings[process.identifier] = process;
+ },
+ "Languages": function(node, obj) {
+ obj.languages = [];
+ this.readChildNodes(node, obj.languages);
+ },
+ "Default": function(node, languages) {
+ var language = {isDefault: true};
+ this.readChildNodes(node, language);
+ languages.push(language);
+ },
+ "Supported": function(node, languages) {
+ var language = {};
+ this.readChildNodes(node, language);
+ languages.push(language);
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WPSCapabilities.v1_0_0"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WPSDescribeProcess.js b/misc/openlayers/lib/OpenLayers/Format/WPSDescribeProcess.js
new file mode 100644
index 0000000..e8f96bb
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WPSDescribeProcess.js
@@ -0,0 +1,185 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSDescribeProcess
+ * Read WPS DescribeProcess responses.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WPSDescribeProcess = OpenLayers.Class(
+ OpenLayers.Format.XML, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ wps: "http://www.opengis.net/wps/1.0.0",
+ ows: "http://www.opengis.net/ows/1.1",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "wps",
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WPSDescribeProcess
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: read
+ * Parse a WPS DescribeProcess and return an object with its information.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object}
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var info = {};
+ this.readNode(data, info);
+ return info;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wps": {
+ "ProcessDescriptions": function(node, obj) {
+ obj.processDescriptions = {};
+ this.readChildNodes(node, obj.processDescriptions);
+ },
+ "ProcessDescription": function(node, processDescriptions) {
+ var processVersion = this.getAttributeNS(node, this.namespaces.wps, "processVersion");
+ var processDescription = {
+ processVersion: processVersion,
+ statusSupported: (node.getAttribute("statusSupported") === "true"),
+ storeSupported: (node.getAttribute("storeSupported") === "true")
+ };
+ this.readChildNodes(node, processDescription);
+ processDescriptions[processDescription.identifier] = processDescription;
+ },
+ "DataInputs": function(node, processDescription) {
+ processDescription.dataInputs = [];
+ this.readChildNodes(node, processDescription.dataInputs);
+ },
+ "ProcessOutputs": function(node, processDescription) {
+ processDescription.processOutputs = [];
+ this.readChildNodes(node, processDescription.processOutputs);
+ },
+ "Output": function(node, processOutputs) {
+ var output = {};
+ this.readChildNodes(node, output);
+ processOutputs.push(output);
+ },
+ "ComplexOutput": function(node, output) {
+ output.complexOutput = {};
+ this.readChildNodes(node, output.complexOutput);
+ },
+ "LiteralOutput": function(node, output) {
+ output.literalOutput = {};
+ this.readChildNodes(node, output.literalOutput);
+ },
+ "Input": function(node, dataInputs) {
+ var input = {
+ maxOccurs: parseInt(node.getAttribute("maxOccurs")),
+ minOccurs: parseInt(node.getAttribute("minOccurs"))
+ };
+ this.readChildNodes(node, input);
+ dataInputs.push(input);
+ },
+ "BoundingBoxData": function(node, input) {
+ input.boundingBoxData = {};
+ this.readChildNodes(node, input.boundingBoxData);
+ },
+ "CRS": function(node, obj) {
+ if (!obj.CRSs) {
+ obj.CRSs = {};
+ }
+ obj.CRSs[this.getChildValue(node)] = true;
+ },
+ "LiteralData": function(node, input) {
+ input.literalData = {};
+ this.readChildNodes(node, input.literalData);
+ },
+ "ComplexData": function(node, input) {
+ input.complexData = {};
+ this.readChildNodes(node, input.complexData);
+ },
+ "Default": function(node, complexData) {
+ complexData["default"] = {};
+ this.readChildNodes(node, complexData["default"]);
+ },
+ "Supported": function(node, complexData) {
+ complexData["supported"] = {};
+ this.readChildNodes(node, complexData["supported"]);
+ },
+ "Format": function(node, obj) {
+ var format = {};
+ this.readChildNodes(node, format);
+ if (!obj.formats) {
+ obj.formats = {};
+ }
+ obj.formats[format.mimeType] = true;
+ },
+ "MimeType": function(node, format) {
+ format.mimeType = this.getChildValue(node);
+ }
+ },
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WPSDescribeProcess"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/WPSExecute.js b/misc/openlayers/lib/OpenLayers/Format/WPSExecute.js
new file mode 100644
index 0000000..0795b0d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/WPSExecute.js
@@ -0,0 +1,395 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OWSCommon/v1_1_0.js
+ * @requires OpenLayers/Format/WCSGetCoverage.js
+ * @requires OpenLayers/Format/WFST/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WPSExecute version 1.0.0
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WPSExecute = OpenLayers.Class(OpenLayers.Format.XML,
+ OpenLayers.Format.Filter.v1_1_0, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ ows: "http://www.opengis.net/ows/1.1",
+ gml: "http://www.opengis.net/gml",
+ wps: "http://www.opengis.net/wps/1.0.0",
+ wfs: "http://www.opengis.net/wfs",
+ ogc: "http://www.opengis.net/ogc",
+ wcs: "http://www.opengis.net/wcs",
+ xlink: "http://www.w3.org/1999/xlink",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * Constant: VERSION
+ * {String} 1.0.0
+ */
+ VERSION: "1.0.0",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location
+ */
+ schemaLocation: "http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd",
+
+ schemaLocationAttr: function(options) {
+ return undefined;
+ },
+
+ /**
+ * Constructor: OpenLayers.Format.WPSExecute
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * options - {Object} Optional object.
+ *
+ * Returns:
+ * {String} An WPS Execute request XML string.
+ */
+ write: function(options) {
+ var doc;
+ if (window.ActiveXObject) {
+ doc = new ActiveXObject("Microsoft.XMLDOM");
+ this.xmldom = doc;
+ } else {
+ doc = document.implementation.createDocument("", "", null);
+ }
+ var node = this.writeNode("wps:Execute", options, doc);
+ this.setAttributeNS(
+ node, this.namespaces.xsi,
+ "xsi:schemaLocation", this.schemaLocation
+ );
+ return OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+ },
+
+ /**
+ * APIMethod: read
+ * Parse a WPS Execute and return an object with its information.
+ *
+ * Parameters:
+ * data - {String} or {DOMElement} data to read/parse.
+ *
+ * Returns:
+ * {Object}
+ */
+ read: function(data) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ if(data && data.nodeType == 9) {
+ data = data.documentElement;
+ }
+ var info = {};
+ this.readNode(data, info);
+ return info;
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "wps": {
+ "Execute": function(options) {
+ var node = this.createElementNSPlus("wps:Execute", {
+ attributes: {
+ version: this.VERSION,
+ service: 'WPS'
+ }
+ });
+ this.writeNode("ows:Identifier", options.identifier, node);
+ this.writeNode("wps:DataInputs", options.dataInputs, node);
+ this.writeNode("wps:ResponseForm", options.responseForm, node);
+ return node;
+ },
+ "ResponseForm": function(responseForm) {
+ var node = this.createElementNSPlus("wps:ResponseForm", {});
+ if (responseForm.rawDataOutput) {
+ this.writeNode("wps:RawDataOutput", responseForm.rawDataOutput, node);
+ }
+ if (responseForm.responseDocument) {
+ this.writeNode("wps:ResponseDocument", responseForm.responseDocument, node);
+ }
+ return node;
+ },
+ "ResponseDocument": function(responseDocument) {
+ var node = this.createElementNSPlus("wps:ResponseDocument", {
+ attributes: {
+ storeExecuteResponse: responseDocument.storeExecuteResponse,
+ lineage: responseDocument.lineage,
+ status: responseDocument.status
+ }
+ });
+ if (responseDocument.outputs) {
+ for (var i = 0, len = responseDocument.outputs.length; i < len; i++) {
+ this.writeNode("wps:Output", responseDocument.outputs[i], node);
+ }
+ }
+ return node;
+ },
+ "Output": function(output) {
+ var node = this.createElementNSPlus("wps:Output", {
+ attributes: {
+ asReference: output.asReference,
+ mimeType: output.mimeType,
+ encoding: output.encoding,
+ schema: output.schema
+ }
+ });
+ this.writeNode("ows:Identifier", output.identifier, node);
+ this.writeNode("ows:Title", output.title, node);
+ this.writeNode("ows:Abstract", output["abstract"], node);
+ return node;
+ },
+ "RawDataOutput": function(rawDataOutput) {
+ var node = this.createElementNSPlus("wps:RawDataOutput", {
+ attributes: {
+ mimeType: rawDataOutput.mimeType,
+ encoding: rawDataOutput.encoding,
+ schema: rawDataOutput.schema
+ }
+ });
+ this.writeNode("ows:Identifier", rawDataOutput.identifier, node);
+ return node;
+ },
+ "DataInputs": function(dataInputs) {
+ var node = this.createElementNSPlus("wps:DataInputs", {});
+ for (var i=0, ii=dataInputs.length; i<ii; ++i) {
+ this.writeNode("wps:Input", dataInputs[i], node);
+ }
+ return node;
+ },
+ "Input": function(input) {
+ var node = this.createElementNSPlus("wps:Input", {});
+ this.writeNode("ows:Identifier", input.identifier, node);
+ if (input.title) {
+ this.writeNode("ows:Title", input.title, node);
+ }
+ if (input.data) {
+ this.writeNode("wps:Data", input.data, node);
+ }
+ if (input.reference) {
+ this.writeNode("wps:Reference", input.reference, node);
+ }
+ if (input.boundingBoxData) {
+ this.writeNode("wps:BoundingBoxData", input.boundingBoxData, node);
+ }
+ return node;
+ },
+ "Data": function(data) {
+ var node = this.createElementNSPlus("wps:Data", {});
+ if (data.literalData) {
+ this.writeNode("wps:LiteralData", data.literalData, node);
+ } else if (data.complexData) {
+ this.writeNode("wps:ComplexData", data.complexData, node);
+ } else if (data.boundingBoxData) {
+ this.writeNode("ows:BoundingBox", data.boundingBoxData, node);
+ }
+ return node;
+ },
+ "LiteralData": function(literalData) {
+ var node = this.createElementNSPlus("wps:LiteralData", {
+ attributes: {
+ uom: literalData.uom
+ },
+ value: literalData.value
+ });
+ return node;
+ },
+ "ComplexData": function(complexData) {
+ var node = this.createElementNSPlus("wps:ComplexData", {
+ attributes: {
+ mimeType: complexData.mimeType,
+ encoding: complexData.encoding,
+ schema: complexData.schema
+ }
+ });
+ var data = complexData.value;
+ if (typeof data === "string") {
+ node.appendChild(
+ this.getXMLDoc().createCDATASection(complexData.value)
+ );
+ } else {
+ node.appendChild(data);
+ }
+ return node;
+ },
+ "Reference": function(reference) {
+ var node = this.createElementNSPlus("wps:Reference", {
+ attributes: {
+ mimeType: reference.mimeType,
+ "xlink:href": reference.href,
+ method: reference.method,
+ encoding: reference.encoding,
+ schema: reference.schema
+ }
+ });
+ if (reference.body) {
+ this.writeNode("wps:Body", reference.body, node);
+ }
+ return node;
+ },
+ "BoundingBoxData": function(node, obj) {
+ this.writers['ows']['BoundingBox'].apply(this, [node, obj, "wps:BoundingBoxData"]);
+ },
+ "Body": function(body) {
+ var node = this.createElementNSPlus("wps:Body", {});
+ if (body.wcs) {
+ this.writeNode("wcs:GetCoverage", body.wcs, node);
+ }
+ else if (body.wfs) {
+ // OpenLayers.Format.WFST expects these to be on the
+ // instance and not in the options
+ this.featureType = body.wfs.featureType;
+ this.version = body.wfs.version;
+ this.writeNode("wfs:GetFeature", body.wfs, node);
+ } else {
+ this.writeNode("wps:Execute", body, node);
+ }
+ return node;
+ }
+ },
+ "wcs": OpenLayers.Format.WCSGetCoverage.prototype.writers.wcs,
+ "wfs": OpenLayers.Format.WFST.v1_1_0.prototype.writers.wfs,
+ "ogc": OpenLayers.Format.Filter.v1_1_0.prototype.writers.ogc,
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.writers.ows
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "wps": {
+ "ExecuteResponse": function(node, obj) {
+ obj.executeResponse = {
+ lang: node.getAttribute("lang"),
+ statusLocation: node.getAttribute("statusLocation"),
+ serviceInstance: node.getAttribute("serviceInstance"),
+ service: node.getAttribute("service")
+ };
+ this.readChildNodes(node, obj.executeResponse);
+ },
+ "Process":function(node,obj) {
+ obj.process = {};
+ this.readChildNodes(node, obj.process);
+ },
+ "Status":function(node,obj) {
+ obj.status = {
+ creationTime: node.getAttribute("creationTime")
+ };
+ this.readChildNodes(node, obj.status);
+ },
+ "ProcessSucceeded": function(node,obj) {
+ obj.processSucceeded = true;
+ },
+ "ProcessOutputs": function(node, processDescription) {
+ processDescription.processOutputs = [];
+ this.readChildNodes(node, processDescription.processOutputs);
+ },
+ "Output": function(node, processOutputs) {
+ var output = {};
+ this.readChildNodes(node, output);
+ processOutputs.push(output);
+ },
+ "Reference": function(node, output) {
+ output.reference = {
+ href: node.getAttribute("href"),
+ mimeType: node.getAttribute("mimeType"),
+ encoding: node.getAttribute("encoding"),
+ schema: node.getAttribute("schema")
+ };
+ },
+ "Data": function(node, output) {
+ output.data = {};
+ this.readChildNodes(node, output);
+ },
+ "LiteralData": function(node, output) {
+ output.literalData = {
+ dataType: node.getAttribute("dataType"),
+ uom: node.getAttribute("uom"),
+ value: this.getChildValue(node)
+ };
+ },
+ "ComplexData": function(node, output) {
+ output.complexData = {
+ mimeType: node.getAttribute("mimeType"),
+ schema: node.getAttribute("schema"),
+ encoding: node.getAttribute("encoding"),
+ value: ""
+ };
+
+ // try to get *some* value, ignore the empty text values
+ if (this.isSimpleContent(node)) {
+ var child;
+ for(child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 3: // text node
+ case 4: // cdata section
+ output.complexData.value += child.nodeValue;
+ }
+ }
+ }
+ else {
+ for(child=node.firstChild; child; child=child.nextSibling) {
+ if (child.nodeType == 1) {
+ output.complexData.value = child;
+ }
+ }
+ }
+
+ },
+ "BoundingBox": function(node, output) {
+ output.boundingBoxData = {
+ dimensions: node.getAttribute("dimensions"),
+ crs: node.getAttribute("crs")
+ };
+ this.readChildNodes(node, output.boundingBoxData);
+ }
+ },
+
+ // TODO: we should add Exception parsing here
+ "ows": OpenLayers.Format.OWSCommon.v1_1_0.prototype.readers["ows"]
+ },
+
+ CLASS_NAME: "OpenLayers.Format.WPSExecute"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/XLS.js b/misc/openlayers/lib/OpenLayers/Format/XLS.js
new file mode 100644
index 0000000..76f3f10
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/XLS.js
@@ -0,0 +1,68 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML/VersionedOGC.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XLS
+ * Read/Write XLS (OpenLS). Create a new instance with the <OpenLayers.Format.XLS>
+ * constructor. Currently only implemented for Location Utility Services, more
+ * specifically only for Geocoding. No support for Reverse Geocoding as yet.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML.VersionedOGC>
+ */
+OpenLayers.Format.XLS = OpenLayers.Class(OpenLayers.Format.XML.VersionedOGC, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found. Default is "1.1.0".
+ */
+ defaultVersion: "1.1.0",
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is true.
+ */
+ stringifyOutput: true,
+
+ /**
+ * Constructor: OpenLayers.Format.XLS
+ * Create a new parser for XLS.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * APIMethod: write
+ * Write out an XLS request.
+ *
+ * Parameters:
+ * request - {Object} An object representing the LUS request.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} An XLS document string.
+ */
+
+ /**
+ * APIMethod: read
+ * Read an XLS doc and return an object representing the result.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the GeocodeResponse.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.XLS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/XLS/v1.js b/misc/openlayers/lib/OpenLayers/Format/XLS/v1.js
new file mode 100644
index 0000000..642474f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/XLS/v1.js
@@ -0,0 +1,304 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XLS.js
+ * @requires OpenLayers/Format/GML/v3.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XLS.v1
+ * Superclass for XLS version 1 parsers. Only supports GeocodeRequest for now.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.XLS.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs.
+ */
+ namespaces: {
+ xls: "http://www.opengis.net/xls",
+ gml: "http://www.opengis.net/gml",
+ xsi: "http://www.w3.org/2001/XMLSchema-instance"
+ },
+
+ /**
+ * Property: regExes
+ * Compiled regular expressions for manipulating strings.
+ */
+ regExes: {
+ trimSpace: (/^\s*|\s*$/g),
+ removeSpace: (/\s*/g),
+ splitSpace: (/\s+/),
+ trimComma: (/\s*,\s*/g)
+ },
+
+ /**
+ * APIProperty: xy
+ * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+ * Changing is not recommended, a new Format should be instantiated.
+ */
+ xy: true,
+
+ /**
+ * Property: defaultPrefix
+ */
+ defaultPrefix: "xls",
+
+ /**
+ * Property: schemaLocation
+ * {String} Schema location for a particular minor version.
+ */
+ schemaLocation: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XLS.v1
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.XLS> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ /**
+ * Method: read
+ *
+ * Parameters:
+ * data - {DOMElement} An XLS document element.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the XLSResponse.
+ */
+ read: function(data, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var xls = {};
+ this.readChildNodes(data, xls);
+ return xls;
+ },
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {
+ "xls": {
+ "XLS": function(node, xls) {
+ xls.version = node.getAttribute("version");
+ this.readChildNodes(node, xls);
+ },
+ "Response": function(node, xls) {
+ this.readChildNodes(node, xls);
+ },
+ "GeocodeResponse": function(node, xls) {
+ xls.responseLists = [];
+ this.readChildNodes(node, xls);
+ },
+ "GeocodeResponseList": function(node, xls) {
+ var responseList = {
+ features: [],
+ numberOfGeocodedAddresses:
+ parseInt(node.getAttribute("numberOfGeocodedAddresses"))
+ };
+ xls.responseLists.push(responseList);
+ this.readChildNodes(node, responseList);
+ },
+ "GeocodedAddress": function(node, responseList) {
+ var feature = new OpenLayers.Feature.Vector();
+ responseList.features.push(feature);
+ this.readChildNodes(node, feature);
+ // post-process geometry
+ feature.geometry = feature.components[0];
+ },
+ "GeocodeMatchCode": function(node, feature) {
+ feature.attributes.matchCode = {
+ accuracy: parseFloat(node.getAttribute("accuracy")),
+ matchType: node.getAttribute("matchType")
+ };
+ },
+ "Address": function(node, feature) {
+ var address = {
+ countryCode: node.getAttribute("countryCode"),
+ addressee: node.getAttribute("addressee"),
+ street: [],
+ place: []
+ };
+ feature.attributes.address = address;
+ this.readChildNodes(node, address);
+ },
+ "freeFormAddress": function(node, address) {
+ address.freeFormAddress = this.getChildValue(node);
+ },
+ "StreetAddress": function(node, address) {
+ this.readChildNodes(node, address);
+ },
+ "Building": function(node, address) {
+ address.building = {
+ 'number': node.getAttribute("number"),
+ subdivision: node.getAttribute("subdivision"),
+ buildingName: node.getAttribute("buildingName")
+ };
+ },
+ "Street": function(node, address) {
+ // only support the built-in primitive type for now
+ address.street.push(this.getChildValue(node));
+ },
+ "Place": function(node, address) {
+ // type is one of CountrySubdivision,
+ // CountrySecondarySubdivision, Municipality or
+ // MunicipalitySubdivision
+ address.place[node.getAttribute("type")] =
+ this.getChildValue(node);
+ },
+ "PostalCode": function(node, address) {
+ address.postalCode = this.getChildValue(node);
+ }
+ },
+ "gml": OpenLayers.Format.GML.v3.prototype.readers.gml
+ },
+
+ /**
+ * Method: write
+ *
+ * Parameters:
+ * request - {Object} An object representing the geocode request.
+ *
+ * Returns:
+ * {DOMElement} The root of an XLS document.
+ */
+ write: function(request) {
+ return this.writers.xls.XLS.apply(this, [request]);
+ },
+
+ /**
+ * Property: writers
+ * As a compliment to the readers property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {
+ "xls": {
+ "XLS": function(request) {
+ var root = this.createElementNSPlus(
+ "xls:XLS",
+ {attributes: {
+ "version": this.VERSION,
+ "xsi:schemaLocation": this.schemaLocation
+ }}
+ );
+ this.writeNode("RequestHeader", request.header, root);
+ this.writeNode("Request", request, root);
+ return root;
+ },
+ "RequestHeader": function(header) {
+ return this.createElementNSPlus("xls:RequestHeader");
+ },
+ "Request": function(request) {
+ var node = this.createElementNSPlus("xls:Request", {
+ attributes: {
+ methodName: "GeocodeRequest",
+ requestID: request.requestID || "",
+ version: this.VERSION
+ }
+ });
+ this.writeNode("GeocodeRequest", request.addresses, node);
+ return node;
+ },
+ "GeocodeRequest": function(addresses) {
+ var node = this.createElementNSPlus("xls:GeocodeRequest");
+ for (var i=0, len=addresses.length; i<len; i++) {
+ this.writeNode("Address", addresses[i], node);
+ }
+ return node;
+ },
+ "Address": function(address) {
+ var node = this.createElementNSPlus("xls:Address", {
+ attributes: {
+ countryCode: address.countryCode
+ }
+ });
+ if (address.freeFormAddress) {
+ this.writeNode("freeFormAddress", address.freeFormAddress, node);
+ } else {
+ if (address.street) {
+ this.writeNode("StreetAddress", address, node);
+ }
+ if (address.municipality) {
+ this.writeNode("Municipality", address.municipality, node);
+ }
+ if (address.countrySubdivision) {
+ this.writeNode("CountrySubdivision", address.countrySubdivision, node);
+ }
+ if (address.postalCode) {
+ this.writeNode("PostalCode", address.postalCode, node);
+ }
+ }
+ return node;
+ },
+ "freeFormAddress": function(freeFormAddress) {
+ return this.createElementNSPlus("freeFormAddress",
+ {value: freeFormAddress});
+ },
+ "StreetAddress": function(address) {
+ var node = this.createElementNSPlus("xls:StreetAddress");
+ if (address.building) {
+ this.writeNode(node, "Building", address.building);
+ }
+ var street = address.street;
+ if (!(OpenLayers.Util.isArray(street))) {
+ street = [street];
+ }
+ for (var i=0, len=street.length; i < len; i++) {
+ this.writeNode("Street", street[i], node);
+ }
+ return node;
+ },
+ "Building": function(building) {
+ return this.createElementNSPlus("xls:Building", {
+ attributes: {
+ "number": building["number"],
+ "subdivision": building.subdivision,
+ "buildingName": building.buildingName
+ }
+ });
+ },
+ "Street": function(street) {
+ return this.createElementNSPlus("xls:Street", {value: street});
+ },
+ "Municipality": function(municipality) {
+ return this.createElementNSPlus("xls:Place", {
+ attributes: {
+ type: "Municipality"
+ },
+ value: municipality
+ });
+ },
+ "CountrySubdivision": function(countrySubdivision) {
+ return this.createElementNSPlus("xls:Place", {
+ attributes: {
+ type: "CountrySubdivision"
+ },
+ value: countrySubdivision
+ });
+ },
+ "PostalCode": function(postalCode) {
+ return this.createElementNSPlus("xls:PostalCode", {
+ value: postalCode
+ });
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XLS.v1"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Format/XLS/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Format/XLS/v1_1_0.js
new file mode 100644
index 0000000..7ffca26
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/XLS/v1_1_0.js
@@ -0,0 +1,48 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XLS/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XLS.v1_1_0
+ * Read / write XLS version 1.1.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XLS.v1>
+ */
+OpenLayers.Format.XLS.v1_1_0 = OpenLayers.Class(
+ OpenLayers.Format.XLS.v1, {
+
+ /**
+ * Constant: VERSION
+ * {String} 1.1
+ */
+ VERSION: "1.1",
+
+ /**
+ * Property: schemaLocation
+ * {String} http://www.opengis.net/xls
+ * http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd
+ */
+ schemaLocation: "http://www.opengis.net/xls http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd",
+
+ /**
+ * Constructor: OpenLayers.Format.XLS.v1_1_0
+ * Instances of this class are not created directly. Use the
+ * <OpenLayers.Format.XLS> constructor instead.
+ *
+ * Parameters:
+ * options - {Object} An optional object whose properties will be set on
+ * this instance.
+ */
+
+ CLASS_NAME: "OpenLayers.Format.XLS.v1_1_0"
+
+});
+
+// Support non standard implementation
+OpenLayers.Format.XLS.v1_1 = OpenLayers.Format.XLS.v1_1_0;
diff --git a/misc/openlayers/lib/OpenLayers/Format/XML.js b/misc/openlayers/lib/OpenLayers/Format/XML.js
new file mode 100644
index 0000000..56f5871
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/XML.js
@@ -0,0 +1,897 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML
+ * Read and write XML. For cross-browser XML generation, use methods on an
+ * instance of the XML format class instead of on <code>document<end>.
+ * The DOM creation and traversing methods exposed here all mimic the
+ * W3C XML DOM methods. Create a new parser with the
+ * <OpenLayers.Format.XML> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Format>
+ */
+OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
+
+ /**
+ * Property: namespaces
+ * {Object} Mapping of namespace aliases to namespace URIs. Properties
+ * of this object should not be set individually. Read-only. All
+ * XML subclasses should have their own namespaces object. Use
+ * <setNamespace> to add or set a namespace alias after construction.
+ */
+ namespaces: null,
+
+ /**
+ * Property: namespaceAlias
+ * {Object} Mapping of namespace URI to namespace alias. This object
+ * is read-only. Use <setNamespace> to add or set a namespace alias.
+ */
+ namespaceAlias: null,
+
+ /**
+ * Property: defaultPrefix
+ * {String} The default namespace alias for creating element nodes.
+ */
+ defaultPrefix: null,
+
+ /**
+ * Property: readers
+ * Contains public functions, grouped by namespace prefix, that will
+ * be applied when a namespaced node is found matching the function
+ * name. The function will be applied in the scope of this parser
+ * with two arguments: the node being read and a context object passed
+ * from the parent.
+ */
+ readers: {},
+
+ /**
+ * Property: writers
+ * As a compliment to the <readers> property, this structure contains public
+ * writing functions grouped by namespace alias and named like the
+ * node names they produce.
+ */
+ writers: {},
+
+ /**
+ * Property: xmldom
+ * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
+ * object. It is not intended to be a browser sniffing property.
+ * Instead, the xmldom property is used instead of <code>document<end>
+ * where namespaced node creation methods are not supported. In all
+ * other browsers, this remains null.
+ */
+ xmldom: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XML
+ * Construct an XML parser. The parser is used to read and write XML.
+ * Reading XML from a string returns a DOM element. Writing XML from
+ * a DOM element returns a string.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on
+ * the object.
+ */
+ initialize: function(options) {
+ if(window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ OpenLayers.Format.prototype.initialize.apply(this, [options]);
+ // clone the namespace object and set all namespace aliases
+ this.namespaces = OpenLayers.Util.extend({}, this.namespaces);
+ this.namespaceAlias = {};
+ for(var alias in this.namespaces) {
+ this.namespaceAlias[this.namespaces[alias]] = alias;
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ this.xmldom = null;
+ OpenLayers.Format.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setNamespace
+ * Set a namespace alias and URI for the format.
+ *
+ * Parameters:
+ * alias - {String} The namespace alias (prefix).
+ * uri - {String} The namespace URI.
+ */
+ setNamespace: function(alias, uri) {
+ this.namespaces[alias] = uri;
+ this.namespaceAlias[uri] = alias;
+ },
+
+ /**
+ * APIMethod: read
+ * Deserialize a XML string and return a DOM node.
+ *
+ * Parameters:
+ * text - {String} A XML string
+
+ * Returns:
+ * {DOMElement} A DOM node
+ */
+ read: function(text) {
+ var index = text.indexOf('<');
+ if(index > 0) {
+ text = text.substring(index);
+ }
+ var node = OpenLayers.Util.Try(
+ OpenLayers.Function.bind((
+ function() {
+ var xmldom;
+ /**
+ * Since we want to be able to call this method on the prototype
+ * itself, this.xmldom may not exist even if in IE.
+ */
+ if(window.ActiveXObject && !this.xmldom) {
+ xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ } else {
+ xmldom = this.xmldom;
+
+ }
+ xmldom.loadXML(text);
+ return xmldom;
+ }
+ ), this),
+ function() {
+ return new DOMParser().parseFromString(text, 'text/xml');
+ },
+ function() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "data:" + "text/xml" +
+ ";charset=utf-8," + encodeURIComponent(text), false);
+ if(req.overrideMimeType) {
+ req.overrideMimeType("text/xml");
+ }
+ req.send(null);
+ return req.responseXML;
+ }
+ );
+
+ if(this.keepData) {
+ this.data = node;
+ }
+
+ return node;
+ },
+
+ /**
+ * APIMethod: write
+ * Serialize a DOM node into a XML string.
+ *
+ * Parameters:
+ * node - {DOMElement} A DOM node.
+ *
+ * Returns:
+ * {String} The XML string representation of the input node.
+ */
+ write: function(node) {
+ var data;
+ if(this.xmldom) {
+ data = node.xml;
+ } else {
+ var serializer = new XMLSerializer();
+ if (node.nodeType == 1) {
+ // Add nodes to a document before serializing. Everything else
+ // is serialized as is. This may need more work. See #1218 .
+ var doc = document.implementation.createDocument("", "", null);
+ if (doc.importNode) {
+ node = doc.importNode(node, true);
+ }
+ doc.appendChild(node);
+ data = serializer.serializeToString(doc);
+ } else {
+ data = serializer.serializeToString(node);
+ }
+ }
+ return data;
+ },
+
+ /**
+ * APIMethod: createElementNS
+ * Create a new element with namespace. This node can be appended to
+ * another node with the standard node.appendChild method. For
+ * cross-browser support, this method must be used instead of
+ * document.createElementNS.
+ *
+ * Parameters:
+ * uri - {String} Namespace URI for the element.
+ * name - {String} The qualified name of the element (prefix:localname).
+ *
+ * Returns:
+ * {Element} A DOM element with namespace.
+ */
+ createElementNS: function(uri, name) {
+ var element;
+ if(this.xmldom) {
+ if(typeof uri == "string") {
+ element = this.xmldom.createNode(1, name, uri);
+ } else {
+ element = this.xmldom.createNode(1, name, "");
+ }
+ } else {
+ element = document.createElementNS(uri, name);
+ }
+ return element;
+ },
+
+ /**
+ * APIMethod: createDocumentFragment
+ * Create a document fragment node that can be appended to another node
+ * created by createElementNS. This will call
+ * document.createDocumentFragment outside of IE. In IE, the ActiveX
+ * object's createDocumentFragment method is used.
+ *
+ * Returns:
+ * {Element} A document fragment.
+ */
+ createDocumentFragment: function() {
+ var element;
+ if (this.xmldom) {
+ element = this.xmldom.createDocumentFragment();
+ } else {
+ element = document.createDocumentFragment();
+ }
+ return element;
+ },
+
+ /**
+ * APIMethod: createTextNode
+ * Create a text node. This node can be appended to another node with
+ * the standard node.appendChild method. For cross-browser support,
+ * this method must be used instead of document.createTextNode.
+ *
+ * Parameters:
+ * text - {String} The text of the node.
+ *
+ * Returns:
+ * {DOMElement} A DOM text node.
+ */
+ createTextNode: function(text) {
+ var node;
+ if (typeof text !== "string") {
+ text = String(text);
+ }
+ if(this.xmldom) {
+ node = this.xmldom.createTextNode(text);
+ } else {
+ node = document.createTextNode(text);
+ }
+ return node;
+ },
+
+ /**
+ * APIMethod: getElementsByTagNameNS
+ * Get a list of elements on a node given the namespace URI and local name.
+ * To return all nodes in a given namespace, use '*' for the name
+ * argument. To return all nodes of a given (local) name, regardless
+ * of namespace, use '*' for the uri argument.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for other nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the tag (without the prefix).
+ *
+ * Returns:
+ * {NodeList} A node list or array of elements.
+ */
+ getElementsByTagNameNS: function(node, uri, name) {
+ var elements = [];
+ if(node.getElementsByTagNameNS) {
+ elements = node.getElementsByTagNameNS(uri, name);
+ } else {
+ // brute force method
+ var allNodes = node.getElementsByTagName("*");
+ var potentialNode, fullName;
+ for(var i=0, len=allNodes.length; i<len; ++i) {
+ potentialNode = allNodes[i];
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if((name == "*") || (fullName == potentialNode.nodeName)) {
+ if((uri == "*") || (uri == potentialNode.namespaceURI)) {
+ elements.push(potentialNode);
+ }
+ }
+ }
+ }
+ return elements;
+ },
+
+ /**
+ * APIMethod: getAttributeNodeNS
+ * Get an attribute node given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for attribute nodes.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {DOMElement} An attribute node or null if none found.
+ */
+ getAttributeNodeNS: function(node, uri, name) {
+ var attributeNode = null;
+ if(node.getAttributeNodeNS) {
+ attributeNode = node.getAttributeNodeNS(uri, name);
+ } else {
+ var attributes = node.attributes;
+ var potentialNode, fullName;
+ for(var i=0, len=attributes.length; i<len; ++i) {
+ potentialNode = attributes[i];
+ if(potentialNode.namespaceURI == uri) {
+ fullName = (potentialNode.prefix) ?
+ (potentialNode.prefix + ":" + name) : name;
+ if(fullName == potentialNode.nodeName) {
+ attributeNode = potentialNode;
+ break;
+ }
+ }
+ }
+ }
+ return attributeNode;
+ },
+
+ /**
+ * APIMethod: getAttributeNS
+ * Get an attribute value given the namespace URI and local name.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {String} An attribute value or and empty string if none found.
+ */
+ getAttributeNS: function(node, uri, name) {
+ var attributeValue = "";
+ if(node.getAttributeNS) {
+ attributeValue = node.getAttributeNS(uri, name) || "";
+ } else {
+ var attributeNode = this.getAttributeNodeNS(node, uri, name);
+ if(attributeNode) {
+ attributeValue = attributeNode.nodeValue;
+ }
+ }
+ return attributeValue;
+ },
+
+ /**
+ * APIMethod: getChildValue
+ * Get the textual value of the node if it exists, or return an
+ * optional default string. Returns an empty string if no first child
+ * exists and no default value is supplied.
+ *
+ * Parameters:
+ * node - {DOMElement} The element used to look for a first child value.
+ * def - {String} Optional string to return in the event that no
+ * first child value exists.
+ *
+ * Returns:
+ * {String} The value of the first child of the given node.
+ */
+ getChildValue: function(node, def) {
+ var value = def || "";
+ if(node) {
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 3: // text node
+ case 4: // cdata section
+ value += child.nodeValue;
+ }
+ }
+ }
+ return value;
+ },
+
+ /**
+ * APIMethod: isSimpleContent
+ * Test if the given node has only simple content (i.e. no child element
+ * nodes).
+ *
+ * Parameters:
+ * node - {DOMElement} An element node.
+ *
+ * Returns:
+ * {Boolean} The node has no child element nodes (nodes of type 1).
+ */
+ isSimpleContent: function(node) {
+ var simple = true;
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ if(child.nodeType === 1) {
+ simple = false;
+ break;
+ }
+ }
+ return simple;
+ },
+
+ /**
+ * APIMethod: contentType
+ * Determine the content type for a given node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Integer} One of OpenLayers.Format.XML.CONTENT_TYPE.{EMPTY,SIMPLE,COMPLEX,MIXED}
+ * if the node has no, simple, complex, or mixed content.
+ */
+ contentType: function(node) {
+ var simple = false,
+ complex = false;
+
+ var type = OpenLayers.Format.XML.CONTENT_TYPE.EMPTY;
+
+ for(var child=node.firstChild; child; child=child.nextSibling) {
+ switch(child.nodeType) {
+ case 1: // element
+ complex = true;
+ break;
+ case 8: // comment
+ break;
+ default:
+ simple = true;
+ }
+ if(complex && simple) {
+ break;
+ }
+ }
+
+ if(complex && simple) {
+ type = OpenLayers.Format.XML.CONTENT_TYPE.MIXED;
+ } else if(complex) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.COMPLEX;
+ } else if(simple) {
+ return OpenLayers.Format.XML.CONTENT_TYPE.SIMPLE;
+ }
+ return type;
+ },
+
+ /**
+ * APIMethod: hasAttributeNS
+ * Determine whether a node has a particular attribute matching the given
+ * name and namespace.
+ *
+ * Parameters:
+ * node - {Element} Node on which to search for an attribute.
+ * uri - {String} Namespace URI.
+ * name - {String} Local name of the attribute (without the prefix).
+ *
+ * Returns:
+ * {Boolean} The node has an attribute matching the name and namespace.
+ */
+ hasAttributeNS: function(node, uri, name) {
+ var found = false;
+ if(node.hasAttributeNS) {
+ found = node.hasAttributeNS(uri, name);
+ } else {
+ found = !!this.getAttributeNodeNS(node, uri, name);
+ }
+ return found;
+ },
+
+ /**
+ * APIMethod: setAttributeNS
+ * Adds a new attribute or changes the value of an attribute with the given
+ * namespace and name.
+ *
+ * Parameters:
+ * node - {Element} Element node on which to set the attribute.
+ * uri - {String} Namespace URI for the attribute.
+ * name - {String} Qualified name (prefix:localname) for the attribute.
+ * value - {String} Attribute value.
+ */
+ setAttributeNS: function(node, uri, name, value) {
+ if(node.setAttributeNS) {
+ node.setAttributeNS(uri, name, value);
+ } else {
+ if(this.xmldom) {
+ if(uri) {
+ var attribute = node.ownerDocument.createNode(
+ 2, name, uri
+ );
+ attribute.nodeValue = value;
+ node.setAttributeNode(attribute);
+ } else {
+ node.setAttribute(name, value);
+ }
+ } else {
+ throw "setAttributeNS not implemented";
+ }
+ }
+ },
+
+ /**
+ * Method: createElementNSPlus
+ * Shorthand for creating namespaced elements with optional attributes and
+ * child text nodes.
+ *
+ * Parameters:
+ * name - {String} The qualified node name.
+ * options - {Object} Optional object for node configuration.
+ *
+ * Valid options:
+ * uri - {String} Optional namespace uri for the element - supply a prefix
+ * instead if the namespace uri is a property of the format's namespace
+ * object.
+ * attributes - {Object} Optional attributes to be set using the
+ * <setAttributes> method.
+ * value - {String} Optional text to be appended as a text node.
+ *
+ * Returns:
+ * {Element} An element node.
+ */
+ createElementNSPlus: function(name, options) {
+ options = options || {};
+ // order of prefix preference
+ // 1. in the uri option
+ // 2. in the prefix option
+ // 3. in the qualified name
+ // 4. from the defaultPrefix
+ var uri = options.uri || this.namespaces[options.prefix];
+ if(!uri) {
+ var loc = name.indexOf(":");
+ uri = this.namespaces[name.substring(0, loc)];
+ }
+ if(!uri) {
+ uri = this.namespaces[this.defaultPrefix];
+ }
+ var node = this.createElementNS(uri, name);
+ if(options.attributes) {
+ this.setAttributes(node, options.attributes);
+ }
+ var value = options.value;
+ if(value != null) {
+ node.appendChild(this.createTextNode(value));
+ }
+ return node;
+ },
+
+ /**
+ * Method: setAttributes
+ * Set multiple attributes given key value pairs from an object.
+ *
+ * Parameters:
+ * node - {Element} An element node.
+ * obj - {Object || Array} An object whose properties represent attribute
+ * names and values represent attribute values. If an attribute name
+ * is a qualified name ("prefix:local"), the prefix will be looked up
+ * in the parsers {namespaces} object. If the prefix is found,
+ * setAttributeNS will be used instead of setAttribute.
+ */
+ setAttributes: function(node, obj) {
+ var value, uri;
+ for(var name in obj) {
+ if(obj[name] != null && obj[name].toString) {
+ value = obj[name].toString();
+ // check for qualified attribute name ("prefix:local")
+ uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+ this.setAttributeNS(node, uri, name, value);
+ }
+ }
+ },
+
+ /**
+ * Method: readNode
+ * Shorthand for applying one of the named readers given the node
+ * namespace and local name. Readers take two args (node, obj) and
+ * generally extend or modify the second.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ *
+ * Returns:
+ * {Object} The input object, modified (or a new one if none was provided).
+ */
+ readNode: function(node, obj) {
+ if(!obj) {
+ obj = {};
+ }
+ var group = this.readers[node.namespaceURI ? this.namespaceAlias[node.namespaceURI]: this.defaultPrefix];
+ if(group) {
+ var local = node.localName || node.nodeName.split(":").pop();
+ var reader = group[local] || group["*"];
+ if(reader) {
+ reader.apply(this, [node, obj]);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: readChildNodes
+ * Shorthand for applying the named readers to all children of a node.
+ * For each child of type 1 (element), <readSelf> is called.
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be read (required).
+ * obj - {Object} The object to be modified (optional).
+ *
+ * Returns:
+ * {Object} The input object, modified.
+ */
+ readChildNodes: function(node, obj) {
+ if(!obj) {
+ obj = {};
+ }
+ var children = node.childNodes;
+ var child;
+ for(var i=0, len=children.length; i<len; ++i) {
+ child = children[i];
+ if(child.nodeType == 1) {
+ this.readNode(child, obj);
+ }
+ }
+ return obj;
+ },
+
+ /**
+ * Method: writeNode
+ * Shorthand for applying one of the named writers and appending the
+ * results to a node. If a qualified name is not provided for the
+ * second argument (and a local name is used instead), the namespace
+ * of the parent node will be assumed.
+ *
+ * Parameters:
+ * name - {String} The name of a node to generate. If a qualified name
+ * (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+ * in the <writers> group. If a local name is used (e.g. "Name") then
+ * the namespace of the parent is assumed. If a local name is used
+ * and no parent is supplied, then the default namespace is assumed.
+ * obj - {Object} Structure containing data for the writer.
+ * parent - {DOMElement} Result will be appended to this node. If no parent
+ * is supplied, the node will not be appended to anything.
+ *
+ * Returns:
+ * {DOMElement} The child node.
+ */
+ writeNode: function(name, obj, parent) {
+ var prefix, local;
+ var split = name.indexOf(":");
+ if(split > 0) {
+ prefix = name.substring(0, split);
+ local = name.substring(split + 1);
+ } else {
+ if(parent) {
+ prefix = this.namespaceAlias[parent.namespaceURI];
+ } else {
+ prefix = this.defaultPrefix;
+ }
+ local = name;
+ }
+ var child = this.writers[prefix][local].apply(this, [obj]);
+ if(parent) {
+ parent.appendChild(child);
+ }
+ return child;
+ },
+
+ /**
+ * APIMethod: getChildEl
+ * Get the first child element. Optionally only return the first child
+ * if it matches the given name and namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The parent node.
+ * name - {String} Optional node name (local) to search for.
+ * uri - {String} Optional namespace URI to search for.
+ *
+ * Returns:
+ * {DOMElement} The first child. Returns null if no element is found, if
+ * something significant besides an element is found, or if the element
+ * found does not match the optional name and uri.
+ */
+ getChildEl: function(node, name, uri) {
+ return node && this.getThisOrNextEl(node.firstChild, name, uri);
+ },
+
+ /**
+ * APIMethod: getNextEl
+ * Get the next sibling element. Optionally get the first sibling only
+ * if it matches the given local name and namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The node.
+ * name - {String} Optional local name of the sibling to search for.
+ * uri - {String} Optional namespace URI of the sibling to search for.
+ *
+ * Returns:
+ * {DOMElement} The next sibling element. Returns null if no element is
+ * found, something significant besides an element is found, or the
+ * found element does not match the optional name and uri.
+ */
+ getNextEl: function(node, name, uri) {
+ return node && this.getThisOrNextEl(node.nextSibling, name, uri);
+ },
+
+ /**
+ * Method: getThisOrNextEl
+ * Return this node or the next element node. Optionally get the first
+ * sibling with the given local name or namespace URI.
+ *
+ * Parameters:
+ * node - {DOMElement} The node.
+ * name - {String} Optional local name of the sibling to search for.
+ * uri - {String} Optional namespace URI of the sibling to search for.
+ *
+ * Returns:
+ * {DOMElement} The next sibling element. Returns null if no element is
+ * found, something significant besides an element is found, or the
+ * found element does not match the query.
+ */
+ getThisOrNextEl: function(node, name, uri) {
+ outer: for(var sibling=node; sibling; sibling=sibling.nextSibling) {
+ switch(sibling.nodeType) {
+ case 1: // Element
+ if((!name || name === (sibling.localName || sibling.nodeName.split(":").pop())) &&
+ (!uri || uri === sibling.namespaceURI)) {
+ // matches
+ break outer;
+ }
+ sibling = null;
+ break outer;
+ case 3: // Text
+ if(/^\s*$/.test(sibling.nodeValue)) {
+ break;
+ }
+ case 4: // CDATA
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ sibling = null;
+ break outer;
+ } // ignore comments and processing instructions
+ }
+ return sibling || null;
+ },
+
+ /**
+ * APIMethod: lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+ lookupNamespaceURI: function(node, prefix) {
+ var uri = null;
+ if(node) {
+ if(node.lookupNamespaceURI) {
+ uri = node.lookupNamespaceURI(prefix);
+ } else {
+ outer: switch(node.nodeType) {
+ case 1: // ELEMENT_NODE
+ if(node.namespaceURI !== null && node.prefix === prefix) {
+ uri = node.namespaceURI;
+ break outer;
+ }
+ var len = node.attributes.length;
+ if(len) {
+ var attr;
+ for(var i=0; i<len; ++i) {
+ attr = node.attributes[i];
+ if(attr.prefix === "xmlns" && attr.name === "xmlns:" + prefix) {
+ uri = attr.value || null;
+ break outer;
+ } else if(attr.name === "xmlns" && prefix === null) {
+ uri = attr.value || null;
+ break outer;
+ }
+ }
+ }
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ case 2: // ATTRIBUTE_NODE
+ uri = this.lookupNamespaceURI(node.ownerElement, prefix);
+ break outer;
+ case 9: // DOCUMENT_NODE
+ uri = this.lookupNamespaceURI(node.documentElement, prefix);
+ break outer;
+ case 6: // ENTITY_NODE
+ case 12: // NOTATION_NODE
+ case 10: // DOCUMENT_TYPE_NODE
+ case 11: // DOCUMENT_FRAGMENT_NODE
+ break outer;
+ default:
+ // TEXT_NODE (3), CDATA_SECTION_NODE (4), ENTITY_REFERENCE_NODE (5),
+ // PROCESSING_INSTRUCTION_NODE (7), COMMENT_NODE (8)
+ uri = this.lookupNamespaceURI(node.parentNode, prefix);
+ break outer;
+ }
+ }
+ }
+ return uri;
+ },
+
+ /**
+ * Method: getXMLDoc
+ * Get an XML document for nodes that are not supported in HTML (e.g.
+ * createCDATASection). On IE, this will either return an existing or
+ * create a new <xmldom> on the instance. On other browsers, this will
+ * either return an existing or create a new shared document (see
+ * <OpenLayers.Format.XML.document>).
+ *
+ * Returns:
+ * {XMLDocument}
+ */
+ getXMLDoc: function() {
+ if (!OpenLayers.Format.XML.document && !this.xmldom) {
+ if (document.implementation && document.implementation.createDocument) {
+ OpenLayers.Format.XML.document =
+ document.implementation.createDocument("", "", null);
+ } else if (!this.xmldom && window.ActiveXObject) {
+ this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+ }
+ }
+ return OpenLayers.Format.XML.document || this.xmldom;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML"
+
+});
+
+OpenLayers.Format.XML.CONTENT_TYPE = {EMPTY: 0, SIMPLE: 1, COMPLEX: 2, MIXED: 3};
+
+/**
+ * APIFunction: OpenLayers.Format.XML.lookupNamespaceURI
+ * Takes a prefix and returns the namespace URI associated with it on the given
+ * node if found (and null if not). Supplying null for the prefix will
+ * return the default namespace.
+ *
+ * For browsers that support it, this calls the native lookupNamesapceURI
+ * function. In other browsers, this is an implementation of
+ * http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-lookupNamespaceURI.
+ *
+ * For browsers that don't support the attribute.ownerElement property, this
+ * method cannot be called on attribute nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The node from which to start looking.
+ * prefix - {String} The prefix to lookup or null to lookup the default namespace.
+ *
+ * Returns:
+ * {String} The namespace URI for the given prefix. Returns null if the prefix
+ * cannot be found or the node is the wrong type.
+ */
+OpenLayers.Format.XML.lookupNamespaceURI = OpenLayers.Function.bind(
+ OpenLayers.Format.XML.prototype.lookupNamespaceURI,
+ OpenLayers.Format.XML.prototype
+);
+
+/**
+ * Property: OpenLayers.Format.XML.document
+ * {XMLDocument} XML document to reuse for creating non-HTML compliant nodes,
+ * like document.createCDATASection.
+ */
+OpenLayers.Format.XML.document = null;
diff --git a/misc/openlayers/lib/OpenLayers/Format/XML/VersionedOGC.js b/misc/openlayers/lib/OpenLayers/Format/XML/VersionedOGC.js
new file mode 100644
index 0000000..e68d968
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Format/XML/VersionedOGC.js
@@ -0,0 +1,212 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Format/OGCExceptionReport.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML.VersionedOGC
+ * Base class for versioned formats, i.e. a format which supports multiple
+ * versions.
+ *
+ * To enable checking if parsing succeeded, you will need to define a property
+ * called errorProperty on the parser you want to check. The parser will then
+ * check the returned object to see if that property is present. If it is, it
+ * assumes the parsing was successful. If it is not present (or is null), it will
+ * pass the document through an OGCExceptionReport parser.
+ *
+ * If errorProperty is undefined for the parser, this error checking mechanism
+ * will be disabled.
+ *
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.XML.VersionedOGC = OpenLayers.Class(OpenLayers.Format.XML, {
+
+ /**
+ * APIProperty: defaultVersion
+ * {String} Version number to assume if none found.
+ */
+ defaultVersion: null,
+
+ /**
+ * APIProperty: version
+ * {String} Specify a version string if one is known.
+ */
+ version: null,
+
+ /**
+ * APIProperty: profile
+ * {String} If provided, use a custom profile.
+ */
+ profile: null,
+
+ /**
+ * APIProperty: allowFallback
+ * {Boolean} If a profiled parser cannot be found for the returned version,
+ * use a non-profiled parser as the fallback. Application code using this
+ * should take into account that the return object structure might be
+ * missing the specifics of the profile. Defaults to false.
+ */
+ allowFallback: false,
+
+ /**
+ * Property: name
+ * {String} The name of this parser, this is the part of the CLASS_NAME
+ * except for "OpenLayers.Format."
+ */
+ name: null,
+
+ /**
+ * APIProperty: stringifyOutput
+ * {Boolean} If true, write will return a string otherwise a DOMElement.
+ * Default is false.
+ */
+ stringifyOutput: false,
+
+ /**
+ * Property: parser
+ * {Object} Instance of the versioned parser. Cached for multiple read and
+ * write calls of the same version.
+ */
+ parser: null,
+
+ /**
+ * Constructor: OpenLayers.Format.XML.VersionedOGC.
+ * Constructor.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on
+ * the object.
+ */
+ initialize: function(options) {
+ OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+ var className = this.CLASS_NAME;
+ this.name = className.substring(className.lastIndexOf(".")+1);
+ },
+
+ /**
+ * Method: getVersion
+ * Returns the version to use. Subclasses can override this function
+ * if a different version detection is needed.
+ *
+ * Parameters:
+ * root - {DOMElement}
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The version to use.
+ */
+ getVersion: function(root, options) {
+ var version;
+ // read
+ if (root) {
+ version = this.version;
+ if(!version) {
+ version = root.getAttribute("version");
+ if(!version) {
+ version = this.defaultVersion;
+ }
+ }
+ } else { // write
+ version = (options && options.version) ||
+ this.version || this.defaultVersion;
+ }
+ return version;
+ },
+
+ /**
+ * Method: getParser
+ * Get an instance of the cached parser if available, otherwise create one.
+ *
+ * Parameters:
+ * version - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Format>}
+ */
+ getParser: function(version) {
+ version = version || this.defaultVersion;
+ var profile = this.profile ? "_" + this.profile : "";
+ if(!this.parser || this.parser.VERSION != version) {
+ var format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_") + profile
+ ];
+ if(!format) {
+ if (profile !== "" && this.allowFallback) {
+ // fallback to the non-profiled version of the parser
+ profile = "";
+ format = OpenLayers.Format[this.name][
+ "v" + version.replace(/\./g, "_")
+ ];
+ }
+ if (!format) {
+ throw "Can't find a " + this.name + " parser for version " +
+ version + profile;
+ }
+ }
+ this.parser = new format(this.options);
+ }
+ return this.parser;
+ },
+
+ /**
+ * APIMethod: write
+ * Write a document.
+ *
+ * Parameters:
+ * obj - {Object} An object representing the document.
+ * options - {Object} Optional configuration object.
+ *
+ * Returns:
+ * {String} The document as a string
+ */
+ write: function(obj, options) {
+ var version = this.getVersion(null, options);
+ this.parser = this.getParser(version);
+ var root = this.parser.write(obj, options);
+ if (this.stringifyOutput === false) {
+ return root;
+ } else {
+ return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+ }
+ },
+
+ /**
+ * APIMethod: read
+ * Read a doc and return an object representing the document.
+ *
+ * Parameters:
+ * data - {String | DOMElement} Data to read.
+ * options - {Object} Options for the reader.
+ *
+ * Returns:
+ * {Object} An object representing the document.
+ */
+ read: function(data, options) {
+ if(typeof data == "string") {
+ data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+ }
+ var root = data.documentElement;
+ var version = this.getVersion(root);
+ this.parser = this.getParser(version); // Select the parser
+ var obj = this.parser.read(data, options); // Parse the data
+
+ var errorProperty = this.parser.errorProperty || null;
+ if (errorProperty !== null && obj[errorProperty] === undefined) {
+ // an error must have happened, so parse it and report back
+ var format = new OpenLayers.Format.OGCExceptionReport();
+ obj.error = format.read(data);
+ }
+ obj.version = version;
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Format.XML.VersionedOGC"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry.js b/misc/openlayers/lib/OpenLayers/Geometry.js
new file mode 100644
index 0000000..e7b8e59
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry.js
@@ -0,0 +1,500 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object. Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor. This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ *
+ * Note that if you use the <OpenLayers.Geometry.fromWKT> method, you must
+ * explicitly include the OpenLayers.Format.WKT in your build.
+ */
+OpenLayers.Geometry = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique identifier for this geometry.
+ */
+ id: null,
+
+ /**
+ * Property: parent
+ * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+ * of another geometry
+ */
+ parent: null,
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The bounds of this geometry
+ */
+ bounds: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry
+ * Creates a geometry object.
+ */
+ initialize: function() {
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
+ },
+
+ /**
+ * Method: destroy
+ * Destroy this geometry.
+ */
+ destroy: function() {
+ this.id = null;
+ this.bounds = null;
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this geometry. Does not set any non-standard
+ * properties of the cloned geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} An exact clone of this geometry.
+ */
+ clone: function() {
+ return new OpenLayers.Geometry();
+ },
+
+ /**
+ * Method: setBounds
+ * Set the bounds for this Geometry.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ if (bounds) {
+ this.bounds = bounds.clone();
+ }
+ },
+
+ /**
+ * Method: clearBounds
+ * Nullify this components bounds and that of its parent as well.
+ */
+ clearBounds: function() {
+ this.bounds = null;
+ if (this.parent) {
+ this.parent.clearBounds();
+ }
+ },
+
+ /**
+ * Method: extendBounds
+ * Extend the existing bounds to include the new bounds.
+ * If geometry's bounds is not yet set, then set a new Bounds.
+ *
+ * Parameters:
+ * newBounds - {<OpenLayers.Bounds>}
+ */
+ extendBounds: function(newBounds){
+ var bounds = this.getBounds();
+ if (!bounds) {
+ this.setBounds(newBounds);
+ } else {
+ this.bounds.extend(newBounds);
+ }
+ },
+
+ /**
+ * APIMethod: getBounds
+ * Get the bounds for this Geometry. If bounds is not set, it
+ * is calculated again, this makes queries faster.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getBounds: function() {
+ if (this.bounds == null) {
+ this.calculateBounds();
+ }
+ return this.bounds;
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds for the geometry.
+ */
+ calculateBounds: function() {
+ //
+ // This should be overridden by subclasses.
+ //
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options depend on the specific geometry type.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ },
+
+ /**
+ * Method: atPoint
+ * Note - This is only an approximation based on the bounds of the
+ * geometry.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * toleranceLon - {float} Optional tolerance in Geometric Coords
+ * toleranceLat - {float} Optional tolerance in Geographic Coords
+ *
+ * Returns:
+ * {Boolean} Whether or not the geometry is at the specified location
+ */
+ atPoint: function(lonlat, toleranceLon, toleranceLat) {
+ var atPoint = false;
+ var bounds = this.getBounds();
+ if ((bounds != null) && (lonlat != null)) {
+
+ var dX = (toleranceLon != null) ? toleranceLon : 0;
+ var dY = (toleranceLat != null) ? toleranceLat : 0;
+
+ var toleranceBounds =
+ new OpenLayers.Bounds(this.bounds.left - dX,
+ this.bounds.bottom - dY,
+ this.bounds.right + dX,
+ this.bounds.top + dY);
+
+ atPoint = toleranceBounds.containsLonLat(lonlat);
+ }
+ return atPoint;
+ },
+
+ /**
+ * Method: getLength
+ * Calculate the length of this geometry. This method is defined in
+ * subclasses.
+ *
+ * Returns:
+ * {Float} The length of the collection by summing its parts
+ */
+ getLength: function() {
+ //to be overridden by geometries that actually have a length
+ //
+ return 0.0;
+ },
+
+ /**
+ * Method: getArea
+ * Calculate the area of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ //to be overridden by geometries that actually have an area
+ //
+ return 0.0;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ * Calculate the centroid of this geometry. This method is defined in subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return null;
+ },
+
+ /**
+ * Method: toString
+ * Returns a text representation of the geometry. If the WKT format is
+ * included in a build, this will be the Well-Known Text
+ * representation.
+ *
+ * Returns:
+ * {String} String representation of this geometry.
+ */
+ toString: function() {
+ var string;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ string = OpenLayers.Format.WKT.prototype.write(
+ new OpenLayers.Feature.Vector(this)
+ );
+ } else {
+ string = Object.prototype.toString.call(this);
+ }
+ return string;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry"
+});
+
+/**
+ * Function: OpenLayers.Geometry.fromWKT
+ * Generate a geometry given a Well-Known Text string. For this method to
+ * work, you must include the OpenLayers.Format.WKT in your build
+ * explicitly.
+ *
+ * Parameters:
+ * wkt - {String} A string representing the geometry in Well-Known Text.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} A geometry of the appropriate class.
+ */
+OpenLayers.Geometry.fromWKT = function(wkt) {
+ var geom;
+ if (OpenLayers.Format && OpenLayers.Format.WKT) {
+ var format = OpenLayers.Geometry.fromWKT.format;
+ if (!format) {
+ format = new OpenLayers.Format.WKT();
+ OpenLayers.Geometry.fromWKT.format = format;
+ }
+ var result = format.read(wkt);
+ if (result instanceof OpenLayers.Feature.Vector) {
+ geom = result.geometry;
+ } else if (OpenLayers.Util.isArray(result)) {
+ var len = result.length;
+ var components = new Array(len);
+ for (var i=0; i<len; ++i) {
+ components[i] = result[i].geometry;
+ }
+ geom = new OpenLayers.Geometry.Collection(components);
+ }
+ }
+ return geom;
+};
+
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect. Optionally calculates
+ * and returns the intersection point. This function is optimized for
+ * cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1. In those
+ * obvious cases where there is no intersection, the function should
+ * not be called.
+ *
+ * Parameters:
+ * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
+ * and y2. The start point is represented by x1 and y1. The end point
+ * is represented by x2 and y2. Start and end are ordered so that x1 < x2.
+ * options - {Object} Optional properties for calculating the intersection.
+ *
+ * Valid options:
+ * point - {Boolean} Return the intersection point. If false, the actual
+ * intersection point will not be calculated. If true and the segments
+ * intersect, the intersection point will be returned. If true and
+ * the segments do not intersect, false will be returned. If true and
+ * the segments are coincident, true will be returned.
+ * tolerance - {Number} If a non-null value is provided, if the segments are
+ * within the tolerance distance, this will be considered an intersection.
+ * In addition, if the point option is true and the calculated intersection
+ * is within the tolerance distance of an end point, the endpoint will be
+ * returned instead of the calculated intersection. Further, if the
+ * intersection is within the tolerance of endpoints on both segments, or
+ * if two segment endpoints are within the tolerance distance of eachother
+ * (but no intersection is otherwise calculated), an endpoint on the
+ * first segment provided will be returned.
+ *
+ * Returns:
+ * {Boolean | <OpenLayers.Geometry.Point>} The two segments intersect.
+ * If the point argument is true, the return will be the intersection
+ * point or false if none exists. If point is true and the segments
+ * are coincident, return will be true (and the instersection is equal
+ * to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) {
+ var point = options && options.point;
+ var tolerance = options && options.tolerance;
+ var intersection = false;
+ var x11_21 = seg1.x1 - seg2.x1;
+ var y11_21 = seg1.y1 - seg2.y1;
+ var x12_11 = seg1.x2 - seg1.x1;
+ var y12_11 = seg1.y2 - seg1.y1;
+ var y22_21 = seg2.y2 - seg2.y1;
+ var x22_21 = seg2.x2 - seg2.x1;
+ var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+ var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+ var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+ if(d == 0) {
+ // parallel
+ if(n1 == 0 && n2 == 0) {
+ // coincident
+ intersection = true;
+ }
+ } else {
+ var along1 = n1 / d;
+ var along2 = n2 / d;
+ if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+ // intersect
+ if(!point) {
+ intersection = true;
+ } else {
+ // calculate the intersection point
+ var x = seg1.x1 + (along1 * x12_11);
+ var y = seg1.y1 + (along1 * y12_11);
+ intersection = new OpenLayers.Geometry.Point(x, y);
+ }
+ }
+ }
+ if(tolerance) {
+ var dist;
+ if(intersection) {
+ if(point) {
+ var segs = [seg1, seg2];
+ var seg, x, y;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ seg = segs[i];
+ for(var j=1; j<3; ++j) {
+ x = seg["x" + j];
+ y = seg["y" + j];
+ dist = Math.sqrt(
+ Math.pow(x - intersection.x, 2) +
+ Math.pow(y - intersection.y, 2)
+ );
+ if(dist < tolerance) {
+ intersection.x = x;
+ intersection.y = y;
+ break outer;
+ }
+ }
+ }
+
+ }
+ } else {
+ // no calculated intersection, but segments could be within
+ // the tolerance of one another
+ var segs = [seg1, seg2];
+ var source, target, x, y, p, result;
+ // check segment endpoints for proximity to intersection
+ // set intersection to first endpoint within the tolerance
+ outer: for(var i=0; i<2; ++i) {
+ source = segs[i];
+ target = segs[(i+1)%2];
+ for(var j=1; j<3; ++j) {
+ p = {x: source["x"+j], y: source["y"+j]};
+ result = OpenLayers.Geometry.distanceToSegment(p, target);
+ if(result.distance < tolerance) {
+ if(point) {
+ intersection = new OpenLayers.Geometry.Point(p.x, p.y);
+ } else {
+ intersection = true;
+ }
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ return intersection;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceToSegment
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with distance, along, x, and y properties. The distance
+ * will be the shortest distance between the input point and segment.
+ * The x and y properties represent the coordinates along the segment
+ * where the shortest distance meets the segment. The along attribute
+ * describes how far between the two segment points the given point is.
+ */
+OpenLayers.Geometry.distanceToSegment = function(point, segment) {
+ var result = OpenLayers.Geometry.distanceSquaredToSegment(point, segment);
+ result.distance = Math.sqrt(result.distance);
+ return result;
+};
+
+/**
+ * Function: OpenLayers.Geometry.distanceSquaredToSegment
+ *
+ * Usually the distanceToSegment function should be used. This variant however
+ * can be used for comparisons where the exact distance is not important.
+ *
+ * Parameters:
+ * point - {Object} An object with x and y properties representing the
+ * point coordinates.
+ * segment - {Object} An object with x1, y1, x2, and y2 properties
+ * representing endpoint coordinates.
+ *
+ * Returns:
+ * {Object} An object with squared distance, along, x, and y properties.
+ * The distance will be the shortest distance between the input point and
+ * segment. The x and y properties represent the coordinates along the
+ * segment where the shortest distance meets the segment. The along
+ * attribute describes how far between the two segment points the given
+ * point is.
+ */
+OpenLayers.Geometry.distanceSquaredToSegment = function(point, segment) {
+ var x0 = point.x;
+ var y0 = point.y;
+ var x1 = segment.x1;
+ var y1 = segment.y1;
+ var x2 = segment.x2;
+ var y2 = segment.y2;
+ var dx = x2 - x1;
+ var dy = y2 - y1;
+ var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) /
+ (Math.pow(dx, 2) + Math.pow(dy, 2));
+ var x, y;
+ if(along <= 0.0) {
+ x = x1;
+ y = y1;
+ } else if(along >= 1.0) {
+ x = x2;
+ y = y2;
+ } else {
+ x = x1 + along * dx;
+ y = y1 + along * dy;
+ }
+ return {
+ distance: Math.pow(x - x0, 2) + Math.pow(y - y0, 2),
+ x: x, y: y,
+ along: along
+ };
+};
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/Collection.js b/misc/openlayers/lib/OpenLayers/Geometry/Collection.js
new file mode 100644
index 0000000..f76cc85
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/Collection.js
@@ -0,0 +1,563 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different
+ * Geometries. These are stored in the local parameter <components> (which
+ * can be passed as a parameter to the constructor).
+ *
+ * As new geometries are added to the collection, they are NOT cloned.
+ * When removing geometries, they need to be specified by reference (ie you
+ * have to pass in the *exact* geometry to be removed).
+ *
+ * The <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: components
+ * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
+ */
+ components: null,
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Collection
+ * Creates a Geometry Collection -- a list of geoms.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
+ *
+ */
+ initialize: function (components) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+ this.components = [];
+ if (components != null) {
+ this.addComponents(components);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this geometry.
+ */
+ destroy: function () {
+ this.components.length = 0;
+ this.components = null;
+ OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ * Clone this geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+ */
+ clone: function() {
+ var geometry = eval("new " + this.CLASS_NAME + "()");
+ for(var i=0, len=this.components.length; i<len; i++) {
+ geometry.addComponent(this.components[i].clone());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(geometry, this);
+
+ return geometry;
+ },
+
+ /**
+ * Method: getComponentsString
+ * Get a string representing the components for this collection
+ *
+ * Returns:
+ * {String} A string representation of the components of this geometry
+ */
+ getComponentsString: function(){
+ var strings = [];
+ for(var i=0, len=this.components.length; i<len; i++) {
+ strings.push(this.components[i].toShortString());
+ }
+ return strings.join(",");
+ },
+
+ /**
+ * APIMethod: calculateBounds
+ * Recalculate the bounds by iterating through the components and
+ * calling calling extendBounds() on each item.
+ */
+ calculateBounds: function() {
+ this.bounds = null;
+ var bounds = new OpenLayers.Bounds();
+ var components = this.components;
+ if (components) {
+ for (var i=0, len=components.length; i<len; i++) {
+ bounds.extend(components[i].getBounds());
+ }
+ }
+ // to preserve old behavior, we only set bounds if non-null
+ // in the future, we could add bounds.isEmpty()
+ if (bounds.left != null && bounds.bottom != null &&
+ bounds.right != null && bounds.top != null) {
+ this.setBounds(bounds);
+ }
+ },
+
+ /**
+ * APIMethod: addComponents
+ * Add components to this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+ */
+ addComponents: function(components){
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=0, len=components.length; i<len; i++) {
+ this.addComponent(components[i]);
+ }
+ },
+
+ /**
+ * Method: addComponent
+ * Add a new component (geometry) to the collection. If this.componentTypes
+ * is set, then the component class name must be in the componentTypes array.
+ *
+ * The bounds cache is reset.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>} A geometry to add
+ * index - {int} Optional index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} The component geometry was successfully added
+ */
+ addComponent: function(component, index) {
+ var added = false;
+ if(component) {
+ if(this.componentTypes == null ||
+ (OpenLayers.Util.indexOf(this.componentTypes,
+ component.CLASS_NAME) > -1)) {
+
+ if(index != null && (index < this.components.length)) {
+ var components1 = this.components.slice(0, index);
+ var components2 = this.components.slice(index,
+ this.components.length);
+ components1.push(component);
+ this.components = components1.concat(components2);
+ } else {
+ this.components.push(component);
+ }
+ component.parent = this;
+ this.clearBounds();
+ added = true;
+ }
+ }
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponents
+ * Remove components from this geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry>)} The components to be removed
+ *
+ * Returns:
+ * {Boolean} A component was removed.
+ */
+ removeComponents: function(components) {
+ var removed = false;
+
+ if(!(OpenLayers.Util.isArray(components))) {
+ components = [components];
+ }
+ for(var i=components.length-1; i>=0; --i) {
+ removed = this.removeComponent(components[i]) || removed;
+ }
+ return removed;
+ },
+
+ /**
+ * Method: removeComponent
+ * Remove a component from this geometry.
+ *
+ * Parameters:
+ * component - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(component) {
+
+ OpenLayers.Util.removeItem(this.components, component);
+
+ // clearBounds() so that it gets recalculated on the next call
+ // to this.getBounds();
+ this.clearBounds();
+ return true;
+ },
+
+ /**
+ * APIMethod: getLength
+ * Calculate the length of this geometry
+ *
+ * Returns:
+ * {Float} The length of the geometry
+ */
+ getLength: function() {
+ var length = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getLength();
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getArea
+ * Calculate the area of this geometry. Note how this function is overridden
+ * in <OpenLayers.Geometry.Polygon>.
+ *
+ * Returns:
+ * {Float} The area of the collection by summing its parts
+ */
+ getArea: function() {
+ var area = 0.0;
+ for (var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getArea();
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the geometry in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ area += this.components[i].getGeodesicArea(projection);
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Compute the centroid for this geometry collection.
+ *
+ * Parameters:
+ * weighted - {Boolean} Perform the getCentroid computation recursively,
+ * returning an area weighted average of all geometries in this collection.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function(weighted) {
+ if (!weighted) {
+ return this.components.length && this.components[0].getCentroid();
+ }
+ var len = this.components.length;
+ if (!len) {
+ return false;
+ }
+
+ var areas = [];
+ var centroids = [];
+ var areaSum = 0;
+ var minArea = Number.MAX_VALUE;
+ var component;
+ for (var i=0; i<len; ++i) {
+ component = this.components[i];
+ var area = component.getArea();
+ var centroid = component.getCentroid(true);
+ if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
+ continue;
+ }
+ areas.push(area);
+ areaSum += area;
+ minArea = (area < minArea && area > 0) ? area : minArea;
+ centroids.push(centroid);
+ }
+ len = areas.length;
+ if (areaSum === 0) {
+ // all the components in this collection have 0 area
+ // probably a collection of points -- weight all the points the same
+ for (var i=0; i<len; ++i) {
+ areas[i] = 1;
+ }
+ areaSum = areas.length;
+ } else {
+ // normalize all the areas where the smallest area will get
+ // a value of 1
+ for (var i=0; i<len; ++i) {
+ areas[i] /= minArea;
+ }
+ areaSum /= minArea;
+ }
+
+ var xSum = 0, ySum = 0, centroid, area;
+ for (var i=0; i<len; ++i) {
+ centroid = centroids[i];
+ area = areas[i];
+ xSum += centroid.x * area;
+ ySum += centroid.y * area;
+ }
+
+ return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var length = 0.0;
+ for(var i=0, len=this.components.length; i<len; i++) {
+ length += this.components[i].getGeodesicLength(projection);
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i=0, len=this.components.length; i<len; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0; i<this.components.length; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best, distance;
+ var min = Number.POSITIVE_INFINITY;
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ result = this.components[i].distanceTo(geometry, options);
+ distance = details ? result.distance : result;
+ if(distance < min) {
+ min = distance;
+ best = result;
+ if(min == 0) {
+ break;
+ }
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geometry) {
+ var equivalent = true;
+ if(!geometry || !geometry.CLASS_NAME ||
+ (this.CLASS_NAME != geometry.CLASS_NAME)) {
+ equivalent = false;
+ } else if(!(OpenLayers.Util.isArray(geometry.components)) ||
+ (geometry.components.length != this.components.length)) {
+ equivalent = false;
+ } else {
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ if(!this.components[i].equals(geometry.components[i])) {
+ equivalent = false;
+ break;
+ }
+ }
+ }
+ return equivalent;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ for(var i=0, len=this.components.length; i<len; ++ i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices = [];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ Array.prototype.push.apply(
+ vertices, this.components[i].getVertices(nodes)
+ );
+ }
+ return vertices;
+ },
+
+
+ CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/Curve.js b/misc/openlayers/lib/OpenLayers/Geometry/Curve.js
new file mode 100644
index 0000000..e663e0b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/Curve.js
@@ -0,0 +1,89 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Curve
+ * A Curve is a MultiPoint, whose points are assumed to be connected. To
+ * this end, we provide a "getLength()" function, which iterates through
+ * the points, summing the distances between them.
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.MultiPoint>
+ */
+OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Curve
+ *
+ * Parameters:
+ * point - {Array(<OpenLayers.Geometry.Point>)}
+ */
+
+ /**
+ * APIMethod: getLength
+ *
+ * Returns:
+ * {Float} The length of the curve
+ */
+ getLength: function() {
+ var length = 0.0;
+ if ( this.components && (this.components.length > 1)) {
+ for(var i=1, len=this.components.length; i<len; i++) {
+ length += this.components[i-1].distanceTo(this.components[i]);
+ }
+ }
+ return length;
+ },
+
+ /**
+ * APIMethod: getGeodesicLength
+ * Calculate the approximate length of the geometry were it projected onto
+ * the earth.
+ *
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Returns:
+ * {Float} The appoximate geodesic length of the geometry in meters.
+ */
+ getGeodesicLength: function(projection) {
+ var geom = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ geom = this.clone().transform(projection, gg);
+ }
+ }
+ var length = 0.0;
+ if(geom.components && (geom.components.length > 1)) {
+ var p1, p2;
+ for(var i=1, len=geom.components.length; i<len; i++) {
+ p1 = geom.components[i-1];
+ p2 = geom.components[i];
+ // this returns km and requires lon/lat properties
+ length += OpenLayers.Util.distVincenty(
+ {lon: p1.x, lat: p1.y}, {lon: p2.x, lat: p2.y}
+ );
+ }
+ }
+ // convert to m
+ return length * 1000;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/LineString.js b/misc/openlayers/lib/OpenLayers/Geometry/LineString.js
new file mode 100644
index 0000000..b7d7dac
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/LineString.js
@@ -0,0 +1,646 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can
+ * never be less than two points long.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+ /**
+ * Constructor: OpenLayers.Geometry.LineString
+ * Create a new LineString geometry
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
+ * generate the linestring
+ *
+ */
+
+ /**
+ * APIMethod: removeComponent
+ * Only allows removal of a point if there are three or more points in
+ * the linestring. (otherwise the result would be just a single point)
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} The point to be removed
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 2);
+ if (removed) {
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Test for instersection between two geometries. This is a cheapo
+ * implementation of the Bently-Ottmann algorigithm. It doesn't
+ * really keep track of a sweep line data structure. It is closer
+ * to the brute force method, except that segments are sorted and
+ * potential intersections are only calculated when bounding boxes
+ * intersect.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this geometry.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var type = geometry.CLASS_NAME;
+ if(type == "OpenLayers.Geometry.LineString" ||
+ type == "OpenLayers.Geometry.LinearRing" ||
+ type == "OpenLayers.Geometry.Point") {
+ var segs1 = this.getSortedSegments();
+ var segs2;
+ if(type == "OpenLayers.Geometry.Point") {
+ segs2 = [{
+ x1: geometry.x, y1: geometry.y,
+ x2: geometry.x, y2: geometry.y
+ }];
+ } else {
+ segs2 = geometry.getSortedSegments();
+ }
+ var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
+ seg2, seg2y1, seg2y2;
+ // sweep right
+ outer: for(var i=0, len=segs1.length; i<len; ++i) {
+ seg1 = segs1[i];
+ seg1x1 = seg1.x1;
+ seg1x2 = seg1.x2;
+ seg1y1 = seg1.y1;
+ seg1y2 = seg1.y2;
+ inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+ seg2 = segs2[j];
+ if(seg2.x1 > seg1x2) {
+ // seg1 still left of seg2
+ break;
+ }
+ if(seg2.x2 < seg1x1) {
+ // seg2 still left of seg1
+ continue;
+ }
+ seg2y1 = seg2.y1;
+ seg2y2 = seg2.y2;
+ if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
+ // seg2 above seg1
+ continue;
+ }
+ if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
+ // seg2 below seg1
+ continue;
+ }
+ if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
+ intersect = true;
+ break outer;
+ }
+ }
+ }
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * Method: getSortedSegments
+ *
+ * Returns:
+ * {Array} An array of segment objects. Segment objects have properties
+ * x1, y1, x2, and y2. The start point is represented by x1 and y1.
+ * The end point is represented by x2 and y2. Start and end are
+ * ordered so that x1 < x2.
+ */
+ getSortedSegments: function() {
+ var numSeg = this.components.length - 1;
+ var segments = new Array(numSeg), point1, point2;
+ for(var i=0; i<numSeg; ++i) {
+ point1 = this.components[i];
+ point2 = this.components[i + 1];
+ if(point1.x < point2.x) {
+ segments[i] = {
+ x1: point1.x,
+ y1: point1.y,
+ x2: point2.x,
+ y2: point2.y
+ };
+ } else {
+ segments[i] = {
+ x1: point2.x,
+ y1: point2.y,
+ x2: point1.x,
+ y2: point1.y
+ };
+ }
+ }
+ // more efficient to define this somewhere static
+ function byX1(seg1, seg2) {
+ return seg1.x1 - seg2.x1;
+ }
+ return segments.sort(byX1);
+ },
+
+ /**
+ * Method: splitWithSegment
+ * Split this geometry with the given segment.
+ *
+ * Parameters:
+ * seg - {Object} An object with x1, y1, x2, and y2 properties referencing
+ * segment endpoint coordinates.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source segment must be within the
+ * tolerance distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of one of the source segment's
+ * endpoints will be assumed to occur at the endpoint.
+ *
+ * Returns:
+ * {Object} An object with *lines* and *points* properties. If the given
+ * segment intersects this linestring, the lines array will reference
+ * geometries that result from the split. The points array will contain
+ * all intersection points. Intersection points are sorted along the
+ * segment (in order from x1,y1 to x2,y2).
+ */
+ splitWithSegment: function(seg, options) {
+ var edge = !(options && options.edge === false);
+ var tolerance = options && options.tolerance;
+ var lines = [];
+ var verts = this.getVertices();
+ var points = [];
+ var intersections = [];
+ var split = false;
+ var vert1, vert2, point;
+ var node, vertex, target;
+ var interOptions = {point: true, tolerance: tolerance};
+ var result = null;
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ points.push(vert1.clone());
+ vert2 = verts[i+1];
+ target = {x1: vert1.x, y1: vert1.y, x2: vert2.x, y2: vert2.y};
+ point = OpenLayers.Geometry.segmentsIntersect(
+ seg, target, interOptions
+ );
+ if(point instanceof OpenLayers.Geometry.Point) {
+ if((point.x === seg.x1 && point.y === seg.y1) ||
+ (point.x === seg.x2 && point.y === seg.y2) ||
+ point.equals(vert1) || point.equals(vert2)) {
+ vertex = true;
+ } else {
+ vertex = false;
+ }
+ if(vertex || edge) {
+ // push intersections different than the previous
+ if(!point.equals(intersections[intersections.length-1])) {
+ intersections.push(point.clone());
+ }
+ if(i === 0) {
+ if(point.equals(vert1)) {
+ continue;
+ }
+ }
+ if(point.equals(vert2)) {
+ continue;
+ }
+ split = true;
+ if(!point.equals(vert1)) {
+ points.push(point);
+ }
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ points = [point.clone()];
+ }
+ }
+ }
+ if(split) {
+ points.push(vert2.clone());
+ lines.push(new OpenLayers.Geometry.LineString(points));
+ }
+ if(intersections.length > 0) {
+ // sort intersections along segment
+ var xDir = seg.x1 < seg.x2 ? 1 : -1;
+ var yDir = seg.y1 < seg.y2 ? 1 : -1;
+ result = {
+ lines: lines,
+ points: intersections.sort(function(p1, p2) {
+ return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);
+ })
+ };
+ }
+ return result;
+ },
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * target - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(target, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var sourceSplit, targetSplit, sourceParts, targetParts;
+ if(target instanceof OpenLayers.Geometry.LineString) {
+ var verts = this.getVertices();
+ var vert1, vert2, seg, splits, lines, point;
+ var points = [];
+ sourceParts = [];
+ for(var i=0, stop=verts.length-2; i<=stop; ++i) {
+ vert1 = verts[i];
+ vert2 = verts[i+1];
+ seg = {
+ x1: vert1.x, y1: vert1.y,
+ x2: vert2.x, y2: vert2.y
+ };
+ targetParts = targetParts || [target];
+ if(mutual) {
+ points.push(vert1.clone());
+ }
+ for(var j=0; j<targetParts.length; ++j) {
+ splits = targetParts[j].splitWithSegment(seg, options);
+ if(splits) {
+ // splice in new features
+ lines = splits.lines;
+ if(lines.length > 0) {
+ lines.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, lines);
+ j += lines.length - 2;
+ }
+ if(mutual) {
+ for(var k=0, len=splits.points.length; k<len; ++k) {
+ point = splits.points[k];
+ if(!point.equals(vert1)) {
+ points.push(point);
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ if(point.equals(vert2)) {
+ points = [];
+ } else {
+ points = [point.clone()];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(mutual && sourceParts.length > 0 && points.length > 0) {
+ points.push(vert2.clone());
+ sourceParts.push(new OpenLayers.Geometry.LineString(points));
+ }
+ } else {
+ results = target.splitWith(this, options);
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetSplit || sourceSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ return geometry.split(this, options);
+
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ var vertices;
+ if(nodes === true) {
+ vertices = [
+ this.components[0],
+ this.components[this.components.length-1]
+ ];
+ } else if (nodes === false) {
+ vertices = this.components.slice(1, this.components.length-1);
+ } else {
+ vertices = this.components.slice();
+ }
+ return vertices;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var result, best = {};
+ var min = Number.POSITIVE_INFINITY;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ var segs = this.getSortedSegments();
+ var x = geometry.x;
+ var y = geometry.y;
+ var seg;
+ for(var i=0, len=segs.length; i<len; ++i) {
+ seg = segs[i];
+ result = OpenLayers.Geometry.distanceToSegment(geometry, seg);
+ if(result.distance < min) {
+ min = result.distance;
+ best = result;
+ if(min === 0) {
+ break;
+ }
+ } else {
+ // if distance increases and we cross y0 to the right of x0, no need to keep looking.
+ if(seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {
+ break;
+ }
+ }
+ }
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x, y0: best.y,
+ x1: x, y1: y
+ };
+ } else {
+ best = best.distance;
+ }
+ } else if(geometry instanceof OpenLayers.Geometry.LineString) {
+ var segs0 = this.getSortedSegments();
+ var segs1 = geometry.getSortedSegments();
+ var seg0, seg1, intersection, x0, y0;
+ var len1 = segs1.length;
+ var interOptions = {point: true};
+ outer: for(var i=0, len=segs0.length; i<len; ++i) {
+ seg0 = segs0[i];
+ x0 = seg0.x1;
+ y0 = seg0.y1;
+ for(var j=0; j<len1; ++j) {
+ seg1 = segs1[j];
+ intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);
+ if(intersection) {
+ min = 0;
+ best = {
+ distance: 0,
+ x0: intersection.x, y0: intersection.y,
+ x1: intersection.x, y1: intersection.y
+ };
+ break outer;
+ } else {
+ result = OpenLayers.Geometry.distanceToSegment({x: x0, y: y0}, seg1);
+ if(result.distance < min) {
+ min = result.distance;
+ best = {
+ distance: min,
+ x0: x0, y0: y0,
+ x1: result.x, y1: result.y
+ };
+ }
+ }
+ }
+ }
+ if(!details) {
+ best = best.distance;
+ }
+ if(min !== 0) {
+ // check the final vertex in this line's sorted segments
+ if(seg0) {
+ result = geometry.distanceTo(
+ new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),
+ options
+ );
+ var dist = details ? result.distance : result;
+ if(dist < min) {
+ if(details) {
+ best = {
+ distance: min,
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0
+ };
+ } else {
+ best = dist;
+ }
+ }
+ }
+ }
+ } else {
+ best = geometry.distanceTo(this, options);
+ // swap since target comes from this line
+ if(details) {
+ best = {
+ distance: best.distance,
+ x0: best.x1, y0: best.y1,
+ x1: best.x0, y1: best.y0
+ };
+ }
+ }
+ return best;
+ },
+
+ /**
+ * APIMethod: simplify
+ * This function will return a simplified LineString.
+ * Simplification is based on the Douglas-Peucker algorithm.
+ *
+ *
+ * Parameters:
+ * tolerance - {number} threshhold for simplification in map units
+ *
+ * Returns:
+ * {OpenLayers.Geometry.LineString} the simplified LineString
+ */
+ simplify: function(tolerance){
+ if (this && this !== null) {
+ var points = this.getVertices();
+ if (points.length < 3) {
+ return this;
+ }
+
+ var compareNumbers = function(a, b){
+ return (a-b);
+ };
+
+ /**
+ * Private function doing the Douglas-Peucker reduction
+ */
+ var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance){
+ var maxDistance = 0;
+ var indexFarthest = 0;
+
+ for (var index = firstPoint, distance; index < lastPoint; index++) {
+ distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);
+ if (distance > maxDistance) {
+ maxDistance = distance;
+ indexFarthest = index;
+ }
+ }
+
+ if (maxDistance > tolerance && indexFarthest != firstPoint) {
+ //Add the largest point that exceeds the tolerance
+ pointIndexsToKeep.push(indexFarthest);
+ douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);
+ douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);
+ }
+ };
+
+ /**
+ * Private function calculating the perpendicular distance
+ * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower
+ */
+ var perpendicularDistance = function(point1, point2, point){
+ //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle
+ //Base = v((x1-x2)²+(x1-x2)²) *Base of Triangle*
+ //Area = .5*Base*H *Solve for height
+ //Height = Area/.5/Base
+
+ var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));
+ var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));
+ var height = area / bottom * 2;
+
+ return height;
+ };
+
+ var firstPoint = 0;
+ var lastPoint = points.length - 1;
+ var pointIndexsToKeep = [];
+
+ //Add the first and last index to the keepers
+ pointIndexsToKeep.push(firstPoint);
+ pointIndexsToKeep.push(lastPoint);
+
+ //The first and the last point cannot be the same
+ while (points[firstPoint].equals(points[lastPoint])) {
+ lastPoint--;
+ //Addition: the first point not equal to first point in the LineString is kept as well
+ pointIndexsToKeep.push(lastPoint);
+ }
+
+ douglasPeuckerReduction(points, firstPoint, lastPoint, tolerance);
+ var returnPoints = [];
+ pointIndexsToKeep.sort(compareNumbers);
+ for (var index = 0; index < pointIndexsToKeep.length; index++) {
+ returnPoints.push(points[pointIndexsToKeep[index]]);
+ }
+ return new OpenLayers.Geometry.LineString(returnPoints);
+
+ }
+ else {
+ return this;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/LinearRing.js b/misc/openlayers/lib/OpenLayers/Geometry/LinearRing.js
new file mode 100644
index 0000000..b0a694c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/LinearRing.js
@@ -0,0 +1,433 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ *
+ * A Linear Ring is a special LineString which is closed. It closes itself
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point.
+ *
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry.LineString>
+ */
+OpenLayers.Geometry.LinearRing = OpenLayers.Class(
+ OpenLayers.Geometry.LineString, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null
+ * value means the component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.LinearRing
+ * Linear rings are constructed with an array of points. This array
+ * can represent a closed or open ring. If the ring is open (the last
+ * point does not equal the first point), the constructor will close
+ * the ring. If the ring is already closed (the last point does equal
+ * the first point), it will be left closed.
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)} points
+ */
+
+ /**
+ * APIMethod: addComponent
+ * Adds a point to geometry components. If the point is to be added to
+ * the end of the components array and it is the same as the last point
+ * already in that array, the duplicate point is not added. This has
+ * the effect of closing the ring if it is not already closed, and
+ * doing the right thing if it is already closed. This behavior can
+ * be overridden by calling the method with a non-null index as the
+ * second argument.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * index - {Integer} Index into the array to insert the component
+ *
+ * Returns:
+ * {Boolean} Was the Point successfully added?
+ */
+ addComponent: function(point, index) {
+ var added = false;
+
+ //remove last point
+ var lastPoint = this.components.pop();
+
+ // given an index, add the point
+ // without an index only add non-duplicate points
+ if(index != null || !point.equals(lastPoint)) {
+ added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ arguments);
+ }
+
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+
+ return added;
+ },
+
+ /**
+ * APIMethod: removeComponent
+ * Removes a point from geometry components.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean} The component was removed.
+ */
+ removeComponent: function(point) {
+ var removed = this.components && (this.components.length > 3);
+ if (removed) {
+ //remove last point
+ this.components.pop();
+
+ //remove our point
+ OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
+ arguments);
+ //append copy of first point
+ var firstPoint = this.components[0];
+ OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
+ [firstPoint]);
+ }
+ return removed;
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ for(var i = 0, len=this.components.length; i<len - 1; i++) {
+ this.components[i].move(x, y);
+ }
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a geometry around some origin
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].rotate(angle, origin);
+ }
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a geometry relative to some origin. Use this method to apply
+ * a uniform scaling to a geometry.
+ *
+ * Parameters:
+ * scale - {Float} Factor by which to scale the geometry. A scale of 2
+ * doubles the size of the geometry in each dimension
+ * (lines, for example, will be twice as long, and polygons
+ * will have four times the area).
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ for(var i=0, len=this.components.length; i<len - 1; ++i) {
+ this.components[i].resize(scale, origin, ratio);
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: transform
+ * Reproject the components geometry from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if (source && dest) {
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var component = this.components[i];
+ component.transform(source, dest);
+ }
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ if (this.components) {
+ var len = this.components.length;
+ if (len > 0 && len <= 2) {
+ return this.components[0].clone();
+ } else if (len > 2) {
+ var sumX = 0.0;
+ var sumY = 0.0;
+ var x0 = this.components[0].x;
+ var y0 = this.components[0].y;
+ var area = -1 * this.getArea();
+ if (area != 0) {
+ for (var i = 0; i < len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sumX += (b.x + c.x - 2 * x0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ sumY += (b.y + c.y - 2 * y0) * ((b.x - x0) * (c.y - y0) - (c.x - x0) * (b.y - y0));
+ }
+ var x = x0 + sumX / (6 * area);
+ var y = y0 + sumY / (6 * area);
+ } else {
+ for (var i = 0; i < len - 1; i++) {
+ sumX += this.components[i].x;
+ sumY += this.components[i].y;
+ }
+ var x = sumX / (len - 1);
+ var y = sumY / (len - 1);
+ }
+ return new OpenLayers.Geometry.Point(x, y);
+ } else {
+ return null;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getArea
+ * Note - The area is positive if the ring is oriented CW, otherwise
+ * it will be negative.
+ *
+ * Returns:
+ * {Float} The signed area for a ring.
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 2)) {
+ var sum = 0.0;
+ for (var i=0, len=this.components.length; i<len - 1; i++) {
+ var b = this.components[i];
+ var c = this.components[i+1];
+ sum += (b.x + c.x) * (c.y - b.y);
+ }
+ area = - sum / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth. Note that this area will be positive if ring is oriented
+ * clockwise, otherwise it will be negative.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate signed geodesic area of the polygon in square
+ * meters.
+ */
+ getGeodesicArea: function(projection) {
+ var ring = this; // so we can work with a clone if needed
+ if(projection) {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ if(!gg.equals(projection)) {
+ ring = this.clone().transform(projection, gg);
+ }
+ }
+ var area = 0.0;
+ var len = ring.components && ring.components.length;
+ if(len > 2) {
+ var p1, p2;
+ for(var i=0; i<len-1; i++) {
+ p1 = ring.components[i];
+ p2 = ring.components[i+1];
+ area += OpenLayers.Util.rad(p2.x - p1.x) *
+ (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
+ Math.sin(OpenLayers.Util.rad(p2.y)));
+ }
+ area = area * 6378137.0 * 6378137.0 / 2.0;
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a linear ring. For the case where a point
+ * is coincident with a linear ring edge, returns 1. Otherwise,
+ * returns boolean.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the linear ring. Returns 1 if
+ * the point is coincident with an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var approx = OpenLayers.Number.limitSigDigs;
+ var digs = 14;
+ var px = approx(point.x, digs);
+ var py = approx(point.y, digs);
+ function getX(y, x1, y1, x2, y2) {
+ return (y - y2) * ((x2 - x1) / (y2 - y1)) + x2;
+ }
+ var numSeg = this.components.length - 1;
+ var start, end, x1, y1, x2, y2, cx, cy;
+ var crosses = 0;
+ for(var i=0; i<numSeg; ++i) {
+ start = this.components[i];
+ x1 = approx(start.x, digs);
+ y1 = approx(start.y, digs);
+ end = this.components[i + 1];
+ x2 = approx(end.x, digs);
+ y2 = approx(end.y, digs);
+
+ /**
+ * The following conditions enforce five edge-crossing rules:
+ * 1. points coincident with edges are considered contained;
+ * 2. an upward edge includes its starting endpoint, and
+ * excludes its final endpoint;
+ * 3. a downward edge excludes its starting endpoint, and
+ * includes its final endpoint;
+ * 4. horizontal edges are excluded; and
+ * 5. the edge-ray intersection point must be strictly right
+ * of the point P.
+ */
+ if(y1 == y2) {
+ // horizontal edge
+ if(py == y1) {
+ // point on horizontal line
+ if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
+ x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ // ignore other horizontal edges
+ continue;
+ }
+ cx = approx(getX(py, x1, y1, x2, y2), digs);
+ if(cx == px) {
+ // point on line
+ if(y1 < y2 && (py >= y1 && py <= y2) || // upward
+ y1 > y2 && (py <= y1 && py >= y2)) { // downward
+ // point on edge
+ crosses = -1;
+ break;
+ }
+ }
+ if(cx <= px) {
+ // no crossing to the right
+ continue;
+ }
+ if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
+ // no crossing
+ continue;
+ }
+ if(y1 < y2 && (py >= y1 && py < y2) || // upward
+ y1 > y2 && (py < y1 && py >= y2)) { // downward
+ ++crosses;
+ }
+ }
+ var contained = (crosses == -1) ?
+ // on edge
+ 1 :
+ // even (out) or odd (in)
+ !!(crosses & 1);
+
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+ intersect = geometry.intersects(this);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
+ this, [geometry]
+ );
+ } else {
+ // check for component intersections
+ for(var i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = geometry.components[i].intersects(this);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/MultiLineString.js b/misc/openlayers/lib/OpenLayers/Geometry/MultiLineString.js
new file mode 100644
index 0000000..4e330b0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/MultiLineString.js
@@ -0,0 +1,258 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LineString"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiLineString
+ * Constructor for a MultiLineString Geometry.
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LineString>)}
+ *
+ */
+
+ /**
+ * Method: split
+ * Use this geometry (the source) to attempt to split a target geometry.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ split: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, sourceLine, sourceLines, sourceSplit, targetSplit;
+ var sourceParts = [];
+ var targetParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ sourceLine = this.components[i];
+ sourceSplit = false;
+ for(var j=0; j < targetParts.length; ++j) {
+ splits = sourceLine.split(targetParts[j], options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ for(var k=0, klen=sourceLines.length; k<klen; ++k) {
+ if(k===0 && sourceParts.length) {
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLines[k]
+ );
+ } else {
+ sourceParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ sourceLines[k]
+ ])
+ );
+ }
+ }
+ sourceSplit = true;
+ splits = splits[1];
+ }
+ if(splits.length) {
+ // splice in new target parts
+ splits.unshift(j, 1);
+ Array.prototype.splice.apply(targetParts, splits);
+ break;
+ }
+ }
+ }
+ if(!sourceSplit) {
+ // source line was not hit
+ if(sourceParts.length) {
+ // add line to existing multi
+ sourceParts[sourceParts.length-1].addComponent(
+ sourceLine.clone()
+ );
+ } else {
+ // create a fresh multi
+ sourceParts = [
+ new OpenLayers.Geometry.MultiLineString(
+ sourceLine.clone()
+ )
+ ];
+ }
+ }
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ /**
+ * Method: splitWith
+ * Split this geometry (the target) with the given geometry (the source).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} A geometry used to split this
+ * geometry (the source).
+ * options - {Object} Properties of this object will be used to determine
+ * how the split is conducted.
+ *
+ * Valid options:
+ * mutual - {Boolean} Split the source geometry in addition to the target
+ * geometry. Default is false.
+ * edge - {Boolean} Allow splitting when only edges intersect. Default is
+ * true. If false, a vertex on the source must be within the tolerance
+ * distance of the intersection to be considered a split.
+ * tolerance - {Number} If a non-null value is provided, intersections
+ * within the tolerance distance of an existing vertex on the source
+ * will be assumed to occur at the vertex.
+ *
+ * Returns:
+ * {Array} A list of geometries (of this same type as the target) that
+ * result from splitting the target with the source geometry. The
+ * source and target geometry will remain unmodified. If no split
+ * results, null will be returned. If mutual is true and a split
+ * results, return will be an array of two arrays - the first will be
+ * all geometries that result from splitting the source geometry and
+ * the second will be all geometries that result from splitting the
+ * target geometry.
+ */
+ splitWith: function(geometry, options) {
+ var results = null;
+ var mutual = options && options.mutual;
+ var splits, targetLine, sourceLines, sourceSplit, targetSplit, sourceParts, targetParts;
+ if(geometry instanceof OpenLayers.Geometry.LineString) {
+ targetParts = [];
+ sourceParts = [geometry];
+ for(var i=0, len=this.components.length; i<len; ++i) {
+ targetSplit = false;
+ targetLine = this.components[i];
+ for(var j=0; j<sourceParts.length; ++j) {
+ splits = sourceParts[j].split(targetLine, options);
+ if(splits) {
+ if(mutual) {
+ sourceLines = splits[0];
+ if(sourceLines.length) {
+ // splice in new source parts
+ sourceLines.unshift(j, 1);
+ Array.prototype.splice.apply(sourceParts, sourceLines);
+ j += sourceLines.length - 2;
+ }
+ splits = splits[1];
+ if(splits.length === 0) {
+ splits = [targetLine.clone()];
+ }
+ }
+ for(var k=0, klen=splits.length; k<klen; ++k) {
+ if(k===0 && targetParts.length) {
+ targetParts[targetParts.length-1].addComponent(
+ splits[k]
+ );
+ } else {
+ targetParts.push(
+ new OpenLayers.Geometry.MultiLineString([
+ splits[k]
+ ])
+ );
+ }
+ }
+ targetSplit = true;
+ }
+ }
+ if(!targetSplit) {
+ // target component was not hit
+ if(targetParts.length) {
+ // add it to any existing multi-line
+ targetParts[targetParts.length-1].addComponent(
+ targetLine.clone()
+ );
+ } else {
+ // or start with a fresh multi-line
+ targetParts = [
+ new OpenLayers.Geometry.MultiLineString([
+ targetLine.clone()
+ ])
+ ];
+ }
+
+ }
+ }
+ } else {
+ results = geometry.split(this);
+ }
+ if(sourceParts && sourceParts.length > 1) {
+ sourceSplit = true;
+ } else {
+ sourceParts = [];
+ }
+ if(targetParts && targetParts.length > 1) {
+ targetSplit = true;
+ } else {
+ targetParts = [];
+ }
+ if(sourceSplit || targetSplit) {
+ if(mutual) {
+ results = [sourceParts, targetParts];
+ } else {
+ results = targetParts;
+ }
+ }
+ return results;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/MultiPoint.js b/misc/openlayers/lib/OpenLayers/Geometry/MultiPoint.js
new file mode 100644
index 0000000..ed8ff67
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/MultiPoint.js
@@ -0,0 +1,66 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points. Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Point"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPoint
+ * Create a new MultiPoint Geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.MultiPoint>}
+ */
+
+ /**
+ * APIMethod: addPoint
+ * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be added
+ * index - {Integer} Optional index
+ */
+ addPoint: function(point, index) {
+ this.addComponent(point, index);
+ },
+
+ /**
+ * APIMethod: removePoint
+ * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>} Point to be removed
+ */
+ removePoint: function(point){
+ this.removeComponent(point);
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/MultiPolygon.js b/misc/openlayers/lib/OpenLayers/Geometry/MultiPolygon.js
new file mode 100644
index 0000000..d1e59dc
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/MultiPolygon.js
@@ -0,0 +1,42 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components. Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ */
+OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.Polygon"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.MultiPolygon
+ * Create a new MultiPolygon geometry
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
+ * used to generate the MultiPolygon
+ *
+ */
+
+ CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/Point.js b/misc/openlayers/lib/OpenLayers/Geometry/Point.js
new file mode 100644
index 0000000..456956f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/Point.js
@@ -0,0 +1,283 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Point
+ * Point geometry class.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * APIProperty: x
+ * {float}
+ */
+ x: null,
+
+ /**
+ * APIProperty: y
+ * {float}
+ */
+ y: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Point
+ * Construct a point geometry.
+ *
+ * Parameters:
+ * x - {float}
+ * y - {float}
+ *
+ */
+ initialize: function(x, y) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+
+ this.x = parseFloat(x);
+ this.y = parseFloat(y);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Geometry.Point(this.x, this.y);
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ return obj;
+ },
+
+ /**
+ * Method: calculateBounds
+ * Create a new Bounds based on the lon/lat
+ */
+ calculateBounds: function () {
+ this.bounds = new OpenLayers.Bounds(this.x, this.y,
+ this.x, this.y);
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and x2 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var details = edge && options && options.details;
+ var distance, x0, y0, x1, y1, result;
+ if(geometry instanceof OpenLayers.Geometry.Point) {
+ x0 = this.x;
+ y0 = this.y;
+ x1 = geometry.x;
+ y1 = geometry.y;
+ distance = Math.sqrt(Math.pow(x0 - x1, 2) + Math.pow(y0 - y1, 2));
+ result = !details ?
+ distance : {x0: x0, y0: y0, x1: x1, y1: y1, distance: distance};
+ } else {
+ result = geometry.distanceTo(this, options);
+ if(details) {
+ // switch coord order since this geom is target
+ result = {
+ x0: result.x1, y0: result.y1,
+ x1: result.x0, y1: result.y0,
+ distance: result.distance
+ };
+ }
+ }
+ return result;
+ },
+
+ /**
+ * APIMethod: equals
+ * Determine whether another geometry is equivalent to this one. Geometries
+ * are considered equivalent if all components have the same coordinates.
+ *
+ * Parameters:
+ * geom - {<OpenLayers.Geometry.Point>} The geometry to test.
+ *
+ * Returns:
+ * {Boolean} The supplied geometry is equivalent to this geometry.
+ */
+ equals: function(geom) {
+ var equals = false;
+ if (geom != null) {
+ equals = ((this.x == geom.x && this.y == geom.y) ||
+ (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
+ }
+ return equals;
+ },
+
+ /**
+ * Method: toShortString
+ *
+ * Returns:
+ * {String} Shortened String representation of Point object.
+ * (ex. <i>"5, 42"</i>)
+ */
+ toShortString: function() {
+ return (this.x + ", " + this.y);
+ },
+
+ /**
+ * APIMethod: move
+ * Moves a geometry by the given displacement along positive x and y axes.
+ * This modifies the position of the geometry and clears the cached
+ * bounds.
+ *
+ * Parameters:
+ * x - {Float} Distance to move geometry in positive x direction.
+ * y - {Float} Distance to move geometry in positive y direction.
+ */
+ move: function(x, y) {
+ this.x = this.x + x;
+ this.y = this.y + y;
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: rotate
+ * Rotate a point around another.
+ *
+ * Parameters:
+ * angle - {Float} Rotation angle in degrees (measured counterclockwise
+ * from the positive x-axis)
+ * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+ */
+ rotate: function(angle, origin) {
+ angle *= Math.PI / 180;
+ var radius = this.distanceTo(origin);
+ var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+ this.x = origin.x + (radius * Math.cos(theta));
+ this.y = origin.y + (radius * Math.sin(theta));
+ this.clearBounds();
+ },
+
+ /**
+ * APIMethod: getCentroid
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>} The centroid of the collection
+ */
+ getCentroid: function() {
+ return new OpenLayers.Geometry.Point(this.x, this.y);
+ },
+
+ /**
+ * APIMethod: resize
+ * Resize a point relative to some origin. For points, this has the effect
+ * of scaling a vector (from the origin to the point). This method is
+ * more useful on geometry collection subclasses.
+ *
+ * Parameters:
+ * scale - {Float} Ratio of the new distance from the origin to the old
+ * distance from the origin. A scale of 2 doubles the
+ * distance between the point and origin.
+ * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+ * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>} - The current geometry.
+ */
+ resize: function(scale, origin, ratio) {
+ ratio = (ratio == undefined) ? 1 : ratio;
+ this.x = origin.x + (scale * ratio * (this.x - origin.x));
+ this.y = origin.y + (scale * (this.y - origin.y));
+ this.clearBounds();
+ return this;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.equals(geometry);
+ } else {
+ intersect = geometry.intersects(this);
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: transform
+ * Translate the x,y properties of the point from source to dest.
+ *
+ * Parameters:
+ * source - {<OpenLayers.Projection>}
+ * dest - {<OpenLayers.Projection>}
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ transform: function(source, dest) {
+ if ((source && dest)) {
+ OpenLayers.Projection.transform(
+ this, source, dest);
+ this.bounds = null;
+ }
+ return this;
+ },
+
+ /**
+ * APIMethod: getVertices
+ * Return a list of all points in this geometry.
+ *
+ * Parameters:
+ * nodes - {Boolean} For lines, only return vertices that are
+ * endpoints. If false, for lines, only vertices that are not
+ * endpoints will be returned. If not provided, all vertices will
+ * be returned.
+ *
+ * Returns:
+ * {Array} A list of all vertices in the geometry.
+ */
+ getVertices: function(nodes) {
+ return [this];
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Point"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/Polygon.js b/misc/openlayers/lib/OpenLayers/Geometry/Polygon.js
new file mode 100644
index 0000000..6aaff1f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Geometry/Polygon.js
@@ -0,0 +1,255 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Geometry/LinearRing.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon
+ * Polygon is a collection of Geometry.LinearRings.
+ *
+ * Inherits from:
+ * - <OpenLayers.Geometry.Collection>
+ * - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.Polygon = OpenLayers.Class(
+ OpenLayers.Geometry.Collection, {
+
+ /**
+ * Property: componentTypes
+ * {Array(String)} An array of class names representing the types of
+ * components that the collection can include. A null value means the
+ * component types are not restricted.
+ */
+ componentTypes: ["OpenLayers.Geometry.LinearRing"],
+
+ /**
+ * Constructor: OpenLayers.Geometry.Polygon
+ * Constructor for a Polygon geometry.
+ * The first ring (this.component[0])is the outer bounds of the polygon and
+ * all subsequent rings (this.component[1-n]) are internal holes.
+ *
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.LinearRing>)}
+ */
+
+ /**
+ * APIMethod: getArea
+ * Calculated by subtracting the areas of the internal holes from the
+ * area of the outer hole.
+ *
+ * Returns:
+ * {float} The area of the geometry
+ */
+ getArea: function() {
+ var area = 0.0;
+ if ( this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getArea());
+ for (var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getArea());
+ }
+ }
+ return area;
+ },
+
+ /**
+ * APIMethod: getGeodesicArea
+ * Calculate the approximate area of the polygon were it projected onto
+ * the earth.
+ *
+ * Parameters:
+ * projection - {<OpenLayers.Projection>} The spatial reference system
+ * for the geometry coordinates. If not provided, Geographic/WGS84 is
+ * assumed.
+ *
+ * Reference:
+ * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
+ * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
+ * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
+ *
+ * Returns:
+ * {float} The approximate geodesic area of the polygon in square meters.
+ */
+ getGeodesicArea: function(projection) {
+ var area = 0.0;
+ if(this.components && (this.components.length > 0)) {
+ area += Math.abs(this.components[0].getGeodesicArea(projection));
+ for(var i=1, len=this.components.length; i<len; i++) {
+ area -= Math.abs(this.components[i].getGeodesicArea(projection));
+ }
+ }
+ return area;
+ },
+
+ /**
+ * Method: containsPoint
+ * Test if a point is inside a polygon. Points on a polygon edge are
+ * considered inside.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {Boolean | Number} The point is inside the polygon. Returns 1 if the
+ * point is on an edge. Returns boolean otherwise.
+ */
+ containsPoint: function(point) {
+ var numRings = this.components.length;
+ var contained = false;
+ if(numRings > 0) {
+ // check exterior ring - 1 means on edge, boolean otherwise
+ contained = this.components[0].containsPoint(point);
+ if(contained !== 1) {
+ if(contained && numRings > 1) {
+ // check interior rings
+ var hole;
+ for(var i=1; i<numRings; ++i) {
+ hole = this.components[i].containsPoint(point);
+ if(hole) {
+ if(hole === 1) {
+ // on edge
+ contained = 1;
+ } else {
+ // in hole
+ contained = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+ return contained;
+ },
+
+ /**
+ * APIMethod: intersects
+ * Determine if the input geometry intersects this one.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+ *
+ * Returns:
+ * {Boolean} The input geometry intersects this one.
+ */
+ intersects: function(geometry) {
+ var intersect = false;
+ var i, len;
+ if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+ intersect = this.containsPoint(geometry);
+ } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+ geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+ // check if rings/linestrings intersect
+ for(i=0, len=this.components.length; i<len; ++i) {
+ intersect = geometry.intersects(this.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ if(!intersect) {
+ // check if this poly contains points of the ring/linestring
+ for(i=0, len=geometry.components.length; i<len; ++i) {
+ intersect = this.containsPoint(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ } else {
+ for(i=0, len=geometry.components.length; i<len; ++ i) {
+ intersect = this.intersects(geometry.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ // check case where this poly is wholly contained by another
+ if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+ // exterior ring points will be contained in the other geometry
+ var ring = this.components[0];
+ for(i=0, len=ring.components.length; i<len; ++i) {
+ intersect = geometry.containsPoint(ring.components[i]);
+ if(intersect) {
+ break;
+ }
+ }
+ }
+ return intersect;
+ },
+
+ /**
+ * APIMethod: distanceTo
+ * Calculate the closest distance between two geometries (on the x-y plane).
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>} The target geometry.
+ * options - {Object} Optional properties for configuring the distance
+ * calculation.
+ *
+ * Valid options:
+ * details - {Boolean} Return details from the distance calculation.
+ * Default is false.
+ * edge - {Boolean} Calculate the distance from this geometry to the
+ * nearest edge of the target geometry. Default is true. If true,
+ * calling distanceTo from a geometry that is wholly contained within
+ * the target will result in a non-zero distance. If false, whenever
+ * geometries intersect, calling distanceTo will return 0. If false,
+ * details cannot be returned.
+ *
+ * Returns:
+ * {Number | Object} The distance between this geometry and the target.
+ * If details is true, the return will be an object with distance,
+ * x0, y0, x1, and y1 properties. The x0 and y0 properties represent
+ * the coordinates of the closest point on this geometry. The x1 and y1
+ * properties represent the coordinates of the closest point on the
+ * target geometry.
+ */
+ distanceTo: function(geometry, options) {
+ var edge = !(options && options.edge === false);
+ var result;
+ // this is the case where we might not be looking for distance to edge
+ if(!edge && this.intersects(geometry)) {
+ result = 0;
+ } else {
+ result = OpenLayers.Geometry.Collection.prototype.distanceTo.apply(
+ this, [geometry, options]
+ );
+ }
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Polygon"
+});
+
+/**
+ * APIMethod: createRegularPolygon
+ * Create a regular polygon around a radius. Useful for creating circles
+ * and the like.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.Geometry.Point>} center of polygon.
+ * radius - {Float} distance to vertex, in map units.
+ * sides - {Integer} Number of sides. 20 approximates a circle.
+ * rotation - {Float} original angle of rotation, in degrees.
+ */
+OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {
+ var angle = Math.PI * ((1/sides) - (1/2));
+ if(rotation) {
+ angle += (rotation / 180) * Math.PI;
+ }
+ var rotatedAngle, x, y;
+ var points = [];
+ for(var i=0; i<sides; ++i) {
+ rotatedAngle = angle + (i * 2 * Math.PI / sides);
+ x = origin.x + (radius * Math.cos(rotatedAngle));
+ y = origin.y + (radius * Math.sin(rotatedAngle));
+ points.push(new OpenLayers.Geometry.Point(x, y));
+ }
+ var ring = new OpenLayers.Geometry.LinearRing(points);
+ return new OpenLayers.Geometry.Polygon([ring]);
+};
diff --git a/misc/openlayers/lib/OpenLayers/Handler.js b/misc/openlayers/lib/OpenLayers/Handler.js
new file mode 100644
index 0000000..0fef88e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler.js
@@ -0,0 +1,325 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Handler
+ * Base class to construct a higher-level handler for event sequences. All
+ * handlers have activate and deactivate methods. In addition, they have
+ * methods named like browser events. When a handler is activated, any
+ * additional methods named like a browser event is registered as a
+ * listener for the corresponding event. When a handler is deactivated,
+ * those same methods are unregistered as event listeners.
+ *
+ * Handlers also typically have a callbacks object with keys named like
+ * the abstracted events or event sequences that they are in charge of
+ * handling. The controls that wrap handlers define the methods that
+ * correspond to these abstract events - so instead of listening for
+ * individual browser events, they only listen for the abstract events
+ * defined by the handler.
+ *
+ * Handlers are created by controls, which ultimately have the responsibility
+ * of making changes to the the state of the application. Handlers
+ * themselves may make temporary changes, but in general are expected to
+ * return the application in the same state that they found it.
+ */
+OpenLayers.Handler = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: control
+ * {<OpenLayers.Control>}. The control that initialized this handler. The
+ * control is assumed to have a valid map property - that map is used
+ * in the handler's own setMap method.
+ */
+ control: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>}
+ */
+ map: null,
+
+ /**
+ * APIProperty: keyMask
+ * {Integer} Use bitwise operators and one or more of the OpenLayers.Handler
+ * constants to construct a keyMask. The keyMask is used by
+ * <checkModifiers>. If the keyMask matches the combination of keys
+ * down on an event, checkModifiers returns true.
+ *
+ * Example:
+ * (code)
+ * // handler only responds if the Shift key is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT;
+ *
+ * // handler only responds if Ctrl-Shift is down
+ * handler.keyMask = OpenLayers.Handler.MOD_SHIFT |
+ * OpenLayers.Handler.MOD_CTRL;
+ * (end)
+ */
+ keyMask: null,
+
+ /**
+ * Property: active
+ * {Boolean}
+ */
+ active: false,
+
+ /**
+ * Property: evt
+ * {Event} This property references the last event handled by the handler.
+ * Note that this property is not part of the stable API. Use of the
+ * evt property should be restricted to controls in the library
+ * or other applications that are willing to update with changes to
+ * the OpenLayers code.
+ */
+ evt: null,
+
+ /**
+ * Property: touch
+ * {Boolean} Indicates the support of touch events. When touch events are
+ * started touch will be true and all mouse related listeners will do
+ * nothing.
+ */
+ touch: false,
+
+ /**
+ * Constructor: OpenLayers.Handler
+ * Construct a handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that initialized this
+ * handler. The control is assumed to have a valid map property; that
+ * map is used in the handler's own setMap method. If a map property
+ * is present in the options argument it will be used instead.
+ * callbacks - {Object} An object whose properties correspond to abstracted
+ * events or sequences of browser events. The values for these
+ * properties are functions defined by the control that get called by
+ * the handler.
+ * options - {Object} An optional object whose properties will be set on
+ * the handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Util.extend(this, options);
+ this.control = control;
+ this.callbacks = callbacks;
+
+ var map = this.map || control.map;
+ if (map) {
+ this.setMap(map);
+ }
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ this.map = map;
+ },
+
+ /**
+ * Method: checkModifiers
+ * Check the keyMask on the handler. If no <keyMask> is set, this always
+ * returns true. If a <keyMask> is set and it matches the combination
+ * of keys down on an event, this returns true.
+ *
+ * Returns:
+ * {Boolean} The keyMask matches the keys down on an event.
+ */
+ checkModifiers: function (evt) {
+ if(this.keyMask == null) {
+ return true;
+ }
+ /* calculate the keyboard modifier mask for this event */
+ var keyModifiers =
+ (evt.shiftKey ? OpenLayers.Handler.MOD_SHIFT : 0) |
+ (evt.ctrlKey ? OpenLayers.Handler.MOD_CTRL : 0) |
+ (evt.altKey ? OpenLayers.Handler.MOD_ALT : 0) |
+ (evt.metaKey ? OpenLayers.Handler.MOD_META : 0);
+
+ /* if it differs from the handler object's key mask,
+ bail out of the event handler */
+ return (keyModifiers == this.keyMask);
+ },
+
+ /**
+ * APIMethod: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean} The handler was activated.
+ */
+ activate: function() {
+ if(this.active) {
+ return false;
+ }
+ // register for event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.register(events[i], this[events[i]]);
+ }
+ }
+ this.active = true;
+ return true;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler. Returns false if the handler was already inactive.
+ *
+ * Returns:
+ * {Boolean} The handler was deactivated.
+ */
+ deactivate: function() {
+ if(!this.active) {
+ return false;
+ }
+ // unregister event handlers defined on this class.
+ var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ this.touch = false;
+ this.active = false;
+ return true;
+ },
+
+ /**
+ * Method: startTouch
+ * Start touch events, this method must be called by subclasses in
+ * "touchstart" method. When touch events are started <touch> will be
+ * true and all mouse related listeners will do nothing.
+ */
+ startTouch: function() {
+ if (!this.touch) {
+ this.touch = true;
+ var events = [
+ "mousedown", "mouseup", "mousemove", "click", "dblclick",
+ "mouseout"
+ ];
+ for (var i=0, len=events.length; i<len; i++) {
+ if (this[events[i]]) {
+ this.unregister(events[i], this[events[i]]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array(*)} An array of arguments (any type) with which to call
+ * the callback (defined by the control).
+ */
+ callback: function (name, args) {
+ if (name && this.callbacks[name]) {
+ this.callbacks[name].apply(this.control, args);
+ }
+ },
+
+ /**
+ * Method: register
+ * register an event on the map
+ */
+ register: function (name, method) {
+ // TODO: deal with registerPriority in 3.0
+ this.map.events.registerPriority(name, this, method);
+ this.map.events.registerPriority(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: unregister
+ * unregister an event from the map
+ */
+ unregister: function (name, method) {
+ this.map.events.unregister(name, this, method);
+ this.map.events.unregister(name, this, this.setEvent);
+ },
+
+ /**
+ * Method: setEvent
+ * With each registered browser event, the handler sets its own evt
+ * property. This property can be accessed by controls if needed
+ * to get more information about the event that the handler is
+ * processing.
+ *
+ * This allows modifier keys on the event to be checked (alt, shift, ctrl,
+ * and meta cannot be checked with the keyboard handler). For a
+ * control to determine which modifier keys are associated with the
+ * event that a handler is currently processing, it should access
+ * (code)handler.evt.altKey || handler.evt.shiftKey ||
+ * handler.evt.ctrlKey || handler.evt.metaKey(end).
+ *
+ * Parameters:
+ * evt - {Event} The browser event.
+ */
+ setEvent: function(evt) {
+ this.evt = evt;
+ return true;
+ },
+
+ /**
+ * Method: destroy
+ * Deconstruct the handler.
+ */
+ destroy: function () {
+ // unregister event listeners
+ this.deactivate();
+ // eliminate circular references
+ this.control = this.map = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler"
+});
+
+/**
+ * Constant: OpenLayers.Handler.MOD_NONE
+ * If set as the <keyMask>, <checkModifiers> returns false if any key is down.
+ */
+OpenLayers.Handler.MOD_NONE = 0;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_SHIFT
+ * If set as the <keyMask>, <checkModifiers> returns false if Shift is down.
+ */
+OpenLayers.Handler.MOD_SHIFT = 1;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_CTRL
+ * If set as the <keyMask>, <checkModifiers> returns false if Ctrl is down.
+ */
+OpenLayers.Handler.MOD_CTRL = 2;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_ALT
+ * If set as the <keyMask>, <checkModifiers> returns false if Alt is down.
+ */
+OpenLayers.Handler.MOD_ALT = 4;
+
+/**
+ * Constant: OpenLayers.Handler.MOD_META
+ * If set as the <keyMask>, <checkModifiers> returns false if Cmd is down.
+ */
+OpenLayers.Handler.MOD_META = 8;
+
+
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Box.js b/misc/openlayers/lib/OpenLayers/Handler/Box.js
new file mode 100644
index 0000000..9d3289a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Box.js
@@ -0,0 +1,244 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Box
+ * Handler for dragging a rectangle across the map. Box is displayed
+ * on mouse down, moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Box = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: dragHandler
+ * {<OpenLayers.Handler.Drag>}
+ */
+ dragHandler: null,
+
+ /**
+ * APIProperty: boxDivClassName
+ * {String} The CSS class to use for drawing the box. Default is
+ * olHandlerBoxZoomBox
+ */
+ boxDivClassName: 'olHandlerBoxZoomBox',
+
+ /**
+ * Property: boxOffsets
+ * {Object} Caches box offsets from css. This is used by the getBoxOffsets
+ * method.
+ */
+ boxOffsets: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Box
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object}
+ *
+ * Named callbacks:
+ * start - Called when the box drag operation starts.
+ * done - Called when the box drag operation is finished.
+ * The callback should expect to receive a single argument, the box
+ * bounds or a pixel. If the box dragging didn't span more than a 5
+ * pixel distance, a pixel will be returned instead of a bounds object.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ this.dragHandler = new OpenLayers.Handler.Drag(
+ this,
+ {
+ down: this.startBox,
+ move: this.moveBox,
+ out: this.removeBox,
+ up: this.endBox
+ },
+ {keyMask: this.keyMask}
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ if (this.dragHandler) {
+ this.dragHandler.destroy();
+ this.dragHandler = null;
+ }
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function (map) {
+ OpenLayers.Handler.prototype.setMap.apply(this, arguments);
+ if (this.dragHandler) {
+ this.dragHandler.setMap(map);
+ }
+ },
+
+ /**
+ * Method: startBox
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>}
+ */
+ startBox: function (xy) {
+ this.callback("start", []);
+ this.zoomBox = OpenLayers.Util.createDiv('zoomBox', {
+ x: -9999, y: -9999
+ });
+ this.zoomBox.className = this.boxDivClassName;
+ this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+
+ this.map.viewPortDiv.appendChild(this.zoomBox);
+
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDrawBox"
+ );
+ },
+
+ /**
+ * Method: moveBox
+ */
+ moveBox: function (xy) {
+ var startX = this.dragHandler.start.x;
+ var startY = this.dragHandler.start.y;
+ var deltaX = Math.abs(startX - xy.x);
+ var deltaY = Math.abs(startY - xy.y);
+
+ var offset = this.getBoxOffsets();
+ this.zoomBox.style.width = (deltaX + offset.width + 1) + "px";
+ this.zoomBox.style.height = (deltaY + offset.height + 1) + "px";
+ this.zoomBox.style.left = (xy.x < startX ?
+ startX - deltaX - offset.left : startX - offset.left) + "px";
+ this.zoomBox.style.top = (xy.y < startY ?
+ startY - deltaY - offset.top : startY - offset.top) + "px";
+ },
+
+ /**
+ * Method: endBox
+ */
+ endBox: function(end) {
+ var result;
+ if (Math.abs(this.dragHandler.start.x - end.x) > 5 ||
+ Math.abs(this.dragHandler.start.y - end.y) > 5) {
+ var start = this.dragHandler.start;
+ var top = Math.min(start.y, end.y);
+ var bottom = Math.max(start.y, end.y);
+ var left = Math.min(start.x, end.x);
+ var right = Math.max(start.x, end.x);
+ result = new OpenLayers.Bounds(left, bottom, right, top);
+ } else {
+ result = this.dragHandler.start.clone(); // i.e. OL.Pixel
+ }
+ this.removeBox();
+
+ this.callback("done", [result]);
+ },
+
+ /**
+ * Method: removeBox
+ * Remove the zoombox from the screen and nullify our reference to it.
+ */
+ removeBox: function() {
+ this.map.viewPortDiv.removeChild(this.zoomBox);
+ this.zoomBox = null;
+ this.boxOffsets = null;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDrawBox"
+ );
+
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function () {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragHandler.activate();
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function () {
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ if (this.dragHandler.deactivate()) {
+ if (this.zoomBox) {
+ this.removeBox();
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getBoxOffsets
+ * Determines border offsets for a box, according to the box model.
+ *
+ * Returns:
+ * {Object} an object with the following offsets:
+ * - left
+ * - right
+ * - top
+ * - bottom
+ * - width
+ * - height
+ */
+ getBoxOffsets: function() {
+ if (!this.boxOffsets) {
+ // Determine the box model. If the testDiv's clientWidth is 3, then
+ // the borders are outside and we are dealing with the w3c box
+ // model. Otherwise, the browser uses the traditional box model and
+ // the borders are inside the box bounds, leaving us with a
+ // clientWidth of 1.
+ var testDiv = document.createElement("div");
+ //testDiv.style.visibility = "hidden";
+ testDiv.style.position = "absolute";
+ testDiv.style.border = "1px solid black";
+ testDiv.style.width = "3px";
+ document.body.appendChild(testDiv);
+ var w3cBoxModel = testDiv.clientWidth == 3;
+ document.body.removeChild(testDiv);
+
+ var left = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+ "border-left-width"));
+ var right = parseInt(OpenLayers.Element.getStyle(
+ this.zoomBox, "border-right-width"));
+ var top = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+ "border-top-width"));
+ var bottom = parseInt(OpenLayers.Element.getStyle(
+ this.zoomBox, "border-bottom-width"));
+ this.boxOffsets = {
+ left: left,
+ right: right,
+ top: top,
+ bottom: bottom,
+ width: w3cBoxModel === false ? left + right : 0,
+ height: w3cBoxModel === false ? top + bottom : 0
+ };
+ }
+ return this.boxOffsets;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Box"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Click.js b/misc/openlayers/lib/OpenLayers/Handler/Click.js
new file mode 100644
index 0000000..94a8444
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Click.js
@@ -0,0 +1,505 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Click
+ * A handler for mouse clicks. The intention of this handler is to give
+ * controls more flexibility with handling clicks. Browsers trigger
+ * click events twice for a double-click. In addition, the mousedown,
+ * mousemove, mouseup sequence fires a click event. With this handler,
+ * controls can decide whether to ignore clicks associated with a double
+ * click. By setting a <pixelTolerance>, controls can also ignore clicks
+ * that include a drag. Create a new instance with the
+ * <OpenLayers.Handler.Click> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Click = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * APIProperty: delay
+ * {Number} Number of milliseconds between clicks before the event is
+ * considered a double-click.
+ */
+ delay: 300,
+
+ /**
+ * APIProperty: single
+ * {Boolean} Handle single clicks. Default is true. If false, clicks
+ * will not be reported. If true, single-clicks will be reported.
+ */
+ single: true,
+
+ /**
+ * APIProperty: double
+ * {Boolean} Handle double-clicks. Default is false.
+ */
+ 'double': false,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between mouseup and mousedown for an
+ * event to be considered a click. Default is 0. If set to an
+ * integer value, clicks with a drag greater than the value will be
+ * ignored. This property can only be set when the handler is
+ * constructed.
+ */
+ pixelTolerance: 0,
+
+ /**
+ * APIProperty: dblclickTolerance
+ * {Number} Maximum distance in pixels between clicks for a sequence of
+ * events to be considered a double click. Default is 13. If the
+ * distance between two clicks is greater than this value, a double-
+ * click will not be fired.
+ */
+ dblclickTolerance: 13,
+
+ /**
+ * APIProperty: stopSingle
+ * {Boolean} Stop other listeners from being notified of clicks. Default
+ * is false. If true, any listeners registered before this one for
+ * click or rightclick events will not be notified.
+ */
+ stopSingle: false,
+
+ /**
+ * APIProperty: stopDouble
+ * {Boolean} Stop other listeners from being notified of double-clicks.
+ * Default is false. If true, any click listeners registered before
+ * this one will not be notified of *any* double-click events.
+ *
+ * The one caveat with stopDouble is that given a map with two click
+ * handlers, one with stopDouble true and the other with stopSingle
+ * true, the stopSingle handler should be activated last to get
+ * uniform cross-browser performance. Since IE triggers one click
+ * with a dblclick and FF triggers two, if a stopSingle handler is
+ * activated first, all it gets in IE is a single click when the
+ * second handler stops propagation on the dblclick.
+ */
+ stopDouble: false,
+
+ /**
+ * Property: timerId
+ * {Number} The id of the timeout waiting to clear the <delayedCall>.
+ */
+ timerId: null,
+
+ /**
+ * Property: down
+ * {Object} Object that store relevant information about the last
+ * mousedown or touchstart. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ down: null,
+
+ /**
+ * Property: last
+ * {Object} Object that store relevant information about the last
+ * mousemove or touchmove. Its 'xy' OpenLayers.Pixel property gives
+ * the average location of the mouse/touch event. Its 'touches'
+ * property records clientX/clientY of each touches.
+ */
+ last: null,
+
+ /**
+ * Property: first
+ * {Object} When waiting for double clicks, this object will store
+ * information about the first click in a two click sequence.
+ */
+ first: null,
+
+ /**
+ * Property: rightclickTimerId
+ * {Number} The id of the right mouse timeout waiting to clear the
+ * <delayedEvent>.
+ */
+ rightclickTimerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Click
+ * Create a new click handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handler's setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object with keys corresponding to callbacks
+ * that will be called by the handler. The callbacks should
+ * expect to recieve a single argument, the click event.
+ * Callbacks for 'click' and 'dblclick' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchmove
+ * Store position of last move, because touchend event can have
+ * an empty "touches" property.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchmove: function(evt) {
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Correctly set event xy property, and add lastTouches to have
+ * touches property from last touchstart or touchmove
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ touchend: function(evt) {
+ // touchstart may not have been allowed to propagate
+ if (this.down) {
+ evt.xy = this.last.xy;
+ evt.lastTouches = this.last.touches;
+ this.handleSingle(evt);
+ this.down = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mousedown: function(evt) {
+ this.down = this.getEventInfo(evt);
+ this.last = this.getEventInfo(evt);
+ return true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup. Installed to support collection of right mouse events.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mouseup: function (evt) {
+ var propagate = true;
+
+ // Collect right mouse clicks from the mouseup
+ // IE - ignores the second right click in mousedown so using
+ // mouseup instead
+ if (this.checkModifiers(evt) && this.control.handleRightClicks &&
+ OpenLayers.Event.isRightClick(evt)) {
+ propagate = this.rightclick(evt);
+ }
+
+ return propagate;
+ },
+
+ /**
+ * Method: rightclick
+ * Handle rightclick. For a dblrightclick, we get two clicks so we need
+ * to always register for dblrightclick to properly handle single
+ * clicks.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ rightclick: function(evt) {
+ if(this.passesTolerance(evt)) {
+ if(this.rightclickTimerId != null) {
+ //Second click received before timeout this must be
+ // a double click
+ this.clearTimer();
+ this.callback('dblrightclick', [evt]);
+ return !this.stopDouble;
+ } else {
+ //Set the rightclickTimerId, send evt only if double is
+ // true else trigger single
+ var clickEvent = this['double'] ?
+ OpenLayers.Util.extend({}, evt) :
+ this.callback('rightclick', [evt]);
+
+ var delayedRightCall = OpenLayers.Function.bind(
+ this.delayedRightCall,
+ this,
+ clickEvent
+ );
+ this.rightclickTimerId = window.setTimeout(
+ delayedRightCall, this.delay
+ );
+ }
+ }
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: delayedRightCall
+ * Sets <rightclickTimerId> to null. And optionally triggers the
+ * rightclick callback if evt is set.
+ */
+ delayedRightCall: function(evt) {
+ this.rightclickTimerId = null;
+ if (evt) {
+ this.callback('rightclick', [evt]);
+ }
+ },
+
+ /**
+ * Method: click
+ * Handle click events from the browser. This is registered as a listener
+ * for click events and should not be called from other events in this
+ * handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ click: function(evt) {
+ if (!this.last) {
+ this.last = this.getEventInfo(evt);
+ }
+ this.handleSingle(evt);
+ return !this.stopSingle;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. For a dblclick, we get two clicks in some browsers
+ * (FF) and one in others (IE). So we need to always register for
+ * dblclick to properly handle single clicks. This method is registered
+ * as a listener for the dblclick browser event. It should *not* be
+ * called by other methods in this handler.
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ dblclick: function(evt) {
+ this.handleDouble(evt);
+ return !this.stopDouble;
+ },
+
+ /**
+ * Method: handleDouble
+ * Handle double-click sequence.
+ */
+ handleDouble: function(evt) {
+ if (this.passesDblclickTolerance(evt)) {
+ if (this["double"]) {
+ this.callback("dblclick", [evt]);
+ }
+ // to prevent a dblclick from firing the click callback in IE
+ this.clearTimer();
+ }
+ },
+
+ /**
+ * Method: handleSingle
+ * Handle single click sequence.
+ */
+ handleSingle: function(evt) {
+ if (this.passesTolerance(evt)) {
+ if (this.timerId != null) {
+ // already received a click
+ if (this.last.touches && this.last.touches.length === 1) {
+ // touch device, no dblclick event - this may be a double
+ if (this["double"]) {
+ // on Android don't let the browser zoom on the page
+ OpenLayers.Event.preventDefault(evt);
+ }
+ this.handleDouble(evt);
+ }
+ // if we're not in a touch environment we clear the click timer
+ // if we've got a second touch, we'll get two touchend events
+ if (!this.last.touches || this.last.touches.length !== 2) {
+ this.clearTimer();
+ }
+ } else {
+ // remember the first click info so we can compare to the second
+ this.first = this.getEventInfo(evt);
+ // set the timer, send evt only if single is true
+ //use a clone of the event object because it will no longer
+ //be a valid event object in IE in the timer callback
+ var clickEvent = this.single ?
+ OpenLayers.Util.extend({}, evt) : null;
+ this.queuePotentialClick(clickEvent);
+ }
+ }
+ },
+
+ /**
+ * Method: queuePotentialClick
+ * This method is separated out largely to make testing easier (so we
+ * don't have to override window.setTimeout)
+ */
+ queuePotentialClick: function(evt) {
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance. Note
+ * that the pixel tolerance check only works if mousedown events get to
+ * the listeners registered here. If they are stopped by other elements,
+ * the <pixelTolerance> will have no effect here (this method will always
+ * return true).
+ *
+ * Returns:
+ * {Boolean} The click is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(evt) {
+ var passes = true;
+ if (this.pixelTolerance != null && this.down && this.down.xy) {
+ passes = this.pixelTolerance >= this.down.xy.distanceTo(evt.xy);
+ // for touch environments, we also enforce that all touches
+ // start and end within the given tolerance to be considered a click
+ if (passes && this.touch &&
+ this.down.touches.length === this.last.touches.length) {
+ // the touchend event doesn't come with touches, so we check
+ // down and last
+ for (var i=0, ii=this.down.touches.length; i<ii; ++i) {
+ if (this.getTouchDistance(
+ this.down.touches[i],
+ this.last.touches[i]
+ ) > this.pixelTolerance) {
+ passes = false;
+ break;
+ }
+ }
+ }
+ }
+ return passes;
+ },
+
+ /**
+ * Method: getTouchDistance
+ *
+ * Returns:
+ * {Boolean} The pixel displacement between two touches.
+ */
+ getTouchDistance: function(from, to) {
+ return Math.sqrt(
+ Math.pow(from.clientX - to.clientX, 2) +
+ Math.pow(from.clientY - to.clientY, 2)
+ );
+ },
+
+ /**
+ * Method: passesDblclickTolerance
+ * Determine whether the event is within the optional double-cick pixel
+ * tolerance.
+ *
+ * Returns:
+ * {Boolean} The click is within the double-click pixel tolerance.
+ */
+ passesDblclickTolerance: function(evt) {
+ var passes = true;
+ if (this.down && this.first) {
+ passes = this.down.xy.distanceTo(this.first.xy) <= this.dblclickTolerance;
+ }
+ return passes;
+ },
+
+ /**
+ * Method: clearTimer
+ * Clear the timer and set <timerId> to null.
+ */
+ clearTimer: function() {
+ if (this.timerId != null) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ if (this.rightclickTimerId != null) {
+ window.clearTimeout(this.rightclickTimerId);
+ this.rightclickTimerId = null;
+ }
+ },
+
+ /**
+ * Method: delayedCall
+ * Sets <timerId> to null. And optionally triggers the click callback if
+ * evt is set.
+ */
+ delayedCall: function(evt) {
+ this.timerId = null;
+ if (evt) {
+ this.callback("click", [evt]);
+ }
+ },
+
+ /**
+ * Method: getEventInfo
+ * This method allows us to store event information without storing the
+ * actual event. In touch devices (at least), the same event is
+ * modified between touchstart, touchmove, and touchend.
+ *
+ * Returns:
+ * {Object} An object with event related info.
+ */
+ getEventInfo: function(evt) {
+ var touches;
+ if (evt.touches) {
+ var len = evt.touches.length;
+ touches = new Array(len);
+ var touch;
+ for (var i=0; i<len; i++) {
+ touch = evt.touches[i];
+ touches[i] = {
+ clientX: touch.olClientX,
+ clientY: touch.olClientY
+ };
+ }
+ }
+ return {
+ xy: evt.xy,
+ touches: touches
+ };
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.clearTimer();
+ this.down = null;
+ this.first = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Click"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Drag.js b/misc/openlayers/lib/OpenLayers/Handler/Drag.js
new file mode 100644
index 0000000..8c3cb55
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Drag.js
@@ -0,0 +1,547 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Drag
+ * The drag handler is used to deal with sequences of browser events related
+ * to dragging. The handler is used by controls that want to know when
+ * a drag sequence begins, when a drag is happening, and when it has
+ * finished.
+ *
+ * Controls that use the drag handler typically construct it with callbacks
+ * for 'down', 'move', and 'done'. Callbacks for these keys are called
+ * when the drag begins, with each move, and when the drag is done. In
+ * addition, controls can have callbacks keyed to 'up' and 'out' if they
+ * care to differentiate between the types of events that correspond with
+ * the end of a drag sequence. If no drag actually occurs (no mouse move)
+ * the 'down' and 'up' callbacks will be called, but not the 'done'
+ * callback.
+ *
+ * Create a new drag handler with the <OpenLayers.Handler.Drag> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Drag = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a mousedown or touchstart event is received, we want to
+ * record it, but not set 'dragging' until the mouse moves after starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of mousedown events from getting to listeners
+ * on the same element. Default is true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: dragging
+ * {Boolean}
+ */
+ dragging: false,
+
+ /**
+ * Property: last
+ * {<OpenLayers.Pixel>} The last pixel location of the drag.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {<OpenLayers.Pixel>} The first pixel location of the drag.
+ */
+ start: null,
+
+ /**
+ * Property: lastMoveEvt
+ * {Object} The last mousemove event that occurred. Used to
+ * position the map correctly when our "delay drag"
+ * timeout expired.
+ */
+ lastMoveEvt: null,
+
+ /**
+ * Property: oldOnselectstart
+ * {Function}
+ */
+ oldOnselectstart: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase performance, an interval (in
+ * milliseconds) can be set to reduce the number of drag events
+ * called. If set, a new drag event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: timeoutId
+ * {String} The id of the timeout used for the mousedown interval.
+ * This is "private", and should be left alone.
+ */
+ timeoutId: null,
+
+ /**
+ * APIProperty: documentDrag
+ * {Boolean} If set to true, the handler will also handle mouse moves when
+ * the cursor has moved out of the map viewport. Default is false.
+ */
+ documentDrag: false,
+
+ /**
+ * Property: documentEvents
+ * {Boolean} Are we currently observing document events?
+ */
+ documentEvents: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Drag
+ * Returns OpenLayers.Handler.Drag
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'move' and 'done' are supported. You can also speficy
+ * callbacks for 'down', 'up', and 'out' to respond to those events.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+
+ if (this.documentDrag === true) {
+ var me = this;
+ this._docMove = function(evt) {
+ me.mousemove({
+ xy: {x: evt.clientX, y: evt.clientY},
+ element: document
+ });
+ };
+ this._docUp = function(evt) {
+ me.mouseup({xy: {x: evt.clientX, y: evt.clientY}});
+ };
+ }
+ },
+
+
+ /**
+ * Method: dragstart
+ * This private method is factorized from mousedown and touchstart methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragstart: function (evt) {
+ var propagate = true;
+ this.dragging = false;
+ if (this.checkModifiers(evt) &&
+ (OpenLayers.Event.isLeftClick(evt) ||
+ OpenLayers.Event.isSingleTouch(evt))) {
+ this.started = true;
+ this.start = evt.xy;
+ this.last = evt.xy;
+ OpenLayers.Element.addClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.down(evt);
+ this.callback("down", [evt.xy]);
+
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart ?
+ document.onselectstart : OpenLayers.Function.True;
+ }
+ document.onselectstart = OpenLayers.Function.False;
+
+ propagate = !this.stopDown;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: dragmove
+ * This private method is factorized from mousemove and touchmove methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragmove: function (evt) {
+ this.lastMoveEvt = evt;
+ if (this.started && !this.timeoutId && (evt.xy.x != this.last.x ||
+ evt.xy.y != this.last.y)) {
+ if(this.documentDrag === true && this.documentEvents) {
+ if(evt.element === document) {
+ this.adjustXY(evt);
+ // do setEvent manually because the documentEvents are not
+ // registered with the map
+ this.setEvent(evt);
+ } else {
+ this.removeDocumentEvents();
+ }
+ }
+ if (this.interval > 0) {
+ this.timeoutId = setTimeout(
+ OpenLayers.Function.bind(this.removeTimeout, this),
+ this.interval);
+ }
+ this.dragging = true;
+
+ this.move(evt);
+ this.callback("move", [evt.xy]);
+ if(!this.oldOnselectstart) {
+ this.oldOnselectstart = document.onselectstart;
+ document.onselectstart = OpenLayers.Function.False;
+ }
+ this.last = evt.xy;
+ }
+ return true;
+ },
+
+ /**
+ * Method: dragend
+ * This private method is factorized from mouseup and touchend methods
+ *
+ * Parameters:
+ * evt - {Event} The event
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ dragend: function (evt) {
+ if (this.started) {
+ if(this.documentDrag === true && this.documentEvents) {
+ this.adjustXY(evt);
+ this.removeDocumentEvents();
+ }
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.up(evt);
+ this.callback("up", [evt.xy]);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ document.onselectstart = this.oldOnselectstart;
+ }
+ return true;
+ },
+
+ /**
+ * The four methods below (down, move, up, and out) are used by subclasses
+ * to do their own processing related to these mouse events.
+ */
+
+ /**
+ * Method: down
+ * This method is called during the handling of the mouse down event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse down event
+ */
+ down: function(evt) {
+ },
+
+ /**
+ * Method: move
+ * This method is called during the handling of the mouse move event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse move event
+ *
+ */
+ move: function(evt) {
+ },
+
+ /**
+ * Method: up
+ * This method is called during the handling of the mouse up event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ },
+
+ /**
+ * Method: out
+ * This method is called during the handling of the mouse out event.
+ * Subclasses can do their own processing here.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ },
+
+ /**
+ * The methods below are part of the magic of event handling. Because
+ * they are named like browser events, they are registered as listeners
+ * for the events they represent.
+ */
+
+ /**
+ * Method: mousedown
+ * Handle mousedown events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousedown: function(evt) {
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return this.dragstart(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mousemove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ return this.dragmove(evt);
+ },
+
+ /**
+ * Method: removeTimeout
+ * Private. Called by mousemove() to remove the drag timeout.
+ */
+ removeTimeout: function() {
+ this.timeoutId = null;
+ // if timeout expires while we're still dragging (mouseup
+ // hasn't occurred) then call mousemove to move to the
+ // correct position
+ if(this.dragging) {
+ this.mousemove(this.lastMoveEvt);
+ }
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseup: function(evt) {
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ // override evt.xy with last position since touchend does not have
+ // any touch position
+ evt.xy = this.last;
+ return this.dragend(evt);
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouseout events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ mouseout: function (evt) {
+ if (this.started && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if(this.documentDrag === true) {
+ this.addDocumentEvents();
+ } else {
+ var dragged = (this.start != this.last);
+ this.started = false;
+ this.dragging = false;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ this.out(evt);
+ this.callback("out", []);
+ if(dragged) {
+ this.callback("done", [evt.xy]);
+ }
+ if(document.onselectstart) {
+ document.onselectstart = this.oldOnselectstart;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Method: click
+ * The drag handler captures the click event. If something else registers
+ * for clicks on the same element, its listener will not be called
+ * after a drag.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ click: function (evt) {
+ // let the click event propagate only if the mouse moved
+ return (this.start == this.last);
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.dragging = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.dragging = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ OpenLayers.Element.removeClass(
+ this.map.viewPortDiv, "olDragDown"
+ );
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: adjustXY
+ * Converts event coordinates that are relative to the document body to
+ * ones that are relative to the map viewport. The latter is the default in
+ * OpenLayers.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ adjustXY: function(evt) {
+ var pos = OpenLayers.Util.pagePosition(this.map.viewPortDiv);
+ evt.xy.x -= pos[0];
+ evt.xy.y -= pos[1];
+ },
+
+ /**
+ * Method: addDocumentEvents
+ * Start observing document events when documentDrag is true and the mouse
+ * cursor leaves the map viewport while dragging.
+ */
+ addDocumentEvents: function() {
+ OpenLayers.Element.addClass(document.body, "olDragDown");
+ this.documentEvents = true;
+ OpenLayers.Event.observe(document, "mousemove", this._docMove);
+ OpenLayers.Event.observe(document, "mouseup", this._docUp);
+ },
+
+ /**
+ * Method: removeDocumentEvents
+ * Stops observing document events when documentDrag is true and the mouse
+ * cursor re-enters the map viewport while dragging.
+ */
+ removeDocumentEvents: function() {
+ OpenLayers.Element.removeClass(document.body, "olDragDown");
+ this.documentEvents = false;
+ OpenLayers.Event.stopObserving(document, "mousemove", this._docMove);
+ OpenLayers.Event.stopObserving(document, "mouseup", this._docUp);
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Drag"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Feature.js b/misc/openlayers/lib/OpenLayers/Handler/Feature.js
new file mode 100644
index 0000000..39b86f2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Feature.js
@@ -0,0 +1,434 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature
+ * Handler to respond to mouse events related to a drawn feature. Callbacks
+ * with the following keys will be notified of the following events
+ * associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ * browser events target features that can be selected.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: EVENTMAP
+ * {Object} A object mapping the browser events to objects with callback
+ * keys for in and out.
+ */
+ EVENTMAP: {
+ 'click': {'in': 'click', 'out': 'clickout'},
+ 'mousemove': {'in': 'over', 'out': 'out'},
+ 'dblclick': {'in': 'dblclick', 'out': null},
+ 'mousedown': {'in': null, 'out': null},
+ 'mouseup': {'in': null, 'out': null},
+ 'touchstart': {'in': 'click', 'out': 'clickout'}
+ },
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+ */
+ feature: null,
+
+ /**
+ * Property: lastFeature
+ * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+ */
+ lastFeature: null,
+
+ /**
+ * Property: down
+ * {<OpenLayers.Pixel>} The location of the last mousedown.
+ */
+ down: null,
+
+ /**
+ * Property: up
+ * {<OpenLayers.Pixel>} The location of the last mouseup.
+ */
+ up: null,
+
+ /**
+ * Property: clickTolerance
+ * {Number} The number of pixels the mouse can move between mousedown
+ * and mouseup for the event to still be considered a click.
+ * Dragging the map should not trigger the click and clickout callbacks
+ * unless the map is moved by less than this tolerance. Defaults to 4.
+ */
+ clickTolerance: 4,
+
+ /**
+ * Property: geometryTypes
+ * To restrict dragging to a limited set of geometry types, send a list
+ * of strings corresponding to the geometry class names.
+ *
+ * @type Array(String)
+ */
+ geometryTypes: null,
+
+ /**
+ * Property: stopClick
+ * {Boolean} If stopClick is set to true, handled clicks do not
+ * propagate to other click listeners. Otherwise, handled clicks
+ * do propagate. Unhandled clicks always propagate, whatever the
+ * value of stopClick. Defaults to true.
+ */
+ stopClick: true,
+
+ /**
+ * Property: stopDown
+ * {Boolean} If stopDown is set to true, handled mousedowns do not
+ * propagate to other mousedown listeners. Otherwise, handled
+ * mousedowns do propagate. Unhandled mousedowns always propagate,
+ * whatever the value of stopDown. Defaults to true.
+ */
+ stopDown: true,
+
+ /**
+ * Property: stopUp
+ * {Boolean} If stopUp is set to true, handled mouseups do not
+ * propagate to other mouseup listeners. Otherwise, handled mouseups
+ * do propagate. Unhandled mouseups always propagate, whatever the
+ * value of stopUp. Defaults to false.
+ */
+ stopUp: false,
+
+ /**
+ * Constructor: OpenLayers.Handler.Feature
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * layer - {<OpenLayers.Layer.Vector>}
+ * callbacks - {Object} An object with a 'over' property whos value is
+ * a function to be called when the mouse is over a feature. The
+ * callback should expect to recieve a single argument, the feature.
+ * options - {Object}
+ */
+ initialize: function(control, layer, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+ this.layer = layer;
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ return OpenLayers.Event.isMultiTouch(evt) ?
+ true : this.mousedown(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events. We just prevent the browser default behavior,
+ * for Android Webkit not to select text when moving the finger after
+ * selecting a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ touchmove: function(evt) {
+ OpenLayers.Event.preventDefault(evt);
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mouse down. Stop propagation if a feature is targeted by this
+ * event (stops map dragging during feature selection).
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mousedown: function(evt) {
+ // Feature selection is only done with a left click. Other handlers may stop the
+ // propagation of left-click mousedown events but not right-click mousedown events.
+ // This mismatch causes problems when comparing the location of the down and up
+ // events in the click function so it is important ignore right-clicks.
+ if (OpenLayers.Event.isLeftClick(evt) || OpenLayers.Event.isSingleTouch(evt)) {
+ this.down = evt.xy;
+ }
+ return this.handle(evt) ? !this.stopDown : true;
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouse up. Stop propagation if a feature is targeted by this
+ * event.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ mouseup: function(evt) {
+ this.up = evt.xy;
+ return this.handle(evt) ? !this.stopUp : true;
+ },
+
+ /**
+ * Method: click
+ * Handle click. Call the "click" callback if click on a feature,
+ * or the "clickout" callback if click outside any feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ click: function(evt) {
+ return this.handle(evt) ? !this.stopClick : true;
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mouse moves. Call the "over" callback if moving in to a feature,
+ * or the "out" callback if moving out of a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ mousemove: function(evt) {
+ if (!this.callbacks['over'] && !this.callbacks['out']) {
+ return true;
+ }
+ this.handle(evt);
+ return true;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle dblclick. Call the "dblclick" callback if dblclick on a feature.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ dblclick: function(evt) {
+ return !this.handle(evt);
+ },
+
+ /**
+ * Method: geometryTypeMatches
+ * Return true if the geometry type of the passed feature matches
+ * one of the geometry types in the geometryTypes array.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Vector.Feature>}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ geometryTypeMatches: function(feature) {
+ return this.geometryTypes == null ||
+ OpenLayers.Util.indexOf(this.geometryTypes,
+ feature.geometry.CLASS_NAME) > -1;
+ },
+
+ /**
+ * Method: handle
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} The event occurred over a relevant feature.
+ */
+ handle: function(evt) {
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ var type = evt.type;
+ var handled = false;
+ var previouslyIn = !!(this.feature); // previously in a feature
+ var click = (type == "click" || type == "dblclick" || type == "touchstart");
+ this.feature = this.layer.getFeatureFromEvent(evt);
+ if(this.feature && !this.feature.layer) {
+ // feature has been destroyed
+ this.feature = null;
+ }
+ if(this.lastFeature && !this.lastFeature.layer) {
+ // last feature has been destroyed
+ this.lastFeature = null;
+ }
+ if(this.feature) {
+ if(type === "touchstart") {
+ // stop the event to prevent Android Webkit from
+ // "flashing" the map div
+ OpenLayers.Event.preventDefault(evt);
+ }
+ var inNew = (this.feature != this.lastFeature);
+ if(this.geometryTypeMatches(this.feature)) {
+ // in to a feature
+ if(previouslyIn && inNew) {
+ // out of last feature and in to another
+ if(this.lastFeature) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ this.triggerCallback(type, 'in', [this.feature]);
+ } else if(!previouslyIn || click) {
+ // in feature for the first time
+ this.triggerCallback(type, 'in', [this.feature]);
+ }
+ this.lastFeature = this.feature;
+ handled = true;
+ } else {
+ // not in to a feature
+ if(this.lastFeature && (previouslyIn && inNew || click)) {
+ // out of last feature for the first time
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ // next time the mouse goes in a feature whose geometry type
+ // doesn't match we don't want to call the 'out' callback
+ // again, so let's set this.feature to null so that
+ // previouslyIn will evaluate to false the next time
+ // we enter handle. Yes, a bit hackish...
+ this.feature = null;
+ }
+ } else if(this.lastFeature && (previouslyIn || click)) {
+ this.triggerCallback(type, 'out', [this.lastFeature]);
+ }
+ return handled;
+ },
+
+ /**
+ * Method: triggerCallback
+ * Call the callback keyed in the event map with the supplied arguments.
+ * For click and clickout, the <clickTolerance> is checked first.
+ *
+ * Parameters:
+ * type - {String}
+ */
+ triggerCallback: function(type, mode, args) {
+ var key = this.EVENTMAP[type][mode];
+ if(key) {
+ if(type == 'click' && this.up && this.down) {
+ // for click/clickout, only trigger callback if tolerance is met
+ var dpx = Math.sqrt(
+ Math.pow(this.up.x - this.down.x, 2) +
+ Math.pow(this.up.y - this.down.y, 2)
+ );
+ if(dpx <= this.clickTolerance) {
+ this.callback(key, args);
+ }
+ // we're done with this set of events now: clear the cached
+ // positions so we can't trip over them later (this can occur
+ // if one of the up/down events gets eaten before it gets to us
+ // but we still get the click)
+ this.up = this.down = null;
+ } else {
+ this.callback(key, args);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ * Turn on the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.moveLayerToTop();
+ this.map.events.on({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Turn off the handler. Returns false if the handler was already active.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.moveLayerBack();
+ this.feature = null;
+ this.lastFeature = null;
+ this.down = null;
+ this.up = null;
+ this.map.events.un({
+ "removelayer": this.handleMapEvents,
+ "changelayer": this.handleMapEvents,
+ scope: this
+ });
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleMapEvents
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleMapEvents: function(evt) {
+ if (evt.type == "removelayer" || evt.property == "order") {
+ this.moveLayerToTop();
+ }
+ },
+
+ /**
+ * Method: moveLayerToTop
+ * Moves the layer for this handler to the top, so mouse events can reach
+ * it.
+ */
+ moveLayerToTop: function() {
+ var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+ this.layer.getZIndex()) + 1;
+ this.layer.setZIndex(index);
+
+ },
+
+ /**
+ * Method: moveLayerBack
+ * Moves the layer back to the position determined by the map's layers
+ * array.
+ */
+ moveLayerBack: function() {
+ var index = this.layer.getZIndex() - 1;
+ if (index >= this.map.Z_INDEX_BASE['Feature']) {
+ this.layer.setZIndex(index);
+ } else {
+ this.map.setLayerZIndex(this.layer,
+ this.map.getLayerIndex(this.layer));
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Feature"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Hover.js b/misc/openlayers/lib/OpenLayers/Handler/Hover.js
new file mode 100644
index 0000000..18b81f4
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Hover.js
@@ -0,0 +1,180 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Hover
+ * The hover handler is to be used to emulate mouseovers on objects
+ * on the map that aren't DOM elements. For example one can use
+ * this handler to send WMS/GetFeatureInfo requests as the user
+ * moves the mouve over the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Hover = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * APIProperty: delay
+ * {Integer} - Number of milliseconds between mousemoves before
+ * the event is considered a hover. Default is 500.
+ */
+ delay: 500,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Integer} - Maximum number of pixels between mousemoves for
+ * an event to be considered a hover. Default is null.
+ */
+ pixelTolerance: null,
+
+ /**
+ * APIProperty: stopMove
+ * {Boolean} - Stop other listeners from being notified on mousemoves.
+ * Default is false.
+ */
+ stopMove: false,
+
+ /**
+ * Property: px
+ * {<OpenLayers.Pixel>} - The location of the last mousemove, expressed
+ * in pixels.
+ */
+ px: null,
+
+ /**
+ * Property: timerId
+ * {Number} - The id of the timer.
+ */
+ timerId: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Hover
+ * Construct a hover handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that initialized this
+ * handler. The control is assumed to have a valid map property; that
+ * map is used in the handler's own setMap method.
+ * callbacks - {Object} An object with keys corresponding to callbacks
+ * that will be called by the handler. The callbacks should
+ * expect to receive a single argument, the event. Callbacks for
+ * 'move', the mouse is moving, and 'pause', the mouse is pausing,
+ * are supported.
+ * options - {Object} An optional object whose properties will be set on
+ * the handler.
+ */
+
+ /**
+ * Method: mousemove
+ * Called when the mouse moves on the map.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mousemove: function(evt) {
+ if(this.passesTolerance(evt.xy)) {
+ this.clearTimer();
+ this.callback('move', [evt]);
+ this.px = evt.xy;
+ // clone the evt so original properties can be accessed even
+ // if the browser deletes them during the delay
+ evt = OpenLayers.Util.extend({}, evt);
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(this.delayedCall, this, evt),
+ this.delay
+ );
+ }
+ return !this.stopMove;
+ },
+
+ /**
+ * Method: mouseout
+ * Called when the mouse goes out of the map.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {Boolean} Continue propagating this event.
+ */
+ mouseout: function(evt) {
+ if (OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ this.clearTimer();
+ this.callback('move', [evt]);
+ }
+ return true;
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the mouse move is within the optional pixel tolerance.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Boolean} The mouse move is within the pixel tolerance.
+ */
+ passesTolerance: function(px) {
+ var passes = true;
+ if(this.pixelTolerance && this.px) {
+ var dpx = Math.sqrt(
+ Math.pow(this.px.x - px.x, 2) +
+ Math.pow(this.px.y - px.y, 2)
+ );
+ if(dpx < this.pixelTolerance) {
+ passes = false;
+ }
+ }
+ return passes;
+ },
+
+ /**
+ * Method: clearTimer
+ * Clear the timer and set <timerId> to null.
+ */
+ clearTimer: function() {
+ if(this.timerId != null) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ },
+
+ /**
+ * Method: delayedCall
+ * Triggers pause callback.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ delayedCall: function(evt) {
+ this.callback('pause', [evt]);
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.clearTimer();
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Hover"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Keyboard.js b/misc/openlayers/lib/OpenLayers/Handler/Keyboard.js
new file mode 100644
index 0000000..de7a464
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Keyboard.js
@@ -0,0 +1,117 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.handler.Keyboard
+ * A handler for keyboard events. Create a new instance with the
+ * <OpenLayers.Handler.Keyboard> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, {
+
+ /* http://www.quirksmode.org/js/keys.html explains key x-browser
+ key handling quirks in pretty nice detail */
+
+ /**
+ * Constant: KEY_EVENTS
+ * keydown, keypress, keyup
+ */
+ KEY_EVENTS: ["keydown", "keyup"],
+
+ /**
+ * Property: eventListener
+ * {Function}
+ */
+ eventListener: null,
+
+ /**
+ * Property: observeElement
+ * {DOMElement|String} The DOM element on which we listen for
+ * key events. Default to the document.
+ */
+ observeElement: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Keyboard
+ * Returns a new keyboard handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished. The callback should
+ * expect to recieve a single argument, the pixel location of the event.
+ * Callbacks for 'keydown', 'keypress', and 'keyup' are supported.
+ * options - {Object} Optional object whose properties will be set on the
+ * handler.
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ // cache the bound event listener method so it can be unobserved later
+ this.eventListener = OpenLayers.Function.bindAsEventListener(
+ this.handleKeyEvent, this
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.deactivate();
+ this.eventListener = null;
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function() {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.observeElement = this.observeElement || document;
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.observe(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+ OpenLayers.Event.stopObserving(
+ this.observeElement, this.KEY_EVENTS[i], this.eventListener);
+ }
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: handleKeyEvent
+ */
+ handleKeyEvent: function (evt) {
+ if (this.checkModifiers(evt)) {
+ this.callback(evt.type, [evt]);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Keyboard"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js b/misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js
new file mode 100644
index 0000000..c69dff3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/MouseWheel.js
@@ -0,0 +1,264 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.MouseWheel
+ * Handler for wheel up/down events.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.MouseWheel = OpenLayers.Class(OpenLayers.Handler, {
+ /**
+ * Property: wheelListener
+ * {function}
+ */
+ wheelListener: null,
+
+ /**
+ * Property: interval
+ * {Integer} In order to increase server performance, an interval (in
+ * milliseconds) can be set to reduce the number of up/down events
+ * called. If set, a new up/down event will not be set until the
+ * interval has passed.
+ * Defaults to 0, meaning no interval.
+ */
+ interval: 0,
+
+ /**
+ * Property: maxDelta
+ * {Integer} Maximum delta to collect before breaking from the current
+ * interval. In cumulative mode, this also limits the maximum delta
+ * returned from the handler. Default is Number.POSITIVE_INFINITY.
+ */
+ maxDelta: Number.POSITIVE_INFINITY,
+
+ /**
+ * Property: delta
+ * {Integer} When interval is set, delta collects the mousewheel z-deltas
+ * of the events that occur within the interval.
+ * See also the cumulative option
+ */
+ delta: 0,
+
+ /**
+ * Property: cumulative
+ * {Boolean} When interval is set: true to collect all the mousewheel
+ * z-deltas, false to only record the delta direction (positive or
+ * negative)
+ */
+ cumulative: true,
+
+ /**
+ * Constructor: OpenLayers.Handler.MouseWheel
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * callbacks - {Object} An object containing a single function to be
+ * called when the drag operation is finished.
+ * The callback should expect to recieve a single
+ * argument, the point geometry.
+ * options - {Object}
+ */
+ initialize: function(control, callbacks, options) {
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ this.wheelListener = OpenLayers.Function.bindAsEventListener(
+ this.onWheelEvent, this
+ );
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+ this.wheelListener = null;
+ },
+
+ /**
+ * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
+ */
+
+ /**
+ * Method: onWheelEvent
+ * Catch the wheel event and handle it xbrowserly
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ onWheelEvent: function(e){
+
+ // make sure we have a map and check keyboard modifiers
+ if (!this.map || !this.checkModifiers(e)) {
+ return;
+ }
+
+ // Ride up the element's DOM hierarchy to determine if it or any of
+ // its ancestors was:
+ // * specifically marked as scrollable (CSS overflow property)
+ // * one of our layer divs or a div marked as scrollable
+ // ('olScrollable' CSS class)
+ // * the map div
+ //
+ var overScrollableDiv = false;
+ var allowScroll = false;
+ var overMapDiv = false;
+
+ var elem = OpenLayers.Event.element(e);
+ while((elem != null) && !overMapDiv && !overScrollableDiv) {
+
+ if (!overScrollableDiv) {
+ try {
+ var overflow;
+ if (elem.currentStyle) {
+ overflow = elem.currentStyle["overflow"];
+ } else {
+ var style =
+ document.defaultView.getComputedStyle(elem, null);
+ overflow = style.getPropertyValue("overflow");
+ }
+ overScrollableDiv = ( overflow &&
+ (overflow == "auto") || (overflow == "scroll") );
+ } catch(err) {
+ //sometimes when scrolling in a popup, this causes
+ // obscure browser error
+ }
+ }
+
+ if (!allowScroll) {
+ allowScroll = OpenLayers.Element.hasClass(elem, 'olScrollable');
+ if (!allowScroll) {
+ for (var i = 0, len = this.map.layers.length; i < len; i++) {
+ // Are we in the layer div? Note that we have two cases
+ // here: one is to catch EventPane layers, which have a
+ // pane above the layer (layer.pane)
+ var layer = this.map.layers[i];
+ if (elem == layer.div || elem == layer.pane) {
+ allowScroll = true;
+ break;
+ }
+ }
+ }
+ }
+ overMapDiv = (elem == this.map.div);
+
+ elem = elem.parentNode;
+ }
+
+ // Logic below is the following:
+ //
+ // If we are over a scrollable div or not over the map div:
+ // * do nothing (let the browser handle scrolling)
+ //
+ // otherwise
+ //
+ // If we are over the layer div or a 'olScrollable' div:
+ // * zoom/in out
+ // then
+ // * kill event (so as not to also scroll the page after zooming)
+ //
+ // otherwise
+ //
+ // Kill the event (dont scroll the page if we wheel over the
+ // layerswitcher or the pan/zoom control)
+ //
+ if (!overScrollableDiv && overMapDiv) {
+ if (allowScroll) {
+ var delta = 0;
+
+ if (e.wheelDelta) {
+ delta = e.wheelDelta;
+ if (delta % 160 === 0) {
+ // opera have steps of 160 instead of 120
+ delta = delta * 0.75;
+ }
+ delta = delta / 120;
+ } else if (e.detail) {
+ // detail in Firefox on OS X is 1/3 of Windows
+ // so force delta 1 / -1
+ delta = - (e.detail / Math.abs(e.detail));
+ }
+ this.delta += delta;
+
+ window.clearTimeout(this._timeoutId);
+ if(this.interval && Math.abs(this.delta) < this.maxDelta) {
+ // store e because window.event might change during delay
+ var evt = OpenLayers.Util.extend({}, e);
+ this._timeoutId = window.setTimeout(
+ OpenLayers.Function.bind(function(){
+ this.wheelZoom(evt);
+ }, this),
+ this.interval
+ );
+ } else {
+ this.wheelZoom(e);
+ }
+ }
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ /**
+ * Method: wheelZoom
+ * Given the wheel event, we carry out the appropriate zooming in or out,
+ * based on the 'wheelDelta' or 'detail' property of the event.
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ wheelZoom: function(e) {
+ var delta = this.delta;
+ this.delta = 0;
+
+ if (delta) {
+ e.xy = this.map.events.getMousePosition(e);
+ if (delta < 0) {
+ this.callback("down",
+ [e, this.cumulative ? Math.max(-this.maxDelta, delta) : -1]);
+ } else {
+ this.callback("up",
+ [e, this.cumulative ? Math.min(this.maxDelta, delta) : 1]);
+ }
+ }
+ },
+
+ /**
+ * Method: activate
+ */
+ activate: function (evt) {
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ //register mousewheel events specifically on the window and document
+ var wheelListener = this.wheelListener;
+ OpenLayers.Event.observe(window, "DOMMouseScroll", wheelListener);
+ OpenLayers.Event.observe(window, "mousewheel", wheelListener);
+ OpenLayers.Event.observe(document, "mousewheel", wheelListener);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: deactivate
+ */
+ deactivate: function (evt) {
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ // unregister mousewheel events specifically on the window and document
+ var wheelListener = this.wheelListener;
+ OpenLayers.Event.stopObserving(window, "DOMMouseScroll", wheelListener);
+ OpenLayers.Event.stopObserving(window, "mousewheel", wheelListener);
+ OpenLayers.Event.stopObserving(document, "mousewheel", wheelListener);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.MouseWheel"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Path.js b/misc/openlayers/lib/OpenLayers/Handler/Path.js
new file mode 100644
index 0000000..28512a1
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Path.js
@@ -0,0 +1,543 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Path
+ * Handler to draw a path on the map. Path is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Point>
+ */
+OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
+
+ /**
+ * Property: line
+ * {<OpenLayers.Feature.Vector>}
+ */
+ line: null,
+
+ /**
+ * APIProperty: maxVertices
+ * {Number} The maximum number of vertices which can be drawn by this
+ * handler. When the number of vertices reaches maxVertices, the
+ * geometry is automatically finalized. Default is null.
+ */
+ maxVertices: null,
+
+ /**
+ * Property: doubleTouchTolerance
+ * {Number} Maximum number of pixels between two touches for
+ * the gesture to be considered a "finalize feature" action.
+ * Default is 20.
+ */
+ doubleTouchTolerance: 20,
+
+ /**
+ * Property: freehand
+ * {Boolean} In freehand mode, the handler starts the path on mouse down,
+ * adds a point for every mouse move, and finishes the path on mouse up.
+ * Outside of freehand mode, a point is added to the path on every mouse
+ * click and double-click finishes the path.
+ */
+ freehand: false,
+
+ /**
+ * Property: freehandToggle
+ * {String} If set, freehandToggle is checked on mouse events and will set
+ * the freehand mode to the opposite of this.freehand. To disallow
+ * toggling between freehand and non-freehand mode, set freehandToggle to
+ * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
+ */
+ freehandToggle: 'shiftKey',
+
+ /**
+ * Property: timerId
+ * {Integer} The timer used to test the double touch.
+ */
+ timerId: null,
+
+ /**
+ * Property: redoStack
+ * {Array} Stack containing points removed with <undo>.
+ */
+ redoStack: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Path
+ * Create a new path hander
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the linestring geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([this.point.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.line, this.point], {silent: true});
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Point.prototype.destroyFeature.call(
+ this, force);
+ this.line = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 2) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: removePoint
+ * Destroy the temporary point.
+ */
+ removePoint: function() {
+ if(this.point) {
+ this.layer.removeFeatures([this.point]);
+ }
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry. Send the point index to override
+ * the behavior of LinearRing that disregards adding duplicate points.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ this.layer.removeFeatures([this.point]);
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
+ );
+ this.line.geometry.addComponent(
+ this.point.geometry, this.line.geometry.components.length
+ );
+ this.layer.addFeatures([this.point]);
+ this.callback("point", [this.point.geometry, this.getGeometry()]);
+ this.callback("modify", [this.point.geometry, this.getSketch()]);
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertXY
+ * Insert a point in the current sketch given x & y coordinates. The new
+ * point is inserted immediately before the most recently drawn point.
+ *
+ * Parameters:
+ * x - {Number} The x-coordinate of the point.
+ * y - {Number} The y-coordinate of the point.
+ */
+ insertXY: function(x, y) {
+ this.line.geometry.addComponent(
+ new OpenLayers.Geometry.Point(x, y),
+ this.getCurrentPointIndex()
+ );
+ this.drawFeature();
+ delete this.redoStack;
+ },
+
+ /**
+ * Method: insertDeltaXY
+ * Insert a point given offsets from the previously inserted point.
+ *
+ * Parameters:
+ * dx - {Number} The x-coordinate offset of the point.
+ * dy - {Number} The y-coordinate offset of the point.
+ */
+ insertDeltaXY: function(dx, dy) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ var p0 = this.line.geometry.components[previousIndex];
+ if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) {
+ this.insertXY(p0.x + dx, p0.y + dy);
+ }
+ },
+
+ /**
+ * Method: insertDirectionLength
+ * Insert a point in the current sketch given a direction and a length.
+ *
+ * Parameters:
+ * direction - {Number} Degrees clockwise from the positive x-axis.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDirectionLength: function(direction, length) {
+ direction *= Math.PI / 180;
+ var dx = length * Math.cos(direction);
+ var dy = length * Math.sin(direction);
+ this.insertDeltaXY(dx, dy);
+ },
+
+ /**
+ * Method: insertDeflectionLength
+ * Insert a point in the current sketch given a deflection and a length.
+ * The deflection should be degrees clockwise from the previously
+ * digitized segment.
+ *
+ * Parameters:
+ * deflection - {Number} Degrees clockwise from the previous segment.
+ * length - {Number} Distance from the previously drawn point.
+ */
+ insertDeflectionLength: function(deflection, length) {
+ var previousIndex = this.getCurrentPointIndex() - 1;
+ if (previousIndex > 0) {
+ var p1 = this.line.geometry.components[previousIndex];
+ var p0 = this.line.geometry.components[previousIndex-1];
+ var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x);
+ this.insertDirectionLength(
+ (theta * 180 / Math.PI) + deflection, length
+ );
+ }
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 1;
+ },
+
+
+ /**
+ * Method: undo
+ * Remove the most recently added point in the sketch geometry.
+ *
+ * Returns:
+ * {Boolean} A point was removed.
+ */
+ undo: function() {
+ var geometry = this.line.geometry;
+ var components = geometry.components;
+ var index = this.getCurrentPointIndex() - 1;
+ var target = components[index];
+ var undone = geometry.removeComponent(target);
+ if (undone) {
+ // On touch devices, set the current ("mouse location") point to
+ // match the last digitized point.
+ if (this.touch && index > 0) {
+ components = geometry.components; // safety
+ var lastpt = components[index - 1];
+ var curptidx = this.getCurrentPointIndex();
+ var curpt = components[curptidx];
+ curpt.x = lastpt.x;
+ curpt.y = lastpt.y;
+ }
+ if (!this.redoStack) {
+ this.redoStack = [];
+ }
+ this.redoStack.push(target);
+ this.drawFeature();
+ }
+ return undone;
+ },
+
+ /**
+ * Method: redo
+ * Reinsert the most recently removed point resulting from an <undo> call.
+ * The undo stack is deleted whenever a point is added by other means.
+ *
+ * Returns:
+ * {Boolean} A point was added.
+ */
+ redo: function() {
+ var target = this.redoStack && this.redoStack.pop();
+ if (target) {
+ this.line.geometry.addComponent(target, this.getCurrentPointIndex());
+ this.drawFeature();
+ }
+ return !!target;
+ },
+
+ /**
+ * Method: freehandMode
+ * Determine whether to behave in freehand mode or not.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ freehandMode: function(evt) {
+ return (this.freehandToggle && evt[this.freehandToggle]) ?
+ !this.freehand : this.freehand;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given the new point
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest
+ * point.
+ * drawing - {Boolean} Indicate if we're currently drawing.
+ */
+ modifyFeature: function(pixel, drawing) {
+ if(!this.line) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.line, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.line;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.LineString>}
+ */
+ getGeometry: function() {
+ var geometry = this.line && this.line.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * method: touchstart
+ * handle touchstart.
+ *
+ * parameters:
+ * evt - {event} the browser event
+ *
+ * returns:
+ * {boolean} allow event propagation
+ */
+ touchstart: function(evt) {
+ if (this.timerId &&
+ this.passesTolerance(this.lastTouchPx, evt.xy,
+ this.doubleTouchTolerance)) {
+ // double-tap, finalize the geometry
+ this.finishGeometry();
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ return false;
+ } else {
+ if (this.timerId) {
+ window.clearTimeout(this.timerId);
+ this.timerId = null;
+ }
+ this.timerId = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.timerId = null;
+ }, this), 300);
+ return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt);
+ }
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Add a new point to the geometry and
+ * render it. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ var stopDown = this.stopDown;
+ if(this.freehandMode(evt)) {
+ stopDown = true;
+ if (this.touch) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ OpenLayers.Event.stop(evt);
+ }
+ }
+ if (!this.touch && (!this.lastDown ||
+ !this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance))) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ this.stoppedDown = stopDown;
+ return !stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ if(this.maxVertices && this.line &&
+ this.line.geometry.components.length === this.maxVertices) {
+ this.removePoint();
+ this.finalize();
+ } else {
+ this.addPoint(evt.xy);
+ }
+ return false;
+ }
+ if (!this.touch && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy, !!this.lastUp);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to
+ * the control. Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) {
+ if(this.stoppedDown && this.freehandMode(evt)) {
+ if (this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.removePoint();
+ this.finalize();
+ } else {
+ if (this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.lastUp == null && this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.addPoint(evt.xy);
+ this.lastUp = evt.xy;
+ if(this.line.geometry.components.length === this.maxVertices + 1) {
+ this.finishGeometry();
+ }
+ }
+ }
+ }
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ return !this.stopUp;
+ },
+
+ /**
+ * APIMethod: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 1;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ if(!this.freehandMode(evt)) {
+ this.finishGeometry();
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Path"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Pinch.js b/misc/openlayers/lib/OpenLayers/Handler/Pinch.js
new file mode 100644
index 0000000..cd3d086
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Pinch.js
@@ -0,0 +1,239 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Pinch
+ * The pinch handler is used to deal with sequences of browser events related
+ * to pinch gestures. The handler is used by controls that want to know
+ * when a pinch sequence begins, when a pinch is happening, and when it has
+ * finished.
+ *
+ * Controls that use the pinch handler typically construct it with callbacks
+ * for 'start', 'move', and 'done'. Callbacks for these keys are
+ * called when the pinch begins, with each change, and when the pinch is
+ * done.
+ *
+ * Create a new pinch handler with the <OpenLayers.Handler.Pinch> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Pinch = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: started
+ * {Boolean} When a touchstart event is received, we want to record it,
+ * but not set 'pinching' until the touchmove get started after
+ * starting.
+ */
+ started: false,
+
+ /**
+ * Property: stopDown
+ * {Boolean} Stop propagation of touchstart events from getting to
+ * listeners on the same element. Default is false.
+ */
+ stopDown: false,
+
+ /**
+ * Property: pinching
+ * {Boolean}
+ */
+ pinching: false,
+
+ /**
+ * Property: last
+ * {Object} Object that store informations related to pinch last touch.
+ */
+ last: null,
+
+ /**
+ * Property: start
+ * {Object} Object that store informations related to pinch touchstart.
+ */
+ start: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Pinch
+ * Returns OpenLayers.Handler.Pinch
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that is making use of
+ * this handler. If a handler is being used without a control, the
+ * handlers setMap method must be overridden to deal properly with
+ * the map.
+ * callbacks - {Object} An object containing functions to be called when
+ * the pinch operation start, change, or is finished. The callbacks
+ * should expect to receive an object argument, which contains
+ * information about scale, distance, and position of touch points.
+ * options - {Object}
+ */
+
+ /**
+ * Method: touchstart
+ * Handle touchstart events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchstart: function(evt) {
+ var propagate = true;
+ this.pinching = false;
+ if (OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = true;
+ this.last = this.start = {
+ distance: this.getDistance(evt.touches),
+ delta: 0,
+ scale: 1
+ };
+ this.callback("start", [evt, this.start]);
+ propagate = !this.stopDown;
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ } else {
+ this.started = false;
+ this.start = null;
+ this.last = null;
+ }
+ // prevent document dragging
+ OpenLayers.Event.preventDefault(evt);
+ return propagate;
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchmove: function(evt) {
+ if (this.started && OpenLayers.Event.isMultiTouch(evt)) {
+ this.pinching = true;
+ var current = this.getPinchData(evt);
+ this.callback("move", [evt, current]);
+ this.last = current;
+ // prevent document dragging
+ OpenLayers.Event.stop(evt);
+ } else if (this.started) {
+ // Some webkit versions send fake single-touch events during
+ // multitouch, which cause the drag handler to trigger
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend events
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean} Let the event propagate.
+ */
+ touchend: function(evt) {
+ if (this.started && !OpenLayers.Event.isMultiTouch(evt)) {
+ this.started = false;
+ this.pinching = false;
+ this.callback("done", [evt, this.start, this.last]);
+ this.start = null;
+ this.last = null;
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Method: activate
+ * Activate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated.
+ */
+ activate: function() {
+ var activated = false;
+ if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ this.pinching = false;
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ this.started = false;
+ this.pinching = false;
+ this.start = null;
+ this.last = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: getDistance
+ * Get the distance in pixels between two touches.
+ *
+ * Parameters:
+ * touches - {Array(Object)}
+ *
+ * Returns:
+ * {Number} The distance in pixels.
+ */
+ getDistance: function(touches) {
+ var t0 = touches[0];
+ var t1 = touches[1];
+ return Math.sqrt(
+ Math.pow(t0.olClientX - t1.olClientX, 2) +
+ Math.pow(t0.olClientY - t1.olClientY, 2)
+ );
+ },
+
+
+ /**
+ * Method: getPinchData
+ * Get informations about the pinch event.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Object} Object that contains data about the current pinch.
+ */
+ getPinchData: function(evt) {
+ var distance = this.getDistance(evt.touches);
+ var scale = distance / this.start.distance;
+ return {
+ distance: distance,
+ delta: this.last.distance - distance,
+ scale: scale
+ };
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Pinch"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Point.js b/misc/openlayers/lib/OpenLayers/Handler/Point.js
new file mode 100644
index 0000000..b4bb17c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Point.js
@@ -0,0 +1,556 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Point
+ * Handler to draw a point on the map. Point is displayed on activation,
+ * moves on mouse move, and is finished on mouse up. The handler triggers
+ * callbacks for 'done', 'cancel', and 'modify'. The modify callback is
+ * called with each change in the sketch and will receive the latest point
+ * drawn. Create a new instance with the <OpenLayers.Handler.Point>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
+
+ /**
+ * Property: point
+ * {<OpenLayers.Feature.Vector>} The currently drawn point
+ */
+ point: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+ */
+ layer: null,
+
+ /**
+ * APIProperty: multi
+ * {Boolean} Cast features to multi-part geometries before passing to the
+ * layer. Default is false.
+ */
+ multi: false,
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Property: mouseDown
+ * {Boolean} The mouse is down
+ */
+ mouseDown: false,
+
+ /**
+ * Property: stoppedDown
+ * {Boolean} Indicate whether the last mousedown stopped the event
+ * propagation.
+ */
+ stoppedDown: null,
+
+ /**
+ * Property: lastDown
+ * {<OpenLayers.Pixel>} Location of the last mouse down
+ */
+ lastDown: null,
+
+ /**
+ * Property: lastUp
+ * {<OpenLayers.Pixel>}
+ */
+ lastUp: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Leave the feature rendered until destroyFeature is called.
+ * Default is false. If set to true, the feature remains rendered until
+ * destroyFeature is called, typically by deactivating the handler or
+ * starting another drawing.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: stopDown
+ * {Boolean} Stop event propagation on mousedown. Must be false to
+ * allow "pan while drawing". Defaults to false.
+ */
+ stopDown: false,
+
+ /**
+ * APIPropery: stopUp
+ * {Boolean} Stop event propagation on mouse. Must be false to
+ * allow "pan while dragging". Defaults to fase.
+ */
+ stopUp: false,
+
+ /**
+ * Property: layerOptions
+ * {Object} Any optional properties to be set on the sketch layer.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: pixelTolerance
+ * {Number} Maximum number of pixels between down and up (mousedown
+ * and mouseup, or touchstart and touchend) for the handler to
+ * add a new point. If set to an integer value, if the
+ * displacement between down and up is great to this value
+ * no point will be added. Default value is 5.
+ */
+ pixelTolerance: 5,
+
+ /**
+ * Property: lastTouchPx
+ * {<OpenLayers.Pixel>} The last pixel used to know the distance between
+ * two touches (for double touch).
+ */
+ lastTouchPx: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Point
+ * Create a new point handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the point geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+ initialize: function(control, callbacks, options) {
+ if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
+ this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+ }
+
+ OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: activate
+ * turn on the handler
+ */
+ activate: function() {
+ if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+ return false;
+ }
+ // create temporary vector layer for rendering geometry sketch
+ // TBD: this could be moved to initialize/destroy - setting visibility here
+ var options = OpenLayers.Util.extend({
+ displayInLayerSwitcher: false,
+ // indicate that the temp vector layer will never be out of range
+ // without this, resolution properties must be specified at the
+ // map-level for this temporary layer to init its resolutions
+ // correctly
+ calculateInRange: OpenLayers.Function.True,
+ wrapDateLine: this.citeCompliant
+ }, this.layerOptions);
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+ this.map.addLayer(this.layer);
+ return true;
+ },
+
+ /**
+ * Method: createFeature
+ * Add temporary features
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.callback("create", [this.point.geometry, this.point]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.point], {silent: true});
+ },
+
+ /**
+ * APIMethod: deactivate
+ * turn off the handler
+ */
+ deactivate: function() {
+ if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+ return false;
+ }
+ this.cancel();
+ // If a layer's map property is set to null, it means that that layer
+ // isn't added to the map. Since we ourself added the layer to the map
+ // in activate(), we can assume that if this.layer.map is null it means
+ // that the layer has been destroyed (as a result of map.destroy() for
+ // example.
+ if (this.layer.map != null) {
+ this.destroyFeature(true);
+ this.layer.destroy(false);
+ }
+ this.layer = null;
+ return true;
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy the temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ if(this.layer && (force || !this.persist)) {
+ this.layer.destroyFeatures();
+ }
+ this.point = null;
+ },
+
+ /**
+ * Method: destroyPersistedFeature
+ * Destroy the persisted feature.
+ */
+ destroyPersistedFeature: function() {
+ var layer = this.layer;
+ if(layer && layer.features.length > 1) {
+ this.layer.features[0].destroy();
+ }
+ },
+
+ /**
+ * Method: finalize
+ * Finish the geometry and call the "done" callback.
+ *
+ * Parameters:
+ * cancel - {Boolean} Call cancel instead of done callback. Default
+ * is false.
+ */
+ finalize: function(cancel) {
+ var key = cancel ? "cancel" : "done";
+ this.mouseDown = false;
+ this.lastDown = null;
+ this.lastUp = null;
+ this.lastTouchPx = null;
+ this.callback(key, [this.geometryClone()]);
+ this.destroyFeature(cancel);
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ this.finalize(true);
+ },
+
+ /**
+ * Method: click
+ * Handle clicks. Clicks are stopped from propagating to other listeners
+ * on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ click: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: dblclick
+ * Handle double-clicks. Double-clicks are stopped from propagating to other
+ * listeners on map.events or other dom elements.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ dblclick: function(evt) {
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: modifyFeature
+ * Modify the existing geometry given a pixel location.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} A pixel location on the map.
+ */
+ modifyFeature: function(pixel) {
+ if(!this.point) {
+ this.createFeature(pixel);
+ }
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ this.point.geometry.x = lonlat.lon;
+ this.point.geometry.y = lonlat.lat;
+ this.callback("modify", [this.point.geometry, this.point, false]);
+ this.point.geometry.clearBounds();
+ this.drawFeature();
+ },
+
+ /**
+ * Method: drawFeature
+ * Render features on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Point>}
+ */
+ getGeometry: function() {
+ var geometry = this.point && this.point.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPoint([geometry]);
+ }
+ return geometry;
+ },
+
+ /**
+ * Method: geometryClone
+ * Return a clone of the relevant geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry>}
+ */
+ geometryClone: function() {
+ var geom = this.getGeometry();
+ return geom && geom.clone();
+ },
+
+ /**
+ * Method: mousedown
+ * Handle mousedown.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousedown: function(evt) {
+ return this.down(evt);
+ },
+
+ /**
+ * Method: touchstart
+ * Handle touchstart.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchstart: function(evt) {
+ this.startTouch();
+ this.lastTouchPx = evt.xy;
+ return this.down(evt);
+ },
+
+ /**
+ * Method: mousemove
+ * Handle mousemove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mousemove: function(evt) {
+ return this.move(evt);
+ },
+
+ /**
+ * Method: touchmove
+ * Handle touchmove.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchmove: function(evt) {
+ this.lastTouchPx = evt.xy;
+ return this.move(evt);
+ },
+
+ /**
+ * Method: mouseup
+ * Handle mouseup.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ mouseup: function(evt) {
+ return this.up(evt);
+ },
+
+ /**
+ * Method: touchend
+ * Handle touchend.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ touchend: function(evt) {
+ evt.xy = this.lastTouchPx;
+ return this.up(evt);
+ },
+
+ /**
+ * Method: down
+ * Handle mousedown and touchstart. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ down: function(evt) {
+ this.mouseDown = true;
+ this.lastDown = evt.xy;
+ if(!this.touch) { // no point displayed until up on touch devices
+ this.modifyFeature(evt.xy);
+ }
+ this.stoppedDown = this.stopDown;
+ return !this.stopDown;
+ },
+
+ /**
+ * Method: move
+ * Handle mousemove and touchmove. Adjust the geometry and redraw.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ move: function (evt) {
+ if(!this.touch // no point displayed until up on touch devices
+ && (!this.mouseDown || this.stoppedDown)) {
+ this.modifyFeature(evt.xy);
+ }
+ return true;
+ },
+
+ /**
+ * Method: up
+ * Handle mouseup and touchend. Send the latest point in the geometry to the control.
+ * Return determines whether to propagate the event on the map.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ *
+ * Returns:
+ * {Boolean} Allow event propagation
+ */
+ up: function (evt) {
+ this.mouseDown = false;
+ this.stoppedDown = this.stopDown;
+
+ // check keyboard modifiers
+ if(!this.checkModifiers(evt)) {
+ return true;
+ }
+ // ignore double-clicks
+ if (this.lastUp && this.lastUp.equals(evt.xy)) {
+ return true;
+ }
+ if (this.lastDown && this.passesTolerance(this.lastDown, evt.xy,
+ this.pixelTolerance)) {
+ if (this.touch) {
+ this.modifyFeature(evt.xy);
+ }
+ if(this.persist) {
+ this.destroyPersistedFeature();
+ }
+ this.lastUp = evt.xy;
+ this.finalize();
+ return !this.stopUp;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Method: mouseout
+ * Handle mouse out. For better user experience reset mouseDown
+ * and stoppedDown when the mouse leaves the map viewport.
+ *
+ * Parameters:
+ * evt - {Event} The browser event
+ */
+ mouseout: function(evt) {
+ if(OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ this.stoppedDown = this.stopDown;
+ this.mouseDown = false;
+ }
+ },
+
+ /**
+ * Method: passesTolerance
+ * Determine whether the event is within the optional pixel tolerance.
+ *
+ * Returns:
+ * {Boolean} The event is within the pixel tolerance (if specified).
+ */
+ passesTolerance: function(pixel1, pixel2, tolerance) {
+ var passes = true;
+
+ if (tolerance != null && pixel1 && pixel2) {
+ var dist = pixel1.distanceTo(pixel2);
+ if (dist > tolerance) {
+ passes = false;
+ }
+ }
+ return passes;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Point"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/Polygon.js b/misc/openlayers/lib/OpenLayers/Handler/Polygon.js
new file mode 100644
index 0000000..4f6dfd2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/Polygon.js
@@ -0,0 +1,305 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Polygon
+ * Handler to draw a polygon on the map. Polygon is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Path>
+ * - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
+
+ /**
+ * APIProperty: holeModifier
+ * {String} Key modifier to trigger hole digitizing. Acceptable values are
+ * "altKey", "shiftKey", or "ctrlKey". If not set, no hole digitizing
+ * will take place. Default is null.
+ */
+ holeModifier: null,
+
+ /**
+ * Property: drawingHole
+ * {Boolean} Currently drawing an interior ring.
+ */
+ drawingHole: false,
+
+ /**
+ * Property: polygon
+ * {<OpenLayers.Feature.Vector>}
+ */
+ polygon: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.Polygon
+ * Create a Polygon Handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An optional object with properties to be set on the
+ * handler
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * modify - Called with each move of a vertex with the vertex (point)
+ * geometry and the sketch feature.
+ * point - Called as each point is added. Receives the new point geometry.
+ * done - Called when the point drawing is finished. The callback will
+ * recieve a single argument, the polygon geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+
+ /**
+ * Method: createFeature
+ * Add temporary geometries
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
+ * feature.
+ */
+ createFeature: function(pixel) {
+ var lonlat = this.layer.getLonLatFromViewPortPx(pixel);
+ var geometry = new OpenLayers.Geometry.Point(
+ lonlat.lon, lonlat.lat
+ );
+ this.point = new OpenLayers.Feature.Vector(geometry);
+ this.line = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing([this.point.geometry])
+ );
+ this.polygon = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([this.line.geometry])
+ );
+ this.callback("create", [this.point.geometry, this.getSketch()]);
+ this.point.geometry.clearBounds();
+ this.layer.addFeatures([this.polygon, this.point], {silent: true});
+ },
+
+ /**
+ * Method: addPoint
+ * Add point to geometry.
+ *
+ * Parameters:
+ * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
+ */
+ addPoint: function(pixel) {
+ if(!this.drawingHole && this.holeModifier &&
+ this.evt && this.evt[this.holeModifier]) {
+ var geometry = this.point.geometry;
+ var features = this.control.layer.features;
+ var candidate, polygon;
+ // look for intersections, last drawn gets priority
+ for (var i=features.length-1; i>=0; --i) {
+ candidate = features[i].geometry;
+ if ((candidate instanceof OpenLayers.Geometry.Polygon ||
+ candidate instanceof OpenLayers.Geometry.MultiPolygon) &&
+ candidate.intersects(geometry)) {
+ polygon = features[i];
+ this.control.layer.removeFeatures([polygon], {silent: true});
+ this.control.layer.events.registerPriority(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.registerPriority(
+ "sketchmodified", this, this.enforceTopology
+ );
+ polygon.geometry.addComponent(this.line.geometry);
+ this.polygon = polygon;
+ this.drawingHole = true;
+ break;
+ }
+ }
+ }
+ OpenLayers.Handler.Path.prototype.addPoint.apply(this, arguments);
+ },
+
+ /**
+ * Method: getCurrentPointIndex
+ *
+ * Returns:
+ * {Number} The index of the most recently drawn point.
+ */
+ getCurrentPointIndex: function() {
+ return this.line.geometry.components.length - 2;
+ },
+
+ /**
+ * Method: enforceTopology
+ * Simple topology enforcement for drawing interior rings. Ensures vertices
+ * of interior rings are contained by exterior ring. Other topology
+ * rules are enforced in <finalizeInteriorRing> to allow drawing of
+ * rings that intersect only during the sketch (e.g. a "C" shaped ring
+ * that nearly encloses another ring).
+ */
+ enforceTopology: function(event) {
+ var point = event.vertex;
+ var components = this.line.geometry.components;
+ // ensure that vertices of interior ring are contained by exterior ring
+ if (!this.polygon.geometry.intersects(point)) {
+ var last = components[components.length-3];
+ point.x = last.x;
+ point.y = last.y;
+ }
+ },
+
+ /**
+ * Method: finishGeometry
+ * Finish the geometry and send it back to the control.
+ */
+ finishGeometry: function() {
+ var index = this.line.geometry.components.length - 2;
+ this.line.geometry.removeComponent(this.line.geometry.components[index]);
+ this.removePoint();
+ this.finalize();
+ },
+
+ /**
+ * Method: finalizeInteriorRing
+ * Enforces that new ring has some area and doesn't contain vertices of any
+ * other rings.
+ */
+ finalizeInteriorRing: function() {
+ var ring = this.line.geometry;
+ // ensure that ring has some area
+ var modified = (ring.getArea() !== 0);
+ if (modified) {
+ // ensure that new ring doesn't intersect any other rings
+ var rings = this.polygon.geometry.components;
+ for (var i=rings.length-2; i>=0; --i) {
+ if (ring.intersects(rings[i])) {
+ modified = false;
+ break;
+ }
+ }
+ if (modified) {
+ // ensure that new ring doesn't contain any other rings
+ var target;
+ outer: for (var i=rings.length-2; i>0; --i) {
+ var points = rings[i].components;
+ for (var j=0, jj=points.length; j<jj; ++j) {
+ if (ring.containsPoint(points[j])) {
+ modified = false;
+ break outer;
+ }
+ }
+ }
+ }
+ }
+ if (modified) {
+ if (this.polygon.state !== OpenLayers.State.INSERT) {
+ this.polygon.state = OpenLayers.State.UPDATE;
+ }
+ } else {
+ this.polygon.geometry.removeComponent(ring);
+ }
+ this.restoreFeature();
+ return false;
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ if (this.drawingHole) {
+ this.polygon.geometry.removeComponent(this.line.geometry);
+ this.restoreFeature(true);
+ }
+ return OpenLayers.Handler.Path.prototype.cancel.apply(this, arguments);
+ },
+
+ /**
+ * Method: restoreFeature
+ * Move the feature from the sketch layer to the target layer.
+ *
+ * Properties:
+ * cancel - {Boolean} Cancel drawing. If falsey, the "sketchcomplete" event
+ * will be fired.
+ */
+ restoreFeature: function(cancel) {
+ this.control.layer.events.unregister(
+ "sketchcomplete", this, this.finalizeInteriorRing
+ );
+ this.control.layer.events.unregister(
+ "sketchmodified", this, this.enforceTopology
+ );
+ this.layer.removeFeatures([this.polygon], {silent: true});
+ this.control.layer.addFeatures([this.polygon], {silent: true});
+ this.drawingHole = false;
+ if (!cancel) {
+ // Re-trigger "sketchcomplete" so other listeners can do their
+ // business. While this is somewhat sloppy (if a listener is
+ // registered with registerPriority - not common - between the start
+ // and end of a single ring drawing - very uncommon - it will be
+ // called twice).
+ // TODO: In 3.0, collapse sketch handlers into geometry specific
+ // drawing controls.
+ this.control.layer.events.triggerEvent(
+ "sketchcomplete", {feature : this.polygon}
+ );
+ }
+ },
+
+ /**
+ * Method: destroyFeature
+ * Destroy temporary geometries
+ *
+ * Parameters:
+ * force - {Boolean} Destroy even if persist is true.
+ */
+ destroyFeature: function(force) {
+ OpenLayers.Handler.Path.prototype.destroyFeature.call(
+ this, force);
+ this.polygon = null;
+ },
+
+ /**
+ * Method: drawFeature
+ * Render geometries on the temporary layer.
+ */
+ drawFeature: function() {
+ this.layer.drawFeature(this.polygon, this.style);
+ this.layer.drawFeature(this.point, this.style);
+ },
+
+ /**
+ * Method: getSketch
+ * Return the sketch feature.
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getSketch: function() {
+ return this.polygon;
+ },
+
+ /**
+ * Method: getGeometry
+ * Return the sketch geometry. If <multi> is true, this will return
+ * a multi-part geometry.
+ *
+ * Returns:
+ * {<OpenLayers.Geometry.Polygon>}
+ */
+ getGeometry: function() {
+ var geometry = this.polygon && this.polygon.geometry;
+ if(geometry && this.multi) {
+ geometry = new OpenLayers.Geometry.MultiPolygon([geometry]);
+ }
+ return geometry;
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.Polygon"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js b/misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js
new file mode 100644
index 0000000..bf4e2db
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Handler/RegularPolygon.js
@@ -0,0 +1,429 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.RegularPolygon
+ * Handler to draw a regular polygon on the map. Polygon is displayed on mouse
+ * down, moves or is modified on mouse move, and is finished on mouse up.
+ * The handler triggers callbacks for 'done' and 'cancel'. Create a new
+ * instance with the <OpenLayers.Handler.RegularPolygon> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Handler.Drag>
+ */
+OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
+
+ /**
+ * APIProperty: sides
+ * {Integer} Number of sides for the regular polygon. Needs to be greater
+ * than 2. Defaults to 4.
+ */
+ sides: 4,
+
+ /**
+ * APIProperty: radius
+ * {Float} Optional radius in map units of the regular polygon. If this is
+ * set to some non-zero value, a polygon with a fixed radius will be
+ * drawn and dragged with mose movements. If this property is not
+ * set, dragging changes the radius of the polygon. Set to null by
+ * default.
+ */
+ radius: null,
+
+ /**
+ * APIProperty: snapAngle
+ * {Float} If set to a non-zero value, the handler will snap the polygon
+ * rotation to multiples of the snapAngle. Value is an angle measured
+ * in degrees counterclockwise from the positive x-axis.
+ */
+ snapAngle: null,
+
+ /**
+ * APIProperty: snapToggle
+ * {String} If set, snapToggle is checked on mouse events and will set
+ * the snap mode to the opposite of what it currently is. To disallow
+ * toggling between snap and non-snap mode, set freehandToggle to
+ * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and
+ * 'altKey'. Snap mode is only possible if this.snapAngle is set to a
+ * non-zero value.
+ */
+ snapToggle: 'shiftKey',
+
+ /**
+ * Property: layerOptions
+ * {Object} Any optional properties to be set on the sketch layer.
+ */
+ layerOptions: null,
+
+ /**
+ * APIProperty: persist
+ * {Boolean} Leave the feature rendered until clear is called. Default
+ * is false. If set to true, the feature remains rendered until
+ * clear is called, typically by deactivating the handler or starting
+ * another drawing.
+ */
+ persist: false,
+
+ /**
+ * APIProperty: irregular
+ * {Boolean} Draw an irregular polygon instead of a regular polygon.
+ * Default is false. If true, the initial mouse down will represent
+ * one corner of the polygon bounds and with each mouse movement, the
+ * polygon will be stretched so the opposite corner of its bounds
+ * follows the mouse position. This property takes precedence over
+ * the radius property. If set to true, the radius property will
+ * be ignored.
+ */
+ irregular: false,
+
+ /**
+ * APIProperty: citeCompliant
+ * {Boolean} If set to true, coordinates of features drawn in a map extent
+ * crossing the date line won't exceed the world bounds. Default is false.
+ */
+ citeCompliant: false,
+
+ /**
+ * Property: angle
+ * {Float} The angle from the origin (mouse down) to the current mouse
+ * position, in radians. This is measured counterclockwise from the
+ * positive x-axis.
+ */
+ angle: null,
+
+ /**
+ * Property: fixedRadius
+ * {Boolean} The polygon has a fixed radius. True if a radius is set before
+ * drawing begins. False otherwise.
+ */
+ fixedRadius: false,
+
+ /**
+ * Property: feature
+ * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
+ */
+ feature: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+ */
+ layer: null,
+
+ /**
+ * Property: origin
+ * {<OpenLayers.Geometry.Point>} Location of the first mouse down
+ */
+ origin: null,
+
+ /**
+ * Constructor: OpenLayers.Handler.RegularPolygon
+ * Create a new regular polygon handler.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control that owns this handler
+ * callbacks - {Object} An object with a properties whose values are
+ * functions. Various callbacks described below.
+ * options - {Object} An object with properties to be set on the handler.
+ * If the options.sides property is not specified, the number of sides
+ * will default to 4.
+ *
+ * Named callbacks:
+ * create - Called when a sketch is first created. Callback called with
+ * the creation point geometry and sketch feature.
+ * done - Called when the sketch drawing is finished. The callback will
+ * recieve a single argument, the sketch geometry.
+ * cancel - Called when the handler is deactivated while drawing. The
+ * cancel callback will receive a geometry.
+ */
+ initialize: function(control, callbacks, options) {
+ if(!(options && options.layerOptions && options.layerOptions.styleMap)) {
+ this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+ }
+
+ OpenLayers.Handler.Drag.prototype.initialize.apply(this,
+ [control, callbacks, options]);
+ this.options = (options) ? options : {};
+ },
+
+ /**
+ * APIMethod: setOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ */
+ setOptions: function (newOptions) {
+ OpenLayers.Util.extend(this.options, newOptions);
+ OpenLayers.Util.extend(this, newOptions);
+ },
+
+ /**
+ * APIMethod: activate
+ * Turn on the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully activated
+ */
+ activate: function() {
+ var activated = false;
+ if(OpenLayers.Handler.Drag.prototype.activate.apply(this, arguments)) {
+ // create temporary vector layer for rendering geometry sketch
+ var options = OpenLayers.Util.extend({
+ displayInLayerSwitcher: false,
+ // indicate that the temp vector layer will never be out of range
+ // without this, resolution properties must be specified at the
+ // map-level for this temporary layer to init its resolutions
+ // correctly
+ calculateInRange: OpenLayers.Function.True,
+ wrapDateLine: this.citeCompliant
+ }, this.layerOptions);
+ this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+ this.map.addLayer(this.layer);
+ activated = true;
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Turn off the handler.
+ *
+ * Returns:
+ * {Boolean} The handler was successfully deactivated
+ */
+ deactivate: function() {
+ var deactivated = false;
+ if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
+ // call the cancel callback if mid-drawing
+ if(this.dragging) {
+ this.cancel();
+ }
+ // If a layer's map property is set to null, it means that that
+ // layer isn't added to the map. Since we ourself added the layer
+ // to the map in activate(), we can assume that if this.layer.map
+ // is null it means that the layer has been destroyed (as a result
+ // of map.destroy() for example.
+ if (this.layer.map != null) {
+ this.layer.destroy(false);
+ if (this.feature) {
+ this.feature.destroy();
+ }
+ }
+ this.layer = null;
+ this.feature = null;
+ deactivated = true;
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: down
+ * Start drawing a new feature
+ *
+ * Parameters:
+ * evt - {Event} The drag start event
+ */
+ down: function(evt) {
+ this.fixedRadius = !!(this.radius);
+ var maploc = this.layer.getLonLatFromViewPortPx(evt.xy);
+ this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+ // create the new polygon
+ if(!this.fixedRadius || this.irregular) {
+ // smallest radius should not be less one pixel in map units
+ // VML doesn't behave well with smaller
+ this.radius = this.map.getResolution();
+ }
+ if(this.persist) {
+ this.clear();
+ }
+ this.feature = new OpenLayers.Feature.Vector();
+ this.createGeometry();
+ this.callback("create", [this.origin, this.feature]);
+ this.layer.addFeatures([this.feature], {silent: true});
+ this.layer.drawFeature(this.feature, this.style);
+ },
+
+ /**
+ * Method: move
+ * Respond to drag move events
+ *
+ * Parameters:
+ * evt - {Evt} The move event
+ */
+ move: function(evt) {
+ var maploc = this.layer.getLonLatFromViewPortPx(evt.xy);
+ var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+ if(this.irregular) {
+ var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
+ this.radius = Math.max(this.map.getResolution() / 2, ry);
+ } else if(this.fixedRadius) {
+ this.origin = point;
+ } else {
+ this.calculateAngle(point, evt);
+ this.radius = Math.max(this.map.getResolution() / 2,
+ point.distanceTo(this.origin));
+ }
+ this.modifyGeometry();
+ if(this.irregular) {
+ var dx = point.x - this.origin.x;
+ var dy = point.y - this.origin.y;
+ var ratio;
+ if(dy == 0) {
+ ratio = dx / (this.radius * Math.sqrt(2));
+ } else {
+ ratio = dx / dy;
+ }
+ this.feature.geometry.resize(1, this.origin, ratio);
+ this.feature.geometry.move(dx / 2, dy / 2);
+ }
+ this.layer.drawFeature(this.feature, this.style);
+ },
+
+ /**
+ * Method: up
+ * Finish drawing the feature
+ *
+ * Parameters:
+ * evt - {Event} The mouse up event
+ */
+ up: function(evt) {
+ this.finalize();
+ // the mouseup method of superclass doesn't call the
+ // "done" callback if there's been no move between
+ // down and up
+ if (this.start == this.last) {
+ this.callback("done", [evt.xy]);
+ }
+ },
+
+ /**
+ * Method: out
+ * Finish drawing the feature.
+ *
+ * Parameters:
+ * evt - {Event} The mouse out event
+ */
+ out: function(evt) {
+ this.finalize();
+ },
+
+ /**
+ * Method: createGeometry
+ * Create the new polygon geometry. This is called at the start of the
+ * drag and at any point during the drag if the number of sides
+ * changes.
+ */
+ createGeometry: function() {
+ this.angle = Math.PI * ((1/this.sides) - (1/2));
+ if(this.snapAngle) {
+ this.angle += this.snapAngle * (Math.PI / 180);
+ }
+ this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+ this.origin, this.radius, this.sides, this.snapAngle
+ );
+ },
+
+ /**
+ * Method: modifyGeometry
+ * Modify the polygon geometry in place.
+ */
+ modifyGeometry: function() {
+ var angle, point;
+ var ring = this.feature.geometry.components[0];
+ // if the number of sides ever changes, create a new geometry
+ if(ring.components.length != (this.sides + 1)) {
+ this.createGeometry();
+ ring = this.feature.geometry.components[0];
+ }
+ for(var i=0; i<this.sides; ++i) {
+ point = ring.components[i];
+ angle = this.angle + (i * 2 * Math.PI / this.sides);
+ point.x = this.origin.x + (this.radius * Math.cos(angle));
+ point.y = this.origin.y + (this.radius * Math.sin(angle));
+ point.clearBounds();
+ }
+ },
+
+ /**
+ * Method: calculateAngle
+ * Calculate the angle based on settings.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * evt - {Event}
+ */
+ calculateAngle: function(point, evt) {
+ var alpha = Math.atan2(point.y - this.origin.y,
+ point.x - this.origin.x);
+ if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
+ var snapAngleRad = (Math.PI / 180) * this.snapAngle;
+ this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
+ } else {
+ this.angle = alpha;
+ }
+ },
+
+ /**
+ * APIMethod: cancel
+ * Finish the geometry and call the "cancel" callback.
+ */
+ cancel: function() {
+ // the polygon geometry gets cloned in the callback method
+ this.callback("cancel", null);
+ this.finalize();
+ },
+
+ /**
+ * Method: finalize
+ * Finish the geometry and call the "done" callback.
+ */
+ finalize: function() {
+ this.origin = null;
+ this.radius = this.options.radius;
+ },
+
+ /**
+ * APIMethod: clear
+ * Clear any rendered features on the temporary layer. This is called
+ * when the handler is deactivated, canceled, or done (unless persist
+ * is true).
+ */
+ clear: function() {
+ if (this.layer) {
+ this.layer.renderer.clear();
+ this.layer.destroyFeatures();
+ }
+ },
+
+ /**
+ * Method: callback
+ * Trigger the control's named callback with the given arguments
+ *
+ * Parameters:
+ * name - {String} The key for the callback that is one of the properties
+ * of the handler's callbacks object.
+ * args - {Array} An array of arguments with which to call the callback
+ * (defined by the control).
+ */
+ callback: function (name, args) {
+ // override the callback method to always send the polygon geometry
+ if (this.callbacks[name]) {
+ this.callbacks[name].apply(this.control,
+ [this.feature.geometry.clone()]);
+ }
+ // since sketch features are added to the temporary layer
+ // they must be cleared here if done or cancel
+ if(!this.persist && (name == "done" || name == "cancel")) {
+ this.clear();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Handler.RegularPolygon"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Icon.js b/misc/openlayers/lib/OpenLayers/Icon.js
new file mode 100644
index 0000000..2d8f967
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Icon.js
@@ -0,0 +1,243 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Icon
+ *
+ * The icon represents a graphical icon on the screen. Typically used in
+ * conjunction with a <OpenLayers.Marker> to represent markers on a screen.
+ *
+ * An icon has a url, size and position. It also contains an offset which
+ * allows the center point to be represented correctly. This can be
+ * provided either as a fixed offset or a function provided to calculate
+ * the desired offset.
+ *
+ */
+OpenLayers.Icon = OpenLayers.Class({
+
+ /**
+ * Property: url
+ * {String} image url
+ */
+ url: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>|Object} An OpenLayers.Size or
+ * an object with a 'w' and 'h' properties.
+ */
+ size: null,
+
+ /**
+ * Property: offset
+ * {<OpenLayers.Pixel>|Object} distance in pixels to offset the
+ * image when being rendered. An OpenLayers.Pixel or an object
+ * with a 'x' and 'y' properties.
+ */
+ offset: null,
+
+ /**
+ * Property: calculateOffset
+ * {Function} Function to calculate the offset (based on the size)
+ */
+ calculateOffset: null,
+
+ /**
+ * Property: imageDiv
+ * {DOMElement}
+ */
+ imageDiv: null,
+
+ /**
+ * Property: px
+ * {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object
+ * with a 'x' and 'y' properties.
+ */
+ px: null,
+
+ /**
+ * Constructor: OpenLayers.Icon
+ * Creates an icon, which is an image tag in a div.
+ *
+ * url - {String}
+ * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or an
+ * object with a 'w' and 'h'
+ * properties.
+ * offset - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
+ * object with a 'x' and 'y'
+ * properties.
+ * calculateOffset - {Function}
+ */
+ initialize: function(url, size, offset, calculateOffset) {
+ this.url = url;
+ this.size = size || {w: 20, h: 20};
+ this.offset = offset || {x: -(this.size.w/2), y: -(this.size.h/2)};
+ this.calculateOffset = calculateOffset;
+
+ var id = OpenLayers.Util.createUniqueID("OL_Icon_");
+ this.imageDiv = OpenLayers.Util.createAlphaImageDiv(id);
+ },
+
+ /**
+ * Method: destroy
+ * Nullify references and remove event listeners to prevent circular
+ * references and memory leaks
+ */
+ destroy: function() {
+ // erase any drawn elements
+ this.erase();
+
+ OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);
+ this.imageDiv.innerHTML = "";
+ this.imageDiv = null;
+ },
+
+ /**
+ * Method: clone
+ *
+ * Returns:
+ * {<OpenLayers.Icon>} A fresh copy of the icon.
+ */
+ clone: function() {
+ return new OpenLayers.Icon(this.url,
+ this.size,
+ this.offset,
+ this.calculateOffset);
+ },
+
+ /**
+ * Method: setSize
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>|Object} An OpenLayers.Size or
+ * an object with a 'w' and 'h' properties.
+ */
+ setSize: function(size) {
+ if (size != null) {
+ this.size = size;
+ }
+ this.draw();
+ },
+
+ /**
+ * Method: setUrl
+ *
+ * Parameters:
+ * url - {String}
+ */
+ setUrl: function(url) {
+ if (url != null) {
+ this.url = url;
+ }
+ this.draw();
+ },
+
+ /**
+ * Method: draw
+ * Move the div to the given pixel.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an
+ * object with a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {DOMElement} A new DOM Image of this icon set at the location passed-in
+ */
+ draw: function(px) {
+ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv,
+ null,
+ null,
+ this.size,
+ this.url,
+ "absolute");
+ this.moveTo(px);
+ return this.imageDiv;
+ },
+
+ /**
+ * Method: erase
+ * Erase the underlying image element.
+ */
+ erase: function() {
+ if (this.imageDiv != null && this.imageDiv.parentNode != null) {
+ OpenLayers.Element.remove(this.imageDiv);
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Change the icon's opacity
+ *
+ * Parameters:
+ * opacity - {float}
+ */
+ setOpacity: function(opacity) {
+ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null,
+ null, null, null, null, opacity);
+
+ },
+
+ /**
+ * Method: moveTo
+ * move icon to passed in px.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
+ * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
+ */
+ moveTo: function (px) {
+ //if no px passed in, use stored location
+ if (px != null) {
+ this.px = px;
+ }
+
+ if (this.imageDiv != null) {
+ if (this.px == null) {
+ this.display(false);
+ } else {
+ if (this.calculateOffset) {
+ this.offset = this.calculateOffset(this.size);
+ }
+ OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {
+ x: this.px.x + this.offset.x,
+ y: this.px.y + this.offset.y
+ });
+ }
+ }
+ },
+
+ /**
+ * Method: display
+ * Hide or show the icon
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ this.imageDiv.style.display = (display) ? "" : "none";
+ },
+
+
+ /**
+ * APIMethod: isDrawn
+ *
+ * Returns:
+ * {Boolean} Whether or not the icon is drawn.
+ */
+ isDrawn: function() {
+ // nodeType 11 for ie, whose nodes *always* have a parentNode
+ // (of type document fragment)
+ var isDrawn = (this.imageDiv && this.imageDiv.parentNode &&
+ (this.imageDiv.parentNode.nodeType != 11));
+
+ return isDrawn;
+ },
+
+ CLASS_NAME: "OpenLayers.Icon"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Kinetic.js b/misc/openlayers/lib/OpenLayers/Kinetic.js
new file mode 100644
index 0000000..1cd7886
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Kinetic.js
@@ -0,0 +1,178 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+OpenLayers.Kinetic = OpenLayers.Class({
+
+ /**
+ * Property: threshold
+ * In most cases changing the threshold isn't needed.
+ * In px/ms, default to 0.
+ */
+ threshold: 0,
+
+ /**
+ * Property: deceleration
+ * {Float} the deseleration in px/ms², default to 0.0035.
+ */
+ deceleration: 0.0035,
+
+ /**
+ * Property: nbPoints
+ * {Integer} the number of points we use to calculate the kinetic
+ * initial values.
+ */
+ nbPoints: 100,
+
+ /**
+ * Property: delay
+ * {Float} time to consider to calculate the kinetic initial values.
+ * In ms, default to 200.
+ */
+ delay: 200,
+
+ /**
+ * Property: points
+ * List of points use to calculate the kinetic initial values.
+ */
+ points: undefined,
+
+ /**
+ * Property: timerId
+ * ID of the timer.
+ */
+ timerId: undefined,
+
+ /**
+ * Constructor: OpenLayers.Kinetic
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: begin
+ * Begins the dragging.
+ */
+ begin: function() {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = undefined;
+ this.points = [];
+ },
+
+ /**
+ * Method: update
+ * Updates during the dragging.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The new position.
+ */
+ update: function(xy) {
+ this.points.unshift({xy: xy, tick: new Date().getTime()});
+ if (this.points.length > this.nbPoints) {
+ this.points.pop();
+ }
+ },
+
+ /**
+ * Method: end
+ * Ends the dragging, start the kinetic.
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The last position.
+ *
+ * Returns:
+ * {Object} An object with two properties: "speed", and "theta". The
+ * "speed" and "theta" values are to be passed to the move
+ * function when starting the animation.
+ */
+ end: function(xy) {
+ var last, now = new Date().getTime();
+ for (var i = 0, l = this.points.length, point; i < l; i++) {
+ point = this.points[i];
+ if (now - point.tick > this.delay) {
+ break;
+ }
+ last = point;
+ }
+ if (!last) {
+ return;
+ }
+ var time = new Date().getTime() - last.tick;
+ var dist = Math.sqrt(Math.pow(xy.x - last.xy.x, 2) +
+ Math.pow(xy.y - last.xy.y, 2));
+ var speed = dist / time;
+ if (speed == 0 || speed < this.threshold) {
+ return;
+ }
+ var theta = Math.asin((xy.y - last.xy.y) / dist);
+ if (last.xy.x <= xy.x) {
+ theta = Math.PI - theta;
+ }
+ return {speed: speed, theta: theta};
+ },
+
+ /**
+ * Method: move
+ * Launch the kinetic move pan.
+ *
+ * Parameters:
+ * info - {Object} An object with two properties, "speed", and "theta".
+ * These values are those returned from the "end" call.
+ * callback - {Function} Function called on every step of the animation,
+ * receives x, y (values to pan), end (is the last point).
+ */
+ move: function(info, callback) {
+ var v0 = info.speed;
+ var fx = Math.cos(info.theta);
+ var fy = -Math.sin(info.theta);
+
+ var initialTime = new Date().getTime();
+
+ var lastX = 0;
+ var lastY = 0;
+
+ var timerCallback = function() {
+ if (this.timerId == null) {
+ return;
+ }
+
+ var t = new Date().getTime() - initialTime;
+
+ var p = (-this.deceleration * Math.pow(t, 2)) / 2.0 + v0 * t;
+ var x = p * fx;
+ var y = p * fy;
+
+ var args = {};
+ args.end = false;
+ var v = -this.deceleration * t + v0;
+
+ if (v <= 0) {
+ OpenLayers.Animation.stop(this.timerId);
+ this.timerId = null;
+ args.end = true;
+ }
+
+ args.x = x - lastX;
+ args.y = y - lastY;
+ lastX = x;
+ lastY = y;
+ callback(args.x, args.y, args.end);
+ };
+
+ this.timerId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(timerCallback, this)
+ );
+ },
+
+ CLASS_NAME: "OpenLayers.Kinetic"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang.js b/misc/openlayers/lib/OpenLayers/Lang.js
new file mode 100644
index 0000000..068562d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang.js
@@ -0,0 +1,134 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang
+ * Internationalization namespace. Contains dictionaries in various languages
+ * and methods to set and get the current language.
+ */
+OpenLayers.Lang = {
+
+ /**
+ * Property: code
+ * {String} Current language code to use in OpenLayers. Use the
+ * <setCode> method to set this value and the <getCode> method to
+ * retrieve it.
+ */
+ code: null,
+
+ /**
+ * APIProperty: defaultCode
+ * {String} Default language to use when a specific language can't be
+ * found. Default is "en".
+ */
+ defaultCode: "en",
+
+ /**
+ * APIFunction: getCode
+ * Get the current language code.
+ *
+ * Returns:
+ * {String} The current language code.
+ */
+ getCode: function() {
+ if(!OpenLayers.Lang.code) {
+ OpenLayers.Lang.setCode();
+ }
+ return OpenLayers.Lang.code;
+ },
+
+ /**
+ * APIFunction: setCode
+ * Set the language code for string translation. This code is used by
+ * the <OpenLayers.Lang.translate> method.
+ *
+ * Parameters:
+ * code - {String} These codes follow the IETF recommendations at
+ * http://www.ietf.org/rfc/rfc3066.txt. If no value is set, the
+ * browser's language setting will be tested. If no <OpenLayers.Lang>
+ * dictionary exists for the code, the <OpenLayers.String.defaultLang>
+ * will be used.
+ */
+ setCode: function(code) {
+ var lang;
+ if(!code) {
+ code = (OpenLayers.BROWSER_NAME == "msie") ?
+ navigator.userLanguage : navigator.language;
+ }
+ var parts = code.split('-');
+ parts[0] = parts[0].toLowerCase();
+ if(typeof OpenLayers.Lang[parts[0]] == "object") {
+ lang = parts[0];
+ }
+
+ // check for regional extensions
+ if(parts[1]) {
+ var testLang = parts[0] + '-' + parts[1].toUpperCase();
+ if(typeof OpenLayers.Lang[testLang] == "object") {
+ lang = testLang;
+ }
+ }
+ if(!lang) {
+ OpenLayers.Console.warn(
+ 'Failed to find OpenLayers.Lang.' + parts.join("-") +
+ ' dictionary, falling back to default language'
+ );
+ lang = OpenLayers.Lang.defaultCode;
+ }
+
+ OpenLayers.Lang.code = lang;
+ },
+
+ /**
+ * APIMethod: translate
+ * Looks up a key from a dictionary based on the current language string.
+ * The value of <getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+ translate: function(key, context) {
+ var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
+ var message = dictionary && dictionary[key];
+ if(!message) {
+ // Message not found, fall back to message key
+ message = key;
+ }
+ if(context) {
+ message = OpenLayers.String.format(message, context);
+ }
+ return message;
+ }
+
+};
+
+
+/**
+ * APIMethod: OpenLayers.i18n
+ * Alias for <OpenLayers.Lang.translate>. Looks up a key from a dictionary
+ * based on the current language string. The value of
+ * <OpenLayers.Lang.getCode> will be used to determine the appropriate
+ * dictionary. Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ * <OpenLayers.String.format>.
+ *
+ * Returns:
+ * {String} A internationalized string.
+ */
+OpenLayers.i18n = OpenLayers.Lang.translate;
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ar.js b/misc/openlayers/lib/OpenLayers/Lang/ar.js
new file mode 100644
index 0000000..b9fe224
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ar.js
@@ -0,0 +1,32 @@
+/* Translators (2009 onwards):
+ * - Meno25
+ * - Mutarjem horr
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["ar"]
+ * Dictionary for العربية. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["ar"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "وصلة دائمة",
+
+ 'Base Layer': "الطبقة الاساسية",
+
+ 'Scale = 1 : ${scaleDenom}': "النسبة = 1 : ${scaleDenom}",
+
+ 'W': "غ",
+
+ 'E': "شر",
+
+ 'N': "شم",
+
+ 'S': "ج"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/be-tarask.js b/misc/openlayers/lib/OpenLayers/Lang/be-tarask.js
new file mode 100644
index 0000000..29fc1a3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/be-tarask.js
@@ -0,0 +1,54 @@
+/* Translators (2009 onwards):
+ * - EugeneZelenko
+ * - Jim-by
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["be-tarask"]
+ * Dictionary for БеларуÑÐºÐ°Ñ (тарашкевіца). Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["be-tarask"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Ðеапрацаваны вынік запыту ${statusText}",
+
+ 'Permalink': "Ð¡Ñ‚Ð°Ð»Ð°Ñ ÑпаÑылка",
+
+ 'Overlays': "Слаі",
+
+ 'Base Layer': "Базавы Ñлой",
+
+ 'noFID': "Ðемагчыма абнавіць магчымаÑьць, Ð´Ð»Ñ Ñкога не Ñ–Ñнуе FID.",
+
+ 'browserNotSupported': "Ваш браўзÑÑ€ не падтрымлівае вÑктарную графіку. У цÑперашні момант падтрымліваюцца: ${renderers}",
+
+ 'minZoomLevelError': "УлаÑьціваÑьць minZoomLevel прызначана толькі Ð´Ð»Ñ Ð²Ñ‹ÐºÐ°Ñ€Ñ‹ÑÑ‚Ð°Ð½ÑŒÐ½Ñ Ñа ÑлаÑмі вытворнымі ад FixedZoomLevels. Тое, што гÑÑ‚Ñ‹ wfs-Ñлой правÑраецца на minZoomLevel — Ñ€Ñха прошлага. Ðле мы Ð½Ñ Ð¼Ð¾Ð¶Ð°Ð¼ выдаліць гÑтую магчымаÑьць, таму што ад Ñе залежаць Ð½ÐµÐºÐ°Ñ‚Ð¾Ñ€Ñ‹Ñ Ð·Ð°ÑÐ½Ð°Ð²Ð°Ð½Ñ‹Ñ Ð½Ð° OL даÑтаÑаваньні. Тым Ð½Ñ Ð¼ÐµÐ½Ñˆ, праверка minZoomLevel будзе Ð²Ñ‹Ð´Ð°Ð»ÐµÐ½Ð°Ñ Ñž вÑÑ€ÑÑ–Ñ– 3.0. Калі лаÑка, выкарыÑтоўваеце замеÑÑ‚ Ñе ÑžÑтаноўкі мінімальнага/макÑымальнага памераў, Ñк апіÑана тут: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS-транзакцыÑ: ПОСЬПЕХ ${response}",
+
+ 'commitFailed': "WFS-транзакцыÑ: ПÐМЫЛКР${response}",
+
+ 'googleWarning': "Ðе атрымалаÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ·Ñ–Ñ†ÑŒ Ñлой Google. \x3cbr\x3e\x3cbr\x3eКаб пазбавіцца гÑтага паведамленьнÑ, выберыце новы базавы Ñлой у ÑьпіÑе Ñž верхнім правым куце.\x3cbr\x3e\x3cbr\x3e ХутчÑй за ÑžÑÑ‘, прычына Ñž тым, што Ñкрыпт бібліÑÑ‚Ñкі Google Maps Ð½Ñ Ð±Ñ‹Ñž ÑƒÐºÐ»ÑŽÑ‡Ð°Ð½Ñ‹Ñ Ð°Ð»ÑŒÐ±Ð¾ не ўтрымлівае Ñлушны API-ключ Ð´Ð»Ñ Ð’Ð°ÑˆÐ°Ð³Ð° Ñайта.\x3cbr\x3e\x3cbr\x3eРаÑпрацоўшчыкам: Ð”Ð»Ñ Ñ‚Ð°Ð³Ð¾, каб даведацца Ñк зрабіць так, каб уÑÑ‘ працавала, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eнаціÑьніце тут\x3c/a\x3e",
+
+ 'getLayerWarning': "Ðемагчыма загрузіць Ñлой ${layerType}.\x3cbr\x3e\x3cbr\x3eКаб пазбавіцца гÑтага паведамленьнÑ, выберыце новы базавы Ñлой у ÑьпіÑе Ñž верхнім правым куце.\x3cbr\x3e\x3cbr\x3eХутчÑй за ÑžÑÑ‘, прычына Ñž тым, што Ñкрыпт бібліÑÑ‚Ñкі ${layerLib} Ð½Ñ Ð±Ñ‹Ñž Ñлушна ўключаны.\x3cbr\x3e\x3cbr\x3eРаÑпрацоўшчыкам: Ð”Ð»Ñ Ñ‚Ð°Ð³Ð¾, каб даведацца Ñк зрабіць так, каб уÑÑ‘ працавала, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eнаціÑьніце тут\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Маштаб = 1 : ${scaleDenom}",
+
+ 'W': "З",
+
+ 'E': "У",
+
+ 'N': "Пн",
+
+ 'S': "Пд",
+
+ 'reprojectDeprecated': "Ð’Ñ‹ выкарыÑтоўваеце ÑžÑтаноўку \'reproject\' Ð´Ð»Ñ ÑÐ»Ð¾Ñ ${layerName}. ГÑÑ‚Ð°Ñ ÑžÑтаноўка зьÑўлÑецца ÑаÑтарÑлай: Ñна выкарыÑтоўвалаÑÑ Ð´Ð»Ñ Ð¿Ð°Ð´Ñ‚Ñ€Ñ‹Ð¼ÐºÑ– паказу зьвеÑтак на камÑрцыйных базавых мапах, але гÑта Ñ„ÑƒÐ½ÐºÑ†Ñ‹Ñ Ñ†Ñпер Ñ€ÑÐ°Ð»Ñ–Ð·Ð°Ð²Ð°Ð½Ð°Ñ Ñž убудаванай падтрымцы ÑÑ„Ñрычнай праекцыі ÐœÑркатара. Ð”Ð°Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð°Ñ Ñ–Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ Ñ‘Ñьць на http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "ГÑÑ‚Ñ‹ мÑтад ÑаÑтарÑлы Ñ– будзе выдалены Ñž вÑÑ€ÑÑ–Ñ– 3.0. Калі лаÑка, замеÑÑ‚ Ñго выкарыÑтоўвайце ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/bg.js b/misc/openlayers/lib/OpenLayers/Lang/bg.js
new file mode 100644
index 0000000..86f53aa
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/bg.js
@@ -0,0 +1,25 @@
+/* Translators (2009 onwards):
+ * - DCLXVI
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["bg"]
+ * Dictionary for БългарÑки. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["bg"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "ПоÑтоÑнна препратка",
+
+ 'Base Layer': "ОÑновен Ñлой",
+
+ 'Scale = 1 : ${scaleDenom}': "Мащаб = 1 : ${scaleDenom}",
+
+ 'methodDeprecated': "Този метод е оÑтарÑл и ще бъде премахват в 3.0. ВмеÑто него използвайте ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/br.js b/misc/openlayers/lib/OpenLayers/Lang/br.js
new file mode 100644
index 0000000..1e81559
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/br.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Fulup
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["br"]
+ * Dictionary for Brezhoneg. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["br"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Distro evel reked anveret ${statusText}",
+
+ 'Permalink': "Peurliamm",
+
+ 'Overlays': "Gwiskadoù",
+
+ 'Base Layer': "Gwiskad diazez",
+
+ 'noFID': "N\'haller ket hizivaat un elfenn ma n\'eus ket a niverenn-anaout (FID) eviti.",
+
+ 'browserNotSupported': "N\'eo ket skoret an daskor vektorel gant ho merdeer. Setu aze an daskorerioù skoret evit ar poent :\n${renderers}",
+
+ 'minZoomLevelError': "Ne zleer implijout ar perzh minZoomLevel nemet evit gwiskadoù FixedZoomLevels-descendent. Ar fed ma wiria ar gwiskad WHS-se hag-eñ ez eus eus minZoomLevel zo un aspadenn gozh. Koulskoude n\'omp ket evit e ziverkañ kuit da derriñ arloadoù diazezet war OL a c\'hallfe bezañ stag outañ. Setu perak eo dispredet -- Lamet kuit e vo ar gwiriañ minZoomLevel a-is er stumm 3.0. Ober gant an arventennoù bihanañ/brasañ evel deskrivet amañ e plas : http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Treuzgread WFS : MAT EO ${response}",
+
+ 'commitFailed': "Treuzgread WFS Transaction: C\'HWITET ${response}",
+
+ 'googleWarning': "N\'eus ket bet gallet kargañ ar gwiskad Google ent reizh.\x3cbr\x3e\x3cbr\x3eEvit en em zizober eus ar c\'hemenn-mañ, dibabit ur BaseLayer nevez en diuzer gwiskadoù er c\'horn dehoù el laez.\x3cbr\x3e\x3cbr\x3eSur a-walc\'h eo peogwir n\'eo ket bet ensoc\'het levraoueg Google Maps pe neuze ne glot ket an alc\'hwez API gant ho lec\'hienn.\x3cbr\x3e\x3cbr\x3eDiorroerien : Evit reizhañ an dra-se, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclick here\x3c/a\x3e",
+
+ 'getLayerWarning': "N\'haller ket kargañ ar gwiskad ${layerType} ent reizh.\x3cbr\x3e\x3cbr\x3eEvit en em zizober eus ar c\'hemenn-mañ, dibabit ur BaseLayer nevez en diuzer gwiskadoù er c\'horn dehoù el laez.\x3cbr\x3e\x3cbr\x3eSur a-walc\'h eo peogwir n\'eo ket bet ensoc\'het mat al levraoueg ${layerLib}.\x3cbr\x3e\x3cbr\x3eDiorroerien : Evit gouzout penaos reizhañ an dra-se, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclick here\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Skeul = 1 : ${scaleDenom}",
+
+ 'W': "K",
+
+ 'E': "R",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Emaoc\'h oc\'h implijout an dibarzh \'reproject\' war ar gwiskad ${layerName}. Dispredet eo an dibarzh-mañ : bet eo hag e talveze da ziskwel roadennoù war-c\'horre kartennoù diazez kenwerzhel, un dra hag a c\'haller ober bremañ gant an arc\'hwel dre skor banndres boullek Mercator. Muioc\'h a ditouroù a c\'haller da gaout war http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Dispredet eo an daore-se ha tennet e vo kuit eus ar stumm 3.0. Grit gant ${newMethod} e plas."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ca.js b/misc/openlayers/lib/OpenLayers/Lang/ca.js
new file mode 100644
index 0000000..75bcf8a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ca.js
@@ -0,0 +1,89 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["ca"]
+ * Dictionary for Catalan, UTF8 encoding. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang.ca = {
+
+ 'unhandledRequest': "Resposta a petició no gestionada ${statusText}",
+
+ 'Permalink': "Enllaç permanent",
+
+ 'Overlays': "Capes addicionals",
+
+ 'Base Layer': "Capa Base",
+
+ 'noFID': "No es pot actualitzar un element per al que no existeix FID.",
+
+ 'browserNotSupported':
+ "El seu navegador no suporta renderització vectorial. Els renderitzadors suportats actualment són:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "La propietat minZoomLevel s'ha d'utilitzar només " +
+ "amb les capes que tenen FixedZoomLevels. El fet que " +
+ "una capa wfs comprovi minZoomLevel és una relíquia del " +
+ "passat. No podem, però, eliminar-la sense trencar " +
+ "les aplicacions d'OpenLayers que en puguin dependre. " +
+ "Així doncs estem fent-la obsoleta -- la comprovació " +
+ "minZoomLevel s'eliminarà a la versió 3.0. Feu servir " +
+ "els paràmetres min/max resolution en substitució, tal com es descriu aquí: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transacció WFS: CORRECTA ${response}",
+
+ 'commitFailed': "Transacció WFS: HA FALLAT ${response}",
+
+ 'googleWarning':
+ "La capa Google no s'ha pogut carregar correctament.<br><br>" +
+ "Per evitar aquest missatge, seleccioneu una nova Capa Base " +
+ "al gestor de capes de la cantonada superior dreta.<br><br>" +
+ "Probablement això és degut a que l'script de la biblioteca de " +
+ "Google Maps no ha estat inclòs a la vostra pàgina, o no " +
+ "conté la clau de l'API correcta per a la vostra adreça.<br><br>" +
+ "Desenvolupadors: Per obtenir consells sobre com fer anar això, " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>féu clic aquí</a>",
+
+ 'getLayerWarning':
+ "Per evitar aquest missatge, seleccioneu una nova Capa Base " +
+ "al gestor de capes de la cantonada superior dreta.<br><br>" +
+ "Probablement això és degut a que l'script de la biblioteca " +
+ "${layerLib} " +
+ "no ha estat inclòs a la vostra pàgina.<br><br>" +
+ "Desenvolupadors: Per obtenir consells sobre com fer anar això, " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>féu clic aquí</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",
+
+ //labels for the graticule control
+ 'W': 'O',
+ 'E': 'E',
+ 'N': 'N',
+ 'S': 'S',
+ 'Graticule': 'Retícula',
+
+ // console message
+ 'reprojectDeprecated':
+ "Esteu fent servir l'opció 'reproject' a la capa " +
+ "${layerName}. Aquesta opció és obsoleta: el seu ús fou concebut " +
+ "per suportar la visualització de dades sobre mapes base comercials, " +
+ "però ara aquesta funcionalitat s'hauria d'assolir mitjançant el suport " +
+ "de la projecció Spherical Mercator. Més informació disponible a " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "Aquest mètode és obsolet i s'eliminarà a la versió 3.0. " +
+ "Si us plau feu servir em mètode alternatiu ${newMethod}.",
+
+ // **** end ****
+ 'end': ''
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/cs-CZ.js b/misc/openlayers/lib/OpenLayers/Lang/cs-CZ.js
new file mode 100644
index 0000000..85a55da
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/cs-CZ.js
@@ -0,0 +1,45 @@
+/* Translators (2009 onwards):
+ * - Mormegil
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["cs-CZ"]
+ * Dictionary for ÄŒesky. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["cs-CZ"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Nezpracovaná návratová hodnota ${statusText}",
+
+ 'Permalink': "Trvalý odkaz",
+
+ 'Overlays': "Překryvné vrstvy",
+
+ 'Base Layer': "Podkladové vrstvy",
+
+ 'noFID': "Nelze aktualizovat prvek, pro který neexistuje FID.",
+
+ 'browserNotSupported': "Váš prohlížeÄ nepodporuje vykreslování vektorů. MomentálnÄ› podporované nástroje jsou::\n${renderers}",
+
+ 'minZoomLevelError': "Vlastnost minZoomLevel by se mÄ›la používat pouze s potomky FixedZoomLevels vrstvami. To znamená, že vrstva wfs kontroluje, zda-li minZoomLevel není zbytek z minulosti.Nelze to ovÅ¡em vyjmout bez možnosti, že bychom rozbili aplikace postavené na OL, které by na tom mohly záviset. Proto tuto vlastnost nedoporuÄujeme používat -- kontrola minZoomLevel bude odstranÄ›na ve verzi 3.0. Použijte prosím radÄ›ji nastavení min/max podle příkaldu popsaného na: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS Transaction: ÚSPĚCH ${response}",
+
+ 'commitFailed': "WFS Transaction: CHYBA ${response}",
+
+ 'googleWarning': "NepodaÅ™ilo se správnÄ› naÄíst vrstvu Google.\x3cbr\x3e\x3cbr\x3eAbyste se zbavili této zprávy, zvolte jinou základní vrstvu v pÅ™epínaÄi vrstev.\x3cbr\x3e\x3cbr\x3eTo se vÄ›tÅ¡inou stává, pokud nebyl naÄten skript, nebo neobsahuje správný klÃ­Ä pro API pro tuto stránku.\x3cbr\x3e\x3cbr\x3eVývojáři: Pro pomoc, aby tohle fungovalo , \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ekliknÄ›te sem\x3c/a\x3e",
+
+ 'getLayerWarning': "The ${layerType} Layer was unable to load correctly.\x3cbr\x3e\x3cbr\x3eTo get rid of this message, select a new BaseLayer in the layer switcher in the upper-right corner.\x3cbr\x3e\x3cbr\x3eMost likely, this is because the ${layerLib} library script was either not correctly included.\x3cbr\x3e\x3cbr\x3eDevelopers: For help getting this working correctly, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclick here\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Měřítko = 1 : ${scaleDenom}",
+
+ 'reprojectDeprecated': "Použil jste volbu \'reproject\' ve vrstvÄ› ${layerName}. Tato volba není doporuÄená: byla zde proto, aby bylo možno zobrazovat data z okomerÄních serverů, ale tato funkce je nyní zajiÅ¡tÄ›na pomocí podpory Spherical Mercator. Více informací naleznete na http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Tato metoda je zavržená a bude ve verzi 3.0 odstraněna. Prosím, použijte raději ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/da-DK.js b/misc/openlayers/lib/OpenLayers/Lang/da-DK.js
new file mode 100644
index 0000000..3ba42c3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/da-DK.js
@@ -0,0 +1,80 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["da-DK"]
+ * Dictionary for Danish. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang['da-DK'] = {
+
+ 'unhandledRequest': "En ikke håndteret forespørgsel returnerede ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Kortlag",
+
+ 'Base Layer': "Baggrundslag",
+
+ 'noFID': "Kan ikke opdateret en feature (et objekt) der ikke har et FID.",
+
+ 'browserNotSupported':
+ "Din browser understøtter ikke vektor visning. Følgende vektor visninger understøttes:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "Egenskaben minZoomLevel er kun beregnet til brug " +
+ "med FixedZoomLevels. At dette WFS lag kontrollerer " +
+ "minZoomLevel egenskaben, er et levn fra en tidligere " +
+ "version. Vi kan desværre ikke fjerne dette uden at risikere " +
+ "at ødelægge eksisterende OL baserede programmer der " +
+ " benytter denne funktionalitet. " +
+ "Egenskaben bør derfor ikke anvendes, og minZoomLevel " +
+ "kontrollen herunder vil blive fjernet i version 3.0. " +
+ "Benyt istedet min/max opløsnings indstillingerne, som " +
+ "er beskrevet her: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS transaktion: LYKKEDES ${response}",
+
+ 'commitFailed': "WFS transaktion: MISLYKKEDES ${response}",
+
+ 'googleWarning':
+ "Google laget kunne ikke indlæses.<br><br>" +
+ "For at fjerne denne besked, vælg et nyt bagrundskort i " +
+ "lagskifteren i øverste højre hjørne.<br><br>" +
+ "Fejlen skyldes formentlig at Google Maps bibliotekts " +
+ "scriptet ikke er inkluderet, eller ikke indeholder den " +
+ "korrkte API nøgle for dit site.<br><br>" +
+ "Udviklere: For hjælp til at få dette til at fungere, " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>klik her</a>",
+
+ 'getLayerWarning':
+ "${layerType}-laget kunne ikke indlæses.<br><br>" +
+ "For at fjerne denne besked, vælg et nyt bagrundskort i " +
+ "lagskifteren i øverste højre hjørne.<br><br>" +
+ "Fejlen skyldes formentlig at ${layerLib} bibliotekts " +
+ "scriptet ikke er inkluderet.<br><br>" +
+ "Udviklere: For hjælp til at få dette til at fungere, " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>klik her</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "MÃ¥lforhold = 1 : ${scaleDenom}",
+
+ // console message
+ 'reprojectDeprecated':
+ "Du anvender indstillingen 'reproject' på laget ${layerName}." +
+ "Denne indstilling bør ikke længere anvendes. Den var beregnet " +
+ "til at vise data ovenpå kommercielle grundkort, men den funktionalitet " +
+ "bør nu opnås ved at anvende Spherical Mercator understøttelsen. " +
+ "Mere information er tilgængelig her: " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "Denne funktion bør ikke længere anvendes, og vil blive fjernet i version 3.0. " +
+ "Anvend venligst funktionen ${newMethod} istedet."
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/de.js b/misc/openlayers/lib/OpenLayers/Lang/de.js
new file mode 100644
index 0000000..62011c2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/de.js
@@ -0,0 +1,55 @@
+/* Translators (2009 onwards):
+ * - Grille chompa
+ * - Nikiwaibel
+ * - Umherirrender
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["de"]
+ * Dictionary for Deutsch. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["de"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Unbehandelte Anfragerückmeldung ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Overlays",
+
+ 'Base Layer': "Grundkarte",
+
+ 'noFID': "Ein Feature, für das keine FID existiert, kann nicht aktualisiert werden.",
+
+ 'browserNotSupported': "Ihr Browser unterstützt keine Vektordarstellung. Aktuell unterstützte Renderer:\n${renderers}",
+
+ 'minZoomLevelError': "Die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Eigenschaft ist nur für die Verwendung mit \x3ccode\x3eFixedZoomLevels\x3c/code\x3e-untergeordneten Layers vorgesehen. Das dieser \x3ctt\x3ewfs\x3c/tt\x3e-Layer die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Eigenschaft überprüft ist ein Relikt der Vergangenheit. Wir können diese Überprüfung nicht entfernen, ohne das OL basierende Applikationen nicht mehr funktionieren. Daher markieren wir es als veraltet - die \x3ccode\x3eminZoomLevel\x3c/code\x3e-Überprüfung wird in Version 3.0 entfernt werden. Bitte verwenden Sie stattdessen die Min-/Max-Lösung, wie sie unter http://trac.openlayers.org/wiki/SettingZoomLevels beschrieben ist.",
+
+ 'commitSuccess': "WFS-Transaktion: Erfolgreich ${response}",
+
+ 'commitFailed': "WFS-Transaktion: Fehlgeschlagen ${response}",
+
+ 'googleWarning': "Der Google-Layer konnte nicht korrekt geladen werden.\x3cbr\x3e\x3cbr\x3eUm diese Meldung nicht mehr zu erhalten, wählen Sie einen anderen Hintergrundlayer aus dem LayerSwitcher in der rechten oberen Ecke.\x3cbr\x3e\x3cbr\x3eSehr wahrscheinlich tritt dieser Fehler auf, weil das Skript der Google-Maps-Bibliothek nicht eingebunden wurde oder keinen gültigen API-Schlüssel für Ihre URL enthält.\x3cbr\x3e\x3cbr\x3eEntwickler: Besuche \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3edas Wiki\x3c/a\x3e für Hilfe zum korrekten Einbinden des Google-Layers",
+
+ 'getLayerWarning': "Der ${layerType}-Layer konnte nicht korrekt geladen werden.\x3cbr\x3e\x3cbr\x3eUm diese Meldung nicht mehr zu erhalten, wählen Sie einen anderen Hintergrundlayer aus dem LayerSwitcher in der rechten oberen Ecke.\x3cbr\x3e\x3cbr\x3eSehr wahrscheinlich tritt dieser Fehler auf, weil das Skript der \'${layerLib}\'-Bibliothek nicht eingebunden wurde.\x3cbr\x3e\x3cbr\x3eEntwickler: Besuche \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3edas Wiki\x3c/a\x3e für Hilfe zum korrekten Einbinden von Layern",
+
+ 'Scale = 1 : ${scaleDenom}': "Maßstab = 1 : ${scaleDenom}",
+
+ 'W': "W",
+
+ 'E': "O",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Sie verwenden die „Reproject“-Option des Layers ${layerName}. Diese Option ist veraltet: Sie wurde entwickelt um die Anzeige von Daten auf kommerziellen Basiskarten zu unterstützen, aber diese Funktion sollte jetzt durch Unterstützung der „Spherical Mercator“ erreicht werden. Weitere Informationen sind unter http://trac.openlayers.org/wiki/SphericalMercator verfügbar.",
+
+ 'methodDeprecated': "Die Methode ist veraltet und wird in 3.0 entfernt. Bitte verwende stattdessen ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/el.js b/misc/openlayers/lib/OpenLayers/Lang/el.js
new file mode 100644
index 0000000..0b3d067
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/el.js
@@ -0,0 +1,19 @@
+/* Translators (2009 onwards):
+ * - Omnipaedista
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["el"]
+ * Dictionary for Ελληνικά. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["el"] = OpenLayers.Util.applyDefaults({
+
+ 'Scale = 1 : ${scaleDenom}': "Κλίμακα ~ 1 : ${scaleDenom}"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/en-CA.js b/misc/openlayers/lib/OpenLayers/Lang/en-CA.js
new file mode 100644
index 0000000..5939333
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/en-CA.js
@@ -0,0 +1,21 @@
+/**
+ * @requires OpenLayers/Lang/en.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["en-CA"]
+ * Dictionary for English-CA. This dictionary inherits from the standard
+ * English dictionary. Override only those entries with language specific
+ * to the CA region.
+ *
+ * Keys for entries are used in calls to <OpenLayers.Lang.translate>. Entry
+ * bodies are normal strings or strings formatted for use with
+ * <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang['en-CA'] = OpenLayers.Util.applyDefaults({
+
+ // add any entries specific for this region here
+ // e.g.
+ // "someKey": "Some regionally specific value"
+
+}, OpenLayers.Lang["en"]);
diff --git a/misc/openlayers/lib/OpenLayers/Lang/en.js b/misc/openlayers/lib/OpenLayers/Lang/en.js
new file mode 100644
index 0000000..8fd0827
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/en.js
@@ -0,0 +1,89 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["en"]
+ * Dictionary for English. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang.en = {
+
+ 'unhandledRequest': "Unhandled request return ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Overlays",
+
+ 'Base Layer': "Base Layer",
+
+ 'noFID': "Can't update a feature for which there is no FID.",
+
+ 'browserNotSupported':
+ "Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "The minZoomLevel property is only intended for use " +
+ "with the FixedZoomLevels-descendent layers. That this " +
+ "wfs layer checks for minZoomLevel is a relic of the" +
+ "past. We cannot, however, remove it without possibly " +
+ "breaking OL based applications that may depend on it." +
+ " Therefore we are deprecating it -- the minZoomLevel " +
+ "check below will be removed at 3.0. Please instead " +
+ "use min/max resolution setting as described here: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS Transaction: SUCCESS ${response}",
+
+ 'commitFailed': "WFS Transaction: FAILED ${response}",
+
+ 'googleWarning':
+ "The Google Layer was unable to load correctly.<br><br>" +
+ "To get rid of this message, select a new BaseLayer " +
+ "in the layer switcher in the upper-right corner.<br><br>" +
+ "Most likely, this is because the Google Maps library " +
+ "script was either not included, or does not contain the " +
+ "correct API key for your site.<br><br>" +
+ "Developers: For help getting this working correctly, " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>click here</a>",
+
+ 'getLayerWarning':
+ "The ${layerType} Layer was unable to load correctly.<br><br>" +
+ "To get rid of this message, select a new BaseLayer " +
+ "in the layer switcher in the upper-right corner.<br><br>" +
+ "Most likely, this is because the ${layerLib} library " +
+ "script was not correctly included.<br><br>" +
+ "Developers: For help getting this working correctly, " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>click here</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "Scale = 1 : ${scaleDenom}",
+
+ //labels for the graticule control
+ 'W': 'W',
+ 'E': 'E',
+ 'N': 'N',
+ 'S': 'S',
+ 'Graticule': 'Graticule',
+
+ // console message
+ 'reprojectDeprecated':
+ "You are using the 'reproject' option " +
+ "on the ${layerName} layer. This option is deprecated: " +
+ "its use was designed to support displaying data over commercial " +
+ "basemaps, but that functionality should now be achieved by using " +
+ "Spherical Mercator support. More information is available from " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "This method has been deprecated and will be removed in 3.0. " +
+ "Please use ${newMethod} instead.",
+
+ // **** end ****
+ 'end': ''
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/es.js b/misc/openlayers/lib/OpenLayers/Lang/es.js
new file mode 100644
index 0000000..d8b77b6
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/es.js
@@ -0,0 +1,90 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["es"]
+ * Dictionary for Spanish, UTF8 encoding. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang.es = {
+
+ 'unhandledRequest': "Respuesta a petición no gestionada ${statusText}",
+
+ 'Permalink': "Enlace permanente",
+
+ 'Overlays': "Capas superpuestas",
+
+ 'Base Layer': "Capa Base",
+
+ 'noFID': "No se puede actualizar un elemento para el que no existe FID.",
+
+ 'browserNotSupported':
+ "Su navegador no soporta renderización vectorial. Los renderizadores soportados actualmente son:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "La propiedad minZoomLevel debe sólo utilizarse " +
+ "con las capas que tienen FixedZoomLevels. El hecho de que " +
+ "una capa wfs compruebe minZoomLevel es una reliquia del " +
+ "pasado. Sin embargo, no podemos eliminarla sin discontinuar " +
+ "probablemente las aplicaciones OL que puedan depender de ello. " +
+ "Así pues estamos haciéndolo obsoleto --la comprobación " +
+ "minZoomLevel se eliminará en la versión 3.0. Utilice el ajuste " +
+ "de resolution min/max en su lugar, tal como se describe aquí: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transacción WFS: ÉXITO ${response}",
+
+ 'commitFailed': "Transacción WFS: FALLÓ ${response}",
+
+ 'googleWarning':
+ "La capa Google no pudo ser cargada correctamente.<br><br>" +
+ "Para evitar este mensaje, seleccione una nueva Capa Base " +
+ "en el selector de capas en la esquina superior derecha.<br><br>" +
+ "Probablemente, esto se debe a que el script de la biblioteca de " +
+ "Google Maps no fue correctamente incluido en su página, o no " +
+ "contiene la clave del API correcta para su sitio.<br><br>" +
+ "Desarrolladores: Para ayudar a hacer funcionar esto correctamente, " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>haga clic aquí</a>",
+
+ 'getLayerWarning':
+ "La capa ${layerType} no pudo ser cargada correctamente.<br><br>" +
+ "Para evitar este mensaje, seleccione una nueva Capa Base " +
+ "en el selector de capas en la esquina superior derecha.<br><br>" +
+ "Probablemente, esto se debe a que el script de " +
+ "la biblioteca ${layerLib} " +
+ "no fue correctamente incluido en su página.<br><br>" +
+ "Desarrolladores: Para ayudar a hacer funcionar esto correctamente, " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>haga clic aquí</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",
+
+ //labels for the graticule control
+ 'W': 'O',
+ 'E': 'E',
+ 'N': 'N',
+ 'S': 'S',
+ 'Graticule': 'Retícula',
+
+ // console message
+ 'reprojectDeprecated':
+ "Está usando la opción 'reproject' en la capa " +
+ "${layerName}. Esta opción es obsoleta: su uso fue diseñado " +
+ "para soportar la visualización de datos sobre mapas base comerciales, " +
+ "pero ahora esa funcionalidad debería conseguirse mediante el soporte " +
+ "de la proyección Spherical Mercator. Más información disponible en " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "Este método es obsoleto y se eliminará en la versión 3.0. " +
+ "Por favor utilice el método ${newMethod} en su lugar.",
+
+ // **** end ****
+ 'end': ''
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/fi.js b/misc/openlayers/lib/OpenLayers/Lang/fi.js
new file mode 100644
index 0000000..078508e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/fi.js
@@ -0,0 +1,32 @@
+/* Translators (2009 onwards):
+ * - Nike
+ * - Str4nd
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["fi"]
+ * Dictionary for Suomi. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["fi"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "Ikilinkki",
+
+ 'Overlays': "Kerrokset",
+
+ 'Base Layer': "Peruskerros",
+
+ 'W': "L",
+
+ 'E': "I",
+
+ 'N': "P",
+
+ 'S': "E"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/fr.js b/misc/openlayers/lib/OpenLayers/Lang/fr.js
new file mode 100644
index 0000000..c8ecdc9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/fr.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Damouns
+ * - IAlex
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["fr"]
+ * Dictionary for Français. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["fr"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Requête non gérée, retournant ${statusText}",
+
+ 'Permalink': "Permalien",
+
+ 'Overlays': "Calques",
+
+ 'Base Layer': "Calque de base",
+
+ 'noFID': "Impossible de mettre à jour un objet sans identifiant (fid).",
+
+ 'browserNotSupported': "Votre navigateur ne supporte pas le rendu vectoriel. Les renderers actuellement supportés sont : \n${renderers}",
+
+ 'minZoomLevelError': "La propriété minZoomLevel doit seulement être utilisée pour des couches FixedZoomLevels-descendent. Le fait que cette couche WFS vérifie la présence de minZoomLevel est une relique du passé. Nous ne pouvons toutefois la supprimer sans casser des applications qui pourraient en dépendre. C\'est pourquoi nous la déprécions -- la vérification du minZoomLevel sera supprimée en version 3.0. A la place, merci d\'utiliser les paramètres de résolutions min/max tel que décrit sur : http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transaction WFS : SUCCES ${response}",
+
+ 'commitFailed': "Transaction WFS : ECHEC ${response}",
+
+ 'googleWarning': "La couche Google n\'a pas été en mesure de se charger correctement.\x3cbr\x3e\x3cbr\x3ePour supprimer ce message, choisissez une nouvelle BaseLayer dans le sélecteur de couche en haut à droite.\x3cbr\x3e\x3cbr\x3eCela est possiblement causé par la non-inclusion de la librairie Google Maps, ou alors parce que la clé de l\'API ne correspond pas à votre site.\x3cbr\x3e\x3cbr\x3eDéveloppeurs : pour savoir comment corriger ceci, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ecliquez ici\x3c/a\x3e",
+
+ 'getLayerWarning': "La couche ${layerType} n\'est pas en mesure de se charger correctement.\x3cbr\x3e\x3cbr\x3ePour supprimer ce message, choisissez une nouvelle BaseLayer dans le sélecteur de couche en haut à droite.\x3cbr\x3e\x3cbr\x3eCela est possiblement causé par la non-inclusion de la librairie ${layerLib}.\x3cbr\x3e\x3cbr\x3eDéveloppeurs : pour savoir comment corriger ceci, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ecliquez ici\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Echelle ~ 1 : ${scaleDenom}",
+
+ 'W': "O",
+
+ 'E': "E",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Vous utilisez l\'option \'reproject\' sur la couche ${layerName}. Cette option est dépréciée : Son usage permettait d\'afficher des données au dessus de couches raster commerciales.Cette fonctionalité est maintenant supportée en utilisant le support de la projection Mercator Sphérique. Plus d\'information est disponible sur http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Cette méthode est dépréciée, et sera supprimée à la version 3.0. Merci d\'utiliser ${newMethod} à la place."
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/fur.js b/misc/openlayers/lib/OpenLayers/Lang/fur.js
new file mode 100644
index 0000000..4e99f32
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/fur.js
@@ -0,0 +1,35 @@
+/* Translators (2009 onwards):
+ * - Klenje
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["fur"]
+ * Dictionary for Furlan. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["fur"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "Leam Permanent",
+
+ 'Overlays': "Livei parsore",
+
+ 'Base Layer': "Livel di base",
+
+ 'browserNotSupported': "Il to sgarfadôr nol supuarte la renderizazion vetoriâl. Al moment a son supuartâts:\n${renderers}",
+
+ 'Scale = 1 : ${scaleDenom}': "Scjale = 1 : ${scaleDenom}",
+
+ 'W': "O",
+
+ 'E': "E",
+
+ 'N': "N",
+
+ 'S': "S"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/gl.js b/misc/openlayers/lib/OpenLayers/Lang/gl.js
new file mode 100644
index 0000000..d6e74d8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/gl.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Toliño
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["gl"]
+ * Dictionary for Galego. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["gl"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Solicitude non xerada; a resposta foi: ${statusText}",
+
+ 'Permalink': "Ligazón permanente",
+
+ 'Overlays': "Capas superpostas",
+
+ 'Base Layer': "Capa base",
+
+ 'noFID': "Non se pode actualizar a funcionalidade para a que non hai FID.",
+
+ 'browserNotSupported': "O seu navegador non soporta a renderización de vectores. Os renderizadores soportados actualmente son:\n${renderers}",
+
+ 'minZoomLevelError': "A propiedade minZoomLevel é só para uso conxuntamente coas capas FixedZoomLevels-descendent. O feito de que esa capa wfs verifique o minZoomLevel é unha reliquia do pasado. Non podemos, con todo, eliminala sen a posibilidade de non romper as aplicacións baseadas en OL que poidan depender dela. Por iso a estamos deixando obsoleta (a comprobación minZoomLevel de embaixo será eliminada na versión 3.0). Por favor, no canto diso use o axuste de resolución mín/máx tal e como está descrito aquí: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transacción WFS: ÉXITO ${response}",
+
+ 'commitFailed': "Transacción WFS: FALLIDA ${response}",
+
+ 'googleWarning': "A capa do Google non puido cargarse correctamente.\x3cbr\x3e\x3cbr\x3ePara evitar esta mensaxe, escolla unha nova capa base no seleccionador de capas na marxe superior dereita.\x3cbr\x3e\x3cbr\x3eProbablemente, isto acontece porque a escritura da libraría do Google Maps ou ben non foi incluída ou ben non contén a clave API correcta para o seu sitio.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: para axudar a facer funcionar isto correctamente, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3epremede aquí\x3c/a\x3e",
+
+ 'getLayerWarning': "A capa ${layerType} foi incapaz de cargarse correctamente.\x3cbr\x3e\x3cbr\x3ePara evitar esta mensaxe, escolla unha nova capa base no seleccionador de capas na marxe superior dereita.\x3cbr\x3e\x3cbr\x3eProbablemente, isto acontece porque a escritura da libraría ${layerLib} non foi ben incluída.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: para axudar a facer funcionar isto correctamente, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3epremede aquí\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",
+
+ 'W': "O",
+
+ 'E': "L",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Está usando a opción \"reproject\" na capa ${layerName}. Esta opción está obsoleta: o seu uso foi deseñado para a visualización de datos sobre mapas base comerciais, pero esta funcionalidade debera agora ser obtida utilizando a proxección Spherical Mercator. Hai dispoñible máis información en http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Este método está obsoleto e será eliminado na versión 3.0. Por favor, no canto deste use ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/gsw.js b/misc/openlayers/lib/OpenLayers/Lang/gsw.js
new file mode 100644
index 0000000..4508501
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/gsw.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Als-Holder
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["gsw"]
+ * Dictionary for Alemannisch. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["gsw"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Nit behandleti Aafrogsruckmäldig ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Iberlagerige",
+
+ 'Base Layer': "Grundcharte",
+
+ 'noFID': "E Feature, wu s kei FID derfir git, cha nit aktualisiert wäre.",
+
+ 'browserNotSupported': "Dyy Browser unterstitzt kei Vektordarstellig. Aktuäll unterstitzti Renderer:\n${renderers}",
+
+ 'minZoomLevelError': "D minZoomLevel-Eigeschaft isch nume dänk fir d Layer, wu vu dr FixedZoomLevels abstamme. Ass dää wfs-Layer minZoomLevel prieft, scih e Relikt us dr Vergangeheit. Mir chenne s aber nit ändere ohni OL_basierti Aawändige villicht kaputt gehn, wu dervu abhänge. Us däm Grund het die Funktion d Eigeschaft \'deprecated\' iberchuu. D minZoomLevel-Priefig unte wird in dr Version 3.0 usegnuu. Bitte verwänd statt däm e min/max-Uflesig wie s do bschriben isch: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS-Transaktion: ERFOLGRYCH ${response}",
+
+ 'commitFailed': "WFS-Transaktion: FÄHLGSCHLAA ${response}",
+
+ 'googleWarning': "Dr Google-Layer het nit korräkt chenne glade wäre.\x3cbr\x3e\x3cbr\x3eGo die Mäldig nimi z kriege, wehl e andere Hintergrundlayer us em LayerSwitcher im rächte obere Ecke.\x3cbr\x3e\x3cbr\x3eDää Fähler git s seli hyfig, wel s Skript vu dr Google-Maps-Bibliothek nit yybunde woren isch oder wel s kei giltige API-Schlissel fir Dyy URL din het.\x3cbr\x3e\x3cbr\x3eEntwickler: Fir Hilf zum korräkte Yybinde vum Google-Layer \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3edoo drucke\x3c/a\x3e",
+
+ 'getLayerWarning': "Dr ${layerType}-Layer het nit korräkt chenne glade wäre.\x3cbr\x3e\x3cbr\x3eGo die Mäldig nimi z kriege, wehl e andere Hintergrundlayer us em LayerSwitcher im rächte obere Ecke.\x3cbr\x3e\x3cbr\x3eDää Fähler git s seli hyfig, wel s Skript vu dr \'${layerLib}\'-Bibliothek nit yybunde woren isch oder wel s kei giltige API-Schlissel fir Dyy URL din het.\x3cbr\x3e\x3cbr\x3eEntwickler: Fir Hilf zum korräkte Yybinde vu Layer \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3edoo drucke\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Maßstab = 1 : ${scaleDenom}",
+
+ 'W': "W",
+
+ 'E': "O",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Du bruchsch d \'reproject\'-Option bim ${layerName}-Layer. Die Option isch nimi giltig: si isch aagleit wore go Date iber kommerziälli Grundcharte lege, aber des sott mer jetz mache mit dr Unterstitzig vu Spherical Mercator. Meh Informatione git s uf http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Die Methode isch veraltet un wird us dr Version 3.0 usegnuu. Bitte verwäbnd statt däm ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/hr.js b/misc/openlayers/lib/OpenLayers/Lang/hr.js
new file mode 100644
index 0000000..d56085a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/hr.js
@@ -0,0 +1,37 @@
+/* Translators (2009 onwards):
+ * - Mvrban
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["hr"]
+ * Dictionary for Hrvatski. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["hr"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Nepodržani zahtjev ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Overlays",
+
+ 'Base Layer': "Osnovna karta",
+
+ 'noFID': "Ne mogu ažurirati znaÄajku za koju ne postoji FID.",
+
+ 'browserNotSupported': "Vaš preglednik ne podržava vektorsko renderiranje. Trenutno podržani rendereri su: ${renderers}",
+
+ 'commitSuccess': "WFS Transakcija: USPJEÅ NA ${response}",
+
+ 'commitFailed': "WFS Transakcija: NEUSPJEÅ NA ${response}",
+
+ 'Scale = 1 : ${scaleDenom}': "Mjerilo = 1 : ${scaleDenom}",
+
+ 'methodDeprecated': "Ova metoda nije odobrena i biti će maknuta u 3.0. Koristite ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/hsb.js b/misc/openlayers/lib/OpenLayers/Lang/hsb.js
new file mode 100644
index 0000000..7a8e888
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/hsb.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Michawiki
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["hsb"]
+ * Dictionary for Hornjoserbsce. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["hsb"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Wotmołwa njewobdźěłaneho naprašowanja ${statusText}",
+
+ 'Permalink': "Trajny wotkaz",
+
+ 'Overlays': "Naworštowanja",
+
+ 'Base Layer': "Zakładna runina",
+
+ 'noFID': "Funkcija, za kotruž FID njeje, njeda so aktualizować.",
+
+ 'browserNotSupported': "Twój wobhladowak wektorowe rysowanje njepodpěruje. Tuchwilu podpěrowane rysowaki su:\n${renderers}",
+
+ 'minZoomLevelError': "Kajkosć minZoomLevel je jenož za wužiwanje z worštami myslena, kotrež wot FixedZoomLevels pochadźeja. Zo tuta woršta wfs za minZoomLevel přepruwuje, je relikt zańdźenosće. Njemóžemy wšak ju wotstronić, bjeztoho zo aplikacije, kotrež na OpenLayers bazěruja a snano tutu kajkosć wužiwaja, hižo njefunguja. Tohodla smy ju jako zestarjenu woznamjenili -- přepruwowanje za minZoomLevel budu so we wersiji 3.0 wotstronjeć. Prošu wužij město toho nastajenje min/max, kaž je tu wopisane: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS-Transakcija: WUSPĚŠNA ${response}",
+
+ 'commitFailed': "WFS-Transakcija: NJEPORADŹENA ${response}",
+
+ 'googleWarning': "WorÅ¡ta Google njemóžeÅ¡e so korektnje zaÄitać.\x3cbr\x3e\x3cbr\x3eZo by tutu zdźělenku wotbyÅ‚, wubjer nowy BaseLayer z wubÄ›ra worÅ¡tow horjeka naprawo.\x3cbr\x3e\x3cbr\x3eNajskerje so to stawa, dokelž skript biblioteki Google Maps pak njebu zapÅ™ijaty pak njewobsahuje korektny kluÄ API za twoje sydÅ‚o.\x3cbr\x3e\x3cbr\x3eWuwiwarjo: Za pomoc ke korektnemu fungowanju worÅ¡tow\n\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3etu kliknyć\x3c/a\x3e",
+
+ 'getLayerWarning': "WorÅ¡ta ${layerType} njemóžeÅ¡e so korektnje zaÄitać.\x3cbr\x3e\x3cbr\x3eZo by tutu zdźělenku wotbyÅ‚, wubjer nowy BaseLayer z wubÄ›ra worÅ¡tow horjeka naprawo.\x3cbr\x3e\x3cbr\x3eNajskerje so to stawa, dokelž skript biblioteki ${layerLib} njebu korektnje zapÅ™ijaty.\x3cbr\x3e\x3cbr\x3eWuwiwarjo: Za pomoc ke korektnemu fungowanju worÅ¡tow\n\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3etu kliknyć\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Měritko = 1 : ${scaleDenom}",
+
+ 'W': "Z",
+
+ 'E': "W",
+
+ 'N': "S",
+
+ 'S': "J",
+
+ 'reprojectDeprecated': "Wužiwaš opciju \"reproject\" wořšty ${layerName}. Tuta opcija je zestarjena: jeje wužiwanje bě myslene, zo by zwobraznjenje datow nad komercielnymi bazowymi kartami podpěrało, ale funkcionalnosć měła so nětko z pomocu Sperical Mercator docpěć. Dalše informacije steja na http://trac.openlayers.org/wiki/SphericalMercator k dispoziciji.",
+
+ 'methodDeprecated': "Tuta metoda je so njeschwaliła a budźe so w 3.0 wotstronjeć. Prošu wužij ${newMethod} město toho."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/hu.js b/misc/openlayers/lib/OpenLayers/Lang/hu.js
new file mode 100644
index 0000000..10c9f5f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/hu.js
@@ -0,0 +1,54 @@
+/* Translators (2009 onwards):
+ * - City-busz
+ * - Glanthor Reviol
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["hu"]
+ * Dictionary for Magyar. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["hu"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Nem kezelt kérés visszatérése ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Rávetítések",
+
+ 'Base Layer': "Alapréteg",
+
+ 'noFID': "Nem frissíthető olyan jellemző, amely nem rendelkezik FID-del.",
+
+ 'browserNotSupported': "A böngészője nem támogatja a vektoros renderelést. A jelenleg támogatott renderelők:\n${renderers}",
+
+ 'minZoomLevelError': "A minZoomLevel tulajdonságot csak a következővel való használatra szánták: FixedZoomLevels-leszármazott fóliák. Ez azt jelenti, hogy a minZoomLevel wfs fólia jelölőnégyzetei már a múlté. Mi azonban nem távolíthatjuk el annak a veszélye nélkül, hogy az esetlegesen ettől függő OL alapú alkalmazásokat tönkretennénk. Ezért ezt érvénytelenítjük -- a minZoomLevel az alul levő jelölőnégyzet a 3.0-s verzióból el lesz távolítva. Kérjük, helyette használja a min/max felbontás beállítást, amelyről az alábbi helyen talál leírást: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS tranzakció: SIKERES ${response}",
+
+ 'commitFailed': "WFS tranzakció: SIKERTELEN ${response}",
+
+ 'googleWarning': "A Google fólia betöltése sikertelen.\x3cbr\x3e\x3cbr\x3eAhhoz, hogy ez az üzenet eltűnjön, válasszon egy új BaseLayer fóliát a jobb felső sarokban található fóliakapcsoló segítségével.\x3cbr\x3e\x3cbr\x3eNagy valószínűséggel ez azért van, mert a Google Maps könyvtár parancsfájlja nem található, vagy nem tartalmazza az Ön oldalához tartozó megfelelő API-kulcsot.\x3cbr\x3e\x3cbr\x3eFejlesztőknek: A helyes működtetésre vonatkozó segítség az alábbi helyen érhető el, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ekattintson ide\x3c/a\x3e",
+
+ 'getLayerWarning': "A(z) ${layerType} fólia nem töltődött be helyesen.\x3cbr\x3e\x3cbr\x3eAhhoz, hogy ez az üzenet eltűnjön, válasszon egy új BaseLayer fóliát a jobb felső sarokban található fóliakapcsoló segítségével.\x3cbr\x3e\x3cbr\x3eNagy valószínűséggel ez azért van, mert a(z) ${layerLib} könyvtár parancsfájlja helytelen.\x3cbr\x3e\x3cbr\x3eFejlesztőknek: A helyes működtetésre vonatkozó segítség az alábbi helyen érhető el, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ekattintson ide\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Lépték = 1 : ${scaleDenom}",
+
+ 'W': "Ny",
+
+ 'E': "K",
+
+ 'N': "É",
+
+ 'S': "D",
+
+ 'reprojectDeprecated': "Ön a \'reproject\' beállítást használja a(z) ${layerName} fólián. Ez a beállítás érvénytelen: használata az üzleti alaptérképek fölötti adatok megjelenítésének támogatására szolgált, de ezt a funkció ezentúl a Gömbi Mercator használatával érhető el. További információ az alábbi helyen érhető el: http://trac.openlayers.org/wiki/SphericalMercator",
+
+ 'methodDeprecated': "Ez a módszer érvénytelenítve lett és a 3.0-s verzióból el lesz távolítva. Használja a(z) ${newMethod} módszert helyette."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ia.js b/misc/openlayers/lib/OpenLayers/Lang/ia.js
new file mode 100644
index 0000000..02dfbc8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ia.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - McDutchie
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["ia"]
+ * Dictionary for Interlingua. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["ia"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Le responsa a un requesta non esseva maneate: ${statusText}",
+
+ 'Permalink': "Permaligamine",
+
+ 'Overlays': "Superpositiones",
+
+ 'Base Layer': "Strato de base",
+
+ 'noFID': "Non pote actualisar un elemento sin FID.",
+
+ 'browserNotSupported': "Tu navigator non supporta le rendition de vectores. Le renditores actualmente supportate es:\n${renderers}",
+
+ 'minZoomLevelError': "Le proprietate minZoomLevel es solmente pro uso con le stratos descendente de FixedZoomLevels. Le facto que iste strato WFS verifica minZoomLevel es un reliquia del passato. Nonobstante, si nos lo remove immediatemente, nos pote rumper applicationes a base de OL que depende de illo. Ergo nos lo declara obsolete; le verification de minZoomLevel in basso essera removite in version 3.0. Per favor usa in su loco le configuration de resolutiones min/max como describite a: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transaction WFS: SUCCESSO ${response}",
+
+ 'commitFailed': "Transaction WFS: FALLEVA ${response}",
+
+ 'googleWarning': "Le strato Google non poteva esser cargate correctemente.\x3cbr\x3e\x3cbr\x3ePro disfacer te de iste message, selige un nove BaseLayer in le selector de strato in alto a dextra.\x3cbr\x3e\x3cbr\x3eMulto probabilemente, isto es proque le script del libreria de Google Maps non esseva includite o non contine le clave API correcte pro tu sito.\x3cbr\x3e\x3cbr\x3eDisveloppatores: Pro adjuta de corriger isto, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclicca hic\x3c/a",
+
+ 'getLayerWarning': "Le strato ${layerType} non poteva esser cargate correctemente.\x3cbr\x3e\x3cbr\x3ePro disfacer te de iste message, selige un nove BaseLayer in le selector de strato in alto a dextra.\x3cbr\x3e\x3cbr\x3eMulto probabilemente, isto es proque le script del libreria de ${layerLib} non esseva correctemente includite.\x3cbr\x3e\x3cbr\x3eDisveloppatores: Pro adjuta de corriger isto, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclicca hic\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Scala = 1 : ${scaleDenom}",
+
+ 'W': "W",
+
+ 'E': "E",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Tu usa le option \'reproject\' in le strato ${layerName} layer. Iste option es obsolescente: illo esseva pro poter monstrar datos super cartas de base commercial, ma iste functionalitate pote ora esser attingite con le uso de Spherical Mercator. Ulterior information es disponibile a http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Iste methodo ha essite declarate obsolescente e essera removite in version 3.0. Per favor usa ${newMethod} in su loco."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/id.js b/misc/openlayers/lib/OpenLayers/Lang/id.js
new file mode 100644
index 0000000..49b7f43
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/id.js
@@ -0,0 +1,54 @@
+/* Translators (2009 onwards):
+ * - Irwangatot
+ * - IvanLanin
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["id"]
+ * Dictionary for Bahasa Indonesia. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["id"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Permintaan yang tak tertangani menghasilkan ${statusText}",
+
+ 'Permalink': "Pranala permanen",
+
+ 'Overlays': "Hamparan",
+
+ 'Base Layer': "Lapisan Dasar",
+
+ 'noFID': "Tidak dapat memperbarui fitur yang tidak memiliki FID.",
+
+ 'browserNotSupported': "Peramban Anda tidak mendukung penggambaran vektor. Penggambar yang didukung saat ini adalah:\n${renderers}",
+
+ 'minZoomLevelError': "Properti minZoomLevel hanya ditujukan bekerja dengan lapisan FixedZoomLevels-descendent. Pengecekan minZoomLevel oleh lapisan wfs adalah peninggalan masa lalu. Kami tidak dapat menghapusnya tanpa kemungkinan merusak aplikasi berbasis OL yang mungkin bergantung padanya. Karenanya, kami menganggapnya tidak berlaku -- Cek minZoomLevel di bawah ini akan dihapus pada 3.0. Silakan gunakan penyetelan resolusi min/maks seperti dijabarkan di sini: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS Transaksi: BERHASIL ${respon}",
+
+ 'commitFailed': "WFS Transaksi: GAGAL ${respon}",
+
+ 'googleWarning': "Lapisan Google tidak dapat dimuat dengan benar.\x3cbr\x3e\x3cbr\x3eUntuk menghilangkan pesan ini, pilih suatu BaseLayer baru melalui penukar lapisan (layer switcher) di ujung kanan atas.\x3cbr\x3e\x3cbr\x3eKemungkinan besar ini karena pustaka skrip Google Maps tidak disertakan atau tidak mengandung kunci API yang tepat untuk situs Anda.\x3cbr\x3e\x3cbr\x3ePengembang: Untuk bantuan mengatasi masalah ini, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eklik di sini\x3c/a\x3e",
+
+ 'getLayerWarning': "Lapisan ${layerType} tidak dapat dimuat dengan benar.\x3cbr\x3e\x3cbr\x3eUntuk menghilangkan pesan ini, pilih suatu BaseLayer baru melalui penukar lapisan (layer switcher) di ujung kanan atas.\x3cbr\x3e\x3cbr\x3eKemungkinan besar ini karena pustaka skrip Google Maps tidak disertakan dengan benar.\x3cbr\x3e\x3cbr\x3ePengembang: Untuk bantuan mengatasi masalah ini, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik di sini\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Sekala = 1 : ${scaleDenom}",
+
+ 'W': "B",
+
+ 'E': "T",
+
+ 'N': "U",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Anda menggunakan opsi \'reproject\' pada lapisan ${layerName}. Opsi ini telah ditinggalkan: penggunaannya dirancang untuk mendukung tampilan data melalui peta dasar komersial, tapi fungsionalitas tersebut saat ini harus dilakukan dengan menggunakan dukungan Spherical Mercator. Informasi lebih lanjut tersedia di http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Metode ini telah usang dan akan dihapus di 3.0. Sebaliknya, harap gunakan ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/io.js b/misc/openlayers/lib/OpenLayers/Lang/io.js
new file mode 100644
index 0000000..6f2263a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/io.js
@@ -0,0 +1,19 @@
+/* Translators (2009 onwards):
+ * - Malafaya
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["io"]
+ * Dictionary for Ido. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["io"] = OpenLayers.Util.applyDefaults({
+
+ 'Scale = 1 : ${scaleDenom}': "Skalo = 1 : ${scaleDenom}"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/is.js b/misc/openlayers/lib/OpenLayers/Lang/is.js
new file mode 100644
index 0000000..e2bc536
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/is.js
@@ -0,0 +1,27 @@
+/* Translators (2009 onwards):
+ * - Ævar Arnfjörð Bjarmason
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["is"]
+ * Dictionary for Ãslenska. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["is"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "Varanlegur tengill",
+
+ 'Overlays': "Þekjur",
+
+ 'Base Layer': "Grunnlag",
+
+ 'Scale = 1 : ${scaleDenom}': "Skali = 1 : ${scaleDenom}",
+
+ 'methodDeprecated': "Þetta fall hefur verið úrelt og verður fjarlægt í 3.0. Notaðu ${newMethod} í staðin."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/it.js b/misc/openlayers/lib/OpenLayers/Lang/it.js
new file mode 100644
index 0000000..a56ef0f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/it.js
@@ -0,0 +1,80 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["it"]
+ * Dictionary for Italian. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang.it = {
+
+ 'unhandledRequest': "Codice di ritorno della richiesta ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Overlays",
+
+ 'Base Layer': "Livello base",
+
+ 'noFID': "Impossibile aggiornare un elemento grafico che non abbia il FID.",
+
+ 'browserNotSupported':
+ "Il tuo browser non supporta il rendering vettoriale. I renderizzatore attualemnte supportati sono:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "La proprietà minZoomLevel è da utilizzare solamente " +
+ "con livelli che abbiano FixedZoomLevels. Il fatto che " +
+ "questo livello wfs controlli la proprietà minZoomLevel è " +
+ "un retaggio del passato. Non possiamo comunque rimuoverla " +
+ "senza rompere le vecchie applicazioni che dipendono su di essa." +
+ "Quindi siamo costretti a deprecarla -- minZoomLevel " +
+ "e sarà rimossa dalla vesione 3.0. Si prega di utilizzare i " +
+ "settaggi di risoluzione min/max come descritto qui: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transazione WFS: SUCCESS ${response}",
+
+ 'commitFailed': "Transazione WFS: FAILED ${response}",
+
+ 'googleWarning':
+ "Il livello Google non è riuscito a caricare correttamente.<br><br>" +
+ "Per evitare questo messaggio, seleziona un nuovo BaseLayer " +
+ "nel selettore di livelli nell'angolo in alto a destra.<br><br>" +
+ "Più precisamente, ciò accade perchè la libreria Google Maps " +
+ "non è stata inclusa nella pagina, oppure non contiene la " +
+ "corretta API key per il tuo sito.<br><br>" +
+ "Sviluppatori: Per aiuto su come farlo funzionare correttamente, " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>clicca qui</a>",
+
+ 'getLayerWarning':
+ "Il livello ${layerType} non è riuscito a caricare correttamente.<br><br>" +
+ "Per evitare questo messaggio, seleziona un nuovo BaseLayer " +
+ "nel selettore di livelli nell'angolo in alto a destra.<br><br>" +
+ "Più precisamente, ciò accade perchè la libreria ${layerLib} " +
+ "non è stata inclusa nella pagina.<br><br>" +
+ "Sviluppatori: Per aiuto su come farlo funzionare correttamente, " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>clicca qui</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "Scala = 1 : ${scaleDenom}",
+
+ // console message
+ 'reprojectDeprecated':
+ "Stai utilizzando l'opzione 'reproject' sul livello ${layerName}. " +
+ "Questa opzione è deprecata: il suo utilizzo è stato introdotto per" +
+ "supportare il disegno dei dati sopra mappe commerciali, ma tale " +
+ "funzionalità dovrebbe essere ottenuta tramite l'utilizzo della proiezione " +
+ "Spherical Mercator. Per maggiori informazioni consultare qui " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "Questo metodo è stato deprecato e sarà rimosso dalla versione 3.0. " +
+ "Si prega di utilizzare il metodo ${newMethod} in alternativa.",
+
+ 'end': ''
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ja.js b/misc/openlayers/lib/OpenLayers/Lang/ja.js
new file mode 100644
index 0000000..75cc1e3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ja.js
@@ -0,0 +1,54 @@
+/* Translators (2009 onwards):
+ * - Fryed-peach
+ * - Mage Whopper
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["ja"]
+ * Dictionary for 日本語. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["ja"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "未処ç†ã®è¦æ±‚㯠${statusText} ã‚’è¿”ã—ã¾ã™",
+
+ 'Permalink': "パーマリンク",
+
+ 'Overlays': "オーãƒãƒ¼ãƒ¬ã‚¤",
+
+ 'Base Layer': "基底レイヤー",
+
+ 'noFID': "FID ã®ãªã„地物ã¯æ›´æ–°ã§ãã¾ã›ã‚“。",
+
+ 'browserNotSupported': "ã‚ãªãŸã®ãƒ–ラウザã¯ãƒ™ã‚¯ã‚¿ãƒ¼ã‚°ãƒ©ãƒ•ã‚£ãƒƒã‚¯ã‚¹ã®æ写ã«å¯¾å¿œã—ã¦ã„ã¾ã›ã‚“。ç¾æ™‚点ã§å¯¾å¿œã—ã¦ã„るソフトウェアã¯ä»¥ä¸‹ã®ã‚‚ã®ã§ã™ã€‚\n${renderers}",
+
+ 'minZoomLevelError': "minZoomLevel プロパティ㯠FixedZoomLevels を継承ã™ã‚‹ãƒ¬ã‚¤ãƒ¤ãƒ¼ã§ã®ä½¿ç”¨ã®ã¿ã‚’想定ã—ã¦ã„ã¾ã™ã€‚ã“ã® minZoomLevel ã«å¯¾ã™ã‚‹ WFS レイヤーã®æ¤œæŸ»ã¯æ­´å²çš„ãªã‚‚ã®ã§ã™ã€‚ã—ã‹ã—ãªãŒã‚‰ã€ã“ã®æ¤œæŸ»ã‚’除去ã™ã‚‹ã¨ãã‚Œã«ä¾å­˜ã™ã‚‹ OpenLayers ベースã®ã‚¢ãƒ—リケーションを破壊ã—ã¦ã—ã¾ã†å¯èƒ½æ€§ãŒã‚ã‚Šã¾ã™ã€‚よã£ã¦å»ƒæ­¢ãŒäºˆå®šã•ã‚Œã¦ãŠã‚Šã€ã“ã® minZoomLevel 検査ã¯ãƒãƒ¼ã‚¸ãƒ§ãƒ³3.0ã§é™¤åŽ»ã•ã‚Œã¾ã™ã€‚代ã‚ã‚Šã«ã€http://trac.openlayers.org/wiki/SettingZoomLevels ã§è§£èª¬ã•ã‚Œã¦ã„ã‚‹ã€æœ€å°ãŠã‚ˆã³æœ€å¤§è§£åƒåº¦è¨­å®šã‚’使用ã—ã¦ãã ã•ã„。",
+
+ 'commitSuccess': "WFS トランザクション: æˆåŠŸ ${response}",
+
+ 'commitFailed': "WFS トランザクション: 失敗 ${response}",
+
+ 'googleWarning': "Google レイヤーãŒæ­£ã—ã読ã¿è¾¼ã¿ã‚’è¡Œãˆã¾ã›ã‚“ã§ã—ãŸã€‚\x3cbr\x3e\x3cbr\x3eã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’消ã™ã«ã¯ã€å³ä¸Šã®éš…ã«ã‚るレイヤー切り替ãˆéƒ¨åˆ†ã§æ–°ã—ã„基底レイヤーをé¸ã‚“ã§ãã ã•ã„。\x3cbr\x3e\x3cbr\x3eãŠãらãã€ã“れ㯠Google マップ用ライブラリã®ã‚¹ã‚¯ãƒªãƒ—トãŒçµ„ã¿è¾¼ã¾ã‚Œã¦ã„ãªã„ã‹ã€ã‚ãªãŸã®ã‚µã‚¤ãƒˆã«å¯¾å¿œã™ã‚‹æ­£ã—ã„ API キーãŒè¨­å®šã•ã‚Œã¦ã„ãªã„ãŸã‚ã§ã™ã€‚\x3cbr\x3e\x3cbr\x3e開発者ã®æ–¹ã¸: æ­£ã—ã„動作をã•ã›ã‚‹ãŸã‚ã«\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eã“ã¡ã‚‰ã®ã‚¦ã‚£ã‚­\x3c/a\x3eã‚’å‚ç…§ã—ã¦ãã ã•ã„。",
+
+ 'getLayerWarning': "${layerType} レイヤーãŒæ­£ã—ã読ã¿è¾¼ã¿ã‚’è¡Œãˆã¾ã›ã‚“ã§ã—ãŸã€‚\x3cbr\x3e\x3cbr\x3eã“ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’消ã™ã«ã¯ã€å³ä¸Šã®éš…ã«ã‚るレイヤー切り替ãˆéƒ¨åˆ†ã§æ–°ã—ã„基底レイヤーをé¸ã‚“ã§ãã ã•ã„。\x3cbr\x3e\x3cbr\x3eãŠãらãã€ã“れ㯠${layerLib} ライブラリã®ã‚¹ã‚¯ãƒªãƒ—トãŒæ­£ã—ã組ã¿è¾¼ã¾ã‚Œã¦ã„ãªã„ãŸã‚ã§ã™ã€‚\x3cbr\x3e\x3cbr\x3e開発者ã®æ–¹ã¸: æ­£ã—ã„動作をã•ã›ã‚‹ãŸã‚ã«\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eã“ã¡ã‚‰ã®ã‚¦ã‚£ã‚­\x3c/a\x3eã‚’å‚ç…§ã—ã¦ãã ã•ã„。",
+
+ 'Scale = 1 : ${scaleDenom}': "縮尺 = 1 : ${scaleDenom}",
+
+ 'W': "西",
+
+ 'E': "æ±",
+
+ 'N': "北",
+
+ 'S': "å—",
+
+ 'reprojectDeprecated': "ã‚ãªãŸã¯ã€Œ${layerName}ã€ãƒ¬ã‚¤ãƒ¤ãƒ¼ã§ reproject オプションを使ã£ã¦ã„ã¾ã™ã€‚ã“ã®ã‚ªãƒ—ションã¯å•†ç”¨ã®åŸºåº•åœ°å›³ä¸Šã«æƒ…報を表示ã™ã‚‹ç›®çš„ã§è¨­è¨ˆã•ã‚Œã¾ã—ãŸãŒã€ç¾åœ¨ã§ã¯ãã®æ©Ÿèƒ½ã¯ Spherical Mercator サãƒãƒ¼ãƒˆã‚’利用ã—ã¦å®Ÿç¾ã•ã‚Œã¦ãŠã‚Šã€ã“ã®ã‚ªãƒ—ションã®ä½¿ç”¨ã¯éžæŽ¨å¥¨ã§ã™ã€‚追加ã®æƒ…報㯠http://trac.openlayers.org/wiki/SphericalMercator ã§å…¥æ‰‹ã§ãã¾ã™ã€‚",
+
+ 'methodDeprecated': "ã“ã®ãƒ¡ã‚½ãƒƒãƒ‰ã¯å»ƒæ­¢ãŒäºˆå®šã•ã‚Œã¦ãŠã‚Šã€ãƒãƒ¼ã‚¸ãƒ§ãƒ³3.0ã§é™¤åŽ»ã•ã‚Œã¾ã™ã€‚代ã‚ã‚Šã« ${newMethod} を使用ã—ã¦ãã ã•ã„。"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/km.js b/misc/openlayers/lib/OpenLayers/Lang/km.js
new file mode 100644
index 0000000..8835177
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/km.js
@@ -0,0 +1,23 @@
+/* Translators (2009 onwards):
+ * - ážœáŸážŽážáž¶ážšáž·áž‘្ធ
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["km"]
+ * Dictionary for ភាសាážáŸ’មែរ. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["km"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "ážáŸ†ážŽáž—្ជាប់អចិន្ážáŸ’រៃយáŸ",
+
+ 'Base Layer': "ស្រទាប់បាážâ€‹",
+
+ 'Scale = 1 : ${scaleDenom}': "មាážáŸ’រដ្ឋាន = ១ ៖ ${scaleDenom}"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ksh.js b/misc/openlayers/lib/OpenLayers/Lang/ksh.js
new file mode 100644
index 0000000..3ba94f4
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ksh.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Purodha
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["ksh"]
+ * Dictionary for Ripoarisch. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["ksh"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Met dä Antwoot op en Aanfrooch ham_mer nix aanjefange: ${statusText}",
+
+ 'Permalink': "Lengk op Duuer",
+
+ 'Overlays': "Drövver jelaat",
+
+ 'Base Layer': "Jrund-Nivoh",
+
+ 'noFID': "En Saach, woh kein \x3ci lang=\"en\"\x3eFID\x3c/i\x3e för doh es, löht sesch nit ändere.",
+
+ 'browserNotSupported': "Dinge Brauser kann kein Väktore ußjävve. De Zoote Ußjaabe, di em Momang jon, sen:\n${renderers}",
+
+ 'minZoomLevelError': "De Eijeschaff „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“ es bloß doför jedaach, dat mer se met dä Nivvohß bruch, di vun \x3ccode lang=\"en\"\x3eFixedZoomLevels\x3c/code\x3e affhange don. Dat dat \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Nivvoh övverhoup de Eijeschaff „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“ pröhfe deiht, es noch övveresch vun fröhjer. Mer künne dat ävver jez nit fott lohße, oohne dat mer Jevaa loufe, dat Aanwendunge vun OpenLayers nit mieh loufe, di sesch doh velleijsch noch drop am verlohße sin. Dröm sare mer, dat mer et nit mieh han welle, un de „\x3ccode lang=\"en\"\x3eminZoomLevel\x3c/code\x3e“-Eijeschaff weed hee vun de Version 3.0 af nit mieh jeprööf wäde. Nemm doför de Enstellung för de hühßte un de kleinßte Oplöhsung, esu wi et en http://trac.openlayers.org/wiki/SettingZoomLevels opjeschrevve es.",
+
+ 'commitSuccess': "Dä \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Vörjang es joot jeloufe: ${response}",
+
+ 'commitFailed': "Dä \x3ci lang=\"en\"\x3eWFS\x3c/i\x3e-Vörjang es scheif jejange: ${response}",
+
+ 'googleWarning': "Dat Nivvoh \x3ccode lang=\"en\"\x3eGoogle\x3c/code\x3e kunnt nit reschtesch jelaade wääde.\x3cbr /\x3e\x3cbr /\x3eÖm hee di Nohreesch loß ze krijje, donn en ander Jrund-Nivvoh ußsöhke, rähß bovve en de Äk.\x3cbr /\x3e\x3cbr /\x3eWascheinlesch es dat wiel dat \x3ci lang=\"en\"\x3eGoogle-Maps\x3c/i\x3e-Skrepp entweeder nit reschtesch enjebonge wood, udder nit dä reschtejje \x3ci lang=\"en\"\x3eAPI\x3c/i\x3e-Schlößel för Ding Web-ßait scheke deiht.\x3cbr /\x3e\x3cbr /\x3eFör Projrammierer jidd_et Hölp do_drövver, \x3ca href=\"http://trac.openlayers.org/wiki/Google\" target=\"_blank\"\x3ewi mer dat aan et Loufe brengk\x3c/a\x3e.",
+
+ 'getLayerWarning': "Dat Nivvoh \x3ccode\x3e${layerType}\x3c/code\x3e kunnt nit reschtesch jelaade wääde.\x3cbr /\x3e\x3cbr /\x3eÖm hee di Nohreesch loß ze krijje, donn en ander Jrund-Nivvoh ußsöhkre, rähß bovve en de Äk.\x3cbr /\x3e\x3cbr /\x3eWascheinlesch es dat, wiel dat Skrepp \x3ccode\x3e${layerLib}\x3c/code\x3e nit reschtesch enjebonge wood.\x3cbr /\x3e\x3cbr /\x3eFör Projrammierer jidd_Et Hölp do_drövver, \x3ca href=\"http://trac.openlayers.org/wiki/${layerLib}\" target=\"_blank\"\x3ewi mer dat aan et Loufe brengk\x3c/a\x3e.",
+
+ 'Scale = 1 : ${scaleDenom}': "Mohßshtaab = 1 : ${scaleDenom}",
+
+ 'W': "W",
+
+ 'E': "O",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Do bruchs de Ußwahl \x3ccode\x3ereproject\x3c/code\x3e op däm Nivvoh \x3ccode\x3e${layerName}\x3c/code\x3e. Di Ußwahl es nit mieh jähn jesinn. Se wohr doför jedaach, öm Date op jeschääfsmäßesch eruß jejovve Kaate bovve drop ze moole, wat ävver enzwesche besser met dä Öngershtözung för de ßfääresche Mäkaator Beldscher jeiht. Doh kanns De mieh drövver fenge op dä Sigg: http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Hee di Metood es nim_mih aktoäll un et weed se en dä Version 3.0 nit mieh jävve. Nemm \x3ccode\x3e${newMethod}\x3c/code\x3e doföör."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/lt.js b/misc/openlayers/lib/OpenLayers/Lang/lt.js
new file mode 100644
index 0000000..e1f7897
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/lt.js
@@ -0,0 +1,47 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["lt"]
+ * Dictionary for Lithuanian. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang['lt'] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Neapdorota užklausa gražino ${statusText}",
+
+ 'Permalink': "Pastovi nuoroda",
+
+ 'Overlays': "Papildomi sluoksniai",
+
+ 'Base Layer': "Pagrindinis sluoksnis",
+
+ 'noFID': "Negaliu atnaujinti objekto, kuris neturi FID.",
+
+ 'browserNotSupported':
+ "Jūsų naršyklė nemoka parodyti vektorių. Šiuo metu galima naudotis tokiais rodymo varikliais:\n{renderers}",
+
+ 'commitSuccess': "WFS Tranzakcija: PAVYKO ${response}",
+
+ 'commitFailed': "WFS Tranzakcija: ŽLUGO ${response}",
+
+ 'Scale = 1 : ${scaleDenom}': "Mastelis = 1 : ${scaleDenom}",
+
+ //labels for the graticule control
+ 'W': 'V',
+ 'E': 'R',
+ 'N': 'Å ',
+ 'S': 'P',
+ 'Graticule': 'Tinklelis',
+
+ // console message
+ 'methodDeprecated':
+ "Šis metodas yra pasenęs ir 3.0 versijoje bus pašalintas. " +
+ "Prašome naudoti ${newMethod}.",
+
+ // **** end ****
+ 'end': ''
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/nb.js b/misc/openlayers/lib/OpenLayers/Lang/nb.js
new file mode 100644
index 0000000..24a4a7e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/nb.js
@@ -0,0 +1,82 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["nb"]
+ * Dictionary for norwegian bokmål (Norway). Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["nb"] = {
+
+ 'unhandledRequest': "Ubehandlet forespørsel returnerte ${statusText}",
+
+ 'Permalink': "Kobling til denne siden",
+
+ 'Overlays': "Kartlag",
+
+ 'Base Layer': "Bakgrunnskart",
+
+ 'noFID': "Kan ikke oppdatere et feature (et objekt) som ikke har FID.",
+
+ 'browserNotSupported':
+ "Din nettleser støtter ikke vektortegning. Tegnemetodene som støttes er:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "Egenskapen minZoomLevel er kun ment til bruk på lag " +
+ "basert på FixedZoomLevels. At dette wfs-laget sjekker " +
+ "minZoomLevel er en etterlevning fra tidligere versjoner. Det kan dog ikke " +
+ "tas bort uten å risikere at OL-baserte applikasjoner " +
+ "slutter å virke, så det er merket som foreldet: " +
+ "minZoomLevel i sjekken nedenfor vil fjernes i 3.0. " +
+ "Vennligst bruk innstillingene for min/maks oppløsning " +
+ "som er beskrevet her: "+
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS-transaksjon: LYKTES ${response}",
+
+ 'commitFailed': "WFS-transaksjon: MISLYKTES ${response}",
+
+ 'googleWarning':
+ "Google-laget kunne ikke lastes.<br><br>" +
+ "Bytt til et annet bakgrunnslag i lagvelgeren i " +
+ "øvre høyre hjørne for å slippe denne meldingen.<br><br>" +
+ "Sannsynligvis forårsakes feilen av at Google Maps-biblioteket " +
+ "ikke er riktig inkludert på nettsiden, eller at det ikke er " +
+ "angitt riktig API-nøkkel for nettstedet.<br><br>" +
+ "Utviklere: For hjelp til å få dette til å virke se "+
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>her</a>.",
+
+ 'getLayerWarning':
+ "${layerType}-laget kunne ikke lastes.<br><br>" +
+ "Bytt til et annet bakgrunnslag i lagvelgeren i " +
+ "øvre høyre hjørne for å slippe denne meldingen.<br><br>" +
+ "Sannsynligvis forårsakes feilen av at " +
+ "${layerLib}-biblioteket ikke var riktig inkludert " +
+ "på nettsiden.<br><br>" +
+ "Utviklere: For hjelp til å få dette til å virke se " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>her</a>.",
+
+ 'Scale = 1 : ${scaleDenom}': "<strong>Skala</strong> 1 : ${scaleDenom}",
+
+ // console message
+ 'reprojectDeprecated':
+ "Du bruker innstillingen 'reproject' på laget ${layerName}. " +
+ "Denne innstillingen er foreldet, den var ment for å støtte " +
+ "visning av kartdata over kommersielle bakgrunnskart, men det " +
+ "bør nå gjøres med støtten for Spherical Mercator. Mer informasjon " +
+ "finnes på http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "Denne metoden er markert som foreldet og vil bli fjernet i 3.0. " +
+ "Vennligst bruk ${newMethod} i stedet.",
+
+ 'end': ''
+};
+
+OpenLayers.Lang["no"] = OpenLayers.Lang["nb"];
diff --git a/misc/openlayers/lib/OpenLayers/Lang/nds.js b/misc/openlayers/lib/OpenLayers/Lang/nds.js
new file mode 100644
index 0000000..9be21b9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/nds.js
@@ -0,0 +1,37 @@
+/* Translators (2009 onwards):
+ * - Slomox
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["nds"]
+ * Dictionary for Plattdüütsch. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["nds"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Unbehannelt Trüchmellels för de Anfraag ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Overlays",
+
+ 'Base Layer': "Achtergrundkoort",
+
+ 'noFID': "En Feature, dat keen FID hett, kann nich aktuell maakt warrn.",
+
+ 'browserNotSupported': "Dien Browser ünnerstütt keen Vektorbiller. Ünnerstütt Renderers:\n${renderers}",
+
+ 'commitSuccess': "WFS-Transakschoon: hett klappt ${response}",
+
+ 'commitFailed': "WFS-Transakschoon: hett nich klappt ${response}",
+
+ 'Scale = 1 : ${scaleDenom}': "Skaal = 1 : ${scaleDenom}",
+
+ 'methodDeprecated': "Disse Methood is oold un schall dat in 3.0 nich mehr geven. Bruuk dor man beter ${newMethod} för."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/nl.js b/misc/openlayers/lib/OpenLayers/Lang/nl.js
new file mode 100644
index 0000000..8888716
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/nl.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Siebrand
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["nl"]
+ * Dictionary for Nederlands. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["nl"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Het verzoek is niet afgehandeld met de volgende melding: ${statusText}",
+
+ 'Permalink': "Permanente verwijzing",
+
+ 'Overlays': "Overlays",
+
+ 'Base Layer': "Achtergrondkaart",
+
+ 'noFID': "Een optie die geen FID heeft kan niet bijgewerkt worden.",
+
+ 'browserNotSupported': "Uw browser ondersteunt het weergeven van vectoren niet.\nMomenteel ondersteunde weergavemogelijkheden:\n${renderers}",
+
+ 'minZoomLevelError': "De eigenschap minZoomLevel is alleen bedoeld voor gebruik lagen met die afstammen van FixedZoomLevels-lagen.\nDat deze WFS-laag minZoomLevel controleert, is een overblijfsel uit het verleden.\nWe kunnen deze controle echter niet verwijderen zonder op OL gebaseerde applicaties die hervan afhankelijk zijn stuk te maken.\nDaarom heeft deze functionaliteit de eigenschap \'deprecated\' gekregen - de minZoomLevel wordt verwijderd in versie 3.0.\nGebruik in plaats van deze functie de mogelijkheid om min/max voor resolutie in te stellen zoals op de volgende pagina wordt beschreven:\nhttp://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS-transactie: succesvol ${response}",
+
+ 'commitFailed': "WFS-transactie: mislukt ${response}",
+
+ 'googleWarning': "De Google-Layer kon niet correct geladen worden.\x3cbr /\x3e\x3cbr /\x3e\nOm deze melding niet meer te krijgen, moet u een andere achtergrondkaart kiezen in de laagwisselaar in de rechterbovenhoek.\x3cbr /\x3e\x3cbr /\x3e\nDit komt waarschijnlijk doordat de bibliotheek ${layerLib} niet correct ingevoegd is.\x3cbr /\x3e\x3cbr /\x3e\nOntwikkelaars: \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik hier\x3c/a\x3e om dit werkend te krijgen.",
+
+ 'getLayerWarning': "De laag ${layerType} kon niet goed geladen worden.\x3cbr /\x3e\x3cbr /\x3e\nOm deze melding niet meer te krijgen, moet u een andere achtergrondkaart kiezen in de laagwisselaar in de rechterbovenhoek.\x3cbr /\x3e\x3cbr /\x3e\nDit komt waarschijnlijk doordat de bibliotheek ${layerLib} niet correct is ingevoegd.\x3cbr /\x3e\x3cbr /\x3e\nOntwikkelaars: \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklik hier\x3c/a\x3e om dit werkend te krijgen.",
+
+ 'Scale = 1 : ${scaleDenom}': "Schaal = 1 : ${scaleDenom}",
+
+ 'W': "W",
+
+ 'E': "O",
+
+ 'N': "N",
+
+ 'S': "Z",
+
+ 'reprojectDeprecated': "U gebruikt de optie \'reproject\' op de laag ${layerName}.\nDeze optie is vervallen: deze optie was ontwikkeld om gegevens over commerciële basiskaarten weer te geven, maar deze functionaliteit wordt nu bereikt door ondersteuning van Spherical Mercator.\nMeer informatie is beschikbaar op http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Deze methode is verouderd en wordt verwijderd in versie 3.0.\nGebruik ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/nn.js b/misc/openlayers/lib/OpenLayers/Lang/nn.js
new file mode 100644
index 0000000..cb47f31
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/nn.js
@@ -0,0 +1,19 @@
+/* Translators (2009 onwards):
+ * - Harald Khan
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["nn"]
+ * Dictionary for ‪Norsk (nynorsk)‬. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["nn"] = OpenLayers.Util.applyDefaults({
+
+ 'Scale = 1 : ${scaleDenom}': "Skala = 1 : ${scaleDenom}"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/oc.js b/misc/openlayers/lib/OpenLayers/Lang/oc.js
new file mode 100644
index 0000000..244c290
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/oc.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Cedric31
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["oc"]
+ * Dictionary for Occitan. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["oc"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Requèsta pas gerida, retorna ${statusText}",
+
+ 'Permalink': "Permaligam",
+
+ 'Overlays': "Calques",
+
+ 'Base Layer': "Calc de basa",
+
+ 'noFID': "Impossible de metre a jorn un objècte sens identificant (fid).",
+
+ 'browserNotSupported': "Vòstre navegidor supòrta pas lo rendut vectorial. Los renderers actualament suportats son : \n${renderers}",
+
+ 'minZoomLevelError': "La proprietat minZoomLevel deu èsser utilizada solament per de jaces FixedZoomLevels-descendent. Lo fach qu\'aqueste jaç WFS verifique la preséncia de minZoomLevel es una relica del passat. Çaquelà, la podèm suprimir sens copar d\'aplicacions que ne poirián dependre. Es per aquò que la depreciam -- la verificacion del minZoomLevel serà suprimida en version 3.0. A la plaça, mercés d\'utilizar los paramètres de resolucions min/max tal coma descrich sus : http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transaccion WFS : SUCCES ${response}",
+
+ 'commitFailed': "Transaccion WFS : FRACAS ${response}",
+
+ 'googleWarning': "Lo jaç Google es pas estat en mesura de se cargar corrèctament.\x3cbr\x3e\x3cbr\x3ePer suprimir aqueste messatge, causissètz una BaseLayer novèla dins lo selector de jaç en naut a drecha.\x3cbr\x3e\x3cbr\x3eAquò es possiblament causat par la non-inclusion de la librariá Google Maps, o alara perque que la clau de l\'API correspond pas a vòstre site.\x3cbr\x3e\x3cbr\x3eDesvolopaires : per saber cossí corregir aquò, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclicatz aicí\x3c/a\x3e",
+
+ 'getLayerWarning': "Lo jaç ${layerType} es pas en mesura de se cargar corrèctament.\x3cbr\x3e\x3cbr\x3ePer suprimir aqueste messatge, causissètz una BaseLayer novèla dins lo selector de jaç en naut a drecha.\x3cbr\x3e\x3cbr\x3eAquò es possiblament causat per la non-inclusion de la librariá ${layerLib}.\x3cbr\x3e\x3cbr\x3eDesvolopaires : per saber cossí corregir aquí, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclicatz aicí\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Escala ~ 1 : ${scaleDenom}",
+
+ 'W': "O",
+
+ 'E': "È",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Utilizatz l\'opcion \'reproject\' sul jaç ${layerName}. Aquesta opcion es despreciada : Son usatge permetiá d\'afichar de donadas al dessús de jaces raster comercials. Aquesta foncionalitat ara es suportada en utilizant lo supòrt de la projeccion Mercator Esferica. Mai d\'informacion es disponibla sus http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Aqueste metòde es despreciada, e serà suprimida a la version 3.0. Mercés d\'utilizar ${newMethod} a la plaça."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/pl.js b/misc/openlayers/lib/OpenLayers/Lang/pl.js
new file mode 100644
index 0000000..dfaa42a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/pl.js
@@ -0,0 +1,89 @@
+/* Translators:
+ * - Arkadiusz Grabka
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["pl"]
+ * Dictionary for Polish. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["pl"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Nieobsługiwane żądanie zwróciło ${statusText}",
+
+ 'Permalink': "Permalink",
+
+ 'Overlays': "Nakładki",
+
+ 'Base Layer': "Warstwa podstawowa",
+
+ 'noFID': "Nie można zaktualizować funkcji, dla których nie ma FID.",
+
+ 'browserNotSupported':
+ "Twoja przeglądarka nie obsługuje renderowania wektorów. Obecnie obsługiwane renderowanie to:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "Właściwość minZoomLevel jest przeznaczona tylko do użytku " +
+ "z warstwami FixedZoomLevels-descendent." +
+ "Warstwa wfs, która sprawdza minZoomLevel jest reliktem przeszłości." +
+ "Nie możemy jej jednak usunąc bez mozliwości łamania OL aplikacji, " +
+ "które mogą być od niej zależne. " +
+ "Dlatego jesteśmy za deprecjację -- minZoomLevel " +
+ "zostanie usunięta w wersji 3.0. W zamian prosze użyj " +
+ "min/max rozdzielczości w sposób opisany tutaj: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transakcja WFS: SUKCES ${response}",
+
+ 'commitFailed': "Transakcja WFS: FAILED ${response}",
+
+ 'googleWarning':
+ "Warstwa Google nie był w stanie załadować się poprawnie.<br><br>" +
+ "Aby pozbyć się tej wiadomości, wybierz nową Warstwe podstawową " +
+ "w przełączniku warstw w górnym prawym rogu mapy.<br><br>" +
+ "Najprawdopodobniej jest to spowodowane tym, że biblioteka Google Maps " +
+ "nie jest załadowana, lub nie zawiera poprawnego klucza do API dla twojej strony<br><br>" +
+ "Programisto: Aby uzyskać pomoc , " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>kliknij tutaj</a>",
+
+ 'getLayerWarning':
+ "Warstwa ${layerType} nie mogła zostać załadowana poprawnie.<br><br>" +
+ "Aby pozbyć się tej wiadomości, wybierz nową Warstwe podstawową " +
+ "w przełączniku warstw w górnym prawym rogu mapy.<br><br>" +
+ "Najprawdopodobniej jest to spowodowane tym, że biblioteka ${layerLib} " +
+ "nie jest załadowana, lub może(o ile biblioteka tego wymaga) " +
+ "byc potrzebny klucza do API dla twojej strony<br><br>" +
+ "Programisto: Aby uzyskać pomoc , " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>kliknij tutaj</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "Skala = 1 : ${scaleDenom}",
+
+ //labels for the graticule control
+ 'W': 'ZACH',
+ 'E': 'WSCH',
+ 'N': 'PN',
+ 'S': 'PD',
+ 'Graticule': 'Siatka',
+
+ // console message
+ 'reprojectDeprecated':
+ "w warstwie ${layerName} używasz opcji 'reproject'. " +
+ "Ta opcja jest przestarzała: " +
+ "jej zastosowanie został zaprojektowany, aby wspierać wyświetlania danych przez komercyjne mapy, "+
+ "jednak obecnie ta funkcjonalność powinien zostać osiągnięty za pomocą Spherical Mercator " +
+ "its use was designed to support displaying data over commercial. Więcje informacji na ten temat możesz znaleźć na stronie " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "Ta metoda jest przestarzała i będzie usunięta od wersji 3.0. " +
+ "W zamian użyj ${newMethod}."
+}); \ No newline at end of file
diff --git a/misc/openlayers/lib/OpenLayers/Lang/pt-BR.js b/misc/openlayers/lib/OpenLayers/Lang/pt-BR.js
new file mode 100644
index 0000000..60e6779
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/pt-BR.js
@@ -0,0 +1,54 @@
+/* Translators (2009 onwards):
+ * - Luckas Blade
+ * - Rodrigo Avila
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["pt-br"]
+ * Dictionary for Português do Brasil. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["pt-BR"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "A requisição retornou um erro não tratado: ${statusText}",
+
+ 'Permalink': "Link para essa página",
+
+ 'Overlays': "Camadas de Sobreposição",
+
+ 'Base Layer': "Camada Base",
+
+ 'noFID': "Não é possível atualizar uma feição que não tenha um FID.",
+
+ 'browserNotSupported': "Seu navegador não suporta renderização de vetores. Os renderizadores suportados atualmente são:\n${renderers}",
+
+ 'minZoomLevelError': "A propriedade minZoomLevel é de uso restrito das camadas descendentes de FixedZoomLevels. A verificação dessa propriedade pelas camadas wfs é um resíduo do passado. Não podemos, entretanto não é possível removê-la sem possívelmente quebrar o funcionamento de aplicações OL que possuem depência com ela. Portanto estamos tornando seu uso obsoleto -- a verificação desse atributo será removida na versão 3.0. Ao invés, use as opções de resolução min/max como descrito em: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transação WFS : SUCESSO ${response}",
+
+ 'commitFailed': "Transação WFS : ERRO ${response}",
+
+ 'googleWarning': "Não foi possível carregar a camada Google corretamente.\x3cbr\x3e\x3cbr\x3ePara se livrar dessa mensagem, selecione uma nova Camada Base, na ferramenta de alternação de camadas localização do canto superior direito.\x3cbr\x3e\x3cbr\x3eMuito provavelmente, isso foi causado porque o script da biblioteca do Google Maps não foi incluído, ou porque ele não contém a chave correta da API para o seu site.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: Para obter ajuda em solucionar esse problema \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3ecliquem aqui\x3c/a\x3e",
+
+ 'getLayerWarning': "Não foi possível carregar a camada ${layerType} corretamente.\x3cbr\x3e\x3cbr\x3ePara se livrar dessa mensagem, selecione uma nova Camada Base, na ferramenta de alternação de camadas localização do canto superior direito.\x3cbr\x3e\x3cbr\x3eMuito provavelmente, isso foi causado porque o script da biblioteca ${layerLib} não foi incluído corretamente.\x3cbr\x3e\x3cbr\x3eDesenvolvedores: Para obter ajuda em solucionar esse problema \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3ecliquem aqui\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",
+
+ 'W': "O",
+
+ 'E': "L",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Você está usando a opção \'reproject\' na camada ${layerName}. Essa opção está obsoleta: seu uso foi projetado para suportar a visualização de dados sobre bases de mapas comerciais, entretanto essa funcionalidade deve agora ser alcançada usando o suporte à projeção Mercator. Mais informação está disponível em: http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Esse método está obsoleto e será removido na versão 3.0. Ao invés, por favor use ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/pt.js b/misc/openlayers/lib/OpenLayers/Lang/pt.js
new file mode 100644
index 0000000..af8e519
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/pt.js
@@ -0,0 +1,55 @@
+/* Translators (2009 onwards):
+ * - Hamilton Abreu
+ * - Malafaya
+ * - Waldir
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["pt"]
+ * Dictionary for Português. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["pt"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Servidor devolveu erro não contemplado ${statusText}",
+
+ 'Permalink': "Ligação permanente",
+
+ 'Overlays': "Sobreposições",
+
+ 'Base Layer': "Camada Base",
+
+ 'noFID': "Não é possível atualizar um elemento para a qual não há FID.",
+
+ 'browserNotSupported': "O seu navegador não suporta renderização vetorial. Actualmente os renderizadores suportados são:\n${renderers}",
+
+ 'minZoomLevelError': "A propriedade minZoomLevel só deve ser usada com as camadas descendentes da FixedZoomLevels. A verificação da propriedade por esta camada wfs é uma relíquia do passado. No entanto, não podemos removê-la sem correr o risco de afectar aplicações OL que dependam dela. Portanto, estamos a torná-la obsoleta -- a verificação minZoomLevel será removida na versão 3.0. Em vez dela, por favor, use as opções de resolução min/max descritas aqui: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transacção WFS: SUCESSO ${response}",
+
+ 'commitFailed': "Transacção WFS: FALHOU ${response}",
+
+ 'googleWarning': "A Camada Google não foi correctamente carregada.\x3cbr\x3e\x3cbr\x3ePara deixar de receber esta mensagem, seleccione uma nova Camada-Base no \'\'switcher\'\' de camadas no canto superior direito.\x3cbr\x3e\x3cbr\x3eProvavelmente, isto acontece porque o \'\'script\'\' da biblioteca do Google Maps não foi incluído ou não contém a chave API correcta para o seu sítio.\x3cbr\x3e\x3cbr\x3eProgramadores: Para ajuda sobre como solucionar o problema \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eclique aqui\x3c/a\x3e .",
+
+ 'getLayerWarning': "A camada ${layerType} não foi correctamente carregada.\x3cbr\x3e\x3cbr\x3ePara desactivar esta mensagem, seleccione uma nova Camada-Base no \'\'switcher\'\' de camadas no canto superior direito.\x3cbr\x3e\x3cbr\x3eProvavelmente, isto acontece porque o \'\'script\'\' da biblioteca ${layerLib} não foi incluído correctamente.\x3cbr\x3e\x3cbr\x3eProgramadores: Para ajuda sobre como solucionar o problema \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eclique aqui\x3c/a\x3e .",
+
+ 'Scale = 1 : ${scaleDenom}': "Escala = 1 : ${scaleDenom}",
+
+ 'W': "O",
+
+ 'E': "E",
+
+ 'N': "N",
+
+ 'S': "S",
+
+ 'reprojectDeprecated': "Está usando a opção \'reproject\' na camada ${layerName}. Esta opção é obsoleta: foi concebida para permitir a apresentação de dados sobre mapas-base comerciais, mas esta funcionalidade é agora suportada pelo Mercator Esférico. Mais informação está disponível em http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Este método foi declarado obsoleto e será removido na versão 3.0. Por favor, use ${newMethod} em vez disso."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ro.js b/misc/openlayers/lib/OpenLayers/Lang/ro.js
new file mode 100644
index 0000000..6e8a04f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ro.js
@@ -0,0 +1,69 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+/**
+ * Namespace: OpenLayers.Lang["ro"]
+ * Dictionary for Romanian. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+*/
+OpenLayers.Lang.ro = {
+ 'unhandledRequest': "Cerere nesoluționată return ${statusText}",
+ 'Permalink': "Legatură permanentă",
+ 'Overlays': "Straturi vector",
+ 'Base Layer': "Straturi de bază",
+ 'noFID': "Nu pot actualiza un feature pentru care nu există FID.",
+ 'browserNotSupported':
+ "Browserul tău nu suportă afișarea vectorilor. Supoetul curent pentru randare:\n${renderers}",
+ // console message
+ 'minZoomLevelError':
+ "Proprietatea minZoomLevel este doar pentru a fi folosită " +
+ "cu straturile FixedZoomLevels-descendent. De aceea acest " +
+ "strat wfs verifică dacă minZoomLevel este o relicvă" +
+ ". Nu îl putem , oricum, înlătura fără " +
+ "a afecta aplicațiile Openlayers care depind de ea." +
+ " De aceea considerăm depreciat -- minZoomLevel " +
+ "și îl vom înlătura în 3.0. Folosește " +
+ "min/max resolution cum este descrisă aici: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+ 'commitSuccess': "Tranzacție WFS: SUCCES ${response}",
+ 'commitFailed': "Tranzacție WFS : EȘEC ${response}",
+ 'googleWarning':
+ "Stratul Google nu a putut fi încărcat corect.<br><br>" +
+ "Pentru a elimina acest mesaj, selectează un nou strat de bază " +
+ "în Layerswitcher din colțul dreata-sus.<br><br>" +
+ "Asta datorită, faptului că Google Maps library " +
+ "script nu este inclus, sau nu conține " +
+ "cheia API corectă pentru situl tău.<br><br>" +
+ "Developeri: Pentru ajutor, " +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>apăsați aici</a>",
+ 'getLayerWarning':
+ "Stratul ${layerType} nu a putut fi încărcat corect.<br><br>" +
+ "pentru a înlătura acest mesaj, selectează un nou strat de bază " +
+ "Acesta eroare apare de obicei când ${layerLib} library " +
+ "script nu a fost încărcat corect.<br><br>" +
+ "Developeri: Pentru ajutor privind utilizarea corectă, " +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>apasă aici</a>",
+ 'Scale = 1 : ${scaleDenom}': "Scara = 1 : ${scaleDenom}",
+ //labels for the graticule control
+ 'W': 'V',
+ 'E': 'E',
+ 'N': 'N',
+ 'S': 'S',
+ 'Graticule': 'Graticule',
+ // console message
+ 'reprojectDeprecated':
+ "folosești opțiunea 'reproject' " +
+ "pentru stratul ${layerName} . Această opțiune este depreciată: " +
+ "a fost utilizată pentru afișarea straturilor de bază comerciale " +
+ "Mai multe informații despre proiecția Mercator sunt disponibile aici " +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+ // console message
+ 'methodDeprecated':
+ "Această metodă este depreciată și va fi înlăturată in versiunea 3.0. " +
+ "folosește metoda ${newMethod}.",
+ // **** end ****
+ 'end': ''
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/ru.js b/misc/openlayers/lib/OpenLayers/Lang/ru.js
new file mode 100644
index 0000000..23ecda8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/ru.js
@@ -0,0 +1,56 @@
+/* Translators (2009 onwards):
+ * - Ferrer
+ * - Komzpa
+ * - Lockal
+ * - ÐлекÑандр Сигачёв
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["ru"]
+ * Dictionary for РуÑÑкий. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["ru"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Ðеобработанный Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²ÐµÑ€Ð½ÑƒÐ» ${statusText}",
+
+ 'Permalink': "ПоÑтоÑÐ½Ð½Ð°Ñ ÑÑылка",
+
+ 'Overlays': "Слои",
+
+ 'Base Layer': "ОÑновной Ñлой",
+
+ 'noFID': "Ðевозможно обновить объект, Ð´Ð»Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð³Ð¾ нет FID.",
+
+ 'browserNotSupported': "Ваш браузер не поддерживает векторную графику. Ðа данный момент поддерживаютÑÑ:\n${renderers}",
+
+ 'minZoomLevelError': "СвойÑтво minZoomLevel предназначено только Ð´Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñо ÑлоÑми, ÑвлÑющимиÑÑ Ð¿Ð¾Ñ‚Ð¾Ð¼ÐºÐ°Ð¼Ð¸ FixedZoomLevels. То, что Ñтот WFS-Ñлой проверÑетÑÑ Ð½Ð° minZoomLevel — реликт прошлого. Однако мы не можем удалить Ñту функцию, так как, возможно, от неё завиÑÑÑ‚ некоторые оÑнованные на OpenLayers приложениÑ. Ð¤ÑƒÐ½ÐºÑ†Ð¸Ñ Ð¾Ð±ÑŠÑвлена уÑтаревшей — проверка minZoomLevel будет удалена в 3.0. ПожалуйÑта, иÑпользуйте вмеÑто неё наÑтройку мин/Ð¼Ð°ÐºÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ, опиÑанную здеÑÑŒ: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ WFS: УСПЕШÐО ${response}",
+
+ 'commitFailed': "Ð¢Ñ€Ð°Ð½Ð·Ð°ÐºÑ†Ð¸Ñ WFS: ОШИБКР${response}",
+
+ 'googleWarning': "Слой Google не удалоÑÑŒ нормально загрузить.\x3cbr\x3e\x3cbr\x3eЧтобы избавитьÑÑ Ð¾Ñ‚ Ñтого ÑообщениÑ, выбите другой оÑновной Ñлой в переключателе в правом верхнем углу.\x3cbr\x3e\x3cbr\x3eСкорее вÑего, причина в том, что библиотека Google Maps не была включена или не Ñодержит корректного API-ключа Ð´Ð»Ñ Ð²Ð°ÑˆÐµÐ³Ð¾ Ñайта.\x3cbr\x3e\x3cbr\x3eРазработчикам: чтобы узнать, как Ñделать, чтобы вÑÑ‘ заработало, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eщёлкните тут\x3c/a\x3e",
+
+ 'getLayerWarning': "Слой ${layerType} не удалоÑÑŒ нормально загрузить. \x3cbr\x3e\x3cbr\x3eЧтобы избавитьÑÑ Ð¾Ñ‚ Ñтого ÑообщениÑ, выбите другой оÑновной Ñлой в переключателе в правом верхнем углу.\x3cbr\x3e\x3cbr\x3eСкорее вÑего, причина в том, что библиотека ${layerLib} не была включена или была включена некорректно.\x3cbr\x3e\x3cbr\x3eРазработчикам: чтобы узнать, как Ñделать, чтобы вÑÑ‘ заработало, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eщёлкните тут\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "МаÑштаб = 1 : ${scaleDenom}",
+
+ 'W': "З",
+
+ 'E': "Ð’",
+
+ 'N': "С",
+
+ 'S': "Ю",
+
+ 'reprojectDeprecated': "Ð’Ñ‹ иÑпользуете опцию \'reproject\' Ð´Ð»Ñ ÑÐ»Ð¾Ñ ${layerName}. Эта Ð¾Ð¿Ñ†Ð¸Ñ ÑвлÑетÑÑ ÑƒÑтаревшей: ее иÑпользование предполагалоÑÑŒ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ¸ показа данных поверх коммерчеÑких базовых карт, но теперь Ñтот функционал неÑÑ‘Ñ‚ вÑÑ‚Ñ€Ð¾ÐµÐ½Ð½Ð°Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ° ÑферичеÑкой проекции Меркатора. Больше Ñведений доÑтупно на http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Этот метод ÑчитаетÑÑ ÑƒÑтаревшим и будет удалён в верÑии 3.0. ПожалуйÑта, пользуйтеÑÑŒ ${newMethod}."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/sk.js b/misc/openlayers/lib/OpenLayers/Lang/sk.js
new file mode 100644
index 0000000..475647f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/sk.js
@@ -0,0 +1,44 @@
+/* Translators (2009 onwards):
+ * - Helix84
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["sk"]
+ * Dictionary for SlovenÄina. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["sk"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Neobslúžené požiadavky vracajú ${statusText}",
+
+ 'Permalink': "Trvalý odkaz",
+
+ 'Overlays': "Prekrytia",
+
+ 'Base Layer': "Základná vrstva",
+
+ 'noFID': "Nie je možné aktualizovať vlastnosť, pre ktorú neexistuje FID.",
+
+ 'browserNotSupported': "Váš prehliadaÄ nepodporuje vykresľovanie vektorov. Momentálne podporované vykresľovaÄe sú:\n${renderers}",
+
+ 'minZoomLevelError': "VlastnosÅ¥ minZoomLevel je urÄený iba na použitie s vrstvami odvodenými od FixedZoomLevels. To, že táto wfs vrstva kontroluje minZoomLevel je pozostatok z minulosti. Nemôžeme ho vÅ¡ak odstrániÅ¥, aby sme sa vyhli možnému poruÅ¡eniu aplikácií založených na Open Layers, ktoré na tomto môže závisieÅ¥. Preto ho oznaÄujeme ako zavrhovaný - dolu uvedená kontrola minZoomLevel bude odstránená vo verzii 3.0. Použite prosím namiesto toho kontrolu min./max. rozlíšenia podľa tu uvedeného popisu: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Transakcia WFS: ÚSPEŠNà ${response}",
+
+ 'commitFailed': "Transakcia WFS: ZLYHALA ${response}",
+
+ 'googleWarning': "Vrstvu Google nebolo možné správne naÄítaÅ¥.\x3cbr\x3e\x3cbr\x3eAby ste sa tejto správy zbavili vyberte novú BaseLayer v prepínaÄi vrstiev v pravom hornom rohu.\x3cbr\x3e\x3cbr\x3eToto sa stalo pravdepodobne preto, že skript knižnice Google Maps buÄ nebol naÄítaný alebo neobsahuje správny kÄ¾ÃºÄ API pre vaÅ¡u lokalitu.\x3cbr\x3e\x3cbr\x3eVývojári: Tu môžete získaÅ¥ \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3epomoc so sfunkÄnením\x3c/a\x3e",
+
+ 'getLayerWarning': "Vrstvu ${layerType} nebolo možné správne naÄítaÅ¥.\x3cbr\x3e\x3cbr\x3eAby ste sa tejto správy zbavili vyberte novú BaseLayer v prepínaÄi vrstiev v pravom hornom rohu.\x3cbr\x3e\x3cbr\x3eToto sa stalo pravdepodobne preto, že skript knižnice ${layerType} buÄ nebol naÄítaný alebo neobsahuje správny kÄ¾ÃºÄ API pre vaÅ¡u lokalitu.\x3cbr\x3e\x3cbr\x3eVývojári: Tu môžete získaÅ¥ \x3ca href=\'http://trac.openlayers.org/wiki/${layerType}\' target=\'_blank\'\x3epomoc so sfunkÄnením\x3c/a\x3e",
+
+ 'Scale = 1 : ${scaleDenom}': "Mierka = 1 : ${scaleDenom}",
+
+ 'reprojectDeprecated': "Používate voľby „reproject“ vrstvy ${layerType}. Táto voľba je zzavrhovaná: jej použitie bolo navrhnuté na podporu zobrazovania údajov nad komerÄnými základovými mapami, ale túto funkcionalitu je teraz možné dosiahnuÅ¥ pomocou Spherical Mercator. ÄŽalÅ¡ie informácie získate na stránke http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Táto metóda je zavrhovaná a bude odstránená vo verzii 3.0. Použite prosím namiesto nej metódu ${newMethod}."
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/sv-SE.js b/misc/openlayers/lib/OpenLayers/Lang/sv-SE.js
new file mode 100644
index 0000000..2176250
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/sv-SE.js
@@ -0,0 +1,45 @@
+/* Translators (2009 onwards):
+ * - Sannab
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["sv"]
+ * Dictionary for Svenska. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["sv"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Ej hanterad fråga retur ${statusText}",
+
+ 'Permalink': "Permalänk",
+
+ 'Overlays': "Kartlager",
+
+ 'Base Layer': "Bakgrundskarta",
+
+ 'noFID': "Kan ej uppdatera feature (objekt) för vilket FID saknas.",
+
+ 'browserNotSupported': "Din webbläsare stöder inte vektorvisning. För närvarande stöds följande visning:\n${renderers}",
+
+ 'minZoomLevelError': "Egenskapen minZoomLevel är endast avsedd att användas med lager med FixedZoomLevels. Att detta WFS-lager kontrollerar minZoomLevel är en relik från äldre versioner. Vi kan dock inte ta bort det utan att riskera att OL-baserade tillämpningar som använder detta slutar fungera. Därför är det satt som deprecated, minZoomLevel kommer att tas bort i version 3.0. Använd i stället inställning av min/max resolution som beskrivs här: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS-transaktion: LYCKADES ${response}",
+
+ 'commitFailed': "WFS-transaktion: MISSLYCKADES ${response}",
+
+ 'googleWarning': "Google-lagret kunde inte laddas korrekt.\x3cbr\x3e\x3cbr\x3eFör att slippa detta meddelande, välj en annan bakgrundskarta i lagerväljaren i övre högra hörnet.\x3cbr\x3e\x3cbr\x3eSannolikt beror felet på att Google Maps-biblioteket inte är inkluderat på webbsidan eller på att sidan inte anger korrekt API-nyckel för webbplatsen.\x3cbr\x3e\x3cbr\x3eUtvecklare: hjälp för att åtgärda detta, \x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eklicka här\x3c/a\x3e.",
+
+ 'getLayerWarning': "${layerType}-lagret kunde inte laddas korrekt.\x3cbr\x3e\x3cbr\x3eFör att slippa detta meddelande, välj en annan bakgrundskarta i lagerväljaren i övre högra hörnet.\x3cbr\x3e\x3cbr\x3eSannolikt beror felet på att ${layerLib}-biblioteket inte är inkluderat på webbsidan.\x3cbr\x3e\x3cbr\x3eUtvecklare: hjälp för att åtgärda detta, \x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eklicka här\x3c/a\x3e.",
+
+ 'Scale = 1 : ${scaleDenom}': "\x3cstrong\x3eSkala\x3c/strong\x3e 1 : ${scaleDenom}",
+
+ 'reprojectDeprecated': "Du använder inställningen \'reproject\' på lagret ${layerName}. Denna inställning markerad som deprecated: den var avsedd att användas för att stödja visning av kartdata på kommersiella bakgrundskartor, men nu bör man i stället använda Spherical Mercator-stöd för den funktionaliteten. Mer information finns på http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ 'methodDeprecated': "Denna metod är markerad som deprecated och kommer att tas bort i 3.0. Använd ${newMethod} i stället."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/te.js b/misc/openlayers/lib/OpenLayers/Lang/te.js
new file mode 100644
index 0000000..289eff3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/te.js
@@ -0,0 +1,27 @@
+/* Translators (2009 onwards):
+ * - Veeven
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["te"]
+ * Dictionary for తెలà±à°—à±. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["te"] = OpenLayers.Util.applyDefaults({
+
+ 'Permalink': "à°¸à±à°¥à°¿à°°à°²à°¿à°‚à°•à±",
+
+ 'W': "à°ª",
+
+ 'E': "తూ",
+
+ 'N': "à°‰",
+
+ 'S': "à°¦"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/vi.js b/misc/openlayers/lib/OpenLayers/Lang/vi.js
new file mode 100644
index 0000000..63b4270
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/vi.js
@@ -0,0 +1,53 @@
+/* Translators (2009 onwards):
+ * - Minh Nguyen
+ */
+
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["vi"]
+ * Dictionary for Tiếng Việt. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["vi"] = OpenLayers.Util.applyDefaults({
+
+ 'unhandledRequest': "Không xử lý được phản hồi ${statusText} cho yêu cầu",
+
+ 'Permalink': "Liên kết thÆ°á»ng trá»±c",
+
+ 'Overlays': "Lấp bản đồ",
+
+ 'Base Layer': "Lá»›p ná»n",
+
+ 'noFID': "Không thể cập nhật tính năng thiếu FID.",
+
+ 'browserNotSupported': "Trình duyệt của bạn không hỗ trợ chức năng vẽ bằng vectơ. Hiện hỗ trợ các bộ kết xuất:\n${renderers}",
+
+ 'minZoomLevelError': "Chỉ nên sá»­ dụng thuá»™c tính minZoomLevel vá»›i các lá»›p FixedZoomLevels-descendent. Việc lá»›p wfs này tìm cho minZoomLevel là di tích còn lại từ xÆ°a. Tuy nhiên, nếu chúng tôi dá»i nó thì sẽ vỡ các chÆ°Æ¡ng trình OpenLayers mà dá»±a trên nó. Bởi vậy chúng tôi phản đối sá»­ dụng nó\x26nbsp;– bÆ°á»›c tìm cho minZoomLevel sẽ được dá»i vào phiên bản 3.0. Xin sá»­ dụng thiết lập Ä‘á»™ phân tích tối thiểu / tối Ä‘a thay thế, theo hÆ°á»›ng dẫn này: http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "Giao dịch WFS: THÀNH CÔNG ${response}",
+
+ 'commitFailed': "Giao dịch WFS: THẤT BẠI ${response}",
+
+ 'googleWarning': "Không thể tải lá»›p Google đúng đắn.\x3cbr\x3e\x3cbr\x3eÄể tránh thông báo này lần sau, hãy chá»n BaseLayer má»›i dùng Ä‘iá»u khiển chá»n lá»›p ở góc trên phải.\x3cbr\x3e\x3cbr\x3eChắc script thÆ° viện Google Maps hoặc không được bao gồm hoặc không chứa khóa API hợp vá»›i website của bạn.\x3cbr\x3e\x3cbr\x3e\x3ca href=\'http://trac.openlayers.org/wiki/Google\' target=\'_blank\'\x3eTrợ giúp vá» tính năng này\x3c/a\x3e cho ngÆ°á»i phát triển.",
+
+ 'getLayerWarning': "Không thể tải lá»›p ${layerType} đúng đắn.\x3cbr\x3e\x3cbr\x3eÄể tránh thông báo này lần sau, hãy chá»n BaseLayer má»›i dùng Ä‘iá»u khiển chá»n lá»›p ở góc trên phải.\x3cbr\x3e\x3cbr\x3eChắc script thÆ° viện ${layerLib} không được bao gồm đúng kiểu.\x3cbr\x3e\x3cbr\x3e\x3ca href=\'http://trac.openlayers.org/wiki/${layerLib}\' target=\'_blank\'\x3eTrợ giúp vá» tính năng này\x3c/a\x3e cho ngÆ°á»i phát triển.",
+
+ 'Scale = 1 : ${scaleDenom}': "Tỷ lệ = 1 : ${scaleDenom}",
+
+ 'W': "T",
+
+ 'E': "Ä",
+
+ 'N': "B",
+
+ 'S': "N",
+
+ 'reprojectDeprecated': "Bạn Ä‘ang áp dụng chế Ä‘á»™ “reproject†vào lá»›p ${layerName}. Chế Ä‘á»™ này đã bị phản đối: nó có mục đích há»— trợ lấp dữ liệu trên các ná»n bản đồ thÆ°Æ¡ng mại; nên thá»±c hiện hiệu ứng đó dùng tính năng Mercator Hình cầu. Có sẵn thêm chi tiết tại http://trac.openlayers.org/wiki/SphericalMercator .",
+
+ 'methodDeprecated': "PhÆ°Æ¡ng thức này đã bị phản đối và sẽ bị dá»i vào phiên bản 3.0. Xin hãy sá»­ dụng ${newMethod} thay thế."
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Lang/zh-CN.js b/misc/openlayers/lib/OpenLayers/Lang/zh-CN.js
new file mode 100644
index 0000000..449dd69
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/zh-CN.js
@@ -0,0 +1,80 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["zh-CN"]
+ * Dictionary for Simplified Chinese. Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["zh-CN"] = {
+
+ 'unhandledRequest': "未处ç†çš„请求,返回值为 ${statusText}",
+
+ 'Permalink': "永久链接",
+
+ 'Overlays': "å åŠ å±‚",
+
+ 'Base Layer': "基础图层",
+
+ 'noFID': "无法更新feature,缺少FID。",
+
+ 'browserNotSupported':
+ "你使用的æµè§ˆå™¨ä¸æ”¯æŒçŸ¢é‡æ¸²æŸ“。当å‰æ”¯æŒçš„渲染方å¼åŒ…括:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "minZoomLevel属性仅适åˆç”¨äºŽ" +
+ "使用了固定缩放级别的图层。这个 " +
+ "wfs 图层检查 minZoomLevel 是过去é—留下æ¥çš„。" +
+ "然而,我们ä¸èƒ½ç§»é™¤å®ƒï¼Œ" +
+ "而破åä¾èµ–于它的基于OL的应用程åºã€‚" +
+ "因此,我们废除了它 -- minZoomLevel " +
+ "将会在3.0中被移除。请改用 " +
+ "min/max resolution 设置,å‚考:" +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS Transaction: æˆåŠŸã€‚ ${response}",
+
+ 'commitFailed': "WFS Transaction: 失败。 ${response}",
+
+ 'googleWarning':
+ "Google图层ä¸èƒ½æ­£ç¡®åŠ è½½ã€‚<br><br>" +
+ "è¦æ¶ˆé™¤è¿™ä¸ªä¿¡æ¯ï¼Œè¯·åœ¨å³ä¸Šè§’çš„" +
+ "图层控制é¢æ¿ä¸­é€‰æ‹©å…¶ä»–的基础图层。<br><br>" +
+ "è¿™ç§æƒ…况很å¯èƒ½æ˜¯æ²¡æœ‰æ­£ç¡®çš„包å«Google地图脚本库," +
+ "或者是没有包å«åœ¨ä½ çš„站点上" +
+ "使用的正确的Google Maps API密匙。<br><br>" +
+ "å¼€å‘者:获å–使其正确工作的帮助信æ¯ï¼Œ" +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>点击这里</a>",
+
+ 'getLayerWarning':
+ "${layerType} 图层ä¸èƒ½æ­£ç¡®åŠ è½½ã€‚<br><br>" +
+ "è¦æ¶ˆé™¤è¿™ä¸ªä¿¡æ¯ï¼Œè¯·åœ¨å³ä¸Šè§’çš„" +
+ "图层控制é¢æ¿ä¸­é€‰æ‹©å…¶ä»–的基础图层。<br><br>" +
+ "è¿™ç§æƒ…况很å¯èƒ½æ˜¯æ²¡æœ‰æ­£ç¡®çš„包å«" +
+ "${layerLib} 脚本库。<br><br>" +
+ "å¼€å‘者:获å–使其正确工作的帮助信æ¯ï¼Œ" +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>点击这里</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "比例尺 = 1 : ${scaleDenom}",
+
+ // console message
+ 'reprojectDeprecated':
+ "你正在使用 ${layerName} 图层上的'reproject'选项。" +
+ "这个选项已ç»ä¸å†ä½¿ç”¨ï¼š" +
+ "它是被设计用æ¥æ”¯æŒæ˜¾ç¤ºå•†ä¸šçš„地图数æ®ï¼Œ" +
+ "ä¸è¿‡çŽ°åœ¨è¯¥åŠŸèƒ½å¯ä»¥é€šè¿‡ä½¿ç”¨Spherical Mercatoræ¥å®žçŽ°ã€‚" +
+ "更多信æ¯å¯ä»¥å‚阅" +
+ "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+ // console message
+ 'methodDeprecated':
+ "该方法已ç»ä¸å†è¢«æ”¯æŒï¼Œå¹¶ä¸”将在3.0中被移除。" +
+ "请使用 ${newMethod} 方法æ¥æ›¿ä»£ã€‚",
+
+ 'end': ''
+};
diff --git a/misc/openlayers/lib/OpenLayers/Lang/zh-TW.js b/misc/openlayers/lib/OpenLayers/Lang/zh-TW.js
new file mode 100644
index 0000000..b70e861
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Lang/zh-TW.js
@@ -0,0 +1,81 @@
+/**
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang["zh-TW"]
+ * Dictionary for Traditional Chinese. (Used Mainly in Taiwan)
+ * Keys for entries are used in calls to
+ * <OpenLayers.Lang.translate>. Entry bodies are normal strings or
+ * strings formatted for use with <OpenLayers.String.format> calls.
+ */
+OpenLayers.Lang["zh-TW"] = {
+
+ 'unhandledRequest': "未處ç†çš„請求,傳回值為 ${statusText}。",
+
+ 'Permalink': "永久連çµ",
+
+ 'Overlays': "é¡å¤–圖層",
+
+ 'Base Layer': "基礎圖層",
+
+ 'noFID': "因為沒有 FID 所以無法更新 feature。",
+
+ 'browserNotSupported':
+ "您的ç€è¦½å™¨æœªæ”¯æ´å‘é‡æ¸²æŸ“. ç›®å‰æ”¯æ´çš„渲染方å¼æ˜¯:\n${renderers}",
+
+ // console message
+ 'minZoomLevelError':
+ "minZoomLevel 屬性僅é©åˆç”¨åœ¨ " +
+ "FixedZoomLevels-descendent 類型的圖層. 這個" +
+ "wfs layer çš„ minZoomLevel 是éŽåŽ»æ‰€éºç•™ä¸‹ä¾†çš„," +
+ "然而我們ä¸èƒ½ç§»é™¤å®ƒè€Œä¸è®“它將" +
+ "éŽåŽ»çš„程å¼ç›¸å®¹æ€§çµ¦ç ´å£žæŽ‰ã€‚" +
+ "因此我們將會迴é¿ä½¿ç”¨å®ƒ -- minZoomLevel " +
+ "會在3.0被移除,請改" +
+ "用在這邊æè¿°çš„ min/max resolution 設定: " +
+ "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+ 'commitSuccess': "WFS Transaction: æˆåŠŸ ${response}",
+
+ 'commitFailed': "WFS Transaction: 失敗 ${response}",
+
+ 'googleWarning':
+ "The Google Layer 圖層無法被正確的載入。<br><br>" +
+ "è¦è¿´é¿é€™å€‹è¨Šæ¯, 請在å³ä¸Šè§’的圖層改變器裡," +
+ "é¸ä¸€å€‹æ–°çš„基礎圖層。<br><br>" +
+ "很有å¯èƒ½æ˜¯å› ç‚º Google Maps 的函å¼åº«" +
+ "è…³æœ¬æ²’æœ‰è¢«æ­£ç¢ºçš„ç½®å…¥ï¼Œæˆ–æ²’æœ‰åŒ…å« " +
+ "您網站上正確的 API key <br><br>" +
+ "開發者: è¦å¹«åŠ©é€™å€‹è¡Œç‚ºæ­£ç¢ºå®Œæˆï¼Œ" +
+ "<a href='http://trac.openlayers.org/wiki/Google' " +
+ "target='_blank'>請按這裡</a>",
+
+ 'getLayerWarning':
+ "${layerType} 圖層無法被正確的載入。<br><br>" +
+ "è¦è¿´é¿é€™å€‹è¨Šæ¯, 請在å³ä¸Šè§’的圖層改變器裡," +
+ "é¸ä¸€å€‹æ–°çš„基礎圖層。<br><br>" +
+ "很有å¯èƒ½æ˜¯å› ç‚º ${layerLib} 的函å¼åº«" +
+ "腳本沒有被正確的置入。<br><br>" +
+ "開發者: è¦å¹«åŠ©é€™å€‹è¡Œç‚ºæ­£ç¢ºå®Œæˆï¼Œ" +
+ "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+ "target='_blank'>請按這裡</a>",
+
+ 'Scale = 1 : ${scaleDenom}': "Scale = 1 : ${scaleDenom}",
+
+ // console message
+ 'reprojectDeprecated':
+ "你正使用 'reproject' 這個é¸é … " +
+ "在 ${layerName} 層。這個é¸é …已經ä¸å†ä½¿ç”¨:" +
+ "它的使用原本是設計用來支æ´åœ¨å•†æ¥­åœ°åœ–上秀出資料," +
+ "但這個功能已經被" +
+ "Spherical Mercator所å–代。更多的資訊å¯ä»¥åœ¨ " +
+ "http://trac.openlayers.org/wiki/SphericalMercator 找到。",
+
+ // console message
+ 'methodDeprecated':
+ "這個方法已經ä¸å†ä½¿ç”¨ä¸”在3.0將會被移除," +
+ "請使用 ${newMethod} 來代替。",
+
+ 'end': ''
+};
diff --git a/misc/openlayers/lib/OpenLayers/Layer.js b/misc/openlayers/lib/OpenLayers/Layer.js
new file mode 100644
index 0000000..3bd4186
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer.js
@@ -0,0 +1,1377 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Map.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer
+ */
+OpenLayers.Layer = OpenLayers.Class({
+
+ /**
+ * APIProperty: id
+ * {String}
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * APIProperty: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * APIProperty: opacity
+ * {Float} The layer's opacity. Float number between 0.0 and 1.0. Default
+ * is 1.
+ */
+ opacity: 1,
+
+ /**
+ * APIProperty: alwaysInRange
+ * {Boolean} If a layer's display should not be scale-based, this should
+ * be set to true. This will cause the layer, as an overlay, to always
+ * be 'active', by always returning true from the calculateInRange()
+ * function.
+ *
+ * If not explicitly specified for a layer, its value will be
+ * determined on startup in initResolutions() based on whether or not
+ * any scale-specific properties have been set as options on the
+ * layer. If no scale-specific options have been set on the layer, we
+ * assume that it should always be in range.
+ *
+ * See #987 for more info.
+ */
+ alwaysInRange: null,
+
+ /**
+ * Constant: RESOLUTION_PROPERTIES
+ * {Array} The properties that are used for calculating resolutions
+ * information.
+ */
+ RESOLUTION_PROPERTIES: [
+ 'scales', 'resolutions',
+ 'maxScale', 'minScale',
+ 'maxResolution', 'minResolution',
+ 'numZoomLevels', 'maxZoomLevel'
+ ],
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types:
+ * loadstart - Triggered when layer loading starts. When using a Vector
+ * layer with a Fixed or BBOX strategy, the event object includes
+ * a *filter* property holding the OpenLayers.Filter used when
+ * calling read on the protocol.
+ * loadend - Triggered when layer loading ends. When using a Vector layer
+ * with a Fixed or BBOX strategy, the event object includes a
+ * *response* property holding an OpenLayers.Protocol.Response object.
+ * visibilitychanged - Triggered when the layer's visibility property is
+ * changed, e.g. by turning the layer on or off in the layer switcher.
+ * Note that the actual visibility of the layer can also change if it
+ * gets out of range (see <calculateInRange>). If you also want to catch
+ * these cases, register for the map's 'changelayer' event instead.
+ * move - Triggered when layer moves (triggered with every mousemove
+ * during a drag).
+ * moveend - Triggered when layer is done moving, object passed as
+ * argument has a zoomChanged boolean property which tells that the
+ * zoom has changed.
+ * added - Triggered after the layer is added to a map. Listeners will
+ * receive an object with a *map* property referencing the map and a
+ * *layer* property referencing the layer.
+ * removed - Triggered after the layer is removed from the map. Listeners
+ * will receive an object with a *map* property referencing the map and
+ * a *layer* property referencing the layer.
+ */
+ events: null,
+
+ /**
+ * APIProperty: map
+ * {<OpenLayers.Map>} This variable is set when the layer is added to
+ * the map, via the accessor function setMap().
+ */
+ map: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Whether or not the layer is a base layer. This should be set
+ * individually by all subclasses. Default is false
+ */
+ isBaseLayer: false,
+
+ /**
+ * Property: alpha
+ * {Boolean} The layer's images have an alpha channel. Default is false.
+ */
+ alpha: false,
+
+ /**
+ * APIProperty: displayInLayerSwitcher
+ * {Boolean} Display the layer's name in the layer switcher. Default is
+ * true.
+ */
+ displayInLayerSwitcher: true,
+
+ /**
+ * APIProperty: visibility
+ * {Boolean} The layer should be displayed in the map. Default is true.
+ */
+ visibility: true,
+
+ /**
+ * APIProperty: attribution
+ * {String} Attribution string, displayed when an
+ * <OpenLayers.Control.Attribution> has been added to the map.
+ */
+ attribution: null,
+
+ /**
+ * Property: inRange
+ * {Boolean} The current map resolution is within the layer's min/max
+ * range. This is set in <OpenLayers.Map.setCenter> whenever the zoom
+ * changes.
+ */
+ inRange: false,
+
+ /**
+ * Propery: imageSize
+ * {<OpenLayers.Size>} For layers with a gutter, the image is larger than
+ * the tile by twice the gutter in each dimension.
+ */
+ imageSize: null,
+
+ // OPTIONS
+
+ /**
+ * Property: options
+ * {Object} An optional object whose properties will be set on the layer.
+ * Any of the layer properties can be set as a property of the options
+ * object and sent to the constructor when the layer is created.
+ */
+ options: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * APIProperty: gutter
+ * {Integer} Determines the width (in pixels) of the gutter around image
+ * tiles to ignore. By setting this property to a non-zero value,
+ * images will be requested that are wider and taller than the tile
+ * size by a value of 2 x gutter. This allows artifacts of rendering
+ * at tile edges to be ignored. Set a gutter value that is equal to
+ * half the size of the widest symbol that needs to be displayed.
+ * Defaults to zero. Non-tiled layers always have zero gutter.
+ */
+ gutter: 0,
+
+ /**
+ * APIProperty: projection
+ * {<OpenLayers.Projection>} or {<String>} Specifies the projection of the layer.
+ * Can be set in the layer options. If not specified in the layer options,
+ * it is set to the default projection specified in the map,
+ * when the layer is added to the map.
+ * Projection along with default maxExtent and resolutions
+ * are set automatically with commercial baselayers in EPSG:3857,
+ * such as Google, Bing and OpenStreetMap, and do not need to be specified.
+ * Otherwise, if specifying projection, also set maxExtent,
+ * maxResolution or resolutions as appropriate.
+ * When using vector layers with strategies, layer projection should be set
+ * to the projection of the source data if that is different from the map default.
+ *
+ * Can be either a string or an <OpenLayers.Projection> object;
+ * if a string is passed, will be converted to an object when
+ * the layer is added to the map.
+ *
+ */
+ projection: null,
+
+ /**
+ * APIProperty: units
+ * {String} The layer map units. Defaults to null. Possible values
+ * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
+ * Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units.
+ */
+ units: null,
+
+ /**
+ * APIProperty: scales
+ * {Array} An array of map scales in descending order. The values in the
+ * array correspond to the map scale denominator. Note that these
+ * values only make sense if the display (monitor) resolution of the
+ * client is correctly guessed by whomever is configuring the
+ * application. In addition, the units property must also be set.
+ * Use <resolutions> instead wherever possible.
+ */
+ scales: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array} A list of map resolutions (map units per pixel) in descending
+ * order. If this is not set in the layer constructor, it will be set
+ * based on other resolution related properties (maxExtent,
+ * maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the layer. Defaults to null.
+ *
+ * The center of these bounds will not stray outside
+ * of the viewport extent during panning. In addition, if
+ * <displayOutsideMaxExtent> is set to false, data will not be
+ * requested that falls completely outside of these bounds.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the layer. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Default max is 360 deg / 256 px, which corresponds to
+ * zoom level 0 on gmaps. Specify a different value in the layer
+ * options if you are not using the default <OpenLayers.Map.tileSize>
+ * and displaying the whole world.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer}
+ */
+ numZoomLevels: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: displayOutsideMaxExtent
+ * {Boolean} Request map tiles that are completely outside of the max
+ * extent for this layer. Defaults to false.
+ */
+ displayOutsideMaxExtent: false,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Wraps the world at the international dateline, so the map can
+ * be panned infinitely in longitudinal direction. Only use this on the
+ * base layer, and only if the layer's maxExtent equals the world bounds.
+ * #487 for more info.
+ */
+ wrapDateLine: false,
+
+ /**
+ * Property: metadata
+ * {Object} This object can be used to store additional information on a
+ * layer object.
+ */
+ metadata: null,
+
+ /**
+ * Constructor: OpenLayers.Layer
+ *
+ * Parameters:
+ * name - {String} The layer name
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+
+ this.metadata = {};
+
+ options = OpenLayers.Util.extend({}, options);
+ // make sure we respect alwaysInRange if set on the prototype
+ if (this.alwaysInRange != null) {
+ options.alwaysInRange = this.alwaysInRange;
+ }
+ this.addOptions(options);
+
+ this.name = name;
+
+ if (this.id == null) {
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+
+ this.div = OpenLayers.Util.createDiv(this.id);
+ this.div.style.width = "100%";
+ this.div.style.height = "100%";
+ this.div.dir = "ltr";
+
+ this.events = new OpenLayers.Events(this, this.div);
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ }
+ },
+
+ /**
+ * Method: destroy
+ * Destroy is a destructor: this is to alleviate cyclic references which
+ * the Javascript garbage cleaner can not take care of on its own.
+ *
+ * Parameters:
+ * setNewBaseLayer - {Boolean} Set a new base layer when this layer has
+ * been destroyed. Default is true.
+ */
+ destroy: function(setNewBaseLayer) {
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+ if (this.map != null) {
+ this.map.removeLayer(this, setNewBaseLayer);
+ }
+ this.projection = null;
+ this.map = null;
+ this.name = null;
+ this.div = null;
+ this.options = null;
+
+ if (this.events) {
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ }
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: clone
+ *
+ * Parameters:
+ * obj - {<OpenLayers.Layer>} The layer to be cloned
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} An exact clone of this <OpenLayers.Layer>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer(this.name, this.getOptions());
+ }
+
+ // catch any randomly tagged-on properties
+ OpenLayers.Util.applyDefaults(obj, this);
+
+ // a cloned layer should never have its map property set
+ // because it has not been added to a map yet.
+ obj.map = null;
+
+ return obj;
+ },
+
+ /**
+ * Method: getOptions
+ * Extracts an object from the layer with the properties that were set as
+ * options, but updates them with the values currently set on the
+ * instance.
+ *
+ * Returns:
+ * {Object} the <options> of the layer, representing the current state.
+ */
+ getOptions: function() {
+ var options = {};
+ for(var o in this.options) {
+ options[o] = this[o];
+ }
+ return options;
+ },
+
+ /**
+ * APIMethod: setName
+ * Sets the new layer name for this layer. Can trigger a changelayer event
+ * on the map.
+ *
+ * Parameters:
+ * newName - {String} The new name.
+ */
+ setName: function(newName) {
+ if (newName != this.name) {
+ this.name = newName;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "name"
+ });
+ }
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+
+ if (this.options == null) {
+ this.options = {};
+ }
+
+ if (newOptions) {
+ // make sure this.projection references a projection object
+ if(typeof newOptions.projection == "string") {
+ newOptions.projection = new OpenLayers.Projection(newOptions.projection);
+ }
+ if (newOptions.projection) {
+ // get maxResolution, units and maxExtent from projection defaults if
+ // they are not defined already
+ OpenLayers.Util.applyDefaults(newOptions,
+ OpenLayers.Projection.defaults[newOptions.projection.getCode()]);
+ }
+ // allow array for extents
+ if (newOptions.maxExtent && !(newOptions.maxExtent instanceof OpenLayers.Bounds)) {
+ newOptions.maxExtent = new OpenLayers.Bounds(newOptions.maxExtent);
+ }
+ if (newOptions.minExtent && !(newOptions.minExtent instanceof OpenLayers.Bounds)) {
+ newOptions.minExtent = new OpenLayers.Bounds(newOptions.minExtent);
+ }
+ }
+
+ // update our copy for clone
+ OpenLayers.Util.extend(this.options, newOptions);
+
+ // add new options to this
+ OpenLayers.Util.extend(this, newOptions);
+
+ // get the units from the projection, if we have a projection
+ // and it it has units
+ if(this.projection && this.projection.getUnits()) {
+ this.units = this.projection.getUnits();
+ }
+
+ // re-initialize resolutions if necessary, i.e. if any of the
+ // properties of the "properties" array defined below is set
+ // in the new options
+ if(this.map) {
+ // store current resolution so we can try to restore it later
+ var resolution = this.map.getResolution();
+ var properties = this.RESOLUTION_PROPERTIES.concat(
+ ["projection", "units", "minExtent", "maxExtent"]
+ );
+ for(var o in newOptions) {
+ if(newOptions.hasOwnProperty(o) &&
+ OpenLayers.Util.indexOf(properties, o) >= 0) {
+
+ this.initResolutions();
+ if (reinitialize && this.map.baseLayer === this) {
+ // update map position, and restore previous resolution
+ this.map.setCenter(this.map.getCenter(),
+ this.map.getZoomForResolution(resolution),
+ false, true
+ );
+ // trigger a changebaselayer event to make sure that
+ // all controls (especially
+ // OpenLayers.Control.PanZoomBar) get notified of the
+ // new options
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: onMapResize
+ * This function can be implemented by subclasses
+ */
+ onMapResize: function() {
+ //this function can be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function() {
+ var redrawn = false;
+ if (this.map) {
+
+ // min/max Range may have changed
+ this.inRange = this.calculateInRange();
+
+ // map's center might not yet be set
+ var extent = this.getExtent();
+
+ if (extent && this.inRange && this.visibility) {
+ var zoomChanged = true;
+ this.moveTo(extent, zoomChanged, false);
+ this.events.triggerEvent("moveend",
+ {"zoomChanged": zoomChanged});
+ redrawn = true;
+ }
+ }
+ return redrawn;
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ var display = this.visibility;
+ if (!this.isBaseLayer) {
+ display = display && this.inRange;
+ }
+ this.display(display);
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ },
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Here we take care to bring over any of the necessary default
+ * properties from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ if (this.map == null) {
+
+ this.map = map;
+
+ // grab some essential layer data from the map if it hasn't already
+ // been set
+ this.maxExtent = this.maxExtent || this.map.maxExtent;
+ this.minExtent = this.minExtent || this.map.minExtent;
+
+ this.projection = this.projection || this.map.projection;
+ if (typeof this.projection == "string") {
+ this.projection = new OpenLayers.Projection(this.projection);
+ }
+
+ // Check the projection to see if we can get units -- if not, refer
+ // to properties.
+ this.units = this.projection.getUnits() ||
+ this.units || this.map.units;
+
+ this.initResolutions();
+
+ if (!this.isBaseLayer) {
+ this.inRange = this.calculateInRange();
+ var show = ((this.visibility) && (this.inRange));
+ this.div.style.display = show ? "" : "none";
+ }
+
+ // deal with gutters
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. To be overridden by subclasses.
+ */
+ afterAdd: function() {
+ },
+
+ /**
+ * APIMethod: removeMap
+ * Just as setMap() allows each layer the possibility to take a
+ * personalized action on being added to the map, removeMap() allows
+ * each layer to take a personalized action on being removed from it.
+ * For now, this will be mostly unused, except for the EventPane layer,
+ * which needs this hook so that it can remove the special invisible
+ * pane.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * APIMethod: getImageSize
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} optional tile bounds, can be used
+ * by subclasses that have to deal with different tile sizes at the
+ * layer extent edges (e.g. Zoomify)
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The size that the image should be, taking into
+ * account gutters.
+ */
+ getImageSize: function(bounds) {
+ return (this.imageSize || this.tileSize);
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Set the tile size based on the map size. This also sets layer.imageSize
+ * or use by Tile.Image.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ var tileSize = (size) ? size :
+ ((this.tileSize) ? this.tileSize :
+ this.map.getTileSize());
+ this.tileSize = tileSize;
+ if(this.gutter) {
+ // layers with gutters need non-null tile sizes
+ //if(tileSize == null) {
+ // OpenLayers.console.error("Error in layer.setMap() for " +
+ // this.name + ": layers with " +
+ // "gutters need non-null tile sizes");
+ //}
+ this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter),
+ tileSize.h + (2*this.gutter));
+ }
+ },
+
+ /**
+ * APIMethod: getVisibility
+ *
+ * Returns:
+ * {Boolean} The layer should be displayed (if in range).
+ */
+ getVisibility: function() {
+ return this.visibility;
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visibility - {Boolean} Whether or not to display the layer (if in range)
+ */
+ setVisibility: function(visibility) {
+ if (visibility != this.visibility) {
+ this.visibility = visibility;
+ this.display(visibility);
+ this.redraw();
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "visibility"
+ });
+ }
+ this.events.triggerEvent("visibilitychanged");
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer. This is designed to be used internally, and
+ * is not generally the way to enable or disable the layer. For that,
+ * use the setVisibility function instead..
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ if (display != (this.div.style.display != "none")) {
+ this.div.style.display = (display && this.calculateInRange()) ? "block" : "none";
+ }
+ },
+
+ /**
+ * APIMethod: calculateInRange
+ *
+ * Returns:
+ * {Boolean} The layer is displayable at the current map's current
+ * resolution. Note that if 'alwaysInRange' is true for the layer,
+ * this function will always return true.
+ */
+ calculateInRange: function() {
+ var inRange = false;
+
+ if (this.alwaysInRange) {
+ inRange = true;
+ } else {
+ if (this.map) {
+ var resolution = this.map.getResolution();
+ inRange = ( (resolution >= this.minResolution) &&
+ (resolution <= this.maxResolution) );
+ }
+ }
+ return inRange;
+ },
+
+ /**
+ * APIMethod: setIsBaseLayer
+ *
+ * Parameters:
+ * isBaseLayer - {Boolean}
+ */
+ setIsBaseLayer: function(isBaseLayer) {
+ if (isBaseLayer != this.isBaseLayer) {
+ this.isBaseLayer = isBaseLayer;
+ if (this.map != null) {
+ this.map.events.triggerEvent("changebaselayer", {
+ layer: this
+ });
+ }
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: initResolutions
+ * This method's responsibility is to set up the 'resolutions' array
+ * for the layer -- this array is what the layer will use to interface
+ * between the zoom levels of the map and the resolution display
+ * of the layer.
+ *
+ * The user has several options that determine how the array is set up.
+ *
+ * For a detailed explanation, see the following wiki from the
+ * openlayers.org homepage:
+ * http://trac.openlayers.org/wiki/SettingZoomLevels
+ */
+ initResolutions: function() {
+
+ // ok we want resolutions, here's our strategy:
+ //
+ // 1. if resolutions are defined in the layer config, use them
+ // 2. else, if scales are defined in the layer config then derive
+ // resolutions from these scales
+ // 3. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // layer config
+ // 4. if we still don't have resolutions, and if resolutions
+ // are defined in the same, use them
+ // 5. else, if scales are defined in the map then derive
+ // resolutions from these scales
+ // 6. else, attempt to calculate resolutions from maxResolution,
+ // minResolution, numZoomLevels, maxZoomLevel set in the
+ // map
+ // 7. hope for the best!
+
+ var i, len, p;
+ var props = {}, alwaysInRange = true;
+
+ // get resolution data from layer config
+ // (we also set alwaysInRange in the layer as appropriate)
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p];
+ if(alwaysInRange && this.options[p]) {
+ alwaysInRange = false;
+ }
+ }
+ if(this.options.alwaysInRange == null) {
+ this.alwaysInRange = alwaysInRange;
+ }
+
+ // if we don't have resolutions then attempt to derive them from scales
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+
+ // if we still don't have resolutions then attempt to calculate them
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+
+ // if we couldn't calculate resolutions then we look at we have
+ // in the map
+ if(props.resolutions == null) {
+ for(i=0, len=this.RESOLUTION_PROPERTIES.length; i<len; i++) {
+ p = this.RESOLUTION_PROPERTIES[i];
+ props[p] = this.options[p] != null ?
+ this.options[p] : this.map[p];
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.resolutionsFromScales(props.scales);
+ }
+ if(props.resolutions == null) {
+ props.resolutions = this.calculateResolutions(props);
+ }
+ }
+
+ // ok, we new need to set properties in the instance
+
+ // get maxResolution from the config if it's defined there
+ var maxResolution;
+ if(this.options.maxResolution &&
+ this.options.maxResolution !== "auto") {
+ maxResolution = this.options.maxResolution;
+ }
+ if(this.options.minScale) {
+ maxResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.minScale, this.units);
+ }
+
+ // get minResolution from the config if it's defined there
+ var minResolution;
+ if(this.options.minResolution &&
+ this.options.minResolution !== "auto") {
+ minResolution = this.options.minResolution;
+ }
+ if(this.options.maxScale) {
+ minResolution = OpenLayers.Util.getResolutionFromScale(
+ this.options.maxScale, this.units);
+ }
+
+ if(props.resolutions) {
+
+ //sort resolutions array descendingly
+ props.resolutions.sort(function(a, b) {
+ return (b - a);
+ });
+
+ // if we still don't have a maxResolution get it from the
+ // resolutions array
+ if(!maxResolution) {
+ maxResolution = props.resolutions[0];
+ }
+
+ // if we still don't have a minResolution get it from the
+ // resolutions array
+ if(!minResolution) {
+ var lastIdx = props.resolutions.length - 1;
+ minResolution = props.resolutions[lastIdx];
+ }
+ }
+
+ this.resolutions = props.resolutions;
+ if(this.resolutions) {
+ len = this.resolutions.length;
+ this.scales = new Array(len);
+ for(i=0; i<len; i++) {
+ this.scales[i] = OpenLayers.Util.getScaleFromResolution(
+ this.resolutions[i], this.units);
+ }
+ this.numZoomLevels = len;
+ }
+ this.minResolution = minResolution;
+ if(minResolution) {
+ this.maxScale = OpenLayers.Util.getScaleFromResolution(
+ minResolution, this.units);
+ }
+ this.maxResolution = maxResolution;
+ if(maxResolution) {
+ this.minScale = OpenLayers.Util.getScaleFromResolution(
+ maxResolution, this.units);
+ }
+ },
+
+ /**
+ * Method: resolutionsFromScales
+ * Derive resolutions from scales.
+ *
+ * Parameters:
+ * scales - {Array(Number)} Scales
+ *
+ * Returns
+ * {Array(Number)} Resolutions
+ */
+ resolutionsFromScales: function(scales) {
+ if(scales == null) {
+ return;
+ }
+ var resolutions, i, len;
+ len = scales.length;
+ resolutions = new Array(len);
+ for(i=0; i<len; i++) {
+ resolutions[i] = OpenLayers.Util.getResolutionFromScale(
+ scales[i], this.units);
+ }
+ return resolutions;
+ },
+
+ /**
+ * Method: calculateResolutions
+ * Calculate resolutions based on the provided properties.
+ *
+ * Parameters:
+ * props - {Object} Properties
+ *
+ * Returns:
+ * {Array({Number})} Array of resolutions.
+ */
+ calculateResolutions: function(props) {
+
+ var viewSize, wRes, hRes;
+
+ // determine maxResolution
+ var maxResolution = props.maxResolution;
+ if(props.minScale != null) {
+ maxResolution =
+ OpenLayers.Util.getResolutionFromScale(props.minScale,
+ this.units);
+ } else if(maxResolution == "auto" && this.maxExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.maxExtent.getWidth() / viewSize.w;
+ hRes = this.maxExtent.getHeight() / viewSize.h;
+ maxResolution = Math.max(wRes, hRes);
+ }
+
+ // determine minResolution
+ var minResolution = props.minResolution;
+ if(props.maxScale != null) {
+ minResolution =
+ OpenLayers.Util.getResolutionFromScale(props.maxScale,
+ this.units);
+ } else if(props.minResolution == "auto" && this.minExtent != null) {
+ viewSize = this.map.getSize();
+ wRes = this.minExtent.getWidth() / viewSize.w;
+ hRes = this.minExtent.getHeight()/ viewSize.h;
+ minResolution = Math.max(wRes, hRes);
+ }
+
+ if(typeof maxResolution !== "number" &&
+ typeof minResolution !== "number" &&
+ this.maxExtent != null) {
+ // maxResolution for default grid sets assumes that at zoom
+ // level zero, the whole world fits on one tile.
+ var tileSize = this.map.getTileSize();
+ maxResolution = Math.max(
+ this.maxExtent.getWidth() / tileSize.w,
+ this.maxExtent.getHeight() / tileSize.h
+ );
+ }
+
+ // determine numZoomLevels
+ var maxZoomLevel = props.maxZoomLevel;
+ var numZoomLevels = props.numZoomLevels;
+ if(typeof minResolution === "number" &&
+ typeof maxResolution === "number" && numZoomLevels === undefined) {
+ var ratio = maxResolution / minResolution;
+ numZoomLevels = Math.floor(Math.log(ratio) / Math.log(2)) + 1;
+ } else if(numZoomLevels === undefined && maxZoomLevel != null) {
+ numZoomLevels = maxZoomLevel + 1;
+ }
+
+ // are we able to calculate resolutions?
+ if(typeof numZoomLevels !== "number" || numZoomLevels <= 0 ||
+ (typeof maxResolution !== "number" &&
+ typeof minResolution !== "number")) {
+ return;
+ }
+
+ // now we have numZoomLevels and at least one of maxResolution
+ // or minResolution, we can populate the resolutions array
+
+ var resolutions = new Array(numZoomLevels);
+ var base = 2;
+ if(typeof minResolution == "number" &&
+ typeof maxResolution == "number") {
+ // if maxResolution and minResolution are set, we calculate
+ // the base for exponential scaling that starts at
+ // maxResolution and ends at minResolution in numZoomLevels
+ // steps.
+ base = Math.pow(
+ (maxResolution / minResolution),
+ (1 / (numZoomLevels - 1))
+ );
+ }
+
+ var i;
+ if(typeof maxResolution === "number") {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[i] = maxResolution / Math.pow(base, i);
+ }
+ } else {
+ for(i=0; i<numZoomLevels; i++) {
+ resolutions[numZoomLevels - 1 - i] =
+ minResolution * Math.pow(base, i);
+ }
+ }
+
+ return resolutions;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The currently selected resolution of the map, taken from the
+ * resolutions array, indexed by current zoom level.
+ */
+ getResolution: function() {
+ var zoom = this.map.getZoom();
+ return this.getResolutionForZoom(zoom);
+ },
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function() {
+ // just use stock map calculateBounds function -- passing no arguments
+ // means it will user map's current center & resolution
+ //
+ return this.map.calculateBounds();
+ },
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * for the passed-in extent. We do this by calculating the ideal
+ * resolution for the given extent (based on the map size) and then
+ * calling getZoomForResolution(), passing along the 'closest'
+ * parameter.
+ */
+ getZoomForExtent: function(extent, closest) {
+ var viewSize = this.map.getSize();
+ var idealResolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+
+ return this.getZoomForResolution(idealResolution, closest);
+ },
+
+ /**
+ * Method: getDataExtent
+ * Calculates the max extent which includes all of the data for the layer.
+ * This function is to be implemented by subclasses.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ //to be implemented by subclasses
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom.
+ */
+ getResolutionForZoom: function(zoom) {
+ zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1));
+ var resolution;
+ if(this.map.fractionalZoom) {
+ var low = Math.floor(zoom);
+ var high = Math.ceil(zoom);
+ resolution = this.resolutions[low] -
+ ((zoom-low) * (this.resolutions[low]-this.resolutions[high]));
+ } else {
+ resolution = this.resolutions[Math.round(zoom)];
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} The index of the zoomLevel (entry in the resolutions array)
+ * that corresponds to the best fit resolution given the passed in
+ * value and the 'closest' specification.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom, i, len;
+ if(this.map.fractionalZoom) {
+ var lowZoom = 0;
+ var highZoom = this.resolutions.length - 1;
+ var highRes = this.resolutions[lowZoom];
+ var lowRes = this.resolutions[highZoom];
+ var res;
+ for(i=0, len=this.resolutions.length; i<len; ++i) {
+ res = this.resolutions[i];
+ if(res >= resolution) {
+ highRes = res;
+ lowZoom = i;
+ }
+ if(res <= resolution) {
+ lowRes = res;
+ highZoom = i;
+ break;
+ }
+ }
+ var dRes = highRes - lowRes;
+ if(dRes > 0) {
+ zoom = lowZoom + ((highRes - resolution) / dRes);
+ } else {
+ zoom = lowZoom;
+ }
+ } else {
+ var diff;
+ var minDiff = Number.POSITIVE_INFINITY;
+ for(i=0, len=this.resolutions.length; i<len; i++) {
+ if (closest) {
+ diff = Math.abs(this.resolutions[i] - resolution);
+ if (diff > minDiff) {
+ break;
+ }
+ minDiff = diff;
+ } else {
+ if (this.resolutions[i] < resolution) {
+ break;
+ }
+ }
+ }
+ zoom = Math.max(0, i-1);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in
+ * view port <OpenLayers.Pixel>, translated into lon/lat by the layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ var map = this.map;
+ if (viewPortPx != null && map.minPx) {
+ var res = map.getResolution();
+ var maxExtent = map.getMaxExtent({restricted: true});
+ var lon = (viewPortPx.x - map.minPx.x) * res + maxExtent.left;
+ var lat = (map.minPx.y - viewPortPx.y) * res + maxExtent.top;
+ lonlat = new OpenLayers.LonLat(lon, lat);
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ * Returns a pixel location given a map location. This method will return
+ * fractional pixel values.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Object} An OpenLayers.LonLat or
+ * an object with a 'lon'
+ * and 'lat' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An <OpenLayers.Pixel> which is the passed-in
+ * lonlat translated into view port pixels.
+ */
+ getViewPortPxFromLonLat: function (lonlat, resolution) {
+ var px = null;
+ if (lonlat != null) {
+ resolution = resolution || this.map.getResolution();
+ var extent = this.map.calculateBounds(null, resolution);
+ px = new OpenLayers.Pixel(
+ (1/resolution * (lonlat.lon - extent.left)),
+ (1/resolution * (extent.top - lonlat.lat))
+ );
+ }
+ return px;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity != this.opacity) {
+ this.opacity = opacity;
+ var childNodes = this.div.childNodes;
+ for(var i = 0, len = childNodes.length; i < len; ++i) {
+ var element = childNodes[i].firstChild || childNodes[i];
+ var lastChild = childNodes[i].lastChild;
+ //TODO de-uglify this
+ if (lastChild && lastChild.nodeName.toLowerCase() === "iframe") {
+ element = lastChild.parentNode;
+ }
+ OpenLayers.Util.modifyDOMElement(element, null, null, null,
+ null, null, null, opacity);
+ }
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ }
+ },
+
+ /**
+ * Method: getZIndex
+ *
+ * Returns:
+ * {Integer} the z-index of this layer
+ */
+ getZIndex: function () {
+ return this.div.style.zIndex;
+ },
+
+ /**
+ * Method: setZIndex
+ *
+ * Parameters:
+ * zIndex - {Integer}
+ */
+ setZIndex: function (zIndex) {
+ this.div.style.zIndex = zIndex;
+ },
+
+ /**
+ * Method: adjustBounds
+ * This function will take a bounds, and if wrapDateLine option is set
+ * on the layer, it will return a bounds which is wrapped around the
+ * world. We do not wrap for bounds which *cross* the
+ * maxExtent.left/right, only bounds which are entirely to the left
+ * or entirely to the right.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ adjustBounds: function (bounds) {
+
+ if (this.gutter) {
+ // Adjust the extent of a bounds in map units by the
+ // layer's gutter in pixels.
+ var mapGutter = this.gutter * this.map.getResolution();
+ bounds = new OpenLayers.Bounds(bounds.left - mapGutter,
+ bounds.bottom - mapGutter,
+ bounds.right + mapGutter,
+ bounds.top + mapGutter);
+ }
+
+ if (this.wrapDateLine) {
+ // wrap around the date line, within the limits of rounding error
+ var wrappingOptions = {
+ 'rightTolerance':this.getResolution(),
+ 'leftTolerance':this.getResolution()
+ };
+ bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions);
+
+ }
+ return bounds;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js b/misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js
new file mode 100644
index 0000000..c5bac36
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/ArcGIS93Rest.js
@@ -0,0 +1,225 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.ArcGIS93Rest
+ * Instances of OpenLayers.Layer.ArcGIS93Rest are used to display data from
+ * ESRI ArcGIS Server 9.3 (and up?) Mapping Services using the REST API.
+ * Create a new ArcGIS93Rest layer with the <OpenLayers.Layer.ArcGIS93Rest>
+ * constructor. More detail on the REST API is available at
+ * http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/index.html ;
+ * specifically, the URL provided to this layer should be an export service
+ * URL: http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/export.html
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.ArcGIS93Rest = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: {
+ format: "png"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for ArcGIS93Rest layer
+ */
+ isBaseLayer: true,
+
+
+ /**
+ * Constructor: OpenLayers.Layer.ArcGIS93Rest
+ * Create a new ArcGIS93Rest layer object.
+ *
+ * Example:
+ * (code)
+ * var arcims = new OpenLayers.Layer.ArcGIS93Rest("MyName",
+ * "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer/export",
+ * {
+ * layers: "0,1,2"
+ * });
+ * (end)
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the ArcGIS server REST service
+ * options - {Object} An object with key/value pairs representing the
+ * options and option values.
+ *
+ * Valid Options:
+ * format - {String} MIME type of desired image type.
+ * layers - {String} Comma-separated list of layers to display.
+ * srs - {String} Projection ID.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+ //layer is transparent
+ if (this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "jpg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "gif"
+ : "png";
+ }
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.ArcGIS93Rest>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.ArcGIS93Rest(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+
+ /**
+ * Method: getURL
+ * Return an image url this layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the map image's url.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+
+ // ArcGIS Server only wants the numeric portion of the projection ID.
+ var projWords = this.projection.getCode().split(":");
+ var srid = projWords[projWords.length - 1];
+
+ var imageSize = this.getImageSize();
+ var newParams = {
+ 'BBOX': bounds.toBBOX(),
+ 'SIZE': imageSize.w + "," + imageSize.h,
+ // We always want image, the other options were json, image with a whole lotta html around it, etc.
+ 'F': "image",
+ 'BBOXSR': srid,
+ 'IMAGESR': srid
+ };
+
+ // Now add the filter parameters.
+ if (this.layerDefs) {
+ var layerDefStrList = [];
+ var layerID;
+ for(layerID in this.layerDefs) {
+ if (this.layerDefs.hasOwnProperty(layerID)) {
+ if (this.layerDefs[layerID]) {
+ layerDefStrList.push(layerID);
+ layerDefStrList.push(":");
+ layerDefStrList.push(this.layerDefs[layerID]);
+ layerDefStrList.push(";");
+ }
+ }
+ }
+ if (layerDefStrList.length > 0) {
+ newParams['LAYERDEFS'] = layerDefStrList.join("");
+ }
+ }
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * Method: setLayerFilter
+ * addTile creates a tile, initializes it, and adds it to the layer div.
+ *
+ * Parameters:
+ * id - {String} The id of the layer to which the filter applies.
+ * queryDef - {String} A sql-ish query filter, for more detail see the ESRI
+ * documentation at http://sampleserver1.arcgisonline.com/ArcGIS/SDK/REST/export.html
+ */
+ setLayerFilter: function ( id, queryDef ) {
+ if (!this.layerDefs) {
+ this.layerDefs = {};
+ }
+ if (queryDef) {
+ this.layerDefs[id] = queryDef;
+ } else {
+ delete this.layerDefs[id];
+ }
+ },
+
+ /**
+ * Method: clearLayerFilter
+ * Clears layer filters, either from a specific layer,
+ * or all of them.
+ *
+ * Parameters:
+ * id - {String} The id of the layer from which to remove any
+ * filter. If unspecified/blank, all filters
+ * will be removed.
+ */
+ clearLayerFilter: function ( id ) {
+ if (id) {
+ delete this.layerDefs[id];
+ } else {
+ delete this.layerDefs;
+ }
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.ArcGIS93Rest"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js b/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js
new file mode 100644
index 0000000..99f7dda
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/ArcGISCache.js
@@ -0,0 +1,480 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.ArcGISCache
+ * Layer for accessing cached map tiles from an ArcGIS Server style mapcache.
+ * Tile must already be cached for this layer to access it. This does not require
+ * ArcGIS Server itself.
+ *
+ * A few attempts have been made at this kind of layer before. See
+ * http://trac.osgeo.org/openlayers/ticket/1967
+ * and
+ * http://trac.osgeo.org/openlayers/browser/sandbox/tschaub/arcgiscache/lib/OpenLayers/Layer/ArcGISCache.js
+ *
+ * Typically the problem encountered is that the tiles seem to "jump around".
+ * This is due to the fact that the actual max extent for the tiles on AGS layers
+ * changes at each zoom level due to the way these caches are constructed.
+ * We have attempted to use the resolutions, tile size, and tile origin
+ * from the cache meta data to make the appropriate changes to the max extent
+ * of the tile to compensate for this behavior. This must be done as zoom levels change
+ * and before tiles are requested, which is why methods from base classes are overridden.
+ *
+ * For reference, you can access mapcache meta data in two ways. For accessing a
+ * mapcache through ArcGIS Server, you can simply go to the landing page for the
+ * layer. (ie. http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer)
+ * For accessing it directly through HTTP, there should always be a conf.xml file
+ * in the root directory.
+ * (ie. http://serverx.esri.com/arcgiscache/DG_County_roads_yesA_backgroundDark/Layers/conf.xml)
+ *
+ *Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.ArcGISCache = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: url
+ * {String | Array} The base URL for the layer cache. You can also
+ * provide a list of URL strings for the layer if your cache is
+ * available from multiple origins. This must be set before the layer
+ * is drawn.
+ */
+ url: null,
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} The location of the tile origin for the cache.
+ * An ArcGIS cache has it's origin at the upper-left (lowest x value
+ * and highest y value of the coordinate system). The units for the
+ * tile origin should be the same as the units for the cached data.
+ */
+ tileOrigin: null,
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} This size of each tile. Defaults to 256 by 256 pixels.
+ */
+ tileSize: new OpenLayers.Size(256, 256),
+
+ /**
+ * APIProperty: useAGS
+ * {Boolean} Indicates if we are going to be accessing the ArcGIS Server (AGS)
+ * cache via an AGS MapServer or directly through HTTP. When accessing via
+ * AGS the path structure uses a standard z/y/x structure. But AGS actually
+ * stores the tile images on disk using a hex based folder structure that looks
+ * like "http://example.com/mylayer/L00/R00000000/C00000000.png". Learn more
+ * about this here:
+ * http://blogs.esri.com/Support/blogs/mappingcenter/archive/2010/08/20/Checking-Your-Local-Cache-Folders.aspx
+ * Defaults to true;
+ */
+ useArcGISServer: true,
+
+ /**
+ * APIProperty: type
+ * {String} Image type for the layer. This becomes the filename extension
+ * in tile requests. Default is "png" (generating a url like
+ * "http://example.com/mylayer/L00/R00000000/C00000000.png").
+ */
+ type: 'png',
+
+ /**
+ * APIProperty: useScales
+ * {Boolean} Optional override to indicate that the layer should use 'scale' information
+ * returned from the server capabilities object instead of 'resolution' information.
+ * This can be important if your tile server uses an unusual DPI for the tiles.
+ */
+ useScales: false,
+
+ /**
+ * APIProperty: overrideDPI
+ * {Boolean} Optional override to change the OpenLayers.DOTS_PER_INCH setting based
+ * on the tile information in the server capabilities object. This can be useful
+ * if your server has a non-standard DPI setting on its tiles, and you're only using
+ * tiles with that DPI. This value is used while OpenLayers is calculating resolution
+ * using scales, and is not necessary if you have resolution information. (This is
+ * typically the case) Regardless, this setting can be useful, but is dangerous
+ * because it will impact other layers while calculating resolution. Only use this
+ * if you know what you are doing. (See OpenLayers.Util.getResolutionFromScale)
+ */
+ overrideDPI: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.ArcGISCache
+ * Creates a new instance of this class
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * options - {Object} extra layer options
+ */
+ initialize: function(name, url, options) {
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
+
+ if (this.resolutions) {
+ this.serverResolutions = this.resolutions;
+ this.maxExtent = this.getMaxExtentForResolution(this.resolutions[0]);
+ }
+
+ // this block steps through translating the values from the server layer JSON
+ // capabilities object into values that we can use. This is also a helpful
+ // reference when configuring this layer directly.
+ if (this.layerInfo) {
+ // alias the object
+ var info = this.layerInfo;
+
+ // build our extents
+ var startingTileExtent = new OpenLayers.Bounds(
+ info.fullExtent.xmin,
+ info.fullExtent.ymin,
+ info.fullExtent.xmax,
+ info.fullExtent.ymax
+ );
+
+ // set our projection based on the given spatial reference.
+ // esri uses slightly different IDs, so this may not be comprehensive
+ this.projection = 'EPSG:' + info.spatialReference.wkid;
+ this.sphericalMercator = (info.spatialReference.wkid == 102100);
+
+ // convert esri units into openlayers units (basic feet or meters only)
+ this.units = (info.units == "esriFeet") ? 'ft' : 'm';
+
+ // optional extended section based on whether or not the server returned
+ // specific tile information
+ if (!!info.tileInfo) {
+ // either set the tiles based on rows/columns, or specific width/height
+ this.tileSize = new OpenLayers.Size(
+ info.tileInfo.width || info.tileInfo.cols,
+ info.tileInfo.height || info.tileInfo.rows
+ );
+
+ // this must be set when manually configuring this layer
+ this.tileOrigin = new OpenLayers.LonLat(
+ info.tileInfo.origin.x,
+ info.tileInfo.origin.y
+ );
+
+ var upperLeft = new OpenLayers.Geometry.Point(
+ startingTileExtent.left,
+ startingTileExtent.top
+ );
+
+ var bottomRight = new OpenLayers.Geometry.Point(
+ startingTileExtent.right,
+ startingTileExtent.bottom
+ );
+
+ if (this.useScales) {
+ this.scales = [];
+ } else {
+ this.resolutions = [];
+ }
+
+ this.lods = [];
+ for(var key in info.tileInfo.lods) {
+ if (info.tileInfo.lods.hasOwnProperty(key)) {
+ var lod = info.tileInfo.lods[key];
+ if (this.useScales) {
+ this.scales.push(lod.scale);
+ } else {
+ this.resolutions.push(lod.resolution);
+ }
+
+ var start = this.getContainingTileCoords(upperLeft, lod.resolution);
+ lod.startTileCol = start.x;
+ lod.startTileRow = start.y;
+
+ var end = this.getContainingTileCoords(bottomRight, lod.resolution);
+ lod.endTileCol = end.x;
+ lod.endTileRow = end.y;
+ this.lods.push(lod);
+ }
+ }
+
+ this.maxExtent = this.calculateMaxExtentWithLOD(this.lods[0]);
+ this.serverResolutions = this.resolutions;
+ if (this.overrideDPI && info.tileInfo.dpi) {
+ // see comment above for 'overrideDPI'
+ OpenLayers.DOTS_PER_INCH = info.tileInfo.dpi;
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getContainingTileCoords
+ * Calculates the x/y pixel corresponding to the position of the tile
+ * that contains the given point and for the for the given resolution.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position
+ * of the upper left tile for the given resolution.
+ */
+ getContainingTileCoords: function(point, res) {
+ return new OpenLayers.Pixel(
+ Math.max(Math.floor((point.x - this.tileOrigin.lon) / (this.tileSize.w * res)),0),
+ Math.max(Math.floor((this.tileOrigin.lat - point.y) / (this.tileSize.h * res)),0)
+ );
+ },
+
+ /**
+ * Method: calculateMaxExtentWithLOD
+ * Given a Level of Detail object from the server, this function
+ * calculates the actual max extent
+ *
+ * Parameters:
+ * lod - {Object} a Level of Detail Object from the server capabilities object
+ representing a particular zoom level
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The actual extent of the tiles for the given zoom level
+ */
+ calculateMaxExtentWithLOD: function(lod) {
+ // the max extent we're provided with just overlaps some tiles
+ // our real extent is the bounds of all the tiles we touch
+
+ var numTileCols = (lod.endTileCol - lod.startTileCol) + 1;
+ var numTileRows = (lod.endTileRow - lod.startTileRow) + 1;
+
+ var minX = this.tileOrigin.lon + (lod.startTileCol * this.tileSize.w * lod.resolution);
+ var maxX = minX + (numTileCols * this.tileSize.w * lod.resolution);
+
+ var maxY = this.tileOrigin.lat - (lod.startTileRow * this.tileSize.h * lod.resolution);
+ var minY = maxY - (numTileRows * this.tileSize.h * lod.resolution);
+ return new OpenLayers.Bounds(minX, minY, maxX, maxY);
+ },
+
+ /**
+ * Method: calculateMaxExtentWithExtent
+ * Given a 'suggested' max extent from the server, this function uses
+ * information about the actual tile sizes to determine the actual
+ * extent of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>} The 'suggested' extent for the layer
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The actual extent of the tiles for the given zoom level
+ */
+ calculateMaxExtentWithExtent: function(extent, res) {
+ var upperLeft = new OpenLayers.Geometry.Point(extent.left, extent.top);
+ var bottomRight = new OpenLayers.Geometry.Point(extent.right, extent.bottom);
+ var start = this.getContainingTileCoords(upperLeft, res);
+ var end = this.getContainingTileCoords(bottomRight, res);
+ var lod = {
+ resolution: res,
+ startTileCol: start.x,
+ startTileRow: start.y,
+ endTileCol: end.x,
+ endTileRow: end.y
+ };
+ return this.calculateMaxExtentWithLOD(lod);
+ },
+
+ /**
+ * Method: getUpperLeftTileCoord
+ * Calculates the x/y pixel corresponding to the position
+ * of the upper left tile for the given resolution.
+ *
+ * Parameters:
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position
+ * of the upper left tile for the given resolution.
+ */
+ getUpperLeftTileCoord: function(res) {
+ var upperLeft = new OpenLayers.Geometry.Point(
+ this.maxExtent.left,
+ this.maxExtent.top);
+ return this.getContainingTileCoords(upperLeft, res);
+ },
+
+ /**
+ * Method: getLowerRightTileCoord
+ * Calculates the x/y pixel corresponding to the position
+ * of the lower right tile for the given resolution.
+ *
+ * Parameters:
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The x/y pixel corresponding to the position
+ * of the lower right tile for the given resolution.
+ */
+ getLowerRightTileCoord: function(res) {
+ var bottomRight = new OpenLayers.Geometry.Point(
+ this.maxExtent.right,
+ this.maxExtent.bottom);
+ return this.getContainingTileCoords(bottomRight, res);
+ },
+
+ /**
+ * Method: getMaxExtentForResolution
+ * Since the max extent of a set of tiles can change from zoom level
+ * to zoom level, we need to be able to calculate that max extent
+ * for a given resolution.
+ *
+ * Parameters:
+ * res - {Float} The resolution for which to compute the extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The extent for this resolution
+ */
+ getMaxExtentForResolution: function(res) {
+ var start = this.getUpperLeftTileCoord(res);
+ var end = this.getLowerRightTileCoord(res);
+
+ var numTileCols = (end.x - start.x) + 1;
+ var numTileRows = (end.y - start.y) + 1;
+
+ var minX = this.tileOrigin.lon + (start.x * this.tileSize.w * res);
+ var maxX = minX + (numTileCols * this.tileSize.w * res);
+
+ var maxY = this.tileOrigin.lat - (start.y * this.tileSize.h * res);
+ var minY = maxY - (numTileRows * this.tileSize.h * res);
+ return new OpenLayers.Bounds(minX, minY, maxX, maxY);
+ },
+
+ /**
+ * APIMethod: clone
+ * Returns an exact clone of this OpenLayers.Layer.ArcGISCache
+ *
+ * Parameters:
+ * [obj] - {Object} optional object to assign the cloned instance to.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.ArcGISCache>} clone of this instance
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.ArcGISCache(this.name, this.url, this.options);
+ }
+ return OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ },
+
+ /**
+ * Method: initGriddedTiles
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initGriddedTiles: function(bounds) {
+ delete this._tileOrigin;
+ OpenLayers.Layer.XYZ.prototype.initGriddedTiles.apply(this, arguments);
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ var resolution = this.map.getResolution();
+ return this.maxExtent = this.getMaxExtentForResolution(resolution);
+ },
+
+ /**
+ * Method: getTileOrigin
+ * Determine the origin for aligning the grid of tiles.
+ * The origin will be derived from the layer's <maxExtent> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The tile origin.
+ */
+ getTileOrigin: function() {
+ if (!this._tileOrigin) {
+ var extent = this.getMaxExtent();
+ this._tileOrigin = new OpenLayers.LonLat(extent.left, extent.bottom);
+ }
+ return this._tileOrigin;
+ },
+
+ /**
+ * Method: getURL
+ * Determine the URL for a tile given the tile bounds. This is should support
+ * urls that access tiles through an ArcGIS Server MapServer or directly through
+ * the hex folder structure using HTTP. Just be sure to set the useArcGISServer
+ * property appropriately! This is basically the same as
+ * 'OpenLayers.Layer.TMS.getURL', but with the addition of hex addressing,
+ * and tile rounding.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} The URL for a tile based on given bounds.
+ */
+ getURL: function (bounds) {
+ var res = this.getResolution();
+
+ // tile center
+ var originTileX = (this.tileOrigin.lon + (res * this.tileSize.w/2));
+ var originTileY = (this.tileOrigin.lat - (res * this.tileSize.h/2));
+
+ var center = bounds.getCenterLonLat();
+ var point = { x: center.lon, y: center.lat };
+ var x = (Math.round(Math.abs((center.lon - originTileX) / (res * this.tileSize.w))));
+ var y = (Math.round(Math.abs((originTileY - center.lat) / (res * this.tileSize.h))));
+ var z = this.map.getZoom();
+
+ // this prevents us from getting pink tiles (non-existant tiles)
+ if (this.lods) {
+ var lod = this.lods[this.map.getZoom()];
+ if ((x < lod.startTileCol || x > lod.endTileCol)
+ || (y < lod.startTileRow || y > lod.endTileRow)) {
+ return null;
+ }
+ }
+ else {
+ var start = this.getUpperLeftTileCoord(res);
+ var end = this.getLowerRightTileCoord(res);
+ if ((x < start.x || x >= end.x)
+ || (y < start.y || y >= end.y)) {
+ return null;
+ }
+ }
+
+ // Construct the url string
+ var url = this.url;
+ var s = '' + x + y + z;
+
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(s, url);
+ }
+
+ // Accessing tiles through ArcGIS Server uses a different path
+ // structure than direct access via the folder structure.
+ if (this.useArcGISServer) {
+ // AGS MapServers have pretty url access to tiles
+ url = url + '/tile/${z}/${y}/${x}';
+ } else {
+ // The tile images are stored using hex values on disk.
+ x = 'C' + OpenLayers.Number.zeroPad(x, 8, 16);
+ y = 'R' + OpenLayers.Number.zeroPad(y, 8, 16);
+ z = 'L' + OpenLayers.Number.zeroPad(z, 2, 10);
+ url = url + '/${z}/${y}/${x}.' + this.type;
+ }
+
+ // Write the values into our formatted url
+ url = OpenLayers.String.format(url, {'x': x, 'y': y, 'z': z});
+
+ return OpenLayers.Util.urlAppend(
+ url, OpenLayers.Util.getParameterString(this.params)
+ );
+ },
+
+ CLASS_NAME: 'OpenLayers.Layer.ArcGISCache'
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js b/misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js
new file mode 100644
index 0000000..e19584c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/ArcIMS.js
@@ -0,0 +1,425 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Format/ArcXML.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.ArcIMS
+ * Instances of OpenLayers.Layer.ArcIMS are used to display data from ESRI ArcIMS
+ * Mapping Services. Create a new ArcIMS layer with the <OpenLayers.Layer.ArcIMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.ArcIMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Default query string parameters.
+ */
+ DEFAULT_PARAMS: {
+ ClientVersion: "9.2",
+ ServiceName: ''
+ },
+
+ /**
+ * APIProperty: featureCoordSys
+ * {String} Code for feature coordinate system. Default is "4326".
+ */
+ featureCoordSys: "4326",
+
+ /**
+ * APIProperty: filterCoordSys
+ * {String} Code for filter coordinate system. Default is "4326".
+ */
+ filterCoordSys: "4326",
+
+ /**
+ * APIProperty: layers
+ * {Array} An array of objects with layer properties.
+ */
+ layers: null,
+
+ /**
+ * APIProperty: async
+ * {Boolean} Request images asynchronously. Default is true.
+ */
+ async: true,
+
+ /**
+ * APIProperty: name
+ * {String} Layer name. Default is "ArcIMS".
+ */
+ name: "ArcIMS",
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is true.
+ */
+ isBaseLayer: true,
+
+ /**
+ * Constant: DEFAULT_OPTIONS
+ * {Object} Default layers properties.
+ */
+ DEFAULT_OPTIONS: {
+ tileSize: new OpenLayers.Size(512, 512),
+ featureCoordSys: "4326",
+ filterCoordSys: "4326",
+ layers: null,
+ isBaseLayer: true,
+ async: true,
+ name: "ArcIMS"
+ },
+
+ /**
+ * Constructor: OpenLayers.Layer.ArcIMS
+ * Create a new ArcIMS layer object.
+ *
+ * Example:
+ * (code)
+ * var arcims = new OpenLayers.Layer.ArcIMS(
+ * "Global Sample",
+ * "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap",
+ * {
+ * service: "OpenLayers_Sample",
+ * layers: [
+ * // layers to manipulate
+ * {id: "1", visible: true}
+ * ]
+ * }
+ * );
+ * (end)
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the ArcIMS server
+ * options - {Object} Optional object with properties to be set on the
+ * layer.
+ */
+ initialize: function(name, url, options) {
+
+ this.tileSize = new OpenLayers.Size(512, 512);
+
+ // parameters
+ this.params = OpenLayers.Util.applyDefaults(
+ {ServiceName: options.serviceName},
+ this.DEFAULT_PARAMS
+ );
+ this.options = OpenLayers.Util.applyDefaults(
+ options, this.DEFAULT_OPTIONS
+ );
+
+ OpenLayers.Layer.Grid.prototype.initialize.apply(
+ this, [name, url, this.params, options]
+ );
+
+ //layer is transparent
+ if (this.transparent) {
+
+ // unless explicitly set in options, make layer an overlay
+ if (!this.isBaseLayer) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.format == "image/jpeg") {
+ this.format = OpenLayers.Util.alphaHack() ? "image/gif" : "image/png";
+ }
+ }
+
+ // create an empty layer list if no layers specified in the options
+ if (this.options.layers === null) {
+ this.options.layers = [];
+ }
+ },
+
+ /**
+ * Method: getURL
+ * Return an image url this layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the map image's url.
+ */
+ getURL: function(bounds) {
+ var url = "";
+ bounds = this.adjustBounds(bounds);
+
+ // create an arcxml request to generate the image
+ var axlReq = new OpenLayers.Format.ArcXML(
+ OpenLayers.Util.extend(this.options, {
+ requesttype: "image",
+ envelope: bounds.toArray(),
+ tileSize: this.tileSize
+ })
+ );
+
+ // create a synchronous ajax request to get an arcims image
+ var req = new OpenLayers.Request.POST({
+ url: this.getFullRequestString(),
+ data: axlReq.write(),
+ async: false
+ });
+
+ // if the response exists
+ if (req != null) {
+ var doc = req.responseXML;
+
+ if (!doc || !doc.documentElement) {
+ doc = req.responseText;
+ }
+
+ // create a new arcxml format to read the response
+ var axlResp = new OpenLayers.Format.ArcXML();
+ var arcxml = axlResp.read(doc);
+ url = this.getUrlOrImage(arcxml.image.output);
+ }
+
+ return url;
+ },
+
+
+ /**
+ * Method: getURLasync
+ * Get an image url this layer asynchronously, and execute a callback
+ * when the image url is generated.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ * callback - {Function} Function to call when image url is retrieved.
+ * scope - {Object} The scope of the callback method.
+ */
+ getURLasync: function(bounds, callback, scope) {
+ bounds = this.adjustBounds(bounds);
+
+ // create an arcxml request to generate the image
+ var axlReq = new OpenLayers.Format.ArcXML(
+ OpenLayers.Util.extend(this.options, {
+ requesttype: "image",
+ envelope: bounds.toArray(),
+ tileSize: this.tileSize
+ })
+ );
+
+ // create an asynchronous ajax request to get an arcims image
+ OpenLayers.Request.POST({
+ url: this.getFullRequestString(),
+ async: true,
+ data: axlReq.write(),
+ callback: function(req) {
+ // process the response from ArcIMS, and call the callback function
+ // to set the image URL
+ var doc = req.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = req.responseText;
+ }
+
+ // create a new arcxml format to read the response
+ var axlResp = new OpenLayers.Format.ArcXML();
+ var arcxml = axlResp.read(doc);
+
+ callback.call(scope, this.getUrlOrImage(arcxml.image.output));
+ },
+ scope: this
+ });
+ },
+
+ /**
+ * Method: getUrlOrImage
+ * Extract a url or image from the ArcXML image output.
+ *
+ * Parameters:
+ * output - {Object} The image.output property of the object returned from
+ * the ArcXML format read method.
+ *
+ * Returns:
+ * {String} A URL for an image (potentially with the data protocol).
+ */
+ getUrlOrImage: function(output) {
+ var ret = "";
+ if(output.url) {
+ // If the image response output url is a string, then the image
+ // data is not inline.
+ ret = output.url;
+ } else if(output.data) {
+ // The image data is inline and base64 encoded, create a data
+ // url for the image. This will only work for small images,
+ // due to browser url length limits.
+ ret = "data:image/" + output.type +
+ ";base64," + output.data;
+ }
+ return ret;
+ },
+
+ /**
+ * Method: setLayerQuery
+ * Set the query definition on this layer. Query definitions are used to
+ * render parts of the spatial data in an image, and can be used to
+ * filter features or layers in the ArcIMS service.
+ *
+ * Parameters:
+ * id - {String} The ArcIMS layer ID.
+ * querydef - {Object} The query definition to apply to this layer.
+ */
+ setLayerQuery: function(id, querydef) {
+ // find the matching layer, if it exists
+ for (var lyr = 0; lyr < this.options.layers.length; lyr++) {
+ if (id == this.options.layers[lyr].id) {
+ // replace this layer definition
+ this.options.layers[lyr].query = querydef;
+ return;
+ }
+ }
+
+ // no layer found, create a new definition
+ this.options.layers.push({id: id, visible: true, query: querydef});
+ },
+
+ /**
+ * Method: getFeatureInfo
+ * Get feature information from ArcIMS. Using the applied geometry, apply
+ * the options to the query (buffer, area/envelope intersection), and
+ * query the ArcIMS service.
+ *
+ * A note about accuracy:
+ * ArcIMS interprets the accuracy attribute in feature requests to be
+ * something like the 'modulus' operator on feature coordinates,
+ * applied to the database geometry of the feature. It doesn't round,
+ * so your feature coordinates may be up to (1 x accuracy) offset from
+ * the actual feature coordinates. If the accuracy of the layer is not
+ * specified, the accuracy will be computed to be approximately 1
+ * feature coordinate per screen pixel.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.LonLat>} or {<OpenLayers.Geometry.Polygon>} The
+ * geometry to use when making the query. This should be a closed
+ * polygon for behavior approximating a free selection.
+ * layer - {Object} The ArcIMS layer definition. This is an anonymous object
+ * that looks like:
+ * (code)
+ * {
+ * id: "ArcXML layer ID", // the ArcXML layer ID
+ * query: {
+ * where: "STATE = 'PA'", // the where clause of the query
+ * accuracy: 100 // the accuracy of the returned feature
+ * }
+ * }
+ * (end)
+ * options - {Object} Object with non-default properties to set on the layer.
+ * Supported properties are buffer, callback, scope, and any other
+ * properties applicable to the ArcXML format. Set the 'callback' and
+ * 'scope' for an object and function to recieve the parsed features
+ * from ArcIMS.
+ */
+ getFeatureInfo: function(geometry, layer, options) {
+ // set the buffer to 1 unit (dd/m/ft?) by default
+ var buffer = options.buffer || 1;
+ // empty callback by default
+ var callback = options.callback || function() {};
+ // default scope is window (global)
+ var scope = options.scope || window;
+
+ // apply these option to the request options
+ var requestOptions = {};
+ OpenLayers.Util.extend(requestOptions, this.options);
+
+ // this is a feature request
+ requestOptions.requesttype = "feature";
+
+ if (geometry instanceof OpenLayers.LonLat) {
+ // create an envelope if the geometry is really a lon/lat
+ requestOptions.polygon = null;
+ requestOptions.envelope = [
+ geometry.lon - buffer,
+ geometry.lat - buffer,
+ geometry.lon + buffer,
+ geometry.lat + buffer
+ ];
+ } else if (geometry instanceof OpenLayers.Geometry.Polygon) {
+ // use the polygon assigned, and empty the envelope
+ requestOptions.envelope = null;
+ requestOptions.polygon = geometry;
+ }
+
+ // create an arcxml request to get feature requests
+ var arcxml = new OpenLayers.Format.ArcXML(requestOptions);
+
+ // apply any get feature options to the arcxml request
+ OpenLayers.Util.extend(arcxml.request.get_feature, options);
+
+ arcxml.request.get_feature.layer = layer.id;
+ if (typeof layer.query.accuracy == "number") {
+ // set the accuracy if it was specified
+ arcxml.request.get_feature.query.accuracy = layer.query.accuracy;
+ } else {
+ // guess that the accuracy is 1 per screen pixel
+ var mapCenter = this.map.getCenter();
+ var viewPx = this.map.getViewPortPxFromLonLat(mapCenter);
+ viewPx.x++;
+ var mapOffCenter = this.map.getLonLatFromPixel(viewPx);
+ arcxml.request.get_feature.query.accuracy = mapOffCenter.lon - mapCenter.lon;
+ }
+
+ // set the get_feature query to be the same as the layer passed in
+ arcxml.request.get_feature.query.where = layer.query.where;
+
+ // use area_intersection
+ arcxml.request.get_feature.query.spatialfilter.relation = "area_intersection";
+
+ // create a new asynchronous request to get the feature info
+ OpenLayers.Request.POST({
+ url: this.getFullRequestString({'CustomService': 'Query'}),
+ data: arcxml.write(),
+ callback: function(request) {
+ // parse the arcxml response
+ var response = arcxml.parseResponse(request.responseText);
+
+ if (!arcxml.iserror()) {
+ // if the arcxml is not an error, call the callback with the features parsed
+ callback.call(scope, response.features);
+ } else {
+ // if the arcxml is an error, return null features selected
+ callback.call(scope, null);
+ }
+ }
+ });
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.ArcIMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.ArcIMS(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.ArcIMS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Bing.js b/misc/openlayers/lib/OpenLayers/Layer/Bing.js
new file mode 100644
index 0000000..0615285
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Bing.js
@@ -0,0 +1,333 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Bing
+ * Bing layer using direct tile access as provided by Bing Maps REST Services.
+ * See http://msdn.microsoft.com/en-us/library/ff701713.aspx for more
+ * information. Note: Terms of Service compliant use requires the map to be
+ * configured with an <OpenLayers.Control.Attribution> control and the
+ * attribution placed on or near the map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * Property: key
+ * {String} API key for Bing maps, get your own key
+ * at http://bingmapsportal.com/ .
+ */
+ key: null,
+
+ /**
+ * Property: serverResolutions
+ * {Array} the resolutions provided by the Bing servers.
+ */
+ serverResolutions: [
+ 156543.03390625, 78271.516953125, 39135.7584765625,
+ 19567.87923828125, 9783.939619140625, 4891.9698095703125,
+ 2445.9849047851562, 1222.9924523925781, 611.4962261962891,
+ 305.74811309814453, 152.87405654907226, 76.43702827453613,
+ 38.218514137268066, 19.109257068634033, 9.554628534317017,
+ 4.777314267158508, 2.388657133579254, 1.194328566789627,
+ 0.5971642833948135, 0.29858214169740677, 0.14929107084870338,
+ 0.07464553542435169
+ ],
+
+ /**
+ * Property: attributionTemplate
+ * {String}
+ */
+ attributionTemplate: '<span class="olBingAttribution ${type}">' +
+ '<div><a target="_blank" href="http://www.bing.com/maps/">' +
+ '<img src="${logo}" /></a></div>${copyrights}' +
+ '<a style="white-space: nowrap" target="_blank" '+
+ 'href="http://www.microsoft.com/maps/product/terms.html">' +
+ 'Terms of Use</a></span>',
+
+ /**
+ * Property: metadata
+ * {Object} Metadata for this layer, as returned by the callback script
+ */
+ metadata: null,
+
+ /**
+ * Property: protocolRegex
+ * {RegExp} Regular expression to match and replace http: in bing urls
+ */
+ protocolRegex: /^http:/i,
+
+ /**
+ * APIProperty: type
+ * {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used. Default is "Road".
+ */
+ type: "Road",
+
+ /**
+ * APIProperty: culture
+ * {String} The culture identifier. See http://msdn.microsoft.com/en-us/library/ff701709.aspx
+ * for the definition and the possible values. Default is "en-US".
+ */
+ culture: "en-US",
+
+ /**
+ * APIProperty: metadataParams
+ * {Object} Optional url parameters for the Get Imagery Metadata request
+ * as described here: http://msdn.microsoft.com/en-us/library/ff701716.aspx
+ */
+ metadataParams: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ */
+ tileOptions: null,
+
+ /** APIProperty: protocol
+ * {String} Protocol to use to fetch Imagery Metadata, tiles and bing logo
+ * Can be 'http:' 'https:' or ''
+ *
+ * Warning: tiles may not be available under both HTTP and HTTPS protocols.
+ * Microsoft approved use of both HTTP and HTTPS urls for tiles. However
+ * this is undocumented and the Imagery Metadata API always returns HTTP
+ * urls.
+ *
+ * Default is '', unless when executed from a file:/// uri, in which case
+ * it is 'http:'.
+ */
+ protocol: ~window.location.href.indexOf('http') ? '' : 'http:',
+
+ /**
+ * Constructor: OpenLayers.Layer.Bing
+ * Create a new Bing layer.
+ *
+ * Example:
+ * (code)
+ * var road = new OpenLayers.Layer.Bing({
+ * name: "My Bing Aerial Layer",
+ * type: "Aerial",
+ * key: "my-api-key-here",
+ * });
+ * (end)
+ *
+ * Parameters:
+ * options - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * key - {String} Bing Maps API key for your application. Get one at
+ * http://bingmapsportal.com/.
+ * type - {String} The layer identifier. Any non-birdseye imageryType
+ * from http://msdn.microsoft.com/en-us/library/ff701716.aspx can be
+ * used.
+ *
+ * Any other documented layer properties can be provided in the config object.
+ */
+ initialize: function(options) {
+ options = OpenLayers.Util.applyDefaults({
+ sphericalMercator: true
+ }, options);
+ var name = options.name || "Bing " + (options.type || this.type);
+
+ var newArgs = [name, null, options];
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, newArgs);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options.tileOptions);
+ this.loadMetadata();
+ },
+
+ /**
+ * Method: loadMetadata
+ */
+ loadMetadata: function() {
+ this._callbackId = "_callback_" + this.id.replace(/\./g, "_");
+ // link the processMetadata method to the global scope and bind it
+ // to this instance
+ window[this._callbackId] = OpenLayers.Function.bind(
+ OpenLayers.Layer.Bing.processMetadata, this
+ );
+ var params = OpenLayers.Util.applyDefaults({
+ key: this.key,
+ jsonp: this._callbackId,
+ include: "ImageryProviders"
+ }, this.metadataParams);
+ var url = this.protocol + "//dev.virtualearth.net/REST/v1/Imagery/Metadata/" +
+ this.type + "?" + OpenLayers.Util.getParameterString(params);
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.id = this._callbackId;
+ document.getElementsByTagName("head")[0].appendChild(script);
+ },
+
+ /**
+ * Method: initLayer
+ *
+ * Sets layer properties according to the metadata provided by the API
+ */
+ initLayer: function() {
+ var res = this.metadata.resourceSets[0].resources[0];
+ var url = res.imageUrl.replace("{quadkey}", "${quadkey}");
+ url = url.replace("{culture}", this.culture);
+ url = url.replace(this.protocolRegex, this.protocol);
+ this.url = [];
+ for (var i=0; i<res.imageUrlSubdomains.length; ++i) {
+ this.url.push(url.replace("{subdomain}", res.imageUrlSubdomains[i]));
+ }
+ this.addOptions({
+ maxResolution: Math.min(
+ this.serverResolutions[res.zoomMin],
+ this.maxResolution || Number.POSITIVE_INFINITY
+ ),
+ numZoomLevels: Math.min(
+ res.zoomMax + 1 - res.zoomMin, this.numZoomLevels
+ )
+ }, true);
+ if (!this.isBaseLayer) {
+ this.redraw();
+ }
+ this.updateAttribution();
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Paramters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getURL: function(bounds) {
+ if (!this.url) {
+ return;
+ }
+ var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z;
+ var quadDigits = [];
+ for (var i = z; i > 0; --i) {
+ var digit = '0';
+ var mask = 1 << (i - 1);
+ if ((x & mask) != 0) {
+ digit++;
+ }
+ if ((y & mask) != 0) {
+ digit++;
+ digit++;
+ }
+ quadDigits.push(digit);
+ }
+ var quadKey = quadDigits.join("");
+ var url = this.selectUrl('' + x + y + z, this.url);
+
+ return OpenLayers.String.format(url, {'quadkey': quadKey});
+ },
+
+ /**
+ * Method: updateAttribution
+ * Updates the attribution according to the requirements outlined in
+ * http://gis.638310.n2.nabble.com/Bing-imagery-td5789168.html
+ */
+ updateAttribution: function() {
+ var metadata = this.metadata;
+ if (!metadata.resourceSets || !this.map || !this.map.center) {
+ return;
+ }
+ var res = metadata.resourceSets[0].resources[0];
+ var extent = this.map.getExtent().transform(
+ this.map.getProjectionObject(),
+ new OpenLayers.Projection("EPSG:4326")
+ );
+ var providers = res.imageryProviders || [],
+ zoom = OpenLayers.Util.indexOf(this.serverResolutions,
+ this.getServerResolution()),
+ copyrights = "", provider, i, ii, j, jj, bbox, coverage;
+ for (i=0,ii=providers.length; i<ii; ++i) {
+ provider = providers[i];
+ for (j=0,jj=provider.coverageAreas.length; j<jj; ++j) {
+ coverage = provider.coverageAreas[j];
+ // axis order provided is Y,X
+ bbox = OpenLayers.Bounds.fromArray(coverage.bbox, true);
+ if (extent.intersectsBounds(bbox) &&
+ zoom <= coverage.zoomMax && zoom >= coverage.zoomMin) {
+ copyrights += provider.attribution + " ";
+ }
+ }
+ }
+ var logo = metadata.brandLogoUri.replace(this.protocolRegex, this.protocol);
+ this.attribution = OpenLayers.String.format(this.attributionTemplate, {
+ type: this.type.toLowerCase(),
+ logo: logo,
+ copyrights: copyrights
+ });
+ this.map && this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "attribution"
+ });
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function() {
+ OpenLayers.Layer.XYZ.prototype.setMap.apply(this, arguments);
+ this.map.events.register("moveend", this, this.updateAttribution);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Bing>} An exact clone of this <OpenLayers.Layer.Bing>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Bing(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.map &&
+ this.map.events.unregister("moveend", this, this.updateAttribution);
+ OpenLayers.Layer.XYZ.prototype.destroy.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Bing"
+});
+
+/**
+ * Function: OpenLayers.Layer.Bing.processMetadata
+ * This function will be bound to an instance, linked to the global scope with
+ * an id, and called by the JSONP script returned by the API.
+ *
+ * Parameters:
+ * metadata - {Object} metadata as returned by the API
+ */
+OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ this.metadata = metadata;
+ this.initLayer();
+ var script = document.getElementById(this._callbackId);
+ script.parentNode.removeChild(script);
+ window[this._callbackId] = undefined; // cannot delete from window in IE
+ delete this._callbackId;
+};
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Boxes.js b/misc/openlayers/lib/OpenLayers/Layer/Boxes.js
new file mode 100644
index 0000000..7cd605a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Boxes.js
@@ -0,0 +1,76 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Layer/Markers.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Boxes
+ * Draw divs as 'boxes' on the layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Boxes = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+ /**
+ * Constructor: OpenLayers.Layer.Boxes
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+
+ /**
+ * Method: drawMarker
+ * Calculate the pixel location for the marker, create it, and
+ * add it to the layer's div
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker.Box>}
+ */
+ drawMarker: function(marker) {
+ var topleft = this.map.getLayerPxFromLonLat({
+ lon: marker.bounds.left,
+ lat: marker.bounds.top
+ });
+ var botright = this.map.getLayerPxFromLonLat({
+ lon: marker.bounds.right,
+ lat: marker.bounds.bottom
+ });
+ if (botright == null || topleft == null) {
+ marker.display(false);
+ } else {
+ var markerDiv = marker.draw(topleft, {
+ w: Math.max(1, botright.x - topleft.x),
+ h: Math.max(1, botright.y - topleft.y)
+ });
+ if (!marker.drawn) {
+ this.div.appendChild(markerDiv);
+ marker.drawn = true;
+ }
+ }
+ },
+
+
+ /**
+ * APIMethod: removeMarker
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker.Box>}
+ */
+ removeMarker: function(marker) {
+ OpenLayers.Util.removeItem(this.markers, marker);
+ if ((marker.div != null) &&
+ (marker.div.parentNode == this.div) ) {
+ this.div.removeChild(marker.div);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Boxes"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/EventPane.js b/misc/openlayers/lib/OpenLayers/Layer/EventPane.js
new file mode 100644
index 0000000..15a852f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/EventPane.js
@@ -0,0 +1,441 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.EventPane
+ * Base class for 3rd party layers, providing a DOM element which isolates
+ * the 3rd-party layer from mouse events.
+ * Only used by Google layers.
+ *
+ * Automatically instantiated by the Google constructor, and not usually instantiated directly.
+ *
+ * Create a new event pane layer with the
+ * <OpenLayers.Layer.EventPane> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: smoothDragPan
+ * {Boolean} smoothDragPan determines whether non-public/internal API
+ * methods are used for better performance while dragging EventPane
+ * layers. When not in sphericalMercator mode, the smoother dragging
+ * doesn't actually move north/south directly with the number of
+ * pixels moved, resulting in a slight offset when you drag your mouse
+ * north south with this option on. If this visual disparity bothers
+ * you, you should turn this option off, or use spherical mercator.
+ * Default is on.
+ */
+ smoothDragPan: true,
+
+ /**
+ * Property: isBaseLayer
+ * {Boolean} EventPaned layers are always base layers, by necessity.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} EventPaned layers are fixed by default.
+ */
+ isFixed: true,
+
+ /**
+ * Property: pane
+ * {DOMElement} A reference to the element that controls the events.
+ */
+ pane: null,
+
+
+ /**
+ * Property: mapObject
+ * {Object} This is the object which will be used to load the 3rd party library
+ * in the case of the google layer, this will be of type GMap,
+ * in the case of the ve layer, this will be of type VEMap
+ */
+ mapObject: null,
+
+
+ /**
+ * Constructor: OpenLayers.Layer.EventPane
+ * Create a new event pane layer
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ if (this.pane == null) {
+ this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct this layer.
+ */
+ destroy: function() {
+ this.mapObject = null;
+ this.pane = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+
+ /**
+ * Method: setMap
+ * Set the map property for the layer. This is done through an accessor
+ * so that subclasses can override this and take special action once
+ * they have their map variable set.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+ this.pane.style.display = this.div.style.display;
+ this.pane.style.width="100%";
+ this.pane.style.height="100%";
+ if (OpenLayers.BROWSER_NAME == "msie") {
+ this.pane.style.background =
+ "url(" + OpenLayers.Util.getImageLocation("blank.gif") + ")";
+ }
+
+ if (this.isFixed) {
+ this.map.viewPortDiv.appendChild(this.pane);
+ } else {
+ this.map.layerContainerDiv.appendChild(this.pane);
+ }
+
+ // once our layer has been added to the map, we can load it
+ this.loadMapObject();
+
+ // if map didn't load, display warning
+ if (this.mapObject == null) {
+ this.loadWarningMessage();
+ }
+ },
+
+ /**
+ * APIMethod: removeMap
+ * On being removed from the map, we'll like to remove the invisible 'pane'
+ * div that we added to it on creation.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this.pane && this.pane.parentNode) {
+ this.pane.parentNode.removeChild(this.pane);
+ }
+ OpenLayers.Layer.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: loadWarningMessage
+ * If we can't load the map lib, then display an error message to the
+ * user and tell them where to go for help.
+ *
+ * This function sets up the layout for the warning message. Each 3rd
+ * party layer must implement its own getWarningHTML() function to
+ * provide the actual warning message.
+ */
+ loadWarningMessage:function() {
+
+ this.div.style.backgroundColor = "darkblue";
+
+ var viewSize = this.map.getSize();
+
+ var msgW = Math.min(viewSize.w, 300);
+ var msgH = Math.min(viewSize.h, 200);
+ var size = new OpenLayers.Size(msgW, msgH);
+
+ var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2);
+
+ var topLeft = centerPx.add(-size.w/2, -size.h/2);
+
+ var div = OpenLayers.Util.createDiv(this.name + "_warning",
+ topLeft,
+ size,
+ null,
+ null,
+ null,
+ "auto");
+
+ div.style.padding = "7px";
+ div.style.backgroundColor = "yellow";
+
+ div.innerHTML = this.getWarningHTML();
+ this.div.appendChild(div);
+ },
+
+ /**
+ * Method: getWarningHTML
+ * To be implemented by subclasses.
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ //should be implemented by subclasses
+ return "";
+ },
+
+ /**
+ * Method: display
+ * Set the display on the pane
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ this.pane.style.display = this.div.style.display;
+ },
+
+ /**
+ * Method: setZIndex
+ * Set the z-index order for the pane.
+ *
+ * Parameters:
+ * zIndex - {int}
+ */
+ setZIndex: function (zIndex) {
+ OpenLayers.Layer.prototype.setZIndex.apply(this, arguments);
+ this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector. To be implemented by subclasses.
+ *
+ * Parameters:
+ * dx - {Number} The x coord of the displacement vector.
+ * dy - {Number} The y coord of the displacement vector.
+ */
+ moveByPx: function(dx, dy) {
+ OpenLayers.Layer.prototype.moveByPx.apply(this, arguments);
+
+ if (this.dragPanMapObject) {
+ this.dragPanMapObject(dx, -dy);
+ } else {
+ this.moveTo(this.map.getCachedCenter());
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * Handle calls to move the layer.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ if (this.mapObject != null) {
+
+ var newCenter = this.map.getCenter();
+ var newZoom = this.map.getZoom();
+
+ if (newCenter != null) {
+
+ var moOldCenter = this.getMapObjectCenter();
+ var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter);
+
+ var moOldZoom = this.getMapObjectZoom();
+ var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom);
+
+ if (!(newCenter.equals(oldCenter)) || newZoom != oldZoom) {
+
+ if (!zoomChanged && oldCenter && this.dragPanMapObject &&
+ this.smoothDragPan) {
+ var oldPx = this.map.getViewPortPxFromLonLat(oldCenter);
+ var newPx = this.map.getViewPortPxFromLonLat(newCenter);
+ this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y);
+ } else {
+ var center = this.getMapObjectLonLatFromOLLonLat(newCenter);
+ var zoom = this.getMapObjectZoomFromOLZoom(newZoom);
+ this.setMapObjectCenter(center, zoom, dragging);
+ }
+ }
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /********************************************************/
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port OpenLayers.Pixel, translated into lon/lat by map lib
+ * If the map lib is not loaded or not centered, returns null
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if ( (this.mapObject != null) &&
+ (this.getMapObjectCenter() != null) ) {
+ var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx);
+ var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel);
+ lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat);
+ }
+ return lonlat;
+ },
+
+
+ /**
+ * Method: getViewPortPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * OpenLayers.LonLat, translated into view port pixels by map lib
+ * If map lib is not loaded or not centered, returns null
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var viewPortPx = null;
+ if ( (this.mapObject != null) &&
+ (this.getMapObjectCenter() != null) ) {
+
+ var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat);
+ var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat);
+
+ viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel);
+ }
+ return viewPortPx;
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate Map Object and */
+ /* OL formats for Pixel, LonLat */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat
+ //
+
+ /**
+ * Method: getOLLonLatFromMapObjectLonLat
+ * Get an OL style map location from a 3rd party style map location
+ *
+ * Parameters
+ * moLonLat - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in
+ * MapObject LonLat
+ * Returns null if null value is passed in
+ */
+ getOLLonLatFromMapObjectLonLat: function(moLonLat) {
+ var olLonLat = null;
+ if (moLonLat != null) {
+ var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+ var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+ olLonLat = new OpenLayers.LonLat(lon, lat);
+ }
+ return olLonLat;
+ },
+
+ /**
+ * Method: getMapObjectLonLatFromOLLonLat
+ * Get a 3rd party map location from an OL map location.
+ *
+ * Parameters:
+ * olLonLat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Object} A MapObject LonLat, translated from the passed in
+ * OpenLayers.LonLat
+ * Returns null if null value is passed in
+ */
+ getMapObjectLonLatFromOLLonLat: function(olLonLat) {
+ var moLatLng = null;
+ if (olLonLat != null) {
+ moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon,
+ olLonLat.lat);
+ }
+ return moLatLng;
+ },
+
+
+ //
+ // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel
+ //
+
+ /**
+ * Method: getOLPixelFromMapObjectPixel
+ * Get an OL pixel location from a 3rd party pixel location.
+ *
+ * Parameters:
+ * moPixel - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in
+ * MapObject Pixel
+ * Returns null if null value is passed in
+ */
+ getOLPixelFromMapObjectPixel: function(moPixel) {
+ var olPixel = null;
+ if (moPixel != null) {
+ var x = this.getXFromMapObjectPixel(moPixel);
+ var y = this.getYFromMapObjectPixel(moPixel);
+ olPixel = new OpenLayers.Pixel(x, y);
+ }
+ return olPixel;
+ },
+
+ /**
+ * Method: getMapObjectPixelFromOLPixel
+ * Get a 3rd party pixel location from an OL pixel location
+ *
+ * Parameters:
+ * olPixel - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {Object} A MapObject Pixel, translated from the passed in
+ * OpenLayers.Pixel
+ * Returns null if null value is passed in
+ */
+ getMapObjectPixelFromOLPixel: function(olPixel) {
+ var moPixel = null;
+ if (olPixel != null) {
+ moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y);
+ }
+ return moPixel;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.EventPane"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js b/misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js
new file mode 100644
index 0000000..f647238
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/FixedZoomLevels.js
@@ -0,0 +1,319 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.FixedZoomLevels
+ * Some Layers will already have established zoom levels (like google
+ * or ve). Instead of trying to determine them and populate a resolutions[]
+ * Array with those values, we will hijack the resolution functionality
+ * here.
+ *
+ * When you subclass FixedZoomLevels:
+ *
+ * The initResolutions() call gets nullified, meaning no resolutions[] array
+ * is set up. Which would be a big problem getResolution() in Layer, since
+ * it merely takes map.zoom and indexes into resolutions[]... but....
+ *
+ * The getResolution() call is also overridden. Instead of using the
+ * resolutions[] array, we simply calculate the current resolution based
+ * on the current extent and the current map size. But how will we be able
+ * to calculate the current extent without knowing the resolution...?
+ *
+ * The getExtent() function is also overridden. Instead of calculating extent
+ * based on the center point and the current resolution, we instead
+ * calculate the extent by getting the lonlats at the top-left and
+ * bottom-right by using the getLonLatFromViewPortPx() translation function,
+ * taken from the pixel locations (0,0) and the size of the map. But how
+ * will we be able to do lonlat-px translation without resolution....?
+ *
+ * The getZoomForResolution() method is overridden. Instead of indexing into
+ * the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in
+ * the desired resolution. With this extent, we then call getZoomForExtent()
+ *
+ *
+ * Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels,
+ * it is your responsibility to provide the following three functions:
+ *
+ * - getLonLatFromViewPortPx
+ * - getViewPortPxFromLonLat
+ * - getZoomForExtent
+ *
+ * ...those three functions should generally be provided by any reasonable
+ * API that you might be working from.
+ *
+ */
+OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions must all be implemented */
+ /* by all base layers */
+ /* */
+ /********************************************************/
+
+ /**
+ * Constructor: OpenLayers.Layer.FixedZoomLevels
+ * Create a new fixed zoom levels layer.
+ */
+ initialize: function() {
+ //this class is only just to add the following functions...
+ // nothing to actually do here... but it is probably a good
+ // idea to have layers that use these functions call this
+ // inititalize() anyways, in case at some point we decide we
+ // do want to put some functionality or state in here.
+ },
+
+ /**
+ * Method: initResolutions
+ * Populate the resolutions array
+ */
+ initResolutions: function() {
+
+ var props = ['minZoomLevel', 'maxZoomLevel', 'numZoomLevels'];
+
+ for(var i=0, len=props.length; i<len; i++) {
+ var property = props[i];
+ this[property] = (this.options[property] != null)
+ ? this.options[property]
+ : this.map[property];
+ }
+
+ if ( (this.minZoomLevel == null) ||
+ (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){
+ this.minZoomLevel = this.MIN_ZOOM_LEVEL;
+ }
+
+ //
+ // At this point, we know what the minimum desired zoom level is, and
+ // we must calculate the total number of zoom levels.
+ //
+ // Because we allow for the setting of either the 'numZoomLevels'
+ // or the 'maxZoomLevel' properties... on either the layer or the
+ // map, we have to define some rules to see which we take into
+ // account first in this calculation.
+ //
+ // The following is the precedence list for these properties:
+ //
+ // (1) numZoomLevels set on layer
+ // (2) maxZoomLevel set on layer
+ // (3) numZoomLevels set on map
+ // (4) maxZoomLevel set on map*
+ // (5) none of the above*
+ //
+ // *Note that options (4) and (5) are only possible if the user
+ // _explicitly_ sets the 'numZoomLevels' property on the map to
+ // null, since it is set by default to 16.
+ //
+
+ //
+ // Note to future: In 3.0, I think we should remove the default
+ // value of 16 for map.numZoomLevels. Rather, I think that value
+ // should be set as a default on the Layer.WMS class. If someone
+ // creates a 3rd party layer and does not specify any 'minZoomLevel',
+ // 'maxZoomLevel', or 'numZoomLevels', and has not explicitly
+ // specified any of those on the map object either.. then I think
+ // it is fair to say that s/he wants all the zoom levels available.
+ //
+ // By making map.numZoomLevels *null* by default, that will be the
+ // case. As it is, I don't feel comfortable changing that right now
+ // as it would be a glaring API change and actually would probably
+ // break many peoples' codes.
+ //
+
+ //the number of zoom levels we'd like to have.
+ var desiredZoomLevels;
+
+ //this is the maximum number of zoom levels the layer will allow,
+ // given the specified starting minimum zoom level.
+ var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;
+
+ if ( ((this.options.numZoomLevels == null) &&
+ (this.options.maxZoomLevel != null)) // (2)
+ ||
+ ((this.numZoomLevels == null) &&
+ (this.maxZoomLevel != null)) // (4)
+ ) {
+ //calculate based on specified maxZoomLevel (on layer or map)
+ desiredZoomLevels = this.maxZoomLevel - this.minZoomLevel + 1;
+ } else {
+ //calculate based on specified numZoomLevels (on layer or map)
+ // this covers cases (1) and (3)
+ desiredZoomLevels = this.numZoomLevels;
+ }
+
+ if (desiredZoomLevels != null) {
+ //Now that we know what we would *like* the number of zoom levels
+ // to be, based on layer or map options, we have to make sure that
+ // it does not conflict with the actual limit, as specified by
+ // the constants on the layer itself (and calculated into the
+ // 'limitZoomLevels' variable).
+ this.numZoomLevels = Math.min(desiredZoomLevels, limitZoomLevels);
+ } else {
+ // case (5) -- neither 'numZoomLevels' not 'maxZoomLevel' was
+ // set on either the layer or the map. So we just use the
+ // maximum limit as calculated by the layer's constants.
+ this.numZoomLevels = limitZoomLevels;
+ }
+
+ //now that the 'numZoomLevels' is appropriately, safely set,
+ // we go back and re-calculate the 'maxZoomLevel'.
+ this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1;
+
+ if (this.RESOLUTIONS != null) {
+ var resolutionsIndex = 0;
+ this.resolutions = [];
+ for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) {
+ this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i];
+ }
+ this.maxResolution = this.resolutions[0];
+ this.minResolution = this.resolutions[this.resolutions.length - 1];
+ }
+ },
+
+ /**
+ * APIMethod: getResolution
+ * Get the current map resolution
+ *
+ * Returns:
+ * {Float} Map units per Pixel
+ */
+ getResolution: function() {
+
+ if (this.resolutions != null) {
+ return OpenLayers.Layer.prototype.getResolution.apply(this, arguments);
+ } else {
+ var resolution = null;
+
+ var viewSize = this.map.getSize();
+ var extent = this.getExtent();
+
+ if ((viewSize != null) && (extent != null)) {
+ resolution = Math.max( extent.getWidth() / viewSize.w,
+ extent.getHeight() / viewSize.h );
+ }
+ return resolution;
+ }
+ },
+
+ /**
+ * APIMethod: getExtent
+ * Calculates using px-> lonlat translation functions on tl and br
+ * corners of viewport
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ */
+ getExtent: function () {
+ var size = this.map.getSize();
+ var tl = this.getLonLatFromViewPortPx({
+ x: 0, y: 0
+ });
+ var br = this.getLonLatFromViewPortPx({
+ x: size.w, y: size.h
+ });
+
+ if ((tl != null) && (br != null)) {
+ return new OpenLayers.Bounds(tl.lon, br.lat, br.lon, tl.lat);
+ } else {
+ return null;
+ }
+ },
+
+ /**
+ * Method: getZoomForResolution
+ * Get the zoom level for a given resolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution) {
+
+ if (this.resolutions != null) {
+ return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments);
+ } else {
+ var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []);
+ return this.getZoomForExtent(extent);
+ }
+ },
+
+
+
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate GMaps and OL */
+ /* formats for Pixel, LonLat, Bounds, and Zoom */
+ /* */
+ /********************************************************/
+
+
+ //
+ // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+ //
+
+ /**
+ * Method: getOLZoomFromMapObjectZoom
+ * Get the OL zoom index from the map object zoom level
+ *
+ * Parameters:
+ * moZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} An OpenLayers Zoom level, translated from the passed in zoom
+ * Returns null if null value is passed in
+ */
+ getOLZoomFromMapObjectZoom: function(moZoom) {
+ var zoom = null;
+ if (moZoom != null) {
+ zoom = moZoom - this.minZoomLevel;
+ if (this.map.baseLayer !== this) {
+ zoom = this.map.baseLayer.getZoomForResolution(
+ this.getResolutionForZoom(zoom)
+ );
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * Method: getMapObjectZoomFromOLZoom
+ * Get the map object zoom level from the OL zoom level
+ *
+ * Parameters:
+ * olZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} A MapObject level, translated from the passed in olZoom
+ * Returns null if null value is passed in
+ */
+ getMapObjectZoomFromOLZoom: function(olZoom) {
+ var zoom = null;
+ if (olZoom != null) {
+ zoom = olZoom + this.minZoomLevel;
+ if (this.map.baseLayer !== this) {
+ zoom = this.getZoomForResolution(
+ this.map.baseLayer.getResolutionForZoom(zoom)
+ );
+ }
+ }
+ return zoom;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.FixedZoomLevels"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js b/misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js
new file mode 100644
index 0000000..564d071
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/GeoRSS.js
@@ -0,0 +1,265 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.GeoRSS
+ * Add GeoRSS Point features to your map.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.GeoRSS = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+ /**
+ * Property: location
+ * {String} store url of text file
+ */
+ location: null,
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature>)}
+ */
+ features: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Hash of options which should be passed to the format when it is
+ * created. Must be passed in the constructor.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: selectedFeature
+ * {<OpenLayers.Feature>}
+ */
+ selectedFeature: null,
+
+ /**
+ * APIProperty: icon
+ * {<OpenLayers.Icon>}. This determines the Icon to be used on the map
+ * for this GeoRSS layer.
+ */
+ icon: null,
+
+ /**
+ * APIProperty: popupSize
+ * {<OpenLayers.Size>} This determines the size of GeoRSS popups. If
+ * not provided, defaults to 250px by 120px.
+ */
+ popupSize: null,
+
+ /**
+ * APIProperty: useFeedTitle
+ * {Boolean} Set layer.name to the first <title> element in the feed. Default is true.
+ */
+ useFeedTitle: true,
+
+ /**
+ * Constructor: OpenLayers.Layer.GeoRSS
+ * Create a GeoRSS Layer.
+ *
+ * Parameters:
+ * name - {String}
+ * location - {String}
+ * options - {Object}
+ */
+ initialize: function(name, location, options) {
+ OpenLayers.Layer.Markers.prototype.initialize.apply(this, [name, options]);
+ this.location = location;
+ this.features = [];
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ // Warning: Layer.Markers.destroy() must be called prior to calling
+ // clearFeatures() here, otherwise we leak memory. Indeed, if
+ // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+ // able to remove the marker image elements from the layer's div since
+ // the markers will have been destroyed by clearFeatures().
+ OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+ this.clearFeatures();
+ this.features = null;
+ },
+
+ /**
+ * Method: loadRSS
+ * Start the load of the RSS data. Don't do this when we first add the layer,
+ * since we may not be visible at any point, and it would therefore be a waste.
+ */
+ loadRSS: function() {
+ if (!this.loaded) {
+ this.events.triggerEvent("loadstart");
+ OpenLayers.Request.GET({
+ url: this.location,
+ success: this.parseData,
+ scope: this
+ });
+ this.loaded = true;
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * If layer is visible and RSS has not been loaded, load RSS.
+ *
+ * Parameters:
+ * bounds - {Object}
+ * zoomChanged - {Object}
+ * minor - {Object}
+ */
+ moveTo:function(bounds, zoomChanged, minor) {
+ OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+ if(this.visibility && !this.loaded){
+ this.loadRSS();
+ }
+ },
+
+ /**
+ * Method: parseData
+ * Parse the data returned from the Events call.
+ *
+ * Parameters:
+ * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>}
+ */
+ parseData: function(ajaxRequest) {
+ var doc = ajaxRequest.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText);
+ }
+
+ if (this.useFeedTitle) {
+ var name = null;
+ try {
+ name = doc.getElementsByTagNameNS('*', 'title')[0].firstChild.nodeValue;
+ }
+ catch (e) {
+ name = doc.getElementsByTagName('title')[0].firstChild.nodeValue;
+ }
+ if (name) {
+ this.setName(name);
+ }
+ }
+
+ var options = {};
+
+ OpenLayers.Util.extend(options, this.formatOptions);
+
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ var format = new OpenLayers.Format.GeoRSS(options);
+ var features = format.read(doc);
+
+ for (var i=0, len=features.length; i<len; i++) {
+ var data = {};
+ var feature = features[i];
+
+ // we don't support features with no geometry in the GeoRSS
+ // layer at this time.
+ if (!feature.geometry) {
+ continue;
+ }
+
+ var title = feature.attributes.title ?
+ feature.attributes.title : "Untitled";
+
+ var description = feature.attributes.description ?
+ feature.attributes.description : "No description.";
+
+ var link = feature.attributes.link ? feature.attributes.link : "";
+
+ var location = feature.geometry.getBounds().getCenterLonLat();
+
+
+ data.icon = this.icon == null ?
+ OpenLayers.Marker.defaultIcon() :
+ this.icon.clone();
+
+ data.popupSize = this.popupSize ?
+ this.popupSize.clone() :
+ new OpenLayers.Size(250, 120);
+
+ if (title || description) {
+ // we have supplemental data, store them.
+ data.title = title;
+ data.description = description;
+
+ var contentHTML = '<div class="olLayerGeoRSSClose">[x]</div>';
+ contentHTML += '<div class="olLayerGeoRSSTitle">';
+ if (link) {
+ contentHTML += '<a class="link" href="'+link+'" target="_blank">';
+ }
+ contentHTML += title;
+ if (link) {
+ contentHTML += '</a>';
+ }
+ contentHTML += '</div>';
+ contentHTML += '<div style="" class="olLayerGeoRSSDescription">';
+ contentHTML += description;
+ contentHTML += '</div>';
+ data['popupContentHTML'] = contentHTML;
+ }
+ var feature = new OpenLayers.Feature(this, location, data);
+ this.features.push(feature);
+ var marker = feature.createMarker();
+ marker.events.register('click', feature, this.markerClick);
+ this.addMarker(marker);
+ }
+ this.events.triggerEvent("loadend");
+ },
+
+ /**
+ * Method: markerClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ markerClick: function(evt) {
+ var sameMarkerClicked = (this == this.layer.selectedFeature);
+ this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+ for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+ this.layer.map.removePopup(this.layer.map.popups[i]);
+ }
+ if (!sameMarkerClicked) {
+ var popup = this.createPopup();
+ OpenLayers.Event.observe(popup.div, "click",
+ OpenLayers.Function.bind(function() {
+ for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+ this.layer.map.removePopup(this.layer.map.popups[i]);
+ }
+ }, this)
+ );
+ this.layer.map.addPopup(popup);
+ }
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: clearFeatures
+ * Destroy all features in this layer.
+ */
+ clearFeatures: function() {
+ if (this.features != null) {
+ while(this.features.length > 0) {
+ var feature = this.features[0];
+ OpenLayers.Util.removeItem(this.features, feature);
+ feature.destroy();
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.GeoRSS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Google.js b/misc/openlayers/lib/OpenLayers/Layer/Google.js
new file mode 100644
index 0000000..6e85fba
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Google.js
@@ -0,0 +1,809 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/SphericalMercator.js
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Google
+ *
+ * Provides a wrapper for Google's Maps API
+ * Normally the Terms of Use for this API do not allow wrapping, but Google
+ * have provided written consent to OpenLayers for this - see email in
+ * http://osgeo-org.1560.n6.nabble.com/Google-Maps-API-Terms-of-Use-changes-tp4910013p4911981.html
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.SphericalMercator>
+ * - <OpenLayers.Layer.EventPane>
+ * - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Google = OpenLayers.Class(
+ OpenLayers.Layer.EventPane,
+ OpenLayers.Layer.FixedZoomLevels, {
+
+ /**
+ * Constant: MIN_ZOOM_LEVEL
+ * {Integer} 0
+ */
+ MIN_ZOOM_LEVEL: 0,
+
+ /**
+ * Constant: MAX_ZOOM_LEVEL
+ * {Integer} 21
+ */
+ MAX_ZOOM_LEVEL: 21,
+
+ /**
+ * Constant: RESOLUTIONS
+ * {Array(Float)} Hardcode these resolutions so that they are more closely
+ * tied with the standard wms projection
+ */
+ RESOLUTIONS: [
+ 1.40625,
+ 0.703125,
+ 0.3515625,
+ 0.17578125,
+ 0.087890625,
+ 0.0439453125,
+ 0.02197265625,
+ 0.010986328125,
+ 0.0054931640625,
+ 0.00274658203125,
+ 0.001373291015625,
+ 0.0006866455078125,
+ 0.00034332275390625,
+ 0.000171661376953125,
+ 0.0000858306884765625,
+ 0.00004291534423828125,
+ 0.00002145767211914062,
+ 0.00001072883605957031,
+ 0.00000536441802978515,
+ 0.00000268220901489257,
+ 0.0000013411045074462891,
+ 0.00000067055225372314453
+ ],
+
+ /**
+ * APIProperty: type
+ * {GMapType}
+ */
+ type: null,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Allow user to pan forever east/west. Default is true.
+ * Setting this to false only restricts panning if
+ * <sphericalMercator> is true.
+ */
+ wrapDateLine: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * {Boolean} Should the map act as a mercator-projected map? This will
+ * cause all interactions with the map to be in the actual map
+ * projection, which allows support for vector drawing, overlaying
+ * other maps, etc.
+ */
+ sphericalMercator: false,
+
+ /**
+ * Property: version
+ * {Number} The version of the Google Maps API
+ */
+ version: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Google
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * options - {Object} An optional object whose properties will be set
+ * on the layer.
+ */
+ initialize: function(name, options) {
+ options = options || {};
+ if(!options.version) {
+ options.version = typeof GMap2 === "function" ? "2" : "3";
+ }
+ var mixin = OpenLayers.Layer.Google["v" +
+ options.version.replace(/\./g, "_")];
+ if (mixin) {
+ OpenLayers.Util.applyDefaults(options, mixin);
+ } else {
+ throw "Unsupported Google Maps API version: " + options.version;
+ }
+
+ OpenLayers.Util.applyDefaults(options, mixin.DEFAULTS);
+ if (options.maxExtent) {
+ options.maxExtent = options.maxExtent.clone();
+ }
+
+ OpenLayers.Layer.EventPane.prototype.initialize.apply(this,
+ [name, options]);
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+ [name, options]);
+
+ if (this.sphericalMercator) {
+ OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+ this.initMercatorParameters();
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Google>} An exact clone of this layer
+ */
+ clone: function() {
+ /**
+ * This method isn't intended to be called by a subclass and it
+ * doesn't call the same method on the superclass. We don't call
+ * the super's clone because we don't want properties that are set
+ * on this layer after initialize (i.e. this.mapObject etc.).
+ */
+ return new OpenLayers.Layer.Google(
+ this.name, this.getOptions()
+ );
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show & redraw
+ * accordingly. Fire event unless otherwise specified
+ *
+ * Note that visibility is no longer simply whether or not the layer's
+ * style.display is set to "block". Now we store a 'visibility' state
+ * property on the layer class, this allows us to remember whether or
+ * not we *desire* for a layer to be visible. In the case where the
+ * map's resolution is out of the layer's range, this desire may be
+ * subverted.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the layer (if in range)
+ */
+ setVisibility: function(visible) {
+ // sharing a map container, opacity has to be set per layer
+ var opacity = this.opacity == null ? 1 : this.opacity;
+ OpenLayers.Layer.EventPane.prototype.setVisibility.apply(this, arguments);
+ this.setOpacity(opacity);
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * visible - {Boolean}
+ */
+ display: function(visible) {
+ if (!this._dragging) {
+ this.setGMapVisibility(visible);
+ }
+ OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ this._dragging = dragging;
+ OpenLayers.Layer.EventPane.prototype.moveTo.apply(this, arguments);
+ delete this._dragging;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for the entire layer (all images)
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity !== this.opacity) {
+ if (this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "opacity"
+ });
+ }
+ this.opacity = opacity;
+ }
+ // Though this layer's opacity may not change, we're sharing a container
+ // and need to update the opacity for the entire container.
+ if (this.getVisibility()) {
+ var container = this.getMapContainer();
+ OpenLayers.Util.modifyDOMElement(
+ container, null, null, null, null, null, null, opacity
+ );
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up this layer.
+ */
+ destroy: function() {
+ /**
+ * We have to override this method because the event pane destroy
+ * deletes the mapObject reference before removing this layer from
+ * the map.
+ */
+ if (this.map) {
+ this.setGMapVisibility(false);
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache && cache.count <= 1) {
+ this.removeGMapElements();
+ }
+ }
+ OpenLayers.Layer.EventPane.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: removeGMapElements
+ * Remove all elements added to the dom. This should only be called if
+ * this is the last of the Google layers for the given map.
+ */
+ removeGMapElements: function() {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // remove shared elements from dom
+ var container = this.mapObject && this.getMapContainer();
+ if (container && container.parentNode) {
+ container.parentNode.removeChild(container);
+ }
+ var termsOfUse = cache.termsOfUse;
+ if (termsOfUse && termsOfUse.parentNode) {
+ termsOfUse.parentNode.removeChild(termsOfUse);
+ }
+ var poweredBy = cache.poweredBy;
+ if (poweredBy && poweredBy.parentNode) {
+ poweredBy.parentNode.removeChild(poweredBy);
+ }
+ if (this.mapObject && window.google && google.maps &&
+ google.maps.event && google.maps.event.clearListeners) {
+ google.maps.event.clearListeners(this.mapObject, 'tilesloaded');
+ }
+ }
+ },
+
+ /**
+ * APIMethod: removeMap
+ * On being removed from the map, also remove termsOfUse and poweredBy divs
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ // hide layer before removing
+ if (this.visibility && this.mapObject) {
+ this.setGMapVisibility(false);
+ }
+ // check to see if last Google layer in this map
+ var cache = OpenLayers.Layer.Google.cache[map.id];
+ if (cache) {
+ if (cache.count <= 1) {
+ this.removeGMapElements();
+ delete OpenLayers.Layer.Google.cache[map.id];
+ } else {
+ // decrement the layer count
+ --cache.count;
+ }
+ }
+ // remove references to gmap elements
+ delete this.termsOfUse;
+ delete this.poweredBy;
+ delete this.mapObject;
+ delete this.dragObject;
+ OpenLayers.Layer.EventPane.prototype.removeMap.apply(this, arguments);
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getOLBoundsFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the
+ * passed-in MapObject Bounds.
+ * Returns null if null value is passed in.
+ */
+ getOLBoundsFromMapObjectBounds: function(moBounds) {
+ var olBounds = null;
+ if (moBounds != null) {
+ var sw = moBounds.getSouthWest();
+ var ne = moBounds.getNorthEast();
+ if (this.sphericalMercator) {
+ sw = this.forwardMercator(sw.lng(), sw.lat());
+ ne = this.forwardMercator(ne.lng(), ne.lat());
+ } else {
+ sw = new OpenLayers.LonLat(sw.lng(), sw.lat());
+ ne = new OpenLayers.LonLat(ne.lng(), ne.lat());
+ }
+ olBounds = new OpenLayers.Bounds(sw.lon,
+ sw.lat,
+ ne.lon,
+ ne.lat );
+ }
+ return olBounds;
+ },
+
+ /**
+ * APIMethod: getWarningHTML
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ return OpenLayers.i18n("googleWarning");
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: getMapObjectCenter
+ *
+ * Returns:
+ * {Object} The mapObject's current center in Map Object format
+ */
+ getMapObjectCenter: function() {
+ return this.mapObject.getCenter();
+ },
+
+ /**
+ * APIMethod: getMapObjectZoom
+ *
+ * Returns:
+ * {Integer} The mapObject's current zoom, in Map Object format
+ */
+ getMapObjectZoom: function() {
+ return this.mapObject.getZoom();
+ },
+
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getLongitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Longitude of the given MapObject LonLat
+ */
+ getLongitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon :
+ moLonLat.lng();
+ },
+
+ /**
+ * APIMethod: getLatitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Latitude of the given MapObject LonLat
+ */
+ getLatitudeFromMapObjectLonLat: function(moLonLat) {
+ var lat = this.sphericalMercator ?
+ this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat :
+ moLonLat.lat();
+ return lat;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getXFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} X value of the MapObject Pixel
+ */
+ getXFromMapObjectPixel: function(moPixel) {
+ return moPixel.x;
+ },
+
+ /**
+ * APIMethod: getYFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} Y value of the MapObject Pixel
+ */
+ getYFromMapObjectPixel: function(moPixel) {
+ return moPixel.y;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Google"
+});
+
+/**
+ * Property: OpenLayers.Layer.Google.cache
+ * {Object} Cache for elements that should only be created once per map.
+ */
+OpenLayers.Layer.Google.cache = {};
+
+
+/**
+ * Constant: OpenLayers.Layer.Google.v2
+ *
+ * Mixin providing functionality specific to the Google Maps API v2.
+ *
+ * This API has been deprecated by Google.
+ * Developers are encouraged to migrate to v3 of the API; support for this
+ * is provided by <OpenLayers.Layer.Google.v3>
+ */
+OpenLayers.Layer.Google.v2 = {
+
+ /**
+ * Property: termsOfUse
+ * {DOMElement} Div for Google's copyright and terms of use link
+ */
+ termsOfUse: null,
+
+ /**
+ * Property: poweredBy
+ * {DOMElement} Div for Google's powered by logo and link
+ */
+ poweredBy: null,
+
+ /**
+ * Property: dragObject
+ * {GDraggableObject} Since 2.93, Google has exposed the ability to get
+ * the maps GDraggableObject. We can now use this for smooth panning
+ */
+ dragObject: null,
+
+ /**
+ * Method: loadMapObject
+ * Load the GMap and register appropriate event listeners. If we can't
+ * load GMap2, then display a warning message.
+ */
+ loadMapObject:function() {
+ if (!this.type) {
+ this.type = G_NORMAL_MAP;
+ }
+ var mapObject, termsOfUse, poweredBy;
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // there are already Google layers added to this map
+ mapObject = cache.mapObject;
+ termsOfUse = cache.termsOfUse;
+ poweredBy = cache.poweredBy;
+ // increment the layer count
+ ++cache.count;
+ } else {
+ // this is the first Google layer for this map
+
+ var container = this.map.viewPortDiv;
+ var div = document.createElement("div");
+ div.id = this.map.id + "_GMap2Container";
+ div.style.position = "absolute";
+ div.style.width = "100%";
+ div.style.height = "100%";
+ container.appendChild(div);
+
+ // create GMap and shuffle elements
+ try {
+ mapObject = new GMap2(div);
+
+ // move the ToS and branding stuff up to the container div
+ termsOfUse = div.lastChild;
+ container.appendChild(termsOfUse);
+ termsOfUse.style.zIndex = "1100";
+ termsOfUse.style.right = "";
+ termsOfUse.style.bottom = "";
+ termsOfUse.className = "olLayerGoogleCopyright";
+
+ poweredBy = div.lastChild;
+ container.appendChild(poweredBy);
+ poweredBy.style.zIndex = "1100";
+ poweredBy.style.right = "";
+ poweredBy.style.bottom = "";
+ poweredBy.className = "olLayerGooglePoweredBy gmnoprint";
+
+ } catch (e) {
+ throw(e);
+ }
+ // cache elements for use by any other google layers added to
+ // this same map
+ OpenLayers.Layer.Google.cache[this.map.id] = {
+ mapObject: mapObject,
+ termsOfUse: termsOfUse,
+ poweredBy: poweredBy,
+ count: 1
+ };
+ }
+
+ this.mapObject = mapObject;
+ this.termsOfUse = termsOfUse;
+ this.poweredBy = poweredBy;
+
+ // ensure this layer type is one of the mapObject types
+ if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),
+ this.type) === -1) {
+ this.mapObject.addMapType(this.type);
+ }
+
+ //since v 2.93 getDragObject is now available.
+ if(typeof mapObject.getDragObject == "function") {
+ this.dragObject = mapObject.getDragObject();
+ } else {
+ this.dragPanMapObject = null;
+ }
+
+ if(this.isBaseLayer === false) {
+ this.setGMapVisibility(this.div.style.display !== "none");
+ }
+
+ },
+
+ /**
+ * APIMethod: onMapResize
+ */
+ onMapResize: function() {
+ // workaround for resizing of invisible or not yet fully loaded layers
+ // where GMap2.checkResize() does not work. We need to load the GMap
+ // for the old div size, then checkResize(), and then call
+ // layer.moveTo() to trigger GMap.setCenter() (which will finish
+ // the GMap initialization).
+ if(this.visibility && this.mapObject.isLoaded()) {
+ this.mapObject.checkResize();
+ } else {
+ if(!this._resized) {
+ var layer = this;
+ var handle = GEvent.addListener(this.mapObject, "load", function() {
+ GEvent.removeListener(handle);
+ delete layer._resized;
+ layer.mapObject.checkResize();
+ layer.moveTo(layer.map.getCenter(), layer.map.getZoom());
+ });
+ }
+ this._resized = true;
+ }
+ },
+
+ /**
+ * Method: setGMapVisibility
+ * Display the GMap container and associated elements.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the GMap elements.
+ */
+ setGMapVisibility: function(visible) {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ var container = this.mapObject.getContainer();
+ if (visible === true) {
+ this.mapObject.setMapType(this.type);
+ container.style.display = "";
+ this.termsOfUse.style.left = "";
+ this.termsOfUse.style.display = "";
+ this.poweredBy.style.display = "";
+ cache.displayed = this.id;
+ } else {
+ if (cache.displayed === this.id) {
+ delete cache.displayed;
+ }
+ if (!cache.displayed) {
+ container.style.display = "none";
+ this.termsOfUse.style.display = "none";
+ // move ToU far to the left in addition to setting display
+ // to "none", because at the end of the GMap2 load
+ // sequence, display: none will be unset and ToU would be
+ // visible after loading a map with a google layer that is
+ // initially hidden.
+ this.termsOfUse.style.left = "-9999px";
+ this.poweredBy.style.display = "none";
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getMapContainer
+ *
+ * Returns:
+ * {DOMElement} the GMap container's div
+ */
+ getMapContainer: function() {
+ return this.mapObject.getContainer();
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getMapObjectBoundsFromOLBounds
+ *
+ * Parameters:
+ * olBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} A MapObject Bounds, translated from olBounds
+ * Returns null if null value is passed in
+ */
+ getMapObjectBoundsFromOLBounds: function(olBounds) {
+ var moBounds = null;
+ if (olBounds != null) {
+ var sw = this.sphericalMercator ?
+ this.inverseMercator(olBounds.bottom, olBounds.left) :
+ new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+ var ne = this.sphericalMercator ?
+ this.inverseMercator(olBounds.top, olBounds.right) :
+ new OpenLayers.LonLat(olBounds.top, olBounds.right);
+ moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon),
+ new GLatLng(ne.lat, ne.lon));
+ }
+ return moBounds;
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ this.mapObject.setCenter(center, zoom);
+ },
+
+ /**
+ * APIMethod: dragPanMapObject
+ *
+ * Parameters:
+ * dX - {Integer}
+ * dY - {Integer}
+ */
+ dragPanMapObject: function(dX, dY) {
+ this.dragObject.moveBy(new GSize(-dX, dY));
+ },
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ return this.mapObject.fromContainerPixelToLatLng(moPixel);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ return this.mapObject.fromLatLngToContainerPixel(moLonLat);
+ },
+
+
+ // Bounds
+
+ /**
+ * APIMethod: getMapObjectZoomFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object} MapObject Bounds format
+ *
+ * Returns:
+ * {Object} MapObject Zoom for specified MapObject Bounds
+ */
+ getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+ return this.mapObject.getBoundsZoomLevel(moBounds);
+ },
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var gLatLng;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ gLatLng = new GLatLng(lonlat.lat, lonlat.lon);
+ } else {
+ gLatLng = new GLatLng(lat, lon);
+ }
+ return gLatLng;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new GPoint(x, y);
+ }
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Google/v3.js b/misc/openlayers/lib/OpenLayers/Layer/Google/v3.js
new file mode 100644
index 0000000..067b7a0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Google/v3.js
@@ -0,0 +1,351 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Google.js
+ */
+
+/**
+ * Constant: OpenLayers.Layer.Google.v3
+ *
+ * Mixin providing functionality specific to the Google Maps API v3.
+ *
+ * To use this layer, you must include the GMaps v3 API in your html.
+ *
+ * Note that this layer configures the google.maps.map object with the
+ * "disableDefaultUI" option set to true. Using UI controls that the Google
+ * Maps API provides is not supported by the OpenLayers API.
+ */
+OpenLayers.Layer.Google.v3 = {
+
+ /**
+ * Constant: DEFAULTS
+ * {Object} It is not recommended to change the properties set here. Note
+ * that Google.v3 layers only work when sphericalMercator is set to true.
+ *
+ * (code)
+ * {
+ * sphericalMercator: true,
+ * projection: "EPSG:900913"
+ * }
+ * (end)
+ */
+ DEFAULTS: {
+ sphericalMercator: true,
+ projection: "EPSG:900913"
+ },
+
+ /**
+ * APIProperty: animationEnabled
+ * {Boolean} If set to true, the transition between zoom levels will be
+ * animated (if supported by the GMaps API for the device used). Set to
+ * false to match the zooming experience of other layer types. Default
+ * is true. Note that the GMaps API does not give us control over zoom
+ * animation, so if set to false, when zooming, this will make the
+ * layer temporarily invisible, wait until GMaps reports the map being
+ * idle, and make it visible again. The result will be a blank layer
+ * for a few moments while zooming.
+ */
+ animationEnabled: true,
+
+ /**
+ * Method: loadMapObject
+ * Load the GMap and register appropriate event listeners.
+ */
+ loadMapObject: function() {
+ if (!this.type) {
+ this.type = google.maps.MapTypeId.ROADMAP;
+ }
+ var mapObject;
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ if (cache) {
+ // there are already Google layers added to this map
+ mapObject = cache.mapObject;
+ // increment the layer count
+ ++cache.count;
+ } else {
+ // this is the first Google layer for this map
+ // create GMap
+ var center = this.map.getCenter();
+ var container = document.createElement('div');
+ container.className = "olForeignContainer";
+ container.style.width = '100%';
+ container.style.height = '100%';
+ mapObject = new google.maps.Map(container, {
+ center: center ?
+ new google.maps.LatLng(center.lat, center.lon) :
+ new google.maps.LatLng(0, 0),
+ zoom: this.map.getZoom() || 0,
+ mapTypeId: this.type,
+ disableDefaultUI: true,
+ keyboardShortcuts: false,
+ draggable: false,
+ disableDoubleClickZoom: true,
+ scrollwheel: false,
+ streetViewControl: false
+ });
+ var googleControl = document.createElement('div');
+ googleControl.style.width = '100%';
+ googleControl.style.height = '100%';
+ mapObject.controls[google.maps.ControlPosition.TOP_LEFT].push(googleControl);
+
+ // cache elements for use by any other google layers added to
+ // this same map
+ cache = {
+ googleControl: googleControl,
+ mapObject: mapObject,
+ count: 1
+ };
+ OpenLayers.Layer.Google.cache[this.map.id] = cache;
+ }
+ this.mapObject = mapObject;
+ this.setGMapVisibility(this.visibility);
+ },
+
+ /**
+ * APIMethod: onMapResize
+ */
+ onMapResize: function() {
+ if (this.visibility) {
+ google.maps.event.trigger(this.mapObject, "resize");
+ }
+ },
+
+ /**
+ * Method: setGMapVisibility
+ * Display the GMap container and associated elements.
+ *
+ * Parameters:
+ * visible - {Boolean} Display the GMap elements.
+ */
+ setGMapVisibility: function(visible) {
+ var cache = OpenLayers.Layer.Google.cache[this.map.id];
+ var map = this.map;
+ if (cache) {
+ var type = this.type;
+ var layers = map.layers;
+ var layer;
+ for (var i=layers.length-1; i>=0; --i) {
+ layer = layers[i];
+ if (layer instanceof OpenLayers.Layer.Google &&
+ layer.visibility === true && layer.inRange === true) {
+ type = layer.type;
+ visible = true;
+ break;
+ }
+ }
+ var container = this.mapObject.getDiv();
+ if (visible === true) {
+ if (container.parentNode !== map.div) {
+ if (!cache.rendered) {
+ var me = this;
+ google.maps.event.addListenerOnce(this.mapObject, 'tilesloaded', function() {
+ cache.rendered = true;
+ me.setGMapVisibility(me.getVisibility());
+ me.moveTo(me.map.getCenter());
+ });
+ } else {
+ map.div.appendChild(container);
+ cache.googleControl.appendChild(map.viewPortDiv);
+ google.maps.event.trigger(this.mapObject, 'resize');
+ }
+ }
+ this.mapObject.setMapTypeId(type);
+ } else if (cache.googleControl.hasChildNodes()) {
+ map.div.appendChild(map.viewPortDiv);
+ map.div.removeChild(container);
+ }
+ }
+ },
+
+ /**
+ * Method: getMapContainer
+ *
+ * Returns:
+ * {DOMElement} the GMap container's div
+ */
+ getMapContainer: function() {
+ return this.mapObject.getDiv();
+ },
+
+ //
+ // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+ //
+
+ /**
+ * APIMethod: getMapObjectBoundsFromOLBounds
+ *
+ * Parameters:
+ * olBounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} A MapObject Bounds, translated from olBounds
+ * Returns null if null value is passed in
+ */
+ getMapObjectBoundsFromOLBounds: function(olBounds) {
+ var moBounds = null;
+ if (olBounds != null) {
+ var sw = this.sphericalMercator ?
+ this.inverseMercator(olBounds.bottom, olBounds.left) :
+ new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+ var ne = this.sphericalMercator ?
+ this.inverseMercator(olBounds.top, olBounds.right) :
+ new OpenLayers.LonLat(olBounds.top, olBounds.right);
+ moBounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(sw.lat, sw.lon),
+ new google.maps.LatLng(ne.lat, ne.lon)
+ );
+ }
+ return moBounds;
+ },
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ var size = this.map.getSize();
+ var lon = this.getLongitudeFromMapObjectLonLat(this.mapObject.center);
+ var lat = this.getLatitudeFromMapObjectLonLat(this.mapObject.center);
+ var res = this.map.getResolution();
+
+ var delta_x = moPixel.x - (size.w / 2);
+ var delta_y = moPixel.y - (size.h / 2);
+
+ var lonlat = new OpenLayers.LonLat(
+ lon + delta_x * res,
+ lat - delta_y * res
+ );
+
+ if (this.wrapDateLine) {
+ lonlat = lonlat.wrapDateLine(this.maxExtent);
+ }
+ return this.getMapObjectLonLatFromLonLat(lonlat.lon, lonlat.lat);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+ var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+ var res = this.map.getResolution();
+ var extent = this.map.getExtent();
+ return this.getMapObjectPixelFromXY((1/res * (lon - extent.left)),
+ (1/res * (extent.top - lat)));
+ },
+
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ if (this.animationEnabled === false && zoom != this.mapObject.zoom) {
+ var mapContainer = this.getMapContainer();
+ google.maps.event.addListenerOnce(
+ this.mapObject,
+ "idle",
+ function() {
+ mapContainer.style.visibility = "";
+ }
+ );
+ mapContainer.style.visibility = "hidden";
+ }
+ this.mapObject.setOptions({
+ center: center,
+ zoom: zoom
+ });
+ },
+
+
+ // Bounds
+
+ /**
+ * APIMethod: getMapObjectZoomFromMapObjectBounds
+ *
+ * Parameters:
+ * moBounds - {Object} MapObject Bounds format
+ *
+ * Returns:
+ * {Object} MapObject Zoom for specified MapObject Bounds
+ */
+ getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+ return this.mapObject.getBoundsZoomLevel(moBounds);
+ },
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var gLatLng;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ gLatLng = new google.maps.LatLng(lonlat.lat, lonlat.lon);
+ } else {
+ gLatLng = new google.maps.LatLng(lat, lon);
+ }
+ return gLatLng;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new google.maps.Point(x, y);
+ }
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Grid.js b/misc/openlayers/lib/OpenLayers/Layer/Grid.js
new file mode 100644
index 0000000..a94075f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Grid.js
@@ -0,0 +1,1343 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/HTTPRequest.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Grid
+ * Base class for layers that use a lattice of tiles. Create a new grid
+ * layer with the <OpenLayers.Layer.Grid> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.HTTPRequest>
+ */
+OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>}
+ */
+ tileSize: null,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} If the <tileOrigin> property is not provided, the tile origin
+ * will be derived from the layer's <maxExtent>. The corner of the
+ * <maxExtent> used is determined by this property. Acceptable values
+ * are "tl" (top left), "tr" (top right), "bl" (bottom left), and "br"
+ * (bottom right). Default is "bl".
+ */
+ tileOriginCorner: "bl",
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
+ * If provided, requests for tiles at all resolutions will be aligned
+ * with this location (no tiles shall overlap this location). If
+ * not provided, the grid of tiles will be aligned with the layer's
+ * <maxExtent>. Default is ``null``.
+ */
+ tileOrigin: null,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer, if supported by the tile class.
+ */
+ tileOptions: null,
+
+ /**
+ * APIProperty: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is OpenLayers.Tile.Image.
+ */
+ tileClass: OpenLayers.Tile.Image,
+
+ /**
+ * Property: grid
+ * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is
+ * an array of tiles.
+ */
+ grid: null,
+
+ /**
+ * APIProperty: singleTile
+ * {Boolean} Moves the layer into single-tile mode, meaning that one tile
+ * will be loaded. The tile's size will be determined by the 'ratio'
+ * property. When the tile is dragged such that it does not cover the
+ * entire viewport, it is reloaded.
+ */
+ singleTile: false,
+
+ /** APIProperty: ratio
+ * {Float} Used only when in single-tile mode, this specifies the
+ * ratio of the size of the single tile to the size of the map.
+ * Default value is 1.5.
+ */
+ ratio: 1.5,
+
+ /**
+ * APIProperty: buffer
+ * {Integer} Used only when in gridded mode, this specifies the number of
+ * extra rows and colums of tiles on each side which will
+ * surround the minimum grid tiles to cover the map.
+ * For very slow loading layers, a larger value may increase
+ * performance somewhat when dragging, but will increase bandwidth
+ * use significantly.
+ */
+ buffer: 0,
+
+ /**
+ * APIProperty: transitionEffect
+ * {String} The transition effect to use when the map is zoomed.
+ * Two posible values:
+ *
+ * "resize" - Existing tiles are resized on zoom to provide a visual
+ * effect of the zoom having taken place immediately. As the
+ * new tiles become available, they are drawn on top of the
+ * resized tiles (this is the default setting).
+ * "map-resize" - Existing tiles are resized on zoom and placed below the
+ * base layer. New tiles for the base layer will cover existing tiles.
+ * This setting is recommended when having an overlay duplicated during
+ * the transition is undesirable (e.g. street labels or big transparent
+ * fills).
+ * null - No transition effect.
+ *
+ * Using "resize" on non-opaque layers can cause undesired visual
+ * effects. Set transitionEffect to null in this case.
+ */
+ transitionEffect: "resize",
+
+ /**
+ * APIProperty: numLoadingTiles
+ * {Integer} How many tiles are still loading?
+ */
+ numLoadingTiles: 0,
+
+ /**
+ * Property: serverResolutions
+ * {Array(Number}} This property is documented in subclasses as
+ * an API property.
+ */
+ serverResolutions: null,
+
+ /**
+ * Property: loading
+ * {Boolean} Indicates if tiles are being loaded.
+ */
+ loading: false,
+
+ /**
+ * Property: backBuffer
+ * {DOMElement} The back buffer.
+ */
+ backBuffer: null,
+
+ /**
+ * Property: gridResolution
+ * {Number} The resolution of the current grid. Used for backbuffer and
+ * client zoom. This property is updated every time the grid is
+ * initialized.
+ */
+ gridResolution: null,
+
+ /**
+ * Property: backBufferResolution
+ * {Number} The resolution of the current back buffer. This property is
+ * updated each time a back buffer is created.
+ */
+ backBufferResolution: null,
+
+ /**
+ * Property: backBufferLonLat
+ * {Object} The top-left corner of the current back buffer. Includes lon
+ * and lat properties. This object is updated each time a back buffer
+ * is created.
+ */
+ backBufferLonLat: null,
+
+ /**
+ * Property: backBufferTimerId
+ * {Number} The id of the back buffer timer. This timer is used to
+ * delay the removal of the back buffer, thereby preventing
+ * flash effects caused by tile animation.
+ */
+ backBufferTimerId: null,
+
+ /**
+ * APIProperty: removeBackBufferDelay
+ * {Number} Delay for removing the backbuffer when all tiles have finished
+ * loading. Can be set to 0 when no css opacity transitions for the
+ * olTileImage class are used. Default is 0 for <singleTile> layers,
+ * 2500 for tiled layers. See <className> for more information on
+ * tile animation.
+ */
+ removeBackBufferDelay: null,
+
+ /**
+ * APIProperty: className
+ * {String} Name of the class added to the layer div. If not set in the
+ * options passed to the constructor then className defaults to
+ * "olLayerGridSingleTile" for single tile layers (see <singleTile>),
+ * and "olLayerGrid" for non single tile layers.
+ *
+ * Note:
+ *
+ * The displaying of tiles is not animated by default for single tile
+ * layers - OpenLayers' default theme (style.css) includes this:
+ * (code)
+ * .olLayerGrid .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * To animate tile displaying for any grid layer the following
+ * CSS rule can be used:
+ * (code)
+ * .olTileImage {
+ * -webkit-transition: opacity 0.2s linear;
+ * -moz-transition: opacity 0.2s linear;
+ * -o-transition: opacity 0.2s linear;
+ * transition: opacity 0.2s linear;
+ * }
+ * (end)
+ * In that case, to avoid flash effects, <removeBackBufferDelay>
+ * should not be zero.
+ */
+ className: null,
+
+ /**
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported event types:
+ * addtile - Triggered when a tile is added to this layer. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that has been added.
+ * tileloadstart - Triggered when a tile starts loading. Listeners receive
+ * an object as first argument, which has a tile property that
+ * references the tile that starts loading.
+ * tileloaded - Triggered when each new tile is
+ * loaded, as a means of progress update to listeners.
+ * listeners can access 'numLoadingTiles' if they wish to keep
+ * track of the loading progress. Listeners are called with an object
+ * with a 'tile' property as first argument, making the loaded tile
+ * available to the listener, and an 'aborted' property, which will be
+ * true when loading was aborted and no tile data is available.
+ * tileerror - Triggered before the tileloaded event (i.e. when the tile is
+ * still hidden) if a tile failed to load. Listeners receive an object
+ * as first argument, which has a tile property that references the
+ * tile that could not be loaded.
+ * retile - Triggered when the layer recreates its tile grid.
+ */
+
+ /**
+ * Property: gridLayout
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ gridLayout: null,
+
+ /**
+ * Property: rowSign
+ * {Number} 1 for grids starting at the top, -1 for grids starting at the
+ * bottom. This is used for several grid index and offset calculations.
+ */
+ rowSign: null,
+
+ /**
+ * Property: transitionendEvents
+ * {Array} Event names for transitionend
+ */
+ transitionendEvents: [
+ 'transitionend', 'webkitTransitionEnd', 'otransitionend',
+ 'oTransitionEnd'
+ ],
+
+ /**
+ * Constructor: OpenLayers.Layer.Grid
+ * Create a new grid layer
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,
+ arguments);
+ this.grid = [];
+ this._removeBackBuffer = OpenLayers.Function.bind(this.removeBackBuffer, this);
+
+ this.initProperties();
+
+ this.rowSign = this.tileOriginCorner.substr(0, 1) === "t" ? 1 : -1;
+ },
+
+ /**
+ * Method: initProperties
+ * Set any properties that depend on the value of singleTile.
+ * Currently sets removeBackBufferDelay and className
+ */
+ initProperties: function() {
+ if (this.options.removeBackBufferDelay === undefined) {
+ this.removeBackBufferDelay = this.singleTile ? 0 : 2500;
+ }
+
+ if (this.options.className === undefined) {
+ this.className = this.singleTile ? 'olLayerGridSingleTile' :
+ 'olLayerGrid';
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.HTTPRequest.prototype.setMap.call(this, map);
+ OpenLayers.Element.addClass(this.div, this.className);
+ },
+
+ /**
+ * Method: removeMap
+ * Called when the layer is removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map.
+ */
+ removeMap: function(map) {
+ this.removeBackBuffer();
+ },
+
+ /**
+ * APIMethod: destroy
+ * Deconstruct the layer and clear the grid.
+ */
+ destroy: function() {
+ this.removeBackBuffer();
+ this.clearGrid();
+
+ this.grid = null;
+ this.tileSize = null;
+ OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Refetches tiles with new params merged, keeping a backbuffer. Each
+ * loading new tile will have a css class of '.olTileReplacing'. If a
+ * stylesheet applies a 'display: none' style to that class, any fade-in
+ * transition will not apply, and backbuffers for each tile will be removed
+ * as soon as the tile is loaded.
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+
+ /**
+ * Method: clearGrid
+ * Go through and remove all tiles from the grid, calling
+ * destroy() on each of them to kill circular references
+ */
+ clearGrid:function() {
+ if (this.grid) {
+ for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
+ var row = this.grid[iRow];
+ for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
+ var tile = row[iCol];
+ this.destroyTile(tile);
+ }
+ }
+ this.grid = [];
+ this.gridResolution = null;
+ this.gridLayout = null;
+ }
+ },
+
+ /**
+ * APIMethod: addOptions
+ *
+ * Parameters:
+ * newOptions - {Object}
+ * reinitialize - {Boolean} If set to true, and if resolution options of the
+ * current baseLayer were changed, the map will be recentered to make
+ * sure that it is displayed with a valid resolution, and a
+ * changebaselayer event will be triggered.
+ */
+ addOptions: function (newOptions, reinitialize) {
+ var singleTileChanged = newOptions.singleTile !== undefined &&
+ newOptions.singleTile !== this.singleTile;
+ OpenLayers.Layer.HTTPRequest.prototype.addOptions.apply(this, arguments);
+ if (this.map && singleTileChanged) {
+ this.initProperties();
+ this.clearGrid();
+ this.tileSize = this.options.tileSize;
+ this.setTileSize();
+ this.moveTo(null, true);
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Grid(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ if (this.tileSize != null) {
+ obj.tileSize = this.tileSize.clone();
+ }
+
+ // we do not want to copy reference to grid, so we make a new array
+ obj.grid = [];
+ obj.gridResolution = null;
+ // same for backbuffer
+ obj.backBuffer = null;
+ obj.backBufferTimerId = null;
+ obj.loading = false;
+ obj.numLoadingTiles = 0;
+
+ return obj;
+ },
+
+ /**
+ * Method: moveTo
+ * This function is called whenever the map is moved. All the moving
+ * of actual 'tiles' is done by the map, but moveTo's role is to accept
+ * a bounds and make sure the data that that bounds requires is pre-loaded.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+
+ OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
+
+ bounds = bounds || this.map.getExtent();
+
+ if (bounds != null) {
+
+ // if grid is empty or zoom has changed, we *must* re-tile
+ var forceReTile = !this.grid.length || zoomChanged;
+
+ // total bounds of the tiles
+ var tilesBounds = this.getTilesBounds();
+
+ // the new map resolution
+ var resolution = this.map.getResolution();
+
+ // the server-supported resolution for the new map resolution
+ var serverResolution = this.getServerResolution(resolution);
+
+ if (this.singleTile) {
+
+ // We want to redraw whenever even the slightest part of the
+ // current bounds is not contained by our tile.
+ // (thus, we do not specify partial -- its default is false)
+
+ if ( forceReTile ||
+ (!dragging && !tilesBounds.containsBounds(bounds))) {
+
+ // In single tile mode with no transition effect, we insert
+ // a non-scaled backbuffer when the layer is moved. But if
+ // a zoom occurs right after a move, i.e. before the new
+ // image is received, we need to remove the backbuffer, or
+ // an ill-positioned image will be visible during the zoom
+ // transition.
+
+ if(zoomChanged && this.transitionEffect !== 'resize') {
+ this.removeBackBuffer();
+ }
+
+ if(!zoomChanged || this.transitionEffect === 'resize') {
+ this.applyBackBuffer(resolution);
+ }
+
+ this.initSingleTile(bounds);
+ }
+ } else {
+
+ // if the bounds have changed such that they are not even
+ // *partially* contained by our tiles (e.g. when user has
+ // programmatically panned to the other side of the earth on
+ // zoom level 18), then moveGriddedTiles could potentially have
+ // to run through thousands of cycles, so we want to reTile
+ // instead (thus, partial true).
+ forceReTile = forceReTile ||
+ !tilesBounds.intersectsBounds(bounds, {
+ worldBounds: this.map.baseLayer.wrapDateLine &&
+ this.map.getMaxExtent()
+ });
+
+ if(forceReTile) {
+ if(zoomChanged && (this.transitionEffect === 'resize' ||
+ this.gridResolution === resolution)) {
+ this.applyBackBuffer(resolution);
+ }
+ this.initGriddedTiles(bounds);
+ } else {
+ this.moveGriddedTiles();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: getTileData
+ * Given a map location, retrieve a tile and the pixel offset within that
+ * tile corresponding to the location. If there is not an existing
+ * tile in the grid that covers the given location, null will be
+ * returned.
+ *
+ * Parameters:
+ * loc - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object with the following properties: tile ({<OpenLayers.Tile>}),
+ * i ({Number} x-pixel offset from top left), and j ({Integer} y-pixel
+ * offset from top left).
+ */
+ getTileData: function(loc) {
+ var data = null,
+ x = loc.lon,
+ y = loc.lat,
+ numRows = this.grid.length;
+
+ if (this.map && numRows) {
+ var res = this.map.getResolution(),
+ tileWidth = this.tileSize.w,
+ tileHeight = this.tileSize.h,
+ bounds = this.grid[0][0].bounds,
+ left = bounds.left,
+ top = bounds.top;
+
+ if (x < left) {
+ // deal with multiple worlds
+ if (this.map.baseLayer.wrapDateLine) {
+ var worldWidth = this.map.getMaxExtent().getWidth();
+ var worldsAway = Math.ceil((left - x) / worldWidth);
+ x += worldWidth * worldsAway;
+ }
+ }
+ // tile distance to location (fractional number of tiles);
+ var dtx = (x - left) / (res * tileWidth);
+ var dty = (top - y) / (res * tileHeight);
+ // index of tile in grid
+ var col = Math.floor(dtx);
+ var row = Math.floor(dty);
+ if (row >= 0 && row < numRows) {
+ var tile = this.grid[row][col];
+ if (tile) {
+ data = {
+ tile: tile,
+ // pixel index within tile
+ i: Math.floor((dtx - col) * tileWidth),
+ j: Math.floor((dty - row) * tileHeight)
+ };
+ }
+ }
+ }
+ return data;
+ },
+
+ /**
+ * Method: destroyTile
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ destroyTile: function(tile) {
+ this.removeTileMonitoringHooks(tile);
+ tile.destroy();
+ },
+
+ /**
+ * Method: getServerResolution
+ * Return the closest server-supported resolution.
+ *
+ * Parameters:
+ * resolution - {Number} The base resolution. If undefined the
+ * map resolution is used.
+ *
+ * Returns:
+ * {Number} The closest server resolution value.
+ */
+ getServerResolution: function(resolution) {
+ var distance = Number.POSITIVE_INFINITY;
+ resolution = resolution || this.map.getResolution();
+ if(this.serverResolutions &&
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) === -1) {
+ var i, newDistance, newResolution, serverResolution;
+ for(i=this.serverResolutions.length-1; i>= 0; i--) {
+ newResolution = this.serverResolutions[i];
+ newDistance = Math.abs(newResolution - resolution);
+ if (newDistance > distance) {
+ break;
+ }
+ distance = newDistance;
+ serverResolution = newResolution;
+ }
+ resolution = serverResolution;
+ }
+ return resolution;
+ },
+
+ /**
+ * Method: getServerZoom
+ * Return the zoom value corresponding to the best matching server
+ * resolution, taking into account <serverResolutions> and <zoomOffset>.
+ *
+ * Returns:
+ * {Number} The closest server supported zoom. This is not the map zoom
+ * level, but an index of the server's resolutions array.
+ */
+ getServerZoom: function() {
+ var resolution = this.getServerResolution();
+ return this.serverResolutions ?
+ OpenLayers.Util.indexOf(this.serverResolutions, resolution) :
+ this.map.getZoomForResolution(resolution) + (this.zoomOffset || 0);
+ },
+
+ /**
+ * Method: applyBackBuffer
+ * Create, insert, scale and position a back buffer for the layer.
+ *
+ * Parameters:
+ * resolution - {Number} The resolution to transition to.
+ */
+ applyBackBuffer: function(resolution) {
+ if(this.backBufferTimerId !== null) {
+ this.removeBackBuffer();
+ }
+ var backBuffer = this.backBuffer;
+ if(!backBuffer) {
+ backBuffer = this.createBackBuffer();
+ if(!backBuffer) {
+ return;
+ }
+ if (resolution === this.gridResolution) {
+ this.div.insertBefore(backBuffer, this.div.firstChild);
+ } else {
+ this.map.baseLayer.div.parentNode.insertBefore(backBuffer, this.map.baseLayer.div);
+ }
+ this.backBuffer = backBuffer;
+
+ // set some information in the instance for subsequent
+ // calls to applyBackBuffer where the same back buffer
+ // is reused
+ var topLeftTileBounds = this.grid[0][0].bounds;
+ this.backBufferLonLat = {
+ lon: topLeftTileBounds.left,
+ lat: topLeftTileBounds.top
+ };
+ this.backBufferResolution = this.gridResolution;
+ }
+
+ var ratio = this.backBufferResolution / resolution;
+
+ // scale the tiles inside the back buffer
+ var tiles = backBuffer.childNodes, tile;
+ for (var i=tiles.length-1; i>=0; --i) {
+ tile = tiles[i];
+ tile.style.top = ((ratio * tile._i * tile._h) | 0) + 'px';
+ tile.style.left = ((ratio * tile._j * tile._w) | 0) + 'px';
+ tile.style.width = Math.round(ratio * tile._w) + 'px';
+ tile.style.height = Math.round(ratio * tile._h) + 'px';
+ }
+
+ // and position it (based on the grid's top-left corner)
+ var position = this.getViewPortPxFromLonLat(
+ this.backBufferLonLat, resolution);
+ var leftOffset = this.map.layerContainerOriginPx.x;
+ var topOffset = this.map.layerContainerOriginPx.y;
+ backBuffer.style.left = Math.round(position.x - leftOffset) + 'px';
+ backBuffer.style.top = Math.round(position.y - topOffset) + 'px';
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a back buffer.
+ *
+ * Returns:
+ * {DOMElement} The DOM element for the back buffer, undefined if the
+ * grid isn't initialized yet.
+ */
+ createBackBuffer: function() {
+ var backBuffer;
+ if(this.grid.length > 0) {
+ backBuffer = document.createElement('div');
+ backBuffer.id = this.div.id + '_bb';
+ backBuffer.className = 'olBackBuffer';
+ backBuffer.style.position = 'absolute';
+ var map = this.map;
+ backBuffer.style.zIndex = this.transitionEffect === 'resize' ?
+ this.getZIndex() - 1 :
+ // 'map-resize':
+ map.Z_INDEX_BASE.BaseLayer -
+ (map.getNumLayers() - map.getLayerIndex(this));
+ for(var i=0, lenI=this.grid.length; i<lenI; i++) {
+ for(var j=0, lenJ=this.grid[i].length; j<lenJ; j++) {
+ var tile = this.grid[i][j],
+ markup = this.grid[i][j].createBackBuffer();
+ if (markup) {
+ markup._i = i;
+ markup._j = j;
+ markup._w = tile.size.w;
+ markup._h = tile.size.h;
+ markup.id = tile.id + '_bb';
+ backBuffer.appendChild(markup);
+ }
+ }
+ }
+ }
+ return backBuffer;
+ },
+
+ /**
+ * Method: removeBackBuffer
+ * Remove back buffer from DOM.
+ */
+ removeBackBuffer: function() {
+ if (this._transitionElement) {
+ for (var i=this.transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.stopObserving(this._transitionElement,
+ this.transitionendEvents[i], this._removeBackBuffer);
+ }
+ delete this._transitionElement;
+ }
+ if(this.backBuffer) {
+ if (this.backBuffer.parentNode) {
+ this.backBuffer.parentNode.removeChild(this.backBuffer);
+ }
+ this.backBuffer = null;
+ this.backBufferResolution = null;
+ if(this.backBufferTimerId !== null) {
+ window.clearTimeout(this.backBufferTimerId);
+ this.backBufferTimerId = null;
+ }
+ }
+ },
+
+ /**
+ * Method: moveByPx
+ * Move the layer based on pixel vector.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ if (!this.singleTile) {
+ this.moveGriddedTiles();
+ }
+ },
+
+ /**
+ * APIMethod: setTileSize
+ * Check if we are in singleTile mode and if so, set the size as a ratio
+ * of the map size (as specified by the layer's 'ratio' property).
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setTileSize: function(size) {
+ if (this.singleTile) {
+ size = this.map.getSize();
+ size.h = parseInt(size.h * this.ratio, 10);
+ size.w = parseInt(size.w * this.ratio, 10);
+ }
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
+ },
+
+ /**
+ * APIMethod: getTilesBounds
+ * Return the bounds of the tile grid.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
+ * currently loaded tiles (including those partially or not at all seen
+ * onscreen).
+ */
+ getTilesBounds: function() {
+ var bounds = null;
+
+ var length = this.grid.length;
+ if (length) {
+ var bottomLeftTileBounds = this.grid[length - 1][0].bounds,
+ width = this.grid[0].length * bottomLeftTileBounds.getWidth(),
+ height = this.grid.length * bottomLeftTileBounds.getHeight();
+
+ bounds = new OpenLayers.Bounds(bottomLeftTileBounds.left,
+ bottomLeftTileBounds.bottom,
+ bottomLeftTileBounds.left + width,
+ bottomLeftTileBounds.bottom + height);
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: initSingleTile
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initSingleTile: function(bounds) {
+ this.events.triggerEvent("retile");
+
+ //determine new tile bounds
+ var center = bounds.getCenterLonLat();
+ var tileWidth = bounds.getWidth() * this.ratio;
+ var tileHeight = bounds.getHeight() * this.ratio;
+
+ var tileBounds =
+ new OpenLayers.Bounds(center.lon - (tileWidth/2),
+ center.lat - (tileHeight/2),
+ center.lon + (tileWidth/2),
+ center.lat + (tileHeight/2));
+
+ var px = this.map.getLayerPxFromLonLat({
+ lon: tileBounds.left,
+ lat: tileBounds.top
+ });
+
+ if (!this.grid.length) {
+ this.grid[0] = [];
+ }
+
+ var tile = this.grid[0][0];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+
+ this.addTileMonitoringHooks(tile);
+ tile.draw();
+ this.grid[0][0] = tile;
+ } else {
+ tile.moveTo(tileBounds, px);
+ }
+
+ //remove all but our single tile
+ this.removeExcessTiles(1,1);
+
+ // store the resolution of the grid
+ this.gridResolution = this.getServerResolution();
+ },
+
+ /**
+ * Method: calculateGridLayout
+ * Generate parameters for the grid layout.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bound>|Object} OpenLayers.Bounds or an
+ * object with a 'left' and 'top' properties.
+ * origin - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+ * object with a 'lon' and 'lat' properties.
+ * resolution - {Number}
+ *
+ * Returns:
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ calculateGridLayout: function(bounds, origin, resolution) {
+ var tilelon = resolution * this.tileSize.w;
+ var tilelat = resolution * this.tileSize.h;
+
+ var offsetlon = bounds.left - origin.lon;
+ var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+
+ var rowSign = this.rowSign;
+
+ var offsetlat = rowSign * (origin.lat - bounds.top + tilelat);
+ var tilerow = Math[~rowSign ? 'floor' : 'ceil'](offsetlat/tilelat) - this.buffer * rowSign;
+
+ return {
+ tilelon: tilelon, tilelat: tilelat,
+ startcol: tilecol, startrow: tilerow
+ };
+
+ },
+
+ /**
+ * Method: getTileOrigin
+ * Determine the origin for aligning the grid of tiles. If a <tileOrigin>
+ * property is supplied, that will be returned. Otherwise, the origin
+ * will be derived from the layer's <maxExtent> property. In this case,
+ * the tile origin will be the corner of the <maxExtent> given by the
+ * <tileOriginCorner> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The tile origin.
+ */
+ getTileOrigin: function() {
+ var origin = this.tileOrigin;
+ if (!origin) {
+ var extent = this.getMaxExtent();
+ var edges = ({
+ "tl": ["left", "top"],
+ "tr": ["right", "top"],
+ "bl": ["left", "bottom"],
+ "br": ["right", "bottom"]
+ })[this.tileOriginCorner];
+ origin = new OpenLayers.LonLat(extent[edges[0]], extent[edges[1]]);
+ }
+ return origin;
+ },
+
+ /**
+ * Method: getTileBoundsForGridIndex
+ *
+ * Parameters:
+ * row - {Number} The row of the grid
+ * col - {Number} The column of the grid
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
+ */
+ getTileBoundsForGridIndex: function(row, col) {
+ var origin = this.getTileOrigin();
+ var tileLayout = this.gridLayout;
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+ var startcol = tileLayout.startcol;
+ var startrow = tileLayout.startrow;
+ var rowSign = this.rowSign;
+ return new OpenLayers.Bounds(
+ origin.lon + (startcol + col) * tilelon,
+ origin.lat - (startrow + row * rowSign) * tilelat * rowSign,
+ origin.lon + (startcol + col + 1) * tilelon,
+ origin.lat - (startrow + (row - 1) * rowSign) * tilelat * rowSign
+ );
+ },
+
+ /**
+ * Method: initGriddedTiles
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ initGriddedTiles:function(bounds) {
+ this.events.triggerEvent("retile");
+
+ // work out mininum number of rows and columns; this is the number of
+ // tiles required to cover the viewport plus at least one for panning
+
+ var viewSize = this.map.getSize();
+
+ var origin = this.getTileOrigin();
+ var resolution = this.map.getResolution(),
+ serverResolution = this.getServerResolution(),
+ ratio = resolution / serverResolution,
+ tileSize = {
+ w: this.tileSize.w / ratio,
+ h: this.tileSize.h / ratio
+ };
+
+ var minRows = Math.ceil(viewSize.h/tileSize.h) +
+ 2 * this.buffer + 1;
+ var minCols = Math.ceil(viewSize.w/tileSize.w) +
+ 2 * this.buffer + 1;
+
+ var tileLayout = this.calculateGridLayout(bounds, origin, serverResolution);
+ this.gridLayout = tileLayout;
+
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+
+ var layerContainerDivLeft = this.map.layerContainerOriginPx.x;
+ var layerContainerDivTop = this.map.layerContainerOriginPx.y;
+
+ var tileBounds = this.getTileBoundsForGridIndex(0, 0);
+ var startPx = this.map.getViewPortPxFromLonLat(
+ new OpenLayers.LonLat(tileBounds.left, tileBounds.top)
+ );
+ startPx.x = Math.round(startPx.x) - layerContainerDivLeft;
+ startPx.y = Math.round(startPx.y) - layerContainerDivTop;
+
+ var tileData = [], center = this.map.getCenter();
+
+ var rowidx = 0;
+ do {
+ var row = this.grid[rowidx];
+ if (!row) {
+ row = [];
+ this.grid.push(row);
+ }
+
+ var colidx = 0;
+ do {
+ tileBounds = this.getTileBoundsForGridIndex(rowidx, colidx);
+ var px = startPx.clone();
+ px.x = px.x + colidx * Math.round(tileSize.w);
+ px.y = px.y + rowidx * Math.round(tileSize.h);
+ var tile = row[colidx];
+ if (!tile) {
+ tile = this.addTile(tileBounds, px);
+ this.addTileMonitoringHooks(tile);
+ row.push(tile);
+ } else {
+ tile.moveTo(tileBounds, px, false);
+ }
+ var tileCenter = tileBounds.getCenterLonLat();
+ tileData.push({
+ tile: tile,
+ distance: Math.pow(tileCenter.lon - center.lon, 2) +
+ Math.pow(tileCenter.lat - center.lat, 2)
+ });
+
+ colidx += 1;
+ } while ((tileBounds.right <= bounds.right + tilelon * this.buffer)
+ || colidx < minCols);
+
+ rowidx += 1;
+ } while((tileBounds.bottom >= bounds.bottom - tilelat * this.buffer)
+ || rowidx < minRows);
+
+ //shave off exceess rows and colums
+ this.removeExcessTiles(rowidx, colidx);
+
+ var resolution = this.getServerResolution();
+ // store the resolution of the grid
+ this.gridResolution = resolution;
+
+ //now actually draw the tiles
+ tileData.sort(function(a, b) {
+ return a.distance - b.distance;
+ });
+ for (var i=0, ii=tileData.length; i<ii; ++i) {
+ tileData[i].tile.draw();
+ }
+ },
+
+ /**
+ * Method: getMaxExtent
+ * Get this layer's maximum extent. (Implemented as a getter for
+ * potential specific implementations in sub-classes.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getMaxExtent: function() {
+ return this.maxExtent;
+ },
+
+ /**
+ * APIMethod: addTile
+ * Create a tile, initialize it, and add it to the layer div.
+ *
+ * Parameters
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Tile>} The added OpenLayers.Tile
+ */
+ addTile: function(bounds, position) {
+ var tile = new this.tileClass(
+ this, position, bounds, null, this.tileSize, this.tileOptions
+ );
+ this.events.triggerEvent("addtile", {tile: tile});
+ return tile;
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tiles.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+
+ var replacingCls = 'olTileReplacing';
+
+ tile.onLoadStart = function() {
+ //if that was first tile then trigger a 'loadstart' on the layer
+ if (this.loading === false) {
+ this.loading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.events.triggerEvent("tileloadstart", {tile: tile});
+ this.numLoadingTiles++;
+ if (!this.singleTile && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ OpenLayers.Element.addClass(tile.getTile(), replacingCls);
+ }
+ };
+
+ tile.onLoadEnd = function(evt) {
+ this.numLoadingTiles--;
+ var aborted = evt.type === 'unload';
+ this.events.triggerEvent("tileloaded", {
+ tile: tile,
+ aborted: aborted
+ });
+ if (!this.singleTile && !aborted && this.backBuffer && this.gridResolution === this.backBufferResolution) {
+ var tileDiv = tile.getTile();
+ if (OpenLayers.Element.getStyle(tileDiv, 'display') === 'none') {
+ var bufferTile = document.getElementById(tile.id + '_bb');
+ if (bufferTile) {
+ bufferTile.parentNode.removeChild(bufferTile);
+ }
+ }
+ OpenLayers.Element.removeClass(tileDiv, replacingCls);
+ }
+ //if that was the last tile, then trigger a 'loadend' on the layer
+ if (this.numLoadingTiles === 0) {
+ if (this.backBuffer) {
+ if (this.backBuffer.childNodes.length === 0) {
+ // no tiles transitioning, remove immediately
+ this.removeBackBuffer();
+ } else {
+ // wait until transition has ended or delay has passed
+ this._transitionElement = aborted ?
+ this.div.lastChild : tile.imgDiv;
+ var transitionendEvents = this.transitionendEvents;
+ for (var i=transitionendEvents.length-1; i>=0; --i) {
+ OpenLayers.Event.observe(this._transitionElement,
+ transitionendEvents[i],
+ this._removeBackBuffer);
+ }
+ // the removal of the back buffer is delayed to prevent
+ // flash effects due to the animation of tile displaying
+ this.backBufferTimerId = window.setTimeout(
+ this._removeBackBuffer, this.removeBackBufferDelay
+ );
+ }
+ }
+ this.loading = false;
+ this.events.triggerEvent("loadend");
+ }
+ };
+
+ tile.onLoadError = function() {
+ this.events.triggerEvent("tileerror", {tile: tile});
+ };
+
+ tile.events.on({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in addTileMonitoringHooks()
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ "loaderror": tile.onLoadError,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: moveGriddedTiles
+ */
+ moveGriddedTiles: function() {
+ var buffer = this.buffer + 1;
+ while(true) {
+ var tlTile = this.grid[0][0];
+ var tlViewPort = {
+ x: tlTile.position.x +
+ this.map.layerContainerOriginPx.x,
+ y: tlTile.position.y +
+ this.map.layerContainerOriginPx.y
+ };
+ var ratio = this.getServerResolution() / this.map.getResolution();
+ var tileSize = {
+ w: Math.round(this.tileSize.w * ratio),
+ h: Math.round(this.tileSize.h * ratio)
+ };
+ if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
+ this.shiftColumn(true, tileSize);
+ } else if (tlViewPort.x < -tileSize.w * buffer) {
+ this.shiftColumn(false, tileSize);
+ } else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
+ this.shiftRow(true, tileSize);
+ } else if (tlViewPort.y < -tileSize.h * buffer) {
+ this.shiftRow(false, tileSize);
+ } else {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: shiftRow
+ * Shifty grid work
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftRow: function(prepend, tileSize) {
+ var grid = this.grid;
+ var rowIndex = prepend ? 0 : (grid.length - 1);
+ var sign = prepend ? -1 : 1;
+ var rowSign = this.rowSign;
+ var tileLayout = this.gridLayout;
+ tileLayout.startrow += sign * rowSign;
+
+ var modelRow = grid[rowIndex];
+ var row = grid[prepend ? 'pop' : 'shift']();
+ for (var i=0, len=row.length; i<len; i++) {
+ var tile = row[i];
+ var position = modelRow[i].position.clone();
+ position.y += tileSize.h * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(rowIndex, i), position);
+ }
+ grid[prepend ? 'unshift' : 'push'](row);
+ },
+
+ /**
+ * Method: shiftColumn
+ * Shift grid work in the other dimension
+ *
+ * Parameters:
+ * prepend - {Boolean} if true, prepend to beginning.
+ * if false, then append to end
+ * tileSize - {Object} rendered tile size; object with w and h properties
+ */
+ shiftColumn: function(prepend, tileSize) {
+ var grid = this.grid;
+ var colIndex = prepend ? 0 : (grid[0].length - 1);
+ var sign = prepend ? -1 : 1;
+ var tileLayout = this.gridLayout;
+ tileLayout.startcol += sign;
+
+ for (var i=0, len=grid.length; i<len; i++) {
+ var row = grid[i];
+ var position = row[colIndex].position.clone();
+ var tile = row[prepend ? 'pop' : 'shift']();
+ position.x += tileSize.w * sign;
+ tile.moveTo(this.getTileBoundsForGridIndex(i, colIndex), position);
+ row[prepend ? 'unshift' : 'push'](tile);
+ }
+ },
+
+ /**
+ * Method: removeExcessTiles
+ * When the size of the map or the buffer changes, we may need to
+ * remove some excess rows and columns.
+ *
+ * Parameters:
+ * rows - {Integer} Maximum number of rows we want our grid to have.
+ * columns - {Integer} Maximum number of columns we want our grid to have.
+ */
+ removeExcessTiles: function(rows, columns) {
+ var i, l;
+
+ // remove extra rows
+ while (this.grid.length > rows) {
+ var row = this.grid.pop();
+ for (i=0, l=row.length; i<l; i++) {
+ var tile = row[i];
+ this.destroyTile(tile);
+ }
+ }
+
+ // remove extra columns
+ for (i=0, l=this.grid.length; i<l; i++) {
+ while (this.grid[i].length > columns) {
+ var row = this.grid[i];
+ var tile = row.pop();
+ this.destroyTile(tile);
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * For singleTile layers, this will set a new tile size according to the
+ * dimensions of the map pane.
+ */
+ onMapResize: function() {
+ if (this.singleTile) {
+ this.clearGrid();
+ this.setTileSize();
+ }
+ },
+
+ /**
+ * APIMethod: getTileBounds
+ * Returns The tile bounds for a layer given a pixel location.
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var maxExtent = this.maxExtent;
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = maxExtent.left + (tileMapWidth *
+ Math.floor((mapPoint.lon -
+ maxExtent.left) /
+ tileMapWidth));
+ var tileBottom = maxExtent.bottom + (tileMapHeight *
+ Math.floor((mapPoint.lat -
+ maxExtent.bottom) /
+ tileMapHeight));
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Grid"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js b/misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js
new file mode 100644
index 0000000..ccb0291
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/HTTPRequest.js
@@ -0,0 +1,230 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.HTTPRequest
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * Constant: URL_HASH_FACTOR
+ * {Float} Used to hash URL param strings for multi-WMS server selection.
+ * Set to the Golden Ratio per Knuth's recommendation.
+ */
+ URL_HASH_FACTOR: (Math.sqrt(5) - 1) / 2,
+
+ /**
+ * Property: url
+ * {Array(String) or String} This is either an array of url strings or
+ * a single url string.
+ */
+ url: null,
+
+ /**
+ * Property: params
+ * {Object} Hashtable of key/value parameters
+ */
+ params: null,
+
+ /**
+ * APIProperty: reproject
+ * *Deprecated*. See http://docs.openlayers.org/library/spherical_mercator.html
+ * for information on the replacement for this functionality.
+ * {Boolean} Whether layer should reproject itself based on base layer
+ * locations. This allows reprojection onto commercial layers.
+ * Default is false: Most layers can't reproject, but layers
+ * which can create non-square geographic pixels can, like WMS.
+ *
+ */
+ reproject: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.HTTPRequest
+ *
+ * Parameters:
+ * name - {String}
+ * url - {Array(String) or String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+ this.url = url;
+ if (!this.params) {
+ this.params = OpenLayers.Util.extend({}, params);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.url = null;
+ this.params = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.HTTPRequest>} An exact clone of this
+ * <OpenLayers.Layer.HTTPRequest>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.HTTPRequest(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: setUrl
+ *
+ * Parameters:
+ * newUrl - {String}
+ */
+ setUrl: function(newUrl) {
+ this.url = newUrl;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ *
+ * Parameters:
+ * newParams - {Object}
+ *
+ * Returns:
+ * redrawn: {Boolean} whether the layer was actually redrawn.
+ */
+ mergeNewParams:function(newParams) {
+ this.params = OpenLayers.Util.extend(this.params, newParams);
+ var ret = this.redraw();
+ if(this.map != null) {
+ this.map.events.triggerEvent("changelayer", {
+ layer: this,
+ property: "params"
+ });
+ }
+ return ret;
+ },
+
+ /**
+ * APIMethod: redraw
+ * Redraws the layer. Returns true if the layer was redrawn, false if not.
+ *
+ * Parameters:
+ * force - {Boolean} Force redraw by adding random parameter.
+ *
+ * Returns:
+ * {Boolean} The layer was redrawn.
+ */
+ redraw: function(force) {
+ if (force) {
+ return this.mergeNewParams({"_olSalt": Math.random()});
+ } else {
+ return OpenLayers.Layer.prototype.redraw.apply(this, []);
+ }
+ },
+
+ /**
+ * Method: selectUrl
+ * selectUrl() implements the standard floating-point multiplicative
+ * hash function described by Knuth, and hashes the contents of the
+ * given param string into a float between 0 and 1. This float is then
+ * scaled to the size of the provided urls array, and used to select
+ * a URL.
+ *
+ * Parameters:
+ * paramString - {String}
+ * urls - {Array(String)}
+ *
+ * Returns:
+ * {String} An entry from the urls array, deterministically selected based
+ * on the paramString.
+ */
+ selectUrl: function(paramString, urls) {
+ var product = 1;
+ for (var i=0, len=paramString.length; i<len; i++) {
+ product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR;
+ product -= Math.floor(product);
+ }
+ return urls[Math.floor(product * urls.length)];
+ },
+
+ /**
+ * Method: getFullRequestString
+ * Combine url with layer's params and these newParams.
+ *
+ * does checking on the serverPath variable, allowing for cases when it
+ * is supplied with trailing ? or &, as well as cases where not.
+ *
+ * return in formatted string like this:
+ * "server?key1=value1&key2=value2&key3=value3"
+ *
+ * WARNING: The altUrl parameter is deprecated and will be removed in 3.0.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+
+ // if not altUrl passed in, use layer's url
+ var url = altUrl || this.url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ //
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams =
+ OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ return OpenLayers.Util.urlAppend(url, paramsString);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Image.js b/misc/openlayers/lib/OpenLayers/Layer/Image.js
new file mode 100644
index 0000000..b96e369
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Image.js
@@ -0,0 +1,259 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Image
+ * Instances of OpenLayers.Layer.Image are used to display data from a web
+ * accessible image as a map layer. Create a new image layer with the
+ * <OpenLayers.Layer.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * Property: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is true. Set this property
+ * in the layer options
+ */
+ isBaseLayer: true,
+
+ /**
+ * Property: url
+ * {String} URL of the image to use
+ */
+ url: null,
+
+ /**
+ * Property: extent
+ * {<OpenLayers.Bounds>} The image bounds in map units. This extent will
+ * also be used as the default maxExtent for the layer. If you wish
+ * to have a maxExtent that is different than the image extent, set the
+ * maxExtent property of the options argument (as with any other layer).
+ */
+ extent: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} The image size in pixels
+ */
+ size: null,
+
+ /**
+ * Property: tile
+ * {<OpenLayers.Tile.Image>}
+ */
+ tile: null,
+
+ /**
+ * Property: aspectRatio
+ * {Float} The ratio of height/width represented by a single pixel in the
+ * graphic
+ */
+ aspectRatio: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Image
+ * Create a new image layer
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * url - {String} Relative or absolute path to the image
+ * extent - {<OpenLayers.Bounds>} The extent represented by the image
+ * size - {<OpenLayers.Size>} The size (in pixels) of the image
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, extent, size, options) {
+ this.url = url;
+ this.extent = extent;
+ this.maxExtent = extent;
+ this.size = size;
+ OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+
+ this.aspectRatio = (this.extent.getHeight() / this.size.h) /
+ (this.extent.getWidth() / this.size.w);
+ },
+
+ /**
+ * Method: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.tile) {
+ this.removeTileMonitoringHooks(this.tile);
+ this.tile.destroy();
+ this.tile = null;
+ }
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Paramters:
+ * obj - {Object} An optional layer (is this ever used?)
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Image>} An exact copy of this layer
+ */
+ clone: function(obj) {
+
+ if(obj == null) {
+ obj = new OpenLayers.Layer.Image(this.name,
+ this.url,
+ this.extent,
+ this.size,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ /**
+ * If nothing to do with resolutions has been set, assume a single
+ * resolution determined by ratio*extent/size - if an image has a
+ * pixel aspect ratio different than one (as calculated above), the
+ * image will be stretched in one dimension only.
+ */
+ if( this.options.maxResolution == null ) {
+ this.options.maxResolution = this.aspectRatio *
+ this.extent.getWidth() /
+ this.size.w;
+ }
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ * Create the tile for the image or resize it for the new resolution
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ var firstRendering = (this.tile == null);
+
+ if(zoomChanged || firstRendering) {
+
+ //determine new tile size
+ this.setTileSize();
+
+ //determine new position (upper left corner of new bounds)
+ var ulPx = this.map.getLayerPxFromLonLat({
+ lon: this.extent.left,
+ lat: this.extent.top
+ });
+
+ if(firstRendering) {
+ //create the new tile
+ this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent,
+ null, this.tileSize);
+ this.addTileMonitoringHooks(this.tile);
+ } else {
+ //just resize the tile and set it's new position
+ this.tile.size = this.tileSize.clone();
+ this.tile.position = ulPx.clone();
+ }
+ this.tile.draw();
+ }
+ },
+
+ /**
+ * Set the tile size based on the map size.
+ */
+ setTileSize: function() {
+ var tileWidth = this.extent.getWidth() / this.map.getResolution();
+ var tileHeight = this.extent.getHeight() / this.map.getResolution();
+ this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tiles.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+ tile.onLoadStart = function() {
+ this.events.triggerEvent("loadstart");
+ };
+ tile.events.register("loadstart", this, tile.onLoadStart);
+
+ tile.onLoadEnd = function() {
+ this.events.triggerEvent("loadend");
+ };
+ tile.events.register("loadend", this, tile.onLoadEnd);
+ tile.events.register("unload", this, tile.onLoadEnd);
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in <addTileMonitoringHooks>.
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ scope: this
+ });
+ },
+
+ /**
+ * APIMethod: setUrl
+ *
+ * Parameters:
+ * newUrl - {String}
+ */
+ setUrl: function(newUrl) {
+ this.url = newUrl;
+ this.tile.draw();
+ },
+
+ /**
+ * APIMethod: getURL
+ * The url we return is always the same (the image itself never changes)
+ * so we can ignore the bounds parameter (it will always be the same,
+ * anyways)
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getURL: function(bounds) {
+ return this.url;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Image"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/KaMap.js b/misc/openlayers/lib/OpenLayers/Layer/KaMap.js
new file mode 100644
index 0000000..3de2224
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/KaMap.js
@@ -0,0 +1,192 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.KaMap
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.KaMap = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} KaMap Layer is always a base layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} parameters set by default. The default parameters set
+ * the format via the 'i' parameter to 'jpeg'.
+ */
+ DEFAULT_PARAMS: {
+ i: 'jpeg',
+ map: ''
+ },
+
+ /**
+ * Constructor: OpenLayers.Layer.KaMap
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object} Parameters to be sent to the HTTP server in the
+ * query string for the tile. The format can be set via the 'i'
+ * parameter (defaults to jpg) , and the map should be set via
+ * the 'map' parameter. It has been reported that ka-Map may behave
+ * inconsistently if your format parameter does not match the format
+ * parameter configured in your config.php. (See ticket #327 for more
+ * information.)
+ * options - {Object} Additional options for the layer. Any of the
+ * APIProperties listed on this layer, and any layer types it
+ * extends, can be overridden through the options parameter.
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
+ this.params = OpenLayers.Util.applyDefaults(
+ this.params, this.DEFAULT_PARAMS
+ );
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var mapRes = this.map.getResolution();
+ var scale = Math.round((this.map.getScale() * 10000)) / 10000;
+ var pX = Math.round(bounds.left / mapRes);
+ var pY = -Math.round(bounds.top / mapRes);
+ return this.getFullRequestString(
+ { t: pY,
+ l: pX,
+ s: scale
+ });
+ },
+
+ /**
+ * Method: calculateGridLayout
+ * ka-Map uses the center point of the map as an origin for
+ * its tiles. Override calculateGridLayout to center tiles
+ * correctly for this case.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bound>}
+ * origin - {<OpenLayers.LonLat>}
+ * resolution - {Number}
+ *
+ * Returns:
+ * {Object} Object containing properties tilelon, tilelat, startcol,
+ * startrow
+ */
+ calculateGridLayout: function(bounds, origin, resolution) {
+ var tilelon = resolution*this.tileSize.w;
+ var tilelat = resolution*this.tileSize.h;
+
+ var offsetlon = bounds.left;
+ var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+
+ var offsetlat = bounds.top;
+ var tilerow = Math.floor(offsetlat/tilelat) + this.buffer;
+
+ return {
+ tilelon: tilelon, tilelat: tilelat,
+ startcol: tilecol, startrow: tilerow
+ };
+ },
+
+ /**
+ * Method: getTileBoundsForGridIndex
+ *
+ * Parameters:
+ * row - {Number} The row of the grid
+ * col - {Number} The column of the grid
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The bounds for the tile at (row, col)
+ */
+ getTileBoundsForGridIndex: function(row, col) {
+ var origin = this.getTileOrigin();
+ var tileLayout = this.gridLayout;
+ var tilelon = tileLayout.tilelon;
+ var tilelat = tileLayout.tilelat;
+ var minX = (tileLayout.startcol + col) * tilelon;
+ var minY = (tileLayout.startrow - row) * tilelat;
+ return new OpenLayers.Bounds(
+ minX, minY,
+ minX + tilelon, minY + tilelat
+ );
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Kamap>} An exact clone of this OpenLayers.Layer.KaMap
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.KaMap(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ if (this.tileSize != null) {
+ obj.tileSize = this.tileSize.clone();
+ }
+
+ // we do not want to copy reference to grid, so we make a new array
+ obj.grid = [];
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: getTileBounds
+ * Returns The tile bounds for a layer given a pixel location.
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+ */
+ getTileBounds: function(viewPortPx) {
+ var resolution = this.getResolution();
+ var tileMapWidth = resolution * this.tileSize.w;
+ var tileMapHeight = resolution * this.tileSize.h;
+ var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+ var tileLeft = tileMapWidth * Math.floor(mapPoint.lon / tileMapWidth);
+ var tileBottom = tileMapHeight * Math.floor(mapPoint.lat / tileMapHeight);
+ return new OpenLayers.Bounds(tileLeft, tileBottom,
+ tileLeft + tileMapWidth,
+ tileBottom + tileMapHeight);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.KaMap"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js b/misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js
new file mode 100644
index 0000000..be6bdb0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/KaMapCache.js
@@ -0,0 +1,143 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Layer/KaMap.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.KaMapCache
+ *
+ * This class is designed to talk directly to a web-accessible ka-Map
+ * cache generated by the precache2.php script.
+ *
+ * To create a a new KaMapCache layer, you must indicate also the "i" parameter
+ * (that will be used to calculate the file extension), and another special
+ * parameter, object names "metaTileSize", with "h" (height) and "w" (width)
+ * properties.
+ *
+ * // Create a new kaMapCache layer.
+ * var kamap_base = new OpenLayers.Layer.KaMapCache(
+ * "Satellite",
+ * "http://www.example.org/web/acessible/cache",
+ * {g: "satellite", map: "world", i: 'png24', metaTileSize: {w: 5, h: 5} }
+ * );
+ *
+ * // Create an kaMapCache overlay layer (using "isBaseLayer: false").
+ * // Forces the output to be a "gif", using the "i" parameter.
+ * var kamap_overlay = new OpenLayers.Layer.KaMapCache(
+ * "Streets",
+ * "http://www.example.org/web/acessible/cache",
+ * {g: "streets", map: "world", i: "gif", metaTileSize: {w: 5, h: 5} },
+ * {isBaseLayer: false}
+ * );
+ *
+ * The cache URLs must look like:
+ * var/cache/World/50000/Group_Name/def/t-440320/l20480
+ *
+ * This means that the cache generated via tile.php will *not* work with
+ * this class, and should instead use the KaMap layer.
+ *
+ * More information is available in Ticket #1518.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.KaMap>
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.KaMapCache = OpenLayers.Class(OpenLayers.Layer.KaMap, {
+
+ /**
+ * Constant: IMAGE_EXTENSIONS
+ * {Object} Simple hash map to convert format to extension.
+ */
+ IMAGE_EXTENSIONS: {
+ 'jpeg': 'jpg',
+ 'gif' : 'gif',
+ 'png' : 'png',
+ 'png8' : 'png',
+ 'png24' : 'png',
+ 'dithered' : 'png'
+ },
+
+ /**
+ * Constant: DEFAULT_FORMAT
+ * {Object} Simple hash map to convert format to extension.
+ */
+ DEFAULT_FORMAT: 'jpeg',
+
+ /**
+ * Constructor: OpenLayers.Layer.KaMapCache
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object} Parameters to be sent to the HTTP server in the
+ * query string for the tile. The format can be set via the 'i'
+ * parameter (defaults to jpg) , and the map should be set via
+ * the 'map' parameter. It has been reported that ka-Map may behave
+ * inconsistently if your format parameter does not match the format
+ * parameter configured in your config.php. (See ticket #327 for more
+ * information.)
+ * options - {Object} Additional options for the layer. Any of the
+ * APIProperties listed on this layer, and any layer types it
+ * extends, can be overridden through the options parameter.
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.KaMap.prototype.initialize.apply(this, arguments);
+ this.extension = this.IMAGE_EXTENSIONS[this.params.i.toLowerCase() || this.DEFAULT_FORMAT];
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var mapRes = this.map.getResolution();
+ var scale = Math.round((this.map.getScale() * 10000)) / 10000;
+ var pX = Math.round(bounds.left / mapRes);
+ var pY = -Math.round(bounds.top / mapRes);
+
+ var metaX = Math.floor(pX / this.tileSize.w / this.params.metaTileSize.w) * this.tileSize.w * this.params.metaTileSize.w;
+ var metaY = Math.floor(pY / this.tileSize.h / this.params.metaTileSize.h) * this.tileSize.h * this.params.metaTileSize.h;
+
+ var components = [
+ "/",
+ this.params.map,
+ "/",
+ scale,
+ "/",
+ this.params.g.replace(/\s/g, '_'),
+ "/def/t",
+ metaY,
+ "/l",
+ metaX,
+ "/t",
+ pY,
+ "l",
+ pX,
+ ".",
+ this.extension
+ ];
+
+ var url = this.url;
+
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(components.join(''), url);
+ }
+ return url + components.join("");
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.KaMapCache"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/MapGuide.js b/misc/openlayers/lib/OpenLayers/Layer/MapGuide.js
new file mode 100644
index 0000000..8f7d979
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/MapGuide.js
@@ -0,0 +1,443 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MapGuide
+ * Instances of OpenLayers.Layer.MapGuide are used to display
+ * data from a MapGuide OS instance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.MapGuide = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Treat this layer as a base layer. Default is true.
+ **/
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: useHttpTile
+ * {Boolean} use a tile cache exposed directly via a webserver rather than the
+ * via mapguide server. This does require extra configuration on the Mapguide Server,
+ * and will only work when singleTile is false. The url for the layer must be set to the
+ * webserver path rather than the Mapguide mapagent.
+ * See http://trac.osgeo.org/mapguide/wiki/CodeSamples/Tiles/ServingTilesViaHttp
+ **/
+ useHttpTile: false,
+
+ /**
+ * APIProperty: singleTile
+ * {Boolean} use tile server or request single tile image.
+ **/
+ singleTile: false,
+
+ /**
+ * APIProperty: useOverlay
+ * {Boolean} flag to indicate if the layer should be retrieved using
+ * GETMAPIMAGE (default) or using GETDYNAMICOVERLAY requests.
+ **/
+ useOverlay: false,
+
+ /**
+ * APIProperty: useAsyncOverlay
+ * {Boolean} indicates if the MapGuide site supports the asynchronous
+ * GETDYNAMICOVERLAY requests which is available in MapGuide Enterprise 2010
+ * and MapGuide Open Source v2.0.3 or higher. The newer versions of MG
+ * is called asynchronously, allows selections to be drawn separately from
+ * the map and offers styling options.
+ *
+ * With older versions of MapGuide, set useAsyncOverlay=false. Note that in
+ * this case a synchronous AJAX call is issued and the mapname and session
+ * parameters must be used to initialize the layer, not the mapdefinition
+ * parameter. Also note that this will issue a synchronous AJAX request
+ * before the image request can be issued so the users browser may lock
+ * up if the MG Web tier does not respond in a timely fashion.
+ **/
+ useAsyncOverlay: true,
+
+ /**
+ * Constant: TILE_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs for tiled layer
+ */
+ TILE_PARAMS: {
+ operation: 'GETTILEIMAGE',
+ version: '1.2.0'
+ },
+
+ /**
+ * Constant: SINGLE_TILE_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs for untiled layer
+ */
+ SINGLE_TILE_PARAMS: {
+ operation: 'GETMAPIMAGE',
+ format: 'PNG',
+ locale: 'en',
+ clip: '1',
+ version: '1.0.0'
+ },
+
+ /**
+ * Constant: OVERLAY_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs for untiled layer
+ */
+ OVERLAY_PARAMS: {
+ operation: 'GETDYNAMICMAPOVERLAYIMAGE',
+ format: 'PNG',
+ locale: 'en',
+ clip: '1',
+ version: '2.0.0'
+ },
+
+ /**
+ * Constant: FOLDER_PARAMS
+ * {Object} Hashtable of parameter key/value pairs which describe
+ * the folder structure for tiles as configured in the mapguide
+ * serverconfig.ini section [TileServiceProperties]
+ */
+ FOLDER_PARAMS: {
+ tileColumnsPerFolder: 30,
+ tileRowsPerFolder: 30,
+ format: 'png',
+ querystring: null
+ },
+
+ /**
+ * Property: defaultSize
+ * {<OpenLayers.Size>} Tile size as produced by MapGuide server
+ **/
+ defaultSize: new OpenLayers.Size(300,300),
+
+ /**
+ * Property: tileOriginCorner
+ * {String} MapGuide tile server uses top-left as tile origin
+ **/
+ tileOriginCorner: "tl",
+
+ /**
+ * Constructor: OpenLayers.Layer.MapGuide
+ * Create a new Mapguide layer, either tiled or untiled.
+ *
+ * For tiled layers, the 'groupName' and 'mapDefinition' values
+ * must be specified as parameters in the constructor.
+ *
+ * For untiled base layers, specify either combination of 'mapName' and
+ * 'session', or 'mapDefinition' and 'locale'.
+ *
+ * For older versions of MapGuide and overlay layers, set useAsyncOverlay
+ * to false and in this case mapName and session are required parameters
+ * for the constructor.
+ *
+ * NOTE: MapGuide OS uses a DPI value and degrees to meters conversion
+ * factor that are different than the defaults used in OpenLayers,
+ * so these must be adjusted accordingly in your application.
+ * See the MapGuide example for how to set these values for MGOS.
+ *
+ * Parameters:
+ * name - {String} Name of the layer displayed in the interface
+ * url - {String} Location of the MapGuide mapagent executable
+ * (e.g. http://localhost:8008/mapguide/mapagent/mapagent.fcgi)
+ * params - {Object} hashtable of additional parameters to use. Some
+ * parameters may require additional code on the server. The ones that
+ * you may want to use are:
+ * - mapDefinition - {String} The MapGuide resource definition
+ * (e.g. Library://Samples/Gmap/Maps/gmapTiled.MapDefinition)
+ * - locale - Locale setting
+ * (for untiled overlays layers only)
+ * - mapName - {String} Name of the map as stored in the MapGuide session.
+ * (for untiled layers with a session parameter only)
+ * - session - { String} MapGuide session ID
+ * (for untiled overlays layers only)
+ * - basemaplayergroupname - {String} GroupName for tiled MapGuide layers only
+ * - format - Image format to be returned (for untiled overlay layers only)
+ * - showLayers - {String} A comma separated list of GUID's for the
+ * layers to display eg: 'cvc-xcv34,453-345-345sdf'.
+ * - hideLayers - {String} A comma separated list of GUID's for the
+ * layers to hide eg: 'cvc-xcv34,453-345-345sdf'.
+ * - showGroups - {String} A comma separated list of GUID's for the
+ * groups to display eg: 'cvc-xcv34,453-345-345sdf'.
+ * - hideGroups - {String} A comma separated list of GUID's for the
+ * groups to hide eg: 'cvc-xcv34,453-345-345sdf'
+ * - selectionXml - {String} A selection xml string Some server plumbing
+ * is required to read such a value.
+ * options - {Object} Hashtable of extra options to tag onto the layer;
+ * will vary depending if tiled or untiled maps are being requested
+ */
+ initialize: function(name, url, params, options) {
+
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
+
+ // unless explicitly set in options, if the layer is transparent,
+ // it will be an overlay
+ if (options == null || options.isBaseLayer == null) {
+ this.isBaseLayer = ((this.transparent != "true") &&
+ (this.transparent != true));
+ }
+
+ if (options && options.useOverlay!=null) {
+ this.useOverlay = options.useOverlay;
+ }
+
+ //initialize for untiled layers
+ if (this.singleTile) {
+ if (this.useOverlay) {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.OVERLAY_PARAMS
+ );
+ if (!this.useAsyncOverlay) {
+ this.params.version = "1.0.0";
+ }
+ } else {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.SINGLE_TILE_PARAMS
+ );
+ }
+ } else {
+ //initialize for tiled layers
+ if (this.useHttpTile) {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.FOLDER_PARAMS
+ );
+ } else {
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ this.TILE_PARAMS
+ );
+ }
+ this.setTileSize(this.defaultSize);
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.MapGuide>} An exact clone of this layer
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.MapGuide(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ * Return a query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox
+ * for the request
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also
+ * the passed-in bounds and appropriate tile size specified
+ * as parameters.
+ */
+ getURL: function (bounds) {
+ var url;
+ var center = bounds.getCenterLonLat();
+ var mapSize = this.map.getSize();
+
+ if (this.singleTile) {
+ //set up the call for GETMAPIMAGE or GETDYNAMICMAPOVERLAY with
+ //dynamic map parameters
+ var params = {
+ setdisplaydpi: OpenLayers.DOTS_PER_INCH,
+ setdisplayheight: mapSize.h*this.ratio,
+ setdisplaywidth: mapSize.w*this.ratio,
+ setviewcenterx: center.lon,
+ setviewcentery: center.lat,
+ setviewscale: this.map.getScale()
+ };
+
+ if (this.useOverlay && !this.useAsyncOverlay) {
+ //first we need to call GETVISIBLEMAPEXTENT to set the extent
+ var getVisParams = {};
+ getVisParams = OpenLayers.Util.extend(getVisParams, params);
+ getVisParams.operation = "GETVISIBLEMAPEXTENT";
+ getVisParams.version = "1.0.0";
+ getVisParams.session = this.params.session;
+ getVisParams.mapName = this.params.mapName;
+ getVisParams.format = 'text/xml';
+ url = this.getFullRequestString( getVisParams );
+
+ OpenLayers.Request.GET({url: url, async: false});
+ }
+ //construct the full URL
+ url = this.getFullRequestString( params );
+ } else {
+
+ //tiled version
+ var currentRes = this.map.getResolution();
+ var colidx = Math.floor((bounds.left-this.maxExtent.left)/currentRes);
+ colidx = Math.round(colidx/this.tileSize.w);
+ var rowidx = Math.floor((this.maxExtent.top-bounds.top)/currentRes);
+ rowidx = Math.round(rowidx/this.tileSize.h);
+
+ if (this.useHttpTile){
+ url = this.getImageFilePath(
+ {
+ tilecol: colidx,
+ tilerow: rowidx,
+ scaleindex: this.resolutions.length - this.map.zoom - 1
+ });
+
+ } else {
+ url = this.getFullRequestString(
+ {
+ tilecol: colidx,
+ tilerow: rowidx,
+ scaleindex: this.resolutions.length - this.map.zoom - 1
+ });
+ }
+ }
+ return url;
+ },
+
+ /**
+ * Method: getFullRequestString
+ * getFullRequestString on MapGuide layers is special, because we
+ * do a regular expression replace on ',' in parameters to '+'.
+ * This is why it is subclassed here.
+ *
+ * Parameters:
+ * altUrl - {String} Alternative base URL to use.
+ *
+ * Returns:
+ * {String} A string with the layer's url appropriately encoded for MapGuide
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ // use layer's url unless altUrl passed in
+ var url = (altUrl == null) ? this.url : altUrl;
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will randomly select one of them in order
+ // to evenly distribute requests to different urls.
+ if (typeof url == "object") {
+ url = url[Math.floor(Math.random()*url.length)];
+ }
+ // requestString always starts with url
+ var requestString = url;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ // ignore parameters that are already in the url search string
+ var urlParams = OpenLayers.Util.upperCaseObject(
+ OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ /* MapGuide needs '+' seperating things like bounds/height/width.
+ Since typically this is URL encoded, we use a slight hack: we
+ depend on the list-like functionality of getParameterString to
+ leave ',' only in the case of list items (since otherwise it is
+ encoded) then do a regular expression replace on the , characters
+ to '+' */
+ paramsString = paramsString.replace(/,/g, "+");
+
+ if (paramsString != "") {
+ var lastServerChar = url.charAt(url.length - 1);
+ if ((lastServerChar == "&") || (lastServerChar == "?")) {
+ requestString += paramsString;
+ } else {
+ if (url.indexOf('?') == -1) {
+ //serverPath has no ? -- add one
+ requestString += '?' + paramsString;
+ } else {
+ //serverPath contains ?, so must already have paramsString at the end
+ requestString += '&' + paramsString;
+ }
+ }
+ }
+ return requestString;
+ },
+
+ /**
+ * Method: getImageFilePath
+ * special handler to request mapguide tiles from an http exposed tilecache
+ *
+ * Parameters:
+ * altUrl - {String} Alternative base URL to use.
+ *
+ * Returns:
+ * {String} A string with the url for the tile image
+ */
+ getImageFilePath:function(newParams, altUrl) {
+ // use layer's url unless altUrl passed in
+ var url = (altUrl == null) ? this.url : altUrl;
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will randomly select one of them in order
+ // to evenly distribute requests to different urls.
+ if (typeof url == "object") {
+ url = url[Math.floor(Math.random()*url.length)];
+ }
+ // requestString always starts with url
+ var requestString = url;
+
+ var tileRowGroup = "";
+ var tileColGroup = "";
+
+ if (newParams.tilerow < 0) {
+ tileRowGroup = '-';
+ }
+
+ if (newParams.tilerow == 0 ) {
+ tileRowGroup += '0';
+ } else {
+ tileRowGroup += Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder)) * this.params.tileRowsPerFolder;
+ }
+
+ if (newParams.tilecol < 0) {
+ tileColGroup = '-';
+ }
+
+ if (newParams.tilecol == 0) {
+ tileColGroup += '0';
+ } else {
+ tileColGroup += Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder)) * this.params.tileColumnsPerFolder;
+ }
+
+ var tilePath = '/S' + Math.floor(newParams.scaleindex)
+ + '/' + this.params.basemaplayergroupname
+ + '/R' + tileRowGroup
+ + '/C' + tileColGroup
+ + '/' + (newParams.tilerow % this.params.tileRowsPerFolder)
+ + '_' + (newParams.tilecol % this.params.tileColumnsPerFolder)
+ + '.' + this.params.format;
+
+ if (this.params.querystring) {
+ tilePath += "?" + this.params.querystring;
+ }
+
+ requestString += tilePath;
+ return requestString;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.MapGuide"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/MapServer.js b/misc/openlayers/lib/OpenLayers/Layer/MapServer.js
new file mode 100644
index 0000000..713035f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/MapServer.js
@@ -0,0 +1,181 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MapServer
+ * Instances of OpenLayers.Layer.MapServer are used to display
+ * data from a MapServer CGI instance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.MapServer = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: {
+ mode: "map",
+ map_imagetype: "png"
+ },
+
+ /**
+ * Constructor: OpenLayers.Layer.MapServer
+ * Create a new MapServer layer object
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the MapServer CGI
+ * (e.g. http://www2.dmsolutions.ca/cgi-bin/mapserv)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, arguments);
+
+ this.params = OpenLayers.Util.applyDefaults(
+ this.params, this.DEFAULT_PARAMS
+ );
+
+ // unless explicitly set in options, if the layer is transparent,
+ // it will be an overlay
+ if (options == null || options.isBaseLayer == null) {
+ this.isBaseLayer = ((this.params.transparent != "true") &&
+ (this.params.transparent != true));
+ }
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.MapServer>} An exact clone of this layer
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.MapServer(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ * Return a query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox
+ * for the request
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also
+ * the passed-in bounds and appropriate tile size specified
+ * as parameters.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ // Make a list, so that getFullRequestString uses literal ","
+ var extent = [bounds.left, bounds. bottom, bounds.right, bounds.top];
+
+ var imageSize = this.getImageSize();
+
+ // make lists, so that literal ','s are used
+ var url = this.getFullRequestString(
+ {mapext: extent,
+ imgext: extent,
+ map_size: [imageSize.w, imageSize.h],
+ imgx: imageSize.w / 2,
+ imgy: imageSize.h / 2,
+ imgxy: [imageSize.w, imageSize.h]
+ });
+
+ return url;
+ },
+
+ /**
+ * Method: getFullRequestString
+ * combine the layer's url with its params and these newParams.
+ *
+ * Parameters:
+ * newParams - {Object} New parameters that should be added to the
+ * request string.
+ * altUrl - {String} (optional) Replace the URL in the full request
+ * string with the provided URL.
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters embedded in it.
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ // use layer's url unless altUrl passed in
+ var url = (altUrl == null) ? this.url : altUrl;
+
+ // create a new params hashtable with all the layer params and the
+ // new params together. then convert to string
+ var allParams = OpenLayers.Util.extend({}, this.params);
+ allParams = OpenLayers.Util.extend(allParams, newParams);
+ var paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // if url is not a string, it should be an array of strings,
+ // in which case we will deterministically select one of them in
+ // order to evenly distribute requests to different urls.
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(paramsString, url);
+ }
+
+ // ignore parameters that are already in the url search string
+ var urlParams = OpenLayers.Util.upperCaseObject(
+ OpenLayers.Util.getParameters(url));
+ for(var key in allParams) {
+ if(key.toUpperCase() in urlParams) {
+ delete allParams[key];
+ }
+ }
+ paramsString = OpenLayers.Util.getParameterString(allParams);
+
+ // requestString always starts with url
+ var requestString = url;
+
+ // MapServer needs '+' seperating things like bounds/height/width.
+ // Since typically this is URL encoded, we use a slight hack: we
+ // depend on the list-like functionality of getParameterString to
+ // leave ',' only in the case of list items (since otherwise it is
+ // encoded) then do a regular expression replace on the , characters
+ // to '+'
+ //
+ paramsString = paramsString.replace(/,/g, "+");
+
+ if (paramsString != "") {
+ var lastServerChar = url.charAt(url.length - 1);
+ if ((lastServerChar == "&") || (lastServerChar == "?")) {
+ requestString += paramsString;
+ } else {
+ if (url.indexOf('?') == -1) {
+ //serverPath has no ? -- add one
+ requestString += '?' + paramsString;
+ } else {
+ //serverPath contains ?, so must already have paramsString at the end
+ requestString += '&' + paramsString;
+ }
+ }
+ }
+ return requestString;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.MapServer"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Markers.js b/misc/openlayers/lib/OpenLayers/Layer/Markers.js
new file mode 100644
index 0000000..de848d4
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Markers.js
@@ -0,0 +1,187 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Markers
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Markers layer is never a base layer.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: markers
+ * {Array(<OpenLayers.Marker>)} internal marker list
+ */
+ markers: null,
+
+
+ /**
+ * Property: drawn
+ * {Boolean} internal state of drawing. This is a workaround for the fact
+ * that the map does not call moveTo with a zoomChanged when the map is
+ * first starting up. This lets us catch the case where we have *never*
+ * drawn the layer, and draw it even if the zoom hasn't changed.
+ */
+ drawn: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.Markers
+ * Create a Markers layer.
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ this.markers = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.clearMarkers();
+ this.markers = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Sets the opacity for all the markers.
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function(opacity) {
+ if (opacity != this.opacity) {
+ this.opacity = opacity;
+ for (var i=0, len=this.markers.length; i<len; i++) {
+ this.markers[i].setOpacity(this.opacity);
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ if (zoomChanged || !this.drawn) {
+ for(var i=0, len=this.markers.length; i<len; i++) {
+ this.drawMarker(this.markers[i]);
+ }
+ this.drawn = true;
+ }
+ },
+
+ /**
+ * APIMethod: addMarker
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker>}
+ */
+ addMarker: function(marker) {
+ this.markers.push(marker);
+
+ if (this.opacity < 1) {
+ marker.setOpacity(this.opacity);
+ }
+
+ if (this.map && this.map.getExtent()) {
+ marker.map = this.map;
+ this.drawMarker(marker);
+ }
+ },
+
+ /**
+ * APIMethod: removeMarker
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker>}
+ */
+ removeMarker: function(marker) {
+ if (this.markers && this.markers.length) {
+ OpenLayers.Util.removeItem(this.markers, marker);
+ marker.erase();
+ }
+ },
+
+ /**
+ * Method: clearMarkers
+ * This method removes all markers from a layer. The markers are not
+ * destroyed by this function, but are removed from the list of markers.
+ */
+ clearMarkers: function() {
+ if (this.markers != null) {
+ while(this.markers.length > 0) {
+ this.removeMarker(this.markers[0]);
+ }
+ }
+ },
+
+ /**
+ * Method: drawMarker
+ * Calculate the pixel location for the marker, create it, and
+ * add it to the layer's div
+ *
+ * Parameters:
+ * marker - {<OpenLayers.Marker>}
+ */
+ drawMarker: function(marker) {
+ var px = this.map.getLayerPxFromLonLat(marker.lonlat);
+ if (px == null) {
+ marker.display(false);
+ } else {
+ if (!marker.isDrawn()) {
+ var markerImg = marker.draw(px);
+ this.div.appendChild(markerImg);
+ } else if(marker.icon) {
+ marker.icon.moveTo(px);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the markers.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+
+ if ( this.markers && (this.markers.length > 0)) {
+ var maxExtent = new OpenLayers.Bounds();
+ for(var i=0, len=this.markers.length; i<len; i++) {
+ var marker = this.markers[i];
+ maxExtent.extend(marker.lonlat);
+ }
+ }
+
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Markers"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/OSM.js b/misc/openlayers/lib/OpenLayers/Layer/OSM.js
new file mode 100644
index 0000000..49150fd
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/OSM.js
@@ -0,0 +1,123 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.OSM
+ * This layer allows accessing OpenStreetMap tiles. By default the OpenStreetMap
+ * hosted tile.openstreetmap.org Mapnik tileset is used. If you wish to use
+ * a different layer instead, you need to provide a different
+ * URL to the constructor. Here's an example for using OpenCycleMap:
+ *
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: name
+ * {String} The layer name. Defaults to "OpenStreetMap" if the first
+ * argument to the constructor is null or undefined.
+ */
+ name: "OpenStreetMap",
+
+ /**
+ * APIProperty: url
+ * {String} The tileset URL scheme. Defaults to
+ * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png
+ * (the official OSM tileset) if the second argument to the constructor
+ * is null or undefined. To use another tileset you can have something
+ * like this:
+ * (code)
+ * new OpenLayers.Layer.OSM("OpenCycleMap",
+ * ["http://a.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://b.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png",
+ * "http://c.tile.opencyclemap.org/cycle/${z}/${x}/${y}.png"]);
+ * (end)
+ */
+ url: [
+ 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png',
+ 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png'
+ ],
+
+ /**
+ * Property: attribution
+ * {String} The layer attribution.
+ */
+ attribution: "&copy; <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> contributors",
+
+ /**
+ * Property: sphericalMercator
+ * {Boolean}
+ */
+ sphericalMercator: true,
+
+ /**
+ * Property: wrapDateLine
+ * {Boolean}
+ */
+ wrapDateLine: true,
+
+ /** APIProperty: tileOptions
+ * {Object} optional configuration options for <OpenLayers.Tile> instances
+ * created by this Layer. Default is
+ *
+ * (code)
+ * {crossOriginKeyword: 'anonymous'}
+ * (end)
+ *
+ * When using OSM tilesets other than the default ones, it may be
+ * necessary to set this to
+ *
+ * (code)
+ * {crossOriginKeyword: null}
+ * (end)
+ *
+ * if the server does not send Access-Control-Allow-Origin headers.
+ */
+ tileOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.OSM
+ *
+ * Parameters:
+ * name - {String} The layer name.
+ * url - {String} The tileset URL scheme.
+ * options - {Object} Configuration options for the layer. Any inherited
+ * layer option can be set in this object (e.g.
+ * <OpenLayers.Layer.Grid.buffer>).
+ */
+ initialize: function(name, url, options) {
+ OpenLayers.Layer.XYZ.prototype.initialize.apply(this, arguments);
+ this.tileOptions = OpenLayers.Util.extend({
+ crossOriginKeyword: 'anonymous'
+ }, this.options && this.options.tileOptions);
+ },
+
+ /**
+ * Method: clone
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.OSM(
+ this.name, this.url, this.getOptions());
+ }
+ obj = OpenLayers.Layer.XYZ.prototype.clone.apply(this, [obj]);
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.OSM"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/PointGrid.js b/misc/openlayers/lib/OpenLayers/Layer/PointGrid.js
new file mode 100644
index 0000000..10a4d02
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/PointGrid.js
@@ -0,0 +1,299 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.PointGrid
+ * A point grid layer dynamically generates a regularly spaced grid of point
+ * features. This is a specialty layer for cases where an application needs
+ * a regular grid of points. It can be used, for example, in an editing
+ * environment to snap to a grid.
+ *
+ * Create a new vector layer with the <OpenLayers.Layer.PointGrid> constructor.
+ * (code)
+ * // create a grid with points spaced at 10 map units
+ * var points = new OpenLayers.Layer.PointGrid({dx: 10, dy: 10});
+ *
+ * // create a grid with different x/y spacing rotated 15 degrees clockwise.
+ * var points = new OpenLayers.Layer.PointGrid({dx: 5, dy: 10, rotation: 15});
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.PointGrid = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * APIProperty: dx
+ * {Number} Point grid spacing in the x-axis direction (map units).
+ * Read-only. Use the <setSpacing> method to modify this value.
+ */
+ dx: null,
+
+ /**
+ * APIProperty: dy
+ * {Number} Point grid spacing in the y-axis direction (map units).
+ * Read-only. Use the <setSpacing> method to modify this value.
+ */
+ dy: null,
+
+ /**
+ * APIProperty: ratio
+ * {Number} Ratio of the desired grid size to the map viewport size.
+ * Default is 1.5. Larger ratios mean the grid is recalculated less often
+ * while panning. The <maxFeatures> setting has precedence when determining
+ * grid size. Read-only. Use the <setRatio> method to modify this value.
+ */
+ ratio: 1.5,
+
+ /**
+ * APIProperty: maxFeatures
+ * {Number} The maximum number of points to generate in the grid. Default
+ * is 250. Read-only. Use the <setMaxFeatures> method to modify this value.
+ */
+ maxFeatures: 250,
+
+ /**
+ * APIProperty: rotation
+ * {Number} Grid rotation (in degrees clockwise from the positive x-axis).
+ * Default is 0. Read-only. Use the <setRotation> method to modify this
+ * value.
+ */
+ rotation: 0,
+
+ /**
+ * APIProperty: origin
+ * {<OpenLayers.LonLat>} Grid origin. The grid lattice will be aligned with
+ * the origin. If not set at construction, the center of the map's maximum
+ * extent is used. Read-only. Use the <setOrigin> method to modify this
+ * value.
+ */
+ origin: null,
+
+ /**
+ * Property: gridBounds
+ * {<OpenLayers.Bounds>} Internally cached grid bounds (with optional
+ * rotation applied).
+ */
+ gridBounds: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.PointGrid
+ * Creates a new point grid layer.
+ *
+ * Parameters:
+ * config - {Object} An object containing all configuration properties for
+ * the layer. The <dx> and <dy> properties are required to be set at
+ * construction. Any other layer properties may be set in this object.
+ */
+ initialize: function(config) {
+ config = config || {};
+ OpenLayers.Layer.Vector.prototype.initialize.apply(this, [config.name, config]);
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ map.events.register("moveend", this, this.onMoveEnd);
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("moveend", this, this.onMoveEnd);
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setRatio
+ * Set the grid <ratio> property and update the grid. Can only be called
+ * after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * ratio - {Number}
+ */
+ setRatio: function(ratio) {
+ this.ratio = ratio;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: setMaxFeatures
+ * Set the grid <maxFeatures> property and update the grid. Can only be
+ * called after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * maxFeatures - {Number}
+ */
+ setMaxFeatures: function(maxFeatures) {
+ this.maxFeatures = maxFeatures;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: setSpacing
+ * Set the grid <dx> and <dy> properties and update the grid. If only one
+ * argument is provided, it will be set as <dx> and <dy>. Can only be
+ * called after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ setSpacing: function(dx, dy) {
+ this.dx = dx;
+ this.dy = dy || dx;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: setOrigin
+ * Set the grid <origin> property and update the grid. Can only be called
+ * after the layer has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.LonLat>}
+ */
+ setOrigin: function(origin) {
+ this.origin = origin;
+ this.updateGrid(true);
+ },
+
+ /**
+ * APIMethod: getOrigin
+ * Get the grid <origin> property.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The grid origin.
+ */
+ getOrigin: function() {
+ if (!this.origin) {
+ this.origin = this.map.getExtent().getCenterLonLat();
+ }
+ return this.origin;
+ },
+
+ /**
+ * APIMethod: setRotation
+ * Set the grid <rotation> property and update the grid. Rotation values
+ * are in degrees clockwise from the positive x-axis (negative values
+ * for counter-clockwise rotation). Can only be called after the layer
+ * has been added to a map with a center/extent.
+ *
+ * Parameters:
+ * rotation - {Number} Degrees clockwise from the positive x-axis.
+ */
+ setRotation: function(rotation) {
+ this.rotation = rotation;
+ this.updateGrid(true);
+ },
+
+ /**
+ * Method: onMoveEnd
+ * Listener for map "moveend" events.
+ */
+ onMoveEnd: function() {
+ this.updateGrid();
+ },
+
+ /**
+ * Method: getViewBounds
+ * Gets the (potentially rotated) view bounds for grid calculations.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getViewBounds: function() {
+ var bounds = this.map.getExtent();
+ if (this.rotation) {
+ var origin = this.getOrigin();
+ var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat);
+ var rect = bounds.toGeometry();
+ rect.rotate(-this.rotation, rotationOrigin);
+ bounds = rect.getBounds();
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: updateGrid
+ * Update the grid.
+ *
+ * Parameters:
+ * force - {Boolean} Update the grid even if the previous bounds are still
+ * valid.
+ */
+ updateGrid: function(force) {
+ if (force || this.invalidBounds()) {
+ var viewBounds = this.getViewBounds();
+ var origin = this.getOrigin();
+ var rotationOrigin = new OpenLayers.Geometry.Point(origin.lon, origin.lat);
+ var viewBoundsWidth = viewBounds.getWidth();
+ var viewBoundsHeight = viewBounds.getHeight();
+ var aspectRatio = viewBoundsWidth / viewBoundsHeight;
+ var maxHeight = Math.sqrt(this.dx * this.dy * this.maxFeatures / aspectRatio);
+ var maxWidth = maxHeight * aspectRatio;
+ var gridWidth = Math.min(viewBoundsWidth * this.ratio, maxWidth);
+ var gridHeight = Math.min(viewBoundsHeight * this.ratio, maxHeight);
+ var center = viewBounds.getCenterLonLat();
+ this.gridBounds = new OpenLayers.Bounds(
+ center.lon - (gridWidth / 2),
+ center.lat - (gridHeight / 2),
+ center.lon + (gridWidth / 2),
+ center.lat + (gridHeight / 2)
+ );
+ var rows = Math.floor(gridHeight / this.dy);
+ var cols = Math.floor(gridWidth / this.dx);
+ var gridLeft = origin.lon + (this.dx * Math.ceil((this.gridBounds.left - origin.lon) / this.dx));
+ var gridBottom = origin.lat + (this.dy * Math.ceil((this.gridBounds.bottom - origin.lat) / this.dy));
+ var features = new Array(rows * cols);
+ var x, y, point;
+ for (var i=0; i<cols; ++i) {
+ x = gridLeft + (i * this.dx);
+ for (var j=0; j<rows; ++j) {
+ y = gridBottom + (j * this.dy);
+ point = new OpenLayers.Geometry.Point(x, y);
+ if (this.rotation) {
+ point.rotate(this.rotation, rotationOrigin);
+ }
+ features[(i*rows)+j] = new OpenLayers.Feature.Vector(point);
+ }
+ }
+ this.destroyFeatures(this.features, {silent: true});
+ this.addFeatures(features, {silent: true});
+ }
+ },
+
+ /**
+ * Method: invalidBounds
+ * Determine whether the previously generated point grid is invalid.
+ * This occurs when the map bounds extends beyond the previously
+ * generated grid bounds.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ invalidBounds: function() {
+ return !this.gridBounds || !this.gridBounds.containsBounds(this.getViewBounds());
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.PointGrid"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/PointTrack.js b/misc/openlayers/lib/OpenLayers/Layer/PointTrack.js
new file mode 100644
index 0000000..accfac7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/PointTrack.js
@@ -0,0 +1,125 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.PointTrack
+ * Vector layer to display ordered point features as a line, creating one
+ * LineString feature for each pair of two points.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.PointTrack = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * APIProperty: dataFrom
+ * {<OpenLayers.Layer.PointTrack.TARGET_NODE>} or
+ * {<OpenLayers.Layer.PointTrack.SOURCE_NODE>} optional. If the lines
+ * should get the data/attributes from one of the two points it is
+ * composed of, which one should it be?
+ */
+ dataFrom: null,
+
+ /**
+ * APIProperty: styleFrom
+ * {<OpenLayers.Layer.PointTrack.TARGET_NODE>} or
+ * {<OpenLayers.Layer.PointTrack.SOURCE_NODE>} optional. If the lines
+ * should get the style from one of the two points it is composed of,
+ * which one should it be?
+ */
+ styleFrom: null,
+
+ /**
+ * Constructor: OpenLayers.PointTrack
+ * Constructor for a new OpenLayers.PointTrack instance.
+ *
+ * Parameters:
+ * name - {String} name of the layer
+ * options - {Object} Optional object with properties to tag onto the
+ * instance.
+ */
+
+ /**
+ * APIMethod: addNodes
+ * Adds point features that will be used to create lines from, using point
+ * pairs. The first point of a pair will be the source node, the second
+ * will be the target node.
+ *
+ * Parameters:
+ * pointFeatures - {Array(<OpenLayers.Feature>)}
+ * options - {Object}
+ *
+ * Supported options:
+ * silent - {Boolean} true to suppress (before)feature(s)added events
+ */
+ addNodes: function(pointFeatures, options) {
+ if (pointFeatures.length < 2) {
+ throw new Error("At least two point features have to be added to " +
+ "create a line from");
+ }
+
+ var lines = new Array(pointFeatures.length-1);
+
+ var pointFeature, startPoint, endPoint;
+ for(var i=0, len=pointFeatures.length; i<len; i++) {
+ pointFeature = pointFeatures[i];
+ endPoint = pointFeature.geometry;
+
+ if (!endPoint) {
+ var lonlat = pointFeature.lonlat;
+ endPoint = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
+ } else if(endPoint.CLASS_NAME != "OpenLayers.Geometry.Point") {
+ throw new TypeError("Only features with point geometries are supported.");
+ }
+
+ if(i > 0) {
+ var attributes = (this.dataFrom != null) ?
+ (pointFeatures[i+this.dataFrom].data ||
+ pointFeatures[i+this.dataFrom].attributes) :
+ null;
+ var style = (this.styleFrom != null) ?
+ (pointFeatures[i+this.styleFrom].style) :
+ null;
+ var line = new OpenLayers.Geometry.LineString([startPoint,
+ endPoint]);
+
+ lines[i-1] = new OpenLayers.Feature.Vector(line, attributes,
+ style);
+ }
+
+ startPoint = endPoint;
+ }
+
+ this.addFeatures(lines, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.PointTrack"
+});
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.SOURCE_NODE
+ * {Number} value for <OpenLayers.Layer.PointTrack.dataFrom> and
+ * <OpenLayers.Layer.PointTrack.styleFrom>
+ */
+OpenLayers.Layer.PointTrack.SOURCE_NODE = -1;
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.TARGET_NODE
+ * {Number} value for <OpenLayers.Layer.PointTrack.dataFrom> and
+ * <OpenLayers.Layer.PointTrack.styleFrom>
+ */
+OpenLayers.Layer.PointTrack.TARGET_NODE = 0;
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.dataFrom
+ * {Object} with the following keys - *deprecated*
+ * - SOURCE_NODE: take data/attributes from the source node of the line
+ * - TARGET_NODE: take data/attributes from the target node of the line
+ */
+OpenLayers.Layer.PointTrack.dataFrom = {'SOURCE_NODE': -1, 'TARGET_NODE': 0};
diff --git a/misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js b/misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js
new file mode 100644
index 0000000..60bb2fe
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/SphericalMercator.js
@@ -0,0 +1,146 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.SphericalMercator
+ * A mixin for layers that wraps up the pieces neccesary to have a coordinate
+ * conversion for working with commercial APIs which use a spherical
+ * mercator projection. Using this layer as a base layer, additional
+ * layers can be used as overlays if they are in the same projection.
+ *
+ * A layer is given properties of this object by setting the sphericalMercator
+ * property to true.
+ *
+ * More projection information:
+ * - http://spatialreference.org/ref/user/google-projection/
+ *
+ * Proj4 Text:
+ * +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
+ * +k=1.0 +units=m +nadgrids=@null +no_defs
+ *
+ * WKT:
+ * 900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
+ * DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]],
+ * PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295],
+ * AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
+ * PROJECTION["Mercator_1SP_Google"],
+ * PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0],
+ * PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0],
+ * PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
+ * AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]
+ */
+OpenLayers.Layer.SphericalMercator = {
+
+ /**
+ * Method: getExtent
+ * Get the map's extent.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The map extent.
+ */
+ getExtent: function() {
+ var extent = null;
+ if (this.sphericalMercator) {
+ extent = this.map.calculateBounds();
+ } else {
+ extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);
+ }
+ return extent;
+ },
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ * Get a map location from a pixel location
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port OpenLayers.Pixel, translated into lon/lat by map lib
+ * If the map lib is not loaded or not centered, returns null
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ return OpenLayers.Layer.prototype.getLonLatFromViewPortPx.apply(this, arguments);
+ },
+
+ /**
+ * Method: getViewPortPxFromLonLat
+ * Get a pixel location from a map location
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * OpenLayers.LonLat, translated into view port pixels by map lib
+ * If map lib is not loaded or not centered, returns null
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ return OpenLayers.Layer.prototype.getViewPortPxFromLonLat.apply(this, arguments);
+ },
+
+ /**
+ * Method: initMercatorParameters
+ * Set up the mercator parameters on the layer: resolutions,
+ * projection, units.
+ */
+ initMercatorParameters: function() {
+ // set up properties for Mercator - assume EPSG:900913
+ this.RESOLUTIONS = [];
+ var maxResolution = 156543.03390625;
+ for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
+ this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
+ }
+ this.units = "m";
+ this.projection = this.projection || "EPSG:900913";
+ },
+
+ /**
+ * APIMethod: forwardMercator
+ * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator.
+ *
+ * Parameters:
+ * lon - {float}
+ * lat - {float}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The coordinates transformed to Mercator.
+ */
+ forwardMercator: (function() {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ return function(lon, lat) {
+ var point = OpenLayers.Projection.transform({x: lon, y: lat}, gg, sm);
+ return new OpenLayers.LonLat(point.x, point.y);
+ };
+ })(),
+
+ /**
+ * APIMethod: inverseMercator
+ * Given a x,y in Spherical Mercator, return a point in EPSG:4326.
+ *
+ * Parameters:
+ * x - {float} A map x in Spherical Mercator.
+ * y - {float} A map y in Spherical Mercator.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326.
+ */
+ inverseMercator: (function() {
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ return function(x, y) {
+ var point = OpenLayers.Projection.transform({x: x, y: y}, sm, gg);
+ return new OpenLayers.LonLat(point.x, point.y);
+ };
+ })()
+
+};
diff --git a/misc/openlayers/lib/OpenLayers/Layer/TMS.js b/misc/openlayers/lib/OpenLayers/Layer/TMS.js
new file mode 100644
index 0000000..ab76847
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/TMS.js
@@ -0,0 +1,202 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TMS
+ * Create a layer for accessing tiles from services that conform with the
+ * Tile Map Service Specification
+ * (http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification).
+ *
+ * Example:
+ * (code)
+ * var layer = new OpenLayers.Layer.TMS(
+ * "My Layer", // name for display in LayerSwitcher
+ * "http://tilecache.osgeo.org/wms-c/Basic.py/", // service endpoint
+ * {layername: "basic", type: "png"} // required properties
+ * );
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: serviceVersion
+ * {String} Service version for tile requests. Default is "1.0.0".
+ */
+ serviceVersion: "1.0.0",
+
+ /**
+ * APIProperty: layername
+ * {String} The identifier for the <TileMap> as advertised by the service.
+ * For example, if the service advertises a <TileMap> with
+ * 'href="http://tms.osgeo.org/1.0.0/vmap0"', the <layername> property
+ * would be set to "vmap0".
+ */
+ layername: null,
+
+ /**
+ * APIProperty: type
+ * {String} The format extension corresponding to the requested tile image
+ * type. This is advertised in a <TileFormat> element as the
+ * "extension" attribute. For example, if the service advertises a
+ * <TileMap> with <TileFormat width="256" height="256" mime-type="image/jpeg" extension="jpg" />,
+ * the <type> property would be set to "jpg".
+ */
+ type: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Make this layer a base layer. Default is true. Set false to
+ * use the layer as an overlay.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} Optional origin for aligning the grid of tiles.
+ * If provided, requests for tiles at all resolutions will be aligned
+ * with this location (no tiles shall overlap this location). If
+ * not provided, the grid of tiles will be aligned with the bottom-left
+ * corner of the map's <maxExtent>. Default is ``null``.
+ *
+ * Example:
+ * (code)
+ * var layer = new OpenLayers.Layer.TMS(
+ * "My Layer",
+ * "http://tilecache.osgeo.org/wms-c/Basic.py/",
+ * {
+ * layername: "basic",
+ * type: "png",
+ * // set if different than the bottom left of map.maxExtent
+ * tileOrigin: new OpenLayers.LonLat(-180, -90)
+ * }
+ * );
+ * (end)
+ */
+ tileOrigin: null,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more zoom levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Using <zoomOffset> is an alternative to
+ * setting <serverResolutions> if you only want to expose a subset
+ * of the server resolutions.
+ */
+ zoomOffset: 0,
+
+ /**
+ * Constructor: OpenLayers.Layer.TMS
+ *
+ * Parameters:
+ * name - {String} Title to be displayed in a <OpenLayers.Control.LayerSwitcher>
+ * url - {String} Service endpoint (without the version number). E.g.
+ * "http://tms.osgeo.org/".
+ * options - {Object} Additional properties to be set on the layer. The
+ * <layername> and <type> properties must be set here.
+ */
+ initialize: function(name, url, options) {
+ var newArguments = [];
+ newArguments.push(name, url, {}, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a complete copy of this layer.
+ *
+ * Parameters:
+ * obj - {Object} Should only be provided by subclasses that call this
+ * method.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.TMS>} An exact clone of this <OpenLayers.Layer.TMS>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.TMS(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+ var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
+ var z = this.getServerZoom();
+ var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type;
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(path, url);
+ }
+ return url + path;
+ },
+
+ /**
+ * Method: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+ this.map.maxExtent.bottom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.TMS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Text.js b/misc/openlayers/lib/OpenLayers/Layer/Text.js
new file mode 100644
index 0000000..4a4c9e3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Text.js
@@ -0,0 +1,267 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Format/Text.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Text
+ * This layer creates markers given data in a text file. The <location>
+ * property of the layer (specified as a property of the options argument
+ * in the <OpenLayers.Layer.Text> constructor) points to a tab delimited
+ * file with data used to create markers.
+ *
+ * The first row of the data file should be a header line with the column names
+ * of the data. Each column should be delimited by a tab space. The
+ * possible columns are:
+ * - *point* lat,lon of the point where a marker is to be placed
+ * - *lat* Latitude of the point where a marker is to be placed
+ * - *lon* Longitude of the point where a marker is to be placed
+ * - *icon* or *image* URL of marker icon to use.
+ * - *iconSize* Size of Icon to use.
+ * - *iconOffset* Where the top-left corner of the icon is to be placed
+ * relative to the latitude and longitude of the point.
+ * - *title* The text of the 'title' is placed inside an 'h2' marker
+ * inside a popup, which opens when the marker is clicked.
+ * - *description* The text of the 'description' is placed below the h2
+ * in the popup. this can be plain text or HTML.
+ *
+ * Example text file:
+ * (code)
+ * lat lon title description iconSize iconOffset icon
+ * 10 20 title description 21,25 -10,-25 http://www.openlayers.org/dev/img/marker.png
+ * (end)
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Text = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+ /**
+ * APIProperty: location
+ * {String} URL of text file. Must be specified in the "options" argument
+ * of the constructor. Can not be changed once passed in.
+ */
+ location:null,
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature>)}
+ */
+ features: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Hash of options which should be passed to the format when it is
+ * created. Must be passed in the constructor.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: selectedFeature
+ * {<OpenLayers.Feature>}
+ */
+ selectedFeature: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Text
+ * Create a text layer.
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object} Object with properties to be set on the layer.
+ * Must include <location> property.
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
+ this.features = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ // Warning: Layer.Markers.destroy() must be called prior to calling
+ // clearFeatures() here, otherwise we leak memory. Indeed, if
+ // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+ // able to remove the marker image elements from the layer's div since
+ // the markers will have been destroyed by clearFeatures().
+ OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+ this.clearFeatures();
+ this.features = null;
+ },
+
+ /**
+ * Method: loadText
+ * Start the load of the Text data. Don't do this when we first add the layer,
+ * since we may not be visible at any point, and it would therefore be a waste.
+ */
+ loadText: function() {
+ if (!this.loaded) {
+ if (this.location != null) {
+
+ var onFail = function(e) {
+ this.events.triggerEvent("loadend");
+ };
+
+ this.events.triggerEvent("loadstart");
+ OpenLayers.Request.GET({
+ url: this.location,
+ success: this.parseData,
+ failure: onFail,
+ scope: this
+ });
+ this.loaded = true;
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * If layer is visible and Text has not been loaded, load Text.
+ *
+ * Parameters:
+ * bounds - {Object}
+ * zoomChanged - {Object}
+ * minor - {Object}
+ */
+ moveTo:function(bounds, zoomChanged, minor) {
+ OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+ if(this.visibility && !this.loaded){
+ this.loadText();
+ }
+ },
+
+ /**
+ * Method: parseData
+ *
+ * Parameters:
+ * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>}
+ */
+ parseData: function(ajaxRequest) {
+ var text = ajaxRequest.responseText;
+
+ var options = {};
+
+ OpenLayers.Util.extend(options, this.formatOptions);
+
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ var parser = new OpenLayers.Format.Text(options);
+ var features = parser.read(text);
+ for (var i=0, len=features.length; i<len; i++) {
+ var data = {};
+ var feature = features[i];
+ var location;
+ var iconSize, iconOffset;
+
+ location = new OpenLayers.LonLat(feature.geometry.x,
+ feature.geometry.y);
+
+ if (feature.style.graphicWidth
+ && feature.style.graphicHeight) {
+ iconSize = new OpenLayers.Size(
+ feature.style.graphicWidth,
+ feature.style.graphicHeight);
+ }
+
+ // FIXME: At the moment, we only use this if we have an
+ // externalGraphic, because icon has no setOffset API Method.
+ /**
+ * FIXME FIRST!!
+ * The Text format does all sorts of parseFloating
+ * The result of a parseFloat for a bogus string is NaN. That
+ * means the three possible values here are undefined, NaN, or a
+ * number. The previous check was an identity check for null. This
+ * means it was failing for all undefined or NaN. A slightly better
+ * check is for undefined. An even better check is to see if the
+ * value is a number (see #1441).
+ */
+ if (feature.style.graphicXOffset !== undefined
+ && feature.style.graphicYOffset !== undefined) {
+ iconOffset = new OpenLayers.Pixel(
+ feature.style.graphicXOffset,
+ feature.style.graphicYOffset);
+ }
+
+ if (feature.style.externalGraphic != null) {
+ data.icon = new OpenLayers.Icon(feature.style.externalGraphic,
+ iconSize,
+ iconOffset);
+ } else {
+ data.icon = OpenLayers.Marker.defaultIcon();
+
+ //allows for the case where the image url is not
+ // specified but the size is. use a default icon
+ // but change the size
+ if (iconSize != null) {
+ data.icon.setSize(iconSize);
+ }
+ }
+
+ if ((feature.attributes.title != null)
+ && (feature.attributes.description != null)) {
+ data['popupContentHTML'] =
+ '<h2>'+feature.attributes.title+'</h2>' +
+ '<p>'+feature.attributes.description+'</p>';
+ }
+
+ data['overflow'] = feature.attributes.overflow || "auto";
+
+ var markerFeature = new OpenLayers.Feature(this, location, data);
+ this.features.push(markerFeature);
+ var marker = markerFeature.createMarker();
+ if ((feature.attributes.title != null)
+ && (feature.attributes.description != null)) {
+ marker.events.register('click', markerFeature, this.markerClick);
+ }
+ this.addMarker(marker);
+ }
+ this.events.triggerEvent("loadend");
+ },
+
+ /**
+ * Property: markerClick
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Context:
+ * - {<OpenLayers.Feature>}
+ */
+ markerClick: function(evt) {
+ var sameMarkerClicked = (this == this.layer.selectedFeature);
+ this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+ for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+ this.layer.map.removePopup(this.layer.map.popups[i]);
+ }
+ if (!sameMarkerClicked) {
+ this.layer.map.addPopup(this.createPopup());
+ }
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: clearFeatures
+ */
+ clearFeatures: function() {
+ if (this.features != null) {
+ while(this.features.length > 0) {
+ var feature = this.features[0];
+ OpenLayers.Util.removeItem(this.features, feature);
+ feature.destroy();
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Text"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/TileCache.js b/misc/openlayers/lib/OpenLayers/Layer/TileCache.js
new file mode 100644
index 0000000..5b336be
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/TileCache.js
@@ -0,0 +1,140 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TileCache
+ * A read only TileCache layer. Used to requests tiles cached by TileCache in
+ * a web accessible cache. This means that you have to pre-populate your
+ * cache before this layer can be used. It is meant only to read tiles
+ * created by TileCache, and not to make calls to TileCache for tile
+ * creation. Create a new instance with the
+ * <OpenLayers.Layer.TileCache> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Treat this layer as a base layer. Default is true.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: format
+ * {String} Mime type of the images returned. Default is image/png.
+ */
+ format: 'image/png',
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer. (b) The map can work with resolutions
+ * that aren't supported by the server, i.e. that aren't in
+ * <serverResolutions>. When the map is displayed in such a resolution
+ * data for the closest server-supported resolution is loaded and the
+ * layer div is stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.TileCache
+ * Create a new read only TileCache layer.
+ *
+ * Parameters:
+ * name - {String} Name of the layer displayed in the interface
+ * url - {String} Location of the web accessible cache (not the location of
+ * your tilecache script!)
+ * layername - {String} Layer name as defined in the TileCache
+ * configuration
+ * options - {Object} Optional object with properties to be set on the
+ * layer. Note that you should speficy your resolutions to match
+ * your TileCache configuration. This can be done by setting
+ * the resolutions array directly (here or on the map), by setting
+ * maxResolution and numZoomLevels, or by using scale based properties.
+ */
+ initialize: function(name, url, layername, options) {
+ this.layername = layername;
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this,
+ [name, url, {}, options]);
+ this.extension = this.format.split('/')[1].toLowerCase();
+ this.extension = (this.extension == 'jpg') ? 'jpeg' : this.extension;
+ },
+
+ /**
+ * APIMethod: clone
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.TileCache>} An exact clone of this
+ * <OpenLayers.Layer.TileCache>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.TileCache(this.name,
+ this.url,
+ this.layername,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as parameters.
+ */
+ getURL: function(bounds) {
+ var res = this.getServerResolution();
+ var bbox = this.maxExtent;
+ var size = this.tileSize;
+ var tileX = Math.round((bounds.left - bbox.left) / (res * size.w));
+ var tileY = Math.round((bounds.bottom - bbox.bottom) / (res * size.h));
+ var tileZ = this.serverResolutions != null ?
+ OpenLayers.Util.indexOf(this.serverResolutions, res) :
+ this.map.getZoom();
+
+ var components = [
+ this.layername,
+ OpenLayers.Number.zeroPad(tileZ, 2),
+ OpenLayers.Number.zeroPad(parseInt(tileX / 1000000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileX / 1000) % 1000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileX) % 1000), 3),
+ OpenLayers.Number.zeroPad(parseInt(tileY / 1000000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileY / 1000) % 1000), 3),
+ OpenLayers.Number.zeroPad((parseInt(tileY) % 1000), 3) + '.' + this.extension
+ ];
+ var path = components.join('/');
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(path, url);
+ }
+ url = (url.charAt(url.length - 1) == '/') ? url : url + '/';
+ return url + path;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.TileCache"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js b/misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js
new file mode 100644
index 0000000..878cb4b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/UTFGrid.js
@@ -0,0 +1,184 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/XYZ.js
+ * @requires OpenLayers/Tile/UTFGrid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.UTFGrid
+ * This Layer reads from UTFGrid tiled data sources. Since UTFGrids are
+ * essentially JSON-based ASCII art with attached attributes, they are not
+ * visibly rendered. In order to use them in the map, you must add a
+ * <OpenLayers.Control.UTFGrid> control as well.
+ *
+ * Example:
+ *
+ * (start code)
+ * var world_utfgrid = new OpenLayers.Layer.UTFGrid({
+ * url: "/tiles/world_utfgrid/${z}/${x}/${y}.json",
+ * utfgridResolution: 4,
+ * displayInLayerSwitcher: false
+ * );
+ * map.addLayer(world_utfgrid);
+ *
+ * var control = new OpenLayers.Control.UTFGrid({
+ * layers: [world_utfgrid],
+ * handlerMode: 'move',
+ * callback: function(dataLookup) {
+ * // do something with returned data
+ * }
+ * })
+ * (end code)
+ *
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.XYZ>
+ */
+OpenLayers.Layer.UTFGrid = OpenLayers.Class(OpenLayers.Layer.XYZ, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * Default is false, as UTFGrids are designed to be a transparent overlay layer.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: projection
+ * {<OpenLayers.Projection>}
+ * Source projection for the UTFGrids. Default is "EPSG:900913".
+ */
+ projection: new OpenLayers.Projection("EPSG:900913"),
+
+ /**
+ * Property: useJSONP
+ * {Boolean}
+ * Should we use a JSONP script approach instead of a standard AJAX call?
+ *
+ * Set to true for using utfgrids from another server.
+ * Avoids same-domain policy restrictions.
+ * Note that this only works if the server accepts
+ * the callback GET parameter and dynamically
+ * wraps the returned json in a function call.
+ *
+ * Default is false
+ */
+ useJSONP: false,
+
+ /**
+ * APIProperty: url
+ * {String}
+ * URL tempate for UTFGrid tiles. Include x, y, and z parameters.
+ * E.g. "/tiles/${z}/${x}/${y}.json"
+ */
+
+ /**
+ * APIProperty: utfgridResolution
+ * {Number}
+ * Ratio of the pixel width to the width of a UTFGrid data point. If an
+ * entry in the grid represents a 4x4 block of pixels, the
+ * utfgridResolution would be 4. Default is 2 (specified in
+ * <OpenLayers.Tile.UTFGrid>).
+ */
+
+ /**
+ * Property: tileClass
+ * {<OpenLayers.Tile>} The tile class to use for this layer.
+ * Defaults is <OpenLayers.Tile.UTFGrid>.
+ */
+ tileClass: OpenLayers.Tile.UTFGrid,
+
+ /**
+ * Constructor: OpenLayers.Layer.UTFGrid
+ * Create a new UTFGrid layer.
+ *
+ * Parameters:
+ * config - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * url - {String} The url template for UTFGrid tiles. See the <url> property.
+ */
+ initialize: function(options) {
+ OpenLayers.Layer.Grid.prototype.initialize.apply(
+ this, [options.name, options.url, {}, options]
+ );
+ this.tileOptions = OpenLayers.Util.extend({
+ utfgridResolution: this.utfgridResolution
+ }, this.tileOptions);
+ },
+
+ /**
+ * Method: createBackBuffer
+ * The UTFGrid cannot create a back buffer, so this method is overriden.
+ */
+ createBackBuffer: function() {},
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Only used by a subclass of this layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.UTFGrid>} An exact clone of this OpenLayers.Layer.UTFGrid
+ */
+ clone: function (obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.UTFGrid(this.getOptions());
+ }
+
+ // get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * APIProperty: getFeatureInfo
+ * Get details about a feature associated with a map location. The object
+ * returned will have id and data properties. If the given location
+ * doesn't correspond to a feature, null will be returned.
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {Object} Object representing the feature id and UTFGrid data
+ * corresponding to the given map location. Returns null if the given
+ * location doesn't hit a feature.
+ */
+ getFeatureInfo: function(location) {
+ var info = null;
+ var tileInfo = this.getTileData(location);
+ if (tileInfo && tileInfo.tile) {
+ info = tileInfo.tile.getFeatureInfo(tileInfo.i, tileInfo.j);
+ }
+ return info;
+ },
+
+ /**
+ * APIMethod: getFeatureId
+ * Get the identifier for the feature associated with a map location.
+ *
+ * Parameters:
+ * location - {<OpenLayers.LonLat>} map location
+ *
+ * Returns:
+ * {String} The feature identifier corresponding to the given map location.
+ * Returns null if the location doesn't hit a feature.
+ */
+ getFeatureId: function(location) {
+ var id = null;
+ var info = this.getTileData(location);
+ if (info.tile) {
+ id = info.tile.getFeatureId(info.i, info.j);
+ }
+ return id;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.UTFGrid"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Vector.js b/misc/openlayers/lib/OpenLayers/Layer/Vector.js
new file mode 100644
index 0000000..4ef4cbf
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Vector.js
@@ -0,0 +1,1007 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Renderer.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector
+ * Instances of OpenLayers.Layer.Vector are used to render vector data from
+ * a variety of sources. Create a new vector layer with the
+ * <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * layer.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to layer.events.object.
+ * element - {DOMElement} A reference to layer.events.element.
+ *
+ * Supported map event types (in addition to those from <OpenLayers.Layer.events>):
+ * beforefeatureadded - Triggered before a feature is added. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be added. To stop the feature from being added, a
+ * listener should return false.
+ * beforefeaturesadded - Triggered before an array of features is added.
+ * Listeners will receive an object with a *features* property
+ * referencing the feature to be added. To stop the features from
+ * being added, a listener should return false.
+ * featureadded - Triggered after a feature is added. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the added feature.
+ * featuresadded - Triggered after features are added. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of added features.
+ * beforefeatureremoved - Triggered before a feature is removed. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be removed.
+ * beforefeaturesremoved - Triggered before multiple features are removed.
+ * Listeners will receive an object with a *features* property
+ * referencing the features to be removed.
+ * featureremoved - Triggerd after a feature is removed. The event
+ * object passed to listeners will have a *feature* property with a
+ * reference to the removed feature.
+ * featuresremoved - Triggered after features are removed. The event
+ * object passed to listeners will have a *features* property with a
+ * reference to an array of removed features.
+ * beforefeatureselected - Triggered before a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * feature to be selected. To stop the feature from being selectd, a
+ * listener should return false.
+ * featureselected - Triggered after a feature is selected. Listeners
+ * will receive an object with a *feature* property referencing the
+ * selected feature.
+ * featureunselected - Triggered after a feature is unselected.
+ * Listeners will receive an object with a *feature* property
+ * referencing the unselected feature.
+ * beforefeaturemodified - Triggered when a feature is selected to
+ * be modified. Listeners will receive an object with a *feature*
+ * property referencing the selected feature.
+ * featuremodified - Triggered when a feature has been modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * afterfeaturemodified - Triggered when a feature is finished being modified.
+ * Listeners will receive an object with a *feature* property referencing
+ * the modified feature.
+ * vertexmodified - Triggered when a vertex within any feature geometry
+ * has been modified. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * modification.
+ * vertexremoved - Triggered when a vertex within any feature geometry
+ * has been deleted. Listeners will receive an object with a
+ * *feature* property referencing the modified feature, a *vertex*
+ * property referencing the vertex modified (always a point geometry),
+ * and a *pixel* property referencing the pixel location of the
+ * removal.
+ * sketchstarted - Triggered when a feature sketch bound for this layer
+ * is started. Listeners will receive an object with a *feature*
+ * property referencing the new sketch feature and a *vertex* property
+ * referencing the creation point.
+ * sketchmodified - Triggered when a feature sketch bound for this layer
+ * is modified. Listeners will receive an object with a *vertex*
+ * property referencing the modified vertex and a *feature* property
+ * referencing the sketch feature.
+ * sketchcomplete - Triggered when a feature sketch bound for this layer
+ * is complete. Listeners will receive an object with a *feature*
+ * property referencing the sketch feature. By returning false, a
+ * listener can stop the sketch feature from being added to the layer.
+ * refresh - Triggered when something wants a strategy to ask the protocol
+ * for a new set of features.
+ */
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer is a base layer. Default is false. Set this property
+ * in the layer options.
+ */
+ isBaseLayer: false,
+
+ /**
+ * APIProperty: isFixed
+ * {Boolean} Whether the layer remains in one place while dragging the
+ * map. Note that setting this to true will move the layer to the bottom
+ * of the layer stack.
+ */
+ isFixed: false,
+
+ /**
+ * APIProperty: features
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ features: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} The filter set in this layer,
+ * a strategy launching read requests can combined
+ * this filter with its own filter.
+ */
+ filter: null,
+
+ /**
+ * Property: selectedFeatures
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ selectedFeatures: null,
+
+ /**
+ * Property: unrenderedFeatures
+ * {Object} hash of features, keyed by feature.id, that the renderer
+ * failed to draw
+ */
+ unrenderedFeatures: null,
+
+ /**
+ * APIProperty: reportError
+ * {Boolean} report friendly error message when loading of renderer
+ * fails.
+ */
+ reportError: true,
+
+ /**
+ * APIProperty: style
+ * {Object} Default style for the layer
+ */
+ style: null,
+
+ /**
+ * Property: styleMap
+ * {<OpenLayers.StyleMap>}
+ */
+ styleMap: null,
+
+ /**
+ * Property: strategies
+ * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+ */
+ strategies: null,
+
+ /**
+ * Property: protocol
+ * {<OpenLayers.Protocol>} Optional protocol for the layer.
+ */
+ protocol: null,
+
+ /**
+ * Property: renderers
+ * {Array(String)} List of supported Renderer classes. Add to this list to
+ * add support for additional renderers. This list is ordered:
+ * the first renderer which returns true for the 'supported()'
+ * method will be used, if not defined in the 'renderer' option.
+ */
+ renderers: ['SVG', 'VML', 'Canvas'],
+
+ /**
+ * Property: renderer
+ * {<OpenLayers.Renderer>}
+ */
+ renderer: null,
+
+ /**
+ * APIProperty: rendererOptions
+ * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
+ * supported options.
+ */
+ rendererOptions: null,
+
+ /**
+ * APIProperty: geometryType
+ * {String} geometryType allows you to limit the types of geometries this
+ * layer supports. This should be set to something like
+ * "OpenLayers.Geometry.Point" to limit types.
+ */
+ geometryType: null,
+
+ /**
+ * Property: drawn
+ * {Boolean} Whether the Vector Layer features have been drawn yet.
+ */
+ drawn: false,
+
+ /**
+ * APIProperty: ratio
+ * {Float} This specifies the ratio of the size of the visiblity of the Vector Layer features to the size of the map.
+ */
+ ratio: 1,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector
+ * Create a new vector layer
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} A new vector layer
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+
+ // allow user-set renderer, otherwise assign one
+ if (!this.renderer || !this.renderer.supported()) {
+ this.assignRenderer();
+ }
+
+ // if no valid renderer found, display error
+ if (!this.renderer || !this.renderer.supported()) {
+ this.renderer = null;
+ this.displayError();
+ }
+
+ if (!this.styleMap) {
+ this.styleMap = new OpenLayers.StyleMap();
+ }
+
+ this.features = [];
+ this.selectedFeatures = [];
+ this.unrenderedFeatures = {};
+
+ // Allow for custom layer behavior
+ if(this.strategies){
+ for(var i=0, len=this.strategies.length; i<len; i++) {
+ this.strategies[i].setLayer(this);
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy this layer
+ */
+ destroy: function() {
+ if (this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoDestroy) {
+ strategy.destroy();
+ }
+ }
+ this.strategies = null;
+ }
+ if (this.protocol) {
+ if(this.protocol.autoDestroy) {
+ this.protocol.destroy();
+ }
+ this.protocol = null;
+ }
+ this.destroyFeatures();
+ this.features = null;
+ this.selectedFeatures = null;
+ this.unrenderedFeatures = null;
+ if (this.renderer) {
+ this.renderer.destroy();
+ }
+ this.renderer = null;
+ this.geometryType = null;
+ this.drawn = null;
+ OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer.
+ *
+ * Note: Features of the layer are also cloned.
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Vector(this.name, this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+ var features = this.features;
+ var len = features.length;
+ var clonedFeatures = new Array(len);
+ for(var i=0; i<len; ++i) {
+ clonedFeatures[i] = features[i].clone();
+ }
+ obj.features = clonedFeatures;
+
+ return obj;
+ },
+
+ /**
+ * Method: refresh
+ * Ask the layer to request features again and redraw them. Triggers
+ * the refresh event if the layer is in range and visible.
+ *
+ * Parameters:
+ * obj - {Object} Optional object with properties for any listener of
+ * the refresh event.
+ */
+ refresh: function(obj) {
+ if(this.calculateInRange() && this.visibility) {
+ this.events.triggerEvent("refresh", obj);
+ }
+ },
+
+ /**
+ * Method: assignRenderer
+ * Iterates through the available renderer implementations and selects
+ * and assigns the first one whose "supported()" function returns true.
+ */
+ assignRenderer: function() {
+ for (var i=0, len=this.renderers.length; i<len; i++) {
+ var rendererClass = this.renderers[i];
+ var renderer = (typeof rendererClass == "function") ?
+ rendererClass :
+ OpenLayers.Renderer[rendererClass];
+ if (renderer && renderer.prototype.supported()) {
+ this.renderer = new renderer(this.div, this.rendererOptions);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Method: displayError
+ * Let the user know their browser isn't supported.
+ */
+ displayError: function() {
+ if (this.reportError) {
+ OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",
+ {renderers: this. renderers.join('\n')}));
+ }
+ },
+
+ /**
+ * Method: setMap
+ * The layer has been added to the map.
+ *
+ * If there is no renderer set, the layer can't be used. Remove it.
+ * Otherwise, give the renderer a reference to the map and set its size.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+ if (!this.renderer) {
+ this.map.removeLayer(this);
+ } else {
+ this.renderer.map = this.map;
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ }
+ },
+
+ /**
+ * Method: afterAdd
+ * Called at the end of the map.addLayer sequence. At this point, the map
+ * will have a base layer. Any autoActivate strategies will be
+ * activated here.
+ */
+ afterAdd: function() {
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.activate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeMap
+ * The layer has been removed from the map.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ this.drawn = false;
+ if(this.strategies) {
+ var strategy, i, len;
+ for(i=0, len=this.strategies.length; i<len; i++) {
+ strategy = this.strategies[i];
+ if(strategy.autoActivate) {
+ strategy.deactivate();
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: onMapResize
+ * Notify the renderer of the change in size.
+ *
+ */
+ onMapResize: function() {
+ OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+
+ var newSize = this.map.getSize();
+ newSize.w = newSize.w * this.ratio;
+ newSize.h = newSize.h * this.ratio;
+ this.renderer.setSize(newSize);
+ },
+
+ /**
+ * Method: moveTo
+ * Reset the vector layer's div so that it once again is lined up with
+ * the map. Notify the renderer of the change of extent, and in the
+ * case of a change of zoom level (resolution), have the
+ * renderer redraw features.
+ *
+ * If the layer has not yet been drawn, cycle through the layer's
+ * features and draw each one.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo: function(bounds, zoomChanged, dragging) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+ var coordSysUnchanged = true;
+ if (!dragging) {
+ this.renderer.root.style.visibility = 'hidden';
+
+ var viewSize = this.map.getSize(),
+ viewWidth = viewSize.w,
+ viewHeight = viewSize.h,
+ offsetLeft = (viewWidth / 2 * this.ratio) - viewWidth / 2,
+ offsetTop = (viewHeight / 2 * this.ratio) - viewHeight / 2;
+ offsetLeft += this.map.layerContainerOriginPx.x;
+ offsetLeft = -Math.round(offsetLeft);
+ offsetTop += this.map.layerContainerOriginPx.y;
+ offsetTop = -Math.round(offsetTop);
+
+ this.div.style.left = offsetLeft + 'px';
+ this.div.style.top = offsetTop + 'px';
+
+ var extent = this.map.getExtent().scale(this.ratio);
+ coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
+
+ this.renderer.root.style.visibility = 'visible';
+
+ // Force a reflow on gecko based browsers to prevent jump/flicker.
+ // This seems to happen on only certain configurations; it was originally
+ // noticed in FF 2.0 and Linux.
+ if (OpenLayers.IS_GECKO === true) {
+ this.div.scrollLeft = this.div.scrollLeft;
+ }
+
+ if (!zoomChanged && coordSysUnchanged) {
+ for (var i in this.unrenderedFeatures) {
+ var feature = this.unrenderedFeatures[i];
+ this.drawFeature(feature);
+ }
+ }
+ }
+ if (!this.drawn || zoomChanged || !coordSysUnchanged) {
+ this.drawn = true;
+ var feature;
+ for(var i=0, len=this.features.length; i<len; i++) {
+ this.renderer.locked = (i !== (len - 1));
+ feature = this.features[i];
+ this.drawFeature(feature);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: display
+ * Hide or show the Layer
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ OpenLayers.Layer.prototype.display.apply(this, arguments);
+ // we need to set the display style of the root in case it is attached
+ // to a foreign layer
+ var currentDisplay = this.div.style.display;
+ if(currentDisplay != this.renderer.root.style.display) {
+ this.renderer.root.style.display = currentDisplay;
+ }
+ },
+
+ /**
+ * APIMethod: addFeatures
+ * Add Features to the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ */
+ addFeatures: function(features, options) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var notify = !options || !options.silent;
+ if(notify) {
+ var event = {features: features};
+ var ret = this.events.triggerEvent("beforefeaturesadded", event);
+ if(ret === false) {
+ return;
+ }
+ features = event.features;
+ }
+
+ // Track successfully added features for featuresadded event, since
+ // beforefeatureadded can veto single features.
+ var featuresAdded = [];
+ for (var i=0, len=features.length; i<len; i++) {
+ if (i != (features.length - 1)) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+ var feature = features[i];
+
+ if (this.geometryType &&
+ !(feature.geometry instanceof this.geometryType)) {
+ throw new TypeError('addFeatures: component should be an ' +
+ this.geometryType.prototype.CLASS_NAME);
+ }
+
+ //give feature reference to its layer
+ feature.layer = this;
+
+ if (!feature.style && this.style) {
+ feature.style = OpenLayers.Util.extend({}, this.style);
+ }
+
+ if (notify) {
+ if(this.events.triggerEvent("beforefeatureadded",
+ {feature: feature}) === false) {
+ continue;
+ }
+ this.preFeatureInsert(feature);
+ }
+
+ featuresAdded.push(feature);
+ this.features.push(feature);
+ this.drawFeature(feature);
+
+ if (notify) {
+ this.events.triggerEvent("featureadded", {
+ feature: feature
+ });
+ this.onFeatureInsert(feature);
+ }
+ }
+
+ if(notify) {
+ this.events.triggerEvent("featuresadded", {features: featuresAdded});
+ }
+ },
+
+
+ /**
+ * APIMethod: removeFeatures
+ * Remove features from the layer. This erases any drawn features and
+ * removes them from the layer's control. The beforefeatureremoved
+ * and featureremoved events will be triggered for each feature. The
+ * featuresremoved event will be triggered after all features have
+ * been removed. To supress event triggering, use the silent option.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} List of features to be
+ * removed.
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeFeatures: function(features, options) {
+ if(!features || features.length === 0) {
+ return;
+ }
+ if (features === this.features) {
+ return this.removeAllFeatures(options);
+ }
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ if (features === this.selectedFeatures) {
+ features = features.slice();
+ }
+
+ var notify = !options || !options.silent;
+
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+
+ for (var i = features.length - 1; i >= 0; i--) {
+ // We remain locked so long as we're not at 0
+ // and the 'next' feature has a geometry. We do the geometry check
+ // because if all the features after the current one are 'null', we
+ // won't call eraseGeometry, so we break the 'renderer functions
+ // will always be called with locked=false *last*' rule. The end result
+ // is a possible gratiutious unlocking to save a loop through the rest
+ // of the list checking the remaining features every time. So long as
+ // null geoms are rare, this is probably okay.
+ if (i != 0 && features[i-1].geometry) {
+ this.renderer.locked = true;
+ } else {
+ this.renderer.locked = false;
+ }
+
+ var feature = features[i];
+ delete this.unrenderedFeatures[feature.id];
+
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+
+ this.features = OpenLayers.Util.removeItem(this.features, feature);
+ // feature has no layer at this point
+ feature.layer = null;
+
+ if (feature.geometry) {
+ this.renderer.eraseFeatures(feature);
+ }
+
+ //in the case that this feature is one of the selected features,
+ // remove it from that array as well.
+ if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
+ OpenLayers.Util.removeItem(this.selectedFeatures, feature);
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: removeAllFeatures
+ * Remove all features from the layer.
+ *
+ * Parameters:
+ * options - {Object} Optional properties for changing behavior of the
+ * removal.
+ *
+ * Valid options:
+ * silent - {Boolean} Supress event triggering. Default is false.
+ */
+ removeAllFeatures: function(options) {
+ var notify = !options || !options.silent;
+ var features = this.features;
+ if (notify) {
+ this.events.triggerEvent(
+ "beforefeaturesremoved", {features: features}
+ );
+ }
+ var feature;
+ for (var i = features.length-1; i >= 0; i--) {
+ feature = features[i];
+ if (notify) {
+ this.events.triggerEvent("beforefeatureremoved", {
+ feature: feature
+ });
+ }
+ feature.layer = null;
+ if (notify) {
+ this.events.triggerEvent("featureremoved", {
+ feature: feature
+ });
+ }
+ }
+ this.renderer.clear();
+ this.features = [];
+ this.unrenderedFeatures = {};
+ this.selectedFeatures = [];
+ if (notify) {
+ this.events.triggerEvent("featuresremoved", {features: features});
+ }
+ },
+
+ /**
+ * APIMethod: destroyFeatures
+ * Erase and destroy features on the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
+ * features to destroy. If not supplied, all features on the layer
+ * will be destroyed.
+ * options - {Object}
+ */
+ destroyFeatures: function(features, options) {
+ var all = (features == undefined); // evaluates to true if
+ // features is null
+ if(all) {
+ features = this.features;
+ }
+ if(features) {
+ this.removeFeatures(features, options);
+ for(var i=features.length-1; i>=0; i--) {
+ features[i].destroy();
+ }
+ }
+ },
+
+ /**
+ * APIMethod: drawFeature
+ * Draw (or redraw) a feature on the layer. If the optional style argument
+ * is included, this style will be used. If no style is included, the
+ * feature's style will be used. If the feature doesn't have a style,
+ * the layer's style will be used.
+ *
+ * This function is not designed to be used when adding features to
+ * the layer (use addFeatures instead). It is meant to be used when
+ * the style of a feature has changed, or in some other way needs to
+ * visually updated *after* it has already been added to a layer. You
+ * must add the feature to the layer for most layer-related events to
+ * happen.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {String | Object} Named render intent or full symbolizer object.
+ */
+ drawFeature: function(feature, style) {
+ // don't try to draw the feature with the renderer if the layer is not
+ // drawn itself
+ if (!this.drawn) {
+ return;
+ }
+ if (typeof style != "object") {
+ if(!style && feature.state === OpenLayers.State.DELETE) {
+ style = "delete";
+ }
+ var renderIntent = style || feature.renderIntent;
+ style = feature.style || this.style;
+ if (!style) {
+ style = this.styleMap.createSymbolizer(feature, renderIntent);
+ }
+ }
+
+ var drawn = this.renderer.drawFeature(feature, style);
+ //TODO remove the check for null when we get rid of Renderer.SVG
+ if (drawn === false || drawn === null) {
+ this.unrenderedFeatures[feature.id] = feature;
+ } else {
+ delete this.unrenderedFeatures[feature.id];
+ }
+ },
+
+ /**
+ * Method: eraseFeatures
+ * Erase features from the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ this.renderer.eraseFeatures(features);
+ },
+
+ /**
+ * Method: getFeatureFromEvent
+ * Given an event, return a feature if the event occurred over one.
+ * Otherwise, return null.
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
+ */
+ getFeatureFromEvent: function(evt) {
+ if (!this.renderer) {
+ throw new Error('getFeatureFromEvent called on layer with no ' +
+ 'renderer. This usually means you destroyed a ' +
+ 'layer, but not some handler which is associated ' +
+ 'with it.');
+ }
+ var feature = null;
+ var featureId = this.renderer.getFeatureIdFromEvent(evt);
+ if (featureId) {
+ if (typeof featureId === "string") {
+ feature = this.getFeatureById(featureId);
+ } else {
+ feature = featureId;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureBy
+ * Given a property value, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * property - {String}
+ * value - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * property value or null if there is no such feature.
+ */
+ getFeatureBy: function(property, value) {
+ //TBD - would it be more efficient to use a hash for this.features?
+ var feature = null;
+ for(var i=0, len=this.features.length; i<len; ++i) {
+ if(this.features[i][property] == value) {
+ feature = this.features[i];
+ break;
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * APIMethod: getFeatureById
+ * Given a feature id, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureId - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureId or null if there is no such feature.
+ */
+ getFeatureById: function(featureId) {
+ return this.getFeatureBy('id', featureId);
+ },
+
+ /**
+ * APIMethod: getFeatureByFid
+ * Given a feature fid, return the feature if it exists in the features array
+ *
+ * Parameters:
+ * featureFid - {String}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+ * featureFid or null if there is no such feature.
+ */
+ getFeatureByFid: function(featureFid) {
+ return this.getFeatureBy('fid', featureFid);
+ },
+
+ /**
+ * APIMethod: getFeaturesByAttribute
+ * Returns an array of features that have the given attribute key set to the
+ * given value. Comparison of attribute values takes care of datatypes, e.g.
+ * the string '1234' is not equal to the number 1234.
+ *
+ * Parameters:
+ * attrName - {String}
+ * attrValue - {Mixed}
+ *
+ * Returns:
+ * Array({<OpenLayers.Feature.Vector>}) An array of features that have the
+ * passed named attribute set to the given value.
+ */
+ getFeaturesByAttribute: function(attrName, attrValue) {
+ var i,
+ feature,
+ len = this.features.length,
+ foundFeatures = [];
+ for(i = 0; i < len; i++) {
+ feature = this.features[i];
+ if(feature && feature.attributes) {
+ if (feature.attributes[attrName] === attrValue) {
+ foundFeatures.push(feature);
+ }
+ }
+ }
+ return foundFeatures;
+ },
+
+ /**
+ * Unselect the selected features
+ * i.e. clears the featureSelection array
+ * change the style back
+ clearSelection: function() {
+
+ var vectorLayer = this.map.vectorLayer;
+ for (var i = 0; i < this.map.featureSelection.length; i++) {
+ var featureSelection = this.map.featureSelection[i];
+ vectorLayer.drawFeature(featureSelection, vectorLayer.style);
+ }
+ this.map.featureSelection = [];
+ },
+ */
+
+
+ /**
+ * APIMethod: onFeatureInsert
+ * method called after a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something on feature updates.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ onFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: preFeatureInsert
+ * method called before a feature is inserted.
+ * Does nothing by default. Override this if you
+ * need to do something when features are first added to the
+ * layer, but before they are drawn, such as adjust the style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ */
+ preFeatureInsert: function(feature) {
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the features.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} or null if the layer has no features with
+ * geometries.
+ */
+ getDataExtent: function () {
+ var maxExtent = null;
+ var features = this.features;
+ if(features && (features.length > 0)) {
+ var geometry = null;
+ for(var i=0, len=features.length; i<len; i++) {
+ geometry = features[i].geometry;
+ if (geometry) {
+ if (maxExtent === null) {
+ maxExtent = new OpenLayers.Bounds();
+ }
+ maxExtent.extend(geometry.getBounds());
+ }
+ }
+ }
+ return maxExtent;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js b/misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js
new file mode 100644
index 0000000..075edaa
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Vector/RootContainer.js
@@ -0,0 +1,154 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector.RootContainer
+ * A special layer type to combine multiple vector layers inside a single
+ * renderer root container. This class is not supposed to be instantiated
+ * from user space, it is a helper class for controls that require event
+ * processing for multiple vector layers.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.Vector.RootContainer = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * Property: displayInLayerSwitcher
+ * Set to false for this layer type
+ */
+ displayInLayerSwitcher: false,
+
+ /**
+ * APIProperty: layers
+ * Layers that are attached to this container. Required config option.
+ */
+ layers: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Vector.RootContainer
+ * Create a new root container for multiple vector layer. This constructor
+ * is not supposed to be used from user space, it is only to be used by
+ * controls that need feature selection across multiple vector layers.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * options - {Object} Optional object with non-default properties to set on
+ * the layer.
+ *
+ * Required options properties:
+ * layers - {Array(<OpenLayers.Layer.Vector>)} The layers managed by this
+ * container
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Vector.RootContainer>} A new vector layer root
+ * container
+ */
+
+ /**
+ * Method: display
+ */
+ display: function() {},
+
+ /**
+ * Method: getFeatureFromEvent
+ * walk through the layers to find the feature returned by the event
+ *
+ * Parameters:
+ * evt - {Object} event object with a feature property
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ getFeatureFromEvent: function(evt) {
+ var layers = this.layers;
+ var feature;
+ for(var i=0; i<layers.length; i++) {
+ feature = layers[i].getFeatureFromEvent(evt);
+ if(feature) {
+ return feature;
+ }
+ }
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+ this.collectRoots();
+ map.events.register("changelayer", this, this.handleChangeLayer);
+ },
+
+ /**
+ * Method: removeMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ map.events.unregister("changelayer", this, this.handleChangeLayer);
+ this.resetRoots();
+ OpenLayers.Layer.Vector.prototype.removeMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: collectRoots
+ * Collects the root nodes of all layers this control is configured with
+ * and moveswien the nodes to this control's layer
+ */
+ collectRoots: function() {
+ var layer;
+ // walk through all map layers, because we want to keep the order
+ for(var i=0; i<this.map.layers.length; ++i) {
+ layer = this.map.layers[i];
+ if(OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ layer.renderer.moveRoot(this.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: resetRoots
+ * Resets the root nodes back into the layers they belong to.
+ */
+ resetRoots: function() {
+ var layer;
+ for(var i=0; i<this.layers.length; ++i) {
+ layer = this.layers[i];
+ if(this.renderer && layer.renderer.getRenderLayerId() == this.id) {
+ this.renderer.moveRoot(layer.renderer);
+ }
+ }
+ },
+
+ /**
+ * Method: handleChangeLayer
+ * Event handler for the map's changelayer event. We need to rebuild
+ * this container's layer dom if order of one of its layers changes.
+ * This handler is added with the setMap method, and removed with the
+ * removeMap method.
+ *
+ * Parameters:
+ * evt - {Object}
+ */
+ handleChangeLayer: function(evt) {
+ var layer = evt.layer;
+ if(evt.property == "order" &&
+ OpenLayers.Util.indexOf(this.layers, layer) != -1) {
+ this.resetRoots();
+ this.collectRoots();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Vector.RootContainer"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/WMS.js b/misc/openlayers/lib/OpenLayers/Layer/WMS.js
new file mode 100644
index 0000000..15dee2f
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/WMS.js
@@ -0,0 +1,267 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMS
+ * Instances of OpenLayers.Layer.WMS are used to display data from OGC Web
+ * Mapping Services. Create a new WMS layer with the <OpenLayers.Layer.WMS>
+ * constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Constant: DEFAULT_PARAMS
+ * {Object} Hashtable of default parameter key/value pairs
+ */
+ DEFAULT_PARAMS: { service: "WMS",
+ version: "1.1.1",
+ request: "GetMap",
+ styles: "",
+ format: "image/jpeg"
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} Default is true for WMS layer
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: encodeBBOX
+ * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
+ * but some services want it that way. Default false.
+ */
+ encodeBBOX: false,
+
+ /**
+ * APIProperty: noMagic
+ * {Boolean} If true, the image format will not be automagicaly switched
+ * from image/jpeg to image/png or image/gif when using
+ * TRANSPARENT=TRUE. Also isBaseLayer will not changed by the
+ * constructor. Default false.
+ */
+ noMagic: false,
+
+ /**
+ * Property: yx
+ * {Object} Keys in this object are EPSG codes for which the axis order
+ * is to be reversed (yx instead of xy, LatLon instead of LonLat), with
+ * true as value. This is only relevant for WMS versions >= 1.3.0, and
+ * only if yx is not set in <OpenLayers.Projection.defaults> for the
+ * used projection.
+ */
+ yx: {},
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS
+ * Create a new WMS layer object
+ *
+ * Examples:
+ *
+ * The code below creates a simple WMS layer using the image/jpeg format.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {layers: "modis,global_mosaic"});
+ * (end)
+ * Note the 3rd argument (params). Properties added to this object will be
+ * added to the WMS GetMap requests used for this layer's tiles. The only
+ * mandatory parameter is "layers". Other common WMS params include
+ * "transparent", "styles" and "format". Note that the "srs" param will
+ * always be ignored. Instead, it will be derived from the baseLayer's or
+ * map's projection.
+ *
+ * The code below creates a transparent WMS layer with additional options.
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS("NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {
+ * layers: "modis,global_mosaic",
+ * transparent: true
+ * }, {
+ * opacity: 0.5,
+ * singleTile: true
+ * });
+ * (end)
+ * Note that by default, a WMS layer is configured as baseLayer. Setting
+ * the "transparent" param to true will apply some magic (see <noMagic>).
+ * The default image format changes from image/jpeg to image/png, and the
+ * layer is not configured as baseLayer.
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the WMS
+ * (e.g. http://wms.jpl.nasa.gov/wms.cgi)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ * These options include all properties listed above, plus the ones
+ * inherited from superclasses.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ //uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+ if (parseFloat(params.VERSION) >= 1.3 && !params.EXCEPTIONS) {
+ params.EXCEPTIONS = "INIMAGE";
+ }
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ OpenLayers.Util.applyDefaults(
+ this.params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+
+
+ //layer is transparent
+ if (!this.noMagic && this.params.TRANSPARENT &&
+ this.params.TRANSPARENT.toString().toLowerCase() == "true") {
+
+ // unless explicitly set in options, make layer an overlay
+ if ( (options == null) || (!options.isBaseLayer) ) {
+ this.isBaseLayer = false;
+ }
+
+ // jpegs can never be transparent, so intelligently switch the
+ // format, depending on the browser's capabilities
+ if (this.params.FORMAT == "image/jpeg") {
+ this.params.FORMAT = OpenLayers.Util.alphaHack() ? "image/gif"
+ : "image/png";
+ }
+ }
+
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMS(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: reverseAxisOrder
+ * Returns true if the axis order is reversed for the WMS version and
+ * projection of the layer.
+ *
+ * Returns:
+ * {Boolean} true if the axis order is reversed, false otherwise.
+ */
+ reverseAxisOrder: function() {
+ var projCode = this.projection.getCode();
+ return parseFloat(this.params.VERSION) >= 1.3 &&
+ !!(this.yx[projCode] || (OpenLayers.Projection.defaults[projCode] &&
+ OpenLayers.Projection.defaults[projCode].yx));
+ },
+
+ /**
+ * Method: getURL
+ * Return a GetMap query string for this layer
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} A bounds representing the bbox for the
+ * request.
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters.
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+
+ var imageSize = this.getImageSize();
+ var newParams = {};
+ // WMS 1.3 introduced axis order
+ var reverseAxisOrder = this.reverseAxisOrder();
+ newParams.BBOX = this.encodeBBOX ?
+ bounds.toBBOX(null, reverseAxisOrder) :
+ bounds.toArray(reverseAxisOrder);
+ newParams.WIDTH = imageSize.w;
+ newParams.HEIGHT = imageSize.h;
+ var requestString = this.getFullRequestString(newParams);
+ return requestString;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Catch changeParams and uppercase the new params to be merged in
+ * before calling changeParams on the super class.
+ *
+ * Once params have been changed, the tiles will be reloaded with
+ * the new parameters.
+ *
+ * Parameters:
+ * newParams - {Object} Hashtable of new params to use
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ /**
+ * APIMethod: getFullRequestString
+ * Combine the layer's url with its params and these newParams.
+ *
+ * Add the SRS parameter from projection -- this is probably
+ * more eloquently done via a setProjection() method, but this
+ * works for now and always.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ *
+ * Returns:
+ * {String}
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ var mapProjection = this.map.getProjectionObject();
+ var projectionCode = this.projection && this.projection.equals(mapProjection) ?
+ this.projection.getCode() :
+ mapProjection.getCode();
+ var value = (projectionCode == "none") ? null : projectionCode;
+ if (parseFloat(this.params.VERSION) >= 1.3) {
+ this.params.CRS = value;
+ } else {
+ this.params.SRS = value;
+ }
+
+ if (typeof this.params.TRANSPARENT == "boolean") {
+ newParams.TRANSPARENT = this.params.TRANSPARENT ? "TRUE" : "FALSE";
+ }
+
+ return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+ this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/WMTS.js b/misc/openlayers/lib/OpenLayers/Layer/WMTS.js
new file mode 100644
index 0000000..9c41629
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/WMTS.js
@@ -0,0 +1,510 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMTS
+ * Instances of the WMTS class allow viewing of tiles from a service that
+ * implements the OGC WMTS specification version 1.0.0.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WMTS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} The layer will be considered a base layer. Default is true.
+ */
+ isBaseLayer: true,
+
+ /**
+ * Property: version
+ * {String} WMTS version. Default is "1.0.0".
+ */
+ version: "1.0.0",
+
+ /**
+ * APIProperty: requestEncoding
+ * {String} Request encoding. Can be "REST" or "KVP". Default is "KVP".
+ */
+ requestEncoding: "KVP",
+
+ /**
+ * APIProperty: url
+ * {String|Array(String)} The base URL or request URL template for the WMTS
+ * service. Must be provided. Array is only supported for base URLs, not
+ * for request URL templates. URL templates are only supported for
+ * REST <requestEncoding>.
+ */
+ url: null,
+
+ /**
+ * APIProperty: layer
+ * {String} The layer identifier advertised by the WMTS service. Must be
+ * provided.
+ */
+ layer: null,
+
+ /**
+ * APIProperty: matrixSet
+ * {String} One of the advertised matrix set identifiers. Must be provided.
+ */
+ matrixSet: null,
+
+ /**
+ * APIProperty: style
+ * {String} One of the advertised layer styles. Must be provided.
+ */
+ style: null,
+
+ /**
+ * APIProperty: format
+ * {String} The image MIME type. Default is "image/jpeg".
+ */
+ format: "image/jpeg",
+
+ /**
+ * APIProperty: tileOrigin
+ * {<OpenLayers.LonLat>} The top-left corner of the tile matrix in map
+ * units. If the tile origin for each matrix in a set is different,
+ * the <matrixIds> should include a topLeftCorner property. If
+ * not provided, the tile origin will default to the top left corner
+ * of the layer <maxExtent>.
+ */
+ tileOrigin: null,
+
+ /**
+ * APIProperty: tileFullExtent
+ * {<OpenLayers.Bounds>} The full extent of the tile set. If not supplied,
+ * the layer's <maxExtent> property will be used.
+ */
+ tileFullExtent: null,
+
+ /**
+ * APIProperty: formatSuffix
+ * {String} For REST request encoding, an image format suffix must be
+ * included in the request. If not provided, the suffix will be derived
+ * from the <format> property.
+ */
+ formatSuffix: null,
+
+ /**
+ * APIProperty: matrixIds
+ * {Array} A list of tile matrix identifiers. If not provided, the matrix
+ * identifiers will be assumed to be integers corresponding to the
+ * map zoom level. If a list of strings is provided, each item should
+ * be the matrix identifier that corresponds to the map zoom level.
+ * Additionally, a list of objects can be provided. Each object should
+ * describe the matrix as presented in the WMTS capabilities. These
+ * objects should have the propertes shown below.
+ *
+ * Matrix properties:
+ * identifier - {String} The matrix identifier (required).
+ * scaleDenominator - {Number} The matrix scale denominator.
+ * topLeftCorner - {<OpenLayers.LonLat>} The top left corner of the
+ * matrix. Must be provided if different than the layer <tileOrigin>.
+ * tileWidth - {Number} The tile width for the matrix. Must be provided
+ * if different than the width given in the layer <tileSize>.
+ * tileHeight - {Number} The tile height for the matrix. Must be provided
+ * if different than the height given in the layer <tileSize>.
+ */
+ matrixIds: null,
+
+ /**
+ * APIProperty: dimensions
+ * {Array} For RESTful request encoding, extra dimensions may be specified.
+ * Items in this list should be property names in the <params> object.
+ * Values of extra dimensions will be determined from the corresponding
+ * values in the <params> object.
+ */
+ dimensions: null,
+
+ /**
+ * APIProperty: params
+ * {Object} Extra parameters to include in tile requests. For KVP
+ * <requestEncoding>, these properties will be encoded in the request
+ * query string. For REST <requestEncoding>, these properties will
+ * become part of the request path, with order determined by the
+ * <dimensions> list.
+ */
+ params: null,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Additionally, if this layer is to be used
+ * as an overlay and the cache has fewer zoom levels than the base
+ * layer, you can supply a negative zoomOffset. For example, if a
+ * map zoom level of 1 corresponds to your cache level zero, you would
+ * supply a -1 zoomOffset (and set the maxResolution of the layer
+ * appropriately). The zoomOffset value has no effect if complete
+ * matrix definitions (including scaleDenominator) are supplied in
+ * the <matrixIds> property. Defaults to 0 (no zoom offset).
+ */
+ zoomOffset: 0,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Property: formatSuffixMap
+ * {Object} a map between WMTS 'format' request parameter and tile image file suffix
+ */
+ formatSuffixMap: {
+ "image/png": "png",
+ "image/png8": "png",
+ "image/png24": "png",
+ "image/png32": "png",
+ "png": "png",
+ "image/jpeg": "jpg",
+ "image/jpg": "jpg",
+ "jpeg": "jpg",
+ "jpg": "jpg"
+ },
+
+ /**
+ * Property: matrix
+ * {Object} Matrix definition for the current map resolution. Updated by
+ * the <updateMatrixProperties> method.
+ */
+ matrix: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.WMTS
+ * Create a new WMTS layer.
+ *
+ * Example:
+ * (code)
+ * var wmts = new OpenLayers.Layer.WMTS({
+ * name: "My WMTS Layer",
+ * url: "http://example.com/wmts",
+ * layer: "layer_id",
+ * style: "default",
+ * matrixSet: "matrix_id"
+ * });
+ * (end)
+ *
+ * Parameters:
+ * config - {Object} Configuration properties for the layer.
+ *
+ * Required configuration properties:
+ * url - {String} The base url for the service. See the <url> property.
+ * layer - {String} The layer identifier. See the <layer> property.
+ * style - {String} The layer style identifier. See the <style> property.
+ * matrixSet - {String} The tile matrix set identifier. See the <matrixSet>
+ * property.
+ *
+ * Any other documented layer properties can be provided in the config object.
+ */
+ initialize: function(config) {
+
+ // confirm required properties are supplied
+ var required = {
+ url: true,
+ layer: true,
+ style: true,
+ matrixSet: true
+ };
+ for (var prop in required) {
+ if (!(prop in config)) {
+ throw new Error("Missing property '" + prop + "' in layer configuration.");
+ }
+ }
+
+ config.params = OpenLayers.Util.upperCaseObject(config.params);
+ var args = [config.name, config.url, config.params, config];
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, args);
+
+
+ // determine format suffix (for REST)
+ if (!this.formatSuffix) {
+ this.formatSuffix = this.formatSuffixMap[this.format] || this.format.split("/").pop();
+ }
+
+ // expand matrixIds (may be array of string or array of object)
+ if (this.matrixIds) {
+ var len = this.matrixIds.length;
+ if (len && typeof this.matrixIds[0] === "string") {
+ var ids = this.matrixIds;
+ this.matrixIds = new Array(len);
+ for (var i=0; i<len; ++i) {
+ this.matrixIds[i] = {identifier: ids[i]};
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Method: setMap
+ */
+ setMap: function() {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ },
+
+ /**
+ * Method: updateMatrixProperties
+ * Called when map resolution changes to update matrix related properties.
+ */
+ updateMatrixProperties: function() {
+ this.matrix = this.getMatrix();
+ if (this.matrix) {
+ if (this.matrix.topLeftCorner) {
+ this.tileOrigin = this.matrix.topLeftCorner;
+ }
+ if (this.matrix.tileWidth && this.matrix.tileHeight) {
+ this.tileSize = new OpenLayers.Size(
+ this.matrix.tileWidth, this.matrix.tileHeight
+ );
+ }
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(
+ this.maxExtent.left, this.maxExtent.top
+ );
+ }
+ if (!this.tileFullExtent) {
+ this.tileFullExtent = this.maxExtent;
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to
+ * do some init work in that case.
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ if (zoomChanged || !this.matrix) {
+ this.updateMatrixProperties();
+ }
+ return OpenLayers.Layer.Grid.prototype.moveTo.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMTS>} An exact clone of this <OpenLayers.Layer.WMTS>
+ */
+ clone: function(obj) {
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMTS(this.options);
+ }
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+ // copy/set any non-init, non-simple values here
+ return obj;
+ },
+
+ /**
+ * Method: getIdentifier
+ * Get the current index in the matrixIds array.
+ */
+ getIdentifier: function() {
+ return this.getServerZoom();
+ },
+
+ /**
+ * Method: getMatrix
+ * Get the appropriate matrix definition for the current map resolution.
+ */
+ getMatrix: function() {
+ var matrix;
+ if (!this.matrixIds || this.matrixIds.length === 0) {
+ matrix = {identifier: this.getIdentifier()};
+ } else {
+ // get appropriate matrix given the map scale if possible
+ if ("scaleDenominator" in this.matrixIds[0]) {
+ // scale denominator calculation based on WMTS spec
+ var denom =
+ OpenLayers.METERS_PER_INCH *
+ OpenLayers.INCHES_PER_UNIT[this.units] *
+ this.getServerResolution() / 0.28E-3;
+ var diff = Number.POSITIVE_INFINITY;
+ var delta;
+ for (var i=0, ii=this.matrixIds.length; i<ii; ++i) {
+ delta = Math.abs(1 - (this.matrixIds[i].scaleDenominator / denom));
+ if (delta < diff) {
+ diff = delta;
+ matrix = this.matrixIds[i];
+ }
+ }
+ } else {
+ // fall back on zoom as index
+ matrix = this.matrixIds[this.getIdentifier()];
+ }
+ }
+ return matrix;
+ },
+
+ /**
+ * Method: getTileInfo
+ * Get tile information for a given location at the current map resolution.
+ *
+ * Parameters:
+ * loc - {<OpenLayers.LonLat} A location in map coordinates.
+ *
+ * Returns:
+ * {Object} An object with "col", "row", "i", and "j" properties. The col
+ * and row values are zero based tile indexes from the top left. The
+ * i and j values are the number of pixels to the left and top
+ * (respectively) of the given location within the target tile.
+ */
+ getTileInfo: function(loc) {
+ var res = this.getServerResolution();
+
+ var fx = (loc.lon - this.tileOrigin.lon) / (res * this.tileSize.w);
+ var fy = (this.tileOrigin.lat - loc.lat) / (res * this.tileSize.h);
+
+ var col = Math.floor(fx);
+ var row = Math.floor(fy);
+
+ return {
+ col: col,
+ row: row,
+ i: Math.floor((fx - col) * this.tileSize.w),
+ j: Math.floor((fy - row) * this.tileSize.h)
+ };
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A URL for the tile corresponding to the given bounds.
+ */
+ getURL: function(bounds) {
+ bounds = this.adjustBounds(bounds);
+ var url = "";
+ if (!this.tileFullExtent || this.tileFullExtent.intersectsBounds(bounds)) {
+
+ var center = bounds.getCenterLonLat();
+ var info = this.getTileInfo(center);
+ var matrixId = this.matrix.identifier;
+ var dimensions = this.dimensions, params;
+
+ if (OpenLayers.Util.isArray(this.url)) {
+ url = this.selectUrl([
+ this.version, this.style, this.matrixSet,
+ this.matrix.identifier, info.row, info.col
+ ].join(","), this.url);
+ } else {
+ url = this.url;
+ }
+
+ if (this.requestEncoding.toUpperCase() === "REST") {
+ params = this.params;
+ if (url.indexOf("{") !== -1) {
+ var template = url.replace(/\{/g, "${");
+ var context = {
+ // spec does not make clear if capital S or not
+ style: this.style, Style: this.style,
+ TileMatrixSet: this.matrixSet,
+ TileMatrix: this.matrix.identifier,
+ TileRow: info.row,
+ TileCol: info.col
+ };
+ if (dimensions) {
+ var dimension, i;
+ for (i=dimensions.length-1; i>=0; --i) {
+ dimension = dimensions[i];
+ context[dimension] = params[dimension.toUpperCase()];
+ }
+ }
+ url = OpenLayers.String.format(template, context);
+ } else {
+ // include 'version', 'layer' and 'style' in tile resource url
+ var path = this.version + "/" + this.layer + "/" + this.style + "/";
+
+ // append optional dimension path elements
+ if (dimensions) {
+ for (var i=0; i<dimensions.length; i++) {
+ if (params[dimensions[i]]) {
+ path = path + params[dimensions[i]] + "/";
+ }
+ }
+ }
+
+ // append other required path elements
+ path = path + this.matrixSet + "/" + this.matrix.identifier +
+ "/" + info.row + "/" + info.col + "." + this.formatSuffix;
+
+ if (!url.match(/\/$/)) {
+ url = url + "/";
+ }
+ url = url + path;
+ }
+ } else if (this.requestEncoding.toUpperCase() === "KVP") {
+
+ // assemble all required parameters
+ params = {
+ SERVICE: "WMTS",
+ REQUEST: "GetTile",
+ VERSION: this.version,
+ LAYER: this.layer,
+ STYLE: this.style,
+ TILEMATRIXSET: this.matrixSet,
+ TILEMATRIX: this.matrix.identifier,
+ TILEROW: info.row,
+ TILECOL: info.col,
+ FORMAT: this.format
+ };
+ url = OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this, [params]);
+
+ }
+ }
+ return url;
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Extend the existing layer <params> with new properties. Tiles will be
+ * reloaded with updated params in the request.
+ *
+ * Parameters:
+ * newParams - {Object} Properties to extend to existing <params>.
+ */
+ mergeNewParams: function(newParams) {
+ if (this.requestEncoding.toUpperCase() === "KVP") {
+ return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(
+ this, [OpenLayers.Util.upperCaseObject(newParams)]
+ );
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMTS"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/WorldWind.js b/misc/openlayers/lib/OpenLayers/Layer/WorldWind.js
new file mode 100644
index 0000000..8581289
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/WorldWind.js
@@ -0,0 +1,105 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WorldWind
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WorldWind = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ DEFAULT_PARAMS: {
+ },
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} WorldWind layer is a base layer by default.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: lzd
+ * {Float} LevelZeroTileSizeDegrees
+ */
+ lzd: null,
+
+ /**
+ * APIProperty: zoomLevels
+ * {Integer} Number of zoom levels.
+ */
+ zoomLevels: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.WorldWind
+ *
+ * Parameters:
+ * name - {String} Name of Layer
+ * url - {String} Base URL
+ * lzd - {Float} Level zero tile size degrees
+ * zoomLevels - {Integer} number of zoom levels
+ * params - {Object} additional parameters
+ * options - {Object} additional options
+ */
+ initialize: function(name, url, lzd, zoomLevels, params, options) {
+ this.lzd = lzd;
+ this.zoomLevels = zoomLevels;
+ var newArguments = [];
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+ this.params = OpenLayers.Util.applyDefaults(
+ this.params, this.DEFAULT_PARAMS
+ );
+ },
+
+ /**
+ * Method: getZoom
+ * Convert map zoom to WW zoom.
+ */
+ getZoom: function () {
+ var zoom = this.map.getZoom();
+ var extent = this.map.getMaxExtent();
+ zoom = zoom - Math.log(this.maxResolution / (this.lzd/512))/Math.log(2);
+ return zoom;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var zoom = this.getZoom();
+ var extent = this.map.getMaxExtent();
+ var deg = this.lzd/Math.pow(2,this.getZoom());
+ var x = Math.floor((bounds.left - extent.left)/deg);
+ var y = Math.floor((bounds.bottom - extent.bottom)/deg);
+ if (this.map.getResolution() <= (this.lzd/512)
+ && this.getZoom() <= this.zoomLevels) {
+ return this.getFullRequestString(
+ { L: zoom,
+ X: x,
+ Y: y
+ });
+ } else {
+ return OpenLayers.Util.getImageLocation("blank.gif");
+ }
+
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WorldWind"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/XYZ.js b/misc/openlayers/lib/OpenLayers/Layer/XYZ.js
new file mode 100644
index 0000000..5af5ce3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/XYZ.js
@@ -0,0 +1,172 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.XYZ
+ * The XYZ class is designed to make it easier for people who have tiles
+ * arranged by a standard XYZ grid.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.XYZ = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * Default is true, as this is designed to be a base tile source.
+ */
+ isBaseLayer: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * Whether the tile extents should be set to the defaults for
+ * spherical mercator. Useful for things like OpenStreetMap.
+ * Default is false, except for the OSM subclass.
+ */
+ sphericalMercator: false,
+
+ /**
+ * APIProperty: zoomOffset
+ * {Number} If your cache has more zoom levels than you want to provide
+ * access to with this layer, supply a zoomOffset. This zoom offset
+ * is added to the current map zoom level to determine the level
+ * for a requested tile. For example, if you supply a zoomOffset
+ * of 3, when the map is at the zoom 0, tiles will be requested from
+ * level 3 of your cache. Default is 0 (assumes cache level and map
+ * zoom are equivalent). Using <zoomOffset> is an alternative to
+ * setting <serverResolutions> if you only want to expose a subset
+ * of the server resolutions.
+ */
+ zoomOffset: 0,
+
+ /**
+ * APIProperty: serverResolutions
+ * {Array} A list of all resolutions available on the server. Only set this
+ * property if the map resolutions differ from the server. This
+ * property serves two purposes. (a) <serverResolutions> can include
+ * resolutions that the server supports and that you don't want to
+ * provide with this layer; you can also look at <zoomOffset>, which is
+ * an alternative to <serverResolutions> for that specific purpose.
+ * (b) The map can work with resolutions that aren't supported by
+ * the server, i.e. that aren't in <serverResolutions>. When the
+ * map is displayed in such a resolution data for the closest
+ * server-supported resolution is loaded and the layer div is
+ * stretched as necessary.
+ */
+ serverResolutions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.XYZ
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, options) {
+ if (options && options.sphericalMercator || this.sphericalMercator) {
+ options = OpenLayers.Util.extend({
+ projection: "EPSG:900913",
+ numZoomLevels: 19
+ }, options);
+ }
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
+ name || this.name, url || this.url, {}, options
+ ]);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a clone of this layer
+ *
+ * Parameters:
+ * obj - {Object} Is this ever used?
+ *
+ * Returns:
+ * {<OpenLayers.Layer.XYZ>} An exact clone of this OpenLayers.Layer.XYZ
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.XYZ(this.name,
+ this.url,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ var xyz = this.getXYZ(bounds);
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ var s = '' + xyz.x + xyz.y + xyz.z;
+ url = this.selectUrl(s, url);
+ }
+
+ return OpenLayers.String.format(url, xyz);
+ },
+
+ /**
+ * Method: getXYZ
+ * Calculates x, y and z for the given bounds.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {Object} - an object with x, y and z properties.
+ */
+ getXYZ: function(bounds) {
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.maxExtent.left) /
+ (res * this.tileSize.w));
+ var y = Math.round((this.maxExtent.top - bounds.top) /
+ (res * this.tileSize.h));
+ var z = this.getServerZoom();
+
+ if (this.wrapDateLine) {
+ var limit = Math.pow(2, z);
+ x = ((x % limit) + limit) % limit;
+ }
+
+ return {'x': x, 'y': y, 'z': z};
+ },
+
+ /* APIMethod: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ if (!this.tileOrigin) {
+ this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left,
+ this.maxExtent.bottom);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.XYZ"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Layer/Zoomify.js b/misc/openlayers/lib/OpenLayers/Layer/Zoomify.js
new file mode 100644
index 0000000..1c3d57d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Layer/Zoomify.js
@@ -0,0 +1,260 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/*
+ * Development supported by a R&D grant DC08P02OUK006 - Old Maps Online
+ * (www.oldmapsonline.org) from Ministry of Culture of the Czech Republic.
+ */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Zoomify
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.Zoomify = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} The Zoomify image size in pixels.
+ */
+ size: null,
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean}
+ */
+ isBaseLayer: true,
+
+ /**
+ * Property: standardTileSize
+ * {Integer} The size of a standard (non-border) square tile in pixels.
+ */
+ standardTileSize: 256,
+
+ /**
+ * Property: tileOriginCorner
+ * {String} This layer uses top-left as tile origin
+ **/
+ tileOriginCorner: "tl",
+
+ /**
+ * Property: numberOfTiers
+ * {Integer} Depth of the Zoomify pyramid, number of tiers (zoom levels)
+ * - filled during Zoomify pyramid initialization.
+ */
+ numberOfTiers: 0,
+
+ /**
+ * Property: tileCountUpToTier
+ * {Array(Integer)} Number of tiles up to the given tier of pyramid.
+ * - filled during Zoomify pyramid initialization.
+ */
+ tileCountUpToTier: null,
+
+ /**
+ * Property: tierSizeInTiles
+ * {Array(<OpenLayers.Size>)} Size (in tiles) for each tier of pyramid.
+ * - filled during Zoomify pyramid initialization.
+ */
+ tierSizeInTiles: null,
+
+ /**
+ * Property: tierImageSize
+ * {Array(<OpenLayers.Size>)} Image size in pixels for each pyramid tier.
+ * - filled during Zoomify pyramid initialization.
+ */
+ tierImageSize: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.Zoomify
+ *
+ * Parameters:
+ * name - {String} A name for the layer.
+ * url - {String} - Relative or absolute path to the image or more
+ * precisly to the TileGroup[X] directories root.
+ * Flash plugin use the variable name "zoomifyImagePath" for this.
+ * size - {<OpenLayers.Size>} The size (in pixels) of the image.
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, size, options) {
+
+ // initilize the Zoomify pyramid for given size
+ this.initializeZoomify(size);
+
+ OpenLayers.Layer.Grid.prototype.initialize.apply(this, [
+ name, url, size, {}, options
+ ]);
+ },
+
+ /**
+ * Method: initializeZoomify
+ * It generates constants for all tiers of the Zoomify pyramid
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the image in pixels
+ *
+ */
+ initializeZoomify: function( size ) {
+
+ var imageSize = size.clone();
+ this.size = size.clone();
+ var tiles = new OpenLayers.Size(
+ Math.ceil( imageSize.w / this.standardTileSize ),
+ Math.ceil( imageSize.h / this.standardTileSize )
+ );
+
+ this.tierSizeInTiles = [tiles];
+ this.tierImageSize = [imageSize];
+
+ while (imageSize.w > this.standardTileSize ||
+ imageSize.h > this.standardTileSize ) {
+
+ imageSize = new OpenLayers.Size(
+ Math.floor( imageSize.w / 2 ),
+ Math.floor( imageSize.h / 2 )
+ );
+ tiles = new OpenLayers.Size(
+ Math.ceil( imageSize.w / this.standardTileSize ),
+ Math.ceil( imageSize.h / this.standardTileSize )
+ );
+ this.tierSizeInTiles.push( tiles );
+ this.tierImageSize.push( imageSize );
+ }
+
+ this.tierSizeInTiles.reverse();
+ this.tierImageSize.reverse();
+
+ this.numberOfTiers = this.tierSizeInTiles.length;
+ var resolutions = [1];
+ this.tileCountUpToTier = [0];
+ for (var i = 1; i < this.numberOfTiers; i++) {
+ resolutions.unshift(Math.pow(2, i));
+ this.tileCountUpToTier.push(
+ this.tierSizeInTiles[i-1].w * this.tierSizeInTiles[i-1].h +
+ this.tileCountUpToTier[i-1]
+ );
+ }
+ if (!this.serverResolutions) {
+ this.serverResolutions = resolutions;
+ }
+ },
+
+ /**
+ * APIMethod:destroy
+ */
+ destroy: function() {
+ // for now, nothing special to do here.
+ OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);
+
+ // Remove from memory the Zoomify pyramid - is that enough?
+ this.tileCountUpToTier.length = 0;
+ this.tierSizeInTiles.length = 0;
+ this.tierImageSize.length = 0;
+
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.Zoomify>} An exact clone of this <OpenLayers.Layer.Zoomify>
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.Zoomify(this.name,
+ this.url,
+ this.size,
+ this.options);
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * Method: getURL
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ *
+ * Returns:
+ * {String} A string with the layer's url and parameters and also the
+ * passed-in bounds and appropriate tile size specified as
+ * parameters
+ */
+ getURL: function (bounds) {
+ bounds = this.adjustBounds(bounds);
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+ var y = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h));
+ var z = this.getZoomForResolution( res );
+
+ var tileIndex = x + y * this.tierSizeInTiles[z].w + this.tileCountUpToTier[z];
+ var path = "TileGroup" + Math.floor( (tileIndex) / 256 ) +
+ "/" + z + "-" + x + "-" + y + ".jpg";
+ var url = this.url;
+ if (OpenLayers.Util.isArray(url)) {
+ url = this.selectUrl(path, url);
+ }
+ return url + path;
+ },
+
+ /**
+ * Method: getImageSize
+ * getImageSize returns size for a particular tile. If bounds are given as
+ * first argument, size is calculated (bottom-right tiles are non square).
+ *
+ */
+ getImageSize: function() {
+ if (arguments.length > 0) {
+ var bounds = this.adjustBounds(arguments[0]);
+ var res = this.getServerResolution();
+ var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+ var y = Math.round((this.tileOrigin.lat - bounds.top) / (res * this.tileSize.h));
+ var z = this.getZoomForResolution( res );
+ var w = this.standardTileSize;
+ var h = this.standardTileSize;
+ if (x == this.tierSizeInTiles[z].w -1 ) {
+ var w = this.tierImageSize[z].w % this.standardTileSize;
+ }
+ if (y == this.tierSizeInTiles[z].h -1 ) {
+ var h = this.tierImageSize[z].h % this.standardTileSize;
+ }
+ return (new OpenLayers.Size(w, h));
+ } else {
+ return this.tileSize;
+ }
+ },
+
+ /**
+ * APIMethod: setMap
+ * When the layer is added to a map, then we can fetch our origin
+ * (if we don't have one.)
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+ this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+ this.map.maxExtent.top);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Zoomify"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Map.js b/misc/openlayers/lib/OpenLayers/Map.js
new file mode 100644
index 0000000..56763fa
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Map.js
@@ -0,0 +1,2867 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Tween.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Map
+ * Instances of OpenLayers.Map are interactive maps embedded in a web page.
+ * Create a new map with the <OpenLayers.Map> constructor.
+ *
+ * On their own maps do not provide much functionality. To extend a map
+ * it's necessary to add controls (<OpenLayers.Control>) and
+ * layers (<OpenLayers.Layer>) to the map.
+ */
+OpenLayers.Map = OpenLayers.Class({
+
+ /**
+ * Constant: Z_INDEX_BASE
+ * {Object} Base z-indexes for different classes of thing
+ */
+ Z_INDEX_BASE: {
+ BaseLayer: 100,
+ Overlay: 325,
+ Feature: 725,
+ Popup: 750,
+ Control: 1000
+ },
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>}
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * map.events.register(type, obj, listener);
+ * (end)
+ *
+ * Listeners will be called with a reference to an event object. The
+ * properties of this event depends on exactly what happened.
+ *
+ * All event objects have at least the following properties:
+ * object - {Object} A reference to map.events.object.
+ * element - {DOMElement} A reference to map.events.element.
+ *
+ * Browser events have the following additional properties:
+ * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
+ * to the the map viewport).
+ *
+ * Supported map event types:
+ * preaddlayer - triggered before a layer has been added. The event
+ * object will include a *layer* property that references the layer
+ * to be added. When a listener returns "false" the adding will be
+ * aborted.
+ * addlayer - triggered after a layer has been added. The event object
+ * will include a *layer* property that references the added layer.
+ * preremovelayer - triggered before a layer has been removed. The event
+ * object will include a *layer* property that references the layer
+ * to be removed. When a listener returns "false" the removal will be
+ * aborted.
+ * removelayer - triggered after a layer has been removed. The event
+ * object will include a *layer* property that references the removed
+ * layer.
+ * changelayer - triggered after a layer name change, order change,
+ * opacity change, params change, visibility change (actual visibility,
+ * not the layer's visibility property) or attribution change (due to
+ * extent change). Listeners will receive an event object with *layer*
+ * and *property* properties. The *layer* property will be a reference
+ * to the changed layer. The *property* property will be a key to the
+ * changed property (name, order, opacity, params, visibility or
+ * attribution).
+ * movestart - triggered after the start of a drag, pan, or zoom. The event
+ * object may include a *zoomChanged* property that tells whether the
+ * zoom has changed.
+ * move - triggered after each drag, pan, or zoom
+ * moveend - triggered after a drag, pan, or zoom completes
+ * zoomend - triggered after a zoom completes
+ * mouseover - triggered after mouseover the map
+ * mouseout - triggered after mouseout the map
+ * mousemove - triggered after mousemove the map
+ * changebaselayer - triggered after the base layer changes
+ * updatesize - triggered after the <updateSize> method was executed
+ */
+
+ /**
+ * Property: id
+ * {String} Unique identifier for the map
+ */
+ id: null,
+
+ /**
+ * Property: fractionalZoom
+ * {Boolean} For a base layer that supports it, allow the map resolution
+ * to be set to a value between one of the values in the resolutions
+ * array. Default is false.
+ *
+ * When fractionalZoom is set to true, it is possible to zoom to
+ * an arbitrary extent. This requires a base layer from a source
+ * that supports requests for arbitrary extents (i.e. not cached
+ * tiles on a regular lattice). This means that fractionalZoom
+ * will not work with commercial layers (Google, Yahoo, VE), layers
+ * using TileCache, or any other pre-cached data sources.
+ *
+ * If you are using fractionalZoom, then you should also use
+ * <getResolutionForZoom> instead of layer.resolutions[zoom] as the
+ * former works for non-integer zoom levels.
+ */
+ fractionalZoom: false,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the map
+ */
+ events: null,
+
+ /**
+ * APIProperty: allOverlays
+ * {Boolean} Allow the map to function with "overlays" only. Defaults to
+ * false. If true, the lowest layer in the draw order will act as
+ * the base layer. In addition, if set to true, all layers will
+ * have isBaseLayer set to false when they are added to the map.
+ *
+ * Note:
+ * If you set map.allOverlays to true, then you *cannot* use
+ * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true,
+ * the lowest layer in the draw layer is the base layer. So, to change
+ * the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
+ * index to 0.
+ */
+ allOverlays: false,
+
+ /**
+ * APIProperty: div
+ * {DOMElement|String} The element that contains the map (or an id for
+ * that element). If the <OpenLayers.Map> constructor is called
+ * with two arguments, this should be provided as the first argument.
+ * Alternatively, the map constructor can be called with the options
+ * object as the only argument. In this case (one argument), a
+ * div property may or may not be provided. If the div property
+ * is not provided, the map can be rendered to a container later
+ * using the <render> method.
+ *
+ * Note:
+ * If you are calling <render> after map construction, do not use
+ * <maxResolution> auto. Instead, divide your <maxExtent> by your
+ * maximum expected dimension.
+ */
+ div: null,
+
+ /**
+ * Property: dragging
+ * {Boolean} The map is currently being dragged.
+ */
+ dragging: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} Size of the main div (this.div)
+ */
+ size: null,
+
+ /**
+ * Property: viewPortDiv
+ * {HTMLDivElement} The element that represents the map viewport
+ */
+ viewPortDiv: null,
+
+ /**
+ * Property: layerContainerOrigin
+ * {<OpenLayers.LonLat>} The lonlat at which the later container was
+ * re-initialized (on-zoom)
+ */
+ layerContainerOrigin: null,
+
+ /**
+ * Property: layerContainerDiv
+ * {HTMLDivElement} The element that contains the layers.
+ */
+ layerContainerDiv: null,
+
+ /**
+ * APIProperty: layers
+ * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
+ */
+ layers: null,
+
+ /**
+ * APIProperty: controls
+ * {Array(<OpenLayers.Control>)} List of controls associated with the map.
+ *
+ * If not provided in the map options at construction, the map will
+ * by default be given the following controls if present in the build:
+ * - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
+ * - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
+ * - <OpenLayers.Control.ArgParser>
+ * - <OpenLayers.Control.Attribution>
+ */
+ controls: null,
+
+ /**
+ * Property: popups
+ * {Array(<OpenLayers.Popup>)} List of popups associated with the map
+ */
+ popups: null,
+
+ /**
+ * APIProperty: baseLayer
+ * {<OpenLayers.Layer>} The currently selected base layer. This determines
+ * min/max zoom level, projection, etc.
+ */
+ baseLayer: null,
+
+ /**
+ * Property: center
+ * {<OpenLayers.LonLat>} The current center of the map
+ */
+ center: null,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution of the map.
+ */
+ resolution: null,
+
+ /**
+ * Property: zoom
+ * {Integer} The current zoom level of the map
+ */
+ zoom: 0,
+
+ /**
+ * Property: panRatio
+ * {Float} The ratio of the current extent within
+ * which panning will tween.
+ */
+ panRatio: 1.5,
+
+ /**
+ * APIProperty: options
+ * {Object} The options object passed to the class constructor. Read-only.
+ */
+ options: null,
+
+ // Options
+
+ /**
+ * APIProperty: tileSize
+ * {<OpenLayers.Size>} Set in the map options to override the default tile
+ * size for this map.
+ */
+ tileSize: null,
+
+ /**
+ * APIProperty: projection
+ * {String} Set in the map options to specify the default projection
+ * for layers added to this map. When using a projection other than EPSG:4326
+ * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
+ * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326".
+ * Note that the projection of the map is usually determined
+ * by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
+ */
+ projection: "EPSG:4326",
+
+ /**
+ * APIProperty: units
+ * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm',
+ * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection.
+ * Only required if both map and layers do not define a projection,
+ * or if they define a projection which does not define units
+ */
+ units: null,
+
+ /**
+ * APIProperty: resolutions
+ * {Array(Float)} A list of map resolutions (map units per pixel) in
+ * descending order. If this is not set in the layer constructor, it
+ * will be set based on other resolution related properties
+ * (maxExtent, maxResolution, maxScale, etc.).
+ */
+ resolutions: null,
+
+ /**
+ * APIProperty: maxResolution
+ * {Float} Required if you are not displaying the whole world on a tile
+ * with the size specified in <tileSize>.
+ */
+ maxResolution: null,
+
+ /**
+ * APIProperty: minResolution
+ * {Float}
+ */
+ minResolution: null,
+
+ /**
+ * APIProperty: maxScale
+ * {Float}
+ */
+ maxScale: null,
+
+ /**
+ * APIProperty: minScale
+ * {Float}
+ */
+ minScale: null,
+
+ /**
+ * APIProperty: maxExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The maximum extent for the map.
+ * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
+ * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
+ * else, defaults to null.
+ * To restrict user panning and zooming of the map, use <restrictedExtent> instead.
+ * The value for <maxExtent> will change calculations for tile URLs.
+ */
+ maxExtent: null,
+
+ /**
+ * APIProperty: minExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * The minimum extent for the map. Defaults to null.
+ */
+ minExtent: null,
+
+ /**
+ * APIProperty: restrictedExtent
+ * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * Limit map navigation to this extent where possible.
+ * If a non-null restrictedExtent is set, panning will be restricted
+ * to the given bounds. In addition, zooming to a resolution that
+ * displays more than the restricted extent will center the map
+ * on the restricted extent. If you wish to limit the zoom level
+ * or resolution, use maxResolution.
+ */
+ restrictedExtent: null,
+
+ /**
+ * APIProperty: numZoomLevels
+ * {Integer} Number of zoom levels for the map. Defaults to 16. Set a
+ * different value in the map options if needed.
+ */
+ numZoomLevels: 16,
+
+ /**
+ * APIProperty: theme
+ * {String} Relative path to a CSS file from which to load theme styles.
+ * Specify null in the map options (e.g. {theme: null}) if you
+ * want to get cascading style declarations - by putting links to
+ * stylesheets or style declarations directly in your page.
+ */
+ theme: null,
+
+ /**
+ * APIProperty: displayProjection
+ * {<OpenLayers.Projection>} Requires proj4js support for projections other
+ * than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
+ * several controls to display data to user. If this property is set,
+ * it will be set on any control which has a null displayProjection
+ * property at the time the control is added to the map.
+ */
+ displayProjection: null,
+
+ /**
+ * APIProperty: tileManager
+ * {<OpenLayers.TileManager>|Object} By default, and if the build contains
+ * TileManager.js, the map will use the TileManager to queue image requests
+ * and to cache tile image elements. To create a map without a TileManager
+ * configure the map with tileManager: null. To create a TileManager with
+ * non-default options, supply the options instead or alternatively supply
+ * an instance of {<OpenLayers.TileManager>}.
+ */
+
+ /**
+ * APIProperty: fallThrough
+ * {Boolean} Should OpenLayers allow events on the map to fall through to
+ * other elements on the page, or should it swallow them? (#457)
+ * Default is to swallow.
+ */
+ fallThrough: false,
+
+ /**
+ * APIProperty: autoUpdateSize
+ * {Boolean} Should OpenLayers automatically update the size of the map
+ * when the resize event is fired. Default is true.
+ */
+ autoUpdateSize: true,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ */
+ eventListeners: null,
+
+ /**
+ * Property: panTween
+ * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
+ */
+ panTween: null,
+
+ /**
+ * APIProperty: panMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
+ * animated panning.
+ */
+ panMethod: OpenLayers.Easing.Expo.easeOut,
+
+ /**
+ * Property: panDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is
+ * panned.
+ * Default is 50.
+ */
+ panDuration: 50,
+
+ /**
+ * Property: zoomTween
+ * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
+ */
+ zoomTween: null,
+
+ /**
+ * APIProperty: zoomMethod
+ * {Function} The Easing function to be used for tweening. Default is
+ * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
+ * animated zooming.
+ */
+ zoomMethod: OpenLayers.Easing.Quad.easeOut,
+
+ /**
+ * Property: zoomDuration
+ * {Integer} The number of steps to be passed to the
+ * OpenLayers.Tween.start() method when the map is zoomed.
+ * Default is 20.
+ */
+ zoomDuration: 20,
+
+ /**
+ * Property: paddingForPopups
+ * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent
+ * the popup from getting too close to the map border.
+ */
+ paddingForPopups : null,
+
+ /**
+ * Property: layerContainerOriginPx
+ * {Object} Cached object representing the layer container origin (in pixels).
+ */
+ layerContainerOriginPx: null,
+
+ /**
+ * Property: minPx
+ * {Object} An object with a 'x' and 'y' values that is the lower
+ * left of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid. It is also used in the getLonLatFromViewPortPx function
+ * of Layer.
+ */
+ minPx: null,
+
+ /**
+ * Property: maxPx
+ * {Object} An object with a 'x' and 'y' values that is the top
+ * right of maxExtent in viewport pixel space.
+ * Used to verify in moveByPx that the new location we're moving to
+ * is valid.
+ */
+ maxPx: null,
+
+ /**
+ * Constructor: OpenLayers.Map
+ * Constructor for a new OpenLayers.Map instance. There are two possible
+ * ways to call the map constructor. See the examples below.
+ *
+ * Parameters:
+ * div - {DOMElement|String} The element or id of an element in your page
+ * that will contain the map. May be omitted if the <div> option is
+ * provided or if you intend to call the <render> method later.
+ * options - {Object} Optional object with properties to tag onto the map.
+ *
+ * Valid options (in addition to the listed API properties):
+ * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * Only specify if <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains coordinates, center will be set
+ * by that, and this option will be ignored.
+ * zoom - {Number} The initial zoom level for the map. Only specify if
+ * <layers> is provided.
+ * Note that if an ArgParser/Permalink control is present,
+ * and the querystring contains a zoom level, zoom will be set
+ * by that, and this option will be ignored.
+ * extent - {<OpenLayers.Bounds>|Array} The initial extent of the map.
+ * If provided as an array, the array should consist of
+ * four values (left, bottom, right, top).
+ * Only specify if <center> and <zoom> are not provided.
+ *
+ * Examples:
+ * (code)
+ * // create a map with default options in an element with the id "map1"
+ * var map = new OpenLayers.Map("map1");
+ *
+ * // create a map with non-default options in an element with id "map2"
+ * var options = {
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
+ * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
+ * };
+ * var map = new OpenLayers.Map("map2", options);
+ *
+ * // map with non-default options - same as above but with a single argument,
+ * // a restricted extent, and using arrays for bounds and center
+ * var map = new OpenLayers.Map({
+ * div: "map_id",
+ * projection: "EPSG:3857",
+ * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
+ * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
+ * center: [-12356463.476333, 5621521.4854095]
+ * });
+ *
+ * // create a map without a reference to a container - call render later
+ * var map = new OpenLayers.Map({
+ * projection: "EPSG:3857",
+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
+ * });
+ * (end)
+ */
+ initialize: function (div, options) {
+
+ // If only one argument is provided, check if it is an object.
+ if(arguments.length === 1 && typeof div === "object") {
+ options = div;
+ div = options && options.div;
+ }
+
+ // Simple-type defaults are set in class definition.
+ // Now set complex-type defaults
+ this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+ OpenLayers.Map.TILE_HEIGHT);
+
+ this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
+
+ this.theme = OpenLayers._getScriptLocation() +
+ 'theme/default/style.css';
+
+ // backup original options
+ this.options = OpenLayers.Util.extend({}, options);
+
+ // now override default options
+ OpenLayers.Util.extend(this, options);
+
+ var projCode = this.projection instanceof OpenLayers.Projection ?
+ this.projection.projCode : this.projection;
+ OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
+
+ // allow extents and center to be arrays
+ if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
+ this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
+ }
+ if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
+ this.minExtent = new OpenLayers.Bounds(this.minExtent);
+ }
+ if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
+ this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
+ }
+ if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
+ this.center = new OpenLayers.LonLat(this.center);
+ }
+
+ // initialize layers array
+ this.layers = [];
+
+ this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
+
+ this.div = OpenLayers.Util.getElement(div);
+ if(!this.div) {
+ this.div = document.createElement("div");
+ this.div.style.height = "1px";
+ this.div.style.width = "1px";
+ }
+
+ OpenLayers.Element.addClass(this.div, 'olMap');
+
+ // the viewPortDiv is the outermost div we modify
+ var id = this.id + "_OpenLayers_ViewPort";
+ this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
+ "relative", null,
+ "hidden");
+ this.viewPortDiv.style.width = "100%";
+ this.viewPortDiv.style.height = "100%";
+ this.viewPortDiv.className = "olMapViewport";
+ this.div.appendChild(this.viewPortDiv);
+
+ this.events = new OpenLayers.Events(
+ this, this.viewPortDiv, null, this.fallThrough,
+ {includeXY: true}
+ );
+
+ if (OpenLayers.TileManager && this.tileManager !== null) {
+ if (!(this.tileManager instanceof OpenLayers.TileManager)) {
+ this.tileManager = new OpenLayers.TileManager(this.tileManager);
+ }
+ this.tileManager.addMap(this);
+ }
+
+ // the layerContainerDiv is the one that holds all the layers
+ id = this.id + "_OpenLayers_Container";
+ this.layerContainerDiv = OpenLayers.Util.createDiv(id);
+ this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
+ this.layerContainerOriginPx = {x: 0, y: 0};
+ this.applyTransform();
+
+ this.viewPortDiv.appendChild(this.layerContainerDiv);
+
+ this.updateSize();
+ if(this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+
+ if (this.autoUpdateSize === true) {
+ // updateSize on catching the window's resize
+ // Note that this is ok, as updateSize() does nothing if the
+ // map's size has not actually changed.
+ this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize,
+ this);
+ OpenLayers.Event.observe(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ // only append link stylesheet if the theme property is set
+ if(this.theme) {
+ // check existing links for equivalent url
+ var addNode = true;
+ var nodes = document.getElementsByTagName('link');
+ for(var i=0, len=nodes.length; i<len; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
+ this.theme)) {
+ addNode = false;
+ break;
+ }
+ }
+ // only add a new node if one with an equivalent url hasn't already
+ // been added
+ if(addNode) {
+ var cssNode = document.createElement('link');
+ cssNode.setAttribute('rel', 'stylesheet');
+ cssNode.setAttribute('type', 'text/css');
+ cssNode.setAttribute('href', this.theme);
+ document.getElementsByTagName('head')[0].appendChild(cssNode);
+ }
+ }
+
+ if (this.controls == null) { // default controls
+ this.controls = [];
+ if (OpenLayers.Control != null) { // running full or lite?
+ // Navigation or TouchNavigation depending on what is in build
+ if (OpenLayers.Control.Navigation) {
+ this.controls.push(new OpenLayers.Control.Navigation());
+ } else if (OpenLayers.Control.TouchNavigation) {
+ this.controls.push(new OpenLayers.Control.TouchNavigation());
+ }
+ if (OpenLayers.Control.Zoom) {
+ this.controls.push(new OpenLayers.Control.Zoom());
+ } else if (OpenLayers.Control.PanZoom) {
+ this.controls.push(new OpenLayers.Control.PanZoom());
+ }
+
+ if (OpenLayers.Control.ArgParser) {
+ this.controls.push(new OpenLayers.Control.ArgParser());
+ }
+ if (OpenLayers.Control.Attribution) {
+ this.controls.push(new OpenLayers.Control.Attribution());
+ }
+ }
+ }
+
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ this.addControlToMap(this.controls[i]);
+ }
+
+ this.popups = [];
+
+ this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
+
+
+ // always call map.destroy()
+ OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
+
+ // add any initial layers
+ if (options && options.layers) {
+ /**
+ * If you have set options.center, the map center property will be
+ * set at this point. However, since setCenter has not been called,
+ * addLayers gets confused. So we delete the map center in this
+ * case. Because the check below uses options.center, it will
+ * be properly set below.
+ */
+ delete this.center;
+ delete this.zoom;
+ this.addLayers(options.layers);
+ // set center (and optionally zoom)
+ if (options.center && !this.getCenter()) {
+ // zoom can be undefined here
+ this.setCenter(options.center, options.zoom);
+ }
+ }
+
+ if (this.panMethod) {
+ this.panTween = new OpenLayers.Tween(this.panMethod);
+ }
+ if (this.zoomMethod && this.applyTransform.transform) {
+ this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
+ }
+ },
+
+ /**
+ * APIMethod: getViewport
+ * Get the DOMElement representing the view port.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ getViewport: function() {
+ return this.viewPortDiv;
+ },
+
+ /**
+ * APIMethod: render
+ * Render the map to a specified container.
+ *
+ * Parameters:
+ * div - {String|DOMElement} The container that the map should be rendered
+ * to. If different than the current container, the map viewport
+ * will be moved from the current to the new container.
+ */
+ render: function(div) {
+ this.div = OpenLayers.Util.getElement(div);
+ OpenLayers.Element.addClass(this.div, 'olMap');
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ this.div.appendChild(this.viewPortDiv);
+ this.updateSize();
+ },
+
+ /**
+ * Method: unloadDestroy
+ * Function that is called to destroy the map on page unload. stored here
+ * so that if map is manually destroyed, we can unregister this.
+ */
+ unloadDestroy: null,
+
+ /**
+ * Method: updateSizeDestroy
+ * When the map is destroyed, we need to stop listening to updateSize
+ * events: this method stores the function we need to unregister in
+ * non-IE browsers.
+ */
+ updateSizeDestroy: null,
+
+ /**
+ * APIMethod: destroy
+ * Destroy this map.
+ * Note that if you are using an application which removes a container
+ * of the map from the DOM, you need to ensure that you destroy the
+ * map *before* this happens; otherwise, the page unload handler
+ * will fail because the DOM elements that map.destroy() wants
+ * to clean up will be gone. (See
+ * http://trac.osgeo.org/openlayers/ticket/2277 for more information).
+ * This will apply to GeoExt and also to other applications which
+ * modify the DOM of the container of the OpenLayers Map.
+ */
+ destroy:function() {
+ // if unloadDestroy is null, we've already been destroyed
+ if (!this.unloadDestroy) {
+ return false;
+ }
+
+ // make sure panning doesn't continue after destruction
+ if(this.panTween) {
+ this.panTween.stop();
+ this.panTween = null;
+ }
+ // make sure zooming doesn't continue after destruction
+ if(this.zoomTween) {
+ this.zoomTween.stop();
+ this.zoomTween = null;
+ }
+
+ // map has been destroyed. dont do it again!
+ OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
+ this.unloadDestroy = null;
+
+ if (this.updateSizeDestroy) {
+ OpenLayers.Event.stopObserving(window, 'resize',
+ this.updateSizeDestroy);
+ }
+
+ this.paddingForPopups = null;
+
+ if (this.controls != null) {
+ for (var i = this.controls.length - 1; i>=0; --i) {
+ this.controls[i].destroy();
+ }
+ this.controls = null;
+ }
+ if (this.layers != null) {
+ for (var i = this.layers.length - 1; i>=0; --i) {
+ //pass 'false' to destroy so that map wont try to set a new
+ // baselayer after each baselayer is removed
+ this.layers[i].destroy(false);
+ }
+ this.layers = null;
+ }
+ if (this.viewPortDiv && this.viewPortDiv.parentNode) {
+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+ }
+ this.viewPortDiv = null;
+
+ if (this.tileManager) {
+ this.tileManager.removeMap(this);
+ this.tileManager = null;
+ }
+
+ if(this.eventListeners) {
+ this.events.un(this.eventListeners);
+ this.eventListeners = null;
+ }
+ this.events.destroy();
+ this.events = null;
+
+ this.options = null;
+ },
+
+ /**
+ * APIMethod: setOptions
+ * Change the map options
+ *
+ * Parameters:
+ * options - {Object} Hashtable of options to tag to the map
+ */
+ setOptions: function(options) {
+ var updatePxExtent = this.minPx &&
+ options.restrictedExtent != this.restrictedExtent;
+ OpenLayers.Util.extend(this, options);
+ // force recalculation of minPx and maxPx
+ updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
+ forceZoomChange: true
+ });
+ },
+
+ /**
+ * APIMethod: getTileSize
+ * Get the tile size for the map
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+ getTileSize: function() {
+ return this.tileSize;
+ },
+
+
+ /**
+ * APIMethod: getBy
+ * Get a list of objects given a property and a match item.
+ *
+ * Parameters:
+ * array - {String} A property on the map whose value is an array.
+ * property - {String} A property on each item of the given array.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(map[array][i][property]) evaluates to true, the item will
+ * be included in the array returned. If no items are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array} An array of items where the given property matches the given
+ * criteria.
+ */
+ getBy: function(array, property, match) {
+ var test = (typeof match.test == "function");
+ var found = OpenLayers.Array.filter(this[array], function(item) {
+ return item[property] == match || (test && match.test(item[property]));
+ });
+ return found;
+ },
+
+ /**
+ * APIMethod: getLayersBy
+ * Get a list of layers with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A layer property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersBy: function(property, match) {
+ return this.getBy("layers", property, match);
+ },
+
+ /**
+ * APIMethod: getLayersByName
+ * Get a list of layers with names matching the given name.
+ *
+ * Parameters:
+ * match - {String | Object} A layer name. The name can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * name.test(layer.name) evaluates to true, the layer will be included
+ * in the list of layers returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByName: function(match) {
+ return this.getLayersBy("name", match);
+ },
+
+ /**
+ * APIMethod: getLayersByClass
+ * Get a list of layers of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A layer class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(layer.CLASS_NAME) evaluates to true, the layer will
+ * be included in the list of layers returned. If no layers are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getLayersByClass: function(match) {
+ return this.getLayersBy("CLASS_NAME", match);
+ },
+
+ /**
+ * APIMethod: getControlsBy
+ * Get a list of controls with properties matching the given criteria.
+ *
+ * Parameters:
+ * property - {String} A control property to be matched.
+ * match - {String | Object} A string to match. Can also be a regular
+ * expression literal or object. In addition, it can be any object
+ * with a method named test. For reqular expressions or other, if
+ * match.test(layer[property]) evaluates to true, the layer will be
+ * included in the array returned. If no layers are found, an empty
+ * array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given
+ * criteria. An empty array is returned if no matches are found.
+ */
+ getControlsBy: function(property, match) {
+ return this.getBy("controls", property, match);
+ },
+
+ /**
+ * APIMethod: getControlsByClass
+ * Get a list of controls of a given class (CLASS_NAME).
+ *
+ * Parameters:
+ * match - {String | Object} A control class name. The match can also be a
+ * regular expression literal or object. In addition, it can be any
+ * object with a method named test. For reqular expressions or other,
+ * if type.test(control.CLASS_NAME) evaluates to true, the control will
+ * be included in the list of controls returned. If no controls are
+ * found, an empty array is returned.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
+ * An empty array is returned if no matches are found.
+ */
+ getControlsByClass: function(match) {
+ return this.getControlsBy("CLASS_NAME", match);
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Layers to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getLayer
+ * Get a layer based on its id
+ *
+ * Parameters:
+ * id - {String} A layer id
+ *
+ * Returns:
+ * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
+ * layer collection, or null if not found.
+ */
+ getLayer: function(id) {
+ var foundLayer = null;
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ if (layer.id == id) {
+ foundLayer = layer;
+ break;
+ }
+ }
+ return foundLayer;
+ },
+
+ /**
+ * Method: setLayerZIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * zIdx - {int}
+ */
+ setLayerZIndex: function (layer, zIdx) {
+ layer.setZIndex(
+ this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+ + zIdx * 5 );
+ },
+
+ /**
+ * Method: resetLayersZIndex
+ * Reset each layer's z-index based on layer's array index
+ */
+ resetLayersZIndex: function() {
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ var layer = this.layers[i];
+ this.setLayerZIndex(layer, i);
+ }
+ },
+
+ /**
+ * APIMethod: addLayer
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Boolean} True if the layer has been added to the map.
+ */
+ addLayer: function (layer) {
+ for(var i = 0, len = this.layers.length; i < len; i++) {
+ if (this.layers[i] == layer) {
+ return false;
+ }
+ }
+ if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
+ return false;
+ }
+ if(this.allOverlays) {
+ layer.isBaseLayer = false;
+ }
+
+ layer.div.className = "olLayerDiv";
+ layer.div.style.overflow = "";
+ this.setLayerZIndex(layer, this.layers.length);
+
+ if (layer.isFixed) {
+ this.viewPortDiv.appendChild(layer.div);
+ } else {
+ this.layerContainerDiv.appendChild(layer.div);
+ }
+ this.layers.push(layer);
+ layer.setMap(this);
+
+ if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) {
+ if (this.baseLayer == null) {
+ // set the first baselaye we add as the baselayer
+ this.setBaseLayer(layer);
+ } else {
+ layer.setVisibility(false);
+ }
+ } else {
+ layer.redraw();
+ }
+
+ this.events.triggerEvent("addlayer", {layer: layer});
+ layer.events.triggerEvent("added", {map: this, layer: layer});
+ layer.afterAdd();
+
+ return true;
+ },
+
+ /**
+ * APIMethod: addLayers
+ *
+ * Parameters:
+ * layers - {Array(<OpenLayers.Layer>)}
+ */
+ addLayers: function (layers) {
+ for (var i=0, len=layers.length; i<len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ /**
+ * APIMethod: removeLayer
+ * Removes a layer from the map by removing its visual element (the
+ * layer.div property), then removing it from the map's internal list
+ * of layers, setting the layer's map property to null.
+ *
+ * a "removelayer" event is triggered.
+ *
+ * very worthy of mention is that simply removing a layer from a map
+ * will not cause the removal of any popups which may have been created
+ * by the layer. this is due to the fact that it was decided at some
+ * point that popups would not belong to layers. thus there is no way
+ * for us to know here to which layer the popup belongs.
+ *
+ * A simple solution to this is simply to call destroy() on the layer.
+ * the default OpenLayers.Layer class's destroy() function
+ * automatically takes care to remove itself from whatever map it has
+ * been attached to.
+ *
+ * The correct solution is for the layer itself to register an
+ * event-handler on "removelayer" and when it is called, if it
+ * recognizes itself as the layer being removed, then it cycles through
+ * its own personal list of popups, removing them from the map.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * setNewBaseLayer - {Boolean} Default is true
+ */
+ removeLayer: function(layer, setNewBaseLayer) {
+ if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
+ return;
+ }
+ if (setNewBaseLayer == null) {
+ setNewBaseLayer = true;
+ }
+
+ if (layer.isFixed) {
+ this.viewPortDiv.removeChild(layer.div);
+ } else {
+ this.layerContainerDiv.removeChild(layer.div);
+ }
+ OpenLayers.Util.removeItem(this.layers, layer);
+ layer.removeMap(this);
+ layer.map = null;
+
+ // if we removed the base layer, need to set a new one
+ if(this.baseLayer == layer) {
+ this.baseLayer = null;
+ if(setNewBaseLayer) {
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ var iLayer = this.layers[i];
+ if (iLayer.isBaseLayer || this.allOverlays) {
+ this.setBaseLayer(iLayer);
+ break;
+ }
+ }
+ }
+ }
+
+ this.resetLayersZIndex();
+
+ this.events.triggerEvent("removelayer", {layer: layer});
+ layer.events.triggerEvent("removed", {map: this, layer: layer});
+ },
+
+ /**
+ * APIMethod: getNumLayers
+ *
+ * Returns:
+ * {Int} The number of layers attached to the map.
+ */
+ getNumLayers: function () {
+ return this.layers.length;
+ },
+
+ /**
+ * APIMethod: getLayerIndex
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ *
+ * Returns:
+ * {Integer} The current (zero-based) index of the given layer in the map's
+ * layer stack. Returns -1 if the layer isn't on the map.
+ */
+ getLayerIndex: function (layer) {
+ return OpenLayers.Util.indexOf(this.layers, layer);
+ },
+
+ /**
+ * APIMethod: setLayerIndex
+ * Move the given layer to the specified (zero-based) index in the layer
+ * list, changing its z-index in the map display. Use
+ * map.getLayerIndex() to find out the current index of a layer. Note
+ * that this cannot (or at least should not) be effectively used to
+ * raise base layers above overlays.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * idx - {int}
+ */
+ setLayerIndex: function (layer, idx) {
+ var base = this.getLayerIndex(layer);
+ if (idx < 0) {
+ idx = 0;
+ } else if (idx > this.layers.length) {
+ idx = this.layers.length;
+ }
+ if (base != idx) {
+ this.layers.splice(base, 1);
+ this.layers.splice(idx, 0, layer);
+ for (var i=0, len=this.layers.length; i<len; i++) {
+ this.setLayerZIndex(this.layers[i], i);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "order"
+ });
+ if(this.allOverlays) {
+ if(idx === 0) {
+ this.setBaseLayer(layer);
+ } else if(this.baseLayer !== this.layers[0]) {
+ this.setBaseLayer(this.layers[0]);
+ }
+ }
+ }
+ },
+
+ /**
+ * APIMethod: raiseLayer
+ * Change the index of the given layer by delta. If delta is positive,
+ * the layer is moved up the map's layer stack; if delta is negative,
+ * the layer is moved down. Again, note that this cannot (or at least
+ * should not) be effectively used to raise base layers above overlays.
+ *
+ * Paremeters:
+ * layer - {<OpenLayers.Layer>}
+ * delta - {int}
+ */
+ raiseLayer: function (layer, delta) {
+ var idx = this.getLayerIndex(layer) + delta;
+ this.setLayerIndex(layer, idx);
+ },
+
+ /**
+ * APIMethod: setBaseLayer
+ * Allows user to specify one of the currently-loaded layers as the Map's
+ * new base layer.
+ *
+ * Parameters:
+ * newBaseLayer - {<OpenLayers.Layer>}
+ */
+ setBaseLayer: function(newBaseLayer) {
+
+ if (newBaseLayer != this.baseLayer) {
+
+ // ensure newBaseLayer is already loaded
+ if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
+
+ // preserve center and scale when changing base layers
+ var center = this.getCachedCenter();
+ var newResolution = OpenLayers.Util.getResolutionFromScale(
+ this.getScale(), newBaseLayer.units
+ );
+
+ // make the old base layer invisible
+ if (this.baseLayer != null && !this.allOverlays) {
+ this.baseLayer.setVisibility(false);
+ }
+
+ // set new baselayer
+ this.baseLayer = newBaseLayer;
+
+ if(!this.allOverlays || this.baseLayer.visibility) {
+ this.baseLayer.setVisibility(true);
+ // Layer may previously have been visible but not in range.
+ // In this case we need to redraw it to make it visible.
+ if (this.baseLayer.inRange === false) {
+ this.baseLayer.redraw();
+ }
+ }
+
+ // recenter the map
+ if (center != null) {
+ // new zoom level derived from old scale
+ var newZoom = this.getZoomForResolution(
+ newResolution || this.resolution, true
+ );
+ // zoom and force zoom change
+ this.setCenter(center, newZoom, false, true);
+ }
+
+ this.events.triggerEvent("changebaselayer", {
+ layer: this.baseLayer
+ });
+ }
+ }
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Control Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Controls to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addControl
+ * Add the passed over control to the map. Optionally
+ * position the control at the given pixel.
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControl: function (control, px) {
+ this.controls.push(control);
+ this.addControlToMap(control, px);
+ },
+
+ /**
+ * APIMethod: addControls
+ * Add all of the passed over controls to the map.
+ * You can pass over an optional second array
+ * with pixel-objects to position the controls.
+ * The indices of the two arrays should match and
+ * you can add null as pixel for those controls
+ * you want to be autopositioned.
+ *
+ * Parameters:
+ * controls - {Array(<OpenLayers.Control>)}
+ * pixels - {Array(<OpenLayers.Pixel>)}
+ */
+ addControls: function (controls, pixels) {
+ var pxs = (arguments.length === 1) ? [] : pixels;
+ for (var i=0, len=controls.length; i<len; i++) {
+ var ctrl = controls[i];
+ var px = (pxs[i]) ? pxs[i] : null;
+ this.addControl( ctrl, px );
+ }
+ },
+
+ /**
+ * Method: addControlToMap
+ *
+ * Parameters:
+ *
+ * control - {<OpenLayers.Control>}
+ * px - {<OpenLayers.Pixel>}
+ */
+ addControlToMap: function (control, px) {
+ // If a control doesn't have a div at this point, it belongs in the
+ // viewport.
+ control.outsideViewport = (control.div != null);
+
+ // If the map has a displayProjection, and the control doesn't, set
+ // the display projection.
+ if (this.displayProjection && !control.displayProjection) {
+ control.displayProjection = this.displayProjection;
+ }
+
+ control.setMap(this);
+ var div = control.draw(px);
+ if (div) {
+ if(!control.outsideViewport) {
+ div.style.zIndex = this.Z_INDEX_BASE['Control'] +
+ this.controls.length;
+ this.viewPortDiv.appendChild( div );
+ }
+ }
+ if(control.autoActivate) {
+ control.activate();
+ }
+ },
+
+ /**
+ * APIMethod: getControl
+ *
+ * Parameters:
+ * id - {String} ID of the control to return.
+ *
+ * Returns:
+ * {<OpenLayers.Control>} The control from the map's list of controls
+ * which has a matching 'id'. If none found,
+ * returns null.
+ */
+ getControl: function (id) {
+ var returnControl = null;
+ for(var i=0, len=this.controls.length; i<len; i++) {
+ var control = this.controls[i];
+ if (control.id == id) {
+ returnControl = control;
+ break;
+ }
+ }
+ return returnControl;
+ },
+
+ /**
+ * APIMethod: removeControl
+ * Remove a control from the map. Removes the control both from the map
+ * object's internal array of controls, as well as from the map's
+ * viewPort (assuming the control was not added outsideViewport)
+ *
+ * Parameters:
+ * control - {<OpenLayers.Control>} The control to remove.
+ */
+ removeControl: function (control) {
+ //make sure control is non-null and actually part of our map
+ if ( (control) && (control == this.getControl(control.id)) ) {
+ if (control.div && (control.div.parentNode == this.viewPortDiv)) {
+ this.viewPortDiv.removeChild(control.div);
+ }
+ OpenLayers.Util.removeItem(this.controls, control);
+ }
+ },
+
+ /********************************************************/
+ /* */
+ /* Popup Functions */
+ /* */
+ /* The following functions deal with adding and */
+ /* removing Popups to and from the Map */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: addPopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ * exclusive - {Boolean} If true, closes all other popups first
+ */
+ addPopup: function(popup, exclusive) {
+
+ if (exclusive) {
+ //remove all other popups from screen
+ for (var i = this.popups.length - 1; i >= 0; --i) {
+ this.removePopup(this.popups[i]);
+ }
+ }
+
+ popup.map = this;
+ this.popups.push(popup);
+ var popupDiv = popup.draw();
+ if (popupDiv) {
+ popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
+ this.popups.length;
+ this.layerContainerDiv.appendChild(popupDiv);
+ }
+ },
+
+ /**
+ * APIMethod: removePopup
+ *
+ * Parameters:
+ * popup - {<OpenLayers.Popup>}
+ */
+ removePopup: function(popup) {
+ OpenLayers.Util.removeItem(this.popups, popup);
+ if (popup.div) {
+ try { this.layerContainerDiv.removeChild(popup.div); }
+ catch (e) { } // Popups sometimes apparently get disconnected
+ // from the layerContainerDiv, and cause complaints.
+ }
+ popup.map = null;
+ },
+
+ /********************************************************/
+ /* */
+ /* Container Div Functions */
+ /* */
+ /* The following functions deal with the access to */
+ /* and maintenance of the size of the container div */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
+ * size, in pixels, of the div into which OpenLayers
+ * has been loaded.
+ * Note - A clone() of this locally cached variable is
+ * returned, so as not to allow users to modify it.
+ */
+ getSize: function () {
+ var size = null;
+ if (this.size != null) {
+ size = this.size.clone();
+ }
+ return size;
+ },
+
+ /**
+ * APIMethod: updateSize
+ * This function should be called by any external code which dynamically
+ * changes the size of the map div (because mozilla wont let us catch
+ * the "onresize" for an element)
+ */
+ updateSize: function() {
+ // the div might have moved on the page, also
+ var newSize = this.getCurrentSize();
+ if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
+ this.events.clearMouseCache();
+ var oldSize = this.getSize();
+ if (oldSize == null) {
+ this.size = oldSize = newSize;
+ }
+ if (!newSize.equals(oldSize)) {
+
+ // store the new size
+ this.size = newSize;
+
+ //notify layers of mapresize
+ for(var i=0, len=this.layers.length; i<len; i++) {
+ this.layers[i].onMapResize();
+ }
+
+ var center = this.getCachedCenter();
+
+ if (this.baseLayer != null && center != null) {
+ var zoom = this.getZoom();
+ this.zoom = null;
+ this.setCenter(center, zoom);
+ }
+
+ }
+ }
+ this.events.triggerEvent("updatesize");
+ },
+
+ /**
+ * Method: getCurrentSize
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
+ * of the map div
+ */
+ getCurrentSize: function() {
+
+ var size = new OpenLayers.Size(this.div.clientWidth,
+ this.div.clientHeight);
+
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = this.div.offsetWidth;
+ size.h = this.div.offsetHeight;
+ }
+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+ size.w = parseInt(this.div.style.width);
+ size.h = parseInt(this.div.style.height);
+ }
+ return size;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * center - {<OpenLayers.LonLat>} Default is this.getCenter()
+ * resolution - {float} Default is this.getResolution()
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A bounds based on resolution, center, and
+ * current mapsize.
+ */
+ calculateBounds: function(center, resolution) {
+
+ var extent = null;
+
+ if (center == null) {
+ center = this.getCachedCenter();
+ }
+ if (resolution == null) {
+ resolution = this.getResolution();
+ }
+
+ if ((center != null) && (resolution != null)) {
+ var halfWDeg = (this.size.w * resolution) / 2;
+ var halfHDeg = (this.size.h * resolution) / 2;
+
+ extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ }
+
+ return extent;
+ },
+
+
+ /********************************************************/
+ /* */
+ /* Zoom, Center, Pan Functions */
+ /* */
+ /* The following functions handle the validation, */
+ /* getting and setting of the Zoom Level and Center */
+ /* as well as the panning of the Map */
+ /* */
+ /********************************************************/
+ /**
+ * APIMethod: getCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCenter: function () {
+ var center = null;
+ var cachedCenter = this.getCachedCenter();
+ if (cachedCenter) {
+ center = cachedCenter.clone();
+ }
+ return center;
+ },
+
+ /**
+ * Method: getCachedCenter
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getCachedCenter: function() {
+ if (!this.center && this.size) {
+ this.center = this.getLonLatFromViewPortPx({
+ x: this.size.w / 2,
+ y: this.size.h / 2
+ });
+ }
+ return this.center;
+ },
+
+ /**
+ * APIMethod: getZoom
+ *
+ * Returns:
+ * {Integer}
+ */
+ getZoom: function () {
+ return this.zoom;
+ },
+
+ /**
+ * APIMethod: pan
+ * Allows user to pan by a value of screen pixels
+ *
+ * Parameters:
+ * dx - {Integer}
+ * dy - {Integer}
+ * options - {Object} Options to configure panning:
+ * - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
+ * - *dragging* {Boolean} Call setCenter with dragging true. Default is
+ * false.
+ */
+ pan: function(dx, dy, options) {
+ options = OpenLayers.Util.applyDefaults(options, {
+ animate: true,
+ dragging: false
+ });
+ if (options.dragging) {
+ if (dx != 0 || dy != 0) {
+ this.moveByPx(dx, dy);
+ }
+ } else {
+ // getCenter
+ var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());
+
+ // adjust
+ var newCenterPx = centerPx.add(dx, dy);
+
+ if (this.dragging || !newCenterPx.equals(centerPx)) {
+ var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
+ if (options.animate) {
+ this.panTo(newCenterLonLat);
+ } else {
+ this.moveTo(newCenterLonLat);
+ if(this.dragging) {
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }
+ }
+ }
+ }
+
+ },
+
+ /**
+ * APIMethod: panTo
+ * Allows user to pan to a new lonlat
+ * If the new lonlat is in the current extent the map will slide smoothly
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ panTo: function(lonlat) {
+ if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
+ var center = this.getCachedCenter();
+
+ // center will not change, don't do nothing
+ if (lonlat.equals(center)) {
+ return;
+ }
+
+ var from = this.getPixelFromLonLat(center);
+ var to = this.getPixelFromLonLat(lonlat);
+ var vector = { x: to.x - from.x, y: to.y - from.y };
+ var last = { x: 0, y: 0 };
+
+ this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
+ callbacks: {
+ eachStep: OpenLayers.Function.bind(function(px) {
+ var x = px.x - last.x,
+ y = px.y - last.y;
+ this.moveByPx(x, y);
+ last.x = Math.round(px.x);
+ last.y = Math.round(px.y);
+ }, this),
+ done: OpenLayers.Function.bind(function(px) {
+ this.moveTo(lonlat);
+ this.dragging = false;
+ this.events.triggerEvent("moveend");
+ }, this)
+ }
+ });
+ } else {
+ this.setCenter(lonlat);
+ }
+ },
+
+ /**
+ * APIMethod: setCenter
+ * Set the map center (and optionally, the zoom level).
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
+ * If provided as array, the first value is the x coordinate,
+ * and the 2nd value is the y coordinate.
+ * zoom - {Integer} Optional zoom level.
+ * dragging - {Boolean} Specifies whether or not to trigger
+ * movestart/end events
+ * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom
+ * change events (needed on baseLayer change)
+ *
+ * TBD: reconsider forceZoomChange in 3.0
+ */
+ setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
+ if (this.panTween) {
+ this.panTween.stop();
+ }
+ if (this.zoomTween) {
+ this.zoomTween.stop();
+ }
+ this.moveTo(lonlat, zoom, {
+ 'dragging': dragging,
+ 'forceZoomChange': forceZoomChange
+ });
+ },
+
+ /**
+ * Method: moveByPx
+ * Drag the map by pixels.
+ *
+ * Parameters:
+ * dx - {Number}
+ * dy - {Number}
+ */
+ moveByPx: function(dx, dy) {
+ var hw = this.size.w / 2;
+ var hh = this.size.h / 2;
+ var x = hw + dx;
+ var y = hh + dy;
+ var wrapDateLine = this.baseLayer.wrapDateLine;
+ var xRestriction = 0;
+ var yRestriction = 0;
+ if (this.restrictedExtent) {
+ xRestriction = hw;
+ yRestriction = hh;
+ // wrapping the date line makes no sense for restricted extents
+ wrapDateLine = false;
+ }
+ dx = wrapDateLine ||
+ x <= this.maxPx.x - xRestriction &&
+ x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
+ dy = y <= this.maxPx.y - yRestriction &&
+ y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
+ if (dx || dy) {
+ if (!this.dragging) {
+ this.dragging = true;
+ this.events.triggerEvent("movestart");
+ }
+ this.center = null;
+ if (dx) {
+ this.layerContainerOriginPx.x -= dx;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ }
+ if (dy) {
+ this.layerContainerOriginPx.y -= dy;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ this.applyTransform();
+ var layer, i, len;
+ for (i=0, len=this.layers.length; i<len; ++i) {
+ layer = this.layers[i];
+ if (layer.visibility &&
+ (layer === this.baseLayer || layer.inRange)) {
+ layer.moveByPx(dx, dy);
+ layer.events.triggerEvent("move");
+ }
+ }
+ this.events.triggerEvent("move");
+ }
+ },
+
+ /**
+ * Method: adjustZoom
+ *
+ * Parameters:
+ * zoom - {Number} The zoom level to adjust
+ *
+ * Returns:
+ * {Integer} Adjusted zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent.
+ */
+ adjustZoom: function(zoom) {
+ if (this.baseLayer && this.baseLayer.wrapDateLine) {
+ var resolution, resolutions = this.baseLayer.resolutions,
+ maxResolution = this.getMaxExtent().getWidth() / this.size.w;
+ if (this.getResolutionForZoom(zoom) > maxResolution) {
+ if (this.fractionalZoom) {
+ zoom = this.getZoomForResolution(maxResolution);
+ } else {
+ for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
+ if (resolutions[i] <= maxResolution) {
+ zoom = i;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getMinZoom
+ * Returns the minimum zoom level for the current map view. If the base
+ * layer is configured with <wrapDateLine> set to true, this will be the
+ * first zoom level that shows no more than one world width in the current
+ * map viewport. Components that rely on this value (e.g. zoom sliders)
+ * should also listen to the map's "updatesize" event and call this method
+ * in the "updatesize" listener.
+ *
+ * Returns:
+ * {Number} Minimum zoom level that shows a map not wider than its
+ * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
+ * configured with <fractionalZoom> set to true.
+ */
+ getMinZoom: function() {
+ return this.adjustZoom(0);
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ * zoom - {Integer}
+ * options - {Object}
+ */
+ moveTo: function(lonlat, zoom, options) {
+ if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
+ lonlat = new OpenLayers.LonLat(lonlat);
+ }
+ if (!options) {
+ options = {};
+ }
+ if (zoom != null) {
+ zoom = parseFloat(zoom);
+ if (!this.fractionalZoom) {
+ zoom = Math.round(zoom);
+ }
+ }
+ var requestedZoom = zoom;
+ zoom = this.adjustZoom(zoom);
+ if (zoom !== requestedZoom) {
+ // zoom was adjusted, so keep old lonlat to avoid panning
+ lonlat = this.getCenter();
+ }
+ // dragging is false by default
+ var dragging = options.dragging || this.dragging;
+ // forceZoomChange is false by default
+ var forceZoomChange = options.forceZoomChange;
+
+ if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
+ lonlat = this.maxExtent.getCenterLonLat();
+ this.center = lonlat.clone();
+ }
+
+ if(this.restrictedExtent != null) {
+ // In 3.0, decide if we want to change interpretation of maxExtent.
+ if(lonlat == null) {
+ lonlat = this.center;
+ }
+ if(zoom == null) {
+ zoom = this.getZoom();
+ }
+ var resolution = this.getResolutionForZoom(zoom);
+ var extent = this.calculateBounds(lonlat, resolution);
+ if(!this.restrictedExtent.containsBounds(extent)) {
+ var maxCenter = this.restrictedExtent.getCenterLonLat();
+ if(extent.getWidth() > this.restrictedExtent.getWidth()) {
+ lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);
+ } else if(extent.left < this.restrictedExtent.left) {
+ lonlat = lonlat.add(this.restrictedExtent.left -
+ extent.left, 0);
+ } else if(extent.right > this.restrictedExtent.right) {
+ lonlat = lonlat.add(this.restrictedExtent.right -
+ extent.right, 0);
+ }
+ if(extent.getHeight() > this.restrictedExtent.getHeight()) {
+ lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);
+ } else if(extent.bottom < this.restrictedExtent.bottom) {
+ lonlat = lonlat.add(0, this.restrictedExtent.bottom -
+ extent.bottom);
+ }
+ else if(extent.top > this.restrictedExtent.top) {
+ lonlat = lonlat.add(0, this.restrictedExtent.top -
+ extent.top);
+ }
+ }
+ }
+
+ var zoomChanged = forceZoomChange || (
+ (this.isValidZoomLevel(zoom)) &&
+ (zoom != this.getZoom()) );
+
+ var centerChanged = (this.isValidLonLat(lonlat)) &&
+ (!lonlat.equals(this.center));
+
+ // if neither center nor zoom will change, no need to do anything
+ if (zoomChanged || centerChanged || dragging) {
+ dragging || this.events.triggerEvent("movestart", {
+ zoomChanged: zoomChanged
+ });
+
+ if (centerChanged) {
+ if (!zoomChanged && this.center) {
+ // if zoom hasnt changed, just slide layerContainer
+ // (must be done before setting this.center to new value)
+ this.centerLayerContainer(lonlat);
+ }
+ this.center = lonlat.clone();
+ }
+
+ var res = zoomChanged ?
+ this.getResolutionForZoom(zoom) : this.getResolution();
+ // (re)set the layerContainerDiv's location
+ if (zoomChanged || this.layerContainerOrigin == null) {
+ this.layerContainerOrigin = this.getCachedCenter();
+ this.layerContainerOriginPx.x = 0;
+ this.layerContainerOriginPx.y = 0;
+ this.applyTransform();
+ var maxExtent = this.getMaxExtent({restricted: true});
+ var maxExtentCenter = maxExtent.getCenterLonLat();
+ var lonDelta = this.center.lon - maxExtentCenter.lon;
+ var latDelta = maxExtentCenter.lat - this.center.lat;
+ var extentWidth = Math.round(maxExtent.getWidth() / res);
+ var extentHeight = Math.round(maxExtent.getHeight() / res);
+ this.minPx = {
+ x: (this.size.w - extentWidth) / 2 - lonDelta / res,
+ y: (this.size.h - extentHeight) / 2 - latDelta / res
+ };
+ this.maxPx = {
+ x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
+ y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
+ };
+ }
+
+ if (zoomChanged) {
+ this.zoom = zoom;
+ this.resolution = res;
+ }
+
+ var bounds = this.getExtent();
+
+ //send the move call to the baselayer and all the overlays
+
+ if(this.baseLayer.visibility) {
+ this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || this.baseLayer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+
+ bounds = this.baseLayer.getExtent();
+
+ for (var i=this.layers.length-1; i>=0; --i) {
+ var layer = this.layers[i];
+ if (layer !== this.baseLayer && !layer.isBaseLayer) {
+ var inRange = layer.calculateInRange();
+ if (layer.inRange != inRange) {
+ // the inRange property has changed. If the layer is
+ // no longer in range, we turn it off right away. If
+ // the layer is no longer out of range, the moveTo
+ // call below will turn on the layer.
+ layer.inRange = inRange;
+ if (!inRange) {
+ layer.display(false);
+ }
+ this.events.triggerEvent("changelayer", {
+ layer: layer, property: "visibility"
+ });
+ }
+ if (inRange && layer.visibility) {
+ layer.moveTo(bounds, zoomChanged, options.dragging);
+ options.dragging || layer.events.triggerEvent(
+ "moveend", {zoomChanged: zoomChanged}
+ );
+ }
+ }
+ }
+
+ this.events.triggerEvent("move");
+ dragging || this.events.triggerEvent("moveend");
+
+ if (zoomChanged) {
+ //redraw popups
+ for (var i=0, len=this.popups.length; i<len; i++) {
+ this.popups[i].updatePosition();
+ }
+ this.events.triggerEvent("zoomend");
+ }
+ }
+ },
+
+ /**
+ * Method: centerLayerContainer
+ * This function takes care to recenter the layerContainerDiv.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ */
+ centerLayerContainer: function (lonlat) {
+ var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
+ var newPx = this.getViewPortPxFromLonLat(lonlat);
+
+ if ((originPx != null) && (newPx != null)) {
+ var oldLeft = this.layerContainerOriginPx.x;
+ var oldTop = this.layerContainerOriginPx.y;
+ var newLeft = Math.round(originPx.x - newPx.x);
+ var newTop = Math.round(originPx.y - newPx.y);
+ this.applyTransform(
+ (this.layerContainerOriginPx.x = newLeft),
+ (this.layerContainerOriginPx.y = newTop));
+ var dx = oldLeft - newLeft;
+ var dy = oldTop - newTop;
+ this.minPx.x -= dx;
+ this.maxPx.x -= dx;
+ this.minPx.y -= dy;
+ this.maxPx.y -= dy;
+ }
+ },
+
+ /**
+ * Method: isValidZoomLevel
+ *
+ * Parameters:
+ * zoomLevel - {Integer}
+ *
+ * Returns:
+ * {Boolean} Whether or not the zoom level passed in is non-null and
+ * within the min/max range of zoom levels.
+ */
+ isValidZoomLevel: function(zoomLevel) {
+ return ( (zoomLevel != null) &&
+ (zoomLevel >= 0) &&
+ (zoomLevel < this.getNumZoomLevels()) );
+ },
+
+ /**
+ * Method: isValidLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {Boolean} Whether or not the lonlat passed in is non-null and within
+ * the maxExtent bounds
+ */
+ isValidLonLat: function(lonlat) {
+ var valid = false;
+ if (lonlat != null) {
+ var maxExtent = this.getMaxExtent();
+ var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
+ valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
+ }
+ return valid;
+ },
+
+ /********************************************************/
+ /* */
+ /* Layer Options */
+ /* */
+ /* Accessor functions to Layer Options parameters */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getProjection
+ * This method returns a string representing the projection. In
+ * the case of projection support, this will be the srsCode which
+ * is loaded -- otherwise it will simply be the string value that
+ * was passed to the projection at startup.
+ *
+ * FIXME: In 3.0, we will remove getProjectionObject, and instead
+ * return a Projection object from this function.
+ *
+ * Returns:
+ * {String} The Projection string from the base layer or null.
+ */
+ getProjection: function() {
+ var projection = this.getProjectionObject();
+ return projection ? projection.getCode() : null;
+ },
+
+ /**
+ * APIMethod: getProjectionObject
+ * Returns the projection obect from the baselayer.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} The Projection of the base layer.
+ */
+ getProjectionObject: function() {
+ var projection = null;
+ if (this.baseLayer != null) {
+ projection = this.baseLayer.projection;
+ }
+ return projection;
+ },
+
+ /**
+ * APIMethod: getMaxResolution
+ *
+ * Returns:
+ * {String} The Map's Maximum Resolution
+ */
+ getMaxResolution: function() {
+ var maxResolution = null;
+ if (this.baseLayer != null) {
+ maxResolution = this.baseLayer.maxResolution;
+ }
+ return maxResolution;
+ },
+
+ /**
+ * APIMethod: getMaxExtent
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} If true, returns restricted extent (if it is
+ * available.)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} The maxExtent property as set on the current
+ * baselayer, unless the 'restricted' option is set, in which case
+ * the 'restrictedExtent' option from the map is returned (if it
+ * is set).
+ */
+ getMaxExtent: function (options) {
+ var maxExtent = null;
+ if(options && options.restricted && this.restrictedExtent){
+ maxExtent = this.restrictedExtent;
+ } else if (this.baseLayer != null) {
+ maxExtent = this.baseLayer.maxExtent;
+ }
+ return maxExtent;
+ },
+
+ /**
+ * APIMethod: getNumZoomLevels
+ *
+ * Returns:
+ * {Integer} The total number of zoom levels that can be displayed by the
+ * current baseLayer.
+ */
+ getNumZoomLevels: function() {
+ var numZoomLevels = null;
+ if (this.baseLayer != null) {
+ numZoomLevels = this.baseLayer.numZoomLevels;
+ }
+ return numZoomLevels;
+ },
+
+ /********************************************************/
+ /* */
+ /* Baselayer Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API?, are all merely wrappers to the */
+ /* the same calls on whatever layer is set as */
+ /* the current base layer */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: getExtent
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
+ * bounds of the current viewPort.
+ * If no baselayer is set, returns null.
+ */
+ getExtent: function () {
+ var extent = null;
+ if (this.baseLayer != null) {
+ extent = this.baseLayer.getExtent();
+ }
+ return extent;
+ },
+
+ /**
+ * APIMethod: getResolution
+ *
+ * Returns:
+ * {Float} The current resolution of the map.
+ * If no baselayer is set, returns null.
+ */
+ getResolution: function () {
+ var resolution = null;
+ if (this.baseLayer != null) {
+ resolution = this.baseLayer.getResolution();
+ } else if(this.allOverlays === true && this.layers.length > 0) {
+ // while adding the 1st layer to the map in allOverlays mode,
+ // this.baseLayer is not set yet when we need the resolution
+ // for calculateInRange.
+ resolution = this.layers[0].getResolution();
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getUnits
+ *
+ * Returns:
+ * {Float} The current units of the map.
+ * If no baselayer is set, returns null.
+ */
+ getUnits: function () {
+ var units = null;
+ if (this.baseLayer != null) {
+ units = this.baseLayer.units;
+ }
+ return units;
+ },
+
+ /**
+ * APIMethod: getScale
+ *
+ * Returns:
+ * {Float} The current scale denominator of the map.
+ * If no baselayer is set, returns null.
+ */
+ getScale: function () {
+ var scale = null;
+ if (this.baseLayer != null) {
+ var res = this.getResolution();
+ var units = this.baseLayer.units;
+ scale = OpenLayers.Util.getScaleFromResolution(res, units);
+ }
+ return scale;
+ },
+
+
+ /**
+ * APIMethod: getZoomForExtent
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified bounds.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForExtent: function (bounds, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForExtent(bounds, closest);
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getResolutionForZoom
+ *
+ * Parameters:
+ * zoom - {Float}
+ *
+ * Returns:
+ * {Float} A suitable resolution for the specified zoom. If no baselayer
+ * is set, returns null.
+ */
+ getResolutionForZoom: function(zoom) {
+ var resolution = null;
+ if(this.baseLayer) {
+ resolution = this.baseLayer.getResolutionForZoom(zoom);
+ }
+ return resolution;
+ },
+
+ /**
+ * APIMethod: getZoomForResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * closest - {Boolean} Find the zoom level that corresponds to the absolute
+ * closest resolution, which may result in a zoom whose corresponding
+ * resolution is actually smaller than we would have desired (if this
+ * is being called from a getZoomForExtent() call, then this means that
+ * the returned zoom index might not actually contain the entire
+ * extent specified... but it'll be close).
+ * Default is false.
+ *
+ * Returns:
+ * {Integer} A suitable zoom level for the specified resolution.
+ * If no baselayer is set, returns null.
+ */
+ getZoomForResolution: function(resolution, closest) {
+ var zoom = null;
+ if (this.baseLayer != null) {
+ zoom = this.baseLayer.getZoomForResolution(resolution, closest);
+ }
+ return zoom;
+ },
+
+ /********************************************************/
+ /* */
+ /* Zooming Functions */
+ /* */
+ /* The following functions, all publicly exposed */
+ /* in the API, are all merely wrappers to the */
+ /* the setCenter() function */
+ /* */
+ /********************************************************/
+
+ /**
+ * APIMethod: zoomTo
+ * Zoom to a specific zoom level. Zooming will be animated unless the map
+ * is configured with {zoomMethod: null}. To zoom without animation, use
+ * <setCenter> without a lonlat argument.
+ *
+ * Parameters:
+ * zoom - {Integer}
+ */
+ zoomTo: function(zoom, xy) {
+ // non-API arguments:
+ // xy - {<OpenLayers.Pixel>} optional zoom origin
+
+ var map = this;
+ if (map.isValidZoomLevel(zoom)) {
+ if (map.baseLayer.wrapDateLine) {
+ zoom = map.adjustZoom(zoom);
+ }
+ if (map.zoomTween) {
+ var currentRes = map.getResolution(),
+ targetRes = map.getResolutionForZoom(zoom),
+ start = {scale: 1},
+ end = {scale: currentRes / targetRes};
+ if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
+ // update the end scale, and reuse the running zoomTween
+ map.zoomTween.finish = {
+ scale: map.zoomTween.finish.scale * end.scale
+ };
+ } else {
+ if (!xy) {
+ var size = map.getSize();
+ xy = {x: size.w / 2, y: size.h / 2};
+ }
+ map.zoomTween.start(start, end, map.zoomDuration, {
+ minFrameRate: 50, // don't spend much time zooming
+ callbacks: {
+ eachStep: function(data) {
+ var containerOrigin = map.layerContainerOriginPx,
+ scale = data.scale,
+ dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
+ dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
+ map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
+ },
+ done: function(data) {
+ map.applyTransform();
+ var resolution = map.getResolution() / data.scale,
+ zoom = map.getZoomForResolution(resolution, true)
+ map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
+ }
+ }
+ });
+ }
+ } else {
+ var center = xy ?
+ map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
+ null;
+ map.setCenter(center, zoom);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: zoomIn
+ *
+ */
+ zoomIn: function() {
+ this.zoomTo(this.getZoom() + 1);
+ },
+
+ /**
+ * APIMethod: zoomOut
+ *
+ */
+ zoomOut: function() {
+ this.zoomTo(this.getZoom() - 1);
+ },
+
+ /**
+ * APIMethod: zoomToExtent
+ * Zoom to the passed in bounds, recenter
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
+ * should consist of four values (left, bottom, right, top).
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified bounds. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToExtent: function(bounds, closest) {
+ if (!(bounds instanceof OpenLayers.Bounds)) {
+ bounds = new OpenLayers.Bounds(bounds);
+ }
+ var center = bounds.getCenterLonLat();
+ if (this.baseLayer.wrapDateLine) {
+ var maxExtent = this.getMaxExtent();
+
+ //fix straddling bounds (in the case of a bbox that straddles the
+ // dateline, it's left and right boundaries will appear backwards.
+ // we fix this by allowing a right value that is greater than the
+ // max value at the dateline -- this allows us to pass a valid
+ // bounds to calculate zoom)
+ //
+ bounds = bounds.clone();
+ while (bounds.right < bounds.left) {
+ bounds.right += maxExtent.getWidth();
+ }
+ //if the bounds was straddling (see above), then the center point
+ // we got from it was wrong. So we take our new bounds and ask it
+ // for the center.
+ //
+ center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
+ }
+ this.setCenter(center, this.getZoomForExtent(bounds, closest));
+ },
+
+ /**
+ * APIMethod: zoomToMaxExtent
+ * Zoom to the full extent and recenter.
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Allowed Options:
+ * restricted - {Boolean} True to zoom to restricted extent if it is
+ * set. Defaults to true.
+ */
+ zoomToMaxExtent: function(options) {
+ //restricted is true by default
+ var restricted = (options) ? options.restricted : true;
+
+ var maxExtent = this.getMaxExtent({
+ 'restricted': restricted
+ });
+ this.zoomToExtent(maxExtent);
+ },
+
+ /**
+ * APIMethod: zoomToScale
+ * Zoom to a specified scale
+ *
+ * Parameters:
+ * scale - {float}
+ * closest - {Boolean} Find the zoom level that most closely fits the
+ * specified scale. Note that this may result in a zoom that does
+ * not exactly contain the entire extent.
+ * Default is false.
+ *
+ */
+ zoomToScale: function(scale, closest) {
+ var res = OpenLayers.Util.getResolutionFromScale(scale,
+ this.baseLayer.units);
+
+ var halfWDeg = (this.size.w * res) / 2;
+ var halfHDeg = (this.size.h * res) / 2;
+ var center = this.getCachedCenter();
+
+ var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+ center.lat - halfHDeg,
+ center.lon + halfWDeg,
+ center.lat + halfHDeg);
+ this.zoomToExtent(extent, closest);
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate between */
+ /* LonLat, LayerPx, and ViewPortPx */
+ /* */
+ /********************************************************/
+
+ //
+ // TRANSLATION: LonLat <-> ViewPortPx
+ //
+
+ /**
+ * Method: getLonLatFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or
+ * an object with a 'x'
+ * and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+ * port <OpenLayers.Pixel>, translated into lon/lat
+ * by the current base layer.
+ */
+ getLonLatFromViewPortPx: function (viewPortPx) {
+ var lonlat = null;
+ if (this.baseLayer != null) {
+ lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
+ }
+ return lonlat;
+ },
+
+ /**
+ * APIMethod: getViewPortPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into view port
+ * pixels by the current base layer.
+ */
+ getViewPortPxFromLonLat: function (lonlat) {
+ var px = null;
+ if (this.baseLayer != null) {
+ px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
+ }
+ return px;
+ },
+
+ /**
+ * Method: getZoomTargetCenter
+ *
+ * Parameters:
+ * xy - {<OpenLayers.Pixel>} The zoom origin pixel location on the screen
+ * resolution - {Float} The resolution we want to get the center for
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The location of the map center after the
+ * transformation described by the origin xy and the target resolution.
+ */
+ getZoomTargetCenter: function (xy, resolution) {
+ var lonlat = null,
+ size = this.getSize(),
+ deltaX = size.w/2 - xy.x,
+ deltaY = xy.y - size.h/2,
+ zoomPoint = this.getLonLatFromPixel(xy);
+ if (zoomPoint) {
+ lonlat = new OpenLayers.LonLat(
+ zoomPoint.lon + deltaX * resolution,
+ zoomPoint.lat + deltaY * resolution
+ );
+ }
+ return lonlat;
+ },
+
+ //
+ // CONVENIENCE TRANSLATION FUNCTIONS FOR API
+ //
+
+ /**
+ * APIMethod: getLonLatFromPixel
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
+ * OpenLayers.Pixel, translated into lon/lat by the
+ * current base layer
+ */
+ getLonLatFromPixel: function (px) {
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getPixelFromLonLat
+ * Returns a pixel location given a map location. The map location is
+ * translated to an integer pixel location (in viewport pixel
+ * coordinates) by the current base layer.
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} A map location.
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
+ * <OpenLayers.LonLat> translated into view port pixels by the current
+ * base layer.
+ */
+ getPixelFromLonLat: function (lonlat) {
+ var px = this.getViewPortPxFromLonLat(lonlat);
+ px.x = Math.round(px.x);
+ px.y = Math.round(px.y);
+ return px;
+ },
+
+ /**
+ * Method: getGeodesicPixelSize
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} The pixel to get the geodesic length for. If
+ * not provided, the center pixel of the map viewport will be used.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} The geodesic size of the pixel in kilometers.
+ */
+ getGeodesicPixelSize: function(px) {
+ var lonlat = px ? this.getLonLatFromPixel(px) : (
+ this.getCachedCenter() || new OpenLayers.LonLat(0, 0));
+ var res = this.getResolution();
+ var left = lonlat.add(-res / 2, 0);
+ var right = lonlat.add(res / 2, 0);
+ var bottom = lonlat.add(0, -res / 2);
+ var top = lonlat.add(0, res / 2);
+ var dest = new OpenLayers.Projection("EPSG:4326");
+ var source = this.getProjectionObject() || dest;
+ if(!source.equals(dest)) {
+ left.transform(source, dest);
+ right.transform(source, dest);
+ bottom.transform(source, dest);
+ top.transform(source, dest);
+ }
+
+ return new OpenLayers.Size(
+ OpenLayers.Util.distVincenty(left, right),
+ OpenLayers.Util.distVincenty(bottom, top)
+ );
+ },
+
+
+
+ //
+ // TRANSLATION: ViewPortPx <-> LayerPx
+ //
+
+ /**
+ * APIMethod: getViewPortPxFromLayerPx
+ *
+ * Parameters:
+ * layerPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel
+ * coordinates
+ */
+ getViewPortPxFromLayerPx:function(layerPx) {
+ var viewPortPx = null;
+ if (layerPx != null) {
+ var dX = this.layerContainerOriginPx.x;
+ var dY = this.layerContainerOriginPx.y;
+ viewPortPx = layerPx.add(dX, dY);
+ }
+ return viewPortPx;
+ },
+
+ /**
+ * APIMethod: getLayerPxFromViewPortPx
+ *
+ * Parameters:
+ * viewPortPx - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel
+ * coordinates
+ */
+ getLayerPxFromViewPortPx:function(viewPortPx) {
+ var layerPx = null;
+ if (viewPortPx != null) {
+ var dX = -this.layerContainerOriginPx.x;
+ var dY = -this.layerContainerOriginPx.y;
+ layerPx = viewPortPx.add(dX, dY);
+ if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
+ layerPx = null;
+ }
+ }
+ return layerPx;
+ },
+
+ //
+ // TRANSLATION: LonLat <-> LayerPx
+ //
+
+ /**
+ * Method: getLonLatFromLayerPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>}
+ */
+ getLonLatFromLayerPx: function (px) {
+ //adjust for displacement of layerContainerDiv
+ px = this.getViewPortPxFromLayerPx(px);
+ return this.getLonLatFromViewPortPx(px);
+ },
+
+ /**
+ * APIMethod: getLayerPxFromLonLat
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} lonlat
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+ * <OpenLayers.LonLat>, translated into layer pixels
+ * by the current base layer
+ */
+ getLayerPxFromLonLat: function (lonlat) {
+ //adjust for displacement of layerContainerDiv
+ var px = this.getPixelFromLonLat(lonlat);
+ return this.getLayerPxFromViewPortPx(px);
+ },
+
+ /**
+ * Method: applyTransform
+ * Applies the given transform to the <layerContainerDiv>. This method has
+ * a 2-stage fallback from translate3d/scale3d via translate/scale to plain
+ * style.left/style.top, in which case no scaling is supported.
+ *
+ * Parameters:
+ * x - {Number} x parameter for the translation. Defaults to the x value of
+ * the map's <layerContainerOriginPx>
+ * y - {Number} y parameter for the translation. Defaults to the y value of
+ * the map's <layerContainerOriginPx>
+ * scale - {Number} scale. Defaults to 1 if not provided.
+ */
+ applyTransform: function(x, y, scale) {
+ scale = scale || 1;
+ var origin = this.layerContainerOriginPx,
+ needTransform = scale !== 1;
+ x = x || origin.x;
+ y = y || origin.y;
+
+ var style = this.layerContainerDiv.style,
+ transform = this.applyTransform.transform,
+ template = this.applyTransform.template;
+
+ if (transform === undefined) {
+ transform = OpenLayers.Util.vendorPrefix.style('transform');
+ this.applyTransform.transform = transform;
+ if (transform) {
+ // Try translate3d, but only if the viewPortDiv has a transform
+ // defined in a stylesheet
+ var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv,
+ OpenLayers.Util.vendorPrefix.css('transform'));
+ if (!computedStyle || computedStyle !== 'none') {
+ template = ['translate3d(', ',0) ', 'scale3d(', ',1)'];
+ style[transform] = [template[0], '0,0', template[1]].join('');
+ }
+ // If no transform is defined in the stylesheet or translate3d
+ // does not stick, use translate and scale
+ if (!template || !~style[transform].indexOf(template[0])) {
+ template = ['translate(', ') ', 'scale(', ')'];
+ }
+ this.applyTransform.template = template;
+ }
+ }
+
+ // If we do 3d transforms, we always want to use them. If we do 2d
+ // transforms, we only use them when we need to.
+ if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) {
+ // Our 2d transforms are combined with style.left and style.top, so
+ // adjust x and y values and set the origin as left and top
+ if (needTransform === true && template[0] === 'translate(') {
+ x -= origin.x;
+ y -= origin.y;
+ style.left = origin.x + 'px';
+ style.top = origin.y + 'px';
+ }
+ style[transform] = [
+ template[0], x, 'px,', y, 'px', template[1],
+ template[2], scale, ',', scale, template[3]
+ ].join('');
+ } else {
+ style.left = x + 'px';
+ style.top = y + 'px';
+ // We previously might have had needTransform, so remove transform
+ if (transform !== null) {
+ style[transform] = '';
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Map"
+});
+
+/**
+ * Constant: TILE_WIDTH
+ * {Integer} 256 Default tile width (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_WIDTH = 256;
+/**
+ * Constant: TILE_HEIGHT
+ * {Integer} 256 Default tile height (unless otherwise specified)
+ */
+OpenLayers.Map.TILE_HEIGHT = 256;
diff --git a/misc/openlayers/lib/OpenLayers/Marker.js b/misc/openlayers/lib/OpenLayers/Marker.js
new file mode 100644
index 0000000..1dde347
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Marker.js
@@ -0,0 +1,241 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Icon.js
+ */
+
+/**
+ * Class: OpenLayers.Marker
+ * Instances of OpenLayers.Marker are a combination of a
+ * <OpenLayers.LonLat> and an <OpenLayers.Icon>.
+ *
+ * Markers are generally added to a special layer called
+ * <OpenLayers.Layer.Markers>.
+ *
+ * Example:
+ * (code)
+ * var markers = new OpenLayers.Layer.Markers( "Markers" );
+ * map.addLayer(markers);
+ *
+ * var size = new OpenLayers.Size(21,25);
+ * var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+ * var icon = new OpenLayers.Icon('http://www.openlayers.org/dev/img/marker.png', size, offset);
+ * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon));
+ * markers.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),icon.clone()));
+ *
+ * (end)
+ *
+ * Note that if you pass an icon into the Marker constructor, it will take
+ * that icon and use it. This means that you should not share icons between
+ * markers -- you use them once, but you should clone() for any additional
+ * markers using that same icon.
+ */
+OpenLayers.Marker = OpenLayers.Class({
+
+ /**
+ * Property: icon
+ * {<OpenLayers.Icon>} The icon used by this marker.
+ */
+ icon: null,
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>} location of object
+ */
+ lonlat: null,
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} the event handler.
+ */
+ events: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} the map this marker is attached to
+ */
+ map: null,
+
+ /**
+ * Constructor: OpenLayers.Marker
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} the position of this marker
+ * icon - {<OpenLayers.Icon>} the icon for this marker
+ */
+ initialize: function(lonlat, icon) {
+ this.lonlat = lonlat;
+
+ var newIcon = (icon) ? icon : OpenLayers.Marker.defaultIcon();
+ if (this.icon == null) {
+ this.icon = newIcon;
+ } else {
+ this.icon.url = newIcon.url;
+ this.icon.size = newIcon.size;
+ this.icon.offset = newIcon.offset;
+ this.icon.calculateOffset = newIcon.calculateOffset;
+ }
+ this.events = new OpenLayers.Events(this, this.icon.imageDiv);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Destroy the marker. You must first remove the marker from any
+ * layer which it has been added to, or you will get buggy behavior.
+ * (This can not be done within the marker since the marker does not
+ * know which layer it is attached to.)
+ */
+ destroy: function() {
+ // erase any drawn features
+ this.erase();
+
+ this.map = null;
+
+ this.events.destroy();
+ this.events = null;
+
+ if (this.icon != null) {
+ this.icon.destroy();
+ this.icon = null;
+ }
+ },
+
+ /**
+ * Method: draw
+ * Calls draw on the icon, and returns that output.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {DOMElement} A new DOM Image with this marker's icon set at the
+ * location passed-in
+ */
+ draw: function(px) {
+ return this.icon.draw(px);
+ },
+
+ /**
+ * Method: erase
+ * Erases any drawn elements for this marker.
+ */
+ erase: function() {
+ if (this.icon != null) {
+ this.icon.erase();
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * Move the marker to the new location.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>|Object} the pixel position to move to.
+ * An OpenLayers.Pixel or an object with a 'x' and 'y' properties.
+ */
+ moveTo: function (px) {
+ if ((px != null) && (this.icon != null)) {
+ this.icon.moveTo(px);
+ }
+ this.lonlat = this.map.getLonLatFromLayerPx(px);
+ },
+
+ /**
+ * APIMethod: isDrawn
+ *
+ * Returns:
+ * {Boolean} Whether or not the marker is drawn.
+ */
+ isDrawn: function() {
+ var isDrawn = (this.icon && this.icon.isDrawn());
+ return isDrawn;
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Returns:
+ * {Boolean} Whether or not the marker is currently visible on screen.
+ */
+ onScreen:function() {
+
+ var onScreen = false;
+ if (this.map) {
+ var screenBounds = this.map.getExtent();
+ onScreen = screenBounds.containsLonLat(this.lonlat);
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: inflate
+ * Englarges the markers icon by the specified ratio.
+ *
+ * Parameters:
+ * inflate - {float} the ratio to enlarge the marker by (passing 2
+ * will double the size).
+ */
+ inflate: function(inflate) {
+ if (this.icon) {
+ this.icon.setSize({
+ w: this.icon.size.w * inflate,
+ h: this.icon.size.h * inflate
+ });
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Change the opacity of the marker by changin the opacity of
+ * its icon
+ *
+ * Parameters:
+ * opacity - {float} Specified as fraction (0.4, etc)
+ */
+ setOpacity: function(opacity) {
+ this.icon.setOpacity(opacity);
+ },
+
+ /**
+ * Method: setUrl
+ * Change URL of the Icon Image.
+ *
+ * url - {String}
+ */
+ setUrl: function(url) {
+ this.icon.setUrl(url);
+ },
+
+ /**
+ * Method: display
+ * Hide or show the icon
+ *
+ * display - {Boolean}
+ */
+ display: function(display) {
+ this.icon.display(display);
+ },
+
+ CLASS_NAME: "OpenLayers.Marker"
+});
+
+
+/**
+ * Function: defaultIcon
+ * Creates a default <OpenLayers.Icon>.
+ *
+ * Returns:
+ * {<OpenLayers.Icon>} A default OpenLayers.Icon to use for a marker
+ */
+OpenLayers.Marker.defaultIcon = function() {
+ return new OpenLayers.Icon(OpenLayers.Util.getImageLocation("marker.png"),
+ {w: 21, h: 25}, {x: -10.5, y: -25});
+};
+
+
diff --git a/misc/openlayers/lib/OpenLayers/Marker/Box.js b/misc/openlayers/lib/OpenLayers/Marker/Box.js
new file mode 100644
index 0000000..e42e560
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Marker/Box.js
@@ -0,0 +1,120 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Marker.js
+ */
+
+/**
+ * Class: OpenLayers.Marker.Box
+ *
+ * Inherits from:
+ * - <OpenLayers.Marker>
+ */
+OpenLayers.Marker.Box = OpenLayers.Class(OpenLayers.Marker, {
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>}
+ */
+ bounds: null,
+
+ /**
+ * Property: div
+ * {DOMElement}
+ */
+ div: null,
+
+ /**
+ * Constructor: OpenLayers.Marker.Box
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * borderColor - {String}
+ * borderWidth - {int}
+ */
+ initialize: function(bounds, borderColor, borderWidth) {
+ this.bounds = bounds;
+ this.div = OpenLayers.Util.createDiv();
+ this.div.style.overflow = 'hidden';
+ this.events = new OpenLayers.Events(this, this.div);
+ this.setBorder(borderColor, borderWidth);
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+
+ this.bounds = null;
+ this.div = null;
+
+ OpenLayers.Marker.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: setBorder
+ * Allow the user to change the box's color and border width
+ *
+ * Parameters:
+ * color - {String} Default is "red"
+ * width - {int} Default is 2
+ */
+ setBorder: function (color, width) {
+ if (!color) {
+ color = "red";
+ }
+ if (!width) {
+ width = 2;
+ }
+ this.div.style.border = width + "px solid " + color;
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ * sz - {<OpenLayers.Size>}
+ *
+ * Returns:
+ * {DOMElement} A new DOM Image with this marker's icon set at the
+ * location passed-in
+ */
+ draw: function(px, sz) {
+ OpenLayers.Util.modifyDOMElement(this.div, null, px, sz);
+ return this.div;
+ },
+
+ /**
+ * Method: onScreen
+ *
+ * Rreturn:
+ * {Boolean} Whether or not the marker is currently visible on screen.
+ */
+ onScreen:function() {
+ var onScreen = false;
+ if (this.map) {
+ var screenBounds = this.map.getExtent();
+ onScreen = screenBounds.containsBounds(this.bounds, true, true);
+ }
+ return onScreen;
+ },
+
+ /**
+ * Method: display
+ * Hide or show the icon
+ *
+ * Parameters:
+ * display - {Boolean}
+ */
+ display: function(display) {
+ this.div.style.display = (display) ? "" : "none";
+ },
+
+ CLASS_NAME: "OpenLayers.Marker.Box"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Popup.js b/misc/openlayers/lib/OpenLayers/Popup.js
new file mode 100644
index 0000000..290318e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Popup.js
@@ -0,0 +1,1065 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+
+/**
+ * Class: OpenLayers.Popup
+ * A popup is a small div that can opened and closed on the map.
+ * Typically opened in response to clicking on a marker.
+ * See <OpenLayers.Marker>. Popup's don't require their own
+ * layer and are added the the map using the <OpenLayers.Map.addPopup>
+ * method.
+ *
+ * Example:
+ * (code)
+ * popup = new OpenLayers.Popup("chicken",
+ * new OpenLayers.LonLat(5,40),
+ * new OpenLayers.Size(200,200),
+ * "example popup",
+ * true);
+ *
+ * map.addPopup(popup);
+ * (end)
+ */
+OpenLayers.Popup = OpenLayers.Class({
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} custom event manager
+ */
+ events: null,
+
+ /** Property: id
+ * {String} the unique identifier assigned to this popup.
+ */
+ id: "",
+
+ /**
+ * Property: lonlat
+ * {<OpenLayers.LonLat>} the position of this popup on the map
+ */
+ lonlat: null,
+
+ /**
+ * Property: div
+ * {DOMElement} the div that contains this popup.
+ */
+ div: null,
+
+ /**
+ * Property: contentSize
+ * {<OpenLayers.Size>} the width and height of the content.
+ */
+ contentSize: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} the width and height of the popup.
+ */
+ size: null,
+
+ /**
+ * Property: contentHTML
+ * {String} An HTML string for this popup to display.
+ */
+ contentHTML: null,
+
+ /**
+ * Property: backgroundColor
+ * {String} the background color used by the popup.
+ */
+ backgroundColor: "",
+
+ /**
+ * Property: opacity
+ * {float} the opacity of this popup (between 0.0 and 1.0)
+ */
+ opacity: "",
+
+ /**
+ * Property: border
+ * {String} the border size of the popup. (eg 2px)
+ */
+ border: "",
+
+ /**
+ * Property: contentDiv
+ * {DOMElement} a reference to the element that holds the content of
+ * the div.
+ */
+ contentDiv: null,
+
+ /**
+ * Property: groupDiv
+ * {DOMElement} First and only child of 'div'. The group Div contains the
+ * 'contentDiv' and the 'closeDiv'.
+ */
+ groupDiv: null,
+
+ /**
+ * Property: closeDiv
+ * {DOMElement} the optional closer image
+ */
+ closeDiv: null,
+
+ /**
+ * APIProperty: autoSize
+ * {Boolean} Resize the popup to auto-fit the contents.
+ * Default is false.
+ */
+ autoSize: false,
+
+ /**
+ * APIProperty: minSize
+ * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
+ */
+ minSize: null,
+
+ /**
+ * APIProperty: maxSize
+ * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
+ */
+ maxSize: null,
+
+ /**
+ * Property: displayClass
+ * {String} The CSS class of the popup.
+ */
+ displayClass: "olPopup",
+
+ /**
+ * Property: contentDisplayClass
+ * {String} The CSS class of the popup content div.
+ */
+ contentDisplayClass: "olPopupContent",
+
+ /**
+ * Property: padding
+ * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal
+ * padding of the content div inside the popup. This was originally
+ * confused with the css padding as specified in style.css's
+ * 'olPopupContent' class. We would like to get rid of this altogether,
+ * except that it does come in handy for the framed and anchoredbubble
+ * popups, who need to maintain yet another barrier between their
+ * content and the outer border of the popup itself.
+ *
+ * Note that in order to not break API, we must continue to support
+ * this property being set as an integer. Really, though, we'd like to
+ * have this specified as a Bounds object so that user can specify
+ * distinct left, top, right, bottom paddings. With the 3.0 release
+ * we can make this only a bounds.
+ */
+ padding: 0,
+
+ /**
+ * Property: disableFirefoxOverflowHack
+ * {Boolean} The hack for overflow in Firefox causes all elements
+ * to be re-drawn, which causes Flash elements to be
+ * re-initialized, which is troublesome.
+ * With this property the hack can be disabled.
+ */
+ disableFirefoxOverflowHack: false,
+
+ /**
+ * Method: fixPadding
+ * To be removed in 3.0, this function merely helps us to deal with the
+ * case where the user may have set an integer value for padding,
+ * instead of an <OpenLayers.Bounds> object.
+ */
+ fixPadding: function() {
+ if (typeof this.padding == "number") {
+ this.padding = new OpenLayers.Bounds(
+ this.padding, this.padding, this.padding, this.padding
+ );
+ }
+ },
+
+ /**
+ * APIProperty: panMapIfOutOfView
+ * {Boolean} When drawn, pan map such that the entire popup is visible in
+ * the current viewport (if necessary).
+ * Default is false.
+ */
+ panMapIfOutOfView: false,
+
+ /**
+ * APIProperty: keepInMap
+ * {Boolean} If panMapIfOutOfView is false, and this property is true,
+ * contrain the popup such that it always fits in the available map
+ * space. By default, this is not set on the base class. If you are
+ * creating popups that are near map edges and not allowing pannning,
+ * and especially if you have a popup which has a
+ * fixedRelativePosition, setting this to false may be a smart thing to
+ * do. Subclasses may want to override this setting.
+ *
+ * Default is false.
+ */
+ keepInMap: false,
+
+ /**
+ * APIProperty: closeOnMove
+ * {Boolean} When map pans, close the popup.
+ * Default is false.
+ */
+ closeOnMove: false,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
+ */
+ map: null,
+
+ /**
+ * Constructor: OpenLayers.Popup
+ * Create a popup.
+ *
+ * Parameters:
+ * id - {String} a unqiue identifier for this popup. If null is passed
+ * an identifier will be automatically generated.
+ * lonlat - {<OpenLayers.LonLat>} The position on the map the popup will
+ * be shown.
+ * contentSize - {<OpenLayers.Size>} The size of the content.
+ * contentHTML - {String} An HTML string to display inside the
+ * popup.
+ * closeBox - {Boolean} Whether to display a close box inside
+ * the popup.
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
+ if (id == null) {
+ id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ }
+
+ this.id = id;
+ this.lonlat = lonlat;
+
+ this.contentSize = (contentSize != null) ? contentSize
+ : new OpenLayers.Size(
+ OpenLayers.Popup.WIDTH,
+ OpenLayers.Popup.HEIGHT);
+ if (contentHTML != null) {
+ this.contentHTML = contentHTML;
+ }
+ this.backgroundColor = OpenLayers.Popup.COLOR;
+ this.opacity = OpenLayers.Popup.OPACITY;
+ this.border = OpenLayers.Popup.BORDER;
+
+ this.div = OpenLayers.Util.createDiv(this.id, null, null,
+ null, null, null, "hidden");
+ this.div.className = this.displayClass;
+
+ var groupDivId = this.id + "_GroupDiv";
+ this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null,
+ null, "relative", null,
+ "hidden");
+
+ var id = this.div.id + "_contentDiv";
+ this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(),
+ null, "relative");
+ this.contentDiv.className = this.contentDisplayClass;
+ this.groupDiv.appendChild(this.contentDiv);
+ this.div.appendChild(this.groupDiv);
+
+ if (closeBox) {
+ this.addCloseBox(closeBoxCallback);
+ }
+
+ this.registerEvents();
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+
+ this.id = null;
+ this.lonlat = null;
+ this.size = null;
+ this.contentHTML = null;
+
+ this.backgroundColor = null;
+ this.opacity = null;
+ this.border = null;
+
+ if (this.closeOnMove && this.map) {
+ this.map.events.unregister("movestart", this, this.hide);
+ }
+
+ this.events.destroy();
+ this.events = null;
+
+ if (this.closeDiv) {
+ OpenLayers.Event.stopObservingElement(this.closeDiv);
+ this.groupDiv.removeChild(this.closeDiv);
+ }
+ this.closeDiv = null;
+
+ this.div.removeChild(this.groupDiv);
+ this.groupDiv = null;
+
+ if (this.map != null) {
+ this.map.removePopup(this);
+ }
+ this.map = null;
+ this.div = null;
+
+ this.autoSize = null;
+ this.minSize = null;
+ this.maxSize = null;
+ this.padding = null;
+ this.panMapIfOutOfView = null;
+ },
+
+ /**
+ * Method: draw
+ * Constructs the elements that make up the popup.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} the position the popup in pixels.
+ *
+ * Returns:
+ * {DOMElement} Reference to a div that contains the drawn popup
+ */
+ draw: function(px) {
+ if (px == null) {
+ if ((this.lonlat != null) && (this.map != null)) {
+ px = this.map.getLayerPxFromLonLat(this.lonlat);
+ }
+ }
+
+ // this assumes that this.map already exists, which is okay because
+ // this.draw is only called once the popup has been added to the map.
+ if (this.closeOnMove) {
+ this.map.events.register("movestart", this, this.hide);
+ }
+
+ //listen to movestart, moveend to disable overflow (FF bug)
+ if (!this.disableFirefoxOverflowHack && OpenLayers.BROWSER_NAME == 'firefox') {
+ this.map.events.register("movestart", this, function() {
+ var style = document.defaultView.getComputedStyle(
+ this.contentDiv, null
+ );
+ var currentOverflow = style.getPropertyValue("overflow");
+ if (currentOverflow != "hidden") {
+ this.contentDiv._oldOverflow = currentOverflow;
+ this.contentDiv.style.overflow = "hidden";
+ }
+ });
+ this.map.events.register("moveend", this, function() {
+ var oldOverflow = this.contentDiv._oldOverflow;
+ if (oldOverflow) {
+ this.contentDiv.style.overflow = oldOverflow;
+ this.contentDiv._oldOverflow = null;
+ }
+ });
+ }
+
+ this.moveTo(px);
+ if (!this.autoSize && !this.size) {
+ this.setSize(this.contentSize);
+ }
+ this.setBackgroundColor();
+ this.setOpacity();
+ this.setBorder();
+ this.setContentHTML();
+
+ if (this.panMapIfOutOfView) {
+ this.panIntoView();
+ }
+
+ return this.div;
+ },
+
+ /**
+ * Method: updatePosition
+ * if the popup has a lonlat and its map members set,
+ * then have it move itself to its proper position
+ */
+ updatePosition: function() {
+ if ((this.lonlat) && (this.map)) {
+ var px = this.map.getLayerPxFromLonLat(this.lonlat);
+ if (px) {
+ this.moveTo(px);
+ }
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>} the top and left position of the popup div.
+ */
+ moveTo: function(px) {
+ if ((px != null) && (this.div != null)) {
+ this.div.style.left = px.x + "px";
+ this.div.style.top = px.y + "px";
+ }
+ },
+
+ /**
+ * Method: visible
+ *
+ * Returns:
+ * {Boolean} Boolean indicating whether or not the popup is visible
+ */
+ visible: function() {
+ return OpenLayers.Element.visible(this.div);
+ },
+
+ /**
+ * Method: toggle
+ * Toggles visibility of the popup.
+ */
+ toggle: function() {
+ if (this.visible()) {
+ this.hide();
+ } else {
+ this.show();
+ }
+ },
+
+ /**
+ * Method: show
+ * Makes the popup visible.
+ */
+ show: function() {
+ this.div.style.display = '';
+
+ if (this.panMapIfOutOfView) {
+ this.panIntoView();
+ }
+ },
+
+ /**
+ * Method: hide
+ * Makes the popup invisible.
+ */
+ hide: function() {
+ this.div.style.display = 'none';
+ },
+
+ /**
+ * Method: setSize
+ * Used to adjust the size of the popup.
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ this.size = contentSize.clone();
+
+ // if our contentDiv has a css 'padding' set on it by a stylesheet, we
+ // must add that to the desired "size".
+ var contentDivPadding = this.getContentDivPadding();
+ var wPadding = contentDivPadding.left + contentDivPadding.right;
+ var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+ // take into account the popup's 'padding' property
+ this.fixPadding();
+ wPadding += this.padding.left + this.padding.right;
+ hPadding += this.padding.top + this.padding.bottom;
+
+ // make extra space for the close div
+ if (this.closeDiv) {
+ var closeDivWidth = parseInt(this.closeDiv.style.width);
+ wPadding += closeDivWidth + contentDivPadding.right;
+ }
+
+ //increase size of the main popup div to take into account the
+ // users's desired padding and close div.
+ this.size.w += wPadding;
+ this.size.h += hPadding;
+
+ //now if our browser is IE, we need to actually make the contents
+ // div itself bigger to take its own padding into effect. this makes
+ // me want to shoot someone, but so it goes.
+ if (OpenLayers.BROWSER_NAME == "msie") {
+ this.contentSize.w +=
+ contentDivPadding.left + contentDivPadding.right;
+ this.contentSize.h +=
+ contentDivPadding.bottom + contentDivPadding.top;
+ }
+
+ if (this.div != null) {
+ this.div.style.width = this.size.w + "px";
+ this.div.style.height = this.size.h + "px";
+ }
+ if (this.contentDiv != null){
+ this.contentDiv.style.width = contentSize.w + "px";
+ this.contentDiv.style.height = contentSize.h + "px";
+ }
+ },
+
+ /**
+ * APIMethod: updateSize
+ * Auto size the popup so that it precisely fits its contents (as
+ * determined by this.contentDiv.innerHTML). Popup size will, of
+ * course, be limited by the available space on the current map
+ */
+ updateSize: function() {
+
+ // determine actual render dimensions of the contents by putting its
+ // contents into a fake contentDiv (for the CSS) and then measuring it
+ var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" +
+ this.contentDiv.innerHTML +
+ "</div>";
+
+ var containerElement = (this.map) ? this.map.div : document.body;
+ var realSize = OpenLayers.Util.getRenderedDimensions(
+ preparedHTML, null, {
+ displayClass: this.displayClass,
+ containerElement: containerElement
+ }
+ );
+
+ // is the "real" size of the div is safe to display in our map?
+ var safeSize = this.getSafeContentSize(realSize);
+
+ var newSize = null;
+ if (safeSize.equals(realSize)) {
+ //real size of content is small enough to fit on the map,
+ // so we use real size.
+ newSize = realSize;
+
+ } else {
+
+ // make a new 'size' object with the clipped dimensions
+ // set or null if not clipped.
+ var fixedSize = {
+ w: (safeSize.w < realSize.w) ? safeSize.w : null,
+ h: (safeSize.h < realSize.h) ? safeSize.h : null
+ };
+
+ if (fixedSize.w && fixedSize.h) {
+ //content is too big in both directions, so we will use
+ // max popup size (safeSize), knowing well that it will
+ // overflow both ways.
+ newSize = safeSize;
+ } else {
+ //content is clipped in only one direction, so we need to
+ // run getRenderedDimensions() again with a fixed dimension
+ var clippedSize = OpenLayers.Util.getRenderedDimensions(
+ preparedHTML, fixedSize, {
+ displayClass: this.contentDisplayClass,
+ containerElement: containerElement
+ }
+ );
+
+ //if the clipped size is still the same as the safeSize,
+ // that means that our content must be fixed in the
+ // offending direction. If overflow is 'auto', this means
+ // we are going to have a scrollbar for sure, so we must
+ // adjust for that.
+ //
+ var currentOverflow = OpenLayers.Element.getStyle(
+ this.contentDiv, "overflow"
+ );
+ if ( (currentOverflow != "hidden") &&
+ (clippedSize.equals(safeSize)) ) {
+ var scrollBar = OpenLayers.Util.getScrollbarWidth();
+ if (fixedSize.w) {
+ clippedSize.h += scrollBar;
+ } else {
+ clippedSize.w += scrollBar;
+ }
+ }
+
+ newSize = this.getSafeContentSize(clippedSize);
+ }
+ }
+ this.setSize(newSize);
+ },
+
+ /**
+ * Method: setBackgroundColor
+ * Sets the background color of the popup.
+ *
+ * Parameters:
+ * color - {String} the background color. eg "#FFBBBB"
+ */
+ setBackgroundColor:function(color) {
+ if (color != undefined) {
+ this.backgroundColor = color;
+ }
+
+ if (this.div != null) {
+ this.div.style.backgroundColor = this.backgroundColor;
+ }
+ },
+
+ /**
+ * Method: setOpacity
+ * Sets the opacity of the popup.
+ *
+ * Parameters:
+ * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
+ */
+ setOpacity:function(opacity) {
+ if (opacity != undefined) {
+ this.opacity = opacity;
+ }
+
+ if (this.div != null) {
+ // for Mozilla and Safari
+ this.div.style.opacity = this.opacity;
+
+ // for IE
+ this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
+ }
+ },
+
+ /**
+ * Method: setBorder
+ * Sets the border style of the popup.
+ *
+ * Parameters:
+ * border - {String} The border style value. eg 2px
+ */
+ setBorder:function(border) {
+ if (border != undefined) {
+ this.border = border;
+ }
+
+ if (this.div != null) {
+ this.div.style.border = this.border;
+ }
+ },
+
+ /**
+ * Method: setContentHTML
+ * Allows the user to set the HTML content of the popup.
+ *
+ * Parameters:
+ * contentHTML - {String} HTML for the div.
+ */
+ setContentHTML:function(contentHTML) {
+
+ if (contentHTML != null) {
+ this.contentHTML = contentHTML;
+ }
+
+ if ((this.contentDiv != null) &&
+ (this.contentHTML != null) &&
+ (this.contentHTML != this.contentDiv.innerHTML)) {
+
+ this.contentDiv.innerHTML = this.contentHTML;
+
+ if (this.autoSize) {
+
+ //if popup has images, listen for when they finish
+ // loading and resize accordingly
+ this.registerImageListeners();
+
+ //auto size the popup to its current contents
+ this.updateSize();
+ }
+ }
+
+ },
+
+ /**
+ * Method: registerImageListeners
+ * Called when an image contained by the popup loaded. this function
+ * updates the popup size, then unregisters the image load listener.
+ */
+ registerImageListeners: function() {
+
+ // As the images load, this function will call updateSize() to
+ // resize the popup to fit the content div (which presumably is now
+ // bigger than when the image was not loaded).
+ //
+ // If the 'panMapIfOutOfView' property is set, we will pan the newly
+ // resized popup back into view.
+ //
+ // Note that this function, when called, will have 'popup' and
+ // 'img' properties in the context.
+ //
+ var onImgLoad = function() {
+ if (this.popup.id === null) { // this.popup has been destroyed!
+ return;
+ }
+ this.popup.updateSize();
+
+ if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
+ this.popup.panIntoView();
+ }
+
+ OpenLayers.Event.stopObserving(
+ this.img, "load", this.img._onImgLoad
+ );
+
+ };
+
+ //cycle through the images and if their size is 0x0, that means that
+ // they haven't been loaded yet, so we attach the listener, which
+ // will fire when the images finish loading and will resize the
+ // popup accordingly to its new size.
+ var images = this.contentDiv.getElementsByTagName("img");
+ for (var i = 0, len = images.length; i < len; i++) {
+ var img = images[i];
+ if (img.width == 0 || img.height == 0) {
+
+ var context = {
+ 'popup': this,
+ 'img': img
+ };
+
+ //expando this function to the image itself before registering
+ // it. This way we can easily and properly unregister it.
+ img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
+
+ OpenLayers.Event.observe(img, 'load', img._onImgLoad);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: getSafeContentSize
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} Desired size to make the popup.
+ *
+ * Returns:
+ * {<OpenLayers.Size>} A size to make the popup which is neither smaller
+ * than the specified minimum size, nor bigger than the maximum
+ * size (which is calculated relative to the size of the viewport).
+ */
+ getSafeContentSize: function(size) {
+
+ var safeContentSize = size.clone();
+
+ // if our contentDiv has a css 'padding' set on it by a stylesheet, we
+ // must add that to the desired "size".
+ var contentDivPadding = this.getContentDivPadding();
+ var wPadding = contentDivPadding.left + contentDivPadding.right;
+ var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+ // take into account the popup's 'padding' property
+ this.fixPadding();
+ wPadding += this.padding.left + this.padding.right;
+ hPadding += this.padding.top + this.padding.bottom;
+
+ if (this.closeDiv) {
+ var closeDivWidth = parseInt(this.closeDiv.style.width);
+ wPadding += closeDivWidth + contentDivPadding.right;
+ }
+
+ // prevent the popup from being smaller than a specified minimal size
+ if (this.minSize) {
+ safeContentSize.w = Math.max(safeContentSize.w,
+ (this.minSize.w - wPadding));
+ safeContentSize.h = Math.max(safeContentSize.h,
+ (this.minSize.h - hPadding));
+ }
+
+ // prevent the popup from being bigger than a specified maximum size
+ if (this.maxSize) {
+ safeContentSize.w = Math.min(safeContentSize.w,
+ (this.maxSize.w - wPadding));
+ safeContentSize.h = Math.min(safeContentSize.h,
+ (this.maxSize.h - hPadding));
+ }
+
+ //make sure the desired size to set doesn't result in a popup that
+ // is bigger than the map's viewport.
+ //
+ if (this.map && this.map.size) {
+
+ var extraX = 0, extraY = 0;
+ if (this.keepInMap && !this.panMapIfOutOfView) {
+ var px = this.map.getPixelFromLonLat(this.lonlat);
+ switch (this.relativePosition) {
+ case "tr":
+ extraX = px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ case "tl":
+ extraX = this.map.size.w - px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ case "bl":
+ extraX = this.map.size.w - px.x;
+ extraY = px.y;
+ break;
+ case "br":
+ extraX = px.x;
+ extraY = px.y;
+ break;
+ default:
+ extraX = px.x;
+ extraY = this.map.size.h - px.y;
+ break;
+ }
+ }
+
+ var maxY = this.map.size.h -
+ this.map.paddingForPopups.top -
+ this.map.paddingForPopups.bottom -
+ hPadding - extraY;
+
+ var maxX = this.map.size.w -
+ this.map.paddingForPopups.left -
+ this.map.paddingForPopups.right -
+ wPadding - extraX;
+
+ safeContentSize.w = Math.min(safeContentSize.w, maxX);
+ safeContentSize.h = Math.min(safeContentSize.h, maxY);
+ }
+
+ return safeContentSize;
+ },
+
+ /**
+ * Method: getContentDivPadding
+ * Glorious, oh glorious hack in order to determine the css 'padding' of
+ * the contentDiv. IE/Opera return null here unless we actually add the
+ * popup's main 'div' element (which contains contentDiv) to the DOM.
+ * So we make it invisible and then add it to the document temporarily.
+ *
+ * Once we've taken the padding readings we need, we then remove it
+ * from the DOM (it will actually get added to the DOM in
+ * Map.js's addPopup)
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getContentDivPadding: function() {
+
+ //use cached value if we have it
+ var contentDivPadding = this._contentDivPadding;
+ if (!contentDivPadding) {
+
+ if (this.div.parentNode == null) {
+ //make the div invisible and add it to the page
+ this.div.style.display = "none";
+ document.body.appendChild(this.div);
+ }
+
+ //read the padding settings from css, put them in an OL.Bounds
+ contentDivPadding = new OpenLayers.Bounds(
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
+ OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
+ );
+
+ //cache the value
+ this._contentDivPadding = contentDivPadding;
+
+ if (this.div.parentNode == document.body) {
+ //remove the div from the page and make it visible again
+ document.body.removeChild(this.div);
+ this.div.style.display = "";
+ }
+ }
+ return contentDivPadding;
+ },
+
+ /**
+ * Method: addCloseBox
+ *
+ * Parameters:
+ * callback - {Function} The callback to be called when the close button
+ * is clicked.
+ */
+ addCloseBox: function(callback) {
+
+ this.closeDiv = OpenLayers.Util.createDiv(
+ this.id + "_close", null, {w: 17, h: 17}
+ );
+ this.closeDiv.className = "olPopupCloseBox";
+
+ // use the content div's css padding to determine if we should
+ // padd the close div
+ var contentDivPadding = this.getContentDivPadding();
+
+ this.closeDiv.style.right = contentDivPadding.right + "px";
+ this.closeDiv.style.top = contentDivPadding.top + "px";
+ this.groupDiv.appendChild(this.closeDiv);
+
+ var closePopup = callback || function(e) {
+ this.hide();
+ OpenLayers.Event.stop(e);
+ };
+ OpenLayers.Event.observe(this.closeDiv, "touchend",
+ OpenLayers.Function.bindAsEventListener(closePopup, this));
+ OpenLayers.Event.observe(this.closeDiv, "click",
+ OpenLayers.Function.bindAsEventListener(closePopup, this));
+ },
+
+ /**
+ * Method: panIntoView
+ * Pans the map such that the popup is totaly viewable (if necessary)
+ */
+ panIntoView: function() {
+
+ var mapSize = this.map.getSize();
+
+ //start with the top left corner of the popup, in px,
+ // relative to the viewport
+ var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
+ parseInt(this.div.style.left),
+ parseInt(this.div.style.top)
+ ));
+ var newTL = origTL.clone();
+
+ //new left (compare to margins, using this.size to calculate right)
+ if (origTL.x < this.map.paddingForPopups.left) {
+ newTL.x = this.map.paddingForPopups.left;
+ } else
+ if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
+ newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
+ }
+
+ //new top (compare to margins, using this.size to calculate bottom)
+ if (origTL.y < this.map.paddingForPopups.top) {
+ newTL.y = this.map.paddingForPopups.top;
+ } else
+ if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
+ newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
+ }
+
+ var dx = origTL.x - newTL.x;
+ var dy = origTL.y - newTL.y;
+
+ this.map.pan(dx, dy);
+ },
+
+ /**
+ * Method: registerEvents
+ * Registers events on the popup.
+ *
+ * Do this in a separate function so that subclasses can
+ * choose to override it if they wish to deal differently
+ * with mouse events
+ *
+ * Note in the following handler functions that some special
+ * care is needed to deal correctly with mousing and popups.
+ *
+ * Because the user might select the zoom-rectangle option and
+ * then drag it over a popup, we need a safe way to allow the
+ * mousemove and mouseup events to pass through the popup when
+ * they are initiated from outside. The same procedure is needed for
+ * touchmove and touchend events.
+ *
+ * Otherwise, we want to essentially kill the event propagation
+ * for all other events, though we have to do so carefully,
+ * without disabling basic html functionality, like clicking on
+ * hyperlinks or drag-selecting text.
+ */
+ registerEvents:function() {
+ this.events = new OpenLayers.Events(this, this.div, null, true);
+
+ function onTouchstart(evt) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ this.events.on({
+ "mousedown": this.onmousedown,
+ "mousemove": this.onmousemove,
+ "mouseup": this.onmouseup,
+ "click": this.onclick,
+ "mouseout": this.onmouseout,
+ "dblclick": this.ondblclick,
+ "touchstart": onTouchstart,
+ scope: this
+ });
+
+ },
+
+ /**
+ * Method: onmousedown
+ * When mouse goes down within the popup, make a note of
+ * it locally, and then do not propagate the mousedown
+ * (but do so safely so that user can select text inside)
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmousedown: function (evt) {
+ this.mousedown = true;
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ /**
+ * Method: onmousemove
+ * If the drag was started within the popup, then
+ * do not propagate the mousemove (but do so safely
+ * so that user can select text inside)
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmousemove: function (evt) {
+ if (this.mousedown) {
+ OpenLayers.Event.stop(evt, true);
+ }
+ },
+
+ /**
+ * Method: onmouseup
+ * When mouse comes up within the popup, after going down
+ * in it, reset the flag, and then (once again) do not
+ * propagate the event, but do so safely so that user can
+ * select text inside
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmouseup: function (evt) {
+ if (this.mousedown) {
+ this.mousedown = false;
+ OpenLayers.Event.stop(evt, true);
+ }
+ },
+
+ /**
+ * Method: onclick
+ * Ignore clicks, but allowing default browser handling
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onclick: function (evt) {
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ /**
+ * Method: onmouseout
+ * When mouse goes out of the popup set the flag to false so that
+ * if they let go and then drag back in, we won't be confused.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ onmouseout: function (evt) {
+ this.mousedown = false;
+ },
+
+ /**
+ * Method: ondblclick
+ * Ignore double-clicks, but allowing default browser handling
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ ondblclick: function (evt) {
+ OpenLayers.Event.stop(evt, true);
+ },
+
+ CLASS_NAME: "OpenLayers.Popup"
+});
+
+OpenLayers.Popup.WIDTH = 200;
+OpenLayers.Popup.HEIGHT = 200;
+OpenLayers.Popup.COLOR = "white";
+OpenLayers.Popup.OPACITY = 1;
+OpenLayers.Popup.BORDER = "0px";
diff --git a/misc/openlayers/lib/OpenLayers/Popup/Anchored.js b/misc/openlayers/lib/OpenLayers/Popup/Anchored.js
new file mode 100644
index 0000000..9415546
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Popup/Anchored.js
@@ -0,0 +1,195 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Anchored
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup>
+ */
+OpenLayers.Popup.Anchored =
+ OpenLayers.Class(OpenLayers.Popup, {
+
+ /**
+ * Property: relativePosition
+ * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
+ */
+ relativePosition: null,
+
+ /**
+ * APIProperty: keepInMap
+ * {Boolean} If panMapIfOutOfView is false, and this property is true,
+ * contrain the popup such that it always fits in the available map
+ * space. By default, this is set. If you are creating popups that are
+ * near map edges and not allowing pannning, and especially if you have
+ * a popup which has a fixedRelativePosition, setting this to false may
+ * be a smart thing to do.
+ *
+ * For anchored popups, default is true, since subclasses will
+ * usually want this functionality.
+ */
+ keepInMap: true,
+
+ /**
+ * Property: anchor
+ * {Object} Object to which we'll anchor the popup. Must expose a
+ * 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
+ */
+ anchor: null,
+
+ /**
+ * Constructor: OpenLayers.Popup.Anchored
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object which must expose a 'size' <OpenLayers.Size>
+ * and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+ var newArguments = [
+ id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback
+ ];
+ OpenLayers.Popup.prototype.initialize.apply(this, newArguments);
+
+ this.anchor = (anchor != null) ? anchor
+ : { size: new OpenLayers.Size(0,0),
+ offset: new OpenLayers.Pixel(0,0)};
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.anchor = null;
+ this.relativePosition = null;
+
+ OpenLayers.Popup.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: show
+ * Overridden from Popup since user might hide popup and then show() it
+ * in a new location (meaning we might want to update the relative
+ * position on the show)
+ */
+ show: function() {
+ this.updatePosition();
+ OpenLayers.Popup.prototype.show.apply(this, arguments);
+ },
+
+ /**
+ * Method: moveTo
+ * Since the popup is moving to a new px, it might need also to be moved
+ * relative to where the marker is. We first calculate the new
+ * relativePosition, and then we calculate the new px where we will
+ * put the popup, based on the new relative position.
+ *
+ * If the relativePosition has changed, we must also call
+ * updateRelativePosition() to make any visual changes to the popup
+ * which are associated with putting it in a new relativePosition.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ */
+ moveTo: function(px) {
+ var oldRelativePosition = this.relativePosition;
+ this.relativePosition = this.calculateRelativePosition(px);
+
+ OpenLayers.Popup.prototype.moveTo.call(this, this.calculateNewPx(px));
+
+ //if this move has caused the popup to change its relative position,
+ // we need to make the appropriate cosmetic changes.
+ if (this.relativePosition != oldRelativePosition) {
+ this.updateRelativePosition();
+ }
+ },
+
+ /**
+ * APIMethod: setSize
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.prototype.setSize.apply(this, arguments);
+
+ if ((this.lonlat) && (this.map)) {
+ var px = this.map.getLayerPxFromLonLat(this.lonlat);
+ this.moveTo(px);
+ }
+ },
+
+ /**
+ * Method: calculateRelativePosition
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {String} The relative position ("br" "tr" "tl" "bl") at which the popup
+ * should be placed.
+ */
+ calculateRelativePosition:function(px) {
+ var lonlat = this.map.getLonLatFromLayerPx(px);
+
+ var extent = this.map.getExtent();
+ var quadrant = extent.determineQuadrant(lonlat);
+
+ return OpenLayers.Bounds.oppositeQuadrant(quadrant);
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * The popup has been moved to a new relative location, so we may want to
+ * make some cosmetic adjustments to it.
+ *
+ * Note that in the classic Anchored popup, there is nothing to do
+ * here, since the popup looks exactly the same in all four positions.
+ * Subclasses such as Framed, however, will want to do something
+ * special here.
+ */
+ updateRelativePosition: function() {
+ //to be overridden by subclasses
+ },
+
+ /**
+ * Method: calculateNewPx
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+ * relative to the passed-in px.
+ */
+ calculateNewPx:function(px) {
+ var newPx = px.offset(this.anchor.offset);
+
+ //use contentSize if size is not already set
+ var size = this.size || this.contentSize;
+
+ var top = (this.relativePosition.charAt(0) == 't');
+ newPx.y += (top) ? -size.h : this.anchor.size.h;
+
+ var left = (this.relativePosition.charAt(1) == 'l');
+ newPx.x += (left) ? -size.w : this.anchor.size.w;
+
+ return newPx;
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.Anchored"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Popup/Framed.js b/misc/openlayers/lib/OpenLayers/Popup/Framed.js
new file mode 100644
index 0000000..cb2d5d9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Popup/Framed.js
@@ -0,0 +1,343 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Framed
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.Framed =
+ OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+ /**
+ * Property: imageSrc
+ * {String} location of the image to be used as the popup frame
+ */
+ imageSrc: null,
+
+ /**
+ * Property: imageSize
+ * {<OpenLayers.Size>} Size (measured in pixels) of the image located
+ * by the 'imageSrc' property.
+ */
+ imageSize: null,
+
+ /**
+ * APIProperty: isAlphaImage
+ * {Boolean} The image has some alpha and thus needs to use the alpha
+ * image hack. Note that setting this to true will have no noticeable
+ * effect in FF or IE7 browsers, but will all but crush the ie6
+ * browser.
+ * Default is false.
+ */
+ isAlphaImage: false,
+
+ /**
+ * Property: positionBlocks
+ * {Object} Hash of different position blocks (Object/Hashs). Each block
+ * will be keyed by a two-character 'relativePosition'
+ * code string (ie "tl", "tr", "bl", "br"). Block properties are
+ * 'offset', 'padding' (self-explanatory), and finally the 'blocks'
+ * parameter, which is an array of the block objects.
+ *
+ * Each block object must have 'size', 'anchor', and 'position'
+ * properties.
+ *
+ * Note that positionBlocks should never be modified at runtime.
+ */
+ positionBlocks: null,
+
+ /**
+ * Property: blocks
+ * {Array[Object]} Array of objects, each of which is one "block" of the
+ * popup. Each block has a 'div' and an 'image' property, both of
+ * which are DOMElements, and the latter of which is appended to the
+ * former. These are reused as the popup goes changing positions for
+ * great economy and elegance.
+ */
+ blocks: null,
+
+ /**
+ * APIProperty: fixedRelativePosition
+ * {Boolean} We want the framed popup to work dynamically placed relative
+ * to its anchor but also in just one fixed position. A well designed
+ * framed popup will have the pixels and logic to display itself in
+ * any of the four relative positions, but (understandably), this will
+ * not be the case for all of them. By setting this property to 'true',
+ * framed popup will not recalculate for the best placement each time
+ * it's open, but will always open the same way.
+ * Note that if this is set to true, it is generally advisable to also
+ * set the 'panIntoView' property to true so that the popup can be
+ * scrolled into view (since it will often be offscreen on open)
+ * Default is false.
+ */
+ fixedRelativePosition: false,
+
+ /**
+ * Constructor: OpenLayers.Popup.Framed
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+
+ if (this.fixedRelativePosition) {
+ //based on our decided relativePostion, set the current padding
+ // this keeps us from getting into trouble
+ this.updateRelativePosition();
+
+ //make calculateRelativePosition always return the specified
+ // fixed position.
+ this.calculateRelativePosition = function(px) {
+ return this.relativePosition;
+ };
+ }
+
+ this.contentDiv.style.position = "absolute";
+ this.contentDiv.style.zIndex = 1;
+
+ if (closeBox) {
+ this.closeDiv.style.zIndex = 1;
+ }
+
+ this.groupDiv.style.position = "absolute";
+ this.groupDiv.style.top = "0px";
+ this.groupDiv.style.left = "0px";
+ this.groupDiv.style.height = "100%";
+ this.groupDiv.style.width = "100%";
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.imageSrc = null;
+ this.imageSize = null;
+ this.isAlphaImage = null;
+
+ this.fixedRelativePosition = false;
+ this.positionBlocks = null;
+
+ //remove our blocks
+ for(var i = 0; i < this.blocks.length; i++) {
+ var block = this.blocks[i];
+
+ if (block.image) {
+ block.div.removeChild(block.image);
+ }
+ block.image = null;
+
+ if (block.div) {
+ this.groupDiv.removeChild(block.div);
+ }
+ block.div = null;
+ }
+ this.blocks = null;
+
+ OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: setBackgroundColor
+ */
+ setBackgroundColor:function(color) {
+ //does nothing since the framed popup's entire scheme is based on a
+ // an image -- changing the background color makes no sense.
+ },
+
+ /**
+ * APIMethod: setBorder
+ */
+ setBorder:function() {
+ //does nothing since the framed popup's entire scheme is based on a
+ // an image -- changing the popup's border makes no sense.
+ },
+
+ /**
+ * Method: setOpacity
+ * Sets the opacity of the popup.
+ *
+ * Parameters:
+ * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).
+ */
+ setOpacity:function(opacity) {
+ //does nothing since we suppose that we'll never apply an opacity
+ // to a framed popup
+ },
+
+ /**
+ * APIMethod: setSize
+ * Overridden here, because we need to update the blocks whenever the size
+ * of the popup has changed.
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+ this.updateBlocks();
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * When the relative position changes, we need to set the new padding
+ * BBOX on the popup, reposition the close div, and update the blocks.
+ */
+ updateRelativePosition: function() {
+
+ //update the padding
+ this.padding = this.positionBlocks[this.relativePosition].padding;
+
+ //update the position of our close box to new padding
+ if (this.closeDiv) {
+ // use the content div's css padding to determine if we should
+ // padd the close div
+ var contentDivPadding = this.getContentDivPadding();
+
+ this.closeDiv.style.right = contentDivPadding.right +
+ this.padding.right + "px";
+ this.closeDiv.style.top = contentDivPadding.top +
+ this.padding.top + "px";
+ }
+
+ this.updateBlocks();
+ },
+
+ /**
+ * Method: calculateNewPx
+ * Besides the standard offset as determined by the Anchored class, our
+ * Framed popups have a special 'offset' property for each of their
+ * positions, which is used to offset the popup relative to its anchor.
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+ * relative to the passed-in px.
+ */
+ calculateNewPx:function(px) {
+ var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(
+ this, arguments
+ );
+
+ newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset);
+
+ return newPx;
+ },
+
+ /**
+ * Method: createBlocks
+ */
+ createBlocks: function() {
+ this.blocks = [];
+
+ //since all positions contain the same number of blocks, we can
+ // just pick the first position and use its blocks array to create
+ // our blocks array
+ var firstPosition = null;
+ for(var key in this.positionBlocks) {
+ firstPosition = key;
+ break;
+ }
+
+ var position = this.positionBlocks[firstPosition];
+ for (var i = 0; i < position.blocks.length; i++) {
+
+ var block = {};
+ this.blocks.push(block);
+
+ var divId = this.id + '_FrameDecorationDiv_' + i;
+ block.div = OpenLayers.Util.createDiv(divId,
+ null, null, null, "absolute", null, "hidden", null
+ );
+
+ var imgId = this.id + '_FrameDecorationImg_' + i;
+ var imageCreator =
+ (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv
+ : OpenLayers.Util.createImage;
+
+ block.image = imageCreator(imgId,
+ null, this.imageSize, this.imageSrc,
+ "absolute", null, null, null
+ );
+
+ block.div.appendChild(block.image);
+ this.groupDiv.appendChild(block.div);
+ }
+ },
+
+ /**
+ * Method: updateBlocks
+ * Internal method, called on initialize and when the popup's relative
+ * position has changed. This function takes care of re-positioning
+ * the popup's blocks in their appropropriate places.
+ */
+ updateBlocks: function() {
+ if (!this.blocks) {
+ this.createBlocks();
+ }
+
+ if (this.size && this.relativePosition) {
+ var position = this.positionBlocks[this.relativePosition];
+ for (var i = 0; i < position.blocks.length; i++) {
+
+ var positionBlock = position.blocks[i];
+ var block = this.blocks[i];
+
+ // adjust sizes
+ var l = positionBlock.anchor.left;
+ var b = positionBlock.anchor.bottom;
+ var r = positionBlock.anchor.right;
+ var t = positionBlock.anchor.top;
+
+ //note that we use the isNaN() test here because if the
+ // size object is initialized with a "auto" parameter, the
+ // size constructor calls parseFloat() on the string,
+ // which will turn it into NaN
+ //
+ var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l)
+ : positionBlock.size.w;
+
+ var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t)
+ : positionBlock.size.h;
+
+ block.div.style.width = (w < 0 ? 0 : w) + 'px';
+ block.div.style.height = (h < 0 ? 0 : h) + 'px';
+
+ block.div.style.left = (l != null) ? l + 'px' : '';
+ block.div.style.bottom = (b != null) ? b + 'px' : '';
+ block.div.style.right = (r != null) ? r + 'px' : '';
+ block.div.style.top = (t != null) ? t + 'px' : '';
+
+ block.image.style.left = positionBlock.position.x + 'px';
+ block.image.style.top = positionBlock.position.y + 'px';
+ }
+
+ this.contentDiv.style.left = this.padding.left + "px";
+ this.contentDiv.style.top = this.padding.top + "px";
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.Framed"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Popup/FramedCloud.js b/misc/openlayers/lib/OpenLayers/Popup/FramedCloud.js
new file mode 100644
index 0000000..8ad8b94
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Popup/FramedCloud.js
@@ -0,0 +1,227 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Framed.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.FramedCloud
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Framed>
+ */
+OpenLayers.Popup.FramedCloud =
+ OpenLayers.Class(OpenLayers.Popup.Framed, {
+
+ /**
+ * Property: contentDisplayClass
+ * {String} The CSS class of the popup content div.
+ */
+ contentDisplayClass: "olFramedCloudPopupContent",
+
+ /**
+ * APIProperty: autoSize
+ * {Boolean} Framed Cloud is autosizing by default.
+ */
+ autoSize: true,
+
+ /**
+ * APIProperty: panMapIfOutOfView
+ * {Boolean} Framed Cloud does pan into view by default.
+ */
+ panMapIfOutOfView: true,
+
+ /**
+ * APIProperty: imageSize
+ * {<OpenLayers.Size>}
+ */
+ imageSize: new OpenLayers.Size(1276, 736),
+
+ /**
+ * APIProperty: isAlphaImage
+ * {Boolean} The FramedCloud does not use an alpha image (in honor of the
+ * good ie6 folk out there)
+ */
+ isAlphaImage: false,
+
+ /**
+ * APIProperty: fixedRelativePosition
+ * {Boolean} The Framed Cloud popup works in just one fixed position.
+ */
+ fixedRelativePosition: false,
+
+ /**
+ * Property: positionBlocks
+ * {Object} Hash of differen position blocks, keyed by relativePosition
+ * two-character code string (ie "tl", "tr", "bl", "br")
+ */
+ positionBlocks: {
+ "tl": {
+ 'offset': new OpenLayers.Pixel(44, 0),
+ 'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 19),
+ anchor: new OpenLayers.Bounds(0, 32, 22, null),
+ position: new OpenLayers.Pixel(0, -631)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 18),
+ anchor: new OpenLayers.Bounds(null, 32, 0, null),
+ position: new OpenLayers.Pixel(-1238, -632)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 35),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(0, -688)
+ }
+ ]
+ },
+ "tr": {
+ 'offset': new OpenLayers.Pixel(-45, 0),
+ 'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 19),
+ anchor: new OpenLayers.Bounds(0, 32, 22, null),
+ position: new OpenLayers.Pixel(0, -631)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 19),
+ anchor: new OpenLayers.Bounds(null, 32, 0, null),
+ position: new OpenLayers.Pixel(-1238, -631)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 35),
+ anchor: new OpenLayers.Bounds(0, 0, null, null),
+ position: new OpenLayers.Pixel(-215, -687)
+ }
+ ]
+ },
+ "bl": {
+ 'offset': new OpenLayers.Pixel(45, 0),
+ 'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 21),
+ anchor: new OpenLayers.Bounds(0, 0, 22, null),
+ position: new OpenLayers.Pixel(0, -629)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 21),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(-1238, -629)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 33),
+ anchor: new OpenLayers.Bounds(null, null, 0, 0),
+ position: new OpenLayers.Pixel(-101, -674)
+ }
+ ]
+ },
+ "br": {
+ 'offset': new OpenLayers.Pixel(-44, 0),
+ 'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+ 'blocks': [
+ { // top-left
+ size: new OpenLayers.Size('auto', 'auto'),
+ anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+ position: new OpenLayers.Pixel(0, 0)
+ },
+ { //top-right
+ size: new OpenLayers.Size(22, 'auto'),
+ anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+ position: new OpenLayers.Pixel(-1238, 0)
+ },
+ { //bottom-left
+ size: new OpenLayers.Size('auto', 21),
+ anchor: new OpenLayers.Bounds(0, 0, 22, null),
+ position: new OpenLayers.Pixel(0, -629)
+ },
+ { //bottom-right
+ size: new OpenLayers.Size(22, 21),
+ anchor: new OpenLayers.Bounds(null, 0, 0, null),
+ position: new OpenLayers.Pixel(-1238, -629)
+ },
+ { // stem
+ size: new OpenLayers.Size(81, 33),
+ anchor: new OpenLayers.Bounds(0, null, null, 0),
+ position: new OpenLayers.Pixel(-311, -674)
+ }
+ ]
+ }
+ },
+
+ /**
+ * APIProperty: minSize
+ * {<OpenLayers.Size>}
+ */
+ minSize: new OpenLayers.Size(105, 10),
+
+ /**
+ * APIProperty: maxSize
+ * {<OpenLayers.Size>}
+ */
+ maxSize: new OpenLayers.Size(1200, 660),
+
+ /**
+ * Constructor: OpenLayers.Popup.FramedCloud
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ this.imageSrc = OpenLayers.Util.getImageLocation('cloud-popup-relative.png');
+ OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments);
+ this.contentDiv.className = this.contentDisplayClass;
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.FramedCloud"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Projection.js b/misc/openlayers/lib/OpenLayers/Projection.js
new file mode 100644
index 0000000..387e26a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Projection.js
@@ -0,0 +1,322 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Projection
+ * Methods for coordinate transforms between coordinate systems. By default,
+ * OpenLayers ships with the ability to transform coordinates between
+ * geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
+ * coordinate reference systems. See the <transform> method for details
+ * on usage.
+ *
+ * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
+ * library. If the proj4js library is included, the <transform> method
+ * will work between any two coordinate reference systems with proj4js
+ * definitions.
+ *
+ * If the proj4js library is not included, or if you wish to allow transforms
+ * between arbitrary coordinate reference systems, use the <addTransform>
+ * method to register a custom transform method.
+ */
+OpenLayers.Projection = OpenLayers.Class({
+
+ /**
+ * Property: proj
+ * {Object} Proj4js.Proj instance.
+ */
+ proj: null,
+
+ /**
+ * Property: projCode
+ * {String}
+ */
+ projCode: null,
+
+ /**
+ * Property: titleRegEx
+ * {RegExp} regular expression to strip the title from a proj4js definition
+ */
+ titleRegEx: /\+title=[^\+]*/,
+
+ /**
+ * Constructor: OpenLayers.Projection
+ * This class offers several methods for interacting with a wrapped
+ * pro4js projection object.
+ *
+ * Parameters:
+ * projCode - {String} A string identifying the Well Known Identifier for
+ * the projection.
+ * options - {Object} An optional object to set additional properties
+ * on the projection.
+ *
+ * Returns:
+ * {<OpenLayers.Projection>} A projection object.
+ */
+ initialize: function(projCode, options) {
+ OpenLayers.Util.extend(this, options);
+ this.projCode = projCode;
+ if (typeof Proj4js == "object") {
+ this.proj = new Proj4js.Proj(projCode);
+ }
+ },
+
+ /**
+ * APIMethod: getCode
+ * Get the string SRS code.
+ *
+ * Returns:
+ * {String} The SRS code.
+ */
+ getCode: function() {
+ return this.proj ? this.proj.srsCode : this.projCode;
+ },
+
+ /**
+ * APIMethod: getUnits
+ * Get the units string for the projection -- returns null if
+ * proj4js is not available.
+ *
+ * Returns:
+ * {String} The units abbreviation.
+ */
+ getUnits: function() {
+ return this.proj ? this.proj.units : null;
+ },
+
+ /**
+ * Method: toString
+ * Convert projection to string (getCode wrapper).
+ *
+ * Returns:
+ * {String} The projection code.
+ */
+ toString: function() {
+ return this.getCode();
+ },
+
+ /**
+ * Method: equals
+ * Test equality of two projection instances. Determines equality based
+ * soley on the projection code.
+ *
+ * Returns:
+ * {Boolean} The two projections are equivalent.
+ */
+ equals: function(projection) {
+ var p = projection, equals = false;
+ if (p) {
+ if (!(p instanceof OpenLayers.Projection)) {
+ p = new OpenLayers.Projection(p);
+ }
+ if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
+ equals = this.proj.defData.replace(this.titleRegEx, "") ==
+ p.proj.defData.replace(this.titleRegEx, "");
+ } else if (p.getCode) {
+ var source = this.getCode(), target = p.getCode();
+ equals = source == target ||
+ !!OpenLayers.Projection.transforms[source] &&
+ OpenLayers.Projection.transforms[source][target] ===
+ OpenLayers.Projection.nullTransform;
+ }
+ }
+ return equals;
+ },
+
+ /* Method: destroy
+ * Destroy projection object.
+ */
+ destroy: function() {
+ delete this.proj;
+ delete this.projCode;
+ },
+
+ CLASS_NAME: "OpenLayers.Projection"
+});
+
+/**
+ * Property: transforms
+ * {Object} Transforms is an object, with from properties, each of which may
+ * have a to property. This allows you to define projections without
+ * requiring support for proj4js to be included.
+ *
+ * This object has keys which correspond to a 'source' projection object. The
+ * keys should be strings, corresponding to the projection.getCode() value.
+ * Each source projection object should have a set of destination projection
+ * keys included in the object.
+ *
+ * Each value in the destination object should be a transformation function,
+ * where the function is expected to be passed an object with a .x and a .y
+ * property. The function should return the object, with the .x and .y
+ * transformed according to the transformation function.
+ *
+ * Note - Properties on this object should not be set directly. To add a
+ * transform method to this object, use the <addTransform> method. For an
+ * example of usage, see the OpenLayers.Layer.SphericalMercator file.
+ */
+OpenLayers.Projection.transforms = {};
+
+/**
+ * APIProperty: defaults
+ * {Object} Defaults for the SRS codes known to OpenLayers (currently
+ * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
+ * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units,
+ * maxExtent (the validity extent for the SRS) and yx (true if this SRS is
+ * known to have a reverse axis order).
+ */
+OpenLayers.Projection.defaults = {
+ "EPSG:4326": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90],
+ yx: true
+ },
+ "CRS:84": {
+ units: "degrees",
+ maxExtent: [-180, -90, 180, 90]
+ },
+ "EPSG:900913": {
+ units: "m",
+ maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
+ }
+};
+
+/**
+ * APIMethod: addTransform
+ * Set a custom transform method between two projections. Use this method in
+ * cases where the proj4js lib is not available or where custom projections
+ * need to be handled.
+ *
+ * Parameters:
+ * from - {String} The code for the source projection
+ * to - {String} the code for the destination projection
+ * method - {Function} A function that takes a point as an argument and
+ * transforms that point from the source to the destination projection
+ * in place. The original point should be modified.
+ */
+OpenLayers.Projection.addTransform = function(from, to, method) {
+ if (method === OpenLayers.Projection.nullTransform) {
+ var defaults = OpenLayers.Projection.defaults[from];
+ if (defaults && !OpenLayers.Projection.defaults[to]) {
+ OpenLayers.Projection.defaults[to] = defaults;
+ }
+ }
+ if(!OpenLayers.Projection.transforms[from]) {
+ OpenLayers.Projection.transforms[from] = {};
+ }
+ OpenLayers.Projection.transforms[from][to] = method;
+};
+
+/**
+ * APIMethod: transform
+ * Transform a point coordinate from one projection to another. Note that
+ * the input point is transformed in place.
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
+ * properties representing coordinates in those dimensions.
+ * source - {OpenLayers.Projection} Source map coordinate system
+ * dest - {OpenLayers.Projection} Destination map coordinate system
+ *
+ * Returns:
+ * point - {object} A transformed coordinate. The original point is modified.
+ */
+OpenLayers.Projection.transform = function(point, source, dest) {
+ if (source && dest) {
+ if (!(source instanceof OpenLayers.Projection)) {
+ source = new OpenLayers.Projection(source);
+ }
+ if (!(dest instanceof OpenLayers.Projection)) {
+ dest = new OpenLayers.Projection(dest);
+ }
+ if (source.proj && dest.proj) {
+ point = Proj4js.transform(source.proj, dest.proj, point);
+ } else {
+ var sourceCode = source.getCode();
+ var destCode = dest.getCode();
+ var transforms = OpenLayers.Projection.transforms;
+ if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
+ transforms[sourceCode][destCode](point);
+ }
+ }
+ }
+ return point;
+};
+
+/**
+ * APIFunction: nullTransform
+ * A null transformation - useful for defining projection aliases when
+ * proj4js is not available:
+ *
+ * (code)
+ * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
+ * OpenLayers.Projection.nullTransform);
+ * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
+ * OpenLayers.Projection.nullTransform);
+ * (end)
+ */
+OpenLayers.Projection.nullTransform = function(point) {
+ return point;
+};
+
+/**
+ * Note: Transforms for web mercator <-> geographic
+ * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100.
+ * OpenLayers originally started referring to EPSG:900913 as web mercator.
+ * The EPSG has declared EPSG:3857 to be web mercator.
+ * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
+ * equivalent. See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
+ * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
+ * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
+ * order for EPSG:4326.
+ */
+(function() {
+
+ var pole = 20037508.34;
+
+ function inverseMercator(xy) {
+ xy.x = 180 * xy.x / pole;
+ xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
+ return xy;
+ }
+
+ function forwardMercator(xy) {
+ xy.x = xy.x * pole / 180;
+ var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
+ xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
+ return xy;
+ }
+
+ function map(base, codes) {
+ var add = OpenLayers.Projection.addTransform;
+ var same = OpenLayers.Projection.nullTransform;
+ var i, len, code, other, j;
+ for (i=0, len=codes.length; i<len; ++i) {
+ code = codes[i];
+ add(base, code, forwardMercator);
+ add(code, base, inverseMercator);
+ for (j=i+1; j<len; ++j) {
+ other = codes[j];
+ add(code, other, same);
+ add(other, code, same);
+ }
+ }
+ }
+
+ // list of equivalent codes for web mercator
+ var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
+ geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
+ i;
+ for (i=mercator.length-1; i>=0; --i) {
+ map(mercator[i], geographic);
+ }
+ for (i=geographic.length-1; i>=0; --i) {
+ map(geographic[i], mercator);
+ }
+
+})();
diff --git a/misc/openlayers/lib/OpenLayers/Protocol.js b/misc/openlayers/lib/OpenLayers/Protocol.js
new file mode 100644
index 0000000..7e63439
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol.js
@@ -0,0 +1,291 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class. Not to be instantiated directly. Use
+ * one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+
+ /**
+ * Property: format
+ * {<OpenLayers.Format>} The format used by this protocol.
+ */
+ format: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the protocol can set autoDestroy to false
+ * to fully control when the protocol is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Property: defaultFilter
+ * {<OpenLayers.Filter>} Optional default filter to read requests
+ */
+ defaultFilter: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol
+ * Abstract class for vector protocols. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ options = options || {};
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ },
+
+ /**
+ * Method: mergeWithDefaultFilter
+ * Merge filter passed to the read method with the default one
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>}
+ */
+ mergeWithDefaultFilter: function(filter) {
+ var merged;
+ if (filter && this.defaultFilter) {
+ merged = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.defaultFilter, filter]
+ });
+ } else {
+ merged = filter || this.defaultFilter || undefined;
+ }
+ return merged;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.options = null;
+ this.format = null;
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ read: function(options) {
+ options = options || {};
+ options.filter = this.mergeWithDefaultFilter(options.filter);
+ },
+
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ create: function() {
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ update: function() {
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, the same object will be passed to the callback function passed
+ * if one exists in the options object.
+ */
+ "delete": function() {
+ },
+
+ /**
+ * APIMethod: commit
+ * Go over the features and for each take action
+ * based on the feature state. Possible actions are create,
+ * update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Object whose possible keys are "create", "update",
+ * "delete", "callback" and "scope", the values referenced by the
+ * first three are objects as passed to the "create", "update", and
+ * "delete" methods, the value referenced by the "callback" key is
+ * a function which is called when the commit operation is complete
+ * using the scope referenced by the "scope" key.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Protocol.Response>})} An array of
+ * <OpenLayers.Protocol.Response> objects.
+ */
+ commit: function() {
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ },
+
+ /**
+ * Method: createCallback
+ * Returns a function that applies the given public method with resp and
+ * options arguments.
+ *
+ * Parameters:
+ * method - {Function} The method to be applied by the callback.
+ * response - {<OpenLayers.Protocol.Response>} The protocol response object.
+ * options - {Object} Options sent to the protocol method
+ */
+ createCallback: function(method, response, options) {
+ return OpenLayers.Function.bind(function() {
+ method.apply(this, [response, options]);
+ }, this);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol"
+});
+
+/**
+ * Class: OpenLayers.Protocol.Response
+ * Protocols return Response objects to their users.
+ */
+OpenLayers.Protocol.Response = OpenLayers.Class({
+ /**
+ * Property: code
+ * {Number} - OpenLayers.Protocol.Response.SUCCESS or
+ * OpenLayers.Protocol.Response.FAILURE
+ */
+ code: null,
+
+ /**
+ * Property: requestType
+ * {String} The type of request this response corresponds to. Either
+ * "create", "read", "update" or "delete".
+ */
+ requestType: null,
+
+ /**
+ * Property: last
+ * {Boolean} - true if this is the last response expected in a commit,
+ * false otherwise, defaults to true.
+ */
+ last: true,
+
+ /**
+ * Property: features
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ features: null,
+
+ /**
+ * Property: data
+ * {Object}
+ * The data returned in the response by the server. Depending on the
+ * protocol's read payload, either features or data will be populated.
+ */
+ data: null,
+
+ /**
+ * Property: reqFeatures
+ * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+ * The features provided by the user and placed in the request by the
+ * protocol.
+ */
+ reqFeatures: null,
+
+ /**
+ * Property: priv
+ */
+ priv: null,
+
+ /**
+ * Property: error
+ * {Object} The error object in case a service exception was encountered.
+ */
+ error: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Response
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: success
+ *
+ * Returns:
+ * {Boolean} - true on success, false otherwise
+ */
+ success: function() {
+ return this.code > 0;
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/CSW.js b/misc/openlayers/lib/OpenLayers/Protocol/CSW.js
new file mode 100644
index 0000000..5641182
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/CSW.js
@@ -0,0 +1,30 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.CSW
+ * Used to create a versioned CSW protocol. Default version is 2.0.2.
+ */
+OpenLayers.Protocol.CSW = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.CSW.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.CSW["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported CSW version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Protocol.CSW.DEFAULTS
+ */
+OpenLayers.Protocol.CSW.DEFAULTS = {
+ "version": "2.0.2"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/CSW/v2_0_2.js b/misc/openlayers/lib/OpenLayers/Protocol/CSW/v2_0_2.js
new file mode 100644
index 0000000..88bfd75
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/CSW/v2_0_2.js
@@ -0,0 +1,127 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/CSW.js
+ * @requires OpenLayers/Format/CSWGetRecords/v2_0_2.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.CSW.v2_0_2
+ * CS-W (Catalogue services for the Web) version 2.0.2 protocol.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.CSW.v2_0_2 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.CSW.v2_0_2
+ * A class for CSW version 2.0.2 protocol management.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = new OpenLayers.Format.CSWGetRecords.v2_0_2(OpenLayers.Util.extend({
+ }, this.formatOptions));
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * Method: read
+ * Construct a request for reading new records from the Catalogue.
+ */
+ read: function(options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+
+ var data = this.format.write(options.params || options);
+
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ params: options.params,
+ headers: options.headers,
+ data: data
+ });
+
+ return response;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * This response is given a code property, and optionally a data property.
+ * The latter represents the CSW records as returned by the call to
+ * the CSW format read method.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ response.data = this.parseData(request);
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseData
+ * Read HTTP response body and return records
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Object} The CSW records as returned by the call to the format read method.
+ */
+ parseData: function(request) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.CSW.v2_0_2"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/HTTP.js b/misc/openlayers/lib/OpenLayers/Protocol/HTTP.js
new file mode 100644
index 0000000..a53b497
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/HTTP.js
@@ -0,0 +1,580 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.HTTP
+ * A basic HTTP protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.HTTP> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: url
+ * {String} Service URL, read-only, set through the options
+ * passed to constructor.
+ */
+ url: null,
+
+ /**
+ * Property: headers
+ * {Object} HTTP request headers, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'Content-Type': 'plain/text'}
+ */
+ headers: null,
+
+ /**
+ * Property: params
+ * {Object} Parameters of GET requests, read-only, set through the options
+ * passed to the constructor,
+ * Example: {'bbox': '5,5,5,5'}
+ */
+ params: null,
+
+ /**
+ * Property: callback
+ * {Object} Function to be called when the <read>, <create>,
+ * <update>, <delete> or <commit> operation completes, read-only,
+ * set through the options passed to the constructor.
+ */
+ callback: null,
+
+ /**
+ * Property: scope
+ * {Object} Callback execution scope, read-only, set through the
+ * options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: readWithPOST
+ * {Boolean} true if read operations are done with POST requests
+ * instead of GET, defaults to false.
+ */
+ readWithPOST: false,
+
+ /**
+ * APIProperty: updateWithPOST
+ * {Boolean} true if update operations are done with POST requests
+ * defaults to false.
+ */
+ updateWithPOST: false,
+
+ /**
+ * APIProperty: deleteWithPOST
+ * {Boolean} true if delete operations are done with POST requests
+ * defaults to false.
+ * if true, POST data is set to output of format.write().
+ */
+ deleteWithPOST: false,
+
+ /**
+ * Property: wildcarded.
+ * {Boolean} If true percent signs are added around values
+ * read from LIKE filters, for example if the protocol
+ * read method is passed a LIKE filter whose property
+ * is "foo" and whose value is "bar" the string
+ * "foo__ilike=%bar%" will be sent in the query string;
+ * defaults to false.
+ */
+ wildcarded: false,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Default is false. If true and the layer has a projection object set,
+ * any BBOX filter will be serialized with a fifth item identifying the
+ * projection. E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * Constructor: OpenLayers.Protocol.HTTP
+ * A class for giving layers generic HTTP protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * headers - {Object}
+ * params - {Object} URL parameters for GET requests
+ * format - {<OpenLayers.Format>}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.headers = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ wildcarded: this.wildcarded,
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.params = null;
+ this.headers = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, the filter will be serialized using the
+ * <OpenLayers.Format.QueryStringFilter> class.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Valid options:
+ * url - {String} Url for the request.
+ * params - {Object} Parameters to get serialized as a query string.
+ * headers - {Object} Headers to be set on the request.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ * readWithPOST - {Boolean} If the request should be done with POST.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the HTTP request, this object is also passed to the
+ * callback function when the request completes, its "features" property
+ * is then populated with the features received from the server.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = options || {};
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var readWithPOST = (options.readWithPOST !== undefined) ?
+ options.readWithPOST : this.readWithPOST;
+ var resp = new OpenLayers.Protocol.Response({requestType: "read"});
+ if(readWithPOST) {
+ var headers = options.headers || {};
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ data: OpenLayers.Util.getParameterString(options.params),
+ headers: headers
+ });
+ } else {
+ resp.priv = OpenLayers.Request.GET({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, resp, options),
+ params: options.params,
+ headers: options.headers
+ });
+ }
+ return resp;
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: create
+ * Construct a request for writing newly created features.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the features received from the server.
+ */
+ create: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: features,
+ requestType: "create"
+ });
+
+ resp.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleCreate, resp, options),
+ headers: options.headers,
+ data: this.format.write(features)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleCreate
+ * Called the the request issued by <create> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create call.
+ */
+ handleCreate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes, its "features" property is then populated with the
+ * the feature received from the server.
+ */
+ update: function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "update"
+ });
+
+ var method = this.updateWithPOST ? "POST" : "PUT";
+ resp.priv = OpenLayers.Request[method]({
+ url: url,
+ callback: this.createCallback(this.handleUpdate, resp, options),
+ headers: options.headers,
+ data: this.format.write(feature)
+ });
+
+ return resp;
+ },
+
+ /**
+ * Method: handleUpdate
+ * Called the the request issued by <update> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the update call.
+ */
+ handleUpdate: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * APIMethod: delete
+ * Construct a request deleting a removed feature.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object, whose "priv" property references the HTTP request, this
+ * object is also passed to the callback function when the request
+ * completes.
+ */
+ "delete": function(feature, options) {
+ options = options || {};
+ var url = options.url ||
+ feature.url ||
+ this.options.url + "/" + feature.fid;
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = new OpenLayers.Protocol.Response({
+ reqFeatures: feature,
+ requestType: "delete"
+ });
+
+ var method = this.deleteWithPOST ? "POST" : "DELETE";
+ var requestOptions = {
+ url: url,
+ callback: this.createCallback(this.handleDelete, resp, options),
+ headers: options.headers
+ };
+ if (this.deleteWithPOST) {
+ requestOptions.data = this.format.write(feature);
+ }
+ resp.priv = OpenLayers.Request[method](requestOptions);
+
+ return resp;
+ },
+
+ /**
+ * Method: handleDelete
+ * Called the the request issued by <delete> is complete. May be overridden
+ * by subclasses.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the delete call.
+ */
+ handleDelete: function(resp, options) {
+ this.handleResponse(resp, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create, read, update,
+ * or delete call.
+ */
+ handleResponse: function(resp, options) {
+ var request = resp.priv;
+ if(options.callback) {
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ if(resp.requestType != "delete") {
+ resp.features = this.parseFeatures(request);
+ }
+ resp.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ resp.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, resp);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features.
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if (!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ /**
+ * APIMethod: commit
+ * Iterate over each feature and take action based on the feature state.
+ * Possible actions are create, update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Optional object for setting up intermediate commit
+ * callbacks.
+ *
+ * Valid options:
+ * create - {Object} Optional object to be passed to the <create> method.
+ * update - {Object} Optional object to be passed to the <update> method.
+ * delete - {Object} Optional object to be passed to the <delete> method.
+ * callback - {Function} Optional function to be called when the commit
+ * is complete.
+ * scope - {Object} Optional object to be set as the scope of the callback.
+ *
+ * Returns:
+ * {Array(<OpenLayers.Protocol.Response>)} An array of response objects,
+ * one per request made to the server, each object's "priv" property
+ * references the corresponding HTTP request.
+ */
+ commit: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ var resp = [], nResponses = 0;
+
+ // Divide up features before issuing any requests. This properly
+ // counts requests in the event that any responses come in before
+ // all requests have been issued.
+ var types = {};
+ types[OpenLayers.State.INSERT] = [];
+ types[OpenLayers.State.UPDATE] = [];
+ types[OpenLayers.State.DELETE] = [];
+ var feature, list, requestFeatures = [];
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ list = types[feature.state];
+ if(list) {
+ list.push(feature);
+ requestFeatures.push(feature);
+ }
+ }
+ // tally up number of requests
+ var nRequests = (types[OpenLayers.State.INSERT].length > 0 ? 1 : 0) +
+ types[OpenLayers.State.UPDATE].length +
+ types[OpenLayers.State.DELETE].length;
+
+ // This response will be sent to the final callback after all the others
+ // have been fired.
+ var success = true;
+ var finalResponse = new OpenLayers.Protocol.Response({
+ reqFeatures: requestFeatures
+ });
+
+ function insertCallback(response) {
+ var len = response.features ? response.features.length : 0;
+ var fids = new Array(len);
+ for(var i=0; i<len; ++i) {
+ fids[i] = response.features[i].fid;
+ }
+ finalResponse.insertIds = fids;
+ callback.apply(this, [response]);
+ }
+
+ function callback(response) {
+ this.callUserCallback(response, options);
+ success = success && response.success();
+ nResponses++;
+ if (nResponses >= nRequests) {
+ if (options.callback) {
+ finalResponse.code = success ?
+ OpenLayers.Protocol.Response.SUCCESS :
+ OpenLayers.Protocol.Response.FAILURE;
+ options.callback.apply(options.scope, [finalResponse]);
+ }
+ }
+ }
+
+ // start issuing requests
+ var queue = types[OpenLayers.State.INSERT];
+ if(queue.length > 0) {
+ resp.push(this.create(
+ queue, OpenLayers.Util.applyDefaults(
+ {callback: insertCallback, scope: this}, options.create
+ )
+ ));
+ }
+ queue = types[OpenLayers.State.UPDATE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this.update(
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options.update
+ ))
+ );
+ }
+ queue = types[OpenLayers.State.DELETE];
+ for(var i=queue.length-1; i>=0; --i) {
+ resp.push(this["delete"](
+ queue[i], OpenLayers.Util.applyDefaults(
+ {callback: callback, scope: this}, options["delete"]
+ ))
+ );
+ }
+ return resp;
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this HTTP protocol (as a result
+ * of a create, read, update, delete or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ /**
+ * Method: callUserCallback
+ * This method is used from within the commit method each time an
+ * an HTTP response is received from the server, it is responsible
+ * for calling the user-supplied callbacks.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>}
+ * options - {Object} The map of options passed to the commit call.
+ */
+ callUserCallback: function(resp, options) {
+ var opt = options[resp.requestType];
+ if(opt && opt.callback) {
+ opt.callback.call(opt.scope, resp);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.HTTP"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/SOS.js b/misc/openlayers/lib/OpenLayers/Protocol/SOS.js
new file mode 100644
index 0000000..578f369
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/SOS.js
@@ -0,0 +1,33 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Function: OpenLayers.Protocol.SOS
+ * Used to create a versioned SOS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol>} An SOS protocol for the given version.
+ */
+OpenLayers.Protocol.SOS = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.SOS.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.SOS["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported SOS version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Constant: OpenLayers.Protocol.SOS.DEFAULTS
+ */
+OpenLayers.Protocol.SOS.DEFAULTS = {
+ "version": "1.0.0"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/SOS/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Protocol/SOS/v1_0_0.js
new file mode 100644
index 0000000..9cf87f5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/SOS/v1_0_0.js
@@ -0,0 +1,133 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/SOS.js
+ * @requires OpenLayers/Format/SOSGetFeatureOfInterest.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.SOS.v1_0_0
+ * An SOS v1.0.0 Protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.SOS.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+ OpenLayers.Protocol.SOS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * APIProperty: fois
+ * {Array(String)} Array of features of interest (foi)
+ */
+ fois: null,
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.SOS
+ * A class for giving layers an SOS protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * url - {String} URL to send requests to (required).
+ * fois - {Array} The features of interest (required).
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = new OpenLayers.Format.SOSGetFeatureOfInterest(
+ this.formatOptions);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new sensor positions. This is done by
+ * issuing one GetFeatureOfInterest request.
+ */
+ read: function(options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+ var format = this.format;
+ var data = OpenLayers.Format.XML.prototype.write.apply(format,
+ [format.writeNode("sos:GetFeatureOfInterest", {fois: this.fois})]
+ );
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ data: data
+ });
+ return response;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ response.features = this.parseFeatures(request);
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read HTTP response body and return features
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} Array of features
+ */
+ parseFeatures: function(request) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ return this.format.read(doc);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.SOS.v1_0_0"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/Script.js b/misc/openlayers/lib/OpenLayers/Protocol/Script.js
new file mode 100644
index 0000000..93ab32a
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/Script.js
@@ -0,0 +1,377 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Format/GeoJSON.js
+ */
+
+/**
+ * if application uses the query string, for example, for BBOX parameters,
+ * OpenLayers/Format/QueryStringFilter.js should be included in the build config file
+ */
+
+/**
+ * Class: OpenLayers.Protocol.Script
+ * A basic Script protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.Script> constructor. A script protocol is used to
+ * get around the same origin policy. It works with services that return
+ * JSONP - that is, JSON wrapped in a client-specified callback. The
+ * protocol handles fetching and parsing of feature data and sends parsed
+ * features to the <callback> configured with the protocol. The protocol
+ * expects features serialized as GeoJSON by default, but can be configured
+ * to work with other formats by setting the <format> property.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * APIProperty: url
+ * {String} Service URL. The service is expected to return serialized
+ * features wrapped in a named callback (where the callback name is
+ * generated by this protocol).
+ * Read-only, set through the options passed to the constructor.
+ */
+ url: null,
+
+ /**
+ * APIProperty: params
+ * {Object} Query string parameters to be appended to the URL.
+ * Read-only, set through the options passed to the constructor.
+ * Example: {maxFeatures: 50}
+ */
+ params: null,
+
+ /**
+ * APIProperty: callback
+ * {Object} Function to be called when the <read> operation completes.
+ */
+ callback: null,
+
+ /**
+ * APIProperty: callbackTemplate
+ * {String} Template for creating a unique callback function name
+ * for the registry. Should include ${id}. The ${id} variable will be
+ * replaced with a string identifier prefixed with a "c" (e.g. c1, c2).
+ * Default is "OpenLayers.Protocol.Script.registry.${id}".
+ */
+ callbackTemplate: "OpenLayers.Protocol.Script.registry.${id}",
+
+ /**
+ * APIProperty: callbackKey
+ * {String} The name of the query string parameter that the service
+ * recognizes as the callback identifier. Default is "callback".
+ * This key is used to generate the URL for the script. For example
+ * setting <callbackKey> to "myCallback" would result in a URL like
+ * http://example.com/?myCallback=...
+ */
+ callbackKey: "callback",
+
+ /**
+ * APIProperty: callbackPrefix
+ * {String} Where a service requires that the callback query string
+ * parameter value is prefixed by some string, this value may be set.
+ * For example, setting <callbackPrefix> to "foo:" would result in a
+ * URL like http://example.com/?callback=foo:... Default is "".
+ */
+ callbackPrefix: "",
+
+ /**
+ * APIProperty: scope
+ * {Object} Optional ``this`` object for the callback. Read-only, set
+ * through the options passed to the constructor.
+ */
+ scope: null,
+
+ /**
+ * APIProperty: format
+ * {<OpenLayers.Format>} Format for parsing features. Default is an
+ * <OpenLayers.Format.GeoJSON> format. If an alternative is provided,
+ * the format's read method must take an object and return an array
+ * of features.
+ */
+ format: null,
+
+ /**
+ * Property: pendingRequests
+ * {Object} References all pending requests. Property names are script
+ * identifiers and property values are script elements.
+ */
+ pendingRequests: null,
+
+ /**
+ * APIProperty: srsInBBOX
+ * {Boolean} Include the SRS identifier in BBOX query string parameter.
+ * Setting this property has no effect if a custom filterToParams method
+ * is provided. Default is false. If true and the layer has a
+ * projection object set, any BBOX filter will be serialized with a
+ * fifth item identifying the projection.
+ * E.g. bbox=-1000,-1000,1000,1000,EPSG:900913
+ */
+ srsInBBOX: false,
+
+ /**
+ * Constructor: OpenLayers.Protocol.Script
+ * A class for giving layers generic Script protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options include:
+ * url - {String}
+ * params - {Object}
+ * callback - {Function}
+ * scope - {Object}
+ */
+ initialize: function(options) {
+ options = options || {};
+ this.params = {};
+ this.pendingRequests = {};
+ OpenLayers.Protocol.prototype.initialize.apply(this, arguments);
+ if (!this.format) {
+ this.format = new OpenLayers.Format.GeoJSON();
+ }
+
+ if (!this.filterToParams && OpenLayers.Format.QueryStringFilter) {
+ var format = new OpenLayers.Format.QueryStringFilter({
+ srsInBBOX: this.srsInBBOX
+ });
+ this.filterToParams = function(filter, params) {
+ return format.write(filter, params);
+ };
+ }
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Valid options:
+ * url - {String} Url for the request.
+ * params - {Object} Parameters to get serialized as a query string.
+ * filter - {<OpenLayers.Filter>} Filter to get serialized as a
+ * query string.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object, whose "priv" property
+ * references the injected script. This object is also passed to the
+ * callback function when the request completes, its "features" property
+ * is then populated with the features received from the server.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+ options.params = OpenLayers.Util.applyDefaults(
+ options.params, this.options.params
+ );
+ if (options.filter && this.filterToParams) {
+ options.params = this.filterToParams(
+ options.filter, options.params
+ );
+ }
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+ var request = this.createRequest(
+ options.url,
+ options.params,
+ OpenLayers.Function.bind(function(data) {
+ response.data = data;
+ this.handleRead(response, options);
+ }, this)
+ );
+ response.priv = request;
+ return response;
+ },
+
+ /**
+ * APIMethod: filterToParams
+ * Optional method to translate an <OpenLayers.Filter> object into an object
+ * that can be serialized as request query string provided. If a custom
+ * method is not provided, any filter will not be serialized.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter to convert.
+ * params - {Object} The parameters object.
+ *
+ * Returns:
+ * {Object} The resulting parameters object.
+ */
+
+ /**
+ * Method: createRequest
+ * Issues a request for features by creating injecting a script in the
+ * document head.
+ *
+ * Parameters:
+ * url - {String} Service URL.
+ * params - {Object} Query string parameters.
+ * callback - {Function} Callback to be called with resulting data.
+ *
+ * Returns:
+ * {HTMLScriptElement} The script pending execution.
+ */
+ createRequest: function(url, params, callback) {
+ var id = OpenLayers.Protocol.Script.register(callback);
+ var name = OpenLayers.String.format(this.callbackTemplate, {id: id});
+ params = OpenLayers.Util.extend({}, params);
+ params[this.callbackKey] = this.callbackPrefix + name;
+ url = OpenLayers.Util.urlAppend(
+ url, OpenLayers.Util.getParameterString(params)
+ );
+ var script = document.createElement("script");
+ script.type = "text/javascript";
+ script.src = url;
+ script.id = "OpenLayers_Protocol_Script_" + id;
+ this.pendingRequests[script.id] = script;
+ var head = document.getElementsByTagName("head")[0];
+ head.appendChild(script);
+ return script;
+ },
+
+ /**
+ * Method: destroyRequest
+ * Remove a script node associated with a response from the document. Also
+ * unregisters the callback and removes the script from the
+ * <pendingRequests> object.
+ *
+ * Parameters:
+ * script - {HTMLScriptElement}
+ */
+ destroyRequest: function(script) {
+ OpenLayers.Protocol.Script.unregister(script.id.split("_").pop());
+ delete this.pendingRequests[script.id];
+ if (script.parentNode) {
+ script.parentNode.removeChild(script);
+ }
+ },
+
+ /**
+ * Method: handleRead
+ * Individual callbacks are created for read, create and update, should
+ * a subclass need to override each one separately.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ this.handleResponse(response, options);
+ },
+
+ /**
+ * Method: handleResponse
+ * Called by CRUD specific handlers.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass to
+ * any user callback.
+ * options - {Object} The user options passed to the create, read, update,
+ * or delete call.
+ */
+ handleResponse: function(response, options) {
+ if (options.callback) {
+ if (response.data) {
+ response.features = this.parseFeatures(response.data);
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ this.destroyRequest(response.priv);
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseFeatures
+ * Read Script response body and return features.
+ *
+ * Parameters:
+ * data - {Object} The data sent to the callback function by the server.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} Array of features or a single feature.
+ */
+ parseFeatures: function(data) {
+ return this.format.read(data);
+ },
+
+ /**
+ * APIMethod: abort
+ * Abort an ongoing request. If no response is provided, all pending
+ * requests will be aborted.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object returned
+ * from a <read> request.
+ */
+ abort: function(response) {
+ if (response) {
+ this.destroyRequest(response.priv);
+ } else {
+ for (var key in this.pendingRequests) {
+ this.destroyRequest(this.pendingRequests[key]);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.abort();
+ delete this.params;
+ delete this.format;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.Script"
+});
+
+(function() {
+ var o = OpenLayers.Protocol.Script;
+ var counter = 0;
+ o.registry = {};
+
+ /**
+ * Function: OpenLayers.Protocol.Script.register
+ * Register a callback for a newly created script.
+ *
+ * Parameters:
+ * callback - {Function} The callback to be executed when the newly added
+ * script loads. This callback will be called with a single argument
+ * that is the JSON returned by the service.
+ *
+ * Returns:
+ * {Number} An identifier for retrieving the registered callback.
+ */
+ o.register = function(callback) {
+ var id = "c"+(++counter);
+ o.registry[id] = function() {
+ callback.apply(this, arguments);
+ };
+ return id;
+ };
+
+ /**
+ * Function: OpenLayers.Protocol.Script.unregister
+ * Unregister a callback previously registered with the register function.
+ *
+ * Parameters:
+ * id - {Number} The identifer returned by the register function.
+ */
+ o.unregister = function(id) {
+ delete o.registry[id];
+ };
+})();
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/WFS.js b/misc/openlayers/lib/OpenLayers/Protocol/WFS.js
new file mode 100644
index 0000000..66faf43
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/WFS.js
@@ -0,0 +1,86 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS
+ * Used to create a versioned WFS protocol. Default version is 1.0.0.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol>} A WFS protocol of the given version.
+ *
+ * Example:
+ * (code)
+ * var protocol = new OpenLayers.Protocol.WFS({
+ * version: "1.1.0",
+ * url: "http://demo.opengeo.org/geoserver/wfs",
+ * featureType: "tasmania_roads",
+ * featureNS: "http://www.openplans.org/topp",
+ * geometryName: "the_geom"
+ * });
+ * (end)
+ *
+ * See the protocols for specific WFS versions for more detail.
+ */
+OpenLayers.Protocol.WFS = function(options) {
+ options = OpenLayers.Util.applyDefaults(
+ options, OpenLayers.Protocol.WFS.DEFAULTS
+ );
+ var cls = OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g, "_")];
+ if(!cls) {
+ throw "Unsupported WFS version: " + options.version;
+ }
+ return new cls(options);
+};
+
+/**
+ * Function: fromWMSLayer
+ * Convenience function to create a WFS protocol from a WMS layer. This makes
+ * the assumption that a WFS requests can be issued at the same URL as
+ * WMS requests and that a WFS featureType exists with the same name as the
+ * WMS layer.
+ *
+ * This function is designed to auto-configure <url>, <featureType>,
+ * <featurePrefix> and <srsName> for WFS <version> 1.1.0. Note that
+ * srsName matching with the WMS layer will not work with WFS 1.0.0.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.WMS>} WMS layer that has a matching WFS
+ * FeatureType at the same server url with the same typename.
+ * options - {Object} Default properties to be set on the protocol.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.WFS>}
+ */
+OpenLayers.Protocol.WFS.fromWMSLayer = function(layer, options) {
+ var typeName, featurePrefix;
+ var param = layer.params["LAYERS"];
+ var parts = (OpenLayers.Util.isArray(param) ? param[0] : param).split(":");
+ if(parts.length > 1) {
+ featurePrefix = parts[0];
+ }
+ typeName = parts.pop();
+ var protocolOptions = {
+ url: layer.url,
+ featureType: typeName,
+ featurePrefix: featurePrefix,
+ srsName: layer.projection && layer.projection.getCode() ||
+ layer.map && layer.map.getProjectionObject().getCode(),
+ version: "1.1.0"
+ };
+ return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(
+ options, protocolOptions
+ ));
+};
+
+/**
+ * Constant: OpenLayers.Protocol.WFS.DEFAULTS
+ */
+OpenLayers.Protocol.WFS.DEFAULTS = {
+ "version": "1.0.0"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1.js b/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1.js
new file mode 100644
index 0000000..8a21d7e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1.js
@@ -0,0 +1,453 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1
+ * Abstract class for for v1.0.0 and v1.1.0 protocol.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.WFS.v1 = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: null,
+
+ /**
+ * Property: srsName
+ * {String} Name of spatial reference system. Default is "EPSG:4326".
+ */
+ srsName: "EPSG:4326",
+
+ /**
+ * Property: featureType
+ * {String} Local feature typeName.
+ */
+ featureType: null,
+
+ /**
+ * Property: featureNS
+ * {String} Feature namespace.
+ */
+ featureNS: null,
+
+ /**
+ * Property: geometryName
+ * {String} Name of the geometry attribute for features. Default is
+ * "the_geom" for WFS <version> 1.0, and null for higher versions.
+ */
+ geometryName: "the_geom",
+
+ /**
+ * Property: maxFeatures
+ * {Integer} Optional maximum number of features to retrieve.
+ */
+
+ /**
+ * Property: schema
+ * {String} Optional schema location that will be included in the
+ * schemaLocation attribute value. Note that the feature type schema
+ * is required for a strict XML validator (on transactions with an
+ * insert for example), but is *not* required by the WFS specification
+ * (since the server is supposed to know about feature type schemas).
+ */
+ schema: null,
+
+ /**
+ * Property: featurePrefix
+ * {String} Namespace alias for feature type. Default is "feature".
+ */
+ featurePrefix: "feature",
+
+ /**
+ * Property: formatOptions
+ * {Object} Optional options for the format. If a format is not provided,
+ * this property can be used to extend the default format options.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: readFormat
+ * {<OpenLayers.Format>} For WFS requests it is possible to get a
+ * different output format than GML. In that case, we cannot parse
+ * the response with the default format (WFST) and we need a different
+ * format for reading.
+ */
+ readFormat: null,
+
+ /**
+ * Property: readOptions
+ * {Object} Optional object to pass to format's read.
+ */
+ readOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS
+ * A class for giving layers WFS protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * url - {String} URL to send requests to (required).
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (required, but can be autodetected
+ * during the first query if GML is used as readFormat and
+ * featurePrefix is provided and matches the prefix used by the server
+ * for this featureType).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * for writing if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. The default is
+ * 'the_geom' for WFS <version> 1.0, and null for higher versions. If
+ * null, it will be set to the name of the first geometry found in the
+ * first read operation.
+ * multi - {Boolean} If set to true, geometries will be casted to Multi
+ * geometries before they are written in a transaction. No casting will
+ * be done when reading features.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ if(!options.format) {
+ this.format = OpenLayers.Format.WFST(OpenLayers.Util.extend({
+ version: this.version,
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ featurePrefix: this.featurePrefix,
+ geometryName: this.geometryName,
+ srsName: this.srsName,
+ schema: this.schema
+ }, this.formatOptions));
+ }
+ if (!options.geometryName && parseFloat(this.format.version) > 1.0) {
+ this.setGeometryName(null);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ if(this.options && !this.options.format) {
+ this.format.destroy();
+ }
+ this.format = null;
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: read
+ * Construct a request for reading new features. Since WFS splits the
+ * basic CRUD operations into GetFeature requests (for read) and
+ * Transactions (for all others), this method does not make use of the
+ * format's read method (that is only about reading transaction
+ * responses).
+ *
+ * Parameters:
+ * options - {Object} Options for the read operation, in addition to the
+ * options set on the instance (options set here will take precedence).
+ *
+ * To use a configured protocol to get e.g. a WFS hit count, applications
+ * could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * readOptions: {output: "object"},
+ * resultType: "hits",
+ * maxFeatures: null,
+ * callback: function(resp) {
+ * // process resp.numberOfFeatures here
+ * }
+ * });
+ * (end)
+ *
+ * To use a configured protocol to use WFS paging (if supported by the
+ * server), applications could do the following:
+ *
+ * (code)
+ * protocol.read({
+ * startIndex: 0,
+ * count: 50
+ * });
+ * (end)
+ *
+ * To limit the attributes returned by the GetFeature request, applications
+ * can use the propertyNames option to specify the properties to include in
+ * the response:
+ *
+ * (code)
+ * protocol.read({
+ * propertyNames: ["DURATION", "INTENSITY"]
+ * });
+ * (end)
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options || {});
+ var response = new OpenLayers.Protocol.Response({requestType: "read"});
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [this.format.writeNode("wfs:GetFeature", options)]
+ );
+
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ callback: this.createCallback(this.handleRead, response, options),
+ params: options.params,
+ headers: options.headers,
+ data: data
+ });
+
+ return response;
+ },
+
+ /**
+ * APIMethod: setFeatureType
+ * Change the feature type on the fly.
+ *
+ * Parameters:
+ * featureType - {String} Local (without prefix) feature typeName.
+ */
+ setFeatureType: function(featureType) {
+ this.featureType = featureType;
+ this.format.featureType = featureType;
+ },
+
+ /**
+ * APIMethod: setGeometryName
+ * Sets the geometryName option after instantiation.
+ *
+ * Parameters:
+ * geometryName - {String} Name of geometry attribute.
+ */
+ setGeometryName: function(geometryName) {
+ this.geometryName = geometryName;
+ this.format.geometryName = geometryName;
+ },
+
+ /**
+ * Method: handleRead
+ * Deal with response from the read request.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the read call.
+ */
+ handleRead: function(response, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ if(options.callback) {
+ var request = response.priv;
+ if(request.status >= 200 && request.status < 300) {
+ // success
+ var result = this.parseResponse(request, options.readOptions);
+ if (result && result.success !== false) {
+ if (options.readOptions && options.readOptions.output == "object") {
+ OpenLayers.Util.extend(response, result);
+ } else {
+ response.features = result;
+ }
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ // failure (service exception)
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = result;
+ }
+ } else {
+ // failure
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: parseResponse
+ * Read HTTP response body and return features
+ *
+ * Parameters:
+ * request - {XMLHttpRequest} The request object
+ * options - {Object} Optional object to pass to format's read
+ *
+ * Returns:
+ * {Object} or {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * An object with a features property, an array of features or a single
+ * feature.
+ */
+ parseResponse: function(request, options) {
+ var doc = request.responseXML;
+ if(!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if(!doc || doc.length <= 0) {
+ return null;
+ }
+ var result = (this.readFormat !== null) ? this.readFormat.read(doc) :
+ this.format.read(doc, options);
+ if (!this.featureNS) {
+ var format = this.readFormat || this.format;
+ this.featureNS = format.featureNS;
+ // no need to auto-configure again on subsequent reads
+ format.autoConfig = false;
+ if (!this.geometryName) {
+ this.setGeometryName(format.geometryName);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * Method: commit
+ * Given a list of feature, assemble a batch request for update, create,
+ * and delete transactions. A commit call on the prototype amounts
+ * to writing a WFS transaction - so the write method on the format
+ * is used.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ * options - {Object}
+ *
+ * Valid options properties:
+ * nativeElements - {Array({Object})} Array of objects with information for writing
+ * out <Native> elements, these objects have vendorId, safeToIgnore and
+ * value properties. The <Native> element is intended to allow access to
+ * vendor specific capabilities of any particular web feature server or
+ * datastore.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} A response object with a features
+ * property containing any insertIds and a priv property referencing
+ * the XMLHttpRequest object.
+ */
+ commit: function(features, options) {
+
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit",
+ reqFeatures: features
+ });
+ response.priv = OpenLayers.Request.POST({
+ url: options.url,
+ headers: options.headers,
+ data: this.format.write(features, options),
+ callback: this.createCallback(this.handleCommit, response, options)
+ });
+
+ return response;
+ },
+
+ /**
+ * Method: handleCommit
+ * Called when the commit request returns.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} The response object to pass
+ * to the user callback.
+ * options - {Object} The user options passed to the commit call.
+ */
+ handleCommit: function(response, options) {
+ if(options.callback) {
+ var request = response.priv;
+
+ // ensure that we have an xml doc
+ var data = request.responseXML;
+ if(!data || !data.documentElement) {
+ data = request.responseText;
+ }
+
+ var obj = this.format.read(data) || {};
+
+ response.insertIds = obj.insertIds || [];
+ if (obj.success) {
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ } else {
+ response.code = OpenLayers.Protocol.Response.FAILURE;
+ response.error = obj;
+ }
+ options.callback.call(options.scope, response);
+ }
+ },
+
+ /**
+ * Method: filterDelete
+ * Send a request that deletes all features by their filter.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} filter
+ */
+ filterDelete: function(filter, options) {
+ options = OpenLayers.Util.extend({}, options);
+ OpenLayers.Util.applyDefaults(options, this.options);
+
+ var response = new OpenLayers.Protocol.Response({
+ requestType: "commit"
+ });
+
+ var root = this.format.createElementNSPlus("wfs:Transaction", {
+ attributes: {
+ service: "WFS",
+ version: this.version
+ }
+ });
+
+ var deleteNode = this.format.createElementNSPlus("wfs:Delete", {
+ attributes: {
+ typeName: (options.featureNS ? this.featurePrefix + ":" : "") +
+ options.featureType
+ }
+ });
+
+ if(options.featureNS) {
+ deleteNode.setAttribute("xmlns:" + this.featurePrefix, options.featureNS);
+ }
+ var filterNode = this.format.writeNode("ogc:Filter", filter);
+
+ deleteNode.appendChild(filterNode);
+
+ root.appendChild(deleteNode);
+
+ var data = OpenLayers.Format.XML.prototype.write.apply(
+ this.format, [root]
+ );
+
+ return OpenLayers.Request.POST({
+ url: this.url,
+ callback : options.callback || function(){},
+ data: data
+ });
+
+ },
+
+ /**
+ * Method: abort
+ * Abort an ongoing request, the response object passed to
+ * this method must come from this protocol (as a result
+ * of a read, or commit operation).
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>}
+ */
+ abort: function(response) {
+ if (response) {
+ response.priv.abort();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_0_0.js b/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_0_0.js
new file mode 100644
index 0000000..b1e2ca1
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_0_0.js
@@ -0,0 +1,44 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS/v1.js
+ * @requires OpenLayers/Format/WFST/v1_0_0.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1_0_0
+ * A WFS v1.0.0 protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.WFS.v1_0_0> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.WFS.v1>
+ */
+OpenLayers.Protocol.WFS.v1_0_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.0.0",
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS.v1_0_0
+ * A class for giving layers WFS v1.0.0 protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ */
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1_0_0"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_1_0.js b/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_1_0.js
new file mode 100644
index 0000000..97991a6
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Protocol/WFS/v1_1_0.js
@@ -0,0 +1,68 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Protocol/WFS/v1.js
+ * @requires OpenLayers/Format/WFST/v1_1_0.js
+ */
+
+/**
+ * Class: OpenLayers.Protocol.WFS.v1_1_0
+ * A WFS v1.1.0 protocol for vector layers. Create a new instance with the
+ * <OpenLayers.Protocol.WFS.v1_1_0> constructor.
+ *
+ * Differences from the v1.0.0 protocol:
+ * - uses Filter Encoding 1.1.0 instead of 1.0.0
+ * - uses GML 3 instead of 2 if no format is provided
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.WFS.v1>
+ */
+OpenLayers.Protocol.WFS.v1_1_0 = OpenLayers.Class(OpenLayers.Protocol.WFS.v1, {
+
+ /**
+ * Property: version
+ * {String} WFS version number.
+ */
+ version: "1.1.0",
+
+ /**
+ * Constructor: OpenLayers.Protocol.WFS.v1_1_0
+ * A class for giving layers WFS v1.1.0 protocol.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ *
+ * Valid options properties:
+ * featureType - {String} Local (without prefix) feature typeName (required).
+ * featureNS - {String} Feature namespace (optional).
+ * featurePrefix - {String} Feature namespace alias (optional - only used
+ * if featureNS is provided). Default is 'feature'.
+ * geometryName - {String} Name of geometry attribute. Default is 'the_geom'.
+ * outputFormat - {String} Optional output format to use for WFS GetFeature
+ * requests. This can be any format advertized by the WFS's
+ * GetCapabilities response. If set, an appropriate readFormat also
+ * has to be provided, unless outputFormat is GML3, GML2 or JSON.
+ * readFormat - {<OpenLayers.Format>} An appropriate format parser if
+ * outputFormat is none of GML3, GML2 or JSON.
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.WFS.v1.prototype.initialize.apply(this, arguments);
+ if (this.outputFormat && !this.readFormat) {
+ if (this.outputFormat.toLowerCase() == "gml2") {
+ this.readFormat = new OpenLayers.Format.GML.v2({
+ featureType: this.featureType,
+ featureNS: this.featureNS,
+ geometryName: this.geometryName
+ });
+ } else if (this.outputFormat.toLowerCase() == "json") {
+ this.readFormat = new OpenLayers.Format.GeoJSON();
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.WFS.v1_1_0"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Renderer.js b/misc/openlayers/lib/OpenLayers/Renderer.js
new file mode 100644
index 0000000..c5c7d0d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Renderer.js
@@ -0,0 +1,432 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer
+ * This is the base class for all renderers.
+ *
+ * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
+ * It is largely composed of virtual functions that are to be implemented
+ * in technology-specific subclasses, but there is some generic code too.
+ *
+ * The functions that *are* implemented here merely deal with the maintenance
+ * of the size and extent variables, as well as the cached 'resolution'
+ * value.
+ *
+ * A note to the user that all subclasses should use getResolution() instead
+ * of directly accessing this.resolution in order to correctly use the
+ * cacheing system.
+ *
+ */
+OpenLayers.Renderer = OpenLayers.Class({
+
+ /**
+ * Property: container
+ * {DOMElement}
+ */
+ container: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: extent
+ * {<OpenLayers.Bounds>}
+ */
+ extent: null,
+
+ /**
+ * Property: locked
+ * {Boolean} If the renderer is currently in a state where many things
+ * are changing, the 'locked' property is set to true. This means
+ * that renderers can expect at least one more drawFeature event to be
+ * called with the 'locked' property set to 'true': In some renderers,
+ * this might make sense to use as a 'only update local information'
+ * flag.
+ */
+ locked: false,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>}
+ */
+ size: null,
+
+ /**
+ * Property: resolution
+ * {Float} cache of current map resolution
+ */
+ resolution: null,
+
+ /**
+ * Property: map
+ * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
+ */
+ map: null,
+
+ /**
+ * Property: featureDx
+ * {Number} Feature offset in x direction. Will be calculated for and
+ * applied to the current feature while rendering (see
+ * <calculateFeatureDx>).
+ */
+ featureDx: 0,
+
+ /**
+ * Constructor: OpenLayers.Renderer
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} options for this renderer. See sublcasses for
+ * supported options.
+ */
+ initialize: function(containerID, options) {
+ this.container = OpenLayers.Util.getElement(containerID);
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ this.container = null;
+ this.extent = null;
+ this.size = null;
+ this.resolution = null;
+ this.map = null;
+ },
+
+ /**
+ * APIMethod: supported
+ * This should be overridden by specific subclasses
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return false;
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ * We nullify the resolution cache (this.resolution) if resolutionChanged
+ * is set to true - this way it will be re-computed on the next
+ * getResolution() request.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ this.extent = extent.clone();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio);
+ this.extent = extent.wrapDateLine(this.map.getMaxExtent()).scale(ratio);
+ }
+ if (resolutionChanged) {
+ this.resolution = null;
+ }
+ return true;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Resolution has probably changed, so we nullify the resolution
+ * cache (this.resolution) -- this way it will be re-computed when
+ * next it is needed.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ this.resolution = null;
+ },
+
+ /**
+ * Method: getResolution
+ * Uses cached copy of resolution if available to minimize computing
+ *
+ * Returns:
+ * {Float} The current map's resolution
+ */
+ getResolution: function() {
+ this.resolution = this.resolution || this.map.getResolution();
+ return this.resolution;
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. The optional style argument can be used
+ * to override the feature's own style. This method should only
+ * be called from layer.drawFeature().
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} true if the feature has been drawn completely, false if not,
+ * undefined if the feature had no geometry
+ */
+ drawFeature: function(feature, style) {
+ if(style == null) {
+ style = feature.style;
+ }
+ if (feature.geometry) {
+ var bounds = feature.geometry.getBounds();
+ if(bounds) {
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+ if (!bounds.intersectsBounds(this.extent, {worldBounds: worldBounds})) {
+ style = {display: "none"};
+ } else {
+ this.calculateFeatureDx(bounds, worldBounds);
+ }
+ var rendered = this.drawGeometry(feature.geometry, style, feature.id);
+ if(style.display != "none" && style.label && rendered !== false) {
+
+ var location = feature.geometry.getCentroid();
+ if(style.labelXOffset || style.labelYOffset) {
+ var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
+ var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
+ var res = this.getResolution();
+ location.move(xOffset*res, yOffset*res);
+ }
+ this.drawText(feature.id, style, location);
+ } else {
+ this.removeText(feature.id);
+ }
+ return rendered;
+ }
+ }
+ },
+
+ /**
+ * Method: calculateFeatureDx
+ * {Number} Calculates the feature offset in x direction. Looking at the
+ * center of the feature bounds and the renderer extent, we calculate how
+ * many world widths the two are away from each other. This distance is
+ * used to shift the feature as close as possible to the center of the
+ * current enderer extent, which ensures that the feature is visible in the
+ * current viewport.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>} Bounds of the feature
+ * worldBounds - {<OpenLayers.Bounds>} Bounds of the world
+ */
+ calculateFeatureDx: function(bounds, worldBounds) {
+ this.featureDx = 0;
+ if (worldBounds) {
+ var worldWidth = worldBounds.getWidth(),
+ rendererCenterX = (this.extent.left + this.extent.right) / 2,
+ featureCenterX = (bounds.left + bounds.right) / 2,
+ worldsAway = Math.round((featureCenterX - rendererCenterX) / worldWidth);
+ this.featureDx = worldsAway * worldWidth;
+ }
+ },
+
+ /**
+ * Method: drawGeometry
+ *
+ * Draw a geometry. This should only be called from the renderer itself.
+ * Use layer.drawFeature() from outside the renderer.
+ * virtual function
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {<String>}
+ */
+ drawGeometry: function(geometry, style, featureId) {},
+
+ /**
+ * Method: drawText
+ * Function for drawing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {},
+
+ /**
+ * Method: removeText
+ * Function for removing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {},
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ * virtual function.
+ */
+ clear: function() {},
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ * How this happens is specific to the renderer. This should be
+ * called from layer.getFeatureFromEvent().
+ * Virtual function.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {},
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0, len=features.length; i<len; ++i) {
+ var feature = features[i];
+ this.eraseGeometry(feature.geometry, feature.id);
+ this.removeText(feature.id);
+ }
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Remove a geometry from the renderer (by id).
+ * virtual function.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a (different) renderer.
+ * To be implemented by subclasses that require a common renderer root for
+ * feature selection.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {},
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.container.id;
+ },
+
+ /**
+ * Method: applyDefaultSymbolizer
+ *
+ * Parameters:
+ * symbolizer - {Object}
+ *
+ * Returns:
+ * {Object}
+ */
+ applyDefaultSymbolizer: function(symbolizer) {
+ var result = OpenLayers.Util.extend({},
+ OpenLayers.Renderer.defaultSymbolizer);
+ if(symbolizer.stroke === false) {
+ delete result.strokeWidth;
+ delete result.strokeColor;
+ }
+ if(symbolizer.fill === false) {
+ delete result.fillColor;
+ }
+ OpenLayers.Util.extend(result, symbolizer);
+ return result;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.defaultSymbolizer
+ * {Object} Properties from this symbolizer will be applied to symbolizers
+ * with missing properties. This can also be used to set a global
+ * symbolizer default in OpenLayers. To be SLD 1.x compliant, add the
+ * following code before rendering any vector features:
+ * (code)
+ * OpenLayers.Renderer.defaultSymbolizer = {
+ * fillColor: "#808080",
+ * fillOpacity: 1,
+ * strokeColor: "#000000",
+ * strokeOpacity: 1,
+ * strokeWidth: 1,
+ * pointRadius: 3,
+ * graphicName: "square"
+ * };
+ * (end)
+ */
+OpenLayers.Renderer.defaultSymbolizer = {
+ fillColor: "#000000",
+ strokeColor: "#000000",
+ strokeWidth: 2,
+ fillOpacity: 1,
+ strokeOpacity: 1,
+ pointRadius: 0,
+ labelAlign: 'cm'
+};
+
+
+
+/**
+ * Constant: OpenLayers.Renderer.symbol
+ * Coordinate arrays for well known (named) symbols.
+ */
+OpenLayers.Renderer.symbol = {
+ "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
+ 303,215, 231,161, 321,161, 350,75],
+ "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
+ 4,0],
+ "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
+ "square": [0,0, 0,1, 1,1, 1,0, 0,0],
+ "triangle": [0,10, 10,10, 5,0, 0,10]
+};
diff --git a/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js b/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js
new file mode 100644
index 0000000..61a327c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Renderer/Canvas.js
@@ -0,0 +1,906 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.Canvas
+ * A renderer based on the 2D 'canvas' drawing element.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * APIProperty: hitDetection
+ * {Boolean} Allow for hit detection of features. Default is true.
+ */
+ hitDetection: true,
+
+ /**
+ * Property: hitOverflow
+ * {Number} The method for converting feature identifiers to color values
+ * supports 16777215 sequential values. Two features cannot be
+ * predictably detected if their identifiers differ by more than this
+ * value. The hitOverflow allows for bigger numbers (but the
+ * difference in values is still limited).
+ */
+ hitOverflow: 0,
+
+ /**
+ * Property: canvas
+ * {Canvas} The canvas context object.
+ */
+ canvas: null,
+
+ /**
+ * Property: features
+ * {Object} Internal object of feature/style pairs for use in redrawing the layer.
+ */
+ features: null,
+
+ /**
+ * Property: pendingRedraw
+ * {Boolean} The renderer needs a redraw call to render features added while
+ * the renderer was locked.
+ */
+ pendingRedraw: false,
+
+ /**
+ * Property: cachedSymbolBounds
+ * {Object} Internal cache of calculated symbol extents.
+ */
+ cachedSymbolBounds: {},
+
+ /**
+ * Constructor: OpenLayers.Renderer.Canvas
+ *
+ * Parameters:
+ * containerID - {<String>}
+ * options - {Object} Optional properties to be set on the renderer.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+ this.root = document.createElement("canvas");
+ this.container.appendChild(this.root);
+ this.canvas = this.root.getContext("2d");
+ this.features = {};
+ if (this.hitDetection) {
+ this.hitCanvas = document.createElement("canvas");
+ this.hitContext = this.hitCanvas.getContext("2d");
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function() {
+ OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ // always redraw features
+ return false;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. Because the Canvas renderer has
+ * 'memory' of the features that it has drawn, we have to remove the
+ * feature so it doesn't redraw.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ this.eraseFeatures(this.features[featureId][0]);
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the renderer class
+ */
+ supported: function() {
+ return OpenLayers.CANVAS_SUPPORTED;
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Once the size is updated, redraw the canvas.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>}
+ */
+ setSize: function(size) {
+ this.size = size.clone();
+ var root = this.root;
+ root.style.width = size.w + "px";
+ root.style.height = size.h + "px";
+ root.width = size.w;
+ root.height = size.h;
+ this.resolution = null;
+ if (this.hitDetection) {
+ var hitCanvas = this.hitCanvas;
+ hitCanvas.style.width = size.w + "px";
+ hitCanvas.style.height = size.h + "px";
+ hitCanvas.width = size.w;
+ hitCanvas.height = size.h;
+ }
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. Stores the feature in the features list,
+ * then redraws the layer.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} The feature has been drawn completely. If the feature has no
+ * geometry, undefined will be returned. If the feature is not rendered
+ * for other reasons, false will be returned.
+ */
+ drawFeature: function(feature, style) {
+ var rendered;
+ if (feature.geometry) {
+ style = this.applyDefaultSymbolizer(style || feature.style);
+ // don't render if display none or feature outside extent
+ var bounds = feature.geometry.getBounds();
+
+ var worldBounds;
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ worldBounds = this.map.getMaxExtent();
+ }
+
+ var intersects = bounds && bounds.intersectsBounds(this.extent, {worldBounds: worldBounds});
+
+ rendered = (style.display !== "none") && !!bounds && intersects;
+ if (rendered) {
+ // keep track of what we have rendered for redraw
+ this.features[feature.id] = [feature, style];
+ }
+ else {
+ // remove from features tracked for redraw
+ delete(this.features[feature.id]);
+ }
+ this.pendingRedraw = true;
+ }
+ if (this.pendingRedraw && !this.locked) {
+ this.redraw();
+ this.pendingRedraw = false;
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: drawGeometry
+ * Used when looping (in redraw) over the features; draws
+ * the canvas.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0; i < geometry.components.length; i++) {
+ this.drawGeometry(geometry.components[i], style, featureId);
+ }
+ return;
+ }
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ this.drawPoint(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ this.drawLineString(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ this.drawLinearRing(geometry, style, featureId);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ this.drawPolygon(geometry, style, featureId);
+ break;
+ default:
+ break;
+ }
+ },
+
+ /**
+ * Method: drawExternalGraphic
+ * Called to draw External graphics.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawExternalGraphic: function(geometry, style, featureId) {
+ var img = new Image();
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ img.title = title;
+ }
+
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius * 2;
+ height = height ? height : style.pointRadius * 2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ var onLoad = function() {
+ if(!this.features[featureId]) {
+ return;
+ }
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+ if(!isNaN(p0) && !isNaN(p1)) {
+ var x = (p0 + xOffset) | 0;
+ var y = (p1 + yOffset) | 0;
+ var canvas = this.canvas;
+ canvas.globalAlpha = opacity;
+ var factor = OpenLayers.Renderer.Canvas.drawImageScaleFactor ||
+ (OpenLayers.Renderer.Canvas.drawImageScaleFactor =
+ /android 2.1/.test(navigator.userAgent.toLowerCase()) ?
+ // 320 is the screen width of the G1 phone, for
+ // which drawImage works out of the box.
+ 320 / window.screen.width : 1
+ );
+ canvas.drawImage(
+ img, x*factor, y*factor, width*factor, height*factor
+ );
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId);
+ this.hitContext.fillRect(x, y, width, height);
+ }
+ }
+ };
+
+ img.onload = OpenLayers.Function.bind(onLoad, this);
+ img.src = style.externalGraphic;
+ },
+
+ /**
+ * Method: drawNamedSymbol
+ * Called to draw Well Known Graphic Symbol Name.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawNamedSymbol: function(geometry, style, featureId) {
+ var x, y, cx, cy, i, symbolBounds, scaling, angle;
+ var unscaledStrokeWidth;
+ var deg2rad = Math.PI / 180.0;
+
+ var symbol = OpenLayers.Renderer.symbol[style.graphicName];
+
+ if (!symbol) {
+ throw new Error(style.graphicName + ' is not a valid symbol name');
+ }
+
+ if (!symbol.length || symbol.length < 2) return;
+
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+
+ if (isNaN(p0) || isNaN(p1)) return;
+
+ // Use rounded line caps
+ this.canvas.lineCap = "round";
+ this.canvas.lineJoin = "round";
+
+ if (this.hitDetection) {
+ this.hitContext.lineCap = "round";
+ this.hitContext.lineJoin = "round";
+ }
+
+ // Scale and rotate symbols, using precalculated bounds whenever possible.
+ if (style.graphicName in this.cachedSymbolBounds) {
+ symbolBounds = this.cachedSymbolBounds[style.graphicName];
+ } else {
+ symbolBounds = new OpenLayers.Bounds();
+ for(i = 0; i < symbol.length; i+=2) {
+ symbolBounds.extend(new OpenLayers.LonLat(symbol[i], symbol[i+1]));
+ }
+ this.cachedSymbolBounds[style.graphicName] = symbolBounds;
+ }
+
+ // Push symbol scaling, translation and rotation onto the transformation stack in reverse order.
+ // Don't forget to apply all canvas transformations to the hitContext canvas as well(!)
+ this.canvas.save();
+ if (this.hitDetection) { this.hitContext.save(); }
+
+ // Step 3: place symbol at the desired location
+ this.canvas.translate(p0,p1);
+ if (this.hitDetection) { this.hitContext.translate(p0,p1); }
+
+ // Step 2a. rotate the symbol if necessary
+ angle = deg2rad * style.rotation; // will be NaN when style.rotation is undefined.
+ if (!isNaN(angle)) {
+ this.canvas.rotate(angle);
+ if (this.hitDetection) { this.hitContext.rotate(angle); }
+ }
+
+ // // Step 2: scale symbol such that pointRadius equals half the maximum symbol dimension.
+ scaling = 2.0 * style.pointRadius / Math.max(symbolBounds.getWidth(), symbolBounds.getHeight());
+ this.canvas.scale(scaling,scaling);
+ if (this.hitDetection) { this.hitContext.scale(scaling,scaling); }
+
+ // Step 1: center the symbol at the origin
+ cx = symbolBounds.getCenterLonLat().lon;
+ cy = symbolBounds.getCenterLonLat().lat;
+ this.canvas.translate(-cx,-cy);
+ if (this.hitDetection) { this.hitContext.translate(-cx,-cy); }
+
+ // Don't forget to scale stroke widths, because they are affected by canvas scale transformations as well(!)
+ // Alternative: scale symbol coordinates manually, so stroke width scaling is not needed anymore.
+ unscaledStrokeWidth = style.strokeWidth;
+ style.strokeWidth = unscaledStrokeWidth / scaling;
+
+ if (style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.fill();
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.fill();
+ }
+ }
+
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.canvas.moveTo(x,y);
+ this.canvas.lineTo(x,y);
+ }
+ this.canvas.closePath();
+ this.canvas.stroke();
+
+
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style, scaling);
+ this.hitContext.beginPath();
+ for (i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ if (i == 0) this.hitContext.moveTo(x,y);
+ this.hitContext.lineTo(x,y);
+ }
+ this.hitContext.closePath();
+ this.hitContext.stroke();
+ }
+
+ }
+
+ style.strokeWidth = unscaledStrokeWidth;
+ this.canvas.restore();
+ if (this.hitDetection) { this.hitContext.restore(); }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: setCanvasStyle
+ * Prepare the canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * style - {Object} Symbolizer hash
+ */
+ setCanvasStyle: function(type, style) {
+ if (type === "fill") {
+ this.canvas.globalAlpha = style['fillOpacity'];
+ this.canvas.fillStyle = style['fillColor'];
+ } else if (type === "stroke") {
+ this.canvas.globalAlpha = style['strokeOpacity'];
+ this.canvas.strokeStyle = style['strokeColor'];
+ this.canvas.lineWidth = style['strokeWidth'];
+ } else {
+ this.canvas.globalAlpha = 0;
+ this.canvas.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: featureIdToHex
+ * Convert a feature ID string into an RGB hex string.
+ *
+ * Parameters:
+ * featureId - {String} Feature id
+ *
+ * Returns:
+ * {String} RGB hex string.
+ */
+ featureIdToHex: function(featureId) {
+ var id = Number(featureId.split("_").pop()) + 1; // zero for no feature
+ if (id >= 16777216) {
+ this.hitOverflow = id - 16777215;
+ id = id % 16777216 + 1;
+ }
+ var hex = "000000" + id.toString(16);
+ var len = hex.length;
+ hex = "#" + hex.substring(len-6, len);
+ return hex;
+ },
+
+ /**
+ * Method: setHitContextStyle
+ * Prepare the hit canvas for drawing by setting various global settings.
+ *
+ * Parameters:
+ * type - {String} one of 'stroke', 'fill', or 'reset'
+ * featureId - {String} The feature id.
+ * symbolizer - {<OpenLayers.Symbolizer>} The symbolizer.
+ */
+ setHitContextStyle: function(type, featureId, symbolizer, strokeScaling) {
+ var hex = this.featureIdToHex(featureId);
+ if (type == "fill") {
+ this.hitContext.globalAlpha = 1.0;
+ this.hitContext.fillStyle = hex;
+ } else if (type == "stroke") {
+ this.hitContext.globalAlpha = 1.0;
+ this.hitContext.strokeStyle = hex;
+ // bump up stroke width to deal with antialiasing. If strokeScaling is defined, we're rendering a symbol
+ // on a transformed canvas, so the antialias width bump has to scale as well.
+ if (typeof strokeScaling === "undefined") {
+ this.hitContext.lineWidth = symbolizer.strokeWidth + 2;
+ } else {
+ if (!isNaN(strokeScaling)) { this.hitContext.lineWidth = symbolizer.strokeWidth + 2.0 / strokeScaling; }
+ }
+ } else {
+ this.hitContext.globalAlpha = 0;
+ this.hitContext.lineWidth = 1;
+ }
+ },
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawPoint: function(geometry, style, featureId) {
+ if(style.graphic !== false) {
+ if(style.externalGraphic) {
+ this.drawExternalGraphic(geometry, style, featureId);
+ } else if (style.graphicName && (style.graphicName != "circle")) {
+ this.drawNamedSymbol(geometry, style, featureId);
+ } else {
+ var pt = this.getLocalXY(geometry);
+ var p0 = pt[0];
+ var p1 = pt[1];
+ if(!isNaN(p0) && !isNaN(p1)) {
+ var twoPi = Math.PI*2;
+ var radius = style.pointRadius;
+ if(style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.canvas.beginPath();
+ this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+ this.canvas.fill();
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.hitContext.beginPath();
+ this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+ this.hitContext.fill();
+ }
+ }
+
+ if(style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.canvas.beginPath();
+ this.canvas.arc(p0, p1, radius, 0, twoPi, true);
+ this.canvas.stroke();
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style);
+ this.hitContext.beginPath();
+ this.hitContext.arc(p0, p1, radius, 0, twoPi, true);
+ this.hitContext.stroke();
+ }
+ this.setCanvasStyle("reset");
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawLineString: function(geometry, style, featureId) {
+ style = OpenLayers.Util.applyDefaults({fill: false}, style);
+ this.drawLinearRing(geometry, style, featureId);
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawLinearRing: function(geometry, style, featureId) {
+ if (style.fill !== false) {
+ this.setCanvasStyle("fill", style);
+ this.renderPath(this.canvas, geometry, style, featureId, "fill");
+ if (this.hitDetection) {
+ this.setHitContextStyle("fill", featureId, style);
+ this.renderPath(this.hitContext, geometry, style, featureId, "fill");
+ }
+ }
+ if (style.stroke !== false) {
+ this.setCanvasStyle("stroke", style);
+ this.renderPath(this.canvas, geometry, style, featureId, "stroke");
+ if (this.hitDetection) {
+ this.setHitContextStyle("stroke", featureId, style);
+ this.renderPath(this.hitContext, geometry, style, featureId, "stroke");
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: renderPath
+ * Render a path with stroke and optional fill.
+ */
+ renderPath: function(context, geometry, style, featureId, type) {
+ var components = geometry.components;
+ var len = components.length;
+ context.beginPath();
+ var start = this.getLocalXY(components[0]);
+ var x = start[0];
+ var y = start[1];
+ if (!isNaN(x) && !isNaN(y)) {
+ context.moveTo(start[0], start[1]);
+ for (var i=1; i<len; ++i) {
+ var pt = this.getLocalXY(components[i]);
+ context.lineTo(pt[0], pt[1]);
+ }
+ if (type === "fill") {
+ context.fill();
+ } else {
+ context.stroke();
+ }
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ */
+ drawPolygon: function(geometry, style, featureId) {
+ var components = geometry.components;
+ var len = components.length;
+ this.drawLinearRing(components[0], style, featureId);
+ // erase inner rings
+ for (var i=1; i<len; ++i) {
+ /**
+ * Note that this is overly agressive. Here we punch holes through
+ * all previously rendered features on the same canvas. A better
+ * solution for polygons with interior rings would be to draw the
+ * polygon on a sketch canvas first. We could erase all holes
+ * there and then copy the drawing to the layer canvas.
+ * TODO: http://trac.osgeo.org/openlayers/ticket/3130
+ */
+ this.canvas.globalCompositeOperation = "destination-out";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "destination-out";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({stroke: false, fillOpacity: 1.0}, style),
+ featureId
+ );
+ this.canvas.globalCompositeOperation = "source-over";
+ if (this.hitDetection) {
+ this.hitContext.globalCompositeOperation = "source-over";
+ }
+ this.drawLinearRing(
+ components[i],
+ OpenLayers.Util.applyDefaults({fill: false}, style),
+ featureId
+ );
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * location - {<OpenLayers.Point>}
+ * style - {Object}
+ */
+ drawText: function(location, style) {
+ var pt = this.getLocalXY(location);
+
+ this.setCanvasStyle("reset");
+ this.canvas.fillStyle = style.fontColor;
+ this.canvas.globalAlpha = style.fontOpacity || 1.0;
+ var fontStyle = [style.fontStyle ? style.fontStyle : "normal",
+ "normal", // "font-variant" not supported
+ style.fontWeight ? style.fontWeight : "normal",
+ style.fontSize ? style.fontSize : "1em",
+ style.fontFamily ? style.fontFamily : "sans-serif"].join(" ");
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ if (this.canvas.fillText) {
+ // HTML5
+ this.canvas.font = fontStyle;
+ this.canvas.textAlign =
+ OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[0]] ||
+ "center";
+ this.canvas.textBaseline =
+ OpenLayers.Renderer.Canvas.LABEL_ALIGN[style.labelAlign[1]] ||
+ "middle";
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight =
+ this.canvas.measureText('Mg').height ||
+ this.canvas.measureText('xx').width;
+ pt[1] += lineHeight*vfactor*(numRows-1);
+ for (var i = 0; i < numRows; i++) {
+ if (style.labelOutlineWidth) {
+ this.canvas.save();
+ this.canvas.globalAlpha = style.labelOutlineOpacity || style.fontOpacity || 1.0;
+ this.canvas.strokeStyle = style.labelOutlineColor;
+ this.canvas.lineWidth = style.labelOutlineWidth;
+ this.canvas.strokeText(labelRows[i], pt[0], pt[1] + (lineHeight*i) + 1);
+ this.canvas.restore();
+ }
+ this.canvas.fillText(labelRows[i], pt[0], pt[1] + (lineHeight*i));
+ }
+ } else if (this.canvas.mozDrawText) {
+ // Mozilla pre-Gecko1.9.1 (<FF3.1)
+ this.canvas.mozTextStyle = fontStyle;
+ // No built-in text alignment, so we measure and adjust the position
+ var hfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[0]];
+ if (hfactor == null) {
+ hfactor = -.5;
+ }
+ var vfactor =
+ OpenLayers.Renderer.Canvas.LABEL_FACTOR[style.labelAlign[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ var lineHeight = this.canvas.mozMeasureText('xx');
+ pt[1] += lineHeight*(1 + (vfactor*numRows));
+ for (var i = 0; i < numRows; i++) {
+ var x = pt[0] + (hfactor*this.canvas.mozMeasureText(labelRows[i]));
+ var y = pt[1] + (i*lineHeight);
+ this.canvas.translate(x, y);
+ this.canvas.mozDrawText(labelRows[i]);
+ this.canvas.translate(-x, -y);
+ }
+ }
+ this.setCanvasStyle("reset");
+ },
+
+ /**
+ * Method: getLocalXY
+ * transform geographic xy into pixel xy
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ */
+ getLocalXY: function(point) {
+ var resolution = this.getResolution();
+ var extent = this.extent;
+ var x = ((point.x - this.featureDx) / resolution + (-extent.left / resolution));
+ var y = ((extent.top / resolution) - point.y / resolution);
+ return [x, y];
+ },
+
+ /**
+ * Method: clear
+ * Clear all vectors from the renderer.
+ */
+ clear: function() {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ this.features = {};
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ * Returns a feature id from an event on the renderer.
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector} A feature or undefined. This method returns a
+ * feature instead of a feature id to avoid an unnecessary lookup on the
+ * layer.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId, feature;
+
+ if (this.hitDetection && this.root.style.display !== "none") {
+ // this dragging check should go in the feature handler
+ if (!this.map.dragging) {
+ var xy = evt.xy;
+ var x = xy.x | 0;
+ var y = xy.y | 0;
+ var data = this.hitContext.getImageData(x, y, 1, 1).data;
+ if (data[3] === 255) { // antialiased
+ var id = data[2] + (256 * (data[1] + (256 * data[0])));
+ if (id) {
+ featureId = "OpenLayers_Feature_Vector_" + (id - 1 + this.hitOverflow);
+ try {
+ feature = this.features[featureId][0];
+ } catch(err) {
+ // Because of antialiasing on the canvas, when the hit location is at a point where the edge of
+ // one symbol intersects the interior of another symbol, a wrong hit color (and therefore id) results.
+ // todo: set Antialiasing = 'off' on the hitContext as soon as browsers allow it.
+ }
+ }
+ }
+ }
+ }
+ return feature;
+ },
+
+ /**
+ * Method: eraseFeatures
+ * This is called by the layer to erase features; removes the feature from
+ * the list, then redraws the layer.
+ *
+ * Parameters:
+ * features - {Array(<OpenLayers.Feature.Vector>)}
+ */
+ eraseFeatures: function(features) {
+ if(!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+ for(var i=0; i<features.length; ++i) {
+ delete this.features[features[i].id];
+ }
+ this.redraw();
+ },
+
+ /**
+ * Method: redraw
+ * The real 'meat' of the function: any time things have changed,
+ * redraw() can be called to loop over all the data and (you guessed
+ * it) redraw it. Unlike Elements-based Renderers, we can't interact
+ * with things once they're drawn, to remove them, for example, so
+ * instead we have to just clear everything and draw from scratch.
+ */
+ redraw: function() {
+ if (!this.locked) {
+ var height = this.root.height;
+ var width = this.root.width;
+ this.canvas.clearRect(0, 0, width, height);
+ if (this.hitDetection) {
+ this.hitContext.clearRect(0, 0, width, height);
+ }
+ var labelMap = [];
+ var feature, geometry, style;
+ var worldBounds = (this.map.baseLayer && this.map.baseLayer.wrapDateLine) && this.map.getMaxExtent();
+ for (var id in this.features) {
+ if (!this.features.hasOwnProperty(id)) { continue; }
+ feature = this.features[id][0];
+ geometry = feature.geometry;
+ this.calculateFeatureDx(geometry.getBounds(), worldBounds);
+ style = this.features[id][1];
+ this.drawGeometry(geometry, style, feature.id);
+ if(style.label) {
+ labelMap.push([feature, style]);
+ }
+ }
+ var item;
+ for (var i=0, len=labelMap.length; i<len; ++i) {
+ item = labelMap[i];
+ this.drawText(item[0].geometry.getCentroid(), item[1]);
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Canvas"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_ALIGN = {
+ "l": "left",
+ "r": "right",
+ "t": "top",
+ "b": "bottom"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.LABEL_FACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.Canvas.LABEL_FACTOR = {
+ "l": 0,
+ "r": -1,
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Constant: OpenLayers.Renderer.Canvas.drawImageScaleFactor
+ * {Number} Scale factor to apply to the canvas drawImage arguments. This
+ * is always 1 except for Android 2.1 devices, to work around
+ * http://code.google.com/p/android/issues/detail?id=5141.
+ */
+OpenLayers.Renderer.Canvas.drawImageScaleFactor = null;
diff --git a/misc/openlayers/lib/OpenLayers/Renderer/Elements.js b/misc/openlayers/lib/OpenLayers/Renderer/Elements.js
new file mode 100644
index 0000000..18a0d79
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Renderer/Elements.js
@@ -0,0 +1,1053 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ * placed in the DOM based on given indexing methods.
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+
+ /**
+ * Property: maxZIndex
+ * {Integer} This is the largest-most z-index value for a node
+ * contained within the indexer.
+ */
+ maxZIndex: null,
+
+ /**
+ * Property: order
+ * {Array<String>} This is an array of node id's stored in the
+ * order that they should show up on screen. Id's higher up in the
+ * array (higher array index) represent nodes with higher z-indeces.
+ */
+ order: null,
+
+ /**
+ * Property: indices
+ * {Object} This is a hash that maps node ids to their z-index value
+ * stored in the indexer. This is done to make finding a nodes z-index
+ * value O(1).
+ */
+ indices: null,
+
+ /**
+ * Property: compare
+ * {Function} This is the function used to determine placement of
+ * of a new node within the indexer. If null, this defaults to to
+ * the Z_ORDER_DRAWING_ORDER comparison method.
+ */
+ compare: null,
+
+ /**
+ * APIMethod: initialize
+ * Create a new indexer with
+ *
+ * Parameters:
+ * yOrdering - {Boolean} Whether to use y-ordering.
+ */
+ initialize: function(yOrdering) {
+
+ this.compare = yOrdering ?
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
+ OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
+
+ this.clear();
+ },
+
+ /**
+ * APIMethod: insert
+ * Insert a new node into the indexer. In order to find the correct
+ * positioning for the node to be inserted, this method uses a binary
+ * search. This makes inserting O(log(n)).
+ *
+ * Parameters:
+ * newNode - {DOMElement} The new node to be inserted.
+ *
+ * Returns
+ * {DOMElement} the node before which we should insert our newNode, or
+ * null if newNode can just be appended.
+ */
+ insert: function(newNode) {
+ // If the node is known to the indexer, remove it so we can
+ // recalculate where it should go.
+ if (this.exists(newNode)) {
+ this.remove(newNode);
+ }
+
+ var nodeId = newNode.id;
+
+ this.determineZIndex(newNode);
+
+ var leftIndex = -1;
+ var rightIndex = this.order.length;
+ var middle;
+
+ while (rightIndex - leftIndex > 1) {
+ middle = parseInt((leftIndex + rightIndex) / 2);
+
+ var placement = this.compare(this, newNode,
+ OpenLayers.Util.getElement(this.order[middle]));
+
+ if (placement > 0) {
+ leftIndex = middle;
+ } else {
+ rightIndex = middle;
+ }
+ }
+
+ this.order.splice(rightIndex, 0, nodeId);
+ this.indices[nodeId] = this.getZIndex(newNode);
+
+ // If the new node should be before another in the index
+ // order, return the node before which we have to insert the new one;
+ // else, return null to indicate that the new node can be appended.
+ return this.getNextElement(rightIndex);
+ },
+
+ /**
+ * APIMethod: remove
+ *
+ * Parameters:
+ * node - {DOMElement} The node to be removed.
+ */
+ remove: function(node) {
+ var nodeId = node.id;
+ var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
+ if (arrayIndex >= 0) {
+ // Remove it from the order array, as well as deleting the node
+ // from the indeces hash.
+ this.order.splice(arrayIndex, 1);
+ delete this.indices[nodeId];
+
+ // Reset the maxium z-index based on the last item in the
+ // order array.
+ if (this.order.length > 0) {
+ var lastId = this.order[this.order.length - 1];
+ this.maxZIndex = this.indices[lastId];
+ } else {
+ this.maxZIndex = 0;
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clear
+ */
+ clear: function() {
+ this.order = [];
+ this.indices = {};
+ this.maxZIndex = 0;
+ },
+
+ /**
+ * APIMethod: exists
+ *
+ * Parameters:
+ * node - {DOMElement} The node to test for existence.
+ *
+ * Returns:
+ * {Boolean} Whether or not the node exists in the indexer?
+ */
+ exists: function(node) {
+ return (this.indices[node.id] != null);
+ },
+
+ /**
+ * APIMethod: getZIndex
+ * Get the z-index value for the current node from the node data itself.
+ *
+ * Parameters:
+ * node - {DOMElement} The node whose z-index to get.
+ *
+ * Returns:
+ * {Integer} The z-index value for the specified node (from the node
+ * data itself).
+ */
+ getZIndex: function(node) {
+ return node._style.graphicZIndex;
+ },
+
+ /**
+ * Method: determineZIndex
+ * Determine the z-index for the current node if there isn't one,
+ * and set the maximum value if we've found a new maximum.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ determineZIndex: function(node) {
+ var zIndex = node._style.graphicZIndex;
+
+ // Everything must have a zIndex. If none is specified,
+ // this means the user *must* (hint: assumption) want this
+ // node to succomb to drawing order. To enforce drawing order
+ // over all indexing methods, we'll create a new z-index that's
+ // greater than any currently in the indexer.
+ if (zIndex == null) {
+ zIndex = this.maxZIndex;
+ node._style.graphicZIndex = zIndex;
+ } else if (zIndex > this.maxZIndex) {
+ this.maxZIndex = zIndex;
+ }
+ },
+
+ /**
+ * APIMethod: getNextElement
+ * Get the next element in the order stack.
+ *
+ * Parameters:
+ * index - {Integer} The index of the current node in this.order.
+ *
+ * Returns:
+ * {DOMElement} the node following the index passed in, or
+ * null.
+ */
+ getNextElement: function(index) {
+ var nextIndex = index + 1;
+ if (nextIndex < this.order.length) {
+ var nextElement = OpenLayers.Util.getElement(this.order[nextIndex]);
+ if (nextElement == undefined) {
+ nextElement = this.getNextElement(nextIndex);
+ }
+ return nextElement;
+ } else {
+ return null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.ElementsIndexer"
+});
+
+/**
+ * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
+ * These are the compare methods for figuring out where a new node should be
+ * placed within the indexer. These methods are very similar to general
+ * sorting methods in that they return -1, 0, and 1 to specify the
+ * direction in which new nodes fall in the ordering.
+ */
+OpenLayers.ElementsIndexer.IndexingMethods = {
+
+ /**
+ * Method: Z_ORDER
+ * This compare method is used by other comparison methods.
+ * It can be used individually for ordering, but is not recommended,
+ * because it doesn't subscribe to drawing order.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER: function(indexer, newNode, nextNode) {
+ var newZIndex = indexer.getZIndex(newNode);
+
+ var returnVal = 0;
+ if (nextNode) {
+ var nextZIndex = indexer.getZIndex(nextNode);
+ returnVal = newZIndex - nextZIndex;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_DRAWING_ORDER
+ * This method orders nodes by their z-index, but does so in a way
+ * that, if there are other nodes with the same z-index, the newest
+ * drawn will be the front most within that z-index. This is the
+ * default indexing method.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ // Make Z_ORDER subscribe to drawing order by pushing it above
+ // all of the other nodes with the same z-index.
+ if (nextNode && returnVal == 0) {
+ returnVal = 1;
+ }
+
+ return returnVal;
+ },
+
+ /**
+ * APIMethod: Z_ORDER_Y_ORDER
+ * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
+ * best describes which ordering methods have precedence (though, the
+ * name would be too long). This method orders nodes by their z-index,
+ * but does so in a way that, if there are other nodes with the same
+ * z-index, the nodes with the lower y position will be "closer" than
+ * those with a higher y position. If two nodes have the exact same y
+ * position, however, then this method will revert to using drawing
+ * order to decide placement.
+ *
+ * Parameters:
+ * indexer - {<OpenLayers.ElementsIndexer>}
+ * newNode - {DOMElement}
+ * nextNode - {DOMElement}
+ *
+ * Returns:
+ * {Integer}
+ */
+ Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
+ var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+ indexer,
+ newNode,
+ nextNode
+ );
+
+ if (nextNode && returnVal === 0) {
+ var result = nextNode._boundsBottom - newNode._boundsBottom;
+ returnVal = (result === 0) ? 1 : result;
+ }
+
+ return returnVal;
+ }
+};
+
+/**
+ * Class: OpenLayers.Renderer.Elements
+ * This is another virtual class in that it should never be instantiated by
+ * itself as a Renderer. It exists because there is *tons* of shared
+ * functionality between different vector libraries which use nodes/elements
+ * as a base for rendering vectors.
+ *
+ * The highlevel bits of code that are implemented here are the adding and
+ * removing of geometries, which is essentially the same for any
+ * element-based renderer. The details of creating each node and drawing the
+ * paths are of course different, but the machinery is the same.
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
+
+ /**
+ * Property: rendererRoot
+ * {DOMElement}
+ */
+ rendererRoot: null,
+
+ /**
+ * Property: root
+ * {DOMElement}
+ */
+ root: null,
+
+ /**
+ * Property: vectorRoot
+ * {DOMElement}
+ */
+ vectorRoot: null,
+
+ /**
+ * Property: textRoot
+ * {DOMElement}
+ */
+ textRoot: null,
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: null,
+
+ /**
+ * Property: xOffset
+ * {Number} Offset to apply to the renderer viewport translation in x
+ * direction. If the renderer extent's center is on the right of the
+ * dateline (i.e. exceeds the world bounds), we shift the viewport to the
+ * left by one world width. This avoids that features disappear from the
+ * map viewport. Because our dateline handling logic in other places
+ * ensures that extents crossing the dateline always have a center
+ * exceeding the world bounds on the left, we need this offset to make sure
+ * that the same is true for the renderer extent in pixel space as well.
+ */
+ xOffset: 0,
+
+ /**
+ * Property: rightOfDateLine
+ * {Boolean} Keeps track of the location of the map extent relative to the
+ * date line. The <setExtent> method compares this value (which is the one
+ * from the previous <setExtent> call) with the current position of the map
+ * extent relative to the date line and updates the xOffset when the extent
+ * has moved from one side of the date line to the other.
+ */
+
+ /**
+ * Property: Indexer
+ * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer
+ * created upon initialization if the zIndexing or yOrdering options
+ * passed to this renderer's constructor are set to true.
+ */
+ indexer: null,
+
+ /**
+ * Constant: BACKGROUND_ID_SUFFIX
+ * {String}
+ */
+ BACKGROUND_ID_SUFFIX: "_background",
+
+ /**
+ * Constant: LABEL_ID_SUFFIX
+ * {String}
+ */
+ LABEL_ID_SUFFIX: "_label",
+
+ /**
+ * Constant: LABEL_OUTLINE_SUFFIX
+ * {String}
+ */
+ LABEL_OUTLINE_SUFFIX: "_outline",
+
+ /**
+ * Constructor: OpenLayers.Renderer.Elements
+ *
+ * Parameters:
+ * containerID - {String}
+ * options - {Object} options for this renderer.
+ *
+ * Supported options are:
+ * yOrdering - {Boolean} Whether to use y-ordering
+ * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+ * if yOrdering is set to true.
+ */
+ initialize: function(containerID, options) {
+ OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+
+ this.rendererRoot = this.createRenderRoot();
+ this.root = this.createRoot("_root");
+ this.vectorRoot = this.createRoot("_vroot");
+ this.textRoot = this.createRoot("_troot");
+
+ this.root.appendChild(this.vectorRoot);
+ this.root.appendChild(this.textRoot);
+
+ this.rendererRoot.appendChild(this.root);
+ this.container.appendChild(this.rendererRoot);
+
+ if(options && (options.zIndexing || options.yOrdering)) {
+ this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+
+ this.clear();
+
+ this.rendererRoot = null;
+ this.root = null;
+ this.xmlns = null;
+
+ OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: clear
+ * Remove all the elements from the root
+ */
+ clear: function() {
+ var child;
+ var root = this.vectorRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ root = this.textRoot;
+ if (root) {
+ while (child = root.firstChild) {
+ root.removeChild(child);
+ }
+ }
+ if (this.indexer) {
+ this.indexer.clear();
+ }
+ },
+
+ /**
+ * Method: setExtent
+ * Set the visible part of the layer.
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+ if (this.map.baseLayer && this.map.baseLayer.wrapDateLine) {
+ var rightOfDateLine,
+ ratio = extent.getWidth() / this.map.getExtent().getWidth(),
+ extent = extent.scale(1 / ratio),
+ world = this.map.getMaxExtent();
+ if (world.right > extent.left && world.right < extent.right) {
+ rightOfDateLine = true;
+ } else if (world.left > extent.left && world.left < extent.right) {
+ rightOfDateLine = false;
+ }
+ if (rightOfDateLine !== this.rightOfDateLine || resolutionChanged) {
+ coordSysUnchanged = false;
+ this.xOffset = rightOfDateLine === true ?
+ world.getWidth() / resolution : 0;
+ }
+ this.rightOfDateLine = rightOfDateLine;
+ }
+ return coordSysUnchanged;
+ },
+
+ /**
+ * Method: getNodeType
+ * This function is in charge of asking the specific renderer which type
+ * of node to create for the given geometry and style. All geometries
+ * in an Elements-based renderer consist of one node and some
+ * attributes. We have the nodeFactory() function which creates a node
+ * for us, but it takes a 'type' as input, and that is precisely what
+ * this function tells us.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) { },
+
+ /**
+ * Method: drawGeometry
+ * Draw the geometry, creating new nodes, setting paths, setting style,
+ * setting featureId on the node. This method should only be called
+ * by the renderer itself.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the geometry has been drawn completely; null if
+ * incomplete; false otherwise
+ */
+ drawGeometry: function(geometry, style, featureId) {
+ var className = geometry.CLASS_NAME;
+ var rendered = true;
+ if ((className == "OpenLayers.Geometry.Collection") ||
+ (className == "OpenLayers.Geometry.MultiPoint") ||
+ (className == "OpenLayers.Geometry.MultiLineString") ||
+ (className == "OpenLayers.Geometry.MultiPolygon")) {
+ for (var i = 0, len=geometry.components.length; i<len; i++) {
+ rendered = this.drawGeometry(
+ geometry.components[i], style, featureId) && rendered;
+ }
+ return rendered;
+ }
+
+ rendered = false;
+ var removeBackground = false;
+ if (style.display != "none") {
+ if (style.backgroundGraphic) {
+ this.redrawBackgroundNode(geometry.id, geometry, style,
+ featureId);
+ } else {
+ removeBackground = true;
+ }
+ rendered = this.redrawNode(geometry.id, geometry, style,
+ featureId);
+ }
+ if (rendered == false) {
+ var node = document.getElementById(geometry.id);
+ if (node) {
+ if (node._style.backgroundGraphic) {
+ removeBackground = true;
+ }
+ node.parentNode.removeChild(node);
+ }
+ }
+ if (removeBackground) {
+ var node = document.getElementById(
+ geometry.id + this.BACKGROUND_ID_SUFFIX);
+ if (node) {
+ node.parentNode.removeChild(node);
+ }
+ }
+ return rendered;
+ },
+
+ /**
+ * Method: redrawNode
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawNode: function(id, geometry, style, featureId) {
+ style = this.applyDefaultSymbolizer(style);
+ // Get the node if it's already on the map.
+ var node = this.nodeFactory(id, this.getNodeType(geometry, style));
+
+ // Set the data for the node, then draw it.
+ node._featureId = featureId;
+ node._boundsBottom = geometry.getBounds().bottom;
+ node._geometryClass = geometry.CLASS_NAME;
+ node._style = style;
+
+ var drawResult = this.drawGeometryNode(node, geometry, style);
+ if(drawResult === false) {
+ return false;
+ }
+
+ node = drawResult.node;
+
+ // Insert the node into the indexer so it can show us where to
+ // place it. Note that this operation is O(log(n)). If there's a
+ // performance problem (when dragging, for instance) this is
+ // likely where it would be.
+ if (this.indexer) {
+ var insert = this.indexer.insert(node);
+ if (insert) {
+ this.vectorRoot.insertBefore(node, insert);
+ } else {
+ this.vectorRoot.appendChild(node);
+ }
+ } else {
+ // if there's no indexer, simply append the node to root,
+ // but only if the node is a new one
+ if (node.parentNode !== this.vectorRoot){
+ this.vectorRoot.appendChild(node);
+ }
+ }
+
+ this.postDraw(node);
+
+ return drawResult.complete;
+ },
+
+ /**
+ * Method: redrawBackgroundNode
+ * Redraws the node using special 'background' style properties. Basically
+ * just calls redrawNode(), but instead of directly using the
+ * 'externalGraphic', 'graphicXOffset', 'graphicYOffset', and
+ * 'graphicZIndex' properties directly from the specified 'style'
+ * parameter, we create a new style object and set those properties
+ * from the corresponding 'background'-prefixed properties from
+ * specified 'style' parameter.
+ *
+ * Parameters:
+ * id - {String}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ * featureId - {String}
+ *
+ * Returns:
+ * {Boolean} true if the complete geometry could be drawn, null if parts of
+ * the geometry could not be drawn, false otherwise
+ */
+ redrawBackgroundNode: function(id, geometry, style, featureId) {
+ var backgroundStyle = OpenLayers.Util.extend({}, style);
+
+ // Set regular style attributes to apply to the background styles.
+ backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
+ backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
+ backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
+ backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
+ backgroundStyle.graphicWidth = backgroundStyle.backgroundWidth || backgroundStyle.graphicWidth;
+ backgroundStyle.graphicHeight = backgroundStyle.backgroundHeight || backgroundStyle.graphicHeight;
+
+ // Erase background styles.
+ backgroundStyle.backgroundGraphic = null;
+ backgroundStyle.backgroundXOffset = null;
+ backgroundStyle.backgroundYOffset = null;
+ backgroundStyle.backgroundGraphicZIndex = null;
+
+ return this.redrawNode(
+ id + this.BACKGROUND_ID_SUFFIX,
+ geometry,
+ backgroundStyle,
+ null
+ );
+ },
+
+ /**
+ * Method: drawGeometryNode
+ * Given a node, draw a geometry on the specified layer.
+ * node and geometry are required arguments, style is optional.
+ * This method is only called by the render itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {Object} a hash with properties "node" (the drawn node) and "complete"
+ * (null if parts of the geometry could not be drawn, false if nothing
+ * could be drawn)
+ */
+ drawGeometryNode: function(node, geometry, style) {
+ style = style || node._style;
+
+ var options = {
+ 'isFilled': style.fill === undefined ?
+ true :
+ style.fill,
+ 'isStroked': style.stroke === undefined ?
+ !!style.strokeWidth :
+ style.stroke
+ };
+ var drawn;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if(style.graphic === false) {
+ options.isFilled = false;
+ options.isStroked = false;
+ }
+ drawn = this.drawPoint(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LineString":
+ options.isFilled = false;
+ drawn = this.drawLineString(node, geometry);
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ drawn = this.drawLinearRing(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ drawn = this.drawPolygon(node, geometry);
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ drawn = this.drawRectangle(node, geometry);
+ break;
+ default:
+ break;
+ }
+
+ node._options = options;
+
+ //set style
+ //TBD simplify this
+ if (drawn != false) {
+ return {
+ node: this.setStyle(node, style, options, geometry),
+ complete: drawn
+ };
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: postDraw
+ * Things that have do be done after the geometry node is appended
+ * to its parent node. To be overridden by subclasses.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {},
+
+ /**
+ * Method: drawPoint
+ * Virtual function for drawing Point Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {},
+
+ /**
+ * Method: drawLineString
+ * Virtual function for drawing LineString Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {},
+
+ /**
+ * Method: drawLinearRing
+ * Virtual function for drawing LinearRing Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {},
+
+ /**
+ * Method: drawPolygon
+ * Virtual function for drawing Polygon Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {},
+
+ /**
+ * Method: drawRectangle
+ * Virtual function for drawing Rectangle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {},
+
+ /**
+ * Method: drawCircle
+ * Virtual function for drawing Circle Geometry.
+ * Should be implemented by subclasses.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry) {},
+
+ /**
+ * Method: removeText
+ * Removes a label
+ *
+ * Parameters:
+ * featureId - {String}
+ */
+ removeText: function(featureId) {
+ var label = document.getElementById(featureId + this.LABEL_ID_SUFFIX);
+ if (label) {
+ this.textRoot.removeChild(label);
+ }
+ var outline = document.getElementById(featureId + this.LABEL_OUTLINE_SUFFIX);
+ if (outline) {
+ this.textRoot.removeChild(outline);
+ }
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var target = evt.target;
+ var useElement = target && target.correspondingUseElement;
+ var node = useElement ? useElement : (target || evt.srcElement);
+ return node._featureId;
+ },
+
+ /**
+ * Method: eraseGeometry
+ * Erase a geometry from the renderer. In the case of a multi-geometry,
+ * we cycle through and recurse on ourselves. Otherwise, we look for a
+ * node with the geometry.id, destroy its geometry, and remove it from
+ * the DOM.
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * featureId - {String}
+ */
+ eraseGeometry: function(geometry, featureId) {
+ if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
+ (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
+ for (var i=0, len=geometry.components.length; i<len; i++) {
+ this.eraseGeometry(geometry.components[i], featureId);
+ }
+ } else {
+ var element = OpenLayers.Util.getElement(geometry.id);
+ if (element && element.parentNode) {
+ if (element.geometry) {
+ element.geometry.destroy();
+ element.geometry = null;
+ }
+ element.parentNode.removeChild(element);
+
+ if (this.indexer) {
+ this.indexer.remove(element);
+ }
+
+ if (element._style.backgroundGraphic) {
+ var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+ var bElem = OpenLayers.Util.getElement(backgroundId);
+ if (bElem && bElem.parentNode) {
+ // No need to destroy the geometry since the element and the background
+ // node share the same geometry.
+ bElem.parentNode.removeChild(bElem);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: nodeFactory
+ * Create new node of the specified type, with the (optional) specified id.
+ *
+ * If node already exists with same ID and a different type, we remove it
+ * and then call ourselves again to recreate it.
+ *
+ * Parameters:
+ * id - {String}
+ * type - {String} type Kind of node to draw.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ */
+ nodeFactory: function(id, type) {
+ var node = OpenLayers.Util.getElement(id);
+ if (node) {
+ if (!this.nodeTypeCompare(node, type)) {
+ node.parentNode.removeChild(node);
+ node = this.nodeFactory(id, type);
+ }
+ } else {
+ node = this.createNode(type, id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ * This function must be overridden by subclasses.
+ */
+ nodeTypeCompare: function(node, type) {},
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw.
+ * id - {String} Id for node.
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id.
+ * This function must be overridden by subclasses.
+ */
+ createNode: function(type, id) {},
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ */
+ moveRoot: function(renderer) {
+ var root = this.root;
+ if(renderer.root.parentNode == this.rendererRoot) {
+ root = renderer.root;
+ }
+ root.parentNode.removeChild(root);
+ renderer.rendererRoot.appendChild(root);
+ },
+
+ /**
+ * Method: getRenderLayerId
+ * Gets the layer that this renderer's output appears on. If moveRoot was
+ * used, this will be different from the id of the layer containing the
+ * features rendered by this renderer.
+ *
+ * Returns:
+ * {String} the id of the output layer.
+ */
+ getRenderLayerId: function() {
+ return this.root.parentNode.parentNode.id;
+ },
+
+ /**
+ * Method: isComplexSymbol
+ * Determines if a symbol cannot be rendered using drawCircle
+ *
+ * Parameters:
+ * graphicName - {String}
+ *
+ * Returns
+ * {Boolean} true if the symbol is complex, false if not
+ */
+ isComplexSymbol: function(graphicName) {
+ return (graphicName != "circle") && !!graphicName;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Renderer/SVG.js b/misc/openlayers/lib/OpenLayers/Renderer/SVG.js
new file mode 100644
index 0000000..263aac0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Renderer/SVG.js
@@ -0,0 +1,1012 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ *
+ * Inherits:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: "http://www.w3.org/2000/svg",
+
+ /**
+ * Property: xlinkns
+ * {String}
+ */
+ xlinkns: "http://www.w3.org/1999/xlink",
+
+ /**
+ * Constant: MAX_PIXEL
+ * {Integer} Firefox has a limitation where values larger or smaller than
+ * about 15000 in an SVG document lock the browser up. This
+ * works around it.
+ */
+ MAX_PIXEL: 15000,
+
+ /**
+ * Property: translationParameters
+ * {Object} Hash with "x" and "y" properties
+ */
+ translationParameters: null,
+
+ /**
+ * Property: symbolMetrics
+ * {Object} Cache for symbol metrics according to their svg coordinate
+ * space. This is an object keyed by the symbol's id, and values are
+ * an array of [width, centerX, centerY].
+ */
+ symbolMetrics: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.SVG
+ *
+ * Parameters:
+ * containerID - {String}
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ this.translationParameters = {x: 0, y: 0};
+
+ this.symbolMetrics = {};
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SVG renderer
+ */
+ supported: function() {
+ var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+ return (document.implementation &&
+ (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
+ document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
+ document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+ },
+
+ /**
+ * Method: inValidRange
+ * See #669 for more information
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ * xyOnly - {Boolean} whether or not to just check for x and y, which means
+ * to not take the current translation parameters into account if true.
+ *
+ * Returns:
+ * {Boolean} Whether or not the 'x' and 'y' coordinates are in the
+ * valid range.
+ */
+ inValidRange: function(x, y, xyOnly) {
+ var left = x + (xyOnly ? 0 : this.translationParameters.x);
+ var top = y + (xyOnly ? 0 : this.translationParameters.y);
+ return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+ top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+ },
+
+ /**
+ * Method: setExtent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ * False otherwise.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+
+ var resolution = this.getResolution(),
+ left = -extent.left / resolution,
+ top = extent.top / resolution;
+
+ // If the resolution has changed, start over changing the corner, because
+ // the features will redraw.
+ if (resolutionChanged) {
+ this.left = left;
+ this.top = top;
+ // Set the viewbox
+ var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+ this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+ this.translate(this.xOffset, 0);
+ return true;
+ } else {
+ var inRange = this.translate(left - this.left + this.xOffset, top - this.top);
+ if (!inRange) {
+ // recenter the coordinate system
+ this.setExtent(extent, true);
+ }
+ return coordSysUnchanged && inRange;
+ }
+ },
+
+ /**
+ * Method: translate
+ * Transforms the SVG coordinate system
+ *
+ * Parameters:
+ * x - {Float}
+ * y - {Float}
+ *
+ * Returns:
+ * {Boolean} true if the translation parameters are in the valid coordinates
+ * range, false otherwise.
+ */
+ translate: function(x, y) {
+ if (!this.inValidRange(x, y, true)) {
+ return false;
+ } else {
+ var transformString = "";
+ if (x || y) {
+ transformString = "translate(" + x + "," + y + ")";
+ }
+ this.root.setAttributeNS(null, "transform", transformString);
+ this.translationParameters = {x: x, y: y};
+ return true;
+ }
+ },
+
+ /**
+ * Method: setSize
+ * Sets the size of the drawing surface.
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} The size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+ this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+ },
+
+ /**
+ * Method: getNodeType
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "image";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "svg";
+ } else {
+ nodeType = "circle";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ nodeType = "polyline";
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ nodeType = "polygon";
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "path";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a SVG node.
+ *
+ * Takes care to adjust stroke width and point radius to be
+ * resolution-relative
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ */
+ setStyle: function(node, style, options) {
+ style = style || node._style;
+ options = options || node._options;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.setAttributeNS(null, "title", title);
+ //Standards-conformant SVG
+ // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
+ var titleNode = node.getElementsByTagName("title");
+ if (titleNode.length > 0) {
+ titleNode[0].firstChild.textContent = title;
+ } else {
+ var label = this.nodeFactory(null, "title");
+ label.textContent = title;
+ node.appendChild(label);
+ }
+ }
+
+ var r = parseFloat(node.getAttributeNS(null, "r"));
+ var widthFactor = 1;
+ var pos;
+ if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+ node.style.visibility = "";
+ if (style.graphic === false) {
+ node.style.visibility = "hidden";
+ } else if (style.externalGraphic) {
+ pos = this.getPosition(node);
+ if (style.graphicWidth && style.graphicHeight) {
+ node.setAttributeNS(null, "preserveAspectRatio", "none");
+ }
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+ node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+ node.setAttributeNS(null, "width", width);
+ node.setAttributeNS(null, "height", height);
+ node.setAttributeNS(this.xlinkns, "xlink:href", style.externalGraphic);
+ node.setAttributeNS(null, "style", "opacity: "+opacity);
+ node.onclick = OpenLayers.Event.preventDefault;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ // the symbol viewBox is three times as large as the symbol
+ var offset = style.pointRadius * 3;
+ var size = offset * 2;
+ var src = this.importSymbol(style.graphicName);
+ pos = this.getPosition(node);
+ widthFactor = this.symbolMetrics[src.id][0] * 3 / size;
+
+ // remove the node from the dom before we modify it. This
+ // prevents various rendering issues in Safari and FF
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+ if(parent) {
+ parent.removeChild(node);
+ }
+
+ // The more appropriate way to implement this would be use/defs,
+ // but due to various issues in several browsers, it is safer to
+ // copy the symbols instead of referencing them.
+ // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
+ // and this email thread
+ // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
+ node.firstChild && node.removeChild(node.firstChild);
+ node.appendChild(src.firstChild.cloneNode(true));
+ node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
+
+ node.setAttributeNS(null, "width", size);
+ node.setAttributeNS(null, "height", size);
+ node.setAttributeNS(null, "x", pos.x - offset);
+ node.setAttributeNS(null, "y", pos.y - offset);
+
+ // now that the node has all its new properties, insert it
+ // back into the dom where it was
+ if(nextSibling) {
+ parent.insertBefore(node, nextSibling);
+ } else if(parent) {
+ parent.appendChild(node);
+ }
+ } else {
+ node.setAttributeNS(null, "r", style.pointRadius);
+ }
+
+ var rotation = style.rotation;
+
+ if ((rotation !== undefined || node._rotation !== undefined) && pos) {
+ node._rotation = rotation;
+ rotation |= 0;
+ if (node.nodeName !== "svg") {
+ node.setAttributeNS(null, "transform",
+ "rotate(" + rotation + " " + pos.x + " " +
+ pos.y + ")");
+ } else {
+ var metrics = this.symbolMetrics[src.id];
+ node.firstChild.setAttributeNS(null, "transform", "rotate("
+ + rotation + " "
+ + metrics[1] + " "
+ + metrics[2] + ")");
+ }
+ }
+ }
+
+ if (options.isFilled) {
+ node.setAttributeNS(null, "fill", style.fillColor);
+ node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+ } else {
+ node.setAttributeNS(null, "fill", "none");
+ }
+
+ if (options.isStroked) {
+ node.setAttributeNS(null, "stroke", style.strokeColor);
+ node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+ node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+ node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
+ // Hard-coded linejoin for now, to make it look the same as in VML.
+ // There is no strokeLinejoin property yet for symbolizers.
+ node.setAttributeNS(null, "stroke-linejoin", "round");
+ style.strokeDashstyle && node.setAttributeNS(null,
+ "stroke-dasharray", this.dashStyle(style, widthFactor));
+ } else {
+ node.setAttributeNS(null, "stroke", "none");
+ }
+
+ if (style.pointerEvents) {
+ node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+ }
+
+ if (style.cursor != null) {
+ node.setAttributeNS(null, "cursor", style.cursor);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ * widthFactor - {Number}
+ *
+ * Returns:
+ * {String} A SVG compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style, widthFactor) {
+ var w = style.strokeWidth * widthFactor;
+ var str = style.strokeDashstyle;
+ switch (str) {
+ case 'solid':
+ return 'none';
+ case 'dot':
+ return [1, 4 * w].join();
+ case 'dash':
+ return [4 * w, 4 * w].join();
+ case 'dashdot':
+ return [4 * w, 4 * w, 1, 4 * w].join();
+ case 'longdash':
+ return [8 * w, 4 * w].join();
+ case 'longdashdot':
+ return [8 * w, 4 * w, 1, 4 * w].join();
+ default:
+ return OpenLayers.String.trim(str).replace(/\s+/g, ",");
+ }
+ },
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElementNS(this.xmlns, type);
+ if (id) {
+ node.setAttributeNS(null, "id", id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+ return (type == node.nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ var svg = this.nodeFactory(this.container.id + "_svgRoot", "svg");
+ svg.style.display = "block";
+ return svg;
+ },
+
+ /**
+ * Method: createRoot
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "g");
+ },
+
+ /**
+ * Method: createDefs
+ *
+ * Returns:
+ * {DOMElement} The element to which we'll add the symbol definitions
+ */
+ createDefs: function() {
+ var defs = this.nodeFactory(this.container.id + "_defs", "defs");
+ this.rendererRoot.appendChild(defs);
+ return defs;
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {Float}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry, radius) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "cx", x);
+ node.setAttributeNS(null, "cy", y);
+ node.setAttributeNS(null, "r", radius);
+ return node;
+ } else {
+ return false;
+ }
+
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {
+ var componentsResult = this.getComponentsString(geometry.components);
+ if (componentsResult.path) {
+ node.setAttributeNS(null, "points", componentsResult.path);
+ return (componentsResult.complete ? node : null);
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {
+ var d = "";
+ var draw = true;
+ var complete = true;
+ var linearRingResult, path;
+ for (var j=0, len=geometry.components.length; j<len; j++) {
+ d += " M";
+ linearRingResult = this.getComponentsString(
+ geometry.components[j].components, " ");
+ path = linearRingResult.path;
+ if (path) {
+ d += " " + path;
+ complete = linearRingResult.complete && complete;
+ } else {
+ draw = false;
+ }
+ }
+ d += " z";
+ if (draw) {
+ node.setAttributeNS(null, "d", d);
+ node.setAttributeNS(null, "fill-rule", "evenodd");
+ return complete ? node : null;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawRectangle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+ var x = ((geometry.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - geometry.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ node.setAttributeNS(null, "x", x);
+ node.setAttributeNS(null, "y", y);
+ node.setAttributeNS(null, "width", geometry.width / resolution);
+ node.setAttributeNS(null, "height", geometry.height / resolution);
+ return node;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var drawOutline = (!!style.labelOutlineWidth);
+ // First draw text in halo color and size and overlay the
+ // normal text afterwards
+ if (drawOutline) {
+ var outlineStyle = OpenLayers.Util.extend({}, style);
+ outlineStyle.fontColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeColor = outlineStyle.labelOutlineColor;
+ outlineStyle.fontStrokeWidth = style.labelOutlineWidth;
+ if (style.labelOutlineOpacity) {
+ outlineStyle.fontOpacity = style.labelOutlineOpacity;
+ }
+ delete outlineStyle.labelOutlineWidth;
+ this.drawText(featureId, outlineStyle, location);
+ }
+
+ var resolution = this.getResolution();
+
+ var x = ((location.x - this.featureDx) / resolution + this.left);
+ var y = (location.y / resolution - this.top);
+
+ var suffix = (drawOutline)?this.LABEL_OUTLINE_SUFFIX:this.LABEL_ID_SUFFIX;
+ var label = this.nodeFactory(featureId + suffix, "text");
+
+ label.setAttributeNS(null, "x", x);
+ label.setAttributeNS(null, "y", -y);
+
+ if (style.fontColor) {
+ label.setAttributeNS(null, "fill", style.fontColor);
+ }
+ if (style.fontStrokeColor) {
+ label.setAttributeNS(null, "stroke", style.fontStrokeColor);
+ }
+ if (style.fontStrokeWidth) {
+ label.setAttributeNS(null, "stroke-width", style.fontStrokeWidth);
+ }
+ if (style.fontOpacity) {
+ label.setAttributeNS(null, "opacity", style.fontOpacity);
+ }
+ if (style.fontFamily) {
+ label.setAttributeNS(null, "font-family", style.fontFamily);
+ }
+ if (style.fontSize) {
+ label.setAttributeNS(null, "font-size", style.fontSize);
+ }
+ if (style.fontWeight) {
+ label.setAttributeNS(null, "font-weight", style.fontWeight);
+ }
+ if (style.fontStyle) {
+ label.setAttributeNS(null, "font-style", style.fontStyle);
+ }
+ if (style.labelSelect === true) {
+ label.setAttributeNS(null, "pointer-events", "visible");
+ label._featureId = featureId;
+ } else {
+ label.setAttributeNS(null, "pointer-events", "none");
+ }
+ var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
+ label.setAttributeNS(null, "text-anchor",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[0]] || "middle");
+
+ if (OpenLayers.IS_GECKO === true) {
+ label.setAttributeNS(null, "dominant-baseline",
+ OpenLayers.Renderer.SVG.LABEL_ALIGN[align[1]] || "central");
+ }
+
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ while (label.childNodes.length > numRows) {
+ label.removeChild(label.lastChild);
+ }
+ for (var i = 0; i < numRows; i++) {
+ var tspan = this.nodeFactory(featureId + suffix + "_tspan_" + i, "tspan");
+ if (style.labelSelect === true) {
+ tspan._featureId = featureId;
+ tspan._geometry = location;
+ tspan._geometryClass = location.CLASS_NAME;
+ }
+ if (OpenLayers.IS_GECKO === false) {
+ tspan.setAttributeNS(null, "baseline-shift",
+ OpenLayers.Renderer.SVG.LABEL_VSHIFT[align[1]] || "-35%");
+ }
+ tspan.setAttribute("x", x);
+ if (i == 0) {
+ var vfactor = OpenLayers.Renderer.SVG.LABEL_VFACTOR[align[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
+ } else {
+ tspan.setAttribute("dy", "1em");
+ }
+ tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
+ if (!tspan.parentNode) {
+ label.appendChild(tspan);
+ }
+ }
+
+ if (!label.parentNode) {
+ this.textRoot.appendChild(label);
+ }
+ },
+
+ /**
+ * Method: getComponentString
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+ * separator - {String} character between coordinate pairs. Defaults to ","
+ *
+ * Returns:
+ * {Object} hash with properties "path" (the string created from the
+ * components and "complete" (false if the renderer was unable to
+ * draw all components)
+ */
+ getComponentsString: function(components, separator) {
+ var renderCmp = [];
+ var complete = true;
+ var len = components.length;
+ var strings = [];
+ var str, component;
+ for(var i=0; i<len; i++) {
+ component = components[i];
+ renderCmp.push(component);
+ str = this.getShortString(component);
+ if (str) {
+ strings.push(str);
+ } else {
+ // The current component is outside the valid range. Let's
+ // see if the previous or next component is inside the range.
+ // If so, add the coordinate of the intersection with the
+ // valid range bounds.
+ if (i > 0) {
+ if (this.getShortString(components[i - 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i-1]));
+ }
+ }
+ if (i < len - 1) {
+ if (this.getShortString(components[i + 1])) {
+ strings.push(this.clipLine(components[i],
+ components[i+1]));
+ }
+ }
+ complete = false;
+ }
+ }
+
+ return {
+ path: strings.join(separator || ","),
+ complete: complete
+ };
+ },
+
+ /**
+ * Method: clipLine
+ * Given two points (one inside the valid range, and one outside),
+ * clips the line betweeen the two points so that the new points are both
+ * inside the valid range.
+ *
+ * Parameters:
+ * badComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * invalid point
+ * goodComponent - {<OpenLayers.Geometry.Point>} original geometry of the
+ * valid point
+ * Returns
+ * {String} the SVG coordinate pair of the clipped point (like
+ * getShortString), or an empty string if both passed componets are at
+ * the same point.
+ */
+ clipLine: function(badComponent, goodComponent) {
+ if (goodComponent.equals(badComponent)) {
+ return "";
+ }
+ var resolution = this.getResolution();
+ var maxX = this.MAX_PIXEL - this.translationParameters.x;
+ var maxY = this.MAX_PIXEL - this.translationParameters.y;
+ var x1 = (goodComponent.x - this.featureDx) / resolution + this.left;
+ var y1 = this.top - goodComponent.y / resolution;
+ var x2 = (badComponent.x - this.featureDx) / resolution + this.left;
+ var y2 = this.top - badComponent.y / resolution;
+ var k;
+ if (x2 < -maxX || x2 > maxX) {
+ k = (y2 - y1) / (x2 - x1);
+ x2 = x2 < 0 ? -maxX : maxX;
+ y2 = y1 + (x2 - x1) * k;
+ }
+ if (y2 < -maxY || y2 > maxY) {
+ k = (x2 - x1) / (y2 - y1);
+ y2 = y2 < 0 ? -maxY : maxY;
+ x2 = x1 + (y2 - y1) * k;
+ }
+ return x2 + "," + y2;
+ },
+
+ /**
+ * Method: getShortString
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {String} or false if point is outside the valid range
+ */
+ getShortString: function(point) {
+ var resolution = this.getResolution();
+ var x = ((point.x - this.featureDx) / resolution + this.left);
+ var y = (this.top - point.y / resolution);
+
+ if (this.inValidRange(x, y)) {
+ return x + "," + y;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * Method: getPosition
+ * Finds the position of an svg node.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ *
+ * Returns:
+ * {Object} hash with x and y properties, representing the coordinates
+ * within the svg coordinate system
+ */
+ getPosition: function(node) {
+ return({
+ x: parseFloat(node.getAttributeNS(null, "cx")),
+ y: parseFloat(node.getAttributeNS(null, "cy"))
+ });
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {DOMElement} - the imported symbol
+ */
+ importSymbol: function (graphicName) {
+ if (!this.defs) {
+ // create svg defs tag
+ this.defs = this.createDefs();
+ }
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the defs
+ var existing = document.getElementById(id);
+ if (existing != null) {
+ return existing;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolNode = this.nodeFactory(id, "symbol");
+ var node = this.nodeFactory(null, "polygon");
+ symbolNode.appendChild(node);
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var points = [];
+ var x,y;
+ for (var i=0; i<symbol.length; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+ points.push(x, ",", y);
+ }
+
+ node.setAttributeNS(null, "points", points.join(" "));
+
+ var width = symbolExtent.getWidth();
+ var height = symbolExtent.getHeight();
+ // create a viewBox three times as large as the symbol itself,
+ // to allow for strokeWidth being displayed correctly at the corners.
+ var viewBox = [symbolExtent.left - width,
+ symbolExtent.bottom - height, width * 3, height * 3];
+ symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+ this.symbolMetrics[id] = [
+ Math.max(width, height),
+ symbolExtent.getCenterLonLat().lon,
+ symbolExtent.getCenterLonLat().lat
+ ];
+
+ this.defs.appendChild(symbolNode);
+ return symbolNode;
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
+ if(!featureId) {
+ var target = evt.target;
+ featureId = target.parentNode && target != this.rendererRoot ?
+ target.parentNode._featureId : undefined;
+ }
+ return featureId;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_ALIGN = {
+ "l": "start",
+ "r": "end",
+ "b": "bottom",
+ "t": "hanging"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VSHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VSHIFT = {
+ // according to
+ // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
+ // a baseline-shift of -70% shifts the text exactly from the
+ // bottom to the top of the baseline, so -35% moves the text to
+ // the center of the baseline.
+ "t": "-70%",
+ "b": "0"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG.LABEL_VFACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.SVG.LABEL_VFACTOR = {
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Function: OpenLayers.Renderer.SVG.preventDefault
+ * *Deprecated*. Use <OpenLayers.Event.preventDefault> method instead.
+ * Used to prevent default events (especially opening images in a new tab on
+ * ctrl-click) from being executed for externalGraphic symbols
+ */
+OpenLayers.Renderer.SVG.preventDefault = function(e) {
+ OpenLayers.Event.preventDefault(e);
+};
diff --git a/misc/openlayers/lib/OpenLayers/Renderer/VML.js b/misc/openlayers/lib/OpenLayers/Renderer/VML.js
new file mode 100644
index 0000000..8f6374b
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Renderer/VML.js
@@ -0,0 +1,985 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.VML
+ * Render vector features in browsers with VML capability. Construct a new
+ * VML renderer with the <OpenLayers.Renderer.VML> constructor.
+ *
+ * Note that for all calculations in this class, we use (num | 0) to truncate a
+ * float value to an integer. This is done because it seems that VML doesn't
+ * support float values.
+ *
+ * Inherits from:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Property: xmlns
+ * {String} XML Namespace URN
+ */
+ xmlns: "urn:schemas-microsoft-com:vml",
+
+ /**
+ * Property: symbolCache
+ * {DOMElement} node holding symbols. This hash is keyed by symbol name,
+ * and each value is a hash with a "path" and an "extent" property.
+ */
+ symbolCache: {},
+
+ /**
+ * Property: offset
+ * {Object} Hash with "x" and "y" properties
+ */
+ offset: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.VML
+ * Create a new VML renderer.
+ *
+ * Parameters:
+ * containerID - {String} The id for the element that contains the renderer
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ if (!document.namespaces.olv) {
+ document.namespaces.add("olv", this.xmlns);
+ var style = document.createStyleSheet();
+ var shapes = ['shape','rect', 'oval', 'fill', 'stroke', 'imagedata', 'group','textbox'];
+ for (var i = 0, len = shapes.length; i < len; i++) {
+
+ style.addRule('olv\\:' + shapes[i], "behavior: url(#default#VML); " +
+ "position: absolute; display: inline-block;");
+ }
+ }
+
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+ },
+
+ /**
+ * APIMethod: supported
+ * Determine whether a browser supports this renderer.
+ *
+ * Returns:
+ * {Boolean} The browser supports the VML renderer
+ */
+ supported: function() {
+ return !!(document.namespaces);
+ },
+
+ /**
+ * Method: setExtent
+ * Set the renderer's extent
+ *
+ * Parameters:
+ * extent - {<OpenLayers.Bounds>}
+ * resolutionChanged - {Boolean}
+ *
+ * Returns:
+ * {Boolean} true to notify the layer that the new extent does not exceed
+ * the coordinate range, and the features will not need to be redrawn.
+ */
+ setExtent: function(extent, resolutionChanged) {
+ var coordSysUnchanged = OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, arguments);
+ var resolution = this.getResolution();
+
+ var left = (extent.left/resolution) | 0;
+ var top = (extent.top/resolution - this.size.h) | 0;
+ if (resolutionChanged || !this.offset) {
+ this.offset = {x: left, y: top};
+ left = 0;
+ top = 0;
+ } else {
+ left = left - this.offset.x;
+ top = top - this.offset.y;
+ }
+
+
+ var org = (left - this.xOffset) + " " + top;
+ this.root.coordorigin = org;
+ var roots = [this.root, this.vectorRoot, this.textRoot];
+ var root;
+ for(var i=0, len=roots.length; i<len; ++i) {
+ root = roots[i];
+
+ var size = this.size.w + " " + this.size.h;
+ root.coordsize = size;
+
+ }
+ // flip the VML display Y axis upside down so it
+ // matches the display Y axis of the map
+ this.root.style.flip = "y";
+
+ return coordSysUnchanged;
+ },
+
+
+ /**
+ * Method: setSize
+ * Set the size of the drawing surface
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size>} the size of the drawing surface
+ */
+ setSize: function(size) {
+ OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+ // setting width and height on all roots to avoid flicker which we
+ // would get with 100% width and height on child roots
+ var roots = [
+ this.rendererRoot,
+ this.root,
+ this.vectorRoot,
+ this.textRoot
+ ];
+ var w = this.size.w + "px";
+ var h = this.size.h + "px";
+ var root;
+ for(var i=0, len=roots.length; i<len; ++i) {
+ root = roots[i];
+ root.style.width = w;
+ root.style.height = h;
+ }
+ },
+
+ /**
+ * Method: getNodeType
+ * Get the node type for a geometry and style
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "olv:rect";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "olv:shape";
+ } else {
+ nodeType = "olv:oval";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "olv:rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ case "OpenLayers.Geometry.LinearRing":
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "olv:shape";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a VML node.
+ *
+ * Parameters:
+ * node - {DOMElement} An VML element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ setStyle: function(node, style, options, geometry) {
+ style = style || node._style;
+ options = options || node._options;
+ var fillColor = style.fillColor;
+
+ var title = style.title || style.graphicTitle;
+ if (title) {
+ node.title = title;
+ }
+
+ if (node._geometryClass === "OpenLayers.Geometry.Point") {
+ if (style.externalGraphic) {
+ options.isFilled = true;
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+
+ var resolution = this.getResolution();
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset : -(0.5 * height);
+
+ node.style.left = ((((geometry.x - this.featureDx)/resolution - this.offset.x)+xOffset) | 0) + "px";
+ node.style.top = (((geometry.y/resolution - this.offset.y)-(yOffset+height)) | 0) + "px";
+ node.style.width = width + "px";
+ node.style.height = height + "px";
+ node.style.flip = "y";
+
+ // modify fillColor and options for stroke styling below
+ fillColor = "none";
+ options.isStroked = false;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ var cache = this.importSymbol(style.graphicName);
+ node.path = cache.path;
+ node.coordorigin = cache.left + "," + cache.bottom;
+ var size = cache.size;
+ node.coordsize = size + "," + size;
+ this.drawCircle(node, geometry, style.pointRadius);
+ node.style.flip = "y";
+ } else {
+ this.drawCircle(node, geometry, style.pointRadius);
+ }
+ }
+
+ // fill
+ if (options.isFilled) {
+ node.fillcolor = fillColor;
+ } else {
+ node.filled = "false";
+ }
+ var fills = node.getElementsByTagName("fill");
+ var fill = (fills.length == 0) ? null : fills[0];
+ if (!options.isFilled) {
+ if (fill) {
+ node.removeChild(fill);
+ }
+ } else {
+ if (!fill) {
+ fill = this.createNode('olv:fill', node.id + "_fill");
+ }
+ fill.opacity = style.fillOpacity;
+
+ if (node._geometryClass === "OpenLayers.Geometry.Point" &&
+ style.externalGraphic) {
+
+ // override fillOpacity
+ if (style.graphicOpacity) {
+ fill.opacity = style.graphicOpacity;
+ }
+
+ fill.src = style.externalGraphic;
+ fill.type = "frame";
+
+ if (!(style.graphicWidth && style.graphicHeight)) {
+ fill.aspect = "atmost";
+ }
+ }
+ if (fill.parentNode != node) {
+ node.appendChild(fill);
+ }
+ }
+
+ // additional rendering for rotated graphics or symbols
+ var rotation = style.rotation;
+ if ((rotation !== undefined || node._rotation !== undefined)) {
+ node._rotation = rotation;
+ if (style.externalGraphic) {
+ this.graphicRotate(node, xOffset, yOffset, style);
+ // make the fill fully transparent, because we now have
+ // the graphic as imagedata element. We cannot just remove
+ // the fill, because this is part of the hack described
+ // in graphicRotate
+ fill.opacity = 0;
+ } else if(node._geometryClass === "OpenLayers.Geometry.Point") {
+ node.style.rotation = rotation || 0;
+ }
+ }
+
+ // stroke
+ var strokes = node.getElementsByTagName("stroke");
+ var stroke = (strokes.length == 0) ? null : strokes[0];
+ if (!options.isStroked) {
+ node.stroked = false;
+ if (stroke) {
+ stroke.on = false;
+ }
+ } else {
+ if (!stroke) {
+ stroke = this.createNode('olv:stroke', node.id + "_stroke");
+ node.appendChild(stroke);
+ }
+ stroke.on = true;
+ stroke.color = style.strokeColor;
+ stroke.weight = style.strokeWidth + "px";
+ stroke.opacity = style.strokeOpacity;
+ stroke.endcap = style.strokeLinecap == 'butt' ? 'flat' :
+ (style.strokeLinecap || 'round');
+ if (style.strokeDashstyle) {
+ stroke.dashstyle = this.dashStyle(style);
+ }
+ }
+
+ if (style.cursor != "inherit" && style.cursor != null) {
+ node.style.cursor = style.cursor;
+ }
+ return node;
+ },
+
+ /**
+ * Method: graphicRotate
+ * If a point is to be styled with externalGraphic and rotation, VML fills
+ * cannot be used to display the graphic, because rotation of graphic
+ * fills is not supported by the VML implementation of Internet Explorer.
+ * This method creates a olv:imagedata element inside the VML node,
+ * DXImageTransform.Matrix and BasicImage filters for rotation and
+ * opacity, and a 3-step hack to remove rendering artefacts from the
+ * graphic and preserve the ability of graphics to trigger events.
+ * Finally, OpenLayers methods are used to determine the correct
+ * insertion point of the rotated image, because DXImageTransform.Matrix
+ * does the rotation without the ability to specify a rotation center
+ * point.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * xOffset - {Number} rotation center relative to image, x coordinate
+ * yOffset - {Number} rotation center relative to image, y coordinate
+ * style - {Object}
+ */
+ graphicRotate: function(node, xOffset, yOffset, style) {
+ var style = style || node._style;
+ var rotation = style.rotation || 0;
+
+ var aspectRatio, size;
+ if (!(style.graphicWidth && style.graphicHeight)) {
+ // load the image to determine its size
+ var img = new Image();
+ img.onreadystatechange = OpenLayers.Function.bind(function() {
+ if(img.readyState == "complete" ||
+ img.readyState == "interactive") {
+ aspectRatio = img.width / img.height;
+ size = Math.max(style.pointRadius * 2,
+ style.graphicWidth || 0,
+ style.graphicHeight || 0);
+ xOffset = xOffset * aspectRatio;
+ style.graphicWidth = size * aspectRatio;
+ style.graphicHeight = size;
+ this.graphicRotate(node, xOffset, yOffset, style);
+ }
+ }, this);
+ img.src = style.externalGraphic;
+
+ // will be called again by the onreadystate handler
+ return;
+ } else {
+ size = Math.max(style.graphicWidth, style.graphicHeight);
+ aspectRatio = style.graphicWidth / style.graphicHeight;
+ }
+
+ var width = Math.round(style.graphicWidth || size * aspectRatio);
+ var height = Math.round(style.graphicHeight || size);
+ node.style.width = width + "px";
+ node.style.height = height + "px";
+
+ // Three steps are required to remove artefacts for images with
+ // transparent backgrounds (resulting from using DXImageTransform
+ // filters on svg objects), while preserving awareness for browser
+ // events on images:
+ // - Use the fill as usual (like for unrotated images) to handle
+ // events
+ // - specify an imagedata element with the same src as the fill
+ // - style the imagedata element with an AlphaImageLoader filter
+ // with empty src
+ var image = document.getElementById(node.id + "_image");
+ if (!image) {
+ image = this.createNode("olv:imagedata", node.id + "_image");
+ node.appendChild(image);
+ }
+ image.style.width = width + "px";
+ image.style.height = height + "px";
+ image.src = style.externalGraphic;
+ image.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(" +
+ "src='', sizingMethod='scale')";
+
+ var rot = rotation * Math.PI / 180;
+ var sintheta = Math.sin(rot);
+ var costheta = Math.cos(rot);
+
+ // do the rotation on the image
+ var filter =
+ "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
+ ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
+ ",SizingMethod='auto expand')\n";
+
+ // set the opacity (needed for the imagedata)
+ var opacity = style.graphicOpacity || style.fillOpacity;
+ if (opacity && opacity != 1) {
+ filter +=
+ "progid:DXImageTransform.Microsoft.BasicImage(opacity=" +
+ opacity+")\n";
+ }
+ node.style.filter = filter;
+
+ // do the rotation again on a box, so we know the insertion point
+ var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
+ var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
+ imgBox.rotate(style.rotation, centerPoint);
+ var imgBounds = imgBox.getBounds();
+
+ node.style.left = Math.round(
+ parseInt(node.style.left) + imgBounds.left) + "px";
+ node.style.top = Math.round(
+ parseInt(node.style.top) - imgBounds.bottom) + "px";
+ },
+
+ /**
+ * Method: postDraw
+ * Does some node postprocessing to work around browser issues:
+ * - Some versions of Internet Explorer seem to be unable to set fillcolor
+ * and strokecolor to "none" correctly before the fill node is appended
+ * to a visible vml node. This method takes care of that and sets
+ * fillcolor and strokecolor again if needed.
+ * - In some cases, a node won't become visible after being drawn. Setting
+ * style.visibility to "visible" works around that.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ */
+ postDraw: function(node) {
+ node.style.visibility = "visible";
+ var fillColor = node._style.fillColor;
+ var strokeColor = node._style.strokeColor;
+ if (fillColor == "none" &&
+ node.fillcolor != fillColor) {
+ node.fillcolor = fillColor;
+ }
+ if (strokeColor == "none" &&
+ node.strokecolor != strokeColor) {
+ node.strokecolor = strokeColor;
+ }
+ },
+
+
+ /**
+ * Method: setNodeDimension
+ * Get the geometry's bounds, convert it to our vml coordinate system,
+ * then set the node's position, size, and local coordinate system.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ */
+ setNodeDimension: function(node, geometry) {
+
+ var bbox = geometry.getBounds();
+ if(bbox) {
+ var resolution = this.getResolution();
+
+ var scaledBox =
+ new OpenLayers.Bounds(((bbox.left - this.featureDx)/resolution - this.offset.x) | 0,
+ (bbox.bottom/resolution - this.offset.y) | 0,
+ ((bbox.right - this.featureDx)/resolution - this.offset.x) | 0,
+ (bbox.top/resolution - this.offset.y) | 0);
+
+ // Set the internal coordinate system to draw the path
+ node.style.left = scaledBox.left + "px";
+ node.style.top = scaledBox.top + "px";
+ node.style.width = scaledBox.getWidth() + "px";
+ node.style.height = scaledBox.getHeight() + "px";
+
+ node.coordorigin = scaledBox.left + " " + scaledBox.top;
+ node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
+ }
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ *
+ * Returns:
+ * {String} A VML compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style) {
+ var dash = style.strokeDashstyle;
+ switch (dash) {
+ case 'solid':
+ case 'dot':
+ case 'dash':
+ case 'dashdot':
+ case 'longdash':
+ case 'longdashdot':
+ return dash;
+ default:
+ // very basic guessing of dash style patterns
+ var parts = dash.split(/[ ,]/);
+ if (parts.length == 2) {
+ if (1*parts[0] >= 2*parts[1]) {
+ return "longdash";
+ }
+ return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
+ } else if (parts.length == 4) {
+ return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
+ "dashdot";
+ }
+ return "solid";
+ }
+ },
+
+ /**
+ * Method: createNode
+ * Create a new node
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElement(type);
+ if (id) {
+ node.id = id;
+ }
+
+ // IE hack to make elements unselectable, to prevent 'blue flash'
+ // while dragging vectors; #1410
+ node.unselectable = 'on';
+ node.onselectstart = OpenLayers.Function.False;
+
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ * Determine whether a node is of a given type
+ *
+ * Parameters:
+ * node - {DOMElement} An VML element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+
+ //split type
+ var subType = type;
+ var splitIndex = subType.indexOf(":");
+ if (splitIndex != -1) {
+ subType = subType.substr(splitIndex+1);
+ }
+
+ //split nodeName
+ var nodeName = node.nodeName;
+ splitIndex = nodeName.indexOf(":");
+ if (splitIndex != -1) {
+ nodeName = nodeName.substr(splitIndex+1);
+ }
+
+ return (subType == nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ * Create the renderer root
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ return this.nodeFactory(this.container.id + "_vmlRoot", "div");
+ },
+
+ /**
+ * Method: createRoot
+ * Create the main root element
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "olv:group");
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * Render a point
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the point could not be drawn
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * Render a circle.
+ * Size and Center a circle given geometry (x,y center) and radius
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {float}
+ *
+ * Returns:
+ * {DOMElement} or false if the circle could not ne drawn
+ */
+ drawCircle: function(node, geometry, radius) {
+ if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
+ var resolution = this.getResolution();
+
+ node.style.left = ((((geometry.x - this.featureDx) /resolution - this.offset.x) | 0) - radius) + "px";
+ node.style.top = (((geometry.y /resolution - this.offset.y) | 0) - radius) + "px";
+
+ var diameter = radius * 2;
+
+ node.style.width = diameter + "px";
+ node.style.height = diameter + "px";
+ return node;
+ }
+ return false;
+ },
+
+
+ /**
+ * Method: drawLineString
+ * Render a linestring.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLineString: function(node, geometry) {
+ return this.drawLine(node, geometry, false);
+ },
+
+ /**
+ * Method: drawLinearRing
+ * Render a linearring
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLinearRing: function(node, geometry) {
+ return this.drawLine(node, geometry, true);
+ },
+
+ /**
+ * Method: DrawLine
+ * Render a line.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * closeLine - {Boolean} Close the line? (make it a ring?)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawLine: function(node, geometry, closeLine) {
+
+ this.setNodeDimension(node, geometry);
+
+ var resolution = this.getResolution();
+ var numComponents = geometry.components.length;
+ var parts = new Array(numComponents);
+
+ var comp, x, y;
+ for (var i = 0; i < numComponents; i++) {
+ comp = geometry.components[i];
+ x = ((comp.x - this.featureDx)/resolution - this.offset.x) | 0;
+ y = (comp.y/resolution - this.offset.y) | 0;
+ parts[i] = " " + x + "," + y + " l ";
+ }
+ var end = (closeLine) ? " x e" : " e";
+ node.path = "m" + parts.join("") + end;
+ return node;
+ },
+
+ /**
+ * Method: drawPolygon
+ * Render a polygon
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawPolygon: function(node, geometry) {
+ this.setNodeDimension(node, geometry);
+
+ var resolution = this.getResolution();
+
+ var path = [];
+ var j, jj, points, area, first, second, i, ii, comp, pathComp, x, y;
+ for (j=0, jj=geometry.components.length; j<jj; j++) {
+ path.push("m");
+ points = geometry.components[j].components;
+ // we only close paths of interior rings with area
+ area = (j === 0);
+ first = null;
+ second = null;
+ for (i=0, ii=points.length; i<ii; i++) {
+ comp = points[i];
+ x = ((comp.x - this.featureDx) / resolution - this.offset.x) | 0;
+ y = (comp.y / resolution - this.offset.y) | 0;
+ pathComp = " " + x + "," + y;
+ path.push(pathComp);
+ if (i==0) {
+ path.push(" l");
+ }
+ if (!area) {
+ // IE improperly renders sub-paths that have no area.
+ // Instead of checking the area of every ring, we confirm
+ // the ring has at least three distinct points. This does
+ // not catch all non-zero area cases, but it greatly improves
+ // interior ring digitizing and is a minor performance hit
+ // when rendering rings with many points.
+ if (!first) {
+ first = pathComp;
+ } else if (first != pathComp) {
+ if (!second) {
+ second = pathComp;
+ } else if (second != pathComp) {
+ // stop looking
+ area = true;
+ }
+ }
+ }
+ }
+ path.push(area ? " x " : " ");
+ }
+ path.push("e");
+ node.path = path.join("");
+ return node;
+ },
+
+ /**
+ * Method: drawRectangle
+ * Render a rectangle
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ drawRectangle: function(node, geometry) {
+ var resolution = this.getResolution();
+
+ node.style.left = (((geometry.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
+ node.style.top = ((geometry.y/resolution - this.offset.y) | 0) + "px";
+ node.style.width = ((geometry.width/resolution) | 0) + "px";
+ node.style.height = ((geometry.height/resolution) | 0) + "px";
+
+ return node;
+ },
+
+ /**
+ * Method: drawText
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String}
+ * style -
+ * location - {<OpenLayers.Geometry.Point>}
+ */
+ drawText: function(featureId, style, location) {
+ var label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, "olv:rect");
+ var textbox = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_textbox", "olv:textbox");
+
+ var resolution = this.getResolution();
+ label.style.left = (((location.x - this.featureDx)/resolution - this.offset.x) | 0) + "px";
+ label.style.top = ((location.y/resolution - this.offset.y) | 0) + "px";
+ label.style.flip = "y";
+
+ textbox.innerText = style.label;
+
+ if (style.cursor != "inherit" && style.cursor != null) {
+ textbox.style.cursor = style.cursor;
+ }
+ if (style.fontColor) {
+ textbox.style.color = style.fontColor;
+ }
+ if (style.fontOpacity) {
+ textbox.style.filter = 'alpha(opacity=' + (style.fontOpacity * 100) + ')';
+ }
+ if (style.fontFamily) {
+ textbox.style.fontFamily = style.fontFamily;
+ }
+ if (style.fontSize) {
+ textbox.style.fontSize = style.fontSize;
+ }
+ if (style.fontWeight) {
+ textbox.style.fontWeight = style.fontWeight;
+ }
+ if (style.fontStyle) {
+ textbox.style.fontStyle = style.fontStyle;
+ }
+ if(style.labelSelect === true) {
+ label._featureId = featureId;
+ textbox._featureId = featureId;
+ textbox._geometry = location;
+ textbox._geometryClass = location.CLASS_NAME;
+ }
+ textbox.style.whiteSpace = "nowrap";
+ // fun with IE: IE7 in standards compliant mode does not display any
+ // text with a left inset of 0. So we set this to 1px and subtract one
+ // pixel later when we set label.style.left
+ textbox.inset = "1px,0px,0px,0px";
+
+ if(!label.parentNode) {
+ label.appendChild(textbox);
+ this.textRoot.appendChild(label);
+ }
+
+ var align = style.labelAlign || "cm";
+ if (align.length == 1) {
+ align += "m";
+ }
+ var xshift = textbox.clientWidth *
+ (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(0,1)]);
+ var yshift = textbox.clientHeight *
+ (OpenLayers.Renderer.VML.LABEL_SHIFT[align.substr(1,1)]);
+ label.style.left = parseInt(label.style.left)-xshift-1+"px";
+ label.style.top = parseInt(label.style.top)+yshift+"px";
+
+ },
+
+ /**
+ * Method: moveRoot
+ * moves this renderer's root to a different renderer.
+ *
+ * Parameters:
+ * renderer - {<OpenLayers.Renderer>} target renderer for the moved root
+ * root - {DOMElement} optional root node. To be used when this renderer
+ * holds roots from multiple layers to tell this method which one to
+ * detach
+ *
+ * Returns:
+ * {Boolean} true if successful, false otherwise
+ */
+ moveRoot: function(renderer) {
+ var layer = this.map.getLayer(renderer.container.id);
+ if(layer instanceof OpenLayers.Layer.Vector.RootContainer) {
+ layer = this.map.getLayer(this.container.id);
+ }
+ layer && layer.renderer.clear();
+ OpenLayers.Renderer.Elements.prototype.moveRoot.apply(this, arguments);
+ layer && layer.redraw();
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
+ */
+ importSymbol: function (graphicName) {
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the cache
+ var cache = this.symbolCache[id];
+ if (cache) {
+ return cache;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var pathitems = ["m"];
+ for (var i=0; i<symbol.length; i=i+2) {
+ var x = symbol[i];
+ var y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+
+ pathitems.push(x);
+ pathitems.push(y);
+ if (i == 0) {
+ pathitems.push("l");
+ }
+ }
+ pathitems.push("x e");
+ var path = pathitems.join(" ");
+
+ var diff = (symbolExtent.getWidth() - symbolExtent.getHeight()) / 2;
+ if(diff > 0) {
+ symbolExtent.bottom = symbolExtent.bottom - diff;
+ symbolExtent.top = symbolExtent.top + diff;
+ } else {
+ symbolExtent.left = symbolExtent.left + diff;
+ symbolExtent.right = symbolExtent.right - diff;
+ }
+
+ cache = {
+ path: path,
+ size: symbolExtent.getWidth(), // equals getHeight() now
+ left: symbolExtent.left,
+ bottom: symbolExtent.bottom
+ };
+ this.symbolCache[id] = cache;
+
+ return cache;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.VML"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.VML.LABEL_SHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.VML.LABEL_SHIFT = {
+ "l": 0,
+ "c": .5,
+ "r": 1,
+ "t": 0,
+ "m": .5,
+ "b": 1
+};
diff --git a/misc/openlayers/lib/OpenLayers/Request.js b/misc/openlayers/lib/OpenLayers/Request.js
new file mode 100644
index 0000000..2b28dfc
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Request.js
@@ -0,0 +1,429 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * TODO: deprecate me
+ * Use OpenLayers.Request.proxy instead.
+ */
+OpenLayers.ProxyHost = "";
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ * with XMLHttpRequests. These methods work with a cross-browser
+ * W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
+ */
+if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request/XMLHttpRequest.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+}
+OpenLayers.Util.extend(OpenLayers.Request, {
+
+ /**
+ * Constant: DEFAULT_CONFIG
+ * {Object} Default configuration for all requests.
+ */
+ DEFAULT_CONFIG: {
+ method: "GET",
+ url: window.location.href,
+ async: true,
+ user: undefined,
+ password: undefined,
+ params: null,
+ proxy: OpenLayers.ProxyHost,
+ headers: {},
+ data: null,
+ callback: function() {},
+ success: null,
+ failure: null,
+ scope: null
+ },
+
+ /**
+ * Constant: URL_SPLIT_REGEX
+ */
+ URL_SPLIT_REGEX: /([^:]*:)\/\/([^:]*:?[^@]*@)?([^:\/\?]*):?([^\/\?]*)/,
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the {<OpenLayers.Request>} object.
+ *
+ * All event listeners will receive an event object with three properties:
+ * request - {<OpenLayers.Request.XMLHttpRequest>} The request object.
+ * config - {Object} The config object sent to the specific request method.
+ * requestUrl - {String} The request url.
+ *
+ * Supported event types:
+ * complete - Triggered when we have a response from the request, if a
+ * listener returns false, no further response processing will take
+ * place.
+ * success - Triggered when the HTTP response has a success code (200-299).
+ * failure - Triggered when the HTTP response does not have a success code.
+ */
+ events: new OpenLayers.Events(this),
+
+ /**
+ * Method: makeSameOrigin
+ * Using the specified proxy, returns a same origin url of the provided url.
+ *
+ * Parameters:
+ * url - {String} An arbitrary url
+ * proxy {String|Function} The proxy to use to make the provided url a
+ * same origin url.
+ *
+ * Returns
+ * {String} the same origin url. If no proxy is provided, the returned url
+ * will be the same as the provided url.
+ */
+ makeSameOrigin: function(url, proxy) {
+ var sameOrigin = url.indexOf("http") !== 0;
+ var urlParts = !sameOrigin && url.match(this.URL_SPLIT_REGEX);
+ if (urlParts) {
+ var location = window.location;
+ sameOrigin =
+ urlParts[1] == location.protocol &&
+ urlParts[3] == location.hostname;
+ var uPort = urlParts[4], lPort = location.port;
+ if (uPort != 80 && uPort != "" || lPort != "80" && lPort != "") {
+ sameOrigin = sameOrigin && uPort == lPort;
+ }
+ }
+ if (!sameOrigin) {
+ if (proxy) {
+ if (typeof proxy == "function") {
+ url = proxy(url);
+ } else {
+ url = proxy + encodeURIComponent(url);
+ }
+ }
+ }
+ return url;
+ },
+
+ /**
+ * APIMethod: issue
+ * Create a new XMLHttpRequest object, open it, set any headers, bind
+ * a callback to done state, and send any data. It is recommended that
+ * you use one <GET>, <POST>, <PUT>, <DELETE>, <OPTIONS>, or <HEAD>.
+ * This method is only documented to provide detail on the configuration
+ * options available to all request methods.
+ *
+ * Parameters:
+ * config - {Object} Object containing properties for configuring the
+ * request. Allowed configuration properties are described below.
+ * This object is modified and should not be reused.
+ *
+ * Allowed config properties:
+ * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
+ * OPTIONS. Default is GET.
+ * url - {String} URL for the request.
+ * async - {Boolean} Open an asynchronous request. Default is true.
+ * user - {String} User for relevant authentication scheme. Set
+ * to null to clear current user.
+ * password - {String} Password for relevant authentication scheme.
+ * Set to null to clear current password.
+ * proxy - {String} Optional proxy. Defaults to
+ * <OpenLayers.ProxyHost>.
+ * params - {Object} Any key:value pairs to be appended to the
+ * url as a query string. Assumes url doesn't already include a query
+ * string or hash. Typically, this is only appropriate for <GET>
+ * requests where the query string will be appended to the url.
+ * Parameter values that are arrays will be
+ * concatenated with a comma (note that this goes against form-encoding)
+ * as is done with <OpenLayers.Util.getParameterString>.
+ * headers - {Object} Object with header:value pairs to be set on
+ * the request.
+ * data - {String | Document} Optional data to send with the request.
+ * Typically, this is only used with <POST> and <PUT> requests.
+ * Make sure to provide the appropriate "Content-Type" header for your
+ * data. For <POST> and <PUT> requests, the content type defaults to
+ * "application-xml". If your data is a different content type, or
+ * if you are using a different HTTP method, set the "Content-Type"
+ * header to match your data type.
+ * callback - {Function} Function to call when request is done.
+ * To determine if the request failed, check request.status (200
+ * indicates success).
+ * success - {Function} Optional function to call if request status is in
+ * the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * failure - {Function} Optional function to call if request status is not
+ * in the 200s. This will be called in addition to callback above and
+ * would typically only be used as an alternative.
+ * scope - {Object} If callback is a public method on some object,
+ * set the scope to that object.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object. To abort the request before a response
+ * is received, call abort() on the request object.
+ */
+ issue: function(config) {
+ // apply default config - proxy host may have changed
+ var defaultConfig = OpenLayers.Util.extend(
+ this.DEFAULT_CONFIG,
+ {proxy: OpenLayers.ProxyHost}
+ );
+ config = config || {};
+ config.headers = config.headers || {};
+ config = OpenLayers.Util.applyDefaults(config, defaultConfig);
+ config.headers = OpenLayers.Util.applyDefaults(config.headers, defaultConfig.headers);
+ // Always set the "X-Requested-With" header to signal that this request
+ // was issued through the XHR-object. Since header keys are case
+ // insensitive and we want to allow overriding of the "X-Requested-With"
+ // header through the user we cannot use applyDefaults, but have to
+ // check manually whether we were called with a "X-Requested-With"
+ // header.
+ var customRequestedWithHeader = false,
+ headerKey;
+ for(headerKey in config.headers) {
+ if (config.headers.hasOwnProperty( headerKey )) {
+ if (headerKey.toLowerCase() === 'x-requested-with') {
+ customRequestedWithHeader = true;
+ }
+ }
+ }
+ if (customRequestedWithHeader === false) {
+ // we did not have a custom "X-Requested-With" header
+ config.headers['X-Requested-With'] = 'XMLHttpRequest';
+ }
+
+ // create request, open, and set headers
+ var request = new OpenLayers.Request.XMLHttpRequest();
+ var url = OpenLayers.Util.urlAppend(config.url,
+ OpenLayers.Util.getParameterString(config.params || {}));
+ url = OpenLayers.Request.makeSameOrigin(url, config.proxy);
+ request.open(
+ config.method, url, config.async, config.user, config.password
+ );
+ for(var header in config.headers) {
+ request.setRequestHeader(header, config.headers[header]);
+ }
+
+ var events = this.events;
+
+ // we want to execute runCallbacks with "this" as the
+ // execution scope
+ var self = this;
+
+ request.onreadystatechange = function() {
+ if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
+ var proceed = events.triggerEvent(
+ "complete",
+ {request: request, config: config, requestUrl: url}
+ );
+ if(proceed !== false) {
+ self.runCallbacks(
+ {request: request, config: config, requestUrl: url}
+ );
+ }
+ }
+ };
+
+ // send request (optionally with data) and return
+ // call in a timeout for asynchronous requests so the return is
+ // available before readyState == 4 for cached docs
+ if(config.async === false) {
+ request.send(config.data);
+ } else {
+ window.setTimeout(function(){
+ if (request.readyState !== 0) { // W3C: 0-UNSENT
+ request.send(config.data);
+ }
+ }, 0);
+ }
+ return request;
+ },
+
+ /**
+ * Method: runCallbacks
+ * Calls the complete, success and failure callbacks. Application
+ * can listen to the "complete" event, have the listener
+ * display a confirm window and always return false, and
+ * execute OpenLayers.Request.runCallbacks if the user
+ * hits "yes" in the confirm window.
+ *
+ * Parameters:
+ * options - {Object} Hash containing request, config and requestUrl keys
+ */
+ runCallbacks: function(options) {
+ var request = options.request;
+ var config = options.config;
+
+ // bind callbacks to readyState 4 (done)
+ var complete = (config.scope) ?
+ OpenLayers.Function.bind(config.callback, config.scope) :
+ config.callback;
+
+ // optional success callback
+ var success;
+ if(config.success) {
+ success = (config.scope) ?
+ OpenLayers.Function.bind(config.success, config.scope) :
+ config.success;
+ }
+
+ // optional failure callback
+ var failure;
+ if(config.failure) {
+ failure = (config.scope) ?
+ OpenLayers.Function.bind(config.failure, config.scope) :
+ config.failure;
+ }
+
+ if (OpenLayers.Util.createUrlObject(config.url).protocol == "file:" &&
+ request.responseText) {
+ request.status = 200;
+ }
+ complete(request);
+
+ if (!request.status || (request.status >= 200 && request.status < 300)) {
+ this.events.triggerEvent("success", options);
+ if(success) {
+ success(request);
+ }
+ }
+ if(request.status && (request.status < 200 || request.status >= 300)) {
+ this.events.triggerEvent("failure", options);
+ if(failure) {
+ failure(request);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: GET
+ * Send an HTTP GET request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to GET.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ GET: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "GET"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: POST
+ * Send a POST request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to POST and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ POST: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "POST"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: PUT
+ * Send an HTTP PUT request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to PUT and "Content-Type" header set to "application/xml".
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties. The
+ * default "Content-Type" header will be set to "application-xml" if
+ * none is provided. This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ PUT: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "PUT"});
+ // set content type to application/xml if it isn't already set
+ config.headers = config.headers ? config.headers : {};
+ if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+ config.headers["Content-Type"] = "application/xml";
+ }
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: DELETE
+ * Send an HTTP DELETE request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to DELETE.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ DELETE: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "DELETE"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: HEAD
+ * Send an HTTP HEAD request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to HEAD.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ HEAD: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "HEAD"});
+ return OpenLayers.Request.issue(config);
+ },
+
+ /**
+ * APIMethod: OPTIONS
+ * Send an HTTP OPTIONS request. Additional configuration properties are
+ * documented in the <issue> method, with the method property set
+ * to OPTIONS.
+ *
+ * Parameters:
+ * config - {Object} Object with properties for configuring the request.
+ * See the <issue> method for documentation of allowed properties.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {XMLHttpRequest} Request object.
+ */
+ OPTIONS: function(config) {
+ config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+ return OpenLayers.Request.issue(config);
+ }
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Request/XMLHttpRequest.js b/misc/openlayers/lib/OpenLayers/Request/XMLHttpRequest.js
new file mode 100644
index 0000000..21200a6
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Request/XMLHttpRequest.js
@@ -0,0 +1,458 @@
+// XMLHttpRequest.js Copyright (C) 2010 Sergey Ilinsky (http://www.ilinsky.com)
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+ // Save reference to earlier defined object implementation (if any)
+ var oXMLHttpRequest = window.XMLHttpRequest;
+
+ // Define on browser type
+ var bGecko = !!window.controllers,
+ bIE = window.document.all && !window.opera,
+ bIE7 = bIE && window.navigator.userAgent.match(/MSIE 7.0/);
+
+ // Enables "XMLHttpRequest()" call next to "new XMLHttpReques()"
+ function fXMLHttpRequest() {
+ this._object = oXMLHttpRequest && !bIE7 ? new oXMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
+ this._listeners = [];
+ };
+
+ // Constructor
+ function cXMLHttpRequest() {
+ return new fXMLHttpRequest;
+ };
+ cXMLHttpRequest.prototype = fXMLHttpRequest.prototype;
+
+ // BUGFIX: Firefox with Firebug installed would break pages if not executed
+ if (bGecko && oXMLHttpRequest.wrapped)
+ cXMLHttpRequest.wrapped = oXMLHttpRequest.wrapped;
+
+ // Constants
+ cXMLHttpRequest.UNSENT = 0;
+ cXMLHttpRequest.OPENED = 1;
+ cXMLHttpRequest.HEADERS_RECEIVED = 2;
+ cXMLHttpRequest.LOADING = 3;
+ cXMLHttpRequest.DONE = 4;
+
+ // Public Properties
+ cXMLHttpRequest.prototype.readyState = cXMLHttpRequest.UNSENT;
+ cXMLHttpRequest.prototype.responseText = '';
+ cXMLHttpRequest.prototype.responseXML = null;
+ cXMLHttpRequest.prototype.status = 0;
+ cXMLHttpRequest.prototype.statusText = '';
+
+ // Priority proposal
+ cXMLHttpRequest.prototype.priority = "NORMAL";
+
+ // Instance-level Events Handlers
+ cXMLHttpRequest.prototype.onreadystatechange = null;
+
+ // Class-level Events Handlers
+ cXMLHttpRequest.onreadystatechange = null;
+ cXMLHttpRequest.onopen = null;
+ cXMLHttpRequest.onsend = null;
+ cXMLHttpRequest.onabort = null;
+
+ // Public Methods
+ cXMLHttpRequest.prototype.open = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+ // Delete headers, required when object is reused
+ delete this._headers;
+
+ // When bAsync parameter value is omitted, use true as default
+ if (arguments.length < 3)
+ bAsync = true;
+
+ // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+ this._async = bAsync;
+
+ // Set the onreadystatechange handler
+ var oRequest = this,
+ nState = this.readyState,
+ fOnUnload;
+
+ // BUGFIX: IE - memory leak on page unload (inter-page leak)
+ if (bIE && bAsync) {
+ fOnUnload = function() {
+ if (nState != cXMLHttpRequest.DONE) {
+ fCleanTransport(oRequest);
+ // Safe to abort here since onreadystatechange handler removed
+ oRequest.abort();
+ }
+ };
+ window.attachEvent("onunload", fOnUnload);
+ }
+
+ // Add method sniffer
+ if (cXMLHttpRequest.onopen)
+ cXMLHttpRequest.onopen.apply(this, arguments);
+
+ if (arguments.length > 4)
+ this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ if (arguments.length > 3)
+ this._object.open(sMethod, sUrl, bAsync, sUser);
+ else
+ this._object.open(sMethod, sUrl, bAsync);
+
+ this.readyState = cXMLHttpRequest.OPENED;
+ fReadyStateChange(this);
+
+ this._object.onreadystatechange = function() {
+ if (bGecko && !bAsync)
+ return;
+
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ // BUGFIX: Firefox fires unnecessary DONE when aborting
+ if (oRequest._aborted) {
+ // Reset readyState to UNSENT
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return now
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Free up queue
+ delete oRequest._data;
+/* if (bAsync)
+ fQueue_remove(oRequest);*/
+ //
+ fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+ // BUGFIX: IE - cache issue
+ if (!oRequest._object.getResponseHeader("Date")) {
+ // Save object to cache
+ oRequest._cached = oRequest._object;
+
+ // Instantiate a new transport object
+ cXMLHttpRequest.call(oRequest);
+
+ // Re-send request
+ if (sUser) {
+ if (sPassword)
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync, sUser);
+ }
+ else
+ oRequest._object.open(sMethod, sUrl, bAsync);
+ oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+ // Copy headers set
+ if (oRequest._headers)
+ for (var sHeader in oRequest._headers)
+ if (typeof oRequest._headers[sHeader] == "string") // Some frameworks prototype objects with functions
+ oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+ oRequest._object.onreadystatechange = function() {
+ // Synchronize state
+ oRequest.readyState = oRequest._object.readyState;
+
+ if (oRequest._aborted) {
+ //
+ oRequest.readyState = cXMLHttpRequest.UNSENT;
+
+ // Return
+ return;
+ }
+
+ if (oRequest.readyState == cXMLHttpRequest.DONE) {
+ // Clean Object
+ fCleanTransport(oRequest);
+
+ // get cached request
+ if (oRequest.status == 304)
+ oRequest._object = oRequest._cached;
+
+ //
+ delete oRequest._cached;
+
+ //
+ fSynchronizeValues(oRequest);
+
+ //
+ fReadyStateChange(oRequest);
+
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+ };
+ oRequest._object.send(null);
+
+ // Return now - wait until re-sent request is finished
+ return;
+ };
+*/
+ // BUGFIX: IE - memory leak in interrupted
+ if (bIE && bAsync)
+ window.detachEvent("onunload", fOnUnload);
+ }
+
+ // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+ if (nState != oRequest.readyState)
+ fReadyStateChange(oRequest);
+
+ nState = oRequest.readyState;
+ }
+ };
+ function fXMLHttpRequest_send(oRequest) {
+ oRequest._object.send(oRequest._data);
+
+ // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+ if (bGecko && !oRequest._async) {
+ oRequest.readyState = cXMLHttpRequest.OPENED;
+
+ // Synchronize state
+ fSynchronizeValues(oRequest);
+
+ // Simulate missing states
+ while (oRequest.readyState < cXMLHttpRequest.DONE) {
+ oRequest.readyState++;
+ fReadyStateChange(oRequest);
+ // Check if we are aborted
+ if (oRequest._aborted)
+ return;
+ }
+ }
+ };
+ cXMLHttpRequest.prototype.send = function(vData) {
+ // Add method sniffer
+ if (cXMLHttpRequest.onsend)
+ cXMLHttpRequest.onsend.apply(this, arguments);
+
+ if (!arguments.length)
+ vData = null;
+
+ // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+ // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+ // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+ if (vData && vData.nodeType) {
+ vData = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+ if (!this._headers["Content-Type"])
+ this._object.setRequestHeader("Content-Type", "application/xml");
+ }
+
+ this._data = vData;
+/*
+ // Add to queue
+ if (this._async)
+ fQueue_add(this);
+ else*/
+ fXMLHttpRequest_send(this);
+ };
+ cXMLHttpRequest.prototype.abort = function() {
+ // Add method sniffer
+ if (cXMLHttpRequest.onabort)
+ cXMLHttpRequest.onabort.apply(this, arguments);
+
+ // BUGFIX: Gecko - unnecessary DONE when aborting
+ if (this.readyState > cXMLHttpRequest.UNSENT)
+ this._aborted = true;
+
+ this._object.abort();
+
+ // BUGFIX: IE - memory leak
+ fCleanTransport(this);
+
+ this.readyState = cXMLHttpRequest.UNSENT;
+
+ delete this._data;
+/* if (this._async)
+ fQueue_remove(this);*/
+ };
+ cXMLHttpRequest.prototype.getAllResponseHeaders = function() {
+ return this._object.getAllResponseHeaders();
+ };
+ cXMLHttpRequest.prototype.getResponseHeader = function(sName) {
+ return this._object.getResponseHeader(sName);
+ };
+ cXMLHttpRequest.prototype.setRequestHeader = function(sName, sValue) {
+ // BUGFIX: IE - cache issue
+ if (!this._headers)
+ this._headers = {};
+ this._headers[sName] = sValue;
+
+ return this._object.setRequestHeader(sName, sValue);
+ };
+
+ // EventTarget interface implementation
+ cXMLHttpRequest.prototype.addEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ return;
+ // Add listener
+ this._listeners.push([sName, fHandler, bUseCapture]);
+ };
+
+ cXMLHttpRequest.prototype.removeEventListener = function(sName, fHandler, bUseCapture) {
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == sName && oListener[1] == fHandler && oListener[2] == bUseCapture)
+ break;
+ // Remove listener
+ if (oListener)
+ this._listeners.splice(nIndex, 1);
+ };
+
+ cXMLHttpRequest.prototype.dispatchEvent = function(oEvent) {
+ var oEventPseudo = {
+ 'type': oEvent.type,
+ 'target': this,
+ 'currentTarget':this,
+ 'eventPhase': 2,
+ 'bubbles': oEvent.bubbles,
+ 'cancelable': oEvent.cancelable,
+ 'timeStamp': oEvent.timeStamp,
+ 'stopPropagation': function() {}, // There is no flow
+ 'preventDefault': function() {}, // There is no default action
+ 'initEvent': function() {} // Original event object should be initialized
+ };
+
+ // Execute onreadystatechange
+ if (oEventPseudo.type == "readystatechange" && this.onreadystatechange)
+ (this.onreadystatechange.handleEvent || this.onreadystatechange).apply(this, [oEventPseudo]);
+
+ // Execute listeners
+ for (var nIndex = 0, oListener; oListener = this._listeners[nIndex]; nIndex++)
+ if (oListener[0] == oEventPseudo.type && !oListener[2])
+ (oListener[1].handleEvent || oListener[1]).apply(this, [oEventPseudo]);
+ };
+
+ //
+ cXMLHttpRequest.prototype.toString = function() {
+ return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+ };
+
+ cXMLHttpRequest.toString = function() {
+ return '[' + "XMLHttpRequest" + ']';
+ };
+
+ // Helper function
+ function fReadyStateChange(oRequest) {
+ // Sniffing code
+ if (cXMLHttpRequest.onreadystatechange)
+ cXMLHttpRequest.onreadystatechange.apply(oRequest);
+
+ // Fake event
+ oRequest.dispatchEvent({
+ 'type': "readystatechange",
+ 'bubbles': false,
+ 'cancelable': false,
+ 'timeStamp': new Date + 0
+ });
+ };
+
+ function fGetDocument(oRequest) {
+ var oDocument = oRequest.responseXML,
+ sResponse = oRequest.responseText;
+ // Try parsing responseText
+ if (bIE && sResponse && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+ oDocument = new window.ActiveXObject("Microsoft.XMLDOM");
+ oDocument.async = false;
+ oDocument.validateOnParse = false;
+ oDocument.loadXML(sResponse);
+ }
+ // Check if there is no error in document
+ if (oDocument)
+ if ((bIE && oDocument.parseError != 0) || !oDocument.documentElement || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+ return null;
+ return oDocument;
+ };
+
+ function fSynchronizeValues(oRequest) {
+ try { oRequest.responseText = oRequest._object.responseText; } catch (e) {}
+ try { oRequest.responseXML = fGetDocument(oRequest._object); } catch (e) {}
+ try { oRequest.status = oRequest._object.status; } catch (e) {}
+ try { oRequest.statusText = oRequest._object.statusText; } catch (e) {}
+ };
+
+ function fCleanTransport(oRequest) {
+ // BUGFIX: IE - memory leak (on-page leak)
+ oRequest._object.onreadystatechange = new window.Function;
+ };
+/*
+ // Queue manager
+ var oQueuePending = {"CRITICAL":[],"HIGH":[],"NORMAL":[],"LOW":[],"LOWEST":[]},
+ aQueueRunning = [];
+ function fQueue_add(oRequest) {
+ oQueuePending[oRequest.priority in oQueuePending ? oRequest.priority : "NORMAL"].push(oRequest);
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_remove(oRequest) {
+ for (var nIndex = 0, bFound = false; nIndex < aQueueRunning.length; nIndex++)
+ if (bFound)
+ aQueueRunning[nIndex - 1] = aQueueRunning[nIndex];
+ else
+ if (aQueueRunning[nIndex] == oRequest)
+ bFound = true;
+ if (bFound)
+ aQueueRunning.length--;
+ //
+ setTimeout(fQueue_process);
+ };
+
+ function fQueue_process() {
+ if (aQueueRunning.length < 6) {
+ for (var sPriority in oQueuePending) {
+ if (oQueuePending[sPriority].length) {
+ var oRequest = oQueuePending[sPriority][0];
+ oQueuePending[sPriority] = oQueuePending[sPriority].slice(1);
+ //
+ aQueueRunning.push(oRequest);
+ // Send request
+ fXMLHttpRequest_send(oRequest);
+ break;
+ }
+ }
+ }
+ };
+*/
+ // Internet Explorer 5.0 (missing apply)
+ if (!window.Function.prototype.apply) {
+ window.Function.prototype.apply = function(oRequest, oArguments) {
+ if (!oArguments)
+ oArguments = [];
+ oRequest.__func = this;
+ oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+ delete oRequest.__func;
+ };
+ };
+
+ // Register new object with window
+ /**
+ * Class: OpenLayers.Request.XMLHttpRequest
+ * Standard-compliant (W3C) cross-browser implementation of the
+ * XMLHttpRequest object. From
+ * http://code.google.com/p/xmlhttprequest/.
+ */
+ if (!OpenLayers.Request) {
+ /**
+ * This allows for OpenLayers/Request.js to be included
+ * before or after this script.
+ */
+ OpenLayers.Request = {};
+ }
+ OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
diff --git a/misc/openlayers/lib/OpenLayers/Rule.js b/misc/openlayers/lib/OpenLayers/Rule.js
new file mode 100644
index 0000000..dbf5e68
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Rule.js
@@ -0,0 +1,236 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Rule
+ * This class represents an SLD Rule, as being used for rule-based SLD styling.
+ */
+OpenLayers.Rule = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String} name of this rule
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this rule (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this rule (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * Property: context
+ * {Object} An optional object with properties that the rule should be
+ * evaluated against. If no context is specified, feature.attributes will
+ * be used.
+ */
+ context: null,
+
+ /**
+ * Property: filter
+ * {<OpenLayers.Filter>} Optional filter for the rule.
+ */
+ filter: null,
+
+ /**
+ * Property: elseFilter
+ * {Boolean} Determines whether this rule is only to be applied only if
+ * no other rules match (ElseFilter according to the SLD specification).
+ * Default is false. For instances of OpenLayers.Rule, if elseFilter is
+ * false, the rule will always apply. For subclasses, the else property is
+ * ignored.
+ */
+ elseFilter: false,
+
+ /**
+ * Property: symbolizer
+ * {Object} Symbolizer or hash of symbolizers for this rule. If hash of
+ * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The
+ * latter if useful if it is required to style e.g. vertices of a line
+ * with a point symbolizer. Note, however, that this is not implemented
+ * yet in OpenLayers, but it is the way how symbolizers are defined in
+ * SLD.
+ */
+ symbolizer: null,
+
+ /**
+ * Property: symbolizers
+ * {Array} Collection of symbolizers associated with this rule. If
+ * provided at construction, the symbolizers array has precedence
+ * over the deprecated symbolizer property. Note that multiple
+ * symbolizers are not currently supported by the vector renderers.
+ * Rules with multiple symbolizers are currently only useful for
+ * maintaining elements in an SLD document.
+ */
+ symbolizers: null,
+
+ /**
+ * APIProperty: minScaleDenominator
+ * {Number} or {String} minimum scale at which to draw the feature.
+ * In the case of a String, this can be a combination of text and
+ * propertyNames in the form "literal ${propertyName}"
+ */
+ minScaleDenominator: null,
+
+ /**
+ * APIProperty: maxScaleDenominator
+ * {Number} or {String} maximum scale at which to draw the feature.
+ * In the case of a String, this can be a combination of text and
+ * propertyNames in the form "literal ${propertyName}"
+ */
+ maxScaleDenominator: null,
+
+ /**
+ * Constructor: OpenLayers.Rule
+ * Creates a Rule.
+ *
+ * Parameters:
+ * options - {Object} An optional object with properties to set on the
+ * rule
+ *
+ * Returns:
+ * {<OpenLayers.Rule>}
+ */
+ initialize: function(options) {
+ this.symbolizer = {};
+ OpenLayers.Util.extend(this, options);
+ if (this.symbolizers) {
+ delete this.symbolizer;
+ }
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i in this.symbolizer) {
+ this.symbolizer[i] = null;
+ }
+ this.symbolizer = null;
+ delete this.symbolizers;
+ },
+
+ /**
+ * APIMethod: evaluate
+ * evaluates this rule for a specific feature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+ *
+ * Returns:
+ * {Boolean} true if the rule applies, false if it does not.
+ * This rule is the default rule and always returns true.
+ */
+ evaluate: function(feature) {
+ var context = this.getContext(feature);
+ var applies = true;
+
+ if (this.minScaleDenominator || this.maxScaleDenominator) {
+ var scale = feature.layer.map.getScale();
+ }
+
+ // check if within minScale/maxScale bounds
+ if (this.minScaleDenominator) {
+ applies = scale >= OpenLayers.Style.createLiteral(
+ this.minScaleDenominator, context);
+ }
+ if (applies && this.maxScaleDenominator) {
+ applies = scale < OpenLayers.Style.createLiteral(
+ this.maxScaleDenominator, context);
+ }
+
+ // check if optional filter applies
+ if(applies && this.filter) {
+ // feature id filters get the feature, others get the context
+ if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
+ applies = this.filter.evaluate(feature);
+ } else {
+ applies = this.filter.evaluate(context);
+ }
+ }
+
+ return applies;
+ },
+
+ /**
+ * Method: getContext
+ * Gets the context for evaluating this rule
+ *
+ * Paramters:
+ * feature - {<OpenLayers.Feature>} feature to take the context from if
+ * none is specified.
+ */
+ getContext: function(feature) {
+ var context = this.context;
+ if (!context) {
+ context = feature.attributes || feature.data;
+ }
+ if (typeof this.context == "function") {
+ context = this.context(feature);
+ }
+ return context;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this rule.
+ *
+ * Returns:
+ * {<OpenLayers.Rule>} Clone of this rule.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ if (this.symbolizers) {
+ // clone symbolizers
+ var len = this.symbolizers.length;
+ options.symbolizers = new Array(len);
+ for (var i=0; i<len; ++i) {
+ options.symbolizers[i] = this.symbolizers[i].clone();
+ }
+ } else {
+ // clone symbolizer
+ options.symbolizer = {};
+ var value, type;
+ for(var key in this.symbolizer) {
+ value = this.symbolizer[key];
+ type = typeof value;
+ if(type === "object") {
+ options.symbolizer[key] = OpenLayers.Util.extend({}, value);
+ } else if(type === "string") {
+ options.symbolizer[key] = value;
+ }
+ }
+ }
+ // clone filter
+ options.filter = this.filter && this.filter.clone();
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ return new OpenLayers.Rule(options);
+ },
+
+ CLASS_NAME: "OpenLayers.Rule"
+}); \ No newline at end of file
diff --git a/misc/openlayers/lib/OpenLayers/SingleFile.js b/misc/openlayers/lib/OpenLayers/SingleFile.js
new file mode 100644
index 0000000..1f210c9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/SingleFile.js
@@ -0,0 +1,78 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+var OpenLayers = {
+ /**
+ * Constant: VERSION_NUMBER
+ */
+ VERSION_NUMBER: "Release 2.13.1",
+
+ /**
+ * Constant: singleFile
+ * TODO: remove this in 3.0 when we stop supporting build profiles that
+ * include OpenLayers.js
+ */
+ singleFile: true,
+
+ /**
+ * Method: _getScriptLocation
+ * Return the path to this script. This is also implemented in
+ * OpenLayers.js
+ *
+ * Returns:
+ * {String} Path to this script
+ */
+ _getScriptLocation: (function() {
+ var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
+ s = document.getElementsByTagName('script'),
+ src, m, l = "";
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+ return (function() { return l; });
+ })(),
+
+ /**
+ * Property: ImgPath
+ * {String} Set this to the path where control images are stored, a path
+ * given here must end with a slash. If set to '' (which is the default)
+ * OpenLayers will use its script location + "img/".
+ *
+ * You will need to set this property when you have a singlefile build of
+ * OpenLayers that either is not named "OpenLayers.js" or if you move
+ * the file in a way such that the image directory cannot be derived from
+ * the script location.
+ *
+ * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+ * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+ * way of including OpenLayers in your HTML would be:
+ *
+ * (code)
+ * <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+ * <script type="text/javascript">
+ * // tell OpenLayers where the control images are
+ * // remember the trailing slash
+ * OpenLayers.ImgPath = "/resources/external/images/ol/";
+ * </script>
+ * (end code)
+ *
+ * Please remember that when your OpenLayers script is not named
+ * "OpenLayers.js" you will have to make sure that the default theme is
+ * loaded into the page by including an appropriate <link>-tag,
+ * e.g.:
+ *
+ * (code)
+ * <link rel="stylesheet" href="/path/to/default/style.css" type="text/css">
+ * (end code)
+ */
+ ImgPath : ''
+};
diff --git a/misc/openlayers/lib/OpenLayers/Spherical.js b/misc/openlayers/lib/OpenLayers/Spherical.js
new file mode 100644
index 0000000..74e14a8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Spherical.js
@@ -0,0 +1,67 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Namespace: Spherical
+ * The OpenLayers.Spherical namespace includes utility functions for
+ * calculations on the basis of a spherical earth (ignoring ellipsoidal
+ * effects), which is accurate enough for most purposes.
+ *
+ * Relevant links:
+ * * http://www.movable-type.co.uk/scripts/latlong.html
+ * * http://code.google.com/apis/maps/documentation/javascript/reference.html#spherical
+ */
+
+OpenLayers.Spherical = OpenLayers.Spherical || {};
+
+OpenLayers.Spherical.DEFAULT_RADIUS = 6378137;
+
+/**
+ * APIFunction: computeDistanceBetween
+ * Computes the distance between two LonLats.
+ *
+ * Parameters:
+ * from - {<OpenLayers.LonLat>} or {Object} Starting point. A LonLat or
+ * a JavaScript literal with lon lat properties.
+ * to - {<OpenLayers.LonLat>} or {Object} Ending point. A LonLat or a
+ * JavaScript literal with lon lat properties.
+ * radius - {Float} The radius. Optional. Defaults to 6378137 meters.
+ *
+ * Returns:
+ * {Float} The distance in meters.
+ */
+OpenLayers.Spherical.computeDistanceBetween = function(from, to, radius) {
+ var R = radius || OpenLayers.Spherical.DEFAULT_RADIUS;
+ var sinHalfDeltaLon = Math.sin(Math.PI * (to.lon - from.lon) / 360);
+ var sinHalfDeltaLat = Math.sin(Math.PI * (to.lat - from.lat) / 360);
+ var a = sinHalfDeltaLat * sinHalfDeltaLat +
+ sinHalfDeltaLon * sinHalfDeltaLon * Math.cos(Math.PI * from.lat / 180) * Math.cos(Math.PI * to.lat / 180);
+ return 2 * R * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+};
+
+
+/**
+ * APIFunction: computeHeading
+ * Computes the heading from one LonLat to another LonLat.
+ *
+ * Parameters:
+ * from - {<OpenLayers.LonLat>} or {Object} Starting point. A LonLat or
+ * a JavaScript literal with lon lat properties.
+ * to - {<OpenLayers.LonLat>} or {Object} Ending point. A LonLat or a
+ * JavaScript literal with lon lat properties.
+ *
+ * Returns:
+ * {Float} The heading in degrees.
+ */
+OpenLayers.Spherical.computeHeading = function(from, to) {
+ var y = Math.sin(Math.PI * (from.lon - to.lon) / 180) * Math.cos(Math.PI * to.lat / 180);
+ var x = Math.cos(Math.PI * from.lat / 180) * Math.sin(Math.PI * to.lat / 180) -
+ Math.sin(Math.PI * from.lat / 180) * Math.cos(Math.PI * to.lat / 180) * Math.cos(Math.PI * (from.lon - to.lon) / 180);
+ return 180 * Math.atan2(y, x) / Math.PI;
+};
diff --git a/misc/openlayers/lib/OpenLayers/Strategy.js b/misc/openlayers/lib/OpenLayers/Strategy.js
new file mode 100644
index 0000000..113deb1
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy.js
@@ -0,0 +1,121 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class. Not to be instantiated directly. Use
+ * one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer.Vector>} The layer this strategy belongs to.
+ */
+ layer: null,
+
+ /**
+ * Property: options
+ * {Object} Any options sent to the constructor.
+ */
+ options: null,
+
+ /**
+ * Property: active
+ * {Boolean} The control is active.
+ */
+ active: null,
+
+ /**
+ * Property: autoActivate
+ * {Boolean} The creator of the strategy can set autoActivate to false
+ * to fully control when the protocol is activated and deactivated.
+ * Defaults to true.
+ */
+ autoActivate: true,
+
+ /**
+ * Property: autoDestroy
+ * {Boolean} The creator of the strategy can set autoDestroy to false
+ * to fully control when the strategy is destroyed. Defaults to
+ * true.
+ */
+ autoDestroy: true,
+
+ /**
+ * Constructor: OpenLayers.Strategy
+ * Abstract class for vector strategies. Create instances of a subclass.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.options = options;
+ // set the active property here, so that user cannot override it
+ this.active = false;
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the strategy.
+ */
+ destroy: function() {
+ this.deactivate();
+ this.layer = null;
+ this.options = null;
+ },
+
+ /**
+ * Method: setLayer
+ * Called to set the <layer> property.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer.Vector>}
+ */
+ setLayer: function(layer) {
+ this.layer = layer;
+ },
+
+ /**
+ * Method: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ if (!this.active) {
+ this.active = true;
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ if (this.active) {
+ this.active = false;
+ return true;
+ }
+ return false;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/BBOX.js b/misc/openlayers/lib/OpenLayers/Strategy/BBOX.js
new file mode 100644
index 0000000..e066764
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/BBOX.js
@@ -0,0 +1,290 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ * @requires OpenLayers/Filter/Spatial.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.BBOX
+ * A simple strategy that reads new features when the viewport invalidates
+ * some bounds.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: bounds
+ * {<OpenLayers.Bounds>} The current data bounds (in the same projection
+ * as the layer - not always the same projection as the map).
+ */
+ bounds: null,
+
+ /**
+ * Property: resolution
+ * {Float} The current data resolution.
+ */
+ resolution: null,
+
+ /**
+ * APIProperty: ratio
+ * {Float} The ratio of the data bounds to the viewport bounds (in each
+ * dimension). Default is 2.
+ */
+ ratio: 2,
+
+ /**
+ * Property: resFactor
+ * {Float} Optional factor used to determine when previously requested
+ * features are invalid. If set, the resFactor will be compared to the
+ * resolution of the previous request to the current map resolution.
+ * If resFactor > (old / new) and 1/resFactor < (old / new). If you
+ * set a resFactor of 1, data will be requested every time the
+ * resolution changes. If you set a resFactor of 3, data will be
+ * requested if the old resolution is 3 times the new, or if the new is
+ * 3 times the old. If the old bounds do not contain the new bounds
+ * new data will always be requested (with or without considering
+ * resFactor).
+ */
+ resFactor: null,
+
+ /**
+ * Property: response
+ * {<OpenLayers.Protocol.Response>} The protocol response object returned
+ * by the layer protocol.
+ */
+ response: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.BBOX
+ * Create a new BBOX strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Set up strategy with regard to reading new batches of remote data.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "moveend": this.update,
+ "refresh": this.update,
+ "visibilitychanged": this.update,
+ scope: this
+ });
+ this.update();
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Tear down strategy with regard to reading new batches of remote data.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "moveend": this.update,
+ "refresh": this.update,
+ "visibilitychanged": this.update,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: update
+ * Callback function called on "moveend" or "refresh" layer events.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will determine
+ * the behaviour of this Strategy
+ *
+ * Valid options include:
+ * force - {Boolean} if true, new data must be unconditionally read.
+ * noAbort - {Boolean} if true, do not abort previous requests.
+ */
+ update: function(options) {
+ var mapBounds = this.getMapBounds();
+ if (mapBounds !== null && ((options && options.force) ||
+ (this.layer.visibility && this.layer.calculateInRange() && this.invalidBounds(mapBounds)))) {
+ this.calculateBounds(mapBounds);
+ this.resolution = this.layer.map.getResolution();
+ this.triggerRead(options);
+ }
+ },
+
+ /**
+ * Method: getMapBounds
+ * Get the map bounds expressed in the same projection as this layer.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} Map bounds in the projection of the layer.
+ */
+ getMapBounds: function() {
+ if (this.layer.map === null) {
+ return null;
+ }
+ var bounds = this.layer.map.getExtent();
+ if(bounds && !this.layer.projection.equals(
+ this.layer.map.getProjectionObject())) {
+ bounds = bounds.clone().transform(
+ this.layer.map.getProjectionObject(), this.layer.projection
+ );
+ }
+ return bounds;
+ },
+
+ /**
+ * Method: invalidBounds
+ * Determine whether the previously requested set of features is invalid.
+ * This occurs when the new map bounds do not contain the previously
+ * requested bounds. In addition, if <resFactor> is set, it will be
+ * considered.
+ *
+ * Parameters:
+ * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+ * retrieved from the map object if not provided
+ *
+ * Returns:
+ * {Boolean}
+ */
+ invalidBounds: function(mapBounds) {
+ if(!mapBounds) {
+ mapBounds = this.getMapBounds();
+ }
+ var invalid = !this.bounds || !this.bounds.containsBounds(mapBounds);
+ if(!invalid && this.resFactor) {
+ var ratio = this.resolution / this.layer.map.getResolution();
+ invalid = (ratio >= this.resFactor || ratio <= (1 / this.resFactor));
+ }
+ return invalid;
+ },
+
+ /**
+ * Method: calculateBounds
+ *
+ * Parameters:
+ * mapBounds - {<OpenLayers.Bounds>} the current map extent, will be
+ * retrieved from the map object if not provided
+ */
+ calculateBounds: function(mapBounds) {
+ if(!mapBounds) {
+ mapBounds = this.getMapBounds();
+ }
+ var center = mapBounds.getCenterLonLat();
+ var dataWidth = mapBounds.getWidth() * this.ratio;
+ var dataHeight = mapBounds.getHeight() * this.ratio;
+ this.bounds = new OpenLayers.Bounds(
+ center.lon - (dataWidth / 2),
+ center.lat - (dataHeight / 2),
+ center.lon + (dataWidth / 2),
+ center.lat + (dataHeight / 2)
+ );
+ },
+
+ /**
+ * Method: triggerRead
+ *
+ * Parameters:
+ * options - {Object} Additional options for the protocol's read method
+ * (optional)
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} The protocol response object
+ * returned by the layer protocol.
+ */
+ triggerRead: function(options) {
+ if (this.response && !(options && options.noAbort === true)) {
+ this.layer.protocol.abort(this.response);
+ this.layer.events.triggerEvent("loadend");
+ }
+ var evt = {filter: this.createFilter()};
+ this.layer.events.triggerEvent("loadstart", evt);
+ this.response = this.layer.protocol.read(
+ OpenLayers.Util.applyDefaults({
+ filter: evt.filter,
+ callback: this.merge,
+ scope: this
+ }, options));
+ },
+
+ /**
+ * Method: createFilter
+ * Creates a spatial BBOX filter. If the layer that this strategy belongs
+ * to has a filter property, this filter will be combined with the BBOX
+ * filter.
+ *
+ * Returns
+ * {<OpenLayers.Filter>} The filter object.
+ */
+ createFilter: function() {
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: this.bounds,
+ projection: this.layer.projection
+ });
+ if (this.layer.filter) {
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [this.layer.filter, filter]
+ });
+ }
+ return filter;
+ },
+
+ /**
+ * Method: merge
+ * Given a list of features, determine which ones to add to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ this.layer.destroyFeatures();
+ if (resp.success()) {
+ var features = resp.features;
+ if(features && features.length > 0) {
+ var remote = this.layer.projection;
+ var local = this.layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ this.layer.addFeatures(features);
+ }
+ } else {
+ this.bounds = null;
+ }
+ this.response = null;
+ this.layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.BBOX"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/Cluster.js b/misc/openlayers/lib/OpenLayers/Strategy/Cluster.js
new file mode 100644
index 0000000..d478598
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/Cluster.js
@@ -0,0 +1,283 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Cluster
+ * Strategy for vector feature clustering.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: distance
+ * {Integer} Pixel distance between features that should be considered a
+ * single cluster. Default is 20 pixels.
+ */
+ distance: 20,
+
+ /**
+ * APIProperty: threshold
+ * {Integer} Optional threshold below which original features will be
+ * added to the layer instead of clusters. For example, a threshold
+ * of 3 would mean that any time there are 2 or fewer features in
+ * a cluster, those features will be added directly to the layer instead
+ * of a cluster representing those features. Default is null (which is
+ * equivalent to 1 - meaning that clusters may contain just one feature).
+ */
+ threshold: null,
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature.Vector>)} Cached features.
+ */
+ features: null,
+
+ /**
+ * Property: clusters
+ * {Array(<OpenLayers.Feature.Vector>)} Calculated clusters.
+ */
+ clusters: null,
+
+ /**
+ * Property: clustering
+ * {Boolean} The strategy is currently clustering features.
+ */
+ clustering: false,
+
+ /**
+ * Property: resolution
+ * {Float} The resolution (map units per pixel) of the current cluster set.
+ */
+ resolution: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Cluster
+ * Create a new clustering strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "beforefeaturesadded": this.cacheFeatures,
+ "featuresremoved": this.clearCache,
+ "moveend": this.cluster,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.clearCache();
+ this.layer.events.un({
+ "beforefeaturesadded": this.cacheFeatures,
+ "featuresremoved": this.clearCache,
+ "moveend": this.cluster,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: cacheFeatures
+ * Cache features before they are added to the layer.
+ *
+ * Parameters:
+ * event - {Object} The event that this was listening for. This will come
+ * with a batch of features to be clustered.
+ *
+ * Returns:
+ * {Boolean} False to stop features from being added to the layer.
+ */
+ cacheFeatures: function(event) {
+ var propagate = true;
+ if(!this.clustering) {
+ this.clearCache();
+ this.features = event.features;
+ this.cluster();
+ propagate = false;
+ }
+ return propagate;
+ },
+
+ /**
+ * Method: clearCache
+ * Clear out the cached features.
+ */
+ clearCache: function() {
+ if(!this.clustering) {
+ this.features = null;
+ }
+ },
+
+ /**
+ * Method: cluster
+ * Cluster features based on some threshold distance.
+ *
+ * Parameters:
+ * event - {Object} The event received when cluster is called as a
+ * result of a moveend event.
+ */
+ cluster: function(event) {
+ if((!event || event.zoomChanged) && this.features) {
+ var resolution = this.layer.map.getResolution();
+ if(resolution != this.resolution || !this.clustersExist()) {
+ this.resolution = resolution;
+ var clusters = [];
+ var feature, clustered, cluster;
+ for(var i=0; i<this.features.length; ++i) {
+ feature = this.features[i];
+ if(feature.geometry) {
+ clustered = false;
+ for(var j=clusters.length-1; j>=0; --j) {
+ cluster = clusters[j];
+ if(this.shouldCluster(cluster, feature)) {
+ this.addToCluster(cluster, feature);
+ clustered = true;
+ break;
+ }
+ }
+ if(!clustered) {
+ clusters.push(this.createCluster(this.features[i]));
+ }
+ }
+ }
+ this.clustering = true;
+ this.layer.removeAllFeatures();
+ this.clustering = false;
+ if(clusters.length > 0) {
+ if(this.threshold > 1) {
+ var clone = clusters.slice();
+ clusters = [];
+ var candidate;
+ for(var i=0, len=clone.length; i<len; ++i) {
+ candidate = clone[i];
+ if(candidate.attributes.count < this.threshold) {
+ Array.prototype.push.apply(clusters, candidate.cluster);
+ } else {
+ clusters.push(candidate);
+ }
+ }
+ }
+ this.clustering = true;
+ // A legitimate feature addition could occur during this
+ // addFeatures call. For clustering to behave well, features
+ // should be removed from a layer before requesting a new batch.
+ this.layer.addFeatures(clusters);
+ this.clustering = false;
+ }
+ this.clusters = clusters;
+ }
+ }
+ },
+
+ /**
+ * Method: clustersExist
+ * Determine whether calculated clusters are already on the layer.
+ *
+ * Returns:
+ * {Boolean} The calculated clusters are already on the layer.
+ */
+ clustersExist: function() {
+ var exist = false;
+ if(this.clusters && this.clusters.length > 0 &&
+ this.clusters.length == this.layer.features.length) {
+ exist = true;
+ for(var i=0; i<this.clusters.length; ++i) {
+ if(this.clusters[i] != this.layer.features[i]) {
+ exist = false;
+ break;
+ }
+ }
+ }
+ return exist;
+ },
+
+ /**
+ * Method: shouldCluster
+ * Determine whether to include a feature in a given cluster.
+ *
+ * Parameters:
+ * cluster - {<OpenLayers.Feature.Vector>} A cluster.
+ * feature - {<OpenLayers.Feature.Vector>} A feature.
+ *
+ * Returns:
+ * {Boolean} The feature should be included in the cluster.
+ */
+ shouldCluster: function(cluster, feature) {
+ var cc = cluster.geometry.getBounds().getCenterLonLat();
+ var fc = feature.geometry.getBounds().getCenterLonLat();
+ var distance = (
+ Math.sqrt(
+ Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2)
+ ) / this.resolution
+ );
+ return (distance <= this.distance);
+ },
+
+ /**
+ * Method: addToCluster
+ * Add a feature to a cluster.
+ *
+ * Parameters:
+ * cluster - {<OpenLayers.Feature.Vector>} A cluster.
+ * feature - {<OpenLayers.Feature.Vector>} A feature.
+ */
+ addToCluster: function(cluster, feature) {
+ cluster.cluster.push(feature);
+ cluster.attributes.count += 1;
+ },
+
+ /**
+ * Method: createCluster
+ * Given a feature, create a cluster.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>} A cluster.
+ */
+ createCluster: function(feature) {
+ var center = feature.geometry.getBounds().getCenterLonLat();
+ var cluster = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(center.lon, center.lat),
+ {count: 1}
+ );
+ cluster.cluster = [feature];
+ return cluster;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Cluster"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/Filter.js b/misc/openlayers/lib/OpenLayers/Strategy/Filter.js
new file mode 100644
index 0000000..721fe52
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/Filter.js
@@ -0,0 +1,159 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Filter
+ * Strategy for limiting features that get added to a layer by
+ * evaluating a filter. The strategy maintains a cache of
+ * all features until removeFeatures is called on the layer.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Filter = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: filter
+ * {<OpenLayers.Filter>} Filter for limiting features sent to the layer.
+ * Use the <setFilter> method to update this filter after construction.
+ */
+ filter: null,
+
+ /**
+ * Property: cache
+ * {Array(<OpenLayers.Feature.Vector>)} List of currently cached
+ * features.
+ */
+ cache: null,
+
+ /**
+ * Property: caching
+ * {Boolean} The filter is currently caching features.
+ */
+ caching: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Filter
+ * Create a new filter strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ * By default, this strategy automatically activates itself when a layer
+ * is added to a map.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
+ if (activated) {
+ this.cache = [];
+ this.layer.events.on({
+ "beforefeaturesadded": this.handleAdd,
+ "beforefeaturesremoved": this.handleRemove,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Clear the feature cache.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated or false if
+ * the strategy was already inactive.
+ */
+ deactivate: function() {
+ this.cache = null;
+ if (this.layer && this.layer.events) {
+ this.layer.events.un({
+ "beforefeaturesadded": this.handleAdd,
+ "beforefeaturesremoved": this.handleRemove,
+ scope: this
+ });
+ }
+ return OpenLayers.Strategy.prototype.deactivate.apply(this, arguments);
+ },
+
+ /**
+ * Method: handleAdd
+ */
+ handleAdd: function(event) {
+ if (!this.caching && this.filter) {
+ var features = event.features;
+ event.features = [];
+ var feature;
+ for (var i=0, ii=features.length; i<ii; ++i) {
+ feature = features[i];
+ if (this.filter.evaluate(feature)) {
+ event.features.push(feature);
+ } else {
+ this.cache.push(feature);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: handleRemove
+ */
+ handleRemove: function(event) {
+ if (!this.caching) {
+ this.cache = [];
+ }
+ },
+
+ /**
+ * APIMethod: setFilter
+ * Update the filter for this strategy. This will re-evaluate
+ * any features on the layer and in the cache. Only features
+ * for which filter.evalute(feature) returns true will be
+ * added to the layer. Others will be cached by the strategy.
+ *
+ * Parameters:
+ * filter - {<OpenLayers.Filter>} A filter for evaluating features.
+ */
+ setFilter: function(filter) {
+ this.filter = filter;
+ var previousCache = this.cache;
+ this.cache = [];
+ // look through layer for features to remove from layer
+ this.handleAdd({features: this.layer.features});
+ // cache now contains features to remove from layer
+ if (this.cache.length > 0) {
+ this.caching = true;
+ this.layer.removeFeatures(this.cache.slice());
+ this.caching = false;
+ }
+ // now look through previous cache for features to add to layer
+ if (previousCache.length > 0) {
+ var event = {features: previousCache};
+ this.handleAdd(event);
+ if (event.features.length > 0) {
+ // event has features to add to layer
+ this.caching = true;
+ this.layer.addFeatures(event.features);
+ this.caching = false;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Filter"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/Fixed.js b/misc/openlayers/lib/OpenLayers/Strategy/Fixed.js
new file mode 100644
index 0000000..b06f2cd
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/Fixed.js
@@ -0,0 +1,135 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: preload
+ * {Boolean} Load data before layer made visible. Enabling this may result
+ * in considerable overhead if your application loads many data layers
+ * that are not visible by default. Default is false.
+ */
+ preload: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Fixed
+ * Create a new Fixed strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * Method: activate
+ * Activate the strategy: load data or add listener to load when visible
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated or false if
+ * the strategy was already active.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.apply(this, arguments);
+ if(activated) {
+ this.layer.events.on({
+ "refresh": this.load,
+ scope: this
+ });
+ if(this.layer.visibility == true || this.preload) {
+ this.load();
+ } else {
+ this.layer.events.on({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * Method: deactivate
+ * Deactivate the strategy. Undo what is done in <activate>.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.layer.events.un({
+ "refresh": this.load,
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: load
+ * Tells protocol to load data and unhooks the visibilitychanged event
+ *
+ * Parameters:
+ * options - {Object} options to pass to protocol read.
+ */
+ load: function(options) {
+ var layer = this.layer;
+ layer.events.triggerEvent("loadstart", {filter: layer.filter});
+ layer.protocol.read(OpenLayers.Util.applyDefaults({
+ callback: this.merge,
+ filter: layer.filter,
+ scope: this
+ }, options));
+ layer.events.un({
+ "visibilitychanged": this.load,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: merge
+ * Add all features to the layer.
+ * If the layer projection differs from the map projection, features
+ * will be transformed from the layer projection to the map projection.
+ *
+ * Parameters:
+ * resp - {<OpenLayers.Protocol.Response>} The response object passed
+ * by the protocol.
+ */
+ merge: function(resp) {
+ var layer = this.layer;
+ layer.destroyFeatures();
+ var features = resp.features;
+ if (features && features.length > 0) {
+ var remote = layer.projection;
+ var local = layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var geom;
+ for(var i=0, len=features.length; i<len; ++i) {
+ geom = features[i].geometry;
+ if(geom) {
+ geom.transform(remote, local);
+ }
+ }
+ }
+ layer.addFeatures(features);
+ }
+ layer.events.triggerEvent("loadend", {response: resp});
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/Paging.js b/misc/openlayers/lib/OpenLayers/Strategy/Paging.js
new file mode 100644
index 0000000..22154fa
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/Paging.js
@@ -0,0 +1,233 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Paging
+ * Strategy for vector feature paging
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Paging = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature.Vector>)} Cached features.
+ */
+ features: null,
+
+ /**
+ * Property: length
+ * {Integer} Number of features per page. Default is 10.
+ */
+ length: 10,
+
+ /**
+ * Property: num
+ * {Integer} The currently displayed page number.
+ */
+ num: null,
+
+ /**
+ * Property: paging
+ * {Boolean} The strategy is currently changing pages.
+ */
+ paging: false,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Paging
+ * Create a new paging strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ this.layer.events.on({
+ "beforefeaturesadded": this.cacheFeatures,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.clearCache();
+ this.layer.events.un({
+ "beforefeaturesadded": this.cacheFeatures,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: cacheFeatures
+ * Cache features before they are added to the layer.
+ *
+ * Parameters:
+ * event - {Object} The event that this was listening for. This will come
+ * with a batch of features to be paged.
+ */
+ cacheFeatures: function(event) {
+ if(!this.paging) {
+ this.clearCache();
+ this.features = event.features;
+ this.pageNext(event);
+ }
+ },
+
+ /**
+ * Method: clearCache
+ * Clear out the cached features. This destroys features, assuming
+ * nothing else has a reference.
+ */
+ clearCache: function() {
+ if(this.features) {
+ for(var i=0; i<this.features.length; ++i) {
+ this.features[i].destroy();
+ }
+ }
+ this.features = null;
+ this.num = null;
+ },
+
+ /**
+ * APIMethod: pageCount
+ * Get the total count of pages given the current cache of features.
+ *
+ * Returns:
+ * {Integer} The page count.
+ */
+ pageCount: function() {
+ var numFeatures = this.features ? this.features.length : 0;
+ return Math.ceil(numFeatures / this.length);
+ },
+
+ /**
+ * APIMethod: pageNum
+ * Get the zero based page number.
+ *
+ * Returns:
+ * {Integer} The current page number being displayed.
+ */
+ pageNum: function() {
+ return this.num;
+ },
+
+ /**
+ * APIMethod: pageLength
+ * Gets or sets page length.
+ *
+ * Parameters:
+ * newLength - {Integer} Optional length to be set.
+ *
+ * Returns:
+ * {Integer} The length of a page (number of features per page).
+ */
+ pageLength: function(newLength) {
+ if(newLength && newLength > 0) {
+ this.length = newLength;
+ }
+ return this.length;
+ },
+
+ /**
+ * APIMethod: pageNext
+ * Display the next page of features.
+ *
+ * Returns:
+ * {Boolean} A new page was displayed.
+ */
+ pageNext: function(event) {
+ var changed = false;
+ if(this.features) {
+ if(this.num === null) {
+ this.num = -1;
+ }
+ var start = (this.num + 1) * this.length;
+ changed = this.page(start, event);
+ }
+ return changed;
+ },
+
+ /**
+ * APIMethod: pagePrevious
+ * Display the previous page of features.
+ *
+ * Returns:
+ * {Boolean} A new page was displayed.
+ */
+ pagePrevious: function() {
+ var changed = false;
+ if(this.features) {
+ if(this.num === null) {
+ this.num = this.pageCount();
+ }
+ var start = (this.num - 1) * this.length;
+ changed = this.page(start);
+ }
+ return changed;
+ },
+
+ /**
+ * Method: page
+ * Display the page starting at the given index from the cache.
+ *
+ * Returns:
+ * {Boolean} A new page was displayed.
+ */
+ page: function(start, event) {
+ var changed = false;
+ if(this.features) {
+ if(start >= 0 && start < this.features.length) {
+ var num = Math.floor(start / this.length);
+ if(num != this.num) {
+ this.paging = true;
+ var features = this.features.slice(start, start + this.length);
+ this.layer.removeFeatures(this.layer.features);
+ this.num = num;
+ // modify the event if any
+ if(event && event.features) {
+ // this.was called by an event listener
+ event.features = features;
+ } else {
+ // this was called directly on the strategy
+ this.layer.addFeatures(features);
+ }
+ this.paging = false;
+ changed = true;
+ }
+ }
+ }
+ return changed;
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Paging"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/Refresh.js b/misc/openlayers/lib/OpenLayers/Strategy/Refresh.js
new file mode 100644
index 0000000..cca187c
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/Refresh.js
@@ -0,0 +1,141 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Refresh
+ * A strategy that refreshes the layer. By default the strategy waits for a
+ * call to <refresh> before refreshing. By configuring the strategy with
+ * the <interval> option, refreshing can take place automatically.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Refresh = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * Property: force
+ * {Boolean} Force a refresh on the layer. Default is false.
+ */
+ force: false,
+
+ /**
+ * Property: interval
+ * {Number} Auto-refresh. Default is 0. If > 0, layer will be refreshed
+ * every N milliseconds.
+ */
+ interval: 0,
+
+ /**
+ * Property: timer
+ * {Number} The id of the timer.
+ */
+ timer: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Refresh
+ * Create a new Refresh strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ if(this.layer.visibility === true) {
+ this.start();
+ }
+ this.layer.events.on({
+ "visibilitychanged": this.reset,
+ scope: this
+ });
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} True if the strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ this.stop();
+ this.layer.events.un({
+ "visibilitychanged": this.reset,
+ scope: this
+ });
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: reset
+ * Start or cancel the refresh interval depending on the visibility of
+ * the layer.
+ */
+ reset: function() {
+ if(this.layer.visibility === true) {
+ this.start();
+ } else {
+ this.stop();
+ }
+ },
+
+ /**
+ * Method: start
+ * Start the refresh interval.
+ */
+ start: function() {
+ if(this.interval && typeof this.interval === "number" &&
+ this.interval > 0) {
+
+ this.timer = window.setInterval(
+ OpenLayers.Function.bind(this.refresh, this),
+ this.interval);
+ }
+ },
+
+ /**
+ * APIMethod: refresh
+ * Tell the strategy to refresh which will refresh the layer.
+ */
+ refresh: function() {
+ if (this.layer && this.layer.refresh &&
+ typeof this.layer.refresh == "function") {
+
+ this.layer.refresh({force: this.force});
+ }
+ },
+
+ /**
+ * Method: stop
+ * Cancels the refresh interval.
+ */
+ stop: function() {
+ if(this.timer !== null) {
+ window.clearInterval(this.timer);
+ this.timer = null;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Refresh"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Strategy/Save.js b/misc/openlayers/lib/OpenLayers/Strategy/Save.js
new file mode 100644
index 0000000..2211e95
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Strategy/Save.js
@@ -0,0 +1,231 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Save
+ * A strategy that commits newly created or modified features. By default
+ * the strategy waits for a call to <save> before persisting changes. By
+ * configuring the strategy with the <auto> option, changes can be saved
+ * automatically.
+ *
+ * Inherits from:
+ * - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Save = OpenLayers.Class(OpenLayers.Strategy, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the strategy object.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * strategy.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types:
+ * start - Triggered before saving
+ * success - Triggered after a successful transaction
+ * fail - Triggered after a failed transaction
+ *
+ */
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>} Events instance for triggering this protocol
+ * events.
+ */
+ events: null,
+
+ /**
+ * APIProperty: auto
+ * {Boolean | Number} Auto-save. Default is false. If true, features will be
+ * saved immediately after being added to the layer and with each
+ * modification or deletion. If auto is a number, features will be
+ * saved on an interval provided by the value (in seconds).
+ */
+ auto: false,
+
+ /**
+ * Property: timer
+ * {Number} The id of the timer.
+ */
+ timer: null,
+
+ /**
+ * Constructor: OpenLayers.Strategy.Save
+ * Create a new Save strategy.
+ *
+ * Parameters:
+ * options - {Object} Optional object whose properties will be set on the
+ * instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
+ this.events = new OpenLayers.Events(this);
+ },
+
+ /**
+ * APIMethod: activate
+ * Activate the strategy. Register any listeners, do appropriate setup.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully activated.
+ */
+ activate: function() {
+ var activated = OpenLayers.Strategy.prototype.activate.call(this);
+ if(activated) {
+ if(this.auto) {
+ if(typeof this.auto === "number") {
+ this.timer = window.setInterval(
+ OpenLayers.Function.bind(this.save, this),
+ this.auto * 1000
+ );
+ } else {
+ this.layer.events.on({
+ "featureadded": this.triggerSave,
+ "afterfeaturemodified": this.triggerSave,
+ scope: this
+ });
+ }
+ }
+ }
+ return activated;
+ },
+
+ /**
+ * APIMethod: deactivate
+ * Deactivate the strategy. Unregister any listeners, do appropriate
+ * tear-down.
+ *
+ * Returns:
+ * {Boolean} The strategy was successfully deactivated.
+ */
+ deactivate: function() {
+ var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this);
+ if(deactivated) {
+ if(this.auto) {
+ if(typeof this.auto === "number") {
+ window.clearInterval(this.timer);
+ } else {
+ this.layer.events.un({
+ "featureadded": this.triggerSave,
+ "afterfeaturemodified": this.triggerSave,
+ scope: this
+ });
+ }
+ }
+ }
+ return deactivated;
+ },
+
+ /**
+ * Method: triggerSave
+ * Registered as a listener. Calls save if a feature has insert, update,
+ * or delete state.
+ *
+ * Parameters:
+ * event - {Object} The event this function is listening for.
+ */
+ triggerSave: function(event) {
+ var feature = event.feature;
+ if(feature.state === OpenLayers.State.INSERT ||
+ feature.state === OpenLayers.State.UPDATE ||
+ feature.state === OpenLayers.State.DELETE) {
+ this.save([event.feature]);
+ }
+ },
+
+ /**
+ * APIMethod: save
+ * Tell the layer protocol to commit unsaved features. If the layer
+ * projection differs from the map projection, features will be
+ * transformed into the layer projection before being committed.
+ *
+ * Parameters:
+ * features - {Array} Features to be saved. If null, then default is all
+ * features in the layer. Features are assumed to be in the map
+ * projection.
+ */
+ save: function(features) {
+ if(!features) {
+ features = this.layer.features;
+ }
+ this.events.triggerEvent("start", {features:features});
+ var remote = this.layer.projection;
+ var local = this.layer.map.getProjectionObject();
+ if(!local.equals(remote)) {
+ var len = features.length;
+ var clones = new Array(len);
+ var orig, clone;
+ for(var i=0; i<len; ++i) {
+ orig = features[i];
+ clone = orig.clone();
+ clone.fid = orig.fid;
+ clone.state = orig.state;
+ if(orig.url) {
+ clone.url = orig.url;
+ }
+ clone._original = orig;
+ clone.geometry.transform(local, remote);
+ clones[i] = clone;
+ }
+ features = clones;
+ }
+ this.layer.protocol.commit(features, {
+ callback: this.onCommit,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: onCommit
+ * Called after protocol commit.
+ *
+ * Parameters:
+ * response - {<OpenLayers.Protocol.Response>} A response object.
+ */
+ onCommit: function(response) {
+ var evt = {"response": response};
+ if(response.success()) {
+ var features = response.reqFeatures;
+ // deal with inserts, updates, and deletes
+ var state, feature;
+ var destroys = [];
+ var insertIds = response.insertIds || [];
+ var j = 0;
+ for(var i=0, len=features.length; i<len; ++i) {
+ feature = features[i];
+ // if projection was different, we may be dealing with clones
+ feature = feature._original || feature;
+ state = feature.state;
+ if(state) {
+ if(state == OpenLayers.State.DELETE) {
+ destroys.push(feature);
+ } else if(state == OpenLayers.State.INSERT) {
+ feature.fid = insertIds[j];
+ ++j;
+ }
+ feature.state = null;
+ }
+ }
+
+ if(destroys.length > 0) {
+ this.layer.destroyFeatures(destroys);
+ }
+
+ this.events.triggerEvent("success", evt);
+
+ } else {
+ this.events.triggerEvent("fail", evt);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Strategy.Save"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Style.js b/misc/openlayers/lib/OpenLayers/Style.js
new file mode 100644
index 0000000..39c4a48
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Style.js
@@ -0,0 +1,448 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Style
+ * This class represents a UserStyle obtained
+ * from a SLD, containing styling rules.
+ */
+OpenLayers.Style = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String}
+ */
+ name: null,
+
+ /**
+ * Property: title
+ * {String} Title of this style (set if included in SLD)
+ */
+ title: null,
+
+ /**
+ * Property: description
+ * {String} Description of this style (set if abstract is included in SLD)
+ */
+ description: null,
+
+ /**
+ * APIProperty: layerName
+ * {<String>} name of the layer that this style belongs to, usually
+ * according to the NamedLayer attribute of an SLD document.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: isDefault
+ * {Boolean}
+ */
+ isDefault: false,
+
+ /**
+ * Property: rules
+ * {Array(<OpenLayers.Rule>)}
+ */
+ rules: null,
+
+ /**
+ * APIProperty: context
+ * {Object} An optional object with properties that symbolizers' property
+ * values should be evaluated against. If no context is specified,
+ * feature.attributes will be used
+ */
+ context: null,
+
+ /**
+ * Property: defaultStyle
+ * {Object} hash of style properties to use as default for merging
+ * rule-based style symbolizers onto. If no rules are defined,
+ * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
+ * true, the defaultStyle will only be taken into account if there are
+ * rules defined.
+ */
+ defaultStyle: null,
+
+ /**
+ * Property: defaultsPerSymbolizer
+ * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
+ * of every rule. Properties of the <defaultStyle> will also be used to set
+ * missing symbolizer properties if the symbolizer has stroke, fill or
+ * graphic set to true. Default is false.
+ */
+ defaultsPerSymbolizer: false,
+
+ /**
+ * Property: propertyStyles
+ * {Hash of Boolean} cache of style properties that need to be parsed for
+ * propertyNames. Property names are keys, values won't be used.
+ */
+ propertyStyles: null,
+
+
+ /**
+ * Constructor: OpenLayers.Style
+ * Creates a UserStyle.
+ *
+ * Parameters:
+ * style - {Object} Optional hash of style properties that will be
+ * used as default style for this style object. This style
+ * applies if no rules are specified. Symbolizers defined in
+ * rules will extend this default style.
+ * options - {Object} An optional object with properties to set on the
+ * style.
+ *
+ * Valid options:
+ * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
+ * style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>}
+ */
+ initialize: function(style, options) {
+
+ OpenLayers.Util.extend(this, options);
+ this.rules = [];
+ if(options && options.rules) {
+ this.addRules(options.rules);
+ }
+
+ // use the default style from OpenLayers.Feature.Vector if no style
+ // was given in the constructor
+ this.setDefaultStyle(style ||
+ OpenLayers.Feature.Vector.style["default"]);
+
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i=0, len=this.rules.length; i<len; i++) {
+ this.rules[i].destroy();
+ this.rules[i] = null;
+ }
+ this.rules = null;
+ this.defaultStyle = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * creates a style by applying all feature-dependent rules to the base
+ * style.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} feature to evaluate rules for
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature) {
+ var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
+ OpenLayers.Util.extend({}, this.defaultStyle), feature);
+
+ var rules = this.rules;
+
+ var rule, context;
+ var elseRules = [];
+ var appliedRules = false;
+ for(var i=0, len=rules.length; i<len; i++) {
+ rule = rules[i];
+ // does the rule apply?
+ var applies = rule.evaluate(feature);
+
+ if(applies) {
+ if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+ elseRules.push(rule);
+ } else {
+ appliedRules = true;
+ this.applySymbolizer(rule, style, feature);
+ }
+ }
+ }
+
+ // if no other rules apply, apply the rules with else filters
+ if(appliedRules == false && elseRules.length > 0) {
+ appliedRules = true;
+ for(var i=0, len=elseRules.length; i<len; i++) {
+ this.applySymbolizer(elseRules[i], style, feature);
+ }
+ }
+
+ // don't display if there were rules but none applied
+ if(rules.length > 0 && appliedRules == false) {
+ style.display = "none";
+ }
+
+ if (style.label != null && typeof style.label !== "string") {
+ style.label = String(style.label);
+ }
+
+ return style;
+ },
+
+ /**
+ * Method: applySymbolizer
+ *
+ * Parameters:
+ * rule - {<OpenLayers.Rule>}
+ * style - {Object}
+ * feature - {<OpenLayer.Feature.Vector>}
+ *
+ * Returns:
+ * {Object} A style with new symbolizer applied.
+ */
+ applySymbolizer: function(rule, style, feature) {
+ var symbolizerPrefix = feature.geometry ?
+ this.getSymbolizerPrefix(feature.geometry) :
+ OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
+
+ var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+
+ if(this.defaultsPerSymbolizer === true) {
+ var defaults = this.defaultStyle;
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: defaults.pointRadius
+ });
+ if(symbolizer.stroke === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ strokeWidth: defaults.strokeWidth,
+ strokeColor: defaults.strokeColor,
+ strokeOpacity: defaults.strokeOpacity,
+ strokeDashstyle: defaults.strokeDashstyle,
+ strokeLinecap: defaults.strokeLinecap
+ });
+ }
+ if(symbolizer.fill === true || symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ fillColor: defaults.fillColor,
+ fillOpacity: defaults.fillOpacity
+ });
+ }
+ if(symbolizer.graphic === true) {
+ OpenLayers.Util.applyDefaults(symbolizer, {
+ pointRadius: this.defaultStyle.pointRadius,
+ externalGraphic: this.defaultStyle.externalGraphic,
+ graphicName: this.defaultStyle.graphicName,
+ graphicOpacity: this.defaultStyle.graphicOpacity,
+ graphicWidth: this.defaultStyle.graphicWidth,
+ graphicHeight: this.defaultStyle.graphicHeight,
+ graphicXOffset: this.defaultStyle.graphicXOffset,
+ graphicYOffset: this.defaultStyle.graphicYOffset
+ });
+ }
+ }
+
+ // merge the style with the current style
+ return this.createLiterals(
+ OpenLayers.Util.extend(style, symbolizer), feature);
+ },
+
+ /**
+ * Method: createLiterals
+ * creates literals for all style properties that have an entry in
+ * <this.propertyStyles>.
+ *
+ * Parameters:
+ * style - {Object} style to create literals for. Will be modified
+ * inline.
+ * feature - {Object}
+ *
+ * Returns:
+ * {Object} the modified style
+ */
+ createLiterals: function(style, feature) {
+ var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
+ OpenLayers.Util.extend(context, this.context);
+
+ for (var i in this.propertyStyles) {
+ style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
+ }
+ return style;
+ },
+
+ /**
+ * Method: findPropertyStyles
+ * Looks into all rules for this style and the defaultStyle to collect
+ * all the style hash property names containing ${...} strings that have
+ * to be replaced using the createLiteral method before returning them.
+ *
+ * Returns:
+ * {Object} hash of property names that need createLiteral parsing. The
+ * name of the property is the key, and the value is true;
+ */
+ findPropertyStyles: function() {
+ var propertyStyles = {};
+
+ // check the default style
+ var style = this.defaultStyle;
+ this.addPropertyStyles(propertyStyles, style);
+
+ // walk through all rules to check for properties in their symbolizer
+ var rules = this.rules;
+ var symbolizer, value;
+ for (var i=0, len=rules.length; i<len; i++) {
+ symbolizer = rules[i].symbolizer;
+ for (var key in symbolizer) {
+ value = symbolizer[key];
+ if (typeof value == "object") {
+ // symbolizer key is "Point", "Line" or "Polygon"
+ this.addPropertyStyles(propertyStyles, value);
+ } else {
+ // symbolizer is a hash of style properties
+ this.addPropertyStyles(propertyStyles, symbolizer);
+ break;
+ }
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * Method: addPropertyStyles
+ *
+ * Parameters:
+ * propertyStyles - {Object} hash to add new property styles to. Will be
+ * modified inline
+ * symbolizer - {Object} search this symbolizer for property styles
+ *
+ * Returns:
+ * {Object} propertyStyles hash
+ */
+ addPropertyStyles: function(propertyStyles, symbolizer) {
+ var property;
+ for (var key in symbolizer) {
+ property = symbolizer[key];
+ if (typeof property == "string" &&
+ property.match(/\$\{\w+\}/)) {
+ propertyStyles[key] = true;
+ }
+ }
+ return propertyStyles;
+ },
+
+ /**
+ * APIMethod: addRules
+ * Adds rules to this style.
+ *
+ * Parameters:
+ * rules - {Array(<OpenLayers.Rule>)}
+ */
+ addRules: function(rules) {
+ Array.prototype.push.apply(this.rules, rules);
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * APIMethod: setDefaultStyle
+ * Sets the default style for this style object.
+ *
+ * Parameters:
+ * style - {Object} Hash of style properties
+ */
+ setDefaultStyle: function(style) {
+ this.defaultStyle = style;
+ this.propertyStyles = this.findPropertyStyles();
+ },
+
+ /**
+ * Method: getSymbolizerPrefix
+ * Returns the correct symbolizer prefix according to the
+ * geometry type of the passed geometry
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {String} key of the according symbolizer
+ */
+ getSymbolizerPrefix: function(geometry) {
+ var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+ for (var i=0, len=prefixes.length; i<len; i++) {
+ if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+ return prefixes[i];
+ }
+ }
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style>} Clone of this style.
+ */
+ clone: function() {
+ var options = OpenLayers.Util.extend({}, this);
+ // clone rules
+ if(this.rules) {
+ options.rules = [];
+ for(var i=0, len=this.rules.length; i<len; ++i) {
+ options.rules.push(this.rules[i].clone());
+ }
+ }
+ // clone context
+ options.context = this.context && OpenLayers.Util.extend({}, this.context);
+ //clone default style
+ var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle);
+ return new OpenLayers.Style(defaultStyle, options);
+ },
+
+ CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ *
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ * will be replaced by the value of the "bar" attribute of the passed
+ * feature.
+ * context - {Object} context to take attribute values from
+ * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to
+ * <OpenLayers.String.format> for evaluating functions in the
+ * context.
+ * property - {String} optional, name of the property for which the literal is
+ * being created for evaluating functions in the context.
+ *
+ * Returns:
+ * {String} the parsed value. In the example of the value parameter above, the
+ * result would be "foo valueOfBar", assuming that the passed feature has an
+ * attribute named "bar" with the value "valueOfBar".
+ */
+OpenLayers.Style.createLiteral = function(value, context, feature, property) {
+ if (typeof value == "string" && value.indexOf("${") != -1) {
+ value = OpenLayers.String.format(value, context, [feature, property]);
+ value = (isNaN(value) || !value) ? value : parseFloat(value);
+ }
+ return value;
+};
+
+/**
+ * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
+ * {Array} prefixes of the sld symbolizers. These are the
+ * same as the main geometry types
+ */
+OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text',
+ 'Raster'];
diff --git a/misc/openlayers/lib/OpenLayers/Style2.js b/misc/openlayers/lib/OpenLayers/Style2.js
new file mode 100644
index 0000000..672dae9
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Style2.js
@@ -0,0 +1,112 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Symbolizer/Point.js
+ * @requires OpenLayers/Symbolizer/Line.js
+ * @requires OpenLayers/Symbolizer/Polygon.js
+ * @requires OpenLayers/Symbolizer/Text.js
+ * @requires OpenLayers/Symbolizer/Raster.js
+ */
+
+/**
+ * Class: OpenLayers.Style2
+ * This class represents a collection of rules for rendering features.
+ */
+OpenLayers.Style2 = OpenLayers.Class({
+
+ /**
+ * Property: id
+ * {String} A unique id for this session.
+ */
+ id: null,
+
+ /**
+ * APIProperty: name
+ * {String} Style identifier.
+ */
+ name: null,
+
+ /**
+ * APIProperty: title
+ * {String} Title of this style.
+ */
+ title: null,
+
+ /**
+ * APIProperty: description
+ * {String} Description of this style.
+ */
+ description: null,
+
+ /**
+ * APIProperty: layerName
+ * {<String>} Name of the layer that this style belongs to, usually
+ * according to the NamedLayer attribute of an SLD document.
+ */
+ layerName: null,
+
+ /**
+ * APIProperty: isDefault
+ * {Boolean}
+ */
+ isDefault: false,
+
+ /**
+ * APIProperty: rules
+ * {Array(<OpenLayers.Rule>)} Collection of rendering rules.
+ */
+ rules: null,
+
+ /**
+ * Constructor: OpenLayers.Style2
+ * Creates a style representing a collection of rendering rules.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * style. Any documented properties may be set at construction.
+ *
+ * Returns:
+ * {<OpenLayers.Style2>} A new style object.
+ */
+ initialize: function(config) {
+ OpenLayers.Util.extend(this, config);
+ this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ for (var i=0, len=this.rules.length; i<len; i++) {
+ this.rules[i].destroy();
+ }
+ delete this.rules;
+ },
+
+ /**
+ * APIMethod: clone
+ * Clones this style.
+ *
+ * Returns:
+ * {<OpenLayers.Style2>} Clone of this style.
+ */
+ clone: function() {
+ var config = OpenLayers.Util.extend({}, this);
+ // clone rules
+ if (this.rules) {
+ config.rules = [];
+ for (var i=0, len=this.rules.length; i<len; ++i) {
+ config.rules.push(this.rules[i].clone());
+ }
+ }
+ return new OpenLayers.Style2(config);
+ },
+
+ CLASS_NAME: "OpenLayers.Style2"
+});
diff --git a/misc/openlayers/lib/OpenLayers/StyleMap.js b/misc/openlayers/lib/OpenLayers/StyleMap.js
new file mode 100644
index 0000000..b6daca2
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/StyleMap.js
@@ -0,0 +1,161 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.StyleMap
+ */
+OpenLayers.StyleMap = OpenLayers.Class({
+
+ /**
+ * Property: styles
+ * {Object} Hash of {<OpenLayers.Style>}, keyed by names of well known
+ * rendering intents (e.g. "default", "temporary", "select", "delete").
+ */
+ styles: null,
+
+ /**
+ * Property: extendDefault
+ * {Boolean} if true, every render intent will extend the symbolizers
+ * specified for the "default" intent at rendering time. Otherwise, every
+ * rendering intent will be treated as a completely independent style.
+ */
+ extendDefault: true,
+
+ /**
+ * Constructor: OpenLayers.StyleMap
+ *
+ * Parameters:
+ * style - {Object} Optional. Either a style hash, or a style object, or
+ * a hash of style objects (style hashes) keyed by rendering
+ * intent. If just one style hash or style object is passed,
+ * this will be used for all known render intents (default,
+ * select, temporary)
+ * options - {Object} optional hash of additional options for this
+ * instance
+ */
+ initialize: function (style, options) {
+ this.styles = {
+ "default": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["default"]),
+ "select": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["select"]),
+ "temporary": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["temporary"]),
+ "delete": new OpenLayers.Style(
+ OpenLayers.Feature.Vector.style["delete"])
+ };
+
+ // take whatever the user passed as style parameter and convert it
+ // into parts of stylemap.
+ if(style instanceof OpenLayers.Style) {
+ // user passed a style object
+ this.styles["default"] = style;
+ this.styles["select"] = style;
+ this.styles["temporary"] = style;
+ this.styles["delete"] = style;
+ } else if(typeof style == "object") {
+ for(var key in style) {
+ if(style[key] instanceof OpenLayers.Style) {
+ // user passed a hash of style objects
+ this.styles[key] = style[key];
+ } else if(typeof style[key] == "object") {
+ // user passsed a hash of style hashes
+ this.styles[key] = new OpenLayers.Style(style[key]);
+ } else {
+ // user passed a style hash (i.e. symbolizer)
+ this.styles["default"] = new OpenLayers.Style(style);
+ this.styles["select"] = new OpenLayers.Style(style);
+ this.styles["temporary"] = new OpenLayers.Style(style);
+ this.styles["delete"] = new OpenLayers.Style(style);
+ break;
+ }
+ }
+ }
+ OpenLayers.Util.extend(this, options);
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for(var key in this.styles) {
+ this.styles[key].destroy();
+ }
+ this.styles = null;
+ },
+
+ /**
+ * Method: createSymbolizer
+ * Creates the symbolizer for a feature for a render intent.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
+ * of the intended style against.
+ * intent - {String} The intent determines the symbolizer that will be
+ * used to draw the feature. Well known intents are "default"
+ * (for just drawing the features), "select" (for selected
+ * features) and "temporary" (for drawing features).
+ *
+ * Returns:
+ * {Object} symbolizer hash
+ */
+ createSymbolizer: function(feature, intent) {
+ if(!feature) {
+ feature = new OpenLayers.Feature.Vector();
+ }
+ if(!this.styles[intent]) {
+ intent = "default";
+ }
+ feature.renderIntent = intent;
+ var defaultSymbolizer = {};
+ if(this.extendDefault && intent != "default") {
+ defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
+ }
+ return OpenLayers.Util.extend(defaultSymbolizer,
+ this.styles[intent].createSymbolizer(feature));
+ },
+
+ /**
+ * Method: addUniqueValueRules
+ * Convenience method to create comparison rules for unique values of a
+ * property. The rules will be added to the style object for a specified
+ * rendering intent. This method is a shortcut for creating something like
+ * the "unique value legends" familiar from well known desktop GIS systems
+ *
+ * Parameters:
+ * renderIntent - {String} rendering intent to add the rules to
+ * property - {String} values of feature attributes to create the
+ * rules for
+ * symbolizers - {Object} Hash of symbolizers, keyed by the desired
+ * property values
+ * context - {Object} An optional object with properties that
+ * symbolizers' property values should be evaluated
+ * against. If no context is specified, feature.attributes
+ * will be used
+ */
+ addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
+ var rules = [];
+ for (var value in symbolizers) {
+ rules.push(new OpenLayers.Rule({
+ symbolizer: symbolizers[value],
+ context: context,
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: property,
+ value: value
+ })
+ }));
+ }
+ this.styles[renderIntent].addRules(rules);
+ },
+
+ CLASS_NAME: "OpenLayers.StyleMap"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Symbolizer.js b/misc/openlayers/lib/OpenLayers/Symbolizer.js
new file mode 100644
index 0000000..e0d54e8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Symbolizer.js
@@ -0,0 +1,55 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer
+ * Base class representing a symbolizer used for feature rendering.
+ */
+OpenLayers.Symbolizer = OpenLayers.Class({
+
+
+ /**
+ * APIProperty: zIndex
+ * {Number} The zIndex determines the rendering order for a symbolizer.
+ * Symbolizers with larger zIndex values are rendered over symbolizers
+ * with smaller zIndex values. Default is 0.
+ */
+ zIndex: 0,
+
+ /**
+ * Constructor: OpenLayers.Symbolizer
+ * Instances of this class are not useful. See one of the subclasses.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Util.extend(this, config);
+ },
+
+ /**
+ * APIMethod: clone
+ * Create a copy of this symbolizer.
+ *
+ * Returns a symbolizer of the same type with the same properties.
+ */
+ clone: function() {
+ var Type = eval(this.CLASS_NAME);
+ return new Type(OpenLayers.Util.extend({}, this));
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer"
+
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Symbolizer/Line.js b/misc/openlayers/lib/OpenLayers/Symbolizer/Line.js
new file mode 100644
index 0000000..41203e5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Symbolizer/Line.js
@@ -0,0 +1,74 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Line
+ * A symbolizer used to render line features.
+ */
+OpenLayers.Symbolizer.Line = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: strokeColor
+ * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000"
+ * for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeOpacity
+ * {Number} Stroke opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeWidth
+ * {Number} Pixel stroke width.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeLinecap
+ * {String} Stroke cap type ("butt", "round", or "square").
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: strokeDashstyle
+ * {String} Stroke dash style according to the SLD spec. Note that the
+ * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot",
+ * "longdash", "longdashdot", or "solid") will not work in SLD, but
+ * most SLD patterns will render correctly in OpenLayers.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Line
+ * Create a symbolizer for rendering lines.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new line symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Line"
+
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Symbolizer/Point.js b/misc/openlayers/lib/OpenLayers/Symbolizer/Point.js
new file mode 100644
index 0000000..fa9d932
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Symbolizer/Point.js
@@ -0,0 +1,157 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Point
+ * A symbolizer used to render point features.
+ */
+OpenLayers.Symbolizer.Point = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: strokeColor
+ * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000"
+ * for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeOpacity
+ * {Number} Stroke opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeWidth
+ * {Number} Pixel stroke width.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeLinecap
+ * {String} Stroke cap type ("butt", "round", or "square").
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: strokeDashstyle
+ * {String} Stroke dash style according to the SLD spec. Note that the
+ * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot",
+ * "longdash", "longdashdot", or "solid") will not work in SLD, but
+ * most SLD patterns will render correctly in OpenLayers.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillColor
+ * {String} RGB hex fill color (e.g. "#ff0000" for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillOpacity
+ * {Number} Fill opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: pointRadius
+ * {Number} Pixel point radius.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: externalGraphic
+ * {String} Url to an external graphic that will be used for rendering
+ * points.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicWidth
+ * {Number} Pixel width for sizing an external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicHeight
+ * {Number} Pixel height for sizing an external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicOpacity
+ * {Number} Opacity (0-1) for an external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicXOffset
+ * {Number} Pixel offset along the positive x axis for displacing an
+ * external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicYOffset
+ * {Number} Pixel offset along the positive y axis for displacing an
+ * external graphic.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: rotation
+ * {Number} The rotation of a graphic in the clockwise direction about its
+ * center point (or any point off center as specified by
+ * <graphicXOffset> and <graphicYOffset>).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: graphicName
+ * {String} Named graphic to use when rendering points. Supported values
+ * include "circle", "square", "star", "x", "cross", and "triangle".
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Point
+ * Create a symbolizer for rendering points.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new point symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Point"
+
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Symbolizer/Polygon.js b/misc/openlayers/lib/OpenLayers/Symbolizer/Polygon.js
new file mode 100644
index 0000000..e4158c8
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Symbolizer/Polygon.js
@@ -0,0 +1,88 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Polygon
+ * A symbolizer used to render line features.
+ */
+OpenLayers.Symbolizer.Polygon = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: strokeColor
+ * {String} Color for line stroke. This is a RGB hex value (e.g. "#ff0000"
+ * for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeOpacity
+ * {Number} Stroke opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeWidth
+ * {Number} Pixel stroke width.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: strokeLinecap
+ * {String} Stroke cap type ("butt", "round", or "square").
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: strokeDashstyle
+ * {String} Stroke dash style according to the SLD spec. Note that the
+ * OpenLayers values for strokeDashstyle ("dot", "dash", "dashdot",
+ * "longdash", "longdashdot", or "solid") will not work in SLD, but
+ * most SLD patterns will render correctly in OpenLayers.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillColor
+ * {String} RGB hex fill color (e.g. "#ff0000" for red).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fillOpacity
+ * {Number} Fill opacity (0-1).
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Polygon
+ * Create a symbolizer for rendering polygons.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new polygon symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Polygon"
+
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Symbolizer/Raster.js b/misc/openlayers/lib/OpenLayers/Symbolizer/Raster.js
new file mode 100644
index 0000000..cf87a42
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Symbolizer/Raster.js
@@ -0,0 +1,34 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Raster
+ * A symbolizer used to render raster images.
+ */
+OpenLayers.Symbolizer.Raster = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Raster
+ * Create a symbolizer for rendering rasters.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new raster symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Raster"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Symbolizer/Text.js b/misc/openlayers/lib/OpenLayers/Symbolizer/Text.js
new file mode 100644
index 0000000..10dab20
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Symbolizer/Text.js
@@ -0,0 +1,70 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Symbolizer.js
+ */
+
+/**
+ * Class: OpenLayers.Symbolizer.Text
+ * A symbolizer used to render text labels for features.
+ */
+OpenLayers.Symbolizer.Text = OpenLayers.Class(OpenLayers.Symbolizer, {
+
+ /**
+ * APIProperty: label
+ * {String} The text for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fontFamily
+ * {String} The font family for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fontSize
+ * {String} The font size for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * APIProperty: fontWeight
+ * {String} The font weight for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Property: fontStyle
+ * {String} The font style for the label.
+ *
+ * No default set here. Use OpenLayers.Renderer.defaultRenderer for defaults.
+ */
+
+ /**
+ * Constructor: OpenLayers.Symbolizer.Text
+ * Create a symbolizer for rendering text labels.
+ *
+ * Parameters:
+ * config - {Object} An object containing properties to be set on the
+ * symbolizer. Any documented symbolizer property can be set at
+ * construction.
+ *
+ * Returns:
+ * A new text symbolizer.
+ */
+ initialize: function(config) {
+ OpenLayers.Symbolizer.prototype.initialize.apply(this, arguments);
+ },
+
+ CLASS_NAME: "OpenLayers.Symbolizer.Text"
+
+});
+
diff --git a/misc/openlayers/lib/OpenLayers/Tile.js b/misc/openlayers/lib/OpenLayers/Tile.js
new file mode 100644
index 0000000..b3d1ba5
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Tile.js
@@ -0,0 +1,292 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile
+ * This is a class designed to designate a single tile, however
+ * it is explicitly designed to do relatively little. Tiles store
+ * information about themselves -- such as the URL that they are related
+ * to, and their size - but do not add themselves to the layer div
+ * automatically, for example. Create a new tile with the
+ * <OpenLayers.Tile> constructor, or a subclass.
+ *
+ * TBD 3.0 - remove reference to url in above paragraph
+ *
+ */
+OpenLayers.Tile = OpenLayers.Class({
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types:
+ * beforedraw - Triggered before the tile is drawn. Used to defer
+ * drawing to an animation queue. To defer drawing, listeners need
+ * to return false, which will abort drawing. The queue handler needs
+ * to call <draw>(true) to actually draw the tile.
+ * loadstart - Triggered when tile loading starts.
+ * loadend - Triggered when tile loading ends.
+ * loaderror - Triggered before the loadend event (i.e. when the tile is
+ * still hidden) if the tile could not be loaded.
+ * reload - Triggered when an already loading tile is reloaded.
+ * unload - Triggered before a tile is unloaded.
+ */
+ events: null,
+
+ /**
+ * APIProperty: eventListeners
+ * {Object} If set as an option at construction, the eventListeners
+ * object will be registered with <OpenLayers.Events.on>. Object
+ * structure must be a listeners object as shown in the example for
+ * the events.on method.
+ *
+ * This options can be set in the ``tileOptions`` option from
+ * <OpenLayers.Layer.Grid>. For example, to be notified of the
+ * ``loadend`` event of each tiles:
+ * (code)
+ * new OpenLayers.Layer.OSM('osm', 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', {
+ * tileOptions: {
+ * eventListeners: {
+ * 'loadend': function(evt) {
+ * // do something on loadend
+ * }
+ * }
+ * }
+ * });
+ * (end)
+ */
+ eventListeners: null,
+
+ /**
+ * Property: id
+ * {String} null
+ */
+ id: null,
+
+ /**
+ * Property: layer
+ * {<OpenLayers.Layer>} layer the tile is attached to
+ */
+ layer: null,
+
+ /**
+ * Property: url
+ * {String} url of the request.
+ *
+ * TBD 3.0
+ * Deprecated. The base tile class does not need an url. This should be
+ * handled in subclasses. Does not belong here.
+ */
+ url: null,
+
+ /**
+ * APIProperty: bounds
+ * {<OpenLayers.Bounds>} null
+ */
+ bounds: null,
+
+ /**
+ * Property: size
+ * {<OpenLayers.Size>} null
+ */
+ size: null,
+
+ /**
+ * Property: position
+ * {<OpenLayers.Pixel>} Top Left pixel of the tile
+ */
+ position: null,
+
+ /**
+ * Property: isLoading
+ * {Boolean} Is the tile loading?
+ */
+ isLoading: false,
+
+ /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
+ * there is no need for the base tile class to have a url.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile
+ * Constructor for a new <OpenLayers.Tile> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>}
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ this.layer = layer;
+ this.position = position.clone();
+ this.setBounds(bounds);
+ this.url = url;
+ if (size) {
+ this.size = size.clone();
+ }
+
+ //give the tile a unique id based on its BBOX.
+ this.id = OpenLayers.Util.createUniqueID("Tile_");
+
+ OpenLayers.Util.extend(this, options);
+
+ this.events = new OpenLayers.Events(this);
+ if (this.eventListeners instanceof Object) {
+ this.events.on(this.eventListeners);
+ }
+ },
+
+ /**
+ * Method: unload
+ * Call immediately before destroying if you are listening to tile
+ * events, so that counters are properly handled if tile is still
+ * loading at destroy-time. Will only fire an event if the tile is
+ * still loading.
+ */
+ unload: function() {
+ if (this.isLoading) {
+ this.isLoading = false;
+ this.events.triggerEvent("unload");
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * Nullify references to prevent circular references and memory leaks.
+ */
+ destroy:function() {
+ this.layer = null;
+ this.bounds = null;
+ this.size = null;
+ this.position = null;
+
+ if (this.eventListeners) {
+ this.events.un(this.eventListeners);
+ }
+ this.events.destroy();
+ this.eventListeners = null;
+ this.events = null;
+ },
+
+ /**
+ * Method: draw
+ * Clear whatever is currently in the tile, then return whether or not
+ * it should actually be re-drawn. This is an example implementation
+ * that can be overridden by subclasses. The minimum thing to do here
+ * is to call <clear> and return the result from <shouldDraw>.
+ *
+ * Parameters:
+ * force - {Boolean} If true, the tile will not be cleared and no beforedraw
+ * event will be fired. This is used for drawing tiles asynchronously
+ * after drawing has been cancelled by returning false from a beforedraw
+ * listener.
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn. Returns null
+ * if a beforedraw listener returned false.
+ */
+ draw: function(force) {
+ if (!force) {
+ //clear tile's contents and mark as not drawn
+ this.clear();
+ }
+ var draw = this.shouldDraw();
+ if (draw && !force && this.events.triggerEvent("beforedraw") === false) {
+ draw = null;
+ }
+ return draw;
+ },
+
+ /**
+ * Method: shouldDraw
+ * Return whether or not the tile should actually be (re-)drawn. The only
+ * case where we *wouldn't* want to draw the tile is if the tile is outside
+ * its layer's maxExtent
+ *
+ * Returns:
+ * {Boolean} Whether or not the tile should actually be drawn.
+ */
+ shouldDraw: function() {
+ var withinMaxExtent = false,
+ maxExtent = this.layer.maxExtent;
+ if (maxExtent) {
+ var map = this.layer.map;
+ var worldBounds = map.baseLayer.wrapDateLine && map.getMaxExtent();
+ if (this.bounds.intersectsBounds(maxExtent, {inclusive: false, worldBounds: worldBounds})) {
+ withinMaxExtent = true;
+ }
+ }
+
+ return withinMaxExtent || this.layer.displayOutsideMaxExtent;
+ },
+
+ /**
+ * Method: setBounds
+ * Sets the bounds on this instance
+ *
+ * Parameters:
+ * bounds {<OpenLayers.Bounds>}
+ */
+ setBounds: function(bounds) {
+ bounds = bounds.clone();
+ if (this.layer.map.baseLayer.wrapDateLine) {
+ var worldExtent = this.layer.map.getMaxExtent(),
+ tolerance = this.layer.map.getResolution();
+ bounds = bounds.wrapDateLine(worldExtent, {
+ leftTolerance: tolerance,
+ rightTolerance: tolerance
+ });
+ }
+ this.bounds = bounds;
+ },
+
+ /**
+ * Method: moveTo
+ * Reposition the tile.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ * redraw - {Boolean} Call draw method on tile after moving.
+ * Default is true
+ */
+ moveTo: function (bounds, position, redraw) {
+ if (redraw == null) {
+ redraw = true;
+ }
+
+ this.setBounds(bounds);
+ this.position = position.clone();
+ if (redraw) {
+ this.draw();
+ }
+ },
+
+ /**
+ * Method: clear
+ * Clear the tile of any bounds/position-related data so that it can
+ * be reused in a new location.
+ */
+ clear: function(draw) {
+ // to be extended by subclasses
+ },
+
+ CLASS_NAME: "OpenLayers.Tile"
+});
diff --git a/misc/openlayers/lib/OpenLayers/Tile/Image.js b/misc/openlayers/lib/OpenLayers/Tile/Image.js
new file mode 100644
index 0000000..2fdffb3
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Tile/Image.js
@@ -0,0 +1,510 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Animation.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.Image
+ * Instances of OpenLayers.Tile.Image are used to manage the image tiles
+ * used by various layers. Create a new image tile with the
+ * <OpenLayers.Tile.Image> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * APIProperty: events
+ * {<OpenLayers.Events>} An events object that handles all
+ * events on the tile.
+ *
+ * Register a listener for a particular event with the following syntax:
+ * (code)
+ * tile.events.register(type, obj, listener);
+ * (end)
+ *
+ * Supported event types (in addition to the <OpenLayers.Tile> events):
+ * beforeload - Triggered before an image is prepared for loading, when the
+ * url for the image is known already. Listeners may call <setImage> on
+ * the tile instance. If they do so, that image will be used and no new
+ * one will be created.
+ */
+
+ /**
+ * APIProperty: url
+ * {String} The URL of the image being requested. No default. Filled in by
+ * layer.getURL() function. May be modified by loadstart listeners.
+ */
+ url: null,
+
+ /**
+ * Property: imgDiv
+ * {HTMLImageElement} The image for this tile.
+ */
+ imgDiv: null,
+
+ /**
+ * Property: frame
+ * {DOMElement} The image element is appended to the frame. Any gutter on
+ * the image will be hidden behind the frame. If no gutter is set,
+ * this will be null.
+ */
+ frame: null,
+
+ /**
+ * Property: imageReloadAttempts
+ * {Integer} Attempts to load the image.
+ */
+ imageReloadAttempts: null,
+
+ /**
+ * Property: layerAlphaHack
+ * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
+ */
+ layerAlphaHack: null,
+
+ /**
+ * Property: asyncRequestId
+ * {Integer} ID of an request to see if request is still valid. This is a
+ * number which increments by 1 for each asynchronous request.
+ */
+ asyncRequestId: null,
+
+ /**
+ * APIProperty: maxGetUrlLength
+ * {Number} If set, requests that would result in GET urls with more
+ * characters than the number provided will be made using form-encoded
+ * HTTP POST. It is good practice to avoid urls that are longer than 2048
+ * characters.
+ *
+ * Caution:
+ * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most
+ * Opera versions do not fully support this option. On all browsers,
+ * transition effects are not supported if POST requests are used.
+ */
+ maxGetUrlLength: null,
+
+ /**
+ * Property: canvasContext
+ * {CanvasRenderingContext2D} A canvas context associated with
+ * the tile image.
+ */
+ canvasContext: null,
+
+ /**
+ * APIProperty: crossOriginKeyword
+ * The value of the crossorigin keyword to use when loading images. This is
+ * only relevant when using <getCanvasContext> for tiles from remote
+ * origins and should be set to either 'anonymous' or 'use-credentials'
+ * for servers that send Access-Control-Allow-Origin headers with their
+ * tiles.
+ */
+ crossOriginKeyword: null,
+
+ /** TBD 3.0 - reorder the parameters to the init function to remove
+ * URL. the getUrl() function on the layer gets called on
+ * each draw(), so no need to specify it here.
+ */
+
+ /**
+ * Constructor: OpenLayers.Tile.Image
+ * Constructor for a new <OpenLayers.Tile.Image> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+ initialize: function(layer, position, bounds, url, size, options) {
+ OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+
+ this.url = url; //deprecated remove me
+
+ this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack();
+
+ if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) {
+ // only create frame if it's needed
+ this.frame = document.createElement("div");
+ this.frame.style.position = "absolute";
+ this.frame.style.overflow = "hidden";
+ }
+ if (this.maxGetUrlLength != null) {
+ OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame);
+ }
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.imgDiv) {
+ this.clear();
+ this.imgDiv = null;
+ this.frame = null;
+ }
+ // don't handle async requests any more
+ this.asyncRequestId = null;
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and draw it.
+ *
+ * Returns:
+ * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned
+ * false.
+ */
+ draw: function() {
+ var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+ if (shouldDraw) {
+ // The layer's reproject option is deprecated.
+ if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
+ // getBoundsFromBaseLayer is defined in deprecated.js.
+ this.bounds = this.getBoundsFromBaseLayer(this.position);
+ }
+ if (this.isLoading) {
+ //if we're already loading, send 'reload' instead of 'loadstart'.
+ this._loadEvent = "reload";
+ } else {
+ this.isLoading = true;
+ this._loadEvent = "loadstart";
+ }
+ this.renderTile();
+ this.positionTile();
+ } else if (shouldDraw === false) {
+ this.unload();
+ }
+ return shouldDraw;
+ },
+
+ /**
+ * Method: renderTile
+ * Internal function to actually initialize the image tile,
+ * position it correctly, and set its url.
+ */
+ renderTile: function() {
+ if (this.layer.async) {
+ // Asynchronous image requests call the asynchronous getURL method
+ // on the layer to fetch an image that covers 'this.bounds'.
+ var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1;
+ this.layer.getURLasync(this.bounds, function(url) {
+ if (id == this.asyncRequestId) {
+ this.url = url;
+ this.initImage();
+ }
+ }, this);
+ } else {
+ // synchronous image requests get the url immediately.
+ this.url = this.layer.getURL(this.bounds);
+ this.initImage();
+ }
+ },
+
+ /**
+ * Method: positionTile
+ * Using the properties currenty set on the layer, position the tile correctly.
+ * This method is used both by the async and non-async versions of the Tile.Image
+ * code.
+ */
+ positionTile: function() {
+ var style = this.getTile().style,
+ size = this.frame ? this.size :
+ this.layer.getImageSize(this.bounds),
+ ratio = 1;
+ if (this.layer instanceof OpenLayers.Layer.Grid) {
+ ratio = this.layer.getServerResolution() / this.layer.map.getResolution();
+ }
+ style.left = this.position.x + "px";
+ style.top = this.position.y + "px";
+ style.width = Math.round(ratio * size.w) + "px";
+ style.height = Math.round(ratio * size.h) + "px";
+ },
+
+ /**
+ * Method: clear
+ * Remove the tile from the DOM, clear it of any image related data so that
+ * it can be reused in a new location.
+ */
+ clear: function() {
+ OpenLayers.Tile.prototype.clear.apply(this, arguments);
+ var img = this.imgDiv;
+ if (img) {
+ var tile = this.getTile();
+ if (tile.parentNode === this.layer.div) {
+ this.layer.div.removeChild(tile);
+ }
+ this.setImgSrc();
+ if (this.layerAlphaHack === true) {
+ img.style.filter = "";
+ }
+ OpenLayers.Element.removeClass(img, "olImageLoadError");
+ }
+ this.canvasContext = null;
+ },
+
+ /**
+ * Method: getImage
+ * Returns or creates and returns the tile image.
+ */
+ getImage: function() {
+ if (!this.imgDiv) {
+ this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false);
+
+ var style = this.imgDiv.style;
+ if (this.frame) {
+ var left = 0, top = 0;
+ if (this.layer.gutter) {
+ left = this.layer.gutter / this.layer.tileSize.w * 100;
+ top = this.layer.gutter / this.layer.tileSize.h * 100;
+ }
+ style.left = -left + "%";
+ style.top = -top + "%";
+ style.width = (2 * left + 100) + "%";
+ style.height = (2 * top + 100) + "%";
+ }
+ style.visibility = "hidden";
+ style.opacity = 0;
+ if (this.layer.opacity < 1) {
+ style.filter = 'alpha(opacity=' +
+ (this.layer.opacity * 100) +
+ ')';
+ }
+ style.position = "absolute";
+ if (this.layerAlphaHack) {
+ // move the image out of sight
+ style.paddingTop = style.height;
+ style.height = "0";
+ style.width = "100%";
+ }
+ if (this.frame) {
+ this.frame.appendChild(this.imgDiv);
+ }
+ }
+
+ return this.imgDiv;
+ },
+
+ /**
+ * APIMethod: setImage
+ * Sets the image element for this tile. This method should only be called
+ * from beforeload listeners.
+ *
+ * Parameters
+ * img - {HTMLImageElement} The image to use for this tile.
+ */
+ setImage: function(img) {
+ this.imgDiv = img;
+ },
+
+ /**
+ * Method: initImage
+ * Creates the content for the frame on the tile.
+ */
+ initImage: function() {
+ if (!this.url && !this.imgDiv) {
+ // fast path out - if there is no tile url and no previous image
+ this.isLoading = false;
+ return;
+ }
+ this.events.triggerEvent('beforeload');
+ this.layer.div.appendChild(this.getTile());
+ this.events.triggerEvent(this._loadEvent);
+ var img = this.getImage();
+ var src = img.getAttribute('src') || '';
+ if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) {
+ this._loadTimeout = window.setTimeout(
+ OpenLayers.Function.bind(this.onImageLoad, this), 0
+ );
+ } else {
+ this.stopLoading();
+ if (this.crossOriginKeyword) {
+ img.removeAttribute("crossorigin");
+ }
+ OpenLayers.Event.observe(img, "load",
+ OpenLayers.Function.bind(this.onImageLoad, this)
+ );
+ OpenLayers.Event.observe(img, "error",
+ OpenLayers.Function.bind(this.onImageError, this)
+ );
+ this.imageReloadAttempts = 0;
+ this.setImgSrc(this.url);
+ }
+ },
+
+ /**
+ * Method: setImgSrc
+ * Sets the source for the tile image
+ *
+ * Parameters:
+ * url - {String} or undefined to hide the image
+ */
+ setImgSrc: function(url) {
+ var img = this.imgDiv;
+ if (url) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ // don't set crossOrigin if the url is a data URL
+ if (this.crossOriginKeyword) {
+ if (url.substr(0, 5) !== 'data:') {
+ img.setAttribute("crossorigin", this.crossOriginKeyword);
+ } else {
+ img.removeAttribute("crossorigin");
+ }
+ }
+ img.src = url;
+ } else {
+ // Remove reference to the image, and leave it to the browser's
+ // caching and garbage collection.
+ this.stopLoading();
+ this.imgDiv = null;
+ if (img.parentNode) {
+ img.parentNode.removeChild(img);
+ }
+ }
+ },
+
+ /**
+ * Method: getTile
+ * Get the tile's markup.
+ *
+ * Returns:
+ * {DOMElement} The tile's markup
+ */
+ getTile: function() {
+ return this.frame ? this.frame : this.getImage();
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Create a backbuffer for this tile. A backbuffer isn't exactly a clone
+ * of the tile's markup, because we want to avoid the reloading of the
+ * image. So we clone the frame, and steal the image from the tile.
+ *
+ * Returns:
+ * {DOMElement} The markup, or undefined if the tile has no image
+ * or if it's currently loading.
+ */
+ createBackBuffer: function() {
+ if (!this.imgDiv || this.isLoading) {
+ return;
+ }
+ var backBuffer;
+ if (this.frame) {
+ backBuffer = this.frame.cloneNode(false);
+ backBuffer.appendChild(this.imgDiv);
+ } else {
+ backBuffer = this.imgDiv;
+ }
+ this.imgDiv = null;
+ return backBuffer;
+ },
+
+ /**
+ * Method: onImageLoad
+ * Handler for the image onload event
+ */
+ onImageLoad: function() {
+ var img = this.imgDiv;
+ this.stopLoading();
+ img.style.visibility = 'inherit';
+ img.style.opacity = this.layer.opacity;
+ this.isLoading = false;
+ this.canvasContext = null;
+ this.events.triggerEvent("loadend");
+
+ if (this.layerAlphaHack === true) {
+ img.style.filter =
+ "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" +
+ img.src + "', sizingMethod='scale')";
+ }
+ },
+
+ /**
+ * Method: onImageError
+ * Handler for the image onerror event
+ */
+ onImageError: function() {
+ var img = this.imgDiv;
+ if (img.src != null) {
+ this.imageReloadAttempts++;
+ if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
+ this.setImgSrc(this.layer.getURL(this.bounds));
+ } else {
+ OpenLayers.Element.addClass(img, "olImageLoadError");
+ this.events.triggerEvent("loaderror");
+ this.onImageLoad();
+ }
+ }
+ },
+
+ /**
+ * Method: stopLoading
+ * Stops a loading sequence so <onImageLoad> won't be executed.
+ */
+ stopLoading: function() {
+ OpenLayers.Event.stopObservingElement(this.imgDiv);
+ window.clearTimeout(this._loadTimeout);
+ delete this._loadTimeout;
+ },
+
+ /**
+ * APIMethod: getCanvasContext
+ * Returns a canvas context associated with the tile image (with
+ * the image drawn on it).
+ * Returns undefined if the browser does not support canvas, if
+ * the tile has no image or if it's currently loading.
+ *
+ * The function returns a canvas context instance but the
+ * underlying canvas is still available in the 'canvas' property:
+ * (code)
+ * var context = tile.getCanvasContext();
+ * if (context) {
+ * var data = context.canvas.toDataURL('image/jpeg');
+ * }
+ * (end)
+ *
+ * Returns:
+ * {Boolean}
+ */
+ getCanvasContext: function() {
+ if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) {
+ if (!this.canvasContext) {
+ var canvas = document.createElement("canvas");
+ canvas.width = this.size.w;
+ canvas.height = this.size.h;
+ this.canvasContext = canvas.getContext("2d");
+ this.canvasContext.drawImage(this.imgDiv, 0, 0);
+ }
+ return this.canvasContext;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.Image"
+
+});
+
+/**
+ * Constant: OpenLayers.Tile.Image.IMAGE
+ * {HTMLImageElement} The image for a tile.
+ */
+OpenLayers.Tile.Image.IMAGE = (function() {
+ var img = new Image();
+ img.className = "olTileImage";
+ // avoid image gallery menu in IE6
+ img.galleryImg = "no";
+ return img;
+}());
+
diff --git a/misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js b/misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js
new file mode 100644
index 0000000..9e33acc
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js
@@ -0,0 +1,233 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Constant: OpenLayers.Tile.Image.IFrame
+ * Mixin for tiles that use form-encoded POST requests to get images from
+ * remote services. Images will be loaded using HTTP-POST into an IFrame.
+ *
+ * This mixin will be applied to <OpenLayers.Tile.Image> instances
+ * configured with <OpenLayers.Tile.Image.maxGetUrlLength> set.
+ */
+OpenLayers.Tile.Image.IFrame = {
+
+ /**
+ * Property: useIFrame
+ * {Boolean} true if we are currently using an IFrame to render POST
+ * responses, false if we are using an img element to render GET responses.
+ */
+ useIFrame: null,
+
+ /**
+ * Property: blankImageUrl
+ * {String} Using a data scheme url is not supported by all browsers, but
+ * we don't care because we either set it as css backgroundImage, or the
+ * image's display style is set to "none" when we use it.
+ */
+ blankImageUrl: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7",
+
+ /**
+ * Method: draw
+ * Set useIFrame in the instance, and operate the image/iframe switch.
+ * Then call Tile.Image.draw.
+ *
+ * Returns:
+ * {Boolean}
+ */
+ draw: function() {
+ var draw = OpenLayers.Tile.Image.prototype.shouldDraw.call(this);
+ if(draw) {
+
+ // this.url isn't set to the currect value yet, so we call getURL
+ // on the layer and store the result in a local variable
+ var url = this.layer.getURL(this.bounds);
+
+ var usedIFrame = this.useIFrame;
+ this.useIFrame = this.maxGetUrlLength !== null &&
+ !this.layer.async &&
+ url.length > this.maxGetUrlLength;
+
+ var fromIFrame = usedIFrame && !this.useIFrame;
+ var toIFrame = !usedIFrame && this.useIFrame;
+
+ if(fromIFrame || toIFrame) {
+
+ // Switching between GET (image) and POST (iframe).
+
+ // We remove the imgDiv (really either an image or an iframe)
+ // from the frame and set it to null to make sure initImage
+ // will call getImage.
+
+ if(this.imgDiv && this.imgDiv.parentNode === this.frame) {
+ this.frame.removeChild(this.imgDiv);
+ }
+ this.imgDiv = null;
+
+ // And if we had an iframe we also remove the event pane.
+
+ if(fromIFrame) {
+ this.frame.removeChild(this.frame.firstChild);
+ }
+ }
+ }
+ return OpenLayers.Tile.Image.prototype.draw.apply(this, arguments);
+ },
+
+ /**
+ * Method: getImage
+ * Creates the content for the frame on the tile.
+ */
+ getImage: function() {
+ if (this.useIFrame === true) {
+ if (!this.frame.childNodes.length) {
+ var eventPane = document.createElement("div"),
+ style = eventPane.style;
+ style.position = "absolute";
+ style.width = "100%";
+ style.height = "100%";
+ style.zIndex = 1;
+ style.backgroundImage = "url(" + this.blankImageUrl + ")";
+ this.frame.appendChild(eventPane);
+ }
+
+ var id = this.id + '_iFrame', iframe;
+ if (parseFloat(navigator.appVersion.split("MSIE")[1]) < 9) {
+ // Older IE versions do not set the name attribute of an iFrame
+ // properly via DOM manipulation, so we need to do it on our own with
+ // this hack.
+ iframe = document.createElement('<iframe name="'+id+'">');
+
+ // IFrames in older IE versions are not transparent, if you set
+ // the backgroundColor transparent. This is a workaround to get
+ // transparent iframes.
+ iframe.style.backgroundColor = '#FFFFFF';
+ iframe.style.filter = 'chroma(color=#FFFFFF)';
+ }
+ else {
+ iframe = document.createElement('iframe');
+ iframe.style.backgroundColor = 'transparent';
+
+ // iframe.name needs to be an unique id, otherwise it
+ // could happen that other iframes are overwritten.
+ iframe.name = id;
+ }
+
+ // some special properties to avoid scaling the images and scrollbars
+ // in the iframe
+ iframe.scrolling = 'no';
+ iframe.marginWidth = '0px';
+ iframe.marginHeight = '0px';
+ iframe.frameBorder = '0';
+
+ iframe.style.position = "absolute";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+
+ if (this.layer.opacity < 1) {
+ OpenLayers.Util.modifyDOMElement(iframe, null, null, null,
+ null, null, null, this.layer.opacity);
+ }
+ this.frame.appendChild(iframe);
+ this.imgDiv = iframe;
+ return iframe;
+ } else {
+ return OpenLayers.Tile.Image.prototype.getImage.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Method: createRequestForm
+ * Create the html <form> element with width, height, bbox and all
+ * parameters specified in the layer params.
+ *
+ * Returns:
+ * {DOMElement} The form element which sends the HTTP-POST request to the
+ * WMS.
+ */
+ createRequestForm: function() {
+ // creation of the form element
+ var form = document.createElement('form');
+ form.method = 'POST';
+ var cacheId = this.layer.params["_OLSALT"];
+ cacheId = (cacheId ? cacheId + "_" : "") + this.bounds.toBBOX();
+ form.action = OpenLayers.Util.urlAppend(this.layer.url, cacheId);
+ form.target = this.id + '_iFrame';
+
+ // adding all parameters in layer params as hidden fields to the html
+ // form element
+ var imageSize = this.layer.getImageSize(),
+ params = OpenLayers.Util.getParameters(this.url),
+ field;
+
+ for(var par in params) {
+ field = document.createElement('input');
+ field.type = 'hidden';
+ field.name = par;
+ field.value = params[par];
+ form.appendChild(field);
+ }
+
+ return form;
+ },
+
+ /**
+ * Method: setImgSrc
+ * Sets the source for the tile image
+ *
+ * Parameters:
+ * url - {String}
+ */
+ setImgSrc: function(url) {
+ if (this.useIFrame === true) {
+ if (url) {
+ var form = this.createRequestForm();
+ this.frame.appendChild(form);
+ form.submit();
+ this.frame.removeChild(form);
+ } else if (this.imgDiv.parentNode === this.frame) {
+ // we don't reuse iframes to avoid caching issues
+ this.frame.removeChild(this.imgDiv);
+ this.imgDiv = null;
+ }
+ } else {
+ OpenLayers.Tile.Image.prototype.setImgSrc.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Method: onImageLoad
+ * Handler for the image onload event
+ */
+ onImageLoad: function() {
+ //TODO de-uglify opacity handling
+ OpenLayers.Tile.Image.prototype.onImageLoad.apply(this, arguments);
+ if (this.useIFrame === true) {
+ this.imgDiv.style.opacity = 1;
+ this.frame.style.opacity = this.layer.opacity;
+ }
+ },
+
+ /**
+ * Method: createBackBuffer
+ * Override createBackBuffer to do nothing when we use an iframe. Moving an
+ * iframe from one element to another makes it necessary to reload the iframe
+ * because its content is lost. So we just give up.
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createBackBuffer: function() {
+ var backBuffer;
+ if(this.useIFrame === false) {
+ backBuffer = OpenLayers.Tile.Image.prototype.createBackBuffer.call(this);
+ }
+ return backBuffer;
+ }
+};
diff --git a/misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js b/misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js
new file mode 100644
index 0000000..2836ee0
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js
@@ -0,0 +1,252 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.UTFGrid
+ * Instances of OpenLayers.Tile.UTFGrid are used to manage
+ * UTFGrids. This is an unusual tile type in that it doesn't have a
+ * rendered image; only a 'hit grid' that can be used to
+ * look up feature attributes.
+ *
+ * See the <OpenLayers.Tile.UTFGrid> constructor for details on constructing a
+ * new instance.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.UTFGrid = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * Property: url
+ * {String}
+ * The URL of the UTFGrid file being requested. Provided by the <getURL>
+ * method.
+ */
+ url: null,
+
+ /**
+ * Property: utfgridResolution
+ * {Number}
+ * Ratio of the pixel width to the width of a UTFGrid data point. If an
+ * entry in the grid represents a 4x4 block of pixels, the
+ * utfgridResolution would be 4. Default is 2.
+ */
+ utfgridResolution: 2,
+
+ /**
+ * Property: json
+ * {Object}
+ * Stores the parsed JSON tile data structure.
+ */
+ json: null,
+
+ /**
+ * Property: format
+ * {OpenLayers.Format.JSON}
+ * Parser instance used to parse JSON for cross browser support. The native
+ * JSON.parse method will be used where available (all except IE<8).
+ */
+ format: null,
+
+ /**
+ * Constructor: OpenLayers.Tile.UTFGrid
+ * Constructor for a new <OpenLayers.Tile.UTFGrid> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>} Deprecated. Remove me in 3.0.
+ * size - {<OpenLayers.Size>}
+ * options - {Object}
+ */
+
+ /**
+ * APIMethod: destroy
+ * Clean up.
+ */
+ destroy: function() {
+ this.clear();
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and draw it.
+ * In the case of UTFGrids, "drawing" it means fetching and
+ * parsing the json.
+ *
+ * Returns:
+ * {Boolean} Was a tile drawn?
+ */
+ draw: function() {
+ var drawn = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+ if (drawn) {
+ if (this.isLoading) {
+ this.abortLoading();
+ //if we're already loading, send 'reload' instead of 'loadstart'.
+ this.events.triggerEvent("reload");
+ } else {
+ this.isLoading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.url = this.layer.getURL(this.bounds);
+
+ if (this.layer.useJSONP) {
+ // Use JSONP method to avoid xbrowser policy
+ var ols = new OpenLayers.Protocol.Script({
+ url: this.url,
+ callback: function(response) {
+ this.isLoading = false;
+ this.events.triggerEvent("loadend");
+ this.json = response.data;
+ },
+ scope: this
+ });
+ ols.read();
+ this.request = ols;
+ } else {
+ // Use standard XHR
+ this.request = OpenLayers.Request.GET({
+ url: this.url,
+ callback: function(response) {
+ this.isLoading = false;
+ this.events.triggerEvent("loadend");
+ if (response.status === 200) {
+ this.parseData(response.responseText);
+ }
+ },
+ scope: this
+ });
+ }
+ } else {
+ this.unload();
+ }
+ return drawn;
+ },
+
+ /**
+ * Method: abortLoading
+ * Cancel a pending request.
+ */
+ abortLoading: function() {
+ if (this.request) {
+ this.request.abort();
+ delete this.request;
+ }
+ this.isLoading = false;
+ },
+
+ /**
+ * Method: getFeatureInfo
+ * Get feature information associated with a pixel offset. If the pixel
+ * offset corresponds to a feature, the returned object will have id
+ * and data properties. Otherwise, null will be returned.
+ *
+ *
+ * Parameters:
+ * i - {Number} X-axis pixel offset (from top left of tile)
+ * j - {Number} Y-axis pixel offset (from top left of tile)
+ *
+ * Returns:
+ * {Object} Object with feature id and data properties corresponding to the
+ * given pixel offset.
+ */
+ getFeatureInfo: function(i, j) {
+ var info = null;
+ if (this.json) {
+ var id = this.getFeatureId(i, j);
+ if (id !== null) {
+ info = {id: id, data: this.json.data[id]};
+ }
+ }
+ return info;
+ },
+
+ /**
+ * Method: getFeatureId
+ * Get the identifier for the feature associated with a pixel offset.
+ *
+ * Parameters:
+ * i - {Number} X-axis pixel offset (from top left of tile)
+ * j - {Number} Y-axis pixel offset (from top left of tile)
+ *
+ * Returns:
+ * {Object} The feature identifier corresponding to the given pixel offset.
+ * Returns null if pixel doesn't correspond to a feature.
+ */
+ getFeatureId: function(i, j) {
+ var id = null;
+ if (this.json) {
+ var resolution = this.utfgridResolution;
+ var row = Math.floor(j / resolution);
+ var col = Math.floor(i / resolution);
+ var charCode = this.json.grid[row].charCodeAt(col);
+ var index = this.indexFromCharCode(charCode);
+ var keys = this.json.keys;
+ if (!isNaN(index) && (index in keys)) {
+ id = keys[index];
+ }
+ }
+ return id;
+ },
+
+ /**
+ * Method: indexFromCharCode
+ * Given a character code for one of the UTFGrid "grid" characters,
+ * resolve the integer index for the feature id in the UTFGrid "keys"
+ * array.
+ *
+ * Parameters:
+ * charCode - {Integer}
+ *
+ * Returns:
+ * {Integer} Index for the feature id from the keys array.
+ */
+ indexFromCharCode: function(charCode) {
+ if (charCode >= 93) {
+ charCode--;
+ }
+ if (charCode >= 35) {
+ charCode --;
+ }
+ return charCode - 32;
+ },
+
+ /**
+ * Method: parseData
+ * Parse the JSON from a request
+ *
+ * Parameters:
+ * str - {String} UTFGrid as a JSON string.
+ *
+ * Returns:
+ * {Object} parsed javascript data
+ */
+ parseData: function(str) {
+ if (!this.format) {
+ this.format = new OpenLayers.Format.JSON();
+ }
+ this.json = this.format.read(str);
+ },
+
+ /**
+ * Method: clear
+ * Delete data stored with this tile.
+ */
+ clear: function() {
+ this.json = null;
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.UTFGrid"
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/TileManager.js b/misc/openlayers/lib/OpenLayers/TileManager.js
new file mode 100644
index 0000000..5ae1666
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/TileManager.js
@@ -0,0 +1,462 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/Layer/Grid.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.TileManager
+ * Provides queueing of image requests and caching of image elements.
+ *
+ * Queueing avoids unnecessary image requests while changing zoom levels
+ * quickly, and helps improve dragging performance on mobile devices that show
+ * a lag in dragging when loading of new images starts. <zoomDelay> and
+ * <moveDelay> are the configuration options to control this behavior.
+ *
+ * Caching avoids setting the src on image elements for images that have already
+ * been used. Several maps can share a TileManager instance, in which case each
+ * map gets its own tile queue, but all maps share the same tile cache.
+ */
+OpenLayers.TileManager = OpenLayers.Class({
+
+ /**
+ * APIProperty: cacheSize
+ * {Number} Number of image elements to keep referenced in this instance's
+ * cache for fast reuse. Default is 256.
+ */
+ cacheSize: 256,
+
+ /**
+ * APIProperty: tilesPerFrame
+ * {Number} Number of queued tiles to load per frame (see <frameDelay>).
+ * Default is 2.
+ */
+ tilesPerFrame: 2,
+
+ /**
+ * APIProperty: frameDelay
+ * {Number} Delay between tile loading frames (see <tilesPerFrame>) in
+ * milliseconds. Default is 16.
+ */
+ frameDelay: 16,
+
+ /**
+ * APIProperty: moveDelay
+ * {Number} Delay in milliseconds after a map's move event before loading
+ * tiles. Default is 100.
+ */
+ moveDelay: 100,
+
+ /**
+ * APIProperty: zoomDelay
+ * {Number} Delay in milliseconds after a map's zoomend event before loading
+ * tiles. Default is 200.
+ */
+ zoomDelay: 200,
+
+ /**
+ * Property: maps
+ * {Array(<OpenLayers.Map>)} The maps to manage tiles on.
+ */
+ maps: null,
+
+ /**
+ * Property: tileQueueId
+ * {Object} The ids of the <drawTilesFromQueue> loop, keyed by map id.
+ */
+ tileQueueId: null,
+
+ /**
+ * Property: tileQueue
+ * {Object(Array(<OpenLayers.Tile>))} Tiles queued for drawing, keyed by
+ * map id.
+ */
+ tileQueue: null,
+
+ /**
+ * Property: tileCache
+ * {Object} Cached image elements, keyed by URL.
+ */
+ tileCache: null,
+
+ /**
+ * Property: tileCacheIndex
+ * {Array(String)} URLs of cached tiles. First entry is the least recently
+ * used.
+ */
+ tileCacheIndex: null,
+
+ /**
+ * Constructor: OpenLayers.TileManager
+ * Constructor for a new <OpenLayers.TileManager> instance.
+ *
+ * Parameters:
+ * options - {Object} Configuration for this instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.maps = [];
+ this.tileQueueId = {};
+ this.tileQueue = {};
+ this.tileCache = {};
+ this.tileCacheIndex = [];
+ },
+
+ /**
+ * Method: addMap
+ * Binds this instance to a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ addMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ this.maps.push(map);
+ this.tileQueue[map.id] = [];
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.addLayer({layer: map.layers[i]});
+ }
+ map.events.on({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: removeMap
+ * Unbinds this instance from a map
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ removeMap: function(map) {
+ if (this._destroyed || !OpenLayers.Layer.Grid) {
+ return;
+ }
+ window.clearTimeout(this.tileQueueId[map.id]);
+ if (map.layers) {
+ for (var i=0, ii=map.layers.length; i<ii; ++i) {
+ this.removeLayer({layer: map.layers[i]});
+ }
+ }
+ if (map.events) {
+ map.events.un({
+ move: this.move,
+ zoomend: this.zoomEnd,
+ changelayer: this.changeLayer,
+ addlayer: this.addLayer,
+ preremovelayer: this.removeLayer,
+ scope: this
+ });
+ }
+ delete this.tileQueue[map.id];
+ delete this.tileQueueId[map.id];
+ OpenLayers.Util.removeItem(this.maps, map);
+ },
+
+ /**
+ * Method: move
+ * Handles the map's move event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ move: function(evt) {
+ this.updateTimeout(evt.object, this.moveDelay, true);
+ },
+
+ /**
+ * Method: zoomEnd
+ * Handles the map's zoomEnd event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ zoomEnd: function(evt) {
+ this.updateTimeout(evt.object, this.zoomDelay);
+ },
+
+ /**
+ * Method: changeLayer
+ * Handles the map's changeLayer event
+ *
+ * Parameters:
+ * evt - {Object} Listener argument
+ */
+ changeLayer: function(evt) {
+ if (evt.property === 'visibility' || evt.property === 'params') {
+ this.updateTimeout(evt.object, 0);
+ }
+ },
+
+ /**
+ * Method: addLayer
+ * Handles the map's addlayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ layer.events.on({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.addTile({tile: tile});
+ if (tile.url && !tile.imgDiv) {
+ this.manageTileCache({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: removeLayer
+ * Handles the map's preremovelayer event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ removeLayer: function(evt) {
+ var layer = evt.layer;
+ if (layer instanceof OpenLayers.Layer.Grid) {
+ this.clearTileQueue({object: layer});
+ if (layer.events) {
+ layer.events.un({
+ addtile: this.addTile,
+ retile: this.clearTileQueue,
+ scope: this
+ });
+ }
+ if (layer.grid) {
+ var i, j, tile;
+ for (i=layer.grid.length-1; i>=0; --i) {
+ for (j=layer.grid[i].length-1; j>=0; --j) {
+ tile = layer.grid[i][j];
+ this.unloadTile({object: tile});
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Method: updateTimeout
+ * Applies the <moveDelay> or <zoomDelay> to the <drawTilesFromQueue> loop,
+ * and schedules more queue processing after <frameDelay> if there are still
+ * tiles in the queue.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>} The map to update the timeout for
+ * delay - {Number} The delay to apply
+ * nice - {Boolean} If true, the timeout function will only be created if
+ * the tilequeue is not empty. This is used by the move handler to
+ * avoid impacts on dragging performance. For other events, the tile
+ * queue may not be populated yet, so we need to set the timer
+ * regardless of the queue size.
+ */
+ updateTimeout: function(map, delay, nice) {
+ window.clearTimeout(this.tileQueueId[map.id]);
+ var tileQueue = this.tileQueue[map.id];
+ if (!nice || tileQueue.length) {
+ this.tileQueueId[map.id] = window.setTimeout(
+ OpenLayers.Function.bind(function() {
+ this.drawTilesFromQueue(map);
+ if (tileQueue.length) {
+ this.updateTimeout(map, this.frameDelay);
+ }
+ }, this), delay
+ );
+ }
+ },
+
+ /**
+ * Method: addTile
+ * Listener for the layer's addtile event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ addTile: function(evt) {
+ if (evt.tile instanceof OpenLayers.Tile.Image) {
+ evt.tile.events.on({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ } else {
+ // Layer has the wrong tile type, so don't handle it any longer
+ this.removeLayer({layer: evt.tile.layer});
+ }
+ },
+
+ /**
+ * Method: unloadTile
+ * Listener for the tile's unload event
+ *
+ * Parameters:
+ * evt - {Object} The listener argument
+ */
+ unloadTile: function(evt) {
+ var tile = evt.object;
+ tile.events.un({
+ beforedraw: this.queueTileDraw,
+ beforeload: this.manageTileCache,
+ loadend: this.addToCache,
+ unload: this.unloadTile,
+ scope: this
+ });
+ OpenLayers.Util.removeItem(this.tileQueue[tile.layer.map.id], tile);
+ },
+
+ /**
+ * Method: queueTileDraw
+ * Adds a tile to the queue that will draw it.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforedraw event
+ */
+ queueTileDraw: function(evt) {
+ var tile = evt.object;
+ var queued = false;
+ var layer = tile.layer;
+ var url = layer.getURL(tile.bounds);
+ var img = this.tileCache[url];
+ if (img && img.className !== 'olTileImage') {
+ // cached image no longer valid, e.g. because we're olTileReplacing
+ delete this.tileCache[url];
+ OpenLayers.Util.removeItem(this.tileCacheIndex, url);
+ img = null;
+ }
+ // queue only if image with same url not cached already
+ if (layer.url && (layer.async || !img)) {
+ // add to queue only if not in queue already
+ var tileQueue = this.tileQueue[layer.map.id];
+ if (!~OpenLayers.Util.indexOf(tileQueue, tile)) {
+ tileQueue.push(tile);
+ }
+ queued = true;
+ }
+ return !queued;
+ },
+
+ /**
+ * Method: drawTilesFromQueue
+ * Draws tiles from the tileQueue, and unqueues the tiles
+ */
+ drawTilesFromQueue: function(map) {
+ var tileQueue = this.tileQueue[map.id];
+ var limit = this.tilesPerFrame;
+ var animating = map.zoomTween && map.zoomTween.playing;
+ while (!animating && tileQueue.length && limit) {
+ tileQueue.shift().draw(true);
+ --limit;
+ }
+ },
+
+ /**
+ * Method: manageTileCache
+ * Adds, updates, removes and fetches cache entries.
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the tile's beforeload event
+ */
+ manageTileCache: function(evt) {
+ var tile = evt.object;
+ var img = this.tileCache[tile.url];
+ if (img) {
+ // if image is on its layer's backbuffer, remove it from backbuffer
+ if (img.parentNode &&
+ OpenLayers.Element.hasClass(img.parentNode, 'olBackBuffer')) {
+ img.parentNode.removeChild(img);
+ img.id = null;
+ }
+ // only use image from cache if it is not on a layer already
+ if (!img.parentNode) {
+ img.style.visibility = 'hidden';
+ img.style.opacity = 0;
+ tile.setImage(img);
+ // LRU - move tile to the end of the array to mark it as the most
+ // recently used
+ OpenLayers.Util.removeItem(this.tileCacheIndex, tile.url);
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: addToCache
+ *
+ * Parameters:
+ * evt - {Object} Listener argument for the tile's loadend event
+ */
+ addToCache: function(evt) {
+ var tile = evt.object;
+ if (!this.tileCache[tile.url]) {
+ if (!OpenLayers.Element.hasClass(tile.imgDiv, 'olImageLoadError')) {
+ if (this.tileCacheIndex.length >= this.cacheSize) {
+ delete this.tileCache[this.tileCacheIndex[0]];
+ this.tileCacheIndex.shift();
+ }
+ this.tileCache[tile.url] = tile.imgDiv;
+ this.tileCacheIndex.push(tile.url);
+ }
+ }
+ },
+
+ /**
+ * Method: clearTileQueue
+ * Clears the tile queue from tiles of a specific layer
+ *
+ * Parameters:
+ * evt - {Object} Listener argument of the layer's retile event
+ */
+ clearTileQueue: function(evt) {
+ var layer = evt.object;
+ var tileQueue = this.tileQueue[layer.map.id];
+ for (var i=tileQueue.length-1; i>=0; --i) {
+ if (tileQueue[i].layer === layer) {
+ tileQueue.splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ for (var i=this.maps.length-1; i>=0; --i) {
+ this.removeMap(this.maps[i]);
+ }
+ this.maps = null;
+ this.tileQueue = null;
+ this.tileQueueId = null;
+ this.tileCache = null;
+ this.tileCacheIndex = null;
+ this._destroyed = true;
+ }
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/Tween.js b/misc/openlayers/lib/OpenLayers/Tween.js
new file mode 100644
index 0000000..0432dad
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Tween.js
@@ -0,0 +1,361 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+/**
+ * Namespace: OpenLayers.Tween
+ */
+OpenLayers.Tween = OpenLayers.Class({
+
+ /**
+ * APIProperty: easing
+ * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
+ * Defaultly set to OpenLayers.Easing.Expo.easeOut
+ */
+ easing: null,
+
+ /**
+ * APIProperty: begin
+ * {Object} Values to start the animation with
+ */
+ begin: null,
+
+ /**
+ * APIProperty: finish
+ * {Object} Values to finish the animation with
+ */
+ finish: null,
+
+ /**
+ * APIProperty: duration
+ * {int} duration of the tween (number of steps)
+ */
+ duration: null,
+
+ /**
+ * APIProperty: callbacks
+ * {Object} An object with start, eachStep and done properties whose values
+ * are functions to be call during the animation. They are passed the
+ * current computed value as argument.
+ */
+ callbacks: null,
+
+ /**
+ * Property: time
+ * {int} Step counter
+ */
+ time: null,
+
+ /**
+ * APIProperty: minFrameRate
+ * {Number} The minimum framerate for animations in frames per second. After
+ * each step, the time spent in the animation is compared to the calculated
+ * time at this frame rate. If the animation runs longer than the calculated
+ * time, the next step is skipped. Default is 30.
+ */
+ minFrameRate: null,
+
+ /**
+ * Property: startTime
+ * {Number} The timestamp of the first execution step. Used for skipping
+ * frames
+ */
+ startTime: null,
+
+ /**
+ * Property: animationId
+ * {int} Loop id returned by OpenLayers.Animation.start
+ */
+ animationId: null,
+
+ /**
+ * Property: playing
+ * {Boolean} Tells if the easing is currently playing
+ */
+ playing: false,
+
+ /**
+ * Constructor: OpenLayers.Tween
+ * Creates a Tween.
+ *
+ * Parameters:
+ * easing - {<OpenLayers.Easing>(Function)} easing function method to use
+ */
+ initialize: function(easing) {
+ this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
+ },
+
+ /**
+ * APIMethod: start
+ * Plays the Tween, and calls the callback method on each step
+ *
+ * Parameters:
+ * begin - {Object} values to start the animation with
+ * finish - {Object} values to finish the animation with
+ * duration - {int} duration of the tween (number of steps)
+ * options - {Object} hash of options (callbacks (start, eachStep, done),
+ * minFrameRate)
+ */
+ start: function(begin, finish, duration, options) {
+ this.playing = true;
+ this.begin = begin;
+ this.finish = finish;
+ this.duration = duration;
+ this.callbacks = options.callbacks;
+ this.minFrameRate = options.minFrameRate || 30;
+ this.time = 0;
+ this.startTime = new Date().getTime();
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ if (this.callbacks && this.callbacks.start) {
+ this.callbacks.start.call(this, this.begin);
+ }
+ this.animationId = OpenLayers.Animation.start(
+ OpenLayers.Function.bind(this.play, this)
+ );
+ },
+
+ /**
+ * APIMethod: stop
+ * Stops the Tween, and calls the done callback
+ * Doesn't do anything if animation is already finished
+ */
+ stop: function() {
+ if (!this.playing) {
+ return;
+ }
+
+ if (this.callbacks && this.callbacks.done) {
+ this.callbacks.done.call(this, this.finish);
+ }
+ OpenLayers.Animation.stop(this.animationId);
+ this.animationId = null;
+ this.playing = false;
+ },
+
+ /**
+ * Method: play
+ * Calls the appropriate easing method
+ */
+ play: function() {
+ var value = {};
+ for (var i in this.begin) {
+ var b = this.begin[i];
+ var f = this.finish[i];
+ if (b == null || f == null || isNaN(b) || isNaN(f)) {
+ throw new TypeError('invalid value for Tween');
+ }
+
+ var c = f - b;
+ value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
+ }
+ this.time++;
+
+ if (this.callbacks && this.callbacks.eachStep) {
+ // skip frames if frame rate drops below threshold
+ if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
+ this.callbacks.eachStep.call(this, value);
+ }
+ }
+
+ if (this.time > this.duration) {
+ this.stop();
+ }
+ },
+
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Tween"
+});
+
+/**
+ * Namespace: OpenLayers.Easing
+ *
+ * Credits:
+ * Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
+ */
+OpenLayers.Easing = {
+ /**
+ * Create empty functions for all easing methods.
+ */
+ CLASS_NAME: "OpenLayers.Easing"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Linear
+ */
+OpenLayers.Easing.Linear = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ return c*t/d + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Linear"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Expo
+ */
+OpenLayers.Easing.Expo = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if (t==0) return b;
+ if (t==d) return b+c;
+ if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Expo"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Quad
+ */
+OpenLayers.Easing.Quad = {
+
+ /**
+ * Function: easeIn
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeIn: function(t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+
+ /**
+ * Function: easeOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeOut: function(t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+
+ /**
+ * Function: easeInOut
+ *
+ * Parameters:
+ * t - {Float} time
+ * b - {Float} beginning position
+ * c - {Float} total change
+ * d - {Float} duration of the transition
+ *
+ * Returns:
+ * {Float}
+ */
+ easeInOut: function(t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+
+ CLASS_NAME: "OpenLayers.Easing.Quad"
+};
diff --git a/misc/openlayers/lib/OpenLayers/Util.js b/misc/openlayers/lib/OpenLayers/Util.js
new file mode 100644
index 0000000..f659d3e
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Util.js
@@ -0,0 +1,1773 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/BaseTypes/LonLat.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: Util
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+
+/**
+ * Function: getElement
+ * This is the old $() from prototype
+ *
+ * Parameters:
+ * e - {String or DOMElement or Window}
+ *
+ * Returns:
+ * {Array(DOMElement) or DOMElement}
+ */
+OpenLayers.Util.getElement = function() {
+ var elements = [];
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ if (arguments.length == 1) {
+ return element;
+ }
+ elements.push(element);
+ }
+ return elements;
+};
+
+/**
+ * Function: isElement
+ * A cross-browser implementation of "e instanceof Element".
+ *
+ * Parameters:
+ * o - {Object} The object to test.
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.isElement = function(o) {
+ return !!(o && o.nodeType === 1);
+};
+
+/**
+ * Function: isArray
+ * Tests that the provided object is an array.
+ * This test handles the cross-IFRAME case not caught
+ * by "a instanceof Array" and should be used instead.
+ *
+ * Parameters:
+ * a - {Object} the object test.
+ *
+ * Returns:
+ * {Boolean} true if the object is an array.
+ */
+OpenLayers.Util.isArray = function(a) {
+ return (Object.prototype.toString.call(a) === '[object Array]');
+};
+
+/**
+ * Function: removeItem
+ * Remove an object from an array. Iterates through the array
+ * to find the item, then removes it.
+ *
+ * Parameters:
+ * array - {Array}
+ * item - {Object}
+ *
+ * Returns:
+ * {Array} A reference to the array
+ */
+OpenLayers.Util.removeItem = function(array, item) {
+ for(var i = array.length - 1; i >= 0; i--) {
+ if(array[i] == item) {
+ array.splice(i,1);
+ //break;more than once??
+ }
+ }
+ return array;
+};
+
+/**
+ * Function: indexOf
+ * Seems to exist already in FF, but not in MOZ.
+ *
+ * Parameters:
+ * array - {Array}
+ * obj - {*}
+ *
+ * Returns:
+ * {Integer} The index at which the first object was found in the array.
+ * If not found, returns -1.
+ */
+OpenLayers.Util.indexOf = function(array, obj) {
+ // use the build-in function if available.
+ if (typeof array.indexOf == "function") {
+ return array.indexOf(obj);
+ } else {
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (array[i] == obj) {
+ return i;
+ }
+ }
+ return -1;
+ }
+};
+
+
+/**
+ * Property: dotless
+ * {RegExp}
+ * Compiled regular expression to match dots ("."). This is used for replacing
+ * dots in identifiers. Because object identifiers are frequently used for
+ * DOM element identifiers by the library, we avoid using dots to make for
+ * more sensible CSS selectors.
+ *
+ * TODO: Use a module pattern to avoid bloating the API with stuff like this.
+ */
+OpenLayers.Util.dotless = /\./g;
+
+/**
+ * Function: modifyDOMElement
+ *
+ * Modifies many properties of a DOM element all at once. Passing in
+ * null to an individual parameter will avoid setting the attribute.
+ *
+ * Parameters:
+ * element - {DOMElement} DOM element to modify.
+ * id - {String} The element id attribute to set. Note that dots (".") will be
+ * replaced with underscore ("_") in setting the element id.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * position - {String} The position attribute. eg: absolute,
+ * relative, etc.
+ * border - {String} The style.border attribute. eg:
+ * solid black 2px
+ * overflow - {String} The style.overview attribute.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position,
+ border, overflow, opacity) {
+
+ if (id) {
+ element.id = id.replace(OpenLayers.Util.dotless, "_");
+ }
+ if (px) {
+ element.style.left = px.x + "px";
+ element.style.top = px.y + "px";
+ }
+ if (sz) {
+ element.style.width = sz.w + "px";
+ element.style.height = sz.h + "px";
+ }
+ if (position) {
+ element.style.position = position;
+ }
+ if (border) {
+ element.style.border = border;
+ }
+ if (overflow) {
+ element.style.overflow = overflow;
+ }
+ if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
+ element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
+ element.style.opacity = opacity;
+ } else if (parseFloat(opacity) == 1.0) {
+ element.style.filter = '';
+ element.style.opacity = '';
+ }
+};
+
+/**
+ * Function: createDiv
+ * Creates a new div and optionally set some standard attributes.
+ * Null may be passed to each parameter if you do not wish to
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
+ *
+ * Parameters:
+ * id - {String} An identifier for this element. If no id is
+ * passed an identifier will be created
+ * automatically. Note that dots (".") will be replaced with
+ * underscore ("_") when generating ids.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} A url pointing to an image to use as a
+ * background image.
+ * position - {String} The style.position value. eg: absolute,
+ * relative etc.
+ * border - {String} The the style.border value.
+ * eg: 2px solid black
+ * overflow - {String} The style.overflow value. Eg. hidden
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with the specified attributes.
+ */
+OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position,
+ border, overflow, opacity) {
+
+ var dom = document.createElement('div');
+
+ if (imgURL) {
+ dom.style.backgroundImage = 'url(' + imgURL + ')';
+ }
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "absolute";
+ }
+ OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position,
+ border, overflow, opacity);
+
+ return dom;
+};
+
+/**
+ * Function: createImage
+ * Creates an img element with specific attribute values.
+ *
+ * Parameters:
+ * id - {String} The id field for the img. If none assigned one will be
+ * automatically generated.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ * OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ * OpenLayers.Size or an object with a
+ * 'w' and 'h' properties.
+ * imgURL - {String} The url to use as the image source.
+ * position - {String} The style.position value.
+ * border - {String} The border to place around the image.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Image created with the specified attributes.
+ */
+OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
+ opacity, delayDisplay) {
+
+ var image = document.createElement("img");
+
+ //set generic properties
+ if (!id) {
+ id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+ }
+ if (!position) {
+ position = "relative";
+ }
+ OpenLayers.Util.modifyDOMElement(image, id, px, sz, position,
+ border, null, opacity);
+
+ if (delayDisplay) {
+ image.style.display = "none";
+ function display() {
+ image.style.display = "";
+ OpenLayers.Event.stopObservingElement(image);
+ }
+ OpenLayers.Event.observe(image, "load", display);
+ OpenLayers.Event.observe(image, "error", display);
+ }
+
+ //set special properties
+ image.style.alt = id;
+ image.galleryImg = "no";
+ if (imgURL) {
+ image.src = imgURL;
+ }
+
+ return image;
+};
+
+/**
+ * Property: IMAGE_RELOAD_ATTEMPTS
+ * {Integer} How many times should we try to reload an image before giving up?
+ * Default is 0
+ */
+OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
+
+/**
+ * Property: alphaHackNeeded
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHackNeeded = null;
+
+/**
+ * Function: alphaHack
+ * Checks whether it's necessary (and possible) to use the png alpha
+ * hack which allows alpha transparency for png images under Internet
+ * Explorer.
+ *
+ * Returns:
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHack = function() {
+ if (OpenLayers.Util.alphaHackNeeded == null) {
+ var arVersion = navigator.appVersion.split("MSIE");
+ var version = parseFloat(arVersion[1]);
+ var filter = false;
+
+ // IEs4Lin dies when trying to access document.body.filters, because
+ // the property is there, but requires a DLL that can't be provided. This
+ // means that we need to wrap this in a try/catch so that this can
+ // continue.
+
+ try {
+ filter = !!(document.body.filters);
+ } catch (e) {}
+
+ OpenLayers.Util.alphaHackNeeded = (filter &&
+ (version >= 5.5) && (version < 7));
+ }
+ return OpenLayers.Util.alphaHackNeeded;
+};
+
+/**
+ * Function: modifyAlphaImageDiv
+ *
+ * Parameters:
+ * div - {DOMElement} Div containing Alpha-adjusted Image
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL,
+ position, border, sizing,
+ opacity) {
+
+ OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+ null, null, opacity);
+
+ var img = div.childNodes[0];
+
+ if (imgURL) {
+ img.src = imgURL;
+ }
+ OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz,
+ "relative", border);
+
+ if (OpenLayers.Util.alphaHack()) {
+ if(div.style.display != "none") {
+ div.style.display = "inline-block";
+ }
+ if (sizing == null) {
+ sizing = "scale";
+ }
+
+ div.style.filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img.src + "', " +
+ "sizingMethod='" + sizing + "')";
+ if (parseFloat(div.style.opacity) >= 0.0 &&
+ parseFloat(div.style.opacity) < 1.0) {
+ div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
+ }
+
+ img.style.filter = "alpha(opacity=0)";
+ }
+};
+
+/**
+ * Function: createAlphaImageDiv
+ *
+ * Parameters:
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ * a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ * a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ * loaded.
+ *
+ * Returns:
+ * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is
+ * needed for transparency in IE, it is added.
+ */
+OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL,
+ position, border, sizing,
+ opacity, delayDisplay) {
+
+ var div = OpenLayers.Util.createDiv();
+ var img = OpenLayers.Util.createImage(null, null, null, null, null, null,
+ null, delayDisplay);
+ img.className = "olAlphaImg";
+ div.appendChild(img);
+
+ OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position,
+ border, sizing, opacity);
+
+ return div;
+};
+
+
+/**
+ * Function: upperCaseObject
+ * Creates a new hashtable and copies over all the keys from the
+ * passed-in object, but storing them under an uppercased
+ * version of the key at which they were stored.
+ *
+ * Parameters:
+ * object - {Object}
+ *
+ * Returns:
+ * {Object} A new Object with all the same keys but uppercased
+ */
+OpenLayers.Util.upperCaseObject = function (object) {
+ var uObject = {};
+ for (var key in object) {
+ uObject[key.toUpperCase()] = object[key];
+ }
+ return uObject;
+};
+
+/**
+ * Function: applyDefaults
+ * Takes an object and copies any properties that don't exist from
+ * another properties, by analogy with OpenLayers.Util.extend() from
+ * Prototype.js.
+ *
+ * Parameters:
+ * to - {Object} The destination object.
+ * from - {Object} The source object. Any properties of this object that
+ * are undefined in the to object will be set on the to object.
+ *
+ * Returns:
+ * {Object} A reference to the to object. Note that the to argument is modified
+ * in place and returned by this function.
+ */
+OpenLayers.Util.applyDefaults = function (to, from) {
+ to = to || {};
+ /*
+ * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+ * prototype object" when calling hawOwnProperty if the source object is an
+ * instance of window.Event.
+ */
+ var fromIsEvt = typeof window.Event == "function"
+ && from instanceof window.Event;
+
+ for (var key in from) {
+ if (to[key] === undefined ||
+ (!fromIsEvt && from.hasOwnProperty
+ && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
+ to[key] = from[key];
+ }
+ }
+ /**
+ * IE doesn't include the toString property when iterating over an object's
+ * properties with the for(property in object) syntax. Explicitly check if
+ * the source has its own toString property.
+ */
+ if(!fromIsEvt && from && from.hasOwnProperty
+ && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
+ to.toString = from.toString;
+ }
+
+ return to;
+};
+
+/**
+ * Function: getParameterString
+ *
+ * Parameters:
+ * params - {Object}
+ *
+ * Returns:
+ * {String} A concatenation of the properties of an object in
+ * http parameter notation.
+ * (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
+ * If a parameter is actually a list, that parameter will then
+ * be set to a comma-seperated list of values (foo,bar) instead
+ * of being URL escaped (foo%3Abar).
+ */
+OpenLayers.Util.getParameterString = function(params) {
+ var paramsArray = [];
+
+ for (var key in params) {
+ var value = params[key];
+ if ((value != null) && (typeof value != 'function')) {
+ var encodedValue;
+ if (typeof value == 'object' && value.constructor == Array) {
+ /* value is an array; encode items and separate with "," */
+ var encodedItemArray = [];
+ var item;
+ for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
+ item = value[itemIndex];
+ encodedItemArray.push(encodeURIComponent(
+ (item === null || item === undefined) ? "" : item)
+ );
+ }
+ encodedValue = encodedItemArray.join(",");
+ }
+ else {
+ /* value is a string; simply encode */
+ encodedValue = encodeURIComponent(value);
+ }
+ paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
+ }
+ }
+
+ return paramsArray.join("&");
+};
+
+/**
+ * Function: urlAppend
+ * Appends a parameter string to a url. This function includes the logic for
+ * using the appropriate character (none, & or ?) to append to the url before
+ * appending the param string.
+ *
+ * Parameters:
+ * url - {String} The url to append to
+ * paramStr - {String} The param string to append
+ *
+ * Returns:
+ * {String} The new url
+ */
+OpenLayers.Util.urlAppend = function(url, paramStr) {
+ var newUrl = url;
+ if(paramStr) {
+ var parts = (url + " ").split(/[?&]/);
+ newUrl += (parts.pop() === " " ?
+ paramStr :
+ parts.length ? "&" + paramStr : "?" + paramStr);
+ }
+ return newUrl;
+};
+
+/**
+ * Function: getImagesLocation
+ *
+ * Returns:
+ * {String} The fully formatted image location string
+ */
+OpenLayers.Util.getImagesLocation = function() {
+ return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
+};
+
+/**
+ * Function: getImageLocation
+ *
+ * Returns:
+ * {String} The fully formatted location string for a specified image
+ */
+OpenLayers.Util.getImageLocation = function(image) {
+ return OpenLayers.Util.getImagesLocation() + image;
+};
+
+
+/**
+ * Function: Try
+ * Execute functions until one of them doesn't throw an error.
+ * Capitalized because "try" is a reserved word in JavaScript.
+ * Taken directly from OpenLayers.Util.Try()
+ *
+ * Parameters:
+ * [*] - {Function} Any number of parameters may be passed to Try()
+ * It will attempt to execute each of them until one of them
+ * successfully executes.
+ * If none executes successfully, returns null.
+ *
+ * Returns:
+ * {*} The value returned by the first successfully executed function.
+ */
+OpenLayers.Util.Try = function() {
+ var returnValue = null;
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var lambda = arguments[i];
+ try {
+ returnValue = lambda();
+ break;
+ } catch (e) {}
+ }
+
+ return returnValue;
+};
+
+/**
+ * Function: getXmlNodeValue
+ *
+ * Parameters:
+ * node - {XMLNode}
+ *
+ * Returns:
+ * {String} The text value of the given node, without breaking in firefox or IE
+ */
+OpenLayers.Util.getXmlNodeValue = function(node) {
+ var val = null;
+ OpenLayers.Util.Try(
+ function() {
+ val = node.text;
+ if (!val) {
+ val = node.textContent;
+ }
+ if (!val) {
+ val = node.firstChild.nodeValue;
+ }
+ },
+ function() {
+ val = node.textContent;
+ });
+ return val;
+};
+
+/**
+ * Function: mouseLeft
+ *
+ * Parameters:
+ * evt - {Event}
+ * div - {HTMLDivElement}
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.mouseLeft = function (evt, div) {
+ // start with the element to which the mouse has moved
+ var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
+ // walk up the DOM tree.
+ while (target != div && target != null) {
+ target = target.parentNode;
+ }
+ // if the target we stop at isn't the div, then we've left the div.
+ return (target != div);
+};
+
+/**
+ * Property: precision
+ * {Number} The number of significant digits to retain to avoid
+ * floating point precision errors.
+ *
+ * We use 14 as a "safe" default because, although IEEE 754 double floats
+ * (standard on most modern operating systems) support up to about 16
+ * significant digits, 14 significant digits are sufficient to represent
+ * sub-millimeter accuracy in any coordinate system that anyone is likely to
+ * use with OpenLayers.
+ *
+ * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
+ * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
+ * with certain projections, e.g. spherical Mercator.
+ *
+ */
+OpenLayers.Util.DEFAULT_PRECISION = 14;
+
+/**
+ * Function: toFloat
+ * Convenience method to cast an object to a Number, rounded to the
+ * desired floating point precision.
+ *
+ * Parameters:
+ * number - {Number} The number to cast and round.
+ * precision - {Number} An integer suitable for use with
+ * Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
+ * If set to 0, no rounding is performed.
+ *
+ * Returns:
+ * {Number} The cast, rounded number.
+ */
+OpenLayers.Util.toFloat = function (number, precision) {
+ if (precision == null) {
+ precision = OpenLayers.Util.DEFAULT_PRECISION;
+ }
+ if (typeof number !== "number") {
+ number = parseFloat(number);
+ }
+ return precision === 0 ? number :
+ parseFloat(number.toPrecision(precision));
+};
+
+/**
+ * Function: rad
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
+
+/**
+ * Function: deg
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
+
+/**
+ * Property: VincentyConstants
+ * {Object} Constants for Vincenty functions.
+ */
+OpenLayers.Util.VincentyConstants = {
+ a: 6378137,
+ b: 6356752.3142,
+ f: 1/298.257223563
+};
+
+/**
+ * APIFunction: distVincenty
+ * Given two objects representing points with geographic coordinates, this
+ * calculates the distance between those points on the surface of an
+ * ellipsoid.
+ *
+ * Parameters:
+ * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ *
+ * Returns:
+ * {Float} The distance (in km) between the two input points as measured on an
+ * ellipsoid. Note that the input point objects must be in geographic
+ * coordinates (decimal degrees) and the return distance is in kilometers.
+ */
+OpenLayers.Util.distVincenty = function(p1, p2) {
+ var ct = OpenLayers.Util.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var L = OpenLayers.Util.rad(p2.lon - p1.lon);
+ var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
+ var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
+ var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
+ var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
+ var lambda = L, lambdaP = 2*Math.PI;
+ var iterLimit = 20;
+ while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
+ var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
+ var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
+ (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
+ if (sinSigma==0) {
+ return 0; // co-incident points
+ }
+ var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
+ var sigma = Math.atan2(sinSigma, cosSigma);
+ var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
+ var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
+ var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ lambdaP = lambda;
+ lambda = L + (1-C) * f * Math.sin(alpha) *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+ }
+ if (iterLimit==0) {
+ return NaN; // formula failed to converge
+ }
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ var s = b*A*(sigma-deltaSigma);
+ var d = s.toFixed(3)/1000; // round to 1mm precision
+ return d;
+};
+
+/**
+ * APIFunction: destinationVincenty
+ * Calculate destination point given start point lat/long (numeric degrees),
+ * bearing (numeric degrees) & distance (in m).
+ * Adapted from Chris Veness work, see
+ * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
+ *
+ * Parameters:
+ * lonlat - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
+ * properties) The start point.
+ * brng - {Float} The bearing (degrees).
+ * dist - {Float} The ground distance (meters).
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The destination point.
+ */
+OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
+ var u = OpenLayers.Util;
+ var ct = u.VincentyConstants;
+ var a = ct.a, b = ct.b, f = ct.f;
+
+ var lon1 = lonlat.lon;
+ var lat1 = lonlat.lat;
+
+ var s = dist;
+ var alpha1 = u.rad(brng);
+ var sinAlpha1 = Math.sin(alpha1);
+ var cosAlpha1 = Math.cos(alpha1);
+
+ var tanU1 = (1-f) * Math.tan(u.rad(lat1));
+ var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
+ var sigma1 = Math.atan2(tanU1, cosAlpha1);
+ var sinAlpha = cosU1 * sinAlpha1;
+ var cosSqAlpha = 1 - sinAlpha*sinAlpha;
+ var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+ var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+ var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+
+ var sigma = s / (b*A), sigmaP = 2*Math.PI;
+ while (Math.abs(sigma-sigmaP) > 1e-12) {
+ var cos2SigmaM = Math.cos(2*sigma1 + sigma);
+ var sinSigma = Math.sin(sigma);
+ var cosSigma = Math.cos(sigma);
+ var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+ B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+ sigmaP = sigma;
+ sigma = s / (b*A) + deltaSigma;
+ }
+
+ var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
+ var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
+ (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+ var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
+ var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+ var L = lambda - (1-C) * f * sinAlpha *
+ (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+
+ var revAz = Math.atan2(sinAlpha, -tmp); // final bearing
+
+ return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
+};
+
+/**
+ * Function: getParameters
+ * Parse the parameters from a URL or from the current page itself into a
+ * JavaScript Object. Note that parameter values with commas are separated
+ * out into an Array.
+ *
+ * Parameters:
+ * url - {String} Optional url used to extract the query string.
+ * If url is null or is not supplied, query string is taken
+ * from the page location.
+ * options - {Object} Additional options. Optional.
+ *
+ * Valid options:
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object of key/value pairs from the query string.
+ */
+OpenLayers.Util.getParameters = function(url, options) {
+ options = options || {};
+ // if no url specified, take it from the location bar
+ url = (url === null || url === undefined) ? window.location.href : url;
+
+ //parse out parameters portion of url string
+ var paramsString = "";
+ if (OpenLayers.String.contains(url, '?')) {
+ var start = url.indexOf('?') + 1;
+ var end = OpenLayers.String.contains(url, "#") ?
+ url.indexOf('#') : url.length;
+ paramsString = url.substring(start, end);
+ }
+
+ var parameters = {};
+ var pairs = paramsString.split(/[&;]/);
+ for(var i=0, len=pairs.length; i<len; ++i) {
+ var keyValue = pairs[i].split('=');
+ if (keyValue[0]) {
+
+ var key = keyValue[0];
+ try {
+ key = decodeURIComponent(key);
+ } catch (err) {
+ key = unescape(key);
+ }
+
+ // being liberal by replacing "+" with " "
+ var value = (keyValue[1] || '').replace(/\+/g, " ");
+
+ try {
+ value = decodeURIComponent(value);
+ } catch (err) {
+ value = unescape(value);
+ }
+
+ // follow OGC convention of comma delimited values
+ if (options.splitArgs !== false) {
+ value = value.split(",");
+ }
+
+ //if there's only one value, do not return as array
+ if (value.length == 1) {
+ value = value[0];
+ }
+
+ parameters[key] = value;
+ }
+ }
+ return parameters;
+};
+
+/**
+ * Property: lastSeqID
+ * {Integer} The ever-incrementing count variable.
+ * Used for generating unique ids.
+ */
+OpenLayers.Util.lastSeqID = 0;
+
+/**
+ * Function: createUniqueID
+ * Create a unique identifier for this session. Each time this function
+ * is called, a counter is incremented. The return will be the optional
+ * prefix (defaults to "id_") appended with the counter value.
+ *
+ * Parameters:
+ * prefix - {String} Optional string to prefix unique id. Default is "id_".
+ * Note that dots (".") in the prefix will be replaced with underscore ("_").
+ *
+ * Returns:
+ * {String} A unique id string, built on the passed in prefix.
+ */
+OpenLayers.Util.createUniqueID = function(prefix) {
+ if (prefix == null) {
+ prefix = "id_";
+ } else {
+ prefix = prefix.replace(OpenLayers.Util.dotless, "_");
+ }
+ OpenLayers.Util.lastSeqID += 1;
+ return prefix + OpenLayers.Util.lastSeqID;
+};
+
+/**
+ * Constant: INCHES_PER_UNIT
+ * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
+ * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
+ * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
+ * and PROJ.4 (http://trac.osgeo.org/proj/)
+ * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
+ * The hardcoded table of PROJ.4 units are in pj_units.c.
+ */
+OpenLayers.INCHES_PER_UNIT = {
+ 'inches': 1.0,
+ 'ft': 12.0,
+ 'mi': 63360.0,
+ 'm': 39.37,
+ 'km': 39370,
+ 'dd': 4374754,
+ 'yd': 36
+};
+OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
+OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
+OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
+
+// Units from CS-Map
+OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "Inch": OpenLayers.INCHES_PER_UNIT.inches,
+ "Meter": 1.0 / OpenLayers.METERS_PER_INCH, //EPSG:9001
+ "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH, //EPSG:9003
+ "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9002
+ "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH, //EPSG:9005
+ "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH, //EPSG:9041
+ "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH, //EPSG:9094
+ "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
+ "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
+ "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
+ "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9036
+ "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
+ "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH, //EPSG:9040
+ "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH, //EPSG:9084
+ "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH, //EPSG:9085
+ "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH, //EPSG:9086
+ "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH, //EPSG:9087
+ "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH, //EPSG:9080
+ "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH, //EPSG:9081
+ "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH, //EPSG:9082
+ "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH, //EPSG:9083
+ "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
+ "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9096
+ "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9093
+ "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH, //EPSG:9030
+ "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
+ "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
+ "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH, //EPSG:9031
+ "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
+ "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9038
+ "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9033
+ "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9062
+ "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9042
+ "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH, //EPSG:9039
+ "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH, //EPSG:9034
+ "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH, //EPSG:9063
+ "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH, //EPSG:9043
+ "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH, //EPSG:9097
+ "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH, //EPSG:9098
+ "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+ "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
+ "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
+ "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
+ "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+ "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
+ "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
+ "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
+ "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
+ "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
+});
+
+//unit abbreviations supported by PROJ.4
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+ "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
+ "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
+ "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
+ "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
+ "kmi": OpenLayers.INCHES_PER_UNIT["nmi"], //International Nautical Mile
+ "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
+ "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"], //International Chain
+ "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
+ "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
+ "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
+ "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
+ "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
+ "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"], //U.S. Surveyor's Statute Mile
+ "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"], //Indian Yard
+ "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"], //Indian Foot
+ "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH //Indian Chain
+});
+
+/**
+ * Constant: DOTS_PER_INCH
+ * {Integer} 72 (A sensible default)
+ */
+OpenLayers.DOTS_PER_INCH = 72;
+
+/**
+ * Function: normalizeScale
+ *
+ * Parameters:
+ * scale - {float}
+ *
+ * Returns:
+ * {Float} A normalized scale value, in 1 / X format.
+ * This means that if a value less than one ( already 1/x) is passed
+ * in, it just returns scale directly. Otherwise, it returns
+ * 1 / scale
+ */
+OpenLayers.Util.normalizeScale = function (scale) {
+ var normScale = (scale > 1.0) ? (1.0 / scale)
+ : scale;
+ return normScale;
+};
+
+/**
+ * Function: getResolutionFromScale
+ *
+ * Parameters:
+ * scale - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding resolution given passed-in scale and unit
+ * parameters. If the given scale is falsey, the returned resolution will
+ * be undefined.
+ */
+OpenLayers.Util.getResolutionFromScale = function (scale, units) {
+ var resolution;
+ if (scale) {
+ if (units == null) {
+ units = "degrees";
+ }
+ var normScale = OpenLayers.Util.normalizeScale(scale);
+ resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
+ * OpenLayers.DOTS_PER_INCH);
+ }
+ return resolution;
+};
+
+/**
+ * Function: getScaleFromResolution
+ *
+ * Parameters:
+ * resolution - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ * Default is degrees
+ *
+ * Returns:
+ * {Float} The corresponding scale given passed-in resolution and unit
+ * parameters.
+ */
+OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
+
+ if (units == null) {
+ units = "degrees";
+ }
+
+ var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
+ OpenLayers.DOTS_PER_INCH;
+ return scale;
+};
+
+/**
+ * Function: pagePosition
+ * Calculates the position of an element on the page (see
+ * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
+ *
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission of Yahoo! Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Parameters:
+ * forElement - {DOMElement}
+ *
+ * Returns:
+ * {Array} two item array, Left value then Top value.
+ */
+OpenLayers.Util.pagePosition = function(forElement) {
+ // NOTE: If element is hidden (display none or disconnected or any the
+ // ancestors are hidden) we get (0,0) by default but we still do the
+ // accumulation of scroll position.
+
+ var pos = [0, 0];
+ var viewportElement = OpenLayers.Util.getViewportElement();
+ if (!forElement || forElement == window || forElement == viewportElement) {
+ // viewport is always at 0,0 as that defined the coordinate system for
+ // this function - this avoids special case checks in the code below
+ return pos;
+ }
+
+ // Gecko browsers normally use getBoxObjectFor to calculate the position.
+ // When invoked for an element with an implicit absolute position though it
+ // can be off by one. Therefore the recursive implementation is used in
+ // those (relatively rare) cases.
+ var BUGGY_GECKO_BOX_OBJECT =
+ OpenLayers.IS_GECKO && document.getBoxObjectFor &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
+ (forElement.style.top == '' || forElement.style.left == '');
+
+ var parent = null;
+ var box;
+
+ if (forElement.getBoundingClientRect) { // IE
+ box = forElement.getBoundingClientRect();
+ var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+ var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+
+ pos[0] = box.left + scrollLeft;
+ pos[1] = box.top + scrollTop;
+
+ } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
+ // Gecko ignores the scroll values for ancestors, up to 1.9. See:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
+
+ box = document.getBoxObjectFor(forElement);
+ var vpBox = document.getBoxObjectFor(viewportElement);
+ pos[0] = box.screenX - vpBox.screenX;
+ pos[1] = box.screenY - vpBox.screenY;
+
+ } else { // safari/opera
+ pos[0] = forElement.offsetLeft;
+ pos[1] = forElement.offsetTop;
+ parent = forElement.offsetParent;
+ if (parent != forElement) {
+ while (parent) {
+ pos[0] += parent.offsetLeft;
+ pos[1] += parent.offsetTop;
+ parent = parent.offsetParent;
+ }
+ }
+
+ var browser = OpenLayers.BROWSER_NAME;
+
+ // opera & (safari absolute) incorrectly account for body offsetTop
+ if (browser == "opera" || (browser == "safari" &&
+ OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
+ pos[1] -= document.body.offsetTop;
+ }
+
+ // accumulate the scroll positions for everything but the body element
+ parent = forElement.offsetParent;
+ while (parent && parent != document.body) {
+ pos[0] -= parent.scrollLeft;
+ // see https://bugs.opera.com/show_bug.cgi?id=249965
+ if (browser != "opera" || parent.tagName != 'TR') {
+ pos[1] -= parent.scrollTop;
+ }
+ parent = parent.offsetParent;
+ }
+ }
+
+ return pos;
+};
+
+/**
+ * Function: getViewportElement
+ * Returns die viewport element of the document. The viewport element is
+ * usually document.documentElement, except in IE,where it is either
+ * document.body or document.documentElement, depending on the document's
+ * compatibility mode (see
+ * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+OpenLayers.Util.getViewportElement = function() {
+ var viewportElement = arguments.callee.viewportElement;
+ if (viewportElement == undefined) {
+ viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
+ document.compatMode != 'CSS1Compat') ? document.body :
+ document.documentElement;
+ arguments.callee.viewportElement = viewportElement;
+ }
+ return viewportElement;
+};
+
+/**
+ * Function: isEquivalentUrl
+ * Test two URLs for equivalence.
+ *
+ * Setting 'ignoreCase' allows for case-independent comparison.
+ *
+ * Comparison is based on:
+ * - Protocol
+ * - Host (evaluated without the port)
+ * - Port (set 'ignorePort80' to ignore "80" values)
+ * - Hash ( set 'ignoreHash' to disable)
+ * - Pathname (for relative <-> absolute comparison)
+ * - Arguments (so they can be out of order)
+ *
+ * Parameters:
+ * url1 - {String}
+ * url2 - {String}
+ * options - {Object} Allows for customization of comparison:
+ * 'ignoreCase' - Default is True
+ * 'ignorePort80' - Default is True
+ * 'ignoreHash' - Default is True
+ *
+ * Returns:
+ * {Boolean} Whether or not the two URLs are equivalent
+ */
+OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
+ options = options || {};
+
+ OpenLayers.Util.applyDefaults(options, {
+ ignoreCase: true,
+ ignorePort80: true,
+ ignoreHash: true,
+ splitArgs: false
+ });
+
+ var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
+ var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
+
+ //compare all keys except for "args" (treated below)
+ for(var key in urlObj1) {
+ if(key !== "args") {
+ if(urlObj1[key] != urlObj2[key]) {
+ return false;
+ }
+ }
+ }
+
+ // compare search args - irrespective of order
+ for(var key in urlObj1.args) {
+ if(urlObj1.args[key] != urlObj2.args[key]) {
+ return false;
+ }
+ delete urlObj2.args[key];
+ }
+ // urlObj2 shouldn't have any args left
+ for(var key in urlObj2.args) {
+ return false;
+ }
+
+ return true;
+};
+
+/**
+ * Function: createUrlObject
+ *
+ * Parameters:
+ * url - {String}
+ * options - {Object} A hash of options.
+ *
+ * Valid options:
+ * ignoreCase - {Boolean} lowercase url,
+ * ignorePort80 - {Boolean} don't include explicit port if port is 80,
+ * ignoreHash - {Boolean} Don't include part of url after the hash (#).
+ * splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ * true.
+ *
+ * Returns:
+ * {Object} An object with separate url, a, port, host, and args parsed out
+ * and ready for comparison
+ */
+OpenLayers.Util.createUrlObject = function(url, options) {
+ options = options || {};
+
+ // deal with relative urls first
+ if(!(/^\w+:\/\//).test(url)) {
+ var loc = window.location;
+ var port = loc.port ? ":" + loc.port : "";
+ var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
+ if(url.indexOf("/") === 0) {
+ // full pathname
+ url = fullUrl + url;
+ } else {
+ // relative to current path
+ var parts = loc.pathname.split("/");
+ parts.pop();
+ url = fullUrl + parts.join("/") + "/" + url;
+ }
+ }
+
+ if (options.ignoreCase) {
+ url = url.toLowerCase();
+ }
+
+ var a = document.createElement('a');
+ a.href = url;
+
+ var urlObject = {};
+
+ //host (without port)
+ urlObject.host = a.host.split(":").shift();
+
+ //protocol
+ urlObject.protocol = a.protocol;
+
+ //port (get uniform browser behavior with port 80 here)
+ if(options.ignorePort80) {
+ urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
+ } else {
+ urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
+ }
+
+ //hash
+ urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;
+
+ //args
+ var queryString = a.search;
+ if (!queryString) {
+ var qMark = url.indexOf("?");
+ queryString = (qMark != -1) ? url.substr(qMark) : "";
+ }
+ urlObject.args = OpenLayers.Util.getParameters(queryString,
+ {splitArgs: options.splitArgs});
+
+ // pathname
+ //
+ // This is a workaround for Internet Explorer where
+ // window.location.pathname has a leading "/", but
+ // a.pathname has no leading "/".
+ urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
+
+ return urlObject;
+};
+
+/**
+ * Function: removeTail
+ * Takes a url and removes everything after the ? and #
+ *
+ * Parameters:
+ * url - {String} The url to process
+ *
+ * Returns:
+ * {String} The string with all queryString and Hash removed
+ */
+OpenLayers.Util.removeTail = function(url) {
+ var head = null;
+
+ var qMark = url.indexOf("?");
+ var hashMark = url.indexOf("#");
+
+ if (qMark == -1) {
+ head = (hashMark != -1) ? url.substr(0,hashMark) : url;
+ } else {
+ head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark))
+ : url.substr(0, qMark);
+ }
+ return head;
+};
+
+/**
+ * Constant: IS_GECKO
+ * {Boolean} True if the userAgent reports the browser to use the Gecko engine
+ */
+OpenLayers.IS_GECKO = (function() {
+ var ua = navigator.userAgent.toLowerCase();
+ return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
+})();
+
+/**
+ * Constant: CANVAS_SUPPORTED
+ * {Boolean} True if canvas 2d is supported.
+ */
+OpenLayers.CANVAS_SUPPORTED = (function() {
+ var elem = document.createElement('canvas');
+ return !!(elem.getContext && elem.getContext('2d'));
+})();
+
+/**
+ * Constant: BROWSER_NAME
+ * {String}
+ * A substring of the navigator.userAgent property. Depending on the userAgent
+ * property, this will be the empty string or one of the following:
+ * * "opera" -- Opera
+ * * "msie" -- Internet Explorer
+ * * "safari" -- Safari
+ * * "firefox" -- Firefox
+ * * "mozilla" -- Mozilla
+ */
+OpenLayers.BROWSER_NAME = (function() {
+ var name = "";
+ var ua = navigator.userAgent.toLowerCase();
+ if (ua.indexOf("opera") != -1) {
+ name = "opera";
+ } else if (ua.indexOf("msie") != -1) {
+ name = "msie";
+ } else if (ua.indexOf("safari") != -1) {
+ name = "safari";
+ } else if (ua.indexOf("mozilla") != -1) {
+ if (ua.indexOf("firefox") != -1) {
+ name = "firefox";
+ } else {
+ name = "mozilla";
+ }
+ }
+ return name;
+})();
+
+/**
+ * Function: getBrowserName
+ *
+ * Returns:
+ * {String} A string which specifies which is the current
+ * browser in which we are running.
+ *
+ * Currently-supported browser detection and codes:
+ * * 'opera' -- Opera
+ * * 'msie' -- Internet Explorer
+ * * 'safari' -- Safari
+ * * 'firefox' -- Firefox
+ * * 'mozilla' -- Mozilla
+ *
+ * If we are unable to property identify the browser, we
+ * return an empty string.
+ */
+OpenLayers.Util.getBrowserName = function() {
+ return OpenLayers.BROWSER_NAME;
+};
+
+/**
+ * Method: getRenderedDimensions
+ * Renders the contentHTML offscreen to determine actual dimensions for
+ * popup sizing. As we need layout to determine dimensions the content
+ * is rendered -9999px to the left and absolute to ensure the
+ * scrollbars do not flicker
+ *
+ * Parameters:
+ * contentHTML
+ * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is
+ * specified, we fix that dimension of the div to be measured. This is
+ * useful in the case where we have a limit in one dimension and must
+ * therefore meaure the flow in the other dimension.
+ * options - {Object}
+ *
+ * Allowed Options:
+ * displayClass - {String} Optional parameter. A CSS class name(s) string
+ * to provide the CSS context of the rendered content.
+ * containerElement - {DOMElement} Optional parameter. Insert the HTML to
+ * this node instead of the body root when calculating dimensions.
+ *
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
+
+ var w, h;
+
+ // create temp container div with restricted size
+ var container = document.createElement("div");
+ container.style.visibility = "hidden";
+
+ var containerElement = (options && options.containerElement)
+ ? options.containerElement : document.body;
+
+ // Opera and IE7 can't handle a node with position:aboslute if it inherits
+ // position:absolute from a parent.
+ var parentHasPositionAbsolute = false;
+ var superContainer = null;
+ var parent = containerElement;
+ while (parent && parent.tagName.toLowerCase()!="body") {
+ var parentPosition = OpenLayers.Element.getStyle(parent, "position");
+ if(parentPosition == "absolute") {
+ parentHasPositionAbsolute = true;
+ break;
+ } else if (parentPosition && parentPosition != "static") {
+ break;
+ }
+ parent = parent.parentNode;
+ }
+ if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 ||
+ containerElement.clientWidth === 0) ){
+ superContainer = document.createElement("div");
+ superContainer.style.visibility = "hidden";
+ superContainer.style.position = "absolute";
+ superContainer.style.overflow = "visible";
+ superContainer.style.width = document.body.clientWidth + "px";
+ superContainer.style.height = document.body.clientHeight + "px";
+ superContainer.appendChild(container);
+ }
+ container.style.position = "absolute";
+
+ //fix a dimension, if specified.
+ if (size) {
+ if (size.w) {
+ w = size.w;
+ container.style.width = w + "px";
+ } else if (size.h) {
+ h = size.h;
+ container.style.height = h + "px";
+ }
+ }
+
+ //add css classes, if specified
+ if (options && options.displayClass) {
+ container.className = options.displayClass;
+ }
+
+ // create temp content div and assign content
+ var content = document.createElement("div");
+ content.innerHTML = contentHTML;
+
+ // we need overflow visible when calculating the size
+ content.style.overflow = "visible";
+ if (content.childNodes) {
+ for (var i=0, l=content.childNodes.length; i<l; i++) {
+ if (!content.childNodes[i].style) continue;
+ content.childNodes[i].style.overflow = "visible";
+ }
+ }
+
+ // add content to restricted container
+ container.appendChild(content);
+
+ // append container to body for rendering
+ if (superContainer) {
+ containerElement.appendChild(superContainer);
+ } else {
+ containerElement.appendChild(container);
+ }
+
+ // calculate scroll width of content and add corners and shadow width
+ if (!w) {
+ w = parseInt(content.scrollWidth);
+
+ // update container width to allow height to adjust
+ container.style.width = w + "px";
+ }
+ // capture height and add shadow and corner image widths
+ if (!h) {
+ h = parseInt(content.scrollHeight);
+ }
+
+ // remove elements
+ container.removeChild(content);
+ if (superContainer) {
+ superContainer.removeChild(container);
+ containerElement.removeChild(superContainer);
+ } else {
+ containerElement.removeChild(container);
+ }
+
+ return new OpenLayers.Size(w, h);
+};
+
+/**
+ * APIFunction: getScrollbarWidth
+ * This function has been modified by the OpenLayers from the original version,
+ * written by Matthew Eernisse and released under the Apache 2
+ * license here:
+ *
+ * http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
+ *
+ * It has been modified simply to cache its value, since it is physically
+ * impossible that this code could ever run in more than one browser at
+ * once.
+ *
+ * Returns:
+ * {Integer}
+ */
+OpenLayers.Util.getScrollbarWidth = function() {
+
+ var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+
+ if (scrollbarWidth == null) {
+ var scr = null;
+ var inn = null;
+ var wNoScroll = 0;
+ var wScroll = 0;
+
+ // Outer scrolling div
+ scr = document.createElement('div');
+ scr.style.position = 'absolute';
+ scr.style.top = '-1000px';
+ scr.style.left = '-1000px';
+ scr.style.width = '100px';
+ scr.style.height = '50px';
+ // Start with no scrollbar
+ scr.style.overflow = 'hidden';
+
+ // Inner content div
+ inn = document.createElement('div');
+ inn.style.width = '100%';
+ inn.style.height = '200px';
+
+ // Put the inner div in the scrolling div
+ scr.appendChild(inn);
+ // Append the scrolling div to the doc
+ document.body.appendChild(scr);
+
+ // Width of the inner div sans scrollbar
+ wNoScroll = inn.offsetWidth;
+
+ // Add the scrollbar
+ scr.style.overflow = 'scroll';
+ // Width of the inner div width scrollbar
+ wScroll = inn.offsetWidth;
+
+ // Remove the scrolling div from the doc
+ document.body.removeChild(document.body.lastChild);
+
+ // Pixel width of the scroller
+ OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
+ scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+ }
+
+ return scrollbarWidth;
+};
+
+/**
+ * APIFunction: getFormattedLonLat
+ * This function will return latitude or longitude value formatted as
+ *
+ * Parameters:
+ * coordinate - {Float} the coordinate value to be formatted
+ * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
+ * to be formatted (default = lat)
+ * dmsOption - {String} specify the precision of the output can be one of:
+ * 'dms' show degrees minutes and seconds
+ * 'dm' show only degrees and minutes
+ * 'd' show only degrees
+ *
+ * Returns:
+ * {String} the coordinate value formatted as a string
+ */
+OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
+ if (!dmsOption) {
+ dmsOption = 'dms'; //default to show degree, minutes, seconds
+ }
+
+ coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
+
+ var abscoordinate = Math.abs(coordinate);
+ var coordinatedegrees = Math.floor(abscoordinate);
+
+ var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
+ var tempcoordinateminutes = coordinateminutes;
+ coordinateminutes = Math.floor(coordinateminutes);
+ var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
+ coordinateseconds = Math.round(coordinateseconds*10);
+ coordinateseconds /= 10;
+
+ if( coordinateseconds >= 60) {
+ coordinateseconds -= 60;
+ coordinateminutes += 1;
+ if( coordinateminutes >= 60) {
+ coordinateminutes -= 60;
+ coordinatedegrees += 1;
+ }
+ }
+
+ if( coordinatedegrees < 10 ) {
+ coordinatedegrees = "0" + coordinatedegrees;
+ }
+ var str = coordinatedegrees + "\u00B0";
+
+ if (dmsOption.indexOf('dm') >= 0) {
+ if( coordinateminutes < 10 ) {
+ coordinateminutes = "0" + coordinateminutes;
+ }
+ str += coordinateminutes + "'";
+
+ if (dmsOption.indexOf('dms') >= 0) {
+ if( coordinateseconds < 10 ) {
+ coordinateseconds = "0" + coordinateseconds;
+ }
+ str += coordinateseconds + '"';
+ }
+ }
+
+ if (axis == "lon") {
+ str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
+ } else {
+ str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
+ }
+ return str;
+};
+
diff --git a/misc/openlayers/lib/OpenLayers/Util/vendorPrefix.js b/misc/openlayers/lib/OpenLayers/Util/vendorPrefix.js
new file mode 100644
index 0000000..89286d7
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/Util/vendorPrefix.js
@@ -0,0 +1,131 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+OpenLayers.Util = OpenLayers.Util || {};
+/**
+ * Namespace: OpenLayers.Util.vendorPrefix
+ * A collection of utility functions to detect vendor prefixed features
+ */
+OpenLayers.Util.vendorPrefix = (function() {
+ "use strict";
+
+ var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
+ divStyle = document.createElement("div").style,
+ cssCache = {},
+ jsCache = {};
+
+
+ /**
+ * Function: domToCss
+ * Converts a upper camel case DOM style property name to a CSS property
+ * i.e. transformOrigin -> transform-origin
+ * or WebkitTransformOrigin -> -webkit-transform-origin
+ *
+ * Parameters:
+ * prefixedDom - {String} The property to convert
+ *
+ * Returns:
+ * {String} The CSS property
+ */
+ function domToCss(prefixedDom) {
+ if (!prefixedDom) { return null; }
+ return prefixedDom.
+ replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
+ replace(/^ms-/, "-ms-");
+ }
+
+ /**
+ * APIMethod: css
+ * Detect which property is used for a CSS property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) CSS property name
+ *
+ * Returns:
+ * {String} The standard CSS property, prefixed property or null if not
+ * supported
+ */
+ function css(property) {
+ if (cssCache[property] === undefined) {
+ var domProperty = property.
+ replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
+ var prefixedDom = style(domProperty);
+ cssCache[property] = domToCss(prefixedDom);
+ }
+ return cssCache[property];
+ }
+
+ /**
+ * APIMethod: js
+ * Detect which property is used for a JS property/method
+ *
+ * Parameters:
+ * obj - {Object} The object to test on
+ * property - {String} The standard (unprefixed) JS property name
+ *
+ * Returns:
+ * {String} The standard JS property, prefixed property or null if not
+ * supported
+ */
+ function js(obj, property) {
+ if (jsCache[property] === undefined) {
+ var tmpProp,
+ i = 0,
+ l = VENDOR_PREFIXES.length,
+ prefix,
+ isStyleObj = (typeof obj.cssText !== "undefined");
+
+ jsCache[property] = null;
+ for(; i<l; i++) {
+ prefix = VENDOR_PREFIXES[i];
+ if(prefix) {
+ if (!isStyleObj) {
+ // js prefix should be lower-case, while style
+ // properties have upper case on first character
+ prefix = prefix.toLowerCase();
+ }
+ tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
+ } else {
+ tmpProp = property;
+ }
+
+ if(obj[tmpProp] !== undefined) {
+ jsCache[property] = tmpProp;
+ break;
+ }
+ }
+ }
+ return jsCache[property];
+ }
+
+ /**
+ * APIMethod: style
+ * Detect which property is used for a DOM style property
+ *
+ * Parameters:
+ * property - {String} The standard (unprefixed) style property name
+ *
+ * Returns:
+ * {String} The standard style property, prefixed property or null if not
+ * supported
+ */
+ function style(property) {
+ return js(divStyle, property);
+ }
+
+ return {
+ css: css,
+ js: js,
+ style: style,
+
+ // used for testing
+ cssCache: cssCache,
+ jsCache: jsCache
+ };
+}()); \ No newline at end of file
diff --git a/misc/openlayers/lib/OpenLayers/WPSClient.js b/misc/openlayers/lib/OpenLayers/WPSClient.js
new file mode 100644
index 0000000..e0c8c49
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/WPSClient.js
@@ -0,0 +1,223 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/WPSProcess.js
+ * @requires OpenLayers/Format/WPSDescribeProcess.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.WPSClient
+ * High level API for interaction with Web Processing Services (WPS).
+ * An <OpenLayers.WPSClient> instance is used to create <OpenLayers.WPSProcess>
+ * instances for servers known to the WPSClient. The WPSClient also caches
+ * DescribeProcess responses to reduce the number of requests sent to servers
+ * when processes are created.
+ */
+OpenLayers.WPSClient = OpenLayers.Class({
+
+ /**
+ * Property: servers
+ * {Object} Service metadata, keyed by a local identifier.
+ *
+ * Properties:
+ * url - {String} the url of the server
+ * version - {String} WPS version of the server
+ * processDescription - {Object} Cache of raw DescribeProcess
+ * responses, keyed by process identifier.
+ */
+ servers: null,
+
+ /**
+ * Property: version
+ * {String} The default WPS version to use if none is configured. Default
+ * is '1.0.0'.
+ */
+ version: '1.0.0',
+
+ /**
+ * Property: lazy
+ * {Boolean} Should the DescribeProcess be deferred until a process is
+ * fully configured? Default is false.
+ */
+ lazy: false,
+
+ /**
+ * Property: events
+ * {<OpenLayers.Events>}
+ *
+ * Supported event types:
+ * describeprocess - Fires when the process description is available.
+ * Listeners receive an object with a 'raw' property holding the raw
+ * DescribeProcess response, and an 'identifier' property holding the
+ * process identifier of the described process.
+ */
+ events: null,
+
+ /**
+ * Constructor: OpenLayers.WPSClient
+ *
+ * Parameters:
+ * options - {Object} Object whose properties will be set on the instance.
+ *
+ * Avaliable options:
+ * servers - {Object} Mandatory. Service metadata, keyed by a local
+ * identifier. Can either be a string with the service url or an
+ * object literal with additional metadata:
+ *
+ * (code)
+ * servers: {
+ * local: '/geoserver/wps'
+ * }, {
+ * opengeo: {
+ * url: 'http://demo.opengeo.org/geoserver/wps',
+ * version: '1.0.0'
+ * }
+ * }
+ * (end)
+ *
+ * lazy - {Boolean} Optional. Set to true if DescribeProcess should not be
+ * requested until a process is fully configured. Default is false.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.events = new OpenLayers.Events(this);
+ this.servers = {};
+ for (var s in options.servers) {
+ this.servers[s] = typeof options.servers[s] == 'string' ? {
+ url: options.servers[s],
+ version: this.version,
+ processDescription: {}
+ } : options.servers[s];
+ }
+ },
+
+ /**
+ * APIMethod: execute
+ * Shortcut to execute a process with a single function call. This is
+ * equivalent to using <getProcess> and then calling execute on the
+ * process.
+ *
+ * Parameters:
+ * options - {Object} Options for the execute operation.
+ *
+ * Available options:
+ * server - {String} Mandatory. One of the local identifiers of the
+ * configured servers.
+ * process - {String} Mandatory. A process identifier known to the
+ * server.
+ * inputs - {Object} The inputs for the process, keyed by input identifier.
+ * For spatial data inputs, the value of an input is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ * output - {String} The identifier of an output to parse. Optional. If not
+ * provided, the first output will be parsed.
+ * success - {Function} Callback to call when the process is complete.
+ * This function is called with an outputs object as argument, which
+ * will have a property with the identifier of the requested output
+ * (e.g. 'result'). For processes that generate spatial output, the
+ * value will either be a single <OpenLayers.Feature.Vector> or an
+ * array of features.
+ * scope - {Object} Optional scope for the success callback.
+ */
+ execute: function(options) {
+ var process = this.getProcess(options.server, options.process);
+ process.execute({
+ inputs: options.inputs,
+ success: options.success,
+ scope: options.scope
+ });
+ },
+
+ /**
+ * APIMethod: getProcess
+ * Creates an <OpenLayers.WPSProcess>.
+ *
+ * Parameters:
+ * serverID - {String} Local identifier from the servers that this instance
+ * was constructed with.
+ * processID - {String} Process identifier known to the server.
+ *
+ * Returns:
+ * {<OpenLayers.WPSProcess>}
+ */
+ getProcess: function(serverID, processID) {
+ var process = new OpenLayers.WPSProcess({
+ client: this,
+ server: serverID,
+ identifier: processID
+ });
+ if (!this.lazy) {
+ process.describe();
+ }
+ return process;
+ },
+
+ /**
+ * Method: describeProcess
+ *
+ * Parameters:
+ * serverID - {String} Identifier of the server
+ * processID - {String} Identifier of the requested process
+ * callback - {Function} Callback to call when the description is available
+ * scope - {Object} Optional execution scope for the callback function
+ */
+ describeProcess: function(serverID, processID, callback, scope) {
+ var server = this.servers[serverID];
+ if (!server.processDescription[processID]) {
+ if (!(processID in server.processDescription)) {
+ // set to null so we know a describeFeature request is pending
+ server.processDescription[processID] = null;
+ OpenLayers.Request.GET({
+ url: server.url,
+ params: {
+ SERVICE: 'WPS',
+ VERSION: server.version,
+ REQUEST: 'DescribeProcess',
+ IDENTIFIER: processID
+ },
+ success: function(response) {
+ server.processDescription[processID] = response.responseText;
+ this.events.triggerEvent('describeprocess', {
+ identifier: processID,
+ raw: response.responseText
+ });
+ },
+ scope: this
+ });
+ } else {
+ // pending request
+ this.events.register('describeprocess', this, function describe(evt) {
+ if (evt.identifier === processID) {
+ this.events.unregister('describeprocess', this, describe);
+ callback.call(scope, evt.raw);
+ }
+ });
+ }
+ } else {
+ window.setTimeout(function() {
+ callback.call(scope, server.processDescription[processID]);
+ }, 0);
+ }
+ },
+
+ /**
+ * Method: destroy
+ */
+ destroy: function() {
+ this.events.destroy();
+ this.events = null;
+ this.servers = null;
+ },
+
+ CLASS_NAME: 'OpenLayers.WPSClient'
+
+});
diff --git a/misc/openlayers/lib/OpenLayers/WPSProcess.js b/misc/openlayers/lib/OpenLayers/WPSProcess.js
new file mode 100644
index 0000000..874020d
--- /dev/null
+++ b/misc/openlayers/lib/OpenLayers/WPSProcess.js
@@ -0,0 +1,501 @@
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Format/GeoJSON.js
+ * @requires OpenLayers/Format/WPSExecute.js
+ * @requires OpenLayers/Request.js
+ */
+
+/**
+ * Class: OpenLayers.WPSProcess
+ * Representation of a WPS process. Usually instances of
+ * <OpenLayers.WPSProcess> are created by calling 'getProcess' on an
+ * <OpenLayers.WPSClient> instance.
+ *
+ * Currently <OpenLayers.WPSProcess> supports processes that have geometries
+ * or features as output, using WKT or GeoJSON as output format. It also
+ * supports chaining of processes by using the <output> method to create a
+ * handle that is used as process input instead of a static value.
+ */
+OpenLayers.WPSProcess = OpenLayers.Class({
+
+ /**
+ * Property: client
+ * {<OpenLayers.WPSClient>} The client that manages this process.
+ */
+ client: null,
+
+ /**
+ * Property: server
+ * {String} Local client identifier for this process's server.
+ */
+ server: null,
+
+ /**
+ * Property: identifier
+ * {String} Process identifier known to the server.
+ */
+ identifier: null,
+
+ /**
+ * Property: description
+ * {Object} DescribeProcess response for this process.
+ */
+ description: null,
+
+ /**
+ * APIProperty: localWPS
+ * {String} Service endpoint for locally chained WPS processes. Default is
+ * 'http://geoserver/wps'.
+ */
+ localWPS: 'http://geoserver/wps',
+
+ /**
+ * Property: formats
+ * {Object} OpenLayers.Format instances keyed by mimetype.
+ */
+ formats: null,
+
+ /**
+ * Property: chained
+ * {Integer} Number of chained processes for pending execute requests that
+ * don't have a full configuration yet.
+ */
+ chained: 0,
+
+ /**
+ * Property: executeCallbacks
+ * {Array} Callbacks waiting to be executed until all chained processes
+ * are configured;
+ */
+ executeCallbacks: null,
+
+ /**
+ * Constructor: OpenLayers.WPSProcess
+ *
+ * Parameters:
+ * options - {Object} Object whose properties will be set on the instance.
+ *
+ * Avaliable options:
+ * client - {<OpenLayers.WPSClient>} Mandatory. Client that manages this
+ * process.
+ * server - {String} Mandatory. Local client identifier of this process's
+ * server.
+ * identifier - {String} Mandatory. Process identifier known to the server.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ this.executeCallbacks = [];
+ this.formats = {
+ 'application/wkt': new OpenLayers.Format.WKT(),
+ 'application/json': new OpenLayers.Format.GeoJSON()
+ };
+ },
+
+ /**
+ * Method: describe
+ * Makes the client issue a DescribeProcess request asynchronously.
+ *
+ * Parameters:
+ * options - {Object} Configuration for the method call
+ *
+ * Available options:
+ * callback - {Function} Callback to execute when the description is
+ * available. Will be called with the parsed description as argument.
+ * Optional.
+ * scope - {Object} The scope in which the callback will be executed.
+ * Default is the global object.
+ */
+ describe: function(options) {
+ options = options || {};
+ if (!this.description) {
+ this.client.describeProcess(this.server, this.identifier, function(description) {
+ if (!this.description) {
+ this.parseDescription(description);
+ }
+ if (options.callback) {
+ options.callback.call(options.scope, this.description);
+ }
+ }, this);
+ } else if (options.callback) {
+ var description = this.description;
+ window.setTimeout(function() {
+ options.callback.call(options.scope, description);
+ }, 0);
+ }
+ },
+
+ /**
+ * APIMethod: configure
+ * Configure the process, but do not execute it. Use this for processes
+ * that are chained as input of a different process by means of the
+ * <output> method.
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.WPSProcess>} this process.
+ *
+ * Available options:
+ * inputs - {Object} The inputs for the process, keyed by input identifier.
+ * For spatial data inputs, the value of an input is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ * callback - {Function} Callback to call when the configuration is
+ * complete. Optional.
+ * scope - {Object} Optional scope for the callback.
+ */
+ configure: function(options) {
+ this.describe({
+ callback: function() {
+ var description = this.description,
+ inputs = options.inputs,
+ input, i, ii;
+ for (i=0, ii=description.dataInputs.length; i<ii; ++i) {
+ input = description.dataInputs[i];
+ this.setInputData(input, inputs[input.identifier]);
+ }
+ if (options.callback) {
+ options.callback.call(options.scope);
+ }
+ },
+ scope: this
+ });
+ return this;
+ },
+
+ /**
+ * APIMethod: execute
+ * Configures and executes the process
+ *
+ * Parameters:
+ * options - {Object}
+ *
+ * Available options:
+ * inputs - {Object} The inputs for the process, keyed by input identifier.
+ * For spatial data inputs, the value of an input is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ * output - {String} The identifier of the output to request and parse.
+ * Optional. If not provided, the first output will be requested.
+ * success - {Function} Callback to call when the process is complete.
+ * This function is called with an outputs object as argument, which
+ * will have a property with the identifier of the requested output
+ * (or 'result' if output was not configured). For processes that
+ * generate spatial output, the value will be an array of
+ * <OpenLayers.Feature.Vector> instances.
+ * scope - {Object} Optional scope for the success callback.
+ */
+ execute: function(options) {
+ this.configure({
+ inputs: options.inputs,
+ callback: function() {
+ var me = this;
+ //TODO For now we only deal with a single output
+ var outputIndex = this.getOutputIndex(
+ me.description.processOutputs, options.output
+ );
+ me.setResponseForm({outputIndex: outputIndex});
+ (function callback() {
+ OpenLayers.Util.removeItem(me.executeCallbacks, callback);
+ if (me.chained !== 0) {
+ // need to wait until chained processes have a
+ // description and configuration - see chainProcess
+ me.executeCallbacks.push(callback);
+ return;
+ }
+ // all chained processes are added as references now, so
+ // let's proceed.
+ OpenLayers.Request.POST({
+ url: me.client.servers[me.server].url,
+ data: new OpenLayers.Format.WPSExecute().write(me.description),
+ success: function(response) {
+ var output = me.description.processOutputs[outputIndex];
+ var mimeType = me.findMimeType(
+ output.complexOutput.supported.formats
+ );
+ //TODO For now we assume a spatial output
+ var features = me.formats[mimeType].read(response.responseText);
+ if (features instanceof OpenLayers.Feature.Vector) {
+ features = [features];
+ }
+ if (options.success) {
+ var outputs = {};
+ outputs[options.output || 'result'] = features;
+ options.success.call(options.scope, outputs);
+ }
+ },
+ scope: me
+ });
+ })();
+ },
+ scope: this
+ });
+ },
+
+ /**
+ * APIMethod: output
+ * Chain an output of a configured process (see <configure>) as input to
+ * another process.
+ *
+ * (code)
+ * intersect = client.getProcess('opengeo', 'JTS:intersection');
+ * intersect.configure({
+ * // ...
+ * });
+ * buffer = client.getProcess('opengeo', 'JTS:buffer');
+ * buffer.execute({
+ * inputs: {
+ * geom: intersect.output('result'), // <-- here we're chaining
+ * distance: 1
+ * },
+ * // ...
+ * });
+ * (end)
+ *
+ * Parameters:
+ * identifier - {String} Identifier of the output that we're chaining. If
+ * not provided, the first output will be used.
+ */
+ output: function(identifier) {
+ return new OpenLayers.WPSProcess.ChainLink({
+ process: this,
+ output: identifier
+ });
+ },
+
+ /**
+ * Method: parseDescription
+ * Parses the DescribeProcess response
+ *
+ * Parameters:
+ * description - {Object}
+ */
+ parseDescription: function(description) {
+ var server = this.client.servers[this.server];
+ this.description = new OpenLayers.Format.WPSDescribeProcess()
+ .read(server.processDescription[this.identifier])
+ .processDescriptions[this.identifier];
+ },
+
+ /**
+ * Method: setInputData
+ * Sets the data for a single input
+ *
+ * Parameters:
+ * input - {Object} An entry from the dataInputs array of the process
+ * description.
+ * data - {Mixed} For spatial data inputs, this is usually an
+ * <OpenLayers.Geometry>, an <OpenLayers.Feature.Vector> or an array of
+ * geometries or features.
+ */
+ setInputData: function(input, data) {
+ // clear any previous data
+ delete input.data;
+ delete input.reference;
+ if (data instanceof OpenLayers.WPSProcess.ChainLink) {
+ ++this.chained;
+ input.reference = {
+ method: 'POST',
+ href: data.process.server === this.server ?
+ this.localWPS : this.client.servers[data.process.server].url
+ };
+ data.process.describe({
+ callback: function() {
+ --this.chained;
+ this.chainProcess(input, data);
+ },
+ scope: this
+ });
+ } else {
+ input.data = {};
+ var complexData = input.complexData;
+ if (complexData) {
+ var format = this.findMimeType(complexData.supported.formats);
+ input.data.complexData = {
+ mimeType: format,
+ value: this.formats[format].write(this.toFeatures(data))
+ };
+ } else {
+ input.data.literalData = {
+ value: data
+ };
+ }
+ }
+ },
+
+ /**
+ * Method: setResponseForm
+ * Sets the responseForm property of the <execute> payload.
+ *
+ * Parameters:
+ * options - {Object} See below.
+ *
+ * Available options:
+ * outputIndex - {Integer} The index of the output to use. Optional.
+ * supportedFormats - {Object} Object with supported mime types as key,
+ * and true as value for supported types. Optional.
+ */
+ setResponseForm: function(options) {
+ options = options || {};
+ var output = this.description.processOutputs[options.outputIndex || 0];
+ this.description.responseForm = {
+ rawDataOutput: {
+ identifier: output.identifier,
+ mimeType: this.findMimeType(output.complexOutput.supported.formats, options.supportedFormats)
+ }
+ };
+ },
+
+ /**
+ * Method: getOutputIndex
+ * Gets the index of a processOutput by its identifier
+ *
+ * Parameters:
+ * outputs - {Array} The processOutputs array to look at
+ * identifier - {String} The identifier of the output
+ *
+ * Returns
+ * {Integer} The index of the processOutput with the provided identifier
+ * in the outputs array.
+ */
+ getOutputIndex: function(outputs, identifier) {
+ var output;
+ if (identifier) {
+ for (var i=outputs.length-1; i>=0; --i) {
+ if (outputs[i].identifier === identifier) {
+ output = i;
+ break;
+ }
+ }
+ } else {
+ output = 0;
+ }
+ return output;
+ },
+
+ /**
+ * Method: chainProcess
+ * Sets a fully configured chained process as input for this process.
+ *
+ * Parameters:
+ * input - {Object} The dataInput that the chained process provides.
+ * chainLink - {<OpenLayers.WPSProcess.ChainLink>} The process to chain.
+ */
+ chainProcess: function(input, chainLink) {
+ var output = this.getOutputIndex(
+ chainLink.process.description.processOutputs, chainLink.output
+ );
+ input.reference.mimeType = this.findMimeType(
+ input.complexData.supported.formats,
+ chainLink.process.description.processOutputs[output].complexOutput.supported.formats
+ );
+ var formats = {};
+ formats[input.reference.mimeType] = true;
+ chainLink.process.setResponseForm({
+ outputIndex: output,
+ supportedFormats: formats
+ });
+ input.reference.body = chainLink.process.description;
+ while (this.executeCallbacks.length > 0) {
+ this.executeCallbacks[0]();
+ }
+ },
+
+ /**
+ * Method: toFeatures
+ * Converts spatial input into features so it can be processed by
+ * <OpenLayers.Format> instances.
+ *
+ * Parameters:
+ * source - {Mixed} An <OpenLayers.Geometry>, an
+ * <OpenLayers.Feature.Vector>, or an array of geometries or features
+ *
+ * Returns:
+ * {Array(<OpenLayers.Feature.Vector>)}
+ */
+ toFeatures: function(source) {
+ var isArray = OpenLayers.Util.isArray(source);
+ if (!isArray) {
+ source = [source];
+ }
+ var target = new Array(source.length),
+ current;
+ for (var i=0, ii=source.length; i<ii; ++i) {
+ current = source[i];
+ target[i] = current instanceof OpenLayers.Feature.Vector ?
+ current : new OpenLayers.Feature.Vector(current);
+ }
+ return isArray ? target : target[0];
+ },
+
+ /**
+ * Method: findMimeType
+ * Finds a supported mime type.
+ *
+ * Parameters:
+ * sourceFormats - {Object} An object literal with mime types as key and
+ * true as value for supported formats.
+ * targetFormats - {Object} Like <sourceFormats>, but optional to check for
+ * supported mime types on a different target than this process.
+ * Default is to check against this process's supported formats.
+ *
+ * Returns:
+ * {String} A supported mime type.
+ */
+ findMimeType: function(sourceFormats, targetFormats) {
+ targetFormats = targetFormats || this.formats;
+ for (var f in sourceFormats) {
+ if (f in targetFormats) {
+ return f;
+ }
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.WPSProcess"
+
+});
+
+/**
+ * Class: OpenLayers.WPSProcess.ChainLink
+ * Type for chaining processes.
+ */
+OpenLayers.WPSProcess.ChainLink = OpenLayers.Class({
+
+ /**
+ * Property: process
+ * {<OpenLayers.WPSProcess>} The process to chain
+ */
+ process: null,
+
+ /**
+ * Property: output
+ * {String} The output identifier of the output we are going to use as
+ * input for another process.
+ */
+ output: null,
+
+ /**
+ * Constructor: OpenLayers.WPSProcess.ChainLink
+ *
+ * Parameters:
+ * options - {Object} Properties to set on the instance.
+ */
+ initialize: function(options) {
+ OpenLayers.Util.extend(this, options);
+ },
+
+ CLASS_NAME: "OpenLayers.WPSProcess.ChainLink"
+
+});
diff --git a/misc/openlayers/lib/Rico/Color.js b/misc/openlayers/lib/Rico/Color.js
new file mode 100644
index 0000000..7d9ab6f
--- /dev/null
+++ b/misc/openlayers/lib/Rico/Color.js
@@ -0,0 +1,253 @@
+/**
+ * @requires Rico/license.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ */
+
+
+/*
+ * This file has been edited substantially from the Rico-released version by
+ * the OpenLayers development team.
+ */
+
+OpenLayers.Console.warn("OpenLayers.Rico is deprecated");
+
+OpenLayers.Rico = OpenLayers.Rico || {};
+OpenLayers.Rico.Color = OpenLayers.Class({
+
+ initialize: function(red, green, blue) {
+ this.rgb = { r: red, g : green, b : blue };
+ },
+
+ setRed: function(r) {
+ this.rgb.r = r;
+ },
+
+ setGreen: function(g) {
+ this.rgb.g = g;
+ },
+
+ setBlue: function(b) {
+ this.rgb.b = b;
+ },
+
+ setHue: function(h) {
+
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.h = h;
+
+ // convert back to RGB...
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setSaturation: function(s) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.s = s;
+
+ // convert back to RGB and set values...
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+ },
+
+ setBrightness: function(b) {
+ // get an HSB model, and set the new hue...
+ var hsb = this.asHSB();
+ hsb.b = b;
+
+ // convert back to RGB and set values...
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+ },
+
+ darken: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+ },
+
+ brighten: function(percent) {
+ var hsb = this.asHSB();
+ this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+ },
+
+ blend: function(other) {
+ this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+ this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+ this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+ },
+
+ isBright: function() {
+ var hsb = this.asHSB();
+ return this.asHSB().b > 0.5;
+ },
+
+ isDark: function() {
+ return ! this.isBright();
+ },
+
+ asRGB: function() {
+ return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+ },
+
+ asHex: function() {
+ return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+ },
+
+ asHSB: function() {
+ return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+ },
+
+ toString: function() {
+ return this.asHex();
+ }
+
+});
+
+OpenLayers.Rico.Color.createFromHex = function(hexCode) {
+ if(hexCode.length==4) {
+ var shortHexCode = hexCode;
+ var hexCode = '#';
+ for(var i=1;i<4;i++) {
+ hexCode += (shortHexCode.charAt(i) +
+shortHexCode.charAt(i));
+ }
+ }
+ if ( hexCode.indexOf('#') == 0 ) {
+ hexCode = hexCode.substring(1);
+ }
+ var red = hexCode.substring(0,2);
+ var green = hexCode.substring(2,4);
+ var blue = hexCode.substring(4,6);
+ return new OpenLayers.Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+};
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+OpenLayers.Rico.Color.createColorFromBackground = function(elem) {
+
+ var actualColor =
+ OpenLayers.Element.getStyle(OpenLayers.Util.getElement(elem),
+ "backgroundColor");
+
+ if ( actualColor == "transparent" && elem.parentNode ) {
+ return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);
+ }
+ if ( actualColor == null ) {
+ return new OpenLayers.Rico.Color(255,255,255);
+ }
+ if ( actualColor.indexOf("rgb(") == 0 ) {
+ var colors = actualColor.substring(4, actualColor.length - 1 );
+ var colorArray = colors.split(",");
+ return new OpenLayers.Rico.Color( parseInt( colorArray[0] ),
+ parseInt( colorArray[1] ),
+ parseInt( colorArray[2] ) );
+
+ }
+ else if ( actualColor.indexOf("#") == 0 ) {
+ return OpenLayers.Rico.Color.createFromHex(actualColor);
+ }
+ else {
+ return new OpenLayers.Rico.Color(255,255,255);
+ }
+};
+
+OpenLayers.Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+ var red = 0;
+ var green = 0;
+ var blue = 0;
+
+ if (saturation == 0) {
+ red = parseInt(brightness * 255.0 + 0.5);
+ green = red;
+ blue = red;
+ }
+ else {
+ var h = (hue - Math.floor(hue)) * 6.0;
+ var f = h - Math.floor(h);
+ var p = brightness * (1.0 - saturation);
+ var q = brightness * (1.0 - saturation * f);
+ var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+ switch (parseInt(h)) {
+ case 0:
+ red = (brightness * 255.0 + 0.5);
+ green = (t * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 1:
+ red = (q * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (p * 255.0 + 0.5);
+ break;
+ case 2:
+ red = (p * 255.0 + 0.5);
+ green = (brightness * 255.0 + 0.5);
+ blue = (t * 255.0 + 0.5);
+ break;
+ case 3:
+ red = (p * 255.0 + 0.5);
+ green = (q * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 4:
+ red = (t * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (brightness * 255.0 + 0.5);
+ break;
+ case 5:
+ red = (brightness * 255.0 + 0.5);
+ green = (p * 255.0 + 0.5);
+ blue = (q * 255.0 + 0.5);
+ break;
+ }
+ }
+
+ return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+};
+
+OpenLayers.Rico.Color.RGBtoHSB = function(r, g, b) {
+
+ var hue;
+ var saturation;
+ var brightness;
+
+ var cmax = (r > g) ? r : g;
+ if (b > cmax) {
+ cmax = b;
+ }
+ var cmin = (r < g) ? r : g;
+ if (b < cmin) {
+ cmin = b;
+ }
+ brightness = cmax / 255.0;
+ if (cmax != 0) {
+ saturation = (cmax - cmin)/cmax;
+ } else {
+ saturation = 0;
+ }
+ if (saturation == 0) {
+ hue = 0;
+ } else {
+ var redc = (cmax - r)/(cmax - cmin);
+ var greenc = (cmax - g)/(cmax - cmin);
+ var bluec = (cmax - b)/(cmax - cmin);
+
+ if (r == cmax) {
+ hue = bluec - greenc;
+ } else if (g == cmax) {
+ hue = 2.0 + redc - bluec;
+ } else {
+ hue = 4.0 + greenc - redc;
+ }
+ hue = hue / 6.0;
+ if (hue < 0) {
+ hue = hue + 1.0;
+ }
+ }
+
+ return { h : hue, s : saturation, b : brightness };
+};
+
diff --git a/misc/openlayers/lib/Rico/Corner.js b/misc/openlayers/lib/Rico/Corner.js
new file mode 100644
index 0000000..e5479e5
--- /dev/null
+++ b/misc/openlayers/lib/Rico/Corner.js
@@ -0,0 +1,339 @@
+/**
+ * @requires OpenLayers/Console.js
+ * @requires Rico/Color.js
+ */
+
+
+/*
+ * This file has been edited substantially from the Rico-released
+ * version by the OpenLayers development team.
+ *
+ * Copyright 2005 Sabre Airline Solutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the * License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or
+ * implied. See the License for the specific language governing
+ * permissions * and limitations under the License.
+ *
+ */
+
+OpenLayers.Console.warn("OpenLayers.Rico is deprecated");
+
+OpenLayers.Rico = OpenLayers.Rico || {};
+OpenLayers.Rico.Corner = {
+
+ round: function(e, options) {
+ e = OpenLayers.Util.getElement(e);
+ this._setOptions(options);
+
+ var color = this.options.color;
+ if ( this.options.color == "fromElement" ) {
+ color = this._background(e);
+ }
+ var bgColor = this.options.bgColor;
+ if ( this.options.bgColor == "fromParent" ) {
+ bgColor = this._background(e.offsetParent);
+ }
+ this._roundCornersImpl(e, color, bgColor);
+ },
+
+ /** This is a helper function to change the background
+ * color of <div> that has had Rico rounded corners added.
+ *
+ * It seems we cannot just set the background color for the
+ * outer <div> so each <span> element used to create the
+ * corners must have its background color set individually.
+ *
+ * @param {DOM} theDiv - A child of the outer <div> that was
+ * supplied to the `round` method.
+ *
+ * @param {String} newColor - The new background color to use.
+ */
+ changeColor: function(theDiv, newColor) {
+
+ theDiv.style.backgroundColor = newColor;
+
+ var spanElements = theDiv.parentNode.getElementsByTagName("span");
+
+ for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+ spanElements[currIdx].style.backgroundColor = newColor;
+ }
+ },
+
+
+ /** This is a helper function to change the background
+ * opacity of <div> that has had Rico rounded corners added.
+ *
+ * See changeColor (above) for algorithm explanation
+ *
+ * @param {DOM} theDiv A child of the outer <div> that was
+ * supplied to the `round` method.
+ *
+ * @param {int} newOpacity The new opacity to use (0-1).
+ */
+ changeOpacity: function(theDiv, newOpacity) {
+
+ var mozillaOpacity = newOpacity;
+ var ieOpacity = 'alpha(opacity=' + newOpacity * 100 + ')';
+
+ theDiv.style.opacity = mozillaOpacity;
+ theDiv.style.filter = ieOpacity;
+
+ var spanElements = theDiv.parentNode.getElementsByTagName("span");
+
+ for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+ spanElements[currIdx].style.opacity = mozillaOpacity;
+ spanElements[currIdx].style.filter = ieOpacity;
+ }
+
+ },
+
+ /** this function takes care of redoing the rico cornering
+ *
+ * you can't just call updateRicoCorners() again and pass it a
+ * new options string. you have to first remove the divs that
+ * rico puts on top and below the content div.
+ *
+ * @param {DOM} theDiv - A child of the outer <div> that was
+ * supplied to the `round` method.
+ *
+ * @param {Object} options - list of options
+ */
+ reRound: function(theDiv, options) {
+
+ var topRico = theDiv.parentNode.childNodes[0];
+ //theDiv would be theDiv.parentNode.childNodes[1]
+ var bottomRico = theDiv.parentNode.childNodes[2];
+
+ theDiv.parentNode.removeChild(topRico);
+ theDiv.parentNode.removeChild(bottomRico);
+
+ this.round(theDiv.parentNode, options);
+ },
+
+ _roundCornersImpl: function(e, color, bgColor) {
+ if(this.options.border) {
+ this._renderBorder(e,bgColor);
+ }
+ if(this._isTopRounded()) {
+ this._roundTopCorners(e,color,bgColor);
+ }
+ if(this._isBottomRounded()) {
+ this._roundBottomCorners(e,color,bgColor);
+ }
+ },
+
+ _renderBorder: function(el,bgColor) {
+ var borderValue = "1px solid " + this._borderColor(bgColor);
+ var borderL = "border-left: " + borderValue;
+ var borderR = "border-right: " + borderValue;
+ var style = "style='" + borderL + ";" + borderR + "'";
+ el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>";
+ },
+
+ _roundTopCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=0 ; i < this.options.numSlices ; i++ ) {
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+ }
+ el.style.paddingTop = 0;
+ el.insertBefore(corner,el.firstChild);
+ },
+
+ _roundBottomCorners: function(el, color, bgColor) {
+ var corner = this._createCorner(bgColor);
+ for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- ) {
+ corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+ }
+ el.style.paddingBottom = 0;
+ el.appendChild(corner);
+ },
+
+ _createCorner: function(bgColor) {
+ var corner = document.createElement("div");
+ corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+ return corner;
+ },
+
+ _createCornerSlice: function(color,bgColor, n, position) {
+ var slice = document.createElement("span");
+
+ var inStyle = slice.style;
+ inStyle.backgroundColor = color;
+ inStyle.display = "block";
+ inStyle.height = "1px";
+ inStyle.overflow = "hidden";
+ inStyle.fontSize = "1px";
+
+ var borderColor = this._borderColor(color,bgColor);
+ if ( this.options.border && n == 0 ) {
+ inStyle.borderTopStyle = "solid";
+ inStyle.borderTopWidth = "1px";
+ inStyle.borderLeftWidth = "0px";
+ inStyle.borderRightWidth = "0px";
+ inStyle.borderBottomWidth = "0px";
+ inStyle.height = "0px"; // assumes css compliant box model
+ inStyle.borderColor = borderColor;
+ }
+ else if(borderColor) {
+ inStyle.borderColor = borderColor;
+ inStyle.borderStyle = "solid";
+ inStyle.borderWidth = "0px 1px";
+ }
+
+ if ( !this.options.compact && (n == (this.options.numSlices-1)) ) {
+ inStyle.height = "2px";
+ }
+ this._setMargin(slice, n, position);
+ this._setBorder(slice, n, position);
+ return slice;
+ },
+
+ _setOptions: function(options) {
+ this.options = {
+ corners : "all",
+ color : "fromElement",
+ bgColor : "fromParent",
+ blend : true,
+ border : false,
+ compact : false
+ };
+ OpenLayers.Util.extend(this.options, options || {});
+
+ this.options.numSlices = this.options.compact ? 2 : 4;
+ if ( this._isTransparent() ) {
+ this.options.blend = false;
+ }
+ },
+
+ _whichSideTop: function() {
+ if ( this._hasString(this.options.corners, "all", "top") ) {
+ return "";
+ }
+ if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 ) {
+ return "";
+ }
+ if (this.options.corners.indexOf("tl") >= 0) {
+ return "left";
+ } else if (this.options.corners.indexOf("tr") >= 0) {
+ return "right";
+ }
+ return "";
+ },
+
+ _whichSideBottom: function() {
+ if ( this._hasString(this.options.corners, "all", "bottom") ) {
+ return "";
+ }
+ if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 ) {
+ return "";
+ }
+
+ if(this.options.corners.indexOf("bl") >=0) {
+ return "left";
+ } else if(this.options.corners.indexOf("br")>=0) {
+ return "right";
+ }
+ return "";
+ },
+
+ _borderColor : function(color,bgColor) {
+ if ( color == "transparent" ) {
+ return bgColor;
+ } else if ( this.options.border ) {
+ return this.options.border;
+ } else if ( this.options.blend ) {
+ return this._blend( bgColor, color );
+ } else {
+ return "";
+ }
+ },
+
+
+ _setMargin: function(el, n, corners) {
+ var marginSize = this._marginSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+
+ if ( whichSide == "left" ) {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.marginRight = marginSize + "px"; el.style.marginLeft = "0px";
+ }
+ else {
+ el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+ }
+ },
+
+ _setBorder: function(el,n,corners) {
+ var borderSize = this._borderSize(n);
+ var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+ if ( whichSide == "left" ) {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+ }
+ else if ( whichSide == "right" ) {
+ el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth = "0px";
+ }
+ else {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ }
+ if (this.options.border != false) {
+ el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+ }
+ },
+
+ _marginSize: function(n) {
+ if ( this._isTransparent() ) {
+ return 0;
+ }
+ var marginSizes = [ 5, 3, 2, 1 ];
+ var blendedMarginSizes = [ 3, 2, 1, 0 ];
+ var compactMarginSizes = [ 2, 1 ];
+ var smBlendedMarginSizes = [ 1, 0 ];
+
+ if ( this.options.compact && this.options.blend ) {
+ return smBlendedMarginSizes[n];
+ } else if ( this.options.compact ) {
+ return compactMarginSizes[n];
+ } else if ( this.options.blend ) {
+ return blendedMarginSizes[n];
+ } else {
+ return marginSizes[n];
+ }
+ },
+
+ _borderSize: function(n) {
+ var transparentBorderSizes = [ 5, 3, 2, 1 ];
+ var blendedBorderSizes = [ 2, 1, 1, 1 ];
+ var compactBorderSizes = [ 1, 0 ];
+ var actualBorderSizes = [ 0, 2, 0, 0 ];
+
+ if ( this.options.compact && (this.options.blend || this._isTransparent()) ) {
+ return 1;
+ } else if ( this.options.compact ) {
+ return compactBorderSizes[n];
+ } else if ( this.options.blend ) {
+ return blendedBorderSizes[n];
+ } else if ( this.options.border ) {
+ return actualBorderSizes[n];
+ } else if ( this._isTransparent() ) {
+ return transparentBorderSizes[n];
+ }
+ return 0;
+ },
+
+ _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) { return true; } return false; },
+ _blend: function(c1, c2) { var cc1 = OpenLayers.Rico.Color.createFromHex(c1); cc1.blend(OpenLayers.Rico.Color.createFromHex(c2)); return cc1; },
+ _background: function(el) { try { return OpenLayers.Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+ _isTransparent: function() { return this.options.color == "transparent"; },
+ _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+ _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+ _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
+};
diff --git a/misc/openlayers/lib/Rico/license.js b/misc/openlayers/lib/Rico/license.js
new file mode 100644
index 0000000..aad5cf3
--- /dev/null
+++ b/misc/openlayers/lib/Rico/license.js
@@ -0,0 +1,19 @@
+/**
+ * @license Apache 2
+ *
+ * Contains portions of Rico <http://openrico.org/>
+ *
+ * Copyright 2005 Sabre Airline Solutions
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you
+ * may not use this file except in compliance with the License. You
+ * may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
diff --git a/misc/openlayers/lib/deprecated.js b/misc/openlayers/lib/deprecated.js
new file mode 100644
index 0000000..a492faa
--- /dev/null
+++ b/misc/openlayers/lib/deprecated.js
@@ -0,0 +1,5842 @@
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Request.js
+ * @requires OpenLayers/Layer/WMS.js
+ * @requires OpenLayers/Layer/MapServer.js
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Console.js
+ * @requires OpenLayers/Lang.js
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ * @requires OpenLayers/Layer/SphericalMercator.js
+ * @requires OpenLayers/Protocol.js
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Geometry.js
+ * @requires OpenLayers/Renderer/Elements.js
+ * @requires OpenLayers/Popup/Anchored.js
+ * @requires Rico/Corner.js
+ */
+
+/**
+ * About: Deprecated
+ * The deprecated.js script includes all methods, properties, and constructors
+ * that are not supported as part of the long-term API. If you use any of
+ * these, you have to explicitly include this script in your application.
+ *
+ * For example:
+ * (code)
+ * <script src="deprecated.js" type="text/javascript"></script>
+ * (end)
+ *
+ * You are strongly encouraged to avoid using deprecated functionality. The
+ * documentation here should point you to the supported alternatives.
+ */
+
+/**
+ * Namespace: OpenLayers.Class
+ */
+
+/**
+ * Property: isPrototype
+ * *Deprecated*. This is no longer needed and will be removed at 3.0.
+ */
+OpenLayers.Class.isPrototype = function () {};
+
+/**
+ * APIFunction: OpenLayers.create
+ * *Deprecated*. Old method to create an OpenLayers style class. Use the
+ * <OpenLayers.Class> constructor instead.
+ *
+ * Returns:
+ * An OpenLayers class
+ */
+OpenLayers.Class.create = function() {
+ return function() {
+ if (arguments && arguments[0] != OpenLayers.Class.isPrototype) {
+ this.initialize.apply(this, arguments);
+ }
+ };
+};
+
+/**
+ * APIFunction: inherit
+ * *Deprecated*. Old method to inherit from one or more OpenLayers style
+ * classes. Use the <OpenLayers.Class> constructor instead.
+ *
+ * Parameters:
+ * class - One or more classes can be provided as arguments
+ *
+ * Returns:
+ * An object prototype
+ */
+OpenLayers.Class.inherit = function (P) {
+ var C = function() {
+ P.call(this);
+ };
+ var newArgs = [C].concat(Array.prototype.slice.call(arguments));
+ OpenLayers.inherit.apply(null, newArgs);
+ return C.prototype;
+};
+
+/**
+ * Namespace: OpenLayers.Util
+ */
+
+/**
+ * Function: clearArray
+ * *Deprecated*. This function will disappear in 3.0.
+ * Please use "array.length = 0" instead.
+ *
+ * Parameters:
+ * array - {Array}
+ */
+OpenLayers.Util.clearArray = function(array) {
+ OpenLayers.Console.warn(
+ OpenLayers.i18n(
+ "methodDeprecated", {'newMethod': 'array = []'}
+ )
+ );
+ array.length = 0;
+};
+
+/**
+ * Function: setOpacity
+ * *Deprecated*. This function has been deprecated. Instead, please use
+ * <OpenLayers.Util.modifyDOMElement>
+ * or
+ * <OpenLayers.Util.modifyAlphaImageDiv>
+ *
+ * Set the opacity of a DOM Element
+ * Note that for this function to work in IE, elements must "have layout"
+ * according to:
+ * http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/haslayout.asp
+ *
+ * Parameters:
+ * element - {DOMElement} Set the opacity on this DOM element
+ * opacity - {Float} Opacity value (0.0 - 1.0)
+ */
+OpenLayers.Util.setOpacity = function(element, opacity) {
+ OpenLayers.Util.modifyDOMElement(element, null, null, null,
+ null, null, null, opacity);
+};
+
+/**
+ * Function: safeStopPropagation
+ * *Deprecated*. This function has been deprecated. Please use directly
+ * <OpenLayers.Event.stop> passing 'true' as the 2nd
+ * argument (preventDefault)
+ *
+ * Safely stop the propagation of an event *without* preventing
+ * the default browser action from occurring.
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+OpenLayers.Util.safeStopPropagation = function(evt) {
+ OpenLayers.Event.stop(evt, true);
+};
+
+/**
+ * Function: getArgs
+ * *Deprecated*. Will be removed in 3.0. Please use instead
+ * <OpenLayers.Util.getParameters>
+ *
+ * Parameters:
+ * url - {String} Optional url used to extract the query string.
+ * If null, query string is taken from page location.
+ *
+ * Returns:
+ * {Object} An object of key/value pairs from the query string.
+ */
+OpenLayers.Util.getArgs = function(url) {
+ OpenLayers.Console.warn(
+ OpenLayers.i18n(
+ "methodDeprecated", {'newMethod': 'OpenLayers.Util.getParameters'}
+ )
+ );
+ return OpenLayers.Util.getParameters(url);
+};
+
+/**
+ * Maintain existing definition of $.
+ *
+ * The use of our $-method is deprecated and the mapping of
+ * OpenLayers.Util.getElement will eventually be removed. Do not depend on
+ * window.$ being defined by OpenLayers.
+ */
+if(typeof window.$ === "undefined") {
+ window.$ = OpenLayers.Util.getElement;
+}
+
+/**
+ * Namespace: OpenLayers.Ajax
+ */
+
+/**
+ * Function: OpenLayers.nullHandler
+ * @param {} request
+ */
+OpenLayers.nullHandler = function(request) {
+ OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText}));
+};
+
+/**
+ * APIFunction: OpenLayers.loadURL
+ * Background load a document.
+ * *Deprecated*. Use <OpenLayers.Request.GET> method instead.
+ *
+ * Parameters:
+ * uri - {String} URI of source doc
+ * params - {String} or {Object} GET params. Either a string in the form
+ * "?hello=world&foo=bar" (do not forget the leading question mark)
+ * or an object in the form {'hello': 'world', 'foo': 'bar}
+ * caller - {Object} object which gets callbacks
+ * onComplete - {Function} Optional callback for success. The callback
+ * will be called with this set to caller and will receive the request
+ * object as an argument. Note that if you do not specify an onComplete
+ * function, <OpenLayers.nullHandler> will be called (which pops up a
+ * user friendly error message dialog).
+ * onFailure - {Function} Optional callback for failure. In the event of
+ * a failure, the callback will be called with this set to caller and will
+ * receive the request object as an argument. Note that if you do not
+ * specify an onComplete function, <OpenLayers.nullHandler> will be called
+ * (which pops up a user friendly error message dialog).
+ *
+ * Returns:
+ * {<OpenLayers.Request.XMLHttpRequest>} The request object. To abort loading,
+ * call request.abort().
+ */
+OpenLayers.loadURL = function(uri, params, caller,
+ onComplete, onFailure) {
+
+ if(typeof params == 'string') {
+ params = OpenLayers.Util.getParameters(params);
+ }
+ var success = (onComplete) ? onComplete : OpenLayers.nullHandler;
+ var failure = (onFailure) ? onFailure : OpenLayers.nullHandler;
+
+ return OpenLayers.Request.GET({
+ url: uri, params: params,
+ success: success, failure: failure, scope: caller
+ });
+};
+
+/**
+ * Function: OpenLayers.parseXMLString
+ * Parse XML into a doc structure
+ *
+ * Parameters:
+ * text - {String}
+ *
+ * Returns:
+ * {?} Parsed AJAX Responsev
+ */
+OpenLayers.parseXMLString = function(text) {
+
+ //MS sucks, if the server is bad it dies
+ var index = text.indexOf('<');
+ if (index > 0) {
+ text = text.substring(index);
+ }
+
+ var ajaxResponse = OpenLayers.Util.Try(
+ function() {
+ var xmldom = new ActiveXObject('Microsoft.XMLDOM');
+ xmldom.loadXML(text);
+ return xmldom;
+ },
+ function() {
+ return new DOMParser().parseFromString(text, 'text/xml');
+ },
+ function() {
+ var req = new XMLHttpRequest();
+ req.open("GET", "data:" + "text/xml" +
+ ";charset=utf-8," + encodeURIComponent(text), false);
+ if (req.overrideMimeType) {
+ req.overrideMimeType("text/xml");
+ }
+ req.send(null);
+ return req.responseXML;
+ }
+ );
+
+ return ajaxResponse;
+};
+
+OpenLayers.Ajax = {
+
+ /**
+ * Method: emptyFunction
+ */
+ emptyFunction: function () {},
+
+ /**
+ * Method: getTransport
+ *
+ * Returns:
+ * {Object} Transport mechanism for whichever browser we're in, or false if
+ * none available.
+ */
+ getTransport: function() {
+ return OpenLayers.Util.Try(
+ function() {return new XMLHttpRequest();},
+ function() {return new ActiveXObject('Msxml2.XMLHTTP');},
+ function() {return new ActiveXObject('Microsoft.XMLHTTP');}
+ ) || false;
+ },
+
+ /**
+ * Property: activeRequestCount
+ * {Integer}
+ */
+ activeRequestCount: 0
+};
+
+/**
+ * Namespace: OpenLayers.Ajax.Responders
+ * {Object}
+ */
+OpenLayers.Ajax.Responders = {
+
+ /**
+ * Property: responders
+ * {Array}
+ */
+ responders: [],
+
+ /**
+ * Method: register
+ *
+ * Parameters:
+ * responderToAdd - {?}
+ */
+ register: function(responderToAdd) {
+ for (var i = 0; i < this.responders.length; i++){
+ if (responderToAdd == this.responders[i]){
+ return;
+ }
+ }
+ this.responders.push(responderToAdd);
+ },
+
+ /**
+ * Method: unregister
+ *
+ * Parameters:
+ * responderToRemove - {?}
+ */
+ unregister: function(responderToRemove) {
+ OpenLayers.Util.removeItem(this.reponders, responderToRemove);
+ },
+
+ /**
+ * Method: dispatch
+ *
+ * Parameters:
+ * callback - {?}
+ * request - {?}
+ * transport - {?}
+ */
+ dispatch: function(callback, request, transport) {
+ var responder;
+ for (var i = 0; i < this.responders.length; i++) {
+ responder = this.responders[i];
+
+ if (responder[callback] &&
+ typeof responder[callback] == 'function') {
+ try {
+ responder[callback].apply(responder,
+ [request, transport]);
+ } catch (e) {}
+ }
+ }
+ }
+};
+
+OpenLayers.Ajax.Responders.register({
+ /**
+ * Function: onCreate
+ */
+ onCreate: function() {
+ OpenLayers.Ajax.activeRequestCount++;
+ },
+
+ /**
+ * Function: onComplete
+ */
+ onComplete: function() {
+ OpenLayers.Ajax.activeRequestCount--;
+ }
+});
+
+/**
+ * Class: OpenLayers.Ajax.Base
+ */
+OpenLayers.Ajax.Base = OpenLayers.Class({
+
+ /**
+ * Constructor: OpenLayers.Ajax.Base
+ *
+ * Parameters:
+ * options - {Object}
+ */
+ initialize: function(options) {
+ this.options = {
+ method: 'post',
+ asynchronous: true,
+ contentType: 'application/xml',
+ parameters: ''
+ };
+ OpenLayers.Util.extend(this.options, options || {});
+
+ this.options.method = this.options.method.toLowerCase();
+
+ if (typeof this.options.parameters == 'string') {
+ this.options.parameters =
+ OpenLayers.Util.getParameters(this.options.parameters);
+ }
+ }
+});
+
+/**
+ * Class: OpenLayers.Ajax.Request
+ * *Deprecated*. Use <OpenLayers.Request> method instead.
+ *
+ * Inherit:
+ * - <OpenLayers.Ajax.Base>
+ */
+OpenLayers.Ajax.Request = OpenLayers.Class(OpenLayers.Ajax.Base, {
+
+ /**
+ * Property: _complete
+ *
+ * {Boolean}
+ */
+ _complete: false,
+
+ /**
+ * Constructor: OpenLayers.Ajax.Request
+ *
+ * Parameters:
+ * url - {String}
+ * options - {Object}
+ */
+ initialize: function(url, options) {
+ OpenLayers.Ajax.Base.prototype.initialize.apply(this, [options]);
+
+ if (OpenLayers.ProxyHost && OpenLayers.String.startsWith(url, "http")) {
+ url = OpenLayers.ProxyHost + encodeURIComponent(url);
+ }
+
+ this.transport = OpenLayers.Ajax.getTransport();
+ this.request(url);
+ },
+
+ /**
+ * Method: request
+ *
+ * Parameters:
+ * url - {String}
+ */
+ request: function(url) {
+ this.url = url;
+ this.method = this.options.method;
+ var params = OpenLayers.Util.extend({}, this.options.parameters);
+
+ if (this.method != 'get' && this.method != 'post') {
+ // simulate other verbs over post
+ params['_method'] = this.method;
+ this.method = 'post';
+ }
+
+ this.parameters = params;
+
+ if (params = OpenLayers.Util.getParameterString(params)) {
+ // when GET, append parameters to URL
+ if (this.method == 'get') {
+ this.url += ((this.url.indexOf('?') > -1) ? '&' : '?') + params;
+ } else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ params += '&_=';
+ }
+ }
+ try {
+ var response = new OpenLayers.Ajax.Response(this);
+ if (this.options.onCreate) {
+ this.options.onCreate(response);
+ }
+
+ OpenLayers.Ajax.Responders.dispatch('onCreate',
+ this,
+ response);
+
+ this.transport.open(this.method.toUpperCase(),
+ this.url,
+ this.options.asynchronous);
+
+ if (this.options.asynchronous) {
+ window.setTimeout(
+ OpenLayers.Function.bind(this.respondToReadyState, this, 1),
+ 10);
+ }
+
+ this.transport.onreadystatechange =
+ OpenLayers.Function.bind(this.onStateChange, this);
+ this.setRequestHeaders();
+
+ this.body = this.method == 'post' ?
+ (this.options.postBody || params) : null;
+ this.transport.send(this.body);
+
+ // Force Firefox to handle ready state 4 for synchronous requests
+ if (!this.options.asynchronous &&
+ this.transport.overrideMimeType) {
+ this.onStateChange();
+ }
+ } catch (e) {
+ this.dispatchException(e);
+ }
+ },
+
+ /**
+ * Method: onStateChange
+ */
+ onStateChange: function() {
+ var readyState = this.transport.readyState;
+ if (readyState > 1 && !((readyState == 4) && this._complete)) {
+ this.respondToReadyState(this.transport.readyState);
+ }
+ },
+
+ /**
+ * Method: setRequestHeaders
+ */
+ setRequestHeaders: function() {
+ var headers = {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
+ 'OpenLayers': true
+ };
+
+ if (this.method == 'post') {
+ headers['Content-type'] = this.options.contentType +
+ (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+ /* Force "Connection: close" for older Mozilla browsers to work
+ * around a bug where XMLHttpRequest sends an incorrect
+ * Content-length header. See Mozilla Bugzilla #246651.
+ */
+ if (this.transport.overrideMimeType &&
+ (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) {
+ headers['Connection'] = 'close';
+ }
+ }
+ // user-defined headers
+ if (typeof this.options.requestHeaders == 'object') {
+ var extras = this.options.requestHeaders;
+
+ if (typeof extras.push == 'function') {
+ for (var i = 0, length = extras.length; i < length; i += 2) {
+ headers[extras[i]] = extras[i+1];
+ }
+ } else {
+ for (var i in extras) {
+ headers[i] = extras[i];
+ }
+ }
+ }
+
+ for (var name in headers) {
+ this.transport.setRequestHeader(name, headers[name]);
+ }
+ },
+
+ /**
+ * Method: success
+ *
+ * Returns:
+ * {Boolean} -
+ */
+ success: function() {
+ var status = this.getStatus();
+ return !status || (status >=200 && status < 300);
+ },
+
+ /**
+ * Method: getStatus
+ *
+ * Returns:
+ * {Integer} - Status
+ */
+ getStatus: function() {
+ try {
+ return this.transport.status || 0;
+ } catch (e) {
+ return 0;
+ }
+ },
+
+ /**
+ * Method: respondToReadyState
+ *
+ * Parameters:
+ * readyState - {?}
+ */
+ respondToReadyState: function(readyState) {
+ var state = OpenLayers.Ajax.Request.Events[readyState];
+ var response = new OpenLayers.Ajax.Response(this);
+
+ if (state == 'Complete') {
+ try {
+ this._complete = true;
+ (this.options['on' + response.status] ||
+ this.options['on' + (this.success() ? 'Success' : 'Failure')] ||
+ OpenLayers.Ajax.emptyFunction)(response);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ var contentType = response.getHeader('Content-type');
+ }
+
+ try {
+ (this.options['on' + state] ||
+ OpenLayers.Ajax.emptyFunction)(response);
+ OpenLayers.Ajax.Responders.dispatch('on' + state,
+ this,
+ response);
+ } catch (e) {
+ this.dispatchException(e);
+ }
+
+ if (state == 'Complete') {
+ // avoid memory leak in MSIE: clean up
+ this.transport.onreadystatechange = OpenLayers.Ajax.emptyFunction;
+ }
+ },
+
+ /**
+ * Method: getHeader
+ *
+ * Parameters:
+ * name - {String} Header name
+ *
+ * Returns:
+ * {?} - response header for the given name
+ */
+ getHeader: function(name) {
+ try {
+ return this.transport.getResponseHeader(name);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ /**
+ * Method: dispatchException
+ * If the optional onException function is set, execute it
+ * and then dispatch the call to any other listener registered
+ * for onException.
+ *
+ * If no optional onException function is set, we suspect that
+ * the user may have also not used
+ * OpenLayers.Ajax.Responders.register to register a listener
+ * for the onException call. To make sure that something
+ * gets done with this exception, only dispatch the call if there
+ * are listeners.
+ *
+ * If you explicitly want to swallow exceptions, set
+ * request.options.onException to an empty function (function(){})
+ * or register an empty function with <OpenLayers.Ajax.Responders>
+ * for onException.
+ *
+ * Parameters:
+ * exception - {?}
+ */
+ dispatchException: function(exception) {
+ var handler = this.options.onException;
+ if(handler) {
+ // call options.onException and alert any other listeners
+ handler(this, exception);
+ OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
+ } else {
+ // check if there are any other listeners
+ var listener = false;
+ var responders = OpenLayers.Ajax.Responders.responders;
+ for (var i = 0; i < responders.length; i++) {
+ if(responders[i].onException) {
+ listener = true;
+ break;
+ }
+ }
+ if(listener) {
+ // call all listeners
+ OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
+ } else {
+ // let the exception through
+ throw exception;
+ }
+ }
+ }
+});
+
+/**
+ * Property: Events
+ * {Array(String)}
+ */
+OpenLayers.Ajax.Request.Events =
+ ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+/**
+ * Class: OpenLayers.Ajax.Response
+ */
+OpenLayers.Ajax.Response = OpenLayers.Class({
+
+ /**
+ * Property: status
+ *
+ * {Integer}
+ */
+ status: 0,
+
+
+ /**
+ * Property: statusText
+ *
+ * {String}
+ */
+ statusText: '',
+
+ /**
+ * Constructor: OpenLayers.Ajax.Response
+ *
+ * Parameters:
+ * request - {Object}
+ */
+ initialize: function(request) {
+ this.request = request;
+ var transport = this.transport = request.transport,
+ readyState = this.readyState = transport.readyState;
+
+ if ((readyState > 2 &&
+ !(!!(window.attachEvent && !window.opera))) ||
+ readyState == 4) {
+ this.status = this.getStatus();
+ this.statusText = this.getStatusText();
+ this.responseText = transport.responseText == null ?
+ '' : String(transport.responseText);
+ }
+
+ if(readyState == 4) {
+ var xml = transport.responseXML;
+ this.responseXML = xml === undefined ? null : xml;
+ }
+ },
+
+ /**
+ * Method: getStatus
+ */
+ getStatus: OpenLayers.Ajax.Request.prototype.getStatus,
+
+ /**
+ * Method: getStatustext
+ *
+ * Returns:
+ * {String} - statusText
+ */
+ getStatusText: function() {
+ try {
+ return this.transport.statusText || '';
+ } catch (e) {
+ return '';
+ }
+ },
+
+ /**
+ * Method: getHeader
+ */
+ getHeader: OpenLayers.Ajax.Request.prototype.getHeader,
+
+ /**
+ * Method: getResponseHeader
+ *
+ * Returns:
+ * {?} - response header for given name
+ */
+ getResponseHeader: function(name) {
+ return this.transport.getResponseHeader(name);
+ }
+});
+
+
+/**
+ * Function: getElementsByTagNameNS
+ *
+ * Parameters:
+ * parentnode - {?}
+ * nsuri - {?}
+ * nsprefix - {?}
+ * tagname - {?}
+ *
+ * Returns:
+ * {?}
+ */
+OpenLayers.Ajax.getElementsByTagNameNS = function(parentnode, nsuri,
+ nsprefix, tagname) {
+ var elem = null;
+ if (parentnode.getElementsByTagNameNS) {
+ elem = parentnode.getElementsByTagNameNS(nsuri, tagname);
+ } else {
+ elem = parentnode.getElementsByTagName(nsprefix + ':' + tagname);
+ }
+ return elem;
+};
+
+
+/**
+ * Function: serializeXMLToString
+ * Wrapper function around XMLSerializer, which doesn't exist/work in
+ * IE/Safari. We need to come up with a way to serialize in those browser:
+ * for now, these browsers will just fail. #535, #536
+ *
+ * Parameters:
+ * xmldom {XMLNode} xml dom to serialize
+ *
+ * Returns:
+ * {?}
+ */
+OpenLayers.Ajax.serializeXMLToString = function(xmldom) {
+ var serializer = new XMLSerializer();
+ var data = serializer.serializeToString(xmldom);
+ return data;
+};
+
+/**
+ * Namespace: OpenLayers.Element
+ */
+OpenLayers.Util.extend(OpenLayers.Element, {
+
+ /**
+ * APIFunction: hide
+ * *Deprecated*. Hide element(s) passed in
+ *
+ * Parameters:
+ * element - {DOMElement} Actually user can pass any number of elements
+ */
+ hide: function() {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", {
+ newMethod: "element.style.display = 'none';"
+ }));
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ if (element) {
+ element.style.display = 'none';
+ }
+ }
+ },
+
+ /**
+ * APIFunction: show
+ * *Deprecated*. Show element(s) passed in
+ *
+ * Parameters:
+ * element - {DOMElement} Actually user can pass any number of elements
+ */
+ show: function() {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", {
+ newMethod: "element.style.display = '';"
+ }));
+
+ for (var i=0, len=arguments.length; i<len; i++) {
+ var element = OpenLayers.Util.getElement(arguments[i]);
+ if (element) {
+ element.style.display = '';
+ }
+ }
+ },
+
+ /**
+ * APIFunction: getDimensions
+ * *Deprecated*. Returns dimensions of the element passed in.
+ *
+ * Parameters:
+ * element - {DOMElement}
+ *
+ * Returns:
+ * {Object} Object with 'width' and 'height' properties which are the
+ * dimensions of the element passed in.
+ */
+ getDimensions: function(element) {
+ element = OpenLayers.Util.getElement(element);
+ if (OpenLayers.Element.getStyle(element, 'display') != 'none') {
+ return {width: element.offsetWidth, height: element.offsetHeight};
+ }
+
+ // All *Width and *Height properties give 0 on elements with display none,
+ // so enable the element temporarily
+ var els = element.style;
+ var originalVisibility = els.visibility;
+ var originalPosition = els.position;
+ var originalDisplay = els.display;
+ els.visibility = 'hidden';
+ els.position = 'absolute';
+ els.display = '';
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ els.display = originalDisplay;
+ els.position = originalPosition;
+ els.visibility = originalVisibility;
+ return {width: originalWidth, height: originalHeight};
+ }
+
+});
+
+if (!String.prototype.startsWith) {
+ /**
+ * APIMethod: String.startsWith
+ * *Deprecated*. Whether or not a string starts with another string.
+ *
+ * Parameters:
+ * sStart - {String} The string we're testing for.
+ *
+ * Returns:
+ * {Boolean} Whether or not this string starts with the string passed in.
+ */
+ String.prototype.startsWith = function(sStart) {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
+ {'newMethod':'OpenLayers.String.startsWith'}));
+ return OpenLayers.String.startsWith(this, sStart);
+ };
+}
+
+if (!String.prototype.contains) {
+ /**
+ * APIMethod: String.contains
+ * *Deprecated*. Whether or not a string contains another string.
+ *
+ * Parameters:
+ * str - {String} The string that we're testing for.
+ *
+ * Returns:
+ * {Boolean} Whether or not this string contains with the string passed in.
+ */
+ String.prototype.contains = function(str) {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
+ {'newMethod':'OpenLayers.String.contains'}));
+ return OpenLayers.String.contains(this, str);
+ };
+}
+
+if (!String.prototype.trim) {
+ /**
+ * APIMethod: String.trim
+ * *Deprecated*. Removes leading and trailing whitespace characters from a string.
+ *
+ * Returns:
+ * {String} A trimmed version of the string - all leading and
+ * trailing spaces removed
+ */
+ String.prototype.trim = function() {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
+ {'newMethod':'OpenLayers.String.trim'}));
+ return OpenLayers.String.trim(this);
+ };
+}
+
+if (!String.prototype.camelize) {
+ /**
+ * APIMethod: String.camelize
+ * *Deprecated*. Camel-case a hyphenated string.
+ * Ex. "chicken-head" becomes "chickenHead", and
+ * "-chicken-head" becomes "ChickenHead".
+ *
+ * Returns:
+ * {String} The string, camelized
+ */
+ String.prototype.camelize = function() {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
+ {'newMethod':'OpenLayers.String.camelize'}));
+ return OpenLayers.String.camelize(this);
+ };
+}
+
+if (!Function.prototype.bind) {
+ /**
+ * APIMethod: Function.bind
+ * *Deprecated*. Bind a function to an object.
+ * Method to easily create closures with 'this' altered.
+ *
+ * Parameters:
+ * object - {Object} the this parameter
+ *
+ * Returns:
+ * {Function} A closure with 'this' altered to the first
+ * argument.
+ */
+ Function.prototype.bind = function() {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
+ {'newMethod':'OpenLayers.Function.bind'}));
+ // new function takes the same arguments with this function up front
+ Array.prototype.unshift.apply(arguments, [this]);
+ return OpenLayers.Function.bind.apply(null, arguments);
+ };
+}
+
+if (!Function.prototype.bindAsEventListener) {
+ /**
+ * APIMethod: Function.bindAsEventListener
+ * *Deprecated*. Bind a function to an object, and configure it to receive the
+ * event object as first parameter when called.
+ *
+ * Parameters:
+ * object - {Object} A reference to this.
+ *
+ * Returns:
+ * {Function}
+ */
+ Function.prototype.bindAsEventListener = function(object) {
+ OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",
+ {'newMethod':'OpenLayers.Function.bindAsEventListener'}));
+ return OpenLayers.Function.bindAsEventListener(this, object);
+ };
+}
+
+// FIXME: Remove this in 3.0. In 3.0, Event.stop will no longer be provided
+// by OpenLayers.
+if (window.Event) {
+ OpenLayers.Util.applyDefaults(window.Event, OpenLayers.Event);
+} else {
+ var Event = OpenLayers.Event;
+}
+
+/**
+ * Namespace: OpenLayers.Tile
+ */
+OpenLayers.Util.extend(OpenLayers.Tile.prototype, {
+ /**
+ * Method: getBoundsFromBaseLayer
+ * Take the pixel locations of the corner of the tile, and pass them to
+ * the base layer and ask for the location of those pixels, so that
+ * displaying tiles over Google works fine.
+ *
+ * Parameters:
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * bounds - {<OpenLayers.Bounds>}
+ */
+ getBoundsFromBaseLayer: function(position) {
+ var msg = OpenLayers.i18n('reprojectDeprecated',
+ {'layerName':this.layer.name});
+ OpenLayers.Console.warn(msg);
+ var topLeft = this.layer.map.getLonLatFromLayerPx(position);
+ var bottomRightPx = position.clone();
+ bottomRightPx.x += this.size.w;
+ bottomRightPx.y += this.size.h;
+ var bottomRight = this.layer.map.getLonLatFromLayerPx(bottomRightPx);
+ // Handle the case where the base layer wraps around the date line.
+ // Google does this, and it breaks WMS servers to request bounds in
+ // that fashion.
+ if (topLeft.lon > bottomRight.lon) {
+ if (topLeft.lon < 0) {
+ topLeft.lon = -180 - (topLeft.lon+180);
+ } else {
+ bottomRight.lon = 180+bottomRight.lon+180;
+ }
+ }
+ var bounds = new OpenLayers.Bounds(topLeft.lon,
+ bottomRight.lat,
+ bottomRight.lon,
+ topLeft.lat);
+ return bounds;
+ }
+});
+
+/**
+ * Class: OpenLayers.Control.MouseDefaults
+ * This class is DEPRECATED in 2.4 and will be removed by 3.0.
+ * If you need this functionality, use <OpenLayers.Control.Navigation>
+ * instead!!!
+ *
+ * Inherits from:
+ * - <OpenLayers.Control>
+ */
+OpenLayers.Control.MouseDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+ /** WARNING WARNING WARNING!!!
+ This class is DEPRECATED in 2.4 and will be removed by 3.0.
+ If you need this functionality, use Control.Navigation instead!!! */
+
+ /**
+ * Property: performedDrag
+ * {Boolean}
+ */
+ performedDrag: false,
+
+ /**
+ * Property: wheelObserver
+ * {Function}
+ */
+ wheelObserver: null,
+
+ /**
+ * Constructor: OpenLayers.Control.MouseDefaults
+ */
+ initialize: function() {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+
+ if (this.handler) {
+ this.handler.destroy();
+ }
+ this.handler = null;
+
+ this.map.events.un({
+ "click": this.defaultClick,
+ "dblclick": this.defaultDblClick,
+ "mousedown": this.defaultMouseDown,
+ "mouseup": this.defaultMouseUp,
+ "mousemove": this.defaultMouseMove,
+ "mouseout": this.defaultMouseOut,
+ scope: this
+ });
+
+ //unregister mousewheel events specifically on the window and document
+ OpenLayers.Event.stopObserving(window, "DOMMouseScroll",
+ this.wheelObserver);
+ OpenLayers.Event.stopObserving(window, "mousewheel",
+ this.wheelObserver);
+ OpenLayers.Event.stopObserving(document, "mousewheel",
+ this.wheelObserver);
+ this.wheelObserver = null;
+
+ OpenLayers.Control.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ this.map.events.on({
+ "click": this.defaultClick,
+ "dblclick": this.defaultDblClick,
+ "mousedown": this.defaultMouseDown,
+ "mouseup": this.defaultMouseUp,
+ "mousemove": this.defaultMouseMove,
+ "mouseout": this.defaultMouseOut,
+ scope: this
+ });
+
+ this.registerWheelEvents();
+
+ },
+
+ /**
+ * Method: registerWheelEvents
+ */
+ registerWheelEvents: function() {
+
+ this.wheelObserver = OpenLayers.Function.bindAsEventListener(
+ this.onWheelEvent, this
+ );
+
+ //register mousewheel events specifically on the window and document
+ OpenLayers.Event.observe(window, "DOMMouseScroll", this.wheelObserver);
+ OpenLayers.Event.observe(window, "mousewheel", this.wheelObserver);
+ OpenLayers.Event.observe(document, "mousewheel", this.wheelObserver);
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ defaultClick: function (evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ var notAfterDrag = !this.performedDrag;
+ this.performedDrag = false;
+ return notAfterDrag;
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ var newCenter = this.map.getLonLatFromViewPortPx( evt.xy );
+ this.map.setCenter(newCenter, this.map.zoom + 1);
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: defaultMouseDown
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseDown: function (evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ this.mouseDragStart = evt.xy.clone();
+ this.performedDrag = false;
+ if (evt.shiftKey) {
+ this.map.div.style.cursor = "crosshair";
+ this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
+ this.mouseDragStart,
+ null,
+ null,
+ "absolute",
+ "2px solid red");
+ this.zoomBox.style.backgroundColor = "white";
+ this.zoomBox.style.filter = "alpha(opacity=50)"; // IE
+ this.zoomBox.style.opacity = "0.50";
+ this.zoomBox.style.fontSize = "1px";
+ this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+ this.map.viewPortDiv.appendChild(this.zoomBox);
+ }
+ document.onselectstart = OpenLayers.Function.False;
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: defaultMouseMove
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseMove: function (evt) {
+ // record the mouse position, used in onWheelEvent
+ this.mousePosition = evt.xy.clone();
+
+ if (this.mouseDragStart != null) {
+ if (this.zoomBox) {
+ var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x);
+ var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y);
+ this.zoomBox.style.width = Math.max(1, deltaX) + "px";
+ this.zoomBox.style.height = Math.max(1, deltaY) + "px";
+ if (evt.xy.x < this.mouseDragStart.x) {
+ this.zoomBox.style.left = evt.xy.x+"px";
+ }
+ if (evt.xy.y < this.mouseDragStart.y) {
+ this.zoomBox.style.top = evt.xy.y+"px";
+ }
+ } else {
+ var deltaX = this.mouseDragStart.x - evt.xy.x;
+ var deltaY = this.mouseDragStart.y - evt.xy.y;
+ var size = this.map.getSize();
+ var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX,
+ size.h / 2 + deltaY);
+ var newCenter = this.map.getLonLatFromViewPortPx( newXY );
+ this.map.setCenter(newCenter, null, true);
+ this.mouseDragStart = evt.xy.clone();
+ this.map.div.style.cursor = "move";
+ }
+ this.performedDrag = true;
+ }
+ },
+
+ /**
+ * Method: defaultMouseUp
+ *
+ * Parameters:
+ * evt - {<OpenLayers.Event>}
+ */
+ defaultMouseUp: function (evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ if (this.zoomBox) {
+ this.zoomBoxEnd(evt);
+ } else {
+ if (this.performedDrag) {
+ this.map.setCenter(this.map.center);
+ }
+ }
+ document.onselectstart=null;
+ this.mouseDragStart = null;
+ this.map.div.style.cursor = "";
+ },
+
+ /**
+ * Method: defaultMouseOut
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseOut: function (evt) {
+ if (this.mouseDragStart != null &&
+ OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if (this.zoomBox) {
+ this.removeZoomBox();
+ }
+ this.mouseDragStart = null;
+ }
+ },
+
+
+ /**
+ * Method: defaultWheelUp
+ * User spun scroll wheel up
+ *
+ */
+ defaultWheelUp: function(evt) {
+ if (this.map.getZoom() <= this.map.getNumZoomLevels()) {
+ this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),
+ this.map.getZoom() + 1);
+ }
+ },
+
+ /**
+ * Method: defaultWheelDown
+ * User spun scroll wheel down
+ */
+ defaultWheelDown: function(evt) {
+ if (this.map.getZoom() > 0) {
+ this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),
+ this.map.getZoom() - 1);
+ }
+ },
+
+ /**
+ * Method: zoomBoxEnd
+ * Zoombox function.
+ */
+ zoomBoxEnd: function(evt) {
+ if (this.mouseDragStart != null) {
+ if (Math.abs(this.mouseDragStart.x - evt.xy.x) > 5 ||
+ Math.abs(this.mouseDragStart.y - evt.xy.y) > 5) {
+ var start = this.map.getLonLatFromViewPortPx( this.mouseDragStart );
+ var end = this.map.getLonLatFromViewPortPx( evt.xy );
+ var top = Math.max(start.lat, end.lat);
+ var bottom = Math.min(start.lat, end.lat);
+ var left = Math.min(start.lon, end.lon);
+ var right = Math.max(start.lon, end.lon);
+ var bounds = new OpenLayers.Bounds(left, bottom, right, top);
+ this.map.zoomToExtent(bounds);
+ } else {
+ var end = this.map.getLonLatFromViewPortPx( evt.xy );
+ this.map.setCenter(new OpenLayers.LonLat(
+ (end.lon),
+ (end.lat)
+ ), this.map.getZoom() + 1);
+ }
+ this.removeZoomBox();
+ }
+ },
+
+ /**
+ * Method: removeZoomBox
+ * Remove the zoombox from the screen and nullify our reference to it.
+ */
+ removeZoomBox: function() {
+ this.map.viewPortDiv.removeChild(this.zoomBox);
+ this.zoomBox = null;
+ },
+
+
+/**
+ * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
+ */
+
+
+ /**
+ * Method: onWheelEvent
+ * Catch the wheel event and handle it xbrowserly
+ *
+ * Parameters:
+ * e - {Event}
+ */
+ onWheelEvent: function(e){
+
+ // first determine whether or not the wheeling was inside the map
+ var inMap = false;
+ var elem = OpenLayers.Event.element(e);
+ while(elem != null) {
+ if (this.map && elem == this.map.div) {
+ inMap = true;
+ break;
+ }
+ elem = elem.parentNode;
+ }
+
+ if (inMap) {
+
+ var delta = 0;
+ if (!e) {
+ e = window.event;
+ }
+ if (e.wheelDelta) {
+ delta = e.wheelDelta/120;
+ if (window.opera && window.opera.version() < 9.2) {
+ delta = -delta;
+ }
+ } else if (e.detail) {
+ delta = -e.detail / 3;
+ }
+ if (delta) {
+ // add the mouse position to the event because mozilla has a bug
+ // with clientX and clientY (see https://bugzilla.mozilla.org/show_bug.cgi?id=352179)
+ // getLonLatFromViewPortPx(e) returns wrong values
+ e.xy = this.mousePosition;
+
+ if (delta < 0) {
+ this.defaultWheelDown(e);
+ } else {
+ this.defaultWheelUp(e);
+ }
+ }
+
+ //only wheel the map, not the window
+ OpenLayers.Event.stop(e);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.MouseDefaults"
+});
+
+/**
+ * Class: OpenLayers.Control.MouseToolbar
+ * This class is DEPRECATED in 2.4 and will be removed by 3.0.
+ * If you need this functionality, use <OpenLayers.Control.NavToolbar>
+ * instead!!!
+ */
+OpenLayers.Control.MouseToolbar = OpenLayers.Class(
+ OpenLayers.Control.MouseDefaults, {
+
+ /**
+ * Property: mode
+ */
+ mode: null,
+ /**
+ * Property: buttons
+ */
+ buttons: null,
+
+ /**
+ * APIProperty: direction
+ * {String} 'vertical' or 'horizontal'
+ */
+ direction: "vertical",
+
+ /**
+ * Property: buttonClicked
+ * {String}
+ */
+ buttonClicked: null,
+
+ /**
+ * Constructor: OpenLayers.Control.MouseToolbar
+ *
+ * Parameters:
+ * position - {<OpenLayers.Pixel>}
+ * direction - {String}
+ */
+ initialize: function(position, direction) {
+ OpenLayers.Control.prototype.initialize.apply(this, arguments);
+ this.position = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,
+ OpenLayers.Control.MouseToolbar.Y);
+ if (position) {
+ this.position = position;
+ }
+ if (direction) {
+ this.direction = direction;
+ }
+ this.measureDivs = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ for( var btnId in this.buttons) {
+ var btn = this.buttons[btnId];
+ btn.map = null;
+ btn.events.destroy();
+ }
+ OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this,
+ arguments);
+ },
+
+ /**
+ * Method: draw
+ */
+ draw: function() {
+ OpenLayers.Control.prototype.draw.apply(this, arguments);
+ OpenLayers.Control.MouseDefaults.prototype.draw.apply(this, arguments);
+ this.buttons = {};
+ var sz = new OpenLayers.Size(28,28);
+ var centered = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0);
+ this._addButton("zoombox", "drag-rectangle-off.png", "drag-rectangle-on.png", centered, sz, "Shift->Drag to zoom to area");
+ centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0));
+ this._addButton("pan", "panning-hand-off.png", "panning-hand-on.png", centered, sz, "Drag the map to pan.");
+ centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0));
+ this.switchModeTo("pan");
+
+ return this.div;
+ },
+
+ /**
+ * Method: _addButton
+ */
+ _addButton:function(id, img, activeImg, xy, sz, title) {
+ var imgLocation = OpenLayers.Util.getImageLocation(img);
+ var activeImgLocation = OpenLayers.Util.getImageLocation(activeImg);
+ // var btn = new ol.AlphaImage("_"+id, imgLocation, xy, sz);
+ var btn = OpenLayers.Util.createAlphaImageDiv(
+ "OpenLayers_Control_MouseToolbar_" + id,
+ xy, sz, imgLocation, "absolute");
+
+ //we want to add the outer div
+ this.div.appendChild(btn);
+ btn.imgLocation = imgLocation;
+ btn.activeImgLocation = activeImgLocation;
+
+ btn.events = new OpenLayers.Events(this, btn, null, true);
+ btn.events.on({
+ "mousedown": this.buttonDown,
+ "mouseup": this.buttonUp,
+ "dblclick": OpenLayers.Event.stop,
+ scope: this
+ });
+ btn.action = id;
+ btn.title = title;
+ btn.alt = title;
+ btn.map = this.map;
+
+ //we want to remember/reference the outer div
+ this.buttons[id] = btn;
+ return btn;
+ },
+
+ /**
+ * Method: buttonDown
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonDown: function(evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ this.buttonClicked = evt.element.action;
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: buttonUp
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ buttonUp: function(evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ if (this.buttonClicked != null) {
+ if (this.buttonClicked == evt.element.action) {
+ this.switchModeTo(evt.element.action);
+ }
+ OpenLayers.Event.stop(evt);
+ this.buttonClicked = null;
+ }
+ },
+
+ /**
+ * Method: defaultDblClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultDblClick: function (evt) {
+ this.switchModeTo("pan");
+ this.performedDrag = false;
+ var newCenter = this.map.getLonLatFromViewPortPx( evt.xy );
+ this.map.setCenter(newCenter, this.map.zoom + 1);
+ OpenLayers.Event.stop(evt);
+ return false;
+ },
+
+ /**
+ * Method: defaultMouseDown
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseDown: function (evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ this.mouseDragStart = evt.xy.clone();
+ this.performedDrag = false;
+ this.startViaKeyboard = false;
+ if (evt.shiftKey && this.mode !="zoombox") {
+ this.switchModeTo("zoombox");
+ this.startViaKeyboard = true;
+ } else if (evt.altKey && this.mode !="measure") {
+ this.switchModeTo("measure");
+ } else if (!this.mode) {
+ this.switchModeTo("pan");
+ }
+
+ switch (this.mode) {
+ case "zoombox":
+ this.map.div.style.cursor = "crosshair";
+ this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
+ this.mouseDragStart,
+ null,
+ null,
+ "absolute",
+ "2px solid red");
+ this.zoomBox.style.backgroundColor = "white";
+ this.zoomBox.style.filter = "alpha(opacity=50)"; // IE
+ this.zoomBox.style.opacity = "0.50";
+ this.zoomBox.style.fontSize = "1px";
+ this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+ this.map.viewPortDiv.appendChild(this.zoomBox);
+ this.performedDrag = true;
+ break;
+ case "measure":
+ var distance = "";
+ if (this.measureStart) {
+ var measureEnd = this.map.getLonLatFromViewPortPx(this.mouseDragStart);
+ distance = OpenLayers.Util.distVincenty(this.measureStart, measureEnd);
+ distance = Math.round(distance * 100) / 100;
+ distance = distance + "km";
+ this.measureStartBox = this.measureBox;
+ }
+ this.measureStart = this.map.getLonLatFromViewPortPx(this.mouseDragStart);;
+ this.measureBox = OpenLayers.Util.createDiv(null,
+ this.mouseDragStart.add(
+ -2-parseInt(this.map.layerContainerDiv.style.left),
+ -2-parseInt(this.map.layerContainerDiv.style.top)),
+ null,
+ null,
+ "absolute");
+ this.measureBox.style.width="4px";
+ this.measureBox.style.height="4px";
+ this.measureBox.style.fontSize = "1px";
+ this.measureBox.style.backgroundColor="red";
+ this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+ this.map.layerContainerDiv.appendChild(this.measureBox);
+ if (distance) {
+ this.measureBoxDistance = OpenLayers.Util.createDiv(null,
+ this.mouseDragStart.add(
+ -2-parseInt(this.map.layerContainerDiv.style.left),
+ 2-parseInt(this.map.layerContainerDiv.style.top)),
+ null,
+ null,
+ "absolute");
+
+ this.measureBoxDistance.innerHTML = distance;
+ this.measureBoxDistance.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+ this.map.layerContainerDiv.appendChild(this.measureBoxDistance);
+ this.measureDivs.push(this.measureBoxDistance);
+ }
+ this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+ this.map.layerContainerDiv.appendChild(this.measureBox);
+ this.measureDivs.push(this.measureBox);
+ break;
+ default:
+ this.map.div.style.cursor = "move";
+ break;
+ }
+ document.onselectstart = OpenLayers.Function.False;
+ OpenLayers.Event.stop(evt);
+ },
+
+ /**
+ * Method: switchModeTo
+ *
+ * Parameters:
+ * mode - {String}
+ */
+ switchModeTo: function(mode) {
+ if (mode != this.mode) {
+
+
+ if (this.mode && this.buttons[this.mode]) {
+ OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode], null, null, null, this.buttons[this.mode].imgLocation);
+ }
+ if (this.mode == "measure" && mode != "measure") {
+ for(var i=0, len=this.measureDivs.length; i<len; i++) {
+ if (this.measureDivs[i]) {
+ this.map.layerContainerDiv.removeChild(this.measureDivs[i]);
+ }
+ }
+ this.measureDivs = [];
+ this.measureStart = null;
+ }
+ this.mode = mode;
+ if (this.buttons[mode]) {
+ OpenLayers.Util.modifyAlphaImageDiv(this.buttons[mode], null, null, null, this.buttons[mode].activeImgLocation);
+ }
+ switch (this.mode) {
+ case "zoombox":
+ this.map.div.style.cursor = "crosshair";
+ break;
+ default:
+ this.map.div.style.cursor = "";
+ break;
+ }
+
+ }
+ },
+
+ /**
+ * Method: leaveMode
+ */
+ leaveMode: function() {
+ this.switchModeTo("pan");
+ },
+
+ /**
+ * Method: defaultMouseMove
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseMove: function (evt) {
+ if (this.mouseDragStart != null) {
+ switch (this.mode) {
+ case "zoombox":
+ var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x);
+ var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y);
+ this.zoomBox.style.width = Math.max(1, deltaX) + "px";
+ this.zoomBox.style.height = Math.max(1, deltaY) + "px";
+ if (evt.xy.x < this.mouseDragStart.x) {
+ this.zoomBox.style.left = evt.xy.x+"px";
+ }
+ if (evt.xy.y < this.mouseDragStart.y) {
+ this.zoomBox.style.top = evt.xy.y+"px";
+ }
+ break;
+ default:
+ var deltaX = this.mouseDragStart.x - evt.xy.x;
+ var deltaY = this.mouseDragStart.y - evt.xy.y;
+ var size = this.map.getSize();
+ var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX,
+ size.h / 2 + deltaY);
+ var newCenter = this.map.getLonLatFromViewPortPx( newXY );
+ this.map.setCenter(newCenter, null, true);
+ this.mouseDragStart = evt.xy.clone();
+ }
+ this.performedDrag = true;
+ }
+ },
+
+ /**
+ * Method: defaultMouseUp
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseUp: function (evt) {
+ if (!OpenLayers.Event.isLeftClick(evt)) {
+ return;
+ }
+ switch (this.mode) {
+ case "zoombox":
+ this.zoomBoxEnd(evt);
+ if (this.startViaKeyboard) {
+ this.leaveMode();
+ }
+ break;
+ case "pan":
+ if (this.performedDrag) {
+ this.map.setCenter(this.map.center);
+ }
+ }
+ document.onselectstart = null;
+ this.mouseDragStart = null;
+ this.map.div.style.cursor = "default";
+ },
+
+ /**
+ * Method: defaultMouseOut
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultMouseOut: function (evt) {
+ if (this.mouseDragStart != null
+ && OpenLayers.Util.mouseLeft(evt, this.map.viewPortDiv)) {
+ if (this.zoomBox) {
+ this.removeZoomBox();
+ if (this.startViaKeyboard) {
+ this.leaveMode();
+ }
+ }
+ this.mouseDragStart = null;
+ this.map.div.style.cursor = "default";
+ }
+ },
+
+ /**
+ * Method: defaultClick
+ *
+ * Parameters:
+ * evt - {Event}
+ */
+ defaultClick: function (evt) {
+ if (this.performedDrag) {
+ this.performedDrag = false;
+ return false;
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Control.MouseToolbar"
+});
+
+OpenLayers.Control.MouseToolbar.X = 6;
+OpenLayers.Control.MouseToolbar.Y = 300;
+
+/**
+ * Class: OpenLayers.Layer.Grid
+ */
+
+OpenLayers.Util.extend(OpenLayers.Layer.Grid.prototype, {
+
+ /**
+ * Method: getGridBounds
+ * Deprecated. This function will be removed in 3.0. Please use
+ * getTilesBounds() instead.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
+ * currently loaded tiles (including those partially or not at all seen
+ * onscreen)
+ */
+ getGridBounds: function() {
+ var msg = "The getGridBounds() function is deprecated. It will be " +
+ "removed in 3.0. Please use getTilesBounds() instead.";
+ OpenLayers.Console.warn(msg);
+ return this.getTilesBounds();
+ }
+});
+
+/**
+ * Class: OpenLayers.Format.XML
+ */
+OpenLayers.Util.extend(OpenLayers.Format.XML.prototype, {
+
+ /**
+ * APIMethod: concatChildValues
+ * *Deprecated*. Use <getChildValue> instead.
+ *
+ * Concatenate the value of all child nodes if any exist, or return an
+ * optional default string. Returns an empty string if no children
+ * exist and no default value is supplied. Not optimized for large
+ * numbers of child nodes.
+ *
+ * Parameters:
+ * node - {DOMElement} The element used to look for child values.
+ * def - {String} Optional string to return in the event that no
+ * child exist.
+ *
+ * Returns:
+ * {String} The concatenated value of all child nodes of the given node.
+ */
+ concatChildValues: function(node, def) {
+ var value = "";
+ var child = node.firstChild;
+ var childValue;
+ while(child) {
+ childValue = child.nodeValue;
+ if(childValue) {
+ value += childValue;
+ }
+ child = child.nextSibling;
+ }
+ if(value == "" && def != undefined) {
+ value = def;
+ }
+ return value;
+ }
+
+});
+
+/**
+ * Class: OpenLayers.Layer.WMS.Post
+ * Instances of OpenLayers.Layer.WMS.Post are used to retrieve data from OGC
+ * Web Mapping Services via HTTP-POST (application/x-www-form-urlencoded).
+ * Create a new WMS layer with the <OpenLayers.Layer.WMS.Post> constructor.
+ *
+ * *Deprecated*. Instead of this layer, use <OpenLayers.Layer.WMS> with
+ * <OpenLayers.Tile.Image.maxGetUrlLength> configured in the layer's
+ * <OpenLayers.Layer.WMS.tileOptions>.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.WMS>
+ */
+OpenLayers.Layer.WMS.Post = OpenLayers.Class(OpenLayers.Layer.WMS, {
+
+ /**
+ * APIProperty: unsupportedBrowsers
+ * {Array} Array with browsers, which should use the HTTP-GET protocol
+ * instead of HTTP-POST for fetching tiles from a WMS .
+ * Defaults to ["mozilla", "firefox", "opera"], because Opera is not able
+ * to show transparent images in IFrames and Firefox/Mozilla has some ugly
+ * effects of viewport-shaking when panning the map. Both browsers, Opera
+ * and Firefox/Mozilla, have no problem with long urls, which is the reason
+ * for using POST instead of GET. The strings to pass to this array are
+ * the ones returned by <OpenLayers.BROWSER_NAME>.
+ */
+ unsupportedBrowsers: ["mozilla", "firefox", "opera"],
+
+ /**
+ * Property: SUPPORTED_TRANSITIONS
+ * {Array}
+ * no supported transitions for this type of layer, because it is not
+ * possible to modify the initialized tiles (iframes)
+ */
+ SUPPORTED_TRANSITIONS: [],
+
+ /**
+ * Property: usePost
+ * {Boolean}
+ */
+ usePost: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS.Post
+ * Creates a new WMS layer object.
+ *
+ * Example:
+ * (code)
+ * var wms = new OpenLayers.Layer.WMS.Post(
+ * "NASA Global Mosaic",
+ * "http://wms.jpl.nasa.gov/wms.cgi",
+ * {layers: "modis, global_mosaic"});
+ * (end)
+ *
+ * Parameters:
+ * name - {String} A name for the layer
+ * url - {String} Base url for the WMS
+ * (e.g. http://wms.jpl.nasa.gov/wms.cgi)
+ * params - {Object} An object with key/value pairs representing the
+ * GetMap query string parameters and parameter values.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ */
+ initialize: function(name, url, params, options) {
+ var newArguments = [];
+ newArguments.push(name, url, params, options);
+ OpenLayers.Layer.WMS.prototype.initialize.apply(this, newArguments);
+
+ this.usePost = OpenLayers.Util.indexOf(
+ this.unsupportedBrowsers, OpenLayers.BROWSER_NAME) == -1;
+ },
+
+ /**
+ * Method: addTile
+ * addTile creates a tile, initializes it and adds it as iframe to the
+ * layer div.
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * position - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {<OpenLayers.Tile.Image.IFrame>} The added OpenLayers.Tile.Image.IFrame
+ */
+ addTile: function(bounds,position) {
+ return new OpenLayers.Tile.Image(
+ this, position, bounds, null, this.tileSize, {
+ maxGetUrlLength: this.usePost ? 0 : null
+ });
+ },
+
+ CLASS_NAME: 'OpenLayers.Layer.WMS.Post'
+});
+
+/**
+ * Class: OpenLayers.Layer.WMS.Untiled
+ * *Deprecated*. To be removed in 3.0. Instead use OpenLayers.Layer.WMS and
+ * pass the option 'singleTile' as true.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.WMS>
+ */
+OpenLayers.Layer.WMS.Untiled = OpenLayers.Class(OpenLayers.Layer.WMS, {
+
+ /**
+ * APIProperty: singleTile
+ * {singleTile} Always true for untiled.
+ */
+ singleTile: true,
+
+ /**
+ * Constructor: OpenLayers.Layer.WMS.Untiled
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object}
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.WMS.prototype.initialize.apply(this, arguments);
+
+ var msg = "The OpenLayers.Layer.WMS.Untiled class is deprecated and " +
+ "will be removed in 3.0. Instead, you should use the " +
+ "normal OpenLayers.Layer.WMS class, passing it the option " +
+ "'singleTile' as true.";
+ OpenLayers.Console.warn(msg);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WMS.Untiled>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WMS.Untiled(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.WMS.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WMS.Untiled"
+});
+
+/**
+ * Class: OpenLayers.Layer.MapServer.Untiled
+ * *Deprecated*. To be removed in 3.0. Instead use OpenLayers.Layer.MapServer
+ * and pass the option 'singleTile' as true.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.MapServer>
+ */
+OpenLayers.Layer.MapServer.Untiled = OpenLayers.Class(OpenLayers.Layer.MapServer, {
+
+ /**
+ * APIProperty: singleTile
+ * {singleTile} Always true for untiled.
+ */
+ singleTile: true,
+
+ /**
+ * Constructor: OpenLayers.Layer.MapServer.Untiled
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object}
+ */
+ initialize: function(name, url, params, options) {
+ OpenLayers.Layer.MapServer.prototype.initialize.apply(this, arguments);
+
+ var msg = "The OpenLayers.Layer.MapServer.Untiled class is deprecated and " +
+ "will be removed in 3.0. Instead, you should use the " +
+ "normal OpenLayers.Layer.MapServer class, passing it the option " +
+ "'singleTile' as true.";
+ OpenLayers.Console.warn(msg);
+ },
+
+ /**
+ * Method: clone
+ * Create a clone of this layer
+ *
+ * Returns:
+ * {<OpenLayers.Layer.MapServer.Untiled>} An exact clone of this layer
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.MapServer.Untiled(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ obj = OpenLayers.Layer.MapServer.prototype.clone.apply(this, [obj]);
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.MapServer.Untiled"
+});
+
+/**
+ * Class: OpenLayers.Tile.WFS
+ * Instances of OpenLayers.Tile.WFS are used to manage the image tiles
+ * used by various layers. Create a new image tile with the
+ * <OpenLayers.Tile.WFS> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.WFS = OpenLayers.Class(OpenLayers.Tile, {
+
+ /**
+ * Property: features
+ * {Array(<OpenLayers.Feature>)} list of features in this tile
+ */
+ features: null,
+
+ /**
+ * Property: url
+ * {String}
+ */
+ url: null,
+
+ /**
+ * Property: request
+ * {<OpenLayers.Request.XMLHttpRequest>}
+ */
+ request: null,
+
+ /** TBD 3.0 - reorder the parameters to the init function to put URL
+ * as last, so we can continue to call tile.initialize()
+ * without changing the arguments.
+ *
+ * Constructor: OpenLayers.Tile.WFS
+ * Constructor for a new <OpenLayers.Tile.WFS> instance.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+ * position - {<OpenLayers.Pixel>}
+ * bounds - {<OpenLayers.Bounds>}
+ * url - {<String>}
+ * size - {<OpenLayers.Size>}
+ */
+ initialize: function(layer, position, bounds, url, size) {
+ OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+ this.url = url;
+ this.features = [];
+ },
+
+ /**
+ * APIMethod: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+ this.destroyAllFeatures();
+ this.features = null;
+ this.url = null;
+ if(this.request) {
+ this.request.abort();
+ //this.request.destroy();
+ this.request = null;
+ }
+ },
+
+ /**
+ * Method: clear
+ * Clear the tile of any bounds/position-related data so that it can
+ * be reused in a new location.
+ */
+ clear: function() {
+ this.destroyAllFeatures();
+ },
+
+ /**
+ * Method: draw
+ * Check that a tile should be drawn, and load features for it.
+ */
+ draw:function() {
+ if (OpenLayers.Tile.prototype.draw.apply(this, arguments)) {
+ if (this.isLoading) {
+ //if already loading, send 'reload' instead of 'loadstart'.
+ this.events.triggerEvent("reload");
+ } else {
+ this.isLoading = true;
+ this.events.triggerEvent("loadstart");
+ }
+ this.loadFeaturesForRegion(this.requestSuccess);
+ }
+ },
+
+ /**
+ * Method: loadFeaturesForRegion
+ * Abort any pending requests and issue another request for data.
+ *
+ * Input are function pointers for what to do on success and failure.
+ *
+ * Parameters:
+ * success - {function}
+ * failure - {function}
+ */
+ loadFeaturesForRegion:function(success, failure) {
+ if(this.request) {
+ this.request.abort();
+ }
+ this.request = OpenLayers.Request.GET({
+ url: this.url,
+ success: success,
+ failure: failure,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: requestSuccess
+ * Called on return from request succcess. Adds results via
+ * layer.addFeatures in vector mode, addResults otherwise.
+ *
+ * Parameters:
+ * request - {<OpenLayers.Request.XMLHttpRequest>}
+ */
+ requestSuccess:function(request) {
+ if (this.features) {
+ var doc = request.responseXML;
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+ if (this.layer.vectorMode) {
+ this.layer.addFeatures(this.layer.formatObject.read(doc));
+ } else {
+ var xml = new OpenLayers.Format.XML();
+ if (typeof doc == "string") {
+ doc = xml.read(doc);
+ }
+ var resultFeatures = xml.getElementsByTagNameNS(
+ doc, "http://www.opengis.net/gml", "featureMember"
+ );
+ this.addResults(resultFeatures);
+ }
+ }
+ if (this.events) {
+ this.events.triggerEvent("loadend");
+ }
+
+ //request produced with success, we can delete the request object.
+ //this.request.destroy();
+ this.request = null;
+ },
+
+ /**
+ * Method: addResults
+ * Construct new feature via layer featureClass constructor, and add to
+ * this.features.
+ *
+ * Parameters:
+ * results - {Object}
+ */
+ addResults: function(results) {
+ for (var i=0; i < results.length; i++) {
+ var feature = new this.layer.featureClass(this.layer,
+ results[i]);
+ this.features.push(feature);
+ }
+ },
+
+
+ /**
+ * Method: destroyAllFeatures
+ * Iterate through and call destroy() on each feature, removing it from
+ * the local array
+ */
+ destroyAllFeatures: function() {
+ while(this.features.length > 0) {
+ var feature = this.features.shift();
+ feature.destroy();
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Tile.WFS"
+ }
+);
+
+/**
+ * Class: OpenLayers.Feature.WFS
+ * WFS handling class, for use as a featureClass on the WFS layer for handling
+ * 'point' WFS types. Good for subclassing when creating a custom WFS like
+ * XML application.
+ *
+ * Inherits from:
+ * - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.WFS = OpenLayers.Class(OpenLayers.Feature, {
+
+ /**
+ * Constructor: OpenLayers.Feature.WFS
+ * Create a WFS feature.
+ *
+ * Parameters:
+ * layer - {<OpenLayers.Layer>}
+ * xmlNode - {XMLNode}
+ */
+ initialize: function(layer, xmlNode) {
+ var newArguments = arguments;
+ var data = this.processXMLNode(xmlNode);
+ newArguments = new Array(layer, data.lonlat, data);
+ OpenLayers.Feature.prototype.initialize.apply(this, newArguments);
+ this.createMarker();
+ this.layer.addMarker(this.marker);
+ },
+
+ /**
+ * Method: destroy
+ * nullify references to prevent circular references and memory leaks
+ */
+ destroy: function() {
+ if (this.marker != null) {
+ this.layer.removeMarker(this.marker);
+ }
+ OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+ },
+
+ /**
+ * Method: processXMLNode
+ * When passed an xmlNode, parses it for a GML point, and passes
+ * back an object describing that point.
+ *
+ * For subclasses of Feature.WFS, this is the feature to change.
+ *
+ * Parameters:
+ * xmlNode - {XMLNode}
+ *
+ * Returns:
+ * {Object} Data Object with 'id', 'lonlat', and private properties set
+ */
+ processXMLNode: function(xmlNode) {
+ //this should be overridden by subclasses
+ // must return an Object with 'id' and 'lonlat' values set
+ var point = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, "http://www.opengis.net/gml", "gml", "Point");
+ var text = OpenLayers.Util.getXmlNodeValue(OpenLayers.Ajax.getElementsByTagNameNS(point[0], "http://www.opengis.net/gml","gml", "coordinates")[0]);
+ var floats = text.split(",");
+ return {lonlat: new OpenLayers.LonLat(parseFloat(floats[0]),
+ parseFloat(floats[1])),
+ id: null};
+
+ },
+
+ CLASS_NAME: "OpenLayers.Feature.WFS"
+});
+
+
+/**
+ * Class: OpenLayers.Layer.WFS
+ * *Deprecated*. To be removed in 3.0. Instead use OpenLayers.Layer.Vector
+ * with a Protocol.WFS and one or more Strategies.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ * - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.WFS = OpenLayers.Class(
+ OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, {
+
+ /**
+ * APIProperty: isBaseLayer
+ * {Boolean} WFS layer is not a base layer by default.
+ */
+ isBaseLayer: false,
+
+ /**
+ * Property: tile
+ * {<OpenLayers.Tile.WFS>}
+ */
+ tile: null,
+
+ /**
+ * APIProperty: ratio
+ * {Float} The ratio property determines the size of the serverside query
+ * relative to the map viewport size. By default, we load an area twice
+ * as big as the map, to allow for panning without immediately reload.
+ * Setting this to 1 will cause the area of the WFS request to match
+ * the map area exactly. It is recommended to set this to some number
+ * at least slightly larger than 1, otherwise accidental clicks can
+ * cause a data reload, by moving the map only 1 pixel.
+ */
+ ratio: 2,
+
+ /**
+ * Property: DEFAULT_PARAMS
+ * {Object} Hashtable of default key/value parameters
+ */
+ DEFAULT_PARAMS: { service: "WFS",
+ version: "1.0.0",
+ request: "GetFeature"
+ },
+
+ /**
+ * APIProperty: featureClass
+ * {<OpenLayers.Feature>} If featureClass is defined, an old-style markers
+ * based WFS layer is created instead of a new-style vector layer. If
+ * sent, this should be a subclass of OpenLayers.Feature
+ */
+ featureClass: null,
+
+ /**
+ * APIProperty: format
+ * {<OpenLayers.Format>} The format you want the data to be parsed with.
+ * Must be passed in the constructor. Should be a class, not an instance.
+ * This option can only be used if no featureClass is passed / vectorMode
+ * is false: if a featureClass is passed, then this parameter is ignored.
+ */
+ format: null,
+
+ /**
+ * Property: formatObject
+ * {<OpenLayers.Format>} Internally created/managed format object, used by
+ * the Tile to parse data.
+ */
+ formatObject: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Hash of options which should be passed to the format when it is
+ * created. Must be passed in the constructor.
+ */
+ formatOptions: null,
+
+ /**
+ * Property: vectorMode
+ * {Boolean} Should be calculated automatically. Determines whether the
+ * layer is in vector mode or marker mode.
+ */
+ vectorMode: true,
+
+ /**
+ * APIProperty: encodeBBOX
+ * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no',
+ * but some services want it that way. Default false.
+ */
+ encodeBBOX: false,
+
+ /**
+ * APIProperty: extractAttributes
+ * {Boolean} Should the WFS layer parse attributes from the retrieved
+ * GML? Defaults to false. If enabled, parsing is slower, but
+ * attributes are available in the attributes property of
+ * layer features.
+ */
+ extractAttributes: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.WFS
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String}
+ * params - {Object}
+ * options - {Object} Hashtable of extra options to tag onto the layer
+ */
+ initialize: function(name, url, params, options) {
+ if (options == undefined) { options = {}; }
+
+ if (options.featureClass ||
+ !OpenLayers.Layer.Vector ||
+ !OpenLayers.Feature.Vector) {
+ this.vectorMode = false;
+ }
+
+ // Uppercase params
+ params = OpenLayers.Util.upperCaseObject(params);
+
+ // Turn off error reporting, browsers like Safari may work
+ // depending on the setup, and we don't want an unneccesary alert.
+ OpenLayers.Util.extend(options, {'reportError': false});
+ var newArguments = [];
+ newArguments.push(name, options);
+ OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
+ if (!this.renderer || !this.vectorMode) {
+ this.vectorMode = false;
+ if (!options.featureClass) {
+ options.featureClass = OpenLayers.Feature.WFS;
+ }
+ OpenLayers.Layer.Markers.prototype.initialize.apply(this,
+ newArguments);
+ }
+
+ if (this.params && this.params.typename && !this.options.typename) {
+ this.options.typename = this.params.typename;
+ }
+
+ if (!this.options.geometry_column) {
+ this.options.geometry_column = "the_geom";
+ }
+
+ this.params = OpenLayers.Util.applyDefaults(
+ params,
+ OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+ );
+ this.url = url;
+ },
+
+
+ /**
+ * APIMethod: destroy
+ */
+ destroy: function() {
+ if (this.vectorMode) {
+ OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments);
+ } else {
+ OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+ }
+ if (this.tile) {
+ this.tile.destroy();
+ }
+ this.tile = null;
+
+ this.ratio = null;
+ this.featureClass = null;
+ this.format = null;
+
+ if (this.formatObject && this.formatObject.destroy) {
+ this.formatObject.destroy();
+ }
+ this.formatObject = null;
+
+ this.formatOptions = null;
+ this.vectorMode = null;
+ this.encodeBBOX = null;
+ this.extractAttributes = null;
+ },
+
+ /**
+ * Method: setMap
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ if (this.vectorMode) {
+ OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+
+ var options = {
+ 'extractAttributes': this.extractAttributes
+ };
+
+ OpenLayers.Util.extend(options, this.formatOptions);
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+ } else {
+ OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Method: moveTo
+ *
+ * Parameters:
+ * bounds - {<OpenLayers.Bounds>}
+ * zoomChanged - {Boolean}
+ * dragging - {Boolean}
+ */
+ moveTo:function(bounds, zoomChanged, dragging) {
+ if (this.vectorMode) {
+ OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+ } else {
+ OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+ }
+
+ // don't load wfs features while dragging, wait for drag end
+ if (dragging) {
+ // TBD try to hide the vector layer while dragging
+ // this.setVisibility(false);
+ // this will probably help for panning performances
+ return false;
+ }
+
+ if ( zoomChanged ) {
+ if (this.vectorMode) {
+ this.renderer.clear();
+ }
+ }
+
+ //DEPRECATED - REMOVE IN 3.0
+ // don't load data if current zoom level doesn't match
+ if (this.options.minZoomLevel) {
+ OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError'));
+
+ if (this.map.getZoom() < this.options.minZoomLevel) {
+ return null;
+ }
+ }
+
+ if (bounds == null) {
+ bounds = this.map.getExtent();
+ }
+
+ var firstRendering = (this.tile == null);
+
+ //does the new bounds to which we need to move fall outside of the
+ // current tile's bounds?
+ var outOfBounds = (!firstRendering &&
+ !this.tile.bounds.containsBounds(bounds));
+
+ if (zoomChanged || firstRendering || (!dragging && outOfBounds)) {
+ //determine new tile bounds
+ var center = bounds.getCenterLonLat();
+ var tileWidth = bounds.getWidth() * this.ratio;
+ var tileHeight = bounds.getHeight() * this.ratio;
+ var tileBounds =
+ new OpenLayers.Bounds(center.lon - (tileWidth / 2),
+ center.lat - (tileHeight / 2),
+ center.lon + (tileWidth / 2),
+ center.lat + (tileHeight / 2));
+
+ //determine new tile size
+ var tileSize = this.map.getSize();
+ tileSize.w = tileSize.w * this.ratio;
+ tileSize.h = tileSize.h * this.ratio;
+
+ //determine new position (upper left corner of new bounds)
+ var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
+ var pos = this.map.getLayerPxFromLonLat(ul);
+
+ //formulate request url string
+ var url = this.getFullRequestString();
+
+ var params = null;
+
+ // Cant combine "filter" and "BBOX". This is a cheap hack to help
+ // people out who can't migrate to the WFS protocol immediately.
+ var filter = this.params.filter || this.params.FILTER;
+ if (filter) {
+ params = {FILTER: filter};
+ }
+ else {
+ params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX()
+ : tileBounds.toArray()};
+ }
+
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ var projectedBounds = tileBounds.clone();
+ projectedBounds.transform(this.map.getProjectionObject(),
+ this.projection);
+ if (!filter){
+ params.BBOX = this.encodeBBOX ? projectedBounds.toBBOX()
+ : projectedBounds.toArray();
+ }
+ }
+
+ url += "&" + OpenLayers.Util.getParameterString(params);
+
+ if (!this.tile) {
+ this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds,
+ url, tileSize);
+ this.addTileMonitoringHooks(this.tile);
+ this.tile.draw();
+ } else {
+ if (this.vectorMode) {
+ this.destroyFeatures();
+ this.renderer.clear();
+ } else {
+ this.clearMarkers();
+ }
+ this.removeTileMonitoringHooks(this.tile);
+ this.tile.destroy();
+
+ this.tile = null;
+ this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds,
+ url, tileSize);
+ this.addTileMonitoringHooks(this.tile);
+ this.tile.draw();
+ }
+ }
+ },
+
+ /**
+ * Method: addTileMonitoringHooks
+ * This function takes a tile as input and adds the appropriate hooks to
+ * the tile so that the layer can keep track of the loading tile
+ * (making sure to check that the tile is always the layer's current
+ * tile before taking any action).
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ addTileMonitoringHooks: function(tile) {
+ tile.onLoadStart = function() {
+ //if this is the the layer's current tile, then trigger
+ // a 'loadstart'
+ if (this == this.layer.tile) {
+ this.layer.events.triggerEvent("loadstart");
+ }
+ };
+ tile.events.register("loadstart", tile, tile.onLoadStart);
+
+ tile.onLoadEnd = function() {
+ //if this is the the layer's current tile, then trigger
+ // a 'tileloaded' and 'loadend'
+ if (this == this.layer.tile) {
+ this.layer.events.triggerEvent("tileloaded");
+ this.layer.events.triggerEvent("loadend");
+ }
+ };
+ tile.events.register("loadend", tile, tile.onLoadEnd);
+ tile.events.register("unload", tile, tile.onLoadEnd);
+ },
+
+ /**
+ * Method: removeTileMonitoringHooks
+ * This function takes a tile as input and removes the tile hooks
+ * that were added in addTileMonitoringHooks()
+ *
+ * Parameters:
+ * tile - {<OpenLayers.Tile>}
+ */
+ removeTileMonitoringHooks: function(tile) {
+ tile.unload();
+ tile.events.un({
+ "loadstart": tile.onLoadStart,
+ "loadend": tile.onLoadEnd,
+ "unload": tile.onLoadEnd,
+ scope: tile
+ });
+ },
+
+ /**
+ * Method: onMapResize
+ * Call the onMapResize method of the appropriate parent class.
+ */
+ onMapResize: function() {
+ if(this.vectorMode) {
+ OpenLayers.Layer.Vector.prototype.onMapResize.apply(this,
+ arguments);
+ } else {
+ OpenLayers.Layer.Markers.prototype.onMapResize.apply(this,
+ arguments);
+ }
+ },
+
+ /**
+ * Method: display
+ * Call the display method of the appropriate parent class.
+ */
+ display: function() {
+ if(this.vectorMode) {
+ OpenLayers.Layer.Vector.prototype.display.apply(this,
+ arguments);
+ } else {
+ OpenLayers.Layer.Markers.prototype.display.apply(this,
+ arguments);
+ }
+ },
+
+ /**
+ * APIMethod: mergeNewParams
+ * Modify parameters for the layer and redraw.
+ *
+ * Parameters:
+ * newParams - {Object}
+ */
+ mergeNewParams:function(newParams) {
+ var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+ var newArguments = [upperParams];
+ return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this,
+ newArguments);
+ },
+
+ /**
+ * APIMethod: clone
+ *
+ * Parameters:
+ * obj - {Object}
+ *
+ * Returns:
+ * {<OpenLayers.Layer.WFS>} An exact clone of this OpenLayers.Layer.WFS
+ */
+ clone: function (obj) {
+
+ if (obj == null) {
+ obj = new OpenLayers.Layer.WFS(this.name,
+ this.url,
+ this.params,
+ this.getOptions());
+ }
+
+ //get all additions from superclasses
+ if (this.vectorMode) {
+ obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]);
+ } else {
+ obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]);
+ }
+
+ // copy/set any non-init, non-simple values here
+
+ return obj;
+ },
+
+ /**
+ * APIMethod: getFullRequestString
+ * combine the layer's url with its params and these newParams.
+ *
+ * Add the SRS parameter from 'projection' -- this is probably
+ * more eloquently done via a setProjection() method, but this
+ * works for now and always.
+ *
+ * Parameters:
+ * newParams - {Object}
+ * altUrl - {String} Use this as the url instead of the layer's url
+ */
+ getFullRequestString:function(newParams, altUrl) {
+ var projectionCode = this.projection.getCode() || this.map.getProjection();
+ this.params.SRS = (projectionCode == "none") ? null : projectionCode;
+
+ return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+ this, arguments);
+ },
+
+ /**
+ * APIMethod: commit
+ * Write out the data to a WFS server.
+ */
+ commit: function() {
+ if (!this.writer) {
+ var options = {};
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ this.writer = new OpenLayers.Format.WFS(options,this);
+ }
+
+ var data = this.writer.write(this.features);
+
+ OpenLayers.Request.POST({
+ url: this.url,
+ data: data,
+ success: this.commitSuccess,
+ failure: this.commitFailure,
+ scope: this
+ });
+ },
+
+ /**
+ * Method: commitSuccess
+ * Called when the Ajax request returns a response
+ *
+ * Parameters:
+ * response - {XmlNode} from server
+ */
+ commitSuccess: function(request) {
+ var response = request.responseText;
+ if (response.indexOf('SUCCESS') != -1) {
+ this.commitReport(OpenLayers.i18n("commitSuccess", {'response':response}));
+
+ for(var i = 0; i < this.features.length; i++) {
+ this.features[i].state = null;
+ }
+ // TBD redraw the layer or reset the state of features
+ // foreach features: set state to null
+ } else if (response.indexOf('FAILED') != -1 ||
+ response.indexOf('Exception') != -1) {
+ this.commitReport(OpenLayers.i18n("commitFailed", {'response':response}));
+ }
+ },
+
+ /**
+ * Method: commitFailure
+ * Called when the Ajax request fails
+ *
+ * Parameters:
+ * response - {XmlNode} from server
+ */
+ commitFailure: function(request) {},
+
+ /**
+ * APIMethod: commitReport
+ * Called with a 'success' message if the commit succeeded, otherwise
+ * a failure message, and the full request text as a second parameter.
+ * Override this function to provide custom transaction reporting.
+ *
+ * string - {String} reporting string
+ * response - {String} full XML response
+ */
+ commitReport: function(string, response) {
+ OpenLayers.Console.userError(string);
+ },
+
+
+ /**
+ * APIMethod: refresh
+ * Refreshes all the features of the layer
+ */
+ refresh: function() {
+ if (this.tile) {
+ if (this.vectorMode) {
+ this.renderer.clear();
+ this.features.length = 0;
+ } else {
+ this.clearMarkers();
+ this.markers.length = 0;
+ }
+ this.tile.draw();
+ }
+ },
+
+ /**
+ * APIMethod: getDataExtent
+ * Calculates the max extent which includes all of the layer data.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>}
+ */
+ getDataExtent: function () {
+ var extent;
+ //get all additions from superclasses
+ if (this.vectorMode) {
+ extent = OpenLayers.Layer.Vector.prototype.getDataExtent.apply(this);
+ } else {
+ extent = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(this);
+ }
+
+ return extent;
+ },
+
+ /**
+ * APIMethod: setOpacity
+ * Call the setOpacity method of the appropriate parent class to set the
+ * opacity.
+ *
+ * Parameters:
+ * opacity - {Float}
+ */
+ setOpacity: function (opacity) {
+ if (this.vectorMode) {
+ OpenLayers.Layer.Vector.prototype.setOpacity.apply(this, [opacity]);
+ } else {
+ OpenLayers.Layer.Markers.prototype.setOpacity.apply(this, [opacity]);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.WFS"
+});
+
+/**
+ * Class: OpenLayers.Layer.VirtualEarth
+ * *Deprecated*. Use <OpenLayers.Layer.Bing> instead.
+ *
+ * Instances of OpenLayers.Layer.VirtualEarth are used to display the data from
+ * the Bing Maps AJAX Control (see e.g.
+ * http://msdn.microsoft.com/library/bb429619.aspx). Create a VirtualEarth
+ * layer with the <OpenLayers.Layer.VirtualEarth> constructor.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.EventPane>
+ * - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.VirtualEarth = OpenLayers.Class(
+ OpenLayers.Layer.EventPane,
+ OpenLayers.Layer.FixedZoomLevels, {
+
+ /**
+ * Constant: MIN_ZOOM_LEVEL
+ * {Integer} 1
+ */
+ MIN_ZOOM_LEVEL: 1,
+
+ /**
+ * Constant: MAX_ZOOM_LEVEL
+ * {Integer} 19
+ */
+ MAX_ZOOM_LEVEL: 19,
+
+ /**
+ * Constant: RESOLUTIONS
+ * {Array(Float)} Hardcode these resolutions so that they are more closely
+ * tied with the standard wms projection
+ */
+ RESOLUTIONS: [
+ 1.40625,
+ 0.703125,
+ 0.3515625,
+ 0.17578125,
+ 0.087890625,
+ 0.0439453125,
+ 0.02197265625,
+ 0.010986328125,
+ 0.0054931640625,
+ 0.00274658203125,
+ 0.001373291015625,
+ 0.0006866455078125,
+ 0.00034332275390625,
+ 0.000171661376953125,
+ 0.0000858306884765625,
+ 0.00004291534423828125,
+ 0.00002145767211914062,
+ 0.00001072883605957031,
+ 0.00000536441802978515
+ ],
+
+ /**
+ * APIProperty: type
+ * {VEMapType}
+ */
+ type: null,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Allow user to pan forever east/west. Default is true.
+ * Setting this to false only restricts panning if
+ * <sphericalMercator> is true.
+ */
+ wrapDateLine: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * {Boolean} Should the map act as a mercator-projected map? This will
+ * cause all interactions with the map to be in the actual map
+ * projection, which allows support for vector drawing, overlaying
+ * other maps, etc.
+ */
+ sphericalMercator: false,
+
+ /**
+ * APIProperty: animationEnabled
+ * {Boolean} If set to true, the transition between zoom levels will be
+ * animated. Set to false to match the zooming experience of other
+ * layer types. Default is true.
+ */
+ animationEnabled: true,
+
+ /**
+ * Constructor: OpenLayers.Layer.VirtualEarth
+ * Creates a new instance of a OpenLayers.Layer.VirtualEarth. If you use an
+ * instance of OpenLayers.Layer.VirtualEarth in you map, you should set
+ * the <OpenLayers.Map> option restrictedExtent to a meaningful value,
+ * e.g.:
+ * (code)
+ * var map = new OpenLayers.Map( 'map', {
+ * // other map options
+ * restrictedExtent : OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508)
+ * } );
+ *
+ * var veLayer = new OpenLayers.Layer.VirtualEarth (
+ * "Virtual Earth Layer"
+ * );
+ *
+ * map.addLayer( veLayer );
+ * (end)
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object}
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+ arguments);
+ if(this.sphericalMercator) {
+ OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+ this.initMercatorParameters();
+ }
+ },
+
+ /**
+ * Method: loadMapObject
+ */
+ loadMapObject:function() {
+
+ // create div and set to same size as map
+ var veDiv = OpenLayers.Util.createDiv(this.name);
+ var sz = this.map.getSize();
+ veDiv.style.width = sz.w + "px";
+ veDiv.style.height = sz.h + "px";
+ this.div.appendChild(veDiv);
+
+ try { // crash prevention
+ this.mapObject = new VEMap(this.name);
+ } catch (e) { }
+
+ if (this.mapObject != null) {
+ try { // this is to catch a Mozilla bug without falling apart
+
+ // The fourth argument is whether the map is 'fixed' -- not
+ // draggable. See:
+ // http://blogs.msdn.com/virtualearth/archive/2007/09/28/locking-a-virtual-earth-map.aspx
+ //
+ this.mapObject.LoadMap(null, null, this.type, true);
+ this.mapObject.AttachEvent("onmousedown", OpenLayers.Function.True);
+
+ } catch (e) { }
+ this.mapObject.HideDashboard();
+ if(typeof this.mapObject.SetAnimationEnabled == "function") {
+ this.mapObject.SetAnimationEnabled(this.animationEnabled);
+ }
+ }
+
+ //can we do smooth panning? this is an unpublished method, so we need
+ // to be careful
+ if ( !this.mapObject ||
+ !this.mapObject.vemapcontrol ||
+ !this.mapObject.vemapcontrol.PanMap ||
+ (typeof this.mapObject.vemapcontrol.PanMap != "function")) {
+
+ this.dragPanMapObject = null;
+ }
+
+ },
+
+ /**
+ * Method: onMapResize
+ */
+ onMapResize: function() {
+ this.mapObject.Resize(this.map.size.w, this.map.size.h);
+ },
+
+ /**
+ * APIMethod: getWarningHTML
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ return OpenLayers.i18n(
+ "getLayerWarning", {'layerType':'VE', 'layerLib':'VirtualEarth'}
+ );
+ },
+
+
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ this.mapObject.SetCenterAndZoom(center, zoom);
+ },
+
+ /**
+ * APIMethod: getMapObjectCenter
+ *
+ * Returns:
+ * {Object} The mapObject's current center in Map Object format
+ */
+ getMapObjectCenter: function() {
+ return this.mapObject.GetCenter();
+ },
+
+ /**
+ * APIMethod: dragPanMapObject
+ *
+ * Parameters:
+ * dX - {Integer}
+ * dY - {Integer}
+ */
+ dragPanMapObject: function(dX, dY) {
+ this.mapObject.vemapcontrol.PanMap(dX, -dY);
+ },
+
+ /**
+ * APIMethod: getMapObjectZoom
+ *
+ * Returns:
+ * {Integer} The mapObject's current zoom, in Map Object format
+ */
+ getMapObjectZoom: function() {
+ return this.mapObject.GetZoomLevel();
+ },
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ //the conditional here is to test if we are running the v6 of VE
+ return (typeof VEPixel != 'undefined')
+ ? this.mapObject.PixelToLatLong(moPixel)
+ : this.mapObject.PixelToLatLong(moPixel.x, moPixel.y);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ return this.mapObject.LatLongToPixel(moLonLat);
+ },
+
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getLongitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Longitude of the given MapObject LonLat
+ */
+ getLongitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.Longitude, moLonLat.Latitude).lon :
+ moLonLat.Longitude;
+ },
+
+ /**
+ * APIMethod: getLatitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Latitude of the given MapObject LonLat
+ */
+ getLatitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.Longitude, moLonLat.Latitude).lat :
+ moLonLat.Latitude;
+ },
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var veLatLong;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ veLatLong = new VELatLong(lonlat.lat, lonlat.lon);
+ } else {
+ veLatLong = new VELatLong(lat, lon);
+ }
+ return veLatLong;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getXFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} X value of the MapObject Pixel
+ */
+ getXFromMapObjectPixel: function(moPixel) {
+ return moPixel.x;
+ },
+
+ /**
+ * APIMethod: getYFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} Y value of the MapObject Pixel
+ */
+ getYFromMapObjectPixel: function(moPixel) {
+ return moPixel.y;
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ //the conditional here is to test if we are running the v6 of VE
+ return (typeof VEPixel != 'undefined') ? new VEPixel(x, y)
+ : new Msn.VE.Pixel(x, y);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.VirtualEarth"
+});
+
+/*
+ * Copyright 2007, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of Google Inc. nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Sets up google.gears.*, which is *the only* supported way to access Gears.
+ *
+ * Circumvent this file at your own risk!
+ *
+ * In the future, Gears may automatically define google.gears.* without this
+ * file. Gears may use these objects to transparently fix bugs and compatibility
+ * issues. Applications that use the code below will continue to work seamlessly
+ * when that happens.
+ */
+
+(function() {
+ // We are already defined. Hooray!
+ if (window.google && google.gears) {
+ return;
+ }
+
+ var factory = null;
+
+ // Firefox
+ if (typeof GearsFactory != 'undefined') {
+ factory = new GearsFactory();
+ } else {
+ // IE
+ try {
+ factory = new ActiveXObject('Gears.Factory');
+ // privateSetGlobalObject is only required and supported on WinCE.
+ if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
+ factory.privateSetGlobalObject(this);
+ }
+ } catch (e) {
+ // Safari
+ if ((typeof navigator.mimeTypes != 'undefined')
+ && navigator.mimeTypes["application/x-googlegears"]) {
+ factory = document.createElement("object");
+ factory.style.display = "none";
+ factory.width = 0;
+ factory.height = 0;
+ factory.type = "application/x-googlegears";
+ document.documentElement.appendChild(factory);
+ }
+ }
+ }
+
+ // *Do not* define any objects if Gears is not installed. This mimics the
+ // behavior of Gears defining the objects in the future.
+ if (!factory) {
+ return;
+ }
+
+ // Now set up the objects, being careful not to overwrite anything.
+ //
+ // Note: In Internet Explorer for Windows Mobile, you can't add properties to
+ // the window object. However, global objects are automatically added as
+ // properties of the window object in all browsers.
+ if (!window.google) {
+ google = {};
+ }
+
+ if (!google.gears) {
+ google.gears = {factory: factory};
+ }
+})();
+
+/**
+ * Class: OpenLayers.Protocol.SQL
+ * Abstract SQL protocol class. Not to be instantiated directly. Use
+ * one of the SQL protocol subclasses instead.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol>
+ */
+OpenLayers.Protocol.SQL = OpenLayers.Class(OpenLayers.Protocol, {
+
+ /**
+ * APIProperty: databaseName
+ * {String}
+ */
+ databaseName: 'ol',
+
+ /**
+ * APIProperty: tableName
+ * Name of the database table into which Features should be saved.
+ */
+ tableName: "ol_vector_features",
+
+ /**
+ * Property: postReadFiltering
+ * {Boolean} Whether the filter (if there's one) must be applied after
+ * the features have been read from the database; for example the
+ * BBOX strategy passes the read method a BBOX spatial filter, if
+ * postReadFiltering is true every feature read from the database
+ * will go through the BBOX spatial filter, which can be costly;
+ * defaults to true.
+ */
+ postReadFiltering: true,
+
+ /**
+ * Constructor: OpenLayers.Protocol.SQL
+ */
+ initialize: function(options) {
+ OpenLayers.Protocol.prototype.initialize.apply(this, [options]);
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ OpenLayers.Protocol.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: supported
+ * This should be overridden by specific subclasses
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SQL backend
+ */
+ supported: function() {
+ return false;
+ },
+
+ /**
+ * Method: evaluateFilter
+ * If postReadFiltering is true evaluate the filter against the feature
+ * and return the result of the evaluation, otherwise return true.
+ *
+ * Parameters:
+ * {<OpenLayers.Feature.Vector>} The feature.
+ * {<OpenLayers.Filter>} The filter.
+ *
+ * Returns:
+ * {Boolean} true if postReadFiltering if false, the result of the
+ * filter evaluation otherwise.
+ */
+ evaluateFilter: function(feature, filter) {
+ return filter && this.postReadFiltering ?
+ filter.evaluate(feature) : true;
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.SQL"
+});
+
+/**
+ * Class: OpenLayers.Protocol.SQL.Gears
+ * This Protocol stores feature in the browser via the Gears Database module
+ * <http://code.google.com/apis/gears/api_database.html>.
+ *
+ * The main advantage is that all the read, create, update and delete operations
+ * can be done offline.
+ *
+ * Inherits from:
+ * - <OpenLayers.Protocol.SQL>
+ */
+OpenLayers.Protocol.SQL.Gears = OpenLayers.Class(OpenLayers.Protocol.SQL, {
+
+ /**
+ * Property: FID_PREFIX
+ * {String}
+ */
+ FID_PREFIX: '__gears_fid__',
+
+ /**
+ * Property: NULL_GEOMETRY
+ * {String}
+ */
+ NULL_GEOMETRY: '__gears_null_geometry__',
+
+ /**
+ * Property: NULL_FEATURE_STATE
+ * {String}
+ */
+ NULL_FEATURE_STATE: '__gears_null_feature_state__',
+
+ /**
+ * Property: jsonParser
+ * {<OpenLayers.Format.JSON>}
+ */
+ jsonParser: null,
+
+ /**
+ * Property: wktParser
+ * {<OpenLayers.Format.WKT>}
+ */
+ wktParser: null,
+
+ /**
+ * Property: fidRegExp
+ * {RegExp} Regular expression to know whether a feature was
+ * created in offline mode.
+ */
+ fidRegExp: null,
+
+ /**
+ * Property: saveFeatureState
+ * {Boolean} Whether to save the feature state (<OpenLayers.State>)
+ * into the database, defaults to true.
+ */
+ saveFeatureState: true,
+
+ /**
+ * Property: typeOfFid
+ * {String} The type of the feature identifier, either "number" or
+ * "string", defaults to "string".
+ */
+ typeOfFid: "string",
+
+ /**
+ * Property: db
+ * {GearsDatabase}
+ */
+ db: null,
+
+ /**
+ * Constructor: OpenLayers.Protocol.SQL.Gears
+ */
+ initialize: function(options) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Protocol.SQL.prototype.initialize.apply(this, [options]);
+ this.jsonParser = new OpenLayers.Format.JSON();
+ this.wktParser = new OpenLayers.Format.WKT();
+
+ this.fidRegExp = new RegExp('^' + this.FID_PREFIX);
+ this.initializeDatabase();
+
+
+ },
+
+ /**
+ * Method: initializeDatabase
+ */
+ initializeDatabase: function() {
+ this.db = google.gears.factory.create('beta.database');
+ this.db.open(this.databaseName);
+ this.db.execute(
+ "CREATE TABLE IF NOT EXISTS " + this.tableName +
+ " (fid TEXT UNIQUE, geometry TEXT, properties TEXT," +
+ " state TEXT)");
+ },
+
+ /**
+ * APIMethod: destroy
+ * Clean up the protocol.
+ */
+ destroy: function() {
+ this.db.close();
+ this.db = null;
+
+ this.jsonParser = null;
+ this.wktParser = null;
+
+ OpenLayers.Protocol.SQL.prototype.destroy.apply(this);
+ },
+
+ /**
+ * APIMethod: supported
+ * Determine whether a browser supports Gears
+ *
+ * Returns:
+ * {Boolean} The browser supports Gears
+ */
+ supported: function() {
+ return !!(window.google && google.gears);
+ },
+
+ /**
+ * APIMethod: read
+ * Read all features from the database and return a
+ * <OpenLayers.Protocol.Response> instance. If the options parameter
+ * contains a callback attribute, the function is called with the response
+ * as a parameter.
+ *
+ * Parameters:
+ * options - {Object} Optional object for configuring the request; it
+ * can have the {Boolean} property "noFeatureStateReset" which
+ * specifies if the state of features read from the Gears
+ * database must be reset to null, if "noFeatureStateReset"
+ * is undefined or false then each feature's state is reset
+ * to null, if "noFeatureStateReset" is true the feature state
+ * is preserved.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object.
+ */
+ read: function(options) {
+ OpenLayers.Protocol.prototype.read.apply(this, arguments);
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var feature, features = [];
+ var rs = this.db.execute("SELECT * FROM " + this.tableName);
+ while (rs.isValidRow()) {
+ feature = this.unfreezeFeature(rs);
+ if (this.evaluateFilter(feature, options.filter)) {
+ if (!options.noFeatureStateReset) {
+ feature.state = null;
+ }
+ features.push(feature);
+ }
+ rs.next();
+ }
+ rs.close();
+
+ var resp = new OpenLayers.Protocol.Response({
+ code: OpenLayers.Protocol.Response.SUCCESS,
+ requestType: "read",
+ features: features
+ });
+
+ if (options && options.callback) {
+ options.callback.call(options.scope, resp);
+ }
+
+ return resp;
+ },
+
+ /**
+ * Method: unfreezeFeature
+ *
+ * Parameters:
+ * row - {ResultSet}
+ *
+ * Returns:
+ * {<OpenLayers.Feature.Vector>}
+ */
+ unfreezeFeature: function(row) {
+ var feature;
+ var wkt = row.fieldByName('geometry');
+ if (wkt == this.NULL_GEOMETRY) {
+ feature = new OpenLayers.Feature.Vector();
+ } else {
+ feature = this.wktParser.read(wkt);
+ }
+
+ feature.attributes = this.jsonParser.read(
+ row.fieldByName('properties'));
+
+ feature.fid = this.extractFidFromField(row.fieldByName('fid'));
+
+ var state = row.fieldByName('state');
+ if (state == this.NULL_FEATURE_STATE) {
+ state = null;
+ }
+ feature.state = state;
+
+ return feature;
+ },
+
+ /**
+ * Method: extractFidFromField
+ *
+ * Parameters:
+ * field - {String}
+ *
+ * Returns
+ * {String} or {Number} The fid.
+ */
+ extractFidFromField: function(field) {
+ if (!field.match(this.fidRegExp) && this.typeOfFid == "number") {
+ field = parseFloat(field);
+ }
+ return field;
+ },
+
+ /**
+ * APIMethod: create
+ * Create new features into the database.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} The features to create in
+ * the database.
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object.
+ */
+ create: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = this.createOrUpdate(features);
+ resp.requestType = "create";
+
+ if (options && options.callback) {
+ options.callback.call(options.scope, resp);
+ }
+
+ return resp;
+ },
+
+ /**
+ * APIMethod: update
+ * Construct a request updating modified feature.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} The features to update in
+ * the database.
+ * options - {Object} Optional object for configuring the request.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object.
+ */
+ update: function(features, options) {
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var resp = this.createOrUpdate(features);
+ resp.requestType = "update";
+
+ if (options && options.callback) {
+ options.callback.call(options.scope, resp);
+ }
+
+ return resp;
+ },
+
+ /**
+ * Method: createOrUpdate
+ * Construct a request for updating or creating features in the
+ * database.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>} The feature to create or update
+ * in the database.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object.
+ */
+ createOrUpdate: function(features) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ var i, len = features.length, feature;
+ var insertedFeatures = new Array(len);
+
+ for (i = 0; i < len; i++) {
+ feature = features[i];
+ var params = this.freezeFeature(feature);
+ this.db.execute(
+ "REPLACE INTO " + this.tableName +
+ " (fid, geometry, properties, state)" +
+ " VALUES (?, ?, ?, ?)",
+ params);
+
+ var clone = feature.clone();
+ clone.fid = this.extractFidFromField(params[0]);
+ insertedFeatures[i] = clone;
+ }
+
+ return new OpenLayers.Protocol.Response({
+ code: OpenLayers.Protocol.Response.SUCCESS,
+ features: insertedFeatures,
+ reqFeatures: features
+ });
+ },
+
+ /**
+ * Method: freezeFeature
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * state - {String} The feature state to store in the database.
+ *
+ * Returns:
+ * {Array}
+ */
+ freezeFeature: function(feature) {
+ // 2 notes:
+ // - fid might not be a string
+ // - getFeatureStateForFreeze needs the feature fid to it's stored
+ // in the feature here
+ feature.fid = feature.fid != null ?
+ "" + feature.fid : OpenLayers.Util.createUniqueID(this.FID_PREFIX);
+
+ var geometry = feature.geometry != null ?
+ feature.geometry.toString() : this.NULL_GEOMETRY;
+
+ var properties = this.jsonParser.write(feature.attributes);
+
+ var state = this.getFeatureStateForFreeze(feature);
+
+ return [feature.fid, geometry, properties, state];
+ },
+
+ /**
+ * Method: getFeatureStateForFreeze
+ * Get the state of the feature to store into the database.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>} The feature.
+ *
+ * Returns
+ * {String} The state
+ */
+ getFeatureStateForFreeze: function(feature) {
+ var state;
+ if (!this.saveFeatureState) {
+ state = this.NULL_FEATURE_STATE;
+ } else if (this.createdOffline(feature)) {
+ // if the feature was created in offline mode, its
+ // state must remain INSERT
+ state = OpenLayers.State.INSERT;
+ } else {
+ state = feature.state;
+ }
+ return state;
+ },
+
+ /**
+ * APIMethod: delete
+ * Delete features from the database.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})} or
+ * {<OpenLayers.Feature.Vector>}
+ * options - {Object} Optional object for configuring the request.
+ * This object is modified and should not be reused.
+ *
+ * Returns:
+ * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+ * object.
+ */
+ "delete": function(features, options) {
+ if (!(OpenLayers.Util.isArray(features))) {
+ features = [features];
+ }
+
+ options = OpenLayers.Util.applyDefaults(options, this.options);
+
+ var i, len, feature;
+ for (i = 0, len = features.length; i < len; i++) {
+ feature = features[i];
+
+ // if saveFeatureState is set to true and if the feature wasn't created
+ // in offline mode we don't delete it in the database but just update
+ // it state column
+ if (this.saveFeatureState && !this.createdOffline(feature)) {
+ var toDelete = feature.clone();
+ toDelete.fid = feature.fid;
+ if (toDelete.geometry) {
+ toDelete.geometry.destroy();
+ toDelete.geometry = null;
+ }
+ toDelete.state = feature.state;
+ this.createOrUpdate(toDelete);
+ } else {
+ this.db.execute(
+ "DELETE FROM " + this.tableName +
+ " WHERE fid = ?", [feature.fid]);
+ }
+ }
+
+ var resp = new OpenLayers.Protocol.Response({
+ code: OpenLayers.Protocol.Response.SUCCESS,
+ requestType: "delete",
+ reqFeatures: features
+ });
+
+ if (options && options.callback) {
+ options.callback.call(options.scope, resp);
+ }
+
+ return resp;
+ },
+
+ /**
+ * Method: createdOffline
+ * Returns true if the feature had a feature id when it was created in
+ * the Gears database, false otherwise; this is determined by
+ * checking the form of the feature's fid value.
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ *
+ * Returns:
+ * {Boolean}
+ */
+ createdOffline: function(feature) {
+ return (typeof feature.fid == "string" &&
+ !!(feature.fid.match(this.fidRegExp)));
+ },
+
+ /**
+ * APIMethod: commit
+ * Go over the features and for each take action
+ * based on the feature state. Possible actions are create,
+ * update and delete.
+ *
+ * Parameters:
+ * features - {Array({<OpenLayers.Feature.Vector>})}
+ * options - {Object} Object whose possible keys are "create", "update",
+ * "delete", "callback" and "scope", the values referenced by the
+ * first three are objects as passed to the "create", "update", and
+ * "delete" methods, the value referenced by the "callback" key is
+ * a function which is called when the commit operation is complete
+ * using the scope referenced by the "scope" key.
+ *
+ * Returns:
+ * {Array({<OpenLayers.Protocol.Response>})} An array of
+ * <OpenLayers.Protocol.Response> objects, one per request made
+ * to the database.
+ */
+ commit: function(features, options) {
+ var opt, resp = [], nRequests = 0, nResponses = 0;
+
+ function callback(resp) {
+ if (++nResponses < nRequests) {
+ resp.last = false;
+ }
+ this.callUserCallback(options, resp);
+ }
+
+ var feature, toCreate = [], toUpdate = [], toDelete = [];
+ for (var i = features.length - 1; i >= 0; i--) {
+ feature = features[i];
+ switch (feature.state) {
+ case OpenLayers.State.INSERT:
+ toCreate.push(feature);
+ break;
+ case OpenLayers.State.UPDATE:
+ toUpdate.push(feature);
+ break;
+ case OpenLayers.State.DELETE:
+ toDelete.push(feature);
+ break;
+ }
+ }
+ if (toCreate.length > 0) {
+ nRequests++;
+ opt = OpenLayers.Util.applyDefaults(
+ {"callback": callback, "scope": this},
+ options.create
+ );
+ resp.push(this.create(toCreate, opt));
+ }
+ if (toUpdate.length > 0) {
+ nRequests++;
+ opt = OpenLayers.Util.applyDefaults(
+ {"callback": callback, "scope": this},
+ options.update
+ );
+ resp.push(this.update(toUpdate, opt));
+ }
+ if (toDelete.length > 0) {
+ nRequests++;
+ opt = OpenLayers.Util.applyDefaults(
+ {"callback": callback, "scope": this},
+ options["delete"]
+ );
+ resp.push(this["delete"](toDelete, opt));
+ }
+
+ return resp;
+ },
+
+ /**
+ * Method: clear
+ * Removes all rows of the table.
+ */
+ clear: function() {
+ this.db.execute("DELETE FROM " + this.tableName);
+ },
+
+ /**
+ * Method: callUserCallback
+ * This method is called from within commit each time a request is made
+ * to the database, it is responsible for calling the user-supplied
+ * callbacks.
+ *
+ * Parameters:
+ * options - {Object} The map of options passed to the commit call.
+ * resp - {<OpenLayers.Protocol.Response>}
+ */
+ callUserCallback: function(options, resp) {
+ var opt = options[resp.requestType];
+ if (opt && opt.callback) {
+ opt.callback.call(opt.scope, resp);
+ }
+ if (resp.last && options.callback) {
+ options.callback.call(options.scope);
+ }
+ },
+
+ CLASS_NAME: "OpenLayers.Protocol.SQL.Gears"
+});
+
+/**
+ * Class: OpenLayers.Layer.Yahoo
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.EventPane>
+ * - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Yahoo = OpenLayers.Class(
+ OpenLayers.Layer.EventPane, OpenLayers.Layer.FixedZoomLevels, {
+
+ /**
+ * Constant: MIN_ZOOM_LEVEL
+ * {Integer} 0
+ */
+ MIN_ZOOM_LEVEL: 0,
+
+ /**
+ * Constant: MAX_ZOOM_LEVEL
+ * {Integer} 17
+ */
+ MAX_ZOOM_LEVEL: 17,
+
+ /**
+ * Constant: RESOLUTIONS
+ * {Array(Float)} Hardcode these resolutions so that they are more closely
+ * tied with the standard wms projection
+ */
+ RESOLUTIONS: [
+ 1.40625,
+ 0.703125,
+ 0.3515625,
+ 0.17578125,
+ 0.087890625,
+ 0.0439453125,
+ 0.02197265625,
+ 0.010986328125,
+ 0.0054931640625,
+ 0.00274658203125,
+ 0.001373291015625,
+ 0.0006866455078125,
+ 0.00034332275390625,
+ 0.000171661376953125,
+ 0.0000858306884765625,
+ 0.00004291534423828125,
+ 0.00002145767211914062,
+ 0.00001072883605957031
+ ],
+
+ /**
+ * APIProperty: type
+ * {YahooMapType}
+ */
+ type: null,
+
+ /**
+ * APIProperty: wrapDateLine
+ * {Boolean} Allow user to pan forever east/west. Default is true.
+ * Setting this to false only restricts panning if
+ * <sphericalMercator> is true.
+ */
+ wrapDateLine: true,
+
+ /**
+ * APIProperty: sphericalMercator
+ * {Boolean} Should the map act as a mercator-projected map? This will
+ * cause all interactions with the map to be in the actual map projection,
+ * which allows support for vector drawing, overlaying other maps, etc.
+ */
+ sphericalMercator: false,
+
+ /**
+ * Constructor: OpenLayers.Layer.Yahoo
+ *
+ * Parameters:
+ * name - {String}
+ * options - {Object}
+ */
+ initialize: function(name, options) {
+ OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,
+ arguments);
+ if(this.sphericalMercator) {
+ OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+ this.initMercatorParameters();
+ }
+ },
+
+ /**
+ * Method: loadMapObject
+ */
+ loadMapObject:function() {
+ try { //do not crash!
+ var size = this.getMapObjectSizeFromOLSize(this.map.getSize());
+ this.mapObject = new YMap(this.div, this.type, size);
+ this.mapObject.disableKeyControls();
+ this.mapObject.disableDragMap();
+
+ //can we do smooth panning? (moveByXY is not an API function)
+ if ( !this.mapObject.moveByXY ||
+ (typeof this.mapObject.moveByXY != "function" ) ) {
+
+ this.dragPanMapObject = null;
+ }
+ } catch(e) {}
+ },
+
+ /**
+ * Method: onMapResize
+ *
+ */
+ onMapResize: function() {
+ try {
+ var size = this.getMapObjectSizeFromOLSize(this.map.getSize());
+ this.mapObject.resizeTo(size);
+ } catch(e) {}
+ },
+
+
+ /**
+ * APIMethod: setMap
+ * Overridden from EventPane because we need to remove this yahoo event
+ * pane which prohibits our drag and drop, and we can only do this
+ * once the map has been loaded and centered.
+ *
+ * Parameters:
+ * map - {<OpenLayers.Map>}
+ */
+ setMap: function(map) {
+ OpenLayers.Layer.EventPane.prototype.setMap.apply(this, arguments);
+
+ this.map.events.register("moveend", this, this.fixYahooEventPane);
+ },
+
+ /**
+ * Method: fixYahooEventPane
+ * The map has been centered, so the mysterious yahoo eventpane has been
+ * added. we remove it so that it doesnt mess with *our* event pane.
+ */
+ fixYahooEventPane: function() {
+ var yahooEventPane = OpenLayers.Util.getElement("ygddfdiv");
+ if (yahooEventPane != null) {
+ if (yahooEventPane.parentNode != null) {
+ yahooEventPane.parentNode.removeChild(yahooEventPane);
+ }
+ this.map.events.unregister("moveend", this,
+ this.fixYahooEventPane);
+ }
+ },
+
+ /**
+ * APIMethod: getWarningHTML
+ *
+ * Returns:
+ * {String} String with information on why layer is broken, how to get
+ * it working.
+ */
+ getWarningHTML:function() {
+ return OpenLayers.i18n(
+ "getLayerWarning", {'layerType':'Yahoo', 'layerLib':'Yahoo'}
+ );
+ },
+
+ /********************************************************/
+ /* */
+ /* Translation Functions */
+ /* */
+ /* The following functions translate GMaps and OL */
+ /* formats for Pixel, LonLat, Bounds, and Zoom */
+ /* */
+ /********************************************************/
+
+
+ //
+ // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+ //
+
+ /**
+ * APIMethod: getOLZoomFromMapObjectZoom
+ *
+ * Parameters:
+ * gZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} An OpenLayers Zoom level, translated from the passed in gZoom
+ * Returns null if null value is passed in.
+ */
+ getOLZoomFromMapObjectZoom: function(moZoom) {
+ var zoom = null;
+ if (moZoom != null) {
+ zoom = OpenLayers.Layer.FixedZoomLevels.prototype.getOLZoomFromMapObjectZoom.apply(this, [moZoom]);
+ zoom = 18 - zoom;
+ }
+ return zoom;
+ },
+
+ /**
+ * APIMethod: getMapObjectZoomFromOLZoom
+ *
+ * Parameters:
+ * olZoom - {Integer}
+ *
+ * Returns:
+ * {Integer} A MapObject level, translated from the passed in olZoom
+ * Returns null if null value is passed in
+ */
+ getMapObjectZoomFromOLZoom: function(olZoom) {
+ var zoom = null;
+ if (olZoom != null) {
+ zoom = OpenLayers.Layer.FixedZoomLevels.prototype.getMapObjectZoomFromOLZoom.apply(this, [olZoom]);
+ zoom = 18 - zoom;
+ }
+ return zoom;
+ },
+
+ /************************************
+ * *
+ * MapObject Interface Controls *
+ * *
+ ************************************/
+
+
+ // Get&Set Center, Zoom
+
+ /**
+ * APIMethod: setMapObjectCenter
+ * Set the mapObject to the specified center and zoom
+ *
+ * Parameters:
+ * center - {Object} MapObject LonLat format
+ * zoom - {int} MapObject zoom format
+ */
+ setMapObjectCenter: function(center, zoom) {
+ this.mapObject.drawZoomAndCenter(center, zoom);
+ },
+
+ /**
+ * APIMethod: getMapObjectCenter
+ *
+ * Returns:
+ * {Object} The mapObject's current center in Map Object format
+ */
+ getMapObjectCenter: function() {
+ return this.mapObject.getCenterLatLon();
+ },
+
+ /**
+ * APIMethod: dragPanMapObject
+ *
+ * Parameters:
+ * dX - {Integer}
+ * dY - {Integer}
+ */
+ dragPanMapObject: function(dX, dY) {
+ this.mapObject.moveByXY({
+ 'x': -dX,
+ 'y': dY
+ });
+ },
+
+ /**
+ * APIMethod: getMapObjectZoom
+ *
+ * Returns:
+ * {Integer} The mapObject's current zoom, in Map Object format
+ */
+ getMapObjectZoom: function() {
+ return this.mapObject.getZoomLevel();
+ },
+
+
+ // LonLat - Pixel Translation
+
+ /**
+ * APIMethod: getMapObjectLonLatFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Object} MapObject LonLat translated from MapObject Pixel
+ */
+ getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+ return this.mapObject.convertXYLatLon(moPixel);
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Object} MapObject Pixel transtlated from MapObject LonLat
+ */
+ getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+ return this.mapObject.convertLatLonXY(moLonLat);
+ },
+
+
+ /************************************
+ * *
+ * MapObject Primitives *
+ * *
+ ************************************/
+
+
+ // LonLat
+
+ /**
+ * APIMethod: getLongitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Longitude of the given MapObject LonLat
+ */
+ getLongitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.Lon, moLonLat.Lat).lon :
+ moLonLat.Lon;
+ },
+
+ /**
+ * APIMethod: getLatitudeFromMapObjectLonLat
+ *
+ * Parameters:
+ * moLonLat - {Object} MapObject LonLat format
+ *
+ * Returns:
+ * {Float} Latitude of the given MapObject LonLat
+ */
+ getLatitudeFromMapObjectLonLat: function(moLonLat) {
+ return this.sphericalMercator ?
+ this.forwardMercator(moLonLat.Lon, moLonLat.Lat).lat :
+ moLonLat.Lat;
+ },
+
+ /**
+ * APIMethod: getMapObjectLonLatFromLonLat
+ *
+ * Parameters:
+ * lon - {Float}
+ * lat - {Float}
+ *
+ * Returns:
+ * {Object} MapObject LonLat built from lon and lat params
+ */
+ getMapObjectLonLatFromLonLat: function(lon, lat) {
+ var yLatLong;
+ if(this.sphericalMercator) {
+ var lonlat = this.inverseMercator(lon, lat);
+ yLatLong = new YGeoPoint(lonlat.lat, lonlat.lon);
+ } else {
+ yLatLong = new YGeoPoint(lat, lon);
+ }
+ return yLatLong;
+ },
+
+ // Pixel
+
+ /**
+ * APIMethod: getXFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} X value of the MapObject Pixel
+ */
+ getXFromMapObjectPixel: function(moPixel) {
+ return moPixel.x;
+ },
+
+ /**
+ * APIMethod: getYFromMapObjectPixel
+ *
+ * Parameters:
+ * moPixel - {Object} MapObject Pixel format
+ *
+ * Returns:
+ * {Integer} Y value of the MapObject Pixel
+ */
+ getYFromMapObjectPixel: function(moPixel) {
+ return moPixel.y;
+ },
+
+ /**
+ * APIMethod: getMapObjectPixelFromXY
+ *
+ * Parameters:
+ * x - {Integer}
+ * y - {Integer}
+ *
+ * Returns:
+ * {Object} MapObject Pixel from x and y parameters
+ */
+ getMapObjectPixelFromXY: function(x, y) {
+ return new YCoordPoint(x, y);
+ },
+
+ // Size
+
+ /**
+ * APIMethod: getMapObjectSizeFromOLSize
+ *
+ * Parameters:
+ * olSize - {<OpenLayers.Size>}
+ *
+ * Returns:
+ * {Object} MapObject Size from olSize parameter
+ */
+ getMapObjectSizeFromOLSize: function(olSize) {
+ return new YSize(olSize.w, olSize.h);
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.Yahoo"
+});
+
+/**
+ * Class: OpenLayers.Layer.GML
+ * Create a vector layer by parsing a GML file. The GML file is
+ * passed in as a parameter.
+ * *Deprecated*. To be removed in 3.0. Instead use OpenLayers.Layer.Vector
+ * with Protocol.HTTP and Strategy.Fixed. Provide the protocol with a
+ * format parameter to get the parser you want for your data.
+ *
+ * Inherits from:
+ * - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.GML = OpenLayers.Class(OpenLayers.Layer.Vector, {
+
+ /**
+ * Property: loaded
+ * {Boolean} Flag for whether the GML data has been loaded yet.
+ */
+ loaded: false,
+
+ /**
+ * APIProperty: format
+ * {<OpenLayers.Format>} The format you want the data to be parsed with.
+ */
+ format: null,
+
+ /**
+ * APIProperty: formatOptions
+ * {Object} Hash of options which should be passed to the format when it is
+ * created. Must be passed in the constructor.
+ */
+ formatOptions: null,
+
+ /**
+ * Constructor: OpenLayers.Layer.GML
+ * Load and parse a single file on the web, according to the format
+ * provided via the 'format' option, defaulting to GML.
+ *
+ * Parameters:
+ * name - {String}
+ * url - {String} URL of a GML file.
+ * options - {Object} Hashtable of extra options to tag onto the layer.
+ */
+ initialize: function(name, url, options) {
+ var newArguments = [];
+ newArguments.push(name, options);
+ OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
+ this.url = url;
+ },
+
+ /**
+ * APIMethod: setVisibility
+ * Set the visibility flag for the layer and hide/show&redraw accordingly.
+ * Fire event unless otherwise specified
+ * GML will be loaded if the layer is being made visible for the first
+ * time.
+ *
+ * Parameters:
+ * visible - {Boolean} Whether or not to display the layer
+ * (if in range)
+ * noEvent - {Boolean}
+ */
+ setVisibility: function(visibility, noEvent) {
+ OpenLayers.Layer.Vector.prototype.setVisibility.apply(this, arguments);
+ if(this.visibility && !this.loaded){
+ // Load the GML
+ this.loadGML();
+ }
+ },
+
+ /**
+ * Method: moveTo
+ * If layer is visible and GML has not been loaded, load GML, then load GML
+ * and call OpenLayers.Layer.Vector.moveTo() to redraw at the new location.
+ *
+ * Parameters:
+ * bounds - {Object}
+ * zoomChanged - {Object}
+ * minor - {Object}
+ */
+ moveTo:function(bounds, zoomChanged, minor) {
+ OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+ // Wait until initialisation is complete before loading GML
+ // otherwise we can get a race condition where the root HTML DOM is
+ // loaded after the GML is paited.
+ // See http://trac.openlayers.org/ticket/404
+ if(this.visibility && !this.loaded){
+ this.loadGML();
+ }
+ },
+
+ /**
+ * Method: loadGML
+ */
+ loadGML: function() {
+ if (!this.loaded) {
+ this.events.triggerEvent("loadstart");
+ OpenLayers.Request.GET({
+ url: this.url,
+ success: this.requestSuccess,
+ failure: this.requestFailure,
+ scope: this
+ });
+ this.loaded = true;
+ }
+ },
+
+ /**
+ * Method: setUrl
+ * Change the URL and reload the GML
+ *
+ * Parameters:
+ * url - {String} URL of a GML file.
+ */
+ setUrl:function(url) {
+ this.url = url;
+ this.destroyFeatures();
+ this.loaded = false;
+ this.loadGML();
+ },
+
+ /**
+ * Method: requestSuccess
+ * Process GML after it has been loaded.
+ * Called by initialize() and loadUrl() after the GML has been loaded.
+ *
+ * Parameters:
+ * request - {String}
+ */
+ requestSuccess:function(request) {
+ var doc = request.responseXML;
+
+ if (!doc || !doc.documentElement) {
+ doc = request.responseText;
+ }
+
+ var options = {};
+
+ OpenLayers.Util.extend(options, this.formatOptions);
+ if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+ options.externalProjection = this.projection;
+ options.internalProjection = this.map.getProjectionObject();
+ }
+
+ var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+ this.addFeatures(gml.read(doc));
+ this.events.triggerEvent("loadend");
+ },
+
+ /**
+ * Method: requestFailure
+ * Process a failed loading of GML.
+ * Called by initialize() and loadUrl() if there was a problem loading GML.
+ *
+ * Parameters:
+ * request - {String}
+ */
+ requestFailure: function(request) {
+ OpenLayers.Console.userError('Error in loading GML file ' + this.url);
+ this.events.triggerEvent("loadend");
+ },
+
+ CLASS_NAME: "OpenLayers.Layer.GML"
+});
+
+/**
+ * Class: OpenLayers.Geometry.Rectangle
+ * This class is *not supported*, and probably isn't what you're looking for.
+ * Instead, most users probably want something like:
+ * (code)
+ * var poly = new OpenLayers.Bounds(0,0,10,10).toGeometry();
+ * (end)
+ * This will create a rectangular Polygon geometry.
+ *
+ * Inherits:
+ * - <OpenLayers.Geometry>
+ */
+
+OpenLayers.Geometry.Rectangle = OpenLayers.Class(OpenLayers.Geometry, {
+
+ /**
+ * Property: x
+ * {Float}
+ */
+ x: null,
+
+ /**
+ * Property: y
+ * {Float}
+ */
+ y: null,
+
+ /**
+ * Property: width
+ * {Float}
+ */
+ width: null,
+
+ /**
+ * Property: height
+ * {Float}
+ */
+ height: null,
+
+ /**
+ * Constructor: OpenLayers.Geometry.Rectangle
+ *
+ * Parameters:
+ * points - {Array(<OpenLayers.Geometry.Point>)}
+ */
+ initialize: function(x, y, width, height) {
+ OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+
+ this.x = x;
+ this.y = y;
+
+ this.width = width;
+ this.height = height;
+ },
+
+ /**
+ * Method: calculateBounds
+ * Recalculate the bounds for the geometry.
+ */
+ calculateBounds: function() {
+ this.bounds = new OpenLayers.Bounds(this.x, this.y,
+ this.x + this.width,
+ this.y + this.height);
+ },
+
+
+ /**
+ * APIMethod: getLength
+ *
+ * Returns:
+ * {Float} The length of the geometry
+ */
+ getLength: function() {
+ var length = (2 * this.width) + (2 * this.height);
+ return length;
+ },
+
+ /**
+ * APIMethod: getArea
+ *
+ * Returns:
+ * {Float} The area of the geometry
+ */
+ getArea: function() {
+ var area = this.width * this.height;
+ return area;
+ },
+
+ CLASS_NAME: "OpenLayers.Geometry.Rectangle"
+});
+
+/**
+ * Class: OpenLayers.Renderer.NG
+ *
+ * Inherits from:
+ * - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.NG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+ /**
+ * Constant: labelNodeType
+ * {String} The node type for text label containers. To be defined by
+ * subclasses.
+ */
+ labelNodeType: null,
+
+ /**
+ * Constructor: OpenLayers.Renderer.NG
+ *
+ * Parameters:
+ * containerID - {String}
+ * options - {Object} options for this renderer. Supported options are:
+ * * yOrdering - {Boolean} Whether to use y-ordering
+ * * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+ * if yOrdering is set to true.
+ */
+
+ /**
+ * Method: updateDimensions
+ * To be extended by subclasses - here we set positioning related styles
+ * on HTML elements, subclasses have to do the same for renderer specific
+ * elements (e.g. viewBox, width and height of the rendererRoot)
+ *
+ * Parameters:
+ * zoomChanged - {Boolean} Has the zoom changed? If so, subclasses may have
+ * to update feature styles/dimensions.
+ */
+ updateDimensions: function(zoomChanged) {
+ var mapExtent = this.map.getExtent();
+ var renderExtent = mapExtent.scale(3);
+ this.setExtent(renderExtent, true);
+ var res = this.getResolution();
+ var div = this.rendererRoot.parentNode;
+ var layerLeft = parseFloat(div.parentNode.style.left);
+ var layerTop = parseFloat(div.parentNode.style.top);
+ div.style.left = ((renderExtent.left - mapExtent.left) / res - layerLeft) + "px";
+ div.style.top = ((mapExtent.top - renderExtent.top) / res - layerTop) + "px";
+ },
+
+ /**
+ * Method: resize
+ */
+ setSize: function() {
+ this.map.getExtent() && this.updateDimensions();
+ },
+
+ /**
+ * Method: drawFeature
+ * Draw the feature. The optional style argument can be used
+ * to override the feature's own style. This method should only
+ * be called from layer.drawFeature().
+ *
+ * Parameters:
+ * feature - {<OpenLayers.Feature.Vector>}
+ * style - {<Object>}
+ *
+ * Returns:
+ * {Boolean} true if the feature has been drawn completely, false if not,
+ * undefined if the feature had no geometry
+ */
+ drawFeature: function(feature, style) {
+ if(style == null) {
+ style = feature.style;
+ }
+ if (feature.geometry) {
+ var rendered = this.drawGeometry(feature.geometry, style, feature.id);
+ if(rendered !== false && style.label) {
+ var location = feature.geometry.getCentroid();
+ this.drawText(feature.id, style, location);
+ } else {
+ this.removeText(feature.id);
+ }
+ return rendered;
+ }
+ },
+
+ /**
+ * Method: drawText
+ * Function for drawing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String|DOMElement}
+ * style - {Object}
+ * location - {<OpenLayers.Geometry.Point>}, will be modified inline
+ *
+ * Returns:
+ * {DOMElement} container holding the text label (to be populated by
+ * subclasses)
+ */
+ drawText: function(featureId, style, location) {
+ var label;
+ if (typeof featureId !== "string") {
+ label = featureId;
+ } else {
+ label = this.nodeFactory(featureId + this.LABEL_ID_SUFFIX, this.labelNodeType);
+ label._featureId = featureId;
+ }
+ label._style = style;
+ label._x = location.x;
+ label._y = location.y;
+ if(style.labelXOffset || style.labelYOffset) {
+ var xOffset = isNaN(style.labelXOffset) ? 0 : style.labelXOffset;
+ var yOffset = isNaN(style.labelYOffset) ? 0 : style.labelYOffset;
+ var res = this.getResolution();
+ location.move(xOffset*res, yOffset*res);
+ }
+
+ if(label.parentNode !== this.textRoot) {
+ this.textRoot.appendChild(label);
+ }
+
+ return label;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.NG"
+});
+
+// Monkey-patching Layer.Vector for Renderer.NG support
+(function() {
+ var moveTo = OpenLayers.Layer.Vector.prototype.moveTo;
+ OpenLayers.Layer.Vector.prototype.moveTo = function(bounds, zoomChanged, dragging) {
+ if (OpenLayers.Renderer.NG && this.renderer instanceof OpenLayers.Renderer.NG) {
+ OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+ dragging || this.renderer.updateDimensions(zoomChanged);
+ if (!this.drawn) {
+ this.drawn = true;
+ var feature;
+ for(var i=0, len=this.features.length; i<len; i++) {
+ this.renderer.locked = (i !== (len - 1));
+ feature = this.features[i];
+ this.drawFeature(feature);
+ }
+ }
+ } else {
+ moveTo.apply(this, arguments);
+ }
+ }
+ var redraw = OpenLayers.Layer.Vector.prototype.redraw;
+ OpenLayers.Layer.Vector.prototype.redraw = function() {
+ if (OpenLayers.Renderer.NG && this.renderer instanceof OpenLayers.Renderer.NG) {
+ this.drawn = false;
+ }
+ redraw.apply(this, arguments);
+ }
+})();
+
+/**
+ * Class: OpenLayers.Renderer.SVG2
+ *
+ * Inherits from:
+ * - <OpenLayers.Renderer.NG>
+ */
+OpenLayers.Renderer.SVG2 = OpenLayers.Class(OpenLayers.Renderer.NG, {
+
+ /**
+ * Property: xmlns
+ * {String}
+ */
+ xmlns: "http://www.w3.org/2000/svg",
+
+ /**
+ * Property: xlinkns
+ * {String}
+ */
+ xlinkns: "http://www.w3.org/1999/xlink",
+
+ /**
+ * Property: symbolMetrics
+ * {Object} Cache for symbol metrics according to their svg coordinate
+ * space. This is an object keyed by the symbol's id, and values are
+ * an object with size, x and y properties.
+ */
+ symbolMetrics: null,
+
+ /**
+ * Constant: labelNodeType
+ * {String} The node type for text label containers.
+ */
+ labelNodeType: "g",
+
+ /**
+ * Constructor: OpenLayers.Renderer.SVG2
+ *
+ * Parameters:
+ * containerID - {String}
+ */
+ initialize: function(containerID) {
+ if (!this.supported()) {
+ return;
+ }
+ OpenLayers.Renderer.Elements.prototype.initialize.apply(this,
+ arguments);
+
+ this.symbolMetrics = {};
+ },
+
+ /**
+ * APIMethod: supported
+ *
+ * Returns:
+ * {Boolean} Whether or not the browser supports the SVG renderer
+ */
+ supported: function() {
+ var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+ return (document.implementation &&
+ (document.implementation.hasFeature("org.w3c.svg", "1.0") ||
+ document.implementation.hasFeature(svgFeature + "SVG", "1.1") ||
+ document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+ },
+
+ /**
+ * Method: updateDimensions
+ *
+ * Parameters:
+ * zoomChanged - {Boolean}
+ */
+ updateDimensions: function(zoomChanged) {
+ OpenLayers.Renderer.NG.prototype.updateDimensions.apply(this, arguments);
+
+ var res = this.getResolution();
+
+ var width = this.extent.getWidth();
+ var height = this.extent.getHeight();
+
+ var extentString = [
+ this.extent.left,
+ -this.extent.top,
+ width,
+ height
+ ].join(" ");
+ this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+ this.rendererRoot.setAttributeNS(null, "width", width / res);
+ this.rendererRoot.setAttributeNS(null, "height", height / res);
+
+ if (zoomChanged === true) {
+ // update styles for the new resolution
+ var i, len;
+ var nodes = this.vectorRoot.childNodes;
+ for (i=0, len=nodes.length; i<len; ++i) {
+ this.setStyle(nodes[i]);
+ }
+ var textNodes = this.textRoot.childNodes;
+ var label;
+ for (i=0, len=textNodes.length; i<len; ++i) {
+ label = textNodes[i];
+ this.drawText(label, label._style,
+ new OpenLayers.Geometry.Point(label._x, label._y)
+ );
+ }
+ }
+ },
+
+ /**
+ * Method: getNodeType
+ *
+ * Parameters:
+ * geometry - {<OpenLayers.Geometry>}
+ * style - {Object}
+ *
+ * Returns:
+ * {String} The corresponding node type for the specified geometry
+ */
+ getNodeType: function(geometry, style) {
+ var nodeType = null;
+ switch (geometry.CLASS_NAME) {
+ case "OpenLayers.Geometry.Point":
+ if (style.externalGraphic) {
+ nodeType = "image";
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ nodeType = "svg";
+ } else {
+ nodeType = "circle";
+ }
+ break;
+ case "OpenLayers.Geometry.Rectangle":
+ nodeType = "rect";
+ break;
+ case "OpenLayers.Geometry.LineString":
+ nodeType = "polyline";
+ break;
+ case "OpenLayers.Geometry.LinearRing":
+ nodeType = "polygon";
+ break;
+ case "OpenLayers.Geometry.Polygon":
+ case "OpenLayers.Geometry.Curve":
+ nodeType = "path";
+ break;
+ default:
+ break;
+ }
+ return nodeType;
+ },
+
+ /**
+ * Method: setStyle
+ * Use to set all the style attributes to a SVG node.
+ *
+ * Takes care to adjust stroke width and point radius to be
+ * resolution-relative
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element to decorate
+ * style - {Object}
+ * options - {Object} Currently supported options include
+ * 'isFilled' {Boolean} and
+ * 'isStroked' {Boolean}
+ */
+ setStyle: function(node, style, options) {
+ style = style || node._style;
+ options = options || node._options;
+ var resolution = this.getResolution();
+ var r = node._radius;
+ var widthFactor = resolution;
+ if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+ node.style.visibility = "";
+ if (style.graphic === false) {
+ node.style.visibility = "hidden";
+ } else if (style.externalGraphic) {
+
+ if (style.graphicTitle) {
+ node.setAttributeNS(null, "title", style.graphicTitle);
+ //Standards-conformant SVG
+ // Prevent duplicate nodes. See issue https://github.com/openlayers/openlayers/issues/92
+ var titleNode = node.getElementsByTagName("title");
+ if (titleNode.length > 0) {
+ titleNode[0].firstChild.textContent = style.graphicTitle;
+ } else {
+ var label = this.nodeFactory(null, "title");
+ label.textContent = style.graphicTitle;
+ node.appendChild(label);
+ }
+ }
+ if (style.graphicWidth && style.graphicHeight) {
+ node.setAttributeNS(null, "preserveAspectRatio", "none");
+ }
+ var width = style.graphicWidth || style.graphicHeight;
+ var height = style.graphicHeight || style.graphicWidth;
+ width = width ? width : style.pointRadius*2;
+ height = height ? height : style.pointRadius*2;
+ width *= resolution;
+ height *= resolution;
+
+ var xOffset = (style.graphicXOffset != undefined) ?
+ style.graphicXOffset * resolution : -(0.5 * width);
+ var yOffset = (style.graphicYOffset != undefined) ?
+ style.graphicYOffset * resolution : -(0.5 * height);
+
+ var opacity = style.graphicOpacity || style.fillOpacity;
+
+ node.setAttributeNS(null, "x", node._x + xOffset);
+ node.setAttributeNS(null, "y", node._y + yOffset);
+ node.setAttributeNS(null, "width", width);
+ node.setAttributeNS(null, "height", height);
+ node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
+ node.setAttributeNS(null, "style", "opacity: "+opacity);
+ node.onclick = OpenLayers.Renderer.SVG2.preventDefault;
+ } else if (this.isComplexSymbol(style.graphicName)) {
+ // the symbol viewBox is three times as large as the symbol
+ var offset = style.pointRadius * 3 * resolution;
+ var size = offset * 2;
+ var src = this.importSymbol(style.graphicName);
+ widthFactor = this.symbolMetrics[src.id].size * 3 / size * resolution;
+
+ // remove the node from the dom before we modify it. This
+ // prevents various rendering issues in Safari and FF
+ var parent = node.parentNode;
+ var nextSibling = node.nextSibling;
+ if(parent) {
+ parent.removeChild(node);
+ }
+
+ // The more appropriate way to implement this would be use/defs,
+ // but due to various issues in several browsers, it is safer to
+ // copy the symbols instead of referencing them.
+ // See e.g. ticket http://trac.osgeo.org/openlayers/ticket/2985
+ // and this email thread
+ // http://osgeo-org.1803224.n2.nabble.com/Select-Control-Ctrl-click-on-Feature-with-a-graphicName-opens-new-browser-window-tc5846039.html
+ node.firstChild && node.removeChild(node.firstChild);
+ node.appendChild(src.firstChild.cloneNode(true));
+ node.setAttributeNS(null, "viewBox", src.getAttributeNS(null, "viewBox"));
+
+ node.setAttributeNS(null, "width", size);
+ node.setAttributeNS(null, "height", size);
+ node.setAttributeNS(null, "x", node._x - offset);
+ node.setAttributeNS(null, "y", node._y - offset);
+
+ // now that the node has all its new properties, insert it
+ // back into the dom where it was
+ if(nextSibling) {
+ parent.insertBefore(node, nextSibling);
+ } else if(parent) {
+ parent.appendChild(node);
+ }
+ } else {
+ node.setAttributeNS(null, "r", style.pointRadius * resolution);
+ }
+
+ var rotation = style.rotation;
+ if (rotation !== undefined || node._rotation !== undefined) {
+ node._rotation = rotation;
+ rotation |= 0;
+ if (node.nodeName !== "svg") {
+ node.setAttributeNS(null, "transform",
+ ["rotate(", rotation, node._x, node._y, ")"].join(" ")
+ );
+ } else {
+ var metrics = this.symbolMetrics[src.id];
+ node.firstChild.setAttributeNS(null, "transform",
+ ["rotate(", rotation, metrics.x, metrics.y, ")"].join(" ")
+ );
+ }
+ }
+ }
+
+ if (options.isFilled) {
+ node.setAttributeNS(null, "fill", style.fillColor);
+ node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+ } else {
+ node.setAttributeNS(null, "fill", "none");
+ }
+
+ if (options.isStroked) {
+ node.setAttributeNS(null, "stroke", style.strokeColor);
+ node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+ node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+ node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap || "round");
+ // Hard-coded linejoin for now, to make it look the same as in VML.
+ // There is no strokeLinejoin property yet for symbolizers.
+ node.setAttributeNS(null, "stroke-linejoin", "round");
+ style.strokeDashstyle && node.setAttributeNS(null,
+ "stroke-dasharray", this.dashStyle(style, widthFactor));
+ } else {
+ node.setAttributeNS(null, "stroke", "none");
+ }
+
+ if (style.pointerEvents) {
+ node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+ }
+
+ if (style.cursor != null) {
+ node.setAttributeNS(null, "cursor", style.cursor);
+ }
+
+ return node;
+ },
+
+ /**
+ * Method: dashStyle
+ *
+ * Parameters:
+ * style - {Object}
+ * widthFactor - {Number}
+ *
+ * Returns:
+ * {String} A SVG compliant 'stroke-dasharray' value
+ */
+ dashStyle: function(style, widthFactor) {
+ var w = style.strokeWidth * widthFactor;
+ var str = style.strokeDashstyle;
+ switch (str) {
+ case 'solid':
+ return 'none';
+ case 'dot':
+ return [widthFactor, 4 * w].join();
+ case 'dash':
+ return [4 * w, 4 * w].join();
+ case 'dashdot':
+ return [4 * w, 4 * w, widthFactor, 4 * w].join();
+ case 'longdash':
+ return [8 * w, 4 * w].join();
+ case 'longdashdot':
+ return [8 * w, 4 * w, widthFactor, 4 * w].join();
+ default:
+ var parts = OpenLayers.String.trim(str).split(/\s+/g);
+ for (var i=0, ii=parts.length; i<ii; i++) {
+ parts[i] = parts[i] * widthFactor;
+ }
+ return parts.join();
+ }
+ },
+
+ /**
+ * Method: createNode
+ *
+ * Parameters:
+ * type - {String} Kind of node to draw
+ * id - {String} Id for node
+ *
+ * Returns:
+ * {DOMElement} A new node of the given type and id
+ */
+ createNode: function(type, id) {
+ var node = document.createElementNS(this.xmlns, type);
+ if (id) {
+ node.setAttributeNS(null, "id", id);
+ }
+ return node;
+ },
+
+ /**
+ * Method: nodeTypeCompare
+ *
+ * Parameters:
+ * node - {SVGDomElement} An SVG element
+ * type - {String} Kind of node
+ *
+ * Returns:
+ * {Boolean} Whether or not the specified node is of the specified type
+ */
+ nodeTypeCompare: function(node, type) {
+ return (type == node.nodeName);
+ },
+
+ /**
+ * Method: createRenderRoot
+ *
+ * Returns:
+ * {DOMElement} The specific render engine's root element
+ */
+ createRenderRoot: function() {
+ return this.nodeFactory(this.container.id + "_svgRoot", "svg");
+ },
+
+ /**
+ * Method: createRoot
+ *
+ * Parameters:
+ * suffix - {String} suffix to append to the id
+ *
+ * Returns:
+ * {DOMElement}
+ */
+ createRoot: function(suffix) {
+ return this.nodeFactory(this.container.id + suffix, "g");
+ },
+
+ /**
+ * Method: createDefs
+ *
+ * Returns:
+ * {DOMElement} The element to which we'll add the symbol definitions
+ */
+ createDefs: function() {
+ var defs = this.nodeFactory(this.container.id + "_defs", "defs");
+ this.rendererRoot.appendChild(defs);
+ return defs;
+ },
+
+ /**************************************
+ * *
+ * GEOMETRY DRAWING FUNCTIONS *
+ * *
+ **************************************/
+
+ /**
+ * Method: drawPoint
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the point
+ */
+ drawPoint: function(node, geometry) {
+ return this.drawCircle(node, geometry, 1);
+ },
+
+ /**
+ * Method: drawCircle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ * radius - {Float}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the circle
+ */
+ drawCircle: function(node, geometry, radius) {
+ var x = geometry.x;
+ var y = -geometry.y;
+ node.setAttributeNS(null, "cx", x);
+ node.setAttributeNS(null, "cy", y);
+ node._x = x;
+ node._y = y;
+ node._radius = radius;
+ return node;
+ },
+
+ /**
+ * Method: drawLineString
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components of
+ * the linestring, or false if nothing could be drawn
+ */
+ drawLineString: function(node, geometry) {
+ var path = this.getComponentsString(geometry.components);
+ node.setAttributeNS(null, "points", path);
+ return node;
+ },
+
+ /**
+ * Method: drawLinearRing
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the linear ring, or false if nothing could be drawn
+ */
+ drawLinearRing: function(node, geometry) {
+ var path = this.getComponentsString(geometry.components);
+ node.setAttributeNS(null, "points", path);
+ return node;
+ },
+
+ /**
+ * Method: drawPolygon
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or null if the renderer could not draw all components
+ * of the polygon, or false if nothing could be drawn
+ */
+ drawPolygon: function(node, geometry) {
+ var d = [];
+ var draw = true;
+ var complete = true;
+ var linearRingResult, path;
+ for (var j=0, len=geometry.components.length; j<len; j++) {
+ d.push("M");
+ path = this.getComponentsString(
+ geometry.components[j].components, " ");
+ d.push(path);
+ }
+ d.push("z");
+ node.setAttributeNS(null, "d", d.join(" "));
+ node.setAttributeNS(null, "fill-rule", "evenodd");
+ return node;
+ },
+
+ /**
+ * Method: drawRectangle
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * geometry - {<OpenLayers.Geometry>}
+ *
+ * Returns:
+ * {DOMElement} or false if the renderer could not draw the rectangle
+ */
+ drawRectangle: function(node, geometry) {
+ node.setAttributeNS(null, "x", geometry.x);
+ node.setAttributeNS(null, "y", -geometry.y);
+ node.setAttributeNS(null, "width", geometry.width);
+ node.setAttributeNS(null, "height", geometry.height);
+ return node;
+ },
+
+ /**
+ * Method: drawText
+ * Function for drawing text labels.
+ * This method is only called by the renderer itself.
+ *
+ * Parameters:
+ * featureId - {String|DOMElement}
+ * style - {Object}
+ * location - {<OpenLayers.Geometry.Point>}, will be modified inline
+ *
+ * Returns:
+ * {DOMElement} container holding the text label
+ */
+ drawText: function(featureId, style, location) {
+ var g = OpenLayers.Renderer.NG.prototype.drawText.apply(this, arguments);
+ var text = g.firstChild ||
+ this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_text", "text");
+
+ var res = this.getResolution();
+ text.setAttributeNS(null, "x", location.x / res);
+ text.setAttributeNS(null, "y", - location.y / res);
+ g.setAttributeNS(null, "transform", "scale(" + res + ")");
+
+ if (style.fontColor) {
+ text.setAttributeNS(null, "fill", style.fontColor);
+ }
+ if (style.fontOpacity) {
+ text.setAttributeNS(null, "opacity", style.fontOpacity);
+ }
+ if (style.fontFamily) {
+ text.setAttributeNS(null, "font-family", style.fontFamily);
+ }
+ if (style.fontSize) {
+ text.setAttributeNS(null, "font-size", style.fontSize);
+ }
+ if (style.fontWeight) {
+ text.setAttributeNS(null, "font-weight", style.fontWeight);
+ }
+ if (style.fontStyle) {
+ text.setAttributeNS(null, "font-style", style.fontStyle);
+ }
+ if (style.labelSelect === true) {
+ text.setAttributeNS(null, "pointer-events", "visible");
+ text._featureId = featureId;
+ } else {
+ text.setAttributeNS(null, "pointer-events", "none");
+ }
+ var align = style.labelAlign || OpenLayers.Renderer.defaultSymbolizer.labelAlign;
+ text.setAttributeNS(null, "text-anchor",
+ OpenLayers.Renderer.SVG2.LABEL_ALIGN[align[0]] || "middle");
+
+ if (OpenLayers.IS_GECKO === true) {
+ text.setAttributeNS(null, "dominant-baseline",
+ OpenLayers.Renderer.SVG2.LABEL_ALIGN[align[1]] || "central");
+ }
+
+ var labelRows = style.label.split('\n');
+ var numRows = labelRows.length;
+ while (text.childNodes.length > numRows) {
+ text.removeChild(text.lastChild);
+ }
+ for (var i = 0; i < numRows; i++) {
+ var tspan = text.childNodes[i] ||
+ this.nodeFactory(featureId + this.LABEL_ID_SUFFIX + "_tspan_" + i, "tspan");
+ if (style.labelSelect === true) {
+ tspan._featureId = featureId;
+ }
+ if (OpenLayers.IS_GECKO === false) {
+ tspan.setAttributeNS(null, "baseline-shift",
+ OpenLayers.Renderer.SVG2.LABEL_VSHIFT[align[1]] || "-35%");
+ }
+ tspan.setAttribute("x", location.x / res);
+ if (i == 0) {
+ var vfactor = OpenLayers.Renderer.SVG2.LABEL_VFACTOR[align[1]];
+ if (vfactor == null) {
+ vfactor = -.5;
+ }
+ tspan.setAttribute("dy", (vfactor*(numRows-1)) + "em");
+ } else {
+ tspan.setAttribute("dy", "1em");
+ }
+ tspan.textContent = (labelRows[i] === '') ? ' ' : labelRows[i];
+ if (!tspan.parentNode) {
+ text.appendChild(tspan);
+ }
+ }
+
+ if (!text.parentNode) {
+ g.appendChild(text);
+ }
+
+ return g;
+ },
+
+ /**
+ * Method: getComponentString
+ *
+ * Parameters:
+ * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+ * separator - {String} character between coordinate pairs. Defaults to ","
+ *
+ * Returns:
+ * {Object} hash with properties "path" (the string created from the
+ * components and "complete" (false if the renderer was unable to
+ * draw all components)
+ */
+ getComponentsString: function(components, separator) {
+ var len = components.length;
+ var strings = new Array(len);
+ for (var i=0; i<len; i++) {
+ strings[i] = this.getShortString(components[i]);
+ }
+
+ return strings.join(separator || ",");
+ },
+
+ /**
+ * Method: getShortString
+ *
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point>}
+ *
+ * Returns:
+ * {String} or false if point is outside the valid range
+ */
+ getShortString: function(point) {
+ return point.x + "," + (-point.y);
+ },
+
+ /**
+ * Method: importSymbol
+ * add a new symbol definition from the rendererer's symbol hash
+ *
+ * Parameters:
+ * graphicName - {String} name of the symbol to import
+ *
+ * Returns:
+ * {DOMElement} - the imported symbol
+ */
+ importSymbol: function (graphicName) {
+ if (!this.defs) {
+ // create svg defs tag
+ this.defs = this.createDefs();
+ }
+ var id = this.container.id + "-" + graphicName;
+
+ // check if symbol already exists in the defs
+ var existing = document.getElementById(id);
+ if (existing != null) {
+ return existing;
+ }
+
+ var symbol = OpenLayers.Renderer.symbol[graphicName];
+ if (!symbol) {
+ throw new Error(graphicName + ' is not a valid symbol name');
+ }
+
+ var symbolNode = this.nodeFactory(id, "symbol");
+ var node = this.nodeFactory(null, "polygon");
+ symbolNode.appendChild(node);
+ var symbolExtent = new OpenLayers.Bounds(
+ Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+ var points = [];
+ var x,y;
+ for (var i=0, len=symbol.length; i<len; i=i+2) {
+ x = symbol[i];
+ y = symbol[i+1];
+ symbolExtent.left = Math.min(symbolExtent.left, x);
+ symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+ symbolExtent.right = Math.max(symbolExtent.right, x);
+ symbolExtent.top = Math.max(symbolExtent.top, y);
+ points.push(x, ",", y);
+ }
+
+ node.setAttributeNS(null, "points", points.join(" "));
+
+ var width = symbolExtent.getWidth();
+ var height = symbolExtent.getHeight();
+ // create a viewBox three times as large as the symbol itself,
+ // to allow for strokeWidth being displayed correctly at the corners.
+ var viewBox = [symbolExtent.left - width,
+ symbolExtent.bottom - height, width * 3, height * 3];
+ symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+ this.symbolMetrics[id] = {
+ size: Math.max(width, height),
+ x: symbolExtent.getCenterLonLat().lon,
+ y: symbolExtent.getCenterLonLat().lat
+ };
+
+ this.defs.appendChild(symbolNode);
+ return symbolNode;
+ },
+
+ /**
+ * Method: getFeatureIdFromEvent
+ *
+ * Parameters:
+ * evt - {Object} An <OpenLayers.Event> object
+ *
+ * Returns:
+ * {String} A feature id or undefined.
+ */
+ getFeatureIdFromEvent: function(evt) {
+ var featureId = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent.apply(this, arguments);
+ if(!featureId) {
+ var target = evt.target;
+ featureId = target.parentNode && target != this.rendererRoot ?
+ target.parentNode._featureId : undefined;
+ }
+ return featureId;
+ },
+
+ CLASS_NAME: "OpenLayers.Renderer.SVG2"
+});
+
+/**
+ * Constant: OpenLayers.Renderer.SVG2.LABEL_ALIGN
+ * {Object}
+ */
+OpenLayers.Renderer.SVG2.LABEL_ALIGN = {
+ "l": "start",
+ "r": "end",
+ "b": "bottom",
+ "t": "hanging"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG2.LABEL_VSHIFT
+ * {Object}
+ */
+OpenLayers.Renderer.SVG2.LABEL_VSHIFT = {
+ // according to
+ // http://www.w3.org/Graphics/SVG/Test/20061213/htmlObjectHarness/full-text-align-02-b.html
+ // a baseline-shift of -70% shifts the text exactly from the
+ // bottom to the top of the baseline, so -35% moves the text to
+ // the center of the baseline.
+ "t": "-70%",
+ "b": "0"
+};
+
+/**
+ * Constant: OpenLayers.Renderer.SVG2.LABEL_VFACTOR
+ * {Object}
+ */
+OpenLayers.Renderer.SVG2.LABEL_VFACTOR = {
+ "t": 0,
+ "b": -1
+};
+
+/**
+ * Function: OpenLayers.Renderer.SVG2.preventDefault
+ * Used to prevent default events (especially opening images in a new tab on
+ * ctrl-click) from being executed for externalGraphic and graphicName symbols
+ */
+OpenLayers.Renderer.SVG2.preventDefault = function(e) {
+ e.preventDefault && e.preventDefault();
+};
+
+/**
+ * Class: OpenLayers.Popup.AnchoredBubble
+ * This class is *deprecated*. Use {<OpenLayers.Popup.Anchored>} and
+ * round corners using CSS3's border-radius property.
+ *
+ * Inherits from:
+ * - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.AnchoredBubble = OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+ /**
+ * Property: rounded
+ * {Boolean} Has the popup been rounded yet?
+ */
+ rounded: false,
+
+ /**
+ * Constructor: OpenLayers.Popup.AnchoredBubble
+ *
+ * Parameters:
+ * id - {String}
+ * lonlat - {<OpenLayers.LonLat>}
+ * contentSize - {<OpenLayers.Size>}
+ * contentHTML - {String}
+ * anchor - {Object} Object to which we'll anchor the popup. Must expose
+ * a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>)
+ * (Note that this is generally an <OpenLayers.Icon>).
+ * closeBox - {Boolean}
+ * closeBoxCallback - {Function} Function to be called on closeBox click.
+ */
+ initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+ closeBoxCallback) {
+
+ this.padding = new OpenLayers.Bounds(
+ 0, OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,
+ 0, OpenLayers.Popup.AnchoredBubble.CORNER_SIZE
+ );
+ OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+ },
+
+ /**
+ * Method: draw
+ *
+ * Parameters:
+ * px - {<OpenLayers.Pixel>}
+ *
+ * Returns:
+ * {DOMElement} Reference to a div that contains the drawn popup.
+ */
+ draw: function(px) {
+
+ OpenLayers.Popup.Anchored.prototype.draw.apply(this, arguments);
+
+ this.setContentHTML();
+
+ //set the popup color and opacity
+ this.setBackgroundColor();
+ this.setOpacity();
+
+ return this.div;
+ },
+
+ /**
+ * Method: updateRelativePosition
+ * The popup has been moved to a new relative location, in which case
+ * we will want to re-do the rico corners.
+ */
+ updateRelativePosition: function() {
+ this.setRicoCorners();
+ },
+
+ /**
+ * APIMethod: setSize
+ *
+ * Parameters:
+ * contentSize - {<OpenLayers.Size>} the new size for the popup's
+ * contents div (in pixels).
+ */
+ setSize:function(contentSize) {
+ OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+ this.setRicoCorners();
+ },
+
+ /**
+ * APIMethod: setBackgroundColor
+ *
+ * Parameters:
+ * color - {String}
+ */
+ setBackgroundColor:function(color) {
+ if (color != undefined) {
+ this.backgroundColor = color;
+ }
+
+ if (this.div != null) {
+ if (this.contentDiv != null) {
+ this.div.style.background = "transparent";
+ OpenLayers.Rico.Corner.changeColor(this.groupDiv,
+ this.backgroundColor);
+ }
+ }
+ },
+
+ /**
+ * APIMethod: setOpacity
+ *
+ * Parameters:
+ * opacity - {float}
+ */
+ setOpacity:function(opacity) {
+ OpenLayers.Popup.Anchored.prototype.setOpacity.call(this, opacity);
+
+ if (this.div != null) {
+ if (this.groupDiv != null) {
+ OpenLayers.Rico.Corner.changeOpacity(this.groupDiv,
+ this.opacity);
+ }
+ }
+ },
+
+ /**
+ * Method: setBorder
+ * Always sets border to 0. Bubble Popups can not have a border.
+ *
+ * Parameters:
+ * border - {Integer}
+ */
+ setBorder:function(border) {
+ this.border = 0;
+ },
+
+ /**
+ * Method: setRicoCorners
+ * Update RICO corners according to the popup's current relative postion.
+ */
+ setRicoCorners:function() {
+
+ var corners = this.getCornersToRound(this.relativePosition);
+ var options = {corners: corners,
+ color: this.backgroundColor,
+ bgColor: "transparent",
+ blend: false};
+
+ if (!this.rounded) {
+ OpenLayers.Rico.Corner.round(this.div, options);
+ this.rounded = true;
+ } else {
+ OpenLayers.Rico.Corner.reRound(this.groupDiv, options);
+ //set the popup color and opacity
+ this.setBackgroundColor();
+ this.setOpacity();
+ }
+ },
+
+ /**
+ * Method: getCornersToRound
+ *
+ * Returns:
+ * {String} The proper corners string ("tr tl bl br") for rico to round.
+ */
+ getCornersToRound:function() {
+
+ var corners = ['tl', 'tr', 'bl', 'br'];
+
+ //we want to round all the corners _except_ the opposite one.
+ var corner = OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);
+ OpenLayers.Util.removeItem(corners, corner);
+
+ return corners.join(" ");
+ },
+
+ CLASS_NAME: "OpenLayers.Popup.AnchoredBubble"
+});
+
+/**
+ * Constant: CORNER_SIZE
+ * {Integer} 5. Border space for the RICO corners.
+ */
+OpenLayers.Popup.AnchoredBubble.CORNER_SIZE = 5;
diff --git a/misc/openlayers/license.txt b/misc/openlayers/license.txt
new file mode 100644
index 0000000..cb829cc
--- /dev/null
+++ b/misc/openlayers/license.txt
@@ -0,0 +1,27 @@
+Copyright 2005-2013 OpenLayers Contributors. All rights reserved. See
+authors.txt for full list.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or
+other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The views and conclusions contained in the software and documentation are those
+of the authors and should not be interpreted as representing official policies,
+either expressed or implied, of OpenLayers Contributors.
diff --git a/misc/openlayers/licenses/APACHE-2.0.txt b/misc/openlayers/licenses/APACHE-2.0.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/misc/openlayers/licenses/APACHE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/misc/openlayers/licenses/BSD-LICENSE.txt b/misc/openlayers/licenses/BSD-LICENSE.txt
new file mode 100644
index 0000000..aa321e6
--- /dev/null
+++ b/misc/openlayers/licenses/BSD-LICENSE.txt
@@ -0,0 +1,28 @@
+Redistribution and use of this software in source and binary forms, with or
+without modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+ contributors may be used to endorse or promote products
+ derived from this software without specific prior
+ written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/misc/openlayers/licenses/MIT-LICENSE.txt b/misc/openlayers/licenses/MIT-LICENSE.txt
new file mode 100644
index 0000000..c9b44cb
--- /dev/null
+++ b/misc/openlayers/licenses/MIT-LICENSE.txt
@@ -0,0 +1,18 @@
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/misc/openlayers/notes/2.12.md b/misc/openlayers/notes/2.12.md
new file mode 100644
index 0000000..06d38cf
--- /dev/null
+++ b/misc/openlayers/notes/2.12.md
@@ -0,0 +1,391 @@
+# Major enhancements and additions
+
+## Zoom Control
+
+A simple control to add zoom in/out buttons on the map that can be entirely styled using CSS.
+See it live in this [example](http://openlayers.org/dev/examples/zoom.html).
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/291
+ * https://github.com/openlayers/openlayers/pull/292
+
+## Builds
+
+This version of OpenLayers ships with three builds:
+
+ * `OpenLayers.js`
+ * `OpenLayers.light.js`
+ * `OpenLayers.mobile.js`
+
+See the README.md file and the docs on docs.openlayers.org for more information.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/254
+ * https://github.com/openlayers/openlayers/pull/261
+
+## style.mobile.css
+
+The theme/default directory now includes a mobile-specific CSS file, namely
+style.mobile.css. The OpenLayers mobile examples use this file. To use it
+in your mobile pages use tags like this:
+
+<link rel="stylesheet" href="openlayers/theme/default/style.mobile.css" type="text/css">
+
+(This file used to be in the examples/ directory).
+
+## Sensible projection defaults
+
+The geographic and web mercator projections define default values for the maxExtent, and units. This simplifies the map and layer configuration.
+
+For example, a map that used to be created with this:
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ units: "m",
+ maxExtent: new OpenLayers.Bounds(
+ -20037508.34, -20037508.34, 20037508.34, 20037508.34
+ )
+ });
+
+can now be created with this:
+
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913"
+ });
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/219
+
+## Tile Offline Storage
+
+With the new `OpenLayers.Control.CacheRead` and `OpenLayers.Control.CacheWrite` controls, applications can cache tiles for offline use or for use with slow connections.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/301
+
+## Tile Animation
+
+The displaying of tiles can now be animated, using CSS3 transitions. Transitions operate on the `opacity` property. Here's the CSS rule defined in OpenLayers' default theme:
+
+ .olLayerGrid .olTileImage {
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ -o-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+ }
+
+People can override this rule to use other transition settings. To remove tile animation entirely use:
+
+ .olLayerGrid .olTileImage {
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: all 0 none;
+ transition: none;
+ }
+
+Note that by default tile animation is not enabled for single tile layers.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/127
+
+Note: Issue #511 has reported that tile animation causes flickering/blinking in
+the iOS native browser. Forcing the browser to use hardware-accelerated
+animations fixed the issue, but #542 has reported that it also considerably
+slows down freehand drawing on iOS. If you're experiencing this and want to
+disable hardware-accelerated animations you can use the following rule in your
+CSS:
+
+ @media (-webkit-transform-3d) {
+ img.olTileImage {
+ -webkit-transform: none;
+ }
+ }
+
+## Tile Queue
+
+The tiling code has been overhauled so tile loading in grid layers is now done in a queue.
+The tile queue gives more control on the tile requests sent to the server. Pending requests for tiles that are not needed any more (e.g. after zooming or panning) are avoided, which increases performance and reduces server load.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/179
+
+## Tile Canvas
+
+Image tiles expose a `getCanvasContext` function that can be used for various
+things, like changing the image pixels, save the image using the File API, etc.
+
+See the [osm-grayscale
+example](http://openlayers.org/dev/examples/osm-grayscale.html).
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/160
+
+## Tile Interaction Event Improvements
+
+The layer's `tileloaded` event now returns a reference to the loaded tile. The new `tileloaderror` event does the same, and is fired when a tile could not be loaded.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/283
+
+## Tile and Backbuffer Overhaul
+
+The whole image tile and backbuffer code (behind `transitionEffect:resize`) has been redesigned and
+rewritten. This overhaul yields better performance and code simplifications.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/16
+
+## Continuous Zooming
+
+Tile layers can now be displayed at resolutions not supported by their tiling
+services. This works by requesting tiles at higher resolutions and stretching
+the layer div as appropriate. With this change fractionalZoom:true will work
+for single tile layers as well as for tiled layers.
+
+See the [client zoom
+example](http://openlayers.org/dev/examples/clientzoom.html).
+
+Corresponding issues/pull requests:
+
+ * http://trac.osgeo.org/openlayers/ticket/3531
+ * https://github.com/openlayers/openlayers/pull/5
+
+# Behavior Changes from Past Releases
+
+## MultiMap Layer Removal
+
+The `OpenLayers.Layer.MultiMap` class has been removed entirely, as the MultiMap service was discontinued.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/328
+
+## GPX API change
+
+The `gpxns` API property has been removed. The GPX namespace is now defined in the `namespaces` property but is not intended to be overriden.
+
+GPX also now has a basic write function.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/221
+
+## Function return values
+
+Previously a few functions in the library displayed error messages and returned `undefined`, `null` or `false` if the parameters passed in were bad. In 2.12 these functions now just throw an error/exception. People relying on return values to know if a function call is successful may need to change their code. Here are the modified functions:
+
+ * `OpenLayers.Bounds.add` throws a `TypeError` exception if `x` or `y` is null
+ * `OpenLayers.LonLat.add` throws a `TypeError` exception if `lon` or `lat` is null
+ * `OpenLayers.Pixel.add` throws a `TypeError` exception if `x` or `y` is null
+ * `OpenLayers.Filter.Comparison.value2regex` throws an `Error` exception if `wildcard` equals to `"."`
+ * `OpenLayers.Layer.PointTrack.addNodes` throws a `TypeError` exception if `endPoint` isn't actually a point
+ * `OpenLayers.Layer.Vector.getFeatureFromEvent` throws an `Error` exception if the layer has no renderer
+
+Corresponding issues/pull requests:
+
+ * http://trac.osgeo.org/openlayers/ticket/3320
+
+## Changes in formats WMTSCapabilities and SOSCapabilities
+
+The structure of the object returned by `Format.WMTSCapabilities:read` and `Format.SOSCapabilities:read` has slightly changed.
+
+For `WMTSCapabilities` the GET href used to be made available at `operationsMetadata.GetCapabilities.dcp.http.get`, the latter is now an array of objects with two properties: `url` and `constrains`. People using `operationsMetadata.GetCapabilities.dcp.http.get` in their applications should certainly use `operationsMetadata.GetCapabilities.dcp.http.get[0].url`.
+
+Likewise for `SOSCapabilities`.
+
+Looking at the tests is a good way to understand what the requires changes are. See [SOSCapabilities/v1_0_0.html](https://github.com/openlayers/openlayers/blob/master/tests/Format/SOSCapabilities/v1_0_0.html) and [WMTSCapabilities/v1_0_0.html](https://github.com/openlayers/openlayers/blob/master/tests/Format/WMTSCapabilities/v1_0_0.html).
+
+Corresponding issues/pull requests:
+
+ * http://trac.osgeo.org/openlayers/ticket/3568
+ * https://github.com/openlayers/openlayers/pull/40
+
+
+## Rico deprecation
+
+We are deprecating the Rico classes/objects in OpenLayers. This has the following implications:
+
+`Popup.AnchoredBubble` is deprecated. Its constructor now displays a deprecation message on the console. If you want popups with rounded corners either use `Popup.FramedClould`, or use `Popup.Anchored` and round corners using the [border-radius](https://developer.mozilla.org/en/CSS/border-radius) CSS property.
+
+The `roundedCorner` option of `Control.LayerSwitcher` is deprecated, and it now defaults to `false`. Setting it to true results in deprecation messages being output on the console. If you still want to set `roundedCorner` to `true` (you should not!) you need to make sure that the Rico/Corner.js and Rico/Color.js scripts are loaded in the page. This can be ensured by adding Rico/Corner.js in the build profile. The controls.html example demonstrates how to use `border-radius` to round corners of a layer switcher:
+
+
+ .olControlLayerSwitcher .layersDiv {
+ border-radius: 10px 0 0 10px;
+ }
+
+
+In future releases we intend to move the Rico and `AnchoredBubble` code into deprecated.js. You really should consider stop using Rico-based functionalities in your applications.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/99
+
+## Changes in Geometry
+
+The base `OpenLayers.Geometry` class no longer depends on `OpenLayers.Format.WKT` or `OpenLayers.Feature.Vector`. If you want to make use of the `OpenLayers.Geometry.fromWKT` method, you must explicitly include the OpenLayers/Format/WKT.js file in your build.
+
+Without the WKT format included (by default), the `OpenLayers.Geometry::toString` method now returns "[object Object]." Previously, it returned the Well-Known Text representation of the geometry. To maintain the previous behavior, include the OpenLayers/Format/WKT.js file in your build.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/101
+
+## Google v3 Layer
+
+This release fixes a problem with the clickable elements supplied by Google. `OpenLayers.Layer.Google.v3` is now compatible with the current frozen version of Google's API (3.7) and also with the current release and nightly versions (3.8 and 3.9), but be aware that Google may change these elements in their release and nightly versions at any time, and an interim fix OpenLayers release may be needed.
+
+It's recommended that production servers always load the frozen version of Google's API, but it would help find potential problems if development pages used the latest nightly version.
+
+See the class description in the API docs for `OpenLayers.Layer.Google.v3` for more details.
+
+Good ideas on how to improve this unsatisfactory situation welcome!
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/472
+
+## OSM and Bing Layers
+
+`Layer.OSM` is now defined in its own script file, namely `OpenLayers/Layer/OSM.js`. So people using `Layer.OSM` should now include `OpenLayers/Layer/OSM.js`, as opposed to `OpenLayers/Layer/XYZ.js`, in their OpenLayers builds.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/issues/138
+ * https://github.com/openlayers/openlayers/pull/144
+
+The `OpenLayers.Tile.Image` class now has a method to get a canvas context for processing tiles. Since both OSM and Bing set Access-Control-Allow-Origin headers for their tiles, it is possible to manipulate a canvas that these tiles were rendered to even if the tiles come from a remote origin. Especially when working with custom OSM tilesets from servers that do not send Access-Control-Allow-Origin headers, it is now necessary to configure the layer with
+
+ tileOptions: {crossOriginKeyword: null}
+
+Both `OpenLayers.Layer.OSM` and `OpenLayers.Layer.Bing` do not have defaults for `maxExtent`, `maxResolutions` and `units` any more. This may break maps that are configured with a `maxResolution` of `156543.0339`, which was used in examples before 2.11, but is incorrect. The correct value is `156543.03390625`, but it is no longer necessary to specify a maxResolution, maxExtent and units if the correct resolution is set. See "Projection and Spherical Mercator" below.
+
+## Projection & SphericalMercator
+
+When working with Web Mercator layers (e.g. Google, Bing, OSM), it was previously necessary to configure the map or the base layer with the correct `projection`, `maxExtent`, `maxResolutions` and `units`. Now OpenLayers has defaults for WGS84 and Web Mercator in `OpenLayers.Projection.defaults`, so it is enough to provide the `projection`.
+
+Old:
+
+ new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ maxResolution: 156543.03390625,
+ maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
+ units: "m",
+ layers: [
+ new OpenLayers.Layer.Google("Google Streets"),
+ new OpenLayers.Layer.OSM(null, null, {isBaseLayer: false, opacity: 0.7})
+ ],
+ zoom: 1
+ });
+
+New:
+
+ new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [
+ new OpenLayers.Layer.Google("Google Streets"),
+ new OpenLayers.Layer.OSM(null, null, {isBaseLayer: false, opacity: 0.7})
+ ],
+ zoom: 1
+ });
+
+In previous releases, coordinate transforms between EPSG:4326 and EPSG:900913 were defined in the SphericalMercator.js script. In 2.12, these default transforms are included in the Projection.js script. The Projection.js script is included as a dependency in builds with any layer types, so no special build configuration is necessary to get the web mercator transforms.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/219
+
+If you were previously using the `OpenLayers.Layer.SphericalMercator.forwardMercator` or `inverseMercator` methods, you may have to explicitly include the SphericalMercator.js script in your build. The Google layer is the only layer that depends on the SphericalMercator mixin. If you are not using the Google layer but want to use the SphericalMercator methods listed above, you have to explicitly include the SphericalMercator.js script in your build.
+
+Corresponding issues/pull requests:
+
+* https://github.com/openlayers/openlayers/pull/153
+
+## QueryStringFilter
+
+`OpenLayers.Protocol.HTTP` no longer requires `OpenLayers.Format.QueryStringFilter`. It you need this, make sure it is included in your build config file.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/issues/147
+ * https://github.com/openlayers/openlayers/pull/148
+
+## Changes in getURLasync
+
+The internal `OpenLayers.Layer.getURLasync` function now take a bound, a callback and a scope. The function no longer needs update the passed property but simply to return to url.
+
+## Changes when base layer configured with wrapDateLine: true
+
+Vector editing across the date line works reliably now. To make this work, OpenLayers won't zoom out to resolutions where more than one world is visible any more. For maps that have base layers with wrapDateLine set to false, no zoom restrictions apply.
+
+## OpenLayers.Util.onImageLoadError no longer exists
+
+To replace a tile that couldn't be loaded with a static image, create a css selector for the `.olImageLoadError` class (e.g. a `background-image`).
+
+For more complex tile loading error handling, register a listener to the layer's `tileerror` event.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/283
+
+## Deprecated Components
+
+A number of properties, methods, and constructors have been marked as deprecated for multiple releases in the 2.x series. For the 2.12 release this deprecated functionality has been moved to a separate deprecated.js file. If you use any of the constructors or methods below, you will have to explicitly include the deprecated.js file in your build (or add it in a separate `<script>` tag after OpenLayers.js).
+
+ * OpenLayers.Class.isPrototype
+ * OpenLayers.Class.create
+ * OpenLayers.Class.inherit
+ * OpenLayers.Util.clearArray
+ * OpenLayers.Util.setOpacity
+ * OpenLayers.Util.safeStopPropagation
+ * OpenLayers.Util.getArgs
+ * OpenLayers.nullHandler
+ * OpenLayers.loadURL
+ * OpenLayers.parseXMLString
+ * OpenLayers.Ajax.* (all methods)
+ * OpenLayers.Element.hide
+ * OpenLayers.Element.show
+ * OpenLayers.Element.getDimensions
+ * OpenLayers.Tile.prototype.getBoundsFromBaseLayer
+ * OpenLayers.Control.MouseDefaults
+ * OpenLayers.Control.MouseToolbar
+ * OpenLayers.Layer.Grid.prototype.getGridBounds
+ * OpenLayers.Format.XML.prototype.concatChildValues
+ * OpenLayers.Layer.WMS.Post
+ * OpenLayers.Layer.WMS.Untiled
+ * OpenLayers.Layer.MapServer.Untiled
+ * OpenLayers.Tile.WFS
+ * OpenLayers.Feature.WFS
+ * OpenLayers.Layer.WFS
+ * OpenLayers.Layer.VirtualEarth
+ * OpenLayers.Protocol.SQL
+ * OpenLayers.Protocol.SQL.Gears
+ * OpenLayers.Layer.Yahoo
+ * OpenLayers.Layer.GML
+ * OpenLayers.Geometry.Rectangle
+ * OpenLayers.Renderer.NG
+ * OpenLayers.Renderer.SVG2
+
+In addition, OpenLayers no longer modifies any native prototypes or objects by default. If you rely on any of the following, you'll need to include deprecated.js explicitly to get the same behavior.
+
+ * String.prototype.startsWith
+ * String.prototype.contains
+ * String.prototype.trim
+ * String.prototype.camelize
+ * Function.prototype.bind
+ * Function.prototype.bindAsEventListener
+ * Event.stop
+
diff --git a/misc/openlayers/notes/2.13.md b/misc/openlayers/notes/2.13.md
new file mode 100644
index 0000000..75b334e
--- /dev/null
+++ b/misc/openlayers/notes/2.13.md
@@ -0,0 +1,159 @@
+# Enhancements and Additions
+
+## Dotless identifiers
+
+Previously, objects generated by the library were given id properties with values that contained dots (e.g. "OpenLayers.Control.Navigation_2"). These same identifiers are also used for DOM elements in some case. Though uncommon, a developer may want to access these elements with a CSS selector. To facilitate this, we now always generate ids with underscore instead of dot.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/416
+
+## Better support for analog scroll wheel
+
+Removed rounding of zoom level for maps with fractionalZoom == true. So users with an OS and interface device with analog scroll support will now get smooth zooming.
+
+Corresponding issues/pull requests:
+
+ * https://github.com/openlayers/openlayers/pull/483
+
+## Google v3 Layer
+
+This release changes the way Google layers are integrated with OpenLayers. With this change, OpenLayers should be less fragile to changes of the GMaps API version, because no DOM elements inside the Google container need to be modified by OpenLayers any more.
+
+Application developers should be aware that the Google Map of an `OpenLayers.Layer.Google.v3` instance is no longer added to the map's `viewPortDiv`. Instead, the `viewPortDiv` is added as Google Maps control to the Google map. This means that when switching base layers, the whole DOM structure below the map's `div` changes.
+
+Corresponding issues/pull requests:
+
+* https://github.com/openlayers/openlayers/pull/484
+
+## Bing Layer
+
+All requests to the Bing Maps service are now sent using the same protocol as the OpenLayers application using the Bing layer. For file:/// URIs, the http
+protocol is used. A new config option `protocol` has been introduced to set the protocol to use for requests to the Bing Maps service. 'https:' should work fine, but the availability of tiles and attribution logo with the https protocol is not guaranteed. If in doubt, set `protocol` to 'http:'.
+
+Corresponding issues/pull requests:
+
+* http://github.com/openlayers/openlayers/pull/700
+
+## New Map and Vector Layer Events for Feature Interaction
+
+The featureclick events extension (`lib/Events/featureclick.js`) provides four new events ("featureclick", "nofeatureclick", "featureover", "featureout") that can be used as an alternative to the Feature handler or the
+SelectFeature control. It works with multiple layers out of the box and can detect hits on multiple features (except when using the Canvas renderer). See `examples/feature-events.html` for an implementation example.
+
+# Behavior Changes from Past Releases
+
+## Control.DragPan: Kinetic by default
+
+The `enableKinetic` property for the DragPan control has been changed to true by default. This will enable kinetic panning only if the `OpenLayers/Kinetic.js` file is included in your build.
+
+## Control.ModifyFeature: no more built-in SelectFeature control
+
+The ModifyFeature control is now much leaner, making it more reliable when combined with other controls. The most noticable change is that it has no
+`selectControl` member any more. Users who previously relied on this built-in SelectFeature control will now have to create both a SelectFeature and a ModifyFeature control and configure the ModifyFeature control with `standalone: true`. To get features selected, call the `selectFeature` method e.g. from a `featureselected` listener on the vector layer. Note that other than in the old implementation, calling `selectFeature` on an already selected feature will not do anything.
+
+## Format.GPX: No more prefixes
+
+No `gpx:` prefix is added in the XML tags anymore when writing GPX from `OpenLayers` features. It seems like it is not supported by most of the tools that are able to read GPX.
+
+## Different return type for OpenLayers.Format.WMSDescribeLayer
+
+The return type of WMSDescribeLayer format's `read` method was different from the one of the VersionedOGC format superclass. So it was changed from an array to an object with a layerDescriptions property that holds the array. For backwards compatibility, the object still has a length property and 0, ..., n properties with the previous array values.
+
+## Moved errorProperty from the base class to the parser in Format.OWSCommon
+
+This was necessary for WCS support because there are no properties in common between versions 1.0.0 and 1.1.0 that were appropriate for checking. The only existing code that this affected was WFS parsing.
+
+## Layer.Grid: Tile queue and tileLoadingDelay changes
+
+With the introduction of OpenLayers.TileManager, tile queueing has become optional but is enabled by default. To not use a tile queue in 2.13, the map needs to be configured with tileManager: null, e.g.:
+
+ var map = new OpenLayers.Map('map', {
+ tileManager: null
+ });
+
+The tile queue works differently than before: it no longer loads one tile at a time. Instead, it waits after a zoom or pan, and loads all tiles after a delay. This has the same effect as previously (less burden on the server), but makes use of the browser's request management. The delay can be configured separately for zooming and moving the map, using the `zoomDelay` (default: 200 ms) and `moveDelay` (default: 100 ms) config options of the TileManager. If you want to have the map be associated with a TileManager with non-default options, supply the options instead or create your own TileManager instance and supply it to the Map constructor.
+
+The `moveDelay` is the replacement for the `tileLoadingDelay` layer config option, which has been removed. There is no magic any more to only use the delay when requestAnimationFrame is not natively available.
+
+## Layer.Grid: Resize transitions by default
+
+The `transitionEffect` property for grid layers has been changed to "resize" by default. This allows smooth transitions with animated zooming (also enabled by default). If resize transitions are not wanted for individual layers, set `transitionEffect` to `null`.
+
+## Map: Animated zooming and GPU support
+
+OpenLayers now has animated zooming, which is enabled by default. To turn it off, configure the map with `zoomMethod: null`.
+
+To make the zoom animation smooth, GPU support is active by default for rendering tiles. This may interfere with UI widgets that overlay the map. In this case, it may be necessary to turn GPU support off, which is done with the following css declaration:
+
+ img.olTileImage {
+ -webkit-transform: inherit;
+ -moz-transform: inherit;
+ -o-transform: inherit;
+ -ms-transform: inherit;
+ transform: inherit;
+ -webkit-backface-visibility: inherit;
+ -moz-backface-visibility: inherit;
+ -ms-backface-visibility: inherit;
+ backface-visibility: inherit;
+ -webkit-perspective: inherit;
+ -moz-perspective: inherit;
+ -ms-perspective: inherit;
+ perspective: inherit;
+ }
+
+## Map property fallThrough defaults to false
+
+The behaviour controlled by map property fallThrough wasn't consistent (some events were swallowed even with fallThrough set to true) and changes have been made to fix that. Defaulting fallThrough to false after this change is sensible in most situations and will probably be what most applications expect, but if you previously relied on pointer or keyboard events being passed through you will probably want to set fallThrough to true.
+
+Behavioural change was made in this commit:
+
+* https://github.com/openlayers/openlayers/commit/a6119f6a7528e013b922fd0d997a07df13f6bd6e
+
+## window.$ is no longer an alias for OpenLayers.Util.getElement
+
+We do no longer create a global variable `$` when such a symbol isn't already
+defined. Previous versions of OpenLayers would define `$` to be an alias for
+`OpenLayers.Util.getElement`. If your application requires `window.$` to be
+defined in such a way you can either
+
+* include deprecated.js in your custom build or as additional ressource in your
+ HTML-file
+* or you do the aliasing in your application code yourself:
+
+ `window.$ = OpenLayers.Util.getElement;`
+
+Corresponding issue/pull requests:
+
+* https://github.com/openlayers/openlayers/pull/423
+
+# New Options for Build Script
+
+* add the contents of a file as a comment at the front of the build, for example, the output of 'git describe --tags' could be saved as a file and then included
+* create build file as an AMD module
+
+run 'build.py -h' for more details
+
+Corresponding issue/pull requests:
+
+* https://github.com/openlayers/openlayers/pull/528
+
+## Deprecated Components
+A number of properties, methods, and constructors have been marked as
+deprecated for multiple releases in the 2.x series.
+For the 2.13 release this deprecated functionality has been moved to a
+separate deprecated.js file. If you use any of the constructors or
+methods below, you will have to explicitly include the deprecated.js
+file in your build (or add it in a separate `<script>` tag after
+OpenLayers.js).
+
+ * OpenLayers.Layer.Popup.AnchoredBubble
+
+Because the Rico library is now only used by deprecated components, the
+files have been removed from the debug loader in lib/OpenLayers.js;
+the files have now to be explicitly loaded in a script tag.
+
+## LayerSwitcher rounded corner removal
+
+The deprecated `roundedCorner` and `roundedCornerColor` options have
+been removed from the `OpenLayers.Control.LayerSwitcher` control. Use
+CSS3's border-radius instead.
diff --git a/misc/openlayers/readme.md b/misc/openlayers/readme.md
new file mode 100644
index 0000000..e8e5dcb
--- /dev/null
+++ b/misc/openlayers/readme.md
@@ -0,0 +1,79 @@
+# OpenLayers
+
+Copyright (c) 2005-2013 OpenLayers Contributors. See authors.txt for
+more details.
+
+OpenLayers is a JavaScript library for building map applications
+on the web. OpenLayers is made available under a BSD-license.
+Please see license.txt in this distribution for more details.
+
+## Getting OpenLayers
+
+OpenLayers lives at http://www.openlayers.org/. Find details on downloading stable releases or the development version the [development site](http://trac.osgeo.org/openlayers/wiki/HowToDownload).
+
+## Installing OpenLayers
+
+You can use OpenLayers as-is by copying build/OpenLayers.js and the
+entire theme/ and img/ directories up to your webserver and putting them
+in the same directory. The files can be in subdirectories on your website,
+or right in the root of the site, as in these examples.
+To include the OpenLayers library in your web page from the root of the site, use:
+
+ <script type="text/javascript" src="/OpenLayers.js" />
+
+As an example, using bash (with the release files in ~/openlayers):
+
+ $ cd /var/www/html
+ $ cp ~/openlayers/OpenLayers.js ./
+ $ cp -R ~/openlayers/theme ./
+ $ cp -R ~/openlayers/img ./
+
+If you want to use the multiple-file version of OpenLayers (for, say,
+debugging or development purposes), copy the lib/ directory up to your
+webserver in the same directory you put the img/ folder. Then add
+the following to your web page instead:
+
+ <script type="text/javascript" src="/lib/OpenLayers.js" />
+
+As an example, using bash (with the release files in ~/openlayers):
+
+ $ cd /var/www/html
+ $ cp -R ~/openlayers/lib ./
+ $ cp -R ~/openlayers/theme ./
+ $ cp -R ~/openlayers/img ./
+
+## Alternate OpenLayers Versions in this Release
+
+The following versions of OpenLayers single file builds are included in this release
+and can be used in place of OpenLayers.js in any of the above instructions:
+
+1. OpenLayers.js - full build --> Includes everything except the alternate language
+ translations and deprecated classes.
+2. OpenLayers.mobile.js - a mobile focused build --> Includes a subset of the OpenLayers
+ library to serve common mobile web app use cases. This build provides access to
+ OpenStreetMap, Bing, WMS, WFS and vector layers; touch optimized controls; geolocation;
+ vector editing and interaction tools. The examples tagged ``mobile`` can use this build.
+3. OpenLayers.light.js - a simple use case focused build --> Includes a subset of the
+ OpenLayers library to serve the basic use case of displaying points and polygons
+ on a map. This build provides access to OpenStreetMap, Bing, Google, WMS, and
+ vector layers; basic map controls; and vector interaction tools. The examples
+ tagged ``light`` can use this build.
+
+## Using OpenLayers in Your Own Website
+
+The [examples directory](http://openlayers.org/dev/examples/) is full of useful examples.
+
+Documentation is available at http://trac.osgeo.org/openlayers/wiki/Documentation.
+You can generate the API documentation with http://www.naturaldocs.org/
+As an example, using bash (with the release files in ~/openlayers):
+
+ $ cd ~/openlayers/
+ $ /path/to/NaturalDocs -i lib/ -o HTML doc/ -p doc_config/ -s Default OL
+
+Information on changes in the API is available in release notes found in the notes folder.
+
+## Contributing to OpenLayers
+
+Please join the email lists at http://openlayers.org/mailman/listinfo
+Patches are welcome!
+
diff --git a/misc/openlayers/tests/Animation.html b/misc/openlayers/tests/Animation.html
new file mode 100644
index 0000000..c24ae49
--- /dev/null
+++ b/misc/openlayers/tests/Animation.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Animation.js Tests</title>
+ <script>
+
+ // dependencies for tests
+ var OpenLayers = [
+ "OpenLayers/Util/vendorPrefix.js",
+ "OpenLayers/Animation.js"
+ ];
+
+ </script>
+ <script src="OLLoader.js"></script>
+
+ <script>
+
+ function test_all(t) {
+ t.plan(8);
+ t.ok(OpenLayers.Animation.isNative !== undefined, "isNative is set.");
+
+ function doIt(win) {
+ win.requestFrame(t);
+ win.start(t);
+ win.startDuration(t);
+ win.stop(t);
+ }
+
+ // Test in an extra window in Firefox, and directly in other browsers.
+ // This is needed because requestAnimationFrame does not work
+ // correctly in Firefox in a hidden IFrame.
+ if (window.mozRequestAnimationFrame) {
+ t.open_window("Animation.html", doIt);
+ } else {
+ doIt(window);
+ }
+ }
+
+ function requestFrame(t) {
+
+ t.eq(typeof OpenLayers.Animation.requestFrame, "function", "requestFrame is a function");
+
+ var calls = 0;
+ OpenLayers.Animation.requestFrame(function() {
+ ++calls;
+ });
+ t.delay_call(0.1, function() {
+ t.ok(calls > 0, "callback called: " + calls);
+ });
+ }
+
+ function start(t) {
+
+ var calls = 0;
+ var id = OpenLayers.Animation.start(function() {
+ ++calls;
+ });
+ t.delay_call(0.1, function() {
+ t.ok(calls > 1, "looped: " + calls);
+ OpenLayers.Animation.stop(id);
+ });
+ }
+
+ function startDuration(t) {
+
+ var calls = 0;
+ var id = OpenLayers.Animation.start(function() {
+ ++calls;
+ }, 100);
+ var first;
+ t.delay_call(0.2, function() {
+ first = calls;
+ t.ok(calls > 1, "looped: " + calls);
+ });
+ t.delay_call(0.3, function() {
+ t.eq(calls, first, "not being called any more");
+ });
+ }
+
+ function stop(t) {
+
+ var calls = 0;
+ var id = OpenLayers.Animation.start(function() {
+ ++calls;
+ });
+ var first;
+ t.delay_call(0.2, function() {
+ first = calls;
+ t.ok(calls > 1, "looped: " + calls);
+ OpenLayers.Animation.stop(id);
+ });
+ t.delay_call(0.3, function() {
+ t.eq(calls, first, "not being called any more");
+ });
+ }
+ </script> \ No newline at end of file
diff --git a/misc/openlayers/tests/BaseTypes.html b/misc/openlayers/tests/BaseTypes.html
new file mode 100644
index 0000000..38878dc
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes.html
@@ -0,0 +1,387 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_String_startsWith(t) {
+ t.plan(3);
+
+ var str = "chickenHead";
+
+ var test1 = "chicken";
+ var test2 = "beet";
+
+ t.ok(OpenLayers.String.startsWith(str, "chicken"),
+ "'chickenHead' starts with 'chicken'");
+ t.ok(!OpenLayers.String.startsWith(str, "Head"),
+ "'chickenHead' does not start with 'Head'");
+ t.ok(!OpenLayers.String.startsWith(str, "beet"),
+ "'chickenHead' doesnt start with 'beet'");
+ }
+
+ function test_String_contains(t) {
+ t.plan(4);
+
+ var str = "chickenHead";
+
+ t.ok(OpenLayers.String.contains(str, "chicken"),
+ "(beginning) 'chickenHead' contains with 'chicken'");
+ t.ok(OpenLayers.String.contains(str, "ick"),
+ "(middle) 'chickenHead' contains with 'ick'");
+ t.ok(OpenLayers.String.contains(str, "Head"),
+ "(end) 'chickenHead' contains with 'Head'");
+ t.ok(!OpenLayers.String.startsWith(str, "beet"),
+ "'chickenHead' doesnt start with 'beet'");
+ }
+
+ function test_String_trim(t) {
+ t.plan(6);
+
+ var str = "chickenHead";
+ t.eq(OpenLayers.String.trim(str),
+ "chickenHead", "string with no extra whitespace is left alone");
+
+ str = " chickenHead";
+ t.eq(OpenLayers.String.trim(str),
+ "chickenHead", "string with extra whitespace at beginning is trimmed correctly");
+
+ str = "chickenHead ";
+ t.eq(OpenLayers.String.trim(str),
+ "chickenHead", "string with extra whitespace at end is trimmed correctly");
+
+ str = " chickenHead ";
+ t.eq(OpenLayers.String.trim(str),
+ "chickenHead", "string with extra whitespace at beginning and end is trimmed correctly");
+
+ str = "chicken\nHead ";
+ t.eq(OpenLayers.String.trim(str),
+ "chicken\nHead", "multi-line string with extra whitespace at end is trimmed correctly");
+ str = " ";
+ t.eq(OpenLayers.String.trim(str), "", "whitespace string is trimmed correctly");
+ }
+
+ function test_String_camelize(t) {
+ t.plan(7);
+
+ var str = "chickenhead";
+ t.eq(OpenLayers.String.camelize(str), "chickenhead", "string with no hyphens is left alone");
+
+ str = "chicken-head";
+ t.eq(OpenLayers.String.camelize(str), "chickenHead", "string with one middle hyphen is camelized correctly");
+
+ str = "chicken-head-man";
+ t.eq(OpenLayers.String.camelize(str), "chickenHeadMan", "string with multiple middle hyphens is camelized correctly");
+
+ str = "-chickenhead";
+ t.eq(OpenLayers.String.camelize(str), "Chickenhead", "string with starting hyphen is camelized correctly (capitalized)");
+
+ str = "-chicken-head-man";
+ t.eq(OpenLayers.String.camelize(str), "ChickenHeadMan", "string with starting hypen and multiple middle hyphens is camelized correctly");
+
+ str = "chicken-";
+ t.eq(OpenLayers.String.camelize(str), "chicken", "string ending in hyphen is camelized correctly (hyphen dropped)");
+
+ str = "chicken-head-man-";
+ t.eq(OpenLayers.String.camelize(str), "chickenHeadMan", "string with multiple middle hyphens and end hyphen is camelized correctly (end hyphen dropped)");
+
+
+ }
+
+ function test_String_format(t) {
+ var unchanged = [
+ "", "${ ", "${", " ${", "${${", "${}", "${${}}", " ${ ${",
+ "}", "${${} }"
+ ]
+ t.plan(7 + unchanged.length);
+
+ var format = OpenLayers.String.format;
+
+ var expected;
+ for(var i=0; i<unchanged.length; ++i) {
+ expected = unchanged[i];
+ t.eq(format(expected), expected,
+ "'" + expected + "' left unchanged");
+ }
+
+ t.eq(format("${foo} none"),
+ "undefined none", "undefined properties don't bomb");
+
+ window.foo = "bar";
+ t.eq(format("${foo} none"),
+ "bar none", "window context used if none passed");
+
+ var context = {bar: "foo"};
+ t.eq(format("${bar} foo", context), "foo foo",
+ "properties accessed from context");
+
+ var context = {bar: "foo", foo: "bar"};
+ t.eq(format("a ${bar} is a ${foo}", context), "a foo is a bar",
+ "multiple properties replaced correctly");
+
+ // test context with properties that are functions
+ var context = {
+ bar: "church",
+ getDrunk: function() {
+ return arguments[0];
+ }
+ };
+ t.eq(
+ format("I go to the ${bar} to ${getDrunk}.", context, ["eat pretzels"]),
+ "I go to the church to eat pretzels.",
+ "function correctly called in context with arguments"
+ );
+
+ // test that things don't break
+ var context = {
+ meaning: function(truth) {
+ return truth;
+ }
+ };
+ t.eq(
+ format("In life, truth is ${meaning}.", context),
+ "In life, truth is undefined.",
+ "still works if arguments are not supplied"
+ );
+
+ // test contexts where attribute values can be objects
+ var context = {
+ a: {
+ b: {
+ c: 'd',
+ e: function() {
+ return 'f';
+ }
+ }
+ }
+ };
+ t.eq(
+ format("${a.b.c} ${a.b.e} ${a.b.q} ${a} ${a...b...c} ${aa.b} ${a.bb.c}", context),
+ "d f undefined [object Object] d undefined undefined",
+ "attribute values that are objects are supported"
+ );
+
+ }
+
+ function test_String_isNumeric(t) {
+ var cases = [
+ {value: "3", expect: true},
+ {value: "+3", expect: true},
+ {value: "-3", expect: true},
+ {value: "3.0", expect: true},
+ {value: "+3.0", expect: true},
+ {value: "-3.0", expect: true},
+ {value: "6.02e23", expect: true},
+ {value: "+1.0e-100", expect: true},
+ {value: "-1.0e+100", expect: true},
+ {value: "1E100", expect: true},
+ {value: null, expect: false},
+ {value: true, expect: false},
+ {value: false, expect: false},
+ {value: undefined, expect: false},
+ {value: "", expect: false},
+ {value: "3 ", expect: false},
+ {value: " 3", expect: false},
+ {value: "1e", expect: false},
+ {value: "1+e", expect: false},
+ {value: "1-e", expect: false}
+ ];
+ t.plan(cases.length);
+
+ var func = OpenLayers.String.isNumeric;
+ var obj, val, got, exp;
+ for(var i=0; i<cases.length; ++i) {
+ obj = cases[i];
+ val = obj.value;
+ exp = obj.expect;
+ got = func(val);
+ t.eq(got, exp, "'" + val + "' returns " + exp);
+ }
+
+ }
+
+ function test_Number_numericIf(t) {
+ var cases = [
+ {value: "3", expect: 3, expectWithTrim: 3},
+ {value: "+3", expect: 3, expectWithTrim: 3},
+ {value: "-3", expect: -3, expectWithTrim: -3},
+ {value: "3.0", expect: 3, expectWithTrim: 3},
+ {value: "+3.0", expect: 3, expectWithTrim: 3},
+ {value: "-3.0", expect: -3, expectWithTrim: -3},
+ {value: "6.02e23", expect: 6.02e23, expectWithTrim: 6.02e23},
+ {value: "+1.0e-100", expect: 1e-100, expectWithTrim: 1e-100},
+ {value: "-1.0e+100", expect: -1e100, expectWithTrim: -1e100},
+ {value: "1E100", expect: 1e100, expectWithTrim: 1e100},
+ {value: null, expect: null, expectWithTrim: null},
+ {value: true, expect: true, expectWithTrim: true},
+ {value: false, expect: false, expectWithTrim: false},
+ {value: undefined, expect: undefined, expectWithTrim: undefined},
+ {value: "", expect: "", expectWithTrim: ""},
+ {value: "3 ", expect: "3 ", expectWithTrim: 3},
+ {value: " 3", expect: " 3", expectWithTrim: 3},
+ {value: "1e", expect: "1e", expectWithTrim: "1e"},
+ {value: "1+e", expect: "1+e", expectWithTrim: "1+e"},
+ {value: "1-e", expect: "1-e", expectWithTrim: "1-e"},
+ {value: " 27 ", expect: " 27 ", expectWithTrim: 27},
+ {value: " abc ", expect: " abc ", expectWithTrim: " abc "}
+ ];
+ t.plan(cases.length*2);
+
+ var func = OpenLayers.String.numericIf;
+ var obj, val, got, exp;
+ for(var i=0; i<cases.length; ++i) {
+ obj = cases[i];
+ val = obj.value;
+ exp = obj.expect;
+ got = func(val);
+ t.eq(got, exp, "'" + val + "' returns " + exp);
+ got = func(val, true);
+ exp = obj.expectWithTrim;
+ t.eq(got, exp, "'" + val + "' returns " + exp + " with trimWhitespace true");
+ }
+ }
+
+
+ function test_Number_limitSigDigs(t) {
+ t.plan(9);
+
+ var num = 123456789;
+ t.eq(OpenLayers.Number.limitSigDigs(num), 0, "passing 'null' as sig returns 0");
+ t.eq(OpenLayers.Number.limitSigDigs(num, -1), 0, "passing -1 as sig returns 0");
+ t.eq(OpenLayers.Number.limitSigDigs(num, 0), 0, "passing 0 as sig returns 0");
+
+ t.eq(OpenLayers.Number.limitSigDigs(num, 15), 123456789, "passing sig greater than num digits in number returns number unmodified");
+
+ t.eq(OpenLayers.Number.limitSigDigs(num, 1), 100000000, "passing sig 1 works");
+ t.eq(OpenLayers.Number.limitSigDigs(num, 3), 123000000, "passing middle sig works (rounds down)");
+ t.eq(OpenLayers.Number.limitSigDigs(num, 5), 123460000, "passing middle sig works (rounds up)");
+ t.eq(OpenLayers.Number.limitSigDigs(num, 9), 123456789, "passing sig equal to num digits in number works");
+
+ num = 1234.56789;
+ t.eq(OpenLayers.Number.limitSigDigs(num, 5), 1234.6, "running limSigDig() on a floating point number works fine");
+
+ }
+
+ function test_Number_format(t) {
+ t.plan(9);
+ var format = OpenLayers.Number.format;
+ t.eq(format(12345), "12,345", "formatting an integer number works");
+ t.eq(format(12345, 3), "12,345.000", "zero padding an integer works");
+ t.eq(format(12345, null, ","), "12,345", "adding thousands separator to an integer works");
+ t.eq(format(12345, 0, ","), "12,345", "adding thousands separator to an integer with defined 0 decimal places works");
+
+ var num = 12345.6789
+ t.eq(format(num, null, "", ","), "12345,6789", "only changing decimal separator and leaving everything else untouched works");
+ t.eq(format(num, 5), "12,345.67890", "filling up decimals with trailing zeroes works");
+ t.eq(format(num, 3, ".", ","), "12.345,679", "rounding and changing decimal/thousands separator in function call works");
+ t.eq(format(num, 0, ""), "12346", "empty thousands separator in function call works");
+ OpenLayers.Number.thousandsSeparator = ".";
+ OpenLayers.Number.decimalSeparator = ",";
+ t.eq(format(num, 3), "12.345,679", "changing thousands/decimal separator globally works");
+ }
+
+ function test_Number_zeroPad(t) {
+ t.plan(6);
+ var pad = OpenLayers.Number.zeroPad;
+ t.eq(pad(15, 4), "0015", "left padding works");
+ t.eq(pad(15, 2), "15", "no left padding when equal to number of digits");
+ t.eq(pad(15, 1), "15", "no left padding when less than number of digits");
+ t.eq(pad(10, 5, 2), "01010", "radix modified and padding works");
+ t.eq(pad(10, 5, 8), "00012", "radix modified and padding works");
+ t.eq(pad(10, 5, 36), "0000a", "radix modified and padding works");
+ }
+
+ function test_Function_bind(t) {
+ t.plan(12);
+
+ g_obj = {};
+ g_Arg1 = {};
+ g_Arg2 = {};
+ g_Arg3 = {};
+ g_Arg4 = {};
+ var foo = function(x,y,z,a) {
+ t.ok(this == g_obj, "context correctly set");
+ t.ok(x == g_Arg1, "arg1 passed correctly");
+ t.ok(y == g_Arg2, "arg2 passed correctly");
+ t.ok(z == g_Arg3, "arg3 passed correctly");
+ t.ok(a == g_Arg4, "arg4 passed correctly");
+ t.eq(arguments.length, 4, "correct number of arguments ((regression test for #876))");
+ };
+
+ var newFoo = OpenLayers.Function.bind(foo, g_obj, g_Arg1, g_Arg2);
+
+ newFoo(g_Arg3, g_Arg4);
+
+ //run again to make sure the arguments are handled correctly
+ newFoo(g_Arg3, g_Arg4);
+ }
+
+ function test_Function_Void(t) {
+
+ t.plan(1);
+ t.eq(OpenLayers.Function.Void(), undefined, "returns undefined");
+
+ }
+
+ function test_Function_bindAsEventListener(t) {
+ t.plan(4);
+
+ g_obj = {};
+ g_Event = {};
+ g_WindowEvent = {};
+
+ var foo = function(x) {
+ t.ok(this == g_obj, "context correctly set");
+ g_X = x;
+ };
+
+ var newFoo = OpenLayers.Function.bindAsEventListener(foo, g_obj);
+
+
+ g_X = null;
+ newFoo(g_Event);
+ t.ok(g_X == g_Event, "event properly passed as first argument when event specified");
+
+ g_X = null;
+ newFoo();
+ t.ok(g_X == window.event, "window.event properly passed as first argument when nothing specified");
+ }
+
+ function test_Array_filter(t) {
+
+ t.plan(8);
+
+ OpenLayers.Array.filter(["foo"], function(item, index, array) {
+ t.eq(item, "foo", "callback called with proper item");
+ t.eq(index, 0, "callback called with proper index");
+ t.eq(array, ["foo"], "callback called with proper array");
+ t.eq(this, {"foo": "bar"}, "callback called with this set properly");
+ }, {"foo": "bar"});
+
+ var array = [0, 1, 2, 3];
+ var select = OpenLayers.Array.filter(array, function(value) {
+ return value > 1;
+ });
+ t.eq(select, [2, 3], "filter works for basic callback");
+ t.eq(array, [0, 1, 2, 3], "filter doesn't modify original");
+
+ var obj = {
+ test: function(value) {
+ if(value > 1) {
+ return true;
+ }
+ }
+ };
+ var select = OpenLayers.Array.filter(array, function(value) {
+ return this.test(value);
+ }, obj);
+ t.eq(select, [2, 3], "filter works for callback and caller");
+ t.eq(array, [0, 1, 2, 3], "filter doesn't modify original");
+
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/BaseTypes/Bounds.html b/misc/openlayers/tests/BaseTypes/Bounds.html
new file mode 100644
index 0000000..bdfeaf2
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/Bounds.html
@@ -0,0 +1,738 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var bounds;
+ function test_Bounds_constructor (t) {
+ t.plan( 26 );
+
+ bounds = new OpenLayers.Bounds();
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.CLASS_NAME, "OpenLayers.Bounds", "bounds.CLASS_NAME is set correctly" );
+ t.eq( bounds.left, null, "bounds.left is initialized to null" );
+ t.eq( bounds.bottom, null, "bounds.bottom is initialized to null" );
+ t.eq( bounds.right, null, "bounds.right is initialized to null" );
+ t.eq( bounds.top, null, "bounds.top is initialized to null" );
+
+
+ bounds = new OpenLayers.Bounds(0,2,10,4);
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.CLASS_NAME, "OpenLayers.Bounds", "bounds.CLASS_NAME is set correctly" );
+ t.eq( bounds.left, 0, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, 2, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, 10, "bounds.right is set correctly" );
+ t.eq( bounds.top, 4, "bounds.top is set correctly" );
+ t.eq( bounds.getWidth(), 10, "bounds.getWidth() returns correct value" );
+ t.eq( bounds.getHeight(), 2, "bounds.getHeight() returns correct value" );
+
+ var sz = bounds.getSize();
+ var size = new OpenLayers.Size(10,2);
+ t.ok(sz.equals(size),"bounds.getSize() has correct value" );
+
+ var center = new OpenLayers.Pixel(5,3);
+ var boundsCenter = bounds.getCenterPixel();
+ t.ok( boundsCenter.equals(center), "bounds.getCenterLonLat() has correct value" );
+
+ var center = new OpenLayers.LonLat(5,3);
+ var boundsCenter = bounds.getCenterLonLat();
+ t.ok( boundsCenter.equals(center), "bounds.getCenterLonLat() has correct value" );
+
+ // This is an actual use case with Mercator projection at global scale
+ bounds = new OpenLayers.Bounds(-40075016.67999999,-20037508.339999992,
+ 40075016.67999999,20037508.339999992);
+ t.eq( bounds.left, -40075016.68, "bounds.left adjusted for floating precision");
+ t.eq( bounds.bottom, -20037508.34, "bounds.bottom adjusted for floating precision");
+ t.eq( bounds.right, 40075016.68, "bounds.right adjusted for floating precision");
+ t.eq( bounds.top, 20037508.34, "bounds.top adjusted for floating precision");
+
+ // allow construction from a single arg
+ bounds = new OpenLayers.Bounds([-180, -90, 180, 90]);
+ t.ok(bounds instanceof OpenLayers.Bounds, "(array) correct instance");
+ t.eq(bounds.left, -180, "(array) left");
+ t.eq(bounds.bottom, -90, "(array) bottom");
+ t.eq(bounds.right, 180, "(array) right");
+ t.eq(bounds.top, 90, "(array) top");
+
+ }
+
+ function test_Bounds_constructorFromStrings(t) {
+ t.plan( 6 );
+ bounds = new OpenLayers.Bounds("0","2","10","4");
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.CLASS_NAME, "OpenLayers.Bounds", "bounds.CLASS_NAME is set correctly" );
+ t.eq( bounds.left, 0, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, 2, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, 10, "bounds.right is set correctly" );
+ t.eq( bounds.top, 4, "bounds.top is set correctly" );
+
+ }
+
+ function test_Bounds_toBBOX(t) {
+ t.plan( 5 );
+ bounds = new OpenLayers.Bounds(1,2,3,4);
+ t.eq( bounds.toBBOX(), "1,2,3,4", "toBBOX() returns correct value." );
+ bounds = new OpenLayers.Bounds(1.00000001,2,3,4);
+ t.eq( bounds.toBBOX(), "1,2,3,4", "toBBOX() rounds off small differences." );
+ bounds = new OpenLayers.Bounds(1.00000001,2.5,3,4);
+ t.eq( bounds.toBBOX(), "1,2.5,3,4", "toBBOX() returns correct value. for a half number" );
+ bounds = new OpenLayers.Bounds(1,2.5555555,3,4);
+ t.eq( bounds.toBBOX(), "1,2.555556,3,4", "toBBOX() rounds to correct value." );
+ bounds = new OpenLayers.Bounds(1,2.5555555,3,4);
+ t.eq( bounds.toBBOX(1), "1,2.6,3,4", "toBBOX() rounds to correct value with power provided." );
+ bounds = new OpenLayers.Bounds(1,2.5555555,3,4);
+ }
+
+ function test_Bounds_toString(t) {
+ t.plan( 1 );
+ bounds = new OpenLayers.Bounds(1,2,3,4);
+ t.eq( bounds.toString(), "1,2,3,4", "toString() returns correct value." );
+ }
+ function test_Bounds_toArray(t) {
+ t.plan( 1 );
+ bounds = new OpenLayers.Bounds(1,2,3,4);
+ t.eq( bounds.toArray(), [1,2,3,4], "toArray() returns correct value." );
+ }
+
+ function test_Bounds_toGeometry(t) {
+ t.plan(7);
+ var minx = Math.random();
+ var miny = Math.random();
+ var maxx = Math.random();
+ var maxy = Math.random();
+ var bounds = new OpenLayers.Bounds(minx, miny, maxx, maxy);
+ var poly = bounds.toGeometry();
+ t.eq(poly.CLASS_NAME, "OpenLayers.Geometry.Polygon",
+ "polygon instance created");
+ t.eq(poly.components.length, 1,
+ "polygon with one ring created");
+ var ring = poly.components[0];
+ t.eq(ring.components.length, 5,
+ "four sided polygon created");
+ t.eq(ring.components[0].x, OpenLayers.Util.toFloat(minx),
+ "bounds left preserved");
+ t.eq(ring.components[0].y, OpenLayers.Util.toFloat(miny),
+ "bounds bottom preserved");
+ t.eq(ring.components[2].x, OpenLayers.Util.toFloat(maxx),
+ "bounds left preserved");
+ t.eq(ring.components[2].y, OpenLayers.Util.toFloat(maxy),
+ "bounds bottom preserved");
+ }
+
+ function test_Bounds_contains(t) {
+ t.plan( 6 );
+ bounds = new OpenLayers.Bounds(10,10,40,40);
+ t.eq( bounds.contains(20,20), true, "bounds(10,10,40,40) correctly contains LonLat(20,20)" );
+ t.eq( bounds.contains(0,0), false, "bounds(10,10,40,40) correctly does not contain LonLat(0,0)" );
+ t.eq( bounds.contains(40,40), true, "bounds(10,10,40,40) correctly contains LonLat(40,40) with inclusive set to true" );
+ t.eq( bounds.contains(40,40, false), false, "bounds(10,10,40,40) correctly does not contain LonLat(40,40) with inclusive set to false" );
+
+ var px = new OpenLayers.Pixel(15,30);
+ t.eq( bounds.containsPixel(px), bounds.contains(px.x, px.y), "containsPixel works");
+
+ var ll = new OpenLayers.LonLat(15,30);
+ t.eq( bounds.containsLonLat(ll), bounds.contains(ll.lon, ll.lat), "containsLonLat works");
+
+ }
+
+ function test_containsLonLat_wraped(t) {
+
+ var worldBounds = new OpenLayers.Bounds(-180, -90, 180, 90);
+
+ var cases = [{
+ ll: [0, 0], bbox: [-10, -10, 10, 10], contained: true
+ }, {
+ ll: [20, 0], bbox: [-10, -10, 10, 10], contained: false
+ }, {
+ ll: [360, 0], bbox: [-10, -10, 10, 10], contained: true
+ }, {
+ ll: [380, 0], bbox: [-10, -10, 10, 10], contained: false
+ }, {
+ ll: [725, 5], bbox: [-10, -10, 10, 10], contained: true
+ }, {
+ ll: [-355, -5], bbox: [-10, -10, 10, 10], contained: true
+ }, {
+ ll: [-715, 5], bbox: [-10, -10, 10, 10], contained: true
+ }, {
+ ll: [-735, 5], bbox: [-10, -10, 10, 10], contained: false
+ }, {
+ ll: [-180 * 50, 5], bbox: [-10, -10, 10, 10], contained: true
+ }];
+
+ var len = cases.length;
+ t.plan(len);
+
+ var c, bounds, loc;
+ for (var i=0; i<len; ++i) {
+ c = cases[i];
+ loc = new OpenLayers.LonLat(c.ll[0], c.ll[1]);
+ bounds = new OpenLayers.Bounds.fromArray(c.bbox);
+ t.eq(bounds.containsLonLat(loc, {worldBounds: worldBounds}), c.contained, "case " + i);
+ }
+
+ }
+
+ function test_Bounds_fromString(t) {
+ t.plan( 12 );
+ bounds = OpenLayers.Bounds.fromString("1,2,3,4");
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.left, 1, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, 2, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, 3, "bounds.right is set correctly" );
+ t.eq( bounds.top, 4, "bounds.top is set correctly" );
+
+ // reverse axis order
+ var reverseBbox = bounds.toBBOX(null, true);
+ t.eq(reverseBbox, "2,1,4,3", "toBBOX with reverseAxisOrder set to true works as expected");
+ var boundsFromReverse = OpenLayers.Bounds.fromString(reverseBbox, true);
+ t.ok(bounds.equals(boundsFromReverse), "Bounds created from string with reverseAxisOrder are correct");
+
+ bounds = OpenLayers.Bounds.fromString("1.1,2.2,3.3,4.4");
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.left, 1.1, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, 2.2, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, 3.3, "bounds.right is set correctly" );
+ t.eq( bounds.top, 4.4, "bounds.top is set correctly" );
+ }
+
+ function test_Bounds_getSize(t) {
+ t.plan( 1 );
+ var bounds = new OpenLayers.Bounds(0,10,100,120);
+
+ t.ok( bounds.getSize().equals(new OpenLayers.Size(100, 110)), "getCenterPixel() works correctly");
+ }
+
+ function test_Bounds_clone(t) {
+ t.plan( 6 );
+ var oldBounds = new OpenLayers.Bounds(1,2,3,4);
+ var bounds = oldBounds.clone();
+ t.ok( bounds instanceof OpenLayers.Bounds, "clone returns new OpenLayers.Bounds object" );
+ t.eq( bounds.left, 1, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, 2, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, 3, "bounds.right is set correctly" );
+ t.eq( bounds.top, 4, "bounds.top is set correctly" );
+
+ oldBounds.left = 100;
+ t.eq( bounds.left, 1, "changing olBounds.left does not change bounds.left" );
+ }
+
+ function test_Bounds_intersectsBounds(t) {
+ t.plan(21);
+
+ var aBounds = new OpenLayers.Bounds(-180, -90, 180, 90);
+
+ //inside
+ var bBounds = new OpenLayers.Bounds(-20, -10, 20, 10);
+ var cBounds = new OpenLayers.Bounds(-181,-90,180,90);
+ t.eq( aBounds.intersectsBounds(bBounds), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + ")" );
+ t.eq( aBounds.intersectsBounds(bBounds, true), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is true" );
+ t.eq( aBounds.intersectsBounds(bBounds, false), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is false" );
+ t.eq( aBounds.intersectsBounds(cBounds, false), true, "aBounds with cBounds adjusted one degree left passes intersect bounds. (3 sides match, 1 side different)." );
+ t.eq( cBounds.intersectsBounds(aBounds, false), true, "cBounds with aBounds adjusted one degree left passes intersect bounds. (3 sides match, 1 side different)." );
+
+ //outside
+ bBounds = new OpenLayers.Bounds(-181, -91, 181, 91);
+ t.eq( aBounds.intersectsBounds(bBounds), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + ")" );
+ t.eq( aBounds.intersectsBounds(bBounds, true), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is true" );
+ t.eq( aBounds.intersectsBounds(bBounds, false), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is false" );
+
+ //total intersect
+ bBounds = new OpenLayers.Bounds(-185, -100, 20, 50);
+ t.eq( aBounds.intersectsBounds(bBounds), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + ")" );
+ t.eq( aBounds.intersectsBounds(bBounds, true), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is true" );
+ t.eq( aBounds.intersectsBounds(bBounds, false), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is false" );
+
+ //border intersect
+ bBounds = new OpenLayers.Bounds(-360, -180, -180, -90);
+ t.eq( aBounds.intersectsBounds(bBounds), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + ")" );
+ t.eq( aBounds.intersectsBounds(bBounds, true), true, "(" + aBounds.toBBOX() + ") correctly intersects (" + bBounds.toBBOX() + "), inclusive is true" );
+ t.eq( aBounds.intersectsBounds(bBounds, false), false, "(" + aBounds.toBBOX() + ") does not intersect (" + bBounds.toBBOX() + "), inclusive is false" );
+
+ //no intersect
+ bBounds = new OpenLayers.Bounds(-360, -180, -185, -95);
+ t.eq( aBounds.intersectsBounds(bBounds), false, "(" + aBounds.toBBOX() + ") does not intersect (" + bBounds.toBBOX() + ")" );
+ t.eq( aBounds.intersectsBounds(bBounds, true), false, "(" + aBounds.toBBOX() + ") does not intersect (" + bBounds.toBBOX() + "), inclusive is true" );
+ t.eq( aBounds.intersectsBounds(bBounds, false), false, "(" + aBounds.toBBOX() + ") does not intersect (" + bBounds.toBBOX() + "), inclusive is false" );
+
+ // This is an actual use case with Mercator tiles at global scale
+ var merc_aBounds = new OpenLayers.Bounds(-40075016.67999999,20037508.339999992,
+ -20037508.339999992,40075016.67999999),
+ merc_bBounds = new OpenLayers.Bounds(-20037508.34,-20037508.34,
+ 20037508.34,20037508.34);
+ t.eq( merc_aBounds.intersectsBounds(merc_bBounds, true), true, "intersect shouldn't fall prey to floating point errors, inclusive is true");
+ t.eq( merc_aBounds.intersectsBounds(merc_bBounds, false), false, "intersect shouldn't fall prey to floating point errors, inclusive is false");
+
+ // test for bounds intersection where none of the corners are contained within the other bounds
+ var b1 = new OpenLayers.Bounds(-1, -2, 1, 2);
+ var b2 = new OpenLayers.Bounds(-2, -1, 2, 1);
+ t.eq(b1.intersectsBounds(b2), true, "vertical rectangle intersects horizontal rectangle");
+ t.eq(b2.intersectsBounds(b1), true, "horizontal rectangle intersects vertical rectangle");
+
+ }
+
+ function test_Bounds_containsBounds(t) {
+ t.plan( 35 );
+ containerBounds = new OpenLayers.Bounds(10,10,40,40);
+
+ //totally outside
+ bounds = new OpenLayers.Bounds(0,0,5,5);
+ t.eq( containerBounds.containsBounds(bounds) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ")");
+ t.eq( containerBounds.containsBounds(bounds, false) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false" );
+ t.eq( containerBounds.containsBounds(bounds, false, true) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, false, false), false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is false" );
+ t.eq( containerBounds.containsBounds(bounds, true) , false , "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, true) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is true, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, false) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is true, inclusive is false" );
+
+ //totally outside on border
+ bounds = new OpenLayers.Bounds(15,0,30,10);
+ t.eq( containerBounds.containsBounds(bounds) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ")");
+ t.eq( containerBounds.containsBounds(bounds, false) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false" );
+ t.eq( containerBounds.containsBounds(bounds, false, true) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, false, false), false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is false" );
+ t.eq( containerBounds.containsBounds(bounds, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, false) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is true, inclusive is false" );
+
+ //partially inside
+ bounds = new OpenLayers.Bounds(20,20,50,30);
+ t.eq( containerBounds.containsBounds(bounds) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ")");
+ t.eq( containerBounds.containsBounds(bounds, false) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false" );
+ t.eq( containerBounds.containsBounds(bounds, false, true) , false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, false, false), false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is false" );
+ t.eq( containerBounds.containsBounds(bounds, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, false) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is false" );
+
+ //totally inside on border
+ bounds = new OpenLayers.Bounds(10,20,30,30);
+ t.eq( containerBounds.containsBounds(bounds) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ")");
+ t.eq( containerBounds.containsBounds(bounds, false) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is false" );
+ t.eq( containerBounds.containsBounds(bounds, false, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is false, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, false, false), false, "(" + containerBounds.toBBOX() + ") correctly does not contain (" + bounds.toBBOX() + ") when partial is false, inclusive is false" );
+ t.eq( containerBounds.containsBounds(bounds, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, false) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is false" );
+
+ //totally inside
+ bounds = new OpenLayers.Bounds(20,20,30,30);
+ t.eq( containerBounds.containsBounds(bounds) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ")");
+ t.eq( containerBounds.containsBounds(bounds, false) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is false" );
+ t.eq( containerBounds.containsBounds(bounds, false, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is false, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, false, false), true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is false, inclusive is false" );
+ t.eq( containerBounds.containsBounds(bounds, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, true) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is true" );
+ t.eq( containerBounds.containsBounds(bounds, true, false) , true, "(" + containerBounds.toBBOX() + ") correctly contains (" + bounds.toBBOX() + ") when partial is true, inclusive is false" );
+
+ }
+
+ function test_Bounds_determineQuadrant(t) {
+
+ t.plan( 4 );
+ var bounds = new OpenLayers.Bounds(0,0,100,100);
+
+ var tl = new OpenLayers.LonLat(25, 75);
+ var tr = new OpenLayers.LonLat(75, 75);
+ var bl = new OpenLayers.LonLat(25, 25);
+ var br = new OpenLayers.LonLat(75, 25);
+
+ t.eq( bounds.determineQuadrant(tl), "tl", "bounds.determineQuadrant correctly identifies a coordinate in the top left quadrant");
+ t.eq( bounds.determineQuadrant(tr), "tr", "bounds.determineQuadrant correctly identifies a coordinate in the top right quadrant");
+ t.eq( bounds.determineQuadrant(bl), "bl", "bounds.determineQuadrant correctly identifies a coordinate in the bottom left quadrant");
+ t.eq( bounds.determineQuadrant(br), "br", "bounds.determineQuadrant correctly identifies a coordinate in the bottom right quadrant");
+ }
+
+ function test_Bounds_oppositeQuadrant(t) {
+
+ t.plan( 4 );
+
+ t.eq( OpenLayers.Bounds.oppositeQuadrant("tl"), "br", "OpenLayers.Bounds.oppositeQuadrant returns 'br' for 'tl'");
+ t.eq( OpenLayers.Bounds.oppositeQuadrant("tr"), "bl", "OpenLayers.Bounds.oppositeQuadrant returns 'bl' for 'tr'");
+ t.eq( OpenLayers.Bounds.oppositeQuadrant("bl"), "tr", "OpenLayers.Bounds.oppositeQuadrant returns 'tr' for 'bl'");
+ t.eq( OpenLayers.Bounds.oppositeQuadrant("br"), "tl", "OpenLayers.Bounds.oppositeQuadrant returns 'tl' for 'br'");
+ }
+
+ function test_Bounds_equals(t) {
+ t.plan( 3 );
+ var boundsA = new OpenLayers.Bounds(1,2,3,4);
+ var boundsB = new OpenLayers.Bounds(1,2,3,4);
+ var boundsC = new OpenLayers.Bounds(1,5,3,4);
+
+ t.ok( boundsA.equals(boundsB), "equals() returns true on two equal bounds." );
+ t.ok( !boundsA.equals(boundsC), "equals() returns false on two different bounds." );
+ t.ok( !boundsA.equals(null), "equals() returns false on comparison to null");
+ }
+
+ function test_Bounds_getHeight_getWidth(t) {
+ t.plan( 2 );
+ var bounds = new OpenLayers.Bounds(10,20,100,120);
+
+ t.eq( bounds.getWidth(), 90, "getWidth() works" );
+ t.eq( bounds.getHeight(), 100, "getHeight() works" );
+
+ }
+
+ function test_Bounds_getCenters(t) {
+ t.plan( 2 );
+ var bounds = new OpenLayers.Bounds(0,20,100,120);
+
+ t.ok( bounds.getCenterPixel().equals(new OpenLayers.Pixel(50, 70)), "getCenterPixel() works correctly");
+ t.ok( bounds.getCenterLonLat().equals(new OpenLayers.LonLat(50, 70)), "getCenterLonLat() works correctly");
+ }
+
+ function test_getCenterLonLat(t) {
+ t.plan(7);
+ var bounds = new OpenLayers.Bounds(0, 10, 20, 60);
+
+ // set private centerLonLat to confirm that it is getting returned if set
+ bounds.centerLonLat = "foo";
+ t.eq(bounds.getCenterLonLat(), "foo", "returns cached value");
+ bounds.centerLonLat = null;
+
+ // unmodified
+ var center = bounds.getCenterLonLat();
+ t.eq(center.lon, 10, "unmodified: correct x");
+ t.eq(center.lat, 35, "unmodified: correct y");
+
+ // transformed
+ bounds.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
+ center = bounds.getCenterLonLat();
+ t.eq(Math.round(center.lon), 1113195, "transformed: correct x");
+ t.eq(Math.round(center.lat), 4759314, "transformed: correct y");
+
+ // extended
+ bounds.extend(new OpenLayers.Bounds(-10000000, -10000000, 10000000, 10000000));
+ center = bounds.getCenterLonLat();
+ t.eq(center.lon, 0, "extended: correct x");
+ t.eq(center.lat, 0, "extended: correct y");
+
+
+ }
+
+ function test_Bounds_fromArray(t) {
+ t.plan( 7 );
+
+ var bbox = [1,2,3,4];
+ bounds = OpenLayers.Bounds.fromArray(bbox);
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.left, 1, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, 2, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, 3, "bounds.right is set correctly" );
+ t.eq( bounds.top, 4, "bounds.top is set correctly" );
+
+ // reverse axis order
+ var reverseBbox = bounds.toArray(true);
+ t.eq(reverseBbox, [2,1,4,3], "toArray with reverseAxisOrder set to true works as expected");
+ var boundsFromReverse = OpenLayers.Bounds.fromArray(reverseBbox, true);
+ t.ok(bounds.equals(boundsFromReverse), "Bounds created from array with reverseAxisOrder are correct");
+ }
+
+ function test_Bounds_fromSize(t) {
+ t.plan( 5 );
+
+ var height = 15;
+ var width = 16;
+ var size = new OpenLayers.Size(width, height);
+ bounds = OpenLayers.Bounds.fromSize(size);
+ t.ok( bounds instanceof OpenLayers.Bounds, "new OpenLayers.Bounds returns Bounds object" );
+ t.eq( bounds.left, 0, "bounds.left is set correctly" );
+ t.eq( bounds.bottom, height, "bounds.bottom is set correctly" );
+ t.eq( bounds.right, width, "bounds.right is set correctly" );
+ t.eq( bounds.top, 0, "bounds.top is set correctly" );
+ }
+
+
+ function test_Bounds_extend(t) {
+ t.plan( 9 );
+
+ // null bounds to start
+ var originalBounds = new OpenLayers.Bounds();
+ var bounds = originalBounds.clone();
+
+ bounds.extend(new OpenLayers.LonLat(4,5));
+ t.ok(bounds.equals(new OpenLayers.Bounds(4,5,4,5)), "uninitialized bounds can be safely extended");
+
+ // extend with null obj
+ originalBounds = new OpenLayers.Bounds(10,20,50,80);
+ bounds = originalBounds.clone();
+
+ bounds.extend(null);
+ t.ok(bounds.equals(originalBounds), "null to extend does not crash or change original bounds");
+
+ // obj with no classname
+ var object = {};
+ bounds.extend(object);
+ t.ok(bounds.equals(originalBounds), "extend() passing object with no classname does not crash or change original bounds")
+
+
+ // obj is bounds
+
+ // pushing all limits with bounds obj
+ var testBounds = new OpenLayers.Bounds(5, 10, 60, 90);
+ object = testBounds.clone();
+
+ bounds.extend(object);
+ t.ok(bounds.equals(testBounds), "extend by valid bounds, pushing all limits, correctly extends bounds");
+
+ // pushing no limits with bounds obj
+ bounds = originalBounds.clone();
+
+ testBounds = new OpenLayers.Bounds(15, 30, 40, 70);
+ object = testBounds.clone();
+
+ bounds.extend(object);
+ t.ok(bounds.equals(originalBounds), "extend by valid bounds, pushing no limits, correctly does not extend bounds");
+
+
+ // obj is lonlat
+
+ // left, bottom
+ bounds = originalBounds.clone();
+
+ object = new OpenLayers.LonLat(5, 10);
+
+ bounds.extend(object);
+
+ t.ok( ((bounds.left == object.lon) &&
+ (bounds.bottom == object.lat) &&
+ (bounds.right == originalBounds.right) &&
+ (bounds.top == originalBounds.top)), "obj lonlat to extends correctly modifies left and bottom");
+
+ // right, top
+ bounds = originalBounds.clone();
+
+ object = new OpenLayers.LonLat(60,90);
+
+ bounds.extend(object);
+
+ t.ok( ((bounds.left == originalBounds.left) &&
+ (bounds.bottom == originalBounds.bottom) &&
+ (bounds.right == object.lon) &&
+ (bounds.top == object.lat)), "obj lonlat to extends correctly modifies right and top");
+
+
+ // obj is point
+
+ // left, bottom
+ bounds = originalBounds.clone();
+
+ object = new OpenLayers.Geometry.Point(5, 10);
+
+ bounds.extend(object);
+
+ t.ok( ((bounds.left == object.x) &&
+ (bounds.bottom == object.y) &&
+ (bounds.right == originalBounds.right) &&
+ (bounds.top == originalBounds.top)), "obj Point to extends correctly modifies left and bottom");
+
+ // right, top
+ bounds = originalBounds.clone();
+
+ object = new OpenLayers.Geometry.Point(60,90);
+
+ bounds.extend(object);
+
+ t.ok( ((bounds.left == originalBounds.left) &&
+ (bounds.bottom == originalBounds.bottom) &&
+ (bounds.right == object.x) &&
+ (bounds.top == object.y)), "obj Point to extends correctly modifies right and top");
+
+ }
+
+
+ function test_Bounds_extendXY(t) {
+ t.plan(3);
+
+ // null bounds to start
+ var originalBounds = new OpenLayers.Bounds();
+
+ var bounds = originalBounds.clone();
+ bounds.extendXY(4, 5);
+
+ t.ok(bounds.equals(new OpenLayers.Bounds(4,5,4,5)), "uninitialized bounds can be safely extended");
+
+ // left, bottom
+ originalBounds = new OpenLayers.Bounds(10,20,50,80);
+
+ bounds = originalBounds.clone();
+ bounds.extendXY(5, 10);
+
+ t.ok( ((bounds.left == 5) &&
+ (bounds.bottom == 10) &&
+ (bounds.right == originalBounds.right) &&
+ (bounds.top == originalBounds.top)), "extendXY correctly modifies left and bottom");
+
+ // right, top
+ bounds = originalBounds.clone();
+ bounds.extendXY(60, 90);
+
+ t.ok( ((bounds.left == originalBounds.left) &&
+ (bounds.bottom == originalBounds.bottom) &&
+ (bounds.right == 60) &&
+ (bounds.top == 90)), "extendXY correctly modifies right and top");
+ }
+
+
+ function test_Bounds_wrapDateLine(t) {
+ t.plan( 13 );
+
+ var testBounds, wrappedBounds, desiredBounds;
+
+ var maxExtent = new OpenLayers.Bounds(-10,-10,10,10);
+ var exactBounds = maxExtent.clone();
+ var simpleBounds = new OpenLayers.Bounds( -5,-5,5,5);
+
+
+
+ //bad maxextent
+ testBounds = simpleBounds.clone();
+ wrappedBounds = testBounds.wrapDateLine(null);
+ t.ok(wrappedBounds.equals(simpleBounds), "wrapping a bounds with a bad maxextent does nothing");
+
+
+
+ //exactly inside
+ testBounds = exactBounds.clone();
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(exactBounds), "wrapping a bounds precisely within (equal to) maxextent does nothing");
+
+
+ //inside
+ testBounds = simpleBounds.clone();
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(simpleBounds), "wrapping a bounds within maxextent does nothing");
+
+// LEFT //
+
+ //straddling left
+ testBounds = simpleBounds.add(-10,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(testBounds), "wrapping a bounds that straddles the left of maxextent does nothing");
+
+ //left rightTolerance
+ testBounds = simpleBounds.add(-14,0);
+ wrappedBounds =
+ testBounds.wrapDateLine(maxExtent, {'rightTolerance': 1} );
+ desiredBounds = simpleBounds.add(6,0);
+ t.ok(wrappedBounds.equals(desiredBounds), "wrapping a bounds rightTolerance left of maxextent works");
+
+ //exactly left
+ testBounds = exactBounds.add(-20,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(exactBounds), "wrapping an exact bounds once left of maxextent works");
+
+ //left
+ testBounds = simpleBounds.add(-20,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(simpleBounds), "wrapping a bounds once left of maxextent works");
+
+ //way left
+ testBounds = simpleBounds.add(-200,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(simpleBounds), "wrapping a bounds way left of maxextent works");
+
+// RIGHT //
+
+ //straddling right
+ testBounds = simpleBounds.add(10,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ desiredBounds = testBounds.add(-maxExtent.getWidth(), 0)
+ t.ok(wrappedBounds.equals(desiredBounds), "wrapping a bounds that straddles the right of maxextent moves extent to left side of the world");
+
+ //right leftTolerance
+ testBounds = simpleBounds.add(14,0);
+ wrappedBounds =
+ testBounds.wrapDateLine(maxExtent, {'leftTolerance': 1} );
+ desiredBounds = simpleBounds.add(-6,0);
+ t.ok(wrappedBounds.equals(desiredBounds), "wrapping a bounds leftTolerance right of maxextent works");
+
+ //exactly right
+ testBounds = exactBounds.add(20,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(exactBounds), "wrapping an exact bounds once right of maxextent works");
+
+ //right
+ testBounds = simpleBounds.add(20,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(simpleBounds), "wrapping a bounds once right of maxextent works");
+
+ //way right
+ testBounds = simpleBounds.add(200,0);
+ wrappedBounds = testBounds.wrapDateLine(maxExtent);
+ t.ok(wrappedBounds.equals(simpleBounds), "wrapping a bounds way right of maxextent works");
+
+
+
+ }
+ function test_Bounds_transform(t) {
+ t.plan( 5 );
+ bounds = new OpenLayers.Bounds(10, -10, 20, 10);
+ bounds.transform(new OpenLayers.Projection("foo"), new OpenLayers.Projection("Bar"));
+ t.eq(bounds.toBBOX(), "10,-10,20,10", "null transform okay");
+ bounds.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
+ t.eq(bounds.toBBOX(), "1113194.907778,-1118889.974702,2226389.815556,1118889.974702", "bounds for spherical mercator transform are correct");
+ bounds.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
+ t.eq(bounds.toBBOX(), "10,-10,20,10", "bounds for inverse spherical mercator transform are correct");
+
+ // transform with string
+ bounds = new OpenLayers.Bounds(10, -10, 20, 10);
+ bounds.transform("EPSG:4326", "EPSG:900913");
+ t.eq(bounds.toBBOX(), "1113194.907778,-1118889.974702,2226389.815556,1118889.974702", "(string) bounds for spherical mercator transform are correct");
+ bounds.transform("EPSG:900913", "EPSG:4326");
+ t.eq(bounds.toBBOX(), "10,-10,20,10", "(string) bounds for inverse spherical mercator transform are correct");
+
+ }
+
+ function test_Bounds_add(t) {
+ t.plan( 6 );
+
+ origBounds = new OpenLayers.Bounds(1,2,3,4);
+ testBounds = origBounds.clone();
+
+ var bounds = testBounds.add(5, 50);
+ t.ok( testBounds.equals(origBounds), "testBounds is not modified by add operation");
+
+ var b = new OpenLayers.Bounds(6,52,8,54);
+ t.ok( bounds.equals(b), "bounds is set correctly");
+
+ //null values
+ try {
+ bounds = testBounds.add(null, 50);
+ } catch(e) {
+ t.ok("exception thrown when passing null value to add()");
+ }
+ t.ok( testBounds.equals(origBounds), "testBounds is not modified by erroneous add operation (null x)");
+
+ try {
+ bounds = testBounds.add(5, null);
+ } catch(e) {
+ t.ok("exception thrown when passing null value to add()");
+ }
+ t.ok( testBounds.equals(origBounds), "testBounds is not modified by erroneous add operation (null y)");
+ }
+
+ function test_Bounds_scale(t) {
+ t.plan(3);
+
+ origBounds = new OpenLayers.Bounds(1,2,3,4);
+ bounds = origBounds.scale(2);
+ var b = new OpenLayers.Bounds(0,1,4,5);
+ t.ok(bounds.equals(b), "Bounds scale correctly with default origin at center")
+
+ var origin = new OpenLayers.Pixel(0,1);
+ bounds = origBounds.scale(2,origin);
+ b = new OpenLayers.Bounds(2,3,6,7);
+ t.ok(bounds.equals(b), "Bounds scale correctly with offset origin");
+
+ origin = new OpenLayers.Pixel(5,1);
+ bounds = bounds.scale(2, origin);
+ b = new OpenLayers.Bounds(-1, 5, 7, 13);
+ t.ok(bounds.equals(b), "Bounds scale correctly with offset origin");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/BaseTypes/Class.html b/misc/openlayers/tests/BaseTypes/Class.html
new file mode 100644
index 0000000..11d5826
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/Class.html
@@ -0,0 +1,350 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Class(t) {
+ t.plan(1);
+ var MyClass = OpenLayers.Class({
+ initialize: function () {
+ t.ok(false, "initialize should not be called");
+ }
+ });
+ t.ok(true,
+ "defining a class does not call the constructor for the class");
+ }
+
+ function test_Class_constructor(t) {
+ t.plan(7);
+
+ var MyClass = OpenLayers.Class({
+ prop: null,
+ classProp: {'bad': 'practice'},
+ initialize: function(a1, a2) {
+ this.prop = "instance property";
+ t.ok(true,
+ "initialize is called when a new instance is created");
+ t.eq(a1, arg1,
+ "initialize is called with the proper first argument");
+ t.eq(a2, arg2,
+ "initialize is called with the proper second argument");
+ },
+ CLASS_NAME: "MyClass"
+ });
+
+ var arg1 = "anArg";
+ var arg2 = {"another": "arg"};
+ var myObj = new MyClass(arg1, arg2);
+ t.eq(MyClass.prop, null,
+ "creating a new instance doesn't modify the class");
+ t.eq(myObj.prop, "instance property",
+ "the new instance is assigned a property in the constructor");
+ t.eq(myObj["CLASS_NAME"], "MyClass",
+ "the new object is an instance of MyClass");
+
+ // allow for modification of class properties
+ MyClass.prototype.classProp.bad = "good";
+ t.eq(myObj.classProp.bad, "good",
+ "modifying a class property modifies properties of the instance");
+ }
+
+ function test_Class_inheritance(t) {
+ t.plan(7);
+
+ var BaseClass = OpenLayers.Class({
+ prop: "base",
+ initialize: function() {
+ t.ok(false,
+ "base class constructor is not called during inheritance");
+ },
+ toString: function() {
+ return "toString inherited";
+ },
+ CLASS_NAME: "BaseClass"
+ });
+
+ var ChildClass = OpenLayers.Class(BaseClass, {
+ initialize: function() {
+ t.ok(true,
+ "child class constructor is called in creating an instance");
+ },
+ CLASS_NAME: "ChildClass"
+ });
+
+ var child = new ChildClass();
+ t.eq(child.prop, "base",
+ "instance of child inherits properties from base");
+ t.eq(child.toString(), "toString inherited",
+ "instance of child inherits toString method from base");
+ t.eq(child["CLASS_NAME"],
+ "ChildClass",
+ "new object is an instance of the child class");
+
+ var F = OpenLayers.Class(Object, {});
+ t.ok(!("initialize" in Object.prototype), "no messing with non OL prototypes");
+
+ // test with an abstract class (i.e. a class that doesn't have an initialize
+ // method) as the parent class
+ var Vehicule = OpenLayers.Class({
+ numWheels: null
+ });
+ var Bike = OpenLayers.Class(Vehicule, {
+ initialize: function() {
+ this.numWheels = 2;
+ }
+ });
+ var b = new Bike();
+ t.ok(b instanceof Vehicule, "a bike is a vehicule");
+
+ // test inheritance with something that has a non-function initialize property
+ var P = OpenLayers.Class({
+ initialize: "foo"
+ });
+ var C = OpenLayers.Class(P, {
+ initialize: function() {
+ // pass
+ }
+ });
+ var c = new C();
+ t.eq(P.prototype.initialize, "foo", "Class restores custom initialize property.");
+
+ }
+
+ function test_Class_multiple_inheritance(t) {
+ t.plan(7);
+ var BaseClass1 = OpenLayers.Class({
+ override: "base1",
+ prop: "base1",
+ variable: null,
+ initialize: function() {
+ t.ok(true,
+ "only called when an instance of this class is created");
+ },
+ CLASS_NAME: "BaseClass1"
+ });
+
+ var BaseClass2 = OpenLayers.Class({
+ override: "base2",
+ initialize: function() {
+ t.ok(false,
+ "base class constructor is not called during inheritance");
+ },
+ CLASS_NAME: "BaseClass1"
+ });
+
+ var ChildClass = OpenLayers.Class(BaseClass1, BaseClass2, {
+ initialize: function(arg) {
+ if(this.prop == "base1") {
+ this.variable = "child";
+ }
+ t.ok(true,
+ "only child class constructor is called on initialization");
+ },
+ CLASS_NAME: "ChildClass"
+ });
+
+ var arg = "child";
+ var child = new ChildClass(arg);
+ t.eq(child.variable, arg,
+ "inheritance works before construction");
+ t.eq(child.prop, "base1",
+ "properties are inherited with multiple classes")
+ t.eq(child.override, "base2",
+ "properties are inherited in the expected order");
+ t.eq(child["CLASS_NAME"],
+ "ChildClass",
+ "object is an instance of child class");
+
+ var base1 = new BaseClass1();
+ t.eq(base1.override, "base1",
+ "inheritance doesn't mess with parents");
+
+ }
+
+ function test_inheritance_chain(t) {
+ t.plan(1);
+ var A = new OpenLayers.Class({
+ initialize: function() {
+ this.a = 'foo';
+ }
+ });
+ var B = new OpenLayers.Class(A, {});
+ var C = new OpenLayers.Class(B, {
+ initialize: function() {
+ B.prototype.initialize.apply(this, arguments);
+ this.a = this.a + 'bar';
+ }
+ });
+ var c = new C;
+ t.eq(c.a, 'foobar', 'constructor at the root is called');
+ }
+
+
+ function test_Class_isInstanceOf(t) {
+ t.plan(7);
+ var wms = new OpenLayers.Layer.WMS({});
+ var drag = new OpenLayers.Control.DragFeature({});
+ t.ok(wms instanceof OpenLayers.Layer.WMS, "isInstanceOf(WMS)");
+ t.ok(wms instanceof OpenLayers.Layer, "isInstanceOf(Layer)");
+ t.ok(!(wms instanceof OpenLayers.Format), "not isInstanceOf(Format)");
+ t.ok(drag instanceof OpenLayers.Control, "drag is a control");
+ t.ok(!(drag instanceof OpenLayers.Layer), "drag is not a layer");
+
+ //test a class with multiple inheritance
+ var BadClass=OpenLayers.Class(OpenLayers.Layer.WMS, OpenLayers.Control.DragFeature);
+ var bad = new BadClass({});
+ t.ok(!(bad instanceof OpenLayers.Control), "bad is a control, but it is also a layer and we cannot have two superclasses");
+ t.ok(bad instanceof OpenLayers.Layer, "bad is a layer, it inherits from the layer first");
+ }
+
+ //
+ // IGN's GeoPortal API overwrite prototypes of OpenLayers constructors.
+ // The tests below aim to cover their usage pattens.
+ //
+
+ // the overwrite function under test
+ function overwrite(C, o) {
+ if(typeof o.initialize === "function" &&
+ C === C.prototype.initialize) {
+ // OL 2.11
+
+ var proto = C.prototype;
+ var staticProps = OpenLayers.Util.extend({}, C);
+
+ C = o.initialize;
+
+ C.prototype = proto;
+ OpenLayers.Util.extend(C, staticProps);
+ }
+ OpenLayers.Util.extend(C.prototype, o);
+ return C;
+ }
+
+ function test_overwrite_1(t) {
+ // overwrite constructor
+ t.plan(1);
+ var A = OpenLayers.Class({
+ initialize: function() {
+ this.a = "foo";
+ }
+ });
+ A = overwrite(A, {
+ initialize: function() {
+ this.a = "bar";
+ }
+ });
+ var a = new A;
+ t.eq(a.a, "bar", "ctor overwritten");
+ }
+
+ function test_overwrite_2(t) {
+ // overwrite regular method
+ t.plan(1);
+ var A = OpenLayers.Class({
+ initialize: function() {
+ },
+ method: function() {
+ this.a = "foo";
+ }
+ });
+ A = overwrite(A, {
+ method: function() {
+ this.a = "bar";
+ }
+ });
+ var a = new A;
+ a.method();
+ t.eq(a.a, "bar", "method overwritten");
+ }
+
+ function test_overwrite_3(t) {
+ // overwrite constructor of subclass
+ t.plan(1);
+ var A = OpenLayers.Class({
+ initialize: function() {
+ this.a = "foo";
+ }
+ });
+ var B = OpenLayers.Class(A, {
+ initialize: function() {
+ A.prototype.initialize.call(this);
+ }
+ });
+ B = overwrite(B, {
+ initialize: function() {
+ A.prototype.initialize.call(this);
+ this.a = "bar";
+ }
+ });
+ var b = new B;
+ t.eq(b.a, "bar", "ctor overwritten");
+ }
+
+ function test_overwrite_4(t) {
+ // overwrite constructor of parent class
+ t.plan(1);
+ var A = OpenLayers.Class({
+ initialize: function() {
+ this.a = "foo";
+ }
+ });
+ var B = OpenLayers.Class(A, {
+ initialize: function() {
+ A.prototype.initialize.call(this);
+ }
+ });
+ A = overwrite(A, {
+ initialize: function() {
+ this.a = "bar";
+ }
+ });
+ var b = new B;
+ t.eq(b.a, "bar", "ctor overwritten");
+ }
+
+ function test_overwrite_5(t) {
+ // overwrite constructor of parent class, which itself
+ // doesn't defined "initialize"
+ t.plan(2);
+ var A = OpenLayers.Class({
+ initialize: function() {
+ this.a = "foo";
+ }
+ });
+ var B = OpenLayers.Class(A, {});
+ var _A = A;
+ A = overwrite(A, {
+ initialize: function() {
+ this.a = "bar";
+ }
+ });
+ var b = new B;
+ t.ok(A.prototype === _A.prototype, "A and _A share the prototype");
+ t.eq(b.a, "bar", "ctor overwritten");
+ }
+
+ function test_overwrite_6(t) {
+ // with static methods
+ t.plan(1);
+ var A = OpenLayers.Class({
+ initialize: function() {
+ }
+ });
+ A.staticMethod = function() {};
+ A = overwrite(A, {
+ initialize: function() {
+ }
+ });
+ var exc = false;
+ try {
+ A.staticMethod();
+ } catch(e) {
+ exc = true;
+ }
+ t.ok(!exc, "static method still there");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/BaseTypes/Date.html b/misc/openlayers/tests/BaseTypes/Date.html
new file mode 100644
index 0000000..e54fb31
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/Date.html
@@ -0,0 +1,191 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Date_toISOString(t) {
+ t.plan(3);
+
+ var date, str;
+
+ // check valid date
+ date = new Date(Date.UTC(2010, 10, 27, 18, 19, 15, 123));
+ str = OpenLayers.Date.toISOString(date);
+ t.eq(str, "2010-11-27T18:19:15.123Z", "valid date");
+
+ // check zero padding
+ date = new Date(Date.UTC(2010, 7, 7, 18, 9, 5, 12));
+ str = OpenLayers.Date.toISOString(date);
+ t.eq(str, "2010-08-07T18:09:05.012Z", "zero padding");
+
+ // check invalid date
+ date = new Date("foo");
+ try {
+ str = OpenLayers.Date.toISOString(date);
+ } catch (err) {
+ // some implementations throw RangeError
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=649575
+ if (err instanceof RangeError) {
+ str = "Invalid Date";
+ }
+ }
+ t.eq(str, "Invalid Date", "invalid date");
+
+ }
+
+ function test_Date_parse(t) {
+
+ t.plan(114);
+
+ var cases = {
+ "2000": {
+ year: 2000,
+ month: 0,
+ date: 1
+ },
+ "2005-10": {
+ year: 2005,
+ month: 9,
+ date: 1
+ },
+ "1971-07-23": {
+ year: 1971,
+ month: 6,
+ date: 23
+ },
+ "1801-11-20T04:30:15Z": {
+ year: 1801,
+ month: 10,
+ date: 20,
+ hour: 4,
+ minutes: 30,
+ seconds: 15
+ },
+ "1989-06-15T18:30:15.91Z": {
+ year: 1989,
+ month: 5,
+ date: 15,
+ hour: 18,
+ minutes: 30,
+ seconds: 15,
+ milliseconds: 910
+ },
+ "1989-06-15T18:30:15.091Z": {
+ year: 1989,
+ month: 5,
+ date: 15,
+ hour: 18,
+ minutes: 30,
+ seconds: 15,
+ milliseconds: 91
+ },
+ "1989-06-15T13:30:15.091-05": {
+ year: 1989,
+ month: 5,
+ date: 15,
+ hour: 18,
+ minutes: 30,
+ seconds: 15,
+ milliseconds: 91
+ },
+ "2010-08-06T15:21:25-06": { // MDT
+ year: 2010,
+ month: 7,
+ date: 6,
+ hour: 21,
+ minutes: 21,
+ seconds: 25
+ },
+ "2010-08-07T06:21:25+9": { // JSP
+ year: 2010,
+ month: 7,
+ date: 6,
+ hour: 21,
+ minutes: 21,
+ seconds: 25
+ },
+ "2010-08-07T02:51:25+05:30": { // IST
+ year: 2010,
+ month: 7,
+ date: 6,
+ hour: 21,
+ minutes: 21,
+ seconds: 25
+ },
+ "T21:51:25Z": {
+ hour: 21,
+ minutes: 51,
+ seconds: 25
+ },
+ "T02:51:25+05:30": { // IST
+ hour: 21,
+ minutes: 21,
+ seconds: 25
+ },
+ "T2:51:25.1234-7": { // lenient
+ hour: 9,
+ minutes: 51,
+ seconds: 25,
+ milliseconds: 123
+ },
+ "2000Z": { // lenient (Chrome accepts this)
+ year: 2000
+ },
+ "2000-02Z": { // lenient (Chrome accepts this)
+ year: 2000,
+ month: 1
+ },
+ "2000-04-15Z": { // lenient (Chrome accepts this)
+ year: 2000,
+ month: 3,
+ date: 15
+ }
+ };
+
+ var o, got, exp;
+ for (var str in cases) {
+ o = cases[str];
+ got = OpenLayers.Date.parse(str);
+ exp = new Date(Date.UTC(o.year || 0, o.month || 0, o.date || 1, o.hour || 0, o.minutes || 0, o.seconds || 0, o.milliseconds || 0));
+ if ("year" in o) {
+ t.eq(got.getUTCFullYear(), exp.getUTCFullYear(), str + ": correct UTCFullYear");
+ t.eq(got.getUTCMonth(), exp.getUTCMonth(), str + ": correct UTCMonth");
+ t.eq(got.getUTCDate(), exp.getUTCDate(), str + ": correct UTCDate");
+ } else {
+ t.ok(true, str + ": ECMA doesn't specify how years are handled in time only strings");
+ t.ok(true, str + ": ECMA doesn't specify how months are handled in time only strings");
+ t.ok(true, str + ": ECMA doesn't specify how days are handled in time only strings");
+ }
+ if ("hour" in o) {
+ t.eq(got.getUTCHours(), exp.getUTCHours(), str + ": correct UTCHours");
+ t.eq(got.getUTCMinutes(), exp.getUTCMinutes(), str + ": correct UTCMinutes");
+ t.eq(got.getUTCSeconds(), exp.getUTCSeconds(), str + ": correct UTCSeconds");
+ t.eq(got.getUTCMilliseconds(), exp.getUTCMilliseconds(), str + ": correct UTCMilliseconds");
+ } else {
+ t.ok(true, str + ": ECMA doesn't specify how hours are handled in date only strings");
+ t.ok(true, str + ": ECMA doesn't specify how minutes are handled in date only strings");
+ t.ok(true, str + ": ECMA doesn't specify how seconds are handled in date only strings");
+ t.ok(true, str + ": ECMA doesn't specify how milliseconds are handled in date only strings");
+ }
+ }
+
+ // check invalid date parsing
+ var invalid = OpenLayers.Date.parse("foo");
+ t.ok(invalid instanceof Date, "invalid is a date");
+ t.ok(isNaN(invalid.getTime()), "invalid has no time");
+ }
+
+ function test_regex(t) {
+ t.plan(1);
+ var regex = OpenLayers.Date.dateRegEx;
+ OpenLayers.Date.dateRegEx = /^(?:(-?\d{4})(?:-(\d{2})(?:-(\d{2}))?)?)?(?:(?:T(\d{1,2}):(\d{2}):(\d{2}(?:\.\d+)?)(Z|(?:[+-]\d{1,2}(?::(\d{2}))?)))|Z)?$/;
+ var date = OpenLayers.Date.parse("-0501-03-01T00:00:00.000Z");
+ t.ok(!isNaN(date.getTime()), "date with negative year is parsed when providing alternative regex");
+ OpenLayers.Date.dateRegEx = regex;
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/BaseTypes/Element.html b/misc/openlayers/tests/BaseTypes/Element.html
new file mode 100644
index 0000000..6e094cc
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/Element.html
@@ -0,0 +1,195 @@
+<html>
+ <head>
+
+ <script src="../OLLoader.js"></script>
+
+ <script type="text/javascript">
+
+ function test_Element_visible(t) {
+ t.plan(3);
+
+ var elem = {
+ style: {
+ 'display': ""
+ }
+ };
+
+ elem.style.display = "";
+ t.ok(OpenLayers.Element.visible(elem), "element with style.display == '' is visible");
+
+ elem.style.display = "block";
+ t.ok(OpenLayers.Element.visible(elem), "element with style.display == block is visible");
+
+ elem.style.display = "none";
+ t.ok(!OpenLayers.Element.visible(elem), "element with style.display == none is not visible");
+ }
+
+ function test_Element_toggle(t) {
+ t.plan(2);
+
+ var elem1 = {
+ style: {
+ 'display': "none"
+ }
+ };
+
+ var elem2 = {
+ style: {
+ 'display': ""
+ }
+ };
+
+ OpenLayers.Element.toggle(elem1, elem2);
+
+ t.eq(elem1.style.display, "", "hidden element toggled to display");
+ t.eq(elem2.style.display, "none", "shown element toggled to hidden");
+ }
+
+ function test_Element_remove(t) {
+ t.plan(1);
+
+ var elem = {
+ parentNode: {
+ 'removeChild': function(elem) {
+ t.ok(true, "removeChild called");
+ }
+ }
+ };
+ OpenLayers.Element.remove(elem);
+ }
+
+ function test_Element_getHeight(t) {
+ t.plan(1);
+
+ var elem = {
+ 'offsetHeight': {}
+ };
+
+ t.ok(OpenLayers.Element.getHeight(elem) == elem.offsetHeight, "offsetHeight returned");
+ }
+
+ function test_hasClass(t) {
+ t.plan(14);
+ var has = OpenLayers.Element.hasClass;
+
+ var element = document.createElement("div");
+ element.className = "fe fi fo fum one.part two-part three:part four";
+
+ t.ok(has(element, "fe"), "has fe");
+ t.ok(has(element, "fi"), "has fi");
+ t.ok(has(element, "fo"), "has fo");
+ t.ok(!has(element, "f"), "hasn't f");
+ t.ok(!has(element, "o"), "hasn't o");
+ t.ok(!has(element, "fumb"), "hasn't fumb");
+ t.ok(!has(element, "one"), "hasn't one");
+ t.ok(has(element, "one.part"), "has one.part");
+ t.ok(!has(element, "two"), "hasn't two");
+ t.ok(has(element, "two-part"), "has two-part");
+ t.ok(!has(element, "three"), "hasn't three");
+ t.ok(has(element, "three:part"), "has three:part");
+ t.ok(has(element, "four"), "has four");
+
+ element.className = "";
+ t.ok(!has(element, "nada"), "hasn't nada");
+ }
+
+ function test_addClass(t) {
+ t.plan(6);
+ var add = OpenLayers.Element.addClass;
+
+ var element = document.createElement("div");
+ element.id = "foo";
+ t.eq(element.className, "", "starts with no class name");
+
+ var mod = add(element, "first");
+ t.eq(mod.id, element.id, "returns the same element");
+ t.eq(element.className, "first", "properly adds first class name");
+ t.eq(add(element, "second").className, "first second",
+ "properly adds second class name");
+ t.eq(add(element, "second").className, "first second",
+ "doesn't do anything for duplicated names");
+ t.eq(add(element, "third").className, "first second third",
+ "properly adds third class name");
+ }
+
+ function test_removeClass(t) {
+ t.plan(5);
+ var remove = OpenLayers.Element.removeClass;
+
+ var element = document.createElement("div");
+ element.id = "foo";
+ element.className = "first second middle fourth last";
+
+ var mod = remove(element, "last");
+ t.eq(mod.id, element.id, "returns the same element");
+ t.eq(element.className, "first second middle fourth",
+ "properly removes last class name");
+ t.eq(remove(element, "first").className, "second middle fourth",
+ "properly removes first class name");
+ t.eq(remove(element, "middle").className, "second fourth",
+ "properly removes middle class name");
+ t.eq(remove(element, "nada").className, "second fourth",
+ "doesn't do anything for bogus class name");
+ }
+
+ function test_toggleClass(t) {
+ t.plan(5);
+ var toggle = OpenLayers.Element.toggleClass;
+
+ var element = document.createElement("div");
+ element.id = "foo";
+
+ var mod = toggle(element, "first");
+ t.eq(mod.id, element.id, "returns the same element");
+ t.eq(element.className, "first", "adds first new class name");
+ t.eq(toggle(element, "second").className, "first second",
+ "adds second new class name");
+ t.eq(toggle(element, "first").className, "second",
+ "removes first existing class name");
+ t.eq(toggle(element, "second").className, "",
+ "removes second existing class name");
+ }
+
+ function test_Element_getStyle(t) {
+ t.plan(5);
+
+ //tests for this function are not 100% complete... there is some funky
+ // business going on in there with
+ // * document.defaultView (moz/ff I believe)
+ // but I cant seem to find a good way to test them.
+ //
+ t.ok(OpenLayers.Element.getStyle(null, null) == null, "passing null values in to getStyle() doesnt bomb, returns null");
+
+ var elem = document.getElementById("elemID");
+ elem.style.chickenHead = {}
+
+ var style = "chickenHead";
+ t.ok(OpenLayers.Element.getStyle(elem, style) == elem.style.chickenHead, "get style on basic stylename returns correctly");
+
+ elem.style.chickenHead = "auto";
+ style = "chickenHead";
+ t.ok(OpenLayers.Element.getStyle(elem, style) == null, "get style on 'auto' style returns null");
+
+ if (OpenLayers.BROWSER_NAME == "opera") {
+ elem.style.top = "15px";
+ style = "top";
+
+ elem.style.position = "static";
+ t.ok(OpenLayers.Element.getStyle(elem, style) == null, "in opera: get (top/left/right/bottom) style when position == 'static' returns null");
+
+ elem.style.position = "";
+ t.ok(OpenLayers.Element.getStyle(elem, style) == null, "in opera: get (top/left/right/bottom) style when position != 'static', gets computedStyle as static and returns null");
+
+ } else {
+ t.ok(true, "browser is not opera.");
+ t.ok(true, "trust me. browser is not opera.");
+ }
+
+ }
+
+ </script>
+ </head>
+ <body>
+ <div id="elemID" style="width:50px; height:100px; background-color:red">test</div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/BaseTypes/LonLat.html b/misc/openlayers/tests/BaseTypes/LonLat.html
new file mode 100644
index 0000000..a1801f6
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/LonLat.html
@@ -0,0 +1,241 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var lonlat;
+
+ function test_LonLat_constructor (t) {
+ t.plan( 8 );
+ lonlat = new OpenLayers.LonLat(6, 5);
+ t.ok( lonlat instanceof OpenLayers.LonLat, "new OpenLayers.LonLat returns LonLat object" );
+ t.eq( lonlat.CLASS_NAME, "OpenLayers.LonLat", "lonlat.CLASS_NAME is set correctly");
+ t.eq( lonlat.lon, 6, "lonlat.lon is set correctly");
+ t.eq( lonlat.lat, 5, "lonlat.lat is set correctly");
+
+ // possible global Mercator projection values
+ lonlat = new OpenLayers.LonLat(20037508.33999999, -20037508.33999999);
+ t.eq( lonlat.lon, 20037508.34, "lonlat.lon rounds correctly");
+ t.eq( lonlat.lat, -20037508.34, "lonlat.lat rounds correctly");
+
+ // allow construction from single arg
+ lonlat = new OpenLayers.LonLat([1, 2]);
+ t.eq(lonlat.lon, 1, "lon from array");
+ t.eq(lonlat.lat, 2, "lat from array");
+
+ }
+
+ function test_LonLat_constructorFromStrings (t) {
+ t.plan( 4 );
+ lonlat = new OpenLayers.LonLat("6", "5");
+ t.ok( lonlat instanceof OpenLayers.LonLat, "new OpenLayers.LonLat returns LonLat object" );
+ t.eq( lonlat.CLASS_NAME, "OpenLayers.LonLat", "lonlat.CLASS_NAME is set correctly");
+ t.eq( lonlat.lon, 6, "lonlat.lon is set correctly");
+ t.eq( lonlat.lat, 5, "lonlat.lat is set correctly");
+ }
+
+ function test_LonLat_toString(t) {
+ t.plan( 1 );
+ lonlat = new OpenLayers.LonLat(5,6);
+ t.eq( lonlat.toString(), "lon=5,lat=6", "lonlat.toString() returns correctly");
+ }
+
+ function test_LonLat_toShortString(t) {
+ t.plan( 1 );
+ lonlat = new OpenLayers.LonLat(5,6);
+ t.eq( lonlat.toShortString(), "5, 6", "lonlat.toShortString() returns correctly");
+ }
+
+ function test_LonLat_clone(t) {
+ t.plan( 3 );
+ oldLonLat = new OpenLayers.LonLat(5,6);
+ lonlat = oldLonLat.clone();
+ t.ok( lonlat instanceof OpenLayers.LonLat, "clone returns new OpenLayers.LonLat object" );
+ t.ok( lonlat.equals(oldLonLat), "lonlat is set correctly");
+
+ oldLonLat.lon = 100;
+ t.eq( lonlat.lon, 5, "changing oldLonLat.lon doesn't change lonlat.lon");
+ }
+
+ function test_LonLat_add(t) {
+ t.plan(8);
+
+ origLL = new OpenLayers.LonLat(10,100);
+ lonlatA = origLL.clone();
+
+ addpx = lonlatA.add(5, 50);
+ t.ok( lonlatA.equals(origLL), "lonlatA is not modified by add operation");
+
+ var ll = new OpenLayers.LonLat(15,150);
+ t.ok( addpx.equals(ll), "addpx is set correctly");
+
+ //null values
+ try {
+ addpx = lonlatA.add(null, 50);
+ } catch(e) {
+ t.ok("exception thrown when passing null value to add()");
+ }
+ t.ok( lonlatA.equals(origLL), "lonlatA is not modified by erroneous add operation (null lon)");
+
+ try {
+ addpx = lonlatA.add(5, null);
+ } catch(e) {
+ t.ok("exception thrown when passing null value to add()");
+ }
+ t.ok( lonlatA.equals(origLL), "lonlatA is not modified by erroneous add operation (null lat)");
+
+ // string values
+ addpx = origLL.clone().add("5", "50");
+ t.eq(addpx.lon, 15, "addpx.lon is set correctly");
+ t.eq(addpx.lat, 150, "addpx.lat is set correctly");
+ }
+
+ function test_LonLat_equals(t) {
+ t.plan( 5 );
+ lonlat = new OpenLayers.LonLat(5,6);
+
+ ll = new OpenLayers.LonLat(5,6);
+ t.eq( lonlat.equals(ll), true, "(5,6) equals (5,6)");
+
+ ll = new OpenLayers.LonLat(1,6);
+ t.eq( lonlat.equals(ll), false, "(5,6) does not equal (1,6)");
+
+ ll = new OpenLayers.LonLat(5,2);
+ t.eq( lonlat.equals(ll), false, "(5,6) does not equal (5,2)");
+
+ ll = new OpenLayers.LonLat(1,2);
+ t.eq( lonlat.equals(ll), false, "(5,6) does not equal (1,2)");
+
+ t.ok( !lonlat.equals(null), "equals() returns false on comparison to null");
+
+ }
+
+ function test_LonLat_fromString(t) {
+ t.plan( 2 );
+ lonlat = OpenLayers.LonLat.fromString("6,5");
+ t.ok( lonlat instanceof OpenLayers.LonLat, "OpenLayers.LonLat.fromString() returns LonLat object" );
+
+ var ll = new OpenLayers.LonLat(6, 5);
+ t.ok( lonlat.equals(ll), "lonlat is set correctly");
+ }
+
+ function test_LonLat_fromArray(t) {
+ t.plan( 3 );
+
+ // (1 test) must return a OpenLayers.LonLat-instance
+ lonlat = OpenLayers.LonLat.fromArray([6,5]);
+ t.ok( lonlat instanceof OpenLayers.LonLat, "OpenLayers.LonLat.fromArray returns LonLat object" );
+
+ var ll = new OpenLayers.LonLat(6, 5);
+ // (1 test) must return correct LonLat-object
+ t.ok( lonlat.equals(ll), "lonlat is set correctly");
+
+
+ // (1 test) check how function deals with illegal arguments, it should
+ // never throw an exception and always return an instance of
+ // OpenLayers.LonLat.
+ var unexpectedResult = false,
+ undef,
+ checkArgs = [
+ {},
+ '',
+ 6,
+ false,
+ true,
+ [undef, 5],
+ [6, undef]
+ ],
+ returnedVal;
+
+ try {
+ for(var i = 0, len = checkArgs.length; i < len; i++ ){
+ returnedVal = OpenLayers.LonLat.fromArray( checkArgs[i] );
+ if (!(returnedVal instanceof OpenLayers.LonLat) ) {
+ unexpectedResult = true;
+ break;
+ }
+ }
+ // no arguments at all
+ returnedVal = OpenLayers.LonLat.fromArray();
+ unexpectedResult = !(returnedVal instanceof OpenLayers.LonLat);
+ } catch(e) {
+ unexpectedResult = true;
+ } finally {
+ t.ok(!unexpectedResult, "OpenLayers.LonLat.fromArray always returns an instance of OpenLayers.LonLat and doesn't throw an exception when called with unexpected argument.");
+ }
+ }
+
+ function test_LonLat_transform(t) {
+ t.plan(10);
+ lonlat = new OpenLayers.LonLat(10, -10);
+ lonlat.transform(new OpenLayers.Projection("foo"), new OpenLayers.Projection("Bar"));
+ t.eq(lonlat.lon, 10, "lon for null transform is the same")
+ t.eq(lonlat.lat, -10, "lat for null transform is the same")
+ lonlat.transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913"));
+ t.eq(Math.round(lonlat.lon), 1113195, "lon for spherical mercator transform is correct");
+ t.eq(Math.round(lonlat.lat), -1118890, "lat for spherical mercator correct")
+ lonlat.transform(new OpenLayers.Projection("EPSG:900913"), new OpenLayers.Projection("EPSG:4326"));
+ t.eq(lonlat.lon, 10, "lon for inverse spherical mercator transform is correct");
+ t.eq(Math.round(lonlat.lat), -10, "lat for inverse spherical mercator correct")
+
+ // transform with string
+ lonlat = new OpenLayers.LonLat(10, -10);
+ lonlat.transform("EPSG:4326", "EPSG:900913");
+ t.eq(Math.round(lonlat.lon), 1113195, "(string) lon for spherical mercator transform is correct");
+ t.eq(Math.round(lonlat.lat), -1118890, "(string) lat for spherical mercator correct")
+ lonlat.transform("EPSG:900913", "EPSG:4326");
+ t.eq(lonlat.lon, 10, "(string) lon for inverse spherical mercator transform is correct");
+ t.eq(Math.round(lonlat.lat), -10, "(string) lat for inverse spherical mercator correct")
+
+ }
+
+ function test_LonLat_wrapDateLine(t) {
+ t.plan( 6 );
+
+ var goodLL = new OpenLayers.LonLat(0,0);
+ var testLL, wrappedLL;
+
+ //bad maxextent
+ var maxExtent = null;
+
+ testLL = goodLL.clone();
+ wrappedLL = testLL.wrapDateLine(maxExtent);
+ t.ok(wrappedLL.equals(goodLL), "wrapping a ll with a bad maxextent does nothing");
+
+
+ //good maxextent
+ maxExtent = new OpenLayers.Bounds(-10,-10,10,10);
+
+ //inside
+ testLL = goodLL.clone();
+ wrappedLL = testLL.wrapDateLine(maxExtent);
+ t.ok(wrappedLL.equals(goodLL), "wrapping a ll within the maxextent does nothing");
+
+ //left
+ testLL = goodLL.add(-20,0);
+ wrappedLL = testLL.wrapDateLine(maxExtent);
+ t.ok(wrappedLL.equals(goodLL), "wrapping a ll once left of maxextent works");
+
+ //way left
+ testLL = goodLL.add(-200,0);
+ wrappedLL = testLL.wrapDateLine(maxExtent);
+ t.ok(wrappedLL.equals(goodLL), "wrapping a ll way left of maxextent works");
+
+ //right
+ testLL = goodLL.add(20,0);
+ wrappedLL = testLL.wrapDateLine(maxExtent);
+ t.ok(wrappedLL.equals(goodLL), "wrapping a ll once right of maxextent works");
+
+ //way right
+ testLL = goodLL.add(200,0);
+ wrappedLL = testLL.wrapDateLine(maxExtent);
+ t.ok(wrappedLL.equals(goodLL), "wrapping a ll way right of maxextent works");
+
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/BaseTypes/Pixel.html b/misc/openlayers/tests/BaseTypes/Pixel.html
new file mode 100644
index 0000000..8f9f5c9
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/Pixel.html
@@ -0,0 +1,123 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var pixel;
+
+ function test_Pixel_constructor (t) {
+ t.plan( 4 );
+ pixel = new OpenLayers.Pixel(5,6);
+ t.ok( pixel instanceof OpenLayers.Pixel, "new OpenLayers.Pixel returns Pixel object" );
+ t.eq( pixel.CLASS_NAME, "OpenLayers.Pixel", "pixel.CLASS_NAME is set correctly");
+ t.eq( pixel.x, 5, "pixel.x is set correctly");
+ t.eq( pixel.y, 6, "pixel.y is set correctly");
+ }
+
+ function test_Pixel_constructorFromString (t) {
+ t.plan( 4 );
+ pixel = new OpenLayers.Pixel("5","6");
+ t.ok( pixel instanceof OpenLayers.Pixel, "new OpenLayers.Pixel returns Pixel object" );
+ t.eq( pixel.CLASS_NAME, "OpenLayers.Pixel", "pixel.CLASS_NAME is set correctly");
+ t.eq( pixel.x, 5, "pixel.x is set correctly");
+ t.eq( pixel.y, 6, "pixel.y is set correctly");
+ }
+
+ function test_Pixel_toString(t) {
+ t.plan( 1 );
+ pixel = new OpenLayers.Pixel(5,6);
+ t.eq( pixel.toString(), "x=5,y=6", "pixel.toString() returns correctly");
+ }
+
+ function test_Pixel_clone(t) {
+ t.plan( 4 );
+ oldPixel = new OpenLayers.Pixel(5,6);
+ pixel = oldPixel.clone();
+ t.ok( pixel instanceof OpenLayers.Pixel, "clone returns new OpenLayers.Pixel object" );
+ t.eq( pixel.x, 5, "pixel.x is set correctly");
+ t.eq( pixel.y, 6, "pixel.y is set correctly");
+
+ oldPixel.x = 100;
+ t.eq( pixel.x, 5, "changing oldPixel.x doesn't change pixel.x");
+ }
+
+ function test_Pixel_distanceTo(t) {
+ t.plan( 2 );
+ var px = new OpenLayers.Pixel(0,-2);
+ pixel = new OpenLayers.Pixel(0,0);
+ t.eq( pixel.distanceTo(px), 2, "(0,0) distanceTo (0,-2) = 2");
+
+ px = new OpenLayers.Pixel(-4,6);
+ pixel = new OpenLayers.Pixel(4,6);
+ t.eq( pixel.distanceTo(px), 8, "(4,6) distanceTo (-4,6) = 8");
+ }
+
+ function test_Pixel_equals(t) {
+ t.plan( 5 );
+ pixel = new OpenLayers.Pixel(5,6);
+
+ var px = new OpenLayers.Pixel(5,6);
+ t.eq( pixel.equals(px), true, "(5,6) equals (5,6)");
+
+ px = new OpenLayers.Pixel(1,6);
+ t.eq( pixel.equals(px), false, "(5,6) does not equal (1,6)");
+
+ px = new OpenLayers.Pixel(5,2);
+ t.eq( pixel.equals(px), false, "(5,6) does not equal (5,2)");
+
+ px = new OpenLayers.Pixel(1,2);
+ t.eq( pixel.equals(px), false, "(5,6) does not equal (1,2)");
+
+ t.ok( !pixel.equals(null), "equals() returns false on comparison to null");
+
+ }
+
+ function test_Pixel_add(t) {
+ t.plan( 6 );
+
+ var origPX = new OpenLayers.Pixel(5,6);
+ var oldPixel = origPX.clone();
+
+ var pixel = oldPixel.add(10,20);
+
+ t.ok( oldPixel.equals(origPX), "oldPixel not modified by add operation");
+
+ var px = new OpenLayers.Pixel(15,26);
+ t.ok( pixel.equals(px), "returned pixel is correct");
+
+ //null values
+ try {
+ pixel = oldPixel.add(null, 50);
+ } catch(e) {
+ t.ok("exception thrown when passing null value to add()");
+ }
+ t.ok( oldPixel.equals(origPX), "oldPixel is not modified by erroneous add operation (null x)");
+
+ try {
+ addpx = oldPixel.add(5, null);
+ } catch(e) {
+ t.ok("exception thrown when passing null value to add()");
+ }
+ t.ok( oldPixel.equals(origPX), "oldPixel is not modified by erroneous add operation (null y)");
+ }
+
+ function test_Pixel_offset(t) {
+ t.plan( 4 );
+
+ var oldPixel = new OpenLayers.Pixel(5,6);
+ var offset = new OpenLayers.Pixel(10,20);
+
+ pixel = oldPixel.offset(offset);
+
+ t.eq( oldPixel.x, 5, "oldPixel.x not modified by offset operation");
+ t.eq( oldPixel.y, 6, "oldPixel.y not modified by offset operation");
+
+ t.eq( pixel.x, 15, "pixel.x is set correctly");
+ t.eq( pixel.y, 26, "pixel.y is set correctly");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/BaseTypes/Size.html b/misc/openlayers/tests/BaseTypes/Size.html
new file mode 100644
index 0000000..b52906c
--- /dev/null
+++ b/misc/openlayers/tests/BaseTypes/Size.html
@@ -0,0 +1,67 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var Size;
+
+ function test_Size_constructor (t) {
+ t.plan( 4 );
+ size = new OpenLayers.Size(5,6);
+ t.ok( size instanceof OpenLayers.Size, "new OpenLayers.Size returns size object" );
+ t.eq( size.CLASS_NAME, "OpenLayers.Size", "size.CLASS_NAME is set correctly");
+ t.eq( size.w, 5, "size.w is set correctly");
+ t.eq( size.h, 6, "size.h is set correctly");
+ }
+
+ function test_Size_constructorFromString (t) {
+ t.plan( 4 );
+ size = new OpenLayers.Size("5","6");
+ t.ok( size instanceof OpenLayers.Size, "new OpenLayers.Size returns size object" );
+ t.eq( size.CLASS_NAME, "OpenLayers.Size", "size.CLASS_NAME is set correctly");
+ t.eq( size.w, 5, "size.w is set correctly");
+ t.eq( size.h, 6, "size.h is set correctly");
+ }
+
+ function test_Size_toString(t) {
+ t.plan( 1 );
+ size = new OpenLayers.Size(5,6);
+ t.eq( size.toString(), "w=5,h=6", "size.toString() returns correctly");
+ }
+
+ function test_Size_clone(t) {
+ t.plan( 3 );
+
+ oldSize = new OpenLayers.Size(5,6);
+ size = oldSize.clone();
+ t.ok( size instanceof OpenLayers.Size, "clone returns new OpenLayers.Size object" );
+ t.ok( size.equals(oldSize), "new size is equal to old size correctly");
+
+ oldSize.w = 100;
+ t.eq( size.w, 5, "changing oldSize.w doesn't change size.w");
+ }
+
+ function test_Size_equals(t) {
+ t.plan( 5 );
+ size = new OpenLayers.Size(5,6);
+
+ sz = new OpenLayers.Size(5,6);
+ t.eq( size.equals(sz), true, "(5,6) equals (5,6)");
+
+ sz = new OpenLayers.Size(1,6);
+ t.eq( size.equals(sz), false, "(5,6) does not equal (1,6)");
+
+ sz = new OpenLayers.Size(5,2);
+ t.eq( size.equals(sz), false, "(5,6) does not equal (5,2)");
+
+ sz = new OpenLayers.Size(1,2);
+ t.eq( size.equals(sz), false, "(5,6) does not equal (1,2)");
+
+ t.ok( !size.equals(null), "equals() returns false on comparison to null");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Console.html b/misc/openlayers/tests/Console.html
new file mode 100644
index 0000000..48b27c2
--- /dev/null
+++ b/misc/openlayers/tests/Console.html
@@ -0,0 +1,39 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Console(t) {
+
+ /**
+ * These tests only assure that OpenLayers works when not
+ * in debug mode. It means that calls to OpenLayers.Console
+ * will not do anything in this case. When OpenLayers is in
+ * debug mode, we assume that the Firebug extension or Firebug Lite
+ * works as advertised.
+ */
+
+ // supported OpenLayers.Console methods
+ var methods = ['log', 'debug', 'info', 'warn', 'error', 'assert',
+ 'dir', 'dirxml', 'trace', 'group', 'groupEnd', 'time',
+ 'timeEnd', 'profile', 'profileEnd', 'count'];
+
+ t.plan(methods.length * 2);
+
+ var nothing, method;
+ for(var i=0; i<methods.length; ++i) {
+ method = OpenLayers.Console[methods[i]];
+ t.ok(method,
+ "OpenLayers.Console." + methods[i] + " exists");
+ nothing = OpenLayers.Console[methods[i]]();
+ t.eq(nothing, null,
+ "OpenLayers.Console." + methods[i] + "() " +
+ "call is harmless when not in debug mode");
+ }
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control.html b/misc/openlayers/tests/Control.html
new file mode 100644
index 0000000..06f057c
--- /dev/null
+++ b/misc/openlayers/tests/Control.html
@@ -0,0 +1,107 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Control_constructor(t) {
+ t.plan(4);
+
+ var control = new OpenLayers.Control();
+
+ t.ok(control instanceof OpenLayers.Control, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControl", "displayClass set correctly");
+ t.ok(control.id != null, "default id assigned to control");
+
+ var testID = {};
+ control = new OpenLayers.Control({ 'id': testID });
+ t.ok(control.id == testID, "if id specified in options, no default assigned.");
+ }
+
+ function test_Control_addControl(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ t.ok(control.map === map, "Control.map is set to the map object" );
+ t.ok(map.controls[map.controls.length - 1] === control, "map.controls contains control");
+ }
+
+ function test_Control_title(t) {
+ t.plan( 1 );
+ var titleText = 'Title test';
+ control = new OpenLayers.Control({title:titleText});
+ t.eq( control.title, titleText, "control.title set correctly" );
+ }
+
+ function test_eventListeners(t) {
+ t.plan(1);
+
+ var method = OpenLayers.Events.prototype.on;
+ // test that events.on is called at control construction
+ var options = {
+ eventListeners: {foo: "bar"}
+ };
+ OpenLayers.Events.prototype.on = function(obj) {
+ t.eq(obj, options.eventListeners, "events.on called with eventListeners");
+ }
+ var control = new OpenLayers.Control(options);
+ OpenLayers.Events.prototype.on = method;
+ control.destroy();
+
+ // if events.on is called again, this will fail due to an extra test
+ // test control without eventListeners
+ OpenLayers.Events.prototype.on = function(obj) {
+ t.fail("events.on called without eventListeners");
+ }
+ var control2 = new OpenLayers.Control();
+ OpenLayers.Events.prototype.on = method;
+ control2.destroy();
+ }
+
+ function test_Control_destroy(t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ control.destroy();
+ t.ok(map.controls[map.controls.length - 1] != control, "map.controls doesn't contains control");
+
+ t.ok(control.map == null, "Control.map is null");
+ t.ok(control.div == null, "Control.div is null");
+ t.ok(control.handler == null, "Control.handler is null");
+ }
+
+ function test_autoActivate(t) {
+
+ t.plan(3);
+
+ var control, map = new OpenLayers.Map("map");
+
+ // confirm that a control is not activated by default
+ control = new OpenLayers.Control();
+ map.addControl(control);
+ t.ok(!control.active, "control is not activated by default");
+
+ // confirm that control is activated with autoActivate true
+ control = new OpenLayers.Control({autoActivate: true});
+ map.addControl(control);
+ t.ok(control.active, "control is activated with autoActivate true");
+
+ // confirm that control is not activated with autoActivate false
+ control = new OpenLayers.Control({autoActivate: false});
+ map.addControl(control);
+ t.ok(!control.active, "control is not activated with autoActivate false");
+
+ map.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/ArgParser.html b/misc/openlayers/tests/Control/ArgParser.html
new file mode 100644
index 0000000..34e9f5b
--- /dev/null
+++ b/misc/openlayers/tests/Control/ArgParser.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_getParameters(t) {
+ t.plan(4);
+
+ var c = new OpenLayers.Control.ArgParser(), p;
+
+ p = c.getParameters('http://example.com?fook=foov&bark=barv');
+ t.eq(p, {fook: 'foov', bark: 'barv'}, 'a) params are correct');
+
+ p = c.getParameters('http://example.com#fook=foov&bark=barv');
+ t.eq(p, {fook: 'foov', bark: 'barv'}, 'b) params are correct');
+
+ p = c.getParameters('http://example.com?a=b&b=c#fook=foov&bark=barv');
+ t.eq(p, {a: 'b', b: 'c', fook: 'foov', bark: 'barv'},
+ 'c) params are correct');
+
+ p = c.getParameters('http://example.com?a=b&b=c&fook=a&bark=b#fook=foov&bark=barv');
+ t.eq(p, {a: 'b', b: 'c', fook: 'foov', bark: 'barv'},
+ 'd) params are correct');
+ }
+ </script>
+</head>
+</html>
diff --git a/misc/openlayers/tests/Control/Attribution.html b/misc/openlayers/tests/Control/Attribution.html
new file mode 100644
index 0000000..04b85cf
--- /dev/null
+++ b/misc/openlayers/tests/Control/Attribution.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_Attribution_constructor (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.Attribution();
+ t.ok( control instanceof OpenLayers.Control.Attribution, "new OpenLayers.Control returns object" );
+ t.eq( control.displayClass, "olControlAttribution", "displayClass is correct" );
+ }
+ function test_Control_Attribution_setBaseLayer (t) {
+ t.plan(1);
+ map = new OpenLayers.Map("map");
+ map.addControl(control);
+ map.addLayer(new OpenLayers.Layer("name",{'attribution':'My layer!', isBaseLayer: true}));
+ map.addLayer(new OpenLayers.Layer("name",{'attribution':'My layer 2!', isBaseLayer: true}));
+ map.setBaseLayer(map.layers[1]);
+ t.eq(control.div.innerHTML, 'My layer 2!', "Attribution correct with changed base layer");
+
+ }
+ function test_Control_Attribution_draw (t) {
+ t.plan(3);
+ control = new OpenLayers.Control.Attribution();
+ map = new OpenLayers.Map("map");
+ map.addControl(control);
+ map.addLayer(new OpenLayers.Layer("name",{'attribution':'My layer!'}));
+ t.eq(control.div.innerHTML, 'My layer!', "Attribution correct with one layer.");
+ map.addLayer(new OpenLayers.Layer("name", {'attribution':'My layer 2!'}));
+ t.eq(control.div.innerHTML, 'My layer!, My layer 2!', "Attribution correct with two layers.");
+ control.separator = '|';
+ control.template = "Map Copyright (c) 2012 by Foo Bar; ${layers}";
+ map.addLayer(new OpenLayers.Layer("name",{'attribution':'My layer 3!'}));
+ t.eq(control.div.innerHTML, 'Map Copyright (c) 2012 by Foo Bar; My layer!|My layer 2!|My layer 3!', "Attribution correct with three layers and diff seperator.");
+
+
+ }
+
+ function test_Control_Attribution_no_duplicates(t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map("map");
+ map.addLayer(new OpenLayers.Layer("Company A: 1",{'attribution':'company A'}));
+ map.addLayer(new OpenLayers.Layer("Company A: 2",{'attribution':'company A'}));
+
+ control = new OpenLayers.Control.Attribution();
+ map.addControl(control);
+ t.eq(control.div.innerHTML, 'company A', "Attribution not duplicated.");
+
+ map.addLayer(new OpenLayers.Layer("Company B: 1",{'attribution':'company B'}));
+ map.addLayer(new OpenLayers.Layer("Company A: 3",{'attribution':'company A'}));
+ t.eq(control.div.innerHTML, 'company A, company B', "Attribution correct with four layers (3 with same attribution).");
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Button.html b/misc/openlayers/tests/Control/Button.html
new file mode 100644
index 0000000..8d5c036
--- /dev/null
+++ b/misc/openlayers/tests/Control/Button.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Control_Button_constructor (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.Button();
+ t.ok( control instanceof OpenLayers.Control.Button, "new OpenLayers.Control returns object" );
+ t.eq( control.displayClass, "olControlButton", "displayClass is correct" );
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/CacheRead.html b/misc/openlayers/tests/Control/CacheRead.html
new file mode 100644
index 0000000..ae0addf
--- /dev/null
+++ b/misc/openlayers/tests/Control/CacheRead.html
@@ -0,0 +1,108 @@
+<html>
+<head>
+ <script>
+ /**
+ * Because browsers that implement requestAnimationFrame may not execute
+ * animation functions while a window is not displayed (e.g. in a hidden
+ * iframe as in these tests), we mask the native implementations here. The
+ * native requestAnimationFrame functionality is tested in Util.html and
+ * in PanZoom.html (where a popup is opened before panning). The tests
+ * here will test the fallback setTimeout implementation for animation.
+ */
+ window.requestAnimationFrame =
+ window.webkitRequestAnimationFrame =
+ window.mozRequestAnimationFrame =
+ window.oRequestAnimationFrame =
+ window.msRequestAnimationFrame = null;
+ </script>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_addLayer_removeLayer(t) {
+ t.plan(6);
+ var control = new OpenLayers.Control.CacheRead();
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [control],
+ layers: [
+ new OpenLayers.Layer.WMS("One"),
+ new OpenLayers.Layer.WMS("Two")
+ ]
+ });
+ t.ok(map.layers[0].events.listeners.tileloadstart, "tileloadstart listener registered on layer One");
+ t.ok(map.layers[1].events.listeners.tileloadstart, "tileloadstart listener registered on layer Two");
+ control.destroy();
+ t.ok(!map.layers[1].events.listeners.tileloadstart.length, "tileloadstart listener unregistered");
+
+ control = new OpenLayers.Control.CacheRead({
+ fetchEvent: "tileerror",
+ layers: [map.layers[0]]
+ });
+ map.addControl(control);
+ t.ok(map.layers[0].events.listeners.tileerror, "tileerror listener registered on layer One");
+ t.ok(!map.layers[1].events.listeners.tileerror, "tileerror listener not registered on layer Two");
+ control.destroy();
+ t.ok(!map.layers[0].events.listeners.tileerror.length, "tileerror listener unregistered");
+
+ map.destroy();
+ }
+
+ function test_fetch(t) {
+
+ if (!window.localStorage) {
+ t.plan(1);
+ var scope = {active: true};
+ t.eq(OpenLayers.Control.CacheRead.prototype.fetch.call(scope), undefined, "no tiles fetched when localStorage is not supported.");
+ return;
+ }
+
+ t.plan(5);
+
+ var data = "data:image/gif;base64,R0lGODlhAQABAIAAAP7//wAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==";
+ window.localStorage.setItem("olCache_foo/1/1/1", data);
+ window.localStorage.setItem("olCache_bar/1/1/1", data);
+
+ var layer1 = new OpenLayers.Layer.XYZ("One", "foo/${x}/${y}/${z}");
+ var layer2 = new OpenLayers.Layer.XYZ("Two", "bar/${x}/${y}/${z}", {isBaseLayer: false});
+ var control1 = new OpenLayers.Control.CacheRead({
+ layers: [layer1]
+ });
+ var control2 = new OpenLayers.Control.CacheRead({
+ layers: [layer2],
+ fetchEvent: "tileerror"
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ controls: [control1, control2],
+ layers: [layer1, layer2],
+ zoom: 1,
+ center: [0, 0]
+ });
+
+ OpenLayers.ProxyHost = "proxy?url=";
+ var tile = new OpenLayers.Tile.Image(layer1, new OpenLayers.LonLat(0, 0), new OpenLayers.Bounds(0, 0, 1, 1), "proxy?url=foo/1/1/1");
+ OpenLayers.Control.CacheWrite.urlMap[tile.url] = "foo/1/1/1";
+
+ control1.fetch({tile: tile});
+ t.eq(tile.url, data, "proxied url replaced with data uri for original url");
+ delete OpenLayers.Control.CacheWrite.urlMap[tile.url];
+
+ t.delay_call(1, function() {
+ t.eq(layer1.grid[1][1].imgDiv.src, data, "[tileloadstart] tile content from cache");
+ t.ok(layer1.grid[0][0].imgDiv.src !== data, "[tileloadstart] tile content from remote resource");
+ t.eq(layer2.grid[1][1].imgDiv.src, data, "[tileerror] tile content from cache");
+ t.ok(layer2.grid[0][0].imgDiv.src !== data, "[tileerror] tile content from remote resource");
+
+ window.localStorage.removeItem("olCache_foo/1/1/1");
+ window.localStorage.removeItem("olCache_bar/1/1/1");
+ map.destroy();
+ });
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/CacheWrite.html b/misc/openlayers/tests/Control/CacheWrite.html
new file mode 100644
index 0000000..9922569
--- /dev/null
+++ b/misc/openlayers/tests/Control/CacheWrite.html
@@ -0,0 +1,90 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_addLayer_removeLayer(t) {
+ t.plan(6);
+ var control = new OpenLayers.Control.CacheWrite();
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [control],
+ layers: [
+ new OpenLayers.Layer.WMS("One"),
+ new OpenLayers.Layer.WMS("Two")
+ ]
+ });
+ t.ok(map.layers[0].events.listeners.tileloaded, "tileloaded listener registered on layer One");
+ t.ok(map.layers[1].events.listeners.tileloaded, "tileloaded listener registered on layer Two");
+ control.destroy();
+ t.ok(!map.layers[1].events.listeners.tileloaded.length, "tileloaded listener unregistered");
+
+ control = new OpenLayers.Control.CacheWrite({
+ layers: [map.layers[0]]
+ });
+ map.addControl(control);
+ t.ok(map.layers[0].events.listeners.tileloaded.length, "tileloaded listener registered on layer One");
+ t.ok(!map.layers[1].events.listeners.tileloaded.length, "tileloaded listener not registered on layer Two");
+ control.destroy();
+ t.ok(!map.layers[0].events.listeners.tileloaded.length, "tileloaded listener unregistered");
+
+ map.destroy();
+ }
+
+ function test_cache_clearCache(t) {
+
+ if (!window.localStorage) {
+ t.plan(2);
+ var scope = {active: true};
+ t.eq(OpenLayers.Control.CacheWrite.prototype.cache.call(scope), undefined, "no tiles cached when localStorage is not supported.");
+ t.ok(!OpenLayers.Control.CacheWrite.clearCache(), "clearCache does nothing when localStorage is not supported.");
+ return;
+ }
+
+ t.plan(4);
+ OpenLayers.Control.CacheWrite.clearCache();
+ var length = window.localStorage.length;
+
+ var tiles = 0;
+ var layer = new OpenLayers.Layer.XYZ("One", "../../img/blank.gif?${x},${y},${z}", {
+ eventListeners: {
+ tileloaded: function() {
+ tiles++;
+ }
+ }
+ });
+ var control = new OpenLayers.Control.CacheWrite({autoActivate: true});
+ var map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ controls: [control],
+ layers: [layer],
+ zoom: 1,
+ center: [0, 0]
+ });
+ t.delay_call(1, function() {
+ var canvasContext = layer.grid[1][1].getCanvasContext();
+ t.eq(window.localStorage.length, length + (canvasContext ? tiles : 0), "cache filled with tiles");
+ var url = layer.grid[1][1].url;
+ // content will be null for browsers that have localStorage but no canvas support
+ var content = canvasContext ? canvasContext.canvas.toDataURL("image/png") : null;
+ t.eq(window.localStorage.getItem("olCache_"+url), content, "localStorage contains correct image data");
+
+ layer.events.triggerEvent('tileloaded', {aborted: true, tile: layer.grid[1][1]});
+ t.eq(window.localStorage.length, length + (canvasContext ? tiles-1 : 0), "tile aborted during load not cached");
+
+ var key = Math.random();
+ window.localStorage.setItem(key, "bar");
+ OpenLayers.Control.CacheWrite.clearCache();
+ t.eq(window.localStorage.length, length + 1, "cache cleared, but foreign entries left in localStorage");
+ window.localStorage.removeItem(key);
+
+ map.destroy();
+ });
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/DragFeature.html b/misc/openlayers/tests/Control/DragFeature.html
new file mode 100644
index 0000000..cfc3a63
--- /dev/null
+++ b/misc/openlayers/tests/Control/DragFeature.html
@@ -0,0 +1,383 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Control_DragFeature_constructor(t) {
+ t.plan(3);
+
+ var options = {
+ geometryTypes: "foo"
+ };
+ var layer = "bar";
+ var control = new OpenLayers.Control.DragFeature(layer, options);
+ t.ok(control instanceof OpenLayers.Control.DragFeature,
+ "new OpenLayers.Control.DragFeature returns an instance");
+ t.eq(control.layer, "bar",
+ "constructor sets layer correctly");
+ t.eq(control.handlers.feature.geometryTypes, "foo",
+ "constructor sets options correctly on feature handler");
+ }
+
+ function test_Control_DragFeature_destroy(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ control.handlers.drag.destroy = function() {
+ t.ok(true,
+ "control.destroy calls destroy on drag handler");
+ }
+ control.handlers.feature.destroy = function() {
+ t.ok(true,
+ "control.destroy calls destroy on feature handler");
+ }
+
+ control.destroy();
+
+ }
+
+ function test_Control_DragFeature_activate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+ t.ok(!control.handlers.feature.active,
+ "feature handler is not active prior to activating control");
+ control.activate();
+ t.ok(control.handlers.feature.active,
+ "feature handler is active after activating control");
+ }
+
+ function test_Control_DragFeature_deactivate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+
+ control.handlers.drag.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on drag handler");
+ }
+ control.handlers.feature.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on feature handler");
+ }
+ control.deactivate();
+ }
+
+ function test_Control_DragFeature_over(t) {
+ t.plan(5);
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer, {
+ onEnter: function(f) { log.push({feature: f}); }
+ });
+ map.addControl(control);
+
+ control.activate();
+ t.ok(!control.handlers.drag.active,
+ "drag handler is not active before over a feature");
+
+ // simulate a mouseover on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function(evt) {
+ return feature;
+ }
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+
+ t.eq(control.feature.id, feature.id,
+ "control gets the proper feature from the feature handler");
+ t.ok(control.handlers.drag.active,
+ "drag handler activated when over a feature");
+ t.eq(log.length, 1,
+ "onEnter called exactly once");
+ t.eq(log[0].feature.id, feature.id,
+ "onEnter called with expected feature");
+ }
+
+ function test_Control_DragFeature_over_touch(t) {
+ t.plan(7);
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer, {
+ onEnter: function(f) { log.push({feature: f}); }
+ });
+ map.addControl(control);
+
+ control.activate();
+ t.ok(!control.handlers.drag.active,
+ "drag handler is not active before touch on a feature");
+
+ // simulate a touch on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function(evt) {
+ return feature;
+ }
+ map.events.triggerEvent("touchstart", {type: "touchstart", touches: ['foo']});
+
+ t.eq(control.feature.id, feature.id,
+ "control gets the proper feature from the feature handler");
+ t.ok(control.handlers.drag.active,
+ "drag handler activated when touch on a feature");
+ t.ok(control.handlers.drag.started, "drag handler has started");
+ t.ok(!control.handlers.drag.stopDown, "drag handler is not stopping down");
+ t.eq(log.length, 1,
+ "onEnter called exactly once");
+ t.eq(log[0].feature.id, feature.id,
+ "onEnter called with expected feature");
+ }
+
+ function test_Control_DragFeature_down(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+
+ control.activate();
+
+ // simulate a mouseover on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function(evt) {
+ return feature;
+ }
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+
+ // simulate a mousedown on a feature
+ control.onStart = function(feat, pixel) {
+ t.eq(feat.id, feature.id, "onStart called with the correct feature");
+ t.eq(pixel, "bar", "onStart called with the correct pixel");
+ }
+ map.events.triggerEvent("mousedown", {xy: "bar", which: 1, type: "mousemove"});
+
+ t.eq(control.lastPixel, "bar",
+ "mousedown sets the lastPixel correctly");
+ }
+
+ function test_Control_DragFeature_move(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+ map.getResolution = function() {
+ return 2;
+ }
+
+ control.activate();
+
+ // mock up a feature - for the sole purpose of testing mousemove
+ var uid = Math.random();
+ layer.getFeatureFromEvent = function() {
+ var geom = new OpenLayers.Geometry.Point(Math.random(),
+ Math.random());
+ geom.move = function(x, y) {
+ t.eq(x, 2, "move called with dx * res");
+ t.eq(y, -4, "move called with -dy * res");
+ };
+ var feature = new OpenLayers.Feature.Vector(geom);
+ feature.layer = layer;
+ feature.uid = uid;
+ return feature;
+ };
+ layer.drawFeature = function(feature) {
+ t.eq(feature.uid, uid,
+ "layer.drawFeature called with correct feature");
+ };
+
+ // simulate a mouseover on a feature
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+
+ // simulate a mousedown on a feature
+ var down = new OpenLayers.Pixel(0, 0);
+ map.events.triggerEvent("mousedown", {xy: down, which: 1, type: "mousemove"});
+
+ // simulate a mousemove on a feature
+ var move = new OpenLayers.Pixel(1, 2);
+ map.events.triggerEvent("mousemove", {xy: move, which: 1, type: "mousemove"});
+
+ }
+
+ function test_Control_DragFeature_up(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+
+ control.activate();
+
+ // simulate a mouseover on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function(evt) {
+ return feature;
+ }
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+ t.eq(control.over, true,
+ "mouseover on a feature sets the over property to true");
+ t.ok(OpenLayers.Element.hasClass(control.map.viewPortDiv, "olControlDragFeatureOver"),
+ "mouseover on a feature adds class name to map container");
+ t.eq(control.handlers.drag.active, true,
+ "mouseover on a feature activates drag handler");
+
+ // simulate a mouse-up on the map, with the mouse still
+ // over the dragged feature
+ control.handlers.drag.started = true;
+ map.events.triggerEvent("mouseup", {type: "mouseup"});
+ t.eq(control.handlers.drag.active, true,
+ "mouseup while still over dragged feature does not deactivate drag handler");
+
+ // simulate a mouse-up on the map, with the mouse out of
+ // the dragged feature
+ control.handlers.drag.started = true;
+ control.over = false;
+ map.events.triggerEvent("mouseup", {type: "mouseup"});
+ t.eq(control.handlers.drag.active, false,
+ "mouseup deactivates drag handler");
+
+ control.deactivate();
+ t.ok(!OpenLayers.Element.hasClass(control.map.viewPortDiv, "olControlDragFeatureOver"),
+ "deactivate removes class name from map container");
+ }
+
+ function test_Control_DragFeature_done(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+
+ control.activate();
+
+
+ // simulate a mouseover on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function() {
+ return feature;
+ };
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+ t.eq(control.feature.id, feature.id,
+ "feature is set on mouse over");
+ control.doneDragging();
+ t.eq(control.feature.id, feature.id,
+ "feature sticks around after doneDragging is called.");
+
+ }
+
+ function test_Control_DragFeature_out(t) {
+ t.plan(4);
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer, {
+ onLeave: function(f) { log.push({feature: f}); }
+ });
+ map.addControl(control);
+
+ control.activate();
+
+
+ // simulate a mouseover on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function() {
+ return feature;
+ };
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+ t.eq(control.feature.id, feature.id,
+ "feature is set on mouse over");
+
+ // simulate a mouseout on a feature
+ layer.getFeatureFromEvent = function() {
+ return null;
+ };
+ map.events.triggerEvent("mousemove", {type: "mousemove"});
+ t.ok(control.feature == null,
+ "feature is set to null on mouse out");
+ t.eq(log.length, 1,
+ "onLeave called exactly once");
+ t.eq(log[0].feature.id, feature.id,
+ "onLeave called with expected feature");
+ }
+
+ function test_Control_DragFeature_out_touch(t) {
+ t.plan(5);
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DragFeature(layer, {
+ onLeave: function(f) { log.push({feature: f}); }
+ });
+ map.addControl(control);
+
+ control.activate();
+
+ // simulate a touch on a feature
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function() {
+ return feature;
+ };
+ map.events.triggerEvent("touchstart", {type: "touchstart", touches: ['foo']});
+ t.eq(control.feature.id, feature.id,
+ "feature is set on mouse over");
+
+ // simulate a touch outside the feature
+ layer.getFeatureFromEvent = function() {
+ return null;
+ };
+ map.events.triggerEvent("touchstart", {type: "touchstart", touches: ['foo']});
+ t.ok(control.feature == null,
+ "feature is set to null on mouse out");
+ t.ok(control.handlers.drag.stopDown,
+ "drag handler is stopping down again");
+ t.eq(log.length, 1,
+ "onLeave called exactly once");
+ t.eq(log[0].feature.id, feature.id,
+ "onLeave called with expected feature");
+ }
+
+ function test_Control_DragFeature_click(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+ var control = new OpenLayers.Control.DragFeature(layer);
+ map.addControl(control);
+
+ control.activate();
+
+ control.overFeature(feature);
+ control.handlers.feature.evt = {which: 1};
+ control.clickFeature(feature);
+ t.eq(control.handlers.drag.started, false, "click after over does not start drag handler");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/DragPan.html b/misc/openlayers/tests/Control/DragPan.html
new file mode 100644
index 0000000..ba5224f
--- /dev/null
+++ b/misc/openlayers/tests/Control/DragPan.html
@@ -0,0 +1,104 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map, control, layer;
+
+ function init_map() {
+ control = new OpenLayers.Control.DragPan();
+ map = new OpenLayers.Map("map", {controls:[control]});
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ map.zoomIn();
+ control.activate();
+ return [map, control];
+ }
+ function test_Control_DragPan_constructor (t) {
+ t.plan( 1 );
+
+ control = new OpenLayers.Control.DragPan();
+ t.ok( control instanceof OpenLayers.Control.DragPan, "new OpenLayers.Control returns object" );
+ }
+ function test_Control_DragPan_drag (t) {
+ t.plan(4);
+ var data = init_map();
+ map = data[0]; control = data[1];
+ res = map.baseLayer.resolutions[map.getZoom()];
+ t.eq(map.center.lat, 0, "Lat is 0 before drag");
+ t.eq(map.center.lon, 0, "Lon is 0 before drag");
+ map.events.triggerEvent('mousedown', {'type':'mousedown', 'xy':new OpenLayers.Pixel(0,0), 'which':1});
+ map.events.triggerEvent('mousemove', {'type':'mousemove', 'xy':new OpenLayers.Pixel(5,5), 'which':1});
+ map.events.triggerEvent('mouseup', {'type':'mouseup', 'xy':new OpenLayers.Pixel(5,5), 'which':1});
+
+ t.eq(map.getCenter().lat, res * 5, "Lat is " + (res * 5) + " after drag");
+ t.eq(map.getCenter().lon, res * -5, "Lon is " + (res * -5) + " after drag");
+ }
+ function test_Control_DragPan_drag_documentDrag (t) {
+ t.plan(4);
+ control = new OpenLayers.Control.DragPan({documentDrag: true});
+ map = new OpenLayers.Map("map", {controls:[control]});
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ map.zoomIn();
+ control.activate();
+
+ res = map.baseLayer.resolutions[map.getZoom()];
+ t.eq(map.center.lat, 0, "Lat is 0 before drag");
+ t.eq(map.center.lon, 0, "Lon is 0 before drag");
+ map.events.triggerEvent('mousedown', {'type':'mousedown', 'xy':new OpenLayers.Pixel(0,0), 'which':1});
+ map.events.triggerEvent('mousemove', {'type':'mousemove', 'xy':new OpenLayers.Pixel(5,5), 'which':1});
+ map.events.triggerEvent('mouseup', {'type':'mouseup', 'xy':new OpenLayers.Pixel(5,5), 'which':1});
+
+ t.eq(map.getCenter().lat, res * 5, "Lat is " + (res * 5) + " after drag");
+ t.eq(map.getCenter().lon, res * -5, "Lon is " + (res * -5) + " after drag");
+ }
+ function test_Control_DragPan_click(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control.DragPan();
+ var map = new OpenLayers.Map("map", {controls:[control]});
+ var layer = new OpenLayers.Layer.WMS("OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ map.zoomIn();
+ control.activate();
+ map.setCenter = function() {
+ t.ok(false, "map.setCenter should not be called here");
+ };
+ var xy = new OpenLayers.Pixel(0, 0);
+ var down = {
+ 'type': 'mousedown',
+ 'xy': xy,
+ 'which': 1
+ };
+ var move = {
+ 'type': 'mousemove',
+ 'xy': xy,
+ 'which': 1
+ };
+ var up = {
+ 'type': 'mouseup',
+ 'xy': xy,
+ 'which': 1
+ };
+ map.events.triggerEvent('mousedown', down);
+ map.events.triggerEvent('mousemove', move);
+ map.events.triggerEvent('mouseup', up);
+ t.ok(true, "clicking without moving the mouse does not call setCenter");
+ }
+
+
+ </script>
+</head>
+<body>
+ <a id="scale" href="">DragPan</a> <br />
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/DrawFeature.html b/misc/openlayers/tests/Control/DrawFeature.html
new file mode 100644
index 0000000..ef0be5a
--- /dev/null
+++ b/misc/openlayers/tests/Control/DrawFeature.html
@@ -0,0 +1,160 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control.DrawFeature("foo", function() {});
+ t.ok(control instanceof OpenLayers.Control.DrawFeature,
+ "constructor returns an instance");
+ }
+
+ function test_multi(t) {
+ t.plan(4);
+
+ var layer = new OpenLayers.Layer.Vector();
+ var control;
+
+ // multi false by default
+ control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Polygon
+ );
+ t.ok(!control.multi, "control.multi false by default");
+ t.ok(!control.handler.multi, "handler.multi false by default");
+
+ // set on handler
+ control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Polygon, {multi: true}
+ );
+ t.ok(control.handler.multi, "handler.multi set from control options");
+
+ // respect handlerOptions
+ control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Polygon,
+ {multi: true, handlerOptions: {multi: false}}
+ );
+ t.ok(!control.handler.multi, "handlerOptions.multi respected");
+
+ }
+
+ function test_rendererOptions(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map("map");
+ var renderers = ["Canvas", "VML"];
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ renderers: renderers,
+ rendererOptions: {zIndexing: true},
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+
+ var control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Polygon, {autoActivate: true}
+ );
+ map.addControl(control);
+
+ var sketchLayer = control.handler.layer;
+
+ t.eq(sketchLayer.renderers, renderers, "Preferred renderers");
+ t.eq(sketchLayer.rendererOptions.zIndexing, true, "renderer options");
+
+ map.destroy();
+
+ }
+
+ function test_drawFeature(t) {
+ t.plan(3);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.DrawFeature(layer, function() {});
+ var geom = {};
+
+ layer.addFeatures = function(features) {
+ t.ok(features[0].geometry == geom, "layer.addFeatures called");
+ t.eq(features[0].state, OpenLayers.State.INSERT, "layer state set");
+ };
+ function handlefeatureadded(event) {
+ t.ok(event.feature.geometry == geom, "featureadded triggered");
+ }
+ control.events.on({"featureadded": handlefeatureadded});
+ control.drawFeature(geom);
+ control.events.un({"featureadded": handlefeatureadded});
+
+ }
+
+ function test_sketch_events(t) {
+ t.plan(11);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ var control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Path, {
+ handlerOptions: {persist: true}
+ }
+ );
+ map.addLayer(layer);
+ map.addControl(control);
+ map.zoomToMaxExtent();
+
+ var log;
+ layer.events.on({
+ sketchstarted: function(event) {
+ log['sketchstarted'] = event;
+ },
+ sketchmodified: function(event) {
+ log['sketchmodified'] = event;
+ },
+ sketchcomplete: function(event) {
+ log['sketchcomplete'] = event;
+ }
+ });
+
+ // mock up draw/modify of a point
+ log = {};
+ control.activate();
+ t.eq(log, {}, "[activate] no event triggered");
+
+ log = {};
+ map.events.triggerEvent("mousemove", {xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(log.sketchstarted.type, "sketchstarted", "[mousemove] sketchstarted triggered");
+ t.geom_eq(log.sketchstarted.vertex, new OpenLayers.Geometry.Point(-200, 125), "[mousemove] correct vertex");
+ t.eq(log.sketchmodified.type, "sketchmodified", "[mousemove] sketchmodified triggered");
+ t.geom_eq(log.sketchmodified.vertex, new OpenLayers.Geometry.Point(-200, 125), "[mousemove] correct vertex");
+
+ map.events.triggerEvent("mousedown", {xy: new OpenLayers.Pixel(0, 0)});
+
+ log = {};
+ map.events.triggerEvent("mouseup", {xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(log.sketchmodified.type, "sketchmodified", "[mouseup] sketchmodified triggered");
+ t.geom_eq(log.sketchmodified.vertex, new OpenLayers.Geometry.Point(-200, 125), "[mouseup] correct vertex");
+
+ log = {};
+ map.events.triggerEvent("mousemove", {xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(log.sketchmodified.type, "sketchmodified", "[mousemove] sketchmodified triggered");
+ t.geom_eq(log.sketchmodified.vertex, new OpenLayers.Geometry.Point(-190, 115), "[mousemove] correct vertex");
+
+ log = {};
+ map.events.triggerEvent("dblclick", {xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(log.sketchcomplete.type, "sketchcomplete", "[dblclick] sketchcomplete triggered");
+ t.geom_eq(log.sketchcomplete.feature.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-200, 125),
+ new OpenLayers.Geometry.Point(-190, 115)
+ ]),
+ "[dblclick] correct geometry");
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/EditingToolbar.html b/misc/openlayers/tests/Control/EditingToolbar.html
new file mode 100644
index 0000000..570986d
--- /dev/null
+++ b/misc/openlayers/tests/Control/EditingToolbar.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_ctor_draw(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map('map');
+ var vLayer = new OpenLayers.Layer.Vector();
+ map.addLayer(vLayer);
+
+ var editingToolbar = new OpenLayers.Control.EditingToolbar(vLayer, {
+ citeCompliant: "foo"
+ });
+ map.addControl(editingToolbar);
+
+ t.ok(editingToolbar instanceof OpenLayers.Control.EditingToolbar,
+ "new OpenLayers.Control.EditingToolbar returns object" );
+ t.ok(editingToolbar.controls[0] instanceof OpenLayers.Control.Navigation,
+ "EditingToolbar contains Control.Navigation object" );
+ t.eq(editingToolbar.controls[0].active, true,
+ "First control is active" );
+ t.eq(editingToolbar.controls.length, 4,
+ "EditingToolbar contains 4 Controls" );
+ t.eq(editingToolbar.controls[1].handler.citeCompliant, "foo", "citeCompliant option passed to handler correctly")
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Geolocate.html b/misc/openlayers/tests/Control/Geolocate.html
new file mode 100644
index 0000000..4e43f39
--- /dev/null
+++ b/misc/openlayers/tests/Control/Geolocate.html
@@ -0,0 +1,129 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map, control, centerLL
+ watch = null,
+ geolocation= {
+ getCurrentPosition: function(f) {
+ f({
+ coords: { latitude: 10, longitude: 10 }
+ });
+ },
+ watchPosition: function(f) {
+ watch = true;
+ },
+ clearWatch: function() {
+ watch = null;
+ }
+ };
+
+ function test_initialize(t) {
+ t.plan(3);
+ control = new OpenLayers.Control.Geolocate({geolocationOptions: {foo: 'bar'}});
+ t.ok(control instanceof OpenLayers.Control.Geolocate,
+ "new OpenLayers.Control returns object" );
+ t.eq(control.displayClass, "olControlGeolocate", "displayClass is correct" );
+ t.eq(control.geolocationOptions.foo, 'bar',
+ 'provided geolocation options are set in the geolocationOptions prop');
+ }
+ function test_bind(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control.Geolocate({
+ geolocation: geolocation
+ });
+ control.events.register('locationupdated', null, function() {
+ t.ok(true, 'locationupdated event is fired when bound');
+ });
+ map.addControl(control);
+ control.activate();
+ var center = map.getCenter();
+ t.eq(center.lon, 10, 'bound control sets the map lon');
+ t.eq(center.lat, 10, 'bound control sets the map lat');
+ control.deactivate();
+ map.removeControl(control);
+ map.setCenter(centerLL);
+ }
+ function test_unbind(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control.Geolocate({
+ geolocation: geolocation,
+ bind: false
+ });
+ control.events.register('locationupdated', null, function() {
+ t.ok(true, 'locationupdated event is fired when unbound');
+ });
+ map.addControl(control);
+ control.activate();
+ var center = map.getCenter();
+ t.eq(center.lon, 0, 'unbound control doesnt sets the map lon');
+ t.eq(center.lat, 0, 'unbound control doesnt sets the map lat');
+ control.deactivate();
+ map.removeControl(control);
+ map.setCenter(centerLL);
+ }
+ function test_getCurrentLocation(t) {
+ t.plan(5);
+ var control = new OpenLayers.Control.Geolocate({
+ geolocation: geolocation
+ });
+ map.addControl(control);
+ t.eq(control.getCurrentLocation(), false, 'getCurrentLocation return false if control hasnt been activated');
+ control.activate();
+ map.setCenter(centerLL);
+ t.eq(control.getCurrentLocation(), true, 'getCurrentLocation return true if control has been activated');
+ var center = map.getCenter();
+ t.eq(center.lon, 10, 'bound control sets the map lon when calling getCurrentLocation');
+ t.eq(center.lat, 10, 'bound control sets the map lat when calling getCurrentLocation');
+ control.deactivate();
+ map.removeControl(control);
+ map.setCenter(centerLL);
+ var control2 = new OpenLayers.Control.Geolocate({
+ geolocation: geolocation
+ });
+ map.addControl(control2);
+ t.eq(control2.getCurrentLocation(), false, 'getCurrentLocation return false if control is in watch mode');
+ control2.deactivate();
+ map.removeControl(control2);
+ map.setCenter(centerLL);
+ }
+ function test_watch(t) {
+ t.plan(2);
+ var control = new OpenLayers.Control.Geolocate({
+ geolocation: geolocation,
+ watch: true
+ });
+ map.addControl(control);
+ control.activate();
+ t.eq(watch, true, 'watch option makes calls to watchPosition');
+ control.deactivate();
+ t.eq(watch, null, 'deactivate control calls the clearwatch');
+ map.removeControl(control);
+ map.setCenter(centerLL);
+ }
+ function test_destroy(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control.Geolocate({
+ geolocation: geolocation,
+ watch: true
+ });
+ control.activate();
+ control.destroy();
+ t.ok(control.active === false, "control deactivated before being destroyed");
+ }
+
+ function loader() {
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://labs.metacarta.com/wms-c/Basic.py?",
+ {layers: "basic"});
+ map.addLayer(layer);
+ centerLL = new OpenLayers.LonLat(0,0);
+ map.setCenter(centerLL, 5);
+ }
+ </script>
+</head>
+<body onload="loader()">
+ <div id="map" style="width: 256px; height: 256px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/GetFeature.html b/misc/openlayers/tests/Control/GetFeature.html
new file mode 100644
index 0000000..bbdd0e4
--- /dev/null
+++ b/misc/openlayers/tests/Control/GetFeature.html
@@ -0,0 +1,177 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Control_GetFeature_constructor(t) {
+ t.plan(3);
+ var protocol = "foo";
+ var control = new OpenLayers.Control.GetFeature({
+ protocol: protocol
+ });
+ t.ok(control instanceof OpenLayers.Control.GetFeature,
+ "new OpenLayers.Control.SelectFeature returns an instance");
+ t.eq(control.protocol, "foo",
+ "constructor sets protocol correctly");
+
+ control = new OpenLayers.Control.GetFeature({
+ filterType: OpenLayers.Filter.Spatial.INTERSECTS
+ });
+ t.eq(control.filterType, OpenLayers.Filter.Spatial.INTERSECTS,
+ "constructor sets filterType correctly");
+
+ }
+
+ function test_Control_GetFeature_select(t) {
+ t.plan(10);
+ var cssAdded;
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.WMS("foo", "wms", {
+ layers: "foo"
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(1,2));
+ var feature1 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,2));
+ var feature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(2,3));
+ var feature3 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(3,1));
+ var control = new OpenLayers.Control.GetFeature({
+ protocol: new OpenLayers.Protocol({
+ read: function(obj) {
+ cssAdded = OpenLayers.Element.hasClass(map.viewPortDiv,
+ "olCursorWait");
+ obj.callback.call(obj.scope, {
+ features: [feature1, feature2, feature3],
+ success: function() {return true;}
+ });
+ }
+ }),
+ box: true
+ });
+ map.addControl(control);
+
+ var singleTest = function(evt) {
+ t.eq(evt.feature.id, feature1.id, "featureselected callback called with closest feature");
+ }
+ cssAdded = false;
+ control.events.register("featureselected", this, singleTest);
+ control.selectClick({xy: new OpenLayers.Pixel(200, 125)});
+ t.ok(cssAdded,
+ "select adds CSS class (click)");
+ t.ok(!OpenLayers.Element.hasClass(map.viewPortDiv, "olCursorWait"),
+ "callback removes CSS class (click)");
+ control.events.unregister("featureselected", this, singleTest);
+
+ var count = 0;
+ var beforeFeatureSelected = function(evt) {
+ count++;
+ return count < 3;
+ }
+ var features = [];
+ var boxTest = function(evt) {
+ features.push(evt.feature);
+ }
+ var beforeFeaturesSelected = function(evt) {
+ t.eq(evt.features.length, 3, "3 features passed to the beforefeaturesselected handler");
+ }
+ var featuresSelected = function(evt) {
+ t.eq(evt.features.length, 2, "2 features passed to the featuresselected handler");
+ }
+ control.events.register("beforefeatureselected", this, beforeFeatureSelected);
+ control.events.register("featureselected", this, boxTest);
+ control.events.register("beforefeaturesselected", this, beforeFeaturesSelected);
+ control.events.register("featuresselected", this, featuresSelected);
+ cssAdded = false;
+ control.selectBox(new OpenLayers.Bounds(0,0,4,4));
+ control.events.unregister("beforefeatureselected", this, beforeFeatureSelected);
+ control.events.unregister("featureselected", this, boxTest);
+ control.events.unregister("beforefeaturesselected", this, beforeFeaturesSelected);
+ control.events.unregister("featuresselected", this, featuresSelected);
+ t.eq(features.length, 2, "2 features inside box selected");
+ t.eq(features[1].id, feature2.id, "featureselected callback called with multiple features");
+ t.ok(cssAdded,
+ "select adds CSS class (box)");
+ t.ok(!OpenLayers.Element.hasClass(map.viewPortDiv, "olCursorWait"),
+ "callback removes CSS class (box)");
+
+ // allow several features even for single click
+ control.single = false;
+ var multiplePointTest = function(evt) {
+ t.eq(evt.features.length, 3, "3 features passed to the featuresselected handler");
+ }
+ control.events.register("featuresselected", this, multiplePointTest);
+ control.selectClick({xy: new OpenLayers.Pixel(200, 125)});
+ control.events.unregister("featuresselected", this, multiplePointTest);
+ }
+
+ function test_Control_GetFeature_hover(t) {
+ t.plan(9);
+ var cssAdded;
+ var abortedResponse = null;
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.WMS("foo", "wms", {
+ layers: "foo"
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(1,2));
+ var feature1 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,2));
+ var feature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(2,3));
+ var response = new OpenLayers.Protocol.Response();
+ var control = new OpenLayers.Control.GetFeature({
+ protocol: new OpenLayers.Protocol({
+ read: function(obj){
+ cssAdded = OpenLayers.Element.hasClass(map.viewPortDiv,
+ "olCursorWait");
+ obj.callback.call(obj.scope, {
+ features: [feature1, feature2],
+ success: function() {return true;}
+ });
+ return response;
+ },
+ abort: function(response) {
+ abortedResponse = response;
+ }
+ }),
+ hover: true
+ });
+ map.addControl(control);
+
+ var hoverFeature;
+ var hoverTest = function(evt) {
+ t.eq(evt.feature.id, hoverFeature.id, "hoverfeature callback called with closest feature");
+ }
+ var outTest = function(evt) {
+ t.eq(evt.feature.id, feature1.id, "outfeature callback called with previously hovered feature");
+ }
+ control.events.register("hoverfeature", this, hoverTest);
+ control.events.register("outfeature", this, outTest);
+ hoverFeature = feature1;
+ control.selectHover({xy: new OpenLayers.Pixel(200, 125)});
+ t.ok(control.hoverResponse == response,
+ "selectHover stores the protocol response in the hoverResponse property");
+
+ hoverFeature = feature2;
+ cssAdded = false;
+ control.selectHover({xy: new OpenLayers.Pixel(400, 0)});
+ t.ok(cssAdded,
+ "select adds CSS class (hover)");
+ t.ok(!OpenLayers.Element.hasClass(map.viewPortDiv, "olCursorWait"),
+ "callback removes CSS class (hover)");
+
+ OpenLayers.Element.addClass(map.viewPortDiv, "olCursorWait");
+ control.cancelHover();
+ t.ok(abortedResponse == response,
+ "cancelHover calls protocol.abort() with the expected response");
+ t.eq(control.hoverResponse, null,
+ "cancelHover sets this.hoverResponse to null");
+ t.ok(!OpenLayers.Element.hasClass(map.viewPortDiv, "olCursorWait"),
+ "cancelHover removes CSS class");
+
+ control.events.unregister("hoverfeature", this, hoverTest);
+ control.events.unregister("outfeature", this, outTest);
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Graticule.html b/misc/openlayers/tests/Control/Graticule.html
new file mode 100644
index 0000000..4aa867f
--- /dev/null
+++ b/misc/openlayers/tests/Control/Graticule.html
@@ -0,0 +1,66 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script src="http://svn.osgeo.org/metacrs/proj4js/trunk/lib/proj4js-compressed.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(2);
+ var options = {};
+ var map = new OpenLayers.Map("map",{projection:"EPSG:4326"});
+ var layer = new OpenLayers.Layer.WMS();
+ map.addLayers([layer]);
+
+ var control = new OpenLayers.Control.Graticule(options);
+ map.addControl(control);
+ map.zoomToMaxExtent();
+
+ t.ok(control.gratLayer instanceof OpenLayers.Layer.Vector,
+ "constructor sets layer correctly");
+ t.ok(control.gratLayer.features.length > 0,
+ "graticule has features");
+ control.destroy();
+ }
+
+ function test_activate(t) {
+ t.plan(7);
+ var map = new OpenLayers.Map("map",{projection:"EPSG:4326"});
+ var layer = new OpenLayers.Layer.WMS();
+ map.addLayers([layer]);
+
+ var control = new OpenLayers.Control.Graticule({});
+ map.addControl(control);
+ map.zoomToMaxExtent();
+
+ t.ok(control.gratLayer.visibility, "Graticule layer is visible by default");
+ control.deactivate();
+ t.ok(control.gratLayer.map == null,
+ "Graticule layer is not in map when control is deactivated");
+ control.destroy();
+
+ var control2 = new OpenLayers.Control.Graticule({autoActivate:false});
+ map.addControl(control2);
+ t.ok(control2.gratLayer.map == null,
+ "Graticule layer is not in map when autoActivate:false");
+ t.ok(control2.gratLayer.features.length == 0,
+ "Graticule layer is empty when autoActivate:false");
+ control2.activate();
+ t.ok(control2.gratLayer.map != null,
+ "Graticule layer is on map when control is activated");
+ t.ok(control2.gratLayer.features.length > 0,
+ "Graticule features refreshed after control is activated");
+ control2.gratLayer.setVisibility(false);
+
+ control2.destroy();
+ t.ok(control2.gratLayer == null,
+ "Graticule layer is destroyed when control is destroyed");
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/KeyboardDefaults.html b/misc/openlayers/tests/Control/KeyboardDefaults.html
new file mode 100644
index 0000000..e190177
--- /dev/null
+++ b/misc/openlayers/tests/Control/KeyboardDefaults.html
@@ -0,0 +1,173 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_KeyboardDefaults_constructor (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.KeyboardDefaults();
+ t.ok( control instanceof OpenLayers.Control.KeyboardDefaults,
+ "new OpenLayers.Control.KeyboardDefaults returns object" );
+ t.eq( control.displayClass, "olControlKeyboardDefaults", "displayClass is correct" );
+ }
+
+ function test_Control_KeyboardDefaults_destroy (t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control.KeyboardDefaults();
+ map.addControl(control);
+ t.ok(control.handler != null, "control.handler is created");
+ control.destroy();
+ t.ok(control.handler == null, "control.handler is null after destroy");
+ map.destroy();
+ }
+
+ function test_Control_KeyboardDefaults_addControl (t) {
+ t.plan( 4 );
+
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.KeyboardDefaults();
+ t.ok( control instanceof OpenLayers.Control.KeyboardDefaults,
+ "new OpenLayers.Control.KeyboardDefaults returns object" );
+ t.ok( map instanceof OpenLayers.Map,
+ "new OpenLayers.Map creates map" );
+ map.addControl(control);
+ t.ok( control.map === map, "Control.map is set to the map object" );
+ t.ok( OpenLayers.Util.indexOf(map.controls, control), "map.controls contains control" );
+ }
+
+ /* When interpretting
+ * the keycodes below (including the comments associated with them),
+ * consult the URL below. For instance, the Safari browser returns
+ * "IE keycodes", and so is supported by any keycode labeled "IE".
+ *
+ * Very informative URL:
+ * http://unixpapa.com/js/key.html
+ */
+ function test_Control_KeyboardDefaults_KeyDownEvent (t) {
+ t.plan( 25 );
+
+ var evt = {which: 1}, pans = [], zoomIns = 0, zoomOuts = 0;
+
+ map = new OpenLayers.Map('map');
+
+ // mock "pan", "zoomIn" and "zoomOut"
+ map.pan = function(dx, dy) {
+ pans.push({dx: dx, dy: dy});
+ };
+ map.zoomIn = function() {
+ zoomIns++;
+ };
+ map.zoomOut = function() {
+ zoomOuts++;
+ };
+
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+
+ var control = new OpenLayers.Control.KeyboardDefaults({
+ slideFactor: 100
+ });
+ map.addControl(control);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 4);
+
+ // Start new test.
+ evt.keyCode = OpenLayers.Event.KEY_LEFT;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 1, '[KEY_LEFT] pan called once');
+ t.eq(pans[0], {dx: -100, dy: 0},
+ '[KEY LEFT] pan called with expected args');
+
+ evt.keyCode = OpenLayers.Event.KEY_RIGHT;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 2, '[KEY_RIGHT] pan called once');
+ t.eq(pans[1], {dx: 100, dy: 0},
+ '[KEY RIGHT] pan called with expected args');
+
+ evt.keyCode = OpenLayers.Event.KEY_UP;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 3, '[KEY_UP] pan called once');
+ t.eq(pans[2], {dx: 0, dy: -100},
+ '[KEY UP] pan called with expected args');
+
+ evt.keyCode = OpenLayers.Event.KEY_DOWN;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 4, '[KEY_DOWN] pan called once');
+ t.eq(pans[3], {dx: 0, dy: 100},
+ '[KEY DOWN] pan called with expected args');
+
+ evt.keyCode = 33;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 5, '[33] pan called once');
+ t.eq(pans[4], {dx: 0, dy: -384},
+ '[33] pan called with expected args');
+
+ evt.keyCode = 34;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 6, '[34] pan called once');
+ t.eq(pans[5], {dx: 0, dy: 384},
+ '[34] pan called with expected args');
+
+ evt.keyCode = 35;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 7, '[35] pan called once');
+ t.eq(pans[6], {dx: 768, dy: 0},
+ '[35] pan called with expected args');
+
+ evt.keyCode = 36;
+ control.defaultKeyPress(evt);
+ t.eq(pans.length, 8, '[36] pan called once');
+ t.eq(pans[7], {dx: -768, dy: 0},
+ '[36] pan called with expected args');
+
+ evt.keyCode = 43;
+ control.defaultKeyPress(evt);
+ t.eq(zoomIns, 1, '[43] zoomIn called once');
+
+ evt.keyCode = 61;
+ control.defaultKeyPress(evt);
+ t.eq(zoomIns, 2, '[61] zoomIn called once');
+
+ evt.keyCode = 187;
+ control.defaultKeyPress(evt);
+ t.eq(zoomIns, 3, '[187] zoomIn called once');
+
+ evt.keyCode = 107;
+ control.defaultKeyPress(evt);
+ t.eq(zoomIns, 4, '[107] zoomIn called once');
+
+ evt.keyCode = 107;
+ control.defaultKeyPress(evt);
+ t.eq(zoomIns, 5, '[107] zoomIn called once');
+
+ evt.keyCode = 45;
+ control.defaultKeyPress(evt);
+ t.eq(zoomOuts, 1, '[45] zoomOut called once');
+
+ evt.keyCode = 109;
+ control.defaultKeyPress(evt);
+ t.eq(zoomOuts, 2, '[109] zoomOut called once');
+
+ evt.keyCode = 189;
+ control.defaultKeyPress(evt);
+ t.eq(zoomOuts, 3, '[189] zoomOut called once');
+
+ evt.keyCode = 95;
+ control.defaultKeyPress(evt);
+ t.eq(zoomOuts, 4, '[95] zoomOut called once');
+
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/LayerSwitcher.html b/misc/openlayers/tests/Control/LayerSwitcher.html
new file mode 100644
index 0000000..c81a779
--- /dev/null
+++ b/misc/openlayers/tests/Control/LayerSwitcher.html
@@ -0,0 +1,249 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ OpenLayers.Lang.setCode('en');
+
+ function test_Control_LayerSwitcher_constructor (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ t.ok( control instanceof OpenLayers.Control.LayerSwitcher, "new OpenLayers.Control.LayerSwitcher returns object" );
+ t.eq( control.displayClass, "olControlLayerSwitcher", "displayClass is correct" );
+ }
+
+ function test_Control_LayerSwitcher_draw (t) {
+ t.plan( 2 );
+
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+
+ var div = control.draw();
+ t.ok( control.div != null, "draw makes a div" );
+ t.ok( div != null, "draw returns its div" );
+ }
+ function test_Control_LayerSwitcher_outsideViewport (t) {
+ t.plan( 4 );
+
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.LayerSwitcher({'div':OpenLayers.Util.getElement('layerswitcher')});
+ map.addControl(control);
+ t.eq(control.div.style.width, "250px", "Div is not minimized when added.");
+ t.ok(control.events.element && control.events.listeners.buttonclick, "[outside] Events instance attached to div and has buttonclick event");
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+ t.eq(control.div.style.width, "0px", "Div is minimized when added.");
+ t.ok(!control.events.element && map.events.listeners.buttonclick, "[inside] Events instance not attached to div and buttonclick event registered on map");
+ }
+
+ function test_Control_LayerSwitcher_loadContents(t) {
+
+ t.plan( 10 );
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("WMS",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+
+ markers = new OpenLayers.Layer.Markers("markers");
+ map.addLayer(markers);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+
+ t.ok(control.layersDiv != null, "correctly makes layers div");
+ t.ok(OpenLayers.Element.hasClass(control.layersDiv, "layersDiv"),
+ "layers div has class layersDiv");
+ t.ok(control.baseLayersDiv != null, "correctly makes layers div");
+ t.ok(OpenLayers.Element.hasClass(control.baseLayersDiv, "baseLayersDiv"),
+ "base layers div has class baseLayersDiv");
+ t.ok(control.dataLayersDiv != null, "correctly makes layers div");
+ t.ok(OpenLayers.Element.hasClass(control.dataLayersDiv, "dataLayersDiv"),
+ "data layers div has class dataLayersDiv");
+ t.ok(control.maximizeDiv != null, "correctly makes resize div");
+ t.ok(OpenLayers.Element.hasClass(control.maximizeDiv, "maximizeDiv"),
+ "maximize div has class maximizeDiv");
+ t.ok(control.minimizeDiv != null, "correctly makes resize div");
+ t.ok(OpenLayers.Element.hasClass(control.minimizeDiv, "minimizeDiv"),
+ "minimize div has class minmizeDiv");
+ }
+
+
+ function test_Control_LayerSwitcher_redraw (t) {
+
+ t.plan( (OpenLayers.BROWSER_NAME == "opera" ? 9 : 19 ) );
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("WMS",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+
+ markers = new OpenLayers.Layer.Markers("markers");
+ map.addLayer(markers);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+
+ var wmsInput = control.div.childNodes[0].childNodes[1].childNodes[0];
+ t.ok(wmsInput != null, "correctly makes an input for wms layer");
+ t.eq(wmsInput.type, "radio", "wms correctly made a radio button");
+ t.eq(wmsInput.name, control.id + "_baseLayers", "wms correctly named");
+ t.eq(wmsInput.value, layer.name, "wms correctly valued");
+
+ var markersInput = control.div.childNodes[0].childNodes[3].childNodes[0];
+ t.ok(markersInput != null, "correctly makes an input for markers layer");
+ t.eq(markersInput.type, "checkbox", "wms correctly made a radio button");
+ t.eq(markersInput.name, markers.name, "wms correctly named");
+ t.eq(markersInput.value, markers.name, "wms correctly valued");
+
+ t.eq(false, control.checkRedraw(), "check redraw is false");
+ if (OpenLayers.BROWSER_NAME != "opera") {
+ control = new OpenLayers.Control.LayerSwitcher();
+ var myredraw = control.redraw;
+ control.redraw = function() {
+ t.ok(true, "redraw called when setting vis");
+ }
+ map.addControl(control);
+ var func = OpenLayers.Function.bind(myredraw, control);
+ func();
+ markers.setVisibility(false);
+ t.eq(control.checkRedraw(), true, "check redraw is true after changing layer and not letting redraw happen.");
+ map.removeControl(control);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ var myredraw = control.redraw;
+ control.redraw = function() {
+ t.ok(true, "redraw called when setting inRange");
+ }
+ map.addControl(control);
+ var func = OpenLayers.Function.bind(myredraw, control);
+ func();
+ markers.inRange = false;
+ t.eq(control.checkRedraw(), true, "check redraw is true after changing layer.inRange and not letting redraw happen.");
+ map.removeControl(control);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ var myredraw = control.redraw;
+ control.redraw = function() {
+ t.ok(true, "redraw called when raising base layer ");
+ }
+
+ map.addControl(control);
+ var func = OpenLayers.Function.bind(myredraw, control);
+ func();
+ map.raiseLayer(layer, 1);
+ t.eq(control.checkRedraw(), true, "check redraw is true after changing layer.inRange and not letting redraw happen.");
+ map.removeControl(control);
+ } else {
+ t.debug_print("FIXME: Some LayerSwitcher tests fail in Opera.");
+ }
+
+ }
+ function test_Control_LayerSwitcher_ascending (t) {
+
+ t.plan( 4 );
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("WMS",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+
+ markers = new OpenLayers.Layer.Markers("markers");
+ map.addLayer(markers);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+ control2 = new OpenLayers.Control.LayerSwitcher({'ascending':false});
+ map.addControl(control2);
+ t.ok(control.div.childNodes[0].childNodes[0].innerHTML.match("Base Layer"), "Base Layers first in LayerSwitcher with ascending true");
+ t.ok(control.div.childNodes[0].childNodes[2].innerHTML.match("Overlays"), "Overlays in LayerSwitcher with ascending true");
+ t.ok(control2.div.childNodes[0].childNodes[2].innerHTML.match("Base Layer"), "Base Layers last in LayerSwitcher with ascending false");
+ t.ok(control2.div.childNodes[0].childNodes[0].innerHTML.match("Overlays"), "Base Layers last in LayerSwitcher with ascending false");
+ }
+
+ function test_Control_LayerSwitcher_displayInLayerSwitcher (t) {
+
+ t.plan( 2 );
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("WMS",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"}, {'displayInLayerSwitcher': false});
+ map.addLayer(layer);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+ t.eq(control.div.childNodes[0].childNodes[0].style.display, "none" , "Base layer display off when no visble base layer");
+
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("WMS",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+
+ control = new OpenLayers.Control.LayerSwitcher();
+ map.addControl(control);
+ t.eq(control.div.childNodes[0].childNodes[0].style.display, "" , "Base layer display on when visble base layer");
+ }
+
+ // See e.g. https://github.com/openlayers/openlayers/issues/866
+ function test_Control_LayerSwitcher_validIds(t){
+ t.plan(2);
+
+ // setup
+ var layername = "Name with spaces & illegal characters * + ~ ` ' ? )",
+ map = new OpenLayers.Map("map", {
+ controls: [
+ new OpenLayers.Control.LayerSwitcher()
+ ],
+ layers: [
+ new OpenLayers.Layer.WMS(
+ layername,
+ "../../img/blank.gif"
+ ),
+ // add another layer with the same name, the generated id
+ // must be different
+ new OpenLayers.Layer.WMS(
+ layername,
+ "../../img/blank.gif"
+ )
+ ]
+ });
+
+ var baselayerDiv = map.controls[0].div.childNodes[0].childNodes[1],
+ firstGeneratedInputId = baselayerDiv.childNodes[0].id,
+ secondGeneratedInputId = baselayerDiv.childNodes[1].id,
+ // legal ids start with a letter and are followed only by word
+ // characters (letters, digits, and underscores) plus the dash (-)
+ // This is only a subset of all allowed charcters inside of ids.
+ allowedIdChars = (/^[a-zA-Z]{1}[\w-]*$/g);
+
+ // tests
+ // validity
+ t.ok(
+ allowedIdChars.test(firstGeneratedInputId),
+ "id only contains letters, digits, underscores and dashes. It " +
+ "starts with a letter."
+ );
+ // uniqueness
+ t.ok(
+ firstGeneratedInputId !== secondGeneratedInputId,
+ "generated ids are different even for equal layernames"
+ );
+
+ // teardown
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+ <div id="layerswitcher" style="width:250px; height:256px;" />
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Measure.html b/misc/openlayers/tests/Control/Measure.html
new file mode 100644
index 0000000..ee6d192
--- /dev/null
+++ b/misc/openlayers/tests/Control/Measure.html
@@ -0,0 +1,386 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+
+ t.plan(1);
+
+ var map = new OpenLayers.Map("map");
+ var control = new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Path, {persist: true}
+ );
+ map.addControl(control);
+
+ t.eq(control.persist, true, "passing persist to constructor sets persist on handler");
+
+ map.destroy();
+
+ }
+
+ function test_cancel(t) {
+
+ t.plan(4);
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Path, {persist: true}
+ );
+ map.addControl(control);
+
+ control.activate();
+
+ try {
+ control.cancel();
+ t.ok(true, "calling cancel before drawing works");
+ } catch(err) {
+ t.fail("calling cancel before drawing causes trouble: " + err);
+ }
+ t.eq(control.active, true, "control remains active after cancel");
+
+ // create a simple measurement
+ function trigger(type, x, y) {
+ map.events.triggerEvent(type, {
+ xy: new OpenLayers.Pixel(x, y)
+ })
+ };
+
+ trigger("mousemove", 0, 0);
+ // keep a reference to the line being drawn
+ var line = control.handler.line;
+ trigger("mousedown", 0, 0);
+ trigger("mouseup", 0, 0);
+ trigger("mousemove", 10, 10);
+ trigger("mousedown", 10, 10);
+ trigger("mouseup", 10, 10);
+ trigger("dblclick", 10, 10);
+
+ // the geometry is finalized, we first confirm that it is persisted
+ t.ok(line.layer === control.handler.layer, "feature persists");
+
+ // cancel and see that sketch is gone
+ control.cancel();
+ t.eq(line.layer, null, "feature is gone after cancel");
+
+ map.destroy();
+ }
+
+ // test for <http://trac.openlayers.org/ticket/2691>
+ function test_partial(t) {
+
+ t.plan(28);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ units: "m",
+ resolutions: [1],
+ layers: [
+ new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0)
+ });
+
+ var log = [];
+ var control = new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Path, {persist: true,
+ eventListeners: {
+ measurepartial: function(evt) {
+ log.push(evt);
+ },
+ measure: function(evt){
+ log.push(evt);
+ }
+ },
+ handlerOptions: {
+ pixelTolerance: 0,
+ dblclickTolerance: 0
+ }
+ }
+ );
+ map.addControl(control);
+ control.activate();
+
+
+ // convenience function to trigger mouse events
+ function trigger(type, x, y) {
+ map.events.triggerEvent(type, {
+ xy: new OpenLayers.Pixel(x, y)
+ })
+ };
+
+ // delay in seconds
+ var delay = control.partialDelay / 1000;
+
+ // establish first point
+ trigger("mousemove", 0, 0);
+ trigger("mousedown", 0, 0);
+ trigger("mouseup", 0, 0);
+
+
+ // a) move 10 pixels and click
+ trigger("mousemove", 0, 10);
+ trigger("mousedown", 0, 10);
+ trigger("mouseup", 0, 10);
+
+ // confirm measurepartial is not fired before delay
+ t.eq(log.length, 0, "a) no event fired yet")
+
+ t.delay_call(
+ // wait for delay then confirm event was logged
+ delay, function() {
+ t.eq(log.length, 1, "a) event logged")
+ t.eq(log[0] && log[0].type, "measurepartial", "a) event logged");
+ t.eq(log[0] && log[0].measure, 10, "a) correct measure");
+
+ // b) move 10 pixels and click
+ trigger("mousemove", 0, 20);
+ trigger("mousedown", 0, 20);
+ trigger("mouseup", 0, 20);
+
+ // confirm measurepartial is not fired before delay
+ t.eq(log.length, 1, "b) no event fired yet")
+
+ },
+ delay, function() {
+ t.eq(log.length, 2, "b) event logged");
+ t.eq(log[1] && log[1].type, "measurepartial", "b) correct type");
+ t.eq(log[1] && log[1].measure, 20, "b) correct measure");
+
+ // c) move 10 pixels and click
+ trigger("mousemove", 0, 30);
+ trigger("mousedown", 0, 30);
+ trigger("mouseup", 0, 30);
+ },
+ // wait for half delay and confirm event not logged
+ delay / 2, function() {
+ // confirm measurepartial is not fired before delay
+ t.eq(log.length, 2, "c) no event fired yet")
+ },
+ // wait for rest of delay and confirm event logged
+ delay / 2, function() {
+ t.eq(log.length, 3, "c) event logged");
+ t.eq(log[2] && log[2].type, "measurepartial", "c) correct type");
+ t.eq(log[2] && log[2].measure, 30, "c) correct measure");
+
+ // d) move 10 pixels and click
+ trigger("mousemove", 0, 40);
+ trigger("mousedown", 0, 40);
+ trigger("mouseup", 0, 40);
+
+ // confirm measurepartial is not fired before delay
+ t.eq(log.length, 3, "d) no event fired yet")
+
+ // e) double click to finish
+ trigger("dblclick", 0, 40);
+
+ t.eq(log.length, 4, "e) event logged");
+ t.eq(log[3] && log[3].type, "measure", "e) correct type");
+ t.eq(log[3] && log[3].measure, 40, "e) correct measure");
+ },
+ // wait for rest of delay and confirm no measurepartial logged
+ delay, function() {
+ // confirm measurepartial is not fired after dblclick
+ t.eq(log.length, 4, "e) no additional event fired");
+
+ // change to freehand mode and confirm synchronous event dispatch
+ control.handler.freehand = true;
+ // clear log
+ log = [];
+
+ // f) establish first freehand point
+ trigger("mousemove", 0, 0);
+ trigger("mousedown", 0, 0);
+ t.eq(log.length, 0, "f) no event fired yet")
+
+ // g) move 10 pixels
+ trigger("mousemove", 10, 0);
+
+ t.eq(log.length, 1, "g) event logged");
+ t.eq(log[0] && log[0].type, "measurepartial", "g) correct type");
+ t.eq(log[0] && log[0].measure, 10, "g) correct measure");
+
+ // h) move 10 pixels
+ trigger("mousemove", 20, 0);
+
+ t.eq(log.length, 2, "h) event logged");
+ t.eq(log[1] && log[1].type, "measurepartial", "h) correct type");
+ t.eq(log[1] && log[1].measure, 20, "h) correct measure");
+
+ // i) mouse up to finish
+ trigger("mouseup", 20, 0);
+
+ t.eq(log.length, 3, "i) event logged");
+ t.eq(log[2] && log[2].type, "measure", "i) correct type");
+ t.eq(log[2] && log[2].measure, 20, "i) correct measure");
+
+ // j) clean up
+ log = [];
+ map.destroy();
+ },
+ // wait for delay and confirm event not logged
+ delay, function() {
+ t.eq(log.length, 0, "j) no event fired after destroy");
+ }
+ );
+
+ }
+
+ function test_immediate(t) {
+ t.plan(32);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ units: "m",
+ resolutions: [1],
+ layers: [
+ new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0)
+ });
+
+ var log = [];
+ var control = new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Path, {
+ persist: true,
+ immediate: true,
+ eventListeners: {
+ measurepartial: function(evt) {
+ log.push(evt);
+ },
+ measure: function(evt){
+ log.push(evt);
+ }
+ },
+ handlerOptions: {
+ pixelTolerance: 0,
+ dblclickTolerance: 0
+ }
+ }
+ );
+ map.addControl(control);
+ control.activate();
+
+ // convenience function to trigger mouse events
+ function trigger(type, x, y) {
+ map.events.triggerEvent(type, {
+ xy: new OpenLayers.Pixel(x, y)
+ })
+ };
+
+ // delay in seconds
+ var delay = control.partialDelay / 1000;
+
+ // a) establish first point
+ trigger("mousemove", 0, 0);
+ trigger("mousedown", 0, 0);
+ trigger("mouseup", 0, 0);
+
+ // move 10 pixels
+ trigger("mousemove", 0, 10);
+
+ t.eq(log.length, 1, "a) has fired an event");
+
+ t.delay_call(
+ delay, function() {
+ // confirm measurepartial is fired
+ t.eq(log.length, 1, "a) one event logged");
+ t.ok(log[0] && log[0].type == "measurepartial", "a) correct type");
+ // mousemove within the partialDelay fires no event, so the
+ // measure below is the one of the initial point
+ t.eq(log[0]?log[0].measure:-1 , 10, "a) correct measure");
+
+ // b) move 10 pixels
+ trigger("mousemove", 0, 20);
+ // c) move 10 pixels again
+ trigger("mousemove", 0, 30);
+
+ // confirm measurepartial is fired 2 times
+ t.eq(log.length, 3, "b) event logged");
+ t.eq(log[1] && log[1].type, "measurepartial", "b) correct type");
+ t.eq(log[1] && log[1].measure, 20, "b) correct measure");
+ t.eq(log[2] && log[2].type, "measurepartial", "c) correct type");
+ t.eq(log[2] && log[2].measure, 30, "c) correct measure");
+
+ // d) switch immediate measurement off
+ control.setImmediate(false);
+ t.eq(control.immediate, false, "d) immediate is false");
+
+ // e) move 10 pixels and click
+ trigger("mousemove", 0, 40);
+ trigger("mousedown", 0, 40);
+ trigger("mouseup", 0, 40);
+ // confirm measurepartial is not fired before delay
+ t.eq(log.length, 3, "e) no event fired yet")
+ },
+ // wait for delay then confirm event was logged
+ delay, function() {
+ t.eq(log.length, 4, "e) event logged")
+ t.ok(log[3] && log[3].type == "measurepartial", "e) correct type");
+ t.ok(log[3] && log[3].measure == 40, "e) correct measure");
+
+ // f) switch immediate measurement on
+ control.setImmediate(true);
+ t.eq(control.immediate, true, "f) immediate is true");
+
+ // g) move 10 pixels
+ trigger("mousemove", 0, 50);
+ },
+ delay, function() {
+ t.eq(log.length, 5, "g) event logged");
+ t.ok(log[4] && log[4].type == "measurepartial", "g) correct type");
+ t.ok(log[4] && log[4].measure == 50, "g) correct measure");
+
+ // h) move 10 pixels
+ trigger("mousemove", 0, 60);
+
+ t.eq(log.length, 6, "h) event logged");
+ t.ok(log[5] && log[5].type == "measurepartial", "h) correct type");
+ t.ok(log[5] && log[5].measure == 60, "h) correct measure");
+
+ // i) double click to finish
+ trigger("mousedown", 0, 60);
+ t.eq(log.length, 7, "i) event logged");
+ t.eq(log[6] && log[6].type, "measurepartial", "i) correct type");
+ t.eq(log[6] && log[6].measure, 60, "i) correct measure");
+ trigger("mouseup", 0, 60);
+ t.eq(log.length, 7, "i) no event fired yet");
+ },
+ delay, function() {
+ t.eq(log.length, 8, "j) event logged");
+ t.eq(log[7] && log[7].type, "measurepartial", "j) correct type");
+ t.eq(log[7] && log[7].measure, 60, "j) correct measure");
+
+ trigger("dblclick", 0, 60);
+ t.eq(log.length, 9, "k) event logged");
+ t.eq(log[8] && log[8].type, "measure", "k) correct type");
+ t.eq(log[8] && log[8].measure, 60, "k) correct measure");
+ // clear log
+ log = [];
+
+ // l) clean up
+ map.destroy();
+ // wait for delay and confirm event not logged
+ },
+ delay, function() {
+ t.eq(log.length, 0, "l) no event fired after destroy");
+ }
+ );
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 512px; height: 256px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/ModifyFeature.html b/misc/openlayers/tests/Control/ModifyFeature.html
new file mode 100644
index 0000000..6226733
--- /dev/null
+++ b/misc/openlayers/tests/Control/ModifyFeature.html
@@ -0,0 +1,828 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+ var layer = {
+ styleMap: {createSymbolizer: function(){}},
+ events: {
+ on: function() {},
+ un: function() {}
+ }
+ };
+ var options = {
+ documentDrag: true
+ };
+ var control = new OpenLayers.Control.ModifyFeature(layer, options);
+
+ t.ok(control.layer == layer,
+ "constructor sets layer correctly");
+ t.eq(control.handlers.drag.documentDrag, true,
+ "constructor sets options correctly on drag handler");
+ t.eq(control.mode, OpenLayers.Control.ModifyFeature.RESHAPE,
+ "constructor initializes modification mode correctly");
+ control.destroy();
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ control.destroy();
+ t.eq(control.layer, null, "Layer reference removed on destroy.");
+ map.destroy();
+ }
+
+ function test_activate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ t.ok(!control.handlers.drag.active,
+ "drag handler is not active prior to activating control");
+ control.activate();
+ t.ok(control.handlers.drag.active,
+ "drag handler is active after activating control");
+
+ map.destroy();
+ }
+
+ function test_initDeleteCodes(t) {
+ t.plan(3);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.ModifyFeature(layer, {'deleteCodes': 46});
+ t.eq(control.deleteCodes[0], 46, "Delete code properly turned into an array.");
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ t.eq(control.deleteCodes[0], 46, "Default deleteCodes include delete");
+ t.eq(control.deleteCodes[1], 68, "Default deleteCodes include 'd'");
+
+ control.destroy();
+ layer.destroy();
+ }
+
+ function test_handleKeypress(t) {
+ t.plan(16);
+
+ /**
+ * There are two things that we want to test here
+ * 1) test that control.deleteCodes are respected
+ * 3) test that a vertex is properly deleted
+ *
+ * In the future, feature deletion may be added to the control.
+ */
+
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ var delKey = 46;
+ var dKey = 100;
+ control.deleteCodes = [delKey, dKey];
+
+ // test that a polygon vertex is deleted for all delete codes
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point()
+ );
+ var poly = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon()
+ );
+
+ // mock up vertex deletion
+ var origGetFeatureFromEvent = layer.getFeatureFromEvent;
+ layer.getFeatureFromEvent = function() { return point; };
+ control.feature = poly;
+ // we cannot use selectFeature since the control is not part of a map
+ control._originalGeometry = poly.geometry.clone();
+ control.vertices = [point];
+ point.geometry.parent = {
+ removeComponent: function(geometry) {
+ t.eq(geometry.id, point.geometry.id,
+ "vertex deletion: removeComponent called on parent with proper geometry");
+ }
+ };
+ layer.events.on({
+ "featuremodified": function(event) {
+ t.ok(event.feature.modified !== null, "modified property of feature should have been set");
+ t.eq(event.feature.id, poly.id, "vertex deletion: featuremodifed triggered");
+ },
+ "vertexremoved": function(evt) {
+ layer.events.unregister("vertexremoved", this, arguments.callee);
+ t.eq(evt.feature.id, poly.id, "vertexremoved triggered with correct feature");
+ t.eq(evt.vertex.id, point.geometry.id, "vertexremoved triggered with correct vertex");
+ t.eq(evt.pixel, "foo", "vertexremoved triggered with correct pixel");
+ }
+ });
+ layer.drawFeature = function(feature) {
+ t.eq(feature.id, poly.id,
+ "vertex deletion: drawFeature called with the proper feature");
+ };
+ control.resetVertices = function() {
+ t.ok(true, "vertex deletion: resetVertices called");
+ };
+ control.onModification = function(feature) {
+ t.eq(feature.id, poly.id,
+ "vertex deletion: onModification called with the proper feature");
+ };
+ // run the above four tests twice
+ control.handleKeypress({keyCode:delKey, xy: "foo"});
+ control.handleKeypress({keyCode:dKey});
+ t.eq(control.feature.state, OpenLayers.State.UPDATE, "feature state set to update");
+
+ // now make sure nothing happens if the vertex is mid-drag
+ control.handlers.drag.dragging = true;
+ control.handleKeypress({keyCode:delKey});
+
+ // clean up
+ layer.getFeatureFromEvent = origGetFeatureFromEvent;
+ control.destroy();
+ layer.destroy();
+ }
+
+
+ function test_onUnSelect(t) {
+ t.plan(5);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ var fakeFeature = {'id':'myid'};
+ control.vertices = 'a';
+ control.virtualVertices = 'b';
+ control.features = true;
+ layer.events.on({"afterfeaturemodified": function(event) {
+ t.eq(event.feature, fakeFeature, "afterfeaturemodified triggered");
+ }});
+ control.onModificationEnd = function (feature) { t.eq(feature.id, fakeFeature.id, "onModificationEnd got feature.") }
+ layer.removeFeatures = function(verts) {
+ t.ok(verts == 'a', "Normal verts removed correctly");
+ }
+ layer.destroyFeatures = function(verts) {
+ t.ok(verts == 'b', "Virtual verts destroyed correctly");
+ }
+ control.unselectFeature(fakeFeature);
+ t.eq(control.feature, null, "feature is set to null");
+
+ layer.destroyFeatures = function() {};
+ control.destroy();
+ layer.destroy();
+ }
+
+ function test_stop_modification(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Vector("Vectors!", {isBaseLayer: true});
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0)
+ );
+ layer.addFeatures([feature]);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+
+
+ // If a feature is to be modified, control.selectFeature gets called.
+ // We want this test to fail if selectFeature gets called.
+ var modified = false;
+
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+
+ // register a listener that will stop feature modification
+ layer.events.on({"beforefeaturemodified": function() {return false}});
+
+ // we can initiate feature modification by programmatically selecting
+ // a feature
+ control.selectFeature(feature);
+
+ if(modified) {
+ t.fail("selectFeature called, prepping feature for modification");
+ } else {
+ t.ok(true, "the beforefeaturemodified listener stopped feature modification");
+ }
+ }
+
+ function test_selectFeature(t) {
+ t.plan(12);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Vector("Vectors!", {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ control.vertices = [];
+ control.virtualVertices = [];
+ var callback = function(obj) {
+ t.ok(obj.feature == fakeFeature, "beforefeaturemodified triggered");
+ };
+ layer.events.on({"beforefeaturemodified": callback});
+ control.onModificationStart = function(feature) { t.eq(feature.id, fakeFeature.id, "On Modification Start called with correct feature."); }
+
+ // Start of testing
+
+ control.collectVertices = function() { t.fail("Collect vertices called when geom is a point"); }
+ var fakeFeature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 0));
+
+ // Points don't call collectVertices
+ control.selectFeature(fakeFeature);
+ control.unselectFeature(fakeFeature);
+
+ control.collectVertices = function() {
+ t.ok(true, "collectVertices called");
+ this.vertices = 'a';
+ this.virtualVertices = 'd';
+ layer.addFeatures(this.vertices);
+ layer.addFeatures(this.virtualVertices);
+ }
+
+ layer.addFeatures = function(features) {
+ t.ok(features == 'a' || features == 'd', "features passed correctly");
+ }
+ layer.destroyFeatures = function() {};
+
+ fakeFeature.geometry = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(1, 1)
+ ])
+ ]);
+
+ // OnSelect calls collectVertices and passes features to layer
+ control.selectFeature(fakeFeature);
+ control.unselectFeature(fakeFeature);
+ layer.destroyFeatures = OpenLayers.Layer.Vector.prototype.destroyFeatures;
+
+ control.vertices = ['a'];
+ control.virtualVertices = [{destroy: function() {}}];
+
+ layer.addFeatures = function(features) {}
+
+ layer.removeFeatures = function(features) {
+ t.eq(features.length, 1, "Correct feature length passed in");
+ }
+
+ // Features are removed whenever they exist
+ control.selectFeature(fakeFeature);
+
+ control.destroy();
+
+ // layer.destroy() will call removeFeatures with an empty array, make
+ // removeFeatures reference an empty function to prevent the above
+ // test to fail
+ layer.removeFeatures = function(features) {};
+ layer.destroy();
+ }
+
+ function test_resetVertices(t) {
+ t.plan(20);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ var point = new OpenLayers.Geometry.Point(5,6);
+ var point2 = new OpenLayers.Geometry.Point(7,8);
+ var point3 = new OpenLayers.Geometry.Point(9,10);
+
+ control.feature = new OpenLayers.Feature.Vector(point);
+ control.resetVertices();
+ t.eq(control.vertices.length, 0, "Correct vertices length");
+ t.eq(control.virtualVertices.length, 0, "Correct virtual vertices length.");
+
+ var multiPoint = new OpenLayers.Geometry.MultiPoint([point, point2]);
+ control.feature = new OpenLayers.Feature.Vector(multiPoint);
+ control.resetVertices();
+ t.eq(control.vertices.length, 2, "Correct vertices length with multipoint");
+ t.eq(control.virtualVertices.length, 0, "Correct virtual vertices length (multipoint).");
+
+ var line = new OpenLayers.Geometry.LineString([point, point2]);
+ control.feature = new OpenLayers.Feature.Vector(line);
+ control.resetVertices();
+ t.eq(control.vertices.length, 2, "Correct vertices length with line");
+ t.eq(control.virtualVertices.length, 1, "Correct virtual vertices length (linestring).");
+
+ var polygon = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing([point, point2, point3])]);
+ control.feature = new OpenLayers.Feature.Vector(polygon);
+ control.resetVertices();
+ t.eq(control.vertices.length, 3, "Correct vertices length with polygon");
+ t.eq(control.virtualVertices.length, 3, "Correct virtual vertices length (polygon).");
+
+ control.mode = OpenLayers.Control.ModifyFeature.DRAG;
+ control.resetVertices();
+ t.ok(control.dragHandle != null, "Drag handle is set");
+ t.eq(control.vertices.length, 0, "Correct vertices length with polygon (DRAG)");
+
+ control.mode = OpenLayers.Control.ModifyFeature.ROTATE;
+ control.resetVertices();
+ t.ok(control.radiusHandle != null, "Radius handle is set");
+ t.eq(control.vertices.length, 0, "Correct vertices length with polygon (ROTATE)");
+
+ control.mode = OpenLayers.Control.ModifyFeature.RESIZE;
+ control.resetVertices();
+ t.ok(control.radiusHandle != null, "Radius handle is set");
+ t.eq(control.vertices.length, 0, "Correct vertices length with polygon (RESIZE)");
+
+ control.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+ control.resetVertices();
+ t.ok(control.radiusHandle == null, "Radius handle is not set (RESHAPE)");
+ t.eq(control.vertices.length, 3, "Correct vertices length with polygon (RESHAPE)");
+ t.eq(control.virtualVertices.length, 3, "Correct virtual vertices length (RESHAPE)");
+
+ control.mode = OpenLayers.Control.ModifyFeature.RESIZE | OpenLayers.Control.ModifyFeature.RESHAPE;
+ control.resetVertices();
+ t.ok(control.radiusHandle != null, "Radius handle is set (RESIZE|RESHAPE)");
+ t.eq(control.vertices.length, 0, "No vertices when both resizing and reshaping (RESIZE|RESHAPE)");
+ t.eq(control.virtualVertices.length, 0, "No virtual vertices when both resizing and reshaping (RESIZE|RESHAPE)");
+
+ control.destroy();
+ layer.destroy();
+ }
+
+ function test_dragVertex(t) {
+ t.plan(8);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+
+ map.zoomToMaxExtent();
+
+ var log = {};
+ layer.events.on({
+ "vertexmodified": function(event) {
+ log.event = event;
+ }
+ });
+
+ // pretend to drag a point
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0)
+ );
+ control.feature = feature;
+ var pixel = new OpenLayers.Pixel(-100, 100);
+ control.dragVertex(feature, pixel);
+ t.eq(log.event.type, "vertexmodified", "[drag point] vertexmodified triggered");
+ t.geom_eq(log.event.vertex, feature.geometry, "[drag point] listeners receive correct vertex");
+ t.eq(log.event.feature.id, feature.id, "[drag point] listeners receive correct feature");
+ t.ok(log.event.pixel === pixel, "[drag point] listeners receive correct pixel");
+
+ // pretend to drag vertex of a linestring
+ var vert = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0)
+ );
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([
+ vert.geometry, new OpenLayers.Geometry.Point(10, 0)
+ ])
+ );
+ control.feature = feature;
+ var pixel = new OpenLayers.Pixel(-100, 100);
+ control.dragVertex(vert, pixel);
+ t.eq(log.event.type, "vertexmodified", "[drag vertex] vertexmodified triggered");
+ t.geom_eq(log.event.vertex, vert.geometry, "[drag vertex] listeners receive correct vertex");
+ t.eq(log.event.feature.id, feature.id, "[drag vertex] listeners receive correct feature");
+ t.ok(log.event.pixel === pixel, "[drag vertex] listeners receive correct pixel");
+
+
+ map.destroy();
+ }
+ function test_collectDragHandle(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,1));
+ layer.addFeatures([feature]);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+ control.feature = feature;
+ control.collectDragHandle();
+ t.ok(control.dragHandle != null, "Drag handle created");
+ t.ok(control.dragHandle._sketch == true, "Handle has _sketch true");
+ t.ok(control.dragHandle.renderIntent == control.vertexRenderIntent,"Render intent for handle set");
+ t.ok(control.layer.getFeatureById(control.dragHandle.id) != null, "Drag handle added to layer");
+ }
+ function test_collectRadiusHandle(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,1));
+ layer.addFeatures([feature]);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+ control.feature = feature;
+ control.collectRadiusHandle();
+ t.ok(control.radiusHandle != null, "Radius handle created");
+ t.ok(control.radiusHandle._sketch == true, "Radius has _sketch true");
+ t.ok(control.radiusHandle.renderIntent == control.vertexRenderIntent,"Render intent for handle set");
+ t.ok(control.layer.getFeatureById(control.radiusHandle.id) != null, "Drag radius added to layer");
+ }
+ function test_onDrag(t) {
+ t.plan(1);
+ t.ok(true, "onDrag not tested yet.");
+ }
+
+ function test_dragComplete(t) {
+ t.plan(8);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+
+ var fakeFeature = {
+ 'geometry': { 'id':'myGeom'},
+ 'id': 'fakeFeature'
+ };
+ layer.addFeatures = function (verts) {
+ t.ok(verts == 'virtual' || verts == 'normal', verts + " verts correct");
+ }
+ layer.removeFeatures = function (verts) {
+ t.ok(verts == 'previous virtual' || verts == 'previous normal', verts + " verts correct");
+ }
+ layer.events.on({"featuremodified": function(event) {
+ t.eq(event.feature, fakeFeature, "featuremodified triggered");
+ }});
+ control.onModification = function(feat) {
+ t.eq(feat.id, fakeFeature.id, "onModification gets correct feat");
+ }
+ control.collectVertices = function() {
+ t.ok(true, "collectVertices called");
+ this.vertices = 'normal';
+ this.virtualVertices = 'virtual';
+ layer.addFeatures(this.vertices);
+ layer.addFeatures(this.virtualVertices);
+ }
+ control.feature = fakeFeature;
+ control.vertices = 'previous normal';
+ control.virtualVertices = 'previous virtual';
+ control.dragComplete();
+ t.eq(fakeFeature.state, OpenLayers.State.UPDATE, "feature state set to UPDATE");
+
+ control.destroy();
+
+ // layer.destroy() will call removeFeatures with an empty array, make
+ // removeFeatures reference an empty function to prevent the above
+ // test to fail
+ layer.removeFeatures = function(verts) {};
+ layer.destroy();
+ }
+
+ function test_deactivate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+
+ control.handlers.keyboard.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on keyboard handler");
+ }
+ control.handlers.drag.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on drag handler");
+ }
+ control.active = true;
+ control.deactivate();
+
+ control.handlers.keyboard.deactivate = OpenLayers.Handler.Keyboard.prototype.deactivate;
+ control.handlers.drag.deactivate = OpenLayers.Handler.Drag.prototype.deactivate;
+ map.destroy();
+ }
+
+ function test_onModificationStart(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector(null, {
+ styleMap: new OpenLayers.StyleMap({
+ "vertex": new OpenLayers.Style({foo: "bar"})
+ }, {extendDefault: false})
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+
+ // make sure onModificationStart is called on feature selection
+ var testFeature = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("LINESTRING(3 4,10 50,20 25)")
+ );
+ layer.addFeatures([testFeature]);
+ control.onModificationStart = function(feature) {
+ t.eq(feature.id, testFeature.id,
+ "onModificationStart called with the right feature");
+ };
+ control.selectFeature(testFeature);
+
+ // make sure styles are set correctly from default style
+ t.eq(control.virtualStyle, OpenLayers.Util.applyDefaults({
+ strokeOpacity: 0.3,
+ fillOpacity: 0.3
+ }, OpenLayers.Feature.Vector.style["default"]), "virtual style set correctly");
+ var vertex = layer.features[layer.features.length-1];
+ t.eq(vertex.renderIntent, null, "vertex style set correctly - uses default style");
+ control.unselectFeature(testFeature);
+
+ // make sure styles are set correctly with vertexRenderIntent
+ control = new OpenLayers.Control.ModifyFeature(layer, {vertexRenderIntent: "vertex"});
+ map.addControl(control);
+ control.activate();
+ control.selectFeature(testFeature);
+ t.eq(control.virtualStyle, {
+ strokeOpacity: 0.3,
+ fillOpacity: 0.3,
+ foo: "bar"
+ }, "virtual style set correctly");
+ var vertex = layer.features[layer.features.length-1];
+ t.eq(vertex.renderIntent, "vertex", "vertex style set correctly - uses 'vertex' renderIntent");
+ control.unselectFeature(testFeature);
+
+ map.destroy();
+ }
+
+ function test_onModification(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+
+ // make sure onModification is called on drag complete
+ var point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(Math.random(), Math.random())
+ );
+ control.feature = point;
+ control.onModification = function(feature) {
+ t.eq(feature.id, point.id,
+ "onModification called with the right feature on drag complete");
+ };
+ control.dragComplete();
+
+ // make sure onModification is called on vertex deletion
+ var poly = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon()
+ );
+ var oldDraw = layer.drawFeature;
+ layer.drawFeature = function() {
+ return;
+ };
+ control.feature = poly;
+ control.vertices = [point];
+ layer.events.on({"featuremodified": function(event) {
+ t.eq(event.feature.id, poly.id, "featuremodified triggered");
+ }});
+
+ control.onModification = function(feature) {
+ t.eq(feature.id, poly.id,
+ "onModification called with the right feature on vertex delete");
+ };
+ point.geometry.parent = poly.geometry;
+ origGetFeatureFromEvent = layer.getFeatureFromEvent;
+ layer.getFeatureFromEvent = function() { return point; };
+ control.handleKeypress({keyCode:46});
+ layer.drawFeature = oldDraw;
+ layer.getFeatureFromEvent = origGetFeatureFromEvent;
+
+ map.destroy();
+ }
+
+ function test_onModificationEnd(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+
+ // make sure onModificationEnd is called on unselect feature
+ var testFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(Math.random(), Math.random())
+ );
+ layer.events.on({"afterfeaturemodified": function(event) {
+ t.eq(event.feature.id, testFeature.id, "afterfeaturemodified triggered");
+ t.eq(event.modified, false, "afterfeaturemodified event given proper modified property (false - feature was not modified in this case)");
+ }});
+ control.onModificationEnd = function(feature) {
+ t.eq(feature.id, testFeature.id,
+ "onModificationEnd called with the right feature");
+ };
+ control.unselectFeature(testFeature);
+
+ map.destroy();
+ }
+
+ function test_events(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer);
+ map.addControl(control);
+ control.activate();
+
+ // make sure onModificationStart is called on feature selection
+ var testFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(Math.random(), Math.random())
+ );
+
+ // test that beforefeatureselected is triggered
+ function handle_beforefeatureselected(event) {
+ t.ok(event.feature == testFeature, "beforefeatureselected called with the correct feature");
+ }
+ layer.events.on({
+ "beforefeatureselected": handle_beforefeatureselected
+ });
+ layer.events.triggerEvent("beforefeatureselected", {
+ feature: testFeature
+ });
+ layer.events.un({
+ "beforefeatureselected": handle_beforefeatureselected
+ });
+
+ // test that beforefeatureselected is triggered
+ function handle_featureselected(event) {
+ t.ok(event.feature == testFeature, "featureselected called with the correct feature");
+ }
+ layer.events.on({
+ "featureselected": handle_featureselected
+ });
+ layer.events.triggerEvent("featureselected", {
+ feature: testFeature
+ });
+ layer.events.un({
+ "featureselected": handle_featureselected
+ });
+
+ map.destroy();
+ }
+
+ function test_standalone(t) {
+
+ t.plan(17);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+
+ var f1 = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("LINESTRING(3 4,10 50,20 25)")
+ );
+ var f2 = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("POLYGON((1 1,5 1,5 5,1 5,1 1),(2 2, 3 2, 3 3, 2 3,2 2))")
+ );
+ var f3 = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("POINT(10 15)")
+ );
+ var f4 = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("POINT(15 10)")
+ );
+ layer.addFeatures([f1, f2, f3, f4]);
+
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.ModifyFeature(layer, {standalone: true});
+ map.addControl(control);
+
+ var log = [];
+ layer.events.on({
+ beforefeaturemodified: function(evt) {
+ layer.events.unregister("beforefeaturemodified", this, arguments.callee);
+ log.push(evt);
+ },
+ featuremodified: function(evt) {
+ log.push(evt);
+ },
+ afterfeaturemodified: function(evt) {
+ log.push(evt);
+ }
+ });
+
+ // activate control
+ control.activate();
+ t.eq(control.active, true, "[activate] control activated");
+
+ // manually select feature for editing
+ control.selectFeature(f1);
+ t.eq(log.length, 1, "[select f1] beforefeaturemodified triggered");
+ t.ok(control.feature === f1, "[select f1] control.feature set to f1");
+ log = []
+
+ // manually unselect feature for editing
+ control.unselectFeature(f1);
+ t.eq(control.feature, null, "[unselect f1] control.feature set to null");
+ t.eq(log.length, 1, "[unselect f1] event logged");
+ t.eq(log[0].type, "afterfeaturemodified", "[unselect f1] afterfeaturemodified triggered");
+ t.ok(log[0].feature === f1, "[unselect f1] correct feature");
+ t.eq(log[0].modified, false, "[unselect f1] feature not actually modified");
+
+ // clear log and select new feature for editing
+ log = [];
+ control.selectFeature(f2);
+ t.ok(control.feature === f2, "[select f2] control.feature set to f2");
+
+ // deactivate control and confirm feature is unselected
+ control.deactivate();
+ t.eq(log.length, 1, "[deactivate] event logged");
+ t.eq(log[0].type, "afterfeaturemodified", "[deactivate] afterfeaturemodified triggered");
+ t.ok(log[0].feature === f2, "[deactivate] correct feature");
+ t.eq(log[0].modified, false, "[deactivate] feature not actually modified");
+
+ // select the polygon feature to make sure that we can drag vertices and
+ // virtual vertices
+ control.selectFeature(f2);
+ var origGetFeatureFromEvent = layer.getFeatureFromEvent;
+ layer.getFeatureFromEvent = function() { return control.vertices[0]; };
+ control.handlers.drag.callbacks.down.call(control, new OpenLayers.Pixel(0,0));
+ t.ok(control.vertex === control.vertices[0], "can drag vertex of feature f2");
+ t.ok(control.feature === f2, "dragging a vertex does not change the selected feature");
+ layer.getFeatureFromEvent = function() { return control.virtualVertices[0]; };
+ control.handlers.drag.callbacks.down.call(control, new OpenLayers.Pixel(0,0));
+ t.ok(control.vertex === control.virtualVertices[0], "can drag virtual vertex of feature f2");
+ t.ok(control.feature === f2, "dragging a vertex does not change the selected feature");
+ layer.getFeatureFromEvent = origGetFeatureFromEvent;
+ control.deactivate();
+
+ map.destroy();
+
+ }
+
+ function test_setFeatureState(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector("vector", {isBaseLayer: true});
+ map.addLayer(layer);
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,2));
+ layer.addFeatures([feature]);
+ var control = new OpenLayers.Control.ModifyFeature(layer, {standalone: true});
+ map.addControl(control);
+
+ control.selectFeature(feature);
+ var originalGeometry = feature.geometry;
+
+ t.ok(control._originalGeometry, "original geometry stored for later use in setFeatureState");
+
+ feature.geometry = new OpenLayers.Geometry.Point(2,3);
+ control.modified = true;
+ control.setFeatureState();
+
+ t.eq(feature.state, OpenLayers.State.UPDATE, "feature state set to UPDATE");
+ t.geom_eq(feature.modified.geometry, originalGeometry, "original geometry stored on the modified property");
+ t.eq(control._originalGeometry, undefined, "original geometry deleted once it is set on the modified property");
+ }
+
+ function test_createVertices(t) {
+ t.plan(2);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.ModifyFeature(layer, {
+ createVertices: false
+ });
+ var line = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(7, 8),
+ new OpenLayers.Geometry.Point(9, 10)
+ ]);
+ control.feature = new OpenLayers.Feature.Vector(line);
+ control.resetVertices();
+
+ t.eq(control.vertices.length, 3, "Correct vertices length with createVertices is false");
+ t.eq(control.virtualVertices.length, 0, "Correct virtual vertices length with createVertices is false");
+ control.destroy();
+ }
+
+ function test_moveLayerToTop_moveLayerBack(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector();
+ var layer2 = new OpenLayers.Layer.Vector();
+ map.addLayers([layer1, layer2]);
+ var control = new OpenLayers.Control.ModifyFeature(layer1);
+ map.addControl(control);
+ control.activate();
+ t.ok(layer1.div.style.zIndex > layer2.div.style.zIndex, "layer raised so events don't get swallowed");
+ control.deactivate();
+ t.ok(layer1.div.style.zIndex < layer2.div.style.zIndex, 'layer order restored on deactivation');
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/MousePosition.html b/misc/openlayers/tests/Control/MousePosition.html
new file mode 100644
index 0000000..0695e16
--- /dev/null
+++ b/misc/openlayers/tests/Control/MousePosition.html
@@ -0,0 +1,109 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map, control;
+ function test_initialize (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.MousePosition();
+ t.ok( control instanceof OpenLayers.Control.MousePosition, "new OpenLayers.Control returns object" );
+ t.eq( control.displayClass, "olControlMousePosition", "displayClass is correct" );
+ }
+ function test_destroy(t) {
+ t.plan(1);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control.MousePosition();
+ map.addControl(control);
+
+ var listeners = map.events.listeners.mousemove.length;
+ control.destroy();
+
+ t.eq(map.events.listeners.mousemove.length, listeners - 1, "mousemove event is unregistered");
+ map.destroy();
+ }
+ function test_addControl(t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control.MousePosition();
+ map.addControl(control);
+
+ t.ok(control.map === map, "Control.map is set to the map object");
+ t.ok(map.controls[map.controls.length - 1] === control, "map.controls contains control");
+ t.eq(parseInt(control.div.style.zIndex), map.Z_INDEX_BASE['Control'] + 5, "Control div zIndexed properly" );
+ t.eq(parseInt(map.viewPortDiv.lastChild.style.zIndex), map.Z_INDEX_BASE['Control'] + 5, "Viewport div contains control div");
+ map.destroy();
+ }
+ function test_redraw_noLayer_displayProjection(t) {
+ t.plan(4);
+ var control = new OpenLayers.Control.MousePosition({'emptyString':''});
+ var map = new OpenLayers.Map('map');
+ map.addControl(control);
+ var control2 = new OpenLayers.Control.MousePosition();
+ map.addControl(control2);
+ t.eq(control2.emptyString, null, "Emptystring is null");
+ t.eq(control.div.innerHTML, "", "innerHTML set correctly");
+ control.redraw({'xy': new OpenLayers.Pixel(10,10)});
+ control.redraw({'xy': new OpenLayers.Pixel(12,12)});
+ t.eq(control.div.innerHTML, "", "innerHTML set correctly");
+ var l = new OpenLayers.Layer('name', {'isBaseLayer': true});
+ map.addLayer(l);
+ map.zoomToMaxExtent();
+ control.redraw({'xy': new OpenLayers.Pixel(10,10)});
+ control.redraw({'xy': new OpenLayers.Pixel(12,12)});
+ t.eq(control.div.innerHTML, "-175.78125, 85.78125", "innerHTML set correctly when triggered.");
+ map.destroy();
+ }
+ function test_formatOutput(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control.MousePosition({
+ prefix: 'prefix',
+ suffix: 'suffix',
+ separator: 'separator',
+ numDigits: 3
+ });
+ var lonlat = new OpenLayers.LonLat(0.75699, 0.37365);
+ var val = control.formatOutput(lonlat);
+ t.eq(val, 'prefix0.757separator0.374suffix', 'formatOutput correctly formats the mouse position output');
+ }
+ function test_deactivate(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ // Auxiliary function
+ function trigger(type, x, y) {
+ map.events.triggerEvent(type, {
+ xy: new OpenLayers.Pixel(x, y)
+ })
+ };
+
+ var control = new OpenLayers.Control.MousePosition();
+ map.addControl(control);
+ trigger("mousemove", 0, 0);
+
+ trigger("mousemove", 0, 1);
+ t.ok(control.div.innerHTML != "",
+ "Shows the position after add control (with autoActivate) and move");
+ control.deactivate();
+ t.ok(control.div.innerHTML == "",
+ "Position is not displayed after deactivate and move");
+ trigger("mousemove", 0, 2);
+ t.ok(control.div.innerHTML == "",
+ "Position is not displayed after move when deactivate");
+ control.activate();
+ trigger("mousemove", 0, 3);
+ t.ok(control.div.innerHTML != "",
+ "Shows the position after activate and move");
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/NavToolbar.html b/misc/openlayers/tests/Control/NavToolbar.html
new file mode 100644
index 0000000..9b3bbec
--- /dev/null
+++ b/misc/openlayers/tests/Control/NavToolbar.html
@@ -0,0 +1,45 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_NavToolbar_constructor (t) {
+ t.plan( 4 );
+ control = new OpenLayers.Control.NavToolbar();
+ t.ok( control instanceof OpenLayers.Control.NavToolbar, "new OpenLayers.Control.NavToolbar returns object" );
+ t.eq( control.displayClass, "olControlNavToolbar", "displayClass is correct" );
+ t.ok( control.controls[0] instanceof OpenLayers.Control.Navigation, "NavToolbar contains Control.Navigation object" );
+ t.ok( control.controls[1] instanceof OpenLayers.Control.ZoomBox, "NavToolbar contains Control.ZoomBox object" );
+ }
+ function test_Control_NavToolbar_addControl (t) {
+ t.plan( 6 );
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.NavToolbar();
+ t.ok( control instanceof OpenLayers.Control.NavToolbar, "new OpenLayers.Control.NavToolbar returns object" );
+ t.ok( map instanceof OpenLayers.Map, "new OpenLayers.Map creates map" );
+ map.addControl(control);
+ t.ok( control.map === map, "Control.map is set to the map object" );
+ t.ok( map.controls[4] === control, "map.controls contains control" );
+ t.eq( parseInt(control.div.style.zIndex), map.Z_INDEX_BASE['Control'] + 7, "Control div zIndexed properly" );
+ t.eq( parseInt(map.viewPortDiv.lastChild.style.zIndex), map.Z_INDEX_BASE['Control'] + 7, "Viewport div contains control div" );
+ // t.eq( control.div.style.top, "6px", "Control div top located correctly by default");
+
+ }
+
+ function test_Control_NavToolbar_defaultControl (t) {
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+
+ var nav = new OpenLayers.Control.NavToolbar();
+ map.addControl(nav);
+
+ t.eq(nav.controls[0].active, true, "First control is active" );
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Navigation.html b/misc/openlayers/tests/Control/Navigation.html
new file mode 100644
index 0000000..e73ee42
--- /dev/null
+++ b/misc/openlayers/tests/Control/Navigation.html
@@ -0,0 +1,200 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Control_Navigation_constructor (t) {
+ t.plan( 3 );
+ var temp = OpenLayers.Control.prototype.initialize;
+ OpenLayers.Control.prototype.initialize = function() {
+ t.ok(true, "OpenLayers.Control's constructor called");
+ };
+
+ var control = new OpenLayers.Control.Navigation();
+ t.ok( control instanceof OpenLayers.Control.Navigation, "new OpenLayers.Control returns object" );
+
+ t.ok( !control.handleRightClicks, "'handleRightClicks' property is disabled by default");
+
+ OpenLayers.Control.prototype.initialize = temp;
+ }
+
+ function test_draw(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map({div: 'map', controls: []});
+ var control = new OpenLayers.Control.Navigation();
+ map.addControl(control);
+ t.ok(control.handlers.click instanceof OpenLayers.Handler.Click,
+ "click handler set in instance");
+ t.ok(control.dragPan instanceof OpenLayers.Control.DragPan,
+ "drag pan control set in instance");
+ t.ok(control.zoomBox instanceof OpenLayers.Control.ZoomBox,
+ "zoom box control set in instance");
+ t.ok(control.handlers.wheel instanceof OpenLayers.Handler.MouseWheel,
+ "mousewheel handler set in instance");
+ t.ok(control.pinchZoom instanceof OpenLayers.Control.PinchZoom,
+ "pinch zoom control set in instance");
+ map.destroy();
+ }
+
+ function test_Control_Navigation_destroy (t) {
+ t.plan(12);
+
+ var temp = OpenLayers.Control.prototype.destroy;
+ OpenLayers.Control.prototype.destroy = function() {
+ t.ok(true, "OpenLayers.Control's destroy called");
+ temp.call(this);
+ };
+
+ var control = {
+ events: {
+ destroy: function() {
+ t.ok(true, "events destroyed");
+ }
+ },
+ 'deactivate': function() {
+ t.ok(true, "navigation control deactivated before being destroyed");
+ },
+ 'dragPan': {
+ 'destroy': function() {
+ t.ok(true, "dragPan destroyed");
+ }
+ },
+ 'zoomBox': {
+ 'destroy': function() {
+ t.ok(true, "zoomBox destroyed");
+ }
+ },
+ 'pinchZoom': {
+ 'destroy': function() {
+ t.ok(true, "pinchZoom destroyed");
+ }
+ },
+ handlers: {
+ 'wheel': {
+ 'destroy': function() {
+ t.ok(true, "wheelHandler destroyed");
+ }
+ },
+ 'click': {
+ 'destroy': function() {
+ t.ok(true, "clickHandler destroyed");
+ }
+ }
+ }
+ };
+
+ //this will also trigger one test by calling OpenLayers.Control's destroy
+ // and three more for the destruction of dragPan, zoomBox, and wheelHandler
+ OpenLayers.Control.Navigation.prototype.destroy.apply(control, []);
+
+ t.eq(control.dragPan, null, "'dragPan' set to null");
+ t.eq(control.zoomBox, null, "'zoomBox' set to null");
+ t.eq(control.pinchZoom, null, "'pinchZoom' set to null");
+ t.eq(control.handlers, null, "handlers set to null");
+
+ OpenLayers.Control.prototype.destroy = temp;
+ }
+
+ function test_Control_Navigation_disableZoomBox(t) {
+ t.plan(2);
+ var nav = new OpenLayers.Control.Navigation();
+ var zb = new OpenLayers.Control.ZoomBox({});
+ nav.zoomBox = zb;
+ zb.activate();
+ nav.disableZoomBox();
+ t.eq(nav.zoomBoxEnabled, false, "zoom box deactivated");
+ t.eq(zb.active, false, "zoom box control deactivated");
+ }
+
+ function test_Control_Navigation_enableZoomBox(t) {
+ t.plan(2);
+ var nav = new OpenLayers.Control.Navigation();
+ var zb = new OpenLayers.Control.ZoomBox({});
+ nav.zoomBox = zb;
+ nav.active = true;
+ nav.enableZoomBox();
+ t.eq(nav.zoomBoxEnabled, true, "zoom box activated");
+ t.eq(zb.active, true, "zoom box control activated");
+ }
+
+ function test_Control_Navigation_disableZoomWheel(t) {
+ t.plan(2);
+ var nav = new OpenLayers.Control.Navigation();
+ var wheel = new OpenLayers.Handler.MouseWheel(nav, {});
+ nav.handlers.wheel = wheel;
+ wheel.register = function() {};
+ wheel.unregister = function() {};
+ wheel.activate();
+ nav.disableZoomWheel();
+ t.eq(nav.zoomWheelEnabled, false, "mouse wheel deactivated");
+ t.eq(wheel.active, false, "mouse wheel handler deactivated");
+ }
+
+ function test_Control_Navigation_enableZoomWheel(t) {
+ t.plan(2);
+ var nav = new OpenLayers.Control.Navigation({zoomWheelEnabled: false});
+ nav.active = true;
+ var wheel = new OpenLayers.Handler.MouseWheel(nav, {});
+ wheel.register = function() {};
+ wheel.unregister = function() {};
+ nav.handlers.wheel = wheel;
+ nav.enableZoomWheel();
+ t.eq(nav.zoomWheelEnabled, true, "mouse wheel activated");
+ t.eq(wheel.active, true, "mouse wheel handler activated");
+ }
+
+ function test_touches_zoom(t) {
+ t.plan(3);
+ var nav = new OpenLayers.Control.Navigation({zoomWheelEnabled: false});
+ var map = new OpenLayers.Map({
+ div: "map",
+ zoomMethod: null,
+ controls: [nav],
+ layers: [
+ new OpenLayers.Layer(null, {isBaseLayer: true})
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 3
+ });
+ t.eq(map.getZoom(), 3, "map zoom starts at 3");
+ nav.handlers.click.callback("click", [{lastTouches: ["foo", "bar"]}]);
+ t.eq(map.getZoom(), 2, "map zooms out with a two touch tap");
+ nav.handlers.click.callback("click", [{}]);
+ t.eq(map.getZoom(), 2, "map doesn't do anything with click");
+
+ map.destroy();
+ }
+
+ function test_documentDrag(t) {
+
+ t.plan(2);
+
+ /**
+ * These tests confirm that the documentDrag property is false by
+ * default and is passed on to the DragPan control. Tests of panning
+ * while dragging outside the viewport should go in the DragPan tests.
+ * Tests of the document events and appropriate callbacks from the
+ * handler should go in the Drag handler tests.
+ */
+
+ var nav = new OpenLayers.Control.Navigation();
+ t.eq(nav.documentDrag, false, "documentDrag false by default");
+ // nav.destroy(); // fails if called before draw
+
+ var map = new OpenLayers.Map({
+ div: document.body,
+ controls: [new OpenLayers.Control.Navigation({documentDrag: true})]
+ });
+ nav = map.controls[0];
+
+ t.eq(nav.dragPan.documentDrag, true, "documentDrag set on the DragPan control");
+ map.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 256px; height: 256px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/NavigationHistory.html b/misc/openlayers/tests/Control/NavigationHistory.html
new file mode 100644
index 0000000..c992ff2
--- /dev/null
+++ b/misc/openlayers/tests/Control/NavigationHistory.html
@@ -0,0 +1,245 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(4);
+ control = new OpenLayers.Control.NavigationHistory();
+ t.ok(control instanceof OpenLayers.Control.NavigationHistory,
+ "constructor returns correct instance");
+ t.eq(control.displayClass, "olControlNavigationHistory",
+ "displayClass is correct");
+ t.ok(control.next instanceof OpenLayers.Control.Button,
+ "constructor creates next control");
+ t.ok(control.previous instanceof OpenLayers.Control.Button,
+ "constructor creates previous control");
+ }
+
+ function test_destroy(t) {
+ t.plan(2);
+ control = new OpenLayers.Control.NavigationHistory();
+ control.next.destroy = function() {
+ t.ok(true, "destroy calls next.destroy");
+ }
+ control.previous.destroy = function() {
+ t.ok(true, "destroy calls previous.destroy");
+ }
+ control.destroy();
+ }
+
+ function test_previous(t) {
+ var numStates = 10;
+
+ t.plan(
+ numStates * 3 // for lon, lat, zoom
+ + 3 // for confirming that previous with empty stack works
+ );
+
+ var history = new Array(numStates);
+ for(var i=0; i<numStates; ++i) {
+ history[i] = {
+ center: new OpenLayers.LonLat(
+ (i * 360 / numStates) - 180, (i * 180 / numStates) - 90
+ ),
+ zoom: i
+ };
+ }
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(
+ "test", {isBaseLayer: true}
+ );
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.NavigationHistory();
+ map.addControl(control);
+
+ // set previous states
+ for(i=0; i<numStates; ++i) {
+ map.setCenter(history[i].center, history[i].zoom);
+ }
+ // test previous states
+ for(i=numStates-1; i>=0; --i) {
+ t.eq(map.getCenter().lon, history[i].center.lon, "(step " + i + ") lon correct");
+ t.eq(map.getCenter().lat, history[i].center.lat, "(step " + i + ") lat correct");
+ t.eq(map.getZoom(), history[i].zoom, "(step " + i + ") zoom correct");
+ control.previous.trigger();
+ }
+ // test previous with empty stack
+ t.eq(map.getCenter().lon, history[0].center.lon, "(step 0 again) lon correct");
+ t.eq(map.getCenter().lat, history[0].center.lat, "(step 0 again) lat correct");
+ t.eq(map.getZoom(), history[0].zoom, "(step 0 again) zoom correct");
+ }
+
+ function test_next(t) {
+ var numStates = 10;
+
+ t.plan(
+ numStates * 3 // for lon, lat, zoom
+ + 3 // for confirming that next with empty stack works
+ );
+
+ var history = new Array(numStates);
+ for(var i=0; i<numStates; ++i) {
+ history[i] = {
+ center: new OpenLayers.LonLat(
+ (i * 360 / numStates) - 180, (i * 180 / numStates) - 90
+ ),
+ zoom: i
+ };
+ }
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(
+ "test", {isBaseLayer: true}
+ );
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.NavigationHistory();
+ map.addControl(control);
+
+ // set previous states
+ for(i=0; i<numStates; ++i) {
+ map.setCenter(history[i].center, history[i].zoom);
+ }
+ // set next states
+ for(i=numStates-1; i>=0; --i) {
+ control.previous.trigger();
+ }
+ // test next states
+ for(i=0; i<numStates; ++i) {
+ t.eq(map.getCenter().lon, history[i].center.lon, "(step " + i + ") lon correct");
+ t.eq(map.getCenter().lat, history[i].center.lat, "(step " + i + ") lat correct");
+ t.eq(map.getZoom(), history[i].zoom, "(step " + i + ") zoom correct");
+ control.next.trigger();
+ }
+ // test next with empty stack
+ t.eq(map.getCenter().lon, history[numStates-1].center.lon, "(step " + (numStates-1) + " again) lon correct");
+ t.eq(map.getCenter().lat, history[numStates-1].center.lat, "(step " + (numStates-1) + " again) lat correct");
+ t.eq(map.getZoom(), history[numStates-1].zoom, "(step " + (numStates-1) + " again) zoom correct");
+ }
+
+ function test_limit(t) {
+ var numStates = 10;
+ var limit = 3;
+
+ t.plan(
+ numStates * 6 // for previous & next lon, lat, zoom
+ );
+
+ var history = new Array(numStates);
+ for(var i=0; i<numStates; ++i) {
+ history[i] = {
+ center: new OpenLayers.LonLat(
+ (i * 360 / numStates) - 180, (i * 180 / numStates) - 90
+ ),
+ zoom: i
+ };
+ }
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(
+ "test", {isBaseLayer: true}
+ );
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.NavigationHistory({limit: limit});
+ map.addControl(control);
+
+ // set previous states
+ for(i=0; i<numStates; ++i) {
+ map.setCenter(history[i].center, history[i].zoom);
+ }
+ // test previous states (only up to limit should work)
+ var state;
+ for(i=numStates-1; i>=0; --i) {
+ state = Math.max(i, numStates - limit - 1);
+ t.eq(map.getCenter().lon, history[state].center.lon, "(previous step " + i + ") lon correct: state " + state);
+ t.eq(map.getCenter().lat, history[state].center.lat, "(previous step " + i + ") lat correct: state " + state);
+ t.eq(map.getZoom(), history[state].zoom, "(previous step " + i + ") zoom correct: state " + state);
+ control.previous.trigger();
+ }
+ // test next states
+ for(i=0; i<numStates; ++i) {
+ state = Math.min(numStates - 1, numStates - limit - 1 + i);
+ t.eq(map.getCenter().lon, history[state].center.lon, "(next step " + i + ") lon correct: state " + state);
+ t.eq(map.getCenter().lat, history[state].center.lat, "(next step " + i + ") lat correct: state " + state);
+ t.eq(map.getZoom(), history[state].zoom, "(next step " + i + ") zoom correct: state " + state);
+ control.next.trigger();
+ }
+
+ }
+
+ function test_clear(t) {
+ t.plan(7);
+ var map = new OpenLayers.Map("map", {zoomMethod: null});
+ var layer = new OpenLayers.Layer(
+ "test", {isBaseLayer: true}
+ );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.NavigationHistory();
+ map.addControl(control);
+
+ t.ok(!control.previous.active, "previous control not active");
+ t.ok(!control.next.active, "next control not active");
+
+ map.zoomTo(4);
+ t.ok(control.previous.active, "previous control is active after a move");
+ t.ok(!control.next.active, "next control is not active after a move");
+
+ control.clear();
+ t.eq(control.previousStack.length + control.nextStack.length, 0, "stacks are empty after a clear");
+ t.ok(!control.previous.active, "previous control not active after a clear");
+ t.ok(!control.next.active, "next control not active after a clear");
+
+ control.destroy();
+ }
+
+ function test_reprojection(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(
+ "test", {isBaseLayer: true}
+ );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.NavigationHistory();
+ map.addControl(control);
+
+ map.zoomTo(4);
+ var bounds = map.getExtent().clone();
+ var expected = bounds.transform(new OpenLayers.Projection('EPSG:4326'),
+ new OpenLayers.Projection('EPSG:900913'));
+ // change the projection to EPSG:900913
+ var projSettings = {
+ units: "m",
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508),
+ maxResolution: 156543.0339
+ };
+ map.setOptions(projSettings);
+ map.projection = 'EPSG:900913';
+ delete projSettings.maxResolution;
+ projSettings.projection = new OpenLayers.Projection('EPSG:900913');
+ layer.addOptions(projSettings);
+ layer.initResolutions();
+
+ map.zoomTo(7);
+
+ // go back one in the history
+ control.previous.trigger();
+
+ t.eq(map.getExtent().left.toFixed(3), expected.left.toFixed(3), "The extent [left] is reprojected correctly");
+ t.eq(map.getExtent().right.toFixed(3), expected.right.toFixed(3), "The extent [right] is reprojected correctly");
+ // top and bottom cannot be checked here since in EPSG:900913 the extent is not a rectangle so they are adjusted.
+
+ control.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 100px; height: 100px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/OverviewMap.html b/misc/openlayers/tests/Control/OverviewMap.html
new file mode 100644
index 0000000..a5a598d
--- /dev/null
+++ b/misc/openlayers/tests/Control/OverviewMap.html
@@ -0,0 +1,266 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map, control;
+
+ function test_initialize(t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.OverviewMap();
+ t.ok( control instanceof OpenLayers.Control.OverviewMap,
+ "new OpenLayers.Control.OverviewMap returns object" );
+ t.eq( control.displayClass,
+ "olControlOverviewMap", "displayClass is correct" );
+ }
+
+ function test_divs_title(t) {
+ t.plan(2);
+
+ control = new OpenLayers.Control.OverviewMap({
+ maximizeTitle: "maximize title",
+ minimizeTitle: "minimize title"
+ });
+ map = new OpenLayers.Map('map', {
+ layers: [new OpenLayers.Layer("layer", {isBaseLayer: true})],
+ controls: [control]
+ });
+ map.zoomToMaxExtent();
+ t.eq(control.maximizeDiv.title, "maximize title", "maximizeDiv.title is correct");
+ t.eq(control.minimizeDiv.title, "minimize title", "minimizeDiv.title is correct");
+ map.destroy();
+ }
+
+ function test_setMap(t) {
+ t.plan(4);
+
+ var setMapTest = function(map) {
+ t.ok(true,
+ "Handler.setMap called for " + this.CLASS_NAME);
+ this.map = map;
+ };
+ var drag_setMap = OpenLayers.Handler.Drag.prototype.setMap;
+ OpenLayers.Handler.Drag.prototype.setMap = setMapTest;
+ var click_setMap = OpenLayers.Handler.Click.prototype.setMap;
+ OpenLayers.Handler.Click.prototype.setMap = setMapTest;
+
+ map = new OpenLayers.Map('map', {
+ layers : [new OpenLayers.Layer("layer", {isBaseLayer: true})],
+ controls: []
+ });
+ control = new OpenLayers.Control.OverviewMap();
+
+ map.addControl(control);
+
+ map.zoomToMaxExtent();
+ t.eq(control.handlers.drag.map.id, control.ovmap.id,
+ "drag.map is correct");
+ t.eq(control.handlers.click.map.id, control.ovmap.id,
+ "click.map is correct");
+
+ map.destroy();
+ OpenLayers.Handler.Drag.prototype.setMap = drag_setMap;
+ OpenLayers.Handler.Click.prototype.setMap = click_setMap;
+ }
+
+ function test_destroy(t) {
+ t.plan(6);
+
+ // set up
+
+ var log_drag = [], log_click = [], control;
+
+ map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer("layer", {isBaseLayer: true}));
+
+ control = new OpenLayers.Control.OverviewMap();
+ map.addControl(control);
+
+ map.zoomToMaxExtent();
+
+ control.handlers.drag.destroy = function() {
+ log_drag.push({"map": !!this.map.events});
+ };
+ control.handlers.click.destroy = function() {
+ log_click.push({"map": !!this.map.events});
+ };
+
+ // test
+
+ control.destroy();
+ t.eq(log_drag.length, 2,
+ "destroy() destroys drag handler twice, expected");
+ if (log_drag.length == 2) {
+ t.eq(log_drag[0].map, true,
+ "destroy() destroys drag handler before ovmap is destroyed (0)");
+ t.eq(log_drag[1].map, false,
+ "destroy() destroys drag handler after ovmap is destroyed (1)");
+ }
+ t.eq(log_click.length, 2,
+ "destroy() destroys click handler twice, expected");
+ if (log_click.length == 2) {
+ t.eq(log_click[0].map, true,
+ "destroy() destroys click handler before ovmap is destroyed (0)");
+ t.eq(log_click[1].map, false,
+ "destroy() destroys click handler after ovmap is destroyed (1)");
+ }
+
+ // tear down
+ map.destroy();
+ }
+
+ function test_addControl (t) {
+ t.plan( 6 );
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.OverviewMap();
+ t.ok( control instanceof OpenLayers.Control.OverviewMap,
+ "new OpenLayers.Control.OverviewMap returns object" );
+ t.ok( map instanceof OpenLayers.Map,
+ "new OpenLayers.Map creates map" );
+ map.addControl(control);
+ t.ok( control.map === map,
+ "Control.map is set to the map object" );
+ t.ok( map.controls[4] === control,
+ "map.controls contains control" );
+ t.eq( parseInt(control.div.style.zIndex), map.Z_INDEX_BASE['Control'] + 5,
+ "Control div zIndexed properly" );
+ t.eq( parseInt(map.viewPortDiv.lastChild.style.zIndex), map.Z_INDEX_BASE['Control'] + 5,
+ "Viewport div contains control div" );
+
+ map.destroy();
+ }
+
+ function test_control_events (t) {
+ t.plan( 10 );
+
+ map = new OpenLayers.Map('map', {
+ // when we recenter, don't waste time animating the panning
+ // without this, the test fails in Firefox 10.0.1 on Linux
+ panMethod: null,
+ layers: [ new OpenLayers.Layer('Test Layer', {isBaseLayer: true}) ]
+ });
+
+ control = new OpenLayers.Control.OverviewMap();
+ map.addControl(control, new OpenLayers.Pixel(20,20));
+
+ var centerLL = new OpenLayers.LonLat(-71,42);
+ map.setCenter(centerLL, 11);
+
+ t.delay_call(
+ 0.1,
+ function() {
+ var overviewCenter = control.ovmap.getCenter();
+ var overviewZoom = control.ovmap.getZoom();
+ t.eq(overviewCenter.lon, -71,
+ "OverviewMap center lon correct");
+ t.eq(overviewCenter.lat, 42,
+ "OverviewMap center lat correct");
+ t.eq(overviewZoom, 8,
+ "OverviewMap zoom correct");
+
+ control.mapDivClick({'xy':new OpenLayers.Pixel(5,5)});
+ },
+ 0.1,
+ function() {
+ var cent = map.getCenter();
+ t.eq(cent.lon, -71.3515625,
+ "Clicking on OverviewMap has correct effect on map lon");
+ t.eq(cent.lat, 42.17578125,
+ "Clicking on OverviewMap has correct effect on map lat");
+
+ control.handlers.drag = {
+ last: new OpenLayers.Pixel(5,5),
+ destroy: function() {}
+ };
+ control.rectDrag(new OpenLayers.Pixel(15, 15));
+ control.updateMapToRect();
+ },
+ 0.1,
+ function() {
+ var cent = map.getCenter();
+ t.eq(cent.lon, -71.2734375,
+ "Dragging on OverviewMap has correct effect on map lon");
+ t.eq(cent.lat, 42.09765625,
+ "Dragging on OverviewMap has correct effect on map lat");
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 0);
+ var overviewCenter = control.ovmap.getCenter();
+ var overviewZoom = control.ovmap.getZoom();
+ t.eq(overviewCenter.lon, 0,
+ "OverviewMap center lon correct -- second zoom");
+ t.eq(overviewCenter.lat, 0,
+ "OverviewMap center lat correct -- second zoom");
+ t.eq(overviewZoom, 0,
+ "OverviewMap zoomcorrect -- second zoom");
+ map.destroy();
+ }
+ );
+ }
+
+ function test_initialize_maximized(t) {
+ t.plan(4);
+
+ control = new OpenLayers.Control.OverviewMap()
+ map = new OpenLayers.Map('map', {
+ layers : [new OpenLayers.Layer("layer", {isBaseLayer: true})],
+ controls: [control]
+ });
+
+ t.eq(control.maximized, false,
+ "OverviewMap is not maximized by default");
+ t.eq(control.element.style.display, 'none',
+ "OverviewMap.element is not visible");
+ map.destroy();
+
+ control = new OpenLayers.Control.OverviewMap({
+ maximized: true
+ })
+ map = new OpenLayers.Map('map', {
+ layers : [new OpenLayers.Layer("layer", {isBaseLayer: true})],
+ controls: [control]
+ });
+ t.eq(control.maximized, true,
+ "OverviewMap.maximized is set");
+ t.eq(control.element.style.display, '',
+ "OverviewMap.element is visible");
+
+ map.destroy();
+ }
+
+ function test_custom_div(t) {
+ t.plan(3);
+ var div = document.createElement('div');
+
+ control = new OpenLayers.Control.OverviewMap({
+ div: div
+ });
+
+ map = new OpenLayers.Map('map', {
+ layers : [new OpenLayers.Layer("layer", {isBaseLayer: true})],
+ controls: [control]
+ });
+
+ t.eq(control.maximizeDiv, null,
+ "OverviewMap does not create maximize div");
+ t.eq(control.minimizeDiv, null,
+ "OverviewMap does not create minimize div");
+
+ var exc;
+ try {
+ control.maximizeControl();
+ control.minimizeControl();
+ } catch(e) {
+ exc = e;
+ }
+
+ t.eq(exc, undefined, 'maximize and minimize do not trigger an exception');
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Pan.html b/misc/openlayers/tests/Control/Pan.html
new file mode 100644
index 0000000..0c9dfaf
--- /dev/null
+++ b/misc/openlayers/tests/Control/Pan.html
@@ -0,0 +1,201 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+function test_Pan_constructor (t) {
+ t.plan( 2 );
+
+ // setup
+ var control = new OpenLayers.Control.Pan(
+ "Gargoyle" // the direction, here mocked up
+ );
+
+ // tests
+ //
+ t.ok(
+ control instanceof OpenLayers.Control.Pan,
+ "new OpenLayers.Control.Pan returns object"
+ );
+ t.eq(
+ control.displayClass, "olControlPanGargoyle",
+ "displayClass is correct"
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_Pan_type (t) {
+ t.plan( 1 );
+
+ // setup
+ var control = new OpenLayers.Control.Pan();
+
+ // tests
+ //
+ t.eq(
+ control.type,
+ OpenLayers.Control.TYPE_BUTTON,
+ "Pan control is of type OpenLayers.Control.TYPE_BUTTON"
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_Pan_constants (t) {
+ var dirs = [
+ 'North',
+ 'East',
+ 'South',
+ 'West'
+ ],
+ numDirs = dirs.length,
+ dir, uc_dir;
+
+ t.plan(numDirs);
+
+ for ( ; numDirs > 0; numDirs-- ) {
+ dir = dirs[numDirs - 1 ];
+ uc_dir = dir.toUpperCase();
+
+ t.eq(
+ OpenLayers.Control.Pan[ uc_dir ],
+ dir,
+ "A constant 'OpenLayers.Control.Pan." + uc_dir + "' is defined "+
+ "and has the correct value of '" + dir + "'."
+ );
+ }
+}
+
+function test_Pan_trigger (t) {
+ t.plan( 12 );
+
+ // set up
+ var controls = {
+ n: new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH),
+ e: new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST),
+ s: new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH),
+ w: new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST)
+ },
+ controlKey, control,
+ zoomlevel = 5,
+ center = new OpenLayers.LonLat(25,25),
+ log = {
+ dx: null,
+ dy: null
+ },
+ map = new OpenLayers.Map("map", {
+ allOverlays: true,
+ layers: [
+ new OpenLayers.Layer.Vector()
+ ],
+ center: center,
+ zoom: zoomlevel
+ }),
+ oldZoom;
+
+ // overwrite native Map::pan
+ map.pan = function(dx, dy) {
+ log = {
+ dx: dx,
+ dy: dy
+ };
+ OpenLayers.Map.prototype.pan.apply(map, arguments);
+ };
+
+ oldCenter = map.getCenter().toString();
+
+ for (controlKey in controls) {
+ if (controls.hasOwnProperty(controlKey)) {
+ control = controls[controlKey];
+ // trigger the control; nothing should change, we aren't added yet.
+ control.trigger();
+
+ t.ok(
+ log.dx === null && log.dy === null,
+ 'Calling trigger on a non added control doesn\'t do anything.'
+ );
+
+ // reset log object
+ log = {
+ dx: null,
+ dy: null
+ };
+ }
+ }
+
+ // now lets add the controls, and trigger them again
+ for (controlKey in controls) {
+ if (controls.hasOwnProperty(controlKey)) {
+ control = controls[controlKey];
+ map.addControl(control);
+ // trigger again, now ...
+ control.trigger();
+
+ // ... the center should change ...
+ t.ok(
+ log.dx !== null && log.dy !== null,
+ 'Calling trigger on an added pan control calls map.pan()... '
+ );
+
+ // ... with sane arguments according to the passed direction.
+ switch (control.direction) {
+ case OpenLayers.Control.Pan.NORTH:
+ t.ok(
+ log.dx === 0 && log.dy < 0,
+ '... with sane arguments: pan north only results in ' +
+ 'negative delta y'
+ );
+ break;
+ case OpenLayers.Control.Pan.SOUTH:
+ t.ok(
+ log.dx === 0 && log.dy > 0,
+ '... with sane arguments: pan south only results in ' +
+ 'positive delta y'
+ );
+ break;
+ case OpenLayers.Control.Pan.WEST:
+ t.ok(
+ log.dx < 0 && log.dy === 0,
+ '... with sane arguments: pan west only results in ' +
+ 'negative delta x'
+ );
+ break;
+ case OpenLayers.Control.Pan.EAST:
+ t.ok(
+ log.dx > 0 && log.dy === 0,
+ '... with sane arguments: pan east only results in ' +
+ 'positive delta x'
+ );
+ break;
+ }
+
+ // reset log-object
+ log = {
+ dx: null,
+ dy: null
+ };
+ // always set to initial center and zoom:
+ map.setCenter(center, zoomlevel);
+ }
+ }
+
+ // tear down
+ for (controlKey in controls) {
+ if (controls.hasOwnProperty(controlKey)) {
+ control = controls[controlKey];
+ control.destroy();
+ }
+ }
+ map.destroy();
+}
+
+ </script>
+ </head>
+ <body>
+ <div id="map" style="width: 1000px; height: 1000px;"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Control/PanPanel.html b/misc/openlayers/tests/Control/PanPanel.html
new file mode 100644
index 0000000..978a051
--- /dev/null
+++ b/misc/openlayers/tests/Control/PanPanel.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_constructor (t) {
+ t.plan(2);
+
+ // set up
+ var control;
+
+ // tests
+ control = new OpenLayers.Control.PanPanel({slideFactor: 200});
+ t.ok(control.controls[0].slideFactor == 200 &&
+ control.controls[1].slideFactor == 200 &&
+ control.controls[2].slideFactor == 200 &&
+ control.controls[3].slideFactor == 200,
+ "ctor sets slideFactor in all Pan controls");
+
+ control.destroy();
+
+ control = new OpenLayers.Control.PanPanel({slideRatio: .5});
+ t.ok(control.controls[0].slideRatio == .5 &&
+ control.controls[1].slideRatio == .5 &&
+ control.controls[2].slideRatio == .5 &&
+ control.controls[3].slideRatio == .5,
+ "ctor sets slideRatio in all Pan controls");
+
+ control.destroy();
+ }
+
+ function test_slide(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map", {
+ panMethod: null,
+ controls: [
+ new OpenLayers.Control.PanPanel(),
+ new OpenLayers.Control.PanPanel({slideRatio: .5})
+ ],
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+
+ map.controls[0].controls[0].trigger();
+ map.controls[0].controls[2].trigger();
+ map.pan(-50, 50);
+ t.eq(map.getCenter().toShortString(), "0, 0", "correct pan distance with slideFactor");
+
+ map.controls[1].controls[0].trigger();
+ map.controls[1].controls[2].trigger();
+ map.pan(-128, 64);
+ t.eq(map.getCenter().toShortString(), "0, 0", "correct pan distance with slideRatio");
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 256px; height: 128px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/PanZoom.html b/misc/openlayers/tests/Control/PanZoom.html
new file mode 100644
index 0000000..4982fb0
--- /dev/null
+++ b/misc/openlayers/tests/Control/PanZoom.html
@@ -0,0 +1,244 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_PanZoom_constructor (t) {
+ t.plan( 4 );
+
+ control = new OpenLayers.Control.PanZoom();
+ t.ok( control instanceof OpenLayers.Control.PanZoom, "new OpenLayers.Control.PanZoom returns object" );
+ t.eq( control.displayClass, "olControlPanZoom", "displayClass is correct" );
+ control = new OpenLayers.Control.PanZoom({position: new OpenLayers.Pixel(100,100)});
+ t.eq( control.position.x, 100, "PanZoom X Set correctly.");
+ t.eq( control.position.y, 100, "PanZoom y Set correctly.");
+ }
+ function test_Control_PanZoom_addControl (t) {
+ t.plan( 8 );
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.PanZoom();
+ t.ok( control instanceof OpenLayers.Control.PanZoom, "new OpenLayers.Control.PanZoom returns object" );
+ t.ok( map instanceof OpenLayers.Map, "new OpenLayers.Map creates map" );
+ map.addControl(control);
+ t.ok( control.map === map, "Control.map is set to the map object" );
+ t.ok( map.controls[4] === control, "map.controls contains control" );
+ t.eq( parseInt(control.div.style.zIndex), map.Z_INDEX_BASE['Control'] + 5, "Control div zIndexed properly" );
+ t.eq( parseInt(map.viewPortDiv.lastChild.style.zIndex), map.Z_INDEX_BASE['Control'] + 5, "Viewport div contains control div" );
+ t.eq( control.div.style.top, "4px", "Control div top located correctly by default");
+
+ var control2 = new OpenLayers.Control.PanZoom();
+ map.addControl(control2, new OpenLayers.Pixel(100,100));
+ t.eq( control2.div.style.top, "100px", "2nd control div is located correctly");
+ }
+
+ function test_Control_PanZoom_removeButtons(t) {
+ t.plan(2);
+ map = new OpenLayers.Map("map");
+ control = new OpenLayers.Control.PanZoom();
+ map.addControl(control);
+ control.removeButtons();
+ t.eq(control.buttons.length, 0, "buttons array cleared correctly");
+ t.eq(control.div.childNodes.length, 0, "control div is empty");
+ }
+
+ function test_Control_PanZoom_control_events (t) {
+
+ // IE 9+ does support the standard document.createEvent,
+ // event.initMouseEvent, and elem.dispatchEvent calls, so it
+ // should be possible to simulate clicks in this browser.
+ // For example it looks like jQuery UI does simulate events
+ // using document.createElement in IE 9+. See
+ // https://github.com/jquery/jquery-ui/blob/master/tests/jquery.simulate.js.
+ // I haven't been able to make it work though.
+
+ if ( !window.document.createEvent ||
+ OpenLayers.BROWSER_NAME == "msie" ||
+ OpenLayers.BROWSER_NAME == "opera" ||
+ !t.open_window) {
+
+ t.plan(0);
+ t.debug_print("FIXME: This browser does not support the PanZoom test at this time.");
+ } else {
+ t.plan(35);
+ t.open_window( "Control/PanZoom.html", function( wnd ) {
+ t.delay_call( 3, function() {
+ var flag;
+ function setFlag(evt) {
+ flag[evt.type] = true;
+ }
+ function resetFlags() {
+ flag = {
+ mousedown: false,
+ mouseup: false,
+ click: false,
+ dblclick: false
+ };
+ }
+ resetFlags();
+
+ wnd.mapper.events.register("mousedown", mapper, setFlag);
+ wnd.mapper.events.register("mouseup", mapper, setFlag);
+ wnd.mapper.events.register("click", mapper, setFlag);
+ wnd.mapper.events.register("dblclick", mapper, setFlag);
+
+ simulateClick(wnd, wnd.control.buttons[0]);
+ t.delay_call(2, function() {
+ t.ok( wnd.mapper.getCenter().lat > wnd.centerLL.lat, "1) Pan up works correctly" );
+ t.ok(!flag.mousedown, "1) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "1) mouseup does not get to the map");
+ t.ok(!flag.click, "1) click does not get to the map");
+ t.ok(!flag.dblclick, "1) dblclick does not get to the map");
+ resetFlags();
+
+ simulateClick(wnd, wnd.control.buttons[1]);
+ }, 2, function() {
+ t.ok( wnd.mapper.getCenter().lon < wnd.centerLL.lon, "2) Pan left works correctly" );
+ t.ok(!flag.mousedown, "2) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "2) mouseup does not get to the map");
+ t.ok(!flag.click, "2) click does not get to the map");
+ t.ok(!flag.dblclick, "2) dblclick does not get to the map");
+ resetFlags();
+
+ simulateClick(wnd, wnd.control.buttons[2]);
+ }, 2, function() {
+ t.ok( wnd.mapper.getCenter().lon == wnd.centerLL.lon, "3) Pan right works correctly" );
+ t.ok(!flag.mousedown, "3) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "3) mouseup does not get to the map");
+ t.ok(!flag.click, "3) click does not get to the map");
+ t.ok(!flag.dblclick, "3) dblclick does not get to the map");
+ resetFlags();
+
+ simulateClick(wnd, wnd.control.buttons[3]);
+ }, 2, function() {
+ t.ok( wnd.mapper.getCenter().lat == wnd.centerLL.lat, "4) Pan down works correctly" );
+ t.ok(!flag.mousedown, "4) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "4) mouseup does not get to the map");
+ t.ok(!flag.click, "4) click does not get to the map");
+ t.ok(!flag.dblclick, "4) dblclick does not get to the map");
+ resetFlags();
+
+ simulateClick(wnd, wnd.control.buttons[4]);
+ }, 2, function() {
+ t.eq( wnd.mapper.getZoom(), 6, "5) zoomin works correctly" );
+ t.ok(!flag.mousedown, "5) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "5) mouseup does not get to the map");
+ t.ok(!flag.click, "5) click does not get to the map");
+ t.ok(!flag.dblclick, "5) dblclick does not get to the map");
+ resetFlags();
+
+ simulateClick(wnd, wnd.control.buttons[6]);
+ }, 2, function() {
+ t.eq( wnd.mapper.getZoom(), 5, "6) zoomout works correctly" );
+ t.ok(!flag.mousedown, "6) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "6) mouseup does not get to the map");
+ t.ok(!flag.click, "6) click does not get to the map");
+ t.ok(!flag.dblclick, "6) dblclick does not get to the map");
+ resetFlags();
+
+ simulateClick(wnd, wnd.control.buttons[5]);
+ }, 2, function() {
+ t.eq( wnd.mapper.getZoom(), 2, "7) zoomworld works correctly" );
+ t.ok(!flag.mousedown, "7) mousedown does not get to the map");
+ t.ok(!flag.mouseup, "7) mouseup does not get to the map");
+ t.ok(!flag.click, "7) click does not get to the map");
+ t.ok(!flag.dblclick, "7) dblclick does not get to the map");
+ resetFlags();
+ });
+ });
+ });
+ }
+ }
+
+ function test_slideRatio(t) {
+ t.plan(4);
+
+ var control = new OpenLayers.Control.PanZoom({
+ slideRatio: .5
+ });
+
+ var map = new OpenLayers.Map();
+
+ map.addControl(control);
+ control.draw();
+ control.activate();
+
+ map.getSize = function() {
+ return {
+ w: 250,
+ h: 100
+ }
+ };
+
+ var delta, dir;
+ var buttons = control.buttons;
+ map.pan = function(dx, dy){
+ t.eq([dx,dy],delta,"Panning " + dir + " sets right delta with slideRatio");
+ };
+
+ //up
+ var delta = [0, -50];
+ var dir = "up";
+ var evt = {buttonElement: buttons[0]};
+ control.onButtonClick.call(control, evt);
+
+ //left
+ var delta = [-125, 0];
+ var dir = "left";
+ evt.buttonElement = buttons[1];
+ control.onButtonClick.call(control, evt);
+
+ //right
+ var delta = [125, 0];
+ var dir = "right";
+ evt.buttonElement = buttons[2];
+ control.onButtonClick.call(control, evt);
+
+ //down
+ var delta = [0, 50];
+ var dir = "down";
+ evt.buttonElement = buttons[3];
+ control.onButtonClick.call(control, evt);
+
+ map.destroy();
+ }
+
+ function simulateClick(wnd, elem) {
+ var evt = wnd.document.createEvent("MouseEvents");
+ evt.initMouseEvent("mousedown", true, true, wnd, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ elem.dispatchEvent(evt);
+
+ evt = wnd.document.createEvent("MouseEvents");
+ evt.initMouseEvent("mouseup", true, true, wnd, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ elem.dispatchEvent(evt);
+
+ evt = wnd.document.createEvent("MouseEvents");
+ evt.initMouseEvent("click", true, true, wnd, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ elem.dispatchEvent(evt);
+
+ evt = wnd.document.createEvent("MouseEvents");
+ evt.initMouseEvent("dblclick", true, true, wnd, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ elem.dispatchEvent(evt);
+ }
+
+ function loader() {
+ control = new OpenLayers.Control.PanZoom();
+
+ mapper = new OpenLayers.Map('map', { controls: [control]});
+
+
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://labs.metacarta.com/wms-c/Basic.py?",
+ {layers: "basic"});
+ mapper.addLayer(layer);
+
+ centerLL = new OpenLayers.LonLat(0,0);
+ mapper.setCenter(centerLL, 5);
+ }
+
+
+ </script>
+</head>
+<body onload="loader()">
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/PanZoomBar.html b/misc/openlayers/tests/Control/PanZoomBar.html
new file mode 100644
index 0000000..5ed2833
--- /dev/null
+++ b/misc/openlayers/tests/Control/PanZoomBar.html
@@ -0,0 +1,245 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_PanZoomBar_constructor (t) {
+ t.plan( 4 );
+
+ control = new OpenLayers.Control.PanZoomBar({position: new OpenLayers.Pixel(100,100)});
+ t.ok( control instanceof OpenLayers.Control.PanZoomBar, "new OpenLayers.Control.PanZoomBar returns object" );
+ t.eq( control.displayClass, "olControlPanZoomBar", "displayClass is correct" );
+ t.eq( control.position.x, 100, "PanZoom X Set correctly.");
+ t.eq( control.position.y, 100, "PanZoom y Set correctly.");
+ }
+ function test_Control_PanZoomBar_addControl (t) {
+ t.plan( 8 );
+ map = new OpenLayers.Map('map', {controls:[]});
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+ control = new OpenLayers.Control.PanZoomBar();
+ t.ok( control instanceof OpenLayers.Control.PanZoomBar, "new OpenLayers.Control.PanZoomBar returns object" );
+ t.ok( map instanceof OpenLayers.Map, "new OpenLayers.Map creates map" );
+ map.addControl(control);
+ t.ok( control.map === map, "Control.map is set to the map object" );
+ t.ok( map.controls[0] === control, "map.controls contains control" );
+ t.eq( parseInt(control.div.style.zIndex), 1001, "Control div zIndexed properly" );
+ t.eq( parseInt(map.viewPortDiv.lastChild.style.zIndex), 1001, "Viewport div contains control div" );
+ t.eq( control.div.style.top, "4px", "Control div top located correctly by default");
+
+ var control2 = new OpenLayers.Control.PanZoomBar();
+ map.addControl(control2, new OpenLayers.Pixel(100,100));
+ t.eq( control2.div.style.top, "100px", "2nd control div is located correctly");
+ }
+
+ function test_draw(t) {
+ t.plan(3);
+ map = new OpenLayers.Map('map', {controls:[]});
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ control = new OpenLayers.Control.PanZoomBar();
+ map.addControl(control);
+ t.eq(control.zoombarDiv.style.height, '176px', "Bar's height is correct.");
+
+ map.baseLayer.wrapDateLine = true;
+
+ control.redraw();
+ t.eq(control.zoombarDiv.style.height, '154px', "Bar's height is correct after minZoom restriction.");
+
+ map.div.style.width = "512px";
+ map.updateSize();
+ t.eq(control.zoombarDiv.style.height, '165px', "Bar's height is correct after resize and minZoom restriction.");
+
+ map.div.style.width = "1024px";
+ map.destroy();
+ }
+
+ function test_Control_PanZoomBar_clearDiv(t) {
+ t.plan(2);
+ map = new OpenLayers.Map('map', {controls:[]});
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+ control = new OpenLayers.Control.PanZoomBar();
+ map.addControl(control);
+ control.removeButtons();
+ var div = control.div;
+ map.destroy();
+ t.eq(div.childNodes.length, 0, "control's div cleared.");
+ t.eq(control.zoombarDiv, null, "zoombar div nullified.")
+ }
+
+ function test_Control_PanZoomBar_onButtonClick (t) {
+ t.plan(2);
+ map = new OpenLayers.Map('map', {controls:[], zoomMethod: null});
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+ control = new OpenLayers.Control.PanZoomBar();
+ map.addControl(control);
+ control.onButtonClick({'buttonXY': {'x': 0, 'y': 50}, buttonElement: control.zoombarDiv});
+ t.eq(map.zoom, 11, "zoom is correct on standard map");
+
+ map.fractionalZoom = true;
+ control.onButtonClick({'buttonXY': {'x': 0, 'y': 49}, buttonElement: control.zoombarDiv});
+ t.eq(map.zoom.toFixed(3), '10.545', "zoom is correct on fractional zoom map");
+
+ }
+
+ function test_Control_PanZoomBar_forceFixedZoomLevel_onButtonClick(t){
+ t.plan(1);
+ map = new OpenLayers.Map('map', {
+ controls: [],
+ fractionalZoom: true,
+ zoomMethod: null
+ });
+ var layer = new OpenLayers.Layer.WMS("Test Layer", "http://octo.metacarta.com/cgi-bin/mapserv?", {
+ map: "/mapdata/vmap_wms.map",
+ layers: "basic"
+ });
+ map.addLayer(layer);
+ control = new OpenLayers.Control.PanZoomBar({
+ forceFixedZoomLevel: true
+ });
+ map.addControl(control);
+
+ control.onButtonClick({
+ 'buttonXY': {
+ 'x': 0,
+ 'y': 49
+ },
+ buttonElement: control.zoombarDiv
+ });
+ t.eq(map.zoom, 11, "forceFixedZoomLevel makes sure that after a div click only fixed zoom levels are used even if the map has fractionalZoom");
+ }
+
+ function test_Control_PanZoomBar_forceFixedZoomLevel_zoomBarUp (t) {
+ var numRandomDrags = 25;
+ // plan one static recorded test and two for every random drag
+ t.plan(1 + (numRandomDrags * 2));
+
+
+ var map = new OpenLayers.Map('map', {
+ controls: [],
+ fractionalZoom: true,
+ zoomMethod: null
+ });
+ var layer = new OpenLayers.Layer.WMS("Test Layer", "http://octo.metacarta.com/cgi-bin/mapserv?", {
+ map: "/mapdata/vmap_wms.map",
+ layers: "basic"
+ });
+ map.addLayer(layer);
+
+ // zoom to a fractional ZoomLevel initially:
+ map.setCenter(new OpenLayers.LonLat(0, 0), 9.545);
+
+ control = new OpenLayers.Control.PanZoomBar({
+ forceFixedZoomLevel: true
+ });
+ map.addControl(control);
+
+ // The y values come from manually recording real values in an example
+ var evt = {
+ 'xy': {
+ 'x': 0,
+ 'y': -10.633
+ },
+ which: 1
+ };
+ control.zoomStart = {
+ 'x': 0,
+ 'y': 5.366
+ };
+ control.mouseDragStart = {
+ 'x': 0,
+ 'y': -10.633
+ };
+ control.deltaY = control.zoomStart.y - evt.xy.y
+ control.zoomBarUp(evt);
+ t.eq(map.zoom, 11, "forceFixedZoomLevel makes sure that after dragging of the handle only fixed zoom levels are used even if the map has fractionalZoom");
+
+ // randomly drag the handle around
+ // we should never get a zoom < 0 or a non-integer zoom, regardless of
+ // captured random values for start and end of the drag.
+ for (var i = 0; i < numRandomDrags; i++) {
+ var randStartY = Math.random() * 10 * ((i % 2 === 0) ? -1 : 1);
+ var randStopY = Math.random() * 160 * ((i % 2 === 1) ? -1 : 1);
+ var evt = {
+ 'xy': {
+ 'x': 0,
+ 'y': randStopY
+ },
+ which: 1
+ };
+ control.zoomStart = {
+ 'x': 0,
+ 'y': randStartY
+ };
+ control.mouseDragStart = {
+ 'x': 0,
+ 'y': randStopY
+ };
+ control.deltaY = control.zoomStart.y - evt.xy.y
+ control.zoomBarUp(evt);
+
+ t.eq(Math.floor(map.zoom), Math.ceil(map.zoom), 'Only integer zooms after random handle drag with forceFixedZoomLevel=true and fractionalZoom=true (current zoom was ' + map.zoom + ')');
+ t.ok(map.zoom >= 0, 'map.zoom is never < 0 after random handle drag with forceFixedZoomLevel=true and fractionalZoom=true');
+ }
+ }
+
+ function test_Control_PanZoomBar_shows (t) {
+ t.plan(22);
+
+ var control, map;
+
+ control = new OpenLayers.Control.PanZoomBar({panIcons: true, zoomWorldIcon: false});
+ map = new OpenLayers.Map('map', {controls: [control]});
+ t.eq(control.buttons.length, 6, "(a) pan, no world - expected number of buttons");
+ t.ok(control.buttons[0].id.match("_panup$"), "(a) pan, no world - pan up");
+ t.ok(control.buttons[1].id.match("_panleft$"), "(a) pan, no world - pan left");
+ t.ok(control.buttons[2].id.match("_panright$"), "(a) pan, no world - pan right");
+ t.ok(control.buttons[3].id.match("_pandown$"), "(a) pan, no world - pan down");
+ t.ok(control.buttons[4].id.match("_zoomin$"), "(a) pan, no world - zoom in");
+ t.ok(control.buttons[5].id.match("_zoomout$"), "(a) pan, no world - zoom out");
+ map.destroy();
+
+ control = new OpenLayers.Control.PanZoomBar({panIcons: true, zoomWorldIcon: true});
+ map = new OpenLayers.Map('map', {controls:[control]});
+ t.eq(control.buttons.length, 7, "(b) pan, world - expected number of buttons");
+ t.ok(control.buttons[0].id.match("_panup$"), "(b) pan, world - pan up");
+ t.ok(control.buttons[1].id.match("_panleft$"), "(b) pan, world - pan left");
+ t.ok(control.buttons[2].id.match("_zoomworld$"), "(b) pan, world - zoom world");
+ t.ok(control.buttons[3].id.match("_panright$"), "(b) pan, world - pan right");
+ t.ok(control.buttons[4].id.match("_pandown$"), "(b) pan, world - pan down");
+ t.ok(control.buttons[5].id.match("_zoomin$"), "(b) pan, world - zoom in");
+ t.ok(control.buttons[6].id.match("_zoomout$"), "(b) pan, world - zoom out");
+ map.destroy();
+
+ control = new OpenLayers.Control.PanZoomBar({panIcons: false, zoomWorldIcon: false});
+ map = new OpenLayers.Map('map', {controls:[control]});
+ t.eq(control.buttons.length, 2, "(c) no pan, no world - expected number of buttons");
+ t.ok(control.buttons[0].id.match("_zoomin$"), "(c) no pan, no world - zoom in");
+ t.ok(control.buttons[1].id.match("_zoomout$"), "(c) no pan, no world - zoom out");
+ map.destroy();
+
+ control = new OpenLayers.Control.PanZoomBar({panIcons: false, zoomWorldIcon: true});
+ map = new OpenLayers.Map('map', {controls:[control]});
+ t.eq(control.buttons.length, 3, "(d) no pan, world - expected number of buttons");
+ t.ok(control.buttons[0].id.match("_zoomin$"), "(d) no pan, world - zoom in");
+ t.ok(control.buttons[1].id.match("_zoomout$"), "(d) no pan, world - zoom out");
+ t.ok(control.buttons[2].id.match("_zoomworld$"), "(d) no pan, world - zoom world");
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Panel.html b/misc/openlayers/tests/Control/Panel.html
new file mode 100644
index 0000000..f02e643
--- /dev/null
+++ b/misc/openlayers/tests/Control/Panel.html
@@ -0,0 +1,382 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Control_Panel_constructor (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.Panel();
+ t.ok( control instanceof OpenLayers.Control.Panel, "new OpenLayers.Control returns object" );
+ t.eq( control.displayClass, "olControlPanel", "displayClass is correct" );
+ }
+ function test_Control_Panel_constructor2 (t) {
+ t.plan(19);
+ var map = new OpenLayers.Map('map');
+ var toolControl = new OpenLayers.Control.ZoomBox();
+ var AnotherToolControl = OpenLayers.Class(OpenLayers.Control, {
+ CLASS_NAME: 'mbControl.TestTool',
+ type: OpenLayers.Control.TYPE_TOOL
+ });
+ var anotherToolControl = new AnotherToolControl();
+ var ToggleControl = OpenLayers.Class(OpenLayers.Control, {
+ CLASS_NAME: 'mbControl.TestToggle',
+ type: OpenLayers.Control.TYPE_TOGGLE
+ });
+
+ var toggleControl = new ToggleControl();
+ var buttonControl = new OpenLayers.Control.Button({
+ trigger: function () {
+ t.ok(true, "trigger function of button is called.");
+ }
+ });
+
+ var panel = new OpenLayers.Control.Panel(
+ {defaultControl: anotherToolControl});
+ t.ok(panel instanceof OpenLayers.Control.Panel,
+ "new OpenLayers.Control.Panel returns object");
+ panel.redraw = function(){
+ panel.redrawsCount++;
+ OpenLayers.Control.Panel.prototype.redraw.apply(this, arguments);
+ };
+
+ // To get length of events.listeners error-free
+ var getListenerLength= function(events,key){
+ if(!events) {
+ return -2; // events is destroyed
+ } else if(!events.listeners) {
+ return -1; // events is destroyed
+ } else if(!events.listeners[key]) {
+ return 0; // no listener in event
+ } else {
+ return events.listeners[key].length;
+ }
+ };
+ var toolEventListenerLength = getListenerLength(toolControl.events,"activate");
+ panel.addControls([toolControl, anotherToolControl, toggleControl]);
+ t.eq(panel.controls.length, 3,
+ "added three controls to the panel");
+ panel.addControls([buttonControl]);
+
+ panel.redrawsCount = 0;
+ map.addControl(panel);
+ t.eq(getListenerLength(toolControl.events,"activate"), toolEventListenerLength+1,
+ "toolControl additional listener for \"activate\" after adding Panel to the map.");
+ t.ok((panel.redrawsCount > 0), "Redraw called on add panel to map " +
+ panel.redrawsCount + " times.");
+ t.ok((panel.active),"Panel is active after add panel to map.");
+
+ panel.redrawsCount = 0;
+ panel.addControls(new AnotherToolControl());
+ t.ok((panel.redrawsCount > 0),
+ "Redraw called on add control to panel after add panel to map " +
+ panel.redrawsCount + " times.");
+
+ panel.deactivate();
+ panel.redrawsCount = 0;
+ panel.activate();
+ t.ok((panel.redrawsCount > 0),"Redraw called on activate panel " +
+ panel.redrawsCount + " times.");
+
+ panel.activateControl(toolControl);
+ t.ok(toolControl.active && !anotherToolControl.active && !toggleControl.active && !buttonControl.active,
+ "activated one tool control, the other one is inactive and the toggle & button controls also.");
+
+ panel.activateControl(toggleControl);
+ t.eq(toggleControl.panel_div.className,"mbControlTestToggleItemActive olButton",
+ "className of icon div for toggle control is active.");
+ t.ok(toolControl.active && !anotherToolControl.active && toggleControl.active,
+ "activated the toggle control, which has no influence on the tool & togggle controls.");
+ panel.activateControl(buttonControl);
+ t.ok(toolControl.active && !anotherToolControl.active && toggleControl.active,
+ "activateContol calling for button, which has no influence on the tool & togggle controls.");
+ t.ok(!buttonControl.active,
+ "activateContol calling for button, button remains inactive.");
+ buttonControl.activate();
+ t.ok(buttonControl.active && toolControl.active && !anotherToolControl.active && toggleControl.active,
+ "activated the button control, which has no influence on the tool & togggle controls.");
+
+ panel.activateControl(anotherToolControl);
+ t.eq(anotherToolControl.panel_div.className,"mbControlTestToolItemActive olButton",
+ "className of icon div for anotherToolControl is active.");
+ t.eq(toolControl.panel_div.className,"olControlZoomBoxItemInactive olButton",
+ "className of icon div for toolControl is inactive.");
+ t.ok(!toolControl.active && anotherToolControl.active && toggleControl.active,
+ "activated the other tool control, the first one is inactive and the toggle control still active.");
+ t.ok(buttonControl.active,
+ "activated the other tool control, the button control still active.");
+
+ panel.destroy();
+ t.eq(getListenerLength(toolControl.events,"activate"), toolEventListenerLength,
+ "toolControl additional listeners removed after destroy Panel.");
+ map.destroy();
+ }
+ function test_Control_Panel_titles (t) {
+ t.plan(2);
+ var panel = new OpenLayers.Control.Panel();
+ var toolControl = new OpenLayers.Control.ZoomBox({
+ title:"Zoom box: Selecting it you can zoom on an area by clicking and dragging."
+ });
+ panel.addControls([toolControl]);
+ t.eq(panel.controls.length, 1, "added a control to the panel");
+ t.eq(panel.controls[0].title, toolControl.panel_div.title, "the title is correctly set");
+ }
+
+ function test_Control_Panel_getBy(t) {
+
+ var panel = {
+ getBy: OpenLayers.Control.Panel.prototype.getBy,
+ getControlsBy: OpenLayers.Control.Panel.prototype.getControlsBy,
+ controls: [
+ {foo: "foo", id: Math.random()},
+ {foo: "bar", id: Math.random()},
+ {foo: "foobar", id: Math.random()},
+ {foo: "foo bar", id: Math.random()},
+ {foo: "foo", id: Math.random()}
+ ]
+ };
+
+ var cases = [
+ {
+ got: panel.getControlsBy("foo", "foo"),
+ expected: [panel.controls[0], panel.controls[4]],
+ message: "(string literal) got two controls matching foo"
+ }, {
+ got: panel.getControlsBy("foo", "bar"),
+ expected: [panel.controls[1]],
+ message: "(string literal) got one control matching foo"
+ }, {
+ got: panel.getControlsBy("foo", "barfoo"),
+ expected: [],
+ message: "(string literal) got empty array for no foo match"
+ }, {
+ got: panel.getControlsBy("foo", /foo/),
+ expected: [panel.controls[0], panel.controls[2], panel.controls[3], panel.controls[4]],
+ message: "(regexp literal) got three controls containing string"
+ }, {
+ got: panel.getControlsBy("foo", /foo$/),
+ expected: [panel.controls[0], panel.controls[4]],
+ message: "(regexp literal) got three controls ending with string"
+ }, {
+ got: panel.getControlsBy("foo", /\s/),
+ expected: [panel.controls[3]],
+ message: "(regexp literal) got control containing space"
+ }, {
+ got: panel.getControlsBy("foo", new RegExp("BAR", "i")),
+ expected: [panel.controls[1], panel.controls[2], panel.controls[3]],
+ message: "(regexp object) got layers ignoring case"
+ }, {
+ got: panel.getControlsBy("foo", {test: function(str) {return str.length > 3;}}),
+ expected: [panel.controls[2], panel.controls[3]],
+ message: "(custom object) got controls with foo length greater than 3"
+ }
+ ];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, cases[i].message);
+ }
+
+
+ }
+
+ function test_Control_Panel_saveState (t) {
+ t.plan(11);
+ var map = new OpenLayers.Map('map');
+
+ var defaultControl = new OpenLayers.Control();
+ var panel = new OpenLayers.Control.Panel({
+ defaultControl: defaultControl
+ });
+ panel.addControls([new OpenLayers.Control(), defaultControl]);
+ map.addControl(panel);
+ t.eq(defaultControl.active, true,
+ "After panel activation default control is active.");
+ t.ok(panel.defaultControl,
+ "defaultControl not nullified after initial panel activation");
+ // activate the 1st control
+ panel.activateControl(panel.controls[0]);
+ panel.deactivate();
+ t.ok(!panel.controls[0].active && !panel.controls[1].active,
+ "No controls are active after panel deactivation.");
+ panel.activate();
+ t.eq(panel.controls[0].active, false,
+ "After panel reactivation first control is inactive.");
+ t.eq(panel.controls[1].active, true,
+ "After panel reactivation default control is active again.");
+ panel.destroy();
+
+ defaultControl = new OpenLayers.Control();
+ panel = new OpenLayers.Control.Panel({
+ saveState: true,
+ defaultControl: defaultControl
+ });
+ panel.addControls([new OpenLayers.Control(), defaultControl]);
+ map.addControl(panel);
+ t.eq(defaultControl.active, true,
+ "After panel activation default control is active.");
+ t.eq(panel.defaultControl, null,
+ "defaultControl nullified after initial panel activation");
+ // activate the 1st control, which will deactivate the 2nd
+ panel.activateControl(panel.controls[0]);
+ t.eq(panel.controls[1].active, false,
+ "2nd control deactivated with activation of 1st");
+ panel.deactivate();
+ t.ok(!panel.controls[0].active && !panel.controls[1].active,
+ "No controls are active after panel deactivation.");
+ panel.activate();
+ t.eq(panel.controls[0].active, true,
+ "After panel reactivation first control is active.");
+ t.eq(panel.controls[1].active, false,
+ "After panel reactivation second control is inactive.");
+ panel.destroy();
+ map.destroy();
+ }
+
+ function test_Control_Panel_autoActivate (t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var controlNoDeactive = new OpenLayers.Control({autoActivate:true});
+ var chkDeactivate = function () {
+ t.ok(false, "Tool control autoActivate:true was deactivated unnecessarily");
+ };
+ controlNoDeactive.events.on({deactivate: chkDeactivate});
+ var panel = new OpenLayers.Control.Panel();
+
+ map.addControl(panel);
+ panel.addControls([controlNoDeactive]);
+ controlNoDeactive.events.un({deactivate: chkDeactivate});
+ t.ok(!controlNoDeactive.active, "Tool control autoActivate:true is not active");
+
+ }
+
+ function test_Control_Panel_deactivate (t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ var panel = new OpenLayers.Control.Panel();
+ map.addControl(panel);
+ panel.addControls([control]);
+ t.ok(panel.div.innerHTML != "", "Panel displayed after activate");
+
+ panel.deactivate();
+ t.ok(panel.div.innerHTML == "",
+ "Panel is not displayed after deactivate without any active control");
+
+ map.destroy();
+ }
+
+ function test_allowDepress (t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+
+ var panel = new OpenLayers.Control.Panel();
+ panel.addControls([new OpenLayers.Control(),new OpenLayers.Control()]);
+ map.addControl(panel);
+
+ var control1 = panel.controls[1]
+
+ panel.activateControl(control1);
+
+ panel.allowDepress = false;
+ panel.activateControl(control1);
+ t.eq(control1.active, true,
+ "control1 remains active after calling again activateControl when allowDepress = false");
+ panel.allowDepress = true;
+ panel.activateControl(control1);
+ t.eq(control1.active, false,
+ "control1 is inactive after calling again activateControl when allowDepress = true");
+
+ // panel.deactivate();
+ map.destroy();
+ }
+
+ function test_iconOn_iconOff(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map');
+
+ var panel = new OpenLayers.Control.Panel();
+ var ctrl = new OpenLayers.Control({displayClass: 'ctrl'});
+ panel.addControls([ctrl]);
+
+ map.addControl(panel);
+
+ // add arbitrary classes to the panel div - we want to test
+ // than iconOn and iconOff do their jobs even when the panel
+ // div has application-specific classes.
+
+ ctrl.panel_div.className =
+ 'ctrlItemInactive fooItemActive fooItemInactive';
+
+ panel.iconOn.call(ctrl);
+ t.eq(ctrl.panel_div.className,
+ 'ctrlItemActive fooItemActive fooItemInactive',
+ 'iconOn behaves as expected');
+
+ ctrl.panel_div.className =
+ 'ctrlItemActive fooItemActive fooItemInactive';
+
+ panel.iconOff.call(ctrl);
+ t.eq(ctrl.panel_div.className,
+ 'ctrlItemInactive fooItemActive fooItemInactive',
+ 'iconOff behaves as expected');
+
+ map.destroy();
+ }
+
+ function test_buttonclick(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+ var panel1 = new OpenLayers.Control.Panel();
+ var div = document.createElement("div");
+ var panel2 = new OpenLayers.Control.Panel({div: div});
+ map.addControls([panel1, panel2]);
+
+ t.ok(map.events.listeners.buttonclick, "buttonclick event registered on map's Events instance for panel inside map");
+ t.ok(!panel1.events.element, "Panel inside map has no element on its Events instance");
+ t.ok(panel2.events.listeners.buttonclick, "buttonclick event registered on panel's Events instance if outside map")
+ t.ok(panel2.events.element === div, "Panel outside map has the panel's div as element on its Events instance");
+
+ }
+
+ function test_iconOniconOff (t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map"),
+ navControl = new OpenLayers.Control.Navigation({autoActivate: true}),
+ zbControl = new OpenLayers.Control.ZoomBox(),
+ panel = new OpenLayers.Control.Panel({defaultControl: navControl}),
+ navActiveClass, navInactiveClass, zbActiveClass, zbInactiveClass;
+
+ panel.addControls([navControl, zbControl]);
+ map.addControl(panel);
+
+ navControl.panel_div.className += " foo";
+ zbControl.panel_div.className = "bar " + zbControl.panel_div.className;
+
+ t.eq(navControl.panel_div.className, "olControlNavigationItemActive olButton foo",
+ "defaultControl className is set to [displayClass]Active on panel instantiation");
+ t.eq(zbControl.panel_div.className, "bar olControlZoomBoxItemInactive olButton",
+ "non-defaultControl className is set to [displayClass]Inactive on panel instantiation");
+
+ panel.activateControl(zbControl);
+
+ t.eq(zbControl.panel_div.className, "bar olControlZoomBoxItemActive olButton",
+ "active control class name with preceding secondary class name is set to [displayClass]Active");
+ t.eq(navControl.panel_div.className, "olControlNavigationItemInactive olButton foo",
+ "inactive control class name with trailing secondary class name is set to [displayClass]Inactive");
+
+ panel.activateControl(navControl);
+
+ t.eq(navControl.panel_div.className, "olControlNavigationItemActive olButton foo",
+ "active control class name with trailing secondary class name is set to [displayClass]Active");
+ t.eq(zbControl.panel_div.className, "bar olControlZoomBoxItemInactive olButton",
+ "inactive control class name with preceding secondary class name is set to [displayClass]Inactive");
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Permalink.html b/misc/openlayers/tests/Control/Permalink.html
new file mode 100644
index 0000000..0b729da
--- /dev/null
+++ b/misc/openlayers/tests/Control/Permalink.html
@@ -0,0 +1,453 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_Permalink_constructor (t) {
+ t.plan(42);
+
+ control = new OpenLayers.Control.Permalink();
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, document.location.href, "base is correct");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink('permalink', 'test.html');
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, 'test.html', "base is correct");
+ t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink('permalink');
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, document.location.href, "base is correct");
+ t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink(OpenLayers.Util.getElement('permalink'));
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, document.location.href, "base is correct");
+ t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink({anchor: true});
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, document.location.href, "base is correct");
+ t.ok(control.element == null, "element is null");
+ t.ok(control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink({anchor: false});
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, document.location.href, "base is correct");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink({});
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, document.location.href, "base is correct");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink({element: 'permalink', base: 'test.html'});
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, 'test.html', "base is correct");
+ t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object");
+ t.ok(!control.anchor, "anchor is correct");
+ control.destroy();
+
+ control = new OpenLayers.Control.Permalink({element: 'permalink', base: 'test.html', anchor: true});
+ t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object");
+ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct");
+ t.eq(control.base, 'test.html', "base is correct");
+ t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object");
+ t.ok(control.anchor, "anchor is correct");
+ control.destroy();
+ }
+ function test_Control_Permalink_uncentered (t) {
+ t.plan( 1 );
+
+ control = new OpenLayers.Control.Permalink('permalink');
+ map = new OpenLayers.Map('map');
+ map.addControl(control);
+ map.events.triggerEvent("changelayer", {});
+ t.ok(true, "permalink didn't bomb out.");
+ map.destroy();
+ }
+ function test_Control_Permalink_initwithelem (t) {
+ t.plan( 1 );
+
+ control = new OpenLayers.Control.Permalink(OpenLayers.Util.getElement('permalink'));
+ t.ok(true, "If the above line doesn't throw an error, we're safe.");
+ control.destroy();
+ }
+ function test_Control_Permalink_updateLinks (t) {
+ t.plan( 3 );
+
+ control = new OpenLayers.Control.Permalink('permalink');
+ t.ok( control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'}, {'isBaseLayer': false});
+ map.addLayer(layer);
+ layer.setVisibility(true);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getElement('permalink').href, location+"?zoom=2&lat=0&lon=1.75781&layers=BT"), 'pan sets permalink');
+
+ map.layers[1].setVisibility(false);
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getElement('permalink').href, location+"?zoom=2&lat=0&lon=1.75781&layers=BF"), 'setVisibility sets permalink');
+ map.destroy();
+ }
+ function test_Control_Permalink_updateLinksBase (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html" );
+ t.ok( control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B';
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base");
+ map.destroy();
+ }
+ function test_Control_Permalink_noElement (t) {
+ t.plan( 2 );
+ control = new OpenLayers.Control.Permalink( );
+ t.ok( control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map');
+ map.addControl(control);
+ t.eq(map.controls[4].div.firstChild.nodeName, "A", "Permalink control creates div with 'a' inside." );
+ map.destroy();
+ }
+ function test_Control_Permalink_base_with_query (t) {
+ t.plan( 3 );
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html?foo=bar" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://example.com" );
+ map.addLayer(layer);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?foo=bar&zoom=2&lat=0&lon=1.75781&layers=B';
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and querystring");
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html?foo=bar&" );
+ map.addControl(control);
+ map.pan(0, 0, {animate:false});
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and querystring ending with '&'");
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html?" );
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B';
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ map.pan(-5, 0, {animate:false});
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and querystring ending with '?'");
+ map.destroy();
+ }
+
+ function test_Control_Permalink_base_with_anchor (t) {
+ t.plan( 4 );
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html#foo" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://example.com" );
+ map.addLayer(layer);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B#foo';
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and anchor");
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html#" );
+ map.addControl(control);
+ map.pan(0, 0, {animate:false});
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B#';
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and an empty anchor");
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html?foo=bar#test" );
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?foo=bar&zoom=2&lat=0&lon=1.75781&layers=B#test';
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ map.pan(-5, 0, {animate:false});
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base, querystring and an anchor");
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html#foo", {anchor : true} );
+ map.addControl(control);
+ map.pan(0, 0, {animate:false});
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html#zoom=2&lat=0&lon=1.75781&layers=B';
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and an empty anchor");
+ }
+
+ function test_Control_Permalink_nonRepeating (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.Permalink('permalink', "./edit.html?zoom=3" );
+ t.ok( control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B';
+ t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with existing zoom in base");
+ map.destroy();
+ }
+
+ function test_Control_Permalink_customized(t) {
+ t.plan(2);
+
+ var argParserClass = OpenLayers.Class(OpenLayers.Control.ArgParser, {
+ CLASS_NAME: "CustomArgParser"
+ });
+
+ control = new OpenLayers.Control.Permalink(null, "./edit.html", {
+ argParserClass: argParserClass,
+ createParams: function(center, zoom, layers) {
+ var params = OpenLayers.Control.Permalink.prototype.createParams.apply(control, arguments);
+ params.customParam = "foo";
+ return params;
+ }
+ });
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+
+ t.eq(this.map.controls[this.map.controls.length-1].CLASS_NAME, "CustomArgParser", "Custom ArgParser added correctly.");
+ t.eq(control.div.firstChild.getAttribute("href"), "./edit.html?zoom=2&lat=0&lon=1.75781&layers=B&customParam=foo", "Custom parameter encoded correctly.");
+ map.destroy();
+ }
+
+ function test_Control_Permalink_createParams(t) {
+ t.plan(18);
+
+ var baseLayer = { 'isBaseLayer': true };
+
+ var m = {
+ 'getCenter': function() { return null; }
+ };
+
+ var pl = {
+ 'map': m,
+ 'base': {}
+ };
+
+ old_getParameters = OpenLayers.Util.getParameters;
+ OpenLayers.Util.getParameters = function(base) {
+ t.ok(base == pl.base, "correct base sent in to Util.getParameters()");
+ return g_Params;
+ };
+
+ //null center, null map.getCenter()
+ g_Params = {};
+ m.baseLayer = baseLayer;
+ var returnParams = OpenLayers.Control.Permalink.prototype.createParams.apply(pl, []);
+ t.ok(returnParams == g_Params, "correct params returned on null center");
+
+ //valid center, zoom, layers
+ g_Params = { 'test': {} };
+ var center = { 'lon': 1.2345678901, 'lat': 9.8765432109 };
+ var zoom = {};
+ var layers = [
+ { 'isBaseLayer': true },
+ baseLayer,
+ { 'isBaseLayer': false, 'getVisibility': function() { return true; } },
+ { 'isBaseLayer': false, 'getVisibility': function() { return false; } }
+ ];
+ var returnParams = OpenLayers.Control.Permalink.prototype.createParams.apply(pl, [center, zoom, layers]);
+
+ t.ok(returnParams.test == g_Params.test, "correct params returned from Util.getParameters() when valid center, zoom, layers");
+ t.ok(returnParams.zoom == zoom, "params.zoom set correctly when valid center, zoom, layers");
+ t.eq(returnParams.lon, 1.23457, "lon set and rounded correctly when valid center, zoom, layers");
+ t.eq(returnParams.lat, 9.87654, "lat set and rounded correctly when valid center, zoom, layers");
+ t.eq(returnParams.layers, "0BTF", "layers processed correctly when valid center, zoom, layers")
+
+
+ //null center, zoom, layers, with displayProjection
+ g_Params = { 'test': {} };
+ g_Projection = {};
+ m = {
+ 'baseLayer': baseLayer,
+ 'getProjectionObject': function() { return g_Projection; },
+ 'center': { 'lon': {}, 'lat': {} },
+ 'getCenter': function() { return this.center; },
+ 'zoom': {},
+ 'getZoom': function() { return this.zoom; },
+ 'layers': [
+ { 'isBaseLayer': false, 'getVisibility': function() { return true; } },
+ baseLayer,
+ { 'isBaseLayer': false, 'getVisibility': function() { return false; } },
+ { 'isBaseLayer': true }
+ ],
+ 'getLayers': function() { return this.layers; }
+ };
+ pl = {
+ 'base': {},
+ 'map': m,
+ 'displayProjection': {}
+ };
+
+ old_transform = OpenLayers.Projection.transform;
+ OpenLayers.Projection.transform = function(point, projObj, dispProj) {
+ t.ok(point.x = m.center.lon, "correct x value passed into transform");
+ t.ok(point.y = m.center.lat, "correct x value passed into transform");
+ t.ok(projObj == g_Projection, "correct projection object from map passed into transform");
+ t.ok(dispProj == pl.displayProjection, "correct displayProjection from control passed into transform");
+
+ return { 'x': 9.8765432109, 'y': 1.2345678901 };
+ };
+
+ center = zoom = layers = null;
+
+ var returnParams = OpenLayers.Control.Permalink.prototype.createParams.apply(pl, [center, zoom, layers]);
+ t.ok(returnParams.test == g_Params.test, "correct params returned from Util.getParameters() when null center, zoom, layers, with displayProjection");
+ t.ok(returnParams.zoom == m.zoom, "params.zoom set correctly when null center, zoom, layers, with displayProjection");
+ t.eq(returnParams.lon, 9.87654, "lon set, transformed, and rounded correctly when null center, zoom, layers, with displayProjection");
+ t.eq(returnParams.lat, 1.23457, "lat set, transformed, and rounded correctly when null center, zoom, layers, with displayProjection");
+ t.eq(returnParams.layers, "TBF0", "layers processed correctly when null center, zoom, layers, with displayProjection");
+
+ OpenLayers.Util.getParameters = old_getParameters;
+ OpenLayers.Projection.transform = old_transform;
+ }
+ function test_Control_Permalink_Anchor (t) {
+ t.plan(3);
+
+ control = new OpenLayers.Control.Permalink({anchor: true});
+ t.ok( control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'}, {'isBaseLayer': false});
+ map.addLayer(layer);
+ layer.setVisibility(true);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getParameterString(control.createParams()), "zoom=2&lat=0&lon=1.75781&layers=BT"), 'pan sets permalink');
+
+ map.layers[1].setVisibility(false);
+ t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getParameterString(control.createParams()), "zoom=2&lat=0&lon=1.75781&layers=BF"), 'setVisibility sets permalink');
+ map.destroy();
+ }
+
+ function test_Control_Permalink_AnchorBaseElement (t) {
+ t.plan(3);
+
+ control = new OpenLayers.Control.Permalink('permalink', document.location.href, {anchor: true});
+ t.ok( control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'}, {'isBaseLayer': false});
+ map.addLayer(layer);
+ layer.setVisibility(true);
+ if (!map.getCenter()) map.zoomToMaxExtent();
+ map.addControl(control);
+ map.pan(5, 0, {animate:false});
+ t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getElement('permalink').href, location+"#zoom=2&lat=0&lon=1.75781&layers=BT"), 'pan sets permalink');
+
+ map.layers[1].setVisibility(false);
+ t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getElement('permalink').href, location+"#zoom=2&lat=0&lon=1.75781&layers=BF"), 'setVisibility sets permalink');
+ map.destroy();
+ }
+
+ function test_center_from_map(t) {
+ t.plan(7);
+
+ var previous = window.location.hash;
+ window.location.hash = "";
+
+ var err;
+ try {
+ var map = new OpenLayers.Map({
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ controls: [
+ new OpenLayers.Control.Permalink({anchor: true})
+ ],
+ center: [1, 2],
+ zoom: 3
+ });
+ } catch (e) {
+ err = e;
+ }
+ if (err) {
+ t.fail("Map construction failure: " + err.message);
+ } else {
+ t.ok(true, "Map construction works");
+ }
+
+ // confirm that map center is correctly set
+ var center = map.getCenter();
+ t.eq(center.lon, 1, "map x");
+ t.eq(center.lat, 2, "map y")
+ t.eq(map.getZoom(), 3, "map z");
+
+ // confirm that location from map options has been added to url
+ var params = OpenLayers.Util.getParameters(window.location.hash.replace("#", "?"));
+ t.eq(params.lon, "1", "url x");
+ t.eq(params.lat, "2", "url y");
+ t.eq(params.zoom, "3", "url z");
+
+ map.destroy();
+ window.location.hash = previous;
+ }
+
+ function test_center_from_url(t) {
+ t.plan(6);
+
+ // In cases where the location is specified in the URL and given in
+ // the map options, we respect the location in the URL.
+ var previous = window.location.hash;
+ window.location.hash = "#zoom=6&lat=5&lon=4&layers=B"
+
+ var map = new OpenLayers.Map({
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ controls: [new OpenLayers.Control.Permalink({anchor: true})],
+ center: [0, 0],
+ zoom: 0
+ });
+
+ // confirm that map center is correctly set
+ var center = map.getCenter();
+ t.eq(center.lon, 4, "map x");
+ t.eq(center.lat, 5, "map y")
+ t.eq(map.getZoom(), 6, "map z");
+
+ var params = OpenLayers.Util.getParameters(window.location.hash.replace("#", "?"));
+ t.eq(params.lon, "4", "x set");
+ t.eq(params.lat, "5", "y set");
+ t.eq(params.zoom, "6", "z set");
+
+ map.destroy();
+ window.location.hash = previous;
+ }
+
+ </script>
+</head>
+<body>
+ <a id="permalink" href="">Permalink</a> <br />
+ <a id="edit_permalink" href="">Edit</a> <br />
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/PinchZoom.html b/misc/openlayers/tests/Control/PinchZoom.html
new file mode 100644
index 0000000..22db6a5
--- /dev/null
+++ b/misc/openlayers/tests/Control/PinchZoom.html
@@ -0,0 +1,134 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(2);
+ var control = new OpenLayers.Control.PinchZoom();
+ t.ok(control instanceof OpenLayers.Control.PinchZoom, "got an instance");
+ t.ok(control.handler instanceof OpenLayers.Handler.Pinch, "control has pinch handler");
+ control.destroy();
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control.PinchZoom();
+ control.destroy();
+ t.ok(!control.handler, "handler destroyed");
+ }
+
+ function test_activate(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control.PinchZoom();
+ t.ok(!control.active, "control not activated after construction");
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [control]
+ });
+ t.ok(control.active, "control activated after being added to the map");
+
+ control.deactivate();
+ t.ok(!control.active, "control deactivated");
+
+ map.destroy();
+ }
+
+ function test_pinchMove(t) {
+
+ var control = new OpenLayers.Control.PinchZoom();
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [control]
+ });
+
+ var log = [];
+ map.applyTransform = function(x, y, scale) {
+ log.push([x, y, scale]);
+ }
+
+ map.layerContainerOriginPx = {
+ x: -50, y: -50
+ };
+
+ control.pinchOrigin = {
+ x: 100, y: 50
+ };
+
+ var cases = [
+ {x: 100, y: 60, scale: 1, transform: [-50, -40, 1]},
+ {x: 150, y: 60, scale: 1, transform: [0, -40, 1]},
+ {x: 150, y: 60, scale: 2, transform: [-150, -140, 2]},
+ {x: 50, y: 20, scale: 2.5, transform: [-325, -230, 2.5]},
+ {x: 150, y: 60, scale: 2, transform: [-150, -140, 2]},
+ {x: 50, y: 20, scale: 0.25, transform: [13, -5, 0.25]}
+ ];
+
+ var len = cases.length;
+ t.plan(len*2);
+
+ var c;
+ for (var i=0; i<len; ++i) {
+ c = cases[i];
+ control.pinchMove({xy: {x: c.x, y: c.y}}, {scale: c.scale});
+ t.eq(log.length, i+1, i + " called once");
+ t.eq(log[i], c.transform, i + " correct transform");
+ }
+
+ }
+
+ function test_pinchMove_preservecenter(t) {
+
+ var control = new OpenLayers.Control.PinchZoom({
+ preserveCenter: true
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [control],
+ layers: [new OpenLayers.Layer('fake', {isBaseLayer: true})]
+ });
+ map.zoomToMaxExtent();
+
+ var centerPx = map.getPixelFromLonLat(map.getCenter());
+
+ control.pinchStart = function(evt, pinchData) {
+ t.eq(map.layerContainerOriginPx, {x: 0, y: 0}, "center preserved");
+ t.eq(map.getPixelFromLonLat(map.getCenter()), centerPx, "center preserved");
+ }
+
+ control.pinchStart(null);
+
+ var log = [];
+ map.applyTransform = function(x, y, scale) {
+ log.push([x, y, scale]);
+ }
+ control.pinchOrigin = map.getPixelFromLonLat(map.getCenter());
+
+ var cases = [
+ {scale: 1, transform: [0, 0, 1]},
+ {scale: 2, transform: [-128, -128, 2]},
+ {scale: 2.5, transform: [-192, -192, 2.5]},
+ {scale: 0.25, transform: [96, 96, 0.25]}
+ ];
+
+ var len = cases.length;
+ t.plan(2 + len*2);
+
+ var c;
+ for (var i=0; i<len; ++i) {
+ c = cases[i];
+ control.pinchMove(null, {scale: c.scale});
+ t.eq(log.length, i+1, i + " called once");
+ t.eq(log[i], c.transform, i + " correct transform");
+ }
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 256px; height: 256px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/SLDSelect.html b/misc/openlayers/tests/Control/SLDSelect.html
new file mode 100644
index 0000000..03b871c
--- /dev/null
+++ b/misc/openlayers/tests/Control/SLDSelect.html
@@ -0,0 +1,239 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(11);
+ var control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.Click);
+ t.eq(control.handler instanceof OpenLayers.Handler.Click, true, "Click handler created");
+ t.ok(control.handler.callbacks["click"] === control.select, "Click callback correctly set");
+ control.destroy();
+ control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.RegularPolygon, {handlerOptions: {irregular: true}});
+ t.eq(control.handler instanceof OpenLayers.Handler.RegularPolygon, true, "RegularPolygon handler created");
+ t.eq(control.handler.irregular, true, "RegularPolygon handler is irregular");
+ t.eq(control.handler.persist, false, "RegularPolygon handler is not persistant");
+ t.ok(control.handler.callbacks["done"] === control.select, "Done callback correctly set");
+ control.destroy();
+ control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.Polygon);
+ t.eq(control.handler instanceof OpenLayers.Handler.Polygon, true, "Polygon handler created");
+ t.ok(control.handler.callbacks["done"] === control.select, "Done callback correctly set");
+ control.destroy();
+ control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.Path);
+ t.eq(control.handler instanceof OpenLayers.Handler.Path, true, "Path handler created");
+ t.ok(control.handler.callbacks["done"] === control.select, "Done callback correctly set");
+ control.destroy();
+ var layer = new OpenLayers.Layer.WMS('Foo', 'http://foo', {LAYERS: 'A'});
+ control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.RegularPolygon, {layers: [layer]});
+ t.eq(control.layers.length, 1, "Layers property correctly set");
+ control.destroy();
+ layer.destroy();
+ }
+
+ function test_select(t) {
+ t.plan(9);
+ var parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Foo', 'http://foo', {LAYERS: 'AAA64'});
+ map.addLayer(layer);
+
+ var text =
+ '<?xml version="1.0" encoding="ISO-8859-1" ?>' +
+ '<schema' +
+ ' targetNamespace="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:rws="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' elementFormDefault="qualified" version="0.1" >' +
+ ' <import namespace="http://www.opengis.net/gml"' +
+ ' schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd" />' +
+ ' <element name="AAA64" ' +
+ ' type="rws:AAA64Type" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="AAA64Type">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiLineStringPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' <element name="OBJECTID" type="string"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ '</schema>';
+
+ OpenLayers.Control.SLDSelect.prototype.wfsCache[layer.id] = parser.read(text);
+ var control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.RegularPolygon,
+ {layers: [layer], clearOnDeactivate: true, handlerOptions: {irregular: true} });
+
+ var testEvent = function(evt) {
+ t.eq(evt.filters.length, 1, "Event has a filters array set");
+ t.eq(evt.filters[0] instanceof OpenLayers.Filter.Spatial, true, "Spatial filter has been created");
+ };
+
+ control.events.register("selected", this, testEvent);
+ map.addControl(control);
+ var geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(0, 0), 5, 4);
+ control.select(geometry);
+ control.events.unregister("selected", this, testEvent);
+ t.eq(map.layers.length, 2, "Selection layer has been created and added to the map");
+ t.eq(map.layers[1] instanceof OpenLayers.Layer.WMS, true, "A WMS layer has been created as the selection layer");
+ t.eq(map.layers[1].tileOptions.maxGetUrlLength, 2048, "Selection layer will automatically switch to HTTP Post if content gets longer than 2048");
+ var expected_sld = '<sld:StyledLayerDescriptor xmlns:sld="http://www.opengis.net/sld" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml"><sld:NamedLayer><sld:Name>AAA64</sld:Name><sld:UserStyle><sld:Name>default</sld:Name><sld:FeatureTypeStyle><sld:Rule><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:BBOX><ogc:PropertyName>geometry</ogc:PropertyName><gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326"><gml:coordinates decimal="." cs="," ts=" ">-3.5355339059327,-3.5355339059327 3.5355339059327,3.5355339059327</gml:coordinates></gml:Box></ogc:BBOX></ogc:Filter><sld:LineSymbolizer><sld:Stroke><sld:CssParameter name="stroke">#FF0000</sld:CssParameter><sld:CssParameter name="stroke-width">2</sld:CssParameter></sld:Stroke></sld:LineSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor>';
+
+ t.xml_eq(map.layers[1].params.SLD_BODY, expected_sld, "SLD generated correctly");
+
+ var geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(0, 0), 7, 4);
+ control.select(geometry);
+ t.eq(map.layers.length, 2, "Selection layer is reused when new selection is performed");
+
+ map.layers[0].setVisibility(false);
+ t.eq(map.layers[1].getVisibility(), false, "Visibility of selection layer is synchronized with source layer");
+ // activate would issue a SLD WMS DescribeLayer request and we are bypassing this here
+ control.active = true;
+ control.deactivate();
+ t.eq(map.layers.length, 1, "Selection layer is removed on deactive if clearOnDeactivate is set to true");
+ map.destroy();
+ }
+
+ function test_filterModificationOnSelected(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Foo', 'http://foo', {LAYERS: 'AAA64'});
+ map.addLayer(layer);
+
+ var text =
+ '<?xml version="1.0" encoding="ISO-8859-1" ?>' +
+ '<schema' +
+ ' targetNamespace="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:rws="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' elementFormDefault="qualified" version="0.1" >' +
+ ' <import namespace="http://www.opengis.net/gml"' +
+ ' schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd" />' +
+ ' <element name="AAA64" ' +
+ ' type="rws:AAA64Type" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="AAA64Type">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiLineStringPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' <element name="OBJECTID" type="string"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ '</schema>';
+
+ OpenLayers.Control.SLDSelect.prototype.wfsCache[layer.id] = parser.read(text);
+ var control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.RegularPolygon,
+ {layers: [layer], clearOnDeactivate: true, handlerOptions: {irregular: true} });
+
+ var testEvent = function(evt) {
+ // manipulate filter
+ var bbox = OpenLayers.Bounds.fromString('1,2,3,4');
+ evt.filters[0].value = bbox;
+ };
+ control.events.register("selected", this, testEvent);
+ map.addControl(control);
+ var geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(0, 0), 5, 4);
+ control.select(geometry);
+ control.events.unregister("selected", this, testEvent);
+
+ var expected_sld = '<sld:StyledLayerDescriptor xmlns:sld="http://www.opengis.net/sld" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml"><sld:NamedLayer><sld:Name>AAA64</sld:Name><sld:UserStyle><sld:Name>default</sld:Name><sld:FeatureTypeStyle><sld:Rule><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:BBOX><ogc:PropertyName>geometry</ogc:PropertyName><gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326"><gml:coordinates decimal="." cs="," ts=" ">1,2 3,4</gml:coordinates></gml:Box></ogc:BBOX></ogc:Filter><sld:LineSymbolizer><sld:Stroke><sld:CssParameter name="stroke">#FF0000</sld:CssParameter><sld:CssParameter name="stroke-width">2</sld:CssParameter></sld:Stroke></sld:LineSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor>';
+
+ t.xml_eq(map.layers[1].params.SLD_BODY, expected_sld, "Filter / SLD manipulated in select-callback correctly");
+
+ }
+
+ function test_multiselect(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Multi', 'http://foo', {LAYERS: 'KGNAT.VKUNSTWERK,KGNAT.LKUNSTWERK,KGNAT.PKUNSTWERK'});
+ map.addLayer(layer);
+
+ var text =
+ '<?xml version="1.0" encoding="ISO-8859-1" ?>' +
+ '<schema' +
+ ' targetNamespace="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:rws="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' elementFormDefault="qualified" version="0.1" >' +
+ ' <import namespace="http://www.opengis.net/gml"' +
+ ' schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd" />' +
+ ' <element name="KGNAT.VKUNSTWERK" ' +
+ ' type="rws:KGNAT.VKUNSTWERKType" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="KGNAT.VKUNSTWERKType">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiPolygonPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ ' <element name="KGNAT.LKUNSTWERK" ' +
+ ' type="rws:KGNAT.LKUNSTWERKType" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="KGNAT.LKUNSTWERKType">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiLineStringPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ ' <element name="KGNAT.PKUNSTWERK" ' +
+ ' type="rws:KGNAT.PKUNSTWERKType" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="KGNAT.PKUNSTWERKType">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiPointPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ '</schema>';
+
+ OpenLayers.Control.SLDSelect.prototype.wfsCache[layer.id] = parser.read(text);
+ var control = new OpenLayers.Control.SLDSelect(OpenLayers.Handler.RegularPolygon, {handlerOptions: {irregular: true}, layers: [layer]});
+ var testEvent = function(evt) {
+ t.eq(evt.filters.length, 3, "Event has a filters array set with length tree");
+ };
+ control.events.register("selected", this, testEvent);
+
+ map.addControl(control);
+ var geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+ new OpenLayers.Geometry.Point(0, 0), 5, 4);
+ control.select(geometry);
+ var expected_sld = '<sld:StyledLayerDescriptor xmlns:sld="http://www.opengis.net/sld" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml"><sld:NamedLayer><sld:Name>KGNAT.VKUNSTWERK</sld:Name><sld:UserStyle><sld:Name>default</sld:Name><sld:FeatureTypeStyle><sld:Rule><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:BBOX><ogc:PropertyName>geometry</ogc:PropertyName><gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326"><gml:coordinates decimal="." cs="," ts=" ">-3.5355339059327,-3.5355339059327 3.5355339059327,3.5355339059327</gml:coordinates></gml:Box></ogc:BBOX></ogc:Filter><sld:PolygonSymbolizer><sld:Fill><sld:CssParameter name="fill">#FF0000</sld:CssParameter></sld:Fill></sld:PolygonSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer><sld:NamedLayer><sld:Name>KGNAT.LKUNSTWERK</sld:Name><sld:UserStyle><sld:Name>default</sld:Name><sld:FeatureTypeStyle><sld:Rule><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:BBOX><ogc:PropertyName>geometry</ogc:PropertyName><gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326"><gml:coordinates decimal="." cs="," ts=" ">-3.5355339059327,-3.5355339059327 3.5355339059327,3.5355339059327</gml:coordinates></gml:Box></ogc:BBOX></ogc:Filter><sld:LineSymbolizer><sld:Stroke><sld:CssParameter name="stroke">#FF0000</sld:CssParameter><sld:CssParameter name="stroke-width">2</sld:CssParameter></sld:Stroke></sld:LineSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer><sld:NamedLayer><sld:Name>KGNAT.PKUNSTWERK</sld:Name><sld:UserStyle><sld:Name>default</sld:Name><sld:FeatureTypeStyle><sld:Rule><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:BBOX><ogc:PropertyName>geometry</ogc:PropertyName><gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326"><gml:coordinates decimal="." cs="," ts=" ">-3.5355339059327,-3.5355339059327 3.5355339059327,3.5355339059327</gml:coordinates></gml:Box></ogc:BBOX></ogc:Filter><sld:PointSymbolizer><sld:Graphic><sld:Mark><sld:WellKnownName>square</sld:WellKnownName><sld:Fill><sld:CssParameter name="fill">#FF0000</sld:CssParameter></sld:Fill><sld:Stroke/></sld:Mark><sld:Size>10</sld:Size></sld:Graphic></sld:PointSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor>';
+
+ t.xml_eq(map.layers[1].params.SLD_BODY, expected_sld, "SLD generated correctly");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 100px; height: 100px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Scale.html b/misc/openlayers/tests/Control/Scale.html
new file mode 100644
index 0000000..1d43b25
--- /dev/null
+++ b/misc/openlayers/tests/Control/Scale.html
@@ -0,0 +1,54 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ OpenLayers.Lang.setCode('en');
+ var map;
+ function test_Control_Scale_constructor (t) {
+ t.plan( 2 );
+
+ control = new OpenLayers.Control.Scale();
+ t.ok( control instanceof OpenLayers.Control.Scale, "new OpenLayers.Control returns object" );
+ t.eq( control.displayClass, "olControlScale", "displayClass is correct" );
+ }
+ function test_Control_Scale_initwithelem (t) {
+ t.plan( 1 );
+
+ control = new OpenLayers.Control.Scale(OpenLayers.Util.getElement('scale'));
+ t.ok(true, "If this happens, then we passed. (FF throws an error above otherwise)");
+ }
+ function test_Control_Scale_updateScale (t) {
+ t.plan( 4 );
+
+ control = new OpenLayers.Control.Scale('scale');
+ t.ok( control instanceof OpenLayers.Control.Scale, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map', {zoomMethod: null});
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ t.eq(OpenLayers.Util.getElement('scale').innerHTML, "Scale = 1 : 443M", "Scale set by default." );
+ map.zoomIn();
+ t.eq(OpenLayers.Util.getElement('scale').innerHTML, "Scale = 1 : 221M", "Zooming in changes scale" );
+ map.baseLayer.resolutions = [OpenLayers.Util.getResolutionFromScale(110)];
+ map.zoomTo(0);
+ t.eq(OpenLayers.Util.getElement('scale').innerHTML, "Scale = 1 : 110", "Scale of 100 isn't rounded" );
+ }
+ function test_Control_Scale_internalScale (t) {
+ t.plan(2);
+ control = new OpenLayers.Control.Scale();
+ t.ok( control instanceof OpenLayers.Control.Scale, "new OpenLayers.Control returns object" );
+ map = new OpenLayers.Map('map', {zoomMethod: null});
+ layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ t.eq(control.div.firstChild.innerHTML, "Scale = 1 : 443M", "Internal scale displayed properly.");
+ }
+ </script>
+</head>
+<body>
+ <a id="scale" href="">Scale</a> <br />
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/ScaleLine.html b/misc/openlayers/tests/Control/ScaleLine.html
new file mode 100644
index 0000000..e863fd9
--- /dev/null
+++ b/misc/openlayers/tests/Control/ScaleLine.html
@@ -0,0 +1,187 @@
+<html>
+<head>
+ <script type="text/javascript">var oldAlert = window.alert, gMess; window.alert = function(message) {gMess = message; return true;};</script>
+ <!-- this gmaps key generated for http://openlayers.org/dev/ -->
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA9XNhd8q0UdwNC7YSO4YZghSPUCi5aRYVveCcVYxzezM4iaj_gxQ9t-UajFL70jfcpquH5l1IJ-Zyyw'></script>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var validkey = (window.location.protocol == "file:") ||
+ (window.location.host == "localhost") ||
+ (window.location.host == "openlayers.org");
+
+ function test_initialize(t) {
+ t.plan(2);
+ var control = new OpenLayers.Control.ScaleLine();
+ t.ok(control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ t.eq(control.displayClass, "olControlScaleLine", "displayClass is correct" );
+ control.destroy();
+ }
+
+ function test_initwithelem(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control.ScaleLine({"div":OpenLayers.Util.getElement('ScaleLine')});
+ t.ok(true, "If this happens, then we passed. (FF throws an error above otherwise)");
+ control.destroy();
+ }
+
+ function test_calcDegrees(t) {
+ t.plan(5);
+ var control = new OpenLayers.Control.ScaleLine();
+ t.ok(control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ t.eq(control.div.firstChild.style.visibility, "visible", "top scale is present.");
+ t.eq(control.div.lastChild.style.visibility, "visible", "bottom scale is present.");
+ t.eq(control.div.firstChild.innerHTML, "10000 km", "top scale has correct text.");
+ t.eq(control.div.lastChild.innerHTML, "5000 mi", "bottom scale has correct text.");
+ map.destroy();
+ }
+
+ function test_calcsOther (t) {
+ t.plan(5);
+ var control = new OpenLayers.Control.ScaleLine();
+ t.ok(control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ var map = new OpenLayers.Map('map');
+ map.units = "mi";
+ var layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ t.eq(control.div.firstChild.style.visibility, "visible", "top scale is present.");
+ t.eq(control.div.lastChild.style.visibility, "visible", "bottom scale is present.");
+ t.eq(control.div.firstChild.innerHTML, "100 km", "top scale has correct text.");
+ t.eq(control.div.lastChild.innerHTML, "100 mi", "bottom scale has correct text.");
+ map.destroy();
+ }
+
+ function test_calcMeters (t) {
+ t.plan(5);
+ // this example is taken from the projected-map.html OpenLayers example
+ var lat = 900863;
+ var lon = 235829;
+ var zoom = 6;
+ var map = new OpenLayers.Map( 'map' );
+ var basemap = new OpenLayers.Layer.WMS( "Boston",
+ "http://boston.freemap.in/cgi-bin/mapserv?",
+ {
+ map: '/www/freemap.in/boston/map/gmaps.map',
+ layers: 'border,water,roads,rapid_transit,buildings',
+ format: 'png',
+ transparent: 'off'
+ },
+
+ {
+ maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656),
+ maxResolution: 296985/1024,
+ projection:"EPSG:2805", // Used in WMS/WFS requests.
+ units: "m" // Only neccesary for working with scales.
+ } );
+
+ map.addLayer(basemap);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ var control = new OpenLayers.Control.ScaleLine();
+ t.ok( control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ map.addControl(control);
+ t.eq(control.div.firstChild.style.visibility, "visible", "top scale is present.");
+ t.eq(control.div.lastChild.style.visibility, "visible", "bottom scale is present.");
+ t.eq(control.div.firstChild.innerHTML, "200 m", "top scale has correct text.");
+ t.eq(control.div.lastChild.innerHTML, "1000 ft", "bottom scale has correct text.");
+ map.destroy();
+ }
+
+ function test_useArguments (t) {
+ t.plan(5);
+ var control = new OpenLayers.Control.ScaleLine({topOutUnits: 'dd'} );
+ t.ok( control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ t.eq(control.div.firstChild.style.visibility, "visible", "top scale is present.");
+ t.eq(control.div.lastChild.style.visibility, "visible", "bottom scale is present.");
+ t.eq(control.div.firstChild.innerHTML, "100 dd", "top scale has correct text.");
+ t.eq(control.div.lastChild.innerHTML, "5000 mi", "bottom scale has correct text.");
+ map.destroy();
+ }
+
+ function test_respectZoom (t) {
+ if(validkey) {
+ t.plan( 4 );
+ } else {
+ t.plan( 3 );
+ }
+ // ok, switch the units we use for zoomed in values. This will test that we're both
+ // correctly respecting all specified parameters and that we're switching to the
+ // "in" units when zoomed in
+ var control = new OpenLayers.Control.ScaleLine({topOutUnits : "mi", bottomOutUnits: "km", topInUnits: 'ft', bottomInUnits: 'm'});
+ t.ok( control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Test Layer', "http://octo.metacarta.com/cgi-bin/mapserv", {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ var widthIsOk = true;
+ for (var i=0; i<map.numZoomLevels && widthIsOk; i++) {
+ map.zoomTo(i);
+ var w1 = parseInt(control.eTop.style.width);
+ var w2 = parseInt(control.eBottom.style.width);
+ widthIsOk = w1 <= control.maxWidth && w2 <= control.maxWidth;
+ }
+ t.ok(widthIsOk, "respects maxWidth at all zoom levels in dd");
+
+ widthIsOk = true;
+ control.maxWidth = 200;
+ for (var i=0; i<map.numZoomLevels && widthIsOk; i++) {
+ map.zoomTo(i);
+ var w1 = parseInt(control.eTop.style.width);
+ var w2 = parseInt(control.eBottom.style.width);
+ widthIsOk = w1 <= control.maxWidth && w2 <= control.maxWidth;
+ }
+ t.ok(widthIsOk, "respects modified maxWidth at all zoom levels in dd");
+
+ if (validkey) {
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ var control = new OpenLayers.Control.ScaleLine({topOutUnits : "mi", bottomOutUnits: "km", topInUnits: 'ft', bottomInUnits: 'm'});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ var widthIsOk = true;
+ for (var i=0; i<map.numZoomLevels && widthIsOk; i++) {
+ map.zoomTo(i);
+ var w1 = parseInt(control.eTop.style.width);
+ var w2 = parseInt(control.eBottom.style.width);
+ widthIsOk = w1 <= control.maxWidth && w2 <= control.maxWidth;
+ }
+ t.ok(widthIsOk, "respects maxWidth at all zoom levels in m");
+ } else {
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+
+ map.destroy();
+ }
+ function test_ie_oneunit(t) {
+ t.plan(2);
+ var control = new OpenLayers.Control.ScaleLine({bottomOutUnits:'',bottomInUnits:'',maxWidth:150});
+ t.ok(control instanceof OpenLayers.Control.ScaleLine, "new OpenLayers.Control returns object" );
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('Test Layer', "bogus", {});
+ map.addLayer(layer);
+ map.zoomTo(0);
+ map.addControl(control);
+ t.ok(true, "invisible bottom scale doesn't cause scaleline failure (IE only)");
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <a id="ScaleLine" href="">ScaleLine</a> <br/>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/SelectFeature.html b/misc/openlayers/tests/Control/SelectFeature.html
new file mode 100644
index 0000000..6e522e7
--- /dev/null
+++ b/misc/openlayers/tests/Control/SelectFeature.html
@@ -0,0 +1,632 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Control_SelectFeature_constructor(t) {
+ t.plan(5);
+ var options = {
+// geometryTypes: "foo"
+ };
+ var layer = "bar";
+ var control = new OpenLayers.Control.SelectFeature([layer], options);
+ t.ok(control instanceof OpenLayers.Control.SelectFeature,
+ "new OpenLayers.Control.SelectFeature returns an instance");
+ t.eq(control.layers[0], "bar",
+ "constructor with array of layers sets layer correctly");
+// t.eq(control.handlers.feature.geometryTypes, "foo",
+// "constructor sets options correctly on feature handler");
+ t.ok(control.layer instanceof OpenLayers.Layer.Vector.RootContainer, "control has a root container layer if constructor was called with an array of layers");
+
+ control = new OpenLayers.Control.SelectFeature(layer, options);
+ t.eq(control.layers, null, "this.layers is null if constructor called with a single layer");
+ t.eq(control.layer, layer, "this.layer holds the layer that was passed with the constructor if called with a single layer")
+ }
+
+ function test_Control_SelectFeature_destroy(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.SelectFeature(layer, {box: true});
+ control.handlers.feature.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on feature handler");
+ }
+ control.handlers.box.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on box handler");
+ }
+// should nullify the layer property here
+ control.destroy();
+
+ }
+
+ function test_Control_SelectFeature_select(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector();
+ var layer2 = new OpenLayers.Layer.Vector();
+ map.addLayers([layer1, layer2]);
+ var control = new OpenLayers.Control.SelectFeature([layer1, layer2]);
+ var feature1 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,1));
+ var feature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,0));
+ layer1.addFeatures(feature1);
+ layer2.addFeatures(feature2);
+ var drawFeature = function(feature, style) {
+ feature.layer.styleMap.createSymbolizer(feature, style);
+ }
+ layer1.drawFeature = drawFeature;
+ layer2.drawFeature = drawFeature;
+ control.select(feature1);
+ t.eq(feature1.renderIntent, "select", "render intent is set to select");
+ control.select(feature2);
+ t.eq(feature2.renderIntent, "select", "render intent is set to select");
+ control.unselect(feature1);
+ t.eq(feature1.renderIntent, "default", "render intent is set back to default");
+ control.unselect(feature2);
+ t.eq(feature2.renderIntent, "default", "render intent is set back to default");
+ }
+
+ function test_Control_SelectFeature_clickFeature(t) {
+ t.plan(6);
+ // mock up layer
+ var layer = {
+ selectedFeatures: [],
+ drawFeature: function() {},
+ events: {
+ triggerEvent: function() {}
+ }
+ };
+ // mock up active control
+ var control = new OpenLayers.Control.SelectFeature(layer);
+ control.handlers.feature = {
+ evt: {}
+ };
+ // mock up features
+ var features = new Array(4);
+ for(var i=0; i<features.length; ++i) {
+ features[i] = {
+ id: Math.random(),
+ tested: 0,
+ style: "",
+ layer: layer
+ };
+ }
+
+ // test that onSelect gets called properly
+ control.onSelect = function(feature) {
+ feature.tested += 1;
+ t.eq(feature.id, features[feature.index].id,
+ "onSelect called with proper feature (" + feature.index + ")");
+ t.eq(feature.tested, feature.test,
+ "onSelect called only once for feature (" + feature.index + ")");
+ t.ok(this == control, "onSelect called in the scope of the control if control.scope is not provided");
+ }
+
+ // test that onUnselect gets called properly
+ control.onUnselect = function(feature) {
+ feature.tested += 1;
+ t.eq(feature.id, features[feature.index].id,
+ "onUnselect called with proper feature (" + feature.index + ")");
+ t.eq(feature.tested, feature.test,
+ "onUnselect called only once for feature (" + feature.index + ")");
+ t.ok(this == control, "onUnselect called in the scope of the control if control.scope is not provided");
+ }
+
+ // mock up first click on first feature (runs 3 tests from onSelect)
+ var feature = features[0];
+ feature.index = 0;
+ feature.test = 1;
+ control.clickFeature(feature);
+
+ // mock up second click on first feature (runs no tests - already selected)
+ control.toggle = false;
+ control.clickFeature(feature);
+
+ // mock up second click on first feature (runs 3 tests from onUnselect)
+ control.toggle = true;
+ feature.test = 2;
+ control.clickFeature(feature);
+
+
+ }
+
+ function test_box(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ map.setBaseLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(1,1));
+ var control = new OpenLayers.Control.SelectFeature(layer, {'multiple': true, box: true });
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ var feature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,1));
+ var feature3 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-2,-2));
+ var feature4 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0, 0), new OpenLayers.Geometry.Point(1, 1)
+ ]));
+ layer.addFeatures([feature, feature2, feature3, feature4]);
+ control.setMap(map);
+ map.getLonLatFromPixel = function(arg) {
+ return new OpenLayers.LonLat(arg.x, arg.y);
+ }
+ control.selectBox(new OpenLayers.Bounds(-1, -1, 2, 2));
+ t.eq(layer.selectedFeatures.length, 3, "box around all features selects 3 features");
+
+ control.selectBox(new OpenLayers.Bounds(-3, -3, -1, -1));
+ t.eq(layer.selectedFeatures.length, 4, "box around other features doesn't turn off already selected features.");
+
+ control.multipleSelect = function() {
+ return false;
+ };
+ control.selectBox(new OpenLayers.Bounds(-3, -3, -1, -1));
+ t.eq(layer.selectedFeatures.length, 1, "box around other features correctly turns off already selected features.");
+
+ control.geometryTypes = null;
+ control.selectBox(new OpenLayers.Bounds(-100, -100, 100, 100));
+ t.eq(layer.selectedFeatures.length, layer.features.length, "all features selected with no geometryTypes filter");
+
+ control.geometryTypes = ["OpenLayers.Geometry.Point"];
+ control.selectBox(new OpenLayers.Bounds(-100, -100, 100, 100));
+ t.eq(layer.selectedFeatures.length, 3, "3 features selected with geometryTypes filter");
+
+
+ }
+
+
+ function test_selectBox_events(t){
+ t.plan(8);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector();
+ map.addLayer(layer1);
+ map.setBaseLayer(layer1);
+ var layer2 = new OpenLayers.Layer.Vector();
+ map.addLayer(layer2);
+ map.setCenter(new OpenLayers.LonLat(1,1));
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,1));
+ layer1.addFeatures([feature]);
+ var control = new OpenLayers.Control.SelectFeature(layer1);
+ control.setMap(map);
+ map.getLonLatFromPixel = function(arg) {
+ return new OpenLayers.LonLat(arg.x, arg.y);
+ }
+ control.activate();
+ var firesBoxselectionstart = false;
+ var beforeSelectingNumberOfFeatures = -1;
+ var firesBoxselectionend = false;
+ var afterSelectingNumberOfFeatures = -1;
+ control.events.register("boxselectionstart",null, function(e){
+ firesBoxselectionstart=true;
+ t.eq(e.layers.length,1,"right number of layers in event boxselectionstart");
+ t.eq(layer1.id, e.layers[0].id,"correct layer in event boxselectionstart");
+ beforeSelectingNumberOfFeatures = e.layers[0].selectedFeatures.length;
+ });
+ control.events.register("boxselectionend",null, function(e){
+ firesBoxselectionend=true;
+ t.eq(e.layers.length,1,"right number of layers in event boxselectionend");
+ t.eq(layer1.id, e.layers[0].id,"correct layer in event boxselectionend");
+ afterSelectingNumberOfFeatures = e.layers[0].selectedFeatures.length;
+ });
+ var bounds = new OpenLayers.Bounds(-1, -1, 2, 2);
+ control.selectBox(bounds);
+ t.ok(firesBoxselectionstart,"selectBox fires boxselectionstart event");
+ t.eq(beforeSelectingNumberOfFeatures,0,"boxselectionstart fires before selection of feature");
+ t.ok(firesBoxselectionend,"selectBox fires boxselectionend event");
+ t.eq(afterSelectingNumberOfFeatures,1,"boxselectionend fires after feature selected");
+ }
+ function test_Control_SelectFeature_activate(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.SelectFeature(layer, {box: true});
+ map.addControl(control);
+ t.ok(!control.handlers.feature.active,
+ "feature handler is not active prior to activating control");
+ t.ok(!control.handlers.box.active,
+ "box handler is not active prior to activating control");
+ control.activate();
+ t.ok(control.handlers.feature.active,
+ "feature handler is active after activating control");
+ t.ok(control.handlers.box.active,
+ "box handler is active after activating control");
+ }
+
+ function test_Control_SelectFeature_deactivate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.SelectFeature(layer, {box: true});
+ map.addControl(control);
+
+ control.activate();
+ control.handlers.feature.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on feature handler");
+ OpenLayers.Handler.Feature.prototype.deactivate.apply(this, arguments);
+ }
+ control.handlers.box.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on box handler");
+ }
+ control.deactivate();
+ }
+
+ function test_highlighyOnly(t) {
+ t.plan(23);
+
+ /*
+ * setup
+ */
+
+ var map, layer, ctrl1, ctrl2, _feature, feature, evt, _style;
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Vector("name", {isBaseLayer: true});
+ map.addLayer(layer);
+
+ ctrl1 = new OpenLayers.Control.SelectFeature(layer, {
+ highlightOnly: false,
+ hover: false
+ });
+ map.addControl(ctrl1);
+
+ ctrl2 = new OpenLayers.Control.SelectFeature(layer, {
+ highlightOnly: true,
+ hover: true
+ });
+ map.addControl(ctrl2);
+
+ ctrl2.activate();
+ ctrl1.activate();
+
+ feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+
+ // override the layer's getFeatureFromEvent func so that it always
+ // returns the feature referenced to by _feature
+ layer.getFeatureFromEvent = function(evt) { return _feature; };
+
+ evt = {xy: new OpenLayers.Pixel(Math.random(), Math.random())};
+
+ map.zoomToMaxExtent();
+
+ /*
+ * tests
+ */
+
+ // with renderIntent
+
+ ctrl1.renderIntent = "select";
+ ctrl2.renderIntent = "temporary";
+
+ // mouse over feature, feature is drawn with "temporary"
+ _feature = feature;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "temporary",
+ "feature drawn with expected render intent after \"mouseover\"");
+ t.eq(feature._lastHighlighter, ctrl2.id,
+ "feature._lastHighlighter properly set after \"mouseover\"");
+ t.eq(feature._prevHighlighter, undefined,
+ "feature._prevHighlighter properly set after \"mouseover\"");
+
+ // click in feature, feature is drawn with "select"
+ _feature = feature;
+ evt.type = "click";
+ map.events.triggerEvent("click", evt);
+ t.eq(feature.renderIntent, "select",
+ "feature drawn with expected render intent after \"clickin\"");
+ t.eq(feature._lastHighlighter, ctrl1.id,
+ "feature._lastHighlighter properly set after \"clickin\"");
+ t.eq(feature._prevHighlighter, ctrl2.id,
+ "feature._prevHighlighter properly set after \"clickin\"");
+
+ // mouse out of feature, feature is still drawn with "select"
+ _feature = null;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "select",
+ "feature drawn with expected render intent after \"mouseout\"");
+ t.eq(feature._lastHighlighter, ctrl1.id,
+ "feature._lastHighlighter properly set after \"nouseout\"");
+ t.ok(feature._prevHighlighter, ctrl2.id,
+ "feature._prevHighlighter properly set after \"mouseout\"");
+
+ // mouse over feature again, feature is drawn with "temporary"
+ _feature = feature;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "temporary",
+ "feature drawn with expected render intent after \"mouseover\"");
+ t.eq(feature._lastHighlighter, ctrl2.id,
+ "feature._lastHighlighter properly set after \"mouseover\"");
+ t.eq(feature._prevHighlighter, ctrl1.id,
+ "feature._prevHighlighter properly set after \"mouseover\"");
+
+ // mouve out of feature again, feature is still drawn with "select"
+ _feature = null;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "select",
+ "feature drawn with expected render intent after \"mouseout\"");
+ t.eq(feature._lastHighlighter, ctrl1.id,
+ "feature._lastHighlighter properly set after \"mouseout\"");
+ t.eq(feature._prevHighlighter, undefined,
+ "feature._prevHighlighter properly set after \"mouseout\"");
+
+ // click out of feature, feature is drawn with "default"
+ _feature = null;
+ evt.type = "click";
+ map.events.triggerEvent("click", evt);
+ t.eq(feature.renderIntent, "default",
+ "feature drawn with expected render intent after \"clickout\"");
+ t.eq(feature._lastHighlighter, undefined,
+ "feature._lastHighlighter properly set after \"clickout\"");
+ t.eq(feature._prevHighlighter, undefined,
+ "feature._prevHighlighter properly set after \"clickout\"");
+
+ // with selectStyle
+
+ ctrl1.selectStyle = OpenLayers.Feature.Vector.style["select"];
+ ctrl2.selectStyle = OpenLayers.Feature.Vector.style["temporary"];
+
+ layer.renderer.drawFeature = function(f, s) {
+ var style = OpenLayers.Feature.Vector.style[_style];
+ t.eq(s, style, "renderer drawFeature called with expected style obj (" + _style + ")");
+ };
+
+ // mouse over feature, feature is drawn with "temporary"
+ _feature = feature;
+ _style = "temporary";
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+
+ // click in feature, feature is drawn with "select"
+ _feature = feature;
+ _style = "select";
+ evt.type = "click";
+ map.events.triggerEvent("click", evt);
+
+ // mouse out of feature, feature is still drawn with "select" and
+ // the renderer drawFeature method should not be called
+ _feature = null;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+
+ // mouse over feature again, feature is drawn with "temporary"
+ _feature = feature;
+ _style = "temporary";
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+
+ // mouve out of feature again, feature is still drawn with "select"
+ _feature = null;
+ _style = "select";
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+
+ // click out of feature, feature is drawn with "default"
+ _feature = null;
+ _style = "default";
+ evt.type = "click";
+ map.events.triggerEvent("click", evt);
+ }
+
+ // test for http://trac.openlayers.org/ticket/2812
+ function test_highlightOnly_toggle(t) {
+ t.plan(8);
+
+ /*
+ * setup
+ */
+
+ var map, layer, ctrl1, ctrl2, _feature, feature, evt, _style;
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Vector("name", {isBaseLayer: true});
+ map.addLayer(layer);
+
+ ctrl1 = new OpenLayers.Control.SelectFeature(layer, {
+ highlightOnly: false,
+ hover: false,
+ clickout: false,
+ toggle: true
+ });
+ map.addControl(ctrl1);
+
+ ctrl2 = new OpenLayers.Control.SelectFeature(layer, {
+ highlightOnly: true,
+ hover: true
+ });
+ map.addControl(ctrl2);
+
+ ctrl2.activate();
+ ctrl1.activate();
+
+ feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+
+ // override the layer's getFeatureFromEvent func so that it always
+ // returns the feature referenced to by _feature
+ layer.getFeatureFromEvent = function(evt) { return _feature; };
+
+ evt = {xy: new OpenLayers.Pixel(Math.random(), Math.random())};
+
+ map.zoomToMaxExtent();
+
+ /*
+ * tests
+ */
+
+ // with renderIntent
+
+ ctrl1.renderIntent = "select";
+ ctrl2.renderIntent = "temporary";
+
+ // mouse over feature, feature is drawn with "temporary"
+ _feature = feature;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "temporary",
+ "feature drawn with expected render intent after \"mouseover\"");
+
+ // click in feature, feature is drawn with "select"
+ _feature = feature;
+ evt.type = "click";
+ map.events.triggerEvent("click", evt);
+ t.eq(feature.renderIntent, "select",
+ "feature drawn with expected render intent after \"clickin\"");
+
+ // mouse out of feature, feature is still drawn with "select"
+ _feature = null;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "select",
+ "feature drawn with expected render intent after \"mouseout\"");
+
+ // mouse over feature again, feature is drawn with "temporary"
+ _feature = feature;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "temporary",
+ "feature drawn with expected render intent after \"mouseover\"");
+
+ // click in feature again, feature is drawn with "default"
+ _feature = feature;
+ evt.type = "click";
+ map.events.triggerEvent("click", evt);
+ t.eq(feature.renderIntent, "default",
+ "feature drawn with expected render intent after \"clickin\"");
+
+ // mouse out of feature again, feature is still drawn with "default"
+ _feature = null;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "default",
+ "feature drawn with expected render intent after \"mouseout\"");
+
+ // mouse over feature again, feature is drawn with "temporary"
+ _feature = feature;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "temporary",
+ "feature drawn with expected render intent after \"mouseover\"");
+
+ // mouse out of feature again, feature is still drawn with "default"
+ _feature = null;
+ evt.type = "mousemove";
+ map.events.triggerEvent("mousemove", evt);
+ t.eq(feature.renderIntent, "default",
+ "feature drawn with expected render intent after \"mouseout\"");
+ }
+
+ function test_setLayer(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector();
+ var layer2 = new OpenLayers.Layer.Vector();
+ var layer3 = new OpenLayers.Layer.Vector();
+ map.addLayer(layer1, layer2, layer3);
+ // initialize it with a single layer
+ var control = new OpenLayers.Control.SelectFeature(layer1);
+ map.addControl(control);
+ control.activate();
+ control.setLayer([layer2, layer3]);
+ t.eq(control.layer instanceof OpenLayers.Layer.Vector.RootContainer, true, "Root container created correctly on setLayer with multiple layers");
+ t.eq(control.active, true, "Control should be active still after using setLayer");
+ t.eq((control.handlers.feature.layer === control.layer), true, "Feature handler layer set correctly");
+ control.destroy();
+ // initialize with an array of layers
+ control = new OpenLayers.Control.SelectFeature([layer1, layer2]);
+ t.eq((control.layers !== null), true, "Control has a layers property");
+ control.setLayer(layer3);
+ t.eq((control.layers === null), true, "When using setLayer with a single layer, the layers property is removed if present before");
+ map.destroy();
+ }
+
+ function test_setLayerWithRemoving(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector();
+ var layer2 = new OpenLayers.Layer.Vector();
+ map.addLayer(layer1, layer2);
+ // initialize it with a single layer
+ var control = new OpenLayers.Control.SelectFeature(layer1);
+ map.addControl(control);
+ control.activate();
+ var noError = null;
+ map.events.register("preremovelayer", this, function(e) {
+ try {
+ control.setLayer(layer2);
+ } catch (e) {
+ noError = e;
+ }
+ });
+ layer1.destroy();
+ t.eq(layer2.id, control.layer.id, "Layer is set correctly without error");
+ t.eq(noError, null,"No error occured during setLayer. Error is: '"+noError+"'");
+ control.destroy();
+ map.destroy();
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector();
+ map.addLayer(layer1);
+ var control = new OpenLayers.Control.SelectFeature([layer1]);
+ map.addControl(control);
+ control.activate();
+ control.destroy();
+ t.eq(layer1.renderer.getRenderLayerId(), layer1.id,
+ "Root container moved correctly when control is destroyed and layers was an array parameter");
+ }
+
+ function test_unselectAll(t) {
+ t.plan(2);
+
+ var layer = new OpenLayers.Layer.Vector();
+
+ var control = new OpenLayers.Control.SelectFeature(layer);
+
+ var feature1 = new OpenLayers.Feature.Vector();
+ feature1.id = 1;
+ var feature2 = new OpenLayers.Feature.Vector();
+ feature2.id = 2;
+ var feature3 = new OpenLayers.Feature.Vector();
+ feature3.id = 3;
+ var feature4 = new OpenLayers.Feature.Vector();
+ feature4.id = 4;
+
+ layer.addFeatures([feature1, feature2, feature3, feature4]);
+
+ control.select(feature1);
+ control.select(feature2);
+ control.select(feature3);
+ control.select(feature4);
+
+ layer.events.on({
+ featureunselected: function(e) {
+ // we change the selectedFeatures array while
+ // unselectAll is iterating over that array.
+ if(feature2.layer) {
+ layer.removeFeatures([feature2]);
+ }
+ }
+ });
+
+ control.unselectAll({except: feature3});
+ t.eq(layer.selectedFeatures.length, 1,
+ 'unselectAll unselected all but one');
+ t.eq(layer.selectedFeatures[0].id, 3,
+ 'the remaining selected features is the one expected');
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Snapping.html b/misc/openlayers/tests/Control/Snapping.html
new file mode 100644
index 0000000..f065f66
--- /dev/null
+++ b/misc/openlayers/tests/Control/Snapping.html
@@ -0,0 +1,448 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+
+ t.plan(5);
+
+ // construct with a single layer
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.Snapping({
+ layer: layer
+ });
+
+ t.ok(control.layer === layer, "[a] source layer properly set");
+ t.eq(control.targets.length, 1, "[a] one target layer");
+ t.ok(control.targets[0].layer === layer, "[a] target set");
+ control.destroy();
+
+ // construct with a different target, default target config
+ var layer2 = new OpenLayers.Layer.Vector();
+ control = new OpenLayers.Control.Snapping({
+ layer: layer,
+ targets: [layer2]
+ });
+
+ t.eq(control.targets.length, 1, "[b] one target layer");
+ t.ok(control.targets[0].layer == layer2, "[b] target set");
+ control.destroy();
+ }
+
+ function test_setLayer(t) {
+
+ t.plan(4);
+
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.Snapping();
+ control.setLayer(layer);
+
+ t.ok(control.layer === layer, "layer properly set");
+
+ // confirm that the control is deactivated and reactivated when setting new layer
+ var log = {
+ activated: 0,
+ deactivated: 0
+ };
+ control.activate = function() {
+ log.activated++;
+ }
+ control.deactivate = function() {
+ log.deactivated++;
+ }
+ control.active = true;
+
+ var layer2 = new OpenLayers.Layer.Vector();
+ control.setLayer(layer2);
+
+ t.eq(log.deactivated, 1, "control deactivated");
+ t.ok(control.layer === layer2, "layer properly reset");
+ t.eq(log.activated, 1, "control reactivated");
+
+ control.destroy();
+ }
+
+ function test_setTargets(t) {
+
+ t.plan(4);
+
+ var layer1 = new OpenLayers.Layer.Vector();
+ var layer2 = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.Snapping();
+
+ var log = {
+ addTarget: [],
+ addTargetLayer: []
+ };
+ control.addTarget = function(target) {
+ log.addTarget.push(target);
+ };
+ control.addTargetLayer = function(target) {
+ log.addTargetLayer.push(target);
+ };
+
+ control.setTargets([layer1, {layer: layer2}]);
+
+ t.eq(log.addTargetLayer.length, 1, "setTargetLayer called once");
+ t.ok(log.addTargetLayer[0] === layer1, "setTargetLayer called with layer1");
+ t.eq(log.addTarget.length, 1, "setTarget called once");
+ t.ok(log.addTarget[0].layer === layer2, "setTarget called with layer2");
+
+ control.destroy();
+ }
+
+ function test_addTarget(t) {
+ t.plan(5);
+
+ var layer = new OpenLayers.Layer.Vector();
+
+ var control = new OpenLayers.Control.Snapping({
+ defaults: {
+ nodeTolerance: 30,
+ tolerance: 40
+ }
+ });
+
+ var log = {};
+ control.addTarget({layer: layer});
+
+ t.eq(control.targets.length, 1, "single target");
+ var target = control.targets[0];
+ t.ok(target.layer === layer, "correct target layer");
+ t.eq(target.nodeTolerance, 30, "correct nodeTolerance");
+ t.eq(target.edgeTolerance, 40, "correct edgeTolerance");
+ t.eq(target.vertexTolerance, 40, "correct vertexTolerance");
+
+ control.destroy();
+ }
+
+ function test_removeTargetLayer(t) {
+
+ t.plan(3);
+
+ var layer1 = new OpenLayers.Layer.Vector();
+ var layer2 = new OpenLayers.Layer.Vector();
+ var layer3 = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.Snapping({
+ targets: [layer1, layer2, layer3]
+ });
+
+ control.removeTargetLayer(layer2);
+
+ t.eq(control.targets.length, 2, "correct targets length");
+ t.ok(control.targets[0].layer === layer1, "layer1 remains");
+ t.ok(control.targets[1].layer === layer3, "layer3 remains");
+
+ control.destroy();
+
+ }
+
+ function test_activate(t) {
+
+ t.plan(4);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.Snapping({
+ layer: layer
+ });
+
+ control.activate();
+
+ t.eq(layer.events.listeners.sketchmodified.length, 1, "one sketchmodified listener");
+ t.ok(layer.events.listeners.sketchmodified[0].func === control.onSketchModified, "correct sketchmodified listener");
+ t.eq(layer.events.listeners.vertexmodified.length, 1, "one vertexmodified listener");
+ t.ok(layer.events.listeners.vertexmodified[0].func === control.onVertexModified, "correct vertexmodified listener");
+
+ control.destroy();
+ }
+
+ function test_deactivate(t) {
+
+ t.plan(2);
+ var layer = new OpenLayers.Layer.Vector();
+ var control = new OpenLayers.Control.Snapping({
+ layer: layer
+ });
+
+ control.activate();
+ control.deactivate();
+
+ t.eq(layer.events.listeners.sketchmodified.length, 0, "no sketchmodified listeners");
+ t.eq(layer.events.listeners.vertexmodified.length, 0, "no vertexmodified listeners");
+
+ control.destroy();
+ }
+
+ function test_resolution_limits(t) {
+ t.plan(7);
+
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(0, 0, 100, 100)
+ });
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true
+ });
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "POINT(50 50)"
+ ))
+ ]);
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.Snapping({layer: layer});
+
+ var result;
+ var loc = new OpenLayers.Geometry.Point(49, 49);
+
+ // 1) test a target with no constraints
+ control.setTargets([{layer: layer}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result !== null, "1) target is eligible");
+
+ // 2) test a target with minResolution < map.resolution
+ control.setTargets([{layer: layer, minResolution: 0.5}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result !== null, "2) target is eligible");
+
+ // 3) test a target with minResolution === map.resolution
+ control.setTargets([{layer: layer, minResolution: 1}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result !== null, "3) target is eligible");
+
+ // 4) test a target with minResolution > map.resolution
+ control.setTargets([{layer: layer, minResolution: 1.5}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result === null, "4) target is not eligible");
+
+ // 5) test a target with maxResolution < map.resolution
+ control.setTargets([{layer: layer, maxResolution: 0.5}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result === null, "5) target is not eligible");
+
+ // 6) test a target with maxResolution === map.resolution
+ control.setTargets([{layer: layer, maxResolution: 1}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result === null, "6) target is not eligible");
+
+ // 7) test a target with maxResolution > map.resolution
+ control.setTargets([{layer: layer, maxResolution: 1.5}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result !== null, "7) target is eligible");
+
+ map.destroy();
+
+ }
+
+ function test_filter(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(0, 0, 100, 100)
+ });
+
+ var layer1 = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true
+ });
+ var f1 = new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "LINESTRING(0 0, 10 10, 20 20, 30 30)"
+ ), {foo: 'bar'});
+ f1.fid = "FID1";
+ var f2 = new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "LINESTRING(11 10, 20 10, 30 10)"
+ ), {foo: 'bar'});
+ f2.fid = "FID2";
+ layer1.addFeatures([f1, f2]);
+ map.addLayers([layer1]);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.Snapping({
+ layer: layer1,
+ targets: [layer1],
+ defaults: {tolerance: 4}
+ });
+ control.activate();
+
+ var result;
+ var loc = new OpenLayers.Geometry.Point(1, 1);
+
+ control.setTargets([{layer: layer1}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result !== null, "target is eligible without a filter set");
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT,
+ filters: [
+ new OpenLayers.Filter.FeatureId({fids: ["FID1", "FID2"]})
+ ]
+ });
+ control.setTargets([{layer: layer1, filter: filter}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result === null, "target is not eligible with a filter set which excludes the target's features");
+ filter = new OpenLayers.Filter.Comparison({type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO, value: 'bar', property: 'foo'});
+ control.setTargets([{layer: layer1, filter: filter}]);
+ result = control.testTarget(control.targets[0], loc);
+ t.ok(result === null, "target is not eligible with a filter set which excludes the target's features using a comparison filter");
+ }
+
+ function test_snapping(t) {
+
+ t.plan(46);
+
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(0, 0, 100, 100)
+ });
+
+ var layer1 = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true
+ });
+ layer1.addFeatures([
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "LINESTRING(0 0, 10 10, 20 20, 30 30)"
+ )),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "LINESTRING(11 10, 20 10, 30 10)"
+ ))
+ ]);
+
+ var layer2 = new OpenLayers.Layer.Vector();
+ layer2.addFeatures([
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "LINESTRING(10 10, 20 20, 30 30)"
+ )),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT(
+ "LINESTRING(21 10, 20 20, 20 30)"
+ ))
+ ]);
+
+ map.addLayers([layer1, layer2]);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.Snapping({
+ layer: layer1,
+ targets: [layer1, layer2],
+ defaults: {tolerance: 4}
+ });
+ control.activate();
+ map.addControl(control);
+
+ // log beforesnap, snap, and unsnap events
+ var events = [];
+ function listener(event) {
+ events.push(event);
+ }
+ control.events.on({
+ beforesnap: listener,
+ snap: listener,
+ unsnap: listener
+ });
+
+ // create a vertex and a convenience method for mocking the drag
+ var vertex = new OpenLayers.Geometry.Point(-100, -100);
+ function drag(x, y) {
+ var px = map.getPixelFromLonLat(new OpenLayers.LonLat(x, y));
+ layer1.events.triggerEvent("vertexmodified", {
+ vertex: vertex, pixel: px
+ });
+ }
+
+ // mock up drag far from features
+ drag(-100, -100);
+ t.eq(events.length, 0, "no snapping");
+
+ // mock up drag near first node of first feature
+ drag(0, 1);
+ t.eq(events.length, 2, "[a] 2 events triggered");
+ t.eq(events[0].type, "beforesnap", "[a] beforesnap triggered");
+ t.eq(events[0].snapType, "node", "[a] beforesnap triggered for node");
+ t.ok(events[0].point === vertex, "[a] beforesnap triggered with vertex");
+ t.eq(events[0].x, 0, "[a] beforesnap triggered correct x");
+ t.eq(events[0].y, 0, "[a] beforesnap triggered with correct y");
+ t.eq(events[1].type, "snap", "[a] snap triggered");
+ t.eq(events[1].snapType, "node", "[a] snap triggered for node");
+ t.ok(events[1].point === vertex, "[a] snap triggered with point");
+ t.eq(events[1].distance, 1, "[a] snap triggered correct distance");
+ t.ok(events[1].layer === layer1, "[a] snap triggered with correct target layer");
+ t.eq(vertex.x, 0, "[a] vertex x modified");
+ t.eq(vertex.y, 0, "[a] vertex y modified");
+ events = [];
+
+ // mock up drag that unsnaps
+ drag(-100, -50);
+ t.eq(events.length, 1, "[b] 1 event triggered");
+ t.eq(events[0].type, "unsnap", "[b] unsnap triggered");
+ t.ok(events[0].point === vertex, "[b] unsnap triggered with vertex");
+ t.eq(vertex.x, -100, "[b] vertex x unsnapped");
+ t.eq(vertex.y, -50, "[b] vertex y unsnapped");
+ events = [];
+
+ // drag near node of second feature in first layer to demonstrate precedence of node snapping
+ drag(9, 10);
+ t.eq(events.length, 2, "[c] 2 events triggered");
+ t.eq(events[0].type, "beforesnap", "[c] beforesnap triggered first");
+ t.eq(events[1].type, "snap", "[c] snap triggered second");
+ t.eq(events[1].snapType, "node", "[c] snap to node");
+ // unsnap & reset
+ drag(-100, -50);
+ events = [];
+
+ // drag near node of second feature in second layer to demonstrate greedy property
+ // with greedy true (default) the best target from the first layer with eligible targets is used
+ drag(22, 10);
+ t.eq(events.length, 2, "[d] 2 events triggered");
+ t.eq(events[1].type, "snap", "[d] snap triggered second");
+ t.eq(events[1].snapType, "vertex", "[d] snap to vertex");
+ t.ok(events[1].layer === layer1, "[d] snap to vertex in first layer");
+ t.eq(vertex.x, 20, "[d] vertex x modified");
+ t.eq(vertex.y, 10, "[d] vertex y modified");
+ // unsnap & reset
+ drag(-100, -50);
+ events = [];
+
+ // do the same drag but with greedy false - this will look for best target in all layers
+ control.greedy = false;
+ drag(22, 10);
+ t.eq(events.length, 2, "[d] 2 events triggered");
+ t.eq(events[1].type, "snap", "[d] snap triggered second");
+ t.eq(events[1].snapType, "node", "[d] snap to node");
+ t.ok(events[1].layer === layer2, "[d] snap to node in second layer");
+ // unsnap & reset
+ drag(-100, -50);
+ control.greedy = true;
+ events = [];
+
+ // demonstrate snapping on sketchstarted
+ var p = new OpenLayers.Geometry.Point(0, 1);
+ layer1.events.triggerEvent("sketchstarted", {
+ vertex: p,
+ feature: new OpenLayers.Feature.Vector(p)
+ });
+ t.eq(events.length, 2, "[sketchstarted] 2 events triggered");
+ t.eq(events[0].type, "beforesnap", "[sketchstarted] beforesnap triggered");
+ t.eq(events[0].snapType, "node", "[sketchstarted] beforesnap triggered for node");
+ t.ok(events[0].point === p, "[sketchstarted] beforesnap triggered with vertex");
+ t.eq(events[0].x, 0, "[sketchstarted] beforesnap triggered correct x");
+ t.eq(events[0].y, 0, "[sketchstarted] beforesnap triggered with correct y");
+ t.eq(events[1].type, "snap", "[sketchstarted] snap triggered");
+ t.eq(events[1].snapType, "node", "[sketchstarted] snap triggered for node");
+ t.ok(events[1].point === p, "[sketchstarted] snap triggered with point");
+ t.eq(events[1].distance, 1, "[sketchstarted] snap triggered correct distance");
+ t.ok(events[1].layer === layer1, "[sketchstarted] snap triggered with correct target layer");
+ t.eq(p.x, 0, "[sketchstarted] vertex x modified");
+ t.eq(p.y, 0, "[sketchstarted] vertex y modified");
+ // reset
+ events = [];
+
+ map.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 100px; height: 100px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Split.html b/misc/openlayers/tests/Control/Split.html
new file mode 100644
index 0000000..e3a6eac
--- /dev/null
+++ b/misc/openlayers/tests/Control/Split.html
@@ -0,0 +1,319 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+
+ t.plan(4);
+
+ var layer = new OpenLayers.Layer.Vector();
+ var control;
+
+ // construct with nothing
+ control = new OpenLayers.Control.Split();
+ t.ok(control instanceof OpenLayers.Control, "instanceof OpenLayers.Control");
+ t.ok(control instanceof OpenLayers.Control, "instanceof OpenLayers.Control.Split")
+ control.destroy();
+
+ // construct with a single target layer
+ control = new OpenLayers.Control.Split({
+ layer: layer
+ });
+ t.ok(control.layer === layer, "target layer properly set");
+ control.destroy();
+
+ // construct with same target and source
+ control = new OpenLayers.Control.Split({
+ layer: layer,
+ source: layer
+ });
+ t.ok(control.source === layer, "source layer properly set");
+ control.destroy();
+ }
+
+ function test_setSource(t) {
+ t.plan(5);
+
+ var layer1 = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ var layer2 = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+
+ var control = new OpenLayers.Control.Split({layer: layer1});
+
+ var map = new OpenLayers.Map("map");
+ map.addLayers([layer1, layer2]);
+ map.zoomToMaxExtent();
+ map.addControl(control);
+ control.activate();
+
+ // confirm sketch hander created
+ t.ok(control.handler, "sketch handler created");
+ t.eq(control.handler.active, true, "sketch handler active");
+
+ control.setSource(layer1);
+ t.ok(control.source === layer1, "layer1 properly set");
+ t.ok(!control.handler, "no more sketch handler");
+
+ // activate and switch to new source layer
+ control.setSource(layer2);
+ t.ok(control.source === layer2, "layer2 properly set");
+
+ map.destroy();
+
+ }
+
+ function test_activate(t) {
+ t.plan(8);
+
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ var control = new OpenLayers.Control.Split({layer: layer});
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ map.addControl(control);
+
+ // test activation with no source layer
+ control.activate();
+ t.eq(control.active, true, "control is active");
+ t.ok(control.handler instanceof OpenLayers.Handler.Path, "line sketch handler created");
+ t.ok(control.handler.callbacks.done, "done callback set on sketch handler");
+ t.eq(control.handler.active, true, "sketch handler is active");
+
+ // change the source layer - this should call activate again
+ control.setSource(layer);
+
+ t.eq(control.active, true, "control is still active");
+ t.ok(control.source === layer, "source layer set");
+ t.ok(layer.events.listeners.sketchcomplete, "sketchcomplete listener registered");
+ t.ok(layer.events.listeners.afterfeaturemodified, "afterfeaturemodified listener registered");
+
+ map.destroy();
+
+ }
+
+ function test_deactivate(t) {
+
+ t.plan(7);
+
+ var layer1 = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ var layer2 = new OpenLayers.Layer.Vector("bar", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: false
+ });
+ var control = new OpenLayers.Control.Split({layer: layer1});
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer1);
+ map.addLayer(layer2);
+ map.zoomToMaxExtent();
+ map.addControl(control);
+
+ // activate and check sketch handler
+ control.activate();
+ t.ok(control.handler, "sketch handler present");
+ t.eq(control.handler.active, true, "sketch handler active");
+
+ // deactivate and check sketch handler
+ control.deactivate();
+ t.eq(control.handler.active, false, "sketch handler deactivated");
+
+ // set a source layer
+ control.setSource(layer2);
+
+ // activate and check that listeners are registered
+ control.activate();
+ t.ok(layer2.events.listeners.sketchcomplete, "sketchcomplete listener registered");
+ t.ok(layer2.events.listeners.afterfeaturemodified, "afterfeaturemodified listener registered");
+
+ // deactivate and confirm no draw related events
+ control.deactivate();
+ t.eq(layer2.events.listeners.sketchcomplete.length, 0, "no sketchcomplete listeners");
+ t.eq(layer2.events.listeners.afterfeaturemodified.length, 0, "no afterfeaturemodified listeners");
+
+ map.destroy();
+ }
+
+ function test_isEligible(t) {
+
+ t.plan(10);
+
+ var control = new OpenLayers.Control.Split();
+ var geometry = OpenLayers.Geometry.fromWKT("LINESTRING(0 1, 1 2)");
+ var feature = new OpenLayers.Feature.Vector(
+ geometry,
+ {foo: "bar"}
+ );
+
+ t.eq(control.isEligible(feature), true, "plain old feature is eligible");
+
+ feature.state = OpenLayers.State.DELETE;
+ t.eq(control.isEligible(feature), false, "feature slated for deletion is not eligible");
+ delete feature.state;
+ t.eq(control.isEligible(feature), true, "feature with no state is eligible");
+
+ feature.geometry = new OpenLayers.Geometry.Point(1, 1);
+ t.eq(control.isEligible(feature), false, "feature with point geometry is not eligible");
+ feature.geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
+ t.eq(control.isEligible(feature), true, "feature with multilinestring geometry is eligible");
+
+ control.feature = feature;
+ t.eq(control.isEligible(feature), false, "source feature is not eligible as target");
+ control.feature = new OpenLayers.Feature.Vector();
+ t.eq(control.isEligible(feature), true, "feature is eligible if different than source feature");
+
+ control.targetFilter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ });
+ t.eq(control.isEligible(feature), false, "feature is not eligible unless it matches filter");
+ control.targetFilter.value = "baz";
+ t.eq(control.isEligible(feature), true, "feature is eligible if it matches filter");
+
+ delete feature.geometry;
+ t.eq(control.isEligible(feature), false, "feature with no geometry is not eligible");
+
+ control.destroy();
+
+ }
+
+ function test_considerSplit(t) {
+
+ var layer = new OpenLayers.Layer.Vector();
+
+ var wkt = OpenLayers.Geometry.fromWKT;
+ var geoms = {
+ abc: wkt("LINESTRING(0 0, 2 2)"),
+ ab: wkt("LINESTRING(0 0, 1 1)"),
+ bc: wkt("LINESTRING(1 1, 2 2)"),
+ dbe: wkt("LINESTRING(2 0, 0 2)"),
+ db: wkt("LINESTRING(2 0, 1 1)"),
+ be: wkt("LINESTRING(1 1, 0 2)")
+ };
+
+ var Feature = OpenLayers.Feature.Vector;
+ var feats = {
+ abc: new Feature(geoms.abc),
+ ab: new Feature(geoms.ab),
+ bc: new Feature(geoms.bc),
+ dbe: new Feature(geoms.dbe),
+ db: new Feature(geoms.db),
+ be: new Feature(geoms.be)
+ };
+
+ function feature(id, options) {
+ var f = OpenLayers.Util.extend(feats[id].clone(), options);
+ // for testing, we want to check when features are destroyed
+ f.destroy = function() {
+ f.state = "destroyed";
+ }
+ return f;
+ }
+ var DELETE = OpenLayers.State.DELETE;
+ var INSERT = OpenLayers.State.INSERT;
+ var UPDATE = OpenLayers.State.UPDATE;
+
+ var cases = [{
+ targets: [
+ feature("abc")
+ ],
+ source: feature("dbe"),
+ splits: [{
+ original: feature("abc", {state: "destroyed"}),
+ features: [feature("ab", {state: INSERT}), feature("bc", {state: INSERT})]
+ }, {
+ original: feature("dbe", {state: "destroyed"}),
+ features: [feature("db", {state: INSERT}), feature("be", {state: INSERT})]
+ }]
+ }, {
+ options: {deferDelete: true},
+ targets: [
+ feature("abc", {state: INSERT})
+ ],
+ source: feature("dbe"),
+ splits: [{
+ original: feature("abc", {state: "destroyed"}),
+ features: [feature("ab", {state: INSERT}), feature("bc", {state: INSERT})]
+ }, {
+ original: feature("dbe", {state: DELETE}),
+ features: [feature("db", {state: INSERT}), feature("be", {state: INSERT})]
+ }]
+ }, {
+ options: {deferDelete: true},
+ targets: [
+ feature("abc", {state: UPDATE})
+ ],
+ source: feature("dbe", {state: INSERT}),
+ splits: [{
+ original: feature("abc", {state: DELETE}),
+ features: [feature("ab", {state: INSERT}), feature("bc", {state: INSERT})]
+ }, {
+ original: feature("dbe", {state: "destroyed"}),
+ features: [feature("db", {state: INSERT}), feature("be", {state: INSERT})]
+ }]
+ }];
+
+ var count = 0;
+ var c, control, options, log, event, split;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ ++count; // test number of splits
+ for(var j=0; j<c.splits.length; ++j) {
+ split = c.splits[j];
+ ++count; // test original state
+ ++count; // test original geometry
+ ++count; // test number of parts
+ for(var k=0; k<split.features.length; ++k) {
+ ++count; // test part state
+ ++count; // test part geometry
+ }
+ }
+ }
+ t.plan(count);
+
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ log = {events: []};
+ options = OpenLayers.Util.extend({layer: layer, source: layer}, c.options);
+ control = new OpenLayers.Control.Split(options);
+ control.events.on({
+ split: function(e) {
+ log.events.push(e);
+ }
+ });
+ layer.features = c.targets;
+ control.considerSplit(c.source);
+ t.eq(log.events.length, c.splits.length, "case " + i + ": correct number of split events");
+ for(var j=0; j<log.events.length; ++j) {
+ event = log.events[j];
+ split = c.splits[j];
+ t.eq(event.original.state, split.original.state, "case " + i + " split " + j + ": correct original state");
+ t.geom_eq(event.original.geometry, split.original.geometry, "case " + i + " split " + j + ": correct original geometry");
+ t.eq(event.features.length, split.features.length, "case " + i + " split " + j + ": correct number of parts");
+ for(var k=0; k<split.features.length; ++k) {
+ t.eq(event.features[k].state, split.features[k].state, "case " + i + " split " + j + " feature " + k + ": correct state");
+ t.geom_eq(event.features[k].geometry, split.features[k].geometry, "case " + i + " split " + j + " feature " + k + ": correct geometry");
+ }
+ }
+ control.destroy();
+ }
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 100px; height: 100px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/TouchNavigation.html b/misc/openlayers/tests/Control/TouchNavigation.html
new file mode 100644
index 0000000..bffc225
--- /dev/null
+++ b/misc/openlayers/tests/Control/TouchNavigation.html
@@ -0,0 +1,155 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Control_TouchNavigation_constructor (t) {
+ t.plan( 2 );
+ var options = {bar: "foo"};
+ var temp = OpenLayers.Control.prototype.initialize;
+ OpenLayers.Control.prototype.initialize = function(opt) {
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ };
+
+ var control = new OpenLayers.Control.TouchNavigation(options);
+ t.ok(control instanceof OpenLayers.Control.TouchNavigation,
+ "new OpenLayers.Control returns object");
+
+ OpenLayers.Control.prototype.initialize = temp;
+ }
+
+ function test_Control_TouchNavigation_destroy(t) {
+ t.plan(6);
+
+ var control = {
+ events: {
+ destroy: function() {
+ t.ok(true, "events destroyed");
+ }
+ },
+ deactivate: function() {
+ t.ok(true, "navigation control deactivated before being destroyed");
+ },
+ dragPan: {
+ destroy: function() {
+ t.ok(true, "dragPan destroyed");
+ }
+ },
+ handlers: {
+ click: {
+ destroy: function() {
+ t.ok(true, "clickHandler destroyed");
+ }
+ }
+ }
+ };
+
+ //this will also trigger one test by calling OpenLayers.Control's destroy
+ // and three more for the destruction of dragPan, zoomBox, and wheelHandler
+ OpenLayers.Control.TouchNavigation.prototype.destroy.apply(control, []);
+
+ t.eq(control.dragPan, null, "'dragPan' set to null");
+ t.eq(control.handlers, null, "handlers set to null");
+ }
+
+ function test_documentDrag(t) {
+
+ t.plan(2);
+
+ /**
+ * These tests confirm that the documentDrag property is false by
+ * default and is passed on to the DragPan control. Tests of panning
+ * while dragging outside the viewport should go in the DragPan tests.
+ * Tests of the document events and appropriate callbacks from the
+ * handler should go in the Drag handler tests.
+ */
+
+ var nav = new OpenLayers.Control.TouchNavigation();
+ t.eq(nav.documentDrag, false, "documentDrag false by default");
+
+ var map = new OpenLayers.Map({
+ div: document.body,
+ controls: [new OpenLayers.Control.TouchNavigation({documentDrag: true})]
+ });
+ nav = map.controls[0];
+
+ t.eq(nav.dragPan.documentDrag, true, "documentDrag set on the DragPan control");
+ map.destroy();
+
+ }
+
+ function test_dragPanOptions(t) {
+
+ t.plan(2);
+
+ var nav = new OpenLayers.Control.TouchNavigation();
+ t.eq(nav.dragPanOptions, null, "dragPanOptions null by default");
+
+ var map = new OpenLayers.Map({
+ div: document.body,
+ controls: [
+ new OpenLayers.Control.TouchNavigation({
+ dragPanOptions: {foo: 'bar'}
+ })
+ ]
+ });
+ nav = map.controls[0];
+
+ t.eq(nav.dragPan.foo, 'bar',
+ "foo property is set on the DragPan control");
+ map.destroy();
+
+ }
+
+ function test_clickHandlerOptions(t) {
+
+ t.plan(3);
+
+ var nav = new OpenLayers.Control.TouchNavigation();
+ t.eq(nav.clickHandlerOptions, null, "clickHandlerOptions null by default");
+
+ var map = new OpenLayers.Map({
+ div: document.body,
+ controls: [
+ new OpenLayers.Control.TouchNavigation({
+ clickHandlerOptions: {foo: "bar"}
+ })
+ ]
+ });
+ nav = map.controls[0];
+
+ t.eq(nav.handlers.click.foo, "bar", "foo property is set on the click handler");
+ t.eq(nav.handlers.click.pixelTolerance, 2, "pixelTolerance is 2 by default");
+ map.destroy();
+
+ }
+
+ function test_zoomOut(t) {
+ t.plan(1);
+
+ var map = new OpenLayers.Map('map', {zoomMethod: null});
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 5);
+ var origSetTimeout = window.setTimeout;
+ window.setTimeout = function(fn) { fn(); return 'id'; };
+ var control = new OpenLayers.Control.TouchNavigation();
+ map.addControl(control);
+ var handler = control.handlers.click;
+ handler.touchstart({xy: new OpenLayers.Pixel(1 ,1), touches: ["foo", "bar"]});
+ handler.touchend({});
+ t.eq(map.getZoom(), 4, "Did we zoom out?");
+ // tear down
+ map.destroy();
+ window.setTimeout = origSetTimeout;
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:512px;height:256px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/TransformFeature.html b/misc/openlayers/tests/Control/TransformFeature.html
new file mode 100644
index 0000000..3279867
--- /dev/null
+++ b/misc/openlayers/tests/Control/TransformFeature.html
@@ -0,0 +1,129 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(6);
+ var layer = "foo";
+ var control = new OpenLayers.Control.TransformFeature(layer);
+
+ t.ok(control.layer == layer,
+ "constructor sets layer correctly");
+ t.ok(control.dragControl instanceof OpenLayers.Control.DragFeature,
+ "constructor sets the drag control correctly");
+ t.ok(control.box instanceof OpenLayers.Feature.Vector,
+ "box feature created");
+ t.eq(control.handles.length, 8, "8 handles created");
+ t.eq(control.rotationHandles.length, 4, "4 rotation handles created")
+ t.eq(typeof control.rotationHandleSymbolizer, "object",
+ "rotationHandleSymbolizer initialized");
+ control.destroy();
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.TransformFeature(layer);
+ control.dragControl.destroy = function() {
+ t.ok(true,
+ "control.destroy calls destroy on drag control");
+ };
+ control.destroy();
+ map.destroy();
+ }
+
+ function test_activate(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.TransformFeature(layer);
+ map.addControl(control);
+
+ t.ok(!control.dragControl.active,
+ "drag control is not active prior to activating control");
+ control.activate();
+ t.ok(control.dragControl.active,
+ "drag control is active after activating control");
+ t.ok(control.box.layer === layer, "box added to layer");
+
+ map.destroy();
+ }
+
+ function test_setFeature(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map", {allOverlays: true});
+ var layer = new OpenLayers.Layer.Vector();
+ var feature = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("POLYGON((-1 -1, 1 -1, 1 1, -1 1))"));
+ layer.addFeatures([feature]);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 18);
+ var control = new OpenLayers.Control.TransformFeature(layer);
+ map.addControl(control);
+ var log = [];
+ control.events.on({
+ "beforesetfeature": function(e) { log.push(e); },
+ "setfeature": function(e) { log.push(e); }
+ });
+ control.setFeature(feature);
+
+ t.eq(log[0].type, "beforesetfeature", "beforesetfeature event fired with correct event type");
+ t.eq(log[1].type, "setfeature", "setfeature event fired with correct event type");
+
+ t.ok(control.active, "control activated on setFeature");
+ t.ok(feature.geometry.getBounds().equals(control.box.geometry.getBounds()), "box positioned correctly");
+ t.geom_eq(control.handles[0].geometry, control.box.geometry.components[0], "handle positioned with box");
+
+ var center = new OpenLayers.LonLat(1, 1);
+ control.box.move(center);
+ t.geom_eq(control.handles[0].geometry, control.box.geometry.components[0], "handle moved with box");
+ }
+
+ function test_handleMove(t) {
+ t.plan(16);
+ var map = new OpenLayers.Map("map", {allOverlays: true});
+ var layer = new OpenLayers.Layer.Vector();
+ var feature = new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("POLYGON((-1 -1, 1 -1, 1 1, -1 1))"));
+ layer.addFeatures([feature]);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 18);
+ var control = new OpenLayers.Control.TransformFeature(layer);
+ map.addControl(control);
+ control.setFeature(feature);
+
+ var bottomLeft = new OpenLayers.LonLat(-2, -2);
+ control.handles[0].move(bottomLeft);
+ t.geom_eq(control.handles[0].geometry, new OpenLayers.Geometry.Point(-2, -2), "bottom left handle at -2,-2");
+ t.geom_eq(control.handles[1].geometry, new OpenLayers.Geometry.Point(0, -2), "bottom handle at 0,-2");
+ t.geom_eq(control.handles[2].geometry, new OpenLayers.Geometry.Point(2, -2), "bottom right handle at 2,-2");
+ t.geom_eq(control.handles[3].geometry, new OpenLayers.Geometry.Point(2, 0), "right handle at 2,0");
+ t.geom_eq(control.handles[4].geometry, new OpenLayers.Geometry.Point(2, 2), "top right handle at 2,2");
+ t.geom_eq(control.handles[5].geometry, new OpenLayers.Geometry.Point(0, 2), "top handle at 0,2");
+ t.geom_eq(control.handles[6].geometry, new OpenLayers.Geometry.Point(-2, 2), "top left handle at -2,2");
+ t.geom_eq(control.handles[7].geometry, new OpenLayers.Geometry.Point(-2, 0), "left handle at -2,0");
+
+ control.irregular = true;
+
+ var bottomLeft = new OpenLayers.LonLat(-3, -3);
+ control.handles[0].move(bottomLeft);
+ t.geom_eq(control.handles[0].geometry, new OpenLayers.Geometry.Point(-3, -3), "bottom left handle at -3,-3");
+ t.geom_eq(control.handles[1].geometry, new OpenLayers.Geometry.Point(-0.5, -3), "bottom handle at 0,-3");
+ t.geom_eq(control.handles[2].geometry, new OpenLayers.Geometry.Point(2, -3), "bottom right handle at 2,-3");
+ t.geom_eq(control.handles[3].geometry, new OpenLayers.Geometry.Point(2, -0.5), "right handle at 2,0");
+ t.geom_eq(control.handles[4].geometry, new OpenLayers.Geometry.Point(2, 2), "top right handle at 2,2");
+ t.geom_eq(control.handles[5].geometry, new OpenLayers.Geometry.Point(-0.5, 2), "top handle at 0,2");
+ t.geom_eq(control.handles[6].geometry, new OpenLayers.Geometry.Point(-3, 2), "top left handle at -3,2");
+ t.geom_eq(control.handles[7].geometry, new OpenLayers.Geometry.Point(-3, -0.5), "left handle at -3,0");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/UTFGrid.html b/misc/openlayers/tests/Control/UTFGrid.html
new file mode 100644
index 0000000..74b4b99
--- /dev/null
+++ b/misc/openlayers/tests/Control/UTFGrid.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script>
+ /**
+ * Because browsers that implement requestAnimationFrame may not execute
+ * animation functions while a window is not displayed (e.g. in a hidden
+ * iframe as in these tests), we mask the native implementations here. The
+ * native requestAnimationFrame functionality is tested in Util.html and
+ * in PanZoom.html (where a popup is opened before panning). The panTo tests
+ * here will test the fallback setTimeout implementation for animation.
+ */
+ window.requestAnimationFrame =
+ window.webkitRequestAnimationFrame =
+ window.mozRequestAnimationFrame =
+ window.oRequestAnimationFrame =
+ window.msRequestAnimationFrame = null;
+ </script>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var map, layer, control;
+ var log;
+ function setUp() {
+ layer = new OpenLayers.Layer.UTFGrid({
+ url: "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json",
+ isBaseLayer: true,
+ utfgridResolution: 4
+ });
+ map = new OpenLayers.Map({
+ div: "map",
+ tileManager: null,
+ projection: "EPSG:900913",
+ layers: [layer],
+ center: [0, 0],
+ zoom: 1
+ });
+ log = [];
+ control = new OpenLayers.Control.UTFGrid({
+ callback: function(infoLookup, loc, pixel) {
+ log.push([infoLookup, loc, pixel]);
+ }
+ });
+ map.addControl(control);
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ control = null;
+ log = [];
+ }
+
+ function test_constructor(t) {
+ t.plan(2);
+
+ var control = new OpenLayers.Control.UTFGrid();
+ t.ok(control instanceof OpenLayers.Control.UTFGrid, "utfgrid instance");
+ t.eq(control.handlerMode, "click", "control mode");
+
+ control.destroy();
+
+ }
+
+ function test_handleEvent(t) {
+ setUp();
+
+ var cases = [{
+ evt: {xy: {x: 100, y: 70}},
+ lookup: {
+ "0": {
+ id: "207",
+ data: {
+ NAME: "United States",
+ POP2005: 299846449
+ }
+ }
+ }
+ }, {
+ evt: {xy: {x: 350, y: 20}},
+ lookup: {
+ "0": {
+ id: "245",
+ data: {
+ NAME: "Russia",
+ POP2005: 143953092
+ }
+ }
+ }
+ }];
+
+ var len = cases.length;
+ t.plan(4*len);
+
+ // wait for tile loading to finish
+ t.delay_call(0.5, function() {
+ var c, arg;
+ for (var i=0; i<len; ++i) {
+ c = cases[i];
+ t.eq(log.length, i, i + ": log length before");
+ control.handleEvent(c.evt);
+ t.eq(log.length, i+1, i + ": log length after");
+ t.eq(log[i][0], c.lookup, i + ": callback infoLookup");
+ t.eq(log[i][2], c.evt.xy, i + ": callback pixel");
+ }
+
+ tearDown();
+ });
+
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="height: 256px; width: 512px"></div>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/Control/WMSGetFeatureInfo.html b/misc/openlayers/tests/Control/WMSGetFeatureInfo.html
new file mode 100644
index 0000000..b5e6d5d
--- /dev/null
+++ b/misc/openlayers/tests/Control/WMSGetFeatureInfo.html
@@ -0,0 +1,644 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(5);
+
+ var options = {
+ url: 'http://localhost/wms',
+ layers: ["foo"],
+ formatOptions: {
+ foo: "bar"
+ }
+ };
+ var control = new OpenLayers.Control.WMSGetFeatureInfo(options);
+ t.ok(control instanceof OpenLayers.Control.WMSGetFeatureInfo,
+ "new OpenLayers.Control.WMSGetFeatureInfo returns an instance");
+ t.eq(control.url, 'http://localhost/wms',
+ "constructor sets url correctly");
+ t.eq(control.layers, ["foo"],
+ "constructor layers"
+ );
+ t.ok(control.format instanceof OpenLayers.Format.WMSGetFeatureInfo, "format created");
+ t.eq(control.format.foo, "bar", "format options used")
+ }
+
+ function test_clickCallBack_option(t) {
+ t.plan(9);
+
+ var control;
+
+ control = new OpenLayers.Control.WMSGetFeatureInfo({
+ hover: true
+ });
+ t.ok(control.handler instanceof OpenLayers.Handler.Hover,
+ 'constructor creates hover handler');
+ t.ok(control.handler.callbacks["move"] === control.cancelHover,
+ 'constructor registers proper "move" callback in handler');
+ t.ok(control.handler.callbacks["pause"] === control.getInfoForHover,
+ 'constructor registers proper "pause" callback in handler');
+
+ control = new OpenLayers.Control.WMSGetFeatureInfo();
+ t.ok(control.handler instanceof OpenLayers.Handler.Click,
+ 'constructor creates click handler');
+ t.ok(control.handler.callbacks["click"] === control.getInfoForClick,
+ 'constructor registers proper "click" callback in handler');
+
+ control = new OpenLayers.Control.WMSGetFeatureInfo({
+ clickCallback: "rightclick"
+ });
+ t.ok(control.handler.callbacks["rightclick"] === control.getInfoForClick,
+ 'constructor registers proper "rightclick" callback in handler');
+
+ control = new OpenLayers.Control.WMSGetFeatureInfo({
+ clickCallback: "dblclick",
+ handlerOptions: {
+ click: {
+ "single": false,
+ "double": true
+ }
+ }
+ });
+ t.ok(control.handler.callbacks["dblclick"] === control.getInfoForClick,
+ 'constructor registers proper "dblclick" callback in handler');
+ t.eq(control.handler["single"], false,
+ 'constructor sets "single" to false in handler');
+ t.eq(control.handler["double"], true,
+ 'constructor sets "double" to true in handler');
+ }
+
+ function test_destroy(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ layers: ["foo"]
+ });
+
+ var hover = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ layers: ["foo"],
+ hover: true
+ });
+
+ click.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on click handler");
+ };
+ hover.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on hover handler");
+ };
+ click.destroy();
+ hover.destroy();
+ }
+
+ function test_click(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+
+ // mock up active control
+ var control = new OpenLayers.Control.WMSGetFeatureInfo();
+ map.addControl(control);
+ control.activate();
+
+ control.request = function(position) {
+ t.eq(position.x, 50,
+ "x position is as expected");
+ t.eq(position.y, 50,
+ "y position is as expected");
+ };
+
+ control.getInfoForClick({xy: {x: 50, y: 50}});
+ control.getInfoForHover({xy: {x: 50, y: 50}});
+ }
+
+ function test_getfeatureinfo_event(t) {
+
+ t.plan(5);
+
+ var text =
+ '<?xml version="1.0" encoding="UTF-8" ?>' +
+ '<FeatureInfoResponse>' +
+ ' <FIELDS OBJECTID="1188" HECTARES="1819.734" ZONENR="5854" NULZONES=" " AREA="18197340.1426" PERIMETER="19177.4073627" SHAPE="NULL" SE_ANNO_CAD_DATA="NULL" SHAPE.AREA="0" SHAPE.LEN="0"/>' +
+ '</FeatureInfoResponse>';
+
+ var map = new OpenLayers.Map('map');
+
+ var xy;
+ var url = "http://foo";
+
+ // mock up a control with output "object" and drillDown true
+ var control = new OpenLayers.Control.WMSGetFeatureInfo({
+ output: "object",
+ drillDown: true,
+ request: function(position) {},
+ eventListeners: {
+ getfeatureinfo: function(evt) {
+ t.ok(evt.features[0].url === url, "features is an object with a property url when output is object");
+ var features = evt.features[0].features;
+ t.ok(features.length === 1, "features properties has a length of 1");
+ t.ok(features[0] instanceof OpenLayers.Feature.Vector, "Feature array contains 1 feature");
+ }
+ }
+ });
+
+ // mock up a control with output "features" and drillDown true
+ var control2 = new OpenLayers.Control.WMSGetFeatureInfo({
+ autoActivate: true,
+ drillDown: true,
+ request: function(position) {},
+ eventListeners: {
+ getfeatureinfo: function(evt) {
+ var features = evt.features;
+ t.ok(features.length === 1, "features properties has a length of 1");
+ t.ok(features[0] instanceof OpenLayers.Feature.Vector, "Feature array contains 1 feature");
+ }
+ }
+ });
+
+ map.addControls([control, control2]);
+ control.activate();
+
+ xy = {x: 50, y: 50};
+ control._requestCount = control2._requestCount = 0;
+ control._numRequests = control2._numRequests = 1;
+ control.handleResponse({xy: xy}, {responseText: text}, url);
+ control2.handleResponse({xy: xy}, {responseText: text}, url);
+ map.destroy();
+ }
+
+ function test_beforegetfeatureinfo_event(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+
+ var xy, mode;
+
+ // mock up active control
+ var control = new OpenLayers.Control.WMSGetFeatureInfo({
+ request: function(position) {},
+ eventListeners: {
+ beforegetfeatureinfo: function(evt) {
+ t.eq(evt.xy, xy,
+ "beforegetfeatureinfo listener gets " +
+ "expected xy (" + mode + ")");
+ }
+ }
+ });
+ map.addControl(control);
+ control.activate();
+
+ // 1 test
+ mode = "click";
+ xy = {x: 50, y: 50};
+ control.getInfoForClick({xy: xy});
+
+ // 1 test
+ mode = "hover";
+ xy = {x: 70, y: 70};
+ control.getInfoForHover({xy: xy});
+ }
+
+ function test_nogetfeatureinfo_event(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ // mock up active control
+ var control = new OpenLayers.Control.WMSGetFeatureInfo({
+ eventListeners: {
+ nogetfeatureinfo: function(evt) {
+ t.ok((evt.type == "nogetfeatureinfo"), "nogetfeatureinfo listener gets called when there are no queryable layers");
+ }
+ }
+ });
+ map.addControl(control);
+ control.activate();
+
+ // 1 test
+ mode = "click";
+ xy = {x: 50, y: 50};
+ control.getInfoForClick({xy: xy});
+ }
+
+ function test_activate(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map");
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type'
+ });
+ var hover = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type',
+ hover: true
+ });
+ map.addControl(click);
+ map.addControl(hover);
+ t.ok(!click.handler.active,
+ "click handler is not active prior to activating control");
+ t.ok(!hover.handler.active,
+ "hover handler is not active prior to activating control");
+ click.activate();
+ hover.activate();
+ t.ok(click.handler.active,
+ "click handler is active after activating control");
+ t.ok(hover.handler.active,
+ "hover handler is active after activating control");
+ }
+
+ function test_deactivate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type'
+ });
+ var hover = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type'
+ });
+ map.addControl(click);
+ map.addControl(hover);
+ click.activate();
+ hover.activate();
+
+ click.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on click handler");
+ OpenLayers.Handler.Click.prototype.deactivate.apply(this, arguments);
+ };
+ hover.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on hover handler");
+ OpenLayers.Handler.Hover.prototype.deactivate.apply(this, arguments);
+ };
+ click.deactivate();
+ hover.deactivate();
+ }
+
+ // Verify that things work all right when we combine different types for the STYLES and LAYERS
+ // params in the WMS Layers involved
+ function test_mixedParams(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map", {
+ getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));}
+ });
+ var geographic = new OpenLayers.Projection("EPSG:4326");
+
+ var a = new OpenLayers.Layer.WMS("dummy","http://localhost/wms", {
+ layers: "a,b,c,d",
+ styles: "a,b,c,d"
+ }, {projection: geographic});
+
+ var b = new OpenLayers.Layer.WMS("dummy","http://localhost/wms", {
+ layers: ["a","b","c","d"],
+ styles: ["a","b","c","d"]
+ }, {projection: geographic});
+
+ var c = new OpenLayers.Layer.WMS("dummy","http://localhost/wms", {
+ layers: ["a","b","c","d"]
+ });
+
+ var d = new OpenLayers.Layer.WMS("dummy","http://localhost/wms", {
+ layers: "a,b,c,d"
+ }, {projection: geographic});
+
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ featureType: 'type',
+ featureNS: 'ns',
+ layers: [a, b, c, d]
+ }, {projection: geographic});
+
+ map.addControl(click);
+
+ var log = {};
+ var _request = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) {
+ log.options = options;
+ };
+ click.activate();
+ click.getInfoForClick({xy: {x: 50.2, y: 50.1}});
+ OpenLayers.Request.GET = _request;
+
+ t.eq(
+ log.options && log.options.params.X,
+ 50,
+ "X should be an integer"
+ );
+
+ t.eq(
+ log.options && log.options.params.Y,
+ 50,
+ "Y should be an integer"
+ );
+
+ t.eq(
+ log.options && log.options.url,
+ "http://localhost/wms",
+ "url from first layer used"
+ );
+ t.eq(
+ log.options && log.options.params.STYLES.join(","),
+ ",,,,,,,,a,b,c,d,a,b,c,d",
+ "Styles merged correctly"
+ );
+
+ t.eq(
+ log.options && log.options.params.FORMAT,
+ "image/jpeg",
+ "Required 'format' parameter included"
+ );
+
+ }
+
+ function test_urlMatches(t) {
+
+ t.plan(5);
+
+ var control = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: "http://host/wms?one=1&two=2"
+ });
+
+ t.ok(!control.urlMatches("foo"), "doesn't match garbage");
+ t.ok(control.urlMatches("http://host:80/wms?two=2&one=1"), "matches equivalent url");
+
+ // give the control more urls to match from
+ control.layerUrls = ["http://a.host/wms", "http://b.host/wms"];
+
+ t.ok(control.urlMatches("http://host:80/wms?two=2&one=1"), "still matches equivalent url");
+ t.ok(control.urlMatches("http://a.host:80/wms"), "matches equivalent of first of layerUrls");
+ t.ok(control.urlMatches("http://b.host:80/wms"), "matches equivalent of second of layerUrls");
+
+ }
+
+ function test_layerUrls(t) {
+
+ t.plan(4);
+ var map = new OpenLayers.Map({
+ div: "map",
+ getExtent: function() {
+ return new OpenLayers.Bounds(-180,-90,180,90);
+ }
+ });
+
+ var a = new OpenLayers.Layer.WMS(
+ null, "http://a.mirror/wms", {layers: "a"}
+ );
+ var b = new OpenLayers.Layer.WMS(
+ null, "http://b.mirror/wms", {layers: "b"}
+ );
+ var c = new OpenLayers.Layer.WMS(
+ null, ["http://c.mirror/wms", "http://d.mirror/wms"], {layers: "c"}
+ );
+ map.addLayers([a, b, c]);
+
+ var control = new OpenLayers.Control.WMSGetFeatureInfo({
+ url: "http://host/wms",
+ layers: [a, b, c]
+ });
+ map.addControl(control);
+ control.activate();
+
+ // log calls to GET
+ var log;
+ var _request = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) {
+ log.options = options;
+ };
+
+ // control url doesn't match layer urls, no request issued
+ log = {};
+ control.getInfoForClick({xy: {x: 50, y: 50}});
+ t.ok(!log.options, "no url match, no request issued");
+
+ // give control a list of urls to match
+ log = {};
+ control.layerUrls = ["http://a.mirror/wms", "http://b.mirror/wms"];
+ control.getInfoForClick({xy: {x: 50, y: 50}});
+ t.eq(log.options && log.options.url, "http://host/wms", "some match, request issued");
+ t.eq(log.options && log.options.params["QUERY_LAYERS"].join(","), "b,a", "selected layers queried");
+
+ // show that a layer can be matched if it has a urls array itself (first needs to be matched)
+ log = {};
+ control.layerUrls = ["http://c.mirror/wms"];
+ control.getInfoForClick({xy: {x: 50, y: 50}});
+ t.eq(log.options && log.options.params["QUERY_LAYERS"].join(","), "c", "layer with urls array can be queried");
+
+ // clean up
+ OpenLayers.Request.GET = _request;
+ map.destroy();
+
+ }
+
+ function test_hover(t) {
+
+ t.plan(2);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer.WMS(null, "/dummywms", {layers: "one"})
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+
+ var control = new OpenLayers.Control.WMSGetFeatureInfo({
+ hover: true
+ });
+ map.addControl(control);
+ control.activate();
+
+ // mock up a mousemove
+ control.getInfoForHover({xy: new OpenLayers.Pixel(10, 10)});
+ t.ok(!!control.hoverRequest, "hoverRequest set");
+
+ // confirm that request is canceled on next move
+ var called = 0;
+ control.hoverRequest.abort = function() {
+ ++called;
+ };
+ control.handler.px = null;
+ control.handler.mousemove({xy: new OpenLayers.Pixel(20, 20)});
+ t.eq(called, 1, "hover request aborted");
+
+ map.destroy();
+
+ }
+
+ function test_exceptions(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));}
+ }
+ );
+
+ var a = new OpenLayers.Layer.WMS("dummy","http://myhost/wms", {
+ layers: "x",
+ exceptions: "text/xml"
+ });
+
+ map.addLayer(a);
+
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ });
+
+ map.addControl(click);
+
+ var _request = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) {
+ t.eq(options.params["EXCEPTIONS"], "text/xml", "Exceptions parameter taken from the WMS layer if provided");
+ };
+ click.activate();
+ click.getInfoForClick({xy: {x: 50, y: 50}});
+ OpenLayers.Request.GET = _request;
+ map.destroy();
+ }
+
+ function test_drillDown(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map", {
+ getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));}
+ }
+ );
+
+ var a = new OpenLayers.Layer.WMS("dummy","http://localhost/wms", {
+ layers: "a"
+ });
+
+ var b = new OpenLayers.Layer.WMS("dummy","http://localhost/wms", {
+ layers: "c"
+ });
+
+ // this service does not support application/vnd.ogc.gml for GetFeatureInfo, only text/xml
+ var c = new OpenLayers.Layer.WMS("dummy","http://myhost/wms", {
+ layers: "x",
+ info_format: "text/xml"
+ });
+
+ map.addLayers([a, b, c]);
+
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ drillDown: true,
+ infoFormat: "application/vnd.ogc.gml"
+ });
+
+ map.addControl(click);
+
+ var count = 0;
+ var _request = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) {
+ count++;
+ if (count == 2) {
+ t.eq(options.params["INFO_FORMAT"], "application/vnd.ogc.gml", "Default info format of the control is used");
+ t.eq(options.params["QUERY_LAYERS"].join(","), "c,a", "Layers should be grouped by service url");
+ t.eq(options.url, "http://localhost/wms", "Correct url used for second request");
+ } else if (count == 1) {
+ t.eq(options.params["INFO_FORMAT"], "text/xml", "Overridden info format is used instead of the control's infoFormat");
+ t.eq(options.url, "http://myhost/wms", "Correct url used for first request");
+ }
+ };
+ click.activate();
+ click.getInfoForClick({xy: {x: 50, y: 50}});
+ OpenLayers.Request.GET = _request;
+ t.eq(count, 2, "We expect 2 requests to go off");
+ map.destroy();
+ }
+
+ function test_GetFeatureInfo_buildWMSOptions(t) {
+ t.plan(3);
+
+ var map = new OpenLayers.Map("map", {
+ getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));},
+ projection: "EPSG:900913"
+ });
+ var a = new OpenLayers.Layer.WMS("dummy", "http://localhost/wms", {
+ layers: "a"
+ }, {projection: "EPSG:3857"});
+ var b = new OpenLayers.Layer.WMS("dummy", "http://localhost/wms", {
+ layers: "b"
+ });
+ var c = new OpenLayers.Layer.WMS("dummy", "http://localhost/wms", {
+ layers: "c"
+ }, {projection: "EPSG:4326"});
+ map.addLayers([a, b, c]);
+ var gfi = new OpenLayers.Control.WMSGetFeatureInfo();
+ map.addControl(gfi);
+ gfi.activate();
+
+ var options = gfi.buildWMSOptions("http://localhost/wms", [a], {xy: {x: 50, y: 50}}, "text/html");
+ t.eq(options.params.SRS, "EPSG:3857", "layer projection used if provided and equal map projection");
+
+ options = gfi.buildWMSOptions("http://localhost/wms", [b], {xy: {x: 50, y: 50}}, "text/html");
+ t.eq(options.params.SRS, "EPSG:900913", "map projection used if layer has no projection configured");
+
+ options = gfi.buildWMSOptions("http://localhost/wms", [b], {xy: {x: 50, y: 50}}, "text/html");
+ t.eq(options.params.SRS, "EPSG:900913", "map projection used if layer configured with an incompatible projection");
+ }
+
+ function test_GetFeatureInfo_WMS13(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map", {
+ getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));}
+ }
+ );
+
+ var a = new OpenLayers.Layer.WMS(null, "http://localhost/wms", {
+ layers: "a",
+ version: "1.3.0"
+ });
+ map.addLayer(a);
+
+ var click = new OpenLayers.Control.WMSGetFeatureInfo({
+ });
+
+ map.addControl(click);
+ var log = {};
+ var _request = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) {
+ log.options = options;
+ };
+ click.activate();
+ click.getInfoForClick({xy: {x: 50.1, y: 60.2}});
+ OpenLayers.Request.GET = _request;
+ t.eq(
+ log.options && log.options.params.CRS,
+ "EPSG:4326",
+ "Since it is WMS 1.3 use CRS parameter instead of SRS in the GetFeatureInfo request"
+ );
+
+ t.eq(
+ log.options && log.options.params.I,
+ 50,
+ "Since it is WMS 1.3 use I parameter instead of X in the GetFeatureInfo request"
+ );
+
+ t.eq(
+ log.options && log.options.params.J,
+ 60,
+ "Since it is WMS 1.3 use J parameter instead of Y in the GetFeatureInfo request"
+ );
+
+ t.eq(
+ log.options && log.options.params.BBOX,
+ "-90,-180,90,180",
+ "Since it is WMS 1.3 the BBOX should respect axis order"
+ );
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/WMTSGetFeatureInfo.html b/misc/openlayers/tests/Control/WMTSGetFeatureInfo.html
new file mode 100644
index 0000000..c7be78c
--- /dev/null
+++ b/misc/openlayers/tests/Control/WMTSGetFeatureInfo.html
@@ -0,0 +1,334 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(4);
+
+ var options = {
+ url: "http://localhost/wmts",
+ layers: ["foo"],
+ formatOptions: {
+ foo: "bar"
+ }
+ };
+ var control = new OpenLayers.Control.WMTSGetFeatureInfo(options);
+ t.ok(control instanceof OpenLayers.Control.WMTSGetFeatureInfo,
+ "new OpenLayers.Control.WMTSGetFeatureInfo returns an instance");
+ t.eq(control.layers, ["foo"],
+ "constructor layers"
+ );
+ t.ok(control.format instanceof OpenLayers.Format.WMSGetFeatureInfo, "format created");
+ t.eq(control.format.foo, "bar", "format options used")
+ }
+
+ function test_clickCallBack_option(t) {
+ t.plan(9);
+
+ var control;
+
+ control = new OpenLayers.Control.WMTSGetFeatureInfo({
+ hover: true
+ });
+ t.ok(control.handler instanceof OpenLayers.Handler.Hover,
+ 'constructor creates hover handler');
+ t.ok(control.handler.callbacks["move"] === control.cancelHover,
+ 'constructor registers proper "move" callback in handler');
+ t.ok(control.handler.callbacks["pause"] === control.getInfoForHover,
+ 'constructor registers proper "pause" callback in handler');
+
+ control = new OpenLayers.Control.WMTSGetFeatureInfo();
+ t.ok(control.handler instanceof OpenLayers.Handler.Click,
+ 'constructor creates click handler');
+ t.ok(control.handler.callbacks["click"] === control.getInfoForClick,
+ 'constructor registers proper "click" callback in handler');
+
+ control = new OpenLayers.Control.WMTSGetFeatureInfo({
+ clickCallback: "rightclick"
+ });
+ t.ok(control.handler.callbacks["rightclick"] === control.getInfoForClick,
+ 'constructor registers proper "rightclick" callback in handler');
+
+ control = new OpenLayers.Control.WMTSGetFeatureInfo({
+ clickCallback: "dblclick",
+ handlerOptions: {
+ click: {
+ "single": false,
+ "double": true
+ }
+ }
+ });
+ t.ok(control.handler.callbacks["dblclick"] === control.getInfoForClick,
+ 'constructor registers proper "dblclick" callback in handler');
+ t.eq(control.handler["single"], false,
+ 'constructor sets "single" to false in handler');
+ t.eq(control.handler["double"], true,
+ 'constructor sets "double" to true in handler');
+ }
+
+ function test_destroy(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var click = new OpenLayers.Control.WMTSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ layers: ["foo"]
+ });
+
+ var hover = new OpenLayers.Control.WMTSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ layers: ["foo"],
+ hover: true
+ });
+
+ click.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on click handler");
+ };
+ hover.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on hover handler");
+ };
+ click.destroy();
+ hover.destroy();
+ }
+
+ function test_click(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+
+ // mock up active control
+ var control = new OpenLayers.Control.WMTSGetFeatureInfo();
+ map.addControl(control);
+ control.activate();
+
+ control.request = function(position) {
+ t.eq(position.x, 200,
+ "x position is as expected");
+ t.eq(position.y, 125,
+ "y position is as expected");
+ };
+
+ control.getInfoForClick({xy: {x: 200, y: 125}});
+ control.getInfoForHover({xy: {x: 200, y: 125}});
+ }
+
+ function test_beforegetfeatureinfo_event(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ layers: [
+ new OpenLayers.Layer.WMTS({
+ name: "Test WMTS 1",
+ url: "/testwmts/",
+ layer: "test1",
+ style: "",
+ matrixSet: "set-id",
+ isBaseLayer: false
+ }),
+ new OpenLayers.Layer.WMTS({
+ name: "Test WMTS 2",
+ url: "/testwmts/",
+ layer: "test2",
+ style: "",
+ matrixSet: "set-id",
+ isBaseLayer: false
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ var log = [];
+
+ // test click
+ var click = new OpenLayers.Control.WMTSGetFeatureInfo({
+ drillDown: true,
+ eventListeners: {
+ beforegetfeatureinfo: function(evt) {
+ log.push({xy: evt.xy});
+ }
+ }
+ });
+ map.addControl(click);
+ click.activate();
+ click.getInfoForClick({xy: {x: 200, y: 125}});
+ t.eq(log.length, 2, "click: beforegetfeatureinfo triggered twice");
+ log = [];
+ click.deactivate();
+
+ // test hover
+ var hover = new OpenLayers.Control.WMTSGetFeatureInfo({
+ hover: true,
+ eventListeners: {
+ beforegetfeatureinfo: function(evt) {
+ log.push({xy: evt.xy});
+ }
+ }
+ });
+ map.addControl(hover);
+ hover.activate();
+ xy = {x: 70, y: 70};
+ hover.getInfoForHover({xy: {x: 70, y: 70}});
+ t.eq(log.length, 1, "hover: beforegetfeatureinfo triggered once");
+ log = [];
+ hover.deactivate();
+
+ map.destroy();
+ }
+
+ function test_activate(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map");
+ var click = new OpenLayers.Control.WMTSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ layers: ['ns:type']
+ });
+ var hover = new OpenLayers.Control.WMTSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type',
+ hover: true
+ });
+ map.addControl(click);
+ map.addControl(hover);
+ t.ok(!click.handler.active,
+ "click handler is not active prior to activating control");
+ t.ok(!hover.handler.active,
+ "hover handler is not active prior to activating control");
+ click.activate();
+ hover.activate();
+ t.ok(click.handler.active,
+ "click handler is active after activating control");
+ t.ok(hover.handler.active,
+ "hover handler is active after activating control");
+ }
+
+ function test_deactivate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var click = new OpenLayers.Control.WMTSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type'
+ });
+ var hover = new OpenLayers.Control.WMTSGetFeatureInfo({
+ url: 'http://localhost/wms',
+ featureType: 'type',
+ featureNS: 'http://localhost/ns',
+ layers: 'ns:type'
+ });
+ map.addControl(click);
+ map.addControl(hover);
+ click.activate();
+ hover.activate();
+
+ click.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on click handler");
+ OpenLayers.Handler.Click.prototype.deactivate.apply(this, arguments);
+ };
+ hover.handler.deactivate = function() {
+ t.ok(true,
+ "control.deactivate calls deactivate on hover handler");
+ OpenLayers.Handler.Hover.prototype.deactivate.apply(this, arguments);
+ };
+ click.deactivate();
+ hover.deactivate();
+ }
+
+ function test_getInfoForClick(t) {
+
+ t.plan(13);
+ var map = new OpenLayers.Map({
+ div: "map",
+ getExtent: function() {
+ return new OpenLayers.Bounds(-180,-90,180,90);
+ }
+ });
+
+ var a = new OpenLayers.Layer.WMTS({
+ url: "http://a.example.com/wmts",
+ layer: "a",
+ matrixSet: "bar",
+ style: "default"
+ });
+
+ var b = new OpenLayers.Layer.WMTS({
+ url: "http://b.example.com/wmts",
+ layer: "b",
+ matrixSet: "bar",
+ style: "default",
+ isBaseLayer: false
+ });
+
+ var c = new OpenLayers.Layer.WMTS({
+ url: ["http://c1.example.com/wmts", "http://c2.example.com"],
+ layer: "c",
+ matrixSet: "bar",
+ style: "default",
+ isBaseLayer: false
+ });
+ map.addLayers([a, b, c]);
+ map.zoomToMaxExtent();
+
+ var control = new OpenLayers.Control.WMTSGetFeatureInfo({
+ layers: [a, b, c]
+ });
+ map.addControl(control);
+ control.activate();
+
+ // log calls to GET
+ var log;
+ var _request = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) {
+ log.push(options);
+ };
+
+ // query first layer (drillDown false)
+ log = [];
+ control.drillDown = false;
+ control.queryVisible = false;
+ control.getInfoForClick({xy: {x: 200, y: 125}});
+ t.eq(log.length, 1, "one requests issued");
+ t.eq(log[0].url, "http://c1.example.com/wmts", "{drillDown: false} correct url");
+ t.eq(log[0].params["LAYER"], "c", "{drillDown: false} correct layer parameter");
+
+ // query all layers
+ log = [];
+ control.drillDown = true;
+ control.queryVisible = false;
+ control.getInfoForClick({xy: {x: 200, y: 125}});
+ t.eq(log.length, 3, "three requests issued");
+ t.eq(log[0].url, "http://c1.example.com/wmts", "[c] correct url");
+ t.eq(log[0].params["LAYER"], "c", "[c] correct layer parameter");
+ t.eq(log[1].url, "http://b.example.com/wmts", "[b] correct url");
+ t.eq(log[1].params["LAYER"], "b", "[b] correct layer parameter");
+ t.eq(log[2].url, "http://a.example.com/wmts", "[a] correct url");
+ t.eq(log[2].params["LAYER"], "a", "[a] correct layer parameter");
+
+ // query only visible layers
+ log = [];
+ control.drillDown = true;
+ control.queryVisible = true;
+ b.setVisibility(false);
+ control.getInfoForClick({xy: {x: 200, y: 125}});
+ t.eq(log.length, 2, "two requests issued");
+ t.eq(log[0].url, "http://c1.example.com/wmts", "correct url for second visible layer");
+ t.eq(log[1].url, "http://a.example.com/wmts", "correct url for first visible layer");
+
+ // clean up
+ OpenLayers.Request.GET = _request;
+ map.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 250px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/Zoom.html b/misc/openlayers/tests/Control/Zoom.html
new file mode 100644
index 0000000..cfeb082
--- /dev/null
+++ b/misc/openlayers/tests/Control/Zoom.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(5);
+
+ var control = new OpenLayers.Control.Zoom();
+ t.ok(control instanceof OpenLayers.Control, "instance of Control");
+ t.ok(control instanceof OpenLayers.Control.Zoom, "instance of Zoom");
+ t.eq(control.displayClass, "olControlZoom", "displayClass");
+ control.destroy();
+
+ control = new OpenLayers.Control.Zoom({
+ zoomInText: "zoom in!",
+ zoomOutText: "zoom out!"
+ });
+ t.eq(control.zoomInText, "zoom in!", "zoomInText");
+ t.eq(control.zoomOutText, "zoom out!", "zoomOutText");
+ control.destroy();
+ }
+
+ function test_addControl(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ var control = new OpenLayers.Control.Zoom();
+ map.addControl(control);
+ t.ok(control.map === map, "Control.map set");
+ t.ok(!!~OpenLayers.Util.indexOf(map.controls, control), "map.controls contains control");
+
+ control = new OpenLayers.Control.Zoom({zoomInId: "in", zoomOutId: "out"});
+ map.addControl(control);
+ var eventsEl = document.getElementById("out").parentNode;
+ t.ok(control.events.element === eventsEl, "Events instance listens to custom div's parentNode");
+
+ map.destroy();
+ }
+
+ function test_zoomIn(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ zoomMethod: null
+ });
+ var control = new OpenLayers.Control.Zoom();
+ map.addControl(control);
+ map.setCenter([0, 0], 0);
+
+ t.eq(map.getZoom(), 0, "initial center");
+ map.events.triggerEvent("buttonclick", {buttonElement: control.zoomInLink});
+ t.eq(map.getZoom(), 1, "after zoom in");
+ map.destroy();
+ }
+
+ function test_zoomOut(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ zoomMethod: null
+ });
+ var control = new OpenLayers.Control.Zoom();
+ map.addControl(control);
+ map.setCenter([0, 0], 1);
+
+ t.eq(map.getZoom(), 1, "initial center");
+ map.events.triggerEvent("buttonclick", {buttonElement: control.zoomOutLink});
+ t.eq(map.getZoom(), 0, "after zoom out");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 512px; height: 256px;"/>
+ <div id="in">in</div><div id="out">out</out>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/ZoomBox.html b/misc/openlayers/tests/Control/ZoomBox.html
new file mode 100644
index 0000000..7763bcf
--- /dev/null
+++ b/misc/openlayers/tests/Control/ZoomBox.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(4);
+
+ var control = new OpenLayers.Control.ZoomBox();
+ t.ok(control instanceof OpenLayers.Control, "instance of Control");
+ t.ok(control instanceof OpenLayers.Control.ZoomBox, "instance of ZoomBox");
+ t.eq(control.displayClass, "olControlZoomBox", "displayClass");
+ control.destroy();
+
+ control = new OpenLayers.Control.ZoomBox({
+ zoomOnClick: false
+ });
+ t.eq(control.zoomOnClick, false, "zoomOnClick");
+ control.destroy();
+ }
+
+ function test_zoomBox(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map", {
+ zoomMethod: null,
+ layers: [new OpenLayers.Layer("", {isBaseLayer: true})],
+ center: [0, 0],
+ zoom: 1
+ });
+ var control = new OpenLayers.Control.ZoomBox();
+ map.addControl(control);
+ control.zoomBox(new OpenLayers.Pixel(50, 60));
+ t.eq(map.getZoom(), 2, "zoomed on click");
+
+ control.zoomOnClick = false;
+ control.zoomBox(new OpenLayers.Pixel(-50, -60));
+ t.eq(map.getZoom(), 2, "not zoomed with zoomOnClick set to false");
+
+ map.zoomToMaxExtent();
+ // pixel bounds bottom > top
+ control.zoomBox(new OpenLayers.Bounds(128, 128, 256, 64));
+ t.eq(map.getCenter().toShortString(), "-45, 22.5", "centered to box center");
+ t.eq(map.getZoom(), 3, "zoomed to box extent");
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 512px; height: 256px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Control/ZoomIn.html b/misc/openlayers/tests/Control/ZoomIn.html
new file mode 100644
index 0000000..844ded5
--- /dev/null
+++ b/misc/openlayers/tests/Control/ZoomIn.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+function test_ZoomIn_constructor (t) {
+ t.plan( 2 );
+
+ // setup
+ var control = new OpenLayers.Control.ZoomIn();
+
+ // tests
+ //
+ t.ok(
+ control instanceof OpenLayers.Control.ZoomIn,
+ "new OpenLayers.Control.ZoomIn returns object"
+ );
+ t.eq(
+ control.displayClass, "olControlZoomIn",
+ "displayClass is correct"
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_ZoomIn_type (t) {
+ t.plan( 1 );
+
+ // setup
+ var control = new OpenLayers.Control.ZoomIn();
+
+ // tests
+ //
+ t.eq(
+ control.type,
+ OpenLayers.Control.TYPE_BUTTON,
+ "ZoomIn control is of type OpenLayers.Control.TYPE_BUTTON"
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_ZoomIn_trigger (t) {
+ t.plan( 2 );
+
+ // set up
+ var control = new OpenLayers.Control.ZoomIn(),
+ zoomlevel = 5,
+ map = new OpenLayers.Map("map", {
+ allOverlays: true,
+ layers: [
+ new OpenLayers.Layer.Vector()
+ ],
+ center: new OpenLayers.LonLat(1,1),
+ zoom: zoomlevel
+ }),
+ oldZoom;
+
+ oldZoom = map.getZoom();
+
+ // tests
+ //
+ // trigger the control before it is being added,
+ // nothing should change
+ control.trigger();
+
+ t.eq(
+ oldZoom,
+ zoomlevel,
+ 'Calling trigger on a non added control doesn\'t do anything ' +
+ '(map zoom is ' + oldZoom + ').'
+ );
+
+ // now lets add the control
+ map.addControl(control);
+
+ // trigger it again, now the map should have a different extent
+ control.trigger();
+
+ t.eq(
+ map.getZoom(),
+ zoomlevel + 1,
+ 'Calling trigger on a added control changes the map zoom ' +
+ '(map zoom was ' + zoomlevel +
+ ' and is now ' + map.getZoom() + ').'
+ );
+
+ // tear down
+ control.destroy();
+ map.destroy();
+}
+
+ </script>
+ </head>
+ <body>
+ <div id="map" style="width: 1000px; height: 1000px;"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Control/ZoomOut.html b/misc/openlayers/tests/Control/ZoomOut.html
new file mode 100644
index 0000000..5345c55
--- /dev/null
+++ b/misc/openlayers/tests/Control/ZoomOut.html
@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+function test_ZoomOut_constructor (t) {
+ t.plan( 2 );
+
+ // setup
+ var control = new OpenLayers.Control.ZoomOut();
+
+ // tests
+ //
+ t.ok(
+ control instanceof OpenLayers.Control.ZoomOut,
+ "new OpenLayers.Control.ZoomOut returns object"
+ );
+ t.eq(
+ control.displayClass, "olControlZoomOut",
+ "displayClass is correct"
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_ZoomOut_type(t){
+ t.plan( 1 );
+
+ // setup
+ var control = new OpenLayers.Control.ZoomOut();
+
+ // check that the type of the control equals OpenLayers.Control.TYPE_BUTTON
+ t.eq(
+ control.type,
+ OpenLayers.Control.TYPE_BUTTON,
+ 'ZoomOut-control is of type "OpenLayers.Control.TYPE_BUTTON".'
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_ZoomOut_trigger (t) {
+ t.plan( 2 );
+
+ // set up
+ var control = new OpenLayers.Control.ZoomOut(),
+ zoomlevel = 5,
+ map = new OpenLayers.Map("map", {
+ allOverlays: true,
+ layers: [
+ new OpenLayers.Layer.Vector()
+ ],
+ center: new OpenLayers.LonLat(1,1),
+ zoom: zoomlevel
+ }),
+ oldZoom;
+
+ oldZoom = map.getZoom();
+
+ // tests
+ //
+ // trigger the control before it is being added,
+ // nothing should change
+ control.trigger();
+
+ t.eq(
+ oldZoom,
+ zoomlevel,
+ 'Calling trigger on a non added control doesn\'t do anything ' +
+ '(map zoom is ' + oldZoom + ').'
+ );
+
+ // now lets add the control
+ map.addControl(control);
+
+ // trigger it again, now the map should have a different extent
+ control.trigger();
+
+ t.eq(
+ map.getZoom(),
+ zoomlevel - 1,
+ 'Calling trigger on a added control changes the map zoom ' +
+ '(map zoom was ' + zoomlevel +
+ ' and is now ' + map.getZoom() + ').'
+ );
+
+ // tear down
+ control.destroy();
+ map.destroy();
+}
+
+ </script>
+ </head>
+ <body>
+ <div id="map" style="width: 1000px; height: 1000px;"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Control/ZoomToMaxExtent.html b/misc/openlayers/tests/Control/ZoomToMaxExtent.html
new file mode 100644
index 0000000..8ed5512
--- /dev/null
+++ b/misc/openlayers/tests/Control/ZoomToMaxExtent.html
@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+function test_ZoomToMaxExtent_constructor (t) {
+ t.plan( 2 );
+
+ // setup
+ var control = new OpenLayers.Control.ZoomToMaxExtent();
+
+ // tests
+ //
+ t.ok(
+ control instanceof OpenLayers.Control.ZoomToMaxExtent,
+ "new OpenLayers.Control.ZoomToMaxExtent returns object"
+ );
+ t.eq(
+ control.displayClass, "olControlZoomToMaxExtent",
+ "displayClass is correct"
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_ZoomToMaxExtent_type (t) {
+ t.plan( 1 );
+
+ // setup
+ var control = new OpenLayers.Control.ZoomToMaxExtent();
+
+ // check that the type of the control equals OpenLayers.Control.TYPE_BUTTON
+ t.eq(
+ control.type,
+ OpenLayers.Control.TYPE_BUTTON,
+ 'ZoomToMaxExtent-control is of type "OpenLayers.Control.TYPE_BUTTON".'
+ );
+
+ // tear down
+ control.destroy();
+}
+
+function test_ZoomToMaxExtent_trigger (t) {
+ t.plan( 2 );
+
+ // set up
+ var mapsMaxExtent = new OpenLayers.Bounds(0, 0, 45, 45),
+ mapsInitialExtent = new OpenLayers.Bounds(5, 5, 7, 7),
+ control = new OpenLayers.Control.ZoomToMaxExtent(),
+ map = new OpenLayers.Map("map", {
+ maxExtent: mapsMaxExtent,
+ allOverlays: true,
+ fractionalZoom: true,
+ layers: [
+ new OpenLayers.Layer.Vector()
+ ]
+ }),
+ oldExtent;
+
+ map.zoomToExtent(mapsInitialExtent);
+
+ oldExtent = map.getExtent().toString();
+
+ // tests
+ //
+ // trigger the control before it is being added,
+ // nothing should change
+ control.trigger();
+ t.eq(
+ oldExtent,
+ map.getExtent().toString(),
+ 'Calling trigger on a non added control doesn\'t do anything ' +
+ '(map extent is "' + oldExtent + '").'
+ );
+
+ // now lets add the control
+ map. addControl(control);
+
+ // trigger it again, now the map should have a different extent
+ control.trigger();
+
+ t.eq(
+ map.getExtent().toString(),
+ mapsMaxExtent.toString(),
+ 'Calling trigger on a added control changes the map extent ' +
+ '(map extent was "' + oldExtent + '"' +
+ ' and is now "' + mapsMaxExtent.toString() + '").'
+ );
+
+ // tear down
+ control.destroy();
+ map.destroy();
+}
+
+ </script>
+ </head>
+ <body>
+ <div id="map" style="width: 1000px; height: 1000px;"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Events.html b/misc/openlayers/tests/Events.html
new file mode 100644
index 0000000..03c540c
--- /dev/null
+++ b/misc/openlayers/tests/Events.html
@@ -0,0 +1,487 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var map;
+ var a;
+
+ function test_Events_constructor (t) {
+ t.plan(4);
+
+ var mapDiv = OpenLayers.Util.getElement('map');
+ var obj = {result: 0};
+
+ events = new OpenLayers.Events(obj, mapDiv);
+ t.ok( events instanceof OpenLayers.Events, "new OpenLayers.Control returns object" );
+ t.ok(events.object ==obj, " 'object' property correctly set");
+ t.ok(events.element == mapDiv, " 'element' property correctly set");
+ events.destroy();
+
+ // default/nulls
+ events = new OpenLayers.Events(null, null, null);
+ t.ok( events.listeners != null,
+ "init of Events with null object/element/eventTypes still creates listeners array" );
+ events.destroy();
+ }
+
+ function test_Events_register(t){
+ t.plan(4);
+
+ var ev = {
+ 'object': {},
+ 'extensionCount': {
+ 'listenerA': 0,
+ 'listenerB': 0
+ },
+ 'listeners': {
+ 'listenerA': {
+ 'push': function(options){
+ gObjA = options.obj;
+ gFuncA = options.func;
+ }
+ },
+ 'listenerB': {
+ 'push': function(options){
+ gObjB = options.obj;
+ gFuncB = options.func;
+ }
+ }
+ }
+ };
+
+ var type = null;
+ var object = null;
+ var func = null;
+
+ //func null
+ gObjA = null; gFuncA = null; gObjB = null; gFuncB = null;
+ OpenLayers.Events.prototype.register.apply(ev, [type, object, func]);
+ t.ok((gObjA == null) && (gFuncA == null) &&
+ (gObjB == null) && (gFuncB == null), "no push called func null");
+
+ //valid func, type not in ev.eventTypes
+ func = function() {};
+ gObjA = null; gFuncA = null; gObjB = null; gFuncB = null;
+ OpenLayers.Events.prototype.register.apply(ev, [type, object, func]);
+ t.ok((gObjA == null) && (gFuncA == null) &&
+ (gObjB == null) && (gFuncB == null), "no push called func null");
+
+ //valid func, type in ev.eventTypes, null obj
+ type = 'listenerA';
+ gObjA = null; gFuncA = null; gObjB = null; gFuncB = null;
+ OpenLayers.Events.prototype.register.apply(ev, [type, object, func]);
+ t.ok((gObjA == ev.object) && (gFuncA == func) &&
+ (gObjB == null) && (gFuncB == null), "push called on listenerA's mock array when type passed in 'listenerA'. events.object taken since obj is null.");
+
+ //valid func, type in ev.eventTypes, valid obj
+ type = 'listenerB';
+ object = {};
+ gObjA = null; gFuncA = null; gObjB = null; gFuncB = null;
+ OpenLayers.Events.prototype.register.apply(ev, [type, object, func]);
+ t.ok((gObjA == null) && (gFuncA == null) &&
+ (gObjB == object) && (gFuncB == func), "push called on listenerB's mock array when type passed in 'listenerB'.");
+
+ }
+
+ function test_Events_register_unregister(t) {
+
+ t.plan(20);
+
+ var mapDiv = OpenLayers.Util.getElement('map');
+ var obj = {result: 0};
+
+ events = new OpenLayers.Events(obj, mapDiv);
+
+ var func = function () { this.result++ }
+ events.register( "doThingA", obj, func );
+
+ var listenerList = events.listeners["doThingA"];
+ t.eq( listenerList.length, 1, "register correctly adds to event.listeners" );
+ t.ok( listenerList[0].obj == obj, "obj property correctly registered");
+ t.ok( listenerList[0].func == func, "func property correctly registered");
+
+ var func2 = function () { this.result-- }
+ events.register( "doThingA", obj, func2 );
+
+ var listenerList = events.listeners["doThingA"];
+ t.eq( listenerList.length, 2, "register correctly appends new callback to event.listeners[doThingA]" );
+ t.ok( listenerList[1].obj == obj, "obj property correctly registered");
+ t.ok( listenerList[1].func == func2, "func property correctly registered");
+
+ var func3 = function () { this.result = this.result + 3; }
+ events.register( "doThingA", null, func3 );
+
+ var listenerList = events.listeners["doThingA"];
+ t.eq( listenerList.length, 3, "register correctly appends new callback to event.listeners[doThingA] even when obj passed in is null" );
+ t.ok( listenerList[2].obj == obj, "obj is correctly set to Events.object default when null is passed in.");
+ t.ok( listenerList[2].func == func3, "func property correctly registered");
+
+ events.register( "doThingA", obj, null);
+
+ var listenerList = events.listeners["doThingA"];
+ t.eq( listenerList.length, 3, "register correctly does not append null callback to event.listeners[doThingA] even when obj passed in is null" );
+
+ events.register("chicken", obj, func);
+ t.eq(events.listeners["chicken"].length, 1, "register() allows listeners for any named event");
+
+ events.unregister("chicken", obj, func);
+ t.eq(events.listeners["chicken"].length, 0, "unregistering an event that is not in eventTypes list works")
+
+ events.unregister("doThingA", obj, null);
+ var listenerList = events.listeners["doThingA"];
+ t.eq( listenerList.length, 3, "trying to unregister a null callback does nothing -- but doesnt crash :-)" );
+
+ events.unregister("doThingA", obj, func);
+ var found = false;
+ for (var i = 0; i < listenerList.length; i++) {
+ var listener = listenerList[i];
+ if (listener.obj == obj && listener.func == func) {
+ found = true;
+ }
+ }
+ t.ok( (listenerList.length == 2) && !found, "unregister correctly removes callback" );
+
+ events.unregister("doThingA", null, func3);
+ var found = false;
+ for (var i = 0; i < listenerList.length; i++) {
+ var listener = listenerList[i];
+ if (listener.obj == obj && listener.func == func) {
+ found = true;
+ }
+ }
+ t.ok( (listenerList.length == 1) && !found, "unregister correctly removes callback when no obj specified" );
+
+ var func4 = function () { this.result = "chicken"; }
+ events.unregister("doThingA", obj, func4);
+ t.ok( (listenerList.length == 1), "unregister does not bomb if you try to remove an unregistered callback" );
+
+ var obj2 = { chicken: 151 };
+ events.unregister("doThingA", obj2, func2);
+ t.ok( (listenerList.length == 1), "unregister does not bomb or accidntally remove if you try to remove a valid callback on a valid event type, but with the wrong context object" );
+
+ events.unregister("doThingA", obj, null);
+ t.ok( (listenerList.length == 1), "unregister does not bomb if you try to remove a null callback" );
+
+ try {
+ events.unregister("asdf", obj, func);
+ t.ok("unregistering for an event with no registered listeners works");
+ } catch (err) {
+ t.fail("unregistering for an event with no registered listeners causes trouble: " + err);
+ }
+
+ events.register("buttonclick", obj, func);
+ t.ok(events.extensions.buttonclick, "buttonclick extension registered");
+
+ }
+
+ function test_Events_remove(t) {
+
+ t.plan( 2 );
+
+ var mapDiv = OpenLayers.Util.getElement('map');
+ var obj = {result: 0};
+
+ events = new OpenLayers.Events(obj, mapDiv);
+
+ var func = function () { this.result++ }
+ var func2 = function () { this.result-- }
+ var func3 = function () { this.result = this.result + 3; }
+
+ events.register( "doThingA", obj, func );
+ events.register( "doThingA", obj, func2 );
+ events.register( "doThingA", null, func3 );
+
+ events.remove("doThingA");
+
+ t.eq( events.listeners["doThingA"].length, 0, "remove() correctly clears the event listeners" );
+
+ events.remove("chicken");
+ t.ok( events.listeners["chicken"] == null, "remove on non-enabled event does not break or accidentally enable the event");
+
+ }
+
+ function test_Events_triggerEvent(t) {
+
+ t.plan(13);
+
+ var mapDiv = OpenLayers.Util.getElement('map');
+ var obj = {result: 0};
+
+ events = new OpenLayers.Events(obj, mapDiv);
+
+
+ var func = function () { this.result++ }
+ events.register( "doThingA", obj, func );
+
+ events.triggerEvent("doThingA", {});
+ t.eq( obj.result, 1, "result is 1 after we call triggerEvent" );
+ events.triggerEvent("doThingA");
+ t.eq( obj.result, 2, "result is 2 after we call triggerEvent with no event" );
+
+ var funcB = function() { this.FUNK = "B"; return false; }
+ events.register( "doThingA", obj, funcB);
+
+ events.triggerEvent("doThingA");
+ t.ok ((obj.result == 3) && (obj.FUNK == "B"), "executing multiple callbacks works")
+
+ var funcZ = function() { this.FUNK = "Z"; }
+ events.register( "doThingA", obj, funcZ);
+
+ events.triggerEvent("doThingA");
+ t.ok ((obj.result == 4) && (obj.FUNK == "B"), "executing multiple callbacks works, and when one returns false, it stops chain correctly")
+
+
+ var func2 = function() { this.result = this.result + 10; }
+ events.register( "doThingB", null, func2);
+
+ events.triggerEvent("doThingB");
+ t.eq( obj.result, 14, "passing a null object default gets set correctly");
+
+ //no specific t.ok for this one, but if it breaks, you will know it.
+ events.triggerEvent("chicken");
+
+ events = new OpenLayers.Events(null, mapDiv);
+
+ // a is global variable (context-irrelevant)
+ a = 0;
+ var func = function () { a = 5; }
+ events.register( "doThingC", null, func );
+ events.triggerEvent("doThingC");
+
+ t.eq(a, 5, "if Events has no object set and an event is registered also with no object, triggerEvent() calls it without trying to set the context to null");
+
+ // trigger events with additional arguments
+ events = new OpenLayers.Events();
+ var instance = {id: Math.random()};
+ var listener = function(obj) {
+ t.eq(this.id, instance.id,
+ "listener called with proper scope");
+ t.eq(arguments.length, 1,
+ "listener called with a single argument");
+ t.eq(typeof arguments, "object",
+ "listener called with an object");
+ t.eq(obj.foo, evt.foo,
+ "foo property set on the layer");
+ };
+ events.register("something", instance, listener);
+ var evt = {
+ id: Math.random(),
+ "foo": "bar"
+ };
+ events.triggerEvent("something", evt);
+ events.unregister("something", instance, listener);
+
+ // test return from triggerEvent
+ var listener1 = function() {
+ return "foo";
+ }
+ var listener2 = function() {
+ return false;
+ }
+ var listener3 = function() {
+ t.fail("never call me again!");
+ }
+ events.register("something", instance, listener1);
+ var ret = events.triggerEvent("something", evt);
+ t.eq(ret, "foo", "correct return from single listener");
+
+ events.register("something", instance, listener2);
+ ret = events.triggerEvent("something", evt);
+ t.eq(ret, false, "correct return for two listeners");
+
+ events.register("something", instance, listener3);
+ ret = events.triggerEvent("something", evt);
+ t.eq(ret, false, "correct return for three listeners where second cancels");
+
+ events.unregister("something", instance, listener1);
+ events.unregister("something", instance, listener2);
+ events.unregister("something", instance, listener3);
+ }
+
+ function test_Events_handleBrowserEvent(t) {
+ t.plan(8);
+ var events = new OpenLayers.Events({}, null);
+ events.on({'sometouchevent': function() {}});
+
+ // this test verifies that when handling a touch event we correctly
+ // set clientX and clientY in the event object
+ var evt = {type: 'sometouchevent',
+ touches: [{clientX: 1, clientY: 1}, {clientX: 2, clientY: 2}]
+ };
+ events.handleBrowserEvent(evt);
+ t.eq(evt.clientX, 1.5, "evt.clientX value is correct");
+ t.eq(evt.clientY, 1.5, "evt.clientY value is correct");
+
+ // test bug where clientX/clientY includes scroll offset
+ window.olMockWin = {
+ pageXOffset: 10,
+ pageYOffset: 20
+ };
+ evt = {type: 'sometouchevent',
+ touches: [{
+ clientX: 11,
+ clientY: 21,
+ pageX: 0,
+ pageY: 0
+ }]
+ };
+ events.handleBrowserEvent(evt);
+ t.eq(evt.clientX, 1, "evt.clientX value is correct");
+ t.eq(evt.clientY, 1, "evt.clientY value is correct");
+
+
+ // test bug where clientX/clientY have negative values
+ evt = {
+ type: 'sometouchevent',
+ touches: [{
+ clientX: -412,
+ clientY: -1005,
+ pageX: 11,
+ pageY: 21
+ }]
+ };
+ events.handleBrowserEvent(evt);
+ t.eq(evt.clientX, 1, "evt.clientX value is correct");
+ t.eq(evt.clientY, 1, "evt.clientY value is correct");
+
+ window.olMockWin = {
+ pageXOffset: 11,
+ pageYOffset: 299
+ };
+ evt = {
+ type: 'sometouchevent',
+ touches: [{
+ clientX: 223,
+ clientY: 119,
+ pageX: 242,
+ pageY: 623
+ }]
+ };
+ events.handleBrowserEvent(evt);
+ t.eq(evt.clientX, 231, "evt.clientX value is correct");
+ t.eq(evt.clientY, 324, "evt.clientY value is correct");
+
+ window.olMockWin = undefined;
+ }
+
+ function test_Events_attachToElement(t) {
+ t.plan(3);
+ var events = new OpenLayers.Events({}, null);
+ var element = document.createElement("div");
+ events.attachToElement(element);
+ t.ok(events.eventHandler, "eventHandler method bound");
+ t.ok(events.clearMouseListener, "clearMouseListener method bound");
+ t.ok(events.element === element, "element set");
+ }
+
+ function test_Events_destroy (t) {
+ t.plan(2);
+
+ var div = OpenLayers.Util.getElement('test');
+ var obj = {};
+ var events = new OpenLayers.Events(obj, div);
+
+ // +1 because of blocking dragstart in attachToElement()
+ t.eq(OpenLayers.Event.observers[div._eventCacheID].length,
+ OpenLayers.Events.prototype.BROWSER_EVENTS.length + 1,
+ "construction creates new arrayin hash, registers appropriate events");
+
+ events.destroy();
+ events = null;
+ t.eq(OpenLayers.Event.observers["test"], null,
+ "destruction removes the event observer from hash");
+ }
+
+ function test_Event(t) {
+ t.plan(24);
+
+ var div = OpenLayers.Util.getElement('test');
+ var name = "mouseover";
+ var func = function() {};
+
+ //1st elem 1st listener
+ OpenLayers.Event.observe(div, name, func);
+
+ var cacheID = div._eventCacheID;
+ t.ok(cacheID, "element given new cache id");
+
+ var elementObservers = OpenLayers.Event.observers[cacheID];
+
+ t.ok(elementObservers, "new cache bucket made for event");
+ t.eq(elementObservers.length, 1, "one listener registered");
+
+ var listener = elementObservers[0];
+
+ t.ok(listener.element == div, "element registered");
+ t.eq(listener.name, name, "name registered");
+ t.ok(listener.observer == func, "function registered");
+ t.eq(listener.useCapture, false, "useCapture defaults to false");
+
+ //1st elem 2nd listener
+ name = "mouseout";
+ var newFunc = function() {};
+
+ OpenLayers.Event.observe(div, name, newFunc, true);
+ var newCacheID = div._eventCacheID;
+ t.eq(newCacheID, cacheID, "element's cache id not overridden");
+
+ t.eq(elementObservers.length, 2, "listener added to existing bucket");
+
+ var listener = elementObservers[1];
+
+ t.ok(listener.element == div, "element registered");
+ t.eq(listener.name, name, "name registered");
+ t.ok(listener.observer == newFunc, "function registered");
+ t.eq(listener.useCapture, true, "useCapture correctly registered");
+
+ //2st elem 1st listener
+ div = OpenLayers.Util.getElement('test2');
+ OpenLayers.Event.observe(div, name, func);
+
+ var cacheID = div._eventCacheID;
+ t.ok(cacheID, "new element given new cache id");
+ t.ok(cacheID != newCacheID, "new cache id is unique");
+
+ elementObservers = OpenLayers.Event.observers[cacheID];
+
+ t.ok(elementObservers, "new cache bucket made for event");
+ t.eq(elementObservers.length, 1, "one listener registered");
+
+ var listener = elementObservers[0];
+
+ t.ok(listener.element == div, "element registered");
+ t.eq(listener.name, name, "name registered");
+ t.ok(listener.observer == func, "function registered");
+ t.eq(listener.useCapture, false, "useCapture defaults to false");
+
+ //stopObservingElement by element
+ OpenLayers.Event.stopObservingElement(div);
+ elementObservers = OpenLayers.Event.observers[cacheID];
+ t.ok(elementObservers == null, "stopObservingElement by elem works");
+
+ //stopObservingElement by id
+ OpenLayers.Event.stopObservingElement("test");
+ elementObservers = OpenLayers.Event.observers[newCacheID];
+ t.ok(elementObservers == null, "stopObservingElement by id works");
+
+
+ //unloadCache by element
+ OpenLayers.Event.observe(div, name, func);
+
+ OpenLayers.Event.unloadCache();
+
+ elementObservers = OpenLayers.Event.observers[cacheID];
+ t.ok(elementObservers == null, "stopObservingElement by elem works");
+
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+ <div id="test"></div>
+ <div id="test2"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Events/buttonclick.html b/misc/openlayers/tests/Events/buttonclick.html
new file mode 100644
index 0000000..dadbd3a
--- /dev/null
+++ b/misc/openlayers/tests/Events/buttonclick.html
@@ -0,0 +1,214 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var log, buttonClick, events, element, button;
+ function init() {
+ element = document.getElementById("map");
+ button = document.getElementById("button");
+ }
+ function trigger(evt) {
+ OpenLayers.Util.applyDefaults(evt, {
+ button: 1,
+ target: button
+ });
+ events.handleBrowserEvent(evt);
+ }
+ function logEvent(evt) {
+ log.push(evt);
+ }
+
+ function test_ButtonClick(t) {
+ t.plan(1);
+ events = new OpenLayers.Events({}, element);
+ buttonClick = new OpenLayers.Events.buttonclick(events);
+ t.ok(buttonClick.target === events, "target set from constructor arg");
+ buttonClick.destroy();
+ events.destroy();
+ }
+
+ function test_getPressedButton(t) {
+ t.plan(4);
+
+ // set up
+
+ events = new OpenLayers.Events({}, element);
+ buttonClick = new OpenLayers.Events.buttonclick(events);
+
+ var button = document.createElement('button'),
+ span1 = document.createElement('span'),
+ span2 = document.createElement('span'),
+ span3 = document.createElement('span');
+ button.className = 'olButton';
+ button.appendChild(span1);
+ span1.appendChild(span2);
+ span2.appendChild(span3);
+
+ t.ok(buttonClick.getPressedButton(button) === button,
+ 'getPressedButton returns button when element is button');
+ t.ok(buttonClick.getPressedButton(span1) === button,
+ 'getPressedButton returns button when element is button descendant level 1');
+ t.ok(buttonClick.getPressedButton(span2) === button,
+ 'getPressedButton returns button when element is button descendant level 2');
+ t.eq(buttonClick.getPressedButton(span3), undefined,
+ 'getPressedButton returns undefined when element is button descendant level 3');
+
+ // test
+
+
+ // tear down
+
+ buttonClick.destroy();
+ events.destroy();
+ }
+
+ function test_ignore(t) {
+ t.plan(5);
+
+ // set up
+
+ events = new OpenLayers.Events({}, element);
+ buttonClick = new OpenLayers.Events.buttonclick(events);
+
+ var link = document.createElement('a'),
+ span1 = document.createElement('span'),
+ span2 = document.createElement('span'),
+ span3 = document.createElement('span');
+ link.appendChild(span1);
+ span1.appendChild(span2);
+ span2.appendChild(span3);
+
+ t.eq(buttonClick.ignore(link), true,
+ 'ignore returns true when element is a link');
+ t.eq(buttonClick.ignore(span1), true,
+ 'ignore returns true when element is link descendant level 1');
+ t.eq(buttonClick.ignore(span2), true,
+ 'ignore returns true when element is link descendant level 2');
+ t.eq(buttonClick.ignore(span3), false,
+ 'ignore returns false when element is link descendant level 3');
+ t.eq(buttonClick.ignore(element), false,
+ 'ignore returns false when element is not a link');
+
+
+ // tear down
+
+ buttonClick.destroy();
+ events.destroy();
+ }
+
+ function test_ButtonClick_buttonClick(t) {
+ t.plan(27);
+ events = new OpenLayers.Events({}, element);
+ events.on({
+ "buttonclick": logEvent,
+ "mousedown": logEvent,
+ "mouseup": logEvent,
+ "click": logEvent,
+ "dblclick": logEvent,
+ "touchstart": logEvent,
+ "touchend": logEvent,
+ "keydown": logEvent
+ });
+ buttonClick = events.extensions["buttonclick"];
+
+ // a complete click
+ log = [];
+ trigger({type: "mousedown"});
+ trigger({type: "mouseup"});
+ t.eq(log.length, 1, "one event fired for mousedown-mouseup");
+ t.eq(log[0].type, "buttonclick", "buttonclick event fired");
+
+ // a complete tap
+ log = [];
+ trigger({type: "touchstart"});
+ trigger({type: "touchend"});
+ t.eq(log.length, 1, "one event fired for touchstart-touchend");
+ t.eq(log[0].type, "buttonclick", "buttonclick event fired");
+
+ // mouse sequence started on button
+ log = [];
+ trigger({type: "mousedown"});
+ trigger({type: "mouseup", target: element});
+ t.eq(log.length, 1, "one event fired for mousedown-(leave)-mouseup");
+ t.eq(log[0].type, "mouseup", "mouseup event goes through when sequence not finished on button");
+
+ // touch sequence started on button
+ log = [];
+ trigger({type: "touchstart"});
+ trigger({type: "touchmove"});
+ trigger({type: "touchend"});
+ t.eq(log.length, 1, "one event fired for touchstart-(leave)-touchend");
+ t.eq(log[0].type, "touchend", "touchend event goes through when sequence not finished on button");
+
+ // mouse sequence finished on button
+ log = [];
+ trigger({type: "mousedown", target: element});
+ trigger({type: "mouseup"});
+ t.eq(log.length, 2, "two event fired for mousedown-(enter)-mouseup");
+ t.eq(log[0].type, "mousedown", "mousedown unrelated to button goes through");
+ t.eq(log[1].type, "mouseup", "mouseup goes through when sequence started outside button");
+
+ // touch sequence finished on button
+ log = [];
+ trigger({type: "touchstart", target: element});
+ trigger({type: "touchend"});
+ t.eq(log.length, 2, "two event fired for touchstart-(enter)-touchend");
+ t.eq(log[0].type, "touchstart", "touchstart unrelated to button goes through");
+ t.eq(log[1].type, "touchend", "touchend goes through when sequence started outside button");
+
+ // dblclick
+ log = [];
+ trigger({type: "mousedown"});
+ trigger({type: "mouseup"});
+ trigger({type: "click"});
+ trigger({type: "mousedown"});
+ trigger({type: "mouseup"});
+ trigger({type: "click"});
+ trigger({type: "dblclick"});
+ t.eq(log.length, 2, "two events fired for doubleclick");
+ t.eq(log[0].type, "buttonclick", "buttonclick for 1st click");
+ t.eq(log[1].type, "buttonclick", "buttonclick for 2nd click");
+
+ // dblclick - IE
+ log = [];
+ trigger({type: "mousedown"});
+ trigger({type: "mouseup"});
+ trigger({type: "mouseup"});
+ trigger({type: "dblclick"});
+ t.eq(log.length, 2, "two events fired for dblclick in IE");
+ t.eq(log[0].type, "buttonclick", "buttonclick for 1st click in IE");
+ t.eq(log[1].type, "buttonclick", "buttonclick for 2nd click IE");
+
+ // rightclick
+ log = [];
+ trigger({type: "mousedown", button: 2});
+ trigger({type: "mouseup", button: 2});
+ t.eq(log.length, 2, "two events fired for rightclick");
+ t.eq(log[0].type, "mousedown", "mousedown from rightclick goes through");
+ t.eq(log[1].type, "mouseup", "mouseup from rightclick goes through");
+
+ // keydown RETURN
+ log = [];
+ trigger({type: "keydown", keyCode: OpenLayers.Event.KEY_RETURN});
+ trigger({type: "click"});
+ t.eq(log.length, 1, "one event fired for RETURN keydown");
+ t.eq(log[0].type, "buttonclick", "buttonclick for RETURN keydown");
+
+ // keydown SPACE
+ log = [];
+ trigger({type: "keydown", keyCode: OpenLayers.Event.KEY_SPACE});
+ trigger({type: "click"});
+ t.eq(log.length, 1, "one event fired for SPACE keydown");
+ t.eq(log[0].type, "buttonclick", "buttonclick for SPACE keydown");
+ }
+ </script>
+</head>
+<body onload="init()">
+ <div id="map" style="width: 600px; height: 300px;">
+ <div id="button" class="olButton">
+ <img class="olAlphaImg">
+ </div>
+ </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Events/featureclick.html b/misc/openlayers/tests/Events/featureclick.html
new file mode 100644
index 0000000..ae111b7
--- /dev/null
+++ b/misc/openlayers/tests/Events/featureclick.html
@@ -0,0 +1,91 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+<script type="text/javascript">
+
+var layer1, style, logevt, lognoevt, map, lonlat, pixel, element;
+
+function init() {
+
+ element = document.getElementById("map");
+
+ style = new OpenLayers.StyleMap({
+ 'default': OpenLayers.Util.applyDefaults(
+ {label: "${l}", pointRadius: 30},
+ OpenLayers.Feature.Vector.style["default"]
+ ),
+ 'select': OpenLayers.Util.applyDefaults(
+ {pointRadius: 30},
+ OpenLayers.Feature.Vector.style.select
+ )
+ });
+
+ layer1 = new OpenLayers.Layer.Vector("Layer 1", {
+ styleMap: style
+ });
+
+ layer1.addFeatures([
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(0 0)"), {l:1}),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(0 0)"), {l:1}),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(0 0)"), {l:1}),
+ new OpenLayers.Feature.Vector(OpenLayers.Geometry.fromWKT("POINT(0 0)"), {l:1})
+ ]);
+
+ map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ layers: [layer1],
+ zoom: 6,
+ center: [0, 0],
+ eventListeners: {
+ featureclick: logEvent,
+ nofeatureclick: logNoEvent
+ }
+ });
+}
+
+function logNoEvent(e) {
+ lognoevt.push(e);
+}
+
+function logEvent(e) {
+ logevt.push(e);
+}
+
+function trigger(type, pxl) {
+ var map_position = OpenLayers.Util.pagePosition(element);
+ map.events.triggerEvent(type, {
+ xy: pxl,
+ clientX: pxl.x + map_position[0],
+ clientY: pxl.y + map_position[1],
+ which: 1 // which == 1 means left-click
+ });
+}
+
+// TESTS
+
+function test_onClick(t) {
+ t.plan(2);
+ logevt = [];
+ lognoevt = [];
+ lonlat = new OpenLayers.LonLat(0,0);
+ pixel = map.getPixelFromLonLat(lonlat);
+
+ trigger('mousedown', pixel);
+ trigger('mouseup', pixel);
+
+ t.eq(logevt.length, 4, "4 features hit");
+
+ trigger('mousedown', {x: 40, y: 40});
+ trigger('mouseup', {x: 40, y: 40});
+ t.eq(lognoevt.length, 1, "nofeatureclick fired for click outside features.");
+}
+
+// END TESTS
+
+</script>
+</head>
+<body onload="init()">
+<div id="map" style="width: 300px; height: 150px; border: 1px solid black"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Extras.html b/misc/openlayers/tests/Extras.html
new file mode 100644
index 0000000..264eb0f
--- /dev/null
+++ b/misc/openlayers/tests/Extras.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var map;
+
+ // Ensure that we continue to work if silly Javascript frameworks
+ // extend object.
+ Object.prototype.foo = function() { }
+ function test_Events_Object_Extension(t) {
+ t.plan(1)
+ map = new OpenLayers.Map("map");
+ t.ok(true, "Map created if object prototype is extended.");
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 600px; height: 300px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Feature.html b/misc/openlayers/tests/Feature.html
new file mode 100644
index 0000000..aa3db24
--- /dev/null
+++ b/misc/openlayers/tests/Feature.html
@@ -0,0 +1,205 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var map;
+ var feature, layer;
+
+ function test_Feature_constructor (t) {
+ t.plan( 6 );
+
+ var layer = {};
+ var lonlat = new OpenLayers.LonLat(2,1);
+ var iconURL = 'http://boston.openguides.org/features/ORANGE.png';
+ var iconSize = new OpenLayers.Size(12, 17);
+ var data = { iconURL: iconURL,
+ iconSize: iconSize
+ };
+
+ feature = new OpenLayers.Feature(layer, lonlat, data);
+
+ t.ok( feature instanceof OpenLayers.Feature, "new OpenLayers.Feature returns Feature object" );
+ t.eq( feature.layer, layer, "feature.layer set correctly" );
+ t.ok(OpenLayers.String.startsWith(feature.id, "OpenLayers_Feature_"),
+ "feature.id set correctly");
+ t.ok( feature.lonlat.equals(lonlat), "feature.lonlat set correctly" );
+ t.eq( feature.data.iconURL, iconURL, "feature.data.iconURL set correctly" );
+ t.ok( feature.data.iconSize.equals(iconSize), "feature.data.iconSize set correctly" );
+ }
+
+ function test_Feature_createPopup (t) {
+ t.plan(3);
+ var layer = {};
+ var lonlat = new OpenLayers.LonLat(2,1);
+ var iconURL = 'http://boston.openguides.org/features/ORANGE.png';
+ var iconSize = new OpenLayers.Size(12, 17);
+ var data = { iconURL: iconURL,
+ iconSize: iconSize,
+ 'overflow':'auto'
+ };
+
+ feature = new OpenLayers.Feature(layer, lonlat, data);
+ popup = feature.createPopup();
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq(popup.contentDiv.style[prop], "auto", 'overflow on popup is correct');
+ t.ok( popup instanceof OpenLayers.Popup.Anchored, "popup is a Popup.Anchored by default");
+ feature.destroyPopup();
+
+ feature.popupClass = OpenLayers.Popup.FramedCloud;
+ popup = feature.createPopup();
+ t.ok( popup instanceof OpenLayers.Popup.FramedCloud, "setting feature.popupClass works");
+ }
+ function test_Feature_createMarker (t) {
+ t.plan(1);
+ t.ok(true);
+/*
+
+ t.plan( 11 );
+ feature = new OpenLayers.Feature("myfeature", new OpenLayers.LonLat(2,1),
+ {
+ iconURL:'http://boston.openguides.org/features/ORANGE.png',
+ iconW: 12,
+ iconH: 17
+ });
+ layer = new OpenLayers.Layer.Markers('Marker Layer');
+ t.ok( feature instanceof OpenLayers.Feature, "new OpenLayers.Feature returns Feature object" );
+ t.ok( layer instanceof OpenLayers.Layer.Markers, "Layer is a marker layer" );
+ feature.createMarker(layer);
+
+ t.ok( feature.marker instanceof OpenLayers.Marker,
+ "createMarker sets a marker property to a marker" );
+ t.ok( layer.markers[0] === feature.marker,
+ "First marker in layer is the feature marker" );
+
+ t.ok( feature.marker.lonlat instanceof OpenLayers.LonLat,
+ "createMarker sets a marker lontlat property to a lonlat" );
+ t.ok( layer.markers[0].lonlat === feature.lonlat,
+ "First marker in the layer matches feature lonlat" );
+
+ t.ok( feature.marker.icon instanceof OpenLayers.Icon,
+ "createMarker sets a marker icon property to an icon" );
+
+ t.eq( feature.marker.icon.url,
+ "http://boston.openguides.org/features/ORANGE.png",
+ "createMarker sets marker url correctly" );
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ t.ok( map.layers[0] == layer,
+ "Marker layer added to map okay." );
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( map.layers[0].div.firstChild instanceof HTMLImageElement,
+ "layer div firstChild is an image" );
+ t.eq( map.layers[0].div.firstChild.src,
+ "http://boston.openguides.org/features/ORANGE.png",
+ "Layer div img contains correct url" );
+*/
+ }
+
+ function test_Feature_onScreen(t) {
+ t.plan( 2 );
+
+ var map = new OpenLayers.Map("map");
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var wms = new OpenLayers.Layer.WMS(name, url);
+
+ map.addLayer(wms);
+
+ var layer = new OpenLayers.Layer("foo");
+ map.addLayer(layer);
+
+ map.zoomToExtent(new OpenLayers.Bounds(-50,-50,50,50));
+
+ //onscreen feature
+ var feature1 = new OpenLayers.Feature(layer,
+ new OpenLayers.LonLat(0,0));
+ t.ok( feature1.onScreen(), "feature knows it's onscreen" );
+
+ //onscreen feature
+ var feature2 = new OpenLayers.Feature(layer,
+ new OpenLayers.LonLat(100,100));
+ t.ok( !feature2.onScreen(), "feature knows it's offscreen" );
+ }
+
+ function test_Feature_createPopup_2(t) {
+ t.plan(11);
+
+ //no lonlat
+ var f = {
+ 'popup': null
+ };
+
+ var ret = OpenLayers.Feature.prototype.createPopup.apply(f, []);
+ t.ok((ret == f.popup) && (f.popup == null), "if no 'lonlat' set on feature, still returns reference to this.popup (though it is null)");
+
+
+
+ f.popupClass = OpenLayers.Class({
+ initialize: function(id, lonlat, size, contentHTML, anchor, closeBox) {
+ t.eq(id, "Campion_popup", "correctly generates new popup id from feature's id");
+ t.eq(lonlat, f.lonlat, "correctly passes feature's lonlat to popup constructor");
+ t.eq(size, f.data.popupSize, "correctly passes feature's data.popupSize to popup constructor");
+ t.eq(contentHTML, f.data.popupContentHTML, "correctly passes feature's data.popupContentHTML to popup constructor");
+ t.eq(anchor, g_ExpectedAnchor, "passes correct anchor to popup constructor");
+ t.eq(closeBox, g_CloseBox, "correctly relays closeBox argument to popup constructor");
+ }
+ });
+
+
+ //valid lonlat but no anchor
+ f.popup = null;
+
+ f.id = "Campion";
+ f.lonlat = {};
+ f.data = {
+ 'popupSize': {},
+ 'popupContentHTML': {}
+ };
+ g_ExpectedAnchor = null;
+ g_CloseBox = {};
+
+ ret = OpenLayers.Feature.prototype.createPopup.apply(f, [g_CloseBox]);
+
+ t.ok((ret == f.popup) && (f.popup != null), "a valid popup has been set and returned")
+ t.ok( f.popup.feature == f, "popup's 'feature' property correctly set");
+
+
+ //valid lonlat with anchor
+
+ f.marker = {
+ 'icon': {}
+ };
+ g_ExpectedAnchor = f.marker.icon;
+ ret = OpenLayers.Feature.prototype.createPopup.apply(f, [g_CloseBox]);
+ t.ok((ret == f.popup) && (f.popup != null), "a valid popup has been set and returned")
+ t.ok( f.popup.feature == f, "popup's 'feature' property correctly set");
+ }
+
+ function test_Feature_destroyPopup(t) {
+ t.plan(2);
+
+ var f = {
+ 'popup': {
+ 'feature': {},
+ 'destroy': function() {
+ t.ok(true, "default destroyPopup() calls popup.destroy");
+ }
+ }
+ };
+
+ OpenLayers.Feature.prototype.destroyPopup.apply(f, []);
+ t.ok(f.popup == null, "popup property nullified on destroy");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 500px; height: 300px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Feature/Vector.html b/misc/openlayers/tests/Feature/Vector.html
new file mode 100644
index 0000000..59b0abb
--- /dev/null
+++ b/misc/openlayers/tests/Feature/Vector.html
@@ -0,0 +1,170 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map;
+ var feature;
+
+ function test_Feature_Vector_constructor(t) {
+ t.plan(3);
+
+ var geometry = new OpenLayers.Geometry();
+ geometry.id = Math.random();
+ var style = {foo: "bar"};
+ var attributes = {bar: "foo"};
+
+ feature = new OpenLayers.Feature.Vector(geometry, attributes, style);
+
+ t.ok(feature instanceof OpenLayers.Feature.Vector,
+ "new OpenLayers.Feature.Vector returns Feature.Vector object" );
+ t.eq(feature.attributes, attributes,
+ "attributes property set properly" );
+ t.eq(feature.geometry.id, geometry.id,
+ "geometry.property set properly" );
+ }
+
+ function test_Feature_onScreen(t) {
+ t.plan(6);
+ var line = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(10, 20)
+ ]);
+ var feature = new OpenLayers.Feature.Vector(line);
+ feature.layer = {
+ map: {
+ getExtent: function() {
+ return new OpenLayers.Bounds(5, 5, 10, 10);
+ }
+ }
+ };
+ t.eq(feature.onScreen(), true,
+ "intersecting feature returns true for intersection");
+ t.eq(feature.onScreen(true), true,
+ "intersecting feature returns true for bounds only");
+
+ // move the line so only the bounds intersects
+ line.move(0, 5);
+ t.eq(feature.onScreen(), false,
+ "bounds-only feature returns false for intersection");
+ t.eq(feature.onScreen(true), true,
+ "bounds-only feature returns true for bounds only");
+
+ // move the line so bounds does not intersect
+ line.move(0, 10);
+ t.eq(feature.onScreen(), false,
+ "off-screen feature returns false for intersection");
+ t.eq(feature.onScreen(true), false,
+ "off-screen feature returns false for bounds only");
+
+ }
+
+ function test_Feature_getVisibility(t) {
+ t.plan(5);
+ var feature = new OpenLayers.Feature.Vector();
+ feature.layer = {
+ getVisibility: function() {return true}
+ };
+
+ t.ok(feature.getVisibility(),
+ "returns true in a not specific case");
+
+ feature.style = {display: 'none'};
+ t.eq(feature.getVisibility(), false,
+ "returns false when feature style display property is set to 'none'");
+
+ feature.style = null;
+ feature.layer.styleMap = {
+ createSymbolizer: function() {
+ return {display: 'none'}
+ }
+ }
+ t.eq(feature.getVisibility(), false,
+ "returns false when layer styleMap is configured so that the feature" +
+ "should not be displayed");
+
+ delete feature.layer.styleMap;
+ feature.layer.getVisibility = function() {return false}
+ t.eq(feature.getVisibility(), false,
+ "returns false when layer it belongs to is not visible");
+
+ feature.layer = null;
+ t.eq(feature.getVisibility(), false,
+ "returns false when it doesn't belong to any layer");
+ }
+
+ function test_Feature_Vector_clone(t) {
+ t.plan(6);
+
+ var geometry = new OpenLayers.Geometry.Point(Math.random(),
+ Math.random());
+ var style = {foo: "bar"};
+ var attributes = {bar: "foo"};
+
+ feature = new OpenLayers.Feature.Vector(geometry, attributes, style);
+ var clone = feature.clone();
+
+ t.ok(clone instanceof OpenLayers.Feature.Vector,
+ "new OpenLayers.Feature.Vector returns Feature.Vector object");
+ t.eq(clone.attributes, attributes,
+ "attributes property set properly");
+ t.eq(clone.style, style,
+ "style property set properly");
+ t.eq(clone.geometry.x, geometry.x,
+ "geometry.x property set properly");
+ t.eq(clone.geometry.y, geometry.y,
+ "geometry.y property set properly");
+
+ feature = new OpenLayers.Feature.Vector();
+ clone = feature.clone();
+ t.ok(clone instanceof OpenLayers.Feature.Vector,
+ "clone can clone geometry-less features");
+ }
+
+ function test_Feature_Vector_move(t) {
+ t.plan(3);
+
+ var oldx = 26;
+ var oldy = 14;
+ var newx = 6;
+ var newy = 4;
+ var res = 10;
+
+ var geometry = new OpenLayers.Geometry.Point(oldx,
+ oldy);
+
+ var drawn = false;
+
+ feature = new OpenLayers.Feature.Vector(geometry);
+
+ feature.layer = {
+ getViewPortPxFromLonLat : function(lonlat){
+ return new OpenLayers.Pixel(lonlat.lon,lonlat.lat);
+ },
+ map: {
+ getResolution: function(){
+ return res;
+ }
+ },
+ drawFeature: function(){
+ drawn = true;
+ }
+ }
+
+ var pixel = new OpenLayers.Pixel(newx,newy)
+
+ feature.move(pixel);
+
+ geometry = feature.geometry;
+
+ t.ok(drawn, "The feature is redrawn after the move");
+ t.eq(geometry.x, res * (newx - oldx) + oldx, "New geometry has proper x coordinate");
+ t.eq(geometry.y, res * (oldy - newy) + oldy, "New geometry has proper y coordinate");
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 500px; height: 300px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Filter.html b/misc/openlayers/tests/Filter.html
new file mode 100644
index 0000000..01b8f47
--- /dev/null
+++ b/misc/openlayers/tests/Filter.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var filter = new OpenLayers.Filter(options);
+ t.ok(filter instanceof OpenLayers.Filter,
+ "new OpenLayers.Filter returns object" );
+ t.eq(filter.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof filter.evaluate, "function", "filter has an evaluate function");
+ }
+
+ function test_toString(t) {
+ t.plan(1);
+ var filter = new OpenLayers.Filter.Comparison({
+ property: "PERSONS",
+ value: 2000000,
+ type: OpenLayers.Filter.Comparison.LESS_THAN
+ });
+ t.eq(filter.toString(), "PERSONS < 2000000", "toString returns CQL representation");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Filter/Comparison.html b/misc/openlayers/tests/Filter/Comparison.html
new file mode 100644
index 0000000..04e192a
--- /dev/null
+++ b/misc/openlayers/tests/Filter/Comparison.html
@@ -0,0 +1,373 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var filter = new OpenLayers.Filter.Comparison(options);
+ t.ok(filter instanceof OpenLayers.Filter.Comparison,
+ "new OpenLayers.Filter.Comparison returns object" );
+ t.eq(filter.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof filter.evaluate, "function", "filter has an evaluate function");
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.Comparison();
+ filter.destroy();
+ t.eq(filter.symbolizer, null, "symbolizer hash nulled properly");
+ }
+
+ function test_value2regex(t) {
+ t.plan(4);
+
+ var filter = new OpenLayers.Filter.Comparison({
+ property: "foo",
+ value: "*b?r\\*\\?*",
+ type: OpenLayers.Filter.Comparison.LIKE});
+ filter.value2regex("*", "?", "\\");
+ t.eq(filter.value, ".*b.r\\*\\?.*", "Regular expression generated correctly.");
+
+ filter.value = "%b.r!%!.%";
+ filter.value2regex("%", ".", "!");
+ t.eq(filter.value, ".*b.r\\%\\..*", "Regular expression with different wildcard and escape chars generated correctly.");
+
+ filter.value = "!!";
+ filter.value2regex();
+ t.eq(filter.value, "\\!", "!! successfully unescaped to \\!");
+
+ // Big one.
+ filter.value = "!!c!!!d!e";
+ filter.value2regex();
+ t.eq(filter.value, "\\!c\\!\\d\\e", "!!c!!!d!e successfully unescaped to \\!c\\!\\d\\e");
+ }
+
+ function test_regex2value(t) {
+ t.plan(8);
+
+ function r2v(regex) {
+ return OpenLayers.Filter.Comparison.prototype.regex2value.call(
+ {value: regex}
+ );
+ }
+
+ t.eq(r2v("foo"), "foo", "doesn't change string without special chars");
+ t.eq(r2v("foo.*foo"), "foo*foo", "wildCard replaced");
+ t.eq(r2v("foo.foo"), "foo.foo", "singleChar replaced");
+ t.eq(r2v("foo\\\\foo"), "foo\\foo", "escape removed");
+ t.eq(r2v("foo!foo"), "foo!!foo", "escapes !");
+ t.eq(r2v("foo\\*foo"), "foo!*foo", "replaces escape on *");
+ t.eq(r2v("foo\\.foo"), "foo!.foo", "replaces escape on .");
+ t.eq(r2v("foo\\\\.foo"), "foo\\.foo", "unescapes only \\ before .");
+
+ }
+
+ function test_evaluate(t) {
+
+ var cases = [{
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: {area: 999},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: {area: 1000},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: {area: 4999},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: {area: 5000},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: {area: 999},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "Foo"
+ }),
+ context: {prop: "Foo"},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "Foo"
+ }),
+ context: {prop: "foo"},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ matchCase: true,
+ property: "prop",
+ value: "Foo"
+ }),
+ context: {prop: "foo"},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "prop",
+ value: "foo"
+ }),
+ context: {prop: "FOO"},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: true,
+ property: "prop",
+ value: "foo"
+ }),
+ context: {prop: "FOO"},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: false,
+ property: "prop",
+ value: "foo"
+ }),
+ context: {prop: "FOO"},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: {prop: null},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: {},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: {prop: "foo"},
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: {prop: 0},
+ expect: false
+ }];
+
+ t.plan(cases.length);
+
+ var c;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ t.eq(c.filter.evaluate(c.context), c.expect, "case " + i + ": " + c.filter.type);
+ }
+
+ }
+
+ function test_evaluate_feature(t) {
+
+ var cases = [{
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: new OpenLayers.Feature.Vector(null, {area: 999}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: new OpenLayers.Feature.Vector(null, {area: 1000}),
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: new OpenLayers.Feature.Vector(null, {area: 4999}),
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: new OpenLayers.Feature.Vector(null, {area: 5000}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "area",
+ lowerBoundary: 1000,
+ upperBoundary: 4999
+ }),
+ context: new OpenLayers.Feature.Vector(null, {area: 999}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "Foo"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: "Foo"}),
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "Foo"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: "foo"}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ matchCase: true,
+ property: "prop",
+ value: "Foo"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: "foo"}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "prop",
+ value: "foo"
+ }),
+ context: {prop: "FOO"},
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: true,
+ property: "prop",
+ value: "foo"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: "FOO"}),
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: false,
+ property: "prop",
+ value: "foo"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: "FOO"}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: null}),
+ expect: true
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: "foo"}),
+ expect: false
+ }, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ }),
+ context: new OpenLayers.Feature.Vector(null, {prop: 0}),
+ expect: false
+ }];
+
+ t.plan(cases.length);
+
+ var c;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ t.eq(c.filter.evaluate(c.context), c.expect, "case " + i + ": " + c.filter.type);
+ }
+
+ }
+
+ function test_clone(t) {
+
+ t.plan(3);
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "val"
+ });
+
+ var clone = filter.clone();
+
+ // modify the original
+ filter.type = OpenLayers.Filter.Comparison.NOT_EQUAL_TO;
+
+ t.eq(clone.type, OpenLayers.Filter.Comparison.EQUAL_TO, "clone has proper type");
+ t.eq(clone.property, "prop", "clone has proper property");
+ t.eq(clone.value, "val", "clone has proper value");
+
+ filter.destroy();
+
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Filter/FeatureId.html b/misc/openlayers/tests/Filter/FeatureId.html
new file mode 100644
index 0000000..8c20192
--- /dev/null
+++ b/misc/openlayers/tests/Filter/FeatureId.html
@@ -0,0 +1,67 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var filter = new OpenLayers.Filter.FeatureId(options);
+ t.ok(filter instanceof OpenLayers.Filter.FeatureId,
+ "new OpenLayers.Filter.FeatureId returns object" );
+ t.eq(filter.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof filter.evaluate, "function", "filter has an evaluate function");
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.FeatureId();
+ filter.destroy();
+ t.eq(filter.symbolizer, null, "symbolizer hash nulled properly");
+ }
+
+ function test_evaluate(t) {
+ t.plan(3);
+
+ var filter = new OpenLayers.Filter.FeatureId(
+ {fids: ["fid_1", "fid_3"]});
+
+ var filterResults = {
+ "fid_1" : true,
+ "fid_2" : false,
+ "fid_3" : true};
+ for (var i in filterResults) {
+ var feature = new OpenLayers.Feature.Vector();
+ feature.fid = i;
+ var result = filter.evaluate(feature);
+ t.eq(result, filterResults[i], "feature "+i+" evaluates to "+result.toString()+" correctly.");
+ feature.destroy();
+ }
+ }
+
+ function test_clone(t) {
+
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.FeatureId({
+ fids: [1, 2, 3]
+ });
+
+ var clone = filter.clone();
+
+ // modify the original
+ filter.fids.push(4);
+
+ t.eq(clone.fids.length, 3, "clone has proper fids length");
+
+ filter.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Filter/Logical.html b/misc/openlayers/tests/Filter/Logical.html
new file mode 100644
index 0000000..187cf5b
--- /dev/null
+++ b/misc/openlayers/tests/Filter/Logical.html
@@ -0,0 +1,144 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var filter = new OpenLayers.Filter.Logical(options);
+ t.ok(filter instanceof OpenLayers.Filter.Logical,
+ "new OpenLayers.Filter.Logical returns object" );
+ t.eq(filter.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof filter.evaluate, "function", "filter has an evaluate function");
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.Logical();
+ filter.destroy();
+ t.eq(filter.filters, null, "filters array nulled properly");
+ }
+
+ function test_evaluate(t) {
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT});
+ filter.filters.push(new OpenLayers.Filter());
+
+ var feature = new OpenLayers.Feature.Vector();
+
+ t.eq(filter.evaluate(feature.attributes), false,
+ "feature evaluates to false correctly.");
+ }
+
+ function test_evaluate_feature(t) {
+ t.plan(6);
+
+ var feature = new OpenLayers.Feature.Vector(null, {
+ pop: 200,
+ name: "foo"
+ });
+
+ var smallPop = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "pop",
+ value: 120
+ });
+
+ var bigPop = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN,
+ property: "pop",
+ value: 120
+ });
+
+ var namedFoo = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "name",
+ value: "foo"
+ });
+
+ var filter;
+
+ // test simple not
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT,
+ filters: [smallPop]
+ });
+ t.eq(filter.evaluate(feature), true, "not smallPop");
+
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT,
+ filters: [bigPop]
+ });
+ t.eq(filter.evaluate(feature), false, "not bigPop");
+
+ // test or
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR,
+ filters: [smallPop, namedFoo]
+ });
+ t.eq(filter.evaluate(feature), true, "smallPop or namedFoo");
+
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR,
+ filters: [bigPop, namedFoo]
+ });
+ t.eq(filter.evaluate(feature), true, "bigPop or namedFoo");
+
+ // test and
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [smallPop, namedFoo]
+ });
+ t.eq(filter.evaluate(feature), false, "smallPop and namedFoo");
+
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [bigPop, namedFoo]
+ });
+ t.eq(filter.evaluate(feature), true, "bigPop and namedFoo");
+
+ }
+
+ function test_clone(t) {
+
+ t.plan(2);
+
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop1",
+ value: "val1"
+ }),
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "prop2",
+ value: "val2"
+ })
+ ]
+ });
+
+ var clone = filter.clone();
+
+ // modify the original
+ filter.type = OpenLayers.Filter.Logical.OR;
+ filter.filters[0].value = "nada";
+
+ t.eq(clone.type, OpenLayers.Filter.Logical.AND, "clone has proper type");
+ t.eq(clone.filters[0].value, "val1", "clone has cloned child filters");
+
+ filter.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Filter/Spatial.html b/misc/openlayers/tests/Filter/Spatial.html
new file mode 100644
index 0000000..558f924
--- /dev/null
+++ b/misc/openlayers/tests/Filter/Spatial.html
@@ -0,0 +1,112 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var filter = new OpenLayers.Filter.Spatial(options);
+ t.ok(filter instanceof OpenLayers.Filter.Spatial,
+ "new OpenLayers.Filter.Spatial returns object" );
+ t.eq(filter.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof filter.evaluate, "function", "filter has an evaluate function");
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.Spatial();
+ filter.destroy();
+ t.eq(filter.symbolizer, null, "symbolizer hash nulled properly");
+ }
+
+ function test_evaluate(t) {
+ t.plan(8);
+
+ var filer, feature, res, geom, bounds;
+
+ bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: bounds
+ });
+
+ var not = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT,
+ filters: [filter]
+ });
+
+ feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(2, 2));
+ res = filter.evaluate(feature);
+ t.eq(res, true,
+ "evaluates returns correct value when feature intersects bounds");
+ t.eq(not.evaluate(feature), !res, "not bbox");
+
+ feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(20, 20));
+ res = filter.evaluate(feature);
+ t.eq(res, false,
+ "evaluates returns correct value when feature does not intersect bounds");
+ t.eq(not.evaluate(feature), !res, "not outside bbox");
+
+ geom = bounds.toGeometry();
+ feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(2, 2));
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ value: geom
+ });
+ res = filter.evaluate(feature);
+ t.eq(res, true,
+ "evaluates returns correct value when feature intersects bounds");
+ not.filters = [filter];
+ t.eq(not.evaluate(feature), !res, "not intersection");
+
+ geom = bounds.toGeometry();
+ feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(20, 20));
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ value: geom
+ });
+ not.filters = [filter];
+ res = filter.evaluate(feature);
+ t.eq(res, false,
+ "evaluates returns correct value when feature does not intersect bounds");
+ t.eq(not.evaluate(feature), !res, "not non-intersection");
+
+
+ }
+
+ function test_clone(t) {
+
+ t.plan(2);
+
+ var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: bounds
+ });
+
+ var clone = filter.clone();
+
+ // modify the original
+ filter.value.bottom = -100;
+
+ t.eq(clone.type, OpenLayers.Filter.Spatial.BBOX, "clone has proper type");
+ t.eq(clone.value.toBBOX(), "0,0,10,10", "clone has proper value");
+
+ filter.destroy();
+ clone.destroy();
+
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format.html b/misc/openlayers/tests/Format.html
new file mode 100644
index 0000000..66d2696
--- /dev/null
+++ b/misc/openlayers/tests/Format.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Format_constructor(t) {
+ t.plan(5);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format(options);
+ t.ok(format instanceof OpenLayers.Format,
+ "new OpenLayers.Format returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ t.eq(format.options, options, "format.options correctly set");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/ArcXML.html b/misc/openlayers/tests/Format/ArcXML.html
new file mode 100644
index 0000000..ea7d273
--- /dev/null
+++ b/misc/openlayers/tests/Format/ArcXML.html
@@ -0,0 +1,277 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var axl_image_response = '<?xml version="1.0" encoding="UTF-8"?><ARCXML version="1.1"><RESPONSE><IMAGE><ENVELOPE minx="-2471.42857142857" miny="0" maxx="105671.428571429" maxy="75700" /><OUTPUT url="http://localhost/output/364826560.png" /></IMAGE></RESPONSE></ARCXML>';
+ var axl_feature_response = '<?xml version="1.0" encoding="Cp1252"?><ARCXML version="1.1"><RESPONSE><FEATURES><FEATURE><FIELDS><FIELD name="UNIQUE_ID" value="514504b5-0458-461d-b540-8e18a454f619" /><FIELD name="LABEL" value="LIBRARY" /><FIELD name="Y_COORD" value="39.57" /><FIELD name="X_COORD" value="-104.24" /><FIELD name="#SHAPE#" value="[Geometry]" /><FIELD name="OBJECTID" value="1" /><FIELD name="shape.area" value="0" /><FIELD name="shape.len" value="0" /></FIELDS></FEATURE><FEATURE><FIELDS><FIELD name="UNIQUE_ID" value="514504b5-0458-461d-b540-8e81a454f619" /><FIELD name="LABEL" value="LIBRARY2" /><FIELD name="Y_COORD" value="39.75" /><FIELD name="X_COORD" value="-104.42" /><FIELD name="#SHAPE#" value="[Geometry]" /><FIELD name="OBJECTID" value="2" /><FIELD name="shape.area" value="0" /><FIELD name="shape.len" value="0" /></FIELDS></FEATURE><FEATURECOUNT count="2" hasmore="false" /><ENVELOPE minx="-678853.220047791" miny="1810.22081371862" maxx="-678853.220047791" maxy="1810.22081371862"/></FEATURES></RESPONSE></ARCXML>';
+
+ //
+ // creating a new arcxml format creates an object that has a read and write function
+ //
+ function test_Format_ArcXML_constructor1(t) {
+ t.plan(4);
+
+ var format = new OpenLayers.Format.ArcXML();
+ t.ok(format instanceof OpenLayers.Format.ArcXML,
+ "new OpenLayers.Format.ArcXML returns object" );
+
+ t.ok(format.request, null, "no options creates a null request");
+
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ //
+ // creating a new arcxml format with a set of options for an image request
+ // creates a request child object, and a get_image grandchild.
+ //
+ function test_Format_ArcXML_constructor2(t) {
+ t.plan(6);
+
+ var options = {
+ requesttype:'image',
+ envelope: new OpenLayers.Bounds( -180, -90, 180, 90 ).toArray(),
+ layers: [],
+ tileSize: new OpenLayers.Size( 256,256 ),
+ featureCoordSys: '4326',
+ filterCoordSys: '4326'
+ };
+
+ var format = new OpenLayers.Format.ArcXML( options );
+ t.ok(format instanceof OpenLayers.Format.ArcXML,
+ "new OpenLayers.Format.ArcXML returns object" );
+
+ t.ok(format.request instanceof OpenLayers.Format.ArcXML.Request,
+ "constructor with 'image' requesttype generates a request");
+ t.ok( format.request.get_image !== null, "get_image property exists" );
+ t.ok( format.request.get_feature === null, "get_feature property does not exists" );
+
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ //
+ // creating a new arcxml format with a set of options for a feature request
+ // creates a request child object, and a get_feature grandchild
+ //
+ function test_Format_ArcXML_constructor3(t) {
+ t.plan(6);
+
+ var options = {
+ requesttype:'feature'
+ };
+
+ var format = new OpenLayers.Format.ArcXML( options );
+ t.ok(format instanceof OpenLayers.Format.ArcXML,
+ "new OpenLayers.Format.ArcXML returns object" );
+
+ t.ok(format.request instanceof OpenLayers.Format.ArcXML.Request,
+ "constructor with 'feature' requesttype generates a request");
+ t.ok( format.request.get_feature !== null, "get_feature property exists" );
+ t.ok( format.request.get_image === null, "get_image property does not exists" );
+
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ //
+ // read in a known good axl image response
+ //
+ function test_Format_ArcXML_read1(t) {
+ t.plan(4);
+ var f = new OpenLayers.Format.ArcXML();
+ var response = f.read(axl_image_response);
+
+ t.ok(response !== null, "get_image response object is not null" );
+ t.ok(response.image !== null, "get_image image tag is not null");
+ t.ok(response.image.envelope !== null, "get_image image envelope tag is not null");
+ t.ok(response.image.output !== null, "get_image image output tag is not null");
+ }
+
+ //
+ // read in a known good axl feature response
+ //
+ function test_Format_ArcXML_read2(t) {
+ t.plan(10);
+ var f = new OpenLayers.Format.ArcXML();
+ var response = f.read(axl_feature_response);
+
+ t.ok(response !== null, "get_feature response object is not null" );
+ t.ok(response.features !== null, "get_feature features tag is not null");
+ t.ok(response.features.envelope !== null, "get_feature envelope tag is not null");
+ t.eq(response.features.featurecount, "2", "feature count is 2" );
+
+ // test the second feature parsed
+ // <FIELD name="UNIQUE_ID" value="514504b5-0458-461d-b540-8e81a454f619" />
+ // <FIELD name="LABEL" value="LIBRARY2" />
+ // <FIELD name="Y_COORD" value="39.75" />
+ // <FIELD name="X_COORD" value="-104.42" />
+ // <FIELD name="#SHAPE#" value="[Geometry]" />
+ // <FIELD name="OBJECTID" value="2" />
+ // <FIELD name="shape.area" value="0" />
+ // <FIELD name="shape.len" value="0" />
+ t.eq( response.features.feature[1].attributes['UNIQUE_ID'], "514504b5-0458-461d-b540-8e81a454f619", "field 1 for feature 2 is correct" );
+ t.eq( response.features.feature[1].attributes['LABEL'], "LIBRARY2", "field 2 for feature 2 is correct" );
+ t.eq( response.features.feature[1].attributes['Y_COORD'], "39.75", "field 3 for feature 2 is correct" );
+ t.eq( response.features.feature[1].attributes['X_COORD'], "-104.42", "field 4 for feature 2 is correct" );
+ t.eq( response.features.feature[1].attributes['#SHAPE#'], "[Geometry]", "field 5 for feature 2 is correct" );
+ t.eq( response.features.feature[1].attributes['OBJECTID'], "2", "field 6 for feature 2 is correct" );
+ }
+
+ //
+ // cause an error by parsing bad axl
+ //
+ function test_Format_ArcXML_parseerror(t) {
+ t.plan(1);
+ var f = new OpenLayers.Format.ArcXML();
+
+ try {
+ f.read( '<?xml version="1.0" encoding="Cp1252"?><ARCXML version="1.1"><NO END TAG>' );
+ t.fail("parsing failed to fail")
+ } catch (ex) {
+ t.ok( true, "Exception message indicates parsing error." );
+ }
+ }
+
+ //
+ // create an arcxml image request, and verify that it matches a known image request
+ //
+ function test_format_ArcXML_write1(t) {
+ var options = {
+ requesttype:'image',
+ envelope: new OpenLayers.Bounds( -180, -90, 180, 90 ).toArray(),
+ layers: [],
+ tileSize: new OpenLayers.Size( 256,256 ),
+ featureCoordSys: '4326',
+ filterCoordSys: '4326'
+ };
+ var truth = '<ARCXML version="1.1"><REQUEST><GET_IMAGE><PROPERTIES><FEATURECOORDSYS id="4326"/><FILTERCOORDSYS id="4326"/><ENVELOPE minx="-180" miny="-90" maxx="180" maxy="90"/><IMAGESIZE height="256" width="256"/></PROPERTIES></GET_IMAGE></REQUEST></ARCXML>';
+ axl_write(t,options,truth);
+ }
+
+ //
+ // create an arcxml image request that specifies layer visibilities, and
+ // verify that it matches a known image request
+ //
+ function test_format_ArcXML_write2(t) {
+ var options = {
+ requesttype:'image',
+ envelope: new OpenLayers.Bounds( -180, -90, 180, 90 ).toArray(),
+ layers: [{
+ id: "0",
+ visible: "true"
+ }],
+ tileSize: new OpenLayers.Size( 256,256 ),
+ featureCoordSys: '4326',
+ filterCoordSys: '4326'
+ };
+ var truth = '<ARCXML version="1.1"><REQUEST><GET_IMAGE><PROPERTIES><FEATURECOORDSYS id="4326"/><FILTERCOORDSYS id="4326"/><ENVELOPE minx="-180" miny="-90" maxx="180" maxy="90"/><IMAGESIZE height="256" width="256"/><LAYERLIST><LAYERDEF id="0" visible="true"/></LAYERLIST></PROPERTIES></GET_IMAGE></REQUEST></ARCXML>';
+ axl_write(t, options, truth );
+ }
+
+ //
+ // create an arcxml image request that performs a query for thematic mapping,
+ // and verify that it matches a known image request
+ //
+ function test_format_ArcXML_write3(t) {
+ var options = {
+ requesttype:'image',
+ envelope: new OpenLayers.Bounds( -180, -90, 180, 90 ).toArray(),
+ layers: [{
+ id: "0",
+ visible: "true",
+ query: {
+ where: "COMPANY='AVENCIA'"
+ }
+ }],
+ tileSize: new OpenLayers.Size( 256,256 ),
+ featureCoordSys: '4326',
+ filterCoordSys: '4326'
+ };
+ var truth = '<ARCXML version="1.1"><REQUEST><GET_IMAGE><PROPERTIES><FEATURECOORDSYS id="4326"/><FILTERCOORDSYS id="4326"/><ENVELOPE minx="-180" miny="-90" maxx="180" maxy="90"/><IMAGESIZE height="256" width="256"/><LAYERLIST><LAYERDEF id="0" visible="true"><QUERY where="COMPANY=\'AVENCIA\'"/></LAYERDEF></LAYERLIST></PROPERTIES></GET_IMAGE></REQUEST></ARCXML>';
+ axl_write(t, options, truth );
+ }
+
+ //
+ // create an arcxml image request that performs a spatial query for thematic mapping,
+ // and verify that it matches a known image request
+ //
+ function test_format_ArcXML_write4(t) {
+ var options = {
+ requesttype:'image',
+ envelope: new OpenLayers.Bounds( -180, -90, 180, 90 ).toArray(),
+ layers: [{
+ id: "0",
+ visible: "true",
+ query: {
+ spatialfilter: true,
+ where: "COMPANY='AVENCIA'"
+ }
+ }],
+ tileSize: new OpenLayers.Size( 256,256 ),
+ featureCoordSys: '4326',
+ filterCoordSys: '4326'
+ };
+ var truth = '<ARCXML version="1.1"><REQUEST><GET_IMAGE><PROPERTIES><FEATURECOORDSYS id="4326"/><FILTERCOORDSYS id="4326"/><ENVELOPE minx="-180" miny="-90" maxx="180" maxy="90"/><IMAGESIZE height="256" width="256"/><LAYERLIST><LAYERDEF id="0" visible="true"><SPATIALQUERY where="COMPANY=\'AVENCIA\'"/></LAYERDEF></LAYERLIST></PROPERTIES></GET_IMAGE></REQUEST></ARCXML>';
+ axl_write(t, options, truth );
+ }
+
+ //
+ // create an arcxml image request that performs a thematic map request, and
+ // verify that it matches a known image request.
+ //
+ function test_format_ArcXML_write5(t) {
+ var options = {
+ requesttype:'image',
+ envelope: new OpenLayers.Bounds( -180, -90, 180, 90 ).toArray(),
+ layers: [{
+ id: "0",
+ visible: "true",
+ query: {
+ spatialfilter: true,
+ where: "COMPANY='AVENCIA'"
+ },
+ renderer: {
+ type: 'valuemap',
+ lookupfield: 'lookup',
+ ranges: [{
+ lower: 0,
+ upper: 10,
+ symbol: {
+ type: 'simplepolygon',
+ fillcolor: '0,0,0'
+ }
+ },{
+ lower: 10,
+ upper: 20,
+ symbol: {
+ type: 'simplepolygon',
+ fillcolor: '255,255,255'
+ }
+ }]
+ }
+ }],
+ tileSize: new OpenLayers.Size( 256,256 ),
+ featureCoordSys: '4326',
+ filterCoordSys: '4326'
+ };
+ var truth = '<ARCXML version="1.1"><REQUEST><GET_IMAGE><PROPERTIES><FEATURECOORDSYS id="4326"/><FILTERCOORDSYS id="4326"/><ENVELOPE minx="-180" miny="-90" maxx="180" maxy="90"/><IMAGESIZE height="256" width="256"/><LAYERLIST><LAYERDEF id="0" visible="true"><SPATIALQUERY where="COMPANY=\'AVENCIA\'"/><VALUEMAPRENDERER lookupfield="lookup"><RANGE lower="0" upper="10"><SIMPLEPOLYGONSYMBOL fillcolor="0,0,0"/></RANGE><RANGE lower="10" upper="20"><SIMPLEPOLYGONSYMBOL fillcolor="255,255,255"/></RANGE></VALUEMAPRENDERER></LAYERDEF></LAYERLIST></PROPERTIES></GET_IMAGE></REQUEST></ARCXML>';
+ axl_write(t, options, truth );
+ }
+
+ //
+ // helper function to write some axl, and compare it against a truth axl string
+ //
+ function axl_write(t, options, truth) {
+ t.plan(1);
+
+ var f = new OpenLayers.Format.ArcXML( options );
+ var arcxml = f.write();
+ t.eq( arcxml, truth, "ArcXML request is correct.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/ArcXML/Features.html b/misc/openlayers/tests/Format/ArcXML/Features.html
new file mode 100644
index 0000000..bd2f680
--- /dev/null
+++ b/misc/openlayers/tests/Format/ArcXML/Features.html
@@ -0,0 +1,69 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var axl_feature_response = '<?xml version="1.0" encoding="Cp1252"?><ARCXML version="1.1"><RESPONSE><FEATURES><FEATURE><FIELDS><FIELD name="UNIQUE_ID" value="514504b5-0458-461d-b540-8e18a454f619" /><FIELD name="LABEL" value="LIBRARY" /><FIELD name="Y_COORD" value="39.57" /><FIELD name="X_COORD" value="-104.24" /><FIELD name="#SHAPE#" value="[Geometry]" /><FIELD name="OBJECTID" value="1" /><FIELD name="shape.area" value="0" /><FIELD name="shape.len" value="0" /></FIELDS></FEATURE><FEATURE><FIELDS><FIELD name="UNIQUE_ID" value="514504b5-0458-461d-b540-8e81a454f619" /><FIELD name="LABEL" value="LIBRARY2" /><FIELD name="Y_COORD" value="39.75" /><FIELD name="X_COORD" value="-104.42" /><FIELD name="#SHAPE#" value="[Geometry]" /><FIELD name="OBJECTID" value="2" /><FIELD name="shape.area" value="0" /><FIELD name="shape.len" value="0" /></FIELDS></FEATURE><FEATURECOUNT count="2" hasmore="false" /><ENVELOPE minx="-678853.220047791" miny="1810.22081371862" maxx="-678853.220047791" maxy="1810.22081371862"/></FEATURES></RESPONSE></ARCXML>';
+
+ //
+ // creating a new arcxml features format creates an object that has a read and write function
+ //
+ function test_initialize(t) {
+ t.plan(3);
+
+ var format = new OpenLayers.Format.ArcXML.Features();
+ t.ok(format instanceof OpenLayers.Format.ArcXML.Features,
+ "new OpenLayers.Format.ArcXML.Features returns object" );
+
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ //
+ // read in a known good axl feature response
+ //
+ function test_read1(t) {
+ t.plan(8);
+ var f = new OpenLayers.Format.ArcXML.Features();
+ var features = f.read(axl_feature_response);
+
+ t.ok(features !== null, "features are not null" );
+ t.eq(features.length, 2, "feature count is 2" );
+
+ // test the second feature parsed
+ // <FIELD name="UNIQUE_ID" value="514504b5-0458-461d-b540-8e81a454f619" />
+ // <FIELD name="LABEL" value="LIBRARY2" />
+ // <FIELD name="Y_COORD" value="39.75" />
+ // <FIELD name="X_COORD" value="-104.42" />
+ // <FIELD name="#SHAPE#" value="[Geometry]" />
+ // <FIELD name="OBJECTID" value="2" />
+ // <FIELD name="shape.area" value="0" />
+ // <FIELD name="shape.len" value="0" />
+ t.eq( features[1].attributes['UNIQUE_ID'], "514504b5-0458-461d-b540-8e81a454f619", "field 1 for feature 2 is correct" );
+ t.eq( features[1].attributes['LABEL'], "LIBRARY2", "field 2 for feature 2 is correct" );
+ t.eq( features[1].attributes['Y_COORD'], "39.75", "field 3 for feature 2 is correct" );
+ t.eq( features[1].attributes['X_COORD'], "-104.42", "field 4 for feature 2 is correct" );
+ t.eq( features[1].attributes['#SHAPE#'], "[Geometry]", "field 5 for feature 2 is correct" );
+ t.eq( features[1].attributes['OBJECTID'], "2", "field 6 for feature 2 is correct" );
+ }
+
+ //
+ // cause an error by parsing bad axl
+ //
+ function test_parseerror(t) {
+ t.plan(1);
+ var f = new OpenLayers.Format.ArcXML.Features();
+
+ try {
+ f.read( '<?xml version="1.0" encoding="Cp1252"?><ARCXML version="1.1"><NO END TAG>' );
+ t.fail("reading didn't fail");
+ } catch (ex) {
+ t.eq( ex.message, "Error parsing the ArcXML request", "Exception message indicates parsing error." );
+ }
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/Atom.html b/misc/openlayers/tests/Format/Atom.html
new file mode 100644
index 0000000..71bccc5
--- /dev/null
+++ b/misc/openlayers/tests/Format/Atom.html
@@ -0,0 +1,450 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(4);
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.Atom(options);
+ t.ok(format instanceof OpenLayers.Format.Atom,
+ "new OpenLayers.Format.GeoRSS returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ /* Reading tests */
+
+ function test_reproject_null(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.Atom({'internalProjection':new OpenLayers.Projection("EPSG:4326"), 'externalProjection': new OpenLayers.Projection("EPSG:4326")});
+ var data = parser.read(
+ // begin document
+ '<feed xmlns="http://www.w3.org/2005/Atom">' +
+ '<entry></entry>' +
+ '</feed>'
+ // end document
+ );
+ t.eq(
+ data.length, 1,
+ "Parsing items with null geometry and reprojection doesn't fail"
+ );
+ }
+
+ // read entry 1: basic entry, no categories or persons
+ function test_readentry1(t) {
+ t.plan(10);
+ var parser = new OpenLayers.Format.Atom();
+ var data = parser.read(
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ ' <id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ ' <link href="http://example.com/blog/1" rel="alternate"/>' +
+ ' <summary>An Atom testing entry</summary>' +
+ ' <title>Atom test</title>' +
+ ' <updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>'
+ // end document
+ );
+ t.ok(data instanceof Array, "Read features");
+ var fx = data[0];
+ t.ok(fx instanceof OpenLayers.Feature.Vector, "Read feature");
+ t.eq(fx.geometry, null, "Geometry is null");
+ t.eq(
+ fx.fid,
+ "urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed",
+ "Read fid"
+ );
+ var attrib = fx.attributes;
+ t.eq(attrib.title, "Atom test", "Correct title attribute");
+ t.eq(
+ attrib.description,
+ "An Atom testing entry",
+ "Correct description attribute"
+ );
+ var atomAttrib = attrib.atom;
+ t.eq(
+ atomAttrib.links,
+ [{href: "http://example.com/blog/1", rel: "alternate"}],
+ "Correct links in atom namespace"
+ );
+ t.eq(
+ atomAttrib.summary,
+ "An Atom testing entry",
+ "Correct summary in atom namespace"
+ );
+ t.eq(
+ atomAttrib.title,
+ "Atom test",
+ "Correct title in atom namespace"
+ );
+ t.eq(
+ atomAttrib.updated,
+ "2009-06-02T10:00:00Z",
+ "Correct timestamp in atom namespace"
+ );
+ }
+
+ // read entry 2: with georss:where
+ function test_readentry2(t) {
+ t.plan(5);
+ var parser = new OpenLayers.Format.Atom();
+ var data = parser.read(
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ ' <id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ ' <georss:where xmlns:georss="http://www.georss.org/georss">' +
+ ' <gml:Point xmlns:gml="http://www.opengis.net/gml">' +
+ ' <gml:pos>45.68 -111.04</gml:pos>' +
+ ' </gml:Point>' +
+ ' </georss:where>' +
+ '</entry>'
+ // end document
+ );
+ t.ok(data instanceof Array, "Read features");
+ var fx = data[0];
+ t.ok(fx instanceof OpenLayers.Feature.Vector, "Read feature");
+ t.ok(fx.geometry instanceof OpenLayers.Geometry.Point, "Read geometry");
+ t.eq(fx.geometry.x, -111.04, "Read x");
+ t.eq(fx.geometry.y, 45.68, "Read y");
+ }
+
+ // read entry 3: with georss:point
+ function test_readentry3(t) {
+ t.plan(5);
+ var parser = new OpenLayers.Format.Atom();
+ var data = parser.read(
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ ' <id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ ' <georss:point xmlns:georss="http://www.georss.org/georss">45.68 -111.04</georss:point>' +
+ '</entry>'
+ // end document
+ );
+ t.ok(data instanceof Array, "Read features");
+ var fx = data[0];
+ t.ok(fx instanceof OpenLayers.Feature.Vector, "Read feature");
+ t.ok(fx.geometry instanceof OpenLayers.Geometry.Point, "Read geometry");
+ t.eq(fx.geometry.x, -111.04, "Read x");
+ t.eq(fx.geometry.y, 45.68, "Read y");
+ }
+
+ // read entry 4: basic entry, text content
+ function test_readentry4(t) {
+ t.plan(3);
+ var parser = new OpenLayers.Format.Atom();
+ var data = parser.read(
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ ' <id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ ' <link href="http://example.com/blog/1" rel="alternate"/>' +
+ ' <summary>An Atom testing entry</summary>' +
+ ' <title>Atom test</title>' +
+ ' <updated>2009-06-02T10:00:00Z</updated>' +
+ ' <content type="text">Blah, blah, blah</content>' +
+ '</entry>'
+ // end document
+ );
+ t.ok(data instanceof Array, "Read features");
+ var fx = data[0];
+ var attrib = fx.attributes;
+ var atomAttrib = attrib.atom;
+ t.eq(
+ atomAttrib.content.type,
+ "text",
+ "Correct content.type in atom namespace"
+ );
+ t.eq(
+ atomAttrib.content.value,
+ "Blah, blah, blah",
+ "Correct content.value in atom namespace"
+ );
+ }
+
+ // read entry 5: basic entry, KML content
+ function test_readentry5(t) {
+ t.plan(3);
+ var parser = new OpenLayers.Format.Atom();
+ var data = parser.read(
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ ' <id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ ' <link href="http://example.com/blog/1" rel="alternate"/>' +
+ ' <summary>An Atom testing entry</summary>' +
+ ' <title>Atom test</title>' +
+ ' <updated>2009-06-02T10:00:00Z</updated>' +
+ ' <content type="application/vnd.google-earth.kml+xml"><kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>A folder</name><description>It\'s a folder</description></Folder></kml></content>' +
+ '</entry>'
+ // end document
+ );
+ t.ok(data instanceof Array, "Read features");
+ var fx = data[0];
+ var attrib = fx.attributes;
+ var atomAttrib = attrib.atom;
+ t.eq(
+ atomAttrib.content.type,
+ "application/vnd.google-earth.kml+xml",
+ "Correct content.type in atom namespace"
+ );
+ var node = atomAttrib.content.value;
+ var name = node.localName || node.nodeName.split(":").pop();
+ t.eq(
+ name,
+ "kml",
+ "Correct content.value in atom namespace"
+ );
+ }
+
+ // read feed 1
+ function test_readfeed1(t) {
+ t.plan(2);
+ var parser = new OpenLayers.Format.Atom();
+ var data = parser.read(
+ // begin document
+ '<feed xmlns="http://www.w3.org/2005/Atom">' +
+ ' <entry>' +
+ ' <id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ ' </entry>' +
+ '</feed>'
+ // end document
+ );
+ t.ok(data instanceof Array, "Read features");
+ var fx = data[0];
+ t.ok(fx instanceof OpenLayers.Feature.Vector, "Read feature");
+ }
+
+ /* Writing tests */
+
+ // write entry 1: null geometry, no attributes
+ function test_writeentry1(t) {
+ t.plan(1);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<id>1</id>' +
+ '<title>untitled</title>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with id, no attributes'
+ );
+ }
+
+ // write entry 2: null geometry, well-known attributes
+ function test_writeentry2(t) {
+ t.plan(1);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {title: "Test", description: "A testing feature"});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<id>1</id>' +
+ '<summary>A testing feature</summary>' +
+ '<title>Test</title>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with id, well-known attributes'
+ );
+ }
+
+ // write entry 3: null geometry, Atom constructs to override
+ // well-known attributes
+ function test_writeentry3(t) {
+ t.plan(1);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {title: "Test", description: "A testing feature", atom: {title: "Atom test", summary: "An Atom testing feature", updated: "2009-06-02T10:00:00Z"}});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<id>1</id>' +
+ '<summary>An Atom testing feature</summary>' +
+ '<title>Atom test</title>' +
+ '<updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with Atom constructs overriding well-known atts'
+ );
+ }
+
+ // write entry 4: Atom categories
+ function test_writeentry4(t) {
+ t.plan(1);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {title: "Test", description: "A testing feature", atom: {title: "Atom test", summary: "An Atom testing feature", updated: "2009-06-02T10:00:00Z", categories: [{term: "blog", scheme: "http://example.com/terms", label: "A blog post"}]}});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<category term="blog" scheme="http://example.com/terms" label="A blog post"/>' +
+ '<id>1</id>' +
+ '<summary>An Atom testing feature</summary>' +
+ '<title>Atom test</title>' +
+ '<updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with Atom constructs and categories'
+ );
+ }
+
+ // write entry 5: Atom authors, contributors
+ function test_writeentry5(t) {
+ t.plan(1);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {title: "Test", description: "A testing feature", atom: {title: "Atom test", summary: "An Atom testing feature", updated: "2009-06-02T10:00:00Z", authors: [{name: "John Doe", uri: "http://example.com/people/jdoe", email: "jdoe@example.com"}], contributors: [{name: "Pikov Andropov", uri: "http://example.com/people/pandropov", email: "pandropov@example.com"}]}});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<author>' +
+ ' <name>John Doe</name>' +
+ ' <uri>http://example.com/people/jdoe</uri>' +
+ ' <email>jdoe@example.com</email>' +
+ '</author>' +
+ '<contributor>' +
+ ' <name>Pikov Andropov</name>' +
+ ' <uri>http://example.com/people/pandropov</uri>' +
+ ' <email>pandropov@example.com</email>' +
+ '</contributor>' +
+ '<id>1</id>' +
+ '<summary>An Atom testing feature</summary>' +
+ '<title>Atom test</title>' +
+ '<updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with Atom constructs and persons'
+ );
+ }
+
+ // write entry 6: Atom links
+ function test_writeentry6(t) {
+ t.plan(1);
+
+ // Feature attributes in Atom namespace
+ var atomAttrib = {
+ title: "Atom test",
+ summary: "An Atom testing feature",
+ updated: "2009-06-02T10:00:00Z",
+ links: [
+ { href: "http://example.com/blog/1", rel: "alternate" }
+ ]
+ };
+ var fx = new OpenLayers.Feature.Vector(null, {atom: atomAttrib});
+ fx.fid = 'urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed';
+
+ var writer = new OpenLayers.Format.Atom();
+ var data = writer.write(fx);
+
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ '<link href="http://example.com/blog/1" rel="alternate"/>' +
+ '<summary>An Atom testing feature</summary>' +
+ '<title>Atom test</title>' +
+ '<updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with Atom constructs and links'
+ );
+ }
+
+ // write out point -- just enough to see that we're getting the
+ // georss:where element with a Point. We'll trust GML.v3 to get the
+ // details right.
+ function test_writepoint(t) {
+ t.plan(1);
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var fx = new OpenLayers.Feature.Vector(point, {});
+ fx.fid = 'urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed';
+
+ var writer = new OpenLayers.Format.Atom();
+ var data = writer.write(fx);
+
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<id>urn:uuid:82ede847-b31a-4e3d-b773-7471bad154ed</id>' +
+ '<title>untitled</title>' +
+ '<georss:where xmlns:georss="http://www.georss.org/georss">' +
+ ' <gml:Point xmlns:gml="http://www.opengis.net/gml">' +
+ ' <gml:pos>45.68 -111.04</gml:pos>' +
+ ' </gml:Point>' +
+ '</georss:where>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with a point location'
+ );
+ }
+
+ // write entry 7: text type content
+ function test_writeentry7(t) {
+ t.plan(1);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {title: "Test", description: "A testing feature", atom: {title: "Atom test", summary: "An Atom testing feature", updated: "2009-06-02T10:00:00Z", content: {type: "text", value: "Blah, blah, blah"}}});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<content type="text">Blah, blah, blah</content>' +
+ '<id>1</id>' +
+ '<summary>An Atom testing feature</summary>' +
+ '<title>Atom test</title>' +
+ '<updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with Atom constructs overriding well-known atts'
+ );
+ }
+
+ // write entry 8: +xml type content
+ function test_writeentry8(t) {
+ t.plan(1);
+ var kml = new OpenLayers.Format.KML();
+ kml.foldersName = "A folder";
+ kml.foldersDesc = "It's a folder";
+ var kmlDoc = kml.createElementNS(kml.kmlns, "kml");
+ var kmlFolder = kml.createFolderXML();
+ kmlDoc.appendChild(kmlFolder);
+ var writer = new OpenLayers.Format.Atom();
+ var feature = new OpenLayers.Feature.Vector(null, {title: "Test", description: "A testing feature", atom: {title: "Atom test", summary: "An Atom testing feature", updated: "2009-06-02T10:00:00Z", content: {type: "application/vnd.google-earth.kml+xml", value: kmlDoc}}});
+ feature.fid = '1';
+ var data = writer.write(feature);
+ t.xml_eq(
+ data,
+ // begin document
+ '<entry xmlns="http://www.w3.org/2005/Atom">' +
+ '<content type="application/vnd.google-earth.kml+xml"><kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>A folder</name><description>It\'s a folder</description></Folder></kml></content>' +
+ '<id>1</id>' +
+ '<summary>An Atom testing feature</summary>' +
+ '<title>Atom test</title>' +
+ '<updated>2009-06-02T10:00:00Z</updated>' +
+ '</entry>',
+ // end document
+ 'Writes an entry doc with Atom constructs overriding well-known atts'
+ );
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/CQL.html b/misc/openlayers/tests/Format/CQL.html
new file mode 100644
index 0000000..7b31eab
--- /dev/null
+++ b/misc/openlayers/tests/Format/CQL.html
@@ -0,0 +1,364 @@
+<html>
+ <head>
+ <script src="../OLLoader.js"></script>
+
+ <script type="text/javascript">
+
+function test_CQL_Constructor(t) {
+ t.plan(5);
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.CQL(options);
+ t.ok(format instanceof OpenLayers.Format.CQL,
+ "new OpenLayers.Format.CQL object");
+ t.eq(format.foo, "bar", "constructor sets options correctly")
+ t.eq(typeof format.read, 'function', 'format has a read function');
+ t.eq(typeof format.write, 'function', 'format has a write function');
+ t.eq(format.options, options, "format.options correctly set");
+}
+
+function test_Comparison_string(t) {
+ t.plan(5);
+ var test_cql, format, filter;
+ test_cql = "X >= 'B'";
+ format = new OpenLayers.Format.CQL();
+ filter = format.read(test_cql);
+ t.ok(filter instanceof OpenLayers.Filter.Comparison,
+ "Parsing a simple >= filter produces a Filter.Comparison");
+ t.eq(filter.type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+ ">= parsed as Filter.Comparison.GREATER_THAN_OR_EQUAL_TO");
+ t.eq(filter.property, 'X',
+ "Property extracted from CQL text");
+ t.eq(filter.value, 'B',
+ "Value extracted from CQL text");
+
+
+ t.eq(format.write(filter), test_cql, "write returned test cql");
+}
+
+function test_read_whitespace(t) {
+ t.plan(4);
+ var cql = "TYPEDESC = 'BOE Numbered Plans'";
+ var format = new OpenLayers.Format.CQL();
+ var filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Comparison, "filter parsed correctly with whitespace in string");
+ t.eq(filter.property, 'TYPEDESC', "filter property parsed correctly");
+ t.eq(filter.value, 'BOE Numbered Plans', "value parsed correctly");
+ t.eq(filter.type, '==', 'filter type parsed correctly');
+}
+
+function test_read_escaped_quotes(t) {
+ t.plan(14);
+ var cql = "PROP = 'don''t worry' or PROP = 'value''s value' or PROP = 'foo'";
+ var format = new OpenLayers.Format.CQL();
+
+ var filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Logical, "filter type");
+ t.eq(filter.filters.length, 2, "filter children");
+
+ var f0 = filter.filters[0];
+ t.ok(f0 instanceof OpenLayers.Filter.Logical, "f0 type");
+ t.eq(f0.filters.length, 2, "f0 children");
+
+ var f00 = f0.filters[0];
+ t.eq(f00.property, "PROP", "f000 property");
+ t.eq(f00.type, "==", "f000 type");
+ t.eq(f00.value, "don't worry", "f000 value");
+
+ var f01 = f0.filters[1];
+ t.eq(f01.property, "PROP", "f001 property");
+ t.eq(f01.type, "==", "f001 type");
+ t.eq(f01.value, "value's value", "f001 value");
+
+ var f1 = filter.filters[1];
+ t.ok(f1 instanceof OpenLayers.Filter.Comparison, "f1 type");
+ t.eq(f1.property, "PROP", "f1 property");
+ t.eq(f1.type, "==", "f1 type");
+ t.eq(f1.value, "foo", "f1 value");
+}
+
+function test_write_escaped_quotes(t) {
+ t.plan(1);
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "PROP",
+ value: "quot'd string"
+ }),
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "PROP",
+ value: "don't quote's"
+ })
+ ]
+ });
+ var format = new OpenLayers.Format.CQL();
+ var cql = format.write(filter);
+ t.eq(cql, "(PROP = 'quot''d string') OR (PROP = 'don''t quote''s')", "escaped");
+}
+
+function test_Comparison_number(t) {
+ t.plan(5);
+ var test_cql, format, filter;
+ test_cql = "X >= 10";
+ format = new OpenLayers.Format.CQL();
+ filter = format.read(test_cql);
+ t.ok(filter instanceof OpenLayers.Filter.Comparison,
+ "Parsing a simple >= filter produces a Filter.Comparison");
+ t.eq(filter.type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+ ">= parsed as Filter.Comparison.GREATER_THAN_OR_EQUAL_TO");
+ t.eq(filter.property, 'X',
+ "Property extracted from CQL text");
+ t.eq(filter.value, 10,
+ "Value extracted from CQL text");
+
+
+ t.eq(format.write(filter), test_cql, "write returned test cql");
+}
+
+function test_Logical(t) {
+ t.plan(7);
+ var test_cql, format, filter;
+ test_cql = "X >= 'B' AND X < 'M'";
+ format = new OpenLayers.Format.CQL();
+ filter = format.read(test_cql);
+ t.ok(filter instanceof OpenLayers.Filter.Logical,
+ "Parsing ANDed filters produces a Filter.Logical");
+ t.eq(filter.type, OpenLayers.Filter.Logical.AND,
+ "AND parsed as Filter.Logical.AND");
+ t.eq(filter.filters.length, 2,
+ "AND Filter contains two subfilters");
+ t.ok(filter.filters[0] instanceof OpenLayers.Filter.Comparison,
+ "First sub-filter is a Filter.Comparison");
+ t.eq(filter.filters[0].type, OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+ "First sub-filter is the first filter in the CQL text");
+ t.ok(filter.filters[1] instanceof OpenLayers.Filter.Comparison,
+ "Second sub-filter is a Filter.Comparison");
+ t.eq(filter.filters[1].type, OpenLayers.Filter.Comparison.LESS_THAN,
+ "Second sub-filter is the second filter in the CQL text");
+
+}
+
+function test_Logical_write(t) {
+ t.plan(1);
+ var cql = "(X >= 'B') AND (X < 'M')";
+ var format = new OpenLayers.Format.CQL();
+ var filter = format.read(cql);
+ t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_Logical_spatial(t) {
+ t.plan(9);
+ var test_cql, format, filter;
+ test_cql = "INTERSECTS(the_geom, POLYGON((-111 41,-115 41,-115 45,-110 45,-111 41))) AND CONTAINS(the_geom, POINT(-111 41))";
+ format = new OpenLayers.Format.CQL();
+ filter = format.read(test_cql);
+ t.ok(filter instanceof OpenLayers.Filter.Logical,
+ "Parsing ANDed filters produces a Filter.Logical");
+ t.eq(filter.type, OpenLayers.Filter.Logical.AND,
+ "AND parsed as Filter.Logical.AND");
+ t.eq(filter.filters.length, 2,
+ "AND Filter contains two subfilters");
+ t.ok(filter.filters[0] instanceof OpenLayers.Filter.Spatial,
+ "First sub-filter is a Filter.Spatial");
+ t.eq(filter.filters[0].type, OpenLayers.Filter.Spatial.INTERSECTS,
+ "First sub-filter is the first filter in the CQL text");
+ t.geom_eq(filter.filters[0].value, OpenLayers.Geometry.fromWKT("POLYGON((-111 41,-115 41,-115 45,-110 45,-111 41))"),
+ "First sub-filter is has correct geometry");
+ t.ok(filter.filters[1] instanceof OpenLayers.Filter.Spatial,
+ "Second sub-filter is a Filter.Comparison");
+ t.eq(filter.filters[1].type, OpenLayers.Filter.Spatial.CONTAINS,
+ "Second sub-filter is the second filter in the CQL text");
+ t.geom_eq(filter.filters[1].value, OpenLayers.Geometry.fromWKT("POINT(-111 41)"),
+ "Second sub-filter is has correct geometry");
+}
+
+function test_Logical_spatial_write(t) {
+ // TODO: remove this if extra parentheses are avoided by checking logical operator precedence
+ t.plan(1);
+ var cql = "(INTERSECTS(the_geom, POLYGON((-111 41,-115 41,-115 45,-110 45,-111 41)))) AND (CONTAINS(the_geom, POINT(-111 41)))";
+ var format = new OpenLayers.Format.CQL();
+ var filter = format.read(cql);
+ t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_Parentheticals(t) {
+ t.plan(2);
+ var format, cqlA, filterA, cqlB, filterB;
+ format = new OpenLayers.Format.CQL();
+ cqlA = "A = '1' AND B = '2' OR C = '3'";
+ cqlB = "A = '1' AND (B = '2' OR C = '3')";
+ filterA = format.read(cqlA);
+ filterB = format.read(cqlB);
+
+ t.ok(filterA instanceof OpenLayers.Filter.Logical &&
+ filterA.filters[0] instanceof OpenLayers.Filter.Logical &&
+ filterA.filters[1] instanceof OpenLayers.Filter.Comparison,
+ "Unparenthesized expression groups left to right");
+ t.ok(filterB instanceof OpenLayers.Filter.Logical &&
+ filterB.filters[0] instanceof OpenLayers.Filter.Comparison &&
+ filterB.filters[1] instanceof OpenLayers.Filter.Logical,
+ "Parenthesized expression groups as specified by parentheses");
+}
+
+function test_Parentheticals_write(t) {
+ // TODO: remove this if extra parentheses are avoided by checking logical operator precedence
+ t.plan(1);
+ var format = new OpenLayers.Format.CQL();
+ var cql = "(A = '1') AND ((B = '2') OR (C = '3'))";
+ var filter = format.read(cql);
+ t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_BBOX(t) {
+ t.plan(5);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "BBOX(the_geom,1,2,3,4)",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Spatial,
+ "Parsing BBOX expression produces Filter.Spatial");
+ t.eq(filter.type, OpenLayers.Filter.Spatial.BBOX,
+ "Spatial filter is a bbox filter");
+ t.eq(filter.property, "the_geom",
+ "Property name is as specified in CQL");
+ t.eq(filter.value.toBBOX(), "1,2,3,4",
+ "Value is as specified in CQL");
+
+ t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_INTERSECTS(t) {
+ t.plan(5);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "INTERSECTS(the_geom, POINT(1 2))",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Spatial,
+ "Parsing BBOX expression produces Filter.Spatial");
+ t.eq(filter.type, OpenLayers.Filter.Spatial.INTERSECTS,
+ "Spatial filter is an intersects filter");
+ t.eq(filter.property, "the_geom",
+ "Property name is as specified in CQL");
+ t.ok(filter.value instanceof OpenLayers.Geometry,
+ "Value is a geometry");
+
+ t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_WITHIN(t) {
+ t.plan(5);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "WITHIN(the_geom, POLYGON((1 2,3 4,5 6,3 8,1 6,1 2)))",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Spatial,
+ "Parsing BBOX expression produces Filter.Spatial");
+ t.eq(filter.type, OpenLayers.Filter.Spatial.WITHIN,
+ "Spatial filter is a within filter");
+ t.eq(filter.property, "the_geom",
+ "Property name is as specified in CQL");
+ t.ok(filter.value instanceof OpenLayers.Geometry,
+ "Value is a geometry");
+
+ t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_DWITHIN(t) {
+ t.plan(6);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "DWITHIN(the_geom, POINT(1 2), 6)",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Spatial,
+ "Parsing DWITHIN expression produces Filter.Spatial");
+ t.eq(filter.type, OpenLayers.Filter.Spatial.DWITHIN,
+ "Spatial filter is a DWITHIN filter");
+ t.eq(filter.property, "the_geom",
+ "Property name is as specified in CQL");
+ t.ok(filter.value instanceof OpenLayers.Geometry,
+ "Value is a geometry");
+ t.eq(filter.distance, 6,
+ "Distance is as specified in CQL");
+
+ t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_CONTAINS(t) {
+ t.plan(5);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "CONTAINS(the_geom, POINT(1 2))",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Spatial,
+ "Parsing BBOX expression produces Filter.Spatial");
+ t.eq(filter.type, OpenLayers.Filter.Spatial.CONTAINS,
+ "Spatial filter is a within filter");
+ t.eq(filter.property, "the_geom",
+ "Property name is as specified in CQL");
+ t.ok(filter.value instanceof OpenLayers.Geometry,
+ "Value is a geometry");
+
+ t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_NOT(t) {
+ t.plan(4);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "NOT X < 12",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Logical,
+ "Parsing NOT expression produces Logical.Not");
+ t.eq(filter.type, OpenLayers.Filter.Logical.NOT,
+ "Logical filter is a NOT filter");
+ t.eq(filter.filters[0].property, "X",
+ "Property name is as specified in CQL");
+ t.eq(filter.filters[0].value, 12, "Value is as specified in CQL");
+}
+
+function test_NOT_write(t) {
+ // TODO: remove this if extra parentheses are avoided by checking logical operator precedence
+ t.plan(1);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "NOT (X < 12)",
+ filter = format.read(cql);
+ t.eq(format.write(filter), cql, "write returned test cql");
+}
+
+function test_BETWEEN(t) {
+ t.plan(6);
+ var format = new OpenLayers.Format.CQL(),
+ cql = "A BETWEEN 0 AND 5",
+ filter = format.read(cql);
+ t.ok(filter instanceof OpenLayers.Filter.Comparison,
+ "Parsing BETWEEN expression produces Filter.Comparison");
+ t.eq(filter.type, OpenLayers.Filter.Comparison.BETWEEN,
+ "Comparison filter is a between filter");
+ t.eq(filter.property, "A",
+ "Property name is as specified in CQL");
+ t.eq(filter.lowerBoundary, 0, 'Lower boundary is as specified in CQL');
+ t.eq(filter.upperBoundary, 5, 'Upper bondary is as specified in CQL');
+
+ t.eq(format.write(filter), cql, "write returned test cql");
+
+}
+
+function test_NULL(t) {
+ t.plan(3);
+ var filter = new OpenLayers.Filter.Comparison({
+ property: "GEOM",
+ type: "NULL"
+ });
+ var format = new OpenLayers.Format.CQL();
+ var str = 'GEOM IS NULL';
+ t.eq(format.write(filter), str, "NULL filter written correctly");
+ filter = format.read(str);
+ t.eq(filter.type, OpenLayers.Filter.Comparison.IS_NULL, "filter type is correctly parsed");
+ t.eq(filter.property, "GEOM", "filter property is correctly parsed");
+}
+
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/misc/openlayers/tests/Format/CSWGetDomain.html b/misc/openlayers/tests/Format/CSWGetDomain.html
new file mode 100644
index 0000000..1e37826
--- /dev/null
+++ b/misc/openlayers/tests/Format/CSWGetDomain.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.CSWGetDomain();
+ t.ok(format instanceof OpenLayers.Format.CSWGetDomain.v2_0_2, "constructor returns instance with default versioned format");
+
+ format = new OpenLayers.Format.CSWGetDomain({
+ version: "2.0.2"
+ });
+ t.ok(format instanceof OpenLayers.Format.CSWGetDomain.v2_0_2, "constructor returns instance with custom versioned format");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.html b/misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.html
new file mode 100644
index 0000000..ea0be83
--- /dev/null
+++ b/misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.html
@@ -0,0 +1,56 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="v2_0_2.js"></script>
+ <script type="text/javascript">
+
+ var format = new OpenLayers.Format.CSWGetDomain();
+
+ function test_write(t) {
+
+ t.plan(1);
+
+ var options = {
+ PropertyName: "type"
+ };
+
+ var result = format.write(options);
+
+ t.eq(result, csw_request, "check value returned by format " +
+ "CSWGetDomain: write method");
+
+ }
+
+
+ function test_read(t) {
+
+ t.plan(9);
+
+ var obj = format.read(csw_response);
+
+ var domainValues = obj.DomainValues;
+ // test getRecordsResponse object
+ t.ok(domainValues, "object contains DomainValues property");
+
+ // test DomainValues
+ t.eq(domainValues.length, 1, "object contains 1 object in DomainValues");
+ var domainValue = domainValues[0];
+ t.eq(domainValue.type, "csw:Record", "check value for attribute type");
+ t.eq(domainValue.PropertyName, "type", "check value for element PropertyName");
+ t.ok(domainValue.ListOfValues, "object contains ListOfValues property");
+
+ // test ListOfValues
+ t.eq(domainValue.ListOfValues.length, 2, "object contains 2 objects " +
+ "in ListOfValues");
+ var val = domainValue.ListOfValues[0];
+ t.ok(val.Value, "object contains Value property");
+ t.eq(val.Value.my_attr, "my_value", "check value for attribute my_attr");
+ t.eq(val.Value.value, "dataset", "check value for element Value");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.js b/misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.js
new file mode 100644
index 0000000..19e1ad0
--- /dev/null
+++ b/misc/openlayers/tests/Format/CSWGetDomain/v2_0_2.js
@@ -0,0 +1,18 @@
+var csw_request =
+'<csw:GetDomain xmlns:csw="http://www.opengis.net/cat/csw/2.0.2" service="CSW" version="2.0.2">' +
+ '<csw:PropertyName>type</csw:PropertyName>' +
+'</csw:GetDomain>';
+
+var csw_response =
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<csw:GetDomainResponse xmlns:csw="http://www.opengis.net/cat/csw/2.0.2">' +
+ '<csw:DomainValues type="csw:Record">' +
+ '<csw:PropertyName>type</csw:PropertyName>' +
+ '<csw:ListOfValues>' +
+ '<csw:Value my_attr="my_value">dataset</csw:Value>' +
+ '<csw:Value>service</csw:Value>' +
+ '</csw:ListOfValues>' +
+ '</csw:DomainValues>' +
+'</csw:GetDomainResponse>'
+;
+
diff --git a/misc/openlayers/tests/Format/CSWGetRecords.html b/misc/openlayers/tests/Format/CSWGetRecords.html
new file mode 100644
index 0000000..2b8bc6b
--- /dev/null
+++ b/misc/openlayers/tests/Format/CSWGetRecords.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.CSWGetRecords();
+ t.ok(format instanceof OpenLayers.Format.CSWGetRecords.v2_0_2, "constructor returns instance with default versioned format");
+
+ format = new OpenLayers.Format.CSWGetRecords({
+ version: "2.0.2"
+ });
+ t.ok(format instanceof OpenLayers.Format.CSWGetRecords.v2_0_2, "constructor returns instance with custom versioned format");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.html b/misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.html
new file mode 100644
index 0000000..07e1b96
--- /dev/null
+++ b/misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.html
@@ -0,0 +1,88 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="v2_0_2.js"></script>
+ <script type="text/javascript">
+
+ var format = new OpenLayers.Format.CSWGetRecords();
+
+ function test_write(t) {
+
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "my_prop",
+ value: "my_prop_value"
+ });
+
+ var options = {
+ "resultType": "results",
+ "startPosition": "10",
+ "maxRecords": "20",
+ "Query": {
+ "ElementSetName": {
+ "value": "brief"
+ },
+ "Constraint": {
+ "version": "1.1.0",
+ "Filter": filter
+ }
+ }
+ };
+
+ var result = format.write(options);
+
+ t.eq(result, csw_request, "check value returned by format " +
+ "CSWGetRecords: write method");
+
+ }
+
+
+ function test_read(t) {
+
+ t.plan(17);
+
+ var obj = format.read(csw_response);
+
+ var searchStatus = obj.SearchStatus;
+ var searchResults = obj.SearchResults;
+ var records = obj.records;
+ // test getRecordsResponse object
+ t.ok(searchStatus, "object contains SearchStatus property");
+ t.ok(searchResults, "object contains SearchResults property");
+ t.ok(records, "object contains records property");
+
+ // test SearchResults attributes
+ t.eq(searchResults.numberOfRecordsMatched, 10, "check value for SearchResults.numberOfRecordsMatched");
+ t.eq(searchResults.numberOfRecordsReturned, 2, "check value for SearchResults.numberOfRecordsReturned");
+ t.eq(searchResults.elementSet, "brief", "check value for SearchResults.elementSet");
+ t.eq(searchResults.nextRecord, 3, "check value for SearchResults.nextRecord");
+
+ // test records
+ t.eq(records.length, 2, "object contains 10 records");
+ var testRecord = records[0];
+ t.eq(testRecord.type, "BriefRecord", "check value for record.type");
+ t.eq(testRecord.title, [{value:"Sample title"}], "check value for record.title");
+
+ // test empty subject
+ t.eq(testRecord.subject, [], "Empty subject tags are ignored");
+
+ //test bbox
+ t.eq(testRecord.BoundingBox.length, 2, "object contains 2 BoundingBoxes");
+ var bbox = testRecord.BoundingBox[0];
+ t.ok(bbox, "object contains BoundingBox properties");
+ t.eq(bbox.crs, "::Lambert Azimuthal Projection", "check value for BoundingBox.crs");
+ t.eq(bbox.value, [156, -3, 37, 83], "check value for record.BoundingBox");
+
+ // test gninfo
+ testRecord = records[1];
+ t.ok(testRecord.gninfo, "object contains gninfo properties");
+ t.eq(testRecord.gninfo.schema, "iso19139", "check value for schema property in record.gninfo");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.js b/misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.js
new file mode 100644
index 0000000..4763aef
--- /dev/null
+++ b/misc/openlayers/tests/Format/CSWGetRecords/v2_0_2.js
@@ -0,0 +1,50 @@
+var csw_request =
+'<csw:GetRecords xmlns:csw="http://www.opengis.net/cat/csw/2.0.2" service="CSW" version="2.0.2" resultType="results" startPosition="10" maxRecords="20" xmlns:gmd="http://www.isotc211.org/2005/gmd">' +
+ '<csw:Query typeNames="csw:Record">' +
+ '<csw:ElementSetName>brief</csw:ElementSetName>' +
+ '<csw:Constraint version="1.1.0">' +
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsLike wildCard="*" singleChar="." escapeChar="!">' +
+ '<ogc:PropertyName>my_prop</ogc:PropertyName>' +
+ '<ogc:Literal>my_prop_value</ogc:Literal>' +
+ '</ogc:PropertyIsLike>' +
+ '</ogc:Filter>' +
+ '</csw:Constraint>' +
+ '</csw:Query>' +
+'</csw:GetRecords>';
+
+var csw_response =
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<csw:GetRecordsResponse xmlns:csw="http://www.opengis.net/cat/csw/2.0.2">' +
+ '<csw:SearchStatus timestamp="2009-06-08T12:03:34" />' +
+ '<csw:SearchResults numberOfRecordsMatched="10" numberOfRecordsReturned="2" elementSet="brief" nextRecord="3">' +
+ '<csw:BriefRecord xmlns:geonet="http://www.fao.org/geonetwork" xmlns:ows="http://www.opengis.net/ows" xmlns:dc="http://purl.org/dc/elements/1.1/">' +
+ '<dc:identifier>895ac38b-7aef-4a21-b593-b35a6fc7bba9</dc:identifier>' +
+ '<dc:title>Sample title</dc:title>' +
+ '<dc:subject />' +
+ '<dc:subject />' +
+ '<ows:BoundingBox crs="::Lambert Azimuthal Projection">' +
+ '<ows:LowerCorner>156 -3</ows:LowerCorner>' +
+ '<ows:UpperCorner>37 83</ows:UpperCorner>' +
+ '</ows:BoundingBox>' +
+ '<ows:BoundingBox crs="::WGS 1984">' +
+ '<ows:LowerCorner>51.1 -34.6</ows:LowerCorner>' +
+ '<ows:UpperCorner>-17.3 38.2</ows:UpperCorner>' +
+ '</ows:BoundingBox>' +
+ '</csw:BriefRecord>' +
+ '<csw:BriefRecord xmlns:geonet="http://www.fao.org/geonetwork" xmlns:ows="http://www.opengis.net/ows" xmlns:dc="http://purl.org/dc/elements/1.1/">' +
+ '<dc:identifier>8a7245c3-8546-42de-8e6f-8fb8b5fd1bc3</dc:identifier>' +
+ '<dc:title>Second record : sample title</dc:title>' +
+ '<ows:BoundingBox crs="::WGS 1984">' +
+ '<ows:LowerCorner>51.1 -34.6</ows:LowerCorner>' +
+ '<ows:UpperCorner>-17.3 38.2</ows:UpperCorner>' +
+ '</ows:BoundingBox>' +
+ '<geonet:info xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:gts="http://www.isotc211.org/2005/gts" xmlns:gml="http://www.opengis.net/gml">' +
+ '<id>859</id>' +
+ '<schema>iso19139</schema>' +
+ '</geonet:info>' +
+ '</csw:BriefRecord>' +
+ '</csw:SearchResults>' +
+'</csw:GetRecordsResponse>'
+;
+
diff --git a/misc/openlayers/tests/Format/EncodedPolyline.html b/misc/openlayers/tests/Format/EncodedPolyline.html
new file mode 100644
index 0000000..1466347
--- /dev/null
+++ b/misc/openlayers/tests/Format/EncodedPolyline.html
@@ -0,0 +1,372 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var flatPoints;
+ var floats, smallFloats, encodedFloats;
+ var signedIntegers, encodedSignedIntegers;
+ var unsignedIntegers, encodedUnsignedIntegers;
+
+ function resetTestingData() {
+ flatPoints = [38.50000, -120.20000,
+ 40.70000, -120.95000,
+ 43.25200, -126.45300];
+
+ floats = [0.00, 0.15, -0.01, -0.16, 0.16, 0.01];
+ smallFloats = [0.00000, 0.00015, -0.00001, -0.00016, 0.00016, 0.00001];
+ encodedFloats = '?]@^_@A';
+
+ signedIntegers = [0, 15, -1, -16, 16, 1];
+ encodedSignedIntegers = '?]@^_@A';
+
+ unsignedIntegers = [0, 30, 1, 31, 32, 2, 174];
+ encodedUnsignedIntegers = '?]@^_@AmD';
+ }
+
+ var basePoints = new Array(
+ new Array(3850000, -12020000),
+ new Array(4070000, -12095000),
+ new Array(4325200, -12645300)
+ );
+
+ var points = [
+ new OpenLayers.Geometry.Point(-120.200, 38.500),
+ new OpenLayers.Geometry.Point(-120.950, 40.700),
+ new OpenLayers.Geometry.Point(-126.453, 43.252)
+ ];
+
+ var singlePoint = new OpenLayers.Feature.Vector(points[0]);
+
+ var linestring = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(points)
+ );
+
+ var multipoint = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPoint(points)
+ );
+
+ var linearring = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing(points)
+ );
+
+ var polygon = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing(points)
+ ])
+ );
+
+ var encoded = "_p~iF~ps|U_ulLnnqC_mqNvxq`@";
+
+ function test_Format_EncodedPolyline_constructor(t) {
+ t.plan(4);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.EncodedPolyline(options);
+ t.ok(format instanceof OpenLayers.Format.EncodedPolyline,
+ "new OpenLayers.Format.EncodedPolyline returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ function test_Format_EncodedPolyline_read(t) {
+ t.plan(5);
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.ok(linestring.geometry.equals(format.read(encoded).geometry),
+ "format correctly reads encoded polyline");
+
+ format = new OpenLayers.Format.EncodedPolyline({
+ geometryType: "multipoint"
+ });
+ t.ok(multipoint.geometry.equals(format.read(encoded).geometry),
+ "format correctly reads encoded multipoint");
+
+ format.geometryType = "linearring";
+ t.ok(linearring.geometry.equals(format.read(encoded).geometry),
+ "format correctly reads encoded linearring");
+
+ format.geometryType = "polygon";
+ t.ok(polygon.geometry.equals(format.read(encoded).geometry),
+ "format correctly reads encoded polygon");
+
+ format.geometryType = "point";
+ t.ok(points[0].equals(format.read(encoded).geometry),
+ "format correctly reads encoded point");
+ }
+
+ function test_Format_EncodedPolyline_decode(t) {
+ t.plan(6);
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ var decodedPoints = format.decode(encoded, 2);
+ for (i in decodedPoints) {
+ var point = basePoints[i];
+ var decodedPoint = decodedPoints[i];
+ t.eq(parseInt(decodedPoint[0] * 1e5), point[0]);
+ t.eq(parseInt(decodedPoint[1] * 1e5), point[1]);
+ }
+ }
+
+ function test_Format_EncodedPolyline_write(t) {
+ t.plan(5);
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.write(linestring), encoded,
+ "format correctly writes encoded polyline");
+
+ t.eq(format.write(multipoint), encoded,
+ "format correctly writes encoded multipoint");
+
+ // Different output than encoded,
+ // because polygon closing point is included
+ t.eq(format.write(linearring),
+ "_p~iF~ps|U_ulLnnqC_mqNvxq`@~b_\\ghde@",
+ "format correctly writes encoded linearring");
+
+ t.eq(format.write(polygon),
+ "_p~iF~ps|U_ulLnnqC_mqNvxq`@~b_\\ghde@",
+ "format correctly writes encoded polygon");
+
+ t.eq(format.write(singlePoint), "_p~iF~ps|U",
+ "format correctly writes encoded point");
+ }
+
+ function test_Format_EncodedPolyline_encode(t) {
+ t.plan(1);
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encode(basePoints, 2, 1), encoded);
+ }
+
+ function test_encodeDeltas_returns_expected_value(t) {
+ t.plan(1);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeDeltas(flatPoints, 2), encoded);
+ }
+
+ function test_decodeDeltas_returns_expected_value(t) {
+ t.plan(1);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeDeltas(encoded, 2), flatPoints);
+ }
+
+
+
+ function test_encodeFloats_returns_expected_value(t) {
+ t.plan(3);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeFloats(smallFloats), encodedFloats);
+
+ resetTestingData();
+ t.eq(format.encodeFloats(smallFloats, 1e5), encodedFloats);
+
+ t.eq(format.encodeFloats(floats, 1e2), encodedFloats);
+ }
+
+ function test_decodeFloats_returns_expected_value(t) {
+ t.plan(3);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeFloats(encodedFloats), smallFloats);
+ t.eq(format.decodeFloats(encodedFloats, 1e5), smallFloats);
+ t.eq(format.decodeFloats(encodedFloats, 1e2), floats);
+ }
+
+
+
+ function test_encodeSignedIntegers_returns_expected_value(t) {
+ t.plan(1);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeSignedIntegers(
+ signedIntegers), encodedSignedIntegers);
+ }
+
+ function test_decodeSignedIntegers_returns_expected_value(t) {
+ t.plan(1);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeSignedIntegers(
+ encodedSignedIntegers), signedIntegers);
+ }
+
+
+
+ function test_encodeUnsignedIntegers_returns_expected_value(t) {
+ t.plan(1);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeUnsignedIntegers(
+ unsignedIntegers), encodedUnsignedIntegers);
+ }
+
+ function test_decodeUnsignedIntegers_returns_expected_value(t) {
+ t.plan(1);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeUnsignedIntegers(
+ encodedUnsignedIntegers), unsignedIntegers);
+ }
+
+
+
+ function test_encodeFloat_returns_expected_value(t) {
+ t.plan(12);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeFloat(0.00000), '?');
+ t.eq(format.encodeFloat(-0.00001), '@');
+ t.eq(format.encodeFloat(0.00001), 'A');
+ t.eq(format.encodeFloat(-0.00002), 'B');
+ t.eq(format.encodeFloat(0.00002), 'C');
+ t.eq(format.encodeFloat(0.00015), ']');
+ t.eq(format.encodeFloat(-0.00016), '^');
+
+ t.eq(format.encodeFloat(-0.1, 10), '@');
+ t.eq(format.encodeFloat(0.1, 10), 'A');
+
+ t.eq(format.encodeFloat(16 * 32 / 1e5), '__@');
+ t.eq(format.encodeFloat(16 * 32 * 32 / 1e5), '___@');
+
+ // from the "Encoded Polyline Algorithm Format" page at Google
+ t.eq(format.encodeFloat(-179.9832104), '`~oia@');
+ }
+
+ function test_decodeFloat_returns_expected_value(t) {
+ t.plan(12);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeFloat('?'), 0.00000);
+ t.eq(format.decodeFloat('@'), -0.00001);
+ t.eq(format.decodeFloat('A'), 0.00001);
+ t.eq(format.decodeFloat('B'), -0.00002);
+ t.eq(format.decodeFloat('C'), 0.00002);
+ t.eq(format.decodeFloat(']'), 0.00015);
+ t.eq(format.decodeFloat('^'), -0.00016);
+
+ t.eq(format.decodeFloat('@', 10), -0.1);
+ t.eq(format.decodeFloat('A', 10), 0.1);
+
+ t.eq(format.decodeFloat('__@'), 16 * 32 / 1e5);
+ t.eq(format.decodeFloat('___@'), 16 * 32 * 32 / 1e5);
+
+ // from the "Encoded Polyline Algorithm Format" page at Google
+ t.eq(format.decodeFloat('`~oia@'), -179.98321);
+ }
+
+
+
+ function test_encodeSignedInteger_returns_expected_value(t) {
+ t.plan(10);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeSignedInteger(0), '?');
+ t.eq(format.encodeSignedInteger(-1), '@');
+ t.eq(format.encodeSignedInteger(1), 'A');
+ t.eq(format.encodeSignedInteger(-2), 'B');
+ t.eq(format.encodeSignedInteger(2), 'C');
+ t.eq(format.encodeSignedInteger(15), ']');
+ t.eq(format.encodeSignedInteger(-16), '^');
+
+ t.eq(format.encodeSignedInteger(16), '_@');
+ t.eq(format.encodeSignedInteger(16 * 32), '__@');
+ t.eq(format.encodeSignedInteger(16 * 32 * 32), '___@');
+ }
+
+ function test_decodeSignedInteger_returns_expected_value(t) {
+ t.plan(10);
+ resetTestingData();
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeSignedInteger('?'), 0);
+ t.eq(format.decodeSignedInteger('@'), -1);
+ t.eq(format.decodeSignedInteger('A'), 1);
+ t.eq(format.decodeSignedInteger('B'), -2);
+ t.eq(format.decodeSignedInteger('C'), 2);
+ t.eq(format.decodeSignedInteger(']'), 15);
+ t.eq(format.decodeSignedInteger('^'), -16);
+
+ t.eq(format.decodeSignedInteger('__@'), 16 * 32);
+ t.eq(format.decodeSignedInteger('___@'), 16 * 32 * 32);
+ t.eq(format.decodeSignedInteger('_@'), 16);
+ }
+
+
+
+ function test_encodeUnsignedInteger_returns_expected_value(t) {
+ t.plan(10);
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.encodeUnsignedInteger(0), '?');
+ t.eq(format.encodeUnsignedInteger(1), '@');
+ t.eq(format.encodeUnsignedInteger(2), 'A');
+ t.eq(format.encodeUnsignedInteger(30), ']');
+ t.eq(format.encodeUnsignedInteger(31), '^');
+ t.eq(format.encodeUnsignedInteger(32), '_@');
+
+ t.eq(format.encodeUnsignedInteger(32 * 32), '__@');
+ t.eq(format.encodeUnsignedInteger(5 * 32 * 32), '__D');
+ t.eq(format.encodeUnsignedInteger(32 * 32 * 32), '___@');
+
+ // from the "Encoded Polyline Algorithm Format" page at Google
+ t.eq(format.encodeUnsignedInteger(174), 'mD');
+ }
+
+ function test_decodeUnsignedInteger_returns_expected_value(t) {
+ t.plan(10);
+
+ var format = new OpenLayers.Format.EncodedPolyline();
+
+ t.eq(format.decodeUnsignedInteger('?'), 0);
+ t.eq(format.decodeUnsignedInteger('@'), 1);
+ t.eq(format.decodeUnsignedInteger('A'), 2);
+ t.eq(format.decodeUnsignedInteger(']'), 30);
+ t.eq(format.decodeUnsignedInteger('^'), 31);
+ t.eq(format.decodeUnsignedInteger('_@'), 32);
+
+ t.eq(format.decodeUnsignedInteger('__@'), 32 * 32);
+ t.eq(format.decodeUnsignedInteger('__D'), 5 * 32 * 32);
+ t.eq(format.decodeUnsignedInteger('___@'), 32 * 32 * 32);
+
+ // from the "Encoded Polyline Algorithm Format" page at Google
+ t.eq(format.decodeUnsignedInteger('mD'), 174);
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/Filter.html b/misc/openlayers/tests/Format/Filter.html
new file mode 100644
index 0000000..69a0564
--- /dev/null
+++ b/misc/openlayers/tests/Format/Filter.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.Filter(options);
+ t.ok(format instanceof OpenLayers.Format.Filter,
+ "new OpenLayers.Format.Filter returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/Filter/v1.html b/misc/openlayers/tests/Format/Filter/v1.html
new file mode 100644
index 0000000..a862955
--- /dev/null
+++ b/misc/openlayers/tests/Format/Filter/v1.html
@@ -0,0 +1,404 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_PropertyIsBetween(t) {
+
+ t.plan(6);
+
+ var test_xml, parser, xml;
+
+ parser = new OpenLayers.Format.Filter.v1();
+ xml = new OpenLayers.Format.XML();
+
+ test_xml =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsBetween>' +
+ '<ogc:PropertyName>number</ogc:PropertyName>' +
+ '<ogc:LowerBoundary>' +
+ '<ogc:Literal>0</ogc:Literal>' +
+ '</ogc:LowerBoundary>' +
+ '<ogc:UpperBoundary>' +
+ '<ogc:Literal>100</ogc:Literal>' +
+ '</ogc:UpperBoundary>' +
+ '</ogc:PropertyIsBetween>' +
+ '</ogc:Filter>';
+
+ var filter = parser.read(xml.read(test_xml).documentElement);
+ t.eq(filter.type, OpenLayers.Filter.Comparison.BETWEEN,
+ "[0] read correct type");
+ t.eq(filter.lowerBoundary, 0,
+ "[0] record correct lower boundary value");
+ t.eq(filter.upperBoundary, 100,
+ "[0] record correct upper boundary value");
+
+ test_xml =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsBetween>' +
+ '<ogc:PropertyName>number</ogc:PropertyName>' +
+ '<ogc:LowerBoundary>0</ogc:LowerBoundary>' +
+ '<ogc:UpperBoundary>100</ogc:UpperBoundary>' +
+ '</ogc:PropertyIsBetween>' +
+ '</ogc:Filter>';
+
+ var filter = parser.read(xml.read(test_xml).documentElement);
+ t.eq(filter.type, OpenLayers.Filter.Comparison.BETWEEN,
+ "[1] read correct type");
+ t.eq(filter.lowerBoundary, 0,
+ "[1] record correct lower boundary value");
+ t.eq(filter.upperBoundary, 100,
+ "[1] record correct upper boundary value");
+ }
+
+ function test_PropertyIsNull(t) {
+
+ t.plan(3);
+
+ var format, test_xml, xml, filter, node;
+
+ format = new OpenLayers.Format.Filter.v1();
+
+ test_xml =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsNull>' +
+ '<ogc:PropertyName>prop</ogc:PropertyName>' +
+ '</ogc:PropertyIsNull>' +
+ '</ogc:Filter>';
+
+ // Test reading a PropertyIsNull filter from an XML doc
+ xml = new OpenLayers.Format.XML();
+ filter = format.read(xml.read(test_xml).documentElement);
+ t.eq(filter.type, OpenLayers.Filter.Comparison.IS_NULL,
+ "[0] read correct type");
+ t.eq(filter.property, 'prop',
+ "[0] record correct property name");
+
+ // Test writing a PropertyIsNull filter out to XML
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.IS_NULL,
+ property: "prop"
+ });
+ node = format.write(filter);
+ t.xml_eq(node, test_xml, "filter correctly written");
+
+ }
+
+ function test_Intersects(t) {
+
+ t.plan(4);
+
+ var str =
+ '<Filter xmlns="http://www.opengis.net/ogc">' +
+ '<Intersects>' +
+ '<PropertyName>Geometry</PropertyName>' +
+ '<gml:Polygon xmlns:gml="http://www.opengis.net/gml">' +
+ '<gml:outerBoundaryIs>' +
+ '<gml:LinearRing>' +
+ '<gml:coordinates decimal="." cs="," ts=" ">2488789,289552 2588789,289552 2588789,389552 2488789,389552 2488789,289552</gml:coordinates>' +
+ '</gml:LinearRing>' +
+ '</gml:outerBoundaryIs>' +
+ '</gml:Polygon>' +
+ '</Intersects>' +
+ '</Filter>';
+
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: "Geometry",
+ value: OpenLayers.Geometry.fromWKT("POLYGON((2488789 289552, 2588789 289552, 2588789 389552, 2488789 389552, 2488789 289552))")
+ });
+
+ // test writing
+ var node = format.write(filter);
+ t.xml_eq(node, str, "filter correctly written");
+
+ // test reading
+ var doc = (new OpenLayers.Format.XML).read(str);
+ var got = format.read(doc.firstChild);
+ t.eq(got.type, filter.type, "read correct type");
+ t.eq(got.property, filter.property, "read correct property");
+ t.geom_eq(got.value, filter.value, "read correct value");
+
+ }
+
+ function test_Within(t) {
+
+ t.plan(4);
+
+ var str =
+ '<Filter xmlns="http://www.opengis.net/ogc">' +
+ '<Within>' +
+ '<PropertyName>Geometry</PropertyName>' +
+ '<gml:Polygon xmlns:gml="http://www.opengis.net/gml">' +
+ '<gml:outerBoundaryIs>' +
+ '<gml:LinearRing>' +
+ '<gml:coordinates decimal="." cs="," ts=" ">2488789,289552 2588789,289552 2588789,389552 2488789,389552 2488789,289552</gml:coordinates>' +
+ '</gml:LinearRing>' +
+ '</gml:outerBoundaryIs>' +
+ '</gml:Polygon>' +
+ '</Within>' +
+ '</Filter>';
+
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.WITHIN,
+ property: "Geometry",
+ value: OpenLayers.Geometry.fromWKT("POLYGON((2488789 289552, 2588789 289552, 2588789 389552, 2488789 389552, 2488789 289552))")
+ });
+
+ // test writing
+ var node = format.write(filter);
+ t.xml_eq(node, str, "filter correctly written");
+
+ // test reading
+ var doc = (new OpenLayers.Format.XML).read(str);
+ var got = format.read(doc.firstChild);
+ t.eq(got.type, filter.type, "read correct type");
+ t.eq(got.property, filter.property, "read correct property");
+ t.geom_eq(got.value, filter.value, "read correct value");
+
+ }
+
+ function test_Contains(t) {
+
+ t.plan(4);
+
+ var str =
+ '<Filter xmlns="http://www.opengis.net/ogc">' +
+ '<Contains>' +
+ '<PropertyName>Geometry</PropertyName>' +
+ '<gml:Polygon xmlns:gml="http://www.opengis.net/gml">' +
+ '<gml:outerBoundaryIs>' +
+ '<gml:LinearRing>' +
+ '<gml:coordinates decimal="." cs="," ts=" ">2488789,289552 2588789,289552 2588789,389552 2488789,389552 2488789,289552</gml:coordinates>' +
+ '</gml:LinearRing>' +
+ '</gml:outerBoundaryIs>' +
+ '</gml:Polygon>' +
+ '</Contains>' +
+ '</Filter>';
+
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.CONTAINS,
+ property: "Geometry",
+ value: OpenLayers.Geometry.fromWKT("POLYGON((2488789 289552, 2588789 289552, 2588789 389552, 2488789 389552, 2488789 289552))")
+ });
+
+ // test writing
+ var node = format.write(filter);
+ t.xml_eq(node, str, "filter correctly written");
+
+ // test reading
+ var doc = (new OpenLayers.Format.XML).read(str);
+ var got = format.read(doc.firstChild);
+ t.eq(got.type, filter.type, "read correct type");
+ t.eq(got.property, filter.property, "read correct property");
+ t.geom_eq(got.value, filter.value, "read correct value");
+
+ }
+
+ function test_logical_fid(t) {
+ // the Filter Encoding spec doesn't allow for FID filters inside logical filters
+ // however, to be liberal, we will write them without complaining
+ t.plan(3);
+
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "person",
+ value: "me"
+ }),
+ new OpenLayers.Filter.FeatureId({fids: ["foo.1", "foo.2"]})
+ ]
+ });
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+
+ var got = format.write(filter);
+ var exp = readXML("LogicalFeatureId");
+ t.xml_eq(got, exp, "wrote FID filter in logical OR without complaint");
+
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "person",
+ value: "me"
+ }),
+ new OpenLayers.Filter.FeatureId({fids: ["foo.1", "foo.2"]})
+ ]
+ });
+ got = format.write(filter);
+ exp = readXML("LogicalFeatureIdAnd");
+ t.xml_eq(got, exp, "wrote FID filter in logical AND without complaint");
+
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.NOT,
+ filters: [
+ new OpenLayers.Filter.FeatureId({fids: ["foo.2"]})
+ ]
+ });
+ got = format.write(filter);
+ exp = readXML("LogicalFeatureIdNot");
+ t.xml_eq(got, exp, "wrote FID filter in logical NOT without complaint");
+ }
+
+ function test_between_literal(t) {
+ t.plan(3);
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "foo",
+ lowerBoundary: 1.0,
+ upperBoundary: 2.0
+ });
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+
+ var exp = format.read(readXML("BetweenLiteral"));
+
+ // confirm that reading works as expected
+ t.eq(exp.property, "foo", "property");
+ t.eq(exp.lowerBoundary, 1.0, "lowerBoundary");
+ t.eq(exp.upperBoundary, 2.0, "upperBoundary");
+ }
+
+
+ function test_date_writing(t) {
+ t.plan(1);
+
+ // ISO 8601: 2010-11-27T18:19:15.123Z
+ var start = new Date(Date.UTC(2010, 10, 27, 18, 19, 15, 123));
+
+ // ISO 8601: 2011-12-27T18:19:15.123Z
+ var end = new Date(Date.UTC(2011, 11, 27, 18, 19, 15, 123));
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "when",
+ lowerBoundary: start,
+ upperBoundary: end
+ });
+
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+
+ var got = format.write(filter);
+ var exp = readXML("BetweenDates");
+ t.xml_eq(got, exp, "comparison filter with dates");
+ }
+
+
+ function test_custom_date_writing(t) {
+ t.plan(1);
+
+ // ISO 8601: 2010-11-27T18:19:15.123Z
+ var start = new Date(Date.UTC(2010, 10, 27, 18, 19, 15, 123));
+
+ // ISO 8601: 2011-12-27T18:19:15.123Z
+ var end = new Date(Date.UTC(2011, 11, 27, 18, 19, 15, 123));
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.BETWEEN,
+ property: "when",
+ lowerBoundary: start,
+ upperBoundary: end
+ });
+
+ var format = new OpenLayers.Format.Filter({
+ encodeLiteral: function(value) {
+ // return just the date and not the time portion
+ return OpenLayers.Date.toISOString(value).split("T").shift();
+ }
+ });
+
+ var got = format.write(filter);
+ var exp = readXML("CustomBetweenDates");
+ t.xml_eq(got, exp, "comparison filter with dates");
+ }
+
+
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return new OpenLayers.Format.XML().read(xml).documentElement;
+ }
+
+
+ </script>
+</head>
+<body>
+
+<div id="LogicalFeatureId"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:Or>
+ <ogc:PropertyIsLike wildCard="*" singleChar="." escape="!">
+ <ogc:PropertyName>person</ogc:PropertyName>
+ <ogc:Literal>me</ogc:Literal>
+ </ogc:PropertyIsLike>
+ <ogc:FeatureId fid="foo.1"/>
+ <ogc:FeatureId fid="foo.2"/>
+ </ogc:Or>
+</ogc:Filter>
+--></div>
+<div id="LogicalFeatureIdAnd"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:And>
+ <ogc:PropertyIsLike wildCard="*" singleChar="." escape="!">
+ <ogc:PropertyName>person</ogc:PropertyName>
+ <ogc:Literal>me</ogc:Literal>
+ </ogc:PropertyIsLike>
+ <ogc:FeatureId fid="foo.1"/>
+ <ogc:FeatureId fid="foo.2"/>
+ </ogc:And>
+</ogc:Filter>
+--></div>
+<div id="LogicalFeatureIdNot"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:Not>
+ <ogc:FeatureId fid="foo.2"/>
+ </ogc:Not>
+</ogc:Filter>
+--></div>
+<div id="BetweenLiteral"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsBetween>
+ <ogc:PropertyName>foo</ogc:PropertyName>
+ <ogc:LowerBoundary>
+ <ogc:Literal>1.0</ogc:Literal>
+ </ogc:LowerBoundary>
+ <ogc:UpperBoundary>
+ <ogc:Literal>2.0</ogc:Literal>
+ </ogc:UpperBoundary>
+ </ogc:PropertyIsBetween>
+</ogc:Filter>
+--></div>
+<div id="BetweenDates"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsBetween>
+ <ogc:PropertyName>when</ogc:PropertyName>
+ <ogc:LowerBoundary>
+ <ogc:Literal>2010-11-27T18:19:15.123Z</ogc:Literal>
+ </ogc:LowerBoundary>
+ <ogc:UpperBoundary>
+ <ogc:Literal>2011-12-27T18:19:15.123Z</ogc:Literal>
+ </ogc:UpperBoundary>
+ </ogc:PropertyIsBetween>
+</ogc:Filter>
+--></div>
+<div id="CustomBetweenDates"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsBetween>
+ <ogc:PropertyName>when</ogc:PropertyName>
+ <ogc:LowerBoundary>
+ <ogc:Literal>2010-11-27</ogc:Literal>
+ </ogc:LowerBoundary>
+ <ogc:UpperBoundary>
+ <ogc:Literal>2011-12-27</ogc:Literal>
+ </ogc:UpperBoundary>
+ </ogc:PropertyIsBetween>
+</ogc:Filter>
+--></div>
+
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/Filter/v1_0_0.html b/misc/openlayers/tests/Format/Filter/v1_0_0.html
new file mode 100644
index 0000000..876723f
--- /dev/null
+++ b/misc/openlayers/tests/Format/Filter/v1_0_0.html
@@ -0,0 +1,295 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var test_xml =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:Or>' +
+ '<ogc:PropertyIsBetween>' +
+ '<ogc:PropertyName>number</ogc:PropertyName>' +
+ '<ogc:LowerBoundary>' +
+ '<ogc:Literal>1064866676</ogc:Literal>' +
+ '</ogc:LowerBoundary>' +
+ '<ogc:UpperBoundary>' +
+ '<ogc:Literal>1065512599</ogc:Literal>' +
+ '</ogc:UpperBoundary>' +
+ '</ogc:PropertyIsBetween>' +
+ '<ogc:PropertyIsLike wildCard="*" singleChar="." escape="!">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>*dog.food!*good</ogc:Literal>' +
+ '</ogc:PropertyIsLike>' +
+ '<ogc:Not>' +
+ '<ogc:PropertyIsLessThanOrEqualTo>' +
+ '<ogc:PropertyName>FOO</ogc:PropertyName>' +
+ '<ogc:Literal>5000</ogc:Literal>' +
+ '</ogc:PropertyIsLessThanOrEqualTo>' +
+ '</ogc:Not>' +
+ '</ogc:Or>' +
+ '</ogc:Filter>';
+
+ function test_read(t) {
+ t.plan(3);
+
+ var parser = new OpenLayers.Format.Filter.v1_0_0();
+ var xml = new OpenLayers.Format.XML();
+ var filter = parser.read(xml.read(test_xml).documentElement);
+
+ t.ok(filter instanceof OpenLayers.Filter.Logical, "instance of correct class");
+ t.eq(filter.type, OpenLayers.Filter.Logical.OR, "correct type");
+ t.eq(filter.filters.length, 3, "correct number of child filters");
+ }
+
+ function test_write(t) {
+ t.plan(1);
+
+ // read first - testing that write produces the ogc:Filter element above
+ var parser = new OpenLayers.Format.Filter.v1_0_0();
+ var xml = new OpenLayers.Format.XML();
+ var filter = parser.read(xml.read(test_xml).documentElement);
+
+ var node = parser.write(filter);
+ t.xml_eq(node, test_xml, "filter correctly written");
+
+ }
+
+ function test_BBOX(t) {
+ t.plan(1);
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ property: "the_geom",
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:BBOX>' +
+ '<ogc:PropertyName>the_geom</ogc:PropertyName>' +
+ '<gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">' +
+ '<gml:coordinates decimal="." cs="," ts=" ">-180,-90 180,90</gml:coordinates>' +
+ '</gml:Box>' +
+ '</ogc:BBOX>' +
+ '</ogc:Filter>';
+
+ var parser = new OpenLayers.Format.Filter.v1_0_0();
+ var node = parser.write(filter);
+
+ t.xml_eq(node, out, "bbox correctly written");
+ }
+
+ function test_BBOX_noGeometryName(t) {
+ t.plan(1);
+ // WFS 1.0.0 does not allow BBOX filters without property, but
+ // GeoServer accepts them.
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:BBOX>' +
+ '<gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">' +
+ '<gml:coordinates decimal="." cs="," ts=" ">-180,-90 180,90</gml:coordinates>' +
+ '</gml:Box>' +
+ '</ogc:BBOX>' +
+ '</ogc:Filter>';
+
+ var parser = new OpenLayers.Format.Filter.v1_0_0();
+ var node = parser.write(filter);
+
+ t.xml_eq(node, out, "bbox correctly written");
+ }
+
+ function test_DWithin(t) {
+
+ t.plan(6);
+
+ var str =
+ '<Filter xmlns="http://www.opengis.net/ogc">' +
+ '<DWithin>' +
+ '<PropertyName>Geometry</PropertyName>' +
+ '<gml:Point xmlns:gml="http://www.opengis.net/gml">' +
+ '<gml:coordinates decimal="." cs="," ts=" ">2488789,289552</gml:coordinates>' +
+ '</gml:Point>' +
+ '<Distance units="m">1000</Distance>' +
+ '</DWithin>' +
+ '</Filter>';
+
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ property: "Geometry",
+ value: new OpenLayers.Geometry.Point(2488789,289552),
+ distance: 1000,
+ distanceUnits: "m"
+ });
+
+ // test writing
+ var node = format.write(filter);
+ t.xml_eq(node, str, "filter correctly written");
+
+ // test reading
+ var doc = (new OpenLayers.Format.XML).read(str);
+ var got = format.read(doc.firstChild);
+ t.eq(got.type, filter.type, "read correct type");
+ t.eq(got.property, filter.property, "read correct property");
+ t.geom_eq(got.value, filter.value, "read correct value");
+ t.eq(got.distance, filter.distance, "read correct distance");
+ t.eq(got.distanceUnits, filter.distanceUnits, "read correct distance units");
+
+ }
+
+ function test_Intersects(t) {
+
+ t.plan(4);
+
+ var str =
+ '<Filter xmlns="http://www.opengis.net/ogc">' +
+ '<Intersects>' +
+ '<PropertyName>Geometry</PropertyName>' +
+ '<gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">' +
+ '<gml:coordinates decimal="." cs="," ts=" ">-180,-90 180,90</gml:coordinates>' +
+ '</gml:Box>' +
+ '</Intersects>' +
+ '</Filter>';
+
+ var format = new OpenLayers.Format.Filter.v1_0_0();
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: "Geometry",
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ });
+
+ // test writing
+ var node = format.write(filter);
+ t.xml_eq(node, str, "filter correctly written");
+
+ // test reading
+ var doc = (new OpenLayers.Format.XML).read(str);
+ var got = format.read(doc.firstChild);
+ t.eq(got.type, filter.type, "read correct type");
+ t.eq(got.property, filter.property, "read correct property");
+ t.eq(got.value.toArray(), filter.value.toArray(), "read correct value");
+
+ }
+
+ function test_FilterFunctions(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.Filter.v1_0_0();
+
+ //test spatial intersects with filter function
+ var filter = new OpenLayers.Filter.Spatial({
+ property: 'the_geom',
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ value: new OpenLayers.Filter.Function({
+ name : 'querySingle',
+ params: ['sf:restricted', 'the_geom', 'cat=3']
+ })
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:Intersects>' +
+ '<ogc:PropertyName>the_geom</ogc:PropertyName>' +
+ '<ogc:Function name="querySingle">' +
+ '<ogc:Literal>sf:restricted</ogc:Literal>' +
+ '<ogc:Literal>the_geom</ogc:Literal>' +
+ '<ogc:Literal>cat=3</ogc:Literal>' +
+ '</ogc:Function>' +
+ '</ogc:Intersects>' +
+ '</ogc:Filter>';
+
+
+ var node = parser.write(filter);
+
+ //test writer
+ t.xml_eq(node, out, "spatial intersect filter with functions correctly written");
+
+ //test logical filter with custom function
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "FOO",
+ value: new OpenLayers.Filter.Function({
+ name : 'customFunction',
+ params : ['param1', 'param2']
+ })
+ })
+ ]
+ });
+
+ out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:And>' +
+ '<ogc:PropertyIsNotEqualTo>' +
+ '<ogc:PropertyName>FOO</ogc:PropertyName>' +
+ '<ogc:Function name="customFunction">' +
+ '<ogc:Literal>param1</ogc:Literal>' +
+ '<ogc:Literal>param2</ogc:Literal>' +
+ '</ogc:Function>' +
+ '</ogc:PropertyIsNotEqualTo>' +
+ '</ogc:And>' +
+ '</ogc:Filter>';
+
+ node = parser.write(filter);
+
+ //test writer
+ t.xml_eq(node, out, "comparison filter with filter functions correctly written");
+
+ }
+
+ function test_NestedFilterFunctions(t) {
+ t.plan(1);
+
+ //test spatial dwithin with nested filter function
+ var filter = new OpenLayers.Filter.Spatial({
+ property: 'the_geom',
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ value: new OpenLayers.Filter.Function({
+ name : 'collectGeometries',
+ params: [
+ new OpenLayers.Filter.Function({
+ name : 'queryCollection',
+ params: ['sf:roads', 'the_geom', 'INCLUDE']
+ })
+ ]
+ }),
+ distanceUnits: "meters",
+ distance: 200
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:DWithin>' +
+ '<ogc:PropertyName>the_geom</ogc:PropertyName>' +
+ '<ogc:Function name="collectGeometries">' +
+ '<ogc:Function name="queryCollection">' +
+ '<ogc:Literal>sf:roads</ogc:Literal>' +
+ '<ogc:Literal>the_geom</ogc:Literal>' +
+ '<ogc:Literal>INCLUDE</ogc:Literal>' +
+ '</ogc:Function>' +
+ '</ogc:Function>' +
+ '<ogc:Distance units="meters">200</ogc:Distance>' +
+ '</ogc:DWithin>' +
+ '</ogc:Filter>';
+
+ var parser = new OpenLayers.Format.Filter.v1_0_0();
+ var node = parser.write(filter);
+
+ //test writer
+ t.xml_eq(node, out, "spatial dwithin filter with nested functions correctly written");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/Filter/v1_1_0.html b/misc/openlayers/tests/Format/Filter/v1_1_0.html
new file mode 100644
index 0000000..68d1ec1
--- /dev/null
+++ b/misc/openlayers/tests/Format/Filter/v1_1_0.html
@@ -0,0 +1,402 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var test_xml =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:Or>' +
+ '<ogc:PropertyIsBetween>' +
+ '<ogc:PropertyName>number</ogc:PropertyName>' +
+ '<ogc:LowerBoundary>' +
+ '<ogc:Literal>1064866676</ogc:Literal>' +
+ '</ogc:LowerBoundary>' +
+ '<ogc:UpperBoundary>' +
+ '<ogc:Literal>1065512599</ogc:Literal>' +
+ '</ogc:UpperBoundary>' +
+ '</ogc:PropertyIsBetween>' +
+ '<ogc:PropertyIsLike wildCard="*" singleChar="." escapeChar="!">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>*dog.food!*good</ogc:Literal>' +
+ '</ogc:PropertyIsLike>' +
+ '<ogc:Not>' +
+ '<ogc:PropertyIsLessThanOrEqualTo>' +
+ '<ogc:PropertyName>FOO</ogc:PropertyName>' +
+ '<ogc:Literal>5000</ogc:Literal>' +
+ '</ogc:PropertyIsLessThanOrEqualTo>' +
+ '</ogc:Not>' +
+ '<ogc:PropertyIsEqualTo matchCase="true">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '<ogc:PropertyIsEqualTo matchCase="false">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Or>' +
+ '</ogc:Filter>';
+
+ function test_read(t) {
+ t.plan(3);
+
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var xml = new OpenLayers.Format.XML();
+ var filter = parser.read(xml.read(test_xml).documentElement);
+
+ t.ok(filter instanceof OpenLayers.Filter.Logical, "instance of correct class");
+ t.eq(filter.type, OpenLayers.Filter.Logical.OR, "correct type");
+ t.eq(filter.filters.length, 5, "correct number of child filters");
+ }
+
+ function test_write(t) {
+ t.plan(1);
+
+ // read first - testing that write produces the ogc:Filter element above
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var xml = new OpenLayers.Format.XML();
+ var filter = parser.read(xml.read(test_xml).documentElement);
+
+ var node = parser.write(filter);
+ t.xml_eq(node, test_xml, "filter correctly written");
+
+ }
+
+ function test_matchCase(t) {
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var xml = new OpenLayers.Format.XML();
+
+ var cases = [{
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsEqualTo>' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Filter>',
+ exp: true
+ }, {
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsEqualTo matchCase="1">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Filter>',
+ exp: true
+ }, {
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsEqualTo matchCase="true">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Filter>',
+ exp: true
+ }, {
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsEqualTo matchCase="0">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Filter>',
+ exp: false
+ }, {
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsEqualTo matchCase="0">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Filter>',
+ exp: false
+ }, {
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsNotEqualTo matchCase="true">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsNotEqualTo>' +
+ '</ogc:Filter>',
+ exp: true
+ }, {
+ str:
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:PropertyIsNotEqualTo matchCase="false">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>dog</ogc:Literal>' +
+ '</ogc:PropertyIsNotEqualTo>' +
+ '</ogc:Filter>',
+ exp: false
+ }];
+
+ t.plan(cases.length);
+
+ var filter, c;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ filter = parser.read(xml.read(c.str).documentElement);
+ t.eq(filter.matchCase, c.exp, "case " + i);
+ }
+
+ }
+
+ function test_BBOX(t) {
+ t.plan(1);
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ property: "the_geom",
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:BBOX>' +
+ '<ogc:PropertyName>the_geom</ogc:PropertyName>' +
+ '<gml:Envelope xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">' +
+ '<gml:lowerCorner>-180 -90</gml:lowerCorner>' +
+ '<gml:upperCorner>180 90</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</ogc:BBOX>' +
+ '</ogc:Filter>';
+
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var node = parser.write(filter);
+
+ t.xml_eq(node, out, "bbox correctly written");
+ }
+
+ function test_BBOX_noGeometryName(t) {
+ t.plan(1);
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:BBOX>' +
+ '<gml:Envelope xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">' +
+ '<gml:lowerCorner>-180 -90</gml:lowerCorner>' +
+ '<gml:upperCorner>180 90</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</ogc:BBOX>' +
+ '</ogc:Filter>';
+
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var node = parser.write(filter);
+
+ t.xml_eq(node, out, "bbox correctly written");
+ }
+
+
+ function test_Intersects(t) {
+
+ t.plan(4);
+
+ var str =
+ '<Filter xmlns="http://www.opengis.net/ogc">' +
+ '<Intersects>' +
+ '<PropertyName>Geometry</PropertyName>' +
+ '<gml:Envelope xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">' +
+ '<gml:lowerCorner>-180 -90</gml:lowerCorner>' +
+ '<gml:upperCorner>180 90</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</Intersects>' +
+ '</Filter>';
+
+ var format = new OpenLayers.Format.Filter.v1_1_0();
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ property: "Geometry",
+ value: new OpenLayers.Bounds(-180, -90, 180, 90),
+ projection: "EPSG:4326"
+ });
+
+ // test writing
+ var node = format.write(filter);
+ t.xml_eq(node, str, "filter correctly written");
+
+ // test reading
+ var doc = (new OpenLayers.Format.XML).read(str);
+ var got = format.read(doc.firstChild);
+ t.eq(got.type, filter.type, "read correct type");
+ t.eq(got.property, filter.property, "read correct property");
+ t.eq(got.value.toArray(), filter.value.toArray(), "read correct value");
+
+ }
+
+ function test_FilterFunctions(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+
+ //test spatial intersects with filter function
+ var filter = new OpenLayers.Filter.Spatial({
+ property: 'the_geom',
+ type: OpenLayers.Filter.Spatial.INTERSECTS,
+ value: new OpenLayers.Filter.Function({
+ name : 'querySingle',
+ params: ['sf:restricted', 'the_geom', 'cat=3']
+ })
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:Intersects>' +
+ '<ogc:PropertyName>the_geom</ogc:PropertyName>' +
+ '<ogc:Function name="querySingle">' +
+ '<ogc:Literal>sf:restricted</ogc:Literal>' +
+ '<ogc:Literal>the_geom</ogc:Literal>' +
+ '<ogc:Literal>cat=3</ogc:Literal>' +
+ '</ogc:Function>' +
+ '</ogc:Intersects>' +
+ '</ogc:Filter>';
+
+
+ var node = parser.write(filter);
+
+ //test writer
+ t.xml_eq(node, out, "spatial intersect filter with functions correctly written");
+
+ //test logical filter with custom function
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ matchCase: false,
+ property: "FOO",
+ value: new OpenLayers.Filter.Function({
+ name : 'customFunction',
+ params : ['param1', 'param2']
+ })
+ })
+ ]
+ });
+
+ out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:And>' +
+ '<ogc:PropertyIsNotEqualTo matchCase="false">' +
+ '<ogc:PropertyName>FOO</ogc:PropertyName>' +
+ '<ogc:Function name="customFunction">' +
+ '<ogc:Literal>param1</ogc:Literal>' +
+ '<ogc:Literal>param2</ogc:Literal>' +
+ '</ogc:Function>' +
+ '</ogc:PropertyIsNotEqualTo>' +
+ '</ogc:And>' +
+ '</ogc:Filter>';
+
+ node = parser.write(filter);
+
+ //test writer
+ t.xml_eq(node, out, "comparison filter with filter functions correctly written");
+
+ }
+
+ function test_NestedFilterFunctions(t) {
+ t.plan(1);
+
+ //test spatial dwithin with nested filter function
+ var filter = new OpenLayers.Filter.Spatial({
+ property: 'the_geom',
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ value: new OpenLayers.Filter.Function({
+ name : 'collectGeometries',
+ params: [
+ new OpenLayers.Filter.Function({
+ name : 'queryCollection',
+ params: ['sf:roads', 'the_geom', 'INCLUDE']
+ })
+ ]
+ }),
+ distanceUnits: "meters",
+ distance: 200
+ });
+
+ var out =
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:DWithin>' +
+ '<ogc:PropertyName>the_geom</ogc:PropertyName>' +
+ '<ogc:Function name="collectGeometries">' +
+ '<ogc:Function name="queryCollection">' +
+ '<ogc:Literal>sf:roads</ogc:Literal>' +
+ '<ogc:Literal>the_geom</ogc:Literal>' +
+ '<ogc:Literal>INCLUDE</ogc:Literal>' +
+ '</ogc:Function>' +
+ '</ogc:Function>' +
+ '<ogc:Distance units="meters">200</ogc:Distance>' +
+ '</ogc:DWithin>' +
+ '</ogc:Filter>';
+
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var node = parser.write(filter);
+
+ //test writer
+ t.xml_eq(node, out, "spatial dwithin filter with nested functions correctly written");
+ }
+
+ function test_write_like_matchcase(t) {
+ t.plan(1);
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "person",
+ value: "*me*",
+ matchCase: false
+ });
+
+ var format = new OpenLayers.Format.Filter.v1_1_0();
+
+ var got = format.write(filter);
+ var exp = readXML("LikeMatchCase");
+ t.xml_eq(got, exp, "wrote matchCase attribute on PropertyIsLike");
+ }
+
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return new OpenLayers.Format.XML().read(xml).documentElement;
+ }
+
+ function test_SortBy(t) {
+ t.plan(1);
+
+ var out =
+ '<ogc:SortBy xmlns:ogc="http://www.opengis.net/ogc">'+
+ '<ogc:SortProperty>'+
+ '<ogc:PropertyName>Title</ogc:PropertyName>'+
+ '<ogc:SortOrder>ASC</ogc:SortOrder>'+
+ '</ogc:SortProperty>'+
+ '<ogc:SortProperty>'+
+ '<ogc:PropertyName>Relevance</ogc:PropertyName>'+
+ '<ogc:SortOrder>DESC</ogc:SortOrder>'+
+ '</ogc:SortProperty>'+
+ '</ogc:SortBy>';
+
+ var parser = new OpenLayers.Format.Filter.v1_1_0();
+ var node = parser.writers['ogc'].SortBy.call(parser, [{
+ "property": 'Title',
+ "order": "ASC"
+ },{
+ "property": 'Relevance',
+ "order": "DESC"
+ }]);
+
+ t.xml_eq(node, out, "Check SortBy");
+ }
+ </script>
+</head>
+<body>
+<div id="LikeMatchCase"><!--
+<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsLike wildCard="*" singleChar="." escapeChar="!" matchCase="false">
+ <ogc:PropertyName>person</ogc:PropertyName>
+ <ogc:Literal>*me*</ogc:Literal>
+ </ogc:PropertyIsLike>
+</ogc:Filter>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/GML.html b/misc/openlayers/tests/Format/GML.html
new file mode 100644
index 0000000..43b8fbd
--- /dev/null
+++ b/misc/openlayers/tests/Format/GML.html
@@ -0,0 +1,462 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Format_GML_constructor(t) {
+ t.plan(4);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.GML(options);
+ t.ok(format instanceof OpenLayers.Format.GML,
+ "new OpenLayers.Format.GML returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+ function test_Format_GML_getFid(t) {
+ t.plan(2);
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(test_content[1]);
+ t.eq(data[0].fid, '221', 'fid on polygons set correctly (with whitespace)');
+ t.eq(data[1].fid, '8', 'fid on linestrings set correctly with whitespace');
+ }
+ function test_Format_GML_no_clobber(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(test_content[1]);
+ t.eq(window.i, undefined,
+ "i is undefined in window scope after reading.");
+ }
+ function test_Format_GML_read_3d(t) {
+ t.plan(2);
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(test_content[0]);
+ t.eq(data[0].geometry.getBounds().toBBOX(), "-1254041.389712,250906.951598,-634517.119991,762236.29408", "Reading 3d content returns geometry with correct bounds (no 0,0)");
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Reading 3d content returns polygon geometry");
+ }
+ function test_Format_GML_write_geoms(t) {
+ t.plan(5);
+ var parser = new OpenLayers.Format.GML();
+
+ var point = shell_start + serialize_geoms['point'] + shell_end;
+ data = parser.read(point);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, point, "Point geometry round trips correctly.");
+
+ var linestring = shell_start + serialize_geoms['linestring'] + shell_end;
+ data = parser.read(linestring);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, linestring, "Line geometry round trips correctly.");
+
+ var polygon = shell_start + serialize_geoms['polygon'] + shell_end;
+ data = parser.read(polygon);
+ var output = parser.write(data);
+ output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, polygon, "Poly geometry round trips correctly.");
+
+ var multipoint = shell_start + serialize_geoms['multipoint'] + shell_end;
+ data = parser.read(multipoint);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, multipoint, "MultiPoint geometry round trips correctly.");
+
+ var multilinestring = shell_start + serialize_geoms['multilinestring'] + shell_end;
+ data = parser.read(multilinestring);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, multilinestring, "MultiLine geometry round trips correctly.");
+
+ }
+ function test_Format_GML_read_point_geom(t) {
+ t.plan(3);
+
+ var point = shell_start + geoms['point'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(point);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Point", "Point GML returns correct classname");
+ t.eq(data[0].geometry.x, 1, "x coord correct");
+ t.eq(data[0].geometry.y, 2, "y coord correct");
+ }
+ function test_Format_GML_read_linestring_geom(t) {
+ t.plan(5);
+
+ var line = shell_start + geoms['linestring'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(line);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.LineString", "LineString GML returns correct classname");
+ t.eq(data[0].geometry.components[0].x, 1, "first x coord correct");
+ t.eq(data[0].geometry.components[0].y, 2, "first y coord correct");
+ t.eq(data[0].geometry.components[1].x, 4, "second x coord correct");
+ t.eq(data[0].geometry.components[1].y, 5, "second y coord correct");
+ }
+ function test_Format_GML_read_polygon_geom(t) {
+ t.plan(7);
+
+ var polygon = shell_start + geoms['polygon'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(polygon);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Polygon GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 1, "first x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 2, "first y coord correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 4, "second x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 5, "second y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 4, "coords length correct");
+ t.eq(data[0].geometry.components.length, 1, "rings length correct");
+ }
+ function test_Format_GML_read_multipoint_geom(t) {
+ t.plan(6);
+
+ var multipoint = shell_start + geoms['multipoint'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(multipoint);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.MultiPoint", "MultiPoint GML returns correct classname");
+ t.eq(data[0].geometry.components[0].x, 1, "x coord correct");
+ t.eq(data[0].geometry.components[0].y, 2, "y coord correct");
+ t.eq(data[0].geometry.components.length, 2, "length correct");
+ t.eq(data[0].geometry.components[1].x, 4, "x coord correct");
+ t.eq(data[0].geometry.components[1].y, 5, "y coord correct");
+ }
+ function test_Format_GML_read_multilinestring_geom(t) {
+ t.plan(6);
+
+ var multilinestring = shell_start + geoms['multilinestring'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(multilinestring);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.MultiLineString", "MultiLineString GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 1, "x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 2, "y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 2, "length correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 4, "x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 5, "y coord correct");
+
+ }
+ function test_Format_GML_read_polygon_with_holes_geom(t) {
+ t.plan(12);
+
+ var polygon_with_holes = shell_start + geoms['polygon_with_holes'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(polygon_with_holes);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Polygon GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 1, "first x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 2, "first y coord correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 4, "second x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 5, "second y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 4, "coords length correct");
+ t.eq(data[0].geometry.components[1].components[0].x, 11, "first x coord correct");
+ t.eq(data[0].geometry.components[1].components[0].y, 12, "first y coord correct");
+ t.eq(data[0].geometry.components[1].components[1].x, 14, "second x coord correct");
+ t.eq(data[0].geometry.components[1].components[1].y, 15, "second y coord correct");
+ t.eq(data[0].geometry.components[1].components.length, 4, "coords length correct");
+ t.eq(data[0].geometry.components.length, 2, "rings length correct");
+ }
+ function test_Format_GML_read_attributes(t) {
+ t.plan(3);
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(test_content[0]);
+ t.eq(data[0].attributes['NAME'], "WY", "Simple Attribute data is read correctly.");
+ t.eq(data[0].attributes['LONGNAME'], "Wyoming", "Attribute data is read from CDATA node correctly.");
+ t.ok(data[0].attributes['EMPTYATTR'] === null, "Attribute set to null for empty element.");
+ }
+ function test_Format_GML_read_envelope_geom(t) {
+ t.plan(13);
+
+ var envelope = shell_start + geoms['envelope'] + shell_end;
+ var parser = new OpenLayers.Format.GML();
+ data = parser.read(envelope);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Envelope GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 0, "first x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 1, "first y coord correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 20, "second x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 1, "second y coord correct");
+ t.eq(data[0].geometry.components[0].components[2].x, 20, "third x coord correct");
+ t.eq(data[0].geometry.components[0].components[2].y, 21, "third y coord correct");
+ t.eq(data[0].geometry.components[0].components[3].x, 0, "fouth x coord correct");
+ t.eq(data[0].geometry.components[0].components[3].y, 21, "fourth y coord correct");
+ t.eq(data[0].geometry.components[0].components[4].x, 0, "fifth x coord correct");
+ t.eq(data[0].geometry.components[0].components[4].y, 1, "fifth y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 5, "coords length correct");
+ t.eq(data[0].geometry.components.length, 1, "rings length correct");
+ }
+ ///////////////////////////////////////////////////////////////
+ // tests the y x order of gml point
+ /////////////////////////////////////////////////////////////
+ function test_Format_GML_read_point_geom_yx(t) {
+ t.plan(3);
+
+ var point = shell_start + geoms_yx['point'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(point);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Point", "Point GML returns correct classname");
+ t.eq(data[0].geometry.x, 1, "x coord correct");
+ t.eq(data[0].geometry.y, 2, "y coord correct");
+ }
+ function test_Format_GML_read_linestring_geom_yx(t) {
+ t.plan(5);
+
+ var line = shell_start + geoms_yx['linestring'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(line);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.LineString", "LineString GML returns correct classname");
+ t.eq(data[0].geometry.components[0].x, 1, "first x coord correct");
+ t.eq(data[0].geometry.components[0].y, 2, "first y coord correct");
+ t.eq(data[0].geometry.components[1].x, 4, "second x coord correct");
+ t.eq(data[0].geometry.components[1].y, 5, "second y coord correct");
+ }
+ function test_Format_GML_read_polygon_geom_yx(t) {
+ t.plan(7);
+
+ var polygon = shell_start + geoms_yx['polygon'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(polygon);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Polygon GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 1, "first x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 2, "first y coord correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 4, "second x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 5, "second y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 4, "coords length correct");
+ t.eq(data[0].geometry.components.length, 1, "rings length correct");
+ }
+ function test_Format_GML_read_multipoint_geom_yx(t) {
+ t.plan(6);
+
+ var multipoint = shell_start + geoms_yx['multipoint'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(multipoint);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.MultiPoint", "MultiPoint GML returns correct classname");
+ t.eq(data[0].geometry.components[0].x, 1, "x coord correct");
+ t.eq(data[0].geometry.components[0].y, 2, "y coord correct");
+ t.eq(data[0].geometry.components.length, 2, "length correct");
+ t.eq(data[0].geometry.components[1].x, 4, "x coord correct");
+ t.eq(data[0].geometry.components[1].y, 5, "y coord correct");
+ }
+ function test_Format_GML_read_multilinestring_geom_yx(t) {
+ t.plan(6);
+
+ var multilinestring = shell_start + geoms_yx['multilinestring'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(multilinestring);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.MultiLineString", "MultiLineString GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 1, "x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 2, "y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 2, "length correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 4, "x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 5, "y coord correct");
+
+ }
+ function test_Format_GML_read_polygon_with_holes_geom_yx(t) {
+ t.plan(12);
+
+ var polygon_with_holes = shell_start + geoms_yx['polygon_with_holes'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(polygon_with_holes);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Polygon GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 1, "first x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 2, "first y coord correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 4, "second x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 5, "second y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 4, "coords length correct");
+ t.eq(data[0].geometry.components[1].components[0].x, 11, "first x coord correct");
+ t.eq(data[0].geometry.components[1].components[0].y, 12, "first y coord correct");
+ t.eq(data[0].geometry.components[1].components[1].x, 14, "second x coord correct");
+ t.eq(data[0].geometry.components[1].components[1].y, 15, "second y coord correct");
+ t.eq(data[0].geometry.components[1].components.length, 4, "coords length correct");
+ t.eq(data[0].geometry.components.length, 2, "rings length correct");
+ }
+
+ function test_Format_GML_read_envelope_geom_yx(t) {
+ t.plan(13);
+
+ var envelope = shell_start + geoms_yx['envelope'] + shell_end;
+ var parser = new OpenLayers.Format.GML({'xy':false});
+ data = parser.read(envelope);
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Envelope GML returns correct classname");
+ t.eq(data[0].geometry.components[0].components[0].x, 0, "first x coord correct");
+ t.eq(data[0].geometry.components[0].components[0].y, 1, "first y coord correct");
+ t.eq(data[0].geometry.components[0].components[1].x, 20, "second x coord correct");
+ t.eq(data[0].geometry.components[0].components[1].y, 1, "second y coord correct");
+ t.eq(data[0].geometry.components[0].components[2].x, 20, "third x coord correct");
+ t.eq(data[0].geometry.components[0].components[2].y, 21, "third y coord correct");
+ t.eq(data[0].geometry.components[0].components[3].x, 0, "fouth x coord correct");
+ t.eq(data[0].geometry.components[0].components[3].y, 21, "fourth y coord correct");
+ t.eq(data[0].geometry.components[0].components[4].x, 0, "fifth x coord correct");
+ t.eq(data[0].geometry.components[0].components[4].y, 1, "fifth y coord correct");
+ t.eq(data[0].geometry.components[0].components.length, 5, "coords length correct");
+ t.eq(data[0].geometry.components.length, 1, "rings length correct");
+ }
+
+ function test_Format_GML_write_geoms_yx(t) {
+ t.plan(5);
+ var parser = new OpenLayers.Format.GML({'xy':false});
+
+ var point_xy = shell_start + serialize_geoms['point'] + shell_end;
+ var point = shell_start + serialize_geoms_yx['point'] + shell_end;
+ data = parser.read(point);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, point_xy, "Point geometry round trips correctly.");
+
+ var linestring_xy = shell_start + serialize_geoms['linestring'] + shell_end;
+ var linestring = shell_start + serialize_geoms_yx['linestring'] + shell_end;
+ data = parser.read(linestring);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, linestring_xy, "Line geometry round trips correctly.");
+
+ var polygon_xy = shell_start + serialize_geoms['polygon'] + shell_end;
+ var polygon = shell_start + serialize_geoms_yx['polygon'] + shell_end;
+ data = parser.read(polygon);
+ var output = parser.write(data);
+ output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, polygon_xy, "Poly geometry round trips correctly.");
+
+ var multipoint_xy = shell_start + serialize_geoms['multipoint'] + shell_end;
+ var multipoint = shell_start + serialize_geoms_yx['multipoint'] + shell_end;
+ data = parser.read(multipoint);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, multipoint_xy, "MultiPoint geometry round trips correctly.");
+
+ var multilinestring_xy = shell_start + serialize_geoms['multilinestring'] + shell_end;
+ var multilinestring = shell_start + serialize_geoms_yx['multilinestring'] + shell_end;
+ data = parser.read(multilinestring);
+ var output = parser.write(data);
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, multilinestring_xy, "MultiLine geometry round trips correctly.");
+ }
+
+ function test_buildGeometryNode_bounds(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.GML();
+ var bounds = new OpenLayers.Bounds(-180, -90, 180, 90);
+ var output, expect;
+
+ // test that bounds are written as gml:Box
+ var output = parser.buildGeometryNode(bounds);
+ var expect = '<gml:Box xmlns:gml="http://www.opengis.net/gml"><gml:coordinates decimal="." cs="," ts=" ">-180,-90 180,90</gml:coordinates></gml:Box>';
+ t.xml_eq(output, expect, "[xy true] Bounds correctly written as gml:Box");
+ }
+
+ function test_parseFeatureBox(t) {
+ t.plan(8);
+ var parser = new OpenLayers.Format.GML();
+ var xmlparser = new OpenLayers.Format.XML();
+
+ var data = xmlparser.read(test_box[0]);
+ var feature = parser.parseFeature(data);
+ t.ok(feature.bounds instanceof OpenLayers.Bounds,
+ "got bounds object for feature.bounds when with boundedBy Box, without geometry");
+ t.eq(feature.geometry, null, 'geometry is null for a feature with boundedBy Box, but no geometry');
+
+ var data = xmlparser.read(test_box[1]);
+ feature = parser.parseFeature(data);
+ t.eq(feature.bounds, null,
+ "feature is null when without boundedBy Box, with Box geometry");
+ t.ok(feature.geometry instanceof OpenLayers.Geometry.Polygon,
+ "got polygon object for feature.geometry when without boundedBy Box, with Box geometry");
+
+ data = xmlparser.read(test_box[2]);
+ feature = parser.parseFeature(data);
+ t.eq(feature.bounds, null,
+ "feature.bounds is null when without boundedBy Box, without geometry");
+ t.eq(feature.geometry, null, 'geometry is null when without boundedBy Box, without geometry');
+
+ data = xmlparser.read(test_box[3]);
+ feature = parser.parseFeature(data);
+ t.ok(feature.bounds instanceof OpenLayers.Bounds,
+ "got bounds object for feature.bounds when with boundedBy Box, with Box geometry");
+ t.ok(feature.geometry instanceof OpenLayers.Geometry.Polygon,
+ "got polygon object for feature.geometry when with boundedBy Box, with Box geometry");
+ }
+
+ var test_box = [
+ // with boundedBy Box, without geometry
+ '<Sentiers_littoraux_feature><gml:boundedBy xmlns:gml="http://www.opengis.net/gml"><gml:Box srsName="EPSG:2154"><gml:coordinates>143564.081753,6817901.121957 144209.641321,6819104.781451</gml:coordinates></gml:Box></gml:boundedBy><DEPARTEMENT>Finistère</DEPARTEMENT></Sentiers_littoraux_feature>',
+ // without boundedBy Box, with Box geometry
+ '<Sentiers_littoraux_feature><msGeometry><gml:Box srsName="EPSG:2154" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates>143564.081753,6817901.121957 144209.641321,6819104.781451</gml:coordinates></gml:Box></msGeometry><DEPARTEMENT>Finistère</DEPARTEMENT></Sentiers_littoraux_feature>',
+ // without boundedBy, without geometry
+ '<Sentiers_littoraux_feature><DEPARTEMENT>Finistère</DEPARTEMENT></Sentiers_littoraux_feature>',
+ // with boundedBy Box, with Box geometry
+ '<Sentiers_littoraux_feature><msGeometry><gml:Box srsName="EPSG:2154" xmlns:gml="http://www.opengis.net/gml"><gml:coordinates>143564.081753,6817901.121957 144209.641321,6819104.781451</gml:coordinates></gml:Box></msGeometry><gml:boundedBy xmlns:gml="http://www.opengis.net/gml"><gml:Box srsName="EPSG:2154"><gml:coordinates>143564.081753,6817901.121957 144209.641321,6819104.781451</gml:coordinates></gml:Box></gml:boundedBy><DEPARTEMENT>Finistère</DEPARTEMENT></Sentiers_littoraux_feature>'];
+
+ var test_content = ['<?xml version="1.0" encoding="utf-8" ?>\n<ogr:FeatureCollection\n xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n xsi:schemaLocation="http://ogr.maptools.org/ testoutput.xsd"\n xmlns:ogr="http://ogr.maptools.org/"\n xmlns:gml="http://www.opengis.net/gml">\n <gml:boundedBy>\n <gml:Box>\n <gml:coord><gml:X>-1254041.389711702</gml:X><gml:Y>250906.9515983529</gml:Y></gml:coord>\n <gml:coord><gml:X>-634517.1199908922</gml:X><gml:Y>762236.2940800377</gml:Y></gml:coord>\n </gml:Box>\n </gml:boundedBy> \n <gml:featureMember>\n <ogr:states fid="F0">\n <ogr:geometryProperty><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-634517.11999089224,691849.77929356066,0 -653761.64509297756,471181.53429472551,0 -673343.60852865304,250906.9515983529,0 -1088825.734430399,299284.85108220269,0 -1254041.3897117018,324729.27754874947,0 -1235750.4212498858,434167.33911316615,0 -1190777.7803201093,704392.96327195223,0 -1181607.835811228,762236.29408003774,0 -634517.11999089224,691849.77929356066,0</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>\n <ogr:NAME>WY</ogr:NAME>\n <ogr:EMPTYATTR/><ogr:LONGNAME><![CDATA[Wyoming]]></ogr:LONGNAME>\n </ogr:states>\n </gml:featureMember>\n</ogr:FeatureCollection>\n',
+ '<wfs:FeatureCollection' +
+ ' xmlns:fs="http://example.com/featureserver"' +
+ ' xmlns:wfs="http://www.opengis.net/wfs"' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+ ' xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd">' +
+ ' ' +
+ '' +
+ ' <gml:featureMember>' +
+ ' \n<fs:scribble fid="221">' +
+ ' <fs:geometry>' +
+ ' <gml:Polygon>' +
+ ' ' +
+ ' <gml:outerBoundaryIs><gml:LinearRing>' +
+ ' <gml:coordinates>149.105072021,-35.1816558838 149.100608826,-35.1844024658 149.098892212,-35.1898956299 149.105072021,-35.1816558838</gml:coordinates>' +
+ ' </gml:LinearRing></gml:outerBoundaryIs>' +
+ ' ' +
+ ' </gml:Polygon>' +
+ ' </fs:geometry>' +
+ ' <fs:title>random test features</fs:title>' +
+ ' </fs:scribble>' +
+ '</gml:featureMember> ' +
+ ' <gml:featureMember><fs:scribble fid="8"> <fs:geometry> <gml:Point><gml:coordinates>-81.38671875,27.0703125</gml:coordinates></gml:Point> </fs:geometry> ' +
+ ' <fs:down>south</fs:down><fs:title>Florida</fs:title> </fs:scribble></gml:featureMember>' +
+ '</wfs:FeatureCollection>'
+ ];
+
+ var shell_start = '<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs"><gml:featureMember xmlns:gml="http://www.opengis.net/gml"><feature:features xmlns:feature="http://mapserver.gis.umn.edu/mapserver" fid="221"><feature:geometry>';
+ if (OpenLayers.BROWSER_NAME == "opera") {
+ shell_start = '<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs"><gml:featureMember xmlns:gml="http://www.opengis.net/gml"><feature:features fid="221" xmlns:feature="http://mapserver.gis.umn.edu/mapserver"><feature:geometry>';
+ }
+ var shell_end = '</feature:geometry></feature:features></gml:featureMember></wfs:FeatureCollection>';
+ var serialize_geoms = {
+ 'point': '<gml:Point><gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates></gml:Point>',
+ 'linestring': '<gml:LineString><gml:coordinates decimal="." cs="," ts=" ">1,2 4,5</gml:coordinates></gml:LineString>',
+ 'polygon': '<gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates decimal="." cs="," ts=" ">1,2 4,5 3,6 1,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>',
+ 'multipoint': '<gml:MultiPoint><gml:pointMember><gml:Point><gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates decimal="." cs="," ts=" ">4,5</gml:coordinates></gml:Point></gml:pointMember></gml:MultiPoint>',
+ 'multilinestring': '<gml:MultiLineString><gml:lineStringMember><gml:LineString><gml:coordinates decimal="." cs="," ts=" ">1,2 4,5</gml:coordinates></gml:LineString></gml:lineStringMember><gml:lineStringMember><gml:LineString><gml:coordinates decimal="." cs="," ts=" ">11,12 14,15</gml:coordinates></gml:LineString></gml:lineStringMember></gml:MultiLineString>'
+ };
+ var geoms = {
+ 'point': '<gml:Point><gml:coordinates>1,2,3</gml:coordinates></gml:Point>',
+ 'linestring': '<gml:LineString><gml:coordinates>1,2,3 4,5,6</gml:coordinates></gml:LineString>',
+ 'polygon': '<gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1,2 4,5 3,6 1,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>',
+ 'polygon_with_holes': '<gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>1,2 4,5 3,6 1,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>11,12 14,15 13,16 11,12</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon>',
+ 'multipoint': '<gml:MultiPoint><gml:Point><gml:coordinates>1,2,3</gml:coordinates></gml:Point><gml:Point><gml:coordinates>4,5,6</gml:coordinates></gml:Point></gml:MultiPoint>',
+ 'multilinestring': '<gml:MultiLineString><gml:LineString><gml:coordinates>1,2,3 4,5,6</gml:coordinates></gml:LineString><gml:LineString><gml:coordinates>11,12,13 14,15,16</gml:coordinates></gml:LineString></gml:MultiLineString>',
+ 'envelope': '<gml:Envelope><gml:lowerCorner>0 1</gml:lowerCorner><gml:upperCorner>20 21</gml:upperCorner></gml:Envelope>' // ,
+ // 'multipolygon_with_holes': '<gml:MultiPolygon><gml:Polygon><gml:outerBoundaryIs>1,2 4,5 3,6 1,2</gml:outerBoundaryIs><gml:innerBoundaryIs>11,12 14,15 13,16 11,12</gml:innerBoundaryIs></gml:Polygon><gml:Polygon><gml:outerBoundaryIs>101,102 104,105 103,106 101,102</gml:outerBoundaryIs><gml:innerBoundaryIs>111,112 114,115 113,116 111,112</gml:innerBoundaryIs></gml:Polygon></gml:MultiPolygon>'
+ };
+
+
+//
+// The following data has the (x y) reordered to (y x), in 3 dimensions it goes from (x y z) to (y x z)
+//
+
+ var serialize_geoms_yx = {
+ 'point': '<gml:Point><gml:coordinates decimal="." cs="," ts=" ">2,1</gml:coordinates></gml:Point>',
+ 'linestring': '<gml:LineString><gml:coordinates decimal="." cs="," ts=" ">2,1 5,4</gml:coordinates></gml:LineString>',
+ 'polygon': '<gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates decimal="." cs="," ts=" ">2,1 5,4 6,3 2,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>',
+ 'multipoint': '<gml:MultiPoint><gml:pointMember><gml:Point><gml:coordinates decimal="." cs="," ts=" ">2,1</gml:coordinates></gml:Point></gml:pointMember><gml:pointMember><gml:Point><gml:coordinates decimal="." cs="," ts=" ">5,4</gml:coordinates></gml:Point></gml:pointMember></gml:MultiPoint>',
+ 'multilinestring': '<gml:MultiLineString><gml:lineStringMember><gml:LineString><gml:coordinates decimal="." cs="," ts=" ">2,1 5,4</gml:coordinates></gml:LineString></gml:lineStringMember><gml:lineStringMember><gml:LineString><gml:coordinates decimal="." cs="," ts=" ">12,11 15,14</gml:coordinates></gml:LineString></gml:lineStringMember></gml:MultiLineString>'
+ };
+ var geoms_yx = {
+ 'point': '<gml:Point><gml:coordinates>2,1,3</gml:coordinates></gml:Point>',
+ 'linestring': '<gml:LineString><gml:coordinates>2,1,3 5,4,6</gml:coordinates></gml:LineString>',
+ 'polygon': '<gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,1 5,4 6,3 2,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>',
+ 'polygon_with_holes': '<gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,1 5,4 6,3 2,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>12,11 15,14 16,13 12,11</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon>',
+ 'multipoint': '<gml:MultiPoint><gml:Point><gml:coordinates>2,1,3</gml:coordinates></gml:Point><gml:Point><gml:coordinates>5,4,6</gml:coordinates></gml:Point></gml:MultiPoint>',
+ 'multilinestring': '<gml:MultiLineString><gml:LineString><gml:coordinates>2,1,3 5,4,6</gml:coordinates></gml:LineString><gml:LineString><gml:coordinates>12,11,13 15,14,16</gml:coordinates></gml:LineString></gml:MultiLineString>',
+ 'envelope': '<gml:Envelope><gml:lowerCorner>1 0</gml:lowerCorner><gml:upperCorner>21 20</gml:upperCorner></gml:Envelope>'
+ };
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/GML/cases.js b/misc/openlayers/tests/Format/GML/cases.js
new file mode 100644
index 0000000..88499cd
--- /dev/null
+++ b/misc/openlayers/tests/Format/GML/cases.js
@@ -0,0 +1,232 @@
+var xml = new OpenLayers.Format.XML();
+function readXML(file) {
+ return xml.read(document.getElementById(file).firstChild.nodeValue);
+}
+
+var cases = {
+
+ "v2/point-coord.xml": new OpenLayers.Geometry.Point(1, 2),
+
+ "v2/point-coordinates.xml": new OpenLayers.Geometry.Point(1, 2),
+
+ "v2/linestring-coord.xml": new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4)
+ ]),
+
+ "v2/linestring-coordinates.xml": new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4)
+ ]),
+
+ "v2/linearring-coord.xml": new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+
+ "v2/linearring-coordinates.xml": new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+
+ "v2/polygon-coord.xml": new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(4, 5),
+ new OpenLayers.Geometry.Point(6, 7),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(7, 8),
+ new OpenLayers.Geometry.Point(3, 4)
+ ])
+ ]),
+
+ "v2/polygon-coordinates.xml": new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(4, 5),
+ new OpenLayers.Geometry.Point(6, 7),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(7, 8),
+ new OpenLayers.Geometry.Point(3, 4)
+ ])
+ ]),
+
+ "v2/multipoint-coord.xml": new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(3, 4)
+ ]),
+
+ "v2/multipoint-coordinates.xml": new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(3, 4)
+ ]),
+
+ "v2/multilinestring-coord.xml": new OpenLayers.Geometry.MultiLineString([
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(4, 5)
+ ])
+ ]),
+
+ "v2/multilinestring-coordinates.xml": new OpenLayers.Geometry.MultiLineString([
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(4, 5)
+ ])
+ ]),
+
+ "v2/multipolygon-coord.xml": new OpenLayers.Geometry.MultiPolygon([
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(4, 5),
+ new OpenLayers.Geometry.Point(6, 7),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(7, 8),
+ new OpenLayers.Geometry.Point(3, 4)
+ ])
+ ]),
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ])
+ ])
+ ]),
+
+ "v2/multipolygon-coordinates.xml": new OpenLayers.Geometry.MultiPolygon([
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(4, 5),
+ new OpenLayers.Geometry.Point(6, 7),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(7, 8),
+ new OpenLayers.Geometry.Point(3, 4)
+ ])
+ ]),
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ])
+ ])
+ ]),
+
+ "v2/geometrycollection-coordinates.xml": new OpenLayers.Geometry.Collection([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4)
+ ]),
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(1, 2)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(2, 3),
+ new OpenLayers.Geometry.Point(4, 5),
+ new OpenLayers.Geometry.Point(6, 7),
+ new OpenLayers.Geometry.Point(2, 3)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(3, 4),
+ new OpenLayers.Geometry.Point(5, 6),
+ new OpenLayers.Geometry.Point(7, 8),
+ new OpenLayers.Geometry.Point(3, 4)
+ ])
+ ])
+ ]),
+
+ "v2/box-coord.xml": new OpenLayers.Bounds(1, 2, 3, 4),
+
+ "v2/box-coordinates.xml": new OpenLayers.Bounds(1, 2, 3, 4),
+
+ "v3/linestring3d.xml": new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2, 3),
+ new OpenLayers.Geometry.Point(4, 5, 6)
+ ])
+
+};
+
+// some cases for v3 use the same geometries
+OpenLayers.Util.extend(cases, {
+ "v3/point.xml": cases["v2/point-coordinates.xml"],
+ "v3/linestring.xml": cases["v2/linestring-coordinates.xml"],
+ "v3/curve.xml": cases["v2/linestring-coordinates.xml"],
+ "v3/polygon.xml": cases["v2/polygon-coordinates.xml"],
+ "v3/surface.xml": cases["v2/polygon-coordinates.xml"],
+ "v3/multipoint-singular.xml": cases["v2/multipoint-coordinates.xml"],
+ "v3/multipoint-plural.xml": cases["v2/multipoint-coordinates.xml"],
+ "v3/multilinestring-singular.xml": cases["v2/multilinestring-coordinates.xml"],
+ "v3/multilinestring-plural.xml": cases["v2/multilinestring-coordinates.xml"],
+ "v3/multicurve-singular.xml": cases["v2/multilinestring-coordinates.xml"],
+ "v3/multicurve-curve.xml": cases["v2/multilinestring-coordinates.xml"],
+ "v3/multipolygon-singular.xml": cases["v2/multipolygon-coordinates.xml"],
+ "v3/multipolygon-plural.xml": cases["v2/multipolygon-coordinates.xml"],
+ "v3/multisurface-singular.xml": cases["v2/multipolygon-coordinates.xml"],
+ "v3/multisurface-plural.xml": cases["v2/multipolygon-coordinates.xml"],
+ "v3/multisurface-surface.xml": cases["v2/multipolygon-coordinates.xml"],
+ "v3/envelope.xml": cases["v2/box-coordinates.xml"]
+}); \ No newline at end of file
diff --git a/misc/openlayers/tests/Format/GML/v2.html b/misc/openlayers/tests/Format/GML/v2.html
new file mode 100644
index 0000000..8857d05
--- /dev/null
+++ b/misc/openlayers/tests/Format/GML/v2.html
@@ -0,0 +1,684 @@
+<?xml version="1.0" encoding="utf-8"?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="cases.js"></script>
+ <script type="text/javascript">
+
+ function test_readNode_geometry(t) {
+ var files = [
+ "v2/point-coord.xml", "v2/point-coordinates.xml",
+ "v2/linestring-coord.xml", "v2/linestring-coordinates.xml",
+ "v2/polygon-coord.xml", "v2/polygon-coordinates.xml",
+ "v2/multipoint-coord.xml", "v2/multipoint-coordinates.xml",
+ "v2/multilinestring-coord.xml", "v2/multilinestring-coordinates.xml",
+ "v2/multipolygon-coord.xml", "v2/multipolygon-coordinates.xml",
+ "v2/geometrycollection-coordinates.xml"
+ ];
+
+ var len = files.length;
+ t.plan(len);
+
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "feature",
+ featureNS: "http://example.com/feature"
+ });
+ var file, doc, expect, out;
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ expect = cases[file];
+ if(expect) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ out = format.readNode(doc.documentElement);
+ if(out.components && out.components.length == 1) {
+ t.geom_eq(out.components[0], expect, "[" + file + "] geometry read");
+ } else {
+ t.fail("[" + file + "] gml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+
+ }
+
+ function test_readNode_bounds(t) {
+ var files = ["v2/box-coord.xml", "v2/box-coordinates.xml"];
+
+ var len = files.length;
+ t.plan(len);
+
+ var file, doc, expect, got;
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "feature",
+ featureNS: "http://example.com/feature"
+ });
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ expect = cases[file];
+ if(expect) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ out = format.readNode(doc.documentElement);
+ if(out.components && out.components.length == 1) {
+ got = out.components[0];
+ if(got instanceof OpenLayers.Bounds) {
+ t.ok(out.components[0].equals(expect), "[" + file + "] bounds read")
+ } else {
+ t.fail("[" + file + "] expected a bounds, got " + got);
+ }
+ } else {
+ t.fail("[" + file + "] gml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+
+ }
+
+ function test_writeNode_geometry(t) {
+ // we only care to write the 'coordinates' variant of GML 2
+ var files = [
+ "v2/point-coordinates.xml",
+ "v2/linestring-coordinates.xml",
+ "v2/polygon-coordinates.xml",
+ "v2/multipoint-coordinates.xml",
+ "v2/multilinestring-coordinates.xml",
+ "v2/multipolygon-coordinates.xml",
+ "v2/geometrycollection-coordinates.xml"
+ ];
+
+ var len = files.length;
+ t.plan(len);
+
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "feature",
+ featureNS: "http://example.com/feature",
+ srsName: "foo" // GML geometry collections require srsName, we only write if provided
+ });
+ var file, geom, doc, node;
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ geom = cases[file];
+ if(geom) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ node = format.writeNode("feature:_geometry", geom);
+ t.xml_eq(node.firstChild, doc.documentElement, "[" + file + "] geometry written");
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+ }
+
+ function test_writeNode_bounds(t) {
+ // we only care to write the 'coordinates' variant of GML 2
+ var files = [
+ "v2/box-coordinates.xml"
+ ];
+
+ var len = files.length;
+ t.plan(len);
+
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "feature",
+ featureNS: "http://example.com/feature",
+ srsName: "foo" // GML box does not require srsName, we only write if provided
+ });
+ var file, bounds, doc, node;
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ bounds = cases[file];
+ if(bounds) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ node = format.writeNode("gml:Box", bounds);
+ t.xml_eq(node, doc.documentElement, "[" + file + "] bounds written");
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+ }
+
+ function test_read(t) {
+ t.plan(8);
+ var doc = readXML("v2/topp-states.xml");
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom"
+ });
+ var features = format.read(doc.documentElement);
+
+ t.eq(features.length, 3, "read 3 features");
+ var feature = features[0];
+ t.eq(feature.fid, "states.1", "read fid");
+ t.eq(feature.geometry.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon",
+ "read multipolygon geometry");
+ var attributes = feature.attributes;
+ t.eq(attributes["STATE_NAME"], "Illinois", "read STATE_NAME");
+ t.eq(attributes["STATE_FIPS"], "17", "read STATE_FIPS");
+ t.eq(attributes["SUB_REGION"], "E N Cen", "read SUB_REGION");
+ t.eq(attributes["STATE_ABBR"], "IL", "read STATE_ABBR");
+ t.eq(attributes["LAND_KM"], "143986.61", "read LAND_KM");
+ }
+
+ function test_read_autoconfig(t) {
+ t.plan(5);
+ var doc = readXML("v2/topp-states.xml");
+ var format = new OpenLayers.Format.GML.v2();
+ var features = format.read(doc.documentElement);
+
+ t.eq(features.length, 3, "read 3 features");
+ var feature = features[0];
+ t.eq(feature.fid, "states.1", "read fid");
+ t.eq(feature.geometry.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon",
+ "read multipolygon geometry");
+ t.eq(format.featureType, "states", "featureType correctly auto-configured");
+ t.eq(format.featureNS, "http://www.openplans.org/topp", "featureNS correctly auto-configured");
+ }
+
+ function test_boundedBy(t) {
+ t.plan(5);
+
+ var doc = readXML("v2/boundedBy.xml");
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom",
+ xy: false
+ });
+ var features = format.read(doc.documentElement);
+ var bounds = features[0].bounds;
+
+ t.ok(bounds instanceof OpenLayers.Bounds, "feature given a bounds");
+ t.eq(bounds.left.toFixed(2), "-91.52", "bounds left correct");
+ t.eq(bounds.bottom.toFixed(2), "36.99", "bounds bottom correct");
+ t.eq(bounds.right.toFixed(2), "-87.51", "bounds right correct");
+ t.eq(bounds.top.toFixed(2), "42.51", "bounds top correct");
+ }
+
+ function test_write(t) {
+ t.plan(1);
+ var doc = readXML("v2/topp-states.xml");
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom",
+ schemaLocation: "http://www.openplans.org/topp http://sigma.openplans.org:80/geoserver/wfs?service=WFS&version=1.0.0&request=DescribeFeatureType&typeName=topp:states http://www.opengis.net/wfs http://sigma.openplans.org:80/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd",
+ srsName: "http://www.opengis.net/gml/srs/epsg.xml#4326"
+ });
+ var features = format.read(doc.documentElement);
+
+ var got = format.write(features);
+ t.xml_eq(got, doc.documentElement, "wfs:FeatureCollection round trip");
+
+ }
+
+ function test_multipleTypenames(t) {
+ t.plan(5);
+ var doc = readXML("v2/multipletypenames.xml");
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: ["LKUNSTWERK", "PKUNSTWERK", "VKUNSTWERK"],
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+ geometry_name: "geometry"
+ });
+ var features = format.read(doc.documentElement);
+ t.eq(features.length, 3, "Expected 3 features from GML containing multiple typenames");
+ t.eq(features[0].type, "VKUNSTWERK", "First feature type is from the VKUNSTWERK typename");
+ t.eq(features[1].type, "LKUNSTWERK", "Second feature type is from the LKUNSTWERK typename");
+ t.eq(features[2].type, "PKUNSTWERK", "Third feature type is from the PKUNSTWERK typename");
+ t.eq(features[0].namespace, "http://mapserver.gis.umn.edu/mapserver", "Namespace is set correctly on feature");
+ }
+
+ function test_noGeom(t) {
+ t.plan(7);
+ var doc = readXML("v2/nogeom.xml");
+ var format = new OpenLayers.Format.GML.v2({
+ featureType: "DEPARTEMENT",
+ featureNS: "http://server.fr/geoserver/loc"
+ });
+ var features = format.read(doc.documentElement);
+ t.eq(features.length, 2, "Expected 2 features from GML with no geom");
+ var feature = features[0];
+ t.ok(feature.geometry == null, "feature 0 has no geometry");
+ var bounds = feature.bounds;
+ t.ok(bounds && (bounds instanceof OpenLayers.Bounds), "feature 0 has been assigned bounds");
+ t.eq(bounds.left, 209565, "bounds left correct");
+ t.eq(bounds.bottom, 6785323, "bounds bottom correct");
+ t.eq(bounds.right, 337568, "bounds right correct");
+ t.eq(bounds.top, 6885985, "bounds top correct");
+ }
+
+ </script>
+</head>
+<body>
+<div id="v2/point-coord.xml"><!--
+<gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+</gml:Point>
+--></div>
+<div id="v2/point-coordinates.xml"><!--
+<gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+</gml:Point>
+--></div>
+<div id="v2/linestring-coord.xml"><!--
+<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+</gml:LineString>
+--></div>
+<div id="v2/linestring-coordinates.xml"><!--
+<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4</gml:coordinates>
+</gml:LineString>
+--></div>
+<div id="v2/polygon-coord.xml"><!--
+<gml:Polygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>5</gml:X>
+ <gml:Y>6</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>4</gml:X>
+ <gml:Y>5</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>6</gml:X>
+ <gml:Y>7</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>5</gml:X>
+ <gml:Y>6</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>7</gml:X>
+ <gml:Y>8</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+</gml:Polygon>
+--></div>
+<div id="v2/polygon-coordinates.xml"><!--
+<gml:Polygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4 5,6 1,2</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">2,3 4,5 6,7 2,3</gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4 5,6 7,8 3,4</gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+</gml:Polygon>
+--></div>
+<div id="v2/multipoint-coord.xml"><!--
+<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ </gml:Point>
+ </gml:pointMember>
+</gml:MultiPoint>
+--></div>
+<div id="v2/multipoint-coordinates.xml"><!--
+<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">2,3</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+</gml:MultiPoint>
+--></div>
+<div id="v2/multilinestring-coord.xml"><!--
+<gml:MultiLineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ </gml:LineString>
+ </gml:lineStringMember>
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>4</gml:X>
+ <gml:Y>5</gml:Y>
+ </gml:coord>
+ </gml:LineString>
+ </gml:lineStringMember>
+</gml:MultiLineString>
+--></div>
+<div id="v2/multilinestring-coordinates.xml"><!--
+<gml:MultiLineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 2,3</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4 4,5</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+</gml:MultiLineString>
+--></div>
+<div id="v2/multipolygon-coord.xml"><!--
+<gml:MultiPolygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>5</gml:X>
+ <gml:Y>6</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>4</gml:X>
+ <gml:Y>5</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>6</gml:X>
+ <gml:Y>7</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>5</gml:X>
+ <gml:Y>6</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>7</gml:X>
+ <gml:Y>8</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>5</gml:X>
+ <gml:Y>6</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+</gml:MultiPolygon>
+--></div>
+<div id="v2/multipolygon-coordinates.xml"><!--
+<gml:MultiPolygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4 5,6 1,2</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">2,3 4,5 6,7 2,3</gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4 5,6 7,8 3,4</gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4 5,6 1,2</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+</gml:MultiPolygon>
+--></div>
+<div id="v2/geometrycollection-coordinates.xml"><!--
+<gml:GeometryCollection xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:geometryMember>
+ <gml:Point srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </gml:geometryMember>
+ <gml:geometryMember>
+ <gml:LineString srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4</gml:coordinates>
+ </gml:LineString>
+ </gml:geometryMember>
+ <gml:geometryMember>
+ <gml:Polygon srsName="foo">
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4 5,6 1,2</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">2,3 4,5 6,7 2,3</gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ <gml:innerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4 5,6 7,8 3,4</gml:coordinates>
+ </gml:LinearRing>
+ </gml:innerBoundaryIs>
+ </gml:Polygon>
+ </gml:geometryMember>
+</gml:GeometryCollection>
+--></div>
+<div id="v2/box-coord.xml"><!--
+<gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+</gml:Box>
+--></div>
+<div id="v2/box-coordinates.xml"><!--
+<gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4</gml:coordinates>
+</gml:Box>
+--></div>
+<div id="v2/linearring-coord.xml"><!--
+<gml:LinearRing xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>5</gml:X>
+ <gml:Y>6</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+</gml:LinearRing>
+--></div>
+<div id="v2/linearring-coordinates.xml"><!--
+<gml:LinearRing xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4 5,6 1,2</gml:coordinates>
+</gml:LinearRing>
+--></div>
+<div id="v2/topp-states.xml"><!--
+<?xml version="1.0" encoding="UTF-8"?><wfs:FeatureCollection xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openplans.org/topp http://sigma.openplans.org:80/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;typeName=topp:states http://www.opengis.net/wfs http://sigma.openplans.org:80/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd"><gml:featureMember><topp:states fid="states.1"><topp:the_geom><gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">37.5101,-88.0711 37.4761,-88.0871 37.4421,-88.3111 37.4091,-88.3591 37.4201,-88.4191 37.4001,-88.4671 37.2961,-88.5111 37.2571,-88.5011 37.2051,-88.4501 37.1561,-88.4221 37.0981,-88.4501 37.0721,-88.4761 37.0681,-88.4901 37.0641,-88.5171 37.0721,-88.5591 37.1091,-88.6141 37.1351,-88.6881 37.1411,-88.7391 37.1521,-88.7461 37.2021,-88.8631 37.2181,-88.9321 37.2201,-88.9931 37.1851,-89.0651 37.1121,-89.1161 37.0931,-89.1461 37.0641,-89.1691 37.0251,-89.1741 36.9981,-89.1501 36.9881,-89.1291 36.9861,-89.1931 37.0281,-89.2101 37.0411,-89.2371 37.0871,-89.2641 37.0911,-89.2841 37.0851,-89.3031 37.0601,-89.3091 37.0271,-89.2641 37.0081,-89.2621 36.9991,-89.2821 37.0091,-89.3101 37.0491,-89.3821 37.0991,-89.3791 37.1371,-89.4231 37.1651,-89.4401 37.2241,-89.4681 37.2531,-89.4651 37.2561,-89.4891 37.2761,-89.5131 37.3041,-89.5131 37.3291,-89.5001 37.3391,-89.4681 37.3551,-89.4351 37.4111,-89.4271 37.4531,-89.4531 37.4911,-89.4941 37.5711,-89.5241 37.6151,-89.5131 37.6501,-89.5191 37.6791,-89.5131 37.6941,-89.5211 37.7061,-89.5811 37.7451,-89.6661 37.7831,-89.6751 37.8041,-89.6911 37.8401,-89.7281 37.9051,-89.8511 37.9051,-89.8611 37.8911,-89.8661 37.8751,-89.9001 37.8781,-89.9371 37.9111,-89.9781 37.9631,-89.9581 37.9691,-90.0101 37.9931,-90.0411 38.0321,-90.1191 38.0531,-90.1341 38.0881,-90.2071 38.1221,-90.2541 38.1661,-90.2891 38.1881,-90.3361 38.2341,-90.3641 38.3231,-90.3691 38.3651,-90.3581 38.3901,-90.3391 38.4271,-90.3011 38.5181,-90.2651 38.5321,-90.2611 38.5621,-90.2401 38.6101,-90.1831 38.6581,-90.1831 38.7001,-90.2021 38.7231,-90.1961 38.7731,-90.1631 38.7851,-90.1351 38.8001,-90.1211 38.8301,-90.1131 38.8531,-90.1321 38.9141,-90.2431 38.9241,-90.2781 38.9241,-90.3191 38.9621,-90.4131 38.9591,-90.4691 38.8911,-90.5301 38.8711,-90.5701 38.8801,-90.6271 38.9351,-90.6681 39.0371,-90.7061 39.0581,-90.7071 39.0931,-90.6901 39.1441,-90.7161 39.1951,-90.7181 39.2241,-90.7321 39.2471,-90.7381 39.2961,-90.7791 39.3501,-90.8501 39.4001,-90.9471 39.4441,-91.0361 39.4731,-91.0641 39.5281,-91.0931 39.5521,-91.1561 39.6001,-91.2031 39.6851,-91.3171 39.7241,-91.3671 39.7611,-91.3731 39.8031,-91.3811 39.8631,-91.4491 39.8851,-91.4501 39.9011,-91.4341 39.9211,-91.4301 39.9461,-91.4471 40.0051,-91.4871 40.0661,-91.5041 40.1341,-91.5161 40.2001,-91.5061 40.2511,-91.4981 40.3091,-91.4861 40.3711,-91.4481 40.3861,-91.4181 40.3921,-91.3851 40.4021,-91.3721 40.4471,-91.3851 40.5031,-91.3741 40.5281,-91.3821 40.5471,-91.4121 40.5721,-91.4111 40.6031,-91.3751 40.6391,-91.2621 40.6431,-91.2141 40.6561,-91.1621 40.6821,-91.1291 40.7051,-91.1191 40.7611,-91.0921 40.8331,-91.0881 40.8791,-91.0491 40.9231,-90.9831 40.9501,-90.9601 41.0701,-90.9541 41.1041,-90.9571 41.1441,-90.9901 41.1651,-91.0181 41.1761,-91.0561 41.2311,-91.1011 41.2671,-91.1021 41.3341,-91.0731 41.4011,-91.0551 41.4231,-91.0271 41.4311,-91.0001 41.4211,-90.9491 41.4441,-90.8441 41.4491,-90.7791 41.4501,-90.7081 41.4621,-90.6581 41.5091,-90.6001 41.5251,-90.5401 41.5271,-90.4541 41.5431,-90.4341 41.5671,-90.4231 41.5861,-90.3481 41.6021,-90.3391 41.6491,-90.3411 41.7221,-90.3261 41.7561,-90.3041 41.7811,-90.2551 41.8061,-90.1951 41.9301,-90.1541 41.9831,-90.1421 42.0331,-90.1501 42.0611,-90.1681 42.1031,-90.1661 42.1201,-90.1761 42.1221,-90.1911 42.1591,-90.2301 42.1971,-90.3231 42.2101,-90.3671 42.2421,-90.4071 42.2631,-90.4171 42.3401,-90.4271 42.3601,-90.4411 42.3881,-90.4911 42.4211,-90.5631 42.4601,-90.6051 42.4751,-90.6481 42.4941,-90.6511 42.5091,-90.6381 42.5081,-90.4191 42.5041,-89.9231 42.5031,-89.8341 42.4971,-89.4001 42.4971,-89.3591 42.4901,-88.9391 42.4901,-88.7641 42.4891,-88.7061 42.4911,-88.2971 42.4891,-88.1941 42.4891,-87.7971 42.3141,-87.8361 42.1561,-87.7601 42.0591,-87.6701 41.8471,-87.6121 41.7231,-87.5291 41.4691,-87.5321 41.3011,-87.5321 41.1731,-87.5311 41.0091,-87.5321 40.7451,-87.5321 40.4941,-87.5371 40.4831,-87.5351 40.1661,-87.5351 39.8871,-87.5351 39.6091,-87.5351 39.4771,-87.5381 39.3501,-87.5401 39.3381,-87.5971 39.3071,-87.6251 39.2971,-87.6101 39.2811,-87.6151 39.2581,-87.6061 39.2481,-87.5841 39.2081,-87.5881 39.1981,-87.5941 39.1961,-87.6071 39.1681,-87.6441 39.1461,-87.6701 39.1301,-87.6591 39.1131,-87.6621 39.1031,-87.6311 39.0881,-87.6301 39.0841,-87.6121 39.0621,-87.5851 38.9951,-87.5811 38.9941,-87.5911 38.9771,-87.5471 38.9631,-87.5331 38.9311,-87.5301 38.9041,-87.5391 38.8691,-87.5591 38.8571,-87.5501 38.7951,-87.5071 38.7761,-87.5191 38.7691,-87.5081 38.7361,-87.5081 38.6851,-87.5431 38.6721,-87.5881 38.6421,-87.6251 38.6221,-87.6281 38.5991,-87.6191 38.5931,-87.6401 38.5731,-87.6521 38.5471,-87.6721 38.5151,-87.6511 38.5001,-87.6531 38.5041,-87.6791 38.4811,-87.6921 38.4661,-87.7561 38.4571,-87.7581 38.4451,-87.7381 38.4171,-87.7481 38.3781,-87.7841 38.3521,-87.8341 38.2861,-87.8501 38.2851,-87.8631 38.3161,-87.8741 38.3151,-87.8831 38.3001,-87.8881 38.2811,-87.9141 38.3021,-87.9131 38.3041,-87.9251 38.2411,-87.9801 38.2341,-87.9861 38.2001,-87.9771 38.1711,-87.9321 38.1571,-87.9311 38.1361,-87.9501 38.1311,-87.9731 38.1031,-88.0181 38.0921,-88.0121 38.0961,-87.9641 38.0731,-87.9751 38.0541,-88.0341 38.0451,-88.0431 38.0381,-88.0411 38.0331,-88.0211 38.0081,-88.0291 37.9751,-88.0211 37.9561,-88.0421 37.9341,-88.0411 37.9291,-88.0641 37.944,-88.0781 37.9231,-88.084 37.9171,-88.0301 37.9051,-88.0261 37.8961,-88.0441 37.9061,-88.1001 37.8951,-88.1011 37.8671,-88.0751 37.8431,-88.0341 37.8271,-88.0421 37.8311,-88.0891 37.8171,-88.0861 37.8051,-88.0351 37.7351,-88.0721 37.7001,-88.1331 37.6601,-88.1591 37.6281,-88.1571 37.5831,-88.1341 37.5101,-88.0711</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></topp:the_geom><topp:STATE_NAME>Illinois</topp:STATE_NAME><topp:STATE_FIPS>17</topp:STATE_FIPS><topp:SUB_REGION>E N Cen</topp:SUB_REGION><topp:STATE_ABBR>IL</topp:STATE_ABBR><topp:LAND_KM>143986.61</topp:LAND_KM><topp:WATER_KM>1993.335</topp:WATER_KM><topp:PERSONS>1.1431E7</topp:PERSONS><topp:FAMILIES>2924880.0</topp:FAMILIES><topp:HOUSHOLD>4202240.0</topp:HOUSHOLD><topp:MALE>5552233.0</topp:MALE><topp:FEMALE>5878369.0</topp:FEMALE><topp:WORKERS>4199206.0</topp:WORKERS><topp:DRVALONE>3741715.0</topp:DRVALONE><topp:CARPOOL>652603.0</topp:CARPOOL><topp:PUBTRANS>538071.0</topp:PUBTRANS><topp:EMPLOYED>5417967.0</topp:EMPLOYED><topp:UNEMPLOY>385040.0</topp:UNEMPLOY><topp:SERVICE>1360159.0</topp:SERVICE><topp:MANUAL>828906.0</topp:MANUAL><topp:P_MALE>0.486</topp:P_MALE><topp:P_FEMALE>0.514</topp:P_FEMALE><topp:SAMP_POP>1747776.0</topp:SAMP_POP></topp:states></gml:featureMember><gml:featureMember><topp:states fid="states.2"><topp:the_geom><gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">38.9661,-77.0081 38.8891,-76.9111 38.7881,-77.0451 38.8131,-77.0351 38.8291,-77.0451 38.8381,-77.0401 38.8621,-77.0391 38.8861,-77.0671 38.9151,-77.0781 38.9321,-77.1221 38.9931,-77.0421 38.9661,-77.0081</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></topp:the_geom><topp:STATE_NAME>District of Columbia</topp:STATE_NAME><topp:STATE_FIPS>11</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>DC</topp:STATE_ABBR><topp:LAND_KM>159.055</topp:LAND_KM><topp:WATER_KM>17.991</topp:WATER_KM><topp:PERSONS>606900.0</topp:PERSONS><topp:FAMILIES>122087.0</topp:FAMILIES><topp:HOUSHOLD>249634.0</topp:HOUSHOLD><topp:MALE>282970.0</topp:MALE><topp:FEMALE>323930.0</topp:FEMALE><topp:WORKERS>229975.0</topp:WORKERS><topp:DRVALONE>106694.0</topp:DRVALONE><topp:CARPOOL>36621.0</topp:CARPOOL><topp:PUBTRANS>111422.0</topp:PUBTRANS><topp:EMPLOYED>303994.0</topp:EMPLOYED><topp:UNEMPLOY>23442.0</topp:UNEMPLOY><topp:SERVICE>65498.0</topp:SERVICE><topp:MANUAL>22407.0</topp:MANUAL><topp:P_MALE>0.466</topp:P_MALE><topp:P_FEMALE>0.534</topp:P_FEMALE><topp:SAMP_POP>72696.0</topp:SAMP_POP></topp:states></gml:featureMember><gml:featureMember><topp:states fid="states.3"><topp:the_geom><gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">38.5571,-75.7071 38.6491,-75.7111 38.8301,-75.7241 39.1411,-75.7521 39.2471,-75.7611 39.2951,-75.7641 39.3831,-75.7721 39.7231,-75.7911 39.7241,-75.7751 39.7741,-75.7451 39.8201,-75.6951 39.8381,-75.6441 39.8401,-75.5831 39.8261,-75.4701 39.7981,-75.4201 39.7891,-75.4121 39.7781,-75.4281 39.7631,-75.4601 39.7411,-75.4751 39.7191,-75.4761 39.7141,-75.4891 39.6121,-75.6101 39.5661,-75.5621 39.4631,-75.5901 39.3661,-75.5151 39.2571,-75.4021 39.0731,-75.3971 39.0121,-75.3241 38.9451,-75.3071 38.8081,-75.1901 38.7991,-75.0831 38.4491,-75.0451 38.4491,-75.0681 38.4501,-75.0931 38.4551,-75.3501 38.4631,-75.6991 38.5571,-75.7071</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></topp:the_geom><topp:STATE_NAME>Delaware</topp:STATE_NAME><topp:STATE_FIPS>10</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>DE</topp:STATE_ABBR><topp:LAND_KM>5062.456</topp:LAND_KM><topp:WATER_KM>1385.022</topp:WATER_KM><topp:PERSONS>666168.0</topp:PERSONS><topp:FAMILIES>175867.0</topp:FAMILIES><topp:HOUSHOLD>247497.0</topp:HOUSHOLD><topp:MALE>322968.0</topp:MALE><topp:FEMALE>343200.0</topp:FEMALE><topp:WORKERS>247566.0</topp:WORKERS><topp:DRVALONE>258087.0</topp:DRVALONE><topp:CARPOOL>42968.0</topp:CARPOOL><topp:PUBTRANS>8069.0</topp:PUBTRANS><topp:EMPLOYED>335147.0</topp:EMPLOYED><topp:UNEMPLOY>13945.0</topp:UNEMPLOY><topp:SERVICE>87973.0</topp:SERVICE><topp:MANUAL>44140.0</topp:MANUAL><topp:P_MALE>0.485</topp:P_MALE><topp:P_FEMALE>0.515</topp:P_FEMALE><topp:SAMP_POP>102776.0</topp:SAMP_POP></topp:states></gml:featureMember></wfs:FeatureCollection>
+--></div>
+<div id="v2/boundedBy.xml"><!--
+<?xml version="1.0" encoding="UTF-8"?><wfs:FeatureCollection xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openplans.org/topp http://publicus.opengeo.org:80/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;typeName=topp:states http://www.opengis.net/wfs http://publicus.opengeo.org:80/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd"><gml:featureMember><topp:states fid="states.1"><gml:boundedBy><gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">36.9861,-91.5161 42.5091,-87.5071</gml:coordinates></gml:Box></gml:boundedBy><topp:the_geom><gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326"><gml:polygonMember><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">37.5101,-88.0711 37.4761,-88.0871 37.4421,-88.3111 37.4091,-88.3591 37.4201,-88.4191 37.4001,-88.4671 37.2961,-88.5111 37.2571,-88.5011 37.2051,-88.4501 37.1561,-88.4221 37.0981,-88.4501 37.0721,-88.4761 37.0681,-88.4901 37.0641,-88.5171 37.0721,-88.5591 37.1091,-88.6141 37.1351,-88.6881 37.1411,-88.7391 37.1521,-88.7461 37.2021,-88.8631 37.2181,-88.9321 37.2201,-88.9931 37.1851,-89.0651 37.1121,-89.1161 37.0931,-89.1461 37.0641,-89.1691 37.0251,-89.1741 36.9981,-89.1501 36.9881,-89.1291 36.9861,-89.1931 37.0281,-89.2101 37.0411,-89.2371 37.0871,-89.2641 37.0911,-89.2841 37.0851,-89.3031 37.0601,-89.3091 37.0271,-89.2641 37.0081,-89.2621 36.9991,-89.2821 37.0091,-89.3101 37.0491,-89.3821 37.0991,-89.3791 37.1371,-89.4231 37.1651,-89.4401 37.2241,-89.4681 37.2531,-89.4651 37.2561,-89.4891 37.2761,-89.5131 37.3041,-89.5131 37.3291,-89.5001 37.3391,-89.4681 37.3551,-89.4351 37.4111,-89.4271 37.4531,-89.4531 37.4911,-89.4941 37.5711,-89.5241 37.6151,-89.5131 37.6501,-89.5191 37.6791,-89.5131 37.6941,-89.5211 37.7061,-89.5811 37.7451,-89.6661 37.7831,-89.6751 37.8041,-89.6911 37.8401,-89.7281 37.9051,-89.8511 37.9051,-89.8611 37.8911,-89.8661 37.8751,-89.9001 37.8781,-89.9371 37.9111,-89.9781 37.9631,-89.9581 37.9691,-90.0101 37.9931,-90.0411 38.0321,-90.1191 38.0531,-90.1341 38.0881,-90.2071 38.1221,-90.2541 38.1661,-90.2891 38.1881,-90.3361 38.2341,-90.3641 38.3231,-90.3691 38.3651,-90.3581 38.3901,-90.3391 38.4271,-90.3011 38.5181,-90.2651 38.5321,-90.2611 38.5621,-90.2401 38.6101,-90.1831 38.6581,-90.1831 38.7001,-90.2021 38.7231,-90.1961 38.7731,-90.1631 38.7851,-90.1351 38.8001,-90.1211 38.8301,-90.1131 38.8531,-90.1321 38.9141,-90.2431 38.9241,-90.2781 38.9241,-90.3191 38.9621,-90.4131 38.9591,-90.4691 38.8911,-90.5301 38.8711,-90.5701 38.8801,-90.6271 38.9351,-90.6681 39.0371,-90.7061 39.0581,-90.7071 39.0931,-90.6901 39.1441,-90.7161 39.1951,-90.7181 39.2241,-90.7321 39.2471,-90.7381 39.2961,-90.7791 39.3501,-90.8501 39.4001,-90.9471 39.4441,-91.0361 39.4731,-91.0641 39.5281,-91.0931 39.5521,-91.1561 39.6001,-91.2031 39.6851,-91.3171 39.7241,-91.3671 39.7611,-91.3731 39.8031,-91.3811 39.8631,-91.4491 39.8851,-91.4501 39.9011,-91.4341 39.9211,-91.4301 39.9461,-91.4471 40.0051,-91.4871 40.0661,-91.5041 40.1341,-91.5161 40.2001,-91.5061 40.2511,-91.4981 40.3091,-91.4861 40.3711,-91.4481 40.3861,-91.4181 40.3921,-91.3851 40.4021,-91.3721 40.4471,-91.3851 40.5031,-91.3741 40.5281,-91.3821 40.5471,-91.4121 40.5721,-91.4111 40.6031,-91.3751 40.6391,-91.2621 40.6431,-91.2141 40.6561,-91.1621 40.6821,-91.1291 40.7051,-91.1191 40.7611,-91.0921 40.8331,-91.0881 40.8791,-91.0491 40.9231,-90.9831 40.9501,-90.9601 41.0701,-90.9541 41.1041,-90.9571 41.1441,-90.9901 41.1651,-91.0181 41.1761,-91.0561 41.2311,-91.1011 41.2671,-91.1021 41.3341,-91.0731 41.4011,-91.0551 41.4231,-91.0271 41.4311,-91.0001 41.4211,-90.9491 41.4441,-90.8441 41.4491,-90.7791 41.4501,-90.7081 41.4621,-90.6581 41.5091,-90.6001 41.5251,-90.5401 41.5271,-90.4541 41.5431,-90.4341 41.5671,-90.4231 41.5861,-90.3481 41.6021,-90.3391 41.6491,-90.3411 41.7221,-90.3261 41.7561,-90.3041 41.7811,-90.2551 41.8061,-90.1951 41.9301,-90.1541 41.9831,-90.1421 42.0331,-90.1501 42.0611,-90.1681 42.1031,-90.1661 42.1201,-90.1761 42.1221,-90.1911 42.1591,-90.2301 42.1971,-90.3231 42.2101,-90.3671 42.2421,-90.4071 42.2631,-90.4171 42.3401,-90.4271 42.3601,-90.4411 42.3881,-90.4911 42.4211,-90.5631 42.4601,-90.6051 42.4751,-90.6481 42.4941,-90.6511 42.5091,-90.6381 42.5081,-90.4191 42.5041,-89.9231 42.5031,-89.8341 42.4971,-89.4001 42.4971,-89.3591 42.4901,-88.9391 42.4901,-88.7641 42.4891,-88.7061 42.4911,-88.2971 42.4891,-88.1941 42.4891,-87.7971 42.3141,-87.8361 42.1561,-87.7601 42.0591,-87.6701 41.8471,-87.6121 41.7231,-87.5291 41.4691,-87.5321 41.3011,-87.5321 41.1731,-87.5311 41.0091,-87.5321 40.7451,-87.5321 40.4941,-87.5371 40.4831,-87.5351 40.1661,-87.5351 39.8871,-87.5351 39.6091,-87.5351 39.4771,-87.5381 39.3501,-87.5401 39.3381,-87.5971 39.3071,-87.6251 39.2971,-87.6101 39.2811,-87.6151 39.2581,-87.6061 39.2481,-87.5841 39.2081,-87.5881 39.1981,-87.5941 39.1961,-87.6071 39.1681,-87.6441 39.1461,-87.6701 39.1301,-87.6591 39.1131,-87.6621 39.1031,-87.6311 39.0881,-87.6301 39.0841,-87.6121 39.0621,-87.5851 38.9951,-87.5811 38.9941,-87.5911 38.9771,-87.5471 38.9631,-87.5331 38.9311,-87.5301 38.9041,-87.5391 38.8691,-87.5591 38.8571,-87.5501 38.7951,-87.5071 38.7761,-87.5191 38.7691,-87.5081 38.7361,-87.5081 38.6851,-87.5431 38.6721,-87.5881 38.6421,-87.6251 38.6221,-87.6281 38.5991,-87.6191 38.5931,-87.6401 38.5731,-87.6521 38.5471,-87.6721 38.5151,-87.6511 38.5001,-87.6531 38.5041,-87.6791 38.4811,-87.6921 38.4661,-87.7561 38.4571,-87.7581 38.4451,-87.7381 38.4171,-87.7481 38.3781,-87.7841 38.3521,-87.8341 38.2861,-87.8501 38.2851,-87.8631 38.3161,-87.8741 38.3151,-87.8831 38.3001,-87.8881 38.2811,-87.9141 38.3021,-87.9131 38.3041,-87.9251 38.2411,-87.9801 38.2341,-87.9861 38.2001,-87.9771 38.1711,-87.9321 38.1571,-87.9311 38.1361,-87.9501 38.1311,-87.9731 38.1031,-88.0181 38.0921,-88.0121 38.0961,-87.9641 38.0731,-87.9751 38.0541,-88.0341 38.0451,-88.0431 38.0381,-88.0411 38.0331,-88.0211 38.0081,-88.0291 37.9751,-88.0211 37.9561,-88.0421 37.9341,-88.0411 37.9291,-88.0641 37.944,-88.0781 37.9231,-88.084 37.9171,-88.0301 37.9051,-88.0261 37.8961,-88.0441 37.9061,-88.1001 37.8951,-88.1011 37.8671,-88.0751 37.8431,-88.0341 37.8271,-88.0421 37.8311,-88.0891 37.8171,-88.0861 37.8051,-88.0351 37.7351,-88.0721 37.7001,-88.1331 37.6601,-88.1591 37.6281,-88.1571 37.5831,-88.1341 37.5101,-88.0711</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></gml:polygonMember></gml:MultiPolygon></topp:the_geom><topp:STATE_NAME>Illinois</topp:STATE_NAME><topp:STATE_FIPS>17</topp:STATE_FIPS><topp:SUB_REGION>E N Cen</topp:SUB_REGION><topp:STATE_ABBR>IL</topp:STATE_ABBR><topp:LAND_KM>143986.61</topp:LAND_KM><topp:WATER_KM>1993.335</topp:WATER_KM><topp:PERSONS>1.1431E7</topp:PERSONS><topp:FAMILIES>2924880.0</topp:FAMILIES><topp:HOUSHOLD>4202240.0</topp:HOUSHOLD><topp:MALE>5552233.0</topp:MALE><topp:FEMALE>5878369.0</topp:FEMALE><topp:WORKERS>4199206.0</topp:WORKERS><topp:DRVALONE>3741715.0</topp:DRVALONE><topp:CARPOOL>652603.0</topp:CARPOOL><topp:PUBTRANS>538071.0</topp:PUBTRANS><topp:EMPLOYED>5417967.0</topp:EMPLOYED><topp:UNEMPLOY>385040.0</topp:UNEMPLOY><topp:SERVICE>1360159.0</topp:SERVICE><topp:MANUAL>828906.0</topp:MANUAL><topp:P_MALE>0.486</topp:P_MALE><topp:P_FEMALE>0.514</topp:P_FEMALE><topp:SAMP_POP>1747776.0</topp:SAMP_POP></topp:states></gml:featureMember></wfs:FeatureCollection>
+--></div>
+<div id="v2/multipletypenames.xml"><!--
+<?xml version='1.0' encoding="ISO-8859-1" ?><wfs:FeatureCollection xmlns:rws="http://mapserver.gis.umn.edu/mapserver" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-basic.xsd http://mapserver.gis.umn.edu/mapserver http://intranet.rijkswaterstaat.nl/services/geoservices/kerngisnat_utre?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=VKUNSTWERK,LKUNSTWERK,PKUNSTWERK&amp;OUTPUTFORMAT=XMLSCHEMA"> <gml:boundedBy> <gml:Box srsName="EPSG:28992"> <gml:coordinates>134503.789000,455332.337000 135149.909000,455893.926000</gml:coordinates> </gml:Box> </gml:boundedBy> <gml:featureMember> <rws:VKUNSTWERK fid="VKUNSTWERK.16"> <gml:boundedBy> <gml:Box srsName="EPSG:28992"> <gml:coordinates>134949.571000,455438.845000 134978.799000,455471.762000</gml:coordinates> </gml:Box> </gml:boundedBy> <rws:geometry> <gml:MultiPolygon srsName="EPSG:28992"> <gml:polygonMember> <gml:Polygon> <gml:outerBoundaryIs> <gml:LinearRing> <gml:coordinates>134974.191000,455471.587000 134973.974000,455471.762000 134973.558000,455471.248000 134973.579000,455471.230000 134963.143000,455458.768000 134962.787000,455458.653000 134960.514000,455456.003000 134960.440000,455455.539000 134950.207000,455443.320000 134950.158000,455443.360000 134949.571000,455442.638000 134949.810000,455442.462000 134951.417000,455441.223000 134951.435000,455441.209000 134954.158000,455439.108000 134954.507000,455438.845000 134955.000000,455439.420000 134954.954000,455439.458000 134965.046000,455451.520000 134965.568000,455451.606000 134968.159000,455454.642000 134968.120000,455455.195000 134978.294000,455467.355000 134978.330000,455467.326000 134978.799000,455467.881000 134978.598000,455468.042000 134975.885000,455470.224000 134974.191000,455471.587000 </gml:coordinates> </gml:LinearRing> </gml:outerBoundaryIs> <gml:innerBoundaryIs> <gml:LinearRing> <gml:coordinates>134960.590000,455455.163000 134963.589000,455458.755000 134973.756000,455470.929000 134973.836000,455471.019000 134974.216000,455471.445000 134975.807000,455470.163000 134978.485000,455468.005000 134978.077000,455467.534000 134978.015000,455467.462000 134967.969000,455455.479000 134964.782000,455451.678000 134954.705000,455439.660000 134954.622000,455439.561000 134954.271000,455439.152000 134951.498000,455441.284000 134949.973000,455442.456000 134950.452000,455443.023000 134950.501000,455443.081000 134960.590000,455455.163000 </gml:coordinates> </gml:LinearRing> </gml:innerBoundaryIs> </gml:Polygon> </gml:polygonMember> </gml:MultiPolygon> </rws:geometry> <rws:OBJECTID>16</rws:OBJECTID> <rws:OBJECTSUBCATEGORIE>31</rws:OBJECTSUBCATEGORIE> </rws:VKUNSTWERK> </gml:featureMember> <gml:featureMember> <rws:LKUNSTWERK fid="LKUNSTWERK.14"> <gml:boundedBy> <gml:Box srsName="EPSG:28992"> <gml:coordinates>135080.966000,455332.337000 135149.909000,455390.384000</gml:coordinates> </gml:Box> </gml:boundedBy> <rws:geometry> <gml:MultiLineString srsName="EPSG:28992"> <gml:lineStringMember> <gml:LineString> <gml:coordinates>135080.966000,455390.384000 135096.654000,455377.009000 135109.082000,455366.755000 135122.769000,455355.276000 135141.565000,455339.633000 135149.909000,455332.337000 </gml:coordinates> </gml:LineString> </gml:lineStringMember> </gml:MultiLineString> </rws:geometry> <rws:OBJECTID>14</rws:OBJECTID> <rws:OBJECTSUBCATEGORIE>30</rws:OBJECTSUBCATEGORIE> </rws:LKUNSTWERK> </gml:featureMember> <gml:featureMember> <rws:PKUNSTWERK fid="PKUNSTWERK.29"> <gml:boundedBy> <gml:Box srsName="EPSG:28992"> <gml:coordinates>134832.017000,455596.187000 134832.017000,455596.187000</gml:coordinates> </gml:Box> </gml:boundedBy> <rws:geometry> <gml:MultiPoint srsName="EPSG:28992"> <gml:pointMember> <gml:Point> <gml:coordinates>134832.017000,455596.187000</gml:coordinates> </gml:Point> </gml:pointMember> </gml:MultiPoint> </rws:geometry> <rws:OBJECTID>29</rws:OBJECTID> <rws:OBJECTSUBCATEGORIE>30</rws:OBJECTSUBCATEGORIE> </rws:PKUNSTWERK> </gml:featureMember></wfs:FeatureCollection>
+--></div>
+<div id="v2/nogeom.xml"><!--
+<?xml version="1.0" encoding="UTF-8"?><wfs:FeatureCollection xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:loc="http://server.fr/geoserver/loc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://server.fr:80/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd http://server.fr/geoserver/loc http://server.fr:80/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;typeName=loc:DEPARTEMENT"><gml:boundedBy><gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#2154"><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">199373,6704170 337568,6885985</gml:coordinates></gml:Box></gml:boundedBy><gml:featureMember><loc:DEPARTEMENT fid="DEPARTEMENT.1"><gml:boundedBy><gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#2154"><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">209565,6785323 337568,6885985</gml:coordinates></gml:Box></gml:boundedBy><loc:NOM_DEPT>COTES-D'ARMOR</loc:NOM_DEPT></loc:DEPARTEMENT></gml:featureMember><gml:featureMember><loc:DEPARTEMENT fid="DEPARTEMENT.3"><gml:boundedBy><gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#2154"><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">199373,6704170 323518,6807542</gml:coordinates></gml:Box></gml:boundedBy><loc:NOM_DEPT>MORBIHAN</loc:NOM_DEPT></loc:DEPARTEMENT></gml:featureMember></wfs:FeatureCollection>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/GML/v3.html b/misc/openlayers/tests/Format/GML/v3.html
new file mode 100644
index 0000000..92f2154
--- /dev/null
+++ b/misc/openlayers/tests/Format/GML/v3.html
@@ -0,0 +1,828 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="cases.js"></script>
+ <script type="text/javascript">
+
+ function test_readNode_geometry(t) {
+ var files = [
+ "v2/point-coord.xml", "v2/point-coordinates.xml",
+ "v2/linestring-coord.xml", "v2/linestring-coordinates.xml",
+ "v2/multipoint-coord.xml", "v2/multipoint-coordinates.xml",
+ "v2/multilinestring-coord.xml", "v2/multilinestring-coordinates.xml",
+ "v3/point.xml", "v3/linestring.xml", "v3/linestring3d.xml",
+ "v3/curve.xml", "v3/polygon.xml", "v3/surface.xml",
+ "v3/multipoint-singular.xml", "v3/multipoint-plural.xml",
+ "v3/multilinestring-singular.xml", "v3/multilinestring-plural.xml",
+ "v3/multicurve-singular.xml", "v3/multicurve-curve.xml",
+ "v3/multipolygon-singular.xml", "v3/multipolygon-plural.xml",
+ "v3/multisurface-singular.xml", "v3/multisurface-plural.xml",
+ "v3/multisurface-surface.xml"
+ ];
+
+ var len = files.length;
+ t.plan(len);
+
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "feature",
+ featureNS: "http://example.com/feature"
+ });
+ var file, doc, expect, out;
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ expect = cases[file];
+ if(expect) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ out = format.readNode(doc.documentElement);
+ if(out.components && out.components.length == 1) {
+ t.geom_eq(out.components[0], expect, "[" + file + "] geometry read");
+ } else {
+ t.fail("[" + file + "] gml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+
+ }
+
+ function test_read_setGeometryName(t) {
+ t.plan(1);
+ var doc = readXML("v3/topp-states-gml.xml");
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: null
+ });
+ var features = format.read(doc.documentElement);
+
+ t.eq(format.geometryName, "the_geom", "geometryName set when parsing features");
+ }
+
+ function test_readNode_bounds(t) {
+ var files = ["v3/envelope.xml"];
+
+ var len = files.length;
+ t.plan(len);
+
+ var file, doc, expect, got;
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "feature",
+ featureNS: "http://example.com/feature"
+ });
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ expect = cases[file];
+ if(expect) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ out = format.readNode(doc.documentElement);
+ if(out.components && out.components.length == 1) {
+ got = out.components[0];
+ if(got instanceof OpenLayers.Bounds) {
+ t.ok(out.components[0].equals(expect), "[" + file + "] bounds read")
+ } else {
+ t.fail("[" + file + "] expected a bounds, got " + got);
+ }
+ } else {
+ t.fail("[" + file + "] gml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+
+ }
+
+ function test_writeNode_geometry(t) {
+ // we only care to write the 'pos' and 'posList' variants of GML 3 - conforming with simple features profile
+ var files = [
+ {path: "v3/point.xml"},
+ {path: "v3/linestring.xml"},
+ {path: "v3/curve.xml", options: {curve: true}},
+ {path: "v3/polygon.xml"},
+ {path: "v3/surface.xml", options: {surface: true}},
+ {path: "v3/multipoint-singular.xml"},
+ {path: "v3/multilinestring-singular.xml", options: {multiCurve: false}},
+ {path: "v3/multicurve-singular.xml"},
+ {path: "v3/multicurve-curve.xml", options: {curve: true}},
+ {path: "v3/multipolygon-singular.xml", options: {multiSurface: false}},
+ {path: "v3/multisurface-singular.xml"},
+ {path: "v3/multisurface-surface.xml", options: {surface: true}}
+ ];
+
+ var len = files.length;
+ t.plan(len);
+
+ var defaults = {
+ featureType: "feature",
+ featureNS: "http://example.com/feature",
+ srsName: "foo" // GML geometry collections require srsName, we only write if provided
+ };
+
+ var format, options, file, geom, doc, node;
+ for(var i=0; i<len; ++i) {
+ file = files[i].path;
+ geom = cases[file];
+ if(geom) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ options = OpenLayers.Util.extend({}, defaults);
+ if(files[i].options) {
+ OpenLayers.Util.extend(options, files[i].options);
+ }
+ format = new OpenLayers.Format.GML.v3(options);
+ node = format.writeNode("feature:_geometry", geom);
+ t.xml_eq(node.firstChild, doc.documentElement, "[" + file + "] geometry written");
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+ }
+
+ function test_writeNode_bounds(t) {
+ var files = [
+ "v3/envelope.xml"
+ ];
+
+ var len = files.length;
+ t.plan(len);
+
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "feature",
+ featureNS: "http://example.com/feature",
+ srsName: "foo" // GML envelopes require srsName, we only write if provided
+ });
+ var file, bounds, doc, node;
+ for(var i=0; i<len; ++i) {
+ file = files[i];
+ bounds = cases[file];
+ if(bounds) {
+ doc = readXML(file);
+ if(doc && doc.documentElement) {
+ node = format.writeNode("gml:Envelope", bounds);
+ t.xml_eq(node, doc.documentElement, "[" + file + "] bounds written");
+ } else {
+ t.fail("[" + file + "] xml parsing");
+ }
+ } else {
+ t.fail("[" + file + "] case not found");
+ }
+ }
+ }
+
+ function test_boundedBy(t) {
+ t.plan(5);
+
+ var doc = readXML("v3/topp-states-wfs.xml");
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom",
+ xy: false
+ });
+ var features = format.read(doc.documentElement);
+ var bounds = features[0].bounds;
+
+ t.ok(bounds instanceof OpenLayers.Bounds, "feature given a bounds");
+ t.eq(bounds.left.toFixed(2), "-91.52", "bounds left correct");
+ t.eq(bounds.bottom.toFixed(2), "36.99", "bounds bottom correct");
+ t.eq(bounds.right.toFixed(2), "-87.51", "bounds right correct");
+ t.eq(bounds.top.toFixed(2), "42.51", "bounds top correct");
+ }
+
+ function test_read(t) {
+ t.plan(8);
+ var doc = readXML("v3/topp-states-wfs.xml");
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom",
+ xy: false
+ });
+ var features = format.read(doc.documentElement);
+
+ t.eq(features.length, 3, "read 3 features");
+ var feature = features[0];
+ t.eq(feature.fid, "states.1", "read fid");
+ t.eq(feature.geometry.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon",
+ "read multipolygon geometry");
+ var attributes = feature.attributes;
+ t.eq(attributes["STATE_NAME"], "Illinois", "read STATE_NAME");
+ t.eq(attributes["STATE_FIPS"], "17", "read STATE_FIPS");
+ t.eq(attributes["SUB_REGION"], "E N Cen", "read SUB_REGION");
+ t.eq(attributes["STATE_ABBR"], "IL", "read STATE_ABBR");
+ t.eq(attributes["LAND_KM"], "143986.61", "read LAND_KM");
+ }
+
+ function test_read_autoconfig(t) {
+ t.plan(7);
+ var doc = readXML("v3/topp-states-wfs.xml");
+ var format = new OpenLayers.Format.GML.v3();
+ var features = format.read(doc.documentElement);
+
+ t.eq(features.length, 3, "read 3 features");
+ var feature = features[0];
+ t.eq(feature.fid, "states.1", "read fid");
+ t.eq(feature.geometry.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon",
+ "read multipolygon geometry");
+ t.eq(format.featureType, "states", "featureType correctly auto-configured");
+ t.eq(format.featureNS, "http://www.openplans.org/topp", "featureNS correctly auto-configured");
+
+ t.eq(format.autoConfig, true, "autoConfig set to true");
+ format.autoConfig = false;
+ format.read(doc.documentElement);
+ t.eq(format.autoConfig, false, "now that featureNS is set, the format does not auto-configure again");
+ }
+
+ function test_emptyAttribute(t) {
+ t.plan(4);
+ var str =
+ '<gml:featureMembers xmlns:gml="http://www.opengis.net/gml">' +
+ '<topp:gnis_pop gml:id="gnis_pop.148604" xmlns:topp="http://www.openplans.org/topp">' +
+ '<gml:name>Aflu</gml:name>' +
+ '<topp:the_geom>' +
+ '<gml:Point srsName="urn:x-ogc:def:crs:EPSG:4326">' +
+ '<gml:pos>34.12 2.09</gml:pos>' +
+ '</gml:Point>' +
+ '</topp:the_geom>' +
+ '<topp:population>84683</topp:population>' +
+ '<topp:country>Algeria</topp:country>' +
+ '<topp:type>place</topp:type>' +
+ '<topp:name>Aflu</topp:name>' +
+ '<topp:empty></topp:empty>' +
+ '</topp:gnis_pop>' +
+ '</gml:featureMembers>';
+
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "gnis_pop",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom"
+ });
+
+ var features = format.read(str);
+ t.eq(features.length, 1, "read one feature");
+ var attr = features[0].attributes;
+ t.eq(attr.name, "Aflu", "correctly read attribute value");
+ t.eq(attr.foo, undefined, "bogus attribute is undefined");
+ t.eq(attr.empty, "", "empty attribute value is empty string");
+ }
+
+ function test_repeatedName(t) {
+ // test that if an attribute name matches the featureType, all goes well
+ t.plan(2);
+ var doc = readXML("v3/repeated-name.xml");
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "zoning",
+ featureNS: "http://opengeo.org/#medford",
+ geometryName: "the_geom",
+ xy: false
+ });
+ var features = format.read(doc.documentElement);
+
+ t.eq(features.length, 1, "read one feature");
+ var atts = features[0].attributes;
+ t.eq(atts.zoning, "I-L", "correct zoning attribute on zoning feature type");
+
+ }
+
+ function test_write(t) {
+ t.plan(1);
+ var doc = readXML("v3/topp-states-gml.xml");
+ var format = new OpenLayers.Format.GML.v3({
+ featureType: "states",
+ featureNS: "http://www.openplans.org/topp",
+ geometryName: "the_geom",
+ srsName: "urn:x-ogc:def:crs:EPSG:4326",
+ xy: false,
+ schemaLocation: "http://www.openplans.org/topp http://sigma.openplans.org:80/geoserver/wfs?service=WFS&version=1.1.0&request=DescribeFeatureType&typeName=topp:states http://www.opengis.net/gml http://schemas.opengis.net/gml/3.2.1/gml.xsd"
+ });
+ var features = format.read(doc.documentElement);
+
+ var got = format.write(features);
+ t.xml_eq(got, doc.documentElement, "gml:featureMembers round trip");
+ }
+
+ </script>
+</head>
+<body>
+<div id="v3/envelope.xml"><!--
+<gml:Envelope xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lowerCorner>1 2</gml:lowerCorner>
+ <gml:upperCorner>3 4</gml:upperCorner>
+</gml:Envelope>
+--></div>
+<div id="v3/linearring.xml"><!--
+<gml:LinearRing xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+</gml:LinearRing>
+--></div>
+<div id="v3/linestring.xml"><!--
+<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:posList>1 2 3 4</gml:posList>
+</gml:LineString>
+--></div>
+<div id="v3/linestring3d.xml"><!--
+<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="foo" srsDimension="3">
+ <gml:posList>1 2 3 4 5 6</gml:posList>
+</gml:LineString>
+--></div>
+<div id="v3/curve.xml"><!--
+<gml:Curve xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:segments>
+ <gml:LineStringSegment>
+ <gml:posList>1 2 3 4</gml:posList>
+ </gml:LineStringSegment>
+ </gml:segments>
+</gml:Curve>
+--></div>
+<div id="v3/multilinestring-plural.xml"><!--
+<gml:MultiLineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lineStringMembers>
+ <gml:LineString>
+ <gml:posList>1 2 2 3</gml:posList>
+ </gml:LineString>
+ <gml:LineString>
+ <gml:posList>3 4 4 5</gml:posList>
+ </gml:LineString>
+ </gml:lineStringMembers>
+</gml:MultiLineString>
+--></div>
+<div id="v3/multilinestring-singular.xml"><!--
+<gml:MultiLineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:posList>1 2 2 3</gml:posList>
+ </gml:LineString>
+ </gml:lineStringMember>
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:posList>3 4 4 5</gml:posList>
+ </gml:LineString>
+ </gml:lineStringMember>
+</gml:MultiLineString>
+--></div>
+<div id="v3/multicurve-singular.xml"><!--
+<gml:MultiCurve xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:curveMember>
+ <gml:LineString>
+ <gml:posList>1 2 2 3</gml:posList>
+ </gml:LineString>
+ </gml:curveMember>
+ <gml:curveMember>
+ <gml:LineString>
+ <gml:posList>3 4 4 5</gml:posList>
+ </gml:LineString>
+ </gml:curveMember>
+</gml:MultiCurve>
+--></div>
+<div id="v3/multicurve-curve.xml"><!--
+<gml:MultiCurve xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:curveMember>
+ <gml:Curve>
+ <gml:segments>
+ <gml:LineStringSegment>
+ <gml:posList>1 2 2 3</gml:posList>
+ </gml:LineStringSegment>
+ </gml:segments>
+ </gml:Curve>
+ </gml:curveMember>
+ <gml:curveMember>
+ <gml:Curve>
+ <gml:segments>
+ <gml:LineStringSegment>
+ <gml:posList>3 4 4 5</gml:posList>
+ </gml:LineStringSegment>
+ </gml:segments>
+ </gml:Curve>
+ </gml:curveMember>
+</gml:MultiCurve>
+--></div>
+<div id="v3/multipoint-plural.xml"><!--
+<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pointMembers>
+ <gml:Point>
+ <gml:pos>1 2</gml:pos>
+ </gml:Point>
+ <gml:Point>
+ <gml:pos>2 3</gml:pos>
+ </gml:Point>
+ <gml:Point>
+ <gml:pos>3 4</gml:pos>
+ </gml:Point>
+ </gml:pointMembers>
+</gml:MultiPoint>
+--></div>
+<div id="v3/multipoint-singular.xml"><!--
+<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:pos>1 2</gml:pos>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:pos>2 3</gml:pos>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:pos>3 4</gml:pos>
+ </gml:Point>
+ </gml:pointMember>
+</gml:MultiPoint>
+--></div>
+<div id="v3/multipolygon-plural.xml"><!--
+<gml:MultiPolygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:polygonMembers>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ </gml:Polygon>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </gml:polygonMembers>
+</gml:MultiPolygon>
+--></div>
+<div id="v3/multipolygon-singular.xml"><!--
+<gml:MultiPolygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ </gml:Polygon>
+ </gml:polygonMember>
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </gml:polygonMember>
+</gml:MultiPolygon>
+--></div>
+<div id="v3/multisurface-plural.xml"><!--
+<gml:MultiSurface xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:surfaceMembers>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ </gml:Polygon>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </gml:surfaceMembers>
+</gml:MultiSurface>
+--></div>
+<div id="v3/multisurface-singular.xml"><!--
+<gml:MultiSurface xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:surfaceMember>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ </gml:Polygon>
+ </gml:surfaceMember>
+ <gml:surfaceMember>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </gml:surfaceMember>
+</gml:MultiSurface>
+--></div>
+<div id="v3/multisurface-surface.xml"><!--
+<gml:MultiSurface xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:surfaceMember>
+ <gml:Surface>
+ <gml:patches>
+ <gml:PolygonPatch interpolation="planar">
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ </gml:PolygonPatch>
+ </gml:patches>
+ </gml:Surface>
+ </gml:surfaceMember>
+ <gml:surfaceMember>
+ <gml:Surface>
+ <gml:patches>
+ <gml:PolygonPatch interpolation="planar">
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:PolygonPatch>
+ </gml:patches>
+ </gml:Surface>
+ </gml:surfaceMember>
+</gml:MultiSurface>
+--></div>
+<div id="v3/point.xml"><!--
+<gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pos>1 2</gml:pos>
+</gml:Point>
+--></div>
+<div id="v3/polygon.xml"><!--
+<gml:Polygon xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+</gml:Polygon>
+--></div>
+<div id="v3/surface.xml"><!--
+<gml:Surface xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:patches>
+ <gml:PolygonPatch interpolation="planar">
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>1 2 3 4 5 6 1 2</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>2 3 4 5 6 7 2 3</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ <gml:interior>
+ <gml:LinearRing>
+ <gml:posList>3 4 5 6 7 8 3 4</gml:posList>
+ </gml:LinearRing>
+ </gml:interior>
+ </gml:PolygonPatch>
+ </gml:patches>
+</gml:Surface>
+--></div>
+<div id="v3/topp-states-gml.xml"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<gml:featureMembers xsi:schemaLocation="http://www.openplans.org/topp http://sigma.openplans.org:80/geoserver/wfs?service=WFS&amp;version=1.1.0&amp;request=DescribeFeatureType&amp;typeName=topp:states http://www.opengis.net/gml http://schemas.opengis.net/gml/3.2.1/gml.xsd" xmlns:topp="http://www.openplans.org/topp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml"><topp:states fid="states.1"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>37.5101 -88.0711 37.4761 -88.0871 37.4421 -88.3111 37.4091 -88.3591 37.4201 -88.4191 37.4001 -88.4671 37.2961 -88.5111 37.2571 -88.5011 37.2051 -88.4501 37.1561 -88.4221 37.0981 -88.4501 37.0721 -88.4761 37.0681 -88.4901 37.0641 -88.5171 37.0721 -88.5591 37.1091 -88.6141 37.1351 -88.6881 37.1411 -88.7391 37.1521 -88.7461 37.2021 -88.8631 37.2181 -88.9321 37.2201 -88.9931 37.1851 -89.0651 37.1121 -89.1161 37.0931 -89.1461 37.0641 -89.1691 37.0251 -89.1741 36.9981 -89.1501 36.9881 -89.1291 36.9861 -89.1931 37.0281 -89.2101 37.0411 -89.2371 37.0871 -89.2641 37.0911 -89.2841 37.0851 -89.3031 37.0601 -89.3091 37.0271 -89.2641 37.0081 -89.2621 36.9991 -89.2821 37.0091 -89.3101 37.0491 -89.3821 37.0991 -89.3791 37.1371 -89.4231 37.1651 -89.4401 37.2241 -89.4681 37.2531 -89.4651 37.2561 -89.4891 37.2761 -89.5131 37.3041 -89.5131 37.3291 -89.5001 37.3391 -89.4681 37.3551 -89.4351 37.4111 -89.4271 37.4531 -89.4531 37.4911 -89.4941 37.5711 -89.5241 37.6151 -89.5131 37.6501 -89.5191 37.6791 -89.5131 37.6941 -89.5211 37.7061 -89.5811 37.7451 -89.6661 37.7831 -89.6751 37.8041 -89.6911 37.8401 -89.7281 37.9051 -89.8511 37.9051 -89.8611 37.8911 -89.8661 37.8751 -89.9001 37.8781 -89.9371 37.9111 -89.9781 37.9631 -89.9581 37.9691 -90.0101 37.9931 -90.0411 38.0321 -90.1191 38.0531 -90.1341 38.0881 -90.2071 38.1221 -90.2541 38.1661 -90.2891 38.1881 -90.3361 38.2341 -90.3641 38.3231 -90.3691 38.3651 -90.3581 38.3901 -90.3391 38.4271 -90.3011 38.5181 -90.2651 38.5321 -90.2611 38.5621 -90.2401 38.6101 -90.1831 38.6581 -90.1831 38.7001 -90.2021 38.7231 -90.1961 38.7731 -90.1631 38.7851 -90.1351 38.8001 -90.1211 38.8301 -90.1131 38.8531 -90.1321 38.9141 -90.2431 38.9241 -90.2781 38.9241 -90.3191 38.9621 -90.4131 38.9591 -90.4691 38.8911 -90.5301 38.8711 -90.5701 38.8801 -90.6271 38.9351 -90.6681 39.0371 -90.7061 39.0581 -90.7071 39.0931 -90.6901 39.1441 -90.7161 39.1951 -90.7181 39.2241 -90.7321 39.2471 -90.7381 39.2961 -90.7791 39.3501 -90.8501 39.4001 -90.9471 39.4441 -91.0361 39.4731 -91.0641 39.5281 -91.0931 39.5521 -91.1561 39.6001 -91.2031 39.6851 -91.3171 39.7241 -91.3671 39.7611 -91.3731 39.8031 -91.3811 39.8631 -91.4491 39.8851 -91.4501 39.9011 -91.4341 39.9211 -91.4301 39.9461 -91.4471 40.0051 -91.4871 40.0661 -91.5041 40.1341 -91.5161 40.2001 -91.5061 40.2511 -91.4981 40.3091 -91.4861 40.3711 -91.4481 40.3861 -91.4181 40.3921 -91.3851 40.4021 -91.3721 40.4471 -91.3851 40.5031 -91.3741 40.5281 -91.3821 40.5471 -91.4121 40.5721 -91.4111 40.6031 -91.3751 40.6391 -91.2621 40.6431 -91.2141 40.6561 -91.1621 40.6821 -91.1291 40.7051 -91.1191 40.7611 -91.0921 40.8331 -91.0881 40.8791 -91.0491 40.9231 -90.9831 40.9501 -90.9601 41.0701 -90.9541 41.1041 -90.9571 41.1441 -90.9901 41.1651 -91.0181 41.1761 -91.0561 41.2311 -91.1011 41.2671 -91.1021 41.3341 -91.0731 41.4011 -91.0551 41.4231 -91.0271 41.4311 -91.0001 41.4211 -90.9491 41.4441 -90.8441 41.4491 -90.7791 41.4501 -90.7081 41.4621 -90.6581 41.5091 -90.6001 41.5251 -90.5401 41.5271 -90.4541 41.5431 -90.4341 41.5671 -90.4231 41.5861 -90.3481 41.6021 -90.3391 41.6491 -90.3411 41.7221 -90.3261 41.7561 -90.3041 41.7811 -90.2551 41.8061 -90.1951 41.9301 -90.1541 41.9831 -90.1421 42.0331 -90.1501 42.0611 -90.1681 42.1031 -90.1661 42.1201 -90.1761 42.1221 -90.1911 42.1591 -90.2301 42.1971 -90.3231 42.2101 -90.3671 42.2421 -90.4071 42.2631 -90.4171 42.3401 -90.4271 42.3601 -90.4411 42.3881 -90.4911 42.4211 -90.5631 42.4601 -90.6051 42.4751 -90.6481 42.4941 -90.6511 42.5091 -90.6381 42.5081 -90.4191 42.5041 -89.9231 42.5031 -89.8341 42.4971 -89.4001 42.4971 -89.3591 42.4901 -88.9391 42.4901 -88.7641 42.4891 -88.7061 42.4911 -88.2971 42.4891 -88.1941 42.4891 -87.7971 42.3141 -87.8361 42.1561 -87.7601 42.0591 -87.6701 41.8471 -87.6121 41.7231 -87.5291 41.4691 -87.5321 41.3011 -87.5321 41.1731 -87.5311 41.0091 -87.5321 40.7451 -87.5321 40.4941 -87.5371 40.4831 -87.5351 40.1661 -87.5351 39.8871 -87.5351 39.6091 -87.5351 39.4771 -87.5381 39.3501 -87.5401 39.3381 -87.5971 39.3071 -87.6251 39.2971 -87.6101 39.2811 -87.6151 39.2581 -87.6061 39.2481 -87.5841 39.2081 -87.5881 39.1981 -87.5941 39.1961 -87.6071 39.1681 -87.6441 39.1461 -87.6701 39.1301 -87.6591 39.1131 -87.6621 39.1031 -87.6311 39.0881 -87.6301 39.0841 -87.6121 39.0621 -87.5851 38.9951 -87.5811 38.9941 -87.5911 38.9771 -87.5471 38.9631 -87.5331 38.9311 -87.5301 38.9041 -87.5391 38.8691 -87.5591 38.8571 -87.5501 38.7951 -87.5071 38.7761 -87.5191 38.7691 -87.5081 38.7361 -87.5081 38.6851 -87.5431 38.6721 -87.5881 38.6421 -87.6251 38.6221 -87.6281 38.5991 -87.6191 38.5931 -87.6401 38.5731 -87.6521 38.5471 -87.6721 38.5151 -87.6511 38.5001 -87.6531 38.5041 -87.6791 38.4811 -87.6921 38.4661 -87.7561 38.4571 -87.7581 38.4451 -87.7381 38.4171 -87.7481 38.3781 -87.7841 38.3521 -87.8341 38.2861 -87.8501 38.2851 -87.8631 38.3161 -87.8741 38.3151 -87.8831 38.3001 -87.8881 38.2811 -87.9141 38.3021 -87.9131 38.3041 -87.9251 38.2411 -87.9801 38.2341 -87.9861 38.2001 -87.9771 38.1711 -87.9321 38.1571 -87.9311 38.1361 -87.9501 38.1311 -87.9731 38.1031 -88.0181 38.0921 -88.0121 38.0961 -87.9641 38.0731 -87.9751 38.0541 -88.0341 38.0451 -88.0431 38.0381 -88.0411 38.0331 -88.0211 38.0081 -88.0291 37.9751 -88.0211 37.9561 -88.0421 37.9341 -88.0411 37.9291 -88.0641 37.944 -88.0781 37.9231 -88.084 37.9171 -88.0301 37.9051 -88.0261 37.8961 -88.0441 37.9061 -88.1001 37.8951 -88.1011 37.8671 -88.0751 37.8431 -88.0341 37.8271 -88.0421 37.8311 -88.0891 37.8171 -88.0861 37.8051 -88.0351 37.7351 -88.0721 37.7001 -88.1331 37.6601 -88.1591 37.6281 -88.1571 37.5831 -88.1341 37.5101 -88.0711</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Illinois</topp:STATE_NAME><topp:STATE_FIPS>17</topp:STATE_FIPS><topp:SUB_REGION>E N Cen</topp:SUB_REGION><topp:STATE_ABBR>IL</topp:STATE_ABBR><topp:LAND_KM>143986.61</topp:LAND_KM><topp:WATER_KM>1993.335</topp:WATER_KM><topp:PERSONS>1.1431E7</topp:PERSONS><topp:FAMILIES>2924880.0</topp:FAMILIES><topp:HOUSHOLD>4202240.0</topp:HOUSHOLD><topp:MALE>5552233.0</topp:MALE><topp:FEMALE>5878369.0</topp:FEMALE><topp:WORKERS>4199206.0</topp:WORKERS><topp:DRVALONE>3741715.0</topp:DRVALONE><topp:CARPOOL>652603.0</topp:CARPOOL><topp:PUBTRANS>538071.0</topp:PUBTRANS><topp:EMPLOYED>5417967.0</topp:EMPLOYED><topp:UNEMPLOY>385040.0</topp:UNEMPLOY><topp:SERVICE>1360159.0</topp:SERVICE><topp:MANUAL>828906.0</topp:MANUAL><topp:P_MALE>0.486</topp:P_MALE><topp:P_FEMALE>0.514</topp:P_FEMALE><topp:SAMP_POP>1747776.0</topp:SAMP_POP></topp:states><topp:states fid="states.2"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.9661 -77.0081 38.8891 -76.9111 38.7881 -77.0451 38.8131 -77.0351 38.8291 -77.0451 38.8381 -77.0401 38.8621 -77.0391 38.8861 -77.0671 38.9151 -77.0781 38.9321 -77.1221 38.9931 -77.0421 38.9661 -77.0081</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>District of Columbia</topp:STATE_NAME><topp:STATE_FIPS>11</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>DC</topp:STATE_ABBR><topp:LAND_KM>159.055</topp:LAND_KM><topp:WATER_KM>17.991</topp:WATER_KM><topp:PERSONS>606900.0</topp:PERSONS><topp:FAMILIES>122087.0</topp:FAMILIES><topp:HOUSHOLD>249634.0</topp:HOUSHOLD><topp:MALE>282970.0</topp:MALE><topp:FEMALE>323930.0</topp:FEMALE><topp:WORKERS>229975.0</topp:WORKERS><topp:DRVALONE>106694.0</topp:DRVALONE><topp:CARPOOL>36621.0</topp:CARPOOL><topp:PUBTRANS>111422.0</topp:PUBTRANS><topp:EMPLOYED>303994.0</topp:EMPLOYED><topp:UNEMPLOY>23442.0</topp:UNEMPLOY><topp:SERVICE>65498.0</topp:SERVICE><topp:MANUAL>22407.0</topp:MANUAL><topp:P_MALE>0.466</topp:P_MALE><topp:P_FEMALE>0.534</topp:P_FEMALE><topp:SAMP_POP>72696.0</topp:SAMP_POP></topp:states><topp:states fid="states.3"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.5571 -75.7071 38.6491 -75.7111 38.8301 -75.7241 39.1411 -75.7521 39.2471 -75.7611 39.2951 -75.7641 39.3831 -75.7721 39.7231 -75.7911 39.7241 -75.7751 39.7741 -75.7451 39.8201 -75.6951 39.8381 -75.6441 39.8401 -75.5831 39.8261 -75.4701 39.7981 -75.4201 39.7891 -75.4121 39.7781 -75.4281 39.7631 -75.4601 39.7411 -75.4751 39.7191 -75.4761 39.7141 -75.4891 39.6121 -75.6101 39.5661 -75.5621 39.4631 -75.5901 39.3661 -75.5151 39.2571 -75.4021 39.0731 -75.3971 39.0121 -75.3241 38.9451 -75.3071 38.8081 -75.1901 38.7991 -75.0831 38.4491 -75.0451 38.4491 -75.0681 38.4501 -75.0931 38.4551 -75.3501 38.4631 -75.6991 38.5571 -75.7071</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Delaware</topp:STATE_NAME><topp:STATE_FIPS>10</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>DE</topp:STATE_ABBR><topp:LAND_KM>5062.456</topp:LAND_KM><topp:WATER_KM>1385.022</topp:WATER_KM><topp:PERSONS>666168.0</topp:PERSONS><topp:FAMILIES>175867.0</topp:FAMILIES><topp:HOUSHOLD>247497.0</topp:HOUSHOLD><topp:MALE>322968.0</topp:MALE><topp:FEMALE>343200.0</topp:FEMALE><topp:WORKERS>247566.0</topp:WORKERS><topp:DRVALONE>258087.0</topp:DRVALONE><topp:CARPOOL>42968.0</topp:CARPOOL><topp:PUBTRANS>8069.0</topp:PUBTRANS><topp:EMPLOYED>335147.0</topp:EMPLOYED><topp:UNEMPLOY>13945.0</topp:UNEMPLOY><topp:SERVICE>87973.0</topp:SERVICE><topp:MANUAL>44140.0</topp:MANUAL><topp:P_MALE>0.485</topp:P_MALE><topp:P_FEMALE>0.515</topp:P_FEMALE><topp:SAMP_POP>102776.0</topp:SAMP_POP></topp:states><topp:states fid="states.4"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.4801 -79.2311 38.4371 -79.2721 38.4121 -79.3171 38.4621 -79.4861 38.5531 -79.5361 38.5921 -79.6421 38.5501 -79.6691 38.5201 -79.6651 38.5001 -79.6921 38.4301 -79.6841 38.3941 -79.7201 38.3511 -79.7331 38.3531 -79.7641 38.3141 -79.8001 38.2981 -79.8031 38.2841 -79.7861 38.2681 -79.7931 38.2501 -79.8311 38.1791 -79.9161 38.1621 -79.9101 38.1211 -79.9351 38.1031 -79.9281 38.0671 -79.9571 38.0381 -79.9661 37.9891 -80.0001 37.9551 -80.0551 37.9141 -80.1061 37.8911 -80.1181 37.8771 -80.1601 37.8601 -80.1721 37.8421 -80.1711 37.8021 -80.2231 37.7781 -80.2201 37.7571 -80.2541 37.7251 -80.2501 37.6821 -80.3031 37.6711 -80.2951 37.6521 -80.3051 37.6401 -80.3011 37.6401 -80.2541 37.6241 -80.2191 37.5961 -80.2461 37.5661 -80.3161 37.5331 -80.3261 37.5281 -80.3081 37.5361 -80.2801 37.5111 -80.2881 37.4911 -80.3471 37.4751 -80.3521 37.4651 -80.3881 37.4341 -80.4251 37.4221 -80.4741 37.4331 -80.4871 37.4601 -80.4881 37.4741 -80.5081 37.4691 -80.5421 37.4451 -80.5971 37.3881 -80.7051 37.3921 -80.7291 37.3871 -80.7461 37.3781 -80.7471 37.3711 -80.7631 37.3861 -80.7701 37.3911 -80.7991 37.4121 -80.7991 37.4231 -80.8501 37.3881 -80.8771 37.3501 -80.8481 37.3391 -80.8551 37.3011 -80.9341 37.2911 -80.9681 37.2961 -80.9781 37.3061 -80.9861 37.2851 -81.0251 37.2741 -81.1401 37.2401 -81.2231 37.2931 -81.3121 37.3381 -81.3581 37.3111 -81.3911 37.2821 -81.4031 37.2541 -81.4751 37.2521 -81.4951 37.2341 -81.5051 37.2061 -81.5561 37.2041 -81.6661 37.2351 -81.7011 37.2501 -81.7381 37.2721 -81.7521 37.2871 -81.7921 37.2791 -81.8151 37.2851 -81.8391 37.3061 -81.8581 37.3251 -81.8631 37.3401 -81.8971 37.3711 -81.9261 37.4151 -81.9201 37.4661 -81.9881 37.4821 -81.9761 37.4921 -81.9481 37.5061 -81.9351 37.5311 -81.9591 37.5431 -81.9761 37.5301 -82.0261 37.5511 -82.0491 37.5251 -82.0551 37.5481 -82.0841 37.5571 -82.1421 37.5651 -82.1461 37.5691 -82.1371 37.5901 -82.1311 37.5931 -82.1591 37.6401 -82.1851 37.6231 -82.2051 37.6561 -82.2381 37.6681 -82.2951 37.7441 -82.3291 37.7581 -82.3191 37.7841 -82.3391 37.8111 -82.4051 37.8721 -82.4211 37.8941 -82.4371 37.9221 -82.5001 37.9421 -82.4931 37.9541 -82.4801 37.9751 -82.4751 38.0151 -82.5241 38.1091 -82.5931 38.1461 -82.6461 38.1691 -82.6471 38.1781 -82.6131 38.1931 -82.6061 38.2381 -82.6161 38.2451 -82.5891 38.2551 -82.5741 38.2921 -82.5801 38.3071 -82.5721 38.3681 -82.5981 38.4121 -82.5861 38.4031 -82.5751 38.4001 -82.5471 38.4051 -82.4951 38.4301 -82.4151 38.4281 -82.3941 38.4411 -82.3291 38.4651 -82.3141 38.5791 -82.2901 38.5941 -82.2711 38.5841 -82.2131 38.5941 -82.1841 38.6321 -82.1731 38.6771 -82.1891 38.7101 -82.1841 38.7781 -82.2161 38.8041 -82.1971 38.8381 -82.1461 38.8991 -82.1391 38.9521 -82.1011 38.9771 -82.0851 38.9881 -82.0581 39.0141 -82.0431 39.0151 -81.9991 38.9921 -81.9751 38.9911 -81.9371 38.9841 -81.9271 38.9321 -81.8981 38.8941 -81.9311 38.8841 -81.9151 38.8731 -81.8921 38.8851 -81.8661 38.9371 -81.8411 38.9481 -81.8231 38.9231 -81.7831 38.9301 -81.7621 38.9681 -81.7811 39.0161 -81.7751 39.0441 -81.8131 39.0661 -81.8241 39.0761 -81.8191 39.0771 -81.7861 39.0941 -81.7531 39.1251 -81.7441 39.1751 -81.7591 39.2131 -81.7231 39.2191 -81.6981 39.2601 -81.6891 39.2701 -81.6671 39.2651 -81.5721 39.3321 -81.5571 39.3521 -81.5401 39.4061 -81.4651 39.4101 -81.4481 39.4051 -81.4341 39.3451 -81.3761 39.3531 -81.3381 39.3861 -81.2841 39.3881 -81.2371 39.4081 -81.2251 39.4151 -81.2001 39.4371 -81.1801 39.4671 -81.1171 39.4961 -81.0981 39.5321 -81.0371 39.5441 -81.0321 39.5811 -80.9831 39.6061 -80.9321 39.6071 -80.9121 39.6241 -80.8811 39.6621 -80.8721 39.6801 -80.8631 39.7031 -80.8321 39.7181 -80.8321 39.7361 -80.8561 39.7591 -80.8701 39.8081 -80.8191 39.8391 -80.8261 39.8561 -80.7981 39.8721 -80.7911 39.9041 -80.8121 39.9151 -80.8081 39.9191 -80.7961 39.9131 -80.7681 39.9211 -80.7591 39.9461 -80.7631 39.9831 -80.7391 40.0351 -80.7381 40.1541 -80.7021 40.1681 -80.7011 40.1941 -80.6781 40.2451 -80.6501 40.2761 -80.6141 40.3061 -80.6041 40.3731 -80.6091 40.3881 -80.6291 40.3981 -80.6281 40.4801 -80.6021 40.5041 -80.6251 40.5391 -80.6331 40.5681 -80.6681 40.5821 -80.6671 40.6131 -80.6371 40.6191 -80.6111 40.6151 -80.5741 40.6371 -80.5221 40.4781 -80.5241 40.4021 -80.5231 40.1621 -80.5261 40.0221 -80.5251 39.9581 -80.5241 39.7211 -80.5241 39.7191 -80.4291 39.7211 -79.9181 39.7211 -79.7651 39.7201 -79.4811 39.1971 -79.4901 39.2131 -79.4611 39.2111 -79.4491 39.2691 -79.3851 39.2911 -79.3461 39.3001 -79.2951 39.3251 -79.2801 39.3481 -79.2601 39.3931 -79.1631 39.4131 -79.1581 39.4161 -79.1311 39.4471 -79.1041 39.4641 -79.0961 39.4701 -79.1041 39.4701 -79.0701 39.4851 -79.0641 39.4831 -79.0491 39.4381 -78.9701 39.4601 -78.9551 39.5251 -78.8711 39.5631 -78.8381 39.5661 -78.8061 39.5851 -78.8221 39.6151 -78.7981 39.6301 -78.7981 39.6441 -78.7721 39.6261 -78.7671 39.6261 -78.7321 39.6211 -78.7301 39.6081 -78.7361 39.6011 -78.7741 39.5811 -78.7611 39.5761 -78.7321 39.5591 -78.7161 39.5361 -78.6661 39.5371 -78.6491 39.5291 -78.6371 39.5351 -78.6041 39.5201 -78.5641 39.5251 -78.5091 39.5191 -78.4811 39.5331 -78.4561 39.5481 -78.4461 39.5491 -78.4211 39.5801 -78.4621 39.5921 -78.4501 39.5871 -78.4041 39.6201 -78.4321 39.6141 -78.3841 39.6311 -78.3771 39.6321 -78.3571 39.6401 -78.3481 39.6181 -78.2731 39.6411 -78.2571 39.6581 -78.2291 39.6731 -78.2271 39.6751 -78.2041 39.6941 -78.1831 39.6751 -78.0941 39.6221 -78.0261 39.5981 -77.9951 39.6111 -77.9641 39.5851 -77.9451 39.5911 -77.9351 39.6141 -77.9471 39.6181 -77.9381 39.5961 -77.9031 39.6001 -77.8911 39.6161 -77.8881 39.6021 -77.8551 39.6051 -77.8421 39.5721 -77.8401 39.5651 -77.8531 39.5641 -77.8851 39.5571 -77.8901 39.5451 -77.8691 39.5141 -77.8641 39.5311 -77.8441 39.5251 -77.8351 39.5291 -77.8291 39.5111 -77.8251 39.5011 -77.8481 39.4931 -77.8251 39.4981 -77.7711 39.4801 -77.7991 39.4591 -77.7851 39.4631 -77.8041 39.4501 -77.7961 39.4391 -77.8041 39.4321 -77.8021 39.4251 -77.7571 39.4031 -77.7411 39.3961 -77.7371 39.3781 -77.7561 39.3601 -77.7451 39.3381 -77.7541 39.3261 -77.7501 39.3171 -77.7271 39.2841 -77.7591 39.2461 -77.7681 39.1961 -77.8051 39.1411 -77.8201 39.1321 -77.8301 39.2651 -78.0331 39.3911 -78.2291 39.4231 -78.2771 39.4561 -78.3471 39.3801 -78.3501 39.3611 -78.3651 39.3501 -78.3441 39.3411 -78.3411 39.2571 -78.4131 39.2441 -78.3991 39.2121 -78.4231 39.1971 -78.4241 39.1701 -78.4021 39.1481 -78.4301 39.1181 -78.4481 39.1111 -78.4851 39.0931 -78.5011 39.0571 -78.5361 39.0351 -78.5641 39.0231 -78.5491 39.0131 -78.5531 38.9671 -78.5981 38.9791 -78.6311 38.9501 -78.6471 38.9211 -78.6801 38.9041 -78.7191 38.9301 -78.7241 38.9291 -78.7371 38.9111 -78.7491 38.8801 -78.7931 38.8331 -78.8161 38.7631 -78.8661 38.8461 -78.9871 38.7991 -79.0331 38.7901 -79.0551 38.7611 -79.0561 38.7071 -79.0871 38.6591 -79.0881 38.6631 -79.1211 38.6581 -79.1271 38.4801 -79.2311</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>West Virginia</topp:STATE_NAME><topp:STATE_FIPS>54</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>WV</topp:STATE_ABBR><topp:LAND_KM>62384.2</topp:LAND_KM><topp:WATER_KM>375.199</topp:WATER_KM><topp:PERSONS>1793477.0</topp:PERSONS><topp:FAMILIES>500259.0</topp:FAMILIES><topp:HOUSHOLD>688557.0</topp:HOUSHOLD><topp:MALE>861536.0</topp:MALE><topp:FEMALE>931941.0</topp:FEMALE><topp:WORKERS>661702.0</topp:WORKERS><topp:DRVALONE>493164.0</topp:DRVALONE><topp:CARPOOL>106918.0</topp:CARPOOL><topp:PUBTRANS>7237.0</topp:PUBTRANS><topp:EMPLOYED>671085.0</topp:EMPLOYED><topp:UNEMPLOY>71142.0</topp:UNEMPLOY><topp:SERVICE>205950.0</topp:SERVICE><topp:MANUAL>124172.0</topp:MANUAL><topp:P_MALE>0.48</topp:P_MALE><topp:P_FEMALE>0.52</topp:P_FEMALE><topp:SAMP_POP>317564.0</topp:SAMP_POP></topp:states><topp:states fid="states.5"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.6491 -75.7111 38.5571 -75.7071 38.4631 -75.6991 38.4551 -75.3501 38.4501 -75.0931 38.3691 -75.1551 38.2731 -75.1501 38.2011 -75.2621 38.0681 -75.3731 38.0161 -75.3721 37.9961 -75.6261 37.9701 -75.6481 37.9791 -75.8651 38.0971 -75.7691 38.1741 -75.8971 38.2311 -75.8381 38.2401 -75.8611 38.2631 -75.7941 38.2581 -75.8941 38.3571 -75.8721 38.3751 -75.8861 38.2821 -75.9491 38.2821 -75.9951 38.3211 -76.0201 38.2581 -76.0651 38.4361 -76.2941 38.4781 -76.2911 38.5431 -76.1921 38.5951 -76.2511 38.5711 -76.0311 38.6221 -76.0281 38.5911 -76.0461 38.6101 -76.0751 38.7071 -76.1241 38.7091 -76.1741 38.7621 -76.2231 38.7691 -76.2671 38.6791 -76.3371 38.6991 -76.3501 38.834 -76.2721 38.7651 -76.1951 38.7881 -76.1651 38.8851 -76.1141 38.8891 -76.0751 38.8981 -76.1021 38.9481 -76.0951 38.9201 -76.1131 38.9731 -76.1991 39.1181 -76.1111 39.0921 -76.2211 39.1301 -76.2381 39.2041 -76.2181 39.3211 -76.1121 39.3581 -76.0371 39.3791 -75.8491 39.3941 -75.9781 39.4711 -75.9521 39.5241 -75.9741 39.5691 -76.0311 39.5421 -76.0781 39.4011 -76.1541 39.3741 -76.2261 39.3931 -76.3641 39.2311 -76.3991 39.2421 -76.5311 39.2591 -76.6041 39.2311 -76.5651 39.1981 -76.5761 39.1801 -76.6071 39.1581 -76.5951 39.1961 -76.5631 39.1181 -76.4231 38.9081 -76.4721 38.7581 -76.5491 38.7091 -76.5251 38.5221 -76.5081 38.3911 -76.3851 38.3461 -76.4051 38.3201 -76.4211 38.3351 -76.4711 38.4101 -76.5201 38.4501 -76.6471 38.2131 -76.3431 38.0451 -76.3301 38.2221 -76.5771 38.2341 -76.7601 38.3911 -76.8641 38.2991 -76.9081 38.3311 -76.9731 38.4261 -77.0021 38.3901 -77.2201 38.4131 -77.2551 38.4871 -77.2771 38.6481 -77.1291 38.6771 -77.1251 38.7031 -77.0931 38.7151 -77.0811 38.7121 -77.0571 38.7181 -77.0461 38.7881 -77.0451 38.8891 -76.9111 38.9661 -77.0081 38.9931 -77.0421 38.9321 -77.1221 38.9641 -77.1521 38.9751 -77.2431 39.0271 -77.2551 39.0621 -77.3241 39.0681 -77.3461 39.0661 -77.4331 39.0801 -77.4591 39.1031 -77.4791 39.1161 -77.5131 39.1571 -77.5161 39.1761 -77.4781 39.2181 -77.4611 39.2291 -77.4641 39.2491 -77.4941 39.2681 -77.5421 39.2981 -77.5681 39.2991 -77.6161 39.3181 -77.6791 39.3171 -77.7271 39.3261 -77.7501 39.3381 -77.7541 39.3601 -77.7451 39.3781 -77.7561 39.3961 -77.7371 39.4031 -77.7411 39.4251 -77.7571 39.4321 -77.8021 39.4391 -77.8041 39.4501 -77.7961 39.4631 -77.8041 39.4591 -77.7851 39.4801 -77.7991 39.4981 -77.7711 39.4931 -77.8251 39.5011 -77.8481 39.5111 -77.8251 39.5291 -77.8291 39.5251 -77.8351 39.5311 -77.8441 39.5141 -77.8641 39.5451 -77.8691 39.5571 -77.8901 39.5641 -77.8851 39.5651 -77.8531 39.5721 -77.8401 39.6051 -77.8421 39.6021 -77.8551 39.6161 -77.8881 39.6001 -77.8911 39.5961 -77.9031 39.6181 -77.9381 39.6141 -77.9471 39.5911 -77.9351 39.5851 -77.9451 39.6111 -77.9641 39.5981 -77.9951 39.6221 -78.0261 39.6751 -78.0941 39.6941 -78.1831 39.6751 -78.2041 39.6731 -78.2271 39.6581 -78.2291 39.6411 -78.2571 39.6181 -78.2731 39.6401 -78.3481 39.6321 -78.3571 39.6311 -78.3771 39.6141 -78.3841 39.6201 -78.4321 39.5871 -78.4041 39.5921 -78.4501 39.5801 -78.4621 39.5491 -78.4211 39.5481 -78.4461 39.5331 -78.4561 39.5191 -78.4811 39.5251 -78.5091 39.5201 -78.5641 39.5351 -78.6041 39.5291 -78.6371 39.5371 -78.6491 39.5361 -78.6661 39.5591 -78.7161 39.5761 -78.7321 39.5811 -78.7611 39.6011 -78.7741 39.6081 -78.7361 39.6211 -78.7301 39.6261 -78.7321 39.6261 -78.7671 39.6441 -78.7721 39.6301 -78.7981 39.6151 -78.7981 39.5851 -78.8221 39.5661 -78.8061 39.5631 -78.8381 39.5251 -78.8711 39.4601 -78.9551 39.4381 -78.9701 39.4831 -79.0491 39.4851 -79.0641 39.4701 -79.0701 39.4701 -79.1041 39.4641 -79.0961 39.4471 -79.1041 39.4161 -79.1311 39.4131 -79.1581 39.3931 -79.1631 39.3481 -79.2601 39.3251 -79.2801 39.3001 -79.2951 39.2911 -79.3461 39.2691 -79.3851 39.2111 -79.4491 39.2131 -79.4611 39.1971 -79.4901 39.7201 -79.4811 39.7191 -79.3961 39.7221 -78.9301 39.7231 -78.8181 39.7231 -78.3851 39.7241 -78.3341 39.7251 -78.0961 39.7191 -77.4761 39.7191 -77.4641 39.7201 -77.2211 39.7201 -76.9971 39.7211 -76.7901 39.7201 -76.5701 39.7211 -76.2331 39.7221 -76.1391 39.7231 -75.7911 39.3831 -75.7721 39.2951 -75.7641 39.2471 -75.7611 39.1411 -75.7521 38.8301 -75.7241 38.6491 -75.7111</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.9071 -76.2931 38.9671 -76.2941 38.9561 -76.3391 38.9411 -76.3141 38.9121 -76.3221 38.9241 -76.3421 38.8751 -76.3291 38.8541 -76.3751 38.9581 -76.3561 39.0401 -76.2991 38.9781 -76.2481 38.9231 -76.2461 38.9491 -76.2731 38.9071 -76.2931</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.4491 -75.0681 38.4491 -75.0451 38.3221 -75.0871 38.4491 -75.0681</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.0271 -75.2701 38.0371 -75.2441 38.0941 -75.2091 38.2041 -75.1641 38.3201 -75.0941 38.1241 -75.1731 38.0281 -75.2421 38.0271 -75.2701</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Maryland</topp:STATE_NAME><topp:STATE_FIPS>24</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>MD</topp:STATE_ABBR><topp:LAND_KM>25316.345</topp:LAND_KM><topp:WATER_KM>6188.794</topp:WATER_KM><topp:PERSONS>4781468.0</topp:PERSONS><topp:FAMILIES>1245814.0</topp:FAMILIES><topp:HOUSHOLD>1748991.0</topp:HOUSHOLD><topp:MALE>2318671.0</topp:MALE><topp:FEMALE>2462797.0</topp:FEMALE><topp:WORKERS>1783061.0</topp:WORKERS><topp:DRVALONE>1732837.0</topp:DRVALONE><topp:CARPOOL>376449.0</topp:CARPOOL><topp:PUBTRANS>202169.0</topp:PUBTRANS><topp:EMPLOYED>2481342.0</topp:EMPLOYED><topp:UNEMPLOY>111536.0</topp:UNEMPLOY><topp:SERVICE>586994.0</topp:SERVICE><topp:MANUAL>260308.0</topp:MANUAL><topp:P_MALE>0.485</topp:P_MALE><topp:P_FEMALE>0.515</topp:P_FEMALE><topp:SAMP_POP>684773.0</topp:SAMP_POP></topp:states><topp:states fid="states.6"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>37.6411 -102.0431 37.3861 -102.0411 36.9881 -102.0361 36.9981 -102.9971 36.9991 -103.0771 36.9941 -103.9931 36.9931 -105.1451 36.9921 -105.2121 36.9941 -105.7121 36.9921 -105.9911 36.9911 -106.4711 36.9891 -106.8601 36.9991 -106.8891 36.9971 -107.4101 36.9981 -107.4711 36.9991 -108.3711 36.9961 -109.0471 37.6301 -109.0441 37.8871 -109.0421 38.1521 -109.0421 38.2441 -109.0551 38.4941 -109.0531 39.3601 -109.0501 39.5181 -109.0521 39.6571 -109.0511 40.2101 -109.0501 40.6651 -109.0451 40.9981 -109.0471 41.0031 -107.9181 41.0001 -107.3031 40.9981 -106.8641 41.0011 -106.3281 41.0001 -106.2021 40.9961 -105.2781 40.9941 -104.9331 41.0031 -104.0511 40.9991 -103.5711 41.0001 -103.3821 40.9981 -102.6511 41.0001 -102.6201 40.9981 -102.0471 40.7431 -102.0461 40.6971 -102.0451 40.4311 -102.0471 40.3421 -102.0471 39.9981 -102.0511 39.5681 -102.0481 39.5621 -102.0481 39.1261 -102.0471 39.0361 -102.0481 38.6921 -102.0471 38.6151 -102.0471 38.2631 -102.0451 38.2531 -102.0451 37.7341 -102.0431 37.6411 -102.0431</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Colorado</topp:STATE_NAME><topp:STATE_FIPS>08</topp:STATE_FIPS><topp:SUB_REGION>Mtn</topp:SUB_REGION><topp:STATE_ABBR>CO</topp:STATE_ABBR><topp:LAND_KM>268659.501</topp:LAND_KM><topp:WATER_KM>960.364</topp:WATER_KM><topp:PERSONS>3294394.0</topp:PERSONS><topp:FAMILIES>854214.0</topp:FAMILIES><topp:HOUSHOLD>1282489.0</topp:HOUSHOLD><topp:MALE>1631295.0</topp:MALE><topp:FEMALE>1663099.0</topp:FEMALE><topp:WORKERS>1233023.0</topp:WORKERS><topp:DRVALONE>1216639.0</topp:DRVALONE><topp:CARPOOL>210274.0</topp:CARPOOL><topp:PUBTRANS>46983.0</topp:PUBTRANS><topp:EMPLOYED>1633281.0</topp:EMPLOYED><topp:UNEMPLOY>99438.0</topp:UNEMPLOY><topp:SERVICE>421079.0</topp:SERVICE><topp:MANUAL>181760.0</topp:MANUAL><topp:P_MALE>0.495</topp:P_MALE><topp:P_FEMALE>0.505</topp:P_FEMALE><topp:SAMP_POP>512677.0</topp:SAMP_POP></topp:states><topp:states fid="states.7"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>36.6551 -86.5101 36.6521 -86.7701 36.6501 -87.0681 36.6511 -87.1121 36.6491 -87.3461 36.6451 -87.6401 36.6441 -87.6931 36.6411 -87.8531 36.6691 -87.8701 36.6791 -88.0711 36.5821 -88.0411 36.5381 -88.0351 36.4961 -88.0421 36.4981 -88.4951 36.4991 -88.5121 36.4981 -88.8101 36.4991 -88.8261 36.4991 -88.8301 36.5021 -89.3461 36.5021 -89.4141 36.5101 -89.4181 36.6161 -89.3731 36.6251 -89.3631 36.6281 -89.3421 36.6221 -89.3221 36.5751 -89.2831 36.5691 -89.2411 36.5811 -89.2101 36.6311 -89.2001 36.6531 -89.1771 36.6711 -89.1671 36.7131 -89.1971 36.7271 -89.1961 36.7601 -89.1771 36.7591 -89.1511 36.7681 -89.1251 36.7921 -89.1251 36.8041 -89.1641 36.8291 -89.1731 36.8431 -89.1661 36.8661 -89.1291 36.9531 -89.1041 36.9771 -89.1071 36.9881 -89.1291 36.9981 -89.1501 37.0251 -89.1741 37.0641 -89.1691 37.0931 -89.1461 37.1121 -89.1161 37.1851 -89.0651 37.2201 -88.9931 37.2181 -88.9321 37.2021 -88.8631 37.1521 -88.7461 37.1411 -88.7391 37.1351 -88.6881 37.1091 -88.6141 37.0721 -88.5591 37.0641 -88.5171 37.0681 -88.4901 37.0721 -88.4761 37.0981 -88.4501 37.1561 -88.4221 37.2051 -88.4501 37.2571 -88.5011 37.2961 -88.5111 37.4001 -88.4671 37.4201 -88.4191 37.4091 -88.3591 37.4421 -88.3111 37.4761 -88.0871 37.5101 -88.0711 37.5831 -88.1341 37.6281 -88.1571 37.6601 -88.1591 37.7001 -88.1331 37.7351 -88.0721 37.8051 -88.0351 37.8011 -88.0111 37.7761 -87.9581 37.7991 -87.9391 37.8091 -87.9201 37.8381 -87.9101 37.8751 -87.9361 37.9041 -87.9341 37.9191 -87.9211 37.9241 -87.8991 37.8901 -87.8571 37.8781 -87.8231 37.8981 -87.7531 37.8941 -87.7281 37.8991 -87.7091 37.8971 -87.6791 37.8361 -87.6841 37.8281 -87.6511 37.8431 -87.6071 37.8641 -87.5931 37.8901 -87.5941 37.9231 -87.6271 37.9711 -87.6041 37.9151 -87.5041 37.9361 -87.4521 37.9341 -87.3871 37.8931 -87.3101 37.8701 -87.2721 37.8491 -87.2261 37.8381 -87.1751 37.8261 -87.1581 37.7891 -87.1311 37.7841 -87.1061 37.8071 -87.0711 37.9071 -87.0361 37.9241 -87.0131 37.9301 -86.9891 37.9371 -86.9311 37.9531 -86.9001 37.9861 -86.8631 37.9911 -86.8261 37.9781 -86.8021 37.8981 -86.7531 37.8941 -86.7281 37.9111 -86.6891 37.9131 -86.6681 37.9021 -86.6601 37.8601 -86.6701 37.8471 -86.6651 37.8451 -86.6451 37.8571 -86.6141 37.9211 -86.5981 37.9251 -86.5811 37.9211 -86.5411 37.9271 -86.5221 37.9421 -86.5161 37.9871 -86.5301 38.0181 -86.5271 38.0461 -86.5191 38.0511 -86.5031 38.0591 -86.4581 38.0751 -86.4421 38.0881 -86.4421 38.1111 -86.4741 38.1291 -86.4641 38.1291 -86.4521 38.1081 -86.4071 38.1231 -86.3931 38.1341 -86.3441 38.1431 -86.3351 38.1551 -86.3431 38.1671 -86.3871 38.1941 -86.3881 38.1931 -86.3641 38.1771 -86.3411 38.1501 -86.2971 38.0781 -86.2911 38.0581 -86.2771 38.0401 -86.2521 38.0171 -86.1901 38.0111 -86.1051 37.9661 -86.0521 37.9921 -86.0311 38.0011 -86.0061 38.0111 -85.9581 38.0331 -85.9301 38.0641 -85.9141 38.1791 -85.9121 38.2381 -85.8521 38.2761 -85.8391 38.2861 -85.8061 38.2821 -85.7861 38.2701 -85.7461 38.3001 -85.6811 38.3371 -85.6541 38.3831 -85.6431 38.4461 -85.6121 38.4711 -85.5071 38.5181 -85.4661 38.5361 -85.4321 38.5611 -85.4171 38.5841 -85.4241 38.6941 -85.4531 38.7241 -85.4461 38.7381 -85.4181 38.7361 -85.3351 38.7441 -85.2711 38.6951 -85.2051 38.6951 -85.1601 38.7141 -85.1191 38.7501 -85.0681 38.7641 -85.0251 38.7801 -84.9751 38.7931 -84.8181 38.8341 -84.8241 38.8661 -84.7871 38.8841 -84.7881 38.8971 -84.8031 38.9011 -84.8591 38.9091 -84.8751 38.9271 -84.8751 38.9541 -84.8461 38.9821 -84.8341 39.0051 -84.8441 39.0321 -84.8761 39.0501 -84.8901 39.0641 -84.8861 39.1031 -84.8271 39.1021 -84.8111 39.1061 -84.7891 39.1421 -84.7421 39.0891 -84.6671 39.0741 -84.6221 39.0701 -84.5931 39.0941 -84.5151 39.1071 -84.4921 39.1111 -84.4441 39.0841 -84.4251 39.0471 -84.4191 39.0351 -84.3911 39.0371 -84.3451 39.0141 -84.3131 38.9441 -84.2901 38.9171 -84.2611 38.8741 -84.2351 38.8121 -84.2281 38.7881 -84.1761 38.7651 -84.0881 38.7631 -84.0531 38.7771 -83.9621 38.7571 -83.9121 38.7441 -83.8571 38.7111 -83.8371 38.6931 -83.7901 38.6501 -83.7701 38.6351 -83.7121 38.6201 -83.6781 38.6231 -83.6551 38.6351 -83.6431 38.6641 -83.6331 38.6771 -83.6181 38.6961 -83.5261 38.6901 -83.5001 38.6631 -83.4531 38.6541 -83.3711 38.6311 -83.3301 38.6061 -83.3201 38.5961 -83.3061 38.5961 -83.2901 38.6091 -83.2721 38.6241 -83.2451 38.6091 -83.1821 38.6191 -83.1431 38.6641 -83.1111 38.6851 -83.0601 38.7141 -83.0271 38.7191 -82.9721 38.7461 -82.9211 38.7421 -82.8901 38.7181 -82.8731 38.6831 -82.8801 38.6521 -82.8601 38.6001 -82.8531 38.5711 -82.8271 38.5571 -82.8021 38.5521 -82.7421 38.5391 -82.6951 38.5021 -82.6691 38.4721 -82.6131 38.4121 -82.5861 38.3681 -82.5981 38.3071 -82.5721 38.2921 -82.5801 38.2551 -82.5741 38.2451 -82.5891 38.2381 -82.6161 38.1931 -82.6061 38.1781 -82.6131 38.1691 -82.6471 38.1461 -82.6461 38.1091 -82.5931 38.0151 -82.5241 37.9751 -82.4751 37.9541 -82.4801 37.9421 -82.4931 37.9221 -82.5001 37.8941 -82.4371 37.8721 -82.4211 37.8111 -82.4051 37.7841 -82.3391 37.7581 -82.3191 37.7441 -82.3291 37.6681 -82.2951 37.6561 -82.2381 37.6231 -82.2051 37.6401 -82.1851 37.5931 -82.1591 37.5901 -82.1311 37.5691 -82.1371 37.5651 -82.1461 37.5571 -82.1421 37.5481 -82.0841 37.5251 -82.0551 37.5511 -82.0491 37.5301 -82.0261 37.5431 -81.9761 37.5311 -81.9591 37.3041 -82.2891 37.2601 -82.3531 37.2501 -82.4061 37.1991 -82.5501 37.1931 -82.5681 37.1091 -82.7191 37.0931 -82.7211 37.0751 -82.7091 37.0651 -82.7201 37.0331 -82.7231 37.0051 -82.8121 36.9741 -82.8661 36.9321 -82.8601 36.8931 -82.8781 36.8631 -82.9501 36.8581 -83.0461 36.8501 -83.0681 36.7791 -83.1281 36.7511 -83.1241 36.7391 -83.1381 36.7341 -83.2031 36.7091 -83.3211 36.6881 -83.3851 36.6721 -83.4041 36.6611 -83.4601 36.6611 -83.5301 36.6161 -83.6461 36.5981 -83.6751 36.5841 -83.6951 36.5911 -83.9351 36.5921 -84.0061 36.5951 -84.2541 36.5951 -84.2561 36.6051 -84.7811 36.6051 -84.7911 36.6201 -84.9981 36.6251 -85.2721 36.6261 -85.3001 36.6181 -85.4371 36.6261 -85.7851 36.6331 -85.9801 36.6431 -86.1991 36.6501 -86.4151 36.6551 -86.5101</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>36.4981 -89.5331 36.5181 -89.5661 36.5411 -89.5681 36.5571 -89.5561 36.5641 -89.5301 36.5591 -89.4931 36.5471 -89.4811 36.5251 -89.4711 36.5041 -89.4811 36.4981 -89.4751 36.4981 -89.5331</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Kentucky</topp:STATE_NAME><topp:STATE_FIPS>21</topp:STATE_FIPS><topp:SUB_REGION>E S Cen</topp:SUB_REGION><topp:STATE_ABBR>KY</topp:STATE_ABBR><topp:LAND_KM>103961.904</topp:LAND_KM><topp:WATER_KM>1772.542</topp:WATER_KM><topp:PERSONS>4551524.0</topp:PERSONS><topp:FAMILIES>1237346.0</topp:FAMILIES><topp:HOUSHOLD>1718663.0</topp:HOUSHOLD><topp:MALE>2195130.0</topp:MALE><topp:FEMALE>2356394.0</topp:FEMALE><topp:WORKERS>1656590.0</topp:WORKERS><topp:DRVALONE>1502949.0</topp:DRVALONE><topp:CARPOOL>273091.0</topp:CARPOOL><topp:PUBTRANS>48158.0</topp:PUBTRANS><topp:EMPLOYED>1970934.0</topp:EMPLOYED><topp:UNEMPLOY>148125.0</topp:UNEMPLOY><topp:SERVICE>556744.0</topp:SERVICE><topp:MANUAL>361621.0</topp:MANUAL><topp:P_MALE>0.482</topp:P_MALE><topp:P_FEMALE>0.518</topp:P_FEMALE><topp:SAMP_POP>646517.0</topp:SAMP_POP></topp:states><topp:states fid="states.8"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>37.0011 -95.0711 37.0001 -95.4061 37.0001 -95.5251 36.9981 -95.7851 37.0001 -95.9571 36.9981 -96.0051 37.0001 -96.5181 37.0001 -96.7481 36.9991 -97.1371 36.9961 -97.4651 36.9981 -97.8031 36.9981 -98.1041 36.9991 -98.3461 36.9981 -98.5391 36.9981 -98.9991 36.9941 -99.4371 36.9951 -99.5441 36.9951 -99.9981 36.9971 -100.0881 36.9971 -100.6331 36.9961 -100.9501 36.9971 -101.0711 36.9961 -101.5531 36.9881 -102.0241 36.9881 -102.0361 37.3861 -102.0411 37.6411 -102.0431 37.7341 -102.0431 38.2531 -102.0451 38.2631 -102.0451 38.6151 -102.0471 38.6921 -102.0471 39.0361 -102.0481 39.1261 -102.0471 39.5621 -102.0481 39.5681 -102.0481 39.9981 -102.0511 40.0011 -101.4061 40.0011 -101.3211 40.0001 -100.7541 39.9991 -100.7341 40.0001 -100.1901 40.0001 -100.1801 40.0021 -99.6271 39.9991 -99.1771 39.9981 -99.0641 39.9981 -98.7201 39.9971 -98.5041 39.9981 -98.2631 39.9981 -97.9291 39.9991 -97.8161 39.9971 -97.3611 39.9961 -96.9071 39.9941 -96.8011 39.9941 -96.4531 39.9941 -96.2401 39.9951 -96.0001 39.9931 -95.7801 39.9921 -95.3291 39.9991 -95.3081 39.9421 -95.2401 39.9381 -95.2071 39.9101 -95.1931 39.9081 -95.1501 39.8691 -95.1001 39.8661 -95.0621 39.8771 -95.0331 39.8961 -95.0211 39.9001 -94.9641 39.8961 -94.9371 39.8491 -94.9361 39.8331 -94.9231 39.8281 -94.8981 39.8171 -94.8881 39.7931 -94.8991 39.7821 -94.9331 39.7751 -94.9341 39.7571 -94.9211 39.7601 -94.8761 39.7541 -94.8701 39.7391 -94.8771 39.7261 -94.9051 39.7271 -94.9301 39.7361 -94.9521 39.7321 -94.9611 39.6841 -94.9781 39.6611 -95.0281 39.6251 -95.0551 39.5861 -95.0531 39.5601 -95.1081 39.5321 -95.1011 39.4851 -95.0471 39.4621 -95.0401 39.4391 -94.9851 39.4111 -94.9581 39.3811 -94.9251 39.3801 -94.8981 39.3401 -94.9111 39.3231 -94.9071 39.2861 -94.8801 39.2611 -94.8331 39.2111 -94.8201 39.1961 -94.7891 39.1711 -94.7301 39.1741 -94.6751 39.1581 -94.6461 39.1511 -94.6121 39.1411 -94.6001 39.1121 -94.6071 39.0441 -94.6091 38.8371 -94.6121 38.7371 -94.6121 38.4711 -94.6181 38.3921 -94.6181 38.0551 -94.6171 38.0301 -94.6161 37.6791 -94.6191 37.6501 -94.6181 37.3601 -94.6181 37.3271 -94.6181 37.0601 -94.6201 36.9961 -94.6201 37.0001 -95.0321 37.0011 -95.0711</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Kansas</topp:STATE_NAME><topp:STATE_FIPS>20</topp:STATE_FIPS><topp:SUB_REGION>W N Cen</topp:SUB_REGION><topp:STATE_ABBR>KS</topp:STATE_ABBR><topp:LAND_KM>211921.641</topp:LAND_KM><topp:WATER_KM>1188.865</topp:WATER_KM><topp:PERSONS>2477574.0</topp:PERSONS><topp:FAMILIES>658600.0</topp:FAMILIES><topp:HOUSHOLD>944726.0</topp:HOUSHOLD><topp:MALE>1214645.0</topp:MALE><topp:FEMALE>1262929.0</topp:FEMALE><topp:WORKERS>907383.0</topp:WORKERS><topp:DRVALONE>928575.0</topp:DRVALONE><topp:CARPOOL>135598.0</topp:CARPOOL><topp:PUBTRANS>7585.0</topp:PUBTRANS><topp:EMPLOYED>1172214.0</topp:EMPLOYED><topp:UNEMPLOY>57772.0</topp:UNEMPLOY><topp:SERVICE>346339.0</topp:SERVICE><topp:MANUAL>166429.0</topp:MANUAL><topp:P_MALE>0.49</topp:P_MALE><topp:P_FEMALE>0.51</topp:P_FEMALE><topp:SAMP_POP>453411.0</topp:SAMP_POP></topp:states><topp:states fid="states.9"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>36.5461 -79.1441 36.5491 -79.2171 36.5471 -79.5101 36.5471 -79.7171 36.5451 -80.0241 36.5471 -80.0481 36.5511 -80.4351 36.5571 -80.6111 36.5631 -80.8381 36.5651 -80.9031 36.5721 -81.3451 36.5891 -81.6691 36.6071 -81.6521 36.6111 -81.8291 36.6131 -81.9181 36.5951 -81.9291 36.5951 -82.1541 36.5931 -82.2161 36.5911 -82.2961 36.5911 -82.6101 36.5901 -82.8491 36.5911 -82.9861 36.5881 -83.2111 36.5891 -83.2481 36.6001 -83.2751 36.5981 -83.4641 36.5981 -83.6751 36.6161 -83.6461 36.6611 -83.5301 36.6611 -83.4601 36.6721 -83.4041 36.6881 -83.3851 36.7091 -83.3211 36.7341 -83.2031 36.7391 -83.1381 36.7511 -83.1241 36.7791 -83.1281 36.8501 -83.0681 36.8581 -83.0461 36.8631 -82.9501 36.8931 -82.8781 36.9321 -82.8601 36.9741 -82.8661 37.0051 -82.8121 37.0331 -82.7231 37.0651 -82.7201 37.0751 -82.7091 37.0931 -82.7211 37.1091 -82.7191 37.1931 -82.5681 37.1991 -82.5501 37.2501 -82.4061 37.2601 -82.3531 37.3041 -82.2891 37.5311 -81.9591 37.5061 -81.9351 37.4921 -81.9481 37.4821 -81.9761 37.4661 -81.9881 37.4151 -81.9201 37.3711 -81.9261 37.3401 -81.8971 37.3251 -81.8631 37.3061 -81.8581 37.2851 -81.8391 37.2791 -81.8151 37.2871 -81.7921 37.2721 -81.7521 37.2501 -81.7381 37.2351 -81.7011 37.2041 -81.6661 37.2061 -81.5561 37.2341 -81.5051 37.2521 -81.4951 37.2541 -81.4751 37.2821 -81.4031 37.3111 -81.3911 37.3381 -81.3581 37.2931 -81.3121 37.2401 -81.2231 37.2741 -81.1401 37.2851 -81.0251 37.3061 -80.9861 37.2961 -80.9781 37.2911 -80.9681 37.3011 -80.9341 37.3391 -80.8551 37.3501 -80.8481 37.3881 -80.8771 37.4231 -80.8501 37.4121 -80.7991 37.3911 -80.7991 37.3861 -80.7701 37.3711 -80.7631 37.3781 -80.7471 37.3871 -80.7461 37.3921 -80.7291 37.3881 -80.7051 37.4451 -80.5971 37.4691 -80.5421 37.4741 -80.5081 37.4601 -80.4881 37.4331 -80.4871 37.4221 -80.4741 37.4341 -80.4251 37.4651 -80.3881 37.4751 -80.3521 37.4911 -80.3471 37.5111 -80.2881 37.5361 -80.2801 37.5281 -80.3081 37.5331 -80.3261 37.5661 -80.3161 37.5961 -80.2461 37.6241 -80.2191 37.6401 -80.2541 37.6401 -80.3011 37.6521 -80.3051 37.6711 -80.2951 37.6821 -80.3031 37.7251 -80.2501 37.7571 -80.2541 37.7781 -80.2201 37.8021 -80.2231 37.8421 -80.1711 37.8601 -80.1721 37.8771 -80.1601 37.8911 -80.1181 37.9141 -80.1061 37.9551 -80.0551 37.9891 -80.0001 38.0381 -79.9661 38.0671 -79.9571 38.1031 -79.9281 38.1211 -79.9351 38.1621 -79.9101 38.1791 -79.9161 38.2501 -79.8311 38.2681 -79.7931 38.2841 -79.7861 38.2981 -79.8031 38.3141 -79.8001 38.3531 -79.7641 38.3511 -79.7331 38.3941 -79.7201 38.4301 -79.6841 38.5001 -79.6921 38.5201 -79.6651 38.5501 -79.6691 38.5921 -79.6421 38.5531 -79.5361 38.4621 -79.4861 38.4121 -79.3171 38.4371 -79.2721 38.4801 -79.2311 38.6581 -79.1271 38.6631 -79.1211 38.6591 -79.0881 38.7071 -79.0871 38.7611 -79.0561 38.7901 -79.0551 38.7991 -79.0331 38.8461 -78.9871 38.7631 -78.8661 38.8331 -78.8161 38.8801 -78.7931 38.9111 -78.7491 38.9291 -78.7371 38.9301 -78.7241 38.9041 -78.7191 38.9211 -78.6801 38.9501 -78.6471 38.9791 -78.6311 38.9671 -78.5981 39.0131 -78.5531 39.0231 -78.5491 39.0351 -78.5641 39.0571 -78.5361 39.0931 -78.5011 39.1111 -78.4851 39.1181 -78.4481 39.1481 -78.4301 39.1701 -78.4021 39.1971 -78.4241 39.2121 -78.4231 39.2441 -78.3991 39.2571 -78.4131 39.3411 -78.3411 39.3501 -78.3441 39.3611 -78.3651 39.3801 -78.3501 39.4561 -78.3471 39.4231 -78.2771 39.3911 -78.2291 39.2651 -78.0331 39.1321 -77.8301 39.1411 -77.8201 39.1961 -77.8051 39.2461 -77.7681 39.2841 -77.7591 39.3171 -77.7271 39.3181 -77.6791 39.2991 -77.6161 39.2981 -77.5681 39.2681 -77.5421 39.2491 -77.4941 39.2291 -77.4641 39.2181 -77.4611 39.1761 -77.4781 39.1571 -77.5161 39.1161 -77.5131 39.1031 -77.4791 39.0801 -77.4591 39.0661 -77.4331 39.0681 -77.3461 39.0621 -77.3241 39.0271 -77.2551 38.9751 -77.2431 38.9641 -77.1521 38.9321 -77.1221 38.9151 -77.0781 38.8861 -77.0671 38.8621 -77.0391 38.8381 -77.0401 38.8291 -77.0451 38.8131 -77.0351 38.7881 -77.0451 38.7181 -77.0461 38.7121 -77.0571 38.7151 -77.0811 38.7031 -77.0931 38.6771 -77.1251 38.6481 -77.1291 38.6221 -77.1971 38.6601 -77.1941 38.6501 -77.2271 38.5011 -77.3031 38.4361 -77.3381 38.3621 -77.2891 38.3431 -77.3211 38.3311 -77.2401 38.3751 -77.0541 38.2801 -76.9991 38.2021 -76.9361 38.1201 -76.5951 38.0741 -76.5491 38.0251 -76.5581 38.0031 -76.5731 38.0121 -76.5241 37.9561 -76.3671 37.8901 -76.2591 37.8501 -76.2511 37.7981 -76.3241 37.7191 -76.3091 37.7001 -76.3571 37.6771 -76.3231 37.6221 -76.3441 37.6561 -76.5071 37.7701 -76.5801 37.7961 -76.6311 37.9161 -76.7711 37.9191 -76.8181 37.7981 -76.7321 37.7741 -76.6811 37.6411 -76.5691 37.5511 -76.3141 37.5251 -76.3481 37.5521 -76.5121 37.5151 -76.4341 37.5151 -76.3551 37.3901 -76.2541 37.3301 -76.2751 37.3341 -76.3001 37.3931 -76.3391 37.4571 -76.4461 37.4181 -76.4631 37.4121 -76.4171 37.3731 -76.4031 37.3771 -76.4551 37.2931 -76.3921 37.2551 -76.4611 37.4121 -76.6531 37.4181 -76.7041 37.3711 -76.6691 37.2911 -76.5951 37.2071 -76.4241 37.1521 -76.4121 37.1731 -76.3961 37.1461 -76.3631 37.1771 -76.3371 37.1221 -76.2851 37.1071 -76.3951 37.0741 -76.2781 37.0201 -76.2931 36.9901 -76.3841 36.9651 -76.4261 37.0671 -76.5311 37.0881 -76.5151 37.1171 -76.5641 37.0801 -76.5681 37.1321 -76.6241 37.1781 -76.6101 37.2251 -76.6481 37.2321 -76.6971 37.1931 -76.7461 37.2401 -76.7951 37.2431 -76.8571 37.3221 -76.8751 37.2591 -76.8781 37.2361 -76.9411 37.2011 -76.9001 37.2071 -76.7971 37.1501 -76.7291 37.1971 -76.6851 37.1471 -76.6711 37.0541 -76.6651 37.0241 -76.5771 36.9941 -76.6131 37.0061 -76.5551 36.9611 -76.4891 36.9121 -76.5171 36.9191 -76.4821 36.8951 -76.4861 36.8411 -76.5601 36.7951 -76.5611 36.8691 -76.5071 36.9011 -76.4101 36.9131 -76.3481 36.8601 -76.3411 36.8351 -76.3941 36.8261 -76.4011 36.8451 -76.3171 36.8281 -76.2921 36.9421 -76.3071 36.9621 -76.2841 36.9351 -76.2021 36.9041 -76.1911 36.9311 -76.1181 36.9231 -75.9951 36.5551 -75.8781 36.5561 -75.9011 36.5991 -75.8921 36.7211 -75.9501 36.5561 -75.9981 36.5561 -76.0271 36.6031 -76.0611 36.5561 -76.0451 36.5571 -76.1271 36.5561 -76.3301 36.5551 -76.4971 36.5551 -76.5631 36.5541 -76.9211 36.5541 -76.9241 36.5561 -77.1771 36.5531 -77.3201 36.5531 -77.7631 36.5521 -77.8981 36.5521 -78.0511 36.5451 -78.3211 36.5411 -78.4581 36.5461 -78.7371 36.5431 -78.7961 36.5461 -79.1441</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.0271 -75.2701 38.0281 -75.2421 37.9621 -75.2981 37.8881 -75.3391 37.8751 -75.3861 37.9011 -75.3441 37.9001 -75.3781 37.9181 -75.3461 38.0271 -75.2701</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>37.5521 -75.8671 37.5611 -75.9411 37.5851 -75.9291 37.5801 -75.8871 37.5921 -75.9051 37.7111 -75.7991 37.7891 -75.7821 37.8241 -75.6961 37.8581 -75.6861 37.9301 -75.7331 37.9411 -75.6581 37.9701 -75.6481 37.9961 -75.6261 38.0161 -75.3721 37.6971 -75.6171 37.6771 -75.5891 37.5891 -75.6991 37.5591 -75.6501 37.5581 -75.7271 37.5101 -75.7561 37.4931 -75.7051 37.4691 -75.8131 37.4261 -75.8201 37.4081 -75.7901 37.4181 -75.8261 37.3671 -75.8971 37.1421 -75.9311 37.1261 -75.9701 37.3081 -76.0181 37.4841 -75.9341 37.4791 -75.9651 37.5211 -75.9541 37.5561 -75.9301 37.5521 -75.8671</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Virginia</topp:STATE_NAME><topp:STATE_FIPS>51</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>VA</topp:STATE_ABBR><topp:LAND_KM>102537.328</topp:LAND_KM><topp:WATER_KM>4263.82</topp:WATER_KM><topp:PERSONS>6180651.0</topp:PERSONS><topp:FAMILIES>1627615.0</topp:FAMILIES><topp:HOUSHOLD>2289067.0</topp:HOUSHOLD><topp:MALE>3030948.0</topp:MALE><topp:FEMALE>3149703.0</topp:FEMALE><topp:WORKERS>2343200.0</topp:WORKERS><topp:DRVALONE>2278600.0</topp:DRVALONE><topp:CARPOOL>499251.0</topp:CARPOOL><topp:PUBTRANS>125792.0</topp:PUBTRANS><topp:EMPLOYED>3025109.0</topp:EMPLOYED><topp:UNEMPLOY>141926.0</topp:UNEMPLOY><topp:SERVICE>777181.0</topp:SERVICE><topp:MANUAL>420070.0</topp:MANUAL><topp:P_MALE>0.49</topp:P_MALE><topp:P_FEMALE>0.51</topp:P_FEMALE><topp:SAMP_POP>898089.0</topp:SAMP_POP></topp:states><topp:states fid="states.10"><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>36.9531 -89.1041 36.8661 -89.1291 36.8431 -89.1661 36.8291 -89.1731 36.8041 -89.1641 36.7921 -89.1251 36.7681 -89.1251 36.7591 -89.1511 36.7601 -89.1771 36.7271 -89.1961 36.7131 -89.1971 36.6711 -89.1671 36.6531 -89.1771 36.6311 -89.2001 36.5811 -89.2101 36.5691 -89.2411 36.5751 -89.2831 36.6221 -89.3221 36.6281 -89.3421 36.6251 -89.3631 36.6161 -89.3731 36.5101 -89.4181 36.5021 -89.4141 36.4561 -89.4481 36.4451 -89.4701 36.4651 -89.4911 36.4981 -89.4751 36.5041 -89.4811 36.5251 -89.4711 36.5471 -89.4811 36.5591 -89.4931 36.5641 -89.5301 36.5571 -89.5561 36.5411 -89.5681 36.5181 -89.5661 36.4981 -89.5331 36.4711 -89.5161 36.4401 -89.5451 36.4011 -89.5201 36.3551 -89.5191 36.3451 -89.5441 36.3541 -89.6051 36.3341 -89.6221 36.3081 -89.6061 36.2801 -89.5421 36.2641 -89.5351 36.2571 -89.5411 36.2401 -89.6181 36.2541 -89.6701 36.2521 -89.6941 36.2401 -89.6951 36.2201 -89.6761 36.1831 -89.6181 36.1521 -89.5891 36.1291 -89.5891 36.0991 -89.6671 36.0821 -89.6781 36.0251 -89.6881 35.9991 -89.7211 35.9961 -89.9631 35.9911 -90.2831 35.9891 -90.3781 36.0911 -90.3151 36.1151 -90.2841 36.1181 -90.2631 36.1371 -90.2341 36.1611 -90.2321 36.1721 -90.2191 36.1961 -90.1611 36.2121 -90.1311 36.2571 -90.1091 36.2721 -90.0661 36.3001 -90.0491 36.3251 -90.0671 36.3621 -90.0501 36.3821 -90.0521 36.3971 -90.0801 36.4041 -90.1161 36.4221 -90.1231 36.4531 -90.1171 36.4571 -90.1371 36.4911 -90.1501 36.4921 -90.2241 36.4901 -90.5811 36.4891 -90.8041 36.4871 -91.1331 36.4911 -91.4111 36.4901 -91.4521 36.4901 -91.6881 36.4911 -92.1271 36.4911 -92.1461 36.4901 -92.5221 36.4891 -92.7771 36.4891 -92.8521 36.4901 -93.2971 36.4901 -93.3281 36.4891 -93.5961 36.4891 -93.8571 36.4901 -94.0801 36.4891 -94.6171 36.6701 -94.6201 36.7631 -94.6211 36.9961 -94.6201 37.0601 -94.6201 37.3271 -94.6181 37.3601 -94.6181 37.6501 -94.6181 37.6791 -94.6191 38.0301 -94.6161 38.0551 -94.6171 38.3921 -94.6181 38.4711 -94.6181 38.7371 -94.6121 38.8371 -94.6121 39.0441 -94.6091 39.1121 -94.6071 39.1411 -94.6001 39.1511 -94.6121 39.1581 -94.6461 39.1741 -94.6751 39.1711 -94.7301 39.1961 -94.7891 39.2111 -94.8201 39.2611 -94.8331 39.2861 -94.8801 39.3231 -94.9071 39.3401 -94.9111 39.3801 -94.8981 39.3811 -94.9251 39.4111 -94.9581 39.4391 -94.9851 39.4621 -95.0401 39.4851 -95.0471 39.5321 -95.1011 39.5601 -95.1081 39.5861 -95.0531 39.6251 -95.0551 39.6611 -95.0281 39.6841 -94.9781 39.7321 -94.9611 39.7361 -94.9521 39.7271 -94.9301 39.7261 -94.9051 39.7391 -94.8771 39.7541 -94.8701 39.7601 -94.8761 39.7571 -94.9211 39.7751 -94.9341 39.7821 -94.9331 39.7931 -94.8991 39.8171 -94.8881 39.8281 -94.8981 39.8331 -94.9231 39.8491 -94.9361 39.8961 -94.9371 39.9001 -94.9641 39.8961 -95.0211 39.8771 -95.0331 39.8661 -95.0621 39.8691 -95.1001 39.9081 -95.1501 39.9101 -95.1931 39.9381 -95.2071 39.9421 -95.2401 39.9991 -95.3081 40.0241 -95.3441 40.0281 -95.3701 40.0431 -95.3901 40.0481 -95.4131 40.0801 -95.4031 40.0951 -95.3841 40.1151 -95.3921 40.1311 -95.4221 40.1731 -95.4601 40.2131 -95.4661 40.2261 -95.4761 40.2661 -95.5461 40.3091 -95.5951 40.3091 -95.6461 40.3221 -95.6451 40.3311 -95.6171 40.3461 -95.6151 40.3581 -95.6331 40.3961 -95.6361 40.4851 -95.6951 40.5121 -95.6841 40.5301 -95.6571 40.5581 -95.6621 40.5651 -95.6751 40.5611 -95.6871 40.5241 -95.6911 40.5321 -95.7361 40.5491 -95.7631 40.5891 -95.7671 40.5841 -95.3821 40.5811 -95.2171 40.5771 -94.9201 40.5751 -94.6391 40.5741 -94.4841 40.5701 -94.2381 40.5741 -94.0171 40.5781 -93.7861 40.5801 -93.5621 40.5801 -93.3701 40.5841 -93.1001 40.5891 -92.7171 40.5911 -92.6461 40.5991 -92.3611 40.6001 -92.1921 40.6081 -91.9461 40.6091 -91.7411 40.5931 -91.7161 40.5811 -91.6891 40.5511 -91.6911 40.5321 -91.6221 40.5041 -91.6161 40.4841 -91.5851 40.4631 -91.5791 40.4551 -91.5331 40.4411 -91.5381 40.4351 -91.5291 40.4101 -91.5271 40.4051 -91.5001 40.3901 -91.4901 40.3901 -91.4761 40.3711 -91.4481 40.3091 -91.4861 40.2511 -91.4981 40.2001 -91.5061 40.1341 -91.5161 40.0661 -91.5041 40.0051 -91.4871 39.9461 -91.4471 39.9211 -91.4301 39.9011 -91.4341 39.8851 -91.4501 39.8631 -91.4491 39.8031 -91.3811 39.7611 -91.3731 39.7241 -91.3671 39.6851 -91.3171 39.6001 -91.2031 39.5521 -91.1561 39.5281 -91.0931 39.4731 -91.0641 39.4441 -91.0361 39.4001 -90.9471 39.3501 -90.8501 39.2961 -90.7791 39.2471 -90.7381 39.2241 -90.7321 39.1951 -90.7181 39.1441 -90.7161 39.0931 -90.6901 39.0581 -90.7071 39.0371 -90.7061 38.9351 -90.6681 38.8801 -90.6271 38.8711 -90.5701 38.8911 -90.5301 38.9591 -90.4691 38.9621 -90.4131 38.9241 -90.3191 38.9241 -90.2781 38.9141 -90.2431 38.8531 -90.1321 38.8301 -90.1131 38.8001 -90.1211 38.7851 -90.1351 38.7731 -90.1631 38.7231 -90.1961 38.7001 -90.2021 38.6581 -90.1831 38.6101 -90.1831 38.5621 -90.2401 38.5321 -90.2611 38.5181 -90.2651 38.4271 -90.3011 38.3901 -90.3391 38.3651 -90.3581 38.3231 -90.3691 38.2341 -90.3641 38.1881 -90.3361 38.1661 -90.2891 38.1221 -90.2541 38.0881 -90.2071 38.0531 -90.1341 38.0321 -90.1191 37.9931 -90.0411 37.9691 -90.0101 37.9631 -89.9581 37.9111 -89.9781 37.8781 -89.9371 37.8751 -89.9001 37.8911 -89.8661 37.9051 -89.8611 37.9051 -89.8511 37.8401 -89.7281 37.8041 -89.6911 37.7831 -89.6751 37.7451 -89.6661 37.7061 -89.5811 37.6941 -89.5211 37.6791 -89.5131 37.6501 -89.5191 37.6151 -89.5131 37.5711 -89.5241 37.4911 -89.4941 37.4531 -89.4531 37.4111 -89.4271 37.3551 -89.4351 37.3391 -89.4681 37.3291 -89.5001 37.3041 -89.5131 37.2761 -89.5131 37.2561 -89.4891 37.2531 -89.4651 37.2241 -89.4681 37.1651 -89.4401 37.1371 -89.4231 37.0991 -89.3791 37.0491 -89.3821 37.0091 -89.3101 36.9991 -89.2821 37.0081 -89.2621 37.0271 -89.2641 37.0601 -89.3091 37.0851 -89.3031 37.0911 -89.2841 37.0871 -89.2641 37.0411 -89.2371 37.0281 -89.2101 36.9861 -89.1931 36.9881 -89.1291 36.9771 -89.1071 36.9531 -89.1041</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Missouri</topp:STATE_NAME><topp:STATE_FIPS>29</topp:STATE_FIPS><topp:SUB_REGION>W N Cen</topp:SUB_REGION><topp:STATE_ABBR>MO</topp:STATE_ABBR><topp:LAND_KM>178445.951</topp:LAND_KM><topp:WATER_KM>2100.115</topp:WATER_KM><topp:PERSONS>5117073.0</topp:PERSONS><topp:FAMILIES>1368334.0</topp:FAMILIES><topp:HOUSHOLD>1961206.0</topp:HOUSHOLD><topp:MALE>2464315.0</topp:MALE><topp:FEMALE>2652758.0</topp:FEMALE><topp:WORKERS>1861192.0</topp:WORKERS><topp:DRVALONE>1816079.0</topp:DRVALONE><topp:CARPOOL>312042.0</topp:CARPOOL><topp:PUBTRANS>47129.0</topp:PUBTRANS><topp:EMPLOYED>2367395.0</topp:EMPLOYED><topp:UNEMPLOY>155388.0</topp:UNEMPLOY><topp:SERVICE>659782.0</topp:SERVICE><topp:MANUAL>386746.0</topp:MANUAL><topp:P_MALE>0.482</topp:P_MALE><topp:P_FEMALE>0.518</topp:P_FEMALE><topp:SAMP_POP>864999.0</topp:SAMP_POP></topp:states></gml:featureMembers>
+--></div>
+<div id="v3/topp-states-wfs.xml"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection numberOfFeatures="3" timeStamp="2008-09-12T00:24:21.013-04:00" xsi:schemaLocation="http://www.openplans.org/topp http://sigma.openplans.org:80/geoserver/wfs?service=WFS&amp;version=1.1.0&amp;request=DescribeFeatureType&amp;typeName=topp:states http://www.opengis.net/wfs http://sigma.openplans.org:80/geoserver/schemas/wfs/1.1.0/wfs.xsd" xmlns:ogc="http://www.opengis.net/ogc" xmlns:opengeo="http://open-geo.com" xmlns:tiger="http://www.census.gov" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:seb="http://seb.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink"><gml:featureMembers><topp:states gml:id="states.1"><gml:boundedBy><gml:Envelope srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:lowerCorner>36.986 -91.516</gml:lowerCorner><gml:upperCorner>42.509 -87.507</gml:upperCorner></gml:Envelope></gml:boundedBy><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>37.511 -88.071 37.476 -88.087 37.442 -88.311 37.409 -88.359 37.421 -88.419 37.401 -88.467 37.296 -88.511 37.257 -88.501 37.205 -88.451 37.156 -88.422 37.098 -88.451 37.072 -88.476 37.068 -88.491 37.064 -88.517 37.072 -88.559 37.109 -88.614 37.135 -88.688 37.141 -88.739 37.152 -88.746 37.202 -88.863 37.218 -88.932 37.221 -88.993 37.185 -89.065 37.112 -89.116 37.093 -89.146 37.064 -89.169 37.025 -89.174 36.998 -89.151 36.988 -89.129 36.986 -89.193 37.028 -89.211 37.041 -89.237 37.087 -89.264 37.091 -89.284 37.085 -89.303 37.061 -89.309 37.027 -89.264 37.008 -89.262 36.999 -89.282 37.009 -89.311 37.049 -89.382 37.099 -89.379 37.137 -89.423 37.165 -89.441 37.224 -89.468 37.253 -89.465 37.256 -89.489 37.276 -89.513 37.304 -89.513 37.329 -89.501 37.339 -89.468 37.355 -89.435 37.411 -89.427 37.453 -89.453 37.491 -89.494 37.571 -89.524 37.615 -89.513 37.651 -89.519 37.679 -89.513 37.694 -89.521 37.706 -89.581 37.745 -89.666 37.783 -89.675 37.804 -89.691 37.841 -89.728 37.905 -89.851 37.905 -89.861 37.891 -89.866 37.875 -89.901 37.878 -89.937 37.911 -89.978 37.963 -89.958 37.969 -90.011 37.993 -90.041 38.032 -90.119 38.053 -90.134 38.088 -90.207 38.122 -90.254 38.166 -90.289 38.188 -90.336 38.234 -90.364 38.323 -90.369 38.365 -90.358 38.391 -90.339 38.427 -90.301 38.518 -90.265 38.532 -90.261 38.562 -90.241 38.611 -90.183 38.658 -90.183 38.701 -90.202 38.723 -90.196 38.773 -90.163 38.785 -90.135 38.801 -90.121 38.831 -90.113 38.853 -90.132 38.914 -90.243 38.924 -90.278 38.924 -90.319 38.962 -90.413 38.959 -90.469 38.891 -90.531 38.871 -90.571 38.881 -90.627 38.935 -90.668 39.037 -90.706 39.058 -90.707 39.093 -90.691 39.144 -90.716 39.195 -90.718 39.224 -90.732 39.247 -90.738 39.296 -90.779 39.351 -90.851 39.401 -90.947 39.444 -91.036 39.473 -91.064 39.528 -91.093 39.552 -91.156 39.601 -91.203 39.685 -91.317 39.724 -91.367 39.761 -91.373 39.803 -91.381 39.863 -91.449 39.885 -91.451 39.901 -91.434 39.921 -91.431 39.946 -91.447 40.005 -91.487 40.066 -91.504 40.134 -91.516 40.201 -91.506 40.251 -91.498 40.309 -91.486 40.371 -91.448 40.386 -91.418 40.392 -91.385 40.402 -91.372 40.447 -91.385 40.503 -91.374 40.528 -91.382 40.547 -91.412 40.572 -91.411 40.603 -91.375 40.639 -91.262 40.643 -91.214 40.656 -91.162 40.682 -91.129 40.705 -91.119 40.761 -91.092 40.833 -91.088 40.879 -91.049 40.923 -90.983 40.951 -90.961 41.071 -90.954 41.104 -90.957 41.144 -90.991 41.165 -91.018 41.176 -91.056 41.231 -91.101 41.267 -91.102 41.334 -91.073 41.401 -91.055 41.423 -91.027 41.431 -91.001 41.421 -90.949 41.444 -90.844 41.449 -90.779 41.451 -90.708 41.462 -90.658 41.509 -90.601 41.525 -90.541 41.527 -90.454 41.543 -90.434 41.567 -90.423 41.586 -90.348 41.602 -90.339 41.649 -90.341 41.722 -90.326 41.756 -90.304 41.781 -90.255 41.806 -90.195 41.931 -90.154 41.983 -90.142 42.033 -90.151 42.061 -90.168 42.103 -90.166 42.121 -90.176 42.122 -90.191 42.159 -90.231 42.197 -90.323 42.211 -90.367 42.242 -90.407 42.263 -90.417 42.341 -90.427 42.361 -90.441 42.388 -90.491 42.421 -90.563 42.461 -90.605 42.475 -90.648 42.494 -90.651 42.509 -90.638 42.508 -90.419 42.504 -89.923 42.503 -89.834 42.497 -89.401 42.497 -89.359 42.491 -88.939 42.491 -88.764 42.489 -88.706 42.491 -88.297 42.489 -88.194 42.489 -87.797 42.314 -87.836 42.156 -87.761 42.059 -87.671 41.847 -87.612 41.723 -87.529 41.469 -87.532 41.301 -87.532 41.173 -87.531 41.009 -87.532 40.745 -87.532 40.494 -87.537 40.483 -87.535 40.166 -87.535 39.887 -87.535 39.609 -87.535 39.477 -87.538 39.351 -87.541 39.338 -87.597 39.307 -87.625 39.297 -87.611 39.281 -87.615 39.258 -87.606 39.248 -87.584 39.208 -87.588 39.198 -87.594 39.196 -87.607 39.168 -87.644 39.146 -87.671 39.131 -87.659 39.113 -87.662 39.103 -87.631 39.088 -87.631 39.084 -87.612 39.062 -87.585 38.995 -87.581 38.994 -87.591 38.977 -87.547 38.963 -87.533 38.931 -87.531 38.904 -87.539 38.869 -87.559 38.857 -87.551 38.795 -87.507 38.776 -87.519 38.769 -87.508 38.736 -87.508 38.685 -87.543 38.672 -87.588 38.642 -87.625 38.622 -87.628 38.599 -87.619 38.593 -87.641 38.573 -87.652 38.547 -87.672 38.515 -87.651 38.501 -87.653 38.504 -87.679 38.481 -87.692 38.466 -87.756 38.457 -87.758 38.445 -87.738 38.417 -87.748 38.378 -87.784 38.352 -87.834 38.286 -87.851 38.285 -87.863 38.316 -87.874 38.315 -87.883 38.301 -87.888 38.281 -87.914 38.302 -87.913 38.304 -87.925 38.241 -87.981 38.234 -87.986 38.201 -87.977 38.171 -87.932 38.157 -87.931 38.136 -87.951 38.131 -87.973 38.103 -88.018 38.092 -88.012 38.096 -87.964 38.073 -87.975 38.054 -88.034 38.045 -88.043 38.038 -88.041 38.033 -88.021 38.008 -88.029 37.975 -88.021 37.956 -88.042 37.934 -88.041 37.929 -88.064 37.944 -88.078 37.923 -88.084 37.917 -88.031 37.905 -88.026 37.896 -88.044 37.906 -88.101 37.895 -88.101 37.867 -88.075 37.843 -88.034 37.827 -88.042 37.831 -88.089 37.817 -88.086 37.805 -88.035 37.735 -88.072 37.701 -88.133 37.661 -88.159 37.628 -88.157 37.583 -88.134 37.511 -88.071</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Illinois</topp:STATE_NAME><topp:STATE_FIPS>17</topp:STATE_FIPS><topp:SUB_REGION>E N Cen</topp:SUB_REGION><topp:STATE_ABBR>IL</topp:STATE_ABBR><topp:LAND_KM>143986.61</topp:LAND_KM><topp:WATER_KM>1993.335</topp:WATER_KM><topp:PERSONS>1.143E7</topp:PERSONS><topp:FAMILIES>2924880.0</topp:FAMILIES><topp:HOUSHOLD>4202240.0</topp:HOUSHOLD><topp:MALE>5552233.0</topp:MALE><topp:FEMALE>5878369.0</topp:FEMALE><topp:WORKERS>4199206.0</topp:WORKERS><topp:DRVALONE>3741715.0</topp:DRVALONE><topp:CARPOOL>652603.0</topp:CARPOOL><topp:PUBTRANS>538071.0</topp:PUBTRANS><topp:EMPLOYED>5417967.0</topp:EMPLOYED><topp:UNEMPLOY>385040.0</topp:UNEMPLOY><topp:SERVICE>1360159.0</topp:SERVICE><topp:MANUAL>828906.0</topp:MANUAL><topp:P_MALE>0.486</topp:P_MALE><topp:P_FEMALE>0.514</topp:P_FEMALE><topp:SAMP_POP>1747776.0</topp:SAMP_POP></topp:states><topp:states gml:id="states.2"><gml:boundedBy><gml:Envelope srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:lowerCorner>38.788 -77.122</gml:lowerCorner><gml:upperCorner>38.993 -76.911</gml:upperCorner></gml:Envelope></gml:boundedBy><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.966 -77.008 38.889 -76.911 38.788 -77.045 38.813 -77.035 38.829 -77.045 38.838 -77.041 38.862 -77.039 38.886 -77.067 38.915 -77.078 38.932 -77.122 38.993 -77.042 38.966 -77.008</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>District of Columbia</topp:STATE_NAME><topp:STATE_FIPS>11</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>DC</topp:STATE_ABBR><topp:LAND_KM>159.055</topp:LAND_KM><topp:WATER_KM>17.991</topp:WATER_KM><topp:PERSONS>606900.0</topp:PERSONS><topp:FAMILIES>122087.0</topp:FAMILIES><topp:HOUSHOLD>249634.0</topp:HOUSHOLD><topp:MALE>282970.0</topp:MALE><topp:FEMALE>323930.0</topp:FEMALE><topp:WORKERS>229975.0</topp:WORKERS><topp:DRVALONE>106694.0</topp:DRVALONE><topp:CARPOOL>36621.0</topp:CARPOOL><topp:PUBTRANS>111422.0</topp:PUBTRANS><topp:EMPLOYED>303994.0</topp:EMPLOYED><topp:UNEMPLOY>23442.0</topp:UNEMPLOY><topp:SERVICE>65498.0</topp:SERVICE><topp:MANUAL>22407.0</topp:MANUAL><topp:P_MALE>0.466</topp:P_MALE><topp:P_FEMALE>0.534</topp:P_FEMALE><topp:SAMP_POP>72696.0</topp:SAMP_POP></topp:states><topp:states gml:id="states.3"><gml:boundedBy><gml:Envelope srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:lowerCorner>38.449 -75.791</gml:lowerCorner><gml:upperCorner>39.841 -75.045</gml:upperCorner></gml:Envelope></gml:boundedBy><topp:the_geom><gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:surfaceMember><gml:Polygon><gml:exterior><gml:LinearRing><gml:posList>38.557 -75.707 38.649 -75.711 38.831 -75.724 39.141 -75.752 39.247 -75.761 39.295 -75.764 39.383 -75.772 39.723 -75.791 39.724 -75.775 39.774 -75.745 39.821 -75.695 39.838 -75.644 39.841 -75.583 39.826 -75.471 39.798 -75.421 39.789 -75.412 39.778 -75.428 39.763 -75.461 39.741 -75.475 39.719 -75.476 39.714 -75.489 39.612 -75.611 39.566 -75.562 39.463 -75.591 39.366 -75.515 39.257 -75.402 39.073 -75.397 39.012 -75.324 38.945 -75.307 38.808 -75.191 38.799 -75.083 38.449 -75.045 38.449 -75.068 38.451 -75.093 38.455 -75.351 38.463 -75.699 38.557 -75.707</gml:posList></gml:LinearRing></gml:exterior></gml:Polygon></gml:surfaceMember></gml:MultiSurface></topp:the_geom><topp:STATE_NAME>Delaware</topp:STATE_NAME><topp:STATE_FIPS>10</topp:STATE_FIPS><topp:SUB_REGION>S Atl</topp:SUB_REGION><topp:STATE_ABBR>DE</topp:STATE_ABBR><topp:LAND_KM>5062.456</topp:LAND_KM><topp:WATER_KM>1385.022</topp:WATER_KM><topp:PERSONS>666168.0</topp:PERSONS><topp:FAMILIES>175867.0</topp:FAMILIES><topp:HOUSHOLD>247497.0</topp:HOUSHOLD><topp:MALE>322968.0</topp:MALE><topp:FEMALE>343200.0</topp:FEMALE><topp:WORKERS>247566.0</topp:WORKERS><topp:DRVALONE>258087.0</topp:DRVALONE><topp:CARPOOL>42968.0</topp:CARPOOL><topp:PUBTRANS>8069.0</topp:PUBTRANS><topp:EMPLOYED>335147.0</topp:EMPLOYED><topp:UNEMPLOY>13945.0</topp:UNEMPLOY><topp:SERVICE>87973.0</topp:SERVICE><topp:MANUAL>44140.0</topp:MANUAL><topp:P_MALE>0.485</topp:P_MALE><topp:P_FEMALE>0.515</topp:P_FEMALE><topp:SAMP_POP>102776.0</topp:SAMP_POP></topp:states></gml:featureMembers></wfs:FeatureCollection>
+--></div>
+<div id="v2/point-coord.xml"><!--
+<gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+</gml:Point>
+--></div>
+<div id="v2/point-coordinates.xml"><!--
+<gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+</gml:Point>
+--></div>
+<div id="v2/linestring-coord.xml"><!--
+<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+</gml:LineString>
+--></div>
+<div id="v2/linestring-coordinates.xml"><!--
+<gml:LineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4</gml:coordinates>
+</gml:LineString>
+--></div>
+<div id="v2/multipoint-coord.xml"><!--
+<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ </gml:Point>
+ </gml:pointMember>
+</gml:MultiPoint>
+--></div>
+<div id="v2/multipoint-coordinates.xml"><!--
+<gml:MultiPoint xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">2,3</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+</gml:MultiPoint>
+--></div>
+<div id="v2/multilinestring-coord.xml"><!--
+<gml:MultiLineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coord>
+ <gml:X>1</gml:X>
+ <gml:Y>2</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>2</gml:X>
+ <gml:Y>3</gml:Y>
+ </gml:coord>
+ </gml:LineString>
+ </gml:lineStringMember>
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coord>
+ <gml:X>3</gml:X>
+ <gml:Y>4</gml:Y>
+ </gml:coord>
+ <gml:coord>
+ <gml:X>4</gml:X>
+ <gml:Y>5</gml:Y>
+ </gml:coord>
+ </gml:LineString>
+ </gml:lineStringMember>
+</gml:MultiLineString>
+--></div>
+<div id="v2/multilinestring-coordinates.xml"><!--
+<gml:MultiLineString xmlns:gml="http://www.opengis.net/gml" srsName="foo">
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 2,3</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+ <gml:lineStringMember>
+ <gml:LineString>
+ <gml:coordinates decimal="." cs="," ts=" ">3,4 4,5</gml:coordinates>
+ </gml:LineString>
+ </gml:lineStringMember>
+</gml:MultiLineString>
+--></div>
+<div id="v3/repeated-name.xml"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection numberOfFeatures="1" timeStamp="2010-01-29T15:10:38.921-07:00"
+ xsi:schemaLocation="http://medford.opengeo.org http://localhost:8080/geoserver/wfs?service=WFS&amp;version=1.1.0&amp;request=DescribeFeatureType&amp;typeName=medford%3Azoning http://www.opengis.net/wfs http://localhost:8080/geoserver/schemas/wfs/1.1.0/wfs.xsd"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:medford="http://opengeo.org/#medford"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:ows="http://www.opengis.net/ows"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:xlink="http://www.w3.org/1999/xlink">
+ <gml:featureMembers>
+ <medford:zoning gml:id="zoning.1">
+ <medford:the_geom>
+ <gml:MultiSurface srsName="urn:x-ogc:def:crs:EPSG:4326">
+ <gml:surfaceMember>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList>42.397027571297585 -122.88465674265922 42.39702893980587 -122.88509730796012 42.397029086785146 -122.88511582432085 42.39702379767053 -122.88528111596624 42.39748517484964 -122.88529300380065 42.39748473847452 -122.88509914138723 42.39748482219041 -122.8849959517568 42.397485082635576 -122.8846741899541 42.3974853307826 -122.88436529392652 42.39702663751206 -122.88435664014142 42.397027571297585 -122.88465674265922</gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+ </gml:Polygon>
+ </gml:surfaceMember>
+ </gml:MultiSurface>
+ </medford:the_geom>
+ <medford:objectid>1</medford:objectid>
+ <medford:cityzone>YES</medford:cityzone>
+ <medford:zoning>I-L</medford:zoning>
+ <medford:revdate>2004-04-12T00:00:00-06:00</medford:revdate>
+ <medford:finord></medford:finord>
+ <medford:filenum></medford:filenum>
+ <medford:acres>0.95741118624</medford:acres>
+ <medford:misc></medford:misc>
+ <medford:shape_leng>835.705330224</medford:shape_leng>
+ <medford:perimeter>835.705330224</medford:perimeter>
+ <medford:area>41704.8312728</medford:area>
+ <medford:shape_le_1>835.705330224</medford:shape_le_1>
+ <medford:shape_area>41704.8312728</medford:shape_area>
+ <medford:hectares>0.38745056079</medford:hectares>
+ </medford:zoning>
+ </gml:featureMembers>
+</wfs:FeatureCollection>
+-->
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/GPX.html b/misc/openlayers/tests/Format/GPX.html
new file mode 100644
index 0000000..6286cfe
--- /dev/null
+++ b/misc/openlayers/tests/Format/GPX.html
@@ -0,0 +1,179 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var gpx_data = '<?xml version="1.0" encoding="ISO-8859-1"?><gpx version="1.1" creator="Memory-Map 5.1.3.715 http://www.memory-map.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"><wpt lat="51.3697845627" lon="-0.1853562259"><name>Mark</name><sym><![CDATA[Flag]]></sym><type><![CDATA[Marks]]></type></wpt><rte><name><![CDATA[Route8]]></name><type><![CDATA[Route]]></type><rtept lat="51.3761803674" lon="-0.1829991904"><name><![CDATA[WP0801]]></name><sym><![CDATA[Dot]]></sym><type><![CDATA[Waypoints]]></type></rtept><rtept lat="51.3697894659" lon="-0.1758887005"><name><![CDATA[WP0802]]></name><sym><![CDATA[Dot]]></sym><type><![CDATA[Waypoints]]></type></rtept><rtept lat="51.3639790884" lon="-0.1833202965"><name><![CDATA[WP0803]]></name><sym><![CDATA[Dot]]></sym><type><![CDATA[Waypoints]]></type></rtept><rtept lat="51.3567607069" lon="-0.1751119509"><name><![CDATA[WP0804]]></name><sym><![CDATA[Dot]]></sym><type><![CDATA[Waypoints]]></type></rtept></rte><trk><name><![CDATA[Track]]></name><type><![CDATA[Track]]></type><trkseg><trkpt lat="51.3768216433" lon="-0.1721292044"></trkpt><trkpt lat="51.3708337670" lon="-0.1649230916"></trkpt><trkpt lat="51.3644368725" lon="-0.1736741378"></trkpt><trkpt lat="51.3576354272" lon="-0.1662595250"></trkpt></trkseg></trk></gpx>';
+
+ function test_Format_GPX_constructor(t) {
+ t.plan(5);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.GPX(options);
+ t.ok(format instanceof OpenLayers.Format.GPX,
+ "new OpenLayers.Format.GPX returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ t.eq(format.externalProjection.getCode(), "EPSG:4326",
+ "default external projection is EPSG:4326");
+ }
+ function test_Format_GPX_read(t) {
+ t.plan(7);
+
+ var origDefaultPrecision = OpenLayers.Util.DEFAULT_PRECISION;
+ OpenLayers.Util.DEFAULT_PRECISION = 9;
+
+ var expected,
+ P = OpenLayers.Geometry.Point,
+ LS = OpenLayers.Geometry.LineString;
+ var f = new OpenLayers.Format.GPX();
+ var features = f.read(gpx_data);
+ t.eq(features.length, 3, "Number of features read is correct");
+ expected = new P(-0.1853562259, 51.3697845627);
+ t.geom_eq(features[2].geometry, expected, "waypoint feature correctly created");
+ expected = new LS([
+ new P(-0.1721292044, 51.3768216433),
+ new P(-0.1649230916, 51.370833767),
+ new P(-0.1736741378, 51.3644368725),
+ new P(-0.166259525, 51.3576354272)
+ ]);
+ t.geom_eq(features[0].geometry, expected, "track feature correctly created");
+ expected = new LS([
+ new P(-0.1829991904, 51.3761803674),
+ new P(-0.1758887005, 51.3697894659),
+ new P(-0.1833202965, 51.3639790884),
+ new P(-0.1751119509, 51.3567607069)
+ ]);
+ t.geom_eq(features[1].geometry, expected, "route feature correctly created");
+
+ f.internalProjection = new OpenLayers.Projection("EPSG:3857");
+ features = f.read(gpx_data);
+ expected = new P(-20633.760679678744, 6686966.841929403);
+ t.geom_eq(features[2].geometry, expected, "transformed waypoint feature correctly created");
+ expected = new LS([
+ new P(-19161.33538179203, 6688221.743275255),
+ new P(-18359.1545744088, 6687153.931130851),
+ new P(-19333.316581165607, 6686013.33343931),
+ new P(-18507.925659955214, 6684800.777090962)
+ ]);
+ t.geom_eq(features[0].geometry, expected, "transformed track feature correctly created");
+ expected = new LS([
+ new P(-20371.3766880736, 6688107.378491073),
+ new P(-19579.84057322507, 6686967.716235109),
+ new P(-20407.12205561124, 6685931.714395953),
+ new P(-19493.373203291227, 6684644.845706556)
+ ]);
+ t.geom_eq(features[1].geometry, expected, "transformed route feature correctly created");
+
+ OpenLayers.Util.DEFAULT_PRECISION = origDefaultPrecision;
+ }
+ function test_format_GPX_read_attributes(t) {
+ t.plan(2);
+ var f = new OpenLayers.Format.GPX();
+ var features = f.read(gpx_data);
+ t.eq(features[2].attributes['name'], "Mark", "Text attribute node read correctly.");
+ t.eq(features[2].attributes['sym'], "Flag", "CDATA attribute node read correctly.");
+ }
+ function test_Format_GPX_serialize_points(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.GPX();
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var point2 = new OpenLayers.Geometry.Point(-112.04, 45.68);
+ var features = [
+ new OpenLayers.Feature.Vector(point, {name: 'foo', description: 'bar'}),
+ new OpenLayers.Feature.Vector(point2, {name: 'foo', description: 'bar'})
+ ];
+ var data = parser.write(features);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><wpt lon="-111.04" lat="45.68"><name>foo</name><desc>bar</desc></wpt><wpt lon="-112.04" lat="45.68"><name>foo</name><desc>bar</desc></wpt></gpx>', 'GPX serializes points correctly');
+
+ parser.internalProjection = new OpenLayers.Projection("EPSG:3857");
+ point = new OpenLayers.Geometry.Point(-12367595.42541111, 5621521.485409545);
+ point2 = new OpenLayers.Geometry.Point(-12472235.746742222, 5621521.485409545);
+ features = [
+ new OpenLayers.Feature.Vector(point, {name: 'foo', description: 'bar'}),
+ new OpenLayers.Feature.Vector(point2, {name: 'foo', description: 'bar'})
+ ];
+ data = parser.write(features);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><wpt lon="-111.1" lat="45"><name>foo</name><desc>bar</desc></wpt><wpt lon="-112.04" lat="45"><name>foo</name><desc>bar</desc></wpt></gpx>', 'GPX serializes transformed points correctly');
+ }
+ function test_Format_GPX_serialize_line(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.GPX();
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var point2 = new OpenLayers.Geometry.Point(-112.04, 45.68);
+ var line = new OpenLayers.Geometry.LineString([point, point2]);
+ var f = new OpenLayers.Feature.Vector(line, {name: 'foo', description: 'bar'});
+ var data = parser.write(f);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><trk><name>foo</name><desc>bar</desc><trkseg><trkpt lon="-111.04" lat="45.68"/><trkpt lon="-112.04" lat="45.68"/></trkseg></trk></gpx>', 'GPX serializes line correctly');
+
+ parser.internalProjection = new OpenLayers.Projection("EPSG:3857");
+ point = new OpenLayers.Geometry.Point(-12367595.42541111, 5621521.485409545);
+ point2 = new OpenLayers.Geometry.Point(-12472235.746742222, 5621521.485409545);
+ line = new OpenLayers.Geometry.LineString([point, point2]);
+ f = new OpenLayers.Feature.Vector(line, {name: 'foo', description: 'bar'});
+ data = parser.write(f);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><trk><name>foo</name><desc>bar</desc><trkseg><trkpt lon="-111.1" lat="45"/><trkpt lon="-112.04" lat="45"/></trkseg></trk></gpx>', 'GPX serializes transformed line correctly');
+ }
+ function test_Format_GPX_serialize_lines(t) {
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.GPX();
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var point2 = new OpenLayers.Geometry.Point(-112.04, 45.68);
+ var line = new OpenLayers.Geometry.LineString([point, point2]);
+ var point3 = new OpenLayers.Geometry.Point(1, 2);
+ var point4 = new OpenLayers.Geometry.Point(3, 4);
+ var line2 = new OpenLayers.Geometry.LineString([point3, point4]);
+ var f = new OpenLayers.Feature.Vector(line, {name: 'foo', description: 'bar'});
+ var f2 = new OpenLayers.Feature.Vector(line2, {name: 'dude', description: 'truite'});
+ var data = parser.write([f, f2]);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><trk><name>foo</name><desc>bar</desc><trkseg><trkpt lon="-111.04" lat="45.68"/><trkpt lon="-112.04" lat="45.68"/></trkseg></trk><trk><name>dude</name><desc>truite</desc><trkseg><trkpt lon="1" lat="2"/><trkpt lon="3" lat="4"/></trkseg></trk></gpx>', 'GPX serializes lines correctly');
+ }
+ function test_Format_GPX_serialize_multiline(t) {
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.GPX();
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var point2 = new OpenLayers.Geometry.Point(-112.04, 45.68);
+ var line = new OpenLayers.Geometry.LineString([point, point2]);
+ var point3 = new OpenLayers.Geometry.Point(1, 2);
+ var point4 = new OpenLayers.Geometry.Point(3, 4);
+ var line2 = new OpenLayers.Geometry.LineString([point3, point4]);
+ var multiline = new OpenLayers.Geometry.MultiLineString([line, line2]);
+ var f = new OpenLayers.Feature.Vector(multiline, {name: 'foo', description: 'bar'});
+ var data = parser.write([f]);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><trk><name>foo</name><desc>bar</desc><trkseg><trkpt lon="-111.04" lat="45.68"/><trkpt lon="-112.04" lat="45.68"/></trkseg><trkseg><trkpt lon="1" lat="2"/><trkpt lon="3" lat="4"/></trkseg></trk></gpx>', 'GPX serializes multiline correctly');
+ }
+ function test_Format_GPX_serialize_polygon(t) {
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.GPX();
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var point2 = new OpenLayers.Geometry.Point(-112.04, 45.68);
+ var linearRing = new OpenLayers.Geometry.LinearRing([point, point2, point.clone()]);
+ var polygon = new OpenLayers.Geometry.Polygon([linearRing]);
+ var f = new OpenLayers.Feature.Vector(polygon, {name: 'foo', description: 'bar'});
+ var data = parser.write([f]);
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><trk><name>foo</name><desc>bar</desc><trkseg><trkpt lon="-111.04" lat="45.68"/><trkpt lon="-112.04" lat="45.68"/><trkpt lon="-111.04" lat="45.68"/></trkseg></trk></gpx>', 'GPX serializes polygon correctly');
+ }
+ function test_Format_GPX_serialize_metadata(t) {
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.GPX();
+
+ var data = parser.write([], {name: 'foo', desc: 'bar'});
+ t.xml_eq(data, '<gpx xmlns="http://www.topografix.com/GPX/1/1" version="1.1" creator="OpenLayers" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><metadata><name>foo</name><desc>bar</desc></metadata></gpx>', 'GPX serializes metadata correctly');
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/GeoJSON.html b/misc/openlayers/tests/Format/GeoJSON.html
new file mode 100644
index 0000000..98e950f
--- /dev/null
+++ b/misc/openlayers/tests/Format/GeoJSON.html
@@ -0,0 +1,468 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var poly_content = '{"type": "FeatureCollection", "features": [{"geometry": {"type": "Polygon", "coordinates": [[[-131.484375, -5.9765625], [-112.5, -58.0078125], [-32.34375, -50.2734375], [-114.609375, 52.3828125], [-167.34375, -35.5078125], [-146.953125, -57.3046875], [-139.921875, -34.1015625], [-131.484375, -5.9765625]]]}, "type": "Feature", "id": 562, "properties": {"strokeColor": "red", "title": "Feature 2", "author": "Your Name Here"}}]}';
+ var null_geom_feature = '{"type":"Feature","properties":{"strokeColor":"blue","title":"Feature 5","author":"Your Name Here"},"geometry":null,"id":573}';
+ var point_feature = '{"geometry": {"type": "Point", "coordinates": [94.21875, 72.94921875]}, "type": "Feature", "id": 573, "properties": {"strokeColor": "blue", "title": "Feature 5", "author": "Your Name Here"}}';
+ var line_feature = '{"type": "FeatureCollection", "features": [{"geometry": {"type": "LineString", "coordinates": [[-27.0703125, 59.4140625], [-77.6953125, 20.7421875], [30.5859375, -36.2109375], [67.1484375, 34.8046875]]}, "type": "Feature", "id": 559, "properties": {"strokeColor": "red", "title": "Feature 1", "author": "Your Name Here"}}]}';
+ var multiple_features = '{"type": "FeatureCollection", "features": [{"geometry": {"type": "Point", "coordinates": [-91.0546875, 43.9453125]}, "type": "Feature", "id": 577, "properties": {"strokeColor": "red", "title": "Feature 2", "image": "foo.gif", "author": "Your Name Here"}}, {"geometry": {"type": "LineString", "coordinates": [[91.40625, -1.40625], [116.015625, -42.890625], [153.28125, -28.125], [108.984375, 11.25], [75.234375, 8.4375], [76.640625, 9.140625], [67.5, -36.5625], [67.5, -35.859375]]}, "type": "Feature", "id": 576, "properties": {"strokeColor": "red", "title": "Feature 1", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [139.5703125, 57.48046875]}, "type": "Feature", "id": 575, "properties": {"strokeColor": "blue", "title": "Feature 7", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [107.2265625, 82.44140625]}, "type": "Feature", "id": 574, "properties": {"strokeColor": "blue", "title": "Feature 6", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [94.21875, 72.94921875]}, "type": "Feature", "id": 573, "properties": {"strokeColor": "blue", "title": "Feature 5", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [116.3671875, 61.69921875]}, "type": "Feature", "id": 572, "properties": {"strokeColor": "blue", "title": "Feature 4", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [145.8984375, 73.65234375]}, "type": "Feature", "id": 571, "properties": {"strokeColor": "blue", "title": "Feature 3", "author": "Your Name Here"}}, {"geometry": {"type": "Polygon", "coordinates": [[[32.34375, 52.20703125], [87.1875, 70.13671875], [122.6953125, 37.44140625], [75.234375, 42.36328125], [40.078125, 42.36328125], [28.828125, 48.33984375], [18.6328125, 56.77734375], [23.203125, 65.56640625], [32.34375, 52.20703125]]]}, "type": "Feature", "id": 570, "properties": {"strokeColor": "blue", "title": "Feature 2", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [62.578125, -53.4375]}, "type": "Feature", "id": 569, "properties": {"strokeColor": "red", "title": "Feature 3", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [121.640625, 16.875]}, "type": "Feature", "id": 568, "properties": {"strokeColor": "red", "title": "Feature 6", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [135.703125, 8.4375]}, "type": "Feature", "id": 567, "properties": {"strokeColor": "red", "title": "Feature 4", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [137.109375, 48.515625]}, "type": "Feature", "id": 566, "properties": {"strokeColor": "red", "title": "Feature 274", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [0, 5]}, "type": "Feature", "id": 565, "properties": {}}, {"geometry": {"type": "Point", "coordinates": [0, 5]}, "type": "Feature", "id": 564, "properties": {}}, {"geometry": {"type": "Point", "coordinates": [0, 5]}, "type": "Feature", "id": 563, "properties": {}}, {"geometry": {"type": "Polygon", "coordinates": [[[-131.484375, -5.9765625], [-112.5, -58.0078125], [-32.34375, -50.2734375], [-114.609375, 52.3828125], [-167.34375, -35.5078125], [-146.953125, -57.3046875], [-139.921875, -34.1015625], [-131.484375, -5.9765625]]]}, "type": "Feature", "id": 562, "properties": {"strokeColor": "red", "title": "Feature 2", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [48.8671875, -15.8203125]}, "type": "Feature", "id": 560, "properties": {"strokeColor": "red", "title": "Feature 2", "author": "Your Name Here"}}, {"geometry": {"type": "LineString", "coordinates": [[-27.0703125, 59.4140625], [-77.6953125, 20.7421875], [30.5859375, -36.2109375], [67.1484375, 34.8046875]]}, "type": "Feature", "id": 559, "properties": {"strokeColor": "red", "title": "Feature 1", "author": "Your Name Here"}}, {"geometry": {"type": "Point", "coordinates": [12.65625, 16.5234375]}, "type": "Feature", "id": 558, "properties": {"styleUrl": "#allstyle", "title": "Feature 1", "strokeColor": "red", "author": "Your Name Here"}}]}';
+ var parser = new OpenLayers.Format.GeoJSON();
+ var data;
+
+ function test_Format_GeoJSON_constructor(t) {
+ t.plan(4);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.GeoJSON(options);
+ t.ok(format instanceof OpenLayers.Format.GeoJSON,
+ "new OpenLayers.Format.GeoJSON returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ function test_Format_GeoJSON_null_geom(t) {
+ t.plan(2);
+ var f = new OpenLayers.Format.GeoJSON();
+ var fs = f.read(null_geom_feature);
+ t.ok(fs[0].geometry == null, "Reading feature with null geom works okay");
+ t.eq(f.write(fs[0]), null_geom_feature, "round trip null okay");
+
+ }
+ function test_Format_GeoJSON_valid_type(t) {
+ t.plan(14);
+
+ OpenLayers.Console.error = function(error) { window.global_error = error;};
+ var types = ["Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon", "Box", "GeometryCollection"];
+ for (var i = 0; i < types.length; i++) {
+ t.ok(parser.isValidType({'type':types[i]}, "Geometry"), "Geometry with type " + types[i] + " is valid");
+ }
+ t.ok(!parser.isValidType({'type':"foo"}, "Geometry"), "Geometry with type foo is not valid");
+ t.eq(global_error, "Unsupported geometry type: foo", "error message set correctly for 'foo' geom.");
+ t.ok(parser.isValidType({}, "FeatureCollection"), "Feature collection type is always valid");
+ t.ok(parser.isValidType({'type':"GeometryCollection"}, "GeometryCollection"), "Geometry Collection type is valid");
+ t.ok(!parser.isValidType({'type':"GeometryCollection2"}, "GeometryCollection"), "Geometry Collection 2 type is invalid");
+ t.eq(global_error, "Cannot convert types from GeometryCollection2 to GeometryCollection", "error message set correctly for bad geometrycollection type");
+ }
+
+ function test_Format_GeoJSON_point(t) {
+ t.plan(3);
+
+ data = parser.read(point_feature);
+ t.eq(data[0].fid, 573, "Fid is correct on point feature");
+ t.eq(data[0].geometry.x, 94.21875, 'Reading point feature gives correct x');
+ data = parser.read(point_feature, "Feature");
+ t.eq(data.fid, 573, 'Reading point feature with type gives feature instead of array of features ');
+ }
+
+ function test_Format_GeoJSON_line(t) {
+ t.plan(5);
+
+ data = parser.read(line_feature);
+ t.eq(data[0].fid, 559, "Fid is correct on line feature");
+ t.eq(data[0].geometry.components.length, 4, 'Reading line feature gives correct length');
+ t.eq(data[0].geometry.CLASS_NAME, 'OpenLayers.Geometry.LineString', 'Reading line feature gives correct class');
+ t.eq(data[0].geometry.components[0].x, -27.0703125, 'Reading line feature gives correct x');
+ t.eq(data[0].geometry.components[0].y, 59.4140625, 'Reading line feature gives correct y');
+ }
+
+ function test_Format_GeoJSON_poly(t) {
+ t.plan(2);
+
+ data = parser.read(poly_content);
+ t.eq(data[0].fid, 562, "poly id is correct");
+ t.eq(data[0].geometry.components[0].components.length, 8,
+ 'Reading polygon first ring on feature from featurecollection gives correct length');
+ }
+
+ function test_Format_GeoJSON_multipoint(t) {
+ t.plan(5);
+
+ var multipoint = {
+ "type": "MultiPoint",
+ "coordinates": [
+ [100.0, 0.0], [101.0, 1.0]
+ ]
+ };
+ data = parser.read(multipoint, "Geometry");
+ t.eq(data.components.length, 2,
+ "Right number of components");
+ t.eq(data.components[0].CLASS_NAME, "OpenLayers.Geometry.Point", "First component is point");
+ t.eq(data.components[1].CLASS_NAME, "OpenLayers.Geometry.Point", "Second component is point");
+ t.eq(data.components[1].x, 101, "x of second component is right");
+ t.eq(data.components[1].y, 1, "y of second component is right");
+ }
+
+ function test_Format_GeoJSON_multipoint_projected(t) {
+ t.plan(1);
+ var f = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(15555162, 4247484)]));
+ var format = new OpenLayers.Format.GeoJSON({
+ internalProjection: new OpenLayers.Projection("EPSG:900913"),
+ externalProjection: new OpenLayers.Projection("EPSG:4326")
+ });
+ var data = format.write(f);
+ var found = (data.search('139.734') != -1);
+ t.ok(found, "Found 139.734 (correct reprojection) in data output.");
+ }
+
+ function test_Format_GeoJSON_multiline(t) {
+ t.plan(3);
+
+ var multiline = {
+ "type": "MultiLineString",
+ "coordinates": [
+ [ [100.0, 0.0], [101.0, 1.0] ],
+ [ [102.0, 2.0], [103.0, 3.0] ]
+ ]
+ };
+ data = parser.read(multiline, "Geometry");
+ t.eq(data.CLASS_NAME, "OpenLayers.Geometry.MultiLineString", "Correct class retrieved");
+ t.eq(data.components[0].components[0].CLASS_NAME, "OpenLayers.Geometry.Point", "correct type of components");
+ t.eq(data.components[0].CLASS_NAME, "OpenLayers.Geometry.LineString", "correct type of components");
+ }
+
+ function test_Format_GeoJSON_multipol(t) {
+ t.plan(2);
+
+ var multipol = {
+ "type": "MultiPolygon",
+ "coordinates": [
+ [
+ [ [102.0, 2.0], [103.0, 2.0], [103.0, 3.0], [102.0, 3.0], [102.0, 2.0] ]
+ ],
+ [
+ [ [100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0] ],
+ [ [100.2, 0.2], [100.8, 0.2], [100.8, 0.8], [100.2, 0.8], [100.2, 0.2] ]
+ ]
+ ]
+ };
+ OpenLayers.Console.error = function(error) { window.global_error = error; };
+ data = parser.read(multipol, "Geometry");
+ t.eq(data.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon", "Correct class retrieved");
+ t.eq(data.components[1].components[0].components[0].CLASS_NAME, "OpenLayers.Geometry.Point", "correct type of components");
+ }
+
+ function test_Format_GeoJSON_box(t) {
+ t.plan(6);
+
+ var box = {
+ "type": "Box",
+ "coordinates": [[100.0, 0.0], [101.0, 1.0]]
+ };
+ var poly = parser.read(box, "Geometry");
+ t.eq(poly.CLASS_NAME, "OpenLayers.Geometry.Polygon", "Box creates polygon");
+ t.eq(poly.components[0].components[1].x, 101, "x of lower right is correct");
+ t.eq(poly.components[0].components[1].y, 0, "y of lower right is correct");
+ t.eq(poly.components[0].components[3].x, 100, "x of upper left is correct");
+ t.eq(poly.components[0].components[3].y, 1, "y of upper left is correct");
+ box = parser.write(poly );
+ t.ok(box.search("Polygon") != -1 , "Serializes back to polygon");
+ }
+
+ // This test is from the geom_collection example on geojson spec.
+ function test_Format_GeoJSON_collection(t) {
+ t.plan(12);
+
+ var geomcol = {
+ "type": "GeometryCollection",
+ "geometries": [
+ {
+ "type": "Point",
+ "coordinates": [100.0, 0.0]
+ },
+ {
+ "type": "LineString",
+ "coordinates": [
+ [101.0, 0.0], [102.0, 1.0]
+ ]
+ }
+ ]
+ };
+ data = parser.read(geomcol, "Geometry");
+ t.eq(data.CLASS_NAME, "OpenLayers.Geometry.Collection",
+ "GeometryCollection deserialized into geometry.collection");
+ t.eq(data.components[0].CLASS_NAME, "OpenLayers.Geometry.Point",
+ "First geom is correct type");
+ t.eq(data.components[0].x, 100,
+ "First geom in geom collection has correct x");
+ t.eq(data.components[0].y, 0,
+ "First geom in geom collection has correct x");
+
+ t.eq(data.components[1].CLASS_NAME, "OpenLayers.Geometry.LineString",
+ "Second geom in geom collection is point linestring");
+ t.eq(data.components[1].components.length, 2,
+ "linestring is correct length");
+ t.eq(data.components[1].components[1].x, 102,
+ "linestring is correct x end");
+ t.eq(data.components[1].components[1].y, 1,
+ "linestring is correct y end");
+
+ data = parser.read(geomcol, "FeatureCollection");
+ t.eq(data[0].CLASS_NAME, "OpenLayers.Feature.Vector",
+ "GeometryCollection can be read in as a feature collection");
+ t.eq(data[0].geometry.CLASS_NAME, "OpenLayers.Geometry.Collection",
+ "feature contains the correct geometry type");
+ var feature = {
+ "type": "Feature",
+ "geometry": {
+ "type": "GeometryCollection",
+ "geometries": [
+ {
+ "type": "Point",
+ "coordinates": [100.0, 0.0]
+ },
+ {
+ "type": "LineString",
+ "coordinates": [
+ [101.0, 0.0], [102.0, 1.0]
+ ]
+ }
+ ]
+ },
+ "properties": {
+ "prop0": "value0",
+ "prop1": "value1"
+ }
+ };
+ data = parser.read(feature, "Feature");
+ t.eq(data.geometry.CLASS_NAME, "OpenLayers.Geometry.Collection", "Geometry of feature is a collection");
+ var l = new OpenLayers.Layer.Vector();
+ l.addFeatures(data);
+ t.ok(true, "adding a feature with geomcollection to layer doesn't cause error.");
+ }
+
+ function test_Format_GeoJSON_multipleFeatures(t) {
+ t.plan(2);
+
+ var feats = parser.read(multiple_features);
+ t.eq(feats.length, 19, "parsing a feature collection returns the correct number of features.");
+ var types = {'Point':0, 'LineString':0, 'Polygon':0};
+ for(var i = 0; i < feats.length; i++) {
+ var type = feats[i].geometry.CLASS_NAME.replace("OpenLayers.Geometry.", "");
+ types[type]++;
+ }
+ t.eq(types, {'Point':15, 'Polygon': 2, 'LineString':2}, "Correct number of each type");
+ }
+
+ function test_Format_GeoJSON_writeWithCRS(t) {
+ t.plan(2);
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,2));
+ feature.fid = 0;
+ var output = '{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[1,2]},"id":0,"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}';
+ var layer = new OpenLayers.Layer.Vector();
+ layer.projection = "EPSG:4326";
+ feature.layer = layer;
+ var parser = new OpenLayers.Format.GeoJSON();
+ var test_out = parser.write(feature);
+ t.eq(test_out, output, "Output is equal for vector with layer in EPSG:4326 ");
+ feature.layer.projection = "EPSG:2805";
+ output = '{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[1,2]},"id":0,"crs":{"type":"name","properties":{"name":"EPSG:2805"}}}';
+ test_out = parser.write(feature);
+ t.eq(test_out, output, "Output is equal for vector with point");
+ }
+
+ function test_Format_GeoJSON_write(t) {
+ t.plan(10);
+
+ var line_object = {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [-27.0703125, 59.4140625],
+ [-77.6953125, 20.7421875],
+ [30.5859375, -36.2109375],
+ [67.1484375, 34.8046875]
+ ]
+ },
+ "type": "Feature",
+ "id": 559,
+ "properties": {
+ "strokeColor": "red",
+ "title": "Feature 1",
+ "author": "Your Name Here"
+ }
+ }
+ ]
+ };
+ data = parser.read(line_object);
+ out = parser.write(data);
+ serialized = '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"strokeColor":"red","title":"Feature 1","author":"Your Name Here"},"geometry":{"type":"LineString","coordinates":[[-27.0703125,59.4140625],[-77.6953125,20.7421875],[30.5859375,-36.2109375],[67.1484375,34.8046875]]},"id":559}]}';
+ t.eq(out, serialized, "input and output on line collections are the same");
+
+ var serialize_tests = [
+ [
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,2)),
+ '{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[1,2]},"id":0}'
+ ],
+ [
+ new OpenLayers.Geometry.Point(1,2),
+ '{"type":"Point","coordinates":[1,2]}'
+ ],
+ [
+ new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(1,2)
+ ]),
+ '{"type":"MultiPoint","coordinates":[[1,2]]}'
+ ],
+ [
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1,2),
+ new OpenLayers.Geometry.Point(3,4)
+ ]),
+ '{"type":"LineString","coordinates":[[1,2],[3,4]]}'
+ ],
+ [
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1,2),
+ new OpenLayers.Geometry.Point(3,4),
+ new OpenLayers.Geometry.Point(5,6)
+ ])
+ ]),
+ '{"type":"Polygon","coordinates":[[[1,2],[3,4],[5,6],[1,2]]]}'
+ ],
+ [
+ new OpenLayers.Geometry.Collection([
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1,2),
+ new OpenLayers.Geometry.Point(3,4),
+ new OpenLayers.Geometry.Point(5,6)
+ ])
+ ]),
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1,2),
+ new OpenLayers.Geometry.Point(3,4)
+ ]),
+ new OpenLayers.Geometry.Point(1,2)
+ ]),
+ '{"type":"GeometryCollection","geometries":[{"type":"Polygon","coordinates":[[[1,2],[3,4],[5,6],[1,2]]]},{"type":"LineString","coordinates":[[1,2],[3,4]]},{"type":"Point","coordinates":[1,2]}]}'
+ ]
+ ];
+ serialize_tests[0][0].fid = 0;
+ multiline = new OpenLayers.Geometry.MultiLineString([serialize_tests[3][0], serialize_tests[3][0]]);
+ serialize_tests.push([multiline, '{"type":"MultiLineString","coordinates":[[[1,2],[3,4]],[[1,2],[3,4]]]}']);
+ multipolygon = new OpenLayers.Geometry.MultiPolygon([serialize_tests[4][0], serialize_tests[4][0]]);
+ serialize_tests.push([multipolygon, '{"type":"MultiPolygon","coordinates":[[[[1,2],[3,4],[5,6],[1,2]]],[[[1,2],[3,4],[5,6],[1,2]]]]}']);
+ serialize_tests.push([ [ serialize_tests[0][0] ], '{"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Point","coordinates":[1,2]},"id":0}]}' ]);
+ for (var i = 0; i < serialize_tests.length; i++) {
+ var input = serialize_tests[i][0];
+ var output = serialize_tests[i][1];
+ test_out = parser.write(input);
+ t.eq(test_out, output, "Serializing " + input.toString() + " saved correctly.");
+ }
+ }
+
+ function test_write_no_fid(t) {
+ t.plan(4);
+
+ var geojson;
+ var feature = new OpenLayers.Feature.Vector();
+
+ feature.fid = null;
+ geojson = parser.write(feature);
+ t.eq(geojson, '{"type":"Feature","properties":{},"geometry":null}',
+ "no id set in the GeoJSON string when fid is null");
+
+ feature.fid = undefined;
+ geojson = parser.write(feature);
+ t.eq(geojson, '{"type":"Feature","properties":{},"geometry":null}',
+ "no id set in the GeoJSON string when fid is undefined");
+
+ feature.fid = 0;
+ geojson = parser.write(feature);
+ t.eq(geojson, '{"type":"Feature","properties":{},"geometry":null,"id":0}',
+ "id set in the GeoJSON string when fid is 0");
+
+ delete feature.fid;
+ geojson = parser.write(feature);
+ t.eq(geojson, '{"type":"Feature","properties":{},"geometry":null}',
+ "id not set in the GeoJSON string when fid is delected");
+ }
+
+ function test_Format_GeoJSON_read_object(t) {
+ t.plan(1);
+
+ var line_object = {
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [-27.0703125, 59.4140625],
+ [-77.6953125, 20.7421875],
+ [30.5859375, -36.2109375],
+ [67.1484375, 34.8046875]
+ ]
+ },
+ "type": "Feature",
+ "id": 559,
+ "properties": {
+ "strokeColor": "red",
+ "title": "Feature 1",
+ "author": "Your Name Here"
+ }
+ }
+ ]
+ };
+ data = parser.read(line_object);
+ t.eq(data[0].fid, 559, "Can read data from an object correctly.");
+ }
+
+ function test_Format_GeoJSON_read_attributes(t) {
+ t.plan(3);
+
+ var parser = new OpenLayers.Format.GeoJSON();
+ data = parser.read(line_feature);
+ t.eq(data[0].attributes['strokeColor'], 'red', 'read strokeColor attribute properly');
+ t.eq(data[0].attributes['title'], 'Feature 1', 'read title attribute properly');
+ t.eq(data[0].attributes['author'], 'Your Name Here', 'read author attribute properly');
+ }
+
+ function test_read_bbox(t) {
+ t.plan(8);
+
+ var f;
+ parser = new OpenLayers.Format.GeoJSON();
+
+ // 4 tests
+ f = '{"geometry": {"type": "Point", "coordinates": [94.21875, 72.94921875], "bbox": [94.21875, 72.94921875, 94.21875, 72.94921875]}, "type": "Feature", "id": 573, "properties": {}, "bbox": [95.0, 73.0]}';
+ data = parser.read(f);
+ t.eq(data[0].bounds.left, 94.21875, "read left bound is correct");
+ t.eq(data[0].bounds.bottom, 72.94921875, "read bottom left bound is correct");
+ t.eq(data[0].bounds.right, 94.21875, "read right bound is correct");
+ t.eq(data[0].bounds.top, 72.94921875, "read top left bound is correct");
+
+ // 4 tests
+ f = '{"geometry": {"type": "Point", "coordinates": [94.21875, 72.94921875]}, "type": "Feature", "id": 573, "properties": {}, "bbox": [95.0, 73.0, 96.0, 74.0]}';
+ data = parser.read(f);
+ t.eq(data[0].bounds.left, 95.0, "read left bound is correct");
+ t.eq(data[0].bounds.bottom, 73.0, "read bottom left bound is correct");
+ t.eq(data[0].bounds.right, 96.0, "read right bound is correct");
+ t.eq(data[0].bounds.top, 74.0, "read top left bound is correct");
+ }
+
+ function test_Format_GeoJSON_point_extradims(t) {
+ t.plan(3);
+ var point_feature_3d = '{"geometry": {"type": "Point", "coordinates": [94.21875, 72.94921875, 0.0]}, "type": "Feature", "id": 573, "properties": {"strokeColor": "blue", "title": "Feature 5", "author": "Your Name Here"}}';
+ p = new OpenLayers.Format.GeoJSON({"ignoreExtraDims": true});
+ data = p.read(point_feature_3d);
+ t.eq(data[0].fid, 573, "Fid is correct on point feature");
+ t.eq(data[0].geometry.x, 94.21875, 'Reading point feature gives correct x');
+ data = p.read(point_feature_3d, "Feature");
+ t.eq(data.fid, 573, 'Reading point feature with type gives feature instead of array of features ');
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/GeoRSS.html b/misc/openlayers/tests/Format/GeoRSS.html
new file mode 100644
index 0000000..6e82f05
--- /dev/null
+++ b/misc/openlayers/tests/Format/GeoRSS.html
@@ -0,0 +1,122 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Format_GeoRSS_constructor(t) {
+ t.plan(4);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.GeoRSS(options);
+ t.ok(format instanceof OpenLayers.Format.GeoRSS,
+ "new OpenLayers.Format.GeoRSS returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ function test_Format_GeoRSS_serializeline(t) {
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.GeoRSS();
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var point2 = new OpenLayers.Geometry.Point(-112.04, 45.68);
+ var l = new OpenLayers.Geometry.LineString([point, point2]);
+ var f = new OpenLayers.Feature.Vector(l);
+ var data = parser.write([f]);
+ t.xml_eq(data, '<rss xmlns="http://backend.userland.com/rss2"><item><title></title><description></description><georss:line xmlns:georss="http://www.georss.org/georss">45.68 -111.04 45.68 -112.04</georss:line></item></rss>', 'GeoRSS serializes a line correctly');
+ }
+ function test_Format_GeoRSS_box(t) {
+ t.plan(4);
+ var xml = '<rss xmlns="http://backend.userland.com/rss2"><item><title></title><description></description><georss:box xmlns:georss="http://www.georss.org/georss">45.68 -112.04 47.68 -111.04</georss:box></item></rss>';
+ var format = new OpenLayers.Format.GeoRSS();
+ var features = format.read(xml);
+ t.eq(features.length, 1, "one feature returned");
+ t.eq(features[0].geometry.components[0].components.length, 5, "polygon returned");
+ t.eq(features[0].geometry.components[0].components[0].x, -112.04, "polygon returned with correct first x");
+ t.eq(features[0].geometry.components[0].components[0].y, 45.68, "polygon returned with correct first y");
+ }
+ function test_Format_GeoRSS_w3cgeo(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.GeoRSS();
+ var data = parser.read('<rss xmlns="http://backend.userland.com/rss2" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"><item><geo:long>-1</geo:long><geo:lat>1</geo:lat></item></rss>');
+ t.eq(data[0].geometry.x, -1, "w3c geo x read correctly");
+ t.eq(data[0].geometry.y, 1, "w3c geo y read correctly");
+ }
+ function test_Format_GeoRSS_reproject_null(t) {
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.GeoRSS({'internalProjection':new OpenLayers.Projection("EPSG:4326"), 'externalProjection': new OpenLayers.Projection("EPSG:4326")});
+ var data = parser.read('<rss xmlns="http://backend.userland.com/rss2" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"><item></item></rss>');
+ t.eq(data.length, 1, "Parsing items with null geometry and reprojection doesn't fail");
+ }
+ function test_Format_GeoRSS_roundtrip(t) {
+ t.plan(input.length);
+ var parser = new OpenLayers.Format.GeoRSS();
+ for(var i=0; i < input.length; i++) {
+ var feed = shell_start+input[i]+shell_end;
+ var data = parser.read(feed);
+ var out = parser.write(data);
+ var expected_result = output[i];
+ t.xml_eq(out, expected_result, "Output gave expected value");
+ }
+ }
+ function test_Format_GeoRSS_gml_roundtrip(t) {
+ t.plan(input_gml.length);
+ var parser = new OpenLayers.Format.GeoRSS();
+ for(var i=0; i < input_gml.length; i++) {
+ var feed = shell_start_gml+input_gml[i]+shell_end_gml;
+ var data = parser.read(feed);
+ var out = parser.write(data);
+ var expected_result = output_gml[i];
+ t.xml_eq(out, expected_result, "Output gave expected value");
+ }
+ }
+
+ function test_leading_space(t) {
+ t.plan(2);
+
+ var parser = new OpenLayers.Format.GeoRSS();
+ var items = parser.read('<rss version="2.0" xmlns:georss="http://www.georss.org/georss"><item><description> <![CDATA[foo]]></description></item></rss>');
+ t.eq(items.length, 1, "item created");
+
+ // when parsing a node composed of both spaces and a cdata section
+ // (e.g. <description> <![DATA[foo]]></description> IE8 ignores
+ // the leading white spaces, and reports that the node does not
+ // include a text node. For that reason, we need to trim the
+ // string value resulting from the parsing.
+
+ var description = OpenLayers.String.trim(items[0].attributes.description);
+ t.eq(description, "foo", "description value is ok");
+ }
+
+ var shell_start = '<feed xmlns="http://www.w3.org/2005/Atom" \n xmlns:georss="http://www.georss.org/georss">\n <title>scribble</title>\n <id>http://featureserver.org/featureserver.cgi/scribble?format=atom</id>\n <author><name>FeatureServer</name></author>\n';
+ var shell_end = '</feed>';
+ var input = ['<entry><id>http://featureserver.org/featureserver.cgi/scribble/562.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/562.atom"/><title>Feature 2</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:polygon>-5.9765625 -131.484375 -58.0078125 -112.5 -50.2734375 -32.34375 52.3828125 -114.609375 -35.5078125 -167.34375 -57.3046875 -146.953125 -34.1015625 -139.921875 -5.9765625 -131.484375</georss:polygon></entry>',
+ '<entry><id>http://featureserver.org/featureserver.cgi/scribble/796.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/796.atom"/><title>Feature 2</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: 00ccff&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:point>75.5859375 15.46875</georss:point></entry>',
+ '<entry><id>http://featureserver.org/featureserver.cgi/scribble/794.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/794.atom"/><title>Feature 5</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 5&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:line>28.828125 32.6953125 49.921875 34.8046875 39.375 58.0078125 39.375 58.0078125 40.078125 58.0078125 41.484375 58.0078125 43.59375 58.0078125 45.703125 58.7109375 47.8125 58.7109375 49.21875 58.7109375 51.328125 59.4140625 52.03125 59.4140625 54.140625 60.8203125 56.25 61.5234375 56.25 62.2265625 57.65625 62.2265625 57.65625 62.9296875 58.359375 63.6328125 58.359375 65.0390625 58.359375 65.7421875 59.0625 66.4453125 59.0625 67.1484375 59.0625 68.5546875 59.765625 69.9609375 59.765625 72.0703125 59.765625 73.4765625 59.765625 76.2890625 59.765625 78.3984375 59.765625 79.8046875 59.765625 81.9140625 59.765625 83.3203125 59.0625 84.7265625 59.0625 86.8359375 58.359375 87.5390625 58.359375 88.2421875 56.953125 89.6484375 56.25 91.0546875 54.84375 93.8671875 52.03125 96.6796875 51.328125 98.7890625 50.625 100.1953125 49.21875 102.3046875 48.515625 103.7109375 47.8125 104.4140625 47.109375 105.8203125 46.40625 106.5234375 46.40625 107.9296875 45.703125 109.3359375 45 110.7421875 43.59375 112.8515625 43.59375 114.2578125 43.59375 114.9609375 42.890625 117.0703125 42.890625 117.7734375 42.1875 118.4765625 42.1875 119.1796875 42.1875 119.8828125</georss:line></entry>'
+ ];
+ var output= ['<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 2</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/562.atom</link><georss:polygon xmlns:georss="http://www.georss.org/georss">-5.9765625 -131.484375 -58.0078125 -112.5 -50.2734375 -32.34375 52.3828125 -114.609375 -35.5078125 -167.34375 -57.3046875 -146.953125 -34.1015625 -139.921875 -5.9765625 -131.484375</georss:polygon></item></rss>',
+ '<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 2</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: 00ccff&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/796.atom</link><georss:point xmlns:georss="http://www.georss.org/georss">75.5859375 15.46875</georss:point></item></rss>',
+ '<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 5</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 5&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/794.atom</link><georss:line xmlns:georss="http://www.georss.org/georss">28.828125 32.6953125 49.921875 34.8046875 39.375 58.0078125 39.375 58.0078125 40.078125 58.0078125 41.484375 58.0078125 43.59375 58.0078125 45.703125 58.7109375 47.8125 58.7109375 49.21875 58.7109375 51.328125 59.4140625 52.03125 59.4140625 54.140625 60.8203125 56.25 61.5234375 56.25 62.2265625 57.65625 62.2265625 57.65625 62.9296875 58.359375 63.6328125 58.359375 65.0390625 58.359375 65.7421875 59.0625 66.4453125 59.0625 67.1484375 59.0625 68.5546875 59.765625 69.9609375 59.765625 72.0703125 59.765625 73.4765625 59.765625 76.2890625 59.765625 78.3984375 59.765625 79.8046875 59.765625 81.9140625 59.765625 83.3203125 59.0625 84.7265625 59.0625 86.8359375 58.359375 87.5390625 58.359375 88.2421875 56.953125 89.6484375 56.25 91.0546875 54.84375 93.8671875 52.03125 96.6796875 51.328125 98.7890625 50.625 100.1953125 49.21875 102.3046875 48.515625 103.7109375 47.8125 104.4140625 47.109375 105.8203125 46.40625 106.5234375 46.40625 107.9296875 45.703125 109.3359375 45 110.7421875 43.59375 112.8515625 43.59375 114.2578125 43.59375 114.9609375 42.890625 117.0703125 42.890625 117.7734375 42.1875 118.4765625 42.1875 119.1796875 42.1875 119.8828125</georss:line></item></rss>'
+ ];
+
+ var shell_start_gml = '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:gml="http://www.opengis.net/gml" xmlns:georss="http://www.georss.org/georss"> <title>scribble</title><id>http://featureserver.org/featureserver.cgi/scribble?format=atom</id><author><name>FeatureServer</name></author>';
+ var shell_end_gml = '</feed>';
+ var input_gml = ['<entry><id>http://featureserver.org/featureserver.cgi/scribble/111.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/111.atom"/><title>Feature 2</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:where><gml:Point><gml:pos>0 10</gml:pos></gml:Point></georss:where></entry>',
+ '<entry><id>http://featureserver.org/featureserver.cgi/scribble/111.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/111.atom"/><title>Feature 2</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:where><gml:Polygon><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>110,-50 110,-10 155,-10 155,-50 110,-50</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></georss:where></entry>',
+ '<entry><id>http://featureserver.org/featureserver.cgi/scribble/111.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/111.atom"/><title>Feature 2</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:where><gml:LineString><gml:coordinates>0,10 10,20</gml:coordinates></gml:LineString></georss:where></entry>',
+ '<entry><id>http://featureserver.org/featureserver.cgi/scribble/111.atom</id><link href="http://featureserver.org/featureserver.cgi/scribble/111.atom"/><title>Feature 2</title><content type="html">&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</content><georss:where><gml:Envelope><gml:lowerCorner>0 1</gml:lowerCorner><gml:upperCorner>20 21</gml:upperCorner></gml:Envelope></georss:where></entry>'
+ ];
+ var output_gml = ['<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 2</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/111.atom</link><georss:point xmlns:georss="http://www.georss.org/georss">0 10</georss:point></item></rss>',
+ '<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 2</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/111.atom</link><georss:polygon xmlns:georss="http://www.georss.org/georss">110 -50 110 -10 155 -10 155 -50 110 -50</georss:polygon></item></rss>',
+ '<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 2</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/111.atom</link><georss:line xmlns:georss="http://www.georss.org/georss">0 10 10 20</georss:line></item></rss>',
+ '<rss xmlns="http://backend.userland.com/rss2"><item><title>Feature 2</title><description>&lt;b&gt;strokeColor&lt;/b&gt;: red&lt;br /&gt;&lt;b&gt;title&lt;/b&gt;: Feature 2&lt;br /&gt;&lt;b&gt;author&lt;/b&gt;: Your Name Here</description><link>http://featureserver.org/featureserver.cgi/scribble/111.atom</link><georss:polygon xmlns:georss="http://www.georss.org/georss">0 1 0 21 20 21 20 1 0 1</georss:polygon></item></rss>'
+ ];
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/JSON.html b/misc/openlayers/tests/Format/JSON.html
new file mode 100644
index 0000000..84461e1
--- /dev/null
+++ b/misc/openlayers/tests/Format/JSON.html
@@ -0,0 +1,53 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Format_JSON_constructor(t) {
+ t.plan(4);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.JSON(options);
+ t.ok(format instanceof OpenLayers.Format.JSON,
+ "new OpenLayers.Format.JSON returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ function test_Format_JSON_parser(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.JSON();
+ var data = format.read('{"a":["b"], "c":1}');
+ var obj = {"a":["b"], "c":1};
+ t.eq(obj['a'], data['a'], "element with array parsed correctly.");
+ t.eq(obj['c'], data['c'], "element with number parsed correctly.");
+ }
+
+ function test_Format_JSON_writer(t) {
+ t.plan(1);
+
+ var format = new OpenLayers.Format.JSON();
+ var data = format.write({"a":["b"], "c":1});
+ var obj = '{"a":["b"],"c":1}';
+ t.eq(data, obj, "writing data to json works.");
+ }
+
+
+ function test_keepData(t) {
+ t.plan(2);
+
+ var options = {'keepData': true};
+ var format = new OpenLayers.Format.JSON(options);
+ format.read('{"a":["b"], "c":1}');
+
+ t.ok(format.data != null, 'data property is not null after read with keepData=true');
+ t.eq(format.data.c,1,'keepData keeps the right data');
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/KML.html b/misc/openlayers/tests/Format/KML.html
new file mode 100644
index 0000000..ba87637
--- /dev/null
+++ b/misc/openlayers/tests/Format/KML.html
@@ -0,0 +1,1437 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var test_content = '<kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>OpenLayers export</name><description>Vector geometries from OpenLayers</description><Placemark id="KML.Polygon"><name>OpenLayers.Feature.Vector_344</name><description>A KLM Polygon</description><Polygon><outerBoundaryIs><LinearRing><coordinates>5.001370157823406,49.26855713824488 8.214706453896161,49.630662409673505 8.397385910100951,48.45172350357396 5.001370157823406,49.26855713824488</coordinates></LinearRing></outerBoundaryIs></Polygon></Placemark><Placemark id="KML.LineString"><name>OpenLayers.Feature.Vector_402</name><description>A KML LineString</description><LineString><coordinates>5.838523393080493,49.74814616928052 5.787079558782349,48.410795432216574 8.91427702008381,49.28932499608202</coordinates></LineString></Placemark><Placemark id="KML.Point"><name>OpenLayers.Feature.Vector_451</name><description>A KML Point</description><Point><coordinates>6.985073041685488,49.8682250149058</coordinates></Point></Placemark><Placemark id="KML.MultiGeometry"><name>SF Marina Harbor Master</name><description>KML MultiGeometry</description><MultiGeometry><LineString><coordinates>-122.4425587930444,37.80666418607323 -122.4428379594768,37.80663578323093</coordinates></LineString><LineString><coordinates>-122.4425509770566,37.80662588061205 -122.4428340530617,37.8065999493009</coordinates></LineString></MultiGeometry></Placemark></Folder></kml>';
+ var test_style = '<kml xmlns="http://earth.google.com/kml/2.0"> <Placemark> <Style> <LineStyle> <color>870000ff</color> <width>10</width> </LineStyle> </Style> <LineString> <coordinates> -112,36 -113,37 </coordinates> </LineString> </Placemark></kml>';
+ var test_style_fill = '<kml xmlns="http://earth.google.com/kml/2.0"> <Placemark> <Style> <PolyStyle> <fill>1</fill> <color>870000ff</color> <width>10</width> </PolyStyle> </Style> <LineString> <coordinates> -112,36 -113,37 </coordinates> </LineString> </Placemark><Placemark> <Style> <PolyStyle> <fill>0</fill> <color>870000ff</color> <width>10</width> </PolyStyle> </Style> <LineString> <coordinates> -112,36 -113,37 </coordinates> </LineString> </Placemark></kml>';
+ var test_style_outline = '<kml xmlns="http://earth.google.com/kml/2.0"> <Placemark> <Style> <PolyStyle> <outline>0</outline> <color>870000ff</color> <width>10</width> </PolyStyle> </Style> <LineString> <coordinates> -112,36 -113,37 </coordinates> </LineString> </Placemark></kml>';
+ var test_style_font = '<kml xmlns="http://earth.google.com/kml/2.0"> <Placemark><Style><LabelStyle><color>870000ff</color><scale>1.5</scale></LabelStyle></Style><LineString><coordinates> -112,36 -113,37 </coordinates></LineString></Placemark></kml>';
+ var test_nl = '<kml xmlns="http://earth.google.com/kml/2.2"> <Document> <NetworkLink> <Link> <href>http://maker.geocommons.com/maps/1717/overlays/0</href> </Link> </NetworkLink> </Document></kml>';
+ function test_Format_KML_constructor(t) {
+ t.plan(5);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.KML(options);
+ t.ok(format instanceof OpenLayers.Format.KML,
+ "new OpenLayers.Format.KML returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ t.eq(format.externalProjection.getCode(), "EPSG:4326",
+ "default external projection is EPSG:4326");
+ }
+ function test_Format_KML_multipoint(t) {
+ t.plan(1);
+ var f = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(15555162, 4247484)]));
+ var format = new OpenLayers.Format.KML({
+ extractStyles: true,
+ extractAttributes: true,
+ internalProjection: new OpenLayers.Projection("EPSG:900913"),
+ externalProjection: new OpenLayers.Projection("EPSG:4326")
+ });
+ var data = format.write(f);
+ var found = (data.search('139.734') != -1);
+ t.ok(found, "Found 139.734 (correct reprojection) in data output.");
+ }
+ function test_Format_KML_read(t) {
+ t.plan(5);
+ var features = (new OpenLayers.Format.KML()).read(this.test_content);
+ t.eq(features.length, 4, "Number of features read is correct");
+ t.ok(features[0].geometry.toString() == "POLYGON((5.001370157823406 49.26855713824488,8.214706453896161 49.630662409673505,8.397385910100951 48.45172350357396,5.001370157823406 49.26855713824488))", "polygon feature geometry correctly created");
+ t.ok(features[1].geometry.toString() == "LINESTRING(5.838523393080493 49.74814616928052,5.787079558782349 48.410795432216574,8.91427702008381 49.28932499608202)", "linestring feature geometry correctly created");
+ t.ok(features[2].geometry.toString() == "POINT(6.985073041685488 49.8682250149058)", "point feature geometry correctly created");
+ t.ok(features[3].geometry.CLASS_NAME == "OpenLayers.Geometry.Collection",
+ "read geometry collection");
+ }
+
+
+ function test_Format_KML_readCdataAttributes_20(t) {
+ t.plan(2);
+ var cdata = '<kml xmlns="http://earth.google.com/kml/2.0"><Document><Placemark><name><![CDATA[Pezinok]]> </name><description><![CDATA[Full of text.]]></description><styleUrl>#rel1.0</styleUrl><Point> <coordinates>17.266666, 48.283333</coordinates></Point></Placemark></Document></kml>';
+ var features = (new OpenLayers.Format.KML()).read(cdata);
+ t.eq(features[0].attributes.description, "Full of text.", "Description attribute in cdata read correctly");
+ t.eq(features[0].attributes.name, "Pezinok", "title attribute in cdata read correctly");
+
+ }
+
+ function test_Format_KML_networklink(t) {
+ t.plan(1);
+ var f = new OpenLayers.Format.KML({'maxDepth':1});
+ f.fetchLink = function(url) {
+ t.eq(url, "http://maker.geocommons.com/maps/1717/overlays/0", "network link fetched a link correctly.");
+ return '';
+ }
+ f.read(test_nl);
+ }
+ function test_Format_KML_readCdataAttributes_21(t) {
+ t.plan(2);
+ var cdata = '<kml xmlns="http://earth.google.com/kml/2.1"><Document><Placemark><name><![CDATA[Pezinok]]></name><description><![CDATA[Full of text.]]></description><styleUrl>#rel1.0</styleUrl><Point> <coordinates>17.266666, 48.283333</coordinates></Point></Placemark></Document></kml>';
+ var features = (new OpenLayers.Format.KML()).read(cdata);
+ t.eq(features[0].attributes.description, "Full of text.", "Description attribute in cdata read correctly");
+ t.eq(features[0].attributes.name, "Pezinok", "title attribute in cdata read correctly");
+
+ }
+
+ function test_Format_KML_write(t) {
+ // make sure id, name, and description are preserved
+ t.plan(1);
+ var kmlExpected = this.test_content;
+ var options = {
+ foldersName: "OpenLayers export",
+ foldersDesc: "Vector geometries from OpenLayers"
+ };
+
+ var format = new OpenLayers.Format.KML(options);
+ var features = format.read(kmlExpected);
+ var kmlOut = format.write(features);
+ var kmlOut = kmlOut.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(kmlOut, kmlExpected, "correctly writes an KML doc string");
+ }
+
+ function test_Format_KML_write_noNameDesc(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.KML({
+ foldersName: null,
+ foldersDesc: null
+ });
+ var geom = new OpenLayers.Geometry.Point(0, 0)
+ var feature = new OpenLayers.Feature.Vector(geom);
+ feature.id = 42;
+ var kmlOut = format.write(feature);
+ var expected = '<kml xmlns="http://earth.google.com/kml/2.0"><Folder><Placemark><name>42</name><description>No description available</description><Point><coordinates>0,0</coordinates></Point></Placemark></Folder></kml>'
+ t.eq(kmlOut, expected, "null foldersName or foldersDesc don't create elements");
+ }
+
+ function test_Format_KML_write_multis(t) {
+ /**
+ * KML doesn't have a representation for multi geometries of a
+ * specific type. KML MultiGeometry maps to OL Geometry.Collection.
+ * Because of this, multi-geometries in OL can't make a round trip
+ * through KML (an OL MultiPoint maps to a KML MultiGeometry
+ * containing points, which maps back to an OL Collection containing
+ * points). So we need to acceptance tests for the writing of
+ * multi-geometries specifically instead of relying on the round-trip
+ * write test above.
+ */
+ t.plan(3);
+ var format = new OpenLayers.Format.KML({foldersDesc: "test output"});
+ var multi, feature, output, expected;
+
+ // test multipoint
+ var multi = new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(0, 1)
+ ]);
+ feature = new OpenLayers.Feature.Vector(multi, {name: "test name"});
+ output = format.write(feature);
+ expected = '<kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>OpenLayers export</name><description>test output</description><Placemark><name>test name</name><description>No description available</description><MultiGeometry><Point><coordinates>0,1</coordinates></Point></MultiGeometry></Placemark></Folder></kml>';
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, expected, "multipoint correctly written");
+
+ // test multilinestring
+ var multi = new OpenLayers.Geometry.MultiLineString([
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 0),
+ new OpenLayers.Geometry.Point(0, 1)
+ ])
+ ]);
+ feature = new OpenLayers.Feature.Vector(multi, {name: "test name"});
+ output = format.write(feature);
+ expected = '<kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>OpenLayers export</name><description>test output</description><Placemark><name>test name</name><description>No description available</description><MultiGeometry><LineString><coordinates>1,0 0,1</coordinates></LineString></MultiGeometry></Placemark></Folder></kml>';
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, expected, "multilinestring correctly written");
+
+ // test multipolygon
+ var multi = new OpenLayers.Geometry.MultiPolygon([
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(1, 0),
+ new OpenLayers.Geometry.Point(0, 1)
+ ])
+ ])
+ ]);
+ feature = new OpenLayers.Feature.Vector(multi, {name: "test name"});
+ output = format.write(feature);
+ expected = '<kml xmlns="http://earth.google.com/kml/2.0"><Folder><name>OpenLayers export</name><description>test output</description><Placemark><name>test name</name><description>No description available</description><MultiGeometry><Polygon><outerBoundaryIs><LinearRing><coordinates>0,0 1,0 0,1 0,0</coordinates></LinearRing></outerBoundaryIs></Polygon></MultiGeometry></Placemark></Folder></kml>';
+ var output = output.replace(/<\?[^>]*\?>/, ''); // Remove XML Prolog
+ t.eq(output, expected, "multilinestring correctly written");
+
+ }
+ function test_Format_KML_extractStyle(t) {
+ t.plan(1);
+ var f = new OpenLayers.Format.KML();
+ var features = f.read(test_style);
+ t.ok(features[0].style == undefined, "KML Feature has no style with extractStyle false");
+ }
+ function test_Format_KML_extractStyleFill(t) {
+ t.plan(2);
+ var f = new OpenLayers.Format.KML({extractStyles: true});
+ var features = f.read(test_style_fill);
+ t.eq(features[0].style.fillColor, "#ff0000", "default fill is set");
+ t.eq(features[1].style.fillColor, "none", "KML Feature has none fill");
+ }
+ function test_Format_KML_extractStyleOutline(t) {
+ t.plan(2);
+ var f = new OpenLayers.Format.KML({extractStyles: true});
+ var features = f.read(test_style);
+ t.eq(features[0].style.strokeWidth, "10", "default stroke is set");
+ var features = f.read(test_style_outline);
+ t.eq(features[0].style.strokeWidth, "0", "KML Feature has no outline");
+ }
+ function test_Format_KML_extractStyleFont(t) {
+ t.plan(2);
+ var f = new OpenLayers.Format.KML({extractStyles: true});
+ var features = f.read(test_style_font);
+ t.eq(features[0].style.fontColor, "#ff0000", "font color is set");
+ t.eq(features[0].style.fontOpacity, parseInt('87', 16) / 255, "font opacity is set");
+ }
+ function test_Format_KML_getStyle(t) {
+ t.plan(1);
+ var style = {t: true};
+ var f = new OpenLayers.Format.KML();
+ f.styles = {test: style};
+ var gotStyle = f.getStyle('test');
+ gotStyle.t = false;
+ t.ok(style.t, "getStyle returns copy of style rather than reference");
+ }
+ function test_Format_KML_extendedData(t) {
+ t.plan(6);
+ var f = new OpenLayers.Format.KML();
+ var features = f.read(OpenLayers.Util.getElement("kml_extendeddata").value);
+ t.eq(features[0].attributes.holeYardage.value, "234", "read value from extendeddata correctly.");
+ t.eq(features[0].attributes.holeYardage.displayName, "<b><i>The yardage is </i></b>", "read displayName from extendeddata correctly.");
+ t.eq(f.read(f.write(features[0]))[0].attributes.holeYardage.value, features[0].attributes.holeYardage.value, "attribute value written correctly");
+ t.eq(f.read(f.write(features[0]))[0].attributes.holeYardage.displayName, features[0].attributes.holeYardage.displayName, "attribute displayName written correctly");
+ f.kvpAttributes = true;
+ features = f.read(OpenLayers.Util.getElement("kml_extendeddata").value);
+ t.eq(features[0].attributes.holeYardage, "234", "read kvp value from extendeddata correctly.");
+ t.eq(f.read(f.write(features[0]))[0].attributes.holeYardage, features[0].attributes.holeYardage, "kvp attribute value written correctly");
+ }
+
+ function test_Format_KML_extendedData_SchemaData(t) {
+ t.plan(10);
+ var f = new OpenLayers.Format.KML();
+ var features = f.read(OpenLayers.Util.getElement("kml_extendeddata2").value);
+ t.eq(features[0].attributes.TrailHeadName.value, "Pi in the sky", "read value from extendeddata (schema data) correctly.");
+ t.eq(features[0].attributes.TrailHeadName.displayName, "TrailHeadName", "read displayName from extendeddata correctly");
+ t.eq(features[0].attributes.ElevationGain.value, "10", "read value from extendeddata (schema data) correctly.");
+ t.eq(features[0].attributes.ElevationGain.displayName, "ElevationGain", "read displayName from extendeddata correctly");
+ t.eq(f.read(f.write(features[0]))[0].attributes.TrailHeadName.value, features[0].attributes.TrailHeadName.value, "attribute value from extendeddata (schema data) written correctly");
+ t.eq(f.read(f.write(features[0]))[0].attributes.ElevationGain.value, features[0].attributes.ElevationGain.value, "attribute value from extendeddata (schema data) written correctly");
+ f.kvpAttributes = true;
+ features = f.read(OpenLayers.Util.getElement("kml_extendeddata2").value);
+ t.eq(features[0].attributes.TrailHeadName, "Pi in the sky", "read kvp value from extendeddata (schema data) correctly.");
+ t.eq(features[0].attributes.ElevationGain, "10", "read kvp value from extendeddata (schema data) correctly.");
+ t.eq(f.read(f.write(features[0]))[0].attributes.TrailHeadName, features[0].attributes.TrailHeadName, "kvp attribute value from extendeddata (schema data) written correctly");
+ t.eq(f.read(f.write(features[0]))[0].attributes.ElevationGain, features[0].attributes.ElevationGain, "kvp attribute value from extendeddata (schema data) written correctly");
+ }
+
+ function test_Format_KML_placemarkName(t) {
+ t.plan(3);
+
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ var f = new OpenLayers.Format.KML();
+
+ t.eq(f.read(f.write(feature))[0].attributes.name, feature.id, "placemark name from feature.id");
+ feature.style = {
+ label: "placemark name from style.label"
+ };
+ t.eq(f.read(f.write(feature))[0].attributes.name, feature.style.label, "placemark name from style.label");
+
+ feature.attributes.name = "placemark name from attributes.name";
+ t.eq(f.read(f.write(feature))[0].attributes.name, feature.attributes.name, "placemark name from attributes.name");
+ }
+ function test_Format_KML_linestring_projected(t) {
+ t.plan(1);
+ var f = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(15555162, 4247484), new OpenLayers.Geometry.Point(15555163, 4247485)]));
+ var format = new OpenLayers.Format.KML({
+ internalProjection: new OpenLayers.Projection("EPSG:900913"),
+ externalProjection: new OpenLayers.Projection("EPSG:4326")
+ });
+ var data = format.write(f);
+ var found = (data.search('139.734') != -1);
+ t.ok(found, "Found 139.734 (correct reprojection) in data output.");
+ }
+
+ function test_extractTracks(t) {
+
+ t.plan(13);
+
+ var xml = new OpenLayers.Format.XML();
+ var doc = xml.read(document.getElementById("macnoise.kml").firstChild.nodeValue);
+
+ var format = new OpenLayers.Format.KML({
+ extractTracks: true,
+ trackAttributes: ["speed", "num"] // additional custom attributes
+ });
+
+ var features = format.read(doc.documentElement);
+ t.eq(features.length, 170, "got 170 features");
+
+ var attr = features[4].attributes;
+
+ // standard track point attributes
+ t.ok(attr.when instanceof Date, "features have when attribute");
+ t.eq(attr.when.getTime(), 1272736815000, "correct time for fifth feature");
+ t.eq(attr.altitude, 1006, "altitude parsed");
+ t.eq(attr.heading, 230, "heading parsed");
+ t.eq(attr.tilt, 0, "tilt parsed");
+ t.eq(attr.roll, 0, "roll parsed");
+
+ // custom track attributes (all features acquire from the placemark)
+ t.eq(attr.name, "B752", "correct name");
+ t.eq(attr.adflag, "A", "correct adflag");
+ t.eq(attr.flightid, "DAL2973", "correct flightid");
+
+ // additional per point attributes (determined by trackAttributes property)
+ t.eq(attr.speed, "166", "correct speed");
+ t.eq(attr.num, "50", "correct num");
+
+ var exp = new OpenLayers.Geometry.Point(-93.0753620391713, 44.9879724110872);
+ exp.z = 1006;
+ t.geom_eq(features[4].geometry, exp, "correct geometry");
+
+ }
+
+
+ </script>
+</head>
+<body>
+ <textarea id="kml_extendeddata" style="display:none">
+<?xml version="1.0" encoding="UTF-8"?>
+<kml xmlns="http://www.opengis.net/kml/2.2">
+ <Document>
+ <name>Entity-Replacement</name>
+ <Placemark>
+ <name>Club house</name>
+ <ExtendedData>
+ <Data name="holeNumber">
+ <displayName><![CDATA[
+ <b>This is hole </b>
+ ]]></displayName>
+ <value>1</value>
+ </Data>
+ <Data name="holePar">
+ <displayName><![CDATA[
+ <i>The par for this hole is </i>
+ ]]></displayName>
+ <value>4</value>
+ </Data>
+ <Data name="holeYardage">
+ <displayName><![CDATA[<b><i>The yardage is </i></b>]]></displayName>
+ <value>234</value>
+ </Data>
+ </ExtendedData>
+ <Point>
+ <coordinates>-111.956,33.5043</coordinates>
+ </Point>
+ </Placemark>
+ <Placemark>
+ <name>By the lake</name>
+ <ExtendedData>
+ <Data name="holeNumber">
+ <displayName><![CDATA[
+ <b>This is hole </b>
+ ]]></displayName>
+ <value>5</value>
+ </Data>
+ <Data name="holePar">
+ <displayName><![CDATA[
+ <i>The par for this hole is </i>
+ ]]></displayName>
+ <value>5</value>
+ </Data>
+ <Data name="holeYardage">
+ <displayName><![CDATA[
+ <b><i>The yardage is </i></b>
+ ]]></displayName>
+ <value>523</value>
+ </Data>
+ </ExtendedData>
+ <Point>
+ <coordinates>-111.95,33.5024</coordinates>
+ </Point>
+ </Placemark>
+ </Document>
+</kml>
+</textarea>
+ <textarea id="kml_extendeddata2" style="display:none">
+<kml xmlns="http://earth.google.com/kml/2.2">
+<Document>
+<Placemark>
+ <name>Easy trail</name>
+ <ExtendedData>
+ <SchemaData schemaUrl="#TrailHeadTypeId">
+ <SimpleData name="TrailHeadName">Pi in the sky</SimpleData>
+ <SimpleData name="TrailLength">3.14159</SimpleData>
+ <SimpleData name="ElevationGain">10</SimpleData>
+ </SchemaData>
+ </ExtendedData>
+ <Point>
+ <coordinates>-122.000,37.002</coordinates>
+ </Point>
+</Placemark>
+<Placemark>
+ <name>Difficult trail</name>
+ <ExtendedData>
+ <SchemaData schemaUrl="#TrailHeadTypeId">
+ <SimpleData name="TrailHeadName">Mount Everest</SimpleData>
+ <SimpleData name="TrailLength">347.45</SimpleData>
+ <SimpleData name="ElevationGain">10000</SimpleData>
+ </SchemaData>
+ </ExtendedData>
+ <Point>
+ <coordinates>-122.000,37.002</coordinates>
+ </Point>
+</Placemark>
+</Document>
+</kml>
+</textarea>
+
+<div id="macnoise.kml"><!--
+<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2">
+<Document>
+<Camera>
+ <gx:TimeStamp>
+ <when>2010-05-01T13:00:00-05:00</when>
+ </gx:TimeStamp>
+ <longitude>-93.2207</longitude>
+ <latitude>44.882</latitude>
+ <altitude>50000</altitude>
+ <heading>0</heading>
+ <tilt>0</tilt>
+</Camera>
+<Style id="arrival">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.macnoise.com/scripts/plane.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>ff0000ff</color>
+ <width>3</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7fffffff</color>
+ </PolyStyle>
+</Style>
+<Style id="departure">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.macnoise.com/scripts/plane.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>ff00ff00</color>
+ <width>3</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7fffffff</color>
+ </PolyStyle>
+</Style>
+<Style id="overflight">
+ <IconStyle>
+ <Icon>
+ <href>http://maps.macnoise.com/scripts/plane.png</href>
+ </Icon>
+ </IconStyle>
+ <LineStyle>
+ <color>ff222222</color>
+ <width>3</width>
+ </LineStyle>
+ <PolyStyle>
+ <color>7fffffff</color>
+ </PolyStyle>
+</Style>
+<Style id='rmt'>
+ <LabelStyle>
+ <color>ff0000cc</color>
+ <colorMode>normal</colorMode>
+ <scale>1</scale>
+ </LabelStyle>
+</Style>
+
+<name>Flight Tracks</name>
+<Folder>
+ <name>Arrivals</name>
+<Placemark>
+ <name>B752</name>
+ <adflag>A</adflag>
+ <flightid>DAL2973</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:10-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:33-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.0658625188843 44.9949645987875 1036</gx:coord>
+ <gx:coord>-93.0664690096445 44.9945424635331 1036</gx:coord>
+ <gx:coord>-93.0694347065378 44.9923936108644 1036</gx:coord>
+ <gx:coord>-93.0722946883822 44.9901649091109 1006</gx:coord>
+ <gx:coord>-93.0753620391713 44.9879724110872 1006</gx:coord>
+ <gx:coord>-93.078638650624 44.985904678007 975</gx:coord>
+ <gx:coord>-93.0817463907976 44.9836868456013 975</gx:coord>
+ <gx:coord>-93.0847749343212 44.9813998515538 945</gx:coord>
+ <gx:coord>-93.0879207383429 44.9791066547511 914</gx:coord>
+ <gx:coord>-93.091282218058 44.976822731273 914</gx:coord>
+ <gx:coord>-93.0945882606646 44.9745372955479 884</gx:coord>
+ <gx:coord>-93.0979053364864 44.9722421846492 884</gx:coord>
+ <gx:coord>-93.1012678619471 44.9698451058525 853</gx:coord>
+ <gx:coord>-93.1044570741037 44.967424293466 853</gx:coord>
+ <gx:coord>-93.1068079756418 44.9657037851018 853</gx:coord>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <speed>162</speed>
+ <speed>160</speed>
+ <speed>159</speed>
+ <speed>165</speed>
+ <speed>166</speed>
+ <speed>174</speed>
+ <speed>170</speed>
+ <speed>172</speed>
+ <speed>180</speed>
+ <speed>176</speed>
+ <speed>177</speed>
+ <speed>177</speed>
+ <speed>180</speed>
+ <speed>184</speed>
+ <speed>177</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>E170</name>
+ <adflag>A</adflag>
+ <flightid>TCF7521</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:04-05</when>
+ <when>2010-05-01T13:00:09-05</when>
+ <when>2010-05-01T13:00:13-05</when>
+ <when>2010-05-01T13:00:18-05</when>
+ <when>2010-05-01T13:00:23-05</when>
+ <when>2010-05-01T13:00:27-05</when>
+ <when>2010-05-01T13:00:32-05</when>
+ <when>2010-05-01T13:00:37-05</when>
+ <when>2010-05-01T13:00:41-05</when>
+ <when>2010-05-01T13:00:46-05</when>
+ <when>2010-05-01T13:00:51-05</when>
+ <when>2010-05-01T13:00:55-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.3806146339391 44.8823651507134 2743</gx:coord>
+ <gx:coord>-93.3773041814209 44.887531728655 2743</gx:coord>
+ <gx:coord>-93.3742856469083 44.8942041806778 2743</gx:coord>
+ <gx:coord>-93.3722375106026 44.9009231720158 2743</gx:coord>
+ <gx:coord>-93.3711934089417 44.9077495987718 2712</gx:coord>
+ <gx:coord>-93.3707288919852 44.9145219645156 2712</gx:coord>
+ <gx:coord>-93.3703882714439 44.921240089024 2682</gx:coord>
+ <gx:coord>-93.3700882719793 44.9278850664392 2682</gx:coord>
+ <gx:coord>-93.369810041597 44.934389356737 2651</gx:coord>
+ <gx:coord>-93.3696836566166 44.9408553642446 2651</gx:coord>
+ <gx:coord>-93.3695425129226 44.9473561165969 2621</gx:coord>
+ <gx:coord>-93.3693185423471 44.9537360442564 2621</gx:coord>
+ <gx:coord>-93.3693194298816 44.9599975904123 2590</gx:coord>
+ <gx:coord>-93.3694031671108 44.9661411653607 2590</gx:coord>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>20 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>360 0 0</gx:angles>
+ <speed>376</speed>
+ <speed>367</speed>
+ <speed>361</speed>
+ <speed>371</speed>
+ <speed>367</speed>
+ <speed>363</speed>
+ <speed>359</speed>
+ <speed>356</speed>
+ <speed>352</speed>
+ <speed>347</speed>
+ <speed>343</speed>
+ <speed>347</speed>
+ <speed>334</speed>
+ <speed>337</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>BE33</name>
+ <adflag>A</adflag>
+ <flightid>N38175</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:02-05</when>
+ <when>2010-05-01T13:00:07-05</when>
+ <when>2010-05-01T13:00:12-05</when>
+ <when>2010-05-01T13:00:16-05</when>
+ <when>2010-05-01T13:00:21-05</when>
+ <when>2010-05-01T13:00:25-05</when>
+ <when>2010-05-01T13:00:30-05</when>
+ <when>2010-05-01T13:00:35-05</when>
+ <when>2010-05-01T13:00:39-05</when>
+ <when>2010-05-01T13:00:44-05</when>
+ <when>2010-05-01T13:00:49-05</when>
+ <when>2010-05-01T13:00:53-05</when>
+ <when>2010-05-01T13:00:58-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.0144637208028 44.6541474764804 1006</gx:coord>
+ <gx:coord>-93.0162681345228 44.6547274296664 1006</gx:coord>
+ <gx:coord>-93.0196734868835 44.6559915702004 975</gx:coord>
+ <gx:coord>-93.0231899415297 44.657188463998 945</gx:coord>
+ <gx:coord>-93.0267619421777 44.6582849847887 945</gx:coord>
+ <gx:coord>-93.0302021384369 44.6594728216183 914</gx:coord>
+ <gx:coord>-93.0338776768471 44.6606515995762 914</gx:coord>
+ <gx:coord>-93.0375866343814 44.6618806707998 884</gx:coord>
+ <gx:coord>-93.0411146687035 44.6632657982455 884</gx:coord>
+ <gx:coord>-93.0447829038862 44.6646495821585 884</gx:coord>
+ <gx:coord>-93.0486933143218 44.6659856209571 914</gx:coord>
+ <gx:coord>-93.0525604964428 44.6672664774449 884</gx:coord>
+ <gx:coord>-93.0559892061682 44.6686325276705 884</gx:coord>
+ <gx:coord>-93.0595122787868 44.6700360197293 884</gx:coord>
+ <gx:coord>-93.0610274392619 44.6706087373734 884</gx:coord>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>290 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <gx:angles>300 0 0</gx:angles>
+ <speed>150</speed>
+ <speed>156</speed>
+ <speed>152</speed>
+ <speed>156</speed>
+ <speed>151</speed>
+ <speed>152</speed>
+ <speed>160</speed>
+ <speed>157</speed>
+ <speed>159</speed>
+ <speed>158</speed>
+ <speed>158</speed>
+ <speed>160</speed>
+ <speed>155</speed>
+ <speed>155</speed>
+ <speed>156</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>A319</name>
+ <adflag>A</adflag>
+ <flightid>DAL1588</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:04-05</when>
+ <when>2010-05-01T13:00:08-05</when>
+ <when>2010-05-01T13:00:13-05</when>
+ <when>2010-05-01T13:00:18-05</when>
+ <when>2010-05-01T13:00:22-05</when>
+ <when>2010-05-01T13:00:27-05</when>
+ <when>2010-05-01T13:00:31-05</when>
+ <when>2010-05-01T13:00:36-05</when>
+ <when>2010-05-01T13:00:41-05</when>
+ <when>2010-05-01T13:00:45-05</when>
+ <when>2010-05-01T13:00:50-05</when>
+ <when>2010-05-01T13:00:55-05</when>
+ <when>2010-05-01T13:00:59-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.6927825194056 44.7952011849485 3011</gx:coord>
+ <gx:coord>-93.6850156681578 44.7968042586582 2987</gx:coord>
+ <gx:coord>-93.6752785488692 44.7990458605003 2956</gx:coord>
+ <gx:coord>-93.6657083011645 44.8014897663497 2926</gx:coord>
+ <gx:coord>-93.6560029615388 44.803768841381 2865</gx:coord>
+ <gx:coord>-93.6462045264035 44.8058749817725 2834</gx:coord>
+ <gx:coord>-93.6365671200126 44.8080848199989 2804</gx:coord>
+ <gx:coord>-93.6269933807039 44.8102767000109 2773</gx:coord>
+ <gx:coord>-93.6175405757462 44.8123960709083 2743</gx:coord>
+ <gx:coord>-93.6082528975965 44.8146455509748 2743</gx:coord>
+ <gx:coord>-93.599077315807 44.816765612372 2743</gx:coord>
+ <gx:coord>-93.5899428762254 44.8186933623744 2743</gx:coord>
+ <gx:coord>-93.5809104439923 44.8205403457841 2743</gx:coord>
+ <gx:coord>-93.5720785209701 44.8224608846058 2743</gx:coord>
+ <gx:coord>-93.5703603013364 44.8228739543212 2743</gx:coord>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <gx:angles>70 0 0</gx:angles>
+ <speed>390</speed>
+ <speed>383</speed>
+ <speed>397</speed>
+ <speed>390</speed>
+ <speed>405</speed>
+ <speed>388</speed>
+ <speed>386</speed>
+ <speed>397</speed>
+ <speed>377</speed>
+ <speed>373</speed>
+ <speed>367</speed>
+ <speed>362</speed>
+ <speed>365</speed>
+ <speed>350</speed>
+ <speed>354</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>E145</name>
+ <adflag>A</adflag>
+ <flightid>CHQ1453</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:11-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:48-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-92.5727580977974 45.0236058844647 2530</gx:coord>
+ <gx:coord>-92.5742776202954 45.0237913896498 2530</gx:coord>
+ <gx:coord>-92.5803397933112 45.0241784662561 2499</gx:coord>
+ <gx:coord>-92.5865075192046 45.0247891381303 2469</gx:coord>
+ <gx:coord>-92.5926877928765 45.0257073410966 2469</gx:coord>
+ <gx:coord>-92.5986546763805 45.0261844476041 2438</gx:coord>
+ <gx:coord>-92.6046737535477 45.0267206733977 2438</gx:coord>
+ <gx:coord>-92.6106885874739 45.0275061986719 2438</gx:coord>
+ <gx:coord>-92.616359210337 45.027935793162 2438</gx:coord>
+ <gx:coord>-92.6220735719954 45.028379077688 2438</gx:coord>
+ <gx:coord>-92.6280403097635 45.0290552550566 2438</gx:coord>
+ <gx:coord>-92.6341725652711 45.029824064212 2438</gx:coord>
+ <gx:coord>-92.640279209769 45.0304963952702 2438</gx:coord>
+ <gx:coord>-92.6463747377703 45.0311129317319 2438</gx:coord>
+ <gx:coord>-92.650043383232 45.0314890298388 2438</gx:coord>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <speed>235</speed>
+ <speed>246</speed>
+ <speed>239</speed>
+ <speed>244</speed>
+ <speed>234</speed>
+ <speed>232</speed>
+ <speed>238</speed>
+ <speed>227</speed>
+ <speed>228</speed>
+ <speed>229</speed>
+ <speed>229</speed>
+ <speed>232</speed>
+ <speed>228</speed>
+ <speed>232</speed>
+ <speed>236</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>E170</name>
+ <adflag>A</adflag>
+ <flightid>CPZ5695</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:11-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:25-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:48-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-92.3689380245182 45.0389467469425 2804</gx:coord>
+ <gx:coord>-92.3759530819834 45.0380951007958 2773</gx:coord>
+ <gx:coord>-92.3831159633175 45.0369957486846 2712</gx:coord>
+ <gx:coord>-92.3901362714549 45.0355238496347 2651</gx:coord>
+ <gx:coord>-92.3970814910858 45.0339385808083 2621</gx:coord>
+ <gx:coord>-92.4043121546626 45.032585906621 2560</gx:coord>
+ <gx:coord>-92.4118367565321 45.0319048652958 2499</gx:coord>
+ <gx:coord>-92.419078934653 45.030875157485 2469</gx:coord>
+ <gx:coord>-92.4262095560369 45.0291153314744 2438</gx:coord>
+ <gx:coord>-92.4335237384463 45.0273941113051 2438</gx:coord>
+ <gx:coord>-92.4408178608932 45.0260076351757 2438</gx:coord>
+ <gx:coord>-92.4451575746228 45.0254275529773 2438</gx:coord>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>250 0 0</gx:angles>
+ <gx:angles>260 0 0</gx:angles>
+ <speed>277</speed>
+ <speed>288</speed>
+ <speed>283</speed>
+ <speed>291</speed>
+ <speed>283</speed>
+ <speed>284</speed>
+ <speed>298</speed>
+ <speed>288</speed>
+ <speed>288</speed>
+ <speed>278</speed>
+ <speed>283</speed>
+ <speed>288</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>DC95</name>
+ <adflag>A</adflag>
+ <flightid>DAL2858</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:03-05</when>
+ <when>2010-05-01T13:00:07-05</when>
+ <when>2010-05-01T13:00:12-05</when>
+ <when>2010-05-01T13:00:17-05</when>
+ <when>2010-05-01T13:00:21-05</when>
+ <when>2010-05-01T13:00:26-05</when>
+ <when>2010-05-01T13:00:30-05</when>
+ <when>2010-05-01T13:00:35-05</when>
+ <when>2010-05-01T13:00:40-05</when>
+ <when>2010-05-01T13:00:44-05</when>
+ <when>2010-05-01T13:00:49-05</when>
+ <when>2010-05-01T13:00:54-05</when>
+ <when>2010-05-01T13:00:58-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.1962465696187 44.4584257162471 3078</gx:coord>
+ <gx:coord>-93.1954858158128 44.462643897726 3078</gx:coord>
+ <gx:coord>-93.1945524569257 44.4696206853623 3048</gx:coord>
+ <gx:coord>-93.1935347734104 44.4765680167011 3048</gx:coord>
+ <gx:coord>-93.1921548885013 44.4834366892852 3048</gx:coord>
+ <gx:coord>-93.1912787899895 44.4902740201102 3048</gx:coord>
+ <gx:coord>-93.190869393024 44.496999598511 3048</gx:coord>
+ <gx:coord>-93.190355669541 44.503701889363 3048</gx:coord>
+ <gx:coord>-93.1899042890233 44.510392533924 3048</gx:coord>
+ <gx:coord>-93.1894352972433 44.5171043633827 3048</gx:coord>
+ <gx:coord>-93.1887272976791 44.523838031578 3017</gx:coord>
+ <gx:coord>-93.1882343860587 44.5305421014878 2987</gx:coord>
+ <gx:coord>-93.1878483537445 44.5373007218153 2987</gx:coord>
+ <gx:coord>-93.187206305476 44.5440099500882 2956</gx:coord>
+ <gx:coord>-93.1870547021374 44.5466877366242 2956</gx:coord>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>10 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <gx:angles>0 0 0</gx:angles>
+ <speed>378</speed>
+ <speed>370</speed>
+ <speed>381</speed>
+ <speed>373</speed>
+ <speed>384</speed>
+ <speed>367</speed>
+ <speed>365</speed>
+ <speed>377</speed>
+ <speed>362</speed>
+ <speed>362</speed>
+ <speed>362</speed>
+ <speed>362</speed>
+ <speed>368</speed>
+ <speed>355</speed>
+ <speed>362</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>B737</name>
+ <adflag>A</adflag>
+ <flightid>SWA1488</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:11-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:48-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-92.7436038977339 45.0176449723009 2438</gx:coord>
+ <gx:coord>-92.745419752639 45.0178405701636 2438</gx:coord>
+ <gx:coord>-92.7525586927583 45.0181852080204 2438</gx:coord>
+ <gx:coord>-92.7599978682742 45.0189437491361 2438</gx:coord>
+ <gx:coord>-92.7673964649616 45.0200176804669 2438</gx:coord>
+ <gx:coord>-92.7743047878147 45.0206512321095 2438</gx:coord>
+ <gx:coord>-92.7812211106102 45.0212438545962 2438</gx:coord>
+ <gx:coord>-92.7880905786106 45.0219352711124 2438</gx:coord>
+ <gx:coord>-92.7948110303679 45.0225135550872 2438</gx:coord>
+ <gx:coord>-92.8016256231407 45.0231539091809 2377</gx:coord>
+ <gx:coord>-92.808436321378 45.0237782407713 2316</gx:coord>
+ <gx:coord>-92.8153060032773 45.0245123996427 2255</gx:coord>
+ <gx:coord>-92.8220950756464 45.0250388052127 2194</gx:coord>
+ <gx:coord>-92.8289929014999 45.0256725515916 2164</gx:coord>
+ <gx:coord>-92.8342709686589 45.0263726025032 2118.25</gx:coord>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <gx:angles>280 0 0</gx:angles>
+ <speed>280</speed>
+ <speed>293</speed>
+ <speed>284</speed>
+ <speed>288</speed>
+ <speed>274</speed>
+ <speed>272</speed>
+ <speed>279</speed>
+ <speed>263</speed>
+ <speed>263</speed>
+ <speed>262</speed>
+ <speed>262</speed>
+ <speed>275</speed>
+ <speed>270</speed>
+ <speed>277</speed>
+ <speed>287</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>A318</name>
+ <adflag>A</adflag>
+ <flightid>FFT106</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:05-05</when>
+ <when>2010-05-01T13:00:09-05</when>
+ <when>2010-05-01T13:00:14-05</when>
+ <when>2010-05-01T13:00:19-05</when>
+ <when>2010-05-01T13:00:23-05</when>
+ <when>2010-05-01T13:00:28-05</when>
+ <when>2010-05-01T13:00:33-05</when>
+ <when>2010-05-01T13:00:37-05</when>
+ <when>2010-05-01T13:00:42-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:51-05</when>
+ <when>2010-05-01T13:00:56-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.2974568508014 45.0687622602847 1432</gx:coord>
+ <gx:coord>-93.2934457905393 45.0660257042941 1371</gx:coord>
+ <gx:coord>-93.2902010482642 45.0627382200457 1341</gx:coord>
+ <gx:coord>-93.2880735868205 45.0592062737728 1280</gx:coord>
+ <gx:coord>-93.2866251180089 45.0556538417996 1280</gx:coord>
+ <gx:coord>-93.2855706436895 45.0521555770546 1249</gx:coord>
+ <gx:coord>-93.2848929213344 45.0486326683558 1249</gx:coord>
+ <gx:coord>-93.284149302237 45.0450445279501 1219</gx:coord>
+ <gx:coord>-93.2832681542582 45.0414770478452 1219</gx:coord>
+ <gx:coord>-93.2822163760078 45.0378266141909 1219</gx:coord>
+ <gx:coord>-93.2810695206555 45.0339762188888 1249</gx:coord>
+ <gx:coord>-93.2800852709943 45.0300242656845 1249</gx:coord>
+ <gx:coord>-93.2789451826991 45.026165428423 1249</gx:coord>
+ <gx:coord>-93.2776553627852 45.0222881273358 1219</gx:coord>
+ <gx:angles>140 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>150 0 0</gx:angles>
+ <gx:angles>160 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <gx:angles>170 0 0</gx:angles>
+ <speed>212</speed>
+ <speed>205</speed>
+ <speed>208</speed>
+ <speed>203</speed>
+ <speed>201</speed>
+ <speed>196</speed>
+ <speed>196</speed>
+ <speed>197</speed>
+ <speed>202</speed>
+ <speed>205</speed>
+ <speed>216</speed>
+ <speed>215</speed>
+ <speed>222</speed>
+ <speed>231</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name></name>
+ <adflag>A</adflag>
+ <flightid></flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:05-05</when>
+ <when>2010-05-01T13:00:10-05</when>
+ <when>2010-05-01T13:00:14-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:33-05</when>
+ <when>2010-05-01T13:00:37-05</when>
+ <when>2010-05-01T13:00:42-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:51-05</when>
+ <when>2010-05-01T13:00:56-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-93.5287325331323 45.3502794027397 731</gx:coord>
+ <gx:coord>-93.5305174337715 45.3463816209029 731</gx:coord>
+ <gx:coord>-93.532323089283 45.3433065196778 731</gx:coord>
+ <gx:coord>-93.5344374505075 45.3397938806867 731</gx:coord>
+ <gx:coord>-93.5365879669744 45.3355152994798 731</gx:coord>
+ <gx:coord>-93.538455345577 45.3317693717468 731</gx:coord>
+ <gx:coord>-93.5402440337749 45.3288175816964 731</gx:coord>
+ <gx:coord>-93.5420054353005 45.3261482119682 701</gx:coord>
+ <gx:coord>-93.5437972875724 45.3236486426325 701</gx:coord>
+ <gx:coord>-93.5449025453586 45.3213557809437 670</gx:coord>
+ <gx:coord>-93.5460939368394 45.3190373998605 670</gx:coord>
+ <gx:coord>-93.5479457332637 45.3165177805485 670</gx:coord>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <gx:angles>200 0 0</gx:angles>
+ <speed>202</speed>
+ <speed>180</speed>
+ <speed>166</speed>
+ <speed>171</speed>
+ <speed>162</speed>
+ <speed>157</speed>
+ <speed>143</speed>
+ <speed>145</speed>
+ <speed>156</speed>
+ <speed>147</speed>
+ <speed>147</speed>
+ <speed>150</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>CRJ2</name>
+ <adflag>A</adflag>
+ <flightid>SKW4805</flightid>
+ <styleUrl>#arrival</styleUrl>
+</Placemark>
+<Placemark>
+ <name>CRJ2</name>
+ <adflag>A</adflag>
+ <flightid>FLG4092</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:10-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:44-05</when>
+ <when>2010-05-01T13:00:49-05</when>
+ <when>2010-05-01T13:00:54-05</when>
+ <gx:coord>-93.1836067392297 44.9110362339843 432.2</gx:coord>
+ <gx:coord>-93.1841170614853 44.910663862492 426</gx:coord>
+ <gx:coord>-93.1867007876887 44.908842129317 426</gx:coord>
+ <gx:coord>-93.1893728799637 44.9069842219291 396</gx:coord>
+ <gx:coord>-93.1919479660705 44.9051548529609 365</gx:coord>
+ <gx:coord>-93.1944798212107 44.9032897679148 365</gx:coord>
+ <gx:coord>-93.197164452306 44.9014210542153 335</gx:coord>
+ <gx:coord>-93.1996234874761 44.8995719817206 335</gx:coord>
+ <gx:coord>-93.2021701211426 44.8975674983317 304</gx:coord>
+ <gx:coord>-93.2050345971567 44.8955942303701 304</gx:coord>
+ <gx:coord>-93.2075455037487 44.8938556558558 304</gx:coord>
+ <gx:coord>-93.2100820128846 44.8918590963212 304</gx:coord>
+ <gx:coord>-93.2127524858241 44.89000250047 256</gx:coord>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>230 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <gx:angles>220 0 0</gx:angles>
+ <speed>141</speed>
+ <speed>138</speed>
+ <speed>136</speed>
+ <speed>141</speed>
+ <speed>141</speed>
+ <speed>142</speed>
+ <speed>143</speed>
+ <speed>139</speed>
+ <speed>140</speed>
+ <speed>134</speed>
+ <speed>136</speed>
+ <speed>136</speed>
+ <speed>123</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+</gx:Track></Placemark>
+<Placemark>
+ <name>E170</name>
+ <adflag>A</adflag>
+ <flightid>CPZ5667</flightid>
+ <styleUrl>#arrival</styleUrl>
+<gx:Track>
+ <altitudeMode>absolute</altitudeMode>
+ <extrude>1</extrude>
+ <when>2010-05-01T13:00:00-05</when>
+ <when>2010-05-01T13:00:01-05</when>
+ <when>2010-05-01T13:00:06-05</when>
+ <when>2010-05-01T13:00:10-05</when>
+ <when>2010-05-01T13:00:15-05</when>
+ <when>2010-05-01T13:00:20-05</when>
+ <when>2010-05-01T13:00:24-05</when>
+ <when>2010-05-01T13:00:29-05</when>
+ <when>2010-05-01T13:00:34-05</when>
+ <when>2010-05-01T13:00:38-05</when>
+ <when>2010-05-01T13:00:43-05</when>
+ <when>2010-05-01T13:00:47-05</when>
+ <when>2010-05-01T13:00:52-05</when>
+ <when>2010-05-01T13:00:57-05</when>
+ <when>2010-05-01T13:01:00-05</when>
+ <gx:coord>-92.9496238812799 45.0117549407746 1438.2</gx:coord>
+ <gx:coord>-92.9507065768732 45.0116702587604 1432</gx:coord>
+ <gx:coord>-92.9563739191926 45.0116271226204 1432</gx:coord>
+ <gx:coord>-92.9620225732021 45.0115639668496 1432</gx:coord>
+ <gx:coord>-92.9673675587699 45.0113432900049 1402</gx:coord>
+ <gx:coord>-92.9725115032188 45.0111442254373 1402</gx:coord>
+ <gx:coord>-92.9778810091229 45.0112050922639 1371</gx:coord>
+ <gx:coord>-92.9832227114571 45.0112143826731 1371</gx:coord>
+ <gx:coord>-92.9884546803523 45.0110418166788 1341</gx:coord>
+ <gx:coord>-92.9938268606229 45.0109652220709 1341</gx:coord>
+ <gx:coord>-92.9991151069756 45.010802144845 1310</gx:coord>
+ <gx:coord>-93.0041467584036 45.0105516668541 1310</gx:coord>
+ <gx:coord>-93.0090742909164 45.0105233046799 1280</gx:coord>
+ <gx:coord>-93.0139435770527 45.0106265340001 1280</gx:coord>
+ <gx:coord>-93.0174882575928 45.0106328449121 1256.75</gx:coord>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <gx:angles>270 0 0</gx:angles>
+ <speed>214</speed>
+ <speed>207</speed>
+ <speed>202</speed>
+ <speed>208</speed>
+ <speed>207</speed>
+ <speed>205</speed>
+ <speed>203</speed>
+ <speed>202</speed>
+ <speed>209</speed>
+ <speed>199</speed>
+ <speed>196</speed>
+ <speed>200</speed>
+ <speed>193</speed>
+ <speed>194</speed>
+ <speed>185</speed>
+ <num>10</num>
+ <num>20</num>
+ <num>30</num>
+ <num>40</num>
+ <num>50</num>
+ <num>60</num>
+ <num>70</num>
+ <num>80</num>
+ <num>90</num>
+ <num>100</num>
+ <num>110</num>
+ <num>120</num>
+ <num>130</num>
+ <num>140</num>
+ <num>150</num>
+</gx:Track></Placemark>
+</Folder>
+<Folder>
+ <name>Departures</name>
+</Folder>
+<Folder>
+ <name>Overflights</name>
+</Folder>
+</Document>
+</kml>
+--></div>
+
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/OGCExceptionReport.html b/misc/openlayers/tests/Format/OGCExceptionReport.html
new file mode 100644
index 0000000..7846f94
--- /dev/null
+++ b/misc/openlayers/tests/Format/OGCExceptionReport.html
@@ -0,0 +1,100 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+
+ t.plan(21);
+
+ // OCG WMS 1.3.0 exceptions
+ var text = '<?xml version="1.0" encoding="UTF-8"?> ' +
+'<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+' xsi:schemaLocation="http://www.opengis.net/ogc' +
+' http://schemas.opengis.net/wms/1.3.0/exceptions_1_3_0.xsd">' +
+' <ServiceException> Plain text message about an error. </ServiceException>' +
+' <ServiceException code="InvalidUpdateSequence"> Another error message, this one with a service exception code supplied. </ServiceException>' +
+' <ServiceException>' +
+' <![CDATA[ Error in module <foo.c>, line 42' +
+'A message that includes angle brackets in text must be enclosed in a Character Data Section as in this example. All XML-like markup is ignored except for this sequence of three closing characters:' +
+']]>' +
+' </ServiceException>' +
+' <ServiceException>' +
+' <![CDATA[ <Module>foo.c</Module> <Error>An error occurred</Error> <Explanation>Similarly, actual XML can be enclosed in a CDATA section. A generic parser will ignore that XML, but application-specific software may choose to process it.</Explanation> ]]>' +
+' </ServiceException>' +
+'</ServiceExceptionReport>';
+
+ var parser = new OpenLayers.Format.OGCExceptionReport();
+ var result = parser.read(text);
+
+ var exceptions = result.exceptionReport.exceptions;
+
+ var testWMS = function(exceptions) {
+ t.eq(exceptions.length, 4, "We expect 4 exception messages");
+ t.eq(exceptions[0].text, " Plain text message about an error. ", "First error message correctly parsed");
+ t.eq(exceptions[1].code, "InvalidUpdateSequence", "Code of second error message correctly parsed");
+ t.eq(exceptions[1].text, " Another error message, this one with a service exception code supplied. ", "Text of second error message correctly parsed");
+ t.eq(OpenLayers.String.trim(exceptions[2].text), "Error in module <foo.c>, line 42A message that includes angle brackets in text must be enclosed in a Character Data Section as in this example. All XML-like markup is ignored except for this sequence of three closing characters:", "Third message correctly parsed");
+ t.eq(OpenLayers.String.trim(exceptions[3].text), "<Module>foo.c</Module> <Error>An error occurred</Error> <Explanation>Similarly, actual XML can be enclosed in a CDATA section. A generic parser will ignore that XML, but application-specific software may choose to process it.</Explanation>", "Fourth message correctly parsed");
+ };
+
+ testWMS(exceptions);
+
+ // OGC WMS 1.1.1 exceptions
+ text = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?> <!DOCTYPE ServiceExceptionReport SYSTEM "http://schemas.opengis.net/wms/1.1.1/WMS_exception_1_1_1.dtd"> ' +
+'<ServiceExceptionReport version="1.1.1">' +
+' <ServiceException> Plain text message about an error. </ServiceException>' +
+' <ServiceException code="InvalidUpdateSequence"> Another error message, this one with a service exception code supplied. </ServiceException>' +
+' <ServiceException>' +
+' <![CDATA[ Error in module <foo.c>, line 42' +
+'A message that includes angle brackets in text must be enclosed in a Character Data Section as in this example. All XML-like markup is ignored except for this sequence of three closing characters:' +
+']]>' +
+' </ServiceException>' +
+' <ServiceException>' +
+' <![CDATA[ <Module>foo.c</Module> <Error>An error occurred</Error> <Explanation>Similarly, actual XML can be enclosed in a CDATA section. A generic parser will ignore that XML, but application-specific software may choose to process it.</Explanation> ]]>' +
+' </ServiceException>' +
+'</ServiceExceptionReport>';
+ result = parser.read(text);
+ exceptions = result.exceptionReport.exceptions;
+ testWMS(exceptions);
+
+ // OGC WFS 1.0.0 exceptions
+ text = '<?xml version="1.0" ?> ' +
+'<ServiceExceptionReport version="1.2.0" xmlns="http://www.opengis.net/ogc"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+' xsi:schemaLocation="http://www.opengis.net/ogc http://schemas.opengis.net/wfs/1.0.0/OGC-exception.xsd">' +
+' <ServiceException code="999" locator="INSERT STMT 01"> parse error: missing closing tag for element WKB_GEOM </ServiceException>' +
+'</ServiceExceptionReport>';
+ result = parser.read(text);
+ t.eq(result.exceptionReport.exceptions[0].code, "999", "code parsed correctly");
+ t.eq(result.exceptionReport.exceptions[0].locator, "INSERT STMT 01", "locator parsed correctly");
+ t.eq(result.exceptionReport.exceptions[0].text, " parse error: missing closing tag for element WKB_GEOM ", "error text parsed correctly");
+
+ // OGC WFS 1.1.0 exceptions that use OWSCommon 1.0
+ text = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<ows:ExceptionReport language="en" version="1.0.0"' +
+' xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.0.0/owsExceptionReport.xsd"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows">' +
+' <ows:Exception locator="foo" exceptionCode="InvalidParameterValue">' +
+' <ows:ExceptionText>Update error: Error occured updating features</ows:ExceptionText>' +
+' <ows:ExceptionText>Second exception line</ows:ExceptionText>' +
+' </ows:Exception>' +
+'</ows:ExceptionReport>';
+
+ var result = parser.read(text);
+ var report = result.exceptionReport;
+ t.eq(report.version, "1.0.0", "Version parsed correctly");
+ t.eq(report.language, "en", "Language parsed correctly");
+ var exception = report.exceptions[0];
+ t.eq(exception.code, "InvalidParameterValue", "exceptionCode properly parsed");
+ t.eq(exception.locator, "foo", "locator properly parsed");
+ t.eq(exception.texts[0], "Update error: Error occured updating features", "ExceptionText correctly parsed");
+ t.eq(exception.texts[1], "Second exception line", "Second ExceptionText correctly parsed");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/OSM.html b/misc/openlayers/tests/Format/OSM.html
new file mode 100644
index 0000000..6ceb316
--- /dev/null
+++ b/misc/openlayers/tests/Format/OSM.html
@@ -0,0 +1,115 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script src="../data/osm.js"></script>
+ <script type="text/javascript">
+
+ function test_Format_OSM_constructor(t) {
+ t.plan(5);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.OSM(options);
+ t.ok(format instanceof OpenLayers.Format.OSM,
+ "new OpenLayers.Format.OSM returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ t.eq(format.externalProjection.getCode(), "EPSG:4326",
+ "default external projection is EPSG:4326");
+ }
+ function test_Format_OSM_node(t) {
+ t.plan(4);
+ var f = new OpenLayers.Format.OSM();
+ var features = f.read(osm_test_data['node']);
+ var feat = features[0];
+ t.eq(feat.attributes, {}, "attributes is empty");
+ t.eq(feat.osm_id, 200545, "internal osm_id property set correctly");
+ t.eq(feat.geometry.x, -1.8166417, "lon is correct");
+ t.eq(feat.geometry.y, 52.5503033, "lat is correct");
+ }
+ function test_Format_OSM_node_with_tags(t) {
+ t.plan(5);
+ var f = new OpenLayers.Format.OSM();
+ var features = f.read(osm_test_data['node_with_tags']);
+ var feat = features[0];
+ t.eq(feat.attributes, {'a':'b'}, "attributes match");
+ t.eq(feat.osm_id, 200545, "internal osm_id property set correctly");
+ t.eq(feat.fid, "node.200545", "OSM-based FID set correctly.");
+ t.eq(feat.geometry.x, -1.8166417, "lon is correct");
+ t.eq(feat.geometry.y, 52.5503033, "lat is correct");
+ }
+ function test_Format_OSM_way(t) {
+ t.plan(8);
+ var f = new OpenLayers.Format.OSM();
+ var features = f.read(osm_test_data['way']);
+ t.eq(features.length, 1, "One feature");
+ var feat = features[0];
+ t.eq(feat.osm_id, 4685537, "OSM ID set correctly.");
+ t.eq(feat.fid, "way.4685537", "OSM-based FID set correctly.");
+ t.eq(feat.geometry.CLASS_NAME, "OpenLayers.Geometry.Polygon", "returned as polygon");
+ t.eq(feat.geometry.components[0].components.length, 11, "Correct number of components");
+ t.eq(feat.geometry.components[0].components[0].osm_id, 29783472, "OSM ID set on components");
+ t.eq(feat.geometry.toString(), "POLYGON((-1.8164007 52.5501836,-1.8170311 52.5506035,-1.8164092 52.5509559,-1.8169385 52.5513103,-1.8159626 52.5517893,-1.8145067 52.5518461,-1.8143197 52.5511883,-1.8141177 52.5506446,-1.8151451 52.5501275,-1.8157703 52.5505521,-1.8164007 52.5501836))", "WKT of feature is correct");
+ t.eq(feat.attributes.landuse, "school", "landuse attribute correct");
+ }
+
+ function test_Format_OSM_node_way(t) {
+ t.plan(5)
+ var f = new OpenLayers.Format.OSM();
+ var features = f.read(osm_test_data['node_way']);
+ t.eq(features.length, 1, "One feature");
+ var feat = features[0];
+ t.eq(feat.osm_id, 21329267, "OSM ID set correctly");
+ t.eq(feat.attributes.highway, "unclassified", "highway attribute is correct.");
+ t.eq(feat.geometry.CLASS_NAME, "OpenLayers.Geometry.LineString", "returned as linestring");
+ t.eq(feat.geometry.components.length, 12, "correct number of segments");
+ }
+
+ function test_Format_OSM_node_way_checkTags(t) {
+ t.plan(9)
+ var f = new OpenLayers.Format.OSM({'checkTags': true});
+ var features = f.read(osm_test_data['node_way']);
+ t.eq(features.length, 3, "multiple features");
+
+ var feat = features[1];
+ t.eq(feat.geometry.CLASS_NAME, "OpenLayers.Geometry.Point", "point class");
+ t.ok(feat.attributes != {}, "feature has attributes");
+
+ var feat = features[2];
+ t.eq(feat.geometry.CLASS_NAME, "OpenLayers.Geometry.Point", "point class");
+ t.ok(feat.attributes != {}, "feature has attributes");
+
+ feat = features[0];
+ t.eq(feat.osm_id, 21329267, "OSM ID set correctly");
+ t.eq(feat.attributes.highway, "unclassified", "highway attribute is correct.");
+ t.eq(feat.geometry.CLASS_NAME, "OpenLayers.Geometry.LineString", "returned as linestring");
+ t.eq(feat.geometry.components.length, 12, "correct number of segments");
+ }
+
+ function test_Format_OSM_serialize(t) {
+ t.plan(4);
+ var f = new OpenLayers.Format.OSM({'checkTags': true});
+ for (var key in osm_serialized_data) {
+ var input = f.read(osm_test_data[key]);
+ var output = f.write(input);
+ output = output.replace(/<\?[^>]*\?>/, '');
+ t.eq(output, osm_serialized_data[key], key + " serialized correctly");
+ }
+ }
+ function test_Format_OSM_write_reproject(t) {
+ t.plan(1);
+ var f = new OpenLayers.Format.OSM({'internalProjection': new OpenLayers.Projection("EPSG:900913")});
+ var feat = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(100000, 100000)
+ );
+ var data = f.write([feat]);
+ var f = new OpenLayers.Format.OSM();
+ var features = f.read(data);
+
+ t.eq(OpenLayers.Util.toFloat(features[0].geometry.x, 3), .898, "exported to lonlat and re-read as lonlat correctly")
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/OWSCommon/v1_0_0.html b/misc/openlayers/tests/Format/OWSCommon/v1_0_0.html
new file mode 100644
index 0000000..9d255b2
--- /dev/null
+++ b/misc/openlayers/tests/Format/OWSCommon/v1_0_0.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+ t.plan(6);
+ var text = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<ows:ExceptionReport language="en" version="1.0.0"' +
+' xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.0.0/owsExceptionReport.xsd"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows">' +
+' <ows:Exception locator="foo" exceptionCode="InvalidParameterValue">' +
+' <ows:ExceptionText>Update error: Error occured updating features</ows:ExceptionText>' +
+' <ows:ExceptionText>Second exception line</ows:ExceptionText>' +
+' </ows:Exception>' +
+'</ows:ExceptionReport>';
+
+ var format = new OpenLayers.Format.OWSCommon();
+ var result = format.read(text);
+ var report = result.exceptionReport;
+ t.eq(report.version, "1.0.0", "Version parsed correctly");
+ t.eq(report.language, "en", "Language parsed correctly");
+ var exception = report.exceptions[0];
+ t.eq(exception.code, "InvalidParameterValue", "exceptionCode properly parsed");
+ t.eq(exception.locator, "foo", "locator properly parsed");
+ t.eq(exception.texts[0], "Update error: Error occured updating features", "ExceptionText correctly parsed");
+ t.eq(exception.texts[1], "Second exception line", "Second ExceptionText correctly parsed");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/OWSCommon/v1_1_0.html b/misc/openlayers/tests/Format/OWSCommon/v1_1_0.html
new file mode 100644
index 0000000..1cdf7ee
--- /dev/null
+++ b/misc/openlayers/tests/Format/OWSCommon/v1_1_0.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+ t.plan(6);
+ var text = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<ows:ExceptionReport xml:lang="en" version="1.1.0"' +
+' xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.1.0/owsExceptionReport.xsd"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows/1.1">' +
+' <ows:Exception locator="foo" exceptionCode="InvalidParameterValue">' +
+' <ows:ExceptionText>Update error: Error occured updating features</ows:ExceptionText>' +
+' <ows:ExceptionText>Second exception line</ows:ExceptionText>' +
+' </ows:Exception>' +
+'</ows:ExceptionReport>';
+
+ var format = new OpenLayers.Format.OWSCommon();
+ var result = format.read(text);
+ var report = result.exceptionReport;
+ t.eq(report.version, "1.1.0", "Version parsed correctly");
+ t.eq(report.language, "en", "Language parsed correctly");
+ var exception = report.exceptions[0];
+ t.eq(exception.code, "InvalidParameterValue", "exceptionCode properly parsed");
+ t.eq(exception.locator, "foo", "locator properly parsed");
+ t.eq(exception.texts[0], "Update error: Error occured updating features", "ExceptionText correctly parsed");
+ t.eq(exception.texts[1], "Second exception line", "Second ExceptionText correctly parsed");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/OWSContext/v0_3_1.html b/misc/openlayers/tests/Format/OWSContext/v0_3_1.html
new file mode 100644
index 0000000..54b63b3
--- /dev/null
+++ b/misc/openlayers/tests/Format/OWSContext/v0_3_1.html
@@ -0,0 +1,278 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_wmswfs(t) {
+ t.plan(17);
+ // taken from http://www.ogcnetwork.net/schemas/owc/0.3.1/context_nested.xml
+ // adapted: add an extra slash (roads/railways) in the Title of the WMS layer to test nesting
+ var text = '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<OWSContext version="0.3.1" id="ows-context-ex-1-v3" xmlns="http://www.opengis.net/ows-context"' +
+ ' xmlns:gml="http://www.opengis.net/gml" xmlns:kml="http://www.opengis.net/kml/2.2"' +
+ ' xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows"' +
+ ' xmlns:sld="http://www.opengis.net/sld" xmlns:xlink="http://www.w3.org/1999/xlink"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+ ' xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd">' +
+ ' <General>' +
+ ' <ows:BoundingBox crs="EPSG:4326">' +
+ ' <ows:LowerCorner>-117 32</ows:LowerCorner>' +
+ ' <ows:UpperCorner>-116 33</ows:UpperCorner>' +
+ ' </ows:BoundingBox>' +
+ ' <ows:Title>OWS Context version 0.3.1 showing nested layers</ows:Title>' +
+ ' </General>' +
+ ' <ResourceList>' +
+ ' <!-- WMS Example -->' +
+ ' <Layer name="topp:major_roads" queryable="1" hidden="1">' +
+ ' <ows:Title>Tiger 2005fe major roads/railways</ows:Title>' +
+ ' <ows:OutputFormat>image/png</ows:OutputFormat>' +
+ ' <Server service="urn:ogc:serviceType:WMS" version="1.1.1">' +
+ ' <OnlineResource' +
+ ' xlink:href="http://sigma.openplans.org:8080/geoserver/wms?SERVICE=WMS"/>' +
+ ' </Server>' +
+ ' <!-- WFS Example -->' +
+ ' <Layer name="topp:gnis_pop" hidden="0">' +
+ ' <ows:Title>GNIS Population</ows:Title>' +
+ ' <Server service="urn:ogc:serviceType:WFS" version="1.0.0">' +
+ ' <OnlineResource xlink:href="geoserver/wfs?"/>' +
+ ' </Server>' +
+ ' </Layer>' +
+ ' </Layer>' +
+ ' </ResourceList>' +
+ '</OWSContext>';
+ var parser = new OpenLayers.Format.OWSContext();
+ var map = new OpenLayers.Map('map', {allOverlays: true, fractionalZoom: true});
+ var context = parser.read(text, {map: map});
+ t.eq(context.layers.length, 2, "2 layers parsed from OWSContext document");
+ t.eq(context.layers[1].metadata.nestingPath[0], "Tiger 2005fe major roads/railways", "Nesting path correctly set");
+ t.ok(context.layers[0] instanceof OpenLayers.Layer.WMS, "First layer is a WMS layer");
+ t.ok(context.layers[1] instanceof OpenLayers.Layer.Vector, "Second layer is a vector layer");
+ t.eq(context.layers[0].params.LAYERS, "topp:major_roads", "WMS layer name correctly read");
+ t.eq(context.layers[0].params.FORMAT, "image/png", "WMS format correctly read");
+ t.eq(context.layers[0].url, "http://sigma.openplans.org:8080/geoserver/wms?SERVICE=WMS", "Layer url correctly read");
+ t.eq(context.layers[0].getVisibility(), false, "WMS Layer is hidden");
+ t.ok(context.layers[0].queryable, "WMS layer is queryable");
+ t.eq(context.layers[0].name, "Tiger 2005fe major roads/railways", "Title correctly set");
+ t.ok(context.layers[1].protocol instanceof OpenLayers.Protocol.WFS.v1_0_0, "Vector layer configured with a WFS Protocol");
+ t.eq(context.layers[1].protocol.url, "geoserver/wfs?", "WFS url set correctly");
+ t.ok(context.layers[1].strategies[0] instanceof OpenLayers.Strategy.BBOX, "BBOX strategy configured correctly");
+ t.eq(context.layers[1].name, "GNIS Population", "Title of second layer correctly set");
+ t.eq(context.layers[1].getVisibility(), true, "Second layer is visible");
+ map.zoomToExtent(new OpenLayers.Bounds(-117, 32, -116, 33));
+ var owc = parser.write(map, {id: 'ows-context-ex-1-v3', title: 'OWS Context version 0.3.1 showing nested layers'});
+ t.xml_eq(text, owc, "Can we roundtrip this nested OWSContext with a WMS and WFS layer?");
+ t.eq(context.layers[1].metadata.nestingPath[0], "Tiger 2005fe major roads/railways", "Nesting path is preserved even after calling write");
+ }
+
+ function test_write_wmswfs(t) {
+ t.plan(1);
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map = new OpenLayers.Map( 'map' );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'},
+ {singleTile: true}
+ );
+ var wfs = new OpenLayers.Layer.Vector("myroads", {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol.WFS({
+ url: "foo/wfs?",
+ featureType: "roads",
+ featureNS: "http://foo/myns"
+ })
+ });
+ map.addLayers([layer, wfs]);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var owc = new OpenLayers.Format.OWSContext();
+ var output = owc.write(map, {id: 'foo'});
+ var expected = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="foo" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-5.986328125 29.013671875</ows:LowerCorner><ows:UpperCorner>15.986328125 50.986328125</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer name="feature:roads" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">myroads</ows:Title><Server version="1.0.0" service="urn:ogc:serviceType:WFS"><OnlineResource xlink:href="foo/wfs?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer></ResourceList></OWSContext>';
+ t.xml_eq(output, expected, "OWSContext with a WMS and a WFS layer generated correctly");
+ }
+
+ function test_write_wmsinlinegml(t) {
+ t.plan(1);
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map = new OpenLayers.Map( 'map' );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'},
+ {singleTile: true}
+ );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var vector = new OpenLayers.Layer.Vector();
+ map.addLayer(vector);
+ var feature1 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,1));
+ var feature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,0));
+ vector.addFeatures(feature1);
+ vector.addFeatures(feature2);
+ var owc = new OpenLayers.Format.OWSContext();
+ var output = owc.write(map, {id: 'foo'});
+ var expected = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="foo" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-5.986328125 29.013671875</ows:LowerCorner><ows:UpperCorner>15.986328125 50.986328125</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer name="vector" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows"/><InlineGeometry><gml:boundedBy xmlns:gml="http://www.opengis.net/gml"><gml:Box><gml:coordinates decimal="." cs="," ts=" ">0,0 1,1</gml:coordinates></gml:Box></gml:boundedBy><gml:featureMember xmlns:gml="http://www.opengis.net/gml"><feature:vector xmlns:feature="http://mapserver.gis.umn.edu/mapserver"><feature:geometry><gml:Point><gml:coordinates decimal="." cs="," ts=" ">0,1</gml:coordinates></gml:Point></feature:geometry></feature:vector></gml:featureMember><gml:featureMember xmlns:gml="http://www.opengis.net/gml"><feature:vector xmlns:feature="http://mapserver.gis.umn.edu/mapserver"><feature:geometry><gml:Point><gml:coordinates decimal="." cs="," ts=" ">1,0</gml:coordinates></gml:Point></feature:geometry></feature:vector></gml:featureMember></InlineGeometry></Layer></ResourceList></OWSContext>';
+ t.xml_eq(output, expected, "OWSContext with a WMS and an inline GML vector layer generated correctly");
+ }
+
+ function test_write_inlinegml_no_features(t){
+ var lon = 5,
+ lat = 40,
+ zoom = 5,
+ map = new OpenLayers.Map( 'map' ),
+ layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'},
+ {singleTile: true}
+ ),
+ vector = new OpenLayers.Layer.Vector();
+
+ map.addLayers( [ layer, vector ] );
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var owc = new OpenLayers.Format.OWSContext(),
+ output,
+ caughtException = false,
+ expectedXml = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="foo" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-5.986328125 29.013671875</ows:LowerCorner><ows:UpperCorner>15.986328125 50.986328125</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer name="vector" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows"/><InlineGeometry/></Layer></ResourceList></OWSContext>';
+
+ try {
+ output = owc.write(map, {id: 'foo'});
+ } catch (e){
+ caughtException = true;
+ }
+
+ if (caughtException) {
+ t.plan(1);
+ t.fail('OWSContext with a WMS and an inline vector layer failed and threw an exception');
+ } else {
+ t.plan(2);
+ t.ok(true, 'OWSContext with a WMS and an inline vector layer generated without exception');
+ t.xml_eq(output, expectedXml, "OWSContext with a WMS and an inline vector layer generated correctly");
+ }
+ }
+
+ function test_read_inline(t) {
+ t.plan(10);
+ var text = '<?xml version="1.0" encoding="UTF-8"?><OWSContext xmlns="http://www.opengis.net/ows-context" xmlns:gml="http://www.opengis.net/gml" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows" xmlns:sld="http://www.opengis.net/sld" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="0.3.1" id="ows-context-ex-1-v3" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.0/owsContext.xsd"><General><ows:BoundingBox crs="urn:ogc:def:crs:EPSG:6.6:4326"><ows:LowerCorner>-117.44667178362664 32.57086210449395</ows:LowerCorner><ows:UpperCorner>-116.74066794885977 32.921986352104064</ows:UpperCorner></ows:BoundingBox><ows:Title>OWS Context version 0.3.0 Inline KML and GML examples</ows:Title></General><ResourceList><!-- WMS Example --><Layer name="topp:major_roads" queryable="1" hidden="1"> <ows:Title>Tiger 2005fe major roads</ows:Title> <ows:OutputFormat>image/png</ows:OutputFormat><Server service="urn:ogc:serviceType:WMS" version="1.1.1"><OnlineResource xlink:href="http://sigma.openplans.org:8080/geoserver/wms?SERVICE=WMS"/></Server></Layer><!-- Inline KML Example --><Layer name="archsites"><ows:Title>Architectural Sites</ows:Title><kml:Document><kml:name>opengeo:archsites 1 to 100</kml:name><kml:Style id="archsitesStyle"><kml:IconStyle><kml:color>ffffffff</kml:color><kml:colorMode>normal</kml:colorMode><kml:Icon><kml:href>http://maps.google.com/mapfiles/kml/pal4/icon25.png</kml:href></kml:Icon></kml:IconStyle></kml:Style><kml:Placemark id="archsites.1"><kml:name>Signature Rock</kml:name><kml:description>Signature Rock Description</kml:description><kml:styleUrl>#archsitesStyle</kml:styleUrl><kml:Point><kml:coordinates>-103.82681673,44.38162255</kml:coordinates></kml:Point></kml:Placemark></kml:Document></Layer><!-- Inline GML Example --><Layer name="coastg"><ows:Title>Coastg as GML Points</ows:Title><InlineGeometry><gml:boundedBy><gml:Box><gml:coord><gml:X>-43.379</gml:X><gml:Y>72.746</gml:Y></gml:coord><gml:coord><gml:X>-43.390</gml:X><gml:Y>72.755</gml:Y></gml:coord></gml:Box></gml:boundedBy><gml:featureMember><au1:coastg xmlns:au1="http://www.ionicsoft.com/wfs" fid="coastg.0"><au1:MERGE>1</au1:MERGE><au1:AREA>0.0020000000000000005</au1:AREA><au1:PERIMETER>0.167</au1:PERIMETER><au1:GEOMETRY><gml:Polygon srsName="urn:ogc:def:crs:EPSG:6.6:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>129.29167335893825,71.9583353847737 129.29167335893825,72.0000014248896 129.33332733905414,72.0000014248896 129.33332733905414,71.9583353847737 129.29167335893825,71.9583353847737</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></au1:GEOMETRY></au1:coastg></gml:featureMember><gml:featureMember><au1:coastg xmlns:au1="http://www.ionicsoft.com/wfs" fid="coastg.1"><au1:MERGE>1</au1:MERGE><au1:AREA>0.0020000000000000005</au1:AREA><au1:PERIMETER>0.167</au1:PERIMETER><au1:GEOMETRY><gml:Polygon srsName="urn:ogc:def:crs:EPSG:6.6:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>135.45832829609282,35.66666796381659 135.41667179597695,35.66666796381659 135.41667179597695,35.70833202393249 135.45832829609282,35.70833202393249 135.45832829609282,35.66666796381659</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></au1:GEOMETRY></au1:coastg></gml:featureMember></InlineGeometry></Layer></ResourceList></OWSContext>';
+ var parser = new OpenLayers.Format.OWSContext();
+ var context = parser.read(text, {map: 'map'});
+ t.ok(context.layers[1] instanceof OpenLayers.Layer.Vector, "Inline KML results in a vector layer");
+ t.eq(context.layers[1].features.length, 1, "Inline KML layer has one feature");
+ t.ok(context.layers[1].features[0].geometry instanceof OpenLayers.Geometry.Point, "KML feature is a point");
+ t.eq(context.layers[1].features[0].attributes.description, "Signature Rock Description", "KML Description correctly parsed");
+ t.eq(context.layers[1].features[0].fid, "archsites.1", "KML feature id correctly parsed");
+ t.eq(context.layers[1].features[0].style.externalGraphic, "http://maps.google.com/mapfiles/kml/pal4/icon25.png", "Style url for KML feature correctly parsed");
+ t.ok(context.layers[2] instanceof OpenLayers.Layer.Vector, "Inline GML results in a vector layer");
+ t.eq(context.layers[2].features.length, 2, "Inline GML layer has two features");
+ t.ok(context.layers[2].features[0].geometry instanceof OpenLayers.Geometry.Polygon, "GML feature is a polygon");
+ t.eq(context.layers[2].features[0].attributes.MERGE, "1", "GML attribute read correctly");
+ }
+
+ function test_read_gml(t) {
+ t.plan(5);
+ var text = '<?xml version="1.0" encoding="UTF-8"?><OWSContext version="0.3.0" id="ows-context-ex-1-v3" xmlns="http://www.opengis.net/ows-context" xmlns:gml="http://www.opengis.net/gml" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows" xmlns:sld="http://www.opengis.net/sld" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.0/owsContext.xsd"><General><ows:BoundingBox crs="urn:ogc:def:crs:EPSG:6.6:4326"><ows:LowerCorner>-117.44667178362664 32.57086210449395</ows:LowerCorner><ows:UpperCorner>-116.74066794885977 32.921986352104064</ows:UpperCorner></ows:BoundingBox><ows:Title>OWS Context version 0.3.0 examples</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0" opacity="1"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><!-- Referenced GML Example --><Layer name="Landuse"><ows:Title>Boston Landuse Polygons</ows:Title><Server service="urn:ogc:serviceType:GML" version="2.1.2" title="Cadcorp GeognoSIS.NET Web Feature Service"><OnlineResource xlink:href="gml/MassGIS/LandUse.gml"/></Server><sld:MinScaleDenominator>5000</sld:MinScaleDenominator><sld:MaxScaleDenominator>50000</sld:MaxScaleDenominator><MaxFeatures>99</MaxFeatures></Layer></ResourceList></OWSContext>';
+ var parser = new OpenLayers.Format.OWSContext();
+ var context = parser.read(text, {map: 'map'});
+ t.ok(context.layers[1].protocol instanceof OpenLayers.Protocol.HTTP, "serviceType GML is translated into an HTTP Protocol");
+ t.eq(context.layers[1].protocol.url, "gml/MassGIS/LandUse.gml", "Url of GML file correctly set");
+ t.ok(context.layers[1].protocol.format instanceof OpenLayers.Format.GML, "GML Format associated with protocol");
+ t.eq(Math.round(context.layers[1].minScale), 50000, "Minscale correctly read");
+ t.eq(Math.round(context.layers[1].maxScale), 5000, "Maxscale correctly read");
+ }
+
+ function test_read_kml(t) {
+ t.plan(3);
+ var text = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="foo" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-5.986328125 27.9150390625</ows:LowerCorner><ows:UpperCorner>15.986328125 52.0849609375</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0" opacity="1"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">KML</ows:Title><Server version="2.2" service="urn:ogc:serviceType:KML"><OnlineResource xlink:href="foo/sundials.kml" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer></ResourceList></OWSContext>';
+ var parser = new OpenLayers.Format.OWSContext();
+ var context = parser.read(text, {map: 'map'});
+ t.ok(context.layers[1].protocol instanceof OpenLayers.Protocol.HTTP, "serviceType KML is translated into an HTTP Protocol");
+ t.eq(context.layers[1].protocol.url, "foo/sundials.kml", "Url of KML file correctly set");
+ t.ok(context.layers[1].protocol.format instanceof OpenLayers.Format.KML, "KML Format associated with protocol");
+ }
+
+ function test_write_gml(t) {
+ t.plan(1);
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map = new OpenLayers.Map( 'map' );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'},
+ {singleTile: true}
+ );
+ var sundials = new OpenLayers.Layer.Vector("GML", {
+ projection: map.displayProjection,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "foo/sundials.gml",
+ format: new OpenLayers.Format.GML()
+ })
+ });
+ map.addLayers([layer, sundials]);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var owc = new OpenLayers.Format.OWSContext();
+ var output = owc.write(map, {id: 'foo'});
+ var expected = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="foo" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-5.986328125 29.013671875</ows:LowerCorner><ows:UpperCorner>15.986328125 50.986328125</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">GML</ows:Title><Server version="2.1.2" service="urn:ogc:serviceType:GML"><OnlineResource xlink:href="foo/sundials.gml" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer></ResourceList></OWSContext>';
+ t.xml_eq(output, expected, "OWSContext with a WMS and a GML vector layer generated correctly");
+ }
+
+ function test_write_kml(t) {
+ t.plan(1);
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map = new OpenLayers.Map( 'map' );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'},
+ {singleTile: true}
+ );
+ var sundials = new OpenLayers.Layer.Vector("KML", {
+ projection: map.displayProjection,
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "foo/sundials.kml",
+ format: new OpenLayers.Format.KML({
+ extractStyles: true
+ })
+ })
+ });
+ map.addLayers([layer, sundials]);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var owc = new OpenLayers.Format.OWSContext();
+ var output = owc.write(map, {id: 'foo'});
+ var expected = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="foo" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-5.986328125 29.013671875</ows:LowerCorner><ows:UpperCorner>15.986328125 50.986328125</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer name="basic" queryable="0" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers WMS</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://labs.metacarta.com/wms/vmap0" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">KML</ows:Title><Server version="2.2" service="urn:ogc:serviceType:KML"><OnlineResource xlink:href="foo/sundials.kml" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer></ResourceList></OWSContext>';
+ t.xml_eq(output, expected, "OWSContext with a WMS and a KML vector layer generated correctly");
+ }
+
+ function test_nested(t) {
+ t.plan(4);
+ var text = '<OWSContext xmlns="http://www.opengis.net/ows-context" version="0.3.1" id="machu" xsi:schemaLocation="http://www.opengis.net/ows-context http://www.ogcnetwork.net/schemas/owc/0.3.1/owsContext.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><ows:BoundingBox xmlns:ows="http://www.opengis.net/ows" crs="EPSG:4326"><ows:LowerCorner>-40 30</ows:LowerCorner><ows:UpperCorner>55 125</ows:UpperCorner></ows:BoundingBox><ows:Title xmlns:ows="http://www.opengis.net/ows">OpenLayers OWSContext</ows:Title></General><ResourceList><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">General Bathymetric Chart</ows:Title><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">GEBCO</ows:Title><Layer name="GEBCO" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">GEBCO</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/jpeg</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_bathymetry?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/gebco.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer></Layer></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">Administrative boundaries</ows:Title><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">National boundaries</ows:Title><Layer name="GAUL" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">GAUL</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_topography?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/administrative_boundaries_land.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">Maritime boundaries</ows:Title><Layer name="World_Maritime_Boundaries_v4_20090811" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">World_Maritime_Boundaries_v4_20090811</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_topography?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/administrative_boundaries_sea.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer></Layer></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">Cultural Heritage Underwater</ows:Title><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">Sites</ows:Title><Layer name="ARCH_NL" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_NL</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_nl?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer><Layer name="ARCH_PL" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_PL</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_pl?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer><Layer name="ARCH_PT" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_PT</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_pt?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer><Layer name="ARCH_BE" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_BE</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_be?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer><Layer name="ARCH_SE" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_SE</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_se?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer><Layer name="ARCH_DE" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_DE</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_de?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer><Layer name="ARCH_UK" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">ARCH_UK</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_uk?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server><StyleList><Style><Name>default</Name><Title>default</Title><LegendURL><OnlineResource xlink:href="http://foo/services/geoservices/legends/machu/cultural_heritage_underwater.png" xmlns:xlink="http://www.w3.org/1999/xlink"/></LegendURL></Style></StyleList></Layer></Layer></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">Theme1</ows:Title><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">layer1</ows:Title><Layer name="TEST_AREA_BE" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">TEST_AREA_BE</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_be?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer><Layer name="TEST_AREA_PT" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">TEST_AREA_PT</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_pt?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer></Layer></Layer><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">Theme2</ows:Title><Layer><ows:Title xmlns:ows="http://www.opengis.net/ows">layer1</ows:Title><Layer name="TEST_AREA_SE" queryable="1" hidden="0"><ows:Title xmlns:ows="http://www.opengis.net/ows">TEST_AREA_SE</ows:Title><ows:OutputFormat xmlns:ows="http://www.opengis.net/ows">image/png</ows:OutputFormat><Server version="1.1.1" service="urn:ogc:serviceType:WMS"><OnlineResource xlink:href="http://foo/bar_se?" xmlns:xlink="http://www.w3.org/1999/xlink"/></Server></Layer></Layer></Layer></ResourceList></OWSContext>';
+ var parser = new OpenLayers.Format.OWSContext();
+ var map = new OpenLayers.Map('map', {allOverlays: true, fractionalZoom: true});
+ var context = parser.read(text, {map: map});
+ t.eq(map.layers.length, 13, "13 layers parsed from document");
+ t.eq(map.layers[0].metadata.nestingPath.join("/"), "General Bathymetric Chart/GEBCO", "Category layers read correctly");
+ t.eq(map.layers[0].metadata.styles[0].legend.url, "http://foo/services/geoservices/legends/machu/gebco.png", "Legend url correctly parsed");
+ map.zoomToExtent(new OpenLayers.Bounds(-40, 30, 55, 125));
+ var owc = parser.write(map, {id: 'machu'});
+ t.xml_eq(text, owc, "Can we roundtrip nested OWSContext successfully?");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/QueryStringFilter.html b/misc/openlayers/tests/Format/QueryStringFilter.html
new file mode 100644
index 0000000..b38d1e4
--- /dev/null
+++ b/misc/openlayers/tests/Format/QueryStringFilter.html
@@ -0,0 +1,306 @@
+<html>
+<head>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(4);
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.QueryStringFilter(options);
+ t.ok(format instanceof OpenLayers.Format.QueryStringFilter,
+ "new OpenLayers.Format.QueryStringFilter object");
+ t.eq(format.foo, "bar", "constructor sets options correctly")
+ t.eq(typeof format.write, 'function', 'format has a write function');
+ t.eq(format.options, options, "format.options correctly set");
+ }
+
+ function test_write(t) {
+ t.plan(30);
+
+ // setup
+
+ var format, filter, params;
+
+ format = new OpenLayers.Format.QueryStringFilter();
+
+ // 1 test
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(0, 1, 2, 3)
+ });
+ params = format.write(filter);
+ t.eq(params.bbox, [0, 1, 2, 3], "correct bbox param if passed a BBOX filter");
+
+ // 3 tests
+ var lon = 100, lat = 200, tolerance = 10;
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ value: new OpenLayers.Geometry.Point(lon, lat),
+ distance: tolerance
+ });
+ params = format.write(filter);
+ t.eq(params.lon, lon, "correct lon param if passed a DWITHIN filter");
+ t.eq(params.lat, lat, "correct lat param if passed a DWITHIN filter");
+ t.eq(params.tolerance, tolerance, "correct tolerance param if passed a DWITHIN filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.WITHIN,
+ value: new OpenLayers.Geometry.Point(lon, lat)
+ });
+ params = format.write(filter);
+ t.eq(params.lon, lon, "correct lon param if passed a WITHIN filter");
+ t.eq(params.lat, lat, "correct lat param if passed a WITHIN filter");
+
+ // Some bbox filters used in the next tests.
+
+ var bboxFilter1 = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(0, 0, 10, 10)
+ });
+
+ var bboxFilter2 = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(0, 0, 20, 20)
+ });
+
+ // 1 test
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: []
+ });
+ params = format.write(filter);
+ t.eq(params, {}, "returns empty object if given empty AND Logical filter");
+
+ // 1 test
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.OR,
+ filters: [
+ bboxFilter1
+ ]
+ });
+ params = format.write(filter);
+ t.eq(params, {}, "does not support OR Logical filter");
+
+ // 1 test
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ bboxFilter1
+ ]
+ });
+ params = format.write(filter);
+ t.eq(params.bbox, [0, 0, 10, 10],
+ "correct bbox param if passed a Logical filter containing a BBOX");
+
+ // 1 test
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ bboxFilter1, bboxFilter2
+ ]
+ });
+ params = format.write(filter);
+ t.eq(params.bbox, [0, 0, 20, 20],
+ "correct bbox param if passed multiple BBOX filter in a Logical filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an EQUAL_TO filter");
+ t.eq(params["foo__eq"], "bar",
+ "correct param key and value if passed an EQUAL_TO filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an NOT_EQUAL_TO filter");
+ t.eq(params["foo__ne"], "bar",
+ "correct param key and value if passed an NOT_EQUAL_TO filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "foo",
+ value: "bar"
+ });
+ var params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an LESS_THAN filter");
+ t.eq(params["foo__lt"], "bar",
+ "correct param key and value if passed an LESS_THAN filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ });
+ var params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an LESS_THAN_OR_EQUAL_TO filter");
+ t.eq(params["foo__lte"], "bar",
+ "correct param key and value if passed an LESS_THAN_OR_EQUAL_TO filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN,
+ property: "foo",
+ value: "bar"
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an GREATER_THAN filter");
+ t.eq(params["foo__gt"], "bar",
+ "correct param key and value if passed an GREATER_THAN filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an GREATER_THAN_OR_EQUAL_TO filter");
+ t.eq(params["foo__gte"], "bar",
+ "correct param key and value if passed an GREATER_THAN_OR_EQUAL_TO filter");
+
+ // 2 tests
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "foo",
+ value: "bar"
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed a LIKE filter");
+ t.eq(params["foo__ilike"], "bar",
+ "correct param key and value if passed an LIKE filter");
+
+ // 4 tests
+ filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ }),
+ new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "foo2",
+ value: "baz"
+ })
+ ]
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed an EQUAL_TO filter within a AND filter");
+ t.eq(params["foo__eq"], "bar",
+ "correct param key and value if passed an EQUAL_TO filter within a AND filter");
+ t.eq(params.queryable[1], "foo2",
+ "correct queryable param if passed a LESS_THAN filter within a AND filter");
+ t.eq(params["foo2__lt"], "baz",
+ "correct param key and value if passed a LESS_THAN filter within a AND filter");
+
+ // 2 tests
+ format = new OpenLayers.Format.QueryStringFilter({wildcarded: true});
+ filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "foo",
+ value: "bar"
+ });
+ params = format.write(filter);
+ t.eq(params.queryable[0], "foo",
+ "correct queryable param if passed a LIKE filter (wildcarded true)");
+ t.eq(params["foo__ilike"], "%bar%",
+ "correct param key and value if passed an LIKE filter (wildcarded true)");
+ }
+
+ function test_regex2value(t) {
+ t.plan(16);
+
+ // setup
+
+ var format = new OpenLayers.Format.QueryStringFilter();
+
+ var value;
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LIKE,
+ property: "prop"
+ });
+
+ function serialize(value) {
+ filter.value = value;
+ return format.write(filter).prop__ilike;
+ }
+
+ // test
+
+ value = serialize("foo");
+ t.eq(value, "foo", 'regex2value converts "foo" to "foo"');
+
+ value = serialize("foo%");
+ t.eq(value, "foo\\%", 'regex2value converts "foo%" to "foo\\%"');
+
+ value = serialize("foo.*");
+ t.eq(value, "foo%", 'regex2value converts "foo.*" to "foo%"');
+
+ value = serialize("f.*oo.*");
+ t.eq(value, "f%oo%", 'regex2value converts "f.*oo.*" to "f%oo%"');
+
+ value = serialize("foo.");
+ t.eq(value, "foo_", 'regex2value converts "foo." to "foo_"');
+
+ value = serialize("f.oo.");
+ t.eq(value, "f_oo_", 'regex2value converts "f.oo." to "f_oo_"');
+
+ value = serialize("f.oo.*");
+ t.eq(value, "f_oo%", 'regex2value converts "f.oo.*" to "f_oo%"');
+
+ value = serialize("foo\\\\");
+ t.eq(value, "foo\\\\", 'regex2value converts "foo\\\\" to "foo\\\\"');
+
+ value = serialize("foo\\.");
+ t.eq(value, "foo.", 'regex2value converts "foo\\." to "foo."');
+
+ value = serialize("foo\\\\.");
+ t.eq(value, "foo\\\\_", 'regex2value converts "foo\\\\." to "foo\\\\_"');
+
+ value = serialize("foo\\*");
+ t.eq(value, "foo*", 'regex2value converts "foo\\*" to "foo*"');
+
+ value = serialize("foo\\\\*");
+ t.eq(value, "foo\\\\*", 'regex2value converts "foo\\\\*" to "foo\\\\*"');
+
+ value = serialize("foo\\\\.*");
+ t.eq(value, "foo\\\\%", 'regex2value converts "foo\\\\.*" to "foo\\\\%"');
+
+ value = serialize("fo\\.o.*");
+ t.eq(value, "fo.o%", 'regex2value converts from "fo\\.o.*" to "fo.o%"');
+
+ value = serialize("fo.*o\\.");
+ t.eq(value, "fo%o.", 'regex2value converts from "fo.*o\\." to "to%o."');
+
+ value = serialize("\\*\\..*.\\\\.*\\\\.%");
+ t.eq(value, "*.%_\\\\%\\\\_\\%",
+ 'regex2value converts from "\\*\\..*.\\\\.*\\\\.%" ' +
+ 'to "*.%_\\\\%\\\\_\\%"');
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/SLD.html b/misc/openlayers/tests/Format/SLD.html
new file mode 100644
index 0000000..bc4bd82
--- /dev/null
+++ b/misc/openlayers/tests/Format/SLD.html
@@ -0,0 +1,36 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var test_content = '<sld:StyledLayerDescriptor xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml"><sld:NamedLayer><sld:Name>TestLayer</sld:Name><sld:UserStyle><sld:Name>foo</sld:Name><sld:FeatureTypeStyle><sld:Rule><sld:Name>bar</sld:Name><ogc:Filter></ogc:Filter><sld:PolygonSymbolizer><sld:Fill><sld:CssParameter name="fill"><ogc:Literal>blue</ogc:Literal></sld:CssParameter></sld:Fill></sld:PolygonSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor>';
+
+ function test_Format_SLD_constructor(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.SLD(options);
+ t.ok(format instanceof OpenLayers.Format.SLD,
+ "new OpenLayers.Format.SLD returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ }
+
+ function test_Format_SLD_read(t) {
+ t.plan(4);
+ var sld = new OpenLayers.Format.SLD().read(this.test_content);
+
+ var testLayer = sld.namedLayers["TestLayer"];
+ var userStyles = testLayer.userStyles;
+
+ t.eq(userStyles[0].name, "foo", "SLD correctly reads a UserStyle named 'foo'");
+ t.eq(userStyles[0].rules.length, 1, "The number of rules for the UserStyle is correct");
+ t.eq(userStyles[0].rules[0].name, "bar", "The first rule's name is 'bar'");
+ t.eq(userStyles[0].rules[0].symbolizer.Polygon.fillColor, "blue", "The fillColor for the Polygon symbolizer is correct");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/SLD/v1_0_0.html b/misc/openlayers/tests/Format/SLD/v1_0_0.html
new file mode 100644
index 0000000..fbc18a6
--- /dev/null
+++ b/misc/openlayers/tests/Format/SLD/v1_0_0.html
@@ -0,0 +1,1028 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var xml = new OpenLayers.Format.XML();
+ function readXML(id) {
+ return xml.read(document.getElementById(id).firstChild.nodeValue);
+ }
+
+ var sld =
+ '<StyledLayerDescriptor version="1.0.0" ' +
+ 'xmlns="http://www.opengis.net/sld" ' +
+ 'xmlns:gml="http://www.opengis.net/gml" ' +
+ 'xmlns:ogc="http://www.opengis.net/ogc" ' +
+ 'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
+ 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
+ 'xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd">' +
+ '<NamedLayer>' +
+ '<Name>AAA161</Name>' +
+ '<UserStyle>' +
+ '<FeatureTypeStyle>' +
+ '<Rule>' +
+ '<Name>stortsteen</Name>' +
+ '<ogc:Filter>' +
+ '<ogc:PropertyIsEqualTo>' +
+ '<ogc:PropertyName>CTE</ogc:PropertyName>' +
+ '<ogc:Literal>V0305</ogc:Literal>' +
+ '</ogc:PropertyIsEqualTo>' +
+ '</ogc:Filter>' +
+ '<MaxScaleDenominator>50000</MaxScaleDenominator>' +
+ '<PolygonSymbolizer>' +
+ '<Fill>' +
+ '<CssParameter name="fill">#ffffff</CssParameter>' +
+ '</Fill>' +
+ '<Stroke>' +
+ '<CssParameter name="stroke">#000000</CssParameter>' +
+ '</Stroke>' +
+ '</PolygonSymbolizer>' +
+ '<TextSymbolizer>' +
+ '<Label>' +
+ 'A <ogc:PropertyName>FOO</ogc:PropertyName> label' +
+ '</Label>' +
+ '<Font>' +
+ '<CssParameter name="font-family">Arial</CssParameter>' +
+ '<CssParameter name="font-size">14</CssParameter>' +
+ '<CssParameter name="font-weight">bold</CssParameter>' +
+ '<CssParameter name="font-style">normal</CssParameter>' +
+ '</Font>' +
+ '<LabelPlacement>' +
+ '<PointPlacement>' +
+ '<AnchorPoint>' +
+ '<AnchorPointX>0.5</AnchorPointX>' +
+ '<AnchorPointY>0.5</AnchorPointY>' +
+ '</AnchorPoint>' +
+ '<Displacement>' +
+ '<DisplacementX>5</DisplacementX>' +
+ '<DisplacementY>5</DisplacementY>' +
+ '</Displacement>' +
+ '<Rotation>45</Rotation>' +
+ '</PointPlacement>' +
+ '</LabelPlacement>' +
+ '<Halo>' +
+ '<Radius>3</Radius>' +
+ '<Fill>' +
+ '<CssParameter name="fill">#ffffff</CssParameter>' +
+ '</Fill>' +
+ '</Halo>' +
+ '<Fill>' +
+ '<CssParameter name="fill">#000000</CssParameter>' +
+ '</Fill>' +
+ '</TextSymbolizer>' +
+ '</Rule>' +
+ '<Rule>' +
+ '<Name>betonbekleding</Name>' +
+ '<ogc:Filter>' +
+ '<ogc:PropertyIsLessThan>' +
+ '<ogc:PropertyName>CTE</ogc:PropertyName>' +
+ '<ogc:Literal>1000</ogc:Literal>' +
+ '</ogc:PropertyIsLessThan>' +
+ '</ogc:Filter>' +
+ '<MaxScaleDenominator>50000</MaxScaleDenominator>' +
+ '<PolygonSymbolizer>' +
+ '<Fill>' +
+ '<CssParameter name="fill">#ffff00</CssParameter>' +
+ '</Fill>' +
+ '<Stroke>' +
+ '<CssParameter name="stroke">#0000ff</CssParameter>' +
+ '</Stroke>' +
+ '</PolygonSymbolizer>' +
+ '</Rule>' +
+ '</FeatureTypeStyle>' +
+ '</UserStyle>' +
+ '</NamedLayer>' +
+ '<NamedLayer>' +
+ '<Name>Second Layer</Name>' +
+ '<UserStyle>' +
+ '<FeatureTypeStyle>' +
+ '<Rule>' +
+ '<Name>first rule second layer</Name>' +
+ '<ogc:Filter>' +
+ '<ogc:Or>' +
+ '<ogc:PropertyIsBetween>' +
+ '<ogc:PropertyName>number</ogc:PropertyName>' +
+ '<ogc:LowerBoundary>' +
+ '<ogc:Literal>1064866676</ogc:Literal>' +
+ '</ogc:LowerBoundary>' +
+ '<ogc:UpperBoundary>' +
+ '<ogc:Literal>1065512599</ogc:Literal>' +
+ '</ogc:UpperBoundary>' +
+ '</ogc:PropertyIsBetween>' +
+ '<ogc:PropertyIsLike wildCard="*" singleChar="." escape="!">' +
+ '<ogc:PropertyName>cat</ogc:PropertyName>' +
+ '<ogc:Literal>*dog.food!*good</ogc:Literal>' +
+ '</ogc:PropertyIsLike>' +
+ '<ogc:Not>' +
+ '<ogc:PropertyIsLessThanOrEqualTo>' +
+ '<ogc:PropertyName>FOO</ogc:PropertyName>' +
+ '<ogc:Literal>5000</ogc:Literal>' +
+ '</ogc:PropertyIsLessThanOrEqualTo>' +
+ '</ogc:Not>' +
+ '</ogc:Or>' +
+ '</ogc:Filter>' +
+ '<MaxScaleDenominator>10000</MaxScaleDenominator>' +
+ '<PointSymbolizer>' +
+ '<Graphic>' +
+ '<Mark>' +
+ '<WellKnownName>star</WellKnownName>' +
+ '<Fill>' +
+ '<CssParameter name="fill">lime</CssParameter>' +
+ '</Fill>' +
+ '<Stroke>' +
+ '<CssParameter name="stroke">olive</CssParameter>' +
+ '<CssParameter name="stroke-width">2</CssParameter>' +
+ '</Stroke>' +
+ '</Mark>' +
+ '<Size><ogc:PropertyName>SIZE</ogc:PropertyName></Size>' +
+ '</Graphic>' +
+ '</PointSymbolizer>' +
+ '</Rule>' +
+ '</FeatureTypeStyle>' +
+ '</UserStyle>' +
+ '</NamedLayer>' +
+ '</StyledLayerDescriptor>';
+
+ function test_read(t) {
+ t.plan(23);
+
+ var xml = new OpenLayers.Format.XML();
+ var sldxml = xml.read(sld);
+
+ // test that format options are considered in read
+ var parser = new OpenLayers.Format.SLD({
+ version: "1.0.0",
+ namedLayersAsArray: true
+ });
+ var obj = parser.read(sldxml);
+ t.ok(obj.namedLayers instanceof Array, "namedLayersAsArray option for read works");
+
+ parser = new OpenLayers.Format.SLD.v1_0_0();
+ var obj = parser.read(sldxml, {namedLayersAsArray: true});
+ t.ok(obj.namedLayers instanceof Array, "namedLayersAsArray option for read works");
+ var arrayLen = obj.namedLayers.length;
+
+ var obj = parser.read(sldxml);
+ t.eq(typeof obj.namedLayers, "object", "read returns a namedLayers object by default");
+ // test the named layer count
+ var count = 0;
+ for(var key in obj.namedLayers) {
+ ++count;
+ }
+ t.eq(count, arrayLen, "number of named layers in array equals number of named layers in object");
+
+ var layer, style, rule;
+
+ // check the first named layer
+ layer = obj.namedLayers["AAA161"];
+ t.ok(layer, "first named layer exists");
+ t.ok(layer.userStyles instanceof Array, "(AAA161) layer has array of user styles");
+ t.eq(layer.userStyles.length, 1, "(AAA161) first layer has a single user style");
+ t.eq(layer.userStyles[0].rules.length, 2, "(AAA161) first style has two rules");
+ var rule = layer.userStyles[0].rules[0];
+ t.ok(rule.filter, "(AAA161) first rule has a filter");
+ var symbolizer = rule.symbolizer;
+ t.ok(symbolizer, "(AAA161) first rule has a symbolizer");
+ var poly = symbolizer["Polygon"];
+ t.eq(poly.fillColor, "#ffffff", "(AAA161) first rule has proper fill");
+ t.eq(poly.strokeColor, "#000000", "(AAA161) first rule has proper stroke");
+ var text = symbolizer["Text"];
+ t.eq(text.label, "A ${FOO} label", "(AAA161) first rule has proper text label");
+ t.eq(layer.userStyles[0].propertyStyles["label"], true, "label added to propertyStyles");
+ t.eq(text.fontFamily, "Arial", "(AAA161) first rule has proper font family");
+ t.eq(text.fontColor, "#000000", "(AAA161) first rule has proper text fill");
+ t.eq(text.haloRadius, "3", "(AAA161) first rule has proper halo radius");
+ t.eq(text.haloColor, "#ffffff", "(AAA161) first rule has proper halo color");
+
+
+ // check the first user style
+ style = layer.userStyles[0];
+ t.ok(style instanceof OpenLayers.Style, "(AAA161,0) user style is instance of OpenLayers.Style");
+ t.eq(style.rules.length, 2, "(AAA161,0) user style has 2 rules");
+
+ // check the second rule
+ rule = style.rules[1];
+ var feature = {
+ layer: {
+ map: {
+ getScale: function(){
+ return 40000;
+ }
+ }
+ },
+ attributes: {
+ CTE: "900"
+ }
+ };
+ t.ok(typeof rule.maxScaleDenominator == "number", "MaxScaleDenominator is a number");
+ t.eq(rule.evaluate(feature), true, "numeric filter comparison evaluates correctly");
+
+ // check for PropertyName size
+ layer = obj.namedLayers["Second Layer"];
+ style = layer.userStyles[0];
+ rule = style.rules[0];
+ t.eq(rule.symbolizer["Point"].graphicWidth, "${SIZE}", "dynamic size correctly set on graphicWidth");
+
+ // etc. I'm convinced read works, really wanted to test write (since examples don't test that)
+ // I'll add more tests here later.
+
+ }
+
+ function test_write(t) {
+ t.plan(3);
+
+ // read first - testing that write produces the SLD above
+ var parser = new OpenLayers.Format.SLD.v1_0_0();
+ var xml = new OpenLayers.Format.XML();
+ var sldxml = xml.read(sld);
+ var obj = parser.read(sldxml);
+
+ var node = parser.write(obj);
+ t.xml_eq(node, sld, "SLD correctly written");
+
+ obj = parser.read(sldxml, {namedLayersAsArray: true});
+ node = parser.write(obj);
+ t.xml_eq(node, sld, "SLD from namedLayers array correctly written");
+
+ // test that 0 fill opacity gets written
+ var symbolizer = {
+ fillColor: "red",
+ fillOpacity: 0
+ };
+ var root = parser.createElementNSPlus("PolygonSymbolizer");
+ var got = parser.writeNode("Fill", symbolizer, root);
+ var expect =
+ '<Fill xmlns="http://www.opengis.net/sld">' +
+ '<CssParameter name="fill">red</CssParameter>' +
+ '<CssParameter name="fill-opacity">0</CssParameter>' +
+ '</Fill>';
+ t.xml_eq(got, expect, "zero fill opacity written");
+ }
+
+ function test_writePointSymbolizer(t) {
+
+ t.plan(3);
+
+ var parser = new OpenLayers.Format.SLD.v1_0_0();
+ var symbolizer, node, exp;
+
+ // test symbolizer with fill color only
+ symbolizer = {
+ "fillColor": "blue"
+ };
+ node = parser.writeNode("sld:PointSymbolizer", symbolizer);
+ exp =
+ '<PointSymbolizer xmlns="http://www.opengis.net/sld">' +
+ '<Graphic>' +
+ '<Mark>' +
+ '<Fill>' +
+ '<CssParameter name="fill">blue</CssParameter>' +
+ '</Fill>' +
+ '<Stroke/>' +
+ '</Mark>' +
+ '</Graphic>' +
+ '</PointSymbolizer>';
+ t.xml_eq(node, exp, "fillColor only written");
+
+ // test symbolizer with stroke color only
+ symbolizer = {
+ "strokeColor": "blue"
+ };
+ node = parser.writeNode("sld:PointSymbolizer", symbolizer);
+ exp =
+ '<PointSymbolizer xmlns="http://www.opengis.net/sld">' +
+ '<Graphic>' +
+ '<Mark>' +
+ '<Fill/>' +
+ '<Stroke>' +
+ '<CssParameter name="stroke">blue</CssParameter>' +
+ '</Stroke>' +
+ '</Mark>' +
+ '</Graphic>' +
+ '</PointSymbolizer>';
+ t.xml_eq(node, exp, "strokeColor only written");
+
+ // test symbolizer with graphic name only
+ symbolizer = {
+ "graphicName": "star"
+ };
+ node = parser.writeNode("sld:PointSymbolizer", symbolizer);
+ exp =
+ '<PointSymbolizer xmlns="http://www.opengis.net/sld">' +
+ '<Graphic>' +
+ '<Mark>' +
+ '<WellKnownName>star</WellKnownName>' +
+ '<Fill/>' +
+ '<Stroke/>' +
+ '</Mark>' +
+ '</Graphic>' +
+ '</PointSymbolizer>';
+ t.xml_eq(node, exp, "graphicName only written");
+
+
+ }
+
+
+ function test_writeLineSymbolizer(t) {
+
+ t.plan(1);
+
+ var parser = new OpenLayers.Format.SLD.v1_0_0();
+ var symbolizer, node, exp;
+
+ // test symbolizer with fill color only
+ symbolizer = {
+ strokeDashstyle: "4 4",
+ strokeLinecap: "round",
+ strokeColor: "#0000ff",
+ strokeWidth: 2
+ };
+ node = parser.writeNode("sld:LineSymbolizer", symbolizer);
+ exp =
+ '<LineSymbolizer xmlns="http://www.opengis.net/sld">' +
+ '<Stroke>' +
+ '<CssParameter name="stroke">#0000ff</CssParameter>' +
+ '<CssParameter name="stroke-width">2</CssParameter>' +
+ '<CssParameter name="stroke-dasharray">4 4</CssParameter>' +
+ '<CssParameter name="stroke-linecap">round</CssParameter>' +
+ '</Stroke>' +
+ '</LineSymbolizer>';
+ t.xml_eq(node, exp, "line symbolizer correctly written");
+
+
+ }
+
+ function test_writeTextSymbolizer(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.SLD.v1_0_0();
+ var symbolizer = {
+ "Text": {
+ "label": "This is the ${city} in ${state}.",
+ "fontFamily": "Arial",
+ "fontSize": 10,
+ "fontColor": "blue",
+ "fontWeight": "bold",
+ "fontStyle": "normal",
+ "haloRadius": 2,
+ "haloColor": "white"
+ }
+ };
+ var node = parser.writers["sld"]["TextSymbolizer"].apply(
+ parser, [symbolizer["Text"]]
+ );
+
+ var expected =
+ '<TextSymbolizer xmlns="http://www.opengis.net/sld">' +
+ '<Label>' +
+ 'This is the ' +
+ '<ogc:PropertyName xmlns:ogc="http://www.opengis.net/ogc">city</ogc:PropertyName>' +
+ ' in ' +
+ '<ogc:PropertyName xmlns:ogc="http://www.opengis.net/ogc">state</ogc:PropertyName>' +
+ '.' +
+ '</Label>' +
+ '<Font>' +
+ '<CssParameter name="font-family">Arial</CssParameter>' +
+ '<CssParameter name="font-size">10</CssParameter>' +
+ '<CssParameter name="font-weight">bold</CssParameter>' +
+ '<CssParameter name="font-style">normal</CssParameter>' +
+ '</Font>' +
+ '<Halo>' +
+ '<Radius>2</Radius>' +
+ '<Fill>' +
+ '<CssParameter name="fill">white</CssParameter>' +
+ '</Fill>' +
+ '</Halo>' +
+ '<Fill>' +
+ '<CssParameter name="fill">blue</CssParameter>' +
+ '</Fill>' +
+ '</TextSymbolizer>';
+
+ t.xml_eq(node, expected, "TextSymbolizer correctly written");
+
+ }
+
+ function test_writeSpatialFilter(t) {
+
+ t.plan(1);
+
+ var format = new OpenLayers.Format.SLD.v1_0_0();
+
+ var rule = new OpenLayers.Rule({
+ name: "test",
+ filter: new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(0, 0, 10, 10)
+ })
+ });
+
+ var sld = format.writeNode("sld:Rule", rule);
+
+ var expect =
+ '<Rule xmlns="http://www.opengis.net/sld">' +
+ '<Name>test</Name>' +
+ '<ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<ogc:BBOX>' +
+ '<gml:Box xmlns:gml="http://www.opengis.net/gml">' +
+ '<gml:coordinates decimal="." cs="," ts=" ">0,0 10,10</gml:coordinates>' +
+ '</gml:Box>' +
+ '</ogc:BBOX>' +
+ '</ogc:Filter>' +
+ '</Rule>';
+
+ t.xml_eq(sld, expect, "rule with spatial filter correctly written");
+
+ }
+
+ function test_RasterSymbolizer(t) {
+ t.plan(4);
+
+ var format = new OpenLayers.Format.SLD.v1_0_0();
+
+ var snippet =
+ '<sld:RasterSymbolizer xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc">' +
+ '<sld:Geometry>' +
+ '<ogc:PropertyName>geom</ogc:PropertyName>' +
+ '</sld:Geometry>' +
+ '<sld:Opacity>1</sld:Opacity>' +
+ '<sld:ColorMap>' +
+ '<sld:ColorMapEntry color="#000000" opacity="0.5" quantity="0" label="nodata"/>' +
+ '<sld:ColorMapEntry color="#00FFFF" quantity="1" label="values"/>' +
+ '<sld:ColorMapEntry color="#FF0000" quantity="1000" label="values"/>' +
+ '</sld:ColorMap>' +
+ '</sld:RasterSymbolizer>';
+ var expected = new OpenLayers.Format.XML().read(snippet).documentElement;
+
+ var symbolizer = {};
+ format.readNode(expected, {symbolizer: symbolizer});
+
+ t.eq(symbolizer.Raster.colorMap[0].quantity, 0, "quantity set correctly");
+ t.eq(symbolizer.Raster.colorMap[0].opacity, 0.5, "opacity set correctly");
+ t.eq(symbolizer.Raster.colorMap[1].opacity, undefined, "non-existent opacity results in undefined");
+
+ var got = format.writeNode("sld:RasterSymbolizer", symbolizer["Raster"]);
+
+ t.xml_eq(got, expected, "Successfully round tripped RasterSymbolizer");
+ }
+
+ function test_zIndex(t) {
+ t.plan(1);
+
+ var format = new OpenLayers.Format.SLD.v1_0_0({
+ multipleSymbolizers: true
+ });
+
+ // three zIndex values -> three FeatureTypeStyle elements
+ var style = new OpenLayers.Style2({
+ rules: [
+ new OpenLayers.Rule({
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ }),
+ minScaleDenominator: 100000,
+ maxScaleDenominator: 200000,
+ symbolizers: [
+ new OpenLayers.Symbolizer.Line({
+ strokeColor: "green",
+ strokeWidth: 2,
+ zIndex: 2
+ }),
+ new OpenLayers.Symbolizer.Line({
+ strokeColor: "red",
+ strokeWidth: 3,
+ zIndex: -1
+ }),
+ new OpenLayers.Symbolizer.Line({
+ strokeColor: "blue",
+ strokeWidth: 1,
+ zIndex: 5
+ })
+ ]
+ }),
+ new OpenLayers.Rule({
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "foo",
+ value: "baz"
+ }),
+ symbolizers: [
+ new OpenLayers.Symbolizer.Line({
+ strokeColor: "#000000",
+ strokeWidth: 2,
+ zIndex: 2
+ })
+ ]
+ })
+ ]
+ });
+
+ var got = format.writeNode("sld:UserStyle", style);
+ var exp = readXML("zindex_test.sld").documentElement;
+ t.xml_eq(got, exp, "duplicated rules to write zIndex as FeatureTypeStyle elements");
+
+ }
+
+ function test_whitespace(t) {
+ t.plan(1);
+ var xml = readXML("propertyisbetweenwhitespace.sld");
+ var output = new OpenLayers.Format.SLD().read(xml);
+ var filter = output.namedLayers['geonode:US_Stat0'].userStyles[0].rules[0].filter;
+ t.eq(filter.lowerBoundary, 29.7, "whitespace ignored in values and value transformed to number");
+ }
+
+ function test_label_LinePlacement(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.SLD.v1_0_0({
+ multipleSymbolizers: true
+ });
+ // labelPerpendicularOffset takes precedence over labelAlign
+ var style = new OpenLayers.Style2({
+ rules: [
+ new OpenLayers.Rule({
+ symbolizers: [
+ new OpenLayers.Symbolizer.Line({
+ strokeColor: "red",
+ strokeWidth: 3
+ }),
+ new OpenLayers.Symbolizer.Text({
+ label: "${FOO}",
+ labelPerpendicularOffset: 10,
+ labelAlign: "rb"
+ })
+ ]
+ })
+ ]
+ });
+ var got = format.writeNode("sld:UserStyle", style);
+ var exp = readXML("label_lineplacement_test.sld").documentElement;
+ t.xml_eq(got, exp, "LinePlacement written out correctly");
+ }
+
+ function test_labelAlignToAnchorPosition(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.SLD.v1_0_0({
+ multipleSymbolizers: true
+ });
+ var style = new OpenLayers.Style2({
+ rules: [
+ new OpenLayers.Rule({
+ symbolizers: [
+ new OpenLayers.Symbolizer.Text({
+ label: "${FOO}",
+ labelAlign: "rb"
+ })
+ ]
+ })
+ ]
+ });
+ var got = format.writeNode("sld:UserStyle", style);
+ var exp = readXML("label_pointplacement_test.sld").documentElement;
+ t.xml_eq(got, exp, "PointPlacement with labelAlign written out correctly");
+ }
+
+ function test_read_FeatureTypeStyles(t) {
+
+ t.plan(13);
+
+ var format = new OpenLayers.Format.SLD.v1_0_0({
+ multipleSymbolizers: true,
+ namedLayersAsArray: true
+ });
+ var doc = readXML("line_linewithborder.sld");
+
+ var obj = format.read(doc);
+
+ t.eq(obj.namedLayers.length, 1, "got one named layer");
+ var namedLayer = obj.namedLayers[0];
+
+ t.eq(namedLayer.userStyles.length, 1, "got one user style");
+ var userStyle = namedLayer.userStyles[0];
+ t.ok(userStyle instanceof OpenLayers.Style2, "user style represented with OpenLayers.Style2");
+
+ // check rules and symbolizers
+ var rule, symbolizer;
+
+ t.eq(userStyle.rules.length, 2, "pulled two rules (from two FeatureTypeStyle elements)");
+ rule = userStyle.rules[0];
+ t.ok(rule instanceof OpenLayers.Rule, "first rule is an OpenLayers.Rule");
+
+ t.eq(rule.symbolizers && rule.symbolizers.length, 1, "first rule has one symbolizer");
+ symbolizer = rule.symbolizers[0];
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "first symbolizer in first rule is an OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Line, "first symbolizer in first rule is an OpenLayers.Symbolizer.Line");
+ t.eq(symbolizer.zIndex, 0, "symbolizer from first FeatureTypeStyle element has zIndex 0");
+
+ rule = userStyle.rules[1];
+ t.eq(rule.symbolizers && rule.symbolizers.length, 1, "second rule has one symbolizer");
+ symbolizer = rule.symbolizers[0];
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "first symbolizer in second rule is an OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Line, "first symbolizer in second rule is an OpenLayers.Symbolizer.Line");
+ t.eq(symbolizer.zIndex, 1, "symbolizer from second FeatureTypeStyle element has zIndex 1");
+
+ }
+
+ function test_roundtrip(t) {
+
+ t.plan(5);
+
+ var format = new OpenLayers.Format.SLD.v1_0_0({
+ multipleSymbolizers: true,
+ namedLayersAsArray: true
+ });
+ var doc, out;
+
+ // two FeatureTypeStyle elements and line symbolizers
+ doc = readXML("line_linewithborder.sld");
+ out = format.write(format.read(doc));
+ t.xml_eq(out, doc.documentElement, "round-tripped line_linewithborder.sld");
+
+ // three FeatureTypeStyle elements and line symbolizers
+ doc = readXML("line_attributebasedline.sld");
+ out = format.write(format.read(doc));
+ t.xml_eq(out, doc.documentElement, "round-tripped line_attributebasedline.sld");
+
+ // point symbolizer and text symbolizer
+ doc = readXML("point_pointwithdefaultlabel.sld");
+ out = format.write(format.read(doc));
+ t.xml_eq(out, doc.documentElement, "round-tripped point_pointwithdefaultlabel.sld");
+
+ // polygon symbolizer with fill only
+ doc = readXML("polygon_simplepolygon.sld");
+ out = format.write(format.read(doc));
+ t.xml_eq(out, doc.documentElement, "round-tripped polygon_simplepolygon.sld");
+
+ // polygon symbolizer and text symbolizer with halo
+ doc = readXML("polygon_labelhalo.sld");
+ out = format.write(format.read(doc));
+ t.xml_eq(out, doc.documentElement, "round-tripped polygon_labelhalo.sld");
+ }
+
+ </script>
+</head>
+<body>
+<div id="line_linewithborder.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Line with border</Name>
+ <UserStyle>
+ <Title>SLD Cook Book: Line w2th border</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <LineSymbolizer>
+ <Stroke>
+ <CssParameter name="stroke">#333333</CssParameter>
+ <CssParameter name="stroke-width">5</CssParameter>
+ <CssParameter name="stroke-linecap">round</CssParameter>
+ </Stroke>
+ </LineSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ <FeatureTypeStyle>
+ <Rule>
+ <LineSymbolizer>
+ <Stroke>
+ <CssParameter name="stroke">#6699FF</CssParameter>
+ <CssParameter name="stroke-width">3</CssParameter>
+ <CssParameter name="stroke-linecap">round</CssParameter>
+ </Stroke>
+ </LineSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="line_attributebasedline.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Attribute-based line</Name>
+ <UserStyle>
+ <Title>SLD Cook Book: Attribute-based line</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <Name>local-road</Name>
+ <ogc:Filter>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>type</ogc:PropertyName>
+ <ogc:Literal>local-road</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <LineSymbolizer>
+ <Stroke>
+ <CssParameter name="stroke">#009933</CssParameter>
+ <CssParameter name="stroke-width">2</CssParameter>
+ </Stroke>
+ </LineSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ <FeatureTypeStyle>
+ <Rule>
+ <Name>secondary</Name>
+ <ogc:Filter>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>type</ogc:PropertyName>
+ <ogc:Literal>secondary</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <LineSymbolizer>
+ <Stroke>
+ <CssParameter name="stroke">#0055CC</CssParameter>
+ <CssParameter name="stroke-width">3</CssParameter>
+ </Stroke>
+ </LineSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ <FeatureTypeStyle>
+ <Rule>
+ <Name>highway</Name>
+ <ogc:Filter>
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>type</ogc:PropertyName>
+ <ogc:Literal>highway</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <LineSymbolizer>
+ <Stroke>
+ <CssParameter name="stroke">#FF0000</CssParameter>
+ <CssParameter name="stroke-width">6</CssParameter>
+ </Stroke>
+ </LineSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="point_pointwithdefaultlabel.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Point with default label</Name>
+ <UserStyle>
+ <Title>GeoServer SLD Cook Book: Point with default label</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <PointSymbolizer>
+ <Graphic>
+ <Mark>
+ <WellKnownName>circle</WellKnownName>
+ <Fill>
+ <CssParameter name="fill">#FF0000</CssParameter>
+ </Fill>
+ </Mark>
+ <Size>6</Size>
+ </Graphic>
+ </PointSymbolizer>
+ <TextSymbolizer>
+ <Label>
+ <ogc:PropertyName>name</ogc:PropertyName>
+ </Label>
+ <Fill>
+ <CssParameter name="fill">#000000</CssParameter>
+ </Fill>
+ </TextSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="polygon_simplepolygon.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Simple polygon</Name>
+ <UserStyle>
+ <Title>SLD Cook Book: Simple polygon</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <PolygonSymbolizer>
+ <Fill>
+ <CssParameter name="fill">#000080</CssParameter>
+ </Fill>
+ </PolygonSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="polygon_labelhalo.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Label halo</Name>
+ <UserStyle>
+ <Title>SLD Cook Book: Label halo</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <PolygonSymbolizer>
+ <Fill>
+ <CssParameter name="fill">#40FF40</CssParameter>
+ </Fill>
+ <Stroke>
+ <CssParameter name="stroke">#FFFFFF</CssParameter>
+ <CssParameter name="stroke-width">2</CssParameter>
+ </Stroke>
+ </PolygonSymbolizer>
+ <TextSymbolizer>
+ <Label>
+ <ogc:PropertyName>name</ogc:PropertyName>
+ </Label>
+ <Halo>
+ <Radius>3</Radius>
+ <Fill>
+ <CssParameter name="fill">#FFFFFF</CssParameter>
+ </Fill>
+ </Halo>
+ </TextSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="zindex_test.sld"><!--
+<sld:UserStyle xmlns:sld="http://www.opengis.net/sld">
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>foo</ogc:PropertyName>
+ <ogc:Literal>bar</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:MinScaleDenominator>100000</sld:MinScaleDenominator>
+ <sld:MaxScaleDenominator>200000</sld:MaxScaleDenominator>
+ <sld:LineSymbolizer>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">red</sld:CssParameter>
+ <sld:CssParameter name="stroke-width">3</sld:CssParameter>
+ </sld:Stroke>
+ </sld:LineSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>foo</ogc:PropertyName>
+ <ogc:Literal>bar</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:MinScaleDenominator>100000</sld:MinScaleDenominator>
+ <sld:MaxScaleDenominator>200000</sld:MaxScaleDenominator>
+ <sld:LineSymbolizer>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">green</sld:CssParameter>
+ <sld:CssParameter name="stroke-width">2</sld:CssParameter>
+ </sld:Stroke>
+ </sld:LineSymbolizer>
+ </sld:Rule>
+ <sld:Rule>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>foo</ogc:PropertyName>
+ <ogc:Literal>baz</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:LineSymbolizer>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">#000000</sld:CssParameter>
+ <sld:CssParameter name="stroke-width">2</sld:CssParameter>
+ </sld:Stroke>
+ </sld:LineSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:PropertyIsEqualTo>
+ <ogc:PropertyName>foo</ogc:PropertyName>
+ <ogc:Literal>bar</ogc:Literal>
+ </ogc:PropertyIsEqualTo>
+ </ogc:Filter>
+ <sld:MinScaleDenominator>100000</sld:MinScaleDenominator>
+ <sld:MaxScaleDenominator>200000</sld:MaxScaleDenominator>
+ <sld:LineSymbolizer>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">blue</sld:CssParameter>
+ <sld:CssParameter name="stroke-width">1</sld:CssParameter>
+ </sld:Stroke>
+ </sld:LineSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+</sld:UserStyle>
+--></div>
+<div id="label_lineplacement_test.sld"><!--
+<sld:UserStyle xmlns:sld="http://www.opengis.net/sld">
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <sld:LineSymbolizer>
+ <sld:Stroke>
+ <sld:CssParameter name="stroke">red</sld:CssParameter>
+ <sld:CssParameter name="stroke-width">3</sld:CssParameter>
+ </sld:Stroke>
+ </sld:LineSymbolizer>
+ <sld:TextSymbolizer>
+ <sld:Label><ogc:PropertyName xmlns:ogc="http://www.opengis.net/ogc">FOO</ogc:PropertyName></sld:Label>
+ <sld:LabelPlacement>
+ <sld:LinePlacement>
+ <sld:PerpendicularOffset>10</sld:PerpendicularOffset>
+ </sld:LinePlacement>
+ </sld:LabelPlacement>
+ </sld:TextSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+</sld:UserStyle>
+--></div>
+<div id="label_pointplacement_test.sld"><!--
+<sld:UserStyle xmlns:sld="http://www.opengis.net/sld">
+ <sld:FeatureTypeStyle>
+ <sld:Rule>
+ <sld:TextSymbolizer>
+ <sld:Label><ogc:PropertyName xmlns:ogc="http://www.opengis.net/ogc">FOO</ogc:PropertyName></sld:Label>
+ <sld:LabelPlacement>
+ <sld:PointPlacement>
+ <sld:AnchorPoint>
+ <sld:AnchorPointX>1</sld:AnchorPointX>
+ <sld:AnchorPointY>0</sld:AnchorPointY>
+ </sld:AnchorPoint>
+ </sld:PointPlacement>
+ </sld:LabelPlacement>
+ </sld:TextSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+</sld:UserStyle>
+--></div>
+<div id="propertyisbetweenwhitespace.sld"><!--
+<sld:StyledLayerDescriptor xmlns="http://www.opengis.net/sld" xmlns:sld="http://www.opengis.net/sld" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" version="1.0.0">
+ <sld:NamedLayer>
+ <sld:Name>geonode:US_Stat0</sld:Name>
+ <sld:UserStyle>
+ <sld:Name>US_Stat0_5cbbe918</sld:Name>
+ <sld:Title>BMI&lt;25</sld:Title>
+ <sld:FeatureTypeStyle>
+ <sld:Name>name</sld:Name>
+ <sld:Rule>
+ <sld:Title>BMI&lt;25</sld:Title>
+ <ogc:Filter>
+ <ogc:PropertyIsBetween>
+ <ogc:PropertyName>Hlt_st_BMI</ogc:PropertyName>
+ <ogc:LowerBoundary>
+ <ogc:Literal>
+
+
+ 29.7
+
+
+ </ogc:Literal>
+ </ogc:LowerBoundary>
+ <ogc:UpperBoundary>
+ <ogc:Literal>
+
+
+ 36.2
+
+
+ </ogc:Literal>
+ </ogc:UpperBoundary>
+ </ogc:PropertyIsBetween>
+ </ogc:Filter>
+ <sld:PolygonSymbolizer>
+ <sld:Fill>
+ <sld:CssParameter name="fill">#C0F58C</sld:CssParameter>
+ </sld:Fill>
+ <sld:Stroke/>
+ </sld:PolygonSymbolizer>
+ </sld:Rule>
+ </sld:FeatureTypeStyle>
+ </sld:UserStyle>
+ </sld:NamedLayer>
+</sld:StyledLayerDescriptor>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/SLD/v1_0_0_GeoServer.html b/misc/openlayers/tests/Format/SLD/v1_0_0_GeoServer.html
new file mode 100644
index 0000000..96a3ef6
--- /dev/null
+++ b/misc/openlayers/tests/Format/SLD/v1_0_0_GeoServer.html
@@ -0,0 +1,228 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var xml = new OpenLayers.Format.XML();
+ function readXML(id) {
+ return xml.read(document.getElementById(id).firstChild.nodeValue);
+ }
+
+ function test_VendorExtensions(t) {
+
+ var cases = [
+ "poly_label.sld"
+ ];
+ var len = cases.length;
+ t.plan(len+1);
+
+ var format = new OpenLayers.Format.SLD({
+ profile: "GeoServer",
+ multipleSymbolizers: true,
+ namedLayersAsArray: true,
+ schemaLocation: "http://www.opengis.net/sld StyledLayerDescriptor.xsd"
+ });
+
+ var c, doc, data, out;
+ for (var i=0; i<len; ++i) {
+ c = cases[i];
+ doc = readXML(c);
+ data = format.read(doc);
+ out = format.write(data);
+ t.xml_eq(out, doc.documentElement, "round-tripped " + c);
+ }
+ doc = readXML("poly_label.sld");
+ data = format.read(doc);
+ data.namedLayers[0].userStyles[0].rules[0].symbolizers[1].graphic = false;
+ out = format.write(data);
+ t.xml_eq(out, readXML("poly_label_nographic.sld").documentElement, "If graphic is false no Graphic is outputted");
+ }
+
+ function test_readTextSymbolizer(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.SLD({
+ profile: "GeoServer",
+ multipleSymbolizers: true,
+ namedLayersAsArray: true
+ });
+ doc = readXML("point_pointwithdefaultlabel.sld");
+ var sld = format.read(doc);
+ t.eq(sld.namedLayers[0].userStyles[0].rules[0].symbolizers[1].graphic, false, "graphic set to false on TextSymbolizer");
+ }
+
+ </script>
+</head>
+<body>
+<div id="poly_label.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Polygon with styled label</Name>
+ <UserStyle>
+ <Title>SLD Cook Book: Polygon with styled label</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <PolygonSymbolizer>
+ <Fill>
+ <CssParameter name="fill">#40FF40</CssParameter>
+ </Fill>
+ <Stroke>
+ <CssParameter name="stroke">#FFFFFF</CssParameter>
+ <CssParameter name="stroke-width">2</CssParameter>
+ </Stroke>
+ </PolygonSymbolizer>
+ <TextSymbolizer>
+ <Label>
+ <ogc:PropertyName>name</ogc:PropertyName>
+ </Label>
+ <Font>
+ <CssParameter name="font-family">Arial</CssParameter>
+ <CssParameter name="font-size">11</CssParameter>
+ <CssParameter name="font-weight">bold</CssParameter>
+ <CssParameter name="font-style">normal</CssParameter>
+ </Font>
+ <Fill>
+ <CssParameter name="fill">#000000</CssParameter>
+ <CssParameter name="fill-opacity">0.5</CssParameter>
+ </Fill>
+ <Graphic>
+ <Mark>
+ <WellKnownName>square</WellKnownName>
+ <Fill>
+ <CssParameter name="fill">#59BF34</CssParameter>
+ <CssParameter name="fill-opacity">0.8</CssParameter>
+ </Fill>
+ <Stroke>
+ <CssParameter name="stroke">#2D6917</CssParameter>
+ </Stroke>
+ </Mark>
+ <Size>24</Size>
+ </Graphic>
+ <Priority>
+ <ogc:PropertyName>population</ogc:PropertyName>
+ </Priority>
+ <VendorOption name="autoWrap">60</VendorOption>
+ <VendorOption name="followLine">true</VendorOption>
+ <VendorOption name="repeat">300</VendorOption>
+ <VendorOption name="maxDisplacement">150</VendorOption>
+ <VendorOption name="forceLeftToRight">false</VendorOption>
+ <VendorOption name="graphic-margin">3</VendorOption>
+ <VendorOption name="graphic-resize">stretch</VendorOption>
+ <VendorOption name="group">yes</VendorOption>
+ <VendorOption name="spaceAround">10</VendorOption>
+ <VendorOption name="labelAllGroup">true</VendorOption>
+ <VendorOption name="maxAngleDelta">15</VendorOption>
+ <VendorOption name="conflictResolution">false</VendorOption>
+ <VendorOption name="goodnessOfFit">0.3</VendorOption>
+ <VendorOption name="polygonAlign">mbr</VendorOption>
+ </TextSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="poly_label_nographic.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Polygon with styled label</Name>
+ <UserStyle>
+ <Title>SLD Cook Book: Polygon with styled label</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <PolygonSymbolizer>
+ <Fill>
+ <CssParameter name="fill">#40FF40</CssParameter>
+ </Fill>
+ <Stroke>
+ <CssParameter name="stroke">#FFFFFF</CssParameter>
+ <CssParameter name="stroke-width">2</CssParameter>
+ </Stroke>
+ </PolygonSymbolizer>
+ <TextSymbolizer>
+ <Label>
+ <ogc:PropertyName>name</ogc:PropertyName>
+ </Label>
+ <Font>
+ <CssParameter name="font-family">Arial</CssParameter>
+ <CssParameter name="font-size">11</CssParameter>
+ <CssParameter name="font-weight">bold</CssParameter>
+ <CssParameter name="font-style">normal</CssParameter>
+ </Font>
+ <Fill>
+ <CssParameter name="fill">#000000</CssParameter>
+ <CssParameter name="fill-opacity">0.5</CssParameter>
+ </Fill>
+ <Priority>
+ <ogc:PropertyName>population</ogc:PropertyName>
+ </Priority>
+ <VendorOption name="autoWrap">60</VendorOption>
+ <VendorOption name="followLine">true</VendorOption>
+ <VendorOption name="repeat">300</VendorOption>
+ <VendorOption name="maxDisplacement">150</VendorOption>
+ <VendorOption name="forceLeftToRight">false</VendorOption>
+ <VendorOption name="graphic-margin">3</VendorOption>
+ <VendorOption name="graphic-resize">stretch</VendorOption>
+ <VendorOption name="group">yes</VendorOption>
+ <VendorOption name="spaceAround">10</VendorOption>
+ <VendorOption name="labelAllGroup">true</VendorOption>
+ <VendorOption name="maxAngleDelta">15</VendorOption>
+ <VendorOption name="conflictResolution">false</VendorOption>
+ <VendorOption name="goodnessOfFit">0.3</VendorOption>
+ <VendorOption name="polygonAlign">mbr</VendorOption>
+ </TextSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+<div id="point_pointwithdefaultlabel.sld"><!--
+<StyledLayerDescriptor version="1.0.0"
+ xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd"
+ xmlns="http://www.opengis.net/sld"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <NamedLayer>
+ <Name>Point with default label</Name>
+ <UserStyle>
+ <Title>GeoServer SLD Cook Book: Point with default label</Title>
+ <FeatureTypeStyle>
+ <Rule>
+ <PointSymbolizer>
+ <Graphic>
+ <Mark>
+ <WellKnownName>circle</WellKnownName>
+ <Fill>
+ <CssParameter name="fill">#FF0000</CssParameter>
+ </Fill>
+ </Mark>
+ <Size>6</Size>
+ </Graphic>
+ </PointSymbolizer>
+ <TextSymbolizer>
+ <Label>
+ <ogc:PropertyName>name</ogc:PropertyName>
+ </Label>
+ <Fill>
+ <CssParameter name="fill">#000000</CssParameter>
+ </Fill>
+ </TextSymbolizer>
+ </Rule>
+ </FeatureTypeStyle>
+ </UserStyle>
+ </NamedLayer>
+</StyledLayerDescriptor>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.html b/misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.html
new file mode 100644
index 0000000..6713685
--- /dev/null
+++ b/misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.html
@@ -0,0 +1,80 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="v1_0_0.js"></script>
+ <script type="text/javascript">
+
+ function test_read(t) {
+
+ t.plan(41);
+
+ var format = new OpenLayers.Format.SOSCapabilities();
+ var obj = format.read(doc);
+
+ t.eq(obj.version, "1.0.0", "Version parsed correctly");
+
+ // service identification (from OWSCommon)
+ t.eq(obj.serviceIdentification.abstract, "WeatherSOS (stable) at IfGI, Muenster, Germany. For more info: http://ifgipedia.uni-muenster.de/kms/documentation/swsl/sos/", "Abstract parsed correctly");
+ t.eq(obj.serviceIdentification.accessConstraints, "NONE", "AccessConstraints parsed correctly");
+ t.eq(obj.serviceIdentification.fees, "NONE", "Fees parsed correctly");
+ for (var key in obj.serviceIdentification.keywords) {
+ t.eq(key, "rain gauge, radiation, pressure, windspeed, winddirection, temperature", "Keywords parsed correctly");
+ }
+ t.eq(obj.serviceIdentification.serviceType.codeSpace, "http://opengeospatial.net", "codeSpace correctly parsed");
+ t.eq(obj.serviceIdentification.serviceType.value, "OGC:SOS", "ServiceType correctly parsed");
+ t.eq(obj.serviceIdentification.serviceTypeVersion, "1.0.0", "ServiceTypeVersion correctly parsed");
+ t.eq(obj.serviceIdentification.title, "IFGI WeatherSOS (stable)", "Title correctly parsed");
+
+ // service provider (from OWSCommon)
+ t.eq(obj.serviceProvider.providerName, "Institute for Geoinformatics, University of Muenster", "ProviderName correctly parsed");
+ t.eq(obj.serviceProvider.providerSite, "http://ifgi.uni-muenster.de", "ProviderSite correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.individualName, "Eike Hinderk Juerrens", "IndividualName parsed correctly");
+ t.eq(obj.serviceProvider.serviceContact.positionName, "Student Associate", "PositionName parsed correctly");
+ t.eq(obj.serviceProvider.serviceContact.role, "", "Role parsed correctly");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.address.administrativeArea, "NRW", "AdministrativeArea correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.address.city, "Muenster", "City correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.address.country, "Germany", "Country correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.address.deliveryPoint, "Weselerstrasse 253", "DeliveryPoint correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.address.electronicMailAddress, "ehjuerrens@uni-muenster.de", "ElectronicMailAddress correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.address.postalCode, "48149", "Postalcode correctly parsed");
+ t.eq(obj.serviceProvider.serviceContact.contactInfo.phone.voice, "+49-251-83-30088", "Voice phone correctly parsed");
+
+ // operationsMetadata (from OWSCommon)
+ t.eq(obj.operationsMetadata.DescribeSensor.dcp.http.post[0].url, "http://v-swe.uni-muenster.de:8080/WeatherSOS/sos", "POST url for DescribeSensor correctly parsed");
+ var counter = 0;
+ for (var key in obj.operationsMetadata.DescribeSensor.parameters.procedure.allowedValues) {
+ if (counter == 0) {
+ t.eq(key, "urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111", "Allowed value (1) for procedure parameter in DescribeSensor request correctly parsed");
+ } else if (counter == 1) {
+ t.eq(key, "urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93", "Allowed value (2) for procedure parameter in DescribeSensor request correctly parsed");
+ }
+ counter++;
+ }
+ t.eq(obj.operationsMetadata.GetFeatureOfInterest.parameters.location.anyValue, true, "AnyValue parsed correctly");
+
+ t.eq(obj.operationsMetadata.GetObservation.parameters.eventTime.allowedValues.range.maxValue, "2009-11-04T14:45:00+01", "Range maxValue parsed correctly");
+ t.eq(obj.operationsMetadata.GetObservation.parameters.eventTime.allowedValues.range.minValue, "2008-02-14T11:03:02+01", "Range minValue parsed correctly");
+
+ // Contents (from SOS)
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.name, "Pressure of the atmosphere", "Name of offering correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.observedProperties[0], "urn:x-ogc:def:property:OGC::BarometricPressure", "ObservedProperty correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.featureOfInterestIds[0], "urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93", "Allowed value (1) for featureOfInterest correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.featureOfInterestIds[1], "urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111", "Allowed value (2) for featureOfInterest correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.procedures[0], "urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93", "Allowed value (1) for procedures correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.procedures[1], "urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111", "Allowed value (2) for procedures correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.responseFormats[0], 'text/xml;subtype="om/1.0.0"', "Allowed value (1) for responseFormats correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.responseFormats[1], "application/zip", "Allowed value (2) for responseFormats correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.responseModes[0], "inline", "Allowed value (1) for responseModes correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.responseModes[1], "resultTemplate", "Allowed value (2) for responseModes correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.resultModels[0], "ns:Measurement", "Allowed value (1) for resultModels correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.resultModels[1], "ns:Observation", "Allowed value (2) for resultModels correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.time.timePeriod.beginPosition, "2008-12-20T02:29:27+01:00", "TimePeriod beginPosition correctly parsed");
+ t.eq(obj.contents.offeringList.ATMOSPHERIC_PRESSURE.time.timePeriod.endPosition, "2009-11-04T14:45:00+01:00", "TimePeriod endPosition correctly parsed");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.js b/misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..78556f5
--- /dev/null
+++ b/misc/openlayers/tests/Format/SOSCapabilities/v1_0_0.js
@@ -0,0 +1,484 @@
+var doc = new OpenLayers.Format.XML().read(
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<sos:Capabilities version="1.0.0" updateSequence="2005-12-14T10:12:39+01" xsi:schemaLocation="http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd" xmlns:sos="http://www.opengis.net/sos/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">' +
+ '<ows:ServiceIdentification xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:om="http://www.opengis.net/om/1.0" xmlns:swe="http://www.opengis.net/swe/1.0">' +
+ '<ows:Title>IFGI WeatherSOS (stable)</ows:Title>' +
+ '<ows:Abstract>WeatherSOS (stable) at IfGI, Muenster, Germany. For more info: http://ifgipedia.uni-muenster.de/kms/documentation/swsl/sos/</ows:Abstract>' +
+ '<ows:Keywords>' +
+ '<ows:Keyword>rain gauge, radiation, pressure, windspeed, winddirection, temperature</ows:Keyword>' +
+ '</ows:Keywords>' +
+ '<ows:ServiceType codeSpace="http://opengeospatial.net">OGC:SOS</ows:ServiceType>' +
+ '<ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>' +
+ '<ows:Fees>NONE</ows:Fees>' +
+ '<ows:AccessConstraints>NONE</ows:AccessConstraints>' +
+ '</ows:ServiceIdentification>' +
+ '<ows:ServiceProvider xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:om="http://www.opengis.net/om/1.0" xmlns:swe="http://www.opengis.net/swe/1.0">' +
+ '<ows:ProviderName>Institute for Geoinformatics, University of Muenster</ows:ProviderName>' +
+ '<ows:ProviderSite xlink:href="http://ifgi.uni-muenster.de"/>' +
+ '<ows:ServiceContact>' +
+ '<ows:IndividualName>Eike Hinderk Juerrens</ows:IndividualName>' +
+ '<ows:PositionName>Student Associate</ows:PositionName>' +
+ '<ows:ContactInfo>' +
+ '<ows:Phone>' +
+ '<ows:Voice>+49-251-83-30088</ows:Voice>' +
+ '</ows:Phone>' +
+ '<ows:Address>' +
+ '<ows:DeliveryPoint>Weselerstrasse 253</ows:DeliveryPoint>' +
+ '<ows:City>Muenster</ows:City>' +
+ '<ows:AdministrativeArea>NRW</ows:AdministrativeArea>' +
+ '<ows:PostalCode>48149</ows:PostalCode>' +
+ '<ows:Country>Germany</ows:Country>' +
+ '<ows:ElectronicMailAddress>ehjuerrens@uni-muenster.de</ows:ElectronicMailAddress>' +
+ '</ows:Address>' +
+ '</ows:ContactInfo>' +
+ '<ows:Role/>' +
+ '</ows:ServiceContact>' +
+ '</ows:ServiceProvider>' +
+ '<ows:OperationsMetadata xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:om="http://www.opengis.net/om/1.0" xmlns:swe="http://www.opengis.net/swe/1.0">' +
+ '<ows:Operation name="GetCapabilities">' +
+ '<ows:DCP>' +
+ '<ows:HTTP>' +
+ '<ows:Get xlink:href="http://v-swe.uni-muenster.de:8080/WeatherSOS/sos?"/>' +
+ '<ows:Post xlink:href="http://v-swe.uni-muenster.de:8080/WeatherSOS/sos"/>' +
+ '</ows:HTTP>' +
+ '</ows:DCP>' +
+ '<ows:Parameter name="service">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>SOS</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="updateSequence">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="AcceptVersions">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>1.0.0</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="Sections">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>ServiceIdentification</ows:Value>' +
+ '<ows:Value>ServiceProvider</ows:Value>' +
+ '<ows:Value>OperationsMetadata</ows:Value>' +
+ '<ows:Value>Contents</ows:Value>' +
+ '<ows:Value>All</ows:Value>' +
+ '<ows:Value>Filter_Capabilities</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="AcceptFormats">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>text/xml</ows:Value>' +
+ '<ows:Value>application/zip</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '</ows:Operation>' +
+ '<ows:Operation name="GetObservation">' +
+ '<ows:DCP>' +
+ '<ows:HTTP>' +
+ '<ows:Post xlink:href="http://v-swe.uni-muenster.de:8080/WeatherSOS/sos"/>' +
+ '</ows:HTTP>' +
+ '</ows:DCP>' +
+ '<ows:Parameter name="version">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>1.0.0</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="service">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>SOS</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="srsName">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="offering">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>ATMOSPHERIC_TEMPERATURE</ows:Value>' +
+ '<ows:Value>RAIN_GAUGE</ows:Value>' +
+ '<ows:Value>WIND_DIRECTION</ows:Value>' +
+ '<ows:Value>WIND_SPEED</ows:Value>' +
+ '<ows:Value>HUMIDITY</ows:Value>' +
+ '<ows:Value>LUMINANCE</ows:Value>' +
+ '<ows:Value>ATMOSPHERIC_PRESSURE</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="eventTime">' +
+ '<ows:AllowedValues>' +
+ '<ows:Range>' +
+ '<ows:MinimumValue>2008-02-14T11:03:02+01</ows:MinimumValue>' +
+ '<ows:MaximumValue>2009-11-04T14:45:00+01</ows:MaximumValue>' +
+ '</ows:Range>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="procedure">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111</ows:Value>' +
+ '<ows:Value>urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="observedProperty">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::Temperature</ows:Value>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::Precipitation1Hour</ows:Value>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::WindDirection</ows:Value>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::WindSpeed</ows:Value>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::RelativeHumidity</ows:Value>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::Luminance</ows:Value>' +
+ '<ows:Value>urn:x-ogc:def:property:OGC::BarometricPressure</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="featureOfInterest">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="result">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="responseFormat">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>text/xml;subtype="OM/1.0.0"</ows:Value>' +
+ '<ows:Value>application/zip</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="resultModel">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>om:Observation</ows:Value>' +
+ '<ows:Value>om:CategoryObservation</ows:Value>' +
+ '<ows:Value>om:Measurement</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="responseMode">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>resultTemplate</ows:Value>' +
+ '<ows:Value>inline</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '</ows:Operation>' +
+ '<ows:Operation name="GetObservationById">' +
+ '<ows:DCP>' +
+ '<ows:HTTP>' +
+ '<ows:Post xlink:href="http://v-swe.uni-muenster.de:8080/WeatherSOS/sos"/>' +
+ '</ows:HTTP>' +
+ '</ows:DCP>' +
+ '<ows:Parameter name="version">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>1.0.0</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="service">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>SOS</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="srsName">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="ObservationId">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="responseFormat">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>text/xml;subtype="OM/1.0.0"</ows:Value>' +
+ '<ows:Value>application/zip</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="resultModel">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>om:Observation</ows:Value>' +
+ '<ows:Value>om:CategoryObservation</ows:Value>' +
+ '<ows:Value>om:Measurement</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="responseMode">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>inline</ows:Value>' +
+ '<ows:Value>resultTemplate</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '</ows:Operation>' +
+ '<ows:Operation name="DescribeSensor">' +
+ '<ows:DCP>' +
+ '<ows:HTTP>' +
+ '<ows:Post xlink:href="http://v-swe.uni-muenster.de:8080/WeatherSOS/sos"/>' +
+ '</ows:HTTP>' +
+ '</ows:DCP>' +
+ '<ows:Parameter name="version">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>1.0.0</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="service">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>SOS</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="outputFormat">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>text/xml;subtype="sensorML/1.0.1"</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="procedure">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111</ows:Value>' +
+ '<ows:Value>urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '</ows:Operation>' +
+ '<ows:Operation name="GetFeatureOfInterest">' +
+ '<ows:DCP>' +
+ '<ows:HTTP>' +
+ '<ows:Post xlink:href="http://v-swe.uni-muenster.de:8080/WeatherSOS/sos"/>' +
+ '</ows:HTTP>' +
+ '</ows:DCP>' +
+ '<ows:Parameter name="service">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>SOS</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="version">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>1.0.0</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="featureOfInterestId">' +
+ '<ows:AllowedValues>' +
+ '<ows:Value>urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93</ows:Value>' +
+ '<ows:Value>urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111</ows:Value>' +
+ '</ows:AllowedValues>' +
+ '</ows:Parameter>' +
+ '<ows:Parameter name="location">' +
+ '<ows:AnyValue/>' +
+ '</ows:Parameter>' +
+ '</ows:Operation>' +
+ '</ows:OperationsMetadata>' +
+ '<sos:Filter_Capabilities xmlns:ogc="http://www.opengis.net/ogc" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:om="http://www.opengis.net/om/1.0" xmlns:swe="http://www.opengis.net/swe/1.0">' +
+ '<ogc:Spatial_Capabilities>' +
+ '<ogc:GeometryOperands>' +
+ '<ogc:GeometryOperand>gml:Envelope</ogc:GeometryOperand>' +
+ '<ogc:GeometryOperand>gml:Polygon</ogc:GeometryOperand>' +
+ '<ogc:GeometryOperand>gml:Point</ogc:GeometryOperand>' +
+ '<ogc:GeometryOperand>gml:LineString</ogc:GeometryOperand>' +
+ '</ogc:GeometryOperands>' +
+ '<ogc:SpatialOperators>' +
+ '<ogc:SpatialOperator name="BBOX"/>' +
+ '<ogc:SpatialOperator name="Contains"/>' +
+ '<ogc:SpatialOperator name="Intersects"/>' +
+ '<ogc:SpatialOperator name="Overlaps"/>' +
+ '</ogc:SpatialOperators>' +
+ '</ogc:Spatial_Capabilities>' +
+ '<ogc:Temporal_Capabilities>' +
+ '<ogc:TemporalOperands>' +
+ '<ogc:TemporalOperand>gml:TimeInstant</ogc:TemporalOperand>' +
+ '<ogc:TemporalOperand>gml:TimePeriod</ogc:TemporalOperand>' +
+ '</ogc:TemporalOperands>' +
+ '<ogc:TemporalOperators>' +
+ '<ogc:TemporalOperator name="TM_During"/>' +
+ '<ogc:TemporalOperator name="TM_Equals"/>' +
+ '<ogc:TemporalOperator name="TM_After"/>' +
+ '<ogc:TemporalOperator name="TM_Before"/>' +
+ '</ogc:TemporalOperators>' +
+ '</ogc:Temporal_Capabilities>' +
+ '<ogc:Scalar_Capabilities>' +
+ '<ogc:ComparisonOperators>' +
+ '<ogc:ComparisonOperator>Between</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>EqualTo</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>NotEqualTo</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>LessThan</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>LessThanEqualTo</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>GreaterThan</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>GreaterThanEqualTo</ogc:ComparisonOperator>' +
+ '<ogc:ComparisonOperator>Like</ogc:ComparisonOperator>' +
+ '</ogc:ComparisonOperators>' +
+ '</ogc:Scalar_Capabilities>' +
+ '<ogc:Id_Capabilities>' +
+ '<ogc:FID/>' +
+ '<ogc:EID/>' +
+ '</ogc:Id_Capabilities>' +
+ '</sos:Filter_Capabilities>' +
+ '<sos:Contents>' +
+ '<sos:ObservationOfferingList>' +
+ '<sos:ObservationOffering gml:id="ATMOSPHERIC_TEMPERATURE">' +
+ '<gml:name>Temperature of the atmosphere</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-11-20T15:20:22+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::Temperature"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '<sos:ObservationOffering gml:id="RAIN_GAUGE">' +
+ '<gml:name>Rain</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-11-20T15:35:22+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::Precipitation1Hour"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '<sos:ObservationOffering gml:id="WIND_DIRECTION">' +
+ '<gml:name>Direction of the wind</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-11-20T15:20:22+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::WindDirection"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '<sos:ObservationOffering gml:id="WIND_SPEED">' +
+ '<gml:name>Speed of the wind</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-11-20T15:20:22+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::WindSpeed"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '<sos:ObservationOffering gml:id="HUMIDITY">' +
+ '<gml:name>Humidity of the atmosphere</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-02-14T11:03:02+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::RelativeHumidity"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '<sos:ObservationOffering gml:id="LUMINANCE">' +
+ '<gml:name>Luminance</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-11-20T15:20:22+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::Luminance"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '<sos:ObservationOffering gml:id="ATMOSPHERIC_PRESSURE">' +
+ '<gml:name>Pressure of the atmosphere</gml:name>' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>' +
+ '<gml:upperCorner>51.9412 13.883498</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<sos:time>' +
+ '<gml:TimePeriod xsi:type="gml:TimePeriodType">' +
+ '<gml:beginPosition>2008-12-20T02:29:27+01:00</gml:beginPosition>' +
+ '<gml:endPosition>2009-11-04T14:45:00+01:00</gml:endPosition>' +
+ '</gml:TimePeriod>' +
+ '</sos:time>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:observedProperty xlink:href="urn:x-ogc:def:property:OGC::BarometricPressure"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93"/>' +
+ '<sos:featureOfInterest xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>' +
+ '<sos:responseFormat>text/xml;subtype="om/1.0.0"</sos:responseFormat>' +
+ '<sos:responseFormat>application/zip</sos:responseFormat>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Measurement</sos:resultModel>' +
+ '<sos:resultModel xmlns:ns="http://www.opengis.net/om/1.0">ns:Observation</sos:resultModel>' +
+ '<sos:responseMode>inline</sos:responseMode>' +
+ '<sos:responseMode>resultTemplate</sos:responseMode>' +
+ '</sos:ObservationOffering>' +
+ '</sos:ObservationOfferingList>' +
+ '</sos:Contents>' +
+'</sos:Capabilities>'
+); \ No newline at end of file
diff --git a/misc/openlayers/tests/Format/SOSGetFeatureOfInterest.html b/misc/openlayers/tests/Format/SOSGetFeatureOfInterest.html
new file mode 100644
index 0000000..c80078f
--- /dev/null
+++ b/misc/openlayers/tests/Format/SOSGetFeatureOfInterest.html
@@ -0,0 +1,80 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_SOSGetFeatureOfInterest_single(t) {
+ t.plan(6);
+
+ var parser = new OpenLayers.Format.SOSGetFeatureOfInterest();
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<sa:SamplingPoint xmlns:sa="http://www.opengis.net/sampling/1.0" xmlns:gml="http://www.opengis.net/gml" gml:id="urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56">' +
+ '<gml:name>Roof of the IfGI</gml:name>' +
+ '<sa:position>' +
+ '<gml:Point>' +
+ '<gml:pos srsName="urn:ogc:def:crs:EPSG:4326">52.1524 5.3722</gml:pos>' +
+ '</gml:Point>' +
+ '</sa:position>' +
+ '</sa:SamplingPoint>';
+
+ var res = parser.read(text);
+ t.eq(res.length, 1, "One feature parsed from response");
+ t.eq(res[0].attributes.id, "urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56", "gml:id correctly parsed");
+ t.eq(res[0].attributes.name, "Roof of the IfGI", "gml:name correctly parsed");
+ t.eq(res[0].geometry instanceof OpenLayers.Geometry.Point, true, "Geometry is a point geometry");
+ t.eq(res[0].geometry.x, 5.3722, "Geometry x coordinate correctly parsed");
+ t.eq(res[0].geometry.y, 52.1524, "Geometry y coordinate correctly parsed");
+ }
+
+ function test_read_SOSGetFeatureOfInterest_multiple(t) {
+ t.plan(6);
+
+ var parser = new OpenLayers.Format.SOSGetFeatureOfInterest();
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<gml:FeatureCollection xmlns:gml="http://www.opengis.net/gml" xmlns:sa="http://www.opengis.net/sampling/1.0">' +
+ '<gml:featureMember>' +
+ '<sa:SamplingPoint gml:id="urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93">' +
+ '<gml:name>weather @ roof of the ifgi, MS, Germany</gml:name>' +
+ '<sa:position>' +
+ '<gml:Point>' +
+ '<gml:pos srsName="urn:ogc:def:crs:EPSG:4326">51.9412 7.6103</gml:pos>' +
+ '</gml:Point>' +
+ '</sa:position>' +
+ '</sa:SamplingPoint>' +
+ '</gml:featureMember>' +
+ '<gml:featureMember>' +
+ '<sa:SamplingPoint gml:id="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111">' +
+ '<gml:name>waether @ roof of the FH Kaernten, Villach, Austria</gml:name>' +
+ '<sa:position>' +
+ '<gml:Point>' +
+ '<gml:pos srsName="urn:ogc:def:crs:EPSG:4326">46.611644 13.883498</gml:pos>' +
+ '</gml:Point>' +
+ '</sa:position>' +
+ '</sa:SamplingPoint>' +
+ '</gml:featureMember>' +
+ '</gml:FeatureCollection>';
+
+ var res = parser.read(text);
+ t.eq(res.length, 2, "Two features parsed from response");
+ t.eq(res[0].attributes.id, "urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93", "gml:id correctly parsed");
+ t.eq(res[1].attributes.name, "waether @ roof of the FH Kaernten, Villach, Austria", "gml:name correctly parsed");
+ t.eq(res[1].geometry instanceof OpenLayers.Geometry.Point, true, "Geometry is a point geometry");
+ t.eq(res[1].geometry.x, 13.883498, "Geometry x coordinate correctly parsed");
+ t.eq(res[1].geometry.y, 46.611644, "Geometry y coordinate correctly parsed");
+ }
+
+ function test_write_SOSGetFeatureOfInterest(t) {
+ t.plan(1);
+ var expect = '<GetFeatureOfInterest xmlns="http://www.opengis.net/sos/1.0" version="1.0.0" service="SOS" xsi:schemaLocation="http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><FeatureOfInterestId>urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93</FeatureOfInterestId><FeatureOfInterestId>urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111</FeatureOfInterestId></GetFeatureOfInterest>';
+ var format = new OpenLayers.Format.SOSGetFeatureOfInterest();
+ var output = format.writeNode("sos:GetFeatureOfInterest", {fois: ['urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93', 'urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111']});
+ t.xml_eq(output, expect, "Request XML is written out correctly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/SOSGetObservation.html b/misc/openlayers/tests/Format/SOSGetObservation.html
new file mode 100644
index 0000000..3256d5a
--- /dev/null
+++ b/misc/openlayers/tests/Format/SOSGetObservation.html
@@ -0,0 +1,183 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_SOSGetObservation(t) {
+ t.plan(13);
+
+ var parser = new OpenLayers.Format.SOSGetObservation();
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<om:ObservationCollection xmlns:om="http://www.opengis.net/om/1.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sa="http://www.opengis.net/sampling/1.0" gml:id="oc_0" xsi:schemaLocation="http://www.opengis.net/om/1.0 http://schemas.opengis.net/om/1.0.0/om.xsd http://www.opengis.net/sampling/1.0 http://schemas.opengis.net/sampling/1.0.0/sampling.xsd">' +
+ '<gml:boundedBy>' +
+ '<gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">' +
+ '<gml:lowerCorner>52.1524 5.3722</gml:lowerCorner>' +
+ '<gml:upperCorner>52.1524 5.3722</gml:upperCorner>' +
+ '</gml:Envelope>' +
+ '</gml:boundedBy>' +
+ '<om:member>' +
+ '<om:Measurement gml:id="o_51082">' +
+ '<om:samplingTime>' +
+ '<gml:TimeInstant xsi:type="gml:TimeInstantType">' +
+ '<gml:timePosition>2009-12-02T10:35:00.000+01:00</gml:timePosition>' +
+ '</gml:TimeInstant>' +
+ '</om:samplingTime>' +
+ '<om:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56"/>' +
+ '<om:observedProperty xlink:href="urn:x-ogc:def:property:OGC::Temperature"/>' +
+ '<om:featureOfInterest>' +
+ '<sa:SamplingPoint gml:id="urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56">' +
+ '<gml:name>Roof of the IfGI</gml:name>' +
+ '<sa:position>' +
+ '<gml:Point>' +
+ '<gml:pos srsName="urn:ogc:def:crs:EPSG:4326">52.1524 5.3722</gml:pos>' +
+ '</gml:Point>' +
+ '</sa:position>' +
+ '</sa:SamplingPoint>' +
+ '</om:featureOfInterest>' +
+ '<om:result uom="Cel">4.9</om:result>' +
+ '</om:Measurement>' +
+ '</om:member>' +
+ '</om:ObservationCollection>';
+
+ var res = parser.read(text);
+ t.eq(res.measurements.length, 1, "One measurement parsed");
+ t.eq(res.id, "oc_0", "Observation collection id correctly parsed");
+ var measurement = res.measurements[0];
+ t.eq(measurement.observedProperty, "urn:x-ogc:def:property:OGC::Temperature", "Observed property correctly parsed");
+ t.eq(measurement.procedure, "urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56", "Procedure correctly parsed");
+ t.eq(measurement.result.uom, "Cel", "Units of measurement correctly parsed");
+ t.eq(measurement.result.value, "4.9", "Value correctly parsed");
+ t.eq(measurement.samplingTime.timeInstant.timePosition, "2009-12-02T10:35:00.000+01:00", "Sampling time correctly parsed");
+
+ var response = [];
+ response.push('<?xml version="1.0" encoding="UTF-8"?>',
+'<om:ObservationCollection gml:id="oc_0" xsi:schemaLocation="http://www.opengis.net/om/1.0 http://schemas.opengis.net/om/1.0.0/om.xsd http://www.opengis.net/sampling/1.0 http://schemas.opengis.net/sampling/1.0.0/sampling.xsd" xmlns:om="http://www.opengis.net/om/1.0" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:swe="http://www.opengis.net/swe/1.0.1" xmlns:sa="http://www.opengis.net/sampling/1.0">',
+' <gml:boundedBy>',
+' <gml:Envelope srsName="urn:ogc:def:crs:EPSG:4326">',
+' <gml:lowerCorner>46.611644 7.6103</gml:lowerCorner>',
+' <gml:upperCorner>51.9412 13.883498</gml:upperCorner>',
+' </gml:Envelope>',
+' </gml:boundedBy>',
+' <om:member>',
+' <om:Observation gml:id="ot_583227">',
+' <om:samplingTime>',
+' <gml:TimePeriod xsi:type="gml:TimePeriodType">',
+' <gml:beginPosition>2009-09-28T13:45:00.000+02:00</gml:beginPosition>',
+' <gml:endPosition>2009-09-28T13:45:00.000+02:00</gml:endPosition>',
+' </gml:TimePeriod>',
+' </om:samplingTime>',
+' <om:procedure xlink:href="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111"/>',
+' <om:observedProperty>',
+' <swe:CompositePhenomenon gml:id="cpid0" dimension="1">',
+' <gml:name>resultComponents</gml:name>',
+' <swe:component xlink:href="urn:ogc:data:time:iso8601"/>',
+' <swe:component xlink:href="urn:ogc:def:property:OGC::Precipitation1Hour"/>',
+' </swe:CompositePhenomenon>',
+' </om:observedProperty>',
+' <om:featureOfInterest>',
+' <gml:FeatureCollection>',
+' <gml:featureMember>',
+' <sa:SamplingPoint gml:id="urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111" xsi:schemaLocation=" http://www.opengis.net/sampling/1.0 http://schemas.opengis.net/sampling/1.0.0/sampling.xsd">',
+' <gml:name>waether @ roof of the FH Kaernten, Villach, Austria</gml:name>',
+' <sa:sampledFeature xlink:href="urn:ogc:def:nil:OGC:unknown"/>',
+' <sa:position>',
+' <gml:Point>',
+' <gml:pos srsName="urn:ogc:def:crs:EPSG:4326">46.611644 13.883498</gml:pos>',
+' </gml:Point>',
+' </sa:position>',
+' </sa:SamplingPoint>',
+' </gml:featureMember>',
+' </gml:FeatureCollection>',
+' </om:featureOfInterest>',
+' <om:result>',
+' <swe:DataArray>',
+' <swe:elementCount>',
+' <swe:Count>',
+' <swe:value>1</swe:value>',
+' </swe:Count>',
+' </swe:elementCount>',
+' <swe:elementType name="Components">',
+' <swe:DataRecord>',
+' <swe:field name="Time">',
+' <swe:Time definition="urn:ogc:data:time:iso8601"/>',
+' </swe:field>',
+' <swe:field name="feature">',
+' <swe:Text definition="urn:ogc:data:feature"/>',
+' </swe:field>',
+' <swe:field name="urn:ogc:def:property:OGC::Precipitation1Hour">',
+' <swe:Quantity definition="urn:ogc:def:property:OGC::Precipitation1Hour">',
+' <swe:uom code="mm"/>',
+' </swe:Quantity>',
+' </swe:field>',
+' </swe:DataRecord>',
+' </swe:elementType>',
+' <swe:encoding>',
+' <swe:TextBlock decimalSeparator="." tokenSeparator="," blockSeparator=";"/>',
+' </swe:encoding>',
+' <swe:values>2009-09-28T13:45:00.000+02:00,urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111,0.0;</swe:values>',
+' </swe:DataArray>',
+' </om:result>',
+' </om:Observation>',
+' </om:member>',
+'</om:ObservationCollection>');
+ text = response.join("");
+ var res = parser.read(text);
+ t.eq(res.observations.length, 1, "1 observation parsed");
+ var observation = res.observations[0];
+ t.eq(observation.procedure, "urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111", "procedure parsed correctly");
+ t.eq(observation.fois.length, 1, "One foi parsed for the observation");
+ var foi = observation.fois[0];
+ var feature = foi.features[0];
+ t.eq(feature.attributes.id, "urn:ogc:object:feature:OSIRIS-HWS:efeb807b-bd24-4128-a920-f6729bcdd111", "Foi id correctly parsed");
+ t.eq(feature.attributes.name, "waether @ roof of the FH Kaernten, Villach, Austria", "Foi name correctly parsed");
+ t.ok(feature.geometry instanceof OpenLayers.Geometry.Point, "Geometry correctly parsed");
+ }
+
+ function test_write_SOSGetObservation(t) {
+ t.plan(2);
+ var expect = '<GetObservation xmlns="http://www.opengis.net/sos/1.0" version="1.0.0" service="SOS" xsi:schemaLocation="http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><offering>TEMPERATURE</offering><eventTime><ogc:TM_Equals xmlns:ogc="http://www.opengis.net/ogc"><ogc:PropertyName>urn:ogc:data:time:iso8601</ogc:PropertyName><gml:TimeInstant xmlns:gml="http://www.opengis.net/gml"><gml:timePosition>latest</gml:timePosition></gml:TimeInstant></ogc:TM_Equals></eventTime><procedure>urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56</procedure><observedProperty>urn:x-ogc:def:property:OGC::Temperature</observedProperty><responseFormat>text/xml;subtype="om/1.0.0"</responseFormat><resultModel>Measurement</resultModel><responseMode>inline</responseMode></GetObservation>';
+ var format = new OpenLayers.Format.SOSGetObservation();
+ var output = format.write({eventTime: 'latest', resultModel: 'Measurement', responseMode: 'inline',
+ procedures: ['urn:ogc:object:feature:OSIRIS-HWS:4fc335bc-06d7-4d5e-a72a-1ac73b9f3b56'], responseFormat: 'text/xml;subtype="om/1.0.0"',
+ offering: 'TEMPERATURE', observedProperties: ['urn:x-ogc:def:property:OGC::Temperature']});
+ t.xml_eq(output, expect, "Request XML is written out correctly");
+
+ var expected = [];
+
+ expected.push('<?xml version="1.0" encoding="UTF-8"?>',
+'<GetObservation xmlns="http://www.opengis.net/sos/1.0"',
+' xmlns:gml="http://www.opengis.net/gml"',
+' xmlns:om="http://www.opengis.net/om/1.0"',
+' xmlns:ogc="http://www.opengis.net/ogc"',
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"',
+' xsi:schemaLocation="http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosGetObservation.xsd"',
+' service="SOS" version="1.0.0">',
+' <offering>RAIN_GAUGE</offering>',
+' <eventTime>',
+' <ogc:TM_Equals>',
+' <ogc:PropertyName>urn:ogc:data:time:iso8601</ogc:PropertyName>',
+' <gml:TimeInstant>',
+' <gml:timePosition>latest</gml:timePosition>',
+' </gml:TimeInstant>',
+' </ogc:TM_Equals>',
+' </eventTime>',
+' <observedProperty>urn:ogc:def:property:OGC::Precipitation1Hour</observedProperty>',
+' <featureOfInterest>',
+' <ObjectID>urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93</ObjectID>',
+' </featureOfInterest>',
+' <responseFormat>text/xml;subtype="om/1.0.0"</responseFormat>',
+'</GetObservation>');
+ expect = expected.join("");
+ var output = format.write({eventTime: 'latest', offering: 'RAIN_GAUGE',
+ observedProperties: ['urn:ogc:def:property:OGC::Precipitation1Hour'],
+ responseFormat: 'text/xml;subtype="om/1.0.0"',
+ foi: {objectId: 'urn:ogc:object:feature:OSIRIS-HWS:3d3b239f-7696-4864-9d07-15447eae2b93'}});
+ t.xml_eq(output, expect, "Request XML is written out correctly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/Text.html b/misc/openlayers/tests/Format/Text.html
new file mode 100644
index 0000000..9b18bb5
--- /dev/null
+++ b/misc/openlayers/tests/Format/Text.html
@@ -0,0 +1,49 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_basic(t) {
+ t.plan(5);
+ var format = new OpenLayers.Format.Text({extractStyles: true});
+ var features = format.read(OpenLayers.Util.getElement("content").value);
+ t.eq(features[0].style.externalGraphic, format.defaultStyle.externalGraphic, "style is set to defaults if no style props set in text file");
+ var features = format.read(OpenLayers.Util.getElement("contentMarker").value);
+ t.eq(features[0].style.externalGraphic, OpenLayers.Util.getImagesLocation() + "marker.png", "marker set correctly by default.");
+
+ var features = format.read(OpenLayers.Util.getElement("content2").value);
+ t.eq(features.length, 2, "two features read");
+ t.eq(features[0].style.externalGraphic, "marker.png", "marker set correctly from data.");
+ // t.eq(format.defaultStyle.externalGraphic, "../../img/marker.png", "defaultStyle externalGraphic not changed by pulling from data");
+
+ var format = new OpenLayers.Format.Text({extractStyles: false});
+ var features = format.read(OpenLayers.Util.getElement("content2").value);
+ t.eq(features[0].style, null, "extractStyles: false results in null style property, even with style properties used");
+ }
+ function test_extra(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.Text();
+ var features = format.read(OpenLayers.Util.getElement("content3").value);
+ t.eq(features[0].attributes.whee, "chicken", "extra attributes are stored for later use");
+ }
+ </script>
+</head>
+<body>
+<textarea id="content">
+point
+5,5
+</textarea>
+<textarea id="contentMarker">
+point iconSize
+5,5 8,8
+</textarea>
+<textarea id="content2">
+point icon
+5,5 marker.png
+10,10 marker2.png
+</textarea>
+<textarea id="content3">
+point whee
+5,5 chicken
+</textarea>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WCSCapabilities.html b/misc/openlayers/tests/Format/WCSCapabilities.html
new file mode 100644
index 0000000..b3e90b6
--- /dev/null
+++ b/misc/openlayers/tests/Format/WCSCapabilities.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read(t) {
+ t.plan(4);
+
+ var _v1_0_0 = OpenLayers.Format.WCSCapabilities.v1_0_0.prototype.read;
+ var _v1_1_0 = OpenLayers.Format.WCSCapabilities.v1_1_0.prototype.read;
+
+ var parser = new OpenLayers.Format.WCSCapabilities();
+
+ // version 1.0.0
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wcs:WCS_Capabilities version="1.0.0" xmlns:wcs="http://www.opengis.net/wcs"></wcs:WCS_Capabilities>';
+ OpenLayers.Format.WCSCapabilities.v1_0_0.prototype.read = function() {
+ t.ok(true, "Version 1.0.0 detected");
+ return {};
+ }
+ var res = parser.read(text);
+ t.eq(res.version, "1.0.0", "version 1.0.0 written to result object");
+ OpenLayers.Format.WCSCapabilities.v1_1_0.prototype.read = _v1_1_0;
+
+ // version 1.1.0
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wcs:WCS_Capabilities version="1.1.0" xmlns:wcs="http://www.opengis.net/wcs/1.1"></wcs:WCS_Capabilities>';
+ OpenLayers.Format.WCSCapabilities.v1_1_0.prototype.read = function() {
+ t.ok(true, "Version 1.1.0 detected");
+ return {};
+ }
+ var res = parser.read(text);
+ t.eq(res.version, "1.1.0", "version 1.1.0 written to result object");
+ OpenLayers.Format.WCSCapabilities.v1_1_0.prototype.read = _v1_1_0;
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WCSCapabilities/v1.html b/misc/openlayers/tests/Format/WCSCapabilities/v1.html
new file mode 100644
index 0000000..180edc7
--- /dev/null
+++ b/misc/openlayers/tests/Format/WCSCapabilities/v1.html
@@ -0,0 +1,87 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.WCSCapabilities();
+ var text = '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<ows:ExceptionReport language="en" version="1.0.0"' +
+ ' xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.0.0/owsExceptionReport.xsd"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows">' +
+ ' <ows:Exception locator="foo" exceptionCode="InvalidParameterValue">' +
+ ' <ows:ExceptionText>Update error: Error occured updating features</ows:ExceptionText>' +
+ ' <ows:ExceptionText>Second exception line</ows:ExceptionText>' +
+ ' </ows:Exception>' +
+ '</ows:ExceptionReport>';
+
+ var obj = parser.read(text);
+ t.ok(!!obj.error, "Error reported correctly"); // The above should place an error in obj.error
+ }
+
+ function test_read(t) {
+ t.plan(34); // Number of tests performed: If you add a test below, be sure to increment this accordingly
+
+ var parser = new OpenLayers.Format.WCSCapabilities();
+
+ // MapServer, v1.0.0
+ var text = '<?xml version="1.0" encoding="UTF-8"?><WCS_Capabilities xmlns="http://www.opengis.net/wcs" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" updateSequence="0" xsi:schemaLocation="http://www.opengis.net/wcs http://schemas.opengis.net/wcs/1.0.0/wcsCapabilities.xsd"><Service><name>MapServer WCS</name><label>WCS Sample Data Server 1.0.0</label><keywords><keyword>Geospatial WebServices</keyword><keyword>Luxembourg!</keyword></keywords><responsibleParty><individualName>Franko Lemmer</individualName><organisationName>CRP Henri Tudor</organisationName><positionName>R+D engineer</positionName><contactInfo><phone><voice>6463320</voice><facsimile>6465955</facsimile></phone><address><deliveryPoint>66, rue de Luxembourg</deliveryPoint><city>Esch-sur-Alzette</city><administrativeArea/><postalCode>97202</postalCode><country>Luxembourg</country><electronicMailAddress>franko.lemmer@flensburger.de</electronicMailAddress></address><onlineResource xlink:type="simple" xlink:href="http://services.magnificent.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></contactInfo></responsibleParty><fees>mucho dinero</fees><accessConstraints>Open to the public</accessConstraints></Service><Capability><Request><GetCapabilities><DCPType><HTTP><Get><OnlineResource xlink:type="simple" xlink:href="http://services.magnificent.get.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></Get></HTTP></DCPType><DCPType><HTTP><Post><OnlineResource xlink:type="simple" xlink:href="http://services.magnificent.post.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></Post></HTTP></DCPType></GetCapabilities><DescribeCoverage><DCPType><HTTP><Get><OnlineResource xlink:type="simple" xlink:href="http://services.magnificent.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></Get></HTTP></DCPType><DCPType><HTTP><Post><OnlineResource xlink:type="simple" xlink:href="http://services.magnificent.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></Post></HTTP></DCPType></DescribeCoverage><GetCoverage><DCPType><HTTP><Get><OnlineResource xlink:type="simple" xlink:href="http://services.magnificent.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></Get></HTTP></DCPType><DCPType><HTTP><Post><OnlineResource xlink:type="simple" xlink:href="http://services.magnificent.lu/cgi-bin/mapserv?map=/var/www/MapFiles/wcs_test.map&amp;"/></Post></HTTP></DCPType></GetCoverage></Request><Exception><Format>application/vnd.ogc.se_xml</Format></Exception></Capability><ContentMetadata><CoverageOfferingBrief><name>ro_dsm</name><label>Rotterdam DSM</label><lonLatEnvelope srsName="urn:ogc:def:crs:OGC:1.3:CRS84"><gml:pos>4.44444 51.515151</gml:pos><gml:pos>5.55555 52.525252</gml:pos></lonLatEnvelope></CoverageOfferingBrief><CoverageOfferingBrief><name>ro_dsm_mini</name><label>ro_dsm_mini</label><lonLatEnvelope srsName="urn:ogc:def:crs:OGC:1.3:CRS84"><gml:pos>4.47489346945755 51.9159453786927</gml:pos><gml:pos>4.47687824892444 51.9170706688033</gml:pos></lonLatEnvelope></CoverageOfferingBrief><CoverageOfferingBrief><name>ro_irra</name><label>ro_irra</label><lonLatEnvelope srsName="urn:ogc:def:crs:OGC:1.3:CRS84"><gml:pos>4.471333734139 51.912813427383</gml:pos><gml:pos>4.4808508475645 51.9248713705576</gml:pos></lonLatEnvelope></CoverageOfferingBrief><CoverageOfferingBrief><name>ro_irra_ext</name><label>ro_irra_ext</label><lonLatEnvelope srsName="urn:ogc:def:crs:OGC:1.3:CRS84"><gml:pos>4.10024171314823 51.9359764992844</gml:pos><gml:pos>4.21909054278063 52.001415228243</gml:pos></lonLatEnvelope></CoverageOfferingBrief></ContentMetadata></WCS_Capabilities>';
+
+ var res = parser.read(text);
+
+ t.ok(!res.error, "Parsing XML generated no errors");
+ t.eq(res.service.fees, "mucho dinero", "Service>Fees correctly parsed");
+ t.eq(res.service.accessConstraints, "Open to the public", "Service>AccessConstraints correctly parsed");
+ t.eq(res.service.keywords.length, 2, "Correct number of Service>Keywords found");
+ t.eq(res.service.keywords[0], "Geospatial WebServices", "Service>Keywords correctly parsed");
+ t.eq(res.service.label, "WCS Sample Data Server 1.0.0", "Service>Label correctly parsed");
+ t.eq(res.service.name, "MapServer WCS", "Service>Name correctly parsed");
+
+ var responsibleParty = res.service.responsibleParty;
+ t.eq(responsibleParty.individualName, "Franko Lemmer", "Service>ResponsibleParty>IndividualName correctly parsed");
+ t.eq(responsibleParty.organisationName, "CRP Henri Tudor", "Service>ResponsibleParty>OrganisationName correctly parsed");
+ t.eq(responsibleParty.positionName, "R+D engineer", "Service>ResponsibleParty>PositionName correctly parsed");
+ t.eq(responsibleParty.contactInfo.address.city, "Esch-sur-Alzette", "Service>responsibleParty>ContactInfo>Address>City correctly parsed");
+ t.eq(responsibleParty.contactInfo.address.country, "Luxembourg", "Service>responsibleParty>ContactInfo>Address>Country correctly parsed");
+ t.eq(responsibleParty.contactInfo.address.deliveryPoint, "66, rue de Luxembourg", "Service>responsibleParty>ContactInfo>Address>DeliveryPoint correctly parsed");
+ t.eq(responsibleParty.contactInfo.address.electronicMailAddress, "franko.lemmer@flensburger.de", "Service>responsibleParty>ContactInfo>Address>ElectronicMailAddress correctly parsed");
+ t.eq(responsibleParty.contactInfo.address.postalCode, "97202", "Service>responsibleParty>ContactInfo>Address>PostalCode correctly parsed");
+ t.eq(responsibleParty.contactInfo.phone.facsimile, "6465955", "Service>responsibleParty>ContactInfo>Phone>Facsimile correctly parsed");
+ t.eq(responsibleParty.contactInfo.phone.voice, "6463320", "Service>responsibleParty>ContactInfo>Phone>Voice correctly parsed");
+
+ var metadata = res.contentMetadata[0];
+
+ t.eq(metadata.name, "ro_dsm", "ContentMetadata>Name correctly parsed");
+ t.eq(metadata.label, "Rotterdam DSM", "ContentMetadata>Label correctly parsed");
+ t.eq(metadata.lonLatEnvelope.min.x, 51.515151, "ContentMetadata>lonLatEnvelope>Min>Lat correctly parsed");
+ t.eq(metadata.lonLatEnvelope.min.y, 4.44444, "ContentMetadata>lonLatEnvelope>Min>Lon correctly parsed");
+ t.eq(metadata.lonLatEnvelope.max.x, 52.525252, "ContentMetadata>lonLatEnvelope>Max>Lat correctly parsed");
+ t.eq(metadata.lonLatEnvelope.max.y, 5.55555, "ContentMetadata>lonLatEnvelope>Max>Lon correctly parsed");
+ t.eq(metadata.lonLatEnvelope.srsName, "urn:ogc:def:crs:OGC:1.3:CRS84", "ContentMetadata>lonLatEnvelope>SrsName correctly parsed");
+ t.eq(res.contentMetadata.length, 4, "Correct number of metadata records found");
+
+
+ // MapServer, v1.1.0
+ text = '<?xml version="1.0" encoding="UTF-8"?><Capabilities xmlns="http://www.opengis.net/wcs/1.1" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ogc="http://www.opengis.net/ogc" version="1.1.0" xsi:schemaLocation="http://www.opengis.net/wcs/1.1 http://schemas.opengis.net/wcs/1.1/wcsGetCapabilities.xsd http://www.opengis.net/ows/1.1 http://schemas.opengis.net/ows/1.1.0/owsAll.xsd"><ows:ServiceIdentification><ows:Title>Web-Service Demo with data stored at TUDOR site</ows:Title><ows:Abstract>This installation serves different Web-Service types (WMS, WFS) for testing</ows:Abstract><ows:Keywords><ows:Keyword>Geospatial WebServices</ows:Keyword><ows:Keyword>Keyword One</ows:Keyword><ows:Keyword>Keyword Two!!</ows:Keyword></ows:Keywords><ows:ServiceType codeSpace="OGC">OGC WCS</ows:ServiceType><ows:ServiceTypeVersion>1.1.0</ows:ServiceTypeVersion><ows:Fees>No fee!</ows:Fees><ows:AccessConstraints>Unconstrained!</ows:AccessConstraints></ows:ServiceIdentification><ows:ServiceProvider><ows:ProviderName>CRP Henri Tudor</ows:ProviderName><ows:ProviderSite xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/><ows:ServiceContact><ows:IndividualName>Roy Dumerde</ows:IndividualName><ows:PositionName>R+D engineer</ows:PositionName><ows:ContactInfo><ows:Phone><ows:Voice>6463320</ows:Voice><ows:Facsimile>6465955</ows:Facsimile></ows:Phone><ows:Address><ows:DeliveryPoint>66, rue de Luxembourg</ows:DeliveryPoint><ows:City>Esch-sur-Alzette</ows:City><ows:AdministrativeArea/><ows:PostalCode>97202</ows:PostalCode><ows:Country>Luxembourg</ows:Country><ows:ElectronicMailAddress>flappy@tutones.com</ows:ElectronicMailAddress></ows:Address><ows:OnlineResource xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/><ows:HoursOfService>24/7</ows:HoursOfService><ows:ContactInstructions>by phone</ows:ContactInstructions></ows:ContactInfo><ows:Role>GIS-Analyst</ows:Role></ows:ServiceContact></ows:ServiceProvider><ows:OperationsMetadata><ows:Operation name="GetCapabilities"><ows:DCP><ows:HTTP><ows:Get xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/><ows:Post xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/></ows:HTTP></ows:DCP><ows:Parameter name="service"><ows:AllowedValues><ows:Value>WCS</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="version"><ows:AllowedValues><ows:Value>1.1.0</ows:Value></ows:AllowedValues></ows:Parameter></ows:Operation><ows:Operation name="DescribeCoverage"><ows:DCP><ows:HTTP><ows:Get xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/><ows:Post xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/></ows:HTTP></ows:DCP><ows:Parameter name="service"><ows:AllowedValues><ows:Value>WCS</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="version"><ows:AllowedValues><ows:Value>1.1.0</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="identifiers"><ows:AllowedValues><ows:Value>ro_dsm</ows:Value><ows:Value>ro_dsm_mini</ows:Value><ows:Value>ro_irra</ows:Value><ows:Value>ro_irra_ext</ows:Value></ows:AllowedValues></ows:Parameter></ows:Operation><ows:Operation name="GetCoverage"><ows:DCP><ows:HTTP><ows:Get xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/><ows:Post xlink:type="simple" xlink:href="http://services.testorama.lu/cgi-bin/mapserv?map=/var/www/MapFiles/RO_localOWS_test.map&amp;"/></ows:HTTP></ows:DCP><ows:Parameter name="service"><ows:AllowedValues><ows:Value>WCS</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="version"><ows:AllowedValues><ows:Value>1.1.0</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="Identifier"><ows:AllowedValues><ows:Value>ro_dsm</ows:Value><ows:Value>ro_dsm_mini</ows:Value><ows:Value>ro_irra</ows:Value><ows:Value>ro_irra_ext</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="InterpolationType"><ows:AllowedValues><ows:Value>NEAREST_NEIGHBOUR</ows:Value><ows:Value>BILINEAR</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="format"><ows:AllowedValues><ows:Value>image/tiff</ows:Value><ows:Value>image/png</ows:Value><ows:Value>image/jpeg</ows:Value><ows:Value>image/gif</ows:Value><ows:Value>image/png; mode=8bit</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="store"><ows:AllowedValues><ows:Value>false</ows:Value></ows:AllowedValues></ows:Parameter><ows:Parameter name="GridBaseCRS"><ows:AllowedValues><ows:Value>urn:ogc:def:crs:epsg::4326</ows:Value></ows:AllowedValues></ows:Parameter></ows:Operation></ows:OperationsMetadata><Contents><CoverageSummary><ows:Title>Rotterdam DSM</ows:Title><ows:Abstract>Digital Surface Model (DSM) raster data set of inner city Rotterdam</ows:Abstract><ows:WGS84BoundingBox dimensions="2"><ows:LowerCorner>4.471333734139 51.912813427383</ows:LowerCorner><ows:UpperCorner>4.4808508475645 51.9248713705576</ows:UpperCorner></ows:WGS84BoundingBox><SupportedCRS>urn:ogc:def:crs:EPSG::28992</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::900913</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::3857</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::4326</SupportedCRS><SupportedFormat>image/tiff</SupportedFormat><Identifier>ro_dsm</Identifier></CoverageSummary><CoverageSummary><ows:Title>Rotterdam sample DSM subset</ows:Title><ows:Abstract>This a test data set of Rotterdams DSM subset</ows:Abstract><ows:WGS84BoundingBox dimensions="2"><ows:LowerCorner>4.47489346945755 51.9159453786927</ows:LowerCorner><ows:UpperCorner>4.47687824892444 51.9170706688033</ows:UpperCorner></ows:WGS84BoundingBox><SupportedCRS>urn:ogc:def:crs:EPSG::28992</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::900913</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::3857</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::4326</SupportedCRS><SupportedFormat>image/tiff</SupportedFormat><Identifier>ro_dsm_mini</Identifier></CoverageSummary><CoverageSummary><ows:Title>Rotterdam (Ljinbaan) solar irradiation data 2010</ows:Title><ows:Abstract>This a result data set of a solar computation of Ljinbaan area. It shows the sum of kWh/a per sqmeter for 2010</ows:Abstract><ows:WGS84BoundingBox dimensions="2"><ows:LowerCorner>4.471333734139 51.912813427383</ows:LowerCorner><ows:UpperCorner>4.4808508475645 51.9248713705576</ows:UpperCorner></ows:WGS84BoundingBox><SupportedCRS>urn:ogc:def:crs:EPSG::28992</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::900913</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::3857</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::4326</SupportedCRS><SupportedFormat>image/tiff</SupportedFormat><Identifier>ro_irra</Identifier></CoverageSummary><CoverageSummary><ows:Title>Rotterdam (extended) solar irradiation data 2010</ows:Title><ows:Abstract>This a result data set of a solar computation of extended Rotterdam area. It shows the sum of kWh/a per sqmeter for 2010</ows:Abstract><ows:WGS84BoundingBox dimensions="2"><ows:LowerCorner>4.10024171314823 51.9359764992844</ows:LowerCorner><ows:UpperCorner>4.21909054278063 52.001415228243</ows:UpperCorner></ows:WGS84BoundingBox><SupportedCRS>urn:ogc:def:crs:EPSG::28992</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::900913</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::3857</SupportedCRS><SupportedCRS>urn:ogc:def:crs:EPSG::4326</SupportedCRS><SupportedFormat>image/tiff</SupportedFormat><Identifier>ro_irra_ext</Identifier></CoverageSummary></Contents></Capabilities>';
+
+ res = parser.read(text);
+
+ // Most of the parsing is handled by other objects, so not much actually requires testing here
+ t.ok(!res.error, "Parsing XML generated no errors");
+ t.eq(res.contentMetadata.length, 4, "number of features correct");
+
+ var metadata = res.contentMetadata[0];
+ t.eq(metadata.identifier, "ro_dsm", "correct identifier");
+ t.eq(metadata.title, "Rotterdam DSM", "correct title");
+ t.eq(metadata.abstract, "Digital Surface Model (DSM) raster data set of inner city Rotterdam", "correct abstract");
+ t.eq(metadata.supportedFormat.length, 1, "correct number of supported formats");
+ t.eq(metadata.supportedFormat[0], "image/tiff", "correct format");
+ t.eq(metadata.supportedCRS.length, 4, "correct number of CRS records");
+ t.eq(metadata.supportedCRS[2], "urn:ogc:def:crs:EPSG::3857", "correct CRS");
+ }
+
+ </script>
+</head>
+<body> </body>
+</html>
diff --git a/misc/openlayers/tests/Format/WCSGetCoverage.html b/misc/openlayers/tests/Format/WCSGetCoverage.html
new file mode 100644
index 0000000..6379b2b
--- /dev/null
+++ b/misc/openlayers/tests/Format/WCSGetCoverage.html
@@ -0,0 +1,80 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_write_WCSGetCoverage(t) {
+ t.plan(1);
+ var expected = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<GetCoverage xmlns="http://www.opengis.net/wcs/1.1" xmlns:ows="http://www.opengis.net/ows/1.1"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+' xsi:schemaLocation="http://www.opengis.net/wcs/1.1 http://schemas.opengis.net/wcs/1.1/wcsGetCoverage.xsd"' +
+' service="WCS" version="1.1.2">' +
+' <ows:Identifier>Cov123</ows:Identifier>' +
+' <DomainSubset>' +
+' <ows:BoundingBox crs="urn:ogc:def:crs:OGC:2:84">' +
+' <ows:LowerCorner>-71 47</ows:LowerCorner>' +
+' <ows:UpperCorner>-66 51</ows:UpperCorner>' +
+' </ows:BoundingBox>' +
+' <TemporalSubset>' +
+' <TimePeriod>' +
+' <BeginPosition>2006-08-01</BeginPosition>' +
+' <EndPosition>2006-09-01</EndPosition>' +
+' <TimeResolution>P1D</TimeResolution>' +
+' </TimePeriod>' +
+' <TimePeriod>' +
+' <BeginPosition>2007-08-01</BeginPosition>' +
+' <EndPosition>2007-09-01</EndPosition>' +
+' <TimeResolution>P1D</TimeResolution>' +
+' </TimePeriod>' +
+' </TemporalSubset>' +
+' </DomainSubset>' +
+' <Output format="image/netcdf">' +
+' <GridCRS>' +
+' <GridBaseCRS>urn:ogc:def:crs:EPSG:6.6:32618</GridBaseCRS>' +
+' <GridType>urn:ogc:def:method:WCS:1.1:2dGridin2dCrs</GridType>' +
+' <GridOrigin>3000 4000</GridOrigin>' +
+' <GridOffsets>6.0 8.0 -8.0 6.0</GridOffsets>' +
+' <GridCS>urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS</GridCS>' +
+' </GridCRS>' +
+' </Output>' +
+'</GetCoverage>';
+
+ var format = new OpenLayers.Format.WCSGetCoverage();
+ var result = format.write({
+ identifier: 'Cov123',
+ domainSubset: {
+ boundingBox: {projection: 'urn:ogc:def:crs:OGC:2:84', bounds: new OpenLayers.Bounds(-71, 47, -66, 51)},
+ temporalSubset: {
+ timePeriods: [
+ {
+ begin: '2006-08-01',
+ end: '2006-09-01',
+ resolution: 'P1D'
+ }, {
+ begin: '2007-08-01',
+ end: '2007-09-01',
+ resolution: 'P1D'
+ }
+ ]
+ }
+ },
+ output: {
+ format: 'image/netcdf',
+ gridCRS: {
+ baseCRS: 'urn:ogc:def:crs:EPSG:6.6:32618',
+ type: 'urn:ogc:def:method:WCS:1.1:2dGridin2dCrs',
+ origin: '3000 4000',
+ offsets: '6.0 8.0 -8.0 6.0',
+ CS: 'urn:ogc:def:cs:OGC:0.0:Grid2dSquareCS'
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WCS GetCoverage written out correctly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFS.html b/misc/openlayers/tests/Format/WFS.html
new file mode 100644
index 0000000..7b3b737
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFS.html
@@ -0,0 +1,81 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script>
+ function test_wfs_update_node(t) {
+ t.plan(2);
+ var expected = readXML("Update");
+ var updateFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(1,2),
+ {foo: "bar"});
+ updateFeature.fid = "fid.42";
+ updateFeature.state = OpenLayers.State.UPDATE;
+ var format = new OpenLayers.Format.WFS({
+ 'featureNS':'http://www.openplans.org/topp',
+ 'featureName': 'states',
+ 'geometryName': 'the_geom',
+ 'featurePrefix': 'topp'
+ }, {options:{}});
+ var updateNode = format.update(updateFeature);
+ t.xml_eq(updateNode, expected, "update node matches expected XML value.");
+ var format = new OpenLayers.Format.WFS({
+ 'featurePrefix': 'topp'
+ }, {options:{typename: 'states', 'featureNS': 'http://www.openplans.org/topp', 'geometry_column': 'the_geom' }});
+ var updateNode = format.update(updateFeature);
+ t.xml_eq(updateNode, expected, "update node matches expected XML value.");
+ }
+ function test_wfs_delete_node(t) {
+ t.plan(2);
+ var expected = readXML("Delete");
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ feature.state = OpenLayers.State.DELETE;
+ feature.fid = "fid.37";
+ var format = new OpenLayers.Format.WFS({
+ 'featureNS':'http://www.openplans.org/topp',
+ 'featureName': 'states',
+ 'featurePrefix': 'topp'
+ }, {options:{}});
+ var deleteNode = format.remove(feature);
+ t.xml_eq(deleteNode, expected, "delete node matches expected XML value.");
+ var format = new OpenLayers.Format.WFS({
+ 'featurePrefix': 'topp'
+ }, {options:{typename: 'states', 'featureNS': 'http://www.openplans.org/topp'}});
+ var deleteNode = format.remove(feature);
+ t.xml_eq(deleteNode, expected, "delete node matches expected XML value.");
+ }
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return new OpenLayers.Format.XML().read(xml).documentElement;
+ }
+
+ </script>
+</head>
+<body>
+<div id="Update"><!--
+<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>the_geom</wfs:Name>
+ <wfs:Value>
+ <gml:Point xmlns:gml="http://www.opengis.net/gml">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>bar</wfs:Value>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.42"/>
+ </ogc:Filter>
+</wfs:Update>
+--></div>
+<div id="Delete"><!--
+<wfs:Delete xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.37"/>
+ </ogc:Filter>
+</wfs:Delete>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFSCapabilities.html b/misc/openlayers/tests/Format/WFSCapabilities.html
new file mode 100644
index 0000000..fc2ff2d
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFSCapabilities.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read(t) {
+ t.plan(4);
+
+ var _v1_0_0 = OpenLayers.Format.WFSCapabilities.v1_0_0.prototype.read;
+ var _v1_1_0 = OpenLayers.Format.WFSCapabilities.v1_1_0.prototype.read;
+
+ var parser = new OpenLayers.Format.WFSCapabilities();
+
+ // version 1.0.0
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wfs:WFS_Capabilities version="1.0.0" xmlns:wfs="http://www.opengis.net/wfs"></wfs:WFS_Capabilities>';
+ OpenLayers.Format.WFSCapabilities.v1_0_0.prototype.read = function() {
+ t.ok(true, "Version 1.0.0 detected");
+ return {};
+ }
+ var res = parser.read(text);
+ t.eq(res.version, "1.0.0", "version 1.0.0 written to result object");
+ OpenLayers.Format.WFSCapabilities.v1_1_0.prototype.read = _v1_1_0;
+
+ // version 1.1.0
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wfs:WFS_Capabilities version="1.1.0" xmlns:wfs="http://www.opengis.net/wfs"></wfs:WFS_Capabilities>';
+ OpenLayers.Format.WFSCapabilities.v1_1_0.prototype.read = function() {
+ t.ok(true, "Version 1.1.0 detected");
+ return {};
+ }
+ var res = parser.read(text);
+ t.eq(res.version, "1.1.0", "version 1.1.0 written to result object");
+ OpenLayers.Format.WFSCapabilities.v1_1_0.prototype.read = _v1_1_0;
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFSCapabilities/v1.html b/misc/openlayers/tests/Format/WFSCapabilities/v1.html
new file mode 100644
index 0000000..0bc1705
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFSCapabilities/v1.html
@@ -0,0 +1,179 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+ t.plan(1);
+ var parser = new OpenLayers.Format.WFSCapabilities();
+ var text = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<ows:ExceptionReport language="en" version="1.0.0"' +
+' xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.0.0/owsExceptionReport.xsd"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows">' +
+' <ows:Exception locator="foo" exceptionCode="InvalidParameterValue">' +
+' <ows:ExceptionText>Update error: Error occured updating features</ows:ExceptionText>' +
+' <ows:ExceptionText>Second exception line</ows:ExceptionText>' +
+' </ows:Exception>' +
+'</ows:ExceptionReport>';
+
+ var obj = parser.read(text);
+ t.ok(!!obj.error, "Error reported correctly"); // The above should place an error in obj.error
+ }
+
+ function test_read(t) {
+ t.plan(37); // Number of tests performed: If you add a test below, be sure to increment this accordingly
+
+ var parser = new OpenLayers.Format.WFSCapabilities();
+
+ // GeoServer, v1.1.0
+ var text = '<?xml version="1.0" encoding="UTF-8"?><wfs:WFS_Capabilities version="1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="http://www.opengis.net/wfs http://localhost:80/geoserver/schemas/wfs/1.1.0/wfs.xsd" xmlns:it.geosolutions="http://www.geo-solutions.it" xmlns:cite="http://www.opengeospatial.net/cite" xmlns:tiger="http://www.census.gov" xmlns:sde="http://geoserver.sf.net" xmlns:topp="http://www.openplans.org/topp" xmlns:sf="http://www.openplans.org/spearfish" xmlns:nurc="http://www.nurc.nato.int" updateSequence="57"><ows:ServiceIdentification><ows:Title>GeoServer Web Feature Service</ows:Title><ows:Abstract>This is the reference implementation of WFS 1.0.0 and WFS 1.1.0, supports all WFS operations including Transaction.</ows:Abstract><ows:Keywords><ows:Keyword>WFS</ows:Keyword><ows:Keyword>WMS</ows:Keyword><ows:Keyword>GEOSERVER</ows:Keyword></ows:Keywords><ows:ServiceType>WFS</ows:ServiceType><ows:ServiceTypeVersion>1.1.0</ows:ServiceTypeVersion><ows:Fees>NONE</ows:Fees><ows:AccessConstraints>NONE</ows:AccessConstraints></ows:ServiceIdentification><ows:ServiceProvider><ows:ProviderName>The ancient geographes INC</ows:ProviderName><ows:ServiceContact><ows:IndividualName>Claudius Ptolomaeus</ows:IndividualName><ows:PositionName>Chief geographer</ows:PositionName><ows:ContactInfo><ows:Phone><ows:Voice/><ows:Facsimile/></ows:Phone><ows:Address><ows:City>Alexandria</ows:City><ows:AdministrativeArea/><ows:PostalCode/><ows:Country>Egypt</ows:Country></ows:Address></ows:ContactInfo></ows:ServiceContact></ows:ServiceProvider><ows:OperationsMetadata><ows:Operation name="GetCapabilities"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP><ows:Parameter name="AcceptVersions"><ows:Value>1.0.0</ows:Value><ows:Value>1.1.0</ows:Value></ows:Parameter><ows:Parameter name="AcceptFormats"><ows:Value>text/xml</ows:Value></ows:Parameter></ows:Operation><ows:Operation name="DescribeFeatureType"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP><ows:Parameter name="outputFormat"><ows:Value>text/xml; subtype=gml/3.1.1</ows:Value></ows:Parameter></ows:Operation><ows:Operation name="GetFeature"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP><ows:Parameter name="resultType"><ows:Value>results</ows:Value><ows:Value>hits</ows:Value></ows:Parameter><ows:Parameter name="outputFormat"><ows:Value>text/xml; subtype=gml/3.1.1</ows:Value><ows:Value>GML2</ows:Value><ows:Value>GML2-GZIP</ows:Value><ows:Value>SHAPE-ZIP</ows:Value><ows:Value>csv</ows:Value><ows:Value>gml3</ows:Value><ows:Value>json</ows:Value><ows:Value>text/xml; subtype=gml/2.1.2</ows:Value></ows:Parameter><ows:Constraint name="LocalTraverseXLinkScope"><ows:Value>2</ows:Value></ows:Constraint></ows:Operation><ows:Operation name="GetGmlObject"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP></ows:Operation><ows:Operation name="LockFeature"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP><ows:Parameter name="releaseAction"><ows:Value>ALL</ows:Value><ows:Value>SOME</ows:Value></ows:Parameter></ows:Operation><ows:Operation name="GetFeatureWithLock"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP><ows:Parameter name="resultType"><ows:Value>results</ows:Value><ows:Value>hits</ows:Value></ows:Parameter><ows:Parameter name="outputFormat"><ows:Value>text/xml; subtype=gml/3.1.1</ows:Value><ows:Value>GML2</ows:Value><ows:Value>GML2-GZIP</ows:Value><ows:Value>SHAPE-ZIP</ows:Value><ows:Value>csv</ows:Value><ows:Value>gml3</ows:Value><ows:Value>json</ows:Value><ows:Value>text/xml; subtype=gml/2.1.2</ows:Value></ows:Parameter></ows:Operation><ows:Operation name="Transaction"><ows:DCP><ows:HTTP><ows:Get xlink:href="http://localhost:80/geoserver/wfs?"/><ows:Post xlink:href="http://localhost:80/geoserver/wfs?"/></ows:HTTP></ows:DCP><ows:Parameter name="inputFormat"><ows:Value>text/xml; subtype=gml/3.1.1</ows:Value></ows:Parameter><ows:Parameter name="idgen"><ows:Value>GenerateNew</ows:Value><ows:Value>UseExisting</ows:Value><ows:Value>ReplaceDuplicate</ows:Value></ows:Parameter><ows:Parameter name="releaseAction"><ows:Value>ALL</ows:Value><ows:Value>SOME</ows:Value></ows:Parameter></ows:Operation></ows:OperationsMetadata><FeatureTypeList><Operations><Operation>Query</Operation><Operation>Insert</Operation><Operation>Update</Operation><Operation>Delete</Operation><Operation>Lock</Operation></Operations><FeatureType xmlns:tiger="http://www.census.gov"><Name>tiger:poly_landmarks</Name><Title>Manhattan (NY) landmarks</Title><Abstract>Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs</Abstract><ows:Keywords><ows:Keyword>DS_poly_landmarks</ows:Keyword><ows:Keyword>poly_landmarks</ows:Keyword><ows:Keyword>landmarks</ows:Keyword><ows:Keyword>manhattan</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-74.047185 40.679648</ows:LowerCorner><ows:UpperCorner>-73.90782 40.882078</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:tiger="http://www.census.gov"><Name>tiger:poi</Name><Title>Manhattan (NY) points of interest</Title><Abstract>Points of interest in New York, New York (on Manhattan). One of the attributes contains the name of a file with a picture of the point of interest.</Abstract><ows:Keywords><ows:Keyword>poi</ows:Keyword><ows:Keyword>DS_poi</ows:Keyword><ows:Keyword>points_of_interest</ows:Keyword><ows:Keyword>Manhattan</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-74.0118315772888 40.70754683896324</ows:LowerCorner><ows:UpperCorner>-74.00857344353275 40.711945649065406</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:tiger="http://www.census.gov"><Name>tiger:tiger_roads</Name><Title>Manhattan (NY) roads</Title><Abstract>Highly simplified road layout of Manhattan in New York..</Abstract><ows:Keywords><ows:Keyword>DS_tiger_roads</ows:Keyword><ows:Keyword>tiger_roads</ows:Keyword><ows:Keyword>roads</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-74.02722 40.684221</ows:LowerCorner><ows:UpperCorner>-73.907005 40.878178</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:sf="http://www.openplans.org/spearfish"><Name>sf:archsites</Name><Title>Spearfish archeological sites</Title><Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract><ows:Keywords><ows:Keyword>archsites</ows:Keyword><ows:Keyword>sfArchsites</ows:Keyword><ows:Keyword>spearfish</ows:Keyword><ows:Keyword>archeology</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:26713</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-103.8725637911543 44.37740330855979</ows:LowerCorner><ows:UpperCorner>-103.63794182141925 44.48804280772808</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:sf="http://www.openplans.org/spearfish"><Name>sf:bugsites</Name><Title>Spearfish bug locations</Title><Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract><ows:Keywords><ows:Keyword>sfBugsites</ows:Keyword><ows:Keyword>bugsites</ows:Keyword><ows:Keyword>insects</ows:Keyword><ows:Keyword>spearfish</ows:Keyword><ows:Keyword>tiger_beetles</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:26713</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-103.86796131703647 44.373938816704396</ows:LowerCorner><ows:UpperCorner>-103.63773523234195 44.43418821380063</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:sf="http://www.openplans.org/spearfish"><Name>sf:restricted</Name><Title>Spearfish restricted areas</Title><Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract><ows:Keywords><ows:Keyword>restricted</ows:Keyword><ows:Keyword>sfRestricted</ows:Keyword><ows:Keyword>spearfish</ows:Keyword><ows:Keyword>areas</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:26713</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-103.85057172920756 44.39436387625042</ows:LowerCorner><ows:UpperCorner>-103.74741494853805 44.48215752041131</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:sf="http://www.openplans.org/spearfish"><Name>sf:roads</Name><Title>Spearfish roads</Title><Abstract>Sample data from GRASS, road layout, Spearfish, South Dakota, USA</Abstract><ows:Keywords><ows:Keyword>sfRoads</ows:Keyword><ows:Keyword>roads</ows:Keyword><ows:Keyword>spearfish</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:26713</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-103.87741691493184 44.37087275281798</ows:LowerCorner><ows:UpperCorner>-103.62231404880659 44.50015918338962</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:sf="http://www.openplans.org/spearfish"><Name>sf:streams</Name><Title>Spearfish streams</Title><Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract><ows:Keywords><ows:Keyword>sfStreams</ows:Keyword><ows:Keyword>streams</ows:Keyword><ows:Keyword>spearfish</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:26713</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-103.87789019829768 44.372335260095554</ows:LowerCorner><ows:UpperCorner>-103.62287788915457 44.502218486214815</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:topp="http://www.openplans.org/topp"><Name>topp:tasmania_cities</Name><Title>Tasmania cities</Title><Abstract>Cities in Tasmania (actually, just the capital)</Abstract><ows:Keywords><ows:Keyword>cities</ows:Keyword><ows:Keyword>Tasmania</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>145.19754 -43.423512</ows:LowerCorner><ows:UpperCorner>148.27298000000002 -40.852802</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:topp="http://www.openplans.org/topp"><Name>topp:tasmania_roads</Name><Title>Tasmania roads</Title><Abstract>Main Tasmania roads</Abstract><ows:Keywords><ows:Keyword>Roads</ows:Keyword><ows:Keyword>Tasmania</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>145.19754 -43.423512</ows:LowerCorner><ows:UpperCorner>148.27298000000002 -40.852802</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:topp="http://www.openplans.org/topp"><Name>topp:tasmania_state_boundaries</Name><Title>Tasmania state boundaries</Title><Abstract>Tasmania state boundaries</Abstract><ows:Keywords><ows:Keyword>tasmania_state_boundaries</ows:Keyword><ows:Keyword>Tasmania</ows:Keyword><ows:Keyword>boundaries</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>143.83482400000003 -43.648056</ows:LowerCorner><ows:UpperCorner>148.47914100000003 -39.573891</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:topp="http://www.openplans.org/topp"><Name>topp:tasmania_water_bodies</Name><Title>Tasmania water bodies</Title><Abstract>Tasmania water bodies</Abstract><ows:Keywords><ows:Keyword>Lakes</ows:Keyword><ows:Keyword>Bodies</ows:Keyword><ows:Keyword>Australia</ows:Keyword><ows:Keyword>Water</ows:Keyword><ows:Keyword>Tasmania</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>145.97161899999998 -43.031944</ows:LowerCorner><ows:UpperCorner>147.219696 -41.775558</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:topp="http://www.openplans.org/topp"><Name>topp:states</Name><Title>USA Population</Title><Abstract>This is some census data on the states.</Abstract><ows:Keywords><ows:Keyword>census</ows:Keyword><ows:Keyword>united</ows:Keyword><ows:Keyword>boundaries</ows:Keyword><ows:Keyword>state</ows:Keyword><ows:Keyword>states</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-124.731422 24.955967</ows:LowerCorner><ows:UpperCorner>-66.969849 49.371735</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType><FeatureType xmlns:tiger="http://www.census.gov"><Name>tiger:giant_polygon</Name><Title>World rectangle</Title><Abstract>A simple rectangular polygon covering most of the world, it\'s only used for the purpose of providing a background (WMS bgcolor could be used instead)</Abstract><ows:Keywords><ows:Keyword>DS_giant_polygon</ows:Keyword><ows:Keyword>giant_polygon</ows:Keyword></ows:Keywords><DefaultSRS>urn:x-ogc:def:crs:EPSG:4326</DefaultSRS><ows:WGS84BoundingBox><ows:LowerCorner>-180.0 -90.0</ows:LowerCorner><ows:UpperCorner>180.0 90.0</ows:UpperCorner></ows:WGS84BoundingBox></FeatureType></FeatureTypeList><ogc:Filter_Capabilities><ogc:Spatial_Capabilities><ogc:GeometryOperands><ogc:GeometryOperand>gml:Envelope</ogc:GeometryOperand><ogc:GeometryOperand>gml:Point</ogc:GeometryOperand><ogc:GeometryOperand>gml:LineString</ogc:GeometryOperand><ogc:GeometryOperand>gml:Polygon</ogc:GeometryOperand></ogc:GeometryOperands><ogc:SpatialOperators><ogc:SpatialOperator name="Disjoint"/><ogc:SpatialOperator name="Equals"/><ogc:SpatialOperator name="DWithin"/><ogc:SpatialOperator name="Beyond"/><ogc:SpatialOperator name="Intersects"/><ogc:SpatialOperator name="Touches"/><ogc:SpatialOperator name="Crosses"/><ogc:SpatialOperator name="Contains"/><ogc:SpatialOperator name="Overlaps"/><ogc:SpatialOperator name="BBOX"/></ogc:SpatialOperators></ogc:Spatial_Capabilities><ogc:Scalar_Capabilities><ogc:LogicalOperators/><ogc:ComparisonOperators><ogc:ComparisonOperator>LessThan</ogc:ComparisonOperator><ogc:ComparisonOperator>GreaterThan</ogc:ComparisonOperator><ogc:ComparisonOperator>LessThanEqualTo</ogc:ComparisonOperator><ogc:ComparisonOperator>GreaterThanEqualTo</ogc:ComparisonOperator><ogc:ComparisonOperator>EqualTo</ogc:ComparisonOperator><ogc:ComparisonOperator>NotEqualTo</ogc:ComparisonOperator><ogc:ComparisonOperator>Like</ogc:ComparisonOperator><ogc:ComparisonOperator>Between</ogc:ComparisonOperator><ogc:ComparisonOperator>NullCheck</ogc:ComparisonOperator></ogc:ComparisonOperators><ogc:ArithmeticOperators><ogc:SimpleArithmetic/><ogc:Functions><ogc:FunctionNames><ogc:FunctionName nArgs="1">abs</ogc:FunctionName><ogc:FunctionName nArgs="1">abs_2</ogc:FunctionName><ogc:FunctionName nArgs="1">abs_3</ogc:FunctionName><ogc:FunctionName nArgs="1">abs_4</ogc:FunctionName><ogc:FunctionName nArgs="1">acos</ogc:FunctionName><ogc:FunctionName nArgs="1">Area</ogc:FunctionName><ogc:FunctionName nArgs="1">asin</ogc:FunctionName><ogc:FunctionName nArgs="1">atan</ogc:FunctionName><ogc:FunctionName nArgs="2">atan2</ogc:FunctionName><ogc:FunctionName nArgs="3">between</ogc:FunctionName><ogc:FunctionName nArgs="1">boundary</ogc:FunctionName><ogc:FunctionName nArgs="1">boundaryDimension</ogc:FunctionName><ogc:FunctionName nArgs="2">buffer</ogc:FunctionName><ogc:FunctionName nArgs="3">bufferWithSegments</ogc:FunctionName><ogc:FunctionName nArgs="0">Categorize</ogc:FunctionName><ogc:FunctionName nArgs="1">ceil</ogc:FunctionName><ogc:FunctionName nArgs="1">centroid</ogc:FunctionName><ogc:FunctionName nArgs="2">classify</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Average</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Bounds</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Count</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Max</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Median</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Min</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Sum</ogc:FunctionName><ogc:FunctionName nArgs="1">Collection_Unique</ogc:FunctionName><ogc:FunctionName nArgs="2">Concatenate</ogc:FunctionName><ogc:FunctionName nArgs="2">contains</ogc:FunctionName><ogc:FunctionName nArgs="1">convexHull</ogc:FunctionName><ogc:FunctionName nArgs="1">cos</ogc:FunctionName><ogc:FunctionName nArgs="2">crosses</ogc:FunctionName><ogc:FunctionName nArgs="2">dateFormat</ogc:FunctionName><ogc:FunctionName nArgs="2">dateParse</ogc:FunctionName><ogc:FunctionName nArgs="2">difference</ogc:FunctionName><ogc:FunctionName nArgs="1">dimension</ogc:FunctionName><ogc:FunctionName nArgs="2">disjoint</ogc:FunctionName><ogc:FunctionName nArgs="2">distance</ogc:FunctionName><ogc:FunctionName nArgs="1">double2bool</ogc:FunctionName><ogc:FunctionName nArgs="1">endPoint</ogc:FunctionName><ogc:FunctionName nArgs="1">envelope</ogc:FunctionName><ogc:FunctionName nArgs="2">EqualInterval</ogc:FunctionName><ogc:FunctionName nArgs="2">equalsExact</ogc:FunctionName><ogc:FunctionName nArgs="3">equalsExactTolerance</ogc:FunctionName><ogc:FunctionName nArgs="2">equalTo</ogc:FunctionName><ogc:FunctionName nArgs="1">exp</ogc:FunctionName><ogc:FunctionName nArgs="1">exteriorRing</ogc:FunctionName><ogc:FunctionName nArgs="1">floor</ogc:FunctionName><ogc:FunctionName nArgs="1">geometryType</ogc:FunctionName><ogc:FunctionName nArgs="1">geomFromWKT</ogc:FunctionName><ogc:FunctionName nArgs="1">geomLength</ogc:FunctionName><ogc:FunctionName nArgs="2">getGeometryN</ogc:FunctionName><ogc:FunctionName nArgs="1">getX</ogc:FunctionName><ogc:FunctionName nArgs="1">getY</ogc:FunctionName><ogc:FunctionName nArgs="1">getZ</ogc:FunctionName><ogc:FunctionName nArgs="2">greaterEqualThan</ogc:FunctionName><ogc:FunctionName nArgs="2">greaterThan</ogc:FunctionName><ogc:FunctionName nArgs="0">id</ogc:FunctionName><ogc:FunctionName nArgs="2">IEEEremainder</ogc:FunctionName><ogc:FunctionName nArgs="3">if_then_else</ogc:FunctionName><ogc:FunctionName nArgs="11">in10</ogc:FunctionName><ogc:FunctionName nArgs="3">in2</ogc:FunctionName><ogc:FunctionName nArgs="4">in3</ogc:FunctionName><ogc:FunctionName nArgs="5">in4</ogc:FunctionName><ogc:FunctionName nArgs="6">in5</ogc:FunctionName><ogc:FunctionName nArgs="7">in6</ogc:FunctionName><ogc:FunctionName nArgs="8">in7</ogc:FunctionName><ogc:FunctionName nArgs="9">in8</ogc:FunctionName><ogc:FunctionName nArgs="10">in9</ogc:FunctionName><ogc:FunctionName nArgs="1">int2bbool</ogc:FunctionName><ogc:FunctionName nArgs="1">int2ddouble</ogc:FunctionName><ogc:FunctionName nArgs="1">interiorPoint</ogc:FunctionName><ogc:FunctionName nArgs="2">interiorRingN</ogc:FunctionName><ogc:FunctionName nArgs="2">intersection</ogc:FunctionName><ogc:FunctionName nArgs="2">intersects</ogc:FunctionName><ogc:FunctionName nArgs="1">isClosed</ogc:FunctionName><ogc:FunctionName nArgs="1">isEmpty</ogc:FunctionName><ogc:FunctionName nArgs="2">isLike</ogc:FunctionName><ogc:FunctionName nArgs="1">isNull</ogc:FunctionName><ogc:FunctionName nArgs="1">isRing</ogc:FunctionName><ogc:FunctionName nArgs="1">isSimple</ogc:FunctionName><ogc:FunctionName nArgs="1">isValid</ogc:FunctionName><ogc:FunctionName nArgs="3">isWithinDistance</ogc:FunctionName><ogc:FunctionName nArgs="1">length</ogc:FunctionName><ogc:FunctionName nArgs="2">lessEqualThan</ogc:FunctionName><ogc:FunctionName nArgs="2">lessThan</ogc:FunctionName><ogc:FunctionName nArgs="1">log</ogc:FunctionName><ogc:FunctionName nArgs="2">max</ogc:FunctionName><ogc:FunctionName nArgs="2">max_2</ogc:FunctionName><ogc:FunctionName nArgs="2">max_3</ogc:FunctionName><ogc:FunctionName nArgs="2">max_4</ogc:FunctionName><ogc:FunctionName nArgs="2">min</ogc:FunctionName><ogc:FunctionName nArgs="2">min_2</ogc:FunctionName><ogc:FunctionName nArgs="2">min_3</ogc:FunctionName><ogc:FunctionName nArgs="2">min_4</ogc:FunctionName><ogc:FunctionName nArgs="1">not</ogc:FunctionName><ogc:FunctionName nArgs="2">notEqualTo</ogc:FunctionName><ogc:FunctionName nArgs="1">numGeometries</ogc:FunctionName><ogc:FunctionName nArgs="1">numInteriorRing</ogc:FunctionName><ogc:FunctionName nArgs="1">numPoints</ogc:FunctionName><ogc:FunctionName nArgs="2">overlaps</ogc:FunctionName><ogc:FunctionName nArgs="1">parseBoolean</ogc:FunctionName><ogc:FunctionName nArgs="1">parseDouble</ogc:FunctionName><ogc:FunctionName nArgs="1">parseInt</ogc:FunctionName><ogc:FunctionName nArgs="0">pi</ogc:FunctionName><ogc:FunctionName nArgs="2">pointN</ogc:FunctionName><ogc:FunctionName nArgs="2">pow</ogc:FunctionName><ogc:FunctionName nArgs="1">PropertyExists</ogc:FunctionName><ogc:FunctionName nArgs="2">Quantile</ogc:FunctionName><ogc:FunctionName nArgs="0">random</ogc:FunctionName><ogc:FunctionName nArgs="2">relate</ogc:FunctionName><ogc:FunctionName nArgs="3">relatePattern</ogc:FunctionName><ogc:FunctionName nArgs="1">rint</ogc:FunctionName><ogc:FunctionName nArgs="1">round</ogc:FunctionName><ogc:FunctionName nArgs="1">round_2</ogc:FunctionName><ogc:FunctionName nArgs="1">roundDouble</ogc:FunctionName><ogc:FunctionName nArgs="1">sin</ogc:FunctionName><ogc:FunctionName nArgs="1">sqrt</ogc:FunctionName><ogc:FunctionName nArgs="2">StandardDeviation</ogc:FunctionName><ogc:FunctionName nArgs="1">startPoint</ogc:FunctionName><ogc:FunctionName nArgs="2">strConcat</ogc:FunctionName><ogc:FunctionName nArgs="2">strEndsWith</ogc:FunctionName><ogc:FunctionName nArgs="2">strEqualsIgnoreCase</ogc:FunctionName><ogc:FunctionName nArgs="2">strIndexOf</ogc:FunctionName><ogc:FunctionName nArgs="2">strLastIndexOf</ogc:FunctionName><ogc:FunctionName nArgs="1">strLength</ogc:FunctionName><ogc:FunctionName nArgs="2">strMatches</ogc:FunctionName><ogc:FunctionName nArgs="4">strReplace</ogc:FunctionName><ogc:FunctionName nArgs="2">strStartsWith</ogc:FunctionName><ogc:FunctionName nArgs="3">strSubstring</ogc:FunctionName><ogc:FunctionName nArgs="2">strSubstringStart</ogc:FunctionName><ogc:FunctionName nArgs="1">strToLowerCase</ogc:FunctionName><ogc:FunctionName nArgs="1">strToUpperCase</ogc:FunctionName><ogc:FunctionName nArgs="1">strTrim</ogc:FunctionName><ogc:FunctionName nArgs="2">symDifference</ogc:FunctionName><ogc:FunctionName nArgs="1">tan</ogc:FunctionName><ogc:FunctionName nArgs="1">toDegrees</ogc:FunctionName><ogc:FunctionName nArgs="1">toRadians</ogc:FunctionName><ogc:FunctionName nArgs="2">touches</ogc:FunctionName><ogc:FunctionName nArgs="1">toWKT</ogc:FunctionName><ogc:FunctionName nArgs="2">union</ogc:FunctionName><ogc:FunctionName nArgs="2">UniqueInterval</ogc:FunctionName><ogc:FunctionName nArgs="2">within</ogc:FunctionName></ogc:FunctionNames></ogc:Functions></ogc:ArithmeticOperators></ogc:Scalar_Capabilities><ogc:Id_Capabilities><ogc:FID/><ogc:EID/></ogc:Id_Capabilities></ogc:Filter_Capabilities></wfs:WFS_Capabilities>';
+ var res = parser.read(text);
+
+ t.ok(!res.error, "Parsing XML generated no errors");
+ t.eq(res.operationsMetadata["GetFeature"].dcp.http.get[0].url, "http://localhost:80/geoserver/wfs?", "GetFeature GET endpoint correctly parsed");
+ t.eq(res.operationsMetadata["GetFeature"].dcp.http.post[0].url, "http://localhost:80/geoserver/wfs?", "GetFeature POST endpoint correctly parsed");
+ var ft = res.featureTypeList.featureTypes;
+ t.eq(ft.length, 14, "number of feature types correct");
+ t.eq(ft[0]["abstract"], "Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs", "abstract of first feature type correct");
+ t.eq(ft[0]["title"], "Manhattan (NY) landmarks", "title of first feature type correct");
+ t.eq(ft[0]["name"], "poly_landmarks", "name of first feature type correct");
+ t.eq(ft[0]["featureNS"], "http://www.census.gov", "ns of first feature type correct");
+ t.eq(ft[0]["srs"], "urn:x-ogc:def:crs:EPSG:4326", "srs of first feature type correct");
+
+ // GeoServer, v1.0.0
+ text = '<?xml version="1.0" encoding="UTF-8"?><WFS_Capabilities version="1.0.0" xmlns="http://www.opengis.net/wfs" xmlns:it.geosolutions="http://www.geo-solutions.it" xmlns:cite="http://www.opengeospatial.net/cite" xmlns:tiger="http://www.census.gov" xmlns:sde="http://geoserver.sf.net" xmlns:topp="http://www.openplans.org/topp" xmlns:sf="http://www.openplans.org/spearfish" xmlns:nurc="http://www.nurc.nato.int" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://localhost:80/geoserver/schemas/wfs/1.0.0/WFS-capabilities.xsd"><Service><Name>WFS</Name><Title>GeoServer Web Feature Service</Title><Abstract>This is the reference implementation of WFS 1.0.0 and WFS 1.1.0, supports all WFS operations including Transaction.</Abstract><Keywords>WFS, WMS, GEOSERVER</Keywords><OnlineResource>http://localhost:80/geoserver/wfs</OnlineResource><Fees>NONE</Fees><AccessConstraints>NONE</AccessConstraints></Service><Capability><Request><GetCapabilities><DCPType><HTTP><Get onlineResource="http://localhost:80/geoserver/wfs?request=GetCapabilities"/></HTTP></DCPType><DCPType><HTTP><Post onlineResource="http://localhost:80/geoserver/wfs?"/></HTTP></DCPType></GetCapabilities><DescribeFeatureType><SchemaDescriptionLanguage><XMLSCHEMA/></SchemaDescriptionLanguage><DCPType><HTTP><Get onlineResource="http://localhost:80/geoserver/wfs?request=DescribeFeatureType"/></HTTP></DCPType><DCPType><HTTP><Post onlineResource="http://localhost:80/geoserver/wfs?"/></HTTP></DCPType></DescribeFeatureType><GetFeature><ResultFormat><GML2/><SHAPE-ZIP/><GEOJSON/><csv/><GML3/></ResultFormat><DCPType><HTTP><Get onlineResource="http://localhost:80/geoserver/wfs?request=GetFeature"/></HTTP></DCPType><DCPType><HTTP><Post onlineResource="http://localhost:80/geoserver/wfs?"/></HTTP></DCPType></GetFeature><Transaction><DCPType><HTTP><Get onlineResource="http://localhost:80/geoserver/wfs?request=Transaction"/></HTTP></DCPType><DCPType><HTTP><Post onlineResource="http://localhost:80/geoserver/wfs?"/></HTTP></DCPType></Transaction><LockFeature><DCPType><HTTP><Get onlineResource="http://localhost:80/geoserver/wfs?request=LockFeature"/></HTTP></DCPType><DCPType><HTTP><Post onlineResource="http://localhost:80/geoserver/wfs?"/></HTTP></DCPType></LockFeature><GetFeatureWithLock><ResultFormat><GML2/></ResultFormat><DCPType><HTTP><Get onlineResource="http://localhost:80/geoserver/wfs?request=GetFeatureWithLock"/></HTTP></DCPType><DCPType><HTTP><Post onlineResource="http://localhost:80/geoserver/wfs?"/></HTTP></DCPType></GetFeatureWithLock></Request></Capability><FeatureTypeList><Operations><Query/><Insert/><Update/><Delete/><Lock/></Operations><FeatureType><Name>tiger:poly_landmarks</Name><Title>Manhattan (NY) landmarks</Title><Abstract>Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs</Abstract><Keywords>DS_poly_landmarks, poly_landmarks, landmarks, manhattan</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="-74.047185" miny="40.679648" maxx="-73.90782" maxy="40.882078"/></FeatureType><FeatureType><Name>tiger:poi</Name><Title>Manhattan (NY) points of interest</Title><Abstract>Points of interest in New York, New York (on Manhattan). One of the attributes contains the name of a file with a picture of the point of interest.</Abstract><Keywords>poi, DS_poi, points_of_interest, Manhattan</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="-74.0118315772888" miny="40.70754683896324" maxx="-74.00857344353275" maxy="40.711945649065406"/></FeatureType><FeatureType><Name>tiger:tiger_roads</Name><Title>Manhattan (NY) roads</Title><Abstract>Highly simplified road layout of Manhattan in New York..</Abstract><Keywords>DS_tiger_roads, tiger_roads, roads</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="-74.02722" miny="40.684221" maxx="-73.907005" maxy="40.878178"/></FeatureType><FeatureType><Name>sf:archsites</Name><Title>Spearfish archeological sites</Title><Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract><Keywords>archsites, sfArchsites, spearfish, archeology</Keywords><SRS>EPSG:26713</SRS><LatLongBoundingBox minx="-103.8725637911543" miny="44.37740330855979" maxx="-103.63794182141925" maxy="44.48804280772808"/></FeatureType><FeatureType><Name>sf:bugsites</Name><Title>Spearfish bug locations</Title><Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract><Keywords>sfBugsites, bugsites, insects, spearfish, tiger_beetles</Keywords><SRS>EPSG:26713</SRS><LatLongBoundingBox minx="-103.86796131703647" miny="44.373938816704396" maxx="-103.63773523234195" maxy="44.43418821380063"/></FeatureType><FeatureType><Name>sf:restricted</Name><Title>Spearfish restricted areas</Title><Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract><Keywords>restricted, sfRestricted, spearfish, areas</Keywords><SRS>EPSG:26713</SRS><LatLongBoundingBox minx="-103.85057172920756" miny="44.39436387625042" maxx="-103.74741494853805" maxy="44.48215752041131"/></FeatureType><FeatureType><Name>sf:roads</Name><Title>Spearfish roads</Title><Abstract>Sample data from GRASS, road layout, Spearfish, South Dakota, USA</Abstract><Keywords>sfRoads, roads, spearfish</Keywords><SRS>EPSG:26713</SRS><LatLongBoundingBox minx="-103.87741691493184" miny="44.37087275281798" maxx="-103.62231404880659" maxy="44.50015918338962"/></FeatureType><FeatureType><Name>sf:streams</Name><Title>Spearfish streams</Title><Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract><Keywords>sfStreams, streams, spearfish</Keywords><SRS>EPSG:26713</SRS><LatLongBoundingBox minx="-103.87789019829768" miny="44.372335260095554" maxx="-103.62287788915457" maxy="44.502218486214815"/></FeatureType><FeatureType><Name>topp:tasmania_cities</Name><Title>Tasmania cities</Title><Abstract>Cities in Tasmania (actually, just the capital)</Abstract><Keywords>cities, Tasmania</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="145.19754" miny="-43.423512" maxx="148.27298000000002" maxy="-40.852802"/></FeatureType><FeatureType><Name>topp:tasmania_roads</Name><Title>Tasmania roads</Title><Abstract>Main Tasmania roads</Abstract><Keywords>Roads, Tasmania</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="145.19754" miny="-43.423512" maxx="148.27298000000002" maxy="-40.852802"/></FeatureType><FeatureType><Name>topp:tasmania_state_boundaries</Name><Title>Tasmania state boundaries</Title><Abstract>Tasmania state boundaries</Abstract><Keywords>tasmania_state_boundaries, Tasmania, boundaries</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/></FeatureType><FeatureType><Name>topp:tasmania_water_bodies</Name><Title>Tasmania water bodies</Title><Abstract>Tasmania water bodies</Abstract><Keywords>Lakes, Bodies, Australia, Water, Tasmania</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="145.97161899999998" miny="-43.031944" maxx="147.219696" maxy="-41.775558"/></FeatureType><FeatureType><Name>topp:states</Name><Title>USA Population</Title><Abstract>This is some census data on the states.</Abstract><Keywords>census, united, boundaries, state, states</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="-124.731422" miny="24.955967" maxx="-66.969849" maxy="49.371735"/></FeatureType><FeatureType><Name>tiger:giant_polygon</Name><Title>World rectangle</Title><Abstract>A simple rectangular polygon covering most of the world, it\'s only used for the purpose of providing a background (WMS bgcolor could be used instead)</Abstract><Keywords>DS_giant_polygon, giant_polygon</Keywords><SRS>EPSG:4326</SRS><LatLongBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/></FeatureType></FeatureTypeList><ogc:Filter_Capabilities><ogc:Spatial_Capabilities><ogc:Spatial_Operators><ogc:Disjoint/><ogc:Equals/><ogc:DWithin/><ogc:Beyond/><ogc:Intersect/><ogc:Touches/><ogc:Crosses/><ogc:Within/><ogc:Contains/><ogc:Overlaps/><ogc:BBOX/></ogc:Spatial_Operators></ogc:Spatial_Capabilities><ogc:Scalar_Capabilities><ogc:Logical_Operators/><ogc:Comparison_Operators><ogc:Simple_Comparisons/><ogc:Between/><ogc:Like/><ogc:NullCheck/></ogc:Comparison_Operators><ogc:Arithmetic_Operators><ogc:Simple_Arithmetic/><ogc:Functions><ogc:Function_Names><ogc:Function_Name nArgs="1">abs</ogc:Function_Name><ogc:Function_Name nArgs="1">abs_2</ogc:Function_Name><ogc:Function_Name nArgs="1">abs_3</ogc:Function_Name><ogc:Function_Name nArgs="1">abs_4</ogc:Function_Name><ogc:Function_Name nArgs="1">acos</ogc:Function_Name><ogc:Function_Name nArgs="1">Area</ogc:Function_Name><ogc:Function_Name nArgs="1">asin</ogc:Function_Name><ogc:Function_Name nArgs="1">atan</ogc:Function_Name><ogc:Function_Name nArgs="2">atan2</ogc:Function_Name><ogc:Function_Name nArgs="3">between</ogc:Function_Name><ogc:Function_Name nArgs="1">boundary</ogc:Function_Name><ogc:Function_Name nArgs="1">boundaryDimension</ogc:Function_Name><ogc:Function_Name nArgs="2">buffer</ogc:Function_Name><ogc:Function_Name nArgs="3">bufferWithSegments</ogc:Function_Name><ogc:Function_Name nArgs="1">ceil</ogc:Function_Name><ogc:Function_Name nArgs="1">centroid</ogc:Function_Name><ogc:Function_Name nArgs="2">classify</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Average</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Bounds</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Count</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Max</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Median</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Min</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Sum</ogc:Function_Name><ogc:Function_Name nArgs="1">Collection_Unique</ogc:Function_Name><ogc:Function_Name nArgs="2">Concatenate</ogc:Function_Name><ogc:Function_Name nArgs="2">contains</ogc:Function_Name><ogc:Function_Name nArgs="1">convexHull</ogc:Function_Name><ogc:Function_Name nArgs="1">cos</ogc:Function_Name><ogc:Function_Name nArgs="2">crosses</ogc:Function_Name><ogc:Function_Name nArgs="2">dateFormat</ogc:Function_Name><ogc:Function_Name nArgs="2">dateParse</ogc:Function_Name><ogc:Function_Name nArgs="2">difference</ogc:Function_Name><ogc:Function_Name nArgs="1">dimension</ogc:Function_Name><ogc:Function_Name nArgs="2">disjoint</ogc:Function_Name><ogc:Function_Name nArgs="2">distance</ogc:Function_Name><ogc:Function_Name nArgs="1">double2bool</ogc:Function_Name><ogc:Function_Name nArgs="1">endPoint</ogc:Function_Name><ogc:Function_Name nArgs="1">envelope</ogc:Function_Name><ogc:Function_Name nArgs="2">EqualInterval</ogc:Function_Name><ogc:Function_Name nArgs="2">equalsExact</ogc:Function_Name><ogc:Function_Name nArgs="3">equalsExactTolerance</ogc:Function_Name><ogc:Function_Name nArgs="2">equalTo</ogc:Function_Name><ogc:Function_Name nArgs="1">exp</ogc:Function_Name><ogc:Function_Name nArgs="1">exteriorRing</ogc:Function_Name><ogc:Function_Name nArgs="1">floor</ogc:Function_Name><ogc:Function_Name nArgs="1">geometryType</ogc:Function_Name><ogc:Function_Name nArgs="1">geomFromWKT</ogc:Function_Name><ogc:Function_Name nArgs="1">geomLength</ogc:Function_Name><ogc:Function_Name nArgs="2">getGeometryN</ogc:Function_Name><ogc:Function_Name nArgs="1">getX</ogc:Function_Name><ogc:Function_Name nArgs="1">getY</ogc:Function_Name><ogc:Function_Name nArgs="1">getZ</ogc:Function_Name><ogc:Function_Name nArgs="2">greaterEqualThan</ogc:Function_Name><ogc:Function_Name nArgs="2">greaterThan</ogc:Function_Name><ogc:Function_Name nArgs="0">id</ogc:Function_Name><ogc:Function_Name nArgs="2">IEEEremainder</ogc:Function_Name><ogc:Function_Name nArgs="3">if_then_else</ogc:Function_Name><ogc:Function_Name nArgs="11">in10</ogc:Function_Name><ogc:Function_Name nArgs="3">in2</ogc:Function_Name><ogc:Function_Name nArgs="4">in3</ogc:Function_Name><ogc:Function_Name nArgs="5">in4</ogc:Function_Name><ogc:Function_Name nArgs="6">in5</ogc:Function_Name><ogc:Function_Name nArgs="7">in6</ogc:Function_Name><ogc:Function_Name nArgs="8">in7</ogc:Function_Name><ogc:Function_Name nArgs="9">in8</ogc:Function_Name><ogc:Function_Name nArgs="10">in9</ogc:Function_Name><ogc:Function_Name nArgs="1">int2bbool</ogc:Function_Name><ogc:Function_Name nArgs="1">int2ddouble</ogc:Function_Name><ogc:Function_Name nArgs="1">interiorPoint</ogc:Function_Name><ogc:Function_Name nArgs="2">interiorRingN</ogc:Function_Name><ogc:Function_Name nArgs="2">intersection</ogc:Function_Name><ogc:Function_Name nArgs="2">intersects</ogc:Function_Name><ogc:Function_Name nArgs="1">isClosed</ogc:Function_Name><ogc:Function_Name nArgs="1">isEmpty</ogc:Function_Name><ogc:Function_Name nArgs="2">isLike</ogc:Function_Name><ogc:Function_Name nArgs="1">isNull</ogc:Function_Name><ogc:Function_Name nArgs="1">isRing</ogc:Function_Name><ogc:Function_Name nArgs="1">isSimple</ogc:Function_Name><ogc:Function_Name nArgs="1">isValid</ogc:Function_Name><ogc:Function_Name nArgs="3">isWithinDistance</ogc:Function_Name><ogc:Function_Name nArgs="1">length</ogc:Function_Name><ogc:Function_Name nArgs="2">lessEqualThan</ogc:Function_Name><ogc:Function_Name nArgs="2">lessThan</ogc:Function_Name><ogc:Function_Name nArgs="1">log</ogc:Function_Name><ogc:Function_Name nArgs="2">max</ogc:Function_Name><ogc:Function_Name nArgs="2">max_2</ogc:Function_Name><ogc:Function_Name nArgs="2">max_3</ogc:Function_Name><ogc:Function_Name nArgs="2">max_4</ogc:Function_Name><ogc:Function_Name nArgs="2">min</ogc:Function_Name><ogc:Function_Name nArgs="2">min_2</ogc:Function_Name><ogc:Function_Name nArgs="2">min_3</ogc:Function_Name><ogc:Function_Name nArgs="2">min_4</ogc:Function_Name><ogc:Function_Name nArgs="1">not</ogc:Function_Name><ogc:Function_Name nArgs="2">notEqualTo</ogc:Function_Name><ogc:Function_Name nArgs="1">numGeometries</ogc:Function_Name><ogc:Function_Name nArgs="1">numInteriorRing</ogc:Function_Name><ogc:Function_Name nArgs="1">numPoints</ogc:Function_Name><ogc:Function_Name nArgs="2">overlaps</ogc:Function_Name><ogc:Function_Name nArgs="1">parseBoolean</ogc:Function_Name><ogc:Function_Name nArgs="1">parseDouble</ogc:Function_Name><ogc:Function_Name nArgs="1">parseInt</ogc:Function_Name><ogc:Function_Name nArgs="0">pi</ogc:Function_Name><ogc:Function_Name nArgs="2">pointN</ogc:Function_Name><ogc:Function_Name nArgs="2">pow</ogc:Function_Name><ogc:Function_Name nArgs="1">PropertyExists</ogc:Function_Name><ogc:Function_Name nArgs="2">Quantile</ogc:Function_Name><ogc:Function_Name nArgs="0">random</ogc:Function_Name><ogc:Function_Name nArgs="2">relate</ogc:Function_Name><ogc:Function_Name nArgs="3">relatePattern</ogc:Function_Name><ogc:Function_Name nArgs="1">rint</ogc:Function_Name><ogc:Function_Name nArgs="1">round</ogc:Function_Name><ogc:Function_Name nArgs="1">round_2</ogc:Function_Name><ogc:Function_Name nArgs="1">roundDouble</ogc:Function_Name><ogc:Function_Name nArgs="1">sin</ogc:Function_Name><ogc:Function_Name nArgs="1">sqrt</ogc:Function_Name><ogc:Function_Name nArgs="2">StandardDeviation</ogc:Function_Name><ogc:Function_Name nArgs="1">startPoint</ogc:Function_Name><ogc:Function_Name nArgs="2">strConcat</ogc:Function_Name><ogc:Function_Name nArgs="2">strEndsWith</ogc:Function_Name><ogc:Function_Name nArgs="2">strEqualsIgnoreCase</ogc:Function_Name><ogc:Function_Name nArgs="2">strIndexOf</ogc:Function_Name><ogc:Function_Name nArgs="2">strLastIndexOf</ogc:Function_Name><ogc:Function_Name nArgs="1">strLength</ogc:Function_Name><ogc:Function_Name nArgs="2">strMatches</ogc:Function_Name><ogc:Function_Name nArgs="4">strReplace</ogc:Function_Name><ogc:Function_Name nArgs="2">strStartsWith</ogc:Function_Name><ogc:Function_Name nArgs="3">strSubstring</ogc:Function_Name><ogc:Function_Name nArgs="2">strSubstringStart</ogc:Function_Name><ogc:Function_Name nArgs="1">strToLowerCase</ogc:Function_Name><ogc:Function_Name nArgs="1">strToUpperCase</ogc:Function_Name><ogc:Function_Name nArgs="1">strTrim</ogc:Function_Name><ogc:Function_Name nArgs="2">symDifference</ogc:Function_Name><ogc:Function_Name nArgs="1">tan</ogc:Function_Name><ogc:Function_Name nArgs="1">toDegrees</ogc:Function_Name><ogc:Function_Name nArgs="1">toRadians</ogc:Function_Name><ogc:Function_Name nArgs="2">touches</ogc:Function_Name><ogc:Function_Name nArgs="1">toWKT</ogc:Function_Name><ogc:Function_Name nArgs="2">union</ogc:Function_Name><ogc:Function_Name nArgs="2">UniqueInterval</ogc:Function_Name><ogc:Function_Name nArgs="2">within</ogc:Function_Name></ogc:Function_Names></ogc:Functions></ogc:Arithmetic_Operators></ogc:Scalar_Capabilities></ogc:Filter_Capabilities></WFS_Capabilities>';
+ res = parser.read(text);
+
+ t.ok(!res.error, "Parsing XML generated no errors");
+ ft = res.featureTypeList.featureTypes;
+ t.eq(ft.length, 14, "number of feature types correct");
+ t.eq(ft[0]["abstract"], "Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs", "abstract of first feature type correct");
+ t.eq(ft[0]["title"], "Manhattan (NY) landmarks", "title of first feature type correct");
+ t.eq(ft[0]["name"], "poly_landmarks", "name of first feature type correct");
+ t.eq(ft[0]["featureNS"], "http://www.census.gov", "ns of first feature type correct");
+ t.eq(ft[0]["srs"], "EPSG:4326", "srs of first feature type correct");
+
+ var service = res.service;
+ t.eq(service.name, 'WFS', "service name correct");
+ t.eq(service.title, 'GeoServer Web Feature Service', "service title correct");
+ t.eq(service.abstract, 'This is the reference implementation of WFS 1.0.0 and WFS 1.1.0, supports all WFS operations including Transaction.', "service title correct");
+ t.eq(service.keywords[0], 'WFS', "service keyword [0] correct");
+ t.eq(service.keywords[2], 'GEOSERVER', "service keyword [2] correct");
+ t.eq(service.onlineResource, 'http://localhost:80/geoserver/wfs', "service onlineresource correct");
+ t.ok(typeof service.fees == 'undefined', "service fees correct");
+ t.ok(typeof service.accessConstraints == 'undefined', "service accessconstraints correct");
+
+ t.eq(res.capability.request.getfeature.href.post, "http://localhost:80/geoserver/wfs?", "getfeature request post href correct");
+ t.eq(res.capability.request.getfeature.href.get, "http://localhost:80/geoserver/wfs?request=GetFeature", "getfeature request get href correct");
+
+ t.eq(res.capability.request.getfeature.formats[0], "GML2", "getfeature response format [0] correct");
+ t.eq(res.capability.request.getfeature.formats[4], "GML3", "getfeature response format [4] correct");
+
+ // UMN Mapserer, v1.0.0
+ text =
+ '<?xml version="1.0" encoding="ISO-8859-1" ?>' +
+ '<WFS_Capabilities' +
+ ' version="1.0.0"' +
+ ' updateSequence="0"' +
+ ' xmlns="http://www.opengis.net/wfs"' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+ ' xsi:schemaLocation="http://www.opengis.net/wfs http://ogc.dmsolutions.ca/wfs/1.0.0/WFS-capabilities.xsd">' +
+ '' +
+ '<!-- MapServer version 4.0 (development) OUTPUT=GIF OUTPUT=PNG OUTPUT=JPEG OUTPUT=WBMP OUTPUT=PDF OUTPUT=SWF SUPPORTS=PROJ SUPPORTS=FREETYPE SUPPORTS=WMS_SERVER SUPPORTS=WMS_CLIENT SUPPORTS=WFS_SERVER SUPPORTS=WFS_CLIENT INPUT=POSTGIS INPUT=OGR INPUT=GDAL INPUT=SHAPEFILE -->' +
+ '' +
+ '<Service>' +
+ ' <Name>MapServer WFS</Name>' +
+ ' <Title>GMap WMS Demo Server</Title>' +
+ ' <OnlineResource>http://127.0.0.1/cgi-bin/mapserv_40?map=/msroot/apache/htdocs/gmap/htdocs/gmap75_wfs.map&amp;service=WFS&amp;</OnlineResource>' +
+ '</Service>' +
+ '' +
+ '<Capability>' +
+ ' <Request>' +
+ ' <GetCapabilities>' +
+ ' <DCPType>' +
+ ' <HTTP>' +
+ ' <Get onlineResource="http://127.0.0.1/cgi-bin/mapserv_40?map=/msroot/apache/htdocs/gmap/htdocs/gmap75_wfs.map&amp;service=WFS&amp;" />' +
+ ' </HTTP>' +
+ ' </DCPType>' +
+ ' </GetCapabilities>' +
+ '' +
+ ' <DescribeFeatureType>' +
+ ' <SchemaDescriptionLanguage>' +
+ ' <XMLSCHEMA/>' +
+ ' </SchemaDescriptionLanguage>' +
+ ' <DCPType>' +
+ ' <HTTP>' +
+ ' <Get onlineResource="http://127.0.0.1/cgi-bin/mapserv_40?map=/msroot/apache/htdocs/gmap/htdocs/gmap75_wfs.map&amp;service=WFS&amp;" />' +
+ ' </HTTP>' +
+ ' </DCPType>' +
+ '' +
+ ' </DescribeFeatureType>' +
+ ' <GetFeature>' +
+ ' <ResultFormat>' +
+ ' <GML2/>' +
+ ' </ResultFormat>' +
+ ' <DCPType>' +
+ ' <HTTP>' +
+ ' <Get onlineResource="http://127.0.0.1/cgi-bin/mapserv_40?map=/msroot/apache/htdocs/gmap/htdocs/gmap75_wfs.map&amp;service=WFS&amp;" />' +
+ ' </HTTP>' +
+ '' +
+ ' </DCPType>' +
+ ' </GetFeature>' +
+ ' </Request>' +
+ '</Capability>' +
+ '' +
+ '<FeatureTypeList>' +
+ ' <Operations>' +
+ ' <Query/>' +
+ ' </Operations>' +
+ ' <FeatureType>' +
+ '' +
+ ' <Name>park</Name>' +
+ ' <Title>Parks</Title>' +
+ ' <SRS>EPSG:42304</SRS>' +
+ ' <LatLongBoundingBox minx="-173.433" miny="41.4271" maxx="-13.3643" maxy="83.7466" />' +
+ ' </FeatureType>' +
+ ' <FeatureType>' +
+ ' <Name>popplace</Name>' +
+ '' +
+ ' <Title>Cities</Title>' +
+ ' <SRS>EPSG:42304</SRS>' +
+ ' <LatLongBoundingBox minx="-172.301" miny="36.3541" maxx="-12.9698" maxy="83.4832" />' +
+ ' </FeatureType>' +
+ '</FeatureTypeList>' +
+ '' +
+ '<ogc:Filter_Capabilities>' +
+ ' <ogc:Spatial_Capabilities>' +
+ ' <ogc:Spatial_Operators>' +
+ '' +
+ ' <ogc:BBOX/>' +
+ ' </ogc:Spatial_Operators>' +
+ ' </ogc:Spatial_Capabilities>' +
+ ' <!-- Provide some ScalarCapabilties just for the XML to validate against the schema even if we don\'t support any. (Yes this is stupid!) -->' +
+ ' <ogc:Scalar_Capabilities>' +
+ ' <ogc:Logical_Operators />' +
+ ' </ogc:Scalar_Capabilities>' +
+ '</ogc:Filter_Capabilities>' +
+ '' +
+ '</WFS_Capabilities>';
+ res = parser.read(text);
+ var ft = res.featureTypeList.featureTypes;
+ t.eq(ft.length, 2, "number of feature types correct");
+ t.eq(ft[0]["title"], "Parks", "title of first feature type correct");
+ t.eq(ft[0]["name"], "park", "name of first feature type correct");
+ t.eq(ft[0]["srs"], "EPSG:42304", "srs of first feature type correct");
+
+ var service = res.service;
+ t.eq(service.name, 'MapServer WFS', "service name correct");
+ t.eq(service.title, 'GMap WMS Demo Server', "service title correct");
+ t.eq(service.onlineResource, 'http://127.0.0.1/cgi-bin/mapserv_40?map=/msroot/apache/htdocs/gmap/htdocs/gmap75_wfs.map&service=WFS&', "service onlineresource correct");
+ t.eq(res.capability.request.getfeature.href.get, "http://127.0.0.1/cgi-bin/mapserv_40?map=/msroot/apache/htdocs/gmap/htdocs/gmap75_wfs.map&service=WFS&", "getfeature request get href correct");
+ t.eq(res.capability.request.getfeature.formats[0], "GML2", "getfeature response format [0] correct");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFSDescribeFeatureType.html b/misc/openlayers/tests/Format/WFSDescribeFeatureType.html
new file mode 100644
index 0000000..77f348d
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFSDescribeFeatureType.html
@@ -0,0 +1,436 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_WFSDescribeFeatureType(t) {
+ t.plan(39);
+
+ var parser = new OpenLayers.Format.WFSDescribeFeatureType();
+
+ // single typeName from UMN Mapserver
+ var text =
+ '<?xml version="1.0" encoding="ISO-8859-1" ?>' +
+ '<schema' +
+ ' targetNamespace="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:rws="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' elementFormDefault="qualified" version="0.1" >' +
+ ' <import namespace="http://www.opengis.net/gml"' +
+ ' schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd" />' +
+ ' <element name="AAA64" ' +
+ ' type="rws:AAA64Type" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="AAA64Type">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiLineStringPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' <element name="OBJECTID" type="string"/>' +
+ ' <element name="ROUTE" type="string"/>' +
+ ' <element name="ROUTE_CH" type="string"/>' +
+ ' <element name="COUNT" type="string"/>' +
+ ' <element name="BEHEERDER" type="string"/>' +
+ ' <element name="LENGTH" type="string"/>' +
+ ' <element name="SHAPE" type="string"/>' +
+ ' <element name="SE_ANNO_CAD_DATA" type="string"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ '</schema>';
+ var res = parser.read(text);
+
+ t.eq(res.featureTypes.length, 1,
+ "There is only 1 typename, so length should be 1");
+
+ t.eq(res.featureTypes[0].properties[0].type, 'gml:MultiLineStringPropertyType',
+ "The first attribute is of type multi line string");
+
+ t.eq(res.featureTypes[0].properties[2].name, 'ROUTE',
+ "The third attribute is named ROUTE");
+
+ t.eq(res.featureTypes[0].properties[2].type, 'string',
+ "The third attribute is of type string");
+
+ // three typeNames in one response from UMN Mapserver
+ text =
+ '<?xml version="1.0" encoding="ISO-8859-1" ?>' +
+ '<schema' +
+ ' targetNamespace="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:rws="http://mapserver.gis.umn.edu/mapserver" ' +
+ ' xmlns:ogc="http://www.opengis.net/ogc"' +
+ ' xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' elementFormDefault="qualified" version="0.1" >' +
+ ' <import namespace="http://www.opengis.net/gml"' +
+ ' schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd" />' +
+ ' <element name="KGNAT.VKUNSTWERK" ' +
+ ' type="rws:KGNAT.VKUNSTWERKType" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="KGNAT.VKUNSTWERKType">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiPolygonPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' <element name="OBJECTID" type="string"/>' +
+ ' <element name="OBJECTSUBCATEGORIE" type="string"/>' +
+ ' <element name="DIENSTCODE" type="string"/>' +
+ ' <element name="DISTRICTNAAM" type="string"/>' +
+ ' <element name="CODEBPN" type="string"/>' +
+ ' <element name="WSD" type="string"/>' +
+ ' <element name="SUBCAT" type="string"/>' +
+ ' <element name="ZIJDE" type="string"/>' +
+ ' <element name="KM" type="string"/>' +
+ ' <element name="ELEMENTCODE" type="string"/>' +
+ ' <element name="COMPLEXCODE" type="string"/>' +
+ ' <element name="BEHEEROBJECTCODE" type="string"/>' +
+ ' <element name="BEGINDATUM" type="string"/>' +
+ ' <element name="NAAMCONTACTPERSOON" type="string"/>' +
+ ' <element name="KMTOT" type="string"/>' +
+ ' <element name="HOOFDWATERSYSTEEM" type="string"/>' +
+ ' <element name="WATERSYSTEEMNAAM" type="string"/>' +
+ ' <element name="OBJECTNAAM" type="string"/>' +
+ ' <element name="HERKOMST" type="string"/>' +
+ ' <element name="BEHEERSREGIME" type="string"/>' +
+ ' <element name="VERSIE" type="string"/>' +
+ ' <element name="KWALITEITSNIVEAU" type="string"/>' +
+ ' <element name="STICHTINGSJAAR" type="string"/>' +
+ ' <element name="OBJECTTYPE" type="string"/>' +
+ ' <element name="OPMERKING" type="string"/>' +
+ ' <element name="OPPERVLAKTE" type="string"/>' +
+ ' <element name="SE_ANNO_CAD_DATA" type="string"/>' +
+ ' <element name="SHAPE" type="string"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ ' <element name="KGNAT.LKUNSTWERK" ' +
+ ' type="rws:KGNAT.LKUNSTWERKType" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="KGNAT.LKUNSTWERKType">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiLineStringPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' <element name="OBJECTID" type="string"/>' +
+ ' <element name="OBJECTSUBCATEGORIE" type="string"/>' +
+ ' <element name="DIENSTCODE" type="string"/>' +
+ ' <element name="DISTRICTNAAM" type="string"/>' +
+ ' <element name="CODEBPN" type="string"/>' +
+ ' <element name="WSD" type="string"/>' +
+ ' <element name="SUBCAT" type="string"/>' +
+ ' <element name="ZIJDE" type="string"/>' +
+ ' <element name="KM" type="string"/>' +
+ ' <element name="ELEMENTCODE" type="string"/>' +
+ ' <element name="COMPLEXCODE" type="string"/>' +
+ ' <element name="BEHEEROBJECTCODE" type="string"/>' +
+ ' <element name="BEGINDATUM" type="string"/>' +
+ ' <element name="NAAMCONTACTPERSOON" type="string"/>' +
+ ' <element name="KMTOT" type="string"/>' +
+ ' <element name="HOOFDWATERSYSTEEM" type="string"/>' +
+ ' <element name="WATERSYSTEEMNAAM" type="string"/>' +
+ ' <element name="OBJECTNAAM" type="string"/>' +
+ ' <element name="HERKOMST" type="string"/>' +
+ ' <element name="BEHEERSREGIME" type="string"/>' +
+ ' <element name="VERSIE" type="string"/>' +
+ ' <element name="KWALITEITSNIVEAU" type="string"/>' +
+ ' <element name="STICHTINGSJAAR" type="string"/>' +
+ ' <element name="OBJECTTYPE" type="string"/>' +
+ ' <element name="OPMERKING" type="string"/>' +
+ ' <element name="LENGTE" type="string"/>' +
+ ' <element name="SE_ANNO_CAD_DATA" type="string"/>' +
+ ' <element name="SHAPE" type="string"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ ' <element name="KGNAT.PKUNSTWERK" ' +
+ ' type="rws:KGNAT.PKUNSTWERKType" ' +
+ ' substitutionGroup="gml:_Feature" />' +
+ ' <complexType name="KGNAT.PKUNSTWERKType">' +
+ ' <complexContent>' +
+ ' <extension base="gml:AbstractFeatureType">' +
+ ' <sequence>' +
+ ' <element name="geometry" type="gml:MultiPointPropertyType" minOccurs="0" maxOccurs="1"/>' +
+ ' <element name="OBJECTID" type="string"/>' +
+ ' <element name="OBJECTSUBCATEGORIE" type="string"/>' +
+ ' <element name="DIENSTCODE" type="string"/>' +
+ ' <element name="DISTRICTNAAM" type="string"/>' +
+ ' <element name="CODEBPN" type="string"/>' +
+ ' <element name="WSD" type="string"/>' +
+ ' <element name="SUBCAT" type="string"/>' +
+ ' <element name="ZIJDE" type="string"/>' +
+ ' <element name="KM" type="string"/>' +
+ ' <element name="ELEMENTCODE" type="string"/>' +
+ ' <element name="COMPLEXCODE" type="string"/>' +
+ ' <element name="BEHEEROBJECTCODE" type="string"/>' +
+ ' <element name="BEGINDATUM" type="string"/>' +
+ ' <element name="NAAMCONTACTPERSOON" type="string"/>' +
+ ' <element name="KMTOT" type="string"/>' +
+ ' <element name="HOOFDWATERSYSTEEM" type="string"/>' +
+ ' <element name="WATERSYSTEEMNAAM" type="string"/>' +
+ ' <element name="OBJECTNAAM" type="string"/>' +
+ ' <element name="HERKOMST" type="string"/>' +
+ ' <element name="BEHEERSREGIME" type="string"/>' +
+ ' <element name="VERSIE" type="string"/>' +
+ ' <element name="KWALITEITSNIVEAU" type="string"/>' +
+ ' <element name="STICHTINGSJAAR" type="string"/>' +
+ ' <element name="OBJECTTYPE" type="string"/>' +
+ ' <element name="OPMERKING" type="string"/>' +
+ ' <element name="X" type="string"/>' +
+ ' <element name="Y" type="string"/>' +
+ ' <element name="SE_ANNO_CAD_DATA" type="string"/>' +
+ ' <element name="SHAPE" type="string"/>' +
+ ' </sequence>' +
+ ' </extension>' +
+ ' </complexContent>' +
+ ' </complexType>' +
+ '</schema>';
+
+ parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ res = parser.read(text);
+
+ t.eq(res.featureTypes.length, 3,
+ "There are 3 typenames, so length should be 3");
+
+ t.eq(res.featureTypes[0].typeName, 'KGNAT.VKUNSTWERK',
+ "There name of the first typename is KGNAT.VKUNSTWERK");
+
+ t.eq(res.featureTypes[2].properties.length, 30,
+ "We expect 30 attributes in the third typename");
+
+ t.eq(res.featureTypes[2].properties[1].name, 'OBJECTID',
+ "The second attribute has name OBJECTID");
+
+ t.eq(res.featureTypes[2].properties[1].type, 'string',
+ "The second attribute has type string");
+
+ t.eq(res.targetNamespace, 'http://mapserver.gis.umn.edu/mapserver',
+ "The targetNamespace should be http://mapserver.gis.umn.edu/mapserver");
+
+ t.eq(res.targetPrefix, 'rws', "the targetPrefix should be rws");
+
+ // response from Ionic WFS, taken from:
+ // http://webservices.ionicsoft.com/ionicweb/wfs/BOSTON_ORA?service=WFS&request=DescribeFeatureType&version=1.0.0&typename=wfs:highways
+ text =
+ "<?xml version='1.0' encoding='utf-8' ?>" +
+ ' <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" xmlns:wfs="http://www.ionicsoft.com/wfs" targetNamespace="http://www.ionicsoft.com/wfs" xmlns:xlink="http://www.w3.org/1999/xlink" elementFormDefault="qualified" version="0.1">' +
+ ' <xsd:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>' +
+ ' <xsd:element name="highways" substitutionGroup="gml:_Feature" type="wfs:highways"/>' +
+ ' <xsd:complexType name="highways">' +
+ ' <xsd:complexContent>' +
+ ' <xsd:extension base="gml:AbstractFeatureType">' +
+ ' <xsd:sequence>' +
+ ' <xsd:element name="ROUTE_" minOccurs="0" nillable="true" type="xsd:int"/>' +
+ ' <xsd:element name="ROUTE_ID" minOccurs="0" nillable="true" type="xsd:int"/>' +
+ ' <xsd:element name="RT_NUMBER" minOccurs="0" nillable="true" type="xsd:string"/>' +
+ ' <xsd:element name="GEOMETRY" minOccurs="0" nillable="true" type="gml:GeometryAssociationType"/>' +
+ ' </xsd:sequence>' +
+ ' </xsd:extension>' +
+ ' </xsd:complexContent>' +
+ ' </xsd:complexType>' +
+ ' </xsd:schema>';
+
+ parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ res = parser.read(text);
+
+ t.eq(res.featureTypes.length, 1,
+ "There is 1 typename, so length should be 1");
+
+ t.eq(res.featureTypes[0].typeName, "highways",
+ "The name of the typename is highways");
+
+ t.eq(res.featureTypes[0].properties.length, 4,
+ "We expect 4 attributes in the first typename");
+
+ t.eq(res.featureTypes[0].properties[1].name, 'ROUTE_ID',
+ "The second attribute has name ROUTE_ID");
+
+ t.eq(res.featureTypes[0].properties[1].type, 'xsd:int',
+ "The second attribute has type integer");
+
+ t.eq(parseInt(res.featureTypes[0].properties[1].minOccurs), 0,
+ "The second attribute has minOccurs 0");
+
+ t.eq(res.targetNamespace, 'http://www.ionicsoft.com/wfs',
+ "The targetNamespace should be http://www.ionicsoft.com/wfs");
+
+ t.eq(res.targetPrefix, 'wfs', "the targetPrefix should be wfs");
+
+
+ // GeoServer tests
+
+ function geoServerTests(text) {
+ parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ res = parser.read(text);
+
+ t.eq(res.featureTypes.length, 1,
+ "There is 1 typename, so length should be 1");
+
+ t.eq(res.featureTypes[0].typeName, "railroads",
+ "The name of the typeName is railroads");
+
+ t.eq(res.featureTypes[0].properties.length, 2,
+ "We expect 2 attributes in the typename");
+
+ t.eq(res.featureTypes[0].properties[0].name, 'cat',
+ "The first attribute has name cat");
+
+ t.eq(res.featureTypes[0].properties[0].type.split(":")[1], 'long',
+ "The first attribute has type long with a prefix");
+
+ t.eq(res.featureTypes[0].properties[0].localType, 'long',
+ "The first attribute has localType long");
+
+ t.eq(res.targetNamespace, 'http://opengeo.org',
+ "The targetNamespace should be http://opengeo.org");
+
+ t.eq(res.targetPrefix, 'opengeo', "the targetPrefix should be opengeo");
+ }
+
+
+ // Read Geoserver WFS 1.0.0 response
+ // Taken from: http://demo.opengeo.org/geoserver/wfs?service=WFS&request=DescribeFeatureType&typename=opengeo:railroads&version=1.0.0
+ text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<xs:schema targetNamespace="http://opengeo.org" xmlns:opengeo="http://opengeo.org" xmlns:gml="http://www.opengis.net/gml" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0">' +
+ ' <xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://demo.opengeo.org:80/geoserver/schemas/gml/2.1.2.1/feature.xsd"/>' +
+ ' <xs:complexType xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/2001/XMLSchema" name="railroads_Type">' +
+ ' <xs:complexContent>' +
+ ' <xs:extension base="gml:AbstractFeatureType">' +
+ ' <xs:sequence>' +
+ ' <xs:element name="cat" minOccurs="0" nillable="true" type="xs:long"/>' +
+ ' <xs:element name="the_geom" minOccurs="0" nillable="true" type="gml:MultiLineStringPropertyType"/>' +
+ ' </xs:sequence>' +
+ ' </xs:extension>' +
+ ' </xs:complexContent>' +
+ ' </xs:complexType>' +
+ ' <xs:element name="railroads" type="opengeo:railroads_Type" substitutionGroup="gml:_Feature"/>' +
+ '</xs:schema>';
+
+ geoServerTests(text);
+
+
+ // Read GeoServer WFS 1.1.0 response
+ // taken from http://demo.opengeo.org/geoserver/wfs?service=WFS&request=DescribeFeatureType&typename=opengeo:railroads&version=1.1.0
+ text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ ' <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" xmlns:opengeo="http://opengeo.org" elementFormDefault="qualified" targetNamespace="http://opengeo.org">' +
+ ' <xsd:import namespace="http://www.opengis.net/gml" schemaLocation="http://demo.opengeo.org:80/geoserver/wfs/schemas/gml/3.1.1/base/gml.xsd"/>' +
+ ' <xsd:complexType name="railroadsType">' +
+ ' <xsd:complexContent>' +
+ ' <xsd:extension base="gml:AbstractFeatureType">' +
+ ' <xsd:sequence>' +
+ ' <xsd:element maxOccurs="1" minOccurs="0" name="cat" nillable="true" type="xsd:long"/>' +
+ ' <xsd:element maxOccurs="1" minOccurs="0" name="the_geom" nillable="true" type="gml:MultiLineStringPropertyType"/>' +
+ ' </xsd:sequence>' +
+ ' </xsd:extension>' +
+ ' </xsd:complexContent>' +
+ ' </xsd:complexType>' +
+ ' <xsd:element name="railroads" substitutionGroup="gml:_Feature" type="opengeo:railroadsType"/>' +
+ ' </xsd:schema>';
+
+ geoServerTests(text);
+
+ // Another GeoServer response with type restrictions
+ // taken from http://sigma.openplans.org/geoserver/wfs?service=WFS&request=DescribeFeatureType&typename=topp:states&version=1.0.0
+ text = '<?xml version="1.0" encoding="UTF-8"?><xs:schema targetNamespace="http://www.openplans.org/topp" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml" xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0"><xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://sigma.openplans.org:80/geoserver/schemas/gml/2.1.2.1/feature.xsd"/><xs:complexType xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.w3.org/2001/XMLSchema" name="states_Type"><xs:complexContent><xs:extension base="gml:AbstractFeatureType"><xs:sequence><xs:element name="the_geom" minOccurs="0" nillable="true" type="gml:MultiPolygonPropertyType"/><xs:element name="STATE_NAME" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="25"/></xs:restriction></xs:simpleType></xs:element><xs:element name="STATE_FIPS" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="2"/></xs:restriction></xs:simpleType></xs:element><xs:element name="SUB_REGION" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="7"/></xs:restriction></xs:simpleType></xs:element><xs:element name="STATE_ABBR" minOccurs="0" nillable="true"><xs:simpleType><xs:restriction base="xs:string"><xs:maxLength value="2"/></xs:restriction></xs:simpleType></xs:element><xs:element name="LAND_KM" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="WATER_KM" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="PERSONS" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="FAMILIES" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="HOUSHOLD" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="MALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="FEMALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="WORKERS" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="DRVALONE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="CARPOOL" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="PUBTRANS" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="EMPLOYED" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="UNEMPLOY" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="SERVICE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="MANUAL" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="P_MALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="P_FEMALE" minOccurs="0" nillable="true" type="xs:double"/><xs:element name="SAMP_POP" minOccurs="0" nillable="true" type="xs:double"/></xs:sequence></xs:extension></xs:complexContent></xs:complexType><xs:element name="states" type="topp:states_Type" substitutionGroup="gml:_Feature"/></xs:schema>';
+
+ parser = new OpenLayers.Format.WFSDescribeFeatureType();
+ res = parser.read(text);
+
+ t.eq(res.featureTypes[0].properties[1].name, "STATE_NAME",
+ "name of 2nd property of 1st featureType should be 'STATE_NAME'");
+
+ t.eq(res.featureTypes[0].properties[1].type, "xs:string",
+ "type of 2nd property of 1st featureType should be 'xs:string'");
+
+ t.eq(res.featureTypes[0].properties[1].localType, "string",
+ "localType of 2nd property of 1st featureType should be 'string'");
+
+ t.eq(res.featureTypes[0].properties[1].restriction.maxLength, "25",
+ "the maxLength restriction should be 25");
+ }
+
+ function test_readRestriction(t) {
+ t.plan(2);
+ var text =
+ '<restriction xmlns="http://www.w3.org/2001/XMLSchema" base="xs:string">' +
+ ' <enumeration value="One"/>' +
+ ' <enumeration value="Two"/>' +
+ '</restriction>';
+ var doc = OpenLayers.Format.XML.prototype.read(text).documentElement;
+ var obj = {};
+ new OpenLayers.Format.WFSDescribeFeatureType().readRestriction(doc, obj);
+
+ t.eq(obj.enumeration.length, 2, "enumeration has a length of 2");
+ t.eq(obj.enumeration[1], "Two", "2nd enumeration value is 'Two'");
+ // other functionality of readRestriction already tested in the last
+ // GeoServer example above
+ }
+
+ function test_read_exception(t) {
+ t.plan(1);
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<ows:ExceptionReport version="1.0.0"' +
+ ' xsi:schemaLocation="http://www.opengis.net/ows http://localhost:8080/geoserver/schemas/ows/1.0.0/owsExceptionReport.xsd"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows">' +
+ ' <ows:Exception exceptionCode="NoApplicableCode">' +
+ ' <ows:ExceptionText>Could not find type: {http://geonode.org/}_map_4_annotations</ows:ExceptionText>' +
+ ' </ows:Exception>' +
+ '</ows:ExceptionReport>';
+ var format = new OpenLayers.Format.WFSDescribeFeatureType();
+ var obj = format.read(text);
+ t.ok(!!obj.error, "Error reported correctly");
+ }
+
+ function test_read_annotation(t) {
+ t.plan(2);
+ var text =
+ '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"' +
+ ' xmlns:analytics="http://opengeo.org/analytics" xmlns:cite="http://www.opengeospatial.net/cite"' +
+ ' xmlns:gml="http://www.opengis.net/gml" xmlns:it.geosolutions="http://www.geo-solutions.it"' +
+ ' xmlns:nurc="http://www.nurc.nato.int" xmlns:og="http://opengeo.org"' +
+ ' xmlns:sde="http://geoserver.sf.net" xmlns:sf="http://www.openplans.org/spearfish"' +
+ ' xmlns:tiger="http://www.census.gov" xmlns:tike="http://opengeo.org/#tike"' +
+ ' xmlns:topp="http://www.openplans.org/topp" xmlns:usgs="http://www.usgs.gov/"' +
+ ' xmlns:za="http://opengeo.org/za" elementFormDefault="qualified"' +
+ ' targetNamespace="http://www.openplans.org/topp">' +
+ ' <xsd:import namespace="http://www.opengis.net/gml"' +
+ ' schemaLocation="http://demo.opengeo.org/geoserver/schemas/gml/3.1.1/base/gml.xsd"/>' +
+ ' <xsd:complexType name="statesType">' +
+ ' <xsd:complexContent>' +
+ ' <xsd:extension base="gml:AbstractFeatureType">' +
+ ' <xsd:sequence>' +
+ ' <xsd:element maxOccurs="1" minOccurs="0" name="PERSONS" nillable="true" type="xsd:double">' +
+ ' <xsd:annotation>' +
+ ' <xsd:appinfo>{"title":{"en":"Population"}}</xsd:appinfo>' +
+ ' <xsd:documentation xml:lang="en"> Number of persons living in the state' +
+ ' </xsd:documentation>' +
+ ' </xsd:annotation>' +
+ ' </xsd:element>' +
+ ' </xsd:sequence>' +
+ ' </xsd:extension>' +
+ ' </xsd:complexContent>' +
+ ' </xsd:complexType>' +
+ ' <xsd:element name="states" substitutionGroup="gml:_Feature" type="topp:statesType"/>' +
+ '</xsd:schema>';
+ var format = new OpenLayers.Format.WFSDescribeFeatureType();
+ var res = format.read(text);
+ var property = res.featureTypes[0].properties[0];
+ t.eq(property.annotation.appinfo[0], '{"title":{"en":"Population"}}', "appinfo read correctly");
+ t.eq(property.annotation.documentation[0], {lang: "en", textContent: 'Number of persons living in the state'}, "documentation read correctly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFST.html b/misc/openlayers/tests/Format/WFST.html
new file mode 100644
index 0000000..9623b05
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFST.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.WFST();
+ t.ok(format instanceof OpenLayers.Format.WFST.v1_0_0, "constructor returns instance with default versioned format");
+
+ format = new OpenLayers.Format.WFST({
+ version: "1.1.0"
+ });
+ t.ok(format instanceof OpenLayers.Format.WFST.v1_1_0, "constructor returns instance with custom versioned format");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFST/v1.html b/misc/openlayers/tests/Format/WFST/v1.html
new file mode 100644
index 0000000..6cfb1ca
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFST/v1.html
@@ -0,0 +1,455 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read(t) {
+ t.plan(1);
+
+ var data = readXML("FeatureCollection");
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states"
+ });
+ var features = format.read(data);
+
+ t.eq(features.length, 1, "number of features read from FeatureCollection is correct");
+ }
+
+ function test_write(t) {
+
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ featurePrefix: "topp",
+ geometryName: "the_geom"
+ });
+
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(1,2),
+ {foo: "bar"}
+ );
+
+ var insertFeature = feature.clone();
+ // null value does not show up in insert
+ insertFeature.attributes.nul = null;
+ insertFeature.state = OpenLayers.State.INSERT;
+ var updateFeature = feature.clone();
+ // undefined value means don't create a Property element
+ updateFeature.attributes.unwritten = undefined;
+ // null value gets Property element with no Value
+ updateFeature.attributes.nul = null;
+ updateFeature.fid = "fid.42";
+ updateFeature.state = OpenLayers.State.UPDATE;
+ var deleteFeature = feature.clone();
+ deleteFeature.state = OpenLayers.State.DELETE;
+ deleteFeature.fid = "fid.37";
+
+ t.plan(8);
+ var snippets = {
+ "GetFeature": {handle: "handle_g", maxFeatures: 1, outputFormat: 'json'},
+ "Transaction": {handle: "handle_t"},
+ "Insert": {feature: insertFeature, options: {handle: "handle_i"}},
+ "Update": {feature: updateFeature, options: {handle: "handle_u"}},
+ "Delete": {feature: deleteFeature, options: {handle: "handle_d"}}
+ }
+
+ var arg;
+ for(var snippet in snippets) {
+ arg = snippets[snippet]
+ var expected = readXML(snippet);
+ var got = format.writers["wfs"][snippet].apply(format, [arg]);
+ t.xml_eq(got, expected, snippet + " request created correctly");
+ }
+
+ updateFeature.modified = {geometry: updateFeature.geometry.clone()};
+ updateFeature.geometry = new OpenLayers.Geometry.Point(2,3);
+ var expected = readXML("UpdateModified");
+ var got = format.writers["wfs"]["Update"].apply(format, [{feature: updateFeature}]);
+ t.xml_eq(got, expected, "Update request for feature with modified geometry created correctly");
+
+ updateFeature.modified.attributes = {foo: "bar"};
+ updateFeature.attributes.foo = "baz";
+ delete updateFeature.modified.geometry;
+ var expected = readXML("UpdateModifiedNoGeometry");
+ var got = format.writers["wfs"]["Update"].apply(format, [{feature: updateFeature}]);
+ t.xml_eq(got, expected, "Update request for feature with no modified geometry but modified attributes created correctly");
+
+ // test for a feature that originally had a null geometry and a null value for the attribute
+ updateFeature.modified = {attributes: {foo: null, nul: "nul"}, geometry: null};
+ updateFeature.attributes.foo = "bar";
+ updateFeature.geometry = new OpenLayers.Geometry.Point(2,3);
+ var expected = readXML("UpdateModified");
+ var got = format.writers["wfs"]["Update"].apply(format, [{feature: updateFeature}]);
+ t.xml_eq(got, expected, "Update request for feature with modified geometry created correctly even if original geometry was null");
+ }
+
+ function test_writeNative(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ version: "1.1.0",
+ featurePrefix: "topp",
+ geometryName: null
+ });
+ var output = format.write(null, {nativeElements: [
+ {
+ vendorId: "ORACLE",
+ safeToIgnore: true,
+ value: "ALTER SESSION ENABLE PARALLEL DML"
+ }, {
+ vendorId: "ORACLE",
+ safeToIgnore: false,
+ value: "Another native line goes here"
+ }]
+ });
+ var expected = '<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.1.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><wfs:Native vendorId="ORACLE" safeToIgnore="true">ALTER SESSION ENABLE PARALLEL DML</wfs:Native><wfs:Native vendorId="ORACLE" safeToIgnore="false">Another native line goes here</wfs:Native></wfs:Transaction>';
+ t.xml_eq(output, expected, "Native elements written out correctly");
+ }
+
+ function test_write_no_geometry(t) {
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ featurePrefix: "topp",
+ geometryName: null
+ });
+
+ var feature = new OpenLayers.Feature.Vector(null, {foo: "bar"});
+ feature.state = OpenLayers.State.UPDATE;
+ feature.fid = "fid.36";
+
+ t.plan(1);
+ var snippets = {
+ "UpdateNoGeometry": {feature: feature}
+ }
+
+ var arg;
+ for(var snippet in snippets) {
+ arg = snippets[snippet]
+ var expected = readXML(snippet);
+ var got = format.writers["wfs"]["Update"].apply(format, [arg]);
+ t.xml_eq(got, expected, snippet + " request without geometry created correctly");
+ }
+ }
+
+ function test_setFilterProperty(t) {
+ t.plan(2);
+ var format = new OpenLayers.Format.WFST({
+ geometryName: "foo"
+ });
+ var filter = new OpenLayers.Filter.Logical({
+ type: OpenLayers.Filter.Logical.AND,
+ filters: [new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(1,2,3,4)
+ }), new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.DWITHIN,
+ property: "bar",
+ value: new OpenLayers.Geometry.Point(1,2),
+ distance: 10
+ })]
+ });
+ format.setFilterProperty(filter);
+ t.eq(filter.filters[0].property, "foo", "property set if not set on filter");
+ t.eq(filter.filters[1].property, "bar", "property not set if set on filter");
+ }
+
+ function test_update_null_geometry(t) {
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ featurePrefix: "topp",
+ geometryName: "the_geom"
+ });
+
+ var feature = new OpenLayers.Feature.Vector(null, {foo: "bar"});
+ feature.state = OpenLayers.State.UPDATE;
+ feature.fid = "fid.36";
+
+ t.plan(1);
+ var snippets = {
+ "UpdateNullGeometry": {feature: feature}
+ };
+
+ var arg;
+ for (var snippet in snippets) {
+ arg = snippets[snippet]
+ var expected = readXML(snippet);
+ var got = format.writers["wfs"]["Update"].apply(format, [arg]);
+ t.xml_eq(got, expected, snippet + " request with null geometry created correctly");
+ }
+ }
+
+ function test_write_multiple(t) {
+
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: ["states", "cities"],
+ featurePrefix: "topp",
+ geometryName: "the_geom"
+ });
+
+ t.plan(1);
+ var snippets = {
+ "GetFeatureMultiple": {}
+ }
+
+ var arg;
+ for(var snippet in snippets) {
+ arg = snippets[snippet]
+ var expected = readXML(snippet);
+ var got = format.writers["wfs"]["GetFeature"].apply(format, [arg]);
+ t.xml_eq(got, expected, snippet + " request created correctly with multiple typenames");
+ }
+ }
+
+ function test_write_multi(t) {
+ t.plan(2);
+ var format = new OpenLayers.Format.WFST({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ featurePrefix: "topp",
+ geometryName: "the_geom"
+ });
+
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(1,2),
+ {foo: "bar"}
+ );
+
+ var insertFeature = feature.clone();
+ // null value does not show up in insert
+ insertFeature.attributes.nul = null;
+ insertFeature.state = OpenLayers.State.INSERT;
+ var updateFeature = feature.clone();
+ // undefined value means don't create a Property element
+ updateFeature.attributes.unwritten = undefined;
+ // null value gets Property element with no Value
+ updateFeature.attributes.nul = null;
+ updateFeature.fid = "fid.42";
+ updateFeature.state = OpenLayers.State.UPDATE;
+ var features = [insertFeature, updateFeature];
+
+ var expected = readXML("TransactionMulti");
+ var geomTypes = OpenLayers.Util.extend({}, format.geometryTypes);
+ var got = format.writers["wfs"]["Transaction"].apply(format, [{
+ features: features,
+ options: {multi: true}}
+ ]);
+ t.xml_eq(got, expected, "Transaction request with multi option created correctly");
+ t.eq(format.geometryTypes, geomTypes, "geometry types unchanged after write with multi option");
+ }
+
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return new OpenLayers.Format.XML().read(xml).documentElement;
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+
+<div id="FeatureCollection"><!--
+<wfs:FeatureCollection xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:gml="http://www.opengis.net/gml">
+ <gml:featureMember>
+ <topp:states fid="states.3">
+ <topp:the_geom>
+ <gml:MultiPolygon srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
+ <gml:polygonMember>
+ <gml:Polygon>
+ <gml:outerBoundaryIs>
+ <gml:LinearRing>
+ <gml:coordinates decimal="." cs="," ts=" ">-75.70742,38.557476 -75.71106,38.649551 -75.724937,38.83017 -75.752922,39.141548 -75.761658,39.247753 -75.764664,39.295849 -75.772697,39.383007 -75.791435,39.723755 -75.775269,39.724442 -75.745934,39.774818 -75.695114,39.820347 -75.644341,39.838196 -75.583794,39.840008 -75.470345,39.826435 -75.42083,39.79887 -75.412117,39.789658 -75.428009,39.77813 -75.460754,39.763248 -75.475128,39.741718 -75.476334,39.719971 -75.489639,39.714745 -75.610725,39.612793 -75.562996,39.566723 -75.590187,39.463768 -75.515572,39.36694 -75.402481,39.257637 -75.397728,39.073036 -75.324852,39.012386 -75.307899,38.945911 -75.190941,38.80867 -75.083138,38.799812 -75.045998,38.44949 -75.068298,38.449963 -75.093094,38.450451 -75.350204,38.455208 -75.69915,38.463066 -75.70742,38.557476</gml:coordinates>
+ </gml:LinearRing>
+ </gml:outerBoundaryIs>
+ </gml:Polygon>
+ </gml:polygonMember>
+ </gml:MultiPolygon>
+ </topp:the_geom>
+ <topp:STATE_NAME>Delaware</topp:STATE_NAME>
+ <topp:STATE_FIPS>10</topp:STATE_FIPS>
+ <topp:SUB_REGION>S Atl</topp:SUB_REGION>
+ <topp:STATE_ABBR>DE</topp:STATE_ABBR>
+ <topp:LAND_KM>5062.456</topp:LAND_KM>
+ <topp:WATER_KM>1385.022</topp:WATER_KM>
+ <topp:PERSONS>666168.0</topp:PERSONS>
+ <topp:FAMILIES>175867.0</topp:FAMILIES>
+ <topp:HOUSHOLD>247497.0</topp:HOUSHOLD>
+ <topp:MALE>322968.0</topp:MALE>
+ <topp:FEMALE>343200.0</topp:FEMALE>
+ <topp:WORKERS>247566.0</topp:WORKERS>
+ <topp:DRVALONE>258087.0</topp:DRVALONE>
+ <topp:CARPOOL>42968.0</topp:CARPOOL>
+ <topp:PUBTRANS>8069.0</topp:PUBTRANS>
+ <topp:EMPLOYED>335147.0</topp:EMPLOYED>
+ <topp:UNEMPLOY>13945.0</topp:UNEMPLOY>
+ <topp:SERVICE>87973.0</topp:SERVICE>
+ <topp:MANUAL>44140.0</topp:MANUAL>
+ <topp:P_MALE>0.485</topp:P_MALE>
+ <topp:P_FEMALE>0.515</topp:P_FEMALE>
+ <topp:SAMP_POP>102776.0</topp:SAMP_POP>
+ </topp:states>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+--></div>
+
+<div id="GetFeature"><!--
+<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0" handle="handle_g" outputFormat="json" maxFeatures="1" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wfs:Query typeName="topp:states" xmlns:topp="http://www.openplans.org/topp"/>
+</wfs:GetFeature>
+--></div>
+<div id="GetFeatureMultiple"><!--
+<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wfs:Query typeName="topp:states" xmlns:topp="http://www.openplans.org/topp"/>
+ <wfs:Query typeName="topp:cities" xmlns:topp="http://www.openplans.org/topp"/>
+</wfs:GetFeature>
+--></div>
+<div id="Transaction"><!--
+<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0"/>
+--></div>
+<div id="TransactionMulti"><!--
+<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0">
+ <wfs:Insert>
+ <feature:states xmlns:feature="http://www.openplans.org/topp">
+ <feature:the_geom>
+ <gml:MultiPoint xmlns:gml="http://www.opengis.net/gml">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ </gml:MultiPoint>
+ </feature:the_geom>
+ <feature:foo>bar</feature:foo>
+ </feature:states>
+ </wfs:Insert>
+ <wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>the_geom</wfs:Name>
+ <wfs:Value>
+ <gml:MultiPoint xmlns:gml="http://www.opengis.net/gml">
+ <gml:pointMember>
+ <gml:Point>
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </gml:pointMember>
+ </gml:MultiPoint>
+ </wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>bar</wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>nul</wfs:Name>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.42"/>
+ </ogc:Filter>
+ </wfs:Update>
+</wfs:Transaction>
+--></div>
+<div id="Insert"><!--
+<wfs:Insert xmlns:wfs="http://www.opengis.net/wfs" handle="handle_i">
+ <feature:states xmlns:feature="http://www.openplans.org/topp">
+ <feature:the_geom>
+ <gml:Point xmlns:gml="http://www.opengis.net/gml">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </feature:the_geom>
+ <feature:foo>bar</feature:foo>
+ </feature:states>
+</wfs:Insert>
+--></div>
+<div id="Update"><!--
+<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" handle="handle_u" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>the_geom</wfs:Name>
+ <wfs:Value>
+ <gml:Point xmlns:gml="http://www.opengis.net/gml">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2</gml:coordinates>
+ </gml:Point>
+ </wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>bar</wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>nul</wfs:Name>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.42"/>
+ </ogc:Filter>
+</wfs:Update>
+--></div>
+<div id="UpdateModified"><!--
+<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>the_geom</wfs:Name>
+ <wfs:Value>
+ <gml:Point xmlns:gml="http://www.opengis.net/gml">
+ <gml:coordinates decimal="." cs="," ts=" ">2,3</gml:coordinates>
+ </gml:Point>
+ </wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>bar</wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>nul</wfs:Name>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.42"/>
+ </ogc:Filter>
+</wfs:Update>
+--></div>
+<div id="UpdateModifiedNoGeometry"><!--
+<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>baz</wfs:Value>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.42"/>
+ </ogc:Filter>
+</wfs:Update>
+--></div>
+<div id="Delete"><!--
+<wfs:Delete xmlns:wfs="http://www.opengis.net/wfs" handle="handle_d" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.37"/>
+ </ogc:Filter>
+</wfs:Delete>
+--></div>
+<div id="UpdateNoGeometry"><!--
+<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>bar</wfs:Value>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.36"/>
+ </ogc:Filter>
+</wfs:Update>
+--></div>
+<div id="UpdateNullGeometry"><!--
+<wfs:Update xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:Property>
+ <wfs:Name>the_geom</wfs:Name>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>foo</wfs:Name>
+ <wfs:Value>bar</wfs:Value>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.36"/>
+ </ogc:Filter>
+</wfs:Update>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFST/v1_0_0.html b/misc/openlayers/tests/Format/WFST/v1_0_0.html
new file mode 100644
index 0000000..a8fce79
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFST/v1_0_0.html
@@ -0,0 +1,135 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(1);
+
+ var format = new OpenLayers.Format.WFST.v1_0_0({});
+ t.ok(format instanceof OpenLayers.Format.WFST.v1_0_0, "constructor returns instance");
+ }
+
+ function test_read(t) {
+ t.plan(3);
+
+ var data = readXML("Transaction_Response");
+ var format = new OpenLayers.Format.WFST.v1_0_0({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states"
+ });
+ var result = format.read(data);
+ t.eq(result.insertIds[0], "parcelle.40", "First InsertId read correctly");
+ t.eq(result.insertIds[1], "parcelle.41", "Second InsertId read correctly");
+ t.eq(result.success, true, "Success read correctly");
+ }
+
+ function test_write(t) {
+
+ var format = new OpenLayers.Format.WFST.v1_0_0({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ featurePrefix: "topp",
+ geometryName: "the_geom"
+ });
+
+ var cases = [{
+ id: "query0",
+ writer: "wfs:Query",
+ arg: {
+ filter: new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds (1,2,3,4)
+ })
+ }
+ }, {
+ id: "query1",
+ writer: "wfs:Query",
+ arg: {
+ srsNameInQuery: true,
+ srsName: "EPSG:900913"
+ }
+ }, {
+ id: "getfeature0",
+ writer: "wfs:GetFeature",
+ arg: {
+ propertyNames: ["STATE_NAME", "STATE_FIPS", "STATE_ABBR"]
+ }
+ }];
+
+ t.plan(cases.length);
+
+ var test, got, exp;
+ for(var i=0; i<cases.length; ++i) {
+ test = cases[i];
+ exp = readXML(test.id);
+ got = format.writeNode(test.writer, test.arg);
+ t.xml_eq(got, exp, test.id + ": correct request");
+ }
+
+ }
+
+ function test_write_poorconfig(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.WFST.v1_0_0({
+ featureType: "states",
+ featurePrefix: "topp"
+ });
+ var exp = "topp:states";
+ var got = format.writeNode("wfs:Query").getAttribute("typeName");
+ t.eq(got, exp, "Query without featureNS but with featurePrefix queries for the correct featureType");
+ }
+
+ var xmlFormat = new OpenLayers.Format.XML();
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return xmlFormat.read(xml).documentElement;
+ }
+
+ </script>
+</head>
+<body>
+<div id="Transaction_Response"><!--
+<wfs:WFS_TransactionResponse version="1.0.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:ogc="http://www.opengis.net/ogc">
+ <wfs:InsertResult>
+ <ogc:FeatureId fid="parcelle.40"/>
+ <ogc:FeatureId fid="parcelle.41"/>
+ </wfs:InsertResult>
+ <wfs:TransactionResult>
+ <wfs:Status>
+ <wfs:SUCCESS/>
+ </wfs:Status>
+ </wfs:TransactionResult>
+</wfs:WFS_TransactionResponse>
+--></div>
+<div id="query0"><!--
+<wfs:Query xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:BBOX>
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
+ <gml:Box xmlns:gml="http://www.opengis.net/gml">
+ <gml:coordinates decimal="." cs="," ts=" ">1,2 3,4</gml:coordinates>
+ </gml:Box>
+ </ogc:BBOX>
+ </ogc:Filter>
+</wfs:Query>
+--></div>
+<div id="query1"><!--
+<wfs:Query xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" srsName="EPSG:900913" xmlns:topp="http://www.openplans.org/topp">
+</wfs:Query>
+--></div>
+<div id="getfeature0"><!--
+<wfs:GetFeature service="WFS" version="1.0.0" xmlns:topp="http://www.openplans.org/topp"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd">
+ <wfs:Query xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" xmlns:topp="http://www.openplans.org/topp">
+ <ogc:PropertyName>STATE_NAME</ogc:PropertyName>
+ <ogc:PropertyName>STATE_FIPS</ogc:PropertyName>
+ <ogc:PropertyName>STATE_ABBR</ogc:PropertyName>
+ </wfs:Query>
+</wfs:GetFeature>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WFST/v1_1_0.html b/misc/openlayers/tests/Format/WFST/v1_1_0.html
new file mode 100644
index 0000000..52c9cee
--- /dev/null
+++ b/misc/openlayers/tests/Format/WFST/v1_1_0.html
@@ -0,0 +1,236 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(1);
+
+ var format = new OpenLayers.Format.WFST.v1_1_0({});
+ t.ok(format instanceof OpenLayers.Format.WFST.v1_1_0, "constructor returns instance");
+ }
+
+ function test_read(t) {
+ t.plan(3);
+
+ var data = readXML("TransactionResponse");
+ var format = new OpenLayers.Format.WFST.v1_1_0({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states"
+ });
+ var result = format.read(data);
+ t.eq(result.insertIds[0], "parcelle.40", "First InsertId read correctly");
+ t.eq(result.insertIds[1], "parcelle.41", "Second InsertId read correctly");
+ t.eq(result.success, true, "Success read correctly");
+ }
+
+ function test_read_hits(t) {
+ t.plan(1);
+ var data = readXML("NumberOfFeatures");
+ var format = new OpenLayers.Format.WFST.v1_1_0({
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+ featureType: "AAA64"
+ });
+ var result = format.read(data, {output: "object"});
+ t.eq(result.numberOfFeatures, 625, "numberOfFeatures of FeatureCollection correctly read");
+ }
+
+ function test_read_boundedBy(t) {
+ t.plan(4);
+ var data = readXML("boundedBy");
+ var format = new OpenLayers.Format.WFST.v1_1_0({
+ featureNS: "http://mapserver.gis.umn.edu/mapserver",
+ featureType: "AAA212"
+ });
+ var result = format.read(data, {output: "object"});
+ var bounds = result.bounds;
+ t.eq(bounds.left.toFixed(3), '3197.880', "Left bounds of the feature collection correctly parsed");
+ t.eq(bounds.bottom.toFixed(3), '306457.313', "Bottom bounds of the feature collection correctly parsed");
+ t.eq(bounds.right.toFixed(3), '280339.156', "Right bounds of the feature collection correctly parsed");
+ t.eq(bounds.top.toFixed(3), '613850.438', "Top bounds of the feature collection corectly parsed");
+ }
+
+ function test_write(t) {
+
+ var format = new OpenLayers.Format.WFST.v1_1_0({
+ featureNS: "http://www.openplans.org/topp",
+ featureType: "states",
+ featurePrefix: "topp",
+ srsName: "urn:ogc:def:crs:EPSG::4326",
+ geometryName: "the_geom"
+ });
+
+ var cases = [{
+ id: "query0",
+ writer: "wfs:Query",
+ arg: {
+ filter: new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds (1,2,3,4)
+ })
+ }
+ }, {
+ id: "getfeature0",
+ writer: "wfs:GetFeature",
+ arg: {
+ resultType: "hits",
+ propertyNames: ["STATE_NAME", "STATE_FIPS", "STATE_ABBR"]
+ }
+ }, {
+ id: "getfeature1",
+ writer: "wfs:GetFeature",
+ arg: {
+ count: 10,
+ startIndex: 20
+ }
+ }];
+
+ t.plan(cases.length);
+
+ var test, got, exp;
+ for(var i=0; i<cases.length; ++i) {
+ test = cases[i];
+ exp = readXML(test.id);
+ got = format.writeNode(test.writer, test.arg);
+ t.xml_eq(got, exp, test.id + ": correct request");
+ }
+ }
+
+ function test_write_poorconfig(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.WFST.v1_1_0({
+ featureType: "states",
+ featurePrefix: "topp"
+ });
+ var exp = "topp:states";
+ var got = format.writeNode("wfs:Query").getAttribute("typeName");
+ t.eq(got, exp, "Query without featureNS but with featurePrefix queries for the correct featureType");
+ }
+
+ var xmlFormat = new OpenLayers.Format.XML();
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return xmlFormat.read(xml).documentElement;
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+<div id="NumberOfFeatures"><!--
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:rws="http://mapserver.gis.umn.edu/mapserver"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://mapserver.gis.umn.edu/mapserver http://intranet.rijkswaterstaat.nl/services/geoservices/nwb_wegen?SERVICE=WFS&amp;VERSION=1.1.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=feature:AAA64&amp;OUTPUTFORMAT=text/xml; subtype=gml/3.1.1 http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd" numberOfFeatures="625">
+</wfs:FeatureCollection>
+--></div>
+<div id="TransactionResponse"><!--
+<wfs:TransactionResponse version="1.1.0" xmlns:ogc="http://www.opengis.net/ogc" xmlns:tiger="http://www.census.gov" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:sf="http://www.openplans.org/spearfish" xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <wfs:TransactionSummary>
+ <wfs:totalInserted>0</wfs:totalInserted>
+ <wfs:totalUpdated>1</wfs:totalUpdated>
+ <wfs:totalDeleted>0</wfs:totalDeleted>
+ </wfs:TransactionSummary>
+ <wfs:TransactionResults/>
+ <wfs:InsertResults>
+ <wfs:Feature>
+ <ogc:FeatureId fid="parcelle.40"/>
+ </wfs:Feature>
+ <wfs:Feature>
+ <ogc:FeatureId fid="parcelle.41"/>
+ </wfs:Feature>
+ </wfs:InsertResults>
+</wfs:TransactionResponse>
+--></div>
+<div id="query0"><!--
+<wfs:Query xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" srsName="urn:ogc:def:crs:EPSG::4326" xmlns:topp="http://www.openplans.org/topp">
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:BBOX>
+ <ogc:PropertyName>the_geom</ogc:PropertyName>
+ <gml:Envelope xmlns:gml="http://www.opengis.net/gml" srsName="urn:ogc:def:crs:EPSG::4326">
+ <gml:lowerCorner>1 2</gml:lowerCorner>
+ <gml:upperCorner>3 4</gml:upperCorner>
+ </gml:Envelope>
+ </ogc:BBOX>
+ </ogc:Filter>
+</wfs:Query>
+--></div>
+<div id="getfeature0"><!--
+<wfs:GetFeature service="WFS" version="1.1.0" resultType="hits" xmlns:topp="http://www.openplans.org/topp"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
+ <wfs:Query xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" srsName="urn:ogc:def:crs:EPSG::4326" xmlns:topp="http://www.openplans.org/topp">
+ <wfs:PropertyName>STATE_NAME</wfs:PropertyName>
+ <wfs:PropertyName>STATE_FIPS</wfs:PropertyName>
+ <wfs:PropertyName>STATE_ABBR</wfs:PropertyName>
+ </wfs:Query>
+</wfs:GetFeature>
+--></div>
+<div id="getfeature1"><!--
+<wfs:GetFeature service="WFS" version="1.1.0" startIndex="20" count="10" xmlns:topp="http://www.openplans.org/topp"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
+ <wfs:Query xmlns:wfs="http://www.opengis.net/wfs" typeName="topp:states" srsName="urn:ogc:def:crs:EPSG::4326" xmlns:topp="http://www.openplans.org/topp">
+ </wfs:Query>
+</wfs:GetFeature>
+--></div>
+<div id="boundedBy"><!--
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:rws="http://mapserver.gis.umn.edu/mapserver"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://mapserver.gis.umn.edu/mapserver http://ontwikkel.intranet.rijkswaterstaat.nl/services/geoservices/ov_zonering?SERVICE=WFS&amp;VERSION=1.1.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=AAA212&amp;OUTPUTFORMAT=text/xml; subtype=gml/3.1.1 http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
+ <gml:boundedBy>
+ <gml:Envelope srsName="EPSG:28992">
+ <gml:lowerCorner>3197.880000 306457.313000</gml:lowerCorner>
+ <gml:upperCorner>280339.156000 613850.438000</gml:upperCorner>
+ </gml:Envelope>
+ </gml:boundedBy>
+ <gml:featureMember>
+
+ <rws:AAA212 gml:id="AAA212.791">
+ <gml:boundedBy>
+ <gml:Envelope srsName="EPSG:28992">
+ <gml:lowerCorner>196507.469000 502347.938000</gml:lowerCorner>
+ <gml:upperCorner>202430.844000 510383.719000</gml:upperCorner>
+ </gml:Envelope>
+ </gml:boundedBy>
+ <rws:geometry>
+
+ <gml:MultiSurface srsName="EPSG:28992">
+ <gml:surfaceMembers>
+ <gml:Polygon>
+ <gml:exterior>
+ <gml:LinearRing>
+ <gml:posList srsDimension="2">200448.047000 510383.719000 198475.031000 509253.875000 198477.422000 507339.688000 196507.469000 505841.969000 196507.625000 504980.281000 196621.359000 505029.969000 196825.328000 505114.000000 197310.031000 505183.469000 197636.609000 505148.750000 197837.594000 505061.563000 197941.031000 504953.688000 198003.094000 504817.719000 198023.781000 504721.688000 198016.391000 504597.531000 197907.234000 504363.219000 197716.734000 504013.969000 197700.156000 503567.563000 197775.531000 503373.969000 197930.688000 503153.781000 198034.234000 503045.594000 198170.078000 502932.125000 198504.047000 502725.250000 198858.719000 502550.875000 199138.000000 502460.719000 199336.000000 502347.938000 199044.125000 504910.969000 199549.359000 507065.781000 200280.594000 506878.938000 202430.844000 507474.625000 202430.844000 508850.906000 200448.047000 510383.719000 </gml:posList>
+ </gml:LinearRing>
+ </gml:exterior>
+
+ </gml:Polygon>
+ </gml:surfaceMembers>
+ </gml:MultiSurface>
+ </rws:geometry>
+ <rws:OBJECTID>791</rws:OBJECTID>
+ <rws:HECTARES>1800.89</rws:HECTARES>
+ <rws:ZONENR>4620</rws:ZONENR>
+
+ <rws:NULZONES> </rws:NULZONES>
+ <rws:AREA>0</rws:AREA>
+ <rws:PERIMETER>24305.1</rws:PERIMETER>
+ </rws:AAA212>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WKT.html b/misc/openlayers/tests/Format/WKT.html
new file mode 100644
index 0000000..bdfc233
--- /dev/null
+++ b/misc/openlayers/tests/Format/WKT.html
@@ -0,0 +1,297 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var points = [];
+ for(var i=0; i<12; ++i) {
+ points.push(new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(Math.random() * 100,
+ Math.random() * 100))
+ );
+ }
+ var multipoint = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPoint([
+ points[0].geometry,
+ points[1].geometry,
+ points[2].geometry
+ ])
+ );
+
+ var linestrings = [
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([
+ points[0].geometry,
+ points[1].geometry,
+ points[2].geometry
+ ])
+ ),
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString([
+ points[3].geometry,
+ points[4].geometry,
+ points[5].geometry
+ ])
+ )
+ ];
+
+ var multilinestring = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiLineString([
+ linestrings[0].geometry,
+ linestrings[1].geometry
+ ])
+ );
+
+ var rings = [
+ new OpenLayers.Geometry.LinearRing([
+ points[0].geometry,
+ points[1].geometry,
+ points[2].geometry
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ points[3].geometry,
+ points[4].geometry,
+ points[5].geometry
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ points[6].geometry,
+ points[7].geometry,
+ points[8].geometry
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ points[9].geometry,
+ points[10].geometry,
+ points[11].geometry
+ ])
+ ];
+
+ var polygons = [
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([rings[0], rings[1]])
+ ),
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([rings[2], rings[3]])
+ )
+ ];
+
+ var multipolygon = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.MultiPolygon([
+ polygons[0].geometry,
+ polygons[1].geometry
+ ])
+ );
+
+ var collection = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Collection([
+ points[0].geometry,
+ linestrings[0].geometry
+ ])
+ );
+
+ var geom_array = [points[0], linestrings[0]];
+
+ function test_Format_WKT_constructor(t) {
+ t.plan(4);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.WKT(options);
+ t.ok(format instanceof OpenLayers.Format.WKT,
+ "new OpenLayers.Format.WKT returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+ }
+
+ function test_Format_WKT_write(t) {
+ t.plan(8);
+
+ var format = new OpenLayers.Format.WKT();
+
+ // test a point
+
+ t.eq(format.write(points[0]),
+ "POINT(" + points[0].geometry.x + " " + points[0].geometry.y + ")",
+ "format correctly writes Point WKT");
+
+ // test a multipoint
+ t.eq(format.write(multipoint),
+ "MULTIPOINT((" + points[0].geometry.x + " " + points[0].geometry.y + "),(" +
+ points[1].geometry.x + " " + points[1].geometry.y + "),(" +
+ points[2].geometry.x + " " + points[2].geometry.y + "))",
+ "format correctly writes MultiPoint WKT");
+
+ // test a linestring
+ t.eq(format.write(linestrings[0]),
+ "LINESTRING(" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + ")",
+ "format correctly writes LineString WKT");
+
+ // test a multilinestring
+ t.eq(format.write(multilinestring),
+ "MULTILINESTRING((" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + ")," +
+ "(" + points[3].geometry.x + " " + points[3].geometry.y + "," +
+ points[4].geometry.x + " " + points[4].geometry.y + "," +
+ points[5].geometry.x + " " + points[5].geometry.y + "))",
+ "format correctly writes MultiLineString WKT");
+
+ // test a polygon
+ t.eq(format.write(polygons[0]),
+ "POLYGON((" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + "," +
+ points[0].geometry.x + " " + points[0].geometry.y + ")," +
+ "(" + points[3].geometry.x + " " + points[3].geometry.y + "," +
+ points[4].geometry.x + " " + points[4].geometry.y + "," +
+ points[5].geometry.x + " " + points[5].geometry.y + "," +
+ points[3].geometry.x + " " + points[3].geometry.y + "))",
+ "format correctly writes Polygon WKT");
+
+ // test a multipolygon
+ t.eq(format.write(multipolygon),
+ "MULTIPOLYGON(((" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + "," +
+ points[0].geometry.x + " " + points[0].geometry.y + ")," +
+ "(" + points[3].geometry.x + " " + points[3].geometry.y + "," +
+ points[4].geometry.x + " " + points[4].geometry.y + "," +
+ points[5].geometry.x + " " + points[5].geometry.y + "," +
+ points[3].geometry.x + " " + points[3].geometry.y + "))," +
+ "((" + points[6].geometry.x + " " + points[6].geometry.y + "," +
+ points[7].geometry.x + " " + points[7].geometry.y + "," +
+ points[8].geometry.x + " " + points[8].geometry.y + "," +
+ points[6].geometry.x + " " + points[6].geometry.y + ")," +
+ "(" + points[9].geometry.x + " " + points[9].geometry.y + "," +
+ points[10].geometry.x + " " + points[10].geometry.y + "," +
+ points[11].geometry.x + " " + points[11].geometry.y + "," +
+ points[9].geometry.x + " " + points[9].geometry.y + ")))",
+ "format correctly writes MultiPolygon WKT");
+
+ // test geometrycollection
+ t.eq(format.write(collection),
+ "GEOMETRYCOLLECTION(POINT(" + points[0].geometry.x + " " + points[0].geometry.y + ")," +
+ "LINESTRING(" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + "))",
+ "format correctly writes GeometryCollection WKT");
+
+ // test writing an array of geometries
+ t.eq(format.write(geom_array),
+ "GEOMETRYCOLLECTION(POINT(" + points[0].geometry.x + " " + points[0].geometry.y + ")," +
+ "LINESTRING(" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + "))",
+ "format correctly writes WKT for an array of Geometries");
+
+ }
+
+ function test_Format_WKT_read(t) {
+ t.plan(13);
+
+ var format = new OpenLayers.Format.WKT();
+
+ /**
+ * Since we're explicitly testing calls to write, the read tests
+ * just make sure that geometry can make a round trip from read to write.
+ */
+
+ // test a point
+ t.ok(points[0].geometry.equals(format.read(format.write(points[0])).geometry),
+ "format correctly reads Point WKT");
+
+ // test a multipoint
+ t.ok(multipoint.geometry.equals(format.read(format.write(multipoint)).geometry),
+ "format correctly reads MultiPoint WKT");
+
+ // test a multipoint without separating parens
+ t.ok(multipoint.geometry.equals(format.read(
+ "MULTIPOINT(" + points[0].geometry.x + " " + points[0].geometry.y + "," +
+ points[1].geometry.x + " " + points[1].geometry.y + "," +
+ points[2].geometry.x + " " + points[2].geometry.y + ")").geometry),
+ "format correctly reads MultiPoint WKT without parens");
+
+ // test a linestring
+ t.ok(linestrings[0].geometry.equals(format.read(format.write(linestrings[0])).geometry),
+ "format correctly reads LineString WKT");
+
+ // test a multilinestring
+ t.ok(multilinestring.geometry.equals(format.read(format.write(multilinestring)).geometry),
+ "format correctly reads MultiLineString WKT");
+
+ // test a polygon
+ t.ok(polygons[0].geometry.equals(format.read(format.write(polygons[0])).geometry),
+ "format correctly reads Polygon WKT");
+
+ // test a multipolygon
+ t.ok(multipolygon.geometry.equals(format.read(format.write(multipolygon)).geometry),
+ "format correctly reads MultiPolygon WKT");
+
+ // test a collection
+ var wkt = format.write(collection);
+ var got = format.read(wkt);
+ t.ok(got instanceof Array, "by default, reading a collection returns an array");
+ t.eq(got.length, 2, "read two items");
+ t.ok(got[0] instanceof OpenLayers.Feature.Vector, "first item is a feature");
+ t.geom_eq(got[0].geometry, points[0].geometry, "first feature's geometry is the correct point");
+ t.ok(got[1] instanceof OpenLayers.Feature.Vector, "second item is a feature");
+ t.geom_eq(got[1].geometry, linestrings[0].geometry, "second feature's geometry is the correct linestring");
+
+ }
+
+ function test_whitespace(t) {
+ t.plan(3);
+ var wkt = "LINESTRING(7.120068\t43.583917,\n7.120154 43.583652,\n7.120385\t43.582716,\r\n7.12039 43.582568, 7.120712 43.581511,7.120873\n43.580718)";
+ var format = new OpenLayers.Format.WKT();
+ var got = format.read(wkt);
+ t.ok(got instanceof OpenLayers.Feature.Vector, "read a feature");
+ t.ok(got.geometry instanceof OpenLayers.Geometry.LineString, "read a linestring");
+ t.ok(got.geometry.components.length, 6, "read a geometry with 6 components");
+ }
+
+ function test_Format_WKT_read_projection(t) {
+ t.plan(1);
+
+ var projections = {
+ src: new OpenLayers.Projection("EPSG:4326"),
+ dest: new OpenLayers.Projection("EPSG:900913")
+ },
+ points = {
+ src: new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-87.9, 41.9)
+ ),
+ dest: new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-9784983.2393667, 5146011.6785665)
+ )
+ },
+ format = new OpenLayers.Format.WKT({
+ externalProjection: projections["src"],
+ internalProjection: projections["dest"]
+ }),
+ gc_wkt_parts = [
+ "GEOMETRYCOLLECTION(",
+ "POINT(",
+ points["src"].geometry.x,
+ " ",
+ points["src"].geometry.y,
+ ")",
+ ")"
+ ],
+ feature = format.read( gc_wkt_parts.join("") )[0],
+ gotGeom = feature.geometry,
+ expectGeom = points["dest"].geometry,
+ // we don't use geometry::toString because we might run into
+ // precision issues
+ precision = 7,
+ got = gotGeom.x.toFixed(precision) + ' ' + gotGeom.y.toFixed(precision),
+ expected = expectGeom.x.toFixed(precision) + ' ' + expectGeom.y.toFixed(precision);
+
+ t.eq(got, expected,
+ "Geometry collections aren't transformed twice when reprojection.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMC.html b/misc/openlayers/tests/Format/WMC.html
new file mode 100644
index 0000000..fbaec81
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMC.html
@@ -0,0 +1,315 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var v1_0_0 = '<ViewContext xmlns="http://www.opengis.net/context" version="1.0.0" id="OpenLayers_Context_233" xsi:schemaLocation="http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><Window width="512" height="256"/><BoundingBox minx="-109.9709708" miny="27.01451459" maxx="-80.02902918" maxy="41.98548541" SRS="EPSG:4326"/><Title/><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/></Extension></General><LayerList><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://t1.hypercube.telascience.org/cgi-bin/landsat7"/></Server><Name>landsat7</Name><Title>NASA Global Mosaic</Title><FormatList><Format current="1">image/jpeg</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">true</ol:isBaseLayer><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile><ol:tileSize xmlns:ol="http://openlayers.org/context" width="512" height="1024"/></Extension></Layer><Layer queryable="1" hidden="1"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://labs.metacarta.com/wms/vmap0"/></Server><Name>basic</Name><Title>OpenLayers WMS</Title><FormatList><Format current="1">image/jpeg</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">true</ol:isBaseLayer><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://lioapp.lrc.gov.on.ca/cubeserv/cubeserv.pl"/></Server><Name>na_road:CCRS</Name><Title>Transportation Network</Title><FormatList><Format current="1">image/png</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-166.5320000" miny="4.050460000" maxx="-0.2068180000" maxy="70.28700000"/><ol:transparent xmlns:ol="http://openlayers.org/context">TRUE</ol:transparent><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer><ol:opacity xmlns:ol="http://openlayers.org/context">0.6</ol:opacity><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">false</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet/AccuWeather_Maps.wms"/></Server><Name>3:1</Name><Title>Radar 3:1</Title><FormatList><Format current="1">image/png</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-131.0294952" miny="14.56289673" maxx="-61.02950287" maxy="54.56289673"/><ol:transparent xmlns:ol="http://openlayers.org/context">TRUE</ol:transparent><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer><ol:opacity xmlns:ol="http://openlayers.org/context">0.8</ol:opacity><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">false</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">true</ol:singleTile></Extension></Layer></LayerList></ViewContext>';
+ var v1_1_0 = '<ViewContext xmlns="http://www.opengis.net/context" version="1.1.0" id="OpenLayers_Context_232" xsi:schemaLocation="http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><Window width="512" height="256"/><BoundingBox minx="-109.9709708" miny="27.01451459" maxx="-80.02902918" maxy="41.98548541" SRS="EPSG:4326"/><Title/><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/></Extension></General><LayerList><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://t1.hypercube.telascience.org/cgi-bin/landsat7"/></Server><Name>landsat7</Name><Title>NASA Global Mosaic</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6299645.760</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">31498228.80</sld:MaxScaleDenominator><FormatList><Format current="1">image/jpeg</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/><ol:tileSize xmlns:ol="http://openlayers.org/context" width="512" height="1024"/><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">true</ol:isBaseLayer><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="1"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://labs.metacarta.com/wms/vmap0"/></Server><Name>basic</Name><Title>OpenLayers WMS</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6299645.760</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">31498228.80</sld:MaxScaleDenominator><FormatList><Format current="1">image/jpeg</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/><ol:tileSize xmlns:ol="http://openlayers.org/context" width="512" height="1024"/><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">true</ol:isBaseLayer><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://lioapp.lrc.gov.on.ca/cubeserv/cubeserv.pl"/></Server><Name>na_road:CCRS</Name><Title>Transportation Network</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6200000.000</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">32000000.00</sld:MaxScaleDenominator><FormatList><Format current="1">image/png</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-166.5320000" miny="4.050460000" maxx="-0.2068180000" maxy="70.28700000"/><ol:tileSize xmlns:ol="http://openlayers.org/context" width="512" height="1024"/><ol:transparent xmlns:ol="http://openlayers.org/context">TRUE</ol:transparent><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer><ol:opacity xmlns:ol="http://openlayers.org/context">0.6</ol:opacity><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">false</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet/AccuWeather_Maps.wms"/></Server><Name>3:1</Name><Title>Radar 3:1</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6299645.760</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">31498228.80</sld:MaxScaleDenominator><FormatList><Format current="1">image/png</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-131.0294952" miny="14.56289673" maxx="-61.02950287" maxy="54.56289673"/><ol:transparent xmlns:ol="http://openlayers.org/context">TRUE</ol:transparent><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer><ol:opacity xmlns:ol="http://openlayers.org/context">0.8</ol:opacity><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">true</ol:singleTile></Extension></Layer></LayerList></ViewContext>';
+ var polar = '<ViewContext xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:default="http://www.opengis.net/context" xmlns:ol="http://openlayers.org/context" xmlns="http://www.opengis.net/context" xmlns:wms="http://www.opengis.net/wms" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1.0" id="OpenLayers_Context_466" xsi:schemaLocation="http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd"><General><BoundingBox minx="-3000000" miny="-3000000" maxx="7000000" maxy="7000000" SRS="EPSG:32661"/><Title>WMS viewer</Title><Extension><ol:maxExtent minx="-3000000" miny="-3000000" maxx="7000000" maxy="7000000"/><ol:units>m</ol:units></Extension></General><LayerList xmlns="http://www.opengis.net/context"><Layer queryable="0" hidden="0"><Server service="OGC:WMS" version="1.1.1" foo="bar"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://wms.met.no/maps/northpole.map"/></Server><Name>world</Name><Title>The World</Title><FormatList><Format>image/jpeg</Format><Format current="1">image/png</Format></FormatList></Layer></LayerList></ViewContext>';
+ var fulldoc = '<ViewContext xmlns="http://www.opengis.net/context" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sld="http://www.opengis.net/sld" version="1.1.0" id="eos_data_gateways" xsi:schemaLocation="http://www.opengis.net/context context.xsd"><General><Window width="500" height="300"/><BoundingBox SRS="EPSG:4326" minx="-180.000000" miny="-90.000000" maxx="180.000000" maxy="90.000000"/><Title>EOS Data Gateways</Title><KeywordList><Keyword>EOS</Keyword><Keyword>EOSDIS</Keyword><Keyword>NASA</Keyword><Keyword>CCRS</Keyword><Keyword>CEOS</Keyword><Keyword>OGC</Keyword></KeywordList><Abstract>Map View of EOSDIS partners locations</Abstract><LogoURL width="130" height="74" format="image/gif"><OnlineResource xlink:type="simple" xlink:href="http://redhook.gsfc.nasa.gov/~imswww/pub/icons/logo.gif"/></LogoURL><DescriptionURL format="text/html"><OnlineResource xlink:type="simple" xlink:href="http://eos.nasa.gov/imswelcome"/></DescriptionURL><ContactInformation><ContactPersonPrimary><ContactPerson>Tom Kralidis</ContactPerson><ContactOrganization>Environment Canada</ContactOrganization></ContactPersonPrimary><ContactPosition>Systems Scientist</ContactPosition><ContactAddress><AddressType>postal</AddressType><Address>867 Lakeshore Road</Address><City>Burlington</City><StateOrProvince>Ontario</StateOrProvince><PostCode>L7R 4A6</PostCode><Country>Canada</Country></ContactAddress><ContactVoiceTelephone>+01-905-336-4409</ContactVoiceTelephone><ContactFacsimileTelephone>+01-905-336-4499</ContactFacsimileTelephone><ContactElectronicMailAddress>tom.kralidis@ec.gc.ca</ContactElectronicMailAddress></ContactInformation></General><LayerList><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1" title="ESA CubeSERV"><OnlineResource xlink:type="simple" xlink:href="http://mapserv2.esrin.esa.it/cubestor/cubeserv/cubeserv.cgi"/></Server><Name>WORLD_MODIS_1KM:MapAdmin</Name><Title>WORLD_MODIS_1KM</Title><Abstract>Global maps derived from various Earth Observation sensors / WORLD_MODIS_1KM:MapAdmin</Abstract><SRS>EPSG:4326</SRS><FormatList><Format current="1">image/png</Format><Format>image/gif</Format><Format>image/jpeg</Format></FormatList><StyleList><Style current="1"><Name>default</Name><Title>default</Title><LegendURL width="16" height="16" format="image/gif"><OnlineResource xlink:type="simple" xlink:href="http://mapserv2.esrin.esa.it/cubestor/cubeserv/cubeserv.cgi?version=1.1.1&amp;request=GetLegendGraphic&amp;layer=WORLD_MODIS_1KM:MapAdmin&amp;style=default&amp;format=image/gif"/></LegendURL></Style></StyleList></Layer><Layer queryable="0" hidden="0"><Server service="OGC:WMS" version="1.1.1" title="The GLOBE Program Visualization Server"><OnlineResource xlink:type="simple" xlink:href="http://globe.digitalearth.gov/viz-bin/wmt.cgi"/></Server><Name>COASTLINES</Name><Title>Coastlines</Title><Abstract>Context layer: Coastlines</Abstract><SRS>EPSG:4326</SRS><SRS>EPSG:900913</SRS><FormatList><Format current="1">image/gif</Format><Format>image/png</Format></FormatList><StyleList><Style current="1"><Name>default</Name><Title>Default</Title><LegendURL width="180" format="image/gif" height="50"><OnlineResource xlink:type="simple" xlink:href="http://globe.digitalearth.gov/globe/en/icons/colorbars/COASTLINES.gif"/></LegendURL></Style></StyleList><DimensionList><Dimension name="time" units="ISO8601" nearestValue="1">2011-03-31,2011-04-01</Dimension></DimensionList></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1" title="The GLOBE Program Visualization Server"><OnlineResource xlink:type="simple" xlink:href="http://globe.digitalearth.gov/viz-bin/wmt.cgi"/></Server><Name>NATIONAL</Name><Title>National Boundaries</Title><Abstract>Context layer: National Boundaries</Abstract><DataURL><OnlineResource xlink:type="simple" xlink:href="http://globe.digitalearth.gov/data/national.gml"/></DataURL><MetadataURL><OnlineResource xlink:type="simple" xlink:href="http://globe.digitalearth.gov/metadata/national.txt"/></MetadataURL><SRS>EPSG:4326</SRS><FormatList><Format current="1">image/gif</Format><Format>image/png</Format></FormatList><StyleList><Style current="1"><Name>default</Name><Title>Default</Title><LegendURL width="180" format="image/gif" height="50"><OnlineResource xlink:type="simple" xlink:href="http://globe.digitalearth.gov/globe/en/icons/colorbars/NATIONAL.gif"/></LegendURL></Style></StyleList></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1" title="Canada Centre for Remote Sensing Web Map Service"><OnlineResource xlink:type="simple" xlink:href="http://ceoware2.ccrs.nrcan.gc.ca/cubewerx/cubeserv/cubeserv.cgi"/></Server><Name>EOS_DATA_GATEWAYS:CEOWARE2</Name><Title>EOS Data Gateways</Title><Abstract>Locations of EOS Data Gateway Locations. The same services and data are available through each gateway location.</Abstract><sld:MinScaleDenominator>1000</sld:MinScaleDenominator><sld:MaxScaleDenominator>500000</sld:MaxScaleDenominator><SRS>EPSG:4326</SRS><FormatList><Format current="1">image/gif</Format><Format>image/png</Format><Format>image/jpeg</Format></FormatList><StyleList><Style current="1"><Name>default</Name><Title>default</Title><LegendURL width="16" height="16" format="image/gif"><OnlineResource xlink:type="simple" xlink:href="http://ceoware2.ccrs.nrcan.gc.ca/cubewerx/cubeserv/cubeserv.cgi?version=1.1.1&amp;request=GetLegendGraphic&amp;layer=EOS_DATA_GATEWAYS:CEOWARE2&amp;style=default&amp;format=image/gif"/></LegendURL></Style><Style><SLD><Title>Default</Title><sld:StyledLayerDescriptor xmlns:ogc="http://www.opengis.net/ogc" xmlns:sld="http://www.opengis.net/sld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd"><sld:NamedLayer><sld:Name>AAA212</sld:Name><sld:UserStyle><sld:FeatureTypeStyle><sld:Rule><sld:TextSymbolizer><sld:Label><ogc:PropertyName>ZONENR</ogc:PropertyName></sld:Label><sld:Font><sld:CssParameter name="font-family">Arial</sld:CssParameter><sld:CssParameter name="font-size">10</sld:CssParameter></sld:Font><sld:Fill><sld:CssParameter name="fill">#FF9900</sld:CssParameter></sld:Fill></sld:TextSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor></SLD></Style><Style><SLD><Title>Default</Title><sld:FeatureTypeStyle xmlns:ogc="http://www.opengis.net/ogc" xmlns:sld="http://www.opengis.net/sld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd"><sld:Rule><sld:TextSymbolizer><sld:Label><PropertyName>ZONENR</PropertyName></sld:Label><sld:Font><sld:CssParameter name="font-family">Arial</sld:CssParameter><sld:CssParameter name="font-size">10</sld:CssParameter></sld:Font><sld:Fill><sld:CssParameter name="fill">#FF9900</sld:CssParameter></sld:Fill></sld:TextSymbolizer></sld:Rule></sld:FeatureTypeStyle></SLD></Style></StyleList></Layer></LayerList></ViewContext>';
+
+ function test_Format_WMC_read(t) {
+ t.plan(64);
+
+ var format = new OpenLayers.Format.WMC();
+ var map, layer;
+
+ // test v1.0.0
+ map = format.read(v1_0_0, {map: "map"});
+ t.eq(map.center.lon.toPrecision(6), (-95).toPrecision(6), "(v1.0.0) map center correctly set");
+ t.eq(map.center.lat.toPrecision(6), (34.5).toPrecision(6), "(v1.0.0) map center correctly set");
+ t.eq(map.maxExtent.left.toPrecision(6), (-130).toPrecision(6), "(v1.0.0) map maxExtent correctly set");
+ t.eq(map.layers.length, 4, "(v1.0.0) correct number of layers");
+ t.eq(map.projection, "EPSG:4326", "(v1.0.0) map projection set correctly");
+ // check out first base layer
+ layer = map.layers[0];
+ t.eq(layer.queryable, true, "(1.0.0) Layer is queryable");
+ t.ok(layer instanceof OpenLayers.Layer.WMS,
+ "(v1.0.0) wms layer correctly instantiated");
+ t.eq(layer.url, "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ "(v1.0.0) layer url correctly set");
+ t.eq(layer.maxExtent.right.toPrecision(6), (-60).toPrecision(6),
+ "(v1.0.0) layer maxExtent correctly set");
+ t.eq(layer.numZoomLevels, 4,
+ "(v1.0.0) layer numZoomLevels correctly set");
+ t.eq(layer.isBaseLayer, true,
+ "(v1.0.0) layer isBaseLayer correctly set for base layer");
+ t.eq(layer.tileSize.w, 512,
+ "(v1.0.0) layer tileSize width correctly set");
+ t.eq(layer.tileSize.h, 1024,
+ "(v1.0.0) layer tileSize height correctly set");
+ // check out layer not in switcher
+ layer = map.layers[2];
+ t.eq(layer.displayInLayerSwitcher, false,
+ "(v1.0.0) layer displayInLayerSwitcher correctly set");
+ t.eq(layer.params["TRANSPARENT"], "TRUE",
+ "(v1.0.0) layer param.transparent correctly set");
+ t.eq(layer.isBaseLayer, false,
+ "(v1.0.0) layer isBaseLayer correctly set for overlay");
+ // check out single tile overlay
+ layer = map.layers[3];
+ t.eq(layer.singleTile, true,
+ "(v1.0.0) layer singleTile correctly set");
+ t.eq(layer.opacity, 0.8,
+ "(v1.0.0) layer opacity correctly set");
+ map.destroy();
+
+ // test v1.1.0
+ map = format.read(v1_1_0, {map: "map"});
+ t.eq(map.center.lon.toPrecision(6), (-95).toPrecision(6), "(v1.1.0) map center correctly set");
+ t.eq(map.center.lat.toPrecision(6), (34.5).toPrecision(6), "(v1.1.0) map center correctly set");
+ t.eq(map.maxExtent.left.toPrecision(6), (-130).toPrecision(6), "(v1.1.0) map maxExtent correctly set");
+ t.eq(map.layers.length, 4, "(v1.1.0) correct number of layers");
+ t.eq(map.projection, "EPSG:4326", "(v1.1.0) map projection set correctly");
+ // check out first baseLayer
+ layer = map.layers[0];
+ t.ok(layer instanceof OpenLayers.Layer.WMS,
+ "(v1.1.0) wms layer correctly instantiated");
+ t.eq(layer.url, "http://t1.hypercube.telascience.org/cgi-bin/landsat7",
+ "(v1.1.0) layer url correctly set");
+ t.eq(layer.maxExtent.right.toPrecision(6), (-60).toPrecision(6),
+ "(v1.1.0) layer maxExtent correctly set");
+ t.eq(layer.numZoomLevels, 4,
+ "(v1.1.0) layer numZoomLevels correctly set");
+ t.eq(layer.isBaseLayer, true,
+ "(v1.1.0) layer isBaseLayer correctly set for overlay");
+ // check out layer not in switcher
+ layer = map.layers[2];
+ t.eq(layer.displayInLayerSwitcher, false,
+ "(v1.1.0) layer displayInLayerSwitcher correctly set");
+ t.eq(layer.params["TRANSPARENT"], "TRUE",
+ "(v1.1.0) layer param.transparent correctly set");
+ t.eq(layer.isBaseLayer, false,
+ "(v1.1.0) layer isBaseLayer correctly set for overlay");
+ t.eq(layer.minScale.toPrecision(6), (32000000).toPrecision(6),
+ "(v1.1.0) layer minScale correctly set");
+ t.eq(layer.maxScale.toPrecision(6), (6200000).toPrecision(6),
+ "(v1.1.0) layer maxScale correctly set");
+ // check out single tile overlay
+ layer = map.layers[3];
+ t.eq(layer.singleTile, true,
+ "(v1.1.0) layer singleTile correctly set");
+ t.eq(layer.opacity, 0.8,
+ "(v1.1.0) layer opacity correctly set");
+ map.destroy();
+
+ // Check if maxResolution is set correctly
+ map = format.read(polar, {map: "map"});
+ t.eq(map.maxResolution, 39062.5,
+ "maxResolution correctly set");
+ map.destroy();
+
+ // test mapOptions
+ map = format.read(v1_1_0, {map: {foo: 'bar', div: 'map'}});
+ t.eq(map.foo, "bar",
+ "mapOptions correctly passed to the created map object");
+ map.destroy();
+
+ map = format.read(fulldoc, {map: "map"});
+
+ var meta = map.metadata;
+
+ // Check if ContextInformation is set properly
+ var cinfo = meta.contactInformation;
+
+ t.eq(cinfo.personPrimary.person, "Tom Kralidis", "got correct person");
+ t.eq(cinfo.personPrimary.organization, "Environment Canada", "got correct organization");
+
+ t.eq(cinfo.contactAddress.address, "867 Lakeshore Road", "got correct address");
+ t.eq(cinfo.contactAddress.city, "Burlington", "got correct city");
+ t.eq(cinfo.contactAddress.country, "Canada", "got correct country");
+ t.eq(cinfo.contactAddress.postcode, "L7R 4A6", "got correct postcode");
+ t.eq(cinfo.contactAddress.stateOrProvince, "Ontario", "got correct stateOrProvince");
+ t.eq(cinfo.contactAddress.type, "postal", "got correct address type");
+
+ t.eq(cinfo.email, "tom.kralidis@ec.gc.ca", "got correct email");
+ t.eq(cinfo.fax, "+01-905-336-4499", "got correct fax number");
+ t.eq(cinfo.phone, "+01-905-336-4409", "got correct phone number");
+ t.eq(cinfo.position, "Systems Scientist", "got correct position");
+
+ // Check if LogoURL is read properly
+ var logo = meta.logo;
+ t.eq(logo, {
+ href: "http://redhook.gsfc.nasa.gov/~imswww/pub/icons/logo.gif",
+ width: "130",
+ height: "74",
+ format: "image/gif"},
+ "got currect logo");
+
+ t.eq(meta.descriptionURL, "http://eos.nasa.gov/imswelcome", "got correct descriptionURL");
+
+ t.eq(meta.keywords,
+ ["EOS", "EOSDIS", "NASA", "CCRS", "CEOS", "OGC"],
+ "got correct keywords");
+
+ layer = map.layers[1];
+
+ t.eq(layer.metadata.servertitle,
+ "The GLOBE Program Visualization Server",
+ "got correct title for server");
+
+ t.eq(layer.srs,
+ {"EPSG:4326": true, "EPSG:900913": true},
+ "SRS read correctly");
+
+ t.eq(layer.metadata.formats,
+ [{value: "image/gif", current: true},
+ {value: "image/png"}],
+ "formats read correctly");
+
+ var style = layer.metadata.styles[0];
+ t.eq(style.legend, {
+ href: "http://globe.digitalearth.gov/globe/en/icons/colorbars/COASTLINES.gif",
+ width: "180",
+ height: "50",
+ format: "image/gif"},
+ "got currect legend");
+
+
+ var dim = layer.dimensions["time"];
+ t.eq(dim.name, "time", "got correct name of dimension");
+ t.eq(dim.units, "ISO8601", "got correct units for dimension");
+ t.eq(dim.nearestValue, true, "got correct value for nearestValue");
+ t.eq(dim.values, ["2011-03-31", "2011-04-01"], "got correct values for dimension");
+
+ layer = map.layers[2];
+ t.eq(layer.metadata.dataURL,
+ "http://globe.digitalearth.gov/data/national.gml",
+ "got correct dataURL");
+ t.eq(layer.metadataURL,
+ "http://globe.digitalearth.gov/metadata/national.txt",
+ "got correct metadataURL");
+
+ layer = map.layers[3];
+ var sld_body = '<sld:StyledLayerDescriptor xmlns:ogc="http://www.opengis.net/ogc" xmlns:sld="http://www.opengis.net/sld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd"><sld:NamedLayer><sld:Name>AAA212</sld:Name><sld:UserStyle><sld:FeatureTypeStyle><sld:Rule><sld:TextSymbolizer><sld:Label><ogc:PropertyName>ZONENR</ogc:PropertyName></sld:Label><sld:Font><sld:CssParameter name="font-family">Arial</sld:CssParameter><sld:CssParameter name="font-size">10</sld:CssParameter></sld:Font><sld:Fill><sld:CssParameter name="fill">#FF9900</sld:CssParameter></sld:Fill></sld:TextSymbolizer></sld:Rule></sld:FeatureTypeStyle></sld:UserStyle></sld:NamedLayer></sld:StyledLayerDescriptor>';
+ var styles = layer.metadata.styles;
+ t.xml_eq(styles[1].body, sld_body, "StyledLayerDescriptor body read correctly");
+
+ sld_body = '<sld:FeatureTypeStyle xmlns:ogc="http://www.opengis.net/ogc" xmlns:sld="http://www.opengis.net/sld" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd"><sld:Rule><sld:TextSymbolizer><sld:Label><PropertyName xmlns="http://www.opengis.net/context">ZONENR</PropertyName></sld:Label><sld:Font><sld:CssParameter name="font-family">Arial</sld:CssParameter><sld:CssParameter name="font-size">10</sld:CssParameter></sld:Font><sld:Fill><sld:CssParameter name="fill">#FF9900</sld:CssParameter></sld:Fill></sld:TextSymbolizer></sld:Rule></sld:FeatureTypeStyle>';
+ t.xml_eq(styles[2].body, sld_body, "FeatureTypeStyle body read correctly");
+
+ }
+
+ function test_Format_WMC_write(t) {
+
+ var format = new OpenLayers.Format.WMC();
+ var map = format.read(v1_1_0, {map: "map"});
+ var wmc;
+
+ function sanitize(str) {
+ // id is unique so we don't expect it to be the same
+ str = str.replace(/OpenLayers_Context_\d+/, "foo");
+ // FF and IE differ on ordering of attribute nodes
+ // a better way to compare xml would be nice
+ str = str.replace(/\s*xsi:schemaLocation=".*?"/, "");
+ str = str.replace(/\s*xmlns:xlink=".*?"/g, "");
+ // and of course floating point precision is an issue
+ str = str.replace(/(-?\d+\.\d+)/g, function(m) {return parseFloat(m).toPrecision(6)});
+ return str;
+ }
+
+ /**
+ * Version 1.0.0 doesn't make the round trip because min/max resolutions
+ * are not stored. Version 1.1.0 introduced minScaleDenominator and
+ * maxScaleDenominator. The parser still writes valid WMC v1.0.0, you
+ * just need a validator to prove it.
+ *
+ * The wmc.html example can be used to produce WMC docs for validation.
+ * Prior to validation, the Extension tags need to be removed unless someone
+ * cares to write and maintain some XSD. Both version 1.0.0 and 1.1.0
+ * validate on http://www.validome.org/xml/.
+ */
+
+ // test v1.1.0
+ if(OpenLayers.BROWSER_NAME== "opera") {
+ t.plan(0);
+ t.debug_print("WMC writing works but is not tested in Opera");
+ } else {
+ t.plan(11);
+
+ map = format.read(v1_1_0, {map: "map"});
+ wmc = format.write(map);
+ t.eq(sanitize(wmc), sanitize(v1_1_0),
+ "(v1.1.0) write gives what read got");
+ map.destroy();
+
+ var parser = format.getParser("1.1.0");
+ map = format.read(fulldoc, {map: "map"});
+ var context = format.toContext(map);
+
+ // KeywordList
+ var expected = '<KeywordList xmlns="http://www.opengis.net/context"><Keyword>EOS</Keyword><Keyword>EOSDIS</Keyword><Keyword>NASA</Keyword><Keyword>CCRS</Keyword><Keyword>CEOS</Keyword><Keyword>OGC</Keyword></KeywordList>';
+ t.xml_eq(parser.write_wmc_KeywordList(context.keywords),
+ expected,
+ "keywordlist written correctly");
+
+ // ContactInformation
+ expected = '<ContactInformation xmlns="http://www.opengis.net/context"><ContactPersonPrimary><ContactPerson>Tom Kralidis</ContactPerson><ContactOrganization>Environment Canada</ContactOrganization></ContactPersonPrimary><ContactPosition>Systems Scientist</ContactPosition><ContactAddress><AddressType>postal</AddressType><Address>867 Lakeshore Road</Address><City>Burlington</City><StateOrProvince>Ontario</StateOrProvince><PostCode>L7R 4A6</PostCode><Country>Canada</Country></ContactAddress><ContactVoiceTelephone>+01-905-336-4409</ContactVoiceTelephone><ContactFacsimileTelephone>+01-905-336-4499</ContactFacsimileTelephone><ContactElectronicMailAddress>tom.kralidis@ec.gc.ca</ContactElectronicMailAddress></ContactInformation>';
+ t.xml_eq(parser.write_wmc_ContactInformation(context.contactInformation),
+ expected,
+ "contactInformation written correctly");
+
+ // LogoURL
+ expected = '<LogoURL xmlns="http://www.opengis.net/context" width="130" height="74" format="image/gif"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://redhook.gsfc.nasa.gov/~imswww/pub/icons/logo.gif"/></LogoURL>';
+ t.xml_eq(parser.write_wmc_URLType("LogoURL", context.logo.href, context.logo),
+ expected,
+ "LogoURL written correctly");
+
+ // DescriptionURL
+ expected = '<DescriptionURL xmlns="http://www.opengis.net/context"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://eos.nasa.gov/imswelcome"/></DescriptionURL>';
+ t.xml_eq(parser.write_wmc_URLType("DescriptionURL", context.descriptionURL),
+ expected,
+ "DescriptionURL written correctly");
+
+
+ var layerContext = context.layersContext[1];
+
+ // Server
+ expected = '<Server xmlns="http://www.opengis.net/context" service="OGC:WMS" version="1.1.1" title="The GLOBE Program Visualization Server"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://globe.digitalearth.gov/viz-bin/wmt.cgi"/></Server>';
+ t.xml_eq(parser.write_wmc_Server(layerContext),
+ expected,
+ "Server written correctly");
+
+ // FormatList
+ expected = '<FormatList xmlns="http://www.opengis.net/context"><Format current="1">image/gif</Format><Format>image/png</Format></FormatList>';
+ t.xml_eq(parser.write_wmc_FormatList(layerContext),
+ expected,
+ "FormatList written correctly");
+
+ // DimensionList
+ expected = '<DimensionList xmlns="http://www.opengis.net/context"><Dimension name="time" units="ISO8601" nearestValue="1" unitSymbol="" userValue="" multipleValues="0" current="0" default="">2011-03-31,2011-04-01</Dimension></DimensionList>';
+ t.xml_eq(parser.write_wmc_DimensionList(layerContext),
+ expected,
+ "DimensionList written correctly");
+
+ // LegendURL
+ var legend = layerContext.styles[0].legend;
+ expected = '<LegendURL xmlns="http://www.opengis.net/context" width="180" format="image/gif" height="50"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://globe.digitalearth.gov/globe/en/icons/colorbars/COASTLINES.gif"/></LegendURL>';
+ t.xml_eq(parser.write_wmc_URLType("LegendURL", legend.href, legend),
+ expected,
+ "LegendURL written correctly");
+
+ layerContext = context.layersContext[2];
+
+ // DataURL
+ expected = '<DataURL xmlns="http://www.opengis.net/context"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://globe.digitalearth.gov/data/national.gml" /></DataURL>';
+ t.xml_eq(parser.write_wmc_URLType("DataURL", layerContext.dataURL),
+ expected,
+ "DataURL written correctly");
+
+ // MetadataURL
+ expected = '<MetadataURL xmlns="http://www.opengis.net/context"><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://globe.digitalearth.gov/metadata/national.txt" /></MetadataURL>';
+ t.xml_eq(parser.write_wmc_URLType("MetadataURL", layerContext.metadataURL),
+ expected,
+ "MetadataURL written correctly");
+
+ }
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 512px; height: 256px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMC/v1.html b/misc/openlayers/tests/Format/WMC/v1.html
new file mode 100644
index 0000000..05e6078
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMC/v1.html
@@ -0,0 +1,266 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_write_wmc_StyleList(t) {
+ t.plan(4);
+
+ var layer, layerContext, got, expected;
+
+ var format = new OpenLayers.Format.WMC();
+ var parser = format.getParser("1");
+ var name = "test";
+ var url = "http://foo";
+
+ // test named style
+ layer = new OpenLayers.Layer.WMS(name, url, {
+ styles: "mystyle"
+ });
+ layerContext = format.layerToContext(layer);
+ got = parser.write_wmc_StyleList(layerContext);
+ expected =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<Name>mystyle</Name><Title>Default</Title>" +
+ "</Style>" +
+ "</StyleList>";
+
+ t.xml_eq(got, expected, "named style correctly written");
+ layer.destroy();
+
+ // test linked style
+ layer = new OpenLayers.Layer.WMS(name, url, {
+ sld: "http://linked.sld"
+ });
+ layerContext = format.layerToContext(layer);
+ got = parser.write_wmc_StyleList(layerContext);
+ expected =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<SLD>" +
+ "<Title>Default</Title>" +
+ "<OnlineResource xmlns:xlink='http://www.w3.org/1999/xlink' "+
+ "xlink:type='simple' " +
+ "xlink:href='http://linked.sld' />" +
+ "</SLD>" +
+ "</Style>" +
+ "</StyleList>";
+
+ t.xml_eq(got, expected, "linked style correctly written");
+ layer.destroy();
+
+ // test inline style
+ layer = new OpenLayers.Layer.WMS(name, url, {
+ sld_body:
+ "<sld:StyledLayerDescriptor version='1.0.0' " +
+ "xmlns:ogc='http://www.opengis.net/ogc' " +
+ "xmlns:sld='http://www.opengis.net/sld' " +
+ "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
+ "xsi:schemaLocation='http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd'>" +
+ "<sld:NamedLayer>" +
+ "<sld:Name>AAA212</sld:Name>" +
+ "<sld:UserStyle>" +
+ "<sld:FeatureTypeStyle>" +
+ "<sld:Rule>" +
+ "<sld:TextSymbolizer>" +
+ "<sld:Label>" +
+ "<ogc:PropertyName>ZONENR</ogc:PropertyName>" +
+ "</sld:Label>" +
+ "<sld:Font>" +
+ "<sld:CssParameter name='font-family'>Arial</sld:CssParameter>" +
+ "<sld:CssParameter name='font-size'>10</sld:CssParameter>" +
+ "</sld:Font>" +
+ "<sld:Fill>" +
+ "<sld:CssParameter name='fill'>#FF9900</sld:CssParameter>" +
+ "</sld:Fill>" +
+ "</sld:TextSymbolizer>" +
+ "</sld:Rule>" +
+ "</sld:FeatureTypeStyle>" +
+ "</sld:UserStyle>" +
+ "</sld:NamedLayer>" +
+ "</sld:StyledLayerDescriptor>"
+ });
+
+ layerContext = format.layerToContext(layer);
+ got = parser.write_wmc_StyleList(layerContext);
+ expected =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<SLD>" +
+ "<Title>Default</Title>" +
+ "<sld:StyledLayerDescriptor version='1.0.0' " +
+ "xmlns:sld='http://www.opengis.net/sld' " +
+ "xmlns:ogc='http://www.opengis.net/ogc' " +
+ "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
+ "xsi:schemaLocation='http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd'>" +
+ "<sld:NamedLayer>" +
+ "<sld:Name>AAA212</sld:Name>" +
+ "<sld:UserStyle>" +
+ "<sld:FeatureTypeStyle>" +
+ "<sld:Rule>" +
+ "<sld:TextSymbolizer>" +
+ "<sld:Label>" +
+ "<ogc:PropertyName>ZONENR</ogc:PropertyName>" +
+ "</sld:Label>" +
+ "<sld:Font>" +
+ "<sld:CssParameter name='font-family'>Arial</sld:CssParameter>" +
+ "<sld:CssParameter name='font-size'>10</sld:CssParameter>" +
+ "</sld:Font>" +
+ "<sld:Fill>" +
+ "<sld:CssParameter name='fill'>#FF9900</sld:CssParameter>" +
+ "</sld:Fill>" +
+ "</sld:TextSymbolizer>" +
+ "</sld:Rule>" +
+ "</sld:FeatureTypeStyle>" +
+ "</sld:UserStyle>" +
+ "</sld:NamedLayer>" +
+ "</sld:StyledLayerDescriptor>" +
+ "</SLD>" +
+ "</Style>" +
+ "</StyleList>";
+
+ t.xml_eq(got, expected, "inline style correctly written");
+ layer.destroy();
+
+ // test inline FeatureTypeStyle
+ layer = new OpenLayers.Layer.WMS(name, url, {
+ sld_body:
+ "<sld:FeatureTypeStyle version='1.0.0' " +
+ "xmlns:sld='http://www.opengis.net/sld' " +
+ "xmlns:ogc='http://www.opengis.net/ogc' " +
+ "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
+ "xsi:schemaLocation='http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd'>" +
+ "<sld:Rule>" +
+ "<sld:TextSymbolizer>" +
+ "<sld:Label>" +
+ "<ogc:PropertyName>ZONENR</ogc:PropertyName>" +
+ "</sld:Label>" +
+ "<sld:Font>" +
+ "<sld:CssParameter name='font-family'>Arial</sld:CssParameter>" +
+ "<sld:CssParameter name='font-size'>10</sld:CssParameter>" +
+ "</sld:Font>" +
+ "<sld:Fill>" +
+ "<sld:CssParameter name='fill'>#FF9900</sld:CssParameter>" +
+ "</sld:Fill>" +
+ "</sld:TextSymbolizer>" +
+ "</sld:Rule>" +
+ "</sld:FeatureTypeStyle>"
+ });
+
+ layerContext = format.layerToContext(layer);
+ got = parser.write_wmc_StyleList(layerContext);
+ expected =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<SLD>" +
+ "<Title>Default</Title>" +
+ "<sld:FeatureTypeStyle version='1.0.0' " +
+ "xmlns:sld='http://www.opengis.net/sld' " +
+ "xmlns:ogc='http://www.opengis.net/ogc' " +
+ "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
+ "xsi:schemaLocation='http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd'>" +
+ "<sld:Rule>" +
+ "<sld:TextSymbolizer>" +
+ "<sld:Label>" +
+ "<ogc:PropertyName>ZONENR</ogc:PropertyName>" +
+ "</sld:Label>" +
+ "<sld:Font>" +
+ "<sld:CssParameter name='font-family'>Arial</sld:CssParameter>" +
+ "<sld:CssParameter name='font-size'>10</sld:CssParameter>" +
+ "</sld:Font>" +
+ "<sld:Fill>" +
+ "<sld:CssParameter name='fill'>#FF9900</sld:CssParameter>" +
+ "</sld:Fill>" +
+ "</sld:TextSymbolizer>" +
+ "</sld:Rule>" +
+ "</sld:FeatureTypeStyle>" +
+ "</SLD>" +
+ "</Style>" +
+ "</StyleList>";
+
+ t.xml_eq(got, expected, "inline FeatureTypeStyle correctly written");
+ layer.destroy();
+ }
+
+ function test_read_wmc_StyleList(t) {
+ t.plan(3);
+
+ var xml = new OpenLayers.Format.XML();
+ var format = new OpenLayers.Format.WMC();
+ var parser = format.getParser("1");
+ var node, text, layerContext, layer;
+
+ // test named style
+ text =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<Name>mystyle</Name><Title>Default</Title>" +
+ "</Style>" +
+ "</StyleList>";
+ node = xml.read(text).documentElement;
+ layerContext = {
+ styles: []
+ };
+ parser.read_wmc_StyleList(layerContext, node);
+ layer = format.getLayerFromContext(layerContext);
+ t.eq(layer.params.STYLES, "mystyle", "named style correctly read");
+
+ // test linked style
+ text =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<SLD>" +
+ "<OnlineResource xmlns:xlink='http://www.w3.org/1999/xlink' "+
+ "xlink:type='simple' " +
+ "xlink:href='http://linked.sld' />" +
+ "</SLD>" +
+ "</Style>" +
+ "</StyleList>";
+ node = xml.read(text).documentElement;
+ layerContext = {
+ styles: []
+ };
+ parser.read_wmc_StyleList(layerContext, node);
+ layer = format.getLayerFromContext(layerContext);
+ t.eq(layer.params.SLD, "http://linked.sld", "linked style correctly read");
+
+ // test inline style
+ // any valid xml under the StyledLayerDescriptor node should make the
+ // round trip from string to node and back
+ text =
+ "<StyleList xmlns='http://www.opengis.net/context'>" +
+ "<Style current='1'>" +
+ "<SLD>" +
+ "<sld:StyledLayerDescriptor version='1.0.0' " +
+ "xmlns:sld='http://www.opengis.net/sld' " +
+ "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
+ "xsi:schemaLocation='http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd'>" +
+ "<foo>bar<more/></foo>" +
+ "</sld:StyledLayerDescriptor>" +
+ "</SLD>" +
+ "</Style>" +
+ "</StyleList>";
+ node = xml.read(text).documentElement;
+ layerContext = {
+ styles: []
+ };
+ parser.read_wmc_StyleList(layerContext, node);
+ layer = format.getLayerFromContext(layerContext);
+ var expected =
+ "<sld:StyledLayerDescriptor version='1.0.0' " +
+ "xmlns:sld='http://www.opengis.net/sld' " +
+ "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
+ "xsi:schemaLocation='http://www.opengis.net/sld http://schemas.opengeospatial.net/sld/1.0.0/StyledLayerDescriptor.xsd'>" +
+ "<foo xmlns='http://www.opengis.net/context'>bar<more/></foo>" +
+ "</sld:StyledLayerDescriptor>";
+ t.xml_eq(layer.params.SLD_BODY, expected, "inline style correctly read");
+
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMC/v1_1_0.html b/misc/openlayers/tests/Format/WMC/v1_1_0.html
new file mode 100644
index 0000000..815d3bf
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMC/v1_1_0.html
@@ -0,0 +1,86 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_write_wmc_Layer(t) {
+ if (OpenLayers.BROWSER_NAME == "safari") {
+ t.plan(0);
+ t.debug_print("Safari has wierd behavior with getElementsByTagNameNS: the result is that we can't run these tests there. Patches welcome.");
+ return;
+ }
+ t.plan(12);
+
+ // direct construction of a parser for a unit test
+ var format = new OpenLayers.Format.WMC();
+ var parser = format.getParser("1_1_0");
+ var sldNS = parser.namespaces["sld"];
+
+ // test that Min/MaxScaleDenominator is not written out when no
+ // resolution related options are set
+ var layer = new OpenLayers.Layer.WMS(
+ "test", "http://foo", {},
+ {maxExtent: new OpenLayers.Bounds(1, 2, 3, 4)}
+ );
+ var layerContext = format.layerToContext(layer);
+ var node = parser.write_wmc_Layer(layerContext);
+ var minList = parser.getElementsByTagNameNS(node, sldNS, "MinScaleDenominator");
+ t.eq(minList.length, 0, "(none) node not written with MinScaleDenominator");
+ var maxList = parser.getElementsByTagNameNS(node, sldNS, "MaxScaleDenominator");
+ t.eq(maxList.length, 0, "(none) node not written with MaxScaleDenominator");
+
+ // test that Min/MaxScaleDenominator is written out for explicit
+ // resolutions array
+ layer = new OpenLayers.Layer.WMS(
+ "test", "http://foo", {},
+ {resolutions: [4, 2, 1], maxExtent: new OpenLayers.Bounds(1, 2, 3, 4)}
+ );
+ layer.minScale = Math.random();
+ layer.maxScale = Math.random();
+ sldNS = parser.namespaces["sld"];
+ layerContext = format.layerToContext(layer);
+ node = parser.write_wmc_Layer(layerContext);
+ minList = parser.getElementsByTagNameNS(node, sldNS, "MinScaleDenominator");
+ t.eq(minList.length, 1, "(resolutions) node written with MinScaleDenominator");
+ t.eq(layer.maxScale.toPrecision(16), parser.getChildValue(minList[0]),
+ "(resolutions) node written with correct MinScaleDenominator value");
+ maxList = parser.getElementsByTagNameNS(node, sldNS, "MaxScaleDenominator");
+ t.eq(maxList.length, 1, "(resolutions) node written with MaxScaleDenominator");
+ t.eq(layer.minScale.toPrecision(16), parser.getChildValue(maxList[0]),
+ "(resolutions) node written with correct MaxScaleDenominator value");
+
+ layer = new OpenLayers.Layer.WMS(
+ "test", "http://foo", {},
+ {scales: [4, 2, 1], maxExtent: new OpenLayers.Bounds(1, 2, 3, 4)}
+ );
+ layer.minScale = Math.random();
+ layer.maxScale = Math.random();
+ layerContext = format.layerToContext(layer);
+ node = parser.write_wmc_Layer(layerContext);
+ minList = parser.getElementsByTagNameNS(node, sldNS, "MinScaleDenominator");
+ var f = new OpenLayers.Format.XML();
+ t.eq(minList.length, 1, "(scales) node written with MinScaleDenominator");
+ t.eq(layer.maxScale.toPrecision(16), parser.getChildValue(minList[0]),
+ "(scales) node written with correct MinScaleDenominator value");
+ maxList = parser.getElementsByTagNameNS(node, sldNS, "MaxScaleDenominator");
+ t.eq(maxList.length, 1, "(scales) node written with MaxScaleDenominator");
+ t.eq(layer.minScale.toPrecision(16), parser.getChildValue(maxList[0]),
+ "(scales) node written with correct MaxScaleDenominator value");
+
+ layer.metadataURL = 'http://foo';
+ layerContext = format.layerToContext(layer);
+ node = parser.write_wmc_Layer(layerContext);
+ t.eq(node.childNodes[3].localName || node.childNodes[3].nodeName.split(":").pop(),
+ 'MetadataURL', "MinScaleDenominator is written after MetadataURL, so third node should be MetadataURL");
+ t.eq(node.childNodes[4].localName || node.childNodes[4].nodeName.split(":").pop(),
+ 'MinScaleDenominator', "MinScaleDenominator is written after MetadataURL, so fourth node should be MinScaleDenominator");
+
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 512px; height: 256px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMSCapabilities.html b/misc/openlayers/tests/Format/WMSCapabilities.html
new file mode 100644
index 0000000..a447bdd
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMSCapabilities.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+
+ t.plan(1);
+ var format = new OpenLayers.Format.WMSCapabilities({
+ version: "foo"
+ });
+ t.eq(format.version, "foo", "version set on format");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMSCapabilities/v1_1_1.html b/misc/openlayers/tests/Format/WMSCapabilities/v1_1_1.html
new file mode 100644
index 0000000..e3b0863
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMSCapabilities/v1_1_1.html
@@ -0,0 +1,5209 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+ t.plan(1);
+ var xml = document.getElementById("exceptionsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var format = new OpenLayers.Format.WMSCapabilities();
+ var obj = format.read(doc);
+ t.ok(!!obj.error, "Error reported correctly");
+ }
+
+ function test_read(t) {
+
+ t.plan(24);
+
+ var xml = document.getElementById("gssample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var format = new OpenLayers.Format.WMSCapabilities();
+ var obj = format.read(doc);
+
+ var capability = obj.capability;
+ t.ok(capability, "object contains capability property");
+
+ var getmap = capability.request.getmap;
+ t.eq(getmap.formats.length, 28, "getmap formats parsed");
+ t.eq(
+ getmap.href,
+ "http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&",
+ "getmap href parsed"
+ );
+ t.eq(
+ getmap.get.href,
+ getmap.href,
+ "getmap.get.href parsed"
+ );
+ t.eq(
+ getmap.post,
+ undefined,
+ "getmap.post not available"
+ );
+
+ var describelayer = capability.request.describelayer;
+ t.eq(
+ describelayer.href,
+ "http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&",
+ "describelayer href parsed"
+ );
+ t.eq(
+ describelayer.get.href,
+ describelayer.href,
+ "describelayer.get.href parsed"
+ );
+ t.eq(
+ describelayer.post,
+ undefined,
+ "describelayer.post not available"
+ );
+
+ var getfeatureinfo = capability.request.getfeatureinfo;
+ t.eq(
+ getfeatureinfo.href,
+ "http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&",
+ "getfeatureinfo href parsed"
+ );
+ t.eq(
+ getfeatureinfo.get.href,
+ getfeatureinfo.href,
+ "getmap.get.href parsed"
+ );
+ t.eq(
+ getfeatureinfo.post.href,
+ "http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&",
+ "getfeatureinfo.post set correctly"
+ );
+
+ t.ok(capability.layers, "layers parsed");
+ t.eq(capability.layers.length, 22, "correct number of layers parsed");
+
+ var layer = capability.layers[2];
+ t.eq(layer.infoFormats, ["text/plain", "text/html", "application/vnd.ogc.gml"], "infoFormats set on layer");
+ t.eq(layer.name, "tiger:tiger_roads", "[2] correct layer name");
+ t.eq(layer.prefix, "tiger", "[2] correct layer prefix");
+ t.eq(layer.title, "Manhattan (NY) roads", "[2] correct layer title");
+ t.eq(
+ layer["abstract"],
+ "Highly simplified road layout of Manhattan in New York..",
+ "[2] correct layer abstract"
+ );
+ t.eq(
+ layer.llbbox,
+ [-74.08769307536667, 40.660618924633326, -73.84653192463333, 40.90178007536667],
+ "[2] correct layer bbox"
+ );
+ t.eq(layer.styles.length, 1, "[2] correct styles length");
+ t.eq(layer.styles[0].name, "tiger_roads", "[2] correct style name");
+ t.eq(
+ layer.styles[0].legend.href,
+ "http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=tiger:tiger_roads",
+ "[2] correct legend url"
+ );
+ t.eq(
+ layer.styles[0].legend.format, "image/png",
+ "[2] correct legend format"
+ );
+ t.eq(layer.queryable, true, "[2] correct queryable attribute");
+
+
+ }
+
+ function test_layers(t) {
+
+ t.plan(24);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var capability = obj.capability;
+
+ var layers = {};
+ for (var i=0, len=capability.layers.length; i<len; i++) {
+ if ("name" in capability.layers[i]) {
+ layers[ capability.layers[i].name ] = capability.layers[i];
+ }
+ }
+
+ var rootlayer = capability.layers[ capability.layers.length - 1];
+
+ t.eq(rootlayer.srs,
+ {"EPSG:4326": true},
+ "SRS parsed correctly for root layer");
+ t.eq(layers["ROADS_RIVERS"].srs,
+ {"EPSG:4326": true, "EPSG:26986": true},
+ "Inheritance of SRS handled correctly when adding SRSes");
+ t.eq(layers["Temperature"].srs,
+ {"EPSG:4326": true},
+ "Inheritance of SRS handled correctly when redeclaring an inherited SRS");
+
+ var bbox = layers["ROADS_RIVERS"].bbox["EPSG:26986"];
+ t.eq(bbox.bbox,
+ [189000, 834000, 285000, 962000],
+ "Correct bbox from BoundingBox");
+ t.eq(bbox.res, {x: 1, y: 1}, "Correct resolution");
+ bbox = layers["ROADS_RIVERS"].bbox["EPSG:4326"];
+ t.eq(bbox.bbox,
+ [-71.63, 41.75, -70.78, 42.90],
+ "Correct bbox from BoundingBox (override)");
+ t.eq(bbox.res, {x: 0.01, y: 0.01}, "Correct resolution (override)");
+ bbox = layers["ROADS_1M"].bbox["EPSG:26986"];
+ t.eq(bbox.bbox,
+ [189000, 834000, 285000, 962000],
+ "Correctly inherited bbox");
+ t.eq(bbox.res, {x: 1, y: 1}, "Correctly inherited resolution");
+
+
+ var identifiers = layers["ROADS_RIVERS"].identifiers;
+ var authorities = layers["ROADS_RIVERS"].authorityURLs;
+
+ t.ok(identifiers, "got identifiers from layer ROADS_RIVERS");
+ t.ok("DIF_ID" in identifiers,
+ "authority attribute from Identifiers parsed correctly");
+ t.eq(identifiers["DIF_ID"],
+ "123456",
+ "Identifier value parsed correctly");
+ t.ok("DIF_ID" in authorities,
+ "AuthorityURLs parsed and inherited correctly");
+ t.eq(authorities["DIF_ID"],
+ "http://gcmd.gsfc.nasa.gov/difguide/whatisadif.html",
+ "OnlineResource in AuthorityURLs parsed correctly");
+
+ var featurelist = layers["ROADS_RIVERS"].featureListURL;
+ t.ok(featurelist, "layer has FeatureListURL");
+ t.eq(featurelist.format,
+ "application/vnd.ogc.se_xml",
+ "FeatureListURL format parsed correctly");
+ t.eq(featurelist.href,
+ "http://www.university.edu/data/roads_rivers.gml",
+ "FeatureListURL OnlineResource parsed correctly");
+
+ t.eq(layers["Pressure"].queryable,
+ true,
+ "queryable property inherited correctly");
+ t.eq(layers["ozone_image"].queryable,
+ false,
+ "queryable property has correct default value");
+ t.eq(layers["population"].cascaded,
+ 1,
+ "cascaded property parsed correctly");
+ t.eq(layers["ozone_image"].fixedWidth,
+ 512,
+ "fixedWidth property correctly parsed");
+ t.eq(layers["ozone_image"].fixedHeight,
+ 256,
+ "fixedHeight property correctly parsed");
+ t.eq(layers["ozone_image"].opaque,
+ true,
+ "opaque property parsed correctly");
+ t.eq(layers["ozone_image"].noSubsets,
+ true,
+ "noSubsets property parsed correctly");
+
+
+ }
+
+ function test_dimensions(t) {
+
+ t.plan(8);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var capability = obj.capability;
+
+ var layers = {};
+ for (var i=0, len=capability.layers.length; i<len; i++) {
+ if ("name" in capability.layers[i]) {
+ layers[ capability.layers[i].name ] = capability.layers[i];
+ }
+ }
+
+ var time = layers["Clouds"].dimensions.time;
+ t.eq(time["default"], "2000-08-22", "Default time value parsed correctly");
+ t.eq(time.values.length, 1, "Currect number of time extent values/periods");
+ t.eq(time.values[0], "1999-01-01/2000-08-22/P1D", "Time extent values parsed correctly");
+
+ var elevation = layers["Pressure"].dimensions.elevation;
+ t.eq(elevation.units, "EPSG:5030", "Dimension units parsed correctly");
+ t.eq(elevation["default"], "0", "Default elevation value parsed correctly");
+ t.eq(elevation.nearestVal, true, "NearestValue parsed correctly");
+ t.eq(elevation.multipleVal, false, "Absense of MultipleValues handled correctly");
+ t.eq(elevation.values,
+ ["0","1000","3000","5000","10000"],
+ "Parsing of comma-separated values done correctly");
+
+
+ }
+
+ function test_contactinfo(t) {
+ t.plan(15);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var service = obj.service;
+
+ var contactinfo = service.contactInformation;
+ t.ok(contactinfo, "object contains contactInformation property");
+
+ var personPrimary = contactinfo.personPrimary;
+ t.ok(personPrimary, "object contains personPrimary property");
+
+ t.eq(personPrimary.person, "Jeff deLaBeaujardiere", "ContactPerson parsed correctly");
+ t.eq(personPrimary.organization, "NASA", "ContactOrganization parsed correctly");
+
+ t.eq(contactinfo.position,
+ "Computer Scientist",
+ "ContactPosition parsed correctly");
+
+
+ var addr = contactinfo.contactAddress;
+ t.ok(addr, "object contains contactAddress property");
+
+ t.eq(addr.type, "postal", "AddressType parsed correctly");
+ t.eq(addr.address,
+ "NASA Goddard Space Flight Center, Code 933",
+ "Address parsed correctly");
+ t.eq(addr.city, "Greenbelt", "City parsed correctly");
+ t.eq(addr.stateOrProvince, "MD", "StateOrProvince parsed correctly");
+ t.eq(addr.postcode, "20771", "PostCode parsed correctly");
+ t.eq(addr.country, "USA", "Country parsed correctly");
+
+ t.eq(contactinfo.phone,
+ "+1 301 286-1569",
+ "ContactVoiceTelephone parsed correctly");
+ t.eq(contactinfo.fax,
+ "+1 301 286-1777",
+ "ContactFacsimileTelephone parsed correctly");
+ t.eq(contactinfo.email,
+ "delabeau@iniki.gsfc.nasa.gov",
+ "ContactElectronicMailAddress parsed correctly");
+ }
+
+ function test_feesAndConstraints(t) {
+ t.plan(2);
+
+ var xml = document.getElementById("gssample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var service = obj.service;
+
+ t.ok(! ("fees" in service), "Fees=none handled correctly");
+ t.ok(! ("accessConstraints" in service), "AccessConstraints=none handled correctly");
+ }
+
+ function test_requests(t) {
+ t.plan(13);
+
+ var xml = document.getElementById("gssample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var request = obj.capability.request;
+
+ t.ok(request, "request property exists");
+ t.ok("getmap" in request, "got GetMap request");
+
+ t.ok("getfeatureinfo" in request, "got GetFeatureInfo request");
+ t.eq(request.getfeatureinfo.formats,
+ ["text/plain", "text/html", "application/vnd.ogc.gml"],
+ "GetFeatureInfo formats correctly parsed");
+
+ t.ok("describelayer" in request, "got DescribeLayer request");
+
+ t.ok("getlegendgraphic" in request, "got GetLegendGraphic request");
+
+ var exception = obj.capability.exception;
+ t.ok(exception, "exception property exists");
+ t.eq(exception.formats,
+ ["application/vnd.ogc.se_xml"],
+ "Exception Format parsed");
+
+ var userSymbols = obj.capability.userSymbols;
+ t.ok(userSymbols, "userSymbols property exists");
+ t.eq(userSymbols.supportSLD, true, "supportSLD parsed");
+ t.eq(userSymbols.userLayer, true, "userLayer parsed");
+ t.eq(userSymbols.userStyle, true, "userStyle parsed");
+ t.eq(userSymbols.remoteWFS, true, "remoteWFS parsed");
+
+ }
+ function test_ogc(t) {
+ t.plan(16)
+
+ /*
+ * Set up
+ */
+
+ // needed for the minScale/maxScale test, see below
+ var dpi = OpenLayers.DOTS_PER_INCH;
+ OpenLayers.DOTS_PER_INCH = 90.710230403857;
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var capability = obj.capability;
+
+ /*
+ * Test
+ */
+
+ var attribution = capability.layers[2].attribution;
+ t.eq(attribution.title, "State College University", "attribution title parsed correctly.");
+ t.eq(attribution.href, "http://www.university.edu/", "attribution href parsed correctly.")
+ t.eq(attribution.logo.href, "http://www.university.edu/icons/logo.gif", "attribution logo url parsed correctly.");
+ t.eq(attribution.logo.format, "image/gif", "attribution logo format parsed correctly.");
+ t.eq(attribution.logo.width, "100", "attribution logo width parsed correctly.");
+ t.eq(attribution.logo.height, "100", "attribution logo height parsed correctly.");
+
+ var keywords = capability.layers[0].keywords;
+ t.eq(keywords.length, 3, "layer has 3 keywords.");
+ t.eq(keywords[0], "road", "1st keyword parsed correctly.");
+
+ var metadataURLs = capability.layers[0].metadataURLs;
+ t.eq(metadataURLs.length, 2, "layer has 2 metadata urls.");
+ t.eq(metadataURLs[0].type, "FGDC", "type parsed correctly.");
+ t.eq(metadataURLs[0].format, "text/plain", "format parsed correctly.");
+ t.eq(metadataURLs[0].href, "http://www.university.edu/metadata/roads.txt", "href parsed correctly.");
+
+ /*
+ Test minScale and maxScale
+
+ For Mapserver
+
+ <ScaleHint min="0.395998292216226" max="98.9995730540565" />
+
+ corresponds to (RESOLUTION keyword in MAP file has value of 90.710230403857):
+
+ MAXSCALE 250000
+ MINSCALE 1000
+
+ */
+ t.eq(capability.layers[0].minScale, 250000, "layer.minScale is correct");
+ t.eq(capability.layers[0].maxScale, 1000, "layer.maxScale is correct");
+
+ t.eq(capability.layers[1].minScale, undefined, "layer.minScale for max='Infinity' is correct");
+ t.eq(capability.layers[1].maxScale, undefined, "layer.maxScale for min='0' is correct");
+ /*
+ * Tear down
+ */
+
+ OpenLayers.DOTS_PER_INCH = dpi;
+ }
+
+ </script>
+</head>
+<body>
+
+<!--
+OGC example below taken from
+http://schemas.opengis.net/wms/1.1.1/capabilities_1_1_1.xml
+Copyright © 1994-2008 Open Geospatial Consortium, Inc. All Rights Reserved.
+http://www.opengeospatial.org/ogc/document
+Changes:
+* fixed DTD URL
+* removed comments
+-->
+<div id="ogcsample"><!--
+<?xml version='1.0' encoding="UTF-8" standalone="no" ?>
+<!DOCTYPE WMT_MS_Capabilities SYSTEM
+ "http://schemas.opengis.net/wms/1.1.1/capabilities_1_1_1.dtd"
+ [
+ <!ELEMENT VendorSpecificCapabilities EMPTY>
+ ]>
+
+<WMT_MS_Capabilities version="1.1.1" updateSequence="0">
+<Service>
+
+ <Name>OGC:WMS</Name>
+ <Title>Acme Corp. Map Server</Title>
+ <Abstract>WMT Map Server maintained by Acme Corporation. Contact: webmaster@wmt.acme.com. High-quality maps showing roadrunner nests and possible ambush locations.</Abstract>
+ <KeywordList>
+
+ <Keyword>bird</Keyword>
+ <Keyword>roadrunner</Keyword>
+ <Keyword>ambush</Keyword>
+ </KeywordList>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://hostname/" />
+
+ <ContactInformation>
+ <ContactPersonPrimary>
+ <ContactPerson>Jeff deLaBeaujardiere</ContactPerson>
+ <ContactOrganization>NASA</ContactOrganization>
+ </ContactPersonPrimary>
+ <ContactPosition>Computer Scientist</ContactPosition>
+ <ContactAddress>
+
+ <AddressType>postal</AddressType>
+ <Address>NASA Goddard Space Flight Center, Code 933</Address>
+ <City>Greenbelt</City>
+ <StateOrProvince>MD</StateOrProvince>
+ <PostCode>20771</PostCode>
+ <Country>USA</Country>
+
+ </ContactAddress>
+ <ContactVoiceTelephone>+1 301 286-1569</ContactVoiceTelephone>
+ <ContactFacsimileTelephone>+1 301 286-1777</ContactFacsimileTelephone>
+ <ContactElectronicMailAddress>delabeau@iniki.gsfc.nasa.gov</ContactElectronicMailAddress>
+ </ContactInformation>
+ <Fees>none</Fees>
+
+ <AccessConstraints>none</AccessConstraints>
+</Service>
+<Capability>
+ <Request>
+ <GetCapabilities>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname:port/path" />
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname:port/path" />
+ </Post>
+ </HTTP>
+ </DCPType>
+
+ </GetCapabilities>
+ <GetMap>
+ <Format>image/gif</Format>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname:port/path/get" />
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname:port/path/post" />
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetMap>
+ <GetFeatureInfo>
+ <Format>application/vnd.ogc.gml</Format>
+
+ <Format>text/plain</Format>
+ <Format>text/html</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname:port/path" />
+ </Get>
+ </HTTP>
+
+ </DCPType>
+ </GetFeatureInfo>
+ <DescribeLayer>
+ <Format>application/vnd.ogc.gml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname:port/path" />
+
+ </Get>
+ </HTTP>
+ </DCPType>
+ </DescribeLayer>
+ </Request>
+ <Exception>
+ <Format>application/vnd.ogc.se_xml</Format>
+ <Format>application/vnd.ogc.se_inimage</Format>
+
+ <Format>application/vnd.ogc.se_blank</Format>
+ </Exception>
+ <VendorSpecificCapabilities />
+ <UserDefinedSymbolization SupportSLD="1" UserLayer="1" UserStyle="1"
+ RemoteWFS="1" />
+
+ <Layer>
+ <Title>Acme Corp. Map Server</Title>
+ <SRS>EPSG:4326</SRS>
+ <BoundingBox SRS="EPSG:4326"
+ minx="-1" miny="-1" maxx="1" maxy="1" resx="0.0" resy="0.0"/>
+ <AuthorityURL name="DIF_ID">
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://gcmd.gsfc.nasa.gov/difguide/whatisadif.html" />
+ </AuthorityURL>
+ <Layer>
+ <Name>ROADS_RIVERS</Name>
+ <Title>Roads and Rivers</Title>
+ <SRS>EPSG:26986</SRS>
+ <LatLonBoundingBox minx="-71.63" miny="41.75" maxx="-70.78" maxy="42.90"/>
+ <BoundingBox SRS="EPSG:4326"
+ minx="-71.63" miny="41.75" maxx="-70.78" maxy="42.90" resx="0.01" resy="0.01"/>
+
+ <BoundingBox SRS="EPSG:26986"
+ minx="189000" miny="834000" maxx="285000" maxy="962000" resx="1" resy="1" />
+ <Attribution>
+ <Title>State College University</Title>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://www.university.edu/" />
+ <LogoURL width="100" height="100">
+ <Format>image/gif</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/icons/logo.gif" />
+
+ </LogoURL>
+ </Attribution>
+ <Identifier authority="DIF_ID">123456</Identifier>
+ <FeatureListURL>
+ <Format>application/vnd.ogc.se_xml</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://www.university.edu/data/roads_rivers.gml" />
+ </FeatureListURL>
+
+ <Style>
+ <Name>USGS</Name>
+ <Title>USGS Topo Map Style</Title>
+ <Abstract>Features are shown in a style like that used in USGS topographic maps.</Abstract>
+ <LegendURL width="72" height="72">
+ <Format>image/gif</Format>
+
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/legends/usgs.gif" />
+ </LegendURL>
+ <StyleSheetURL>
+ <Format>text/xsl</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/stylesheets/usgs.xsl" />
+ </StyleSheetURL>
+ </Style>
+
+
+ <Layer queryable="1">
+ <Name>ROADS_1M</Name>
+ <Title>Roads at 1:1M scale</Title>
+ <Abstract>Roads at a scale of 1 to 1 million.</Abstract>
+ <KeywordList>
+ <Keyword>road</Keyword>
+
+ <Keyword>transportation</Keyword>
+ <Keyword>atlas</Keyword>
+ </KeywordList>
+ <Identifier authority="DIF_ID">123456</Identifier>
+ <MetadataURL type="FGDC">
+ <Format>text/plain</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/metadata/roads.txt" />
+ </MetadataURL>
+ <MetadataURL type="FGDC">
+ <Format>text/xml</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/metadata/roads.xml" />
+ </MetadataURL>
+ <Style>
+
+ <Name>ATLAS</Name>
+ <Title>Road atlas style</Title>
+ <Abstract>Roads are shown in a style like that used in a commercial road atlas.</Abstract>
+ <LegendURL width="72" height="72">
+ <Format>image/gif</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/legends/atlas.gif" />
+ </LegendURL>
+
+ </Style>
+ <ScaleHint min="0.395998292216226" max="98.9995730540565" />
+ </Layer>
+ <Layer queryable="1">
+ <Name>RIVERS_1M</Name>
+ <Title>Rivers at 1:1M scale</Title>
+ <Abstract>Rivers at a scale of 1 to 1 million.</Abstract>
+ <KeywordList>
+
+ <Keyword>river</Keyword>
+ <Keyword>canal</Keyword>
+ <Keyword>waterway</Keyword>
+ </KeywordList>
+ <ScaleHint min="0" max="Infinity" />
+ </Layer>
+ </Layer>
+ <Layer queryable="1">
+
+ <Title>Weather Forecast Data</Title>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-180" miny="-90" maxx="180" maxy="90" />
+ <Dimension name="time" units="ISO8601" />
+ <Extent name="time" default="2000-08-22">1999-01-01/2000-08-22/P1D</Extent>
+
+ <Layer>
+ <Name>Clouds</Name>
+ <Title>Forecast cloud cover</Title>
+ </Layer>
+
+ <Layer>
+ <Name>Temperature</Name>
+ <Title>Forecast temperature</Title>
+ </Layer>
+
+ <Layer>
+ <Name>Pressure</Name>
+ <Title>Forecast barometric pressure</Title>
+ <Dimension name="time" units="ISO8601" />
+ <Dimension name="elevation" units="EPSG:5030" />
+ <Extent name="time" default="2000-08-22">1999-01-01/2000-08-22/P1D</Extent>
+ <Extent name="elevation" default="0" nearestValue="1">0,1000,3000,5000,10000</Extent>
+ </Layer>
+
+ </Layer>
+
+ <Layer opaque="1" noSubsets="1" fixedWidth="512" fixedHeight="256">
+ <Name>ozone_image</Name>
+ <Title>Global ozone distribution (1992)</Title>
+ <LatLonBoundingBox minx="-180" miny="-90" maxx="180" maxy="90" />
+ <Extent name="time" default="1992">1992</Extent>
+ </Layer>
+
+ <Layer cascaded="1">
+ <Name>population</Name>
+ <Title>World population, annual</Title>
+ <LatLonBoundingBox minx="-180" miny="-90" maxx="180" maxy="90" />
+ <Extent name="time" default="2000">1990/2000/P1Y</Extent>
+ </Layer>
+
+ </Layer>
+
+
+</Capability>
+</WMT_MS_Capabilities>
+--></div>
+<div id="exceptionsample"><!--
+<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
+<!DOCTYPE ServiceExceptionReport SYSTEM "http://schemas.opengis.net/wms/1.1.1/WMS_exception_1_1_1.dtd">
+<ServiceExceptionReport version="1.1.1"><ServiceException> Plain text message about an error. </ServiceException>
+</ServiceExceptionReport>
+--></div>
+<!--
+GeoServer example below taken from
+http://publicus.opengeo.org/geoserver/wms?SERVICE=WMS&VERSION=1.1.1&REQUEST=GetCapabilities
+Changes:
+* fixed DTD URL (publicus is no longer available)
+* removed comments
+-->
+<div id="gssample"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://schemas.opengis.net/wms/1.1.1/capabilities_1_1_1.dtd">
+<WMT_MS_Capabilities version="1.1.1" updateSequence="57">
+ <Service>
+ <Name>OGC:WMS</Name>
+ <Title>GeoServer Web Map Service</Title>
+ <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+ <KeywordList>
+ <Keyword>WFS</Keyword>
+ <Keyword>WMS</Keyword>
+ <Keyword>GEOSERVER</Keyword>
+ </KeywordList>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms"/>
+ <ContactInformation>
+ <ContactPersonPrimary>
+ <ContactPerson>Claudius Ptolomaeus</ContactPerson>
+ <ContactOrganization>The ancient geographes INC</ContactOrganization>
+ </ContactPersonPrimary>
+ <ContactPosition>Chief geographer</ContactPosition>
+ <ContactAddress>
+ <AddressType>Work</AddressType>
+ <Address/>
+ <City>Alexandria</City>
+ <StateOrProvince/>
+ <PostCode/>
+ <Country>Egypt</Country>
+ </ContactAddress>
+ <ContactVoiceTelephone/>
+ <ContactFacsimileTelephone/>
+ <ContactElectronicMailAddress>claudius.ptolomaeus@gmail.com</ContactElectronicMailAddress>
+ </ContactInformation>
+ <Fees>NONE</Fees>
+ <AccessConstraints>NONE</AccessConstraints>
+ </Service>
+ <Capability>
+ <Request>
+ <GetCapabilities>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetCapabilities>
+ <GetMap>
+ <Format>image/png</Format>
+ <Format>application/atom xml</Format>
+ <Format>application/atom+xml</Format>
+ <Format>application/openlayers</Format>
+ <Format>application/pdf</Format>
+ <Format>application/rss xml</Format>
+ <Format>application/rss+xml</Format>
+ <Format>application/vnd.google-earth.kml</Format>
+ <Format>application/vnd.google-earth.kml xml</Format>
+ <Format>application/vnd.google-earth.kml+xml</Format>
+ <Format>application/vnd.google-earth.kmz</Format>
+ <Format>application/vnd.google-earth.kmz xml</Format>
+ <Format>application/vnd.google-earth.kmz+xml</Format>
+ <Format>atom</Format>
+ <Format>image/geotiff</Format>
+ <Format>image/geotiff8</Format>
+ <Format>image/gif</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/png8</Format>
+ <Format>image/svg</Format>
+ <Format>image/svg xml</Format>
+ <Format>image/svg+xml</Format>
+ <Format>image/tiff</Format>
+ <Format>image/tiff8</Format>
+ <Format>kml</Format>
+ <Format>kmz</Format>
+ <Format>openlayers</Format>
+ <Format>rss</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetMap>
+ <GetFeatureInfo>
+ <Format>text/plain</Format>
+ <Format>text/html</Format>
+ <Format>application/vnd.ogc.gml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetFeatureInfo>
+ <DescribeLayer>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </DescribeLayer>
+ <GetLegendGraphic>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/gif</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetLegendGraphic>
+ </Request>
+ <Exception>
+ <Format>application/vnd.ogc.se_xml</Format>
+ </Exception>
+ <UserDefinedSymbolization SupportSLD="1" UserLayer="1" UserStyle="1" RemoteWFS="1"/>
+ <Layer>
+ <Title>GeoServer Web Map Service</Title>
+ <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+ <SRS>EPSG:WGS84(DD)</SRS>
+ <SRS>EPSG:2000</SRS>
+ <SRS>EPSG:2001</SRS>
+ <SRS>EPSG:2002</SRS>
+ <SRS>EPSG:2003</SRS>
+ <SRS>EPSG:2004</SRS>
+ <SRS>EPSG:2005</SRS>
+ <SRS>EPSG:2006</SRS>
+ <SRS>EPSG:2007</SRS>
+ <SRS>EPSG:2008</SRS>
+ <SRS>EPSG:2009</SRS>
+ <SRS>EPSG:2010</SRS>
+ <SRS>EPSG:2011</SRS>
+ <SRS>EPSG:2012</SRS>
+ <SRS>EPSG:2013</SRS>
+ <SRS>EPSG:2014</SRS>
+ <SRS>EPSG:2015</SRS>
+ <SRS>EPSG:2016</SRS>
+ <SRS>EPSG:2017</SRS>
+ <SRS>EPSG:2018</SRS>
+ <SRS>EPSG:2019</SRS>
+ <SRS>EPSG:2020</SRS>
+ <SRS>EPSG:2021</SRS>
+ <SRS>EPSG:2022</SRS>
+ <SRS>EPSG:2023</SRS>
+ <SRS>EPSG:2024</SRS>
+ <SRS>EPSG:2025</SRS>
+ <SRS>EPSG:2026</SRS>
+ <SRS>EPSG:2027</SRS>
+ <SRS>EPSG:2028</SRS>
+ <SRS>EPSG:2029</SRS>
+ <SRS>EPSG:2030</SRS>
+ <SRS>EPSG:2031</SRS>
+ <SRS>EPSG:2032</SRS>
+ <SRS>EPSG:2033</SRS>
+ <SRS>EPSG:2034</SRS>
+ <SRS>EPSG:2035</SRS>
+ <SRS>EPSG:2036</SRS>
+ <SRS>EPSG:2037</SRS>
+ <SRS>EPSG:2038</SRS>
+ <SRS>EPSG:2039</SRS>
+ <SRS>EPSG:2040</SRS>
+ <SRS>EPSG:2041</SRS>
+ <SRS>EPSG:2042</SRS>
+ <SRS>EPSG:2043</SRS>
+ <SRS>EPSG:2044</SRS>
+ <SRS>EPSG:2045</SRS>
+ <SRS>EPSG:2046</SRS>
+ <SRS>EPSG:2047</SRS>
+ <SRS>EPSG:2048</SRS>
+ <SRS>EPSG:2049</SRS>
+ <SRS>EPSG:2050</SRS>
+ <SRS>EPSG:2051</SRS>
+ <SRS>EPSG:2052</SRS>
+ <SRS>EPSG:2053</SRS>
+ <SRS>EPSG:2054</SRS>
+ <SRS>EPSG:2055</SRS>
+ <SRS>EPSG:2056</SRS>
+ <SRS>EPSG:2057</SRS>
+ <SRS>EPSG:2058</SRS>
+ <SRS>EPSG:2059</SRS>
+ <SRS>EPSG:2060</SRS>
+ <SRS>EPSG:2061</SRS>
+ <SRS>EPSG:2062</SRS>
+ <SRS>EPSG:2063</SRS>
+ <SRS>EPSG:2064</SRS>
+ <SRS>EPSG:2065</SRS>
+ <SRS>EPSG:2066</SRS>
+ <SRS>EPSG:2067</SRS>
+ <SRS>EPSG:2068</SRS>
+ <SRS>EPSG:2069</SRS>
+ <SRS>EPSG:2070</SRS>
+ <SRS>EPSG:2071</SRS>
+ <SRS>EPSG:2072</SRS>
+ <SRS>EPSG:2073</SRS>
+ <SRS>EPSG:2074</SRS>
+ <SRS>EPSG:2075</SRS>
+ <SRS>EPSG:2076</SRS>
+ <SRS>EPSG:2077</SRS>
+ <SRS>EPSG:2078</SRS>
+ <SRS>EPSG:2079</SRS>
+ <SRS>EPSG:2080</SRS>
+ <SRS>EPSG:2081</SRS>
+ <SRS>EPSG:2082</SRS>
+ <SRS>EPSG:2083</SRS>
+ <SRS>EPSG:2084</SRS>
+ <SRS>EPSG:2085</SRS>
+ <SRS>EPSG:2086</SRS>
+ <SRS>EPSG:2087</SRS>
+ <SRS>EPSG:2088</SRS>
+ <SRS>EPSG:2089</SRS>
+ <SRS>EPSG:2090</SRS>
+ <SRS>EPSG:2091</SRS>
+ <SRS>EPSG:2092</SRS>
+ <SRS>EPSG:2093</SRS>
+ <SRS>EPSG:2094</SRS>
+ <SRS>EPSG:2095</SRS>
+ <SRS>EPSG:2096</SRS>
+ <SRS>EPSG:2097</SRS>
+ <SRS>EPSG:2098</SRS>
+ <SRS>EPSG:2099</SRS>
+ <SRS>EPSG:2100</SRS>
+ <SRS>EPSG:2101</SRS>
+ <SRS>EPSG:2102</SRS>
+ <SRS>EPSG:2103</SRS>
+ <SRS>EPSG:2104</SRS>
+ <SRS>EPSG:2105</SRS>
+ <SRS>EPSG:2106</SRS>
+ <SRS>EPSG:2107</SRS>
+ <SRS>EPSG:2108</SRS>
+ <SRS>EPSG:2109</SRS>
+ <SRS>EPSG:2110</SRS>
+ <SRS>EPSG:2111</SRS>
+ <SRS>EPSG:2112</SRS>
+ <SRS>EPSG:2113</SRS>
+ <SRS>EPSG:2114</SRS>
+ <SRS>EPSG:2115</SRS>
+ <SRS>EPSG:2116</SRS>
+ <SRS>EPSG:2117</SRS>
+ <SRS>EPSG:2118</SRS>
+ <SRS>EPSG:2119</SRS>
+ <SRS>EPSG:2120</SRS>
+ <SRS>EPSG:2121</SRS>
+ <SRS>EPSG:2122</SRS>
+ <SRS>EPSG:2123</SRS>
+ <SRS>EPSG:2124</SRS>
+ <SRS>EPSG:2125</SRS>
+ <SRS>EPSG:2126</SRS>
+ <SRS>EPSG:2127</SRS>
+ <SRS>EPSG:2128</SRS>
+ <SRS>EPSG:2129</SRS>
+ <SRS>EPSG:2130</SRS>
+ <SRS>EPSG:2131</SRS>
+ <SRS>EPSG:2132</SRS>
+ <SRS>EPSG:2133</SRS>
+ <SRS>EPSG:2134</SRS>
+ <SRS>EPSG:2135</SRS>
+ <SRS>EPSG:2136</SRS>
+ <SRS>EPSG:2137</SRS>
+ <SRS>EPSG:2138</SRS>
+ <SRS>EPSG:2139</SRS>
+ <SRS>EPSG:2140</SRS>
+ <SRS>EPSG:2141</SRS>
+ <SRS>EPSG:2142</SRS>
+ <SRS>EPSG:2143</SRS>
+ <SRS>EPSG:2144</SRS>
+ <SRS>EPSG:2145</SRS>
+ <SRS>EPSG:2146</SRS>
+ <SRS>EPSG:2147</SRS>
+ <SRS>EPSG:2148</SRS>
+ <SRS>EPSG:2149</SRS>
+ <SRS>EPSG:2150</SRS>
+ <SRS>EPSG:2151</SRS>
+ <SRS>EPSG:2152</SRS>
+ <SRS>EPSG:2153</SRS>
+ <SRS>EPSG:2154</SRS>
+ <SRS>EPSG:2155</SRS>
+ <SRS>EPSG:2156</SRS>
+ <SRS>EPSG:2157</SRS>
+ <SRS>EPSG:2158</SRS>
+ <SRS>EPSG:2159</SRS>
+ <SRS>EPSG:2160</SRS>
+ <SRS>EPSG:2161</SRS>
+ <SRS>EPSG:2162</SRS>
+ <SRS>EPSG:2163</SRS>
+ <SRS>EPSG:2164</SRS>
+ <SRS>EPSG:2165</SRS>
+ <SRS>EPSG:2166</SRS>
+ <SRS>EPSG:2167</SRS>
+ <SRS>EPSG:2168</SRS>
+ <SRS>EPSG:2169</SRS>
+ <SRS>EPSG:2170</SRS>
+ <SRS>EPSG:2171</SRS>
+ <SRS>EPSG:2172</SRS>
+ <SRS>EPSG:2173</SRS>
+ <SRS>EPSG:2174</SRS>
+ <SRS>EPSG:2175</SRS>
+ <SRS>EPSG:2176</SRS>
+ <SRS>EPSG:2177</SRS>
+ <SRS>EPSG:2178</SRS>
+ <SRS>EPSG:2179</SRS>
+ <SRS>EPSG:2180</SRS>
+ <SRS>EPSG:2188</SRS>
+ <SRS>EPSG:2189</SRS>
+ <SRS>EPSG:2190</SRS>
+ <SRS>EPSG:2191</SRS>
+ <SRS>EPSG:2192</SRS>
+ <SRS>EPSG:2193</SRS>
+ <SRS>EPSG:2194</SRS>
+ <SRS>EPSG:2195</SRS>
+ <SRS>EPSG:2196</SRS>
+ <SRS>EPSG:2197</SRS>
+ <SRS>EPSG:2198</SRS>
+ <SRS>EPSG:2199</SRS>
+ <SRS>EPSG:2200</SRS>
+ <SRS>EPSG:2201</SRS>
+ <SRS>EPSG:2202</SRS>
+ <SRS>EPSG:2203</SRS>
+ <SRS>EPSG:2204</SRS>
+ <SRS>EPSG:2205</SRS>
+ <SRS>EPSG:2206</SRS>
+ <SRS>EPSG:2207</SRS>
+ <SRS>EPSG:2208</SRS>
+ <SRS>EPSG:2209</SRS>
+ <SRS>EPSG:2210</SRS>
+ <SRS>EPSG:2211</SRS>
+ <SRS>EPSG:2212</SRS>
+ <SRS>EPSG:2213</SRS>
+ <SRS>EPSG:2214</SRS>
+ <SRS>EPSG:2215</SRS>
+ <SRS>EPSG:2216</SRS>
+ <SRS>EPSG:2217</SRS>
+ <SRS>EPSG:2218</SRS>
+ <SRS>EPSG:2219</SRS>
+ <SRS>EPSG:2220</SRS>
+ <SRS>EPSG:2221</SRS>
+ <SRS>EPSG:2222</SRS>
+ <SRS>EPSG:2223</SRS>
+ <SRS>EPSG:2224</SRS>
+ <SRS>EPSG:2225</SRS>
+ <SRS>EPSG:2226</SRS>
+ <SRS>EPSG:2227</SRS>
+ <SRS>EPSG:2228</SRS>
+ <SRS>EPSG:2229</SRS>
+ <SRS>EPSG:2230</SRS>
+ <SRS>EPSG:2231</SRS>
+ <SRS>EPSG:2232</SRS>
+ <SRS>EPSG:2233</SRS>
+ <SRS>EPSG:2234</SRS>
+ <SRS>EPSG:2235</SRS>
+ <SRS>EPSG:2236</SRS>
+ <SRS>EPSG:2237</SRS>
+ <SRS>EPSG:2238</SRS>
+ <SRS>EPSG:2239</SRS>
+ <SRS>EPSG:2240</SRS>
+ <SRS>EPSG:2241</SRS>
+ <SRS>EPSG:2242</SRS>
+ <SRS>EPSG:2243</SRS>
+ <SRS>EPSG:2244</SRS>
+ <SRS>EPSG:2245</SRS>
+ <SRS>EPSG:2246</SRS>
+ <SRS>EPSG:2247</SRS>
+ <SRS>EPSG:2248</SRS>
+ <SRS>EPSG:2249</SRS>
+ <SRS>EPSG:2250</SRS>
+ <SRS>EPSG:2251</SRS>
+ <SRS>EPSG:2252</SRS>
+ <SRS>EPSG:2253</SRS>
+ <SRS>EPSG:2254</SRS>
+ <SRS>EPSG:2255</SRS>
+ <SRS>EPSG:2256</SRS>
+ <SRS>EPSG:2257</SRS>
+ <SRS>EPSG:2258</SRS>
+ <SRS>EPSG:2259</SRS>
+ <SRS>EPSG:2260</SRS>
+ <SRS>EPSG:2261</SRS>
+ <SRS>EPSG:2262</SRS>
+ <SRS>EPSG:2263</SRS>
+ <SRS>EPSG:2264</SRS>
+ <SRS>EPSG:2265</SRS>
+ <SRS>EPSG:2266</SRS>
+ <SRS>EPSG:2267</SRS>
+ <SRS>EPSG:2268</SRS>
+ <SRS>EPSG:2269</SRS>
+ <SRS>EPSG:2270</SRS>
+ <SRS>EPSG:2271</SRS>
+ <SRS>EPSG:2272</SRS>
+ <SRS>EPSG:2273</SRS>
+ <SRS>EPSG:2274</SRS>
+ <SRS>EPSG:2275</SRS>
+ <SRS>EPSG:2276</SRS>
+ <SRS>EPSG:2277</SRS>
+ <SRS>EPSG:2278</SRS>
+ <SRS>EPSG:2279</SRS>
+ <SRS>EPSG:2280</SRS>
+ <SRS>EPSG:2281</SRS>
+ <SRS>EPSG:2282</SRS>
+ <SRS>EPSG:2283</SRS>
+ <SRS>EPSG:2284</SRS>
+ <SRS>EPSG:2285</SRS>
+ <SRS>EPSG:2286</SRS>
+ <SRS>EPSG:2287</SRS>
+ <SRS>EPSG:2288</SRS>
+ <SRS>EPSG:2289</SRS>
+ <SRS>EPSG:2290</SRS>
+ <SRS>EPSG:2291</SRS>
+ <SRS>EPSG:2292</SRS>
+ <SRS>EPSG:2294</SRS>
+ <SRS>EPSG:2295</SRS>
+ <SRS>EPSG:2296</SRS>
+ <SRS>EPSG:2297</SRS>
+ <SRS>EPSG:2298</SRS>
+ <SRS>EPSG:2299</SRS>
+ <SRS>EPSG:2300</SRS>
+ <SRS>EPSG:2301</SRS>
+ <SRS>EPSG:2302</SRS>
+ <SRS>EPSG:2303</SRS>
+ <SRS>EPSG:2304</SRS>
+ <SRS>EPSG:2305</SRS>
+ <SRS>EPSG:2306</SRS>
+ <SRS>EPSG:2307</SRS>
+ <SRS>EPSG:2308</SRS>
+ <SRS>EPSG:2309</SRS>
+ <SRS>EPSG:2310</SRS>
+ <SRS>EPSG:2311</SRS>
+ <SRS>EPSG:2312</SRS>
+ <SRS>EPSG:2313</SRS>
+ <SRS>EPSG:2314</SRS>
+ <SRS>EPSG:2315</SRS>
+ <SRS>EPSG:2316</SRS>
+ <SRS>EPSG:2317</SRS>
+ <SRS>EPSG:2318</SRS>
+ <SRS>EPSG:2319</SRS>
+ <SRS>EPSG:2320</SRS>
+ <SRS>EPSG:2321</SRS>
+ <SRS>EPSG:2322</SRS>
+ <SRS>EPSG:2323</SRS>
+ <SRS>EPSG:2324</SRS>
+ <SRS>EPSG:2325</SRS>
+ <SRS>EPSG:2326</SRS>
+ <SRS>EPSG:2327</SRS>
+ <SRS>EPSG:2328</SRS>
+ <SRS>EPSG:2329</SRS>
+ <SRS>EPSG:2330</SRS>
+ <SRS>EPSG:2331</SRS>
+ <SRS>EPSG:2332</SRS>
+ <SRS>EPSG:2333</SRS>
+ <SRS>EPSG:2334</SRS>
+ <SRS>EPSG:2335</SRS>
+ <SRS>EPSG:2336</SRS>
+ <SRS>EPSG:2337</SRS>
+ <SRS>EPSG:2338</SRS>
+ <SRS>EPSG:2339</SRS>
+ <SRS>EPSG:2340</SRS>
+ <SRS>EPSG:2341</SRS>
+ <SRS>EPSG:2342</SRS>
+ <SRS>EPSG:2343</SRS>
+ <SRS>EPSG:2344</SRS>
+ <SRS>EPSG:2345</SRS>
+ <SRS>EPSG:2346</SRS>
+ <SRS>EPSG:2347</SRS>
+ <SRS>EPSG:2348</SRS>
+ <SRS>EPSG:2349</SRS>
+ <SRS>EPSG:2350</SRS>
+ <SRS>EPSG:2351</SRS>
+ <SRS>EPSG:2352</SRS>
+ <SRS>EPSG:2353</SRS>
+ <SRS>EPSG:2354</SRS>
+ <SRS>EPSG:2355</SRS>
+ <SRS>EPSG:2356</SRS>
+ <SRS>EPSG:2357</SRS>
+ <SRS>EPSG:2358</SRS>
+ <SRS>EPSG:2359</SRS>
+ <SRS>EPSG:2360</SRS>
+ <SRS>EPSG:2361</SRS>
+ <SRS>EPSG:2362</SRS>
+ <SRS>EPSG:2363</SRS>
+ <SRS>EPSG:2364</SRS>
+ <SRS>EPSG:2365</SRS>
+ <SRS>EPSG:2366</SRS>
+ <SRS>EPSG:2367</SRS>
+ <SRS>EPSG:2368</SRS>
+ <SRS>EPSG:2369</SRS>
+ <SRS>EPSG:2370</SRS>
+ <SRS>EPSG:2371</SRS>
+ <SRS>EPSG:2372</SRS>
+ <SRS>EPSG:2373</SRS>
+ <SRS>EPSG:2374</SRS>
+ <SRS>EPSG:2375</SRS>
+ <SRS>EPSG:2376</SRS>
+ <SRS>EPSG:2377</SRS>
+ <SRS>EPSG:2378</SRS>
+ <SRS>EPSG:2379</SRS>
+ <SRS>EPSG:2380</SRS>
+ <SRS>EPSG:2381</SRS>
+ <SRS>EPSG:2382</SRS>
+ <SRS>EPSG:2383</SRS>
+ <SRS>EPSG:2384</SRS>
+ <SRS>EPSG:2385</SRS>
+ <SRS>EPSG:2386</SRS>
+ <SRS>EPSG:2387</SRS>
+ <SRS>EPSG:2388</SRS>
+ <SRS>EPSG:2389</SRS>
+ <SRS>EPSG:2390</SRS>
+ <SRS>EPSG:2391</SRS>
+ <SRS>EPSG:2392</SRS>
+ <SRS>EPSG:2393</SRS>
+ <SRS>EPSG:2394</SRS>
+ <SRS>EPSG:2395</SRS>
+ <SRS>EPSG:2396</SRS>
+ <SRS>EPSG:2397</SRS>
+ <SRS>EPSG:2398</SRS>
+ <SRS>EPSG:2399</SRS>
+ <SRS>EPSG:2400</SRS>
+ <SRS>EPSG:2401</SRS>
+ <SRS>EPSG:2402</SRS>
+ <SRS>EPSG:2403</SRS>
+ <SRS>EPSG:2404</SRS>
+ <SRS>EPSG:2405</SRS>
+ <SRS>EPSG:2406</SRS>
+ <SRS>EPSG:2407</SRS>
+ <SRS>EPSG:2408</SRS>
+ <SRS>EPSG:2409</SRS>
+ <SRS>EPSG:2410</SRS>
+ <SRS>EPSG:2411</SRS>
+ <SRS>EPSG:2412</SRS>
+ <SRS>EPSG:2413</SRS>
+ <SRS>EPSG:2414</SRS>
+ <SRS>EPSG:2415</SRS>
+ <SRS>EPSG:2416</SRS>
+ <SRS>EPSG:2417</SRS>
+ <SRS>EPSG:2418</SRS>
+ <SRS>EPSG:2419</SRS>
+ <SRS>EPSG:2420</SRS>
+ <SRS>EPSG:2421</SRS>
+ <SRS>EPSG:2422</SRS>
+ <SRS>EPSG:2423</SRS>
+ <SRS>EPSG:2424</SRS>
+ <SRS>EPSG:2425</SRS>
+ <SRS>EPSG:2426</SRS>
+ <SRS>EPSG:2427</SRS>
+ <SRS>EPSG:2428</SRS>
+ <SRS>EPSG:2429</SRS>
+ <SRS>EPSG:2430</SRS>
+ <SRS>EPSG:2431</SRS>
+ <SRS>EPSG:2432</SRS>
+ <SRS>EPSG:2433</SRS>
+ <SRS>EPSG:2434</SRS>
+ <SRS>EPSG:2435</SRS>
+ <SRS>EPSG:2436</SRS>
+ <SRS>EPSG:2437</SRS>
+ <SRS>EPSG:2438</SRS>
+ <SRS>EPSG:2439</SRS>
+ <SRS>EPSG:2440</SRS>
+ <SRS>EPSG:2441</SRS>
+ <SRS>EPSG:2442</SRS>
+ <SRS>EPSG:2443</SRS>
+ <SRS>EPSG:2444</SRS>
+ <SRS>EPSG:2445</SRS>
+ <SRS>EPSG:2446</SRS>
+ <SRS>EPSG:2447</SRS>
+ <SRS>EPSG:2448</SRS>
+ <SRS>EPSG:2449</SRS>
+ <SRS>EPSG:2450</SRS>
+ <SRS>EPSG:2451</SRS>
+ <SRS>EPSG:2452</SRS>
+ <SRS>EPSG:2453</SRS>
+ <SRS>EPSG:2454</SRS>
+ <SRS>EPSG:2455</SRS>
+ <SRS>EPSG:2456</SRS>
+ <SRS>EPSG:2457</SRS>
+ <SRS>EPSG:2458</SRS>
+ <SRS>EPSG:2459</SRS>
+ <SRS>EPSG:2460</SRS>
+ <SRS>EPSG:2461</SRS>
+ <SRS>EPSG:2462</SRS>
+ <SRS>EPSG:2463</SRS>
+ <SRS>EPSG:2464</SRS>
+ <SRS>EPSG:2465</SRS>
+ <SRS>EPSG:2466</SRS>
+ <SRS>EPSG:2467</SRS>
+ <SRS>EPSG:2468</SRS>
+ <SRS>EPSG:2469</SRS>
+ <SRS>EPSG:2470</SRS>
+ <SRS>EPSG:2471</SRS>
+ <SRS>EPSG:2472</SRS>
+ <SRS>EPSG:2473</SRS>
+ <SRS>EPSG:2474</SRS>
+ <SRS>EPSG:2475</SRS>
+ <SRS>EPSG:2476</SRS>
+ <SRS>EPSG:2477</SRS>
+ <SRS>EPSG:2478</SRS>
+ <SRS>EPSG:2479</SRS>
+ <SRS>EPSG:2480</SRS>
+ <SRS>EPSG:2481</SRS>
+ <SRS>EPSG:2482</SRS>
+ <SRS>EPSG:2483</SRS>
+ <SRS>EPSG:2484</SRS>
+ <SRS>EPSG:2485</SRS>
+ <SRS>EPSG:2486</SRS>
+ <SRS>EPSG:2487</SRS>
+ <SRS>EPSG:2488</SRS>
+ <SRS>EPSG:2489</SRS>
+ <SRS>EPSG:2490</SRS>
+ <SRS>EPSG:2491</SRS>
+ <SRS>EPSG:2492</SRS>
+ <SRS>EPSG:2493</SRS>
+ <SRS>EPSG:2494</SRS>
+ <SRS>EPSG:2495</SRS>
+ <SRS>EPSG:2496</SRS>
+ <SRS>EPSG:2497</SRS>
+ <SRS>EPSG:2498</SRS>
+ <SRS>EPSG:2499</SRS>
+ <SRS>EPSG:2500</SRS>
+ <SRS>EPSG:2501</SRS>
+ <SRS>EPSG:2502</SRS>
+ <SRS>EPSG:2503</SRS>
+ <SRS>EPSG:2504</SRS>
+ <SRS>EPSG:2505</SRS>
+ <SRS>EPSG:2506</SRS>
+ <SRS>EPSG:2507</SRS>
+ <SRS>EPSG:2508</SRS>
+ <SRS>EPSG:2509</SRS>
+ <SRS>EPSG:2510</SRS>
+ <SRS>EPSG:2511</SRS>
+ <SRS>EPSG:2512</SRS>
+ <SRS>EPSG:2513</SRS>
+ <SRS>EPSG:2514</SRS>
+ <SRS>EPSG:2515</SRS>
+ <SRS>EPSG:2516</SRS>
+ <SRS>EPSG:2517</SRS>
+ <SRS>EPSG:2518</SRS>
+ <SRS>EPSG:2519</SRS>
+ <SRS>EPSG:2520</SRS>
+ <SRS>EPSG:2521</SRS>
+ <SRS>EPSG:2522</SRS>
+ <SRS>EPSG:2523</SRS>
+ <SRS>EPSG:2524</SRS>
+ <SRS>EPSG:2525</SRS>
+ <SRS>EPSG:2526</SRS>
+ <SRS>EPSG:2527</SRS>
+ <SRS>EPSG:2528</SRS>
+ <SRS>EPSG:2529</SRS>
+ <SRS>EPSG:2530</SRS>
+ <SRS>EPSG:2531</SRS>
+ <SRS>EPSG:2532</SRS>
+ <SRS>EPSG:2533</SRS>
+ <SRS>EPSG:2534</SRS>
+ <SRS>EPSG:2535</SRS>
+ <SRS>EPSG:2536</SRS>
+ <SRS>EPSG:2537</SRS>
+ <SRS>EPSG:2538</SRS>
+ <SRS>EPSG:2539</SRS>
+ <SRS>EPSG:2540</SRS>
+ <SRS>EPSG:2541</SRS>
+ <SRS>EPSG:2542</SRS>
+ <SRS>EPSG:2543</SRS>
+ <SRS>EPSG:2544</SRS>
+ <SRS>EPSG:2545</SRS>
+ <SRS>EPSG:2546</SRS>
+ <SRS>EPSG:2547</SRS>
+ <SRS>EPSG:2548</SRS>
+ <SRS>EPSG:2549</SRS>
+ <SRS>EPSG:2550</SRS>
+ <SRS>EPSG:2551</SRS>
+ <SRS>EPSG:2552</SRS>
+ <SRS>EPSG:2553</SRS>
+ <SRS>EPSG:2554</SRS>
+ <SRS>EPSG:2555</SRS>
+ <SRS>EPSG:2556</SRS>
+ <SRS>EPSG:2557</SRS>
+ <SRS>EPSG:2558</SRS>
+ <SRS>EPSG:2559</SRS>
+ <SRS>EPSG:2560</SRS>
+ <SRS>EPSG:2561</SRS>
+ <SRS>EPSG:2562</SRS>
+ <SRS>EPSG:2563</SRS>
+ <SRS>EPSG:2564</SRS>
+ <SRS>EPSG:2565</SRS>
+ <SRS>EPSG:2566</SRS>
+ <SRS>EPSG:2567</SRS>
+ <SRS>EPSG:2568</SRS>
+ <SRS>EPSG:2569</SRS>
+ <SRS>EPSG:2570</SRS>
+ <SRS>EPSG:2571</SRS>
+ <SRS>EPSG:2572</SRS>
+ <SRS>EPSG:2573</SRS>
+ <SRS>EPSG:2574</SRS>
+ <SRS>EPSG:2575</SRS>
+ <SRS>EPSG:2576</SRS>
+ <SRS>EPSG:2577</SRS>
+ <SRS>EPSG:2578</SRS>
+ <SRS>EPSG:2579</SRS>
+ <SRS>EPSG:2580</SRS>
+ <SRS>EPSG:2581</SRS>
+ <SRS>EPSG:2582</SRS>
+ <SRS>EPSG:2583</SRS>
+ <SRS>EPSG:2584</SRS>
+ <SRS>EPSG:2585</SRS>
+ <SRS>EPSG:2586</SRS>
+ <SRS>EPSG:2587</SRS>
+ <SRS>EPSG:2588</SRS>
+ <SRS>EPSG:2589</SRS>
+ <SRS>EPSG:2590</SRS>
+ <SRS>EPSG:2591</SRS>
+ <SRS>EPSG:2592</SRS>
+ <SRS>EPSG:2593</SRS>
+ <SRS>EPSG:2594</SRS>
+ <SRS>EPSG:2595</SRS>
+ <SRS>EPSG:2596</SRS>
+ <SRS>EPSG:2597</SRS>
+ <SRS>EPSG:2598</SRS>
+ <SRS>EPSG:2599</SRS>
+ <SRS>EPSG:2600</SRS>
+ <SRS>EPSG:2601</SRS>
+ <SRS>EPSG:2602</SRS>
+ <SRS>EPSG:2603</SRS>
+ <SRS>EPSG:2604</SRS>
+ <SRS>EPSG:2605</SRS>
+ <SRS>EPSG:2606</SRS>
+ <SRS>EPSG:2607</SRS>
+ <SRS>EPSG:2608</SRS>
+ <SRS>EPSG:2609</SRS>
+ <SRS>EPSG:2610</SRS>
+ <SRS>EPSG:2611</SRS>
+ <SRS>EPSG:2612</SRS>
+ <SRS>EPSG:2613</SRS>
+ <SRS>EPSG:2614</SRS>
+ <SRS>EPSG:2615</SRS>
+ <SRS>EPSG:2616</SRS>
+ <SRS>EPSG:2617</SRS>
+ <SRS>EPSG:2618</SRS>
+ <SRS>EPSG:2619</SRS>
+ <SRS>EPSG:2620</SRS>
+ <SRS>EPSG:2621</SRS>
+ <SRS>EPSG:2622</SRS>
+ <SRS>EPSG:2623</SRS>
+ <SRS>EPSG:2624</SRS>
+ <SRS>EPSG:2625</SRS>
+ <SRS>EPSG:2626</SRS>
+ <SRS>EPSG:2627</SRS>
+ <SRS>EPSG:2628</SRS>
+ <SRS>EPSG:2629</SRS>
+ <SRS>EPSG:2630</SRS>
+ <SRS>EPSG:2631</SRS>
+ <SRS>EPSG:2632</SRS>
+ <SRS>EPSG:2633</SRS>
+ <SRS>EPSG:2634</SRS>
+ <SRS>EPSG:2635</SRS>
+ <SRS>EPSG:2636</SRS>
+ <SRS>EPSG:2637</SRS>
+ <SRS>EPSG:2638</SRS>
+ <SRS>EPSG:2639</SRS>
+ <SRS>EPSG:2640</SRS>
+ <SRS>EPSG:2641</SRS>
+ <SRS>EPSG:2642</SRS>
+ <SRS>EPSG:2643</SRS>
+ <SRS>EPSG:2644</SRS>
+ <SRS>EPSG:2645</SRS>
+ <SRS>EPSG:2646</SRS>
+ <SRS>EPSG:2647</SRS>
+ <SRS>EPSG:2648</SRS>
+ <SRS>EPSG:2649</SRS>
+ <SRS>EPSG:2650</SRS>
+ <SRS>EPSG:2651</SRS>
+ <SRS>EPSG:2652</SRS>
+ <SRS>EPSG:2653</SRS>
+ <SRS>EPSG:2654</SRS>
+ <SRS>EPSG:2655</SRS>
+ <SRS>EPSG:2656</SRS>
+ <SRS>EPSG:2657</SRS>
+ <SRS>EPSG:2658</SRS>
+ <SRS>EPSG:2659</SRS>
+ <SRS>EPSG:2660</SRS>
+ <SRS>EPSG:2661</SRS>
+ <SRS>EPSG:2662</SRS>
+ <SRS>EPSG:2663</SRS>
+ <SRS>EPSG:2664</SRS>
+ <SRS>EPSG:2665</SRS>
+ <SRS>EPSG:2666</SRS>
+ <SRS>EPSG:2667</SRS>
+ <SRS>EPSG:2668</SRS>
+ <SRS>EPSG:2669</SRS>
+ <SRS>EPSG:2670</SRS>
+ <SRS>EPSG:2671</SRS>
+ <SRS>EPSG:2672</SRS>
+ <SRS>EPSG:2673</SRS>
+ <SRS>EPSG:2674</SRS>
+ <SRS>EPSG:2675</SRS>
+ <SRS>EPSG:2676</SRS>
+ <SRS>EPSG:2677</SRS>
+ <SRS>EPSG:2678</SRS>
+ <SRS>EPSG:2679</SRS>
+ <SRS>EPSG:2680</SRS>
+ <SRS>EPSG:2681</SRS>
+ <SRS>EPSG:2682</SRS>
+ <SRS>EPSG:2683</SRS>
+ <SRS>EPSG:2684</SRS>
+ <SRS>EPSG:2685</SRS>
+ <SRS>EPSG:2686</SRS>
+ <SRS>EPSG:2687</SRS>
+ <SRS>EPSG:2688</SRS>
+ <SRS>EPSG:2689</SRS>
+ <SRS>EPSG:2690</SRS>
+ <SRS>EPSG:2691</SRS>
+ <SRS>EPSG:2692</SRS>
+ <SRS>EPSG:2693</SRS>
+ <SRS>EPSG:2694</SRS>
+ <SRS>EPSG:2695</SRS>
+ <SRS>EPSG:2696</SRS>
+ <SRS>EPSG:2697</SRS>
+ <SRS>EPSG:2698</SRS>
+ <SRS>EPSG:2699</SRS>
+ <SRS>EPSG:2700</SRS>
+ <SRS>EPSG:2701</SRS>
+ <SRS>EPSG:2702</SRS>
+ <SRS>EPSG:2703</SRS>
+ <SRS>EPSG:2704</SRS>
+ <SRS>EPSG:2705</SRS>
+ <SRS>EPSG:2706</SRS>
+ <SRS>EPSG:2707</SRS>
+ <SRS>EPSG:2708</SRS>
+ <SRS>EPSG:2709</SRS>
+ <SRS>EPSG:2710</SRS>
+ <SRS>EPSG:2711</SRS>
+ <SRS>EPSG:2712</SRS>
+ <SRS>EPSG:2713</SRS>
+ <SRS>EPSG:2714</SRS>
+ <SRS>EPSG:2715</SRS>
+ <SRS>EPSG:2716</SRS>
+ <SRS>EPSG:2717</SRS>
+ <SRS>EPSG:2718</SRS>
+ <SRS>EPSG:2719</SRS>
+ <SRS>EPSG:2720</SRS>
+ <SRS>EPSG:2721</SRS>
+ <SRS>EPSG:2722</SRS>
+ <SRS>EPSG:2723</SRS>
+ <SRS>EPSG:2724</SRS>
+ <SRS>EPSG:2725</SRS>
+ <SRS>EPSG:2726</SRS>
+ <SRS>EPSG:2727</SRS>
+ <SRS>EPSG:2728</SRS>
+ <SRS>EPSG:2729</SRS>
+ <SRS>EPSG:2730</SRS>
+ <SRS>EPSG:2731</SRS>
+ <SRS>EPSG:2732</SRS>
+ <SRS>EPSG:2733</SRS>
+ <SRS>EPSG:2734</SRS>
+ <SRS>EPSG:2735</SRS>
+ <SRS>EPSG:2736</SRS>
+ <SRS>EPSG:2737</SRS>
+ <SRS>EPSG:2738</SRS>
+ <SRS>EPSG:2739</SRS>
+ <SRS>EPSG:2740</SRS>
+ <SRS>EPSG:2741</SRS>
+ <SRS>EPSG:2742</SRS>
+ <SRS>EPSG:2743</SRS>
+ <SRS>EPSG:2744</SRS>
+ <SRS>EPSG:2745</SRS>
+ <SRS>EPSG:2746</SRS>
+ <SRS>EPSG:2747</SRS>
+ <SRS>EPSG:2748</SRS>
+ <SRS>EPSG:2749</SRS>
+ <SRS>EPSG:2750</SRS>
+ <SRS>EPSG:2751</SRS>
+ <SRS>EPSG:2752</SRS>
+ <SRS>EPSG:2753</SRS>
+ <SRS>EPSG:2754</SRS>
+ <SRS>EPSG:2755</SRS>
+ <SRS>EPSG:2756</SRS>
+ <SRS>EPSG:2757</SRS>
+ <SRS>EPSG:2758</SRS>
+ <SRS>EPSG:2759</SRS>
+ <SRS>EPSG:2760</SRS>
+ <SRS>EPSG:2761</SRS>
+ <SRS>EPSG:2762</SRS>
+ <SRS>EPSG:2763</SRS>
+ <SRS>EPSG:2764</SRS>
+ <SRS>EPSG:2765</SRS>
+ <SRS>EPSG:2766</SRS>
+ <SRS>EPSG:2767</SRS>
+ <SRS>EPSG:2768</SRS>
+ <SRS>EPSG:2769</SRS>
+ <SRS>EPSG:2770</SRS>
+ <SRS>EPSG:2771</SRS>
+ <SRS>EPSG:2772</SRS>
+ <SRS>EPSG:2773</SRS>
+ <SRS>EPSG:2774</SRS>
+ <SRS>EPSG:2775</SRS>
+ <SRS>EPSG:2776</SRS>
+ <SRS>EPSG:2777</SRS>
+ <SRS>EPSG:2778</SRS>
+ <SRS>EPSG:2779</SRS>
+ <SRS>EPSG:2780</SRS>
+ <SRS>EPSG:2781</SRS>
+ <SRS>EPSG:2782</SRS>
+ <SRS>EPSG:2783</SRS>
+ <SRS>EPSG:2784</SRS>
+ <SRS>EPSG:2785</SRS>
+ <SRS>EPSG:2786</SRS>
+ <SRS>EPSG:2787</SRS>
+ <SRS>EPSG:2788</SRS>
+ <SRS>EPSG:2789</SRS>
+ <SRS>EPSG:2790</SRS>
+ <SRS>EPSG:2791</SRS>
+ <SRS>EPSG:2792</SRS>
+ <SRS>EPSG:2793</SRS>
+ <SRS>EPSG:2794</SRS>
+ <SRS>EPSG:2795</SRS>
+ <SRS>EPSG:2796</SRS>
+ <SRS>EPSG:2797</SRS>
+ <SRS>EPSG:2798</SRS>
+ <SRS>EPSG:2799</SRS>
+ <SRS>EPSG:2800</SRS>
+ <SRS>EPSG:2801</SRS>
+ <SRS>EPSG:2802</SRS>
+ <SRS>EPSG:2803</SRS>
+ <SRS>EPSG:2804</SRS>
+ <SRS>EPSG:2805</SRS>
+ <SRS>EPSG:2806</SRS>
+ <SRS>EPSG:2807</SRS>
+ <SRS>EPSG:2808</SRS>
+ <SRS>EPSG:2809</SRS>
+ <SRS>EPSG:2810</SRS>
+ <SRS>EPSG:2811</SRS>
+ <SRS>EPSG:2812</SRS>
+ <SRS>EPSG:2813</SRS>
+ <SRS>EPSG:2814</SRS>
+ <SRS>EPSG:2815</SRS>
+ <SRS>EPSG:2816</SRS>
+ <SRS>EPSG:2817</SRS>
+ <SRS>EPSG:2818</SRS>
+ <SRS>EPSG:2819</SRS>
+ <SRS>EPSG:2820</SRS>
+ <SRS>EPSG:2821</SRS>
+ <SRS>EPSG:2822</SRS>
+ <SRS>EPSG:2823</SRS>
+ <SRS>EPSG:2824</SRS>
+ <SRS>EPSG:2825</SRS>
+ <SRS>EPSG:2826</SRS>
+ <SRS>EPSG:2827</SRS>
+ <SRS>EPSG:2828</SRS>
+ <SRS>EPSG:2829</SRS>
+ <SRS>EPSG:2830</SRS>
+ <SRS>EPSG:2831</SRS>
+ <SRS>EPSG:2832</SRS>
+ <SRS>EPSG:2833</SRS>
+ <SRS>EPSG:2834</SRS>
+ <SRS>EPSG:2835</SRS>
+ <SRS>EPSG:2836</SRS>
+ <SRS>EPSG:2837</SRS>
+ <SRS>EPSG:2838</SRS>
+ <SRS>EPSG:2839</SRS>
+ <SRS>EPSG:2840</SRS>
+ <SRS>EPSG:2841</SRS>
+ <SRS>EPSG:2842</SRS>
+ <SRS>EPSG:2843</SRS>
+ <SRS>EPSG:2844</SRS>
+ <SRS>EPSG:2845</SRS>
+ <SRS>EPSG:2846</SRS>
+ <SRS>EPSG:2847</SRS>
+ <SRS>EPSG:2848</SRS>
+ <SRS>EPSG:2849</SRS>
+ <SRS>EPSG:2850</SRS>
+ <SRS>EPSG:2851</SRS>
+ <SRS>EPSG:2852</SRS>
+ <SRS>EPSG:2853</SRS>
+ <SRS>EPSG:2854</SRS>
+ <SRS>EPSG:2855</SRS>
+ <SRS>EPSG:2856</SRS>
+ <SRS>EPSG:2857</SRS>
+ <SRS>EPSG:2858</SRS>
+ <SRS>EPSG:2859</SRS>
+ <SRS>EPSG:2860</SRS>
+ <SRS>EPSG:2861</SRS>
+ <SRS>EPSG:2862</SRS>
+ <SRS>EPSG:2863</SRS>
+ <SRS>EPSG:2864</SRS>
+ <SRS>EPSG:2865</SRS>
+ <SRS>EPSG:2866</SRS>
+ <SRS>EPSG:2867</SRS>
+ <SRS>EPSG:2868</SRS>
+ <SRS>EPSG:2869</SRS>
+ <SRS>EPSG:2870</SRS>
+ <SRS>EPSG:2871</SRS>
+ <SRS>EPSG:2872</SRS>
+ <SRS>EPSG:2873</SRS>
+ <SRS>EPSG:2874</SRS>
+ <SRS>EPSG:2875</SRS>
+ <SRS>EPSG:2876</SRS>
+ <SRS>EPSG:2877</SRS>
+ <SRS>EPSG:2878</SRS>
+ <SRS>EPSG:2879</SRS>
+ <SRS>EPSG:2880</SRS>
+ <SRS>EPSG:2881</SRS>
+ <SRS>EPSG:2882</SRS>
+ <SRS>EPSG:2883</SRS>
+ <SRS>EPSG:2884</SRS>
+ <SRS>EPSG:2885</SRS>
+ <SRS>EPSG:2886</SRS>
+ <SRS>EPSG:2887</SRS>
+ <SRS>EPSG:2888</SRS>
+ <SRS>EPSG:2889</SRS>
+ <SRS>EPSG:2890</SRS>
+ <SRS>EPSG:2891</SRS>
+ <SRS>EPSG:2892</SRS>
+ <SRS>EPSG:2893</SRS>
+ <SRS>EPSG:2894</SRS>
+ <SRS>EPSG:2895</SRS>
+ <SRS>EPSG:2896</SRS>
+ <SRS>EPSG:2897</SRS>
+ <SRS>EPSG:2898</SRS>
+ <SRS>EPSG:2899</SRS>
+ <SRS>EPSG:2900</SRS>
+ <SRS>EPSG:2901</SRS>
+ <SRS>EPSG:2902</SRS>
+ <SRS>EPSG:2903</SRS>
+ <SRS>EPSG:2904</SRS>
+ <SRS>EPSG:2905</SRS>
+ <SRS>EPSG:2906</SRS>
+ <SRS>EPSG:2907</SRS>
+ <SRS>EPSG:2908</SRS>
+ <SRS>EPSG:2909</SRS>
+ <SRS>EPSG:2910</SRS>
+ <SRS>EPSG:2911</SRS>
+ <SRS>EPSG:2912</SRS>
+ <SRS>EPSG:2913</SRS>
+ <SRS>EPSG:2914</SRS>
+ <SRS>EPSG:2915</SRS>
+ <SRS>EPSG:2916</SRS>
+ <SRS>EPSG:2917</SRS>
+ <SRS>EPSG:2918</SRS>
+ <SRS>EPSG:2919</SRS>
+ <SRS>EPSG:2920</SRS>
+ <SRS>EPSG:2921</SRS>
+ <SRS>EPSG:2922</SRS>
+ <SRS>EPSG:2923</SRS>
+ <SRS>EPSG:2924</SRS>
+ <SRS>EPSG:2925</SRS>
+ <SRS>EPSG:2926</SRS>
+ <SRS>EPSG:2927</SRS>
+ <SRS>EPSG:2928</SRS>
+ <SRS>EPSG:2929</SRS>
+ <SRS>EPSG:2930</SRS>
+ <SRS>EPSG:2931</SRS>
+ <SRS>EPSG:2932</SRS>
+ <SRS>EPSG:2933</SRS>
+ <SRS>EPSG:2934</SRS>
+ <SRS>EPSG:2935</SRS>
+ <SRS>EPSG:2936</SRS>
+ <SRS>EPSG:2937</SRS>
+ <SRS>EPSG:2938</SRS>
+ <SRS>EPSG:2939</SRS>
+ <SRS>EPSG:2940</SRS>
+ <SRS>EPSG:2941</SRS>
+ <SRS>EPSG:2942</SRS>
+ <SRS>EPSG:2943</SRS>
+ <SRS>EPSG:2944</SRS>
+ <SRS>EPSG:2945</SRS>
+ <SRS>EPSG:2946</SRS>
+ <SRS>EPSG:2947</SRS>
+ <SRS>EPSG:2948</SRS>
+ <SRS>EPSG:2949</SRS>
+ <SRS>EPSG:2950</SRS>
+ <SRS>EPSG:2951</SRS>
+ <SRS>EPSG:2952</SRS>
+ <SRS>EPSG:2953</SRS>
+ <SRS>EPSG:2954</SRS>
+ <SRS>EPSG:2955</SRS>
+ <SRS>EPSG:2956</SRS>
+ <SRS>EPSG:2957</SRS>
+ <SRS>EPSG:2958</SRS>
+ <SRS>EPSG:2959</SRS>
+ <SRS>EPSG:2960</SRS>
+ <SRS>EPSG:2961</SRS>
+ <SRS>EPSG:2962</SRS>
+ <SRS>EPSG:2963</SRS>
+ <SRS>EPSG:2964</SRS>
+ <SRS>EPSG:2965</SRS>
+ <SRS>EPSG:2966</SRS>
+ <SRS>EPSG:2967</SRS>
+ <SRS>EPSG:2968</SRS>
+ <SRS>EPSG:2969</SRS>
+ <SRS>EPSG:2970</SRS>
+ <SRS>EPSG:2971</SRS>
+ <SRS>EPSG:2972</SRS>
+ <SRS>EPSG:2973</SRS>
+ <SRS>EPSG:2975</SRS>
+ <SRS>EPSG:2976</SRS>
+ <SRS>EPSG:2977</SRS>
+ <SRS>EPSG:2978</SRS>
+ <SRS>EPSG:2979</SRS>
+ <SRS>EPSG:2980</SRS>
+ <SRS>EPSG:2981</SRS>
+ <SRS>EPSG:2982</SRS>
+ <SRS>EPSG:2983</SRS>
+ <SRS>EPSG:2984</SRS>
+ <SRS>EPSG:2985</SRS>
+ <SRS>EPSG:2986</SRS>
+ <SRS>EPSG:2987</SRS>
+ <SRS>EPSG:2988</SRS>
+ <SRS>EPSG:2989</SRS>
+ <SRS>EPSG:2990</SRS>
+ <SRS>EPSG:2991</SRS>
+ <SRS>EPSG:2992</SRS>
+ <SRS>EPSG:2993</SRS>
+ <SRS>EPSG:2994</SRS>
+ <SRS>EPSG:2995</SRS>
+ <SRS>EPSG:2996</SRS>
+ <SRS>EPSG:2997</SRS>
+ <SRS>EPSG:2998</SRS>
+ <SRS>EPSG:2999</SRS>
+ <SRS>EPSG:3000</SRS>
+ <SRS>EPSG:3001</SRS>
+ <SRS>EPSG:3002</SRS>
+ <SRS>EPSG:3003</SRS>
+ <SRS>EPSG:3004</SRS>
+ <SRS>EPSG:3005</SRS>
+ <SRS>EPSG:3006</SRS>
+ <SRS>EPSG:3007</SRS>
+ <SRS>EPSG:3008</SRS>
+ <SRS>EPSG:3009</SRS>
+ <SRS>EPSG:3010</SRS>
+ <SRS>EPSG:3011</SRS>
+ <SRS>EPSG:3012</SRS>
+ <SRS>EPSG:3013</SRS>
+ <SRS>EPSG:3014</SRS>
+ <SRS>EPSG:3015</SRS>
+ <SRS>EPSG:3016</SRS>
+ <SRS>EPSG:3017</SRS>
+ <SRS>EPSG:3018</SRS>
+ <SRS>EPSG:3019</SRS>
+ <SRS>EPSG:3020</SRS>
+ <SRS>EPSG:3021</SRS>
+ <SRS>EPSG:3022</SRS>
+ <SRS>EPSG:3023</SRS>
+ <SRS>EPSG:3024</SRS>
+ <SRS>EPSG:3025</SRS>
+ <SRS>EPSG:3026</SRS>
+ <SRS>EPSG:3027</SRS>
+ <SRS>EPSG:3028</SRS>
+ <SRS>EPSG:3029</SRS>
+ <SRS>EPSG:3030</SRS>
+ <SRS>EPSG:3031</SRS>
+ <SRS>EPSG:3032</SRS>
+ <SRS>EPSG:3033</SRS>
+ <SRS>EPSG:3034</SRS>
+ <SRS>EPSG:3035</SRS>
+ <SRS>EPSG:3036</SRS>
+ <SRS>EPSG:3037</SRS>
+ <SRS>EPSG:3038</SRS>
+ <SRS>EPSG:3039</SRS>
+ <SRS>EPSG:3040</SRS>
+ <SRS>EPSG:3041</SRS>
+ <SRS>EPSG:3042</SRS>
+ <SRS>EPSG:3043</SRS>
+ <SRS>EPSG:3044</SRS>
+ <SRS>EPSG:3045</SRS>
+ <SRS>EPSG:3046</SRS>
+ <SRS>EPSG:3047</SRS>
+ <SRS>EPSG:3048</SRS>
+ <SRS>EPSG:3049</SRS>
+ <SRS>EPSG:3050</SRS>
+ <SRS>EPSG:3051</SRS>
+ <SRS>EPSG:3052</SRS>
+ <SRS>EPSG:3053</SRS>
+ <SRS>EPSG:3054</SRS>
+ <SRS>EPSG:3055</SRS>
+ <SRS>EPSG:3056</SRS>
+ <SRS>EPSG:3057</SRS>
+ <SRS>EPSG:3058</SRS>
+ <SRS>EPSG:3059</SRS>
+ <SRS>EPSG:3060</SRS>
+ <SRS>EPSG:3061</SRS>
+ <SRS>EPSG:3062</SRS>
+ <SRS>EPSG:3063</SRS>
+ <SRS>EPSG:3064</SRS>
+ <SRS>EPSG:3065</SRS>
+ <SRS>EPSG:3066</SRS>
+ <SRS>EPSG:3067</SRS>
+ <SRS>EPSG:3068</SRS>
+ <SRS>EPSG:3069</SRS>
+ <SRS>EPSG:3070</SRS>
+ <SRS>EPSG:3071</SRS>
+ <SRS>EPSG:3072</SRS>
+ <SRS>EPSG:3073</SRS>
+ <SRS>EPSG:3074</SRS>
+ <SRS>EPSG:3075</SRS>
+ <SRS>EPSG:3076</SRS>
+ <SRS>EPSG:3077</SRS>
+ <SRS>EPSG:3078</SRS>
+ <SRS>EPSG:3079</SRS>
+ <SRS>EPSG:3080</SRS>
+ <SRS>EPSG:3081</SRS>
+ <SRS>EPSG:3082</SRS>
+ <SRS>EPSG:3083</SRS>
+ <SRS>EPSG:3084</SRS>
+ <SRS>EPSG:3085</SRS>
+ <SRS>EPSG:3086</SRS>
+ <SRS>EPSG:3087</SRS>
+ <SRS>EPSG:3088</SRS>
+ <SRS>EPSG:3089</SRS>
+ <SRS>EPSG:3090</SRS>
+ <SRS>EPSG:3091</SRS>
+ <SRS>EPSG:3092</SRS>
+ <SRS>EPSG:3093</SRS>
+ <SRS>EPSG:3094</SRS>
+ <SRS>EPSG:3095</SRS>
+ <SRS>EPSG:3096</SRS>
+ <SRS>EPSG:3097</SRS>
+ <SRS>EPSG:3098</SRS>
+ <SRS>EPSG:3099</SRS>
+ <SRS>EPSG:3100</SRS>
+ <SRS>EPSG:3101</SRS>
+ <SRS>EPSG:3102</SRS>
+ <SRS>EPSG:3103</SRS>
+ <SRS>EPSG:3104</SRS>
+ <SRS>EPSG:3105</SRS>
+ <SRS>EPSG:3106</SRS>
+ <SRS>EPSG:3107</SRS>
+ <SRS>EPSG:3108</SRS>
+ <SRS>EPSG:3109</SRS>
+ <SRS>EPSG:3110</SRS>
+ <SRS>EPSG:3111</SRS>
+ <SRS>EPSG:3112</SRS>
+ <SRS>EPSG:3113</SRS>
+ <SRS>EPSG:3114</SRS>
+ <SRS>EPSG:3115</SRS>
+ <SRS>EPSG:3116</SRS>
+ <SRS>EPSG:3117</SRS>
+ <SRS>EPSG:3118</SRS>
+ <SRS>EPSG:3119</SRS>
+ <SRS>EPSG:3120</SRS>
+ <SRS>EPSG:3121</SRS>
+ <SRS>EPSG:3122</SRS>
+ <SRS>EPSG:3123</SRS>
+ <SRS>EPSG:3124</SRS>
+ <SRS>EPSG:3125</SRS>
+ <SRS>EPSG:3126</SRS>
+ <SRS>EPSG:3127</SRS>
+ <SRS>EPSG:3128</SRS>
+ <SRS>EPSG:3129</SRS>
+ <SRS>EPSG:3130</SRS>
+ <SRS>EPSG:3131</SRS>
+ <SRS>EPSG:3132</SRS>
+ <SRS>EPSG:3133</SRS>
+ <SRS>EPSG:3134</SRS>
+ <SRS>EPSG:3135</SRS>
+ <SRS>EPSG:3136</SRS>
+ <SRS>EPSG:3137</SRS>
+ <SRS>EPSG:3138</SRS>
+ <SRS>EPSG:3139</SRS>
+ <SRS>EPSG:3140</SRS>
+ <SRS>EPSG:3141</SRS>
+ <SRS>EPSG:3142</SRS>
+ <SRS>EPSG:3143</SRS>
+ <SRS>EPSG:3144</SRS>
+ <SRS>EPSG:3145</SRS>
+ <SRS>EPSG:3146</SRS>
+ <SRS>EPSG:3147</SRS>
+ <SRS>EPSG:3148</SRS>
+ <SRS>EPSG:3149</SRS>
+ <SRS>EPSG:3150</SRS>
+ <SRS>EPSG:3151</SRS>
+ <SRS>EPSG:3152</SRS>
+ <SRS>EPSG:3153</SRS>
+ <SRS>EPSG:3154</SRS>
+ <SRS>EPSG:3155</SRS>
+ <SRS>EPSG:3156</SRS>
+ <SRS>EPSG:3157</SRS>
+ <SRS>EPSG:3158</SRS>
+ <SRS>EPSG:3159</SRS>
+ <SRS>EPSG:3160</SRS>
+ <SRS>EPSG:3161</SRS>
+ <SRS>EPSG:3162</SRS>
+ <SRS>EPSG:3163</SRS>
+ <SRS>EPSG:3164</SRS>
+ <SRS>EPSG:3165</SRS>
+ <SRS>EPSG:3166</SRS>
+ <SRS>EPSG:3167</SRS>
+ <SRS>EPSG:3168</SRS>
+ <SRS>EPSG:3169</SRS>
+ <SRS>EPSG:3170</SRS>
+ <SRS>EPSG:3171</SRS>
+ <SRS>EPSG:3172</SRS>
+ <SRS>EPSG:3173</SRS>
+ <SRS>EPSG:3174</SRS>
+ <SRS>EPSG:3175</SRS>
+ <SRS>EPSG:3176</SRS>
+ <SRS>EPSG:3177</SRS>
+ <SRS>EPSG:3178</SRS>
+ <SRS>EPSG:3179</SRS>
+ <SRS>EPSG:3180</SRS>
+ <SRS>EPSG:3181</SRS>
+ <SRS>EPSG:3182</SRS>
+ <SRS>EPSG:3183</SRS>
+ <SRS>EPSG:3184</SRS>
+ <SRS>EPSG:3185</SRS>
+ <SRS>EPSG:3186</SRS>
+ <SRS>EPSG:3187</SRS>
+ <SRS>EPSG:3188</SRS>
+ <SRS>EPSG:3189</SRS>
+ <SRS>EPSG:3190</SRS>
+ <SRS>EPSG:3191</SRS>
+ <SRS>EPSG:3192</SRS>
+ <SRS>EPSG:3193</SRS>
+ <SRS>EPSG:3194</SRS>
+ <SRS>EPSG:3195</SRS>
+ <SRS>EPSG:3196</SRS>
+ <SRS>EPSG:3197</SRS>
+ <SRS>EPSG:3198</SRS>
+ <SRS>EPSG:3199</SRS>
+ <SRS>EPSG:3200</SRS>
+ <SRS>EPSG:3201</SRS>
+ <SRS>EPSG:3202</SRS>
+ <SRS>EPSG:3203</SRS>
+ <SRS>EPSG:3204</SRS>
+ <SRS>EPSG:3205</SRS>
+ <SRS>EPSG:3206</SRS>
+ <SRS>EPSG:3207</SRS>
+ <SRS>EPSG:3208</SRS>
+ <SRS>EPSG:3209</SRS>
+ <SRS>EPSG:3210</SRS>
+ <SRS>EPSG:3211</SRS>
+ <SRS>EPSG:3212</SRS>
+ <SRS>EPSG:3213</SRS>
+ <SRS>EPSG:3214</SRS>
+ <SRS>EPSG:3215</SRS>
+ <SRS>EPSG:3216</SRS>
+ <SRS>EPSG:3217</SRS>
+ <SRS>EPSG:3218</SRS>
+ <SRS>EPSG:3219</SRS>
+ <SRS>EPSG:3220</SRS>
+ <SRS>EPSG:3221</SRS>
+ <SRS>EPSG:3222</SRS>
+ <SRS>EPSG:3223</SRS>
+ <SRS>EPSG:3224</SRS>
+ <SRS>EPSG:3225</SRS>
+ <SRS>EPSG:3226</SRS>
+ <SRS>EPSG:3227</SRS>
+ <SRS>EPSG:3228</SRS>
+ <SRS>EPSG:3229</SRS>
+ <SRS>EPSG:3230</SRS>
+ <SRS>EPSG:3231</SRS>
+ <SRS>EPSG:3232</SRS>
+ <SRS>EPSG:3233</SRS>
+ <SRS>EPSG:3234</SRS>
+ <SRS>EPSG:3235</SRS>
+ <SRS>EPSG:3236</SRS>
+ <SRS>EPSG:3237</SRS>
+ <SRS>EPSG:3238</SRS>
+ <SRS>EPSG:3239</SRS>
+ <SRS>EPSG:3240</SRS>
+ <SRS>EPSG:3241</SRS>
+ <SRS>EPSG:3242</SRS>
+ <SRS>EPSG:3243</SRS>
+ <SRS>EPSG:3244</SRS>
+ <SRS>EPSG:3245</SRS>
+ <SRS>EPSG:3246</SRS>
+ <SRS>EPSG:3247</SRS>
+ <SRS>EPSG:3248</SRS>
+ <SRS>EPSG:3249</SRS>
+ <SRS>EPSG:3250</SRS>
+ <SRS>EPSG:3251</SRS>
+ <SRS>EPSG:3252</SRS>
+ <SRS>EPSG:3253</SRS>
+ <SRS>EPSG:3254</SRS>
+ <SRS>EPSG:3255</SRS>
+ <SRS>EPSG:3256</SRS>
+ <SRS>EPSG:3257</SRS>
+ <SRS>EPSG:3258</SRS>
+ <SRS>EPSG:3259</SRS>
+ <SRS>EPSG:3260</SRS>
+ <SRS>EPSG:3261</SRS>
+ <SRS>EPSG:3262</SRS>
+ <SRS>EPSG:3263</SRS>
+ <SRS>EPSG:3264</SRS>
+ <SRS>EPSG:3265</SRS>
+ <SRS>EPSG:3266</SRS>
+ <SRS>EPSG:3267</SRS>
+ <SRS>EPSG:3268</SRS>
+ <SRS>EPSG:3269</SRS>
+ <SRS>EPSG:3270</SRS>
+ <SRS>EPSG:3271</SRS>
+ <SRS>EPSG:3272</SRS>
+ <SRS>EPSG:3273</SRS>
+ <SRS>EPSG:3274</SRS>
+ <SRS>EPSG:3275</SRS>
+ <SRS>EPSG:3276</SRS>
+ <SRS>EPSG:3277</SRS>
+ <SRS>EPSG:3278</SRS>
+ <SRS>EPSG:3279</SRS>
+ <SRS>EPSG:3280</SRS>
+ <SRS>EPSG:3281</SRS>
+ <SRS>EPSG:3282</SRS>
+ <SRS>EPSG:3283</SRS>
+ <SRS>EPSG:3284</SRS>
+ <SRS>EPSG:3285</SRS>
+ <SRS>EPSG:3286</SRS>
+ <SRS>EPSG:3287</SRS>
+ <SRS>EPSG:3288</SRS>
+ <SRS>EPSG:3289</SRS>
+ <SRS>EPSG:3290</SRS>
+ <SRS>EPSG:3291</SRS>
+ <SRS>EPSG:3292</SRS>
+ <SRS>EPSG:3293</SRS>
+ <SRS>EPSG:3294</SRS>
+ <SRS>EPSG:3295</SRS>
+ <SRS>EPSG:3296</SRS>
+ <SRS>EPSG:3297</SRS>
+ <SRS>EPSG:3298</SRS>
+ <SRS>EPSG:3299</SRS>
+ <SRS>EPSG:3300</SRS>
+ <SRS>EPSG:3301</SRS>
+ <SRS>EPSG:3302</SRS>
+ <SRS>EPSG:3303</SRS>
+ <SRS>EPSG:3304</SRS>
+ <SRS>EPSG:3305</SRS>
+ <SRS>EPSG:3306</SRS>
+ <SRS>EPSG:3307</SRS>
+ <SRS>EPSG:3308</SRS>
+ <SRS>EPSG:3309</SRS>
+ <SRS>EPSG:3310</SRS>
+ <SRS>EPSG:3311</SRS>
+ <SRS>EPSG:3312</SRS>
+ <SRS>EPSG:3313</SRS>
+ <SRS>EPSG:3314</SRS>
+ <SRS>EPSG:3315</SRS>
+ <SRS>EPSG:3316</SRS>
+ <SRS>EPSG:3317</SRS>
+ <SRS>EPSG:3318</SRS>
+ <SRS>EPSG:3319</SRS>
+ <SRS>EPSG:3320</SRS>
+ <SRS>EPSG:3321</SRS>
+ <SRS>EPSG:3322</SRS>
+ <SRS>EPSG:3323</SRS>
+ <SRS>EPSG:3324</SRS>
+ <SRS>EPSG:3325</SRS>
+ <SRS>EPSG:3326</SRS>
+ <SRS>EPSG:3327</SRS>
+ <SRS>EPSG:3328</SRS>
+ <SRS>EPSG:3329</SRS>
+ <SRS>EPSG:3330</SRS>
+ <SRS>EPSG:3331</SRS>
+ <SRS>EPSG:3332</SRS>
+ <SRS>EPSG:3333</SRS>
+ <SRS>EPSG:3334</SRS>
+ <SRS>EPSG:3335</SRS>
+ <SRS>EPSG:3336</SRS>
+ <SRS>EPSG:3337</SRS>
+ <SRS>EPSG:3338</SRS>
+ <SRS>EPSG:3339</SRS>
+ <SRS>EPSG:3340</SRS>
+ <SRS>EPSG:3341</SRS>
+ <SRS>EPSG:3342</SRS>
+ <SRS>EPSG:3343</SRS>
+ <SRS>EPSG:3344</SRS>
+ <SRS>EPSG:3345</SRS>
+ <SRS>EPSG:3346</SRS>
+ <SRS>EPSG:3347</SRS>
+ <SRS>EPSG:3348</SRS>
+ <SRS>EPSG:3349</SRS>
+ <SRS>EPSG:3350</SRS>
+ <SRS>EPSG:3351</SRS>
+ <SRS>EPSG:3352</SRS>
+ <SRS>EPSG:3353</SRS>
+ <SRS>EPSG:3354</SRS>
+ <SRS>EPSG:3355</SRS>
+ <SRS>EPSG:3356</SRS>
+ <SRS>EPSG:3357</SRS>
+ <SRS>EPSG:3358</SRS>
+ <SRS>EPSG:3359</SRS>
+ <SRS>EPSG:3360</SRS>
+ <SRS>EPSG:3361</SRS>
+ <SRS>EPSG:3362</SRS>
+ <SRS>EPSG:3363</SRS>
+ <SRS>EPSG:3364</SRS>
+ <SRS>EPSG:3365</SRS>
+ <SRS>EPSG:3366</SRS>
+ <SRS>EPSG:3367</SRS>
+ <SRS>EPSG:3368</SRS>
+ <SRS>EPSG:3369</SRS>
+ <SRS>EPSG:3370</SRS>
+ <SRS>EPSG:3371</SRS>
+ <SRS>EPSG:3372</SRS>
+ <SRS>EPSG:3373</SRS>
+ <SRS>EPSG:3374</SRS>
+ <SRS>EPSG:3375</SRS>
+ <SRS>EPSG:3376</SRS>
+ <SRS>EPSG:3377</SRS>
+ <SRS>EPSG:3378</SRS>
+ <SRS>EPSG:3379</SRS>
+ <SRS>EPSG:3380</SRS>
+ <SRS>EPSG:3381</SRS>
+ <SRS>EPSG:3382</SRS>
+ <SRS>EPSG:3383</SRS>
+ <SRS>EPSG:3384</SRS>
+ <SRS>EPSG:3385</SRS>
+ <SRS>EPSG:3386</SRS>
+ <SRS>EPSG:3387</SRS>
+ <SRS>EPSG:3388</SRS>
+ <SRS>EPSG:3389</SRS>
+ <SRS>EPSG:3390</SRS>
+ <SRS>EPSG:3391</SRS>
+ <SRS>EPSG:3392</SRS>
+ <SRS>EPSG:3393</SRS>
+ <SRS>EPSG:3394</SRS>
+ <SRS>EPSG:3395</SRS>
+ <SRS>EPSG:3396</SRS>
+ <SRS>EPSG:3397</SRS>
+ <SRS>EPSG:3398</SRS>
+ <SRS>EPSG:3399</SRS>
+ <SRS>EPSG:3400</SRS>
+ <SRS>EPSG:3401</SRS>
+ <SRS>EPSG:3402</SRS>
+ <SRS>EPSG:3403</SRS>
+ <SRS>EPSG:3404</SRS>
+ <SRS>EPSG:3405</SRS>
+ <SRS>EPSG:3406</SRS>
+ <SRS>EPSG:3407</SRS>
+ <SRS>EPSG:3408</SRS>
+ <SRS>EPSG:3409</SRS>
+ <SRS>EPSG:3410</SRS>
+ <SRS>EPSG:3411</SRS>
+ <SRS>EPSG:3412</SRS>
+ <SRS>EPSG:3413</SRS>
+ <SRS>EPSG:3414</SRS>
+ <SRS>EPSG:3415</SRS>
+ <SRS>EPSG:3416</SRS>
+ <SRS>EPSG:3417</SRS>
+ <SRS>EPSG:3418</SRS>
+ <SRS>EPSG:3419</SRS>
+ <SRS>EPSG:3420</SRS>
+ <SRS>EPSG:3421</SRS>
+ <SRS>EPSG:3422</SRS>
+ <SRS>EPSG:3423</SRS>
+ <SRS>EPSG:3424</SRS>
+ <SRS>EPSG:3425</SRS>
+ <SRS>EPSG:3426</SRS>
+ <SRS>EPSG:3427</SRS>
+ <SRS>EPSG:3428</SRS>
+ <SRS>EPSG:3429</SRS>
+ <SRS>EPSG:3430</SRS>
+ <SRS>EPSG:3431</SRS>
+ <SRS>EPSG:3432</SRS>
+ <SRS>EPSG:3433</SRS>
+ <SRS>EPSG:3434</SRS>
+ <SRS>EPSG:3435</SRS>
+ <SRS>EPSG:3436</SRS>
+ <SRS>EPSG:3437</SRS>
+ <SRS>EPSG:3438</SRS>
+ <SRS>EPSG:3439</SRS>
+ <SRS>EPSG:3440</SRS>
+ <SRS>EPSG:3441</SRS>
+ <SRS>EPSG:3442</SRS>
+ <SRS>EPSG:3443</SRS>
+ <SRS>EPSG:3444</SRS>
+ <SRS>EPSG:3445</SRS>
+ <SRS>EPSG:3446</SRS>
+ <SRS>EPSG:3447</SRS>
+ <SRS>EPSG:3448</SRS>
+ <SRS>EPSG:3449</SRS>
+ <SRS>EPSG:3450</SRS>
+ <SRS>EPSG:3451</SRS>
+ <SRS>EPSG:3452</SRS>
+ <SRS>EPSG:3453</SRS>
+ <SRS>EPSG:3454</SRS>
+ <SRS>EPSG:3455</SRS>
+ <SRS>EPSG:3456</SRS>
+ <SRS>EPSG:3457</SRS>
+ <SRS>EPSG:3458</SRS>
+ <SRS>EPSG:3459</SRS>
+ <SRS>EPSG:3460</SRS>
+ <SRS>EPSG:3461</SRS>
+ <SRS>EPSG:3462</SRS>
+ <SRS>EPSG:3463</SRS>
+ <SRS>EPSG:3464</SRS>
+ <SRS>EPSG:3560</SRS>
+ <SRS>EPSG:3561</SRS>
+ <SRS>EPSG:3562</SRS>
+ <SRS>EPSG:3563</SRS>
+ <SRS>EPSG:3564</SRS>
+ <SRS>EPSG:3565</SRS>
+ <SRS>EPSG:3566</SRS>
+ <SRS>EPSG:3567</SRS>
+ <SRS>EPSG:3568</SRS>
+ <SRS>EPSG:3569</SRS>
+ <SRS>EPSG:3570</SRS>
+ <SRS>EPSG:3571</SRS>
+ <SRS>EPSG:3572</SRS>
+ <SRS>EPSG:3573</SRS>
+ <SRS>EPSG:3574</SRS>
+ <SRS>EPSG:3575</SRS>
+ <SRS>EPSG:3576</SRS>
+ <SRS>EPSG:3577</SRS>
+ <SRS>EPSG:3920</SRS>
+ <SRS>EPSG:3991</SRS>
+ <SRS>EPSG:3992</SRS>
+ <SRS>EPSG:3993</SRS>
+ <SRS>EPSG:4001</SRS>
+ <SRS>EPSG:4002</SRS>
+ <SRS>EPSG:4003</SRS>
+ <SRS>EPSG:4004</SRS>
+ <SRS>EPSG:4005</SRS>
+ <SRS>EPSG:4006</SRS>
+ <SRS>EPSG:4007</SRS>
+ <SRS>EPSG:4008</SRS>
+ <SRS>EPSG:4009</SRS>
+ <SRS>EPSG:4010</SRS>
+ <SRS>EPSG:4011</SRS>
+ <SRS>EPSG:4012</SRS>
+ <SRS>EPSG:4013</SRS>
+ <SRS>EPSG:4014</SRS>
+ <SRS>EPSG:4015</SRS>
+ <SRS>EPSG:4016</SRS>
+ <SRS>EPSG:4018</SRS>
+ <SRS>EPSG:4019</SRS>
+ <SRS>EPSG:4020</SRS>
+ <SRS>EPSG:4021</SRS>
+ <SRS>EPSG:4022</SRS>
+ <SRS>EPSG:4024</SRS>
+ <SRS>EPSG:4025</SRS>
+ <SRS>EPSG:4027</SRS>
+ <SRS>EPSG:4028</SRS>
+ <SRS>EPSG:4029</SRS>
+ <SRS>EPSG:4030</SRS>
+ <SRS>EPSG:4031</SRS>
+ <SRS>EPSG:4032</SRS>
+ <SRS>EPSG:4033</SRS>
+ <SRS>EPSG:4034</SRS>
+ <SRS>EPSG:4035</SRS>
+ <SRS>EPSG:4036</SRS>
+ <SRS>EPSG:4041</SRS>
+ <SRS>EPSG:4042</SRS>
+ <SRS>EPSG:4043</SRS>
+ <SRS>EPSG:4044</SRS>
+ <SRS>EPSG:4045</SRS>
+ <SRS>EPSG:4047</SRS>
+ <SRS>EPSG:4052</SRS>
+ <SRS>EPSG:4053</SRS>
+ <SRS>EPSG:4054</SRS>
+ <SRS>EPSG:4120</SRS>
+ <SRS>EPSG:4121</SRS>
+ <SRS>EPSG:4122</SRS>
+ <SRS>EPSG:4123</SRS>
+ <SRS>EPSG:4124</SRS>
+ <SRS>EPSG:4125</SRS>
+ <SRS>EPSG:4126</SRS>
+ <SRS>EPSG:4127</SRS>
+ <SRS>EPSG:4128</SRS>
+ <SRS>EPSG:4129</SRS>
+ <SRS>EPSG:4130</SRS>
+ <SRS>EPSG:4131</SRS>
+ <SRS>EPSG:4132</SRS>
+ <SRS>EPSG:4133</SRS>
+ <SRS>EPSG:4134</SRS>
+ <SRS>EPSG:4135</SRS>
+ <SRS>EPSG:4136</SRS>
+ <SRS>EPSG:4137</SRS>
+ <SRS>EPSG:4138</SRS>
+ <SRS>EPSG:4139</SRS>
+ <SRS>EPSG:4140</SRS>
+ <SRS>EPSG:4141</SRS>
+ <SRS>EPSG:4142</SRS>
+ <SRS>EPSG:4143</SRS>
+ <SRS>EPSG:4144</SRS>
+ <SRS>EPSG:4145</SRS>
+ <SRS>EPSG:4146</SRS>
+ <SRS>EPSG:4147</SRS>
+ <SRS>EPSG:4148</SRS>
+ <SRS>EPSG:4149</SRS>
+ <SRS>EPSG:4150</SRS>
+ <SRS>EPSG:4151</SRS>
+ <SRS>EPSG:4152</SRS>
+ <SRS>EPSG:4153</SRS>
+ <SRS>EPSG:4154</SRS>
+ <SRS>EPSG:4155</SRS>
+ <SRS>EPSG:4156</SRS>
+ <SRS>EPSG:4157</SRS>
+ <SRS>EPSG:4158</SRS>
+ <SRS>EPSG:4159</SRS>
+ <SRS>EPSG:4160</SRS>
+ <SRS>EPSG:4161</SRS>
+ <SRS>EPSG:4162</SRS>
+ <SRS>EPSG:4163</SRS>
+ <SRS>EPSG:4164</SRS>
+ <SRS>EPSG:4165</SRS>
+ <SRS>EPSG:4166</SRS>
+ <SRS>EPSG:4167</SRS>
+ <SRS>EPSG:4168</SRS>
+ <SRS>EPSG:4169</SRS>
+ <SRS>EPSG:4170</SRS>
+ <SRS>EPSG:4171</SRS>
+ <SRS>EPSG:4172</SRS>
+ <SRS>EPSG:4173</SRS>
+ <SRS>EPSG:4174</SRS>
+ <SRS>EPSG:4175</SRS>
+ <SRS>EPSG:4176</SRS>
+ <SRS>EPSG:4178</SRS>
+ <SRS>EPSG:4179</SRS>
+ <SRS>EPSG:4180</SRS>
+ <SRS>EPSG:4181</SRS>
+ <SRS>EPSG:4182</SRS>
+ <SRS>EPSG:4183</SRS>
+ <SRS>EPSG:4184</SRS>
+ <SRS>EPSG:4185</SRS>
+ <SRS>EPSG:4188</SRS>
+ <SRS>EPSG:4189</SRS>
+ <SRS>EPSG:4190</SRS>
+ <SRS>EPSG:4191</SRS>
+ <SRS>EPSG:4192</SRS>
+ <SRS>EPSG:4193</SRS>
+ <SRS>EPSG:4194</SRS>
+ <SRS>EPSG:4195</SRS>
+ <SRS>EPSG:4196</SRS>
+ <SRS>EPSG:4197</SRS>
+ <SRS>EPSG:4198</SRS>
+ <SRS>EPSG:4199</SRS>
+ <SRS>EPSG:4200</SRS>
+ <SRS>EPSG:4201</SRS>
+ <SRS>EPSG:4202</SRS>
+ <SRS>EPSG:4203</SRS>
+ <SRS>EPSG:4204</SRS>
+ <SRS>EPSG:4205</SRS>
+ <SRS>EPSG:4206</SRS>
+ <SRS>EPSG:4207</SRS>
+ <SRS>EPSG:4208</SRS>
+ <SRS>EPSG:4209</SRS>
+ <SRS>EPSG:4210</SRS>
+ <SRS>EPSG:4211</SRS>
+ <SRS>EPSG:4212</SRS>
+ <SRS>EPSG:4213</SRS>
+ <SRS>EPSG:4214</SRS>
+ <SRS>EPSG:4215</SRS>
+ <SRS>EPSG:4216</SRS>
+ <SRS>EPSG:4218</SRS>
+ <SRS>EPSG:4219</SRS>
+ <SRS>EPSG:4220</SRS>
+ <SRS>EPSG:4221</SRS>
+ <SRS>EPSG:4222</SRS>
+ <SRS>EPSG:4223</SRS>
+ <SRS>EPSG:4224</SRS>
+ <SRS>EPSG:4225</SRS>
+ <SRS>EPSG:4226</SRS>
+ <SRS>EPSG:4227</SRS>
+ <SRS>EPSG:4228</SRS>
+ <SRS>EPSG:4229</SRS>
+ <SRS>EPSG:4230</SRS>
+ <SRS>EPSG:4231</SRS>
+ <SRS>EPSG:4232</SRS>
+ <SRS>EPSG:4233</SRS>
+ <SRS>EPSG:4234</SRS>
+ <SRS>EPSG:4235</SRS>
+ <SRS>EPSG:4236</SRS>
+ <SRS>EPSG:4237</SRS>
+ <SRS>EPSG:4238</SRS>
+ <SRS>EPSG:4239</SRS>
+ <SRS>EPSG:4240</SRS>
+ <SRS>EPSG:4241</SRS>
+ <SRS>EPSG:4242</SRS>
+ <SRS>EPSG:4243</SRS>
+ <SRS>EPSG:4244</SRS>
+ <SRS>EPSG:4245</SRS>
+ <SRS>EPSG:4246</SRS>
+ <SRS>EPSG:4247</SRS>
+ <SRS>EPSG:4248</SRS>
+ <SRS>EPSG:4249</SRS>
+ <SRS>EPSG:4250</SRS>
+ <SRS>EPSG:4251</SRS>
+ <SRS>EPSG:4252</SRS>
+ <SRS>EPSG:4253</SRS>
+ <SRS>EPSG:4254</SRS>
+ <SRS>EPSG:4255</SRS>
+ <SRS>EPSG:4256</SRS>
+ <SRS>EPSG:4257</SRS>
+ <SRS>EPSG:4258</SRS>
+ <SRS>EPSG:4259</SRS>
+ <SRS>EPSG:4260</SRS>
+ <SRS>EPSG:4261</SRS>
+ <SRS>EPSG:4262</SRS>
+ <SRS>EPSG:4263</SRS>
+ <SRS>EPSG:4264</SRS>
+ <SRS>EPSG:4265</SRS>
+ <SRS>EPSG:4266</SRS>
+ <SRS>EPSG:4267</SRS>
+ <SRS>EPSG:4268</SRS>
+ <SRS>EPSG:4269</SRS>
+ <SRS>EPSG:4270</SRS>
+ <SRS>EPSG:4271</SRS>
+ <SRS>EPSG:4272</SRS>
+ <SRS>EPSG:4273</SRS>
+ <SRS>EPSG:4274</SRS>
+ <SRS>EPSG:4275</SRS>
+ <SRS>EPSG:4276</SRS>
+ <SRS>EPSG:4277</SRS>
+ <SRS>EPSG:4278</SRS>
+ <SRS>EPSG:4279</SRS>
+ <SRS>EPSG:4280</SRS>
+ <SRS>EPSG:4281</SRS>
+ <SRS>EPSG:4282</SRS>
+ <SRS>EPSG:4283</SRS>
+ <SRS>EPSG:4284</SRS>
+ <SRS>EPSG:4285</SRS>
+ <SRS>EPSG:4286</SRS>
+ <SRS>EPSG:4287</SRS>
+ <SRS>EPSG:4288</SRS>
+ <SRS>EPSG:4289</SRS>
+ <SRS>EPSG:4291</SRS>
+ <SRS>EPSG:4292</SRS>
+ <SRS>EPSG:4293</SRS>
+ <SRS>EPSG:4294</SRS>
+ <SRS>EPSG:4295</SRS>
+ <SRS>EPSG:4296</SRS>
+ <SRS>EPSG:4297</SRS>
+ <SRS>EPSG:4298</SRS>
+ <SRS>EPSG:4299</SRS>
+ <SRS>EPSG:4300</SRS>
+ <SRS>EPSG:4301</SRS>
+ <SRS>EPSG:4302</SRS>
+ <SRS>EPSG:4303</SRS>
+ <SRS>EPSG:4304</SRS>
+ <SRS>EPSG:4306</SRS>
+ <SRS>EPSG:4307</SRS>
+ <SRS>EPSG:4308</SRS>
+ <SRS>EPSG:4309</SRS>
+ <SRS>EPSG:4310</SRS>
+ <SRS>EPSG:4311</SRS>
+ <SRS>EPSG:4312</SRS>
+ <SRS>EPSG:4313</SRS>
+ <SRS>EPSG:4314</SRS>
+ <SRS>EPSG:4315</SRS>
+ <SRS>EPSG:4316</SRS>
+ <SRS>EPSG:4317</SRS>
+ <SRS>EPSG:4318</SRS>
+ <SRS>EPSG:4319</SRS>
+ <SRS>EPSG:4322</SRS>
+ <SRS>EPSG:4324</SRS>
+ <SRS>EPSG:4326</SRS>
+ <SRS>EPSG:4327</SRS>
+ <SRS>EPSG:4328</SRS>
+ <SRS>EPSG:4329</SRS>
+ <SRS>EPSG:4330</SRS>
+ <SRS>EPSG:4331</SRS>
+ <SRS>EPSG:4332</SRS>
+ <SRS>EPSG:4333</SRS>
+ <SRS>EPSG:4334</SRS>
+ <SRS>EPSG:4335</SRS>
+ <SRS>EPSG:4336</SRS>
+ <SRS>EPSG:4337</SRS>
+ <SRS>EPSG:4338</SRS>
+ <SRS>EPSG:4339</SRS>
+ <SRS>EPSG:4340</SRS>
+ <SRS>EPSG:4341</SRS>
+ <SRS>EPSG:4342</SRS>
+ <SRS>EPSG:4343</SRS>
+ <SRS>EPSG:4344</SRS>
+ <SRS>EPSG:4345</SRS>
+ <SRS>EPSG:4346</SRS>
+ <SRS>EPSG:4347</SRS>
+ <SRS>EPSG:4348</SRS>
+ <SRS>EPSG:4349</SRS>
+ <SRS>EPSG:4350</SRS>
+ <SRS>EPSG:4351</SRS>
+ <SRS>EPSG:4352</SRS>
+ <SRS>EPSG:4353</SRS>
+ <SRS>EPSG:4354</SRS>
+ <SRS>EPSG:4355</SRS>
+ <SRS>EPSG:4356</SRS>
+ <SRS>EPSG:4357</SRS>
+ <SRS>EPSG:4358</SRS>
+ <SRS>EPSG:4359</SRS>
+ <SRS>EPSG:4360</SRS>
+ <SRS>EPSG:4361</SRS>
+ <SRS>EPSG:4362</SRS>
+ <SRS>EPSG:4363</SRS>
+ <SRS>EPSG:4364</SRS>
+ <SRS>EPSG:4365</SRS>
+ <SRS>EPSG:4366</SRS>
+ <SRS>EPSG:4367</SRS>
+ <SRS>EPSG:4368</SRS>
+ <SRS>EPSG:4369</SRS>
+ <SRS>EPSG:4370</SRS>
+ <SRS>EPSG:4371</SRS>
+ <SRS>EPSG:4372</SRS>
+ <SRS>EPSG:4373</SRS>
+ <SRS>EPSG:4374</SRS>
+ <SRS>EPSG:4375</SRS>
+ <SRS>EPSG:4376</SRS>
+ <SRS>EPSG:4377</SRS>
+ <SRS>EPSG:4378</SRS>
+ <SRS>EPSG:4379</SRS>
+ <SRS>EPSG:4380</SRS>
+ <SRS>EPSG:4381</SRS>
+ <SRS>EPSG:4382</SRS>
+ <SRS>EPSG:4383</SRS>
+ <SRS>EPSG:4384</SRS>
+ <SRS>EPSG:4385</SRS>
+ <SRS>EPSG:4386</SRS>
+ <SRS>EPSG:4387</SRS>
+ <SRS>EPSG:4388</SRS>
+ <SRS>EPSG:4389</SRS>
+ <SRS>EPSG:4600</SRS>
+ <SRS>EPSG:4601</SRS>
+ <SRS>EPSG:4602</SRS>
+ <SRS>EPSG:4603</SRS>
+ <SRS>EPSG:4604</SRS>
+ <SRS>EPSG:4605</SRS>
+ <SRS>EPSG:4606</SRS>
+ <SRS>EPSG:4607</SRS>
+ <SRS>EPSG:4608</SRS>
+ <SRS>EPSG:4609</SRS>
+ <SRS>EPSG:4610</SRS>
+ <SRS>EPSG:4611</SRS>
+ <SRS>EPSG:4612</SRS>
+ <SRS>EPSG:4613</SRS>
+ <SRS>EPSG:4614</SRS>
+ <SRS>EPSG:4615</SRS>
+ <SRS>EPSG:4616</SRS>
+ <SRS>EPSG:4617</SRS>
+ <SRS>EPSG:4618</SRS>
+ <SRS>EPSG:4619</SRS>
+ <SRS>EPSG:4620</SRS>
+ <SRS>EPSG:4621</SRS>
+ <SRS>EPSG:4622</SRS>
+ <SRS>EPSG:4623</SRS>
+ <SRS>EPSG:4624</SRS>
+ <SRS>EPSG:4625</SRS>
+ <SRS>EPSG:4626</SRS>
+ <SRS>EPSG:4627</SRS>
+ <SRS>EPSG:4628</SRS>
+ <SRS>EPSG:4629</SRS>
+ <SRS>EPSG:4630</SRS>
+ <SRS>EPSG:4631</SRS>
+ <SRS>EPSG:4632</SRS>
+ <SRS>EPSG:4633</SRS>
+ <SRS>EPSG:4634</SRS>
+ <SRS>EPSG:4635</SRS>
+ <SRS>EPSG:4636</SRS>
+ <SRS>EPSG:4637</SRS>
+ <SRS>EPSG:4638</SRS>
+ <SRS>EPSG:4639</SRS>
+ <SRS>EPSG:4640</SRS>
+ <SRS>EPSG:4641</SRS>
+ <SRS>EPSG:4642</SRS>
+ <SRS>EPSG:4643</SRS>
+ <SRS>EPSG:4644</SRS>
+ <SRS>EPSG:4645</SRS>
+ <SRS>EPSG:4646</SRS>
+ <SRS>EPSG:4657</SRS>
+ <SRS>EPSG:4658</SRS>
+ <SRS>EPSG:4659</SRS>
+ <SRS>EPSG:4660</SRS>
+ <SRS>EPSG:4661</SRS>
+ <SRS>EPSG:4662</SRS>
+ <SRS>EPSG:4663</SRS>
+ <SRS>EPSG:4664</SRS>
+ <SRS>EPSG:4665</SRS>
+ <SRS>EPSG:4666</SRS>
+ <SRS>EPSG:4667</SRS>
+ <SRS>EPSG:4668</SRS>
+ <SRS>EPSG:4669</SRS>
+ <SRS>EPSG:4670</SRS>
+ <SRS>EPSG:4671</SRS>
+ <SRS>EPSG:4672</SRS>
+ <SRS>EPSG:4673</SRS>
+ <SRS>EPSG:4674</SRS>
+ <SRS>EPSG:4675</SRS>
+ <SRS>EPSG:4676</SRS>
+ <SRS>EPSG:4677</SRS>
+ <SRS>EPSG:4678</SRS>
+ <SRS>EPSG:4679</SRS>
+ <SRS>EPSG:4680</SRS>
+ <SRS>EPSG:4681</SRS>
+ <SRS>EPSG:4682</SRS>
+ <SRS>EPSG:4683</SRS>
+ <SRS>EPSG:4684</SRS>
+ <SRS>EPSG:4685</SRS>
+ <SRS>EPSG:4686</SRS>
+ <SRS>EPSG:4687</SRS>
+ <SRS>EPSG:4688</SRS>
+ <SRS>EPSG:4689</SRS>
+ <SRS>EPSG:4690</SRS>
+ <SRS>EPSG:4691</SRS>
+ <SRS>EPSG:4692</SRS>
+ <SRS>EPSG:4693</SRS>
+ <SRS>EPSG:4694</SRS>
+ <SRS>EPSG:4695</SRS>
+ <SRS>EPSG:4696</SRS>
+ <SRS>EPSG:4697</SRS>
+ <SRS>EPSG:4698</SRS>
+ <SRS>EPSG:4699</SRS>
+ <SRS>EPSG:4700</SRS>
+ <SRS>EPSG:4701</SRS>
+ <SRS>EPSG:4702</SRS>
+ <SRS>EPSG:4703</SRS>
+ <SRS>EPSG:4704</SRS>
+ <SRS>EPSG:4705</SRS>
+ <SRS>EPSG:4706</SRS>
+ <SRS>EPSG:4707</SRS>
+ <SRS>EPSG:4708</SRS>
+ <SRS>EPSG:4709</SRS>
+ <SRS>EPSG:4710</SRS>
+ <SRS>EPSG:4711</SRS>
+ <SRS>EPSG:4712</SRS>
+ <SRS>EPSG:4713</SRS>
+ <SRS>EPSG:4714</SRS>
+ <SRS>EPSG:4715</SRS>
+ <SRS>EPSG:4716</SRS>
+ <SRS>EPSG:4717</SRS>
+ <SRS>EPSG:4718</SRS>
+ <SRS>EPSG:4719</SRS>
+ <SRS>EPSG:4720</SRS>
+ <SRS>EPSG:4721</SRS>
+ <SRS>EPSG:4722</SRS>
+ <SRS>EPSG:4723</SRS>
+ <SRS>EPSG:4724</SRS>
+ <SRS>EPSG:4725</SRS>
+ <SRS>EPSG:4726</SRS>
+ <SRS>EPSG:4727</SRS>
+ <SRS>EPSG:4728</SRS>
+ <SRS>EPSG:4729</SRS>
+ <SRS>EPSG:4730</SRS>
+ <SRS>EPSG:4731</SRS>
+ <SRS>EPSG:4732</SRS>
+ <SRS>EPSG:4733</SRS>
+ <SRS>EPSG:4734</SRS>
+ <SRS>EPSG:4735</SRS>
+ <SRS>EPSG:4736</SRS>
+ <SRS>EPSG:4737</SRS>
+ <SRS>EPSG:4738</SRS>
+ <SRS>EPSG:4739</SRS>
+ <SRS>EPSG:4740</SRS>
+ <SRS>EPSG:4741</SRS>
+ <SRS>EPSG:4742</SRS>
+ <SRS>EPSG:4743</SRS>
+ <SRS>EPSG:4744</SRS>
+ <SRS>EPSG:4745</SRS>
+ <SRS>EPSG:4746</SRS>
+ <SRS>EPSG:4747</SRS>
+ <SRS>EPSG:4748</SRS>
+ <SRS>EPSG:4749</SRS>
+ <SRS>EPSG:4750</SRS>
+ <SRS>EPSG:4751</SRS>
+ <SRS>EPSG:4752</SRS>
+ <SRS>EPSG:4753</SRS>
+ <SRS>EPSG:4754</SRS>
+ <SRS>EPSG:4755</SRS>
+ <SRS>EPSG:4756</SRS>
+ <SRS>EPSG:4757</SRS>
+ <SRS>EPSG:4758</SRS>
+ <SRS>EPSG:4801</SRS>
+ <SRS>EPSG:4802</SRS>
+ <SRS>EPSG:4803</SRS>
+ <SRS>EPSG:4804</SRS>
+ <SRS>EPSG:4805</SRS>
+ <SRS>EPSG:4806</SRS>
+ <SRS>EPSG:4807</SRS>
+ <SRS>EPSG:4808</SRS>
+ <SRS>EPSG:4809</SRS>
+ <SRS>EPSG:4810</SRS>
+ <SRS>EPSG:4811</SRS>
+ <SRS>EPSG:4813</SRS>
+ <SRS>EPSG:4814</SRS>
+ <SRS>EPSG:4815</SRS>
+ <SRS>EPSG:4816</SRS>
+ <SRS>EPSG:4817</SRS>
+ <SRS>EPSG:4818</SRS>
+ <SRS>EPSG:4819</SRS>
+ <SRS>EPSG:4820</SRS>
+ <SRS>EPSG:4821</SRS>
+ <SRS>EPSG:4894</SRS>
+ <SRS>EPSG:4895</SRS>
+ <SRS>EPSG:4896</SRS>
+ <SRS>EPSG:4897</SRS>
+ <SRS>EPSG:4898</SRS>
+ <SRS>EPSG:4899</SRS>
+ <SRS>EPSG:4900</SRS>
+ <SRS>EPSG:4901</SRS>
+ <SRS>EPSG:4902</SRS>
+ <SRS>EPSG:4903</SRS>
+ <SRS>EPSG:4904</SRS>
+ <SRS>EPSG:4906</SRS>
+ <SRS>EPSG:4907</SRS>
+ <SRS>EPSG:4908</SRS>
+ <SRS>EPSG:4909</SRS>
+ <SRS>EPSG:4910</SRS>
+ <SRS>EPSG:4911</SRS>
+ <SRS>EPSG:4912</SRS>
+ <SRS>EPSG:4913</SRS>
+ <SRS>EPSG:4914</SRS>
+ <SRS>EPSG:4915</SRS>
+ <SRS>EPSG:4916</SRS>
+ <SRS>EPSG:4917</SRS>
+ <SRS>EPSG:4918</SRS>
+ <SRS>EPSG:4919</SRS>
+ <SRS>EPSG:4920</SRS>
+ <SRS>EPSG:4921</SRS>
+ <SRS>EPSG:4922</SRS>
+ <SRS>EPSG:4923</SRS>
+ <SRS>EPSG:4924</SRS>
+ <SRS>EPSG:4925</SRS>
+ <SRS>EPSG:4926</SRS>
+ <SRS>EPSG:4927</SRS>
+ <SRS>EPSG:4928</SRS>
+ <SRS>EPSG:4929</SRS>
+ <SRS>EPSG:4930</SRS>
+ <SRS>EPSG:4931</SRS>
+ <SRS>EPSG:4932</SRS>
+ <SRS>EPSG:4933</SRS>
+ <SRS>EPSG:4934</SRS>
+ <SRS>EPSG:4935</SRS>
+ <SRS>EPSG:4936</SRS>
+ <SRS>EPSG:4937</SRS>
+ <SRS>EPSG:4938</SRS>
+ <SRS>EPSG:4939</SRS>
+ <SRS>EPSG:4940</SRS>
+ <SRS>EPSG:4941</SRS>
+ <SRS>EPSG:4942</SRS>
+ <SRS>EPSG:4943</SRS>
+ <SRS>EPSG:4944</SRS>
+ <SRS>EPSG:4945</SRS>
+ <SRS>EPSG:4946</SRS>
+ <SRS>EPSG:4947</SRS>
+ <SRS>EPSG:4948</SRS>
+ <SRS>EPSG:4949</SRS>
+ <SRS>EPSG:4950</SRS>
+ <SRS>EPSG:4951</SRS>
+ <SRS>EPSG:4952</SRS>
+ <SRS>EPSG:4953</SRS>
+ <SRS>EPSG:4954</SRS>
+ <SRS>EPSG:4955</SRS>
+ <SRS>EPSG:4956</SRS>
+ <SRS>EPSG:4957</SRS>
+ <SRS>EPSG:4958</SRS>
+ <SRS>EPSG:4959</SRS>
+ <SRS>EPSG:4960</SRS>
+ <SRS>EPSG:4961</SRS>
+ <SRS>EPSG:4962</SRS>
+ <SRS>EPSG:4963</SRS>
+ <SRS>EPSG:4964</SRS>
+ <SRS>EPSG:4965</SRS>
+ <SRS>EPSG:4966</SRS>
+ <SRS>EPSG:4967</SRS>
+ <SRS>EPSG:4968</SRS>
+ <SRS>EPSG:4969</SRS>
+ <SRS>EPSG:4970</SRS>
+ <SRS>EPSG:4971</SRS>
+ <SRS>EPSG:4972</SRS>
+ <SRS>EPSG:4973</SRS>
+ <SRS>EPSG:4974</SRS>
+ <SRS>EPSG:4975</SRS>
+ <SRS>EPSG:4976</SRS>
+ <SRS>EPSG:4977</SRS>
+ <SRS>EPSG:4978</SRS>
+ <SRS>EPSG:4979</SRS>
+ <SRS>EPSG:4980</SRS>
+ <SRS>EPSG:4981</SRS>
+ <SRS>EPSG:4982</SRS>
+ <SRS>EPSG:4983</SRS>
+ <SRS>EPSG:4984</SRS>
+ <SRS>EPSG:4985</SRS>
+ <SRS>EPSG:4986</SRS>
+ <SRS>EPSG:4987</SRS>
+ <SRS>EPSG:4988</SRS>
+ <SRS>EPSG:4989</SRS>
+ <SRS>EPSG:4990</SRS>
+ <SRS>EPSG:4991</SRS>
+ <SRS>EPSG:4992</SRS>
+ <SRS>EPSG:4993</SRS>
+ <SRS>EPSG:4994</SRS>
+ <SRS>EPSG:4995</SRS>
+ <SRS>EPSG:4996</SRS>
+ <SRS>EPSG:4997</SRS>
+ <SRS>EPSG:4998</SRS>
+ <SRS>EPSG:4999</SRS>
+ <SRS>EPSG:5600</SRS>
+ <SRS>EPSG:5601</SRS>
+ <SRS>EPSG:5602</SRS>
+ <SRS>EPSG:5603</SRS>
+ <SRS>EPSG:5604</SRS>
+ <SRS>EPSG:5605</SRS>
+ <SRS>EPSG:5606</SRS>
+ <SRS>EPSG:5607</SRS>
+ <SRS>EPSG:5608</SRS>
+ <SRS>EPSG:5609</SRS>
+ <SRS>EPSG:5701</SRS>
+ <SRS>EPSG:5702</SRS>
+ <SRS>EPSG:5703</SRS>
+ <SRS>EPSG:5704</SRS>
+ <SRS>EPSG:5705</SRS>
+ <SRS>EPSG:5706</SRS>
+ <SRS>EPSG:5709</SRS>
+ <SRS>EPSG:5710</SRS>
+ <SRS>EPSG:5711</SRS>
+ <SRS>EPSG:5712</SRS>
+ <SRS>EPSG:5713</SRS>
+ <SRS>EPSG:5714</SRS>
+ <SRS>EPSG:5715</SRS>
+ <SRS>EPSG:5716</SRS>
+ <SRS>EPSG:5717</SRS>
+ <SRS>EPSG:5718</SRS>
+ <SRS>EPSG:5719</SRS>
+ <SRS>EPSG:5720</SRS>
+ <SRS>EPSG:5721</SRS>
+ <SRS>EPSG:5722</SRS>
+ <SRS>EPSG:5723</SRS>
+ <SRS>EPSG:5724</SRS>
+ <SRS>EPSG:5725</SRS>
+ <SRS>EPSG:5726</SRS>
+ <SRS>EPSG:5727</SRS>
+ <SRS>EPSG:5728</SRS>
+ <SRS>EPSG:5729</SRS>
+ <SRS>EPSG:5730</SRS>
+ <SRS>EPSG:5731</SRS>
+ <SRS>EPSG:5732</SRS>
+ <SRS>EPSG:5733</SRS>
+ <SRS>EPSG:5734</SRS>
+ <SRS>EPSG:5735</SRS>
+ <SRS>EPSG:5736</SRS>
+ <SRS>EPSG:5737</SRS>
+ <SRS>EPSG:5738</SRS>
+ <SRS>EPSG:5739</SRS>
+ <SRS>EPSG:5740</SRS>
+ <SRS>EPSG:5741</SRS>
+ <SRS>EPSG:5742</SRS>
+ <SRS>EPSG:5743</SRS>
+ <SRS>EPSG:5744</SRS>
+ <SRS>EPSG:5745</SRS>
+ <SRS>EPSG:5746</SRS>
+ <SRS>EPSG:5747</SRS>
+ <SRS>EPSG:5748</SRS>
+ <SRS>EPSG:5749</SRS>
+ <SRS>EPSG:5750</SRS>
+ <SRS>EPSG:5751</SRS>
+ <SRS>EPSG:5752</SRS>
+ <SRS>EPSG:5753</SRS>
+ <SRS>EPSG:5754</SRS>
+ <SRS>EPSG:5755</SRS>
+ <SRS>EPSG:5756</SRS>
+ <SRS>EPSG:5757</SRS>
+ <SRS>EPSG:5758</SRS>
+ <SRS>EPSG:5759</SRS>
+ <SRS>EPSG:5760</SRS>
+ <SRS>EPSG:5761</SRS>
+ <SRS>EPSG:5762</SRS>
+ <SRS>EPSG:5763</SRS>
+ <SRS>EPSG:5764</SRS>
+ <SRS>EPSG:5765</SRS>
+ <SRS>EPSG:5766</SRS>
+ <SRS>EPSG:5767</SRS>
+ <SRS>EPSG:5768</SRS>
+ <SRS>EPSG:5769</SRS>
+ <SRS>EPSG:5770</SRS>
+ <SRS>EPSG:5771</SRS>
+ <SRS>EPSG:5772</SRS>
+ <SRS>EPSG:5773</SRS>
+ <SRS>EPSG:5774</SRS>
+ <SRS>EPSG:5775</SRS>
+ <SRS>EPSG:5776</SRS>
+ <SRS>EPSG:5777</SRS>
+ <SRS>EPSG:5778</SRS>
+ <SRS>EPSG:5779</SRS>
+ <SRS>EPSG:5780</SRS>
+ <SRS>EPSG:5781</SRS>
+ <SRS>EPSG:5782</SRS>
+ <SRS>EPSG:5783</SRS>
+ <SRS>EPSG:5784</SRS>
+ <SRS>EPSG:5785</SRS>
+ <SRS>EPSG:5786</SRS>
+ <SRS>EPSG:5787</SRS>
+ <SRS>EPSG:5788</SRS>
+ <SRS>EPSG:5789</SRS>
+ <SRS>EPSG:5790</SRS>
+ <SRS>EPSG:5791</SRS>
+ <SRS>EPSG:5792</SRS>
+ <SRS>EPSG:5793</SRS>
+ <SRS>EPSG:5794</SRS>
+ <SRS>EPSG:5795</SRS>
+ <SRS>EPSG:5796</SRS>
+ <SRS>EPSG:5797</SRS>
+ <SRS>EPSG:5798</SRS>
+ <SRS>EPSG:5799</SRS>
+ <SRS>EPSG:5800</SRS>
+ <SRS>EPSG:5801</SRS>
+ <SRS>EPSG:5802</SRS>
+ <SRS>EPSG:5803</SRS>
+ <SRS>EPSG:5804</SRS>
+ <SRS>EPSG:5805</SRS>
+ <SRS>EPSG:5806</SRS>
+ <SRS>EPSG:5807</SRS>
+ <SRS>EPSG:5808</SRS>
+ <SRS>EPSG:5809</SRS>
+ <SRS>EPSG:5810</SRS>
+ <SRS>EPSG:5811</SRS>
+ <SRS>EPSG:5812</SRS>
+ <SRS>EPSG:5813</SRS>
+ <SRS>EPSG:5814</SRS>
+ <SRS>EPSG:5815</SRS>
+ <SRS>EPSG:5816</SRS>
+ <SRS>EPSG:5817</SRS>
+ <SRS>EPSG:5818</SRS>
+ <SRS>EPSG:7400</SRS>
+ <SRS>EPSG:7401</SRS>
+ <SRS>EPSG:7402</SRS>
+ <SRS>EPSG:7403</SRS>
+ <SRS>EPSG:7404</SRS>
+ <SRS>EPSG:7405</SRS>
+ <SRS>EPSG:7406</SRS>
+ <SRS>EPSG:7407</SRS>
+ <SRS>EPSG:7408</SRS>
+ <SRS>EPSG:7409</SRS>
+ <SRS>EPSG:7410</SRS>
+ <SRS>EPSG:7411</SRS>
+ <SRS>EPSG:7412</SRS>
+ <SRS>EPSG:7413</SRS>
+ <SRS>EPSG:7414</SRS>
+ <SRS>EPSG:7415</SRS>
+ <SRS>EPSG:7416</SRS>
+ <SRS>EPSG:7417</SRS>
+ <SRS>EPSG:7418</SRS>
+ <SRS>EPSG:7419</SRS>
+ <SRS>EPSG:7420</SRS>
+ <SRS>EPSG:20004</SRS>
+ <SRS>EPSG:20005</SRS>
+ <SRS>EPSG:20006</SRS>
+ <SRS>EPSG:20007</SRS>
+ <SRS>EPSG:20008</SRS>
+ <SRS>EPSG:20009</SRS>
+ <SRS>EPSG:20010</SRS>
+ <SRS>EPSG:20011</SRS>
+ <SRS>EPSG:20012</SRS>
+ <SRS>EPSG:20013</SRS>
+ <SRS>EPSG:20014</SRS>
+ <SRS>EPSG:20015</SRS>
+ <SRS>EPSG:20016</SRS>
+ <SRS>EPSG:20017</SRS>
+ <SRS>EPSG:20018</SRS>
+ <SRS>EPSG:20019</SRS>
+ <SRS>EPSG:20020</SRS>
+ <SRS>EPSG:20021</SRS>
+ <SRS>EPSG:20022</SRS>
+ <SRS>EPSG:20023</SRS>
+ <SRS>EPSG:20024</SRS>
+ <SRS>EPSG:20025</SRS>
+ <SRS>EPSG:20026</SRS>
+ <SRS>EPSG:20027</SRS>
+ <SRS>EPSG:20028</SRS>
+ <SRS>EPSG:20029</SRS>
+ <SRS>EPSG:20030</SRS>
+ <SRS>EPSG:20031</SRS>
+ <SRS>EPSG:20032</SRS>
+ <SRS>EPSG:20064</SRS>
+ <SRS>EPSG:20065</SRS>
+ <SRS>EPSG:20066</SRS>
+ <SRS>EPSG:20067</SRS>
+ <SRS>EPSG:20068</SRS>
+ <SRS>EPSG:20069</SRS>
+ <SRS>EPSG:20070</SRS>
+ <SRS>EPSG:20071</SRS>
+ <SRS>EPSG:20072</SRS>
+ <SRS>EPSG:20073</SRS>
+ <SRS>EPSG:20074</SRS>
+ <SRS>EPSG:20075</SRS>
+ <SRS>EPSG:20076</SRS>
+ <SRS>EPSG:20077</SRS>
+ <SRS>EPSG:20078</SRS>
+ <SRS>EPSG:20079</SRS>
+ <SRS>EPSG:20080</SRS>
+ <SRS>EPSG:20081</SRS>
+ <SRS>EPSG:20082</SRS>
+ <SRS>EPSG:20083</SRS>
+ <SRS>EPSG:20084</SRS>
+ <SRS>EPSG:20085</SRS>
+ <SRS>EPSG:20086</SRS>
+ <SRS>EPSG:20087</SRS>
+ <SRS>EPSG:20088</SRS>
+ <SRS>EPSG:20089</SRS>
+ <SRS>EPSG:20090</SRS>
+ <SRS>EPSG:20091</SRS>
+ <SRS>EPSG:20092</SRS>
+ <SRS>EPSG:20135</SRS>
+ <SRS>EPSG:20136</SRS>
+ <SRS>EPSG:20137</SRS>
+ <SRS>EPSG:20138</SRS>
+ <SRS>EPSG:20248</SRS>
+ <SRS>EPSG:20249</SRS>
+ <SRS>EPSG:20250</SRS>
+ <SRS>EPSG:20251</SRS>
+ <SRS>EPSG:20252</SRS>
+ <SRS>EPSG:20253</SRS>
+ <SRS>EPSG:20254</SRS>
+ <SRS>EPSG:20255</SRS>
+ <SRS>EPSG:20256</SRS>
+ <SRS>EPSG:20257</SRS>
+ <SRS>EPSG:20258</SRS>
+ <SRS>EPSG:20348</SRS>
+ <SRS>EPSG:20349</SRS>
+ <SRS>EPSG:20350</SRS>
+ <SRS>EPSG:20351</SRS>
+ <SRS>EPSG:20352</SRS>
+ <SRS>EPSG:20353</SRS>
+ <SRS>EPSG:20354</SRS>
+ <SRS>EPSG:20355</SRS>
+ <SRS>EPSG:20356</SRS>
+ <SRS>EPSG:20357</SRS>
+ <SRS>EPSG:20358</SRS>
+ <SRS>EPSG:20436</SRS>
+ <SRS>EPSG:20437</SRS>
+ <SRS>EPSG:20438</SRS>
+ <SRS>EPSG:20439</SRS>
+ <SRS>EPSG:20440</SRS>
+ <SRS>EPSG:20499</SRS>
+ <SRS>EPSG:20538</SRS>
+ <SRS>EPSG:20539</SRS>
+ <SRS>EPSG:20790</SRS>
+ <SRS>EPSG:20791</SRS>
+ <SRS>EPSG:20822</SRS>
+ <SRS>EPSG:20823</SRS>
+ <SRS>EPSG:20824</SRS>
+ <SRS>EPSG:20934</SRS>
+ <SRS>EPSG:20935</SRS>
+ <SRS>EPSG:20936</SRS>
+ <SRS>EPSG:21035</SRS>
+ <SRS>EPSG:21036</SRS>
+ <SRS>EPSG:21037</SRS>
+ <SRS>EPSG:21095</SRS>
+ <SRS>EPSG:21096</SRS>
+ <SRS>EPSG:21097</SRS>
+ <SRS>EPSG:21100</SRS>
+ <SRS>EPSG:21148</SRS>
+ <SRS>EPSG:21149</SRS>
+ <SRS>EPSG:21150</SRS>
+ <SRS>EPSG:21291</SRS>
+ <SRS>EPSG:21292</SRS>
+ <SRS>EPSG:21413</SRS>
+ <SRS>EPSG:21414</SRS>
+ <SRS>EPSG:21415</SRS>
+ <SRS>EPSG:21416</SRS>
+ <SRS>EPSG:21417</SRS>
+ <SRS>EPSG:21418</SRS>
+ <SRS>EPSG:21419</SRS>
+ <SRS>EPSG:21420</SRS>
+ <SRS>EPSG:21421</SRS>
+ <SRS>EPSG:21422</SRS>
+ <SRS>EPSG:21423</SRS>
+ <SRS>EPSG:21453</SRS>
+ <SRS>EPSG:21454</SRS>
+ <SRS>EPSG:21455</SRS>
+ <SRS>EPSG:21456</SRS>
+ <SRS>EPSG:21457</SRS>
+ <SRS>EPSG:21458</SRS>
+ <SRS>EPSG:21459</SRS>
+ <SRS>EPSG:21460</SRS>
+ <SRS>EPSG:21461</SRS>
+ <SRS>EPSG:21462</SRS>
+ <SRS>EPSG:21463</SRS>
+ <SRS>EPSG:21473</SRS>
+ <SRS>EPSG:21474</SRS>
+ <SRS>EPSG:21475</SRS>
+ <SRS>EPSG:21476</SRS>
+ <SRS>EPSG:21477</SRS>
+ <SRS>EPSG:21478</SRS>
+ <SRS>EPSG:21479</SRS>
+ <SRS>EPSG:21480</SRS>
+ <SRS>EPSG:21481</SRS>
+ <SRS>EPSG:21482</SRS>
+ <SRS>EPSG:21483</SRS>
+ <SRS>EPSG:21500</SRS>
+ <SRS>EPSG:21780</SRS>
+ <SRS>EPSG:21781</SRS>
+ <SRS>EPSG:21817</SRS>
+ <SRS>EPSG:21818</SRS>
+ <SRS>EPSG:21891</SRS>
+ <SRS>EPSG:21892</SRS>
+ <SRS>EPSG:21893</SRS>
+ <SRS>EPSG:21894</SRS>
+ <SRS>EPSG:21896</SRS>
+ <SRS>EPSG:21897</SRS>
+ <SRS>EPSG:21898</SRS>
+ <SRS>EPSG:21899</SRS>
+ <SRS>EPSG:22032</SRS>
+ <SRS>EPSG:22033</SRS>
+ <SRS>EPSG:22091</SRS>
+ <SRS>EPSG:22092</SRS>
+ <SRS>EPSG:22171</SRS>
+ <SRS>EPSG:22172</SRS>
+ <SRS>EPSG:22173</SRS>
+ <SRS>EPSG:22174</SRS>
+ <SRS>EPSG:22175</SRS>
+ <SRS>EPSG:22176</SRS>
+ <SRS>EPSG:22177</SRS>
+ <SRS>EPSG:22181</SRS>
+ <SRS>EPSG:22182</SRS>
+ <SRS>EPSG:22183</SRS>
+ <SRS>EPSG:22184</SRS>
+ <SRS>EPSG:22185</SRS>
+ <SRS>EPSG:22186</SRS>
+ <SRS>EPSG:22187</SRS>
+ <SRS>EPSG:22191</SRS>
+ <SRS>EPSG:22192</SRS>
+ <SRS>EPSG:22193</SRS>
+ <SRS>EPSG:22194</SRS>
+ <SRS>EPSG:22195</SRS>
+ <SRS>EPSG:22196</SRS>
+ <SRS>EPSG:22197</SRS>
+ <SRS>EPSG:22234</SRS>
+ <SRS>EPSG:22235</SRS>
+ <SRS>EPSG:22236</SRS>
+ <SRS>EPSG:22275</SRS>
+ <SRS>EPSG:22277</SRS>
+ <SRS>EPSG:22279</SRS>
+ <SRS>EPSG:22281</SRS>
+ <SRS>EPSG:22283</SRS>
+ <SRS>EPSG:22285</SRS>
+ <SRS>EPSG:22287</SRS>
+ <SRS>EPSG:22289</SRS>
+ <SRS>EPSG:22291</SRS>
+ <SRS>EPSG:22293</SRS>
+ <SRS>EPSG:22300</SRS>
+ <SRS>EPSG:22332</SRS>
+ <SRS>EPSG:22391</SRS>
+ <SRS>EPSG:22392</SRS>
+ <SRS>EPSG:22521</SRS>
+ <SRS>EPSG:22522</SRS>
+ <SRS>EPSG:22523</SRS>
+ <SRS>EPSG:22524</SRS>
+ <SRS>EPSG:22525</SRS>
+ <SRS>EPSG:22700</SRS>
+ <SRS>EPSG:22770</SRS>
+ <SRS>EPSG:22780</SRS>
+ <SRS>EPSG:22832</SRS>
+ <SRS>EPSG:22991</SRS>
+ <SRS>EPSG:22992</SRS>
+ <SRS>EPSG:22993</SRS>
+ <SRS>EPSG:22994</SRS>
+ <SRS>EPSG:23028</SRS>
+ <SRS>EPSG:23029</SRS>
+ <SRS>EPSG:23030</SRS>
+ <SRS>EPSG:23031</SRS>
+ <SRS>EPSG:23032</SRS>
+ <SRS>EPSG:23033</SRS>
+ <SRS>EPSG:23034</SRS>
+ <SRS>EPSG:23035</SRS>
+ <SRS>EPSG:23036</SRS>
+ <SRS>EPSG:23037</SRS>
+ <SRS>EPSG:23038</SRS>
+ <SRS>EPSG:23090</SRS>
+ <SRS>EPSG:23095</SRS>
+ <SRS>EPSG:23239</SRS>
+ <SRS>EPSG:23240</SRS>
+ <SRS>EPSG:23433</SRS>
+ <SRS>EPSG:23700</SRS>
+ <SRS>EPSG:23846</SRS>
+ <SRS>EPSG:23847</SRS>
+ <SRS>EPSG:23848</SRS>
+ <SRS>EPSG:23849</SRS>
+ <SRS>EPSG:23850</SRS>
+ <SRS>EPSG:23851</SRS>
+ <SRS>EPSG:23852</SRS>
+ <SRS>EPSG:23853</SRS>
+ <SRS>EPSG:23866</SRS>
+ <SRS>EPSG:23867</SRS>
+ <SRS>EPSG:23868</SRS>
+ <SRS>EPSG:23869</SRS>
+ <SRS>EPSG:23870</SRS>
+ <SRS>EPSG:23871</SRS>
+ <SRS>EPSG:23872</SRS>
+ <SRS>EPSG:23877</SRS>
+ <SRS>EPSG:23878</SRS>
+ <SRS>EPSG:23879</SRS>
+ <SRS>EPSG:23880</SRS>
+ <SRS>EPSG:23881</SRS>
+ <SRS>EPSG:23882</SRS>
+ <SRS>EPSG:23883</SRS>
+ <SRS>EPSG:23884</SRS>
+ <SRS>EPSG:23886</SRS>
+ <SRS>EPSG:23887</SRS>
+ <SRS>EPSG:23888</SRS>
+ <SRS>EPSG:23889</SRS>
+ <SRS>EPSG:23890</SRS>
+ <SRS>EPSG:23891</SRS>
+ <SRS>EPSG:23892</SRS>
+ <SRS>EPSG:23893</SRS>
+ <SRS>EPSG:23894</SRS>
+ <SRS>EPSG:23946</SRS>
+ <SRS>EPSG:23947</SRS>
+ <SRS>EPSG:23948</SRS>
+ <SRS>EPSG:24047</SRS>
+ <SRS>EPSG:24048</SRS>
+ <SRS>EPSG:24100</SRS>
+ <SRS>EPSG:24200</SRS>
+ <SRS>EPSG:24305</SRS>
+ <SRS>EPSG:24306</SRS>
+ <SRS>EPSG:24311</SRS>
+ <SRS>EPSG:24312</SRS>
+ <SRS>EPSG:24313</SRS>
+ <SRS>EPSG:24342</SRS>
+ <SRS>EPSG:24343</SRS>
+ <SRS>EPSG:24344</SRS>
+ <SRS>EPSG:24345</SRS>
+ <SRS>EPSG:24346</SRS>
+ <SRS>EPSG:24347</SRS>
+ <SRS>EPSG:24370</SRS>
+ <SRS>EPSG:24371</SRS>
+ <SRS>EPSG:24372</SRS>
+ <SRS>EPSG:24373</SRS>
+ <SRS>EPSG:24374</SRS>
+ <SRS>EPSG:24375</SRS>
+ <SRS>EPSG:24376</SRS>
+ <SRS>EPSG:24377</SRS>
+ <SRS>EPSG:24378</SRS>
+ <SRS>EPSG:24379</SRS>
+ <SRS>EPSG:24380</SRS>
+ <SRS>EPSG:24381</SRS>
+ <SRS>EPSG:24382</SRS>
+ <SRS>EPSG:24383</SRS>
+ <SRS>EPSG:24500</SRS>
+ <SRS>EPSG:24547</SRS>
+ <SRS>EPSG:24548</SRS>
+ <SRS>EPSG:24571</SRS>
+ <SRS>EPSG:24600</SRS>
+ <SRS>EPSG:24718</SRS>
+ <SRS>EPSG:24719</SRS>
+ <SRS>EPSG:24720</SRS>
+ <SRS>EPSG:24817</SRS>
+ <SRS>EPSG:24818</SRS>
+ <SRS>EPSG:24819</SRS>
+ <SRS>EPSG:24820</SRS>
+ <SRS>EPSG:24821</SRS>
+ <SRS>EPSG:24877</SRS>
+ <SRS>EPSG:24878</SRS>
+ <SRS>EPSG:24879</SRS>
+ <SRS>EPSG:24880</SRS>
+ <SRS>EPSG:24881</SRS>
+ <SRS>EPSG:24882</SRS>
+ <SRS>EPSG:24891</SRS>
+ <SRS>EPSG:24892</SRS>
+ <SRS>EPSG:24893</SRS>
+ <SRS>EPSG:25000</SRS>
+ <SRS>EPSG:25231</SRS>
+ <SRS>EPSG:25391</SRS>
+ <SRS>EPSG:25392</SRS>
+ <SRS>EPSG:25393</SRS>
+ <SRS>EPSG:25394</SRS>
+ <SRS>EPSG:25395</SRS>
+ <SRS>EPSG:25700</SRS>
+ <SRS>EPSG:25828</SRS>
+ <SRS>EPSG:25829</SRS>
+ <SRS>EPSG:25830</SRS>
+ <SRS>EPSG:25831</SRS>
+ <SRS>EPSG:25832</SRS>
+ <SRS>EPSG:25833</SRS>
+ <SRS>EPSG:25834</SRS>
+ <SRS>EPSG:25835</SRS>
+ <SRS>EPSG:25836</SRS>
+ <SRS>EPSG:25837</SRS>
+ <SRS>EPSG:25838</SRS>
+ <SRS>EPSG:25884</SRS>
+ <SRS>EPSG:25932</SRS>
+ <SRS>EPSG:26191</SRS>
+ <SRS>EPSG:26192</SRS>
+ <SRS>EPSG:26193</SRS>
+ <SRS>EPSG:26194</SRS>
+ <SRS>EPSG:26195</SRS>
+ <SRS>EPSG:26237</SRS>
+ <SRS>EPSG:26331</SRS>
+ <SRS>EPSG:26332</SRS>
+ <SRS>EPSG:26391</SRS>
+ <SRS>EPSG:26392</SRS>
+ <SRS>EPSG:26393</SRS>
+ <SRS>EPSG:26432</SRS>
+ <SRS>EPSG:26591</SRS>
+ <SRS>EPSG:26592</SRS>
+ <SRS>EPSG:26632</SRS>
+ <SRS>EPSG:26692</SRS>
+ <SRS>EPSG:26701</SRS>
+ <SRS>EPSG:26702</SRS>
+ <SRS>EPSG:26703</SRS>
+ <SRS>EPSG:26704</SRS>
+ <SRS>EPSG:26705</SRS>
+ <SRS>EPSG:26706</SRS>
+ <SRS>EPSG:26707</SRS>
+ <SRS>EPSG:26708</SRS>
+ <SRS>EPSG:26709</SRS>
+ <SRS>EPSG:26710</SRS>
+ <SRS>EPSG:26711</SRS>
+ <SRS>EPSG:26712</SRS>
+ <SRS>EPSG:26713</SRS>
+ <SRS>EPSG:26714</SRS>
+ <SRS>EPSG:26715</SRS>
+ <SRS>EPSG:26716</SRS>
+ <SRS>EPSG:26717</SRS>
+ <SRS>EPSG:26718</SRS>
+ <SRS>EPSG:26719</SRS>
+ <SRS>EPSG:26720</SRS>
+ <SRS>EPSG:26721</SRS>
+ <SRS>EPSG:26722</SRS>
+ <SRS>EPSG:26729</SRS>
+ <SRS>EPSG:26730</SRS>
+ <SRS>EPSG:26731</SRS>
+ <SRS>EPSG:26732</SRS>
+ <SRS>EPSG:26733</SRS>
+ <SRS>EPSG:26734</SRS>
+ <SRS>EPSG:26735</SRS>
+ <SRS>EPSG:26736</SRS>
+ <SRS>EPSG:26737</SRS>
+ <SRS>EPSG:26738</SRS>
+ <SRS>EPSG:26739</SRS>
+ <SRS>EPSG:26740</SRS>
+ <SRS>EPSG:26741</SRS>
+ <SRS>EPSG:26742</SRS>
+ <SRS>EPSG:26743</SRS>
+ <SRS>EPSG:26744</SRS>
+ <SRS>EPSG:26745</SRS>
+ <SRS>EPSG:26746</SRS>
+ <SRS>EPSG:26747</SRS>
+ <SRS>EPSG:26748</SRS>
+ <SRS>EPSG:26749</SRS>
+ <SRS>EPSG:26750</SRS>
+ <SRS>EPSG:26751</SRS>
+ <SRS>EPSG:26752</SRS>
+ <SRS>EPSG:26753</SRS>
+ <SRS>EPSG:26754</SRS>
+ <SRS>EPSG:26755</SRS>
+ <SRS>EPSG:26756</SRS>
+ <SRS>EPSG:26757</SRS>
+ <SRS>EPSG:26758</SRS>
+ <SRS>EPSG:26759</SRS>
+ <SRS>EPSG:26760</SRS>
+ <SRS>EPSG:26766</SRS>
+ <SRS>EPSG:26767</SRS>
+ <SRS>EPSG:26768</SRS>
+ <SRS>EPSG:26769</SRS>
+ <SRS>EPSG:26770</SRS>
+ <SRS>EPSG:26771</SRS>
+ <SRS>EPSG:26772</SRS>
+ <SRS>EPSG:26773</SRS>
+ <SRS>EPSG:26774</SRS>
+ <SRS>EPSG:26775</SRS>
+ <SRS>EPSG:26776</SRS>
+ <SRS>EPSG:26777</SRS>
+ <SRS>EPSG:26778</SRS>
+ <SRS>EPSG:26779</SRS>
+ <SRS>EPSG:26780</SRS>
+ <SRS>EPSG:26781</SRS>
+ <SRS>EPSG:26782</SRS>
+ <SRS>EPSG:26783</SRS>
+ <SRS>EPSG:26784</SRS>
+ <SRS>EPSG:26785</SRS>
+ <SRS>EPSG:26786</SRS>
+ <SRS>EPSG:26787</SRS>
+ <SRS>EPSG:26791</SRS>
+ <SRS>EPSG:26792</SRS>
+ <SRS>EPSG:26793</SRS>
+ <SRS>EPSG:26794</SRS>
+ <SRS>EPSG:26795</SRS>
+ <SRS>EPSG:26796</SRS>
+ <SRS>EPSG:26797</SRS>
+ <SRS>EPSG:26798</SRS>
+ <SRS>EPSG:26799</SRS>
+ <SRS>EPSG:26801</SRS>
+ <SRS>EPSG:26802</SRS>
+ <SRS>EPSG:26803</SRS>
+ <SRS>EPSG:26811</SRS>
+ <SRS>EPSG:26812</SRS>
+ <SRS>EPSG:26813</SRS>
+ <SRS>EPSG:26901</SRS>
+ <SRS>EPSG:26902</SRS>
+ <SRS>EPSG:26903</SRS>
+ <SRS>EPSG:26904</SRS>
+ <SRS>EPSG:26905</SRS>
+ <SRS>EPSG:26906</SRS>
+ <SRS>EPSG:26907</SRS>
+ <SRS>EPSG:26908</SRS>
+ <SRS>EPSG:26909</SRS>
+ <SRS>EPSG:26910</SRS>
+ <SRS>EPSG:26911</SRS>
+ <SRS>EPSG:26912</SRS>
+ <SRS>EPSG:26913</SRS>
+ <SRS>EPSG:26914</SRS>
+ <SRS>EPSG:26915</SRS>
+ <SRS>EPSG:26916</SRS>
+ <SRS>EPSG:26917</SRS>
+ <SRS>EPSG:26918</SRS>
+ <SRS>EPSG:26919</SRS>
+ <SRS>EPSG:26920</SRS>
+ <SRS>EPSG:26921</SRS>
+ <SRS>EPSG:26922</SRS>
+ <SRS>EPSG:26923</SRS>
+ <SRS>EPSG:26929</SRS>
+ <SRS>EPSG:26930</SRS>
+ <SRS>EPSG:26931</SRS>
+ <SRS>EPSG:26932</SRS>
+ <SRS>EPSG:26933</SRS>
+ <SRS>EPSG:26934</SRS>
+ <SRS>EPSG:26935</SRS>
+ <SRS>EPSG:26936</SRS>
+ <SRS>EPSG:26937</SRS>
+ <SRS>EPSG:26938</SRS>
+ <SRS>EPSG:26939</SRS>
+ <SRS>EPSG:26940</SRS>
+ <SRS>EPSG:26941</SRS>
+ <SRS>EPSG:26942</SRS>
+ <SRS>EPSG:26943</SRS>
+ <SRS>EPSG:26944</SRS>
+ <SRS>EPSG:26945</SRS>
+ <SRS>EPSG:26946</SRS>
+ <SRS>EPSG:26948</SRS>
+ <SRS>EPSG:26949</SRS>
+ <SRS>EPSG:26950</SRS>
+ <SRS>EPSG:26951</SRS>
+ <SRS>EPSG:26952</SRS>
+ <SRS>EPSG:26953</SRS>
+ <SRS>EPSG:26954</SRS>
+ <SRS>EPSG:26955</SRS>
+ <SRS>EPSG:26956</SRS>
+ <SRS>EPSG:26957</SRS>
+ <SRS>EPSG:26958</SRS>
+ <SRS>EPSG:26959</SRS>
+ <SRS>EPSG:26960</SRS>
+ <SRS>EPSG:26961</SRS>
+ <SRS>EPSG:26962</SRS>
+ <SRS>EPSG:26963</SRS>
+ <SRS>EPSG:26964</SRS>
+ <SRS>EPSG:26965</SRS>
+ <SRS>EPSG:26966</SRS>
+ <SRS>EPSG:26967</SRS>
+ <SRS>EPSG:26968</SRS>
+ <SRS>EPSG:26969</SRS>
+ <SRS>EPSG:26970</SRS>
+ <SRS>EPSG:26971</SRS>
+ <SRS>EPSG:26972</SRS>
+ <SRS>EPSG:26973</SRS>
+ <SRS>EPSG:26974</SRS>
+ <SRS>EPSG:26975</SRS>
+ <SRS>EPSG:26976</SRS>
+ <SRS>EPSG:26977</SRS>
+ <SRS>EPSG:26978</SRS>
+ <SRS>EPSG:26979</SRS>
+ <SRS>EPSG:26980</SRS>
+ <SRS>EPSG:26981</SRS>
+ <SRS>EPSG:26982</SRS>
+ <SRS>EPSG:26983</SRS>
+ <SRS>EPSG:26984</SRS>
+ <SRS>EPSG:26985</SRS>
+ <SRS>EPSG:26986</SRS>
+ <SRS>EPSG:26987</SRS>
+ <SRS>EPSG:26988</SRS>
+ <SRS>EPSG:26989</SRS>
+ <SRS>EPSG:26990</SRS>
+ <SRS>EPSG:26991</SRS>
+ <SRS>EPSG:26992</SRS>
+ <SRS>EPSG:26993</SRS>
+ <SRS>EPSG:26994</SRS>
+ <SRS>EPSG:26995</SRS>
+ <SRS>EPSG:26996</SRS>
+ <SRS>EPSG:26997</SRS>
+ <SRS>EPSG:26998</SRS>
+ <SRS>EPSG:27037</SRS>
+ <SRS>EPSG:27038</SRS>
+ <SRS>EPSG:27039</SRS>
+ <SRS>EPSG:27040</SRS>
+ <SRS>EPSG:27120</SRS>
+ <SRS>EPSG:27200</SRS>
+ <SRS>EPSG:27205</SRS>
+ <SRS>EPSG:27206</SRS>
+ <SRS>EPSG:27207</SRS>
+ <SRS>EPSG:27208</SRS>
+ <SRS>EPSG:27209</SRS>
+ <SRS>EPSG:27210</SRS>
+ <SRS>EPSG:27211</SRS>
+ <SRS>EPSG:27212</SRS>
+ <SRS>EPSG:27213</SRS>
+ <SRS>EPSG:27214</SRS>
+ <SRS>EPSG:27215</SRS>
+ <SRS>EPSG:27216</SRS>
+ <SRS>EPSG:27217</SRS>
+ <SRS>EPSG:27218</SRS>
+ <SRS>EPSG:27219</SRS>
+ <SRS>EPSG:27220</SRS>
+ <SRS>EPSG:27221</SRS>
+ <SRS>EPSG:27222</SRS>
+ <SRS>EPSG:27223</SRS>
+ <SRS>EPSG:27224</SRS>
+ <SRS>EPSG:27225</SRS>
+ <SRS>EPSG:27226</SRS>
+ <SRS>EPSG:27227</SRS>
+ <SRS>EPSG:27228</SRS>
+ <SRS>EPSG:27229</SRS>
+ <SRS>EPSG:27230</SRS>
+ <SRS>EPSG:27231</SRS>
+ <SRS>EPSG:27232</SRS>
+ <SRS>EPSG:27258</SRS>
+ <SRS>EPSG:27259</SRS>
+ <SRS>EPSG:27260</SRS>
+ <SRS>EPSG:27291</SRS>
+ <SRS>EPSG:27292</SRS>
+ <SRS>EPSG:27391</SRS>
+ <SRS>EPSG:27392</SRS>
+ <SRS>EPSG:27393</SRS>
+ <SRS>EPSG:27394</SRS>
+ <SRS>EPSG:27395</SRS>
+ <SRS>EPSG:27396</SRS>
+ <SRS>EPSG:27397</SRS>
+ <SRS>EPSG:27398</SRS>
+ <SRS>EPSG:27429</SRS>
+ <SRS>EPSG:27492</SRS>
+ <SRS>EPSG:27500</SRS>
+ <SRS>EPSG:27561</SRS>
+ <SRS>EPSG:27562</SRS>
+ <SRS>EPSG:27563</SRS>
+ <SRS>EPSG:27564</SRS>
+ <SRS>EPSG:27571</SRS>
+ <SRS>EPSG:27572</SRS>
+ <SRS>EPSG:27573</SRS>
+ <SRS>EPSG:27574</SRS>
+ <SRS>EPSG:27581</SRS>
+ <SRS>EPSG:27582</SRS>
+ <SRS>EPSG:27583</SRS>
+ <SRS>EPSG:27584</SRS>
+ <SRS>EPSG:27591</SRS>
+ <SRS>EPSG:27592</SRS>
+ <SRS>EPSG:27593</SRS>
+ <SRS>EPSG:27594</SRS>
+ <SRS>EPSG:27700</SRS>
+ <SRS>EPSG:28191</SRS>
+ <SRS>EPSG:28192</SRS>
+ <SRS>EPSG:28193</SRS>
+ <SRS>EPSG:28232</SRS>
+ <SRS>EPSG:28348</SRS>
+ <SRS>EPSG:28349</SRS>
+ <SRS>EPSG:28350</SRS>
+ <SRS>EPSG:28351</SRS>
+ <SRS>EPSG:28352</SRS>
+ <SRS>EPSG:28353</SRS>
+ <SRS>EPSG:28354</SRS>
+ <SRS>EPSG:28355</SRS>
+ <SRS>EPSG:28356</SRS>
+ <SRS>EPSG:28357</SRS>
+ <SRS>EPSG:28358</SRS>
+ <SRS>EPSG:28402</SRS>
+ <SRS>EPSG:28403</SRS>
+ <SRS>EPSG:28404</SRS>
+ <SRS>EPSG:28405</SRS>
+ <SRS>EPSG:28406</SRS>
+ <SRS>EPSG:28407</SRS>
+ <SRS>EPSG:28408</SRS>
+ <SRS>EPSG:28409</SRS>
+ <SRS>EPSG:28410</SRS>
+ <SRS>EPSG:28411</SRS>
+ <SRS>EPSG:28412</SRS>
+ <SRS>EPSG:28413</SRS>
+ <SRS>EPSG:28414</SRS>
+ <SRS>EPSG:28415</SRS>
+ <SRS>EPSG:28416</SRS>
+ <SRS>EPSG:28417</SRS>
+ <SRS>EPSG:28418</SRS>
+ <SRS>EPSG:28419</SRS>
+ <SRS>EPSG:28420</SRS>
+ <SRS>EPSG:28421</SRS>
+ <SRS>EPSG:28422</SRS>
+ <SRS>EPSG:28423</SRS>
+ <SRS>EPSG:28424</SRS>
+ <SRS>EPSG:28425</SRS>
+ <SRS>EPSG:28426</SRS>
+ <SRS>EPSG:28427</SRS>
+ <SRS>EPSG:28428</SRS>
+ <SRS>EPSG:28429</SRS>
+ <SRS>EPSG:28430</SRS>
+ <SRS>EPSG:28431</SRS>
+ <SRS>EPSG:28432</SRS>
+ <SRS>EPSG:28462</SRS>
+ <SRS>EPSG:28463</SRS>
+ <SRS>EPSG:28464</SRS>
+ <SRS>EPSG:28465</SRS>
+ <SRS>EPSG:28466</SRS>
+ <SRS>EPSG:28467</SRS>
+ <SRS>EPSG:28468</SRS>
+ <SRS>EPSG:28469</SRS>
+ <SRS>EPSG:28470</SRS>
+ <SRS>EPSG:28471</SRS>
+ <SRS>EPSG:28472</SRS>
+ <SRS>EPSG:28473</SRS>
+ <SRS>EPSG:28474</SRS>
+ <SRS>EPSG:28475</SRS>
+ <SRS>EPSG:28476</SRS>
+ <SRS>EPSG:28477</SRS>
+ <SRS>EPSG:28478</SRS>
+ <SRS>EPSG:28479</SRS>
+ <SRS>EPSG:28480</SRS>
+ <SRS>EPSG:28481</SRS>
+ <SRS>EPSG:28482</SRS>
+ <SRS>EPSG:28483</SRS>
+ <SRS>EPSG:28484</SRS>
+ <SRS>EPSG:28485</SRS>
+ <SRS>EPSG:28486</SRS>
+ <SRS>EPSG:28487</SRS>
+ <SRS>EPSG:28488</SRS>
+ <SRS>EPSG:28489</SRS>
+ <SRS>EPSG:28490</SRS>
+ <SRS>EPSG:28491</SRS>
+ <SRS>EPSG:28492</SRS>
+ <SRS>EPSG:28600</SRS>
+ <SRS>EPSG:28991</SRS>
+ <SRS>EPSG:28992</SRS>
+ <SRS>EPSG:29100</SRS>
+ <SRS>EPSG:29101</SRS>
+ <SRS>EPSG:29118</SRS>
+ <SRS>EPSG:29119</SRS>
+ <SRS>EPSG:29120</SRS>
+ <SRS>EPSG:29121</SRS>
+ <SRS>EPSG:29122</SRS>
+ <SRS>EPSG:29168</SRS>
+ <SRS>EPSG:29169</SRS>
+ <SRS>EPSG:29170</SRS>
+ <SRS>EPSG:29171</SRS>
+ <SRS>EPSG:29172</SRS>
+ <SRS>EPSG:29177</SRS>
+ <SRS>EPSG:29178</SRS>
+ <SRS>EPSG:29179</SRS>
+ <SRS>EPSG:29180</SRS>
+ <SRS>EPSG:29181</SRS>
+ <SRS>EPSG:29182</SRS>
+ <SRS>EPSG:29183</SRS>
+ <SRS>EPSG:29184</SRS>
+ <SRS>EPSG:29185</SRS>
+ <SRS>EPSG:29187</SRS>
+ <SRS>EPSG:29188</SRS>
+ <SRS>EPSG:29189</SRS>
+ <SRS>EPSG:29190</SRS>
+ <SRS>EPSG:29191</SRS>
+ <SRS>EPSG:29192</SRS>
+ <SRS>EPSG:29193</SRS>
+ <SRS>EPSG:29194</SRS>
+ <SRS>EPSG:29195</SRS>
+ <SRS>EPSG:29220</SRS>
+ <SRS>EPSG:29221</SRS>
+ <SRS>EPSG:29333</SRS>
+ <SRS>EPSG:29371</SRS>
+ <SRS>EPSG:29373</SRS>
+ <SRS>EPSG:29375</SRS>
+ <SRS>EPSG:29377</SRS>
+ <SRS>EPSG:29379</SRS>
+ <SRS>EPSG:29381</SRS>
+ <SRS>EPSG:29383</SRS>
+ <SRS>EPSG:29385</SRS>
+ <SRS>EPSG:29635</SRS>
+ <SRS>EPSG:29636</SRS>
+ <SRS>EPSG:29700</SRS>
+ <SRS>EPSG:29701</SRS>
+ <SRS>EPSG:29702</SRS>
+ <SRS>EPSG:29738</SRS>
+ <SRS>EPSG:29739</SRS>
+ <SRS>EPSG:29849</SRS>
+ <SRS>EPSG:29850</SRS>
+ <SRS>EPSG:29871</SRS>
+ <SRS>EPSG:29872</SRS>
+ <SRS>EPSG:29873</SRS>
+ <SRS>EPSG:29900</SRS>
+ <SRS>EPSG:29901</SRS>
+ <SRS>EPSG:29902</SRS>
+ <SRS>EPSG:29903</SRS>
+ <SRS>EPSG:30161</SRS>
+ <SRS>EPSG:30162</SRS>
+ <SRS>EPSG:30163</SRS>
+ <SRS>EPSG:30164</SRS>
+ <SRS>EPSG:30165</SRS>
+ <SRS>EPSG:30166</SRS>
+ <SRS>EPSG:30167</SRS>
+ <SRS>EPSG:30168</SRS>
+ <SRS>EPSG:30169</SRS>
+ <SRS>EPSG:30170</SRS>
+ <SRS>EPSG:30171</SRS>
+ <SRS>EPSG:30172</SRS>
+ <SRS>EPSG:30173</SRS>
+ <SRS>EPSG:30174</SRS>
+ <SRS>EPSG:30175</SRS>
+ <SRS>EPSG:30176</SRS>
+ <SRS>EPSG:30177</SRS>
+ <SRS>EPSG:30178</SRS>
+ <SRS>EPSG:30179</SRS>
+ <SRS>EPSG:30200</SRS>
+ <SRS>EPSG:30339</SRS>
+ <SRS>EPSG:30340</SRS>
+ <SRS>EPSG:30491</SRS>
+ <SRS>EPSG:30492</SRS>
+ <SRS>EPSG:30493</SRS>
+ <SRS>EPSG:30494</SRS>
+ <SRS>EPSG:30729</SRS>
+ <SRS>EPSG:30730</SRS>
+ <SRS>EPSG:30731</SRS>
+ <SRS>EPSG:30732</SRS>
+ <SRS>EPSG:30791</SRS>
+ <SRS>EPSG:30792</SRS>
+ <SRS>EPSG:30800</SRS>
+ <SRS>EPSG:31028</SRS>
+ <SRS>EPSG:31121</SRS>
+ <SRS>EPSG:31154</SRS>
+ <SRS>EPSG:31170</SRS>
+ <SRS>EPSG:31171</SRS>
+ <SRS>EPSG:31251</SRS>
+ <SRS>EPSG:31252</SRS>
+ <SRS>EPSG:31253</SRS>
+ <SRS>EPSG:31254</SRS>
+ <SRS>EPSG:31255</SRS>
+ <SRS>EPSG:31256</SRS>
+ <SRS>EPSG:31257</SRS>
+ <SRS>EPSG:31258</SRS>
+ <SRS>EPSG:31259</SRS>
+ <SRS>EPSG:31265</SRS>
+ <SRS>EPSG:31266</SRS>
+ <SRS>EPSG:31267</SRS>
+ <SRS>EPSG:31268</SRS>
+ <SRS>EPSG:31275</SRS>
+ <SRS>EPSG:31276</SRS>
+ <SRS>EPSG:31277</SRS>
+ <SRS>EPSG:31278</SRS>
+ <SRS>EPSG:31279</SRS>
+ <SRS>EPSG:31281</SRS>
+ <SRS>EPSG:31282</SRS>
+ <SRS>EPSG:31283</SRS>
+ <SRS>EPSG:31284</SRS>
+ <SRS>EPSG:31285</SRS>
+ <SRS>EPSG:31286</SRS>
+ <SRS>EPSG:31287</SRS>
+ <SRS>EPSG:31288</SRS>
+ <SRS>EPSG:31289</SRS>
+ <SRS>EPSG:31290</SRS>
+ <SRS>EPSG:31291</SRS>
+ <SRS>EPSG:31292</SRS>
+ <SRS>EPSG:31293</SRS>
+ <SRS>EPSG:31294</SRS>
+ <SRS>EPSG:31295</SRS>
+ <SRS>EPSG:31296</SRS>
+ <SRS>EPSG:31297</SRS>
+ <SRS>EPSG:31300</SRS>
+ <SRS>EPSG:31370</SRS>
+ <SRS>EPSG:31461</SRS>
+ <SRS>EPSG:31462</SRS>
+ <SRS>EPSG:31463</SRS>
+ <SRS>EPSG:31464</SRS>
+ <SRS>EPSG:31465</SRS>
+ <SRS>EPSG:31466</SRS>
+ <SRS>EPSG:31467</SRS>
+ <SRS>EPSG:31468</SRS>
+ <SRS>EPSG:31469</SRS>
+ <SRS>EPSG:31528</SRS>
+ <SRS>EPSG:31529</SRS>
+ <SRS>EPSG:31600</SRS>
+ <SRS>EPSG:31700</SRS>
+ <SRS>EPSG:31838</SRS>
+ <SRS>EPSG:31839</SRS>
+ <SRS>EPSG:31900</SRS>
+ <SRS>EPSG:31901</SRS>
+ <SRS>EPSG:31965</SRS>
+ <SRS>EPSG:31966</SRS>
+ <SRS>EPSG:31967</SRS>
+ <SRS>EPSG:31968</SRS>
+ <SRS>EPSG:31969</SRS>
+ <SRS>EPSG:31970</SRS>
+ <SRS>EPSG:31971</SRS>
+ <SRS>EPSG:31972</SRS>
+ <SRS>EPSG:31973</SRS>
+ <SRS>EPSG:31974</SRS>
+ <SRS>EPSG:31975</SRS>
+ <SRS>EPSG:31976</SRS>
+ <SRS>EPSG:31977</SRS>
+ <SRS>EPSG:31978</SRS>
+ <SRS>EPSG:31979</SRS>
+ <SRS>EPSG:31980</SRS>
+ <SRS>EPSG:31981</SRS>
+ <SRS>EPSG:31982</SRS>
+ <SRS>EPSG:31983</SRS>
+ <SRS>EPSG:31984</SRS>
+ <SRS>EPSG:31985</SRS>
+ <SRS>EPSG:31986</SRS>
+ <SRS>EPSG:31987</SRS>
+ <SRS>EPSG:31988</SRS>
+ <SRS>EPSG:31989</SRS>
+ <SRS>EPSG:31990</SRS>
+ <SRS>EPSG:31991</SRS>
+ <SRS>EPSG:31992</SRS>
+ <SRS>EPSG:31993</SRS>
+ <SRS>EPSG:31994</SRS>
+ <SRS>EPSG:31995</SRS>
+ <SRS>EPSG:31996</SRS>
+ <SRS>EPSG:31997</SRS>
+ <SRS>EPSG:31998</SRS>
+ <SRS>EPSG:31999</SRS>
+ <SRS>EPSG:32000</SRS>
+ <SRS>EPSG:32001</SRS>
+ <SRS>EPSG:32002</SRS>
+ <SRS>EPSG:32003</SRS>
+ <SRS>EPSG:32005</SRS>
+ <SRS>EPSG:32006</SRS>
+ <SRS>EPSG:32007</SRS>
+ <SRS>EPSG:32008</SRS>
+ <SRS>EPSG:32009</SRS>
+ <SRS>EPSG:32010</SRS>
+ <SRS>EPSG:32011</SRS>
+ <SRS>EPSG:32012</SRS>
+ <SRS>EPSG:32013</SRS>
+ <SRS>EPSG:32014</SRS>
+ <SRS>EPSG:32015</SRS>
+ <SRS>EPSG:32016</SRS>
+ <SRS>EPSG:32017</SRS>
+ <SRS>EPSG:32018</SRS>
+ <SRS>EPSG:32019</SRS>
+ <SRS>EPSG:32020</SRS>
+ <SRS>EPSG:32021</SRS>
+ <SRS>EPSG:32022</SRS>
+ <SRS>EPSG:32023</SRS>
+ <SRS>EPSG:32024</SRS>
+ <SRS>EPSG:32025</SRS>
+ <SRS>EPSG:32026</SRS>
+ <SRS>EPSG:32027</SRS>
+ <SRS>EPSG:32028</SRS>
+ <SRS>EPSG:32029</SRS>
+ <SRS>EPSG:32030</SRS>
+ <SRS>EPSG:32031</SRS>
+ <SRS>EPSG:32033</SRS>
+ <SRS>EPSG:32034</SRS>
+ <SRS>EPSG:32035</SRS>
+ <SRS>EPSG:32036</SRS>
+ <SRS>EPSG:32037</SRS>
+ <SRS>EPSG:32038</SRS>
+ <SRS>EPSG:32039</SRS>
+ <SRS>EPSG:32040</SRS>
+ <SRS>EPSG:32041</SRS>
+ <SRS>EPSG:32042</SRS>
+ <SRS>EPSG:32043</SRS>
+ <SRS>EPSG:32044</SRS>
+ <SRS>EPSG:32045</SRS>
+ <SRS>EPSG:32046</SRS>
+ <SRS>EPSG:32047</SRS>
+ <SRS>EPSG:32048</SRS>
+ <SRS>EPSG:32049</SRS>
+ <SRS>EPSG:32050</SRS>
+ <SRS>EPSG:32051</SRS>
+ <SRS>EPSG:32052</SRS>
+ <SRS>EPSG:32053</SRS>
+ <SRS>EPSG:32054</SRS>
+ <SRS>EPSG:32055</SRS>
+ <SRS>EPSG:32056</SRS>
+ <SRS>EPSG:32057</SRS>
+ <SRS>EPSG:32058</SRS>
+ <SRS>EPSG:32061</SRS>
+ <SRS>EPSG:32062</SRS>
+ <SRS>EPSG:32064</SRS>
+ <SRS>EPSG:32065</SRS>
+ <SRS>EPSG:32066</SRS>
+ <SRS>EPSG:32067</SRS>
+ <SRS>EPSG:32074</SRS>
+ <SRS>EPSG:32075</SRS>
+ <SRS>EPSG:32076</SRS>
+ <SRS>EPSG:32077</SRS>
+ <SRS>EPSG:32081</SRS>
+ <SRS>EPSG:32082</SRS>
+ <SRS>EPSG:32083</SRS>
+ <SRS>EPSG:32084</SRS>
+ <SRS>EPSG:32085</SRS>
+ <SRS>EPSG:32086</SRS>
+ <SRS>EPSG:32098</SRS>
+ <SRS>EPSG:32099</SRS>
+ <SRS>EPSG:32100</SRS>
+ <SRS>EPSG:32104</SRS>
+ <SRS>EPSG:32107</SRS>
+ <SRS>EPSG:32108</SRS>
+ <SRS>EPSG:32109</SRS>
+ <SRS>EPSG:32110</SRS>
+ <SRS>EPSG:32111</SRS>
+ <SRS>EPSG:32112</SRS>
+ <SRS>EPSG:32113</SRS>
+ <SRS>EPSG:32114</SRS>
+ <SRS>EPSG:32115</SRS>
+ <SRS>EPSG:32116</SRS>
+ <SRS>EPSG:32117</SRS>
+ <SRS>EPSG:32118</SRS>
+ <SRS>EPSG:32119</SRS>
+ <SRS>EPSG:32120</SRS>
+ <SRS>EPSG:32121</SRS>
+ <SRS>EPSG:32122</SRS>
+ <SRS>EPSG:32123</SRS>
+ <SRS>EPSG:32124</SRS>
+ <SRS>EPSG:32125</SRS>
+ <SRS>EPSG:32126</SRS>
+ <SRS>EPSG:32127</SRS>
+ <SRS>EPSG:32128</SRS>
+ <SRS>EPSG:32129</SRS>
+ <SRS>EPSG:32130</SRS>
+ <SRS>EPSG:32133</SRS>
+ <SRS>EPSG:32134</SRS>
+ <SRS>EPSG:32135</SRS>
+ <SRS>EPSG:32136</SRS>
+ <SRS>EPSG:32137</SRS>
+ <SRS>EPSG:32138</SRS>
+ <SRS>EPSG:32139</SRS>
+ <SRS>EPSG:32140</SRS>
+ <SRS>EPSG:32141</SRS>
+ <SRS>EPSG:32142</SRS>
+ <SRS>EPSG:32143</SRS>
+ <SRS>EPSG:32144</SRS>
+ <SRS>EPSG:32145</SRS>
+ <SRS>EPSG:32146</SRS>
+ <SRS>EPSG:32147</SRS>
+ <SRS>EPSG:32148</SRS>
+ <SRS>EPSG:32149</SRS>
+ <SRS>EPSG:32150</SRS>
+ <SRS>EPSG:32151</SRS>
+ <SRS>EPSG:32152</SRS>
+ <SRS>EPSG:32153</SRS>
+ <SRS>EPSG:32154</SRS>
+ <SRS>EPSG:32155</SRS>
+ <SRS>EPSG:32156</SRS>
+ <SRS>EPSG:32157</SRS>
+ <SRS>EPSG:32158</SRS>
+ <SRS>EPSG:32161</SRS>
+ <SRS>EPSG:32164</SRS>
+ <SRS>EPSG:32165</SRS>
+ <SRS>EPSG:32166</SRS>
+ <SRS>EPSG:32167</SRS>
+ <SRS>EPSG:32180</SRS>
+ <SRS>EPSG:32181</SRS>
+ <SRS>EPSG:32182</SRS>
+ <SRS>EPSG:32183</SRS>
+ <SRS>EPSG:32184</SRS>
+ <SRS>EPSG:32185</SRS>
+ <SRS>EPSG:32186</SRS>
+ <SRS>EPSG:32187</SRS>
+ <SRS>EPSG:32188</SRS>
+ <SRS>EPSG:32189</SRS>
+ <SRS>EPSG:32190</SRS>
+ <SRS>EPSG:32191</SRS>
+ <SRS>EPSG:32192</SRS>
+ <SRS>EPSG:32193</SRS>
+ <SRS>EPSG:32194</SRS>
+ <SRS>EPSG:32195</SRS>
+ <SRS>EPSG:32196</SRS>
+ <SRS>EPSG:32197</SRS>
+ <SRS>EPSG:32198</SRS>
+ <SRS>EPSG:32199</SRS>
+ <SRS>EPSG:32201</SRS>
+ <SRS>EPSG:32202</SRS>
+ <SRS>EPSG:32203</SRS>
+ <SRS>EPSG:32204</SRS>
+ <SRS>EPSG:32205</SRS>
+ <SRS>EPSG:32206</SRS>
+ <SRS>EPSG:32207</SRS>
+ <SRS>EPSG:32208</SRS>
+ <SRS>EPSG:32209</SRS>
+ <SRS>EPSG:32210</SRS>
+ <SRS>EPSG:32211</SRS>
+ <SRS>EPSG:32212</SRS>
+ <SRS>EPSG:32213</SRS>
+ <SRS>EPSG:32214</SRS>
+ <SRS>EPSG:32215</SRS>
+ <SRS>EPSG:32216</SRS>
+ <SRS>EPSG:32217</SRS>
+ <SRS>EPSG:32218</SRS>
+ <SRS>EPSG:32219</SRS>
+ <SRS>EPSG:32220</SRS>
+ <SRS>EPSG:32221</SRS>
+ <SRS>EPSG:32222</SRS>
+ <SRS>EPSG:32223</SRS>
+ <SRS>EPSG:32224</SRS>
+ <SRS>EPSG:32225</SRS>
+ <SRS>EPSG:32226</SRS>
+ <SRS>EPSG:32227</SRS>
+ <SRS>EPSG:32228</SRS>
+ <SRS>EPSG:32229</SRS>
+ <SRS>EPSG:32230</SRS>
+ <SRS>EPSG:32231</SRS>
+ <SRS>EPSG:32232</SRS>
+ <SRS>EPSG:32233</SRS>
+ <SRS>EPSG:32234</SRS>
+ <SRS>EPSG:32235</SRS>
+ <SRS>EPSG:32236</SRS>
+ <SRS>EPSG:32237</SRS>
+ <SRS>EPSG:32238</SRS>
+ <SRS>EPSG:32239</SRS>
+ <SRS>EPSG:32240</SRS>
+ <SRS>EPSG:32241</SRS>
+ <SRS>EPSG:32242</SRS>
+ <SRS>EPSG:32243</SRS>
+ <SRS>EPSG:32244</SRS>
+ <SRS>EPSG:32245</SRS>
+ <SRS>EPSG:32246</SRS>
+ <SRS>EPSG:32247</SRS>
+ <SRS>EPSG:32248</SRS>
+ <SRS>EPSG:32249</SRS>
+ <SRS>EPSG:32250</SRS>
+ <SRS>EPSG:32251</SRS>
+ <SRS>EPSG:32252</SRS>
+ <SRS>EPSG:32253</SRS>
+ <SRS>EPSG:32254</SRS>
+ <SRS>EPSG:32255</SRS>
+ <SRS>EPSG:32256</SRS>
+ <SRS>EPSG:32257</SRS>
+ <SRS>EPSG:32258</SRS>
+ <SRS>EPSG:32259</SRS>
+ <SRS>EPSG:32260</SRS>
+ <SRS>EPSG:32301</SRS>
+ <SRS>EPSG:32302</SRS>
+ <SRS>EPSG:32303</SRS>
+ <SRS>EPSG:32304</SRS>
+ <SRS>EPSG:32305</SRS>
+ <SRS>EPSG:32306</SRS>
+ <SRS>EPSG:32307</SRS>
+ <SRS>EPSG:32308</SRS>
+ <SRS>EPSG:32309</SRS>
+ <SRS>EPSG:32310</SRS>
+ <SRS>EPSG:32311</SRS>
+ <SRS>EPSG:32312</SRS>
+ <SRS>EPSG:32313</SRS>
+ <SRS>EPSG:32314</SRS>
+ <SRS>EPSG:32315</SRS>
+ <SRS>EPSG:32316</SRS>
+ <SRS>EPSG:32317</SRS>
+ <SRS>EPSG:32318</SRS>
+ <SRS>EPSG:32319</SRS>
+ <SRS>EPSG:32320</SRS>
+ <SRS>EPSG:32321</SRS>
+ <SRS>EPSG:32322</SRS>
+ <SRS>EPSG:32323</SRS>
+ <SRS>EPSG:32324</SRS>
+ <SRS>EPSG:32325</SRS>
+ <SRS>EPSG:32326</SRS>
+ <SRS>EPSG:32327</SRS>
+ <SRS>EPSG:32328</SRS>
+ <SRS>EPSG:32329</SRS>
+ <SRS>EPSG:32330</SRS>
+ <SRS>EPSG:32331</SRS>
+ <SRS>EPSG:32332</SRS>
+ <SRS>EPSG:32333</SRS>
+ <SRS>EPSG:32334</SRS>
+ <SRS>EPSG:32335</SRS>
+ <SRS>EPSG:32336</SRS>
+ <SRS>EPSG:32337</SRS>
+ <SRS>EPSG:32338</SRS>
+ <SRS>EPSG:32339</SRS>
+ <SRS>EPSG:32340</SRS>
+ <SRS>EPSG:32341</SRS>
+ <SRS>EPSG:32342</SRS>
+ <SRS>EPSG:32343</SRS>
+ <SRS>EPSG:32344</SRS>
+ <SRS>EPSG:32345</SRS>
+ <SRS>EPSG:32346</SRS>
+ <SRS>EPSG:32347</SRS>
+ <SRS>EPSG:32348</SRS>
+ <SRS>EPSG:32349</SRS>
+ <SRS>EPSG:32350</SRS>
+ <SRS>EPSG:32351</SRS>
+ <SRS>EPSG:32352</SRS>
+ <SRS>EPSG:32353</SRS>
+ <SRS>EPSG:32354</SRS>
+ <SRS>EPSG:32355</SRS>
+ <SRS>EPSG:32356</SRS>
+ <SRS>EPSG:32357</SRS>
+ <SRS>EPSG:32358</SRS>
+ <SRS>EPSG:32359</SRS>
+ <SRS>EPSG:32360</SRS>
+ <SRS>EPSG:32401</SRS>
+ <SRS>EPSG:32402</SRS>
+ <SRS>EPSG:32403</SRS>
+ <SRS>EPSG:32404</SRS>
+ <SRS>EPSG:32405</SRS>
+ <SRS>EPSG:32406</SRS>
+ <SRS>EPSG:32407</SRS>
+ <SRS>EPSG:32408</SRS>
+ <SRS>EPSG:32409</SRS>
+ <SRS>EPSG:32410</SRS>
+ <SRS>EPSG:32411</SRS>
+ <SRS>EPSG:32412</SRS>
+ <SRS>EPSG:32413</SRS>
+ <SRS>EPSG:32414</SRS>
+ <SRS>EPSG:32415</SRS>
+ <SRS>EPSG:32416</SRS>
+ <SRS>EPSG:32417</SRS>
+ <SRS>EPSG:32418</SRS>
+ <SRS>EPSG:32419</SRS>
+ <SRS>EPSG:32420</SRS>
+ <SRS>EPSG:32421</SRS>
+ <SRS>EPSG:32422</SRS>
+ <SRS>EPSG:32423</SRS>
+ <SRS>EPSG:32424</SRS>
+ <SRS>EPSG:32425</SRS>
+ <SRS>EPSG:32426</SRS>
+ <SRS>EPSG:32427</SRS>
+ <SRS>EPSG:32428</SRS>
+ <SRS>EPSG:32429</SRS>
+ <SRS>EPSG:32430</SRS>
+ <SRS>EPSG:32431</SRS>
+ <SRS>EPSG:32432</SRS>
+ <SRS>EPSG:32433</SRS>
+ <SRS>EPSG:32434</SRS>
+ <SRS>EPSG:32435</SRS>
+ <SRS>EPSG:32436</SRS>
+ <SRS>EPSG:32437</SRS>
+ <SRS>EPSG:32438</SRS>
+ <SRS>EPSG:32439</SRS>
+ <SRS>EPSG:32440</SRS>
+ <SRS>EPSG:32441</SRS>
+ <SRS>EPSG:32442</SRS>
+ <SRS>EPSG:32443</SRS>
+ <SRS>EPSG:32444</SRS>
+ <SRS>EPSG:32445</SRS>
+ <SRS>EPSG:32446</SRS>
+ <SRS>EPSG:32447</SRS>
+ <SRS>EPSG:32448</SRS>
+ <SRS>EPSG:32449</SRS>
+ <SRS>EPSG:32450</SRS>
+ <SRS>EPSG:32451</SRS>
+ <SRS>EPSG:32452</SRS>
+ <SRS>EPSG:32453</SRS>
+ <SRS>EPSG:32454</SRS>
+ <SRS>EPSG:32455</SRS>
+ <SRS>EPSG:32456</SRS>
+ <SRS>EPSG:32457</SRS>
+ <SRS>EPSG:32458</SRS>
+ <SRS>EPSG:32459</SRS>
+ <SRS>EPSG:32460</SRS>
+ <SRS>EPSG:32501</SRS>
+ <SRS>EPSG:32502</SRS>
+ <SRS>EPSG:32503</SRS>
+ <SRS>EPSG:32504</SRS>
+ <SRS>EPSG:32505</SRS>
+ <SRS>EPSG:32506</SRS>
+ <SRS>EPSG:32507</SRS>
+ <SRS>EPSG:32508</SRS>
+ <SRS>EPSG:32509</SRS>
+ <SRS>EPSG:32510</SRS>
+ <SRS>EPSG:32511</SRS>
+ <SRS>EPSG:32512</SRS>
+ <SRS>EPSG:32513</SRS>
+ <SRS>EPSG:32514</SRS>
+ <SRS>EPSG:32515</SRS>
+ <SRS>EPSG:32516</SRS>
+ <SRS>EPSG:32517</SRS>
+ <SRS>EPSG:32518</SRS>
+ <SRS>EPSG:32519</SRS>
+ <SRS>EPSG:32520</SRS>
+ <SRS>EPSG:32521</SRS>
+ <SRS>EPSG:32522</SRS>
+ <SRS>EPSG:32523</SRS>
+ <SRS>EPSG:32524</SRS>
+ <SRS>EPSG:32525</SRS>
+ <SRS>EPSG:32526</SRS>
+ <SRS>EPSG:32527</SRS>
+ <SRS>EPSG:32528</SRS>
+ <SRS>EPSG:32529</SRS>
+ <SRS>EPSG:32530</SRS>
+ <SRS>EPSG:32531</SRS>
+ <SRS>EPSG:32532</SRS>
+ <SRS>EPSG:32533</SRS>
+ <SRS>EPSG:32534</SRS>
+ <SRS>EPSG:32535</SRS>
+ <SRS>EPSG:32536</SRS>
+ <SRS>EPSG:32537</SRS>
+ <SRS>EPSG:32538</SRS>
+ <SRS>EPSG:32539</SRS>
+ <SRS>EPSG:32540</SRS>
+ <SRS>EPSG:32541</SRS>
+ <SRS>EPSG:32542</SRS>
+ <SRS>EPSG:32543</SRS>
+ <SRS>EPSG:32544</SRS>
+ <SRS>EPSG:32545</SRS>
+ <SRS>EPSG:32546</SRS>
+ <SRS>EPSG:32547</SRS>
+ <SRS>EPSG:32548</SRS>
+ <SRS>EPSG:32549</SRS>
+ <SRS>EPSG:32550</SRS>
+ <SRS>EPSG:32551</SRS>
+ <SRS>EPSG:32552</SRS>
+ <SRS>EPSG:32553</SRS>
+ <SRS>EPSG:32554</SRS>
+ <SRS>EPSG:32555</SRS>
+ <SRS>EPSG:32556</SRS>
+ <SRS>EPSG:32557</SRS>
+ <SRS>EPSG:32558</SRS>
+ <SRS>EPSG:32559</SRS>
+ <SRS>EPSG:32560</SRS>
+ <SRS>EPSG:32600</SRS>
+ <SRS>EPSG:32601</SRS>
+ <SRS>EPSG:32602</SRS>
+ <SRS>EPSG:32603</SRS>
+ <SRS>EPSG:32604</SRS>
+ <SRS>EPSG:32605</SRS>
+ <SRS>EPSG:32606</SRS>
+ <SRS>EPSG:32607</SRS>
+ <SRS>EPSG:32608</SRS>
+ <SRS>EPSG:32609</SRS>
+ <SRS>EPSG:32610</SRS>
+ <SRS>EPSG:32611</SRS>
+ <SRS>EPSG:32612</SRS>
+ <SRS>EPSG:32613</SRS>
+ <SRS>EPSG:32614</SRS>
+ <SRS>EPSG:32615</SRS>
+ <SRS>EPSG:32616</SRS>
+ <SRS>EPSG:32617</SRS>
+ <SRS>EPSG:32618</SRS>
+ <SRS>EPSG:32619</SRS>
+ <SRS>EPSG:32620</SRS>
+ <SRS>EPSG:32621</SRS>
+ <SRS>EPSG:32622</SRS>
+ <SRS>EPSG:32623</SRS>
+ <SRS>EPSG:32624</SRS>
+ <SRS>EPSG:32625</SRS>
+ <SRS>EPSG:32626</SRS>
+ <SRS>EPSG:32627</SRS>
+ <SRS>EPSG:32628</SRS>
+ <SRS>EPSG:32629</SRS>
+ <SRS>EPSG:32630</SRS>
+ <SRS>EPSG:32631</SRS>
+ <SRS>EPSG:32632</SRS>
+ <SRS>EPSG:32633</SRS>
+ <SRS>EPSG:32634</SRS>
+ <SRS>EPSG:32635</SRS>
+ <SRS>EPSG:32636</SRS>
+ <SRS>EPSG:32637</SRS>
+ <SRS>EPSG:32638</SRS>
+ <SRS>EPSG:32639</SRS>
+ <SRS>EPSG:32640</SRS>
+ <SRS>EPSG:32641</SRS>
+ <SRS>EPSG:32642</SRS>
+ <SRS>EPSG:32643</SRS>
+ <SRS>EPSG:32644</SRS>
+ <SRS>EPSG:32645</SRS>
+ <SRS>EPSG:32646</SRS>
+ <SRS>EPSG:32647</SRS>
+ <SRS>EPSG:32648</SRS>
+ <SRS>EPSG:32649</SRS>
+ <SRS>EPSG:32650</SRS>
+ <SRS>EPSG:32651</SRS>
+ <SRS>EPSG:32652</SRS>
+ <SRS>EPSG:32653</SRS>
+ <SRS>EPSG:32654</SRS>
+ <SRS>EPSG:32655</SRS>
+ <SRS>EPSG:32656</SRS>
+ <SRS>EPSG:32657</SRS>
+ <SRS>EPSG:32658</SRS>
+ <SRS>EPSG:32659</SRS>
+ <SRS>EPSG:32660</SRS>
+ <SRS>EPSG:32661</SRS>
+ <SRS>EPSG:32662</SRS>
+ <SRS>EPSG:32664</SRS>
+ <SRS>EPSG:32665</SRS>
+ <SRS>EPSG:32666</SRS>
+ <SRS>EPSG:32667</SRS>
+ <SRS>EPSG:32700</SRS>
+ <SRS>EPSG:32701</SRS>
+ <SRS>EPSG:32702</SRS>
+ <SRS>EPSG:32703</SRS>
+ <SRS>EPSG:32704</SRS>
+ <SRS>EPSG:32705</SRS>
+ <SRS>EPSG:32706</SRS>
+ <SRS>EPSG:32707</SRS>
+ <SRS>EPSG:32708</SRS>
+ <SRS>EPSG:32709</SRS>
+ <SRS>EPSG:32710</SRS>
+ <SRS>EPSG:32711</SRS>
+ <SRS>EPSG:32712</SRS>
+ <SRS>EPSG:32713</SRS>
+ <SRS>EPSG:32714</SRS>
+ <SRS>EPSG:32715</SRS>
+ <SRS>EPSG:32716</SRS>
+ <SRS>EPSG:32717</SRS>
+ <SRS>EPSG:32718</SRS>
+ <SRS>EPSG:32719</SRS>
+ <SRS>EPSG:32720</SRS>
+ <SRS>EPSG:32721</SRS>
+ <SRS>EPSG:32722</SRS>
+ <SRS>EPSG:32723</SRS>
+ <SRS>EPSG:32724</SRS>
+ <SRS>EPSG:32725</SRS>
+ <SRS>EPSG:32726</SRS>
+ <SRS>EPSG:32727</SRS>
+ <SRS>EPSG:32728</SRS>
+ <SRS>EPSG:32729</SRS>
+ <SRS>EPSG:32730</SRS>
+ <SRS>EPSG:32731</SRS>
+ <SRS>EPSG:32732</SRS>
+ <SRS>EPSG:32733</SRS>
+ <SRS>EPSG:32734</SRS>
+ <SRS>EPSG:32735</SRS>
+ <SRS>EPSG:32736</SRS>
+ <SRS>EPSG:32737</SRS>
+ <SRS>EPSG:32738</SRS>
+ <SRS>EPSG:32739</SRS>
+ <SRS>EPSG:32740</SRS>
+ <SRS>EPSG:32741</SRS>
+ <SRS>EPSG:32742</SRS>
+ <SRS>EPSG:32743</SRS>
+ <SRS>EPSG:32744</SRS>
+ <SRS>EPSG:32745</SRS>
+ <SRS>EPSG:32746</SRS>
+ <SRS>EPSG:32747</SRS>
+ <SRS>EPSG:32748</SRS>
+ <SRS>EPSG:32749</SRS>
+ <SRS>EPSG:32750</SRS>
+ <SRS>EPSG:32751</SRS>
+ <SRS>EPSG:32752</SRS>
+ <SRS>EPSG:32753</SRS>
+ <SRS>EPSG:32754</SRS>
+ <SRS>EPSG:32755</SRS>
+ <SRS>EPSG:32756</SRS>
+ <SRS>EPSG:32757</SRS>
+ <SRS>EPSG:32758</SRS>
+ <SRS>EPSG:32759</SRS>
+ <SRS>EPSG:32760</SRS>
+ <SRS>EPSG:32761</SRS>
+ <SRS>EPSG:32766</SRS>
+ <SRS>EPSG:61206405</SRS>
+ <SRS>EPSG:61216405</SRS>
+ <SRS>EPSG:61226405</SRS>
+ <SRS>EPSG:61236405</SRS>
+ <SRS>EPSG:61246405</SRS>
+ <SRS>EPSG:61266405</SRS>
+ <SRS>EPSG:61266413</SRS>
+ <SRS>EPSG:61276405</SRS>
+ <SRS>EPSG:61286405</SRS>
+ <SRS>EPSG:61296405</SRS>
+ <SRS>EPSG:61306405</SRS>
+ <SRS>EPSG:61306413</SRS>
+ <SRS>EPSG:61316405</SRS>
+ <SRS>EPSG:61326405</SRS>
+ <SRS>EPSG:61336405</SRS>
+ <SRS>EPSG:61346405</SRS>
+ <SRS>EPSG:61356405</SRS>
+ <SRS>EPSG:61366405</SRS>
+ <SRS>EPSG:61376405</SRS>
+ <SRS>EPSG:61386405</SRS>
+ <SRS>EPSG:61396405</SRS>
+ <SRS>EPSG:61406405</SRS>
+ <SRS>EPSG:61406413</SRS>
+ <SRS>EPSG:61416405</SRS>
+ <SRS>EPSG:61426405</SRS>
+ <SRS>EPSG:61436405</SRS>
+ <SRS>EPSG:61446405</SRS>
+ <SRS>EPSG:61456405</SRS>
+ <SRS>EPSG:61466405</SRS>
+ <SRS>EPSG:61476405</SRS>
+ <SRS>EPSG:61486405</SRS>
+ <SRS>EPSG:61486413</SRS>
+ <SRS>EPSG:61496405</SRS>
+ <SRS>EPSG:61506405</SRS>
+ <SRS>EPSG:61516405</SRS>
+ <SRS>EPSG:61516413</SRS>
+ <SRS>EPSG:61526405</SRS>
+ <SRS>EPSG:61526413</SRS>
+ <SRS>EPSG:61536405</SRS>
+ <SRS>EPSG:61546405</SRS>
+ <SRS>EPSG:61556405</SRS>
+ <SRS>EPSG:61566405</SRS>
+ <SRS>EPSG:61576405</SRS>
+ <SRS>EPSG:61586405</SRS>
+ <SRS>EPSG:61596405</SRS>
+ <SRS>EPSG:61606405</SRS>
+ <SRS>EPSG:61616405</SRS>
+ <SRS>EPSG:61626405</SRS>
+ <SRS>EPSG:61636405</SRS>
+ <SRS>EPSG:61636413</SRS>
+ <SRS>EPSG:61646405</SRS>
+ <SRS>EPSG:61656405</SRS>
+ <SRS>EPSG:61666405</SRS>
+ <SRS>EPSG:61676405</SRS>
+ <SRS>EPSG:61676413</SRS>
+ <SRS>EPSG:61686405</SRS>
+ <SRS>EPSG:61696405</SRS>
+ <SRS>EPSG:61706405</SRS>
+ <SRS>EPSG:61706413</SRS>
+ <SRS>EPSG:61716405</SRS>
+ <SRS>EPSG:61716413</SRS>
+ <SRS>EPSG:61736405</SRS>
+ <SRS>EPSG:61736413</SRS>
+ <SRS>EPSG:61746405</SRS>
+ <SRS>EPSG:61756405</SRS>
+ <SRS>EPSG:61766405</SRS>
+ <SRS>EPSG:61766413</SRS>
+ <SRS>EPSG:61786405</SRS>
+ <SRS>EPSG:61796405</SRS>
+ <SRS>EPSG:61806405</SRS>
+ <SRS>EPSG:61806413</SRS>
+ <SRS>EPSG:61816405</SRS>
+ <SRS>EPSG:61826405</SRS>
+ <SRS>EPSG:61836405</SRS>
+ <SRS>EPSG:61846405</SRS>
+ <SRS>EPSG:61886405</SRS>
+ <SRS>EPSG:61896405</SRS>
+ <SRS>EPSG:61896413</SRS>
+ <SRS>EPSG:61906405</SRS>
+ <SRS>EPSG:61906413</SRS>
+ <SRS>EPSG:61916405</SRS>
+ <SRS>EPSG:61926405</SRS>
+ <SRS>EPSG:61936405</SRS>
+ <SRS>EPSG:61946405</SRS>
+ <SRS>EPSG:61956405</SRS>
+ <SRS>EPSG:61966405</SRS>
+ <SRS>EPSG:61976405</SRS>
+ <SRS>EPSG:61986405</SRS>
+ <SRS>EPSG:61996405</SRS>
+ <SRS>EPSG:62006405</SRS>
+ <SRS>EPSG:62016405</SRS>
+ <SRS>EPSG:62026405</SRS>
+ <SRS>EPSG:62036405</SRS>
+ <SRS>EPSG:62046405</SRS>
+ <SRS>EPSG:62056405</SRS>
+ <SRS>EPSG:62066405</SRS>
+ <SRS>EPSG:62076405</SRS>
+ <SRS>EPSG:62086405</SRS>
+ <SRS>EPSG:62096405</SRS>
+ <SRS>EPSG:62106405</SRS>
+ <SRS>EPSG:62116405</SRS>
+ <SRS>EPSG:62126405</SRS>
+ <SRS>EPSG:62136405</SRS>
+ <SRS>EPSG:62146405</SRS>
+ <SRS>EPSG:62156405</SRS>
+ <SRS>EPSG:62166405</SRS>
+ <SRS>EPSG:62186405</SRS>
+ <SRS>EPSG:62196405</SRS>
+ <SRS>EPSG:62206405</SRS>
+ <SRS>EPSG:62216405</SRS>
+ <SRS>EPSG:62226405</SRS>
+ <SRS>EPSG:62236405</SRS>
+ <SRS>EPSG:62246405</SRS>
+ <SRS>EPSG:62256405</SRS>
+ <SRS>EPSG:62276405</SRS>
+ <SRS>EPSG:62296405</SRS>
+ <SRS>EPSG:62306405</SRS>
+ <SRS>EPSG:62316405</SRS>
+ <SRS>EPSG:62326405</SRS>
+ <SRS>EPSG:62336405</SRS>
+ <SRS>EPSG:62366405</SRS>
+ <SRS>EPSG:62376405</SRS>
+ <SRS>EPSG:62386405</SRS>
+ <SRS>EPSG:62396405</SRS>
+ <SRS>EPSG:62406405</SRS>
+ <SRS>EPSG:62416405</SRS>
+ <SRS>EPSG:62426405</SRS>
+ <SRS>EPSG:62436405</SRS>
+ <SRS>EPSG:62446405</SRS>
+ <SRS>EPSG:62456405</SRS>
+ <SRS>EPSG:62466405</SRS>
+ <SRS>EPSG:62476405</SRS>
+ <SRS>EPSG:62486405</SRS>
+ <SRS>EPSG:62496405</SRS>
+ <SRS>EPSG:62506405</SRS>
+ <SRS>EPSG:62516405</SRS>
+ <SRS>EPSG:62526405</SRS>
+ <SRS>EPSG:62536405</SRS>
+ <SRS>EPSG:62546405</SRS>
+ <SRS>EPSG:62556405</SRS>
+ <SRS>EPSG:62566405</SRS>
+ <SRS>EPSG:62576405</SRS>
+ <SRS>EPSG:62586405</SRS>
+ <SRS>EPSG:62586413</SRS>
+ <SRS>EPSG:62596405</SRS>
+ <SRS>EPSG:62616405</SRS>
+ <SRS>EPSG:62626405</SRS>
+ <SRS>EPSG:62636405</SRS>
+ <SRS>EPSG:62646405</SRS>
+ <SRS>EPSG:62656405</SRS>
+ <SRS>EPSG:62666405</SRS>
+ <SRS>EPSG:62676405</SRS>
+ <SRS>EPSG:62686405</SRS>
+ <SRS>EPSG:62696405</SRS>
+ <SRS>EPSG:62706405</SRS>
+ <SRS>EPSG:62716405</SRS>
+ <SRS>EPSG:62726405</SRS>
+ <SRS>EPSG:62736405</SRS>
+ <SRS>EPSG:62746405</SRS>
+ <SRS>EPSG:62756405</SRS>
+ <SRS>EPSG:62766405</SRS>
+ <SRS>EPSG:62776405</SRS>
+ <SRS>EPSG:62786405</SRS>
+ <SRS>EPSG:62796405</SRS>
+ <SRS>EPSG:62806405</SRS>
+ <SRS>EPSG:62816405</SRS>
+ <SRS>EPSG:62826405</SRS>
+ <SRS>EPSG:62836405</SRS>
+ <SRS>EPSG:62836413</SRS>
+ <SRS>EPSG:62846405</SRS>
+ <SRS>EPSG:62856405</SRS>
+ <SRS>EPSG:62866405</SRS>
+ <SRS>EPSG:62886405</SRS>
+ <SRS>EPSG:62896405</SRS>
+ <SRS>EPSG:62926405</SRS>
+ <SRS>EPSG:62936405</SRS>
+ <SRS>EPSG:62956405</SRS>
+ <SRS>EPSG:62976405</SRS>
+ <SRS>EPSG:62986405</SRS>
+ <SRS>EPSG:62996405</SRS>
+ <SRS>EPSG:63006405</SRS>
+ <SRS>EPSG:63016405</SRS>
+ <SRS>EPSG:63026405</SRS>
+ <SRS>EPSG:63036405</SRS>
+ <SRS>EPSG:63046405</SRS>
+ <SRS>EPSG:63066405</SRS>
+ <SRS>EPSG:63076405</SRS>
+ <SRS>EPSG:63086405</SRS>
+ <SRS>EPSG:63096405</SRS>
+ <SRS>EPSG:63106405</SRS>
+ <SRS>EPSG:63116405</SRS>
+ <SRS>EPSG:63126405</SRS>
+ <SRS>EPSG:63136405</SRS>
+ <SRS>EPSG:63146405</SRS>
+ <SRS>EPSG:63156405</SRS>
+ <SRS>EPSG:63166405</SRS>
+ <SRS>EPSG:63176405</SRS>
+ <SRS>EPSG:63186405</SRS>
+ <SRS>EPSG:63196405</SRS>
+ <SRS>EPSG:63226405</SRS>
+ <SRS>EPSG:63246405</SRS>
+ <SRS>EPSG:63266405</SRS>
+ <SRS>EPSG:63266406</SRS>
+ <SRS>EPSG:63266407</SRS>
+ <SRS>EPSG:63266408</SRS>
+ <SRS>EPSG:63266409</SRS>
+ <SRS>EPSG:63266410</SRS>
+ <SRS>EPSG:63266411</SRS>
+ <SRS>EPSG:63266412</SRS>
+ <SRS>EPSG:63266413</SRS>
+ <SRS>EPSG:63266414</SRS>
+ <SRS>EPSG:63266415</SRS>
+ <SRS>EPSG:63266416</SRS>
+ <SRS>EPSG:63266417</SRS>
+ <SRS>EPSG:63266418</SRS>
+ <SRS>EPSG:63266419</SRS>
+ <SRS>EPSG:63266420</SRS>
+ <SRS>EPSG:66006405</SRS>
+ <SRS>EPSG:66016405</SRS>
+ <SRS>EPSG:66026405</SRS>
+ <SRS>EPSG:66036405</SRS>
+ <SRS>EPSG:66046405</SRS>
+ <SRS>EPSG:66056405</SRS>
+ <SRS>EPSG:66066405</SRS>
+ <SRS>EPSG:66076405</SRS>
+ <SRS>EPSG:66086405</SRS>
+ <SRS>EPSG:66096405</SRS>
+ <SRS>EPSG:66106405</SRS>
+ <SRS>EPSG:66116405</SRS>
+ <SRS>EPSG:66126405</SRS>
+ <SRS>EPSG:66126413</SRS>
+ <SRS>EPSG:66136405</SRS>
+ <SRS>EPSG:66146405</SRS>
+ <SRS>EPSG:66156405</SRS>
+ <SRS>EPSG:66166405</SRS>
+ <SRS>EPSG:66186405</SRS>
+ <SRS>EPSG:66196405</SRS>
+ <SRS>EPSG:66196413</SRS>
+ <SRS>EPSG:66206405</SRS>
+ <SRS>EPSG:66216405</SRS>
+ <SRS>EPSG:66226405</SRS>
+ <SRS>EPSG:66236405</SRS>
+ <SRS>EPSG:66246405</SRS>
+ <SRS>EPSG:66246413</SRS>
+ <SRS>EPSG:66256405</SRS>
+ <SRS>EPSG:66266405</SRS>
+ <SRS>EPSG:66276405</SRS>
+ <SRS>EPSG:66276413</SRS>
+ <SRS>EPSG:66286405</SRS>
+ <SRS>EPSG:66296405</SRS>
+ <SRS>EPSG:66306405</SRS>
+ <SRS>EPSG:66316405</SRS>
+ <SRS>EPSG:66326405</SRS>
+ <SRS>EPSG:66336405</SRS>
+ <SRS>EPSG:66346405</SRS>
+ <SRS>EPSG:66356405</SRS>
+ <SRS>EPSG:66366405</SRS>
+ <SRS>EPSG:66376405</SRS>
+ <SRS>EPSG:66386405</SRS>
+ <SRS>EPSG:66396405</SRS>
+ <SRS>EPSG:66406405</SRS>
+ <SRS>EPSG:66406413</SRS>
+ <SRS>EPSG:66416405</SRS>
+ <SRS>EPSG:66426405</SRS>
+ <SRS>EPSG:66436405</SRS>
+ <SRS>EPSG:66446405</SRS>
+ <SRS>EPSG:66456405</SRS>
+ <SRS>EPSG:66456413</SRS>
+ <SRS>EPSG:66466405</SRS>
+ <SRS>EPSG:66576405</SRS>
+ <SRS>EPSG:66586405</SRS>
+ <SRS>EPSG:66596405</SRS>
+ <SRS>EPSG:66596413</SRS>
+ <SRS>EPSG:66606405</SRS>
+ <SRS>EPSG:66616405</SRS>
+ <SRS>EPSG:66616413</SRS>
+ <SRS>EPSG:66636405</SRS>
+ <SRS>EPSG:66646405</SRS>
+ <SRS>EPSG:66656405</SRS>
+ <SRS>EPSG:66666405</SRS>
+ <SRS>EPSG:66676405</SRS>
+ <SRS>EPSG:68016405</SRS>
+ <SRS>EPSG:68026405</SRS>
+ <SRS>EPSG:68036405</SRS>
+ <SRS>EPSG:68046405</SRS>
+ <SRS>EPSG:68056405</SRS>
+ <SRS>EPSG:68066405</SRS>
+ <SRS>EPSG:68086405</SRS>
+ <SRS>EPSG:68096405</SRS>
+ <SRS>EPSG:68136405</SRS>
+ <SRS>EPSG:68146405</SRS>
+ <SRS>EPSG:68156405</SRS>
+ <SRS>EPSG:68186405</SRS>
+ <SRS>EPSG:68206405</SRS>
+ <SRS>EPSG:69036405</SRS>
+ <SRS>EPSG:42302</SRS>
+ <SRS>EPSG:42301</SRS>
+ <SRS>EPSG:900913</SRS>
+ <SRS>EPSG:45556</SRS>
+ <SRS>EPSG:45555</SRS>
+ <SRS>EPSG:54004</SRS>
+ <SRS>EPSG:41001</SRS>
+ <SRS>EPSG:42311</SRS>
+ <SRS>EPSG:42310</SRS>
+ <SRS>EPSG:18001</SRS>
+ <SRS>EPSG:100003</SRS>
+ <SRS>EPSG:42106</SRS>
+ <SRS>EPSG:100002</SRS>
+ <SRS>EPSG:42105</SRS>
+ <SRS>EPSG:100001</SRS>
+ <SRS>EPSG:42309</SRS>
+ <SRS>EPSG:42104</SRS>
+ <SRS>EPSG:42308</SRS>
+ <SRS>EPSG:42103</SRS>
+ <SRS>EPSG:42307</SRS>
+ <SRS>EPSG:42102</SRS>
+ <SRS>EPSG:42306</SRS>
+ <SRS>EPSG:42101</SRS>
+ <SRS>EPSG:42305</SRS>
+ <SRS>EPSG:42304</SRS>
+ <SRS>EPSG:42303</SRS>
+ <LatLonBoundingBox minx="-257.0843245637291" miny="-257.0843245637291" maxx="257.0843245637291" maxy="257.0843245637291"/>
+ <Layer queryable="1">
+ <Name>tiger:poly_landmarks</Name>
+ <Title>Manhattan (NY) landmarks</Title>
+ <Abstract>Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs</Abstract>
+ <KeywordList>
+ <Keyword>DS_poly_landmarks</Keyword>
+ <Keyword>poly_landmarks</Keyword>
+ <Keyword>landmarks</Keyword>
+ <Keyword>manhattan</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-74.1008830202198" miny="40.65748247978021" maxx="-73.8541219797802" maxy="40.90424352021979"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.047185" miny="40.679648" maxx="-73.90782" maxy="40.882078"/>
+ <Style>
+ <Name>poly_landmarks</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poly_landmarks"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tiger:poi</Name>
+ <Title>Manhattan (NY) points of interest</Title>
+ <Abstract>Points of interest in New York, New York (on Manhattan). One of the attributes contains the name of a file with a picture of the point of interest.</Abstract>
+ <KeywordList>
+ <Keyword>poi</Keyword>
+ <Keyword>DS_poi</Keyword>
+ <Keyword>points_of_interest</Keyword>
+ <Keyword>Manhattan</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-74.01288357289539" miny="40.70706518152972" maxx="-74.00752144792617" maxy="40.71242730649893"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.0118315772888" miny="40.70754683896324" maxx="-74.00153046439813" maxy="40.719885123828675"/>
+ <Style>
+ <Name>poi</Name>
+ <Title>Points of interest</Title>
+ <Abstract>Manhattan points of interest</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poi"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tiger:tiger_roads</Name>
+ <Title>Manhattan (NY) roads</Title>
+ <Abstract>Highly simplified road layout of Manhattan in New York..</Abstract>
+ <KeywordList>
+ <Keyword>DS_tiger_roads</Keyword>
+ <Keyword>tiger_roads</Keyword>
+ <Keyword>roads</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-74.08769307536667" miny="40.660618924633326" maxx="-73.84653192463333" maxy="40.90178007536667"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.02722" miny="40.684221" maxx="-73.907005" maxy="40.878178"/>
+ <Style>
+ <Name>tiger_roads</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:tiger_roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:archsites</Name>
+ <Title>Spearfish archeological sites</Title>
+ <Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>archsites</Keyword>
+ <Keyword>sfArchsites</Keyword>
+ <Keyword>spearfish</Keyword>
+ <Keyword>archeology</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.89000625326194" miny="44.29796961116877" maxx="-103.62049935931161" maxy="44.5674765051191"/>
+ <BoundingBox SRS="EPSG:26713" minx="588926.6865343997" miny="4913890.332215005" maxx="609271.2114429093" maxy="4927102.448786693"/>
+ <Style>
+ <Name>point</Name>
+ <Title>Default point</Title>
+ <Abstract>A sample style that just prints out a 6px wide red square</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:archsites"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:bugsites</Name>
+ <Title>Spearfish bug locations</Title>
+ <Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>sfBugsites</Keyword>
+ <Keyword>bugsites</Keyword>
+ <Keyword>insects</Keyword>
+ <Keyword>spearfish</Keyword>
+ <Keyword>tiger_beetles</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.89041901614995" miny="44.266492773791775" maxx="-103.61527753322848" maxy="44.54163425671326"/>
+ <BoundingBox SRS="EPSG:26713" minx="589311.4871629482" miny="4913787.082099182" maxx="609374.4115724327" maxy="4920844.691225147"/>
+ <Style>
+ <Name>capitals</Name>
+ <Title>Capital cities</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:bugsites"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:restricted</Name>
+ <Title>Spearfish restricted areas</Title>
+ <Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>restricted</Keyword>
+ <Keyword>sfRestricted</Keyword>
+ <Keyword>spearfish</Keyword>
+ <Keyword>areas</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.86063428986338" miny="44.37661974734028" maxx="-103.73735238788223" maxy="44.49990164932145"/>
+ <BoundingBox SRS="EPSG:26713" minx="591175.6988413236" miny="4915754.888027622" maxx="600052.4121365736" maxy="4926353.920417598"/>
+ <Style>
+ <Name>restricted</Name>
+ <Title>Red, translucent style</Title>
+ <Abstract>A sample style that just prints out a transparent red interior with a red outline</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:restricted"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:roads</Name>
+ <Title>Spearfish roads</Title>
+ <Abstract>Sample data from GRASS, road layout, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>sfRoads</Keyword>
+ <Keyword>roads</Keyword>
+ <Keyword>spearfish</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.90534996703491" miny="44.2800314829381" maxx="-103.5943809967035" maxy="44.5910004532695"/>
+ <BoundingBox SRS="EPSG:26713" minx="588430.2387813567" miny="4913303.484828213" maxx="610531.8279023392" maxy="4928766.251023613"/>
+ <Style>
+ <Name>simple_roads</Name>
+ <Title>Default Styler for simple road segments</Title>
+ <Abstract>Light red line, 2px wide</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:streams</Name>
+ <Title>Spearfish streams</Title>
+ <Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>sfStreams</Keyword>
+ <Keyword>streams</Keyword>
+ <Keyword>spearfish</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.9089219204826" miny="44.278738996398694" maxx="-103.59184616696963" maxy="44.595814749911675"/>
+ <BoundingBox SRS="EPSG:26713" minx="588430.3113926318" miny="4913241.156915463" maxx="610522.3974737043" maxy="4928777.235349244"/>
+ <Style>
+ <Name>simple_streams</Name>
+ <Title>Default Styler for streams segments</Title>
+ <Abstract>Blue lines, 2px wide</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:streams"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_cities</Name>
+ <Title>Tasmania cities</Title>
+ <Abstract>Cities in Tasmania (actually, just the capital)</Abstract>
+ <KeywordList>
+ <Keyword>cities</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="144.93357593664516" miny="-43.93984106335484" maxx="148.53694406335487" maxy="-40.33647293664516"/>
+ <BoundingBox SRS="EPSG:4326" minx="147.2910004483" miny="-42.851001816890005" maxx="147.2910004483" maxy="-42.851001816890005"/>
+ <Style>
+ <Name>capitals</Name>
+ <Title>Capital cities</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_cities"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_roads</Name>
+ <Title>Tasmania roads</Title>
+ <Abstract>Main Tasmania roads</Abstract>
+ <KeywordList>
+ <Keyword>Roads</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="144.8607879004856" miny="-44.01262909951439" maxx="148.60973209951442" maxy="-40.26368490048561"/>
+ <BoundingBox SRS="EPSG:4326" minx="145.19754" miny="-43.423512" maxx="148.27298000000002" maxy="-40.852802"/>
+ <Style>
+ <Name>simple_roads</Name>
+ <Title>Default Styler for simple road segments</Title>
+ <Abstract>Light red line, 2px wide</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_state_boundaries</Name>
+ <Title>Tasmania state boundaries</Title>
+ <Abstract>Tasmania state boundaries</Abstract>
+ <KeywordList>
+ <Keyword>tasmania_state_boundaries</Keyword>
+ <Keyword>Tasmania</Keyword>
+ <Keyword>boundaries</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="142.70637712387594" miny="-45.06157887612408" maxx="149.60758787612411" maxy="-38.16036812387592"/>
+ <BoundingBox SRS="EPSG:4326" minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>
+ <Style>
+ <Name>green</Name>
+ <Title>Green polygon</Title>
+ <Abstract>Green fill with black outline</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_state_boundaries"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_water_bodies</Name>
+ <Title>Tasmania water bodies</Title>
+ <Abstract>Tasmania water bodies</Abstract>
+ <KeywordList>
+ <Keyword>Lakes</Keyword>
+ <Keyword>Bodies</Keyword>
+ <Keyword>Australia</Keyword>
+ <Keyword>Water</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="145.82989373832018" miny="-43.16951476167979" maxx="147.3614212616798" maxy="-41.63798723832021"/>
+ <BoundingBox SRS="EPSG:4326" minx="145.97161899999998" miny="-43.031944" maxx="147.219696" maxy="-41.775558"/>
+ <Style>
+ <Name>cite_lakes</Name>
+ <Title>Blue lake</Title>
+ <Abstract>A blue fill, solid black outline style</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_water_bodies"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:states</Name>
+ <Title>USA Population</Title>
+ <Abstract>This is some census data on the states.</Abstract>
+ <KeywordList>
+ <Keyword>census</Keyword>
+ <Keyword>united</Keyword>
+ <Keyword>boundaries</Keyword>
+ <Keyword>state</Keyword>
+ <Keyword>states</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-131.05615308855994" miny="1.958333411440066" maxx="-60.645117911440046" maxy="72.36936858855995"/>
+ <BoundingBox SRS="EPSG:4326" minx="-124.73142200000001" miny="24.955967" maxx="-66.969849" maxy="49.371735"/>
+ <Style>
+ <Name>population</Name>
+ <Title>Population in the United States</Title>
+ <Abstract>A sample filter that filters the United States into three
+ categories of population, drawn in different colors</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:states"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tiger:giant_polygon</Name>
+ <Title>World rectangle</Title>
+ <Abstract>A simple rectangular polygon covering most of the world, it\'s only used for the purpose of providing a background (WMS bgcolor could be used instead)</Abstract>
+ <KeywordList>
+ <Keyword>DS_giant_polygon</Keyword>
+ <Keyword>giant_polygon</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-257.0843245637291" miny="-257.0843245637291" maxx="257.0843245637291" maxy="257.0843245637291"/>
+ <BoundingBox SRS="EPSG:4326" minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+ <Style>
+ <Name>giant_polygon</Name>
+ <Title>Border-less gray fill</Title>
+ <Abstract>Light gray polygon fill without a border</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:giant_polygon"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>nurc:Arc_Sample</Name>
+ <Title>Global annual rainfall</Title>
+ <Abstract>Global annual rainfall in ArcGrid format</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>arcGridSample</Keyword>
+ <Keyword>arcGridSample_Coverage</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+ <BoundingBox SRS="EPSG:4326" minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Arc_Sample"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>nurc:Img_Sample</Name>
+ <Title>North America sample imagery</Title>
+ <Abstract>A very rough imagery of North America</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>worldImageSample</Keyword>
+ <Keyword>worldImageSample_Coverage</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-130.85168" miny="20.7052" maxx="-62.0054" maxy="54.1141"/>
+ <BoundingBox SRS="EPSG:4326" minx="-130.85168" miny="20.7052" maxx="-62.0054" maxy="54.1141"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Img_Sample"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>nurc:mosaic</Name>
+ <Title>Sample PNG mosaic</Title>
+ <Abstract>Subsampled satellite imagery loaded as a mosaic of PNG images</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>mosaic</Keyword>
+ <Keyword>mosaic</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="6.34617490847439" miny="36.4917718219401" maxx="20.8296831527815" maxy="46.5907669751351"/>
+ <BoundingBox SRS="EPSG:4326" minx="6.34617490847439" miny="36.4917718219401" maxx="20.8296831527815" maxy="46.5907669751351"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:mosaic"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>nurc:Pk50095</Name>
+ <Title>Sample scanned and georerenced map</Title>
+ <Abstract>This is a sample for the world image format (wld + prj + tiff)</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>img_sample2</Keyword>
+ <Keyword>Pk50095</Keyword>
+ </KeywordList>
+ <SRS>EPSG:32633</SRS>
+ <LatLonBoundingBox minx="12.999446822650462" miny="46.722110379286" maxx="13.308182612644663" maxy="46.91359611878293"/>
+ <BoundingBox SRS="EPSG:32633" minx="347649.93086859107" miny="5176214.082539256" maxx="370725.976428591" maxy="5196961.352859256"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Pk50095"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:sfdem</Name>
+ <Title>sfdem is a Tagged Image File Format with Geographic information</Title>
+ <Abstract>Generated from sfdem</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>sfdem</Keyword>
+ <Keyword>sfdem</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.87108701853181" miny="44.370187074132616" maxx="-103.62940739432703" maxy="44.5016011535299"/>
+ <BoundingBox SRS="EPSG:26713" minx="589980.0" miny="4913700.0" maxx="609000.0" maxy="4928010.0"/>
+ <Style>
+ <Name>dem</Name>
+ <Title>Simple DEM style</Title>
+ <Abstract>Classic elevation color progression</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://publicus.opengeo.org:80/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:sfdem"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="0">
+ <Name>spearfish</Name>
+ <Title>spearfish</Title>
+ <Abstract>Layer-Group type layer: spearfish</Abstract>
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.87799562257162" miny="44.37244213023845" maxx="-103.62286957414864" maxy="44.5023266635277"/>
+ <BoundingBox SRS="EPSG:26713" minx="589425.9342365642" miny="4913959.224611808" maxx="609518.6719560538" maxy="4928082.949945881"/>
+ </Layer>
+ <Layer queryable="0">
+ <Name>tasmania</Name>
+ <Title>tasmania</Title>
+ <Abstract>Layer-Group type layer: tasmania</Abstract>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>
+ <BoundingBox SRS="EPSG:4326" minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>
+ </Layer>
+ <Layer queryable="0">
+ <Name>tiger-ny</Name>
+ <Title>tiger-ny</Title>
+ <Abstract>Layer-Group type layer: tiger-ny</Abstract>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-74.047185" miny="40.679648" maxx="-73.907005" maxy="40.882078"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.047185" miny="40.679648" maxx="-73.907005" maxy="40.882078"/>
+ </Layer>
+ </Layer>
+ </Capability>
+</WMT_MS_Capabilities>--></div>
+
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMSCapabilities/v1_1_1_WMSC.html b/misc/openlayers/tests/Format/WMSCapabilities/v1_1_1_WMSC.html
new file mode 100644
index 0000000..044773d
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMSCapabilities/v1_1_1_WMSC.html
@@ -0,0 +1,348 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read(t) {
+
+ t.plan(9);
+
+ var xml = document.getElementById("wmsc").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var format = new OpenLayers.Format.WMSCapabilities({profile: "WMSC"});
+ var obj = format.read(doc);
+ var tilesets = obj.capability.vendorSpecific.tileSets;
+ t.eq(tilesets.length, 2, "We expect 2 tilesets to be parsed");
+ var tileset = tilesets[0];
+ t.eq(tileset.bbox["EPSG:900913"].bbox, [-13697515.466796875, 5165920.118906248, -13619243.94984375, 5244191.635859374], "BBOX correctly parsed");
+ t.eq(tileset.format, "image/png", "Format correctly parsed");
+ t.eq(tileset.height, 256, "Height correctly parsed");
+ t.eq(tileset.width, 256, "Width correctly parsed");
+ t.eq(tileset.layers, "medford:hydro", "Layers correctly parsed");
+ t.eq(tileset.srs["EPSG:900913"], true, "SRS correctly parsed");
+ t.eq(tileset.resolutions, [156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, 76.43702827453613, 38.218514137268066, 19.109257068634033, 9.554628534317017, 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135, 0.29858214169740677, 0.14929107084870338, 0.07464553542435169, 0.037322767712175846, 0.018661383856087923, 0.009330691928043961, 0.004665345964021981], "Resolutions correctly parsed");
+ t.eq(tileset.styles, "", "Styles correctly parsed");
+ }
+
+ function test_read_fallback(t) {
+ t.plan(1);
+ var xml = document.getElementById("fallback").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var format = new OpenLayers.Format.WMSCapabilities({profile: "WMSC", allowFallback: true});
+ var obj = format.read(doc);
+ t.eq(obj.capability.layers.length, 2, "2 layers parsed with allowFallback true");
+ }
+
+ </script>
+</head>
+<body>
+
+<div id="fallback"><!--
+<?xml version='1.0' encoding="ISO-8859-1" standalone="no" ?>
+<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://schemas.opengis.net/wms/1.1.0/capabilities_1_1_0.dtd"
+ [
+ <!ELEMENT VendorSpecificCapabilities EMPTY>
+ ]>
+<WMT_MS_Capabilities version="1.1.0">
+
+<Service>
+ <Name>OGC:WMS</Name>
+ <Title>i3Geo - i3geo</Title>
+ <Abstract>Web services gerados da base de dados do i3Geo. Para chamar um tema especificamente, veja o sistema de ajuda, digitando no navegador web ogc.php?ajuda=, para uma lista compacta de todos os servicos, digite ogc.php?lista=temas</Abstract>
+ <KeywordList>
+ <Keyword>i3Geo</Keyword>
+ </KeywordList>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/>
+ <ContactInformation>
+ <ContactPersonPrimary>
+ <ContactPerson>Web Master</ContactPerson>
+ <ContactOrganization>Coordena??o Geral de TI</ContactOrganization>
+ </ContactPersonPrimary>
+ <ContactPosition>Administrador do s?tio web</ContactPosition>
+ <ContactAddress>
+ <AddressType>uri</AddressType>
+ <Address>http://www.mma.gov.br</Address>
+ <City>Brasilia</City>
+ <StateOrProvince>DF</StateOrProvince>
+ <PostCode></PostCode>
+ <Country>Brasil</Country>
+ </ContactAddress>
+ <ContactElectronicMailAddress>geoprocessamento@mma.gov.br</ContactElectronicMailAddress>
+ </ContactInformation>
+ <Fees>none</Fees>
+ <AccessConstraints>vedado o uso comercial</AccessConstraints>
+</Service>
+
+<Capability>
+ <Request>
+ <GetCapabilities>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Get>
+ <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Post>
+ </HTTP>
+ </DCPType>
+ </GetCapabilities>
+ <GetMap>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/gif</Format>
+ <Format>image/png; mode=8bit</Format>
+ <Format>application/x-pdf</Format>
+ <Format>image/svg+xml</Format>
+ <Format>image/tiff</Format>
+ <Format>application/vnd.google-earth.kml+xml</Format>
+ <Format>application/vnd.google-earth.kmz</Format>
+ <DCPType>
+ <HTTP>
+ <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Get>
+ <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Post>
+ </HTTP>
+ </DCPType>
+ </GetMap>
+ <GetFeatureInfo>
+ <Format>text/plain</Format>
+ <Format>application/vnd.ogc.gml</Format>
+ <DCPType>
+ <HTTP>
+ <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Get>
+ <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Post>
+ </HTTP>
+ </DCPType>
+ </GetFeatureInfo>
+ <DescribeLayer>
+ <Format>text/xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Get>
+ <Post><OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo/ogc.php?"/></Post>
+ </HTTP>
+ </DCPType>
+ </DescribeLayer>
+ </Request>
+ <Exception>
+ <Format>application/vnd.ogc.se_xml</Format>
+ <Format>application/vnd.ogc.se_inimage</Format>
+ <Format>application/vnd.ogc.se_blank</Format>
+ </Exception>
+ <VendorSpecificCapabilities />
+ <UserDefinedSymbolization SupportSLD="1" UserLayer="0" UserStyle="1" RemoteWFS="0"/>
+ <Layer>
+ <Name>i3geoogc</Name>
+ <Title>i3Geo - i3geo</Title>
+ <Abstract>Web services gerados da base de dados do i3Geo. Para chamar um tema especificamente, veja o sistema de ajuda, digitando no navegador web ogc.php?ajuda=, para uma lista compacta de todos os servicos, digite ogc.php?lista=temas</Abstract>
+ <KeywordList>
+ <Keyword>i3Geo</Keyword>
+ </KeywordList>
+ <SRS></SRS>
+ <LatLonBoundingBox minx="-76.5126" miny="-36.9484" maxx="-29.5852" maxy="7.04601" />
+ <BoundingBox SRS=""
+ minx="-76.5126" miny="-36.9484" maxx="-29.5852" maxy="7.04601" />
+ <Attribution>
+ <Title>i3Geo</Title>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://mapas.mma.gov.br/i3geo"/>
+ <LogoURL width="85" height="56">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://mapas.mma.gov.br/i3geo/imagens/i3geo.png"/>
+ </LogoURL>
+ </Attribution>
+ <Layer queryable="1" opaque="0" cascaded="0">
+ <Name>antigo_caminantes</Name>
+ <Title>Guia de Caminantes - 1817</Title>
+ <SRS> EPSG:4618 EPSG:4291 EPSG:4326 EPSG:22521 EPSG:22522 EPSG:22523 EPSG:22524 EPSG:22525 EPSG:29101 EPSG:29119 EPSG:29120 EPSG:29121 EPSG:29122 EPSG:29177 EPSG:29178 EPSG:29179 EPSG:29180 EPSG:29181 EPSG:29182 EPSG:29183 EPSG:29184 EPSG:29185</SRS>
+ <LatLonBoundingBox minx="-75.2336" miny="-33.7516" maxx="-27.593" maxy="5.27216" />
+ <BoundingBox SRS=""
+ minx="-75.2336" miny="-33.7516" maxx="-27.593" maxy="5.27216" />
+ <MetadataURL type="TC211">
+ <Format>text/html</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://consorcio.bn.br"/>
+ </MetadataURL>
+ </Layer>
+ </Layer>
+</Capability>
+</WMT_MS_Capabilities>
+--></div>
+
+<div id="wmsc"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://schemas.opengis.net/wms/1.1.1/capabilities_1_1_1.dtd"[
+<!ELEMENT VendorSpecificCapabilities (TileSet*) >
+<!ELEMENT TileSet (SRS, BoundingBox?, Resolutions, Width, Height, Format, Layers*, Styles*) >
+<!ELEMENT Resolutions (#PCDATA) >
+<!ELEMENT Width (#PCDATA) >
+<!ELEMENT Height (#PCDATA) >
+<!ELEMENT Layers (#PCDATA) >
+<!ELEMENT Styles (#PCDATA) >
+]>
+<WMT_MS_Capabilities version="1.1.1" updateSequence="57">
+ <Service>
+ <Name>OGC:WMS</Name>
+ <Title>GeoServer Web Map Service</Title>
+ <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+ <KeywordList>
+ <Keyword>WFS</Keyword>
+ <Keyword>WMS</Keyword>
+ <Keyword>GEOSERVER</Keyword>
+ </KeywordList>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms"/>
+ <ContactInformation>
+ <ContactPersonPrimary>
+ <ContactPerson>OpenGeo</ContactPerson>
+ <ContactOrganization>OpenGeo</ContactOrganization>
+ </ContactPersonPrimary>
+ <ContactPosition>Outreach</ContactPosition>
+ <ContactAddress>
+ <AddressType>Work</AddressType>
+ <Address/>
+ <City>New York</City>
+ <StateOrProvince/>
+ <PostCode/>
+ <Country>USA</Country>
+ </ContactAddress>
+ <ContactVoiceTelephone/>
+ <ContactFacsimileTelephone/>
+ <ContactElectronicMailAddress>inquiry@opengeo.org</ContactElectronicMailAddress>
+ </ContactInformation>
+ <Fees>NONE</Fees>
+ <AccessConstraints>NONE</AccessConstraints>
+ </Service>
+ <Capability>
+ <Request>
+ <GetCapabilities>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetCapabilities>
+ <GetMap>
+ <Format>image/png</Format>
+ <Format>application/atom xml</Format>
+ <Format>application/atom+xml</Format>
+ <Format>application/openlayers</Format>
+ <Format>application/pdf</Format>
+ <Format>application/rss xml</Format>
+ <Format>application/rss+xml</Format>
+ <Format>application/vnd.google-earth.kml</Format>
+ <Format>application/vnd.google-earth.kml xml</Format>
+ <Format>application/vnd.google-earth.kml+xml</Format>
+ <Format>application/vnd.google-earth.kmz</Format>
+ <Format>application/vnd.google-earth.kmz xml</Format>
+ <Format>application/vnd.google-earth.kmz+xml</Format>
+ <Format>atom</Format>
+ <Format>image/geotiff</Format>
+ <Format>image/geotiff8</Format>
+ <Format>image/gif</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/png8</Format>
+ <Format>image/svg</Format>
+ <Format>image/svg xml</Format>
+ <Format>image/svg+xml</Format>
+ <Format>image/tiff</Format>
+ <Format>image/tiff8</Format>
+ <Format>kml</Format>
+ <Format>kmz</Format>
+ <Format>openlayers</Format>
+ <Format>rss</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetMap>
+ <GetFeatureInfo>
+ <Format>text/plain</Format>
+ <Format>application/vnd.ogc.gml</Format>
+ <Format>text/html</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetFeatureInfo>
+ <DescribeLayer>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </DescribeLayer>
+ <GetLegendGraphic>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/gif</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetLegendGraphic>
+ <GetStyles>
+ <Format>application/vnd.ogc.sld+xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://localhost:8080/geoserver-suite/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetStyles>
+ </Request>
+ <Exception>
+ <Format>application/vnd.ogc.se_xml</Format>
+ <Format>application/vnd.ogc.se_inimage</Format>
+ </Exception>
+ <VendorSpecificCapabilities>
+ <TileSet>
+ <SRS>EPSG:900913</SRS>
+ <BoundingBox SRS="EPSG:900913" minx="-1.3697515466796875E7" miny="5165920.118906248" maxx="-1.361924394984375E7" maxy="5244191.635859374"/>
+ <Resolutions>156543.03390625 78271.516953125 39135.7584765625 19567.87923828125 9783.939619140625 4891.9698095703125 2445.9849047851562 1222.9924523925781 611.4962261962891 305.74811309814453 152.87405654907226 76.43702827453613 38.218514137268066 19.109257068634033 9.554628534317017 4.777314267158508 2.388657133579254 1.194328566789627 0.5971642833948135 0.29858214169740677 0.14929107084870338 0.07464553542435169 0.037322767712175846 0.018661383856087923 0.009330691928043961 0.004665345964021981 </Resolutions>
+ <Width>256</Width>
+ <Height>256</Height>
+ <Format>image/png</Format>
+ <Layers>medford:hydro</Layers>
+ <Styles/>
+ </TileSet>
+ <TileSet>
+ <SRS>EPSG:4326</SRS>
+ <BoundingBox SRS="EPSG:4326" minx="-123.046875" miny="42.1875" maxx="-122.6953125" maxy="42.5390625"/>
+ <Resolutions>0.703125 0.3515625 0.17578125 0.087890625 0.0439453125 0.02197265625 0.010986328125 0.0054931640625 0.00274658203125 0.001373291015625 6.866455078125E-4 3.4332275390625E-4 1.71661376953125E-4 8.58306884765625E-5 4.291534423828125E-5 2.1457672119140625E-5 1.0728836059570312E-5 5.364418029785156E-6 2.682209014892578E-6 1.341104507446289E-6 6.705522537231445E-7 3.3527612686157227E-7 1.6763806343078613E-7 8.381903171539307E-8 4.190951585769653E-8 2.0954757928848267E-8 </Resolutions>
+ <Width>256</Width>
+ <Height>256</Height>
+ <Format>image/gif</Format>
+ <Layers>medford</Layers>
+ <Styles/>
+ </TileSet>
+ </VendorSpecificCapabilities>
+ <UserDefinedSymbolization SupportSLD="1" UserLayer="1" UserStyle="1" RemoteWFS="1"/>
+ <Layer queryable="0" opaque="0" noSubsets="0">
+ <Title>GeoServer Web Map Service</Title>
+ <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+ <SRS>EPSG:4326</SRS>
+ <SRS>EPSG:900913</SRS>
+ <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="83.624"/>
+ </Layer>
+ </Capability>
+</WMT_MS_Capabilities>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMSCapabilities/v1_3_0.html b/misc/openlayers/tests/Format/WMSCapabilities/v1_3_0.html
new file mode 100644
index 0000000..7120b8c
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMSCapabilities/v1_3_0.html
@@ -0,0 +1,614 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_exception(t) {
+ t.plan(1);
+ var xml = document.getElementById("exceptionsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var format = new OpenLayers.Format.WMSCapabilities();
+ var obj = format.read(doc);
+ t.ok(!!obj.error, "Error reported correctly");
+ }
+
+ function test_layers(t) {
+
+ t.plan(25);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var capability = obj.capability;
+
+ var layers = {};
+ for (var i=0, len=capability.layers.length; i<len; i++) {
+ if ("name" in capability.layers[i]) {
+ layers[ capability.layers[i].name ] = capability.layers[i];
+ }
+ }
+
+ var rootlayer = capability.layers[ capability.layers.length - 1];
+
+ t.eq(rootlayer.srs,
+ {"CRS:84": true},
+ "SRS parsed correctly for root layer");
+ t.eq(layers["ROADS_RIVERS"].srs,
+ {"CRS:84": true, "EPSG:26986": true},
+ "Inheritance of SRS handled correctly when adding SRSes");
+ t.eq(layers["Temperature"].srs,
+ {"CRS:84": true},
+ "Inheritance of SRS handled correctly when redeclaring an inherited SRS");
+ t.eq(layers["Temperature"].infoFormats, ["text/xml", "text/plain", "text/html"], "infoFormats set correctly on layer");
+ var bbox = layers["ROADS_RIVERS"].bbox["EPSG:26986"];
+ t.eq(bbox.bbox,
+ [189000, 834000, 285000, 962000],
+ "Correct bbox from BoundingBox");
+ t.eq(bbox.res, {x: 1, y: 1}, "Correct resolution");
+ bbox = layers["ROADS_RIVERS"].bbox["CRS:84"];
+ t.eq(bbox.bbox,
+ [-71.63, 41.75, -70.78, 42.90],
+ "Correct bbox from BoundingBox (override)");
+ t.eq(bbox.res, {x: 0.01, y: 0.01}, "Correct resolution (override)");
+
+ bbox = layers["ROADS_1M"].bbox["EPSG:26986"];
+ t.eq(bbox.bbox,
+ [189000, 834000, 285000, 962000],
+ "Correctly inherited bbox");
+ t.eq(bbox.res, {x: 1, y: 1}, "Correctly inherited resolution");
+
+
+ var identifiers = layers["ROADS_RIVERS"].identifiers;
+ var authorities = layers["ROADS_RIVERS"].authorityURLs;
+
+ t.ok(identifiers, "got identifiers from layer ROADS_RIVERS");
+ t.ok("DIF_ID" in identifiers,
+ "authority attribute from Identifiers parsed correctly");
+ t.eq(identifiers["DIF_ID"],
+ "123456",
+ "Identifier value parsed correctly");
+ t.ok("DIF_ID" in authorities,
+ "AuthorityURLs parsed and inherited correctly");
+ t.eq(authorities["DIF_ID"],
+ "http://gcmd.gsfc.nasa.gov/difguide/whatisadif.html",
+ "OnlineResource in AuthorityURLs parsed correctly");
+
+ var featurelist = layers["ROADS_RIVERS"].featureListURL;
+ t.ok(featurelist, "layer has FeatureListURL");
+ t.eq(featurelist.format,
+ "XML",
+ "FeatureListURL format parsed correctly");
+ t.eq(featurelist.href,
+ "http://www.university.edu/data/roads_rivers.gml",
+ "FeatureListURL OnlineResource parsed correctly");
+
+ t.eq(layers["Pressure"].queryable,
+ true,
+ "queryable property inherited correctly");
+ t.eq(layers["ozone_image"].queryable,
+ false,
+ "queryable property has correct default value");
+ t.eq(layers["population"].cascaded,
+ 1,
+ "cascaded property parsed correctly");
+ t.eq(layers["ozone_image"].fixedWidth,
+ 512,
+ "fixedWidth property correctly parsed");
+ t.eq(layers["ozone_image"].fixedHeight,
+ 256,
+ "fixedHeight property correctly parsed");
+ t.eq(layers["ozone_image"].opaque,
+ true,
+ "opaque property parsed correctly");
+ t.eq(layers["ozone_image"].noSubsets,
+ true,
+ "noSubsets property parsed correctly");
+
+
+ }
+
+ function test_dimensions(t) {
+
+ t.plan(8);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var capability = obj.capability;
+
+ var layers = {};
+ for (var i=0, len=capability.layers.length; i<len; i++) {
+ if ("name" in capability.layers[i]) {
+ layers[ capability.layers[i].name ] = capability.layers[i];
+ }
+ }
+
+ var time = layers["Clouds"].dimensions.time;
+ t.eq(time["default"], "2000-08-22", "Default time value parsed correctly");
+ t.eq(time.values.length, 1, "Currect number of time extent values/periods");
+ t.eq(time.values[0], "1999-01-01/2000-08-22/P1D", "Time extent values parsed correctly");
+
+ var elevation = layers["Pressure"].dimensions.elevation;
+ t.eq(elevation.units, "CRS:88", "Dimension units parsed correctly");
+ t.eq(elevation["default"], "0", "Default elevation value parsed correctly");
+ t.eq(elevation.nearestVal, true, "NearestValue parsed correctly");
+ t.eq(elevation.multipleVal, false, "Absense of MultipleValues handled correctly");
+ t.eq(elevation.values,
+ ["0","1000","3000","5000","10000"],
+ "Parsing of comma-separated values done correctly");
+
+
+ }
+
+ function test_contactinfo(t) {
+ t.plan(14);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var service = obj.service;
+
+ var contactinfo = service.contactInformation;
+ t.ok(contactinfo, "object contains contactInformation property");
+
+ var personPrimary = contactinfo.personPrimary;
+ t.ok(personPrimary, "object contains personPrimary property");
+
+ t.eq(personPrimary.person, "Jeff Smith", "ContactPerson parsed correctly");
+ t.eq(personPrimary.organization, "NASA", "ContactOrganization parsed correctly");
+
+ t.eq(contactinfo.position,
+ "Computer Scientist",
+ "ContactPosition parsed correctly");
+
+
+ var addr = contactinfo.contactAddress;
+ t.ok(addr, "object contains contactAddress property");
+
+ t.eq(addr.type, "postal", "AddressType parsed correctly");
+ t.eq(addr.address,
+ "NASA Goddard Space Flight Center",
+ "Address parsed correctly");
+ t.eq(addr.city, "Greenbelt", "City parsed correctly");
+ t.eq(addr.stateOrProvince, "MD", "StateOrProvince parsed correctly");
+ t.eq(addr.postcode, "20771", "PostCode parsed correctly");
+ t.eq(addr.country, "USA", "Country parsed correctly");
+
+ t.eq(contactinfo.phone,
+ "+1 301 555-1212",
+ "ContactVoiceTelephone parsed correctly");
+ t.eq(contactinfo.email,
+ "user@host.com",
+ "ContactElectronicMailAddress parsed correctly");
+ }
+
+ function test_feesAndConstraints(t) {
+ t.plan(2);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var service = obj.service;
+
+ t.ok(! ("fees" in service), "Fees=none handled correctly");
+ t.ok(! ("accessConstraints" in service), "AccessConstraints=none handled correctly");
+ }
+
+ function test_requests(t) {
+ t.plan(6);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var request = obj.capability.request;
+
+ t.ok(request, "request property exists");
+ t.ok("getmap" in request, "got GetMap request");
+
+ t.ok("getfeatureinfo" in request, "got GetFeatureInfo request");
+ t.eq(request.getfeatureinfo.formats,
+ ["text/xml", "text/plain", "text/html"],
+ "GetFeatureInfo formats correctly parsed");
+
+ var exception = obj.capability.exception;
+ t.ok(exception, "exception property exists");
+ t.eq(exception.formats,
+ ["XML", "INIMAGE", "BLANK"],
+ "Exception Format parsed");
+ }
+
+ function test_ogc(t) {
+ t.plan(14);
+
+ /*
+ * Set up
+ */
+
+ // needed for the minScale/maxScale test, see below
+ var dpi = OpenLayers.DOTS_PER_INCH;
+ OpenLayers.DOTS_PER_INCH = 90.71;
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+ var capability = obj.capability;
+
+ /*
+ * Test
+ */
+
+ var attribution = capability.layers[2].attribution;
+ t.eq(attribution.title, "State College University", "attribution title parsed correctly.");
+ t.eq(attribution.href, "http://www.university.edu/", "attribution href parsed correctly.")
+ t.eq(attribution.logo.href, "http://www.university.edu/icons/logo.gif", "attribution logo url parsed correctly.");
+ t.eq(attribution.logo.format, "image/gif", "attribution logo format parsed correctly.");
+ t.eq(attribution.logo.width, "100", "attribution logo width parsed correctly.");
+ t.eq(attribution.logo.height, "100", "attribution logo height parsed correctly.");
+
+ var keywords = capability.layers[0].keywords;
+ t.eq(keywords.length, 3, "layer has 3 keywords.");
+ t.eq(keywords[0].value, "road", "1st keyword parsed correctly.");
+
+ var metadataURLs = capability.layers[0].metadataURLs;
+ t.eq(metadataURLs.length, 2, "layer has 2 metadata urls.");
+ t.eq(metadataURLs[0].type, "FGDC:1998", "type parsed correctly.");
+ t.eq(metadataURLs[0].format, "text/plain", "format parsed correctly.");
+ t.eq(metadataURLs[0].href, "http://www.university.edu/metadata/roads.txt", "href parsed correctly.");
+
+ /*
+ Test minScale and maxScale
+ */
+ var minScale = 250000;
+ var maxScale = 1000;
+ t.eq(capability.layers[0].minScale, minScale.toPrecision(16), "layer.minScale is correct");
+ t.eq(capability.layers[0].maxScale, maxScale.toPrecision(16), "layer.maxScale is correct");
+
+ /*
+ * Tear down
+ */
+
+ OpenLayers.DOTS_PER_INCH = dpi;
+ }
+
+ function test_WMS13specials(t) {
+ t.plan(3);
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMSCapabilities().read(doc);
+
+ t.eq(obj.service.layerLimit, 16, "LayerLimit parsed correctly");
+ t.eq(obj.service.maxHeight, 2048, "MaxHeight parsed correctly");
+ t.eq(obj.service.maxWidth, 2048, "MaxWidth parsed correctly");
+
+ }
+
+ </script>
+</head>
+<body>
+
+<div id="exceptionsample"><!--
+<?xml version='1.0' encoding="UTF-8"?>
+<ServiceExceptionReport version="1.3.0" xmlns="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/ogc
+ http://schemas.opengis.net/wms/1.3.0/exceptions_1_3_0.xsd">
+ <ServiceException> Plain text message about an error. </ServiceException>
+ <ServiceException code="InvalidUpdateSequence"> Another error message, this one with a service
+ exception code supplied. </ServiceException>
+ <ServiceException>
+ <![CDATA[ Error in module <foo.c>, line 42
+A message that includes angle brackets in text must be enclosed in a Character Data Section as in this example. All XML-like markup is ignored except for this sequence of three closing characters:
+]]>
+ </ServiceException>
+ <ServiceException>
+ <![CDATA[ <Module>foo.c</Module> <Error>An error occurred</Error> <Explanation>Similarly, actual XML can be enclosed in a CDATA section. A generic parser will ignore that XML, but application-specific software may choose to process it.</Explanation> ]]>
+ </ServiceException>
+</ServiceExceptionReport>
+--></div>
+
+<!--
+OGC example below taken from
+http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xml
+Changes:
+-removed comments
+-corrected typo in FeatureListURL Format XML with double quote
+-added MinScaleDenominator and MaxScaleDenominator
+-remove whitespace in Dimension tags
+-->
+<div id="ogcsample"><!--
+<?xml version='1.0' encoding="UTF-8"?>
+<WMS_Capabilities version="1.3.0" xmlns="http://www.opengis.net/wms"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wms http://schemas.opengis.net/wms/1.3.0/capabilities_1_3_0.xsd">
+<Service>
+ <Name>WMS</Name>
+ <Title>Acme Corp. Map Server</Title>
+ <Abstract>Map Server maintained by Acme Corporation. Contact: webmaster@wmt.acme.com. High-quality maps showing roadrunner nests and possible ambush locations.</Abstract>
+
+ <KeywordList>
+ <Keyword>bird</Keyword>
+ <Keyword>roadrunner</Keyword>
+ <Keyword>ambush</Keyword>
+ </KeywordList>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://hostname/" />
+
+
+ <ContactInformation>
+ <ContactPersonPrimary>
+ <ContactPerson>Jeff Smith</ContactPerson>
+ <ContactOrganization>NASA</ContactOrganization>
+ </ContactPersonPrimary>
+ <ContactPosition>Computer Scientist</ContactPosition>
+
+ <ContactAddress>
+ <AddressType>postal</AddressType>
+ <Address>NASA Goddard Space Flight Center</Address>
+ <City>Greenbelt</City>
+ <StateOrProvince>MD</StateOrProvince>
+ <PostCode>20771</PostCode>
+
+ <Country>USA</Country>
+ </ContactAddress>
+ <ContactVoiceTelephone>+1 301 555-1212</ContactVoiceTelephone>
+ <ContactElectronicMailAddress>user@host.com</ContactElectronicMailAddress>
+ </ContactInformation>
+
+ <Fees>none</Fees>
+
+ <AccessConstraints>none</AccessConstraints>
+ <LayerLimit>16</LayerLimit>
+ <MaxWidth>2048</MaxWidth>
+ <MaxHeight>2048</MaxHeight>
+</Service>
+<Capability>
+ <Request>
+ <GetCapabilities>
+
+ <Format>text/xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname/path?" />
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname/path?" />
+
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetCapabilities>
+ <GetMap>
+ <Format>image/gif</Format>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname/path?" />
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetMap>
+
+ <GetFeatureInfo>
+ <Format>text/xml</Format>
+ <Format>text/plain</Format>
+ <Format>text/html</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://hostname/path?" />
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetFeatureInfo>
+ </Request>
+ <Exception>
+ <Format>XML</Format>
+
+ <Format>INIMAGE</Format>
+ <Format>BLANK</Format>
+ </Exception>
+ <Layer>
+ <Title>Acme Corp. Map Server</Title>
+ <CRS>CRS:84</CRS>
+
+ <AuthorityURL name="DIF_ID">
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://gcmd.gsfc.nasa.gov/difguide/whatisadif.html" />
+ </AuthorityURL>
+ <BoundingBox CRS="CRS:84"
+ minx="-1" miny="-1" maxx="1" maxy="1" resx="0.0" resy="0.0"/>
+ <Layer>
+
+ <Name>ROADS_RIVERS</Name>
+ <Title>Roads and Rivers</Title>
+
+ <CRS>EPSG:26986</CRS>
+ <EX_GeographicBoundingBox>
+ <westBoundLongitude>-71.63</westBoundLongitude>
+ <eastBoundLongitude>-70.78</eastBoundLongitude>
+ <southBoundLatitude>41.75</southBoundLatitude>
+ <northBoundLatitude>42.90</northBoundLatitude>
+
+ </EX_GeographicBoundingBox>
+ <BoundingBox CRS="CRS:84"
+ minx="-71.63" miny="41.75" maxx="-70.78" maxy="42.90" resx="0.01" resy="0.01"/>
+ <BoundingBox CRS="EPSG:26986"
+ minx="189000" miny="834000" maxx="285000" maxy="962000" resx="1" resy="1" />
+ <Attribution>
+ <Title>State College University</Title>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://www.university.edu/" />
+
+ <LogoURL width="100" height="100">
+ <Format>image/gif</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/icons/logo.gif" />
+ </LogoURL>
+ </Attribution>
+ <Identifier authority="DIF_ID">123456</Identifier>
+ <FeatureListURL>
+
+ <Format>XML</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple"
+ xlink:href="http://www.university.edu/data/roads_rivers.gml" />
+ </FeatureListURL>
+ <Style>
+ <Name>USGS</Name>
+ <Title>USGS Topo Map Style</Title>
+ <Abstract>Features are shown in a style like that used in USGS topographic maps.</Abstract>
+
+ <LegendURL width="72" height="72">
+ <Format>image/gif</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/legends/usgs.gif" />
+ </LegendURL>
+ <StyleSheetURL>
+ <Format>text/xsl</Format>
+
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/stylesheets/usgs.xsl" />
+ </StyleSheetURL>
+ </Style>
+ <MinScaleDenominator>1000</MinScaleDenominator>
+ <MaxScaleDenominator>250000</MaxScaleDenominator>
+ <Layer queryable="1">
+ <Name>ROADS_1M</Name>
+ <Title>Roads at 1:1M scale</Title>
+ <Abstract>Roads at a scale of 1 to 1 million.</Abstract>
+
+ <KeywordList>
+ <Keyword>road</Keyword>
+ <Keyword>transportation</Keyword>
+ <Keyword>atlas</Keyword>
+ </KeywordList>
+ <Identifier authority="DIF_ID">123456</Identifier>
+ <MetadataURL type="FGDC:1998">
+
+ <Format>text/plain</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/metadata/roads.txt" />
+ </MetadataURL>
+ <MetadataURL type="ISO19115:2003">
+ <Format>text/xml</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/metadata/roads.xml" />
+ </MetadataURL>
+
+ <Style>
+ <Name>ATLAS</Name>
+ <Title>Road atlas style</Title>
+ <Abstract>Roads are shown in a style like that used in a commercial road atlas.</Abstract>
+ <LegendURL width="72" height="72">
+ <Format>image/gif</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink"
+ xlink:type="simple"
+ xlink:href="http://www.university.edu/legends/atlas.gif" />
+
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>RIVERS_1M</Name>
+ <Title>Rivers at 1:1M scale</Title>
+ <Abstract>Rivers at a scale of 1 to 1 million.</Abstract>
+
+ <KeywordList>
+ <Keyword>river</Keyword>
+ <Keyword>canal</Keyword>
+ <Keyword>waterway</Keyword>
+ </KeywordList>
+ </Layer>
+ </Layer>
+
+ <Layer queryable="1">
+ <Title>Weather Forecast Data</Title>
+ <CRS>CRS:84</CRS>
+
+ <EX_GeographicBoundingBox>
+ <westBoundLongitude>-180</westBoundLongitude>
+ <eastBoundLongitude>180</eastBoundLongitude>
+
+ <southBoundLatitude>-90</southBoundLatitude>
+ <northBoundLatitude>90</northBoundLatitude>
+ </EX_GeographicBoundingBox>
+ <Dimension name="time" units="ISO8601" default="2000-08-22">1999-01-01/2000-08-22/P1D</Dimension>
+ <Layer>
+
+ <Name>Clouds</Name>
+ <Title>Forecast cloud cover</Title>
+ </Layer>
+ <Layer>
+ <Name>Temperature</Name>
+ <Title>Forecast temperature</Title>
+ </Layer>
+
+ <Layer>
+ <Name>Pressure</Name>
+ <Title>Forecast barometric pressure</Title>
+ <Dimension name="elevation" units="EPSG:5030" />
+ <Dimension name="time" units="ISO8601" default="2000-08-22">
+ 1999-01-01/2000-08-22/P1D</Dimension>
+
+ <Dimension name="elevation" units="CRS:88" default="0" nearestValue="1">0,1000,3000,5000,10000</Dimension>
+ </Layer>
+ </Layer>
+ <Layer opaque="1" noSubsets="1" fixedWidth="512" fixedHeight="256">
+ <Name>ozone_image</Name>
+ <Title>Global ozone distribution (1992)</Title>
+
+ <EX_GeographicBoundingBox>
+ <westBoundLongitude>-180</westBoundLongitude>
+ <eastBoundLongitude>180</eastBoundLongitude>
+ <southBoundLatitude>-90</southBoundLatitude>
+ <northBoundLatitude>90</northBoundLatitude>
+ </EX_GeographicBoundingBox>
+ <Dimension name="time" units="ISO8601" default="1992">1992</Dimension>
+
+ </Layer>
+ <Layer cascaded="1">
+ <Name>population</Name>
+ <Title>World population, annual</Title>
+ <EX_GeographicBoundingBox>
+ <westBoundLongitude>-180</westBoundLongitude>
+
+ <eastBoundLongitude>180</eastBoundLongitude>
+ <southBoundLatitude>-90</southBoundLatitude>
+ <northBoundLatitude>90</northBoundLatitude>
+ </EX_GeographicBoundingBox>
+ <Dimension name="time" units="ISO8601" default="2000">1990/2000/P1Y</Dimension>
+ </Layer>
+ </Layer>
+
+</Capability>
+</WMS_Capabilities>
+--></div>
+
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMSDescribeLayer.html b/misc/openlayers/tests/Format/WMSDescribeLayer.html
new file mode 100644
index 0000000..7a53269
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMSDescribeLayer.html
@@ -0,0 +1,65 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_WMSDescribeLayer(t) {
+ t.plan(10);
+
+ var parser = new OpenLayers.Format.WMSDescribeLayer();
+
+ var text =
+ '<WMS_DescribeLayerResponse version="1.1.1">' +
+ ' <LayerDescription name="topp:states" wfs="http://geo.openplans.org:80/geoserver/wfs/WfsDispatcher?">' +
+ ' <Query typeName="topp:states"/>' +
+ ' </LayerDescription>' +
+ '</WMS_DescribeLayerResponse>';
+
+ var res = parser.read(text);
+
+ t.eq(res.layerDescriptions.length, 1,
+ "Only one LayerDescription in data, so only one parsed");
+
+ t.eq(res.layerDescriptions[0].owsType, "WFS",
+ "Properly parses owsType as WFS");
+
+ t.eq(res.layerDescriptions[0].owsURL, "http://geo.openplans.org:80/geoserver/wfs/WfsDispatcher?",
+ "Properly parses owsURL");
+
+ t.eq(res.layerDescriptions[0].typeName, "topp:states",
+ "Properly parses typeName");
+
+ t.eq(res.layerDescriptions[0].layerName, "topp:states",
+ "Properly parses name");
+
+ //TODO remove the 5 tests below when we deprecate the old structure
+ t.eq(res.length, 1,
+ "Only one LayerDescription in data, so only one parsed");
+ t.eq(res[0].owsType, "WFS",
+ "Properly parses owsType as WFS");
+ t.eq(res[0].owsURL, "http://geo.openplans.org:80/geoserver/wfs/WfsDispatcher?",
+ "Properly parses owsURL");
+ t.eq(res[0].typeName, "topp:states",
+ "Properly parses typeName");
+ t.eq(res[0].layerName, "topp:states",
+ "Properly parses name");
+
+ }
+
+ function test_read_exception(t) {
+ t.plan(1);
+ var text = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>' +
+ '<!DOCTYPE ServiceExceptionReport SYSTEM "http://schemas.opengis.net/wms/1.1.1/WMS_exception_1_1_1.dtd">' +
+ '<ServiceExceptionReport version="1.1.1" > <ServiceException code="LayerNotDefined">' +
+ 'geonode:_map_107_annotations: no such layer on this server' +
+ '</ServiceException></ServiceExceptionReport>';
+ var format = new OpenLayers.Format.WMSDescribeLayer();
+ var obj = format.read(text);
+ t.ok(!!obj.error, "Error reported correctly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMSGetFeatureInfo.html b/misc/openlayers/tests/Format/WMSGetFeatureInfo.html
new file mode 100644
index 0000000..1301b65
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMSGetFeatureInfo.html
@@ -0,0 +1,319 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_FeatureInfoResponse(t) {
+ t.plan(7);
+
+ var parser = new OpenLayers.Format.WMSGetFeatureInfo();
+
+ // read empty response
+ var text =
+ '<?xml version="1.0" encoding="UTF-8" ?>' +
+ '<FeatureInfoResponse>' +
+ '</FeatureInfoResponse>';
+
+ var features = parser.read(text);
+ t.eq(features.length, 0,
+ "Parsing empty FeatureInfoResponse response succesfull");
+
+ // read 1 feature
+ text =
+ '<?xml version="1.0" encoding="UTF-8" ?>' +
+ '<FeatureInfoResponse>' +
+ ' <FIELDS OBJECTID="1188" HECTARES="1819.734" ZONENR="5854" NULZONES=" " AREA="18197340.1426" PERIMETER="19177.4073627" SHAPE="NULL" SE_ANNO_CAD_DATA="NULL" SHAPE.AREA="0" SHAPE.LEN="0"/>' +
+ '</FeatureInfoResponse>';
+
+ features = parser.read(text);
+ t.eq(features.length, 1,
+ "Parsed 1 feature in total");
+
+ t.eq(features[0].attributes.OBJECTID, '1188',
+ "Attribute OBJECTID contains the right value");
+
+ // read multiple features
+ text =
+ '<?xml version="1.0" encoding="UTF-8" ?>' +
+ '<FeatureInfoResponse>' +
+ ' <FIELDS OBJECTID="551" Shape="NULL" NAME="Carbon" STATE_NAME="Wyoming" AREA="7999.91062" POP2000="15639" POP00_SQMI="2" Shape_Length="6.61737274334215" Shape_Area="2.23938983524154"/>' +
+ ' <FIELDS OBJECTID="7" Shape="NULL" AREA="97803.199" STATE_NAME="Wyoming" SUB_REGION="Mtn" STATE_ABBR="WY" POP2000="493782" POP00_SQMI="5" Shape_Length="21.9870297323522" Shape_Area="27.9666881382635"/>' +
+ ' <FIELDS OBJECTID="99" Shape="NULL" LENGTH="378.836" TYPE="Multi-Lane Divided" ADMN_CLASS="Interstate" TOLL_RD="N" RTE_NUM1=" 80" RTE_NUM2=" " ROUTE="Interstate 80" Shape_Length="7.04294883879398"/>' +
+ '</FeatureInfoResponse>';
+
+ features = parser.read(text);
+
+ t.eq(features.length, 3,
+ "Parsed 3 features in total");
+
+ t.eq(features[1].attributes.STATE_NAME, 'Wyoming',
+ "Attribute STATE_NAME contains the right value");
+
+ text = '<FeatureInfoResponse>' +
+ '<FIELDS>' +
+ '<FIELD name="ID" value="B31A0154"/>' +
+ '<FIELD name="FID" value="31AL0011"/>' +
+ '</FIELDS>' +
+ '<FIELDS>' +
+ '<FIELD name="ID" value="B31A0153"/>' +
+ '<FIELD name="FID" value="31AL0011"/>' +
+ '</FIELDS>' +
+ '</FeatureInfoResponse>';
+
+ features = parser.read(text);
+
+ t.eq(features.length, 2,
+ "Parsed 2 features in total");
+
+ t.eq(features[1].attributes.FID, '31AL0011',
+ "Attribute FID contains the right value");
+
+ }
+
+ function test_read_msGMLOutput(t) {
+ t.plan(13);
+
+ var parser = new OpenLayers.Format.WMSGetFeatureInfo();
+
+ // read empty response
+ var text =
+ '<?xml version="1.0" encoding="ISO-8859-1"?>' +
+ '<msGMLOutput ' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' xmlns:xlink="http://www.w3.org/1999/xlink"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+ '</msGMLOutput>';
+
+ var features = parser.read(text);
+ t.eq(features.length, 0,
+ "Parsing empty msGMLOutput response succesfull");
+
+ // read empty attribute
+ text =
+ '<?xml version="1.0" encoding="ISO-8859-1"?>' +
+ '<msGMLOutput ' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' xmlns:xlink="http://www.w3.org/1999/xlink"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+ ' <AAA64_layer>' +
+ ' <AAA64_feature>' +
+ ' <gml:boundedBy>' +
+ ' <gml:Box srsName="EPSG:28992">' +
+ ' <gml:coordinates>107397.266000,460681.063000 116568.188000,480609.250000</gml:coordinates>' +
+ ' </gml:Box>' +
+ ' </gml:boundedBy>' +
+ ' <FOO>bar</FOO>' +
+ ' <EMPTY></EMPTY>' +
+ ' </AAA64_feature>' +
+ ' </AAA64_layer>' +
+ '</msGMLOutput>';
+ features = parser.read(text);
+ t.eq((features[0].attributes.EMPTY === null), true, "Empty attribute is parsed as null");
+
+ // read 1 feature from 1 layer
+ text =
+ '<?xml version="1.0" encoding="ISO-8859-1"?>' +
+ '<msGMLOutput ' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' xmlns:xlink="http://www.w3.org/1999/xlink"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+ ' <AAA64_layer>' +
+ ' <AAA64_feature>' +
+ ' <gml:boundedBy>' +
+ ' <gml:Box srsName="EPSG:28992">' +
+ ' <gml:coordinates>107397.266000,460681.063000 116568.188000,480609.250000</gml:coordinates>' +
+ ' </gml:Box>' +
+ ' </gml:boundedBy>' +
+ ' <OBJECTID>109</OBJECTID>' +
+ ' <ROUTE>N231</ROUTE>' +
+ ' <ROUTE_CH>#N231</ROUTE_CH>' +
+ ' <COUNT>2</COUNT>' +
+ ' <BEHEERDER>P</BEHEERDER>' +
+ ' <LENGTH>28641.7</LENGTH>' +
+ ' <SHAPE>&lt;shape&gt;</SHAPE>' +
+ ' <SE_ANNO_CAD_DATA>&lt;null&gt;</SE_ANNO_CAD_DATA>' +
+ ' </AAA64_feature>' +
+ ' </AAA64_layer>' +
+ '</msGMLOutput>';
+
+ features = parser.read(text);
+
+ t.eq(features.length, 1,
+ "Parsed 1 feature in total");
+
+ t.eq(features[0].attributes.OBJECTID, '109',
+ "Attribute OBJECTID contains the right value");
+
+ t.eq(features[0].type, 'AAA64',
+ "Parsed the layer name correctly");
+
+ var bounds = features[0].bounds;
+ t.ok(bounds instanceof OpenLayers.Bounds, "feature given a bounds");
+ t.eq(bounds.left.toFixed(3), "107397.266", "Bounds left parsed correctly");
+ t.eq(bounds.right.toFixed(3), "116568.188", "Bounds right parsed correctly");
+ t.eq(bounds.bottom.toFixed(3), "460681.063", "Bounds bottom parsed correctly");
+ t.eq(bounds.top.toFixed(3), "480609.250", "Bounds top parsed correctly");
+
+ // read 2 features from 2 layers
+ text =
+ '<?xml version="1.0" encoding="ISO-8859-1"?>' +
+ '<msGMLOutput ' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' xmlns:xlink="http://www.w3.org/1999/xlink"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'+
+ ' <AAA64_layer>' +
+ ' <AAA64_feature>' +
+ ' <gml:boundedBy>' +
+ ' <gml:Box srsName="EPSG:28992">' +
+ ' <gml:coordinates>129799.109000,467950.250000 133199.906000,468904.063000</gml:coordinates>' +
+ ' </gml:Box>' +
+ ' </gml:boundedBy>' +
+ ' <OBJECTID>287</OBJECTID>' +
+ ' <ROUTE>N403</ROUTE>' +
+ ' <ROUTE_CH>#N403</ROUTE_CH>' +
+ ' <COUNT>1</COUNT>' +
+ ' <BEHEERDER>P</BEHEERDER>' +
+ ' <LENGTH>4091.25</LENGTH>' +
+ ' <SHAPE>&lt;shape&gt;</SHAPE>' +
+ ' <SE_ANNO_CAD_DATA>&lt;null&gt;</SE_ANNO_CAD_DATA>' +
+ ' </AAA64_feature>' +
+ ' </AAA64_layer>' +
+ ' <AAA62_layer>' +
+ ' <AAA62_feature>' +
+ ' <gml:boundedBy>' +
+ ' <gml:Box srsName="EPSG:28992">' +
+ ' <gml:coordinates>129936.000000,468362.000000 131686.000000,473119.000000</gml:coordinates>' +
+ ' </gml:Box>' +
+ ' </gml:boundedBy>' +
+ ' <OBJECTID>1251</OBJECTID>' +
+ ' <VWK_ID>1515</VWK_ID>' +
+ ' <VWK_BEGDTM>00:00:00 01/01/1998</VWK_BEGDTM>' +
+ ' <VWJ_ID_BEG>1472</VWJ_ID_BEG>' +
+ ' <VWJ_ID_END>1309</VWJ_ID_END>' +
+ ' <VAKTYPE>D</VAKTYPE>' +
+ ' <VRT_CODE>227</VRT_CODE>' +
+ ' <VRT_NAAM>Vecht</VRT_NAAM>' +
+ ' <VWG_NR>2</VWG_NR>' +
+ ' <VWG_NAAM>Vecht</VWG_NAAM>' +
+ ' <BEGKM>18.25</BEGKM>' +
+ ' <ENDKM>23.995</ENDKM>' +
+ ' <LENGTH>5745.09</LENGTH>' +
+ ' <SHAPE>&lt;shape&gt;</SHAPE>' +
+ ' <SE_ANNO_CAD_DATA>&lt;null&gt;</SE_ANNO_CAD_DATA>' +
+ ' </AAA62_feature>' +
+ ' </AAA62_layer>' +
+ '</msGMLOutput>';
+
+ features = parser.read(text);
+
+ t.eq(features.length, 2,
+ "Parsed 2 features in total");
+
+ t.eq((features[0].type == features[1].type), false,
+ "The layer name differs for the two features");
+
+ text =
+ '<?xml version="1.0" encoding="ISO-8859-1"?>' +
+ '<msGMLOutput ' +
+ ' xmlns:gml="http://www.opengis.net/gml"' +
+ ' xmlns:xlink="http://www.w3.org/1999/xlink"' +
+ ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+ ' <wegbeheerderinfo_layer>' +
+ ' <wegbeheerderinfo_feature>' +
+ ' <gml:boundedBy>' +
+ ' <gml:Box srsName="EPSG:28992">' +
+ ' <gml:coordinates>105002.943000,490037.863000 105271.523000,490262.208000</gml:coordinates>' +
+ ' </gml:Box>' +
+ ' </gml:boundedBy>' +
+ ' <geometry>' +
+ ' <gml:MultiLineString srsName="EPSG:28992">' +
+ ' <gml:lineStringMember>' +
+ ' <gml:LineString>' +
+ ' <gml:coordinates>105270.164000,490262.208000 105098.274000,490258.040000 105028.045000,490089.576000 105002.943000,490048.851000 105049.666000,490037.863000 105271.523000,490064.957000 </gml:coordinates>' +
+ ' </gml:LineString>' +
+ ' </gml:lineStringMember>' +
+ ' </gml:MultiLineString>' +
+ ' </geometry>' +
+ ' <OGR_FID>203327</OGR_FID>' +
+ ' </wegbeheerderinfo_feature>' +
+ ' </wegbeheerderinfo_layer>' +
+ '</msGMLOutput>';
+
+ features = parser.read(text);
+
+ t.eq((features[0].geometry instanceof OpenLayers.Geometry.MultiLineString), true,
+ "Parsed geometry is of type multi line string");
+
+ }
+
+ function test_read_GMLFeatureInfoResponse(t) {
+ t.plan(4);
+
+ var parser = new OpenLayers.Format.WMSGetFeatureInfo();
+
+ // read Ionic response, see if parser falls back to GML format
+ // url used:
+ /* http://webservices.ionicsoft.com/ionicweb/wfs/BOSTON_ORA?service=WMS&request=GetFeatureInfo&layers=roads&version=1.1.1&bbox=-71.1,42.25,-71.05,42.3&width=500&height=500&format=image/png&SRS=EPSG:4326&styles=&x=174&y=252&query_layers=roads&info_format=application/vnd.ogc.gml */
+ var text =
+ "<?xml version='1.0' encoding='utf-8' ?>" +
+ ' <ogcwfs:FeatureCollection xsi:schemaLocation="http://www.ionicsoft.com/wfs http://webservices.ionicsoft.com/ionicweb/wfs/BOSTON_ORA?REQUEST=DescribeAllFeatureType&amp;SERVICE=WFS http://www.opengis.net/wfs http://webservices.ionicsoft.com/ionicweb/wfs/BOSTON_ORA/REQUEST/get/DATA/LPR/wfs/1.0.0/WFS-basic.xsd" xmlns:wfs="http://www.ionicsoft.com/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ogcwfs="http://www.opengis.net/wfs">' +
+ ' <gml:boundedBy>' +
+ ' <gml:Box srsName="EPSG:4326">' +
+ ' <gml:coordinates>-71.08301710045646,42.27320863544783 -71.08020014900377,42.27480054530114</gml:coordinates>' +
+ ' </gml:Box>' +
+ ' </gml:boundedBy>' +
+ ' <gml:featureMember>' +
+ ' <wfs:roads fid="roads.9453.0">' +
+ ' <wfs:FNODE_>8943.0</wfs:FNODE_>' +
+ ' <wfs:TNODE_>9070.0</wfs:TNODE_>' +
+ ' <wfs:LPOLY_>0.0</wfs:LPOLY_>' +
+ ' <wfs:RPOLY_>0.0</wfs:RPOLY_>' +
+ ' <wfs:LENGTH>306.875</wfs:LENGTH>' +
+ ' <wfs:MRD_>13109.0</wfs:MRD_>' +
+ ' <wfs:MRD_ID>9453.0</wfs:MRD_ID>' +
+ ' <wfs:TILE_NAME>126</wfs:TILE_NAME>' +
+ ' <wfs:COUNTYCODE>M</wfs:COUNTYCODE>' +
+ ' <wfs:SERIAL_NUM>26000.0</wfs:SERIAL_NUM>' +
+ ' <wfs:CLASS>5.0</wfs:CLASS>' +
+ ' <wfs:ADMIN_TYPE>0.0</wfs:ADMIN_TYPE>' +
+ ' <wfs:ALTRT1TYPE>0.0</wfs:ALTRT1TYPE>' +
+ ' <wfs:STREETNAME>DOCTOR MARY MOORE BEATTY CIRCLE</wfs:STREETNAME>' +
+ ' <wfs:CSN>M 26000</wfs:CSN>' +
+ ' <wfs:GEOMETRY>' +
+ ' <gml:LineString srsName="EPSG:4326">' +
+ ' <gml:coordinates>-71.08300668868151,42.27480054530114 -71.08155305289881,42.27452010256956 -71.08021063085208,42.27320863544783</gml:coordinates>' +
+ ' </gml:LineString>' +
+ ' </wfs:GEOMETRY>' +
+ ' </wfs:roads>' +
+ ' </gml:featureMember>' +
+ ' </ogcwfs:FeatureCollection>';
+
+ var features = parser.read(text);
+
+ t.eq(features.length, 1,
+ "Parsing GML GetFeatureInfo response from Ionic succesfull");
+
+ t.eq(features[0].attributes.TILE_NAME, '126',
+ "Attribute TILE_NAME contains the right value");
+
+ // read Geoserver response
+ // taken from:
+/* http://demo.opengeo.org/geoserver/wms?service=WMS&request=GetFeatureInfo&layers=opengeo:roads&query_layers=opengeo:roads&format=image/png&version=1.1.1&styles=&bbox=-103.9,44.4,-103.7,44.5&srs=EPSG:4326&width=500&height=500&x=158&y=98&info_format=application/vnd.ogc.gml*/
+
+ text = '<?xml version="1.0" encoding="UTF-8"?><wfs:FeatureCollection xmlns="http://www.opengis.net/wfs" xmlns:wfs="http://www.opengis.net/wfs" xmlns:opengeo="http://opengeo.org" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://opengeo.org http://demo.opengeo.org:80/geoserver/wfs?service=WFS&amp;version=1.0.0&amp;request=DescribeFeatureType&amp;typeName=opengeo:roads http://www.opengis.net/wfs http://demo.opengeo.org:80/geoserver/schemas/wfs/1.0.0/WFS-basic.xsd"><gml:boundedBy><gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#26713"><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">591943.9375,4925605 593045.625,4925845</gml:coordinates></gml:Box></gml:boundedBy><gml:featureMember><opengeo:roads fid="roads.90"><opengeo:cat>3</opengeo:cat><opengeo:label>secondary highway, hard surface</opengeo:label><opengeo:the_geom><gml:MultiLineString srsName="http://www.opengis.net/gml/srs/epsg.xml#26713"><gml:lineStringMember><gml:LineString><gml:coordinates xmlns:gml="http://www.opengis.net/gml" decimal="." cs="," ts=" ">593045.60746465,4925605.0059156 593024.32382915,4925606.79305411 592907.54863574,4925624.85647524 592687.35111096,4925670.76834012 592430.76279218,4925678.79393165 592285.97636109,4925715.70811767 592173.39165655,4925761.83511156 592071.1753393,4925793.95523514 591985.96972625,4925831.59842486 591943.98769455,4925844.93220071</gml:coordinates></gml:LineString></gml:lineStringMember></gml:MultiLineString></opengeo:the_geom></opengeo:roads></gml:featureMember></wfs:FeatureCollection>';
+
+ features = parser.read(text);
+
+ t.eq(features.length, 1,
+ "Parsing GML GetFeatureInfo response from Geoserver succesfull");
+
+ t.eq(features[0].attributes.cat, '3',
+ "Attribute cat contains the right value");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMTSCapabilities.html b/misc/openlayers/tests/Format/WMTSCapabilities.html
new file mode 100644
index 0000000..e7a51a3
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMTSCapabilities.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+
+ t.plan(1);
+ var format = new OpenLayers.Format.WMTSCapabilities({
+ version: "foo"
+ });
+ t.eq(format.version, "foo", "version set on format");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WMTSCapabilities/v1_0_0.html b/misc/openlayers/tests/Format/WMTSCapabilities/v1_0_0.html
new file mode 100644
index 0000000..f4fadeb
--- /dev/null
+++ b/misc/openlayers/tests/Format/WMTSCapabilities/v1_0_0.html
@@ -0,0 +1,1042 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_ows(t) {
+ t.plan(20);
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+ // ows:ServiceIdentification
+ var serviceIdentification = obj.serviceIdentification;
+ t.eq(serviceIdentification.title, "Web Map Tile Service", "ows:ServiceIdentification title is correct");
+ t.eq(serviceIdentification.serviceTypeVersion, "1.0.0", "ows:ServiceIdentification serviceTypeVersion is correct");
+ t.eq(serviceIdentification.serviceType.value, "OGC WMTS", "ows:ServiceIdentification serviceType is correct");
+
+ // ows:ServiceProvider
+ var serviceProvider = obj.serviceProvider;
+ t.eq(serviceProvider.providerName, "MiraMon", "ows:ServiceProvider providerName is correct");
+ t.eq(serviceProvider.providerSite, "http://www.creaf.uab.es/miramon", "ows:ServiceProvider providerSite is correct");
+ t.eq(serviceProvider.serviceContact.individualName, "Joan Maso Pau", "ows:ServiceProvider individualName is correct");
+ t.eq(serviceProvider.serviceContact.positionName, "Senior Software Engineer", "ows:ServiceProvider positionName is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.address.administrativeArea, "Barcelona", "ows:ServiceProvider address administrativeArea is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.address.city, "Bellaterra", "ows:ServiceProvider address city is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.address.country, "Spain", "ows:ServiceProvider address country is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.address.deliveryPoint, "Fac Ciencies UAB", "ows:ServiceProvider address deliveryPoint is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.address.electronicMailAddress, "joan.maso@uab.es", "ows:ServiceProvider address electronicMailAddress is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.address.postalCode, "08193", "ows:ServiceProvider address postalCode is correct");
+ t.eq(serviceProvider.serviceContact.contactInfo.phone.voice, "+34 93 581 1312", "ows:ServiceProvider phone voice is correct");
+
+ // ows:OperationsMetadata
+ var operationsMetadata = obj.operationsMetadata;
+ t.eq(operationsMetadata.GetCapabilities.dcp.http.get[0].url, "http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?", "ows:OperationsMetadata GetCapabilities url is correct");
+ t.eq(operationsMetadata.GetCapabilities.dcp.http.get[0].constraints.GetEncoding.allowedValues,
+ {'KVP': true},
+ "ows:OperationsMetadata GetCapabilities Constraints Get is correct");
+ t.eq(operationsMetadata.GetFeatureInfo.dcp.http.get[0].url, "http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?", "ows:OperationsMetadata GetFeatureInfo url is correct");
+ t.eq(operationsMetadata.GetFeatureInfo.dcp.http.get[0].constraints,
+ undefined,
+ "ows:OperationsMetadata GetFeatureInfo Constraints Get is correct");
+ t.eq(operationsMetadata.GetTile.dcp.http.get[0].url, "http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?", "ows:OperationsMetadata GetTile url is correct");
+ t.eq(operationsMetadata.GetTile.dcp.http.get[0].constraints,
+ undefined,
+ "ows:OperationsMetadata GetTile Constraints Get is correct");
+ }
+
+ function test_layers(t) {
+ t.plan(43);
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+ var contents = obj.contents;
+
+ var numOfLayers = contents.layers.length;
+ t.eq(numOfLayers, 1, "correct count of layers");
+
+ var layer = contents.layers[0];
+ t.eq(layer['abstract'], "Coastline/shorelines (BA010)", "layer abstract is correct");
+ t.eq(layer.identifier, "coastlines", "layer identifier is correct");
+ t.eq(layer.title, "Coastlines", "layer title is correct");
+
+ var numOfFormats = layer.formats.length;
+ t.eq(numOfFormats, 2, "correct count of formats");
+ t.eq(layer.formats[0], "image/png", "format image/png is correct");
+ t.eq(layer.formats[1], "image/gif", "format image/gif is correct");
+
+ var numOfStyles = layer.styles.length;
+ t.eq(numOfStyles, 2, "correct count of styles");
+ t.eq(layer.styles[0].identifier, "DarkBlue", "style 0 identifier is correct");
+ t.eq(layer.styles[0].isDefault, true, "style 0 isDefault is correct");
+ t.eq(layer.styles[0].title, "Dark Blue", "style 0 title is correct");
+ t.eq(layer.styles[0].legend.href, "http://www.miramon.uab.es/wmts/Coastlines/coastlines_darkBlue.png", "style 0 legend href is correct");
+ t.eq(layer.styles[0].legend.format, "image/png", "style 0 legend format is correct");
+ t.eq(layer.styles[1].identifier, "thickAndRed", "style 1 identifier is correct");
+ t.ok(!layer.styles[1].isDefault, "style 1 isDefault is correct");
+ t.eq(layer.styles[1].title, "Thick And Red", "style 1 title is correct");
+ t.eq(layer.styles[1].legend, undefined, "style 1 legend is not set");
+ //t.eq(layer.styles[1].abstract, "Specify this style if you want your maps to have thick red coastlines. ", "style 1 abstract is correct");
+
+ t.eq(layer.tileMatrixSetLinks.length, 1, "correct count of tileMatrixSetLinks");
+ t.eq(layer.tileMatrixSetLinks[0].tileMatrixSet, "BigWorld", "tileMatrixSet is correct");
+
+ var wgs84Bbox = layer.bounds;
+ t.ok(wgs84Bbox instanceof OpenLayers.Bounds, "wgs84BoudingBox instance of OpenLayers.Bounds");
+ t.eq(wgs84Bbox.left, -180.0, "wgs84BoudingBox left is correct");
+ t.eq(wgs84Bbox.right, 180.0, "wgs84BoudingBox right is correct");
+ t.eq(wgs84Bbox.bottom, -90.0, "wgs84BoudingBox bottom is correct");
+ t.eq(wgs84Bbox.top, 90.0, "wgs84BoudingBox top is correct");
+
+ t.eq(layer.resourceUrl.tile.format, "image/png", "resourceUrl.tile.format is correct");
+ t.eq(layer.resourceUrl.tile.template, "http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png",
+ "resourceUrl.tile.template is correct");
+
+ t.eq(layer.resourceUrl.FeatureInfo.format, "application/gml+xml; version=3.1", "resourceUrl.FeatureInfo.format is correct");
+ t.eq(layer.resourceUrl.FeatureInfo.template, "http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml",
+ "resourceUrl.FeatureInfo.template is correct");
+
+ t.eq(layer.resourceUrls[0].format, "image/png", "resourceUrls[0].format is correct");
+ t.eq(layer.resourceUrls[0].resourceType, "tile", "resourceUrls[0].resourceType is correct");
+ t.eq(layer.resourceUrls[0].template, "http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png",
+ "resourceUrls[0].template is correct");
+
+ t.eq(layer.resourceUrls[1].format, "application/gml+xml; version=3.1", "resourceUrls[0].format is correct");
+ t.eq(layer.resourceUrls[1].resourceType, "FeatureInfo", "resourceUrls[0].resourceType is correct");
+ t.eq(layer.resourceUrls[1].template, "http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml",
+ "resourceUrls[0].template is correct");
+
+ var dimensions = layer.dimensions;
+ t.eq(dimensions.length, 1, "correct count of dimensions");
+ t.eq(dimensions[0].title, "Time", "first dimension title is correct");
+ t.eq(dimensions[0]['abstract'], "Monthly datasets", "first dimension abstract is correct");
+ t.eq(dimensions[0].identifier, "TIME", "first dimension identifier is correct");
+ t.eq(dimensions[0]['default'], "default", "first dimension default is correct");
+ t.eq(dimensions[0].values.length, 3, "first dimension has correct count of values");
+ t.eq(dimensions[0].values[0], "2007-05", "first value is correct");
+ t.eq(dimensions[0].values[1], "2007-06", "second value is correct");
+ t.eq(dimensions[0].values[2], "2007-07", "third value is correct");
+ }
+
+ function test_tileMatrixSets(t) {
+ t.plan(19);
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+
+ var tileMatrixSets = obj.contents.tileMatrixSets;
+ t.ok(tileMatrixSets['BigWorld'], "tileMatrixSets 'BigWorld' found");
+ var bigWorld = tileMatrixSets['BigWorld'];
+ t.eq(bigWorld.identifier, "BigWorld", "tileMatrixSets identifier is correct");
+ t.eq(bigWorld.matrixIds.length, 2, "tileMatrix count is correct");
+ t.eq(bigWorld.matrixIds[0].identifier, "1e6", "tileMatrix 0 identifier is correct");
+ t.eq(bigWorld.matrixIds[0].matrixHeight, 50000, "tileMatrix 0 matrixHeight is correct");
+ t.eq(bigWorld.matrixIds[0].matrixWidth, 60000, "tileMatrix 0 matrixWidth is correct");
+ t.eq(bigWorld.matrixIds[0].scaleDenominator, 1000000, "tileMatrix 0 scaleDenominator is correct");
+ t.eq(bigWorld.matrixIds[0].tileWidth, 256, "tileMatrix 0 tileWidth is correct");
+ t.eq(bigWorld.matrixIds[0].tileHeight, 256, "tileMatrix 0 tileHeight is correct");
+ t.eq(bigWorld.matrixIds[0].topLeftCorner.lon, -180, "tileMatrix 0 topLeftCorner.lon is correct");
+ t.eq(bigWorld.matrixIds[0].topLeftCorner.lat, 84, "tileMatrix 0 topLeftCorner.lat is correct");
+
+ t.eq(bigWorld.matrixIds[1].identifier, "2.5e6", "tileMatrix 1 identifier is correct");
+ t.eq(bigWorld.matrixIds[1].matrixHeight, 7000, "tileMatrix 1 matrixHeight is correct");
+ t.eq(bigWorld.matrixIds[1].matrixWidth, 9000, "tileMatrix 1 matrixWidth is correct");
+ t.eq(bigWorld.matrixIds[1].scaleDenominator, 2500000, "tileMatrix 1 scaleDenominator is correct");
+ t.eq(bigWorld.matrixIds[1].tileWidth, 256, "tileMatrix 1 tileWidth is correct");
+ t.eq(bigWorld.matrixIds[1].tileHeight, 256, "tileMatrix 1 tileHeight is correct");
+ t.eq(bigWorld.matrixIds[1].topLeftCorner.lon, -180, "tileMatrix 1 topLeftCorner.lon is correct");
+ t.eq(bigWorld.matrixIds[1].topLeftCorner.lat, 84, "tileMatrix 1 topLeftCorner.lat is correct");
+ }
+
+ function test_createLayer(t) {
+ t.plan(43);
+
+ var format = new OpenLayers.Format.WMTSCapabilities();
+
+ var xml = document.getElementById("ogcsample").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+
+ var caps = format.read(doc);
+ var layer;
+
+ var success = true;
+ try {
+ // incomplete config (missing layer)
+ layer = format.createLayer(caps, {
+ });
+ } catch (err) {
+ success = false;
+ }
+ t.ok(!success, "createLayer throws error if provided incomplete layer config");
+
+ // bogus layer identifier
+ try {
+ layer = format.createLayer(caps, {
+ layer: "foo",
+ matrixSet: "BigWorld"
+ });
+ } catch (err) {
+ success = false;
+ }
+ t.ok(!success, "createLayer returns undefined given bad layer identifier");
+
+ // bogus matrixSet identifier
+ try {
+ layer = format.createLayer(caps, {
+ layer: "coastlines",
+ matrixSet: "TheWorld"
+ });
+ } catch (err) {
+ success = false;
+ }
+ t.ok(!success, "createLayer returns undefined given bad matrixSet identifier");
+
+ layer = format.createLayer(caps, {
+ layer: "coastlines",
+ matrixSet: "BigWorld"
+ });
+ t.ok(layer instanceof OpenLayers.Layer.WMTS, "correct instance");
+
+ // autodetect matrixSet
+ layer = format.createLayer(caps, {
+ layer: "coastlines"
+ });
+ t.ok(layer instanceof OpenLayers.Layer.WMTS, "correct instance, with autodetected matrixSet");
+
+ t.eq(layer.matrixIds.length, 2, "correct matrixIds length");
+ t.eq(layer.name, "Coastlines", "correct layer title");
+ t.eq(layer.style, "DarkBlue", "correct style identifier");
+ t.eq(layer.requestEncoding, "KVP", "correct requestEncoding");
+
+ xml = document.getElementById("restsample").firstChild.nodeValue;
+ doc = new OpenLayers.Format.XML().read(xml);
+ caps = format.read(doc);
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781"
+ });
+ t.ok(layer instanceof OpenLayers.Layer.WMTS, "correct instance");
+ t.eq(layer.url[0], "http://wmts.geo.admin.ch/1.0.0/ch.are.agglomerationen_isolierte_staedte-2000/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png", "correct url");
+ t.eq(layer.url[1], "http://wmts1.geo.admin.ch/1.0.0/ch.are.agglomerationen_isolierte_staedte-2000/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png", "correct url");
+ t.eq(layer.matrixIds.length, 3, "correct matrixIds length");
+ t.eq(layer.requestEncoding, "REST", "correct requestEncoding");
+ t.eq(layer.name, "Agglomérations et villes isolées", "correct layer title");
+ t.eq(layer.style, "ch.are.agglomerationen_isolierte_staedte-2000", "correct style identifier");
+ t.eq(layer.projection.getCode(), "EPSG:21781", "correct projection");
+ t.eq(layer.units, "m", "correct untis");
+ t.ok(layer.serverResolutions === layer.resolutions, "serverResolutions set");
+ t.eq(layer.resolutions.length, 3, "correct resolutions length");
+ t.ok((layer.resolutions[0] - 4000) < 1, "correct first resolution");
+ t.eq(layer.dimensions.length, 1, "correct dimensions length");
+ t.eq(layer.dimensions[0], "Time", "correct dimensions");
+ t.eq(layer.params['TIME'], "20090101", "correct params");
+
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ style: "toto",
+ params: {"Time": "2012"}
+ });
+ t.eq(layer.matrixIds.length, 3, "correct matrixIds length");
+ t.eq(layer.style, "toto", "correct style identifier");
+ t.eq(layer.dimensions.length, 1, "correct dimensions length");
+ t.eq(layer.dimensions[0], "Time", "correct dimensions");
+ t.eq(layer.params['TIME'], "2012", "correct params");
+
+ // test projection and units
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ units: 'degrees'
+ });
+ t.eq(layer.units, "degrees", "correct units");
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ projection: "EPSG:4326"
+ });
+ t.eq(layer.projection.getCode(), "EPSG:4326", "correct projection");
+ t.eq(layer.units, "degrees", "correct units");
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ projection: "EPSG:4326",
+ units: 'm'
+ });
+ t.eq(layer.projection.getCode(), "EPSG:4326", "correct projection");
+ t.eq(layer.units, "m", "correct units");
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ projection: "EPSG:900913",
+ units: 'degrees'
+ });
+ t.eq(layer.projection.getCode(), "EPSG:900913", "correct projection");
+ t.eq(layer.units, "degrees", "correct units");
+
+
+ // test get the right url #608/3
+ xml = document.getElementById("multi-getile-1").firstChild.nodeValue;
+ doc = new OpenLayers.Format.XML().read(xml);
+ caps = format.read(doc);
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ requestEncoding: 'REST'
+ });
+ t.eq(layer.url[0], "http://wmts.geo.admin.ch/rest", "correct rest url 1");
+ t.eq(layer.url[1], "http://wmts1.geo.admin.ch/rest", "correct rest url 1");
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ requestEncoding: 'KVP'
+ });
+ t.eq(layer.url[0], "http://wmts.geo.admin.ch/kvp", "correct kvp url 2");
+ t.eq(layer.url[1], "http://wmts1.geo.admin.ch/kvp", "correct kvp url 2");
+ xml = document.getElementById("multi-getile-2").firstChild.nodeValue;
+ doc = new OpenLayers.Format.XML().read(xml);
+ caps = format.read(doc);
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ requestEncoding: 'REST'
+ });
+ t.eq(layer.url[0], "http://wmts.geo.admin.ch/rest", "correct rest url 2");
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781",
+ requestEncoding: 'KVP'
+ });
+ t.eq(layer.url[0], "http://wmts.geo.admin.ch/kvp", "correct kvp url 2");
+
+ // test RESTfull
+ xml = document.getElementById("arcgis").firstChild.nodeValue;
+ doc = new OpenLayers.Format.XML().read(xml);
+ caps = format.read(doc);
+ layer = format.createLayer(caps, {
+ layer: "WorldTimeZones"
+ });
+ t.eq(layer.requestEncoding, "REST", "correct requestEncoding (in RESTfull)");
+ }
+
+ function test_parse_projection(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.WMTSCapabilities();
+
+ var xml = document.getElementById("restsample-alternate-proj1").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var caps = format.read(doc);
+ var layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781"
+ });
+ t.eq(layer.projection.getCode(), "EPSG:21781", "correct projection");
+
+ xml = document.getElementById("restsample-alternate-proj2").firstChild.nodeValue;
+ doc = new OpenLayers.Format.XML().read(xml);
+ caps = format.read(doc);
+ layer = format.createLayer(caps, {
+ layer: "ch.are.agglomerationen_isolierte_staedte-2000",
+ matrixSet: "21781"
+ });
+ t.eq(layer.projection.getCode(), "EPSG:21781", "correct projection");
+ }
+ </script>
+</head>
+<body>
+
+<!--
+OGC example below taken from
+http://schemas.opengis.net/wmts/1.0/examples/wmtsGetCapabilities_response.xml
+-->
+<div id="ogcsample"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:ServiceIdentification>
+ <ows:Title>Web Map Tile Service</ows:Title>
+ <ows:Abstract>Service that contrains the map access interface to some TileMatrixSets</ows:Abstract>
+ <ows:Keywords>
+ <ows:Keyword>tile</ows:Keyword>
+ <ows:Keyword>tile matrix set</ows:Keyword>
+ <ows:Keyword>map</ows:Keyword>
+ </ows:Keywords>
+ <ows:ServiceType>OGC WMTS</ows:ServiceType>
+ <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
+ <ows:Fees>none</ows:Fees>
+ <ows:AccessConstraints>none</ows:AccessConstraints>
+ </ows:ServiceIdentification>
+ <ows:ServiceProvider>
+ <ows:ProviderName>MiraMon</ows:ProviderName>
+ <ows:ProviderSite xlink:href="http://www.creaf.uab.es/miramon"/>
+ <ows:ServiceContact>
+ <ows:IndividualName>Joan Maso Pau</ows:IndividualName>
+ <ows:PositionName>Senior Software Engineer</ows:PositionName>
+ <ows:ContactInfo>
+ <ows:Phone>
+ <ows:Voice>+34 93 581 1312</ows:Voice>
+ <ows:Facsimile>+34 93 581 4151</ows:Facsimile>
+ </ows:Phone>
+ <ows:Address>
+ <ows:DeliveryPoint>Fac Ciencies UAB</ows:DeliveryPoint>
+ <ows:City>Bellaterra</ows:City>
+ <ows:AdministrativeArea>Barcelona</ows:AdministrativeArea>
+ <ows:PostalCode>08193</ows:PostalCode>
+ <ows:Country>Spain</ows:Country>
+ <ows:ElectronicMailAddress>joan.maso@uab.es</ows:ElectronicMailAddress>
+ </ows:Address>
+ </ows:ContactInfo>
+ </ows:ServiceContact>
+ </ows:ServiceProvider>
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?"/>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetFeatureInfo">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://www.miramon.uab.es/cgi-bin/MiraMon5_0.cgi?"/>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>Coastlines</ows:Title>
+ <ows:Abstract>Coastline/shorelines (BA010)</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>-180 -90</ows:LowerCorner>
+ <ows:UpperCorner>180 90</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>coastlines</ows:Identifier>
+ <ResourceURL format="image/png" resourceType="tile"
+ template="http://www.example.com/wmts/coastlines/{TileMatrix}/{TileRow}/{TileCol}.png" />
+ <ResourceURL format="application/gml+xml; version=3.1" resourceType="FeatureInfo"
+ template="http://www.example.com/wmts/coastlines/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}/{J}/{I}.xml" />
+ <Style isDefault="true">
+ <ows:Title>Dark Blue</ows:Title>
+ <ows:Identifier>DarkBlue</ows:Identifier>
+ <LegendURL format="image/png" xlink:href="http://www.miramon.uab.es/wmts/Coastlines/coastlines_darkBlue.png"/>
+ </Style>
+ <Style>
+ <ows:Title>Thick And Red</ows:Title>
+ <ows:Abstract>Specify this style if you want your maps to have thick red coastlines.
+ </ows:Abstract>
+ <ows:Identifier>thickAndRed</ows:Identifier>
+ </Style>
+ <Format>image/png</Format>
+ <Format>image/gif</Format>
+ <Dimension>
+ <ows:Title>Time</ows:Title>
+ <ows:Abstract>Monthly datasets</ows:Abstract>
+ <ows:Identifier>TIME</ows:Identifier>
+ <Value>2007-05</Value>
+ <Value>2007-06</Value>
+ <Value>2007-07</Value>
+ <Default>default</Default>
+ </Dimension>
+ <TileMatrixSetLink>
+ <TileMatrixSet>BigWorld</TileMatrixSet>
+ </TileMatrixSetLink>
+ </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>BigWorld</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:OGC:1.3:CRS84</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>1e6</ows:Identifier>
+ <ScaleDenominator>1e6</ScaleDenominator>
+ <TopLeftCorner>-180 84</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>60000</MatrixWidth>
+ <MatrixHeight>50000</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>2.5e6</ows:Identifier>
+ <ScaleDenominator>2.5e6</ScaleDenominator>
+ <TopLeftCorner>-180 84</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>9000</MatrixWidth>
+ <MatrixHeight>7000</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <Themes>
+ <Theme>
+ <ows:Title>Foundation</ows:Title>
+ <ows:Abstract>"Digital Chart Of The World" data</ows:Abstract>
+ <ows:Identifier>Foundation</ows:Identifier>
+ <Theme>
+ <ows:Title>Boundaries</ows:Title>
+ <ows:Identifier>Boundaries</ows:Identifier>
+ <LayerRef>coastlines</LayerRef>
+ <LayerRef>politicalBoundaries</LayerRef>
+ <LayerRef>depthContours</LayerRef>
+ </Theme>
+ <Theme>
+ <ows:Title>Transportation</ows:Title>
+ <ows:Identifier>Transportation</ows:Identifier>
+ <LayerRef>roads</LayerRef>
+ <LayerRef>railroads</LayerRef>
+ <LayerRef>airports</LayerRef>
+ </Theme>
+ </Theme>
+ <Theme>
+ <ows:Title>World Geology</ows:Title>
+ <ows:Identifier>World Geology</ows:Identifier>
+ <LayerRef>worldAgeRockType</LayerRef>
+ <LayerRef>worldFaultLines</LayerRef>
+ <LayerRef>felsicMagmatic</LayerRef>
+ <LayerRef>maficMagmatic</LayerRef>
+ </Theme>
+ </Themes>
+</Capabilities>
+--></div>
+
+<div id="restsample"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:ServiceIdentification>
+ <ows:Title>Federal Geodata Infrastructure of Switzerland</ows:Title>
+ <ows:Abstract>Some Geodata are subject to license and fees</ows:Abstract>
+ <ows:Keywords>
+ <ows:Keyword>FGDI</ows:Keyword>
+ <ows:Keyword>Pixelkarte</ows:Keyword>
+ <ows:Keyword>Switzerland</ows:Keyword>
+ </ows:Keywords>
+ <ows:ServiceType>OGC WMTS</ows:ServiceType>
+ <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
+ <ows:Fees>yes</ows:Fees>
+ <ows:AccessConstraints>license</ows:AccessConstraints>
+ </ows:ServiceIdentification>
+ <ows:ServiceProvider>
+ <ows:ProviderName>swisstopo</ows:ProviderName>
+ <ows:ProviderSite xlink:href="http://www.swisstopo.admin.ch"/>
+ <ows:ServiceContact>
+ <ows:IndividualName>David Oesch</ows:IndividualName>
+ <ows:PositionName></ows:PositionName>
+ <ows:ContactInfo>
+ <ows:Phone>
+ <ows:Voice>+41 (0)31 / 963 21 11</ows:Voice>
+ <ows:Facsimile>+41 (0)31 / 963 24 59</ows:Facsimile>
+ </ows:Phone>
+ <ows:Address>
+ <ows:DeliveryPoint>swisstopo</ows:DeliveryPoint>
+ <ows:City>Bern</ows:City>
+ <ows:AdministrativeArea>BE</ows:AdministrativeArea>
+ <ows:PostalCode>3084</ows:PostalCode>
+ <ows:Country>Switzerland</ows:Country>
+ <ows:ElectronicMailAddress/>
+ </ows:Address>
+ </ows:ContactInfo>
+ </ows:ServiceContact>
+ </ows:ServiceProvider>
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Abstract>Les agglomérations et villes isolées (communes non rattachées à une agglomération et comptant au moins 10`000 habitants) font partie des régions d’analyse de la statistique suisse. Ce niveau géographique est défini depuis plus de 100 ans, afin de mesurer l’urbanisation, phénomène fondamental structurant l’organisation du territoire. Sa fonction principale est de permettre une comparaison spatiale entre des espaces urbains inégalement délimités sur le plan institutionnel. Une version ancienne est appliquée pour la première fois en 1930, puis révisée en 1984 et 1990, toujours sur la base des recensements de la population. La version actuelle classe les 2896 communes de Suisse (état 2000) selon leur appartenance ou pas à une agglomération ou ville isolée en fonction de critères statistiques (Etat et évolution de la population, lien de continuité de la zone bâtie, rapport entre population active occupée et population résidante, structure économique et flux de pendulaires). Les agglomérations et les villes isolées forment l`espace urbain, les territoires restant l`espace rural. La définition des agglomérations de l’OFS n’a pas valeur d’obligation légale.</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>5.140242 45.398181</ows:LowerCorner>
+ <ows:UpperCorner>11.47757 48.230651</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <ows:Metadata xlink:href="http://www.swisstopo.admin.ch/SITiled/world/AdminBoundaries/metadata.htm"/>
+ <Style>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <LegendURL format="image/png" xlink:href="http://api.geo.admin.ch/legend/ch.are.agglomerationen_isolierte_staedte-2000_fr.png" />
+ </Style>
+ <Format>image/png</Format>
+ <Dimension>
+ <ows:Identifier>Time</ows:Identifier>
+ <Default>20090101</Default>
+ <Value>20090101</Value>
+ </Dimension>
+ <TileMatrixSetLink>
+ <TileMatrixSet>21781</TileMatrixSet>
+ </TileMatrixSetLink>
+ <ResourceURL format="image/png" resourceType="tile" template="http://wmts.geo.admin.ch/1.0.0/ch.are.agglomerationen_isolierte_staedte-2000/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png"/>
+ <ResourceURL format="image/png" resourceType="tile" template="http://wmts1.geo.admin.ch/1.0.0/ch.are.agglomerationen_isolierte_staedte-2000/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png"/>
+ </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>21781</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::21781</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>0</ows:Identifier>
+ <ScaleDenominator>14285750.5715</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>8</ows:Identifier>
+ <ScaleDenominator>7142875.28575</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>12</ows:Identifier>
+ <ScaleDenominator>3571437.64288</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <ServiceMetadataURL xlink:href="http://www.opengis.uab.es/SITiled/world/1.0.0/WMTSCapabilities.xml"/>
+</Capabilities>
+--></div>
+
+<div id="restsample-alternate-proj1"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Abstract>Les agglomérations et villes isolées (communes non rattachées à une agglomération et comptant au moins 10`000 habitants) font partie des régions d’analyse de la statistique suisse. Ce niveau géographique est défini depuis plus de 100 ans, afin de mesurer l’urbanisation, phénomène fondamental structurant l’organisation du territoire. Sa fonction principale est de permettre une comparaison spatiale entre des espaces urbains inégalement délimités sur le plan institutionnel. Une version ancienne est appliquée pour la première fois en 1930, puis révisée en 1984 et 1990, toujours sur la base des recensements de la population. La version actuelle classe les 2896 communes de Suisse (état 2000) selon leur appartenance ou pas à une agglomération ou ville isolée en fonction de critères statistiques (Etat et évolution de la population, lien de continuité de la zone bâtie, rapport entre population active occupée et population résidante, structure économique et flux de pendulaires). Les agglomérations et les villes isolées forment l`espace urbain, les territoires restant l`espace rural. La définition des agglomérations de l’OFS n’a pas valeur d’obligation légale.</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>5.140242 45.398181</ows:LowerCorner>
+ <ows:UpperCorner>11.47757 48.230651</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <ows:Metadata xlink:href="http://www.swisstopo.admin.ch/SITiled/world/AdminBoundaries/metadata.htm"/>
+ <Style>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <LegendURL format="image/png" xlink:href="http://api.geo.admin.ch/legend/ch.are.agglomerationen_isolierte_staedte-2000_fr.png" />
+ </Style>
+ <Format>image/png</Format>
+ <Dimension>
+ <ows:Identifier>Time</ows:Identifier>
+ <Default>20090101</Default>
+ <Value>20090101</Value>
+ </Dimension>
+ <TileMatrixSetLink>
+ <TileMatrixSet>21781</TileMatrixSet>
+ </TileMatrixSetLink>
+ <ResourceURL format="image/png" resourceType="tile" template="http://wmts.geo.admin.ch/1.0.0/ch.are.agglomerationen_isolierte_staedte-2000/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png"/>
+ </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>21781</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG:21781</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>0</ows:Identifier>
+ <ScaleDenominator>14285750.5715</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <ServiceMetadataURL xlink:href="http://www.opengis.uab.es/SITiled/world/1.0.0/WMTSCapabilities.xml"/>
+</Capabilities>
+--></div>
+
+<div id="restsample-alternate-proj2"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Abstract>Les agglomérations et villes isolées (communes non rattachées à une agglomération et comptant au moins 10`000 habitants) font partie des régions d’analyse de la statistique suisse. Ce niveau géographique est défini depuis plus de 100 ans, afin de mesurer l’urbanisation, phénomène fondamental structurant l’organisation du territoire. Sa fonction principale est de permettre une comparaison spatiale entre des espaces urbains inégalement délimités sur le plan institutionnel. Une version ancienne est appliquée pour la première fois en 1930, puis révisée en 1984 et 1990, toujours sur la base des recensements de la population. La version actuelle classe les 2896 communes de Suisse (état 2000) selon leur appartenance ou pas à une agglomération ou ville isolée en fonction de critères statistiques (Etat et évolution de la population, lien de continuité de la zone bâtie, rapport entre population active occupée et population résidante, structure économique et flux de pendulaires). Les agglomérations et les villes isolées forment l`espace urbain, les territoires restant l`espace rural. La définition des agglomérations de l’OFS n’a pas valeur d’obligation légale.</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>5.140242 45.398181</ows:LowerCorner>
+ <ows:UpperCorner>11.47757 48.230651</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <ows:Metadata xlink:href="http://www.swisstopo.admin.ch/SITiled/world/AdminBoundaries/metadata.htm"/>
+ <Style>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <LegendURL format="image/png" xlink:href="http://api.geo.admin.ch/legend/ch.are.agglomerationen_isolierte_staedte-2000_fr.png" />
+ </Style>
+ <Format>image/png</Format>
+ <Dimension>
+ <ows:Identifier>Time</ows:Identifier>
+ <Default>20090101</Default>
+ <Value>20090101</Value>
+ </Dimension>
+ <TileMatrixSetLink>
+ <TileMatrixSet>21781</TileMatrixSet>
+ </TileMatrixSetLink>
+ <ResourceURL format="image/png" resourceType="tile" template="http://wmts.geo.admin.ch/1.0.0/ch.are.agglomerationen_isolierte_staedte-2000/default/{Time}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png"/>
+ </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>21781</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG:1.0:21781</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>0</ows:Identifier>
+ <ScaleDenominator>14285750.5715</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <ServiceMetadataURL xlink:href="http://www.opengis.uab.es/SITiled/world/1.0.0/WMTSCapabilities.xml"/>
+</Capabilities>
+--></div>
+
+<div id="multi-getile-1"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/rest">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ <ows:Get xlink:href="http://wmts1.geo.admin.ch/rest">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/kvp">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ <ows:Get xlink:href="http://wmts1.geo.admin.ch/kvp">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Abstract>Les agglomérations et villes isolées (communes non rattachées à une agglomération et comptant au moins 10`000 habitants) font partie des régions d’analyse de la statistique suisse. Ce niveau géographique est défini depuis plus de 100 ans, afin de mesurer l’urbanisation, phénomène fondamental structurant l’organisation du territoire. Sa fonction principale est de permettre une comparaison spatiale entre des espaces urbains inégalement délimités sur le plan institutionnel. Une version ancienne est appliquée pour la première fois en 1930, puis révisée en 1984 et 1990, toujours sur la base des recensements de la population. La version actuelle classe les 2896 communes de Suisse (état 2000) selon leur appartenance ou pas à une agglomération ou ville isolée en fonction de critères statistiques (Etat et évolution de la population, lien de continuité de la zone bâtie, rapport entre population active occupée et population résidante, structure économique et flux de pendulaires). Les agglomérations et les villes isolées forment l`espace urbain, les territoires restant l`espace rural. La définition des agglomérations de l’OFS n’a pas valeur d’obligation légale.</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>5.140242 45.398181</ows:LowerCorner>
+ <ows:UpperCorner>11.47757 48.230651</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <ows:Metadata xlink:href="http://www.swisstopo.admin.ch/SITiled/world/AdminBoundaries/metadata.htm"/>
+ <Style>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <LegendURL format="image/png" xlink:href="http://api.geo.admin.ch/legend/ch.are.agglomerationen_isolierte_staedte-2000_fr.png" />
+ </Style>
+ <Format>image/png</Format>
+ <Dimension>
+ <ows:Identifier>Time</ows:Identifier>
+ <Default>20090101</Default>
+ <Value>20090101</Value>
+ </Dimension>
+ <TileMatrixSetLink>
+ <TileMatrixSet>21781</TileMatrixSet>
+ </TileMatrixSetLink>
+ </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>21781</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG:1.0:21781</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>0</ows:Identifier>
+ <ScaleDenominator>14285750.5715</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <ServiceMetadataURL xlink:href="http://www.opengis.uab.es/SITiled/world/1.0.0/WMTSCapabilities.xml"/>
+</Capabilities>
+--></div>
+<div id="arcgis"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:ServiceIdentification>
+ <ows:Title>WorldTimeZones</ows:Title>
+ <ows:ServiceType>OGC WMTS</ows:ServiceType>
+ <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
+ </ows:ServiceIdentification>
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://sampleserver6.arcgisonline.com/arcgis/rest/services/WorldTimeZones/MapServer/WMTS/tile/1.0.0/">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>RESTful</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ <ows:Get xlink:href="http://sampleserver6.arcgisonline.com/arcgis/rest/services/WorldTimeZones/MapServer/WMTS?">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>WorldTimeZones</ows:Title>
+ <ows:Identifier>WorldTimeZones</ows:Identifier>
+ <ows:BoundingBox crs="urn:ogc:def:crs:EPSG::102100">
+ <ows:LowerCorner>-2.0037507067161843E7 -3.024097195838617E7</ows:LowerCorner>
+ <ows:UpperCorner>2.0037507067161843E7 3.0240971458386205E7</ows:UpperCorner>
+ </ows:BoundingBox>
+ <ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
+ <ows:LowerCorner>-179.99999550841463 -88.99999992161119</ows:LowerCorner>
+ <ows:UpperCorner>179.99999550841463 88.99999992161118</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <Style isDefault="true">
+ <ows:Title>Default Style</ows:Title>
+ <ows:Identifier>default</ows:Identifier>
+ </Style>
+ <Format>image/png</Format>
+ <TileMatrixSetLink>
+ <TileMatrixSet>GoogleMapsCompatible</TileMatrixSet>
+ </TileMatrixSetLink>
+ <ResourceURL format="image/png" resourceType="tile" template="http://sampleserver6.arcgisonline.com/arcgis/rest/services/WorldTimeZones/MapServer/WMTS/tile/1.0.0/WorldTimeZones/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png" />
+ </Layer>
+ <TileMatrixSet>
+ <ows:Title>GoogleMapsCompatible</ows:Title>
+ <ows:Abstract>the wellknown 'GoogleMapsCompatible' tile matrix set defined by OGC WMTS specification</ows:Abstract>
+ <ows:Identifier>GoogleMapsCompatible</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG:6.18:3:3857</ows:SupportedCRS>
+ <WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible</WellKnownScaleSet>
+ <TileMatrix>
+ <ows:Identifier>5</ows:Identifier>
+ <ScaleDenominator>17471320.75089743</ScaleDenominator>
+ <TopLeftCorner>-20037508.34278925 20037508.34278925</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32</MatrixWidth>
+ <MatrixHeight>32</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <ServiceMetadataURL xlink:href="http://sampleserver6.arcgisonline.com/arcgis/rest/services/WorldTimeZones/MapServer/WMTS/1.0.0/WMTSCapabilities.xml" />
+</Capabilities>
+--></div>
+
+<div id="multi-getile-2"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://schemas.opengis.net/wmts/1.0/wmtsGetCapabilities_response.xsd" version="1.0.0">
+ <ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/kvp">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ <ows:Get xlink:href="http://wmts.geo.admin.ch/rest">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>REST</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ </ows:OperationsMetadata>
+ <Contents>
+ <Layer>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Abstract>Les agglomérations et villes isolées (communes non rattachées à une agglomération et comptant au moins 10`000 habitants) font partie des régions d’analyse de la statistique suisse. Ce niveau géographique est défini depuis plus de 100 ans, afin de mesurer l’urbanisation, phénomène fondamental structurant l’organisation du territoire. Sa fonction principale est de permettre une comparaison spatiale entre des espaces urbains inégalement délimités sur le plan institutionnel. Une version ancienne est appliquée pour la première fois en 1930, puis révisée en 1984 et 1990, toujours sur la base des recensements de la population. La version actuelle classe les 2896 communes de Suisse (état 2000) selon leur appartenance ou pas à une agglomération ou ville isolée en fonction de critères statistiques (Etat et évolution de la population, lien de continuité de la zone bâtie, rapport entre population active occupée et population résidante, structure économique et flux de pendulaires). Les agglomérations et les villes isolées forment l`espace urbain, les territoires restant l`espace rural. La définition des agglomérations de l’OFS n’a pas valeur d’obligation légale.</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>5.140242 45.398181</ows:LowerCorner>
+ <ows:UpperCorner>11.47757 48.230651</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <ows:Metadata xlink:href="http://www.swisstopo.admin.ch/SITiled/world/AdminBoundaries/metadata.htm"/>
+ <Style>
+ <ows:Title>Agglomérations et villes isolées</ows:Title>
+ <ows:Identifier>ch.are.agglomerationen_isolierte_staedte-2000</ows:Identifier>
+ <LegendURL format="image/png" xlink:href="http://api.geo.admin.ch/legend/ch.are.agglomerationen_isolierte_staedte-2000_fr.png" />
+ </Style>
+ <Format>image/png</Format>
+ <Dimension>
+ <ows:Identifier>Time</ows:Identifier>
+ <Default>20090101</Default>
+ <Value>20090101</Value>
+ </Dimension>
+ <TileMatrixSetLink>
+ <TileMatrixSet>21781</TileMatrixSet>
+ </TileMatrixSetLink>
+ </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>21781</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG:1.0:21781</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>0</ows:Identifier>
+ <ScaleDenominator>14285750.5715</ScaleDenominator>
+ <TopLeftCorner>420000.0 350000.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ </Contents>
+ <ServiceMetadataURL xlink:href="http://www.opengis.uab.es/SITiled/world/1.0.0/WMTSCapabilities.xml"/>
+</Capabilities>
+--></div>
+
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.html b/misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.html
new file mode 100644
index 0000000..191f29f
--- /dev/null
+++ b/misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="v1_0_0.js"></script>
+ <script type="text/javascript">
+
+ function test_read(t) {
+
+ t.plan(7);
+
+ var format = new OpenLayers.Format.WPSCapabilities();
+ var obj = format.read(doc);
+
+ t.eq(obj.version, "1.0.0", "Version parsed correctly");
+
+ t.eq(obj.languages.length, 2, "2 language entries parsed");
+ t.eq(obj.languages[0].isDefault, true, "First language is the default language");
+ t.eq(obj.languages[0].language, "en-US", "First language is US English");
+
+ var buffer = obj.processOfferings["JTS:buffer"];
+ t.eq(buffer.processVersion, "1.0.0", "processVersion for buffer is 1.0.0");
+ t.eq(buffer.abstract, "Buffers a geometry using a certain distance", "Buffer abstract correctly read");
+ t.eq(buffer.title, "Buffers a geometry using a certain distance", "Buffer title correctly read");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.js b/misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.js
new file mode 100644
index 0000000..19d12f2
--- /dev/null
+++ b/misc/openlayers/tests/Format/WPSCapabilities/v1_0_0.js
@@ -0,0 +1,112 @@
+var doc = new OpenLayers.Format.XML().read(
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<wps:Capabilities xml:lang="en" service="WPS" version="1.0.0"' +
+' xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd"' +
+' xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink">' +
+' <ows:ServiceIdentification>' +
+' <ows:Title>Prototype GeoServer WPS</ows:Title>' +
+' <ows:Abstract/>' +
+' <ows:ServiceType>WPS</ows:ServiceType>' +
+' <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>' +
+' </ows:ServiceIdentification>' +
+' <ows:ServiceProvider>' +
+' <ows:ProviderName>The ancient geographes INC</ows:ProviderName>' +
+' <ows:ProviderSite xlink:href="http://geoserver.org"/>' +
+' <ows:ServiceContact/>' +
+' </ows:ServiceProvider>' +
+' <ows:OperationsMetadata>' +
+' <ows:Operation name="GetCapabilities">' +
+' <ows:DCP>' +
+' <ows:HTTP>' +
+' <ows:Get xlink:href="http://localhost:8080/geoserver/wps"/>' +
+' <ows:Post xlink:href="http://localhost:8080/geoserver/wps"/>' +
+' </ows:HTTP>' +
+' </ows:DCP>' +
+' </ows:Operation>' +
+' <ows:Operation name="DescribeProcess">' +
+' <ows:DCP>' +
+' <ows:HTTP>' +
+' <ows:Get xlink:href="http://localhost:8080/geoserver/wps"/>' +
+' <ows:Post xlink:href="http://localhost:8080/geoserver/wps"/>' +
+' </ows:HTTP>' +
+' </ows:DCP>' +
+' </ows:Operation>' +
+' <ows:Operation name="Execute">' +
+' <ows:DCP>' +
+' <ows:HTTP>' +
+' <ows:Get xlink:href="http://localhost:8080/geoserver/wps"/>' +
+' <ows:Post xlink:href="http://localhost:8080/geoserver/wps"/>' +
+' </ows:HTTP>' +
+' </ows:DCP>' +
+' </ows:Operation>' +
+' </ows:OperationsMetadata>' +
+' <wps:ProcessOfferings>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>gt:Intersect</ows:Identifier>' +
+' <ows:Title>Intersection</ows:Title>' +
+' <ows:Abstract>Intersection between two literal geometry</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:length</ows:Identifier>' +
+' <ows:Title>Returns the geometry perimeters, computed using cartesian geometry' +
+' expressions in the same unit of measure as the geometry (will not return a valid' +
+' perimeter for geometries expressed geographic coordinates</ows:Title>' +
+' <ows:Abstract>Returns the geometry perimeters, computed using cartesian geometry' +
+' expressions in the same unit of measure as the geometry (will not return a valid' +
+' perimeter for geometries expressed geographic coordinates</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:isEmpty</ows:Identifier>' +
+' <ows:Title>Checks if the provided geometry is empty</ows:Title>' +
+' <ows:Abstract>Checks if the provided geometry is empty</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:contains</ows:Identifier>' +
+' <ows:Title>Checks if a contains b</ows:Title>' +
+' <ows:Abstract>Checks if a contains b</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:disjoint</ows:Identifier>' +
+' <ows:Title>Returns true if the two geometries have no points in common</ows:Title>' +
+' <ows:Abstract>Returns true if the two geometries have no points in common</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:intersects</ows:Identifier>' +
+' <ows:Title>Returns true if the two geometries intersect, false otherwise</ows:Title>' +
+' <ows:Abstract>Returns true if the two geometries intersect, false' +
+' otherwise</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:isClosed</ows:Identifier>' +
+' <ows:Title>Returns true if the line is closed</ows:Title>' +
+' <ows:Abstract>Returns true if the line is closed</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:isValid</ows:Identifier>' +
+' <ows:Title>Returns true if the geometry is topologically valid, false' +
+' otherwise</ows:Title>' +
+' <ows:Abstract>Returns true if the geometry is topologically valid, false' +
+' otherwise</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:buffer</ows:Identifier>' +
+' <ows:Title>Buffers a geometry using a certain distance</ows:Title>' +
+' <ows:Abstract>Buffers a geometry using a certain distance</ows:Abstract>' +
+' </wps:Process>' +
+' <wps:Process wps:processVersion="1.0.0">' +
+' <ows:Identifier>JTS:getY</ows:Identifier>' +
+' <ows:Title>Returns the Y ordinate of the point</ows:Title>' +
+' <ows:Abstract>Returns the Y ordinate of the point</ows:Abstract>' +
+' </wps:Process>' +
+' </wps:ProcessOfferings>' +
+' <wps:Languages>' +
+' <wps:Default>' +
+' <ows:Language>en-US</ows:Language>' +
+' </wps:Default>' +
+' <wps:Supported>' +
+' <ows:Language>en-US</ows:Language>' +
+' </wps:Supported>' +
+' </wps:Languages>' +
+'</wps:Capabilities>'
+);
diff --git a/misc/openlayers/tests/Format/WPSDescribeProcess.html b/misc/openlayers/tests/Format/WPSDescribeProcess.html
new file mode 100644
index 0000000..f52fd21
--- /dev/null
+++ b/misc/openlayers/tests/Format/WPSDescribeProcess.html
@@ -0,0 +1,206 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_read_WPSDescribeProcess(t) {
+ t.plan(17);
+
+ var parser = new OpenLayers.Format.WPSDescribeProcess();
+ var text =
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<wps:ProcessDescriptions xml:lang="en" service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+' xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd"' +
+' xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink">' +
+' <ProcessDescription wps:processVersion="1.0.0" statusSupported="false"' +
+' storeSupported="false">' +
+' <ows:Identifier>JTS:buffer</ows:Identifier>' +
+' <ows:Title>Buffers a geometry using a certain distance</ows:Title>' +
+' <ows:Abstract>Buffers a geometry using a certain distance</ows:Abstract>' +
+' <DataInputs>' +
+' <Input maxOccurs="1" minOccurs="1">' +
+' <ows:Identifier>geom</ows:Identifier>' +
+' <ows:Title>geom</ows:Title>' +
+' <ows:Abstract>The geometry to be buffered</ows:Abstract>' +
+' <ComplexData>' +
+' <Default>' +
+' <Format>' +
+' <MimeType>text/xml; subtype=gml/3.1.1</MimeType>' +
+' </Format>' +
+' </Default>' +
+' <Supported>' +
+' <Format>' +
+' <MimeType>text/xml; subtype=gml/3.1.1</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>text/xml; subtype=gml/2.1.2</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/wkt</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/gml-3.1.1</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/gml-2.1.2</MimeType>' +
+' </Format>' +
+' </Supported>' +
+' </ComplexData>' +
+' </Input>' +
+' <Input maxOccurs="1" minOccurs="1">' +
+' <ows:Identifier>distance</ows:Identifier>' +
+' <ows:Title>distance</ows:Title>' +
+' <ows:Abstract>The distance (same unit of measure as the geometry)</ows:Abstract>' +
+' <LiteralData>' +
+' <ows:DataType>xs:double</ows:DataType>' +
+' <ows:AnyValue/>' +
+' </LiteralData>' +
+' </Input>' +
+' <Input maxOccurs="1" minOccurs="0">' +
+' <ows:Identifier>quadrantSegments</ows:Identifier>' +
+' <ows:Title>quadrantSegments</ows:Title>' +
+' <ows:Abstract>Number of quadrant segments. Use &gt; 0 for round joins, 0 for' +
+' flat joins, &lt; 0 for mitred joins</ows:Abstract>' +
+' <LiteralData>' +
+' <ows:DataType>xs:int</ows:DataType>' +
+' <ows:AnyValue/>' +
+' </LiteralData>' +
+' </Input>' +
+' <Input maxOccurs="1" minOccurs="0">' +
+' <ows:Identifier>capStyle</ows:Identifier>' +
+' <ows:Title>capStyle</ows:Title>' +
+' <ows:Abstract>The buffer cap style, round, flat, square</ows:Abstract>' +
+' <LiteralData>' +
+' <ows:AllowedValues>' +
+' <ows:Value>Round</ows:Value>' +
+' <ows:Value>Flat</ows:Value>' +
+' <ows:Value>Square</ows:Value>' +
+' </ows:AllowedValues>' +
+' </LiteralData>' +
+' </Input>' +
+' </DataInputs>' +
+' <ProcessOutputs>' +
+' <Output>' +
+' <ows:Identifier>result</ows:Identifier>' +
+' <ows:Title>result</ows:Title>' +
+' <ComplexOutput>' +
+' <Default>' +
+' <Format>' +
+' <MimeType>text/xml; subtype=gml/3.1.1</MimeType>' +
+' </Format>' +
+' </Default>' +
+' <Supported>' +
+' <Format>' +
+' <MimeType>text/xml; subtype=gml/3.1.1</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>text/xml; subtype=gml/2.1.2</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/wkt</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/gml-3.1.1</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/gml-2.1.2</MimeType>' +
+' </Format>' +
+' </Supported>' +
+' </ComplexOutput>' +
+' </Output>' +
+' <Output>' +
+' <ows:Identifier>literal</ows:Identifier>' +
+' <ows:Title>literal output</ows:Title>' +
+' <LiteralOutput>' +
+' <ows:DataType ows:reference="http://www.w3.org/TR/xmlschema-2/#integer">integer</ows:DataType>'+
+' </LiteralOutput>' +
+' </Output>' +
+' </ProcessOutputs>' +
+' </ProcessDescription>' +
+'</wps:ProcessDescriptions>';
+
+ var res = parser.read(text);
+ var buffer = res.processDescriptions["JTS:buffer"];
+ t.eq(buffer.statusSupported, false, "statusSupported read correctly");
+ t.eq(buffer.storeSupported, false, "storeSupported read correctly");
+ t.eq(buffer.processVersion, "1.0.0", "processVersion read correctly");
+ var capStyle = buffer.dataInputs[3];
+ t.eq(capStyle.abstract, "The buffer cap style, round, flat, square", "capStyle abstract read correctly");
+ t.eq(capStyle.minOccurs, 0, "capStyle minOccurs read correctly");
+ t.eq(capStyle.maxOccurs, 1, "maxOccurs read correctly");
+ t.eq(capStyle.literalData.allowedValues["Flat"], true, "capStyle allowedValues read correctly");
+ var distance = buffer.dataInputs[1];
+ t.eq(distance.literalData.anyValue, true, "distance anyValue read correctly");
+ t.eq(distance.literalData.dataType, "xs:double", "distance dataType read correctly");
+ var geom = buffer.dataInputs[0];
+ t.eq(geom.complexData["default"].formats["text/xml; subtype=gml/3.1.1"], true, "geom complexData default read correctly");
+ t.eq(geom.complexData["supported"].formats["application/gml-2.1.2"], true, "geom complexData supported read correctly [1/2]");
+ t.eq(geom.complexData["supported"].formats["application/gml-3.1.1"], true, "geom complexData supported read correctly [2/2]");
+ var result = buffer.processOutputs[0];
+ t.eq(result.complexOutput["default"].formats["text/xml; subtype=gml/3.1.1"], true, "processOutputs default format read correctly");
+ t.eq(result.complexOutput["supported"].formats["text/xml; subtype=gml/3.1.1"], true, "processOutputs supported format read correctly [1/2]");
+ t.eq(result.complexOutput["supported"].formats["application/wkt"], true, "processOutputs supported format read correctly [1/2]");
+
+ var literalresult = buffer.processOutputs[1];
+ t.eq(literalresult.literalOutput.dataType, "integer", "processOutputs supported data type read corectly");
+
+ text = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<wps:ProcessDescriptions service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0"' +
+' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xml:lang="en"' +
+' xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd"' +
+' xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink">' +
+' <ProcessDescription wps:processVersion="1.0.0" statusSupported="false"' +
+' storeSupported="false">' +
+' <ows:Identifier>gt:VectorToRaster</ows:Identifier>' +
+' <ows:Title>Rasterize features</ows:Title>' +
+' <ows:Abstract>Rasterize all or selected features in a FeatureCollection</ows:Abstract>' +
+' <DataInputs>' +
+' <Input maxOccurs="1" minOccurs="0">' +
+' <ows:Identifier>bounds</ows:Identifier>' +
+' <ows:Title>Bounds</ows:Title>' +
+' <ows:Abstract>Bounds of the area to rasterize</ows:Abstract>' +
+' <BoundingBoxData>' +
+' <Default>' +
+' <CRS>EPSG:4326</CRS>' +
+' </Default>' +
+' <Supported>' +
+' <CRS>EPSG:4326</CRS>' +
+' </Supported>' +
+' </BoundingBoxData>' +
+' </Input>' +
+' </DataInputs>' +
+' <ProcessOutputs>' +
+' <Output>' +
+' <ows:Identifier>result</ows:Identifier>' +
+' <ows:Title>Result</ows:Title>' +
+' <ComplexOutput>' +
+' <Default>' +
+' <Format>' +
+' <MimeType>image/tiff</MimeType>' +
+' </Format>' +
+' </Default>' +
+' <Supported>' +
+' <Format>' +
+' <MimeType>image/tiff</MimeType>' +
+' </Format>' +
+' <Format>' +
+' <MimeType>application/arcgrid</MimeType>' +
+' </Format>' +
+' </Supported>' +
+' </ComplexOutput>' +
+' </Output>' +
+' </ProcessOutputs>' +
+' </ProcessDescription>' +
+'</wps:ProcessDescriptions>';
+
+ res = parser.read(text);
+ var vector2Raster = res.processDescriptions["gt:VectorToRaster"];
+ t.eq(vector2Raster.dataInputs[0].boundingBoxData["default"].CRSs["EPSG:4326"], true, "BoundingBoxData CRS parsed correctly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/WPSExecute.html b/misc/openlayers/tests/Format/WPSExecute.html
new file mode 100644
index 0000000..e820800
--- /dev/null
+++ b/misc/openlayers/tests/Format/WPSExecute.html
@@ -0,0 +1,549 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_write_WPSExecute_WCS(t) {
+ t.plan(1);
+ var expected = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:wcs="http://www.opengis.net/wcs" xmlns:xlink="http://www.w3.org/1999/xlink" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">' +
+' <ows:Identifier>gs:GeorectifyCoverage</ows:Identifier>' +
+' <wps:DataInputs>' +
+' <wps:Input>' +
+' <ows:Identifier>data</ows:Identifier>' +
+' <wps:Reference mimeType="image/tiff" xlink:href="http://geoserver/wcs" method="POST">' +
+' <wps:Body>' +
+' <wcs:GetCoverage service="WCS" version="1.1.2">' +
+' <ows:Identifier>topp:asbuilt</ows:Identifier>' +
+' <wcs:DomainSubset>' +
+' <ows:BoundingBox crs="http://www.opengis.net/gml/srs/epsg.xml#404000">' +
+' <ows:LowerCorner>0 -7070</ows:LowerCorner>' +
+' <ows:UpperCorner>10647 1</ows:UpperCorner>' +
+' </ows:BoundingBox>' +
+' </wcs:DomainSubset>' +
+' <wcs:Output format="image/tiff"/>' +
+' </wcs:GetCoverage>' +
+' </wps:Body>' +
+' </wps:Reference>' +
+' </wps:Input>' +
+' <wps:Input>' +
+' <ows:Identifier>gcp</ows:Identifier>' +
+' <wps:Data>' +
+' <wps:LiteralData>[[[2721, 3263], [-122.472109, 37.73106003]], [[4163, 3285], [-122.4693417, 37.729929851]], [[5773, 4046], [-122.466702461, 37.7271906]], [[8885, 4187], [-122.462333, 37.725167]]]</wps:LiteralData>' +
+' </wps:Data>' +
+' </wps:Input>' +
+' <wps:Input>' +
+' <ows:Identifier>targetCRS</ows:Identifier>' +
+' <wps:Data>' +
+' <wps:LiteralData>EPSG:4326</wps:LiteralData>' +
+' </wps:Data>' +
+' </wps:Input>' +
+' <wps:Input>' +
+' <ows:Identifier>transparent</ows:Identifier>' +
+' <wps:Data>' +
+' <wps:LiteralData>true</wps:LiteralData>' +
+' </wps:Data>' +
+' </wps:Input>' +
+' </wps:DataInputs>' +
+' <wps:ResponseForm>' +
+' <wps:RawDataOutput mimeType="image/tiff">' +
+' <ows:Identifier>result</ows:Identifier>' +
+' </wps:RawDataOutput>' +
+' </wps:ResponseForm>' +
+'</wps:Execute>';
+
+ var format = new OpenLayers.Format.WPSExecute();
+ var result = format.write({
+ identifier: "gs:GeorectifyCoverage",
+ dataInputs: [{
+ identifier: 'data',
+ reference: {
+ mimeType: "image/tiff",
+ href: "http://geoserver/wcs",
+ method: "POST",
+ body: {
+ wcs: {
+ identifier: 'topp:asbuilt',
+ version: '1.1.2',
+ domainSubset: {
+ boundingBox: {
+ projection: 'http://www.opengis.net/gml/srs/epsg.xml#404000',
+ bounds: new OpenLayers.Bounds(0.0, -7070.0, 10647.0, 1.0)
+ }
+ },
+ output: {format: 'image/tiff'}
+ }
+ }
+ }
+ }, {
+ identifier: 'gcp',
+ data: {
+ literalData: {
+ value: '[[[2721, 3263], [-122.472109, 37.73106003]], [[4163, 3285], [-122.4693417, 37.729929851]], [[5773, 4046], [-122.466702461, 37.7271906]], [[8885, 4187], [-122.462333, 37.725167]]]'
+ }
+ }
+ }, {
+ identifier: 'targetCRS',
+ data: {
+ literalData: {
+ value: 'EPSG:4326'
+ }
+ }
+ }, {
+ identifier: 'transparent',
+ data: {
+ literalData: {
+ value: 'true'
+ }
+ }
+ }],
+ responseForm: {
+ rawDataOutput: {
+ mimeType: "image/tiff",
+ identifier: "result"
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WPS Execute with embedded WCS GetCoverage written out correctly");
+
+ }
+
+ function test_write_WPSExecute(t) {
+ t.plan(1);
+ var expected = '<?xml version="1.0" encoding="UTF-8"?>' +
+'<wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' +
+' xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs"' +
+' xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1"' +
+' xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc"' +
+' xmlns:wcs="http://www.opengis.net/wcs/1.1.1" xmlns:xlink="http://www.w3.org/1999/xlink"' +
+' xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">' +
+' <ows:Identifier>JTS:area</ows:Identifier>' +
+' <wps:DataInputs>' +
+' <wps:Input>' +
+' <ows:Identifier>geom</ows:Identifier>' +
+' <wps:Reference mimeType="text/xml; subtype=gml/3.1.1" xlink:href="http://geoserver/wps"' +
+' method="POST">' +
+' <wps:Body>' +
+' <wps:Execute service="WPS" version="1.0.0">' +
+' <ows:Identifier>gs:CollectGeometries</ows:Identifier>' +
+' <wps:DataInputs>' +
+' <wps:Input>' +
+' <ows:Identifier>features</ows:Identifier>' +
+' <wps:Reference mimeType="text/xml; subtype=wfs-collection/1.0"' +
+' xlink:href="http://geoserver/wfs" method="POST">' +
+' <wps:Body>' +
+' <wfs:GetFeature service="WFS" version="1.0.0"' +
+' outputFormat="GML2">' +
+' <wfs:Query typeName="sf:archsites"/>' +
+' </wfs:GetFeature>' +
+' </wps:Body>' +
+' </wps:Reference>' +
+' </wps:Input>' +
+' </wps:DataInputs>' +
+' <wps:ResponseForm>' +
+' <wps:RawDataOutput mimeType="text/xml; subtype=gml/3.1.1">' +
+' <ows:Identifier>result</ows:Identifier>' +
+' </wps:RawDataOutput>' +
+' </wps:ResponseForm>' +
+' </wps:Execute>' +
+' </wps:Body>' +
+' </wps:Reference>' +
+' </wps:Input>' +
+' </wps:DataInputs>' +
+' <wps:ResponseForm>' +
+' <wps:RawDataOutput>' +
+' <ows:Identifier>result</ows:Identifier>' +
+' </wps:RawDataOutput>' +
+' </wps:ResponseForm>' +
+'</wps:Execute>';
+
+ var format = new OpenLayers.Format.WPSExecute();
+ var result = format.write({
+ identifier: "JTS:area",
+ dataInputs: [{
+ identifier: 'geom',
+ reference: {
+ mimeType: "text/xml; subtype=gml/3.1.1",
+ href: "http://geoserver/wps",
+ method: "POST",
+ body: {
+ identifier: "gs:CollectGeometries",
+ dataInputs: [{
+ identifier: 'features',
+ reference: {
+ mimeType: "text/xml; subtype=wfs-collection/1.0",
+ href: "http://geoserver/wfs",
+ method: "POST",
+ body: {
+ wfs: {
+ version: "1.0.0",
+ outputFormat: "GML2",
+ featureType: "sf:archsites"
+ }
+ }
+ }
+ }],
+ responseForm: {
+ rawDataOutput: {
+ mimeType: "text/xml; subtype=gml/3.1.1",
+ identifier: "result"
+ }
+ }
+ }
+ }
+ }],
+ responseForm: {
+ rawDataOutput: {
+ identifier: "result"
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WPS Execute written out correctly");
+ }
+
+ function test_write_raw_data_output(t) {
+ t.plan(1);
+ // example request taken from: http://geoprocessing.info/wpsdoc/1x0ExecutePOST
+ var expected = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+'<wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" ' +
+'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">' +
+' <ows:Identifier>Buffer</ows:Identifier>' +
+' <wps:DataInputs>' +
+' <wps:Input>' +
+' <ows:Identifier>InputPolygon</ows:Identifier>' +
+' <ows:Title>Playground area</ows:Title>' +
+' <wps:Reference xlink:href="http://foo.bar/some_WFS_request.xml"/>' +
+' </wps:Input>' +
+' <wps:Input>' +
+' <ows:Identifier>BufferDistance</ows:Identifier>' +
+' <ows:Title>Distance which people will walk to get to a playground.</ows:Title>' +
+' <wps:Data>' +
+' <wps:LiteralData>400</wps:LiteralData>' +
+' </wps:Data>' +
+' </wps:Input>' +
+' </wps:DataInputs>' +
+' <wps:ResponseForm>' +
+' <wps:RawDataOutput>' +
+' <ows:Identifier>BufferedPolygon</ows:Identifier>' +
+' </wps:RawDataOutput>' +
+' </wps:ResponseForm>' +
+'</wps:Execute>';
+
+ var format = new OpenLayers.Format.WPSExecute();
+ var result = format.write({
+ identifier: "Buffer",
+ dataInputs: [{
+ identifier: 'InputPolygon',
+ title: 'Playground area',
+ reference: {
+ href: 'http://foo.bar/some_WFS_request.xml'
+ }
+ }, {
+ identifier: 'BufferDistance',
+ title: 'Distance which people will walk to get to a playground.',
+ data: {
+ literalData: {
+ value: 400
+ }
+ }
+ }],
+ responseForm: {
+ rawDataOutput: {
+ identifier: "BufferedPolygon"
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WPS Execute written out correctly");
+ }
+
+ function test_write_request_responseDoc_defaultFormat(t) {
+ t.plan(1);
+ // taken from http://geoprocessing.info/schemas/wps/1.0/examples/51_wpsExecute_request_ResponseDocument.xml
+ var expected = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+'<wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" ' +
+'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">' +
+' <ows:Identifier>Buffer</ows:Identifier>' +
+' <wps:DataInputs>' +
+' <wps:Input>' +
+' <ows:Identifier>InputPolygon</ows:Identifier>' +
+' <ows:Title>Playground area</ows:Title>' +
+' <wps:Reference xlink:href="http://foo.bar/some_WFS_request.xml"/>' +
+' </wps:Input>' +
+' <wps:Input>' +
+' <ows:Identifier>BufferDistance</ows:Identifier>' +
+' <ows:Title>Distance which people will walk to get to a playground.</ows:Title>' +
+' <wps:Data>' +
+' <wps:LiteralData>400</wps:LiteralData>' +
+' </wps:Data>' +
+' </wps:Input>' +
+' </wps:DataInputs>' +
+' <wps:ResponseForm>' +
+' <wps:ResponseDocument storeExecuteResponse="true">' +
+' <wps:Output asReference="true">' +
+' <ows:Identifier>BufferedPolygon</ows:Identifier>' +
+' <ows:Title>Area serviced by playground.</ows:Title>' +
+' <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract>' +
+' </wps:Output>' +
+' </wps:ResponseDocument>' +
+' </wps:ResponseForm>' +
+'</wps:Execute>';
+
+ var format = new OpenLayers.Format.WPSExecute();
+ var result = format.write({
+ identifier: "Buffer",
+ dataInputs: [{
+ identifier: 'InputPolygon',
+ title: 'Playground area',
+ reference: {
+ href: 'http://foo.bar/some_WFS_request.xml'
+ }
+ }, {
+ identifier: 'BufferDistance',
+ title: 'Distance which people will walk to get to a playground.',
+ data: {
+ literalData: {
+ value: 400
+ }
+ }
+ }],
+ responseForm: {
+ responseDocument: {
+ storeExecuteResponse: true,
+ outputs: [{
+ asReference: true,
+ identifier: 'BufferedPolygon',
+ title: 'Area serviced by playground.',
+ 'abstract': 'Area within which most users of this playground will live.'
+ }]
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WPS Execute written out correctly");
+ }
+
+ function test_write_request_responseDoc_specifiedFormat(t) {
+ t.plan(1);
+ // taken from http://geoprocessing.info/schemas/wps/1.0/examples/52_wpsExecute_request_ResponseDocument.xml
+ var expected = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+'<wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" ' +
+'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">' +
+' <ows:Identifier>Buffer</ows:Identifier>' +
+' <wps:DataInputs>' +
+' <wps:Input>' +
+' <ows:Identifier>InputPolygon</ows:Identifier>' +
+' <ows:Title>Playground area</ows:Title>' +
+' <wps:Reference xlink:href="http://foo.bar/some_WFS_request.xml" method="POST" mimeType="text/xml" encoding="UTF-8" schema="http://foo.bar/gml_polygon_schema.xsd"/>' +
+' </wps:Input>' +
+' <wps:Input>' +
+' <ows:Identifier>BufferDistance</ows:Identifier>' +
+' <ows:Title>Distance which people will walk to get to a playground.</ows:Title>' +
+' <wps:Data>' +
+' <wps:LiteralData uom="feet">400</wps:LiteralData>' +
+' </wps:Data>' +
+' </wps:Input>' +
+' </wps:DataInputs>' +
+' <wps:ResponseForm>' +
+' <wps:ResponseDocument storeExecuteResponse="true" lineage="true" status="true">' +
+' <wps:Output asReference="true">' +
+' <ows:Identifier>BufferedPolygon</ows:Identifier>' +
+' <ows:Title>Area serviced by playground.</ows:Title>' +
+' <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract>' +
+' </wps:Output>' +
+' <wps:Output>' +
+' <ows:Identifier>literal</ows:Identifier>' +
+' <ows:Title/>' +
+' <ows:Abstract/>' +
+' </wps:Output>' +
+' </wps:ResponseDocument>' +
+' </wps:ResponseForm>' +
+'</wps:Execute>';
+
+ var format = new OpenLayers.Format.WPSExecute();
+ var result = format.write({
+ identifier: "Buffer",
+ dataInputs: [{
+ identifier: 'InputPolygon',
+ title: 'Playground area',
+ reference: {
+ href: 'http://foo.bar/some_WFS_request.xml',
+ method: "POST",
+ mimeType: "text/xml",
+ encoding: "UTF-8",
+ schema: "http://foo.bar/gml_polygon_schema.xsd"
+ }
+ }, {
+ identifier: 'BufferDistance',
+ title: 'Distance which people will walk to get to a playground.',
+ data: {
+ literalData: {
+ value: 400,
+ uom: 'feet'
+ }
+ }
+ }],
+ responseForm: {
+ responseDocument: {
+ storeExecuteResponse: true,
+ lineage: true,
+ status: true,
+ outputs: [
+ {
+ asReference: true,
+ identifier: 'BufferedPolygon',
+ title: 'Area serviced by playground.',
+ 'abstract': 'Area within which most users of this playground will live.'
+ },
+ {
+ identifier: 'literal'
+ }
+ ]
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WPS Execute written out correctly");
+ }
+
+ function test_write_request_complexData(t) {
+ t.plan(1);
+ // taken from http://geoprocessing.info/schemas/wps/1.0/examples/51_wpsExecute_request_ResponseDocument.xml
+ var expected = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' +
+ '<wps:Execute service="WPS" version="1.0.0" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" ' +
+ 'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">' +
+ ' <ows:Identifier>Buffer</ows:Identifier>' +
+ ' <wps:DataInputs>' +
+ ' <wps:Input>' +
+ ' <ows:Identifier>InputPolygon</ows:Identifier>' +
+ ' <ows:Title>Playground area</ows:Title>' +
+ ' <wps:Reference xlink:href="http://foo.bar/some_WFS_request.xml"/>' +
+ ' </wps:Input>' +
+ ' <wps:Input>' +
+ ' <ows:Identifier>ResultPage</ows:Identifier>' +
+ ' <ows:Title>Nicely formatted HTML of the result</ows:Title>' +
+ ' <wps:Data>' +
+ ' <wps:ComplexData><![CDATA[<html><head></head><body></body></head>]]></wps:ComplexData>' +
+ ' </wps:Data>' +
+ ' </wps:Input>' +
+ ' <wps:Input>' +
+ ' <ows:Identifier>GMLPoint</ows:Identifier>' +
+ ' <ows:Title>Point as GML</ows:Title>' +
+ ' <wps:Data>' +
+ ' <wps:ComplexData><feature:geometry xmlns:feature="http://www.opengis.net/gml"><feature:Point><feature:pos>10 10</feature:pos></feature:Point></feature:geometry></wps:ComplexData>' +
+ ' </wps:Data>' +
+ ' </wps:Input>' +
+ ' </wps:DataInputs>' +
+ ' <wps:ResponseForm>' +
+ ' <wps:ResponseDocument storeExecuteResponse="true">' +
+ ' <wps:Output asReference="true">' +
+ ' <ows:Identifier>BufferedPolygon</ows:Identifier>' +
+ ' <ows:Title>Area serviced by playground.</ows:Title>' +
+ ' <ows:Abstract>Area within which most users of this playground will live.</ows:Abstract>' +
+ ' </wps:Output>' +
+ ' </wps:ResponseDocument>' +
+ ' </wps:ResponseForm>' +
+ '</wps:Execute>';
+
+ var format = new OpenLayers.Format.WPSExecute();
+ var result = format.write({
+ identifier: "Buffer",
+ dataInputs: [{
+ identifier: 'InputPolygon',
+ title: 'Playground area',
+ reference: {
+ href: 'http://foo.bar/some_WFS_request.xml'
+ }
+ }, {
+ identifier: 'ResultPage',
+ title: 'Nicely formatted HTML of the result',
+ data: {
+ complexData: {
+ value: "<html><head></head><body></body></head>"
+ }
+ }
+ }, {
+ identifier: "GMLPoint",
+ title: "Point as GML",
+ data: {
+ complexData: {
+ value: OpenLayers.Format.GML.v3.prototype.writers.feature["_geometry"].apply(new OpenLayers.Format.GML.v3({curve: true, surface: true}), [new OpenLayers.Geometry.Point(10, 10)])
+ }
+ }
+ }],
+ responseForm: {
+ responseDocument: {
+ storeExecuteResponse: true,
+ outputs: [{
+ asReference: true,
+ identifier: 'BufferedPolygon',
+ title: 'Area serviced by playground.',
+ 'abstract': 'Area within which most users of this playground will live.'
+ }]
+ }
+ }
+ });
+ t.xml_eq(result, expected, "WPS Execute written out correctly");
+ }
+
+ function test_write_WPSExecuteFID(t) {
+ t.plan(1);
+
+ var result,
+ expected,
+ format = ({geometryName: 'the_geom'});
+
+ expected = '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wps:Execute xmlns:wps="http://www.opengis.net/wps/1.0.0" version="1.0.0" service="WPS" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+ ' <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">gs:Bounds</ows:Identifier>' +
+ ' <wps:DataInputs>' +
+ ' <wps:Input>' +
+ ' <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">features</ows:Identifier>' +
+ ' <wps:Reference mimeType="text/xml" xlink:href="http://geoserver/wfs" xmlns:xlink="http://www.w3.org/1999/xlink" method="POST">' +
+ ' <wps:Body>' +
+ ' <wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0">' +
+ ' <wfs:Query typeName="foo:bar">' +
+ ' <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">' +
+ ' <ogc:FeatureId fid="123"/>' +
+ ' </ogc:Filter>' +
+ ' </wfs:Query>' +
+ ' </wfs:GetFeature>' +
+ ' </wps:Body>' +
+ ' </wps:Reference>' +
+ ' </wps:Input>' +
+ ' </wps:DataInputs>' +
+ ' <wps:ResponseForm>' +
+ ' <wps:RawDataOutput>' +
+ ' <ows:Identifier xmlns:ows="http://www.opengis.net/ows/1.1">bounds</ows:Identifier>' +
+ ' </wps:RawDataOutput>' +
+ ' </wps:ResponseForm>' +
+ '</wps:Execute>';
+
+ result = new OpenLayers.Format.WPSExecute().write({
+ identifier: 'gs:Bounds',
+ dataInputs: [{
+ identifier: 'features',
+ reference: {
+ mimeType: 'text/xml',
+ href: 'http://geoserver/wfs',
+ method: 'POST',
+ body: {
+ wfs: {
+ featureType: 'foo:bar',
+ version: '1.0.0',
+ filter: new OpenLayers.Filter.FeatureId({fids: [123]})
+ }
+ }
+ }
+ }],
+ responseForm: {
+ rawDataOutput: {
+ identifier: 'bounds'
+ }
+ }
+ });
+ t.xml_eq(result, expected, 'WPS Execute written out correctly with a FID filter');
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/XLS/v1_1_0.html b/misc/openlayers/tests/Format/XLS/v1_1_0.html
new file mode 100644
index 0000000..8a744f9
--- /dev/null
+++ b/misc/openlayers/tests/Format/XLS/v1_1_0.html
@@ -0,0 +1,98 @@
+<html>
+<head>
+ <script src="../../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function test_read(t) {
+ t.plan(16);
+ var response = '<xls:GeocodeResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd" xmlns:xls="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml"><xls:GeocodeResponseList numberOfGeocodedAddresses="1"><xls:GeocodedAddress><gml:Point srsName="EPSG:28992"><gml:pos dimension="2">122650 483904</gml:pos></gml:Point><xls:Address countryCode="NL"><xls:StreetAddress><xls:Building number="1"/><xls:Street>president kennedylaan</xls:Street></xls:StreetAddress><xls:Place type="MunicipalitySubdivision">amsterdam</xls:Place><xls:Place type="Municipality">amsterdam</xls:Place><xls:Place type="CountrySubdivision">noord holland</xls:Place><xls:PostalCode>1079MB</xls:PostalCode></xls:Address></xls:GeocodedAddress></xls:GeocodeResponseList></xls:GeocodeResponse>';
+ var format = new OpenLayers.Format.XLS();
+ var output = format.read(response);
+ t.eq(output.responseLists.length, 1, "Output contains 1 responseList");
+ var responseList = output.responseLists[0];
+ t.eq(responseList.numberOfGeocodedAddresses, 1, "Responselist contains 1 geocoded address");
+ t.eq(responseList.features.length, 1, "1 feature parsed");
+ var feature = responseList.features[0];
+ var address = feature.attributes.address;
+ t.eq(address.building["number"], "1", "Building number correctly parsed");
+ t.eq(address.countryCode, "NL", "Country code correctly parsed");
+ t.eq(address.place.CountrySubdivision, "noord holland", "CountrySubDivision correctly parsed");
+ t.eq(address.place.Municipality, "amsterdam", "Municipality correctly parsed");
+ t.eq(address.place.MunicipalitySubdivision, "amsterdam", "MunicipalitySubdivision correctly parsed");
+ t.eq(address.postalCode, "1079MB", "Postalcode correctly parsed");
+ t.eq(address.street[0], "president kennedylaan", "Street correctly parsed");
+ t.eq(feature.geometry.x, 122650, "Geometry [x] correctly parsed");
+ t.eq(feature.geometry.y, 483904, "Geometry [y] correctly parsed");
+
+ var responseList = [];
+ responseList.push('<?xml version="1.0" encoding="UTF-8" ?>',
+'<XLS xmlns="http://www.opengis.net/xls" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/xls LocationUtilityService.xsd" version="1.1">',
+' <ResponseHeader/>',
+' <Response version="1.1" requestID="">',
+' <GeocodeResponse>',
+' <GeocodeResponseList numberOfGeocodedAddresses="2">',
+' <GeocodedAddress>',
+' <gml:Point>',
+' <gml:pos>-71.4589837781615 41.8317239069808</gml:pos>',
+' </gml:Point>',
+' <Address countryCode="">',
+' <StreetAddress>',
+' <Street></Street>',
+' <Street/>',
+' </StreetAddress>',
+' <Place type="Municipality"></Place>',
+' <Place type="CountrySubdivision"></Place>',
+' <PostalCode></PostalCode>',
+' </Address>',
+' <GeocodeMatchCode accuracy="100.0"/>',
+' </GeocodedAddress>',
+' <GeocodedAddress>',
+' <gml:Point>',
+' <gml:pos>-71.4087296631643 41.8269575002255</gml:pos>',
+' </gml:Point>',
+' <Address countryCode="">',
+' <StreetAddress>',
+' <Street></Street>',
+' <Street/>',
+' </StreetAddress>',
+' <Place type="Municipality"></Place>',
+' <Place type="CountrySubdivision"></Place>',
+' <PostalCode></PostalCode>',
+' </Address>',
+' <GeocodeMatchCode accuracy="100.0"/>',
+' </GeocodedAddress>',
+' </GeocodeResponseList>',
+' </GeocodeResponse>',
+' </Response>',
+'</XLS>');
+ response = responseList.join("");
+ output = format.read(response);
+ t.eq(output.version, "1.1", "Version correctly parsed");
+ var responseList = output.responseLists[0];
+ t.eq(responseList.numberOfGeocodedAddresses, 2, "2 addresses parsed");
+ t.eq(responseList.features.length, 2, "2 features parsed");
+ t.eq(responseList.features[0].attributes.matchCode.accuracy, 100.0, "Accuracy correctly parsed");
+ }
+
+ function test_write(t) {
+ t.plan(1);
+
+ var format = new OpenLayers.Format.XLS();
+ var address = {
+ countryCode: 'US',
+ street: '1 Freedom Rd',
+ municipality: 'Providence',
+ countrySubdivision: 'RI',
+ postalCode: '02909'
+ };
+ var request = format.write({addresses: [address]});
+
+ var expected = '<xls:XLS xmlns:xls="http://www.opengis.net/xls" version="1.1" xsi:schemaLocation="http://www.opengis.net/xls http://schemas.opengis.net/ols/1.1.0/LocationUtilityService.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><xls:RequestHeader/><xls:Request methodName="GeocodeRequest" requestID="" version="1.1"><xls:GeocodeRequest><xls:Address countryCode="US"><xls:StreetAddress><xls:Street>1 Freedom Rd</xls:Street></xls:StreetAddress><xls:Place type="Municipality">Providence</xls:Place><xls:Place type="CountrySubdivision">RI</xls:Place><xls:PostalCode>02909</xls:PostalCode></xls:Address></xls:GeocodeRequest></xls:Request></xls:XLS>';
+
+ t.xml_eq(request, expected, "XLS geocode request correctly written");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/XML.html b/misc/openlayers/tests/Format/XML.html
new file mode 100644
index 0000000..d139506
--- /dev/null
+++ b/misc/openlayers/tests/Format/XML.html
@@ -0,0 +1,900 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var text =
+ '<?xml version="1.0"?>' +
+ '<ol:root xmlns="http://namespace.default.net" ' +
+ 'xmlns:ol="http://namespace.openlayers.org" ' +
+ 'xmlns:ta="http://namespace.testattribute.net">' +
+ '<ol:child ta:attribute="value1" ' +
+ 'attribute="value2">' +
+ 'junk1' +
+ '<' + '/ol:child>' +
+ '<ol:child>junk2<' + '/ol:child>' +
+ '<ol:child>junk3<' + '/ol:child>' +
+ '<element>junk4<' + '/element>' +
+ '<ol:element>junk5<' + '/ol:element>' +
+ '<ol:p>' +
+ '<ol:a>junk' +
+ '<' + '/ol:a>' +
+ '<ol:b>junk' +
+ '<' + '/ol:b>' +
+ '<ol:a>junk' +
+ '<' + '/ol:a>' +
+ '<' + '/ol:p>' +
+ '<' + '/ol:root>';
+
+ function test_Format_XML_constructor(t) {
+ t.plan(13);
+
+ var options = {'foo': 'bar'};
+ var format = new OpenLayers.Format.XML(options);
+ t.ok(format instanceof OpenLayers.Format.XML,
+ "new OpenLayers.Format.XML returns object" );
+ t.eq(format.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a write function");
+
+ t.ok(!window.ActiveXObject || format.xmldom, "browsers with activeX must have xmldom");
+
+ // test namespaces
+ t.ok(format.namespaces instanceof Object, "format has namespace object");
+ var namespaces = {"foo": "bar"};
+ format = new OpenLayers.Format.XML({namespaces: namespaces});
+ t.eq(format.namespaces, namespaces, "format.namespaces correctly set in constructor");
+
+ // test default prefix
+ t.eq(format.defaultPrefix, null, "defaultPrefix is null by default");
+ format = new OpenLayers.Format.XML({defaultPrefix: "foo"});
+ t.eq(format.defaultPrefix, "foo", "defaultPrefix correctly set in constructor");
+
+ // test readers
+ t.ok(format.readers instanceof Object, "format has readers object");
+ var readers = {"foo": "bar"};
+ format = new OpenLayers.Format.XML({readers: readers});
+ t.eq(format.readers, readers, "format.readers correctly set in constructor");
+
+ // test readers
+ t.ok(format.writers instanceof Object, "format has writers object");
+ var writers = {"foo": "bar"};
+ format = new OpenLayers.Format.XML({writers: writers});
+ t.eq(format.writers, writers, "format.writers correctly set in constructor");
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+ var format = new OpenLayers.Format.XML();
+ format.destroy();
+ t.eq(format.xmldom, null, "xmldom set to null for all browsers");
+ }
+
+ function test_Format_XML_read(t) {
+
+ var format = new OpenLayers.Format.XML();
+ t.plan(format.xmldom ? 10 : 9);
+
+ var doc = format.read(text);
+ t.eq(doc.nodeType, 9,
+ "doc has the correct node type");
+ t.eq(doc.nodeName, "#document",
+ "doc has the correct node name");
+ t.ok(doc.documentElement,
+ "ok to access doc.documentElement");
+ t.xml_eq(doc.documentElement, text,
+ "doc.documentElement correctly read");
+
+ // read can also be called on the prototype directly
+ doc = OpenLayers.Format.XML.prototype.read(text);
+ t.eq(doc.nodeType, 9,
+ "doc has the correct node type");
+ t.eq(doc.nodeName, "#document",
+ "doc has the correct node name");
+ t.ok(doc.documentElement,
+ "ok to access doc.documentElement");
+ t.xml_eq(doc.documentElement, text,
+ "doc.documentElement correctly read");
+
+ // where appropriate, make sure doc is loaded into xmldom property
+ if(format.xmldom) {
+ t.xml_eq(format.xmldom.documentElement, text,
+ "xmldom.documentElement contains equivalent xml");
+ }
+
+ // test equivalence with different namespace alias
+ var pre1 =
+ "<pre1:parent xmlns:pre1='http://namespace'>" +
+ "<pre1:child1>value2</pre1:child1>" +
+ "<pre1:child2 pre1:attr1='foo'>value2</pre1:child2>" +
+ "<pre1:child3 chicken:attr='hot' xmlns:chicken='http://soup'/>" +
+ "</pre1:parent>";
+ var pre2 =
+ "<pre2:parent xmlns:pre2='http://namespace'>" +
+ "<pre2:child1>value2</pre2:child1>" +
+ "<pre2:child2 pre2:attr1='foo'>value2</pre2:child2>" +
+ "<pre2:child3 pea:attr='hot' xmlns:pea='http://soup'/>" +
+ "</pre2:parent>";
+ var doc1 = format.read(pre1);
+ t.xml_eq(doc1.documentElement, pre2, "read correctly sets namespaces");
+
+ }
+
+ function test_Format_XML_write(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text);
+ var out = format.write(doc);
+ out = out.replace(/[\r\n]/g, '');
+ out = out.replace( /<\?.*\?>/, '')
+ var expected = text.replace(/<\?.*\?>/, '')
+ t.eq(expected, out,
+ "correctly writes an XML DOM doc");
+ var out = format.write(
+ format.getElementsByTagNameNS(doc,
+ "http://namespace.openlayers.org","root")[0]);
+ out = out.replace(/[\r\n]/g, '');
+ out = out.replace( /<\?.*\?>/, '')
+ t.eq(out, expected,
+ "correctly writes an XML DOM node");
+ }
+
+ function test_Format_XML_createElementNS(t) {
+ t.plan(5);
+
+ var format = new OpenLayers.Format.XML();
+ var uri = "http://foo.com";
+ var prefix = "foo";
+ var localName = "bar";
+ var qualifiedName = prefix + ":" + localName;
+ var node = format.createElementNS(uri, qualifiedName);
+ t.eq(node.nodeType, 1,
+ "node has correct type");
+ t.eq(node.nodeName, qualifiedName,
+ "node has correct qualified name");
+ t.eq(node.prefix, prefix,
+ "node has correct prefix");
+ t.eq(node.namespaceURI, uri,
+ "node has correct namespace uri");
+
+ var doc = format.read(text);
+ if (doc.importNode) {
+ node = doc.importNode(node, true);
+ }
+ t.ok(doc.documentElement.appendChild(node),
+ "node can be appended to a doc root");
+ }
+
+ function test_createDocumentFragment(t) {
+ t.plan(3);
+
+ var format = new OpenLayers.Format.XML();
+ var uri = "http://foo.com";
+ var prefix = "foo";
+ var localName = "bar";
+ var qualifiedName = prefix + ":" + localName;
+ var parent = format.createElementNS(uri, qualifiedName);
+
+ var fragment = format.createDocumentFragment();
+ t.eq(fragment.nodeType, 11, "fragment type");
+
+ try {
+ fragment.appendChild(format.createTextNode("one"));
+ fragment.appendChild(format.createTextNode("two"));
+ t.eq(fragment.childNodes.length, 2, "fragment has two child nodes");
+ } catch (err) {
+ t.fail("trouble appending text nodes to fragment: " + err.message);
+ }
+
+ try {
+ parent.appendChild(fragment);
+ t.eq(parent.childNodes.length, 2, "parent has two child nodes");
+ } catch (err) {
+ t.fail("trouble appending fragment to parent: " + err.message);
+ }
+ }
+
+ function test_Format_XML_createTextNode(t) {
+ t.plan(10);
+
+ var format = new OpenLayers.Format.XML();
+ var value, node;
+
+ value = "string";
+ node = format.createTextNode(value);
+ t.eq(node.nodeType, 3,
+ "[string] node has correct type");
+ t.eq(node.nodeName, "#text",
+ "[string] node has correct name");
+ t.eq(node.nodeValue, "string",
+ "[string] node has correct value");
+
+ value = 0.42;
+ node = format.createTextNode(value);
+ t.eq(node.nodeType, 3,
+ "[number] node has correct type");
+ t.eq(node.nodeName, "#text",
+ "[number] node has correct name");
+ t.eq(node.nodeValue, "0.42",
+ "[number] node has correct value");
+
+ value = false;
+ node = format.createTextNode(value);
+ t.eq(node.nodeType, 3,
+ "[boolean] node has correct type");
+ t.eq(node.nodeName, "#text",
+ "[boolean] node has correct name");
+ t.eq(node.nodeValue, "false",
+ "[boolean] node has correct value");
+
+ var doc = format.read(text);
+ if (doc.importNode) {
+ node = doc.importNode(node, true);
+ }
+ t.ok(doc.documentElement.appendChild(node),
+ "node can be appended to a doc root");
+ }
+
+ function test_Format_XML_getElementsByTagNameNS(t) {
+ t.plan(5);
+
+ var format = new OpenLayers.Format.XML();
+ var olUri = "http://namespace.openlayers.org";
+ var name = "child";
+ var doc = format.read(text);
+ var nodes = format.getElementsByTagNameNS(doc.documentElement,
+ olUri, name);
+ t.eq(nodes.length, 3,
+ "gets correct number of nodes");
+ var qualifiedName = nodes[0].prefix + ":" + name;
+ t.eq(nodes[0].nodeName, qualifiedName,
+ "first node has correct qualified name");
+
+ var defaultUri = "http://namespace.default.net";
+ name = "element";
+ nodes = format.getElementsByTagNameNS(doc.documentElement,
+ defaultUri, name);
+ t.eq(nodes.length, 1,
+ "gets correct number of nodes in default namespace");
+
+ var pList = format.getElementsByTagNameNS(doc.documentElement,
+ olUri, "p");
+ t.eq(pList.length, 1, "got one ol:p element");
+ var p = pList[0];
+
+ var aList = format.getElementsByTagNameNS(p, olUri, "a");
+ t.eq(aList.length, 2, "got two child ol:a elements");
+
+
+
+ }
+
+ function test_Format_XML_getAttributeNodeNS(t) {
+ t.plan(5);
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text);
+ var olUri = "http://namespace.openlayers.org";
+ var taUri = "http://namespace.testattribute.net";
+ var localNodeName = "child";
+ var localAttrName = "attribute";
+ var nodes = format.getElementsByTagNameNS(doc.documentElement,
+ olUri, localNodeName);
+ var attributeNode = format.getAttributeNodeNS(nodes[0],
+ taUri, localAttrName);
+ var qualifiedName = attributeNode.prefix + ":" + localAttrName;
+
+ t.ok(attributeNode,
+ "returns non-null value");
+ t.eq(attributeNode.nodeType, 2,
+ "attribute node has correct type");
+ t.eq(attributeNode.nodeName, qualifiedName,
+ "attribute node has correct qualified name");
+ t.eq(attributeNode.nodeValue, "value1",
+ "attribute node has correct value");
+
+ var nullAttribute = format.getAttributeNodeNS(nodes[0],
+ taUri, "nothing");
+ t.ok(nullAttribute === null,
+ "returns null for nonexistent attribute");
+ }
+
+ function test_Format_XML_getAttributeNS(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text);
+ var olUri = "http://namespace.openlayers.org";
+ var taUri = "http://namespace.testattribute.net";
+ var localNodeName = "child";
+ var localAttrName = "attribute";
+ var nodes = format.getElementsByTagNameNS(doc.documentElement,
+ olUri, localNodeName);
+ var attributeValue = format.getAttributeNS(nodes[0],
+ taUri, localAttrName);
+ t.eq(attributeValue, "value1",
+ "got correct attribute value");
+
+ var emptyValue = format.getAttributeNS(nodes[0],
+ taUri, "nothing");
+ t.ok(emptyValue === "",
+ "returns empty string for nonexistent attributes");
+ }
+
+ function test_Format_XML_hasAttributeNS(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text);
+ var olUri = "http://namespace.openlayers.org";
+ var taUri = "http://namespace.testattribute.net";
+ var localNodeName = "child";
+ var localAttrName = "attribute";
+ var nodes = format.getElementsByTagNameNS(doc.documentElement,
+ olUri, localNodeName);
+ var found = format.hasAttributeNS(nodes[0], taUri, localAttrName);
+ t.ok(found === true, "returns true for good attribute");
+
+ found = format.hasAttributeNS(nodes[0], taUri, "nothing");
+ t.ok(found === false, "returns false for bad attribute");
+ }
+
+ function test_namespaces(t) {
+ t.plan(2);
+
+ var format = new OpenLayers.Format.XML({
+ namespaces: {
+ "def": "http://example.com/default",
+ "foo": "http://example.com/foo",
+ "bar": "http://example.com/bar"
+ },
+ defaultPrefix: "def"
+ });
+
+ // test that prototype has not been altered
+ t.eq(OpenLayers.Format.XML.prototype.namespaces, null,
+ "setting namespaces at construction does not modify prototype");
+
+ // test that namespaceAlias has been set
+ t.eq(format.namespaceAlias["http://example.com/foo"], "foo",
+ "namespaceAlias mapping has been set");
+
+ }
+
+ function test_setNamespace(t) {
+ t.plan(3);
+
+ var format = new OpenLayers.Format.XML();
+
+ // test that namespaces is an object
+ t.ok(format.namespaces instanceof Object, "empty namespace object set");
+
+ format.setNamespace("foo", "http://example.com/foo");
+ t.eq(format.namespaces["foo"], "http://example.com/foo", "alias -> uri mapping set");
+ t.eq(format.namespaceAlias["http://example.com/foo"], "foo", "uri -> alias mapping set");
+
+ }
+
+ function test_readChildNodes(t) {
+
+ var text = "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<container xmlns='http://example.com/foo'>" +
+ "<marker name='my marker 1'>" +
+ "<position>" +
+ "<lon>-180</lon>" +
+ "<lat>90</lat>" +
+ "</position>" +
+ "<detail>some text for first marker</detail>" +
+ "<atom:link xmlns:atom='http://www.w3.org/2005/Atom' href='http://host/path/1'/>" +
+ "</marker>" +
+ "<marker name='my marker 2'>" +
+ "<position>" +
+ "<lon>180</lon>" +
+ "<lat>-90</lat>" +
+ "</position>" +
+ "<detail>some text for second marker</detail>" +
+ "<atom:link xmlns:atom='http://www.w3.org/2005/Atom' href='http://host/path/2'/>" +
+ "</marker>" +
+ "</container>";
+
+ var expect = [
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-180, 90),
+ {
+ name: 'my marker 1',
+ link: 'http://host/path/1',
+ detail: 'some text for first marker'
+ }
+ ),
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(180, -90),
+ {
+ name: 'my marker 2',
+ link: 'http://host/path/2',
+ detail: 'some text for second marker'
+ }
+ )
+ ];
+
+ var format = new OpenLayers.Format.XML({
+ defaultPrefix: "foo",
+ namespaces: {
+ "foo": "http://example.com/foo",
+ "atom": "http://www.w3.org/2005/Atom"
+ },
+ readers: {
+ "foo": {
+ "container": function(node, obj) {
+ var list = [];
+ this.readChildNodes(node, list);
+ obj.list = list;
+ },
+ "marker": function(node, list) {
+ var feature = new OpenLayers.Feature.Vector();
+ feature.attributes.name = node.getAttribute("name");
+ this.readChildNodes(node, feature);
+ list.push(feature);
+ },
+ "position": function(node, feature) {
+ var obj = {};
+ this.readChildNodes(node, obj);
+ feature.geometry = new OpenLayers.Geometry.Point(obj.x, obj.y);
+ },
+ "lon": function(node, obj) {
+ obj.x = this.getChildValue(node);
+ },
+ "lat": function(node, obj) {
+ obj.y = this.getChildValue(node);
+ },
+ "detail": function(node, feature) {
+ feature.attributes.detail = this.getChildValue(node);
+ }
+ },
+ "atom": {
+ "link": function(node, feature) {
+ feature.attributes.link = node.getAttribute("href");
+ }
+ }
+ }
+ });
+
+ // convert text to document node
+ var doc = format.read(text);
+ // read child nodes to get back some object
+ var obj = format.readChildNodes(doc);
+ // start comparing what we got to what we expect
+ var got = obj.list;
+
+ t.plan(11);
+ t.eq(got.length, expect.length, "correct number of items parsed");
+ t.eq(got[0].geometry.x, expect[0].geometry.x, "correct x coord parsed for marker 1");
+ t.eq(got[0].geometry.y, expect[0].geometry.y, "correct y coord parsed for marker 1");
+ t.eq(got[0].attributes.name, expect[0].attributes.name, "correct name parsed for marker 1");
+ t.eq(got[0].attributes.detail, expect[0].attributes.detail, "correct detail parsed for marker 1");
+ t.eq(got[0].attributes.link, expect[0].attributes.link, "correct link parsed for marker 1");
+ t.eq(got[1].geometry.x, expect[1].geometry.x, "correct x coord parsed for marker 2");
+ t.eq(got[1].geometry.y, expect[1].geometry.y, "correct y coord parsed for marker 2");
+ t.eq(got[1].attributes.name, expect[1].attributes.name, "correct name parsed for marker 2");
+ t.eq(got[1].attributes.detail, expect[1].attributes.detail, "correct detail parsed for marker 2");
+ t.eq(got[1].attributes.link, expect[1].attributes.link, "correct link parsed for marker 2");
+
+ }
+
+ function test_writeNode(t) {
+
+ var features = [
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-180, 90),
+ {
+ name: 'my marker 1',
+ link: 'http://host/path/1',
+ detail: 'some text for first marker'
+ }
+ ),
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(180, -90),
+ {
+ name: 'my marker 2',
+ link: 'http://host/path/2',
+ detail: 'some text for second marker'
+ }
+ )
+ ];
+
+ var expect = "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<container xmlns='http://example.com/foo'>" +
+ "<marker name='my marker 1'>" +
+ "<position>" +
+ "<lon>-180</lon>" +
+ "<lat>90</lat>" +
+ "</position>" +
+ "<detail>some text for first marker</detail>" +
+ "<atom:link xmlns:atom='http://www.w3.org/2005/Atom' href='http://host/path/1'/>" +
+ "</marker>" +
+ "<marker name='my marker 2'>" +
+ "<position>" +
+ "<lon>180</lon>" +
+ "<lat>-90</lat>" +
+ "</position>" +
+ "<detail>some text for second marker</detail>" +
+ "<atom:link xmlns:atom='http://www.w3.org/2005/Atom' href='http://host/path/2'/>" +
+ "</marker>" +
+ "</container>";
+
+ var format = new OpenLayers.Format.XML({
+ defaultPrefix: "foo",
+ namespaces: {
+ "foo": "http://example.com/foo",
+ "atom": "http://www.w3.org/2005/Atom"
+ },
+ writers: {
+ "foo": {
+ "container": function(features) {
+ var node = this.createElementNSPlus("container");
+ var feature;
+ for(var i=0; i<features.length; ++i) {
+ feature = features[i];
+ this.writeNode("marker", features[i], node);
+ }
+ return node;
+ },
+ "marker": function(feature) {
+ var node = this.createElementNSPlus("marker", {
+ attributes: {name: feature.attributes.name}
+ });
+ this.writeNode("position", feature.geometry, node);
+ this.writeNode("detail", feature.attributes.detail, node);
+ this.writeNode("atom:link", feature.attributes.link, node);
+ return node;
+ },
+ "position": function(geometry) {
+ var node = this.createElementNSPlus("position");
+ this.writeNode("lon", geometry.x, node);
+ this.writeNode("lat", geometry.y, node);
+ return node;
+ },
+ "lon": function(x) {
+ return this.createElementNSPlus("lon", {
+ value: x
+ });
+ },
+ "lat": function(y) {
+ return this.createElementNSPlus("lat", {
+ value: y
+ });
+ },
+ "detail": function(text) {
+ return this.createElementNSPlus("detail", {
+ value: text
+ });
+ }
+ },
+ "atom": {
+ "link": function(href) {
+ return this.createElementNSPlus("atom:link", {
+ attributes: {href: href}
+ });
+ }
+ }
+ }
+
+ });
+
+ t.plan(1);
+ // test that we get what we expect from writeNode
+ var got = format.writeNode("container", features);
+ t.xml_eq(got, expect, "features correctly written");
+ }
+
+ function test_createElementNSPlus(t) {
+
+ var format = new OpenLayers.Format.XML({
+ defaultPrefix: "def",
+ namespaces: {
+ "def": "http://example.com/default",
+ "foo": "http://example.com/foo",
+ "bar": "http://example.com/bar"
+ }
+ });
+
+ var cases = [
+ {
+ description: "unprefixed name with default options",
+ node: format.createElementNSPlus("FooNode"),
+ expect: "<def:FooNode xmlns:def='http://example.com/default'/>"
+ }, {
+ description: "def prefixed name with default options",
+ node: format.createElementNSPlus("def:FooNode"),
+ expect: "<def:FooNode xmlns:def='http://example.com/default'/>"
+ }, {
+ description: "foo prefixed name with default options",
+ node: format.createElementNSPlus("foo:FooNode"),
+ expect: "<foo:FooNode xmlns:foo='http://example.com/foo'/>"
+ }, {
+ description: "unprefixed name with uri option",
+ node: format.createElementNSPlus("FooNode", {
+ uri: "http://example.com/elsewhere"
+ }),
+ expect: "<FooNode xmlns='http://example.com/elsewhere'/>"
+ }, {
+ description: "foo prefixed name with uri option (overriding format.namespaces)",
+ node: format.createElementNSPlus("foo:FooNode", {
+ uri: "http://example.com/elsewhere"
+ }),
+ expect: "<foo:FooNode xmlns:foo='http://example.com/elsewhere'/>"
+ }, {
+ description: "foo prefixed name with attributes option",
+ node: format.createElementNSPlus("foo:FooNode", {
+ attributes: {
+ "id": "123",
+ "foo:attr1": "namespaced attribute 1",
+ "bar:attr2": "namespaced attribute 2"
+ }
+ }),
+ expect: "<foo:FooNode xmlns:foo='http://example.com/foo' xmlns:bar='http://example.com/bar' id='123' foo:attr1='namespaced attribute 1' bar:attr2='namespaced attribute 2'/>"
+ }, {
+ description: "foo prefixed name with attributes and value options",
+ node: format.createElementNSPlus("foo:FooNode", {
+ attributes: {"id": "123"},
+ value: "text value"
+ }),
+ expect: "<foo:FooNode xmlns:foo='http://example.com/foo' id='123'>text value<" + "/foo:FooNode>"
+ }, {
+ description: "value of 0 gets appended as a text node",
+ node: format.createElementNSPlus("foo:bar", {value: 0}),
+ expect: "<foo:bar xmlns:foo='http://example.com/foo'>0</foo:bar>"
+ }, {
+ description: "value of 0.42 gets appended as a text node",
+ node: format.createElementNSPlus("foo:bar", {value: 0.42}),
+ expect: "<foo:bar xmlns:foo='http://example.com/foo'>0.42</foo:bar>"
+ }, {
+ description: "value of true gets appended as a text node",
+ node: format.createElementNSPlus("foo:bar", {value: true}),
+ expect: "<foo:bar xmlns:foo='http://example.com/foo'>true</foo:bar>"
+ }, {
+ description: "value of false gets appended as a text node",
+ node: format.createElementNSPlus("foo:bar", {value: false}),
+ expect: "<foo:bar xmlns:foo='http://example.com/foo'>false</foo:bar>"
+ }, {
+ description: "null value does not get appended as a text node",
+ node: format.createElementNSPlus("foo:bar", {value: null}),
+ expect: "<foo:bar xmlns:foo='http://example.com/foo'/>"
+ }, {
+ description: "undefined value does not get appended as a text node",
+ node: format.createElementNSPlus("foo:bar"),
+ expect: "<foo:bar xmlns:foo='http://example.com/foo'/>"
+ }
+ ];
+
+ t.plan(cases.length);
+ var test;
+ for(var i=0; i<cases.length; ++i) {
+ test = cases[i];
+ t.xml_eq(test.node, test.expect, test.description);
+ }
+
+ }
+
+ function test_setAttributes(t) {
+
+ var format = new OpenLayers.Format.XML({
+ defaultPrefix: "def",
+ namespaces: {
+ "def": "http://example.com/default",
+ "foo": "http://example.com/foo",
+ "bar": "http://example.com/bar"
+ }
+ });
+
+ var cases = [
+ {
+ description: "unprefixed attribute",
+ node: format.createElementNSPlus("foo:Node"),
+ attributes: {"id": "123"},
+ expect: "<foo:Node xmlns:foo='http://example.com/foo' id='123'/>"
+ }, {
+ description: "foo prefixed attribute",
+ node: format.createElementNSPlus("foo:Node"),
+ attributes: {"foo:id": "123"},
+ expect: "<foo:Node xmlns:foo='http://example.com/foo' foo:id='123'/>"
+ }, {
+ description: "foo prefixed attribute with def prefixed node",
+ node: format.createElementNSPlus("def:Node"),
+ attributes: {"foo:id": "123"},
+ expect: "<def:Node xmlns:def='http://example.com/default' xmlns:foo='http://example.com/foo' foo:id='123'/>"
+ }, {
+ description: "multiple attributes",
+ node: format.createElementNSPlus("def:Node"),
+ attributes: {"id": "123", "foo": "bar"},
+ expect: "<def:Node xmlns:def='http://example.com/default' id='123' foo='bar'/>"
+ }
+ ];
+
+ t.plan(cases.length);
+ var test;
+ for(var i=0; i<cases.length; ++i) {
+ test = cases[i];
+ format.setAttributes(test.node, test.attributes);
+ t.xml_eq(test.node, test.expect, test.description);
+ }
+
+ }
+
+ function test_keepData(t) {
+ t.plan(2);
+
+ var options = {'keepData': true};
+ var format = new OpenLayers.Format.XML(options);
+ format.read(text);
+
+ t.ok(format.data != null, 'data property is not null after read with keepData=true');
+ t.eq(format.data.documentElement.tagName,'ol:root','keepData keeps the right data');
+ }
+
+ function test_getChildValue(t) {
+
+ t.plan(1);
+
+ var text =
+ "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<root>" +
+ "x<!-- comment -->y<!-- comment 2 --><![CDATA[z]]>z<foo />&#x79;" +
+ "</root>";
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text).documentElement;
+
+ t.eq(format.getChildValue(doc), "xyzzy", "child value skips comments, concatenates multiple values, reads through entities");
+
+ }
+
+ function test_getChildEl(t) {
+
+ t.plan(3);
+
+ var text =
+ "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<root>" +
+ "<!-- comment -->" +
+ "<a>x</a>" +
+ "<b>x</b>" +
+ "</root>";
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text).documentElement;
+
+ var a = format.getChildEl(doc);
+ t.eq(a.nodeName, "a", "first element found correctly");
+
+ a = format.getChildEl(doc, "a");
+ t.eq(b, null, "first child element matches the given name");
+
+ var b = format.getChildEl(doc, "b");
+ t.eq(b, null, "first child element does not match the given name");
+
+ }
+
+ function test_getNextEl(t) {
+ t.plan(5);
+
+ var text =
+ "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<root>" +
+ "<!-- comment -->" +
+ "<a>x</a>" +
+ "<!-- comment -->" +
+ "<b xmlns='urn:example'>x</b>" +
+ "</root>";
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text).documentElement;
+
+ var a = format.getChildEl(doc);
+
+ var b = format.getNextEl(a);
+ t.eq(b && b.nodeName, "b", "next element correctly found");
+
+ b = format.getNextEl(a, "b");
+ t.eq(b && b.nodeName, "b", "next element correctly found when name supplied");
+
+ b = format.getNextEl(a, "c");
+ t.eq(b, null, "null returned when name does not match next element");
+
+ b = format.getNextEl(a, null, "urn:example");
+ t.eq(b && b.nodeName, "b", "next element correctly found when namespace supplied");
+
+ b = format.getNextEl(a, null, "foo");
+ t.eq(b, null, "null returned when namespace does not match next element");
+
+ }
+
+ function test_isSimpleContent(t) {
+ t.plan(2);
+
+ var text =
+ "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<root>" +
+ "<!-- comment -->" +
+ "<a>x<!-- comment -->y<!-- comment 2 --><![CDATA[z]]>z<foo />&#x79;</a>" +
+ "<!-- comment -->" +
+ "<b>x<!-- comment -->y<!-- comment 2 --><![CDATA[z]]>z&#x79;</b>" +
+ "</root>";
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text).documentElement;
+
+ var a = format.getChildEl(doc);
+ var b = format.getNextEl(a);
+ t.ok(!format.isSimpleContent(a), "<a> content is not simple");
+ t.ok(format.isSimpleContent(b), "<b> content is simple");
+
+ }
+
+ function test_lookupNamespaceURI(t) {
+
+ t.plan(8);
+
+ var text =
+ "<?xml version='1.0' encoding='UTF-8'?>" +
+ "<root xmlns:baz='urn:baznamespace'>" +
+ "<!-- comment -->" +
+ "<a><foo /></a>" +
+ "<!-- comment -->" +
+ "<b xmlns='urn:example'><!-- comment --><bar foo='value'/></b>" +
+ "</root>";
+
+ var format = new OpenLayers.Format.XML();
+ var doc = format.read(text).documentElement;
+
+ var a = format.getChildEl(doc);
+ t.eq(format.lookupNamespaceURI(a, "baz"), "urn:baznamespace", "prefix lookup on first child");
+
+ var foo = format.getChildEl(a);
+ t.eq(format.lookupNamespaceURI(foo, "baz"), "urn:baznamespace", "prefix lookup on child of first child");
+
+ var b = format.getNextEl(a);
+ t.eq(format.lookupNamespaceURI(b, null), "urn:example", "default namespace lookup on element");
+
+ var bar = format.getChildEl(b);
+ t.eq(format.lookupNamespaceURI(bar, null), "urn:example", "default namespace lookup on child");
+ t.eq(format.lookupNamespaceURI(bar, "baz"), "urn:baznamespace", "prefix lookup on child with different default");
+
+ // test that the alias behaves properly
+ var lookup = OpenLayers.Format.XML.lookupNamespaceURI;
+ t.eq(lookup(bar, "baz"), "urn:baznamespace", "(alias) prefix lookup on child with different default");
+
+ var attr = bar.attributes[0];
+ // Internet Explorer didn't have the ownerElement property until 8.
+ var supportsOwnerElement = !!attr.ownerElement;
+ if(supportsOwnerElement) {
+ t.eq(format.lookupNamespaceURI(attr, null), "urn:example", "default namespace lookup on attribute");
+ t.eq(format.lookupNamespaceURI(attr, "baz"), "urn:baznamespace", "prefix lookup on attribute with different default");
+ } else {
+ t.debug_print("namespace lookup on attributes not supported in this browser");
+ t.ok(true, "namespace lookup on attributes not supported in this browser");
+ t.ok(true, "namespace lookup on attributes not supported in this browser");
+ }
+
+ }
+
+ function test_getXMLDoc(t) {
+ t.plan(2);
+ var format = new OpenLayers.Format.XML();
+ var doc = format.getXMLDoc();
+ t.ok(doc !== document, "document returned from getXMLDoc is not the page's html doc");
+ var root = format.createElementNS("http://test", "root");
+ // appending CDATA created from a different document
+ var cdata = doc.createCDATASection("<foo></foo>");
+ root.appendChild(cdata);
+ var result = format.write(root);
+ var expect = '<root xmlns="http://test"><![CDATA[<foo></foo>]]></root>';
+ t.eq(result, expect, "document with CDATA section serialized correctly");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Format/XML/VersionedOGC.html b/misc/openlayers/tests/Format/XML/VersionedOGC.html
new file mode 100644
index 0000000..ca96d63
--- /dev/null
+++ b/misc/openlayers/tests/Format/XML/VersionedOGC.html
@@ -0,0 +1,51 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var snippet = '<foo version="2.0.0"></foo>';
+ var snippet2 = '<foo></foo>';
+
+ function test_Format_Versioned_constructor(t) {
+ t.plan(5);
+
+ var format = new OpenLayers.Format.XML.VersionedOGC({version: "1.0.0"});
+ t.ok(format instanceof OpenLayers.Format.XML.VersionedOGC,
+ "new OpenLayers.Format.XML.VersionedOGC returns object" );
+ t.eq(format.version, "1.0.0", "constructor sets version correctly");
+ t.eq(format.defaultVersion, null, "defaultVersion should be null if not specified");
+ t.eq(typeof format.read, "function", "format has a read function");
+ t.eq(typeof format.write, "function", "format has a read function");
+ }
+
+ function test_getVersion(t) {
+ t.plan(6);
+ var format = new OpenLayers.Format.XML.VersionedOGC();
+ // read
+ var data = new OpenLayers.Format.XML().read(snippet);
+ var root = data.documentElement;
+ var version = format.getVersion(root);
+ t.eq(version, "2.0.0", "Version taken from document");
+ format = new OpenLayers.Format.XML.VersionedOGC({version: "1.0.0"});
+ version = format.getVersion(root);
+ t.eq(version, "1.0.0", "Version taken from parser takes preference");
+ format = new OpenLayers.Format.XML.VersionedOGC({defaultVersion: "3.0.0"});
+ data = new OpenLayers.Format.XML().read(snippet2);
+ root = data.documentElement;
+ version = format.getVersion(root);
+ t.eq(version, "3.0.0", "If nothing else is set, defaultVersion should be returned");
+ // write
+ version = format.getVersion(null, {version: "1.3.0"});
+ t.eq(version, "1.3.0", "Version from options returned");
+ version = format.getVersion(null);
+ t.eq(version, "3.0.0", "defaultVersion returned if no version specified in options and no version on the format");
+ format.version = "2.1.3";
+ version = format.getVersion(null);
+ t.eq(version, "2.1.3", "version returned of the Format if no version specified in options");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry.html b/misc/openlayers/tests/Geometry.html
new file mode 100644
index 0000000..2a4b4c4
--- /dev/null
+++ b/misc/openlayers/tests/Geometry.html
@@ -0,0 +1,356 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script src="data/geos_wkt_intersects.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function test_Geometry_constructor (t) {
+ t.plan( 2 );
+
+ var g = new OpenLayers.Geometry();
+
+ t.eq(g.CLASS_NAME, "OpenLayers.Geometry", "correct CLASS_NAME")
+ t.ok(OpenLayers.String.startsWith(g.id, "OpenLayers_Geometry_"),
+ "id correctly set");
+ }
+
+
+ function test_Geometry_clone(t) {
+ t.plan(2);
+ var geometry = new OpenLayers.Geometry();
+ var clone = geometry.clone();
+
+ t.eq(clone.CLASS_NAME, "OpenLayers.Geometry", "correct CLASS_NAME")
+ t.ok(OpenLayers.String.startsWith(clone.id, "OpenLayers_Geometry_"),
+ "id correctly set");
+ }
+
+ function test_Geometry_setBounds(t) {
+ t.plan( 2 );
+
+ var g = new OpenLayers.Geometry();
+
+ //null object
+ g.setBounds(null);
+ t.ok(g.bounds == null, "setbounds with null value does not crash or set bounds");
+
+ //no classname object
+ g_clone = {};
+ var object = {
+ 'clone': function() { return g_clone; }
+ };
+ g.setBounds(object);
+ t.ok(g.bounds == g_clone, "setbounds with valid object sets bounds, calls clone");
+ }
+
+ function test_Geometry_extendBounds(t) {
+ t.plan(9);
+
+ OpenLayers.Bounds.prototype._extend =
+ OpenLayers.Bounds.prototype.extend;
+ OpenLayers.Bounds.prototype.extend = function(b) {
+ g_extendBounds = b;
+ };
+
+ var g = new OpenLayers.Geometry();
+
+ //this.bounds null (calculateBounds(), setBounds() called)
+ g.setBounds = function(b) { g_setBounds = b; };
+ g.calculateBounds = function() { g_calculateBounds = {}; };
+ var object = {};
+ g_setBounds = null;
+ g_calculateBounds = null;
+ g_extendBounds = null;
+ g.extendBounds(object);
+ t.ok(g_calculateBounds != null, "calculateBounds() called when this.bounds is null");
+ t.ok(g_setBounds == object, "setBounds() called when this.bounds is null and calculateBounds() is null too");
+ t.ok(g_extendBounds != object, "this.bounds.extend() not called when this.bounds is null and calculateBounds() is null too");
+
+ //this.bounds null (calculateBounds() sets this.bounds:
+ // - setBounds() not called
+ // - this.bounds.extend() called
+ g_calcBounds = new OpenLayers.Bounds(1,2,3,4);
+ g.calculateBounds = function() {
+ g_calculateBounds = {};
+ this.bounds = g_calcBounds;
+ };
+ var object = {};
+
+ g_setBounds = null;
+ g_calculateBounds = null;
+ g_extendBounds = null;
+ g.extendBounds(object);
+ t.ok(g_calculateBounds != null, "calculateBounds() called when this.bounds is null");
+ t.ok(g_setBounds == null, "setBounds() not called when this.bounds is null and calculateBounds() sets this.bounds");
+ t.ok(g_extendBounds == object, "this.bounds.extend() called when this.bounds is null and calculateBounds() sets this.bounds");
+
+
+ //this.bounds non-null thus extend()
+ // - setBounds() not called
+ // - this.bounds.extend() called
+ g_setBounds = null;
+ g_calculateBounds = null;
+ g_extendBounds = null;
+ g.extendBounds(object);
+ t.ok(g_calculateBounds == null, "calculateBounds() not called when this.bounds is non null");
+ t.ok(g_setBounds == null, "setBounds() not called when this.bounds is nonnull");
+ t.ok(g_extendBounds == object, "this.bounds.extend() called when this.bounds is non-null");
+
+ OpenLayers.Bounds.prototype.extend =
+ OpenLayers.Bounds.prototype._extend;
+
+
+ }
+
+ function test_Geometry_getBounds(t) {
+ t.plan(1);
+
+ var g = new OpenLayers.Geometry();
+
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+ g.bounds = testBounds.clone();
+
+ t.ok(g.getBounds().equals(testBounds), "getBounds works");
+ }
+
+ function test_Geometry_atPoint(t) {
+ t.plan(6);
+
+ var g = new OpenLayers.Geometry();
+
+ var lonlat = null;
+ var lon = 5;
+ var lat = 10;
+
+ //null lonlat
+ g.bounds = new OpenLayers.Bounds();
+
+ var atPoint = g.atPoint(lonlat, lon, lat);
+ t.ok(!atPoint, "null lonlat")
+
+ //null this.bounds
+ g.bounds = null;
+ lonlat = new OpenLayers.LonLat(1,2);
+
+ atPoint = g.atPoint(lonlat, lon, lat);
+ t.ok(!atPoint, "null this.bounds")
+
+ //toleranceLon/toleranceLat
+
+ //default toleranceLon/toleranceLat
+ OpenLayers.Bounds.prototype._containsLonLat = OpenLayers.Bounds.prototype.containsLonLat;
+ g_Return = {};
+ OpenLayers.Bounds.prototype.containsLonLat = function(ll) {
+ g_bounds = this;
+ return g_Return;
+ }
+
+ var testBounds = new OpenLayers.Bounds(10,20,30,40);
+ g.bounds = testBounds.clone();
+ lonlat = new OpenLayers.LonLat(20,30);
+
+ g_bounds = null;
+ atPoint = g.atPoint(lonlat);
+ t.ok(g_bounds.equals(testBounds), "default toleranceLon/Lat are 0");
+ t.ok(atPoint == g_Return, "default toleranceLon/Lat returns correctly");
+
+ //real toleranceLon/toleranceLat
+ var testBounds = new OpenLayers.Bounds(10,20,30,40);
+ g.bounds = testBounds.clone();
+ lonlat = new OpenLayers.LonLat(20,30);
+
+ g_bounds = null;
+ atPoint = g.atPoint(lonlat, lon, lat);
+ testBounds.left -= lon;
+ testBounds.bottom -= lat;
+ testBounds.right += lon;
+ testBounds.top += lat;
+ t.ok(g_bounds.equals(testBounds), "real toleranceLon/Lat are 0");
+ t.ok(atPoint == g_Return, "real toleranceLon/Lat returns correctly");
+
+ OpenLayers.Bounds.prototype.containsLonLat = OpenLayers.Bounds.prototype._containsLonLat;
+
+ }
+
+ function test_Geometry_getLength(t) {
+ t.plan(1);
+
+ var g = new OpenLayers.Geometry();
+
+ t.eq(g.getLength(), 0, "getLength is 0");
+ }
+
+ function test_Geometry_getArea(t) {
+ t.plan(1);
+
+ var g = new OpenLayers.Geometry();
+
+ t.eq(g.getArea(), 0, "getArea is 0");
+ }
+
+ function test_Geometry_clearBounds(t) {
+ t.plan(2);
+
+ var g = new OpenLayers.Geometry();
+ g.parent = new OpenLayers.Geometry();
+
+ g.bounds = "foo";
+ g.parent.bounds = "bar";
+
+ g.clearBounds();
+ t.ok(g.bounds == null, "bounds is correctly cleared");
+ t.ok(g.parent.bounds == null, "parent geometry bounds is correctly cleared");
+ }
+
+ function test_Geometry_destroy(t) {
+ t.plan( 2 );
+
+ var g = new OpenLayers.Geometry();
+ g.bounds = new OpenLayers.Bounds();
+
+ g_style_destroy = null;
+ g.destroy();
+
+ t.eq(g.id, null, "id nullified");
+
+ t.eq(g.bounds, null, "bounds nullified");
+
+ }
+
+ function test_Geometry_intersects_geos_wkt(t) {
+ var wkt = new OpenLayers.Format.WKT();
+ var failures = [];
+ var intersect12, intersect21, msg;
+ for (var i = 0; i < geos_test_data.length; i++) {
+ var testcase = geos_test_data[i];
+ f1 = wkt.read(testcase['wkt1']);
+ f2 = wkt.read(testcase['wkt2']);
+ intersect12 = f1.geometry.intersects(f2.geometry);
+ intersect21 = f2.geometry.intersects(f1.geometry);
+ if(intersect12 != testcase.result) {
+ msg = "f1 should " + (testcase.result ? "" : "not ") +
+ "intersect f2: f1 = '" + testcase['wkt1'] + "' " +
+ "f2 = '" + testcase['wkt2'] + "'";
+ failures.push(msg);
+ }
+ if(intersect21 != testcase.result) {
+ msg = "f2 should " + (testcase.result ? "" : "not ") +
+ "intersect f1: f1 = '" + testcase['wkt1'] + "' " +
+ "f2 = '" + testcase['wkt2'] + "'";
+ failures.push(msg);
+ }
+ }
+ if(failures.length == 0) {
+ t.plan(1);
+ t.ok(true, "all " + geos_test_data.length + " geos tests pass");
+ } else {
+ t.plan(failures.length);
+ for(var f=0; f<failures.length; ++f) {
+ t.fail(failures[f]);
+ }
+ }
+ }
+
+ function test_distanceToSegment(t) {
+ var dist = OpenLayers.Geometry.distanceToSegment;
+
+ var cases = [{
+ got: dist({x: 0, y: 0}, {x1: 0, y1: 1, x2: 1, y2: 1}),
+ expected: {distance: 1, x: 0, y: 1, along: 0}
+ }, {
+ got: dist({x: 0, y: 0}, {x1: -1, y1: -1, x2: 0, y2: -1}),
+ expected: {distance: 1, x: 0, y: -1, along: 1}
+ }, {
+ got: dist({x: 0, y: 0}, {x1: -1, y1: -1, x2: 1, y2: 1}),
+ expected: {distance: 0, x: 0, y: 0, along: 0.5}
+ }, {
+ got: dist({x: 1, y: 1}, {x1: 2, y1: 0, x2: 2, y2: 3}),
+ expected: {distance: 1, x: 2, y: 1, along: 1/3.}
+ }, {
+ got: dist({x: -1, y: -1}, {x1: -2, y1: -2, x2: -1, y2: -3}),
+ expected: {distance: Math.sqrt(2), x: -2, y: -2, along: 0}
+ }, {
+ got: dist({x: -1, y: 1}, {x1: -3, y1: 1, x2: -1, y2: 3}),
+ expected: {distance: Math.sqrt(2), x: -2, y: 2, along: 0.5}
+ }];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, "case " + i);
+ }
+
+ }
+
+ function test_fromWKT(t) {
+
+ var cases = [{
+ wkt: "POINT(1 2)",
+ geom: new OpenLayers.Geometry.Point(1, 2)
+ }, {
+ wkt: "MULTIPOINT((3.5 5.6),(4.8 10.5))",
+ geom: new OpenLayers.Geometry.MultiPoint([
+ new OpenLayers.Geometry.Point(3.5, 5.6),
+ new OpenLayers.Geometry.Point(4.8, 10.5)
+ ])
+ }, {
+ wkt: "LINESTRING(1 2, 3 4)",
+ geom: new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(1, 2),
+ new OpenLayers.Geometry.Point(3, 4)
+ ])
+ }, {
+ wkt: "POLYGON((0 0, 0 4, 4 4, 4 0, 0 0),(1 1, 1 3, 3 3, 3 1, 1 1))",
+ geom: new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(0, 4),
+ new OpenLayers.Geometry.Point(4, 4),
+ new OpenLayers.Geometry.Point(4, 0),
+ new OpenLayers.Geometry.Point(0, 0)
+ ]),
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(1, 1),
+ new OpenLayers.Geometry.Point(1, 3),
+ new OpenLayers.Geometry.Point(3, 3),
+ new OpenLayers.Geometry.Point(3, 1),
+ new OpenLayers.Geometry.Point(1, 1)
+ ])
+ ])
+ }, {
+ wkt: "GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))",
+ geom: new OpenLayers.Geometry.Collection([
+ new OpenLayers.Geometry.Point(4, 6),
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(4, 6),
+ new OpenLayers.Geometry.Point(7, 10)
+ ])
+ ])
+ }];
+
+ t.plan(cases.length);
+ var wkt = OpenLayers.Geometry.fromWKT;
+ for(var i=0; i<cases.length; ++i) {
+ t.geom_eq(wkt(cases[i].wkt), cases[i].geom, "case " + i);
+ }
+ }
+
+ function test_fromWKT_undefined(t) {
+ t.plan(1);
+
+ var WKT = OpenLayers.Format.WKT;
+ OpenLayers.Format.WKT = null;
+ delete OpenLayers.Geometry.fromWKT.format;
+
+ var geom = OpenLayers.Geometry.fromWKT("POINT(1 1)");
+ t.eq(geom, undefined, "undefined when OpenLayers.Format.WKT is not available");
+
+ OpenLayers.Format.WKT = WKT;
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/Collection.html b/misc/openlayers/tests/Geometry/Collection.html
new file mode 100644
index 0000000..7c9fd62
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/Collection.html
@@ -0,0 +1,431 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var coll;
+
+ function test_Collection_constructor (t) {
+ t.plan( 4 );
+
+ //null param
+ coll = new OpenLayers.Geometry.Collection();
+ t.ok( coll instanceof OpenLayers.Geometry.Collection, "new OpenLayers.Geometry.Collection returns coll object" );
+ t.eq( coll.CLASS_NAME, "OpenLayers.Geometry.Collection", "coll.CLASS_NAME is set correctly");
+ t.eq( coll.components.length, 0, "coll.components is set correctly");
+
+ OpenLayers.Geometry.Collection.prototype._addComponents =
+ OpenLayers.Geometry.Collection.prototype.addComponents;
+ OpenLayers.Geometry.Collection.prototype.addComponents =
+ function(comps) { g_addcomponents = comps; };
+
+ //valid param
+ g_addcomponents = null;
+ var components = {};
+ coll = new OpenLayers.Geometry.Collection(components);
+ t.ok(g_addcomponents, components, "addcomponents called on non-null param")
+
+ OpenLayers.Geometry.Collection.prototype.addComponents =
+ OpenLayers.Geometry.Collection.prototype._addComponents;
+ }
+
+ function test_Collection_addComponents (t) {
+ t.plan( 10 );
+
+ coll = new OpenLayers.Geometry.Collection();
+
+ //null
+ coll.addComponents(null);
+ t.ok(true, "doesn't break on add null components");
+
+ OpenLayers.Geometry.Collection.prototype._addComponent =
+ OpenLayers.Geometry.Collection.prototype.addComponent;
+
+ OpenLayers.Geometry.Collection.prototype.addComponent =
+ function(comp) { g_addComp = comp; g_added++};
+
+
+ //nonarray argument
+ var g_added = 0;
+ var g_addComp = 0;
+ var component = {};
+ coll.addComponents(component);
+ t.eq(g_added, 1, "added once");
+ t.eq(g_addComp, component, "added component");
+
+ //array arg
+ var g_added = 0;
+ var g_addComp = 0;
+ var component1 = {};
+ var component2 = {};
+ coll.addComponents([component1, component2]);
+ t.eq(g_added, 2, "added twice");
+ t.eq(g_addComp, component2, "added component");
+
+ OpenLayers.Geometry.Collection.prototype.addComponent =
+ OpenLayers.Geometry.Collection.prototype._addComponent;
+
+
+
+ coll.addComponents(new OpenLayers.Geometry.Point(0,0));
+ coll.addComponents(new OpenLayers.Geometry.Point(10,10));
+ t.eq( coll.components.length, 2, "added two components to collection" );
+ bounds = coll.getBounds();
+ t.eq( bounds.left, 0, "left bound is 0" );
+ t.eq( bounds.bottom, 0, "bottom bound is 0" );
+ t.eq( bounds.right, 10, "right bound is 10" );
+ t.eq( bounds.top, 10, "top bound is 10" );
+ }
+
+ function test_Collection_clone (t) {
+ t.plan( 3 );
+ coll = new OpenLayers.Geometry.Collection();
+ coll.addComponents(new OpenLayers.Geometry.Point(0,0));
+ coll.addComponents(new OpenLayers.Geometry.Point(10,10));
+ coll2 = coll.clone();
+ t.ok( coll2 instanceof OpenLayers.Geometry.Collection, "coll.clone() returns collection object" );
+ t.eq( coll2.components.length, 2, "coll2.components.length is set correctly");
+ t.ok( coll2.components[0] instanceof OpenLayers.Geometry.Point,
+ "coll2.components.length is set correctly");
+ }
+
+ function test_Collection_removeComponents (t) {
+ t.plan( 5 );
+ coll = new OpenLayers.Geometry.Collection();
+ point = new OpenLayers.Geometry.Point(0,0);
+ coll.addComponents(point);
+ coll.addComponents(new OpenLayers.Geometry.Point(10,10));
+ coll.removeComponents(coll.components[0]);
+ t.eq( coll.components.length, 1, "coll.components.length is smaller after removeComponent" );
+ t.ok( coll.bounds == null, "bounds are nullified after call to remove (to trigger recalc on getBounds()");
+ bounds = coll.getBounds();
+ t.eq( bounds.left, 10, "left bound is 10 after removeComponent" );
+ t.eq( bounds.bottom, 10, "bottom bound is 10 after removeComponent" );
+
+ coll = new OpenLayers.Geometry.Collection();
+ for(var i=0; i<5; ++i) {
+ coll.addComponents(
+ new OpenLayers.Geometry.Point(Math.random(), Math.random())
+ );
+ }
+ coll.removeComponents(coll.components);
+ t.eq(coll.components.length, 0,
+ "remove components even works with multiple components");
+
+ }
+
+ function test_Collection_calculateBounds(t) {
+ t.plan( 9 );
+
+ var coll = new OpenLayers.Geometry.Collection();
+ coll.calculateBounds();
+ t.eq(coll.bounds, null, "null components list gives null bounds on calculation()");
+
+ var p1 = new OpenLayers.Geometry.Point(10,20);
+ var p2 = new OpenLayers.Geometry.Point(30,40);
+
+ var components = [p1, p2];
+ coll = new OpenLayers.Geometry.Collection(components);
+
+ coll.calculateBounds();
+
+ t.eq(coll.bounds.left, 10, "good left bounds");
+ t.eq(coll.bounds.bottom, 20, "good bottom bounds");
+ t.eq(coll.bounds.right, 30, "good right bounds");
+ t.eq(coll.bounds.top, 40, "good top bounds");
+
+ var newPoint = new OpenLayers.Geometry.Point(60,70);
+ coll.addComponent(newPoint);
+ coll.calculateBounds();
+
+ t.eq(coll.bounds.left, 10, "good left bounds");
+ t.eq(coll.bounds.bottom, 20, "good bottom bounds");
+ t.eq(coll.bounds.right, 60, "good right bounds");
+ t.eq(coll.bounds.top, 70, "good top bounds");
+ }
+
+ function test_Collection_equals(t) {
+ t.plan(1);
+ var geom = new OpenLayers.Geometry.Collection();
+ t.ok(!geom.equals(), "collection.equals() returns false for undefined");
+ }
+
+ function test_Collection_addComponent(t) {
+ t.plan(10);
+
+ var coll = new OpenLayers.Geometry.Collection();
+
+ //null
+ coll.addComponent(null);
+ t.ok(!coll.addComponent(null),
+ "addComponent returns false for bad component")
+
+ //good component
+ var component = new OpenLayers.Geometry.Point(3,4);
+ t.ok(coll.addComponent(component),
+ "addComponent returns true for good component");
+ t.ok(coll.bounds == null, "bounds cache correctly cleared");
+
+ var foundComponent = false;
+ for(var i=0; i< coll.components.length; i++) {
+ if (coll.components[i].equals(component)) {
+ foundComponent = true;
+ }
+ }
+ t.ok(foundComponent, "component added to internal array");
+
+ // restricted components
+ coll.componentTypes = ["OpenLayers.Geometry.Point",
+ "OpenLayers.Geometry.LineString"];
+ var point1 = new OpenLayers.Geometry.Point(0,0);
+ var point2 = new OpenLayers.Geometry.Point(1,1);
+ var line = new OpenLayers.Geometry.LineString([point1, point2]);
+ var multipoint = new OpenLayers.Geometry.MultiPoint([point1, point2]);
+
+ t.ok(coll.addComponent(point1),
+ "addComponent returns true for 1st geometry type in componentTypes");
+ t.ok(OpenLayers.Util.indexOf(coll.components, point1) > -1,
+ "addComponent adds 1st restricted type to components array");
+ t.ok(coll.addComponent(line),
+ "addComponent returns true for 2nd geometry type in componentTypes");
+ t.ok(OpenLayers.Util.indexOf(coll.components, point1) > -1,
+ "addComponent adds 2nd restricted type to components array");
+ t.ok(!coll.addComponent(multipoint),
+ "addComponent returns false for geometry type not in componentTypes");
+ t.ok(OpenLayers.Util.indexOf(coll.components, multipoint) == -1,
+ "addComponent doesn't add restricted type to component array");
+
+ }
+
+ function test_collection_getLength(t) {
+ t.plan(2);
+
+ //null
+ var coll = new OpenLayers.Geometry.Collection();
+ t.eq( coll.getLength(), 0, "null coll has 0 getlength");
+
+ //valid
+ coll.components = [
+ { 'getLength': function() { return 50; } },
+ { 'getLength': function() { return 15; } }
+ ];
+ t.eq( coll.getLength(), 65, "coll with valid components correctly sums getlength");
+ }
+
+ function test_collection_getArea(t) {
+ t.plan(2);
+
+ //null
+ var coll = new OpenLayers.Geometry.Collection();
+ t.eq( coll.getArea(), 0, "null coll has 0 getArea");
+
+ //valid
+ coll.components = [
+ { 'getArea': function() { return 50; } },
+ { 'getArea': function() { return 15; } }
+ ];
+ t.eq( coll.getArea(), 65, "coll with valid components correctly sums getArea");
+ }
+
+ function test_transform(t) {
+ t.plan(5);
+ var p1 = new OpenLayers.Geometry.Point(0,0);
+ p1.bounds = "foo";
+ var p2 = new OpenLayers.Geometry.Point(1,1);
+ p2.bounds = "foo";
+ var line = new OpenLayers.Geometry.LineString([p1, p2]);
+ var multipoint = new OpenLayers.Geometry.MultiPoint([p1, p2]);
+ var coll = new OpenLayers.Geometry.Collection([
+ p1, p2, line, multipoint
+ ]);
+ coll.bounds = "foo";
+
+ var wgs84 = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+ coll.transform(wgs84, sm);
+
+ t.eq(coll.bounds, null, "coll bounds cleared");
+ t.eq(p1.bounds, null, "p1 component bounds cleared");
+ t.eq(p2.bounds, null, "p2 component bounds cleared");
+ t.eq(line.bounds, null, "line component bounds cleared");
+ t.eq(multipoint.bounds, null, "multipoint component bounds cleared");
+
+ }
+
+ function test_getCentroid_pts_only(t) {
+ t.plan(3);
+
+ coll = new OpenLayers.Geometry.Collection();
+ coll.addComponent(new OpenLayers.Geometry.Point(0,0));
+ coll.addComponent(new OpenLayers.Geometry.Point(1,1));
+
+ centroid = coll.getCentroid(true);
+ t.ok(centroid != null, 'The centroid is not null.');
+ t.eq(centroid.x, 0.5, 'The centroid x coordinate is good.');
+ t.eq(centroid.y, 0.5, 'The centroid y coordinate is good.');
+
+ coll.destroy();
+ }
+
+ function test_getCentroid_poly_nonrecursive(t) {
+ t.plan(3);
+
+ coll = new OpenLayers.Geometry.Collection();
+ coll.addComponent(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,1),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(1,0)
+ ])
+ ])
+ );
+ // performing non-recursive getCentroid means this next polygon
+ // is excluded from the centroid computation
+ coll.addComponent(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(2,2),
+ new OpenLayers.Geometry.Point(2,3),
+ new OpenLayers.Geometry.Point(3,3),
+ new OpenLayers.Geometry.Point(3,2)
+ ])
+ ])
+ );
+
+ centroid = coll.getCentroid();
+ t.ok(centroid != null, 'The centroid is not null.');
+ t.eq(centroid.x, 0.5, 'The centroid x coordinate is good.');
+ t.eq(centroid.y, 0.5, 'The centroid y coordinate is good.');
+
+ coll.destroy();
+ }
+
+ function test_getCentroid_poly_only(t) {
+ t.plan(3);
+
+ coll = new OpenLayers.Geometry.Collection();
+ coll.addComponent(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,1),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(1,0)
+ ])
+ ])
+ );
+
+ centroid = coll.getCentroid(true);
+ t.ok(centroid != null, 'The centroid is not null.');
+ t.eq(centroid.x, 0.5, 'The centroid x coordinate is good.');
+ t.eq(centroid.y, 0.5, 'The centroid y coordinate is good.');
+
+ coll.destroy();
+ }
+
+ function test_getCentroid_poly_and_pts(t) {
+ t.plan(3);
+
+ coll = new OpenLayers.Geometry.Collection();
+ coll.addComponent(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,1),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(1,0)
+ ])
+ ])
+ );
+
+ // since the polygon above has an area of 1 and these
+ // points have an area of 0, they should not change the centroid
+ coll.addComponent( new OpenLayers.Geometry.Point(2,2) );
+ coll.addComponent( new OpenLayers.Geometry.Point(4,4) );
+
+ centroid = coll.getCentroid(true);
+ t.ok(centroid != null, 'The centroid is not null.');
+ t.eq(centroid.x, 0.5, 'The centroid x coordinate is good.');
+ t.eq(centroid.y, 0.5, 'The centroid y coordinate is good.');
+
+ coll.destroy();
+ }
+
+ function test_getCentroid_poly_big_and_small(t) {
+ t.plan(3);
+
+ coll = new OpenLayers.Geometry.Collection();
+ // polygon w/area=1, centroid=0.5,0.5
+ coll.addComponent(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,1),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(1,0)
+ ])
+ ])
+ );
+
+ // since the polygon above has an area of 1 and this
+ // polygon has an area of 4, the center is weighted 20% toward
+ // the first polygon
+
+ // polygon w/area=4, centroid=5.5,5.5
+ coll.addComponent(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(4.5,-0.5),
+ new OpenLayers.Geometry.Point(4.5,1.5),
+ new OpenLayers.Geometry.Point(6.5,1.5),
+ new OpenLayers.Geometry.Point(6.5,-0.5)
+ ])
+ ])
+ );
+
+ centroid = coll.getCentroid(true);
+ t.ok(centroid != null, 'The centroid is not null.');
+ t.eq(centroid.x, 4.5, 'The centroid x coordinate is good.');
+ t.eq(centroid.y, 0.5, 'The centroid y coordinate is good.');
+
+ coll.destroy();
+ }
+
+ function test_avoid_infinite_recursion(t) {
+ t.plan(1);
+
+ var g = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing(),
+ new OpenLayers.Geometry.LinearRing()
+ ]);
+ var bounds;
+ try {
+ bounds = g.getBounds();
+ t.eq(bounds, null, "Polygon with empty linear ring has null bounds");
+ } catch (err) {
+ t.fail("Failed to get bounds of polygon with empty linear ring: " + err.message);
+ }
+
+ }
+
+
+ function test_Collection_destroy(t) {
+ t.plan( 3 );
+ coll = new OpenLayers.Geometry.Collection();
+ coll.addComponents(new OpenLayers.Geometry.Point(0,0));
+ coll.addComponents(new OpenLayers.Geometry.Point(10,10));
+ coll.getBounds();
+ coll.destroy();
+
+ t.ok(coll.components == null, "components array cleared");
+ t.ok(coll.getBounds() == null, "bounds is cleared");
+ t.ok(coll.id == null, "id is cleared");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/Curve.html b/misc/openlayers/tests/Geometry/Curve.html
new file mode 100644
index 0000000..5afebdf
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/Curve.html
@@ -0,0 +1,157 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var curve;
+ var components = [new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(0,0)];
+
+ function test_Curve_constructor (t) {
+ t.plan( 3 );
+ curve = new OpenLayers.Geometry.Curve();
+ t.ok( curve instanceof OpenLayers.Geometry.Curve, "new OpenLayers.Geometry.Curve returns curve object" );
+ t.eq( curve.CLASS_NAME, "OpenLayers.Geometry.Curve", "curve.CLASS_NAME is set correctly");
+ t.eq( curve.components, [], "curve.components is set correctly");
+ }
+
+ function test_Curve_constructor (t) {
+ t.plan( 2 );
+ curve = new OpenLayers.Geometry.Curve(components);
+ t.ok( curve instanceof OpenLayers.Geometry.Curve, "new OpenLayers.Geometry.Curve returns curve object" );
+ t.eq( curve.components.length, 2, "curve.components.length is set correctly");
+ }
+
+ function test_Curve_clone (t) {
+ t.plan( 2 );
+ curve = new OpenLayers.Geometry.Curve(components);
+ curve2 = curve.clone();
+ t.ok( curve2 instanceof OpenLayers.Geometry.Curve, "curve.clone() returns curve object" );
+ t.eq( curve2.components.length, 2, "curve2.components.length is set correctly");
+ }
+
+ function test_Curve_calculateBounds(t) {
+ t.plan( 17 );
+
+
+ var curve = new OpenLayers.Geometry.Curve();
+ curve.calculateBounds();
+ t.eq(curve.bounds, null, "bounds null when no components");
+
+ var p1 = new OpenLayers.Geometry.Point(10,20);
+ var p2 = new OpenLayers.Geometry.Point(30,40);
+
+ var components = [p1, p2];
+ var curve = new OpenLayers.Geometry.Curve(components);
+
+ curve.calculateBounds();
+
+ t.eq(curve.bounds.left, 10, "good left bounds");
+ t.eq(curve.bounds.bottom, 20, "good bottom bounds");
+ t.eq(curve.bounds.right, 30, "good right bounds");
+ t.eq(curve.bounds.top, 40, "good top bounds");
+
+ var newPoint = new OpenLayers.Geometry.Point(60,70);
+ curve.addComponent(newPoint);
+ curve.calculateBounds();
+
+ t.eq(curve.bounds.left, 10, "good left bounds");
+ t.eq(curve.bounds.bottom, 20, "good bottom bounds");
+ t.eq(curve.bounds.right, 60, "good right bounds");
+ t.eq(curve.bounds.top, 70, "good top bounds");
+
+ //nullifying the bounds
+
+ //before calculation
+ curve = new OpenLayers.Geometry.Curve(components);
+ curve.bounds = null;
+ curve.calculateBounds();
+
+ t.eq(curve.bounds.left, 10, "good left bounds");
+ t.eq(curve.bounds.bottom, 20, "good bottom bounds");
+ t.eq(curve.bounds.right, 30, "good right bounds");
+ t.eq(curve.bounds.top, 40, "good top bounds");
+
+ //before addComponent
+ curve.bounds = null;
+ curve.addComponent(newPoint);
+ curve.calculateBounds();
+
+ t.eq(curve.bounds.left, 10, "good left bounds");
+ t.eq(curve.bounds.bottom, 20, "good bottom bounds");
+ t.eq(curve.bounds.right, 60, "good right bounds");
+ t.eq(curve.bounds.top, 70, "good top bounds");
+
+ }
+
+ function test_Curve_addComponent (t) {
+ t.plan( 8 );
+ curve = new OpenLayers.Geometry.Curve(components);
+ curve.addComponent(new OpenLayers.Geometry.Point(20,30));
+ bounds = curve.getBounds();
+ t.eq( curve.components.length, 3, "new point added to array" );
+ t.eq( bounds.top, 30, "top bound is 30 after addComponent" );
+ t.eq( bounds.right, 20, "right bound is 20 after addComponent" );
+ curve.addComponent(new OpenLayers.Geometry.Point(-20,-30), 1);
+ bounds = curve.getBounds();
+ t.eq( curve.components.length, 4, "new point added to array" );
+ t.eq( bounds.bottom, -30, "bottom bound is -30 after 2nd addComponent" );
+ t.eq( bounds.left, -20, "left bound is 20 after 2nd addComponent" );
+ t.eq( curve.components[1].x, -20, "new point.lon is -20 (index worked)" );
+ t.eq( curve.components[1].y, -30, "new point.lat is -30 (index worked)" );
+ }
+
+ function test_Curve_removeComponent (t) {
+ t.plan( 4 );
+ curve = new OpenLayers.Geometry.Curve(components);
+ curve.removeComponent(curve.components[1]);
+ t.eq( curve.components.length, 1, "curve.components.length is smaller after removeComponent" );
+ t.eq( curve.bounds, null, "curve.bounds nullified after removeComponent (for recalculation)" );
+ bounds = curve.getBounds();
+ t.eq( bounds.left, 10, "left bound is 10 after removeComponent" );
+ t.eq( bounds.bottom, 10, "bottom bound is 10 after removeComponent" );
+ }
+
+ function test_Curve_getLength (t) {
+ t.plan( 4 );
+
+ //no components
+ curve = new OpenLayers.Geometry.Curve();
+ curve.components = null;
+ t.eq(curve.getLength(), 0, "curve with no components has length 0");
+
+ //empty components
+ curve.components = [];
+ t.eq(curve.getLength(), 0, "curve with empty components has length 0");
+
+ //single point curve
+ curve.components = [ new OpenLayers.Geometry.Point(0,0) ];
+ t.eq(curve.getLength(), 0, "curve with only one point has length 0");
+
+ //multipoint
+ var newcomponents = [ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,10),
+ new OpenLayers.Geometry.Point(20,10),
+ new OpenLayers.Geometry.Point(20,-10)
+ ];
+
+ curve = new OpenLayers.Geometry.Curve(newcomponents);
+ t.eq(curve.getLength(), 50, "curve.getLength returns a reasonably accurate length" );
+ }
+
+ function test_Curve_destroy(t) {
+ t.plan(1);
+
+ var curve = new OpenLayers.Geometry.Curve();
+ curve.components = {};
+
+ curve.destroy();
+
+ t.ok( curve.components == null, "components is cleared well in destruction");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/LineString.html b/misc/openlayers/tests/Geometry/LineString.html
new file mode 100644
index 0000000..4b2ec0e
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/LineString.html
@@ -0,0 +1,443 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var line;
+ var components = [new OpenLayers.Geometry.Point(10,15),
+ new OpenLayers.Geometry.Point(0,0)];
+
+ function test_LineString_constructor (t) {
+ t.plan( 3 );
+ line = new OpenLayers.Geometry.LineString();
+ t.ok( line instanceof OpenLayers.Geometry.LineString, "new OpenLayers.Geometry.LineString returns line object" );
+ t.eq( line.CLASS_NAME, "OpenLayers.Geometry.LineString", "line.CLASS_NAME is set correctly");
+ t.eq( line.components, [], "line.components is set correctly");
+ }
+
+ function test_LineString_constructor (t) {
+ t.plan( 3 );
+ line = new OpenLayers.Geometry.LineString(components);
+ t.ok( line instanceof OpenLayers.Geometry.LineString, "new OpenLayers.Geometry.LineString returns line object" );
+ t.eq( line.CLASS_NAME, "OpenLayers.Geometry.LineString", "line.CLASS_NAME is set correctly");
+ // TBD FIXME, recursion
+ // t.eq( line.components, components, "line.components is set correctly");
+ t.eq( line.components.length, 2, "line.components.length is set correctly");
+ }
+
+ function test_LineString_toString(t) {
+ t.plan(1);
+
+ line = new OpenLayers.Geometry.LineString(components);
+ t.eq(line.toString(),
+ "LINESTRING(10 15,0 0)",
+ "toString() returns WKT");
+ }
+
+ function test_LineString_removeComponent(t) {
+ t.plan(2);
+
+ OpenLayers.Geometry.Collection.prototype._removeComponent =
+ OpenLayers.Geometry.Collection.prototype.removeComponent;
+ OpenLayers.Geometry.Collection.prototype.removeComponent =
+ function(point) { g_removeComponent = point; };
+
+ line = new OpenLayers.Geometry.LineString(components);
+
+ g_removeComponent = null;
+ line.removeComponent(components[0]);
+ t.ok(g_removeComponent == null, "point not removed if only 2 points in components");
+
+ line.components.push(new OpenLayers.Geometry.Point(4,4));
+ line.removeComponent(components[0]);
+ t.ok(g_removeComponent, components[0], "point removed if 3 points in components");
+
+ OpenLayers.Geometry.Collection.prototype.removeComponent =
+ OpenLayers.Geometry.Collection.prototype._removeComponent;
+ }
+
+ function test_LineString_move(t) {
+ t.plan(4);
+
+ var components = [new OpenLayers.Geometry.Point(10,15),
+ new OpenLayers.Geometry.Point(0,0)];
+ var line = new OpenLayers.Geometry.LineString(components);
+
+ var x0 = components[0].x;
+ var y0 = components[0].y;
+ var x1 = components[1].x;
+ var y1 = components[1].y;
+
+ var dx = 10 * Math.random();
+ var dy = 10 * Math.random();
+ line.move(dx, dy);
+
+ t.eq(line.components[0].x, x0 + dx, "move() correctly modifies first x");
+ t.eq(line.components[0].y, y0 + dy, "move() correctly modifies first y");
+ t.eq(line.components[1].x, x1 + dx, "move() correctly modifies second x");
+ t.eq(line.components[1].y, y1 + dy, "move() correctly modifies second y");
+ }
+
+ function test_LineString_rotate(t) {
+ t.plan(6);
+
+ var components = [new OpenLayers.Geometry.Point(10,15),
+ new OpenLayers.Geometry.Point(0,0)];
+ var geometry = new OpenLayers.Geometry.LineString(components);
+
+ var originals = [];
+ var comp;
+ var angle = 2 * Math.PI * Math.random();
+ var origin = new OpenLayers.Geometry.Point(10 * Math.random(),
+ 10 * Math.random());
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp = geometry.components[i];
+ originals[i] = comp.rotate;
+ comp.rotate = function(a, o) {
+ t.ok(true, "rotate called for component " + i);
+ t.ok(a == angle, "rotate called with correct angle");
+ t.ok(o == origin, "rotate called with correct origin");
+ }
+ }
+ geometry.rotate(angle, origin);
+
+ // restore the original rotate defs
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp.rotate = originals[i];
+ }
+ }
+
+ function test_LineString_resize(t) {
+ t.plan(8);
+
+ var tolerance = 1e-10;
+
+ var components = [new OpenLayers.Geometry.Point(10 * Math.random(),
+ 10 * Math.random()),
+ new OpenLayers.Geometry.Point(10 * Math.random(),
+ 10 * Math.random())];
+ var geometry = new OpenLayers.Geometry.LineString(components);
+
+ var origin = new OpenLayers.Geometry.Point(10 * Math.random(),
+ 10 * Math.random());
+
+ var scale = 10 * Math.random();
+
+ var oldLength = geometry.getLength();
+ var ret = geometry.resize(scale, origin);
+ t.ok(ret === geometry, "resize returns geometry");
+ var newLength = geometry.getLength();
+ t.ok((((newLength / oldLength) - scale) / scale) < tolerance,
+ "resize correctly changes the length of a linestring")
+
+ var originals = [];
+ var comp;
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp = geometry.components[i];
+ originals[i] = comp.resize;
+ comp.resize = function(s, o) {
+ t.ok(true, "resize called for component " + i);
+ t.ok(s == scale, "resize called with correct scale");
+ t.ok(o == origin, "resize called with correct origin");
+ }
+ }
+ geometry.resize(scale, origin);
+
+ // restore the original resize defs
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp.resize = originals[i];
+ }
+
+ }
+
+ function test_split(t) {
+ var wkt = OpenLayers.Geometry.fromWKT;
+
+ var cases = [{
+ msg: "no intersection",
+ g1: "LINESTRING(0 0, 0 1)",
+ g2: "LINESTRING(1 0, 1 1)",
+ exp: null
+ } , {
+ msg: "intersection at midpoint",
+ g1: "LINESTRING(0 0, 1 1)",
+ g2: "LINESTRING(1 0, 0 1)",
+ exp: ["LINESTRING(1 0, 0.5 0.5)", "LINESTRING(0.5 0.5, 0 1)"]
+ }, {
+ msg: "intersection at midpoint (reverse source/target)",
+ g1: "LINESTRING(1 0, 0 1)",
+ g2: "LINESTRING(0 0, 1 1)",
+ exp: ["LINESTRING(0 0, 0.5 0.5)", "LINESTRING(0.5 0.5, 1 1)"]
+ }, {
+ msg: "intersection at endpoint",
+ g1: "LINESTRING(0 0, 1 1)",
+ g2: "LINESTRING(1 0, 1 1)",
+ exp: null
+ }, {
+ msg: "midpoint intersection, no options",
+ g1: "LINESTRING(0 0, 2 2)",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: ["LINESTRING(0 2, 1 1)", "LINESTRING(1 1, 2 0)"]
+ }, {
+ msg: "midpoint intersection, edge false",
+ opt: {edge: false},
+ g1: "LINESTRING(0 0, 2 2)",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: null
+ }, {
+ msg: "midpoint intersection, mutual",
+ opt: {mutual: true},
+ g1: "LINESTRING(0 0, 2 2)",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: [["LINESTRING(0 0, 1 1)", "LINESTRING(1 1, 2 2)"], ["LINESTRING(0 2, 1 1)", "LINESTRING(1 1, 2 0)"]]
+ }, {
+ msg: "close intersection, no tolerance",
+ g1: "LINESTRING(0 0, 0.9 0.9)",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: null
+ }, {
+ msg: "close intersection, within tolerance",
+ opt: {tolerance: 0.2},
+ g1: "LINESTRING(0 0, 0.9 0.9)",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: ["LINESTRING(0 2, 0.9 0.9)", "LINESTRING(0.9 0.9, 2 0)"]
+ }];
+
+ t.plan(cases.length);
+ var c, parts, part, midparts;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ var g1 = wkt(c.g1);
+ var g2 = wkt(c.g2);
+ var got = g1.split(g2, c.opt);
+ var exp = c.exp;
+ if(got instanceof Array) {
+ parts = [];
+ for(var j=0; j<got.length; ++j) {
+ part = got[j];
+ if(part instanceof Array) {
+ midparts = [];
+ for(var k=0; k<part.length; ++k) {
+ midparts.push(part[k].toString());
+ }
+ parts.push("[" + midparts.join(", ") + "]");
+ } else {
+ parts.push(got[j].toString());
+ }
+ }
+ got = parts.join(", ");
+ }
+ if(exp instanceof Array) {
+ parts = [];
+ for(var j=0; j<exp.length; ++j) {
+ part = exp[j];
+ if(part instanceof Array) {
+ midparts = [];
+ for(var k=0; k<part.length; ++k) {
+ midparts.push(wkt(part[k]).toString());
+ }
+ parts.push("[" + midparts.join(", ") + "]");
+ } else {
+ parts.push(wkt(exp[j]).toString());
+ }
+ }
+ exp = parts.join(", ");
+ }
+ t.eq(got, exp, "case " + i + ": " + c.msg);
+ }
+
+ }
+
+
+ function test_distanceTo(t) {
+ var wkt = OpenLayers.Geometry.fromWKT;
+ var geoms = [
+ wkt("POINT(0 0)"),
+ wkt("LINESTRING(-2 0, 0 -2, 2 -1, 2 0)")
+ ];
+
+ var cases = [{
+ got: geoms[1].distanceTo(geoms[0]),
+ expected: Math.sqrt(2)
+ }, {
+ got: geoms[1].distanceTo(geoms[0], {details: true}),
+ expected: {
+ distance: Math.sqrt(2),
+ x0: -1, y0: -1,
+ x1: 0, y1: 0
+ }
+ }];
+
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, "case " + i);
+ }
+
+ }
+
+ function test_LineString_equals(t) {
+ t.plan(3);
+
+ var x0 = Math.random() * 100;
+ var y0 = Math.random() * 100;
+ var x1 = Math.random() * 100;
+ var y1 = Math.random() * 100;
+ var point0 = new OpenLayers.Geometry.Point(x0, y0);
+ var point1 = new OpenLayers.Geometry.Point(x1, y1);
+ var geometry = new OpenLayers.Geometry.LineString([point0, point1]);
+ var equal = new OpenLayers.Geometry.LineString([point0, point1]);
+ var offX = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(x0 + 1, y0),
+ new OpenLayers.Geometry.Point(x1 + 1, y1)]);
+ var offY = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(x0, y0 + 1),
+ new OpenLayers.Geometry.Point(x1, y1 + 1)]);
+ t.ok(geometry.equals(equal),
+ "equals() returns true for a geometry with equivalent coordinates");
+ t.ok(!geometry.equals(offX),
+ "equals() returns false for a geometry with offset x");
+ t.ok(!geometry.equals(offY),
+ "equals() returns false for a geometry with offset y");
+ }
+
+
+ function test_getVertices(t) {
+ t.plan(14);
+
+ var points = [
+ new OpenLayers.Geometry.Point(10, 20),
+ new OpenLayers.Geometry.Point(20, 30),
+ new OpenLayers.Geometry.Point(30, 40),
+ new OpenLayers.Geometry.Point(40, 50)
+ ];
+ var line = new OpenLayers.Geometry.LineString(points);
+
+ var verts = line.getVertices();
+ t.ok(verts instanceof Array, "got back an array");
+ t.eq(verts.length, points.length, "of correct length length");
+ t.geom_eq(verts[0], points[0], "0: correct geometry");
+ t.geom_eq(verts[1], points[1], "1: correct geometry");
+ t.geom_eq(verts[2], points[2], "2: correct geometry");
+ t.geom_eq(verts[3], points[3], "3: correct geometry");
+
+ // get nodes only
+ var nodes = line.getVertices(true);
+ t.ok(nodes instanceof Array, "[nodes only] got back an array");
+ t.eq(nodes.length, 2, "[nodes only] of correct length length");
+ t.geom_eq(nodes[0], points[0], "[nodes only] first: correct geometry");
+ t.geom_eq(nodes[1], points[points.length-1], "[nodes only] last: correct geometry");
+
+ // no nodes
+ var nodes = line.getVertices(false);
+ t.ok(nodes instanceof Array, "[no nodes] got back an array");
+ t.eq(nodes.length, 2, "[no nodes] of correct length length");
+ t.geom_eq(nodes[0], points[1], "[no nodes] first: correct geometry");
+ t.geom_eq(nodes[1], points[2], "[no nodes] last: correct geometry");
+
+ }
+
+
+ function test_LineString_clone(t) {
+ t.plan(2);
+
+ var x0 = Math.random() * 100;
+ var y0 = Math.random() * 100;
+ var x1 = Math.random() * 100;
+ var y1 = Math.random() * 100;
+ var point0 = new OpenLayers.Geometry.Point(x0, y0);
+ var point1 = new OpenLayers.Geometry.Point(x1, y1);
+ var geometry = new OpenLayers.Geometry.LineString([point0, point1]);
+ var clone = geometry.clone();
+ t.ok(clone instanceof OpenLayers.Geometry.LineString,
+ "clone() creates an OpenLayers.Geometry.LineString");
+ t.ok(geometry.equals(clone), "clone has equivalent coordinates");
+ }
+
+ function test_getGeodesicLength(t) {
+
+ // expected values from http://www.movable-type.co.uk/scripts/latlong-vincenty.html
+ var cases = [{
+ wkt: "LINESTRING(0 0, -10 45)",
+ exp: 5081689.690
+ }, {
+ wkt: "LINESTRING(-10 45, 0 0)",
+ exp: 5081689.690
+ }, {
+ wkt: "LINESTRING(0 0, -10 45, -20 50)",
+ exp: 5081689.690 + 935018.062
+ }];
+ t.plan(cases.length);
+
+ var geom, got;
+ for(var i=0; i<cases.length; ++i) {
+ geom = new OpenLayers.Geometry.fromWKT(cases[i].wkt);
+ got = geom.getGeodesicLength();
+ t.eq(Math.round(got), Math.round(cases[i].exp), "[case " + i + "] length calculated");
+ }
+
+ }
+
+ function test_LineString_simplify(t){
+ t.plan(8);
+ var ls1 = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(1,2.1),
+ new OpenLayers.Geometry.Point(1.8,3.8),
+ new OpenLayers.Geometry.Point(2,4),
+ new OpenLayers.Geometry.Point(3,4),
+ new OpenLayers.Geometry.Point(4,4.5),
+ new OpenLayers.Geometry.Point(5,5)
+
+ ]);
+ var ls2 = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(1,2.1),
+ new OpenLayers.Geometry.Point(1.8,3.8),
+ new OpenLayers.Geometry.Point(2,4),
+ new OpenLayers.Geometry.Point(3,4),
+ new OpenLayers.Geometry.Point(4,4.5),
+ new OpenLayers.Geometry.Point(5,5),
+ new OpenLayers.Geometry.Point(0,0)
+
+ ]);
+ var ls3 = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(1,1)
+ ]);
+ var ls5 = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(2,2),
+ new OpenLayers.Geometry.Point(3,3),
+ new OpenLayers.Geometry.Point(4,4),
+ new OpenLayers.Geometry.Point(5,5)
+
+ ]);
+ var ls6 = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(3,2)
+ ]);
+
+ t.ok(ls1 instanceof OpenLayers.Geometry.LineString, 'LineString is instance of OpenLayers.Geometry.LineString');
+ var simplified1 = ls1.simplify(0.5);
+ t.ok(simplified1 instanceof OpenLayers.Geometry.LineString, 'Simplified LineString is instance of OpenLayers.Geometry.LineString');
+ t.ok(simplified1.getVertices().length <= ls1.getVertices().length, 'Simplified LineString has less or equal number of vertices');
+ // The simplified version is derived from PostGIS function ST_SIMPLIFY()
+ t.ok(simplified1.toString() === 'LINESTRING(0 0,1.8 3.8,5 5)', 'LineString 1 was simplified correctly');
+ var simplified2 = ls2.simplify(0.5);
+ // The simplified version is derived from PostGIS function ST_SIMPLIFY()
+ t.ok(simplified2.toString() === 'LINESTRING(0 0,1.8 3.8,5 5,0 0)', 'LineString 2 was simplified correctly');
+ var simplified3 = ls3.simplify(0.5);
+ t.ok(simplified3.toString() === ls3.toString(), 'LineString with 2 vertices is left untouched');
+ var simplified5 = ls5.simplify(0.0);
+ t.ok(simplified5.toString() === 'LINESTRING(0 0,5 5)', 'A tolerance of 0 returns the optimized version needless vertices');
+ var simplified6 = ls6.simplify(0.0);
+ t.ok(simplified6.toString() === 'LINESTRING(0 0,1 1,3 2)', 'A tolerance of 0 returns the optimized version without doubled vertices');
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/LinearRing.html b/misc/openlayers/tests/Geometry/LinearRing.html
new file mode 100644
index 0000000..cbbba2a
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/LinearRing.html
@@ -0,0 +1,362 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var line;
+ var components = [new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(0,0)];
+
+ function test_LinearRing_constructor (t) {
+ t.plan( 6 );
+
+ //null
+ ring = new OpenLayers.Geometry.LinearRing();
+ t.ok( ring instanceof OpenLayers.Geometry.LinearRing, "new OpenLayers.Geometry.LinearRing returns ring object" );
+ t.eq( ring.CLASS_NAME, "OpenLayers.Geometry.LinearRing", "ring.CLASS_NAME is set correctly");
+ t.eq( ring.components, [], "ring.components is set correctly");
+
+ //valid components
+ ring = new OpenLayers.Geometry.LinearRing(components);
+ t.ok( ring instanceof OpenLayers.Geometry.LinearRing, "new OpenLayers.Geometry.LinearRing returns ring object" );
+ t.eq( ring.CLASS_NAME, "OpenLayers.Geometry.LinearRing", "ring.CLASS_NAME is set correctly");
+ t.eq( ring.components.length, 3, "ring.components.length is set correctly");
+ }
+
+ function test_LinearRing_addComponent(t) {
+ t.plan(13);
+
+ var ring = new OpenLayers.Geometry.LinearRing();
+
+ var point = new OpenLayers.Geometry.Point(0,0);
+ t.ok(ring.addComponent(point),
+ "addComponent returns true for 1st point");
+ t.eq(ring.components.length, 2, "add first point, correct length");
+ t.ok(ring.components[0].equals(point), "point one correct");
+ t.ok(ring.components[0] === ring.components[ring.components.length - 1],
+ "first and last point are the same");
+
+ newPoint = new OpenLayers.Geometry.Point(10,10);
+ t.ok(ring.addComponent( newPoint ),
+ "addComponent returns true for unique point");
+ t.eq(ring.components.length, 3, "correctly adds 3rd point");
+ t.ok(ring.components[0].equals(point), "point one correct");
+ t.ok(ring.components[1].equals(newPoint), "point one correct");
+ t.ok(ring.components[0] === ring.components[ring.components.length - 1],
+ "first and last point are the same");
+
+ var length = ring.components.length;
+ var clone = ring.components[length - 1].clone();
+ t.ok(!ring.addComponent(clone),
+ "addComponent returns false for adding a duplicate last point");
+ t.eq(ring.components.length, length,
+ "components remains unchanged after trying to add duplicate point");
+ t.ok(ring.addComponent(clone, length - 1),
+ "addComponent returns true when adding a duplicate with an index");
+ t.eq(ring.components.length, length + 1,
+ "components increase in length after adding a duplicate point with index");
+
+ }
+
+ function test_LinearRing_removeComponent(t) {
+ t.plan(10);
+
+ var components = [new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,10),
+ new OpenLayers.Geometry.Point(15,15),
+ new OpenLayers.Geometry.Point(10,0)
+ ];
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+
+ ring.removeComponent( ring.components[2] );
+ t.eq(ring.components.length, 4, "removing from linear ring with 5 points: length ok");
+ t.ok(ring.components[0].equals(components[0]), "point one correct");
+ t.ok(ring.components[1].equals(components[1]), "point two correct");
+ t.ok(ring.components[2].equals(components[3]), "point three correct");
+ t.ok(ring.components[0] === ring.components[ring.components.length - 1],
+ "first and last point are the same");
+
+ var testBounds = new OpenLayers.Bounds(0,0,10,10);
+ var ringBounds = ring.getBounds();
+ t.ok(ringBounds.equals(testBounds), "bounds correctly recalculated");
+
+ ring.removeComponent( ring.components[2] );
+ ring.removeComponent( ring.components[1] );
+ t.eq(ring.components.length, 3, "cant remove from linear ring with only 3 points. new length ok");
+ t.ok(ring.components[0].equals(components[0]), "point one correct");
+ t.ok(ring.components[1].equals(components[1]), "point two correct");
+ t.ok(ring.components[0] === ring.components[ring.components.length - 1],
+ "first and last point are the same");
+
+ }
+
+ function test_LinearRing_getArea(t) {
+ t.plan(1);
+ var components = [new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,10),
+ new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(10,0)
+ ];
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+
+ t.eq(ring.getArea(), 100, "getArea works lovely");
+ }
+
+ function test_LinearRing_getLength(t) {
+ t.plan(1);
+ var components = [
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,10),
+ new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(10,0)
+ ];
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+ t.eq(ring.getLength(), 40, "getLength returns the correct perimiter");
+ }
+
+ function test_LinearRing_getCentroid(t) {
+ t.plan(2);
+ var components = [
+ new OpenLayers.Geometry.Point(0,0),
+ new OpenLayers.Geometry.Point(0,10),
+ new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(10,0)
+ ];
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+ var centroid = ring.getCentroid();
+ t.ok(centroid.x === 5 && centroid.y === 5, "getCentroid returns the correct centroid");
+ ring.destroy();
+
+ ring = new OpenLayers.Geometry.LinearRing();
+ t.eq(ring.getCentroid(), null, "getCentroid returns null if no components");
+ }
+
+ function test_LinearRing_move(t) {
+
+ var nvert = 4,
+ x = new Array(nvert),
+ y = new Array(nvert),
+ components = new Array(nvert);
+
+ t.plan(2 * (nvert + 1));
+
+ for(var i=0; i<nvert; ++i) {
+ x[i] = Math.random();
+ y[i] = Math.random();
+ components[i] = new OpenLayers.Geometry.Point(x[i], y[i]);
+ }
+ x.push(x[0]);
+ y.push(y[0]);
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+
+ var dx = Math.random();
+ var dy = Math.random();
+
+ ring.move(dx, dy);
+
+ for(var j=0; j<nvert + 1; ++j) {
+ t.eq(ring.components[j].x, x[j] + dx,
+ "move correctly adjust x coord of " + j + " component");
+ t.eq(ring.components[j].y, y[j] + dy,
+ "move correctly adjust y coord of " + j + " component");
+ }
+ }
+
+ function test_LinearRing_rotate(t) {
+ t.plan(10);
+
+ var components = [
+ new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(11,10),
+ new OpenLayers.Geometry.Point(11,11),
+ new OpenLayers.Geometry.Point(10,11)
+ ];
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+
+ // rotate a quarter turn around the origin
+ var origin = new OpenLayers.Geometry.Point(0, 0);
+ var angle = 90;
+
+ ring.rotate(angle, origin);
+
+ function withinTolerance(i, j) {
+ return Math.abs(i - j) < 1e-9;
+ }
+
+ t.ok(withinTolerance(ring.components[0].x , -10),
+ "rotate correctly adjusts x of component 0");
+ t.ok(withinTolerance(ring.components[0].y, 10),
+ "rotate correctly adjusts y of component 0");
+ t.ok(withinTolerance(ring.components[1].x, -10),
+ "rotate correctly adjusts x of component 1");
+ t.ok(withinTolerance(ring.components[1].y, 11),
+ "rotate correctly adjusts y of component 1");
+ t.ok(withinTolerance(ring.components[2].x, -11),
+ "rotate correctly adjusts x of component 2");
+ t.ok(withinTolerance(ring.components[2].y, 11),
+ "rotate correctly adjusts y of component 2");
+ t.ok(withinTolerance(ring.components[3].x, -11),
+ "rotate correctly adjusts x of component 3");
+ t.ok(withinTolerance(ring.components[3].y, 10),
+ "rotate correctly adjusts y of component 3");
+ t.ok(withinTolerance(ring.components[4].x, -10),
+ "rotate correctly adjusts x of component 4");
+ t.ok(withinTolerance(ring.components[4].y, 10),
+ "rotate correctly adjusts y of component 4");
+ }
+
+ function test_LinearRing_resize(t) {
+ t.plan(10);
+
+ var components = [
+ new OpenLayers.Geometry.Point(10,10),
+ new OpenLayers.Geometry.Point(11,10),
+ new OpenLayers.Geometry.Point(11,11),
+ new OpenLayers.Geometry.Point(10,11)
+ ];
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+
+ // rotate a quarter turn around the origin
+ var origin = new OpenLayers.Geometry.Point(0, 0);
+ var scale = Math.random();
+
+ ring.resize(scale, origin);
+
+ function withinTolerance(i, j) {
+ return Math.abs(i - j) < 1e-9;
+ }
+
+ t.ok(withinTolerance(ring.components[0].x , 10 * scale),
+ "resize correctly adjusts x of component 0");
+ t.ok(withinTolerance(ring.components[0].y, 10 * scale),
+ "resize correctly adjusts y of component 0");
+ t.ok(withinTolerance(ring.components[1].x, 11 * scale),
+ "resize correctly adjusts x of component 1");
+ t.ok(withinTolerance(ring.components[1].y, 10 * scale),
+ "resize correctly adjusts y of component 1");
+ t.ok(withinTolerance(ring.components[2].x, 11 * scale),
+ "resize correctly adjusts x of component 2");
+ t.ok(withinTolerance(ring.components[2].y, 11 * scale),
+ "resize correctly adjusts y of component 2");
+ t.ok(withinTolerance(ring.components[3].x, 10 * scale),
+ "resize correctly adjusts x of component 3");
+ t.ok(withinTolerance(ring.components[3].y, 11 * scale),
+ "resize correctly adjusts y of component 3");
+ t.ok(withinTolerance(ring.components[4].x, 10 * scale),
+ "resize correctly adjusts x of component 4");
+ t.ok(withinTolerance(ring.components[4].y, 10 * scale),
+ "resize correctly adjusts y of component 4");
+ }
+
+ function test_containsPoint(t) {
+
+ /**
+ * The ring:
+ * edge 3
+ * (5, 10) __________ (15, 10)
+ * / /
+ * edge 4 / / edge 2
+ * / /
+ * (0, 0) /_________/ (10, 0)
+ * edge 1
+ */
+ var components = [
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(10, 0),
+ new OpenLayers.Geometry.Point(15, 10),
+ new OpenLayers.Geometry.Point(5, 10)
+ ];
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+
+ function p(x, y) {
+ return new OpenLayers.Geometry.Point(x, y);
+ }
+
+ // contains: 1 (touches), true (within), false (outside)
+ var cases = [{
+ point: p(5, 5), contains: true
+ }, {
+ point: p(20, 20), contains: false
+ }, {
+ point: p(15, 15), contains: false
+ }, {
+ point: p(0, 0), contains: 1 // lower left corner
+ }, {
+ point: p(10, 0), contains: 1 // lower right corner
+ }, {
+ point: p(15, 10), contains: 1 // upper right corner
+ }, {
+ point: p(5, 10), contains: 1 // upper left corner
+ }, {
+ point: p(5, 0), contains: 1 // on edge 1
+ }, {
+ point: p(5, -0.1), contains: false // below edge 1
+ }, {
+ point: p(5, 0.1), contains: true // above edge 1
+ }, {
+ point: p(12.5, 5), contains: 1 // on edge 2
+ }, {
+ point: p(12.4, 5), contains: true // left of edge 2
+ }, {
+ point: p(12.6, 5), contains: false // right of edge 2
+ }, {
+ point: p(10, 10), contains: 1 // on edge 3
+ }, {
+ point: p(10, 9.9), contains: true // below edge 3
+ }, {
+ point: p(10, 10.1), contains: false // above edge 3
+ }, {
+ point: p(2.5, 5), contains: 1 // on edge 4
+ }, {
+ point: p(2.4, 5), contains: false // left of edge 4
+ }, {
+ point: p(2.6, 5), contains: true // right of edge 4
+ }];
+
+ var len = cases.length;
+ t.plan(len);
+ var c;
+ for (var i=0; i<len; ++i) {
+ c = cases[i];
+ t.eq(ring.containsPoint(c.point), c.contains, "case " + i + ": " + c.point);
+ }
+ }
+
+ function test_containsPoint_precision(t) {
+
+ /**
+ * The test for linear ring containment was sensitive to failure when
+ * looking for ray crossings on nearly vertical edges. With a loss
+ * of precision in calculating the x-coordinate for the crossing,
+ * the method would erronously determine that the x-coordinate was
+ * not within the (very narrow) x-range of the nearly vertical edge.
+ *
+ * The test below creates a polygon whose first vertical edge is
+ * nearly horizontal. The test point lies "far" outside the polygon
+ * and we expect the containsPoint method to return false.
+ */
+
+ t.plan(1);
+
+ var components = [
+ new OpenLayers.Geometry.Point(10000020.000001, 1000000),
+ new OpenLayers.Geometry.Point(10000020.000002, 1000010), // nearly vertical
+ new OpenLayers.Geometry.Point(10000030, 1000010),
+ new OpenLayers.Geometry.Point(10000030, 1000000)
+ ];
+
+ var ring = new OpenLayers.Geometry.LinearRing(components);
+ var point = new OpenLayers.Geometry.Point(10000000, 1000001);
+
+ t.eq(ring.containsPoint(point), false, "false for point outside polygon with nearly vertical edge");
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/MultiLineString.html b/misc/openlayers/tests/Geometry/MultiLineString.html
new file mode 100644
index 0000000..34a6e65
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/MultiLineString.html
@@ -0,0 +1,267 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var line;
+
+ function test_MultiLineString_constructor (t) {
+ t.plan( 3 );
+ mline = new OpenLayers.Geometry.MultiLineString();
+ t.ok( mline instanceof OpenLayers.Geometry.MultiLineString, "new OpenLayers.Geometry.MultiLineString returns mline object" );
+ t.eq( mline.CLASS_NAME, "OpenLayers.Geometry.MultiLineString", "mline.CLASS_NAME is set correctly");
+ t.eq( mline.components, [], "line.components is set correctly");
+ }
+
+ function test_MultiLineString_constructor (t) {
+ t.plan( 3 );
+ line = new OpenLayers.Geometry.LineString();
+ mline = new OpenLayers.Geometry.MultiLineString(line);
+ t.ok( mline instanceof OpenLayers.Geometry.MultiLineString, "new OpenLayers.Geometry.MultiLineString returns mline object" );
+ t.eq( mline.CLASS_NAME, "OpenLayers.Geometry.MultiLineString", "mline.CLASS_NAME is set correctly");
+ t.eq( mline.components.length, 1, "mline.components.length is set correctly");
+ }
+
+ function test_split(t) {
+ var wkt = OpenLayers.Geometry.fromWKT;
+
+ var cases = [{
+ msg: "no intersection",
+ g1: "MULTILINESTRING((0 0, 0 1), (2 2, 3 3))",
+ g2: "MULTILINESTRING((1 0, 1 1), (2 2, 3 2))",
+ exp: null
+ } , {
+ msg: "intersection at midpoint",
+ g1: "MULTILINESTRING((0 0, 1 1))",
+ g2: "MULTILINESTRING((1 0, 0 1))",
+ exp: ["MULTILINESTRING((1 0, 0.5 0.5))", "MULTILINESTRING((0.5 0.5, 0 1))"]
+ }, {
+ msg: "intersection at midpoint (reverse source/target)",
+ g1: "MULTILINESTRING((1 0, 0 1))",
+ g2: "MULTILINESTRING((0 0, 1 1))",
+ exp: ["MULTILINESTRING((0 0, 0.5 0.5))", "MULTILINESTRING((0.5 0.5, 1 1))"]
+ }, {
+ msg: "intersection at endpoint",
+ g1: "MULTILINESTRING((0 0, 1 1))",
+ g2: "MULTILINESTRING((1 0, 1 1))",
+ exp: null
+ }, {
+ msg: "midpoint intersection, no options",
+ g1: "MULTILINESTRING((0 0, 2 2))",
+ g2: "MULTILINESTRING((0 2, 2 0))",
+ exp: ["MULTILINESTRING((0 2, 1 1))", "MULTILINESTRING((1 1, 2 0))"]
+ }, {
+ msg: "midpoint intersection, edge false",
+ opt: {edge: false},
+ g1: "MULTILINESTRING((0 0, 2 2))",
+ g2: "MULTILINESTRING((0 2, 2 0))",
+ exp: null
+ }, {
+ msg: "midpoint intersection, mutual",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 0, 2 2))",
+ g2: "MULTILINESTRING((0 2, 2 0))",
+ exp: [["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2))"], ["MULTILINESTRING((0 2, 1 1))", "MULTILINESTRING((1 1, 2 0))"]]
+ }, {
+ msg: "close intersection, no tolerance",
+ g1: "MULTILINESTRING((0 0, 0.9 0.9))",
+ g2: "MULTILINESTRING((0 2, 2 0))",
+ exp: null
+ }, {
+ msg: "close intersection, within tolerance",
+ opt: {tolerance: 0.2},
+ g1: "MULTILINESTRING((0 0, 0.9 0.9))",
+ g2: "MULTILINESTRING((0 2, 2 0))",
+ exp: ["MULTILINESTRING((0 2, 0.9 0.9))", "MULTILINESTRING((0.9 0.9, 2 0))"]
+ }, {
+ msg: "multi source, single target",
+ g1: "MULTILINESTRING((0 0, 2 2))",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: ["LINESTRING(0 2, 1 1)", "LINESTRING(1 1, 2 0)"]
+ }, {
+ msg: "multi source, single target, mutual split",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 0, 2 2))",
+ g2: "LINESTRING(0 2, 2 0)",
+ exp: [["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2))"], ["LINESTRING(0 2, 1 1)", "LINESTRING(1 1, 2 0)"]]
+ }, {
+ msg: "single source, multi target",
+ g1: "LINESTRING(0 2, 2 0)",
+ g2: "MULTILINESTRING((0 0, 2 2))",
+ exp: ["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2))"]
+ }, {
+ msg: "partial target split",
+ g1: "MULTILINESTRING((2 0, 0 2))",
+ g2: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4))",
+ exp: ["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2), (3 3, 4 4))"]
+ }, {
+ msg: "partial target split, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((2 0, 0 2))",
+ g2: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4))",
+ exp: [["MULTILINESTRING((2 0, 1 1))", "MULTILINESTRING((1 1, 0 2))"], ["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2), (3 3, 4 4))"]]
+ }, {
+ msg: "partial source split, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4))",
+ g2: "MULTILINESTRING((2 0, 0 2))",
+ exp: [["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2), (3 3, 4 4))"], ["MULTILINESTRING((2 0, 1 1))", "MULTILINESTRING((1 1, 0 2))"]]
+ }, {
+ msg: "partial target split with source endpoint",
+ g1: "MULTILINESTRING((1 0, 1 1))",
+ g2: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4))",
+ exp: ["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2), (3 3, 4 4))"]
+ }, {
+ msg: "partial target split with source endpoint, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((5 5, 6 6), (1 0, 1 1))",
+ g2: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4))",
+ exp: [[], ["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2), (3 3, 4 4))"]]
+ }, {
+ msg: "partial source split with target endpoint",
+ g1: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4))",
+ g2: "MULTILINESTRING((1 0, 1 1))",
+ exp: null
+ }, {
+ msg: "partial source split with target endpoint, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 0, 2 2), (3 3, 4 4), (5 5, 6 6))",
+ g2: "MULTILINESTRING((1 0, 1 1))",
+ exp: [["MULTILINESTRING((0 0, 1 1))", "MULTILINESTRING((1 1, 2 2), (3 3, 4 4), (5 5, 6 6))"], []]
+ }, {
+ msg: "partial target and source split",
+ g1: "MULTILINESTRING((0 5, 2 5), (4 5, 6 5), (8 5, 10 5))",
+ g2: "MULTILINESTRING((5 0, 5 2), (5 4, 5 6), (5 8, 5 10))",
+ exp: ["MULTILINESTRING((5 0, 5 2), (5 4, 5 5))", "MULTILINESTRING((5 5, 5 6), (5 8, 5 10))"]
+ }, {
+ msg: "partial target and source split, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 5, 2 5), (4 5, 6 5), (8 5, 10 5))",
+ g2: "MULTILINESTRING((5 0, 5 2), (5 4, 5 6), (5 8, 5 10))",
+ exp: [["MULTILINESTRING((0 5, 2 5), (4 5, 5 5))", "MULTILINESTRING((5 5, 6 5), (8 5, 10 5))"],
+ ["MULTILINESTRING((5 0, 5 2), (5 4, 5 5))", "MULTILINESTRING((5 5, 5 6), (5 8, 5 10))"]]
+ }, {
+ msg: "partial target and source split with source endpoint, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 5, 2 5), (4 5, 6 5), (8 5, 10 5))",
+ g2: "MULTILINESTRING((4 0, 4 2), (4 4, 4 6), (4 8, 4 10))",
+ exp: [[], ["MULTILINESTRING((4 0, 4 2), (4 4, 4 5))", "MULTILINESTRING((4 5, 4 6), (4 8, 4 10))"]]
+ }, {
+ msg: "partial target and source split with target endpoint, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((4 0, 4 2), (4 4, 4 6), (4 8, 4 10))",
+ g2: "MULTILINESTRING((0 5, 2 5), (4 5, 6 5), (8 5, 10 5))",
+ exp: [["MULTILINESTRING((4 0, 4 2), (4 4, 4 5))", "MULTILINESTRING((4 5, 4 6), (4 8, 4 10))"], []]
+ }, {
+ msg: "partial target and source split with source vertex, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((0 5, 2 5), (4 5, 5 5, 6 5), (8 5, 10 5))",
+ g2: "MULTILINESTRING((5 0, 5 2), (5 4, 5 6), (5 8, 5 10))",
+ exp: [["MULTILINESTRING((0 5, 2 5), (4 5, 5 5))", "MULTILINESTRING((5 5, 6 5), (8 5, 10 5))"], ["MULTILINESTRING((5 0, 5 2), (5 4, 5 5))", "MULTILINESTRING((5 5, 5 6), (5 8, 5 10))"]]
+ }, {
+ msg: "partial target and source split with target vertex, mutual true",
+ opt: {mutual: true},
+ g1: "MULTILINESTRING((5 0, 5 2), (5 4, 5 6), (5 8, 5 10))",
+ g2: "MULTILINESTRING((0 5, 2 5), (4 5, 5 5, 6 5), (8 5, 10 5))",
+ exp: [["MULTILINESTRING((5 0, 5 2), (5 4, 5 5))", "MULTILINESTRING((5 5, 5 6), (5 8, 5 10))"], ["MULTILINESTRING((0 5, 2 5), (4 5, 5 5))", "MULTILINESTRING((5 5, 6 5), (8 5, 10 5))"]]
+ }];
+
+
+ t.plan(cases.length);
+ var c, parts, part, midparts;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ var g1 = wkt(c.g1);
+ var g2 = wkt(c.g2);
+ var got = g1.split(g2, c.opt);
+ var exp = c.exp;
+ if(got instanceof Array) {
+ parts = [];
+ for(var j=0; j<got.length; ++j) {
+ part = got[j];
+ if(part instanceof Array) {
+ midparts = [];
+ for(var k=0; k<part.length; ++k) {
+ midparts.push(part[k].toString());
+ }
+ parts.push("[" + midparts.join(", ") + "]");
+ } else {
+ parts.push(got[j].toString());
+ }
+ }
+ got = parts.join(", ");
+ }
+ if(exp instanceof Array) {
+ parts = [];
+ for(var j=0; j<exp.length; ++j) {
+ part = exp[j];
+ if(part instanceof Array) {
+ midparts = [];
+ for(var k=0; k<part.length; ++k) {
+ midparts.push(wkt(part[k]).toString());
+ }
+ parts.push("[" + midparts.join(", ") + "]");
+ } else {
+ parts.push(wkt(exp[j]).toString());
+ }
+ }
+ exp = parts.join(", ");
+ }
+ t.eq(got, exp, "case " + i + ": " + c.msg);
+ }
+
+ }
+
+ function test_getVertices(t) {
+ t.plan(22);
+
+ var points = [
+ new OpenLayers.Geometry.Point(10, 20),
+ new OpenLayers.Geometry.Point(20, 30),
+ new OpenLayers.Geometry.Point(30, 40),
+ new OpenLayers.Geometry.Point(40, 50)
+ ];
+
+ var multi = new OpenLayers.Geometry.MultiLineString([
+ new OpenLayers.Geometry.LineString(points),
+ new OpenLayers.Geometry.LineString(points)
+ ]);
+
+ var verts = multi.getVertices();
+ t.ok(verts instanceof Array, "got back an array");
+ t.eq(verts.length, 2 * points.length, "of correct length length");
+ t.geom_eq(verts[0], points[0], "0: correct geometry");
+ t.geom_eq(verts[1], points[1], "1: correct geometry");
+ t.geom_eq(verts[2], points[2], "2: correct geometry");
+ t.geom_eq(verts[3], points[3], "3: correct geometry");
+ t.geom_eq(verts[4], points[0], "4: correct geometry");
+ t.geom_eq(verts[5], points[1], "5: correct geometry");
+ t.geom_eq(verts[6], points[2], "6: correct geometry");
+ t.geom_eq(verts[7], points[3], "7: correct geometry");
+
+ // nodes only
+ var nodes = multi.getVertices(true);
+ t.ok(nodes instanceof Array, "[nodes only] got back an array");
+ t.eq(nodes.length, 4, "[nodes only] of correct length length");
+ t.geom_eq(nodes[0], points[0], "[nodes only] 0: correct geometry");
+ t.geom_eq(nodes[1], points[3], "[nodes only] 1: correct geometry");
+ t.geom_eq(nodes[2], points[0], "[nodes only] 2: correct geometry");
+ t.geom_eq(nodes[3], points[3], "[nodes only] 3: correct geometry");
+
+ // no nodes
+ var nodes = multi.getVertices(false);
+ t.ok(nodes instanceof Array, "[no nodes] got back an array");
+ t.eq(nodes.length, 4, "[no nodes] of correct length length");
+ t.geom_eq(nodes[0], points[1], "[no nodes] 0: correct geometry");
+ t.geom_eq(nodes[1], points[2], "[no nodes] 1: correct geometry");
+ t.geom_eq(nodes[2], points[1], "[no nodes] 2: correct geometry");
+ t.geom_eq(nodes[3], points[2], "[no nodes] 3: correct geometry");
+
+
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/MultiPoint.html b/misc/openlayers/tests/Geometry/MultiPoint.html
new file mode 100644
index 0000000..47ce430
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/MultiPoint.html
@@ -0,0 +1,130 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var point = new OpenLayers.Geometry.Point(10, 15);
+
+
+ function test_MultiPoint_constructor (t) {
+ t.plan( 2 );
+ var multipoint = new OpenLayers.Geometry.MultiPoint();
+ t.ok( multipoint instanceof OpenLayers.Geometry.MultiPoint, "new OpenLayers.Geometry.MultiPoint returns multipoint object" );
+ t.eq( multipoint.CLASS_NAME, "OpenLayers.Geometry.MultiPoint", "multipoint.CLASS_NAME is set correctly");
+ }
+
+ function test_MultiPoint_constructor (t) {
+ t.plan( 3 );
+ var multipoint = new OpenLayers.Geometry.MultiPoint([point]);
+ t.ok( multipoint instanceof OpenLayers.Geometry.MultiPoint, "new OpenLayers.Geometry.MultiPoint returns multipoint object" );
+ t.eq( multipoint.CLASS_NAME, "OpenLayers.Geometry.MultiPoint", "multipoint.CLASS_NAME is set correctly");
+ t.eq( multipoint.components.length, 1, "multipolygon.components.length is set correctly");
+ }
+
+ function test_MultiPoint_move(t) {
+ t.plan(2);
+
+ var multipoint = new OpenLayers.Geometry.MultiPoint([point]);
+ var x = point.x;
+ var y = point.y;
+
+ var dx = 10 * Math.random();
+ var dy = 10 * Math.random();
+ multipoint.move(dx, dy);
+ t.eq(multipoint.components[0].x, x + dx, "move() correctly modifies x");
+ t.eq(multipoint.components[0].y, y + dy, "move() correctly modifies y");
+ }
+
+ function test_distanceTo(t) {
+ var points = [
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(10, 0),
+ new OpenLayers.Geometry.Point(0, 9),
+ new OpenLayers.Geometry.Point(-5, 0),
+ new OpenLayers.Geometry.Point(-5, 4)
+ ];
+
+ var geoms = [
+ new OpenLayers.Geometry.MultiPoint([points[0], points[1]]),
+ new OpenLayers.Geometry.MultiPoint([points[2], points[3]]),
+ points[4]
+ ];
+
+ var cases = [{
+ got: geoms[0].distanceTo(geoms[0]),
+ expected: 0
+ }, {
+ got: geoms[0].distanceTo(geoms[1]),
+ expected: 5
+ }, {
+ got: geoms[1].distanceTo(geoms[2]),
+ expected: 4
+ }, {
+ got: geoms[0].distanceTo(geoms[1], {details: true}),
+ expected: {
+ distance: 5,
+ x0: 0, y0: 0,
+ x1: -5, y1: 0
+ }
+ }, {
+ got: geoms[1].distanceTo(geoms[0], {details: true}),
+ expected: {
+ distance: 5,
+ x0: -5, y0: 0,
+ x1: 0, y1: 0
+ }
+ }, {
+ got: geoms[1].distanceTo(geoms[2], {details: true}),
+ expected: {
+ distance: 4,
+ x0: -5, y0: 0,
+ x1: -5, y1: 4
+ }
+ }];
+
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, "case " + i);
+ }
+
+ }
+
+ function test_MultiPoint_equals(t) {
+ t.plan(3);
+
+ var x = Math.random() * 100;
+ var y = Math.random() * 100;
+ var geometry = new OpenLayers.Geometry.MultiPoint(
+ [new OpenLayers.Geometry.Point(x, y)]);
+ var equal = new OpenLayers.Geometry.MultiPoint(
+ [new OpenLayers.Geometry.Point(x, y)]);
+ var offX = new OpenLayers.Geometry.MultiPoint(
+ [new OpenLayers.Geometry.Point(x + 1, y)]);
+ var offY = new OpenLayers.Geometry.MultiPoint(
+ [new OpenLayers.Geometry.Point(x, y + 1)]);
+ t.ok(geometry.equals(equal),
+ "equals() returns true for a geometry with equivalent coordinates");
+ t.ok(!geometry.equals(offX),
+ "equals() returns false for a geometry with offset x");
+ t.ok(!geometry.equals(offY),
+ "equals() returns false for a geometry with offset y");
+ }
+
+ function test_MultiPoint_clone(t) {
+ t.plan(2);
+
+ var x = Math.random() * 100;
+ var y = Math.random() * 100;
+ var geometry = new OpenLayers.Geometry.MultiPoint(
+ [new OpenLayers.Geometry.Point(x, y)]);
+ var clone = geometry.clone();
+ t.ok(clone instanceof OpenLayers.Geometry.MultiPoint,
+ "clone() creates an OpenLayers.Geometry.MultiPoint");
+ t.ok(geometry.equals(clone), "clone has equivalent coordinates");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/MultiPolygon.html b/misc/openlayers/tests/Geometry/MultiPolygon.html
new file mode 100644
index 0000000..f44de93
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/MultiPolygon.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var polygon;
+ var components = [new OpenLayers.Geometry.Point(10,10), new OpenLayers.Geometry.Point(0,0)];
+ var components2 = [new OpenLayers.Geometry.Point(10,10), new OpenLayers.Geometry.Point(0,0), new OpenLayers.Geometry.Point(10,0), new OpenLayers.Geometry.Point(10,10)];
+ var linearRing = new OpenLayers.Geometry.LinearRing(components);
+ var linearRing2 = new OpenLayers.Geometry.LinearRing(components2);
+
+ var polygon = new OpenLayers.Geometry.Polygon([linearRing]);
+ var polygon2 = new OpenLayers.Geometry.Polygon([linearRing2]);
+
+ function test_MultiPolygon_constructor (t) {
+ t.plan( 2 );
+ multipolygon = new OpenLayers.Geometry.MultiPolygon();
+ t.ok( multipolygon instanceof OpenLayers.Geometry.MultiPolygon, "new OpenLayers.Geometry.MultiPolygon returns multipolygon object" );
+ t.eq( multipolygon.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon", "multipolygon.CLASS_NAME is set correctly");
+ }
+
+ function test_MultiPolygon_constructor (t) {
+ t.plan( 3 );
+ multipolygon = new OpenLayers.Geometry.MultiPolygon([polygon, polygon2]);
+ t.ok( multipolygon instanceof OpenLayers.Geometry.MultiPolygon, "new OpenLayers.Geometry.MultiPolygon returns multipolygon object" );
+ t.eq( multipolygon.CLASS_NAME, "OpenLayers.Geometry.MultiPolygon", "multipolygon.CLASS_NAME is set correctly");
+ t.eq( multipolygon.components.length, 2, "multipolygon.components.length is set correctly");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/Point.html b/misc/openlayers/tests/Geometry/Point.html
new file mode 100644
index 0000000..e688250
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/Point.html
@@ -0,0 +1,244 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var point;
+
+ function test_Point_constructor (t) {
+ t.plan( 8 );
+
+ //empty
+ point = new OpenLayers.Geometry.Point();
+ t.ok( point instanceof OpenLayers.Geometry.Point, "new OpenLayers.Geometry.Point returns point object" );
+ t.eq( point.CLASS_NAME, "OpenLayers.Geometry.Point", "point.CLASS_NAME is set correctly");
+
+ //valid
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+ t.ok( point instanceof OpenLayers.Geometry.Point, "new OpenLayers.Geometry.Point returns point object" );
+ t.eq( point.CLASS_NAME, "OpenLayers.Geometry.Point", "point.CLASS_NAME is set correctly");
+ t.eq( point.x, x, "point.x is set correctly");
+ t.eq( point.y, y, "point.y is set correctly");
+ t.eq( point.lon, null, "point.lon is not set");
+ t.eq( point.lat, null, "point.lat is not set");
+ }
+
+ function test_Point_calculateBounds (t) {
+ t.plan(4);
+
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+ point.calculateBounds();
+ t.eq( point.bounds.left, x, "bounds.left is 10" );
+ t.eq( point.bounds.right, x, "bounds.right is 10" );
+ t.eq( point.bounds.top, y, "bounds.top is 20" );
+ t.eq( point.bounds.bottom, y, "bounds.bottom is 20" );
+ }
+
+
+ function test_Point_transform_getBounds (t) {
+ t.plan(2);
+
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+ point.calculateBounds();
+ t.ok( point.bounds != null, "bounds calculated by calcBounds" );
+ point.transform(new OpenLayers.Projection("EPSG:4326"),
+ new OpenLayers.Projection("EPSG:900913"));
+ t.eq(point.bounds, null, "Point bounds cleared after transform");
+ }
+
+ function test_Point_transform_string(t) {
+ t.plan(4);
+
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+ point.calculateBounds();
+ t.ok( point.bounds != null, "bounds calculated by calcBounds" );
+ point.transform("EPSG:4326", "EPSG:900913");
+ t.eq(point.bounds, null, "Point bounds cleared after transform");
+ t.eq(point.x.toFixed(2), "1113194.91", "transformed x");
+ t.eq(point.y.toFixed(2), "2273030.93", "transformed y");
+
+ }
+
+ function test_Point_distanceTo(t) {
+ t.plan(7);
+
+ var x1 = 10;
+ var y1 = 20;
+ point1 = new OpenLayers.Geometry.Point(x1, y1);
+
+ var x2 = 100;
+ var y2 = 200;
+ point2 = new OpenLayers.Geometry.Point(x2, y2);
+
+ var dist = point1.distanceTo(point2)
+ t.eq( dist, 201.24611797498107267682563018581, "distances calculating correctly");
+ t.eq( dist, Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)), "distance calculation correct");
+
+ // test that details are returned (though trivial in this case)
+ var result = point1.distanceTo(point2, {details: true});
+ t.eq(result.distance, point1.distanceTo(point2), "[details] distance property is same as return without details");
+ t.eq(result.x0, x1, "[details] x0 property is correct");
+ t.eq(result.y0, y1, "[details] y0 property is correct");
+ t.eq(result.x1, x2, "[details] x1 property is correct");
+ t.eq(result.y1, y2, "[details] y1 property is correct");
+
+ }
+
+ function test_Point_toString(t) {
+ t.plan(1);
+
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+ t.eq(point.toString(), "POINT(" + x + " " + y + ")",
+ "toString() returns WKT" );
+
+ }
+
+ function test_Point_toString_no_wkt(t) {
+ t.plan(1);
+
+ var WKT = OpenLayers.Format.WKT;
+ OpenLayers.Format.WKT = null;
+
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+ t.eq(point.toString(), "[object Object]", "default string representation");
+
+ OpenLayers.Format.WKT = WKT;
+
+ }
+
+ function test_Point_move(t) {
+ t.plan(3);
+
+ var x = 10;
+ var y = 20;
+ point = new OpenLayers.Geometry.Point(x, y);
+
+ var dx = 10 * Math.random();
+ var dy = 10 * Math.random();
+ point.bounds = "foo";
+ point.move(dx, dy);
+ t.eq(point.x, x + dx, "move() correctly modifies x");
+ t.eq(point.y, y + dy, "move() correctly modifies y");
+
+ t.ok(point.bounds == null, "bounds is cleared after a move()");
+ }
+
+ function test_Point_rotate(t) {
+ t.plan(5);
+
+ var tolerance = 1e-10;
+ var x = 10;
+ var y = 20;
+ var point = new OpenLayers.Geometry.Point(x, y);
+ var origin = new OpenLayers.Geometry.Point(5, 10);
+
+ // rotate a full revolution
+ point.bounds = "foo";
+ point.rotate(360, origin);
+ t.ok(((point.x - x) / x) < tolerance,
+ "rotate by 360 returns to the same y");
+ t.ok(((point.y - y) / y) < tolerance,
+ "rotate by 360 returns to the same y");
+
+ t.ok(point.bounds == null, "bounds is cleared after a rotate()");
+
+ // rotate an 1/8 turn
+ point.rotate(45, origin);
+ t.ok(((point.x - 1.4644660940672636) / 1.4644660940672636) < tolerance,
+ "rotate 1/8 turn correctly");
+ t.ok(((point.y - 20.606601717798213) / 20.606601717798213) < tolerance,
+ "rotate 1/8 turn correctly");
+ }
+
+ function test_Point_resize(t) {
+ t.plan(6);
+
+ var tolerance = 1e-10;
+ var x = 100 * Math.random();
+ var y = 100 * Math.random();
+ var point = new OpenLayers.Geometry.Point(x, y);
+ point.bounds = "foo";
+
+ var i = 100 * Math.random();
+ var j = 100 * Math.random();
+ var origin = new OpenLayers.Geometry.Point(i, j);
+
+ var scale = 10 * Math.random();
+ var oldDistance = origin.distanceTo(point);
+
+ var ret = point.resize(scale, origin);
+ var newDistance = origin.distanceTo(point);
+
+ t.ok(ret === point, "resize returns geometry");
+ t.ok((origin.x == i) && (origin.y == j),
+ "resize leaves the origin untouched");
+ t.ok((((newDistance / oldDistance) - scale) / scale) < tolerance,
+ "resize moves points the correct distance from the origin");
+
+ t.ok(point.bounds == null, "bounds is correctly cleared after a resize()");
+
+ // resize with non uniform scaling (ratio != 1)
+ point = new OpenLayers.Geometry.Point(10, 10);
+ origin = new OpenLayers.Geometry.Point(0, 0);
+ point.resize(2, origin, 4);
+ t.eq(point.x, 80, "non-uniform scaling correctly applied in x dim");
+ t.eq(point.y, 20, "non-uniform scaling correctly applied in y dim");
+
+ }
+
+ function test_Point_equals(t) {
+ t.plan(3);
+
+ var x = Math.random() * 100;
+ var y = Math.random() * 100;
+ var geometry = new OpenLayers.Geometry.Point(x, y);
+ var equal = new OpenLayers.Geometry.Point(x, y);
+ var offX = new OpenLayers.Geometry.Point(x + 1, y);
+ var offY = new OpenLayers.Geometry.Point(x, y + 1);
+ t.ok(geometry.equals(equal),
+ "equals() returns true for a geometry with equivalent coordinates");
+ t.ok(!geometry.equals(offX),
+ "equals() returns false for a geometry with offset x");
+ t.ok(!geometry.equals(offY),
+ "equals() returns false for a geometry with offset y");
+ }
+
+ function test_getVertices(t) {
+ t.plan(3);
+
+ var point = new OpenLayers.Geometry.Point(10, 20);
+ var verts = point.getVertices();
+ t.ok(verts instanceof Array, "got back an array");
+ t.eq(verts.length, 1, "of length 1");
+ t.geom_eq(verts[0], point, "with correct geometry");
+ }
+
+ function test_Point_clone(t) {
+ t.plan(2);
+
+ var x = Math.random() * 100;
+ var y = Math.random() * 100;
+ var geometry = new OpenLayers.Geometry.Point(x, y);
+ var clone = geometry.clone();
+ t.ok(clone instanceof OpenLayers.Geometry.Point,
+ "clone() creates an OpenLayers.Geometry.Point");
+ t.ok(geometry.equals(clone), "clone has equivalent coordinates");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Geometry/Polygon.html b/misc/openlayers/tests/Geometry/Polygon.html
new file mode 100644
index 0000000..0df0295
--- /dev/null
+++ b/misc/openlayers/tests/Geometry/Polygon.html
@@ -0,0 +1,420 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var polygon;
+ var components = [new OpenLayers.Geometry.Point(10,14), new OpenLayers.Geometry.Point(5,3)];
+ var components2 = [new OpenLayers.Geometry.Point(12,15), new OpenLayers.Geometry.Point(2,3), new OpenLayers.Geometry.Point(10,0), new OpenLayers.Geometry.Point(10,10)];
+ var linearRing = new OpenLayers.Geometry.LinearRing(components);
+ var linearRing2 = new OpenLayers.Geometry.LinearRing(components2);
+
+ function test_Polygon_constructor (t) {
+ t.plan( 3 );
+ polygon = new OpenLayers.Geometry.Polygon();
+ t.ok( polygon instanceof OpenLayers.Geometry.Polygon, "new OpenLayers.Geometry.Polygon returns polygon object" );
+ t.eq( polygon.CLASS_NAME, "OpenLayers.Geometry.Polygon", "polygon.CLASS_NAME is set correctly");
+ t.eq( polygon.components.length, 0, "polygon.components is set correctly");
+ }
+
+ function test_Polygon_constructor (t) {
+ t.plan( 3 );
+ polygon = new OpenLayers.Geometry.Polygon([linearRing]);
+ t.ok( polygon instanceof OpenLayers.Geometry.Polygon, "new OpenLayers.Geometry.Polygon returns polygon object" );
+ t.eq( polygon.CLASS_NAME, "OpenLayers.Geometry.Polygon", "polygon.CLASS_NAME is set correctly");
+ t.eq( polygon.components.length, 1, "polygon.components.length is set correctly");
+ }
+
+ function test_Polygon_constructor (t) {
+ t.plan( 3 );
+ polygon = new OpenLayers.Geometry.Polygon([linearRing, linearRing2]);
+ t.ok( polygon instanceof OpenLayers.Geometry.Polygon, "new OpenLayers.Geometry.Polygon returns polygon object" );
+ t.eq( polygon.CLASS_NAME, "OpenLayers.Geometry.Polygon", "polygon.CLASS_NAME is set correctly");
+ t.eq( polygon.components.length, 2, "polygon.components.length is set correctly");
+ }
+
+ function test_Polygon_transform_getBounds (t) {
+ t.plan(3);
+
+ var components = [new OpenLayers.Geometry.Point(10,14), new OpenLayers.Geometry.Point(5,3)];
+ var linearRing = new OpenLayers.Geometry.LinearRing(components);
+ polygon = new OpenLayers.Geometry.Polygon([linearRing.clone()]);
+ polygon.calculateBounds();
+ t.ok( polygon.bounds != null, "bounds calculated by calcBounds" );
+ polygon.transform(new OpenLayers.Projection("EPSG:4326"),
+ new OpenLayers.Projection("EPSG:900913"));
+ t.eq(polygon.bounds, null, "Point bounds cleared after transform");
+ t.eq(polygon.getBounds().toBBOX(), "556597.453889,334111.171355,1113194.907778,1574216.547942", "Bounds are correct")
+ }
+
+ function test_Polygon_transform_string (t) {
+ t.plan(3);
+
+ var components = [new OpenLayers.Geometry.Point(10,14), new OpenLayers.Geometry.Point(5,3)];
+ var linearRing = new OpenLayers.Geometry.LinearRing(components);
+ polygon = new OpenLayers.Geometry.Polygon([linearRing.clone()]);
+ polygon.calculateBounds();
+ t.ok( polygon.bounds != null, "bounds calculated by calcBounds" );
+ polygon.transform("EPSG:4326", "EPSG:900913");
+ t.eq(polygon.bounds, null, "Point bounds cleared after transform");
+ t.eq(polygon.getBounds().toBBOX(), "556597.453889,334111.171355,1113194.907778,1574216.547942", "Bounds are correct")
+ }
+
+ function test_Polygon_getArea(t) {
+ t.plan( 5 );
+
+ //no components
+ var polygon = new OpenLayers.Geometry.Polygon();
+ t.eq(polygon.getArea(), 0, "getArea empty polygon is 0");
+
+ var createSquareRing = function(area) {
+ var points = [
+ new OpenLayers.Geometry.Point(0, 0),
+ new OpenLayers.Geometry.Point(0, area),
+ new OpenLayers.Geometry.Point(area, area),
+ new OpenLayers.Geometry.Point(area, 0)
+ ];
+ var ring = new OpenLayers.Geometry.LinearRing(points);
+ return ring;
+ };
+
+
+ //simple polygon
+ var comps = [ createSquareRing(2) ];
+
+ var polygon = new OpenLayers.Geometry.Polygon(comps);
+ t.eq(polygon.getArea(), 4, "getArea simple polygon works lovely");
+
+ //polygon with holes
+ comps = [ createSquareRing(10),
+ createSquareRing(2),
+ createSquareRing(3),
+ createSquareRing(4)
+ ];
+
+ var polygon = new OpenLayers.Geometry.Polygon(comps);
+ t.eq(polygon.getArea(), 71, "getArea polygon with holes works lovely");
+
+ //simple polygon negative
+ comps = [ createSquareRing(-2) ];
+
+ var polygon = new OpenLayers.Geometry.Polygon(comps);
+ t.eq(polygon.getArea(), 4, "getArea simple polygon negative works lovely");
+
+ //polygon with holes negative
+ comps = [ createSquareRing(-10),
+ createSquareRing(-2),
+ createSquareRing(-3),
+ createSquareRing(-4)
+ ];
+
+ var polygon = new OpenLayers.Geometry.Polygon(comps);
+ t.eq(polygon.getArea(), 71, "getArea negative polygon with holes works lovely");
+
+ }
+
+ function test_Polygon_move(t) {
+ t.plan(4);
+
+ polygon = new OpenLayers.Geometry.Polygon([linearRing, linearRing2]);
+
+ var x = linearRing.components[0].x;
+ var y = linearRing.components[0].y;
+ var x2 = linearRing2.components[0].x;
+ var y2 = linearRing2.components[0].y;
+
+ var dx = 10 * Math.random();
+ var dy = 10 * Math.random();
+
+ polygon.move(dx, dy);
+
+ t.eq(polygon.components[0].components[0].x, x + dx, "move() correctly modifies first x");
+ t.eq(polygon.components[0].components[0].y, y + dy, "move() correctly modifies first y");
+ t.eq(polygon.components[1].components[0].x, x2 + dx, "move() correctly modifies second x");
+ t.eq(polygon.components[1].components[0].y, y2 + dy, "move() correctly modifies second y");
+ }
+
+ function test_Polygon_rotate(t) {
+ t.plan(6);
+
+ var geometry = new OpenLayers.Geometry.Polygon([linearRing, linearRing2]);
+
+ var originals = [];
+ var comp;
+ var angle = 2 * Math.PI * Math.random();
+ var origin = new OpenLayers.Geometry.Point(10 * Math.random(),
+ 10 * Math.random());
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp = geometry.components[i];
+ originals[i] = comp.rotate;
+ comp.rotate = function(a, o) {
+ t.ok(true, "rotate called for component " + i);
+ t.ok(a == angle, "rotate called with correct angle");
+ t.ok(o == origin, "rotate called with correct origin");
+ }
+ }
+ geometry.rotate(angle, origin);
+
+ // restore the original rotate defs
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp.rotate = originals[i];
+ }
+ }
+
+ function test_Polygon_resize(t) {
+ t.plan(8);
+
+ var tolerance = 1e-10;
+ var geometry = new OpenLayers.Geometry.Polygon([linearRing, linearRing2]);
+ var origin = new OpenLayers.Geometry.Point(10 * Math.random(),
+ 10 * Math.random());
+ var scale = 10 * Math.random();
+
+ var oldArea = geometry.getArea();
+ var oldPerimeter = geometry.getLength();
+ geometry.resize(scale, origin);
+ var newArea = geometry.getArea();
+ var newPerimeter = geometry.getLength();
+
+ t.ok((((newArea / oldArea) - (scale * scale)) / (scale * scale)) < tolerance,
+ "resize correctly changes the area of a polygon")
+ t.ok((((newPerimeter / oldPerimeter) - scale) / scale) < tolerance,
+ "resize correctly changes the perimeter of a polygon")
+
+ var originals = [];
+ var comp;
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp = geometry.components[i];
+ originals[i] = comp.resize;
+ comp.resize = function(s, o) {
+ t.ok(true, "resize called for component " + i);
+ t.ok(s == scale, "resize called with correct scale");
+ t.ok(o == origin, "resize called with correct origin");
+ }
+ }
+ geometry.resize(scale, origin);
+
+ // restore the original resize defs
+ for(var i=0; i<geometry.components.length; ++i) {
+ comp.resize = originals[i];
+ }
+
+ }
+
+ function test_Polygon_createRegular(t) {
+ t.plan(22);
+ var sides = 40;
+ var poly = OpenLayers.Geometry.Polygon.createRegularPolygon(new OpenLayers.Geometry.Point(5,0), 6, sides);
+ var polyBounds = poly.getBounds();
+ t.eq(polyBounds.toBBOX(), "-0.981504,-5.981504,10.981504,5.981504", sides + " sided figure generates correct bbox.");
+ t.eq(poly.components.length, 1, "Poly has one linear ring");
+ t.eq(poly.components[0].components.length, sides + 1, "ring has 41 components");
+ t.eq(poly.components[0].components[0].id, poly.components[0].components[sides].id, "ring starts and ends with same geom");
+ t.eq(Math.round(poly.getArea()), Math.round(Math.PI * 36), "area of "+sides+" sided poly rounds to same area as a circle.");
+
+ var sides = 3;
+ var poly = OpenLayers.Geometry.Polygon.createRegularPolygon(new OpenLayers.Geometry.Point(5,0), 6, sides);
+ var polyBounds = poly.getBounds();
+ t.eq(polyBounds.toBBOX(), "-0.196152,-3,10.196152,6", sides + " sided figure generates correct bbox.");
+ t.eq(poly.components.length, 1, "Poly has one linear ring");
+ t.eq(poly.components[0].components.length, sides + 1, "ring has correct count of components");
+ t.eq(poly.components[0].components[0].id, poly.components[0].components[sides].id, "ring starts and ends with same geom");
+ t.eq(Math.round(poly.getArea()), 47, "area of 3 sided poly is correct");
+
+ var sides = 3;
+ var poly3 = OpenLayers.Geometry.Polygon.createRegularPolygon(new OpenLayers.Geometry.Point(10,0), 15, sides);
+ var polyBounds = poly3.getBounds();
+ t.eq(polyBounds.toBBOX(), "-2.990381,-7.5,22.990381,15", sides + " sided figure generates correct bbox.");
+ t.eq(Math.round(polyBounds.getCenterLonLat().lon), 10, "longitude of center of bounds is same as origin");
+ t.eq(poly3.components.length, 1, "Poly has one linear ring");
+ t.eq(poly3.components[0].components.length, sides + 1, "ring has correct count of components");
+ t.eq(poly3.components[0].components[0].id, poly3.components[0].components[sides].id, "ring starts and ends with same geom");
+ t.ok(poly3.getArea() > poly.getArea(), "area with radius 15 > poly with radius 6");
+
+ var sides = 4;
+ var poly4 = OpenLayers.Geometry.Polygon.createRegularPolygon(new OpenLayers.Geometry.Point(10,0), 15, sides);
+ var polyBounds = poly4.getBounds();
+ t.eq(polyBounds.toBBOX(), "-0.606602,-10.606602,20.606602,10.606602", sides + " sided figure generates correct bbox.");
+ t.eq(Math.round(polyBounds.getCenterLonLat().lon), 10, "longitude of center of bounds is same as origin");
+ t.eq(poly4.components.length, 1, "Poly has one linear ring");
+ t.eq(poly4.components[0].components.length, sides + 1, "ring has correct count of components");
+ t.eq(poly4.components[0].components[0].id, poly4.components[0].components[sides].id, "ring starts and ends with same geom");
+ t.ok(poly4.getArea() > poly3.getArea(), "square with radius 15 > triangle with radius 15");
+ }
+
+ function test_Polygon_equals(t) {
+ t.plan(3);
+
+ var x0 = Math.random() * 100;
+ var y0 = Math.random() * 100;
+ var x1 = Math.random() * 100;
+ var y1 = Math.random() * 100;
+ var x2 = Math.random() * 100;
+ var y2 = Math.random() * 100;
+ var point0 = new OpenLayers.Geometry.Point(x0, y0);
+ var point1 = new OpenLayers.Geometry.Point(x1, y1);
+ var point2 = new OpenLayers.Geometry.Point(x2, y2);
+ var pointX = new OpenLayers.Geometry.Point(x0 + 1, y0);
+ var pointY = new OpenLayers.Geometry.Point(x0, y0 + 1);
+ var geometry = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([point0, point1, point2])]);
+ var equal = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([point0, point1, point2])]);
+ var offX = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([pointX, point1, point2])]);
+ var offY = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([pointY, point1, point2])]);
+ t.ok(geometry.equals(equal),
+ "equals() returns true for a geometry with equivalent coordinates");
+ t.ok(!geometry.equals(offX),
+ "equals() returns false for a geometry with offset x");
+ t.ok(!geometry.equals(offY),
+ "equals() returns false for a geometry with offset y");
+ }
+
+ function test_distanceTo(t) {
+ var wkt = OpenLayers.Geometry.fromWKT;
+ var geoms = [
+ wkt("POLYGON((0 3, 1 4, 2 3, 1 2, 0 3))"),
+ wkt("POINT(0 0)"),
+ wkt("LINESTRING(-2 0, 0 -2, 2 -1, 2 0)"),
+ wkt("LINESTRING(0 2, 1 3)"),
+ wkt("POINT(1 3)")
+ ];
+
+ var cases = [{
+ got: geoms[0].distanceTo(geoms[1]),
+ expected: Math.sqrt(5)
+ }, {
+ got: geoms[0].distanceTo(geoms[1], {details: true}),
+ expected: {
+ distance: Math.sqrt(5),
+ x0: 1, y0: 2,
+ x1: 0, y1: 0
+ }
+ }, {
+ got: geoms[0].distanceTo(geoms[2], {details: true}),
+ expected: {
+ distance: Math.sqrt(5),
+ x0: 1, y0: 2,
+ x1: 2, y1: 0
+ }
+ }, {
+ got: geoms[0].distanceTo(geoms[3], {details: true}),
+ expected: {
+ distance: 0,
+ x0: 0.5, y0: 2.5,
+ x1: 0.5, y1: 2.5
+ }
+ }, {
+ got: geoms[0].distanceTo(geoms[4]),
+ expected: Math.sqrt(0.5)
+ }, {
+ got: geoms[0].distanceTo(geoms[4], {edge: false}),
+ expected: 0
+ }];
+
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, "case " + i);
+ }
+
+ }
+
+ function test_getVertices(t) {
+ t.plan(6);
+
+ var points = [
+ new OpenLayers.Geometry.Point(10, 20),
+ new OpenLayers.Geometry.Point(20, 30),
+ new OpenLayers.Geometry.Point(30, 40),
+ new OpenLayers.Geometry.Point(40, 50)
+ ];
+ var polygon = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing(points)
+ ]);
+
+ var verts = polygon.getVertices();
+ t.ok(verts instanceof Array, "got back an array");
+ t.eq(verts.length, points.length, "of correct length length");
+ t.geom_eq(verts[0], points[0], "0: correct geometry");
+ t.geom_eq(verts[1], points[1], "1: correct geometry");
+ t.geom_eq(verts[2], points[2], "2: correct geometry");
+ t.geom_eq(verts[3], points[3], "3: correct geometry");
+
+ }
+
+ function test_Polygon_clone(t) {
+ t.plan(2);
+
+ var x0 = Math.random() * 100;
+ var y0 = Math.random() * 100;
+ var x1 = Math.random() * 100;
+ var y1 = Math.random() * 100;
+ var x2 = Math.random() * 100;
+ var y2 = Math.random() * 100;
+ var point0 = new OpenLayers.Geometry.Point(x0, y0);
+ var point1 = new OpenLayers.Geometry.Point(x1, y1);
+ var point2 = new OpenLayers.Geometry.Point(x2, y2);
+ var geometry = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([point0, point1, point2])]);
+ var clone = geometry.clone();
+ t.ok(clone instanceof OpenLayers.Geometry.Polygon,
+ "clone() creates an OpenLayers.Geometry.Polygon");
+ t.ok(geometry.equals(clone), "clone has equivalent coordinates");
+ }
+
+ function test_getGeodesicArea(t) {
+
+ t.plan(1);
+
+ // from the wfs-states.html example
+ var illinois = OpenLayers.Geometry.fromWKT(
+ "MULTIPOLYGON(((-88.071564 37.51099,-88.087883 37.476273,-88.311707 37.442852,-88.359177 37.409309,-88.419853 37.420292,-88.467644 37.400757,-88.511322 37.296852,-88.501427 37.257782,-88.450699 37.205669,-88.422516 37.15691,-88.45047 37.098671,-88.476799 37.072144,-88.4907 37.06818,-88.517273 37.06477,-88.559273 37.072815,-88.61422 37.109047,-88.68837 37.13541,-88.739113 37.141182,-88.746506 37.152107,-88.863289 37.202194,-88.932503 37.218407,-88.993172 37.220036,-89.065033 37.18586,-89.116821 37.112137,-89.146347 37.093185,-89.169548 37.064236,-89.174332 37.025711,-89.150246 36.99844,-89.12986 36.988113,-89.193512 36.986771,-89.210052 37.028973,-89.237679 37.041733,-89.264053 37.087124,-89.284233 37.091244,-89.303291 37.085384,-89.3097 37.060909,-89.264244 37.027733,-89.262001 37.008686,-89.282768 36.999207,-89.310982 37.009682,-89.38295 37.049213,-89.37999 37.099083,-89.423798 37.137203,-89.440521 37.165318,-89.468216 37.224266,-89.465309 37.253731,-89.489594 37.256001,-89.513885 37.276402,-89.513885 37.304962,-89.50058 37.329441,-89.468742 37.339409,-89.435738 37.355717,-89.427574 37.411018,-89.453621 37.453186,-89.494781 37.491726,-89.524971 37.571957,-89.513367 37.615929,-89.51918 37.650375,-89.513374 37.67984,-89.521523 37.694798,-89.581436 37.706104,-89.666458 37.745453,-89.675858 37.78397,-89.691055 37.804794,-89.728447 37.840992,-89.851715 37.905064,-89.861046 37.905487,-89.866814 37.891876,-89.900551 37.875904,-89.937874 37.878044,-89.978912 37.911884,-89.958229 37.963634,-90.010811 37.969318,-90.041924 37.993206,-90.119339 38.032272,-90.134712 38.053951,-90.207527 38.088905,-90.254059 38.122169,-90.289635 38.166817,-90.336716 38.188713,-90.364769 38.234299,-90.369347 38.323559,-90.358688 38.36533,-90.339607 38.390846,-90.301842 38.427357,-90.265785 38.518688,-90.26123 38.532768,-90.240944 38.562805,-90.183708 38.610271,-90.183578 38.658772,-90.20224 38.700363,-90.196571 38.723965,-90.163399 38.773098,-90.135178 38.785484,-90.121727 38.80051,-90.113121 38.830467,-90.132812 38.853031,-90.243927 38.914509,-90.278931 38.924717,-90.31974 38.924908,-90.413071 38.96233,-90.469841 38.959179,-90.530426 38.891609,-90.570328 38.871326,-90.627213 38.880795,-90.668877 38.935253,-90.70607 39.037792,-90.707588 39.058178,-90.690399 39.0937,-90.716736 39.144211,-90.718193 39.195873,-90.732338 39.224747,-90.738083 39.24781,-90.779343 39.296803,-90.850494 39.350452,-90.947891 39.400585,-91.036339 39.444412,-91.064384 39.473984,-91.093613 39.528927,-91.156189 39.552593,-91.203247 39.600021,-91.317665 39.685917,-91.367088 39.72464,-91.373421 39.761272,-91.381714 39.803772,-91.449188 39.863049,-91.450989 39.885242,-91.434052 39.901829,-91.430389 39.921837,-91.447243 39.946064,-91.487289 40.005753,-91.504005 40.066711,-91.516129 40.134544,-91.506546 40.200459,-91.498932 40.251377,-91.486694 40.309624,-91.448593 40.371902,-91.418816 40.386875,-91.385757 40.392361,-91.372757 40.402988,-91.385399 40.44725,-91.374794 40.503654,-91.382103 40.528496,-91.412872 40.547993,-91.411118 40.572971,-91.37561 40.603439,-91.262062 40.639545,-91.214912 40.643818,-91.162498 40.656311,-91.129158 40.682148,-91.119987 40.705402,-91.092751 40.761547,-91.088905 40.833729,-91.04921 40.879585,-90.983276 40.923927,-90.960709 40.950504,-90.954651 41.070362,-90.957787 41.104359,-90.990341 41.144371,-91.018257 41.165825,-91.05632 41.176258,-91.101524 41.231522,-91.102348 41.267818,-91.07328 41.334896,-91.055786 41.401379,-91.027489 41.423508,-91.000694 41.431084,-90.949654 41.421234,-90.844139 41.444622,-90.7799 41.449821,-90.708214 41.450062,-90.658791 41.462318,-90.6007 41.509586,-90.54084 41.52597,-90.454994 41.527546,-90.434967 41.543579,-90.423004 41.567272,-90.348366 41.586849,-90.339348 41.602798,-90.341133 41.64909,-90.326027 41.722736,-90.304886 41.756466,-90.25531 41.781738,-90.195839 41.806137,-90.154518 41.930775,-90.14267 41.983963,-90.150536 42.033428,-90.168098 42.061043,-90.166649 42.103745,-90.176086 42.120502,-90.191574 42.122688,-90.230934 42.159721,-90.323601 42.197319,-90.367729 42.210209,-90.407173 42.242645,-90.417984 42.263924,-90.427681 42.340633,-90.441597 42.360073,-90.491043 42.388783,-90.563583 42.421837,-90.605827 42.46056,-90.648346 42.475643,-90.651772 42.494698,-90.638329 42.509361,-90.419975 42.508362,-89.923569 42.504108,-89.834618 42.50346,-89.400497 42.49749,-89.359444 42.497906,-88.939079 42.490864,-88.764954 42.490906,-88.70652 42.489655,-88.297897 42.49197,-88.194702 42.489613,-87.79731 42.489132,-87.836945 42.314213,-87.760239 42.156456,-87.670547 42.059822,-87.612625 41.847332,-87.529861 41.723591,-87.532646 41.469715,-87.532448 41.301304,-87.531731 41.173756,-87.532021 41.00993,-87.532669 40.745411,-87.53717 40.49461,-87.535675 40.483246,-87.535339 40.166195,-87.535774 39.887302,-87.535576 39.609341,-87.538567 39.477448,-87.540215 39.350525,-87.597664 39.338268,-87.625237 39.307404,-87.610619 39.297661,-87.615799 39.281418,-87.606895 39.258163,-87.584564 39.248753,-87.588593 39.208466,-87.594208 39.198128,-87.607925 39.196068,-87.644257 39.168507,-87.670326 39.146679,-87.659454 39.130653,-87.662262 39.113468,-87.631668 39.103943,-87.630867 39.088974,-87.612007 39.084606,-87.58532 39.062435,-87.581749 38.995743,-87.591858 38.994083,-87.547905 38.977077,-87.53347 38.963703,-87.530182 38.931919,-87.5392 38.904861,-87.559059 38.869812,-87.550507 38.857891,-87.507889 38.795559,-87.519028 38.776699,-87.508003 38.769722,-87.508316 38.736633,-87.543892 38.685974,-87.588478 38.672169,-87.625191 38.642811,-87.628647 38.622917,-87.619827 38.599209,-87.640594 38.593178,-87.652855 38.573872,-87.672943 38.547424,-87.65139 38.515369,-87.653534 38.500443,-87.679909 38.504005,-87.692818 38.481533,-87.756096 38.466125,-87.758659 38.457096,-87.738953 38.44548,-87.748428 38.417965,-87.784019 38.378124,-87.834503 38.352524,-87.850082 38.286098,-87.863007 38.285362,-87.874039 38.316788,-87.883446 38.315552,-87.888466 38.300659,-87.914108 38.281048,-87.913651 38.302345,-87.925919 38.304771,-87.980019 38.241085,-87.986008 38.234814,-87.977928 38.200714,-87.932289 38.171131,-87.931992 38.157528,-87.950569 38.136913,-87.973503 38.13176,-88.018547 38.103302,-88.012329 38.092346,-87.964867 38.096748,-87.975296 38.073307,-88.034729 38.054085,-88.043091 38.04512,-88.041473 38.038303,-88.021698 38.033531,-88.029213 38.008236,-88.021706 37.975056,-88.042511 37.956264,-88.041771 37.934498,-88.064621 37.929783,-88.078941 37.944,-88.084 37.92366,-88.030441 37.917591,-88.026588 37.905758,-88.044868 37.896004,-88.100082 37.90617,-88.101456 37.895306,-88.075737 37.867809,-88.034241 37.843746,-88.042137 37.827522,-88.089264 37.831249,-88.086029 37.817612,-88.035576 37.805683,-88.072472 37.735401,-88.133636 37.700745,-88.15937 37.660686,-88.157631 37.628479,-88.134171 37.583572,-88.071564 37.51099)))"
+ );
+
+ // two calculations of the area (in square meters)
+ var planar = illinois.getArea() * Math.pow(OpenLayers.INCHES_PER_UNIT['dd'] / OpenLayers.INCHES_PER_UNIT['m'], 2);
+ var geodesic = illinois.getGeodesicArea();
+
+ // from http://en.wikipedia.org/wiki/Illinois
+ var expected = 1.40998e11; // square meters
+
+ var planarErr = Math.abs(planar - expected) / expected;
+ var geodesicErr = Math.abs(geodesic - expected) / expected;
+
+ t.ok(geodesicErr < planarErr, "geodesic measure is better (" + geodesicErr.toFixed(3) + " vs. " + planarErr.toFixed(3) + ")");
+
+ }
+
+ function test_getCentroid(t) {
+ t.plan(5);
+ var bounds = new OpenLayers.Bounds(5, 10, 5, 10);
+ var geometry = bounds.toGeometry();
+ var centroid = geometry.getCentroid();
+ t.eq(geometry.components[0].components.length, 2, "only two vertices since the box has left=right and bottom=top");
+ t.ok(centroid && centroid.x === 5 && centroid.y === 10, "getCentroid returns a point geometry even if the ring of the polygon has only 2 vertices");
+ bounds = new OpenLayers.Bounds(123456789.0, 123456789.0, 123456789.1, 123456789.1);
+ geometry = bounds.toGeometry();
+ centroid = geometry.getCentroid();
+ t.eq(geometry.components[0].components.length, 5, "five vertices expected");
+ var dX = Math.abs(centroid.x - 123456789.05);
+ var dY = Math.abs(centroid.y - 123456789.05);
+ t.ok(centroid && dX < 0.0001 && dY < 0.0001, " getCentroid returns the correct point geometry dX = " + dX + ", dY = " + dY);
+
+ var components = [
+ new OpenLayers.Geometry.Point(0,0), new OpenLayers.Geometry.Point(1,1),
+ new OpenLayers.Geometry.Point(0,1), new OpenLayers.Geometry.Point(1,0)];
+ var linearRing = new OpenLayers.Geometry.LinearRing(components);
+ polygon = new OpenLayers.Geometry.Polygon([linearRing.clone()]);
+ centroid = polygon.getCentroid();
+ var tX = centroid.x;
+ var tY = centroid.y;
+ t.ok( !isNaN(tX) && !isNaN(tY) && tX !== Infinity && tY !== Infinity, " getCentroid for wrong polygon works x = " + tX + ", y = " + tY);
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler.html b/misc/openlayers/tests/Handler.html
new file mode 100644
index 0000000..eb266d7
--- /dev/null
+++ b/misc/openlayers/tests/Handler.html
@@ -0,0 +1,265 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_constructor(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+ var handler = new OpenLayers.Handler(control, callbacks, options);
+ t.ok(handler instanceof OpenLayers.Handler,
+ "new OpenLayers.Handler returns object");
+ t.eq(handler.map.id, map.id,
+ "constructing a handler with a map sets the map on the handler");
+ t.eq(handler.callbacks.foo, callbacks.foo,
+ "constructor correctly sets callbacks");
+ t.eq(handler.bar, options.bar,
+ "constructor correctly extends handler with options");
+ }
+
+ function test_Handler_activate(t) {
+ t.plan(52);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var events = ["mouseover", "mouseout", "mousedown",
+ "mouseup", "mousemove", "click",
+ "dblclick", "resize", "focus", "blur"];
+
+ var handler = new OpenLayers.Handler(control);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler is already active");
+
+ handler.active = false;
+ map.events.registerPriority = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ // this is one of the mock handler methods
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(r, type,
+ "activate calls registerPriority with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler",
+ "activate calls registerPriority with the handler");
+ } else {
+ // this is the call with handler.setEvent as the func
+ t.ok(r, "activate calls registerPriority with handler.setEvent");
+ }
+ }
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+ activated = handler.activate();
+ t.ok(activated,
+ "activated returns true if the handler is not already active");
+
+ }
+
+ function test_Handler_deactivate(t) {
+ t.plan(52);
+ var map = new OpenLayers.Map('map', { controls: []});
+ // No controls so that we don't get more thingies than we expect
+ // when we actually clean up after ourselves: r5891 caused this
+ // because we actually destroy things now on the navigation control.
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var events = ["mouseover", "mouseout", "mousedown",
+ "mouseup", "mousemove", "click",
+ "dblclick", "resize", "focus", "blur"];
+
+ var handler = new OpenLayers.Handler(control);
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler is already deactive");
+
+ handler.activate();
+ map.events.unregister = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ // this is one of the mock handler methods
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "deactivate calls unregister with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls unregister with a function");
+ t.eq(func(), type,
+ "activate calls unregister with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler",
+ "activate calls unregister with the handler");
+ } else {
+ // this is the call with handler.setEvent as the func
+ t.ok(r, "activate calls registerPriority with handler.setEvent");
+ }
+ }
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ // add in a closure for key
+ (function(key) {
+ handler[key] = function() {return key};
+ })(events[i]);
+ }
+ deactivated = handler.deactivate();
+ t.ok(deactivated,
+ "deactivated returns true if the handler is already active");
+ map.events.unregister = OpenLayers.Events.prototype.unregister;
+ map.destroy();
+
+ }
+
+ function test_Handler_setEvent(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler(control);
+ handler.click = function(evt) {
+ }
+ handler.activate();
+ var testEvent = {
+ xy: new OpenLayers.Pixel(Math.random(), Math.random()),
+ altKey: (Math.random() > 0.5),
+ shiftKey: (Math.random() > 0.5),
+ ctrlKey: (Math.random() > 0.5),
+ metaKey: (Math.random() > 0.5)
+ }
+ map.events.triggerEvent("click", testEvent);
+ t.ok(handler.evt.xy.x == testEvent.xy.x &&
+ handler.evt.xy.y == testEvent.xy.y,
+ "handler.evt has proper xy object");
+ t.eq(handler.evt.altKey, testEvent.altKey,
+ "handler.evt.altKey correct");
+ t.eq(handler.evt.shiftKey, testEvent.shiftKey,
+ "handler.evt.shiftKey correct");
+ t.eq(handler.evt.ctrlKey, testEvent.ctrlKey,
+ "handler.evt.ctrlKey correct");
+ t.eq(handler.evt.metaKey, testEvent.metaKey,
+ "handler.evt.metaKey correct");
+ }
+
+ function test_Handler_destroy(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler(control);
+ var deactivated = false;
+ handler.deactivate = function() {
+ deactivated = true;
+ };
+ t.ok(handler.control,
+ "handler has a control prior to destroy");
+ t.ok(handler.map,
+ "handler has a map prior to destroy");
+ handler.destroy();
+ t.eq(handler.control, null,
+ "hanlder.control is null after destroy");
+ t.eq(handler.map, null,
+ "handler.map is null after destroy");
+ t.ok(deactivated,
+ "handler.deactivate is called by destroy");
+ }
+
+ function test_Handler_checkModifiers(t) {
+ t.plan(62);
+ var handler = new OpenLayers.Handler({});
+ handler.keyMask = null;
+ var proceed = handler.checkModifiers({});
+ t.ok(proceed,
+ "checkModifiers returns true if no keyMask on the handler");
+
+
+ /**
+ * Test checkModifiers for single keyMask values. The method should
+ * return true if the corresponding key is associated with the
+ * event. For example, if evt.shiftKey is true and handler.keyMask
+ * is OpenLayers.Handler.MOD_SHIFT, checkModifiers should return
+ * true.
+ */
+ var constants = {
+ MOD_NONE: null,
+ MOD_SHIFT: "shiftKey",
+ MOD_CTRL: "ctrlKey",
+ MOD_ALT: "altKey",
+ MOD_META: "metaKey"
+ }
+ var proceed, evt, value, c, k;
+ for(c in constants) {
+ handler.keyMask = OpenLayers.Handler[c];
+ // for this key mask, test all single key possibilities
+ for(k in constants) {
+ value = constants[k];
+ evt = {};
+ if(value) {
+ // mimic a key down on an event
+ evt[value] = true;
+ }
+ proceed = handler.checkModifiers(evt);
+ // if k == c, proceed should be true - false otherwise
+ t.eq(k == c, proceed,
+ "returns " + proceed + " if keyMask is " + c +
+ " and " + ((value) ? value : "no key") + " is down");
+ }
+ }
+
+ /**
+ * Test checkModifiers for double keyMask values. The method should
+ * return true if the corresponding key combo is associated with the
+ * event. For example, if evt.shiftKey is true and handler.keyMask
+ * is OpenLayers.Handler.MOD_SHIFT, checkModifiers should return
+ * true.
+ */
+ var constants = ["MOD_SHIFT", "MOD_CTRL", "MOD_ALT", "MOD_META"];
+ var keys = ["shiftKey", "ctrlKey", "altKey", "metaKey"];
+ var proceed, evt, c1, c2, k1, k2;
+ for(var i=0; i<constants.length-1; ++i) {
+ c1 = constants[i];
+ for(var j=i+1; j<constants.length; ++j) {
+ c2 = constants[j];
+ handler.keyMask = OpenLayers.Handler[c1] |
+ OpenLayers.Handler[c2];
+ // for this key mask, test all double key possibilities
+ for(var x=0; x<keys.length-1; ++x) {
+ k1 = keys[x];
+ for(var y=x+1; y<keys.length; ++y) {
+ k2 = keys[y];
+ evt = {};
+ evt[k1] = true;
+ evt[k2] = true;
+ proceed = handler.checkModifiers(evt);
+ // if the combo matches, proceed should be true
+ // at this point we know that i != j and x != y
+ t.eq(((i == x) || (i == y)) &&
+ ((j == x) || (j == y)),
+ proceed,
+ "returns " + proceed + " if " + c1 + " | " + c2 +
+ " and " + k1 + " + " + k2 + " is down");
+ }
+ }
+ }
+ }
+
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Box.html b/misc/openlayers/tests/Handler/Box.html
new file mode 100644
index 0000000..edb20d0
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Box.html
@@ -0,0 +1,106 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Box_constructor(t) {
+ t.plan(5);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {start: "foo", done: "bar"};
+ var options = {bar: "foo"};
+
+ var handler = new OpenLayers.Handler.Box(control, callbacks, options);
+
+ t.eq(handler.control.id, control.id, "handler created with the correct control");
+ t.eq(handler.callbacks.start, "foo", "handler created with the correct start callback");
+ t.eq(handler.callbacks.done, "bar", "handler created with the correct done callback");
+ t.eq(handler.bar, "foo", "handler created with the correct options");
+ t.ok(handler.dragHandler instanceof OpenLayers.Handler.Drag, "drag handler created");
+ }
+
+ function test_Handler_Box_draw(t) {
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Box(control, {
+ start: function(e) {
+ t.ok(true, "start callback called");
+ },
+ done: function(e) {
+ t.ok(e.equals(new OpenLayers.Bounds(5, 11, 11, 5)), "box result correct");
+ }
+ });
+ handler.activate();
+
+ // determine whether we can test the box position, the hidden frame
+ // our tests run in causes us problem here in FF and IE:
+ // IE8: left is NaN
+ // FF3: left is NaN
+ // FF4; left is NaN
+ // Chromium 10: left is 0
+ var testdiv = OpenLayers.Util.createDiv('testdiv', new OpenLayers.Pixel(5, 5));
+ map.div.appendChild(testdiv);
+ var left = parseInt(OpenLayers.Element.getStyle(testdiv, 'border-left-width'));
+ map.div.removeChild(testdiv);
+ var testAll = !isNaN(left);
+
+ t.plan(testAll ? 11 : 3);
+
+ // we change NaN values to 0 values in the handler's
+ // boxOffsets object, this is to prevent "invalid
+ // "argument" errors in IE
+ if(!testAll) {
+ var offset = handler.getBoxOffsets();
+ offset.left = 0;
+ offset.right = 0;
+ offset.top = 0;
+ offset.bottom = 0;
+ offset.width = 0;
+ offset.height = 0;
+ }
+
+
+ handler.dragHandler.start = {x: 5, y: 5};
+ handler.startBox();
+ offset = handler.getBoxOffsets();
+ handler.moveBox({x: 10, y: 10});
+ if (testAll) {
+ t.eq(parseInt(handler.zoomBox.style.left), 5 - offset.left, "x position of box correct");
+ t.eq(parseInt(handler.zoomBox.style.top), 5 - offset.top, "y position of box correct");
+ t.eq(parseInt(handler.zoomBox.style.width), 5 + offset.width + 1, "x dimension of box correct");
+ t.eq(parseInt(handler.zoomBox.style.height), 5 + offset.height + 1, "y dimension of box correct");
+ }
+ handler.moveBox({x: 0, y: 0});
+ if (testAll) {
+ t.eq(parseInt(handler.zoomBox.style.left), 0 - offset.left, "new x position of box correct");
+ t.eq(parseInt(handler.zoomBox.style.top), 0 - offset.top, "new y position of box correct");
+ t.eq(parseInt(handler.zoomBox.style.width), 5 + offset.width + 1, "x dimension of box still correct");
+ t.eq(parseInt(handler.zoomBox.style.height), 5 + offset.height + 1, "y dimension of box still correct");
+ }
+ handler.endBox({x: 11, y: 11});
+ t.eq(handler.zoomBox, null, "box removed after endBox");
+ }
+
+ function test_Handler_Box_destroy(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Box(control);
+ handler.activate();
+ try {
+ handler.destroy();
+ t.ok(true, "destroying the box handler should not raise any error");
+ } catch(err) {
+ t.fail("destroying the box handler causes trouble: " + err);
+ }
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Click.html b/misc/openlayers/tests/Handler/Click.html
new file mode 100644
index 0000000..be508ca
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Click.html
@@ -0,0 +1,735 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function px(x, y) {
+ return new OpenLayers.Pixel(x, y);
+ }
+
+ function test_Handler_Click_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Click(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_Click_activate(t) {
+ t.plan(2);
+ var control = {
+ map: new OpenLayers.Map('map')
+ };
+ var handler = new OpenLayers.Handler.Click(control);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ handler.dragging = true;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+
+ }
+
+ function test_Handler_Click_events(t) {
+ t.plan(80);
+
+ var map = new OpenLayers.Map('map');
+ var control = {
+ map: map
+ };
+ map.events.registerPriority = function(type, obj, func) {
+ var f = OpenLayers.Function.bind(func, obj)
+ var r = f({xy:null});
+ if(typeof r == "string") {
+ // this is one of the mock handler methods
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled");
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(func(), type,
+ "activate calls registerPriority with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.Click",
+ "activate calls registerPriority with the handler");
+ }
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["click", "dblclick", "mousedown", "mouseup", "rightclick", "touchstart", "touchmove", "touchend"];
+ var nonevents = ["mousemove", "resize", "focus", "blur"];
+ var handler = new OpenLayers.Handler.Click(control);
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ handler.activate();
+
+ // different listeners registered for pixelTolerance option
+ var events = ["click", "dblclick", "mousedown", "mouseup", "rightclick", "touchstart", "touchmove", "touchend"];
+ var nonevents = ["mousemove", "resize", "focus", "blur"];
+ var handler = new OpenLayers.Handler.Click(control, {}, {
+ pixelTolerance: 2
+ });
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ handler.activate();
+
+ }
+
+ var callbackMap;
+ function callbackSetup(log, options) {
+ callbackMap = new OpenLayers.Map({
+ div: "map",
+ controls: [], // no controls here because these tests use a custom setTimeout and we only want setTimeout calls from a single handler
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+ var control = new OpenLayers.Control();
+ callbackMap.addControl(control);
+
+ var callbacks = {
+ "click": function(evt) {
+ log.push({callback: "click", evt: evt});
+ },
+ "dblclick": function(evt) {
+ log.push({callback: "dblclick", evt: evt});
+ }
+ };
+ var handler = new OpenLayers.Handler.Click(control, callbacks, options);
+ handler.activate();
+
+
+ var timers = {};
+ window._setTimeout = window.setTimeout;
+ window.setTimeout = function(func, delay) {
+ log.push({method: "setTimeout", func: func, delay: delay});
+ var key = (new Date).getTime() + "-" + Math.random();
+ timers[key] = true;
+ // execute function that is supposed to be delayed
+ func();
+ return key;
+ }
+ window._clearTimeout = window.clearTimeout;
+ window.clearTimeout = function(key) {
+ log.push({
+ method: "clearTimeout",
+ keyExists: (key in timers)
+ });
+ delete timers[key];
+ }
+ return handler;
+ }
+
+ function callbackTeardown() {
+ window.setTimeout = window._setTimeout;
+ window.clearTimeout = window._clearTimeout;
+ callbackMap.destroy();
+ callbackMap = null;
+ }
+
+ function test_callbacks_click_default(t) {
+ t.plan(6);
+
+ var log = [];
+ var handler = callbackSetup(log);
+
+ // set up for single click - three tests here
+ var testEvt = {id: Math.random()};
+ handler.map.events.triggerEvent("click", testEvt);
+ t.eq(log.length, 2, "(click w/ single true) two items logged");
+
+ // first item logged is setTimeout call
+ t.eq(log[0].method, "setTimeout", "setTimeout called");
+ t.eq(typeof log[0].func, "function", "setTimeout called with a function");
+ t.eq(log[0].delay, handler.delay, "setTimeout called with proper delay");
+
+ // second item logged is from click callback
+ t.eq(log[1].callback, "click", "click callback called");
+ t.eq(log[1].evt.id, testEvt.id, "got correct event");
+
+ callbackTeardown();
+ }
+
+ function test_callbacks_dblclick_default(t) {
+ t.plan(1);
+
+ var log = [];
+ var handler = callbackSetup(log);
+ var testEvt = {id: Math.random()};
+ handler.map.events.triggerEvent("dblclick", testEvt);
+
+ t.eq(log.length, 0, "nothing happens by default with dblclick (double is false)");
+
+ callbackTeardown();
+
+ }
+
+ function test_callbacks_dblclick_double(t) {
+ t.plan(3);
+
+ var log = [];
+ var handler = callbackSetup(log, {"double": true});
+ var testEvt = {id: Math.random()};
+ handler.map.events.triggerEvent("dblclick", testEvt);
+
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0].callback, "dblclick", "dblclick callback called")
+ t.eq(log[0].evt.id, testEvt.id, "dblclick callback called with event");
+
+ callbackTeardown();
+
+ }
+
+ function test_callbacks_dblclick_sequence(t) {
+ t.plan(8);
+
+ var log = [];
+ var handler = callbackSetup(log, {"double": true});
+ var testEvt = {id: Math.random()};
+
+ // first click - set timer for next
+ handler.map.events.triggerEvent("click", testEvt);
+ t.ok(handler.timerId != null, "timer is set");
+ log.pop(); // because the test setTimeout is synchronous we get the click callback immediately
+ t.eq(log.length, 1, "one item logged (after pop due to synchronous setTimeout call in our tests");
+ t.eq(log[0].method, "setTimeout", "setTimeout called first");
+
+ // second click - timer cleared
+ handler.map.events.triggerEvent("click", testEvt);
+ t.ok(handler.timerId == null, "timer is cleared");
+ t.eq(log.length, 2, "two items logged after second click");
+ t.eq(log[1].method, "clearTimeout", "clearTimeout called second");
+
+ // dblclick event - callback called
+ handler.map.events.triggerEvent("dblclick", testEvt);
+ t.eq(log.length, 3, "three items logged");
+ t.eq(log[2].callback, "dblclick", "dblclick callback called third");
+
+ callbackTeardown();
+
+ }
+
+ function test_callbacks_within_pixelTolerance(t) {
+ t.plan(1);
+
+ var log = [];
+ var handler = callbackSetup(log, {"double": true, pixelTolerance: 2});
+
+ var down = {
+ xy: px(0, 0)
+ };
+ var up = {
+ xy: px(0, 1)
+ };
+
+ handler.map.events.triggerEvent("mousedown", down);
+ handler.map.events.triggerEvent("mouseup", up);
+ handler.map.events.triggerEvent("click", up);
+
+ t.eq(log[log.length-1].callback, "click", "click callback called");
+
+ callbackTeardown();
+
+ }
+
+ function test_callbacks_outside_pixelTolerance(t) {
+ t.plan(2);
+
+ var log = [];
+ var handler = callbackSetup(log, {pixelTolerance: 2});
+
+ var down = {
+ xy: px(0, 0)
+ };
+ var up = {
+ xy: px(2, 3)
+ };
+
+ handler.map.events.triggerEvent("mousedown", down);
+ t.ok(handler.down && handler.down.xy.equals(down.xy), "down position set");
+
+ handler.map.events.triggerEvent("mouseup", up);
+ handler.map.events.triggerEvent("click", up);
+ t.eq(log.length, 0, "nothing logged - event outside tolerance");
+
+ callbackTeardown();
+
+ }
+
+ function test_callbacks_within_dblclickTolerance(t) {
+ t.plan(6);
+
+ var log = [];
+ var handler = callbackSetup(log, {single: false, "double": true, dblclickTolerance: 8});
+
+ var first = {
+ xy: px(0, 0)
+ };
+ var second = {
+ xy: px(0, 5)
+ };
+
+ handler.map.events.triggerEvent("mousedown", first);
+ handler.map.events.triggerEvent("mouseup", first);
+ handler.map.events.triggerEvent("click", first);
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].method, "setTimeout", "setTimeout called");
+
+ handler.map.events.triggerEvent("mousedown", second);
+ handler.map.events.triggerEvent("mouseup", second);
+ handler.map.events.triggerEvent("click", second);
+ t.eq(log.length, 2, "two events logged");
+ t.eq(log[1] && log[1].method, "clearTimeout", "clearTimeout called");
+
+ handler.map.events.triggerEvent("dblclick", second);
+ t.eq(log.length, 3, "three items logged");
+ t.eq(log[2] && log[2].callback, "dblclick", "dblclick callback called");
+
+ callbackTeardown();
+ }
+
+ function test_callbacks_outside_dblclickTolerance(t) {
+ t.plan(5);
+
+ var log = [];
+ // default dblclickTolerance is 13
+ var handler = callbackSetup(log, {single: false, "double": true});
+
+ var first = {
+ xy: px(0, 0)
+ };
+ var second = {
+ xy: px(13.5, 0)
+ };
+
+ handler.map.events.triggerEvent("mousedown", first);
+ handler.map.events.triggerEvent("mouseup", first);
+ handler.map.events.triggerEvent("click", first);
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].method, "setTimeout", "setTimeout called");
+
+ handler.map.events.triggerEvent("mousedown", second);
+ handler.map.events.triggerEvent("mouseup", second);
+ handler.map.events.triggerEvent("click", second);
+ t.eq(log.length, 2, "two items logged");
+ t.eq(log[1] && log[1].method, "clearTimeout", "clearTimeout called");
+
+ handler.map.events.triggerEvent("dblclick", second);
+ t.eq(log.length, 2, "still two items logged - dblclick callback is not called");
+
+ callbackTeardown();
+ }
+
+ function test_callbacks_multitouch_single(t) {
+
+ t.plan(2);
+
+ var log = [];
+
+ var callbacks = {
+ click: function(evt) {
+ log.push({callback: "click", type: evt.type});
+ },
+ dblclick: function(evt) {
+ log.push({callback: "dblclick", type: evt.type});
+ }
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Click(
+ control, callbacks,
+ {"double": true, single: true, pixelTolerance: 2}
+ );
+
+ // we override here so we don't have to wait for the timeout
+ handler.queuePotentialClick = function(evt) {
+ log.push({potential: true, evt: evt});
+ OpenLayers.Handler.Click.prototype.queuePotentialClick.call(this, evt);
+ }
+
+ handler.activate();
+
+ function handle(o) {
+ var touches = [];
+ if (("x0" in o) && ("y0" in o)) {
+ touches.push({
+ clientX: o.x0, clientY: o.y0
+ });
+ }
+ if (("x1" in o) && ("y1" in o)) {
+ touches.push({
+ clientX: o.x1, clientY: o.y1
+ });
+ }
+ handler.map.events.handleBrowserEvent({
+ type: o.type, touches: touches
+ });
+ }
+
+ // a typical multitouch sequence goes like this:
+ // touchstart, touchstart, touchend, touchend
+ handle({type: "touchstart", x0: 10, y0: 10});
+ handle({type: "touchstart", x0: 10, y0: 10, x1: 30, y1: 15});
+ handle({type: "touchend"});
+ handle({type: "touchend"});
+
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].potential, true, "click in queue - no dblclick called");
+
+ map.destroy();
+ }
+
+ function test_Handler_Click_deactivate(t) {
+ t.plan(6);
+ var control = {
+ map: new OpenLayers.Map('map')
+ };
+ var handler = new OpenLayers.Handler.Click(control);
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler was not already active");
+ handler.active = true;
+ handler.down = true;
+ handler.timerId = true;
+ handler.touch = true;
+ handler.last = true;
+ deactivated = handler.deactivate();
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+ t.eq(handler.down, null,
+ "deactivate sets down to null");
+ t.eq(handler.timerId, null,
+ "deactivate sets timerId to null");
+ t.eq(handler.touch, false,
+ "deactivate sets touch to false");
+ t.eq(handler.last, null,
+ "deactivate sets last to null");
+
+ }
+
+ function test_Handler_Click_mouseup(t) {
+ t.plan(11);
+
+ var map = new OpenLayers.Map("map");
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Click(control);
+
+ var testEvent = {id: Math.random()};
+ var propagate = true;
+ var log, got, modMatch, rightClick;
+
+ // override methods to log what is called
+ var temp = OpenLayers.Event.isRightClick;
+ OpenLayers.Event.isRightClick = function(e) {
+ log.push({method: "isRightClick", evt: e});
+ return rightClick;
+ };
+ handler.checkModifiers = function(e) {
+ log.push({method: "checkModifiers", evt: e});
+ return modMatch;
+ };
+ handler.rightclick = function(e) {
+ log.push({method: "rightclick", evt: e});
+ return propagate;
+ };
+
+
+ // simulate an event with non-matching modifiers
+ log = [];
+ modMatch = false;
+ rightClick = false;
+ got = handler.mouseup(testEvent);
+ t.eq(log.length, 1, "one item logged");
+ t.eq(log[0] && log[0].method, "checkModifiers", "a) checkModifiers called first");
+ t.eq(log[0] && log[0].evt, testEvent, "a) first method called with correct event");
+
+
+ // modifiers, handlerightclicks, and isrightclick
+ log = [];
+ rightClick = true;
+ modMatch = true;
+ handler.control.handleRightClicks = true;
+ got = handler.mouseup(testEvent);
+ t.eq(log.length, 3, "three items logged");
+ t.eq(log[0] && log[0].method, "checkModifiers", "b) checkModifiers called first");
+ t.eq(log[0] && log[0].evt, testEvent, "b) first method called with correct event");
+ t.eq(log[1] && log[1].method, "isRightClick", "b) isRightClick called second");
+ t.eq(log[1] && log[1].evt, testEvent, "b) second method called with correct event");
+ t.eq(log[2] && log[2].method, "rightclick", "b) rightclick called third");
+ t.eq(log[2] && log[2].evt, testEvent, "b) third method called with correct event");
+ t.eq(got, propagate, "b) return from handler's rightclick returned from mouseup");
+
+ OpenLayers.Event.isRightClick = temp;
+ map.destroy();
+ }
+
+ function test_touch_click(t) {
+ t.plan(5);
+
+ // set up
+
+ var log;
+
+ var map = new OpenLayers.Map('map');
+ var control = {map: map};
+
+ var callbacks = {
+ 'click': function(e) {
+ log = {x: e.xy.x, y: e.xy.y,
+ lastTouches: e.lastTouches};
+ }
+ };
+
+ var handler = new OpenLayers.Handler.Click(
+ control, callbacks,
+ {'single': true, pixelTolerance: null});
+
+ // test
+
+ // the common case: a touchstart followed by a touchend
+ log = null;
+ handler.touchstart({xy: px(1, 1), touches: ["foo"]});
+ handler.touchend({touches: ["foo"]});
+
+ t.delay_call(1, function() {
+ t.ok(log != null, "click callback called");
+ if(log != null) {
+ t.eq(log.x, 1, "evt.xy.x as expected");
+ t.eq(log.y, 1, "evt.xy.y as expected");
+ t.ok(log.lastTouches, "evt.lastTouches as expected");
+ }
+
+ // now emulate a touch where touchstart doesn't propagate
+ // to the click handler, i.e. the click handler gets a
+ // touchend only
+ log = null;
+ handler.touchend({touches: ["foo"]});
+
+ t.delay_call(1, function() {
+ t.ok(log == null, "click callback not called");
+
+ // tear down
+ map.destroy();
+ });
+ });
+ }
+
+ function test_touch_within_dblclickTolerance(t) {
+ t.plan(4);
+
+ var log;
+
+ var callbacks = {
+ click: function(evt) {
+ log.push({callback: "click", type: evt.type});
+ },
+ dblclick: function(evt) {
+ log.push({callback: "dblclick", type: evt.type});
+ }
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Click(
+ control, callbacks,
+ {"double": true, single: true, pixelTolerance: 2}
+ );
+ handler.activate();
+
+ function handle(type, x, y) {
+ map.events.handleBrowserEvent({
+ type: type,
+ touches: [
+ {clientX: x, clientY: y}
+ ]
+ });
+ }
+
+ // test
+ log = [];
+ // sequence of two clicks on a touch device
+ // click 1
+ handle("touchstart", 10, 10);
+ handle("touchend", 11, 10);
+ handle("mousemove", 11, 10);
+ handle("mousedown", 10, 10);
+ handle("mouseup", 11, 10);
+ handle("click", 11, 10);
+ // click 2
+ handle("touchstart", 12, 10);
+ handle("touchend", 12, 10);
+ handle("mousedown", 12, 10);
+ handle("mouseup", 12, 10);
+ handle("click", 12, 10);
+
+ t.eq(log.length, 1, "one callback called");
+ t.eq(log[0] && log[0].callback, "dblclick", "click callback called");
+ t.eq(log[0] && log[0].type, "touchend", "click callback called with touchend event");
+ t.ok(!handler.timerId, "handler doesn't have a timerId waiting for click")
+
+ // tear down
+ map.destroy();
+ }
+
+ function test_touch_outside_dblclickTolerance(t) {
+ t.plan(2);
+
+ var log;
+
+ var callbacks = {
+ click: function(evt) {
+ log.push({callback: "click", type: evt.type});
+ },
+ dblclick: function(evt) {
+ log.push({callback: "dblclick", type: evt.type});
+ }
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Click(
+ control, callbacks,
+ {"double": true, single: true, pixelTolerance: 2, dblclickTolerance: 8}
+ );
+ handler.activate();
+
+ function handle(type, x, y) {
+ var touches = [];
+ if (x !== undefined && y !== undefined) {
+ touches.push({
+ clientX: x, clientY: y
+ });
+ }
+ map.events.handleBrowserEvent({
+ type: type, touches: touches
+ });
+ }
+
+ // test
+ log = [];
+ // sequence of two clicks on a touch device
+ // click 1
+ handle("touchstart", 10, 10);
+ handle("touchend");
+ handle("mousemove", 11, 10);
+ handle("mousedown", 10, 10);
+ handle("mouseup", 11, 10);
+ handle("click", 11, 10);
+ // click 2
+ handle("touchstart", 20, 10);
+ handle("touchend");
+ handle("mousedown", 20, 10);
+ handle("mouseup", 20, 10);
+ handle("click", 20, 10);
+
+ t.eq(log.length, 0, "no callbacks called");
+ t.ok(!handler.timerId, "handler doesn't have a timerId waiting for click")
+
+ // tear down
+ map.destroy();
+ }
+
+ function test_touchstart(t) {
+ // a test to verify that the touchstart function does
+ // unregister the mouse listeners when it's called the
+ // first time
+
+ t.plan(7);
+
+ // set up
+
+ var map = new OpenLayers.Map("map", {
+ controls: []
+ });
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Click(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ handler.activate();
+
+ function allRegistered() {
+ var eventTypes = ['mousedown', 'mouseup', 'click', 'dblclick'],
+ eventType,
+ listeners,
+ listener,
+ flag;
+ for(var i=0, ilen=eventTypes.length; i<ilen; i++) {
+ flag = false;
+ eventType = eventTypes[i];
+ listeners = map.events.listeners[eventType];
+ for(var j=0, jlen=listeners.length; j<jlen; j++) {
+ listener = listeners[j];
+ if(listener.func === handler[eventType] && listener.obj === handler) {
+ flag = true;
+ break;
+ }
+ }
+ if(!flag) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // test
+
+ t.ok(allRegistered(), 'mouse listeners are registered');
+ handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(map.events.listeners.mousedown.length, 0,"mousedown is not registered");
+ t.eq(map.events.listeners.mouseup.length, 0,"mouseup is not registered");
+ t.eq(map.events.listeners.click.length, 0,"click is not registered");
+ t.eq(map.events.listeners.dblclick.length, 0,"dblclick is not registered");
+
+ t.ok(handler.touch, 'handler.touch is set');
+
+ handler.deactivate();
+ t.ok(!handler.touch, 'handler.touch is not set');
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Drag.html b/misc/openlayers/tests/Handler/Drag.html
new file mode 100644
index 0000000..fa9a3b2
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Drag.html
@@ -0,0 +1,603 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Drag_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Drag(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_Drag_activate(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Drag(control);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ handler.dragging = true;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ t.ok(!handler.dragging,
+ "activate sets dragging to false");
+
+ }
+
+ function test_Handler_Drag_events(t) {
+ t.plan(40);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Drag(control);
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["mousedown", "mouseup", "mousemove", "mouseout", "click",
+ "touchstart", "touchmove", "touchend"];
+ var nonevents = ["dblclick", "resize", "focus", "blur"];
+ map.events.registerPriority = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ // this is one of the mock handler methods
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled");
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(func(), type,
+ "activate calls registerPriority with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.Drag",
+ "activate calls registerPriority with the handler");
+ }
+ }
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+
+ var activated = handler.activate();
+
+ }
+
+ function test_Handler_Drag_callbacks(t) {
+ t.plan(33);
+
+ var map = new OpenLayers.Map('map', {controls: []});
+
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ // set callback methods (out doesn't get an xy)
+ var events = ["down", "move", "up", "done"];
+ var testEvents = {};
+ var xys = {};
+ var callbacks = {};
+ for(var i=0; i<events.length; ++i) {
+ var px = new OpenLayers.Pixel(Math.random(), Math.random());
+ testEvents[events[i]] = {xy: px};
+ setCallback(events[i]);
+ }
+ function setCallback(key) {
+ callbacks[key] = function(evtxy) {
+ t.ok(evtxy.x == testEvents[key].xy.x &&
+ evtxy.y == testEvents[key].xy.y,
+ key + " callback called with the proper evt.xy");
+ }
+ }
+
+ var handler = new OpenLayers.Handler.Drag(control, callbacks);
+ handler.activate();
+
+ var oldIsLeftClick = OpenLayers.Event.isLeftClick;
+ var oldPreventDefault = OpenLayers.Event.preventDefault;
+ var oldCheckModifiers = handler.checkModifiers;
+
+ // test mousedown with right click
+ OpenLayers.Event.isLeftClick = function() {
+ return false;
+ }
+ handler.checkModifiers = function() {
+ return true;
+ }
+ handler.started = true;
+ handler.start = {x: "foo", y: "bar"};
+ handler.last = {x: "foo", y: "bar"};
+ map.events.triggerEvent("mousedown", testEvents.down);
+ t.ok(!handler.started, "right-click sets started to false");
+ t.eq(handler.start, null, "right-click sets start to null");
+ t.eq(handler.last, null, "right-click sets last to null");
+
+ // test mousedown with improper modifier
+ OpenLayers.Event.isLeftClick = function() {
+ return true;
+ }
+ handler.checkModifiers = function() {
+ return false;
+ }
+ handler.started = true;
+ handler.start = {x: "foo", y: "bar"};
+ handler.last = {x: "foo", y: "bar"};
+ map.events.triggerEvent("mousedown", testEvents.down);
+ t.ok(!handler.started, "bad modifier sets started to false");
+ t.eq(handler.start, null, "bad modifier sets start to null");
+ t.eq(handler.last, null, "bad modifier sets last to null");
+
+ // test mousedown
+ handler.checkModifiers = function(evt) {
+ t.ok(evt.xy.x == testEvents.down.xy.x &&
+ evt.xy.y == testEvents.down.xy.y,
+ "mousedown calls checkModifiers with the proper event");
+ return true;
+ }
+ OpenLayers.Event.isLeftClick = function(evt) {
+ t.ok(evt.xy.x == testEvents.down.xy.x &&
+ evt.xy.y == testEvents.down.xy.y,
+ "mousedown calls isLeftClick with the proper event");
+ return true;
+ }
+ OpenLayers.Event.preventDefault = function(evt) {
+ t.ok(evt.xy.x == testEvents.down.xy.x && evt.xy.y == testEvents.down.xy.y,
+ "mousedown default action is disabled");
+ }
+ map.events.triggerEvent("mousedown", testEvents.down);
+ t.ok(handler.started, "mousedown sets the started flag to true");
+ t.ok(!handler.dragging, "mouse down sets the dragging flag to false");
+ t.ok(handler.start.x == testEvents.down.xy.x &&
+ handler.start.y == testEvents.down.xy.y,
+ "mouse down sets handler.start correctly");
+ t.ok(handler.last.x == testEvents.down.xy.x &&
+ handler.last.y == testEvents.down.xy.y,
+ "mouse down sets handler.last correctly");
+
+ OpenLayers.Event.preventDefault = oldPreventDefault;
+ OpenLayers.Event.isLeftClick = oldIsLeftClick;
+ handler.checkModifiers = oldCheckModifiers;
+
+ // test mouseup before mousemove
+ var realUp = testEvents.up;
+ testEvents.up = testEvents.down;
+ // this will fail with notice about the done callback being called
+ // if done is called when it shouldn't be
+ map.events.triggerEvent("mouseup", testEvents.up);
+ testEvents.up = realUp;
+
+ // test mousemove
+ handler.started = false;
+ map.events.triggerEvent("mousemove", {xy: {x: null, y: null}});
+ // if the handler triggers the move callback, it will be with the
+ // incorrect evt.xy
+ t.ok(true,
+ "mousemove before the handler has started doesn't call move");
+
+ handler.started = true;
+ map.events.triggerEvent("mousemove", testEvents.move);
+ t.ok(handler.dragging, "mousemove sets the dragging flag to true");
+ t.ok(handler.start.x == testEvents.down.xy.x &&
+ handler.start.y == testEvents.down.xy.y,
+ "mouse move leaves handler.start alone");
+ t.ok(handler.last.x == testEvents.move.xy.x &&
+ handler.last.y == testEvents.move.xy.y,
+ "mouse move sets handler.last correctly");
+
+ // a second move with the same evt.xy should not trigger move callback
+ // if it does, the test page will complain about a bad plan number
+ var oldMove = handler.callbacks.move;
+ handler.callbacks.move = function() {
+ t.ok(false,
+ "a second move with the same evt.xy should not trigger a move callback");
+ }
+ map.events.triggerEvent("mousemove", testEvents.move);
+ handler.callbacks.move = oldMove;
+
+ // test mouseup
+ handler.started = false;
+ map.events.triggerEvent("mouseup", {xy: {x: null, y: null}});
+ // if the handler triggers the up callback, it will be with the
+ // incorrect evt.xy
+ t.ok(true,
+ "mouseup before the handler has started doesn't call up");
+
+ handler.started = true;
+ // mouseup triggers the up and done callbacks
+ testEvents.done = testEvents.up;
+ map.events.triggerEvent("mouseup", testEvents.up);
+ t.ok(!this.started, "mouseup sets the started flag to false");
+ t.ok(!this.dragging, "mouseup sets the dragging flag to false");
+
+ // test mouseout
+ handler.started = false;
+ map.events.triggerEvent("mouseout", {xy: {x: null, y: null}});
+ // if the handler triggers the out or done callback, it will be with the
+ // incorrect evt.xy
+ t.ok(true,
+ "mouseout before the handler has started doesn't call out or done");
+
+ handler.started = true;
+ var oldMouseLeft = OpenLayers.Util.mouseLeft;
+ OpenLayers.Util.mouseLeft = function(evt, element) {
+ t.ok(evt.xy.x == testEvents.done.xy.x &&
+ evt.xy.y == testEvents.done.xy.y,
+ "mouseout calls Util.mouseLeft with the correct event");
+ t.eq(element.id, map.viewPortDiv.id,
+ "mouseout calls Util.mouseLeft with the correct element");
+ return true;
+ }
+ // mouseup triggers the out and done callbacks
+ // out callback gets no arguments
+ handler.callbacks.out = function() {
+ t.eq(arguments.length, 0,
+ "mouseout calls out callback with no arguments");
+ }
+ map.events.triggerEvent("mouseout", testEvents.done);
+ t.ok(!handler.started, "mouseout sets started flag to false");
+ t.ok(!handler.dragging, "mouseout sets dragging flag to false");
+ OpenLayers.Util.mouseLeft = oldMouseLeft;
+
+ // test click with the click.html example - the click method on the
+ // drag handler returns (handler.start == handler.last), stopping
+ // propagation of the click event if the mouse moved during a drag.
+
+ // regression tests will assure that the drag handler doesn't mess
+ // with anything else on a click
+ function getProperties(obj) {
+ var props = {};
+ for(key in obj) {
+ if(typeof obj[key] != "function" && typeof obj[key] != "object") {
+ props[key] = obj[key];
+ }
+ }
+ return props;
+ }
+ var before = getProperties(handler);
+ map.events.triggerEvent("click", null);
+ var after = getProperties(handler);
+ t.eq(before, after, "click doesn't mess with handler");
+
+ }
+
+ function test_Handler_Drag_touch(t) {
+ // In this test we verify that "touchstart", "touchmove", and
+ // "touchend" events set expected states in the drag handler.
+ // We also verify that we prevent the default as appropriate.
+
+ t.plan(19);
+
+ // set up
+
+ var m = new OpenLayers.Map('map', {controls: []});
+ var c = new OpenLayers.Control();
+ m.addControl(c);
+ var h = new OpenLayers.Handler.Drag(c, {
+ done: function(px) {
+ log.push(px);
+ }
+ });
+ h.activate();
+
+ var _preventDefault = OpenLayers.Event.preventDefault;
+ OpenLayers.Event.preventDefault = function(e) {
+ log.push(e);
+ };
+
+ var Px = OpenLayers.Pixel, e;
+ var log = [];
+
+ // test
+ e = {touches: [{}], xy: new Px(0, 0)};
+ m.events.triggerEvent('touchstart', e);
+ t.eq(h.started, true, '[touchstart] started is set');
+ t.eq(h.start.x, 0, '[touchstart] start.x is correct');
+ t.eq(h.start.y, 0, '[touchstart] start.y is correct');
+ t.eq(log.length, 1, '[touchstart] one item in log');
+ t.ok(log[0] === e, "touchstart", '[touchstart] event is stopped');
+ t.eq(m.events.listeners.mousedown.length, 0,"mousedown is not registered");
+ t.eq(m.events.listeners.mouseup.length, 0,"mouseup is not registered");
+ t.eq(m.events.listeners.mousemove.length, 0,"mousemove is not registered");
+ t.eq(m.events.listeners.click.length, 0,"click is not registered");
+ t.eq(m.events.listeners.mouseout.length, 0,"mouseout is not registered");
+
+ e = {xy: new Px(1, 1)};
+ m.events.triggerEvent('touchmove', e);
+ t.eq(h.dragging, true, '[touchmove] dragging is set');
+ t.eq(h.last.x, 1, '[touchmove] last.x is correct');
+ t.eq(h.last.y, 1, '[touchmove] last.y is correct');
+ t.eq(log.length, 1, '[touchmove] one item in log (event is not stopped)');
+
+ e = {xy: new Px(2, 2)};
+ m.events.triggerEvent('touchend', e);
+ t.eq(h.started, false, '[touchend] started is reset');
+ t.eq(h.started, false, '[touchend] started is reset');
+ // the "done" callback gets the position of the last touchmove
+ t.eq(log.length, 2, '[touchend] two items in log');
+ t.ok(log[1] instanceof Px, '[touchend] got');
+ t.ok(log[1].equals(e.xy), '[touchend] done callback got correct position');
+
+ // tear down
+
+ OpenLayers.Event.preventDefault = _preventDefault;
+ m.destroy();
+ }
+
+ function test_Handler_Drag_submethods(t) {
+ t.plan(8);
+
+ var map = new OpenLayers.Map('map', {controls: []});
+
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+
+ var handler = new OpenLayers.Handler.Drag(control, {});
+ // set test events
+ var events = ["down", "move", "up", "out"];
+ var onselect = {
+ "move": OpenLayers.Function.False,
+ "up": OpenLayers.Function.False,
+ "out": OpenLayers.Function.True
+ }
+ var testEvents = {};
+ var type, px;
+ for(var i=0; i<events.length; ++i) {
+ type = events[i];
+ px = new OpenLayers.Pixel(Math.random(), Math.random());
+ testEvents[type] = {xy: px};
+ setMethod(type);
+ }
+ function setMethod(type) {
+ handler[type] = function(evt) {
+ t.ok(evt.xy.x == testEvents[type].xy.x &&
+ evt.xy.y == testEvents[type].xy.y,
+ "handler." + type + " called with the right event");
+ onselect[type] && t.ok(document.onselectstart === onselect[type], "document.onselectstart listener is correct after " + type);
+ }
+ }
+ handler.activate();
+
+ // pretend that we have gone through a down-move-up-out cycle before
+ handler.oldOnselectstart = OpenLayers.Function.True;
+
+ // test mousedown
+ handler.checkModifiers = function(evt) {
+ return true;
+ }
+ var oldIsLeftClick = OpenLayers.Event.isLeftClick;
+ OpenLayers.Event.isLeftClick = function(evt) {
+ return true;
+ }
+ map.events.triggerEvent("mousedown", testEvents.down);
+ OpenLayers.Event.isLeftClick = oldIsLeftClick;
+
+ // test mousemove
+ map.events.triggerEvent("mousemove", testEvents.move);
+
+ // test mouseup
+ map.events.triggerEvent("mouseup", testEvents.up);
+
+ // test mouseout
+ var oldMouseLeft = OpenLayers.Util.mouseLeft;
+ OpenLayers.Util.mouseLeft = function() {
+ return true;
+ };
+ handler.started = true;
+ map.events.triggerEvent("mouseout", testEvents.out);
+ OpenLayers.Util.mouseLeft = oldMouseLeft;
+
+ t.ok(document.onselectstart === OpenLayers.Function.True, "document.onselectstart listener correct after down-move-up-out cycle");
+
+ }
+
+ function test_Handler_Drag_deactivate(t) {
+ t.plan(7);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Drag(control);
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler was not already active");
+ handler.active = true;
+ handler.dragging = true;
+ handler.touch = true;
+ deactivated = handler.deactivate();
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+ t.ok(!handler.started,
+ "deactivate sets started to false");
+ t.ok(!handler.dragging,
+ "deactivate sets dragging to false");
+ t.ok(handler.start == null,
+ "deactivate sets start to null");
+ t.ok(handler.last == null,
+ "deactivate sets last to null");
+ t.ok(!handler.touch,
+ "deactivate sets touch to false");
+ }
+
+ function test_interval_timer_after_mouseup(t) {
+ t.plan(5);
+
+ // set up
+
+ var map = new OpenLayers.Map('map');
+
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var moveCnt;
+
+ var handler = new OpenLayers.Handler.Drag(control, {}, {
+ interval: 1,
+ move: function() {
+ moveCnt++;
+ }
+ });
+ handler.activate();
+
+ handler.checkModifiers = function() { return true; };
+
+ var ilc = OpenLayers.Event.isLeftClick;
+ OpenLayers.Event.isLeftClick = function() { return true; };
+
+ // test
+
+ moveCnt = 0;
+
+ var xy1 = new OpenLayers.Pixel(1, 2);
+ handler.mousedown({xy: xy1});
+ t.ok(handler.last == xy1, "[mousedown] last is as expected");
+ var xy2 = new OpenLayers.Pixel(2, 3);
+ handler.mousemove({xy: xy2});
+ t.ok(handler.last == xy2, "[mousemove 1] last is as expected");
+ t.ok(handler.timeoutId != null, "[mousemove 1] timeoutId is set");
+ var xy3 = new OpenLayers.Pixel(3, 4);
+ handler.mousemove({xy: xy3});
+ t.ok(handler.last == xy2, "[mousemove 2] last is as expected");
+ var xy4 = new OpenLayers.Pixel(4, 5);
+ handler.mouseup({xy: xy4});
+
+ t.delay_call(3, function() {
+ // the timer should not cause a move
+ t.eq(moveCnt, 1, "move called once");
+ // tear down
+ OpenLayers.Event.isLeftClick = ilc;
+ });
+ }
+
+ function test_interval_timer_after_mousedown(t) {
+ t.plan(5);
+
+ // set up
+
+ var map = new OpenLayers.Map('map');
+
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var moveCnt;
+
+ var handler = new OpenLayers.Handler.Drag(control, {}, {
+ interval: 1,
+ move: function() {
+ moveCnt++;
+ }
+ });
+ handler.activate();
+
+ handler.checkModifiers = function() { return true; };
+
+ var ilc = OpenLayers.Event.isLeftClick;
+ OpenLayers.Event.isLeftClick = function() { return true; };
+
+ // test
+
+ moveCnt = 0;
+
+ var xy1 = new OpenLayers.Pixel(1, 2);
+ handler.mousedown({xy: xy1});
+ t.ok(handler.last == xy1, "[mousedown] last is as expected");
+ var xy2 = new OpenLayers.Pixel(2, 3);
+ handler.mousemove({xy: xy2});
+ t.ok(handler.last == xy2, "[mousemove 1] last is as expected");
+ t.ok(handler.timeoutId != null, "[mousemove 1] timeoutId is set");
+ var xy3 = new OpenLayers.Pixel(3, 4);
+ handler.mousemove({xy: xy3});
+ t.ok(handler.last == xy2, "[mousemove 2] last is as expected");
+ var xy4 = new OpenLayers.Pixel(4, 5);
+ handler.mouseup({xy: xy4});
+ var xy5 = new OpenLayers.Pixel(5, 6);
+ handler.mousedown({xy: xy4});
+
+ t.delay_call(3, function() {
+ // the timer should not cause a move
+ t.eq(moveCnt, 1, "move called once");
+ // tear down
+ OpenLayers.Event.isLeftClick = ilc;
+ });
+ }
+
+ function test_interval_timer_before_mouseup(t) {
+ t.plan(5);
+
+ // set up
+
+ var map = new OpenLayers.Map('map');
+
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var moveCnt;
+
+ var handler = new OpenLayers.Handler.Drag(control, {}, {
+ interval: 1,
+ move: function() {
+ moveCnt++;
+ }
+ });
+ handler.activate();
+
+ handler.checkModifiers = function() { return true; };
+
+ var ilc = OpenLayers.Event.isLeftClick;
+ OpenLayers.Event.isLeftClick = function() { return true; };
+
+ // test
+
+ moveCnt = 0;
+
+ var xy1 = new OpenLayers.Pixel(1, 2);
+ handler.mousedown({xy: xy1});
+ t.ok(handler.last == xy1, "[mousedown] last is as expected");
+ var xy2 = new OpenLayers.Pixel(2, 3);
+ handler.mousemove({xy: xy2});
+ t.ok(handler.last == xy2, "[mousemove 1] last is as expected");
+ t.ok(handler.timeoutId != null, "[mousemove 1] timeoutId is set");
+ var xy3 = new OpenLayers.Pixel(3, 4);
+ handler.mousemove({xy: xy3});
+ t.ok(handler.last == xy2, "[mousemove 2] last is as expected");
+
+ t.delay_call(3, function() {
+ // the timer should cause a move
+ t.eq(moveCnt, 2, "move called twice");
+ var xy4 = new OpenLayers.Pixel(4, 5);
+ handler.mouseup({xy: xy4});
+ // tear down
+ OpenLayers.Event.isLeftClick = ilc;
+ });
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Feature.html b/misc/openlayers/tests/Handler/Feature.html
new file mode 100644
index 0000000..4a78e14
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Feature.html
@@ -0,0 +1,698 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_initialize(t) {
+ t.plan(4);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var layer = "boo";
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Feature(control, layer,
+ callbacks, options);
+
+ t.eq(handler.layer, "boo",
+ "layer property properly set");
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_activate(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+ var handler = new OpenLayers.Handler.Feature(control, layer);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+
+ var zIndex = layer.div.style.zIndex;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ t.eq(parseInt(layer.div.style.zIndex),
+ map.Z_INDEX_BASE['Feature'],
+ "layer z-index properly adjusted");
+
+ }
+ function test_events(t) {
+ t.plan(35);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+ var handler = new OpenLayers.Handler.Feature(control, layer);
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["mousedown", "mouseup", "mousemove", "click", "dblclick", "touchstart", "touchmove"];
+ var nonevents = ["mouseout", "resize", "focus", "blur", "touchend"];
+ map.events.registerPriority = function(type, obj, func) {
+ var output = func();
+ // Don't listen for setEvent handlers (#902)
+ if (typeof output == "string") {
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled");
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(func(), type,
+ "activate calls registerPriority with the correct method:"+type);
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.Feature",
+ "activate calls registerPriority with the handler");
+ }
+ }
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+
+ var activated = handler.activate();
+
+ }
+
+ function test_geometrytype_limit(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ feature.layer = layer;
+ layer.getFeatureFromEvent = function(evt) { return feature };
+ map.addLayer(layer);
+ var handler = new OpenLayers.Handler.Feature(control, layer, {}, {'geometryTypes':['OpenLayers.Geometry.Point']});
+ handler.activate();
+ handler.callback = function(type,featurelist) {
+ t.eq(featurelist[0].id, feature.id, "Correct feature called back on");
+ }
+ handler.handle({type: "click"});
+ handler.feature = null;
+ handler.lastFeature = null;
+ handler.callback = function(type,featurelist) {
+ t.fail("Shouldn't have called back on " + featurelist[0].geometry);
+ }
+ feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(0,0));
+ feature.layer = layer;
+ handler.handle("click", {});
+ }
+
+ function test_callbacks(t) {
+ t.plan(14);
+
+ var map = new OpenLayers.Map('map', {controls: []});
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+
+ var callbacks = {};
+ var newFeature, lastFeature;
+ var evtPx = {xy: new OpenLayers.Pixel(Math.random(), Math.random())};
+
+ // define a callback factory function
+ function getCallback(evt, feature) {
+ return function(f) {
+ t.ok(f == feature, evt + " callback called with proper feature");
+ };
+ }
+
+ // override the layer's getFeatureFromEvent func so that it always
+ // returns newFeature
+ layer.getFeatureFromEvent = function(evt) { return newFeature; };
+
+ var handler = new OpenLayers.Handler.Feature(control, layer, callbacks);
+ handler.activate();
+
+ // test click in new feature
+ // only 'click' callback should be called
+ handler.feature = null;
+ lastFeature = null;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['click'] = getCallback('click', newFeature);
+ callbacks['clickout'] = getCallback('clickout', lastFeature);
+ evtPx.type = "click";
+ map.events.triggerEvent('click', evtPx);
+
+ // test click in new feature and out of last feature
+ // both 'click' and 'clickout' callbacks should be called
+ lastFeature = newFeature;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['click'] = getCallback('click', newFeature);
+ callbacks['clickout'] = getCallback('clickout', lastFeature);
+ evtPx.type = "click";
+ map.events.triggerEvent('click', evtPx);
+
+ // test click out of last feature
+ // only 'clickout' callback should be called
+ lastFeature = newFeature;
+ newFeature = null;
+ callbacks['click'] = getCallback('click', newFeature);
+ callbacks['clickout'] = getCallback('clickout', lastFeature);
+ evtPx.type = "click";
+ map.events.triggerEvent('click', evtPx);
+
+ layer.getFeatureFromEvent = function(evt) { t.fail("mousemove called getFeatureFromEvent without any mousemove callbacks"); };
+ evtPx.type = "mousemove";
+ map.events.triggerEvent('mousemove', evtPx);
+ layer.getFeatureFromEvent = function(evt) { return newFeature; };
+
+ // test over a new feature
+ // only 'over' callback should be called
+ handler.feature = null;
+ lastFeature = null;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['over'] = getCallback('over', newFeature);
+ callbacks['out'] = getCallback('out', lastFeature);
+ evtPx.type = "mousemove";
+ map.events.triggerEvent('mousemove', evtPx);
+
+ // test over a new feature and out of last feature
+ // both 'over' and 'out' callbacks should be called
+ lastFeature = newFeature;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['over'] = getCallback('over', newFeature);
+ callbacks['out'] = getCallback('out', lastFeature);
+ evtPx.type = "mousemove";
+ map.events.triggerEvent('mousemove', evtPx);
+
+ // test out of last feature
+ // only 'out' callback should be called
+ lastFeature = newFeature;
+ newFeature = null;
+ callbacks['over'] = getCallback('over', newFeature);
+ callbacks['out'] = getCallback('out', lastFeature);
+ evtPx.type = "mousemove";
+ map.events.triggerEvent('mousemove', evtPx);
+
+ // test dblclick on a feature
+ // 'dblclick' callback should be called
+ handler.feature = null;
+ lastFeature = null;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['dblclick'] = getCallback('dblclick', newFeature);
+ evtPx.type = "dblclick";
+ map.events.triggerEvent('dblclick', evtPx);
+
+ // test touchstart on a feature
+ // 'click' callback should be called
+ handler.feature = null;
+ lastFeature = null;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['click'] = getCallback('click (touch)', newFeature);
+ callbacks['clickout'] = getCallback('clickout (touch)', lastFeature);
+ evtPx.type = "touchstart";
+ map.events.triggerEvent('touchstart', evtPx);
+
+ // test touchstart on the same feature
+ // 'click' callback should be called
+ callbacks['click'] = getCallback('click (touch)', newFeature);
+ evtPx.type = "touchstart";
+ map.events.triggerEvent('touchstart', evtPx);
+
+ // test touchstart in new feature and out of last feature
+ // both 'click' and 'clickout' callbacks should be called
+ lastFeature = newFeature;
+ newFeature = new OpenLayers.Feature.Vector();
+ newFeature.layer = layer;
+ callbacks['click'] = getCallback('click (touch)', newFeature);
+ callbacks['clickout'] = getCallback('clickout (touch)', lastFeature);
+ evtPx.type = "touchstart";
+ map.events.triggerEvent('touchstart', evtPx);
+
+ // test touchstart out of last feature
+ // only 'clickout' callback should be called
+ lastFeature = newFeature;
+ newFeature = null;
+ callbacks['click'] = getCallback('click (touch)', newFeature);
+ callbacks['clickout'] = getCallback('clickout (touch)', lastFeature);
+ evtPx.type = "touchstart";
+ map.events.triggerEvent('touchstart', evtPx);
+ }
+
+ function test_touchstart(t) {
+ // a test to verify that the touchstart function does
+ // unregister the mouse listeners when it's called the
+ // first time
+
+ t.plan(4);
+
+ // set up
+
+ var map = new OpenLayers.Map('map', {controls: []});
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+
+ var handler = new OpenLayers.Handler.Feature(control, layer, {});
+ handler.mousedown = function() {}; // mock mousedown
+ handler.activate();
+
+ var eventTypes = ['mousedown', 'mouseup', 'mousemove', 'click', 'dblclick'];
+
+ function allRegistered() {
+ var eventType,
+ listeners,
+ listener,
+ flag;
+ for(var i=0, ilen=eventTypes.length; i<ilen; i++) {
+ flag = false;
+ eventType = eventTypes[i];
+ listeners = map.events.listeners[eventType];
+ for(var j=0, jlen=listeners.length; j<jlen; j++) {
+ listener = listeners[j];
+ if(listener.func === handler[eventType] && listener.obj === handler) {
+ flag = true;
+ break;
+ }
+ }
+ if(!flag) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function noneRegistered() {
+ var eventType,
+ times,
+ flag = false;
+ for(var i=0, ilen=eventTypes.length; i<ilen; i++) {
+ eventType = eventTypes[i];
+ times = map.events.listeners[eventType].length;
+ if (times != 0) {
+ t.fail(eventType + " is registered " + times + " times");
+ flag = true;
+ }
+ }
+ return !flag;
+ }
+
+ // test
+
+ t.ok(allRegistered(), 'mouse listeners are registered');
+ handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(noneRegistered(), 'mouse listeners are unregistered');
+ t.ok(handler.touch, 'handler.touch is set');
+
+ handler.deactivate();
+ t.ok(!handler.touch, 'handler.touch is not set');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ function test_deactivate(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+ var layerIndex = parseInt(layer.div.style.zIndex);
+
+ var handler = new OpenLayers.Handler.Feature(control, layer);
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler was not already active");
+
+ handler.active = true;
+
+ deactivated = handler.deactivate();
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+ t.eq(parseInt(layer.div.style.zIndex),
+ layerIndex,
+ "deactivate sets the layer z-index back");
+ }
+
+ function test_stopHandled(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+ var handler = new OpenLayers.Handler.Feature(control, layer);
+ handler.activate();
+ handler.handle = function(evt) { return /* handled */ true; };
+ var evtPx = {xy: new OpenLayers.Pixel(Math.random(), Math.random())};
+ map.events.register("click", map, function(e) {
+ t.ok(!handler.stopClick, "clicks propagate with stopClick set to false" );
+ });
+ map.events.register("mousedown", map, function(e) {
+ t.ok(!handler.stopDown, "mousedown propagate with stopDown set to false" );
+ });
+ map.events.register("mouseup", map, function(e) {
+ t.ok(!handler.stopUp, "mouseup propagate with stopUp set to false" );
+ });
+
+ // 0 test
+ map.events.triggerEvent('click', evtPx);
+ // 0 test
+ map.events.triggerEvent('mousedown', evtPx);
+ // 0 test
+ map.events.triggerEvent('mousedown', evtPx);
+
+ // 1 test
+ handler.stopClick = false;
+ map.events.triggerEvent('click', evtPx);
+ // 1 test
+ handler.stopDown = false;
+ map.events.triggerEvent('mousedown', evtPx);
+ // 1 test
+ handler.stopUp = false;
+ map.events.triggerEvent('mouseup', evtPx);
+
+ // 3 tests total
+ }
+
+ function test_destroyed_feature(t) {
+ t.plan(18);
+
+ // setup
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var layer = new OpenLayers.Layer();
+ layer.removeFeatures = function() {};
+ map.addLayer(layer);
+ var handler = new OpenLayers.Handler.Feature(control, layer);
+ var feature, count, lastType, lastHandled;
+ handler.callback = function(type, features) {
+ ++count;
+ lastType = type;
+ lastHandled = features;
+ }
+
+ /**
+ * Test that a destroyed feature doesn't get sent to the "click" callback
+ */
+ count = 0;
+ handler.activate();
+ feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ feature.layer = layer;
+ // mock click on a feature
+ layer.getFeatureFromEvent = function(evt) {return feature};
+ handler.handle({type: "click"});
+ // confirm that feature was handled
+ t.eq(count, 1, "[click] callback called once");
+ t.eq(lastType, "click", "[click] correct callback type");
+ t.eq(lastHandled[0].id, feature.id, "[click] correct feature sent to callback");
+
+ // now destroy the feature and confirm that the callback is not called again
+ feature.destroy();
+ handler.handle({type: "click"});
+ if(count === 1) {
+ t.ok(true, "[click] callback not called after destroy");
+ } else {
+ t.fail("[click] callback called after destroy: " + lastType);
+ }
+
+ /**
+ * Test that a destroyed feature doesn't get sent to the "clickout" callback
+ */
+ count = 0;
+ handler.deactivate();
+ handler.activate();
+ feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ feature.layer = layer;
+
+ // mock a click on a feature
+ layer.getFeatureFromEvent = function(evt) {return feature};
+ handler.handle({type: "click"});
+ // confirm that callback got feature
+ t.eq(count, 1, "[clickout] callback called once on in");
+ t.eq(lastType, "click", "[clickout] click callback called on in");
+ t.eq(lastHandled.length, 1, "[clickout] callback called with one feature");
+ t.eq(lastHandled[0].id, feature.id, "[clickout] callback called with correct feature");
+
+ // now mock a click off a destroyed feature
+ feature.destroy();
+ layer.getFeatureFromEvent = function(evt) {return null};
+ handler.handle({type: "click"});
+ // confirm that callback does not get called
+ if(count === 1) {
+ t.ok(true, "[clickout] callback not called when clicking out of a destroyed feature");
+ } else {
+ t.fail("[clickout] callback called when clicking out of a destroyed feature: " + lastType);
+ }
+
+ /**
+ * Test that a destroyed feature doesn't get sent to the "over" callback
+ */
+ count = 0;
+ handler.deactivate();
+ handler.activate();
+ feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ feature.layer = layer;
+ // mock mousemove over a feature
+ layer.getFeatureFromEvent = function(evt) {return feature};
+ handler.handle({type: "mousemove"});
+ // confirm that feature was handled
+ t.eq(count, 1, "[over] callback called once");
+ t.eq(lastType, "over", "[over] correct callback type");
+ t.eq(lastHandled[0].id, feature.id, "[over] correct feature sent to callback");
+
+ // now destroy the feature and confirm that the callback is not called again
+ feature.destroy();
+ handler.handle({type: "mousemove"});
+ if(count === 1) {
+ t.ok(true, "[over] callback not called after destroy");
+ } else {
+ t.fail("[over] callback called after destroy: " + lastType);
+ }
+
+ /**
+ * Test that a destroyed feature doesn't get sent to the "out" callback
+ */
+ count = 0;
+ handler.deactivate();
+ handler.activate();
+ feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,0));
+ feature.layer = layer;
+
+ // mock a mousemove over a feature
+ layer.getFeatureFromEvent = function(evt) {return feature};
+ handler.handle({type: "mousemove"});
+ // confirm that callback got feature
+ t.eq(count, 1, "[out] callback called once on over");
+ t.eq(lastType, "over", "[out] click callback called on over");
+ t.eq(lastHandled.length, 1, "[out] callback called with one feature");
+ t.eq(lastHandled[0].id, feature.id, "[out] callback called with correct feature");
+
+ // now mock a click off a destroyed feature
+ feature.destroy();
+ layer.getFeatureFromEvent = function(evt) {return null};
+ handler.handle({type: "mousemove"});
+ // confirm that callback does not get called
+ if(count === 1) {
+ t.ok(true, "[out] callback not called when moving out of a destroyed feature");
+ } else {
+ t.fail("[out] callback called when moving out of a destroyed feature: " + lastType);
+ }
+
+ handler.destroy();
+ }
+
+ function test_click_tolerance(t) {
+ t.plan(3);
+
+ var map, control, layer, feature, evtPx;
+ var clicks, callbacks, handler;
+
+ map = new OpenLayers.Map('map', {controls: []});
+ control = new OpenLayers.Control();
+ map.addControl(control);
+ layer = new OpenLayers.Layer();
+ map.addLayer(layer);
+
+ feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+
+ evtPx = {
+ xy: new OpenLayers.Pixel(Math.random(), Math.random()),
+ type: "click"
+ };
+
+ // override the layer's getFeatureFromEvent func so that it always
+ // returns newFeature
+ layer.getFeatureFromEvent = function(evt) { return feature; };
+
+ callbacks = {
+ click: function() {
+ clicks++;
+ }
+ };
+
+ handler = new OpenLayers.Handler.Feature(
+ control, layer, callbacks, {clickTolerance: 4});
+ handler.activate();
+
+ // distance between down and up is 1, which is
+ // lower than clickTolerance so "click" should trigger
+ handler.down = {x: 0, y: 0};
+ handler.up = {x: 1, y: 0};
+ clicks = 0;
+ map.events.triggerEvent("click", evtPx);
+ t.eq(clicks, 1, "click callback triggers when tolerance is not reached (lower than)");
+
+ // distance between down and up is 4, which is
+ // equal to clickTolerance so "click" should trigger
+ handler.down = {x: 0, y: 0}; // cached handler.down cleared (#857)
+ handler.up = {x: 0, y: 4};
+ clicks = 0;
+ map.events.triggerEvent("click", evtPx);
+ t.eq(clicks, 1, "click callback triggers when tolerance is not reached (equal to)");
+
+ // distance between down and up is 5, which is
+ // greater than clickTolerance so "click" should not trigger
+ handler.down = {x: 0, y: 0}; // cached handler.down cleared (#857)
+ handler.up = {x: 5, y: 0};
+ clicks = 0;
+ map.events.triggerEvent("click", evtPx);
+ t.eq(clicks, 0, "click callback does not trigger when tolerance is reached");
+ }
+
+ function test_multitouch_canvas(t) {
+ var supported = OpenLayers.Renderer.Canvas.prototype.supported();
+ if (!supported) { t.plan(0); return; }
+
+ t.plan(1);
+
+ // set up
+
+ var log;
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Vector('vectors', {
+ renderers: ['Canvas'],
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Feature(control, layer,
+ {click: function() { log++; }});
+ control.handler = handler;
+ map.addControl(control);
+ control.activate();
+
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0));
+ layer.addFeatures(feature);
+
+ map.zoomToMaxExtent();
+
+ // test
+
+ // mock getMousePosition on the events object to make
+ // sure scrolls, offsets and leftop do not interfere
+ map.events.getMousePosition = function(evt) {
+ return new OpenLayers.Pixel(evt.clientX,
+ evt.clientY);
+ };
+
+ log = 0;
+ var evt = {
+ type: 'touchstart',
+ touches: [{
+ clientX: 100,
+ clientY: 75
+ }, {
+ clientX: 200,
+ clientY: 75
+ }]
+ };
+ map.events.handleBrowserEvent(evt);
+ t.eq(log, 0, "no feature selection when multi-touching");
+
+ // tear down
+
+ map.destroy();
+ }
+
+ function test_layerorder(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var base = new OpenLayers.Layer(null, {isBaseLayer: true});
+ var vector = new OpenLayers.Layer.Vector();
+ map.addLayers([base, vector]);
+ map.addControl(new OpenLayers.Control.SelectFeature(vector, {autoActivate: true}));
+ map.zoomToMaxExtent();
+ t.eq(parseInt(vector.getZIndex(), 10), 725, "vector layer's zIndex correct");
+ map.events.triggerEvent("changelayer");
+ t.eq(parseInt(vector.getZIndex(), 10), 725, "vector layer's zIndex still correct after changelayer event");
+
+ }
+
+ function test_clear_event_position_cache(t) {
+ t.plan(2);
+
+ var map, control, layer, feature, evtPx;
+
+ map = new OpenLayers.Map('map', {controls: []});
+ control = new OpenLayers.Control();
+ map.addControl(control);
+ layer = new OpenLayers.Layer();
+ layer.getFeatureFromEvent = function(evt) { return feature; };
+ map.addLayer(layer);
+ feature = new OpenLayers.Feature.Vector();
+ feature.layer = layer;
+
+ evtPx = {
+ xy: new OpenLayers.Pixel(Math.random(), Math.random()),
+ type: "click"
+ };
+
+ handler = new OpenLayers.Handler.Feature(
+ control, layer, {}, {});
+ handler.activate();
+
+ handler.down = {x: 0, y: 0};
+ handler.up = {x: 1, y: 0};
+ map.events.triggerEvent("click", evtPx);
+ t.eq(handler.down, null, "cached mousedown position is cleared after handling click");
+ t.eq(handler.up, null, "cached mouseup position is cleared after handling click")
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Hover.html b/misc/openlayers/tests/Handler/Hover.html
new file mode 100644
index 0000000..150218a
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Hover.html
@@ -0,0 +1,136 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Hover_events(t) {
+ t.plan(10);
+
+ var map = new OpenLayers.Map('map');
+ var control = {
+ map: map
+ };
+ map.events.registerPriority = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ // this is one of the mock handler methods
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled");
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(func(), type,
+ "activate calls registerPriority with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.Hover",
+ "activate calls registerPriority with the handler");
+ }
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["mousemove", "mouseout"];
+ var nonevents = ["mousedown", "mouseup", "click", "dblclick", "resize", "focus", "blur"];
+ var handler = new OpenLayers.Handler.Hover(control);
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ handler.activate();
+ }
+
+ function test_Handler_Hover_callbacks(t) {
+ t.plan(8);
+
+ var map = new OpenLayers.Map('map', {controls: []});
+
+ var control = {
+ map: map
+ };
+
+ var timers = {};
+ var sto = window.setTimeout;
+ window.setTimeout = function(func, delay) {
+ var key = Math.random();
+ timers[key] = true;
+ t.ok(typeof func == "function",
+ "setTimeout called with a function");
+ t.eq(delay, handler.delay,
+ "setTimeout called with proper delay");
+ // execute function that is supposed to be delayed
+ func();
+ return key;
+ }
+ var cto = window.clearTimeout;
+ window.clearTimeout = function(key) {
+ if(timers[key] === true) {
+ delete timers[key];
+ } else {
+ t.fail("clearTimeout called with non-existent timerId");
+ }
+ }
+
+ var handler = new OpenLayers.Handler.Hover(control, {});
+ handler.activate();
+ var testEvt;
+
+ // test pause and move callbacks - four tests here (2 from setTimeout above)
+ testEvt = {id: Math.random()};
+ handler.callbacks = {
+ "pause": function(evt) {
+ t.eq(evt.id, testEvt.id,
+ "pause callback called with correct evt");
+ },
+ "move": function(evt) {
+ t.eq(evt.id, testEvt.id,
+ "move callback called with correct evt");
+ }
+ };
+ map.events.triggerEvent("mousemove", testEvt);
+ handler.clearTimer();
+
+ // test pixelTolerance - four tests here (2 from setTimeout above)
+ handler.pixelTolerance = 2;
+ handler.px = new OpenLayers.Pixel(0, 0);
+ testEvt = {
+ xy: new OpenLayers.Pixel(0, 1)
+ };
+ // mouse moves one pixel, callbacks shouldn't be called
+ handler.callbacks = {
+ "pause": function(evt) {
+ t.fail("(pixelTolerance met) pause callback shouldn't be called");
+ },
+ "move": function(evt) {
+ t.fail("(pixelTolerance met) move callback shoudln't be called");
+ }
+ };
+ map.events.triggerEvent("mousemove", testEvt);
+ handler.clearTimer();
+ handler.px = new OpenLayers.Pixel(0, 0);
+ testEvt = {
+ xy: new OpenLayers.Pixel(3, 3)
+ };
+ // mouse moves 3x3 pixels, callbacks should be called
+ handler.callbacks = {
+ "pause": function(evt) {
+ t.ok(evt.xy == testEvt.xy, "(pixelTolerance unmet) pause callback called");
+ },
+ "move": function(evt) {
+ t.ok(evt == testEvt, "(pixelTolerance unmet) move callback called");
+ }
+ };
+ map.events.triggerEvent("mousemove", testEvt);
+ handler.clearTimer();
+
+ window.setTimeout = sto;
+ window.clearTimeout = cto;
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Keyboard.html b/misc/openlayers/tests/Handler/Keyboard.html
new file mode 100644
index 0000000..4a72c92
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Keyboard.html
@@ -0,0 +1,150 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Keyboard_initialize(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Keyboard(control, callbacks,
+ options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_Keyboard_destroy(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Keyboard(control);
+ var old = OpenLayers.Handler.prototype.destroy;
+ t.ok(handler.eventListener != null,
+ "eventListener is not null before destroy");
+ OpenLayers.Handler.prototype.destroy = function() {
+ t.ok(true, "destroy calls destroy on correct parent");
+ };
+ handler.destroy();
+ t.ok(handler.eventListener == null,
+ "eventListeners is null after destroy");
+ OpenLayers.Handler.prototype.destroy = old;
+ }
+
+ function test_Handler_Keyboard_activate(t) {
+ t.plan(15);
+
+ var log;
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Keyboard(control);
+
+ // mock OpenLayers.Event.observe
+ var old = OpenLayers.Event.stopObserving;
+ OpenLayers.Event.observe = function(obj, type, method) {
+ log[type] = obj;
+ var validType = OpenLayers.Util.indexOf(["keydown", "keyup"], type) != -1;
+ t.ok(validType, "activate calls observe for " + type);
+ t.ok(method == handler.eventListener,
+ "activate calls observing with correct method");
+ };
+
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+
+ log = {};
+ handler.active = false;
+ handler.observeElement = map.div;
+ activated = handler.activate();
+ t.ok(log['keydown'] == map.div,
+ "activate calls observing for keydown with correct object");
+ t.ok(log['keyup'] == map.div,
+ "activate calls observing for keyup with correct object");
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+
+ log = {};
+ handler.active = false;
+ handler.observeElement = null;
+ activated = handler.activate();
+ t.ok(log['keydown'] == document,
+ "activate calls observing for keydown with correct object");
+ t.ok(log['keyup'] == document,
+ "activate calls observing for keyup with correct object");
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+
+ OpenLayers.Event.observe = old;
+ map.destroy();
+ }
+
+ function test_Handler_Keyboard_deactivate(t) {
+ t.plan(15);
+
+ var log;
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Keyboard(control);
+
+ // mock OpenLayers.Event.stopObserving
+ var old = OpenLayers.Event.stopObserving;
+ OpenLayers.Event.stopObserving = function(obj, type, method) {
+ log[type] = obj;
+ var validType = OpenLayers.Util.indexOf(["keydown", "keyup"], type) != -1;
+ t.ok(validType, "deactivate calls stopObserving for " + type);
+ t.ok(method == handler.eventListener,
+ "deactivate calls stopObserving with correct method");
+ };
+
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler was not already active");
+
+ log = {};
+ handler.active = true;
+ handler.observeElement = map.div;
+ deactivated = handler.deactivate();
+ t.ok(log['keydown'] == map.div,
+ "deactivate calls stopObserving for keydown with correct object");
+ t.ok(log['keyup'] == map.div,
+ "deactivate calls stopObserving for keyup with correct object");
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+
+ log = {};
+ handler.active = true;
+ handler.observeElement = document;
+ deactivated = handler.deactivate();
+ t.ok(log['keydown'] == document,
+ "deactivate calls stopObserving for keydown with correct object");
+ t.ok(log['keyup'] == document,
+ "deactivate calls stopObserving for keyup with correct object");
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+
+ OpenLayers.Event.stopObserving = old;
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/MouseWheel.html b/misc/openlayers/tests/Handler/MouseWheel.html
new file mode 100644
index 0000000..687a31d
--- /dev/null
+++ b/misc/openlayers/tests/Handler/MouseWheel.html
@@ -0,0 +1,182 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_MouseWheel_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.MouseWheel(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_MouseWheel_activate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.MouseWheel(control);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ }
+
+ function test_Handler_MouseWheel_mousePosition(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("","",{}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var pass = false;
+ var handler = new OpenLayers.Handler.MouseWheel(control, {'up':
+ function (evt) {
+ if (evt.xy) { pass = true; }
+ }
+ });
+ handler.setMap(map);
+ handler.activate();
+ var delta = 120;
+ handler.onWheelEvent({'target':map.layers[0].div, wheelDelta: delta});
+ t.ok(pass, "evt.xy was set even without a mouse move");
+ }
+
+ function test_Handler_MouseWheel_events(t) {
+ t.plan(6);
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("","",{}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var deltaZ;
+ var handler = new OpenLayers.Handler.MouseWheel(control, {
+ 'up': function(evt, delta){
+ deltaZ = delta;
+ }
+ }, {interval: 200});
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["mousemove"];
+ var nonevents = ["dblclick", "resize", "focus", "blur"];
+ map.events.registerPriority = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled");
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(func(), type,
+ "activate calls registerPriority with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.MouseWheel",
+ "activate calls registerPriority with the handler");
+ }
+ }
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+
+ var activated = handler.activate();
+
+ var delta = 120;
+ handler.onWheelEvent({'target':map.layers[0].div, wheelDelta: delta});
+ handler.onWheelEvent({'target':map.layers[0].div, wheelDelta: delta});
+ t.delay_call(1, function() {
+ t.eq(deltaZ, 2, "Multiple scroll actions triggered one event when interval is set");
+ });
+ }
+
+ function test_Handler_MouseWheel_cumulative(t) {
+ t.plan(2);
+
+ var deltaUp = 0, ticks = 0;
+ var callbacks = {
+ up: function(evt, delta) {
+ deltaUp += delta;
+ ticks++;
+ }
+ };
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("","",{}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control({});
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.MouseWheel(control, callbacks, {
+ interval: 150,
+ cumulative: false,
+ maxDelta: 6
+ });
+
+ var delta = 120;
+ // generate 20 scroll up in non cumulative mode
+ for (var i=0; i < 20; i++) {
+ handler.onWheelEvent({'target':map.layers[0].div, wheelDelta: delta});
+ }
+
+ t.delay_call(2, function() {
+ t.eq(deltaUp / ticks, 1, "Cumulative mode works");
+ t.eq(ticks, 4, "up called 4x with maxDelta of 6");
+ });
+ }
+
+ function test_Handler_MouseWheel_deactivate(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.MouseWheel(control);
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler was not already active");
+ handler.active = true;
+ deactivated = handler.deactivate();
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+ }
+ function test_handler_MouseWheel_destroy(t) {
+ t.plan(1);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.MouseWheel(control);
+ handler.deactivate = function() {
+ t.ok(true, "Deactivate called one time.");
+ }
+ handler.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Path.html b/misc/openlayers/tests/Handler/Path.html
new file mode 100644
index 0000000..8351eea
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Path.html
@@ -0,0 +1,1464 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Path_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Path(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_Path_activation(t) {
+ t.plan(5);
+ var log = [];
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Path(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.active = true;
+
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ t.ok(handler.layer instanceof OpenLayers.Layer.Vector,
+ "activate creates a vector layer");
+ t.ok(handler.layer.map == map,
+ "activate adds the vector layer to the map");
+ activated = handler.deactivate();
+ t.ok(activated,
+ "deactivate returns true if the handler was active already");
+
+ map.destroy();
+ }
+
+ // See: http://trac.osgeo.org/openlayers/ticket/3179
+ function test_activate_before_map_is_centered(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map', {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Path(control, {});
+ control.handler = handler;
+ map.addControl(control);
+
+ var error;
+ try {
+ handler.activate();
+ error = false;
+ } catch(err) {
+ error = true;
+ }
+ t.ok(!error, "no error on activate");
+ }
+
+ function test_bounds(t) {
+ t.plan(4);
+ var geometry;
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Path(control, {},
+ {stopDown: true, stopUp: true});
+ var activated = handler.activate();
+ // click on (150, 75)
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ t.eq(handler.layer.features.length, 2,
+ "There are two features in the layer after first click.");
+ // click on (175, 100)
+ evt = {xy: new OpenLayers.Pixel(175, 100), which: 1};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ t.eq(handler.layer.features.length, 2,
+ "There are two features in the layer after second click.");
+ t.ok(handler.line.geometry.getBounds().equals(
+ new OpenLayers.Bounds(0,-35.15625,35.15625,0)),
+ "Correct bounds");
+ // mousedown on (175, 100)
+ evt = {xy: new OpenLayers.Pixel(175, 100), which: 1};
+ handler.mousedown(evt);
+ // mousemove to (125, 100)
+ evt = {xy: new OpenLayers.Pixel(125, 100), which: 1};
+ handler.mousemove(evt);
+ // test that the bounds have changed
+ t.ok(!handler.line.geometry.getBounds().equals(
+ new OpenLayers.Bounds(0,-35.15625,35.15625,0)),
+ "Correct bounds after dragging without letting go. " +
+ "(Came out as " + handler.line.geometry.getBounds().toBBOX() +
+ ".)");
+ map.destroy();
+ }
+
+ function test_callbacks(t) {
+ t.plan(39);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var logs = [], log;
+ var handler = new OpenLayers.Handler.Path(control, {
+ create: function() {
+ logs.push({type: "create", args: arguments});
+ },
+ point: function() {
+ logs.push({type: "point", args: arguments});
+ },
+ modify: function() {
+ logs.push({type: "modify", args: arguments});
+ },
+ done: function() {
+ logs.push({type: "done", args: arguments});
+ },
+ cancel: function() {
+ logs.push({type: "cancel", args: arguments});
+ }
+ },
+ {
+ pixelTolerance: 0
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ // mouse move
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 2, "[mousemove] called back twice");
+ log = logs.shift();
+ t.eq(log.type, "create", "[mousemove] create called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.line,
+ "[mousemove] correct feature");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.line,
+ "[mousemove] correct feature");
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 1, "[mousedown] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousedown] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousedown] correct point");
+ t.ok(log.args[1] === handler.line,
+ "[mousedown] correct feature");
+ // mouse up
+ handler.mouseup({type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 2, "[mouseup] called back twice");
+ log = logs.shift();
+ t.eq(log.type, "point", "[mouseup] point called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mouseup] correct point");
+ t.geom_eq(log.args[1],
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-150, 75),
+ new OpenLayers.Geometry.Point(-150, 75)
+ ]), "[mouseup] correct line");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mouseup] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mouseup] correct point");
+ t.ok(log.args[1] == handler.line,
+ "[mouseup] correct feature");
+ // mouse move
+ handler.mousemove({type: "mousemove",
+ xy: new OpenLayers.Pixel(1, 1)});
+ t.eq(logs.length, 1, "[mousemove] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-149, 74),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.line,
+ "[mousemove] correct feature");
+ // mouse move
+ handler.mousemove({type: "mousemove",
+ xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 1, "[mousemove] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-140, 65),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.line,
+ "[mousemove] correct feature");
+ // mouse down
+ handler.mousedown({type: "mousedown",
+ xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 1, "[mousedown] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousedown] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-140, 65),
+ "[mousedown] correct point");
+ t.ok(log.args[1] === handler.line,
+ "[mousedown] correct feature");
+ // mouse up ("point", "modify")
+ handler.mouseup({type: "mouseup",
+ xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 2, "[mouseup] called back twice");
+ log = logs.shift();
+ log = logs.shift();
+ // mouse down
+ handler.mousedown({type: "mousedown",
+ xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 0, "[mousedown] called back");
+ // mouse up
+ handler.mouseup({type: "mouseup",
+ xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 0, "[mouseup] not called back");
+ // double click
+ handler.dblclick({type: "dblclick",
+ xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 1, "[dblclick] called back");
+ log = logs.shift();
+ t.eq(log.type, "done", "[dblclick] done called");
+ t.geom_eq(log.args[0],
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-150, 75),
+ new OpenLayers.Geometry.Point(-140, 65)
+ ]),
+ "[dblclick] correct linestring"
+ );
+ // cancel
+ handler.cancel();
+ t.eq(logs.length, 1, "[cancel] called back");
+ log = logs.shift();
+ t.eq(log.type, "cancel", "[cancel] canced called");
+ t.eq(log.args[0], null, "[cancel] got null"
+ );
+
+ map.destroy();
+ }
+
+ function test_toggle_freehand(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {
+ done: function(g) {
+ log++;
+ }
+ }, {persist: true});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ log = 0;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.eq(log, 1, "feature drawn when shift pressed on mousedown");
+
+ log = 0;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: false});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.eq(log, 0, "feature not drawn when shift not pressed on mousedown");
+ }
+
+ function test_persist(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ handler.persist = false;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature1 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(1, 1)});
+ t.ok(feature1.layer == null, "a) feature1 destroyed");
+
+ handler.persist = true;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature2 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(1, 1)});
+ t.ok(feature2.layer != null, "b) feature2 not destroyed");
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature3 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(1, 1)});
+ t.ok(feature3.layer != null, "c) feature3 not destroyed");
+ t.ok(feature2.layer == null, "c) feature2 destroyed");
+
+ map.destroy();
+ }
+
+ function test_persist_freehand(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ handler.persist = false;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature1 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.ok(feature1.layer == null, "a) feature1 destroyed");
+
+ handler.persist = true;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ feature2 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.ok(feature2.layer != null, "b) feature2 not destroyed");
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ feature3 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.ok(feature3.layer != null, "c) feature3 not destroyed");
+ t.ok(feature2.layer == null, "c) feature2 destroyed");
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ feature4 = handler.line;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: false});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.ok(feature4.layer != null, "d) feature4 not destroyed");
+ t.ok(feature3.layer == null, "c) feature3 destroyed");
+
+ map.destroy();
+ }
+
+ function test_Handler_Path_destroy(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Path(control, {foo: 'bar'});
+
+ handler.activate();
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.mousedown(evt);
+
+ t.ok(handler.layer,
+ "handler has a layer prior to destroy");
+ t.ok(handler.point,
+ "handler has a point prior to destroy");
+ t.ok(handler.line,
+ "handler has a line prior to destroy");
+ handler.destroy();
+ t.eq(handler.layer, null,
+ "handler.layer is null after destroy");
+ t.eq(handler.point, null,
+ "handler.point is null after destroy");
+ t.eq(handler.line, null,
+ "handler.line is null after destroy");
+ map.destroy();
+ }
+
+ function test_maxVertices(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var log = {};
+ var doneCallback = function(evt) {
+ t.ok(evt, 'When maxVertices is reached, the geometry is finalized automatically');
+ };
+ var handler = new OpenLayers.Handler.Path(control, {'done': doneCallback}, {maxVertices: 2});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // mock up feature drawing
+ handler.activate();
+ var evt = {xy: new OpenLayers.Pixel(0, 0)};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ evt = {xy: new OpenLayers.Pixel(20, 20)};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ evt = {xy: new OpenLayers.Pixel(40, 40)};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ map.destroy();
+ }
+
+ function test_freehand_maxVertices(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var log = {};
+ var MAX_VERTICES = 2;
+ var doneCallback = function(geo) {
+ t.eq(geo.components.length, MAX_VERTICES,
+ 'When maxVertices is reached, the geometry is finalized automatically');
+ };
+ var handler = new OpenLayers.Handler.Path(control,
+ {'done': doneCallback},
+ {freehand: true,
+ maxVertices: MAX_VERTICES});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // mock up feature freehand drawing
+ handler.activate();
+ var evt = {xy: new OpenLayers.Pixel(0, 0)};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ evt = {xy: new OpenLayers.Pixel(20, 20)};
+ handler.mousemove(evt);
+ evt = {xy: new OpenLayers.Pixel(40, 40)};
+ handler.mousemove(evt);
+ map.destroy();
+ }
+
+ /**
+ * Helper functions for editing method tests
+ */
+ function editingMethodsSetup() {
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Path
+ );
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ control.activate();
+ return {
+ handler: control.handler,
+ map: map
+ }
+ }
+ function userClick(handler, x, y) {
+ var px = new OpenLayers.Pixel(x, y);
+ handler.mousemove({type: "mousemove", xy: px});
+ handler.mousedown({type: "mousedown", xy: px});
+ handler.mouseup({type: "mouseup", xy: px});
+ }
+ function userTap(handler, x, y) {
+ var px = new OpenLayers.Pixel(x, y);
+ handler.touchstart({xy: px});
+ handler.touchmove({xy: px});
+ handler.touchend({});
+ }
+
+ /**
+ * Editing method tests: insertXY, insertDeltaXY, insertDirectionXY,
+ * insertDeflectionXY, undo, and redo
+ */
+ function test_insertXY(t) {
+ t.plan(3);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points at px(0, 0) and px(10, 10)
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+
+ t.eq(handler.line.geometry.components.length, 3, "line has three points after two clicks");
+
+ // programmatically add a point
+ handler.insertXY(5, 6);
+ t.eq(handler.line.geometry.components.length, 4, "line has four points after insertXY");
+ t.geom_eq(
+ handler.line.geometry.components[2],
+ new OpenLayers.Geometry.Point(5, 6),
+ "third point comes from insertXY"
+ );
+
+ map.destroy();
+
+ }
+
+ function test_insertDeltaXY(t) {
+ t.plan(3);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points at px(0, 0) and px(10, 10)
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+
+ t.eq(handler.line.geometry.components.length, 3, "line has three points after two clicks");
+
+ // programmatically add a point
+ handler.insertDeltaXY(1, 2);
+ t.eq(handler.line.geometry.components.length, 4, "line has four points after insert");
+ // expect a point that is offset from previous point
+ var exp = handler.line.geometry.components[1].clone();
+ exp.move(1, 2);
+ t.geom_eq(
+ handler.line.geometry.components[2], exp,
+ "third point is offset by dx,dy from second point"
+ );
+
+ map.destroy();
+ }
+
+ function test_insertDirectionLength(t) {
+ t.plan(4);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points at px(0, 0) and px(10, 10)
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+
+ t.eq(handler.line.geometry.components.length, 3, "line has three points after two clicks");
+
+ // programmatically add a point
+ handler.insertDirectionLength(45, 2);
+ t.eq(handler.line.geometry.components.length, 4, "line has four points after insert");
+ var p1 = handler.line.geometry.components[1];
+ var p2 = handler.line.geometry.components[2];
+
+ var direction = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
+ t.eq(direction.toFixed(4), (45).toFixed(4), "inserted point offset with correct direction");
+ var length = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ t.eq(length.toFixed(4), (2).toFixed(4), "inserted point offset with correct length");
+
+ map.destroy();
+ }
+
+ function test_insertDeflectionLength(t) {
+ t.plan(4);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points at px(0, 0) and px(10, 10)
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+
+ t.eq(handler.line.geometry.components.length, 3, "line has three points after two clicks");
+ var p0 = handler.line.geometry.components[0];
+ var p1 = handler.line.geometry.components[1];
+ // angle of first segment
+ var dir0 = Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI;
+
+ // programmatically add a point
+ handler.insertDeflectionLength(-30, 5);
+ t.eq(handler.line.geometry.components.length, 4, "line has four points after insert");
+ var p2 = handler.line.geometry.components[2];
+ // angle of second segment
+ var dir1 = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
+
+ var deflection = dir1 - dir0;
+ t.eq(deflection.toFixed(4), (-30).toFixed(4), "inserted point offset with correct deflection");
+
+ var length = Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+ t.eq(length.toFixed(4), (5).toFixed(4), "inserted point offset with correct length");
+
+ map.destroy();
+ }
+
+ function test_undoredo1(t) {
+ t.plan(5);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points and move mouse
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ userClick(handler, 50, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+ var original = handler.line.geometry.clone();
+ var len = original.components.length;
+ t.eq(len, 4, "original has four points after three clicks");
+
+ // one undo
+ handler.undo();
+ var currentLen = handler.line.geometry.components.length;
+ t.eq(currentLen, len-1, "one point removed on undo");
+ t.geom_eq(
+ handler.line.geometry.components[currentLen-1],
+ original.components[len-1],
+ "current point (mouse position) remains the same after undo"
+ );
+ // one redo
+ handler.redo();
+ t.geom_eq(original, handler.line.geometry, "one redo undoes one undo");
+
+ // add point via touch
+ userTap(handler, 10, 50);
+ handler.undo();
+ currentLen = handler.line.geometry.components.length;
+ t.geom_eq(
+ handler.line.geometry.components[currentLen-1],
+ handler.line.geometry.components[currentLen-2],
+ "current point (mouse position) is set to the last digitized " +
+ "point after undo on touch devices"
+ );
+
+ // cleanup
+ map.destroy();
+ }
+
+ function test_undoredo2(t) {
+ t.plan(8);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points and move mouse
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ userClick(handler, 50, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+ var original = handler.line.geometry.clone();
+ var len = original.components.length;
+ t.eq(len, 4, "original has four points after three clicks");
+
+ // two undos
+ handler.undo();
+ handler.undo();
+ var currentLen = handler.line.geometry.components.length;
+ t.eq(currentLen, len-2, "two points removed on two undos");
+ t.geom_eq(
+ handler.line.geometry.components[currentLen-1],
+ original.components[len-1],
+ "current point (mouse position) remains the same after two undos"
+ );
+ // first redo
+ handler.redo();
+ currentLen = handler.line.geometry.components.length;
+ t.eq(currentLen, len-1, "point added in first redo");
+ t.geom_eq(
+ handler.line.geometry.components[currentLen-2],
+ original.components[len-3],
+ "correct point restored in first redo"
+ );
+
+ // second redo
+ handler.redo();
+ currentLen = handler.line.geometry.components.length;
+ t.eq(currentLen, len, "point added in second redo");
+ t.geom_eq(
+ handler.line.geometry.components[currentLen-2],
+ original.components[len-2],
+ "correct point restored in second redo"
+ );
+ t.geom_eq(handler.line.geometry, original, "correct geometry");
+
+ // cleanup
+ map.destroy();
+ }
+
+ function test_undoredo3(t) {
+ t.plan(3);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points and move mouse
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ userClick(handler, 50, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+ var original = handler.line.geometry.clone();
+ var len = original.components.length;
+ t.eq(len, 4, "original has four points after three clicks");
+
+ // gratuitous redos
+ var trouble = false;
+ try {
+ handler.undo();
+ handler.undo();
+ handler.redo();
+ handler.redo();
+ handler.redo();
+ handler.redo();
+ handler.redo();
+ } catch (err) {
+ trouble = true;
+ }
+ t.ok(!trouble, "extra redos cause no ill effects");
+ t.geom_eq(handler.line.geometry, original, "correct geometry");
+
+ // cleanup
+ map.destroy();
+ }
+
+ function test_undoredo4(t) {
+ t.plan(3);
+ var obj = editingMethodsSetup();
+ var map = obj.map;
+ var handler = obj.handler;
+
+ // add points and move mouse
+ userClick(handler, 0, 0);
+ userClick(handler, 10, 10);
+ userClick(handler, 50, 10);
+ handler.mousemove({type: "mousemove", xy: new OpenLayers.Pixel(50, 50)});
+ var original = handler.line.geometry.clone();
+ var len = original.components.length;
+ t.eq(len, 4, "original has four points after three clicks");
+
+ // gratuitous undos
+ var trouble = false;
+ try {
+ handler.undo();
+ handler.undo();
+ handler.undo();
+ handler.undo();
+ handler.undo();
+ handler.undo();
+ handler.undo();
+ } catch (err) {
+ trouble = true;
+ }
+ t.ok(!trouble, "extra undos cause no ill effects");
+ t.eq(handler.line.geometry.components.length, 2, "still left with two points after many undos")
+
+ // cleanup
+ map.destroy();
+ }
+
+ //
+ // Sequence tests
+ //
+ // Sequence tests basically involve executing a sequence of events
+ // and testing the resulting geometry.
+ //
+ // Below are tests for various drawing sequences. Tests can be
+ // added here each a non-working sequence is found.
+ //
+
+ // stopDown:true, stopUp:true, pixelTolerance:1
+ // a) click on (0, 0)
+ // b) mousedown on (1, 1)
+ // c) mouseup on (2, 2)
+ // d) dblclick on (10, 10)
+ function test_sequence1(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control,
+ {done: function(g) { log.geometry = g; }},
+ {stopDown: true, stopUp: true, pixelTolerance: 1}
+ );
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+ log = {};
+
+ // a) click on (0, 0)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ // b) mousedown on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ // c) mouseup on (2, 2)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2)});
+ // d) dblclick on (10, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(10, 10)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(10, 10)});
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ]), "geometry is correct");
+ }
+
+ // stopDown:false, stopUp:false, pixelTolerance:1
+ // a) click on (0, 0)
+ // b) mousedown on (1, 1)
+ // c) mouseup on (2, 2)
+ // d) dblclick on (10, 10)
+ function test_sequence2(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control,
+ {done: function(g) { log.geometry = g; }},
+ {stopDown: false, stopUp: false, pixelTolerance: 1}
+ );
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+ log = {};
+
+ // a) click on (0, 0)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ // b) mousedown on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ // c) mouseup on (2, 2)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2)});
+ // d) dblclick on (10, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(10, 10)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(10, 10)});
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ]), "geometry is correct");
+ }
+
+ // a) click
+ // b) dblclick
+ // c) mousedown holding shift key
+ // d) mousemove holding shift key
+ function test_sequence3(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {},
+ {
+ pixelTolerance: 0
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ // a) click on (0, 0)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ // b) click on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ // c) click on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ // d) mousemove to (10, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10), shiftKey: true});
+ t.geom_eq(handler.line.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-149, 74), // (1, 1)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ]), "geometry is correct after mousemove");
+ }
+
+ // a) click
+ // b) dblclick
+ // c) mousedown holding shift key
+ // d) mousemove holding shift key
+ function test_sequence4(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control,
+ {done: function(g) { log.geometry = g; }},
+ {stopDown: false, stopUp: false}
+ );
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+ log = {};
+
+ // a) click on (0, 0)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ // b) dblclick on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(1, 1)});
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-149, 74) // (1, 1)
+ ]), "geometry is correct after dblclick");
+ // c) mousedown holding shift key on (1, 1)
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ // d) mousemove holding shift key to (10, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10), shiftKey: true});
+ t.geom_eq(handler.line.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-149, 74), // (1, 1)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ]), "geometry is correct after mousemove");
+ }
+
+
+ // a) tap
+ // c) doubletap
+ function test_touch_sequence1(t) {
+ t.plan(19);
+
+ // set up
+
+ var log;
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {
+ done: function(g, f) {
+ log = {type: 'done', geometry: g, feature: f};
+ },
+ modify: function(g, f) {
+ log = {type: 'modify', geometry: g, feature: f};
+ }
+ }, {
+ doubleTouchTolerance: 2
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ // test
+
+ var ret;
+
+ // tap on (1, 0)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(1, 0)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-149, 75),
+ "[touchend] correct point");
+
+ // doubletap on (10, 10)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(9, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(10, 10)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-140, 65),
+ "[touchend] correct point");
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(11, 10)});
+ t.ok(!ret, '[touchstart] event does not propagate');
+ t.eq(log.type, 'done', '[touchend] feature finalized');
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-149, 75), // (1, 0)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ]), "[touchstart] final geometry is correct");
+ log = null;
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] feature not finalized or modified');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ // a) tap
+ // b) tap-move
+ // c) doubletap
+ function test_touch_sequence2(t) {
+ t.plan(25);
+
+ // set up
+
+ var log;
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {
+ done: function(g, f) {
+ log = {type: 'done', geometry: g, feature: f};
+ },
+ modify: function(g, f) {
+ log = {type: 'modify', geometry: g, feature: f};
+ }
+ }, {
+ doubleTouchTolerance: 2
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ // test
+
+ var ret;
+
+ // tap on (1, 0)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(1, 0)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-149, 75),
+ "[touchend] correct point");
+
+ // tap-move
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(9, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(20, 20)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] feature not finalized or modified');
+
+ // doubletap on (10, 10)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(9, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(10, 10)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-140, 65),
+ "[touchend] correct point");
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(11, 10)});
+ t.ok(!ret, '[touchstart] event does not propagate');
+ t.eq(log.type, 'done', '[touchend] feature finalized');
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(-149, 75), // (1, 0)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ]), "[touchstart] final geometry is correct");
+ log = null;
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] feature not finalized or modified');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ function test_persist_one_click_freehand(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {}, {persist: true});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ var feature1 = handler.line;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ t.ok(feature1.layer != null, "a) feature1 not destroyed");
+
+ // one click freehand
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ var feature2 = handler.line;
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ t.ok(feature2.layer != null, "b) feature2 not destroyed");
+ t.ok(feature1.layer == null, "b) feature1 destroyed");
+
+ map.destroy();
+ }
+
+ function test_set_freehand(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+
+ var geo, pointsCount;
+ var handler = new OpenLayers.Handler.Path(control, {
+ done: function(g) {
+ geo = g;
+ },
+ point: function() {
+ pointsCount++;
+ }
+ },
+ {freehand: true}
+ );
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ geo = null;
+ pointsCount = 0;
+ // Using mouse events
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2)});
+ t.ok(geo != null, "feature drawn when mouseup");
+ t.eq(pointsCount, 2, "two points have been added");
+
+ handler.deactivate();
+ var geoMouse = geo;
+
+ handler.activate();
+
+ geo = null;
+ pointsCount = 0;
+ // Using touch events
+ handler.touchstart(
+ {type: "touchstart", xy: new OpenLayers.Pixel(0, 0)});
+ try {
+ handler.touchmove(
+ {type: "touchmove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.touchmove(
+ {type: "touchmove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.touchend(
+ {type: "touchend"});
+ } catch(err) {
+ t.fail("occurred errors using touch events");
+ }
+ t.ok(geo != null, "feature drawn when touchend");
+ t.eq(pointsCount, 2, "two points have been added");
+
+ t.geom_eq(geo, geoMouse,
+ "geometry obtained using the mouse and touch events are the same");
+
+ map.destroy();
+ }
+
+ function test_citeComplaint(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.OSM());
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Path(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.zoomToExtent(new OpenLayers.Bounds(-24225034.496992, -11368938.517442, -14206280.326992, -1350184.3474418));
+ handler.activate();
+ handler.createFeature(new OpenLayers.Pixel(100, 50));
+ t.ok(handler.point.geometry.x < 0, "Geometry started correctly when wrapping the dateline using citeCompliant false");
+ control.deactivate();
+
+ handler = new OpenLayers.Handler.Path(control, {}, {citeCompliant: true});
+ control.handler = handler;
+ control.activate();
+ handler.createFeature(new OpenLayers.Pixel(100, 50));
+ t.ok(handler.point.geometry.x > 0, "Geometry started correctly when wrapping the dateline using citeCompliant true");
+
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Pinch.html b/misc/openlayers/tests/Handler/Pinch.html
new file mode 100644
index 0000000..c4883df
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Pinch.html
@@ -0,0 +1,285 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ };
+ var handler = new OpenLayers.Handler.Pinch(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_activate(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Pinch(control);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ handler.pinching = true;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ t.ok(!handler.pinching,
+ "activate sets pinching to false");
+
+ }
+
+ function test_events(t) {
+ // each handled event should be activated twice when handler is
+ // activated, so:
+ // 27 = 4tests * 2*3events + 1tests * 3events
+ t.plan(27);
+
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Pinch(control);
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["touchend", "touchmove", "touchstart"];
+ var nonevents = ["mousedown", "mouseup", "mousemove", "mouseout",
+ "click", "dblclick", "resize", "focus", "blur"];
+ map.events.registerPriority = function(type, obj, func) {
+ // this is one of the mock handler methods
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled: " + type);
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.Pinch",
+ "activate calls registerPriority with the handler");
+ };
+ handler.activate();
+ handler.deactivate();
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key;};
+ }
+
+ map.events.registerPriority = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ t.eq(r, type,
+ "activate calls registerPriority with the correct method");
+ }
+ }
+ handler.activate();
+
+ }
+
+ function test_callbacks(t) {
+ t.plan(32);
+
+ var map = new OpenLayers.Map('map', {controls: [], fallThrough: true});
+
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ // set fake values for touches
+ var testEvents = {
+ start: {
+ type: 'touchstart',
+ touches: [{
+ clientX: 100,
+ clientY: 0
+ }, {
+ clientX: 0,
+ clientY: 0
+ }]
+ },
+ move: {
+ type: 'touchmove',
+ touches: [{
+ clientX: 100,
+ clientY: 0
+ }, {
+ clientX: 20,
+ clientY: 0
+ }]
+ },
+ done: {
+ type: 'touchend',
+ touches: []
+ }
+ };
+
+ // set callback methods
+ var customCb = OpenLayers.Function.False;
+ var cb = function(evt) {
+ var callback = evt.type.replace("touch", "").replace("end", "done");;
+ var tch = testEvents[callback].touches;
+ t.ok(evt.touches[0].clientX == tch[0].clientX &&
+ evt.touches[0].clientY == tch[0].clientY,
+ "touchstart sets first touch position correctly in evt");
+ t.ok(evt.touches[1].clientX == tch[1].clientX &&
+ evt.touches[1].clientY == tch[1].clientY,
+ "touchstart sets second touch position correctly in evt");
+ t.eq(handler.start.distance, 100, "start distance is " +
+ "always the same");
+ customCb.apply(this, arguments);
+ }
+ var callbacks = {
+ start: cb,
+ move: cb,
+ done: function () {
+ customCb.apply(this, arguments);
+ }
+ };
+
+ var handler = new OpenLayers.Handler.Pinch(control, callbacks);
+ handler.activate();
+
+ var old_isMultiTouch = OpenLayers.Event.isMultiTouch;
+ var old_stop = OpenLayers.Event.stop;
+
+ // test single touch
+ OpenLayers.Event.isMultiTouch = function() {
+ return false;
+ }
+
+ // no callbacks with tests expected (pinch not started)
+ map.events.handleBrowserEvent(testEvents.start);
+ // test 1, 2, 3
+ t.ok(!handler.started, "1) touchstart (singletouch) sets started to false");
+ t.eq(handler.start, null, "1) touchstart (singletouch) sets start to null");
+ t.eq(handler.last, null, "1) touchstart (singletouch) sets last to null");
+
+ handler.started = true;
+ handler.start = {
+ distance: 100,
+ delta: 0,
+ scale: 1
+ };
+ handler.last = {
+ distance: 150,
+ delta: 10,
+ scale: 1.5
+ };
+
+ // no callbacks with tests expected (multitouch pinch started, so ignores singletouch)
+ map.events.handleBrowserEvent(testEvents.start);
+ // test 4, 5, 6
+ t.ok(handler.started, "1) touchstart (singletouch) after pinch started is ignored");
+ t.ok(!!handler.start, "1) touchstart (singletouch) after pinch started is ignored");
+ t.ok(!!handler.last, "1) touchstart (singletouch) after pinch started is ignored");
+
+ OpenLayers.Event.stop = function(evt, allowDefault) {
+ if(allowDefault) {
+ t.fail(
+ "touchstart is prevented from falling to other elements");
+ }
+ }
+ OpenLayers.Event.isMultiTouch = function(evt) {
+ var res = old_isMultiTouch(evt);
+ t.ok(res, "fake event is a mutitouch touch event");
+ return res;
+ }
+ customCb = function(evt, pinchdata) {
+ t.eq(pinchdata.distance, 100, "2) calculated distance is correct");
+ t.eq(pinchdata.delta, 0, "2) calculated delta is correct");
+ t.eq(pinchdata.scale, 1, "2) calculated scale is correct");
+ }
+ // test 7, 8, 9, 10, 11, 12, 13
+ map.events.handleBrowserEvent(testEvents.start);
+ // test 14, 15
+ t.ok(handler.started, "2) touchstart sets the started flag to true");
+ t.ok(!handler.pinching, "2) touchstart sets the pinching flag to false");
+
+ customCb = function(evt, pinchdata) {
+ t.eq(pinchdata.distance, 80, "3) calculated distance is correct");
+ t.eq(pinchdata.delta, 20, "3) calculated delta is correct");
+ t.eq(pinchdata.scale, 0.8, "3) calculated scale is correct");
+ }
+ // test 16, 17, 18, 19, 20, 21, 22
+ map.events.handleBrowserEvent(testEvents.move);
+ // test 23, 24
+ t.ok(handler.started, "3) started flag still set to true");
+ t.ok(handler.pinching, "3) touchmove sets the pinching flag to true");
+
+ OpenLayers.Event.isMultiTouch = old_isMultiTouch;
+
+ customCb = function(evt, first, last) {
+ t.eq(first.distance, 100, "4) calculated distance is correct");
+ t.eq(first.delta, 0, "4) calculated delta is correct");
+ t.eq(first.scale, 1, "4) calculated scale is correct");
+ t.eq(last.distance, 80, "4) calculated distance is correct");
+ t.eq(last.delta, 20, "4) calculated delta is correct");
+ t.eq(last.scale, 0.8, "4) calculated scale is correct");
+ }
+ // test 25, 26, 27, 28, 29, 30
+ map.events.handleBrowserEvent(testEvents.done);
+ // test 31, 32
+ t.ok(!handler.started, "4) started flag is set to false");
+ t.ok(!handler.pinching, "4) touchdone sets the pinching flag to false");
+
+ OpenLayers.Event.stop = old_stop;
+
+ // test move or done before start
+ customCb = function(evt) {
+ t.fail("should not pass here")
+ }
+ // no callbacks with tests expected
+ map.events.handleBrowserEvent(testEvents.move);
+ map.events.handleBrowserEvent(testEvents.done);
+
+ }
+
+ function test_deactivate(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Pinch(control);
+ handler.active = false;
+ var deactivated = handler.deactivate();
+ t.ok(!deactivated,
+ "deactivate returns false if the handler was not already active");
+ handler.active = true;
+ handler.pinching = true;
+ deactivated = handler.deactivate();
+ t.ok(deactivated,
+ "deactivate returns true if the handler was active already");
+ t.ok(!handler.started,
+ "deactivate sets started to false");
+ t.ok(!handler.pinching,
+ "deactivate sets pinching to false");
+ t.ok(handler.start == null,
+ "deactivate sets start to null");
+ t.ok(handler.last == null,
+ "deactivate sets start to null");
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Point.html b/misc/openlayers/tests/Handler/Point.html
new file mode 100644
index 0000000..b5a7cf3
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Point.html
@@ -0,0 +1,600 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Point_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Point(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_Point_activation(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Point(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ t.ok(handler.layer instanceof OpenLayers.Layer.Vector,
+ "activate creates a vector layer");
+ t.ok(handler.layer.map == map,
+ "activate adds the vector layer to the map");
+ activated = handler.deactivate();
+ t.ok(activated,
+ "deactivate returns true if the handler was active already");
+ var failed = false;
+ try {
+ handler.finalize();
+ msg = "finalizing after deactivation does not throw an error";
+ } catch (err) {
+ failed = true;
+ msg = "finalizing after deactivation throws an error";
+ }
+ t.ok(!failed, msg);
+ map.destroy();
+ }
+
+ // http://trac.osgeo.org/openlayers/ticket/3179
+ function test_activate_before_map_is_centered(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map', {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Point(control, {});
+ control.handler = handler;
+ map.addControl(control);
+
+ var error;
+ try {
+ handler.activate();
+ error = false;
+ } catch(err) {
+ error = true;
+ }
+ t.ok(!error, "no error on activate");
+ }
+
+ function test_Handler_Point_events(t) {
+ t.plan(49);
+ var log = [];
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Point(control, {
+ "create": function(g, f) {
+ log.push({geometry: g, feature: f});
+ }
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // list below events that should be handled (events) and those
+ // that should not be handled (nonevents) by the handler
+ var events = ["click", "dblclick", "mousedown", "mouseup", "mousemove", "mouseout", "touchstart", "touchmove", "touchend"];
+ var nonevents = ["resize", "focus", "blur"];
+ map.events.registerPriority = function(type, obj, func) {
+ var r = func();
+ if(typeof r == "string") {
+ // this is one of the mock handler methods
+ t.eq(OpenLayers.Util.indexOf(nonevents, type), -1,
+ "registered method is not one of the events " +
+ "that should not be handled");
+ t.ok(OpenLayers.Util.indexOf(events, type) > -1,
+ "activate calls registerPriority with browser event: " + type);
+ t.eq(typeof func, "function",
+ "activate calls registerPriority with a function");
+ t.eq(func(), type,
+ "activate calls registerPriority with the correct method");
+ t.eq(obj["CLASS_NAME"], "OpenLayers.Handler.Point",
+ "activate calls registerPriority with the handler");
+ }
+ }
+
+ // set browser event like properties on the handler
+ for(var i=0; i<events.length; ++i) {
+ setMethod(events[i]);
+ }
+ function setMethod(key) {
+ handler[key] = function() {return key};
+ }
+
+ var activated = handler.activate();
+ handler.destroy();
+
+ // test that click and dblclick are stopped
+ var handler = new OpenLayers.Handler.Point(control);
+ var oldStop = OpenLayers.Event.stop;
+ OpenLayers.Event.stop = function(evt) {
+ t.ok(evt.type == "click" || evt.type == "dblclick",
+ evt.type + " stopped");
+ }
+ t.eq(handler.click({type: "click"}), false, "click returns false");
+ t.eq(handler.dblclick({type: "dblclick"}), false, "dblclick returns false");
+ OpenLayers.Event.stop = oldStop;
+
+ }
+
+ function test_callbacks(t) {
+ t.plan(24);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var logs = [], log;
+ var handler = new OpenLayers.Handler.Point(control, {
+ create: function() {
+ logs.push({type: "create", args: arguments});
+ },
+ modify: function() {
+ logs.push({type: "modify", args: arguments});
+ },
+ done: function() {
+ logs.push({type: "done", args: arguments});
+ },
+ cancel: function() {
+ logs.push({type: "cancel", args: arguments});
+ }
+ },
+ {
+ pixelTolerance: 0,
+ dblclickTolerance: 0
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 2, "[mousedown] called back twice");
+ log = logs.shift();
+ t.eq(log.type, "create", "[mousedown] create called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousedown] correct point");
+ t.geom_eq(log.args[1].geometry,
+ new OpenLayers.Geometry.Point(-150, 75),
+ "[mousedown] correct feature");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousedown] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousedown] correct point");
+ t.geom_eq(log.args[1].geometry,
+ new OpenLayers.Geometry.Point(-150, 75),
+ "[mousedown] correct feature");
+ // mouse move
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 0)});
+ t.eq(logs.length, 0, "[mousemove] not called back");
+ // mouse up (no finalize - we moved)
+ handler.mouseup({type: "mouseup", xy: new OpenLayers.Pixel(1, 0)});
+ t.eq(logs.length, 0, "[mouseup] not called back");
+ // mouse move
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 0)});
+ t.eq(logs.length, 1, "[mousemove] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-148, 75),
+ "[mousemove] correct point");
+ t.geom_eq(log.args[1].geometry,
+ new OpenLayers.Geometry.Point(-148, 75),
+ "[mousemove] correct feature");
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(2, 0)});
+ t.eq(logs.length, 1, "[mousedown] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousedown] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-148, 75),
+ "[mousedown] correct point");
+ t.geom_eq(log.args[1].geometry,
+ new OpenLayers.Geometry.Point(-148, 75),
+ "[mousedown] correct feature");
+ // mouse up
+ handler.mouseup({type: "mouseup", xy: new OpenLayers.Pixel(2, 0)});
+ t.eq(logs.length, 1, "[mouseup] called back");
+ log = logs.shift();
+ t.eq(log.type, "done", "[mouseup] done called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-148, 75),
+ "[mouseup] correct point");
+ // mouse up on same pixel
+ handler.mouseup({type: "mouseup", xy: new OpenLayers.Pixel(2, 0)});
+ t.eq(logs.length, 0, "[mouseup] not called back");
+ // cancel
+ handler.cancel();
+ t.eq(logs.length, 1, "[cancel] called back");
+ log = logs.shift();
+ t.eq(log.type, "cancel", "[cancel] cancel called");
+ t.eq(log.args[0], null, "[cancel] got null");
+
+ map.destroy();
+ }
+
+ function test_persist(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Point(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ handler.persist = false;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(handler.layer.features.length, 0,
+ "feature destroyed on mouseup when persist is false");
+
+ handler.persist = true;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 0)});
+ t.eq(handler.layer.features.length, 1,
+ "feature not destroyed on mouseup when persist is true");
+ var feature = handler.layer.features[0];
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(2, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 0)});
+ t.ok(handler.layer.features[0] !== feature,
+ "persisted feature destroyed on next mouseup");
+
+ map.destroy();
+ }
+
+
+ function test_Handler_Point_deactivation(t) {
+ t.plan(5);
+ var log = [];
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Point(control, {
+ "cancel": function(g) {
+ log.push({geometry: g});
+ }
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+ handler.mousemove({xy: new OpenLayers.Pixel(0, 0)});
+ var _layer = handler.layer;
+ var _geometry = handler.point.geometry;
+ handler.deactivate();
+ t.eq(_layer.map, null,
+ "deactivates removes the layer from the map");
+ t.eq(handler.layer, null,
+ "deactivates sets its \"layer\" property to null");
+ t.eq(log.length, 1,
+ "deactivates calls \"cancel\" once");
+ t.ok(log[0].geometry.equals(_geometry),
+ "\"cancel\" called with expected geometry");
+
+ handler.activate();
+ handler.layer.destroy();
+ handler.deactivate();
+ t.eq(handler.layer, null,
+ "deactivate doesn't throw an error if layer was" +
+ " previously destroyed");
+
+ map.destroy();
+ }
+
+ function test_Handler_Point_bounds(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Point(control, {});
+ var activated = handler.activate();
+ var px = new OpenLayers.Pixel(150, 75);
+ var evt = {xy: px, which: 1};
+ handler.mousemove(evt);
+ var lonlat = map.getLonLatFromPixel(px);
+ t.eq(handler.point.geometry.x, lonlat.lon, "X is correct");
+ t.eq(handler.point.geometry.y, lonlat.lat, "Y is correct");
+ t.ok(handler.point.geometry.getBounds().equals(new OpenLayers.Bounds(lonlat.lon,lonlat.lat,lonlat.lon,lonlat.lat)), "Correct bounds");
+ var evt = {xy: new OpenLayers.Pixel(175, 100), which: 1};
+ handler.mousemove(evt);
+ t.ok(!handler.point.geometry.getBounds().equals(new OpenLayers.Bounds(0,0,0,0)), "Bounds changed after moving mouse");
+ }
+
+ function test_Handler_Point_destroy(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Point(control, {foo: 'bar'});
+
+ handler.activate();
+ handler.mousemove({xy: new OpenLayers.Pixel(150, 75)});
+
+ t.ok(handler.layer,
+ "handler has a layer prior to destroy");
+ t.ok(handler.point,
+ "handler has a point prior to destroy");
+ handler.destroy();
+ t.eq(handler.layer, null,
+ "handler.layer is null after destroy");
+ t.eq(handler.point, null,
+ "handler.point is null after destroy");
+ }
+
+ function test_touchstart(t) {
+ // a test to verify that the touchstart function does
+ // unregister the mouse listeners when it's called the
+ // first time
+
+ t.plan(4);
+
+ // set up
+
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1],
+ controls: []
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Point(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ var eventTypes = ['mousedown', 'mouseup', 'mousemove', 'click', 'dblclick',
+ 'mouseout'];
+
+ function allRegistered() {
+ var eventType,
+ listeners,
+ listener,
+ flag;
+ for(var i=0, ilen=eventTypes.length; i<ilen; i++) {
+ flag = false;
+ eventType = eventTypes[i];
+ listeners = map.events.listeners[eventType];
+ for(var j=0, jlen=listeners.length; j<jlen; j++) {
+ listener = listeners[j];
+ if(listener.func === handler[eventType] && listener.obj === handler) {
+ flag = true;
+ break;
+ }
+ }
+ if(!flag) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function noneRegistered() {
+ var eventType,
+ times,
+ flag = false;
+ for(var i=0, ilen=eventTypes.length; i<ilen; i++) {
+ eventType = eventTypes[i];
+ times = map.events.listeners[eventType].length;
+ if (times != 0) {
+ t.fail(eventType + " is registered " + times + " times");
+ flag = true;
+ }
+ }
+ return !flag;
+ }
+
+
+ // test
+
+ t.ok(allRegistered(), 'mouse listeners are registered');
+ handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(noneRegistered(), 'mouse listeners are unregistered');
+ t.ok(handler.touch, 'handler.touch is set');
+
+ handler.deactivate();
+ t.ok(!handler.touch, 'handler.touch is not set');
+
+ // tear down
+
+ map.destroy();
+ }
+
+
+ //
+ // Sequence tests
+ //
+ // Sequence tests basically involve executing a sequence of events
+ // and testing the resulting geometry.
+ //
+ // Below are tests for various drawing sequences. Tests can be
+ // added here each a non-working sequence is found.
+ //
+
+ // tap
+ function test_touch_sequence1(t) {
+ t.plan(8);
+
+ // set up
+
+ var log;
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Point(control, {
+ done: function(g, f) {
+ log = {geometry: g, feature: f};
+ }
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ // test
+
+ var ret;
+
+ // tap on (1, 0)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] no finalization');
+ t.eq(handler.point, null, '[touchstart] feature not modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(1, 0)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] no finalization');
+ t.eq(handler.point, null, '[touchmove] feature not modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-149, 75),
+ "[touchend] correct point");
+ // tear down
+
+ map.destroy();
+ }
+
+ // tap-move
+ function test_touch_sequence2(t) {
+ t.plan(9);
+
+ // set up
+
+ var log;
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Point(control, {
+ done: function(g, f) {
+ log = {geometry: g, feature: f};
+ }
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ // test
+
+ var ret;
+
+ // tap-move (0, 0) -> (9, 0)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] no finalization');
+ t.eq(handler.point, null, null,
+ '[touchstart] feature not modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(9, 0)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] no finalization');
+ t.eq(handler.point, null,
+ '[touchmove] feature not modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] no finalization');
+ t.eq(handler.point, null,
+ '[touchend] feature not modified');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/Polygon.html b/misc/openlayers/tests/Handler/Polygon.html
new file mode 100644
index 0000000..8fad5dd
--- /dev/null
+++ b/misc/openlayers/tests/Handler/Polygon.html
@@ -0,0 +1,1161 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_Polygon_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.Polygon(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_Polygon_activation(t) {
+ t.plan(5);
+ var log = [];
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Polygon(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.active = true;
+
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ t.ok(handler.layer instanceof OpenLayers.Layer.Vector,
+ "activate creates a vector layer");
+ t.ok(handler.layer.map == map,
+ "activate adds the vector layer to the map");
+ activated = handler.deactivate();
+ t.ok(activated,
+ "deactivate returns true if the handler was active already");
+
+ map.destroy();
+ }
+
+ // See: http://trac.osgeo.org/openlayers/ticket/3179
+ function test_activate_before_map_is_centered(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map', {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control();
+ var handler = new OpenLayers.Handler.Polygon(control, {});
+ control.handler = handler;
+ map.addControl(control);
+
+ var error;
+ try {
+ handler.activate();
+ error = false;
+ } catch(err) {
+ error = true;
+ }
+ t.ok(!error, "no error on activate");
+ }
+
+ function test_bounds_stopDown_true(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Polygon(control, {},
+ {stopDown: true, stopUp: true});
+ var activated = handler.activate();
+ // click on (150, 75)
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ // click on (175, 100)
+ evt = {xy: new OpenLayers.Pixel(175, 100), which: 1};
+ handler.mousemove(evt);
+ handler.mousedown(evt);
+ handler.mouseup(evt);
+ t.ok(handler.line.geometry.getBounds().equals(new OpenLayers.Bounds(0,-35.15625,35.15625,0)), "Correct bounds");
+ // mousedown on (175, 100)
+ evt = {xy: new OpenLayers.Pixel(175, 100), which: 1};
+ handler.mousedown(evt);
+ // mousemove to (125, 100)
+ evt = {xy: new OpenLayers.Pixel(125, 100), which: 1};
+ handler.mousemove(evt);
+ // test that the bounds have changed
+ t.ok(!handler.polygon.geometry.getBounds().equals(new OpenLayers.Bounds(0,-35.15625,35.15625,0)),
+ "Correct bounds after dragging without letting go. (Came out as "+handler.line.geometry.getBounds().toBBOX() + ".)");
+ map.destroy();
+ }
+
+ function test_callbacks(t) {
+ t.plan(39);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({
+ });
+ var logs = [], log;
+ var handler = new OpenLayers.Handler.Polygon(control, {
+ create: function() {
+ logs.push({type: "create", args: arguments});
+ },
+ point: function() {
+ logs.push({type: "point", args: arguments});
+ },
+ modify: function() {
+ logs.push({type: "modify", args: arguments});
+ },
+ done: function() {
+ logs.push({type: "done", args: arguments});
+ },
+ cancel: function() {
+ logs.push({type: "cancel", args: arguments});
+ }
+ },
+ {
+ pixelTolerance: 0
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // create polygon
+ handler.activate();
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 2, "[mousemove] called back");
+ log = logs.shift();
+ t.eq(log.type, "create", "[activate] create called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousemove] correct point");
+ t.ok(log.args[1] == handler.polygon,
+ "[mousemove] correct feature");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.polygon,
+ "[mousemove] correct feature");
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 1, "[mousedown] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousedown] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mousedown] correct point");
+ t.ok(log.args[1] === handler.polygon,
+ "[mousedown] correct feature");
+ // mouse up
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ t.eq(logs.length, 2, "[mouseup] called back twice");
+ log = logs.shift();
+ t.eq(log.type, "point", "[mouseup] point called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mouseup] correct point");
+ var geom = new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-150, 75)
+ ])
+ ]);
+ geom.components[0].addComponent(
+ new OpenLayers.Geometry.Point(-150, 75),
+ geom.components[0].components.length
+ );
+ t.geom_eq(log.args[1], geom, "[mouseup] correct polygon");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mouseup] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 75),
+ "[mouseup] correct point");
+ t.ok(log.args[1] == handler.polygon,
+ "[mouseup] correct feature");
+ // mouse move
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 1, "[mousemove] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-140, 65),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.polygon,
+ "[mousemove] correct feature");
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(10, 10)});
+ t.eq(logs.length, 1, "[mousedown] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousedown] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-140, 65),
+ "[mousedown] correct point");
+ t.ok(log.args[1] === handler.polygon,
+ "[mousedown] correct feature");
+ // mouse up
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(10, 10)});
+ log = logs.shift();
+ log = logs.shift();
+ // move to 0, 10 and double click
+ // mouse move
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 10)});
+ t.eq(logs.length, 1, "[mousemove] called back");
+ log = logs.shift();
+ t.eq(log.type, "modify", "[mousemove] modify called");
+ t.geom_eq(log.args[0], new OpenLayers.Geometry.Point(-150, 65),
+ "[mousemove] correct point");
+ t.ok(log.args[1] === handler.polygon,
+ "[mousemove] correct feature");
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 10)});
+ t.eq(logs.length, 1, "[mousedown] not called back");
+ log = logs.shift();
+ // mouse up
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 10)});
+ t.eq(logs.length, 2, "[mouseup] called back");
+ log = logs.shift();
+ log = logs.shift();
+ // mouse down
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 10)});
+ t.eq(logs.length, 0, "[mousedown] not called back");
+ // mouse up
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 10)});
+ t.eq(logs.length, 0, "[mouseup] not called back");
+ // dblclick
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(0, 10)});
+ t.eq(logs.length, 1, "[dblclick] called back");
+ log = logs.shift();
+ t.eq(log.type, "done", "[dblclick] done called");
+ t.geom_eq(
+ log.args[0],
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-150, 75),
+ new OpenLayers.Geometry.Point(-140, 65),
+ new OpenLayers.Geometry.Point(-150, 65),
+ new OpenLayers.Geometry.Point(-150, 75)
+ ])
+ ]),
+ "[dblclick] correct polygon"
+ );
+ // cancel
+ handler.cancel();
+ t.eq(logs.length, 1, "[cancel] called back");
+ log = logs.shift();
+ t.eq(log.type, "cancel", "[cancel] canced called");
+
+ map.destroy();
+ }
+
+ function test_toggle_freehand(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control, {
+ done: function(g) {
+ log++;
+ }
+ }, {persist: true});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ log = 0;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.eq(log, 1, "feature drawn when shift pressed on mousedown");
+
+ log = 0;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: false});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.eq(log, 0, "feature not drawn when shift not pressed on mousedown");
+ }
+
+ function test_persist(t) {
+ t.plan(4);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ handler.persist = false;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature1 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(2, 2)});
+ t.ok(feature1.layer == null, "a) feature1 destroyed");
+
+ handler.persist = true;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature2 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(2, 2)});
+ t.ok(feature2.layer != null, "b) feature2 not destroyed");
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature3 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(2, 2)});
+ t.ok(feature3.layer != null, "c) feature3 not destroyed");
+ t.ok(feature2.layer == null, "c) feature2 destroyed");
+
+ map.destroy();
+ }
+
+ function test_persist_freehand(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+
+ handler.persist = false;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature1 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ t.ok(feature1.layer == null, "a) feature1 destroyed");
+
+ handler.persist = true;
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature2 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ t.ok(feature2.layer != null, "b) feature2 not destroyed");
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ var feature3 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ t.ok(feature3.layer != null, "c) feature3 not destroyed");
+ t.ok(feature2.layer == null, "c) feature2 destroyed");
+
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ feature4 = handler.polygon;
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0), shiftKey: false});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1), shiftKey: true});
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(2, 2), shiftKey: true});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0), shiftKey: true});
+ t.ok(feature4.layer != null, "d) feature4 not destroyed");
+ t.ok(feature3.layer == null, "c) feature3 destroyed");
+
+ map.destroy();
+ }
+
+ function test_rings(t) {
+ t.plan(12);
+
+ var log = [];
+ var map = new OpenLayers.Map({
+ div: "map",
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ layers: [
+ new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ eventListeners: {
+ featureadded: function(event) {
+ log.push(event);
+ },
+ sketchmodified: function(event) {
+ log.push(event);
+ },
+ sketchcomplete: function(event) {
+ log.push(event);
+ }
+ }
+ })
+ ],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ // create control for drawing polygons with holes
+ var draw = new OpenLayers.Control.DrawFeature(
+ map.layers[0],
+ OpenLayers.Handler.Polygon,
+ {handlerOptions: {
+ holeModifier: "altKey",
+ pixelTolerance: 0
+ }}
+ );
+ map.addControl(draw);
+ draw.activate();
+
+ var event;
+ function trigger(type, event) {
+ map.events.triggerEvent(type, OpenLayers.Util.extend({}, event));
+ }
+
+ // a) draw a polygon
+ log = [];
+ // start at -9, 9
+ event = {xy: new OpenLayers.Pixel(-9, 9)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -1, 9
+ event = {xy: new OpenLayers.Pixel(-1, 9)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -1, 1
+ event = {xy: new OpenLayers.Pixel(-1, 1)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -9, 1
+ event = {xy: new OpenLayers.Pixel(-9, 1)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-9, 1)};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 14, "a) correct number of events");
+ t.eq(log[log.length-1].type, "featureadded", "a) featureadded event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 64, "a) correct polygon area");
+
+ // b) draw a hole
+ log = [];
+ // start at -6, 6
+ event = {xy: new OpenLayers.Pixel(-6, 6), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -3, 6
+ event = {xy: new OpenLayers.Pixel(-3, 6), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -3, 3
+ event = {xy: new OpenLayers.Pixel(-3, 3), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -6, 3
+ event = {xy: new OpenLayers.Pixel(-6, 3), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-6, 3), altKey: true};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 13, "b) correct number of events");
+ t.eq(log[log.length-1].type, "sketchcomplete", "b) sketchcomplete event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 55, "b) correct polygon area");
+
+
+ // c) draw a polygon that overlaps the first
+ log = [];
+ // start at -2, 2
+ event = {xy: new OpenLayers.Pixel(-2, 2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 2, 2
+ event = {xy: new OpenLayers.Pixel(2, 2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 2, -2
+ event = {xy: new OpenLayers.Pixel(2, -2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -2, -2
+ event = {xy: new OpenLayers.Pixel(-2, -2)};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-2, -2)};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 14, "c) correct number of events");
+ t.eq(log[log.length-1].type, "featureadded", "c) featureadded event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 16, "c) correct polygon area");
+
+ // d) draw a hole that tries to go outside the exterior ring
+ log = [];
+ // start at -1, 1
+ event = {xy: new OpenLayers.Pixel(-1, 1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 1, 1
+ event = {xy: new OpenLayers.Pixel(1, 1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // try to draw to -8, 8 (ouside active polygon)
+ event = {xy: new OpenLayers.Pixel(-8, 8), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to 1, -1
+ event = {xy: new OpenLayers.Pixel(1, -1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // draw to -1, -1
+ event = {xy: new OpenLayers.Pixel(-1, -1), altKey: true};
+ trigger("mousemove", event);
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ // finish
+ event = {xy: new OpenLayers.Pixel(-1, 1), altKey: true};
+ trigger("mousedown", event);
+ trigger("mouseup", event);
+ trigger("dblclick", event);
+
+ // make assertions
+ t.eq(log.length, 18, "d) correct number of events");
+ t.eq(log[log.length-1].type, "sketchcomplete", "d) sketchcomplete event last");
+ t.eq(log[log.length-1].feature.geometry.getArea(), 12, "d) correct polygon area");
+
+
+ map.destroy();
+ }
+
+ function test_Handler_Polygon_destroy(t) {
+ t.plan(8);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.Polygon(control, {foo: 'bar'});
+
+ handler.activate();
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.mousedown(evt);
+
+ t.ok(handler.layer,
+ "handler has a layer prior to destroy");
+ t.ok(handler.point,
+ "handler has a point prior to destroy");
+ t.ok(handler.line,
+ "handler has a line prior to destroy");
+ t.ok(handler.polygon,
+ "handler has a polygon prior to destroy");
+ handler.destroy();
+ t.eq(handler.layer, null,
+ "handler.layer is null after destroy");
+ t.eq(handler.point, null,
+ "handler.point is null after destroy");
+ t.eq(handler.line, null,
+ "handler.line is null after destroy");
+ t.eq(handler.polygon, null,
+ "handler.polygon is null after destroy");
+ map.destroy();
+ }
+
+ function test_insertXY(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control.DrawFeature(
+ layer, OpenLayers.Handler.Polygon
+ );
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ control.activate();
+ var handler = control.handler;
+
+ function userClick(x, y) {
+ var px = new OpenLayers.Pixel(x, y);
+ handler.mousemove({type: "mousemove", xy: px});
+ handler.mousedown({type: "mousedown", xy: px});
+ handler.mouseup({type: "mouseup", xy: px});
+ }
+
+ // add points at px(0, 0) and px(10, 10)
+ userClick(0, 0);
+ userClick(10, 10);
+ t.eq(handler.line.geometry.components.length, 4, "ring has four points after two clicks");
+
+ // programmatically add a point
+ handler.insertXY(5, 6);
+ t.eq(handler.line.geometry.components.length, 5, "ring has five points after insertXY");
+ t.geom_eq(
+ handler.line.geometry.components[2],
+ new OpenLayers.Geometry.Point(5, 6),
+ "third point comes from insertXY"
+ );
+
+ map.destroy();
+
+ }
+
+ //
+ // Sequence tests
+ //
+ // Sequence tests basically involve executing a sequence of events
+ // and testing the resulting geometry.
+ //
+ // Below are tests for various drawing sequences. Tests can be
+ // added here each a non-working sequence is found.
+ //
+
+ // stopDown:true, stopUp:true
+ // a) click on (0, 0)
+ // b) mousedown on (0.5, 0.5)
+ // c) mouseup on (1, 1)
+ // d) click on (0, 10)
+ // e) dblclick on (10, 10)
+ function test_sequence1(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control,
+ {done: function(g) { log.geometry = g; }},
+ {stopDown: true, stopUp: true,
+ pixelTolerance: 0}
+ );
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+ log = {};
+
+ // a) click on (0, 0)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ // b) mousedown on (0.5, 0.5)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0.5, 0.5)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0.5, 0.5)});
+ // c) mouseup on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ // d) click on (0, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 10)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 10)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 10)});
+ // e) dblclick on (10, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(10, 10)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(10, 10)});
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-150, 65), // (0, 10)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ])
+ ]), "geometry is correct");
+ }
+
+ // stopDown:false, stopUp:false
+ // a) click on (0, 0)
+ // b) mousedown on (0.5, 0.5)
+ // c) mouseup on (1, 1)
+ // d) click on (0, 10)
+ // e) dblclick on (10, 10)
+ function test_sequence2(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control,
+ {done: function(g) { log.geometry = g; }},
+ {stopDown: false, stopUp: false,
+ pixelTolerance: 0}
+ );
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ handler.activate();
+ log = {};
+
+ // a) click on (0, 0)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 0)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 0)});
+ // b) mousedown on (0.5, 0.5)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0.5, 0.5)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0.5, 0.5)});
+ // c) mouseup on (1, 1)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(1, 1)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(1, 1)});
+ // d) click on (0, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(0, 10)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(0, 10)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(0, 10)});
+ // e) dblclick on (10, 10)
+ handler.mousemove(
+ {type: "mousemove", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mousedown(
+ {type: "mousedown", xy: new OpenLayers.Pixel(10, 10)});
+ handler.mouseup(
+ {type: "mouseup", xy: new OpenLayers.Pixel(10, 10)});
+ handler.dblclick(
+ {type: "dblclick", xy: new OpenLayers.Pixel(10, 10)});
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-150, 65), // (0, 10)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ])
+ ]), "geometry is correct");
+ }
+
+ // a) tap
+ // b) tap
+ // c) doubletap
+ function test_touch_sequence1(t) {
+ t.plan(26);
+
+ // set up
+
+ var log;
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control, {
+ done: function(g, f) {
+ log = {type: 'done', geometry: g, feature: f};
+ },
+ modify: function(g, f) {
+ log = {type: 'modify', geometry: g, feature: f};
+ }
+ }, {
+ doubleTouchTolerance: 2
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ // test
+
+ var ret;
+
+ // tap on (0, 0)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(1, 0)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-150, 75),
+ "[touchend] correct point");
+
+ // tap on (0, 10)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(1, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(0, 10)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-150, 65),
+ "[touchend] correct point");
+
+ // doubletap on (10, 10)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(9, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(10, 10)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-140, 65),
+ "[touchend] correct point");
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(11, 10)});
+ t.ok(!ret, '[touchstart] event does not propagate');
+ t.eq(log.type, 'done', '[touchend] feature finalized');
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-150, 65), // (0, 10)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ])
+ ]), "[touchstart] geometry is correct");
+ log = null;
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] feature not finalized or modified');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ // a) tap
+ // b) tap-move
+ // c) tap
+ // d) doubletap
+ function test_touch_sequence2(t) {
+ t.plan(32);
+
+ // set up
+
+ var log;
+ var map = new OpenLayers.Map("map", {
+ resolutions: [1]
+ });
+ var layer = new OpenLayers.Layer.Vector("foo", {
+ maxExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control, {
+ done: function(g, f) {
+ log = {type: 'done', geometry: g, feature: f};
+ },
+ modify: function(g, f) {
+ log = {type: 'modify', geometry: g, feature: f};
+ }
+ }, {
+ doubleTouchTolerance: 2
+ });
+ control.handler = handler;
+ map.addControl(control);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ handler.activate();
+
+ // test
+
+ var ret;
+
+ // tap on (0, 0)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(1, 0)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(0, 0)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-150, 75),
+ "[touchend] correct point");
+
+ // tap-move
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(1, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(20, 20)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] feature not finalized or modified');
+
+ // tap on (0, 10)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(1, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(0, 10)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-150, 65),
+ "[touchend] correct point");
+
+ // doubletap on (10, 10)
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(9, 10)});
+ t.ok(ret, '[touchstart] event propagates');
+ t.eq(log, null, '[touchstart] feature not finalized or modified');
+ ret = handler.touchmove({xy: new OpenLayers.Pixel(10, 10)});
+ t.ok(ret, '[touchmove] event propagates');
+ t.eq(log, null, '[touchmove] feature not finalized or modified');
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log.type, 'modify', '[touchend] feature modified');
+ t.geom_eq(log.geometry, new OpenLayers.Geometry.Point(-140, 65),
+ "[touchend] correct point");
+ log = null;
+ ret = handler.touchstart({xy: new OpenLayers.Pixel(11, 10)});
+ t.ok(!ret, '[touchstart] event does not propagate');
+ t.eq(log.type, 'done', '[touchend] feature finalized');
+ t.geom_eq(log.geometry,
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-150, 75), // (0, 0)
+ new OpenLayers.Geometry.Point(-150, 65), // (0, 10)
+ new OpenLayers.Geometry.Point(-140, 65) // (10, 10)
+ ])
+ ]), "[touchstart] geometry is correct");
+ log = null;
+ ret = handler.touchend({});
+ t.ok(ret, '[touchend] event propagates');
+ t.eq(log, null, '[touchend] feature not finalized or modified');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ function test_citeComplaint(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.OSM());
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var control = new OpenLayers.Control({});
+ var handler = new OpenLayers.Handler.Polygon(control, {});
+ control.handler = handler;
+ map.addControl(control);
+ map.zoomToExtent(new OpenLayers.Bounds(-24225034.496992, -11368938.517442, -14206280.326992, -1350184.3474418));
+ control.activate();
+ handler.createFeature(new OpenLayers.Pixel(100, 50));
+ t.ok(handler.point.geometry.x < 0, "Geometry started correctly when wrapping the dateline using citeCompliant false");
+ control.deactivate();
+
+ var handler = new OpenLayers.Handler.Polygon(control, {}, {citeCompliant: true});
+ control.handler = handler;
+ control.activate();
+ handler.createFeature(new OpenLayers.Pixel(100, 50));
+ t.ok(handler.point.geometry.x > 0, "Geometry started correctly when wrapping the dateline using citeCompliant true");
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Handler/RegularPolygon.html b/misc/openlayers/tests/Handler/RegularPolygon.html
new file mode 100644
index 0000000..ee43dc7
--- /dev/null
+++ b/misc/openlayers/tests/Handler/RegularPolygon.html
@@ -0,0 +1,235 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Handler_RegularPolygon_constructor(t) {
+ t.plan(3);
+ var control = new OpenLayers.Control();
+ control.id = Math.random();
+ var callbacks = {foo: "bar"};
+ var options = {bar: "foo"};
+
+ var oldInit = OpenLayers.Handler.prototype.initialize;
+
+ OpenLayers.Handler.prototype.initialize = function(con, call, opt) {
+ t.eq(con.id, control.id,
+ "constructor calls parent with the correct control");
+ t.eq(call, callbacks,
+ "constructor calls parent with the correct callbacks");
+ t.eq(opt, options,
+ "regular polygon constructor calls parent with the correct options");
+ }
+ var handler = new OpenLayers.Handler.RegularPolygon(control, callbacks, options);
+
+ OpenLayers.Handler.prototype.initialize = oldInit;
+ }
+
+ function test_Handler_RegularPolygon_activation(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.RegularPolygon(control);
+ handler.active = true;
+ var activated = handler.activate();
+ t.ok(!activated,
+ "activate returns false if the handler was already active");
+ handler.active = false;
+ activated = handler.activate();
+ t.ok(activated,
+ "activate returns true if the handler was not already active");
+ activated = handler.deactivate();
+ t.ok(activated,
+ "deactivate returns true if the handler was active already");
+ map.destroy();
+ }
+
+ function test_Handler_RegularPolygon_deactivation(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+
+ var handler = new OpenLayers.Handler.RegularPolygon(control, {foo: 'bar'});
+ handler.activate();
+ handler.layer.destroy();
+ handler.deactivate();
+ t.eq(handler.layer, null,
+ "deactivate doesn't throw an error if layer was" +
+ " previously destroyed");
+ map.destroy();
+ }
+
+ function test_Handler_RegularPolygon_four_corners(t) {
+ t.plan(7);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.RegularPolygon(control, {});
+ var activated = handler.activate();
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.down(evt);
+ var evt = {xy: new OpenLayers.Pixel(175, 75), which: 1};
+ handler.move(evt);
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "-35.15625,-35.15625,35.15625,35.15625",
+ "correct bounds after move");
+ t.eq(handler.feature.geometry.components[0].components.length, 5,
+ "geometry has 5 components");
+ t.eq(handler.feature.geometry.CLASS_NAME,
+ "OpenLayers.Geometry.Polygon",
+ "geometry is a polygon");
+ t.eq(handler.radius, 25*1.40625, "feature radius as set on handler");
+ var evt = {xy: new OpenLayers.Pixel(175, 80), which: 1};
+ handler.move(evt);
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "-35.15625,-35.15625,35.15625,35.15625",
+ "correct bounds after move with a fixed radius");
+ handler.cancel();
+ handler.setOptions({radius:2 / Math.sqrt(2)});
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.down(evt);
+
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "-1,-1,1,1",
+ "bounds with manual radius setting");
+ var evt = {xy: new OpenLayers.Pixel(175, 90), which: 1};
+ handler.move(evt);
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "34.15625,-22.09375,36.15625,-20.09375",
+ "bounds with manual radius setting and mousemove");
+ map.destroy();
+ }
+
+ function test_Handler_RegularPolygon_circle(t) {
+ t.plan(7);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS("", "", {}));
+ map.zoomToMaxExtent();
+ var control = new OpenLayers.Control();
+ map.addControl(control);
+ var handler = new OpenLayers.Handler.RegularPolygon(control, {}, {'sides':40});
+ var activated = handler.activate();
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.down(evt);
+ var evt = {xy: new OpenLayers.Pixel(175, 75), which: 1};
+ handler.move(evt);
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "-35.15625,-35.15625,35.15625,35.15625",
+ "correct bounds after move");
+ t.eq(handler.feature.geometry.components[0].components.length, 41,
+ "geometry has correct numbre of components");
+ t.eq(handler.feature.geometry.CLASS_NAME,
+ "OpenLayers.Geometry.Polygon",
+ "geometry is a polygon");
+ t.eq(handler.radius, 25*1.40625, "feature radius as set on handler");
+ var evt = {xy: new OpenLayers.Pixel(175, 80), which: 1};
+ handler.move(evt);
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "-35.823348,-35.823348,35.823348,35.823348",
+ "correct bounds after move with fixed radius");
+ handler.cancel();
+ handler.setOptions({radius:1});
+ var evt = {xy: new OpenLayers.Pixel(150, 75), which: 1};
+ handler.down(evt);
+
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "-0.996917,-0.996917,0.996917,0.996917",
+ "bounds with manual radius setting");
+ var evt = {xy: new OpenLayers.Pixel(175, 80), which: 1};
+ handler.move(evt);
+ t.eq(handler.feature.geometry.getBounds().toBBOX(),
+ "34.159333,-8.028167,36.153167,-6.034333",
+ "bounds with manual radius setting and mousemove");
+ map.destroy();
+ }
+
+ function test_Handler_RegularPolygon_irregular(t) {
+ t.plan(4);
+ var map = {
+ getResolution: function() {
+ return 1;
+ }
+ };
+ var layer = {
+ addFeatures: function() {},
+ drawFeature: function(feature, style) {
+ var ring = feature.geometry.components[0];
+ t.eq(ring.components[0].x, 20, "correct right");
+ t.eq(ring.components[0].y, 10, "correct bottom");
+ t.eq(ring.components[2].x, 10, "correct left");
+ t.eq(ring.components[2].y, 15, "correct top");
+ },
+ getLonLatFromViewPortPx: function(px) {
+ return {lon: px.x, lat: px.y};
+ }
+ };
+ var control = {};
+ var options = {
+ sides: 4,
+ irregular: true,
+ layer: layer,
+ map: map
+ };
+ var handler = new OpenLayers.Handler.RegularPolygon(
+ control, null, options
+ );
+ handler.origin = new OpenLayers.Geometry.Point(10, 10);
+ handler.feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon(
+ [new OpenLayers.Geometry.LinearRing()]
+ )
+ );
+ // should result in a 10 x 5 rectangle
+ handler.move({xy: {x: 20, y: 15}});
+ }
+
+ function test_callbacks(t) {
+ t.plan(1);
+
+ // setup
+ var map = new OpenLayers.Map("map");
+
+ var control = {"map": map};
+
+ var done = function(geom) {
+ t.ok(true,
+ "done callback called even if no move between down and up");
+ };
+
+ var handler = new OpenLayers.Handler.RegularPolygon(
+ control, {"done": done});
+ handler.activate();
+
+ var xy = new OpenLayers.Pixel(Math.random(), Math.random());
+
+ var isLeftClick = OpenLayers.Event.isLeftClick;
+ OpenLayers.Event.isLeftClick = function() { return true; };
+ handler.layer = {
+ renderer: {
+ clear: OpenLayers.Function.Void
+ },
+ addFeatures: OpenLayers.Function.Void,
+ drawFeature: OpenLayers.Function.Void,
+ destroyFeatures: OpenLayers.Function.Void,
+ getLonLatFromViewPortPx: function() {
+ return xy;
+ }
+ };
+
+ // test
+ map.events.triggerEvent("mousedown", {"xy": xy});
+ map.events.triggerEvent("mouseup", {"xy": xy});
+
+ // tear down
+ OpenLayers.Event.isLeftClick = isLeftClick;
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 300px; height: 150px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Icon.html b/misc/openlayers/tests/Icon.html
new file mode 100644
index 0000000..ac542d2
--- /dev/null
+++ b/misc/openlayers/tests/Icon.html
@@ -0,0 +1,68 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ var icon;
+
+ function test_Icon_constructor (t) {
+ t.plan( 4 );
+ var size = new OpenLayers.Size(5,6);
+ icon = new OpenLayers.Icon("", size);
+ t.ok( icon instanceof OpenLayers.Icon, "new OpenLayers.Icon returns Icon object" );
+ t.ok( icon.size instanceof OpenLayers.Size, "icon.size returns Size object" );
+ t.ok( icon.size.equals(size), "icon.size returns correct value" );
+ t.eq( icon.url, "", "icon.url returns str object" );
+ }
+ function test_Icon_clone (t) {
+ t.plan( 4 );
+ icon = new OpenLayers.Icon("a",new OpenLayers.Size(5,6));
+ t.ok( icon instanceof OpenLayers.Icon, "new OpenLayers.Icon returns Icon object" );
+ var cloned = icon.clone();
+ t.ok( cloned instanceof OpenLayers.Icon, "clone is an OpenLayers.Icon" );
+ cloned.url = "b"
+ t.eq( icon.url, "a", "icon.url doesn't change with clone's url" );
+ t.eq( cloned.url, "b", "cloned.url does change when edited" );
+ }
+
+ function test_Icon_setOpacity(t) {
+ t.plan( 2 );
+
+ icon = new OpenLayers.Icon("a",new OpenLayers.Size(5,6));
+ t.ok(!icon.imageDiv.style.opacity, "default icon has no opacity");
+
+ icon.setOpacity(0.5);
+ t.eq(parseFloat(icon.imageDiv.style.opacity), 0.5, "icon.setOpacity() works");
+ }
+
+ function test_Icon_isDrawn(t) {
+ t.plan(4);
+
+ var icon = {};
+
+ //no imageDiv
+ var drawn = OpenLayers.Icon.prototype.isDrawn.apply(icon, []);
+ t.ok(!drawn, "icon with no imageDiv not drawn");
+
+ //imageDiv no parentNode
+ icon.imageDiv = {};
+ drawn = OpenLayers.Icon.prototype.isDrawn.apply(icon, []);
+ t.ok(!drawn, "icon with imageDiv with no parentNode not drawn");
+
+ //imageDiv with parent
+ icon.imageDiv.parentNode = {};
+ drawn = OpenLayers.Icon.prototype.isDrawn.apply(icon, []);
+ t.ok(drawn, "icon with imageDiv with parentNode drawn");
+
+ //imageDiv with parent but nodetype 11
+ icon.imageDiv.parentNode = {'nodeType': 11};
+ drawn = OpenLayers.Icon.prototype.isDrawn.apply(icon, []);
+ t.ok(!drawn, "imageDiv with parent but nodetype 11 not drawn");
+ }
+
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Kinetic.html b/misc/openlayers/tests/Kinetic.html
new file mode 100644
index 0000000..ba50b53
--- /dev/null
+++ b/misc/openlayers/tests/Kinetic.html
@@ -0,0 +1,132 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Kinetic (t) {
+ t.plan(17);
+ var finish = false;
+ var results = {
+ 110: {x: -2.7, y: -3.6, end: false},
+ 120: {x: -2.1, y: -2.8, end: false},
+ 130: {x: -1.5, y: -2.0, end: false},
+ 140: {x: -0.9, y: -1.2, end: false},
+ 150: {x: -0.3, y: -0.4, end: true}
+ };
+
+ var originalGetTime = Date.prototype.getTime;
+ Date.prototype.getTime = function() { return 0 };
+
+ var interval = 10; // arbitrary value for tests
+
+ var originalLoopAnimation = OpenLayers.Animation.start;
+ OpenLayers.Animation.start = function(callback) {
+ while (!finish) {
+ var time = new Date().getTime();
+ Date.prototype.getTime = function() { return time+interval };
+ callback();
+ }
+ };
+
+ var kinetic = new OpenLayers.Kinetic({
+ deceleration: 0.01
+ });
+ kinetic.begin();
+ kinetic.update({x:0, y:0});
+
+ Date.prototype.getTime = function() { return 100 };
+ var measure = kinetic.end({x:30, y:40});
+
+ t.eq(measure.speed, 0.5, "correct speed");
+ t.eq(measure.theta, Math.PI - Math.atan(40/30), "correct angle");
+
+ // fake timer id
+ kinetic.timerId = 0;
+ kinetic.move(measure, function(x, y, end) {
+ var result = results[new Date().getTime()];
+ t.eq(Math.round(x * 1000) / 1000, result.x, "correct x");
+ t.eq(Math.round(y * 1000) / 1000, result.y, "correct y");
+ t.eq(end, result.end, "correct end");
+ finish = end;
+ });
+
+ Date.prototype.getTime = originalGetTime;
+ OpenLayers.Animation.start = originalLoopAnimation;
+ }
+
+ function test_Angle (t) {
+ t.plan(8);
+ var results = [
+ {speed: 0.5, theta: Math.round((Math.PI - Math.atan(40/30)) * 1000000) / 1000000},
+ {speed: 0.5, theta: Math.round((Math.PI + Math.atan(40/30)) * 1000000) / 1000000},
+ {speed: 0.5, theta: Math.round((- Math.atan(40/30)) * 1000000) / 1000000},
+ {speed: 0.5, theta: Math.round((Math.atan(40/30)) * 1000000) / 1000000}
+ ];
+
+ var originalGetTime = Date.prototype.getTime;
+ Date.prototype.getTime = function() { return 0 };
+
+ var kinetic = new OpenLayers.Kinetic();
+ kinetic.begin();
+ kinetic.update({x:0, y:0});
+
+ Date.prototype.getTime = function() { return 100 };
+ var measure = kinetic.end({x:30, y:40});
+
+ t.eq(measure.speed, results[0].speed, "correct speed");
+ t.eq(Math.round(measure.theta * 1000000) / 1000000,
+ results[0].theta, "correct angle");
+
+
+ var originalGetTime = Date.prototype.getTime;
+ Date.prototype.getTime = function() { return 0 };
+
+ var kinetic = new OpenLayers.Kinetic();
+ kinetic.begin();
+ kinetic.update({x:0, y:0});
+
+ Date.prototype.getTime = function() { return 100 };
+ var measure = kinetic.end({x:30, y:-40});
+
+ t.eq(measure.speed, results[1].speed, "correct speed");
+ t.eq(Math.round(measure.theta * 1000000) / 1000000,
+ results[1].theta, "correct angle");
+
+
+ var originalGetTime = Date.prototype.getTime;
+ Date.prototype.getTime = function() { return 0 };
+
+ var kinetic = new OpenLayers.Kinetic();
+ kinetic.begin();
+ kinetic.update({x:0, y:0});
+
+ Date.prototype.getTime = function() { return 100 };
+ var measure = kinetic.end({x:-30, y:-40});
+
+ t.eq(measure.speed, results[2].speed, "correct speed");
+ t.eq(Math.round(measure.theta * 1000000) / 1000000,
+ results[2].theta, "correct angle");
+
+ var originalGetTime = Date.prototype.getTime;
+ Date.prototype.getTime = function() { return 0 };
+
+ var kinetic = new OpenLayers.Kinetic();
+ kinetic.begin();
+ kinetic.update({x:0, y:0});
+
+ Date.prototype.getTime = function() { return 100 };
+ var measure = kinetic.end({x:-30, y:40});
+
+ t.eq(measure.speed, results[3].speed, "correct speed");
+ t.eq(Math.round(measure.theta * 1000000) / 1000000,
+ results[3].theta, "correct angle");
+
+ Date.prototype.getTime = originalGetTime;
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 600px; height: 300px;"/>
+ <div style="display: none;"><div id="invisimap"></div></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Lang.html b/misc/openlayers/tests/Lang.html
new file mode 100644
index 0000000..9f4fa9b
--- /dev/null
+++ b/misc/openlayers/tests/Lang.html
@@ -0,0 +1,106 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script src="../lib/OpenLayers/Lang/en-CA.js" type="text/javascript"></script>
+ <script src="../lib/OpenLayers/Lang/fr.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+ function test_setCode(t) {
+ t.plan(4);
+ OpenLayers.Lang.code = null;
+
+ // test with no argument - this could result in the default or the
+ // browser language if a dictionary exists
+ OpenLayers.Lang.setCode();
+ t.ok(OpenLayers.Lang.code != null,
+ "code set when no argument is sent");
+
+ var primary = "xx";
+ var subtag = "XX";
+ var code = primary + "-" + subtag;
+ OpenLayers.Lang[code] = {};
+
+ // test code for dictionary that exists
+ OpenLayers.Lang.setCode(code);
+ t.eq(OpenLayers.Lang.code, code,
+ "code properly set for existing dictionary");
+
+ // test code for dictionary that doesn't exist
+ OpenLayers.Lang.setCode(primary + "-YY");
+ t.eq(OpenLayers.Lang.code, OpenLayers.Lang.defaultCode,
+ "code set to default for non-existing dictionary");
+
+ // test code for existing primary but missing subtag
+ OpenLayers.Lang[primary] = {};
+ OpenLayers.Lang.setCode(primary + "-YY");
+ t.eq(OpenLayers.Lang.code, primary,
+ "code set to primary when subtag dictionary is missing");
+
+ // clean up
+ delete OpenLayers.Lang[code];
+ delete OpenLayers.Lang[primary];
+ OpenLayers.Lang.code = null;
+ }
+
+ function test_getCode(t) {
+ t.plan(3);
+ OpenLayers.Lang.code = null;
+
+ // test that a non-null value is retrieved - could be browser language
+ // or defaultCode
+ var code = OpenLayers.Lang.getCode();
+ t.ok(code != null, "returns a non-null code");
+ t.ok(OpenLayers.Lang.code != null, "sets the code to a non-null value");
+
+ // test that the code is returned if non-null
+ OpenLayers.Lang.code = "foo";
+ t.eq(OpenLayers.Lang.getCode(), "foo", "returns the code if non-null");
+
+ // clean up
+ OpenLayers.Lang.code = null;
+ }
+
+ function test_i18n(t) {
+ t.plan(1);
+ t.ok(OpenLayers.i18n === OpenLayers.Lang.translate,
+ "i18n is an alias for OpenLayers.Lang.translate");
+ }
+
+ function test_translate(t) {
+ var keys = ['test1', 'test3', 'noKey'];
+ var codes = ['en', 'en-CA', 'fr', 'fr-CA', 'sw'];
+ var result = {
+ 'en': {'Overlays':'Overlays',
+ 'unhandledRequest':'Unhandled request return foo',
+ 'noKey':'noKey'},
+ 'en-CA': {'Overlays':'Overlays',
+ 'unhandledRequest':'Unhandled request return foo',
+ 'noKey':'noKey'},
+ 'fr': {'Overlays':'Calques',
+ 'unhandledRequest':'Requête non gérée, retournant foo',
+ 'noKey':'noKey'},
+ 'fr-CA': {'Overlays':'Calques', //this should result in 'fr'
+ 'unhandledRequest':'Requête non gérée, retournant foo',
+ 'noKey':'noKey'},
+ 'sw': {'Overlays':'Overlays', //this should result in 'en'
+ 'unhandledRequest':'Unhandled request return foo',
+ 'noKey':'noKey'}
+ };
+
+ t.plan(keys.length*codes.length);
+
+ for (var i=0; i<codes.length; ++i) {
+ var code = codes[i];
+ OpenLayers.Lang.setCode(code);
+ t.eq(OpenLayers.Lang.translate('Overlays'), result[code]['Overlays'], "simple key lookup in "+code);
+ t.eq(OpenLayers.Lang.translate('unhandledRequest',{'statusText':'foo'}),
+ result[code]['unhandledRequest'], "lookup with argument substitution in "+code);
+ t.eq(OpenLayers.Lang.translate('noKey'), result[code]['noKey'], "invalid key returns the key in "+code);
+ }
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer.html b/misc/openlayers/tests/Layer.html
new file mode 100644
index 0000000..954a363
--- /dev/null
+++ b/misc/openlayers/tests/Layer.html
@@ -0,0 +1,910 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ function test_Layer_constructor (t) {
+ t.plan( 15 );
+
+ var options = { chicken: 151, foo: "bar", projection: "none" };
+ var layer = new OpenLayers.Layer('Test Layer', options);
+
+ t.ok( layer instanceof OpenLayers.Layer, "new OpenLayers.Layer returns object" );
+ t.eq( layer.CLASS_NAME, "OpenLayers.Layer", "CLASS_NAME variable set correctly");
+
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ t.ok( layer.id != null, "Layer is given an id");
+ t.ok( layer.projection, "none", "default layer projection correctly set");
+ t.ok( ((layer.chicken == 151) && (layer.foo == "bar")), "layer.options correctly set to Layer Object" );
+ t.ok( ((layer.options["chicken"] == 151) && (layer.options["foo"] == "bar")), "layer.options correctly backed up" );
+
+ t.ok( typeof layer.div == "object" , "layer.div is created" );
+ t.eq( layer.div.id, layer.id, "layer.div.id is correct" );
+
+ options.chicken = 552;
+
+ t.eq( layer.options["chicken"], 151 , "layer.options correctly made fresh copy" );
+
+ t.eq( layer.isBaseLayer, false, "Default layer is not base layer" );
+
+ layer = new OpenLayers.Layer('Test Layer');
+ t.ok( layer instanceof OpenLayers.Layer, "new OpenLayers.Layer returns object" );
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ t.ok( layer.projection == null, "default layer projection correctly set");
+ t.ok( layer.options instanceof Object, "layer.options correctly initialized as a non-null Object" );
+ }
+
+
+ function test_Layer_clone (t) {
+ t.plan( 7 );
+
+ var mapone = new OpenLayers.Map('map');
+ var options = { chicken: 151, foo: "bar", maxResolution: "auto", visibility: false };
+ var layer = new OpenLayers.Layer('Test Layer', options);
+ mapone.addLayer(layer);
+ layer.setVisibility(true);
+
+ // randomly assigned property
+ layer.chocolate = 5;
+
+ var clone = layer.clone();
+
+ t.ok( clone.map == null, "cloned layer has map property set to null")
+
+ var maptwo = new OpenLayers.Map('map2');
+ maptwo.addLayer(clone);
+
+ t.ok( clone instanceof OpenLayers.Layer, "new OpenLayers.Layer returns object" );
+ t.eq( clone.name, "Test Layer", "default clone.name is correct" );
+ t.ok( ((clone.options["chicken"] == 151) && (clone.options["foo"] == "bar")), "clone.options correctly set" );
+ t.eq(clone.chocolate, 5, "correctly copied randomly assigned property");
+
+ t.eq(clone.visibility, true, "visibility correctly cloned");
+
+ layer.addOptions({chicken:152});
+ t.eq(clone.options["chicken"], 151, "made a clean copy of options");
+
+ mapone.destroy();
+ maptwo.destroy();
+ }
+
+ function test_Layer_setName (t) {
+
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer('Test Layer');
+ layer.setName("chicken");
+
+ t.eq(layer.name, "chicken", "setName() works")
+
+ }
+
+ function test_Layer_addOptions (t) {
+
+ t.plan( 20 );
+
+ var map = new OpenLayers.Map("map", {allOverlays: true});
+ var options = { chicken: 151, foo: "bar" };
+ var layer = new OpenLayers.Layer('Test Layer', options);
+ map.addLayer(layer);
+
+ layer.addOptions({bark:55, chicken: 171});
+ t.eq(layer.bark, 55, "addOptions() assigns new option correctly to Layer");
+ t.eq(layer.options.bark, 55, "addOptions() adds new option correctly to backup");
+
+ t.eq(layer.chicken, 171, "addOptions() overwrites option correctly to Layer");
+ t.eq(layer.options.chicken, 171, "addOptions() overwrites option correctly to backup");
+
+ var log;
+ layer.initResolutions = function() {
+ log++;
+ };
+ log = 0;
+ layer.addOptions({bark: 56});
+ t.eq(log, 0, "addOptions doesn't call initResolutions when not given a resolution option");
+
+ log = 0;
+ layer.addOptions({scales: [1, 2]});
+ t.eq(log, 1, "addOptions calls initResolutions when given scales");
+
+ log = 0;
+ layer.addOptions({resolutions: [1, 2]});
+ t.eq(log, 1, "addOptions calls initResolutions when given resolutions");
+
+ log = 0;
+ layer.addOptions({minScale: 4});
+ t.eq(log, 1, "addOptions calls initResolutions when given minScale");
+
+ log = 0;
+ layer.addOptions({maxScale: 4});
+ t.eq(log, 1, "addOptions calls initResolutions when given maxScale");
+
+ log = 0;
+ layer.addOptions({minResolution: 4});
+ t.eq(log, 1, "addOptions calls initResolutions when given minResolution");
+
+ log = 0;
+ layer.addOptions({maxResolution: 4});
+ t.eq(log, 1, "addOptions calls initResolutions when given maxResolution");
+
+ log = 0;
+ layer.addOptions({numZoomLevels: 4});
+ t.eq(log, 1, "addOptions calls initResolutions when given numZoomLevels");
+
+ log = 0;
+ layer.addOptions({maxZoomLevel: 4});
+ t.eq(log, 1, "addOptions calls initResolutions when given maxZoomLevel");
+
+ log = 0;
+ layer.addOptions({projection: new OpenLayers.Projection("EPSG:900913")});
+ t.eq(log, 1, "addOptions calls initResolutions when given projection");
+
+ log = 0;
+ layer.addOptions({units: "m"});
+ t.eq(log, 1, "addOptions calls initResolutions when given units");
+
+ log = 0;
+ layer.addOptions({minExtent: new OpenLayers.Bounds(0, 0, 0, 0)});
+ t.eq(log, 1, "addOptions calls initResolutions when given minExtent");
+
+ log = 0;
+ layer.addOptions({maxExtent: new OpenLayers.Bounds(0, 0, 0, 0)});
+ t.eq(log, 1, "addOptions calls initResolutions when given maxExtent");
+
+ layer.projection = null;
+ layer.addOptions({projection: "EPSG:900913"});
+ t.ok(layer.projection instanceof OpenLayers.Projection,
+ "addOptions creates a Projection object when given a projection string");
+
+ log = null;
+ // adding a 2nd layer to see if it gets reinitialized properly
+ var layer2 = new OpenLayers.Layer(null, {
+ moveTo: function(bounds) {
+ log = bounds;
+ }
+ });
+ map.addLayer(layer2);
+ layer.addOptions({maxResolution: 0.00034332275390625}, true);
+ t.eq(log.toBBOX(), map.getExtent().toBBOX(), "when reinitialize is set to true, changing base layer's resolution property reinitializes all layers.");
+
+ map.removeLayer(layer);
+ log = 0;
+ layer.addOptions({minExtent: new OpenLayers.Bounds(0, 0, 0, 0)});
+ t.eq(log, 0, "addOptions doesn't call initResolutions when layer is not in map");
+ }
+
+ function test_addOptionsScale(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.WMS();
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ layer.addOptions({maxResolution: 0.5, numZoomLevels: 15});
+ t.eq(layer.alwaysInRange, false, "alwaysInRange should not be true anymore");
+ }
+
+ function test_Layer_StandardOptionsAccessors (t) {
+
+ t.plan( 4 );
+
+ var projection = "EPSG:4326";
+ var maxExtent = new OpenLayers.Bounds(50,50,100,100);
+ var maxResolution = 1.5726;
+ var numZoomLevels = 11;
+
+ var options = { projection: projection,
+ maxExtent: maxExtent,
+ maxResolution: maxResolution,
+ numZoomLevels: numZoomLevels
+ };
+
+ var layer = new OpenLayers.Layer('Test Layer', options);
+
+ t.eq(layer.projection.getCode(), projection, "projection set correctly");
+ t.ok(layer.maxExtent.equals(maxExtent), "maxExtent set correctly");
+ t.eq(layer.maxResolution, maxResolution, "maxResolution set correctly");
+ t.eq(layer.numZoomLevels, numZoomLevels, "numZoomLevels set correctly");
+ }
+
+ function test_maxExtent(t) {
+ t.plan(5);
+
+ var layer = new OpenLayers.Layer(
+ null, {maxExtent: [-180, 0, 0, 90]}
+ );
+
+ t.ok(layer.maxExtent instanceof OpenLayers.Bounds, "(array) bounds instance");
+ t.eq(layer.maxExtent.left, -180, "(array) bounds left");
+ t.eq(layer.maxExtent.bottom, 0, "(array) bounds left");
+ t.eq(layer.maxExtent.right, 0, "(array) bounds right");
+ t.eq(layer.maxExtent.top, 90, "(array) bounds top");
+
+ layer.destroy();
+ }
+
+ function test_minExtent(t) {
+ t.plan(5);
+
+ var layer = new OpenLayers.Layer(
+ null, {minExtent: [-180, 0, 0, 90]}
+ );
+
+ t.ok(layer.minExtent instanceof OpenLayers.Bounds, "(array) bounds instance");
+ t.eq(layer.minExtent.left, -180, "(array) bounds left");
+ t.eq(layer.minExtent.bottom, 0, "(array) bounds left");
+ t.eq(layer.minExtent.right, 0, "(array) bounds right");
+ t.eq(layer.minExtent.top, 90, "(array) bounds top");
+
+ layer.destroy();
+ }
+
+
+ function test_eventListeners(t) {
+ t.plan(1);
+
+ var method = OpenLayers.Events.prototype.on;
+ // test that events.on is called at layer construction
+ var options = {
+ eventListeners: {foo: "bar"}
+ };
+ OpenLayers.Events.prototype.on = function(obj) {
+ t.eq(obj, options.eventListeners, "events.on called with eventListeners");
+ }
+ var layer = new OpenLayers.Layer("test", options);
+ OpenLayers.Events.prototype.on = method;
+ layer.destroy();
+
+ // if events.on is called again, this will fail due to an extra test
+ // test layer without eventListeners
+ OpenLayers.Events.prototype.on = function(obj) {
+ t.fail("events.on called without eventListeners");
+ }
+ var layer2 = new OpenLayers.Layer("test");
+ OpenLayers.Events.prototype.on = method;
+ layer2.destroy();
+ }
+
+ function test_initResolutions_alwaysInRange(t) {
+ t.plan(3);
+
+ var map, layer;
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer("test", {maxResolution: 80, minResolution: 10});
+ map.addLayer(layer);
+ t.eq(layer.alwaysInRange, false,
+ "alwaysInRange set to false due to passed options");
+ map.destroy();
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer("test", {projection: "unknown"});
+ map.addLayer(layer);
+ t.eq(layer.alwaysInRange, true,
+ "alwaysInRange true if unknown projection is set.");
+ map.destroy();
+
+ map = new OpenLayers.Map("map");
+ OpenLayers.Layer.prototype.alwaysInRange = false;
+ layer = new OpenLayers.Layer("test", {'projection': 'EPSG:4326'});
+ map.addLayer(layer);
+ t.eq(layer.alwaysInRange, false,
+ "alwaysInRange true if overridden on prototype.");
+ OpenLayers.Layer.prototype.alwaysInRange = null;
+ map.destroy();
+ }
+
+ function test_initResolutions_resolutions(t) {
+
+ function initResolutionsTest(
+ id, mapOptions, layerOptions,
+ expectedResolutions, expectedMinResolution, expectedMaxResolution) {
+
+ // setup
+ var map = new OpenLayers.Map("map", mapOptions);
+ var layer = new OpenLayers.Layer(null, layerOptions);
+ map.addLayer(layer);
+
+ // make resolution assertions
+ t.eq(
+ layer.resolutions.length, expectedResolutions.length,
+ id + ": correct resolutions length"
+ );
+ // allow for floating point imprecision
+ var got, exp, same = true;
+ for (var i=0; i<expectedResolutions.length; ++i) {
+ got = layer.resolutions[i];
+ exp = expectedResolutions[i];
+ if (got.toFixed(10) !== exp.toFixed(10)) {
+ t.fail(id + ": bad resolutions - index " + i + " got " + got + " but expected " + exp);
+ same = false;
+ break;
+ }
+ }
+ if (same) {
+ t.ok(true, id + ": correct resolutions");
+ }
+ t.eq(
+ layer.maxResolution, expectedMaxResolution,
+ id + ": maxResolution set"
+ );
+ t.eq(
+ layer.minResolution, expectedMinResolution,
+ id + ": minResolution set"
+ );
+
+ // teardown
+ map.destroy();
+ }
+
+ // each case is an array of id, map options, layer options, expected resolutions,
+ // expected min resolution, and expected max resolution
+ var cases = [[
+
+ /*
+ * Batch 1: map defaults and sensible layer options
+ */
+
+ "1.0", null, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "1.1", null, {resolutions: [400, 200, 100], minResolution: 150, maxResolution: 300},
+ [400, 200, 100], 150, 300
+ ], [
+ "1.2", null, {maxResolution: 4000, numZoomLevels: 3},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "1.3", null, {maxResolution: 4000, maxZoomLevel: 2},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "1.4", null, {minResolution: 40, numZoomLevels: 3},
+ [160, 80, 40], 40, 160
+ ], [
+ "1.5", null, {minResolution: 40, maxZoomLevel: 2},
+ [160, 80, 40], 40, 160
+ ], [
+ "1.6", null, {minResolution: 10, maxResolution: 40},
+ [40, 20, 10], 10, 40
+ ], [
+ "1.7", null, {minResolution: 10, maxResolution: 40, numZoomLevels: 3},
+ [40, 20, 10], 10, 40
+ ], [
+ "1.8", null, {minResolution: 10, maxResolution: 40, maxZoomLevel: 2},
+ [40, 20, 10], 10, 40
+ ], [
+ "1.9", null, {scales: [400000, 200000, 100000]},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.10", null, {scales: [400000, 200000, 100000], minScale: 400000, maxScale: 100000},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.11", null, {minScale: 400000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.12", null, {minScale: 400000, maxZoomLevel: 2},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.13", null, {maxScale: 100000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.14", null, {maxScale: 100000, maxZoomLevel: 2},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.15", null, {maxScale: 100000, minScale: 400000},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.16", null, {maxScale: 100000, minScale: 400000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.17", null, {maxScale: 100000, minScale: 400000, maxZoomLevel: 2},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "1.18", null, {scales: [400000, 200000, 100000], units: "m"},
+ [141.11139333389778, 70.55569666694889, 35.277848333474445], 35.277848333474445, 141.11139333389778
+ ], [
+ "1.19", null, {minScale: 400000, numZoomLevels: 3, units: "m"},
+ [141.11139333389778, 70.55569666694889, 35.277848333474445], 35.277848333474445, 141.11139333389778
+ ], [
+ "1.20", null, {maxScale: 100000, numZoomLevels: 3, units: "m"},
+ [141.11139333389778, 70.55569666694889, 35.277848333474445], 35.277848333474445, 141.11139333389778
+ ], [
+ "1.21", null, {numZoomLevels: 2}, // maxResolution calculated based on the projection's maxExtent here
+ [1.40625, 0.703125], 0.703125, 1.40625
+ ], [
+
+ /*
+ * Batch 2: custom map options map and sensible layer options
+ */
+
+ /*
+ * Batch 2.1: resolutions set in the layer options
+ */
+ "2.1.0", {resolutions: [300, 150]}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "2.1.1", {numZoomLevels: 4}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "2.1.2", {maxResolution: 300}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "2.1.3", {minResolution: 300}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "2.1.4", {scales: [4, 2, 1]}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "2.1.5", {minScale: 4}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ "2.1.6", {maxScale: 4}, {resolutions: [400, 200, 100]},
+ [400, 200, 100], 100, 400
+ ], [
+ /*
+ * Batch 2.2: minResolution and maxResolution set in the layer options
+ */
+ "2.2.0", {resolutions: [80, 40, 20, 10]}, {minResolution: 12, maxResolution: 48, numZoomLevels: 0},
+ [80, 40, 20, 10], 12, 48
+ ], [
+ "2.2.1", {resolutions: [80, 40, 20, 10]}, {minResolution: 12, maxResolution: 48, numZoomLevels: -1},
+ [80, 40, 20, 10], 12, 48
+ ], [
+ "2.2.2", {resolutions: [80, 40, 20, 10]}, {minResolution: 12, maxResolution: 48, numZoomLevels: null},
+ [80, 40, 20, 10], 12, 48
+ ], [
+ "2.2.3", {resolutions: [80, 40, 20, 10]}, {minResolution: 12, maxResolution: 48, numZoomLevels: undefined},
+ [48, 24, 12], 12, 48
+ ], [
+ "2.2.4", {resolutions: [80, 40, 20, 10]}, {minResolution: 12, maxResolution: 48, numZoomLevels: 3},
+ [48, 24, 12], 12, 48
+ ], [
+ "2.2.5", {resolutions: [80, 40, 20, 10]}, {minResolution: 12, maxResolution: 48},
+ [48, 24, 12], 12, 48
+ ], [
+ /*
+ * Batch 2.3: maxResolution set in the layer options
+ */
+ "2.3.0", {resolutions: [300, 150]}, {maxResolution: 4000, numZoomLevels: 3},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "2.3.1", {numZoomLevels: 2}, {maxResolution: 4000, numZoomLevels: 3},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "2.3.2", {maxResolution: 50}, {maxResolution: 4000, numZoomLevels: 3},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "2.3.3", {scales: [4, 2, 1]}, {maxResolution: 4000, numZoomLevels: 3},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "2.3.4", {minScale: 4}, {maxResolution: 4000, numZoomLevels: 3},
+ [4000, 2000, 1000], 1000, 4000
+ ], [
+ "2.3.5", {resolutions: [300, 150]}, {maxResolution: 250},
+ [300, 150], 150, 250
+ ], [
+ /*
+ * Batch 2.4: minResolution set in the layer options
+ */
+ "2.4.0", {resolutions: [300, 150]}, {minResolution: 40, numZoomLevels: 3},
+ [160, 80, 40], 40, 160
+ ], [
+ "2.4.1", {numZoomLevels: 2}, {minResolution: 40, numZoomLevels: 3},
+ [160, 80, 40], 40, 160
+ ], [
+ "2.4.2", {minResolution: 50}, {minResolution: 40, numZoomLevels: 3},
+ [160, 80, 40], 40, 160
+ ], [
+ "2.4.3", {scales: [4, 2, 1]}, {minResolution: 40, numZoomLevels: 3},
+ [160, 80, 40], 40, 160
+ ], [
+ "2.4.4", {maxScale: 1}, {minResolution: 40, numZoomLevels: 3},
+ [160, 80, 40], 40, 160
+ ], [
+ "2.4.5", {resolutions: [300, 150]}, {minResolution: 250},
+ [300, 150], 250, 300
+ ], [
+ /*
+ * Batch 2.5: scales set in the layer options
+ */
+ "2.5.0", {resolutions: [4, 2, 1]}, {scales: [400000, 200000, 100000]},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.5.1", {numZoomLevels: 2}, {scales: [400000, 200000, 100000]},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.5.2", {maxResolution: 4}, {scales: [400000, 200000, 100000]},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.5.3", {minResolution: 1}, {scales: [400000, 200000, 100000]},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.5.4", {units: "m"}, {scales: [400000, 200000, 100000]},
+ [141.11139333389778, 70.55569666694889, 35.277848333474445], 35.277848333474445, 141.11139333389778
+ ], [
+ /*
+ * Batch 2.6: minScale set in the layer options
+ */
+ "2.6.0", {resolutions: [4, 2, 1]}, {minScale: 400000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.6.1", {numZoomLevels: 2}, {minScale: 400000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.6.2", {maxResolution: 4}, {minScale: 400000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.6.3", {scales: [400000, 200000, 100000]}, {minScale: 200000},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0006349563376084181
+ ], [
+ /*
+ * Batch 2.7: maxScale set in the layer options
+ */
+ "2.7.0", {resolutions: [4, 2, 1]}, {maxScale: 100000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.7.1", {numZoomLevels: 2}, {maxScale: 100000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.7.2", {minResolution: 1}, {maxScale: 100000, numZoomLevels: 3},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.00031747816880420905, 0.0012699126752168362
+ ], [
+ "2.7.3", {scales: [400000, 200000, 100000]}, {maxScale: 200000},
+ [0.0012699126752168362, 0.0006349563376084181, 0.00031747816880420905], 0.0006349563376084181, 0.0012699126752168362
+ ], [
+ /*
+ * Batch 2.8: numZoomLevels set in the layer options
+ */
+ "2.8.0", {maxResolution: 80}, {numZoomLevels: 4}, // maxResolution calculated based on the projection's maxExtent here
+ [1.40625, 0.703125, 0.3515625, 0.17578125], 0.17578125, 1.40625
+ ], [
+ "2.8.1", {maxResolution: 80, numZoomLevels: 4}, {numZoomLevels: null},
+ [80, 40, 20, 10], 10, 80
+ ], [
+ "2.8.2", {maxResolution: 80, numZoomLevels: 4}, {numZoomLevels: undefined},
+ [80, 40, 20, 10], 10, 80
+ ]];
+
+ // run all cases (4 tests each)
+ var i, num = cases.length, c;
+ t.plan(num * 4);
+ for (i=0; i<num; ++i) {
+ c = cases[i];
+ initResolutionsTest(c[0], c[1], c[2], c[3], c[4], c[5]);
+ }
+
+ }
+
+ function test_Layer_visibility(t) {
+
+ t.plan(7);
+
+ var layer = new OpenLayers.Layer('Test Layer');
+
+ t.eq(layer.getVisibility(), true, "default for layer creation is visible");
+
+ layer.setVisibility(false);
+ t.eq(layer.getVisibility(), false, "setVisibility false works");
+
+ layer.setVisibility(true);
+ t.eq(layer.getVisibility(), true, "setVisibility true works");
+
+ // Need a map in order to have moveTo called.
+ // Tests added for #654.
+ var layer = new OpenLayers.Layer.WMS('Test Layer','http://example.com');
+ var m = new OpenLayers.Map('map');
+ m.addLayer(layer);
+ m.zoomToMaxExtent();
+
+ layermoved = false;
+ layer.moveTo = function() { layermoved = true; }
+
+ layer.events.register('visibilitychanged', t, function() {
+ this.ok(true, "Visibility changed calls layer event.");
+ });
+
+ layer.setVisibility(false);
+ t.eq(layermoved, false, "Layer didn't move when calling setvis false");
+
+ layer.setVisibility(true);
+ t.eq(layermoved, true, "Layer moved when calling setvis true.");
+
+ }
+
+
+ function test_Layer_getZoomForResolution(t) {
+
+ t.plan(12);
+
+ var layer = new OpenLayers.Layer('Test Layer');
+ layer.map = {};
+
+ //make some dummy resolutions
+ layer.resolutions = [128, 64, 32, 16, 8, 4, 2];
+
+ t.eq(layer.getZoomForResolution(200), 0, "zoom all the way out");
+ t.eq(layer.getZoomForResolution(25), 2, "zoom in middle");
+ t.eq(layer.getZoomForResolution(3), 5, "zoom allmost all the way in");
+ t.eq(layer.getZoomForResolution(1), 6, "zoom all the way in");
+
+ t.eq(layer.getZoomForResolution(65), 0, "smallest containing res");
+ t.eq(layer.getZoomForResolution(63), 1, "smallest containing res");
+
+ t.eq(layer.getZoomForResolution(65, true), 1, "closest res");
+ t.eq(layer.getZoomForResolution(63, true), 1, "closest res");
+
+ layer.map.fractionalZoom = true;
+ t.eq(layer.getZoomForResolution(64), 1,
+ "(fractionalZoom) correct zoom for res in array");
+ t.eq(layer.getZoomForResolution(48).toPrecision(6), (1.5).toPrecision(6),
+ "(fractionalZoom) linear scaling for res between entries");
+ t.eq(layer.getZoomForResolution(200).toPrecision(6), (0).toPrecision(6),
+ "(fractionalZoom) doesn't return zoom below zero");
+ t.eq(layer.getZoomForResolution(1).toPrecision(6), (layer.resolutions.length - 1).toPrecision(6),
+ "(fractionalZoom) doesn't return zoom above highest index");
+ }
+
+ function test_Layer_redraw(t) {
+ t.plan(11)
+
+ var name = 'Test Layer';
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/jpeg'};
+
+ var layer = new OpenLayers.Layer.WMS(name, url, params);
+
+ t.ok(!layer.redraw(),
+ "redraw on an orphan layer returns false");
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ t.ok(!layer.redraw(),
+ "redraw returns false if map does not yet have a center");
+ map.zoomToMaxExtent();
+
+ t.ok(layer.redraw(),
+ "redraw returns true after map has a center");
+
+ layer.setVisibility(false);
+ t.ok(!layer.redraw(),
+ "redraw returns false if a layer is not visible");
+
+ layer.setVisibility(true);
+ t.ok(layer.redraw(),
+ "redraw returns true even if extent has not changed");
+
+ var log = {};
+ var onMoveend = function(e) {
+ log.event = e;
+ };
+ layer.events.on({"moveend": onMoveend});
+ layer.redraw();
+ layer.events.un({"moveend": onMoveend});
+ // test that the moveend event was triggered
+ t.ok(log.event, "an event was logged");
+ t.eq(log.event.type, "moveend", "moveend was triggered");
+ t.eq(log.event.zoomChanged, true, "event says zoomChanged true - poor name");
+
+ layer.moveTo = function(bounds, zoomChanged, dragging) {
+ var extent = layer.map.getExtent();
+ t.ok(bounds.equals(extent),
+ "redraw calls moveTo with the map extent");
+ t.ok(zoomChanged,
+ "redraw calls moveTo with zoomChanged true");
+ t.ok(!dragging,
+ "redraw calls moveTo with dragging false");
+ }
+ layer.redraw();
+ }
+
+ function test_layer_setIsBaseLayer(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer();
+
+ map.events.register("changebaselayer", t, function() {
+ this.ok(true, "setIsBaseLayer() trig changebaselayer event.")
+ });
+
+ map.addLayer(layer);
+ layer.setIsBaseLayer(true);
+ t.ok(layer.isBaseLayer, "setIsBaseLayer() change isBaseLayer property.");
+ }
+
+ function test_layer_setTileSize(t) {
+ t.plan(4);
+
+ layer = new OpenLayers.Layer();
+
+ g_MapTileSize = new OpenLayers.Size(25,67);
+ layer.map = {
+ getTileSize: function() {
+ return g_MapTileSize;
+ }
+ };
+
+ var layerTileSize = new OpenLayers.Size(1,1);
+
+ //TILE SIZE
+ layer.tileSize = layerTileSize;
+
+ //parameter
+ var size = new OpenLayers.Size(2,2);
+ layer.setTileSize(size);
+ t.ok(layer.tileSize.equals(size), "size paramater set correctly to layer's tile size");
+
+ //set on layer
+ layer.tileSize = layerTileSize;
+ layer.setTileSize();
+ t.ok(layer.tileSize.equals(layerTileSize), "layer's tileSize property preserved if no parameter sent in");
+
+ //take it from map
+ layer.tileSize = null;
+ layer.setTileSize();
+ t.ok(layer.tileSize.equals(g_MapTileSize), "layer's tileSize property is null and so correctly taken from the map");
+
+
+
+ //GUTTERS
+ layer.gutter = 15;
+ size = new OpenLayers.Size(10,100);
+ layer.setTileSize(size);
+
+ var desiredImageSize = new OpenLayers.Size(40, 130);
+
+ t.ok(layer.imageSize.equals(desiredImageSize), "image size correctly calculated");
+ }
+
+ function test_Layer_getResolution(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer("test");
+ layer.map = {
+ getZoom: function() {return "foo";}
+ };
+ layer.getResolutionForZoom = function(zoom) {
+ t.eq(zoom, "foo", "getResolution calls getResolutionForZoom");
+ }
+ layer.getResolution();
+ layer.map = null;
+ layer.destroy();
+ }
+
+ function test_Layer_getResolutionForZoom(t) {
+ t.plan(8);
+ var layer = new OpenLayers.Layer("test");
+ layer.map = {fractionalZoom: false};
+ layer.resolutions = ["zero", "one", "two"];
+ t.eq(layer.getResolutionForZoom(0), "zero",
+ "(fractionalZoom false) returns resolution for given index");
+ t.eq(layer.getResolutionForZoom(0.9), "one",
+ "(fractionalZoom false) returns resolution for float index");
+
+ layer.resolutions = [2, 4, 6, 8];
+ layer.map.fractionalZoom = true;
+ t.eq(layer.getResolutionForZoom(1).toPrecision(6), (4).toPrecision(6),
+ "(fractionalZoom true) returns resolution for integer zoom");
+
+ t.eq(layer.getResolutionForZoom(1.3).toPrecision(6), (4.6).toPrecision(6),
+ "(fractionalZoom true) for zoom 1.3 should be 4.6");
+
+ t.eq(layer.getResolutionForZoom(1.6).toPrecision(6), (5.2).toPrecision(6),
+ "(fractionalZoom true) for zoom 1.6 should be 5.2");
+
+ t.eq(layer.getResolutionForZoom(1.8).toPrecision(6), (5.6).toPrecision(6),
+ "(fractionalZoom true) for zoom 1.8 should be 5.6");
+
+ t.eq(layer.getResolutionForZoom(1.5).toPrecision(6), (5).toPrecision(6),
+ "(fractionalZoom true) returns resolution for float zoom");
+ t.eq(layer.getResolutionForZoom(3.5).toPrecision(6), (8).toPrecision(6),
+ "(fractionalZoom true) returns resolution for zoom beyond res length - 1");
+
+ }
+
+ function test_afterAdd(t) {
+
+ t.plan(4);
+
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {
+ isBaseLayer: true,
+ eventListeners: {
+ "added": function(evt) {
+ log.push(evt);
+ }
+ }
+ });
+ var hasBase = false;
+ layer.afterAdd = function() {
+ hasBase = !!(layer.map && layer.map.baseLayer);
+ }
+ map.addLayer(layer);
+ t.eq(hasBase, true, "when afterAdd is called, map has a base layer");
+ t.eq(log.length, 1, "added event triggered");
+ t.eq(log[0].map.id, map.id, "added listener argument with correct map");
+ t.eq(log[0].layer.id, layer.id, "added listener argument with correct layer");
+
+ }
+
+ function test_setOpacity(t) {
+ t.plan(5);
+
+ var map, layer, log;
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer("");
+ map.addLayer(layer);
+
+ log = [];
+ map.events.register('changelayer', t, function(event) {
+ log.push({layer: event.layer, property: event.property});
+ });
+ layer.setOpacity(0.42);
+ t.eq(layer.opacity, 0.42,
+ "setOpacity() set layer.opacity to correct value");
+ t.eq(log.length, 1,
+ "setOpacity() triggers changelayer once");
+ t.ok(log[0].layer == layer,
+ "changelayer listener called with expected layer");
+ t.eq(log[0].property, "opacity",
+ "changelayer listener called with expected property");
+
+ // This call must not trig the event because the opacity value is the same.
+ log = [];
+ layer.setOpacity(0.42);
+ t.eq(log.length, 0,
+ "setOpacity() does not trigger changelayer if the opacity value is the same");
+ }
+
+
+/******
+ *
+ *
+ * HERE IS WHERE SOME TESTS SHOULD BE PUT TO CHECK ON THE LONLAT-PX TRANSLATION
+ * FUNCTIONS AND RESOLUTION AND GETEXTENT GETZOOMLEVEL, ETC
+ *
+ *
+ */
+
+
+ function test_Layer_destroy (t) {
+ t.plan( 8 );
+
+ var log = [];
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer('Test Layer', {
+ eventListeners: {
+ "removed": function(evt) {
+ log.push(evt);
+ }
+ }
+ });
+
+ map.addLayer(layer);
+
+ layer.destroy();
+
+ t.eq( layer.name, null, "layer.name is null after destroy" );
+ t.eq( layer.div, null, "layer.div is null after destroy" );
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ t.eq( layer.options, null, "layer.options is null after destroy" );
+
+ t.eq(map.layers.length, 0, "layer removed from map");
+ t.eq(log.length, 1, "removed event triggered");
+ t.eq(log[0].map.id, map.id, "removed listener argument with correct map");
+ t.eq(log[0].layer.id, layer.id, "removed listener argument with correct layer");
+
+ map.destroy();
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:500px"></div>
+ <div id="map2" style="width:100px;height:100px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/ArcGIS93Rest.html b/misc/openlayers/tests/Layer/ArcGIS93Rest.html
new file mode 100644
index 0000000..ddca6ac
--- /dev/null
+++ b/misc/openlayers/tests/Layer/ArcGIS93Rest.html
@@ -0,0 +1,324 @@
+<html>
+<head>
+ <script type="text/javascript">var oldAlert = window.alert, gMess; window.alert = function(message) {gMess = message; return true;};</script>
+ <script type="text/javascript">window.alert = oldAlert;</script>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Specialty/ESRI_StateCityHighway_USA/MapServer/export";
+ var params = {layers: "show:0,2"};
+
+ function test_Layer_AGS93_constructor (t) {
+ var params = {layers: "show:0,2"};
+ t.plan( 14 );
+
+ var trans_format = "png";
+ if (OpenLayers.Util.alphaHack()) { trans_format = "gif"; }
+
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.ok( layer instanceof OpenLayers.Layer.ArcGIS93Rest, "new OpenLayers.Layer.ArcGIS93Rest returns object" );
+ t.eq( layer.url, url, "layer.url is correct (HTTPRequest inited)" );
+ t.eq( layer.params.LAYERS, "show:0,2", "params passed in correctly uppercased" );
+
+ t.eq( layer.params.FORMAT, "png", "default params correclty uppercased and copied");
+
+ t.eq(layer.isBaseLayer, true, "no transparency setting, wms is baselayer");
+
+ params.format = 'jpg';
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.eq( layer.params.FORMAT, "jpg", "default params correclty uppercased and overridden");
+
+ params.TRANSPARENT = "true";
+ var layer2 = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.eq(layer2.isBaseLayer, false, "transparency == 'true', wms is not baselayer");
+
+ params.TRANSPARENT = "TRUE";
+ var layer3 = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.eq(layer3.isBaseLayer, false, "transparency == 'TRUE', wms is not baselayer");
+ t.eq(layer3.params.FORMAT, trans_format, "transparent = TRUE causes non-image/jpeg format");
+
+ params.TRANSPARENT = "TRuE";
+ var layer4 = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.eq(layer4.isBaseLayer, false, "transparency == 'TRuE', wms is not baselayer");
+ t.eq(layer4.params.FORMAT, trans_format, "transparent = TRuE causes non-image/jpeg format");
+
+ params.TRANSPARENT = true;
+ var layer5 = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.eq(layer5.isBaseLayer, false, "transparency == true, wms is not baselayer");
+ t.eq(layer5.params.FORMAT, trans_format, "transparent = true causes non-image/jpeg format");
+
+ params.TRANSPARENT = false;
+ var layer6 = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.eq(layer6.isBaseLayer, true, "transparency == false, wms is baselayer");
+ }
+
+ function test_Layer_AGS93_addtile (t) {
+ var params = {layers: "show:0,2"};
+ t.plan( 6 );
+
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ map.addLayer(layer);
+ var pixel = new OpenLayers.Pixel(5,6);
+ var tile = layer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
+ tile.draw();
+
+ var img = tile.imgDiv;
+ var tParams = OpenLayers.Util.extend({},
+ OpenLayers.Util.upperCaseObject(params));
+ tParams = OpenLayers.Util.extend(tParams, {
+ FORMAT: "png", BBOX: "1,2,3,4", SIZE: "256,256", F: "image", BBOXSR: "4326", IMAGESR: "4326"
+ });
+ t.eq( tile.url,
+ url + "?" + OpenLayers.Util.getParameterString(tParams),
+ "image src is created correctly via addtile" );
+ t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+ t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
+
+ var firstChild = layer.div.firstChild;
+ t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
+ t.ok( firstChild == img, "div first child is correct image object" );
+ t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
+ map.destroy();
+ }
+
+ function test_Layer_AGS93_inittiles (t) {
+ var params = {layers: "show:0,2"};
+ t.plan( 2 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params, {buffer: 2});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq( layer.grid.length, 8, "Grid rows is correct." );
+ t.eq( layer.grid[0].length, 7, "Grid cols is correct." );
+ map.destroy();
+ }
+
+
+ function test_Layer_AGS93_clone (t) {
+ var params = {layers: "show:0,2"};
+ t.plan(4);
+
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ var clone = layer.clone();
+
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ layer.tileSize.w += 40;
+
+ t.eq( clone.tileSize.w, 500, "changing layer.tileSize does not change clone.tileSize -- a fresh copy was made, not just copied reference");
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ layer.grid = null;
+ map.destroy();
+ }
+
+ function test_Layer_AGS93_isBaseLayer(t) {
+ var params = {layers: "show:0,2"};
+ t.plan(3);
+
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ t.ok( layer.isBaseLayer, "baselayer is true by default");
+
+ var newParams = OpenLayers.Util.extend({}, params);
+ newParams.transparent = "true";
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, newParams);
+ t.ok( !layer.isBaseLayer, "baselayer is false when transparent is set to true");
+
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params, {isBaseLayer: false});
+ t.ok( !layer.isBaseLayer, "baselayer is false when option is set to false" );
+ }
+
+ function test_Layer_AGS93_mergeNewParams (t) {
+ var params = {layers: "show:0,2"};
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ layer.redraw = function() {
+ t.ok(true, "layer is redrawn after new params merged");
+ }
+
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.LAYERS, "sooper", "mergeNewParams() overwrites well");
+ t.eq( layer.params.CHICKPEAS, "png", "mergeNewParams() adds well");
+
+ newParams.CHICKPEAS = 151;
+
+ t.eq( layer.params.CHICKPEAS, "png", "mergeNewParams() makes clean copy of hashtable");
+ map.destroy();
+ }
+
+ function test_Layer_AGS93_getFullRequestString (t) {
+ var params = {layers: "show:0,2"};
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+ map.projection = "xx";
+ tParams = { layers: 'show:0,2',
+ format: 'png'};
+ var tLayer = new OpenLayers.Layer.ArcGIS93Rest(name, url, tParams);
+ map.addLayer(tLayer);
+ str = tLayer.getFullRequestString();
+ var tParams = {
+ LAYERS: "show:0,2", FORMAT: "png"
+ };
+ t.eq(str,
+ url + "?" + OpenLayers.Util.getParameterString(tParams),
+ "getFullRequestString() adds SRS value");
+ map.destroy();
+
+ }
+
+ function test_Layer_AGS93_noGutters (t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.ArcGIS93Rest("no gutter layer", url, params, {gutter: 0});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ var request = layer.getURL(tile.bounds);
+ var args = OpenLayers.Util.getParameters(request);
+ t.eq(parseInt(args['SIZE'][0]),
+ tile.size.w,
+ "layer without gutter requests images that are as wide as the tile");
+ t.eq(parseInt(args['SIZE'][1]),
+ tile.size.h,
+ "layer without gutter requests images that are as tall as the tile");
+
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_Layer_AGS93_gutters (t) {
+ var params = {layers: "show:0,2"};
+ t.plan(2);
+ var gutter = 15;
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.ArcGIS93Rest("gutter layer", url, params, {gutter: gutter});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ var request = layer.getURL(tile.bounds);
+ var args = OpenLayers.Util.getParameters(request);
+ t.eq(parseInt(args['SIZE'][0]),
+ tile.size.w + (2 * gutter),
+ "layer with gutter requests images that are wider by twice the gutter");
+ t.eq(parseInt(args['SIZE'][1]),
+ tile.size.h + (2 * gutter),
+ "layer with gutter requests images that are taller by twice the gutter");
+
+ layer.destroy();
+ map.destroy();
+
+ }
+
+ function test_Layer_AGS93_destroy (t) {
+
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ // checks to make sure superclass (grid) destroy() was called
+
+ t.ok( layer.grid == null, "grid set to null");
+ }
+
+ function test_Layer_ADG93_Filter(t) {
+ var params = {layers: "show:0,2"};
+ t.plan( 9 );
+
+ layer = new OpenLayers.Layer.ArcGIS93Rest(name, url, params);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ map.addLayer(layer);
+ var pixel = new OpenLayers.Pixel(5,6);
+ var tile = layer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
+ // Set up basic params.
+ var tParams = OpenLayers.Util.extend({}, OpenLayers.Util.upperCaseObject(params));
+ tParams = OpenLayers.Util.extend(tParams, {
+ FORMAT: "png", BBOX: "1,2,3,4", SIZE: "256,256", F: "image", BBOXSR: "4326", IMAGESR: "4326"
+ });
+
+ // We need to actually set the "correct" url on a dom element, because doing so encodes things not encoded by getParameterString.
+ var encodingHack = document.createElement("img");
+
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src no filter" );
+
+ layer.setLayerFilter('1', "MR_TOAD = 'FLYING'");
+ tParams["LAYERDEFS"] = "1:MR_TOAD = 'FLYING';";
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src one filter" );
+
+ layer.setLayerFilter('1', "MR_TOAD = 'NOT FLYING'");
+ tParams["LAYERDEFS"] = "1:MR_TOAD = 'NOT FLYING';";
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src change one filter" );
+
+ layer.setLayerFilter('2', "true = false");
+ tParams["LAYERDEFS"] = "1:MR_TOAD = 'NOT FLYING';2:true = false;";
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src two filters" );
+
+ layer.setLayerFilter('99', "some_col > 5");
+ tParams["LAYERDEFS"] = "1:MR_TOAD = 'NOT FLYING';2:true = false;99:some_col > 5;";
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src three filters" );
+
+ layer.clearLayerFilter('2');
+ tParams["LAYERDEFS"] = "1:MR_TOAD = 'NOT FLYING';99:some_col > 5;";
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src removed middle filter" );
+
+ layer.clearLayerFilter('2');
+ tParams["LAYERDEFS"] = "1:MR_TOAD = 'NOT FLYING';99:some_col > 5;";
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src removed missing filter (no change)" );
+
+ layer.clearLayerFilter();
+ delete tParams["LAYERDEFS"];
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src removed all filters" );
+
+ layer.clearLayerFilter();
+ tile.draw();
+ t.eq( tile.url, url + "?" + OpenLayers.Util.getParameterString(tParams), "image src removed all (no) filters" );
+ }
+
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/ArcGISCache.html b/misc/openlayers/tests/Layer/ArcGISCache.html
new file mode 100644
index 0000000..b5ed5d5
--- /dev/null
+++ b/misc/openlayers/tests/Layer/ArcGISCache.html
@@ -0,0 +1,256 @@
+<html>
+<head>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script src="../../lib/OpenLayers/Layer/ArcGISCache.js" type="text/javascript"></script>
+ <script src="ArcGISCache.json" type="text/javascript"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer";
+ var options = { };
+
+ function test_Layer_ARCGISCACHE_constructor (t) {
+ t.plan( 1 );
+
+ var layer = new OpenLayers.Layer.ArcGISCache(name, url, options);
+ t.ok( layer instanceof OpenLayers.Layer.ArcGISCache, "returns OpenLayers.Layer.ArcGISCache object" );
+ }
+
+ function test_Layer_ARCGISCACHE_autoConfigure (t) {
+ t.plan( 5 );
+ var layerInfo = capabilitiesObject;
+
+ //initialize the layer using the JSON object from an arcgis server
+ //SEE: ArcGISCache.json
+ var layer = new OpenLayers.Layer.ArcGISCache(name, url, {
+ layerInfo: layerInfo
+ });
+ t.ok( layer instanceof OpenLayers.Layer.ArcGISCache, "returns OpenLayers.Layer.ArcGISCache object" );
+ t.ok( layer.projection = 'EPSG:' + layerInfo.spatialReference.wkid, "projection is set correctly");
+ t.ok( layer.units = 'm', "map units are set correctly");
+ t.ok( layer.resolutions && layer.resolutions.length == 20, "resolutions are initialized from LOD objects properly");
+
+ if (layerInfo.tileInfo) {
+ if (layerInfo.tileInfo.width && layerInfo.tileInfo.height) {
+ var tileSize = new OpenLayers.Size(layerInfo.tileInfo.width, layerInfo.tileInfo.height);
+ t.ok((layer.tileSize.width == tileSize.width) && (layer.tileSize.height == tileSize.height), "tile size is set properly");
+ }
+ else {
+ var tileSize = new OpenLayers.Size(layerInfo.tileInfo.cols, layerInfo.tileInfo.rows);
+ t.ok((layer.tileSize.width == tileSize.width) && (layer.tileSize.height == tileSize.height), "tile size is set properly");
+ }
+ }
+ }
+
+ /**
+ * lets make sure we're getting the correct urls back with a basic auto-configure setup
+ */
+ function test_Layer_ARCGISCACHE_autoConfigure_URLS(t) {
+ var layerInfo = capabilitiesObject;
+
+ //initialize the layer using the JSON object from an arcgis server
+ //SEE: ArcGISCache.json
+ var layer = new OpenLayers.Layer.ArcGISCache(name, url, {
+ layerInfo: layerInfo,
+ params: {foo: "bar"}
+ });
+ var map = new OpenLayers.Map('map', {
+ maxExtent: layer.maxExtent,
+ units: layer.units,
+ resolutions: layer.resolutions,
+ numZoomLevels: layer.numZoomLevels,
+ tileSize: layer.tileSize,
+ projection: layer.displayProjection,
+ StartBounds: layer.initialExtent
+ });
+ map.addLayers([layer]);
+
+ //this set represents a few edge cases, and some more specific cases, it is by no means exhaustive,
+ var urlSets = [
+ {
+ bounds: new OpenLayers.Bounds(-36787612.973083,-22463925.368666, 43362420.398053,17611091.316902),
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/0/0/0"
+ },
+ {
+ bounds: new OpenLayers.Bounds(-31793889.951914,4589319.785415, 8281126.733654,24626828.128199),
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/1/0/0"
+ },
+ {
+ bounds: new OpenLayers.Bounds(-24639873.181971,12676071.933457, -4602364.839187,22694826.104849),
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/2/0/0"
+ },
+ {
+ bounds: new OpenLayers.Bounds(-15521241.455665,11580270.695961, 4516266.887119,21599024.867353),
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/2/0/1"
+ },
+ {
+ bounds: new OpenLayers.Bounds(-9265879.5435993,2870892.9335638, -8639707.4078873,3183979.0014198) ,
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/7/54/35"
+ },
+ {
+ bounds: new OpenLayers.Bounds(-10741909.131798,4684560.1640365, -10585366.09787,4762831.6810005),
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/9/195/119"
+ },
+ {
+ bounds: new OpenLayers.Bounds(-13668958.106938,4456961.2611504, -13512415.07301,4535232.7781144),
+ url: "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/9/198/82"
+ }
+ ];
+
+ t.plan( urlSets.length );
+ for(var i=0;i<urlSets.length;i++)
+ {
+ var o = urlSets[i];
+ map.zoomToExtent(o.bounds, true);
+
+ var resultUrl = layer.getURL(o.bounds);
+ t.ok( resultUrl == o.url + "?foo=bar", "correct tile returned for " + o.bounds);
+ }
+ }
+
+ /**
+ * Test the formatting for the 'direct' urls, especially when not auto-configuring the layer
+ */
+ function test_Layer_ARCGISCACHE_direct(t) {
+ var roadsUrl = 'http://serverx.esri.com/arcgiscache/DG_County_roads_yesA_backgroundDark/Layers/_alllayers';
+ var urlSets = [
+ {
+ bounds: new OpenLayers.Bounds(289244.67443386,4317153.7421985, 306178.04163392,4325620.4257985),
+ url: roadsUrl + "/L00/R0000029e/C0000027f.png"
+ },
+ {
+ bounds: new OpenLayers.Bounds(308658.51534463,4303230.0164352, 325591.88254469,4311696.7000352),
+ url: roadsUrl + "/L00/R000002a0/C00000282.png"
+ },
+ {
+ bounds: new OpenLayers.Bounds(311136.39626998,4318933.8711555, 311678.26402038,4319204.8050307) ,
+ url: roadsUrl + "/L05/R000051e0/C00004e52.png"
+ }
+ ];
+ t.plan( urlSets.length );
+
+
+ //perform the exact setup from the arcgiscache_direct example
+
+ // First 4 variables extracted from conf.xml file
+ // Tile layers & map MUST have same projection
+ var proj='EPSG:26915';
+
+ // Layer can also accept serverResolutions array
+ // to deal with situation in which layer resolution array & map resolution
+ // array are out of sync
+ var mapResolutions = [33.0729828126323,16.9333672000677,8.46668360003387,4.23334180001693,2.11667090000847,1.05833545000423];
+
+ // For this example this next line is not really needed, 256x256 is default.
+ // However, you would need to change this if your layer had different tile sizes
+ var tileSize = new OpenLayers.Size(256,256);
+
+ // Tile Origin is required unless it is the same as the implicit map origin
+ // which can be effected by several variables including maxExtent for map or base layer
+ var agsTileOrigin = new OpenLayers.LonLat(-5120900,9998100);
+
+ // This can really be any valid bounds that the map would reasonably be within
+ var mapExtent = new OpenLayers.Bounds(289310.8204,4300021.937,314710.8712,4325421.988);
+
+
+ var map = new OpenLayers.Map('map', {
+ maxExtent:mapExtent,
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.LayerSwitcher(),
+ new OpenLayers.Control.PanZoomBar(),
+ new OpenLayers.Control.MousePosition()]
+ });
+
+ var layer = new OpenLayers.Layer.ArcGISCache('Roads', roadsUrl, {
+ tileOrigin: agsTileOrigin,
+ resolutions: mapResolutions,
+ sphericalMercator: true,
+ maxExtent: mapExtent,
+ useArcGISServer: false,
+ isBaseLayer: true,
+ projection: proj
+ });
+
+ map.addLayers([layer]);
+ map.zoomToExtent(new OpenLayers.Bounds(-8341644, 4711236, -8339198, 4712459));
+
+ for(var i=0;i<urlSets.length;i++)
+ {
+ var o = urlSets[i];
+ map.zoomToExtent(o.bounds, true);
+ var resultUrl = layer.getURL(o.bounds);
+ t.ok( resultUrl == o.url, "correct tile returned for " + o.bounds);
+ }
+ }
+
+ /**
+ * Check the utility function for generating tile indexes against a file cache
+ * This is already tested in BaseTypes test, but these are specific,
+ * common conversions that this class will rely on, so the tests are retained
+ */
+ function test_Layer_ARCGISCACHE_zeroPad(t) {
+ t.plan(4);
+
+ var layer = new OpenLayers.Layer.ArcGISCache('test', null, { });
+
+ //some tile examples
+ t.ok('00000001' == OpenLayers.Number.zeroPad(1, 8, 16), 'zeroPad should generate tile indexes properly ');
+ t.ok('00000020' == OpenLayers.Number.zeroPad(32, 8, 16), 'zeroPad should generate tile indexes properly ');
+ t.ok('00000100' == OpenLayers.Number.zeroPad(256, 8, 16), 'zeroPad should generate tile indexes properly ');
+ t.ok('00001000' == OpenLayers.Number.zeroPad(4096, 8, 16), 'zeroPad should generate tile indexes properly ');
+ }
+
+ /**
+ * Check to ensure our LOD calculation will correctly avoid returning tile indexes less than zero
+ * (see http://trac.osgeo.org/openlayers/ticket/3169)
+ */
+ function test_Layer_ARCGISCACHE_tileBounds(t) {
+ t.plan(1);
+
+ var layer = new OpenLayers.Layer.ArcGISCache('test', null, { });
+ var res = 264.583862501058;
+ layer.tileOrigin = new OpenLayers.LonLat(0.0, 650000.0);
+ layer.tileSize = new OpenLayers.Size(512, 512);
+
+ // pick a point off the left of our tile origin (would be a negative tile index)
+ var point = new OpenLayers.Geometry.Point(-123308.94829, 393128.85817);
+
+ var tile = layer.getContainingTileCoords(point, res);
+ t.ok((tile.x >= 0 && tile.y >= 0), 'layer should not generate negative tile ranges for level of detail');
+ }
+
+ /*
+ * Test that messing up the Array.prototype does not mess up the lods of the layer.
+ * This messes up zooming when resolutions are very small/scales are very large/zoomed way in.
+ */
+ function test_Layer_ARCGISCACHE_lods (t) {
+ t.plan( 2 );
+ var layerInfo = capabilitiesObject;
+
+ lods = layerInfo.tileInfo.lods.length;
+
+ // mess up the Array prototype
+ Array.prototype.foo = function() { };
+
+ t.ok( lods == layerInfo.tileInfo.lods.length, 'proper number of "Levels of Detail" before initialization' );
+
+ // initialize the layer using the JSON object from an arcgis server
+ // see: ArcGISCache.json
+ var layer = new OpenLayers.Layer.ArcGISCache(name, url, {
+ layerInfo: layerInfo
+ });
+
+ t.ok( lods == layer.lods.length, 'proper number of "Levels of Detail" after initialization.' );
+ // restore the Array prototype
+ delete Array.prototype.foo;
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/ArcGISCache.json b/misc/openlayers/tests/Layer/ArcGISCache.json
new file mode 100644
index 0000000..79dffa8
--- /dev/null
+++ b/misc/openlayers/tests/Layer/ArcGISCache.json
@@ -0,0 +1,334 @@
+var capabilitiesObject = {
+ "currentVersion" : 10.01,
+ "serviceDescription" : "This map is designed to be used as a base map by GIS professionals and as a reference map by anyone. The base map includes administrative boundaries, cities, water features, physiographic features, parks, landmarks, highways, roads, railways, airports, and buildings overlaid on land cover and shaded relief imagery for added context. The map was compiled from a variety of best available sources from several data providers, including the U.S. Geological Survey, Food and Agriculture Organization of the United Nations, National Park Service, Tele Atlas, AND, and ESRI. The base map currently provides coverage for the world down to a scale of ~1:1m and coverage for the continental United States and Hawaii to a scale of ~1:20k. The base map also includes detailed maps for selected cities in the United States including Portland, Oregon and Philadephia, Pennsylvania. The base map was designed and developed by ESRI based on the topographic map templates that are available through the ArcGIS Resource Centers. For more information on this map, visit us \u003ca href=\"http://goto.arcgisonline.com/maps/World_Topo_Map \" target=\"_new\"\u003eonline\u003c/a\u003e.",
+ "mapName" : "Layers",
+ "description" : "This map is designed to be used as a base map by GIS professionals and as a reference map by anyone. The base map includes administrative boundaries, cities, water features, physiographic features, parks, landmarks, highways, roads, railways, airports, and buildings overlaid on land cover and shaded relief imagery for added context. The map was compiled from a variety of best available sources from several data providers, including the U.S. Geological Survey, Food and Agriculture Organization of the United Nations, National Park Service, Tele Atlas, AND, and ESRI. The base map currently provides coverage for the world down to a scale of ~1:1m and coverage for the continental United States and Hawaii to a scale of ~1:20k. The base map also includes detailed maps for selected cities in the United States including Portland, Oregon and Philadephia, Pennsylvania. The base map was designed and developed by ESRI based on the topographic map templates that are available through the ArcGIS Resource Centers. For more information on this map, visit us online at http://goto.arcgisonline.com/maps/World_Topo_Map",
+ "copyrightText" : "Sources: USGS, FAO, NPS, EPA, ESRI, DeLorme, TANA, other suppliers",
+ "layers" : [
+ {
+ "id" : 0,
+ "name" : "Topographic Info",
+ "parentLayerId" : -1,
+ "defaultVisibility" : true,
+ "subLayerIds" : [1, 2, 3, 4],
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 1,
+ "name" : "Elevation (m)",
+ "parentLayerId" : 0,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 2,
+ "name" : "Elevation (ft)",
+ "parentLayerId" : 0,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 3,
+ "name" : "Slope",
+ "parentLayerId" : 0,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 4,
+ "name" : "Aspect",
+ "parentLayerId" : 0,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 5,
+ "name" : "Places Info",
+ "parentLayerId" : -1,
+ "defaultVisibility" : true,
+ "subLayerIds" : [6, 7, 8, 9],
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 6,
+ "name" : "Place Names (Country Level)",
+ "parentLayerId" : 5,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 80000000
+ },
+ {
+ "id" : 7,
+ "name" : "Place Names (State Level)",
+ "parentLayerId" : 5,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 80000001,
+ "maxScale" : 1500000
+ },
+ {
+ "id" : 8,
+ "name" : "Place Names (County Level)",
+ "parentLayerId" : 5,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 1500001,
+ "maxScale" : 400000
+ },
+ {
+ "id" : 9,
+ "name" : "Place Names (City Level)",
+ "parentLayerId" : 5,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 399999,
+ "maxScale" : 0
+ },
+ {
+ "id" : 10,
+ "name" : "Scale Descriptions",
+ "parentLayerId" : -1,
+ "defaultVisibility" : true,
+ "subLayerIds" : [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26],
+ "minScale" : 0,
+ "maxScale" : 0
+ },
+ {
+ "id" : 11,
+ "name" : "Level 15 ~1:18K",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 25000,
+ "maxScale" : 15001
+ },
+ {
+ "id" : 12,
+ "name" : "Level 14 ~1:36K",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 50000,
+ "maxScale" : 25001
+ },
+ {
+ "id" : 13,
+ "name" : "Level 13 ~1:72K",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 100000,
+ "maxScale" : 50001
+ },
+ {
+ "id" : 14,
+ "name" : "Level 12 ~1:144K",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 288000,
+ "maxScale" : 100000
+ },
+ {
+ "id" : 15,
+ "name" : "Level 11 ~1:288K",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 575000,
+ "maxScale" : 288000
+ },
+ {
+ "id" : 16,
+ "name" : "Level 10 ~1:577K",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 1150000,
+ "maxScale" : 575000
+ },
+ {
+ "id" : 17,
+ "name" : "Level 9 ~1:1.15M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 2200000,
+ "maxScale" : 1150000
+ },
+ {
+ "id" : 18,
+ "name" : "Level 8 ~1:2.3M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 4500000,
+ "maxScale" : 2200000
+ },
+ {
+ "id" : 19,
+ "name" : "Level 7 ~1:4.5M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 9000000,
+ "maxScale" : 4500000
+ },
+ {
+ "id" : 20,
+ "name" : "Level 6 ~1:9.2M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 18000000,
+ "maxScale" : 9000000
+ },
+ {
+ "id" : 21,
+ "name" : "Level 5 ~1:18M ",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 36000000,
+ "maxScale" : 18000000
+ },
+ {
+ "id" : 22,
+ "name" : "Level 4 ~1:36M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 72000000,
+ "maxScale" : 36000000
+ },
+ {
+ "id" : 23,
+ "name" : "Level 3 ~1:72M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 75500000,
+ "maxScale" : 70000000
+ },
+ {
+ "id" : 24,
+ "name" : "Level 2 ~1:147M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 290000000,
+ "maxScale" : 147000000
+ },
+ {
+ "id" : 25,
+ "name" : "Level 1 ~1:292M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 295000000,
+ "maxScale" : 150000000
+ },
+ {
+ "id" : 26,
+ "name" : "Level 0 ~1:584M",
+ "parentLayerId" : 10,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 295000000
+ },
+ {
+ "id" : 27,
+ "name" : "Citations",
+ "parentLayerId" : -1,
+ "defaultVisibility" : true,
+ "subLayerIds" : null,
+ "minScale" : 0,
+ "maxScale" : 0
+ }
+ ],
+ "tables" : [
+
+ ],
+ "spatialReference" : {
+ "wkid" : 102100
+ },
+ "singleFusedMapCache" : true,
+ "tileInfo" : {
+ "rows" : 256,
+ "cols" : 256,
+ "dpi" : 96,
+ "format" : "JPEG",
+ "compressionQuality" : 90,
+ "origin" : {
+ "x" : -20037508.342787,
+ "y" : 20037508.342787
+ },
+ "spatialReference" : {
+ "wkid" : 102100
+ },
+ "lods" : [
+ {"level" : 0, "resolution" : 156543.033928, "scale" : 591657527.591555},
+ {"level" : 1, "resolution" : 78271.5169639999, "scale" : 295828763.795777},
+ {"level" : 2, "resolution" : 39135.7584820001, "scale" : 147914381.897889},
+ {"level" : 3, "resolution" : 19567.8792409999, "scale" : 73957190.948944},
+ {"level" : 4, "resolution" : 9783.93962049996, "scale" : 36978595.474472},
+ {"level" : 5, "resolution" : 4891.96981024998, "scale" : 18489297.737236},
+ {"level" : 6, "resolution" : 2445.98490512499, "scale" : 9244648.868618},
+ {"level" : 7, "resolution" : 1222.99245256249, "scale" : 4622324.434309},
+ {"level" : 8, "resolution" : 611.49622628138, "scale" : 2311162.217155},
+ {"level" : 9, "resolution" : 305.748113140558, "scale" : 1155581.108577},
+ {"level" : 10, "resolution" : 152.874056570411, "scale" : 577790.554289},
+ {"level" : 11, "resolution" : 76.4370282850732, "scale" : 288895.277144},
+ {"level" : 12, "resolution" : 38.2185141425366, "scale" : 144447.638572},
+ {"level" : 13, "resolution" : 19.1092570712683, "scale" : 72223.819286},
+ {"level" : 14, "resolution" : 9.55462853563415, "scale" : 36111.909643},
+ {"level" : 15, "resolution" : 4.77731426794937, "scale" : 18055.954822},
+ {"level" : 16, "resolution" : 2.38865713397468, "scale" : 9027.977411},
+ {"level" : 17, "resolution" : 1.19432856685505, "scale" : 4513.988705},
+ {"level" : 18, "resolution" : 0.597164283559817, "scale" : 2256.994353},
+ {"level" : 19, "resolution" : 0.298582141647617, "scale" : 1128.497176}
+ ]
+ },
+ "initialExtent" : {
+ "xmin" : -45223792.233066,
+ "ymin" : -22882589.2065154,
+ "xmax" : 45223792.233066,
+ "ymax" : 22882589.2065155,
+ "spatialReference" : {
+ "wkid" : 102100
+ }
+ },
+ "fullExtent" : {
+ "xmin" : -20037507.0671618,
+ "ymin" : -19971868.8804086,
+ "xmax" : 20037507.0671618,
+ "ymax" : 19971868.8804086,
+ "spatialReference" : {
+ "wkid" : 102100
+ }
+ },
+ "units" : "esriMeters",
+ "supportedImageFormatTypes" : "PNG24,PNG,JPG,DIB,TIFF,EMF,PS,PDF,GIF,SVG,SVGZ,AI,BMP",
+ "documentInfo" : {
+ "Title" : "World Topo Map",
+ "Author" : "ESRI",
+ "Comments" : "",
+ "Subject" : "",
+ "Category" : "",
+ "Keywords" : "",
+ "Credits" : ""
+ },
+ "capabilities" : "Map,Query,Data"
+}; \ No newline at end of file
diff --git a/misc/openlayers/tests/Layer/ArcIMS.html b/misc/openlayers/tests/Layer/ArcIMS.html
new file mode 100644
index 0000000..4f86227
--- /dev/null
+++ b/misc/openlayers/tests/Layer/ArcIMS.html
@@ -0,0 +1,123 @@
+<html>
+ <head>
+ <script type="text/javascript" src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ // use an arcims map service against Avencia Inc.'s global sample map services
+ var serviceName = "OpenLayers_Sample";
+ var layerName = "Global Sample Map";
+ var imsUrl = "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap";
+
+ //
+ // create an arcims layer
+ //
+ function test_Layer_ArcIMS_constructor( t ) {
+ t.plan(11);
+
+ var options = {
+ serviceName: serviceName,
+ async: false,
+ displayOutsideMaxExtent: true
+ };
+
+ var layer = new OpenLayers.Layer.ArcIMS( layerName, imsUrl, options );
+
+ // check layer & properties
+ t.ok( layer instanceof OpenLayers.Layer.ArcIMS, "new OpenLayers.Layer.ArcIMS returns object" );
+ t.eq( layer.url, imsUrl, "layer.url is correct (HTTPRequest inited)" );
+ t.eq( layer.name, layerName, "layer.name is correct" );
+ t.eq( layer.displayOutsideMaxExtent, options.displayOutsideMaxExtent,
+ "displayOutsideMaxExtent property set correctly from options" );
+
+ // check request parameters
+ t.eq( layer.params.ServiceName, serviceName, "ServiceName set properly" );
+ t.eq( layer.params.ClientVersion, "9.2", "ClientVersion set properly" );
+
+ // check request options
+ t.eq( layer.options.async, options.async, "async property set correctly from options" );
+ t.eq( layer.options.serviceName, serviceName, "serviceName property set correctly from options" );
+ t.eq( layer.options.layers.length, 0, "layers option is the correct length" );
+ t.eq( layer.options.tileSize.w, 512, "default tile width set correctly" );
+ t.eq( layer.options.tileSize.h, 512, "default tile height set correctly" );
+ }
+
+
+
+ /*
+ * how to test getURL, getURLasync, and getFeatureInfo without a proxy?
+ *
+ */
+
+
+ //
+ // Create an arcims layer, and verify that the query changes properly
+ //
+ function test_Layer_ArcIMS_setLayerQuery(t) {
+ t.plan(9);
+
+ var options = { serviceName: serviceName };
+ var layer = new OpenLayers.Layer.ArcIMS( layerName, imsUrl, options );
+ var querydef = {
+ where: "FIPS_CNTRY = 'US'"
+ };
+
+ t.eq( layer.options.layers.length, 0, "layer definitions are empty" );
+
+ layer.setLayerQuery( "layerID", querydef );
+
+ t.eq( layer.options.layers.length, 1, "layers definitions contain one layerdef" );
+ t.ok( layer.options.layers[0].query !== null, "layer query exists" );
+ t.eq( typeof layer.options.layers[0].query.where, "string", "where query is a string" );
+ t.eq( layer.options.layers[0].query.where, querydef.where, "where query matches" );
+
+ // change the definition
+ querydef = {
+ where: "FIPS_CNTRY = 'UV'",
+ spatialfilter:true
+ }
+
+ layer.setLayerQuery( "layerID", querydef );
+
+ t.eq( layer.options.layers.length, 1, "layers definitions contain one layerdef" );
+ t.ok( layer.options.layers[0].query !== null, "layer query exists" );
+ t.eq( typeof layer.options.layers[0].query.where, "string", "where query is a string" );
+ t.eq( layer.options.layers[0].query.where, querydef.where, "where query matches" );
+ }
+ function test_Layer_ArcIMS_clone (t) {
+ t.plan(5);
+
+ var url = imsUrl;
+ var options = {
+ serviceName: serviceName,
+ async: false,
+ displayOutsideMaxExtent: true
+ };
+ var map = new OpenLayers.Map('map', {controls: []});
+ var layer = new OpenLayers.Layer.ArcIMS(name, url, options);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ var clone = layer.clone();
+
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ t.eq( clone.params.serviceName, layer.params.serviceName, "serviceName copied correctly");
+
+ t.eq( clone.async, layer.async, "async copied correctly");
+
+ t.eq( clone.url, layer.url, "url copied correctly");
+
+ layer.grid = null;
+ map.destroy();
+ }
+
+ </script>
+ </head>
+ <body>
+ <div id="map" style="width:500px;height:550px"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Bing.html b/misc/openlayers/tests/Layer/Bing.html
new file mode 100644
index 0000000..89bbba7
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Bing.html
@@ -0,0 +1,200 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ var layerType = 'Aerial';
+ var key = "AqTGBsziZHIJYYxgivLBf0hVdrAk9mWO5cQcb8Yux8sW5M8c8opEC2lZqKR1ZZXf";
+
+ var options = {
+ type: layerType,
+ key: key
+ };
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var origProcessMetadata = OpenLayers.Layer.Bing.processMetadata;
+ var log = [];
+ OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ var script = document.getElementById(this._callbackId);
+ log.push(script.src);
+ origProcessMetadata.apply(this, arguments);
+ };
+ layer = new OpenLayers.Layer.Bing(OpenLayers.Util.extend({
+ metadataParams: {foo: "bar"}
+ }, options));
+ t.ok(layer instanceof OpenLayers.Layer.Bing, "returns OpenLayers.Layer.Bing object" );
+ t.delay_call(5, function() {
+ t.eq(log.length, 1, "processMetadata called");
+ t.eq(OpenLayers.Util.getParameters(log[0]).foo, "bar", "metadataParams passed to url correctly.");
+ OpenLayers.Layer.Bing.processMetadata = origProcessMetadata;
+ layer.destroy();
+ });
+ }
+
+ function test_initLayer(t) {
+ t.plan(2);
+
+ var meta = [];
+ var origProcessMetadata = OpenLayers.Layer.Bing.processMetadata;
+ OpenLayers.Layer.Bing.processMetadata = function(metadata) {
+ meta.push(metadata);
+ };
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Bing(options);
+ var extent;
+ map.addLayers([layer, new OpenLayers.Layer(null, {
+ moveTo: function(bounds, changed) {
+ extent = bounds;
+ }
+ })]);
+ map.zoomToMaxExtent();
+
+ var map2 = new OpenLayers.Map("map");
+ var layer2 = new OpenLayers.Layer.Bing(OpenLayers.Util.extend({
+ initLayer: function() {
+ // pretend we have a zoomMin of 2
+ this.metadata.resourceSets[0].resources[0].zoomMin = 2;
+ OpenLayers.Layer.Bing.prototype.initLayer.apply(this, arguments);
+ }
+ }, options));
+ var extent2;
+ map2.addLayers([layer2, new OpenLayers.Layer(null, {
+ moveTo: function(bounds, changed) {
+ extent2 = bounds;
+ }
+ })]);
+ map2.zoomToMaxExtent();
+
+ t.delay_call(5, function() {
+ origProcessMetadata.call(layer, meta[0]);
+ t.eq(extent.toBBOX(), map.getExtent().toBBOX(), "layer extent correct for base layer with zoomMin == 1.");
+ map.destroy();
+ });
+
+ t.delay_call(6, function() {
+ origProcessMetadata.call(layer2, meta[1]);
+ t.eq(extent2.toBBOX(), map2.getExtent().toBBOX(), "layer extent correct for base layer with zoomMin == 2.");
+ map2.destroy();
+ OpenLayers.Layer.Bing.processMetadata = origProcessMetadata;
+ });
+ }
+
+ function test_initLayer_notempty(t) {
+ t.plan(1);
+
+ map = new OpenLayers.Map("map", {
+ projection: "EPSG:3857",
+ layers: [new OpenLayers.Layer("dummy", {isBaseLayer: true})]
+ });
+ map.zoomToExtent([-14768652, 4492113, -12263964, 5744457]);
+ var layer = new OpenLayers.Layer.Bing(OpenLayers.Util.extend({
+ isBaseLayer: false
+ }, options));
+ map.addLayer(layer);
+
+ t.delay_call(5, function() {
+ t.ok(layer.grid[0][0].url, "Tile not empty");
+ map.destroy();
+ });
+ }
+
+ function test_attribution(t) {
+ t.plan(3);
+
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Bing(options);
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.delay_call(2, function() {
+ t.ok(OpenLayers.Util.indexOf(layer.attribution, 'olBingAttribution aerial') !== -1, "Attribution has the correct css class");
+ t.ok(OpenLayers.Util.indexOf(layer.attribution, '<img src="">') == -1, "Attribution contains a logo");
+ t.ok(OpenLayers.Util.indexOf(layer.attribution, '</img></div></a><a style=') == -1 , "Attribution contains a copyright");
+ map.destroy();
+ });
+ }
+
+ function test_attribution_notempty(t) {
+ t.plan(1);
+
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Bing(OpenLayers.Util.applyDefaults({type: 'Road'}, options));
+ map.addLayer(layer);
+ var format = OpenLayers.String.format;
+ OpenLayers.String.format = function(tpl, options) {
+ log.push(options.copyrights);
+ }
+ map.zoomToExtent(new OpenLayers.Bounds(-14768652, 4492113, -12263964, 5744457));
+ t.delay_call(2, function() {
+ t.ok(log.join("") !== "", "Copyright not empty");
+ OpenLayers.String.format = format;
+ map.destroy();
+ });
+ }
+
+ function test_getXYZ(t) {
+ t.plan(1);
+
+ var map = new OpenLayers.Map("map", {allOverlays: true});
+ var osm = new OpenLayers.Layer.OSM();
+ map.addLayer(osm);
+ map.zoomToExtent(new OpenLayers.Bounds(11373579,-2445208,13628777,680760));
+ layer = new OpenLayers.Layer.Bing(options);
+ map.addLayer(layer);
+
+ t.delay_call(2, function() {
+ var xyz = layer.getXYZ(layer.getTileBounds(new OpenLayers.Pixel(1,1)));
+ t.eq(xyz.z, OpenLayers.Util.indexOf(layer.serverResolutions, map.getResolution()), "zoom level correct");
+ });
+ }
+
+ function test_clone(t) {
+ t.plan(1);
+
+ var clone;
+
+ layer = new OpenLayers.Layer.Bing(options);
+ clone = layer.clone();
+ t.ok(clone instanceof OpenLayers.Layer.Bing, "clone is a Layer.Bing instance");
+ }
+
+ function test_protocol(t)
+ {
+ t.plan(5);
+
+ var map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Bing(options);
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.delay_call(5, function() {
+ t.ok(OpenLayers.Util.indexOf(layer.attribution, '<img src="//') != -1, "Attribution contains a logo with protocol //");
+ t.ok(OpenLayers.Util.indexOf(layer.attribution, '<img src="http://') == -1, "Attribution logo does not have http:// protocol");
+ t.ok(layer.grid[1][1].url.indexOf('http:') == -1, "Tile url does not contain http:");
+
+ map.destroy();
+ });
+
+ var map2 = new OpenLayers.Map("map");
+ layer_https = new OpenLayers.Layer.Bing(OpenLayers.Util.applyDefaults({protocol: 'https:'}, options));
+ map2.addLayer(layer_https);
+ map2.zoomToMaxExtent();
+
+ t.delay_call(5, function() {
+ t.ok(OpenLayers.Util.indexOf(layer_https.attribution, '<img src="https://') != -1, "Attribution logo has https:// protocol");
+ t.ok(layer_https.grid[1][1].url.indexOf('https:') == 0, "Tile url contains https:");
+ map2.destroy();
+ });
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/EventPane.html b/misc/openlayers/tests/Layer/EventPane.html
new file mode 100644
index 0000000..8d8e180
--- /dev/null
+++ b/misc/openlayers/tests/Layer/EventPane.html
@@ -0,0 +1,172 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var isOpera = (navigator.userAgent.indexOf("Opera") != -1);
+ var layer;
+
+ function test_Layer_EventPane_constructor (t) {
+ t.plan( 5 );
+
+ var layer = new OpenLayers.Layer.EventPane('Test Layer');
+
+ t.ok( layer instanceof OpenLayers.Layer.EventPane, "new OpenLayers.Layer.EventPane returns object" );
+ t.eq( layer.CLASS_NAME, "OpenLayers.Layer.EventPane", "CLASS_NAME variable set correctly");
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ t.eq( layer.isBaseLayer, true, "EventPane layer is always base layer" );
+ if (!isMozilla) {
+ t.ok( true, "skipping element test outside of Mozilla");
+ } else {
+ t.ok( layer.pane instanceof HTMLDivElement, "layer.pane is an HTMLDivElement" );
+ }
+ }
+
+ function test_Layer_EventPane_clone (t) {
+ t.plan( 1 );
+ t.ok( true, "need to actually write some tests here" );
+ return;
+
+ /// FIX ME FIX ME: fix this later
+
+ var map = new OpenLayers.Map('map');
+ var options = { chicken: 151, foo: "bar" };
+ var layer = new OpenLayers.Layer('Test Layer', options);
+ map.addLayer(layer);
+
+ // randomly assigned property
+ layer.chocolate = 5;
+
+ var clone = layer.clone();
+
+ t.ok( clone instanceof OpenLayers.Layer, "new OpenLayers.Layer returns object" );
+ t.eq( clone.name, "Test Layer", "default clone.name is correct" );
+ t.ok( ((clone.options["chicken"] == 151) && (clone.options["foo"] == "bar")), "clone.options correctly set" );
+ t.eq(clone.chocolate, 5, "correctly copied randomly assigned property");
+
+ layer.addOptions({chicken:152});
+ t.eq(clone.options["chicken"], 151, "made a clean copy of options");
+
+
+ t.ok( clone.map == null, "cloned layer has map property set to null")
+
+ }
+
+ function test_Layer_EventPane_setMap (t) {
+
+// MOUSEMOVE test does not seem to work...
+// t.plan( 2 );
+
+ if (OpenLayers.BROWSER_NAME != "firefox" && OpenLayers.BROWSER_NAME != "mozilla") {
+ t.plan(4);
+ } else {
+ t.plan(0);
+ t.debug_print("Firefox gives different results for different browsers on setMap on EventPane, so just don't run it for now.")
+ return;
+ }
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.EventPane('Test Layer');
+
+ //give dummy function so test wont bomb on layer.setMap()
+ layer.loadMapObject = function() { };
+ layer.getWarningHTML = function() { this.warning = true; return ""; };
+ map.addLayer(layer);
+ t.eq( parseInt(layer.pane.style.zIndex) - parseInt(layer.div.style.zIndex),
+ 1, "layer pane is 1 z-level above its div" );
+
+ t.ok( layer.warning, "warning correctly registered on no mapObject load" );
+
+ layer2 = new OpenLayers.Layer.EventPane('Test Layer');
+
+ //give dummy function so test wont bomb on layer.setMap()
+ layer2.loadMapObject = function() { this.mapObject = {}; };
+ layer2.getWarningHTML = function() { this.warning = true; return ""; }
+
+ map.addLayer(layer2);
+ t.ok(!layer2.warning, "warning not registered on mapObject load");
+
+ var log = [];
+ map.events.register("mousemove", map, function(event) {
+ log.push(event);
+ });
+
+ if (document.createEvent) { // Mozilla
+ var evObj = document.createEvent('MouseEvents');
+ evObj.initEvent('mousemove', true, false);
+ map.viewPortDiv.dispatchEvent(evObj);
+ } else if(document.createEventObject) { // IE
+ map.viewPortDiv.fireEvent('onmousemove');
+ }
+
+ t.eq(log.length, 1, "got one event");
+
+ }
+
+ function test_Layer_EventPane_setVisibility (t) {
+ t.plan( 2 );
+ layer = new OpenLayers.Layer.EventPane('Test Layer');
+ layer.setVisibility(false);
+ t.eq(layer.visibility, false, "layer pane is now invisible");
+ layer.setVisibility(true);
+ t.eq(layer.visibility, true, "layer pane is now visible");
+ }
+
+
+ function test_Layer_EventPane_removeLayer(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.EventPane('Test Layer');
+ layer.loadMapObject = function() { };
+ layer.getWarningHTML = function() { this.warning = true; return ""; };
+ map.addLayer(layer);
+ map.removeLayer(layer);
+ var parent = layer.pane.parentNode;
+ // IE creates a DOCUMENT_FRAGMENT_NODE for the parent
+ t.ok(!parent || parent.nodeType == 11, "Layer.pane removed from dom.");
+ }
+
+ function test_repeat_add(t) {
+
+ t.plan(1);
+ var map = new OpenLayers.Map("map");
+
+ layer = new OpenLayers.Layer.EventPane();
+ layer.loadMapObject = function() {};
+ layer.getWarningHTML = function() {this.warning = true; return "";};
+
+ map.addLayer(layer);
+ map.removeLayer(layer);
+
+ // try adding the layer a second time
+ var msg = "layer successfully added after being removed";
+ var pass = true;
+ try {
+ map.addLayer(layer);
+ } catch (err) {
+ msg = "couldn't add layer after removing: " + err;
+ pass = false;
+ }
+ t.ok(pass, msg);
+
+ }
+
+ function test_destroy(t) {
+
+ t.plan(2);
+ layer = new OpenLayers.Layer.EventPane();
+ t.ok(layer.pane, "pane created on initialize");
+
+ layer.destroy();
+ t.ok(!layer.pane, "pane deleted on destroy");
+
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="height:500px;width:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/FixedZoomLevels.html b/misc/openlayers/tests/Layer/FixedZoomLevels.html
new file mode 100644
index 0000000..133571b
--- /dev/null
+++ b/misc/openlayers/tests/Layer/FixedZoomLevels.html
@@ -0,0 +1,137 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ function test_Layer_FixedZoomLevels (t) {
+ t.plan( 39 );
+
+ var layer = { 'MIN_ZOOM_LEVEL': 5,
+ 'MAX_ZOOM_LEVEL': 10 };
+
+
+ //defaults
+
+ layer = p_createLayer(layer);
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, layer.MAX_ZOOM_LEVEL, "nothing specified");
+
+
+ //layer.options
+
+ // min,num
+ layer = p_createLayer(layer, {}, { minZoomLevel: 3, numZoomLevels: 12});
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, layer.MAX_ZOOM_LEVEL, "min too low num too high(layer.options)");
+
+ layer = p_createLayer(layer, {}, { minZoomLevel: 6, numZoomLevels: 3 });
+ p_minMaxNum(t, layer, 6, 8, "valid min,num(layer.options)");
+
+
+ // max
+ layer = p_createLayer(layer, {}, { maxZoomLevel: 9 });
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, 9, "valid max(layer.options)");
+
+ layer = p_createLayer(layer, {}, { maxZoomLevel: 12 });
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, layer.MAX_ZOOM_LEVEL, "invalid max(layer.options)");
+
+
+
+ //map
+
+ // min,num
+ layer = p_createLayer(layer, { minZoomLevel: 3, numZoomLevels: 12});
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, layer.MAX_ZOOM_LEVEL, "min too low num too high(map)");
+
+ layer = p_createLayer(layer, { minZoomLevel: 6, numZoomLevels: 3 });
+ p_minMaxNum(t, layer, 6, 8, "valid min,num(map)");
+
+
+ // max
+ layer = p_createLayer(layer, { maxZoomLevel: 9 });
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, 9, "valid max(map)");
+
+ layer = p_createLayer(layer, { maxZoomLevel: 12 });
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, layer.MAX_ZOOM_LEVEL, "invalid max(map)");
+
+ //map vs. options
+
+ layer = p_createLayer(layer, {minZoomLevel: 6, numZoomLevels: 2}, { minZoomLevel: 7, numZoomLevels: 3});
+ p_minMaxNum(t, layer, 7, 9, "min,num(layer.options) wins over (map)");
+
+ layer = p_createLayer(layer, {minZoomLevel: 6, maxZoomLevel: 8}, { minZoomLevel: 7, maxZoomLevel: 9});
+ p_minMaxNum(t, layer, 7, 9, "min,max(layer.options) wins over (map)");
+
+
+ // numZoomLevels vs. maxZoomLevel
+
+ layer = p_createLayer(layer, {maxZoomLevel: 8, numZoomLevels: 6});
+ p_minMaxNum(t, layer, layer.MIN_ZOOM_LEVEL, 10, "min,max(layer.options) wins over (map)");
+
+ // resolutions array
+
+ var resolutions = Array(20);
+ for (var i = 0; i < 20; i++) {
+ resolutions[i] = Math.random();
+ }
+ OpenLayers.Util.extend(layer, {RESOLUTIONS:resolutions});
+ var minZoomLevel = 6;
+ var numZoomLevels = 2;
+ layer = p_createLayer(layer, {}, {minZoomLevel: minZoomLevel, numZoomLevels: numZoomLevels});
+ t.eq( layer.resolutions.length, numZoomLevels, "length of resolutions array ok");
+ for (var i = 0; i < numZoomLevels; i++) {
+ t.eq( layer.resolutions[i], resolutions[i + minZoomLevel], "resolutions array at index " + i + " ok");
+ }
+ }
+
+ function test_getMapObjectZoomFromOLZoom(t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map("map", {allOverlays: true});
+ var xyz = new OpenLayers.Layer.XYZ("xyz", "${x}${y}${z}", {
+ sphericalMercator: true,
+ resolutions: [39135.7584765625, 19567.87923828125, 9783.939619140625]
+ });
+ var fixed = new (OpenLayers.Class(OpenLayers.Layer, OpenLayers.Layer.FixedZoomLevels, {
+ initialize: function() {
+ OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+ }
+ }))("fixed", {
+ resolutions: [156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625],
+ minZoomLevel: 1
+ });
+ map.addLayers([xyz, fixed]);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+ // map.getZoom() returns 2
+ t.eq(fixed.getMapObjectZoomFromOLZoom(map.getZoom()), 4, "correct return value from getMapObjectZoomFromOLZoom");
+ t.eq(fixed.getOLZoomFromMapObjectZoom(4), map.getZoom() - fixed.minZoomLevel, "correct return value from getOLZoomFromMapObjectZoom");
+
+ map.setBaseLayer(fixed);
+ // map.getZoom() returns 4 now
+ t.eq(fixed.getMapObjectZoomFromOLZoom(map.getZoom()), 5, "correct return value from getMapObjectZoomFromOLZoom");
+ t.eq(fixed.getOLZoomFromMapObjectZoom(5), map.getZoom(), "correct return value from getOLZoomFromMapObjectZoom");
+ }
+
+ function p_createLayer(layer, mapOptions, layerOptions) {
+
+ layer.map = mapOptions || {};
+ layer.options = layerOptions || {};
+ OpenLayers.Layer.FixedZoomLevels.prototype.initResolutions.apply(layer);
+
+ return layer;
+ }
+
+ function p_minMaxNum(t, layer, min, max, msg) {
+
+ t.eq( layer.minZoomLevel, min, "min zoom level inherited from layer constant: " + msg);
+ t.eq( layer.maxZoomLevel, max, "max zoom level inherited from layer constant: " + msg);
+ t.eq( layer.numZoomLevels, max - min + 1, "num zoom levels correctly calcuated: " + msg);
+
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:256px;height:256px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/GeoRSS.html b/misc/openlayers/tests/Layer/GeoRSS.html
new file mode 100644
index 0000000..a942e83
--- /dev/null
+++ b/misc/openlayers/tests/Layer/GeoRSS.html
@@ -0,0 +1,210 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var isMSIE = (navigator.userAgent.indexOf("MSIE") > -1);
+ var layer;
+
+ var georss_txt = "./georss.txt";
+ var atom_xml = "./atom-1.0.xml";
+
+ // if this test is running online, different rules apply
+ if (isMSIE) {
+ georss_txt = "." + georss_txt;
+ atom_xml = "." + atom_xml;
+ }
+
+ function test_Layer_GeoRSS_constructor (t) {
+ t.plan( 7 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt );
+ t.ok( layer instanceof OpenLayers.Layer.GeoRSS, "new OpenLayers.Layer.GeoRSS returns object" );
+ t.eq( layer.location, georss_txt, "layer.location is correct" );
+ var markers;
+ layer.loadRSS();
+ t.delay_call( 1, function() {
+ t.eq( layer.markers.length, 40, "marker length is correct" );
+ var ll = new OpenLayers.LonLat(-71.142197, 42.405696);
+ var theTitle = "Knitting Room";
+ var theDescription = 'This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/90306">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/90306">Grab this on Platial</a> ';
+ t.ok( layer.markers[0].lonlat.equals(ll), "lonlat on first marker is correct" );
+ t.eq( layer.name, "Crschmidt's Places At Platial", "Layer name is correct." );
+ t.eq( layer.features[0].data.title, theTitle);
+ t.eq( layer.features[0].data.description, theDescription);
+ } );
+ }
+
+ function test_Layer_GeoRSS_dontUseFeedTitle (t) {
+ t.plan( 1 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt, {'useFeedTitle': false} );
+ t.delay_call( 1, function() {
+ t.eq( layer.name, "Test Layer", "Layer name is correct when not used from feed." );
+ } );
+ }
+
+ function test_Layer_GeoRSS_AtomParsing (t) {
+ t.plan( 6 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', atom_xml );
+ t.ok( layer instanceof OpenLayers.Layer.GeoRSS, "new OpenLayers.Layer.GeoRSS returns object" );
+ t.eq( layer.location, atom_xml, "layer.location is correct" );
+ var markers;
+ layer.loadRSS();
+ t.delay_call( 1, function() {
+ t.eq( layer.markers.length, 2, "marker length is correct" );
+ var ll = new OpenLayers.LonLat(29.9805, 36.7702);
+ t.ok( layer.markers[0].lonlat.equals(ll), "lonlat on first marker is correct" );
+ t.like( layer.features[0].data['popupContentHTML'], '<a class="link" href="http://pleiades.stoa.org/places/638896" target="_blank">Unnamed Tumulus</a>', "Link is correct.");
+ t.eq( layer.name, "tumulus", "Layer name is correct." );
+ } );
+ }
+
+ function test_Layer_GeoRSS_draw (t) {
+// t.plan(5);
+ t.plan( 2 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ t.ok( layer instanceof OpenLayers.Layer.GeoRSS, "new OpenLayers.Layer.GeoRSS returns object" );
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ t.delay_call( 1, function() {
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ t.eq( map.layers[1].name, layer.name, "Layer name is correct" );
+
+ });;
+ }
+ function test_Layer_GeoRSS_load_events (t) {
+ t.plan( 1 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ layer.events.register("loadstart", t, function() { this.ok(true, "loadstart event triggered once (#1580)") });
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ }
+ function test_Layer_GeoRSS_events (t) {
+ t.plan( 4 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var event = {};
+ t.delay_call( 2, function() {
+ t.ok(layer.markers[0].events, "First marker has an events object");
+ t.eq(layer.markers[0].events.listeners['click'].length, 1, "Marker events has one object");
+ layer.markers[0].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "Popup opened correctly");
+ layer.markers[1].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "1st popup gone, 2nd Popup opened correctly");
+ });
+ }
+ function test_Layer_GeoRSS_popups (t) {
+ t.plan( 4 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var event = {};
+ t.delay_call( 1, function() {
+ t.ok(layer.markers[0].events, "First marker has an events object");
+ t.eq(layer.markers[0].events.listeners['click'].length, 1, "Marker events has one object");
+ layer.markers[0].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "Popup opened correctly");
+ layer.markers[1].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "1st popup gone, 2nd Popup opened correctly");
+ });
+
+ }
+ function test_Layer_GeoRSS_resizedPopups(t) {
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt, {'popupSize': new OpenLayers.Size(200,100)});
+ t.plan( 4 );
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var event = {};
+ t.delay_call( 1, function() {
+ t.ok(layer.markers[0].events, "First marker has an events object");
+ t.eq(layer.markers[0].events.listeners['click'].length, 1, "Marker events has one object");
+ layer.markers[0].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "Popup opened correctly");
+ map.popups[0].size.w=300;
+ layer.markers[1].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "1st popup gone, 2nd Popup opened correctly");
+ });
+ }
+
+ function test_Layer_GeoRSS_icon(t) {
+ t.plan( 3 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ var the_icon = new OpenLayers.Icon('http://boston.openguides.org/markers/AQUA.png');
+ var otherLayer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt,{icon:the_icon});
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayers([layer,otherLayer]);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var defaultIcon = OpenLayers.Marker.defaultIcon();
+ layer.loadRSS();
+ otherLayer.loadRSS();
+ t.delay_call( 2, function() {
+ t.ok(layer.markers[0].icon, "The layer has a icon");
+ t.eq(layer.markers[0].icon.url, defaultIcon.url, "The layer without icon has the default icon.");
+ t.eq(otherLayer.markers[0].icon.url, the_icon.url,"The layer with an icon has that icon.");
+ });
+ }
+ function test_Layer_GeoRSS_loadend_Event(t) {
+ var browserCode = OpenLayers.BROWSER_NAME;
+ if (browserCode == "msie") {
+ t.plan(1);
+ t.ok(true, "IE fails the GeoRSS test. This could probably be fixed by someone with enough energy to fix it.");
+ } else {
+ t.plan(2);
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ t.delay_call(2, function() {
+ layer.events.register('loadend', layer, function() {
+ t.ok(true, "Loadend event fired");
+ });
+ layer.parseData({
+ 'responseText': '<xml xmlns="http://example.com"><title> </title></xml>'
+ });
+ t.ok(true, "Parsing data didn't fail");
+ });
+ }
+ }
+
+ function test_Layer_GeoRSS_destroy (t) {
+ t.plan( 1 );
+ layer = new OpenLayers.Layer.GeoRSS('Test Layer', georss_txt);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ t.delay_call( 1, function() {
+ layer.destroy();
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ });
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px; height:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Google.html b/misc/openlayers/tests/Layer/Google.html
new file mode 100644
index 0000000..84f17c9
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Google.html
@@ -0,0 +1,369 @@
+<html>
+<head>
+ <script type="text/javascript">var oldAlert = window.alert, gMess; window.alert = function(message) {gMess = message; return true;};</script>
+ <!-- this gmaps key generated for http://openlayers.org/dev/ -->
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA9XNhd8q0UdwNC7YSO4YZghSPUCi5aRYVveCcVYxzezM4iaj_gxQ9t-UajFL70jfcpquH5l1IJ-Zyyw'></script>
+ <script type="text/javascript">window.alert = oldAlert;</script>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+ var validkey = (window.location.protocol == "file:") ||
+ (window.location.host == "localhost") ||
+ (window.location.host == "openlayers.org");
+
+ function test_Layer_Google_message(t) {
+ t.plan(0);
+ if(gMess) {
+ t.debug_print(gMess);
+ }
+ }
+
+ function test_Layer_Google_constructor (t) {
+ if(validkey) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ map.addLayer(layer);
+
+
+ t.ok( layer instanceof OpenLayers.Layer.Google, "new OpenLayers.Layer.Google returns object" );
+ t.eq( layer.CLASS_NAME, "OpenLayers.Layer.Google", "CLASS_NAME variable set correctly");
+
+ t.eq( layer.name, "Goog Layer", "layer.name is correct" );
+
+ t.ok ( layer.mapObject != null, "GMap2 Object correctly loaded");
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_clone(t) {
+ if (validkey) {
+ t.plan(2);
+ var layer, clone;
+
+ // test default layer
+ layer = new OpenLayers.Layer.Google();
+ clone = layer.clone();
+ t.ok(clone instanceof OpenLayers.Layer.Google, "[default] good instance");
+
+ layer.destroy();
+ clone.destroy();
+
+ // test with alt type
+ layer = new OpenLayers.Layer.Google(null, {type: G_SATELLITE_MAP});
+ clone = layer.clone();
+ t.ok(clone.type === G_SATELLITE_MAP, "[sat] correct type");
+
+ layer.destroy();
+ clone.destroy();
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_Google_isBaseLayer (t) {
+ if(validkey) {
+ t.plan(1);
+
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+
+ t.ok(layer.isBaseLayer, "a default load of google layer responds as a base layer");
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_Google_Translation_lonlat (t) {
+
+ if(validkey) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ map.addLayer(layer);
+
+ // these two lines specify an appropriate translation.
+ // the code afterwards works by itself to test that translation
+ // works correctly both ways.
+ var gLatLng = new GLatLng(50,100);
+ var correspondingOLLonLat = new OpenLayers.LonLat(100,50);
+
+
+ olLonLat = layer.getOLLonLatFromMapObjectLonLat(gLatLng);
+ t.ok(olLonLat.equals(correspondingOLLonLat), "Translation from GLatLng to OpenLayers.LonLat works");
+
+ var transGLatLng = layer.getMapObjectLonLatFromOLLonLat(olLonLat);
+ t.ok( transGLatLng.equals(gLatLng), "Translation from OpenLayers.LonLat to GLatLng works");
+
+ t.ok( layer.getMapObjectLonLatFromOLLonLat(null) == null, "getGLatLngFromOLLonLat(null) returns null");
+ t.ok( layer.getOLLonLatFromMapObjectLonLat(null) == null, "getOLLonLatFromGLatLng(null) returns null");
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_Google_Translation_pixel (t) {
+ if(validkey) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ map.addLayer(layer);
+
+ // these two lines specify an appropriate translation.
+ // the code afterwards works by itself to test that translation
+ // works correctly both ways.
+ var gPoint = new GPoint(50,100);
+ var correspondingOLPixel = new OpenLayers.Pixel(50, 100);
+
+
+ olPixel = layer.getOLPixelFromMapObjectPixel(gPoint);
+ t.ok( olPixel.equals(correspondingOLPixel), "Translation from GPoint to OpenLayers.Pixel works");
+
+ var transGPoint = layer.getMapObjectPixelFromOLPixel(olPixel);
+ t.ok( transGPoint.equals(gPoint), "Translation from OpenLayers.Pixel to GPoint works");
+
+ t.ok( layer.getMapObjectPixelFromOLPixel(null) == null, "getGPointFromOLPixel(null) returns null");
+ t.ok( layer.getOLPixelFromMapObjectPixel(null) == null, "getOLPixelFromGPoint(null) returns null");
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_destroy (t) {
+ if(validkey) {
+ t.plan( 5 );
+
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.Google('Test Layer');
+ map.addLayer(layer);
+
+ layer.destroy();
+
+ t.eq( layer.name, null, "layer.name is null after destroy" );
+ t.eq( layer.div, null, "layer.div is null after destroy" );
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ t.eq( layer.options, null, "layer.options is null after destroy" );
+ t.eq( layer.gmap, null, "layer.gmap is null after destroy" );
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_Goole_forwardMercator(t){
+ if(validkey) {
+ t.plan(2);
+ //Just test that the fowardMercator function still exists.
+ var layer = new OpenLayers.Layer.Google('Test Layer', {'sphericalMercator': true});
+ layer.forwardMercator = function(evt) {
+ t.ok(true,
+ "GoogleMercator.forwardMercator was called and executed." );
+ return;
+ }
+ layer.forwardMercator();
+ //Now test the fowardMercator returns the expected LonLat object
+ var layer = new OpenLayers.Layer.Google('Test Layer', {'sphericalMercator': true});
+ var lonlat2 = new OpenLayers.LonLat(Math.random(),Math.random());
+ var result = layer.forwardMercator(lonlat2.lon, lonlat2.lat);
+ t.ok(result instanceof OpenLayers.LonLat, "OpenLayers.Google.fowardMercator returns LonLat object" );
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_Google_overlay(t) {
+ // Test for #849.
+ if(validkey) {
+ t.plan(1);
+ var map = new OpenLayers.Map( 'map' ,
+ { controls: [] , 'numZoomLevels':20});
+
+ var satellite = new OpenLayers.Layer.Google( "Google Satellite" , {type: G_SATELLITE_MAP, 'maxZoomLevel':18} );
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic', 'transparent':true},
+ {isBaseLayer: false, singleTile: true} );
+
+ map.addLayers([satellite, layer]);
+ map.setCenter(new OpenLayers.LonLat(10.205188,48.857593), 5);
+ map.zoomIn();
+ var size = map.getSize();
+ var px = new OpenLayers.Pixel(size.w, size.h);
+ var br = map.getLonLatFromPixel(px);
+ t.ok(layer.grid[0][0].bounds.containsLonLat(br), "Bottom right pixel is covered by untiled WMS layer");
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+ function test_Layer_Google_isBaseLayer (t) {
+ if(validkey) {
+ t.plan(3);
+ var map = new OpenLayers.Map( 'map' ,
+ { controls: [] , 'numZoomLevels':20});
+
+ var satellite = new OpenLayers.Layer.Google( "Google Satellite" , {type: G_SATELLITE_MAP, 'maxZoomLevel':18} );
+ map.addLayers([satellite]);
+ map.zoomToMaxExtent();
+
+ t.eq(satellite.div.style.display, "", "Satellite layer is visible.");
+ satellite.setVisibility(false);
+ t.eq(satellite.div.style.display, "none", "Satellite layer is not visible.");
+ satellite.setVisibility(true);
+ t.eq(satellite.div.style.display, "block", "Satellite layer is visible.");
+
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_setOpacity(t) {
+ if(validkey) {
+ t.plan(6);
+
+ var map = new OpenLayers.Map("map");
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", // the default
+ {numZoomLevels: 20}
+ );
+ var ghyb = new OpenLayers.Layer.Google(
+ "Google Hybrid",
+ {type: G_HYBRID_MAP, numZoomLevels: 20}
+ );
+ var gsat = new OpenLayers.Layer.Google(
+ "Google Satellite",
+ {type: G_SATELLITE_MAP, numZoomLevels: 22}
+ );
+ map.addLayers([gmap, ghyb, gsat]);
+ map.zoomToMaxExtent();
+
+ var container = map.baseLayer.mapObject.getContainer();
+ var opacityCheck = function(opacity) {
+ var style = container.style;
+ var current = style.opacity === "" ? 1 : parseFloat(style.opacity);
+ if (style.filter && !style.opacity) {
+ current = Number(style.filter.replace(/alpha\(opacity=(.+?)\)/, "$1"));
+ }
+ return (current === opacity);
+ };
+
+ gmap.setOpacity(0.5);
+ t.ok(opacityCheck(0.5), "container opacity set for visible layer");
+
+ ghyb.setOpacity(0.75);
+ t.ok(opacityCheck(0.5), "container opacity not changed if layer not visible");
+ map.setBaseLayer(ghyb);
+ t.ok(opacityCheck(0.75), "container opacity changed to 0.75 when layer becomes visible");
+
+ map.setBaseLayer(gsat);
+ t.ok(opacityCheck(1), "container opacity set to 1 by default");
+ gsat.setOpacity(0.25);
+ t.ok(opacityCheck(0.25), "container opacity changed to 0.25 for visible layer");
+
+ map.setBaseLayer(gmap);
+ t.ok(opacityCheck(0.5), "container opacity set to layer opacity");
+
+ map.destroy();
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_Layer_Google_setGMapVisibility(t) {
+ if(validkey) {
+ t.plan(4);
+
+ var map1 = new OpenLayers.Map('map');
+ var gmap1 = new OpenLayers.Layer.Google("Google Streets");
+ var dummy1 = new OpenLayers.Layer("Dummy", {isBaseLayer: true});
+ map1.addLayers([dummy1, gmap1]);
+ map1.zoomToMaxExtent();
+
+ t.delay_call(2, function() {
+ t.ok(gmap1.termsOfUse.style.display == "none" || gmap1.termsOfUse.style.left == "-9999px", "termsOfUse is not visible");
+ t.eq(gmap1.poweredBy.style.display, "none", "poweredBy is not visible");
+ map1.destroy();
+ });
+
+ var map2 = new OpenLayers.Map('map', {allOverlays: true});
+ var gmap2 = new OpenLayers.Layer.Google("Google Streets", {visibility: false});
+ var dummy2 = new OpenLayers.Layer("Dummy");
+ map2.addLayers([gmap2, dummy2]);
+ map2.zoomToMaxExtent();
+
+ t.delay_call(2, function() {
+ t.ok(gmap2.termsOfUse.style.display == "none" || gmap2.termsOfUse.style.left == "-9999px", "allOverlays:true - termsOfUse is not visible");
+ t.eq(gmap2.poweredBy.style.display, "none", "allOverlays:true - poweredBy is not visible");
+ map2.destroy();
+ });
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+ function test_sphericalMercator(t) {
+
+ if (validkey) {
+ t.plan(4);
+ var map, layer;
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Google();
+ map.addLayer(layer);
+ t.ok(!layer.sphericalMercator, "sphericalMercator false by default");
+ t.eq(map.getProjection(), "EPSG:4326", "4326 by default without sphericalMercator");
+ map.destroy();
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Google(null, {
+ sphericalMercator: true
+ });
+ map.addLayer(layer);
+ t.eq(map.getProjection(), "EPSG:900913", "900913 by default with sphericalMercator");
+ map.destroy();
+
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Google(null, {
+ sphericalMercator: true,
+ projection: "EPSG:102113"
+ });
+ map.addLayer(layer);
+ t.eq(map.getProjection(), "EPSG:102113", "custom code respected with sphericalMercator");
+ map.destroy();
+ } else {
+ t.plan(0);
+ t.debug_print("Google tests can't be run from " +
+ window.location.host);
+ }
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px; height: 500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Google/v3.html b/misc/openlayers/tests/Layer/Google/v3.html
new file mode 100644
index 0000000..5f14b48
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Google/v3.html
@@ -0,0 +1,337 @@
+<html>
+<head>
+ <script src="http://maps.google.com/maps/api/js?sensor=false&amp;v=3.6"></script>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var layer;
+
+ function test_Layer_Google_constructor (t) {
+ t.plan( 5 );
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ map.addLayer(layer);
+
+
+ t.ok( layer instanceof OpenLayers.Layer.Google, "new OpenLayers.Layer.Google returns object" );
+ t.eq( layer.CLASS_NAME, "OpenLayers.Layer.Google", "CLASS_NAME variable set correctly");
+
+ t.eq( layer.name, "Goog Layer", "layer.name is correct" );
+
+ t.ok ( layer.mapObject != null, "GMap Object correctly loaded");
+
+ t.eq(layer.version, "3", "API version 3 detected.");
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+ var layer, clone;
+
+ // test default layer
+ layer = new OpenLayers.Layer.Google();
+ clone = layer.clone();
+ t.ok(clone instanceof OpenLayers.Layer.Google, "[default] good instance");
+
+ layer.destroy();
+ clone.destroy();
+
+ // test with alt type
+ layer = new OpenLayers.Layer.Google(null, {type: google.maps.MapTypeId.SATELLITE});
+ clone = layer.clone();
+ t.ok(clone.type === google.maps.MapTypeId.SATELLITE, "[sat] correct type");
+
+ layer.destroy();
+ clone.destroy();
+ }
+
+ function test_Layer_Google_isBaseLayer (t) {
+ t.plan(1);
+
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+
+ t.ok(layer.isBaseLayer, "a default load of google layer responds as a base layer");
+ }
+
+ function test_Layer_Google_Translation_lonlat (t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ map.addLayer(layer);
+
+ // these two lines specify an appropriate translation.
+ // the code afterwards works by itself to test that translation
+ // works correctly both ways.
+ var gLatLng = new google.maps.LatLng(50,100);
+ // v3 uses sphericalMercator by default
+ var correspondingOLLonLat = layer.forwardMercator(100, 50);
+
+ olLonLat = layer.getOLLonLatFromMapObjectLonLat(gLatLng);
+ t.ok(olLonLat.equals(correspondingOLLonLat), "Translation from GLatLng to OpenLayers.LonLat works");
+
+ var transGLatLng = layer.getMapObjectLonLatFromOLLonLat(olLonLat);
+ t.ok( transGLatLng.equals(gLatLng), "Translation from OpenLayers.LonLat to GLatLng works");
+
+ t.ok( layer.getMapObjectLonLatFromOLLonLat(null) == null, "getGLatLngFromOLLonLat(null) returns null");
+ t.ok( layer.getOLLonLatFromMapObjectLonLat(null) == null, "getOLLonLatFromGLatLng(null) returns null");
+ }
+
+ function test_Layer_Google_Translation_pixel (t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Google('Goog Layer');
+ map.addLayer(layer);
+
+ // these two lines specify an appropriate translation.
+ // the code afterwards works by itself to test that translation
+ // works correctly both ways.
+ var gPoint = new google.maps.Point(50,100);
+ var correspondingOLPixel = new OpenLayers.Pixel(50, 100);
+
+
+ olPixel = layer.getOLPixelFromMapObjectPixel(gPoint);
+ t.ok( olPixel.equals(correspondingOLPixel), "Translation from GPoint to OpenLayers.Pixel works");
+
+ var transGPoint = layer.getMapObjectPixelFromOLPixel(olPixel);
+ t.ok( transGPoint.equals(gPoint), "Translation from OpenLayers.Pixel to GPoint works");
+
+ t.ok( layer.getMapObjectPixelFromOLPixel(null) == null, "getGPointFromOLPixel(null) returns null");
+ t.ok( layer.getOLPixelFromMapObjectPixel(null) == null, "getOLPixelFromGPoint(null) returns null");
+ }
+
+ function test_Layer_destroy (t) {
+ t.plan( 5 );
+
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.Google('Test Layer');
+ map.addLayer(layer);
+
+ layer.destroy();
+
+ t.eq( layer.name, null, "layer.name is null after destroy" );
+ t.eq( layer.div, null, "layer.div is null after destroy" );
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ t.eq( layer.options, null, "layer.options is null after destroy" );
+ t.eq( layer.gmap, null, "layer.gmap is null after destroy" );
+ }
+
+ function test_Layer_Goole_forwardMercator(t){
+ t.plan(2);
+ //Just test that the fowardMercator function still exists.
+ var layer = new OpenLayers.Layer.Google('Test Layer', {'sphericalMercator': true});
+ layer.forwardMercator = function(evt) {
+ t.ok(true,
+ "GoogleMercator.forwardMercator was called and executed." );
+ return;
+ }
+ layer.forwardMercator();
+ //Now test the fowardMercator returns the expected LonLat object
+ var layer = new OpenLayers.Layer.Google('Test Layer', {'sphericalMercator': true});
+ var lonlat2 = new OpenLayers.LonLat(Math.random(),Math.random());
+ var result = layer.forwardMercator(lonlat2.lon, lonlat2.lat);
+ t.ok(result instanceof OpenLayers.LonLat, "OpenLayers.Google.fowardMercator returns LonLat object" );
+ }
+
+ function test_Layer_Google_overlay(t) {
+ // Test for #849.
+ t.plan(1);
+ var map = new OpenLayers.Map( 'map' ,
+ { controls: [] , 'numZoomLevels':20});
+
+ var satellite = new OpenLayers.Layer.Google( "Google Satellite" , {type: google.maps.MapTypeId.SATELLITE, 'maxZoomLevel':18} );
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic', 'transparent':true},
+ {isBaseLayer: false, singleTile: true, displayOutsideMaxExtent: true} );
+
+ map.addLayers([satellite, layer]);
+ map.setCenter(new OpenLayers.LonLat(10.205188,48.857593), 5);
+ map.zoomIn();
+ var size = map.getSize();
+ var px = new OpenLayers.Pixel(size.w, size.h);
+ var br = map.getLonLatFromPixel(px);
+ t.ok(layer.grid[0][0].bounds.containsLonLat(br), "Bottom right pixel is covered by untiled WMS layer");
+ }
+ function test_Layer_Google_isBaseLayer (t) {
+ t.plan(3);
+ var map = new OpenLayers.Map( 'map' ,
+ { controls: [] , 'numZoomLevels':20});
+
+ var satellite = new OpenLayers.Layer.Google( "Google Satellite" , {type: google.maps.MapTypeId.SATELLITE, 'maxZoomLevel':18} );
+ map.addLayers([satellite]);
+ map.zoomToMaxExtent();
+
+ t.eq(satellite.div.style.display, "", "Satellite layer is visible.");
+ satellite.setVisibility(false);
+ t.eq(satellite.div.style.display, "none", "Satellite layer is not visible.");
+ satellite.setVisibility(true);
+ t.eq(satellite.div.style.display, "block", "Satellite layer is visible.");
+ }
+
+ function test_allOverlays_invisible(t) {
+
+ t.plan(1);
+
+ var map = new OpenLayers.Map('map', {allOverlays: true});
+
+ var osm = new OpenLayers.Layer.OSM();
+ var gmap = new OpenLayers.Layer.Google("Google Streets", {visibility: false});
+
+ // keep track of last argument to setGMapVisibility
+ var visible;
+ var original = gmap.setGMapVisibility;
+ gmap.setGMapVisibility = function(vis) {
+ visible = vis;
+ original.apply(gmap, arguments);
+ }
+
+ map.addLayers([osm, gmap]);
+ map.zoomToMaxExtent();
+
+ t.ok(visible === false, "setGMapVisibility last called with false");
+
+ map.destroy();
+
+ }
+
+ function test_allOverlays_pan(t) {
+
+ t.plan(8);
+
+ var origPrecision = OpenLayers.Util.DEFAULT_PRECISION;
+ // GMaps v3 seems to use a default precision of 13, which is lower
+ // than what we use in OpenLayers.
+ // See http://trac.osgeo.org/openlayers/ticket/3059
+ OpenLayers.Util.DEFAULT_PRECISION = 13;
+
+ var map = new OpenLayers.Map('map', {allOverlays: true});
+
+ var gmap = new OpenLayers.Layer.Google("Google Streets");
+ var osm = new OpenLayers.Layer.OSM();
+ map.addLayers([gmap, osm]);
+
+ var origin = new OpenLayers.LonLat(1000000, 6000000);
+ map.setCenter(origin, 4);
+ var resolution = map.getResolution();
+
+ var dx, dy, center, expected;
+
+ // confirm that panning works with Google visible
+ dx = 100, dy = -100;
+ map.pan(dx, dy, {animate: false});
+ center = map.getCenter();
+ expected = new OpenLayers.LonLat(
+ origin.lon + (resolution * dx),
+ origin.lat - (resolution * dy)
+ );
+ t.eq(center.lon, expected.lon, "x panning with Google visible " + dx + ", " + dy);
+ t.eq(center.lat, expected.lat, "y panning with Google visible " + dx + ", " + dy);
+ map.pan(-dx, -dy, {animate: false});
+ center = map.getCenter();
+ t.eq(center.lon, origin.lon, "x panning with Google visible " + (-dx) + ", " + (-dy));
+ t.eq(center.lat, origin.lat, "y panning with Google visible " + (-dx) + ", " + (-dy));
+
+ // confirm that panning works with Google invisible
+ gmap.setVisibility(false);
+ dx = 100, dy = -100;
+ map.pan(dx, dy, {animate: false});
+ center = map.getCenter();
+ expected = new OpenLayers.LonLat(
+ origin.lon + (resolution * dx),
+ origin.lat - (resolution * dy)
+ );
+ t.eq(center.lon, expected.lon, "x panning with Google invisible " + dx + ", " + dy);
+ t.eq(center.lat, expected.lat, "y panning with Google invisible " + dx + ", " + dy);
+ map.pan(-dx, -dy, {animate: false});
+ center = map.getCenter();
+ t.eq(center.lon, origin.lon, "x panning with Google invisible " + (-dx) + ", " + (-dy));
+ t.eq(center.lat, origin.lat, "y panning with Google invisible " + (-dx) + ", " + (-dy));
+
+ map.destroy();
+ OpenLayers.Util.DEFAULT_PRECISION = origPrecision;
+ }
+
+ function test_wrapDateLine(t) {
+ t.plan(2);
+
+ var origPrecision = OpenLayers.Util.DEFAULT_PRECISION;
+ // Our default precision is very high - millimeters should be enough.
+ // See http://trac.osgeo.org/openlayers/ticket/3059
+ OpenLayers.Util.DEFAULT_PRECISION = 12;
+
+ var map = new OpenLayers.Map("map");
+
+ var gmap = new OpenLayers.Layer.Google("Google Streets");
+ map.addLayer(gmap);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+
+ var center;
+
+ // pan to the edge of the world
+ map.pan(256, 0, {animate: false});
+ center = map.getCenter();
+ t.eq(center.lon, 20037508.34, "edge of the world");
+ // pan off the edge of the world
+ map.pan(100, 0, {animate: false});
+ center = map.getCenter();
+ var expect = OpenLayers.Util.toFloat(100 * map.getResolution() - 20037508.34);
+ t.eq(center.lon, expect, "magically back in the western hemisphere");
+
+ map.destroy();
+ OpenLayers.Util.DEFAULT_PRECISION = origPrecision;
+ }
+
+ function test_respectDateLine(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map("map");
+
+ var gmap = new OpenLayers.Layer.Google("Google Streets", {wrapDateLine: false});
+ map.addLayer(gmap);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+
+ var center;
+
+ // pan to the edge of the world
+ map.pan(256, 0, {animate: false});
+ center = map.getCenter();
+ t.eq(center.lon, 20037508.34, "edge of the world");
+ // pan off the edge of the world
+ map.pan(100, 0, {animate: false});
+ center = map.getCenter();
+ t.eq(center.lon, 20037508.34, "whew, still on the edge");
+
+ map.destroy();
+
+ }
+
+ function test_moveViewportDiv(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map', {
+ projection: 'EPSG:3857',
+ center: [0, 0],
+ zoom: 1
+ });
+ var gmap = new OpenLayers.Layer.Google();
+ map.addLayer(gmap);
+
+ t.delay_call(4, function() {
+ t.ok(map.viewPortDiv.parentNode !== map.div, 'viewport moved inside GMaps');
+
+ var osm = new OpenLayers.Layer.OSM();
+ map.addLayer(osm);
+ map.setBaseLayer(osm);
+
+ t.ok(map.viewPortDiv.parentNode === map.div, 'viewport moved back');
+ });
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px; height: 500px"></div>
+</body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/Layer/Grid.html b/misc/openlayers/tests/Layer/Grid.html
new file mode 100644
index 0000000..495a9ab
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Grid.html
@@ -0,0 +1,1593 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://vmap0.tiles.osgeo.org/wms/vmap0";
+ var params = {layers: 'basic', format: 'image/png'};
+
+ /**
+ * NOTE TO READER:
+ *
+ * Some of the tests on the Grid class actually use the WMS class.
+ * This is because WMS is a subclass of Grid and it implements the
+ * core functions which are necessary to test the tile-generation
+ * mechanism.
+ *
+ */
+
+
+ function test_constructor (t) {
+ t.plan( 7 );
+
+ layer = new OpenLayers.Layer.Grid(name, url, params, null);
+ t.ok( layer instanceof OpenLayers.Layer.Grid, "returns OpenLayers.Layer.Grid object" );
+ t.eq( layer.buffer, 0, "buffer default is 0");
+ t.eq( layer.ratio, 1.5, "ratio default is 1.5");
+ t.eq( layer.numLoadingTiles, 0, "numLoadingTiles starts at 0");
+ t.ok( layer.tileClass === OpenLayers.Tile.Image, "tileClass default is OpenLayers.Tile.Image");
+ t.eq( layer.className, 'olLayerGrid', "className default is olLayerGrid");
+
+ var obj = {};
+ var func = function() {};
+ layer.events.register('tileloaded', obj, func);
+
+ t.ok( layer.events.listeners['tileloaded'].length == 1, "one listener for tileloaded after register");
+ }
+
+ function test_constructor_singleTile(t) {
+ t.plan(2);
+ layer = new OpenLayers.Layer.Grid(name, url, params, {singleTile: true});
+ t.eq( layer.className, 'olLayerGridSingleTile', "className default is olLayerGridSingleTile");
+ t.eq( layer.removeBackBufferDelay, 0, "removeBackBufferDelay default is 0");
+ }
+
+ function test_setMap(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ layer = new OpenLayers.Layer.Grid(name, url, params, null);
+ map.addLayer(layer);
+ t.ok(OpenLayers.Element.hasClass(layer.div, "olLayerGrid"),
+ "olLayerGrid class assigned to layer div");
+ map.destroy();
+ }
+
+ function test_setMap_singleTile(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.Grid(name, url, params, {singleTile: true});
+ map.addLayer(layer);
+ t.ok(OpenLayers.Element.hasClass(layer.div, "olLayerGridSingleTile"),
+ "olLayerGridSingleTile class assigned to layer div");
+ map.destroy();
+ }
+
+ function test_Layer_Grid_inittiles (t) {
+ t.plan( 2 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {buffer:2});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq( layer.grid.length, 8, "Grid rows is correct." );
+ t.eq( layer.grid[0].length, 7, "Grid cols is correct." );
+
+ }
+
+ function test_Layer_Grid_tileClass(t) {
+ t.plan(2);
+
+ var myTileClass = OpenLayers.Class(OpenLayers.Tile, {});
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {
+ tileClass: myTileClass
+ });
+ map.addLayer(layer);
+
+ t.ok(layer.tileClass === myTileClass, "tileClass is set");
+ var instance = layer.addTile(new OpenLayers.Bounds(-10, 10, 50, 100),
+ new OpenLayers.Pixel(10, 12));
+
+ t.ok(instance instanceof myTileClass, "addTile returns type is correct");
+
+ map.destroy();
+ }
+
+ function test_Layer_Grid_clearTiles (t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0));
+
+ var numTiles = layer.grid.length * layer.grid[0].length;
+
+ //our count of how many times tile.destroy() is called
+ tilesDeleted = 0;
+
+ //this will get set to false if we try to destroy a tile that has
+ // not been unhookedv
+ allTilesUnhooked = true;
+
+ OpenLayers.Tile.Image.prototype._destroy =
+ OpenLayers.Tile.Image.prototype.destroy;
+
+ OpenLayers.Tile.Image.prototype.destroy = function() {
+ if (!this.unhooked) {
+ allTilesUnhooked = false;
+ }
+ tilesDeleted++;
+ }
+
+ layer.removeTileMonitoringHooks = function(tile) {
+ tile.unhooked = true;
+ }
+
+ layer.clearGrid();
+
+ t.ok( layer.grid != null, "layer.grid does not get nullified" );
+ t.eq(tilesDeleted, numTiles, "all tiles destroy()ed properly");
+ t.ok(allTilesUnhooked, "all tiles unhooked before being destroyed");
+ t.eq(layer.gridResolution, null, "gridResolution set to null");
+
+ OpenLayers.Tile.Image.prototype.destroy =
+ OpenLayers.Tile.Image.prototype._destroy;
+
+ }
+
+
+ function test_Layer_Grid_getTilesBounds(t) {
+ t.plan(4);
+
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+
+
+ //normal grid
+ var bl = { bounds: new OpenLayers.Bounds(1,2,2,3)};
+ var tr = { bounds: new OpenLayers.Bounds(2,3,3,4)};
+ layer.grid = [ [6, tr],
+ [bl, 7]];
+
+ var bounds = layer.getTilesBounds();
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+
+ t.ok( bounds.equals(testBounds), "getTilesBounds() returns correct bounds");
+
+ //no tiles
+ layer.grid = [];
+ bounds = layer.getTilesBounds();
+
+ t.ok(bounds == null, "getTilesBounds() on a tile-less grid returns null");
+
+
+ //singleTile
+ var singleTile = { bounds: new OpenLayers.Bounds(1,2,3,4)};
+ layer.grid = [ [ singleTile ] ];
+ bounds = layer.getTilesBounds();
+
+ t.ok( bounds.equals(testBounds), "getTilesBounds() returns correct bounds");
+
+ //world wrapped around the dateline
+ var bl = { bounds: new OpenLayers.Bounds(0,-90,180,90)};
+ var tr = { bounds: new OpenLayers.Bounds(-180,-90,0,90)};
+ layer.grid = [[bl, tr]];
+
+ var bounds = layer.getTilesBounds();
+ var testBounds = new OpenLayers.Bounds(0,-90,360,90);
+
+ t.ok( bounds.equals(testBounds), "getTilesBounds() returns correct bounds");
+
+ }
+
+ function test_Layer_Grid_getResolution(t) {
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+
+ map.zoom = 5;
+
+ t.eq( layer.getResolution(), 0.0439453125, "getResolution() returns correct value");
+ }
+
+ function test_Layer_Grid_getZoomForExtent(t) {
+ t.plan( 2 );
+ var bounds, zoom;
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+
+ bounds = new OpenLayers.Bounds(10,10,12,12);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 8, "getZoomForExtent() returns correct value");
+
+ bounds = new OpenLayers.Bounds(10,10,100,100);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 2, "getZoomForExtent() returns correct value");
+ }
+
+ function test_moveGriddedTiles(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {buffer: 2});
+ map.addLayer(layer);
+ map.setCenter([0, 0], 5);
+ var count = 0;
+ layer.shiftColumn = function(prepend) {
+ ++count;
+ OpenLayers.Layer.WMS.prototype.shiftColumn.apply(this, arguments);
+ }
+ map.moveTo([15, 0]);
+ t.delay_call(.5, function() {
+ t.eq(count, 1, "column shifted once");
+ });
+ }
+
+ function test_Layer_Grid_moveTo(t) {
+
+ t.plan(13);
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ layer.destroy = function() {}; //we're going to do funky things with the grid
+ layer.applyBackBuffer = function() {}; // backbuffering isn't under test here
+ map.addLayer(layer);
+ map.setCenter([-10, 0], 5);
+
+ var log = [];
+
+ //make sure null bounds doesnt cause script error.
+ // no test necessary, just action
+ map.getExtent = function() { return null; }
+ layer.singleTile = false;
+ layer.moveTo(); //checks to make sure null bounds doesnt break us
+
+
+ //observing globals
+ layer.initSingleTile = function(bounds) {
+ g_WhichFunc = "InitSingle";
+ g_Bounds = bounds;
+ };
+ layer.initGriddedTiles = function(bounds) {
+ g_WhichFunc = "InitGridded";
+ g_Bounds = bounds;
+ };
+ layer.moveGriddedTiles = function() {
+ g_WhichFunc = "MoveGridded";
+ g_Bounds = layer.map.getExtent();
+ };
+ var clearTestBounds = function() {
+ g_WhichFunc = null;
+ g_Bounds = null;
+ };
+
+ //default map extent (tested every time below)
+ b = new OpenLayers.Bounds(0,0,100,100);
+ map.getExtent = function() {
+ return b;
+ };
+ var tilesBounds = null;
+ layer.getTilesBounds = function() {
+ return tilesBounds;
+ }
+
+
+//FORCE
+
+ //empty grid
+ layer.grid = [];
+ //grid
+ clearTestBounds();
+ layer.singleTile = false;
+ layer.moveTo()
+ t.ok(g_Bounds.equals(b), "if grid is empty, initGridded called");
+
+ //singletile
+ clearTestBounds();
+ layer.singleTile = true;
+ layer.moveTo()
+ t.ok(g_Bounds.equals(b), "if grid is empty, initSingleTile called");
+
+ //zoomChanged
+ zoomChanged = true;
+ layer.grid = [ [ {} ] ];
+
+ //grid
+ clearTestBounds();
+ layer.singleTile = false;
+ layer.moveTo(null, zoomChanged);
+ t.ok(g_Bounds.equals(b), "if layer has grid but zoomChanged is called, initGridded called");
+
+ //singletile
+ clearTestBounds();
+ layer.singleTile = true;
+ layer.moveTo(null, zoomChanged);
+ t.ok(g_Bounds.equals(b), "if layer has grid but zoomChanged is called, initSingleTile called");
+
+
+//NO FORCE
+ zoomChanged = false;
+ layer.grid = [ [ {} ] ];
+
+ //single tile
+ layer.singleTile = true;
+
+ //DRAGGING
+ var dragging = true;
+
+ //in bounds
+ clearTestBounds();
+ tilesBounds = new OpenLayers.Bounds(-10,-10,110,110);
+ layer.moveTo(null, zoomChanged, dragging);
+ t.ok(g_Bounds == null, "if dragging and tile in bounds, no init()");
+
+ //out bounds
+ clearTestBounds();
+ tilesBounds = new OpenLayers.Bounds(10,10,120,120);
+ layer.moveTo(null, zoomChanged, dragging);
+ t.ok(g_Bounds == null, "if dragging and tile out of bounds, no init()");
+
+ //NOT DRAGGING
+ dragging = false;
+
+ //in bounds
+ clearTestBounds();
+ tilesBounds = new OpenLayers.Bounds(-10,-10,110,110);
+ layer.moveTo(null, zoomChanged, dragging);
+ t.ok(g_Bounds == null, "if dragging and tile in bounds, no init()");
+
+ //out bounds
+ clearTestBounds();
+ tilesBounds = new OpenLayers.Bounds(10,10,120,120);
+ layer.moveTo(null, zoomChanged, dragging);
+ t.ok(g_WhichFunc == "InitSingle", "if not dragging and tile out of bounds, we call initSingleTile()");
+ t.ok(g_Bounds.equals(b), "if not dragging and tile out of bounds, we call initSingleTile() with correct bounds");
+
+
+ //gridded
+ layer.grid = [ [ {position: new OpenLayers.Pixel(0,0)} ] ];
+ layer.singleTile = false;
+
+ //regular move
+ clearTestBounds();
+ tilesBounds = new OpenLayers.Bounds(10,10,120,120);
+ g_WhichFunc = null;
+ layer.moveTo(null, zoomChanged);
+ t.eq(g_WhichFunc, "MoveGridded", "if tiles not drastically out of bounds, we call moveGriddedTile()");
+ t.ok(g_Bounds.equals(b), "if tiles not drastically out of bounds, we call moveGriddedTile() with correct bounds");
+
+ // drastic pan
+ clearTestBounds();
+ tilesBounds = new OpenLayers.Bounds(-150,-150,-120,-120);
+ layer.moveTo(null, zoomChanged);
+ t.ok(g_WhichFunc == "InitGridded", "if tiles drastically out of bounds, we call initGriddedTile()");
+ t.ok(g_Bounds.equals(b), "if tiles drastically out of bounds, we call initGriddedTile() with correct bounds");
+ }
+
+ /** THIS WOULD BE WHERE THE TESTS WOULD GO FOR
+ *
+ * -insertColumn
+ * -insertRow
+ *
+
+ function 08_Layer_Grid_insertColumn(t) {
+ }
+
+ function 09_Layer_Grid_insertRow(t) {
+ }
+
+ *
+ */
+
+ function test_Layer_Grid_clone(t) {
+ t.plan(7);
+
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.Grid(name, url, params);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ // if we clone when tiles are still loading, this should not influence the clone
+ layer.numLoadingTiles = 1;
+ var clone = layer.clone();
+ t.eq( clone.numLoadingTiles, 0, "numLoadingTiles should be reset");
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+ t.ok( clone.grid.length == 0, "clone creates a new array instead");
+
+ t.eq(clone.backBuffer, null, "no backbuffer from original");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ layer.tileSize.w += 40;
+
+ t.eq( clone.tileSize.w, 500, "changing layer.tileSize does not change clone.tileSize -- a fresh copy was made, not just copied reference");
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ layer.grid = null;
+ }
+
+ function test_Layer_Grid_setTileSize(t) {
+ t.plan(1);
+
+ OpenLayers.Layer.HTTPRequest.prototype._setTileSize =
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize;
+
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize = function(size) {
+ g_Size = size;
+ };
+
+
+ layer = new OpenLayers.Layer.Grid(name, url, params, {
+ singleTile: true
+ });
+ mapSize = new OpenLayers.Size(100,1000);
+ layer.map = {
+ getSize: function() { return mapSize; }
+ }
+
+ g_Size = null;
+ layer.setTileSize();
+
+ var idealSize = new OpenLayers.Size(150,1500);
+ t.ok( g_Size && g_Size.equals(idealSize), "correctly calculated tile size passed to superclass setTileSize() function");
+
+ OpenLayers.Layer.HTTPRequest.prototype.setTileSize =
+ OpenLayers.Layer.HTTPRequest.prototype._setTileSize;
+ }
+
+ function test_Layer_Grid_initSingleTile(t) {
+ t.plan( 11 );
+
+ layer = new OpenLayers.Layer.Grid(name, url, params, {
+ singleTile: true,
+ ratio: 2
+ });
+
+ var bounds = new OpenLayers.Bounds(-10,10,50,100);
+
+ var desiredTileBounds = new OpenLayers.Bounds(-40,-35,80,145);
+ var desiredUL = new OpenLayers.LonLat(-40,145);
+
+ translatedPX = {};
+ layer.map = {
+ getLayerPxFromLonLat: function(ul) {
+ t.ok(ul.lon === desiredUL.lon && ul.lat === desiredUL.lat, "correct ul passed to translation");
+ return translatedPX;
+ },
+ getResolution: function() {
+ }
+ }
+
+ var newTile = {
+ draw: function() {
+ t.ok(true, "newly created tile has been drawn");
+ }
+ };
+ layer.addTile = function(tileBounds, px) {
+ t.ok(tileBounds.equals(desiredTileBounds), "correct tile bounds passed to addTile to create new tile");
+ t.ok(px == translatedPX, "correct tile px passed to addTile to create new tile");
+ return newTile;
+ };
+ layer.addTileMonitoringHooks = function(tile) {
+ t.ok(tile == newTile, "adding monitoring hooks to the newly added tile");
+ };
+ layer.removeExcessTiles = function(x,y) {
+ t.ok(x == 1 && y == 1, "removeExcessTiles called")
+ };
+
+
+ layer.grid = [];
+ layer.initSingleTile(bounds);
+
+ t.ok(layer.grid[0][0] == newTile, "grid's 0,0 is set to the newly created tile");
+
+ var tile = {
+ moveTo: function(tileBounds, px) {
+ t.ok(tileBounds.equals(desiredTileBounds), "correct tile bounds passed to tile.moveTo()");
+ t.ok(px == translatedPX, "correct tile px passed to tile.moveTo()");
+ }
+ };
+ layer.grid = [[ tile ]];
+ layer.initSingleTile(bounds);
+
+ }
+
+ function test_Layer_Grid_addTileMonitoringHooks(t) {
+ t.plan(18);
+
+ layer = new OpenLayers.Layer.Grid();
+ layer.events = {
+ 'triggerEvent': function(str, evt) {
+ g_events.push([str, evt]);
+ }
+ }
+
+ var tile = {
+ events: {
+ register: function(name, obj, func) {
+ g_registered[name] = [obj, func];
+ },
+ on: function(obj) {
+ for (var o in obj) {
+ if (obj.hasOwnProperty(o)) {
+ tile.events.register(o, obj.scope, obj[o]);
+ }
+ }
+ }
+ },
+ imgDiv: {className: ''}
+ }
+
+ g_registered = {};
+ g_events = [];
+
+ layer.addTileMonitoringHooks(tile);
+
+ //loadstart
+ t.ok(tile.onLoadStart != null, "onLoadStart function created and added to tile");
+ entry = g_registered["loadstart"];
+ t.ok( entry && entry[0] == layer && entry[1] == tile.onLoadStart, "loadstart correctly registered");
+
+ layer.numLoadingTiles = 0;
+ g_events = [];
+ tile.onLoadStart.apply(layer);
+
+ t.eq(g_events[0][0], "loadstart", "loadstart event triggered when numLoadingTiles is 0");
+ t.eq(layer.numLoadingTiles, 1, "numLoadingTiles incremented");
+ t.eq(g_events[1][0], "tileloadstart", "tileloadstart event triggered");
+
+ g_events = [];
+ tile.onLoadStart.apply(layer);
+ t.eq(g_events.length, 1, "tileloadstart, but not loadstart triggered when numLoadingTiles is not 0");
+ t.eq(layer.numLoadingTiles, 2, "numLoadingTiles incremented");
+
+
+ //loadend
+ t.ok(tile.onLoadEnd != null, "onLoadEnd function created and added to tile");
+ entry = g_registered["loadend"];
+ t.ok( entry && entry[0] == layer && entry[1] == tile.onLoadEnd, "loadend correctly registered");
+
+ g_events = [];
+ tile.onLoadError.apply(layer);
+ t.eq(g_events[0][0], "tileerror", "tileerror triggered");
+ t.ok(g_events[0][1].tile === tile, "tile passed as tile property to event object");
+
+ layer.numLoadingTiles = 2;
+ g_events = [];
+ tile.onLoadEnd.apply(layer, [{}]);
+ t.eq(g_events[0][0], "tileloaded", "tileloaded triggered when numLoadingTiles is > 0");
+ t.ok(g_events[0][1].tile === tile, "tile passed as tile property to event object");
+ t.eq(g_events.length, 1, "loadend event not triggered when numLoadingTiles is > 0");
+ t.eq(layer.numLoadingTiles, 1, "numLoadingTiles decremented");
+
+
+ g_events = [];
+ layer.grid = [[{}]]; // to prevent error in updateBackBuffer
+ tile.onLoadEnd.apply(layer, [{}]);
+ t.eq(g_events[0][0], "tileloaded", "tileloaded triggered when numLoadingTiles is 0");
+ t.eq(g_events[1][0], "loadend", "loadend event triggered when numLoadingTiles is 0");
+ t.eq(layer.numLoadingTiles, 0, "numLoadingTiles decremented");
+ }
+
+ function test_Layer_Grid_removeTileMonitoringHooks(t) {
+ t.plan(2);
+
+ layer = new OpenLayers.Layer.Grid();
+
+ var tile = {
+ onLoadStart: {},
+ onLoadEnd: {},
+ unload: function() {},
+ events: {
+ unregister: function(name, obj, func) {
+ g_unregistered[name] = [obj, func];
+ },
+ un: OpenLayers.Events.prototype.un
+ }
+ }
+
+ g_unregistered = {};
+
+ layer.removeTileMonitoringHooks(tile);
+
+ entry = g_unregistered["loadstart"];
+ t.ok( entry && entry[0] == layer && entry[1] == tile.onLoadStart, "loadstart correctly unregistered");
+
+ entry = g_unregistered["loadend"];
+ t.ok( entry && entry[0] == layer && entry[1] == tile.onLoadEnd, "loadend correctly unregistered");
+ }
+
+ function test_Layer_Grid_tileSizeIsInteger(t) {
+ t.plan(1);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Grid(name, url, params, {
+ singleTile: true,
+ ratio: 1.5
+ });
+ map.addLayers([layer]);
+
+ width = layer.tileSize.w;
+ height = layer.tileSize.h;
+ t.ok(width == parseInt(width) && height == parseInt(height), "calculated tileSize width/height are integer values");
+ }
+ function test_Layer_Grid_getTileBounds(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map2", {zoomMethod: null});
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ map.zoomIn();
+ var bounds = layer.getTileBounds(new OpenLayers.Pixel(200,200));
+ t.eq(bounds.toBBOX(), "-180,-90,0,90", "get tile bounds returns correct bounds");
+ map.pan(200,0, {animate:false});
+ var bounds = layer.getTileBounds(new OpenLayers.Pixel(200,200));
+ t.eq(bounds.toBBOX(), "0,-90,180,90", "get tile bounds returns correct bounds after pan");
+ }
+
+ function test_Layer_Grid_moveTo_buffer_calculation (t) {
+ t.plan(6);
+
+ var map = new OpenLayers.Map( 'map3' ); // odd map size
+ var layer0 = new OpenLayers.Layer.WMS( "0 buffer: OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}, {'buffer':0} );
+ map.addLayer(layer0);
+
+ var layer1 = new OpenLayers.Layer.WMS( "1 buffer: OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}, {'buffer':1} );
+ map.addLayer(layer1);
+
+ var layer2 = new OpenLayers.Layer.WMS( "2 buffer: OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}, {'buffer':2} );
+ map.addLayer(layer2);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 4);
+ t.eq( layer0.grid.length, 3, "Grid rows with buffer:0" );
+ map.setBaseLayer(layer1);
+ t.eq( layer1.grid.length, 5, "Grid rows with buffer:1" );
+ map.setBaseLayer(layer2);
+ t.eq( layer2.grid.length, 7, "Grid rows with buffer:2" );
+
+ // zooming in on Greenland exercises the bug from pre-r4313
+ map.setCenter(new OpenLayers.LonLat(0, 90), 4);
+ t.eq( layer0.grid.length, 3, "Grid rows with buffer:0" );
+ map.setBaseLayer(layer1);
+ t.eq( layer1.grid.length, 5, "Grid rows with buffer:1" );
+ map.setBaseLayer(layer2);
+ t.eq( layer2.grid.length, 7, "Grid rows with buffer:2" );
+ }
+
+ function test_Layer_Grid_destroy (t) {
+
+ t.plan( 9 );
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ layer = new OpenLayers.Layer.Grid(name, url, params);
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
+
+
+ //test with tile creation
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 10);
+ map.setCenter(new OpenLayers.LonLat(1,1));
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[1][1];
+ t.eq( tile.imgDiv.className, "olTileImage", "Tile has an image" );
+
+ var removeBackBufferCalled = false;
+ layer.removeBackBuffer = function() {
+ removeBackBufferCalled = true;
+ };
+
+ layer.destroy();
+ t.eq( tile.imgDiv, null, "Tile destroyed" );
+ t.eq( layer.timerId, null, "Tile loading timeout cleared");
+ t.ok( layer.grid == null, "tiles appropriately destroyed")
+ t.ok( removeBackBufferCalled, "destroy calls removeBackBuffer");
+
+ // destroy after remove from map
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 10);
+ map.removeLayer(layer);
+ layer.destroy();
+ t.eq( layer.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
+ }
+
+ function test_setOpacity(t) {
+ t.plan(5);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true,
+ opacity: '0.6'
+ });
+ map.addLayer(layer);
+ // setCenter adds tiles to the layer's grid
+ map.setCenter(new OpenLayers.LonLat(0, 0), 5);
+
+ var tile = layer.grid[0][0], tileImg = tile.imgDiv;
+
+ tile.onImageLoad(); // simulate an image load event
+ t.eq(layer.opacity, '0.6', 'layer opacity value is correct');
+ t.eq(parseFloat(tileImg.style.opacity), 0.6, 'tile opacity is correct');
+
+ layer.setOpacity('0.2');
+ t.eq(layer.opacity, '0.2', 'layer opacity value is correct');
+ t.eq(parseFloat(tileImg.style.opacity), 0.2, 'tile opacity is correct');
+
+ tile = layer.addTile(new OpenLayers.Bounds(1, 2, 3, 4),
+ new OpenLayers.Pixel(5, 6));
+ tile.draw(); // add tile to the grid
+ tile.onImageLoad(); // simulate an image load event
+ t.eq(parseFloat(tile.imgDiv.style.opacity), 0.2, "tile opacity is correc");
+
+ map.destroy();
+ }
+
+ function test_getServerResolution(t) {
+
+ t.plan(4);
+
+ var layer = new OpenLayers.Layer.Grid('', '', {}, {});
+ var res;
+
+ res = layer.getServerResolution(1);
+ t.eq(res, 1, '[1] getServerResolution return value is correct');
+
+ layer.serverResolutions = [2, 1];
+ res = layer.getServerResolution(1);
+ t.eq(res, 1, '[2] getServerResolution return value is correct');
+
+ layer.serverResolutions = [2];
+ res = layer.getServerResolution(1);
+ t.eq(res, 2, '[3] getServerResolution return value is correct');
+
+ var exc;
+ layer.serverResolutions = [0.5];
+ res = layer.getServerResolution(1);
+ t.eq(res, 0.5, '[4] getServerResolution return value is correct');
+ }
+
+ function test_getServerZoom(t) {
+
+ t.plan(5);
+
+ var resolution, zoom;
+ var map = new OpenLayers.Map('map', {
+ resolutions: [8, 4, 2, 1, 0.5],
+ getResolution: function() {
+ return resolution;
+ }
+ });
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {isBaseLayer: true});
+ map.addLayer(layer);
+
+ resolution = 8;
+ zoom = layer.getServerZoom();
+ t.eq(zoom, 0, '[1] getServerZoom return value is correct');
+
+ resolution = 4;
+ zoom = layer.getServerZoom();
+ t.eq(zoom, 1, '[2] getServerZoom return value is correct');
+
+ layer.serverResolutions = [2, 1];
+ resolution = 1;
+ zoom = layer.getServerZoom();
+ t.eq(zoom, 1, '[3] getServerZoom return value is correct');
+
+ layer.serverResolutions = [2];
+ resolution = 0.5;
+ zoom = layer.getServerZoom();
+ t.eq(zoom, 0, '[4] getServerZoom return value is correct');
+
+ var exc;
+ layer.serverResolutions = [0.5];
+ resolution = 1;
+ zoom = layer.getServerZoom();
+ t.eq(zoom, 0, '[4] getServerZoom return value is correct');
+
+ map.destroy();
+ }
+
+ function test_moveTo_scale(t) {
+
+ t.plan(11);
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [32, 16, 8, 4, 2, 1],
+ zoomMethod: null
+ });
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true,
+ serverResolutions: [32, 16, 8]
+ });
+ map.addLayer(layer);
+
+ // initial resolution is 8
+ map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+
+ // test initial conditions
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 1, 'layer div scale is 1');
+
+ // change from resolution 8 to 4
+ map.zoomTo(3);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 2, '[8->4] layer div scale is 2');
+
+ // change from resolution 8 to 2
+ map.zoomTo(2); map.zoomTo(4);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 4, '[8->2] layer div scale is 4');
+
+ // change from resolution 8 to 1
+ map.zoomTo(2); map.zoomTo(5);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 8, '[8->1] layer div scale is 8');
+
+ // change from resolution 4 to 2
+ map.zoomTo(3); map.zoomTo(4);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 4, '[4->2] layer div scale is 4');
+
+ // change from resolution 4 to 1
+ map.zoomTo(3); map.zoomTo(5);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 8, '[4->1] layer div scale is 8');
+
+ // change from resolution 2 to 1
+ map.zoomTo(4); map.zoomTo(5);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 8, '[2->1] layer div scale is 8');
+
+ // change from resolution 1 to 2
+ map.zoomTo(5); map.zoomTo(4);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 4, '[1->2] layer div scale is 4');
+
+ // change from resolution 1 to 4
+ map.zoomTo(5); map.zoomTo(3);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 2, '[1->4] layer div scale is 2');
+
+ // change from resolution 1 to 8
+ map.zoomTo(5); map.zoomTo(2);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 1, '[1->8] layer div scale is 1');
+
+ // change from resolution 1 to 16
+ map.zoomTo(5); map.zoomTo(1);
+ t.eq(parseInt(layer.div.lastChild.style.width) / layer.tileSize.w, 1, '[1->16] layer div scale is 1');
+
+ map.destroy();
+ }
+
+ function test_moveTo_backbuffer_singletile(t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [1, 0.5, 0.025],
+ zoomMethod: null
+ });
+ var resolution;
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ singleTile: true,
+ isBaseLayer: true,
+ transitionEffect: 'resize',
+ applyBackBuffer: function(res) {
+ resolution = res;
+ }
+ });
+ map.addLayer(layer);
+
+ // initial resolution is 0.025
+ resolution = undefined;
+ map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+ t.eq(resolution, 0.025,
+ 'applyBackBuffer not called on first moveTo');
+
+ // move to (-90, 45)
+ resolution = undefined;
+ map.setCenter(new OpenLayers.LonLat(-90, 45));
+ t.eq(resolution, 0.025,
+ 'applyBackBuffer called when map is moved');
+
+ // change to resolution 1
+ resolution = undefined;
+ map.zoomTo(0);
+ t.eq(resolution, 1,
+ 'applyBackBuffer called when map is zoomed out');
+
+ // change to resolution 0.5
+ resolution = undefined;
+ map.zoomTo(1);
+ t.eq(resolution, 0.5,
+ 'applyBackBuffer called when map is zoomed out');
+
+ map.destroy();
+ }
+
+ function test_moveTo_backbuffer(t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [1, 0.5, 0.025],
+ zoomMethod: null
+ });
+ var resolution;
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true,
+ transitionEffect: 'resize',
+ applyBackBuffer: function(res) {
+ resolution = res;
+ }
+ });
+ map.addLayer(layer);
+
+ // initial resolution is 0.025
+ resolution = undefined;
+ map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+ t.eq(resolution, 0.025,
+ 'applyBackBuffer not called on first moveTo');
+
+ // move to (-90, 45)
+ resolution = undefined;
+ map.setCenter(new OpenLayers.LonLat(-90, 45));
+ t.eq(resolution, undefined,
+ 'applyBackBuffer not called when map is moved');
+
+ // change to resolution 1
+ resolution = undefined;
+ map.zoomTo(0);
+ t.eq(resolution, 1,
+ 'applyBackBuffer called when map is zoomed out');
+
+ // change to resolution 0.5
+ map.zoomTo(1);
+ t.eq(resolution, 0.5,
+ 'applyBackBuffer called when map is zoomed out');
+
+ map.destroy();
+ }
+
+ function test_applyBackBuffer(t) {
+ t.plan(12);
+
+ var map = new OpenLayers.Map('map2');
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ var backBuffer;
+
+ // test #1
+ layer.createBackBuffer = function() {
+ return;
+ };
+ layer.applyBackBuffer(2);
+ t.eq(layer.backBuffer, undefined,
+ 'back buffer not created if createBackBuffer returns undefined');
+
+ // test #2
+ layer.createBackBuffer = function() {
+ backBuffer = document.createElement('div');
+ return backBuffer;
+ };
+ layer.gridResolution = 32;
+ layer.backBufferResolution = 2;
+ layer.grid[0][0].bounds = new OpenLayers.Bounds(0, 1, 1, 0);
+ layer.applyBackBuffer(2);
+ t.ok(layer.backBuffer === backBuffer,
+ 'back buffer set in layer');
+ t.ok(map.layerContainerDiv.firstChild === backBuffer,
+ 'back buffer inserted as first child');
+ t.eq(layer.backBuffer.style.left, '250px',
+ 'back buffer has correct left');
+ t.eq(layer.backBuffer.style.top, '275px',
+ 'back buffer has correct top');
+
+ // test #3
+ layer.createBackBuffer = function() {
+ backBuffer = document.createElement('div');
+ return backBuffer;
+ };
+ layer.gridResolution = 32;
+ layer.backBufferResolution = 2;
+ layer.grid[0][0].bounds = new OpenLayers.Bounds(0, 1, 1, 0);
+ map.layerContainerOriginPx.x = 20;
+ map.layerContainerOriginPx.y = -20;
+ layer.applyBackBuffer(2);
+ t.ok(layer.backBuffer === backBuffer,
+ 'back buffer set in layer');
+ t.ok(map.layerContainerDiv.firstChild === backBuffer,
+ 'back buffer inserted as first child');
+ t.eq(layer.backBuffer.style.left, '230px',
+ 'back buffer has correct left');
+ t.eq(layer.backBuffer.style.top, '295px',
+ 'back buffer has correct top');
+
+ // test #4
+ // and a back buffer in the layer and do as if back buffer removal
+ // has been scheduled, and test that applyBackBuffer removes the
+ // back buffer and clears the timer
+ layer.createBackBuffer = function() {
+ return;
+ };
+ backBuffer = document.createElement('div');
+ map.layerContainerDiv.insertBefore(backBuffer, map.baseLayer.div);
+ layer.backBuffer = backBuffer;
+ layer.backBufferTimerId = 'fake';
+ layer.applyBackBuffer(2);
+ t.ok(backBuffer !== map.layerContainerDiv.firstChild,
+ 'back buffer is not first child of layer container div');
+ t.eq(layer.backBuffer, null,
+ 'back buffer not set in layer');
+ t.eq(layer.backBufferTimerId, null,
+ 'back buffer timer cleared');
+ map.destroy();
+ }
+
+ function test_createBackBuffer(t) {
+ t.plan(9);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ var createBackBuffer = OpenLayers.Tile.Image.prototype.createBackBuffer;
+
+ var backBuffer;
+
+ OpenLayers.Tile.Image.prototype.createBackBuffer = function() {
+ return;
+ };
+ backBuffer = layer.createBackBuffer();
+ t.ok(backBuffer != undefined,
+ 'createBackBuffer returns a back buffer');
+ t.eq(backBuffer.childNodes.length, 0,
+ 'returned back buffer has no child nodes');
+
+ OpenLayers.Tile.Image.prototype.createBackBuffer = function() {
+ return document.createElement('div');
+ };
+
+ layer.transitionEffect = 'map-resize';
+ backBuffer = layer.createBackBuffer();
+ t.ok(backBuffer.style.zIndex == 99, 'z-index of backbuffer correct for "map-resize".');
+ layer.removeBackBuffer();
+
+ layer.transitionEffect = 'resize';
+ backBuffer = layer.createBackBuffer();
+ t.ok(backBuffer.style.zIndex == layer.getZIndex() - 1, 'z-index of backbuffer correct for "resize",');
+
+ layer.backBufferResolution = 1;
+ layer.gridResolution = 1;
+ layer.backBuffer = backBuffer;
+ layer.div.appendChild(backBuffer);
+ layer.backBufferLonLat = {lon: 0, lat: 0};
+ layer.applyBackBuffer(1);
+ t.ok(backBuffer != undefined,
+ 'createBackBuffer returns a back buffer');
+ t.eq(backBuffer.childNodes[0].style.left, '0px',
+ 'first tile has correct left');
+ t.eq(backBuffer.childNodes[0].style.top, '0px',
+ 'first tile has correct top');
+ t.eq(backBuffer.childNodes[1].style.left, '256px',
+ 'second tile has correct left');
+ t.eq(backBuffer.childNodes[1].style.top, '0px',
+ 'second tile has correct top');
+
+ map.destroy();
+ OpenLayers.Tile.Image.prototype.createBackBuffer = createBackBuffer;
+ }
+
+ function test_removeBackBuffer(t) {
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {isBaseLayer: true});
+ map.addLayer(layer);
+
+ // add a fake back buffer
+ var backBuffer = document.createElement('div');
+ layer.backBuffer = backBuffer;
+ layer.div.appendChild(backBuffer);
+ layer.backBufferResolution = 32;
+
+ layer.removeBackBuffer();
+ t.eq(layer.backBuffer, null, 'backBuffer set to null in layer');
+ t.eq(layer.backBufferResolution, null,
+ 'backBufferResolution set to null in layer');
+ t.ok(backBuffer.parentNode !== layer.div,
+ 'back buffer removed from layer');
+
+ map.destroy();
+ }
+
+ function test_backbuffer_replace(t) {
+ t.plan(6);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.WMS('', '../../img/blank.gif');
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ layer.grid[1][1].onImageLoad();
+ layer.mergeNewParams({foo: 'bar'});
+ var tile = layer.grid[1][1];
+ t.ok(OpenLayers.Element.hasClass(tile.imgDiv, 'olTileReplacing'), 'tile is marked for being replaced');
+ t.ok(document.getElementById(tile.id + '_bb'), 'backbuffer created for tile');
+ // simulate a css declaration where '.olTileReplacing' sets display
+ // to none.
+ tile.imgDiv.style.display = 'none';
+ tile.onImageLoad();
+ t.ok(!OpenLayers.Element.hasClass(tile.imgDiv, 'olTileReplacing'), 'tile replaced, no longer marked');
+ t.ok(!document.getElementById(tile.id + '_bb'), 'backbuffer removed for tile');
+
+ layer.mergeNewParams({foo: 'baz'});
+ tile = layer.grid[1][1];
+ // simulate a css declaration where '.olTileReplacing' does not set
+ // display to none.
+ tile.imgDiv.style.display = 'block';
+ tile.onImageLoad();
+ t.ok(!OpenLayers.Element.hasClass(tile.imgDiv, 'olTileReplacing'), 'tile replaced, no longer marked');
+ t.ok(document.getElementById(tile.id + '_bb'), 'backbuffer not removed for visible tile');
+ }
+
+ function test_backbuffer_replace_singleTile(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('', '../../img/blank.gif', null, {
+ singleTile: true,
+ transitionEffect: 'resize'
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.delay_call(1, function() {
+ map.zoomIn();
+ var tile = layer.grid[0][0];
+ t.ok(!OpenLayers.Element.hasClass(tile.imgDiv, 'olTileReplacing'), 'tile is not marked for being replaced for singleTile layers');
+ });
+ }
+
+ function test_singleTile_move_and_zoom(t) {
+
+ //
+ // In single tile mode with no transition effect, we insert a non-scaled
+ // backbuffer when the layer is moved. But if a zoom occurs right after
+ // a move, i.e. before the new image is received, we need to remove the
+ // backbuffer, or an ill-positioned image will be visible during the
+ // zoom transition.
+ //
+
+ t.plan(4);
+
+ var map = new OpenLayers.Map('map', {zoomMethod: null});
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true,
+ transitionEffect: null,
+ singleTile: true,
+ ratio: 1.1
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // move
+ map.setCenter(new OpenLayers.LonLat(50, 50));
+ t.ok(layer.backBuffer && layer.backBuffer.parentNode === layer.div,
+ 'backbuffer inserted after map move');
+ t.eq(layer.backBuffer.style.left, '-25px');
+ t.eq(layer.backBuffer.style.top, '-28px');
+ // zoom
+ map.zoomTo(1);
+ t.eq(layer.backBuffer, null,
+ 'back buffer removed when zooming');
+
+ map.destroy();
+ }
+
+ function test_backbuffer_scaled_layer(t) {
+ t.plan(12);
+
+ //
+ // set up
+ //
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [32, 16, 8, 4, 2, 1],
+ zoomMethod: null,
+ tileManager: null
+ });
+ var layer = new OpenLayers.Layer.WMS(
+ "WMS",
+ window.location.href + "#",
+ null,
+ {transitionEffect: "resize"}
+ );
+
+ layer.serverResolutions = [32, 16, 8];
+
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+
+ var origCreateBackBuffer = OpenLayers.Tile.Image.prototype.createBackBuffer;
+ OpenLayers.Tile.Image.prototype.createBackBuffer = function() {
+ return document.createElement('div');
+ };
+
+ // we want to control when the back buffer is removed
+ var removeBackBuffer = OpenLayers.Function.bind(
+ layer.removeBackBuffer, layer);
+ layer.removeBackBuffer = function() {};
+
+ //
+ // test
+ //
+
+ // change resolution from 8 to 4
+ map.zoomTo(3);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 1,
+ '[8->4] back buffer not scaled');
+ removeBackBuffer();
+
+ // change resolution from 8 to 2
+ map.zoomTo(2); removeBackBuffer(); map.zoomTo(4);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 1,
+ '[8->2] back buffer not scaled');
+ removeBackBuffer();
+
+ // change resolution from 16 to 4
+ map.zoomTo(1); removeBackBuffer(); map.zoomTo(3);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 2,
+ '[16->4] back buffer width is as expected');
+ t.eq(parseInt(layer.backBuffer.firstChild.style.height) / parseInt(layer.div.lastChild.style.height), 2,
+ '[16->4] back buffer height is as expected');
+ removeBackBuffer();
+
+ // change resolution from 32 to 1
+ map.zoomTo(0); removeBackBuffer(); map.zoomTo(5);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 4,
+ '[32->1] back buffer width is as expected');
+ t.eq(parseInt(layer.backBuffer.firstChild.style.height) / parseInt(layer.div.lastChild.style.height), 4,
+ '[32->1] back buffer height is as expected');
+ removeBackBuffer();
+
+ // change resolution from 4 to 2
+ map.zoomTo(3); removeBackBuffer(); map.zoomTo(4);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 1,
+ '[4->2] back buffer not scaled');
+ removeBackBuffer();
+
+ // change resolution from 4 to 1
+ map.zoomTo(3); removeBackBuffer(); map.zoomTo(5);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 1,
+ '[4->1] back buffer not scaled');
+ removeBackBuffer();
+
+ // change resolution from 1 to 4
+ map.zoomTo(5); removeBackBuffer(); map.zoomTo(3);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 1,
+ '[1->4] back buffer not scaled');
+ removeBackBuffer();
+
+ // change resolution from 4 to 8
+ map.zoomTo(3); removeBackBuffer(); map.zoomTo(2);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 1,
+ '[4->8] back buffer not scaled');
+ removeBackBuffer();
+
+ // change resolution from 4 to 16
+ map.zoomTo(3); removeBackBuffer(); map.zoomTo(1);
+ t.eq(parseInt(layer.backBuffer.firstChild.style.width) / parseInt(layer.div.lastChild.style.width), 0.5,
+ '[4->16] back buffer width is as expected');
+ t.eq(parseInt(layer.backBuffer.firstChild.style.height) / parseInt(layer.div.lastChild.style.height), 0.5,
+ '[4->16] back buffer height is as expected');
+ removeBackBuffer();
+
+ //
+ // tear down
+ //
+
+ map.destroy();
+ OpenLayers.Tile.Image.prototype.createBackBuffer = origCreateBackBuffer
+ }
+
+
+ function test_delayed_back_buffer_removal(t) {
+ //
+ // Test that the delaying of the back buffer removal behaves
+ // as expected.
+ //
+
+ t.plan(5);
+
+ // set up
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [32, 16, 8, 4, 2, 1],
+ zoomMethod: null
+ });
+ var layer = new OpenLayers.Layer.WMS('', '', {}, {
+ isBaseLayer: true,
+ transitionEffect: 'resize'
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ map.zoomTo(1);
+
+ t.ok(layer.backBuffer === map.layerContainerDiv.firstChild,
+ '[a] back buffer is first child of layer container div');
+
+ // Mark one tile loaded and add an element to the backbuffer, to see if
+ // backbuffer removal gets scheduled.
+ layer.backBuffer.appendChild(document.createElement('img'));
+ layer.grid[1][1].onImageLoad();
+
+ t.ok(layer.backBufferTimerId !== null,
+ '[a] back buffer scheduled for removal');
+
+ var backBuffer = layer.backBuffer;
+
+ map.zoomTo(2);
+
+ t.ok(layer.backBuffer !== backBuffer,
+ '[b] a new back buffer was created');
+ t.ok(layer.backBuffer === map.layerContainerDiv.firstChild,
+ '[b] back buffer is first child of layer container div');
+ t.ok(layer.backBufferTimerId === null,
+ '[b] back buffer no longer scheduled for removal');
+
+ // tear down
+
+ map.destroy();
+ }
+
+ function test_getGridData(t) {
+ t.plan(12);
+
+ var layer = new OpenLayers.Layer.Grid(null, null, null, {
+ isBaseLayer: true, getURL: function() {
+ return "/bogus/path/to/tile";
+ }
+ });
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ controls: [],
+ center: [0, 0],
+ zoom: 1
+ });
+
+ // get tile data for [0, 0]
+ var data = layer.getTileData({lon: 0, lat: 0});
+ t.ok(data && data.tile, "[0, 0]: got tile data");
+ t.eq(data.i, 0, "[0, 0]: i");
+ t.eq(data.j, 128, "[0, 0]: j");
+ t.ok(
+ data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
+ "[0, 0]: tile bounds " + data.tile.bounds.toString()
+ );
+
+ // get tile data for [-110, 45]
+ data = layer.getTileData({lon: -110, lat: 45});
+ t.ok(data && data.tile, "[-110, 45]: got tile data");
+ t.eq(data.i, 99, "[-110, 45]: i");
+ t.eq(data.j, 64, "[-110, 45]: j");
+ t.ok(
+ data.tile.bounds.equals({left: -180, bottom: -90, right: 0, top: 90}),
+ "[-110, 45]: tile bounds " + data.tile.bounds.toString()
+ );
+
+ // get tile data for [0, 300] (north of grid)
+ data = layer.getTileData({lon: 0, lat: 300})
+ t.eq(data, null, "[0, 300]: north of grid");
+
+ // get tile data for [400, 0] (east of grid)
+ data = layer.getTileData({lon: 400, lat: 0})
+ t.eq(data, null, "[400, 0]: east of grid");
+
+ // get tile data for [0, -500] (south of grid)
+ data = layer.getTileData({lon: 0, lat: -500})
+ t.eq(data, null, "[0, -500]: south of grid");
+
+ // get tile data for [-200, 0] (west of grid)
+ data = layer.getTileData({lon: -200, lat: 0})
+ t.eq(data, null, "[-200, 0]: west of grid");
+
+ map.destroy();
+
+ }
+
+ function test_getGridData_wrapped(t) {
+ t.plan(18);
+
+ var layer = new OpenLayers.Layer.Grid(null, null, null, {
+ isBaseLayer: true, getURL: function() {
+ return "/bogus/path/to/tile";
+ },
+ wrapDateLine: true
+ });
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ controls: [],
+ center: [-50, 0],
+ zoom: 1
+ });
+
+ // get tile data for [0, 0]
+ var data = layer.getTileData({lon: 0, lat: 0});
+ t.ok(data && data.tile, "[0, 0]: got tile data");
+ t.eq(data.i, 0, "[0, 0]: i");
+ t.eq(data.j, 128, "[0, 0]: j");
+ t.ok(
+ data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
+ "[0, 0]: tile bounds " + data.tile.bounds.toString()
+ );
+
+ // get tile data for [-110, 45]
+ data = layer.getTileData({lon: -110, lat: 45});
+ t.ok(data && data.tile, "[-110, 45]: got tile data");
+ t.eq(data.i, 99, "[-110, 45]: i");
+ t.eq(data.j, 64, "[-110, 45]: j");
+ t.ok(
+ data.tile.bounds.equals({left: -180, bottom: -90, right: 0, top: 90}),
+ "[-110, 45]: tile bounds " + data.tile.bounds.toString()
+ );
+
+ // get tile data for [0, 300] (north of grid)
+ data = layer.getTileData({lon: 0, lat: 300})
+ t.eq(data, null, "[0, 300]: north of grid");
+
+ // get tile data for [400, 0] (equivalent to [40, 0] and visible on map)
+ data = layer.getTileData({lon: 400, lat: 0})
+ t.ok(data && data.tile, "[400, 0]: got tile data");
+ t.eq(data.i, 56, "[400, 0]: i");
+ t.eq(data.j, 128, "[400, 0]: j");
+ t.ok(
+ data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
+ "[400, 0]: tile bounds " + data.tile.bounds.toString()
+ );
+
+ // get tile data for [0, -500] (south of grid)
+ data = layer.getTileData({lon: 0, lat: -500})
+ t.eq(data, null, "[0, -500]: south of grid");
+
+ // get tile data for [-200, 0] (equivalent to [160, 0] and wrapped to west side map)
+ data = layer.getTileData({lon: -200, lat: 0})
+ t.ok(data && data.tile, "[-200, 0]: got tile data");
+ t.eq(data.i, 227, "[-200, 0]: i");
+ t.eq(data.j, 128, "[-200, 0]: j");
+ t.ok(
+ data.tile.bounds.equals({left: 0, bottom: -90, right: 180, top: 90}),
+ "[-200, 0]: tile bounds " + data.tile.bounds.toString()
+ );
+
+ map.destroy();
+
+ }
+
+ function test_removeExcessTiles(t) {
+ t.plan(15);
+
+ /*
+ * Set up
+ */
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Grid('name', '/url',
+ {}, {isBaseLayer: true});
+ map.addLayer(layer);
+
+ function newTile(id) {
+ var t = new OpenLayers.Tile(layer,
+ new OpenLayers.Pixel(1, 1),
+ new OpenLayers.Bounds(1, 1, 1, 1));
+ t._id = id;
+ return t;
+ }
+
+ layer.grid = [
+ [newTile(1), newTile(2), newTile(3)],
+ [newTile(4), newTile(5)],
+ [newTile(6), newTile(7), newTile(8)]
+ ];
+
+ // create a clone to be able to test whether
+ // tiles have been destroyed or not
+ var grid = [
+ layer.grid[0].slice(),
+ layer.grid[1].slice(),
+ layer.grid[2].slice()
+ ];
+
+ /*
+ * Test
+ */
+
+ layer.removeExcessTiles(2, 2);
+
+ t.eq(layer.grid.length, 2, 'grid has two rows');
+ t.eq(layer.grid[0].length, 2, 'row #1 has two columns');
+ t.eq(layer.grid[0][0]._id, 1, 'row #1 col #1 includes expected tile');
+ t.eq(layer.grid[0][1]._id, 2, 'row #1 col #2 includes expected tile');
+ t.eq(layer.grid[1].length, 2, 'row #2 has two columns');
+ t.eq(layer.grid[1][0]._id, 4, 'row #2 col #1 includes expected tile');
+ t.eq(layer.grid[1][1]._id, 5, 'row #2 col #2 includes expected tile');
+
+ t.ok(grid[0][0].events != null, 'tile 0,0 not destroyed');
+ t.ok(grid[0][1].events != null, 'tile 0,1 not destroyed');
+ t.ok(grid[0][2].events == null, 'tile 0,2 destroyed');
+ t.ok(grid[1][0].events != null, 'tile 1,0 not destroyed');
+ t.ok(grid[1][1].events != null, 'tile 1,1 not destroyed');
+ t.ok(grid[2][0].events == null, 'tile 2,0 destroyed');
+ t.ok(grid[2][1].events == null, 'tile 2,1 destroyed');
+ t.ok(grid[2][2].events == null, 'tile 2,2 destroyed');
+
+ /*
+ * Tear down
+ */
+
+ map.destroy();
+ }
+
+ function test_addOptions(t) {
+ t.plan(15);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {buffer:2});
+ map.addLayer(layer);
+ t.eq(layer.tileSize, map.getTileSize(), "layer's tile size is equal to the map's tile size");
+ t.ok(layer.removeBackBufferDelay !== 0, "removeBackBufferDelay should not be 0 since we are not singleTile");
+ t.eq(layer.className, "olLayerGrid", "className correct for gridded mode");
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq(layer.grid.length, 8, "Grid rows is correct.");
+ t.eq(layer.grid[0].length, 7, "Grid cols is correct.");
+ t.eq(layer.singleTile, false, "singleTile is false by default");
+ layer.addOptions({singleTile: true});
+ t.eq(layer.removeBackBufferDelay, 0, "removeBackBufferDelay set to 0 since singleTile is true");
+ t.eq(layer.singleTile, true, "singleTile set to true");
+ t.eq(layer.className, "olLayerGridSingleTile", "className correct for singleTile mode");
+ t.eq(layer.grid.length, 1, "Grid rows is correct.");
+ t.eq(layer.grid[0].length, 1, "Grid cols is correct.");
+ t.eq(layer.tileSize, new OpenLayers.Size(748, 823), "tile size changed");
+ layer.addOptions({singleTile: false});
+ t.eq(layer.grid.length, 8, "Grid rows is correct.");
+ t.eq(layer.grid[0].length, 7, "Grid cols is correct.");
+ t.eq(layer.tileSize, map.getTileSize(), "layer's tile size is equal to the map's tile size");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:499px;height:549px;display:none"></div>
+<div id="map2" style="width:500px;height:550px;display:none"></div>
+<div id="map3" style="width:594px;height:464px;display:none"></div>
+<div id="map4" style="width:768px;height:512px;display:none"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/HTTPRequest.html b/misc/openlayers/tests/Layer/HTTPRequest.html
new file mode 100644
index 0000000..dcb6e23
--- /dev/null
+++ b/misc/openlayers/tests/Layer/HTTPRequest.html
@@ -0,0 +1,229 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ var name = "Test Layer";
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/png'};
+ var options = { chicken: 151, foo: "bar" };
+
+ function test_Layer_HTTPRequest_constructor (t) {
+ t.plan( 6 );
+
+ layer = new OpenLayers.Layer.HTTPRequest(name, url, params, options);
+
+ t.ok( layer instanceof OpenLayers.Layer.HTTPRequest, "new OpenLayers.Layer.HTTPRequest returns correctly typed object" );
+
+ // correct bubbling up to Layer.initialize()
+ t.eq( layer.name, name, "layer.name is correct" );
+ t.ok( ((layer.options["chicken"] == 151) && (layer.options["foo"] == "bar")), "layer.options correctly set" );
+
+ // HTTPRequest-specific properties
+ t.eq( layer.url, url, "layer.name is correct" );
+ t.ok( ((layer.params["map"] == '/mapdata/vmap_wms.map') &&
+ (layer.params["layers"] == "basic") &&
+ (layer.params["format"] == "image/png")), "layer.params correctly set" );
+
+ layer = new OpenLayers.Layer.HTTPRequest(name, url, null, {params: params});
+ t.ok( ((layer.params["map"] == '/mapdata/vmap_wms.map') &&
+ (layer.params["layers"] == "basic") &&
+ (layer.params["format"] == "image/png")), "layer.params correctly set from options" );
+ }
+
+ function test_Layer_HTTPRequest_clone (t) {
+ t.plan( 6 );
+
+ var toClone = new OpenLayers.Layer.HTTPRequest(name, url, params, options);
+ toClone.chocolate = 5;
+
+ var layer = toClone.clone();
+
+ t.eq(layer.chocolate, 5, "correctly copied randomly assigned property");
+
+ t.ok( layer instanceof OpenLayers.Layer.HTTPRequest, "new OpenLayers.Layer.HTTPRequest returns correctly typed object" );
+
+ // correct bubbling up to Layer.initialize()
+ t.eq( layer.name, name, "layer.name is correct" );
+ t.eq( layer.options, options, "layer.options correctly set" );
+
+ // HTTPRequest-specific properties
+ t.eq( layer.url, url, "layer.name is correct" );
+ t.ok( ((layer.params["map"] == '/mapdata/vmap_wms.map') &&
+ (layer.params["layers"] == "basic") &&
+ (layer.params["format"] == "image/png")), "layer.params correctly set" );
+
+ }
+
+ function test_Layer_HTTPRequest_setUrl (t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.HTTPRequest(name, url, params, options);
+
+ layer.setUrl("foo");
+ t.eq( layer.url, "foo", "setUrl() works");
+ }
+
+ function test_Layer_HTTPRequest_mergeNewParams (t) {
+ t.plan( 8 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.HTTPRequest(name, url, params, options);
+ map.addLayer(layer);
+
+ var scope = {some: "scope"}, log = [];
+ map.events.on({
+ changelayer: function(e) {
+ log.push({layer: e.layer, property: e.property, scope: this});
+ },
+ scope: scope
+ });
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.layers, "sooper", "mergeNewParams() overwrites well");
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() adds well");
+ t.eq( log.length, 1, "mergeNewParams() triggers changelayer once");
+ t.ok( log[0].layer == layer, "mergeNewParams() passes changelayer listener the expected layer");
+ t.ok( log[0].property == "params", "mergeNewParams() passes changelayer listener the property \"params\"");
+ t.eq( log[0].scope, scope, "mergeNewParams() executes changelayer listener with expected scope");
+
+ newParams.chickpeas = 151;
+
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() makes clean copy of hash");
+
+ layer.redraw = function() {
+ t.ok(true, "layer.mergeNewParams calls layer.redraw");
+ }
+ layer.mergeNewParams();
+ }
+
+ function test_Layer_HTTPRequest_getFullRequestString (t) {
+
+ tParams = { layers: 'basic',
+ format: 'image/png'};
+
+ t.plan( 12 );
+
+ // without ?
+ tUrl = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl + '?' + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works for url sans ?");
+
+
+ // with ?
+ tUrl = "http://octo.metacarta.com/cgi-bin/mapserv?";
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works for url with ?");
+
+ // with ?param1=5
+ tUrl = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5";
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl + '&' + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works for url with ?param1=5");
+
+ // with ?param1=5&
+ tUrl = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5&format=image/jpeg";
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl + '&' + OpenLayers.Util.getParameterString({'layers':'basic'}), "getFullRequestString() doesn't override already-existing params in URL");
+
+
+ // with ?param1=5&
+ tUrl = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5&";
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works for url with ?param1=5&");
+
+
+
+ // passing in new params
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString( { chicken: 6,
+ layers:"road" } );
+ t.eq(str, tUrl + OpenLayers.Util.getParameterString({layers: 'road', format: "image/png", chicken: 6}), "getFullRequestString() works for passing in new params");
+
+ // layer with null params
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, null, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl + OpenLayers.Util.getParameterString({}), "getFullRequestString() works for layer with null params");
+
+ // layer with null params passing in new params
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, null, null);
+ str = layer.getFullRequestString( { chicken: 6,
+ layers:"road" } );
+ t.eq(str, tUrl + OpenLayers.Util.getParameterString({chicken: 6, layers: "road"}), "getFullRequestString() works for layer with null params passing in new params");
+
+ // with specified altUrl parameter
+ tUrl = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.HTTPRequest(name, "chicken", tParams, null);
+ str = layer.getFullRequestString(null, tUrl);
+ t.eq(str, tUrl + '?' + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works with specified altUrl parameter");
+
+ // single url object
+ tUrl = ["http://octo.metacarta.com/cgi-bin/mapserv"];
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl[0] + '?' + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works for list of one url");
+
+ // two url object
+ tUrl = ["http://octo.metacarta.com/cgi-bin/mapserv","http://labs.metacarta.com/cgi-bin/mapserv"];
+ layer = new OpenLayers.Layer.HTTPRequest(name, tUrl, tParams, null);
+ str = layer.getFullRequestString();
+ t.eq(str, tUrl[1] + '?' + OpenLayers.Util.getParameterString(tParams), "getFullRequestString() works for list of two urls");
+ str = layer.getFullRequestString({'a':'b'});
+ t.eq(str, tUrl[0] + '?' + OpenLayers.Util.getParameterString(OpenLayers.Util.extend(tParams,{'a':'b'})), "getFullRequestString() works for list of two urls and is deterministic");
+
+ }
+
+ function test_Layer_HTTPRequest_selectUrl (t) {
+ t.plan( 4 );
+
+ layer = new OpenLayers.Layer.HTTPRequest(name, url, params, options);
+
+ urls = ["wms1", "wms2", "wms3", "wms4"];
+ t.eq( layer.selectUrl("bbox=-180,0,0,90", urls), "wms3", "selectUrl(-90,-180) returns 4" );
+ t.eq( layer.selectUrl("bbox=-180,-90,0,0", urls), "wms1", "selectUrl(90,-180) returns 3" );
+ t.eq( layer.selectUrl("bbox=0,90,180,0", urls), "wms1", "selectUrl(-90,180) returns 1" );
+ t.eq( layer.selectUrl("bbox=0,0,180,90", urls), "wms4", "selectUrl(90,180) returns 2" );
+ }
+
+ function test_Layer_HTTPRequest_destroy (t) {
+ t.plan( 6 );
+
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.HTTPRequest("Test Layer",
+ "http://www.openlayers.org",
+ { foo: 2, bar: 3},
+ { opt1: 8, opt2: 9});
+
+ map.addLayer(layer);
+ layer.destroy();
+
+ // Ensure Layer.destroy() is called
+ t.eq( layer.name, null, "layer.name is null after destroy" );
+ t.eq( layer.div, null, "layer.div is null after destroy" );
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ t.eq( layer.options, null, "layer.options is null after destroy" );
+
+
+ // Specific to HTTPRequest
+ t.eq( layer.url, null, "layer.url is null after destroy" );
+ t.eq( layer.params, null, "layer.params is null after destroy" );
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Image.html b/misc/openlayers/tests/Layer/Image.html
new file mode 100644
index 0000000..05ab5c3
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Image.html
@@ -0,0 +1,164 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ function test_Layer_Image_constructor (t) {
+ t.plan( 13 );
+
+ var options = { chicken: 151, foo: "bar", projection: "EPSG:4326" };
+ var layer = new OpenLayers.Layer.Image('Test Layer',
+ 'http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif',
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288), options);
+
+ t.ok( layer instanceof OpenLayers.Layer.Image, "new OpenLayers.Layer.Image returns object" );
+ t.eq( layer.CLASS_NAME, "OpenLayers.Layer.Image", "CLASS_NAME variable set correctly");
+
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ t.ok( layer.id != null, "Layer is given an id");
+ t.eq( layer.projection.getCode(), "EPSG:4326", "default layer projection correctly set");
+ t.ok( ((layer.chicken == 151) && (layer.foo == "bar")), "layer.options correctly set to Layer Object" );
+ t.ok( ((layer.options["chicken"] == 151) && (layer.options["foo"] == "bar")), "layer.options correctly backed up" );
+
+ options.chicken = 552;
+
+ t.eq( layer.options["chicken"], 151 , "layer.options correctly made fresh copy" );
+
+ t.eq( layer.isBaseLayer, true, "Default img layer is base layer" );
+
+ layer = new OpenLayers.Layer.Image('Test Layer',
+ 'http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif',
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288));
+ t.ok( layer instanceof OpenLayers.Layer.Image, "new OpenLayers.Layer.Image returns object" );
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ t.ok( layer.projection == null, "default layer projection correctly set");
+ t.ok( layer.options instanceof Object, "layer.options correctly initialized as a non-null Object" );
+ }
+
+ function test_maxExtent(t) {
+ t.plan(2);
+ var layer;
+
+ // test that the image extent is set as the default maxExtent
+ var extent = new OpenLayers.Bounds(1, 2, 3, 4);
+ var size = new OpenLayers.Size(2, 2);
+ layer = new OpenLayers.Layer.Image("Test", "foo", extent, size);
+ t.eq(layer.maxExtent.toString(), extent.toString(), "extent set as default maxExtent");
+
+ // test that the maxExtent can be set explicitly
+ var maxExtent = new OpenLayers.Bounds(10, 20, 30, 40);
+ layer = new OpenLayers.Layer.Image("Test", "foo", extent, size, {maxExtent: maxExtent});
+ t.eq(layer.maxExtent.toString(), maxExtent.toString(), "maxExtent can be set explicitly");
+
+ }
+
+ function test_Layer_Image_tileTests (t) {
+ t.plan(7);
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.Image('Test Layer',
+ 'http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif',
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288));
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ // no resolution info was sent, so maxResolution should be calculated
+ // by aspectRatio*extent/size (this is the pixel aspect ratio)
+ var aspectRatio = (layer.extent.getHeight() / layer.size.h) /
+ (layer.extent.getWidth() / layer.size.w);
+ t.eq(aspectRatio, layer.aspectRatio, "aspectRatio is properly set");
+ var maxExtent = aspectRatio * layer.extent.getWidth() / layer.size.w;
+ t.eq(maxExtent, layer.maxResolution, "maxResolution is properly set");
+
+ t.eq(layer.tile.position.x,-42, "Tile x positioned correctly at maxextent");
+ t.eq(layer.tile.position.y,106, "Tile y positioned correctly at maxextent");
+ t.eq(layer.tile.url, "http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif", "URL is correct");
+ map.zoomIn();
+ t.eq(layer.tile.url, "http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif", "URL is correct");
+ layer.setUrl('http://labs.metacarta.com/wms/vmap0?LAYERS=basic&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&FORMAT=image%2Fjpeg&SRS=EPSG%3A4326&BBOX=-180,-90,0,90&WIDTH=256&HEIGHT=256');
+ t.eq(layer.tile.url, "http://labs.metacarta.com/wms/vmap0?LAYERS=basic&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&EXCEPTIONS=application%2Fvnd.ogc.se_inimage&FORMAT=image%2Fjpeg&SRS=EPSG%3A4326&BBOX=-180,-90,0,90&WIDTH=256&HEIGHT=256", "URL is correct after setURL");
+ }
+/******
+ *
+ *
+ * HERE IS WHERE SOME TESTS SHOULD BE PUT TO CHECK ON THE LONLAT-PX TRANSLATION
+ * FUNCTIONS AND RESOLUTION AND GETEXTENT GETZOOMLEVEL, ETC
+ *
+ *
+ */
+
+
+ function test_Layer_Image_destroy_before_use (t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.Image('Test', 'http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif', new OpenLayers.Bounds(-180, -88.759, 180, 88.759), new OpenLayers.Size(580, 288));
+ map.addLayer(layer);
+ map.removeLayer(layer);
+ layer.destroy();
+ t.ok(true, "destroy() didn't throw an error");
+ }
+
+ function test_Layer_Image_destroy (t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.Image('Test Layer',
+ 'http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif',
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288));
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ layer.destroy();
+
+ t.eq( layer.name, null, "layer.name is null after destroy" );
+ t.eq( layer.div, null, "layer.div is null after destroy" );
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ t.eq( layer.options, null, "layer.options is null after destroy" );
+
+ }
+
+ function test_loadEvents(t) {
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.Image(
+ 'Test', '../../img/blank.gif',
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288)
+ );
+
+ map.addLayer(layer);
+
+ layer.events.register('loadstart', null, function(obj) {
+ t.ok(obj.object.tile.isLoading, "loadstart triggered while tile is loading");
+ });
+
+ var delay = false;
+ layer.events.register('loadend', null, function(obj) {
+ delay = true;
+ });
+
+ t.delay_call(5, function() {
+ t.eq(delay, true, "registered for loadend");
+ t.eq(layer.tile.isLoading, false, "loadend triggered after tile is loaded");
+ map.destroy(); //tear down
+ return delay;
+ });
+ map.zoomToMaxExtent();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:500px"></div>
+ <div id="map2" style="width:100px;height:100px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/KaMap.html b/misc/openlayers/tests/Layer/KaMap.html
new file mode 100644
index 0000000..a41e4eb
--- /dev/null
+++ b/misc/openlayers/tests/Layer/KaMap.html
@@ -0,0 +1,287 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://boston.freemap.in/tile.php?";
+ var params = {
+ 'map':'boston-new',
+ 'g':'border,water,roads,openspace',
+ 'i':'JPEG'
+ };
+ var units = "meters";
+
+
+
+ function test_Layer_KaMap_constructor (t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ t.ok( layer instanceof OpenLayers.Layer.KaMap, "returns OpenLayers.Layer.KaMap object" );
+ }
+
+ function test_Layer_Grid_moveTo_buffer_calculation (t) {
+ t.plan(6);
+
+ var map = new OpenLayers.Map( 'map3' ); // odd map size
+ var layer0 = new OpenLayers.Layer.KaMap( "0 buffer: OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}, {'buffer':0} );
+ map.addLayer(layer0);
+
+ var layer1 = new OpenLayers.Layer.KaMap( "1 buffer: OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}, {'buffer':1} );
+ map.addLayer(layer1);
+
+ var layer2 = new OpenLayers.Layer.KaMap( "2 buffer: OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}, {'buffer':2} );
+ map.addLayer(layer2);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 4);
+ t.eq( layer0.grid.length, 3, "Grid rows with buffer:0" );
+ map.setBaseLayer(layer1);
+ t.eq( layer1.grid.length, 5, "Grid rows with buffer:1" );
+ map.setBaseLayer(layer2);
+ t.eq( layer2.grid.length, 7, "Grid rows with buffer:2" );
+
+ // zooming in on Greenland exercises the bug from pre-r4313
+ map.setCenter(new OpenLayers.LonLat(0, 90), 4);
+ t.eq( layer0.grid.length, 3, "Grid rows with buffer:0" );
+ map.setBaseLayer(layer1);
+ t.eq( layer1.grid.length, 5, "Grid rows with buffer:1" );
+ map.setBaseLayer(layer2);
+ t.eq( layer2.grid.length, 7, "Grid rows with buffer:2" );
+ map.destroy();
+ }
+
+ function test_Layer_KaMap_inittiles (t) {
+ t.plan( 2 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq( layer.grid.length, 4, "KaMap rows is correct." );
+ t.eq( layer.grid[0].length, 3, "KaMap cols is correct." );
+ map.destroy();
+
+ }
+
+ function test_Layer_KaMap_clearTiles (t) {
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0));
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.clearGrid();
+
+ t.ok( layer.grid != null, "layer.grid does not get nullified" );
+ map.destroy();
+ }
+
+
+ function test_Layer_KaMap_getKaMapBounds(t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+
+ var bl = { bounds: new OpenLayers.Bounds(1,2,2,3)};
+ var tr = { bounds: new OpenLayers.Bounds(2,3,3,4)};
+ layer.grid = [ [6, tr],
+ [bl, 7]];
+
+ var bounds = layer.getTilesBounds();
+
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+
+ t.ok( bounds.equals(testBounds), "getKaMapBounds() returns correct bounds")
+
+ layer.grid = null;
+ }
+
+ function test_Layer_KaMap_getResolution(t) {
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+
+ map.zoom = 5;
+
+ t.eq( layer.getResolution(), 0.0439453125, "getResolution() returns correct value");
+ map.destroy();
+ }
+
+ function test_Layer_KaMap_getZoomForExtent(t) {
+ t.plan( 2 );
+ var bounds, zoom;
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+
+ bounds = new OpenLayers.Bounds(10,10,12,12);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 8, "getZoomForExtent() returns correct value");
+
+ bounds = new OpenLayers.Bounds(10,10,100,100);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 2, "getZoomForExtent() returns correct value");
+ map.destroy();
+ }
+
+ function test_Layer_kaMap_mergeNewParams (t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map("map");
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.KaMap(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ layer.redraw = function() {
+ t.ok(true, "layer is redrawn after new params merged");
+ }
+
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.layers, "sooper", "mergeNewParams() overwrites well");
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() adds well");
+
+ newParams.chickpeas = 151;
+
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() makes clean copy of hashtable");
+ map.destroy();
+ }
+
+
+ /** THIS WOULD BE WHERE THE TESTS WOULD GO FOR
+ *
+ * -moveTo
+ * -insertColumn
+ * -insertRow
+
+ function 07_Layer_KaMap_moveTo(t) {
+ }
+
+ function 08_Layer_KaMap_insertColumn(t) {
+ }
+
+ function 09_Layer_KaMap_insertRow(t) {
+ }
+
+ *
+ */
+
+ function test_Layer_KaMap_clone(t) {
+ t.plan(5);
+
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ var clone = layer.clone();
+
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ layer.tileSize.w += 40;
+
+ t.eq( clone.tileSize.w, 500, "changing layer.tileSize does not change clone.tileSize -- a fresh copy was made, not just copied reference");
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ t.eq( clone.CLASS_NAME, "OpenLayers.Layer.KaMap", "Clone is a ka-map layer");
+
+ layer.grid = null;
+ map.destroy();
+ }
+
+ function test_Layer_KaMap_setMap(t) {
+
+ t.plan(2);
+
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+
+
+ layer.setMap(map);
+
+ t.ok( layer.tileSize != null, "tileSize has been set");
+ t.ok( (layer.tileSize.h == 50) && (layer.tileSize.w == 500), "tileSize has been set correctly");
+ map.destroy();
+ }
+ function test_Layer_KaMap_getTileBounds(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map", {zoomMethod: null});
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.KaMap(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ map.zoomIn();
+ var bounds = layer.getTileBounds(new OpenLayers.Pixel(200,200));
+ t.eq(bounds.toBBOX(), "-180,0,0,180", "get tile bounds returns correct bounds");
+ map.pan(200,0,{animate:false});
+ var bounds = layer.getTileBounds(new OpenLayers.Pixel(200,200));
+ t.eq(bounds.toBBOX(), "0,0,180,180", "get tile bounds returns correct bounds after pan");
+ map.destroy();
+ }
+
+ function test_Layer_KaMap_destroy (t) {
+
+ t.plan( 3 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
+
+
+ //test with tile creation
+ layer = new OpenLayers.Layer.KaMap(name, url, params, units);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ t.ok( layer.grid == null, "tiles appropriately destroyed");
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px;display:none"></div>
+<div id="map2" style="width:500px;height:550px;display:none"></div>
+<div id="map3" style="width:594px;height:464px;display:none"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/MapGuide.html b/misc/openlayers/tests/Layer/MapGuide.html
new file mode 100644
index 0000000..b1eb386
--- /dev/null
+++ b/misc/openlayers/tests/Layer/MapGuide.html
@@ -0,0 +1,177 @@
+<html>
+<head>
+ <script type="text/javascript">var oldAlert = window.alert, gMess; window.alert = function(message) {gMess = message; return true;};</script>
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script type="text/javascript">window.alert = oldAlert;</script>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'MapGuide Test Layer';
+ var url = "http://data.mapguide.com/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous&";
+ var paramsTiled = {
+ mapdefinition: 'Library://Samples/Sheboygan/MapsTiled/Sheboygan.MapDefinition',
+ basemaplayergroupname: "Base Layer Group"
+ }
+ var paramsUntiled = {
+ mapdefinition: 'Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition'
+ };
+
+ function test_Layer_MapGuide_untiled_constructor (t) {
+ t.plan( 8 );
+
+ var trans_format = "image/png";
+ var options = {singleTile:true};
+ if (OpenLayers.Util.alphaHack()) { trans_format = "image/gif"; }
+
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+ t.ok( layer instanceof OpenLayers.Layer.MapGuide, "new OpenLayers.Layer.MapGuide returns object" );
+ t.eq( layer.url, "http://data.mapguide.com/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous&", "layer.url is correct (HTTPRequest inited)" );
+ t.eq( layer.params.mapdefinition, "Library://Samples/Sheboygan/Maps/Sheboygan.MapDefinition", "params passed in correctly" );
+
+ t.eq( layer.params.operation, "GETMAPIMAGE", "default params set correctly and copied");
+
+ t.eq(layer.isBaseLayer, true, "no transparency setting, layer is baselayer");
+
+ options.transparent = "true";
+ var layer2 = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+ t.eq(layer2.isBaseLayer, false, "transparency == 'true', layer is not baselayer");
+
+ options.transparent = true;
+ var layer5 = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+ t.eq(layer5.isBaseLayer, false, "transparency == true, layer is not baselayer");
+
+ options.transparent = false;
+ var layer6 = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+ t.eq(layer6.isBaseLayer, true, "transparency == false, layer is baselayer");
+ }
+
+ function test_Layer_MapGuide_tiled_constructor (t) {
+ t.plan( 5 );
+
+ var trans_format = "image/png";
+ var options = {singleTile:false};
+ if (OpenLayers.Util.alphaHack()) { trans_format = "image/gif"; }
+
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsTiled, options);
+ t.ok( layer instanceof OpenLayers.Layer.MapGuide, "new OpenLayers.Layer.MapGuide returns object" );
+ t.eq( layer.url, "http://data.mapguide.com/mapguide/mapagent/mapagent.fcgi?USERNAME=Anonymous&", "layer.url is correct (HTTPRequest inited)" );
+ t.eq( layer.params.basemaplayergroupname, "Base Layer Group", "params passed in correctly" );
+
+ t.eq( layer.params.operation, "GETTILEIMAGE", "default params correctly uppercased and copied");
+ t.eq( layer.params.version, "1.2.0", "version params set correctly set");
+ }
+
+ function test_Layer_MapGuide_inittiles (t) {
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsTiled);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,400000),5);
+ t.eq( layer.grid.length, 3, "Grid rows is correct." );
+ // t.eq( layer.grid[0].length, 6, "Grid cols is correct." );
+ map.destroy();
+ }
+
+
+ function test_Layer_MapGuide_clone (t) {
+ t.plan(4);
+
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsTiled);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ var clone = layer.clone();
+
+ t.eq( layer.tileSize.w, 300, "layer.tileSize fixed to 300x300");
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ layer.tileSize.w += 40;
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ layer.grid = null;
+ map.destroy();
+ }
+
+ function test_Layer_MapGuide_isBaseLayer(t) {
+ t.plan(3);
+
+ var options = {singleTile:true};
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+ t.ok( layer.isBaseLayer, "baselayer is true by default");
+
+ var newParams = OpenLayers.Util.extend({}, paramsUntiled);
+ options.transparent = "true";
+ layer = new OpenLayers.Layer.MapGuide(name, url, newParams, options);
+ t.ok( !layer.isBaseLayer, "baselayer is false when transparent is set to true");
+
+ newParams = OpenLayers.Util.extend({}, paramsUntiled);
+ options.isBaseLayer = false;
+ layer = new OpenLayers.Layer.MapGuide(name, url, newParams, options);
+ t.ok( !layer.isBaseLayer, "baselayer is false when option is set to false" );
+ }
+
+ function test_Layer_MapGuide_mergeNewParams (t) {
+ t.plan( 4 );
+
+ var options = {singleTile:true};
+ var map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+
+ var newParams = { mapDefinition: 'Library://Samples/Gmap/Maps/gmap.MapDefinition',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ layer.redraw = function() {
+ t.ok(true, "layer is redrawn after new params merged");
+ }
+
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.mapDefinition, "Library://Samples/Gmap/Maps/gmap.MapDefinition", "mergeNewParams() overwrites well");
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() adds well");
+
+ newParams.chickpeas = 151;
+
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() makes clean copy of hashtable");
+ map.destroy();
+ }
+
+ function test_Layer_MapGuide_destroy (t) {
+
+ t.plan( 1 );
+
+ var options = {singleTile:true};
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.MapGuide(name, url, paramsUntiled, options);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ // checks to make sure superclass (grid) destroy() was called
+
+ t.ok( layer.grid == null, "grid set to null");
+ }
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/MapServer.html b/misc/openlayers/tests/Layer/MapServer.html
new file mode 100644
index 0000000..9ae8d01
--- /dev/null
+++ b/misc/openlayers/tests/Layer/MapServer.html
@@ -0,0 +1,238 @@
+<html>
+<head>
+ <script type="text/javascript">var oldAlert = window.alert, gMess; window.alert = function(message) {gMess = message; return true;};</script>
+ <script type="text/javascript">window.alert = oldAlert;</script>
+
+
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ // turn off animation frame handling, so we can check img urls in tests
+ delete OpenLayers.Layer.Grid.prototype.queueTileDraw;
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic'};
+
+ function test_Layer_MapServer_constructor (t) {
+ t.plan( 4 );
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer(name, url, params);
+ t.ok( layer instanceof OpenLayers.Layer.MapServer, "new OpenLayers.Layer.MapServer returns object" );
+ t.eq( layer.url, "http://labs.metacarta.com/cgi-bin/mapserv", "layer.url is correct (HTTPRequest inited)" );
+
+ t.eq( layer.params.mode, "map", "default mode param correctly copied");
+ t.eq( layer.params.map_imagetype, "png", "default imagetype correctly copied");
+
+
+ }
+
+ function test_Layer_MapServer_addtile (t) {
+ t.plan( 6 );
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer(name, url, params);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ map.addLayer(layer);
+ var pixel = new OpenLayers.Pixel(5,6);
+ var tile = layer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
+ tile.draw();
+
+ var img = tile.imgDiv;
+ var tParams = OpenLayers.Util.extend({},params);
+ tParams = OpenLayers.Util.extend(tParams, {
+ layers: 'basic',
+ mode: 'map',
+ map_imagetype: 'png',
+ mapext:[1,2,3,4],
+ imgext:[1,2,3,4],
+ map_size:[256, 256],
+ imgx:128,
+ imgy:128,
+ imgxy:[256,256]
+ });
+ t.eq( tile.url,
+ url + "?" + OpenLayers.Util.getParameterString(tParams).replace(/,/g, "+"),
+ "image src is created correctly via addtile" );
+ t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+ t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
+
+ var firstChild = layer.div.firstChild;
+ t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
+ t.ok( firstChild == img, "div first child is correct image object" );
+ t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
+ map.destroy();
+ }
+
+ function test_Layer_MapServer_inittiles (t) {
+ t.plan( 2 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.MapServer(name, url, params, {buffer: 0});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq( layer.grid.length, 4, "Grid rows is correct." );
+ t.eq( layer.grid[0].length, 3, "Grid cols is correct." );
+ map.destroy();
+
+ }
+
+
+ function test_Layer_MapServer_clone (t) {
+ t.plan(4);
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.MapServer(name, url, params);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ var clone = layer.clone();
+
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ layer.tileSize.w += 40;
+
+ t.eq( clone.tileSize.w, 500, "changing layer.tileSize does not change clone.tileSize -- a fresh copy was made, not just copied reference");
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ layer.grid = null;
+ map.destroy();
+ }
+
+ function test_Layer_MapServer_isBaseLayer(t) {
+ t.plan(3);
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer(name, url, params);
+ t.ok( layer.isBaseLayer, "baselayer is true by default");
+
+ var newParams = OpenLayers.Util.extend({}, params);
+ newParams.transparent = "true";
+ layer = new OpenLayers.Layer.MapServer(name, url, newParams);
+ t.ok( !layer.isBaseLayer, "baselayer is false when transparent is set to true");
+
+ layer = new OpenLayers.Layer.MapServer(name, url, params, {isBaseLayer: false});
+ t.ok( !layer.isBaseLayer, "baselayer is false when option is set to false" );
+ }
+
+ function test_Layer_MapServer_mergeNewParams (t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map("map");
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ layer.redraw = function() {
+ t.ok(true, "layer is redrawn after new params merged");
+ }
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.layers, "sooper", "mergeNewParams() overwrites well");
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() adds well");
+
+ newParams.chickpeas = 151;
+
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() makes clean copy of hashtable");
+ map.destroy();
+ }
+
+ function test_Layer_MapServer_getFullRequestString (t) {
+ t.plan( 3 );
+ var map = new OpenLayers.Map('map');
+ tUrl = "http://labs.metacarta.com/cgi-bin/mapserv";
+ tParams = { layers: 'basic',
+ format: 'png'};
+ var tLayer = new OpenLayers.Layer.MapServer(name, tUrl, tParams);
+ map.addLayer(tLayer);
+ str = tLayer.getFullRequestString();
+ var tParams = {
+ layers: 'basic',
+ format: 'png',
+ mode: 'map',
+ map_imagetype: 'png'
+ };
+
+ var sStr = tUrl + "?" + OpenLayers.Util.getParameterString(tParams);
+ sStr = sStr.replace(/,/g, "+");
+
+ t.eq(str, sStr , "getFullRequestString() works");
+ map.destroy();
+
+ tUrl = ["http://octo.metacarta.com/cgi-bin/mapserv","http://labs.metacarta.com/cgi-bin/mapserv"];
+ layer = new OpenLayers.Layer.MapServer(name, tUrl, tParams, null);
+ str = layer.getFullRequestString({'c':'d'});
+ t.eq(str, tUrl[1] + '?' + OpenLayers.Util.getParameterString(OpenLayers.Util.extend(tParams,{'c':'d'})), "getFullRequestString() works for list of two urls and is deterministic");
+ layer.destroy();
+ var tParams = {
+ layers: 'basic',
+ format: 'png',
+ mode: 'map',
+ map_imagetype: 'png'
+ };
+ tUrl = ["http://octo.metacarta.com/cgi-bin/mapserv","http://labs.metacarta.com/cgi-bin/mapserv"];
+ layer = new OpenLayers.Layer.MapServer(name, tUrl, tParams, null);
+ str = layer.getFullRequestString({'a':'b'});
+ t.eq(str, tUrl[0] + '?' + OpenLayers.Util.getParameterString(OpenLayers.Util.extend(tParams,{'a':'b'})), "getFullRequestString() works for list of two urls and is deterministic");
+ layer.destroy();
+
+ }
+
+ function test_Layer_MapServer_singleTile (t) {
+ t.plan( 5 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.MapServer(name, url, params, {singleTile: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq( layer.singleTile, true, "layer has singleTile property, great!" );
+ t.eq( layer.grid.length, 1, "Grid has only a single row, good enough!" );
+ t.eq( layer.grid[0].length, 1, "Grid has only a single column, good enough!" );
+ t.eq( layer.tileSize.w, 750, "Image width is correct" );
+ t.eq( layer.tileSize.h, 825, "Image height is correct" );
+ map.destroy();
+ }
+
+
+
+ function test_Layer_MapServer_destroy (t) {
+
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.MapServer(name, url, params);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ // checks to make sure superclass (grid) destroy() was called
+
+ t.ok( layer.grid == null, "grid set to null");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Markers.html b/misc/openlayers/tests/Layer/Markers.html
new file mode 100644
index 0000000..07f699f
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Markers.html
@@ -0,0 +1,156 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ function test_initialize(t) {
+ t.plan( 2 );
+
+ layer = new OpenLayers.Layer.Markers('Test Layer');
+ t.ok( layer instanceof OpenLayers.Layer.Markers, "new OpenLayers.Layer.Markers returns object" );
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ }
+ function test_addlayer (t) {
+ t.plan( 3 );
+
+ layer = new OpenLayers.Layer.Markers('Test Layer');
+ t.ok( layer instanceof OpenLayers.Layer.Markers, "new OpenLayers.Layer.Markers returns object" );
+ t.eq( layer.name, "Test Layer", "layer.name is correct" );
+ layer.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0),
+ new OpenLayers.Icon())
+ );
+ t.eq( layer.markers.length, 1, "addLayer adds marker to layer." );
+ }
+ function test_addMarker_removeMarker (t) {
+ t.plan( 6 );
+
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.zoomToMaxExtent();
+ layer = new OpenLayers.Layer.Markers('Test Layer');
+ map.addLayer(layer);
+ var marker = new OpenLayers.Marker(new OpenLayers.LonLat(5,40));
+ layer.addMarker(marker);
+ t.ok( marker.icon.imageDiv.parentNode == layer.div, "addMarker adds marker image node into layer node." );
+ layer.removeMarker(marker);
+ t.ok( marker.icon.imageDiv.parentNode != layer.div, "removeMarker removes marker image node from layer node." );
+ layer.removeMarker(marker);
+ t.ok(true, "Removing marker twice does not fail.");
+ layer.addMarker(marker);
+ t.ok( marker.icon.imageDiv.parentNode == layer.div, "addMarker adds marker image node into layer node." );
+
+ layer.markers = null;
+ layer.removeMarker(marker);
+ t.ok(true, "removing marker when no markers present does not script error");
+
+ var l = new OpenLayers.Layer.Markers();
+ var marker = new OpenLayers.Marker(new OpenLayers.LonLat(5,40));
+ l.addMarker(marker);
+ l.removeMarker(marker);
+ t.ok(true, "Removing marker when layer not added to map does not fail.");
+
+ }
+
+ function test_markerMovement(t) {
+
+ t.plan(6);
+
+ var map = new OpenLayers.Map("map", {zoomMethod: null});
+ var layer = new OpenLayers.Layer.Markers("Base", {isBaseLayer: true});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 1);
+
+ var size = new OpenLayers.Size(10, 10);
+ var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+ var icon = new OpenLayers.Icon("foo", size, offset);
+ var marker = new OpenLayers.Marker(new OpenLayers.LonLat(10, -10), icon)
+ layer.addMarker(marker);
+
+ t.eq(marker.icon.px.x, 554, "marker icon is placed at 554 px on x-axis");
+ t.eq(marker.icon.px.y, 314, "marker icon is placed at 314 px on y-axis");
+
+ map.zoomTo(2);
+
+ t.eq(marker.icon.px.x, 568, "marker icon moved to 568 px on x-axis");
+ t.eq(marker.icon.px.y, 328, "marker icon moved to 328 px on y-axis");
+
+ map.zoomTo(1);
+
+ t.eq(marker.icon.px.x, 554, "marker icon moved back to 554 px on x-axis");
+ t.eq(marker.icon.px.y, 314, "marker icon moved back to 314 px on y-axis");
+
+ }
+
+ function test_destroy (t) {
+ t.plan( 1 );
+ layer = new OpenLayers.Layer.Markers('Test Layer');
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ }
+
+ function test_getDataExtent(t) {
+ t.plan( 4 );
+
+ var layer = {};
+ var ret = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(layer, []);
+ t.eq(ret, null, "does not crash, returns null on layer with null 'this.markers'");
+
+ layer.markers = [];
+ ret = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(layer, []);
+ t.eq(ret, null, "returns null on layer with empty 'this.markers'");
+
+ layer.markers.push({
+ 'lonlat': new OpenLayers.LonLat(4,5)
+ });
+ var expectedBounds = new OpenLayers.Bounds(4,5,4,5);
+ ret = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(layer, []);
+ t.ok(ret.equals(expectedBounds), "returns expected bounds with only one marker");
+
+ layer.markers.push({
+ 'lonlat': new OpenLayers.LonLat(1,2)
+ });
+ var expectedBounds = new OpenLayers.Bounds(1,2,4,5);
+ ret = OpenLayers.Layer.Markers.prototype.getDataExtent.apply(layer, []);
+ t.ok(ret.equals(expectedBounds), "returns expected bounds with multiple markers");
+
+ }
+
+ function test_setOpacity(t) {
+ t.plan(1);
+
+ layer = new OpenLayers.Layer.Markers('Test Layer');
+
+ var opacity = 0.1234;
+
+ for (var i = 0; i < 12; i++) {
+ layer.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0), new OpenLayers.Icon()));
+ }
+
+ layer.setOpacity(opacity);
+
+ for (var i = 0; i < 4; i++) {
+ layer.addMarker(new OpenLayers.Marker(new OpenLayers.LonLat(0,0), new OpenLayers.Icon()));
+ }
+
+ var itWorks = false;
+ for (var i = 0; i < layer.markers.length; i++) {
+ itWorks = parseFloat(layer.markers[i].icon.imageDiv.style.opacity) == opacity;
+ if (!itWorks) {
+ break;
+ }
+ }
+ t.ok(itWorks, "setOpacity change markers opacity");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1080px; height: 600px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/OSM.html b/misc/openlayers/tests/Layer/OSM.html
new file mode 100644
index 0000000..fac471c
--- /dev/null
+++ b/misc/openlayers/tests/Layer/OSM.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_clone(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer.OSM();
+ var clone = layer.clone();
+ t.ok(clone instanceof OpenLayers.Layer.OSM, "clone is a Layer.OSM instance");
+ }
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/PointGrid.html b/misc/openlayers/tests/Layer/PointGrid.html
new file mode 100644
index 0000000..6fb6ae2
--- /dev/null
+++ b/misc/openlayers/tests/Layer/PointGrid.html
@@ -0,0 +1,232 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+<script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer.PointGrid();
+ t.ok(layer instanceof OpenLayers.Layer.PointGrid, "instance created");
+ layer.destroy();
+ }
+
+ function test_name(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer.PointGrid({name: "foo"});
+ t.eq(layer.name, "foo", "name set like every other property");
+ layer.destroy();
+ }
+
+ function test_spacing(t) {
+ t.plan(7);
+
+ var layer = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true,
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
+ dx: 10,
+ dy: 10,
+ ratio: 1
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ t.eq(layer.features.length, 200, "200 features");
+
+ // set dx/dy together
+ layer.setSpacing(20);
+ t.eq(layer.dx, 20, "dx 20");
+ t.eq(layer.dy, 20, "dy 20");
+ t.eq(layer.features.length, 50, "50 features");
+
+ // set dx/dy independently
+ layer.setSpacing(50, 25);
+ t.eq(layer.dx, 50, "dx 50");
+ t.eq(layer.dy, 25, "dy 25");
+ t.eq(layer.features.length, 16, "16 features");
+
+ map.destroy();
+ }
+
+ function test_ratio(t) {
+ t.plan(3);
+
+ var layer = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true,
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
+ dx: 25,
+ dy: 25,
+ ratio: 1
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ t.eq(layer.features.length, 32, "32 features");
+
+ // increase ratio (1.5 -> 300 x 150)
+ layer.setRatio(1.5);
+ t.eq(layer.ratio, 1.5, "ratio 1.5");
+ t.eq(layer.features.length, 72, "72 features");
+
+ map.destroy();
+ }
+
+ function test_maxFeatures(t) {
+ t.plan(3);
+
+ var layer = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true,
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
+ dx: 10,
+ dy: 10,
+ ratio: 1
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ t.eq(layer.features.length, 200, "200 features");
+
+ // limit maxFeatures
+ layer.setMaxFeatures(150);
+ t.eq(layer.maxFeatures, 150, "maxFeatures 150");
+ t.ok(layer.features.length <= 150, "<= 150 features");
+
+ map.destroy();
+ }
+
+ function test_rotation(t) {
+ t.plan(6);
+
+ var layer = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true,
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
+ dx: 10,
+ dy: 10,
+ ratio: 1
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ function getRotation(layer) {
+ // grid starts at bottom left and goes up
+ var g0 = layer.features[0].geometry;
+ var g1 = layer.features[1].geometry;
+ // subtract 90 to get rotation of grid
+ return Math.atan2(g1.y - g0.y, g1.x - g0.x) * (180 / Math.PI) - 90;
+ }
+
+ t.eq(layer.rotation, 0, "0 rotation");
+ t.eq(getRotation(layer).toFixed(3), (0).toFixed(3), "0 grid")
+
+ // rotate grid 25 degrees counter-clockwise
+ layer.setRotation(25);
+ t.eq(layer.rotation, 25, "25 rotation");
+ t.eq(getRotation(layer).toFixed(3), (25).toFixed(3), "25 grid");
+
+ // rotate grid 45 degrees clockwise
+ layer.setRotation(-45);
+ t.eq(layer.rotation, -45, "-45 rotation");
+ t.eq(getRotation(layer).toFixed(3), (-45).toFixed(3), "-45 grid");
+
+ map.destroy();
+ }
+
+ function test_origin(t) {
+ t.plan(7);
+
+ var layer = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true,
+ resolutions: [1],
+ maxExtent: new OpenLayers.Bounds(-100, -50, 100, 50),
+ dx: 10,
+ dy: 10,
+ ratio: 1
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ var origin = layer.getOrigin();
+ t.ok(map.getExtent().getCenterLonLat().equals(origin), "default is center of map extent");
+
+ var g0 = layer.features[0].geometry;
+
+ t.eq((g0.x - origin.lon) % layer.dx, 0, "a) lattice aligned with origin x");
+ t.eq((g0.y - origin.lat) % layer.dy, 0, "a) lattice aligned with origin y");
+
+ // set origin
+ layer.setOrigin(new OpenLayers.LonLat(-5, 12));
+ origin = layer.getOrigin();
+ t.eq(origin.lon, -5, "-5 origin x");
+ t.eq(origin.lat, 12, "12 origin y");
+
+ g0 = layer.features[0].geometry;
+ t.eq((g0.x - origin.lon) % layer.dx, 0, "b) lattice aligned with origin x");
+ t.eq((g0.y - origin.lat) % layer.dy, 0, "b) lattice aligned with origin y");
+
+ map.destroy();
+ }
+
+ function test_zoom(t) {
+ t.plan(2);
+
+ var layer = new OpenLayers.Layer.PointGrid({
+ isBaseLayer: true,
+ resolutions: [2, 1],
+ maxExtent: new OpenLayers.Bounds(-200, -100, 200, 100),
+ dx: 20,
+ dy: 20,
+ ratio: 1
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1,
+ zoomMethod: null
+ });
+
+ t.eq(layer.features.length, 50, "50 features at zoom 1");
+
+ map.zoomTo(0);
+ t.eq(layer.features.length, 200, "200 features at zoom 0")
+
+ map.destroy();
+ }
+
+
+</script>
+</head>
+<body>
+<div id="map" style="width:200px;height:100px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/PointTrack.html b/misc/openlayers/tests/Layer/PointTrack.html
new file mode 100644
index 0000000..95b8ced
--- /dev/null
+++ b/misc/openlayers/tests/Layer/PointTrack.html
@@ -0,0 +1,79 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var name = "PointTrack Layer";
+
+ function test_Layer_PointTrack_constructor(t) {
+ t.plan(2);
+
+ var layer = new OpenLayers.Layer.PointTrack(name);
+ t.ok(layer instanceof OpenLayers.Layer.PointTrack, "new OpenLayers.Layer.PointTrack returns correct object" );
+ t.ok(layer.addNodes, "layer has an addNodes method");
+
+ }
+
+ function test_Layer_PointTrack_addNodes(t) {
+ t.plan(11);
+
+ var layer = new OpenLayers.Layer.PointTrack(name,
+ {dataFrom: OpenLayers.Layer.PointTrack.dataFrom.TARGET_NODE});
+
+ var point1 = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var sourceNode = new OpenLayers.Feature.Vector(point1);
+ var point2 = new OpenLayers.Geometry.Point(-112.34, 45.67);
+ var targetNode = new OpenLayers.Feature.Vector(point2, {foo: "bar"});
+ layer.addNodes([sourceNode, targetNode]);
+
+ t.eq(layer.features.length, 1, "OpenLayers.Layer.PointTrack.addNodes creates one feature from two vector point features");
+ t.eq(layer.features[0].geometry.CLASS_NAME, "OpenLayers.Geometry.LineString", "The created feature has a LineString geometry");
+ var geometry = layer.features[0].geometry;
+ t.eq(geometry.components[0].x, -111.04, "The x of the first point of the line equals the x of the first point added to the layer");
+ t.eq(geometry.components[1].y, 45.67, "The y of the second point of the line equals the y of the second point added to the layer");
+ t.eq(layer.features[0].attributes.foo, "bar", "OpenLayers.Layer.PointTrack.addNodes assigns the attributes of the target node correctly");
+
+ layer.dataFrom = OpenLayers.Layer.PointTrack.dataFrom.SOURCE_NODE;
+
+ point1 = new OpenLayers.Geometry.Point(-123.54, 45.67);
+ sourceNode = new OpenLayers.Feature.Vector(point1, {foo: "bar"});
+ point2 = new OpenLayers.Geometry.Point(-123.21, 45.32);
+ targetNode = new OpenLayers.Feature.Vector(point2);
+ layer.addNodes([sourceNode, targetNode]);
+
+ t.eq(layer.features.length, 2, "added another two points, so the layer now has two features");
+ t.eq(layer.features[1].attributes.foo, "bar", "OpenLayers.Layer.PointTrack.addNodes assigns the attributes of the source node correctly");
+
+ point1 = new OpenLayers.LonLat(-123.58, 45.69);
+ sourceNode = new OpenLayers.Feature(null, point1);
+ point2 = new OpenLayers.LonLat(-123.25, 45.37);
+ targetNode = new OpenLayers.Feature(null, point2);
+ sourceNode.data = {foo: "bar"};
+ layer.addNodes([sourceNode, targetNode]);
+
+ t.eq(layer.features.length, 3, "added another two points, this time from features, so the layer now has two features");
+ t.eq(layer.features[2].geometry.components[0].x, -123.58, "The x of the first point of the line equals the x of the first point added to the layer");
+ t.eq(layer.features[2].geometry.components[1].y, 45.37, "The y of the second point of the line equals the x of the second point added to the layer");
+ t.eq(layer.features[2].data, sourceNode.data, "OpenLayers.Layer.PointTrack.addNodes assigns the data of the source node correctly");
+
+ }
+
+ function test_Layer_PointTrack_destroy (t) {
+ t.plan(3);
+ layer = new OpenLayers.Layer.PointTrack(name);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ t.eq(layer.map.layers.length, 1, "layer added to the map successfully");
+ layer.destroy();
+ t.eq(layer.map, null, "layer.map is null after destroy");
+ t.ok(!layer.renderer, "layer.renderer is falsey after destroy");
+ }
+
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/SphericalMercator.html b/misc/openlayers/tests/Layer/SphericalMercator.html
new file mode 100644
index 0000000..be94735
--- /dev/null
+++ b/misc/openlayers/tests/Layer/SphericalMercator.html
@@ -0,0 +1,126 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_SphericalMercator_forwardMercator(t) {
+ t.plan(12);
+ var arctic = OpenLayers.Layer.SphericalMercator.forwardMercator(0, 85);
+ var antarctic = OpenLayers.Layer.SphericalMercator.forwardMercator(0, -85);
+ var hawaii = OpenLayers.Layer.SphericalMercator.forwardMercator(-180, 0);
+ var phillipines = OpenLayers.Layer.SphericalMercator.forwardMercator(180, 0);
+ var ne = OpenLayers.Layer.SphericalMercator.forwardMercator(180, 90);
+ var sw = OpenLayers.Layer.SphericalMercator.forwardMercator(-180, -90);
+
+ t.eq(arctic.lon, 0, "Arctic longitude is correct");
+ t.eq(Math.round(arctic.lat), 19971869, "Arctic latitude is correct");
+
+ t.eq(antarctic.lon, 0, "Antarctic longitude is correct");
+ t.eq(Math.round(antarctic.lat), -19971869, "Antarctic latitude is correct");
+
+ t.eq(Math.round(hawaii.lat), 0, "Hawaiian lat is correct");
+ t.eq(hawaii.lon, -20037508.34, "Hawaiian lon is correct");
+
+ t.eq(Math.round(phillipines.lat), 0, "Phillipines lat is correct");
+ t.eq(phillipines.lon, 20037508.340, "Phillipines lon is correct");
+
+ // be kind and stay within the world instead of having +/- infinity lat
+ t.ok(ne.lat, 20037508.34, "NE lat is correct");
+ t.eq(ne.lon, 20037508.34, "NE lon is correct");
+
+ t.eq(sw.lat, -20037508.34, "SW lat is correct");
+ t.eq(sw.lon, -20037508.34, "SW lon is correct");
+ }
+
+ function test_sphericalMercator_inverseMercator(t) {
+ t.plan(4);
+ var sw = OpenLayers.Layer.SphericalMercator.inverseMercator(-20037508.34, -20037508.34);
+ var ne = OpenLayers.Layer.SphericalMercator.inverseMercator(20037508.34, 20037508.34);
+ t.eq(sw.lon, -180, "Southwest lon correct");
+ t.eq(ne.lon, 180, "Northeast lon correct");
+
+ t.eq(sw.lat.toFixed(10), "-85.0511287798", "Southwest lat correct");
+ t.eq(ne.lat.toFixed(10), "85.0511287798", "Northeast lat correct");
+ }
+
+ function strToFixed(str, dig) {
+ if(dig == undefined) {
+ dig = 5;
+ }
+ return str.replace(/(\d+\.\d+)/g, function(match) {
+ return parseFloat(match).toFixed(dig);
+ });
+ }
+
+ function test_SphericalMercator_to4326(t) {
+ t.plan(1);
+ var point = new OpenLayers.Geometry.Point(1113195, 2273031);
+ point.transform("EPSG:900913", "EPSG:4326");
+
+ t.eq(strToFixed(point.toString()),
+ strToFixed("POINT(10.000000828446318 20.000000618997227)"),
+ "point transforms from Spherical Mercator to EPSG:4326");
+ }
+
+ function test_SphericalMercator_addTransform(t) {
+ // this class should add two methods to the
+ // OpenLayers.Projection.transforms object
+ t.plan(4);
+ var wgs84 = OpenLayers.Projection.transforms["EPSG:4326"];
+ t.ok(wgs84 instanceof Object, "EPSG:4326 exists in table");
+
+ var smerc = OpenLayers.Projection.transforms["EPSG:900913"];
+ t.ok(smerc instanceof Object, "EPSG:900913 exists in table");
+
+ t.ok(typeof(wgs84["EPSG:900913"]) === "function",
+ "from EPSG:4326 to EPSG:900913 correctly defined");
+ t.ok(typeof(smerc["EPSG:4326"]) === "function",
+ "from EPSG:900913 to EPSG:4326 correctly defined");
+ }
+
+ function test_equivalence(t) {
+
+ // list of equivalent codes for web mercator
+ var codes = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"];
+ var len = codes.length;
+
+ t.plan(len + (len * len));
+
+ var ggPoint = new OpenLayers.Geometry.Point(10, 20);
+ var smPoint = new OpenLayers.Geometry.Point(1113195, 2273031);
+
+ var gg = new OpenLayers.Projection("EPSG:4326");
+
+ var i, proj, forward, inverse, other, j, equiv;
+ for (i=0, len=codes.length; i<len; ++i) {
+ proj = new OpenLayers.Projection(codes[i]);
+
+ // confirm that forward/inverse work
+ forward = ggPoint.clone().transform(gg, proj);
+ t.eq(
+ strToFixed(forward.toString()),
+ strToFixed("POINT(1113194.9077777779 2273030.9266712805)"),
+ "transforms from EPSG:4326 to " + proj
+ );
+ inverse = smPoint.clone().transform(proj, gg);
+ t.eq(
+ strToFixed(inverse.toString()),
+ strToFixed("POINT(10.000000828446318 20.000000618997227)"),
+ "transforms from " + proj + " to EPSG:4326"
+ );
+
+ // confirm that null transform works
+ for (j=i+1; j<len; ++j) {
+ other = new OpenLayers.Projection(codes[j]);
+ equiv = ggPoint.clone().transform(proj, other);
+ t.ok(proj.equals(other), proj + " and " + other + " are equivalent");
+ t.ok(ggPoint.equals(equiv), "transform from " + proj + " to " + other + " preserves geometry");
+ }
+ }
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/TMS.html b/misc/openlayers/tests/Layer/TMS.html
new file mode 100644
index 0000000..4ac629f
--- /dev/null
+++ b/misc/openlayers/tests/Layer/TMS.html
@@ -0,0 +1,262 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var options = {'layername':'basic', 'type':'png'};
+
+
+ function test_Layer_TMS_constructor (t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ t.ok( layer instanceof OpenLayers.Layer.TMS, "returns OpenLayers.Layer.TMS object" );
+ }
+
+
+
+ function test_Layer_TMS_clearTiles (t) {
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0));
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.clearGrid();
+
+ t.ok( layer.grid != null, "layer.grid does not get nullified" );
+ map.destroy();
+ }
+
+
+ function test_Layer_TMS_getTMSBounds(t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+
+ var bl = { bounds: new OpenLayers.Bounds(1,2,2,3)};
+ var tr = { bounds: new OpenLayers.Bounds(2,3,3,4)};
+ layer.grid = [ [6, tr],
+ [bl, 7]];
+
+ var bounds = layer.getTilesBounds();
+
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+
+ t.ok( bounds.equals(testBounds), "getTMSBounds() returns correct bounds");
+
+ layer.grid = null;
+ }
+
+ function test_Layer_TMS_getResolution(t) {
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ map.addLayer(layer);
+
+ map.zoom = 5;
+
+ t.eq( layer.getResolution(), 0.0439453125, "getResolution() returns correct value");
+ map.destroy();
+ }
+
+ function test_Layer_TMS_getZoomForExtent(t) {
+ t.plan( 2 );
+ var bounds, zoom;
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ map.addLayer(layer);
+
+ bounds = new OpenLayers.Bounds(10,10,12,12);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 8, "getZoomForExtent() returns correct value");
+
+ bounds = new OpenLayers.Bounds(10,10,100,100);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 2, "getZoomForExtent() returns correct value");
+ map.destroy();
+ }
+
+
+ /** THIS WOULD BE WHERE THE TESTS WOULD GO FOR
+ *
+ * -moveTo
+ * -insertColumn
+ * -insertRow
+
+ function 07_Layer_TMS_moveTo(t) {
+ }
+
+ function 08_Layer_TMS_insertColumn(t) {
+ }
+
+ function 09_Layer_TMS_insertRow(t) {
+ }
+
+ *
+ */
+ function test_Layer_TMS_getURL(t) {
+
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map', options);
+ var options = {'layername':'basic', 'type':'png'};
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 9);
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/261/192.png", "Tile URL is correct");
+
+ var layer2 = layer.clone();
+ layer2.serviceVersion = "1.2.3";
+ map.addLayer(layer2);
+ tileurl = layer2.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.2.3/basic/9/261/192.png", "TMS serviceVersion is correct");
+
+ layer.url = ["http://tilecache1/", "http://tilecache2/", "http://tilecache3/"];
+ tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://tilecache1/1.0.0/basic/9/261/192.png", "Tile URL is deterministic");
+ map.destroy();
+ }
+ function test_Layer_TMS_Rounding(t) {
+ t.plan(1);
+ m = new OpenLayers.Map("map", {'maxExtent':new OpenLayers.Bounds(-122.6579,37.4901,-122.0738,37.8795)});
+ layer = new OpenLayers.Layer.TMS( "TMS",
+ "http://labs.metacarta.com/wms-c/Basic.py/", {layername: 'basic', type:'png', resolutions:[0.000634956337608418], buffer: 2} );
+ m.addLayer(layer);
+ m.zoomToMaxExtent();
+ t.eq(layer.getURL(layer.grid[3][3].bounds), "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/0/1/1.png", "TMS tiles around rounded properly.");
+ m.destroy();
+ }
+
+ function test_Layer_TMS_serverResolutions(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [13,11]
+ });
+
+ var layer = new OpenLayers.Layer.TMS('tc layer', '', options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 1);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(0,0,0,0));
+ var level = parseInt(tileurl.split('/')[2]);
+ t.eq(map.getZoom(), level, "Tile zoom level is correct without serverResolutions");
+
+ layer.serverResolutions = [14,13,12,11,10];
+ tileurl = layer.getURL(new OpenLayers.Bounds(0,0,0,0));
+ level = parseInt(tileurl.split('/')[2]);
+ var res = map.getResolution();
+ var gotLevel = OpenLayers.Util.indexOf(layer.serverResolutions, res);
+ t.eq(gotLevel, level, "Tile zoom level is correct with serverResolutions");
+
+ map.destroy();
+ }
+
+ function test_zoomOffset(t) {
+
+ t.plan(2);
+
+ var offset, zoom;
+
+ // test offset of 2
+ offset = 2;
+ zoom = 3;
+
+ var map = new OpenLayers.Map({
+ div: "map"
+ });
+ var layer = new OpenLayers.Layer.TMS("TMS", "", {
+ layername: "basic",
+ type: "png",
+ zoomOffset: offset
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), zoom);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(parseInt(tileurl.split("/")[2]), zoom + offset, "correct level for offset 2");
+
+ map.destroy();
+
+ // test offset of -1
+ offset = -1;
+ zoom = 3;
+
+ var map = new OpenLayers.Map({
+ div: "map"
+ });
+ var layer = new OpenLayers.Layer.TMS("TMS", "", {
+ layername: "basic",
+ type: "png",
+ zoomOffset: offset
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), zoom);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(parseInt(tileurl.split("/")[2]), zoom + offset, "correct level for offset -1");
+
+ map.destroy();
+ }
+
+ function test_Layer_TMS_setMap(t) {
+
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+
+ t.eq(layer.tileOrigin, null, "Tile origin starts out null");
+ layer.setMap(map);
+
+ t.eq(layer.tileOrigin.lat, -90, "lat is -90");
+ t.eq(layer.tileOrigin.lon, -180, "lon is -180");
+ map.destroy();
+ }
+
+ function test_Layer_TMS_destroy (t) {
+
+ t.plan( 3 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
+
+
+ //test with tile creation
+ layer = new OpenLayers.Layer.TMS(name, url, options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ t.ok( layer.grid == null, "tiles appropriately destroyed");
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Text.html b/misc/openlayers/tests/Layer/Text.html
new file mode 100644
index 0000000..3bffe4c
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Text.html
@@ -0,0 +1,211 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var isMSIE = (navigator.userAgent.indexOf("MSIE") > -1);
+ var layer;
+
+ var datafile = "./data_Layer_Text_textfile.txt";
+ var datafile2 = "./data_Layer_Text_textfile_2.txt";
+ var datafile_overflow = "./data_Layer_Text_textfile_overflow.txt";
+
+ // if this test is running in IE, different rules apply
+ if (isMSIE) {
+ datafile = "." + datafile;
+ datafile2 = "." + datafile2;
+ datafile_overflow = "." + datafile_overflow;
+ }
+
+ function test_Layer_Text_constructor (t) {
+ t.plan( 5 );
+
+ layer = new OpenLayers.Layer.Text('Test Layer', { location: datafile });
+ layer.loadText();
+ t.ok( layer instanceof OpenLayers.Layer.Text, "new OpenLayers.Layer.Text returns object" );
+ t.eq( layer.location, datafile, "layer.location is correct" );
+ var markers;
+ t.delay_call( 1, function() {
+ t.eq( layer.markers.length, 2, "marker length is correct" );
+ var ll = new OpenLayers.LonLat(20, 10);
+ t.ok( layer.markers[0].lonlat.equals(ll), "first marker is correct" );
+ t.eq( layer.markers[0].icon.url, 'http://boston.openguides.org/markers/ORANGE.png', "icon" );
+ } );
+ }
+ function test_Layer_Text_draw (t) {
+// t.plan(5);
+ t.plan( 2 );
+ layer = new OpenLayers.Layer.Text('Test Layer', { location: datafile });
+ t.ok( layer instanceof OpenLayers.Layer.Text, "new OpenLayers.Layer.Text returns object" );
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ t.eq( map.layers[1].name, layer.name, "Layer added to map okay" );
+ t.delay_call( 1, function() {
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+
+/*
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( map.layers[0].div.firstChild instanceof HTMLImageElement, "Marker added to div" )
+
+ t.eq( map.layers[0].div.firstChild.style.top, "219px", "Marker top set correctly" )
+ t.eq( map.layers[0].div.firstChild.style.left, "273px", "Marker left set correctly" )
+*/
+ });;
+ }
+
+ function test_Layer_Text_moveTo(t) {
+ t.plan(16);
+
+ temp = OpenLayers.Layer.Markers.prototype.moveTo;
+
+ g_Bounds = {};
+ g_ZoomChanged = {};
+ g_Minor = {};
+ var args = [g_Bounds, g_ZoomChanged, g_Minor];
+
+ OpenLayers.Layer.Markers.prototype.moveTo =
+ function(bounds, zoomChanged, minor) {
+ t.ok(bounds == g_Bounds, "correct bounds passed to Markers superclass");
+ t.ok(zoomChanged == g_ZoomChanged, "correct zoomChanged passed to Markers superclass");
+ t.ok(minor == g_Minor, "correct minor passed to Markers superclass");
+ }
+
+ var layer = {
+ 'loadText': function() { g_TextLoaded = true; }
+ };
+
+ //visibility true, loaded true
+ layer.visibility = true;
+ layer.loaded = true;
+ g_TextLoaded = false;
+ OpenLayers.Layer.Text.prototype.moveTo.apply(layer, args);
+ t.ok(g_TextLoaded == false, "text not loaded when visibility true, loaded true");
+
+ //visibility true, loaded false
+ layer.visibility = true;
+ layer.loaded = false;
+ g_TextLoaded = false;
+ OpenLayers.Layer.Text.prototype.moveTo.apply(layer, args);
+ t.ok(g_TextLoaded == true, "text is loaded when visibility true, loaded false");
+
+ //visibility false, loaded true
+ layer.visibility = false;
+ layer.loaded = true;
+ g_TextLoaded = false;
+ OpenLayers.Layer.Text.prototype.moveTo.apply(layer, args);
+ t.ok(g_TextLoaded == false, "text not loaded when visibility false, loaded true");
+
+ //visibility false, loaded false
+ layer.visibility = false;
+ layer.loaded = false;
+ g_TextLoaded = false;
+ OpenLayers.Layer.Text.prototype.moveTo.apply(layer, args);
+ t.ok(g_TextLoaded == false, "text not loaded when visibility false, loaded false");
+
+ OpenLayers.Layer.Markers.prototype.moveTo = temp;
+ }
+
+
+
+ function test_Layer_Text_events (t) {
+ t.plan( 5 );
+ layer = new OpenLayers.Layer.Text('Test Layer', { location: datafile2 });
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var event = {};
+ t.delay_call( 1, function() {
+ t.ok(layer.markers[0].events, "First marker has an events object");
+ t.eq(layer.markers[0].events.listeners['click'].length, 1, "Marker events has one object");
+ layer.markers[0].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "Popup opened correctly");
+ layer.markers[1].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "1st popup gone, 2nd Popup opened correctly");
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq(map.popups[0].contentDiv.style[prop],"auto", "default Popup overflow correct");
+ });
+ }
+ function test_Layer_Text_overflow (t) {
+ t.plan( 4 );
+ layer = new OpenLayers.Layer.Text('Test Layer', { location: datafile_overflow });
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var event = {};
+ t.delay_call( 1, function() {
+ layer.markers[0].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "Popup opened correctly");
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq(map.popups[0].contentDiv.style[prop],"auto", "Popup overflow read from file");
+ layer.markers[1].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 1, "1st popup gone, 2nd Popup opened correctly");
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq(map.popups[0].contentDiv.style[prop],"hidden", "Popup overflow read from file");
+ });
+ }
+ function test_Layer_Text_events_nopopups (t) {
+ t.plan( 4 );
+ layer = new OpenLayers.Layer.Text('Test Layer', { location: datafile });
+ var map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),0);
+ var event = {};
+ t.delay_call( 1, function() {
+ t.ok(layer.markers[0].events, "First marker has an events object");
+ t.eq(layer.markers[0].events.listeners['click'], undefined, "Marker events has no object");
+ layer.markers[0].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 0, "no popup on first marker");
+ layer.markers[1].events.triggerEvent('click', event);
+ t.eq(map.popups.length, 0, "no popup on second marker");
+ });
+ }
+ function test_Layer_Text_loadend_Event(t) {
+ t.plan(2);
+ layer = new OpenLayers.Layer.Text('Test Layer', {location:datafile});
+ t.delay_call(2, function() {
+ layer.events.register('loadend', layer, function() {
+ t.ok(true, "Loadend event fired");
+ });
+ layer.parseData({
+ 'responseText':''
+ });
+ t.ok(true, "Parsing data didn't fail");
+ });
+ }
+
+ function test_Layer_Text_destroy (t) {
+ t.plan( 1 );
+ layer = new OpenLayers.Layer.Text('Test Layer');
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.map, null, "layer.map is null after destroy" );
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px; height:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/TileCache.html b/misc/openlayers/tests/Layer/TileCache.html
new file mode 100644
index 0000000..2bb88f5
--- /dev/null
+++ b/misc/openlayers/tests/Layer/TileCache.html
@@ -0,0 +1,203 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+
+
+ function test_Layer_TileCache_constructor (t) {
+ t.plan( 1 );
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'type':'png'};
+
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+ t.ok( layer instanceof OpenLayers.Layer.TileCache, "returns OpenLayers.Layer.TileCache object" );
+ layer.destroy();
+ }
+
+ function test_Layer_TileCache_clone(t) {
+ t.plan(3);
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'type':'png'};
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+
+ var clone = layer.clone();
+ t.eq(layer.name, clone.name, "clone() correctly copy the 'name' property");
+ t.eq(layer.url, clone.url, "clone() correctly copy the 'url' property");
+ t.eq(layer.layername, clone.layername, "clone() correctly copy the 'layername' property");
+ clone.destroy();
+ layer.destroy();
+ }
+
+ function test_Layer_TileCache_clearTiles (t) {
+ t.plan( 1 );
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'type':'png'};
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0));
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.clearGrid();
+
+ t.ok( layer.grid != null, "layer.grid does not get nullified" );
+ map.destroy();
+ }
+
+
+ function test_Layer_TileCache_getTileCacheBounds(t) {
+ t.plan( 1 );
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'type':'png'};
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+
+ var bl = { bounds: new OpenLayers.Bounds(1,2,2,3)};
+ var tr = { bounds: new OpenLayers.Bounds(2,3,3,4)};
+ layer.grid = [ [6, tr],
+ [bl, 7]];
+
+ var bounds = layer.getTilesBounds();
+
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+
+ t.ok( bounds.equals(testBounds), "getTileCacheBounds() returns correct bounds")
+
+ }
+
+ function test_Layer_TileCache_getResolution(t) {
+ t.plan( 1 );
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'type':'png', maxResolution: 180/256};
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+ map.addLayer(layer);
+
+ map.zoom = 5;
+
+ t.eq( layer.getResolution(), 0.02197265625, "getResolution() returns correct value");
+ map.destroy();
+ }
+
+ function test_Layer_TileCache_getZoomForExtent(t) {
+ t.plan( 2 );
+ var bounds, zoom;
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'type':'png', maxResolution: 180/256};
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+ map.addLayer(layer);
+
+ bounds = new OpenLayers.Bounds(10,10,12,12);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 7, "getZoomForExtent() returns correct value");
+
+ bounds = new OpenLayers.Bounds(10,10,100,100);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 1, "getZoomForExtent() returns correct value");
+ map.destroy();
+ }
+
+ function test_Layer_TileCache_getURL(t) {
+
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map');
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'layername':'basic', 'format':'image/jpg', maxResolution: 180/256};
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 9);
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/basic/09/000/000/522/000/000/384.jpeg", "Tile URL is correct");
+
+ layer.url = ["http://tilecache1/", "http://tilecache2/", "http://tilecache3/"];
+ tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://tilecache2/basic/09/000/000/522/000/000/384.jpeg", "Tile URL is deterministic");
+ map.destroy();
+ }
+
+ function test_Layer_TileCache_serverResolutions(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [13,11]
+ });
+
+ var layer = new OpenLayers.Layer.TileCache('tc layer', '', 'basic');
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 1);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(0,0,0,0));
+ var level = parseInt(tileurl.split('/')[2]);
+ t.eq(map.getZoom(), level, "Tile zoom level is correct without serverResolutions");
+
+ layer.serverResolutions = [14,13,12,11,10];
+ tileurl = layer.getURL(new OpenLayers.Bounds(0,0,0,0));
+ level = parseInt(tileurl.split('/')[2]);
+ var gotLevel = OpenLayers.Util.indexOf(layer.serverResolutions, map.getResolution());
+ t.eq(gotLevel, level, "Tile zoom level is correct with serverResolutions");
+
+ map.destroy();
+ }
+
+ function test_Layer_TileCache_destroy (t) {
+
+ t.plan( 3 );
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/";
+ var layername = "basic";
+ var options = {'layername':'basic', 'format':'image/jpg', maxResolution: 180/256};
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.TileCache(name, url, layername, options);
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
+
+
+ //test with tile creation
+ layer = new OpenLayers.Layer.TileCache(name, url, options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ t.ok( layer.grid == null, "tiles appropriately destroyed");
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/UTFGrid.html b/misc/openlayers/tests/Layer/UTFGrid.html
new file mode 100644
index 0000000..872d796
--- /dev/null
+++ b/misc/openlayers/tests/Layer/UTFGrid.html
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script>
+ /**
+ * Because browsers that implement requestAnimationFrame may not execute
+ * animation functions while a window is not displayed (e.g. in a hidden
+ * iframe as in these tests), we mask the native implementations here. The
+ * native requestAnimationFrame functionality is tested in Util.html and
+ * in PanZoom.html (where a popup is opened before panning). The panTo tests
+ * here will test the fallback setTimeout implementation for animation.
+ */
+ window.requestAnimationFrame =
+ window.webkitRequestAnimationFrame =
+ window.mozRequestAnimationFrame =
+ window.oRequestAnimationFrame =
+ window.msRequestAnimationFrame = null;
+ </script>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var map, layer;
+ function setUp() {
+ layer = new OpenLayers.Layer.UTFGrid({
+ url: "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json",
+ isBaseLayer: true,
+ utfgridResolution: 4
+ });
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [layer],
+ center: [0, 0],
+ zoom: 1,
+ tileManager: null
+ });
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ }
+
+ function test_constructor(t) {
+ t.plan(4);
+
+ var layer = new OpenLayers.Layer.UTFGrid({
+ name: "foo",
+ url: "path/to/tiles/${z}/${x}/${y}",
+ utfgridResolution: 8
+ });
+ t.ok(layer instanceof OpenLayers.Layer.UTFGrid, "utfgrid instance");
+ t.eq(layer.name, "foo", "layer name");
+ t.eq(layer.url, "path/to/tiles/${z}/${x}/${y}", "layer url");
+ t.eq(layer.utfgridResolution, 8, "layer utfgridResolution");
+
+ layer.destroy();
+
+ }
+
+ function test_createBackBuffer(t) {
+ t.plan(1);
+ setUp();
+
+ var got;
+ try {
+ got = layer.createBackBuffer();
+ } catch (e) {
+ got = e;
+ } finally {
+ tearDown();
+ }
+ t.eq(got, undefined, "createBackBuffer returns undefined");
+ }
+
+ function test_clone(t) {
+ t.plan(3);
+ setUp();
+
+ var clone = layer.clone();
+ t.ok(layer instanceof OpenLayers.Layer.UTFGrid, "utfgrid instance");
+ t.eq(layer.url, "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json", "layer url");
+ t.eq(layer.utfgridResolution, 4, "layer utfgridResolution");
+ clone.destroy();
+
+ tearDown();
+ }
+
+ function test_getFeatureInfo(t) {
+ t.plan(2);
+ setUp();
+
+ // wait for tile loading to finish
+ t.delay_call(0.5, function() {
+ var loc = new OpenLayers.LonLat(-110, 45).transform("EPSG:4326", "EPSG:900913");
+ var info = layer.getFeatureInfo(loc);
+
+ t.eq(info.id, "207", "feature id");
+ t.eq(info.data, {POP2005: 299846449, NAME: "United States"}, "feature data");
+
+ tearDown();
+ });
+
+ }
+
+ function test_getFeatureId(t) {
+ t.plan(2);
+ setUp();
+
+ // wait for tile loading to finish
+ t.delay_call(0.5, function() {
+ var ca = new OpenLayers.LonLat(-110, 55).transform("EPSG:4326", "EPSG:900913");
+ var ru = new OpenLayers.LonLat(90, 75).transform("EPSG:4326", "EPSG:900913");
+
+ t.eq(layer.getFeatureId(ca), "24", "feature id for ca");
+ t.eq(layer.getFeatureId(ru), "245", "feature id for ru");
+
+ tearDown();
+ });
+
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="height: 256px; width: 512px"></div>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/Layer/Vector.html b/misc/openlayers/tests/Layer/Vector.html
new file mode 100644
index 0000000..aa3e2f8
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Vector.html
@@ -0,0 +1,879 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var name = "Vector Layer";
+
+ function test_Layer_Vector_constructor(t) {
+ t.plan(5);
+
+ var options = {protocol: new OpenLayers.Protocol(),
+ strategies: [new OpenLayers.Strategy(), new OpenLayers.Strategy()]}
+ var layer = new OpenLayers.Layer.Vector(name, options);
+
+ t.ok(layer instanceof OpenLayers.Layer.Vector, "new OpenLayers.Layer.Vector returns correct object" );
+ t.eq(layer.name, name, "layer name is correctly set");
+ t.ok(layer.renderer.CLASS_NAME, "layer has a renderer");
+
+ t.ok((layer.name == layer.strategies[0].layer.name) &&
+ (layer.strategies[0].layer.name == layer.strategies[1].layer.name),
+ "setLayer was called on strategies");
+
+ options.renderers = [OpenLayers.Renderer.SVG, OpenLayers.Renderer.VML, OpenLayers.Renderer.Canvas];
+ layer.destroy();
+ layer = new OpenLayers.Layer.Vector(name, options);
+ t.ok(layer.renderer.CLASS_NAME, "layer has a renderer when providing a function");
+ layer.destroy();
+ }
+
+ function test_Layer_Vector_assignRenderer(t) {
+ t.plan(2);
+
+ // create a dummy class in the global name space
+ My = {
+ Custom: {
+ Renderer: {
+ Supported: OpenLayers.Class(OpenLayers.Renderer, {
+ supported: OpenLayers.Function.True,
+ CLASS_NAME: 'My.Custom.Renderer.Supported'
+ }),
+ NotSupported: OpenLayers.Class(OpenLayers.Renderer, {
+ supported: OpenLayers.Function.False,
+ CLASS_NAME: 'My.Custom.Renderer.NotSupported'
+ })
+ }
+ }
+ };
+ var layer = new OpenLayers.Layer.Vector('vector', {
+ renderers: [My.Custom.Renderer.NotSupported,
+ My.Custom.Renderer.Supported,
+ OpenLayers.Renderer.Canvas]
+ });
+ t.eq(layer.renderer.CLASS_NAME, 'My.Custom.Renderer.Supported',
+ 'layer has a valid renderer');
+
+ var layer = new OpenLayers.Layer.Vector('vector', {
+ renderers: ['SVG', 'VML', 'Canvas', My.Custom.Renderer.Supported]
+ });
+ t.ok(layer.renderer.CLASS_NAME != 'My.Custom.Renderer.Supported',
+ 'renderers can be strings as well');
+ }
+
+ function test_Layer_Vector_refresh(t) {
+ t.plan(4);
+
+ var obj = {"an": "object"};
+
+ var log;
+ var layer = new OpenLayers.Layer.Vector(name, {
+ eventListeners: {
+ refresh: function(o) {
+ log.obj = o;
+ }
+ }
+ });
+ inRange = false;
+ layer.calculateInRange = function() {
+ return inRange;
+ };
+
+ log = {};
+ inRange = false;
+ layer.visibility = false;
+ layer.refresh(obj);
+ t.eq(log.obj, undefined, "[false, false] refresh not triggered");
+
+ log = {};
+ inRange = true;
+ layer.visibility = false;
+ layer.refresh(obj);
+ t.eq(log.obj, undefined, "[true, false] refresh not triggered");
+
+ log = {};
+ inRange = false;
+ layer.visibility = true;
+ layer.refresh(obj);
+ t.eq(log.obj, undefined, "[false, true] refresh not triggered");
+
+ log = {};
+ inRange = true;
+ layer.visibility = true;
+ layer.refresh(obj);
+ t.ok(log.obj === obj, "[true, true] refresh triggered with correct arg");
+ }
+
+ function test_Layer_Vector_addFeatures(t) {
+ t.plan(8);
+
+ var layer = new OpenLayers.Layer.Vector(name);
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointFeature = new OpenLayers.Feature.Vector(point);
+
+ layer.preFeatureInsert = function(feature) {
+ t.ok(feature == pointFeature, "OpenLayers.Layer.Vector.addFeatures calls preFeatureInsert with the right arg");
+ };
+ layer.onFeatureInsert = function(feature) {
+ t.ok(feature == pointFeature, "OpenLayers.Layer.Vector.addFeatures calls onFeatureInsert with the right arg");
+ };
+ layer.events.register('beforefeatureadded', null, function(obj) {
+ t.ok(pointFeature == obj.feature, "OpenLayers.Layer.Vector.addFeatures triggers beforefeatureadded with correct feature passed to callback");
+ });
+ layer.events.register('featureadded', null, function(obj) {
+ t.ok(pointFeature == obj.feature, "OpenLayers.Layer.Vector.addFeatures triggers featureadded with correct feature passed to callback");
+ });
+ layer.events.register('featuresadded', null, function(obj) {
+ t.ok(pointFeature == obj.features[0], "OpenLayers.Layer.Vector.addFeatures triggers featuresadded with correct features passed to callback");
+ });
+
+ layer.addFeatures([pointFeature]);
+
+ t.eq(layer.features.length, 1, "OpenLayers.Layer.Vector.addFeatures adds something to the array");
+ t.ok(layer.features[0] == pointFeature, "OpenLayers.Layer.Vector.addFeatures returns an array of features");
+
+ layer.preFeatureInsert = function(feature) {
+ t.fail("OpenLayers.Layer.Vector.addFeatures calls preFeatureInsert while it must not");
+ };
+ layer.onFeatureInsert = function(feature) {
+ t.fail("OpenLayers.Layer.Vector.addFeatures calls onFeatureInsert while it must not");
+ };
+ layer.events.register('beforefeatureadded', null, function(obj) {
+ t.fail("OpenLayers.Layer.Vector.addFeatures triggers beforefeatureadded while it must not");
+ });
+ layer.events.register('featureadded', null, function(obj) {
+ t.fail("OpenLayers.Layer.Vector.addFeatures triggers featureadded while it must not");
+ });
+ layer.events.register('featuresadded', null, function(obj) {
+ t.fail("OpenLayers.Layer.Vector.addFeatures triggers featuresadded while it must not");
+ });
+
+ layer.addFeatures([pointFeature], {silent: true});
+
+ var extent = layer.getDataExtent();
+ t.eq(extent.toBBOX(), "-111.04,45.68,-111.04,45.68", "extent from getDataExtent is correct");
+ }
+
+ function test_Layer_Vector_getFeature(t) {
+ t.plan(13);
+
+ var layer = new OpenLayers.Layer.Vector(name);
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-111.04, 45.68));
+
+ t.ok(layer.getFeatureById(feature.id) == null,
+ "OpenLayers.Layer.Vector.getFeatureById returns null while the layer is empty");
+ t.ok(layer.getFeatureByFid('my_fid') == null,
+ "OpenLayers.Layer.Vector.getFeatureByFid returns null while the layer is empty");
+
+ layer.addFeatures([feature]);
+
+ t.ok(layer.getFeatureByFid('my_fid') == null,
+ "OpenLayers.Layer.Vector.getFeatureByFid returns null on unset feature fid");
+
+ feature.fid = 'my_fid';
+
+ t.ok(layer.getFeatureById(feature.id) == feature,
+ "OpenLayers.Layer.Vector.getFeatureById returns the correct feature");
+ t.ok(layer.getFeatureByFid(feature.fid) == feature,
+ "OpenLayers.Layer.Vector.getFeatureByFid returns the correct feature");
+ t.ok(layer.getFeatureById('some_id_that_does_not_exist') == null,
+ "OpenLayers.Layer.Vector.getFeatureById returns null on non-existing feature id");
+ t.ok(layer.getFeatureByFid('some_fid_that_does_not_exist') == null,
+ "OpenLayers.Layer.Vector.getFeatureByFid returns null on non-existing feature fid");
+ t.ok(layer.getFeatureById(feature.fid) == null,
+ "OpenLayers.Layer.Vector.getFeatureById ignores the feature fid");
+ t.ok(layer.getFeatureByFid(feature.id) == null,
+ "OpenLayers.Layer.Vector.getFeatureByFid ignores the feature id");
+
+ t.ok(layer.getFeatureBy('id', feature.id) == feature,
+ "OpenLayers.Layer.Vector.getFeatureBy('id', ...) works like getFeatureById on existing feature id");
+ t.ok(layer.getFeatureBy('id', 'some_id_that_does_not_exist') == null,
+ "OpenLayers.Layer.Vector.getFeatureBy('id', ...) works like getFeatureById on non-existing feature id");
+ t.ok(layer.getFeatureBy('fid', feature.fid) == feature,
+ "OpenLayers.Layer.Vector.getFeatureBy('fid', ...) works like getFeatureByFid on existing feature fid");
+ t.ok(layer.getFeatureBy('fid', 'some_fid_that_does_not_exist') == null,
+ "OpenLayers.Layer.Vector.getFeatureBy('fid', ...) works like getFeatureByFid on non-existing feature fid");
+ }
+
+ function test_Layer_Vector_getFeaturesByAttribute(t) {
+ t.plan( 9 );
+ // setup layer
+ var layer = new OpenLayers.Layer.Vector(name);
+
+ // feature_1
+ var geometry_1 = new OpenLayers.Geometry.Point(-28.63, 153.64);
+ var attributes_1 = {
+ humpty: 'dumpty',
+ clazz: 1
+ };
+ var feature_1 = new OpenLayers.Feature.Vector(geometry_1, attributes_1);
+ feature_1.fid = 'f_01'; // to identify later
+
+ // feature_2
+ var geometry_2 = new OpenLayers.Geometry.Point(-27.48, 153.05);
+ var attributes_2 = {
+ // this feature has attribute humpty === undefined
+ clazz: '1'
+ };
+ var feature_2 = new OpenLayers.Feature.Vector(geometry_2, attributes_2);
+ feature_2.fid = 'f_02'; // to identify later
+
+ // feature_3
+ var geometry_3 = new OpenLayers.Geometry.Point(-33.74, 150.3);
+ var attributes_3 = {
+ humpty: 'foobar',
+ clazz: 1
+ };
+ var feature_3 = new OpenLayers.Feature.Vector(geometry_3, attributes_3);
+ feature_3.fid = 'f_03'; // to identify later
+
+ // Tests
+
+ // don't find anything... no features added
+ // 1 test
+ t.ok(layer.getFeaturesByAttribute('humpty', 'dumpty').length === 0,
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns an empty array while the layer is empty");
+
+ layer.addFeatures([feature_1, feature_2, feature_3]);
+
+ // simple use case: find 1 feature with an attribute and matching value
+ // 2 tests
+ var dumptyResults = layer.getFeaturesByAttribute('humpty', 'dumpty');
+ t.ok(dumptyResults.length === 1,
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns an array with one feature for attribute 'humpty' with value 'dumpty'");
+ t.ok(dumptyResults[0].fid === 'f_01',
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns the correct feature with attribute 'humpty' set to 'dumpty'");
+
+ // simple use case: find 1 feature with an attribute and matching value
+ // and respect data types
+ // 2 tests
+ var strOneResults = layer.getFeaturesByAttribute('clazz', '1');
+ t.ok(strOneResults.length === 1,
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns an array with one feature for attribute 'clazz' with value '1' (a string)");
+ t.ok(strOneResults[0].fid === 'f_02',
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns the correct feature with attribute 'clazz' set to the string '1'");
+
+ // simple use case: find 2 features with an attribute and matching value
+ // and respect data types
+ // 2 tests
+ var numOneResults = layer.getFeaturesByAttribute('clazz', 1);
+ t.ok(numOneResults.length === 2,
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns an array with two features for attribute 'clazz' with value 1 (a number)");
+ var bothFound = !!((numOneResults[0].fid === 'f_01' && numOneResults[1].fid === 'f_03') || (numOneResults[0].fid === 'f_03' && numOneResults[1].fid === 'f_01'));
+ t.ok(bothFound,
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute returns the correct features with attribute 'clazz' set to the number 1");
+
+ // advanced use case: find the 1 feature, that has an attribute not set
+ var undefined;
+ var humptyNotSet = layer.getFeaturesByAttribute('humpty', undefined);
+ t.ok(humptyNotSet.length === 1,
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute can be used to find features that have certain attributes not set");
+ t.ok(humptyNotSet[0].fid === 'f_02',
+ "OpenLayers.Layer.Vector.getFeaturesByAttribute found the correct featuren that has a certain attribute not set");
+ }
+
+ function test_Layer_Vector_getDataExtent(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer.Vector(name);
+
+ var point = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointFeature = new OpenLayers.Feature.Vector(point);
+ layer.addFeatures([pointFeature]);
+ var point = new OpenLayers.Geometry.Point(-111.04, 5.68);
+ var pointFeature = new OpenLayers.Feature.Vector(point);
+ layer.addFeatures([pointFeature]);
+ var extent = layer.getDataExtent();
+ t.ok(extent.toBBOX() != layer.features[0].geometry.getBounds().toBBOX(), "extent from getDataExtent doesn't clobber first feature");
+ }
+
+ function test_Layer_Vector_getDataExtentEmpty(t) {
+ t.plan(1);
+ var layer = new OpenLayers.Layer.Vector(name);
+ layer.addFeatures([new OpenLayers.Feature.Vector(null), new OpenLayers.Feature.Vector(null)]);
+ var extent = layer.getDataExtent();
+ t.eq(extent, null, "We expect null to be returned if there are no features with a geometry");
+ }
+
+ function test_Layer_Vector_removeFeatures(t) {
+ t.plan(17);
+
+ var layer = new OpenLayers.Layer.Vector(name);
+ var features, log;
+
+ var point1 = new OpenLayers.Geometry.Point(-111.04, 45.68);
+ var pointFeature1 = new OpenLayers.Feature.Vector(point1);
+ var point2 = new OpenLayers.Geometry.Point(-111.14, 45.78);
+ var pointFeature2 = new OpenLayers.Feature.Vector(point2);
+
+ // 1 test
+ layer.addFeatures([pointFeature1, pointFeature2]);
+ features = layer.removeFeatures([pointFeature1]);
+ t.ok(layer.features.length == 1, "OpenLayers.Layer.Vector.removeFeatures removes a feature from the features array");
+
+ // 1 test
+ layer.addFeatures([pointFeature1.clone(), pointFeature2.clone()]);
+ layer.selectedFeatures.push(layer.features[0]);
+ layer.removeFeatures(layer.features[0]);
+ t.eq(layer.selectedFeatures, [], "Remove features removes selected features");
+
+ // 1 test
+ features = layer.removeFeatures(layer.features);
+ t.ok(layer.features.length == 0,
+ "OpenLayers.Layer.Vector.removeFeatures(layer.features) removes all feature from the features array");
+
+ // 4 tests
+ log = [];
+ layer.addFeatures([pointFeature1, pointFeature2]);
+ layer.events.register("featuresremoved", null, function(obj) {
+ log.push(obj);
+ });
+ layer.removeFeatures(layer.features);
+ t.eq(log.length, 1,
+ "\"featuresremoved\" triggered once [0]");
+ t.eq(log[0].features.length, 2,
+ "\"featuresremoved\" listener is passed two features [0]");
+ t.ok(log[0].features[0] == pointFeature1,
+ "\"featuresremoved\" listener is passed the correct feature at index 0 [0]");
+ t.ok(log[0].features[1] == pointFeature2,
+ "\"featuresremoved\" listener is passed the correct feature at index 1 [0]");
+ layer.events.remove("featuresremoved");
+
+ // 4 tests
+ log = [];
+ layer.addFeatures([
+ pointFeature1, pointFeature2,
+ pointFeature1.clone(), pointFeature2.clone()
+ ]);
+ layer.selectedFeatures.push(pointFeature1);
+ layer.selectedFeatures.push(pointFeature2);
+ layer.events.register("featuresremoved", null, function(obj) {
+ log.push(obj);
+ });
+ layer.removeFeatures(layer.selectedFeatures);
+ t.eq(log.length, 1,
+ "\"featuresremoved\" triggered once [1]");
+ t.eq(log[0].features.length, 2,
+ "\"featuresremoved\" listener is passed two features [1]");
+ t.ok(log[0].features[0] == pointFeature1,
+ "\"featuresremoved\" listener is passed the correct feature at index 0 [1]");
+ t.ok(log[0].features[1] == pointFeature2,
+ "\"featuresremoved\" listener is passed the correct feature at index 1 [1]");
+ layer.events.remove("featuresremoved");
+ layer.removeFeatures(layer.features);
+
+ // 6 tests
+ layer.events.register('beforefeatureremoved', null, function(obj) {
+ t.ok(pointFeature1 == obj.feature,
+ "OpenLayers.Layer.Vector.removeFeatures triggers beforefeatureremoved with correct feature passed to callback");
+ });
+ layer.events.register('featureremoved', null, function(obj) {
+ t.ok(pointFeature1 == obj.feature,
+ "OpenLayers.Layer.Vector.removeFeatures triggers featureremoved with correct feature passed to callback");
+ });
+ layer.events.register('featuresremoved', null, function(obj) {
+ t.ok(pointFeature1 == obj.features[0],
+ "OpenLayers.Layer.Vector.removeFeatures triggers featuresremoved with correct features passed to callback");
+ });
+ layer.addFeatures([pointFeature1]);
+ layer.removeFeatures([pointFeature1]);
+ layer.addFeatures([pointFeature1]);
+ layer.removeFeatures(layer.features);
+
+ // 0 test
+ layer.events.register('beforefeatureremoved', null, function(obj) {
+ t.fail("OpenLayers.Layer.Vector.removeFeatures triggers beforefeatureremoved while it must not");
+ });
+ layer.events.register('featureremoved', null, function(obj) {
+ t.fail("OpenLayers.Layer.Vector.removeFeatures triggers featureremoved while it must not");
+ });
+ layer.events.register('featuresremoved', null, function(obj) {
+ t.fail("OpenLayers.Layer.Vector.removeFeatures triggers featuresremoved while it must not");
+ });
+ layer.addFeatures([pointFeature1]);
+ layer.removeFeatures([pointFeature1], {silent: true});
+ }
+
+ function test_Layer_Vector_drawFeature(t) {
+ t.plan(7);
+ var layer = new OpenLayers.Layer.Vector("Test Layer", {isBaseLayer: true});
+ var map = new OpenLayers.Map('map', {
+ maxExtent: new OpenLayers.Bounds(-100, -100, 100, 100)
+ });
+ map.addLayer(layer);
+ var geometry = new OpenLayers.Geometry.Point(10, 10);
+ var feature = new OpenLayers.Feature.Vector(geometry);
+
+ var f, s;
+
+ // Bogus layer renderer needs some methods
+ // for functional tests.
+ layer.drawn = true;
+ layer.renderer = {
+ drawFeature: function(feature, style) {
+ f = feature;
+ s = style;
+ },
+ root: document.createElement("div"),
+ destroy: function() { },
+ eraseFeatures: function() {},
+ setExtent: function() {}
+ };
+
+
+ layer.drawFeature(feature);
+ t.ok(geometry.equals(f.geometry),
+ "calls layer.renderer.drawFeature() with feature.geometry");
+
+ feature.style = {foo: "bar"};
+ layer.drawFeature(feature);
+ t.eq(feature.style, s,
+ "calls layer.renderer.drawFeature() with feature.style");
+
+ feature.style = null;
+ layer.style = {foo: "bar"};
+ layer.drawFeature(feature);
+ t.eq(layer.style.foo, s.foo,
+ "given null feature style, uses layer style");
+
+ feature.style = {foo1: "bar1"};
+ layer.style = {foo2: "bar2"};
+ var customStyle = {foo: "bar"};
+ layer.drawFeature(feature, customStyle);
+ t.eq(customStyle.foo, s.foo,
+ "given a custom style, renders with that");
+
+ // the real renderer's drawFeature method is tested in Renderer.html
+ layer.renderer.drawFeature = function(feature) {
+ return(feature.geometry.getBounds().intersectsBounds(map.getExtent()));
+ }
+ // reset the drawn to null as if the layer had never been rendered
+ layer.drawn = null;
+
+ layer.drawFeature(feature);
+ t.ok(true, "Trying to draw a feature on an not drawn layer doesn't throw any error.");
+
+ layer.addFeatures([feature]);
+
+ map.setCenter(new OpenLayers.Bounds(0, 0, 0, 0), 6);
+ t.ok(layer.unrenderedFeatures[feature.id], "Did not render feature outside the viewport.");
+ map.panTo(new OpenLayers.LonLat(10, 10));
+ t.ok(!layer.unrenderedFeatures[feature.id], "Rendered feature inside the viewport.");
+
+ layer.features = [];
+ }
+
+ function test_deleted_state(t) {
+ t.plan(9);
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector();
+ map.addLayer(layer);
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(10, 10)
+ );
+ var log;
+ layer.renderer = {
+ drawFeature: function(f, s) {
+ log = {
+ feature: f,
+ style: s
+ };
+ },
+ destroy: function() {}
+ };
+
+ // draw feature with no state
+ layer.drawn = true;
+ layer.drawFeature(feature);
+ t.ok(log.feature === feature, "[no state] drawFeature called with correct feature");
+ t.ok(log.style.display !== "none", "[no state] drawFeature called with style display not none");
+
+ // draw feature with delete style
+ feature.state = OpenLayers.State.DELETE;
+ layer.drawFeature(feature);
+ t.ok(log.feature === feature, "[delete] drawFeature called with correct feature");
+ t.eq(log.style.display, "none", "[delete] drawFeature called with style display none");
+
+ // undelete the feature and redraw
+ delete feature.state;
+ delete feature.renderIntent;
+ layer.drawFeature(feature);
+ t.ok(log.feature === feature, "[undelete] drawFeature called with correct feature");
+ t.ok(log.style.display !== "none", "[undelete] drawFeature called with style display not none");
+
+ // change deleted style
+ layer.styleMap.styles["delete"] = new OpenLayers.Style({fillOpacity: 0.1});
+
+ // draw feature with delete style
+ feature.state = OpenLayers.State.DELETE;
+ layer.drawFeature(feature);
+ t.ok(log.feature === feature, "[draw deleted] drawFeature called with correct feature");
+ t.ok(log.style.display !== "none", "[draw deleted] drawFeature called with style display not none");
+ t.eq(log.style.fillOpacity, 0.1,"[draw deleted] drawFeature called with correct fill opacity");
+
+
+ }
+
+ function test_Layer_Vector_eraseFeatures(t) {
+ t.plan(2);
+ var layer = new OpenLayers.Layer.Vector("Test Layer");
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ var geometry = new OpenLayers.Geometry.Point(10, 10);
+ var feature = new OpenLayers.Feature.Vector(geometry);
+
+ var f;
+ layer.renderer = {
+ eraseFeatures: function(features) {
+ f = features[0];
+ },
+ destroy: function() { }
+ };
+
+ layer.eraseFeatures([feature]);
+ t.ok(f, "calls layer.renderer.eraseFeatures");
+ t.ok(geometry.equals(f.geometry),
+ "calls layer.renderer.eraseFeatures() given an array of features");
+ }
+
+ function test_Layer_Vector_destroyFeatures (t) {
+ t.plan(8);
+ var layer = new OpenLayers.Layer.Vector(name);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ var features = [], i;
+ for (i = 0; i < 5; i++) {
+ features.push(new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0,0)));
+ }
+ layer.addFeatures(features);
+ t.eq(layer.features.length, 5, "addFeatures adds 5 features");
+ layer.selectedFeatures.push(features[0]);
+ layer.destroyFeatures();
+ t.eq(layer.features.length, 0, "destroyFeatures triggers removal");
+ t.eq(layer.selectedFeatures, [], "Destroy features removes selected features");
+ var allDestroyed = true;
+ for (i = 0; i < 5; i++) {
+ if(features[i].geometry) {
+ allDestroyed = false;
+ }
+ }
+ t.ok(allDestroyed, "destroyFeatures actually destroys features");
+ features = [];
+ for (i = 0; i < 5; i++) {
+ features.push(new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0,0)));
+ }
+ layer.addFeatures(features);
+ layer.selectedFeatures.push(features[0]);
+ layer.selectedFeatures.push(features[1]);
+ layer.destroyFeatures([features[0], features[1]]);
+ t.eq(layer.features.length, 3, "destroyFeatures removes appropriate features");
+ t.eq(layer.selectedFeatures, [], "destroyFeatures removes appropriate selected features");
+ t.eq(features[0].geometry, null, "destroyFeatures destroys feature 0");
+ t.eq(features[1].geometry, null, "destroyFeatures destroys feature 1");
+ }
+
+ function test_Layer_Vector_destroy (t) {
+ t.plan(6);
+
+ var options = {protocol: new OpenLayers.Protocol(),
+ strategies: [new OpenLayers.Strategy(), new OpenLayers.Strategy()]}
+ var layer = new OpenLayers.Layer.Vector(name, options);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq(layer.map, null, "layer.map is null after destroy");
+ t.ok(!layer.renderer, "layer.renderer is falsey");
+ var err;
+ try {
+ layer.getFeatureFromEvent({target: "map"});
+ } catch (ex) {
+ err = ex;
+ }
+ t.ok(err, "Error thrown when calling getFeatureFromEvent on destroyed layer");
+
+ t.eq(layer.protocol, null, "layer.protocol is null after destroy");
+ t.eq(layer.strategies, null, "layer.strategies is null after destroy");
+
+ // test that we can call layer.destroy a second time without trouble
+ try {
+ layer.destroy();
+ layer.destroy();
+ t.ok(true, "layer.destroy called twice without any issues");
+ } catch(err) {
+ t.fail("calling layer.destroy twice triggers exception: " + err + " in " + err.fileName + " line " + err.lineNumber);
+ }
+
+ }
+
+ function test_Layer_Vector_clone(t) {
+ t.plan(5);
+ var original = new OpenLayers.Layer.Vector(name, {dummyOption: "foo"});
+ original.addFeatures([new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,2), {foo: "bar"})]);
+ var clone = original.clone();
+ t.ok(clone instanceof OpenLayers.Layer.Vector, "clone is an instance of OpenLayers.Layer.Vector");
+ t.ok(clone.name, original.name, "clone has the same name as the original");
+ t.ok(clone.features[0] != original.features[0], "clone's feature does not equal the original's feature");
+ t.eq(clone.features[0].attributes.foo, original.features[0].attributes.foo, "clone's feature has the same attributes as the original's feature");
+ t.eq(clone.dummyOption, original.dummyOption, "clone's dummyOption equals the original's dummy option");
+ }
+
+ function test_Layer_Vector_externalGraphic(t) {
+ t.plan(11);
+ var layer = new OpenLayers.Layer.Vector("Test Layer", {isBaseLayer: true});
+ var renderer = layer.renderer;
+ var map = new OpenLayers.Map('map');
+ map.addLayers([layer]);
+
+ var geometryX = 10;
+ var geometryY = 10;
+ var geometry = new OpenLayers.Geometry.Point(geometryX, geometryY);
+ var feature = new OpenLayers.Feature.Vector(geometry);
+
+ map.zoomToMaxExtent();
+
+ var customStyle1 = new Object({
+ externalGraphic: 'test.png',
+ pointRadius: 10
+ });
+ var customStyle2 = new Object({
+ externalGraphic: 'test.png',
+ graphicWidth: 12
+ });
+ var customStyle3 = new Object({
+ externalGraphic: 'test.png',
+ graphicHeight: 14
+ });
+ var customStyle4 = new Object({
+ externalGraphic: 'test.png',
+ graphicWidth: 24,
+ graphicHeight: 16
+ });
+ var customStyle5 = new Object({
+ externalGraphic: 'test.png',
+ graphicWidth: 24,
+ graphicOpacity: 1
+ });
+ var customStyle6 = new Object({
+ externalGraphic: 'test.png',
+ graphicWidth: 24,
+ graphicHeight: 16,
+ graphicXOffset: -24,
+ graphicYOffset: -16
+ });
+
+ var root = renderer.vectorRoot;
+ if (layer.renderer.CLASS_NAME == 'OpenLayers.Renderer.SVG') {
+ feature.style = customStyle1;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.getAttributeNS(null, 'width'),
+ (2*customStyle1.pointRadius).toString(),
+ "given a pointRadius, width equals 2*pointRadius");
+ t.eq(root.firstChild.getAttributeNS(null, 'height'),
+ (2*customStyle1.pointRadius).toString(),
+ "given a pointRadius, height equals 2*pointRadius");
+ feature.style = customStyle2;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.getAttributeNS(null, 'width'),
+ root.firstChild.getAttributeNS(null, 'height'),
+ "given a graphicWidth, width equals height");
+ t.eq(root.firstChild.getAttributeNS(null, 'width'),
+ customStyle2.graphicWidth.toString(),
+ "width is set correctly");
+ feature.style = customStyle3;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.getAttributeNS(null, 'height'),
+ root.firstChild.getAttributeNS(null, 'width'),
+ "given a graphicHeight, height equals width");
+ t.eq(root.firstChild.getAttributeNS(null, 'height'),
+ customStyle3.graphicHeight.toString(),
+ "height is set correctly");
+ feature.style = customStyle4;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.getAttributeNS(null, 'height'),
+ customStyle4.graphicHeight.toString(),
+ "given graphicHeight and graphicWidth, both are set: height");
+ t.eq(root.firstChild.getAttributeNS(null, 'width'),
+ customStyle4.graphicWidth.toString(),
+ "given graphicHeight and graphicWidth, both are set: width");
+ feature.style = customStyle5;
+ layer.drawFeature(feature);
+ // we use startsWith here as some browsers (at least Safari 3 and FireFox 4)
+ // do not append a semi-colon to the opacity string
+ t.ok(OpenLayers.String.startsWith(
+ root.firstChild.getAttributeNS(null, 'style'),
+ "opacity: " + customStyle5.graphicOpacity.toString()),
+ "graphicOpacity correctly set");
+ feature.style = customStyle6;
+ layer.drawFeature(feature);
+ var x = geometryX / renderer.getResolution() + renderer.left;
+ var y = geometryY / renderer.getResolution() - renderer.top;
+ // SVG setStyle() gets x and y using getAttributeNS(), which returns
+ // a value with only 3 decimal digits. To mimic this we use toFixed(3) here
+ x = x.toFixed(3);
+ y = y.toFixed(3);
+ // toFixed() returns a string
+ x = parseFloat(x);
+ y = parseFloat(y);
+ t.eq(root.firstChild.getAttributeNS(null, 'x'),
+ (x + customStyle6.graphicXOffset).toFixed().toString(),
+ "graphicXOffset correctly set");
+ t.eq(root.firstChild.getAttributeNS(null, 'y'),
+ (-y + customStyle6.graphicYOffset).toFixed().toString(),
+ "graphicYOffset correctly set");
+ }
+ if (layer.renderer.CLASS_NAME == 'OpenLayers.Renderer.VML') {
+ feature.style = customStyle1;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.style.width,
+ (2*customStyle1.pointRadius).toString()+'px',
+ "given a pointRadius, width equals 2*pointRadius");
+ t.eq(root.firstChild.style.height,
+ (2*customStyle1.pointRadius).toString()+'px',
+ "given a pointRadius, height equals 2*pointRadius");
+ feature.style = customStyle2;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.style.width,
+ root.firstChild.style.height,
+ "given a graphicWidth, width equals height");
+ t.eq(root.firstChild.style.width,
+ customStyle2.graphicWidth.toString()+'px',
+ "width is set correctly");
+ feature.style = customStyle3;
+ layer.drawFeature(feature);
+ t.eq(root.firstChild.style.height,
+ root.firstChild.style.width,
+ "given a graphicHeight, height equals width");
+ t.eq(root.firstChild.style.height,
+ customStyle3.graphicHeight.toString()+'px',
+ "height is set correctly");
+ feature.style = customStyle4;
+ layer.drawFeature(feature);
+ var left = parseInt(root.firstChild.style.left);
+ var top = parseInt(root.firstChild.style.top);
+ t.eq(root.firstChild.style.height,
+ customStyle4.graphicHeight.toString()+'px',
+ "given graphicHeight and graphicWidth, both are set: height");
+ t.eq(root.firstChild.style.width,
+ customStyle4.graphicWidth.toString()+'px',
+ "given graphicHeight and graphicWidth, both are set: width");
+ feature.style = customStyle5;
+ layer.renderer.clear();
+ layer.drawFeature(feature);
+ var fill = root.firstChild.getElementsByTagName("v:fill")[0];
+ var opacity;
+ if(fill) {
+ opacity = fill.getAttribute('opacity');
+ }
+ if(opacity === undefined) {
+ fill = root.firstChild.getElementsByTagName("fill")[0];
+ opacity = fill.getAttribute('opacity');
+ }
+ t.eq(opacity,
+ customStyle5.graphicOpacity,
+ "graphicOpacity correctly set");
+ feature.style = customStyle6;
+ layer.drawFeature(feature);
+ var offsetLeft = parseInt(root.firstChild.style.left);
+ var offsetTop = parseInt(root.firstChild.style.top);
+ t.eq((offsetLeft-left)*2, customStyle6.graphicXOffset, "graphicXOffset correctly set");
+ t.eq((top-offsetTop)*2, customStyle6.graphicYOffset, "graphicYOffset correctly set");
+
+ }
+ }
+
+ function test_removeLayer_drawFeature(t) {
+ // test behaviour when features are redrawn while
+ // the layer has been removed from the map
+
+ t.plan(1);
+
+ // set up
+
+ var map, layer, feature;
+
+ map = new OpenLayers.Map("map");
+ map.addLayer(new OpenLayers.Layer("base", {isBaseLayer: true}));
+
+ layer = new OpenLayers.Layer.Vector("vector");
+ map.addLayer(layer);
+
+ map.zoomToMaxExtent();
+
+ feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(1.0, 1.0));
+ layer.addFeatures(feature);
+
+ // test
+
+ map.removeLayer(layer);
+ layer.drawFeature(feature);
+ layer.drawFeature(feature);
+ map.addLayer(layer);
+
+ var count = 0, node;
+ while(node = document.getElementById(feature.geometry.id)) {
+ node.parentNode.removeChild(node);
+ count++;
+ }
+
+ t.eq(count, 1, "one geometry added, one geometry removed");
+
+ // tear down
+
+ map.destroy();
+ }
+
+ function test_vectorBeforeFeatureAddedVeto(t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.Vector("");
+ map.addLayer(layer);
+ var feature = new OpenLayers.Feature.Vector(new OpenLayers.Geometry(1.0, 1.0));
+ layer.addFeatures([feature]);
+
+ var addedFeatures = [];
+ var beforefeatureadded_veto = function(evt) { return false; };
+ layer.events.register("beforefeatureadded", layer, beforefeatureadded_veto);
+ layer.events.register("featuresadded", layer, function(evt) {
+ if (evt.features) {
+ for (var i = 0; i < evt.features.length; i++) {
+ addedFeatures.push(evt.features[i]);
+ }
+ }
+ });
+
+ var blankFeatures = [
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry(1.0, 1.0)),
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry(1.0, 1.0))];
+ layer.addFeatures(blankFeatures);
+
+ t.eq(layer.features.length, 1,
+ "features not added to layer after beforefeatureadded veto");
+ t.eq(addedFeatures.length, 0,
+ "no features sent to featuresadded on feature veto");
+
+ addedFeatures = [];
+
+ layer.events.unregister("beforefeatureadded", layer, beforefeatureadded_veto);
+ beforefeatureadded_veto = function(evt) { return true; };
+ layer.events.register("beforefeatureadded", layer, beforefeatureadded_veto);
+
+ layer.addFeatures(blankFeatures);
+
+ t.eq(layer.features.length, 3,
+ "features added to layer as expected");
+ t.eq(addedFeatures.length, 2,
+ "featuresadded event received expected number of features");
+ }
+
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/Vector/RootContainer.html b/misc/openlayers/tests/Layer/Vector/RootContainer.html
new file mode 100644
index 0000000..aa92923
--- /dev/null
+++ b/misc/openlayers/tests/Layer/Vector/RootContainer.html
@@ -0,0 +1,63 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer, map;
+
+ function test_RootContainer_collectResetRoots(t) {
+
+ map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector("layer1");
+ var layer2 = new OpenLayers.Layer.Vector("layer2");
+ layer = new OpenLayers.Layer.Vector.RootContainer("layer_1_2", {
+ layers: [layer1, layer2]
+ });
+
+ // we cannot test this with a renderer that does not hava a rendererRoot
+ var plan = layer.renderer.rendererRoot ? 4 : 0;
+ t.plan(plan);
+ if(plan == 0) {
+ return;
+ }
+
+ var numRoots = layer.renderer.rendererRoot.childNodes.length;
+
+ // addLayers will call setMap() for layer, which will call collectRoots()
+ map.addLayers([layer1, layer2, layer]);
+ t.eq(layer.renderer.rendererRoot.childNodes.length, numRoots * 3, "layer has correct number of renderer roots");
+ t.eq(layer1.renderer.rendererRoot.childNodes.length, 0, "layer1 has no own renderer root");
+
+ layer.resetRoots();
+ t.eq(layer.renderer.rendererRoot.childNodes.length, numRoots, "roots removed from container");
+ t.eq(layer1.renderer.rendererRoot.childNodes.length, numRoots, "root re-added to original layer");
+ }
+
+ function test_RootContainer_getFeatureFromEvent(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var layer1 = new OpenLayers.Layer.Vector("layer1");
+ var layer2 = new OpenLayers.Layer.Vector("layer2");
+ layer = new OpenLayers.Layer.Vector.RootContainer("layer_1_2", {
+ layers: [layer1, layer2]
+ });
+ map.addLayers([layer1, layer2, layer]);
+ var feature1 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0,1));
+ var feature2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1,0));
+ layer1.addFeatures(feature1);
+ layer2.addFeatures(feature2);
+ t.eq(layer.getFeatureFromEvent({
+ srcElement: {
+ _featureId: feature1.id
+ }
+ }).id, feature1.id, "feature from layer1 found");
+ t.eq(layer.getFeatureFromEvent({srcElement: {
+ _featureId: feature2.id
+ }}).id, feature2.id, "feature from layer2 found");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/WMS.html b/misc/openlayers/tests/Layer/WMS.html
new file mode 100644
index 0000000..b990f07
--- /dev/null
+++ b/misc/openlayers/tests/Layer/WMS.html
@@ -0,0 +1,583 @@
+<html>
+<head>
+ <script type="text/javascript">var oldAlert = window.alert, gMess; window.alert = function(message) {gMess = message; return true;};</script>
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script type="text/javascript">window.alert = oldAlert;</script>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ // turn off animation frame handling, so we can check img urls in tests
+ delete OpenLayers.Layer.Grid.prototype.queueTileDraw;
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/jpeg'};
+
+ function test_Layer_WMS_constructor (t) {
+ t.plan( 15 );
+
+ var trans_format = "image/png";
+ if (OpenLayers.Util.alphaHack()) { trans_format = "image/gif"; }
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ t.ok( layer instanceof OpenLayers.Layer.WMS, "new OpenLayers.Layer.WMS returns object" );
+ t.eq( layer.url, "http://octo.metacarta.com/cgi-bin/mapserv", "layer.url is correct (HTTPRequest inited)" );
+ t.eq( layer.params.MAP, "/mapdata/vmap_wms.map", "params passed in correctly uppercased" );
+
+ t.eq( layer.params.SERVICE, "WMS", "default params correclty uppercased and copied");
+
+ t.eq(layer.isBaseLayer, true, "no transparency setting, wms is baselayer");
+
+ params.TRANSPARENT = "true";
+ var layer2 = new OpenLayers.Layer.WMS(name, url, params);
+ t.eq(layer2.isBaseLayer, false, "transparency == 'true', wms is not baselayer");
+
+ params.TRANSPARENT = "TRUE";
+ var layer3 = new OpenLayers.Layer.WMS(name, url, params);
+ t.eq(layer3.isBaseLayer, false, "transparency == 'TRUE', wms is not baselayer");
+ t.eq(layer3.params.FORMAT, trans_format, "transparent = TRUE causes non-image/jpeg format");
+
+ params.TRANSPARENT = "TRuE";
+ var layer4 = new OpenLayers.Layer.WMS(name, url, params);
+ t.eq(layer4.isBaseLayer, false, "transparency == 'TRuE', wms is not baselayer");
+ t.eq(layer4.params.FORMAT, trans_format, "transparent = TRuE causes non-image/jpeg format");
+
+ params.TRANSPARENT = true;
+ var layer5 = new OpenLayers.Layer.WMS(name, url, params);
+ t.eq(layer5.isBaseLayer, false, "transparency == true, wms is not baselayer");
+ t.eq(layer5.params.FORMAT, trans_format, "transparent = true causes non-image/jpeg format");
+
+ params.TRANSPARENT = false;
+ var layer6 = new OpenLayers.Layer.WMS(name, url, params);
+ t.eq(layer6.isBaseLayer, true, "transparency == false, wms is baselayer");
+
+ params.TRANSPARENT = true;
+ var layer7 = new OpenLayers.Layer.WMS(name, url, params, {noMagic: true});
+ t.eq(layer7.params.FORMAT, "image/jpeg", "When using noMagic true image/jpeg will not be automagically switched to image/png or image/gif if transparent");
+
+ params.TRANSPARENT = true;
+ var layer8 = new OpenLayers.Layer.WMS(name, url, params, {noMagic: true});
+ t.eq(layer8.isBaseLayer, true, "When using noMagic then transparent means the wms layer is not automagically changed to not being a baselayer");
+
+ params.TRANSPARENT = false;
+
+ }
+
+ function test_Layer_WMS_addtile (t) {
+ t.plan( 6 );
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ map.addLayer(layer);
+ var pixel = new OpenLayers.Pixel(5,6);
+ var tile = layer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
+ tile.draw();
+
+ var img = tile.imgDiv;
+ var tParams = OpenLayers.Util.extend({},
+ OpenLayers.Util.upperCaseObject(params));
+ tParams = OpenLayers.Util.extend(tParams, {
+ BBOX: [1,2,3,4],
+ WIDTH: "256", HEIGHT: "256"
+ });
+ t.eq( tile.url,
+ layer.getFullRequestString(tParams),
+ "image src is created correctly via addtile" );
+ t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+ t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
+
+ var firstChild = layer.div.firstChild;
+ t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
+ t.ok( firstChild == img, "div first child is correct image object" );
+ t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
+ map.destroy();
+ }
+
+ function test_Layer_WMS_bboxEncoding (t) {
+ t.plan( 6 );
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params, {encodeBBOX:true});
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ map.addLayer(layer);
+ var pixel = new OpenLayers.Pixel(5,6);
+ var tile = layer.addTile(new OpenLayers.Bounds(1,2,3,4), pixel);
+ tile.draw();
+
+ var img = tile.imgDiv;
+ var tParams = OpenLayers.Util.extend({},
+ OpenLayers.Util.upperCaseObject(params));
+ tParams = OpenLayers.Util.extend(tParams, {
+ BBOX: "1,2,3,4",
+ WIDTH: "256", HEIGHT: "256"
+ });
+ t.eq( tile.url,
+ layer.getFullRequestString(tParams),
+ "image src is created correctly via addtile" );
+ t.eq( tile.getTile().style.top, "6px", "image top is set correctly via addtile" );
+ t.eq( tile.getTile().style.left, "5px", "image top is set correctly via addtile" );
+
+ var firstChild = layer.div.firstChild;
+ t.eq( firstChild.nodeName.toLowerCase(), "img", "div first child is an image object" );
+ t.ok( firstChild, img, "div first child is correct image object" );
+ t.eq( tile.position.toString(), "x=5,y=6", "Position of tile is set correctly." );
+ map.destroy();
+ }
+
+ function test_Layer_WMS_inittiles (t) {
+ t.plan( 2 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {buffer:2});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0),5);
+ t.eq( layer.grid.length, 8, "Grid rows is correct." );
+ t.eq( layer.grid[0].length, 7, "Grid cols is correct." );
+ map.destroy();
+ }
+
+ function test_Layer_WMS_clone (t) {
+ t.plan(4);
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var options = {tileSize: new OpenLayers.Size(500,50)};
+ var map = new OpenLayers.Map('map', options);
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+
+ layer.grid = [ [6, 7],
+ [8, 9]];
+
+ var clone = layer.clone();
+
+ t.ok( clone.grid != layer.grid, "clone does not copy grid");
+
+ t.ok( clone.tileSize.equals(layer.tileSize), "tileSize correctly cloned");
+
+ layer.tileSize.w += 40;
+
+ t.eq( clone.tileSize.w, 500, "changing layer.tileSize does not change clone.tileSize -- a fresh copy was made, not just copied reference");
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ layer.grid = null;
+ map.destroy();
+ }
+
+ function test_Layer_WMS_isBaseLayer(t) {
+ t.plan(3);
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ t.ok( layer.isBaseLayer, "baselayer is true by default");
+
+ var newParams = OpenLayers.Util.extend({}, params);
+ newParams.transparent = "true";
+ layer = new OpenLayers.Layer.WMS(name, url, newParams);
+ t.ok( !layer.isBaseLayer, "baselayer is false when transparent is set to true");
+
+ layer = new OpenLayers.Layer.WMS(name, url, params, {isBaseLayer: false});
+ t.ok( !layer.isBaseLayer, "baselayer is false when option is set to false" );
+ }
+
+ function test_Layer_WMS_mergeNewParams (t) {
+ t.plan( 4 );
+
+ var map = new OpenLayers.Map("map");
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ layer.redraw = function() {
+ t.ok(true, "layer is redrawn after new params merged");
+ }
+
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.LAYERS, "sooper", "mergeNewParams() overwrites well");
+ t.eq( layer.params.CHICKPEAS, "image/png", "mergeNewParams() adds well");
+
+ newParams.CHICKPEAS = 151;
+
+ t.eq( layer.params.CHICKPEAS, "image/png", "mergeNewParams() makes clean copy of hashtable");
+ map.destroy();
+ }
+
+ function test_Layer_WMS_getFullRequestString (t) {
+
+
+ t.plan( 4 );
+ var map = new OpenLayers.Map('map');
+ map.projection = "xx";
+ var tUrl = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var tParams = { layers: 'basic',
+ format: 'image/png'};
+ var tLayer = new OpenLayers.Layer.WMS(name, tUrl, tParams);
+ map.addLayer(tLayer);
+ var str = tLayer.getFullRequestString();
+ var tParams = {
+ LAYERS: "basic", FORMAT: "image/png", SERVICE: "WMS",
+ VERSION: "1.1.1", REQUEST: "GetMap", STYLES: "",
+ SRS: "xx"
+ };
+ t.eq(str,
+ tUrl + "?" + OpenLayers.Util.getParameterString(tParams),
+ "getFullRequestString() adds SRS value");
+
+ map.removeLayer(tLayer);
+ tLayer.projection = "none";
+ map.addLayer(tLayer);
+ str = tLayer.getFullRequestString();
+ delete tParams['SRS'];
+ t.eq(str,
+ tUrl + "?" + OpenLayers.Util.getParameterString(tParams),
+ "getFullRequestString() by default does *not* add SRS value if projection is 'none'");
+ map.destroy();
+
+ map = new OpenLayers.Map("map", {projection: "EPSG:4326"});
+ var layerProj = new OpenLayers.Projection("FOO", {
+ equals: function() {return true},
+ getCode: function() {return "FOO"}
+ });
+ tLayer = new OpenLayers.Layer.WMS(name, tUrl, tParams, {projection: layerProj});
+ map.addLayer(tLayer);
+ str = tLayer.getFullRequestString();
+ tParams.SRS = "FOO";
+ t.eq(str,
+ tUrl + "?" + OpenLayers.Util.getParameterString(tParams),
+ "getFullRequestString() uses the layer projection if it equals the map projection");
+ map.destroy();
+
+ map = new OpenLayers.Map("map", {projection: "EPSG:4326"});
+ map.addLayer(new OpenLayers.Layer(null, {isBaseLayer: true}));
+ tLayer = new OpenLayers.Layer.WMS(name, tUrl);
+ tLayer.map = map;
+ var error;
+ try {
+ tLayer.getFullRequestString();
+ error = false;
+ } catch(err) {
+ error = true;
+ }
+ t.ok(!error, "no error on getFullRequestString if layer has no projection");
+ map.destroy();
+
+ }
+
+ function test_setOpacity(t) {
+ t.plan(1);
+
+ var layer = new OpenLayers.Layer.WMS(
+ null, "/bogus/wms", {layers: "mylayer"}
+ );
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer);
+
+ map.zoomToMaxExtent();
+
+ layer.setOpacity(0.5);
+ t.delay_call(1, function() {
+ t.eq(parseFloat(layer.div.firstChild.style.opacity), 0.5, "opacity set");
+ map.destroy();
+ });
+ }
+
+
+ function test_Layer_WMS_noGutters (t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("no gutter layer", url, params, {gutter: 0});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ var request = layer.getURL(tile.bounds);
+ var args = OpenLayers.Util.getParameters(request);
+ t.eq(parseInt(args['WIDTH']),
+ tile.size.w,
+ "layer without gutter requests images that are as wide as the tile");
+ t.eq(parseInt(args['HEIGHT']),
+ tile.size.h,
+ "layer without gutter requests images that are as tall as the tile");
+
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_Layer_WMS_gutters (t) {
+ t.plan(2);
+ var gutter = 15;
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("gutter layer", url, params, {gutter: gutter});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ var request = layer.getURL(tile.bounds);
+ var args = OpenLayers.Util.getParameters(request);
+ t.eq(parseInt(args['WIDTH']),
+ tile.size.w + (2 * gutter),
+ "layer with gutter requests images that are wider by twice the gutter");
+ t.eq(parseInt(args['HEIGHT']),
+ tile.size.h + (2 * gutter),
+ "layer with gutter requests images that are taller by twice the gutter");
+
+ layer.destroy();
+ map.destroy();
+
+ }
+
+ function test_maxExtent(t) {
+ t.plan(5);
+
+ var layer = new OpenLayers.Layer.WMS(
+ null, "http://example.com/wms",
+ {layers: "foo"},
+ {maxExtent: [-180, 0, 0, 90]}
+ );
+
+ t.ok(layer.maxExtent instanceof OpenLayers.Bounds, "(array) bounds instance");
+ t.eq(layer.maxExtent.left, -180, "(array) bounds left");
+ t.eq(layer.maxExtent.bottom, 0, "(array) bounds left");
+ t.eq(layer.maxExtent.right, 0, "(array) bounds right");
+ t.eq(layer.maxExtent.top, 90, "(array) bounds top");
+
+ layer.destroy();
+ }
+
+ function test_minExtent(t) {
+ t.plan(5);
+
+ var layer = new OpenLayers.Layer.WMS(
+ null, "http://example.com/wms",
+ {layers: "foo"},
+ {minExtent: [-180, 0, 0, 90]}
+ );
+
+ t.ok(layer.minExtent instanceof OpenLayers.Bounds, "(array) bounds instance");
+ t.eq(layer.minExtent.left, -180, "(array) bounds left");
+ t.eq(layer.minExtent.bottom, 0, "(array) bounds left");
+ t.eq(layer.minExtent.right, 0, "(array) bounds right");
+ t.eq(layer.minExtent.top, 90, "(array) bounds top");
+
+ layer.destroy();
+ }
+
+ function test_tileOrigin(t) {
+ t.plan(4);
+
+ var dummy = new OpenLayers.Layer(null, {isBaseLayer: true});
+ var unconstrained = new OpenLayers.Layer.WMS(
+ null, "http://example.com/wms",
+ {layers: "unconstrained"},
+ {isBaseLayer: false, buffer: 0}
+ );
+ var constrained = new OpenLayers.Layer.WMS(
+ null, "http://example.com/wms-c",
+ {layers: "constrained"},
+ {buffer: 0, isBaseLayer: false, tileOrigin: new OpenLayers.LonLat(-180, -90)}
+ );
+ var map = new OpenLayers.Map({
+ div: "map",
+ maxExtent: new OpenLayers.Bounds(-185, -95, 185, 95),
+ maxResolution: 1.40625,
+ layers: [dummy, unconstrained, constrained],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+
+ t.eq(unconstrained.grid[1][0].bounds.bottom, -95, "unconstrained bottom correct");
+ t.eq(unconstrained.grid[1][0].bounds.left, -185, "unconstrained left correct");
+ t.eq(constrained.grid[1][0].bounds.bottom, -90, "constrained bottom correct");
+ t.eq(constrained.grid[1][0].bounds.left, -180, "constrained left correct");
+
+ map.destroy();
+
+ }
+
+ function test_Layer_WMS_destroy (t) {
+
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ // checks to make sure superclass (grid) destroy() was called
+
+ t.ok( layer.grid == null, "grid set to null");
+ }
+
+ function test_customProjection(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map', {
+ units: 'm',
+ projection: new OpenLayers.Projection('EPSG:28992'),
+ maxExtent: new OpenLayers.Bounds(0, 300000, 300000, 6250000)
+ });
+ var layer = new OpenLayers.Layer.WMS(null, url, {layers: 'mylayer', version: '1.3.0'});
+ map.addLayer(layer);
+ var error = false;
+ try {
+ map.setCenter(new OpenLayers.LonLat(100000,300000), 5);
+ } catch(err) {
+ error = true;
+ }
+ t.ok(!error, "no error on getURL if layer has a custom projection and no defaults defined");
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_Layer_WMS_v13(t) {
+
+ t.plan(6);
+
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map = new OpenLayers.Map( 'map' );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://myserver.org/wms?",
+ {layers: 'mylayer', version: '1.3.0'},
+ {singleTile: true}
+ );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var url = layer.getURL(map.getExtent());
+ var params = url.split("&");
+ var bbox;
+ for (var i=0, len=params.length; i<len; i++) {
+ var param = params[i];
+ var a = param.split('=');
+ if (a[0] === 'BBOX') {
+ bbox = a[1];
+ break;
+ }
+ }
+
+ t.eq(layer.params.CRS, "EPSG:4326", "In WMS 1.3 SRS is now CRS");
+ t.eq(bbox, "27.9150390625,-5.986328125,52.0849609375,15.986328125", "Axis sequence is lat lon for EPSG:4326 in WMS 1.3.0");
+
+ var layer2 = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://myserver.org/wms?",
+ {layers: 'mylayer', version: '1.1.1'},
+ {singleTile: true}
+ );
+ map.addLayer(layer2);
+
+ var url = layer2.getURL(map.getExtent());
+ var params = url.split("&");
+ var bbox;
+ for (var i=0, len=params.length; i<len; i++) {
+ var param = params[i];
+ var a = param.split('=');
+ if (a[0] === 'BBOX') {
+ bbox = a[1];
+ break;
+ }
+ }
+
+ t.eq(layer2.params.SRS, "EPSG:4326", "In WMS 1.1.1 parameter is called SRS");
+ t.eq(bbox, "-5.986328125,27.9150390625,15.986328125,52.0849609375", "Axis sequence is lon lat for EPSG:4326 in WMS 1.1.1");
+
+ map.destroy();
+
+ // CRS:84 has normal axis sequence (lon lat)
+ var map = new OpenLayers.Map( 'map', {projection: 'CRS:84'} );
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://myserver.org/wms?",
+ {layers: 'mylayer', version: '1.3.0'},
+ {singleTile: true}
+ );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+
+ var url = layer.getURL(map.getExtent());
+ var params = url.split("&");
+ var bbox, exceptions;
+ for (var i=0, len=params.length; i<len; i++) {
+ var param = params[i];
+ var a = param.split('=');
+ if (a[0] === 'EXCEPTIONS') {
+ exceptions = a[1];
+ }
+ if (a[0] === 'BBOX') {
+ bbox = a[1];
+ }
+ }
+
+ t.eq(exceptions, "INIMAGE", "If not set, EXCEPTIONS should be INIMAGE for WMS 1.3");
+ t.eq(bbox, "-5.986328125,27.9150390625,15.986328125,52.0849609375", "Axis sequence for CRS:84 is lon lat");
+
+ map.destroy();
+
+ }
+
+ function test_transparent(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map", {allOverlays: true});
+ var layer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://myserver.org/wms?",
+ {layers: 'mylayer', transparent: true}
+ );
+ map.addLayer(layer);
+
+ t.eq(typeof layer.params.TRANSPARENT, "boolean", "transparent param is boolean");
+ t.ok(layer.getFullRequestString({}).indexOf("TRANSPARENT=TRUE") != -1, "Boolean transparent param value is uppercase TRUE");
+ layer.mergeNewParams({transparent: false});
+ t.ok(layer.getFullRequestString({}).indexOf("TRANSPARENT=FALSE") != -1, "Boolean transparent param value is uppercase FALSE");
+
+ layer.mergeNewParams({transparent: "true"});
+ t.eq(typeof layer.params.TRANSPARENT, "string", "transparent param is string");
+ t.ok(layer.getFullRequestString({}).indexOf("TRANSPARENT=true") != -1, "transparent param value passed as provided if String");
+
+ map.destroy();
+ }
+
+ function test_tileBounds(t) {
+ t.plan(3);
+
+ var map = new OpenLayers.Map("map", {projection: "EPSG:3857", zoomMethod: null});
+ var layer = new OpenLayers.Layer.WMS("wms", "../../img/blank.gif");
+ map.addLayer(layer);
+ map.setCenter([0, 0], 1);
+ map.pan(2, -100);
+ map.zoomIn();
+ t.eq(layer.grid[1][0].bounds, new OpenLayers.Bounds(-10018754.17, 0, 0, 10018754.17), "no floating point errors after zooming");
+ map.setCenter([0, 0], 14);
+ var bounds = layer.grid[0][0].bounds.clone();
+ map.pan(260, 520);
+ map.pan(-260, -520);
+ t.eq(layer.grid[0][0].bounds, bounds, "no floating point errors after dragging back and forth");
+ t.eq(bounds.right, 0, "0 is 0, and not some super small number");
+
+ map.destroy();
+ }
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/WMTS.html b/misc/openlayers/tests/Layer/WMTS.html
new file mode 100644
index 0000000..c6dcd4c
--- /dev/null
+++ b/misc/openlayers/tests/Layer/WMTS.html
@@ -0,0 +1,1491 @@
+<html>
+ <head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(6);
+ var xml = document.getElementById("capabilities").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+
+ var layer0 = new OpenLayers.Layer.WMTS({
+ name: "GeoWebCache USA WMTS",
+ url: "http://example.com/geowebcache-1.2.2/service/wmts/",
+ layer: "arcgis-online-wms",
+ style: "",
+ matrixSet: "arcgis-online-wgs84",
+ format: "image/png",
+ isBaseLayer: false,
+ requestEncoding: "KVP",
+ maxResolution: 0.3521969032857032,
+ numZoomLevels: 7,
+ matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds
+ });
+
+ t.ok(layer0 instanceof OpenLayers.Layer.WMTS, "constructor returns instance of OpenLayers.Layer.WMTS");
+ t.eq(layer0.formatSuffix, "png", "formatSuffix is set correct based on 'format' parameter");
+
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST"
+ });
+
+ t.ok(layer1 instanceof OpenLayers.Layer.WMTS, "constructor returns instance of OpenLayers.Layer.WMTS");
+ t.eq(layer1.formatSuffix, "jpg", "formatSuffix is set correct based on default format");
+ t.eq(layer1.tileSize.w, 512.0, "tileSize w is set correctly");
+ t.eq(layer1.tileSize.h, 512.0, "tileSize h is set correctly");
+ }
+
+ function test_moveTo(t) {
+ t.plan(9);
+ var xml = document.getElementById("capabilities").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+
+ var layer0 = new OpenLayers.Layer.WMTS({
+ name: "GeoWebCache USA WMTS",
+ url: "http://example.com/geowebcache-1.2.2/service/wmts/",
+ layer: "arcgis-online-wms",
+ style: "foo",
+ matrixSet: "arcgis-online-wgs84",
+ format: "image/png",
+ requestEncoding: "KVP",
+ maxResolution: 0.3521969032857032,
+ numZoomLevels: 7,
+ matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds
+ });
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer0);
+
+ map.setCenter(new OpenLayers.LonLat(-97, 38), 1);
+
+ t.ok((layer0.tileOrigin instanceof OpenLayers.LonLat), "tileOrigin is an instance of OpenLayers.LonLat");
+ t.ok((layer0.tileOrigin.lon == -180 && layer0.tileOrigin.lat == 90), "tileOrigin is set correctly");
+ t.ok((layer0.tileSize instanceof OpenLayers.Size), "tileSize is an instance of OpenLayers.Size");
+ t.eq(layer0.tileSize.w, 256.0, "tileSize w is set correctly");
+ t.eq(layer0.tileSize.h, 256.0, "tileSize h is set correctly");
+
+ map.setCenter(new OpenLayers.LonLat(-97.0, 38.0), 6);
+
+ t.eq(layer0.tileOrigin.lon, -175, "tileOrigin.lat updated correctly when zoom changed");
+ t.eq(layer0.tileOrigin.lat, 85, "tileOrigin.lat updated correctly when zoom changed");
+ t.eq(layer0.tileSize.w, 512.0, "tileSize w updated correctly when zoom changed");
+ t.eq(layer0.tileSize.h, 512.0, "tileSize h updated correctly when zoom changed");
+
+ map.destroy();
+ }
+
+ function test_clearTiles (t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST"
+ });
+
+ map.addLayer(layer1);
+ map.setCenter(new OpenLayers.LonLat(0,0));
+
+ //grab a reference to one of the tiles
+ var tile = layer1.grid[0][0];
+
+ layer1.clearGrid();
+
+ t.ok( layer1.grid != null, "layer.grid does not get nullified" );
+ map.destroy();
+ }
+
+ function test_getTilesBounds(t) {
+ t.plan(1);
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST"
+ });
+ var bl = {bounds: new OpenLayers.Bounds(1,2,2,3)};
+ var tr = {bounds: new OpenLayers.Bounds(2,3,3,4)};
+ layer1.grid = [[6, tr],[bl, 7]];
+ var bounds = layer1.getTilesBounds();
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+ t.ok(bounds.equals(testBounds), "correct bounds");
+ }
+
+ function test_getResolution(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ maxResolution: 1.40625,
+ requestEncoding: "REST"
+ });
+ map.addLayer(layer1);
+ map.zoom = 5;
+ t.eq(layer1.getResolution(), 0.0439453125, "getResolution() returns correct value");
+ map.destroy();
+ }
+
+ function test_getZoomForExtent(t) {
+ t.plan(2);
+ var bounds, zoom;
+
+ var map = new OpenLayers.Map('map');
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ maxResolution: 1.40625,
+ requestEncoding: "REST"
+ });
+ map.addLayer(layer1);
+ bounds = new OpenLayers.Bounds(10,10,12,12);
+ zoom = layer1.getZoomForExtent(bounds);
+ t.eq(zoom, 8, "correct value for (10,10,12,12)");
+ bounds = new OpenLayers.Bounds(10,10,100,100);
+ zoom = layer1.getZoomForExtent(bounds);
+ t.eq(zoom, 3, "correct value (10,10,100,100)");
+ map.destroy();
+ }
+
+ function test_getURL(t) {
+ t.plan(2);
+ var xml = document.getElementById("capabilities").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+
+ var layer0 = new OpenLayers.Layer.WMTS({
+ name: "GeoWebCache USA WMTS",
+ url: "http://example.com/geowebcache-1.2.2/service/wmts/",
+ layer: "arcgis-online-wms",
+ style: "foo",
+ matrixSet: "arcgis-online-wgs84",
+ format: "image/png",
+ requestEncoding: "KVP",
+ maxResolution: 0.3521969032857032,
+ numZoomLevels: 7,
+ matrixIds: obj.contents.tileMatrixSets["arcgis-online-wgs84"].matrixIds
+ });
+
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ format: "image/jpeg",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST",
+ isBaseLayer: false
+ });
+
+ var options = {
+ controls: [
+ new OpenLayers.Control.LayerSwitcher(),
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.PanZoom()
+ ],
+ projection: "EPSG:4326",
+ maxResolution: 0.3515625,
+ maxExtent: new OpenLayers.Bounds(-180, -90, 180, 90)
+ };
+ var map = new OpenLayers.Map('map', options);
+ map.addLayers([layer0,layer1]);
+ map.setCenter(new OpenLayers.LonLat(-97.0, 38.0), 1);
+ var tileurl0 = layer0.getURL(new OpenLayers.Bounds(-135.0, 0.0, -90.0, 45.0));
+ t.ok(OpenLayers.Util.isEquivalentUrl(tileurl0, "http://example.com/geowebcache-1.2.2/service/wmts/?LAYER=arcgis-online-wms&STYLE=foo&TILEMATRIXSET=arcgis-online-wgs84&FORMAT=image%2Fpng&SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile&TILEMATRIX=arcgis-online-wgs84%3A1&TILEROW=1&TILECOL=1"), "layer0 getURL returns correct url");
+
+ var tileurl1 = layer1.getURL(new OpenLayers.Bounds(-180.0, 0.0, -90.0, 90.0));
+ t.eq(tileurl1, "http://example.com/wmts/1.0.0/world/blue_marble/arcgis_online/1/0/0.jpg", "layer1 getURL returns correct url");
+ map.destroy();
+ }
+
+ function test_getURL_resourceUrl(t) {
+ t.plan(2);
+
+ var xml = document.getElementById("capabilities").firstChild.nodeValue;
+ var doc = new OpenLayers.Format.XML().read(xml);
+ var obj = new OpenLayers.Format.WMTSCapabilities().read(doc);
+
+ var template = "http://www.example.com/{style}/{Time}/{style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png";
+ var layer = new OpenLayers.Layer.WMTS({
+ requestEncoding: "REST",
+ url: template,
+ layer: "GeoWebCache_USA_WMTS",
+ style: "foo",
+ matrixSet: "arcgis-online",
+ params: {Time: "2011"},
+ dimensions: ["Time"]
+ });
+
+ var map = new OpenLayers.Map("map", {
+ layers: [layer],
+ projection: "EPSG:4326",
+ maxResolution: 0.3515625,
+ maxExtent: new OpenLayers.Bounds(-180, -90, 180, 90),
+ zoomMethod: null
+ });
+ map.setCenter(new OpenLayers.LonLat(-97.0, 38.0), 1);
+ t.eq(layer.getURL(new OpenLayers.Bounds(-135.0, 0.0, -90.0, 45.0)),
+ "http://www.example.com/foo/2011/foo/arcgis-online/1/1/1.png", "getURL returns correct url");
+ map.zoomIn();
+ t.eq(layer.getURL(new OpenLayers.Bounds(-180.0, 0.0, -90.0, 90.0)),
+ "http://www.example.com/foo/2011/foo/arcgis-online/2/2/2.png", "getURL returns correct url");
+ map.destroy();
+ }
+
+ function test_destroy (t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ var layer1 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST"
+ });
+ map.addLayer(layer1);
+ layer1.destroy();
+ t.eq( layer1.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer1.tileSize, null, "layer.tileSize is null after destroy" );
+
+ //test with tile creation
+ var layer2 = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST"
+ });
+ map.addLayer(layer2);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ //grab a reference to one of the tiles
+ var tile = layer2.grid[0][0];
+
+ layer2.destroy();
+
+ t.ok( layer2.grid == null, "tiles appropriately destroyed");
+ map.destroy();
+ }
+
+ function test_getIdentifier(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map');
+ var layer, identifier;
+
+ layer = new OpenLayers.Layer.WMTS({
+ name: "Blue Marble WMTS",
+ url: "http://example.com/wmts/",
+ layer: "world",
+ style: "blue_marble",
+ matrixSet: "arcgis_online",
+ tileSize: new OpenLayers.Size(512, 512),
+ requestEncoding: "REST"
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ layer.zoomOffset = 2;
+ identifier = layer.getIdentifier();
+ t.eq(identifier, 7, '[zoomOffset] getIdentifier return value is correct');
+
+ layer.serverResolutions = ['offset', 1.40625, 0.703125, 0.3515625, 0.17578125,
+ 0.087890625, 0.0439453125];
+ identifier = layer.getIdentifier();
+ t.eq(identifier, 6, '[serverResolutions] getIdentifier return value is correct');
+
+ map.destroy();
+ }
+
+ </script>
+ </head>
+ <body>
+ <div id="map" style="width:1024px;height:512px;"></div>
+ <div id="capabilities"><!--
+<Capabilities xmlns="http://www.opengis.net/wmts/1.0"
+xmlns:ows="http://www.opengis.net/ows/1.1"
+xmlns:xlink="http://www.w3.org/1999/xlink"
+xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xmlns:gml="http://www.opengis.net/gml" xsi:schemaLocation="http://www.opengis.net/wmts/1.0 http://geowebcache.org/schema/opengis/wmts/1.0.0/wmtsGetCapabilities_response.xsd"
+version="1.0.0">
+<ows:ServiceIdentification>
+ <ows:Title>Web Map Tile Service - GeoWebCache</ows:Title>
+ <ows:ServiceType>OGC WMTS</ows:ServiceType>
+ <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
+</ows:ServiceIdentification>
+<ows:ServiceProvider>
+ <ows:ProviderName>http://example.com/geowebcache-1.2.2/service/wmts</ows:ProviderName>
+ <ows:ProviderSite xlink:href="http://example.com/geowebcache-1.2.2/service/wmts" />
+ <ows:ServiceContact>
+ <ows:IndividualName>GeoWebCache User</ows:IndividualName>
+ </ows:ServiceContact>
+</ows:ServiceProvider>
+<ows:OperationsMetadata>
+ <ows:Operation name="GetCapabilities">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://example.com/geowebcache-1.2.2/service/wmts?">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetTile">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://example.com/geowebcache-1.2.2/service/wmts?">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+ <ows:Operation name="GetFeatureInfo">
+ <ows:DCP>
+ <ows:HTTP>
+ <ows:Get xlink:href="http://example.com/geowebcache-1.2.2/service/wmts?">
+ <ows:Constraint name="GetEncoding">
+ <ows:AllowedValues>
+ <ows:Value>KVP</ows:Value>
+ </ows:AllowedValues>
+ </ows:Constraint>
+ </ows:Get>
+ </ows:HTTP>
+ </ows:DCP>
+ </ows:Operation>
+</ows:OperationsMetadata>
+<Contents>
+ <Layer>
+ <ows:Title>arcgis-online-wms</ows:Title>
+ <ows:Abstract>arcgis-online-wms</ows:Abstract>
+ <ows:WGS84BoundingBox>
+ <ows:LowerCorner>-180.0 -90.0</ows:LowerCorner>
+ <ows:UpperCorner>180.0 90.0</ows:UpperCorner>
+ </ows:WGS84BoundingBox>
+ <ows:Identifier>arcgis-online-wms</ows:Identifier>
+ <Style isDefault="true">
+ <ows:Identifier>_null</ows:Identifier>
+ </Style>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+ <TileMatrixSetLink> <TileMatrixSet>arcgis-online-wgs84</TileMatrixSet>
+ </TileMatrixSetLink> </Layer>
+ <TileMatrixSet>
+ <ows:Identifier>EPSG:4326</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:0</ows:Identifier>
+ <ScaleDenominator>2.795411320143589E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:1</ows:Identifier>
+ <ScaleDenominator>1.3977056600717944E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:2</ows:Identifier>
+ <ScaleDenominator>6.988528300358972E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8</MatrixWidth>
+ <MatrixHeight>4</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:3</ows:Identifier>
+ <ScaleDenominator>3.494264150179486E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16</MatrixWidth>
+ <MatrixHeight>8</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:4</ows:Identifier>
+ <ScaleDenominator>1.747132075089743E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32</MatrixWidth>
+ <MatrixHeight>16</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:5</ows:Identifier>
+ <ScaleDenominator>8735660.375448715</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>64</MatrixWidth>
+ <MatrixHeight>32</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:6</ows:Identifier>
+ <ScaleDenominator>4367830.1877243575</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>128</MatrixWidth>
+ <MatrixHeight>64</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:7</ows:Identifier>
+ <ScaleDenominator>2183915.0938621787</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>256</MatrixWidth>
+ <MatrixHeight>128</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:8</ows:Identifier>
+ <ScaleDenominator>1091957.5469310894</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>512</MatrixWidth>
+ <MatrixHeight>256</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:9</ows:Identifier>
+ <ScaleDenominator>545978.7734655447</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1024</MatrixWidth>
+ <MatrixHeight>512</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:10</ows:Identifier>
+ <ScaleDenominator>272989.38673277234</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2048</MatrixWidth>
+ <MatrixHeight>1024</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:11</ows:Identifier>
+ <ScaleDenominator>136494.69336638617</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4096</MatrixWidth>
+ <MatrixHeight>2048</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:12</ows:Identifier>
+ <ScaleDenominator>68247.34668319309</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8192</MatrixWidth>
+ <MatrixHeight>4096</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:13</ows:Identifier>
+ <ScaleDenominator>34123.67334159654</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16384</MatrixWidth>
+ <MatrixHeight>8192</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:14</ows:Identifier>
+ <ScaleDenominator>17061.83667079827</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32768</MatrixWidth>
+ <MatrixHeight>16384</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:15</ows:Identifier>
+ <ScaleDenominator>8530.918335399136</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>65536</MatrixWidth>
+ <MatrixHeight>32768</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:16</ows:Identifier>
+ <ScaleDenominator>4265.459167699568</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>131072</MatrixWidth>
+ <MatrixHeight>65536</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:17</ows:Identifier>
+ <ScaleDenominator>2132.729583849784</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>262144</MatrixWidth>
+ <MatrixHeight>131072</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:18</ows:Identifier>
+ <ScaleDenominator>1066.364791924892</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>524288</MatrixWidth>
+ <MatrixHeight>262144</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:19</ows:Identifier>
+ <ScaleDenominator>533.182395962446</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1048576</MatrixWidth>
+ <MatrixHeight>524288</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:20</ows:Identifier>
+ <ScaleDenominator>266.591197981223</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2097152</MatrixWidth>
+ <MatrixHeight>1048576</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:21</ows:Identifier>
+ <ScaleDenominator>133.2955989906115</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4194304</MatrixWidth>
+ <MatrixHeight>2097152</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:22</ows:Identifier>
+ <ScaleDenominator>66.64779949530575</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8388608</MatrixWidth>
+ <MatrixHeight>4194304</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:23</ows:Identifier>
+ <ScaleDenominator>33.323899747652874</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16777216</MatrixWidth>
+ <MatrixHeight>8388608</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:24</ows:Identifier>
+ <ScaleDenominator>16.661949873826437</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>33554432</MatrixWidth>
+ <MatrixHeight>16777216</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:25</ows:Identifier>
+ <ScaleDenominator>8.330974936913218</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>67108864</MatrixWidth>
+ <MatrixHeight>33554432</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:26</ows:Identifier>
+ <ScaleDenominator>4.165487468456609</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>134217728</MatrixWidth>
+ <MatrixHeight>67108864</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:27</ows:Identifier>
+ <ScaleDenominator>2.0827437342283046</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>268435456</MatrixWidth>
+ <MatrixHeight>134217728</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:28</ows:Identifier>
+ <ScaleDenominator>1.0413718671141523</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>536870912</MatrixWidth>
+ <MatrixHeight>268435456</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:29</ows:Identifier>
+ <ScaleDenominator>0.5206859335570762</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1073741824</MatrixWidth>
+ <MatrixHeight>536870912</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:4326:30</ows:Identifier>
+ <ScaleDenominator>0.2603429667785381</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2147483648</MatrixWidth>
+ <MatrixHeight>1073741824</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ <TileMatrixSet>
+ <ows:Identifier>arcgis-online-epsg102113</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::102113</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:0</ows:Identifier>
+ <ScaleDenominator>5.590822639285715E8</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:1</ows:Identifier>
+ <ScaleDenominator>2.7954113196428573E8</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:2</ows:Identifier>
+ <ScaleDenominator>1.3977056598214287E8</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4</MatrixWidth>
+ <MatrixHeight>4</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:3</ows:Identifier>
+ <ScaleDenominator>6.988528299107143E7</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8</MatrixWidth>
+ <MatrixHeight>8</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:4</ows:Identifier>
+ <ScaleDenominator>3.494264149553572E7</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16</MatrixWidth>
+ <MatrixHeight>16</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:5</ows:Identifier>
+ <ScaleDenominator>1.747132074776786E7</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32</MatrixWidth>
+ <MatrixHeight>32</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:6</ows:Identifier>
+ <ScaleDenominator>8735660.37388393</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>64</MatrixWidth>
+ <MatrixHeight>64</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:7</ows:Identifier>
+ <ScaleDenominator>4367830.186941965</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>128</MatrixWidth>
+ <MatrixHeight>128</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Abstract>The grid was not well-defined, the scale therefore assumes 1m per map unit.</ows:Abstract> <ows:Identifier>arcgis-online-epsg102113:8</ows:Identifier>
+ <ScaleDenominator>2183915.0934709823</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.00375083392E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>256</MatrixWidth>
+ <MatrixHeight>256</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ <TileMatrixSet>
+ <ows:Identifier>GlobalCRS84Scale</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:0</ows:Identifier>
+ <ScaleDenominator>5.0000000000000006E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:1</ows:Identifier>
+ <ScaleDenominator>2.5000000000000003E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>3</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:2</ows:Identifier>
+ <ScaleDenominator>1.0000000000000001E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>6</MatrixWidth>
+ <MatrixHeight>3</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:3</ows:Identifier>
+ <ScaleDenominator>5.000000000000001E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>12</MatrixWidth>
+ <MatrixHeight>6</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:4</ows:Identifier>
+ <ScaleDenominator>2.5000000000000004E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>23</MatrixWidth>
+ <MatrixHeight>12</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:5</ows:Identifier>
+ <ScaleDenominator>1.0E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>56</MatrixWidth>
+ <MatrixHeight>28</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:6</ows:Identifier>
+ <ScaleDenominator>5000000.0</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>112</MatrixWidth>
+ <MatrixHeight>56</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:7</ows:Identifier>
+ <ScaleDenominator>2500000.0</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>224</MatrixWidth>
+ <MatrixHeight>112</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:8</ows:Identifier>
+ <ScaleDenominator>1000000.0000000001</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>560</MatrixWidth>
+ <MatrixHeight>280</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:9</ows:Identifier>
+ <ScaleDenominator>500000.00000000006</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1119</MatrixWidth>
+ <MatrixHeight>560</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:10</ows:Identifier>
+ <ScaleDenominator>250000.00000000003</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2237</MatrixWidth>
+ <MatrixHeight>1119</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:11</ows:Identifier>
+ <ScaleDenominator>100000.00000000001</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>5591</MatrixWidth>
+ <MatrixHeight>2796</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:12</ows:Identifier>
+ <ScaleDenominator>50000.00000000001</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>11182</MatrixWidth>
+ <MatrixHeight>5591</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:13</ows:Identifier>
+ <ScaleDenominator>25000.000000000004</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>22364</MatrixWidth>
+ <MatrixHeight>11182</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:14</ows:Identifier>
+ <ScaleDenominator>10000.000000000002</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>55909</MatrixWidth>
+ <MatrixHeight>27955</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:15</ows:Identifier>
+ <ScaleDenominator>5000.000000000001</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>111817</MatrixWidth>
+ <MatrixHeight>55909</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:16</ows:Identifier>
+ <ScaleDenominator>2500.0000000000005</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>223633</MatrixWidth>
+ <MatrixHeight>111817</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:17</ows:Identifier>
+ <ScaleDenominator>1000.0000000000002</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>559083</MatrixWidth>
+ <MatrixHeight>279542</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:18</ows:Identifier>
+ <ScaleDenominator>500.0000000000001</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1118165</MatrixWidth>
+ <MatrixHeight>559083</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:19</ows:Identifier>
+ <ScaleDenominator>250.00000000000006</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2236330</MatrixWidth>
+ <MatrixHeight>1118165</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Scale:20</ows:Identifier>
+ <ScaleDenominator>100.00000000000003</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>5590823</MatrixWidth>
+ <MatrixHeight>2795412</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ <TileMatrixSet>
+ <ows:Identifier>EPSG:900913</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::900913</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:0</ows:Identifier>
+ <ScaleDenominator>5.590822639508929E8</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:1</ows:Identifier>
+ <ScaleDenominator>2.7954113197544646E8</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:2</ows:Identifier>
+ <ScaleDenominator>1.3977056598772323E8</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4</MatrixWidth>
+ <MatrixHeight>4</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:3</ows:Identifier>
+ <ScaleDenominator>6.988528299386162E7</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8</MatrixWidth>
+ <MatrixHeight>8</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:4</ows:Identifier>
+ <ScaleDenominator>3.494264149693081E7</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16</MatrixWidth>
+ <MatrixHeight>16</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:5</ows:Identifier>
+ <ScaleDenominator>1.7471320748465404E7</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32</MatrixWidth>
+ <MatrixHeight>32</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:6</ows:Identifier>
+ <ScaleDenominator>8735660.374232702</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>64</MatrixWidth>
+ <MatrixHeight>64</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:7</ows:Identifier>
+ <ScaleDenominator>4367830.187116351</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>128</MatrixWidth>
+ <MatrixHeight>128</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:8</ows:Identifier>
+ <ScaleDenominator>2183915.0935581755</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>256</MatrixWidth>
+ <MatrixHeight>256</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:9</ows:Identifier>
+ <ScaleDenominator>1091957.5467790877</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>512</MatrixWidth>
+ <MatrixHeight>512</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:10</ows:Identifier>
+ <ScaleDenominator>545978.7733895439</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1024</MatrixWidth>
+ <MatrixHeight>1024</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:11</ows:Identifier>
+ <ScaleDenominator>272989.38669477194</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2048</MatrixWidth>
+ <MatrixHeight>2048</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:12</ows:Identifier>
+ <ScaleDenominator>136494.69334738597</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4096</MatrixWidth>
+ <MatrixHeight>4096</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:13</ows:Identifier>
+ <ScaleDenominator>68247.34667369298</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8192</MatrixWidth>
+ <MatrixHeight>8192</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:14</ows:Identifier>
+ <ScaleDenominator>34123.67333684649</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16384</MatrixWidth>
+ <MatrixHeight>16384</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:15</ows:Identifier>
+ <ScaleDenominator>17061.836668423246</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32768</MatrixWidth>
+ <MatrixHeight>32768</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:16</ows:Identifier>
+ <ScaleDenominator>8530.918334211623</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>65536</MatrixWidth>
+ <MatrixHeight>65536</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:17</ows:Identifier>
+ <ScaleDenominator>4265.4591671058115</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>131072</MatrixWidth>
+ <MatrixHeight>131072</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:18</ows:Identifier>
+ <ScaleDenominator>2132.7295835529058</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>262144</MatrixWidth>
+ <MatrixHeight>262144</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:19</ows:Identifier>
+ <ScaleDenominator>1066.3647917764529</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>524288</MatrixWidth>
+ <MatrixHeight>524288</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:20</ows:Identifier>
+ <ScaleDenominator>533.1823958882264</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1048576</MatrixWidth>
+ <MatrixHeight>1048576</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:21</ows:Identifier>
+ <ScaleDenominator>266.5911979441132</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2097152</MatrixWidth>
+ <MatrixHeight>2097152</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:22</ows:Identifier>
+ <ScaleDenominator>133.2955989720566</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4194304</MatrixWidth>
+ <MatrixHeight>4194304</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:23</ows:Identifier>
+ <ScaleDenominator>66.6477994860283</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8388608</MatrixWidth>
+ <MatrixHeight>8388608</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:24</ows:Identifier>
+ <ScaleDenominator>33.32389974301415</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16777216</MatrixWidth>
+ <MatrixHeight>16777216</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:25</ows:Identifier>
+ <ScaleDenominator>16.661949871507076</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>33554432</MatrixWidth>
+ <MatrixHeight>33554432</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:26</ows:Identifier>
+ <ScaleDenominator>8.330974935753538</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>67108864</MatrixWidth>
+ <MatrixHeight>67108864</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:27</ows:Identifier>
+ <ScaleDenominator>4.165487467876769</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>134217728</MatrixWidth>
+ <MatrixHeight>134217728</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:28</ows:Identifier>
+ <ScaleDenominator>2.0827437339383845</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>268435456</MatrixWidth>
+ <MatrixHeight>268435456</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:29</ows:Identifier>
+ <ScaleDenominator>1.0413718669691923</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>536870912</MatrixWidth>
+ <MatrixHeight>536870912</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>EPSG:900913:30</ows:Identifier>
+ <ScaleDenominator>0.5206859334845961</ScaleDenominator>
+ <TopLeftCorner>2.0037508E7 -2.003750834E7</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1073741824</MatrixWidth>
+ <MatrixHeight>1073741824</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ <TileMatrixSet>
+ <ows:Identifier>arcgis-online-wgs84</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:0</ows:Identifier>
+ <ScaleDenominator>1.3977056600717944E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>4</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:1</ows:Identifier>
+ <ScaleDenominator>6.988528300358972E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>8</MatrixWidth>
+ <MatrixHeight>4</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:2</ows:Identifier>
+ <ScaleDenominator>3.494264150179486E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16</MatrixWidth>
+ <MatrixHeight>8</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:3</ows:Identifier>
+ <ScaleDenominator>1.747132075089743E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>32</MatrixWidth>
+ <MatrixHeight>16</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:4</ows:Identifier>
+ <ScaleDenominator>8735660.375448715</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>64</MatrixWidth>
+ <MatrixHeight>32</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:5</ows:Identifier>
+ <ScaleDenominator>4367830.1877243575</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>128</MatrixWidth>
+ <MatrixHeight>64</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>arcgis-online-wgs84:6</ows:Identifier>
+ <ScaleDenominator>2183915.0938621787</ScaleDenominator>
+ <TopLeftCorner>85 -175</TopLeftCorner>
+ <TileWidth>512</TileWidth>
+ <TileHeight>512</TileHeight>
+ <MatrixWidth>256</MatrixWidth>
+ <MatrixHeight>128</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+ <TileMatrixSet>
+ <ows:Identifier>GlobalCRS84Pixel</ows:Identifier>
+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::4326</ows:SupportedCRS>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:0</ows:Identifier>
+ <ScaleDenominator>7.951392199519542E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:1</ows:Identifier>
+ <ScaleDenominator>3.975696099759771E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>2</MatrixWidth>
+ <MatrixHeight>1</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:2</ows:Identifier>
+ <ScaleDenominator>1.9878480498798856E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>3</MatrixWidth>
+ <MatrixHeight>2</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:3</ows:Identifier>
+ <ScaleDenominator>1.325232033253257E8</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>5</MatrixWidth>
+ <MatrixHeight>3</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:4</ows:Identifier>
+ <ScaleDenominator>6.626160166266285E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>9</MatrixWidth>
+ <MatrixHeight>5</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:5</ows:Identifier>
+ <ScaleDenominator>3.3130800831331424E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>17</MatrixWidth>
+ <MatrixHeight>9</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:6</ows:Identifier>
+ <ScaleDenominator>1.325232033253257E7</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>43</MatrixWidth>
+ <MatrixHeight>22</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:7</ows:Identifier>
+ <ScaleDenominator>6626160.166266285</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>85</MatrixWidth>
+ <MatrixHeight>43</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:8</ows:Identifier>
+ <ScaleDenominator>3313080.0831331424</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>169</MatrixWidth>
+ <MatrixHeight>85</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:9</ows:Identifier>
+ <ScaleDenominator>1656540.0415665712</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>338</MatrixWidth>
+ <MatrixHeight>169</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:10</ows:Identifier>
+ <ScaleDenominator>552180.0138555238</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1013</MatrixWidth>
+ <MatrixHeight>507</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:11</ows:Identifier>
+ <ScaleDenominator>331308.00831331423</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>1688</MatrixWidth>
+ <MatrixHeight>844</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:12</ows:Identifier>
+ <ScaleDenominator>110436.00277110476</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>5063</MatrixWidth>
+ <MatrixHeight>2532</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:13</ows:Identifier>
+ <ScaleDenominator>55218.00138555238</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>10125</MatrixWidth>
+ <MatrixHeight>5063</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:14</ows:Identifier>
+ <ScaleDenominator>33130.80083133143</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>16875</MatrixWidth>
+ <MatrixHeight>8438</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:15</ows:Identifier>
+ <ScaleDenominator>11043.600277110474</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>50625</MatrixWidth>
+ <MatrixHeight>25313</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:16</ows:Identifier>
+ <ScaleDenominator>3313.080083133142</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>168750</MatrixWidth>
+ <MatrixHeight>84375</MatrixHeight>
+ </TileMatrix>
+ <TileMatrix>
+ <ows:Identifier>GlobalCRS84Pixel:17</ows:Identifier>
+ <ScaleDenominator>1104.3600277110472</ScaleDenominator>
+ <TopLeftCorner>90.0 -180.0</TopLeftCorner>
+ <TileWidth>256</TileWidth>
+ <TileHeight>256</TileHeight>
+ <MatrixWidth>506250</MatrixWidth>
+ <MatrixHeight>253125</MatrixHeight>
+ </TileMatrix>
+ </TileMatrixSet>
+</Contents>
+<ServiceMetadataURL xlink:href="http://example.com/geowebcache-1.2.2/service/wmts?REQUEST=getcapabilities&amp;VERSION=1.0.0"/>
+</Capabilities>
+ -->
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Layer/WrapDateLine.html b/misc/openlayers/tests/Layer/WrapDateLine.html
new file mode 100644
index 0000000..efe8903
--- /dev/null
+++ b/misc/openlayers/tests/Layer/WrapDateLine.html
@@ -0,0 +1,188 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ // turn off animation frame handling, so we can check img urls in tests
+ delete OpenLayers.Layer.Grid.prototype.queueTileDraw;
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/png'};
+
+
+ function test_Layer_WrapDateLine_adjustBounds(t) {
+ t.plan(10);
+
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {'wrapDateLine':true});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ var bounds = layer.adjustBounds(new OpenLayers.Bounds(-270,-90,-180,0));
+ t.ok( bounds.equals(new OpenLayers.Bounds(90,-90,180,0)), "-270,-90,-180,0 wraps to 90,-90,180,0");
+ bounds = layer.adjustBounds(new OpenLayers.Bounds(180,-90,270,0));
+ t.ok( bounds.equals(new OpenLayers.Bounds(-180,-90,-90,0)), "180,-90,270,0 wraps to -180,-90,-90,0");
+ bounds = layer.adjustBounds(new OpenLayers.Bounds(-180,-90,0,0));
+ t.ok( bounds.equals(new OpenLayers.Bounds(-180,-90,0,0)), "-180,-90,0,0 doesn't wrap");
+ bounds = layer.adjustBounds(new OpenLayers.Bounds(-181,-90,-179,0));
+ t.ok( bounds.equals(new OpenLayers.Bounds(-181,-90,-179,0)), "-181,-90,-179,0 doesn't wrap, because it straddles the dateline");
+ bounds = layer.adjustBounds(new OpenLayers.Bounds(-180,-180,-90,-90));
+ t.ok( bounds.equals(new OpenLayers.Bounds(-180,-180,-90,-90)), "-180,-180,-90,-90 doesn't wrap, because we don't wrap lats.");
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+ var testBounds = null;
+ var outBounds = null;
+ var testList = [
+ new OpenLayers.Bounds(-270,-90,-180,0),
+ new OpenLayers.Bounds(180,-90,270,0),
+ new OpenLayers.Bounds(-180,-90,0,0),
+ new OpenLayers.Bounds(-181,-90,-179,0),
+ new OpenLayers.Bounds(-180,-180,-90,-90)
+ ];
+ for (var i = 0; i < testList.length; i++) {
+ outBounds = layer.adjustBounds(testList[i]);
+ t.ok( outBounds.equals(testList[i]), testList[i]+" doesn't wrap in non-wrapping layer.");
+ }
+ map.destroy();
+ }
+ function test_Layer_WrapDateLine_getLonLat(t) {
+ t.plan(12);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, url, params, {'wrapDateLine':true});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ var testLonLats = [
+ new OpenLayers.LonLat(-185,5),
+ new OpenLayers.LonLat(-180,-95),
+ new OpenLayers.LonLat(-180,95),
+ new OpenLayers.LonLat(180,-95),
+ new OpenLayers.LonLat(180,95),
+ new OpenLayers.LonLat(185,5)
+ ];
+ var outLonLats = [
+ new OpenLayers.LonLat(175,5),
+ new OpenLayers.LonLat(-180,-95),
+ new OpenLayers.LonLat(-180,95),
+ new OpenLayers.LonLat(180,-95),
+ new OpenLayers.LonLat(180,95),
+ new OpenLayers.LonLat(-175,5)
+ ];
+
+ for (var i = 0; i < testLonLats.length; i++) {
+ var pixel = layer.getViewPortPxFromLonLat(testLonLats[i]);
+ var lonlat = layer.getLonLatFromViewPortPx(pixel);
+ lonlat.lon = Math.round(lonlat.lon);
+ lonlat.lat = Math.round(lonlat.lat);
+ t.ok(outLonLats[i].equals(lonlat), testLonLats[i] + " wraps to " + outLonLats[i]+ " (what happened: " + lonlat + ")");
+ }
+
+ layer = new OpenLayers.Layer.WMS(name, url, params);
+ map.addLayer(layer);
+ var outLonLats = [
+ new OpenLayers.LonLat(-185,5),
+ new OpenLayers.LonLat(-180,-95),
+ new OpenLayers.LonLat(-180,95),
+ new OpenLayers.LonLat(180,-95),
+ new OpenLayers.LonLat(180,95),
+ new OpenLayers.LonLat(185,5)
+ ];
+ for (var i = 0; i < testLonLats.length; i++) {
+ var pixel = layer.getViewPortPxFromLonLat(testLonLats[i]);
+ var lonlat = layer.getLonLatFromViewPortPx(pixel);
+ lonlat.lon = Math.round(lonlat.lon);
+ lonlat.lat = Math.round(lonlat.lat);
+ t.ok(outLonLats[i].equals(lonlat), testLonLats[i] + " wraps to " + outLonLats[i]+ " (what happened: " + lonlat + ")");
+ }
+ map.destroy();
+
+ }
+ function test_Layer_WrapDateLine_ZoomToExtent (t) {
+ t.plan( 4 );
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params, {'wrapDateLine':true});
+ var m = new OpenLayers.Map('map');
+ m.addLayer(layer);
+ m.setCenter = function(myCenter) { this.center = myCenter; }
+ var testBounds = [
+ new OpenLayers.Bounds(-185,-90,-175,-85),
+ new OpenLayers.Bounds(0,-90,-170,-85),
+ new OpenLayers.Bounds(-270,-90,-180,-85),
+ new OpenLayers.Bounds(0,0,45,45)
+ ];
+ var outCenters = [
+ new OpenLayers.LonLat(-180,-87.5),
+ new OpenLayers.LonLat(95,-87.5),
+ new OpenLayers.LonLat(135,-87.5),
+ new OpenLayers.LonLat(22.5,22.5)
+ ];
+ for (var i = 0; i < testBounds.length; i++) {
+ m.zoomToExtent(testBounds[i]);
+ t.ok(m.center.equals(outCenters[i]), "Map center from bounds " + testBounds[i] + " should be " + outCenters[i] + ", got " + m.center);
+ }
+ m.destroy();
+
+
+ }
+ function test_Layer_WrapDateLine_WMS (t) {
+ t.plan( 4 );
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url, params, {'wrapDateLine':true,encodeBBOX:true, buffer: 2});
+ var m = new OpenLayers.Map('map', {tileManager: null, adjustZoom: function(z) {return z;}});
+ m.addLayer(layer);
+ m.zoomToMaxExtent();
+ t.eq(layer.grid[3][0].url, "http://octo.metacarta.com/cgi-bin/mapserv?MAP=%2Fmapdata%2Fvmap_wms.map&LAYERS=basic&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=0%2C-90%2C180%2C90&WIDTH=256&HEIGHT=256", "cell [3][0] is wrapped around the world.");
+ t.eq(layer.grid[3][1].url, "http://octo.metacarta.com/cgi-bin/mapserv?MAP=%2Fmapdata%2Fvmap_wms.map&LAYERS=basic&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=-180%2C-90%2C0%2C90&WIDTH=256&HEIGHT=256", "cell [3][1] is wrapped around the world.");
+ t.eq(layer.grid[3][2].url, "http://octo.metacarta.com/cgi-bin/mapserv?MAP=%2Fmapdata%2Fvmap_wms.map&LAYERS=basic&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=0%2C-90%2C180%2C90&WIDTH=256&HEIGHT=256", "cell [3][2] is not wrapped at all.");
+ t.ok(layer.grid[0][2].url == null, "no latitudinal wrapping - tile not loaded if outside maxExtent");
+ m.destroy();
+
+ }
+ function test_Layer_WrapDateLine_KaMap (t) {
+ t.plan( 4 );
+
+ var layer = new OpenLayers.Layer.KaMap( "Blue Marble NG",
+ "http://www.openlayers.org/world/index.php",
+ {g: "satellite", map: "world"},
+ {wrapDateLine: true, buffer: 2} );
+ var m = new OpenLayers.Map('map', {tileManager: null, adjustZoom: function(z) {return z;}});
+ m.addLayer(layer);
+ m.zoomToMaxExtent();
+ t.eq(layer.grid[4][7].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=-256&s=221471921.25", "grid[5][7] kamap is okay");
+ t.eq(layer.grid[4][6].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=0&s=221471921.25", "grid[5][6] kamap is okay");
+ t.eq(layer.grid[4][5].url, "http://www.openlayers.org/world/index.php?g=satellite&map=world&i=jpeg&t=0&l=-256&s=221471921.25", "grid[5][5] is okay");
+ t.ok(layer.grid[7][6].url == null, "no latitudinal wrapping - tile not loaded if outside maxExtent");
+ m.destroy();
+ }
+ function test_Layer_WrapDateLine_WMS_Overlay (t) {
+ t.plan( 4 );
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ baselayer = new OpenLayers.Layer.WMS(name, url, params, {'wrapDateLine':true, buffer: 2});
+ var layer = new OpenLayers.Layer.WMS( "DM Solutions Demo",
+ "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap",
+ {layers: "bathymetry,land_fn,park,drain_fn,drainage," +
+ "prov_bound,fedlimit,rail,road,popplace",
+ transparent: "true", format: "image/png"},
+ {wrapDateLine: true, encodeBBOX:true, buffer:2});
+ var m = new OpenLayers.Map('map', {tileManager: null, adjustZoom: function(z) {return z;}});
+ m.addLayers([baselayer,layer]);
+ m.zoomToMaxExtent();
+ t.eq(layer.grid[3][0].url, "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?LAYERS=bathymetry%2Cland_fn%2Cpark%2Cdrain_fn%2Cdrainage%2Cprov_bound%2Cfedlimit%2Crail%2Croad%2Cpopplace&TRANSPARENT=true&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=0%2C-90%2C180%2C90&WIDTH=256&HEIGHT=256", "grid[0][0] wms overlay is okay");
+ t.eq(layer.grid[3][1].url, "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?LAYERS=bathymetry%2Cland_fn%2Cpark%2Cdrain_fn%2Cdrainage%2Cprov_bound%2Cfedlimit%2Crail%2Croad%2Cpopplace&TRANSPARENT=true&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=-180%2C-90%2C0%2C90&WIDTH=256&HEIGHT=256", "grid[0][3] wms overlay is okay");
+ t.eq(layer.grid[3][2].url, "http://www2.dmsolutions.ca/cgi-bin/mswms_gmap?LAYERS=bathymetry%2Cland_fn%2Cpark%2Cdrain_fn%2Cdrainage%2Cprov_bound%2Cfedlimit%2Crail%2Croad%2Cpopplace&TRANSPARENT=true&FORMAT=image%2Fpng&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG%3A4326&BBOX=0%2C-90%2C180%2C90&WIDTH=256&HEIGHT=256", "grid[3][0] wms overlay okay");
+ t.ok(layer.grid[0][2].url == null, "no latitudinal wrapping - tile not loaded if outside maxExtent");
+ m.destroy();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:1000px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/XYZ.html b/misc/openlayers/tests/Layer/XYZ.html
new file mode 100644
index 0000000..bd6d26e
--- /dev/null
+++ b/misc/openlayers/tests/Layer/XYZ.html
@@ -0,0 +1,266 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/${z}/${x}/${y}.png";
+ var options = {'layername':'basic', 'type':'png'};
+
+
+ function test_Layer_XYZ_constructor (t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ t.ok( layer instanceof OpenLayers.Layer.XYZ, "returns OpenLayers.Layer.XYZ object" );
+ }
+
+
+
+ function test_Layer_XYZ_clearTiles (t) {
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0));
+
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.clearGrid();
+
+ t.ok( layer.grid != null, "layer.grid does not get nullified" );
+ map.destroy();
+ }
+
+
+ function test_Layer_XYZ_getXYZBounds(t) {
+ t.plan( 1 );
+
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+
+ var bl = { bounds: new OpenLayers.Bounds(1,2,2,3)};
+ var tr = { bounds: new OpenLayers.Bounds(2,3,3,4)};
+ layer.grid = [ [6, tr],
+ [bl, 7]];
+
+ var bounds = layer.getTilesBounds();
+
+ var testBounds = new OpenLayers.Bounds(1,2,3,4);
+
+ t.ok( bounds.equals(testBounds), "getXYZBounds() returns correct bounds")
+
+ layer.grid = null;
+ }
+
+ function test_Layer_XYZ_getResolution(t) {
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ map.addLayer(layer);
+
+ map.zoom = 5;
+
+ t.eq( layer.getResolution(), 0.0439453125, "getResolution() returns correct value");
+ map.destroy();
+ }
+
+ function test_Layer_XYZ_getZoomForExtent(t) {
+ t.plan( 2 );
+ var bounds, zoom;
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ map.addLayer(layer);
+
+ bounds = new OpenLayers.Bounds(10,10,12,12);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 8, "getZoomForExtent() returns correct value");
+
+ bounds = new OpenLayers.Bounds(10,10,100,100);
+ zoom = layer.getZoomForExtent(bounds);
+
+ t.eq( zoom, 2, "getZoomForExtent() returns correct value");
+ map.destroy();
+ }
+
+
+ /** THIS WOULD BE WHERE THE TESTS WOULD GO FOR
+ *
+ * -moveTo
+ * -insertColumn
+ * -insertRow
+
+ function 07_Layer_XYZ_moveTo(t) {
+ }
+
+ function 08_Layer_XYZ_insertColumn(t) {
+ }
+
+ function 09_Layer_XYZ_insertRow(t) {
+ }
+
+ *
+ */
+ function test_Layer_XYZ_getURL(t) {
+
+ t.plan(6);
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.XYZ(name, url);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 9);
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/261/63.png", "Tile URL is correct");
+
+ layer.url = ["http://tilecache1/", "http://tilecache2/", "http://tilecache3/"];
+ tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://tilecache1/", "Tile URL is deterministic");
+
+ layer.url = url;
+ tileurl = layer.getURL(new OpenLayers.Bounds(180.515625,45,181.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/513/63.png", "Tile URL is correct");
+ tileurl = layer.getURL(new OpenLayers.Bounds(-181.515625,45,-180.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/-2/63.png", "Tile URL is correct");
+ layer.wrapDateLine = true;
+ tileurl = layer.getURL(new OpenLayers.Bounds(180.515625,45,181.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/1/63.png", "Tile URL is correct");
+ tileurl = layer.getURL(new OpenLayers.Bounds(-181.515625,45,-180.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/510/63.png", "Tile URL is correct");
+ map.destroy();
+ }
+ function test_Layer_XYZ_Rounding(t) {
+ t.plan(1);
+ m = new OpenLayers.Map("map", {'maxExtent':new OpenLayers.Bounds(-122.6579,37.4901,-122.0738,37.8795)});
+ layer = new OpenLayers.Layer.XYZ( "XYZ",
+ url, {layername: 'basic', type:'png', resolutions:[0.000634956337608418], buffer: 2} );
+ m.addLayer(layer);
+ m.zoomToMaxExtent()
+ t.eq(layer.getURL(layer.grid[3][3].bounds), "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/0/1/0.png", "XYZ tiles around rounded properly.");
+ m.destroy();
+ }
+
+ function test_Layer_XYZ_setMap(t) {
+
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.XYZ(name, url);
+
+ t.eq(layer.tileOrigin, null, "Tile origin starts out null");
+ layer.setMap(map);
+
+ t.eq(layer.tileOrigin.lat, -90, "lat is -90");
+ t.eq(layer.tileOrigin.lon, -180, "lon is -180");
+ map.destroy();
+ }
+
+ function test_Layer_XYZ_serverResolutions(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map', {
+ resolutions: [13,11]
+ });
+
+ var layer = new OpenLayers.Layer.XYZ(name, url, options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 1);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(0,0,0,0));
+ var level = parseInt(tileurl.split('/')[7]);
+ t.eq(map.getZoom(), level, "Tile zoom level is correct without serverResolutions");
+
+ layer.serverResolutions = [14,13,12,11,10];
+ tileurl = layer.getURL(new OpenLayers.Bounds(0,0,0,0));
+ level = parseInt(tileurl.split('/')[7]);
+ var res = map.getResolution();
+ var gotLevel = OpenLayers.Util.indexOf(layer.serverResolutions, res);
+ t.eq(gotLevel, level, "Tile zoom level is correct with serverResolutions");
+
+ map.destroy();
+ }
+
+ function test_zoomOffset(t) {
+
+ t.plan(2);
+
+ var offset;
+
+ // test offset of 2
+ offset = 2;
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ maxResolution: 1.40625 / Math.pow(2, offset)
+ });
+ var layer = new OpenLayers.Layer.XYZ(name, url, {zoomOffset: offset});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 7);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/261/63.png", "correct URL for offset of 2");
+
+ map.destroy();
+
+ // test offset of -1
+ offset = -1;
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ maxResolution: 1.40625 / Math.pow(2, offset)
+ });
+ var layer = new OpenLayers.Layer.XYZ(name, url, {zoomOffset: offset});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 10);
+
+ var tileurl = layer.getURL(new OpenLayers.Bounds(3.515625,45,4.21875,45.703125));
+ t.eq(tileurl, "http://labs.metacarta.com/wms-c/Basic.py/1.0.0/basic/9/261/63.png", "correct URL for offset of -1");
+
+ map.destroy();
+
+
+ }
+
+ function test_Layer_XYZ_destroy (t) {
+
+ t.plan( 3 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ map.addLayer(layer);
+ layer.destroy();
+ t.eq( layer.grid, null, "layer.grid is null after destroy" );
+ t.eq( layer.tileSize, null, "layer.tileSize is null after destroy" );
+
+
+ //test with tile creation
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ //grab a reference to one of the tiles
+ var tile = layer.grid[0][0];
+
+ layer.destroy();
+
+ t.ok( layer.grid == null, "tiles appropriately destroyed");
+ map.destroy();
+ }
+
+ function test_clone(t) {
+ t.plan(1);
+
+ layer = new OpenLayers.Layer.XYZ(name, url, options);
+ var clone = layer.clone();
+ t.ok(clone instanceof OpenLayers.Layer.XYZ, "clone is a Layer.XYZ instance");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Layer/atom-1.0.xml b/misc/openlayers/tests/Layer/atom-1.0.xml
new file mode 100644
index 0000000..f0d5d6f
--- /dev/null
+++ b/misc/openlayers/tests/Layer/atom-1.0.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"
+ xmlns:georss="http://www.georss.org/georss">
+
+ <title>tumulus</title>
+ <link rel="self"
+ href="http://pleiades.stoa.org/places/tumulus"/>
+ <updated/>
+ <author/>
+ <id>http://pleiades.stoa.org/places/tumulus</id>
+
+ <entry>
+ <title>Unnamed Tumulus</title>
+ <link rel="alternate"
+ href="http://pleiades.stoa.org/places/638896"
+ />
+ <id>http://pleiades.stoa.org/places/638896</id>
+ <updated/>
+ <summary>An ancient tumulus, attested during the Classical period (modern location: Karaburun). Its ancient name is not known.</summary>
+ <georss:point>36.7702 29.9805</georss:point>
+ </entry>
+ <entry>
+ <title>Unnamed Tumulus</title>
+ <link rel="alternate"
+ href="http://pleiades.stoa.org/places/638924"
+ />
+ <id>http://pleiades.stoa.org/places/638924</id>
+ <updated/>
+ <summary>An ancient tumulus, attested during the Classical period (modern location: Kızılbel). Its ancient name is not known.</summary>
+ <georss:point>36.7263 29.8619</georss:point>
+ </entry>
+
+</feed>
+
diff --git a/misc/openlayers/tests/Layer/data_Layer_Text_textfile.txt b/misc/openlayers/tests/Layer/data_Layer_Text_textfile.txt
new file mode 100644
index 0000000..8250988
--- /dev/null
+++ b/misc/openlayers/tests/Layer/data_Layer_Text_textfile.txt
@@ -0,0 +1,3 @@
+point image
+10,20 http://boston.openguides.org/markers/ORANGE.png
+15,25 http://boston.openguides.org/markers/ORANGE.png
diff --git a/misc/openlayers/tests/Layer/data_Layer_Text_textfile_2.txt b/misc/openlayers/tests/Layer/data_Layer_Text_textfile_2.txt
new file mode 100644
index 0000000..91a8093
--- /dev/null
+++ b/misc/openlayers/tests/Layer/data_Layer_Text_textfile_2.txt
@@ -0,0 +1,3 @@
+point title description image
+10,20 a b http://boston.openguides.org/markers/ORANGE.png
+15,25 c d http://boston.openguides.org/markers/ORANGE.png
diff --git a/misc/openlayers/tests/Layer/data_Layer_Text_textfile_overflow.txt b/misc/openlayers/tests/Layer/data_Layer_Text_textfile_overflow.txt
new file mode 100644
index 0000000..bb4768e
--- /dev/null
+++ b/misc/openlayers/tests/Layer/data_Layer_Text_textfile_overflow.txt
@@ -0,0 +1,3 @@
+overflow point title description image
+auto 10,20 a b http://boston.openguides.org/markers/ORANGE.png
+hidden 15,25 c d http://boston.openguides.org/markers/ORANGE.png
diff --git a/misc/openlayers/tests/Layer/georss.txt b/misc/openlayers/tests/Layer/georss.txt
new file mode 100644
index 0000000..053749b
--- /dev/null
+++ b/misc/openlayers/tests/Layer/georss.txt
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/css" href="/css/rss.css" ?>
+
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://purl.org/rss/1.0/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:georss="http://www.georss.org/georss">
+<docs>This is an RSS file. Copy the URL into your aggregator of choice. If you don't know what this means and want to learn more, please see: <span>http://platial.typepad.com/news/2006/04/really_simple_t.html</span> for more info.</docs><channel rdf:about="http://platial.com">
+<link>http://platial.com</link>
+<title>Crschmidt's Places At Platial</title>
+<description></description>
+<items>
+<rdf:Seq>
+<rdf:li resource="http://platial.com/place/90306"/>
+<rdf:li resource="http://platial.com/place/67230"/>
+<rdf:li resource="http://platial.com/place/65645"/>
+<rdf:li resource="http://platial.com/place/62200"/>
+<rdf:li resource="http://platial.com/place/28232"/>
+<rdf:li resource="http://platial.com/place/43666"/>
+<rdf:li resource="http://platial.com/place/28394"/>
+<rdf:li resource="http://platial.com/place/28251"/>
+<rdf:li resource="http://platial.com/place/28392"/>
+<rdf:li resource="http://platial.com/place/28391"/>
+<rdf:li resource="http://platial.com/place/28231"/>
+<rdf:li resource="http://platial.com/place/28393"/>
+<rdf:li resource="http://platial.com/place/31685"/>
+<rdf:li resource="http://platial.com/place/28596"/>
+<rdf:li resource="http://platial.com/place/28595"/>
+<rdf:li resource="http://platial.com/place/28594"/>
+<rdf:li resource="http://platial.com/place/28593"/>
+<rdf:li resource="http://platial.com/place/28592"/>
+<rdf:li resource="http://platial.com/place/28591"/>
+<rdf:li resource="http://platial.com/place/28590"/>
+<rdf:li resource="http://platial.com/place/28589"/>
+<rdf:li resource="http://platial.com/place/28588"/>
+<rdf:li resource="http://platial.com/place/28587"/>
+<rdf:li resource="http://platial.com/place/28586"/>
+<rdf:li resource="http://platial.com/place/28585"/>
+<rdf:li resource="http://platial.com/place/28584"/>
+<rdf:li resource="http://platial.com/place/28583"/>
+<rdf:li resource="http://platial.com/place/28582"/>
+<rdf:li resource="http://platial.com/place/28581"/>
+<rdf:li resource="http://platial.com/place/28580"/>
+<rdf:li resource="http://platial.com/place/28579"/>
+<rdf:li resource="http://platial.com/place/28578"/>
+<rdf:li resource="http://platial.com/place/28577"/>
+<rdf:li resource="http://platial.com/place/28576"/>
+<rdf:li resource="http://platial.com/place/28575"/>
+<rdf:li resource="http://platial.com/place/28574"/>
+<rdf:li resource="http://platial.com/place/28573"/>
+<rdf:li resource="http://platial.com/place/28572"/>
+<rdf:li resource="http://platial.com/place/28571"/>
+<rdf:li resource="http://platial.com/place/28570"/>
+</rdf:Seq>
+</items>
+</channel>
+<item rdf:about="http://platial.com/place/90306">
+<link>http://platial.com/place/90306</link>
+<title>Knitting Room</title>
+<description><![CDATA[This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/90306">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/90306">Grab this on Platial</a> ]]></description>
+<georss:point>42.405696 -71.142197</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-06-08T17:35:01.942452+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/67230">
+<link>http://platial.com/place/67230</link>
+<title>Knitting Room</title>
+<description><![CDATA[This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/67230">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/67230">Grab this on Platial</a> ]]></description>
+<georss:point>42.405524 -71.142273</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-24T11:35:26.733857+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/65645">
+<link>http://platial.com/place/65645</link>
+<title>†¢¢™£ˆøœ</title>
+<description><![CDATA[ijeª£∆µˆ˚î<br/>Address: 151 Erie St., Cambridge, MA<br/>Tags: platial graffiti<br /><br /><a href="http://platial.com/place/65645">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/65645">Grab this on Platial</a> ]]></description>
+<georss:point>42.352455 -71.110210</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-20T08:56:12.696224+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/62200">
+<link>http://platial.com/place/62200</link>
+<title>Allen Hall</title>
+<description><![CDATA[My dorm at UIUC.<br/>Address: 1301 W Gregory Dr, Urbana, IL<br/>Tags: dorm, uiuc, college<br/><a href="http://platial.com/place/62200"><img src="http://platial.comhttp://static.flickr.com/4/8576450_0d59cc2531_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/62200">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/62200">Grab this on Platial</a> ]]></description>
+<georss:point>40.104172 -88.220623</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-14T08:01:01.872873+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28232">
+<link>http://platial.com/place/28232</link>
+<title>Bagby Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C. However, the area around the springs are not exactly well looked upon by people who know the place.
+
+<br/>Tags: 20s, rosalie, romance, childhood, hike, camping, soak, relax, beautiful, hot springs, bathhouse, favorite, popular, crowded, organized, honeymoon tub, plumbing made from hollowed out trees, hot springs, mt hood, notorious car break in spot, rash, bacteria<br /><br /><a href="http://platial.com/place/28232">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28232">Grab this on Platial</a> ]]></description>
+<georss:point>44.936000 -122.173000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:10:18.553063+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/43666">
+<link>http://platial.com/place/43666</link>
+<title>Shooting Location for "The Field of Dreams" Film</title>
+<description><![CDATA[1989's Field of Dreams was a Best Picture Academy Award nominee, and the baseball field in the cornfield still stands today, and has become quite a tourist destination. Games are occasionally played at the field, re-enacting professional baseball at the turn of the 20th Century.<br/>Address: Dyersville, Iowa<br/>Tags: iowa, baseball, movie locations, field of dreams, kevin costner, costner, dyersville, kinsella, james earl jones, chicago black sox, shoeless joe, joe jackson, famous farms, film, movie, cinema, shooting location<br /><br /><a href="http://platial.com/place/43666">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/43666">Grab this on Platial</a> ]]></description>
+<georss:point>42.481213 -91.111679</georss:point>
+<dc:creator>echinodermata</dc:creator>
+<dc:date>2006-03-23T11:40:17.654061+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28394">
+<link>http://platial.com/place/28394</link>
+<title>Moffetts (Bonneville) Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 97 degress F, 36 degress C<br/>Tags: soak, hot springs, relax, nature<br /><br /><a href="http://platial.com/place/28394">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28394">Grab this on Platial</a> ]]></description>
+<georss:point>45.658000 -121.962000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:27.329816+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28251">
+<link>http://platial.com/place/28251</link>
+<title>Austin Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 186 degress F, 86 degress C<br/>Tags: soak, hot springs, relax, nature, popular, crowded<br /><br /><a href="http://platial.com/place/28251">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28251">Grab this on Platial</a> ]]></description>
+<georss:point>45.021000 -122.009000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:11:04.489886+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28392">
+<link>http://platial.com/place/28392</link>
+<title>Rock Creek Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br/>Tags: soak, hot springs, relax, nature<br /><br /><a href="http://platial.com/place/28392">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28392">Grab this on Platial</a> ]]></description>
+<georss:point>45.723000 -121.927000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:22.636855+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28391">
+<link>http://platial.com/place/28391</link>
+<title>St. Martins (Wind River) Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 120 degress F, 49 degress C<br/>Tags: hot springs, soak, relax, nature, wonderful<br /><br /><a href="http://platial.com/place/28391">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28391">Grab this on Platial</a> ]]></description>
+<georss:point>45.728000 -121.800000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:20.383244+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28231">
+<link>http://platial.com/place/28231</link>
+<title>Breitenbush Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br/>Tags: hot springs, resort, relax, nature, beautiful, http:www.breitenbush.com, soaking<br /><br /><a href="http://platial.com/place/28231">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28231">Grab this on Platial</a> ]]></description>
+<georss:point>44.782000 -121.975000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:10:16.529195+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28393">
+<link>http://platial.com/place/28393</link>
+<title>Collins Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 122 degress F, 50 degress C<br/>Tags: portland, nice, hot springs, soak<br /><br /><a href="http://platial.com/place/28393">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28393">Grab this on Platial</a> ]]></description>
+<georss:point>45.701000 -121.728000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:24.648745+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/31685">
+<link>http://platial.com/place/31685</link>
+<title>Darwin's Ltd.</title>
+<description><![CDATA[Nice little coffee shop/cafe, free Wifi, close enough to walk from Harvard Square.<br/>Address: 148 Mount Auburn St, Cambridge, MA<br/>Tags: coffee, beer, sandwiches, freewifi<br/><a href="http://platial.com/place/31685"><img src="http://platial.comhttp://static.flickr.com/38/84885937_74fd3d1025_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/31685">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/31685">Grab this on Platial</a> ]]></description>
+<georss:point>42.373974 -71.125053</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-10T09:24:08.152985+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28596">
+<link>http://platial.com/place/28596</link>
+<title>Huckleberry Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, Boiling degress C<br /><br /><a href="http://platial.com/place/28596">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28596">Grab this on Platial</a> ]]></description>
+<georss:point>44.115000 -110.684000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:32.283094+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28595">
+<link>http://platial.com/place/28595</link>
+<title>South Entrance Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 156 degress F, 69 degress C<br/><a href="http://platial.com/place/28595"><img src="http://platial.comhttp://static.flickr.com/52/130989872_f1457f68b5_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/28595">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28595">Grab this on Platial</a> ]]></description>
+<georss:point>44.142000 -110.656000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:30.279497+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28594">
+<link>http://platial.com/place/28594</link>
+<title>Crawfish Creek Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C<br/><a href="http://platial.com/place/28594"><img src="http://platial.comhttp://static.flickr.com/52/128312256_d6a879924c_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/28594">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28594">Grab this on Platial</a> ]]></description>
+<georss:point>44.157000 -110.699000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:28.280271+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28593">
+<link>http://platial.com/place/28593</link>
+<title>Crawfish Creek Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 138 degress F, 59 degress C<br /><br /><a href="http://platial.com/place/28593">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28593">Grab this on Platial</a> ]]></description>
+<georss:point>44.165000 -110.723000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:20.364077+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28592">
+<link>http://platial.com/place/28592</link>
+<title>Snake Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C<br /><br /><a href="http://platial.com/place/28592">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28592">Grab this on Platial</a> ]]></description>
+<georss:point>44.169000 -110.583000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:12.234974+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28591">
+<link>http://platial.com/place/28591</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 142 degress F, 61 degress C<br /><br /><a href="http://platial.com/place/28591">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28591">Grab this on Platial</a> ]]></description>
+<georss:point>44.187000 -110.726000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:10.027857+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28590">
+<link>http://platial.com/place/28590</link>
+<title>Hot Springs on Upper Snake River, WY</title>
+<description><![CDATA[Hot spring, temperature: 167 degress F, 75 degress C<br /><br /><a href="http://platial.com/place/28590">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28590">Grab this on Platial</a> ]]></description>
+<georss:point>44.204000 -110.486000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:07.79658+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28589">
+<link>http://platial.com/place/28589</link>
+<title>Hot Springs on lewis Lake, WY</title>
+<description><![CDATA[Hot spring, temperature: 154 degress F, 68 degress C<br /><br /><a href="http://platial.com/place/28589">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28589">Grab this on Platial</a> ]]></description>
+<georss:point>44.276000 -110.636000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:05.683418+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28588">
+<link>http://platial.com/place/28588</link>
+<title>Rustic Geyser, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28588">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28588">Grab this on Platial</a> ]]></description>
+<georss:point>44.282000 -110.506000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:03.66329+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28587">
+<link>http://platial.com/place/28587</link>
+<title>Bechler River Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 194 degress F, 90 degress C<br /><br /><a href="http://platial.com/place/28587">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28587">Grab this on Platial</a> ]]></description>
+<georss:point>44.285000 -110.900000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:01.611442+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28586">
+<link>http://platial.com/place/28586</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, 201 degress C<br /><br /><a href="http://platial.com/place/28586">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28586">Grab this on Platial</a> ]]></description>
+<georss:point>44.290000 -110.504000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:59.658699+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28585">
+<link>http://platial.com/place/28585</link>
+<title>Heart Lake Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: Middle Group degress F, 174 degress C<br /><br /><a href="http://platial.com/place/28585">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28585">Grab this on Platial</a> ]]></description>
+<georss:point>44.299000 -110.517000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:57.181801+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28584">
+<link>http://platial.com/place/28584</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, 201 degress C<br /><br /><a href="http://platial.com/place/28584">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28584">Grab this on Platial</a> ]]></description>
+<georss:point>44.307000 -110.526000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:55.240485+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28583">
+<link>http://platial.com/place/28583</link>
+<title>Hot Springs on lewis Lake, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28583">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28583">Grab this on Platial</a> ]]></description>
+<georss:point>44.309000 -110.654000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:53.22295+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28582">
+<link>http://platial.com/place/28582</link>
+<title>Shoshone Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28582">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28582">Grab this on Platial</a> ]]></description>
+<georss:point>44.354000 -110.800000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:51.179049+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28581">
+<link>http://platial.com/place/28581</link>
+<title>Hot Springs on Continental Divide, WY</title>
+<description><![CDATA[Hot spring, temperature: 189 degress F, 87 degress C<br /><br /><a href="http://platial.com/place/28581">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28581">Grab this on Platial</a> ]]></description>
+<georss:point>44.401000 -110.936000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:49.077176+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28580">
+<link>http://platial.com/place/28580</link>
+<title>Hot Springs on Upper Firehole River, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28580">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28580">Grab this on Platial</a> ]]></description>
+<georss:point>44.404000 -110.824000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:47.054664+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28579">
+<link>http://platial.com/place/28579</link>
+<title>Summit Lake Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 162 degress F, 72 degress C<br /><br /><a href="http://platial.com/place/28579">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28579">Grab this on Platial</a> ]]></description>
+<georss:point>44.410000 -110.953000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:45.039394+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28578">
+<link>http://platial.com/place/28578</link>
+<title>Lone Star Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: Footbridge degress F, 183 degress C<br /><br /><a href="http://platial.com/place/28578">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28578">Grab this on Platial</a> ]]></description>
+<georss:point>44.414000 -110.817000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:42.938808+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28577">
+<link>http://platial.com/place/28577</link>
+<title>West. Thumb Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28577">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28577">Grab this on Platial</a> ]]></description>
+<georss:point>44.417000 -110.570000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:40.90238+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28576">
+<link>http://platial.com/place/28576</link>
+<title>Lone Star Geyser, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28576">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28576">Grab this on Platial</a> ]]></description>
+<georss:point>44.418000 -110.805000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:38.844625+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28575">
+<link>http://platial.com/place/28575</link>
+<title>Smoke Jumper Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br /><br /><a href="http://platial.com/place/28575">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28575">Grab this on Platial</a> ]]></description>
+<georss:point>44.421000 -110.952000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:36.818513+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28574">
+<link>http://platial.com/place/28574</link>
+<title>West. Thumb Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 196 degress F, 91 degress C<br /><br /><a href="http://platial.com/place/28574">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28574">Grab this on Platial</a> ]]></description>
+<georss:point>44.422000 -110.574000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:34.767729+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28573">
+<link>http://platial.com/place/28573</link>
+<title>Potts Hot Spring Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28573">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28573">Grab this on Platial</a> ]]></description>
+<georss:point>44.433000 -110.581000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:32.749915+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28572">
+<link>http://platial.com/place/28572</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28572">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28572">Grab this on Platial</a> ]]></description>
+<georss:point>44.433000 -110.813000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:30.829745+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28571">
+<link>http://platial.com/place/28571</link>
+<title>Hot Springs on Continental Divide, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28571">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28571">Grab this on Platial</a> ]]></description>
+<georss:point>44.438000 -110.977000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:28.730401+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28570">
+<link>http://platial.com/place/28570</link>
+<title>SouthEastern Group, WY</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br /><br /><a href="http://platial.com/place/28570">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28570">Grab this on Platial</a> ]]></description>
+<georss:point>44.459000 -110.817000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:26.706763+00:00</dc:date>
+</item>
+</rdf:RDF> \ No newline at end of file
diff --git a/misc/openlayers/tests/Map.html b/misc/openlayers/tests/Map.html
new file mode 100644
index 0000000..9693fb1
--- /dev/null
+++ b/misc/openlayers/tests/Map.html
@@ -0,0 +1,2255 @@
+<html>
+<head>
+ <script>
+ /**
+ * Because browsers that implement requestAnimationFrame may not execute
+ * animation functions while a window is not displayed (e.g. in a hidden
+ * iframe as in these tests), we mask the native implementations here. The
+ * native requestAnimationFrame functionality is tested in Util.html and
+ * in PanZoom.html (where a popup is opened before panning). The panTo tests
+ * here will test the fallback setTimeout implementation for animation.
+ */
+ window.requestAnimationFrame =
+ window.webkitRequestAnimationFrame =
+ window.mozRequestAnimationFrame =
+ window.oRequestAnimationFrame =
+ window.msRequestAnimationFrame = null;
+ </script>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var map;
+
+ function test_Map_constructor (t) {
+ t.plan( 11 );
+
+ map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+
+ t.ok( OpenLayers.Element.hasClass(map.div, "olMap"), "Map div has olMap class");
+
+ t.ok( map instanceof OpenLayers.Map, "new OpenLayers.Map returns object" );
+ if (!isMozilla) {
+ t.ok( true, "skipping element test outside of Mozilla");
+ t.ok( true, "skipping element test outside of Mozilla");
+ t.ok( true, "skipping element test outside of Mozilla");
+ } else {
+ t.ok( map.div instanceof HTMLDivElement, "map.div is an HTMLDivElement" );
+ t.ok( map.viewPortDiv instanceof HTMLDivElement, "map.viewPortDiv is an HTMLDivElement" );
+ t.ok( map.layerContainerDiv instanceof HTMLDivElement, "map.layerContainerDiv is an HTMLDivElement" );
+ }
+ t.ok( map.layers instanceof Array, "map.layers is an Array" );
+ t.ok( map.controls instanceof Array, "map.controls is an Array" );
+ t.eq( map.controls.length, 4, "Default map has 4 controls." );
+ t.ok( map.events instanceof OpenLayers.Events, "map.events is an OpenLayers.Events" );
+ t.ok( map.getMaxExtent() instanceof OpenLayers.Bounds, "map.maxExtent is an OpenLayers.Bounds" );
+ t.ok( map.getNumZoomLevels() > 0, "map has a default numZoomLevels" );
+
+ map.destroy();
+ }
+
+ function test_Map_constructor_convenience(t) {
+ t.plan(13);
+ var map = new OpenLayers.Map({
+ maxExtent: [-170, -80, 170, 80],
+ restrictedExtent: [-120, -65, 120, 65],
+ layers: [
+ new OpenLayers.Layer(null, {isBaseLayer: true})
+ ],
+ center: [-111, 45],
+ zoom: 3
+ });
+
+ // maxExtent from array
+ t.ok(map.maxExtent instanceof OpenLayers.Bounds, "maxExtent bounds");
+ t.eq(map.maxExtent.left, -170, "maxExtent left");
+ t.eq(map.maxExtent.bottom, -80, "maxExtent bottom");
+ t.eq(map.maxExtent.right, 170, "maxExtent right");
+ t.eq(map.maxExtent.top, 80, "maxExtent top");
+
+ // restrictedExtent from array
+ t.ok(map.restrictedExtent instanceof OpenLayers.Bounds, "restrictedExtent bounds");
+ t.eq(map.restrictedExtent.left, -120, "restrictedExtent left");
+ t.eq(map.restrictedExtent.bottom, -65, "restrictedExtent bottom");
+ t.eq(map.restrictedExtent.right, 120, "restrictedExtent right");
+ t.eq(map.restrictedExtent.top, 65, "restrictedExtent top");
+
+ var center = map.getCenter();
+ t.eq(center.lon, -111, "center lon");
+ t.eq(center.lat, 45, "center lat");
+
+ t.eq(map.getZoom(), 3, "zoom");
+
+ map.destroy();
+ }
+
+ function test_Map_constructor_late_rendering(t) {
+ t.plan( 4 );
+
+ map = new OpenLayers.Map();
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+
+ t.ok(map.div != null, "Map has a div even though none was specified.");
+ t.ok(map.viewPortDiv.parentNode == map.div, "Map is attached to a temporary div that holds the viewPortDiv.");
+
+ var mapDiv = document.getElementById("map");
+ // clean up the effects of other tests
+ while(OpenLayers.Element.hasClass(mapDiv, "olMap")) {
+ OpenLayers.Element.removeClass(mapDiv, "olMap");
+ }
+ map.render(mapDiv); // Can also take a string.
+
+ t.ok(map.div == mapDiv, "Map is now rendered to the 'map' div.")
+ t.ok( OpenLayers.Element.hasClass(map.div, "olMap"), "Map div has olMap class");
+
+ map.destroy();
+
+ }
+
+ function test_Map_constructor_renderTo(t) {
+ t.plan( 1 );
+
+ map = new OpenLayers.Map({
+ div: "map"
+ });
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+
+ var mapDiv = document.getElementById("map");
+ t.ok(map.div == mapDiv, "Map is rendered to the 'map' div.")
+
+ map.destroy();
+ }
+
+ function test_Map_setOptions(t) {
+ t.plan(2);
+ map = new OpenLayers.Map('map', {maxExtent: new OpenLayers.Bounds(100, 200, 300, 400)});
+ map.setOptions({theme: 'foo'});
+
+ t.eq(map.theme, 'foo', "theme is correctly set by setOptions");
+ t.ok(map.maxExtent.equals(new OpenLayers.Bounds(100, 200, 300, 400)),
+ "maxExtent is correct after calling setOptions");
+
+ map.destroy();
+ }
+
+ function test_Map_add_layers(t) {
+ t.plan(8);
+ map = new OpenLayers.Map('map');
+ var layer1 = new OpenLayers.Layer.WMS("Layer 1",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ var layer2 = new OpenLayers.Layer.WMS("Layer 2",
+ "http://wms.jpl.nasa.gov/wms.cgi", {layers: "modis,global_mosaic"});
+ // this uses map.addLayer internally
+ map.addLayers([layer1, layer2])
+ t.eq( map.layers.length, 2, "map has exactly two layers" );
+ t.ok( map.layers[0] === layer1, "1st layer is layer1" );
+ t.ok( map.layers[1] === layer2, "2nd layer is layer2" );
+ t.ok( layer1.map === map, "layer.map is map" );
+ t.eq( parseInt(layer1.div.style.zIndex), map.Z_INDEX_BASE['BaseLayer'],
+ "layer1 zIndex is set" );
+ t.eq( parseInt(layer2.div.style.zIndex), map.Z_INDEX_BASE['BaseLayer'] + 5,
+ "layer2 zIndex is set" );
+
+ map.events.register('preaddlayer', this, function(evt) {
+ return !(evt.layer.name === 'donotadd');
+ });
+ var layer3 = new OpenLayers.Layer.WMS("donotadd",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayers([layer3]);
+ t.eq(map.layers.length, 2, "layer is not added since preaddlayer returns false");
+ layer3.name = 'pleaseadd';
+ map.addLayers([layer3]);
+ t.eq(map.layers.length, 3, "layer is added since preaddlayer returns true");
+
+ map.destroy();
+ }
+
+ function test_Map_options(t) {
+ t.plan(3);
+ map = new OpenLayers.Map('map', {numZoomLevels: 6, maxResolution: 3.14159, theme: 'foo'});
+ t.eq( map.numZoomLevels, 6, "map.numZoomLevels set correctly via options hashtable" );
+ t.eq( map.maxResolution, 3.14159, "map.maxResolution set correctly via options hashtable" );
+ t.eq( map.theme, 'foo', "map theme set correctly." );
+
+ map.destroy();
+ }
+
+ function test_eventListeners(t) {
+ t.plan(1);
+
+ var method = OpenLayers.Events.prototype.on;
+ // test that events.on is called at map construction
+ var options = {
+ eventListeners: {foo: "bar"},
+ tileManager: null,
+ controls: []
+ };
+ OpenLayers.Events.prototype.on = function(obj) {
+ t.eq(obj, options.eventListeners, "events.on called with eventListeners");
+ }
+ var map = new OpenLayers.Map('map', options);
+ OpenLayers.Events.prototype.on = method;
+ map.destroy();
+
+ // if events.on is called again, this will fail due to an extra test
+ // test map without eventListeners
+ OpenLayers.Events.prototype.on = function(obj) {
+ t.fail("events.on called without eventListeners");
+ }
+ var map2 = new OpenLayers.Map("map", {tileManager: null, controls: []});
+ OpenLayers.Events.prototype.on = method;
+ map2.destroy();
+ }
+
+ function test_Map_center(t) {
+ t.plan(14);
+ var log = [];
+ map = new OpenLayers.Map('map', {
+ zoomMethod: null,
+ eventListeners: {
+ "movestart": function() {log.push("movestart");},
+ "move": function() {log.push("move");},
+ "moveend": function() {log.push("moveend");}
+ }
+ });
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"} );
+ map.addLayer(baseLayer);
+ var ll = new OpenLayers.LonLat(2,1);
+ map.setCenter(ll, 0);
+ t.ok( map.getCenter() instanceof OpenLayers.LonLat, "map.getCenter returns a LonLat");
+ t.eq( map.getZoom(), 0, "map.zoom is correct after calling setCenter");
+ t.ok( map.getCenter().equals(ll), "map center is correct after calling setCenter");
+ map.zoomIn();
+ t.eq( map.getZoom(), 1, "map.zoom is correct after calling setCenter,zoom in");
+ t.ok( map.getCenter().equals(ll), "map center is correct after calling setCenter, zoom in");
+ map.zoomOut();
+ t.eq( map.getZoom(), 0, "map.zoom is correct after calling setCenter,zoom in, zoom out");
+
+ log = [];
+ map.zoomTo(5);
+ t.eq(log[0], "movestart", "zoomTo fires movestart event");
+ t.eq(log[1], "move", "zoomTo fires move event");
+ t.eq(log[2], "moveend", "zoomTo fires moveend event");
+ t.eq( map.getZoom(), 5, "map.zoom is correct after calling zoomTo" );
+
+ /**
+ map.zoomToMaxExtent();
+ t.eq( map.getZoom(), 2, "map.zoom is correct after calling zoomToMaxExtent" );
+ var lonlat = map.getCenter();
+ var zero = new OpenLayers.LonLat(0, 0);
+ t.ok( lonlat.equals(zero), "map center is correct after calling zoomToFullExtent" );
+ */
+
+ map.getCenter().lon = 10;
+ t.ok( map.getCenter().equals(ll), "map.getCenter returns a clone of map.center");
+
+ // allow calling setCenter with an array
+ map.setCenter([4, 2]);
+ var center = map.getCenter();
+ t.ok(center instanceof OpenLayers.LonLat, "(array) center is lonlat");
+ t.eq(center.lon, 4, "(array) center lon");
+ t.eq(center.lat, 2, "(array) center lat");
+
+ map.destroy();
+ }
+
+ function test_Map_zoomend_event (t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map', {zoomMethod: null});
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.events.register("zoomend", {count: 0}, function() {
+ this.count++;
+ t.ok(true, "zoomend event was triggered " + this.count + " times");
+ });
+ map.setCenter(new OpenLayers.LonLat(2, 1), 0);
+ map.zoomIn();
+ map.zoomOut();
+
+ map.destroy();
+ }
+
+ function test_Map_add_remove_popup (t) {
+ t.plan(4);
+
+ map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+
+ var popup = new OpenLayers.Popup("chicken",
+ new OpenLayers.LonLat(0,0),
+ new OpenLayers.Size(200,200));
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ map.addPopup(popup);
+ var pIndex = OpenLayers.Util.indexOf(map.popups, popup);
+ t.eq(pIndex, 0, "popup successfully added to Map's internal popups array");
+
+ var nodes = map.layerContainerDiv.childNodes;
+
+ var found = false;
+ for (var i=0; i < nodes.length; i++) {
+ if (nodes.item(i) == popup.div) {
+ found = true;
+ break;
+ }
+ }
+ t.ok(found, "popup.div successfully added to the map's viewPort");
+
+
+ map.removePopup(popup);
+ var pIndex = OpenLayers.Util.indexOf(map.popups, popup);
+ t.eq(pIndex, -1, "popup successfully removed from Map's internal popups array");
+
+ var found = false;
+ for (var i=0; i < nodes.length; i++) {
+ if (nodes.item(i) == popup.div) {
+ found = true;
+ break;
+ }
+ }
+ t.ok(!found, "popup.div successfully removed from the map's viewPort");
+
+ map.destroy();
+ }
+
+ function test_Map_add_popup_exclusive(t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ for (var i = 0; i < 10; i++) {
+ var popup = new OpenLayers.Popup("chicken",
+ new OpenLayers.LonLat(0,0),
+ new OpenLayers.Size(200,200));
+ map.addPopup(popup);
+ }
+ t.eq(map.popups.length, 10, "addPopup non exclusive mode works");
+
+ var popup = new OpenLayers.Popup("chicken",
+ new OpenLayers.LonLat(0,0),
+ new OpenLayers.Size(200,200));
+ map.addPopup(popup, true);
+ t.eq(map.popups.length, 1, "addPopup exclusive mode works");
+
+ map.destroy();
+ }
+
+
+/*** THIS IS A GOOD TEST, BUT IT SHOULD BE MOVED TO WMS.
+ * Also, it won't work until we figure out the viewSize bug
+
+ function 08_Map_px_lonlat_translation (t) {
+ t.plan( 6 );
+ map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(baseLayer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ var pixel = new OpenLayers.Pixel(50,150);
+ var lonlat = map.getLonLatFromViewPortPx(pixel);
+ t.ok( lonlat instanceof OpenLayers.LonLat, "getLonLatFromViewPortPx returns valid OpenLayers.LonLat" );
+
+ var newPixel = map.getViewPortPxFromLonLat(lonlat);
+ t.ok( newPixel instanceof OpenLayers.Pixel, "getViewPortPxFromLonLat returns valid OpenLayers.Pixel" );
+
+ // WARNING!!! I'm faily sure that the following test's validity
+ // depends highly on rounding and the resolution. For now,
+ // in the default case, it seems to work. This may not
+ // always be so.
+ t.ok( newPixel.equals(pixel), "Translation to pixel and back to lonlat is consistent");
+
+ lonlat = map.getLonLatFromPixel(pixel);
+ t.ok( lonlat instanceof OpenLayers.LonLat, "getLonLatFromPixel returns valid OpenLayers.LonLat" );
+
+ newPixel = map.getPixelFromLonLat(lonlat);
+ t.ok( newPixel instanceof OpenLayers.Pixel, "getPixelFromLonLat returns valid OpenLayers.Pixel" );
+
+ t.ok( newPixel.equals(pixel), "2nd translation to pixel and back to lonlat is consistent");
+ }
+ */
+
+ function test_Map_isValidZoomLevel(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map("map");
+ map.addLayer(new OpenLayers.Layer(null, {
+ isBaseLayer: true, wrapDateLine: true, numZoomLevels: 19
+ }));
+ map.zoomToMaxExtent();
+
+ var valid;
+
+ valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [-1]);
+ t.eq(valid, false, "-1 is not a valid zoomLevel");
+
+ valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [0]);
+ t.eq(valid, true, "0 is a valid zoomLevel");
+
+ valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [18]);
+ t.eq(valid, true, "18 is a valid zoomLevel");
+
+ valid = OpenLayers.Map.prototype.isValidZoomLevel.apply(map, [19]);
+ t.eq(valid, false, "19 is not a valid zoomLevel");
+
+ map.moveTo([16, 48], 0);
+ t.eq(map.getCenter().toShortString(), "0, 0", "no panning when moveTo is called with invalid zoom");
+
+ map.destroy();
+ }
+
+ function test_Map_isValidLonLat(t) {
+ t.plan( 3 );
+
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656), maxResolution: 296985/1024, projection:"EPSG:2805" } );
+ map.addLayer(layer);
+
+ t.ok( !map.isValidLonLat(null), "null lonlat is not valid" );
+ t.ok( map.isValidLonLat(new OpenLayers.LonLat(33862, 717606)), "lonlat outside max extent is valid" );
+ t.ok( !map.isValidLonLat(new OpenLayers.LonLat(10, 10)), "lonlat outside max extent is not valid" );
+
+ map.destroy();
+ }
+
+ function test_Map_getLayer(t) {
+ var numLayers = 3;
+ t.plan( numLayers + 1 );
+
+ var m = {
+ layers: []
+ };
+
+ for(var i = 0; i < numLayers; i++) {
+ m.layers.push( { 'id': i } );
+ }
+
+ for(var i = 0; i < numLayers; i++) {
+ var layer = OpenLayers.Map.prototype.getLayer.apply(m, [i]);
+ t.ok( layer == m.layers[i], "getLayer correctly returns layer " + i);
+ }
+
+ var gotLayer = OpenLayers.Map.prototype.getLayer.apply(m, ["chicken"]);
+ t.ok( gotLayer == null, "getLayer correctly returns null when layer not found");
+
+ map.destroy();
+ }
+
+ function test_Map_getLayersBy(t) {
+
+ var map = {
+ getBy: OpenLayers.Map.prototype.getBy,
+ getLayersBy: OpenLayers.Map.prototype.getLayersBy,
+ layers: [
+ {foo: "foo", id: Math.random()},
+ {foo: "bar", id: Math.random()},
+ {foo: "foobar", id: Math.random()},
+ {foo: "foo bar", id: Math.random()},
+ {foo: "foo", id: Math.random()}
+ ]
+ };
+
+ var cases = [
+ {
+ got: map.getLayersBy("foo", "foo"),
+ expected: [map.layers[0], map.layers[4]],
+ message: "(string literal) got two layers matching foo"
+ }, {
+ got: map.getLayersBy("foo", "bar"),
+ expected: [map.layers[1]],
+ message: "(string literal) got one layer matching foo"
+ }, {
+ got: map.getLayersBy("foo", "barfoo"),
+ expected: [],
+ message: "(string literal) got empty array for no foo match"
+ }, {
+ got: map.getLayersBy("foo", /foo/),
+ expected: [map.layers[0], map.layers[2], map.layers[3], map.layers[4]],
+ message: "(regexp literal) got three layers containing string"
+ }, {
+ got: map.getLayersBy("foo", /foo$/),
+ expected: [map.layers[0], map.layers[4]],
+ message: "(regexp literal) got three layers ending with string"
+ }, {
+ got: map.getLayersBy("foo", /\s/),
+ expected: [map.layers[3]],
+ message: "(regexp literal) got layer containing space"
+ }, {
+ got: map.getLayersBy("foo", new RegExp("BAR", "i")),
+ expected: [map.layers[1], map.layers[2], map.layers[3]],
+ message: "(regexp object) got layers ignoring case"
+ }, {
+ got: map.getLayersBy("foo", {test: function(str) {return str.length > 3;}}),
+ expected: [map.layers[2], map.layers[3]],
+ message: "(custom object) got layers with foo length greater than 3"
+ }
+ ];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, cases[i].message);
+ }
+
+ }
+
+ function test_Map_getLayersByName(t) {
+
+ var map = {
+ getBy: OpenLayers.Map.prototype.getBy,
+ getLayersBy: OpenLayers.Map.prototype.getLayersBy,
+ getLayersByName: OpenLayers.Map.prototype.getLayersByName,
+ layers: [
+ {name: "foo", id: Math.random()},
+ {name: "bar", id: Math.random()},
+ {name: "foobar", id: Math.random()},
+ {name: "foo bar", id: Math.random()},
+ {name: "foo", id: Math.random()}
+ ]
+ };
+
+ var cases = [
+ {
+ got: map.getLayersByName("foo"),
+ expected: [map.layers[0], map.layers[4]],
+ message: "(string literal) got two layers matching name"
+ }, {
+ got: map.getLayersByName("bar"),
+ expected: [map.layers[1]],
+ message: "(string literal) got one layer matching name"
+ }, {
+ got: map.getLayersByName("barfoo"),
+ expected: [],
+ message: "(string literal) got empty array for no match"
+ }, {
+ got: map.getLayersByName(/foo/),
+ expected: [map.layers[0], map.layers[2], map.layers[3], map.layers[4]],
+ message: "(regexp literal) got three layers containing string"
+ }, {
+ got: map.getLayersByName(/foo$/),
+ expected: [map.layers[0], map.layers[4]],
+ message: "(regexp literal) got three layers ending with string"
+ }, {
+ got: map.getLayersByName(/\s/),
+ expected: [map.layers[3]],
+ message: "(regexp literal) got layer containing space"
+ }, {
+ got: map.getLayersByName(new RegExp("BAR", "i")),
+ expected: [map.layers[1], map.layers[2], map.layers[3]],
+ message: "(regexp object) got layers ignoring case"
+ }, {
+ got: map.getLayersByName({test: function(str) {return str.length > 3;}}),
+ expected: [map.layers[2], map.layers[3]],
+ message: "(custom object) got layers with name length greater than 3"
+ }
+ ];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, cases[i].message);
+ }
+
+ }
+
+ function test_Map_getLayersByClass(t) {
+
+ var map = {
+ getBy: OpenLayers.Map.prototype.getBy,
+ getLayersBy: OpenLayers.Map.prototype.getLayersBy,
+ getLayersByClass: OpenLayers.Map.prototype.getLayersByClass,
+ layers: [
+ {CLASS_NAME: "foo", id: Math.random()},
+ {CLASS_NAME: "bar", id: Math.random()},
+ {CLASS_NAME: "foobar", id: Math.random()},
+ {CLASS_NAME: "foo bar", id: Math.random()},
+ {CLASS_NAME: "foo", id: Math.random()}
+ ]
+ };
+
+ var cases = [
+ {
+ got: map.getLayersByClass("foo"),
+ expected: [map.layers[0], map.layers[4]],
+ message: "(string literal) got two layers matching type"
+ }, {
+ got: map.getLayersByClass("bar"),
+ expected: [map.layers[1]],
+ message: "(string literal) got one layer matching type"
+ }, {
+ got: map.getLayersByClass("barfoo"),
+ expected: [],
+ message: "(string literal) got empty array for no match"
+ }, {
+ got: map.getLayersByClass(/foo/),
+ expected: [map.layers[0], map.layers[2], map.layers[3], map.layers[4]],
+ message: "(regexp literal) got three layers containing string"
+ }, {
+ got: map.getLayersByClass(/foo$/),
+ expected: [map.layers[0], map.layers[4]],
+ message: "(regexp literal) got three layers ending with string"
+ }, {
+ got: map.getLayersByClass(/\s/),
+ expected: [map.layers[3]],
+ message: "(regexp literal) got layer containing space"
+ }, {
+ got: map.getLayersByClass(new RegExp("BAR", "i")),
+ expected: [map.layers[1], map.layers[2], map.layers[3]],
+ message: "(regexp object) got layers ignoring case"
+ }, {
+ got: map.getLayersByClass({test: function(str) {return str.length > 3;}}),
+ expected: [map.layers[2], map.layers[3]],
+ message: "(custom object) got layers with type length greater than 3"
+ }
+ ];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, cases[i].message);
+ }
+
+ }
+
+ function test_Map_getControlsBy(t) {
+
+ var map = {
+ getBy: OpenLayers.Map.prototype.getBy,
+ getControlsBy: OpenLayers.Map.prototype.getControlsBy,
+ controls: [
+ {foo: "foo", id: Math.random()},
+ {foo: "bar", id: Math.random()},
+ {foo: "foobar", id: Math.random()},
+ {foo: "foo bar", id: Math.random()},
+ {foo: "foo", id: Math.random()}
+ ]
+ };
+
+ var cases = [
+ {
+ got: map.getControlsBy("foo", "foo"),
+ expected: [map.controls[0], map.controls[4]],
+ message: "(string literal) got two controls matching foo"
+ }, {
+ got: map.getControlsBy("foo", "bar"),
+ expected: [map.controls[1]],
+ message: "(string literal) got one control matching foo"
+ }, {
+ got: map.getControlsBy("foo", "barfoo"),
+ expected: [],
+ message: "(string literal) got empty array for no foo match"
+ }, {
+ got: map.getControlsBy("foo", /foo/),
+ expected: [map.controls[0], map.controls[2], map.controls[3], map.controls[4]],
+ message: "(regexp literal) got three controls containing string"
+ }, {
+ got: map.getControlsBy("foo", /foo$/),
+ expected: [map.controls[0], map.controls[4]],
+ message: "(regexp literal) got three controls ending with string"
+ }, {
+ got: map.getControlsBy("foo", /\s/),
+ expected: [map.controls[3]],
+ message: "(regexp literal) got control containing space"
+ }, {
+ got: map.getControlsBy("foo", new RegExp("BAR", "i")),
+ expected: [map.controls[1], map.controls[2], map.controls[3]],
+ message: "(regexp object) got layers ignoring case"
+ }, {
+ got: map.getControlsBy("foo", {test: function(str) {return str.length > 3;}}),
+ expected: [map.controls[2], map.controls[3]],
+ message: "(custom object) got controls with foo length greater than 3"
+ }
+ ];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, cases[i].message);
+ }
+
+ }
+
+ function test_Map_getControlsByClass(t) {
+
+ var map = {
+ getBy: OpenLayers.Map.prototype.getBy,
+ getControlsBy: OpenLayers.Map.prototype.getControlsBy,
+ getControlsByClass: OpenLayers.Map.prototype.getControlsByClass,
+ controls: [
+ {CLASS_NAME: "foo", id: Math.random()},
+ {CLASS_NAME: "bar", id: Math.random()},
+ {CLASS_NAME: "foobar", id: Math.random()},
+ {CLASS_NAME: "foo bar", id: Math.random()},
+ {CLASS_NAME: "foo", id: Math.random()}
+ ]
+ };
+
+ var cases = [
+ {
+ got: map.getControlsByClass("foo"),
+ expected: [map.controls[0], map.controls[4]],
+ message: "(string literal) got two controls matching type"
+ }, {
+ got: map.getControlsByClass("bar"),
+ expected: [map.controls[1]],
+ message: "(string literal) got one control matching type"
+ }, {
+ got: map.getControlsByClass("barfoo"),
+ expected: [],
+ message: "(string literal) got empty array for no match"
+ }, {
+ got: map.getControlsByClass(/foo/),
+ expected: [map.controls[0], map.controls[2], map.controls[3], map.controls[4]],
+ message: "(regexp literal) got three controls containing string"
+ }, {
+ got: map.getControlsByClass(/foo$/),
+ expected: [map.controls[0], map.controls[4]],
+ message: "(regexp literal) got three controls ending with string"
+ }, {
+ got: map.getControlsByClass(/\s/),
+ expected: [map.controls[3]],
+ message: "(regexp literal) got control containing space"
+ }, {
+ got: map.getControlsByClass(new RegExp("BAR", "i")),
+ expected: [map.controls[1], map.controls[2], map.controls[3]],
+ message: "(regexp object) got controls ignoring case"
+ }, {
+ got: map.getControlsByClass({test: function(str) {return str.length > 3;}}),
+ expected: [map.controls[2], map.controls[3]],
+ message: "(custom object) got controls with type length greater than 3"
+ }
+ ];
+ t.plan(cases.length);
+ for(var i=0; i<cases.length; ++i) {
+ t.eq(cases[i].got, cases[i].expected, cases[i].message);
+ }
+
+ }
+
+ function test_Map_double_addLayer(t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS('Test Layer',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'}
+ );
+
+ var added = map.addLayer(layer);
+ t.ok(added === true, "Map.addLayer returns true if the layer is added to the map.");
+ var added = map.addLayer(layer);
+ t.ok(added === false, "Map.addLayer returns false if the layer is already present.");
+
+ map.destroy();
+ }
+
+ function test_Map_setBaseLayer(t) {
+ t.plan( 6 );
+
+ map = new OpenLayers.Map('map');
+
+ var wmslayer = new OpenLayers.Layer.WMS('Test Layer',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656), maxResolution: 296985/1024, projection:"EPSG:2805" } );
+
+ var wmslayer2 = new OpenLayers.Layer.WMS('Test Layer2',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656), maxResolution: 296985/1024, projection:"EPSG:2805" } );
+
+ map.addLayers([wmslayer, wmslayer2]);
+
+ t.ok(map.baseLayer == wmslayer, "default base layer is first one added");
+
+ map.setBaseLayer(null);
+ t.ok(map.baseLayer == wmslayer, "setBaseLayer on null object does nothing (and does not break)");
+
+ map.setBaseLayer("chicken");
+ t.ok(map.baseLayer == wmslayer, "setBaseLayer on non-layer object does nothing (and does not break)");
+
+ map.setBaseLayer(wmslayer2);
+ t.ok(map.baseLayer == wmslayer2, "setbaselayer correctly sets 'baseLayer' property");
+
+ map.destroy();
+
+ var l1 = new OpenLayers.Layer(),
+ l2 = new OpenLayers.Layer(null, {maxResolution: 1.4});
+ map = new OpenLayers.Map({
+ div: 'map',
+ allOverlays: true,
+ layers: [l1, l2],
+ zoom: 0,
+ center: [0, 0]
+ });
+ t.eq(l2.div.style.display, "none", "Layer invisible because not in range");
+ map.raiseLayer(l1, 1);
+ t.eq(l2.div.style.display, "block", "Layer visible after base layer change because in range now");
+ map.destroy();
+ }
+
+ function test_Map_removeLayer(t) {
+ t.plan(1);
+ var f = function() {};
+ var events = {triggerEvent: f};
+ var layers = [
+ {name: "fee", removeMap: f, events: events},
+ {name: "fi", removeMap: f, events: events},
+ {name: "fo", removeMap: f, events: events},
+ {name: "fum", removeMap: f, events: events}
+ ];
+ var map = {
+ layers: layers,
+ baseLayer: layers[0],
+ layerContainerDiv: {removeChild: f},
+ events: {triggerEvent: f},
+ resetLayersZIndex: function() {}
+ };
+ OpenLayers.Map.prototype.removeLayer.apply(map, [map.baseLayer, false]);
+ t.eq(map.baseLayer, null,
+ "removing the baselayer sets baseLayer to null");
+ }
+
+ function test_Map_removeLayer_res(t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map');
+
+ var layer0 = new OpenLayers.Layer.WMS(
+ 'Test Layer 0',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {resolutions: [4, 2, 1]}
+ );
+
+ var layer1 = new OpenLayers.Layer.WMS(
+ 'Test Layer 1',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {resolutions: [4, 2]}
+ );
+
+ map.addLayers([layer0, layer1]);
+ map.zoomToMaxExtent();
+ map.zoomTo(2);
+ t.eq(map.getResolution(), layer0.resolutions[2],
+ "correct resolution before removal");
+ map.removeLayer(layer0);
+ t.eq(map.getResolution(), layer0.resolutions[1],
+ "correct resolution after removal");
+
+ map.destroy();
+ }
+
+ function test_Map_removeLayer_zindex(t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map');
+
+ var layer0 = new OpenLayers.Layer('Test Layer 0', {isBaseLayer:true});
+ var layer1 = new OpenLayers.Layer('Test Layer 1', {isBaseLayer:true});
+ var layer2 = new OpenLayers.Layer('Test Layer 2', {isBaseLayer:false});
+
+ map.addLayers([layer0, layer1, layer2]);
+ map.removeLayer(layer0);
+
+ t.eq(parseInt(layer1.div.style.zIndex), map.Z_INDEX_BASE['BaseLayer'],
+ "correct z-index after removeLayer");
+ t.eq(parseInt(layer2.div.style.zIndex), map.Z_INDEX_BASE['Overlay'] + 5,
+ "correct z-index after removeLayer");
+
+ map.destroy();
+ }
+
+ function test_Map_removeLayer_preremovelayer(t) {
+ t.plan(4);
+ map = new OpenLayers.Map('map');
+
+ map.addLayer(new OpenLayers.Layer());
+ map.removeLayer(map.layers[0]);
+
+ // one test: standard behaviour without listener
+ t.eq(map.layers.length, 0, "without registered preremovelayer-listener layers can be removed as usual");
+
+ var callCnt = 0;
+
+ map.events.register('preremovelayer', this, function(evt) {
+ callCnt++;
+ return !(evt.layer.name === 'donotremove');
+ });
+ var layer1 = new OpenLayers.Layer('donotremove');
+ var layer2 = new OpenLayers.Layer('doremove');
+
+ map.addLayers([layer1,layer2]);
+
+ // two tests: remove action can be canceled
+ map.removeLayer(layer1);
+ t.eq(map.layers.length, 2, "layer is not removed since preremovelayer returns false");
+ map.removeLayer(layer2);
+ t.eq(map.layers.length, 1, "layer is removed since preremovelayer returns true");
+
+ // one test: listener was called twice
+ t.eq(callCnt, 2, "preremovelayer-listener was called exactly twice");
+
+ map.destroy();
+ }
+
+ function test_Map_setBaseLayer_after_pan (t) {
+ t.plan(1);
+
+ map = new OpenLayers.Map('map');
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
+ var tmsLayer = new OpenLayers.Layer.TMS("TMS",
+ "http://labs.metacarta.com/wms-c/Basic.py/",
+ {'layername':'basic', 'type':'png'});
+ map.addLayers([wmsLayer,tmsLayer]);
+ map.setBaseLayer(wmsLayer);
+ map.zoomToMaxExtent();
+ map.setBaseLayer(tmsLayer);
+ map.zoomIn();
+ map.pan(0, -200, {animate:false});
+ var log = [];
+ map.applyTransform = function(x, y, scale) {
+ log.push([x || map.layerContainerOriginPx.x, y || map.layerContainerOriginPx.y, scale]);
+ OpenLayers.Map.prototype.applyTransform.apply(this, arguments);
+ };
+ map.setBaseLayer(wmsLayer);
+ t.eq(log[0][0], 0, "layerContainer is recentered after setBaseLayer");
+
+ map.destroy();
+ }
+
+ function test_Map_moveLayer (t) {
+ t.plan(10);
+
+ var ct = 0;
+ map = new OpenLayers.Map('map');
+ var wmslayer = new OpenLayers.Layer.WMS('Test Layer',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656), maxResolution: 296985/1024, projection:"EPSG:2805" } );
+
+ var wmslayer2 = new OpenLayers.Layer.WMS('Test Layer2',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656), maxResolution: 296985/1024, projection:"EPSG:2805" } );
+
+ var wmslayer3 = new OpenLayers.Layer.WMS('Test Layer2',
+ "http://octo.metacarta.com/cgi-bin/mapserv",
+ {map: '/mapdata/vmap_wms.map', layers: 'basic', format: 'image/jpeg'},
+ {maxExtent: new OpenLayers.Bounds(33861, 717605, 330846, 1019656), maxResolution: 296985/1024, projection:"EPSG:2805" } );
+
+ map.addLayers([wmslayer, wmslayer2, wmslayer3]);
+ map.events.register("changelayer", map, function (e) { ct++; });
+ t.eq( map.getNumLayers(), 3, "getNumLayers returns the number of layers" );
+ t.eq( map.getLayerIndex(wmslayer3), 2, "getLayerIndex returns the right index" );
+ map.raiseLayer(wmslayer3, 1);
+ t.eq( map.getLayerIndex(wmslayer3), 2, "can't moveLayer up past the top of the stack" );
+ map.raiseLayer(wmslayer, -1);
+ t.eq( map.getLayerIndex(wmslayer), 0, "can't moveLayer down past the bottom of the stack" );
+ map.raiseLayer(wmslayer3, -1);
+ t.eq( map.getLayerIndex(wmslayer3), 1, "can moveLayer down from the top" );
+ t.eq( parseInt(wmslayer3.div.style.zIndex), map.Z_INDEX_BASE['BaseLayer'] + 5,
+ "layer div has the right zIndex after moving down" );
+ map.raiseLayer(wmslayer, 2);
+ t.eq( map.getLayerIndex(wmslayer), 2, "can moveLayer up from the bottom" );
+ t.eq( parseInt(wmslayer.div.style.zIndex), map.Z_INDEX_BASE['BaseLayer'] + 2 * 5,
+ "layer div has the right zIndex after moving up" );
+ t.eq( map.getLayerIndex(wmslayer3), 0, "top layer is now on the bottom" );
+ t.eq( ct, 3, "raiseLayer triggered changelayer the right # of times" );
+
+ map.destroy();
+ }
+
+ function test_Map_moveTo(t) {
+ t.plan(2);
+
+ map = new OpenLayers.Map('map');
+ var baseLayer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"},
+ {maxResolution: 'auto', maxExtent: new OpenLayers.Bounds(-10,-10,10,10)});
+ baseLayer.events.on({
+ move: function() {
+ t.ok(true, "move listener called");
+ },
+ movestart: function(e) {
+ t.eq(e.zoomChanged, true, "movestart listener called with expected value");
+ }
+ });
+ baseLayer.events.on({
+ moveend: function(e) {
+ t.eq(e.zoomChanged, true, "moveend listener called with expected value");
+ }
+ });
+ map.addLayer(baseLayer);
+ var ll = new OpenLayers.LonLat(-100,-150);
+ map.moveTo(ll, 2);
+ t.ok(map.getCenter().equals(new OpenLayers.LonLat(0,0)), "safely sets out-of-bounds lonlat");
+
+ map.destroy();
+ }
+
+ function test_Map_defaultTheme(t) {
+ t.plan(5);
+
+ var links = document.getElementsByTagName('link');
+ map = new OpenLayers.Map('map');
+ var gotNodes = 0;
+ var themeNode = null;
+ for(var i=0; i<links.length; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(map.theme, links.item(i).href)) {
+ gotNodes += 1;
+ themeNode = links.item(i);
+ }
+ }
+ t.eq(gotNodes, 1, "by default, a single link node is added to document");
+ t.ok(themeNode != null, "a link node with the theme href was added");
+ t.eq(themeNode.rel, "stylesheet", "node added has rel set to stylesheet");
+ t.eq(themeNode.type, "text/css", "node added has type set to text/css");
+
+ // reconstruct the map to prove that another link is not added
+ map = new OpenLayers.Map('map');
+ t.eq(links.length, document.getElementsByTagName('link').length,
+ "calling the map constructor twice with the same theme doesn't add duplicate link nodes");
+
+ map.destroy();
+ }
+
+ function test_Map_customTheme(t) {
+ t.plan(5);
+
+ var customTheme = 'foo';
+ var options = {theme: customTheme};
+ map = new OpenLayers.Map('map', options);
+
+ var links = document.getElementsByTagName('link');
+ var gotNodes = 0;
+ var themeNode = null;
+ for(var i=0; i<links.length; ++i) {
+ if(OpenLayers.Util.isEquivalentUrl(map.theme, links.item(i).href)) {
+ gotNodes += 1;
+ themeNode = links.item(i);
+ }
+ }
+
+ t.eq(map.theme, customTheme, "map theme is properly set");
+ t.eq(gotNodes, 1, "with custom theme, a single link node is added to document");
+ t.ok(themeNode != null, "a link node with the theme href was added");
+ t.eq(themeNode.rel, "stylesheet", "node added has rel set to stylesheet");
+ t.eq(themeNode.type, "text/css", "node added has type set to text/css");
+
+ map.destroy();
+ }
+
+ function test_Map_noTheme(t) {
+ t.plan(1);
+
+ var head = document.getElementsByTagName('head')[0];
+ var nodeCount = head.childNodes.length;
+
+ var options = {theme: null};
+ map = new OpenLayers.Map('map', options);
+
+ t.eq(nodeCount, head.childNodes.length, "with no theme, a node is not added to document head" );
+
+ map.destroy();
+ }
+
+ function test_Map_addControls(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map('map', {
+ controls: []
+ });
+ var controls = [
+ new OpenLayers.Control({id:'firstctrl'}),
+ new OpenLayers.Control({id:'secondctrl'})
+ ];
+ map.addControls(controls);
+ t.eq(map.controls.length, 2, "two controls were added by map.addControls without a px-array");
+ t.eq(map.controls[0].id, 'firstctrl', "control with index 0 has id 'firstctrl'");
+ t.eq(map.controls[1].id, 'secondctrl', "control with index 1 has id 'secondctrl'");
+
+ var controls2 = [
+ new OpenLayers.Control({id:'thirdctrl'}),
+ new OpenLayers.Control({id:'fourthctrl'}),
+ new OpenLayers.Control({id:'fifthctrl'})
+ ];
+ // this array is intentionally one element shorter than the above
+ var pixels2 = [
+ null,
+ new OpenLayers.Pixel(27,11)
+ ];
+ map.addControls(controls2, pixels2);
+ t.eq(map.controls.length, 5, "three additional controls were added by map.addControls with a px-array");
+ t.eq(map.controls[3].position.toString(), pixels2[1].toString(), "control 'fourthctrl' has position set to given px");
+
+ map.destroy();
+ }
+
+ function test_Map_getControl(t) {
+ t.plan(2);
+
+ var map1 = new OpenLayers.Map('map');
+
+ var control = new OpenLayers.Control();
+ map1.addControl(control);
+
+ var gotControl = map1.getControl(control.id);
+ t.ok(gotControl == control, "got right control");
+
+ gotControl = map1.getControl("bogus id");
+ t.ok(gotControl == null, "getControl() for bad id returns null");
+
+ map1.destroy();
+ }
+
+ function test_Map_removeControl(t) {
+ t.plan(6);
+
+ var oldNumControls, newNumControls;
+
+ var map1 = new OpenLayers.Map('map');
+ oldNumControls = map1.controls.length;
+
+ var control = new OpenLayers.Control();
+ map1.addControl(control);
+
+ //add control
+ newNumControls = map1.controls.length;
+ t.ok( newNumControls = oldNumControls + 1, "adding a control increases control count")
+
+ var foundDiv = false;
+ for(var i=0; i < map1.viewPortDiv.childNodes.length; i++) {
+ var childNode = map1.viewPortDiv.childNodes[i];
+ if (childNode == control.div) {
+ foundDiv = true;
+ }
+ }
+ t.ok(foundDiv, "new control's div correctly added to viewPort");
+
+ //remove control
+ map1.removeControl(control)
+ newNumControls = map1.controls.length;
+ t.ok( newNumControls == oldNumControls, "removing the control decreases control count")
+
+ var gotControl = map1.getControl(control.id);
+ t.ok( gotControl == null, "control no longer in map's controls array");
+
+ var foundDiv = false;
+ for(var i=0; i < map1.viewPortDiv.childNodes.length; i++) {
+ var childNode = map1.viewPortDiv.childNodes[i];
+ if (childNode == control.div) {
+ foundDiv = true;
+ }
+ }
+ t.ok(!foundDiv, "control no longer child of viewPort");
+
+ //remove bogus
+ control = { id: "bogus id" };
+ map1.removeControl(control);
+ newNumControls = map1.controls.length;
+ t.ok( newNumControls == oldNumControls, "removing bad controlid doesnt crash or decrease control count")
+
+ map1.destroy();
+
+ }
+
+ function test_Map_restrictedExtent(t) {
+ t.plan(25);
+ var extent = new OpenLayers.Bounds(-180, -90, 180, 90);
+ var options = {
+ maxResolution: "auto"
+ };
+ var map = new OpenLayers.Map("map", options);
+ var layer = new OpenLayers.Layer.WMS(
+ "test",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"}
+ );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ var nw = new OpenLayers.LonLat(extent.left, extent.top);
+ var ne = new OpenLayers.LonLat(extent.right, extent.top);
+ var sw = new OpenLayers.LonLat(extent.left, extent.bottom);
+ var se = new OpenLayers.LonLat(extent.right, extent.bottom);
+
+ // try panning to northwest corner
+ map.setOptions({restrictedExtent: extent});
+ map.setCenter(nw, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ extent.getCenterLonLat().toString(),
+ "map extent properly restricted to northwest at zoom 0");
+ t.eq(map.zoom, 0, "zoom not restricted for nw, 0");
+ map.setCenter(nw, 5);
+ t.eq(map.getExtent().top, extent.top,
+ "map extent top properly restricted to northwest at zoom 5");
+ t.eq(map.getExtent().left, extent.left,
+ "map extent left properly restricted to northwest at zoom 5");
+ t.eq(map.zoom, 5, "zoom not restricted for nw, 5");
+ map.setOptions({restrictedExtent: null});
+ map.setCenter(nw, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ nw.toString(),
+ "map extent not restricted with null restrictedExtent for nw");
+
+ // try panning to northeast corner
+ map.setOptions({restrictedExtent: extent});
+ map.setCenter(ne, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ extent.getCenterLonLat().toString(),
+ "map extent properly restricted to northeast at zoom 0");
+ t.eq(map.zoom, 0, "zoom not restricted for ne, 0");
+ map.setCenter(ne, 5);
+ t.eq(map.getExtent().top, extent.top,
+ "map extent top properly restricted to northeast at zoom 5");
+ t.eq(map.getExtent().right, extent.right,
+ "map extent right properly restricted to northeast at zoom 5");
+ t.eq(map.zoom, 5, "zoom not restricted for ne, 5");
+ map.setOptions({restrictedExtent: null});
+ map.setCenter(ne, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ ne.toString(),
+ "map extent not restricted with null restrictedExtent for ne");
+
+ // try panning to southwest corner
+ map.setOptions({restrictedExtent: extent});
+ map.setCenter(sw, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ extent.getCenterLonLat().toString(),
+ "map extent properly restricted to southwest at zoom 0");
+ t.eq(map.zoom, 0, "zoom not restricted for sw, 0");
+ map.setCenter(sw, 5);
+ t.eq(map.getExtent().bottom, extent.bottom,
+ "map extent bottom properly restricted to southwest at zoom 5");
+ t.eq(map.getExtent().left, extent.left,
+ "map extent left properly restricted to southwest at zoom 5");
+ t.eq(map.zoom, 5, "zoom not restricted for sw, 5");
+ map.setOptions({restrictedExtent: null});
+ map.setCenter(sw, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ sw.toString(),
+ "map extent not restricted with null restrictedExtent for sw");
+
+ // try panning to southeast corner
+ map.setOptions({restrictedExtent: extent});
+ map.setCenter(se, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ extent.getCenterLonLat().toString(),
+ "map extent properly restricted to southeast at zoom 0");
+ t.eq(map.zoom, 0, "zoom not restricted for se, 0");
+ map.setCenter(se, 5);
+ t.eq(map.getExtent().bottom, extent.bottom,
+ "map extent bottom properly restricted to southeast at zoom 5");
+ t.eq(map.getExtent().right, extent.right,
+ "map extent right properly restricted to southeast at zoom 5");
+ t.eq(map.zoom, 5, "zoom not restricted for se, 5");
+ map.setOptions({restrictedExtent: null});
+ map.setCenter(se, 0);
+ t.eq(map.getExtent().getCenterLonLat().toString(),
+ se.toString(),
+ "map extent not restricted with null restrictedExtent for se");
+
+ map.destroy();
+
+ extent = new OpenLayers.Bounds(8, 44.5, 19, 50);
+ var options = {
+ restrictedExtent: extent,
+ zoomMethod: null
+ };
+ map = new OpenLayers.Map('map', options);
+
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?",
+ {layers: 'basic'}
+ );
+
+ map.addLayers([wms]);
+ map.zoomToExtent(extent);
+ map.zoomIn();
+ map.setOptions({restrictedExtent: null});
+ map.pan(-250, -250);
+ t.ok((map.getExtent().bottom == 48.3486328125 && map.getExtent().left == 7.45751953125), "Expected extent when toggling restrictedExtent");
+ map.destroy();
+ }
+
+ function test_Map_getResolutionForZoom(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var res = map.getResolutionForZoom();
+ t.eq(res, null, "getResolutionForZoom returns null for no base layer");
+ map.fractionalZoom = true;
+ var layer = new OpenLayers.Layer("test", {isBaseLayer: true});
+ map.addLayer(layer);
+ layer.getResolutionForZoom = function() {
+ t.ok(true, "getResolutionForZoom calls base layer getResolutionForZoom");
+ }
+ var res = map.getResolutionForZoom();
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_zoomTo(t) {
+ t.plan(8);
+
+ var map = new OpenLayers.Map("map", {zoomMethod: null});
+ map.addLayer(new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ }));
+
+ map.zoomToMaxExtent();
+
+ map.zoomTo(2);
+ t.eq(map.getZoom(), 2, 'zoomTo(2)');
+
+ map.zoomTo(3.6);
+ t.eq(map.getZoom(), 4, 'zoomTo(3.6)');
+
+ map.zoomTo("4.6");
+ t.eq(map.getZoom(), 5, 'zoomTo("4.6")');
+
+ map.zoomTo("1.2");
+ t.eq(map.getZoom(), 1, 'zoomTo("1.2")');
+
+ // now allow fractional zoom
+ map.fractionalZoom = true;
+
+ map.zoomTo(2);
+ t.eq(map.getZoom(), 2, '[fractionalZoom] zoomTo(2)');
+
+ map.zoomTo(3.6);
+ t.eq(map.getZoom(), 3.6, '[fractionalZoom] zoomTo(3.6)');
+
+ map.zoomTo("4.6");
+ t.eq(map.getZoom(), 4.6, '[fractionalZoom] zoomTo("4.6")');
+
+ map.zoomTo("1.2");
+ t.eq(map.getZoom(), 1.2, '[fractionalZoom] zoomTo("1.2")');
+
+ map.destroy();
+ }
+
+ function test_zoomTo_animated(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map("map");
+ map.addLayer(new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ }));
+
+ map.zoomToMaxExtent();
+
+ map.zoomTo(2);
+ map.zoomIn();
+ map.zoomOut();
+ map.zoomIn();
+ t.delay_call(2, function() {
+ t.eq(map.getZoom(), 3, '[fractionalZoom: false] zoomTo(2) - zoomIn() - zoomOut() - zoomIn()');
+
+ // now allow fractional zoom
+ map.fractionalZoom = true;
+
+ map.zoomTo(2.6);
+ map.zoomIn();
+ map.zoomOut();
+ map.zoomIn();
+ });
+ t.delay_call(4, function() {
+ t.eq(map.getZoom(), 3.6, '[fractionalZoom: true] zoomTo(2) - zoomIn() - zoomOut() - zoomIn()');
+ map.destroy();
+ });
+
+ }
+
+ function test_Map_getUnits(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map("map");
+ var units = map.getUnits();
+ t.eq(units, null, "getUnits returns null for no base layer");
+
+ var layer = new OpenLayers.Layer("test", {
+ isBaseLayer: true,
+ units: 'foo'
+ });
+ map.addLayer(layer);
+ var units = map.getUnits();
+ t.eq(units, 'foo', "getUnits returns the base layer units property");
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_Map_destroy (t) {
+ t.plan( 3 );
+ map = new OpenLayers.Map('map');
+ map.destroy();
+ t.eq( map.layers, null, "map.layers is null after destroy" );
+ t.eq( map.controls, null, "map.controls is null after destroy" );
+ t.eq( map.viewPortDiv, null, "map's viewportDiv nullified");
+ }
+
+ function test_Map_getMaxExtent(t){
+ t.plan(5);
+
+ var options = null;
+ var map = {};
+
+ //null options, no baseLayer
+ var maxExtent = OpenLayers.Map.prototype.getMaxExtent.apply(map, [options]);
+ t.eq(maxExtent, null, "null options, no baseLayer returns null");
+
+ //null options.restricted, no baseLayer
+ maxExtent = OpenLayers.Map.prototype.getMaxExtent.apply(map, [options]);
+ t.eq(maxExtent, null, "null options.restricted, no baseLayer returns null");
+
+ //true options.restricted, null map.restrictedExtent no baseLayer
+ maxExtent = OpenLayers.Map.prototype.getMaxExtent.apply(map, [options]);
+ t.eq(maxExtent, null, "true options.restricted, null map.restrictedExtent no baseLayer returns null");
+
+ //true options.restricted, valid map.restrictedExtent no baseLayer
+ options = {
+ 'restricted': true
+ };
+ map.restrictedExtent = {};
+ maxExtent = OpenLayers.Map.prototype.getMaxExtent.apply(map, [options]);
+ t.ok(maxExtent == map.restrictedExtent, "true options.restricted, valid map.restrictedExtent no baseLayer returns map.restrictedExtent");
+
+ //null options, valid baseLayer
+ options = null;
+ map.baseLayer = {
+ 'maxExtent': {}
+ };
+ var maxExtent = OpenLayers.Map.prototype.getMaxExtent.apply(map, [options]);
+ t.ok(maxExtent == map.baseLayer.maxExtent, "null options, valid baseLayer returns map.baseLayer.maxExtent");
+ }
+
+ function test_Map_zoomToMaxExtent(t){
+ t.plan(4)
+
+ gMaxExtent = {};
+
+ var map = {
+ 'getMaxExtent': function(options) {
+ gRestricted = options.restricted;
+ return gMaxExtent;
+ },
+ 'zoomToExtent': function(extent) {
+ t.ok(extent == gMaxExtent, "zoomToExtent() always called on return from map.getMaxExtent()");
+ }
+ };
+
+ //options is null
+ var options = null;
+ gRestricted = null;
+ OpenLayers.Map.prototype.zoomToMaxExtent.apply(map, [options]);
+ t.eq(gRestricted, true, "default 'restricted' passed to map.getMaxExtent() is true");
+
+ //valid options
+ options = {
+ 'restricted': {}
+ };
+ gRestricted = null;
+ OpenLayers.Map.prototype.zoomToMaxExtent.apply(map, [options]);
+ t.ok(gRestricted == options.restricted, "when valid options argument, 'options.restricted' passed to map.getMaxExtent()");
+ }
+
+ function test_Map_zoomToScale(t) {
+ t.plan(4);
+
+ var m = {
+ 'baseLayer': { 'units': {} },
+ 'size': {'w': 10, 'h': 15},
+ 'getSize': function() { return {'w': 10, 'h': 15}; },
+ 'getCachedCenter': function() { return {'lon': -5, 'lat': -25}; },
+ 'zoomToExtent': function(extent, closest) {
+ t.ok(extent.equals(g_ExpectedExtent), "extent correctly calculated for zoomToExtent()");
+ t.ok(closest == g_Closest, "closest correctly passed on to zoomToExtent()");
+ }
+ }
+
+ var temp = OpenLayers.Util.getResolutionFromScale;
+ OpenLayers.Util.getResolutionFromScale = function(scale, units) {
+ t.ok(scale == g_Scale, "scale parameter correctly passed to getResolutionFromScale");
+ t.ok(units == m.baseLayer.units, "map's baselayer's units parameter correctly passed to getResolutionFromScale");
+ return 1000;
+ };
+
+ g_ExpectedExtent = new OpenLayers.Bounds(-5005,-7525,4995,7475);
+ g_Scale = {};
+ g_Closest = {};
+ var args = [g_Scale, g_Closest];
+ OpenLayers.Map.prototype.zoomToScale.apply(m, args);
+
+ OpenLayers.Util.getResolutionFromScale = temp;
+ }
+
+ function test_Map_zoomToExtent(t) {
+ t.plan(12);
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true});
+ map.addLayer(layer);
+
+ var bounds = new OpenLayers.Bounds(-160, 15, -50, 69);
+ var center;
+
+ // default for closest
+ map.zoomToExtent(bounds);
+ center = map.getCenter();
+ t.eq(center.lon, -105, "a) correct x");
+ t.eq(center.lat, 42, "a) correct y");
+ t.eq(map.getZoom(), 2, "a) correct zoom");
+
+ // false for closest
+ map.zoomToExtent(bounds, false);
+ center = map.getCenter();
+ t.eq(center.lon, -105, "b) correct x");
+ t.eq(center.lat, 42, "b) correct y");
+ t.eq(map.getZoom(), 2, "b) correct zoom");
+
+ // true for closest
+ map.zoomToExtent(bounds, true);
+ center = map.getCenter();
+ t.eq(center.lon, -105, "c) correct x");
+ t.eq(center.lat, 42, "c) correct y");
+ t.eq(map.getZoom(), 3, "c) correct zoom");
+
+ // accept array
+ map.zoomToExtent([-160, 15, -50, 69]);
+ center = map.getCenter();
+ t.eq(center.lon, -105, "(array) correct x");
+ t.eq(center.lat, 42, "(array) correct y");
+ t.eq(map.getZoom(), 2, "(array) correct zoom");
+
+ map.destroy();
+ }
+
+ function test_Map_zoomToExtent_wrapped(t) {
+ t.plan(9);
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer(null, {isBaseLayer: true, wrapDateLine: true});
+ map.addLayer(layer);
+
+ var bounds, center;
+
+ var cases = [{
+ // real world
+ bbox: [120, -20, 140, 0],
+ center: [130, -10]
+ }, {
+ // one world to the right
+ bbox: [220, -45, 240, 45],
+ center: [-130, 0]
+ }, {
+ // two worlds to the right
+ bbox: [550, -15, 560, 5],
+ center: [-165, -5]
+ }, {
+ // one world to the left
+ bbox: [-240, -15, -220, 5],
+ center: [130, -5]
+ }, {
+ // two worlds to the left
+ bbox: [-600, -15, -580, 5],
+ center: [130, -5]
+ }];
+
+ var num = cases.length;
+ t.plan(num * 2);
+
+ var c, bounds, center;
+ for (var i=0; i<num; ++i) {
+ c = cases[i];
+ bounds = OpenLayers.Bounds.fromArray(c.bbox);
+ map.zoomToExtent(bounds);
+ center = map.getCenter();
+ t.eq(center.lon, c.center[0], "y: " + bounds);
+ t.eq(center.lat, c.center[1], "x: " + bounds);
+ }
+
+ map.destroy();
+ }
+
+
+ function test_allOverlays(t) {
+
+ t.plan(18);
+
+ var map = new OpenLayers.Map({
+ div: "map", allOverlays: true
+ });
+
+ var a = new OpenLayers.Layer.Vector("a", {visibility: true});
+
+ var b = new OpenLayers.Layer.Image(
+ "b",
+ "http://earthtrends.wri.org/images/maps/4_m_citylights_lg.gif",
+ new OpenLayers.Bounds(-180, -88.759, 180, 88.759),
+ new OpenLayers.Size(580, 288)
+ );
+
+ var c = new OpenLayers.Layer.WMS(
+ "c",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ var d = new OpenLayers.Layer.Vector("d");
+
+ map.addLayers([a, b, c, d]);
+
+ var moveCount = 0;
+ a.moveTo = function() {
+ moveCount++;
+ OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+ };
+
+ map.zoomToMaxExtent();
+ t.eq(moveCount, 1, "map.moveTo moves the base layer only once");
+ t.eq(map.getCenter().toString(), "lon=0,lat=0", "a map with all overlays can have a center");
+
+ a.setVisibility(false);
+ var moveend = 0;
+ a.events.on({"moveend": function() { moveend++; }});
+ map.zoomToMaxExtent();
+ t.eq(moveCount, 1, "map.moveTo does not move the base layer if it is invisible");
+ t.eq(moveend, 0, "map.moveTo does not trigger \"moveend\" in the layer if the layer is invisible");
+ a.setVisibility(true);
+
+ // a, b, c, d
+ t.eq(map.baseLayer.name, "a", "base layer set to first layer added");
+
+ map.removeLayer(a);
+ // b, c, d
+ t.eq(map.baseLayer.name, "b", "if base layer is removed, lowest layer becomes base");
+
+ map.addLayer(a);
+ // b, c, d, a
+ t.eq(map.baseLayer.name, "b", "adding a new layer doesn't change base layer");
+
+ map.setLayerIndex(c, 1);
+ // b, d, c, a
+ t.eq(map.baseLayer.name, "b", "changing layer order above base doesn't mess with base");
+
+ map.setLayerIndex(d, 0);
+ // d, b, c, a
+ t.eq(map.baseLayer.name, "d", "changing layer order to 0 sets base layer");
+
+ map.raiseLayer(d, 1);
+ // b, d, c, a
+ t.eq(map.baseLayer.name, "b", "raising the base layer sets a new base layer");
+
+ map.raiseLayer(d, -1);
+ // d, b, c, a
+ t.eq(map.baseLayer.name, "d", "lowering a layer to lowest index sets as base");
+
+ // all this switching of base layer didn't muck with layer visibility
+ t.eq(a.visibility, true, "a is visible");
+ t.eq(b.visibility, true, "b is visible");
+ t.eq(c.visibility, true, "c is visible");
+ t.eq(d.visibility, true, "d is visible");
+
+ // test that map can have an invisible base layer
+ b.setVisibility(false);
+ map.setLayerIndex(b, 0);
+ t.eq(b.visibility, false, "changing layer order doesn't change visibility");
+
+
+ map.destroy();
+
+ // make sure setVisibility is called when adding a single layer to the map
+ map = new OpenLayers.Map({
+ div: "map", allOverlays: true
+ });
+ var count = 0;
+ var layer = new OpenLayers.Layer(null, {
+ visibility: true,
+ maxExtent: new OpenLayers.Bounds(-180, -90, 180, 90),
+ setVisibility: function() {
+ ++count;
+ OpenLayers.Layer.prototype.setVisibility.apply(this, arguments);
+ }
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.eq(count, 1, "setVisibility called when visibility is true in layer config");
+ t.eq(layer.div.style.display, "", "layer is visible.");
+
+ map.destroy();
+
+ }
+
+ function test_panTo(t) {
+
+ t.plan(6);
+
+ var log = [];
+ var map = new OpenLayers.Map("map", {
+ eventListeners: {
+ "movestart": function() {log.push("movestart");},
+ "move": function() {log.push("move");},
+ "moveend": function() {log.push("moveend");}
+ }
+ });
+ map.addLayer(
+ new OpenLayers.Layer(null, {isBaseLayer: true})
+ );
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ t.eq(log[log.length-1], "moveend", "moveend fired when map center is set");
+ log = [];
+
+ map.panTo(new OpenLayers.LonLat(1, 0));
+ t.eq(map.panTween.playing, true, "the map pan tween is playing before destroy");
+
+ t.delay_call(2, function() {
+ t.eq(log[0], "movestart", "panTo starts with movestart event");
+ t.eq(log[1], "move", "move events fired while panning");
+ t.eq(log[log.length-1], "moveend", "panTo finishes with moveend event");
+ map.destroy();
+ t.ok(!map.panTween || !map.panTween.playing, "the map pan tween is not playing after destroy");
+ });
+ }
+
+ function test_pan(t) {
+ t.plan(4);
+
+ var map = new OpenLayers.Map("map");
+ map.addLayer(
+ new OpenLayers.Layer(null, {isBaseLayer: true})
+ );
+ map.setCenter(new OpenLayers.LonLat(0, 0), 5);
+ var log = [];
+ map.events.on({
+ "movestart": function() {log.push("movestart");},
+ "move": function() {log.push("move");},
+ "moveend": function() {log.push("moveend");}
+ });
+
+ // simulate the drag sequence of the DragPan control;
+ map.pan(5,5, {animate: false, dragging: true});
+ map.pan(1,1, {animate: false, dragging: false});
+
+ t.eq(log[0], "movestart", "pan sequence starts with movestart");
+ t.eq(log[1], "move", "followed by move,");
+ t.eq(log[log.length-2], "move", "move again before we stop panning,");
+ t.eq(log[log.length-1], "moveend", "and moveend when we're done.");
+ }
+
+ function test_pan_no_anim_event_sequence(t) {
+ t.plan(4);
+
+ var log = [];
+ var map = new OpenLayers.Map("map");
+ map.addLayer(
+ new OpenLayers.Layer(null, {isBaseLayer: true})
+ );
+ map.setCenter(new OpenLayers.LonLat(0, 0), 5);
+ map.events.on({
+ "movestart": function() {
+ log.push("movestart");
+ },
+ "move": function() {
+ log.push("move");
+ },
+ "moveend": function() {
+ log.push("moveend");
+ }
+ });
+
+ map.pan(5,5, {animate: false});
+ t.eq(log.length, 3, "no more than 3 events happen.");
+ t.eq(log[0], "movestart", "pan sequence starts with movestart");
+ t.eq(log[1], "move", "followed by move,");
+ t.eq(log[2], "moveend", "and moveend when we're done.");
+
+ map.destroy();
+ }
+
+ // test if we can call updateSize before document.body is ready. updateOk
+ // is tested in the test_updateSize function below
+ var earlyMap = new OpenLayers.Map();
+ var updateOk;
+ try {
+ earlyMap.updateSize();
+ updateOk = true;
+ } catch(e) {}
+ earlyMap.destroy();
+ function test_updateSize(t) {
+ t.plan(3);
+
+ // checking updateSize from outside this test function (see above)
+ t.ok(updateOk, "updateSize works before document.body is ready");
+
+ var map, moveToCnt, size;
+
+ map = new OpenLayers.Map({div: "map"});
+ map.addLayer(new OpenLayers.Layer("layer", {isBaseLayer: true}));
+
+ map.moveTo = function() {
+ moveToCnt++;
+ OpenLayers.Map.prototype.moveTo.apply(this, arguments);
+ };
+
+ map.getCurrentSize = function() {
+ return size;
+ };
+
+ // map has no center
+ // 1 test
+ moveToCnt = 0;
+ size = new OpenLayers.Size(650, 350);
+ map.updateSize();
+ t.eq(moveToCnt, 0, "updateSize doesn't move the map if it doesn't have a center");
+
+ // map has a center
+ // 1 test
+ map.zoomToMaxExtent();
+ moveToCnt = 0;
+ size = new OpenLayers.Size(600, 300);
+ map.updateSize();
+ t.eq(moveToCnt, 1, "updateSize move the map if it has a center");
+
+ map.destroy();
+ }
+
+ function test_invisible_map(t) {
+ /**
+ * This test confirms that initializing a map using an element that is
+ * not currently displayed doesn't cause any trouble.
+ */
+ t.plan(1);
+
+ var map, msg = "initializing a map on an undisplayed element";
+ try {
+ map = new OpenLayers.Map("invisimap");
+ } catch (err) {
+ msg += ": " + err;
+ }
+ t.ok(!!map, msg);
+
+ if (map) {
+ map.destroy();
+ }
+ }
+
+ function test_layers_option(t) {
+
+ t.plan(3);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer()
+ ]
+ });
+
+ t.eq(map.layers.length, 1, "single layer from options added");
+
+ map.destroy();
+
+ map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer(null, {isBaseLayer: true}),
+ new OpenLayers.Layer(null, {isBaseLayer: false})
+ ]
+ });
+
+ t.eq(map.layers.length, 2, "multiple layers added from options");
+ t.ok(map.baseLayer, "map has a base layer");
+
+ map.destroy();
+
+ }
+
+ function test_center_option(t) {
+ t.plan(7);
+
+ var map, msg;
+
+
+ // try setting center without layers, this has no effect
+ var failed = false;
+ try {
+ map = new OpenLayers.Map({
+ div: "map",
+ center: new OpenLayers.LonLat(1, 2)
+ });
+ msg = "center option without layers has no effect";
+ } catch (err) {
+ failed = true;
+ msg = "center option without layers throws error";
+ }
+ t.ok(!failed, msg);
+
+ if (map) {
+ map.destroy();
+ }
+
+ var log = [];
+ var meth = OpenLayers.Layer.prototype.moveTo;
+ OpenLayers.Layer.prototype.moveTo = function() {
+ log.push(arguments);
+ meth.apply(this, arguments);
+ };
+
+ // set center without zoom
+ var center = new OpenLayers.LonLat(1, 2);
+ map = new OpenLayers.Map({
+ div: "map",
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ center: center
+ });
+
+ t.ok(center.equals(map.getCenter()), "map center set without zoom");
+ t.eq(log.length, 1, "moveTo called once");
+
+ map.destroy();
+ OpenLayers.Layer.prototype.moveTo = meth;
+
+ // set center and zoom
+ var zoom = 3;
+ map = new OpenLayers.Map({
+ div: "map",
+ layers: [new OpenLayers.Layer(null, {isBaseLayer: true})],
+ center: center,
+ zoom: zoom
+ });
+
+ t.ok(center.equals(map.getCenter()), "map center set with center and zoom");
+ t.eq(zoom, map.getZoom(), "map zoom set with center and zoom");
+
+ map.destroy();
+
+ // set center and zoom with all overlays
+ map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ layers: [new OpenLayers.Layer()],
+ center: center,
+ zoom: zoom
+ });
+
+ t.ok(center.equals(map.getCenter()), "map center set with all overlays");
+ t.eq(zoom, map.getZoom(), "map zoom set with all overlays");
+
+ map.destroy();
+
+ }
+ function test_pixel_lonlat(t) {
+
+ t.plan(4);
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ layers: [
+ new OpenLayers.Layer("name", {isBaseLayer:true})
+ ]
+ });
+ map.zoomToMaxExtent();
+ var px = map.getPixelFromLonLat(map.getLonLatFromPixel(new OpenLayers.Pixel(100, 100)));
+ t.eq(px.x, 100, "x is the same in and ot");
+ t.eq(px.y, 100, "y is the same in and out");
+ var ll = map.getLonLatFromPixel(map.getPixelFromLonLat(new OpenLayers.LonLat(100, 100)));
+ t.ok((ll.lon > (100 -map.getResolution()) && (ll.lon < (100 + map.getResolution()))), "lon is the same in and ot");
+ t.ok((ll.lat > (100 -map.getResolution()) && (ll.lat < (100 + map.getResolution()))), "lat is the same in and ot");
+ map.destroy();
+ }
+
+ function test_moveByPx(t) {
+ t.plan(14);
+
+ var moved;
+ var Layer = OpenLayers.Class(OpenLayers.Layer, {
+ moveByPx: function(dx, dy) {
+ moved[this.name] = true;
+ }
+ });
+
+ var map = new OpenLayers.Map({
+ div: 'map',
+ maxExtent: new OpenLayers.Bounds(-50, -50, 50, 50),
+ restrictedExtent: new OpenLayers.Bounds(-10, -10, 10, 10),
+ layers: [
+ new Layer('base',
+ {isBaseLayer: true}),
+ new Layer('outofrange',
+ {isBaseLayer: false, minResolution:2})
+ ]
+ });
+ var log = [];
+ map.applyTransform = function(x, y, scale) {
+ log.push([x || map.layerContainerOriginPx.x, y || map.layerContainerOriginPx.y, scale]);
+ OpenLayers.Map.prototype.applyTransform.apply(this, arguments);
+ };
+
+ moved = {};
+ map.zoomToExtent(new OpenLayers.Bounds(-1, -1, 1, 1));
+
+ // check initial state
+ t.eq(log[0][0], 0,
+ '[initial state] layer container left correct');
+ t.eq(log[0][1], 0,
+ '[initial state] layer container top correct');
+ t.eq(moved['base'], undefined,
+ '[initial state] base layer not moved');
+ t.eq(moved['outofrange'], undefined,
+ '[initial state] out-of-range layer not moved');
+
+ // move to a valid position
+ moved = {};
+ map.moveByPx(-455, 455);
+ t.eq(log[1][0], 455,
+ '[valid position] layer container left correct');
+ t.eq(log[1][1], -455,
+ '[valid position] layer container top correct');
+ t.eq(moved['base'], true,
+ '[valid position] base layer moved');
+ t.eq(moved['outofrange'], undefined,
+ '[valid position] out-of-range layer not moved');
+
+ // move outside the max extent
+ moved = {};
+ map.moveByPx(-4500, 4500);
+ t.eq(log.length, 2,
+ '[outside max extent] layer container offset unchanged');
+ t.eq(moved['base'], undefined,
+ '[outside max extent] base layer not moved');
+ t.eq(moved['outofrange'], undefined,
+ '[outside max extent] out-of-range layer not moved');
+
+ // move outside the restricted extent
+ moved = {};
+ map.moveByPx(-500, 500);
+ t.eq(log.length, 2,
+ '[outside restricted extent] layer container offset unchanged');
+ t.eq(moved['base'], undefined,
+ '[outside restricted extent] base layer not moved');
+ t.eq(moved['outofrange'], undefined,
+ '[outside restricted extent] out-of-range layer not moved');
+
+
+ map.destroy();
+ }
+
+ // test for http://trac.osgeo.org/openlayers/ticket/3388
+ function test_moveByPx_restrictedExtent(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map({
+ div: 'map',
+ restrictedExtent: new OpenLayers.Bounds(-22.5,-11.25,22.5,11.25),
+ layers: [
+ new OpenLayers.Layer('name', {isBaseLayer: true})
+ ]
+ });
+
+ map.zoomToExtent(new OpenLayers.Bounds(-11.25, 0, 11.25, 11.25));
+
+ var log = [];
+ map.applyTransform = function(x, y, scale) {
+ log.push([x || map.layerContainerOriginPx.x, y || map.layerContainerOriginPx.y, scale]);
+ OpenLayers.Map.prototype.applyTransform.apply(this, arguments);
+ };
+
+ map.moveByPx(-10, -10);
+ t.eq(log[0][0], 10, 'layer container left correct');
+ t.eq(log[0][1], 0, 'layer container top correct');
+ }
+
+ function test_applyTransform(t) {
+ t.plan(10);
+ var origStylePrefix = OpenLayers.Util.vendorPrefix.style;
+ OpenLayers.Util.vendorPrefix.style =
+ OpenLayers.Util.vendorPrefix.css =
+ function(key) { return 'transform'; };
+
+ var map = new OpenLayers.Map('map');
+ map.layerContainerDiv = {style: {}};
+ delete map.applyTransform.transform;
+ delete map.applyTransform.template;
+ var origGetStyle = OpenLayers.Element.getStyle;
+ OpenLayers.Element.getStyle = function() { return 'foo'; }
+ map.applyTransform(1, 2, 3);
+ OpenLayers.Element.getStyle = origGetStyle;
+ t.eq(map.layerContainerDiv.style.transform, 'translate3d(1px,2px,0) scale3d(3,3,1)', '3d transform and scale used when available');
+
+ delete map.applyTransform.transform;
+ delete map.applyTransform.template;
+ var origIndexOf = String.prototype.indexOf;
+ String.prototype.indexOf = function() { return -1; };
+ map.layerContainerOriginPx = {x: -3, y: 2};
+ map.applyTransform(1, 2, 3);
+ String.prototype.indexOf = origIndexOf;
+ t.eq(map.layerContainerDiv.style.transform, 'translate(4px,0px) scale(3,3)', '2d translate and scale correct');
+ t.eq(map.layerContainerDiv.style.left, '-3px', 'container origin x set as style.left');
+ t.eq(map.layerContainerDiv.style.top, '2px', 'container origin y set as style.top');
+ map.applyTransform(1, 2);
+ t.ok(!map.layerContainerDiv.style.transform, 'no transform set when no transform needed');
+ t.eq(map.layerContainerDiv.style.left, '1px', 'style.left correct when no transform needed');
+ t.eq(map.layerContainerDiv.style.top, '2px', 'style.top correct when no transform needed');
+
+ map.applyTransform.transform = null;
+ map.applyTransform(4, 5, 6);
+ t.eq(map.layerContainerDiv.style.left, '4px', 'style.left set when transform not available')
+ t.eq(map.layerContainerDiv.style.top, '5px', 'style.top set when transform not available')
+ t.ok(!map.layerContainerDiv.style.transform, 'no transform set, because not supported');
+
+ map.destroy();
+ delete map.applyTransform.transform;
+ delete map.applyTransform.template;
+ OpenLayers.Util.vendorPrefix.style = origStylePrefix;
+ }
+
+ function test_options(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map');
+ t.eq(map.options, {}, 'map.options is empty with no options');
+ map.destroy();
+
+ var options = {
+ resolutions: [1,2,3,5],
+ projection: "EPSG:4326",
+ units: 'm'
+ };
+ var map = new OpenLayers.Map('map', options);
+ t.eq(map.options, options, 'map.options is a copy of the constructor option');
+ map.destroy();
+ }
+
+ function test_adjustZoom(t) {
+ t.plan(5);
+ var map = new OpenLayers.Map({
+ div: 'map',
+ layers: [
+ new OpenLayers.Layer('name', {
+ isBaseLayer: true,
+ wrapDateLine: true
+ })
+ ]
+ });
+ map.zoomToMaxExtent();
+ t.ok(map.getResolution() <= map.getMaxExtent().getWidth() / map.getSize().w, "wrapDateLine map not wider than world");
+
+ t.eq(map.adjustZoom(9), 9, "valid zoom maintained");
+ t.eq(map.adjustZoom(1), 2, "zoom adjusted to not exceed world width");
+
+ map.fractionalZoom = true;
+ t.eq(map.adjustZoom(1).toPrecision(3), "1.29", "zoom adjusted to match world width");
+
+ map.moveTo([16, 48], 0);
+ t.eq(map.getCenter().toShortString(), "0, 0", "no panning when moveTo is called with invalid zoom");
+ }
+
+ function test_correctCenterAtZoomLevel0(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map({
+ div: 'map',
+ maxExtent: new OpenLayers.Bounds(-30, 48.00, 3.50, 64.00),
+ restrictedExtent: new OpenLayers.Bounds(-30, 48.00, 3.50, 64.00),
+ projection: "EPSG:4258",
+ units: "degrees",
+ layers: [
+ new OpenLayers.Layer('name', {
+ isBaseLayer: true
+ })
+ ]
+ });
+ map.setCenter(new OpenLayers.LonLat(-1.3, 50.8), 4);
+ map.moveTo(null, 0);
+ var center = map.getCenter();
+ t.ok(center.equals(new OpenLayers.LonLat(-13.25, 56)), "Center is correct and not equal to maxExtent's center");
+ }
+
+ function test_getZoomTargetCenter(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map({
+ div: 'map',
+ layers: [
+ new OpenLayers.Layer('', {isBaseLayer: true})
+ ],
+ center: [0, 0],
+ zoom: 1
+ });
+
+ var ll = map.getZoomTargetCenter({x: 44, y: 22}, map.getMaxResolution());
+
+ t.eq(ll.toShortString(), "180, -90", "getZoomTargetCenter works.");
+
+ map.destroy();
+ }
+
+ function test_autoUpdateSize(t) {
+ t.plan(1);
+ OpenLayers.Event.unloadCache();
+ var resizeListener = false;
+ var map = new OpenLayers.Map({
+ autoUpdateSize: false,
+ div: 'map',
+ layers: [
+ new OpenLayers.Layer('name', {
+ isBaseLayer: true,
+ wrapDateLine: true
+ })
+ ]
+ });
+ map.setCenter(new OpenLayers.LonLat(-1.3, 50.8), 4);
+ for (var key in OpenLayers.Event.observers) {
+ var obj = OpenLayers.Event.observers[key];
+ for (var i=0, ii=obj.length; i<ii; ++i) {
+ var listener = obj[i];
+ if (listener.name === 'resize' && listener.element === window) {
+ resizeListener = true;
+ }
+ }
+ }
+ t.eq(resizeListener, map.autoUpdateSize, "resize listener not registered when autoUpdateSize is false");
+ map.destroy();
+ }
+
+ function test_tileManager(t) {
+ t.plan(3);
+ var map = new OpenLayers.Map('map');
+ t.ok(map.tileManager instanceof OpenLayers.TileManager, "Default tileManager created");
+ map.destroy();
+ map = new OpenLayers.Map('map', {tileManager: null});
+ t.ok(map.tileManager === null, "No tileManager created");
+ map.destroy();
+ var options = {cacheSize: 512};
+ map = new OpenLayers.Map('map', {tileManager: options});
+ t.eq(map.tileManager.cacheSize, 512, "cacheSize taken from options");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 600px; height: 300px;"/>
+ <div style="display: none;"><div id="invisimap"></div></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Marker.html b/misc/openlayers/tests/Marker.html
new file mode 100644
index 0000000..fa9b598
--- /dev/null
+++ b/misc/openlayers/tests/Marker.html
@@ -0,0 +1,163 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var marker;
+
+ function test_Marker_constructor (t) {
+ t.plan( 4 );
+ var ll = new OpenLayers.LonLat(2,1);
+ marker = new OpenLayers.Marker(ll,new OpenLayers.Icon());
+ t.ok( marker instanceof OpenLayers.Marker, "new OpenLayers.Marker returns Marker object" );
+ t.ok( marker.icon instanceof OpenLayers.Icon, "new marker.Icon returns Icon object" );
+ t.ok( marker.lonlat instanceof OpenLayers.LonLat, "new marker.lonlat returns LonLat object" );
+ t.ok( marker.lonlat.equals(ll), "marker.lonlat returns correct" );
+ }
+
+ function test_Marker_onScreen(t) {
+ t.plan( 2 );
+
+ var map = new OpenLayers.Map("map");
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url);
+
+ map.addLayer(layer);
+
+ mlayer = new OpenLayers.Layer.Markers('Test Layer');
+ map.addLayer(mlayer);
+
+ map.zoomToExtent(new OpenLayers.Bounds(-50,-50,50,50));
+
+ //onscreen marker
+ var ll = new OpenLayers.LonLat(0,0);
+ var marker = new OpenLayers.Marker(ll);
+ mlayer.addMarker(marker);
+
+ t.ok( marker.onScreen(), "marker knows it's onscreen" );
+
+ //offscreen marker
+ var ll = new OpenLayers.LonLat(100,100);
+ var marker2 = new OpenLayers.Marker(ll);
+ mlayer.addMarker(marker2);
+
+ t.ok( !marker2.onScreen(), "marker knows it's offscreen" );
+ map.destroy();
+ }
+
+ function test_Marker_setOpacity(t) {
+ t.plan( 2 );
+
+ var map = new OpenLayers.Map("map");
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url);
+
+ map.addLayer(layer);
+
+ mlayer = new OpenLayers.Layer.Markers('Test Layer');
+ map.addLayer(mlayer);
+
+ map.zoomToExtent(new OpenLayers.Bounds(-50,-50,50,50));
+
+ //onscreen marker
+ var ll = new OpenLayers.LonLat(0,0);
+ var marker = new OpenLayers.Marker(ll);
+ mlayer.addMarker(marker);
+
+ t.ok(!marker.icon.imageDiv.style.opacity, "default marker has no opacity");
+
+ marker.setOpacity(0.5);
+
+ t.eq(parseFloat(marker.icon.imageDiv.style.opacity), 0.5, "marker.setOpacity() works");
+ map.destroy();
+ }
+
+ function test_Marker_setUrl(t) {
+ t.plan( 2 );
+
+ var map = new OpenLayers.Map("map");
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url);
+
+ map.addLayer(layer);
+
+ mlayer = new OpenLayers.Layer.Markers('Test Layer');
+ map.addLayer(mlayer);
+
+ map.zoomToExtent(new OpenLayers.Bounds(-50,-50,50,50));
+
+ //onscreen marker
+ var ll = new OpenLayers.LonLat(0,0);
+ var marker = new OpenLayers.Marker(ll);
+ mlayer.addMarker(marker);
+
+ t.ok(OpenLayers.String.contains(marker.icon.imageDiv.firstChild.src, "img/marker.png"), "Marker.png is default URL");
+
+ marker.setUrl("http://example.com/broken.png");
+ t.eq(marker.icon.imageDiv.firstChild.src, "http://example.com/broken.png", "image source changes correctly.");
+
+ map.destroy();
+ }
+
+ function test_Marker_moveTo(t) {
+ t.plan( 6 );
+
+ var map = new OpenLayers.Map("map");
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS(name, url);
+
+ map.addLayer(layer);
+
+ mlayer = new OpenLayers.Layer.Markers('Test Layer');
+ map.addLayer(mlayer);
+
+ map.zoomToExtent(new OpenLayers.Bounds(-50,-50,50,50));
+
+ //onscreen marker
+ var ll = new OpenLayers.LonLat(0,0);
+ var marker = new OpenLayers.Marker(ll);
+ mlayer.addMarker(marker);
+
+ t.eq(marker.lonlat.lon, 0, "marker lon okay");
+ t.eq(marker.lonlat.lat, 0, "marker lat okay");
+
+ marker.moveTo(new OpenLayers.Pixel(250,275));
+ t.eq(marker.lonlat.lon, 0, "marker lon no change");
+ t.eq(marker.lonlat.lat, 0, "marker lat no change");
+
+ marker.moveTo(new OpenLayers.Pixel(0,0));
+ t.eq(marker.lonlat.lon, map.getExtent().left, "on left edge of map");
+ t.eq(marker.lonlat.lat, map.getExtent().top, "on top edge of map");
+ map.destroy();
+ }
+
+ function test_Marker_isDrawn(t) {
+ t.plan(3);
+
+ var marker = {};
+
+ //no icon
+ var drawn = OpenLayers.Marker.prototype.isDrawn.apply(marker, []);
+ t.ok(!drawn, "marker with no icon not drawn");
+
+ //not drawn icon
+ marker.icon = { isDrawn: function() { return false; } };
+ drawn = OpenLayers.Marker.prototype.isDrawn.apply(marker, []);
+ t.ok(!drawn, "marker with not drawn icon not drawn");
+
+ //drawn icon
+ marker.icon.isDrawn = function() { return true; };
+ drawn = OpenLayers.Marker.prototype.isDrawn.apply(marker, []);
+ t.ok(drawn, "marker with drawn icon drawn");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Marker/Box.html b/misc/openlayers/tests/Marker/Box.html
new file mode 100644
index 0000000..806336e
--- /dev/null
+++ b/misc/openlayers/tests/Marker/Box.html
@@ -0,0 +1,183 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var box;
+
+ function test_Box_constructor (t) {
+ t.plan( 7 );
+
+ OpenLayers.Marker.Box.prototype._setBorder =
+ OpenLayers.Marker.Box.prototype.setBorder;
+ OpenLayers.Marker.Box.prototype.setBorder = function (x,y) {
+ g_Color = x;
+ g_Width = y;
+ };
+
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var borderColor = "blue";
+ var borderWidth = 55;
+
+
+ g_Color = g_Width = null;
+ box = new OpenLayers.Marker.Box(bounds, borderColor, borderWidth);
+
+ t.ok( box instanceof OpenLayers.Marker.Box, "new OpenLayers.Marker.Box returns Box object" );
+ t.ok( box.bounds.equals(bounds), "bounds object correctly set");
+ t.ok( box.div != null, "div created");
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq( box.div.style[prop], "hidden", "div style overflow hidden");
+ t.ok( box.events != null, "events object created");
+ t.eq( g_Color, borderColor, "setBorder called with correct border color");
+ t.eq( g_Width, borderWidth, "setBorder called with correct border width");
+
+
+ OpenLayers.Marker.Box.prototype.setBorder =
+ OpenLayers.Marker.Box.prototype._setBorder;
+ }
+
+
+ function test_Box_setBorder(t) {
+ t.plan( 2 );
+
+ var box = {
+ div: {
+ style: {}
+ }
+ };
+
+ //defaults
+ var args = [];
+ OpenLayers.Marker.Box.prototype.setBorder.apply(box, args);
+ t.eq(box.div.style.border, "2px solid red", "style correctly set with no good values (defaults work)");
+
+ //good vals
+ var borderColor = "blue";
+ var borderWidth = 55;
+
+ args = [borderColor, borderWidth];
+ OpenLayers.Marker.Box.prototype.setBorder.apply(box, args);
+ t.eq(box.div.style.border, borderWidth + "px solid " + borderColor, "style correctly set with both good values");
+
+ }
+ function test_Box_draw(t) {
+ t.plan( 5 );
+
+ OpenLayers.Util._modifyDOMElement =
+ OpenLayers.Util.modifyDOMElement;
+ OpenLayers.Util.modifyDOMElement =
+ function (element, id, px, sz) {
+ g_Element = element;
+ g_Id = id;
+ g_Px = px;
+ g_Sz = sz;
+ };
+
+ var box = {
+ div: {}
+ };
+
+
+ var px = {};
+ var sz = {};
+ var args = [px, sz];
+
+ g_Element = g_Id = g_Px = g_Sz = null;
+ var retVal = OpenLayers.Marker.Box.prototype.draw.apply(box, args);
+
+ t.eq(g_Element, box.div, "modifyDOMElement passes box's div for element");
+ t.eq(g_Id, null, "modifyDOMElement passes null for id");
+ t.eq(g_Px, px, "modifyDOMElement passes new px value for px");
+ t.eq(g_Sz, sz, "modifyDOMElement passes new sz value for sz");
+ t.ok(retVal == box.div, "draw returns box's div");
+
+ OpenLayers.Util.modifyDOMElement =
+ OpenLayers.Util._modifyDOMElement;
+
+ }
+
+ function test_Box_onScreen(t) {
+ t.plan( 2 );
+
+ var map = new OpenLayers.Map("map");
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.WMS("WMS Layer", url);
+
+ map.addLayer(layer);
+
+ mlayer = new OpenLayers.Layer.Boxes('Test Layer');
+ map.addLayer(mlayer);
+
+ map.zoomToExtent(new OpenLayers.Bounds(-50,-50,50,50));
+
+ //onscreen box
+ var bounds = new OpenLayers.Bounds(-1,-1,1,1);
+ var box = new OpenLayers.Marker.Box(bounds);
+ mlayer.addMarker(box);
+
+ t.ok( box.onScreen(), "box knows it's onscreen" );
+
+ //offscreen box
+ var bounds = new OpenLayers.Bounds(100,100,150,150);
+ var box2 = new OpenLayers.Marker.Box(bounds);
+ mlayer.addMarker(box2);
+
+ t.ok( !box2.onScreen(), "box knows it's offscreen" );
+ map.destroy();
+ }
+
+ function test_Box_display(t) {
+ t.plan( 2 );
+
+ var box = {
+ div: {
+ style: {}
+ }
+ };
+
+ //display(true)
+ var args = [true];
+ OpenLayers.Marker.Box.prototype.display.apply(box, args);
+ t.eq(box.div.style.display, "", "style.display correctly set to '' when display(true)");
+
+ //display(false)
+ var args = [false];
+ OpenLayers.Marker.Box.prototype.display.apply(box, args);
+ t.eq(box.div.style.display, "none", "style.display correctly set to 'none' when display(false)");
+ }
+
+ function test_Box_destroy(t) {
+ t.plan(3);
+
+ OpenLayers.Marker.prototype._destroy =
+ OpenLayers.Marker.prototype.destroy;
+ OpenLayers.Marker.prototype.destroy = function() {
+ g_Destroy = true;
+ }
+
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var borderColor = "blue";
+ var borderWidth = 55;
+
+ g_Destroy = null;
+ box = new OpenLayers.Marker.Box(bounds, borderColor, borderWidth);
+ box.destroy();
+
+ t.eq(box.bounds, null, "bounds nullified");
+ t.eq(box.div, null, "div nullified");
+ t.ok(g_Destroy == true, "OpenLayers.Marker.destroy() called");
+
+
+ OpenLayers.Marker.prototype.destroy =
+ OpenLayers.Marker.prototype._destroy;
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:550px"></div>
+</body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/OLLoader.js b/misc/openlayers/tests/OLLoader.js
new file mode 100644
index 0000000..a2311c7
--- /dev/null
+++ b/misc/openlayers/tests/OLLoader.js
@@ -0,0 +1,26 @@
+// Adding a mode parameter with "build" as value in the run-tests.html will
+// make usage of the build version of the library.
+// get the OLLoader.js script location
+(function() {
+ var r = new RegExp("(^|(.*?\\/))(" + "OLLoader.js" + ")(\\?|$)"),
+ s = document.getElementsByTagName('script'),
+ src, m, l = "";
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ var m = src.match(r);
+ if(m) {
+ l = m[1];
+ break;
+ }
+ }
+ }
+
+ var regex = new RegExp( "[\\?&]mode=([^&#]*)" );
+ var href = window.parent.location.href;
+ var results = regex.exec( href );
+ l += (results && results[1] == 'build') ?
+ "../build/OpenLayers.js" : "../lib/OpenLayers.js";
+ scriptTag = "<script src='" + l + "'></script>";
+ document.write(scriptTag);
+})();
diff --git a/misc/openlayers/tests/OpenLayers1.html b/misc/openlayers/tests/OpenLayers1.html
new file mode 100644
index 0000000..1d96be3
--- /dev/null
+++ b/misc/openlayers/tests/OpenLayers1.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(3);
+
+ t.eq(OpenLayers._getScriptLocation(), "../", "Script location correctly detected.");
+
+ t.ok(OpenLayers.ImgPath !== undefined, "An ImgPath property exists.");
+
+ t.eq(OpenLayers.ImgPath, '', "The default for OpenLayers.ImgPath is the empty string.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/OpenLayers2.html b/misc/openlayers/tests/OpenLayers2.html
new file mode 100644
index 0000000..fbdb043
--- /dev/null
+++ b/misc/openlayers/tests/OpenLayers2.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <script src="bogus/1/OpenLayers.js-foo"></script>
+ <script src="bogus/2/foo-OpenLayers.js"></script>
+ <script src="../lib/OpenLayers.js?foo"></script>
+ <script src="bogus/3/after-OpenLayers.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+
+ var script = document.getElementById("script");
+
+ t.eq(OpenLayers._getScriptLocation(), "../", "Script location with search string correctly detected, and not fooled by other scripts.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/OpenLayers3.html b/misc/openlayers/tests/OpenLayers3.html
new file mode 100644
index 0000000..c4cbb80
--- /dev/null
+++ b/misc/openlayers/tests/OpenLayers3.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+ <script>
+ var OpenLayers = {singleFile: true};
+ </script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+
+ var script = document.getElementById("script");
+
+ t.eq(OpenLayers._getScriptLocation(), "../lib/", "Script location for single file build correctly detected.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/OpenLayers4.html b/misc/openlayers/tests/OpenLayers4.html
new file mode 100644
index 0000000..7c9012c
--- /dev/null
+++ b/misc/openlayers/tests/OpenLayers4.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+ <script type="text/javascript">
+ OpenLayers = {singleFile: true}; // just to make the test run faster
+ document.write('<scr'+'ipt src="../lib/OpenLayers.js"></scr'+'ipt>');
+ document.write('<scr'+'ipt src="bogus/foo-/OpenLayers.js"></scr'+'ipt>');
+ </script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+ t.eq(OpenLayers._getScriptLocation(), "../lib/",
+ "Script location correctly detected, and not fooled by other scripts.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/OpenLayersJsFiles.html b/misc/openlayers/tests/OpenLayersJsFiles.html
new file mode 100644
index 0000000..8dff0ec
--- /dev/null
+++ b/misc/openlayers/tests/OpenLayersJsFiles.html
@@ -0,0 +1,27 @@
+<html>
+<head>
+ <script type="text/javascript">
+ window.OpenLayers = new Array(
+ "OpenLayers/Util.js",
+ "OpenLayers/BaseTypes.js"
+ );
+ </script>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+ var s = document.getElementsByTagName("script");
+ var src, count = 0;
+ for(var i=0, len=s.length; i<len; i++) {
+ src = s[i].getAttribute('src');
+ if(src) {
+ count++;
+ }
+ }
+ t.eq(count, 3, "Three OpenLayers scripts loaded.");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Popup.html b/misc/openlayers/tests/Popup.html
new file mode 100644
index 0000000..d2a9685
--- /dev/null
+++ b/misc/openlayers/tests/Popup.html
@@ -0,0 +1,219 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var popup;
+
+ function test_Popup_default_constructor(t) {
+ t.plan( 8 );
+
+ var size = new OpenLayers.Size(OpenLayers.Popup.WIDTH,
+ OpenLayers.Popup.HEIGHT);
+ popup = new OpenLayers.Popup();
+
+ t.ok( popup instanceof OpenLayers.Popup, "new OpenLayers.Popup returns Popup object" );
+ t.ok(OpenLayers.String.startsWith(popup.id, "OpenLayers_Popup"),
+ "valid default popupid");
+ var firstID = popup.id;
+ t.ok(popup.contentSize.equals(size), "good default popup.size");
+ t.eq(popup.contentHTML, null, "good default popup.contentHTML");
+ t.eq(popup.backgroundColor, OpenLayers.Popup.COLOR, "good default popup.backgroundColor");
+ t.eq(popup.opacity, OpenLayers.Popup.OPACITY, "good default popup.opacity");
+ t.eq(popup.border, OpenLayers.Popup.BORDER, "good default popup.border");
+
+
+ popup = new OpenLayers.Popup();
+ var newID = popup.id;
+ t.ok(newID != firstID, "default id generator creating unique ids");
+ }
+
+ function test_Popup_constructor (t) {
+ t.plan(9);
+
+ var id = "chicken";
+ var w = 500;
+ var h = 400;
+ var sz = new OpenLayers.Size(w,h);
+ var lon = 5;
+ var lat = 40;
+ var ll = new OpenLayers.LonLat(lon, lat);
+ var content = "foo";
+ var closePopupCallback = function(e) {
+ //this should get triggered by the "observer.observer();" call below
+ t.ok(true, "closePopupCallback called")
+ };
+
+ popup = new OpenLayers.Popup(id,
+ ll,
+ sz,
+ content,
+ true,
+ closePopupCallback);
+
+ t.ok( popup instanceof OpenLayers.Popup, "new OpenLayers.Popup returns Popup object" );
+ t.eq(popup.id, id, "popup.id set correctly");
+ t.ok(popup.lonlat.equals(ll), "popup.lonlat set correctly");
+ t.ok(popup.contentSize.equals(sz), "popup.size set correctly");
+ t.eq(popup.contentHTML, content, "contentHTML porpoerty of set correctly");
+
+ // test that a browser event is registered on click on popup closebox
+ var closeImgDiv = popup.groupDiv.childNodes[1];
+ var cacheID = closeImgDiv._eventCacheID;
+ for (var i = 0; i < OpenLayers.Event.observers[cacheID].length; i++) {
+ var observer = OpenLayers.Event.observers[cacheID][i];
+ if (observer.element == closeImgDiv) {
+ if (observer.name == "click") {
+ t.ok(true, "A click event was registered for the close box element");
+ //call the registered observer to make sure it's the right one
+ observer.observer();
+ } else if (observer.name == "touchend") {
+ t.ok(true, "A touchend event was registered for the close box element");
+ //call the registered observer to make sure it's the right one
+ observer.observer();
+ } else {
+ t.fail("A " + observer.name + " event was registered for the close box element");
+ }
+ }
+ }
+ }
+
+ function test_Popup_updatePosition(t) {
+ t.plan(1)
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer('name', {'isBaseLayer':true}));
+ map.zoomToMaxExtent();
+ var popup = new OpenLayers.Popup('id');
+ map.addPopup(popup);
+ map.getLayerPxFromLonLat = function () { return null; }
+ popup.moveTo=function() { t.fail("Shouldnt' call moveTo if layerpx is null"); }
+ popup.lonlat = true;
+ popup.updatePosition();
+ t.ok(true, "update position doesn't fail when getLayerPxFromLonLat fails.");
+ map.destroy();
+ }
+ function test_Popup_keepInMap(t) {
+
+ var bn = OpenLayers.BROWSER_NAME;
+ OpenLayers.BROWSER_NAME = "mock";
+ t.plan(3);
+ var map = new OpenLayers.Map("map");
+ map.addLayer(new OpenLayers.Layer("", {isBaseLayer: true}));
+ map.zoomToMaxExtent();
+ var longString = "<div style='width: 200px; height: 200px'>Abc def</div>";
+ popup = new OpenLayers.Popup("chicken",
+ new OpenLayers.LonLat(90, 60),
+ new OpenLayers.Size(100,100),
+ longString,
+ null, true);
+ popup.panMapIfOutOfView = false;
+ popup.keepInMap = true;
+ map.addPopup(popup);
+ var safeSize = popup.getSafeContentSize(new OpenLayers.Size(1000,1000));
+ popup = new OpenLayers.Popup("chicken",
+ new OpenLayers.LonLat(90, 60),
+ new OpenLayers.Size(100,100),
+ longString,
+ null, true);
+ popup.panMapIfOutOfView = true;
+ popup.keepInMap = true;
+ map.addPopup(popup);
+ var safeSizePanKeep = popup.getSafeContentSize(new OpenLayers.Size(1000,1000));
+ popup.keepInMap = false;
+ map.addPopup(popup);
+ map.setCenter(-180, -90);
+ var safeSizePan = popup.getSafeContentSize(new OpenLayers.Size(1000,1000));
+ t.ok(safeSizePan.equals(safeSizePanKeep), "Panning means that all sizes are equal");
+ t.ok(safeSize.w < safeSizePan.w, "Width of non-panning is less");
+ t.ok(safeSize.h < safeSizePan.h, "Height of non-panning is less");
+ OpenLayers.BROWSER_NAME = bn;
+ }
+ function test_Popup_draw(t) {
+ t.plan( 15 );
+
+ var id = "chicken";
+ var x = 50;
+ var y = 100;
+ var w = 500;
+ var h = 400;
+ var content = "charlie";
+ var color = "red";
+ var hexColor = "#ff0000";
+ var opacity = 0.5;
+ var border = "1px solid";
+ map1 = new OpenLayers.Map("map");
+ popup = new OpenLayers.Popup(id);
+ popup.setSize(new OpenLayers.Size(w, h));
+ popup.setContentHTML(content);
+ popup.setBackgroundColor(color);
+ popup.setOpacity(opacity);
+ popup.setBorder(border);
+ map1.addPopup(popup);
+ popup.moveTo(new OpenLayers.Pixel(x, y));
+
+ t.eq(popup.div.id, id, "popup.div.id set correctly");
+ t.eq(popup.div.style.left, x + "px", "left position of popup.div set correctly");
+ t.eq(popup.div.style.top, y + "px", "top position of popup.div set correctly");
+
+ var contentDiv = popup.div.childNodes[0].childNodes[0];
+
+ t.eq(contentDiv.className, "olPopupContent", "correct content div className");
+ t.eq(contentDiv.id, "chicken_contentDiv", "correct content div id");
+ t.eq(contentDiv.style.position, "relative", "correct content div position");
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq(contentDiv.style[prop], "", "correct content div overflow");
+ t.eq(contentDiv.innerHTML, content, "correct content div content");
+
+ var bColor = popup.div.style.backgroundColor;
+ var goodColor = ( (bColor == color) || (bColor == hexColor));
+ t.ok(goodColor, "good default popup.backgroundColor");
+ if (navigator.appName.indexOf("Microsoft") == -1 || new RegExp(/msie 10/).test(navigator.userAgent.toLowerCase())) {
+ t.eq(parseFloat(popup.div.style.opacity), opacity, "good default popup.opacity");
+ } else {
+ t.eq(popup.div.style.filter, "alpha(opacity=" + opacity*100 + ")", "good default popup.opacity");
+ }
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ var s = border.split(' ');
+ t.ok(popup.div.style.borderTopWidth == s[0] && popup.div.style.borderTopStyle == s[1], "good default popup.border")
+ } else {
+ t.ok(popup.div.style.border.indexOf(border) != -1, "good default popup.border");
+ }
+
+ x += 50;
+ popup.moveTo(new OpenLayers.Pixel(x, y));
+ t.eq(popup.div.style.left, x + "px", "moveTo updates left position of popup.div correctly");
+ t.eq(popup.div.style.top, y + "px", "moveTo updates top position of popup.div correctly");
+
+
+ //closeOnMove
+ var checkMapEvent = function(map, popup) {
+ var startListeners = map.events.listeners['movestart'];
+ if (startListeners) {
+ for (var i = 0; i < startListeners.length; i++) {
+ var listener = startListeners[i];
+ if ((listener.obj == popup) && (listener.func == popup.hide)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ };
+ var registered = checkMapEvent(map1, popup);
+ t.ok(!registered, "when not 'closeOnMove', correctly not registered hide() on map's movestart.")
+
+ var popup2 = new OpenLayers.Popup('test');
+ popup2.closeOnMove = true;
+ map1.addPopup(popup2);
+
+ registered = checkMapEvent(map1, popup2);
+ t.ok(registered, "when 'closeOnMove', correctly registered hide() on map's movestart.")
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Popup/Anchored.html b/misc/openlayers/tests/Popup/Anchored.html
new file mode 100644
index 0000000..3197e84
--- /dev/null
+++ b/misc/openlayers/tests/Popup/Anchored.html
@@ -0,0 +1,37 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var popup;
+
+ function test_Popup_Anchored_default_constructor(t) {
+ t.plan( 4 );
+
+ popup = new OpenLayers.Popup.Anchored();
+
+ t.ok( popup instanceof OpenLayers.Popup.Anchored, "new OpenLayers.Popup.Anchored returns Popup.Anchored object" );
+ t.ok(OpenLayers.String.startsWith(popup.id, "OpenLayers_Popup_Anchored"), "valid default popupid");
+ var firstID = popup.id;
+ t.eq(popup.contentHTML, null, "good default popup.contentHTML");
+
+
+ popup = new OpenLayers.Popup.Anchored();
+ var newID = popup.id;
+ t.ok(newID != firstID, "default id generator creating unique ids");
+ }
+ function test_Popup_Anchored_updateRelPos(t) {
+ t.plan(1);
+ var popup = new OpenLayers.Popup.Anchored();
+ popup.calculateNewPx = function () {}
+ popup.calculateRelativePosition = function() {
+ t.ok(true, "update relative position is called on moveTo");
+ }
+ popup.moveTo(new OpenLayers.Pixel(0,0));
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Popup/FramedCloud.html b/misc/openlayers/tests/Popup/FramedCloud.html
new file mode 100644
index 0000000..7da86e3
--- /dev/null
+++ b/misc/openlayers/tests/Popup/FramedCloud.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Popup_FramedCloud_setHTML(t) {
+ t.plan(1);
+ popup = new OpenLayers.Popup.FramedCloud();
+ popup.setContentHTML("<p></p>");
+ t.ok("setHTML on popup not yet added to map doesn't fail");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Projection.html b/misc/openlayers/tests/Projection.html
new file mode 100644
index 0000000..5864be5
--- /dev/null
+++ b/misc/openlayers/tests/Projection.html
@@ -0,0 +1,87 @@
+<html>
+ <head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_Projection_constructor(t) {
+ t.plan(9);
+
+ var options = {'foo': 'bar'};
+ var projection = new OpenLayers.Projection("code", options);
+ t.ok(projection instanceof OpenLayers.Projection,
+ "new OpenLayers.Projection returns object" );
+ t.eq(projection.projCode, "code", "The proj code is maintained");
+ t.eq(projection.getCode(), "code", "The proj code is maintained.");
+ t.eq(projection.getUnits(), null, "Null units with no proj4js library.");
+ t.eq(projection.foo, "bar", "constructor sets options correctly");
+
+ var projection2 = new OpenLayers.Projection("epsg:4325", options);
+ out = OpenLayers.Projection.transform({'x':10,'y':12}, projection, projection2);
+ t.eq(out.x, 10, "Null transform has no effect");
+ t.eq(out.y, 12, "Null transform has no effect");
+
+ t.eq(projection.equals(null), false, "equals on null projection returns false");
+ t.eq(projection.equals({}), false, "equals on null projection object returns false (doesn't call getCode)");
+ }
+
+ function test_Projection_equals(t) {
+ t.plan(8);
+ var origTransforms = OpenLayers.Util.extend({}, OpenLayers.Projection.transforms);
+ OpenLayers.Projection.addTransform("EPSG:4326", "FOO", OpenLayers.Projection.nullTransform);
+ OpenLayers.Projection.addTransform("FOO", "EPSG:4326", OpenLayers.Projection.nullTransform);
+ var projection = new OpenLayers.Projection("FOO");
+ t.eq(projection.equals(new OpenLayers.Projection("EPSG:4326")), true, "EPSG:4326 and FOO are equal without proj4js");
+ t.eq(projection.equals(new OpenLayers.Projection("EPSG:900913")), false, "EPSG:900913 and FOO are not equal without proj4js");
+ t.eq(new OpenLayers.Projection("EPSG:4326").equals(new OpenLayers.Projection("EPSG:4326")), true, "EPSG:4326 and EPSG:4326 are equal without proj4js");
+ t.eq(new OpenLayers.Projection("BAR").equals(new OpenLayers.Projection("EPSG:4326")), false, "Projection.equals() returns false for unknown projections withoug proj4js");
+ OpenLayers.Projection.transforms = origTransforms;
+
+ var proj1 = new OpenLayers.Projection("EPSG:4326");
+ var proj2 = new OpenLayers.Projection("FOO");
+ var proj3 = new OpenLayers.Projection("EPSG:900913");
+ var proj4 = new OpenLayers.Projection("EPSG:4326");
+ var proj5 = new OpenLayers.Projection("BAR");
+
+ // conditionally mock up proj4js
+ var hasProj = !!window.Proj4js;
+ if (!hasProj) {
+ window.Proj4js = {};
+ }
+ proj1.proj = {defData: "+title= WGS84 +foo=bar +x=0"};
+ proj2.proj = {defData: "+title=FOO +foo=bar +x=0", srsCode: "FOO"};
+ proj3.proj = {defData: "+title=Web Mercator +foo=bar +x=0 +I=am-different"};
+ proj4.proj = proj1.proj;
+ proj5.proj = {srsCode: "BAR"};
+
+ t.eq(proj2.equals(proj1), true, "EPSG:4326 and FOO are equal with proj4js");
+ t.eq(proj2.equals(proj3), false, "EPSG:900913 and FOO are not equal with proj4js");
+ t.eq(proj1.equals(proj4), true, "EPSG:4326 and EPSG:4326 are equal with proj4js");
+ t.eq(proj2.equals(proj5), false, "Projection.equals() returns false for unknown projections with proj4js");
+
+ if (!hasProj) {
+ window.Proj4js = undefined;
+ }
+
+ }
+
+ function test_equals_string(t) {
+
+ t.plan(7);
+ var gg = new OpenLayers.Projection("EPSG:4326");
+ var sm = new OpenLayers.Projection("EPSG:900913");
+
+ // allow comparison with identifier
+ t.eq(gg.equals("EPSG:4326"), true, "EPSG:4326 equality with string");
+ t.eq(gg.equals("EPSG:4327"), false, "EPSG:4326 inequality with string");
+ t.eq(sm.equals("EPSG:900913"), true, "EPSG:900913 equality with string");
+ t.eq(sm.equals("EPSG:900914"), false, "EPSG:900913 inequality with string");
+ t.eq(sm.equals("EPSG:3857"), true, "EPSG:900913 equality with EPSG:3857");
+ t.eq(sm.equals("EPSG:102113"), true, "EPSG:900913 equality with EPSG:102113");
+ t.eq(sm.equals("EPSG:102100"), true, "EPSG:900913 equality with EPSG:102100");
+
+ }
+
+ </script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/Protocol.html b/misc/openlayers/tests/Protocol.html
new file mode 100644
index 0000000..7432b86
--- /dev/null
+++ b/misc/openlayers/tests/Protocol.html
@@ -0,0 +1,63 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(4);
+ var options = {};
+ var protocol = new OpenLayers.Protocol(options);
+
+ t.ok(protocol instanceof OpenLayers.Protocol,
+ "new OpenLayers.Protocol returns object" );
+ t.eq(protocol.options, options, "constructor sets this.options");
+ t.eq(protocol.options.filter, null, "constructor sets defaultFilter to null");
+ t.eq(protocol.autoDestroy, true, "constructor does not modify this.autoDestroy");
+ }
+
+ function test_read_defaultFilter(t) {
+ t.plan(4);
+
+ var protocol = new OpenLayers.Protocol({filter: "a"});
+ var options = {};
+ protocol.read(options);
+ // the line below is what happens in Protocol.WFS.v1::read
+ OpenLayers.Util.applyDefaults(options, protocol.options);
+ t.eq(options.filter, "a", "filter from protocol.options applied to options");
+ protocol.destroy();
+
+ var defaultFilter = 'a';
+ var options = {
+ defaultFilter: defaultFilter
+ };
+
+ protocol = new OpenLayers.Protocol(options);
+ var readFilter = 'b';
+ var options = { filter: readFilter };
+
+ protocol.read(options);
+
+ var filter = options.filter;
+ t.ok(filter instanceof OpenLayers.Filter.Logical, "read method merge default filter & options filter to a logical one");
+ t.eq(filter.type, OpenLayers.Filter.Logical.AND, "logical filter type is OpenLayers.Filter.Logical.AND");
+ t.eq(filter.filters, [defaultFilter, readFilter], "read method has merged filters");
+ protocol.destroy();
+ }
+
+ function test_destroy(t) {
+ t.plan(2);
+ var protocol = new OpenLayers.Protocol({
+ options: {foo: 'bar'},
+ format: 'foo'
+ });
+ protocol.destroy();
+
+ t.eq(protocol.format, null, "destroy nullify protocol.format");
+ t.eq(protocol.options, null, "destroy nullify protocol.options");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Protocol/CSW.html b/misc/openlayers/tests/Protocol/CSW.html
new file mode 100644
index 0000000..8c0847c
--- /dev/null
+++ b/misc/openlayers/tests/Protocol/CSW.html
@@ -0,0 +1,90 @@
+<html>
+<head>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ var protocol = new OpenLayers.Protocol.CSW({formatOptions: {foo: "bar"}});
+ t.ok(protocol instanceof OpenLayers.Protocol.CSW.v2_0_2,
+ "initialize returns instance of default versioned protocol");
+ var format = protocol.format;
+ t.ok(format instanceof OpenLayers.Format.CSWGetRecords.v2_0_2, "Default format created");
+ t.ok(format.foo, "bar", "formatOptions set correctly");
+ protocol.destroy();
+ }
+
+ function test_read(t) {
+ t.plan(6);
+
+ var protocol = new OpenLayers.Protocol.CSW({
+ url: "http://some.url.org",
+ parseData: function(request) {
+ t.eq(request.responseText, "foo", "parseData called properly");
+ return "foo";
+ }
+ });
+
+ var _POST = OpenLayers.Request.POST;
+
+ var expected, status;
+ OpenLayers.Request.POST = function(obj) {
+ t.xml_eq(new OpenLayers.Format.XML().read(obj.data).documentElement, expected, "GetRecords request is correct");
+ obj.status = status;
+ obj.responseText = "foo";
+ obj.options = {};
+ t.delay_call(0.1, function() {obj.callback.call(this)});
+ return obj;
+ };
+
+ expected = readXML("GetRecords");
+ status = 200;
+ var data = {
+ "resultType": "results",
+ "maxRecords": 100,
+ "Query": {
+ "typeNames": "gmd:MD_Metadata",
+ "ElementSetName": {
+ "value": "full"
+ }
+ }
+ };
+ var response = protocol.read({
+ params: data,
+ callback: function(response) {
+ t.eq(response.data, "foo", "user callback properly called with data");
+ t.eq(response.code, OpenLayers.Protocol.Response.SUCCESS, "success reported properly to user callback");
+ }
+ });
+
+ var options = {
+ params: data,
+ callback: function(response) {
+ t.eq(response.code, OpenLayers.Protocol.Response.FAILURE, "failure reported properly to user callback");
+ }
+ };
+ status = 400;
+ var response = protocol.read(options);
+
+ OpenLayers.Request.POST = _POST;
+ }
+
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return new OpenLayers.Format.XML().read(xml).documentElement;
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+<div id="GetRecords"><!--
+<csw:GetRecords xmlns:csw="http://www.opengis.net/cat/csw/2.0.2" service="CSW" version="2.0.2" resultType="results" maxRecords="100">
+ <csw:Query typeNames="gmd:MD_Metadata">
+ <csw:ElementSetName>full</csw:ElementSetName>
+ </csw:Query>
+</csw:GetRecords>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Protocol/HTTP.html b/misc/openlayers/tests/Protocol/HTTP.html
new file mode 100644
index 0000000..fac460b
--- /dev/null
+++ b/misc/openlayers/tests/Protocol/HTTP.html
@@ -0,0 +1,842 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(8);
+ var a = new OpenLayers.Protocol.HTTP({
+ url: "foo"
+ });
+
+ // 4 tests
+ t.eq(a.url, "foo", "constructor sets url");
+ t.eq(a.options.url, a.url, "constructor copies url to options.url");
+ t.eq(a.params, {}, "constructor sets params");
+ t.eq(a.options.params, undefined, "constructor do not copy params to options.params");
+
+ var params = {hello: "world"};
+ var b = new OpenLayers.Protocol.HTTP({
+ url: "bar",
+ params: params
+ });
+
+ // 4 tests
+ t.eq(b.url, "bar", "constructor sets url");
+ t.eq(b.options.url, b.url, "constructor copies url to options.url");
+ t.eq(b.params, params, "constructor sets params");
+ t.eq(b.options.params, b.params, "constructor copies params to options.params");
+ }
+
+ function test_destroy(t) {
+ t.plan(3);
+ var protocol = new OpenLayers.Protocol.HTTP({
+ url: "bar",
+ params: {hello: "world"}
+ });
+ protocol.destroy();
+ t.eq(protocol.options, null, "destroy nullifies options");
+ t.eq(protocol.params, null, "destroy nullifies params");
+ t.eq(protocol.headers, null, "destroy nullifies headers");
+ }
+
+ function test_read(t) {
+ t.plan(10);
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url',
+ 'params': {'k': 'foo_param'}
+ });
+
+ // fake XHR request object
+ var request = {'status': 200};
+
+ // options to pass to read
+ var readOptions = {
+ 'url': 'bar_url',
+ 'params': {'k': 'bar_param'},
+ 'headers': {'k': 'bar_header'},
+ 'scope': {'hello': 'world'},
+ 'callback': function() {}
+ };
+
+ var response;
+
+ protocol.handleResponse = function(resp, opt) {
+ // 4 tests
+ var req = resp.priv;
+ t.ok(this == protocol,
+ 'handleResponse called with correct scope');
+ t.ok(opt == readOptions,
+ 'handleResponse called with correct options');
+ t.eq(resp.CLASS_NAME, 'OpenLayers.Protocol.Response',
+ 'handleResponse called with a Response object');
+ t.eq(req, request,
+ 'handleResponse called with correct request');
+
+ response = resp;
+ };
+
+ var _get = OpenLayers.Request.GET;
+
+ OpenLayers.Request.GET = function(options) {
+ // 5 tests
+ t.eq(options.url, readOptions.url,
+ 'GET called with correct url in options');
+ t.eq(options.params['k'], readOptions.params['k'],
+ 'GET called with correct params in options');
+ t.eq(options.headers['k'], readOptions.headers['k'],
+ 'GET called with correct headers in options');
+ t.eq(options.scope, undefined,
+ 'GET called with correct scope in options');
+ t.ok(typeof options.callback == 'function',
+ 'GET called with a callback in options');
+ t.delay_call(0.1, function() {
+ options.callback(request);
+ t.ok(resp == response,
+ 'read returns the expected response object');
+ // cleanup
+ protocol.destroy();
+ OpenLayers.Request.GET = _get;
+ });
+ return request;
+ };
+
+ var resp = protocol.read(readOptions);
+
+ OpenLayers.Request.GET = _get;
+ }
+
+ function test_readWithPOST(t) {
+ t.plan(10);
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url',
+ 'params': {'k': 'foo_param'}
+ });
+
+ // fake XHR request object
+ var request = {'status': 200};
+
+ // options to pass to read
+ var readOptions = {
+ 'url': 'bar_url',
+ 'params': {'k': 'bar_param'},
+ 'scope': {'hello': 'world'},
+ 'callback': function() {},
+ 'readWithPOST': true
+ };
+
+ var response;
+
+ protocol.handleResponse = function(resp, opt) {
+ // 4 tests
+ var req = resp.priv;
+ t.ok(this == protocol,
+ 'handleResponse called with correct scope');
+ t.ok(opt == readOptions,
+ 'handleResponse called with correct options');
+ t.eq(resp.CLASS_NAME, 'OpenLayers.Protocol.Response',
+ 'handleResponse called with a Response object');
+ t.eq(req, request,
+ 'handleResponse called with correct request');
+
+ response = resp;
+ };
+
+ var _post = OpenLayers.Request.POST;
+
+ OpenLayers.Request.POST = function(options) {
+ // 5 tests
+ t.eq(options.url, readOptions.url,
+ 'GET with POST called with correct url in options');
+ t.eq(options.data, OpenLayers.Util.getParameterString(readOptions.params),
+ 'GET with POST called with correct params encoded in options');
+ t.eq(options.headers, {"Content-Type": "application/x-www-form-urlencoded"},
+ 'GET with POST called with correct headers (application/x-www-form-urlencoded)');
+ t.eq(options.scope, undefined,
+ 'GET with POST called with correct scope in options');
+ t.ok(typeof options.callback == 'function',
+ 'GET with POST called with a callback in options');
+ t.delay_call(0.1, function() {
+ options.callback(request);
+ t.ok(resp == response,
+ 'read returns the expected response object');
+ // cleanup
+ protocol.destroy();
+ OpenLayers.Request.POST = _post;
+ });
+ return request;
+ };
+
+ var resp = protocol.read(readOptions);
+
+ OpenLayers.Request.POST = _post;
+ }
+
+ function test_read_method(t) {
+ t.plan(4);
+
+ var _post = OpenLayers.Request.POST;
+ OpenLayers.Request.POST = function(options) { return 'post'; }
+ var _get = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(options) { return 'get'; }
+
+ var protocol = new OpenLayers.Protocol.HTTP({});
+
+ t.eq(protocol.read({}).priv, 'get',
+ 'readWithPOST is false by default');
+ t.eq(protocol.read({readWithPOST: true}).priv, 'post',
+ 'readWithPOST can be set in read options');
+
+ var protocol = new OpenLayers.Protocol.HTTP({readWithPOST: true});
+
+ t.eq(protocol.read({}).priv, 'post',
+ 'readWithPOST can be set in constructor');
+ t.eq(protocol.read({readWithPOST: false}).priv, 'get',
+ 'readWithPOST can be overridden in read options');
+
+ OpenLayers.Request.POST = _post;
+ OpenLayers.Request.GET = _get;
+ }
+
+ function test_read_bbox(t) {
+ t.plan(6);
+
+ var _get = OpenLayers.Request.GET;
+
+ var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: bounds,
+ projection: new OpenLayers.Projection("foo")
+ });
+
+ // log requests
+ var log, exp;
+ OpenLayers.Request.GET = function(options) {
+ log.push(options.params.bbox);
+ return {status: 200};
+ };
+
+ // 1) issue request with default protocol
+ log = [];
+ new OpenLayers.Protocol.HTTP().read({filter: filter});
+
+ t.eq(log.length, 1, "1) GET called once");
+ t.ok(log[0] instanceof Array, "1) bbox param is array");
+ exp = bounds.toArray();
+ t.eq(log[0], exp, "1) bbox param doesn't include SRS id by default");
+
+ // 2) issue request with default protocol
+ log = [];
+ new OpenLayers.Protocol.HTTP({srsInBBOX: true}).read({filter: filter});
+
+ t.eq(log.length, 1, "2) GET called once");
+ t.ok(log[0] instanceof Array, "2) bbox param is array");
+ exp = bounds.toArray();
+ exp.push("foo");
+ t.eq(log[0], exp, "2) bbox param includes SRS id if srsInBBOX is true");
+
+ OpenLayers.Request.GET = _get;
+ }
+
+ function test_parseFeatures(t) {
+ t.plan(5);
+
+ var protocol = new OpenLayers.Protocol.HTTP();
+
+ // test responseXML - 2 tests
+ var request = {
+ 'responseXML': {
+ 'documentElement': 'xml'
+ }
+ };
+ protocol.format = {
+ 'read': function(doc) {
+ t.eq(doc.documentElement, 'xml',
+ 'format.read called with correct doc');
+ return doc.documentElement;
+ }
+ };
+ var ret = protocol.parseFeatures(request);
+ t.eq(ret, 'xml', 'parseFeatures returns expected value');
+
+ // test responseText - 2 tests
+ var request = {
+ 'responseText': 'text'
+ };
+ protocol.format = {
+ 'read': function(doc) {
+ t.eq(doc, 'text',
+ 'format.read called with correct doc');
+ return doc;
+ }
+ };
+ var ret = protocol.parseFeatures(request);
+ t.eq(ret, 'text', 'parseFeatures returns expected value');
+
+ // test empty responseText - 1 test
+ var request = {
+ 'responseText': ''
+ };
+ protocol.format = {
+ 'read': function(doc) {
+ t.fail('format.read should not be called');
+ }
+ };
+ var ret = protocol.parseFeatures(request);
+ t.eq(ret, null, 'parseFeatures returns expected value');
+ }
+
+ function test_create(t) {
+ t.plan(10);
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url',
+ 'format': {'write': function() {}}
+ });
+
+ // fake XHR request object
+ var request = {'status': 200};
+
+ // features to pass to create
+ var features = ['feature'];
+
+ // options to pass to create
+ var createOptions = {
+ 'url': 'bar_url',
+ 'headers': {'k': 'bar_header'},
+ 'scope': {'hello': 'world'},
+ 'callback': function() {}
+ };
+
+ var response;
+
+ protocol.handleCreate = function(resp, opt) {
+ // 5 tests
+ var req = resp.priv;
+ t.ok(this == protocol,
+ 'handleCreate called with correct scope');
+ t.ok(opt == createOptions,
+ 'handleCreate called with correct options');
+ t.eq(resp.CLASS_NAME, 'OpenLayers.Protocol.Response',
+ 'handleCreate called with a Response object');
+ t.ok(resp.reqFeatures == features,
+ 'handleCreate called with correct requested features in response');
+ t.eq(req, request,
+ 'handleCreate called with correct request');
+
+ response = resp;
+ };
+
+ var _post = OpenLayers.Request.POST;
+
+ OpenLayers.Request.POST = function(options) {
+ // 4 tests
+ t.eq(options.url, createOptions.url,
+ 'POST called with correct url in options');
+ t.eq(options.headers['k'], createOptions.headers['k'],
+ 'POST called with correct headers in options');
+ t.eq(options.scope, undefined,
+ 'POST called with correct scope in options');
+ t.ok(typeof options.callback == 'function',
+ 'POST called with a callback in options');
+ // call callback - delayed because this function has to return first
+ t.delay_call(0.1, function() {
+ options.callback(request);
+ t.ok(resp == response,
+ 'create returns the expected response object');
+ // cleanup
+ protocol.destroy();
+ OpenLayers.Request.POST = _post;
+ });
+ return request;
+ };
+
+ var resp = protocol.create(features, createOptions);
+
+ OpenLayers.Request.POST = _post;
+ }
+
+ function test_update(t) {
+ t.plan(10);
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url',
+ 'format': {'write': function() {}}
+ });
+
+ // fake XHR request object
+ var request = {'status': 200};
+
+ // feature to pass to update
+ var feature = {'feature':'feature'};
+
+ // options to pass to update
+ var updateOptions = {
+ 'url': 'bar_url',
+ 'headers': {'k': 'bar_header'},
+ 'scope': {'hello': 'world'},
+ 'callback': function() {}
+ };
+
+ var response;
+
+ protocol.handleUpdate = function(resp, opt) {
+ var req = resp.priv;
+ // 5 tests
+ t.ok(this == protocol,
+ 'handleUpdate called with correct scope');
+ t.ok(opt == updateOptions,
+ 'handleUpdate called with correct options');
+ t.eq(resp.CLASS_NAME, 'OpenLayers.Protocol.Response',
+ 'handleUpdate called with a Response object');
+ t.ok(resp.reqFeatures == feature,
+ 'handleUpdate called with correct requested feature in response');
+ t.eq(req, request,
+ 'handleUpdate called with correct request');
+
+ response = resp;
+ };
+
+ var _put = OpenLayers.Request.PUT;
+
+ OpenLayers.Request.PUT = function(options) {
+ // 4 tests
+ t.eq(options.url, updateOptions.url,
+ 'PUT called with correct url in options');
+ t.eq(options.headers['k'], updateOptions.headers['k'],
+ 'PUT called with correct headers in options');
+ t.eq(options.scope, undefined,
+ 'PUT called with correct scope in options');
+ t.ok(typeof options.callback == 'function',
+ 'PUT called with a callback in options');
+ // call callback - delayed because this function has to return first
+ t.delay_call(0.1, function() {
+ options.callback(request);
+ t.ok(resp == response,
+ 'update returns the expected response object');
+ // cleanup
+ protocol.destroy();
+ OpenLayers.Request.PUT = _put;
+ });
+ return request;
+ };
+
+ var resp = protocol.update(feature, updateOptions);
+
+ OpenLayers.Request.PUT = _put;
+ }
+
+ function test_update_featureurl(t) {
+
+ // test that OpenLayers.Request.PUT receives the URL
+ // set in the feature
+ // http://trac.openlayers.org/ticket/2393#comment:11
+
+ t.plan(1);
+
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url',
+ 'format': {'write': function() {}}
+ });
+
+ // feature to pass to update
+ var feature = {'feature':'feature', 'url': 'bar_url'};
+
+ var _put = OpenLayers.Request.PUT;
+
+ OpenLayers.Request.PUT = function(options) {
+ t.eq(options.url, feature.url,
+ 'PUT called with correct url in options');
+ };
+
+ protocol.update(feature);
+
+ OpenLayers.Request.PUT = _put;
+ }
+
+ function test_handleResponse(t) {
+ t.plan(6);
+
+ var protocol = new OpenLayers.Protocol.HTTP();
+
+ var options, response, request, features;
+
+ // test options - 2 tests
+ var scope = {'fake': 'scope'};
+ options = {
+ 'scope': scope,
+ 'callback': function(resp) {
+ t.ok(this == scope,
+ '[no status] callback called with correct scope');
+ t.ok(resp == response,
+ '[no status] callback called with correct response');
+ }
+ };
+ response = {priv: {}};
+ protocol.handleResponse(response, options);
+
+ // test failure condition - 1 test
+ options = {
+ 'callback': function(resp) {
+ t.eq(resp.code, OpenLayers.Protocol.Response.FAILURE,
+ '[status 400] callback called with correct response code');
+ }
+ };
+ response = {priv: {status: 400}};
+ protocol.handleResponse(response, options);
+
+ // test success condition - 3 tests
+ features = {'fake': 'features'};
+ options = {
+ 'callback': function(resp) {
+ t.eq(resp.code, OpenLayers.Protocol.Response.SUCCESS,
+ '[status 200] callback called with correct response code');
+ t.eq(resp.features, features,
+ '[status 200] callback called with correct features in response');
+ }
+ };
+ response = {priv: {status: 200}};
+ protocol.parseFeatures = function(request) {
+ t.ok(request == response.priv,
+ '[status 200] parseFeatures called with correct request');
+ return features;
+ }
+ protocol.handleResponse(response, options);
+
+ // cleanup
+ protocol.destroy();
+ }
+
+ function test_delete(t) {
+ t.plan(10);
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url'
+ });
+
+ // fake XHR request object
+ var request = {'status': 200};
+
+ // feature to pass to delete
+ var feature = {'url': 'bar_url'};
+
+ // options to pass to delete
+ var deleteOptions = {
+ 'url': 'bar_url',
+ 'headers': {'k': 'bar_header'},
+ 'scope': {'hello': 'world'},
+ 'callback': function() {}
+ };
+
+ var response;
+
+ protocol.handleDelete = function(resp, opt) {
+ // 5 tests
+ var req = resp.priv;
+ t.ok(this == protocol,
+ 'handleDelete called with correct scope');
+ t.ok(opt == deleteOptions,
+ 'handleDelete called with correct options');
+ t.eq(resp.CLASS_NAME, 'OpenLayers.Protocol.Response',
+ 'handleDelete called with a Response object');
+ t.ok(resp.reqFeatures == feature,
+ 'handleDelete called with correct requested feature in response');
+ t.eq(req, request,
+ 'handleDelete called with correct request');
+
+ response = resp;
+ };
+
+ var _delete = OpenLayers.Request.DELETE;
+
+ OpenLayers.Request.DELETE = function(options) {
+ // 4 tests
+ t.eq(options.url, deleteOptions.url,
+ 'DELETE called with correct url in options');
+ t.eq(options.headers['k'], deleteOptions.headers['k'],
+ 'DELETE called with correct headers in options');
+ t.eq(options.scope, undefined,
+ 'DELETE called with correct scope in options');
+ t.ok(typeof options.callback == 'function',
+ 'DELETE called with a callback in options');
+ // call callback - delayed because this function has to return first
+ t.delay_call(0.1, function() {
+ options.callback(request);
+ t.ok(resp == response,
+ 'read returns the expected response object');
+ // cleanup
+ protocol.destroy();
+ OpenLayers.Request.DELETE = _delete;
+ });
+ return request;
+ };
+
+ var resp = protocol['delete'](feature, deleteOptions);
+
+ OpenLayers.Request.DELETE = _delete;
+ }
+
+ function test_delete_featureurl(t) {
+
+ // test that OpenLayers.Request.DELETE receives the URL
+ // set in the feature
+ // http://trac.openlayers.org/ticket/2393#comment:11
+
+ t.plan(1);
+
+ var protocol = new OpenLayers.Protocol.HTTP({
+ 'url': 'foo_url',
+ 'format': {'write': function() {}}
+ });
+
+ // feature to pass to update
+ var feature = {'feature':'feature', 'url': 'bar_url'};
+
+ var _delete = OpenLayers.Request.DELETE;
+
+ OpenLayers.Request.DELETE = function(options) {
+ t.eq(options.url, feature.url,
+ 'DELETE called with correct url in options');
+ };
+
+ protocol['delete'](feature);
+
+ OpenLayers.Request.DELETE = _delete;
+ }
+
+ function test_handleDelete(t) {
+ t.plan(4);
+
+ var protocol = new OpenLayers.Protocol.HTTP();
+
+ var options, response, request, features;
+
+ // test options - 2 tests
+ var scope = {'fake': 'scope'};
+ options = {
+ 'scope': scope,
+ 'callback': function(resp) {
+ t.ok(this == scope,
+ 'callback called with correct scope');
+ t.ok(resp == response,
+ 'callback called with correct response');
+ }
+ };
+ response = {priv: {}};
+ protocol.handleDelete(response, options);
+
+ // test failure condition - 1 test
+ options = {
+ 'callback': function(resp) {
+ t.eq(resp.code, OpenLayers.Protocol.Response.FAILURE,
+ 'callback called with correct response code');
+ }
+ };
+ response = {priv: {status: 400}};
+ protocol.handleDelete(response, options);
+
+ // test success condition - 1 test
+ options = {
+ 'callback': function(resp) {
+ t.eq(resp.code, OpenLayers.Protocol.Response.SUCCESS,
+ 'callback called with correct response code');
+ }
+ };
+ response = {priv: {status: 200}};
+ protocol.handleDelete(response, options);
+
+ // cleanup
+ protocol.destroy();
+ }
+
+ function test_commit(t) {
+ t.plan(17);
+
+ var protocol = new OpenLayers.Protocol.HTTP();
+
+ // 6 features
+ var features = [
+ {'state': OpenLayers.State.INSERT},
+ {'state': OpenLayers.State.INSERT},
+ {'state': OpenLayers.State.UPDATE},
+ {'state': OpenLayers.State.UPDATE},
+ {'state': OpenLayers.State.DELETE},
+ {'state': OpenLayers.State.DELETE}
+ ];
+
+ var options = {
+ 'create': {
+ 'callback': function(resp) {
+ }
+ },
+ 'update': {
+ 'callback': function(resp) {
+ }
+ },
+ 'delete': {
+ 'callback': function(resp) {
+ }
+ }
+ };
+
+ var respCreate = new OpenLayers.Protocol.Response();
+ var respUpdate = new OpenLayers.Protocol.Response();
+ var respDelete = new OpenLayers.Protocol.Response();
+
+ // 2 tests
+ protocol['create'] = function(feature, options) {
+ t.ok(options.scope == protocol,
+ 'create called with correct scope');
+ t.ok(typeof options.callback == 'function',
+ 'create called with a callback in options');
+ options.callback.call(options.scope, respCreate);
+ return respCreate;
+ };
+ // 4 tests
+ protocol['update'] = function(feature, options) {
+ t.ok(options.scope == protocol,
+ 'update called with correct scope');
+ t.ok(typeof options.callback == 'function',
+ 'update called with a callback in options');
+ options.callback.call(options.scope, respUpdate);
+ return respUpdate;
+ };
+ // 4 tests
+ protocol['delete'] = function(feature, options) {
+ t.ok(options.scope == protocol,
+ 'delete called with correct scope');
+ t.ok(typeof options.callback == 'function',
+ 'delete called with a callback in options');
+ options.callback.call(options.scope, respDelete);
+ return respDelete;
+ };
+
+ var count = 0;
+
+ // 5 tests
+ protocol.callUserCallback = function(resp, opt) {
+ t.ok(opt == options,
+ 'callUserCallback called with correction options map');
+ count++;
+ };
+
+ var resp = protocol.commit(features, options);
+
+ // 2 tests
+ t.eq(count, 5, 'callUserCallback called for each request');
+ t.eq(resp.length, 5, 'commit returns array with correct length');
+
+ // cleanup
+ protocol.destroy();
+ }
+
+ function test_callUserCallback(t) {
+ t.plan(1);
+
+ var protocol = new OpenLayers.Protocol.HTTP();
+
+ var scope = {'fake': 'scope'};
+
+ // test commit callback
+ var log = {};
+ var options = {
+ foo: {
+ callback: function() {
+ log.scope = this;
+ },
+ scope: scope
+ }
+ };
+ var resp = {requestType: 'foo'};
+ protocol.callUserCallback(resp, options);
+ t.ok(log.scope, scope, 'correct callback called with correct scope');
+
+ }
+
+ function test_options(t) {
+ t.plan(6);
+
+ var log1 = {};
+
+ // test that read with no options uses protocol options - 5 tests
+ var url = ".";
+ var headers = {};
+ var params = {};
+ var scope = {};
+ var protocol = new OpenLayers.Protocol.HTTP({
+ format: new OpenLayers.Format({
+ read: function() {},
+ write: function() {}
+ }),
+ url: url,
+ headers: headers,
+ params: params,
+ callback: function(resp) {
+ log1.callbackCalled = true;
+ log1.callbackScope = this;
+ log1.request = resp && resp.priv;
+ log1.requestType = resp && resp.requestType;
+ },
+ scope: scope
+ });
+ protocol.read();
+
+ t.delay_call(2, function() {
+ t.eq(log1.callbackCalled, true, "[read] callback called");
+ t.eq(log1.callbackScope, scope, "[read] correct scope");
+ t.ok(log1.request instanceof OpenLayers.Request.XMLHttpRequest, "[read] correct priv type");
+ t.eq(log1.requestType, "read", "[read] correct request type");
+ });
+
+
+ // test that commit with no options uses protocol options - 2 tests
+ var log2 = {called: 0};
+ protocol.options.callback = function() {
+ log2.called++;
+ log2.scope = this;
+ };
+ protocol.commit([
+ {state: OpenLayers.State.INSERT},
+ {state: OpenLayers.State.INSERT},
+ {state: OpenLayers.State.UPDATE, url: "./1"},
+ {state: OpenLayers.State.UPDATE, url: "./2"},
+ {state: OpenLayers.State.DELETE, url: "./3"},
+ {state: OpenLayers.State.DELETE, url: "./4"}
+ ]);
+ t.delay_call(2, function() {
+ t.eq(log2.called, 1, "[commit] Callback called once.");
+ t.eq(log2.scope, scope, "[commit] Correct scope.");
+ });
+ }
+
+ function test_read_global_options(t) {
+
+ // test that calling read doesn't write params into the protocol's
+ // options object, see ticket #3237
+
+ t.plan(2);
+
+ var protocol = new OpenLayers.Protocol.HTTP({
+ url: '.',
+ callback: function() {},
+ params: {'a': 'a'}
+ });
+
+ // check initial state first
+ t.eq(protocol.options.params, {'a': 'a'},
+ 'protocol params are ok at initial state');
+
+ var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: 'b',
+ value: 'b'
+ });
+ protocol.read({filter: filter});
+ t.eq(protocol.options.params, {'a': 'a'},
+ "protocol params are ok after read");
+ }
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Protocol/SOS.html b/misc/openlayers/tests/Protocol/SOS.html
new file mode 100644
index 0000000..58e6607
--- /dev/null
+++ b/misc/openlayers/tests/Protocol/SOS.html
@@ -0,0 +1,57 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(4);
+ var a = new OpenLayers.Protocol.SOS({
+ url: "foo",
+ fois: ["a", "b", "c"]
+ });
+
+ t.eq(a.url, "foo", "constructor sets url");
+ t.eq(a.options.url, a.url, "constructor copies url to options.url");
+ t.eq(a.fois[0], "a", "constructor sets the fois correctly");
+ t.eq((a.format instanceof OpenLayers.Format.SOSGetFeatureOfInterest), true, "Constructor sets format correctly");
+ }
+
+ function test_read(t) {
+ t.plan(4);
+
+ var protocol = new OpenLayers.Protocol.SOS({
+ url: "http://some.url.org/sos?",
+ fois: ["foi1", "foi2"],
+ parseFeatures: function(request) {
+ t.eq(request.responseText, "foo", "parseFeatures called properly");
+ return "foo";
+ }
+ });
+
+ var _POST = OpenLayers.Request.POST;
+
+ var expected, status;
+ OpenLayers.Request.POST = function(obj) {
+ t.xml_eq(new OpenLayers.Format.XML().read(obj.data).documentElement, expected, "GetFeatureOfInterest request is correct");
+ obj.status = status;
+ obj.responseText = "foo";
+ obj.options = {};
+ t.delay_call(0.1, function() {obj.callback.call(this)});
+ return obj;
+ };
+
+ var xml = '<GetFeatureOfInterest xmlns="http://www.opengis.net/sos/1.0" version="1.0.0" service="SOS" xsi:schemaLocation="http://www.opengis.net/sos/1.0 http://schemas.opengis.net/sos/1.0.0/sosAll.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><FeatureOfInterestId>foi1</FeatureOfInterestId><FeatureOfInterestId>foi2</FeatureOfInterestId></GetFeatureOfInterest>';
+ expected = new OpenLayers.Format.XML().read(xml).documentElement;
+ status = 200;
+ var response = protocol.read({callback: function(response) {
+ t.eq(response.features, "foo", "user callback properly called with features");
+ t.eq(response.code, OpenLayers.Protocol.Response.SUCCESS, "success reported properly");
+ }});
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Protocol/Script.html b/misc/openlayers/tests/Protocol/Script.html
new file mode 100644
index 0000000..894427a
--- /dev/null
+++ b/misc/openlayers/tests/Protocol/Script.html
@@ -0,0 +1,282 @@
+<html>
+<head>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(11);
+ var a = new OpenLayers.Protocol.Script({
+ url: "foo"
+ });
+
+ // 7 tests
+ t.eq(a.url, "foo", "constructor sets url");
+ t.eq(a.options.url, a.url, "constructor copies url to options.url");
+ t.eq(a.params, {}, "constructor sets params");
+ t.eq(a.options.params, undefined, "constructor does not copy params to options.params");
+ t.ok(a.format instanceof OpenLayers.Format.GeoJSON,
+ "constructor sets a GeoJSON format by default");
+ t.eq(a.callbackKey, 'callback',
+ "callbackKey is set to 'callback' by default");
+ t.eq(a.callbackPrefix, '',
+ "callbackPrefix is set to '' by default");
+
+ var params = {hello: "world"};
+ var b = new OpenLayers.Protocol.Script({
+ url: "bar",
+ params: params,
+ callbackKey: 'cb_key',
+ callbackPrefix: 'cb_prefix'
+ });
+
+ // 6 tests
+ t.eq(b.params, params, "constructor sets params");
+ t.eq(b.options.params, b.params, "constructor copies params to options.params");
+ t.eq(b.callbackKey, 'cb_key',
+ "callbackKey is set to 'cb_key'");
+ t.eq(b.callbackPrefix, 'cb_prefix',
+ "callbackPrefix is set to 'cb_prefix'");
+ }
+
+ function test_destroy(t) {
+ t.plan(3);
+ var aborted = false;
+ var protocol = new OpenLayers.Protocol.Script({
+ url: "bar",
+ params: {hello: "world"},
+ abort: function() {
+ aborted = true;
+ }
+ });
+ protocol.destroy();
+ t.ok(aborted, "destroy aborts request");
+ t.eq(protocol.params, null, "destroy nullifies params");
+ t.eq(protocol.format, null, "destroy nullifies format");
+ }
+
+ function test_read(t) {
+ t.plan(5);
+ var protocol = new OpenLayers.Protocol.Script({
+ 'url': 'foo_url',
+ 'params': {'k': 'foo_param'}
+ });
+
+ // fake XHR request object
+ var request = {'status': 200};
+
+ // options to pass to read
+ var readOptions = {
+ 'url': 'bar_url',
+ 'params': {'k': 'bar_param'}
+ };
+
+ var response;
+
+ protocol.createRequest = function(url, params, callback) {
+ // 4 tests
+ t.ok(this == protocol,
+ 'createRequest called with correct scope');
+ t.ok(url == readOptions.url,
+ 'createRequest called with correct url');
+ t.ok(params == readOptions.params,
+ 'createRequest called with correct params');
+ t.ok(callback instanceof Function,
+ 'createRequest called with a function as callback');
+
+ return 'foo_request';
+ };
+
+ var resp = protocol.read(readOptions);
+
+ t.eq(resp.priv, 'foo_request',
+ 'response priv property set to what the createRequest method returns');
+ }
+
+ function test_read_bbox(t) {
+ t.plan(6);
+
+ var _createRequest = OpenLayers.Protocol.Script.prototype.createRequest;
+
+ var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: bounds,
+ projection: new OpenLayers.Projection("foo")
+ });
+
+ // log requests
+ var log, exp;
+ OpenLayers.Protocol.Script.prototype.createRequest = function(url, params,
+ callback) {
+ log.push(params.bbox);
+ return null;
+ };
+
+ // 1) issue request with default protocol
+ log = [];
+ new OpenLayers.Protocol.Script().read({filter: filter});
+
+ t.eq(log.length, 1, "1) createRequest called once");
+ t.ok(log[0] instanceof Array, "1) bbox param is array");
+ exp = bounds.toArray();
+ t.eq(log[0], exp, "1) bbox param doesn't include SRS id by default");
+
+ // 2) issue request with default protocol
+ log = [];
+ new OpenLayers.Protocol.Script({srsInBBOX: true}).read({filter: filter});
+
+ t.eq(log.length, 1, "2) createRequest called once");
+ t.ok(log[0] instanceof Array, "2) bbox param is array");
+ exp = bounds.toArray();
+ exp.push("foo");
+ t.eq(log[0], exp, "2) bbox param includes SRS id if srsInBBOX is true");
+
+ OpenLayers.Protocol.Script.prototype.createRequest = _createRequest;
+ }
+
+ function test_createRequest(t) {
+ t.plan(6);
+ var protocol = new OpenLayers.Protocol.Script({
+ callbackKey: 'cb_key',
+ callbackPrefix: 'cb_prefix:'
+ });
+
+ var _register = OpenLayers.Protocol.Script.register;
+ OpenLayers.Protocol.Script.register = function() {
+ return 'bar';
+ };
+
+ var script = protocol.createRequest('http://bar_url/', {'k': 'bar_param'}, 'bar_callback');
+
+ t.eq(script.type, 'text/javascript',
+ 'created script has a correct type');
+
+ var params = OpenLayers.Util.getParameters(script.src);
+ t.eq(params.k, "bar_param", "custom query string param");
+ t.eq(params.cb_key, "cb_prefix:OpenLayers.Protocol.Script.registry.bar", "callback with prefix");
+
+ t.eq(script.id, 'OpenLayers_Protocol_Script_bar',
+ 'created script has a correct id');
+
+ protocol.callbackTemplate = "customCallback(${id})";
+ script = protocol.createRequest('http://bar_url/', {'k': 'bar_param2'}, 'bar_callback');
+
+ params = OpenLayers.Util.getParameters(script.src);
+ t.eq(params.k, "bar_param2", "custom query string param");
+ t.eq(params.cb_key, "cb_prefix:customCallback(bar)", "custom callback with prefix");
+
+ OpenLayers.Protocol.Script.register = _register;
+
+ }
+
+ function test_destroyRequest(t) {
+ t.plan(2);
+
+ var protocol = new OpenLayers.Protocol.Script({});
+
+ var _unregister = OpenLayers.Protocol.Script.unregister;
+ OpenLayers.Protocol.Script.unregister = function(id) {
+ t.eq(id, 'foo', "destroyRequest calls unregister with correct id");
+ };
+ var script = {
+ id: 'script_foo'
+ };
+ protocol.destroyRequest(script);
+ t.eq(protocol.pendingRequests[script.id], null,
+ "destroyRequest nullifies the pending request");
+
+ OpenLayers.Protocol.Script.unregister = _unregister;
+ }
+
+ function test_handleResponse(t) {
+ t.plan(8);
+
+ var protocol = new OpenLayers.Protocol.Script();
+
+ // 2 tests (should be called only twive)
+ protocol.destroyRequest = function(priv) {
+ t.eq(priv, 'foo_priv', 'destroyRequest called with correct argument');
+ }
+
+ // 1 test (should be called only once)
+ protocol.parseFeatures = function(data) {
+ t.eq(data, 'foo_data', 'parseFeatures called with correct argument');
+ return 'foo_features';
+ }
+
+ var response = {
+ priv: 'foo_priv',
+ data: 'foo_data'
+ }
+ var options = {
+ // 2 tests (should be called twice)
+ scope: 'foo_scope',
+ callback: function(resp) {
+ t.eq(this, 'foo_scope', 'callback called with correct scope');
+ }
+ }
+ protocol.handleResponse(response, options);
+ // 2 tests
+ t.eq(response.code, OpenLayers.Protocol.Response.SUCCESS,
+ 'response code correctly set');
+ t.eq(response.features, 'foo_features',
+ 'response features takes a correct value');
+
+ response = {
+ priv: 'foo_priv'
+ }
+ protocol.handleResponse(response, options);
+ // 1 test
+ t.eq(response.code, OpenLayers.Protocol.Response.FAILURE,
+ 'response code correctly set');
+ }
+
+ function test_parseFeatures(t) {
+ t.plan(1);
+
+ var protocol = new OpenLayers.Protocol.Script();
+
+ protocol.format = {
+ 'read': function(data) {
+ t.ok(true, 'format.read called');
+ }
+ };
+
+ var ret = protocol.parseFeatures({foo: 'bar'});
+ }
+
+ function test_abort(t) {
+ t.plan(2);
+
+ var protocol = new OpenLayers.Protocol.Script();
+
+ // 1 test
+ protocol.destroyRequest = function(priv) {
+ t.eq(priv, 'foo_priv', 'destroyRequest called with correct argument');
+ }
+
+ var response = {
+ priv: 'foo_priv'
+ }
+
+ protocol.abort(response);
+
+ var calls = [];
+ protocol.pendingRequests = {
+ 'foo': 'foo_request',
+ 'bar': 'bar_request'
+ }
+ protocol.destroyRequest = function(priv) {
+ calls.push(priv);
+ }
+ protocol.abort();
+ // 1 test
+ t.eq(calls, ['foo_request', 'bar_request'],
+ 'destroyRequest called for each pending requests');
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Protocol/WFS.html b/misc/openlayers/tests/Protocol/WFS.html
new file mode 100644
index 0000000..24e775d
--- /dev/null
+++ b/misc/openlayers/tests/Protocol/WFS.html
@@ -0,0 +1,471 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(2);
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type"
+ });
+ t.ok(protocol instanceof OpenLayers.Protocol.WFS.v1_0_0,
+ "initialize returns instance of default versioned protocol")
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ version: "1.1.0"
+ });
+ t.ok(protocol instanceof OpenLayers.Protocol.WFS.v1_1_0,
+ "initialize returns instance of custom versioned protocol")
+ }
+
+ function test_setGeometryName(t) {
+ t.plan(4);
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ geometryName: "geom"
+ });
+ t.eq(protocol.geometryName, "geom", "geometryName set correctly by constructor");
+ t.eq(protocol.format.geometryName, "geom", "geometryName correctly set on format by constructor");
+ // change the geometryName on the fly
+ protocol.setGeometryName("SHAPE");
+ t.eq(protocol.geometryName, "SHAPE", "geometryName changed correctly by setGeometryName");
+ t.eq(protocol.format.geometryName, "SHAPE", "geometryName correctly changed on format by setGeometryName");
+ protocol.destroy();
+ }
+
+ function test_setFeatureType(t) {
+ t.plan(4);
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type"
+ });
+ t.eq(protocol.featureType, "type", "featureType set correctly by constructor");
+ t.eq(protocol.format.featureType, "type", "featureType correctly set on format by constructor");
+ // change the feature type on the fly
+ protocol.setFeatureType("foo");
+ t.eq(protocol.featureType, "foo", "featureType changed correctly by setFeatureType");
+ t.eq(protocol.format.featureType, "foo", "featureType correctly changed on format by setFeatureType");
+ protocol.destroy();
+ }
+
+ function test_read(t) {
+ t.plan(7);
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ parseResponse: function(request, options) {
+ t.eq(request.responseText, "foo", "parseResponse called properly");
+ t.eq(options, {foo: "bar"}, "parseResponse receives readOptions");
+ return "foo";
+ }
+ });
+
+ var _POST = OpenLayers.Request.POST;
+
+ var expected, status;
+ OpenLayers.Request.POST = function(obj) {
+ t.xml_eq(new OpenLayers.Format.XML().read(obj.data).documentElement, expected, "GetFeature request is correct");
+ obj.status = status;
+ obj.responseText = "foo";
+ t.delay_call(0.1, function() {obj.callback.call(this)});
+ return obj;
+ };
+
+ expected = readXML("GetFeature_1");
+ status = 200;
+ var response = protocol.read({readOptions: {foo: "bar"}, callback: function(response) {
+ t.eq(response.features, "foo", "user callback properly called with features");
+ t.eq(response.code, OpenLayers.Protocol.Response.SUCCESS, "success reported properly");
+ }});
+
+ options = {
+ maxFeatures: 10,
+ featureType: 'type2',
+ srsName: 'EPSG:900913',
+ featureNS: 'htttp://alternative.namespace.org',
+ callback: function(response) {
+ t.eq(response.code, OpenLayers.Protocol.Response.FAILURE, "failure reported properly to user callback");
+ }
+ };
+ expected = readXML("GetFeature_2");
+ status = 400;
+ var response = protocol.read(options);
+
+ OpenLayers.Request.POST = _POST;
+ }
+
+ function test_parseResponse_poorconfig(t) {
+ t.plan(2);
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featurePrefix: "topp",
+ featureType: "tasmania_roads",
+ geometryName: null
+ });
+
+ protocol.parseResponse({responseText: document.getElementById("query_response").firstChild.nodeValue});
+ t.eq(protocol.geometryName, "geom", "geometryName configured correctly");
+ t.eq(protocol.featureNS, "http://www.openplans.org/topp", "featureNS configured correctly");
+ }
+
+ function test_exception(t) {
+ t.plan(8);
+ var url = "http://some.url.org";
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: url,
+ version: "1.1.0",
+ featureNS: "http://namespace.org",
+ featureType: "type"
+ });
+ // mock up a response
+ var response = {
+ priv: {
+ status: 200,
+ responseText: '<?xml version="1.0" encoding="UTF-8"?><ows:ExceptionReport language="en" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/ows http://schemas.opengis.net/ows/1.0.0/owsExceptionReport.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows"><ows:Exception locator="foo" exceptionCode="InvalidParameterValue"><ows:ExceptionText>Update error: Error occurred updating features</ows:ExceptionText><ows:ExceptionText>Second exception line</ows:ExceptionText></ows:Exception></ows:ExceptionReport>'
+ }
+ };
+ var log, entry, expected;
+
+ // test GetFeature
+ log = [];
+ protocol.handleRead(OpenLayers.Util.extend({}, response), {
+ callback: function(resp) {
+ log.push(resp);
+ }
+ });
+ expected = {
+ exceptionReport: {
+ version: "1.0.0",
+ language: "en",
+ exceptions: [{
+ code: "InvalidParameterValue",
+ locator: "foo",
+ texts: [
+ "Update error: Error occurred updating features",
+ "Second exception line"
+ ]
+ }]
+ },
+ success: false
+ };
+
+ t.eq(log.length, 1, "GetFeature handled");
+ entry = log[0];
+ t.eq(entry.code, OpenLayers.Protocol.Response.FAILURE, "GetFeature failure reported");
+ t.ok(!!entry.error, "GetFeature got error");
+ t.eq(entry.error, expected, "GetFeature error matches expected");
+
+ // test a commit
+ log = [];
+ protocol.handleCommit(response, {
+ callback: function(resp) {
+ log.push(resp);
+ }
+ });
+ t.eq(log.length, 1, "commit handled");
+ entry = log[0];
+ t.eq(entry.code, OpenLayers.Protocol.Response.FAILURE, "commit failure reported");
+ t.ok(!!entry.error, "commit got error");
+ t.eq(entry.error, expected, "GetFeature error matches expected");
+
+ }
+
+ function test_commit(t){
+ t.plan(5);
+
+ var url = "http://some.url.org";
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: url,
+ featureNS: "http://namespace.org",
+ featureType: "type"
+ });
+ protocol.format.read = function(data) {
+ t.eq(data, "foo", "callback called with correct argument");
+ return {
+ insertIds: new Array(3),
+ success: true
+ }
+ };
+
+ var _POST = OpenLayers.Request.POST;
+
+ var expected;
+ OpenLayers.Request.POST = function(obj) {
+ t.xml_eq(new OpenLayers.Format.XML().read(obj.data).documentElement, expected, "Transaction XML with Insert, Update and Delete created correctly");
+ t.eq(obj.headers, {foo: 'bar'}, "HTTP headers passed from commit to Request.POST");
+ obj.responseText = "foo";
+ t.delay_call(0.1, function() {obj.callback.call(this)});
+ return obj;
+ };
+
+ var featureDelete = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(42, 7), {has : "cheeseburger"});
+ featureDelete.fid = "fid.37";
+ featureDelete.state = OpenLayers.State.DELETE;
+ featureDelete.layer = {
+ projection: {
+ getCode : function(){
+ return "EPSG:4326";
+ }
+ }
+ }
+ var featureInsert = featureDelete.clone();
+ featureInsert.state = OpenLayers.State.INSERT;
+ var featureModify = featureDelete.clone();
+ featureModify.fid = "fid.37";
+ featureModify.state = OpenLayers.State.UPDATE;
+
+ options = {
+ featureNS: "http://some.namespace.org",
+ featureType: "type",
+ headers: {foo: 'bar'},
+ callback: function(response) {
+ t.eq(response.insertIds.length, 3, "correct response passed to user callback");
+ t.eq(response.code, OpenLayers.Protocol.Response.SUCCESS, "success properly reported to user callback");
+ }
+ }
+
+ expected = readXML("commit");
+ var response = protocol.commit([featureInsert, featureModify, featureDelete], options);
+
+ OpenLayers.Request.POST = _POST;
+
+ }
+
+ function test_filterDelete(t) {
+ t.plan(2)
+
+ var url = "http://some.url.org";
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: url,
+ featureNS: "http://namespace.org",
+ featureType: "type"
+ });
+
+ var filter = new OpenLayers.Filter.Spatial({
+ type: OpenLayers.Filter.Spatial.BBOX,
+ value: new OpenLayers.Bounds(-5, -5, 5, 5)
+ });
+
+ var _POST = OpenLayers.Request.POST;
+
+ var expected = readXML("filter_delete");
+ OpenLayers.Request.POST = function(obj) {
+ t.xml_eq(new OpenLayers.Format.XML().read(obj.data).documentElement, expected, "request data correct");
+ t.delay_call(0.1, function() {obj.callback.call(this)});
+ return obj;
+ };
+
+ var response = protocol.filterDelete(filter, {
+ callback: function() {
+ t.ok("user callback function called");
+ }
+ });
+
+ OpenLayers.Request.POST = _POST;
+ }
+
+ function test_abort(t) {
+ t.plan(1);
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://example.com",
+ featureNS: "http://example.com#namespace",
+ featureType: "type"
+ });
+
+ var response = {
+ priv: {
+ abort: function() {
+ aborted = true;
+ }
+ }
+ };
+
+ // call abort with mocked response
+ var aborted = false;
+ protocol.abort(response);
+ t.eq(aborted, true, "abort called on response.priv");
+
+ }
+
+ function test_fromWMSLayer(t) {
+ t.plan(9);
+ var map = new OpenLayers.Map("map", {
+ projection: "CRS:84"
+ });
+ var layer = new OpenLayers.Layer.WMS("foo", "htttp://foo/ows",
+ {layers: "topp:states"}
+ );
+ map.addLayer(layer);
+ var protocol = OpenLayers.Protocol.WFS.fromWMSLayer(layer);
+ t.eq(protocol.url, "htttp://foo/ows", "url taken from wms layer");
+ t.eq(protocol.featurePrefix, "topp", "feature prefix correctly extracted");
+ t.eq(protocol.featureType, "states", "typeName correctly extracted");
+ t.eq(protocol.srsName, "CRS:84", "srsName set correctly");
+ t.eq(protocol.version, "1.1.0", "version set correctly");
+ t.eq(protocol.format.geometryName, null, "format's geometryName set to null");
+
+ layer.params["LAYERS"] = ["topp:street_centerline", "topp:states"];
+ layer.projection = new OpenLayers.Projection("EPSG:900913");
+ protocol = OpenLayers.Protocol.WFS.fromWMSLayer(layer);
+ t.eq(protocol.featurePrefix, "topp", "featurePrefix from layer param array");
+ t.eq(protocol.featureType, "street_centerline", "first layer from layer param array as featureType");
+ t.eq(protocol.srsName, "EPSG:900913", "projection from layer preferred");
+ }
+
+ function test_readFormat(t) {
+ t.plan(1);
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ formatOptions: {outputFormat: 'json'},
+ readFormat: new OpenLayers.Format.GeoJSON()
+ });
+
+ var request = {};
+ request.responseText = '{"type":"FeatureCollection","features":[{"type":"Feature","id":"V_HECTOPUNTEN.108411","geometry":{"type":"MultiPoint","coordinates":[[190659.467,349576.19]]},"geometry_name":"ORA_GEOMETRY","properties":{"WEGNUMMER":"002","HECTOMTRNG_ORG":2200,"HECTOMTRNG":"220.00","bbox":[190659.467,349576.19,190659.467,349576.19]}}]}';
+ var features = protocol.parseResponse(request);
+ t.eq(features.length, 1, "the right format is used to read the request (GeoJSON)");
+ }
+
+ function test_outputFormat(t) {
+ t.plan(2);
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ version: "1.1.0",
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ outputFormat: 'json'
+ });
+
+ t.ok(protocol.readFormat instanceof OpenLayers.Format.GeoJSON, "the correct readFormat is used for outputFormat JSON");
+
+ protocol = new OpenLayers.Protocol.WFS({
+ version: "1.1.0",
+ url: "http://some.url.org",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ outputFormat: 'GML2'
+ });
+
+ t.ok(protocol.readFormat instanceof OpenLayers.Format.GML.v2, "the correct readFormat is used for outputFormat GML2");
+ }
+
+ function test_readOptions(t) {
+ t.plan(1);
+
+ var protocol = new OpenLayers.Protocol.WFS({
+ url: "http://some.url.org",
+ version: "1.1.0",
+ featureNS: "http://namespace.org",
+ featureType: "type",
+ readOptions: {'output': 'object'},
+ parseResponse: function(request, options) {
+ t.eq(options.output, "object", "Options object correctly set to pass on to Format's read");
+ }
+ });
+
+ var _POST = OpenLayers.Request.POST;
+
+ OpenLayers.Request.POST = function(obj) {
+ obj.status = 200;
+ obj.responseText = "foo";
+ t.delay_call(0.1, function() {obj.callback.call(this)});
+ return obj;
+ };
+
+ protocol.read({
+ callback: function() {}
+ });
+
+ OpenLayers.Request.POST = _POST;
+ }
+
+ function readXML(id) {
+ var xml = document.getElementById(id).firstChild.nodeValue;
+ return new OpenLayers.Format.XML().read(xml).documentElement;
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+<div id="GetFeature_1"><!--
+<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wfs:Query typeName="feature:type" xmlns:feature="http://namespace.org"/>
+</wfs:GetFeature>
+--></div>
+<div id="GetFeature_2"><!--
+<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0" maxFeatures="10" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wfs:Query typeName="feature:type2" xmlns:feature="htttp://alternative.namespace.org"/>
+</wfs:GetFeature>
+--></div>
+<div id="commit"><!--
+<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wfs:Insert>
+ <feature:type xmlns:feature="http://namespace.org">
+ <feature:the_geom>
+ <gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">
+ <gml:coordinates decimal="." cs="," ts=" ">42,7</gml:coordinates>
+ </gml:Point>
+ </feature:the_geom>
+ <feature:has>cheeseburger</feature:has>
+ </feature:type>
+ </wfs:Insert>
+ <wfs:Update typeName="feature:type" xmlns:feature="http://namespace.org">
+ <wfs:Property>
+ <wfs:Name>the_geom</wfs:Name>
+ <wfs:Value>
+ <gml:Point xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">
+ <gml:coordinates decimal="." cs="," ts=" ">42,7</gml:coordinates>
+ </gml:Point>
+ </wfs:Value>
+ </wfs:Property>
+ <wfs:Property>
+ <wfs:Name>has</wfs:Name>
+ <wfs:Value>cheeseburger</wfs:Value>
+ </wfs:Property>
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.37"/>
+ </ogc:Filter>
+ </wfs:Update>
+ <wfs:Delete typeName="feature:type" xmlns:feature="http://namespace.org">
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:FeatureId fid="fid.37"/>
+ </ogc:Filter>
+ </wfs:Delete>
+</wfs:Transaction>
+--></div>
+<div id="filter_delete"><!--
+<wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" service="WFS" version="1.0.0">
+ <wfs:Delete typeName="feature:type" xmlns:feature="http://namespace.org">
+ <ogc:Filter xmlns:ogc="http://www.opengis.net/ogc">
+ <ogc:BBOX>
+ <gml:Box xmlns:gml="http://www.opengis.net/gml" srsName="EPSG:4326">
+ <gml:coordinates decimal="." cs="," ts=" ">-5,-5 5,5</gml:coordinates>
+ </gml:Box>
+ </ogc:BBOX>
+ </ogc:Filter>
+ </wfs:Delete>
+</wfs:Transaction>
+--></div>
+<div id="query_response"><!--
+<?xml version="1.0" encoding="UTF-8"?>
+<wfs:FeatureCollection xmlns:ogc="http://www.opengis.net/ogc" xmlns:wfs="http://www.opengis.net/wfs" xmlns:topp="http://www.openplans.org/topp" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ows="http://www.opengis.net/ows" xmlns:gml="http://www.opengis.net/gml" xmlns:xlink="http://www.w3.org/1999/xlink"><gml:boundedBy><gml:Envelope srsDimension="2" srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:lowerCorner>5450000.0 500000.0</gml:lowerCorner><gml:upperCorner>5450000.0 540000.0</gml:upperCorner></gml:Envelope></gml:boundedBy><gml:featureMembers><topp:tasmania_roads gml:id="tasmania_roads.1"><gml:boundedBy><gml:Envelope srsDimension="2" srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:lowerCorner>5450000.0 500000.0</gml:lowerCorner><gml:upperCorner>5450000.0 540000.0</gml:upperCorner></gml:Envelope></gml:boundedBy><topp:geom><gml:MultiLineString srsDimension="2" srsName="urn:x-ogc:def:crs:EPSG:4326"><gml:lineStringMember><gml:LineString><gml:posList>5450000.0 500000.0 5450000.0 540000.0</gml:posList></gml:LineString></gml:lineStringMember></gml:MultiLineString></topp:geom><topp:TYPE>street</topp:TYPE></topp:tasmania_roads></gml:featureMembers></wfs:FeatureCollection>
+--></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/README.txt b/misc/openlayers/tests/README.txt
new file mode 100644
index 0000000..dc1f192
--- /dev/null
+++ b/misc/openlayers/tests/README.txt
@@ -0,0 +1,16 @@
+This directory contains unit tests for the OpenLayers library.
+
+Tests use the Test.AnotherWay library from <http://openjsan.org>. The test
+runner is 'run-tests.html' and new test files need to be added to
+'list-tests.html'.
+
+The following file naming conventions are used:
+
+ * A filename that starts with `test_` and has an `.html` extension
+ contains tests. These should contain tests for a specific class.
+
+ * A filename starting with `page_` and has an `.html` extension is a
+ supporting HTML file used in one or more tests.
+
+ * A filename starting with 'data_` is a supporting data file used in one
+ or more tests.
diff --git a/misc/openlayers/tests/Renderer.html b/misc/openlayers/tests/Renderer.html
new file mode 100644
index 0000000..4ec44f6
--- /dev/null
+++ b/misc/openlayers/tests/Renderer.html
@@ -0,0 +1,96 @@
+<html>
+<head>
+<script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Renderer_constructor(t) {
+ t.plan(2);
+ var el = document.body;
+ el.id = "foo";
+ var r = new OpenLayers.Renderer(el.id);
+
+ t.ok(r instanceof OpenLayers.Renderer, "new OpenLayers.Renderer returns Renderer object" );
+ t.ok(r.container == el, "renderer container is correctly set");
+ }
+
+ function test_Renderer_supported(t) {
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer();
+ t.eq(r.supported(), false, "supported returns false by default");
+ }
+
+ function test_Renderer_setextent(t) {
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer();
+ r.map = {};
+ var extent = new OpenLayers.Bounds(1,2,3,4);
+ r.resolution = 1;
+ r.setExtent(extent, true);
+ t.ok(r.extent.equals(extent), "extent is correctly set");
+ t.eq(r.resolution, null, "resolution nullified");
+ }
+
+ function test_Renderer_setsize(t) {
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer();
+ var size = new OpenLayers.Size(1,2);
+ r.resolution = 1;
+ r.setSize(size);
+ t.ok(r.size.equals(size), "size is correctly set");
+ t.eq(r.resolution, null, "resolution nullified");
+ }
+
+ function test_Renderer_getresolution(t) {
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer();
+ var map = new OpenLayers.Map("map");
+ r.map = map;
+ var resolution = r.getResolution();
+ t.eq(resolution, map.getResolution(), "resolution matches the map resolution");
+ t.eq(r.resolution, resolution, "resolution is correctly set");
+ }
+
+ function test_calculateFeatureDx(t) {
+ t.plan(4);
+ var r = new OpenLayers.Renderer();
+ r.extent = new OpenLayers.Bounds(177, -2, 183, 2);
+ var worldBounds = new OpenLayers.Bounds(-180,-90,180,90);
+ r.calculateFeatureDx(new OpenLayers.Bounds(179,-1,181,1), worldBounds);
+ t.eq(r.featureDx, 0, "no offset for feature inside extent");
+ r.calculateFeatureDx(new OpenLayers.Bounds(-181,-1,-179,1), worldBounds);
+ t.eq(r.featureDx, -360, "negative offset for feature on other end of world");
+ r.calculateFeatureDx(new OpenLayers.Bounds(359,-1,361,1), worldBounds);
+ t.eq(r.featureDx, 360, "positive offset for feature that is one world away");
+ r.calculateFeatureDx(new OpenLayers.Bounds(719,-1,721,1), worldBounds);
+ t.eq(r.featureDx, 720, "correct offset for feature that is two worlds away");
+ }
+
+ function test_Renderer_destroy(t) {
+ t.plan(5);
+
+ var r = new OpenLayers.Renderer();
+ r.container = document.createElement("div");
+ r.extent = new OpenLayers.Bounds(1,2,3,4);
+ r.size = new OpenLayers.Size(1,2);
+ r.resolution = 1;
+ r.map = {};
+
+ r.destroy();
+
+ t.eq(r.container, null, "container nullified");
+ t.eq(r.extent, null, "extent nullified");
+ t.eq(r.size, null, "size nullified");
+ t.eq(r.resolution, null, "resolution nullified");
+ t.eq(r.map, null, "map nullified");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Renderer/Canvas.html b/misc/openlayers/tests/Renderer/Canvas.html
new file mode 100644
index 0000000..f9a4c31
--- /dev/null
+++ b/misc/openlayers/tests/Renderer/Canvas.html
@@ -0,0 +1,501 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ var supported = OpenLayers.Renderer.Canvas.prototype.supported();
+
+ var map, layer;
+ function setUp() {
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ renderers: ["Canvas"]
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ }
+
+ function test_Renderer_Canvas_constructor(t) {
+ if (!supported) { t.plan(0); return; }
+ t.plan(2);
+ var el = document.body;
+ el.id = "foo";
+ var r = new OpenLayers.Renderer.Canvas(el.id);
+
+ t.ok(r instanceof OpenLayers.Renderer.Canvas, "new OpenLayers.Renderer.Canvas returns Renderer.Canvas object" );
+ t.ok(r.container == el, "renderer container is correctly set");
+ r.destroy();
+ }
+
+ function test_Renderer_Canvas_setextent(t) {
+ if (!supported) { t.plan(0); return; }
+ t.plan(2);
+
+ setUp();
+
+ var r = layer.renderer;
+ var extent = new OpenLayers.Bounds(1,2,3,4);
+ r.resolution = 1;
+ r.setExtent(extent, true);
+ t.ok(r.extent.equals(extent), "extent is correctly set");
+ t.eq(r.resolution, null, "resolution nullified");
+
+ tearDown();
+ }
+
+ function test_Renderer_Canvas_setsize(t) {
+ if (!supported) { t.plan(0); return; }
+ t.plan(2);
+
+ var el = document.body;
+ el.id = "foo";
+ var r = new OpenLayers.Renderer.Canvas(el.id);
+ var size = new OpenLayers.Size(1,2);
+ r.resolution = 1;
+ r.setSize(size);
+ t.ok(r.size.equals(size), "size is correctly set");
+ t.eq(r.resolution, null, "resolution nullified");
+ r.destroy();
+ }
+
+ function test_Renderer_Canvas_getresolution(t) {
+ if (!supported) { t.plan(0); return; }
+ t.plan(2);
+
+ var el = document.body;
+ el.id = "foo";
+ var r = new OpenLayers.Renderer.Canvas(el.id);
+ var map = new OpenLayers.Map("map");
+ r.map = map;
+ var resolution = r.getResolution();
+ t.eq(resolution, map.getResolution(), "resolution matches the map resolution");
+ t.eq(r.resolution, resolution, "resolution is correctly set");
+ map.destroy();
+ }
+
+ function test_featureIdToHex(t) {
+ if (!supported) {
+ t.plan(0);
+ return;
+ }
+ t.plan(2);
+ var el = document.body;
+ el.id = "foo";
+ var renderer = new OpenLayers.Renderer.Canvas(el.id);
+
+ var cases = [{
+ id: "foo_0", hex: "#000001"
+ }, {
+ id: "foo_10", hex: "#00000b"
+ }, {
+ id: "foo_100", hex: "#000065"
+ }, {
+ id: "foo_1000000", hex: "#0f4241"
+ }, {
+ id: "foo_16777214", hex: "#ffffff"
+ }, {
+ id: "foo_16777215", hex: "#000001"
+ }];
+ t.plan(cases.length);
+
+ var c;
+ for (var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ t.eq(renderer.featureIdToHex(c.id), c.hex, c.id);
+ }
+
+ renderer.destroy();
+ }
+
+
+ function test_Renderer_Canvas_destroy(t) {
+ if (!supported) { t.plan(0); return; }
+ t.plan(5);
+
+ var el = document.body;
+ el.id = "foo";
+ var r = new OpenLayers.Renderer.Canvas(el.id);
+ r.container = document.createElement("div");
+ r.extent = new OpenLayers.Bounds(1,2,3,4);
+ r.size = new OpenLayers.Size(1,2);
+ r.resolution = 1;
+ r.map = {};
+
+ r.destroy();
+
+ t.eq(r.container, null, "container nullified");
+ t.eq(r.extent, null, "extent nullified");
+ t.eq(r.size, null, "size nullified");
+ t.eq(r.resolution, null, "resolution nullified");
+ t.eq(r.map, null, "map nullified");
+ }
+
+ function test_drawFeature(t) {
+ if (!supported) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(10);
+
+ setUp();
+
+ var renderer = layer.renderer;
+ var count = 0;
+ var redraw = layer.renderer.redraw;
+ renderer.redraw = function() {
+ ++count;
+ redraw.apply(this, arguments);
+ }
+ var exp;
+
+ // a) draw a point feature
+ count = 0;
+ exp = renderer.drawFeature(
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 0)), {}
+ );
+ t.eq(exp, true, "a) drawFeature returns true");
+ t.eq(count, 1, "a) redraw called once after drawing a point feature");
+ renderer.clear();
+
+ // b) draw one feature with no geometry
+ count = 0;
+ exp = renderer.drawFeature(
+ new OpenLayers.Feature.Vector(), {}
+ );
+ t.eq(exp, undefined, "b) drawFeature returns undefined");
+ t.eq(count, 0, "b) redraw is not called when drawing a feature with no geometry");
+ renderer.clear();
+
+ // c) draw a point feature with display "none"
+ count = 0;
+ exp = renderer.drawFeature(
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1, 0)),
+ {display: "none"}
+ );
+ t.eq(exp, false, "c) drawFeature returns false");
+ t.eq(count, 1, "c) redraw is called when drawing a feature with display 'none'");
+ renderer.clear();
+
+ // d) draw a point feature outside renderer extent
+ count = 0;
+ exp = renderer.drawFeature(
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-1000, 0)), {}
+ );
+ t.eq(exp, false, "d) drawFeature returns false");
+ t.eq(count, 1, "d) redraw is called when drawing a feature outside renderer extent");
+ renderer.clear();
+
+ // e) draw a polygon feature without bounds
+ count = 0;
+ exp = renderer.drawFeature(
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon()), {}
+ );
+ t.eq(exp, false, "d) drawFeature returns false");
+ t.eq(count, 1, "d) redraw is called when drawing a feature without bounds");
+ renderer.clear();
+
+ tearDown();
+ }
+
+
+ function test_pendingRedraw(t) {
+ if (!supported) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ renderers: ["Canvas"]
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [],
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ var count = 0;
+ var redraw = layer.renderer.redraw;
+ layer.renderer.redraw = function() {
+ ++count;
+ redraw.apply(this, arguments);
+ }
+
+ // add one point feature and confirm redraw is called once
+ count = 0;
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 0))
+ ]);
+ t.eq(count, 1, "redraw called once after adding one point feature");
+
+ // add one feature with no geometry and confirm redraw is not called
+ count = 0;
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector()
+ ]);
+ t.eq(count, 0, "redraw is not called when adding a feature with no geometry");
+
+ // add one point feature, one feature with no geom, and one point feature and confirm redraw is called once
+ count = 0;
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1, 0)),
+ new OpenLayers.Feature.Vector(),
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 1))
+ ]);
+ t.eq(count, 1, "redraw called once after adding three features where middle one has no geometry");
+
+ // add two point features and one feature with no geom, and confirm redraw is called once
+ count = 0;
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(1, 0)),
+ new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 1)),
+ new OpenLayers.Feature.Vector()
+ ]);
+ t.eq(count, 1, "redraw called once after adding three features where last one has no geometry");
+
+ map.destroy();
+ }
+
+ function test_hitDetection(t) {
+ if (!supported) {
+ t.plan(0);
+ return;
+ }
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ resolutions: [1],
+ styleMap: new OpenLayers.StyleMap({
+ pointRadius: 5,
+ strokeWidth: 3,
+ fillColor: "red",
+ fillOpacity: 0.5,
+ strokeColor: "blue",
+ strokeOpacity: 0.75
+ }),
+ renderers: ["Canvas"]
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [],
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-100, 0)
+ ),
+ new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("LINESTRING(-50 0, 50 0)")
+ ),
+ new OpenLayers.Feature.Vector(
+ OpenLayers.Geometry.fromWKT("POLYGON((100 -25, 150 -25, 150 25, 100 25, 100 -25), (120 -5, 130 -5, 130 5, 120 5, 120 -5))")
+ ),
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(80, 0), {}, {
+ graphicName: "square",
+ pointRadius: 8,
+ strokeWidth: 4,
+ fillColor: "red",
+ fillOpacity: 0.5,
+ strokeColor: "blue",
+ strokeOpacity: 0.75
+ }
+ )
+ ]);
+
+ var cases = [{
+ msg: "center of point", x: -100, y: 0, id: layer.features[0].id
+ }, {
+ msg: "edge of point", x: -106, y: 0, id: layer.features[0].id
+ }, {
+ msg: "outside point", x: -110, y: 0, id: null
+ }, {
+ msg: "center of line", x: 0, y: 0, id: layer.features[1].id
+ }, {
+ msg: "edge of line", x: 0, y: 1, id: layer.features[1].id
+ }, {
+ msg: "outside line", x: 0, y: 5, id: null
+ }, {
+ msg: "inside polygon", x: 110, y: 0, id: layer.features[2].id
+ }, {
+ msg: "edge of polygon", x: 99, y: 0, id: layer.features[2].id
+ }, {
+ msg: "inside polygon hole", x: 125, y: 0, id: null
+ }, {
+ msg: "outside polygon", x: 155, y: 0, id: null
+ }, {
+ msg: "inside symbol", x: 80, y: 0, id: layer.features[3].id
+ }, {
+ msg: "outside symbol interior, inside symbol edge", x: 90, y: 8, id: layer.features[3].id
+ }, {
+ msg: "outside symbol", x: 94, y: 0, id: null
+ }];
+
+ function px(x, y) {
+ return map.getPixelFromLonLat(
+ new OpenLayers.LonLat(x, y)
+ );
+ }
+
+ var num = cases.length;
+ t.plan(2 * num);
+ var c, feature;
+ for (var i=0; i<num; ++i) {
+ c = cases[i];
+ feature = layer.renderer.getFeatureIdFromEvent({xy: px(c.x, c.y)});
+ t.eq(feature && feature.id, c.id, c.msg);
+
+ // Extra test: hit detection on an invisible canvas should return undefined
+ layer.setVisibility(false);
+ feature = layer.renderer.getFeatureIdFromEvent({xy: px(c.x, c.y)});
+ t.eq(feature, undefined, c.msg + ' (invisible)');
+ layer.setVisibility(true);
+ }
+
+ map.destroy();
+
+ }
+
+ // see http://trac.osgeo.org/openlayers/ticket/3264
+ function test_externalGraphic_destroyFeatures(t) {
+ if (!supported) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ // set up
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ renderers: ["Canvas"]
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [],
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0),
+ null,
+ {
+ externalGraphic: '../../img/marker.png',
+ graphicHeight: 20,
+ graphicWidth: 20
+ }
+ )
+ ]);
+
+ var called = false;
+ layer.renderer.canvas.drawImage = function(img, x, y, w, h) {
+ called = true;
+ };
+
+ // test
+
+ // schedule a canvas.drawImage
+ layer.renderer.redraw();
+
+ // destroy the feature before drawImage gets called
+ layer.destroyFeatures();
+
+ t.delay_call(0.1, function() {
+ t.ok(!called,
+ 'canvas.drawImage not called if feature is destroyed');
+
+ // tear down
+ map.destroy();
+ });
+ }
+
+ // see http://trac.osgeo.org/openlayers/ticket/3264
+ function test_externalGraphic_moveTo(t) {
+ if (!supported) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ // set up
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ renderers: ["Canvas"]
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ controls: [],
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 0
+ });
+
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0),
+ null,
+ {
+ externalGraphic: '../../img/marker.png',
+ graphicHeight: 20,
+ graphicWidth: 20,
+ graphicXOffset: 0,
+ graphicYOffset: 0
+ }
+ );
+
+ layer.addFeatures([feature]);
+
+ // test
+
+ // delay_call to let the first drawImage (the one
+ // resulting from addFeatures) run
+ t.delay_call(0.1, function() {
+
+ var log = [];
+ layer.renderer.canvas.drawImage = function(img, x, y, w, h) {
+ log.push({x: x, y: y});
+ };
+
+ layer.renderer.redraw();
+ map.setCenter(new OpenLayers.LonLat(45, 0), 0);
+
+ t.delay_call(0.1, function() {
+ t.eq(log.length, 2,
+ "canvas.drawImage called twice");
+ t.ok(log[0].x == log[1].x && log[0].y == log[1].y,
+ "image drawn at the same location");
+
+ // tear down
+ map.destroy();
+ });
+ });
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Renderer/Elements.html b/misc/openlayers/tests/Renderer/Elements.html
new file mode 100644
index 0000000..53590e2
--- /dev/null
+++ b/misc/openlayers/tests/Renderer/Elements.html
@@ -0,0 +1,651 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function setUp() {
+ // Stub out functions that are meant to be overridden by
+ // subclasses.
+ OpenLayers.Renderer.Elements.prototype._createRenderRoot =
+ OpenLayers.Renderer.Elements.prototype.createRenderRoot;
+
+ var rendererRoot = document.createElement("div");
+ OpenLayers.Renderer.Elements.prototype.createRenderRoot = function() {
+ return rendererRoot;
+ };
+
+ OpenLayers.Renderer.Elements.prototype._createRoot =
+ OpenLayers.Renderer.Elements.prototype.createRoot;
+
+ OpenLayers.Renderer.Elements.prototype.createRoot = function() {
+ return document.createElement("div");
+ };
+
+ OpenLayers.Renderer.Elements.prototype._createNode =
+ OpenLayers.Renderer.Elements.prototype.createNode;
+
+ OpenLayers.Renderer.Elements.prototype.createNode = function() {
+ return document.createElement("div");
+ };
+ }
+
+ // Create a new Elements renderer based on an id and an ordering
+ // type. For these tests, both of these parameters are optional.
+ function create_renderer(id, options) {
+
+ rendererRoot = null;
+
+ if (id == null) {
+ var el = document.createElement('div');
+ document.body.appendChild(el);
+ el.id = OpenLayers.Util.createUniqueID();
+ id = el.id;
+ }
+
+ return new OpenLayers.Renderer.Elements(id, options);
+ }
+
+ // Cleanup stubs made in the function above.
+ function tearDown() {
+ OpenLayers.Renderer.Elements.prototype.createRenderRoot =
+ OpenLayers.Renderer.Elements.prototype._createRenderRoot;
+ OpenLayers.Renderer.Elements.prototype.createRoot =
+ OpenLayers.Renderer.Elements.prototype._createRoot;
+ OpenLayers.Renderer.Elements.prototype.createNode =
+ OpenLayers.Renderer.Elements.prototype._createNode;
+ }
+
+ function test_Elements_constructor(t) {
+ t.plan(6);
+
+ setUp();
+
+ var r = create_renderer();
+
+ t.ok(r instanceof OpenLayers.Renderer.Elements, "new OpenLayers.Renderer.Elements returns Elements object" );
+ t.ok(r.rendererRoot != null, "elements rendererRoot is not null");
+ t.ok(r.root != null, "elements root is not null");
+ t.ok(r.indexer == null, "indexer is null if unused.");
+
+ t.ok(r.root.parentNode == r.rendererRoot, "elements root is correctly appended to rendererRoot");
+ t.ok(r.rendererRoot.parentNode == r.container, "elements rendererRoot is correctly appended to container");
+
+ tearDown();
+ }
+
+ function test_Elements_destroy(t) {
+ t.plan(5);
+
+ var elems = {
+ 'clear': function() {
+ t.ok(true, "clear called");
+ },
+ 'rendererRoot': {},
+ 'root': {},
+ 'xmlns': {}
+ };
+
+ OpenLayers.Renderer.prototype._destroy =
+ OpenLayers.Renderer.prototype.destroy;
+
+ var args = [{}, {}, {}];
+ OpenLayers.Renderer.prototype.destroy = function() {
+ t.ok((arguments[0] == args[0]) &&
+ (arguments[1] == args[1]) &&
+ (arguments[2] == args[2]), "correct arguments passed to OpenLayers.Renderer.destroy()");
+ };
+
+ OpenLayers.Renderer.Elements.prototype.destroy.apply(elems, args);
+
+ t.ok(elems.rendererRoot == null, "rendererRoot nullified");
+ t.ok(elems.root == null, "root nullified");
+ t.ok(elems.xmlns == null, "xmlns nullified");
+
+ OpenLayers.Renderer.prototype.destroy =
+ OpenLayers.Renderer.prototype._destroy;
+
+ }
+
+ function test_Elements_clear(t) {
+ t.plan(2);
+
+ setUp();
+
+ var r = create_renderer();
+ var element = document.createElement("div");
+ r.root = element;
+
+ var node = document.createElement("div");
+ element.appendChild(node);
+
+ r.clear();
+
+ t.ok(r.vectorRoot.childNodes.length == 0, "vector root is correctly cleared");
+ t.ok(r.textRoot.childNodes.length == 0, "text root is correctly cleared");
+
+ tearDown();
+ }
+
+ function test_Elements_drawGeometry(t) {
+ t.plan(7);
+
+ setUp();
+
+ var r = create_renderer();
+
+ var element = document.createElement("div");
+ r.vectorRoot = element;
+
+ r.nodeFactory = function(id, type) {
+ var element = document.createElement("div");
+ return element;
+ };
+ var g_Node = null;
+ var b_Node = null;
+ r.drawGeometryNode = function(node, geometry, style) {
+ g_Node = node;
+ return {node: node, complete: true};
+ };
+ r.redrawBackgroundNode = function(id, geometry, style, featureId) {
+ b_Node = r.nodeFactory();
+ b_Node.id = "foo_background";
+ element.appendChild(b_Node);
+ };
+
+ r.getNodeType = function(geometry, style) {
+ return "div";
+ };
+ var geometry = {
+ id: 'foo',
+ CLASS_NAME: 'bar',
+ getBounds: function() {return {bottom: 0}}
+ };
+ var style = {'backgroundGraphic': 'foo'};
+ var featureId = 'dude';
+ r.drawGeometry(geometry, style, featureId);
+ t.ok(g_Node.parentNode == element, "node is correctly appended to root");
+ t.ok(b_Node.parentNode == element, "redrawBackgroundNode appended background node");
+ t.eq(g_Node._featureId, 'dude', "_featureId is correct");
+ t.eq(g_Node._style.backgroundGraphic, "foo", "_style is correct");
+ t.eq(g_Node._geometryClass, 'bar', "_geometryClass is correct");
+
+ var returnNode = function(id) {
+ return id == "foo_background" ? b_Node : g_Node;
+ }
+
+ var _getElement = document.getElementById;
+ document.getElementById = returnNode;
+ OpenLayers.Util.getElement = returnNode;
+
+ style = {'display':'none'};
+ r.drawGeometry(geometry, style, featureId);
+ t.ok(g_Node.parentNode != element, "node is correctly removed");
+ t.ok(b_Node.parentNode != element, "background node correctly removed")
+
+ document.getElementById = _getElement;
+
+ tearDown();
+ }
+
+ function test_Elements_drawGeometry_2(t) {
+ t.plan(8);
+
+ setUp();
+
+ var geometry = {
+ getBounds: function() {return {bottom: 0}}
+ }
+
+ var r = create_renderer();
+
+ var element = document.createElement("div");
+ r.root = element;
+
+ r.nodeFactory = function(id, type) {
+ var element = document.createElement("div");
+ return element;
+ };
+ r.setStyle = function(node, style, options, geometry) {
+ return node;
+ };
+
+ // point
+ var properDraw = false;
+ r.drawPoint = function(node, geometry) {
+ properDraw = true;
+ return {};
+ };
+ var point = OpenLayers.Util.applyDefaults({CLASS_NAME: 'OpenLayers.Geometry.Point'}, geometry);
+ style = true;
+ r.drawGeometry(point, style);
+ t.ok(properDraw, "drawGeometry called drawPoint when passed a point");
+
+ // line string
+ var properDraw = false;
+ r.drawLineString = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var linestring = OpenLayers.Util.applyDefaults({id: "foo", CLASS_NAME: 'OpenLayers.Geometry.LineString'}, geometry);
+ style = true;
+ r.drawGeometry(linestring, style);
+ t.ok(properDraw, "drawGeometry called drawLineString when passed a line string");
+
+ // linear ring
+ var properDraw = false;
+ r.drawLinearRing = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var linearring = OpenLayers.Util.applyDefaults({CLASS_NAME: 'OpenLayers.Geometry.LinearRing'}, geometry);
+ style = true;
+ r.drawGeometry(linearring, style);
+ t.ok(properDraw, "drawGeometry called drawLinearRing when passed a linear ring");
+
+ // polygon
+ var properDraw = false;
+ r.drawPolygon = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var polygon = OpenLayers.Util.applyDefaults({CLASS_NAME: 'OpenLayers.Geometry.Polygon'}, geometry);
+ style = true;
+ r.drawGeometry(polygon, style);
+ t.ok(properDraw, "drawGeometry called drawPolygon when passed a polygon");
+
+ // rectangle
+ var properDraw = false;
+ r.drawRectangle = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var rectangle = OpenLayers.Util.applyDefaults({CLASS_NAME: 'OpenLayers.Geometry.Rectangle'}, geometry);
+ style = true;
+ r.drawGeometry(rectangle, style);
+ t.ok(properDraw, "drawGeometry called drawRectangle when passed a rectangle");
+
+ // multi-point
+ var properDraw = false;
+ r.drawPoint = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var multipoint = OpenLayers.Util.applyDefaults({
+ CLASS_NAME: 'OpenLayers.Geometry.MultiPoint',
+ components: [point]
+ }, geometry);
+ style = true;
+ r.drawGeometry(multipoint, style);
+ t.ok(properDraw, "drawGeometry called drawPoint when passed a multi-point");
+
+ // multi-linestring
+ var properDraw = false;
+ r.drawLineString = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var multilinestring = OpenLayers.Util.applyDefaults({
+ CLASS_NAME: 'OpenLayers.Geometry.MultiLineString',
+ components: [linestring]
+ }, geometry);
+ style = true;
+ r.drawGeometry(multilinestring, style);
+ t.ok(properDraw, "drawGeometry called drawLineString when passed a multi-linestring");
+
+ // multi-polygon
+ var properDraw = false;
+ r.drawPolygon = function(g) {
+ properDraw = true;
+ return {};
+ };
+ var multipolygon = OpenLayers.Util.applyDefaults({
+ CLASS_NAME: 'OpenLayers.Geometry.MultiPolygon',
+ components: [polygon]
+ }, geometry);
+ style = true;
+ r.drawGeometry(multipolygon, style);
+ t.ok(properDraw, "drawGeometry called drawPolygon when passed a multi-polygon");
+
+ tearDown();
+ }
+
+ function test_Elements_getfeatureidfromevent(t) {
+ t.plan(2);
+
+ var node = {
+ _featureId: 'foo'
+ };
+ var event = {
+ target: node
+ };
+
+ var id = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent(event);
+ t.eq(id, 'foo', "returned id is correct when event with target is passed");
+
+ var event = {
+ srcElement: node
+ };
+
+ var id = OpenLayers.Renderer.Elements.prototype.getFeatureIdFromEvent(event);
+ t.eq(id, 'foo', "returned id is correct when event with srcElement is passed");
+ }
+
+ function test_Elements_erasegeometry(t) {
+ t.plan(15);
+
+ var elements = {
+ 'eraseGeometry': function(geometry) {
+ gErased.push(geometry);
+ }
+ };
+
+ var geometry = {
+ 'components': [{}, {}, {}]
+ };
+
+ //multipoint
+ geometry.CLASS_NAME = "OpenLayers.Geometry.MultiPoint";
+ gErased = [];
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gErased[0] == geometry.components[0]) &&
+ (gErased[1] == geometry.components[1]) &&
+ (gErased[2] == geometry.components[2]), "multipoint all components of geometry correctly erased.");
+
+ //multilinestring
+ geometry.CLASS_NAME = "OpenLayers.Geometry.MultiLineString";
+ gErased = [];
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gErased[0] == geometry.components[0]) &&
+ (gErased[1] == geometry.components[1]) &&
+ (gErased[2] == geometry.components[2]), "multilinestring all components of geometry correctly erased.");
+
+ //multipolygon
+ geometry.CLASS_NAME = "OpenLayers.Geometry.MultiPolygon";
+ gErased = [];
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gErased[0] == geometry.components[0]) &&
+ (gErased[1] == geometry.components[1]) &&
+ (gErased[2] == geometry.components[2]), "multipolygon all components of geometry correctly erased.");
+
+ //collection
+ geometry.CLASS_NAME = "OpenLayers.Geometry.Collection";
+ gErased = [];
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gErased[0] == geometry.components[0]) &&
+ (gErased[1] == geometry.components[1]) &&
+ (gErased[2] == geometry.components[2]), "collection all components of geometry correctly erased.");
+
+
+ // OTHERS
+ //
+ geometry.CLASS_NAME = {};
+
+ gElement = null;
+ gBackElement = null;
+
+ OpenLayers.Util._getElement = OpenLayers.Util.getElement;
+ OpenLayers.Util.getElement = function(id) {
+ var retVal = null;
+ if (id != null) {
+ var hasBack = (id.indexOf(elements.BACKGROUND_ID_SUFFIX) != -1);
+ retVal = hasBack ? gBackElement : gElement;
+ }
+ return retVal;
+ };
+
+ //element null
+ geometry.id = null;
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ // (no tests here, just make sure it doesn't bomb)
+
+ //element.parentNode null
+ elements.BACKGROUND_ID_SUFFIX = 'BLAHBLAHBLAH';
+ geometry.id = "foo";
+ gElement = {};
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ // (no tests here, just make sure it doesn't bomb)
+
+ //valid element.parentNode, element.geometry
+ elements.indexer = {
+ 'remove': function(elem) {
+ gIndexerRemoved = elem;
+ }
+ };
+
+ gElement = {
+ 'geometry': {
+ 'destroy': function() {
+ t.ok(true, "geometry destroyed");
+ }
+ },
+ 'parentNode': {
+ 'removeChild': function(elem) {
+ gElemRemoved = elem;
+ }
+ },
+ '_style' : {backgroundGraphic: "foo"}
+ };
+ gBackElement = {
+ 'parentNode': {
+ 'removeChild': function(elem) {
+ gBackRemoved = elem;
+ }
+ }
+ };
+
+ gElemRemoved = gBackRemoved = gIndexerRemoved = null;
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gElement.geometry == null), "all normal: element's 'geometry' property nullified");
+ t.ok( (gElemRemoved == gElement), "all normal: main element properly removed from parent node");
+ t.ok( (gBackRemoved == gBackElement), "all normal: back element properly removed from parent node");
+ t.ok( (gIndexerRemoved == gElement), "all normal: main element properly removed from the indexer");
+
+ //valid element.parentNode, no element.geometry, no bElem
+ gBackElement = null;
+ gElement.geometry = null;
+ gElemRemoved = gBackRemoved = gIndexerRemoved = null;
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gElemRemoved == gElement), "no bElem: main element properly removed from parent node");
+ t.ok( (gBackRemoved == null), "no bElem: back element not tried to remove from parent node when it doesn't exist");
+ t.ok( (gIndexerRemoved == gElement), "no bElem: main element properly removed from the indexer");
+
+ //valid element.parentNode, no element.geometry, valid bElem, no bElem.parentNode
+ gBackElement = {};
+ gElemRemoved = gBackRemoved = gIndexerRemoved = null;
+ OpenLayers.Renderer.Elements.prototype.eraseGeometry.apply(elements, [geometry]);
+ t.ok( (gElemRemoved == gElement), "no bElem.parentNode: main element properly removed from parent node");
+ t.ok( (gBackRemoved == null), "no bElem.parentNode: back element not tried to remove from parent node when it has no parent node");
+ t.ok( (gIndexerRemoved == gElement), "no bElem.parentNode: main element properly removed from the indexer");
+
+
+ OpenLayers.Util.getElement = OpenLayers.Util._getElement;
+ }
+
+ function test_Elements_drawAndErase(t) {
+ t.plan(20);
+
+ setUp();
+
+ var r = create_renderer(null, {zIndexing: true});
+ var element = document.createElement("div");
+ r.vectorRoot = element;
+ document.body.appendChild(element);
+
+ r.createNode = function(type, id) {
+ var element = document.createElement("div");
+ element.id = id;
+ return element;
+ };
+ r.nodeTypeCompare = function() {return true};
+ r.setStyle = function(node, style, options, geometry) {
+ return node;
+ };
+
+ var geometry = {
+ id: 'foo',
+ CLASS_NAME: 'bar',
+ getBounds: function() {return {bottom: 0}}
+ };
+ var style = {
+ graphicZIndex: 10
+ };
+ var featureId = 'foo';
+ r.drawGeometry(geometry, style, featureId);
+
+ function count(obj) {
+ var result = 0;
+ for (var i in obj) {
+ result++;
+ }
+ return result;
+ }
+
+ t.eq(element.childNodes.length, 1, "root is correctly filled");
+ t.eq(r.indexer.maxZIndex, 10, "indexer.maxZIndex is correctly filled");
+ t.eq(r.indexer.order.length, 1, "indexer.order is correctly filled");
+ t.eq(count(r.indexer.indices), 1, "indexer.indices is correctly filled");
+
+ r.eraseGeometry(geometry);
+
+ t.eq(element.childNodes.length, 0, "root is correctly cleared");
+ t.eq(r.indexer.maxZIndex, 0, "indexer.maxZIndex is correctly reset");
+ t.eq(r.indexer.order.length, 0, "indexer.order is correctly reset");
+ t.eq(count(r.indexer.indices), 0, "indexer.indices is correctly reset");
+
+ delete(style.graphicZIndex);
+ r.drawGeometry(geometry, style, featureId);
+
+ t.eq(element.childNodes.length, 1, "root is correctly filled");
+ t.eq(r.indexer.maxZIndex, 0, "indexer.maxZIndex is correctly filled");
+ t.eq(r.indexer.order.length, 1, "indexer.order is correctly filled");
+ t.eq(count(r.indexer.indices), 1, "indexer.indices is correctly filled");
+
+ r.clear();
+
+ t.eq(element.childNodes.length, 0, "root is correctly cleared");
+ t.eq(r.indexer.maxZIndex, 0, "indexer.maxZIndex is correctly reset");
+ t.eq(r.indexer.order.length, 0, "indexer.order is correctly reset");
+ t.eq(count(r.indexer.indices), 0, "indexer.indices is correctly reset");
+
+ style.graphicZIndex = 12;
+ r.drawGeometry(geometry, style, featureId);
+
+ t.eq(element.childNodes.length, 1, "root is correctly filled");
+ t.eq(r.indexer.maxZIndex, 12, "indexer.maxZIndex is correctly filled");
+ t.eq(r.indexer.order.length, 1, "indexer.order is correctly filled");
+ t.eq(count(r.indexer.indices), 1, "indexer.indices is correctly filled");
+
+ tearDown();
+ }
+
+ function test_Elements_moveRoot(t) {
+ t.plan(2);
+ setUp();
+ var r1 = create_renderer();
+ var r2 = create_renderer();
+ r1.moveRoot(r2);
+ t.xml_eq(r1.root.parentNode, r2.root.parentNode, "root moved successfully");
+ r1.moveRoot(r1);
+ t.xml_eq(r1.root.parentNode, r1.rendererRoot, "root moved back successfully");
+ tearDown();
+ }
+
+ function test_Elements_drawGeometry_3(t) {
+ t.plan(2);
+
+ setUp();
+
+ var r = create_renderer();
+
+ var element = document.createElement("div");
+ r.vectorRoot = element;
+
+ r.nodeFactory = function(id, type) {
+ return document.createElement("div");
+ };
+ var g_Node = null;
+ var b_Node = null;
+ r.drawGeometryNode = function(node, geometry, style) {
+ g_Node = node;
+ return {node: node, complete: true};
+ };
+ r.redrawBackgroundNode = function(id, geometry, style, featureId) {
+ b_Node = r.nodeFactory();
+ b_Node.id = "foo_background";
+ element.appendChild(b_Node);
+ };
+
+ r.getNodeType = function(geometry, style) {
+ return "div";
+ };
+ var geometry = {
+ id: 'foo',
+ CLASS_NAME: 'bar',
+ getBounds: function() {return {bottom: 0}}
+ };
+ var style = {'backgroundGraphic': 'foo'};
+ var featureId = 'dude';
+ r.drawGeometry(geometry, style, featureId);
+ t.ok(b_Node.parentNode == element, "redrawBackgroundNode appended background node");
+
+ var returnNode = function(id) {
+ return id == "foo_background" ? b_Node : g_Node;
+ }
+
+ var _getElement = document.getElementById;
+ document.getElementById = returnNode;
+ OpenLayers.Util.getElement = returnNode;
+
+ style = {};
+ r.drawGeometry(geometry, style, featureId);
+ t.ok(b_Node.parentNode != element, "background node correctly removed")
+
+ document.getElementById = _getElement;
+
+ tearDown();
+ }
+
+ function test_setExtent(t) {
+ t.plan(10);
+ setUp();
+ var resolution = 1;
+ var r = create_renderer();
+ r.map = {
+ getMaxExtent: function() {
+ return new OpenLayers.Bounds(-180,-90,180,90);
+ },
+ getExtent: function() {
+ return r.extent;
+ },
+ getResolution: function() {
+ return resolution;
+ },
+ baseLayer: {wrapDateLine: true}
+ }
+
+ r.setExtent(new OpenLayers.Bounds(179, -1, 182, 1), true);
+ t.eq(r.rightOfDateLine, true, "on the right side of the dateline");
+ t.eq(r.xOffset, r.map.getMaxExtent().getWidth(), "correct xOffset");
+ r.setExtent(new OpenLayers.Bounds(179.5, -1, 182.5, 1), false);
+ t.eq(r.rightOfDateLine, true, "still on the right side of the dateline");
+ t.eq(r.xOffset, r.map.getMaxExtent().getWidth(), "still correct xOffset");
+ resolution = 2;
+ r.setExtent(new OpenLayers.Bounds(178, -2, 184, 2), true);
+ t.eq(r.rightOfDateLine, true, "still on the right side of the dateline");
+ t.eq(r.xOffset, r.map.getMaxExtent().getWidth() / resolution, "xOffset adjusted for new resolution");
+ r.setExtent(new OpenLayers.Bounds(-184, -2, 178, 2), false);
+ t.eq(r.rightOfDateLine, false, "on the left side of the dateline");
+ t.eq(r.xOffset, 0, "no xOffset");
+ r.setExtent(new OpenLayers.Bounds(178, -2, 184, 2), true);
+ t.eq(r.rightOfDateLine, true, "back on the right side of the dateline");
+ t.eq(r.xOffset, r.map.getMaxExtent().getWidth() / resolution, "correct xOffset");
+
+ tearDown();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Renderer/SVG.html b/misc/openlayers/tests/Renderer/SVG.html
new file mode 100644
index 0000000..31eb058
--- /dev/null
+++ b/misc/openlayers/tests/Renderer/SVG.html
@@ -0,0 +1,441 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var geometry = null, node = null;
+
+ function test_SVG_constructor(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ t.ok(r instanceof OpenLayers.Renderer.SVG, "new OpenLayers.Renderer.SVG returns SVG object" );
+ }
+
+ function test_SVG_destroy(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var g_Destroy = false;
+
+ OpenLayers.Renderer.Elements.prototype._destroy =
+ OpenLayers.Renderer.Elements.prototype.destroy;
+
+ OpenLayers.Renderer.prototype.destroy = function() {
+ g_Destroy = true;
+ }
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ r.destroy();
+
+ t.eq(g_Destroy, true, "OpenLayers.Renderer.Elements.destroy() called");
+
+ OpenLayers.Renderer.prototype.destroy =
+ OpenLayers.Renderer.prototype._destroy;
+ }
+
+ function test_SVG_setextent(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ OpenLayers.Renderer.Elements.prototype._setExtent =
+ OpenLayers.Renderer.Elements.prototype.setExtent;
+
+ var g_SetExtent = false;
+ OpenLayers.Renderer.Elements.prototype.setExtent = function() {
+ g_SetExtent = true;
+ }
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ r.setSize(new OpenLayers.Size(4,4));
+ r.map = {
+ getResolution: function() {
+ return 0.5;
+ }
+ }
+
+ var extent = new OpenLayers.Bounds(1,2,3,4);
+ r.setExtent(extent);
+
+ t.eq(g_SetExtent, true, "Elements.setExtent() called");
+
+ t.eq(r.left, -2, "left is correct");
+ t.eq(r.top, 8, "top is correct");
+
+ t.eq(r.rendererRoot.getAttributeNS(null, "viewBox"), "0 0 4 4", "rendererRoot viewBox is correct");
+
+ // test extent changes
+ var extent = new OpenLayers.Bounds(4,3,2,1);
+ r.setExtent(extent);
+ var el = r.createNode("g");
+ el.setAttributeNS(null, "transform", "translate(-6,-6)");
+ t.eq(r.root.getAttributeNS(null, "transform"), el.getAttributeNS(null, "transform"), "rendererRoot viewBox is correct after a new setExtent");
+
+ OpenLayers.Renderer.Elements.prototype.setExtent =
+ OpenLayers.Renderer.Elements.prototype._setExtent;
+ }
+
+ function test_SVG_setsize(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ var size = new OpenLayers.Size(1,2);
+ r.setSize(size);
+ t.eq(r.rendererRoot.getAttributeNS(null, "width"), size.w.toString(), "width is correct");
+ t.eq(r.rendererRoot.getAttributeNS(null, "height"), size.h.toString(), "height is correct");
+ }
+
+ function test_SVG_drawpoint(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ var properDraw = false;
+ var g_Radius = null;
+ r.drawCircle = function(n, g, r) {
+ properDraw = true;
+ g_Radius = 1;
+ }
+ r.drawPoint();
+
+ t.ok(properDraw && g_Radius == 1, "drawPoint called drawCircle with radius set to 1");
+ }
+
+ function test_SVG_drawcircle(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ r.resolution = 0.5;
+ r.left = 0;
+ r.top = 0;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ x: 1,
+ y: 2
+ }
+
+ r.drawCircle(node, geometry, 3);
+
+ t.eq(node.getAttributeNS(null, 'cx'), '2', "cx is correct");
+ t.eq(node.getAttributeNS(null, 'cy'), '-4', "cy is correct");
+ t.eq(node.getAttributeNS(null, 'r'), '3', "r is correct");
+
+ // #1274: out of bound node fails when first added
+ var geometry = {
+ x: 10000000,
+ y: 200000000,
+ CLASS_NAME: "OpenLayers.Geometry.Point",
+ id: "foo",
+ getBounds: function() {return {bottom: 0}}
+ }
+ node.id = geometry.id;
+ r.root.appendChild(node);
+
+ var drawCircleCalled = false;
+ r.drawCircle = function() {
+ drawCircleCalled = true;
+ return OpenLayers.Renderer.SVG.prototype.drawCircle.apply(r, arguments);
+ }
+
+ r.drawGeometry(geometry, {pointRadius: 3}, "blah_4000");
+ t.eq(drawCircleCalled, true, "drawCircle called on drawGeometry for a point geometry.")
+ t.ok(node.parentNode != r.root, "circle will not be drawn when coordinates are outside the valid range");
+ }
+
+ function test_SVG_drawlinestring(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ components: "foo"
+ }
+ g_GetString = false;
+ g_Components = null;
+ r.getComponentsString = function(c) {
+ g_GetString = true;
+ g_Components = c;
+ return {path: "bar", complete: true};
+ }
+
+ r.drawLineString(node, geometry);
+
+ t.ok(g_GetString && g_Components == "foo", "getComponentString is called with valid arguments");
+ t.eq(node.getAttributeNS(null, "points"), "bar", "points attribute is correct");
+ }
+
+ function test_SVG_drawlinearring(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ components: "foo"
+ }
+ g_GetString = false;
+ g_Components = null;
+ r.getComponentsString = function(c) {
+ g_GetString = true;
+ g_Components = c;
+ return {path: "bar", complete: true};
+ }
+
+ r.drawLinearRing(node, geometry);
+
+ t.ok(g_GetString, "getComponentString is called with valid arguments");
+ t.eq(node.getAttributeNS(null, "points"), "bar", "points attribute is correct");
+ }
+
+ function test_SVG_drawpolygon(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ var node = document.createElement('div');
+
+ var linearRings = [{
+ components: ["foo"]
+ },{
+ components: ["bar"]
+ }]
+
+ var geometry = {
+ components: linearRings
+ }
+ g_GetString = false;
+ r.getShortString = function(c) {
+ g_GetString = true;
+ return c;
+ }
+
+ r.drawPolygon(node, geometry);
+
+ t.ok(g_GetString, "getShortString is called");
+ t.eq(node.getAttributeNS(null, "d"), " M foo M bar z", "d attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "fill-rule"), "evenodd", "fill-rule attribute is correctly set");
+
+ r.getShortString = function(c) {
+ return false;
+ }
+ t.eq(r.drawPolygon(node, geometry), false, "drawPolygon returns false if one linearRing cannot be drawn");
+ }
+
+ function test_SVG_drawrectangle(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ r.resolution = 0.5;
+ r.left = 0;
+ r.top = 0;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ x: 1,
+ y: 2,
+ width: 3,
+ height: 4
+ }
+
+ r.drawRectangle(node, geometry);
+
+ t.eq(node.getAttributeNS(null, "x"), "2", "x attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "y"), "-4", "y attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "width"), "6", "width attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "height"), "8", "height attribute is correctly set");
+ }
+
+ function test_SVG_getcomponentsstring(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var components = ['foo', 'bar'];
+
+ OpenLayers.Renderer.SVG.prototype._getShortString =
+ OpenLayers.Renderer.SVG.prototype.getShortString;
+
+ OpenLayers.Renderer.SVG.prototype.getShortString = function(p) {
+ return p;
+ };
+
+ var string = OpenLayers.Renderer.SVG.prototype.getComponentsString(components).path;
+ t.eq(string, "foo,bar", "returned string is correct");
+
+ OpenLayers.Renderer.SVG.prototype.getShortString =
+ OpenLayers.Renderer.SVG.prototype._getShortString;
+ }
+
+
+
+ function test_SVG_getshortstring(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ r.resolution = 0.5;
+ r.left = 0;
+ r.top = 0;
+
+ var point = {
+ x: 1,
+ y: 2
+ };
+
+ var string = r.getShortString(point);
+ t.eq(string, "2,-4", "returned string is correct");
+ }
+
+ function test_svg_importsymbol(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ r.importSymbol("square");
+
+ var polygon = document.getElementById(r.container.id + "_defs").firstChild.firstChild;
+
+ var pass = false;
+ for (var i = 0; i < polygon.points.numberOfItems; i++) {
+ var p = polygon.points.getItem(i);
+ pass = p.x === OpenLayers.Renderer.symbol.square[2*i] &&
+ p.y === OpenLayers.Renderer.symbol.square[2*i+1];
+ if (!pass) {
+ break;
+ }
+ }
+ t.ok(pass, "Square symbol rendered correctly");
+ t.ok(r.symbolMetrics["-square"], "Symbol metrics cached correctly.");
+ }
+
+ function test_svg_dashstyle(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "dot"}, 1), "1,4", "dot dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "dash"}, 1), "4,4", "dash dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "longdash"}, 1), "8,4", "longdash dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "dashdot"}, 1), "4,4,1,4", "dashdot dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "longdashdot"}, 1), "8,4,1,4", "dashdot dasharray created correctly");
+ }
+
+ function test_svg_clipline(t) {
+ if (!OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(3);
+
+ var r = new OpenLayers.Renderer.SVG(document.body);
+ r.setSize(new OpenLayers.Size(0, 0));
+ r.map = {
+ getResolution: function() {
+ return 0.5;
+ }
+ }
+ r.setExtent(new OpenLayers.Bounds(0, 0, 0, 0));
+
+ var node = document.createElement('div');
+
+ var geometry = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0, -5000),
+ new OpenLayers.Geometry.Point(10000, 0),
+ new OpenLayers.Geometry.Point(0, 5000)
+ ]);
+ r.drawLineString(node, geometry);
+ t.eq(node.getAttribute("points"), "0,10000,15000,2500,15000,-2500,0,-10000", "Line with 3 points correctly clipped at inValidRange bounds");
+
+ geometry = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(0, -5000),
+ new OpenLayers.Geometry.Point(10000, 0)
+ ]);
+ r.drawLineString(node, geometry);
+ t.eq(node.getAttribute("points"), "0,10000,15000,2500", "2-point line with 2nd point outside range correctly clipped at inValidRange bounds");
+
+ var geometry = new OpenLayers.Geometry.LineString([
+ new OpenLayers.Geometry.Point(10000, 0),
+ new OpenLayers.Geometry.Point(0, 5000)
+ ]);
+ r.drawLineString(node, geometry);
+ t.eq(node.getAttribute("points"), "15000,-2500,0,-10000", "2-point line with 1st point outside range correctly clipped at inValidRange bounds");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Renderer/VML.html b/misc/openlayers/tests/Renderer/VML.html
new file mode 100644
index 0000000..2bdc876
--- /dev/null
+++ b/misc/openlayers/tests/Renderer/VML.html
@@ -0,0 +1,454 @@
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var geometry = null, node = null;
+
+ function test_VML_constructor(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+ var r = new OpenLayers.Renderer.VML(document.body);
+ t.ok(r instanceof OpenLayers.Renderer.VML, "new OpenLayers.Renderer.VML returns VML object" );
+ }
+
+ function test_VML_destroy(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var g_Destroy = false;
+
+ OpenLayers.Renderer.Elements.prototype._destroy =
+ OpenLayers.Renderer.Elements.prototype.destroy;
+
+ OpenLayers.Renderer.prototype.destroy = function() {
+ g_Destroy = true;
+ }
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.destroy();
+
+ t.eq(g_Destroy, true, "OpenLayers.Renderer.Elements.destroy() called");
+
+ OpenLayers.Renderer.prototype.destroy =
+ OpenLayers.Renderer.prototype._destroy;
+ }
+
+ function test_VML_setextent(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ OpenLayers.Renderer.Elements.prototype._setExtent =
+ OpenLayers.Renderer.Elements.prototype.setExtent;
+
+ var g_SetExtent = false;
+ OpenLayers.Renderer.Elements.prototype.setExtent = function() {
+ g_SetExtent = true;
+ }
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.setSize(new OpenLayers.Size(4,4));
+ r.map = {
+ getResolution: function() {
+ return 0.5;
+ }
+ }
+
+ var extent = new OpenLayers.Bounds(1,2,3,4);
+ r.setExtent(extent);
+
+ t.eq(g_SetExtent, true, "Elements.setExtent() called");
+
+ t.ok(r.root.coordorigin == "0,0", "coordorigin is correct");
+ t.ok(r.root.coordsize == "4,4", "coordsize is correct");
+ t.eq(r.offset, {x:2, y:4}, "offset is correct");
+
+ OpenLayers.Renderer.Elements.prototype.setExtent =
+ OpenLayers.Renderer.Elements.prototype._setExtent;
+ }
+
+ function test_VML_setsize(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ var size = new OpenLayers.Size(1,2);
+ r.setSize(size);
+ t.eq(r.rendererRoot.style.width, "1px", "rendererRoot width is correct");
+ t.eq(r.rendererRoot.style.height, "2px", "rendererRoot height is correct");
+
+ t.eq(r.root.style.width, "1px", "root width is correct");
+ t.eq(r.root.style.height, "2px", "root height is correct");
+ }
+
+ function test_VML_drawText(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.offset = {x: 0, y: 0};
+
+ r.LABEL_ID_SUFFIX = "";
+ r.getResolution = function() {
+ return 1;
+ };
+
+ var style = {
+ label: "myText",
+ fontColor: "blue"
+ }
+
+ r.drawText("feature1", style, new OpenLayers.Geometry.Point(1,1));
+
+ var textbox = document.getElementById("feature1_textbox");
+
+ t.eq(textbox.innerText, style.label, "label set correctly");
+ t.eq(textbox.style.color, style.fontColor, "font color of label set correctly");
+ }
+
+ function test_VML_drawpoint(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ var properDraw = false;
+ var g_Radius = null;
+ r.drawCircle = function(n, g, r) {
+ properDraw = true;
+ g_Radius = 1;
+ }
+ r.drawPoint();
+
+ t.ok(properDraw && g_Radius == 1, "drawPoint called drawCircle with radius set to 1");
+ }
+
+ function test_VML_drawcircle(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.offset = {x: 0, y: 0};
+ r.resolution = 0.5;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ x: 1,
+ y: 2
+ }
+
+ var radius = 3;
+ r.drawCircle(node, geometry, radius);
+
+ t.eq(node.style.left, '-1px', "left is correct");
+ t.eq(node.style.top, '1px', "top is correct");
+ t.eq(node.style.width, (2 * radius) + "px", "width is correct");
+ t.eq(node.style.height, (2 * radius) + "px", "height is correct");
+ }
+
+ function test_VML_drawGraphic(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(6);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.offset = {x: 0, y: 0};
+ r.resolution = 1;
+
+ var node = document.createElement('div');
+ node.id = "test"
+ node._geometryClass = "OpenLayers.Geometry.Point";
+
+ var geometry = {
+ x: 1,
+ y: 2
+ }
+
+ var style = {
+ externalGraphic: "foo.png",
+ graphicWidth: 7,
+ graphicHeight: 10
+ }
+
+ r.drawGeometryNode(node, geometry, style);
+
+ t.eq(node.childNodes[0].id, "test_fill", "fill child node correctly created");
+ t.eq(node.style.left, "-2px", "x of insertion point with calculated xOffset correct");
+ t.eq(node.style.top, "-3px", "y of insertion point with calculated yOffset correct");
+
+ style.rotation = 90;
+
+ r.drawGeometryNode(node, geometry, style);
+
+ t.eq(node.childNodes[1].id, "test_image", "image child node correctly created");
+ t.eq(node.style.left, "-3px", "x of insertion point of rotated image correct");
+ t.eq(node.style.top, "-4px", "y of insertion point of rotated image correct");
+ }
+
+ function test_VML_drawlinestring(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ g_DrawLine = false;
+ r.drawLine = function(c) {
+ g_DrawLine = true;
+ }
+
+ r.drawLineString(node, geometry);
+
+ t.ok(g_DrawLine, "drawLine is called");
+ }
+
+ function test_VML_drawlinearring(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ g_DrawLine = false;
+ r.drawLine = function(c) {
+ g_DrawLine = true;
+ }
+
+ r.drawLinearRing(node, geometry);
+
+ t.ok(g_DrawLine, "drawLine is called");
+ }
+
+ function test_VML_drawline(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(8);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.offset = {x: 0, y: 0};
+ r.resolution = 0.5;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ components: [{
+ x: 1,
+ y: 2
+ },{
+ x: 3,
+ y: 4
+ }],
+ getBounds: function() {
+ return new OpenLayers.Bounds(5,6,7,8);
+ }
+ };
+
+ r.drawLine(node, geometry, true);
+ t.ok(node.path.indexOf("x") != -1, "path attribute is correct when passed closeLine = true");
+
+
+ r.drawLine(node, geometry, false);
+ t.eq(node.path, "m 2,4 l 6,8 l e", "path attribute is correct");
+ t.eq(node.style.left, "10px", "node.style.left is correct");
+ t.eq(node.style.top, "16px", "node.style.top is correct");
+ t.eq(node.style.width, "4px", "node.style.width is correct");
+ t.eq(node.style.height, "4px", "node.style.height is correct");
+ t.eq(node.coordorigin, "10 16", "node.coordorigin is correct");
+ t.eq(node.coordsize, "4 4", "node.coordsize is correct");
+ }
+
+ function test_VML_drawpolygon(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(3);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.offset = {x: 0, y: 0};
+ r.resolution = 0.5;
+
+ g_SetNodeDimension = false;
+ r.setNodeDimension = function(){
+ g_SetNodeDimension = true;
+ };
+
+ var node = document.createElement('div');
+
+ var geometry = OpenLayers.Geometry.fromWKT(
+ "POLYGON((1 2, 3 4), (5 6, 7 8))"
+ );
+ r.drawPolygon(node, geometry, true);
+ t.ok(g_SetNodeDimension, "setNodeDimension is called");
+ t.eq(node.path, "m 2,4 l 6,8 2,4 x m 10,12 l 14,16 10,12 e", "path attribute is correct - inner ring has no area and is not closed");
+
+ geometry.components[1].addComponent(new OpenLayers.Geometry.Point(8, 7));
+ r.drawPolygon(node, geometry, true);
+ t.eq(node.path, "m 2,4 l 6,8 2,4 x m 10,12 l 14,16 16,14 10,12 x e", "path attribute is correct - inner ring has an area and is closed");
+ }
+
+ function test_VML_drawrectangle(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+ r.offset = {x: 0, y: 0};
+ r.resolution = 0.5;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ x: 1,
+ y: 2,
+ width: 3,
+ height: 4
+ }
+
+ r.drawRectangle(node, geometry);
+
+ t.eq(node.style.left, "2px", "node.style.left is correct");
+ t.eq(node.style.top, "4px", "node.style.top is correct");
+ t.eq(node.style.width, "6px", "node.style.width is correct");
+ t.eq(node.style.height, "8px", "node.style.height is correct");
+ }
+
+ function test_vml_getnodetype(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ var g = {CLASS_NAME: "OpenLayers.Geometry.Point"}
+ var s = {graphicName: "square"};
+
+ t.eq(r.getNodeType(g, s), "olv:shape", "Correct node type for well known symbols");
+ }
+
+ function test_vml_importsymbol(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ OpenLayers.Renderer.symbol.rect1 = [0,0, 10,0, 10,4, 0,4, 0,0];
+ OpenLayers.Renderer.symbol.rect2 = [0,0, 4,0, 4,10, 0,10, 0,0];
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ var cache = r.importSymbol("square");
+
+ t.eq(cache.path, "m 0 0 l 0 1 1 1 1 0 0 0 x e", "Square symbol rendered correctly");
+ t.ok(r.symbolCache["-square"], "Symbol has been cached correctly.");
+
+ cache = r.importSymbol("rect1");
+ t.eq(cache.bottom, -3, "coordorigin bottom of landscape symbol set to -3 to move topmost part to the bottom (we are flipping y!)");
+
+ cache = r.importSymbol("rect2");
+ t.eq(cache.left, -3, "coordorigin left of portrait symbol set to -3 to move leftmost part to the right");
+
+ delete OpenLayers.Renderer.symbol.rect1;
+ delete OpenLayers.Renderer.symbol.rect2;
+
+ }
+
+ function test_vml_dashstyle(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ var r = new OpenLayers.Renderer.VML(document.body);
+
+ t.eq(r.dashStyle({strokeDashstyle: "1 4"}), "dot", "dot pattern recognized correctly.");
+ t.eq(r.dashStyle({strokeDashstyle: "4 4"}), "dash", "dash pattern recognized correctly.");
+ t.eq(r.dashStyle({strokeDashstyle: "8 4"}), "longdash", "longdash pattern recognized correctly.");
+ t.eq(r.dashStyle({strokeDashstyle: "4 4 1 4"}), "dashdot", "dashdot pattern recognized correctly.");
+ t.eq(r.dashStyle({strokeDashstyle: "8 4 1 4"}), "longdashdot", "longdashdot pattern recognized correctly.");
+ }
+
+ function test_vml_moveRoot(t) {
+ if (!OpenLayers.Renderer.VML.prototype.supported() || OpenLayers.Renderer.SVG.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+ t.plan(2);
+
+ var map = new OpenLayers.Map("map");
+ var l1 = new OpenLayers.Layer.Vector("vector");
+ map.addLayer(l1);
+ var l2 = new OpenLayers.Layer.Vector.RootContainer("rootcontainer", {layers: [l1]});
+
+ var clear = l1.renderer.clear;
+ l1.renderer.clear = function() {
+ // this should be called twice, once when l2 is added to the map,
+ // and once when removed from the map.
+ t.ok(true, "Clearing original layer");
+ };
+ map.addLayer(l2);
+ map.removeLayer(l2);
+ l1.renderer.clear = clear;
+
+ map.removeLayer(l1);
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Request.html b/misc/openlayers/tests/Request.html
new file mode 100644
index 0000000..cd679c6
--- /dev/null
+++ b/misc/openlayers/tests/Request.html
@@ -0,0 +1,524 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ function setup() {
+ window._xhr = OpenLayers.Request.XMLHttpRequest;
+ var anon = new Function();
+ OpenLayers.Request.XMLHttpRequest = function() {};
+ OpenLayers.Request.XMLHttpRequest.prototype = {
+ open: anon,
+ setRequestHeader: anon,
+ send: anon
+ };
+ OpenLayers.Request.XMLHttpRequest.DONE = 4;
+ }
+ function teardown() {
+ OpenLayers.Request.XMLHttpRequest = window._xhr;
+ }
+
+ function test_defaultHeaders(t) {
+ setup();
+ t.plan(2);
+ var config = {
+ headers: {
+ x: 'y'
+ }
+ };
+ OpenLayers.Request.DEFAULT_CONFIG.headers = {
+ foo: 'bar'
+ };
+ var proto = OpenLayers.Request.XMLHttpRequest.prototype;
+ var issue = OpenLayers.Function.bind(OpenLayers.Request.issue,
+ OpenLayers.Request);
+
+ var headers = {};
+ var _setRequestHeader = proto.setRequestHeader;
+ proto.setRequestHeader = function(key, value) {
+ headers[key] = value;
+ };
+ request = issue(config);
+ t.eq(headers.foo, 'bar', "Header from DEFAULT_CONFIG set correctly");
+ t.eq(headers.x, 'y', "Header from config set correctly");
+ proto.setRequestHeader = _setRequestHeader;
+ OpenLayers.Request.DEFAULT_CONFIG.headers = {};
+ teardown();
+ }
+
+ function test_issue(t) {
+ setup();
+
+ t.plan(25);
+ var request, config;
+ var proto = OpenLayers.Request.XMLHttpRequest.prototype;
+ var issue = OpenLayers.Function.bind(OpenLayers.Request.issue,
+ OpenLayers.Request);
+
+ // test that issue returns a new XMLHttpRequest - 1 test
+ request = issue();
+ t.ok(request instanceof OpenLayers.Request.XMLHttpRequest,
+ "returns an XMLHttpRequest instance");
+
+ // test that issue calls xhr.open with correct args from config - 5 tests
+ var _open = proto.open;
+ config = {
+ method: "foo",
+ url: "http://nowhere",
+ async: "bar",
+ user: "uncle",
+ password: "sam"
+ };
+ proto.open = function(method, url, async, user, password) {
+ t.eq(method, config.method, "open called with correct method");
+ t.eq(url, config.url, "open called with correct url");
+ t.eq(async, config.async, "open called with correct async");
+ t.eq(user, config.user, "open called with correct user");
+ t.eq(password, config.password, "open called with correct password");
+ };
+ request = issue(config);
+
+ // test that params are serialized as query string - 1 test
+ config = {
+ method: "GET",
+ url: "http://example.com/",
+ params: {"foo": "bar"}
+ };
+ proto.open = function(method, url, async, user, password) {
+ t.eq(url, config.url + "?foo=bar", "params serialized as query string");
+ };
+ request = issue(config);
+
+ // test that empty params object doesn't produce query string - 1 test
+ config = {
+ method: "GET",
+ url: "http://example.com/",
+ params: {}
+ };
+ proto.open = function(method, url, async, user, password) {
+ t.eq(url, config.url, "empty params doesn't produce query string");
+ }
+ request = issue(config);
+
+ // test that query string doesn't get two ? separators
+ config = {
+ method: "GET",
+ url: "http://example.com/?existing=query",
+ params: {"foo": "bar"}
+ };
+ proto.open = function(method, url, async, user, password) {
+ t.eq(url, config.url + "&foo=bar", "existing query string gets extended with &");
+ }
+ request = issue(config);
+
+ // test that query string doesn't get ? followed by &
+ config = {
+ method: "GET",
+ url: "http://example.com/service?",
+ params: {"foo": "bar"}
+ };
+ proto.open = function(method, url, async, user, password) {
+ t.eq(url, config.url + "foo=bar", "existing query string ending with ? gets extended without &");
+ }
+ request = issue(config);
+
+ // reset open method
+ proto.open = _open;
+
+ // test that headers are correctly set - 6 tests
+ var _setRequestHeader = proto.setRequestHeader;
+ config = {
+ headers: {
+ foo: "bar",
+ chicken: "soup",
+ // This checks whether the autoadded 'X-Requested-With'-header
+ // can be overridden, even though the given key here is spelled
+ // in lowercase.
+ 'x-requested-with': 'humpty'
+ }
+ };
+ // we also track how often setRequestHeader is being called, it should
+ // be called once for every header, even with the above defined
+ // custom 'x-requested-with' header which we usually autoadd.
+ // If the numbers match, we make sure to not send duplicate headers like
+ // x-requested-with: humpty AND
+ // X-Requested-With: XMLHttpRequest
+ var actualSetHeaderCnt = 0;
+ var expectedSetHeaderCnt = 3; // and not four!
+ proto.setRequestHeader = function(key, value) {
+ actualSetHeaderCnt++;
+ t.ok(key in config.headers, "setRequestHeader called with key: " + key);
+ t.eq(value, config.headers[key], "setRequestHeader called with correct value: " + value);
+ };
+ request = issue(config);
+
+ t.eq(actualSetHeaderCnt, expectedSetHeaderCnt, 'A custom "x-requested-with" header overrides the default "X-Requested-With" header.');
+
+ proto.setRequestHeader = _setRequestHeader;
+
+ // test that callback is called (no scope) - 1 test
+ var unbound = function(request) {
+ t.ok(request instanceof OpenLayers.Request.XMLHttpRequest,
+ "unbound callback called with xhr instance");
+ }
+ config = {
+ callback: unbound
+ };
+ request = issue(config);
+ request.readyState = OpenLayers.Request.XMLHttpRequest.DONE;
+ request.onreadystatechange();
+
+ // test that callback is called (with scope) - 2 tests
+ var obj = {};
+ var bound = function(request) {
+ t.ok(this === obj, "bound callback has correct scope");
+ t.ok(request instanceof OpenLayers.Request.XMLHttpRequest,
+ "bound callback called with xhr instance");
+ }
+ config = {
+ callback: bound,
+ scope: obj
+ };
+ request = issue(config);
+ request.readyState = 4;
+ request.onreadystatechange();
+
+ // test that optional success callback is only called with 200s and
+ // failure is only called with non-200s
+ var _send = proto.send;
+ proto.send = function() {};
+
+ config = {
+ success: function(req) {
+ t.ok(!req.status || (req.status >= 200 && req.status < 300),
+ "success callback called with " + req.status + " status");
+ },
+ failure: function(req) {
+ t.ok(req.status && (req.status < 200 || req.status >= 300),
+ "failure callback called with " + req.status + " status");
+ }
+ };
+ request = issue(config);
+ request.readyState = 4;
+
+ // mock up status 200 (1 test)
+ request.status = 200;
+ request.onreadystatechange();
+
+ // mock up status 299 (1 test)
+ request.status = 299;
+ request.onreadystatechange();
+
+ // mock up status 100 (1 test)
+ request.status = 100;
+ request.onreadystatechange();
+
+ // mock up status 300 (1 test)
+ request.status = 300;
+ request.onreadystatechange();
+
+ // mock up a status null (1 test)
+ request.status = null;
+ request.onreadystatechange();
+
+ proto.send = _send;
+
+ teardown();
+ }
+
+ function test_delayed_send(t) {
+ t.plan(1);
+ var proto = OpenLayers.Request.XMLHttpRequest.prototype;
+ var _send = proto.send;
+
+ // test that send is called with data - 1 test
+ var config = {
+ method: "PUT",
+ data: "bling"
+ };
+ var got = {};
+ proto.send = function(data) {
+ got.data = data;
+ }
+ OpenLayers.Request.issue(config);
+
+ t.delay_call(1, function() {
+ t.eq(got.data, config.data, "proper data sent");
+ proto.send = _send;
+ });
+
+ }
+
+ function test_GET(t) {
+ t.plan(1);
+ var _issue = OpenLayers.Request.issue;
+ OpenLayers.Request.issue = function(config) {
+ t.eq(config.method, "GET", "calls issue with correct method");
+ }
+ OpenLayers.Request.GET();
+ OpenLayers.Request.issue = _issue;
+ }
+ function test_POST(t) {
+ t.plan(1);
+ var _issue = OpenLayers.Request.issue;
+ OpenLayers.Request.issue = function(config) {
+ t.eq(config.method, "POST", "calls issue with correct method");
+ }
+ OpenLayers.Request.POST();
+ OpenLayers.Request.issue = _issue;
+ }
+ function test_PUT(t) {
+ t.plan(1);
+ var _issue = OpenLayers.Request.issue;
+ OpenLayers.Request.issue = function(config) {
+ t.eq(config.method, "PUT", "calls issue with correct method");
+ }
+ OpenLayers.Request.PUT();
+ OpenLayers.Request.issue = _issue;
+ }
+ function test_DELETE(t) {
+ t.plan(1);
+ var _issue = OpenLayers.Request.issue;
+ OpenLayers.Request.issue = function(config) {
+ t.eq(config.method, "DELETE", "calls issue with correct method");
+ }
+ OpenLayers.Request.DELETE();
+ OpenLayers.Request.issue = _issue;
+ }
+ function test_HEAD(t) {
+ t.plan(1);
+ var _issue = OpenLayers.Request.issue;
+ OpenLayers.Request.issue = function(config) {
+ t.eq(config.method, "HEAD", "calls issue with correct method");
+ }
+ OpenLayers.Request.HEAD();
+ OpenLayers.Request.issue = _issue;
+ }
+ function test_OPTIONS(t) {
+ t.plan(1);
+ var _issue = OpenLayers.Request.issue;
+ OpenLayers.Request.issue = function(config) {
+ t.eq(config.method, "OPTIONS", "calls issue with correct method");
+ }
+ OpenLayers.Request.OPTIONS();
+ OpenLayers.Request.issue = _issue;
+ }
+
+ function test_events_success(t) {
+
+ t.plan(5);
+
+ var events = [];
+ function listener(event) {
+ events.push(event);
+ }
+
+ // set up event listeners
+ OpenLayers.Request.events.on({
+ complete: listener,
+ success: listener,
+ failure: listener
+ });
+
+ // issue a request that succeeds
+ OpenLayers.Request.GET({
+ url: ".", params: {bar: "baz"}, async: false
+ });
+ t.eq(events.length, 2, "two events logged");
+ t.eq(events[0].type, "complete", "first event is complete");
+ t.eq(events[1].type, "success", "second event is success");
+ t.ok(events[1].config, "success listener sent config");
+ t.eq(events[1].requestUrl, ".?bar=baz", "success listener sent config.url");
+
+ // remove event listeners
+ OpenLayers.Request.events.un({
+ complete: listener,
+ success: listener,
+ failure: listener
+ });
+
+ }
+
+ function test_events_failure(t) {
+
+ t.plan(5);
+
+ var events = [];
+ function listener(event) {
+ events.push(event);
+ }
+
+ // set up event listeners
+ OpenLayers.Request.events.on({
+ complete: listener,
+ success: listener,
+ failure: listener
+ });
+
+ // issue a request that succeeds
+ OpenLayers.Request.GET({
+ url: "foo", params: {bar: "baz"}, async: false
+ });
+ t.eq(events.length, 2, "two events logged");
+ t.eq(events[0].type, "complete", "first event is complete");
+ t.eq(events[1].type, "failure", "second event is failure");
+ t.ok(events[1].config, "failure listener sent config");
+ t.eq(events[1].requestUrl, "foo?bar=baz", "failure listener sent requestUrl");
+
+ // remove event listeners
+ OpenLayers.Request.events.un({
+ complete: listener,
+ success: listener,
+ failure: listener
+ });
+
+ }
+
+ function test_ProxyHost(t) {
+ t.plan(5);
+
+ /*
+ * Setup
+ */
+
+ setup();
+
+ var expectedURL;
+
+ var _ProxyHost = OpenLayers.ProxyHost;
+
+ var proto = OpenLayers.Request.XMLHttpRequest.prototype;
+ var _open = proto.open;
+ var log = [];
+ var port;
+ proto.open = function(method, url, async, user, password) {
+ log.push(url);
+ };
+
+ /*
+ * Test
+ */
+
+ // 2 tests
+ log = [];
+ OpenLayers.ProxyHost = "http://fooproxy/?url=";
+ expectedURL = "http://fooproxy/?url=http%3A%2F%2Fbar%3Fk1%3Dv1%26k2%3Dv2";
+ OpenLayers.Request.GET({url: "http://bar?k1=v1&k2=v2"});
+ t.eq(log.length, 1, "[1] XHR.open called once");
+ t.eq(log[0], expectedURL, "[1] the URL used for XHR is correct (" + log[0] + ")");
+
+ // 1 test
+ log = [];
+ OpenLayers.ProxyHost = "http://fooproxy/?url=";
+ port = window.location.port ? ':'+window.location.port : '';
+ expectedURL = window.location.protocol+"//"+window.location.hostname+port+"/service";
+ OpenLayers.Request.GET({url: expectedURL});
+ t.eq(log[0], expectedURL, "[2] proxy is not used when requesting the same server");
+
+ // 2 tests
+ log = [];
+ OpenLayers.ProxyHost = function(url) {
+ var p = OpenLayers.Util.getParameters(url);
+ var p = OpenLayers.Util.getParameterString(p);
+ return "http://barproxy/?" + p;
+ };
+ expectedURL = "http://barproxy/?k1=v1&k2=v2";
+ OpenLayers.Request.GET({url: "http://bar?k1=v1&k2=v2"});
+ t.eq(log.length, 1, "[3] XHR.open called once");
+ t.eq(log[0], expectedURL, "[3] the URL used for XHR is correct (" + log[0] + ")");
+
+ /*
+ * Teardown
+ */
+
+ OpenLayers.Request.XMLHttpRequest.prototype.open = _open;
+ OpenLayers.ProxyHost = _ProxyHost;
+ teardown();
+ }
+
+ function test_abort(t) {
+
+ t.plan(0);
+
+ var sendCalled;
+
+ // set up
+
+ var _open = OpenLayers.Request.XMLHttpRequest.prototype.open;
+ OpenLayers.Request.XMLHttpRequest.prototype.open = function() {
+ this.readyState = OpenLayers.Request.XMLHttpRequest.OPENED;
+ };
+
+ var _setRequestHeader = OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader;
+ OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = function() {};
+
+ var _send = OpenLayers.Request.XMLHttpRequest.prototype.send;
+ OpenLayers.Request.XMLHttpRequest.prototype.send = function() {
+ sendCalled = true;
+ };
+
+ // test
+
+ sendCalled = false;
+ OpenLayers.Request.issue().abort();
+
+ t.delay_call(0.5, function() {
+ if (sendCalled) {
+ t.fail("Send should not be called because request is aborted");
+ }
+
+ // tear down
+ OpenLayers.Request.XMLHttpRequest.prototype.open = _open;
+ OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = _setRequestHeader;
+ OpenLayers.Request.XMLHttpRequest.prototype.send = _send;
+ });
+ }
+
+ function test_abort2(t) {
+ t.plan(0);
+ var fail = false;
+ OpenLayers.Request.XMLHttpRequest.onsend = function(args) {
+ fail = true;
+ }
+ t.delay_call(0.5, function() {
+ if (fail === true) {
+ t.fail("Send should not be called because request is aborted");
+ }
+ OpenLayers.Request.XMLHttpRequest.onsend = null;
+ });
+ var req = OpenLayers.Request.GET();
+ req.abort();
+ }
+
+ function test_XRequestedWithHeaderAutoadded(t) {
+ t.plan( 2 );
+
+ var headerSet = false;
+ var headerGot = '';
+ var headerExpected = 'XMLHttpRequest';
+
+ // save to be able to restore later
+ var _setRequestHeader = OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader;
+
+ OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = function(field, value) {
+ if (field === 'X-Requested-With') {
+ headerSet = true;
+ headerGot = value;
+ }
+ };
+
+ var req = OpenLayers.Request.issue({
+ url: location.href,
+ async: false
+ });
+
+ t.ok( headerSet, 'We call the method "setRequestHeader" to set a "X-Requested-With"-header' );
+ t.eq( headerGot, headerExpected, 'The "X-Requested-With"-header is set to "' + headerExpected + '" as expected.' );
+
+ // restore old setRequestHeader
+ OpenLayers.Request.XMLHttpRequest.prototype.setRequestHeader = _setRequestHeader;
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Request/XMLHttpRequest.html b/misc/openlayers/tests/Request/XMLHttpRequest.html
new file mode 100644
index 0000000..fa62807
--- /dev/null
+++ b/misc/openlayers/tests/Request/XMLHttpRequest.html
@@ -0,0 +1,59 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ function test_constructor(t) {
+ t.plan(1);
+ t.ok(new OpenLayers.Request.XMLHttpRequest(),
+ "constructor didn't fail and we trust the code is well tested in OpenLayers.Request methods");
+ }
+ function test_readyState(t) {
+ // Verify compliance of the standard (a part) See: http://www.w3.org/TR/XMLHttpRequest/
+ t.plan(9);
+ // Case 1: Request-A: open & abort
+ var requestA = new OpenLayers.Request.XMLHttpRequest();
+ //requestA.onreadystatechange = function() {};
+ t.eq(requestA.readyState, 0, "Request-A: readyState after new is 0-UNSENT");
+ requestA.open("GET", ".", true);
+ t.eq(requestA.readyState, 1, "Request-A: readyState after open is 1-OPENED");
+ requestA.abort();
+ t.eq(requestA.readyState, 0, "Request-A: readyState after abort is 0-UNSENT");
+
+ // Case 2: Request-B: open & send
+ var requestB = new OpenLayers.Request.XMLHttpRequest();
+ requestB.onreadystatechange = function() {
+ if (requestB.readyState == 4) {
+ t.ok(true, "Request-B: triggered the event onreadystatechange when 4-DONE");
+ }
+ };
+ t.eq(requestB.readyState, 0, "Request-B: readyState after new is 0-UNSENT");
+ requestB.open("GET", ".", true);
+ t.eq(requestB.readyState, 1, "Request-B: readyState after open is 1-OPENED");
+ requestB.send();
+
+ // Case 3: Request-C: open, send & abort
+ var requestC = new OpenLayers.Request.XMLHttpRequest();
+ requestC.onreadystatechange = function() {
+ if (requestC.readyState == 4) {
+ t.fail("Request-C: triggered the event onreadystatechange when 4-DONE after abort");
+ }
+ };
+ t.eq(requestC.readyState, 0, "Request-C: readyState after new is 0-UNSENT");
+ requestC.open("GET", ".", true);
+ t.eq(requestC.readyState, 1, "Request-C: readyState after open is 1-OPENED");
+ requestC.send();
+ requestC.abort();
+ t.eq(requestC.readyState, 0, "Request-C: readyState after abort is 0-UNSENT");
+
+ // delay destroy
+ t.delay_call(
+ 2, function() {
+ // to await the end of requestB and requestC
+ }
+ );
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Rule.html b/misc/openlayers/tests/Rule.html
new file mode 100644
index 0000000..56e3483
--- /dev/null
+++ b/misc/openlayers/tests/Rule.html
@@ -0,0 +1,123 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Rule_constructor(t) {
+ t.plan(3);
+
+ var options = {'foo': 'bar'};
+ var rule = new OpenLayers.Rule(options);
+ t.ok(rule instanceof OpenLayers.Rule,
+ "new OpenLayers.Rule returns object" );
+ t.eq(rule.foo, "bar", "constructor sets options correctly");
+ t.eq(typeof rule.evaluate, "function", "rule has an evaluate function");
+ }
+
+ function test_Rule_getContext(t) {
+ t.plan(2);
+ var rule, options;
+
+ var feature = {
+ attributes: {
+ 'dude': 'hello'
+ },
+ 'foobar': 'world'
+ }
+
+ rule = new OpenLayers.Rule();
+ var context = rule.getContext(feature);
+ t.eq(context.dude, "hello", "value returned by getContext is correct"
+ + " if no context is specified");
+
+ var options = {
+ context: function(feature){
+ return feature;
+ }
+ };
+ rule = new OpenLayers.Rule(options);
+ var context = rule.getContext(feature);
+ t.eq(context.foobar, "world", "value returned by getContext is correct"
+ + " if a context is given in constructor options");
+ }
+
+ function test_clone(t) {
+
+ t.plan(9);
+
+ var rule = new OpenLayers.Rule({
+ name: "test rule",
+ minScaleDenominator: 10,
+ maxScaleDenominator: 20,
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "value"
+ }),
+ symbolizer: {
+ fillColor: "black"
+ },
+ context: {
+ foo: "bar"
+ }
+ });
+ var clone = rule.clone();
+ t.eq(clone.name, "test rule", "name copied");
+ t.eq(clone.minScaleDenominator, 10, "minScaleDenominator copied");
+ t.eq(clone.filter.type, OpenLayers.Filter.Comparison.EQUAL_TO, "clone has correct filter type");
+
+ // modify original
+ rule.filter.property = "new";
+ rule.symbolizer.fillColor = "white";
+ rule.context.foo = "baz";
+
+ // confirm that clone didn't change
+ t.eq(clone.filter.property, "prop", "clone has clone of filter");
+ t.eq(clone.symbolizer.fillColor, "black", "clone has clone of symbolizer");
+ t.eq(clone.context.foo, "bar", "clone has clone of context");
+
+ // confirm that ids are different
+ t.ok(clone.id !== rule.id, "clone has different id");
+
+ rule.destroy();
+ clone.destroy();
+
+ // test multiple symbolizers
+ rule = new OpenLayers.Rule({
+ name: "test rule",
+ minScaleDenominator: 10,
+ maxScaleDenominator: 20,
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "prop",
+ value: "value"
+ }),
+ symbolizers: [
+ new OpenLayers.Symbolizer.Line({
+ strokeColor: "black"
+ })
+ ]
+ });
+ clone = rule.clone();
+
+ t.eq(clone.symbolizers.length, 1, "clone has one symbolizer");
+ t.ok(clone.symbolizers[0] !== rule.symbolizers[0], "clone has different symbolizers than original");
+
+ clone.destroy();
+ rule.destroy();
+
+ }
+
+ function test_Rule_destroy(t) {
+ t.plan(1);
+
+ var rule = new OpenLayers.Rule();
+ rule.destroy();
+ t.eq(rule.symbolizer, null, "symbolizer hash nulled properly");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/SingleFile1.html b/misc/openlayers/tests/SingleFile1.html
new file mode 100644
index 0000000..836a1a5
--- /dev/null
+++ b/misc/openlayers/tests/SingleFile1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <script src="some/OpenLayers/path/OpenLayers.js"></script>
+ <script src="../lib/OpenLayers/SingleFile.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+ t.eq(OpenLayers._getScriptLocation(), "some/OpenLayers/path/",
+ "Script location correctly detected (OpenLayers.js).");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/SingleFile2.html b/misc/openlayers/tests/SingleFile2.html
new file mode 100644
index 0000000..68b47a3
--- /dev/null
+++ b/misc/openlayers/tests/SingleFile2.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <script src="some/OpenLayers/path/OpenLayers.light.js"></script>
+ <script src="../lib/OpenLayers/SingleFile.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+ t.eq(OpenLayers._getScriptLocation(), "some/OpenLayers/path/",
+ "Script location correctly detected (OpenLayers.light.js) .");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/SingleFile3.html b/misc/openlayers/tests/SingleFile3.html
new file mode 100644
index 0000000..bb58fcb
--- /dev/null
+++ b/misc/openlayers/tests/SingleFile3.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <script src="some/OpenLayers/path/OpenLayers.light.debug.js"></script>
+ <script src="../lib/OpenLayers/SingleFile.js"></script>
+ <script type="text/javascript">
+ function test_OpenLayers(t) {
+ t.plan(1);
+ t.eq(OpenLayers._getScriptLocation(), "some/OpenLayers/path/",
+ "Script location correctly detected (OpenLayers.light.debug.js).");
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy.html b/misc/openlayers/tests/Strategy.html
new file mode 100644
index 0000000..5ecdef6
--- /dev/null
+++ b/misc/openlayers/tests/Strategy.html
@@ -0,0 +1,94 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(5);
+ var options = {};
+ var strategy = new OpenLayers.Strategy(options);
+
+ t.ok(strategy instanceof OpenLayers.Strategy,
+ "new OpenLayers.Strategy returns object" );
+ t.eq(strategy.options, options, "constructor sets this.options");
+ t.eq(strategy.active, false, "constructor sets this.active to false");
+ t.eq(strategy.autoActivate, true, "constructor does not modify this.autoActivate");
+ t.eq(strategy.autoDestroy, true, "constructor does not modify this.autoDestroy");
+ }
+
+ function test_activate(t) {
+ t.plan(1);
+ var options = {
+ activate: function() {
+ t.ok(true, "OpenLayer.Map.addLayer calls activate");
+ }
+ };
+
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [new OpenLayers.Strategy(options)]
+ });
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ }
+
+ function test_destroy(t) {
+ t.plan(3);
+
+ var strategy = new OpenLayers.Strategy({
+ deactivate: function() {
+ t.ok(true, "destroy calls deactivate");
+ },
+
+ options: {foo: 'bar'},
+ layer: 'foo'
+ });
+ strategy.destroy();
+
+ t.eq(strategy.layer, null, "destroy nullify protocol.layer");
+ t.eq(strategy.options, null, "destroy nullify protocol.options");
+ }
+
+ function test_activate(t) {
+ t.plan(4);
+ var strategy = new OpenLayers.Strategy({
+ layer: 'foo'
+ });
+
+ var ret;
+ ret = strategy.activate();
+
+ t.eq(strategy.active, true, "activate sets this.active to true on first call");
+ t.eq(ret, true, "activate returns true on first call");
+
+ ret = strategy.activate();
+
+ t.eq(strategy.active, true, "activate does not modify this.active on second call");
+ t.eq(ret, false, "activate returns false on second call");
+ }
+
+ function test_deactivate(t) {
+ t.plan(4);
+ var strategy = new OpenLayers.Strategy({
+ layer: 'foo'
+ });
+ strategy.activate();
+
+ var ret;
+ ret = strategy.deactivate();
+
+ t.eq(strategy.active, false, "deactivate sets this.active to false on first call");
+ t.eq(ret, true, "deactivate returns true on first call");
+
+ ret = strategy.deactivate();
+
+ t.eq(strategy.active, false, "deactivate does not modify this.active on second call");
+ t.eq(ret, false, "deactivate returns false on second call");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy/BBOX.html b/misc/openlayers/tests/Strategy/BBOX.html
new file mode 100644
index 0000000..6e409e6
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/BBOX.html
@@ -0,0 +1,361 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(1);
+
+ var ratio = 4;
+
+ var s = new OpenLayers.Strategy.BBOX({ratio: ratio});
+ t.eq(s.ratio, ratio, "ctor sets ratio");
+ }
+
+ function test_activate(t) {
+ t.plan(5);
+
+ var l = new OpenLayers.Layer.Vector();
+ var s = new OpenLayers.Strategy.BBOX();
+ s.setLayer(l);
+
+ t.eq(s.active, false, "not active after construction");
+
+ var activated = s.activate();
+ t.eq(activated, true, "activate returns true");
+ t.eq(s.active, true, "activated after activate");
+ t.ok(l.events.listeners["moveend"][0].obj == s &&
+ l.events.listeners["moveend"][0].func == s.update,
+ "activates registers moveend listener");
+ t.ok(l.events.listeners["refresh"][0].obj == s &&
+ l.events.listeners["refresh"][0].func == s.update,
+ "activates registers refresh listener");
+ }
+
+ function test_update(t) {
+ t.plan(7);
+
+ // Create a dummy layer that can act as the map base layer.
+ // This will be unnecessary if #1921 is addressed (allowing
+ // map to have different projection than base layer).
+ var dummy = new OpenLayers.Layer(null, {isBaseLayer: true});
+
+ var strategy = new OpenLayers.Strategy.BBOX({
+ ratio: 1 // makes for easier comparison to map bounds
+ });
+ var log = [];
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ protocol: new OpenLayers.Protocol({abort: function(response) { log.push(response); }}),
+ strategies: [strategy]
+ });
+
+ // create a map with the layers and a center
+ var map = new OpenLayers.Map("map", {zoomMethod: null});
+ map.addLayers([dummy, layer]);
+ map.zoomToMaxExtent();
+
+ /**
+ * The setCenter call above should set strategy bounds. I *think* this
+ * issue is captured in http://trac.openlayers.org/ticket/1835.
+ * For now, I'm going to force an update on the strategy. This line
+ * should be removed when the issue(s) described in #1835 are addressed.
+ */
+ strategy.update({force: true});
+ strategy.response = {};
+ strategy.update({force: true});
+ t.eq(log.length, 1, "Response aborted");
+ log = [];
+ strategy.update({force: true});
+ strategy.update({force: true, noAbort: true});
+ t.eq(log.length, 0, "Response not aborted when noAbort is true");
+
+ // test that the strategy bounds were set
+ t.ok(map.getExtent().equals(strategy.bounds), "[set center] bounds set to map extent");
+
+ // zoom and test that bounds are not reset
+ var old = strategy.bounds.clone();
+ map.zoomIn();
+ t.ok(strategy.bounds.equals(old), "[zoom in] bounds not reset");
+
+ // force update and check that bounds change
+ strategy.update({force: true});
+ t.ok(!strategy.bounds.equals(old), "[force update] bounds changed");
+ t.ok(strategy.bounds.equals(map.getExtent()), "[force update] bounds set to map extent");
+
+ // change the layer projection to confirm strategy uses same
+ layer.projection = new OpenLayers.Projection("EPSG:900913");
+ strategy.update({force: true});
+ var from = map.getProjectionObject();
+ var to = layer.projection;
+
+ var strategyBounds = strategy.bounds,
+ mapExtent = map.getExtent().transform(from, to),
+ // we don't use bounds::toString because we might run into
+ // precision issues
+ precision = 7,
+ strategyBoundsGot = [
+ strategyBounds.left.toFixed( precision ),
+ strategyBounds.bottom.toFixed( precision ),
+ strategyBounds.right.toFixed( precision ),
+ strategyBounds.top.toFixed( precision )
+ ].join(','),
+ mapExtentExpected = [
+ mapExtent.left.toFixed( precision ),
+ mapExtent.bottom.toFixed( precision ),
+ mapExtent.right.toFixed( precision ),
+ mapExtent.top.toFixed( precision )
+ ].join(',');
+ t.eq(strategyBoundsGot, mapExtentExpected,
+ "[force update different proj] bounds transformed");
+ }
+
+ function test_events(t) {
+
+ t.plan(7);
+
+ var log = [];
+
+ var response = new OpenLayers.Protocol.Response();
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ protocol: new OpenLayers.Protocol({
+ read: function(config) {
+ config.callback.call(config.scope, response);
+ }
+ }),
+ isBaseLayer: true,
+ eventListeners: {
+ loadstart: function(event) {
+ log.push(event);
+ },
+ loadend: function(event) {
+ log.push(event);
+ }
+ }
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.eq(log.length, 2, "2 events logged");
+ t.eq(log[0].type, "loadstart", "loadstart first");
+ t.ok(log[0].filter.type === OpenLayers.Filter.Spatial.BBOX, "loadstart includes filter used");
+ t.eq(log[1].type, "loadend", "loadend second");
+ t.ok(log[1].response == response, "loadend includes response");
+
+ var calls = [];
+ layer.protocol.read = function(obj) {
+ calls.push(obj);
+ }
+ layer.refresh({force: true, whee: 'chicken'});
+
+ t.eq(calls.length, 1, "1 call to read");
+ t.eq(calls[0].whee, "chicken", "properties passed to read");
+
+ map.destroy();
+
+ }
+
+ function test_triggerRead(t) {
+ t.plan(4);
+
+ var s = new OpenLayers.Strategy.BBOX();
+
+ var filter = {"fake": "filter"};
+
+ s.createFilter = function() {
+ return filter;
+ };
+ s.response = {"fake": "response"};
+
+ var log = {};
+
+ var protocol = new OpenLayers.Protocol({
+ read: function(options) {
+ log.options = options;
+ },
+ abort: function(response) {
+ log.abort = response.fake;
+ }
+ });
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [s],
+ protocol: protocol,
+ isBaseLayer: true
+ });
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.ok(log.options.filter == filter,
+ "protocol read called with correct filter");
+ t.ok(log.options.callback == s.merge,
+ "protocol read called with correct callback");
+ t.ok(log.options.scope == s,
+ "protocol read called with correct scope");
+ t.eq(log.abort, "response",
+ "protocol abort called with correct response");
+
+ map.destroy();
+
+ }
+
+ function test_resFactor(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map("map", {zoomMethod: null});
+ var bbox = new OpenLayers.Strategy.BBOX();
+ var fakeProtocol = new OpenLayers.Protocol({
+ 'read': function() {
+ t.ok(true, "read called once without resfactor");
+ }
+ });
+ var layer = new OpenLayers.Layer.Vector("test", {
+ strategies: [bbox],
+ protocol: fakeProtocol,
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ map.zoomIn();
+
+ fakeProtocol.read = function() {
+ t.ok("read called again on zooming with resFactor: 1");
+ }
+ bbox.resFactor = 1;
+ map.zoomIn();
+
+ }
+
+ function test_createFilter(t) {
+ t.plan(3);
+
+ var s = new OpenLayers.Strategy.BBOX();
+
+ var f;
+
+ // 2 test
+ s.setLayer({});
+ f = s.createFilter();
+ t.ok(f.CLASS_NAME.search(/^OpenLayers.Filter.Spatial/) != -1,
+ "createFilter returns a spatial filter object");
+ t.eq(f.type, OpenLayers.Filter.Spatial.BBOX,
+ "createFilter returns a BBOX-typed filter");
+
+ // 1 test
+ s.setLayer({filter: {fake: "filter"}});
+ f = s.createFilter();
+ t.ok(f.CLASS_NAME.search(/^OpenLayers.Filter.Logical/) != -1,
+ "createFilter returns a logical filter object");
+ }
+
+ function test_merge(t) {
+ t.plan(4);
+
+ var strategy = new OpenLayers.Strategy.BBOX();
+
+ // create map with default projection
+ var map = new OpenLayers.Map("map");
+
+ // create layer with custom projection
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ strategies: [strategy],
+ protocol: new OpenLayers.Protocol(),
+ projection: new OpenLayers.Projection("EPSG:900913")
+ });
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ // create some features
+ var geometries = [
+ new OpenLayers.Geometry.Point(100, 200),
+ new OpenLayers.Geometry.Point(1000, 2000)
+ ];
+ var features = [
+ new OpenLayers.Feature.Vector(geometries[0].clone()),
+ new OpenLayers.Feature.Vector(geometries[1].clone())
+ ];
+
+ // call merge with a mocked up response
+ strategy.merge({features: features, success: OpenLayers.Function.True});
+
+ // test that feature geometries have been transformed to map projection
+ var from = layer.projection;
+ var to = map.getProjectionObject();
+ t.geom_eq(layer.features[0].geometry, features[0].geometry.transform(from, to), "[different proj] feature 0 geometry transformed");
+ t.geom_eq(layer.features[1].geometry, features[1].geometry.transform(from, to), "[different proj] feature 1 geometry transformed");
+
+ // same as above but with same map/layer projection
+ layer.destroyFeatures();
+ layer.projection = map.getProjectionObject();
+
+ features = [
+ new OpenLayers.Feature.Vector(geometries[0].clone()),
+ new OpenLayers.Feature.Vector(geometries[1].clone())
+ ];
+
+ // call merge again with mocked up response
+ strategy.merge({features: features, success: OpenLayers.Function.True});
+
+ // test that feature geometries have not been transformed
+ t.geom_eq(layer.features[0].geometry, features[0].geometry, "[same proj] feature 0 geometry not transformed");
+ t.geom_eq(layer.features[1].geometry, features[1].geometry, "[same proj] feature 1 geometry not transformed");
+
+ }
+
+ // Test fix for Ticket #3142
+ function test_layerLoadedAfterBeingAdded(t) {
+ t.plan(3);
+
+ var dummy = new OpenLayers.Layer(null, {isBaseLayer: true});
+
+ var strategy = new OpenLayers.Strategy.BBOX({
+ ratio: 1 // makes for easier comparison to map bounds
+ });
+ var layer = new OpenLayers.Layer.Vector(null, {
+ protocol: new OpenLayers.Protocol(),
+ strategies: [strategy]
+ });
+
+ // Make sure to test the case of a vector layer needing to be
+ // reprojected while the map is not yet centered
+ var layerReproject = new OpenLayers.Layer.Vector(null, {
+ protocol: new OpenLayers.Protocol(),
+ strategies: [new OpenLayers.Strategy.BBOX()],
+ projection: 'EPSG:900913'
+ });
+
+ // Make sure that layers that are not in range don't request data
+ var layerOutOfRange = new OpenLayers.Layer.Vector(null, {
+ maxResolution: 1,
+ protocol: new OpenLayers.Protocol(),
+ strategies: [new OpenLayers.Strategy.BBOX()]
+ });
+
+ var map = new OpenLayers.Map("map");
+ map.addLayer(dummy);
+ map.addLayer(layerReproject);
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+ map.addLayer(layer);
+ map.addLayer(layerOutOfRange);
+ // test that the strategy bounds were set
+ t.ok(map.getExtent().equals(strategy.bounds), "[set center] bounds set to map extent");
+ t.eq(layerOutOfRange.strategies[0].bounds, null, "Data not requested if layer is out of range");
+
+ layerOutOfRange.setVisibility(false);
+ layerOutOfRange.setVisibility(true);
+ t.eq(layerOutOfRange.strategies[0].bounds, null, "Data not requested if layer is out of range when switching visibility");
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 200px" />
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy/Cluster.html b/misc/openlayers/tests/Strategy/Cluster.html
new file mode 100644
index 0000000..3358ff9
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/Cluster.html
@@ -0,0 +1,148 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_activate(t) {
+ t.plan(2);
+
+ var strategy = new OpenLayers.Strategy.Cluster();
+ t.eq(strategy.active, false, "not active after construction");
+
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [strategy]
+ });
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ t.eq(strategy.active, true, "active after adding to map");
+ }
+
+ function test_clusters(t) {
+ t.plan(22);
+
+ function featuresEq(got, exp) {
+ var eq = false;
+ if(got instanceof Array && exp instanceof Array) {
+ if(got.length === exp.length) {
+ for(var i=0; i<got.length; ++i) {
+ if(got[i] !== exp[i]) {
+ console.log(got[i], exp[i]);
+ break;
+ }
+ }
+ eq = (i == got.length);
+ }
+ }
+ return eq;
+ }
+
+ var strategy = new OpenLayers.Strategy.Cluster();
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [strategy],
+ isBaseLayer: true
+ });
+ var map = new OpenLayers.Map('map', {
+ resolutions: [4, 2, 1],
+ maxExtent: new OpenLayers.Bounds(-40, -40, 40, 40),
+ zoomMethod: null
+ });
+ map.addLayer(layer);
+
+ // create features in a line, 1 unit apart
+ var features = new Array(80);
+ for(var i=0; i<80; ++i) {
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-40 + i, 0)
+ );
+ }
+
+ // add one additional feature, with no geometry - just to confirm it doesn't break things
+ features.push(new OpenLayers.Feature.Vector());
+
+ layer.addFeatures(features);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ // resolution 4
+ // threshold: 4 * 20 = 80 units
+ // one cluster
+ t.eq(layer.features.length, 1, "[4] layer has one cluster");
+ t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 80)), "[4] cluster includes all features with geometries");
+
+ // resolution 2
+ // threshold: 2 * 20 = 40 units
+ // two clusters (41 and 39) - first cluster includes all features within 40 units of the first (0-40 or 41 features)
+ map.zoomIn();
+ t.eq(layer.features.length, 2, "[2] layer has two clusters");
+ t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 41)), "[2] first cluster includes first 41 features");
+ t.ok(featuresEq(layer.features[1].cluster, features.slice(41, 80)), "[2] second cluster includes last 39 features");
+
+ // resolution 1
+ // threshold: 1 * 20 = 20 units
+ // four clusters (21, 21, 21, and 17)
+ map.zoomIn();
+ t.eq(layer.features.length, 4, "[1] layer has four clusters");
+ t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 21)), "[1] first cluster includes first 21 features");
+ t.ok(featuresEq(layer.features[1].cluster, features.slice(21, 42)), "[1] second cluster includes second 21 features");
+ t.ok(featuresEq(layer.features[2].cluster, features.slice(42, 63)), "[1] third cluster includes third 21 features");
+ t.ok(featuresEq(layer.features[3].cluster, features.slice(63, 80)), "[1] fourth cluster includes last 17 features");
+
+ // zoom out and back in to test threshold property (21)
+ map.zoomOut();
+ strategy.threshold = 21;
+ map.zoomIn();
+ t.eq(layer.features.length, 20, "[1-threshold 21] layer has 20 clusters");
+ t.ok(featuresEq(layer.features[0].cluster, features.slice(0, 21)), "[1-threshold 21] first cluster includes first 21 features");
+ t.ok(featuresEq(layer.features[1].cluster, features.slice(21, 42)), "[1-threshold 21] second cluster includes second 21 features");
+ t.ok(featuresEq(layer.features[2].cluster, features.slice(42, 63)), "[1-threshold 21] third cluster includes third 21 features");
+ t.ok(featuresEq(layer.features.slice(3, 20), features.slice(63, 80)), "[1-threshold 21] last 17 features are not clustered");
+
+ // zoom out and back in to test high threshold
+ map.zoomOut();
+ strategy.threshold = 100; // clusters must contain 100 features or more
+ map.zoomIn();
+ // the one feature with no geometry is not added to the layer
+ t.eq(layer.features.length, features.length-1, "[1-threshold 100] layer has " + (features.length-1) + " clusters");
+ t.ok(featuresEq(layer.features, features.slice(0, 80)), "[1-threshold 100] layer has all features with geometry");
+
+ // remove features and zoom
+ strategy.threshold = 1;
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+ t.eq(strategy.features.length, 81,
+ "[remove features] cluster has cache");
+ layer.removeAllFeatures();
+ t.eq(layer.features.length, 0,
+ "[remove features] layer has no features after remove");
+ t.ok(!strategy.features,
+ "[remove features] cluster has no cache after remove");
+ map.zoomIn();
+ t.eq(layer.features.length, 0,
+ "[remove features] layer has no features after zoom");
+ t.ok(!strategy.features,
+ "[remove features] cluster has no cache after zoom");
+
+ map.destroy();
+ }
+
+ function test_deactivate(t) {
+ t.plan(2);
+
+ var strategy = new OpenLayers.Strategy.Cluster();
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [strategy]
+ });
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ t.eq(strategy.active, true, "active after adding to map");
+
+ map.removeLayer(layer);
+ t.eq(strategy.active, false, "not active after removing from map");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 200px" />
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy/Filter.html b/misc/openlayers/tests/Strategy/Filter.html
new file mode 100644
index 0000000..7889d1e
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/Filter.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../OLLoader.js"></script>
+<script>
+
+var features = [];
+for (var i=0; i<20; ++i) {
+ features.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0), {index: i}
+ )
+ );
+}
+
+var filter = new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "index",
+ value: 10
+});
+
+function test_initialize(t) {
+
+ t.plan(4);
+
+ var strategy = new OpenLayers.Strategy.Filter({filter: filter});
+
+ t.ok(strategy instanceof OpenLayers.Strategy, "is strategy");
+ t.ok(strategy instanceof OpenLayers.Strategy.Filter, "is filter strategy");
+
+ t.ok(strategy.filter === filter, "has filter");
+
+ strategy.destroy();
+
+ try {
+ strategy = new OpenLayers.Strategy.Filter();
+ t.ok(true, "strategy without filter works");
+ } catch (err) {
+ t.fail("strategy without filter should not throw");
+ }
+
+
+}
+
+function test_autoActivate(t) {
+
+ t.plan(2);
+
+ var strategy = new OpenLayers.Strategy.Filter({filter: filter});
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [strategy]
+ });
+
+ t.ok(!strategy.active, "strategy not active before adding to map");
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+
+ t.ok(strategy.active, "strategy active after adding to map");
+
+ map.destroy();
+
+}
+
+function test_setFilter(t) {
+
+ t.plan(13);
+
+ var strategy = new OpenLayers.Strategy.Filter({filter: filter});
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [strategy]
+ });
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ allOverlays: true,
+ layers: [layer],
+ center: new OpenLayers.LonLat(0, 0),
+ zoom: 1
+ });
+
+ var log = [];
+ layer.events.on({
+ beforefeaturesadded: function(event) {
+ log.push(event.type);
+ },
+ beforefeaturesremoved: function(event) {
+ log.push(event.type);
+ }
+ })
+
+ // a) add all features
+ log = [];
+ layer.addFeatures(features);
+ t.eq(features.length, 20, "a) collection of 20 features")
+ t.eq(layer.features.length, 10, "a) layer got 10 with filter 'index < 10'");
+ t.eq(strategy.cache.length, 10, "a) strategy cached 10 with filter 'index < 10'");
+ t.eq(log.length, 1, "a) one event logged");
+ t.eq(log[0], "beforefeaturesadded", "a) beforefeaturesadded fired");
+
+ // b) update filter
+ log = [];
+ filter.value = 5;
+ strategy.setFilter(filter);
+ t.eq(layer.features.length, 5, "b) layer got 5 with filter 'index < 5'");
+ t.eq(strategy.cache.length, 15, "b) strategy cached 15 with filter 'index < 5'");
+ t.eq(log.length, 1, "b) one event logged");
+ t.eq(log[0], "beforefeaturesremoved", "b) beforefeaturesremoved fired");
+
+ // c) update filter
+ log = [];
+ filter.value = 15;
+ strategy.setFilter(filter);
+ t.eq(layer.features.length, 15, "c) layer got 15 with filter 'index < 15'");
+ t.eq(strategy.cache.length, 5, "c) strategy cached 5 with filter 'index < 15'");
+ t.eq(log.length, 1, "c) one event logged");
+ t.eq(log[0], "beforefeaturesadded", "c) beforefeaturesadded fired");
+
+ map.destroy();
+
+}
+
+
+
+</script></head>
+<body>
+ <div id="map" style="width: 512px; height: 256px" />
+</body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/Strategy/Fixed.html b/misc/openlayers/tests/Strategy/Fixed.html
new file mode 100644
index 0000000..a9bf608
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/Fixed.html
@@ -0,0 +1,253 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_activate(t) {
+ t.plan(5);
+
+ var featureList = ['foo', 'bar'];
+ // a fake protocol
+ var protocol = {
+ read: function(options) {
+ options.callback.call(options.scope, {
+ features: featureList,
+ success: OpenLayers.Function.True
+ });
+ }
+ };
+
+ // Create a dummy layer that can act as the map base layer.
+ // This will be unnecessary if #1920 is addressed or if base layer
+ // handling is changed.
+ var dummy = new OpenLayers.Layer(null, {isBaseLayer: true});
+
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: protocol,
+ addFeatures: function(features) {
+ t.eq(features, featureList, "Features added to the layer");
+ }
+ });
+
+ var layerp = new OpenLayers.Layer.Vector("Hidden preload Layer", {
+ strategies: [new OpenLayers.Strategy.Fixed({preload:true})],
+ protocol: protocol,
+ visibility: false,
+ addFeatures: function(features) {
+ t.ok(!this.visibility, "Features preloaded before visible");
+ }
+ });
+
+ var s = new OpenLayers.Strategy.Fixed();
+ var layer2 = new OpenLayers.Layer.Vector("Hidden lazyload Layer", {
+ strategies: [s],
+ protocol: protocol,
+ visibility: false,
+ addFeatures: function(features) {
+ t.ok(this.visibility, "Layer visible when features added");
+ }
+ });
+
+ var map = new OpenLayers.Map('map');
+ map.addLayers([dummy, layer, layerp, layer2]);
+
+ t.ok(layer2.events.listeners["visibilitychanged"][0].obj == s &&
+ layer2.events.listeners["visibilitychanged"][0].func == s.load,
+ "activate registers visibilitychanged listener if layer hidden"+
+ " and is lazyloading");
+
+ layer2.setVisibility(true);
+
+ t.ok(layer2.events.listeners["visibilitychanged"] == false,
+ "visibilitychanged listener unregistered");
+ }
+
+ function test_events(t) {
+
+ t.plan(7);
+
+ var log = [];
+
+ var response = new OpenLayers.Protocol.Response();
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector(null, {
+ filter: new OpenLayers.Filter.Comparison({
+ type: '==',
+ property: 'foo',
+ value: 'bar'
+ }),
+ strategies: [new OpenLayers.Strategy.Fixed()],
+ protocol: new OpenLayers.Protocol({
+ read: function(config) {
+ config.callback.call(config.scope, response);
+ }
+ }),
+ isBaseLayer: true,
+ eventListeners: {
+ loadstart: function(event) {
+ log.push(event);
+ },
+ loadend: function(event) {
+ log.push(event);
+ }
+ }
+ });
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ t.eq(log.length, 2, "2 events logged");
+ t.eq(log[0].type, "loadstart", "loadstart first");
+ t.eq(log[0].filter, layer.filter, "filter passed on as argument to loadstart");
+ t.eq(log[1].type, "loadend", "loadend second");
+ t.ok(log[1].response == response, "loadend includes response");
+
+ var calls = [];
+ layer.protocol.read = function(obj) {
+ calls.push(obj);
+ }
+ layer.refresh({whee: 'chicken'});
+
+ t.eq(calls.length, 1, "1 call to read");
+ t.eq(calls[0].whee, "chicken", "properties passed to read");
+
+ map.destroy();
+
+ }
+
+
+ function test_merge(t) {
+
+ t.plan(6);
+
+ var strategy = new OpenLayers.Strategy.Fixed();
+
+ // create map with default projection
+ var map = new OpenLayers.Map("map");
+
+ var log = {
+ loadend: 0
+ };
+
+ // create layer with custom projection
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ strategies: [strategy],
+ protocol: new OpenLayers.Protocol(),
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ eventListeners: {
+ loadend: function() {
+ ++log.loadend;
+ }
+ }
+ });
+
+ // give the layer some existing features (one)
+ layer.addFeatures([
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(0, 0)
+ )
+ ]);
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ // create some features
+ var geometries = [
+ new OpenLayers.Geometry.Point(100, 200),
+ new OpenLayers.Geometry.Point(1000, 2000)
+ ];
+ var features = [
+ new OpenLayers.Feature.Vector(geometries[0].clone()),
+ new OpenLayers.Feature.Vector(geometries[1].clone())
+ ];
+
+ // call merge with a mocked up response
+ strategy.merge({features: features, success: OpenLayers.Function.True});
+
+ // confirm that the original features were destroyed
+ t.eq(layer.features.length, 2, "old features destroyed");
+
+ // confirm that loadend was called
+ t.eq(log.loadend, 1, "merge triggers loadend");
+
+ // test that feature geometries have been transformed to map projection
+ var from = layer.projection;
+ var to = map.getProjectionObject();
+ t.geom_eq(layer.features[0].geometry, features[0].geometry.transform(from, to), "[different proj] feature 0 geometry transformed");
+ t.geom_eq(layer.features[1].geometry, features[1].geometry.transform(from, to), "[different proj] feature 1 geometry transformed");
+
+ // same as above but with same map/layer projection
+ layer.destroyFeatures();
+ layer.projection = map.getProjectionObject();
+
+ features = [
+ new OpenLayers.Feature.Vector(geometries[0].clone()),
+ new OpenLayers.Feature.Vector(geometries[1].clone())
+ ];
+
+ // call merge again with mocked up response
+ strategy.merge({features: features, success: OpenLayers.Function.True});
+
+ // test that feature geometries have not been transformed
+ t.geom_eq(layer.features[0].geometry, features[0].geometry, "[same proj] feature 0 geometry not transformed");
+ t.geom_eq(layer.features[1].geometry, features[1].geometry, "[same proj] feature 1 geometry not transformed");
+
+ }
+
+ function test_load(t) {
+ t.plan(3);
+
+ // set up
+
+ var log;
+
+ var map = new OpenLayers.Map({
+ div: "map",
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ layers: [new OpenLayers.Layer("", {isBaseLayer: true})]
+ });
+
+ var response = new OpenLayers.Protocol.Response();
+
+ var strategy = new OpenLayers.Strategy.Fixed({
+ merge: function(r) {
+ log = {scope: this, response: r};
+ }
+ });
+
+ var layer = new OpenLayers.Layer.Vector("vector", {
+ strategies: [strategy],
+ protocol: {
+ read: function(o) {
+ o.callback.call(o.scope, response);
+ }
+ }
+ });
+
+ map.addLayer(layer);
+
+ // test
+
+ strategy.load();
+
+ // verify that the callback is correctly bound
+ t.ok(log !== undefined,
+ "merge was called");
+ t.ok(log.scope == strategy,
+ "merge called with expected scope");
+ t.ok(log.response == response,
+ "merge called with response as the first arg");
+
+ // tear down
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 200px" />
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy/Paging.html b/misc/openlayers/tests/Strategy/Paging.html
new file mode 100644
index 0000000..a85167e
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/Paging.html
@@ -0,0 +1,113 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_activate(t) {
+ t.plan(2);
+
+ var strategy = new OpenLayers.Strategy.Paging();
+ t.eq(strategy.active, false, "not active after construction");
+
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [strategy]
+ });
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ t.eq(strategy.active, true, "active after adding to map");
+ }
+
+ function test_paging(t) {
+ t.plan(18);
+
+ var strategy = new OpenLayers.Strategy.Paging();
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [strategy],
+ drawFeature: function() {}
+ });
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ var features = new Array(25);
+ for(var i=0; i<features.length; ++i) {
+ features[i] = {destroy: function() {}};
+ }
+
+ function featuresEq(got, exp) {
+ var eq = false;
+ if(got instanceof Array && exp instanceof Array) {
+ if(got.length === exp.length) {
+ for(var i=0; i<got.length; ++i) {
+ if(got[i] !== exp[i]) {
+ console.log(got[i], exp[i]);
+ break;
+ }
+ }
+ eq = (i == got.length);
+ }
+ }
+ return eq;
+ }
+
+ var len = strategy.pageLength();
+ t.eq(len, 10, "page length defaults to 10");
+
+ // add 25 features to the layer
+ layer.addFeatures(features);
+ t.eq(strategy.features.length, features.length, "strategy caches all features");
+ t.eq(layer.features.length, len, "layer gets one page of features");
+ t.ok(featuresEq(layer.features, features.slice(0, len)), "layer gets first page initially");
+ t.eq(strategy.pageNum(), 0, "strategy reports 0 based page number");
+ t.eq(strategy.pageCount(), Math.ceil(features.length / len), "strategy reports correct number of pages");
+
+ // load next page of features
+ var changed = strategy.pageNext();
+ t.eq(changed, true, "(1) strategy reports change");
+ t.eq(strategy.pageNum(), 1, "second page");
+ t.ok(featuresEq(layer.features, features.slice(len, 2*len)), "layer has second page of features");
+
+ // load next page of features (half page)
+ changed = strategy.pageNext();
+ t.eq(changed, true, "(2) strategy reports change");
+ t.eq(strategy.pageNum(), 2, "third page");
+
+ // try to change forward again
+ changed = strategy.pageNext();
+ t.eq(changed, false, "strategy reports no change");
+ t.eq(layer.features.length, features.length % len, "layer has partial page");
+ t.ok(featuresEq(layer.features, features.slice(2*len, 3*len)), "layer has third page of features");
+ t.eq(strategy.pageNum(), 2, "still on third page");
+
+ // change back a page
+ changed = strategy.pagePrevious();
+ t.eq(changed, true, "(3) strategy reports change");
+ t.eq(strategy.pageNum(), 1, "back on second page");
+ t.ok(featuresEq(layer.features, features.slice(len, 2*len)), "layer has second page of features again");
+
+ layer.destroy();
+
+ }
+
+ function test_deactivate(t) {
+ t.plan(2);
+
+ var strategy = new OpenLayers.Strategy.Paging();
+ var layer = new OpenLayers.Layer.Vector("Vector Layer", {
+ strategies: [strategy]
+ });
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+
+ t.eq(strategy.active, true, "active after adding to map");
+
+ map.removeLayer(layer);
+ t.eq(strategy.active, false, "not active after removing from map");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 200px" />
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy/Refresh.html b/misc/openlayers/tests/Strategy/Refresh.html
new file mode 100644
index 0000000..054f028
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/Refresh.html
@@ -0,0 +1,177 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var interval = 5000;
+
+ function test_initialize(t) {
+ t.plan(2);
+
+ var s = new OpenLayers.Strategy.Refresh({interval: interval});
+ t.ok(typeof s.interval === "number", "interval must be a number");
+ t.eq(s.interval, interval, "ctor sets interval");
+ }
+
+ function test_activate(t) {
+ t.plan(4);
+
+ var l = new OpenLayers.Layer.Vector();
+ l.setVisibility(false);
+ var s = new OpenLayers.Strategy.Refresh();
+ s.setLayer(l);
+
+ t.eq(s.active, false, "not active after construction");
+
+ var activated = s.activate();
+ t.eq(activated, true, "activate returns true");
+ t.eq(s.active, true, "activated after activate");
+ t.ok(l.events.listeners.visibilitychanged[0].obj == s &&
+ l.events.listeners.visibilitychanged[0].func == s.reset,
+ "activates registers visibilitychanged listener");
+ }
+
+ function test_deactivate(t) {
+ t.plan(3);
+
+ var l = new OpenLayers.Layer.Vector();
+ l.setVisibility(false);
+ var s = new OpenLayers.Strategy.Refresh();
+ s.setLayer(l);
+ s.activate();
+ var deactivated = s.deactivate();
+ t.eq(deactivated, true, "deactivate returns true");
+ t.eq(s.active, false, "deactivated after activate");
+ t.ok(l.events.listeners.visibilitychanged.length == 0,
+ "deactivate unregisters visibilitychanged listener");
+ }
+
+ function test_activateWithVisibleLayer(t) {
+ t.plan(5);
+
+ var l = new OpenLayers.Layer.Vector();
+ l.setVisibility(true);
+ var s = new OpenLayers.Strategy.Refresh({interval: interval});
+ s.setLayer(l);
+
+ t.eq(s.active, false, "not active after construction");
+
+ var activated = s.activate();
+ t.eq(activated, true, "activate returns true");
+ t.eq(s.active, true, "activated after activate");
+ t.ok(l.events.listeners.visibilitychanged[0].obj == s &&
+ l.events.listeners.visibilitychanged[0].func == s.reset,
+ "activates registers visibilitychanged listener");
+ t.ok(s.timer !== null, "timer should be set on activate if layer is visible");
+
+ // reset the timer!!
+ s.stop();
+ }
+
+ function test_events(t) {
+
+ t.plan(1);
+ var log = {
+ visibilitychanged: 0
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [new OpenLayers.Strategy.Refresh({interval: interval})],
+ protocol: new OpenLayers.Protocol({
+ read: function(config) {
+ config.callback.call(config.scope, {});
+ }
+ }),
+ isBaseLayer: true,
+ eventListeners: {
+ visibilitychanged: function() {
+ ++log.visibilitychanged;
+ }
+ }
+ });
+ map.addLayer(layer);
+
+ layer.setVisibility(false);
+ t.eq(log.visibilitychanged, 1, "visibilitychanged triggered");
+
+ map.destroy();
+
+ }
+
+ function test_refreshWithNormalProgress(t) {
+
+ t.plan(1);
+ var log = {
+ refreshcalled: 0
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [new OpenLayers.Strategy.Refresh({
+ interval: interval,
+ refresh: function() {
+ if (this.layer && this.layer.refresh) {
+ ++log.refreshcalled;
+ }
+ }
+ })],
+ protocol: new OpenLayers.Protocol({
+ read: function(config) {
+ config.callback.call(config.scope, {});
+ }
+ }),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+
+ t.delay_call((5 * (interval / 1000)) + 0.5, function() {
+ t.eq(log.refreshcalled, 5, "number of refreshes");
+ map.destroy();
+ });
+
+ }
+
+ function test_refreshWithSwitchingVisibility(t) {
+
+ t.plan(1);
+ var log = {
+ refreshcalled: 0
+ };
+
+ var map = new OpenLayers.Map("map");
+ var layer = new OpenLayers.Layer.Vector(null, {
+ strategies: [new OpenLayers.Strategy.Refresh({
+ interval: interval,
+ refresh: function() {
+ if (this.layer && this.layer.refresh) {
+ ++log.refreshcalled;
+ }
+ }
+ })],
+ protocol: new OpenLayers.Protocol({
+ read: function(config) {
+ config.callback.call(config.scope, {});
+ }
+ }),
+ isBaseLayer: true
+ });
+ map.addLayer(layer);
+
+ window.setTimeout(function() {
+ layer.setVisibility(false);
+ }, 2.5 * interval);
+
+ t.delay_call((5 * (interval / 1000)) + 0.5, function() {
+ t.eq(log.refreshcalled, 2, "number of refreshes");
+ map.destroy();
+ });
+
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 200px;"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Strategy/Save.html b/misc/openlayers/tests/Strategy/Save.html
new file mode 100644
index 0000000..1290485
--- /dev/null
+++ b/misc/openlayers/tests/Strategy/Save.html
@@ -0,0 +1,127 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(1);
+ var strategy = new OpenLayers.Strategy.Save();
+ t.eq(strategy.auto, false, "auto is false by default");
+ }
+
+ function test_activate(t) {
+
+ t.plan(3);
+
+ var strategy = new OpenLayers.Strategy.Save();
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ protocol: new OpenLayers.Protocol(),
+ strategies: [strategy]
+ });
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ // check that auto true registers listeners
+ strategy.deactivate();
+ strategy.auto = true;
+ strategy.activate();
+ t.ok(layer.events.listeners["featureadded"][0].func === strategy.triggerSave,
+ "[auto true] triggerSave registered as listener for featureadded");
+ t.ok(layer.events.listeners["afterfeaturemodified"][0].func === strategy.triggerSave,
+ "[auto true] triggerSave registered as listener for afterfeaturemodified");
+
+ // check that auto can be set to interval
+ strategy.deactivate();
+ strategy.auto = 1;
+ strategy.activate();
+ t.ok(strategy.timer != null, "[auto number] timer set")
+
+ map.destroy();
+
+ }
+
+ function test_save(t) {
+ t.plan(9);
+
+ var strategy = new OpenLayers.Strategy.Save();
+
+ // mock up a protocol for synchronous and successful commits
+ var protocol = new OpenLayers.Protocol({
+ commit: function(features, options) {
+ var response = new OpenLayers.Protocol.Response();
+ response.reqFeatures = features;
+ response.insertIds = [];
+ for(var i=0; i<features.length; ++i) {
+ // test feature.url first
+ t.eq(features[i].url, "url" + i,
+ "feature.url correctly set (url" + i + ")");
+ if(features[i].state == OpenLayers.State.INSERT) {
+ response.insertIds.push("new_" + i);
+ }
+ }
+ response.code = OpenLayers.Protocol.Response.SUCCESS;
+ options.callback.call(options.scope, response);
+ }
+ });
+
+ var layer = new OpenLayers.Layer.Vector(null, {
+ isBaseLayer: true,
+ protocol: protocol,
+ strategies: [strategy],
+ projection: "EPSG:4326"
+ });
+ var map = new OpenLayers.Map("map", {
+ getProjectionObject: function() {
+ return new OpenLayers.Projection("EPSG:900913");
+ }
+ })
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+
+ // give the layer some features
+ var features = [
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(5, 45)
+ ), // insert
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(6, 46)
+ ), // delete
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(7, 47)
+ ), // update
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(8, 48)
+ ) // nothing
+ ];
+ features[0].state = OpenLayers.State.INSERT;
+ features[0].url = "url0";
+ features[1].state = OpenLayers.State.DELETE;
+ features[1].url = "url1";
+ features[2].state = OpenLayers.State.UPDATE;
+ features[2].url = "url2";
+ features[3].url = "url3";
+ layer.addFeatures(features);
+
+ // save feature modifications
+ strategy.save(features);
+
+ // confirm that newly created feature has an id and no longer has insert state
+ t.eq(features[0].fid, "new_0", "newly created feature gets fid");
+ t.ok(features[0].state == null, "newly created feature no longer insert state");
+
+ // confirm that deleted features are not on layer
+ t.eq(layer.features.length, 3, "layer only has three features");
+ t.ok(features[1].layer == null, "deleted feature has no layer");
+
+ // confirm that updated feature no longer has update state
+ t.ok(features[2].state == null, "updated feature no longer update state");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 400px; height: 200px" />
+</body>
+</html>
diff --git a/misc/openlayers/tests/Style.html b/misc/openlayers/tests/Style.html
new file mode 100644
index 0000000..0b8b33b
--- /dev/null
+++ b/misc/openlayers/tests/Style.html
@@ -0,0 +1,426 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Style_constructor(t) {
+ t.plan(6);
+
+ var rules = [
+ new OpenLayers.Rule({
+ symbolizer: {fillColor: "red"},
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "type",
+ value: "fire engine"
+ })
+ }),
+ new OpenLayers.Rule({
+ symbolizer: {fillColor: "yellow"},
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "type",
+ value: "sports car"
+ })
+ })
+ ];
+ var style = new OpenLayers.Style(null, {
+ foo: "bar",
+ rules: rules
+ });
+ t.ok(style instanceof OpenLayers.Style,
+ "new OpenLayers.Style returns object" );
+ t.eq(style.foo, "bar", "constructor sets options correctly");
+ t.eq(style.rules.length, 2, "correct number of rules added");
+ t.ok(style.rules[0] === rules[0], "correct first rule added");
+ t.ok(style.rules[1] === rules[1], "correct second rule added");
+ t.eq(typeof style.createSymbolizer, "function", "style has a createSymbolizer function");
+ }
+
+ function test_Style_create(t) {
+ t.plan(10);
+
+ var map = new OpenLayers.Map("map");
+
+ var layer = new OpenLayers.Layer.Vector("layer");
+
+ var baseStyle = OpenLayers.Util.extend(
+ OpenLayers.Feature.Vector.style["default"],
+ {externalGraphic: "bar${foo}.png"}
+ );
+
+ var style = new OpenLayers.Style(baseStyle);
+
+ var rule1 = new OpenLayers.Rule({
+ symbolizer: {"Point": {fillColor: "green"}},
+ maxScaleDenominator: 500000,
+ filter: new OpenLayers.Filter.FeatureId({
+ fids: ["1"]
+ })
+ });
+ var rule2 = new OpenLayers.Rule({
+ symbolizer: {"Point": {fillColor: "yellow"}},
+ minScaleDenominator: 500000,
+ maxScaleDenominator: 1000000,
+ filter: new OpenLayers.Filter.FeatureId({
+ fids: ["1"]
+ })
+ });
+ var rule3 = new OpenLayers.Rule({
+ symbolizer: {"Point": {fillColor: "red"}},
+ minScaleDenominator: 1000000,
+ maxScaleDenominator: 2500000,
+ filter: new OpenLayers.Filter.FeatureId({
+ fids: ["1"]
+ })
+ });
+
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(3,5),
+ {"foo": "bar"}
+ );
+
+ feature.fid = "1";
+ // for this fid, the above rule should apply
+
+ layer.styleMap = new OpenLayers.StyleMap(style);
+
+ layer.addFeatures([feature]);
+ map.addLayer(layer);
+ map.setBaseLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(3,5), 10);
+
+ var createdStyle = style.createSymbolizer(feature);
+ t.eq(createdStyle.externalGraphic, "barbar.png", "Calculated property style for default symbolizer correctly.");
+
+ style.addRules([rule1, rule2, rule3]);
+ createdStyle = style.createSymbolizer(feature);
+
+ // at this scale, the feature should be green
+ t.eq(createdStyle.display, undefined, "Feature is visible at scale "+map.getScale());
+ t.eq(createdStyle.fillColor, "green", "Point symbolizer from rule applied correctly.");
+
+ map.setCenter(new OpenLayers.LonLat(3,5), 9);
+ // at this scale, the feature should be red
+ createdStyle = style.createSymbolizer(feature);
+ t.eq(createdStyle.display, undefined, "Feature is visible at scale "+map.getScale());
+ t.eq(createdStyle.fillColor, "yellow", "Point symbolizer from rule applied correctly.");
+
+ map.setCenter(new OpenLayers.LonLat(3,5), 8);
+ // at this scale, the feature should be yellow
+ createdStyle = style.createSymbolizer(feature);
+ t.eq(createdStyle.display, undefined, "Feature is visible at scale "+map.getScale());
+ t.eq(createdStyle.fillColor, "red", "Point symbolizer from rule applied correctly.");
+
+ map.setCenter(new OpenLayers.LonLat(3,5), 7);
+ // at this scale, the feature should be invisible
+ createdStyle = style.createSymbolizer(feature);
+ t.eq(createdStyle.display, "none", "Feature is invisible at scale "+map.getScale());
+ t.eq(createdStyle.fillColor, baseStyle.fillColor, "Point symbolizer from base style applied correctly.");
+
+ feature.fid = "2";
+ // now the rule should not apply
+
+ createdStyle = style.createSymbolizer(feature);
+ t.eq(createdStyle.fillColor, baseStyle.fillColor, "Correct style for rule that does not apply to fid=\"2\".");
+ }
+
+ function test_Style_createSymbolizer(t) {
+ t.plan(5);
+ var style = new OpenLayers.Style();
+
+ // override applySymbolizer to log arguments
+ var log = [];
+ style.applySymbolizer = function(r) {
+ log.push(r);
+ OpenLayers.Style.prototype.applySymbolizer.apply(this, arguments);
+ };
+
+ // rules for the style
+ var rule = new OpenLayers.Rule({
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "foo",
+ value: "bar"
+ }),
+ symbolizer: {
+ label: "${labelValue}"
+ }
+ });
+ rule.id = "foo=bar rule";
+ var elseRule = new OpenLayers.Rule({
+ elseFilter: true,
+ symbolizer: {
+ label: "${labelValue}"
+ }
+ });
+ elseRule.id = "else rule";
+ style.addRules([rule, elseRule]);
+
+ // a) test that applySymbolizer is only called with rule
+ log = [];
+ style.createSymbolizer(
+ new OpenLayers.Feature.Vector(null, {foo: "bar"})
+ );
+ t.eq(log.length, 1, "a) applySymbolizer called once");
+ t.eq(log[0] && log[0].id, rule.id, "a) applySymbolizer called with correct rule");
+
+ // b) test that applySymbolizer is only called with elseRule
+ log = [];
+ style.createSymbolizer(
+ new OpenLayers.Feature.Vector(null, {foo: "baz"})
+ );
+ t.eq(log.length, 1, "b) applySymbolizer called once");
+ t.eq(log[0] && log[0].id, elseRule.id, "b) applySymbolizer called with correct rule");
+
+ // c) test that label in returned symbolizer is a string even if property value is a number
+ var symbolizer = style.createSymbolizer(
+ new OpenLayers.Feature.Vector(null, {foo: "bar", labelValue: 0})
+ );
+ t.eq(symbolizer.label, "0", "c) feature property cast to string when used as symbolizer label");
+
+ }
+
+ function test_Style_applySymbolizer(t) {
+ t.plan(15);
+
+ var feature = new OpenLayers.Feature.Vector();
+ var defaults = OpenLayers.Feature.Vector.style["default"];
+ var style, symbolizer;
+
+ style = new OpenLayers.Style();
+ symbolizer = style.createSymbolizer(feature);
+ t.eq(symbolizer.pointRadius, defaults.pointRadius, "symbolizer has the correct pointRadius");
+ t.eq(symbolizer.strokeWidth, defaults.strokeWidth, "symbolizer has the correct strokeWidth");
+ t.eq(symbolizer.fillColor, defaults.fillColor, "symbolizer has the correct fillColor");
+ t.eq(symbolizer.graphicName, defaults.graphicName, "symbolizer has the correct graphicName");
+
+ style = new OpenLayers.Style(null, {
+ defaultsPerSymbolizer: true,
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ stroke: true
+ }
+ })
+ ]
+ });
+ symbolizer = style.createSymbolizer(feature);
+ t.eq(symbolizer.strokeWidth, defaults.strokeWidth, "symbolizer has the correct strokeWidth");
+ t.ok(symbolizer.fillColor == undefined, "fillColor is undefined");
+
+ style = new OpenLayers.Style(null, {
+ defaultsPerSymbolizer: true,
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ }
+ })
+ ]
+ });
+ symbolizer = style.createSymbolizer(feature);
+ t.eq(symbolizer.pointRadius, defaults.pointRadius, "symbolizer has the correct pointRadius");
+ t.ok(symbolizer.strokeWidth == undefined, "strokeWidth is undefined");
+ t.ok(symbolizer.fillColor == undefined, "fillColor is undefined");
+ t.ok(symbolizer.graphicName == undefined, "graphicName is undefined");
+
+ style = new OpenLayers.Style(null, {
+ defaultsPerSymbolizer: true,
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ stroke: true
+ }
+ })
+ ]
+ });
+ symbolizer = style.createSymbolizer(feature);
+ t.eq(symbolizer.strokeWidth, defaults.strokeWidth, "symbolizer has the correct strokeWidth");
+ t.ok(symbolizer.fillColor == undefined, "fillColor is undefined");
+
+ style = new OpenLayers.Style(null, {
+ defaultsPerSymbolizer: true,
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ fill: true
+ }
+ })
+ ]
+ });
+ symbolizer = style.createSymbolizer(feature);
+ t.eq(symbolizer.fillColor, defaults.fillColor, "symbolizer has the correct fillColor");
+ t.ok(symbolizer.strokeWidth == undefined, "strokeWidth is undefined");
+
+ style = new OpenLayers.Style(null, {
+ defaultsPerSymbolizer: true,
+ rules: [
+ new OpenLayers.Rule({
+ symbolizer: {
+ graphic: true
+ }
+ })
+ ]
+ });
+ symbolizer = style.createSymbolizer(feature);
+ t.eq(symbolizer.graphicName, defaults.graphicName, "symbolizer has the correct graphicName");
+ }
+
+ function test_Style_context(t) {
+ t.plan(4);
+ var rule = new OpenLayers.Rule({
+ symbolizer: {"Point": {externalGraphic: "${img1}"}},
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.LESS_THAN,
+ property: "size",
+ value: 11
+ })
+ });
+ var style = new OpenLayers.Style();
+ style.context = {
+ "img1": "myImage.png"
+ };
+ style.addRules([rule]);
+ var feature = new OpenLayers.Feature.Vector();
+ feature.attributes = {size: 10};
+ var styleHash = style.createSymbolizer(feature);
+ t.eq(styleHash.externalGraphic, "myImage.png", "correctly evaluated rule and calculated property styles from a custom context");
+
+ // same as above, but without rule (#1526)
+ style = new OpenLayers.Style(
+ {externalGraphic: "${getExternalGraphic}"},
+ {context: {
+ getExternalGraphic: function(feature) {
+ return "foo" + feature.attributes.size + ".png";
+ }
+ }});
+ t.eq(style.createSymbolizer(feature).externalGraphic, "foo10.png", "correctly evaluated symbolizer without rule");
+
+ style = new OpenLayers.Style(
+ {externalGraphic: "${getExternalGraphic}",
+ pointRadius: "${size}"},
+ {context: {
+ getExternalGraphic: function(feature) {
+ return "foo" + feature.attributes.size + ".png";
+ }
+ }});
+ t.eq(style.createSymbolizer(feature).externalGraphic, "foo10.png", "correctly evaluated symbolizer from context");
+ t.eq(style.createSymbolizer(feature).pointRadius, 10, "correctly evaluated symbolizer from attributes");
+
+ };
+
+ function test_Style_findPropertyStyles(t) {
+ t.plan(4);
+ var rule1 = new OpenLayers.Rule({symbolizer: {
+ pointRadius: 3,
+ externalGraphic: "${foo}.bar"
+ }});
+ var rule2 = new OpenLayers.Rule({symbolizer: {"Point": {
+ strokeWidth: "${foo}"
+ }}});
+ var style = new OpenLayers.Style({
+ strokeOpacity: 1,
+ strokeColor: "${foo}"
+ });
+ style.addRules([rule1, rule2]);
+ var propertyStyles = style.findPropertyStyles();
+ t.ok(propertyStyles.externalGraphic, "detected externalGraphic from rule correctly");
+ t.ok(propertyStyles.strokeWidth, "detected strokeWidth from Point symbolizer correctly");
+ t.ok(propertyStyles.strokeColor, "detected strokeColor from style correctly");
+ t.eq(typeof propertyStyles.pointRadius, "undefined", "correctly detected pointRadius as non-property style");
+ }
+
+ function test_createLiteral(t) {
+ t.plan(6);
+
+ var value, context, feature, result, expected;
+ var func = OpenLayers.Style.createLiteral;
+
+ // without templates
+ value = "foo";
+ expected = value;
+ result = func(value);
+ t.eq(result, expected, "(no template) preserves literal");
+
+ // with templates
+ value = "${foo}"
+ expected = "bar";
+ context = {foo: expected};
+ result = func(value, context);
+ t.eq(result, expected, "(template) preserves literal");
+
+ expected = "";
+ context = {foo: expected};
+ result = func(value, context);
+ t.eq(result, expected, "(template) preserves empty string");
+
+ expected = "16/03/2008";
+ context = {foo: expected};
+ result = func(value, context);
+ t.eq(result, expected, "(template) preserves string with numbers");
+
+ expected = 16;
+ context = {foo: expected + ""};
+ result = func(value, context);
+ t.eq(result, expected, "(template) casts integer in a string");
+
+ expected = 16;
+ context = {foo: " " + expected + " "};
+ result = func(value, context);
+ t.eq(result, expected, "(template) casts integer in a space padded string");
+
+ }
+
+ function test_clone(t) {
+
+ t.plan(7);
+
+ var style = new OpenLayers.Style({bar: "baz"}, {
+ name: "test style",
+ rules: [new OpenLayers.Rule({
+ name: "test rule"
+ })],
+ context: {
+ foo: "bar"
+ }
+ });
+ var clone = style.clone();
+ t.eq(clone.name, "test style", "name copied");
+ t.eq(clone.rules[0].name, "test rule", "clone has correct rule");
+
+ // modify original
+ style.name = "new";
+ style.addRules([new OpenLayers.Rule({
+ name: "new rule"
+ })]);
+ style.context.foo = "baz";
+
+ // confirm that clone didn't change
+ t.eq(clone.defaultStyle.bar, "baz", "clone has clone of defaultStyle");
+ t.eq(clone.name, "test style", "clone has clone of name");
+ t.eq(clone.rules.length, 1, "clone has clone of rules");
+ t.eq(clone.context.foo, "bar", "clone has clone of context");
+
+ // confirm that ids are different
+ t.ok(clone.id !== style.id, "clone has different id");
+
+ style.destroy();
+ clone.destroy();
+
+ }
+
+ function test_Style_destroy(t) {
+ t.plan(1);
+
+ var style = new OpenLayers.Style();
+ style.destroy();
+ t.eq(style.rules, null, "rules array nulled properly");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Style2.html b/misc/openlayers/tests/Style2.html
new file mode 100644
index 0000000..87ab584
--- /dev/null
+++ b/misc/openlayers/tests/Style2.html
@@ -0,0 +1,56 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(4);
+
+ var rules = [
+ new OpenLayers.Rule({
+ symbolizer: {fillColor: "red"},
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "type",
+ value: "fire engine"
+ })
+ }),
+ new OpenLayers.Rule({
+ symbolizer: {fillColor: "yellow"},
+ filter: new OpenLayers.Filter.Comparison({
+ type: OpenLayers.Filter.Comparison.EQUAL_TO,
+ property: "type",
+ value: "sports car"
+ })
+ })
+ ];
+ var style = new OpenLayers.Style2({rules: rules});
+ t.ok(style instanceof OpenLayers.Style2, "correct type");
+ t.eq(style.rules.length, 2, "correct number of rules added");
+ t.ok(style.rules[0] === rules[0], "correct first rule added");
+ t.ok(style.rules[1] === rules[1], "correct second rule added");
+ }
+
+ function test_destroy(t) {
+ t.plan(1);
+
+ var style = new OpenLayers.Style2({
+ rules: [
+ new OpenLayers.Rule({
+ symbolizers: [
+ new OpenLayers.Symbolizer.Point({
+ fillColor: "fuchsia"
+ })
+ ]
+ })
+ ]
+ });
+ style.destroy();
+ t.ok(!style.rules, "rules array gone");
+ }
+
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/StyleMap.html b/misc/openlayers/tests/StyleMap.html
new file mode 100644
index 0000000..6c633c3
--- /dev/null
+++ b/misc/openlayers/tests/StyleMap.html
@@ -0,0 +1,44 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_StyleMap_constructor(t) {
+ t.plan(6);
+
+ var options = {'foo': 'bar'};
+ var styleMap = new OpenLayers.StyleMap(null, options);
+ t.ok(styleMap instanceof OpenLayers.StyleMap,
+ "new OpenLayers.StyleMap returns object" );
+ t.eq(styleMap.foo, "bar", "constructor sets options correctly");
+
+ var style = new OpenLayers.Style();
+ var styleMap = new OpenLayers.StyleMap(style);
+ t.eq(styleMap.styles["default"].defaultStyle.strokeColor, style.defaultStyle.strokeColor, "default style set correctly from style object");
+
+ var style = {strokeColor: "blue"};
+ var styleMap = new OpenLayers.StyleMap(style);
+ t.eq(styleMap.styles["default"].defaultStyle.strokeColor, "blue", "default style set correctly from style hash");
+
+ var style = {
+ "default": new OpenLayers.Style({strokeColor: "yellow"}),
+ "select": {strokeColor: "blue"}};
+ var styleMap = new OpenLayers.StyleMap(style);
+ t.eq(styleMap.styles["default"].defaultStyle.strokeColor, "yellow", "default style set correctly from a mixed hash of renderIntents");
+ t.eq(styleMap.styles["select"].defaultStyle.strokeColor, "blue", "select style set correctly from a mixed hash of renderIntents");
+ }
+
+ function test_StyleMap_destroy(t) {
+ t.plan(2);
+ var styleMap = new OpenLayers.StyleMap();
+ t.ok(styleMap.styles["default"], "Got a default style after initialisation");
+ styleMap.destroy();
+ t.ok(!styleMap.styles, "StyleMap styles successfully destroyed");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Symbolizer.html b/misc/openlayers/tests/Symbolizer.html
new file mode 100644
index 0000000..be24e9c
--- /dev/null
+++ b/misc/openlayers/tests/Symbolizer.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer({foo: "bar"});
+
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "correct type");
+ t.eq(symbolizer.foo, "bar", "constructor applies config properties");
+
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer({foo: "bar"});
+ var clone = symbolizer.clone();
+
+ t.ok(clone instanceof OpenLayers.Symbolizer, "correct type");
+ t.eq(clone.foo, "bar", "clone copies properties");
+
+ }
+
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/Symbolizer/Line.html b/misc/openlayers/tests/Symbolizer/Line.html
new file mode 100644
index 0000000..5396f3b
--- /dev/null
+++ b/misc/openlayers/tests/Symbolizer/Line.html
@@ -0,0 +1,42 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var symbolizer = new OpenLayers.Symbolizer.Line({foo: "bar"});
+
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "instance of OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Line, "instance of OpenLayers.Symbolizer.Line");
+ t.eq(symbolizer.foo, "bar", "constructor applies config properties");
+
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer.Line({foo: "bar"});
+ var clone = symbolizer.clone();
+
+ t.ok(clone instanceof OpenLayers.Symbolizer.Line, "correct type");
+ t.eq(clone.foo, "bar", "clone copies properties");
+
+ }
+
+ function test_defaults(t) {
+ t.plan(5);
+ var symbolizer = new OpenLayers.Symbolizer.Line();
+ t.ok(symbolizer.strokeColor === undefined, "no default strokeColor");
+ t.ok(symbolizer.strokeOpacity === undefined, "no default strokeOpacity");
+ t.ok(symbolizer.strokeWidth === undefined, "no default strokeWidth");
+ t.ok(symbolizer.strokeLinecap === undefined, "no default strokeLinecap");
+ t.ok(symbolizer.strokeDashstyle === undefined, "no default strokeDashstyle");
+ }
+
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/Symbolizer/Point.html b/misc/openlayers/tests/Symbolizer/Point.html
new file mode 100644
index 0000000..b1311c0
--- /dev/null
+++ b/misc/openlayers/tests/Symbolizer/Point.html
@@ -0,0 +1,52 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var symbolizer = new OpenLayers.Symbolizer.Point({foo: "bar"});
+
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "instance of OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Point, "instance of OpenLayers.Symbolizer.Point");
+ t.eq(symbolizer.foo, "bar", "constructor applies config properties");
+
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer.Point({foo: "bar"});
+ var clone = symbolizer.clone();
+
+ t.ok(clone instanceof OpenLayers.Symbolizer.Point, "correct type");
+ t.eq(clone.foo, "bar", "clone copies properties");
+
+ }
+
+ function test_defaults(t) {
+ t.plan(16);
+ var symbolizer = new OpenLayers.Symbolizer.Point();
+ t.ok(symbolizer.strokeColor === undefined, "no default strokeColor");
+ t.ok(symbolizer.strokeOpacity === undefined, "no default strokeOpacity");
+ t.ok(symbolizer.strokeWidth === undefined, "no default strokeWidth");
+ t.ok(symbolizer.strokeLinecap === undefined, "no default strokeLinecap");
+ t.ok(symbolizer.strokeDashstyle === undefined, "no default strokeDashstyle");
+ t.ok(symbolizer.fillColor === undefined, "no default fillColor");
+ t.ok(symbolizer.fillOpacity === undefined, "no default fillOpacity");
+ t.ok(symbolizer.pointRadius === undefined, "no default pointRadius");
+ t.ok(symbolizer.externalGraphic === undefined, "no default externalGraphic");
+ t.ok(symbolizer.graphicWidth === undefined, "no default graphicWidth");
+ t.ok(symbolizer.graphicHeight === undefined, "no default graphicHeight");
+ t.ok(symbolizer.graphicOpacity === undefined, "no default graphicOpacity");
+ t.ok(symbolizer.graphicXOffset === undefined, "no default graphicXOffset");
+ t.ok(symbolizer.graphicYOffset === undefined, "no default graphicYOffset");
+ t.ok(symbolizer.rotation === undefined, "no default rotation");
+ t.ok(symbolizer.graphicName === undefined, "no default graphicName");
+ }
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/Symbolizer/Polygon.html b/misc/openlayers/tests/Symbolizer/Polygon.html
new file mode 100644
index 0000000..ebea5ea
--- /dev/null
+++ b/misc/openlayers/tests/Symbolizer/Polygon.html
@@ -0,0 +1,44 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var symbolizer = new OpenLayers.Symbolizer.Polygon({foo: "bar"});
+
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "instance of OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Polygon, "instance of OpenLayers.Symbolizer.Polygon");
+ t.eq(symbolizer.foo, "bar", "constructor applies config properties");
+
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer.Polygon({foo: "bar"});
+ var clone = symbolizer.clone();
+
+ t.ok(clone instanceof OpenLayers.Symbolizer.Polygon, "correct type");
+ t.eq(clone.foo, "bar", "clone copies properties");
+
+ }
+
+ function test_defaults(t) {
+ t.plan(7);
+ var symbolizer = new OpenLayers.Symbolizer.Polygon();
+ t.ok(symbolizer.strokeColor === undefined, "no default strokeColor");
+ t.ok(symbolizer.strokeOpacity === undefined, "no default strokeOpacity");
+ t.ok(symbolizer.strokeWidth === undefined, "no default strokeWidth");
+ t.ok(symbolizer.strokeLinecap === undefined, "no default strokeLinecap");
+ t.ok(symbolizer.strokeDashstyle === undefined, "no default strokeDashstyle");
+ t.ok(symbolizer.fillColor === undefined, "no default fillColor");
+ t.ok(symbolizer.fillOpacity === undefined, "no default fillOpacity");
+ }
+
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/Symbolizer/Raster.html b/misc/openlayers/tests/Symbolizer/Raster.html
new file mode 100644
index 0000000..8dd9cb9
--- /dev/null
+++ b/misc/openlayers/tests/Symbolizer/Raster.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var symbolizer = new OpenLayers.Symbolizer.Raster({foo: "bar"});
+
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "instance of OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Raster, "instance of OpenLayers.Symbolizer.Raster");
+ t.eq(symbolizer.foo, "bar", "constructor applies config properties");
+
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer.Raster({foo: "bar"});
+ var clone = symbolizer.clone();
+
+ t.ok(clone instanceof OpenLayers.Symbolizer.Raster, "correct type");
+ t.eq(clone.foo, "bar", "clone copies properties");
+
+ }
+
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/Symbolizer/Text.html b/misc/openlayers/tests/Symbolizer/Text.html
new file mode 100644
index 0000000..a849f20
--- /dev/null
+++ b/misc/openlayers/tests/Symbolizer/Text.html
@@ -0,0 +1,42 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_constructor(t) {
+ t.plan(3);
+
+ var symbolizer = new OpenLayers.Symbolizer.Text({foo: "bar"});
+
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer, "instance of OpenLayers.Symbolizer");
+ t.ok(symbolizer instanceof OpenLayers.Symbolizer.Text, "instance of OpenLayers.Symbolizer.Text");
+ t.eq(symbolizer.foo, "bar", "constructor applies config properties");
+
+ }
+
+ function test_clone(t) {
+ t.plan(2);
+
+ var symbolizer = new OpenLayers.Symbolizer.Text({foo: "bar"});
+ var clone = symbolizer.clone();
+
+ t.ok(clone instanceof OpenLayers.Symbolizer.Text, "correct type");
+ t.eq(clone.foo, "bar", "clone copies properties");
+
+ }
+
+ function test_defaults(t) {
+ t.plan(5);
+ var symbolizer = new OpenLayers.Symbolizer.Point();
+ t.ok(symbolizer.label === undefined, "no default label");
+ t.ok(symbolizer.fontFamily === undefined, "no default fontFamily");
+ t.ok(symbolizer.fontSize === undefined, "no default fontSize");
+ t.ok(symbolizer.fontWeight === undefined, "no default fontWeight");
+ t.ok(symbolizer.fontStyle === undefined, "no default fontStyle");
+ }
+
+
+ </script>
+</head>
+<body></body>
+</html>
diff --git a/misc/openlayers/tests/Test.AnotherWay.baseadditions.js b/misc/openlayers/tests/Test.AnotherWay.baseadditions.js
new file mode 100644
index 0000000..338bf82
--- /dev/null
+++ b/misc/openlayers/tests/Test.AnotherWay.baseadditions.js
@@ -0,0 +1,191 @@
+// total counters
+Test.AnotherWay._openlayers_sum_total_detail_ok=0;
+Test.AnotherWay._openlayers_sum_total_detail_fail=0;
+Test.AnotherWay._startTime = null;
+
+// method overwrites
+//
+// behaviour (timing)
+Test.AnotherWay._old_run_all_onclick = Test.AnotherWay._run_all_onclick;
+Test.AnotherWay._run_all_onclick = function(){
+ Test.AnotherWay._startTime = (new Date()).getTime();
+ Test.AnotherWay.reset_running_time();
+ Test.AnotherWay._old_run_all_onclick.apply(this, arguments);
+};
+
+Test.AnotherWay._old_run_selected_onclick = Test.AnotherWay._run_selected_onclick;
+Test.AnotherWay._run_selected_onclick = function(){
+ Test.AnotherWay._startTime = (new Date()).getTime();
+ Test.AnotherWay.reset_running_time();
+ Test.AnotherWay._old_run_selected_onclick.apply(this, arguments);
+};
+
+Test.AnotherWay._old_run_one_onclick = Test.AnotherWay._run_one_onclick;
+Test.AnotherWay._run_one_onclick = function(){
+ Test.AnotherWay._startTime = (new Date()).getTime();
+ Test.AnotherWay.reset_running_time();
+ Test.AnotherWay._old_run_one_onclick.apply(this, arguments);
+};
+
+// test page loading
+Test.AnotherWay.old_load_next_page = Test.AnotherWay._load_next_page;
+Test.AnotherWay._load_next_page = function(){
+ document.getElementById("test_iframe_el").style.display = "none";
+ Test.AnotherWay.update_running_time();
+ Test.AnotherWay.old_load_next_page.apply(this, arguments);
+};
+
+
+Test.AnotherWay._add_test_page_url = function(test_url, convention){
+ var table = document.getElementById("testtable");
+ var record_select = document.getElementById("record_select");
+ var index = Test.AnotherWay._g_test_page_urls.length;
+
+ // trim spaces.
+ if (test_url.match("^(\\s*)(.*\\S)(\\s*)$")) {
+ test_url = RegExp.$2;
+ }
+
+ Test.AnotherWay._g_test_page_urls[index] = {
+ url: test_url,
+ convention: convention
+ };
+ var row = table.insertRow(-1);
+
+ var cell;
+ var cell_child;
+ var link;
+
+ cell = row.insertCell(-1);
+ cell_child = document.createElement("input");
+ cell_child.type = "checkbox";
+ cell_child.id = "checkbox" + index;
+ cell_child.checked = 'checked';
+ cell_child.defaultChecked = 'checked';
+ cell.appendChild(cell_child);
+
+ cell = row.insertCell(-1);
+ cell.setAttribute("width", "75%");
+
+ // make the URL a clickable link that opens in a new window
+ // start changes
+ link = document.createElement("a");
+ link.href=test_url;
+ link.target='_blank';
+ link.title='Opens testfile in a new window.';
+ link.appendChild(document.createTextNode(test_url));
+ cell.appendChild(link);
+ // end changes
+
+ cell = row.insertCell(-1);
+ cell_child = document.createElement("input");
+ cell_child.type = "button";
+ cell_child.id = "test" + index;
+ cell_child.value = " run ";
+ cell_child.onclick = Test.AnotherWay._run_one_onclick;
+ cell.appendChild(cell_child);
+
+ cell = row.insertCell(-1);
+ cell.setAttribute("width", "8em");
+ cell_child = document.createElement("span");
+ cell.appendChild(cell_child);
+
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(test_url));
+ record_select.appendChild(option);
+};
+
+Test.AnotherWay.old_set_iframe_location = Test.AnotherWay._set_iframe_location;
+Test.AnotherWay._set_iframe_location = function(iframe, loc, outside_path_correction){
+ var optionPos = loc.indexOf( "?" ),
+ option;
+ if (optionPos != -1) {
+ option = loc.substring(optionPos+1);
+ loc = loc.substring(0, optionPos);
+ }
+ if (option === "visible") {
+ document.getElementById("test_iframe_el").style.display = "";
+ }
+ return Test.AnotherWay.old_set_iframe_location.call(this, iframe, loc, outside_path_correction);
+};
+
+// new methods
+Test.AnotherWay.update_running_time = function() {
+ var now = (new Date()).getTime();
+ var floor = Math.floor;
+ var elapsed = now - Test.AnotherWay._startTime;
+ var zeroPad = function(num, length){
+ var len = -1 * (length || 2);
+ return ('00000' + num).slice(len);
+ };
+ var ms = zeroPad(elapsed%1000, 3);
+ var seconds=zeroPad(floor((elapsed/1000)%60));
+ var minutes=zeroPad(floor((elapsed/60000)%60));
+
+ document.getElementById('running-time').innerHTML = 'Elapsed time ' + minutes + ':' + seconds + ':' + ms +' (m:s:ms).';
+};
+
+Test.AnotherWay.reset_running_time = function(){
+ document.getElementById('running-time').innerHTML = '';
+};
+
+// quickfilter
+Test.AnotherWay.bindQuicksearchListener = function(){
+ var input = document.getElementById('quickfilter');
+ if (input.addEventListener) {
+ input.addEventListener('keyup', Test.AnotherWay.quicksearch);
+ } else if (input.attachEvent) {
+ input.attachEvent('onkeyup', Test.AnotherWay.quicksearch);
+ } else {
+ // remove the input field
+ input.parentNode.removeChild(input);
+ }
+};
+Test.AnotherWay.quicksearchThrottleTimeOut = null;
+Test.AnotherWay.quicksearch = function(){
+ if (Test.AnotherWay.quicksearchThrottleTimeOut) {
+ window.clearTimeout(Test.AnotherWay.quicksearchThrottleTimeOut);
+ }
+ Test.AnotherWay.quicksearchThrottleTimeOut = window.setTimeout(function(){
+ var input = document.getElementById('quickfilter');
+ Test.AnotherWay.filterTestList(input.value);
+ }, 300);
+};
+
+Test.AnotherWay.filterTestList = function(str){
+ Test.AnotherWay.unfilterTestList();
+ var re = new RegExp(str, 'i');
+ var candidates = document.querySelectorAll('#testtable tr a');
+ for (var idx = 0, len = candidates.length; idx<len; idx++) {
+ var tr = candidates[idx].parentNode.parentNode;
+ var html = candidates[idx].innerHTML;
+ if (re.test(html)) {
+ tr.className = 'isShown';
+ } else {
+ tr.className = 'isHidden';
+ }
+ }
+
+};
+
+Test.AnotherWay.unfilterTestList = function() {
+ if ( document.querySelectorAll ) {
+ var hidden = document.querySelectorAll('.isHidden');
+ for (var idx = 0, len = hidden.length; idx < len; idx++) {
+ hidden[idx].className = 'isShown';
+ }
+ }
+};
+
+// bind our quicksearch init method to body onload.
+(function(win) {
+ if (win.addEventListener) {
+ win.addEventListener('load', Test.AnotherWay.bindQuicksearchListener);
+ } else if (win.attachEvent) {
+ win.attachEvent('onload', Test.AnotherWay.bindQuicksearchListener);
+ } else {
+ win.onload = function(){
+ Test.AnotherWay.bindQuicksearchListener();
+ };
+ }
+})(window);
diff --git a/misc/openlayers/tests/Test.AnotherWay.css b/misc/openlayers/tests/Test.AnotherWay.css
new file mode 100644
index 0000000..5bb1181
--- /dev/null
+++ b/misc/openlayers/tests/Test.AnotherWay.css
@@ -0,0 +1,243 @@
+/**
+ * Test.AnotherWay version 0.5
+ *
+ * Copyright (c) 2005 Artem Khodush, http://straytree.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+* {
+ padding: 0;
+ margin: 0;
+}
+
+html {
+ height: 99%;
+}
+
+body {
+ height: 98%;
+ font: normal normal 10pt sans-serif
+}
+
+#col1 {
+ float: left;
+ width: 27em;
+ margin: 0 0 0 1em;
+ overflow: visible;
+}
+
+#col2 {
+ position: relative;
+ height: 98%;
+ margin: 0 0.5em 0 28em;
+}
+
+#col1_header {
+ margin-top: 0.5em;
+}
+
+#scroller {
+ height: 400px;
+ overflow: auto;
+}
+
+#testtable {
+ margin: 0 0 2em 0;
+ width: 97%;
+ font-size: 1em;
+ border-collapse: collapse;
+}
+#testtable input {
+ cursor: pointer;
+}
+#testtable td {
+ line-height: 2em;
+ padding: 0;
+ margin: 0;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+}
+#testtable tr:hover td {
+ background-color: #ededed;
+}
+#testtable tr.isHidden {
+ display: none;
+}
+#testtable tr.isShown {
+ display: table-row;
+}
+
+#run_buttons, #running-time {
+ margin-bottom: 1em;
+}
+
+#right_header {
+ padding-top: 0.8em;
+}
+
+#results_count {
+ float: left;
+}
+
+#results > p:hover {
+ background-color: #ededed;
+}
+
+.active_tab {
+ float: right;
+ padding: 0 1em 0.2em 1em;
+ background: #0af;
+ border: 1px solid #048;
+ border-bottom: none;
+ cursor: pointer;
+ cursor: hand;
+ position: relative;
+ top: -0.2em;
+}
+
+.inactive_tab {
+ float: right;
+ padding: 0 1em 0 1em;
+ background: #9bb;
+ color: #444;
+ border: 1px solid #9bb;
+ border-bottom: none;
+ cursor: pointer;
+ cursor: hand;
+}
+
+.inactive_mouseover_tab {
+ float: right;
+ padding: 0 1em 0 1em;
+ background: #9bb;
+ color: #062;
+ border: 1px solid #062;
+ border-bottom: none;
+ cursor: pointer;
+ cursor: hand;
+}
+
+#right_frame {
+ overflow: auto;
+ position: relative;
+ top: -0.2em;
+ clear: right;
+ height: 95%;
+ border: 1px solid #048;
+}
+
+#debug {
+ display: none;
+}
+
+#debug p {
+ margin: 2px 0 0 5em;
+ text-indent: -4.8em;
+}
+
+#error {
+ display: none;
+ color: #c22;
+}
+
+#results p {
+ margin: 0 0 2px 0;
+}
+
+/* cursor indicating that detailed results may be expanded/contracted */
+#results p.badtest {
+ cursor: text;
+}
+
+#results p.ok, #results p.fail {
+ cursor: pointer;
+ cursor: hand;
+}
+
+/* colored squares in the results window at the left of test page names */
+#results p.ok .bullet {
+ background: #6d6;
+}
+
+#results p.fail .bullet {
+ background: #d46;
+}
+
+#results p.badtest .bullet {
+ background: #ea3;
+}
+
+#results p.loading .bullet {
+ background: #48f;
+}
+
+#results p.running .bullet {
+ background: #26e;
+}
+
+#results p.waiting .bullet {
+ background: #04d;
+}
+
+/* highlight in the results line */
+#results p .warning {
+ background: #ffc;
+}
+
+/* layout of the detailed results */
+.result_detail {
+ padding-left: 3em;
+}
+
+.result_exception_detail {
+ padding-left: 4em;
+}
+
+.result_exception_stack_detail {
+ padding-left: 5em;
+}
+
+.result_micro_detail {
+ padding-left: 6em;
+}
+
+/* colouring in the detailed results */
+.result_detail .fail, .result_exception_detail .fail, .result_micro_detail .fail {
+ background: #ffd8d8;
+}
+
+/* "start recording" controls*/
+#record_div {
+ margin-top: 3em;
+}
+
+#record_div p {
+ margin-bottom: 0.5em;
+}
+
+#record_select {
+ width: 88%;
+}
+
+#record_input {
+ width: 53%;
+} \ No newline at end of file
diff --git a/misc/openlayers/tests/Test.AnotherWay.geom_eq.js b/misc/openlayers/tests/Test.AnotherWay.geom_eq.js
new file mode 100644
index 0000000..893c5b5
--- /dev/null
+++ b/misc/openlayers/tests/Test.AnotherWay.geom_eq.js
@@ -0,0 +1,139 @@
+/**
+ * File: Test.AnotherWay.geom_eq.js
+ * Adds a geom_eq method to AnotherWay test objects.
+ *
+ */
+
+(function() {
+
+ /**
+ * Function assertEqual
+ * Test two objects for equivalence (based on ==). Throw an exception
+ * if not equivalent.
+ *
+ * Parameters:
+ * got - {Object}
+ * expected - {Object}
+ * msg - {String} The message to be thrown. This message will be appended
+ * with ": got {got} but expected {expected}" where got and expected are
+ * replaced with string representations of the above arguments.
+ */
+ function assertEqual(got, expected, msg) {
+ if(got === undefined) {
+ got = "undefined";
+ } else if (got === null) {
+ got = "null";
+ }
+ if(expected === undefined) {
+ expected = "undefined";
+ } else if (expected === null) {
+ expected = "null";
+ }
+ if(got != expected) {
+ throw msg + ": got '" + got + "' but expected '" + expected + "'";
+ }
+ }
+
+ /**
+ * Function assertFloatEqual
+ * Test two objects for floating point equivalence. Throw an exception
+ * if not equivalent.
+ *
+ * Parameters:
+ * got - {Object}
+ * expected - {Object}
+ * msg - {String} The message to be thrown. This message will be appended
+ * with ": got {got} but expected {expected}" where got and expected are
+ * replaced with string representations of the above arguments.
+ */
+ function assertFloatEqual(got, expected, msg) {
+ var OpenLayers = Test.AnotherWay._g_test_iframe.OpenLayers;
+ if(got === undefined) {
+ got = "undefined";
+ } else if (got === null) {
+ got = "null";
+ }
+ if(expected === undefined) {
+ expected = "undefined";
+ } else if (expected === null) {
+ expected = "null";
+ }
+ if(Math.abs(got - expected) > Math.pow(10, -OpenLayers.Util.DEFAULT_PRECISION)) {
+ throw msg + ": got '" + got + "' but expected '" + expected + "'";
+ }
+ }
+
+ /**
+ * Function assertGeometryEqual
+ * Test two geometries for equivalence. Geometries are considered
+ * equivalent if they are of the same class, and given component
+ * geometries, if all components are equivalent. Throws a message as
+ * exception if not equivalent.
+ *
+ * Parameters:
+ * got - {OpenLayers.Geometry}
+ * expected - {OpenLayers.Geometry}
+ * options - {Object} Optional object for configuring test options.
+ */
+ function assertGeometryEqual(got, expected, options) {
+
+ var OpenLayers = Test.AnotherWay._g_test_iframe.OpenLayers;
+
+ // compare types
+ assertEqual(typeof got, typeof expected, "Object types mismatch");
+
+ // compare classes
+ assertEqual(got.CLASS_NAME, expected.CLASS_NAME, "Object class mismatch");
+
+ if(got instanceof OpenLayers.Geometry.Point) {
+ // compare points
+ assertFloatEqual(got.x, expected.x, "x mismatch");
+ assertFloatEqual(got.y, expected.y, "y mismatch");
+ assertFloatEqual(got.z, expected.z, "z mismatch");
+ } else {
+ // compare components
+ assertEqual(
+ got.components.length, expected.components.length,
+ "Component length mismatch for " + got.CLASS_NAME
+ );
+ for(var i=0; i<got.components.length; ++i) {
+ try {
+ assertGeometryEqual(
+ got.components[i], expected.components[i], options
+ );
+ } catch(err) {
+ throw "Bad component " + i + " for " + got.CLASS_NAME + ": " + err;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Function: Test.AnotherWay._test_object_t.geom_eq
+ * Test if two geometry objects are equivalent. Tests for same geometry
+ * class, same number of components (if any), equivalent component
+ * geometries, and same coordinates.
+ *
+ * (code)
+ * t.geom_eq(got, expected, message);
+ * (end)
+ *
+ * Parameters:
+ * got - {OpenLayers.Geometry} Any geometry instance.
+ * expected - {OpenLayers.Geometry} The expected geometry.
+ * msg - {String} A message to print with test output.
+ * options - {Object} Optional object for configuring test options.
+ */
+ var proto = Test.AnotherWay._test_object_t.prototype;
+ proto.geom_eq = function(got, expected, msg, options) {
+ // test geometries for equivalence
+ try {
+ assertGeometryEqual(got, expected, options);
+ this.ok(true, msg);
+ } catch(err) {
+ this.fail(msg + ": " + err);
+ }
+ }
+
+})();
diff --git a/misc/openlayers/tests/Test.AnotherWay.js b/misc/openlayers/tests/Test.AnotherWay.js
new file mode 100644
index 0000000..8500f13
--- /dev/null
+++ b/misc/openlayers/tests/Test.AnotherWay.js
@@ -0,0 +1,2498 @@
+/**
+ * Test.AnotherWay version 0.5
+ *
+ * Copyright (c) 2005 Artem Khodush, http://straytree.org
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+if (typeof(Test) == "undefined") {
+ Test = {};
+}
+Test.AnotherWay = {};
+
+Test.AnotherWay._g_test_iframe = null; // frame where to load test pages
+Test.AnotherWay._g_test_frame_no_clear = false; // true - leave last page displayed after tests end
+Test.AnotherWay._g_test_page_urls = []; // array of: { url: url, convention: "anotherway" or "jsan" }
+Test.AnotherWay._g_test_object_for_jsan = null; // test object for filling by tests that adhere to jsan Test.Simple calling convention
+Test.AnotherWay._g_pages_to_run = null; // list of pages to run automatically after loading
+Test.AnotherWay._g_run_on_main_load = false; // special handling for run_pages_to_run when it might be called before onload or before list of test pages is known.
+Test.AnotherWay._g_run_on_list_load = false;
+Test.AnotherWay._g_main_loaded = false;
+
+Test.AnotherWay._run_pages_to_run = function(called_from_outside){
+ if (!Test.AnotherWay._g_main_loaded) {
+ Test.AnotherWay._g_run_on_main_load = true;
+ }
+ else {
+ var a_pages = Test.AnotherWay._g_pages_to_run;
+ if (a_pages == "all") {
+ for (var i = 0; i < Test.AnotherWay._g_test_page_urls.length; ++i) {
+ Test.AnotherWay._run_test_page("test" + i);
+ }
+ }
+ else
+ if (a_pages != null) {
+ for (var run_i = 0; run_i < a_pages.length; ++run_i) {
+ var run_page = a_pages[run_i];
+ var found = false;
+ for (var all_i = 0; all_i < Test.AnotherWay._g_test_page_urls.length; ++all_i) {
+ if (run_page == Test.AnotherWay._g_test_page_urls[all_i].url) {
+ Test.AnotherWay._run_test_page("test" + all_i, called_from_outside);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ Test.AnotherWay._show_error("page specified to run is not found in the page list: " + run_page);
+ break;
+ }
+ }
+ }
+ }
+};
+
+Test.AnotherWay._add_test_page_url = function(test_url, convention){
+ var table = document.getElementById("testtable");
+ var record_select = document.getElementById("record_select");
+ var index = Test.AnotherWay._g_test_page_urls.length;
+
+ // trim spaces.
+ if (test_url.match("^(\\s*)(.*\\S)(\\s*)$")) {
+ test_url = RegExp.$2;
+ }
+
+ Test.AnotherWay._g_test_page_urls[index] = {
+ url: test_url,
+ convention: convention
+ };
+ var row = table.insertRow(-1);
+
+ var cell;
+ var cell_child;
+ var link;
+
+ cell = row.insertCell(-1);
+ cell_child = document.createElement("input");
+ cell_child.type = "checkbox";
+ cell_child.id = "checkbox" + index;
+ cell_child.checked = 'checked';
+ cell_child.defaultChecked = 'checked';
+ cell.appendChild(cell_child);
+
+ cell = row.insertCell(-1);
+ cell.setAttribute("width", "75%");
+
+ cell.appendChild(document.createTextNode(test_url));
+
+ cell = row.insertCell(-1);
+ cell_child = document.createElement("input");
+ cell_child.type = "button";
+ cell_child.id = "test" + index;
+ cell_child.value = " run ";
+ cell_child.onclick = Test.AnotherWay._run_one_onclick;
+ cell.appendChild(cell_child);
+
+ cell = row.insertCell(-1);
+ cell.setAttribute("width", "8em");
+ cell_child = document.createElement("span");
+ cell.appendChild(cell_child);
+
+ var option = document.createElement("option");
+ option.appendChild(document.createTextNode(test_url));
+ record_select.appendChild(option);
+};
+Test.AnotherWay._show_error = function(msg){
+ var error_div = document.getElementById("error");
+ error_div.innerHTML = "";
+ error_div.appendChild(document.createTextNode(msg));
+ error_div.style.display = "block";
+};
+
+// read urls from the list in the html file inside the list_iframe
+// fill on-screen list with urls and "run" buttons, and fill the g_test_page_urls object.
+Test.AnotherWay._list_iframe_onload = function(){
+ if (window.frames.list_iframe != null && window.frames.list_iframe.location != "" && window.frames.list_iframe.location != "about:blank") {
+ var list_doc = window.frames.list_iframe.document;
+ var list = list_doc.getElementById("testlist");
+ if (list != null) {
+ for (var i = 0; i < list.childNodes.length; ++i) {
+ var item = list.childNodes[i];
+ if (item.nodeName == "LI" || item.nodeName == "li") {
+ var convention = "anotherway";
+ if (Test.AnotherWay._get_css_class(item) == "jsan") {
+ convention = "jsan";
+ }
+ Test.AnotherWay._add_test_page_url(item.innerHTML, convention);
+ }
+ }
+ if (Test.AnotherWay._g_run_on_list_load) {
+ Test.AnotherWay._g_run_on_list_load = false;
+ Test.AnotherWay._run_pages_to_run();
+ }
+ }
+ else {
+ Test.AnotherWay._show_error("no list with id 'testlist' in a list file " + window.frames.list_iframe.location);
+ }
+ }
+};
+
+Test.AnotherWay._map_checkboxes = function(f){
+ var table = document.getElementById("testtable");
+ var checks = table.getElementsByTagName("INPUT");
+ for (var i = 0; i < checks.length; ++i) {
+ if (checks[i].type == "checkbox" && checks[i].id.match(/^checkbox(\d+)$/)) {
+ f(checks[i], RegExp.$1);
+ }
+ }
+};
+
+Test.AnotherWay._run_all_onclick = function(){
+ Test.AnotherWay._map_checkboxes(function(c, id){
+ Test.AnotherWay._run_test_page("test" + id);
+ });
+};
+Test.AnotherWay._run_selected_onclick = function(){
+ Test.AnotherWay._map_checkboxes(function(c, id){
+ if (c.checked) {
+ Test.AnotherWay._run_test_page("test" + id);
+ }
+ });
+};
+
+Test.AnotherWay._unselect_all_onclick = function(){
+ Test.AnotherWay._map_checkboxes(function(c, id){
+ c.checked = false;
+ });
+};
+
+Test.AnotherWay._run_one_onclick = function(){
+ Test.AnotherWay._run_test_page(this.id);
+};
+
+// construct an object that will gather results of running one test function
+Test.AnotherWay._test_object_t = function(fun_name){
+ this.name = fun_name; // name of the test function
+ this.n_plan = null; // planned number of assertions
+ this.n_ok = 0; // # of ok assertions
+ this.n_fail = 0; // # of failed assertions
+ this.exception = ""; // if the function throwed an exception, it's its message
+ this.exception_stack = []; // strings: function call stack from the exception
+ this.assertions = []; // assertion results: array of { ok: 1 or 0, name: string }
+ this.wait_result_milliseconds = 0; // how long to wait before collecting results from the test
+ this.second_wait_msg = null; // <p> status message (in addition to the page wait_msg)
+ this.delay_actions = []; // array of actions to be perfomed after the test function returns
+ // action : { acton_kind: "call" | "window" | "replay"
+ // when "call": { call_fn call_delay_milliseconds } call_fn takes nothing
+ // when "window" : { wnd_url wnd_wnd wnd_fn wnd_timeout_milliseconds wnd_dont_close } wnd_fn takes wnd
+ // wnen "replay" : { replay_wnd replay_events replay_event_i replay_checkpoints } checkpoint_fn takes this, wnd
+ // }
+ this.delay_action_i = null; // index of delay action currently being performed
+ this.delay_prev_timer_time = 0; // for counting time while performing delay_actions
+ this.delay_current_milliseconds_left = 0; // time left before the next action, runs down
+ this.delay_total_milliseconds_left = 0; // for indication: total estimated time for all actions, runs up and down
+};
+
+Test.AnotherWay._test_object_t.prototype.ok = function(cond, name){
+ if (cond) {
+ ++this.n_ok;
+ cond = 1;
+ }
+ else {
+ ++this.n_fail;
+ cond = 0;
+ }
+ this.assertions.push({
+ ok: cond,
+ name: name
+ });
+};
+Test.AnotherWay._test_object_t.prototype.fail = function(name){
+ this.ok(false, name);
+};
+Test.AnotherWay._test_object_t.prototype.plan = function(n){
+ this.n_plan = n;
+};
+Test.AnotherWay._test_object_t.prototype.wait_result = function(seconds){
+ this.wait_result_milliseconds = 1000 * seconds;
+};
+Test.AnotherWay._eq_fail_msg = function(path, what, expected, got){
+ return "eq: " + path + " " + what + " differ: got " + got + ", but expected " + expected;
+};
+Test.AnotherWay._array_eq = function(expected, got, path, msg){
+ if (expected.length != got.length) {
+ msg.msg = Test.AnotherWay._eq_fail_msg(path, "array length", expected.length, got.length);
+ return false;
+ }
+ for (var i = 0; i < expected.length; ++i) {
+ if (!Test.AnotherWay._thing_eq(expected[i], got[i], path + "[" + i + "]", msg)) {
+ return false;
+ }
+ }
+ return true;
+};
+Test.AnotherWay._object_eq = function(expected, got, path, msg){
+ var v;
+ for (v in expected) {
+ if (!(v in got)) {
+ msg.msg = Test.AnotherWay._eq_fail_msg(path + "." + v, "properties", expected[v], "undefined");
+ return false;
+ }
+ if (!Test.AnotherWay._thing_eq(expected[v], got[v], path + "." + v, msg)) {
+ return false;
+ }
+ }
+ for (v in got) {
+ if (!(v in expected)) {
+ msg.msg = Test.AnotherWay._eq_fail_msg(path + "." + v, "properties", "undefined", got[v]);
+ return false;
+ }
+ }
+ return true;
+};
+
+Test.AnotherWay._constructor_name = function(x){
+ if (x == null) {
+ return "";
+ }
+ var s = "unknown";
+ try {
+ s = typeof(x.constructor);
+ if (s != "unknown") {
+ s = x.constructor.toString();
+ }
+ }
+ catch (e) {
+ s = "unknown";
+ }
+ if (s == "unknown") {
+ // hackish attempt to guess a type
+ var is_array = true;
+ var index = 0;
+ for (i in x) {
+ if (i != index) {
+ is_array = false;
+ }
+ ++index;
+ }
+ return is_array ? "Array" : "Object"; // for empty arrays/objects, this will be wrong half the time
+ }
+ else
+ if (s.match(/^\s*function\s+(\w+)\s*\(/)) {
+ return RegExp.$1;
+ }
+ else {
+ var c = '';
+ switch (typeof x) {
+ case 'string':
+ c = 'String';
+ break;
+ case 'object':
+ c = 'Object';
+ break;
+ default:
+ c = '';
+ }
+ return c;
+ }
+};
+Test.AnotherWay._is_array = function(x){
+ return Test.AnotherWay._constructor_name(x) == "Array";
+};
+
+Test.AnotherWay._is_value_type = function(x){
+ cn = Test.AnotherWay._constructor_name(x);
+ return cn == "Number" || cn == "String" || cn == "Boolean" || cn == "Date";
+};
+
+Test.AnotherWay._thing_eq = function(expected, got, path, msg){
+ if (expected == null && got == null) {
+ return true;
+ }
+ else
+ if ((expected == null && got != null) || (expected != null && got == null)) {
+ msg.msg = Test.AnotherWay._eq_fail_msg(path, "values", expected, got);
+ return false;
+ }
+ else {
+ var expected_cn = Test.AnotherWay._constructor_name(expected);
+ var got_cn = Test.AnotherWay._constructor_name(got);
+ if (expected_cn != got_cn) {
+ msg.msg = Test.AnotherWay._eq_fail_msg(path, "types", expected_cn, got_cn);
+ return false;
+ }
+ else {
+ if (Test.AnotherWay._is_array(expected)) {
+ return Test.AnotherWay._array_eq(expected, got, path, msg);
+ }
+ else
+ if (Test.AnotherWay._is_value_type(expected)) {
+ if (expected != got) {
+ msg.msg = Test.AnotherWay._eq_fail_msg(path, "values", expected, got);
+ return false;
+ }
+ else {
+ return true;
+ }
+ }
+ else { // just a plain object
+ return Test.AnotherWay._object_eq(expected, got, path, msg);
+ }
+ }
+ }
+};
+
+Test.AnotherWay._test_object_t.prototype.eq = function(got, expected, name){
+ var msg = {};
+ if (Test.AnotherWay._thing_eq(expected, got, "", msg)) {
+ this.ok(1, name);
+ }
+ else {
+ this.fail(name + ". " + msg.msg);
+ }
+};
+
+Test.AnotherWay._test_object_t.prototype.like = function(got, expected, name){
+ if (got.match(expected) != null) {
+ this.ok(1, name);
+ }
+ else {
+ this.fail(name + ": got " + got + ", but expected it to match: " + expected);
+ }
+};
+
+Test.AnotherWay._g_html_eq_span = null;
+Test.AnotherWay._html_eq_string_to_node = function(string_or_node, what, msg){
+ if (string_or_node.nodeType != null) {
+ string_or_node = Test.AnotherWay._html_eq_node_to_string(string_or_node); // double trip - to make properties assigned in scripts available as html node attributes
+ }
+ if (Test.AnotherWay._g_html_eq_span == null) {
+ Test.AnotherWay._g_html_eq_span = document.createElement("span");
+ }
+ Test.AnotherWay._g_html_eq_span.innerHTML = string_or_node;
+ if (Test.AnotherWay._g_html_eq_span.childNodes.length != 1) {
+ msg.msg = "bad " + what + " html string given (should contain exactly one outermost element): " + string_or_node;
+ }
+ return Test.AnotherWay._g_html_eq_span.childNodes[0].cloneNode(true);
+};
+
+Test.AnotherWay._html_eq_node_to_string = function(node){
+ if (Test.AnotherWay._g_html_eq_span == null) {
+ Test.AnotherWay._g_html_eq_span = document.createElement("span");
+ }
+ Test.AnotherWay._g_html_eq_span.innerHTML = "";
+ if (node.outerHTML != null) {
+ Test.AnotherWay._g_html_eq_span.innerHTML = node.outerHTML;
+ }
+ else {
+ var clone = node.cloneNode(true);
+ var node = Test.AnotherWay._g_html_eq_span;
+ if (node.ownerDocument && node.ownerDocument.importNode) {
+ if (node.ownerDocument != clone.ownerDocument) {
+ clone = node.ownerDocument.importNode(clone, true);
+ }
+ }
+ node.appendChild(clone);
+ }
+ return Test.AnotherWay._g_html_eq_span.innerHTML;
+};
+
+Test.AnotherWay._html_eq_path_msg = function(path){
+ var msg = "";
+ for (var i = 0; i < path.length; ++i) {
+ msg += " [node " + path[i].node;
+ if (path[i].id != null && path[i].id != "") {
+ msg += " id " + path[i].id;
+ }
+ else
+ if (path[i].index != null) {
+ msg += " at index " + path[i].index;
+ }
+ msg += "] ";
+ }
+ return msg;
+};
+
+Test.AnotherWay._html_eq_fail_msg = function(path, what, expected, got){
+ return Test.AnotherWay._html_eq_path_msg(path) + ": " + what + " differ: got " + got + ", but expected " + expected;
+};
+
+Test.AnotherWay._html_eq_remove_blank = function(text){
+ if (text == null) {
+ return "";
+ }
+ else
+ if (text.match("^(\\s*)(.*\\S)(\\s*)$")) {
+ return RegExp.$2;
+ }
+ else
+ if (text.match("\s*")) {
+ return "";
+ }
+ return text;
+};
+
+Test.AnotherWay._html_eq_remove_blank_nodes = function(node){
+ var to_remove = [];
+ for (var child = node.firstChild; child != null; child = child.nextSibling) {
+ if (child.nodeType == 3) {
+ var value = Test.AnotherWay._html_eq_remove_blank(child.nodeValue);
+ if (value == "") {
+ to_remove.push(child);
+ }
+ else {
+ child.nodeValue = value;
+ }
+ }
+ }
+ for (var i = 0; i < to_remove.length; ++i) {
+ node.removeChild(to_remove[i]);
+ }
+};
+
+Test.AnotherWay._html_node_type_text = function(node_type){
+ if (node_type == 1) {
+ return "1 (html element)";
+ }
+ else
+ if (node_type == 3) {
+ return "3 (text)";
+ }
+ else {
+ return node_type;
+ }
+};
+
+Test.AnotherWay._html_eq_node = function(expected, got, path, msg, expected_loc_base, got_loc_base){
+ if (expected.nodeType != got.nodeType) {
+ msg.msg = Test.AnotherWay._html_eq_fail_msg(path, "node types", Test.AnotherWay._html_node_type_text(expected.nodeType), Test.AnotherWay._html_node_type_text(got.nodeType));
+ return false;
+ }
+ else
+ if (expected.nodeType == 3) {
+ if (expected.nodeValue != got.nodeValue) {
+ msg.msg = Test.AnotherWay._html_eq_fail_msg(path, "text", expected.nodeValue, got.nodeValue);
+ return false;
+ }
+ }
+ else
+ if (expected.nodeType == 1) {
+ if (expected.nodeName != got.nodeName) {
+ msg.msg = Test.AnotherWay._html_eq_fail_msg(path, "node names", expected.nodeName, got.nodeName);
+ return false;
+ }
+ // compare attributes
+ var expected_attrs = {};
+ var got_attrs = {};
+ var i;
+ var a;
+ for (i = 0; i < expected.attributes.length; ++i) {
+ a = expected.attributes[i];
+ if (a.specified) {
+ expected_attrs[a.name] = 1;
+ }
+ }
+ for (i = 0; i < got.attributes.length; ++i) {
+ a = got.attributes[i];
+ if (a.specified) {
+ got_attrs[a.name] = 1;
+ }
+ }
+ for (a in expected_attrs) {
+ if (!(a in got_attrs)) {
+ msg.msg = Test.AnotherWay._html_eq_path_msg(path) + ": attribute sets differ: expected attribute " + a + " is missing";
+ return false;
+ }
+ }
+ for (a in got_attrs) {
+ if (!(a in expected_attrs)) {
+ msg.msg = Test.AnotherWay._html_eq_path_msg(path) + ": attribute sets differ: got extra attribute " + a;
+ return false;
+ }
+ }
+ for (a in expected_attrs) {
+ var expected_value = expected.getAttribute(a);
+ var got_value = got.getAttribute(a);
+ if (typeof(expected_value) == "string" && typeof(got_value) == "string") {
+ expected_value = Test.AnotherWay._html_eq_remove_blank(expected_value);
+ got_value = Test.AnotherWay._html_eq_remove_blank(got_value);
+ var ok = expected_value == got_value;
+ if (!ok && (a == "href" || a == "HREF")) { // try relative hrefs
+ var expected_relative_value = expected_value;
+ if (expected_loc_base != null && expected_value.substring(0, expected_loc_base.length) == expected_loc_base) {
+ expected_relative_value = expected_value.substring(expected_loc_base.length);
+ }
+ var got_relative_value = got_value;
+ if (got_loc_base != null && got_value.substring(0, got_loc_base.length) == got_loc_base) {
+ got_relative_value = got_value.substring(got_loc_base.length);
+ }
+ ok = expected_relative_value == got_relative_value;
+ }
+ if (!ok) {
+ msg.msg = Test.AnotherWay._html_eq_fail_msg(path, "attribute " + a + " values", expected_value, got_value);
+ return false;
+ }
+ }
+ else
+ if (typeof(expected_value) == "function" && typeof(got_value) == "function") {
+ expected_value = expected_value.toString();
+ got_value = got_value.toString();
+ if (expected_value != got_value) {
+ msg.msg = Test.AnotherWay._html_eq_fail_msg(path, "attribute " + a + " values", expected_value, got_value);
+ return false;
+ }
+ }
+ else {
+ var value_msg = {};
+ if (!Test.AnotherWay._thing_eq(expected_value, got_value, "", value_msg)) {
+ msg.msg = Test.AnotherWay._html_eq_path_msg(path) + ": attribute " + a + " values differ: " + value_msg.msg;
+ return false;
+ }
+ }
+ }
+ // compare child nodes
+ Test.AnotherWay._html_eq_remove_blank_nodes(expected);
+ Test.AnotherWay._html_eq_remove_blank_nodes(got);
+ var expected_length = expected.childNodes.length;
+ var got_length = got.childNodes.length;
+ if (expected_length < got_length) {
+ msg.msg = Test.AnotherWay._html_eq_path_msg(path) + ": got " + (got_length - expected_length) + " extra child nodes";
+ return false;
+ }
+ else
+ if (expected_length > got_length) {
+ msg.msg = Test.AnotherWay._html_eq_path_msg(path) + ": expected " + (expected_length - got_length) + " more child nodes";
+ return false;
+ }
+ else {
+ for (i = 0; i < expected_length; ++i) {
+ var expected_node = expected.childNodes[i];
+ path.push({
+ node: expected_node.nodeName,
+ id: expected_node.id,
+ index: i
+ });
+ var eq = Test.AnotherWay._html_eq_node(expected_node, got.childNodes[i], path, msg, expected_loc_base, got_loc_base);
+ path.pop();
+ if (!eq) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+};
+
+Test.AnotherWay._html_eq_get_loc_base = function(node){
+ var loc_base = document.location;
+ if (node.ownerDocument != null) {
+ loc_base = node.ownerDocument.location;
+ }
+ if (loc_base != null) {
+ loc_base = loc_base.href;
+ var slash_pos = loc_base.lastIndexOf("/");
+ if (slash_pos != -1) {
+ loc_base = loc_base.substring(0, slash_pos + 1);
+ }
+ }
+ return loc_base;
+};
+
+Test.AnotherWay._test_object_t.prototype.html_eq = function(got, expected, name){
+ var msg = {};
+ var expected_node = Test.AnotherWay._html_eq_string_to_node(expected, "expected", msg);
+ if (msg.msg != null) {
+ this.fail(name + " html_eq: " + msg.msg);
+ }
+ else {
+ var got_node = Test.AnotherWay._html_eq_string_to_node(got, "got", msg);
+ if (msg.msg != null) {
+ this.fail(name + " html_eq: " + msg.msg);
+ }
+ else {
+ var expected_loc_base = Test.AnotherWay._html_eq_get_loc_base(expected);
+ var got_loc_base = Test.AnotherWay._html_eq_get_loc_base(got);
+ if (Test.AnotherWay._html_eq_node(expected_node, got_node, [], msg, expected_loc_base, got_loc_base)) {
+ this.ok(1, name);
+ }
+ else {
+ var msg = name + " html_eq " + msg.msg;
+ var expected_str = Test.AnotherWay._html_eq_node_to_string(expected_node);
+ var got_str = Test.AnotherWay._html_eq_node_to_string(got_node);
+ msg += ".\n got html: " + got_str;
+ msg += ".\n expected html: " + expected_str;
+ this.fail(msg);
+ }
+ }
+ }
+};
+
+Test.AnotherWay._debug_pane_print = function(msg){
+ var d = new Date();
+ var p = document.createElement("p");
+ p.appendChild(document.createTextNode(d.toLocaleTimeString() + " " + msg));
+ var debug_pane = document.getElementById("debug");
+ debug_pane.appendChild(p);
+ var debug_tab = document.getElementById("debug_tab");
+ var results_tab = document.getElementById("results_tab");
+ debug_tab.style.visibility = "visible";
+ results_tab.style.visibility = "visible";
+};
+
+Test.AnotherWay._test_object_t.prototype.debug_print = function(msg){
+ Test.AnotherWay._debug_pane_print(this.name + ": " + msg);
+};
+
+Test.AnotherWay._test_object_t.prototype.delay_call = function(){
+ var timeout_ms = 200;
+ for (var i = 0; i < arguments.length; ++i) {
+ if (typeof(arguments[i]) != "function") {
+ timeout_ms = 1000 * arguments[i];
+ }
+ else {
+ var action = {
+ action_kind: "call",
+ call_delay_milliseconds: timeout_ms,
+ call_fn: arguments[i]
+ };
+ this.delay_total_milliseconds_left += Test.AnotherWay._action_estimate_milliseconds(action);
+ this.delay_actions.push(action);
+ }
+ }
+};
+
+Test.AnotherWay._test_object_t.prototype.open_window = function(url, fn, timeout_seconds){
+ if (timeout_seconds == null) {
+ timeout_seconds = 4;
+ }
+ var no_close = document.getElementById("dont_close_test_windows");
+ var action = {
+ action_kind: "window",
+ wnd_url: url.toString() + (window.location.search || ""),
+ wnd_wnd: null,
+ wnd_fn: fn,
+ wnd_timeout_milliseconds: timeout_seconds * 1000,
+ wnd_no_close: no_close.checked
+ };
+ this.delay_total_milliseconds_left += Test.AnotherWay._action_estimate_milliseconds(action);
+ this.delay_actions.push(action);
+};
+
+Test.AnotherWay._test_object_t.prototype.replay_events = function(wnd, events){
+ if (Test.AnotherWay._g_no_record_msg != null) {
+ this.fail("replay_events: " + Test.AnotherWay._g_no_record_msg);
+ }
+ else {
+ var action = {
+ action_kind: "replay",
+ replay_wnd: wnd,
+ replay_events: events.events,
+ replay_event_i: null,
+ replay_checkpoints: events.checkpoints
+ };
+ this.delay_total_milliseconds_left += Test.AnotherWay._action_estimate_milliseconds(action);
+ this.delay_actions.push(action);
+ }
+};
+
+Test.AnotherWay._action_estimate_milliseconds = function(action){
+ var ms = 0;
+ if (action.action_kind == "call") {
+ ms = action.call_delay_milliseconds;
+ }
+ else
+ if (action.action_kind == "window") {
+ ms = 0;
+ }
+ else
+ if (action.action_kind == "replay") {
+ ms = 0;
+ for (var i = 0; i < action.replay_events.length; ++i) {
+ ms += action.replay_events[i]["time"] - 0;
+ }
+ }
+ return ms;
+};
+
+Test.AnotherWay._g_timeout_granularity = 200;
+Test.AnotherWay._g_tests_queue = []; // vector of { url: string, test_objects : array of test_object_t, test_object_i: int, wait_msg: <p> object, loading_timeout_milliseconds: int, timeout_id: id }
+// load one html page, schedule further processing
+Test.AnotherWay._run_test_page = function(id, called_from_outside){
+ if (id.match(/^test(\d+)/)) {
+ id = RegExp.$1;
+ Test.AnotherWay._g_tests_queue.push({
+ url: Test.AnotherWay._g_test_page_urls[id].url,
+ convention: Test.AnotherWay._g_test_page_urls[id].convention,
+ test_objects: []
+ });
+ if (Test.AnotherWay._g_tests_queue.length == 1) {
+ if (!called_from_outside) {
+ // Crap. Be careful stepping around.
+ // For Mozilla and Opera, when this file is included into the frameset page that is in another directory (and _g_outside_path_correction!=null)
+ // but the test pages are started from within it (by "run" buttons), then:
+ // depending on whether the page is the first one loaded into the test frame or not,
+ // the base url for relative test pages differs.
+ // Crap, like I said.
+ Test.AnotherWay._g_tests_queue[0].suppress_outside_path_correction = true;
+ }
+ Test.AnotherWay._start_loading_page();
+ }
+ }
+};
+
+Test.AnotherWay._load_next_page = function(){
+ Test.AnotherWay._g_tests_queue.splice(0, 1);
+ if (Test.AnotherWay._g_tests_queue.length > 0) {
+ Test.AnotherWay._start_loading_page();
+ }
+ else {
+ if (!Test.AnotherWay._g_test_frame_no_clear) {
+ Test.AnotherWay._g_test_iframe.location.replace("about:blank");
+ }
+ }
+};
+
+Test.AnotherWay._g_opera_path_correction = null; // ugly wart to support opera
+Test.AnotherWay._g_outside_path_correction = null; // ugly wart to accomodate Opera and Mozilla, where relative url relates to the directory where the page that calls this function is located
+Test.AnotherWay._set_iframe_location = function(iframe, loc, outside_path_correction){
+ // allow to load only locations with the same origin
+ var proto_end = loc.indexOf("://");
+ if (proto_end != -1) { // otherwise, it's safe to assume (for Opera, Mozilla and IE ) that loc will be treated as relative
+ var main_loc = window.location.href;
+ var host_end = loc.substring(proto_end + 3).indexOf("/");
+ var ok = false;
+ if (host_end != -1) {
+ var loc_origin = loc.substring(0, proto_end + 3 + host_end + 1);
+ if (main_loc.length >= loc_origin.length && main_loc.substring(0, loc_origin.length) == loc_origin) {
+ ok = true;
+ }
+ }
+ if (!ok) {
+ return {
+ msg: "test pages may have only urls with the same origin as " + main_loc
+ };
+ }
+ }
+ // opera cannot handle urls relative to file:// without assistance
+ if (window.opera != null && window.location.protocol == "file:" && loc.indexOf(":") == -1) {
+ var base = window.location.href;
+ var q_pos = base.indexOf("?");
+ if (q_pos != -1) {
+ base = base.substring(0, q_pos);
+ }
+ var slash_pos = base.lastIndexOf("/");
+ if (slash_pos != -1) {
+ base = base.substring(0, slash_pos + 1);
+ Test.AnotherWay._g_opera_path_correction = base;
+ loc = base + loc;
+ }
+ }
+ // if this function is called from another page, and if that page is in another directory, correction is needed
+ if (outside_path_correction != null) {
+ var pos = loc.indexOf(outside_path_correction);
+ if (pos == 0) {
+ loc = loc.substring(outside_path_correction.length + 1);
+ }
+ }
+ if (iframe.location != null) {
+ iframe.location.replace(loc);
+ }
+ else {
+ iframe.src = loc;
+ }
+ return {};
+};
+
+Test.AnotherWay._start_loading_page = function(){
+ var test_page = Test.AnotherWay._g_tests_queue[0];
+ test_page.loading_timeout_milliseconds = 12000;
+ test_page.timeout_id = setTimeout(Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity);
+ test_page.wait_msg = Test.AnotherWay._print_counter_result(test_page.url, "loading...", test_page.loading_timeout_milliseconds, "loading");
+ if (test_page.convention == "jsan") {
+ // the tests in that page will run when it's loading, so the test object must be ready
+ Test.AnotherWay._g_test_object_for_jsan = new Test.AnotherWay._test_object_t(test_page.url);
+ }
+ var outside_path_correction = null;
+ if (Test.AnotherWay._g_outside_path_correction != null && !test_page.suppress_outside_path_correction) {
+ outside_path_correction = Test.AnotherWay._g_outside_path_correction;
+ }
+ var result = Test.AnotherWay._set_iframe_location(Test.AnotherWay._g_test_iframe, test_page.url, outside_path_correction);
+ if (result.msg != null) {
+ Test.AnotherWay._unprint_result(test_page.wait_msg);
+ Test.AnotherWay._print_result(test_page.url, result.msg, "badtest", null);
+ Test.AnotherWay._load_next_page();
+ }
+};
+
+Test.AnotherWay._loading_timeout = function(){
+ var test_page = Test.AnotherWay._g_tests_queue[0];
+ test_page.loading_timeout_milliseconds -= Test.AnotherWay._g_timeout_granularity;
+ if (test_page.loading_timeout_milliseconds > 0) {
+ Test.AnotherWay._update_msg_counter(test_page.wait_msg, (test_page.loading_timeout_milliseconds / 1000).toFixed());
+ test_page.timeout_id = setTimeout(Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity);
+ }
+ else {
+ Test.AnotherWay._unprint_result(test_page.wait_msg);
+ Test.AnotherWay._print_result(test_page.url, "Unable to load test page. Timeout expired", "badtest", null);
+ Test.AnotherWay._load_next_page();
+ }
+};
+
+Test.AnotherWay._strip_query_and_hash = function(s){
+ var i = s.lastIndexOf("#");
+ if (i != -1) {
+ s = s.substring(0, i);
+ }
+ i = s.lastIndexOf("?");
+ if (i != -1) {
+ s = s.substring(0, i);
+ }
+ return s;
+};
+
+Test.AnotherWay._is_url_loaded = function(url, wnd){
+ var loaded = false;
+ if (wnd != null && wnd.location != null) {
+ // after some popup blocker interference, location may behave strange..
+ var location_s = "";
+ location_s += wnd.location;
+ if (location_s != "") {
+ var pathname = wnd.location.pathname;
+ var expected_url = url;
+ var i = expected_url.lastIndexOf("#");
+ if (i != -1) {
+ expected_url = expected_url.substring(0, i);
+ }
+ i = expected_url.lastIndexOf("?");
+ if (i != -1) {
+ expected_url = expected_url.substring(0, i);
+ }
+ i = expected_url.lastIndexOf("/");
+ if (i != -1 && i != expected_url.length - 1) {
+ expected_url = expected_url.substring(i + 1);
+ }
+ i = pathname.indexOf(expected_url);
+ if (wnd.location.href == url || (i != -1 && i == pathname.length - expected_url.length)) {
+ if ( /*window.opera==null*/wnd.document.readyState == null || wnd.document.readyState == "complete") { // for opera (and IE?), getElementById does not work until..
+ loaded = true;
+ }
+ }
+ }
+ }
+ return loaded;
+};
+// find and run all test functions in the g_cur_page html page.
+Test.AnotherWay._test_page_onload = function(){
+ if (Test.AnotherWay._g_tests_queue.length == 0) {
+ return;
+ }
+ var test_page = Test.AnotherWay._g_tests_queue[0];
+ if (!Test.AnotherWay._is_url_loaded(test_page.url, Test.AnotherWay._g_test_iframe)) {
+ return;
+ }
+ clearTimeout(test_page.timeout_id);
+ Test.AnotherWay._unprint_result(test_page.wait_msg);
+
+ if (test_page.convention == "anotherway") {
+ // get test function names (those beginning with "test")
+ if (typeof(Test.AnotherWay._g_test_iframe.document.scripts) != 'undefined') { // IE
+ for (var i = 0; i < Test.AnotherWay._g_test_iframe.document.scripts.length; ++i) {
+ var script_text = Test.AnotherWay._g_test_iframe.document.scripts[i].text;
+ var fun_sig = "function test";
+ var fun_start = script_text.indexOf(fun_sig);
+
+ while (fun_start != -1) {
+ script_text = script_text.substring(fun_start, script_text.length);
+ var fun_end = script_text.indexOf('(');
+ var fun_name = script_text.substring("function ".length, fun_end);
+ var whitespace = fun_name.indexOf(' ');
+ if (whitespace >= 0) {
+ fun_name = fun_name.substring(0, whitespace);
+ }
+ test_page.test_objects.push(new Test.AnotherWay._test_object_t(fun_name));
+ script_text = script_text.substring(fun_end, script_text.length);
+ fun_start = script_text.indexOf(fun_sig);
+ }
+ }
+ }
+ else { // otherwise (not IE) it ought to work like this
+ for (var i in Test.AnotherWay._g_test_iframe) {
+ // Hack to prevent failure in FF3.0b1 (innerWidth/innerHeight) and FF>=3.5 (sessionStorage)
+ if (i == "innerWidth" || i == "innerHeight" || i == "sessionStorage") {
+ continue;
+ }
+ if (typeof(Test.AnotherWay._g_test_iframe[i]) == 'function') {
+ if (i.substring(0, 4) == "test") {
+ test_page.test_objects.push(new Test.AnotherWay._test_object_t(i));
+ }
+ }
+ }
+ }
+ }
+ else
+ if (test_page.convention == "jsan") {
+ // the test object is already filled with results
+ test_page.test_objects.push(Test.AnotherWay._g_test_object_for_jsan);
+ }
+
+ if (test_page.test_objects.length == 0) {
+ Test.AnotherWay._print_result(test_page.url, "No test functions defined in the page", "badtest", null);
+ Test.AnotherWay._load_next_page();
+ return;
+ }
+
+ test_page.wait_msg = Test.AnotherWay._print_result(test_page.url, "running tests..<span class=\"counter\">" + test_page.test_objects.length + "</span>", "running", null);
+
+ test_page.test_object_i = 0;
+ Test.AnotherWay._run_more_tests();
+};
+
+Test.AnotherWay._handle_exception = function(o, e, title){
+ var s = title + ": " + typeof(e) + ": ";
+ if (e.message != null) {
+ s += e.message;
+ }
+ else
+ if (e.description != null) {
+ s += e.description;
+ }
+ else {
+ s += e.toString();
+ }
+ // if( e.location!=null ) { // XXX figure out how to display exception location if it's present (like in mozilla)
+ // s+=" location: "+e.location.toString();
+ // }
+ o.exception = s;
+ s = [];
+ if (e.stack) {
+ var lines = e.stack.split("\n");
+ for (var i = 0; i < lines.length; ++i) {
+ // format of the line: func_name(args)@file_name:line_no
+ if (lines[i].match(/(\w*)\(([^\)]*)\)@(.*):([^:]*)$/)) {
+ var func_name = RegExp.$1;
+ if (func_name.length == 0) {
+ func_name = "<anonymous>";
+ }
+ s.push("in " + func_name + "( " + RegExp.$2 + ") at " + RegExp.$3 + " line " + RegExp.$4 + "\n");
+ }
+ }
+ }
+ o.exception_stack = s;
+};
+
+Test.AnotherWay._run_more_tests = function(){
+ var test_page = Test.AnotherWay._g_tests_queue[0];
+ while (test_page.test_object_i < test_page.test_objects.length) {
+ Test.AnotherWay._update_msg_counter(test_page.wait_msg, (1 + test_page.test_object_i) + "/" + test_page.test_objects.length);
+ var o = test_page.test_objects[test_page.test_object_i];
+ if (test_page.convention == "anotherway") {
+ try {
+ Test.AnotherWay._g_test_iframe[o.name](o);
+ }
+ catch (e) {
+ Test.AnotherWay._handle_exception(o, e, "");
+ }
+ } // for "jsan" convention, test has run already
+ if (o.delay_actions.length > 0 || o.wait_result_milliseconds > 0) {
+ o.delay_total_milliseconds_left += o.wait_result_milliseconds;
+ Test.AnotherWay._delay_actions_timeout();
+ return;
+ }
+ ++test_page.test_object_i;
+ }
+ Test.AnotherWay._unprint_result(test_page.wait_msg);
+ Test.AnotherWay._print_result(test_page.url, null, null, test_page.test_objects);
+ Test.AnotherWay._load_next_page();
+};
+
+Test.AnotherWay._delay_actions_timeout = function(){
+ var test_page = Test.AnotherWay._g_tests_queue[0];
+ var test_object = test_page.test_objects[test_page.test_object_i];
+ var finished = true;
+ if (test_object.delay_action_i == null) {
+ // set up to start first action
+ test_object.delay_action_i = -1;
+ }
+ else {
+ // perform current action
+ var milliseconds_passed = (new Date()).getTime() - test_object.delay_prev_timer_time;
+ test_object.delay_current_milliseconds_left -= milliseconds_passed;
+ test_object.delay_total_milliseconds_left -= milliseconds_passed;
+ finished = Test.AnotherWay._delay_continue_action(test_object, milliseconds_passed);
+ }
+ while (finished && test_object.delay_action_i < test_object.delay_actions.length) {
+ ++test_object.delay_action_i; // start next action
+ finished = Test.AnotherWay._delay_start_action(test_object);
+ }
+ if (test_object.delay_action_i <= test_object.delay_actions.length) { // any more actions left ?
+ test_object.delay_prev_timer_time = (new Date()).getTime();
+ var next_timeout = Test.AnotherWay._g_timeout_granularity;
+ if (test_object.delay_current_milliseconds_left < next_timeout) {
+ next_timeout = test_object.delay_current_milliseconds_left;
+ }
+ if (test_object.second_wait_msg != null) {
+ Test.AnotherWay._update_msg_counter(test_object.second_wait_msg, (test_object.delay_total_milliseconds_left / 1000).toFixed());
+ }
+ setTimeout(Test.AnotherWay._delay_actions_timeout, next_timeout);
+ }
+ else { // no more actions left. run the next test.
+ if (test_object.second_wait_msg != null) {
+ Test.AnotherWay._unprint_result(test_object.second_wait_msg);
+ test_object.second_wait_msg = null;
+ }
+ ++test_page.test_object_i;
+ Test.AnotherWay._run_more_tests();
+ }
+};
+
+Test.AnotherWay._delay_start_action = function(test_object){
+ var finished = false;
+ var wait_msg = "";
+ if (test_object.delay_action_i == test_object.delay_actions.length) {
+ if (test_object.wait_result_milliseconds > 0) {
+ test_object.delay_current_milliseconds_left = test_object.wait_result_milliseconds; // wait for result
+ wait_msg = "waiting for results..";
+ }
+ else {
+ ++test_object.delay_action_i; // dont wait for result
+ }
+ }
+ else {
+ var action = test_object.delay_actions[test_object.delay_action_i];
+ if (action.action_kind == "call") {
+ test_object.delay_current_milliseconds_left = action.call_delay_milliseconds;
+ wait_msg = "performing delayed calls..";
+ }
+ else
+ if (action.action_kind == "window") {
+ if (Test.AnotherWay._g_opera_path_correction != null && action.wnd_url.indexOf(":") == -1) {
+ action.wnd_url = Test.AnotherWay._g_opera_path_correction + action.wnd_url;
+ }
+ action.wnd_wnd = window.open(action.wnd_url, "_blank");
+ if (action.wnd_wnd == null) {
+ finished = true;
+ test_object.fail("unable to open window for " + action.wnd_url);
+ }
+ else {
+ test_object.delay_current_milliseconds_left = action.wnd_timeout_milliseconds;
+ wait_msg = "opening window..";
+ }
+ }
+ else
+ if (action.action_kind == "replay") {
+ if (action.replay_events.length == 0) {
+ finished = true;
+ }
+ else {
+ action.replay_event_i = 0;
+ test_object.delay_current_milliseconds_left = action.replay_events[0]["time"];
+ wait_msg = "replaying events..";
+ }
+ }
+ }
+ if (test_object.second_wait_msg != null) {
+ Test.AnotherWay._unprint_result(test_object.second_wait_msg);
+ }
+ if (wait_msg != "") {
+ var test_page = Test.AnotherWay._g_tests_queue[0];
+ test_object.second_wait_msg = Test.AnotherWay._print_counter_result(test_page.url, wait_msg, test_object.delay_total_milliseconds_left, "waiting");
+ }
+ else {
+ test_object.second_wait_msg = null;
+ }
+ return finished;
+};
+Test.AnotherWay._delay_continue_action = function(test_object, milliseconds_passed){
+ var finished = test_object.delay_current_milliseconds_left <= 0;
+ if (test_object.delay_action_i == test_object.delay_actions.length) { // action is "waiting for results"
+ if (test_object.n_plan != null && test_object.n_plan == test_object.n_ok + test_object.n_fail) {
+ finished = true; // if all assertions results are recorded, don't wait any more
+ }
+ if (finished) {
+ ++test_object.delay_action_i; // move on to the next test
+ }
+ }
+ else {
+ var action = test_object.delay_actions[test_object.delay_action_i];
+ if (action.action_kind == "call") {
+ if (finished) {
+ try {
+ action.call_fn();
+ }
+ catch (e) {
+ Test.AnotherWay._handle_exception(test_object, e, "in delay_call");
+ }
+ }
+ }
+ else
+ if (action.action_kind == "window") {
+ test_object.delay_total_milliseconds_left += milliseconds_passed; // for "window", the countdown is suspended since it's unknown how long it will take
+ if (Test.AnotherWay._is_url_loaded(action.wnd_url, action.wnd_wnd)) {
+ try {
+ action.wnd_fn(action.wnd_wnd);
+ }
+ catch (e) {
+ Test.AnotherWay._handle_exception(test_object, e, "in open_window function call");
+ }
+ finished = true;
+ }
+ else
+ if (finished) {
+ test_object.fail("unable to open window for url '" + action.wnd_url + "'. timeout expired");
+ }
+ }
+ else
+ if (action.action_kind == "replay") {
+ if (finished) {
+ // try {
+ Test.AnotherWay._delay_replay_event(test_object, action.replay_wnd, action.replay_events[action.replay_event_i], action.replay_checkpoints);
+ // }catch( e ) { // disabled, until I know how to gel location info from an exception
+ // Test.AnotherWay._handle_exception( test_object, e, "while replaying event" );
+ // }
+ ++action.replay_event_i;
+ finished = action.replay_event_i == action.replay_events.length;
+ if (!finished) {
+ test_object.delay_current_milliseconds_left = action.replay_events[action.replay_event_i]["time"];
+ }
+ }
+ }
+ }
+ return finished;
+};
+
+Test.AnotherWay._delay_replay_event = function(test_object, wnd, event, checkpoints){
+ if (event.type == "_checkpoint") {
+ var checkpoint_n = event.which;
+ var prev_n_fail = test_object.n_fail;
+ checkpoints[checkpoint_n](test_object, wnd);
+ var flash_color = prev_n_fail == test_object.n_fail ? "#2f2" : "#f22";
+ Test.AnotherWay._record_flash_border(flash_color);
+ }
+ else
+ if (event.type == "click" || event.type == "mouseover" || event.type == "mouseout" || event.type == "mousemove" || event.type == "mousedown" || event.type == "mouseup") {
+ var target = Test.AnotherWay._record_node_path_to_node(event["target"], wnd.document);
+ if (target != null) {
+ Test.AnotherWay._record_control_update_highlight(target, "ball", event);
+ var e = wnd.document.createEvent("MouseEvents");
+ var related_target = Test.AnotherWay._record_node_path_to_node(event["relatedTarget"], wnd.document);
+ e.initMouseEvent(event["type"], event["cancelable"], event["bubbles"], wnd.document.defaultView, event["detail"], event["screenX"], event["screenY"], event["clientX"], event["clientY"], event["ctrlKey"], event["altKey"], event["shiftKey"], event["metaKey"], event["button"], Test.AnotherWay._record_node_path_to_node(event["relatedTarget"], wnd.document));
+ // Firefox 1.0.6 somehow loses relatedTarget somewhere on the way. Pass through our own, for those who choose to care.
+ e.passThroughRelatedTarget = related_target;
+ target.dispatchEvent(e);
+ }
+ }
+ else
+ if (event.type == "keyup" || event.type == "keydown" || event.type == "keypress") {
+ var e = wnd.document.createEvent("KeyboardEvents"); // forget it. Apparently it's not supported neither by mozilla nor by opera.
+ e.initKeyboardEvent(event["type"], event["cancelable"], event["bubbles"], wnd.document.defaultView, event["which"], event["which"], event["ctrlKey"], event["altKey"], event["shiftKey"], event["metaKey"], false);
+ wnd.document.dispatchEvent(e);
+ }
+};
+
+Test.AnotherWay._print_counter_result = function(url, msg, milliseconds, style){
+ return Test.AnotherWay._print_result(url, msg + "<span class=\"counter\">" + (milliseconds / 1000).toFixed() + "</span>", style, null);
+};
+
+Test.AnotherWay._g_result_count = 0; // for assigning unique ids to result paragraphs
+// number of pages tested
+Test.AnotherWay._g_ok_pages = 0;
+Test.AnotherWay._g_fail_pages = 0;
+
+Test.AnotherWay._print_result = function(url, msg, style, test_objects){
+ var results = document.getElementById("results");
+ var r = results.appendChild(document.createElement("p"));
+ r.id = "result" + Test.AnotherWay._g_result_count;
+ ++Test.AnotherWay._g_result_count;
+ r.onclick = Test.AnotherWay._toggle_detail;
+ var text = "<span class=\"bullet\">&nbsp;&nbsp;&nbsp;</span>&nbsp;";
+ if (url != "") {
+ text += url + ": ";
+ }
+ if (msg != null) {
+ text += msg;
+ }
+ if (test_objects != null) {
+ // compose summary and detail texts
+ var total_ok = 0;
+ var total_detail_ok = 0;
+ var total_fail = 0;
+ var total_detail_fail = 0;
+ var no_plan = 0;
+
+ var detail = results.appendChild(document.createElement("div"));
+
+ if (r.id.match(/^result(\d+)$/)) {
+ detail.id = "result_detail" + RegExp.$1;
+ }
+
+ for (var i = 0; i < test_objects.length; ++i) {
+ var o = test_objects[i];
+ var p;
+ var p_text;
+ p = document.createElement("P");
+ Test.AnotherWay._set_css_class(p, "result_detail");
+ p_text = o.name;
+ if (o.n_fail > 0 || o.exception || (o.n_plan != null && o.n_plan != o.n_ok + o.n_fail) || (o.n_plan == null && o.n_ok == 0 && o.n_fail == 0)) {
+ ++total_fail;
+ p_text += " <span class=\"fail\">";
+ if (o.n_plan != null && o.n_plan != o.n_ok + o.n_fail) {
+ p_text += "planned " + o.n_plan + " assertions but got " + (o.n_ok + o.n_fail) + "; ";
+ }
+ if (o.n_plan == null && o.n_ok == 0 && o.n_fail == 0) {
+ p_text += "test did not output anything";
+ }
+ else {
+ p_text += " fail " + o.n_fail;
+ }
+ p_text += "</span>";
+ }
+ else {
+ ++total_ok;
+ }
+ p_text += " ok " + o.n_ok;
+ if (o.n_plan == null) {
+ no_plan = 1;
+ p_text += " <span class=\"warning\">no plan</span>";
+ }
+ p.innerHTML = p_text;
+ detail.appendChild(p);
+ if (o.exception) {
+ p = document.createElement("P");
+ Test.AnotherWay._set_css_class(p, "result_exception_detail");
+ p.innerHTML = "<span class=\"fail\">exception:</span> " + o.exception;
+ detail.appendChild(p);
+ p = document.createElement("P");
+ Test.AnotherWay._set_css_class(p, "result_exception_stack_detail");
+ p.innerHTML = o.exception_stack.join("<br/>");
+ detail.appendChild(p);
+ }
+ for (var ii = 0; ii < o.assertions.length; ++ii) {
+ var oo = o.assertions[ii];
+ var status = oo.ok ? "ok" : "<span class=\"fail\">fail</span>";
+ p = document.createElement("P");
+ Test.AnotherWay._set_css_class(p, "result_micro_detail");
+ p.innerHTML = status;
+ p.appendChild(document.createTextNode(" " + oo.name));
+ detail.appendChild(p);
+ }
+ total_detail_ok += o.n_ok;
+ total_detail_fail += o.n_fail;
+ }
+ if (total_fail || total_detail_fail) {
+ text += " fail " + total_fail;
+ }
+ text += " ok " + total_ok + " (detailed:";
+ if (total_fail || total_detail_fail) {
+ text += " fail " + total_detail_fail;
+ }
+ text += " ok " + total_detail_ok + ")";
+ if (no_plan) {
+ text += " <span class=\"warning\">no plan</span>";
+ }
+ style = total_fail == 0 ? "ok" : "fail";
+ detail.style.display = style == "fail" ? "block" : "none";
+ detail.style.cursor = "text";
+ }
+ if (style != null) {
+ Test.AnotherWay._set_css_class(r, style);
+ if (style == "ok") {
+ ++Test.AnotherWay._g_ok_pages;
+ }
+ else
+ if (style == "fail" || style == "badtest") {
+ ++Test.AnotherWay._g_fail_pages;
+ }
+ var pages_total = "";
+ if (Test.AnotherWay._g_fail_pages > 0) {
+ pages_total += " fail " + Test.AnotherWay._g_fail_pages;
+ }
+ pages_total += " ok " + Test.AnotherWay._g_ok_pages;
+
+ // also count out the total number of tests in fail and ok
+ Test.AnotherWay._openlayers_sum_total_detail_ok = Test.AnotherWay._openlayers_sum_total_detail_ok || 0;
+ Test.AnotherWay._openlayers_sum_total_detail_ok += (total_detail_ok||0);
+
+ Test.AnotherWay._openlayers_sum_total_detail_fail = Test.AnotherWay._openlayers_sum_total_detail_fail || 0;
+ Test.AnotherWay._openlayers_sum_total_detail_fail += (total_detail_fail||0);
+
+ pages_total+=" (detailed: fail " + Test.AnotherWay._openlayers_sum_total_detail_fail + " | ok " + Test.AnotherWay._openlayers_sum_total_detail_ok + ")";
+
+ Test.AnotherWay._update_results_total(pages_total);
+ }
+ r.innerHTML = text;
+ if (results.scrollHeight != null && results.scrollTop != null && results.offsetHeight != null) {
+ results.scrollTop = results.scrollHeight - results.offsetHeight;
+ }
+ // when test_objects is not null, the results are final - good time to clean up
+ if (test_objects != null) {
+ for (var i = 0; i < test_objects.length; ++i) {
+ var actions = test_objects[i].delay_actions;
+ for (var action_i = 0; action_i < actions.length; ++action_i) {
+ var action = actions[action_i];
+ if (action.action_kind == "window" && action.wnd_wnd != null && !action.wnd_no_close) {
+ action.wnd_wnd.close();
+ action.wnd_wnd = null;
+ }
+ }
+ }
+ }
+ return r;
+};
+
+Test.AnotherWay._unprint_result = function(child){
+ var results = document.getElementById("results");
+ results.removeChild(child);
+};
+
+Test.AnotherWay._toggle_detail = function(){
+ if (this.id.match(/^result(\d+)$/)) {
+ var detail = document.getElementById("result_detail" + RegExp.$1);
+ if (detail != null) {
+ if (detail.style.display == "none") {
+ detail.style.display = "block";
+ }
+ else
+ if (detail.style.display == "block") {
+ detail.style.display = "none";
+ }
+ }
+ }
+};
+
+Test.AnotherWay._update_msg_counter = function(msg, text){
+ for (var i = 0; i < msg.childNodes.length; ++i) {
+ var item = msg.childNodes[i];
+ if (item.nodeName == "SPAN" && Test.AnotherWay._get_css_class(item) == "counter") {
+ item.innerHTML = text;
+ }
+ }
+};
+
+Test.AnotherWay._update_results_total = function(msg){
+ var total = document.getElementById("total");
+ if (total) {
+ total.innerHTML = msg;
+ }
+};
+
+Test.AnotherWay._results_clear_onclick = function(){
+ var results = document.getElementById("results");
+ results.innerHTML = "";
+ Test.AnotherWay._update_results_total("");
+ Test.AnotherWay._g_ok_pages = 0;
+ Test.AnotherWay._g_fail_pages = 0;
+ Test.AnotherWay._openlayers_sum_total_detail_ok=0;
+ Test.AnotherWay._openlayers_sum_total_detail_fail=0;
+ var debug = document.getElementById("debug");
+ debug.innerHTML = "";
+ Test.AnotherWay.reset_running_time();
+};
+
+Test.AnotherWay._get_css_class = function(o){
+ var c = o.getAttribute("className");
+ if (c == null || c == "") {
+ c = o.getAttribute("class");
+ }
+ return c;
+};
+
+Test.AnotherWay._set_css_class = function(o, css_class){
+ o.setAttribute("className", css_class);
+ o.setAttribute("class", css_class);
+};
+
+Test.AnotherWay._tab_onclick = function(){
+ var tab = this;
+ var tabs = [document.getElementById("debug_tab"), document.getElementById("results_tab")];
+ var panes = [document.getElementById("debug"), document.getElementById("results")];
+ for (var i = 0; i < tabs.length; ++i) {
+ if (tab == tabs[i]) {
+ Test.AnotherWay._set_css_class(tabs[i], "active_tab");
+ panes[i].style.display = "block";
+ }
+ else {
+ Test.AnotherWay._set_css_class(tabs[i], "inactive_tab");
+ panes[i].style.display = "none";
+ }
+ }
+};
+Test.AnotherWay._tab_mouseover = function(){
+ if (Test.AnotherWay._get_css_class(this) == "inactive_tab") {
+ Test.AnotherWay._set_css_class(this, "inactive_mouseover_tab");
+ }
+};
+Test.AnotherWay._tab_mouseout = function(){
+ if (Test.AnotherWay._get_css_class(this) == "inactive_mouseover_tab") {
+ Test.AnotherWay._set_css_class(this, "inactive_tab");
+ }
+};
+
+// recording mouse input
+Test.AnotherWay._record_check_onfocus = function(){
+ var o = this;
+ var check_select = o.type != "text";
+ var div = document.getElementById("record_div");
+ var inputs = div.getElementsByTagName("input");
+ for (var i = 0; i < inputs.length; ++i) {
+ var input = inputs[i];
+ if (input.type == "radio") {
+ if (input.value == "select") {
+ input.checked = check_select;
+ }
+ else
+ if (input.value == "input") {
+ input.checked = !check_select;
+ }
+ }
+ }
+};
+
+Test.AnotherWay._g_no_record_msg = null; // not null - recording is unavailable
+Test.AnotherWay._g_record_timeout_cnt = 0; // opening window for a page for recording
+Test.AnotherWay._g_record_url = null;
+Test.AnotherWay._g_record_wnd = null;
+Test.AnotherWay._g_record_random_id = null; // added to element ids of record_control div so that they do not clash with ids already in the page for which input is recorded
+Test.AnotherWay._g_record_keydown = null; // recording control - which key is down
+Test.AnotherWay._g_record_ctrl_keydown = false;
+Test.AnotherWay._g_record_shift_keydown = false;
+Test.AnotherWay._g_record_control_visible = true; // recording control ui state
+Test.AnotherWay._g_record_started;
+Test.AnotherWay._g_record_paused;
+Test.AnotherWay._g_record_include_mousemove = false;
+Test.AnotherWay._g_record_start_time; // for time references
+Test.AnotherWay._g_record_pause_start_time;
+Test.AnotherWay._g_record_update_time_interval; // showing time in the control ui
+Test.AnotherWay._g_record_waiting_for_results = false; // waiting for results window to open
+Test.AnotherWay._g_record_events; // recorded events
+Test.AnotherWay._g_record_under_cursor; // track element under cursor
+Test.AnotherWay._g_record_checkpoint_count; // for checkpoint numbering
+Test.AnotherWay._g_record_mouse_over_record_control; // for avoiding record control highlight on mouseover
+Test.AnotherWay._g_record_highlighted_element = {
+ element: null,
+ x: null,
+ y: null
+};
+
+Test.AnotherWay._record_control_get_element = function(id){
+ if (Test.AnotherWay._g_record_wnd != null && Test.AnotherWay._g_record_wnd.document != null) {
+ return Test.AnotherWay._g_record_wnd.document.getElementById(id + Test.AnotherWay._g_record_random_id);
+ }
+ else {
+ return null;
+ }
+};
+Test.AnotherWay._record_start_onclick = function() // "record" button on the run_tests.html: open a window for a page for which input is recorded
+{
+ if (Test.AnotherWay._g_no_record_msg != null) {
+ alert(Test.AnotherWay._g_no_record_msg);
+ return;
+ }
+ if (Test.AnotherWay._g_record_timeout_cnt > 0 ||
+ (Test.AnotherWay._g_record_wnd != null && (Test.AnotherWay._g_record_wnd.closed != null && !Test.AnotherWay._g_record_wnd.closed))) { // in opera, closed is null.
+ alert("there is already window opened for recording input for a page " + Test.AnotherWay._g_record_url);
+ return;
+ }
+ var div = document.getElementById("record_div");
+ var inputs = div.getElementsByTagName("input");
+ var url = null;
+ for (var i = 0; i < inputs.length; ++i) {
+ var input = inputs[i];
+ if (input.type == "radio") {
+ if (input.value == "select" && input.checked) {
+ var index = document.getElementById("record_select").selectedIndex;
+ if (index > 0) {
+ url = Test.AnotherWay._g_test_page_urls[index - 1].url;
+ }
+ }
+ else
+ if (input.value == "input" && input.checked) {
+ url = document.getElementById("record_input").value;
+ }
+ }
+ }
+ if (url != null) {
+ Test.AnotherWay._g_record_url = url;
+ Test.AnotherWay._g_record_wnd = window.open(url, "_blank");
+ if (Test.AnotherWay._g_record_wnd == null) {
+ alert("unable to open new window for a page: " + url);
+ }
+ else {
+ Test.AnotherWay._g_record_timeout_cnt = 50;
+ setTimeout(Test.AnotherWay._record_window_timeout, 100);
+ }
+ }
+};
+Test.AnotherWay._record_window_timeout = function(){
+ if (Test.AnotherWay._is_url_loaded(Test.AnotherWay._g_record_url, Test.AnotherWay._g_record_wnd)) {
+ Test.AnotherWay._record_window_setup(Test.AnotherWay._g_record_wnd);
+ }
+ else {
+ if (--Test.AnotherWay._g_record_timeout_cnt > 0) {
+ setTimeout(Test.AnotherWay._record_window_timeout, 100);
+ }
+ else {
+ alert("timeout expired while opening new window for a page: " + Test.AnotherWay._g_record_url);
+ Test.AnotherWay._g_record_wnd = null;
+ Test.AnotherWay._g_record_url = null;
+ Test.AnotherWay._g_record_timeout_cnt = 0;
+ }
+ }
+};
+Test.AnotherWay._record_control_randomize_id = function(e, r){
+ if (e.id != "") {
+ e.id = e.id + r;
+ }
+ for (var c = e.firstChild; c != null; c = c.nextSibling) {
+ Test.AnotherWay._record_control_randomize_id(c, r);
+ }
+};
+Test.AnotherWay._record_window_setup = function(wnd) // insert recording control into the page for which input is recorded
+{
+ Test.AnotherWay._g_record_timeout_cnt = 0;
+ var this_div = document.getElementById("record_control");
+ var record_control = wnd.document.importNode(this_div, true);
+ Test.AnotherWay._g_record_random_id = (1000 * Math.random()).toFixed();
+ Test.AnotherWay._record_control_randomize_id(record_control, Test.AnotherWay._g_record_random_id);
+ Test.AnotherWay._g_record_control_visible = true;
+ Test.AnotherWay._g_record_started = false;
+ Test.AnotherWay._g_record_paused = false;
+ Test.AnotherWay._g_record_checkpoint_count = 0;
+ Test.AnotherWay._g_record_mouse_over_record_control = false;
+ var doc = wnd.document;
+ doc.body.appendChild(record_control);
+ // opera sans-serif font is different
+ if (window.opera) {
+ cursor_over_indicator = Test.AnotherWay._record_control_get_element("record_cursor_over");
+ cursor_over_indicator.style.width = "18em";
+ cursor_over_indicator.style.height = "2em";
+ cursor_over_indicator.style.fontSize = "7pt";
+ }
+ doc.addEventListener("keydown", Test.AnotherWay._record_control_keydown, true);
+ doc.addEventListener("keyup", Test.AnotherWay._record_control_keyup, true);
+ // doc.addEventListener( "keypress", Test.AnotherWay._record_event, true ); // replaying is not supported by any known browser
+
+ doc.body.addEventListener("mousemove", Test.AnotherWay._record_on_mousemove, true);
+ doc.body.addEventListener("click", Test.AnotherWay._record_event, true);
+ doc.body.addEventListener("mouseover", Test.AnotherWay._record_event, true);
+ doc.body.addEventListener("mouseout", Test.AnotherWay._record_event, true);
+ doc.body.addEventListener("mousedown", Test.AnotherWay._record_event, true);
+ doc.body.addEventListener("mouseup", Test.AnotherWay._record_event, true);
+};
+Test.AnotherWay._record_control_key_disabled = function(k){
+ if (k == "c") {
+ return !Test.AnotherWay._g_record_started;
+ }
+ else
+ if (k == "p") {
+ return !Test.AnotherWay._g_record_started;
+ }
+ else
+ if (k == "s") {
+ return Test.AnotherWay._g_record_waiting_for_results;
+ }
+ else {
+ return false;
+ }
+};
+
+Test.AnotherWay._record_control_update_ui = function(){
+ var keydown_color = "#fff";
+ var disabled_color = "#aaa";
+ var button_color = "#adf";
+ var active_color = "#fdf";
+
+ var display = {};
+ display[false] = "none";
+ display[true] = "inline";
+
+ var s_button = Test.AnotherWay._record_control_get_element("record_s");
+ var record_on = Test.AnotherWay._record_control_get_element("record_on");
+ var record_off = Test.AnotherWay._record_control_get_element("record_off");
+
+ s_button.style.backgroundColor = Test.AnotherWay._record_control_key_disabled("s") ? disabled_color : Test.AnotherWay._g_record_keydown == "s" ? keydown_color : Test.AnotherWay._g_record_started ? active_color : button_color;
+ record_on.style.display = display[!Test.AnotherWay._g_record_started];
+ record_off.style.display = display[Test.AnotherWay._g_record_started];
+
+ var h_button = Test.AnotherWay._record_control_get_element("record_h");
+ h_button.style.backgroundColor = Test.AnotherWay._g_record_keydown == "h" ? keydown_color : button_color;
+
+ var p_button = Test.AnotherWay._record_control_get_element("record_p");
+ var record_pause_on = Test.AnotherWay._record_control_get_element("record_pause_on");
+ var record_pause_off = Test.AnotherWay._record_control_get_element("record_pause_off");
+ p_button.style.backgroundColor = Test.AnotherWay._record_control_key_disabled("p") ? disabled_color : Test.AnotherWay._g_record_keydown == "p" ? keydown_color : Test.AnotherWay._g_record_paused ? active_color : button_color;
+ record_pause_on.style.display = display[!Test.AnotherWay._g_record_paused];
+ record_pause_off.style.display = display[Test.AnotherWay._g_record_paused];
+
+ var m_button = Test.AnotherWay._record_control_get_element("record_m");
+ var record_include_mousemove = Test.AnotherWay._record_control_get_element("record_include_mousemove");
+ var record_omit_mousemove = Test.AnotherWay._record_control_get_element("record_omit_mousemove");
+ m_button.style.backgroundColor = Test.AnotherWay._g_record_keydown == "m" ? keydown_color : Test.AnotherWay._g_record_include_mousemove ? active_color : button_color;
+ record_include_mousemove.style.display = display[!Test.AnotherWay._g_record_include_mousemove];
+ record_omit_mousemove.style.display = display[Test.AnotherWay._g_record_include_mousemove];
+
+ var c_button = Test.AnotherWay._record_control_get_element("record_c");
+ c_button.style.backgroundColor = Test.AnotherWay._record_control_key_disabled("c") ? disabled_color : Test.AnotherWay._g_record_keydown == "c" ? keydown_color : button_color;
+
+ var record_indicator = Test.AnotherWay._record_control_get_element("record_indicator");
+ record_indicator.style.display = display[Test.AnotherWay._g_record_started];
+
+ var pause_indicator = Test.AnotherWay._record_control_get_element("record_pause_indicator");
+ pause_indicator.style.display = display[Test.AnotherWay._g_record_paused];
+
+ var record_control = Test.AnotherWay._record_control_get_element("record_control");
+ record_control.style.display = Test.AnotherWay._g_record_control_visible ? "block" : "none";
+
+ var shift_button = Test.AnotherWay._record_control_get_element("record_shift_key");
+ shift_button.style.backgroundColor = Test.AnotherWay._g_record_shift_keydown ? keydown_color : button_color;
+
+ var ctrl_button = Test.AnotherWay._record_control_get_element("record_ctrl_key");
+ ctrl_button.style.backgroundColor = Test.AnotherWay._g_record_ctrl_keydown ? keydown_color : button_color;
+};
+Test.AnotherWay._record_format_time = function(t){
+ t = new Date(t);
+ var m = t.getMinutes();
+ var s = t.getSeconds();
+ var str = m == 0 ? "" : m + "m ";
+ str += s + "s.";
+ return str;
+};
+Test.AnotherWay._record_control_update_time = function(){
+ var time_display = Test.AnotherWay._record_control_get_element("record_time");
+ if (time_display != null) {
+ time_display.innerHTML = Test.AnotherWay._record_format_time((new Date()).getTime() - Test.AnotherWay._g_record_start_time);
+ }
+};
+Test.AnotherWay._record_control_update_highlight = function(elem, style, event){
+ if (elem == null) {
+ Test.AnotherWay._record_highlight_border(null);
+ }
+ else {
+ var pos = Test.AnotherWay._get_page_coords(elem);
+ if (style == "ball" || elem != Test.AnotherWay._g_record_highlighted_element.element || pos.x != Test.AnotherWay._g_record_highlighted_element.x || pos.y != Test.AnotherWay._g_record_highlighted_element.y) {
+ Test.AnotherWay._g_record_highlighted_element = {
+ element: elem,
+ x: pos.x,
+ y: pos.y
+ };
+ Test.AnotherWay._record_highlight_border(elem, style, event);
+ }
+ }
+};
+Test.AnotherWay._record_decode_key = function(event){
+ var k = null;
+ if (event == null) {
+ k = Test.AnotherWay._g_record_wnd.event.keyCode;
+ }
+ else {
+ k = event.which;
+ }
+ if (k == 83) {
+ return "s";
+ }
+ else
+ if (k == 72) {
+ return "h";
+ }
+ else
+ if (k == 73) {
+ return "i";
+ }
+ else
+ if (k == 80) {
+ return "p";
+ }
+ else
+ if (k == 67) {
+ return "c";
+ }
+ else
+ if (k == 77) {
+ return "m";
+ }
+ else
+ if (k == 16) {
+ return "shift";
+ }
+ else
+ if (k == 17) {
+ return "ctrl";
+ }
+ else
+ if (k == 18) {
+ return "alt";
+ }
+ else
+ if (k == 19) {
+ return "pause";
+ }
+ else
+ if (k == 123) {
+ return "f12";
+ }
+ return "";
+};
+Test.AnotherWay._record_control_keydown = function(event){
+ var handled = false;
+ var k = Test.AnotherWay._record_decode_key(event);
+ if (k == "shift") {
+ Test.AnotherWay._g_record_shift_keydown = true;
+ }
+ else
+ if (k == "ctrl") {
+ Test.AnotherWay._g_record_ctrl_keydown = true;
+ }
+ else
+ if (k != "" && (Test.AnotherWay._g_record_keydown == null || Test.AnotherWay._g_record_keydown == k)) {
+ if (Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown && !Test.AnotherWay._record_control_key_disabled(k)) {
+ Test.AnotherWay._g_record_keydown = k;
+ handled = true;
+ }
+ }
+ else {
+ Test.AnotherWay._g_record_keydown = "";
+ }
+ Test.AnotherWay._record_control_update_ui();
+ if (!handled) {
+ // Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+ }
+ return;
+};
+Test.AnotherWay._record_control_keyup = function(event){
+ var handled = false;
+ var k = Test.AnotherWay._record_decode_key(event);
+ if (k == "shift") {
+ Test.AnotherWay._g_record_shift_keydown = false;
+ }
+ else
+ if (k == "ctrl") {
+ Test.AnotherWay._g_record_ctrl_keydown = false;
+ }
+ else
+ if (k != "" && k == Test.AnotherWay._g_record_keydown && Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown) {
+ if (k == "s") {
+ Test.AnotherWay._g_record_started = !Test.AnotherWay._g_record_started;
+ if (Test.AnotherWay._g_record_started) {
+ Test.AnotherWay._g_record_events = [];
+ Test.AnotherWay._g_record_start_time = (new Date()).getTime();
+ Test.AnotherWay._record_control_update_time();
+ Test.AnotherWay._g_record_update_time_interval = window.setInterval(Test.AnotherWay._record_control_update_time, 200);
+ }
+ else {
+ Test.AnotherWay._record_control_update_highlight(null);
+ if (!Test.AnotherWay._g_record_paused) {
+ window.clearInterval(Test.AnotherWay._g_record_update_time_interval);
+ }
+ Test.AnotherWay._g_record_waiting_for_results = true;
+ // open a new window for self, pass a parameter to dump recorded events as javascript code there
+ // (the easiest way to obtain a document from the same origin, so it's writable, is to open this same page again)
+ Test.AnotherWay._g_record_paused = false;
+ var loc = window.location;
+ loc = loc.protocol + "//" + loc.host + loc.pathname + "?recording_results=" + Test.AnotherWay._g_record_random_id;
+ if (window.open(loc, "_blank") == null) {
+ alert("unable to open new window for results");
+ }
+ }
+ handled = true;
+ }
+ else
+ if (k == "h") {
+ Test.AnotherWay._g_record_control_visible = !Test.AnotherWay._g_record_control_visible;
+ handled = true;
+ }
+ else
+ if (k == "p") {
+ Test.AnotherWay._g_record_paused = !Test.AnotherWay._g_record_paused;
+ if (Test.AnotherWay._g_record_paused) {
+ Test.AnotherWay._g_record_pause_start_time = (new Date()).getTime();
+ if (Test.AnotherWay._g_record_started) {
+ window.clearInterval(Test.AnotherWay._g_record_update_time_interval);
+ }
+ Test.AnotherWay._record_control_update_highlight(null);
+ }
+ else {
+ var pause_duration = (new Date()).getTime() - Test.AnotherWay._g_record_pause_start_time;
+ Test.AnotherWay._g_record_start_time += pause_duration;
+ Test.AnotherWay._g_record_update_time_interval = window.setInterval(Test.AnotherWay._record_control_update_time, 200);
+ }
+ handled = true;
+ }
+ else
+ if (k == "m") {
+ Test.AnotherWay._g_record_include_mousemove = !Test.AnotherWay._g_record_include_mousemove;
+ handled = true;
+ }
+ else
+ if (k == "c") {
+ var o = Test.AnotherWay._record_checkpoint();
+ Test.AnotherWay._record_display_checkpoint(o);
+ Test.AnotherWay._record_flash_border("#24d");
+ handled = true;
+ }
+ }
+ Test.AnotherWay._g_record_keydown = null;
+ Test.AnotherWay._record_control_update_ui();
+ if (!handled) {
+ // Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+ }
+ return;
+};
+Test.AnotherWay._record_html_node_path = function(node){
+ if (node == null) {
+ return null;
+ }
+ var path = [];
+ while (true) {
+ if (node.id != null && node.id != "") {
+ path.unshift("#" + node.id + " " + node.nodeName);
+ break;
+ }
+ else {
+ var parent_node = node.parentNode;
+ if (parent_node == null) {
+ return []; // no BODY up the path - this node is screwed (browsers differ in what's above the body), discard
+ }
+ else {
+ var i = 0;
+ var found = false;
+ for (var child = parent_node.firstChild; child != null; child = child.nextSibling) {
+ if (child == node) {
+ found = true;
+ break;
+ }
+ if (child.nodeType == 1) { // count only HTML element nodes
+ ++i;
+ }
+ }
+ if (!found) {
+ i = -1;
+ }
+ path.unshift(i + " " + node.nodeName);
+ if (parent_node.nodeName == "BODY" || parent_node.nodeName == "body") {
+ break;
+ }
+ node = parent_node;
+ }
+ }
+ }
+ return path;
+};
+Test.AnotherWay._record_node_path_to_string = function(path){
+ var s = "";
+ if (path != null) {
+ for (var i = 0; i < path.length; ++i) {
+ s += i == 0 ? "" : ", ";
+ var elem = path[i].split(" ");
+ if (elem[0].charAt(0) == "#") {
+ s += elem[1] + " " + elem[0];
+ }
+ else {
+ s += elem[1] + " [" + elem[0] + "]";
+ }
+ }
+ }
+ return s;
+};
+Test.AnotherWay._record_node_path_to_node = function(path_str, doc){
+ if (path_str == null) {
+ return null;
+ }
+ var path = path_str.split(",");
+ var node = doc.body;
+ for (var i = 0; i < path.length; ++i) {
+ var node_i = path[i].split(" ")[0];
+ if (node_i.charAt(0) == "#") {
+ node = doc.getElementById(node_i.substring(1));
+ }
+ else {
+ if (node_i < 0 || node_i >= node.childNodes.length) {
+ node = null;
+ }
+ else {
+ node = node.firstChild;
+ while (node != null) {
+ if (node.nodeType == 1) { // count only HTML element nodes
+ if (node_i == 0) {
+ break;
+ }
+ --node_i;
+ }
+ node = node.nextSibling;
+ }
+ }
+ }
+ if (node == null) {
+ return null;
+ }
+ }
+ return node;
+};
+Test.AnotherWay._record_control_contains_id = function(s){
+ return s.match(/^#record_[\w_]+/) && s.match(Test.AnotherWay._g_record_random_id);
+};
+Test.AnotherWay._record_checkpoint = function(){
+ var o = {
+ type: "_checkpoint",
+ time: (new Date()).getTime() - Test.AnotherWay._g_record_start_time,
+ which: Test.AnotherWay._g_record_checkpoint_count++,
+ target: Test.AnotherWay._record_html_node_path(Test.AnotherWay._g_record_under_cursor)
+ };
+ Test.AnotherWay._g_record_events.push(o);
+ return o;
+};
+Test.AnotherWay._record_event = function(event){
+ var unneeded = ["rangeOffset", "eventPhase", "timeStamp", "isTrusted", "popupWindowFeatures", "rangeOffset"];
+ if (Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused) {
+ var o = {};
+ for (var n in event) {
+ var needed = !n.match(/^[A-Z0-9_]+$/);
+ if (needed) {
+ for (var ui = 0; ui < unneeded.length; ++ui) {
+ if (unneeded[ui] == n) {
+ needed = false;
+ break;
+ }
+ }
+ if (needed) {
+ var value = event[n];
+ if (typeof(value) != "object" && typeof(value) != "function") {
+ o[n] = value;
+ }
+ else
+ if (n == "target" || n == "relatedTarget") {
+ o[n] = Test.AnotherWay._record_html_node_path(value);
+ }
+ }
+ }
+ }
+ o["time"] = (new Date()).getTime() - Test.AnotherWay._g_record_start_time;
+ var over_record_control = o["target"] != null && o["target"][0] != null && Test.AnotherWay._record_control_contains_id(o["target"][0]);
+ if (!over_record_control) {
+ Test.AnotherWay._g_record_events.push(o);
+ }
+ }
+ return true;
+};
+Test.AnotherWay._record_on_mousemove = function(event){
+ var path = Test.AnotherWay._record_html_node_path(event.target);
+ var new_mouse_over_record_control = path != null && path[0] != null && Test.AnotherWay._record_control_contains_id(path[0]);
+ if (new_mouse_over_record_control != Test.AnotherWay._g_record_mouse_over_record_control) {
+ Test.AnotherWay._g_record_mouse_over_record_control = new_mouse_over_record_control;
+ Test.AnotherWay._record_control_update_ui();
+ }
+ if (event.target != null && event.target != Test.AnotherWay._g_record_under_cursor) {
+ Test.AnotherWay._g_record_under_cursor = event.target;
+ var s = "";
+ if (path == null || path[0] == null || !Test.AnotherWay._record_control_contains_id(path[0])) {
+ s = Test.AnotherWay._record_node_path_to_string(path);
+ }
+ if (s == "") {
+ s = "&nbsp;";
+ }
+ var cursor_over_indicator = Test.AnotherWay._record_control_get_element("record_cursor_over");
+ cursor_over_indicator.innerHTML = s;
+ }
+
+ var highlight_element = null;
+ if (!Test.AnotherWay._g_record_mouse_over_record_control && Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused) {
+ highlight_element = event.target;
+ }
+ // highlight border disabled on recording - it causes page to scroll, issuing spurious mouseover/mouseout event
+ //Test.AnotherWay._record_control_update_highlight( highlight_element, "border" );
+
+ if (Test.AnotherWay._g_record_include_mousemove) {
+ Test.AnotherWay._record_event(event);
+ }
+ return true;
+};
+Test.AnotherWay._record_display_checkpoint = function(o){
+ var checkpoints_div = Test.AnotherWay._record_control_get_element("record_checkpoints");
+ var p = checkpoints_div.appendChild(checkpoints_div.ownerDocument.createElement("div"));
+ p.style.marginTop = "3px";
+ p.style.font = "normal normal 8pt sans-serif";
+ p.style.color = "#000";
+ p.style.textAligh = "left";
+ p.style.position = "relative";
+ p.style.width = "100%";
+ var checkpoint_text = "";
+ checkpoint_text += "#" + (o.which + 1);
+ checkpoint_text += " " + Test.AnotherWay._record_format_time(o.time);
+ if (o.target != null) {
+ checkpoint_text += Test.AnotherWay._record_node_path_to_string(o.target);
+ }
+ p.appendChild(p.ownerDocument.createTextNode(checkpoint_text));
+};
+Test.AnotherWay._record_save_results = function(doc){
+ // strange, but DOM-style append does not work here in opera 8.
+ var append = function(s){
+ doc.write("<div>" + s + "</div>");
+ };
+ append("/* paste this data into your javascript and pass it as an argument to replay_events method */");
+ append("{ checkpoints: [");
+ var first_checkpoint = true;
+ for (var i = 0; i < Test.AnotherWay._g_record_events.length; ++i) {
+ var o = Test.AnotherWay._g_record_events[i];
+ if (o.type == "_checkpoint") {
+ var str = first_checkpoint ? "" : "}, ";
+ str += "function( tst, wnd ) { // #" + o.which + " time " + Test.AnotherWay._record_format_time(o.time) + " cursor was over " + Test.AnotherWay._record_node_path_to_string(o.target);
+ append(str);
+ first_checkpoint = false;
+ }
+ }
+ if (!first_checkpoint) {
+ append("}");
+ }
+ append("], events: [ ");
+ var prev_time = 0;
+ for (var i = 0; i < Test.AnotherWay._g_record_events.length; ++i) {
+ var o = Test.AnotherWay._g_record_events[i];
+ var s = "";
+ s += "{";
+ var n_first = true;
+ for (var n in o) {
+ if (n == "time") { // convert to relative time
+ var cur_time = o[n] - 0;
+ o[n] = cur_time - prev_time;
+ prev_time = cur_time;
+ }
+ s += n_first ? n : ", " + n;
+ s += ":";
+ if (o[n] == null) {
+ s += "null";
+ }
+ else {
+ s += "\"" + o[n] + "\"";
+ }
+ n_first = false;
+ }
+ s += i == Test.AnotherWay._g_record_events.length - 1 ? "}" : "},";
+ append(s);
+ }
+ append("] }");
+ append(";");
+};
+
+Test.AnotherWay._g_record_border; // border highlighting element under cursor
+Test.AnotherWay._g_record_border_flashes = []; // array of { color: color, timeout: milliseconds }
+Test.AnotherWay._g_record_border_flashing = false;
+Test.AnotherWay._g_record_border_normal_color = "#d4b";
+Test.AnotherWay._record_flash_border_timeout = function(){
+ var color = Test.AnotherWay._g_record_border_normal_color;
+ var timeout = null;
+ if (Test.AnotherWay._g_record_border_flashes.length != 0) {
+ color = Test.AnotherWay._g_record_border_flashes[0].color;
+ timeout = Test.AnotherWay._g_record_border_flashes[0].timeout;
+ Test.AnotherWay._g_record_border_flashes.splice(0, 1);
+ }
+ if (Test.AnotherWay._g_record_border != null) {
+ for (var i = 0; i < Test.AnotherWay._g_record_border.length; ++i) {
+ Test.AnotherWay._g_record_border[i].style.backgroundColor = color;
+ }
+ }
+ if (timeout != null) {
+ setTimeout(Test.AnotherWay._record_flash_border_timeout, timeout);
+ }
+ else {
+ Test.AnotherWay._g_record_border_flashing = false;
+ }
+};
+Test.AnotherWay._get_page_coords = function(elm){
+ var point = {
+ x: 0,
+ y: 0
+ };
+ while (elm) {
+ point.x += elm.offsetLeft;
+ point.y += elm.offsetTop;
+ elm = elm.offsetParent;
+ }
+ return point;
+};
+Test.AnotherWay._set_page_coords = function(elm, x, y){
+ var parent_coords = {
+ x: 0,
+ y: 0
+ };
+ if (elm.offsetParent) {
+ parent_coords = Test.AnotherWay._get_page_coords(elm.offsetParent);
+ }
+ var new_x = x - parent_coords.x;
+ if (new_x < 0) {
+ new_x = 0;
+ }
+ elm.style.left = new_x + 'px';
+ var new_y = y - parent_coords.y;
+ if (new_y < 0) {
+ new_y = 0;
+ }
+ elm.style.top = new_y + 'px';
+};
+Test.AnotherWay._record_setup_highlight_positions = function(element, style, coords, positions){
+ if (style == "border") {
+ var width = element.clientWidth;
+ var height = element.clientHeight;
+ var step = 0;
+ var thickness = 2;
+ var fudge_expand = 4;
+ positions.push({
+ x: coords.x - step - thickness,
+ y: coords.y - step - thickness,
+ width: width + 2 * step + 2 * thickness + fudge_expand,
+ height: thickness
+ });
+ positions.push({
+ x: coords.x + width + step + fudge_expand,
+ y: coords.y - step - thickness,
+ width: thickness,
+ height: height + 2 * step + 2 * thickness + fudge_expand
+ });
+ positions.push({
+ x: positions[0].x,
+ y: positions[0].y,
+ width: positions[0].width,
+ height: positions[0].height
+ });
+ positions.push({
+ x: positions[1].x,
+ y: positions[1].y,
+ width: positions[1].width,
+ height: positions[1].height
+ });
+ positions[2].y += height + thickness + 2 * step + fudge_expand;
+ positions[3].x -= width + thickness + 2 * step + fudge_expand;
+ }
+ else
+ if (style == "ball") {
+ positions.push({
+ x: coords.x + 2,
+ y: coords.y,
+ width: 2,
+ height: 6
+ });
+ positions.push({
+ x: coords.x,
+ y: coords.y + 2,
+ width: 6,
+ height: 2
+ });
+ positions.push({
+ x: coords.x + 1,
+ y: coords.y + 1,
+ width: 4,
+ height: 4
+ });
+ }
+};
+Test.AnotherWay._record_highlight_border = function(element, style, event) // null - hide border
+{
+ if (element != null) {
+ if (Test.AnotherWay._g_record_border == null || Test.AnotherWay._g_record_border[0].ownerDocument != element.ownerDocument) {
+ Test.AnotherWay._g_record_border = [];
+ var n = style == "border" ? 4 : style == "ball" ? 3 : 0;
+ for (var i = 0; i < 4; ++i) {
+ var b = element.ownerDocument.createElement("div");
+ b.style.position = "absolute";
+ b.style.zIndex = "1";
+ b.style.backgroundColor = Test.AnotherWay._g_record_border_normal_color;
+ element.ownerDocument.body.appendChild(b);
+ Test.AnotherWay._g_record_border.push(b);
+ }
+ }
+ var coords = null;
+ if (style == "border") {
+ coords = Test.AnotherWay._get_page_coords(element);
+ }
+ else
+ if (style == "ball") {
+ if (event != null) {
+ if (event.pageX != null && event.pageY != null) {
+ coords = {
+ x: event.pageX - 0,
+ y: event.pageY - 0
+ };
+ }
+ else
+ if (event.clientX != null && event.clientY != null) {
+ var doc = element.ownerDocument;
+ if (doc != null) {
+ coords = {
+ x: (event.clientX - 0) + doc.body.scrollLeft,
+ y: (event.clientY - 0) + doc.body.scrollTop
+ };
+ }
+ }
+ }
+ }
+ if (coords != null && element.clientWidth != null && element.clientHeight != null) {
+ var positions = [];
+ Test.AnotherWay._record_setup_highlight_positions(element, style, coords, positions);
+ for (var i = 0; i < positions.length; ++i) {
+ var b = Test.AnotherWay._g_record_border[i];
+ var p = positions[i];
+ Test.AnotherWay._set_page_coords(b, p.x, p.y);
+ b.style.width = p.width + "px";
+ b.style.height = p.height + "px";
+ b.style.display = "block";
+ }
+ }
+ }
+ else {
+ if (Test.AnotherWay._g_record_border != null) {
+ for (var i = 0; i < Test.AnotherWay._g_record_border.length; ++i) {
+ Test.AnotherWay._g_record_border[i].style.display = "none";
+ }
+ }
+ }
+};
+Test.AnotherWay._record_flash_border = function(color){
+ if (Test.AnotherWay._g_record_border_flashing) { //already
+ Test.AnotherWay._g_record_border_flashes.push({
+ color: Test.AnotherWay._g_record_border_normal_color,
+ timeout: 300
+ });
+ Test.AnotherWay._g_record_border_flashes.push({
+ color: color,
+ timeout: 600
+ });
+ }
+ else {
+ Test.AnotherWay._g_record_border_flashing = true;
+ Test.AnotherWay._g_record_border_flashes.push({
+ color: color,
+ timeout: 600
+ });
+ Test.AnotherWay._record_flash_border_timeout();
+ }
+};
+Test.AnotherWay._record_prepare_doc_for_results = function(){
+ document.open();
+ document.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">");
+ document.write("<html><head><title> Input recording results</title>");
+ document.write("<style type=\"text/css\">");
+ document.write("body { font: normal normal smaller sans-serif; }");
+ document.write("div { margin-top: 3px; }");
+ document.write("</style></head><body>");
+ // opera and mozilla disagree over who the opener is.
+ if (typeof(window.opener.Test) != "undefined" && typeof(window.opener.Test.AnotherWay) != "undefined") {
+ window.opener.Test.AnotherWay._record_save_results(document);
+ window.opener.Test.AnotherWay._g_record_waiting_for_results = false;
+ window.opener.Test.AnotherWay._record_control_update_ui();
+ }
+ else
+ if (typeof(window.opener.opener.Test) != "undefined" && typeof(window.opener.opener.Test.AnotherWay) != "undefined") {
+ window.opener.opener.Test.AnotherWay._record_save_results(document);
+ window.opener.opener.Test.AnotherWay._g_record_waiting_for_results = false;
+ window.opener.opener.Test.AnotherWay._record_control_update_ui();
+ }
+ document.write("</body>");
+ document.close();
+};
+
+// global initialization
+onload = function(){
+ if (window.opera) {
+ var good_opera = typeof(window.opera.version) == "function";
+ good_opera = good_opera && window.opera.version().match(/^\s*(\d+)/);
+ good_opera = good_opera && RegExp.$1 >= 8;
+ }
+ var span = document.createElement("SPAN");
+ span.innerHTML = "<!--[if IE]><br /><![endif]-" + "->";
+ var is_ie = span.getElementsByTagName("BR").length > 0;
+
+ Test.AnotherWay._g_test_iframe = window.frames.test_iframe;
+
+ var query_str = window.location.search;
+ if (query_str.charAt(0) == "?") {
+ query_str = query_str.substring(1);
+ }
+ var testlist_page = "list-tests.html";
+ var auto_run = false;
+ if (query_str != "") {
+ var params = [query_str];
+ if (query_str.indexOf(";") != -1) {
+ params = query_str.split(";");
+ }
+ else
+ if (query_str.indexOf("&") != -1) {
+ params = query_str.split("&");
+ }
+ for (var param_i = 0; param_i < params.length; ++param_i) {
+ var param = params[param_i].split("=");
+ if (param[0] == "recording_results") {
+ if (window.opener != null) {
+ // we were told to show recording results - replace everything in the document with the results
+ Test.AnotherWay._record_prepare_doc_for_results();
+ return;
+ }
+ }
+ else
+ if (param[0] == "testpage") {
+ Test.AnotherWay._add_test_page_url(decodeURIComponent(param[1]), "anotherway");
+ }
+ else
+ if (param[0] == "jsantestpage") {
+ Test.AnotherWay._add_test_page_url(decodeURIComponent(param[1]), "jsan");
+ }
+ else
+ if (param[0] == "testlist") {
+ testlist_page = decodeURIComponent(param[1]);
+ }
+ else
+ if (param[0] == "testframe") {
+ if (window.opera && !good_opera) {
+ Test.AnotherWay._show_error("testframe parameter does not work in versions of Opera prior to 8.0. Sorry (pathches are welcome).");
+ // Opera 7 barfs on attempt to access frame.frameElement.
+ // if someone knows a way to assign onload handler to that iframe in Opera 7
+ // without disrupting code that works in other browsers, patches are welcome.
+ }
+ else {
+ var frame_path = param[1].split(".");
+ var frame = top;
+ for (var frame_path_i = 0; frame_path_i < frame_path.length; ++frame_path_i) {
+ frame = frame[frame_path[frame_path_i]];
+ }
+ if (frame == null) {
+ Test.AnotherWay._show_error("unable to find frame specified for loading test pages: " + param[1]);
+ }
+ else {
+ if (frame.frameElement != null) { // for the following assignement to onload to work, frameElement is required
+ frame = frame.frameElement;
+ }
+ Test.AnotherWay._g_test_iframe = frame;
+ }
+ }
+ }
+ else
+ if (param[0] == "testframe_no_clear") {
+ Test.AnotherWay._g_test_frame_no_clear = true;
+ }
+ else
+ if (param[0] == "windows") {
+ if (param[1] == "none") {
+ Test.AnotherWay._test_object_t.prototype.open_window = null;
+ }
+ }
+ else
+ if (param[0] == "run") {
+ auto_run = true;
+ if (param[1] == "all") {
+ Test.AnotherWay._g_pages_to_run = "all";
+ }
+ else {
+ if (Test.AnotherWay._g_pages_to_run == null || Test.AnotherWay._g_pages_to_run == "all") {
+ Test.AnotherWay._g_pages_to_run = [];
+ }
+ var pages = param[1].split(",");
+ for (var i = 0; i < pages.length; ++i) {
+ Test.AnotherWay._g_pages_to_run.push(pages[i]);
+ }
+ }
+ }
+ }
+ }
+ if (Test.AnotherWay._g_test_page_urls.length == 0) { // if no individual pages were given on the command line, load the list
+ var result = Test.AnotherWay._set_iframe_location(window.frames["list_iframe"], testlist_page);
+ if (result.msg != null) {
+ Test.AnotherWay._show_error(result.msg);
+ }
+ Test.AnotherWay._g_run_on_list_load = auto_run;
+ }
+ else {
+ Test.AnotherWay._g_run_on_main_load = auto_run;
+ }
+
+ var f = Test.AnotherWay._g_test_iframe;
+ try {
+ if (f.attachEvent != null) {
+ f.attachEvent("onload", Test.AnotherWay._test_page_onload);
+ }
+ else {
+ f.onload = Test.AnotherWay._test_page_onload;
+ }
+ if (Test.AnotherWay._g_test_iframe.nodeType != null && Test.AnotherWay._g_test_iframe.contentWindow != null) { // it's iframe element, not the iframe. we need iframe.
+ Test.AnotherWay._g_test_iframe = Test.AnotherWay._g_test_iframe.contentWindow;
+ }
+ }
+ catch (e) {
+ // ignore stupid opera error if the frame has onload handler assigned in the inline html
+ }
+ var handlers = {
+ "run_all": {
+ "onclick": Test.AnotherWay._run_all_onclick
+ },
+ "run_selected": {
+ "onclick": Test.AnotherWay._run_selected_onclick
+ },
+ "unselect_all": {
+ "onclick": Test.AnotherWay._unselect_all_onclick
+ },
+ "record_select": {
+ "onfocus": Test.AnotherWay._record_check_onfocus
+ },
+ "record_input": {
+ "onfocus": Test.AnotherWay._record_check_onfocus
+ },
+ "record_start": {
+ "onclick": Test.AnotherWay._record_start_onclick
+ },
+ "clear_btn": {
+ "onclick": Test.AnotherWay._results_clear_onclick
+ },
+ "results_tab": {
+ "onclick": Test.AnotherWay._tab_onclick,
+ "onmouseover": Test.AnotherWay._tab_mouseover,
+ "onmouseout": Test.AnotherWay._tab_mouseout
+ },
+ "debug_tab": {
+ "onclick": Test.AnotherWay._tab_onclick,
+ "onmouseover": Test.AnotherWay._tab_mouseover,
+ "onmouseout": Test.AnotherWay._tab_mouseout
+ }
+ };
+ for (var hs in handlers) {
+ var o = document.getElementById(hs);
+ if (o != null) {
+ for (var h in handlers[hs]) {
+ o[h] = handlers[hs][h];
+ }
+ }
+ else {
+ Test.AnotherWay._show_error("unable to set " + h + " handler: id " + hs + " not found");
+ }
+ }
+
+ if (window.opera && !good_opera) {
+ Test.AnotherWay._g_no_record_msg = "Input events recording and replaying is not available in opera versions prior to 8.0.";
+ }
+ if (is_ie) {
+ Test.AnotherWay._g_no_record_msg = "Input events recording and replaying is not available in internet explorer.";
+ }
+ if (Test.AnotherWay._g_no_record_msg != null) {
+ var no_record_p = document.getElementById("record_not_supported");
+ no_record_p.style.display = "block";
+ no_record_p.appendChild(document.createTextNode(Test.AnotherWay._g_no_record_msg));
+ }
+
+ Test.AnotherWay._g_main_loaded = true;
+ if (Test.AnotherWay._g_run_on_main_load) {
+ Test.AnotherWay._g_run_on_main_load = false;
+ Test.AnotherWay._run_pages_to_run();
+ }
+};
diff --git a/misc/openlayers/tests/Test.AnotherWay.xml_eq.js b/misc/openlayers/tests/Test.AnotherWay.xml_eq.js
new file mode 100644
index 0000000..8c24566
--- /dev/null
+++ b/misc/openlayers/tests/Test.AnotherWay.xml_eq.js
@@ -0,0 +1,311 @@
+/**
+ * File: Test.AnotherWay.xml_eq.js
+ * Adds a xml_eq method to AnotherWay test objects.
+ *
+ */
+
+(function() {
+
+ /**
+ * Function: createNode
+ * Given a string, try to create an XML DOM node. Throws string messages
+ * on failure.
+ *
+ * Parameters:
+ * text - {String} An XML string.
+ *
+ * Returns:
+ * {DOMElement} An element node.
+ */
+ function createNode(text) {
+
+ var index = text.indexOf('<');
+ if(index > 0) {
+ text = text.substring(index);
+ }
+
+ var doc;
+ if(window.ActiveXObject && !this.xmldom) {
+ doc = new ActiveXObject("Microsoft.XMLDOM");
+ try {
+ doc.loadXML(text);
+ } catch(err) {
+ throw "ActiveXObject loadXML failed: " + err;
+ }
+ } else if(window.DOMParser) {
+ try {
+ doc = new DOMParser().parseFromString(text, 'text/xml');
+ } catch(err) {
+ throw "DOMParser.parseFromString failed";
+ }
+ if(doc.documentElement && doc.documentElement.nodeName == "parsererror") {
+ throw "DOMParser.parseFromString returned parsererror";
+ }
+ } else {
+ var req = new XMLHttpRequest();
+ req.open("GET", "data:text/xml;charset=utf-8," +
+ encodeURIComponent(text), false);
+ if(req.overrideMimeType) {
+ req.overrideMimeType("text/xml");
+ }
+ req.send(null);
+ doc = req.responseXML;
+ }
+
+ var root = doc.documentElement;
+ if(!root) {
+ throw "no documentElement";
+ }
+ return root;
+ }
+
+ /**
+ * Function assertEqual
+ * Test two objects for equivalence (based on ==). Throw an exception
+ * if not equivalent.
+ *
+ * Parameters:
+ * got - {Object}
+ * expected - {Object}
+ * msg - {String} The message to be thrown. This message will be appended
+ * with ": got {got} but expected {expected}" where got and expected are
+ * replaced with string representations of the above arguments.
+ */
+ function assertEqual(got, expected, msg) {
+ if(got === undefined) {
+ got = "undefined";
+ } else if (got === null) {
+ got = "null";
+ }
+ if(expected === undefined) {
+ expected = "undefined";
+ } else if (expected === null) {
+ expected = "null";
+ }
+ if(got != expected) {
+ throw msg + ": got '" + got + "' but expected '" + expected + "'";
+ }
+ }
+
+ /**
+ * Function assertElementNodesEqual
+ * Test two element nodes for equivalence. Nodes are considered equivalent
+ * if they are of the same type, have the same name, have the same
+ * namespace prefix and uri, and if all child nodes are equivalent.
+ * Throws a message as exception if not equivalent.
+ *
+ * Parameters:
+ * got - {DOMElement}
+ * expected - {DOMElement}
+ * options - {Object} Optional object for configuring test options.
+ *
+ * Valid options:
+ * prefix - {Boolean} Compare element and attribute
+ * prefixes (namespace uri always tested). Default is false.
+ * includeWhiteSpace - {Boolean} Include whitespace only nodes when
+ * comparing child nodes. Default is false.
+ */
+ function assertElementNodesEqual(got, expected, options) {
+ var testPrefix = (options && options.prefix === true);
+
+ // compare types
+ assertEqual(got.nodeType, expected.nodeType, "Node type mismatch");
+
+ // compare names
+ var gotName = testPrefix ?
+ got.nodeName : got.nodeName.split(":").pop();
+ var expName = testPrefix ?
+ expected.nodeName : expected.nodeName.split(":").pop();
+ assertEqual(gotName, expName, "Node name mismatch");
+
+ // for text nodes compare value
+ if(got.nodeType == 3) {
+ assertEqual(
+ got.nodeValue, expected.nodeValue, "Node value mismatch"
+ );
+ }
+ // for element type nodes compare namespace, attributes, and children
+ else if(got.nodeType == 1) {
+
+ // test namespace alias and uri
+ if(got.prefix || expected.prefix) {
+ if(testPrefix) {
+ assertEqual(
+ got.prefix, expected.prefix,
+ "Bad prefix for " + got.nodeName
+ );
+ }
+ }
+ if(got.namespaceURI || expected.namespaceURI) {
+ assertEqual(
+ got.namespaceURI, expected.namespaceURI,
+ "Bad namespaceURI for " + got.nodeName
+ );
+ }
+
+ // compare attributes - disregard xmlns given namespace handling above
+ var gotAttrLen = 0;
+ var gotAttr = {};
+ var expAttrLen = 0;
+ var expAttr = {};
+ var ga, ea, gn, en;
+ for(var i=0; i<got.attributes.length; ++i) {
+ ga = got.attributes[i];
+ if(ga.specified === undefined || ga.specified === true) {
+ if(ga.name.split(":").shift() != "xmlns") {
+ gn = testPrefix ? ga.name : ga.name.split(":").pop();
+ gotAttr[gn] = ga;
+ ++gotAttrLen;
+ }
+ }
+ }
+ for(var i=0; i<expected.attributes.length; ++i) {
+ ea = expected.attributes[i];
+ if(ea.specified === undefined || ea.specified === true) {
+ if(ea.name.split(":").shift() != "xmlns") {
+ en = testPrefix ? ea.name : ea.name.split(":").pop();
+ expAttr[en] = ea;
+ ++expAttrLen;
+ }
+ }
+ }
+ assertEqual(
+ gotAttrLen, expAttrLen,
+ "Attributes length mismatch for " + got.nodeName
+ );
+ var gv, ev;
+ for(var name in gotAttr) {
+ if(expAttr[name] == undefined) {
+ throw "Attribute name " + gotAttr[name].name + " expected for element " + got.nodeName;
+ }
+ // test attribute namespace
+ assertEqual(
+ gotAttr[name].namespaceURI, expAttr[name].namespaceURI,
+ "Attribute namespace mismatch for element " +
+ got.nodeName + " attribute name " + gotAttr[name].name
+ );
+ // test attribute value
+ assertEqual(
+ gotAttr[name].value, expAttr[name].value,
+ "Attribute value mismatch for element " + got.nodeName +
+ " attribute name " + gotAttr[name].name
+ );
+ }
+
+ // compare children
+ var gotChildNodes = getChildNodes(got, options);
+ var expChildNodes = getChildNodes(expected, options);
+
+ assertEqual(
+ gotChildNodes.length, expChildNodes.length,
+ "Children length mismatch for " + got.nodeName
+ );
+ for(var j=0; j<gotChildNodes.length; ++j) {
+ try {
+ assertElementNodesEqual(
+ gotChildNodes[j], expChildNodes[j], options
+ );
+ } catch(err) {
+ throw "Bad child " + j + " for element " + got.nodeName + ": " + err;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Function getChildNodes
+ * Returns the child nodes of the specified nodes. By default this method
+ * will ignore child text nodes which are made up of whitespace content.
+ * The 'includeWhiteSpace' option is used to control this behaviour.
+ *
+ * Parameters:
+ * node - {DOMElement}
+ * options - {Object} Optional object for test configuration.
+ *
+ * Valid options:
+ * includeWhiteSpace - {Boolean} Include whitespace only nodes when
+ * comparing child nodes. Default is false.
+ *
+ * Returns:
+ * {Array} of {DOMElement}
+ */
+ function getChildNodes(node, options) {
+ //check whitespace
+ if (options && options.includeWhiteSpace) {
+ return node.childNodes;
+ }
+ else {
+ nodes = [];
+ for (var i = 0; i < node.childNodes.length; i++ ) {
+ var child = node.childNodes[i];
+ if (child.nodeType == 1) {
+ //element node, add it
+ nodes.push(child);
+ }
+ else if (child.nodeType == 3) {
+ //text node, add if non empty
+ if (child.nodeValue &&
+ child.nodeValue.replace(/^\s*(.*?)\s*$/, "$1") != "" ) {
+
+ nodes.push(child);
+ }
+ }
+ }
+
+ return nodes;
+ }
+ }
+
+ /**
+ * Function: Test.AnotherWay._test_object_t.xml_eq
+ * Test if two XML nodes are equivalent. Tests for same node types, same
+ * node names, same namespace URI, same attributes, and recursively
+ * tests child nodes for same criteria.
+ *
+ * (code)
+ * t.xml_eq(got, expected, message);
+ * (end)
+ *
+ * Parameters:
+ * got - {DOMElement | String} A DOM node or XML string to test.
+ * expected - {DOMElement | String} The expected DOM node or XML string.
+ * msg - {String} A message to print with test output.
+ * options - {Object} Optional object for configuring test.
+ *
+ * Valid options:
+ * prefix - {Boolean} Compare element and attribute
+ * prefixes (namespace uri always tested). Default is false.
+ * includeWhiteSpace - {Boolean} Include whitespace only nodes when
+ * comparing child nodes. Default is false.
+ */
+ var proto = Test.AnotherWay._test_object_t.prototype;
+ proto.xml_eq = function(got, expected, msg, options) {
+ // convert arguments to nodes if string
+ if(typeof got == "string") {
+ try {
+ got = createNode(got);
+ } catch(err) {
+ this.fail(msg + ": got argument could not be converted to an XML node: " + err);
+ return;
+ }
+ }
+ if(typeof expected == "string") {
+ try {
+ expected = createNode(expected);
+ } catch(err) {
+ this.fail(msg + ": expected argument could not be converted to an XML node: " + err);
+ return;
+ }
+ }
+
+ // test nodes for equivalence
+ try {
+ assertElementNodesEqual(got, expected, options);
+ this.ok(true, msg);
+ } catch(err) {
+ this.fail(msg + ": " + err);
+ }
+ }
+
+})();
diff --git a/misc/openlayers/tests/Tile.html b/misc/openlayers/tests/Tile.html
new file mode 100644
index 0000000..087b320
--- /dev/null
+++ b/misc/openlayers/tests/Tile.html
@@ -0,0 +1,130 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+ var tile;
+
+ var map, layer;
+ function setUp() {
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ });
+ map.addLayer(layer)
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ }
+
+
+ function test_Tile_constructor (t) {
+ t.plan( 13 );
+
+ setUp();
+
+ var dummy = {};
+
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ tile = new OpenLayers.Tile(layer, position, bounds, url, size, {
+ eventListeners: {
+ loadstart: OpenLayers.Function.False
+ }
+ });
+
+ t.ok(tile instanceof OpenLayers.Tile, "new OpenLayers.Tile returns Tile object");
+ t.ok(tile.layer === layer, "tile.layer set correctly");
+ t.ok(tile.position.equals(position), "tile.position set correctly");
+ t.ok(tile.position != position, "tile.position set not by reference");
+ t.ok(tile.bounds.equals(bounds), "tile.bounds set correctly");
+ t.ok(tile.bounds != bounds, "tile.bounds set not by reference");
+ t.eq(tile.url, url, "tile.url set correctly");
+ t.ok(tile.size.equals(size), "tile.size is set correctly");
+ t.ok(tile.size != size, "tile.size set not by reference");
+
+ t.ok(tile.id != null, "tile is given an id");
+ t.ok(OpenLayers.String.startsWith(tile.id, "Tile_"),
+ "tile's id starts correctly");
+ t.ok(tile.events != null, "tile's events initialized");
+ t.ok(tile.events.listeners.loadstart.length == 1,
+ "tile's events initialized from eventListeners option");
+
+ tearDown();
+
+ }
+
+ function test_Tile_draw(t) {
+ t.plan(6);
+ setUp();
+
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ tile = new OpenLayers.Tile(layer, position, bounds, url, size);
+ var log = [];
+ tile.clear = function() {
+ log.push("clear");
+ }
+ tile.draw();
+ t.eq(log.length, 1, "Tile cleared before drawing");
+
+ log = [];
+ tile.events.register("beforedraw", this, function() {
+ log.push("beforedraw");
+ return false;
+ });
+ var drawn = tile.draw();
+ t.eq(log[0], "clear", "tile cleared");
+ t.eq(log[1], "beforedraw", "beforedraw event fired");
+ t.eq(drawn, null, "tile not drawn when beforedraw listener returns false");
+ drawn = tile.draw(true);
+ t.eq(log.length, 2, "no beforedraw event fired and tile not cleared when draw called with 'deferred' argument set to true");
+ t.eq(drawn, true, "tile drawn when draw called with 'deferred' argument set to true");
+
+ tearDown();
+ }
+
+ function test_Tile_destroy(t) {
+ t.plan( 6 );
+
+ setUp();
+
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ tile = new OpenLayers.Tile(layer, position, bounds, url, size);
+ tile.events.destroy = function() {
+ t.ok(true, "tile events destroy() called");
+ };
+
+
+ tile.destroy();
+
+ t.ok(tile.layer == null, "tile.layer set to null");
+ t.ok(tile.bounds == null, "tile.bounds set to null");
+ t.ok(tile.size == null, "tile.size set to null");
+ t.ok(tile.position == null, "tile.position set to null");
+
+ t.ok(tile.events == null, "tile.events set to null");
+
+ tearDown();
+
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/Tile/Image.html b/misc/openlayers/tests/Tile/Image.html
new file mode 100644
index 0000000..825c187
--- /dev/null
+++ b/misc/openlayers/tests/Tile/Image.html
@@ -0,0 +1,490 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+ // turn off tile queue, so we can check img urls in tests
+ delete OpenLayers.Layer.Grid.prototype.queueTileDraw;
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var tile;
+
+ var map, layer;
+ function setUp() {
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ });
+ map.addLayer(layer)
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ }
+
+ function test_Tile_Image_constructor (t) {
+ t.plan( 6 );
+
+ setUp();
+
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "http://www.openlayers.org/dev/tests/tileimage";
+ var size = new OpenLayers.Size(5,6);
+ tile = new OpenLayers.Tile.Image(layer, position, bounds, url, size);
+
+ t.ok( tile instanceof OpenLayers.Tile.Image, "new OpenLayers.Tile returns Tile object" );
+ t.ok( tile.layer == layer, "tile.layer is set correctly");
+ t.ok( tile.position.equals(position), "tile.position is set correctly");
+ t.ok( tile.bounds.equals(bounds), "tile.bounds is set correctly");
+ t.eq( tile.url, url, "tile.url is set correctly");
+ t.ok( tile.size.equals(size), "tile.size is set correctly");
+
+ tearDown();
+ }
+
+ function test_destroy_observers(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "../../img/blank.gif", {layers: 'basic'});
+ map.addLayer(layer);
+
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var size = new OpenLayers.Size(5,6);
+
+ // with alpha hack
+ var withAlpha = new OpenLayers.Tile.Image(layer, position, bounds, null, size);
+ withAlpha.layerAlphaHack = true;
+
+ withAlpha.draw();
+ var cacheID = withAlpha.imgDiv._eventCacheID;
+ withAlpha.destroy();
+
+ t.eq(OpenLayers.Event.observers[cacheID], undefined,
+ "With alpha hack: imgDiv observers are cleared in destroy");
+
+ // without alpha hack
+ var withoutAlpha = new OpenLayers.Tile.Image(layer, position, bounds, null, size);
+ withoutAlpha.layerAlphaHack = false;
+
+ withoutAlpha.draw();
+ var cacheID = withoutAlpha.imgDiv._eventCacheID;
+ withoutAlpha.destroy();
+
+ t.eq(OpenLayers.Event.observers[cacheID], undefined,
+ "Without alpha hack: imgDiv observers are cleared in destroy");
+
+ map.destroy();
+ }
+
+ function test_Tile_Image_async (t) {
+ t.plan( 3 );
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS(
+ "Name",
+ "../../img/blank.gif",
+ {layers: 'basic'}, {async: true, getURLasync: function(bounds, callback, scope) {
+ callback.call(scope, this.getURL(bounds));
+ }}
+ );
+ map.addLayer(layer);
+
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ tile = layer.addTile(bounds, position);
+ tile.renderTile();
+ t.delay_call(0.1, function() {
+ var expected = new Image();
+ expected.src = layer.getURL(bounds);
+ t.eq(tile.imgDiv.src, expected.src, "image src correct for async request");
+ t.eq(tile.asyncRequestId, 1, "asyncRequestId correct after renderTile");
+ tile.renderTile();
+ });
+ t.delay_call(0.2, function() {
+ t.eq(tile.asyncRequestId, 2, "asyncRequestId correct after subsequent renderTile");
+ tile.destroy();
+ layer.destroy();
+ map.destroy();
+ });
+ }
+
+ function test_Tile_Image_draw (t) {
+ t.plan(7);
+
+ var map = new OpenLayers.Map('map');
+
+ var size = new OpenLayers.Size(5,6);
+ layer = new OpenLayers.Layer.WMS("Name",
+ "../../img/blank.gif",
+ null,
+ {tileSize: size});
+ map.addLayer(layer);
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "http://www.openlayers.org/dev/tests/tileimage";
+ tile = new OpenLayers.Tile.Image(layer, position, bounds, url, size);
+
+ tile.events.register("loadstart", this, function() {
+ t.ok(true, "loadstart triggered");
+ });
+ tile.events.register("reload", this, function() {
+ t.ok(true, "reload triggered");
+ });
+
+ //this should trigger a "loadstart" event
+ tile.draw();
+
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( tile.imgDiv instanceof HTMLElement, "tile.draw creates an image");
+ var tParams = {
+ SERVICE: "WMS", VERSION: "1.1.1",
+ REQUEST: "GetMap", STYLES: "",
+ FORMAT: "image/jpeg",
+ SRS: "EPSG:4326", BBOX: [1,2,3,4],
+ WIDTH: String(size.w), HEIGHT: String(size.h)
+ };
+ var expected = new Image();
+ expected.src = "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams)
+ t.delay_call(0.1, function() {
+ t.eq( tile.imgDiv.src, expected.src, "tile.draw creates an image");
+ });
+ t.eq( tile.imgDiv.style.width, "5px", "Image width is correct" );
+ t.eq( tile.imgDiv.style.height, "6px", "Image height is correct" );
+ t.ok( tile.imgDiv.parentNode === layer.div, "Image is directly appended to the layer div" );
+
+ // this should trigger a "reload" event (since the image never actually
+ // loads in tests)
+ tile.draw();
+
+ }
+ function test_Tile_Image_OutsideMaxExtent(t) {
+ t.plan( 11 );
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "http://www.openlayers.org/dev/tests/tileimage";
+ var size = new OpenLayers.Size(5,6);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "../../img/blank.gif", {layers: 'basic'}, {encodeBBOX: true});
+ map.addLayer(layer);
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-185,-90,-180,90), url, size);
+ tile.draw()
+ t.eq(tile.imgDiv, null, "Images against side of maxextent don't load");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-181,-91,180,90), url, size);
+ tile.draw()
+ var tParams = {
+ LAYERS: "basic", SERVICE: "WMS", VERSION: "1.1.1",
+ REQUEST: "GetMap", STYLES: "",
+ FORMAT: "image/jpeg",
+ SRS: "EPSG:4326", BBOX: "-181,-91,180,90",
+ WIDTH: "256", HEIGHT: "256"
+ };
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Images over edges of maxextent do load");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-181,-90,180,90), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-181,-90,180,90"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Images over edges of maxextent do load");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-180,-90,180,90), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-180,-90,180,90"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Image covering all of extent loads");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-80,-45,80,45), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-80,-45,80,45"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Image covering small part of extent loads");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-185,-95,185,95), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-185,-95,185,95"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Image covering more than all of extent loads");
+
+ layer.displayOutsideMaxExtent=1;
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-185,-90,-180,90), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-185,-90,-180,90"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Images against side of maxextent do load with displayOutsideMaxExtent");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-181,-90,180,90), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-181,-90,180,90"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Images over edges of maxextent do load with displayOutsideMaxExtent set");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-180,-90,180,90), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-180,-90,180,90"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Image covering all of extent loads with display outside max extent");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-80,-45,80,45), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-80,-45,80,45"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Image covering small part of extent loads with display outside max extent");
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-185,-95,185,95), url, size);
+ tile.draw()
+ tParams = OpenLayers.Util.extend(tParams, {BBOX: "-185,-95,185,95"});
+ t.eq(tile.url,
+ "../../img/blank.gif?" + OpenLayers.Util.getParameterString(tParams),
+ "Image covering more than all of extent loads");
+ }
+ function test_Tile_Image_Display_After_Move(t) {
+ t.plan(2);
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "http://www.openlayers.org/dev/tests/tileimage";
+ var size = new OpenLayers.Size(5,6);
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "../../img/blank.gif", {layers: 'basic'});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-90,-85,-90,85), url, size);
+ tile.draw();
+ tile.moveTo(new OpenLayers.Bounds(-185,-90,-180,-80), new OpenLayers.Pixel(-180,-85), true);
+ t.delay_call( 1, function() { t.ok(!tile.imgDiv, "Reference to tile image removed.") } );
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "../../img/blank.gif", {layers: 'basic'}, {'alpha':true});
+ map.addLayer(layer);
+ tile = new OpenLayers.Tile.Image(layer, position, new OpenLayers.Bounds(-90,-85,-90,85), url, size);
+ tile.draw();
+ tile.moveTo(new OpenLayers.Bounds(-185,-90,-180,-80), new OpenLayers.Pixel(-180,-85), true)
+ t.delay_call( 1, function() { t.ok(!tile.imgDiv, "Reference to alpha tile image removed.") } );
+
+ }
+
+ function test_Tile_Image_gutters(t) {
+ t.plan(5);
+
+ var gutter = 0;
+ var name = 'Test Layer';
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/png'};
+
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.WMS(name, url, params, {gutter: gutter});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ var tile = layer.grid[0][0];
+ t.ok(tile.layer.imageSize == null,
+ "zero size gutter doesn't set image size");
+
+ var zero_gutter_bounds = tile.bounds;
+
+ map.destroy();
+
+ var gutter = 15;
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.WMS(name, url, params, {gutter: gutter});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ t.ok(tile.layer.imageSize.equals(new OpenLayers.Size(tile.size.w + (2 * gutter),
+ tile.size.h + (2 * gutter))),
+ "gutter properly changes image size");
+
+ var offsetLeft = -(gutter / layer.tileSize.w * 100) | 0;
+ var offsetTop = -(gutter / layer.tileSize.h * 100) | 0;
+ t.eq(parseInt(tile.imgDiv.style.left, 10), offsetLeft,
+ "gutter properly sets image left style");
+ t.eq(parseInt(tile.imgDiv.style.top, 10), offsetTop,
+ "gutter properly sets image top style");
+ t.ok(tile.bounds.equals(zero_gutter_bounds),
+ "gutter doesn't affect tile bounds");
+
+ map.destroy();
+ }
+
+ function test_createBackBuffer(t) {
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "../../img/blank.gif", {layers: 'basic'});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+
+ // we're going to create a back buffer while the image
+ // is actually loading, so we call stopObservingElement
+ // to avoid any unexpected behavior
+ tile.isLoading = false;
+ OpenLayers.Event.stopObservingElement(tile.imgDiv);
+
+ var img = tile.imgDiv;
+ var left = img.style.left;
+ var bb = tile.createBackBuffer();
+ t.eq(bb.style.left, left, "backbuffer has same left style as frame");
+ t.ok(bb === img, "image appended to bb");
+ t.ok(tile.imgDiv == null, "image reference removed from tile");
+ map.destroy();
+ }
+
+ function test_onImageLoad(t) {
+ t.plan(3);
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "../../img/blank.gif", {layers: 'basic'}, {opacity: 0.5});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ var tile = layer.grid[0][0];
+
+ var log;
+ tile.events.on({loadend: function() { log++; }});
+
+ log = 0;
+ tile.onImageLoad();
+ t.eq(tile.imgDiv.style.visibility, 'inherit',
+ 'onImageLoad makes the image visible');
+ t.eq(parseFloat(tile.imgDiv.style.opacity), 0.5,
+ 'onImageLoad sets the expected opacity for the image');
+ t.eq(log, 1,
+ 'onImageLoad does trigger loadend');
+
+ map.destroy();
+ }
+
+ function test_getCanvasContext(t) {
+ if (!OpenLayers.CANVAS_SUPPORTED) {
+ t.plan(0);
+ } else {
+ t.plan(1);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("blank",
+ "../../img/blank.gif", {layers: 'fake'});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 5);
+
+ t.delay_call(5, function() {
+ var tile = layer.grid[0][0];
+ tile.onImageLoad();
+ t.ok(tile.getCanvasContext() instanceof CanvasRenderingContext2D,
+ "getCanvasContext() returns CanvasRenderingContext2D instance");
+ map.destroy();
+ });
+ }
+ }
+
+ /*
+ * A series of tests to verify the dimensions and positions
+ * of the tile frame and img after draw.
+
+ * Written for https://github.com/openlayers/openlayers/issues/441
+ */
+ function test_draw_without_gutter_without_frame(t) {
+ t.plan(5);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('blank',
+ '../../img/blank.gif',
+ {layers: 'fake'},
+ {isBaseLayer: true});
+ map.addLayer(layer);
+ var tile = new OpenLayers.Tile.Image(
+ layer,
+ new OpenLayers.Pixel(6, 6),
+ new OpenLayers.Bounds(5, 45, 6, 46),
+ null,
+ new OpenLayers.Size(256, 256));
+
+ tile.draw();
+ t.eq(tile.frame, null, 'no frame');
+ t.eq(parseInt(tile.imgDiv.style.left, 10), 6, 'correct tile img left');
+ t.eq(parseInt(tile.imgDiv.style.top, 10), 6, 'correct tile img top');
+ t.eq(parseInt(tile.imgDiv.style.width, 10), 256, 'correct tile img width');
+ t.eq(parseInt(tile.imgDiv.style.height, 10), 256, 'correct tile img height');
+
+ map.destroy();
+ }
+ function test_draw_without_gutter_with_frame(t) {
+ t.plan(8);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('blank',
+ '../../img/blank.gif',
+ {layers: 'fake'},
+ {isBaseLayer: true});
+ map.addLayer(layer);
+ layer.gutter = 1; // this is just for a frame to be created for
+ // the tile
+ var tile = new OpenLayers.Tile.Image(
+ layer,
+ new OpenLayers.Pixel(6, 6),
+ new OpenLayers.Bounds(5, 45, 6, 46),
+ null,
+ new OpenLayers.Size(256, 256));
+ layer.gutter = null;
+
+ tile.draw();
+ t.eq(parseInt(tile.frame.style.left, 10), 6, 'correct tile frame left');
+ t.eq(parseInt(tile.frame.style.top, 10), 6, 'correct tile frame top');
+ t.eq(parseInt(tile.frame.style.width, 10), 256, 'correct tile frame width');
+ t.eq(parseInt(tile.frame.style.height, 10), 256, 'correct tile frame height');
+ t.eq(parseInt(tile.imgDiv.style.left, 10), 0, 'correct tile img left');
+ t.eq(parseInt(tile.imgDiv.style.top, 10), 0, 'correct tile img top');
+ t.eq(parseInt(tile.imgDiv.style.width, 10), 100, 'correct tile img width');
+ t.eq(parseInt(tile.imgDiv.style.height, 10), 100, 'correct tile img height');
+
+ map.destroy();
+ }
+ function test_draw_with_gutter(t) {
+ t.plan(8);
+
+ var map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS('blank',
+ '../../img/blank.gif',
+ {layers: 'fake'},
+ {isBaseLayer: true, gutter: 15});
+ map.addLayer(layer);
+ var tile = new OpenLayers.Tile.Image(
+ layer,
+ new OpenLayers.Pixel(6, 6),
+ new OpenLayers.Bounds(5, 45, 6, 46),
+ null,
+ new OpenLayers.Size(256, 256));
+
+ tile.draw();
+ t.eq(parseInt(tile.frame.style.left, 10), 6, 'correct tile frame left');
+ t.eq(parseInt(tile.frame.style.top, 10), 6, 'correct tile frame top');
+ t.eq(parseInt(tile.frame.style.width, 10), 256, 'correct tile frame width');
+ t.eq(parseInt(tile.frame.style.height, 10), 256, 'correct tile frame height');
+ t.eq(parseInt(tile.imgDiv.style.left, 10), -5, 'correct tile img left');
+ t.eq(parseInt(tile.imgDiv.style.top, 10), -5, 'correct tile img top');
+ t.eq(parseInt(tile.imgDiv.style.width, 10), 111, 'correct tile img width');
+ t.eq(parseInt(tile.imgDiv.style.height, 10), 111, 'correct tile img height');
+
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="height:550px;width:500px"></div>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/Tile/Image/IFrame.html b/misc/openlayers/tests/Tile/Image/IFrame.html
new file mode 100644
index 0000000..192c7c6
--- /dev/null
+++ b/misc/openlayers/tests/Tile/Image/IFrame.html
@@ -0,0 +1,183 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script type="text/javascript">
+ // turn off animation frame handling, so we can check img urls in tests
+ delete OpenLayers.Layer.Grid.prototype.queueTileDraw;
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var isOpera = (navigator.userAgent.indexOf("Opera") != -1);
+ var isIElt9 = (parseFloat(navigator.appVersion.split("MSIE")[1]) < 9);
+
+ var map, layer;
+ var position = new OpenLayers.Pixel(20,30);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "http://www.openlayers.org/dev/tests/tileimage";
+ var size = new OpenLayers.Size(5,6);
+ var name = "OpenaLayers WMS";
+ var wmsUrl = "http://labs.metacarta.com/wms/vmap0?";
+
+ function test_Tile_Image_IFrame_create (t) {
+ t.plan( 3 );
+ map = new OpenLayers.Map('map', {tileManager: null});
+ var bar = new Array(205).join("1234567890");
+ layer = new OpenLayers.Layer.WMS(name, wmsUrl,
+ {layers: 'basic', foo: bar},
+ {tileOptions: {maxGetUrlLength: 2048},
+ transitionEffect: 'resize'});
+ map.addLayer(layer);
+
+ var tile = layer.addTile(bounds, position);
+
+ tile.draw();
+ t.eq(tile.imgDiv.nodeName.toLowerCase(), "iframe", "IFrame used for long URL");
+
+ layer.mergeNewParams({foo: null});
+ tile.draw();
+ t.eq(tile.imgDiv.nodeName.toLowerCase(), "img", "IMG used for short URL");
+
+ tile.maxGetUrlLength = 0;
+ tile.draw();
+ t.eq(tile.imgDiv.nodeName.toLowerCase(), "iframe", "IFrame used when maxGetUrlLength is 0");
+
+ tile.destroy();
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_Tile_Image_IFrame_clear (t) {
+ t.plan( 1 );
+
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, wmsUrl, {layers: 'basic'}, {tileOptions: {maxGetUrlLength: 0}});
+ map.addLayer(layer);
+ tile = layer.addTile(bounds, position);
+ tile.draw();
+ tile.clear();
+
+ t.eq(
+ tile.frame.getElementsByTagName("iframe").length, 0,
+ "IFrame removed on clear()");
+ tile.destroy();
+ layer.destroy();
+ map.destroy();
+ }
+
+ function test_Tile_Image_IFrame_initImage (t) {
+ t.plan( 2 );
+
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS(name, wmsUrl, {layers: 'basic'}, {tileOptions: {maxGetUrlLength: 0}});
+ map.addLayer(layer);
+ tile = layer.addTile(bounds, position);
+ tile.url = layer.getURL(bounds);
+ tile.initImage();
+
+ if(isMozilla) {
+ t.ok( tile.imgDiv instanceof HTMLElement, "tile.iFrame successfully created.");
+ }
+ else {
+ t.ok( tile.imgDiv != null, "tile.iFrame successfully created.");
+ }
+ t.eq( tile.imgDiv.className, "olTileImage", "iFrame's className correctly set.");
+
+ map.destroy();
+ }
+
+ function test_Tile_Image_IFrame_createImage (t) {
+ t.plan( 9 );
+
+ map = new OpenLayers.Map('map', {tileManager: null});
+ layer = new OpenLayers.Layer.WMS(name, wmsUrl, {layers: 'basic'}, {tileOptions: {maxGetUrlLength: 0}});
+ map.addLayer(layer);
+ var tile = layer.addTile(bounds, position);
+ tile.draw();
+ var iFrame = tile.imgDiv;
+ var eventPane = tile.frame.childNodes[0];
+
+ t.ok(OpenLayers.String.contains(eventPane.style.backgroundImage,
+ tile.blankImageUrl),
+ "backgroundImage of eventPane is set.");
+ t.eq(parseInt(eventPane.style.zIndex, 10), 1, "zIndex of eventPane is set.");
+ if(isIElt9) {
+ t.ok(iFrame != null, "IFrame successfully created.");
+ t.eq(iFrame.style.backgroundColor, '#ffffff', "backgroundColor correctly set.");
+ t.eq(iFrame.style.filter, 'chroma(color=#FFFFFF)', "filter correctly set.");
+ } else {
+ t.ok(iFrame instanceof HTMLElement, "IFrame successfully created.");
+ t.ok(true, 'Skip IFrame backgroundColor test outside IE < 9');
+ t.ok(true, 'Skip IFrame filter test outside IE < 9');
+ }
+ t.eq( iFrame.scrolling, 'no', "no scrolling");
+ t.eq( parseFloat(iFrame.marginWidth), 0, "no margin width");
+ t.eq( parseFloat(iFrame.marginHeight), 0, "no margin height");
+ t.eq( parseFloat(iFrame.frameBorder), 0, "no iframe border");
+
+ map.destroy();
+ }
+
+ function test_Tile_Image_IFrame_createRequestForm (t) {
+ t.plan( 6 );
+
+ var tParams = {
+ SERVICE: "WMS", VERSION: "1.1.1",
+ REQUEST: "GetMap", STYLES: "",
+ FORMAT: "image/jpeg",
+ SRS: "EPSG:4326", BBOX: [1,2,3,4],
+ WIDTH: String(size.w), HEIGHT: String(size.h)
+ };
+ var newLayer = new OpenLayers.Layer.WMS("Name",
+ "http://labs.metacarta.com/TESTURL",
+ tParams,
+ {tileSize: size, tileOptions: {maxGetUrlLength: 0}});
+ map = new OpenLayers.Map('map');
+ map.addLayer(newLayer);
+ tile = newLayer.addTile(bounds, position);
+ tile.url = newLayer.getURL(bounds);
+ tile.initImage();
+
+ tile.url = newLayer.getURL(bounds);
+ var form = tile.createRequestForm();
+ if(isMozilla) {
+ t.ok( form instanceof HTMLElement, "created html form successfully.");
+ }
+ else {
+ t.ok( form != null, "created html form successfully.");
+ }
+
+
+ var cacheId = newLayer.params["_OLSALT"];
+ cacheId = (cacheId ? cacheId + "_" : "") + tile.bounds.toBBOX();
+ var url = OpenLayers.Util.urlAppend(newLayer.url, cacheId);
+
+ t.eq( form.method.toLowerCase(), 'post', "form method correctly set.");
+ t.eq( form.target, tile.id+'_iFrame', "form target correctly set.");
+ t.eq( form.action, url, "form action correctly set.");
+
+ var formParams = {};
+ var children = form.childNodes;
+ for(var i=0; i<form.childNodes.length; i++) {
+ formParams[children[i].name] = children[i].value
+ }
+ newLayer.params.BBOX = newLayer.params.BBOX.join(",");
+ t.eq(newLayer.params, formParams, "html form elements equal layer's parameters.");
+
+ tile.draw();
+ tile.clear();
+ tile.initImage();
+ tile.createRequestForm();
+ t.ok(
+ tile.imgDiv.nodeName == "IFRAME",
+ "Iframe has been reinserted properly"
+ );
+
+ tile.destroy();
+ newLayer.destroy();
+ map.destroy();
+ }
+</script>
+</head>
+<body>
+<div id="map" style="height:550px;width:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Tile/UTFGrid.html b/misc/openlayers/tests/Tile/UTFGrid.html
new file mode 100644
index 0000000..4998adc
--- /dev/null
+++ b/misc/openlayers/tests/Tile/UTFGrid.html
@@ -0,0 +1,306 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <script>
+ /**
+ * Because browsers that implement requestAnimationFrame may not execute
+ * animation functions while a window is not displayed (e.g. in a hidden
+ * iframe as in these tests), we mask the native implementations here. The
+ * native requestAnimationFrame functionality is tested in Util.html and
+ * in PanZoom.html (where a popup is opened before panning). The panTo tests
+ * here will test the fallback setTimeout implementation for animation.
+ */
+ window.requestAnimationFrame =
+ window.webkitRequestAnimationFrame =
+ window.mozRequestAnimationFrame =
+ window.oRequestAnimationFrame =
+ window.msRequestAnimationFrame = null;
+ </script>
+ <script src="../OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var map, layer;
+ function setUp() {
+ layer = new OpenLayers.Layer.UTFGrid({
+ url: "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json",
+ isBaseLayer: true,
+ utfgridResolution: 4
+ });
+ map = new OpenLayers.Map({
+ div: "map",
+ projection: "EPSG:900913",
+ layers: [layer],
+ center: [0, 0],
+ zoom: 1,
+ tileManager: null
+ });
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ }
+
+ function test_constructor(t) {
+ t.plan(7);
+
+ setUp();
+
+ var position = new OpenLayers.Pixel(20, 30);
+ var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
+ var url = "http://example.com/";
+ var size = new OpenLayers.Size(5, 6);
+ var tile = new OpenLayers.Tile.UTFGrid(layer, position, bounds, url, size);
+
+ t.ok(tile instanceof OpenLayers.Tile, "tile instance");
+ t.ok(tile instanceof OpenLayers.Tile.UTFGrid, "UTFGrid tile instance");
+ t.ok(tile.layer === layer, "layer set");
+ t.ok(tile.position.equals(position), "position set");
+ t.ok(tile.bounds.equals(bounds), "bounds set");
+ t.eq(tile.url, url, "url set");
+ t.ok(tile.size.equals(size), "size set");
+
+ tearDown();
+ }
+
+ function test_parseData(t) {
+ t.plan(2);
+ setUp();
+
+ var tile = layer.grid[0][0];
+
+ tile.parseData('{"foo": "bar"}');
+ t.eq(tile.json, {foo: "bar"}, "valid json parsed");
+
+ var err, obj;
+ try {
+ obj = tile.parseData('foo bar');
+ } catch (e) {
+ err = e;
+ }
+ // The JSON format doesn't actually throw on IE6, so we also check
+ // for undefined here.
+ t.ok(err instanceof Error || obj === undefined, "throws on invalid json");
+
+ tearDown();
+ }
+
+ function test_draw(t) {
+ t.plan(7);
+ setUp();
+
+ var position = new OpenLayers.Pixel(20, 30);
+ var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
+ var url = "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json";
+ var size = new OpenLayers.Size(256, 256);
+ var tile = new OpenLayers.Tile.UTFGrid(layer, position, bounds, url, size);
+
+ var log = [];
+ function logger(event) {
+ log.push(event);
+ }
+ tile.events.on({
+ loadstart: logger,
+ reload: logger,
+ loadend: logger
+ });
+
+ t.eq(log.length, 0, "no events logged");
+
+ // start tile loading
+ tile.draw();
+ t.eq(log.length, 1, "[first draw] one event");
+ t.eq(log[0].type, "loadstart", "[first draw] loadstart");
+
+ // restart tile loading
+ log.length = 0;
+ tile.draw();
+ t.eq(log.length, 1, "[second draw] first event");
+ t.eq(log[0].type, "reload", "[second draw] reload");
+
+ // wait for tile loading to finish
+ t.delay_call(1, function() {
+ t.eq(log.length, 2, "[second draw] second event");
+ t.eq(log[1].type, "loadend", "[second draw] loadend");
+ tearDown();
+ });
+
+ }
+
+ function test_abortLoading(t) {
+ t.plan(7);
+ setUp();
+
+ var position = new OpenLayers.Pixel(20, 30);
+ var bounds = new OpenLayers.Bounds(1, 2, 3, 4);
+ var url = "../data/utfgrid/world_utfgrid/${z}/${x}/${y}.json";
+ var size = new OpenLayers.Size(256, 256);
+ var tile = new OpenLayers.Tile.UTFGrid(layer, position, bounds, url, size);
+
+ var log = [];
+ function logger(event) {
+ log.push(event);
+ }
+ tile.events.on({
+ loadstart: logger,
+ reload: logger,
+ loadend: logger
+ });
+
+ t.eq(log.length, 0, "no events logged");
+
+ // start tile loading
+ tile.draw();
+ t.eq(log.length, 1, "[first draw] one event");
+ t.eq(log[0].type, "loadstart", "[first draw] loadstart");
+
+ // abort tile loading
+ log.length = 0;
+ tile.abortLoading();
+ t.eq(log.length, 0, "[first abort] no events logged"); // TODO: does anybody need an abort event?
+
+ // abort again for the heck of it
+ var err;
+ try {
+ tile.abortLoading();
+ } catch (e) {
+ err = e;
+ }
+ t.ok(!err, "[second abort] no trouble");
+ t.eq(log.length, 0, "[second abort] no events");
+
+ // wait to confirm tile loading doesn't happen after abort
+ t.delay_call(1, function() {
+ t.eq(log.length, 0, "[wait] no events");
+ tearDown();
+ });
+
+ }
+
+ function test_getFeatureId(t) {
+ t.plan(3);
+ setUp();
+
+ var tile = layer.grid[1][1];
+ t.delay_call(0.5, function() {
+ var id = tile.getFeatureId(16, 60);
+ t.eq(id, "238", "feature 238 at 16, 60");
+ t.eq(tile.getFeatureId(18, 63), id, "same feature at 18, 63");
+
+ t.eq(tile.getFeatureId(300, 10), null, "null id outside tile");
+
+ tearDown();
+ });
+ }
+
+ function test_getFeatureInfo(t) {
+ t.plan(3);
+ setUp();
+
+ var tile = layer.grid[1][1];
+ t.delay_call(0.5, function() {
+ var info = tile.getFeatureInfo(16, 60);
+ var exp = {
+ id: "238",
+ data: {
+ NAME: "Svalbard",
+ POP2005: 0
+ }
+ };
+ t.eq(info, exp, "feature info at 16, 60");
+ t.eq(tile.getFeatureInfo(17, 62), exp, "same feature at 17, 62");
+
+ t.eq(tile.getFeatureInfo(300, 10), null, "undefined outside tile");
+
+ tearDown();
+ });
+ }
+
+ // While I dislike committing tests that aren't run, I'd like to make an
+ // exception here. This test (or something like it) should pass. When
+ // https://github.com/mapbox/utfgrid-spec/issues/1 is resolved, we should
+ // either modify this or update demo.json and enable the test.
+ function xtest_getFeatureId_demo(t) {
+ /**
+ * The UTFGrid 1.2 spec (https://github.com/mapbox/utfgrid-spec/blob/master/1.2/utfgrid.md)
+ * links to a demo.json to be used for testing implementations. This
+ * file is constructed with 256x256 data points. Each data point maps
+ * to a "feature id" using this heuristic:
+ *
+ * // x and y are pixel offsets from top left of 256x256 tile
+ * if (y < 255 || x < 222) {
+ * id = (y * 256) + x
+ * } else {
+ * id = 65501; // max number of ids that can be encoded
+ * }
+ */
+ t.plan(1);
+ setUp();
+
+ // look at this beauty of a constructor
+ var tile = new OpenLayers.Tile.UTFGrid(
+ layer, // layer
+ new OpenLayers.Pixel(0, 0), // position
+ new OpenLayers.Bounds(0, 0, 256, 256), // bounds
+ "../data/utfgrid/demo-1.1.json", // url
+ new OpenLayers.Size(256, 256), // size
+ {utfgridResolution: 1} // options
+ );
+
+ var err;
+ var request = new OpenLayers.Request.GET({
+ url: tile.url,
+ success: function(req) {
+ try {
+ tile.parseData(req.responseText);
+ } catch (e) {
+ err = e;
+ }
+ },
+ failure: function(req) {
+ err = new Error("Failed to fetch json. Status: " + req.status);
+ }
+ });
+
+ // wait for response and parsing, then make assertions
+ t.delay_call(1, function() {
+ if (err) {
+ t.fail(err);
+ } else {
+ var got, exp, failure;
+ outer: for (var y=0; y<256; ++y) {
+ for (var x=0; x<256; ++x) {
+ if (y<255 || x<222) {
+ exp = String((y * 256) + x);
+ } else {
+ exp = "65501";
+ }
+ got = tile.getFeatureId(x, y);
+ if (got !== exp) {
+ failure = "Failed to get id for (" + x + ", " + y + "): " +
+ "got " + got + " but expected " + exp;
+
+ break outer;
+ }
+ }
+ }
+ if (!failure) {
+ t.ok(true, "resolved feature ids for all data points");
+ } else {
+ t.fail(failure);
+ }
+ }
+ tearDown();
+ });
+
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="height:550px;width:500px"></div>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/TileManager.html b/misc/openlayers/tests/TileManager.html
new file mode 100644
index 0000000..23398be
--- /dev/null
+++ b/misc/openlayers/tests/TileManager.html
@@ -0,0 +1,137 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(4);
+
+ var tileManager = new OpenLayers.TileManager();
+ var map = new OpenLayers.Map('map', {
+ zoomMethod: null,
+ tileManager: tileManager
+ });
+ var layer = new OpenLayers.Layer.WMS('WMS1', '../img/blank.gif');
+ map.addLayer(layer);
+ map.setCenter([16, 48], 9);
+ t.ok(tileManager.tileQueue[map.id].length, "Tiles queued from layer");
+ map.removeLayer(layer);
+ t.eq(tileManager.tileQueue[map.id].length, 0, "Tiles unqueued when layer is removed");
+ map.addLayer(new OpenLayers.Layer.WMS('WMS2', '../img/blank.gif'));
+ map.zoomIn();
+ t.ok(tileManager.tileQueue[map.id].length, "Tiles queued from added layer");
+ map.destroy();
+ t.eq(tileManager.tileQueue[map.id], undefined, "Tile queue removed when map was destroyed");
+ }
+
+ function test_destroy(t) {
+ t.plan(3);
+
+ var tileManager = new OpenLayers.TileManager();
+ var map = new OpenLayers.Map('map', {tileManager: tileManager});
+ var layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
+ map.addLayer(layer);
+ map.setCenter([16, 48], 9);
+ var numTileListeners = layer.grid[0][0].events.listeners.beforeload.length;
+ var numLayerListeners = layer.events.listeners.retile.length;
+ var numMapListeners = map.events.listeners.preremovelayer.length;
+ tileManager.destroy();
+ t.eq(layer.grid[0][0].events.listeners.beforeload.length, numTileListeners - 1, "no listener on tile after destroy");
+ t.eq(layer.events.listeners.retile.length, numLayerListeners - 1, "no listeners on layer after destroy");
+ t.eq(map.events.listeners.preremovelayer.length, numMapListeners - 1, "no listeners on map after destroy");
+ map.destroy();
+ }
+
+ function test_manageTileCache(t) {
+ t.plan(10);
+
+ var tileManager = new OpenLayers.TileManager({
+ cacheSize: 12
+ });
+ var map = new OpenLayers.Map('map', {tileManager: tileManager});
+ layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
+ map.addLayer(layer);
+ map.setCenter([16, 48], 9);
+ var gridSize;
+
+ var firstInCache, sharedTile;
+ t.delay_call(2, function() {
+ t.eq(tileManager.tileCacheIndex.length, 12, "tiles cached");
+ t.ok(~OpenLayers.Util.indexOf(tileManager.tileCacheIndex, layer.grid[1][2].url), "tile found in cache");
+ t.ok(tileManager.tileCache[layer.grid[1][2].url] === layer.grid[1][2].imgDiv, "correct object cached");
+ firstInCache = tileManager.tileCache[tileManager.tileCacheIndex[0]];
+ sharedTile = tileManager.tileCache[tileManager.tileCacheIndex[11]];
+ gridSize = layer.div.childNodes.length;
+ map.setCenter([17, 47]);
+ });
+
+ function inCache(img) {
+ var search = img.src.split('?')[1];
+ for (var s in tileManager.tileCache) {
+ if (s.split('?')[1] == search) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ t.delay_call(4, function() {
+ t.eq(tileManager.tileCacheIndex.length, 12, "tiles cached");
+ t.ok(tileManager.tileCache[layer.grid[1][2].url] === layer.grid[1][2].imgDiv, "correct object cached");
+ t.ok(!inCache(firstInCache), "old tile discarded");
+ t.ok(inCache(sharedTile), "shared tile still in cache");
+ firstInCache = tileManager.tileCache[tileManager.tileCacheIndex[0]];
+ map.setCenter([16, 48]);
+ });
+ t.delay_call(6, function() {
+ t.ok(!inCache(firstInCache), "old tile discarded");
+ t.ok(inCache(sharedTile), "shared tile still in cache");
+ t.eq(layer.div.childNodes.length, gridSize, 'no unused images left in dom');
+ map.destroy();
+ });
+ }
+
+ function test_queueTileDraw(t) {
+ t.plan(3);
+
+ var tileManager = new OpenLayers.TileManager();
+ var map = new OpenLayers.Map('map', {tileManager: tileManager});
+ layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
+ map.addLayer(layer);
+ map.setCenter([0, 0], 3);
+ var queued = tileManager.tileQueue[map.id].length;
+ t.ok(tileManager.tileQueue[map.id].length, "Tiles queued for drawing");
+ map.zoomIn();
+ t.eq(tileManager.tileQueue[map.id].length, queued, "Tile queue has same length after immediate zoom change");
+ t.delay_call(1, function() {
+ t.eq(tileManager.tileQueue[map.id].length, 0, "Tiles from queue processed");
+ map.destroy();
+ });
+ }
+
+ function test_deferTileDraw(t) {
+
+ t.plan(3);
+
+ var tileManager = new OpenLayers.TileManager();
+ var map = new OpenLayers.Map('map', {tileManager: tileManager});
+ layer = new OpenLayers.Layer.WMS('WMS', '../img/blank.gif');
+ layer.destroy = function() {}; //we're going to do funky things with the grid
+ layer.applyBackBuffer = function() {}; // backbuffering isn't under test here
+ map.addLayer(layer);
+ map.setCenter([-10, 0], 5);
+
+ map.moveTo([5, 0]);
+ t.ok(tileManager.tileQueue[map.id].length, "tile loading deferred after moveTo");
+ map.moveTo([0, 0]);
+ t.ok(tileManager.tileQueue[map.id].length, "deferred again after another moveTo");
+ t.delay_call(1, function() {
+ t.eq(tileManager.tileQueue[map.id].length, 0, "tiles loaded after moveDelay");
+ });
+ }
+ </script>
+</head>
+<body>
+<div id="map" style="width:499px;height:549px;display:none"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Tween.html b/misc/openlayers/tests/Tween.html
new file mode 100644
index 0000000..1fbfa3c
--- /dev/null
+++ b/misc/openlayers/tests/Tween.html
@@ -0,0 +1,116 @@
+<html>
+<head>
+ <script>
+ /**
+ * Because browsers that implement requestAnimationFrame may not execute
+ * animation functions while a window is not displayed (e.g. in a hidden
+ * iframe as in these tests), we mask the native implementations here. The
+ * native requestAnimationFrame functionality is tested in Util.html and
+ * in PanZoom.html (where a popup is opened before panning). The panTo tests
+ * here will test the fallback setTimeout implementation for animation.
+ */
+ window.requestAnimationFrame =
+ window.webkitRequestAnimationFrame =
+ window.mozRequestAnimationFrame =
+ window.oRequestAnimationFrame =
+ window.msRequestAnimationFrame = null;
+ </script>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ function test_Tween_constructor(t) {
+ t.plan(3);
+
+ var tween = new OpenLayers.Tween();
+ t.ok(tween instanceof OpenLayers.Tween,
+ "new OpenLayers.Tween returns object" );
+ t.eq(typeof tween.easing, "function",
+ "constructor sets easing correctly");
+ t.eq(typeof tween.start, "function", "tween has a start function");
+ }
+
+ function test_Tween_start(t) {
+ t.plan(5);
+
+ var tween = new OpenLayers.Tween();
+
+ var start = {foo: 0, bar: 10};
+ var finish = {foo: 10, bar: 0};
+ var _start = false;
+ var _done = false;
+ var _eachStep = false;
+ var callbacks = {
+ start: function() {
+ _start = true;
+ },
+ done: function() {
+ _done = true;
+ },
+ eachStep: function() {
+ _eachStep = true;
+ }
+ }
+ tween.start(start, finish, 10, {callbacks: callbacks});
+ t.ok(tween.animationId != null, "animationId correctly set");
+ t.delay_call(0.8, function() {
+ t.eq(_start, true, "start callback called");
+ t.eq(_done, true, "finish callback called");
+ t.eq(_eachStep, true, "eachStep callback called");
+ t.eq(tween.time, 11, "Number of steps reached is correct");
+ });
+ }
+
+ function test_Tween_stop(t) {
+ t.plan(2);
+
+ var tween = new OpenLayers.Tween();
+ tween.animationId = OpenLayers.Animation.start(function() {});
+ tween.playing = true;
+ tween.stop();
+ t.eq(tween.animationId, null, "tween correctly stopped");
+
+ tween.animationId = OpenLayers.Animation.start(function() {});
+ tween.playing = false;
+ tween.stop();
+ t.ok(tween.animationId != null, "stop method doesn't do anything if tween isn't running");
+ }
+
+ function test_Tween_skip(t) {
+ t.plan(2);
+
+ var tween = new OpenLayers.Tween();
+ var log = 0;
+ tween.start({count: 0}, {count: 10}, 10, {
+ callbacks: {
+ eachStep: function() {
+ log++;
+ }
+ },
+ minFrameRate: 10000
+ });
+
+ t.delay_call(0.8, function() {
+ t.eq(log, 0, 'all frames skipped at a frame rate of 10000');
+
+ log = 0;
+ tween.start({count: 0}, {count: 10}, 10, {
+ callbacks: {
+ eachStep: function() {
+ log++;
+ }
+ },
+ minFrameRate: 1
+ });
+ });
+
+ t.delay_call(1.6, function() {
+ t.eq(log, 11, 'no frames skipped at a frame rate of 1');
+ });
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width:500px;height:500px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Util.html b/misc/openlayers/tests/Util.html
new file mode 100644
index 0000000..07fa5ed
--- /dev/null
+++ b/misc/openlayers/tests/Util.html
@@ -0,0 +1,1180 @@
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ <style>
+ body {
+ margin: 0;
+ padding: 0;
+ }
+ #map {
+ position: absolute;
+ top: 1234px;
+ left: 123px;
+ }
+ .test_getRenderedDimensions p{
+ padding: 20px;
+ }
+ </style>
+ <script>
+ var OpenLayers = [
+ "OpenLayers/BaseTypes/Class.js",
+ "OpenLayers/Util.js",
+ "OpenLayers/BaseTypes.js",
+ "OpenLayers/BaseTypes/Element.js",
+ "OpenLayers/BaseTypes/LonLat.js",
+ "OpenLayers/BaseTypes/Pixel.js",
+ "OpenLayers/BaseTypes/Size.js",
+ "OpenLayers/Lang.js",
+ "OpenLayers/Console.js"
+ ];
+ </script>
+ <script src="OLLoader.js"></script>
+ <script src="Util_common.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var map;
+
+ function test_isElement(t) {
+ t.plan(3);
+
+ // set up
+ var o;
+
+ // tests
+ o = {};
+ t.eq(OpenLayers.Util.isElement(o), false,
+ "isElement reports that {} isn't an Element");
+ o = document.createElement("div");
+ t.eq(OpenLayers.Util.isElement(o), true,
+ "isElement reports that object returned by createElement is an Element");
+ o = OpenLayers.Util.getElement("map");
+ t.eq(OpenLayers.Util.isElement(o), true,
+ "isElement reports that object returned by getElement is an Element");
+ }
+
+ function test_isArray(t) {
+ t.plan(5);
+
+ var a;
+
+ a = null;
+ t.eq(OpenLayers.Util.isArray(a), false,
+ "isArray reports 'null' isn't an Array");
+ a = "Array";
+ t.eq(OpenLayers.Util.isArray(a), false,
+ "isArray reports \"Array\" isn't an Array");
+ a = {};
+ t.eq(OpenLayers.Util.isArray(a), false,
+ "isArray reports {} isn't an Array");
+ a = [];
+ t.eq(OpenLayers.Util.isArray(a), true,
+ "isArray reports [] is an Array");
+ a = new Array();
+ t.eq(OpenLayers.Util.isArray(a), true,
+ "isArray reports new Array() is an Array");
+ }
+
+ function test_iframe_isArray(t) {
+ t.plan(3);
+ // create an array in an iframe
+ var iframe = document.createElement("iframe");
+ document.body.appendChild(iframe);
+ frames[frames.length-1].document.write(
+ "<script>parent.testArray = [];<\/script>"
+ );
+
+ t.ok(!!testArray, "testArray created");
+ t.ok(!(testArray instanceof Array), "instanceof check doesn't work");
+ t.eq(OpenLayers.Util.isArray(testArray), true, "isArray works");
+ }
+
+ function test_Util_getImagesLocation (t) {
+ t.plan( 1 );
+ t.ok( OpenLayers.Util.getImagesLocation(), "../img/",
+ "getImagesLocation()" );
+ }
+
+ function test_Util_IndexOf(t) {
+ t.plan( 3 );
+ var array = new Array(1, "bar");
+ t.eq(OpenLayers.Util.indexOf(array, 1), 0);
+ t.eq(OpenLayers.Util.indexOf(array, "bar"), 1);
+ t.eq(OpenLayers.Util.indexOf(array, "foo"), -1);
+ }
+
+ function test_Util_Array(t) {
+ t.plan( 2 );
+
+ var array = new Array(1,2,3,4,4,5);
+
+ OpenLayers.Util.removeItem(array, 3);
+ t.eq( array.toString(), "1,2,4,4,5", "Util.removeItem works on one element");
+ OpenLayers.Util.removeItem(array, 4);
+ t.eq( array.toString(), "1,2,5", "Util.removeItem works on more than one element ");
+ }
+
+ function test_Util_pagePosition(t) {
+ t.plan( 2 );
+
+ // making sure that the test iframe is visible
+ var origDisplay;
+ var parents = window.parent.document.getElementsByTagName('iframe');
+ if (parents.length) {
+ origDisplay = parents[1].parentNode.style.display;
+ // span containing the test iframe is the invisible element
+ parents[1].parentNode.style.display = "";
+ }
+
+ var pp = OpenLayers.Util.pagePosition(window);
+ t.eq( pp.toString(), "0,0", "Page position doesn't bail if passed 'window'");
+
+ var mapDiv = document.getElementById("map");
+ var beforeScrollPp = OpenLayers.Util.pagePosition(mapDiv);
+ window.scrollTo(100, 1200);
+ pp = OpenLayers.Util.pagePosition(mapDiv);
+ t.eq(pp, beforeScrollPp, "Page position should work after page has been scrolled");
+
+ // reset test iframe visibility
+ if (parents.length) {
+ parents[1].parentNode.style.display = origDisplay;
+ }
+ }
+
+ function test_Util_createDiv(t) {
+ t.plan( 24 );
+
+ var id = "boo";
+ var px = new OpenLayers.Pixel(5,5);
+ var sz = new OpenLayers.Size(10,10);
+ var img = "http://www.openlayers.org/images/OpenLayers.trac.png";
+ var position = "absolute";
+ var border = "13px solid";
+ var overflow = "hidden";
+ var opacity = 0.5;
+
+ var div = OpenLayers.Util.createDiv(id, px, sz, img, position, border, overflow, opacity);
+
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( div instanceof HTMLDivElement, "createDiv creates a valid HTMLDivElement" );
+ t.eq( div.id, id, "div.id set correctly");
+ t.eq( div.style.left, px.x + "px", "div.style.left set correctly");
+ t.eq( div.style.top, px.y + "px", "div.style.top set correctly");
+
+ t.eq( div.style.width, sz.w + "px", "div.style.width set correctly");
+ t.eq( div.style.height, sz.h + "px", "div.style.height set correctly");
+
+ bImg = div.style.backgroundImage;
+ imgCorrect = ( (bImg == "url(" + img + ")") ||
+ (bImg == "url(\"" + img + "\")") );
+ t.ok(imgCorrect, "div.style.backgroundImage correctly");
+
+ t.eq( div.style.position, position, "div.style.positionset correctly");
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ var s = border.split(' ');
+ t.ok(div.style.borderTopWidth == s[0] && div.style.borderTopStyle == s[1], "good default popup.border")
+ } else {
+ t.ok( (div.style.border.indexOf(border) != -1), "div.style.border set correctly");
+ }
+
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq( div.style[prop], overflow, "div.style.overflow set correctly");
+ t.eq( parseFloat(div.style.opacity), opacity, "element.style.opacity set correctly");
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = div.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : div.style.filter;
+ t.eq( div.style.filter, filterString, "element.style.filter set correctly");
+
+ //test defaults
+ var div = OpenLayers.Util.createDiv();
+
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( div instanceof HTMLDivElement, "createDiv creates a valid HTMLDivElement" );
+ t.ok( (div.id != ""), "div.id set correctly");
+ t.eq(div.style.left, "", "div.style.left set correctly");
+ t.eq(div.style.top, "", "div.style.top set correctly");
+
+ t.eq( div.style.width, "", "div.style.width set correctly");
+ t.eq( div.style.height, "", "div.style.height set correctly");
+
+ t.eq(div.style.backgroundImage, "", "div.style.backgroundImage correctly");
+
+ t.eq( div.style.position, "absolute", "div.style.positionset correctly");
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ t.ok(div.style.borderTopWidth == '' && div.style.borderTopStyle == '', "good default popup.border")
+ } else {
+ t.eq( div.style.border, "", "div.style.border set correctly");
+ }
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq(div.style[prop], "", "div.style.overflow set correctly");
+ t.ok( !div.style.opacity, "element.style.opacity set correctly");
+ t.ok( !div.style.filter, "element.style.filter set correctly");
+
+ }
+
+ function test_Util_createImage(t) {
+ t.plan( 22 );
+
+ var img = "http://www.openlayers.org/images/OpenLayers.trac.png";
+ var sz = new OpenLayers.Size(10,10);
+ var xy = new OpenLayers.Pixel(5,5);
+ var position = "absolute";
+ var id = "boo";
+ var border = "1px solid";
+ var opacity = 0.5;
+
+ var image = OpenLayers.Util.createImage(id, xy, sz, img, position, border, opacity);
+
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( image.nodeName == "IMG", "createImage creates a valid HTMLImageElement" );
+ t.eq( image.id, id, "image.id set correctly");
+ t.eq( image.style.left, xy.x + "px", "image.style.left set correctly");
+ t.eq( image.style.top, xy.y + "px", "image.style.top set correctly");
+
+ t.eq( image.style.width, sz.w + "px", "image.style.width set correctly");
+ t.eq( image.style.height, sz.h + "px", "image.style.height set correctly");
+
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ var s = border.split(' ');
+ t.ok(image.style.borderTopWidth == s[0] && image.style.borderTopStyle == s[1], "good default popup.border")
+ } else {
+ t.ok( (image.style.border.indexOf(border) != -1), "image.style.border set correctly");
+ }
+ t.eq( image.src, img, "image.style.backgroundImage correctly");
+ t.eq( image.style.position, position, "image.style.position set correctly");
+ t.eq( parseFloat(image.style.opacity), opacity, "image.style.opacity set correctly");
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = image.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : image.style.filter;
+ t.eq( image.style.filter, filterString, "element.style.filter set correctly");
+
+ //test defaults
+ var image = OpenLayers.Util.createImage();
+
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( image.nodeName == "IMG", "createDiv creates a valid HTMLDivElement" );
+ t.ok( (image.id != ""), "image.id set to something");
+ t.eq( image.style.left, "", "image.style.left set correctly");
+ t.eq( image.style.top, "", "image.style.top set correctly");
+
+ t.eq( image.style.width, "", "image.style.width set correctly");
+ t.eq( image.style.height, "", "image.style.height set correctly");
+
+ t.ok((image.style.border == ""), "image.style.border set correctly");
+ t.eq(image.src, "", "image.style.backgroundImage correctly");
+ t.eq( image.style.position, "relative", "image.style.positionset correctly");
+ t.ok( !image.style.opacity, "element.style.opacity default unset");
+ t.ok( !image.style.filter, "element.style.filter default unset");
+
+ }
+
+ function test_Util_applyDefaults(t) {
+
+ t.plan(12);
+
+ var to = {
+ 'a': "abra",
+ 'b': "blorg",
+ 'n': null
+ };
+
+ var from = {
+ 'b': "zoink",
+ 'c': "press",
+ 'toString': function() {return 'works'},
+ 'n': "broken"
+ };
+
+ OpenLayers.Util.applyDefaults(to, from);
+
+ t.ok( to instanceof Object, " applyDefaults returns an object");
+ t.eq( to["a"], "abra", "key present in to but not from maintained");
+ t.eq( to["b"], "blorg", "key present in to and from, maintained in to");
+ t.eq( to["c"], "press", "key present in from and not to successfully copied to to");
+
+ var ret = OpenLayers.Util.applyDefaults({'a': "abra",'b': "blorg"}, from);
+ t.ok( ret instanceof Object, " applyDefaults returns an object");
+ t.eq( ret["a"], "abra", "key present in ret but not from maintained");
+ t.eq( ret["b"], "blorg", "key present in ret and from, maintained in ret");
+ t.eq( ret["c"], "press", "key present in from and not ret successfully copied to ret");
+ t.eq(to.toString(), "works", "correctly applies custom toString");
+ t.eq(to.n, null, "correctly preserves null");
+
+ var to;
+ var from = {rand: Math.random()};
+
+ var ret = OpenLayers.Util.applyDefaults(to, from);
+ t.eq(ret.rand, from.rand, "works with undefined to");
+
+ //regression test for #1716 -- allow undefined from
+ try {
+ OpenLayers.Util.applyDefaults({}, undefined);
+ t.ok(true, "no exception thrown when from is undefined");
+ } catch(err) {
+ t.fail("exception thrown when from is undefined:" + err);
+ }
+
+ }
+
+ function test_Util_getParameterString(t) {
+ t.plan(6);
+
+ var params = {
+ 'foo': "bar",
+ 'chicken': 1.5
+ };
+
+ t.eq( OpenLayers.Util.getParameterString(params), "foo=bar&chicken=1.5", "getParameterString returns correctly");
+ t.eq( OpenLayers.Util.getParameterString({'a:':'b='}), "a%3A=b%3D", "getParameterString returns correctly with non-ascii keys/values");
+
+ t.eq(OpenLayers.Util.getParameterString({chars: "~!*()'"}), "chars=~!*()'", "~!*()' are unreserved or have no reserved purpose in a URI component");
+
+
+ // Parameters which are a list should end up being a comma-seperated
+ // list of the URL encoded strings
+ var params = { foo: ["bar,baz"] };
+ t.eq( OpenLayers.Util.getParameterString(params), "foo=bar%2Cbaz", "getParameterString encodes , correctly in arrays");
+
+ var params = { foo: ["bar","baz,"] };
+ t.eq( OpenLayers.Util.getParameterString(params), "foo=bar,baz%2C", "getParameterString returns with list of CSVs when given a list. ");
+
+ var params = { foo: [null, undefined, 0, "", "bar"] }
+ t.eq( OpenLayers.Util.getParameterString(params), "foo=,,0,,bar", "getParameterString works fine with null values in array.");
+ }
+
+ function test_Util_urlAppend(t) {
+
+ var params = "foo=bar";
+
+ t.plan( 7 );
+
+ // without ?
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var str = OpenLayers.Util.urlAppend(url, params);
+ t.eq(str, url + '?' + params, "urlAppend() works for url sans ?");
+
+
+ // with ?
+ url = "http://octo.metacarta.com/cgi-bin/mapserv?";
+ str = OpenLayers.Util.urlAppend(url, params);
+ t.eq(str, url + params, "urlAppend() works for url with ?");
+
+ // with ?param1=5
+ url = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5";
+ str = OpenLayers.Util.urlAppend(url, params);
+ t.eq(str, url + '&' + params, "urlAppend() works for url with ?param1=5");
+
+ // with ?param1=5&
+ url = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5&";
+ str = OpenLayers.Util.urlAppend(url, params);
+ t.eq(str, url + params, "urlAppend() works for url with ?param1=5&");
+
+ // with ?param1=5&param2=6
+ url = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5&param2=6";
+ str = OpenLayers.Util.urlAppend(url, params);
+ t.eq(str, url + "&" + params, "urlAppend() works for url with ?param1=5&param2=6");
+
+ // with empty paramStr
+ url = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5"
+ str = OpenLayers.Util.urlAppend(url, "");
+ t.eq(str, url, "urlAppend() works with empty paramStr")
+
+ // with null paramStr
+ url = "http://octo.metacarta.com/cgi-bin/mapserv?param1=5"
+ str = OpenLayers.Util.urlAppend(url, null);
+ t.eq(str, url, "urlAppend() works with null paramStr")
+ }
+
+ function test_Util_createAlphaImageDiv(t) {
+ t.plan( 19 );
+
+ var img = "http://www.openlayers.org/images/OpenLayers.trac.png";
+ var sz = new OpenLayers.Size(10,10);
+ var xy = new OpenLayers.Pixel(5,5);
+ var position = "absolute";
+ var id = "boo";
+ var border = "1px solid";
+ var sizing = "crop";
+ var opacity = 0.5;
+
+ var imageDiv = OpenLayers.Util.createAlphaImageDiv(id, xy, sz, img, position, border, sizing, opacity);
+
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( imageDiv instanceof HTMLDivElement, "createDiv creates a valid HTMLDivElement" );
+
+ t.eq( imageDiv.id, id, "image.id set correctly");
+ t.eq( imageDiv.style.left, xy.x + "px", "image.style.left set correctly");
+ t.eq( imageDiv.style.top, xy.y + "px", "image.style.top set correctly");
+
+ t.eq( imageDiv.style.width, sz.w + "px", "image.style.width set correctly");
+ t.eq( imageDiv.style.height, sz.h + "px", "image.style.height set correctly");
+
+ t.eq( imageDiv.style.position, position, "image.style.positionset correctly");
+ t.eq( parseFloat(imageDiv.style.opacity), opacity, "element.style.opacity set correctly");
+
+ var filterString;
+ if (OpenLayers.Util.alphaHack()) {
+ filterString = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://www.openlayers.org/images/OpenLayers.trac.png', sizingMethod='crop') alpha(opacity=50)";
+ } else {
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = imageDiv.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : imageDiv.style.filter;
+ }
+ t.eq( imageDiv.style.filter, filterString, "element.style.filter set correctly");
+
+
+ image = imageDiv.firstChild;
+ if (!isMozilla)
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( image.nodeName == "IMG", "createImage creates a valid HTMLImageElement" );
+ t.eq( image.id, id + "_innerImage", "image.id set correctly");
+
+ t.eq( image.style.width, sz.w + "px", "image.style.width set correctly");
+ t.eq( image.style.height, sz.h + "px", "image.style.height set correctly");
+
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ var s = border.split(' ');
+ t.ok(image.style.borderTopWidth == s[0] && image.style.borderTopStyle == s[1], "good default popup.border")
+ } else {
+ t.ok( (image.style.border.indexOf(border) != -1), "image.style.border set correctly");
+ }
+
+ t.eq( image.style.position, "relative", "image.style.positionset correctly");
+
+ if (OpenLayers.Util.alphaHack()) {
+
+ t.eq(imageDiv.style.display, "inline-block", "imageDiv.style.display set correctly");
+
+ var filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img + "', " +
+ "sizingMethod='" + sizing + "') alpha(opacity=50)";
+ t.eq(imageDiv.style.filter, filter, "div filter value correctly set");
+
+ filter = "alpha(opacity=0)";
+ t.eq(image.style.filter, filter, "image filter set correctly");
+
+ } else {
+ t.eq( image.src, img, "image.style.backgroundImage correctly");
+ t.ok(true, "div filter value not set (not in IE)");
+ t.ok(true, "image filter value not set (not in IE)");
+ }
+
+ var imageDiv = OpenLayers.Util.createAlphaImageDiv(id, xy, sz, img, position, border);
+ if (OpenLayers.Util.alphaHack()) {
+ var filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img + "', " +
+ "sizingMethod='scale')";
+ t.eq(imageDiv.style.filter, filter, "sizingMethod default correctly set to scale");
+ } else {
+ t.ok(true);
+ }
+
+ }
+
+ function test_Util_modifyDOMElement_opacity(t) {
+ t.plan(8);
+
+ var opacity = 0.2;
+
+ var element = document.createElement("div");
+
+ OpenLayers.Util.modifyDOMElement(element, null, null, null, null,
+ null, null, opacity);
+
+ t.eq(parseFloat(element.style.opacity), opacity,
+ "element.style.opacity set correctly when opacity = " + opacity);
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = element.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : element.style.filter;
+ t.eq(element.style.filter, filterString,
+ "element.style.filter set correctly when opacity = " + opacity);
+
+ OpenLayers.Util.modifyDOMElement(element, null, null, null, null,
+ null, null, "5");
+
+ t.eq(parseFloat(element.style.opacity), opacity,
+ "element.style.opacity not changed if the value is incorrect");
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = element.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : element.style.filter;
+ t.eq(element.style.filter, filterString,
+ "element.style.filter not changed if the value is incorrect");
+
+ OpenLayers.Util.modifyDOMElement(element, null, null, null, null,
+ null, null, "hello");
+
+ t.eq(parseFloat(element.style.opacity), opacity,
+ "element.style.opacity not changed if the value is incorrect");
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = element.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : element.style.filter;
+ t.eq(element.style.filter, filterString,
+ "element.style.filter not changed if the value is incorrect");
+
+ opacity = 1.00;
+ OpenLayers.Util.modifyDOMElement(element, null, null, null, null,
+ null, null, opacity);
+
+ t.eq(element.style.opacity, '',
+ "element.style.opacity is removed when opacity = " + opacity);
+ // Some browser returns null instead of '', which is okay
+ t.ok(element.style.filter == '' || element.style.filter == null,
+ "element.style.filter is removed when opacity = " + opacity);
+ }
+
+ function test_Util_modifyDOMElement(t) {
+ t.plan( 10 );
+
+ var id = "boo";
+ var px = new OpenLayers.Pixel(5,5);
+ var sz = new OpenLayers.Size(10,10);
+ var position = "absolute";
+ var border = "1px solid";
+ var overflow = "hidden";
+ var opacity = 1/2;
+
+ var element = document.createElement("div");
+
+ OpenLayers.Util.modifyDOMElement(element, id, px, sz, position,
+ border, overflow, opacity);
+
+ t.eq( element.id, id, "element.id set correctly");
+ t.eq( element.style.left, px.x + "px", "element.style.left set correctly");
+ t.eq( element.style.top, px.y + "px", "element.style.top set correctly");
+
+ t.eq( element.style.width, sz.w + "px", "element.style.width set correctly");
+ t.eq( element.style.height, sz.h + "px", "element.style.height set correctly");
+
+ t.eq( element.style.position, position, "element.style.position set correctly");
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ var s = border.split(' ');
+ t.ok(element.style.borderTopWidth == s[0] && element.style.borderTopStyle == s[1], "good default popup.border")
+ } else {
+ t.ok( (element.style.border.indexOf(border) != -1), "element.style.border set correctly");
+ }
+ //Safari 3 separates style overflow into overflow-x and overflow-y
+ var prop = (OpenLayers.BROWSER_NAME == 'safari') ? 'overflowX' : 'overflow';
+ t.eq( element.style[prop], overflow, "element.style.overflow set correctly");
+ t.eq( parseFloat(element.style.opacity), opacity, "element.style.opacity set correctly");
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = element.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : element.style.filter;
+ t.eq( element.style.filter, filterString, "element.style.filter set correctly");
+ }
+
+ function test_Util_modifyAlphaImageDiv(t) {
+ t.plan( 21 );
+
+ var imageDiv = OpenLayers.Util.createAlphaImageDiv();
+
+ var img = "http://www.openlayers.org/images/OpenLayers.trac.png";
+ var sz = new OpenLayers.Size(10,10);
+ var xy = new OpenLayers.Pixel(5,5);
+ var position = "absolute";
+ var id = "boo";
+ var border = "1px solid";
+ var sizing = "crop";
+ var opacity = 0.5;
+
+ OpenLayers.Util.modifyAlphaImageDiv(imageDiv, id, xy, sz, img, position, border, sizing, opacity);
+ if (OpenLayers.Util.alphaHack())
+ t.ok( true, "skipping element test outside of Mozilla");
+ else
+ t.ok( imageDiv.nodeName == "DIV", "createDiv creates a valid HTMLDivElement" );
+
+ t.eq( imageDiv.id, id, "image.id set correctly");
+ t.eq( imageDiv.style.left, xy.x + "px", "image.style.left set correctly");
+ t.eq( imageDiv.style.top, xy.y + "px", "image.style.top set correctly");
+
+ t.eq( imageDiv.style.width, sz.w + "px", "image.style.width set correctly");
+ t.eq( imageDiv.style.height, sz.h + "px", "image.style.height set correctly");
+
+ t.eq( imageDiv.style.position, position, "image.style.position set correctly");
+ t.eq( parseFloat(imageDiv.style.opacity), opacity, "element.style.opacity set correctly");
+
+
+
+ image = imageDiv.firstChild;
+
+ var filterString;
+ if (OpenLayers.Util.alphaHack()) {
+ filterString = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://www.openlayers.org/images/OpenLayers.trac.png', sizingMethod='crop') alpha(opacity=50)";
+ t.ok( true, "skipping element test outside of Mozilla");
+ } else {
+ //Some non-IE browsers don't return the alpha string for this value, which is okay
+ var filterString = imageDiv.style.filter.match(/^alpha/) != null ?
+ 'alpha(opacity=' + (opacity * 100) + ')' : imageDiv.style.filter;
+ t.ok( image.nodeName == "IMG", "createImage creates a valid HTMLImageElement" );
+ }
+ t.eq( imageDiv.style.filter, filterString, "element.style.filter set correctly");
+ t.eq( image.id, id + "_innerImage", "image.id set correctly");
+
+ t.eq( image.style.width, sz.w + "px", "image.style.width set correctly");
+ t.eq( image.style.height, sz.h + "px", "image.style.height set correctly");
+
+ //Safari 3 separates the border style into separate entities when reading it
+ if (OpenLayers.BROWSER_NAME == 'safari') {
+ var s = border.split(' ');
+ t.ok(image.style.borderTopWidth == s[0] && image.style.borderTopStyle == s[1], "good default popup.border")
+ } else {
+ t.ok( (image.style.border.indexOf(border) != -1), "image.style.border set correctly");
+ }
+
+ t.eq( image.style.position, "relative", "image.style.positionset correctly");
+ t.eq( image.src, img, "image.style.backgroundImage correctly");
+
+ if (OpenLayers.Util.alphaHack()) {
+
+ var filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img + "', " +
+ "sizingMethod='" + sizing + "') alpha(opacity=" + opacity *100 + ")";
+ t.eq(imageDiv.style.filter, filter, "div filter value correctly set");
+
+ filter = "alpha(opacity=0)";
+ t.eq(image.style.filter, filter, "image filter set correctly");
+
+ } else {
+ t.ok(true, "div filter value not set (not in IE)");
+ t.ok(true, "image filter value not set (not in IE)");
+ }
+
+ var imageDiv = OpenLayers.Util.createAlphaImageDiv();
+ var display = "none";
+ imageDiv.style.display = display;
+ OpenLayers.Util.modifyAlphaImageDiv(imageDiv, id, xy, sz, img, position, border, sizing, opacity);
+ t.eq(imageDiv.style.display, display, "imageDiv.style.display set correctly, if 'none'");
+
+ var imageDiv = OpenLayers.Util.createAlphaImageDiv();
+ var display = "block";
+ imageDiv.style.display = display;
+ OpenLayers.Util.modifyAlphaImageDiv(imageDiv, id, xy, sz, img, position, border, sizing, opacity);
+ if(OpenLayers.Util.alphaHack()) {
+ t.eq(imageDiv.style.display, "inline-block", "imageDiv.style.display set correctly, if not 'none'");
+ } else {
+ t.ok(true, "inline-block is not part of CSS2 and is not supported by Firefox 2");
+ }
+
+
+
+ var imageDiv = OpenLayers.Util.createAlphaImageDiv(id, xy, sz, img, position, border, "scale", opacity);
+ if (OpenLayers.Util.alphaHack()) {
+ var filter = "progid:DXImageTransform.Microsoft" +
+ ".AlphaImageLoader(src='" + img + "', " +
+ "sizingMethod='scale') alpha(opacity=" + opacity *100 + ")";
+ t.eq(imageDiv.style.filter, filter, "sizingMethod default correctly set to scale");
+ } else {
+ t.ok(true);
+ }
+
+ }
+
+ function test_Util_upperCaseObject(t) {
+ t.plan(8);
+
+ var aKey = "chicken";
+ var aValue = "pot pie";
+
+ var bKey = "blorg";
+ var bValue = "us maximus";
+
+ var obj = {};
+ obj[aKey] = aValue;
+ obj[bKey] = bValue;
+
+ var uObj = OpenLayers.Util.upperCaseObject(obj);
+
+ //make sure old object not modified
+ t.eq(obj[aKey], aValue, "old lowercase value still present in old obj");
+ t.eq(obj[bKey], bValue, "old lowercase value still present in old obj");
+
+ t.eq(obj[aKey.toUpperCase()], null, "new uppercase value not present in old obj");
+ t.eq(obj[bKey.toUpperCase()], null, "new uppercase value not present in old obj");
+
+ //make sure new object modified
+ t.eq(uObj[aKey], null, "old lowercase value not present");
+ t.eq(uObj[bKey], null, "old lowercase value not present");
+
+ t.eq(uObj[aKey.toUpperCase()], aValue, "new uppercase value present");
+ t.eq(uObj[bKey.toUpperCase()], bValue, "new uppercase value present");
+ }
+
+ function test_Util_createUniqueID(t) {
+ t.plan(2);
+
+ var id = OpenLayers.Util.createUniqueID();
+ t.ok(OpenLayers.String.startsWith(id, "id_"),
+ "default OpenLayers.Util.createUniqueID starts id correctly");
+
+ var id = OpenLayers.Util.createUniqueID("chicken");
+ t.ok(OpenLayers.String.startsWith(id, "chicken"),
+ "OpenLayers.Util.createUniqueID starts id correctly");
+ }
+
+ function test_units(t) {
+ t.plan(2);
+ t.eq(OpenLayers.INCHES_PER_UNIT.m, OpenLayers.INCHES_PER_UNIT.Meter, 'Same inches per m and Meters');
+ t.eq(OpenLayers.INCHES_PER_UNIT.km, OpenLayers.INCHES_PER_UNIT.Kilometer, 'Same inches per km and Kilometers');
+ }
+
+ function test_Util_normalizeScale(t) {
+ t.plan(2);
+
+ //normal scale
+ var scale = 1/5;
+ t.eq( OpenLayers.Util.normalizeScale(scale), scale, "normalizing a normal scale does nothing");
+
+ //funky scale
+ var scale = 5;
+ t.eq( OpenLayers.Util.normalizeScale(scale), 1/5, "normalizing a wrong scale works!");
+ }
+
+ function test_Util_getScaleResolutionTranslation(t) {
+ t.plan(5);
+
+ var scale = 1/150000000;
+ var resolution = OpenLayers.Util.getResolutionFromScale(scale);
+ t.eq(resolution.toFixed(6), "0.476217", "Calculated correct resolution for " + scale);
+
+ var scale = 1/150000000;
+ var resolution = OpenLayers.Util.getResolutionFromScale(scale, 'm');
+ t.eq(resolution.toFixed(6), "52916.772500", "Calculated correct resolution for " + scale);
+
+ scale = 150000000;
+ resolution = OpenLayers.Util.getResolutionFromScale(scale);
+ t.eq(resolution.toFixed(6), "0.476217", "Calculated correct resolution for " + scale);
+
+ scale = 150000000;
+ resolution = OpenLayers.Util.getResolutionFromScale(scale);
+ t.eq(OpenLayers.Util.getScaleFromResolution(resolution), scale, "scale->resolution->scale works");
+
+ scale = null;
+ resolution = OpenLayers.Util.getResolutionFromScale(scale);
+ t.eq(resolution, undefined, "falsey scale results in undefined resolution");
+
+ }
+
+ function test_Util_getImgLocation(t) {
+ t.plan(3);
+
+ OpenLayers.ImgPath = "foo/";
+ t.eq(OpenLayers.Util.getImagesLocation(), "foo/", "ImgPath works as expected.");
+ OpenLayers.ImgPath = null;
+ t.eq(OpenLayers.Util.getImagesLocation().substr(OpenLayers.Util.getImagesLocation().length-4,4), "img/", "ImgPath works as expected when not set.");
+
+ OpenLayers.ImgPath = '';
+ t.eq(OpenLayers.Util.getImagesLocation().substr(OpenLayers.Util.getImagesLocation().length-4,4), "img/", "ImgPath works as expected when set to ''.");
+ }
+
+ function test_Util_isEquivalentUrl(t) {
+ t.plan(10);
+
+ var url1, url2, options;
+
+ //CASE
+
+ url1 = "http://www.openlayers.org";
+ url2 = "HTTP://WWW.OPENLAYERS.ORG";
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "default ignoreCase works");
+
+ //ARGS
+
+ url1 = "http://www.openlayers.org?foo=5;bar=6";
+ url2 = "http://www.openlayers.org?bar=6;foo=5";
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "shuffled arguments works");
+
+ //PORT
+
+ url1 = "http://www.openlayers.org:80";
+ url2 = "http://www.openlayers.org";
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "default ignorePort80 works");
+
+ options = {
+ 'ignorePort80': false
+ }
+ url1 = "http://www.openlayers.org:80";
+ url2 = "http://www.openlayers.org:50";
+
+ t.ok(!OpenLayers.Util.isEquivalentUrl(url1, url2, options), "port check works");
+
+
+ //HASH
+
+ url1 = "http://www.openlayers.org#barf";
+ url2 = "http://www.openlayers.org";
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "default ignoreHash works");
+ options = {
+ 'ignoreHash': false
+ }
+ t.ok(!OpenLayers.Util.isEquivalentUrl(url1, url2, options), "ignoreHash FALSE works");
+
+ //PROTOCOL
+
+ url1 = "http://www.openlayers.org";
+ url2 = "ftp://www.openlayers.org";
+
+ t.ok(!OpenLayers.Util.isEquivalentUrl(url1, url2), "default ignoreHash works");
+
+
+ //PATHNAME
+ url1 = "foo.html?bar=now#go";
+ url2 = "../tests/../tests/foo.html?bar=now#go";
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "relative vs. absolute paths works");
+
+ url1 = "/foo/bar";
+ url2 = new Array(window.location.pathname.split("/").length-1).join("../")+"foo/bar";
+
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "absolute and relative path without host works for "+url2)
+
+ //ARGS
+ url1 = "foo.html?bbox=1,2,3,4",
+ url2 = url1;
+ t.ok(OpenLayers.Util.isEquivalentUrl(url1, url2), "equal urls with comma delimited params are equal");
+ }
+
+ function test_createUrlObject(t) {
+
+ var cases = [{
+ url: "http://example.com/",
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "80",
+ pathname: "/",
+ args: {},
+ hash: ""
+ }
+ }, {
+ url: "http://example.com:80/",
+ opt: {ignorePort80: true},
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "",
+ pathname: "/",
+ args: {},
+ hash: ""
+ }
+ }, {
+ url: "http://example.com/",
+ opt: {ignorePort80: true},
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "",
+ pathname: "/",
+ args: {},
+ hash: ""
+ }
+ }, {
+ url: "http://example.com:88/",
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "88",
+ pathname: "/",
+ args: {},
+ hash: ""
+ }
+ }, {
+ url: "http://example.com:88/foo#bar",
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "88",
+ pathname: "/foo",
+ args: {},
+ hash: "#bar"
+ }
+ }, {
+ url: "http://example.com:88/?foo=bar",
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "88",
+ pathname: "/",
+ args: {foo: "bar"},
+ hash: ""
+ }
+ }, {
+ url: "http://example.com/bogus/../bogus/../path",
+ exp: {
+ protocol: "http:",
+ host: "example.com",
+ port: "80",
+ pathname: "/path",
+ args: {},
+ hash: ""
+ }
+ }, {
+ url: "/relative#foo",
+ exp: {
+ protocol: window.location.protocol,
+ host: window.location.hostname,
+ port: window.location.port || "80",
+ pathname: "/relative",
+ args: {},
+ hash: "#foo"
+ }
+ }, {
+ url: "../foo",
+ exp: {
+ protocol: window.location.protocol,
+ host: window.location.hostname,
+ port: window.location.port || "80",
+ pathname: (function() {
+ var parts = window.location.pathname.split("/");
+ return parts.slice(0, parts.length -2).join("/") + "/foo";
+ })(),
+ args: {},
+ hash: ""
+ }
+ }];
+
+ t.plan(cases.length);
+
+ var c, obj;
+ for(var i=0; i<cases.length; ++i) {
+ c = cases[i];
+ obj = OpenLayers.Util.createUrlObject(c.url, c.opt);
+ t.eq(obj, c.exp, i + ": '" + c.url + "'");
+ }
+
+ }
+
+ function test_Util_createUniqueIDSeq(t) {
+ t.plan(1);
+
+ OpenLayers.Util.lastSeqID = 0;
+ OpenLayers.Util.createDiv();
+ OpenLayers.Util.createDiv();
+ t.eq(OpenLayers.Util.createDiv().id, "OpenLayersDiv3", "Div created is sequential, starting at lastSeqID in Util.");
+ }
+
+ function test_Util_getParameters(t) {
+ t.plan(20);
+
+ t.eq(OpenLayers.Util.getParameters(''), {},
+ "getParameters works when the given argument is empty string");
+
+ t.eq(OpenLayers.Util.getParameters(), {},
+ "getParameters works with optional argument");
+
+ t.eq(OpenLayers.Util.getParameters(null), {},
+ "getParameters works with optional argument");
+
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com'), {},
+ "getParameters works when args = ''");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?'), {},
+ "getParameters works when args = '?'");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?hello=world&foo=bar'),
+ {'hello' : 'world', 'foo': 'bar'},
+ "getParameters works when args = '?hello=world&foo=bar'");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?hello=&foo=bar'),
+ {'hello' : '', 'foo': 'bar'},
+ "getParameters works when args = '?hello=&foo=bar'");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar#bugssucks'),
+ {'foo': 'bar'},
+ "getParameters works when using a fragment identifier");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar%3Aone'),
+ {'foo': 'bar:one'},
+ "getParameters works with percent encoded values");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar:one,pub,disco'),
+ {'foo': ['bar:one', 'pub', 'disco']},
+ "getParameters works with a comma-separated value (parses into array)");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar%3Aone%2Cpub%2Cdisco'),
+ {'foo': ['bar:one', 'pub', 'disco']},
+ "getParameters works with a URL encoded comma-separated values (parses into array)");
+
+ var value = "%20"; // say you wanted to have a query string parameter value be literal "%20"
+ var encoded = encodeURIComponent(value); // this is the proper URI component encoding
+ var url = "http://example.com/path?key=" + encoded; // this is a properly encoded URL
+ var params = OpenLayers.Util.getParameters(url);
+ t.eq(params.key, value, "a properly encoded value of '%20' is properly decoded");
+
+ /**
+ * IETF RFC 2396 (http://www.ietf.org/rfc/rfc2396.txt) says spaces
+ * should be encoded as "%20". However, the "+" is used widely to
+ * indicate a space in a URL.
+ */
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar+one'),
+ {'foo': 'bar one'},
+ "getParameters works with + instead of %20 in values");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar%20one'),
+ {'foo': 'bar one'},
+ "getParameters works with properly encoded space character");
+ t.eq(OpenLayers.Util.getParameters('http://www.example.com?foo=bar%2Bone'),
+ {'foo': 'bar+one'},
+ "getParameters works with properly encoded + character");
+
+ // Let's do some round tripping to make it harder to introduce regressions
+ var obj = {
+ "a key": "a value with spaces (and +)",
+ "see%2B%2B": "C++",
+ "C++": "see%2B%2B",
+ "~%257E": "+%252B",
+ "who?": "me?",
+ "#yes": "#you",
+ "url": "http://example.com:80/?question=%3F&hash=%23&amp=&26#id"
+ };
+ var str = OpenLayers.Util.getParameterString(obj);
+ t.eq(OpenLayers.Util.getParameters("?" + str), obj, "round tripped parameters");
+
+ // try some oddly encoded strings
+ var url = "http://example.com/?C%E9sar=C%E9sar+Ch%E1vez";
+ var obj = OpenLayers.Util.getParameters(url);
+ t.ok("César" in obj, "got proper key from C%E9sar");
+ t.eq(obj["César"], "César Chávez", "got proper value from C%E9sar+Ch%E1vez");
+
+ // try some properly encoded strings
+ var url = "http://example.com/?C%C3%A9sar=C%C3%A9sar+Ch%C3%A1vez";
+ var obj = OpenLayers.Util.getParameters(url);
+ t.ok("César" in obj, "got proper key from C%C3%A9sar");
+ t.eq(obj["César"], "César Chávez", "got proper value from C%E9sar+Ch%E1vez");
+
+ }
+
+ function tests_Util_extend(t) {
+ t.plan(7);
+
+ var source = {
+ num: Math.random(),
+ obj: {
+ foo: "bar"
+ },
+ method: function() {
+ return "method";
+ },
+ toString: function() {
+ return "source";
+ },
+ nada: undefined
+ };
+ var destination = OpenLayers.Util.extend({nada: "untouched"}, source);
+ t.eq(destination.num, source.num,
+ "extend properly sets primitive property on destination");
+ t.eq(destination.obj, source.obj,
+ "extend properly sets object property on destination");
+ t.eq(destination.method(), "method",
+ "extend properly sets function property on destination");
+ t.eq(destination.toString(), "source",
+ "extend properly sets custom toString method");
+ t.eq(destination.nada, "untouched",
+ "undefined source properties don't clobber existing properties");
+ t.eq(window.property, undefined, "Property variable not clobbered.");
+
+ var destination;
+ var source = {rand: Math.random()};
+ var ret = OpenLayers.Util.extend(destination, source);
+ t.eq(destination.rand, source.rand, "works with undefined destination");
+
+ }
+
+ function test_XX_Util_Try(t) {
+ t.plan(7);
+
+ var func1 = function() {
+ t.ok(true, "func1 executed");
+ throw "error";
+ };
+
+ var func2 = function() {
+ t.ok(true, "func2 executed");
+ throw "error";
+ };
+
+ g_TestVal3 = {};
+ var func3 = function() {
+ t.ok(true, "func3 executed");
+ return g_TestVal3;
+ };
+
+ g_TestVal4 = {};
+ var func4 = function() {
+ t.fail("func4 should *not* be executed");
+ return g_TestVal4;
+ };
+
+ var ret = OpenLayers.Util.Try(func1, func2);
+ t.ok(ret == null, "if all functions throw exceptions, null returned");
+
+ var ret = OpenLayers.Util.Try(func1, func2, func3, func4);
+ t.ok(ret == g_TestVal3, "try returns first sucessfully executed function's return");
+
+ }
+
+ function test_getRenderedDimensions(t) {
+ // from <script src="Util_common.js"> and shared by Util_w3c.html
+ com_test_getRenderedDimensions(t);
+ }
+
+ function test_toFloat(t) {
+ t.plan(2);
+ // actual possible computed Mercator tile coordinates, more or less
+ var a1=40075016.67999999, b1=-20037508.33999999,
+ a2=40075016.68, b2=-20037508.34;
+ t.eq(OpenLayers.Util.toFloat(a1), OpenLayers.Util.toFloat(a2),
+ "toFloat rounds large floats correctly #1");
+ t.eq(OpenLayers.Util.toFloat(b1), OpenLayers.Util.toFloat(b2),
+ "toFloat rounds large floats correctly #2");
+ }
+ function test_getFormattedLonLat(t) {
+ t.plan(3);
+ var z = 2 + (4/60) - 0.000002 ;
+ t.eq(OpenLayers.Util.getFormattedLonLat(z,"lon"), "02°04'00\"E",
+ "LonLat does not show 60 seconds.");
+ t.eq(OpenLayers.Util.getFormattedLonLat(-181, "lon"), "179°00'00\"E", "crossing dateline from the west results in correct east coordinate");
+ t.eq(OpenLayers.Util.getFormattedLonLat(181, "lon"), "179°00'00\"W", "crossing dateline from the east results in correct west coordinate");
+ }
+
+ /**
+ * To test that we can safely call OpenLayers.Util.extend with an Event
+ * instance, we need to capture a real event.
+ */
+ var loadEvent;
+ window.onload = function(evt) {
+ loadEvent = evt || window.event;
+ }
+ function test_extend_event(t) {
+ t.plan(2);
+ t.ok(loadEvent, "loadEvent recorded");
+ var extended, err;
+ try {
+ extended = OpenLayers.Util.extend({foo: "bar"}, loadEvent);
+ } catch (e) {
+ err = e;
+ }
+ if (err) {
+ t.fail("Failed to extend with an event: " + err.message);
+ } else {
+ t.eq(extended && extended.foo, "bar", "extended with event");
+ }
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/Util/vendorPrefix.html b/misc/openlayers/tests/Util/vendorPrefix.html
new file mode 100644
index 0000000..924ae09
--- /dev/null
+++ b/misc/openlayers/tests/Util/vendorPrefix.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>vendorPrefix.js Tests</title>
+ <script>
+ var div = document.createElement("div");
+ var style = div.style,
+ orgCreateElement = document.createElement;
+
+ // wrap document.createElement to control property values
+ document.createElement = function(type) {
+ return div;
+ };
+
+ // dependencies for tests
+ var OpenLayers = [
+ "OpenLayers/Util/vendorPrefix.js"
+ ];
+
+ </script>
+ <script src="../OLLoader.js"></script>
+
+ <script>
+
+ /**
+ * Test vendor prefixing
+ */
+ function test_vendor_prefixes(t) {
+ t.plan(20);
+ var err;
+
+ function clearCache(type) {
+ var cache = OpenLayers.Util.vendorPrefix[type.replace("style", "js") + "Cache"];
+ for (var key in cache) {
+ delete cache[key];
+ }
+ }
+
+ function setStyleMockProp(prop, value) {
+ if (prop && value === undefined) {
+ delete style[prop];
+ } else if (prop) {
+ style[prop] = value;
+ }
+ }
+
+ function curryTestPrefix(type) {
+ return function(standardProp, expectedPrefix, msg) {
+ var prefixedProp, err;
+ try {
+ clearCache(type);
+ setStyleMockProp(expectedPrefix, "");
+ prefixedProp = OpenLayers.Util.vendorPrefix[type](standardProp);
+ } catch(e) {
+ err = e;
+ } finally {
+ setStyleMockProp(expectedPrefix, undefined);
+ }
+
+ if(!err) {
+ t.eq(prefixedProp, expectedPrefix, msg);
+ } else {
+ t.fail("Error when testing " + type.toUpperCase() + " vendor prefix: " + err.message);
+ }
+ };
+ }
+ var testDomPrefix = curryTestPrefix("style"),
+ testCssPrefix = curryTestPrefix("css");
+
+ testDomPrefix("unsupported", null, "DOM vendor prefix - unsupported");
+ testCssPrefix("unsupported", null, "CSS vendor prefix - unsupported");
+
+ testDomPrefix("test", "test", "DOM vendor prefix - single word");
+ testCssPrefix("test", "test", "CSS vendor prefix - single word");
+
+ testDomPrefix("testMultiWord", "testMultiWord", "DOM vendor prefix - multiple words");
+ testCssPrefix("test-multi-word", "test-multi-word", "CSS vendor prefix - multiple words");
+
+ testDomPrefix("multiWord", "WebkitMultiWord", "DOM vendor prefix - multiple words for WebKit");
+ testCssPrefix("multi-word", "-webkit-multi-word", "CSS vendor prefix - multiple words for WebKit");
+
+ testDomPrefix("multiWord", "MozMultiWord", "DOM vendor prefix - multiple words for Mozilla");
+ testCssPrefix("multi-word", "-moz-multi-word", "CSS vendor prefix - multiple words for Mozilla");
+
+ testDomPrefix("multiWord", "OMultiWord", "DOM vendor prefix - multiple words for Opera");
+ testCssPrefix("multi-word", "-o-multi-word", "CSS vendor prefix - multiple words for Opera");
+
+ testDomPrefix("multiWord", "msMultiWord", "DOM vendor prefix - multiple words for Internet Explorer");
+ testCssPrefix("multi-word", "-ms-multi-word", "CSS vendor prefix - multiple words for Internet Explorer");
+
+ // test vendor prefix on object
+ clearCache("js");
+ t.eq( OpenLayers.Util.vendorPrefix.js( {}, "unsupported" ), null, "Standard object property - unsupported");
+
+ clearCache("js");
+ t.eq( OpenLayers.Util.vendorPrefix.js( { "test": true }, "test" ), "test", "Standard object property");
+
+ clearCache("js");
+ t.eq( OpenLayers.Util.vendorPrefix.js( { "oTest": true }, "test" ), "oTest", "Standard object property");
+
+ clearCache("js");
+ t.eq( OpenLayers.Util.vendorPrefix.js( { "msTest": true }, "test" ), "msTest", "Standard object property");
+
+ clearCache("js");
+ t.eq( OpenLayers.Util.vendorPrefix.js( { "mozTest": true }, "test" ), "mozTest", "Standard object property");
+
+ clearCache("js");
+ t.eq( OpenLayers.Util.vendorPrefix.js( { "webkitTest": true }, "test" ), "webkitTest", "Standard object property");
+
+ // unwrap document.createElement
+ document.createElement = orgCreateElement;
+ }
+
+ </script>
+ </head>
+ <body></body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/Util_common.js b/misc/openlayers/tests/Util_common.js
new file mode 100644
index 0000000..471b0d6
--- /dev/null
+++ b/misc/openlayers/tests/Util_common.js
@@ -0,0 +1,64 @@
+function com_test_getRenderedDimensions(t) {
+ t.plan(17);
+ var content = (new Array(100)).join("foo ");
+
+ // test with fixed width
+ var fw = OpenLayers.Util.getRenderedDimensions(content, {w: 20});
+ t.eq(fw.w, 20, "got the fixed width");
+
+ // test with fixed height
+ var fh = OpenLayers.Util.getRenderedDimensions(content, {h: 15});
+ t.eq(fh.h, 15, "got the fixed height");
+
+ var size = OpenLayers.Util.getRenderedDimensions("<p>Content</p>");
+ var bigger = OpenLayers.Util.getRenderedDimensions("<p>Content</p>", null, {displayClass: 'test_getRenderedDimensions'});
+ var overflow = OpenLayers.Util.getRenderedDimensions("<p style='overflow:auto'>Content</p>");
+ var width = OpenLayers.Util.getRenderedDimensions("<p>Content</p>", new OpenLayers.Size(250, null));
+ var height = OpenLayers.Util.getRenderedDimensions("<p>Content</p>", new OpenLayers.Size(null, 40));
+ t.ok((size.w + 40) == bigger.w && (size.h + 40) == bigger.h, "bigger Pass: " + size + ", " + bigger);
+ t.ok(size.w == overflow.w && size.h == overflow.h, "overflow Pass: " + size + ", " + overflow);
+ t.ok(width.w == 250 && width.h == size.h, "width Pass: " + size + ", " + width);
+ t.ok(height.h == 40 && height.w == size.w, "height Pass: " + size + ", " + height);
+
+ content = (new Array(10)).join("foo foo foo <br>");
+ var testName,
+ finalSize,
+ initialSize = OpenLayers.Util.getRenderedDimensions(content, null);
+ // containerElement option on absolute position with width and height
+ testName = "Absolute with w&h: ";
+ var optionAbsDiv ={
+ containerElement: document.getElementById("absoluteDiv")
+ };
+ finalSize = OpenLayers.Util.getRenderedDimensions(content, null, optionAbsDiv);
+ t.ok(initialSize.w > 0 && initialSize.h > 0, "Has initial size (requires visible test_iframe)");
+ t.eq(finalSize.w, initialSize.w,
+ testName + "initial width " + initialSize.w + "px is maintained");
+ t.eq(finalSize.h, initialSize.h,
+ testName + "initial height " + initialSize.h + "px is maintained");
+ testName = "Absolute with w&h (set height): ";
+ finalSize = OpenLayers.Util.getRenderedDimensions(content, {h: 15}, optionAbsDiv);
+ t.eq(finalSize.h, 15, testName + "got the fixed height to 15px");
+ t.eq(finalSize.w, initialSize.w,
+ testName + "initial width " + initialSize.w + "px is maintained");
+ testName = "Absolute with w&h (set width): ";
+ finalSize = OpenLayers.Util.getRenderedDimensions(content, {w: 20}, optionAbsDiv);
+ t.eq(finalSize.w, 20, testName + "got the fixed width to 20px");
+ // containerElement option on absolute position without width and height
+ testName = "Absolute without w&h: ";
+ var optionAbsDiv00 ={
+ containerElement: document.getElementById("absoluteDiv00")
+ };
+ finalSize = OpenLayers.Util.getRenderedDimensions(content, null, optionAbsDiv00);
+ t.eq(finalSize.w, initialSize.w,
+ testName + "initial width " + initialSize.w + "px is maintained");
+ t.eq(finalSize.h, initialSize.h,
+ testName + "initial height " + initialSize.h + "px is maintained");
+ testName = "Absolute without w&h (set height): ";
+ finalSize = OpenLayers.Util.getRenderedDimensions(content, {h: 15}, optionAbsDiv00);
+ t.eq(finalSize.h, 15, testName + "got the fixed height to 15px");
+ t.eq(finalSize.w, initialSize.w,
+ testName + "initial width " + initialSize.w + "px is maintained");
+ testName = "Absolute without w&h (set width): ";
+ finalSize = OpenLayers.Util.getRenderedDimensions(content, {w: 20}, optionAbsDiv00);
+ t.eq(finalSize.w, 20, testName + "got the fixed width to 20px");
+}
diff --git a/misc/openlayers/tests/Util_w3c.html b/misc/openlayers/tests/Util_w3c.html
new file mode 100644
index 0000000..457341f
--- /dev/null
+++ b/misc/openlayers/tests/Util_w3c.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+ <style type="text/css">
+ .test_getRenderedDimensions p{
+ padding: 20px;
+ }
+ </style>
+ <script>
+ var OpenLayers = [
+ "OpenLayers/BaseTypes/Class.js",
+ "OpenLayers/Util.js",
+ "OpenLayers/BaseTypes.js",
+ "OpenLayers/BaseTypes/Element.js",
+ "OpenLayers/BaseTypes/LonLat.js",
+ "OpenLayers/BaseTypes/Pixel.js",
+ "OpenLayers/BaseTypes/Size.js",
+ "OpenLayers/Lang.js",
+ "OpenLayers/Console.js"
+ ];
+ </script>
+ <script src="OLLoader.js"></script>
+ <script src="Util_common.js"></script>
+ <script type="text/javascript">
+ function test_getRenderedDimensions(t) {
+ // from <script src="Util_common.js"> and shared by Util.html
+ com_test_getRenderedDimensions(t);
+ }
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/WPSClient.html b/misc/openlayers/tests/WPSClient.html
new file mode 100644
index 0000000..34b21f9
--- /dev/null
+++ b/misc/openlayers/tests/WPSClient.html
@@ -0,0 +1,108 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var client;
+
+ function test_initialize(t) {
+ t.plan(3);
+
+ client = new OpenLayers.WPSClient({
+ servers: {
+ local: "/geoserver/wps"
+ }
+ });
+
+ t.ok(client instanceof OpenLayers.WPSClient, 'creates an instance');
+ t.ok(client.events, 'has an events instance');
+ t.eq(client.servers.local.url, '/geoserver/wps', 'servers stored on instance');
+ }
+
+ function test_getProcess(t) {
+ t.plan(4);
+
+ client = new OpenLayers.WPSClient({
+ servers: {
+ local: "/geoserver/wps"
+ },
+ lazy: true
+ });
+
+ var process = client.getProcess('local', 'gs:splitPolygon');
+ t.ok(process instanceof OpenLayers.WPSProcess, 'creates a process');
+ t.ok(process.client === client, 'process knows about client');
+ t.eq(process.server, 'local', 'process created with correct server');
+ t.eq(process.identifier, 'gs:splitPolygon', 'process created with correct identifier');
+
+ }
+
+ function test_describeProcess(t) {
+ t.plan(6);
+ var log = {request: [], event: []};
+ var originalGET = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(cfg) {
+ log.request.push(cfg);
+ }
+ function describe(evt) {
+ log.event.push(evt);
+ }
+ client.events.register('describeprocess', this, describe);
+
+ process = client.getProcess('local', 'gs:splitPolygon');
+ t.eq(client.servers.local.processDescription['gs:splitPolyon'], null, 'describeProcess pending');
+ process.describe();
+ t.eq(log.request.length, 1, 'describeProcess request only sent once');
+ log.request[0].success.call(client, {
+ responseText: '<?xml version="1.0" encoding="UTF-8"?><wps:ProcessDescriptions xmlns:wps="http://www.opengis.net/wps/1.0.0"></wps:ProcessDescriptions>'
+ });
+ t.eq(log.event[0].type, 'describeprocess', 'describeprocess event triggered');
+ t.ok(client.servers.local.processDescription['gs:splitPolygon'], 'We have a process description!');
+ process.describe();
+ t.eq(log.request.length, 1, 'describeProcess request only sent once');
+ t.eq(log.event.length, 1, 'describeprocess event only triggered once');
+
+ OpenLayers.Request.GET = originalGET;
+ client.events.unregister('describeprocess', this, describe);
+ }
+
+ function test_execute(t) {
+ t.plan(1);
+
+ client = new OpenLayers.WPSClient({
+ servers: {
+ local: "/geoserver/wps"
+ },
+ lazy: true
+ });
+ var log = [];
+ client.getProcess = function() {
+ return {
+ execute: function(options) {
+ log.push(options);
+ }
+ }
+ }
+
+ client.execute({inputs: 'a', success: 'b', scope: 'c'});
+ t.eq(log[0], {inputs: 'a', success: 'b', scope: 'c'}, "process executed with correct options");
+ }
+
+ function test_destroy(t) {
+ t.plan(2);
+ client = new OpenLayers.WPSClient({
+ servers: {
+ local: "/geoserver/wps"
+ },
+ lazy: true
+ });
+ client.destroy();
+ t.eq(client.events, null, "Events nullified");
+ t.eq(client.servers, null, "Servers nullified");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/WPSProcess.html b/misc/openlayers/tests/WPSProcess.html
new file mode 100644
index 0000000..f668ca3
--- /dev/null
+++ b/misc/openlayers/tests/WPSProcess.html
@@ -0,0 +1,188 @@
+<html>
+<head>
+ <script src="OLLoader.js"></script>
+ <script type="text/javascript">
+
+ var wkt = new OpenLayers.Format.WKT();
+ var process;
+ var client = new OpenLayers.WPSClient({
+ servers: {
+ local: 'geoserver/wps'
+ }
+ });
+ client.servers.local.processDescription = {
+ 'JTS:intersection': '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wps:ProcessDescriptions xml:lang="en" service="WPS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink"><ProcessDescription wps:processVersion="1.0.0" statusSupported="true" storeSupported="true"><ows:Identifier>JTS:intersection</ows:Identifier><ows:Title>Returns the intersectoin between a and b (eventually an empty collection if there is no intersection)</ows:Title><ows:Abstract>Returns the intersectoin between a and b (eventually an empty collection if there is no intersection)</ows:Abstract><DataInputs><Input maxOccurs="1" minOccurs="1"><ows:Identifier>a</ows:Identifier><ows:Title>a</ows:Title><ows:Abstract>[undescribed]</ows:Abstract><ComplexData><Default><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format></Default><Supported><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format><Format><MimeType>text/xml; subtype=gml/2.1.2</MimeType></Format><Format><MimeType>application/wkt</MimeType></Format><Format><MimeType>application/gml-3.1.1</MimeType></Format><Format><MimeType>application/gml-2.1.2</MimeType></Format></Supported></ComplexData></Input><Input maxOccurs="1" minOccurs="1"><ows:Identifier>b</ows:Identifier><ows:Title>b</ows:Title><ows:Abstract>[undescribed]</ows:Abstract><ComplexData><Default><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format></Default><Supported><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format><Format><MimeType>text/xml; subtype=gml/2.1.2</MimeType></Format><Format><MimeType>application/wkt</MimeType></Format><Format><MimeType>application/gml-3.1.1</MimeType></Format><Format><MimeType>application/gml-2.1.2</MimeType></Format></Supported></ComplexData></Input></DataInputs><ProcessOutputs><Output><ows:Identifier>result</ows:Identifier><ows:Title>Process result</ows:Title><ComplexOutput><Default><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format></Default><Supported><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format><Format><MimeType>text/xml; subtype=gml/2.1.2</MimeType></Format><Format><MimeType>application/wkt</MimeType></Format><Format><MimeType>application/gml-3.1.1</MimeType></Format><Format><MimeType>application/gml-2.1.2</MimeType></Format></Supported></ComplexOutput></Output></ProcessOutputs></ProcessDescription></wps:ProcessDescriptions>'
+ };
+
+ function test_initialize(t) {
+ t.plan(1);
+ process = new OpenLayers.WPSProcess();
+ t.ok(process instanceof OpenLayers.WPSProcess, 'creates an instance');
+ }
+
+ function test_describe(t) {
+ t.plan(2);
+ process = client.getProcess('local', 'JTS:intersection');
+ var log = [];
+ process.describe({
+ callback: function(description) { log.push(description); }
+ });
+ t.delay_call(0.1, function() {
+ t.eq(log.length, 1, 'callback called');
+ t.eq(log[0].identifier, 'JTS:intersection', 'callback called with correct description');
+ });
+ }
+
+ function test_execute(t) {
+ t.plan(7);
+
+ var log = [];
+ var originalPOST = OpenLayers.Request.POST;
+ OpenLayers.Request.POST = function(cfg) {
+ log.push(cfg);
+ cfg.success.call(cfg.scope, {responseText: ''});
+ }
+
+ process = new OpenLayers.WPSProcess({
+ client: client,
+ server: 'local',
+ identifier: 'gs:splitPolygon'
+ });
+ process.description = {
+ dataInputs: [{
+ identifier: 'line',
+ complexData: {
+ supported: {
+ formats: {'application/wkt': true}
+ }
+ }
+ }, {
+ identifier: 'polygon',
+ complexData: {
+ supported: {
+ formats: {'application/wkt': true}
+ }
+ }
+ }],
+ processOutputs: [{
+ identifier: 'foo',
+ complexOutput: {
+ supported: {
+ formats: {'application/wkt': true}
+ }
+ }
+ }]
+ };
+ var line = 'LINESTRING(117 22,112 18,118 13,115 8)';
+ var polygon = 'POLYGON((110 20,120 20,120 10,110 10,110 20),(112 17,118 18,118 16,112 15,112 17))';
+ var output = [];
+ function success(result) {
+ output.push(result);
+ }
+ // configured with output identifier
+ process.execute({
+ inputs: {
+ line: wkt.read(line),
+ polygon: wkt.read(polygon)
+ },
+ output: 'foo',
+ success: success
+ });
+ // configured without output identifier
+ process.execute({
+ inputs: {
+ line: wkt.read(line),
+ polygon: wkt.read(polygon)
+ },
+ success: success
+ });
+
+ t.delay_call(0.1, function() {
+ t.eq(log.length, 2, 'Two execute requests sent');
+ t.eq(process.description.dataInputs[0].data.complexData.value, line, 'data for first input correct');
+ t.eq(process.description.dataInputs[0].data.complexData.mimeType, 'application/wkt', 'format for first input correct');
+ t.eq(process.description.responseForm.rawDataOutput.identifier, 'foo', 'correct identifier for responseForm');
+ t.eq(process.description.responseForm.rawDataOutput.mimeType, 'application/wkt', 'correct format for responseForm');
+ t.ok('foo' in output[0], 'process result contains output with correct identifier when configured with output');
+ t.ok('result' in output[1], 'process result contains output with correct identifier when configured without output');
+
+ OpenLayers.Request.POST = originalPOST;
+ });
+ }
+
+ function test_chainProcess(t) {
+ t.plan(5);
+
+ var originalGET = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(cfg) {
+ window.setTimeout(function() {
+ cfg.success.call(cfg.scope, {
+ responseText: '<?xml version="1.0" encoding="UTF-8"?>' +
+ '<wps:ProcessDescriptions xml:lang="en" service="WPS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd" xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink"><ProcessDescription wps:processVersion="1.0.0" statusSupported="true" storeSupported="true"><ows:Identifier>JTS:buffer</ows:Identifier><ows:Title>Buffers a geometry using a certain distance</ows:Title><ows:Abstract>Buffers a geometry using a certain distance</ows:Abstract><DataInputs><Input maxOccurs="1" minOccurs="1"><ows:Identifier>geom</ows:Identifier><ows:Title>geom</ows:Title><ows:Abstract>The geometry to be buffered</ows:Abstract><ComplexData><Default><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format></Default><Supported><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format><Format><MimeType>text/xml; subtype=gml/2.1.2</MimeType></Format><Format><MimeType>application/wkt</MimeType></Format><Format><MimeType>application/gml-3.1.1</MimeType></Format><Format><MimeType>application/gml-2.1.2</MimeType></Format></Supported></ComplexData></Input><Input maxOccurs="1" minOccurs="1"><ows:Identifier>distance</ows:Identifier><ows:Title>distance</ows:Title><ows:Abstract>The distance (same unit of measure as the geometry)</ows:Abstract><LiteralData><ows:DataType>xs:double</ows:DataType><ows:AnyValue/></LiteralData></Input><Input maxOccurs="1" minOccurs="0"><ows:Identifier>quadrantSegments</ows:Identifier><ows:Title>quadrantSegments</ows:Title><ows:Abstract>Number of quadrant segments. Use &gt; 0 for round joins, 0 for flat joins, &lt; 0 for mitred joins</ows:Abstract><LiteralData><ows:DataType>xs:int</ows:DataType><ows:AnyValue/></LiteralData></Input><Input maxOccurs="1" minOccurs="0"><ows:Identifier>capStyle</ows:Identifier><ows:Title>capStyle</ows:Title><ows:Abstract>The buffer cap style, round, flat, square</ows:Abstract><LiteralData><ows:AllowedValues><ows:Value>Round</ows:Value><ows:Value>Flat</ows:Value><ows:Value>Square</ows:Value></ows:AllowedValues></LiteralData></Input></DataInputs><ProcessOutputs><Output><ows:Identifier>result</ows:Identifier><ows:Title>result</ows:Title><ComplexOutput><Default><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format></Default><Supported><Format><MimeType>text/xml; subtype=gml/3.1.1</MimeType></Format><Format><MimeType>text/xml; subtype=gml/2.1.2</MimeType></Format><Format><MimeType>application/wkt</MimeType></Format><Format><MimeType>application/gml-3.1.1</MimeType></Format><Format><MimeType>application/gml-2.1.2</MimeType></Format></Supported></ComplexOutput></Output></ProcessOutputs></ProcessDescription></wps:ProcessDescriptions>'
+ });
+ }, 100);
+ }
+ var originalPOST = OpenLayers.Request.POST;
+ OpenLayers.Request.POST = function(cfg) {
+ cfg.success.call(cfg.scope, {responseText: ''});
+ };
+
+ var intersect = client.getProcess('local', 'JTS:intersection');
+ intersect.configure({
+ inputs: {
+ a: wkt.read(
+ 'LINESTRING(117 22,112 18,118 13,115 8)'
+ ),
+ b: wkt.read(
+ 'POLYGON((110 20,120 20,120 10,110 10,110 20),(112 17,118 18,118 16,112 15,112 17))'
+ )
+ }
+ });
+
+ // one buffer process to make sure chaining works
+ var buffer1 = client.getProcess('local', 'JTS:buffer');
+ // another buffer process to make sure that things work asynchronously
+ var buffer2 = client.getProcess('local', 'JTS:buffer');
+ var log = [];
+ buffer1.chainProcess = buffer2.chainProcess = function() {
+ log.push(this.executeCallbacks.length);
+ OpenLayers.WPSProcess.prototype.chainProcess.apply(this, arguments);
+ };
+ var done1 = done2 = false;
+ buffer1.execute({
+ inputs: {
+ geom: intersect.output(),
+ distance: 1
+ },
+ success: function(outputs) {
+ done1 = true;
+ }
+ });
+ buffer2.execute({
+ inputs: {
+ geom: intersect.output(),
+ distance: 2
+ },
+ success: function(outputs) {
+ done2 = true;
+ }
+ });
+
+ t.delay_call(0.5, function() {
+ t.eq(log.length, 2, 'chainProcess called once for each process');
+ t.eq(log[0], 1, 'executeCallback queued to wait for 1 chained process');
+ t.eq(log[1], 1, 'executeCallback queued to wait for 1 chained process');
+ t.eq(done1, true, 'execute for buffer1 process successfully completed');
+ t.eq(done2, true, 'execute for buffer2 process successfully completed');
+
+ OpenLayers.Request.GET = originalGET;
+ OpenLayers.Request.POST = originalPOST;
+ });
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/atom-1.0.xml b/misc/openlayers/tests/atom-1.0.xml
new file mode 100644
index 0000000..f0d5d6f
--- /dev/null
+++ b/misc/openlayers/tests/atom-1.0.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom"
+ xmlns:georss="http://www.georss.org/georss">
+
+ <title>tumulus</title>
+ <link rel="self"
+ href="http://pleiades.stoa.org/places/tumulus"/>
+ <updated/>
+ <author/>
+ <id>http://pleiades.stoa.org/places/tumulus</id>
+
+ <entry>
+ <title>Unnamed Tumulus</title>
+ <link rel="alternate"
+ href="http://pleiades.stoa.org/places/638896"
+ />
+ <id>http://pleiades.stoa.org/places/638896</id>
+ <updated/>
+ <summary>An ancient tumulus, attested during the Classical period (modern location: Karaburun). Its ancient name is not known.</summary>
+ <georss:point>36.7702 29.9805</georss:point>
+ </entry>
+ <entry>
+ <title>Unnamed Tumulus</title>
+ <link rel="alternate"
+ href="http://pleiades.stoa.org/places/638924"
+ />
+ <id>http://pleiades.stoa.org/places/638924</id>
+ <updated/>
+ <summary>An ancient tumulus, attested during the Classical period (modern location: Kızılbel). Its ancient name is not known.</summary>
+ <georss:point>36.7263 29.8619</georss:point>
+ </entry>
+
+</feed>
+
diff --git a/misc/openlayers/tests/auto-tests.html b/misc/openlayers/tests/auto-tests.html
new file mode 100644
index 0000000..f467e41
--- /dev/null
+++ b/misc/openlayers/tests/auto-tests.html
@@ -0,0 +1,2447 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head>
+<meta http-equiv="refresh" content="1200" />
+<title> Run the testsuite</title>
+<noscript>Javascript is disabled in your browser. This page cannot be displayed correctly without Javascript. Sorry. <br/> If you want to view this page, please change your browser settings so that Javascript is enabled.</noscript>
+<!--
+Test.AnotherWay version 0.5
+
+Copyright (c) 2005 Artem Khodush, http://straytree.org
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<style type="text/css">
+* { padding: 0; margin: 0; }
+html { height: 99%; }
+body { height: 98%; font: normal normal 10pt sans-serif }
+#col1 { float: left; width: 27em; margin: 0 0 0 1em; overflow: visible; }
+#col2 { position: relative; height: 98%; margin: 0 0.5em 0 28em; }
+#col1_header { margin-top: 0.5em; }
+#scroller { height: 400px; overflow: auto;}
+#testtable { margin: 0 0 2em 0; width: 97%; }
+#run_buttons { margin-bottom: 4em; }
+
+#right_header { padding-top: 0.8em; }
+#results_count { float: left; }
+.active_tab { float: right; padding: 0 1em 0.2em 1em; background: #0af; border: 1px solid #048; border-bottom: none; cursor: pointer; cursor: hand;
+ position: relative; top: -0.2em; }
+.inactive_tab { float: right; padding: 0 1em 0 1em; background: #9bb; color: #444; border: 1px solid #9bb; border-bottom: none; cursor: pointer; cursor: hand; }
+.inactive_mouseover_tab { float: right; padding: 0 1em 0 1em; background: #9bb; color: #062; border: 1px solid #062; border-bottom: none; cursor: pointer; cursor: hand; }
+
+#right_frame { overflow: auto; position: relative; top: -0.2em; clear: right; height: 95%; border: 1px solid #048; }
+
+#debug { display: none; }
+#debug p { margin: 2px 0 0 5em; text-indent: -4.8em; }
+
+#error { display: none; color: #c22; }
+
+#results p { margin: 0 0 2px 0; }
+/* cursor indicating that detailed results may be expanded/contracted */
+#results p.badtest { cursor: text; }
+#results p.ok, #results p.fail { cursor: pointer; cursor: hand; }
+
+/* colored squares in the results window at the left of test page names */
+#results p.ok .bullet { background: #6d6; }
+#results p.fail .bullet { background: #d46; }
+#results p.badtest .bullet { background: #ea3; }
+#results p.loading .bullet { background: #48f; }
+#results p.running .bullet { background: #26e; }
+#results p.waiting .bullet { background: #04d; }
+/* highlight in the results line */
+#results p .warning { background: #ffc; }
+
+/* layout of the detailed results */
+.result_detail { padding-left: 3em; }
+.result_exception_detail { padding-left: 4em; }
+.result_exception_stack_detail { padding-left: 5em; }
+.result_micro_detail { padding-left: 6em; }
+/* colouring in the detailed results */
+.result_detail .fail, .result_exception_detail .fail, .result_micro_detail .fail { background: #ffd8d8; }
+
+/* "start recording" controls*/
+#record_div { margin-top: 3em; }
+#record_div p { margin-bottom: 0.5em; }
+#record_select { width: 88%; }
+#record_input { width: 53%; }
+</style>
+<script type="text/javascript">
+<!--
+
+function report_results() {
+ req = false;
+ // branch for native XMLHttpRequest object
+ if(window.XMLHttpRequest && !(window.ActiveXObject)) {
+ try {
+ req = new XMLHttpRequest();
+ } catch(e) {
+ req = false;
+ }
+ // branch for IE/Windows ActiveX version
+ } else if(window.ActiveXObject) {
+ try {
+ req = new ActiveXObject("Msxml2.XMLHTTP");
+ } catch(e) {
+ try {
+ req = new ActiveXObject("Microsoft.XMLHTTP");
+ } catch(e) {
+ req = false;
+ }
+ }
+ }
+ req.open("POST", "/test/results.cgi");
+ req.setRequestHeader("Content-Type", 'application/x-www-form-urlencoded');
+ var results = document.getElementById('total').innerHTML;
+ var test_text = "";
+ if (results.match("fail")) {
+ test_text = document.getElementById("results").innerHTML;
+ }
+ req.send("results="+escape(results)+"&test_text="+escape(test_text));
+}
+
+if( typeof( Test )=="undefined" ) {
+ Test={};
+}
+Test.AnotherWay={};
+
+Test.AnotherWay._g_test_iframe=null; // frame where to load test pages
+Test.AnotherWay._g_test_frame_no_clear=false; // true - leave last page displayed after tests end
+Test.AnotherWay._g_test_page_urls=[]; // array of: { url: url, convention: "anotherway" or "jsan" }
+Test.AnotherWay._g_test_object_for_jsan=null; // test object for filling by tests that adhere to jsan Test.Simple calling convention
+Test.AnotherWay._g_pages_to_run=null; // list of pages to run automatically after loading
+Test.AnotherWay._g_run_on_main_load=false; // special handling for run_pages_to_run when it might be called before onload or before list of test pages is known.
+Test.AnotherWay._g_run_on_list_load=false;
+Test.AnotherWay._g_main_loaded=false;
+
+Test.AnotherWay._run_pages_to_run=function( called_from_outside )
+{
+ if( !Test.AnotherWay._g_main_loaded ) {
+ Test.AnotherWay._g_run_on_main_load=true;
+ }else {
+ var a_pages=Test.AnotherWay._g_pages_to_run;
+ if( a_pages=="all" ) {
+ for( var i=0; i<Test.AnotherWay._g_test_page_urls.length; ++i ) {
+ Test.AnotherWay._run_test_page( "test"+i );
+ }
+ }else if( a_pages!=null ) {
+ for( var run_i=0; run_i<a_pages.length; ++run_i ) {
+ var run_page=a_pages[run_i];
+ var found=false;
+ for( var all_i=0; all_i<Test.AnotherWay._g_test_page_urls.length; ++all_i ) {
+ if( run_page==Test.AnotherWay._g_test_page_urls[all_i].url ) {
+ Test.AnotherWay._run_test_page( "test"+all_i, called_from_outside );
+ found=true;
+ break;
+ }
+ }
+ if( !found ) {
+ Test.AnotherWay._show_error( "page specified to run is not found in the page list: "+run_page );
+ break;
+ }
+ }
+ }
+ }
+}
+
+Test.AnotherWay._add_test_page_url=function( test_url, convention )
+{
+ var table=document.getElementById( "testtable" );
+ var record_select=document.getElementById( "record_select" );
+ var index=Test.AnotherWay._g_test_page_urls.length;
+
+ // trim spaces.
+ if( test_url.match( "^(\\s*)(.*\\S)(\\s*)$" ) ) {
+ test_url=RegExp.$2;
+ }
+
+ Test.AnotherWay._g_test_page_urls[index]={ url: test_url, convention: convention };
+ var row=table.insertRow( -1 );
+
+ var cell;
+ var cell_child;
+ cell=row.insertCell( -1 );
+ cell_child=document.createElement( "input" );
+ cell_child.type="checkbox";
+ cell_child.id="checkbox"+index;
+ cell_child.checked='checked';
+ cell_child.defaultChecked='checked';
+ cell.appendChild( cell_child );
+
+ cell=row.insertCell( -1 );
+ cell.setAttribute( "width", "75%" );
+ cell.appendChild( document.createTextNode( test_url ) );
+
+ cell=row.insertCell( -1 );
+ cell_child=document.createElement( "input" );
+ cell_child.type="button";
+ cell_child.id="test"+index;
+ cell_child.value=" run ";
+ cell_child.onclick=Test.AnotherWay._run_one_onclick;
+ cell.appendChild( cell_child );
+
+ cell=row.insertCell( -1 );
+ cell.setAttribute( "width", "8em" );
+ cell_child=document.createElement( "span" );
+ cell.appendChild( cell_child );
+
+ var option=document.createElement( "option" );
+ option.appendChild( document.createTextNode( test_url ) );
+ record_select.appendChild( option );
+}
+Test.AnotherWay._show_error=function( msg )
+{
+ var error_div=document.getElementById( "error" );
+ error_div.innerHTML="";
+ error_div.appendChild( document.createTextNode( msg ) );
+ error_div.style.display="block";
+}
+
+// read urls from the list in the html file inside the list_iframe
+// fill on-screen list with urls and "run" buttons, and fill the g_test_page_urls object.
+Test.AnotherWay._list_iframe_onload=function()
+{
+ if( window.frames.list_iframe!=null && window.frames.list_iframe.location!="" && window.frames.list_iframe.location!="about:blank" ) {
+ var list_doc=window.frames.list_iframe.document;
+ var list=list_doc.getElementById( "testlist" );
+ if( list!=null ) {
+ for( var i=0; i<list.childNodes.length; ++i ) {
+ var item=list.childNodes[i];
+ if( item.nodeName=="LI" || item.nodeName=="li" ) {
+ var convention="anotherway";
+ if( Test.AnotherWay._get_css_class( item )=="jsan" ) {
+ convention="jsan";
+ }
+ Test.AnotherWay._add_test_page_url( item.innerHTML, convention );
+ }
+ }
+ if( Test.AnotherWay._g_run_on_list_load ) {
+ Test.AnotherWay._g_run_on_list_load=false;
+ Test.AnotherWay._run_pages_to_run();
+ }
+ }else {
+ Test.AnotherWay._show_error( "no list with id 'testlist' in a list file "+window.frames.list_iframe.location );
+ }
+ }
+}
+
+Test.AnotherWay._map_checkboxes=function( f )
+{
+ var table=document.getElementById( "testtable" );
+ var checks=table.getElementsByTagName( "INPUT" );
+ for( var i=0; i<checks.length; ++i ) {
+ if( checks[i].type=="checkbox" && checks[i].id.match( /^checkbox(\d+)$/ ) ) {
+ f( checks[i], RegExp.$1 );
+ }
+ }
+}
+Test.AnotherWay._run_all_onclick=function()
+{
+ Test.AnotherWay._map_checkboxes( function( c, id ) { Test.AnotherWay._run_test_page( "test"+id ); } );
+}
+Test.AnotherWay._run_selected_onclick=function()
+{
+ Test.AnotherWay._map_checkboxes( function( c, id ) { if( c.checked ) Test.AnotherWay._run_test_page( "test"+id ); } );
+}
+Test.AnotherWay._unselect_all_onclick=function()
+{
+ Test.AnotherWay._map_checkboxes( function( c, id ) { c.checked=false; } );
+}
+Test.AnotherWay._run_one_onclick=function()
+{
+ Test.AnotherWay._run_test_page( this.id );
+}
+
+// construct an object that will gather results of running one test function
+Test.AnotherWay._test_object_t=function( fun_name )
+{
+ this.name=fun_name; // name of the test function
+ this.n_plan=null; // planned number of assertions
+ this.n_ok=0; // # of ok assertions
+ this.n_fail=0; // # of failed assertions
+ this.exception=""; // if the function throwed an exception, it's its message
+ this.exception_stack=[]; // strings: function call stack from the exception
+ this.assertions=[]; // assertion results: array of { ok: 1 or 0, name: string }
+ this.wait_result_milliseconds=0; // how long to wait before collecting results from the test
+ this.second_wait_msg=null; // <p> status message (in addition to the page wait_msg)
+ this.delay_actions=[]; // array of actions to be perfomed after the test function returns
+ // action : { acton_kind: "call" | "window" | "replay"
+ // when "call": { call_fn call_delay_milliseconds } call_fn takes nothing
+ // when "window" : { wnd_url wnd_wnd wnd_fn wnd_timeout_milliseconds wnd_dont_close } wnd_fn takes wnd
+ // wnen "replay" : { replay_wnd replay_events replay_event_i replay_checkpoints } checkpoint_fn takes this, wnd
+ // }
+ this.delay_action_i=null; // index of delay action currently being performed
+ this.delay_prev_timer_time=0; // for counting time while performing delay_actions
+ this.delay_current_milliseconds_left=0; // time left before the next action, runs down
+ this.delay_total_milliseconds_left=0; // for indication: total estimated time for all actions, runs up and down
+}
+
+Test.AnotherWay._test_object_t.prototype.ok=function( cond, name )
+{
+ if( cond ) {
+ ++this.n_ok;
+ cond=1;
+ }else {
+ ++this.n_fail;
+ cond=0;
+ }
+ this.assertions.push( { ok: cond, name: name } );
+}
+Test.AnotherWay._test_object_t.prototype.fail=function( name )
+{
+ this.ok( false, name );
+}
+Test.AnotherWay._test_object_t.prototype.plan=function( n )
+{
+ this.n_plan=n;
+}
+Test.AnotherWay._test_object_t.prototype.wait_result=function( seconds )
+{
+ this.wait_result_milliseconds=1000*seconds;
+}
+Test.AnotherWay._eq_fail_msg=function( path, what, expected, got )
+{
+ return "eq: "+path+" "+what+" differ: got "+got+", but expected "+expected;
+}
+Test.AnotherWay._array_eq=function( expected, got, path, msg )
+{
+ if( expected.length!=got.length ) {
+ msg.msg=Test.AnotherWay._eq_fail_msg( path, "array length", expected.length, got.length );
+ return false;
+ }
+ for( var i=0; i<expected.length; ++i ) {
+ if( !Test.AnotherWay._thing_eq( expected[i], got[i], path+"["+i+"]", msg ) ) {
+ return false;
+ }
+ }
+ return true;
+}
+Test.AnotherWay._object_eq=function( expected, got, path, msg )
+{
+ var v;
+ for( v in expected ) {
+ if( ! (v in got) ) {
+ msg.msg=Test.AnotherWay._eq_fail_msg( path+"."+v, "properties", expected[v], "undefined" );
+ return false;
+ }
+ if( !Test.AnotherWay._thing_eq( expected[v], got[v], path+"."+v, msg ) ) {
+ return false;
+ }
+ }
+ for( v in got ) {
+ if( ! (v in expected) ) {
+ msg.msg=Test.AnotherWay._eq_fail_msg( path+"."+v, "properties", "undefined", got[v] );
+ return false;
+ }
+ }
+ return true;
+}
+Test.AnotherWay._constructor_name=function( x )
+{
+ if( x==null ) {
+ return "";
+ }
+ var s="unknown";
+ try {
+ s=typeof( x.constructor );
+ if( s!="unknown" ) {
+ s=x.constructor.toString();
+ }
+ }catch( e ) {
+ s="unknown";
+ }
+ if( s=="unknown" ) {
+ // hackish attempt to guess a type
+ var is_array=true;
+ var index=0;
+ for( i in x ) {
+ if( i!=index ) {
+ is_array=false;
+ }
+ ++index;
+ }
+ return is_array ? "Array" : "Object"; // for empty arrays/objects, this will be wrong half the time
+ }else if( s.match( /^\s*function\s+(\w+)\s*\(/ ) ) {
+ return RegExp.$1;
+ }else {
+ var c = '';
+ switch(typeof x) {
+ case 'string':
+ c = 'String';
+ break;
+ case 'object':
+ c = 'Object';
+ break;
+ default:
+ c = '';
+ }
+ return c; }
+}
+Test.AnotherWay._is_array=function( x )
+{
+ return Test.AnotherWay._constructor_name( x )=="Array";
+}
+Test.AnotherWay._is_value_type=function( x )
+{
+ cn=Test.AnotherWay._constructor_name( x );
+ return cn=="Number" || cn=="String" || cn=="Boolean" || cn=="Date";
+}
+Test.AnotherWay._thing_eq=function( expected, got, path, msg )
+{
+ if( expected==null && got==null ) {
+ return true;
+ }else if( (expected==null && got!=null) || (expected!=null && got==null) ) {
+ msg.msg=Test.AnotherWay._eq_fail_msg( path, "values", expected, got );
+ return false;
+ }else {
+ var expected_cn=Test.AnotherWay._constructor_name( expected );
+ var got_cn=Test.AnotherWay._constructor_name( got );
+ if( expected_cn!=got_cn ) {
+ msg.msg=Test.AnotherWay._eq_fail_msg( path, "types", expected_cn, got_cn );
+ return false;
+ }else {
+ if( Test.AnotherWay._is_array( expected ) ) {
+ return Test.AnotherWay._array_eq( expected, got, path, msg );
+ }else if( Test.AnotherWay._is_value_type( expected ) ) {
+ if( expected!=got ) {
+ msg.msg=Test.AnotherWay._eq_fail_msg( path, "values", expected, got );
+ return false;
+ }else {
+ return true;
+ }
+ }else { // just a plain object
+ return Test.AnotherWay._object_eq( expected, got, path, msg );
+ }
+ }
+ }
+}
+Test.AnotherWay._test_object_t.prototype.eq=function( got, expected, name )
+{
+ var msg={};
+ if( Test.AnotherWay._thing_eq( expected, got, "", msg ) ) {
+ this.ok( 1, name );
+ }else {
+ this.fail( name+". "+msg.msg );
+ }
+}
+Test.AnotherWay._test_object_t.prototype.like=function( got, expected, name )
+{
+ if( got.match( expected )!=null ) {
+ this.ok( 1, name );
+ }else {
+ this.fail( name+": got "+got+", but expected it to match: "+expected );
+ }
+}
+Test.AnotherWay._g_html_eq_span=null;
+Test.AnotherWay._html_eq_string_to_node=function( string_or_node, what, msg )
+{
+ if( string_or_node.nodeType!=null ) {
+ string_or_node=Test.AnotherWay._html_eq_node_to_string( string_or_node ); // double trip - to make properties assigned in scripts available as html node attributes
+ }
+ if( Test.AnotherWay._g_html_eq_span==null ) {
+ Test.AnotherWay._g_html_eq_span=document.createElement( "span" );
+ }
+ Test.AnotherWay._g_html_eq_span.innerHTML=string_or_node;
+ if( Test.AnotherWay._g_html_eq_span.childNodes.length!=1 ) {
+ msg.msg="bad "+what+" html string given (should contain exactly one outermost element): "+string_or_node;
+ }
+ return Test.AnotherWay._g_html_eq_span.childNodes[0].cloneNode( true );
+}
+Test.AnotherWay._html_eq_node_to_string=function( node ) {
+ if( Test.AnotherWay._g_html_eq_span==null ) {
+ Test.AnotherWay._g_html_eq_span=document.createElement( "span" );
+ }
+ Test.AnotherWay._g_html_eq_span.innerHTML="";
+ if( node.outerHTML!=null ) {
+ Test.AnotherWay._g_html_eq_span.innerHTML=node.outerHTML;
+ }else {
+ var clone = node.cloneNode(true);
+ var node = Test.AnotherWay._g_html_eq_span;
+ if(node.ownerDocument && node.ownerDocument.importNode) {
+ if(node.ownerDocument != clone.ownerDocument) {
+ clone = node.ownerDocument.importNode(clone, true);
+ }
+ }
+ node.appendChild(clone);
+ }
+ return Test.AnotherWay._g_html_eq_span.innerHTML;
+}
+Test.AnotherWay._html_eq_path_msg=function( path )
+{
+ var msg="";
+ for( var i=0; i<path.length; ++i ) {
+ msg+=" [node "+path[i].node;
+ if( path[i].id!=null && path[i].id!="" ) {
+ msg+=" id "+path[i].id;
+ }else if( path[i].index!=null ) {
+ msg+=" at index "+path[i].index;
+ }
+ msg+="] "
+ }
+ return msg;
+}
+Test.AnotherWay._html_eq_fail_msg=function( path, what, expected, got )
+{
+ return Test.AnotherWay._html_eq_path_msg( path )+": "+what+" differ: got "+got+", but expected "+expected;
+}
+Test.AnotherWay._html_eq_remove_blank=function( text )
+{
+ if( text==null ) {
+ return "";
+ }else if( text.match( "^(\\s*)(.*\\S)(\\s*)$" ) ) {
+ return RegExp.$2;
+ }else if( text.match( "\s*" ) ) {
+ return "";
+ }
+ return text;
+}
+Test.AnotherWay._html_eq_remove_blank_nodes=function( node )
+{
+ var to_remove=[];
+ for( var child=node.firstChild; child!=null; child=child.nextSibling ) {
+ if( child.nodeType==3 ) {
+ var value=Test.AnotherWay._html_eq_remove_blank( child.nodeValue );
+ if( value=="" ) {
+ to_remove.push( child );
+ }else {
+ child.nodeValue=value;
+ }
+ }
+ }
+ for( var i=0; i<to_remove.length; ++i ) {
+ node.removeChild( to_remove[i] );
+ }
+}
+Test.AnotherWay._html_node_type_text=function( node_type )
+{
+ if( node_type==1 ) {
+ return "1 (html element)";
+ }else if( node_type==3 ) {
+ return "3 (text)";
+ }else {
+ return node_type;
+ }
+}
+Test.AnotherWay._html_eq_node=function( expected, got, path, msg, expected_loc_base, got_loc_base )
+{
+ if( expected.nodeType!=got.nodeType ) {
+ msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "node types", Test.AnotherWay._html_node_type_text( expected.nodeType ), Test.AnotherWay._html_node_type_text( got.nodeType ) );
+ return false;
+ }else if( expected.nodeType==3 ) {
+ if( expected.nodeValue!=got.nodeValue ) {
+ msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "text", expected.nodeValue, got.nodeValue );
+ return false;
+ }
+ }else if( expected.nodeType==1 ) {
+ if( expected.nodeName!=got.nodeName ) {
+ msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "node names", expected.nodeName, got.nodeName );
+ return false;
+ }
+ // compare attributes
+ var expected_attrs={};
+ var got_attrs={};
+ var i;
+ var a;
+ for( i=0; i<expected.attributes.length; ++i ) {
+ a=expected.attributes[i];
+ if( a.specified ) {
+ expected_attrs[a.name]=1;
+ }
+ }
+ for( i=0; i<got.attributes.length; ++i ) {
+ a=got.attributes[i];
+ if( a.specified ) {
+ got_attrs[a.name]=1;
+ }
+ }
+ for( a in expected_attrs ) {
+ if( ! (a in got_attrs) ) {
+ msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute sets differ: expected attribute "+a+" is missing";
+ return false;
+ }
+ }
+ for( a in got_attrs ) {
+ if( ! (a in expected_attrs) ) {
+ msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute sets differ: got extra attribute "+a;
+ return false;
+ }
+ }
+ for( a in expected_attrs ) {
+ var expected_value=expected.getAttribute( a );
+ var got_value=got.getAttribute( a );
+ if( typeof( expected_value )=="string" && typeof( got_value )=="string" ) {
+ expected_value=Test.AnotherWay._html_eq_remove_blank( expected_value );
+ got_value=Test.AnotherWay._html_eq_remove_blank( got_value );
+ var ok=expected_value==got_value;
+ if( !ok && (a=="href" || a=="HREF" ) ) { // try relative hrefs
+ var expected_relative_value=expected_value;
+ if( expected_loc_base!=null && expected_value.substring( 0, expected_loc_base.length )==expected_loc_base ) {
+ expected_relative_value=expected_value.substring( expected_loc_base.length );
+ }
+ var got_relative_value=got_value;
+ if( got_loc_base!=null && got_value.substring( 0, got_loc_base.length )==got_loc_base ) {
+ got_relative_value=got_value.substring( got_loc_base.length );
+ }
+ ok=expected_relative_value==got_relative_value;
+ }
+ if( !ok ) {
+ msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "attribute "+a+" values", expected_value, got_value );
+ return false;
+ }
+ }else if( typeof( expected_value )=="function" && typeof( got_value )=="function" ) {
+ expected_value=expected_value.toString();
+ got_value=got_value.toString();
+ if( expected_value!=got_value ) {
+ msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "attribute "+a+" values", expected_value, got_value );
+ return false;
+ }
+ }else {
+ var value_msg={};
+ if( !Test.AnotherWay._thing_eq( expected_value, got_value, "", value_msg ) ) {
+ msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute "+a+" values differ: "+value_msg.msg;
+ return false;
+ }
+ }
+ }
+ // compare child nodes
+ Test.AnotherWay._html_eq_remove_blank_nodes( expected );
+ Test.AnotherWay._html_eq_remove_blank_nodes( got );
+ var expected_length=expected.childNodes.length;
+ var got_length=got.childNodes.length;
+ if( expected_length<got_length ) {
+ msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": got "+(got_length-expected_length)+" extra child nodes";
+ return false;
+ }else if( expected_length>got_length ) {
+ msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": expected "+(expected_length-got_length)+" more child nodes";
+ return false;
+ }else {
+ for( i=0; i<expected_length; ++i ) {
+ var expected_node=expected.childNodes[i];
+ path.push( { node: expected_node.nodeName, id: expected_node.id, index: i } );
+ var eq=Test.AnotherWay._html_eq_node( expected_node, got.childNodes[i], path, msg, expected_loc_base, got_loc_base );
+ path.pop();
+ if( !eq ) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+Test.AnotherWay._html_eq_get_loc_base=function( node )
+{
+ var loc_base=document.location;
+ if( node.ownerDocument!=null ) {
+ loc_base=node.ownerDocument.location;
+ }
+ if( loc_base!=null ) {
+ loc_base=loc_base.href;
+ var slash_pos=loc_base.lastIndexOf( "/" );
+ if( slash_pos!=-1 ) {
+ loc_base=loc_base.substring( 0, slash_pos+1 );
+ }
+ }
+ return loc_base;
+}
+Test.AnotherWay._test_object_t.prototype.html_eq=function( got, expected, name )
+{
+ var msg={};
+ var expected_node=Test.AnotherWay._html_eq_string_to_node( expected, "expected", msg );
+ if( msg.msg!=null ) {
+ this.fail( name+" html_eq: "+msg.msg );
+ }else {
+ var got_node=Test.AnotherWay._html_eq_string_to_node( got, "got", msg );
+ if( msg.msg!=null ) {
+ this.fail( name+" html_eq: "+msg.msg );
+ }else {
+ var expected_loc_base=Test.AnotherWay._html_eq_get_loc_base( expected );
+ var got_loc_base=Test.AnotherWay._html_eq_get_loc_base( got );
+ if( Test.AnotherWay._html_eq_node( expected_node, got_node, [], msg, expected_loc_base, got_loc_base ) ) {
+ this.ok( 1, name );
+ }else {
+ var msg=name+" html_eq "+msg.msg;
+ var expected_str=Test.AnotherWay._html_eq_node_to_string( expected_node );
+ var got_str=Test.AnotherWay._html_eq_node_to_string( got_node );
+ msg+=".\n got html: "+got_str;
+ msg+=".\n expected html: "+expected_str;
+ this.fail( msg );
+ }
+ }
+ }
+}
+Test.AnotherWay._debug_pane_print=function( msg )
+{
+ var d=new Date();
+ var p=document.createElement( "p" );
+ p.appendChild( document.createTextNode( d.toLocaleTimeString()+" "+msg ) );
+ var debug_pane=document.getElementById( "debug" );
+ debug_pane.appendChild( p );
+ var debug_tab=document.getElementById( "debug_tab" );
+ var results_tab=document.getElementById( "results_tab" );
+ debug_tab.style.visibility="visible";
+ results_tab.style.visibility="visible";
+}
+Test.AnotherWay._test_object_t.prototype.debug_print=function( msg )
+{
+ Test.AnotherWay._debug_pane_print( this.name+": "+msg );
+}
+Test.AnotherWay._test_object_t.prototype.delay_call=function()
+{
+ var timeout_ms=200;
+ for( var i=0; i<arguments.length; ++i ) {
+ if( typeof( arguments[i] )!="function" ) {
+ timeout_ms=3000*arguments[i];
+ }else {
+ var action={ action_kind: "call", call_delay_milliseconds: timeout_ms, call_fn: arguments[i] };
+ this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+ this.delay_actions.push( action );
+ }
+ }
+}
+Test.AnotherWay._test_object_t.prototype.open_window=function( url, fn, timeout_seconds )
+{
+ if( timeout_seconds==null ) {
+ timeout_seconds=4;
+ }
+ var no_close=document.getElementById( "dont_close_test_windows" );
+ var action={ action_kind: "window", wnd_url: url.toString(), wnd_wnd: null, wnd_fn: fn, wnd_timeout_milliseconds: timeout_seconds*1000, wnd_no_close: no_close.checked };
+ this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+ this.delay_actions.push( action );
+}
+Test.AnotherWay._test_object_t.prototype.replay_events=function( wnd, events )
+{
+ if( Test.AnotherWay._g_no_record_msg!=null ) {
+ this.fail( "replay_events: "+Test.AnotherWay._g_no_record_msg );
+ }else {
+ var action={ action_kind: "replay", replay_wnd: wnd, replay_events: events.events, replay_event_i: null, replay_checkpoints: events.checkpoints };
+ this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+ this.delay_actions.push( action );
+ }
+}
+Test.AnotherWay._action_estimate_milliseconds=function( action )
+{
+ var ms=0;
+ if( action.action_kind=="call" ) {
+ ms=action.call_delay_milliseconds;
+ }else if( action.action_kind=="window" ) {
+ ms=0;
+ }else if( action.action_kind=="replay" ) {
+ ms=0;
+ for( var i=0; i<action.replay_events.length; ++i ) {
+ ms+=action.replay_events[i]["time"]-0;
+ }
+ }
+ return ms;
+}
+
+Test.AnotherWay._g_timeout_granularity=200;
+Test.AnotherWay._g_tests_queue=[]; // vector of { url: string, test_objects : array of test_object_t, test_object_i: int, wait_msg: <p> object, loading_timeout_milliseconds: int, timeout_id: id }
+
+// load one html page, schedule further processing
+Test.AnotherWay._run_test_page=function( id, called_from_outside )
+{
+ if( id.match( /^test(\d+)/ ) ) {
+ id=RegExp.$1;
+ Test.AnotherWay._g_tests_queue.push( {
+ url: Test.AnotherWay._g_test_page_urls[id].url,
+ convention: Test.AnotherWay._g_test_page_urls[id].convention,
+ test_objects: []
+ } );
+ if( Test.AnotherWay._g_tests_queue.length==1 ) {
+ if( !called_from_outside ) {
+ // Crap. Be careful stepping around.
+ // For Mozilla and Opera, when this file is included into the frameset page that is in another directory (and _g_outside_path_correction!=null)
+ // but the test pages are started from within it (by "run" buttons), then:
+ // depending on whether the page is the first one loaded into the test frame or not,
+ // the base url for relative test pages differs.
+ // Crap, like I said.
+ Test.AnotherWay._g_tests_queue[0].suppress_outside_path_correction=true;
+ }
+ Test.AnotherWay._start_loading_page();
+ }
+ }
+}
+Test.AnotherWay._load_next_page=function()
+{
+ Test.AnotherWay._g_tests_queue.splice( 0, 1 );
+ if( Test.AnotherWay._g_tests_queue.length>0 ) {
+ Test.AnotherWay._start_loading_page();
+ }else {
+ if( !Test.AnotherWay._g_test_frame_no_clear ) {
+ Test.AnotherWay._g_test_iframe.location.replace( "about:blank" );
+ }
+ report_results();
+ }
+}
+Test.AnotherWay._g_opera_path_correction=null; // ugly wart to support opera
+Test.AnotherWay._g_outside_path_correction=null; // ugly wart to accomodate Opera and Mozilla, where relative url relates to the directory where the page that calls this function is located
+Test.AnotherWay._set_iframe_location=function( iframe, loc, outside_path_correction )
+{
+ // allow to load only locations with the same origin
+ var proto_end=loc.indexOf( "://" );
+ if( proto_end!=-1 ) { // otherwise, it's safe to assume (for Opera, Mozilla and IE ) that loc will be treated as relative
+ var main_loc=window.location.href;
+ var host_end=loc.substring( proto_end+3 ).indexOf( "/" );
+ var ok=false;
+ if( host_end!=-1 ) {
+ var loc_origin=loc.substring( 0, proto_end+3+host_end+1 );
+ if( main_loc.length>=loc_origin.length && main_loc.substring( 0, loc_origin.length )==loc_origin ) {
+ ok=true;
+ }
+ }
+ if( !ok ) {
+ return { msg: "test pages may have only urls with the same origin as "+main_loc };
+ }
+ }
+ // opera cannot handle urls relative to file:// without assistance
+ if( window.opera!=null && window.location.protocol=="file:" && loc.indexOf( ":" )==-1 ) {
+ var base=window.location.href;
+ var q_pos=base.indexOf( "?" );
+ if( q_pos!=-1 ) {
+ base=base.substring( 0, q_pos );
+ }
+ var slash_pos=base.lastIndexOf( "/" );
+ if( slash_pos!=-1 ) {
+ base=base.substring( 0, slash_pos+1 );
+ Test.AnotherWay._g_opera_path_correction=base;
+ loc=base+loc;
+ }
+ }
+ // if this function is called from another page, and if that page is in another directory, correction is needed
+ if( outside_path_correction!=null ) {
+ var pos=loc.indexOf( outside_path_correction );
+ if( pos==0 ) {
+ loc=loc.substring( outside_path_correction.length+1 );
+ }
+ }
+ if( iframe.location!=null ) {
+ iframe.location.replace( loc );
+ }else {
+ iframe.src=loc;
+ }
+ return {};
+}
+Test.AnotherWay._start_loading_page=function()
+{
+ var test_page=Test.AnotherWay._g_tests_queue[0];
+ test_page.loading_timeout_milliseconds=20000;
+ test_page.timeout_id=setTimeout( Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity );
+ test_page.wait_msg=Test.AnotherWay._print_counter_result( test_page.url, "loading...", test_page.loading_timeout_milliseconds, "loading" );
+ if( test_page.convention=="jsan" ) {
+ // the tests in that page will run when it's loading, so the test object must be ready
+ Test.AnotherWay._g_test_object_for_jsan=new Test.AnotherWay._test_object_t( test_page.url );
+ }
+ var outside_path_correction=null;
+ if( Test.AnotherWay._g_outside_path_correction!=null && !test_page.suppress_outside_path_correction ) {
+ outside_path_correction=Test.AnotherWay._g_outside_path_correction;
+ }
+ var result=Test.AnotherWay._set_iframe_location( Test.AnotherWay._g_test_iframe, test_page.url, outside_path_correction );
+ if( result.msg!=null ) {
+ Test.AnotherWay._unprint_result( test_page.wait_msg );
+ Test.AnotherWay._print_result( test_page.url, result.msg, "badtest", null );
+ Test.AnotherWay._load_next_page();
+ }
+}
+
+Test.AnotherWay._loading_timeout=function()
+{
+ var test_page=Test.AnotherWay._g_tests_queue[0];
+ test_page.loading_timeout_milliseconds-=Test.AnotherWay._g_timeout_granularity;
+ if( test_page.loading_timeout_milliseconds>0 ) {
+ Test.AnotherWay._update_msg_counter( test_page.wait_msg, (test_page.loading_timeout_milliseconds/1000).toFixed() );
+ test_page.timeout_id=setTimeout( Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity );
+ }else {
+ Test.AnotherWay._unprint_result( test_page.wait_msg );
+ Test.AnotherWay._print_result( test_page.url, "Unable to load test page. Timeout expired", "badtest", null );
+ Test.AnotherWay._load_next_page();
+ }
+}
+
+Test.AnotherWay._strip_query_and_hash=function( s )
+{
+ var i=s.lastIndexOf( "#" );
+ if( i!=-1 ) {
+ s=s.substring( 0, i );
+ }
+ i=s.lastIndexOf( "?" );
+ if( i!=-1 ) {
+ s=s.substring( 0, i );
+ }
+ return s;
+}
+Test.AnotherWay._is_url_loaded=function( url, wnd )
+{
+ var loaded=false;
+ if( wnd!=null && wnd.location!=null ) {
+ // after some popup blocker interference, location may behave strange..
+ var location_s="";
+ location_s+=wnd.location;
+ if( location_s!="" ) {
+ var pathname=wnd.location.pathname;
+ var expected_url=url;
+ var i=expected_url.lastIndexOf( "#" );
+ if( i!=-1 ) {
+ expected_url=expected_url.substring( 0, i );
+ }
+ i=expected_url.lastIndexOf( "?" );
+ if( i!=-1 ) {
+ expected_url=expected_url.substring( 0, i );
+ }
+ i=expected_url.lastIndexOf( "/" );
+ if( i!=-1 && i!=expected_url.length-1 ) {
+ expected_url=expected_url.substring( i+1 );
+ }
+ i=pathname.indexOf( expected_url )
+ if( wnd.location.href==url || (i!=-1 && i==pathname.length-expected_url.length) ) {
+ if( /*window.opera==null*/wnd.document.readyState==null || wnd.document.readyState=="complete" ) { // for opera (and IE?), getElementById does not work until..
+ loaded=true;
+ }
+ }
+ }
+ }
+ return loaded;
+}
+// find and run all test functions in the g_cur_page html page.
+Test.AnotherWay._test_page_onload=function()
+{
+ if( Test.AnotherWay._g_tests_queue.length==0 ) {
+ return;
+ }
+ var test_page=Test.AnotherWay._g_tests_queue[0];
+ if( !Test.AnotherWay._is_url_loaded( test_page.url, Test.AnotherWay._g_test_iframe ) ) {
+ return;
+ }
+ clearTimeout( test_page.timeout_id );
+ Test.AnotherWay._unprint_result( test_page.wait_msg );
+
+ if( test_page.convention=="anotherway" ) {
+ // get test function names (those beginning with "test")
+ if( typeof( Test.AnotherWay._g_test_iframe.document.scripts )!='undefined' ) { // IE
+ for( var i=0; i<Test.AnotherWay._g_test_iframe.document.scripts.length; ++i ) {
+ var script_text=Test.AnotherWay._g_test_iframe.document.scripts[i].text;
+ var fun_sig="function test";
+ var fun_start=script_text.indexOf( fun_sig );
+
+ while( fun_start!=-1 ) {
+ script_text=script_text.substring( fun_start, script_text.length );
+ var fun_end=script_text.indexOf( '(' );
+ var fun_name=script_text.substring( "function ".length, fun_end );
+ var whitespace = fun_name.indexOf( ' ' );
+ if (whitespace >= 0)
+ fun_name = fun_name.substring( 0, whitespace );
+ test_page.test_objects.push( new Test.AnotherWay._test_object_t( fun_name ) );
+ script_text=script_text.substring( fun_end, script_text.length );
+ fun_start=script_text.indexOf( fun_sig );
+ }
+ }
+ }else { // otherwise (not IE) it ought to work like this
+ for( var i in Test.AnotherWay._g_test_iframe) {
+ // Hack to prevent failure in FF3.0b1
+ if (i == "innerWidth" || i == "innerHeight") { continue; }
+ if( typeof( Test.AnotherWay._g_test_iframe[i] )=='function' ) {
+ if( i.substring( 0, 4 )=="test" ) {
+ test_page.test_objects.push( new Test.AnotherWay._test_object_t( i ) );
+ }
+ }
+ }
+ }
+ }else if( test_page.convention=="jsan" ) {
+ // the test object is already filled with results
+ test_page.test_objects.push( Test.AnotherWay._g_test_object_for_jsan );
+ }
+
+ if( test_page.test_objects.length==0 ) {
+ Test.AnotherWay._print_result( test_page.url, "No test functions defined in the page", "badtest", null );
+ Test.AnotherWay._load_next_page();
+ return;
+ }
+
+ test_page.wait_msg=Test.AnotherWay._print_result( test_page.url, "running tests..<span class=\"counter\">"+test_page.test_objects.length+"</span>", "running", null );
+
+ test_page.test_object_i=0;
+ Test.AnotherWay._run_more_tests();
+}
+
+Test.AnotherWay._handle_exception=function( o, e, title )
+{
+ var s=title+": "+typeof( e )+": ";
+ if( e.message!=null ) {
+ s+=e.message;
+ }else if( e.description!=null ) {
+ s+=e.description;
+ }else {
+ s+=e.toString();
+ }
+// if( e.location!=null ) { // XXX figure out how to display exception location if it's present (like in mozilla)
+// s+=" location: "+e.location.toString();
+// }
+ o.exception=s;
+ s=[];
+ if( e.stack ) {
+ var lines=e.stack.split( "\n" );
+ for( var i=0; i<lines.length; ++i ) {
+ // format of the line: func_name(args)@file_name:line_no
+ if( lines[i].match( /(\w*)\(([^\)]*)\)@(.*):([^:]*)$/ ) ) {
+ var func_name=RegExp.$1;
+ if( func_name.length==0 ) {
+ func_name="<anonymous>";
+ }
+ s.push( "in "+func_name+"( "+RegExp.$2+") at "+RegExp.$3+" line "+RegExp.$4+"\n" );
+ }
+ }
+ }
+ o.exception_stack=s;
+}
+
+Test.AnotherWay._run_more_tests=function()
+{
+ var test_page=Test.AnotherWay._g_tests_queue[0];
+ while( test_page.test_object_i<test_page.test_objects.length ) {
+ Test.AnotherWay._update_msg_counter( test_page.wait_msg, (1+test_page.test_object_i)+"/"+test_page.test_objects.length );
+ var o=test_page.test_objects[test_page.test_object_i];
+ if( test_page.convention=="anotherway" ) {
+ try {
+ Test.AnotherWay._g_test_iframe[o.name]( o );
+ }catch( e ) {
+ Test.AnotherWay._handle_exception( o, e, "" );
+ }
+ } // for "jsan" convention, test has run already
+ if( o.delay_actions.length>0 || o.wait_result_milliseconds>0 ) {
+ o.delay_total_milliseconds_left+=o.wait_result_milliseconds;
+ Test.AnotherWay._delay_actions_timeout();
+ return;
+ }
+ ++test_page.test_object_i;
+ }
+ Test.AnotherWay._unprint_result( test_page.wait_msg );
+ Test.AnotherWay._print_result( test_page.url, null, null, test_page.test_objects );
+ Test.AnotherWay._load_next_page();
+}
+
+Test.AnotherWay._delay_actions_timeout=function()
+{
+ var test_page=Test.AnotherWay._g_tests_queue[0];
+ var test_object=test_page.test_objects[test_page.test_object_i];
+ var finished=true;
+ if( test_object.delay_action_i==null ) {
+ // set up to start first action
+ test_object.delay_action_i=-1;
+ }else {
+ // perform current action
+ var milliseconds_passed=(new Date()).getTime()-test_object.delay_prev_timer_time;
+ test_object.delay_current_milliseconds_left-=milliseconds_passed;
+ test_object.delay_total_milliseconds_left-=milliseconds_passed;
+ finished=Test.AnotherWay._delay_continue_action( test_object, milliseconds_passed );
+ }
+ while( finished && test_object.delay_action_i<test_object.delay_actions.length ) {
+ ++test_object.delay_action_i; // start next action
+ finished=Test.AnotherWay._delay_start_action( test_object );
+ }
+ if( test_object.delay_action_i<=test_object.delay_actions.length ) { // any more actions left ?
+ test_object.delay_prev_timer_time=(new Date()).getTime();
+ var next_timeout=Test.AnotherWay._g_timeout_granularity;
+ if( test_object.delay_current_milliseconds_left<next_timeout ) {
+ next_timeout=test_object.delay_current_milliseconds_left;
+ }
+ if( test_object.second_wait_msg!=null ) {
+ Test.AnotherWay._update_msg_counter( test_object.second_wait_msg, (test_object.delay_total_milliseconds_left/1000).toFixed() );
+ }
+ setTimeout( Test.AnotherWay._delay_actions_timeout, next_timeout );
+ }else { // no more actions left. run the next test.
+ if( test_object.second_wait_msg!=null ) {
+ Test.AnotherWay._unprint_result( test_object.second_wait_msg );
+ test_object.second_wait_msg=null;
+ }
+ ++test_page.test_object_i;
+ Test.AnotherWay._run_more_tests();
+ }
+}
+Test.AnotherWay._delay_start_action=function( test_object )
+{
+ var finished=false;
+ var wait_msg="";
+ if( test_object.delay_action_i==test_object.delay_actions.length ) {
+ if( test_object.wait_result_milliseconds>0 ) {
+ test_object.delay_current_milliseconds_left=test_object.wait_result_milliseconds; // wait for result
+ wait_msg="waiting for results..";
+ }else {
+ ++test_object.delay_action_i; // dont wait for result
+ }
+ }else {
+ var action=test_object.delay_actions[test_object.delay_action_i];
+ if( action.action_kind=="call" ) {
+ test_object.delay_current_milliseconds_left=action.call_delay_milliseconds;
+ wait_msg="performing delayed calls..";
+ }else if( action.action_kind=="window" ) {
+ if( Test.AnotherWay._g_opera_path_correction!=null && action.wnd_url.indexOf( ":" )==-1 ) {
+ action.wnd_url=Test.AnotherWay._g_opera_path_correction+action.wnd_url;
+ }
+ action.wnd_wnd=window.open( action.wnd_url, "_blank" );
+ if( action.wnd_wnd==null ) {
+ finished=true;
+ test_object.fail( "unable to open window for "+action.wnd_url );
+ }else {
+ test_object.delay_current_milliseconds_left=action.wnd_timeout_milliseconds;
+ wait_msg="opening window..";
+ }
+ }else if( action.action_kind=="replay" ) {
+ if( action.replay_events.length==0 ) {
+ finished=true;
+ }else {
+ action.replay_event_i=0;
+ test_object.delay_current_milliseconds_left=action.replay_events[0]["time"];
+ wait_msg="replaying events..";
+ }
+ }
+ }
+ if( test_object.second_wait_msg!=null ) {
+ Test.AnotherWay._unprint_result( test_object.second_wait_msg );
+ }
+ if( wait_msg!="" ) {
+ var test_page=Test.AnotherWay._g_tests_queue[0];
+ test_object.second_wait_msg=Test.AnotherWay._print_counter_result( test_page.url, wait_msg, test_object.delay_total_milliseconds_left, "waiting" );
+ }else {
+ test_object.second_wait_msg=null;
+ }
+ return finished;
+}
+Test.AnotherWay._delay_continue_action=function( test_object, milliseconds_passed )
+{
+ var finished=test_object.delay_current_milliseconds_left<=0;
+ if( test_object.delay_action_i==test_object.delay_actions.length ) { // action is "waiting for results"
+ if( test_object.n_plan!=null && test_object.n_plan==test_object.n_ok+test_object.n_fail ) {
+ finished=true; // if all assertions results are recorded, don't wait any more
+ }
+ if( finished ) {
+ ++test_object.delay_action_i; // move on to the next test
+ }
+ }else {
+ var action=test_object.delay_actions[test_object.delay_action_i];
+ if( action.action_kind=="call" ) {
+ if( finished ) {
+ try {
+ action.call_fn();
+ }catch( e ) {
+ Test.AnotherWay._handle_exception( test_object, e, "in delay_call" );
+ }
+ }
+ }else if( action.action_kind=="window" ) {
+ test_object.delay_total_milliseconds_left+=milliseconds_passed; // for "window", the countdown is suspended since it's unknown how long it will take
+ if( Test.AnotherWay._is_url_loaded( action.wnd_url, action.wnd_wnd ) ) {
+ try {
+ action.wnd_fn( action.wnd_wnd );
+ }catch( e ) {
+ Test.AnotherWay._handle_exception( test_object, e, "in open_window function call" );
+ }
+ finished=true;
+ }else if( finished ) {
+ test_object.fail( "unable to open window for url '"+action.wnd_url+"'. timeout expired" );
+ }
+ }else if( action.action_kind=="replay" ) {
+ if( finished ) {
+// try {
+ Test.AnotherWay._delay_replay_event( test_object, action.replay_wnd, action.replay_events[action.replay_event_i], action.replay_checkpoints );
+// }catch( e ) { // disabled, until I know how to gel location info from an exception
+// Test.AnotherWay._handle_exception( test_object, e, "while replaying event" );
+// }
+ ++action.replay_event_i;
+ finished=action.replay_event_i==action.replay_events.length;
+ if( !finished ) {
+ test_object.delay_current_milliseconds_left=action.replay_events[action.replay_event_i]["time"];
+ }
+ }
+ }
+ }
+ return finished;
+}
+Test.AnotherWay._delay_replay_event=function( test_object, wnd, event, checkpoints )
+{
+ if( event.type=="_checkpoint" ) {
+ var checkpoint_n=event.which;
+ var prev_n_fail=test_object.n_fail;
+ checkpoints[checkpoint_n]( test_object, wnd );
+ var flash_color= prev_n_fail==test_object.n_fail ? "#2f2" : "#f22" ;
+ Test.AnotherWay._record_flash_border( flash_color );
+ }else if( event.type=="click" || event.type=="mouseover" || event.type=="mouseout" || event.type=="mousemove" || event.type=="mousedown" || event.type=="mouseup" ) {
+ var target=Test.AnotherWay._record_node_path_to_node( event["target"], wnd.document );
+ if( target!=null ) {
+ Test.AnotherWay._record_control_update_highlight( target, "ball", event );
+ var e=wnd.document.createEvent( "MouseEvents" );
+ var related_target=Test.AnotherWay._record_node_path_to_node( event["relatedTarget"], wnd.document );
+ e.initMouseEvent(
+ event["type"],
+ event["cancelable"],
+ event["bubbles"],
+ wnd.document.defaultView,
+ event["detail"],
+ event["screenX"],
+ event["screenY"],
+ event["clientX"],
+ event["clientY"],
+ event["ctrlKey"],
+ event["altKey"],
+ event["shiftKey"],
+ event["metaKey"],
+ event["button"],
+ Test.AnotherWay._record_node_path_to_node( event["relatedTarget"], wnd.document )
+ );
+ // Firefox 1.0.6 somehow loses relatedTarget somewhere on the way. Pass through our own, for those who choose to care.
+ e.passThroughRelatedTarget=related_target;
+ target.dispatchEvent( e );
+ }
+ }else if( event.type=="keyup" || event.type=="keydown" || event.type=="keypress" ) {
+ var e=wnd.document.createEvent( "KeyboardEvents" ); // forget it. Apparently it's not supported neither by mozilla nor by opera.
+ e.initKeyboardEvent(
+ event["type"],
+ event["cancelable"],
+ event["bubbles"],
+ wnd.document.defaultView,
+ event["which"],
+ event["which"],
+ event["ctrlKey"],
+ event["altKey"],
+ event["shiftKey"],
+ event["metaKey"],
+ false
+ );
+ wnd.document.dispatchEvent( e );
+ }
+}
+
+Test.AnotherWay._print_counter_result=function( url, msg, milliseconds, style )
+{
+ return Test.AnotherWay._print_result( url, msg+"<span class=\"counter\">"+(milliseconds/1000).toFixed()+"</span>", style, null );
+}
+
+Test.AnotherWay._g_result_count=0; // for assigning unique ids to result paragraphs
+
+// number of pages tested
+Test.AnotherWay._g_ok_pages=0;
+Test.AnotherWay._g_fail_pages=0;
+
+Test.AnotherWay._print_result=function( url, msg, style, test_objects )
+{
+ var results=document.getElementById( "results" );
+ var r=results.appendChild( document.createElement( "p" ) );
+ r.id="result"+Test.AnotherWay._g_result_count;
+ ++Test.AnotherWay._g_result_count;
+ r.onclick=Test.AnotherWay._toggle_detail;
+ var text="<span class=\"bullet\">&nbsp;&nbsp;&nbsp;</span>&nbsp;";
+ if( url!="" ) {
+ text+=url+": ";
+ }
+ if( msg!=null ) {
+ text+=msg;
+ }
+ if( test_objects!=null ) {
+ // compose summary and detail texts
+ var total_ok=0;
+ var total_detail_ok=0;
+ var total_fail=0;
+ var total_detail_fail=0;
+ var no_plan=0;
+
+ var detail=results.appendChild( document.createElement( "div" ) );
+
+ if( r.id.match( /^result(\d+)$/ ) ) {
+ detail.id="result_detail"+RegExp.$1;
+ }
+
+ for( var i=0; i<test_objects.length; ++i ) {
+ var o=test_objects[i];
+ var p;
+ var p_text;
+ p=document.createElement( "P" );
+ Test.AnotherWay._set_css_class( p, "result_detail" );
+ p_text=o.name;
+ if( o.n_fail>0 || o.exception || (o.n_plan!=null && o.n_plan!=o.n_ok+o.n_fail) || (o.n_plan==null && o.n_ok==0 && o.n_fail==0)) {
+ ++total_fail;
+ p_text+=" <span class=\"fail\">";
+ if( o.n_plan!=null && o.n_plan!=o.n_ok+o.n_fail) {
+ p_text+="planned "+o.n_plan+" assertions but got "+(o.n_ok+o.n_fail)+"; ";
+ }
+ if(o.n_plan==null && o.n_ok==0 && o.n_fail==0) {
+ p_text+="test did not output anything";
+ }else {
+ p_text+=" fail "+o.n_fail;
+ }
+ p_text+="</span>";
+ }else {
+ ++total_ok;
+ }
+ p_text+=" ok "+o.n_ok;
+ if( o.n_plan==null ) {
+ no_plan=1;
+ p_text+=" <span class=\"warning\">no plan</span>";
+ }
+ p.innerHTML=p_text;
+ detail.appendChild( p );
+ if( o.exception ) {
+ p=document.createElement( "P" );
+ Test.AnotherWay._set_css_class( p, "result_exception_detail" );
+ p.innerHTML="<span class=\"fail\">exception:</span> "+o.exception;
+ detail.appendChild( p );
+ p=document.createElement( "P" );
+ Test.AnotherWay._set_css_class( p, "result_exception_stack_detail" );
+ p.innerHTML=o.exception_stack.join( "<br/>" );
+ detail.appendChild( p );
+ }
+ for( var ii=0; ii<o.assertions.length; ++ii ) {
+ var oo=o.assertions[ii];
+ var status=oo.ok ? "ok" : "<span class=\"fail\">fail</span>";
+ p=document.createElement( "P" );
+ Test.AnotherWay._set_css_class( p, "result_micro_detail" );
+ p.innerHTML=status;
+ p.appendChild( document.createTextNode( " "+oo.name ) );
+ detail.appendChild( p );
+ }
+ total_detail_ok+=o.n_ok;
+ total_detail_fail+=o.n_fail;
+ }
+ if( total_fail || total_detail_fail ) {
+ text+=" fail "+total_fail;
+ }
+ text+=" ok "+total_ok+" (detailed:";
+ if( total_fail || total_detail_fail ) {
+ text+=" fail "+total_detail_fail;
+ }
+ text+=" ok "+total_detail_ok+")";
+ if( no_plan ) {
+ text+=" <span class=\"warning\">no plan</span>";
+ }
+ style= total_fail==0 ? "ok" : "fail";
+ detail.style.display= style=="fail" ? "block" : "none";
+ detail.style.cursor="text";
+ }
+ if( style!=null ) {
+ Test.AnotherWay._set_css_class( r, style );
+ if( style=="ok" ) {
+ ++Test.AnotherWay._g_ok_pages;
+ }else if( style=="fail" || style=="badtest" ) {
+ ++Test.AnotherWay._g_fail_pages;
+ }
+ var pages_total="";
+ if( Test.AnotherWay._g_fail_pages>0 ) {
+ pages_total+=" fail "+Test.AnotherWay._g_fail_pages;
+ }
+ pages_total+=" ok "+Test.AnotherWay._g_ok_pages;
+ Test.AnotherWay._update_results_total( pages_total );
+ }
+ r.innerHTML=text;
+ if( results.scrollHeight!=null && results.scrollTop!=null && results.offsetHeight!=null ) {
+ results.scrollTop=results.scrollHeight-results.offsetHeight;
+ }
+ // when test_objects is not null, the results are final - good time to clean up
+ if( test_objects!=null ) {
+ for( var i=0; i<test_objects.length; ++i ) {
+ var actions=test_objects[i].delay_actions;
+ for( var action_i=0; action_i<actions.length; ++action_i ) {
+ var action=actions[action_i];
+ if( action.action_kind=="window" && action.wnd_wnd!=null && !action.wnd_no_close ) {
+ action.wnd_wnd.close();
+ action.wnd_wnd=null;
+ }
+ }
+ }
+ }
+ return r;
+}
+Test.AnotherWay._unprint_result=function( child )
+{
+ var results=document.getElementById( "results" );
+ results.removeChild( child );
+}
+Test.AnotherWay._toggle_detail=function()
+{
+ if( this.id.match( /^result(\d+)$/ ) ) {
+ var detail=document.getElementById( "result_detail"+RegExp.$1 );
+ if( detail!=null ) {
+ if( detail.style.display=="none" ) {
+ detail.style.display="block";
+ }else if( detail.style.display=="block" ) {
+ detail.style.display="none";
+ }
+ }
+ }
+}
+Test.AnotherWay._update_msg_counter=function( msg, text )
+{
+ for( var i=0; i<msg.childNodes.length; ++i ) {
+ var item=msg.childNodes[i];
+ if( item.nodeName=="SPAN" && Test.AnotherWay._get_css_class( item )=="counter" ) {
+ item.innerHTML=text;
+ }
+ }
+}
+Test.AnotherWay._update_results_total=function( msg )
+{
+ var total=document.getElementById( "total" );
+ if( total ) {
+ total.innerHTML=msg;
+ }
+}
+Test.AnotherWay._results_clear_onclick=function()
+{
+ var results=document.getElementById( "results" );
+ results.innerHTML="";
+ Test.AnotherWay._update_results_total( "" );
+ Test.AnotherWay._g_ok_pages=0;
+ Test.AnotherWay._g_fail_pages=0;
+ var debug=document.getElementById( "debug" );
+ debug.innerHTML="";
+}
+
+Test.AnotherWay._get_css_class=function( o )
+{
+ var c=o.getAttribute( "className" );
+ if( c==null || c=="" ) {
+ c=o.getAttribute( "class" );
+ }
+ return c;
+}
+Test.AnotherWay._set_css_class=function( o, css_class )
+{
+ o.setAttribute( "className", css_class );
+ o.setAttribute( "class", css_class );
+}
+
+Test.AnotherWay._tab_onclick=function()
+{
+ var tab=this;
+ var tabs=[ document.getElementById( "debug_tab" ), document.getElementById( "results_tab" ) ];
+ var panes=[ document.getElementById( "debug" ), document.getElementById( "results" ) ];
+ for( var i=0; i<tabs.length; ++i ) {
+ if( tab==tabs[i] ) {
+ Test.AnotherWay._set_css_class( tabs[i], "active_tab" );
+ panes[i].style.display="block";
+ }else {
+ Test.AnotherWay._set_css_class( tabs[i], "inactive_tab" );
+ panes[i].style.display="none";
+ }
+ }
+}
+Test.AnotherWay._tab_mouseover=function()
+{
+ if( Test.AnotherWay._get_css_class( this )=="inactive_tab" ) {
+ Test.AnotherWay._set_css_class( this, "inactive_mouseover_tab" );
+ }
+}
+Test.AnotherWay._tab_mouseout=function()
+{
+ if( Test.AnotherWay._get_css_class( this )=="inactive_mouseover_tab" ) {
+ Test.AnotherWay._set_css_class( this, "inactive_tab" );
+ }
+}
+
+// recording mouse input
+Test.AnotherWay._record_check_onfocus=function()
+{
+ var o=this;
+ var check_select=o.type!="text";
+ var div=document.getElementById( "record_div" );
+ var inputs=div.getElementsByTagName( "input" );
+ for( var i=0; i<inputs.length; ++i ) {
+ var input=inputs[i];
+ if( input.type=="radio" ) {
+ if( input.value=="select" ) {
+ input.checked=check_select;
+ }else if( input.value=="input" ) {
+ input.checked=!check_select;
+ }
+ }
+ }
+}
+
+Test.AnotherWay._g_no_record_msg=null; // not null - recording is unavailable
+Test.AnotherWay._g_record_timeout_cnt=0; // opening window for a page for recording
+Test.AnotherWay._g_record_url=null;
+Test.AnotherWay._g_record_wnd=null;
+Test.AnotherWay._g_record_random_id=null; // added to element ids of record_control div so that they do not clash with ids already in the page for which input is recorded
+Test.AnotherWay._g_record_keydown=null; // recording control - which key is down
+Test.AnotherWay._g_record_ctrl_keydown=false;
+Test.AnotherWay._g_record_shift_keydown=false;
+Test.AnotherWay._g_record_control_visible=true; // recording control ui state
+Test.AnotherWay._g_record_started;
+Test.AnotherWay._g_record_paused;
+Test.AnotherWay._g_record_include_mousemove=false;
+Test.AnotherWay._g_record_start_time; // for time references
+Test.AnotherWay._g_record_pause_start_time;
+Test.AnotherWay._g_record_update_time_interval; // showing time in the control ui
+Test.AnotherWay._g_record_waiting_for_results=false; // waiting for results window to open
+Test.AnotherWay._g_record_events; // recorded events
+Test.AnotherWay._g_record_under_cursor; // track element under cursor
+Test.AnotherWay._g_record_checkpoint_count; // for checkpoint numbering
+Test.AnotherWay._g_record_mouse_over_record_control; // for avoiding record control highlight on mouseover
+Test.AnotherWay._g_record_highlighted_element={ element: null, x: null, y: null };
+
+Test.AnotherWay._record_control_get_element=function( id )
+{
+ if( Test.AnotherWay._g_record_wnd!=null && Test.AnotherWay._g_record_wnd.document!=null ) {
+ return Test.AnotherWay._g_record_wnd.document.getElementById( id+Test.AnotherWay._g_record_random_id );
+ }else {
+ return null;
+ }
+}
+Test.AnotherWay._record_start_onclick=function() // "record" button on the run_tests.html: open a window for a page for which input is recorded
+{
+ if( Test.AnotherWay._g_no_record_msg!=null ) {
+ alert( Test.AnotherWay._g_no_record_msg );
+ return;
+ }
+ if( Test.AnotherWay._g_record_timeout_cnt>0
+ || (Test.AnotherWay._g_record_wnd!=null && (Test.AnotherWay._g_record_wnd.closed!=null && !Test.AnotherWay._g_record_wnd.closed)) ) { // in opera, closed is null.
+ alert( "there is already window opened for recording input for a page "+Test.AnotherWay._g_record_url );
+ return;
+ }
+ var div=document.getElementById( "record_div" );
+ var inputs=div.getElementsByTagName( "input" );
+ var url=null;
+ for( var i=0; i<inputs.length; ++i ) {
+ var input=inputs[i];
+ if( input.type=="radio" ) {
+ if( input.value=="select" && input.checked ) {
+ var index=document.getElementById( "record_select" ).selectedIndex;
+ if( index>0 ) {
+ url=Test.AnotherWay._g_test_page_urls[index-1].url;
+ }
+ }else if( input.value=="input" && input.checked ) {
+ url=document.getElementById( "record_input" ).value;
+ }
+ }
+ }
+ if( url!=null ) {
+ Test.AnotherWay._g_record_url=url;
+ Test.AnotherWay._g_record_wnd=window.open( url, "_blank" );
+ if( Test.AnotherWay._g_record_wnd==null ) {
+ alert( "unable to open new window for a page: "+url );
+ }else {
+ Test.AnotherWay._g_record_timeout_cnt=50;
+ setTimeout( Test.AnotherWay._record_window_timeout, 100 );
+ }
+ }
+}
+Test.AnotherWay._record_window_timeout=function()
+{
+ if( Test.AnotherWay._is_url_loaded( Test.AnotherWay._g_record_url, Test.AnotherWay._g_record_wnd ) ) {
+ Test.AnotherWay._record_window_setup( Test.AnotherWay._g_record_wnd );
+ }else {
+ if( --Test.AnotherWay._g_record_timeout_cnt>0 ) {
+ setTimeout( Test.AnotherWay._record_window_timeout, 100 );
+ }else {
+ alert( "timeout expired while opening new window for a page: "+Test.AnotherWay._g_record_url );
+ Test.AnotherWay._g_record_wnd=null;
+ Test.AnotherWay._g_record_url=null;
+ Test.AnotherWay._g_record_timeout_cnt=0;
+ }
+ }
+}
+Test.AnotherWay._record_control_randomize_id=function( e, r )
+{
+ if( e.id!="" ) {
+ e.id=e.id+r;
+ }
+ for( var c=e.firstChild; c!=null; c=c.nextSibling ) {
+ Test.AnotherWay._record_control_randomize_id( c, r );
+ }
+}
+Test.AnotherWay._record_window_setup=function( wnd ) // insert recording control into the page for which input is recorded
+{
+ Test.AnotherWay._g_record_timeout_cnt=0;
+ var this_div=document.getElementById( "record_control" );
+ var record_control=wnd.document.importNode( this_div, true );
+ Test.AnotherWay._g_record_random_id=(1000*Math.random()).toFixed();
+ Test.AnotherWay._record_control_randomize_id( record_control, Test.AnotherWay._g_record_random_id );
+ Test.AnotherWay._g_record_control_visible=true;
+ Test.AnotherWay._g_record_started=false;
+ Test.AnotherWay._g_record_paused=false;
+ Test.AnotherWay._g_record_checkpoint_count=0;
+ Test.AnotherWay._g_record_mouse_over_record_control=false;
+ var doc=wnd.document;
+ doc.body.appendChild( record_control );
+ // opera sans-serif font is different
+ if( window.opera ) {
+ cursor_over_indicator=Test.AnotherWay._record_control_get_element( "record_cursor_over" );
+ cursor_over_indicator.style.width="18em";
+ cursor_over_indicator.style.height="2em";
+ cursor_over_indicator.style.fontSize="7pt";
+ }
+ doc.addEventListener( "keydown", Test.AnotherWay._record_control_keydown, true );
+ doc.addEventListener( "keyup", Test.AnotherWay._record_control_keyup, true );
+// doc.addEventListener( "keypress", Test.AnotherWay._record_event, true ); // replaying is not supported by any known browser
+
+ doc.body.addEventListener( "mousemove", Test.AnotherWay._record_on_mousemove, true );
+ doc.body.addEventListener( "click", Test.AnotherWay._record_event, true );
+ doc.body.addEventListener( "mouseover", Test.AnotherWay._record_event, true );
+ doc.body.addEventListener( "mouseout", Test.AnotherWay._record_event, true );
+ doc.body.addEventListener( "mousedown", Test.AnotherWay._record_event, true );
+ doc.body.addEventListener( "mouseup", Test.AnotherWay._record_event, true );
+}
+Test.AnotherWay._record_control_key_disabled=function( k )
+{
+ if( k=="c" ) {
+ return !Test.AnotherWay._g_record_started;
+ }else if( k=="p" ) {
+ return !Test.AnotherWay._g_record_started;
+ }else if( k=="s" ) {
+ return Test.AnotherWay._g_record_waiting_for_results;
+ }else {
+ return false;
+ }
+}
+
+Test.AnotherWay._record_control_update_ui=function()
+{
+ var keydown_color="#fff";
+ var disabled_color="#aaa";
+ var button_color="#adf";
+ var active_color="#fdf";
+
+ var display={};
+ display[false]="none";
+ display[true]="inline";
+
+ var s_button=Test.AnotherWay._record_control_get_element( "record_s" );
+ var record_on=Test.AnotherWay._record_control_get_element( "record_on" );
+ var record_off=Test.AnotherWay._record_control_get_element( "record_off" );
+
+ s_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "s" ) ? disabled_color
+ : Test.AnotherWay._g_record_keydown=="s" ? keydown_color : Test.AnotherWay._g_record_started ? active_color : button_color;
+ record_on.style.display=display[!Test.AnotherWay._g_record_started];
+ record_off.style.display=display[Test.AnotherWay._g_record_started];
+
+ var h_button=Test.AnotherWay._record_control_get_element( "record_h" );
+ h_button.style.backgroundColor= Test.AnotherWay._g_record_keydown=="h" ? keydown_color : button_color;
+
+ var p_button=Test.AnotherWay._record_control_get_element( "record_p" );
+ var record_pause_on=Test.AnotherWay._record_control_get_element( "record_pause_on" );
+ var record_pause_off=Test.AnotherWay._record_control_get_element( "record_pause_off" );
+ p_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "p" ) ? disabled_color
+ : Test.AnotherWay._g_record_keydown=="p" ? keydown_color : Test.AnotherWay._g_record_paused ? active_color : button_color;
+ record_pause_on.style.display=display[!Test.AnotherWay._g_record_paused];
+ record_pause_off.style.display=display[Test.AnotherWay._g_record_paused];
+
+ var m_button=Test.AnotherWay._record_control_get_element( "record_m" );
+ var record_include_mousemove=Test.AnotherWay._record_control_get_element( "record_include_mousemove" );
+ var record_omit_mousemove=Test.AnotherWay._record_control_get_element( "record_omit_mousemove" );
+ m_button.style.backgroundColor= Test.AnotherWay._g_record_keydown=="m" ? keydown_color : Test.AnotherWay._g_record_include_mousemove ? active_color : button_color;
+ record_include_mousemove.style.display=display[!Test.AnotherWay._g_record_include_mousemove];
+ record_omit_mousemove.style.display=display[Test.AnotherWay._g_record_include_mousemove];
+
+ var c_button=Test.AnotherWay._record_control_get_element( "record_c" );
+ c_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "c" ) ? disabled_color
+ : Test.AnotherWay._g_record_keydown=="c" ? keydown_color : button_color;
+
+ var record_indicator=Test.AnotherWay._record_control_get_element( "record_indicator" );
+ record_indicator.style.display=display[Test.AnotherWay._g_record_started];
+
+ var pause_indicator=Test.AnotherWay._record_control_get_element( "record_pause_indicator" );
+ pause_indicator.style.display=display[Test.AnotherWay._g_record_paused];
+
+ var record_control=Test.AnotherWay._record_control_get_element( "record_control" );
+ record_control.style.display= Test.AnotherWay._g_record_control_visible ? "block" : "none";
+
+ var shift_button=Test.AnotherWay._record_control_get_element( "record_shift_key" );
+ shift_button.style.backgroundColor= Test.AnotherWay._g_record_shift_keydown ? keydown_color : button_color;
+
+ var ctrl_button=Test.AnotherWay._record_control_get_element( "record_ctrl_key" );
+ ctrl_button.style.backgroundColor= Test.AnotherWay._g_record_ctrl_keydown ? keydown_color : button_color;
+}
+Test.AnotherWay._record_format_time=function( t )
+{
+ t=new Date( t );
+ var m=t.getMinutes();
+ var s=t.getSeconds();
+ var str= m==0 ? "" : m+"m ";
+ str+=s+"s.";
+ return str;
+}
+Test.AnotherWay._record_control_update_time=function()
+{
+ var time_display=Test.AnotherWay._record_control_get_element( "record_time" );
+ if( time_display!=null ) {
+ time_display.innerHTML=Test.AnotherWay._record_format_time( (new Date()).getTime()-Test.AnotherWay._g_record_start_time );
+ }
+}
+Test.AnotherWay._record_control_update_highlight=function( elem, style, event )
+{
+ if( elem==null ) {
+ Test.AnotherWay._record_highlight_border( null );
+ }else {
+ var pos=Test.AnotherWay._get_page_coords( elem );
+ if( style=="ball" || elem!=Test.AnotherWay._g_record_highlighted_element.element || pos.x!=Test.AnotherWay._g_record_highlighted_element.x || pos.y!=Test.AnotherWay._g_record_highlighted_element.y ) {
+ Test.AnotherWay._g_record_highlighted_element={ element: elem, x: pos.x, y: pos.y };
+ Test.AnotherWay._record_highlight_border( elem, style, event );
+ }
+ }
+}
+Test.AnotherWay._record_decode_key=function( event )
+{
+ var k=null;
+ if( event==null ) {
+ k=Test.AnotherWay._g_record_wnd.event.keyCode;
+ }else {
+ k=event.which;
+ }
+ if( k==83 ) {
+ return "s";
+ }else if( k==72 ) {
+ return "h";
+ }else if( k==73 ) {
+ return "i";
+ }else if( k==80 ) {
+ return "p";
+ }else if( k==67 ) {
+ return "c";
+ }else if( k==77 ) {
+ return "m";
+ }else if( k==16 ) {
+ return "shift";
+ }else if( k==17 ) {
+ return "ctrl";
+ }else if( k==18 ) {
+ return "alt";
+ }else if( k==19 ) {
+ return "pause";
+ }else if( k==123 ) {
+ return "f12";
+ }
+ return "";
+}
+Test.AnotherWay._record_control_keydown=function( event )
+{
+ var handled=false;
+ var k=Test.AnotherWay._record_decode_key( event );
+ if( k=="shift" ) {
+ Test.AnotherWay._g_record_shift_keydown=true;
+ }else if( k=="ctrl" ) {
+ Test.AnotherWay._g_record_ctrl_keydown=true;
+ }else if( k!="" && (Test.AnotherWay._g_record_keydown==null || Test.AnotherWay._g_record_keydown==k) ) {
+ if( Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown && !Test.AnotherWay._record_control_key_disabled( k ) ) {
+ Test.AnotherWay._g_record_keydown=k;
+ handled=true;
+ }
+ }else {
+ Test.AnotherWay._g_record_keydown="";
+ }
+ Test.AnotherWay._record_control_update_ui();
+ if( !handled ) {
+// Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+ }
+ return;
+}
+Test.AnotherWay._record_control_keyup=function( event )
+{
+ var handled=false;
+ var k=Test.AnotherWay._record_decode_key( event );
+ if( k=="shift" ) {
+ Test.AnotherWay._g_record_shift_keydown=false;
+ }else if( k=="ctrl" ) {
+ Test.AnotherWay._g_record_ctrl_keydown=false;
+ }else if( k!="" && k==Test.AnotherWay._g_record_keydown && Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown ) {
+ if( k=="s" ) {
+ Test.AnotherWay._g_record_started=!Test.AnotherWay._g_record_started;
+ if( Test.AnotherWay._g_record_started ) {
+ Test.AnotherWay._g_record_events=[];
+ Test.AnotherWay._g_record_start_time=(new Date()).getTime();
+ Test.AnotherWay._record_control_update_time();
+ Test.AnotherWay._g_record_update_time_interval=window.setInterval( Test.AnotherWay._record_control_update_time, 200 );
+ }else {
+ Test.AnotherWay._record_control_update_highlight( null );
+ if( !Test.AnotherWay._g_record_paused ) {
+ window.clearInterval( Test.AnotherWay._g_record_update_time_interval );
+ }
+ Test.AnotherWay._g_record_waiting_for_results=true;
+ // open a new window for self, pass a parameter to dump recorded events as javascript code there
+ // (the easiest way to obtain a document from the same origin, so it's writable, is to open this same page again)
+ Test.AnotherWay._g_record_paused=false;
+ var loc=window.location;
+ loc=loc.protocol+"//"+loc.host+loc.pathname+"?recording_results="+Test.AnotherWay._g_record_random_id;
+ if( window.open( loc, "_blank" )==null ) {
+ alert( "unable to open new window for results" );
+ }
+ }
+ handled=true;
+ }else if( k=="h" ) {
+ Test.AnotherWay._g_record_control_visible=!Test.AnotherWay._g_record_control_visible;
+ handled=true;
+ }else if( k=="p" ) {
+ Test.AnotherWay._g_record_paused=!Test.AnotherWay._g_record_paused;
+ if( Test.AnotherWay._g_record_paused ) {
+ Test.AnotherWay._g_record_pause_start_time=(new Date()).getTime();
+ if( Test.AnotherWay._g_record_started ) {
+ window.clearInterval( Test.AnotherWay._g_record_update_time_interval );
+ }
+ Test.AnotherWay._record_control_update_highlight( null );
+ }else {
+ var pause_duration=(new Date()).getTime()-Test.AnotherWay._g_record_pause_start_time;
+ Test.AnotherWay._g_record_start_time+=pause_duration;
+ Test.AnotherWay._g_record_update_time_interval=window.setInterval( Test.AnotherWay._record_control_update_time, 200 );
+ }
+ handled=true;
+ }else if( k=="m" ) {
+ Test.AnotherWay._g_record_include_mousemove=!Test.AnotherWay._g_record_include_mousemove;
+ handled=true;
+ }else if( k=="c" ) {
+ var o=Test.AnotherWay._record_checkpoint();
+ Test.AnotherWay._record_display_checkpoint( o );
+ Test.AnotherWay._record_flash_border( "#24d" );
+ handled=true;
+ }
+ }
+ Test.AnotherWay._g_record_keydown=null;
+ Test.AnotherWay._record_control_update_ui();
+ if( !handled ) {
+// Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+ }
+ return;
+}
+Test.AnotherWay._record_html_node_path=function( node )
+{
+ if( node==null ) {
+ return null;
+ }
+ var path=[];
+ while( true ) {
+ if( node.id!=null && node.id!="" ) {
+ path.unshift( "#"+node.id+" "+node.nodeName );
+ break;
+ }else {
+ var parent_node=node.parentNode;
+ if( parent_node==null ) {
+ return []; // no BODY up the path - this node is screwed (browsers differ in what's above the body), discard
+ }else {
+ var i=0;
+ var found=false;
+ for( var child=parent_node.firstChild; child!=null; child=child.nextSibling ) {
+ if( child==node ) {
+ found=true;
+ break;
+ }
+ if( child.nodeType==1 ) { // count only HTML element nodes
+ ++i;
+ }
+ }
+ if( !found ) {
+ i=-1;
+ }
+ path.unshift( i+" "+node.nodeName );
+ if( parent_node.nodeName=="BODY" || parent_node.nodeName=="body" ) {
+ break;
+ }
+ node=parent_node;
+ }
+ }
+ }
+ return path;
+}
+Test.AnotherWay._record_node_path_to_string=function( path )
+{
+ var s="";
+ if( path!=null ) {
+ for( var i=0; i<path.length; ++i ) {
+ s+= i==0 ? "" : ", ";
+ var elem=path[i].split( " " );
+ if( elem[0].charAt( 0 )=="#" ) {
+ s+=elem[1]+" "+elem[0];
+ }else {
+ s+=elem[1]+" ["+elem[0]+"]";
+ }
+ }
+ }
+ return s;
+}
+Test.AnotherWay._record_node_path_to_node=function( path_str, doc )
+{
+ if( path_str==null ) {
+ return null;
+ }
+ var path=path_str.split( "," );
+ var node=doc.body;
+ for( var i=0; i<path.length; ++i ) {
+ var node_i=path[i].split( " " )[0];
+ if( node_i.charAt( 0 )=="#" ) {
+ node=doc.getElementById( node_i.substring( 1 ) );
+ }else {
+ if( node_i<0 || node_i>=node.childNodes.length ) {
+ node=null;
+ }else {
+ node=node.firstChild;
+ while( node!=null ) {
+ if( node.nodeType==1 ) { // count only HTML element nodes
+ if( node_i==0 ) {
+ break;
+ }
+ --node_i;
+ }
+ node=node.nextSibling;
+ }
+ }
+ }
+ if( node==null ) {
+ return null;
+ }
+ }
+ return node;
+}
+Test.AnotherWay._record_control_contains_id=function( s )
+{
+ return s.match( /^#record_[\w_]+/ ) && s.match( Test.AnotherWay._g_record_random_id );
+}
+Test.AnotherWay._record_checkpoint=function()
+{
+ var o={ type: "_checkpoint", time: (new Date()).getTime()-Test.AnotherWay._g_record_start_time, which: Test.AnotherWay._g_record_checkpoint_count++,
+ target: Test.AnotherWay._record_html_node_path( Test.AnotherWay._g_record_under_cursor ) };
+ Test.AnotherWay._g_record_events.push( o );
+ return o;
+}
+Test.AnotherWay._record_event=function( event )
+{
+ var unneeded=["rangeOffset","eventPhase","timeStamp","isTrusted","popupWindowFeatures","rangeOffset"];
+ if( Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused ) {
+ var o={};
+ for( var n in event ) {
+ var needed=!n.match( /^[A-Z0-9_]+$/ );
+ if( needed ) {
+ for( var ui=0; ui<unneeded.length; ++ui ) {
+ if( unneeded[ui]==n ) {
+ needed=false;
+ break;
+ }
+ }
+ if( needed ) {
+ var value=event[n];
+ if( typeof( value )!="object" && typeof( value )!="function" ) {
+ o[n]=value;
+ }else if( n=="target" || n=="relatedTarget" ) {
+ o[n]=Test.AnotherWay._record_html_node_path( value );
+ }
+ }
+ }
+ }
+ o["time"]=(new Date()).getTime()-Test.AnotherWay._g_record_start_time;
+ var over_record_control= o["target"]!=null && o["target"][0]!=null && Test.AnotherWay._record_control_contains_id( o["target"][0] );
+ if( !over_record_control ) {
+ Test.AnotherWay._g_record_events.push( o );
+ }
+ }
+ return true;
+}
+Test.AnotherWay._record_on_mousemove=function( event )
+{
+ var path=Test.AnotherWay._record_html_node_path( event.target );
+ var new_mouse_over_record_control= path!=null && path[0]!=null && Test.AnotherWay._record_control_contains_id( path[0] );
+ if( new_mouse_over_record_control!=Test.AnotherWay._g_record_mouse_over_record_control ) {
+ Test.AnotherWay._g_record_mouse_over_record_control=new_mouse_over_record_control;
+ Test.AnotherWay._record_control_update_ui();
+ }
+ if( event.target!=null && event.target!=Test.AnotherWay._g_record_under_cursor ) {
+ Test.AnotherWay._g_record_under_cursor=event.target;
+ var s="";
+ if( path==null || path[0]==null || !Test.AnotherWay._record_control_contains_id( path[0] ) ) {
+ s=Test.AnotherWay._record_node_path_to_string( path );
+ }
+ if( s=="" ) {
+ s="&nbsp;";
+ }
+ var cursor_over_indicator=Test.AnotherWay._record_control_get_element( "record_cursor_over" );
+ cursor_over_indicator.innerHTML=s;
+ }
+
+ var highlight_element=null;
+ if( !Test.AnotherWay._g_record_mouse_over_record_control && Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused ) {
+ highlight_element=event.target;
+ }
+ // highlight border disabled on recording - it causes page to scroll, issuing spurious mouseover/mouseout event
+ //Test.AnotherWay._record_control_update_highlight( highlight_element, "border" );
+
+ if( Test.AnotherWay._g_record_include_mousemove ) {
+ Test.AnotherWay._record_event( event );
+ }
+ return true;
+}
+Test.AnotherWay._record_display_checkpoint=function( o )
+{
+ var checkpoints_div=Test.AnotherWay._record_control_get_element( "record_checkpoints" );
+ var p=checkpoints_div.appendChild( checkpoints_div.ownerDocument.createElement( "div" ) );
+ p.style.marginTop="3px";
+ p.style.font="normal normal 8pt sans-serif";
+ p.style.color="#000";
+ p.style.textAligh="left";
+ p.style.position="relative";
+ p.style.width="100%";
+ var checkpoint_text="";
+ checkpoint_text+="#"+(o.which+1);
+ checkpoint_text+=" "+Test.AnotherWay._record_format_time( o.time );
+ if( o.target!=null ) {
+ checkpoint_text+=Test.AnotherWay._record_node_path_to_string( o.target );
+ }
+ p.appendChild( p.ownerDocument.createTextNode( checkpoint_text ) );
+}
+Test.AnotherWay._record_save_results=function( doc )
+{
+ // strange, but DOM-style append does not work here in opera 8.
+ var append=function( s ) { doc.write( "<div>"+s+"</div>" ); };
+ append( "/* paste this data into your javascript and pass it as an argument to replay_events method */" );
+ append( "{ checkpoints: [" );
+ var first_checkpoint=true;
+ for( var i=0; i<Test.AnotherWay._g_record_events.length; ++i ) {
+ var o=Test.AnotherWay._g_record_events[i];
+ if( o.type=="_checkpoint" ) {
+ var str= first_checkpoint ? "" : "}, ";
+ str+="function( tst, wnd ) { // #"+o.which+" time "+Test.AnotherWay._record_format_time( o.time )+" cursor was over "+Test.AnotherWay._record_node_path_to_string( o.target );
+ append( str );
+ first_checkpoint=false;
+ }
+ }
+ if( !first_checkpoint ) {
+ append( "}" );
+ }
+ append( "], events: [ " );
+ var prev_time=0;
+ for( var i=0; i<Test.AnotherWay._g_record_events.length; ++i ) {
+ var o=Test.AnotherWay._g_record_events[i];
+ var s="";
+ s+= "{";
+ var n_first=true;
+ for( var n in o ) {
+ if( n=="time" ) { // convert to relative time
+ var cur_time=o[n]-0;
+ o[n]=cur_time-prev_time;
+ prev_time=cur_time;
+ }
+ s+=n_first ? n : ", "+n;
+ s+=":";
+ if( o[n]==null ) {
+ s+="null";
+ }else {
+ s+="\""+o[n]+"\"";
+ }
+ n_first=false;
+ }
+ s+= i==Test.AnotherWay._g_record_events.length-1 ? "}" : "},";
+ append( s );
+ }
+ append( "] }" );
+ append( ";" );
+}
+
+Test.AnotherWay._g_record_border; // border highlighting element under cursor
+Test.AnotherWay._g_record_border_flashes=[]; // array of { color: color, timeout: milliseconds }
+Test.AnotherWay._g_record_border_flashing=false;
+Test.AnotherWay._g_record_border_normal_color="#d4b";
+Test.AnotherWay._record_flash_border_timeout=function()
+{
+ var color=Test.AnotherWay._g_record_border_normal_color;
+ var timeout=null;
+ if( Test.AnotherWay._g_record_border_flashes.length!=0 ) {
+ color=Test.AnotherWay._g_record_border_flashes[0].color;
+ timeout=Test.AnotherWay._g_record_border_flashes[0].timeout;
+ Test.AnotherWay._g_record_border_flashes.splice( 0, 1 );
+ }
+ if( Test.AnotherWay._g_record_border!=null ) {
+ for( var i=0; i<Test.AnotherWay._g_record_border.length; ++i ) {
+ Test.AnotherWay._g_record_border[i].style.backgroundColor=color;
+ }
+ }
+ if( timeout!=null ) {
+ setTimeout( Test.AnotherWay._record_flash_border_timeout, timeout );
+ }else {
+ Test.AnotherWay._g_record_border_flashing=false;
+ }
+}
+Test.AnotherWay._get_page_coords=function( elm )
+{
+ var point = { x: 0, y: 0 };
+ while( elm ) {
+ point.x+=elm.offsetLeft;
+ point.y+=elm.offsetTop;
+ elm=elm.offsetParent;
+ }
+ return point;
+}
+Test.AnotherWay._set_page_coords=function( elm, x, y )
+{
+ var parent_coords={ x: 0, y: 0 };
+ if( elm.offsetParent ) {
+ parent_coords=Test.AnotherWay._get_page_coords( elm.offsetParent );
+ }
+ var new_x=x-parent_coords.x;
+ if( new_x<0 ) {
+ new_x=0;
+ }
+ elm.style.left=new_x+'px';
+ var new_y=y-parent_coords.y;
+ if( new_y<0 ) {
+ new_y=0;
+ }
+ elm.style.top=new_y+'px';
+}
+Test.AnotherWay._record_setup_highlight_positions=function( element, style, coords, positions )
+{
+ if( style=="border" ) {
+ var width=element.clientWidth;
+ var height=element.clientHeight;
+ var step=0;
+ var thickness=2;
+ var fudge_expand=4;
+ positions.push( { x: coords.x-step-thickness, y: coords.y-step-thickness, width: width+2*step+2*thickness+fudge_expand, height: thickness } );
+ positions.push( { x: coords.x+width+step+fudge_expand, y: coords.y-step-thickness, width: thickness, height: height+2*step+2*thickness+fudge_expand } );
+ positions.push( { x:positions[0].x, y:positions[0].y, width:positions[0].width, height:positions[0].height } );
+ positions.push( { x:positions[1].x, y:positions[1].y, width:positions[1].width, height:positions[1].height } );
+ positions[2].y+=height+thickness+2*step+fudge_expand;
+ positions[3].x-=width+thickness+2*step+fudge_expand;
+ }else if( style=="ball" ) {
+ positions.push( { x: coords.x+2, y: coords.y, width: 2, height: 6 } );
+ positions.push( { x: coords.x, y: coords.y+2, width: 6, height: 2 } );
+ positions.push( { x: coords.x+1, y: coords.y+1, width: 4, height: 4 } );
+ }
+}
+Test.AnotherWay._record_highlight_border=function( element, style, event ) // null - hide border
+{
+ if( element!=null ) {
+ if( Test.AnotherWay._g_record_border==null || Test.AnotherWay._g_record_border[0].ownerDocument!=element.ownerDocument ) {
+ Test.AnotherWay._g_record_border=[];
+ var n= style=="border" ? 4 : style=="ball" ? 3 : 0;
+ for( var i=0; i<4; ++i ) {
+ var b=element.ownerDocument.createElement( "div" );
+ b.style.position="absolute";
+ b.style.zIndex="1";
+ b.style.backgroundColor=Test.AnotherWay._g_record_border_normal_color;
+ element.ownerDocument.body.appendChild( b );
+ Test.AnotherWay._g_record_border.push( b );
+ }
+ }
+ var coords=null;
+ if( style=="border" ) {
+ coords=Test.AnotherWay._get_page_coords( element );
+ }else if( style=="ball" ) {
+ if( event!=null ) {
+ if( event.pageX!=null && event.pageY!=null ) {
+ coords={ x: event.pageX-0, y: event.pageY-0 };
+ }else if( event.clientX!=null && event.clientY!=null ) {
+ var doc=element.ownerDocument;
+ if( doc!=null ) {
+ coords={ x: (event.clientX-0)+doc.body.scrollLeft, y: (event.clientY-0)+doc.body.scrollTop };
+ }
+ }
+ }
+ }
+ if( coords!=null && element.clientWidth!=null && element.clientHeight!=null ) {
+ var positions=[];
+ Test.AnotherWay._record_setup_highlight_positions( element, style, coords, positions );
+ for( var i=0; i<positions.length; ++i ) {
+ var b=Test.AnotherWay._g_record_border[i];
+ var p=positions[i];
+ Test.AnotherWay._set_page_coords( b, p.x, p.y );
+ b.style.width=p.width+"px";
+ b.style.height=p.height+"px";
+ b.style.display="block";
+ }
+ }
+ }else {
+ if( Test.AnotherWay._g_record_border!=null ) {
+ for( var i=0; i<Test.AnotherWay._g_record_border.length; ++i ) {
+ Test.AnotherWay._g_record_border[i].style.display="none";
+ }
+ }
+ }
+}
+Test.AnotherWay._record_flash_border=function( color )
+{
+ if( Test.AnotherWay._g_record_border_flashing ) { //already
+ Test.AnotherWay._g_record_border_flashes.push( { color: Test.AnotherWay._g_record_border_normal_color, timeout:300 } );
+ Test.AnotherWay._g_record_border_flashes.push( { color: color, timeout:600 } );
+ }else {
+ Test.AnotherWay._g_record_border_flashing=true;
+ Test.AnotherWay._g_record_border_flashes.push( { color: color, timeout:600 } );
+ Test.AnotherWay._record_flash_border_timeout();
+ }
+}
+Test.AnotherWay._record_prepare_doc_for_results=function()
+{
+ document.open();
+ document.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" );
+ document.write( "<html><head><title> Input recording results</title>" );
+ document.write( "<style type=\"text/css\">" );
+ document.write( "body { font: normal normal smaller sans-serif; }" );
+ document.write( "div { margin-top: 3px; }" );
+ document.write( "</style></head><body>" );
+ // opera and mozilla disagree over who the opener is.
+ if( typeof( window.opener.Test )!="undefined" && typeof( window.opener.Test.AnotherWay )!="undefined" ) {
+ window.opener.Test.AnotherWay._record_save_results( document );
+ window.opener.Test.AnotherWay._g_record_waiting_for_results=false;
+ window.opener.Test.AnotherWay._record_control_update_ui();
+ }else if( typeof( window.opener.opener.Test )!="undefined" && typeof( window.opener.opener.Test.AnotherWay )!="undefined" ) {
+ window.opener.opener.Test.AnotherWay._record_save_results( document );
+ window.opener.opener.Test.AnotherWay._g_record_waiting_for_results=false;
+ window.opener.opener.Test.AnotherWay._record_control_update_ui();
+ }
+ document.write( "</body>" );
+ document.close();
+}
+
+// global initialization
+onload=function()
+{
+ if( window.opera ) {
+ var good_opera=typeof( window.opera.version )=="function";
+ good_opera=good_opera && window.opera.version().match( /^\s*(\d+)/ );
+ good_opera=good_opera && RegExp.$1>=8;
+ }
+ var span=document.createElement( "SPAN" );
+ span.innerHTML="<!--[if IE]><br /><![endif]-"+"->";
+ var is_ie=span.getElementsByTagName( "BR" ).length>0;
+
+ Test.AnotherWay._g_test_iframe=window.frames.test_iframe;
+
+ var query_str=window.location.search;
+ if( query_str.charAt( 0 )=="?" ) {
+ query_str=query_str.substring( 1 );
+ }
+ var testlist_page="list-tests.html";
+ var auto_run=false;
+ if( query_str!="" ) {
+ var params=[query_str];
+ if( query_str.indexOf( ";" )!=-1 ) {
+ params=query_str.split( ";" );
+ }else if( query_str.indexOf( "&" )!=-1 ) {
+ params=query_str.split( "&" );
+ }
+ for( var param_i=0; param_i<params.length; ++param_i ) {
+ var param=params[param_i].split( "=" );
+ if( param[0]=="recording_results" ) {
+ if( window.opener!=null ) {
+ // we were told to show recording results - replace everything in the document with the results
+ Test.AnotherWay._record_prepare_doc_for_results();
+ return;
+ }
+ }else if( param[0]=="testpage" ) {
+ Test.AnotherWay._add_test_page_url( decodeURIComponent( param[1] ), "anotherway" );
+ }else if( param[0]=="jsantestpage" ) {
+ Test.AnotherWay._add_test_page_url( decodeURIComponent( param[1] ), "jsan" );
+ }else if( param[0]=="testlist" ) {
+ testlist_page=decodeURIComponent( param[1] );
+ }else if( param[0]=="testframe" ) {
+ if( window.opera && !good_opera ) {
+ Test.AnotherWay._show_error( "testframe parameter does not work in versions of Opera prior to 8.0. Sorry (pathches are welcome)." );
+ // Opera 7 barfs on attempt to access frame.frameElement.
+ // if someone knows a way to assign onload handler to that iframe in Opera 7
+ // without disrupting code that works in other browsers, patches are welcome.
+ }else {
+ var frame_path=param[1].split( "." );
+ var frame=top;
+ for( var frame_path_i=0; frame_path_i<frame_path.length; ++frame_path_i ) {
+ frame=frame[frame_path[frame_path_i]];
+ }
+ if( frame==null ) {
+ Test.AnotherWay._show_error( "unable to find frame specified for loading test pages: "+param[1] );
+ }else {
+ if( frame.frameElement!=null ) { // for the following assignement to onload to work, frameElement is required
+ frame=frame.frameElement;
+ }
+ Test.AnotherWay._g_test_iframe=frame;
+ }
+ }
+ }else if( param[0]=="testframe_no_clear" ) {
+ Test.AnotherWay._g_test_frame_no_clear=true;
+ }else if( param[0]=="windows" ) {
+ if (param[1] == "none") {
+ }
+ }else if( param[0]=="run" ) {
+ auto_run=true;
+ if( param[1]=="all" ) {
+ Test.AnotherWay._g_pages_to_run="all";
+ }else {
+ if( Test.AnotherWay._g_pages_to_run==null || Test.AnotherWay._g_pages_to_run=="all" ) {
+ Test.AnotherWay._g_pages_to_run=[];
+ }
+ var pages=param[1].split( "," );
+ for( var i=0; i<pages.length; ++i ) {
+ Test.AnotherWay._g_pages_to_run.push( pages[i] );
+ }
+ }
+ }
+ }
+ }
+ if( Test.AnotherWay._g_test_page_urls.length==0 ) { // if no individual pages were given on the command line, load the list
+ var result=Test.AnotherWay._set_iframe_location( window.frames["list_iframe"], testlist_page );
+ if( result.msg!=null ) {
+ Test.AnotherWay._show_error( result.msg );
+ }
+ Test.AnotherWay._g_run_on_list_load=auto_run;
+ }else {
+ Test.AnotherWay._g_run_on_main_load=auto_run;
+ }
+
+ var f=Test.AnotherWay._g_test_iframe;
+ try {
+ if( f.attachEvent!=null ) {
+ f.attachEvent( "onload", Test.AnotherWay._test_page_onload );
+ }else {
+ f.onload=Test.AnotherWay._test_page_onload;
+ }
+ if( Test.AnotherWay._g_test_iframe.nodeType!=null && Test.AnotherWay._g_test_iframe.contentWindow!=null ) { // it's iframe element, not the iframe. we need iframe.
+ Test.AnotherWay._g_test_iframe=Test.AnotherWay._g_test_iframe.contentWindow;
+ }
+ }catch(e) {
+ // ignore stupid opera error if the frame has onload handler assigned in the inline html
+ }
+ var handlers={
+ "run_all": { "onclick": Test.AnotherWay._run_all_onclick },
+ "run_selected": { "onclick": Test.AnotherWay._run_selected_onclick },
+ "unselect_all": { "onclick": Test.AnotherWay._unselect_all_onclick },
+ "record_select": { "onfocus": Test.AnotherWay._record_check_onfocus },
+ "record_input": { "onfocus": Test.AnotherWay._record_check_onfocus },
+ "record_start": { "onclick": Test.AnotherWay._record_start_onclick },
+ "clear_btn": { "onclick": Test.AnotherWay._results_clear_onclick },
+ "results_tab": { "onclick": Test.AnotherWay._tab_onclick, "onmouseover": Test.AnotherWay._tab_mouseover, "onmouseout": Test.AnotherWay._tab_mouseout },
+ "debug_tab": { "onclick": Test.AnotherWay._tab_onclick, "onmouseover": Test.AnotherWay._tab_mouseover, "onmouseout": Test.AnotherWay._tab_mouseout }
+ };
+ for( var hs in handlers ) {
+ var o=document.getElementById( hs );
+ if( o!=null ) {
+ for( var h in handlers[hs] ) {
+ o[h]=handlers[hs][h];
+ }
+ }else {
+ Test.AnotherWay._show_error( "unable to set "+h+" handler: id "+hs+" not found" );
+ }
+ }
+
+ if( window.opera && !good_opera ) {
+ Test.AnotherWay._g_no_record_msg="Input events recording and replaying is not available in opera versions prior to 8.0.";
+ }
+ if( is_ie ) {
+ Test.AnotherWay._g_no_record_msg="Input events recording and replaying is not available in internet explorer.";
+ }
+ if( Test.AnotherWay._g_no_record_msg!=null ) {
+ var no_record_p=document.getElementById( "record_not_supported" );
+ no_record_p.style.display="block";
+ no_record_p.appendChild( document.createTextNode( Test.AnotherWay._g_no_record_msg ) );
+ }
+
+ Test.AnotherWay._g_main_loaded=true;
+ if( Test.AnotherWay._g_run_on_main_load ) {
+ Test.AnotherWay._g_run_on_main_load=false;
+ Test.AnotherWay._run_pages_to_run();
+ }
+}
+Test.AnotherWay._test_object_t.prototype.open_window=null;
+// -->
+</script>
+<script type="text/javascript" src="xml_eq.js"></script>
+<script type="text/javascript" src="geom_eq.js"></script>
+</head><body>
+
+<div id="col1">
+<div id="col1_header">Test pages:</div>
+<div id="scroller">
+<table id="testtable">
+</table>
+</div>
+<div id="run_buttons">
+<input type="button" value=" clear " id="clear_btn" />
+<input type="button" value=" run all " id="run_all" />
+<input type="button" value=" run selected " id="run_selected" />
+<input type="button" value=" unselect all " id="unselect_all" />
+</div>
+<input type="checkbox" id="dont_close_test_windows" /> do not close windows opened by tests
+<div id="error"></div>
+<div id="record_div">
+<p id="record_not_supported" style="display:none"></p>
+<p>Record mouse input for the page:</p>
+<p><input type="radio" name="record_choose" value="select" checked="checked" /> <select id="record_select"><option selected="selected">-- select a page: --</option></select></p>
+<p><input type="radio" name="record_choose" value="input" /> or enter page url: <input type="text" id="record_input" /></p>
+<p><input type="button" value=" record " id="record_start" /></p>
+</div>
+</div>
+
+<div id="col2">
+<div id="right_header">
+<span id="results_count">Results: <span id="total"></span></span>
+<span id="results_tab" class="active_tab" style="visibility:hidden">Results</span>
+<span id="debug_tab" class="inactive_tab" style="visibility:hidden">Debug</span>
+</div>
+<div id="right_frame">
+<div id="results"></div>
+<div id="debug"></div>
+</div>
+</div>
+
+<span style="display:none">
+<iframe name="list_iframe" onload="Test.AnotherWay._list_iframe_onload();"></iframe>
+<iframe name="test_iframe" onload="Test.AnotherWay._test_page_onload();"></iframe>
+
+<!-- record_control div is to be imported into other documents, so all its styles are inline -->
+-<div id="record_control" style="position:absolute;bottom:0;left:0;margin:0;padding:0.5em;width:22em;height:22em;border:1px solid;background:#ffd;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+
+<p style="margin:0 0 0 0; padding:0">
+&nbsp;
+<span style="display:none;font-weight:bold;color:#408" id="record_indicator">
+recording. <span style="font-weight:normal">time: <span id="record_time"></span></span><span id="record_pause_indicator"> paused</span>
+</span>
+</p>
+
+<div id="record_cursor_over" style="margin:0;padding:2px;width:14em;height:1.1em;overflow:hidden;float:right;border:1px solid #777;background:#fff;font: normal normal 8pt sans-serif;position:relative;top:3px;color:#000;text-align:left;">&nbsp;</div>
+<p style="margin:2px 0 0 0; padding:0">
+cursor is over
+</p>
+
+<p style="margin:8px 0 0 0; padding:0;">
+ keyboard control: press
+ <span id="record_ctrl_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">ctrl</span> -
+ <span id="record_shift_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">shift</span> -
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_s" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">s</span>
+<span id="record_on">to <b>start</b> recording</span>
+<span id="record_off" style="display:none">to <b>stop</b> recording</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_h" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">h</span>
+<span>to <b>hide/show</b> this window</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_m" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">m</span>
+<span id="record_include_mousemove">to <b> record</b> mousemove</span>
+<span id="record_omit_mousemove" style="display:none">to <b>omit</b> mousemove</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_p" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">p</span>
+<span id="record_pause_on">to <b> pause</b> recording</span>
+<span id="record_pause_off" style="display:none">to <b>continue</b> recording</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_c" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">c</span>
+<span>to add checkpoint</span>
+</p>
+
+<p style="margin:6px 0 0 0; padding:0">
+checkpoints:
+</p>
+<div id="record_checkpoints" style="position:relative;width:100%;height:6em;overflow:auto;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+</div>
+</div>
+
+</span>
+</body></html>
diff --git a/misc/openlayers/tests/data/geos_wkt_intersects.js b/misc/openlayers/tests/data/geos_wkt_intersects.js
new file mode 100644
index 0000000..e0a355b
--- /dev/null
+++ b/misc/openlayers/tests/data/geos_wkt_intersects.js
@@ -0,0 +1,495 @@
+var geos_test_data = [
+{'wkt1':'POLYGON ((100 100,100 200,200 200,200 100,100 100))', 'wkt2':'POLYGON ((100 100,1000000000000000.0 110.0,1000000000000000.0 100.0,100 100))', result:true},
+{'wkt1':'POLYGON ((120 100,120 200,200 200,200 100,120 100))', 'wkt2':'POLYGON ((100 100,1000000000000000.0 110.0,1000000000000000.0 100.0,100 100))', result:true},
+{'wkt1':'POLYGON ((20 20,20 100,120 100,140 20,20 20))', 'wkt2':'POLYGON ((20 20,20 100,120 100,140 20,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 100,120 100,140 20,20 20))', 'wkt2':'POLYGON ((20 20,140 20,120 100,20 100,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 100,120 100,140 20,20 20))', 'wkt2':'POLYGON ((120 100,140 20,20 20,20 100,120 100))', result:true},
+{'wkt1':'POLYGON ((20 20,20 100,120 100,140 20,20 20))', 'wkt2':'POLYGON ((20 100,60 100,120 100,140 20,80 20,20 20,20 100))', result:true},
+{'wkt1':'POLYGON ((0 0,80 0,80 80,0 80,0 0))', 'wkt2':'POLYGON ((100 200,100 140,180 140,180 200,100 200))', result:false},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((140 120,140 200,240 200,240 120,140 120))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((80 180,140 260,260 200,200 60,80 180))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((240 80,140 120,180 240,280 200,240 80))', result:true},
+{'wkt1':'POLYGON ((140 160,20 20,270 20,150 160,230 40,60 40,140 160))', 'wkt2':'POLYGON ((140 40,180 80,120 100,140 40))', result:true},
+{'wkt1':'POLYGON ((140 160,20 20,270 20,150 160,230 40,60 40,140 160))', 'wkt2':'POLYGON ((120 100,180 80,130 40,120 100))', result:true},
+{'wkt1':'POLYGON ((20 20,180 20,140 140,20 140,20 20))', 'wkt2':'POLYGON ((180 100,80 200,180 280,260 200,180 100))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((140 140,20 120,0 220,120 240,140 140))', result:true},
+{'wkt1':'POLYGON ((160 200,210 70,120 70,160 200))', 'wkt2':'POLYGON ((160 200,260 40,70 40,160 200,20 20,310 20,160 200))', result:true},
+{'wkt1':'POLYGON ((110 140,200 70,200 160,110 140))', 'wkt2':'POLYGON ((110 140,110 50,60 50,60 90,160 190,20 110,20 20,200 20,110 140))', result:true},
+{'wkt1':'POLYGON ((20 120,20 20,260 20,260 120,200 40,140 120,80 40,20 120))', 'wkt2':'POLYGON ((20 120,20 240,260 240,260 120,200 200,140 120,80 200,20 120))', result:true},
+{'wkt1':'POLYGON ((20 120,20 20,260 20,260 120,180 40,140 120,100 40,20 120))', 'wkt2':'POLYGON ((20 120,300 120,140 240,20 120))', result:true},
+{'wkt1':'POLYGON ((20 20,20 300,280 300,280 260,220 260,60 100,60 60,280 60,280 20,20 20))', 'wkt2':'POLYGON ((100 140,160 80,280 180,200 240,220 160,160 200,180 120,100 140))', result:true},
+{'wkt1':'POLYGON ((20 20,20 300,280 300,280 260,220 260,60 100,60 60,280 60,280 20,20 20))', 'wkt2':'POLYGON ((260 200,180 80,120 160,200 160,180 220,260 200))', result:true},
+{'wkt1':'POLYGON ((20 20,280 20,280 140,220 60,140 140,80 60,20 140,20 20))', 'wkt2':'POLYGON ((0 140,300 140,140 240,0 140))', result:true},
+{'wkt1':'POLYGON ((20 20,280 20,280 140,220 60,140 140,80 60,20 140,20 20))', 'wkt2':'POLYGON ((20 240,20 140,320 140,180 240,20 240))', result:true},
+{'wkt1':'POLYGON ((20 20,280 20,280 140,220 60,140 140,80 60,20 140,20 20))', 'wkt2':'POLYGON ((20 240,20 140,80 180,140 140,220 180,280 140,280 240,20 240))', result:true},
+{'wkt1':'POLYGON ((120 120,180 60,20 20,20 120,120 120))', 'wkt2':'POLYGON ((120 120,220 20,280 20,240 160,120 120))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((140 120,160 20,260 120,220 200,140 120))', result:true},
+{'wkt1':'POLYGON ((20 140,120 40,20 40,20 140))', 'wkt2':'POLYGON ((190 140,190 20,140 20,20 140,190 140))', result:true},
+{'wkt1':'POLYGON ((120 120,180 60,20 20,20 120,120 120))', 'wkt2':'POLYGON ((300 20,220 20,120 120,260 160,300 20))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((140 120,240 160,280 60,160 20,140 120))', result:true},
+{'wkt1':'POLYGON ((120 120,180 60,20 20,20 120,120 120))', 'wkt2':'POLYGON ((280 60,180 60,120 120,260 180,280 60))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((120 200,120 120,40 120,40 200,120 200))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((160 220,140 120,60 120,40 220,160 220))', result:true},
+{'wkt1':'POLYGON ((140 120,160 20,20 20,20 120,140 120))', 'wkt2':'POLYGON ((140 120,20 120,20 220,140 220,140 120))', result:true},
+{'wkt1':'POLYGON ((120 120,180 60,20 20,20 120,120 120))', 'wkt2':'POLYGON ((320 20,220 20,80 160,240 140,320 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((60 40,60 140,180 140,180 40,60 40))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,80 140,160 60,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((160 60,20 20,100 140,160 60))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 100,140 160,160 40,20 100))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((160 40,20 100,160 160,160 40))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 180,180 120,80 40,20 180))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((180 120,100 40,20 180,180 120))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,140 40,140 120,20 160,80 80,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,140 40,140 140,20 180,80 100,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((40 180,60 100,180 100,200 180,120 120,40 180))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 180,60 80,180 80,220 180,120 120,20 180))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((40 60,20 180,100 100,140 180,160 120,220 100,140 40,40 60))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((60 100,180 100,220 180,120 140,20 180,60 100))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,20 140,120 120,120 40,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,20 180,140 140,140 60,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,120 40,120 120,20 140,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((120 40,20 20,20 140,120 120,120 40))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,140 60,140 140,20 180,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((140 60,20 20,20 180,140 140,140 60))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,60 120,140 120,180 20,20 20))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 40,120 40,120 120,20 140,20 40))', result:true},
+{'wkt1':'POLYGON ((20 20,20 180,220 180,220 20,20 20))', 'wkt2':'POLYGON ((20 20,20 180,60 120,100 180,140 120,220 180,200 120,140 60,20 20))', result:true},
+{'wkt1':'POLYGON ((150 150,330 150,250 70,70 70,150 150))', 'wkt2':'POLYGON ((150 150,270 150,140 20,20 20,150 150))', result:true},
+{'wkt1':'POLYGON ((150 150,270 150,330 150,250 70,190 70,70 70,150 150))', 'wkt2':'POLYGON ((150 150,270 150,190 70,140 20,20 20,70 70,150 150))', result:true},
+{'wkt1':'POLYGON ((20 20,60 50,20 40,60 70,20 60,60 90,20 90,70 110,20 130,80 130,20 150,80 160,20 170,80 180,20 200,80 200,30 240,80 220,50 260,100 220,100 260,120 220,130 260,140 220,150 280,150 190,160 280,170 190,180 280,190 190,200 280,210 190,220 280,230 190,240 260,250 230,260 260,260 220,290 270,290 220,330 260,300 210,340 240,290 180,340 210,290 170,350 170,240 150,350 150,240 140,350 130,240 120,350 120,240 110,350 110,240 100,350 100,240 90,350 90,240 80,350 80,300 70,340 60,290 60,340 40,300 50,340 20,270 60,310 20,250 60,270 20,230 60,240 20,210 60,210 20,190 70,190 20,180 90,170 20,160 90,150 20,140 90,130 20,120 90,110 20,100 90,100 20,90 60,80 20,70 40,20 20))', 'wkt2':'POLYGON ((190 140,140 130,200 160,130 150,210 170,130 170,210 180,120 190,220 200,120 200,250 210,120 210,250 220,120 220,250 230,120 240,230 240,120 250,240 260,120 260,240 270,120 270,270 290,120 290,230 300,150 310,250 310,180 320,250 320,200 360,260 330,240 360,280 320,290 370,290 320,320 360,310 320,360 360,310 310,380 340,310 290,390 330,310 280,410 310,310 270,420 280,310 260,430 250,300 250,440 240,300 240,450 230,280 220,440 220,280 210,440 210,300 200,430 190,300 190,440 180,330 180,430 150,320 180,420 130,300 180,410 120,280 180,400 110,280 170,390 90,280 160,400 70,270 160,450 30,260 160,420 30,250 160,390 30,240 160,370 30,230 160,360 30,230 150,330 50,240 130,330 30,230 130,310 30,220 130,280 30,230 100,270 40,220 110,250 30,210 130,240 30,210 100,220 40,200 90,200 20,190 100,180 30,20 20,180 40,20 30,180 50,20 50,180 60,30 60,180 70,20 70,170 80,80 80,170 90,20 80,180 100,40 100,200 110,60 110,200 120,120 120,190 140))', result:true},
+{'wkt1':'POLYGON ((70 150,20 160,110 160,20 180,100 200,20 200,190 210,20 210,160 220,20 220,150 230,60 240,180 250,20 260,170 260,60 270,160 270,100 310,170 280,200 260,180 230,210 260,130 330,230 250,210 290,240 250,230 210,260 300,250 230,270 300,270 240,300 340,280 250,320 330,290 250,340 350,290 240,350 360,270 190,350 340,290 200,350 330,300 190,360 320,310 190,360 300,320 200,360 280,330 200,360 260,340 200,370 260,340 180,390 290,340 170,400 260,350 170,400 250,350 160,410 240,350 150,400 170,350 140,310 170,340 140,270 180,330 140,260 170,310 140,240 170,290 140,200 190,270 140,180 190,260 140,170 190,260 130,170 180,250 130,170 170,240 120,170 160,210 120,170 150,210 110,340 130,230 110,420 140,220 100,410 130,220 90,400 120,220 80,390 110,220 70,420 110,240 70,420 100,260 70,420 90,280 70,430 80,230 60,430 60,270 50,450 40,210 50,370 40,260 40,460 30,160 40,210 60,200 110,190 60,190 120,170 50,180 130,150 30,170 130,140 20,160 120,130 20,160 150,120 20,160 170,110 20,160 190,100 20,150 190,90 20,140 180,80 20,120 140,70 20,120 150,60 20,110 150,50 20,100 140,50 30,90 130,40 30,80 120,30 30,80 130,30 40,80 140,20 40,70 140,40 90,60 130,20 90,60 140,20 130,70 150))', 'wkt2':'POLYGON ((190 140,140 130,200 160,130 150,210 170,130 170,210 180,120 190,220 200,120 200,250 210,120 210,250 220,120 220,250 230,120 240,230 240,120 250,240 260,120 260,240 270,120 270,270 290,120 290,230 300,150 310,250 310,180 320,250 320,200 360,260 330,240 360,280 320,290 370,290 320,320 360,310 320,360 360,310 310,380 340,310 290,390 330,310 280,410 310,310 270,420 280,310 260,430 250,300 250,440 240,300 240,450 230,280 220,440 220,280 210,440 210,300 200,430 190,300 190,440 180,330 180,430 150,320 180,420 130,300 180,410 120,280 180,400 110,280 170,390 90,280 160,400 70,270 160,450 30,260 160,420 30,250 160,390 30,240 160,370 30,230 160,360 30,230 150,330 50,240 130,330 30,230 130,310 30,220 130,280 30,230 100,270 40,220 110,250 30,210 130,240 30,210 100,220 40,200 90,200 20,190 100,180 30,20 20,180 40,20 30,180 50,20 50,180 60,30 60,180 70,20 70,170 80,80 80,170 90,20 80,180 100,40 100,200 110,60 110,200 120,120 120,190 140))', result:true},
+{'wkt1':'POLYGON ((60 160,220 160,220 20,60 20,60 160))', 'wkt2':'POLYGON ((60 160,20 200,260 200,220 160,140 80,60 160))', result:true},
+{'wkt1':'POLYGON ((60 160,220 160,220 20,60 20,60 160))', 'wkt2':'POLYGON ((60 160,20 200,260 200,140 80,60 160))', result:true},
+{'wkt1':'POLYGON ((60 160,220 160,220 20,60 20,60 160))', 'wkt2':'POLYGON ((20 200,140 80,260 200,20 200))', result:true},
+{'wkt1':'POLYGON ((60 160,220 160,220 20,60 20,60 160))', 'wkt2':'POLYGON ((20 200,60 160,140 80,220 160,260 200,20 200))', result:true},
+{'wkt1':'POLYGON ((60 160,220 160,220 20,60 20,60 160))', 'wkt2':'POLYGON ((20 200,60 160,140 80,260 200,20 200))', result:true},
+{'wkt1':'POLYGON ((0 0,0 200,200 200,200 0,0 0))', 'wkt2':'POLYGON ((100 100,1000000 110,10000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((100 0,100 200,200 200,200 0,100 0))', 'wkt2':'POLYGON ((100 100,1000000 110,10000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((120 0,120 200,200 200,200 0,120 0))', 'wkt2':'POLYGON ((100 100,1000000 110,10000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((0 0,0 200,110 200,110 0,0 0))', 'wkt2':'POLYGON ((100 100,1000000 110,10000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((100 100,100 200,200 200,200 100,100 100))', 'wkt2':'POLYGON ((100 100,2100 110,2100 100,100 100))', result:true},
+{'wkt1':'POLYGON ((100 100,100 200,200 200,200 100,100 100))', 'wkt2':'POLYGON ((100 100,2101 110,2101 100,100 100))', result:true},
+{'wkt1':'POLYGON ((100 100,200 200,200 100,100 100))', 'wkt2':'POLYGON ((100 100,2101 110,2101 100,100 100))', result:true},
+{'wkt1':'POLYGON ((100 100,100 200,200 200,200 100,100 100))', 'wkt2':'POLYGON ((100 100,1000000 110,1000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((120 100,120 200,200 200,200 100,120 100))', 'wkt2':'POLYGON ((100 100,500 110,500 100,100 100))', result:true},
+{'wkt1':'POLYGON ((120 100,120 200,200 200,200 100,120 100))', 'wkt2':'POLYGON ((100 100,501 110,501 100,100 100))', result:true},
+{'wkt1':'POLYGON ((120 100,130 200,200 200,200 100,120 100))', 'wkt2':'POLYGON ((100 100,501 110,501 100,100 100))', result:true},
+{'wkt1':'POLYGON ((120 100,17 200,200 200,200 100,120 100))', 'wkt2':'POLYGON ((100 100,501 110,501 100,100 100))', result:true},
+{'wkt1':'POLYGON ((120 100,120 200,200 200,200 100,120 100))', 'wkt2':'POLYGON ((100 100,1000000 110,1000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((101 99,101 1000000,102 1000000,101 99))', 'wkt2':'POLYGON ((100 100,1000000 110,1000000 100,100 100))', result:true},
+{'wkt1':'POLYGON ((100 100,200 101,200 100,100 100))', 'wkt2':'POLYGON ((100 100,2101 110,2101 100,100 100))', result:true},
+{'wkt1':'POLYGON ((16 319,150 39,25 302,160 20,265 20,127 317,16 319))', 'wkt2':'POLYGON ((10 307,22 307,153 34,22 34,10 307))', result:true},
+{'wkt1':'POLYGON ((160 200,210 70,120 70,160 200))', 'wkt2':'POLYGON ((160 200,310 20,20 20,160 200),(160 200,260 40,70 40,160 200))', result:true},
+{'wkt1':'POLYGON ((170 120,240 100,260 50,190 70,170 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((270 90,200 50,150 80,210 120,270 90))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((170 120,260 100,240 60,150 80,170 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((220 120,270 80,200 60,160 100,220 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((260 50,180 70,180 110,260 90,260 50))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((230 110,290 80,190 60,140 90,230 110))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((170 120,330 120,260 50,100 50,170 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((170 120,330 120,280 70,120 70,170 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((170 120,300 120,250 70,120 70,170 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((190 100,310 100,260 50,140 50,190 100))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((280 130,360 130,270 40,190 40,280 130))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,250 120,180 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((220 80,180 40,80 40,170 130,270 130,230 90,300 90,250 30,280 30,390 140,150 140,40 30,230 30,280 80,220 80))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,250 120,180 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((260 130,360 130,280 40,170 40,260 130))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,250 120,180 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((240 110,340 110,290 60,190 60,240 110))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,250 120,180 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((250 120,350 120,280 50,180 50,250 120))', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,250 120,180 50,100 50,170 120))', result:true},
+{'wkt1':'POLYGON ((230 210,230 20,20 20,20 210,230 210),(120 180,50 50,200 50,120 180))', 'wkt2':'POLYGON ((230 210,230 20,20 20,20 210,230 210),(120 180,50 50,200 50,120 180))', result:true},
+{'wkt1':'POLYGON ((230 210,230 20,20 20,20 210,230 210),(140 40,40 40,40 170,140 40),(110 190,210 190,210 50,110 190))', 'wkt2':'POLYGON ((230 210,230 20,20 20,20 210,230 210),(140 40,40 40,40 170,140 40),(110 190,210 190,210 50,110 190))', result:true},
+{'wkt1':'POLYGON ((280 190,330 150,200 110,150 150,280 190))', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'POLYGON ((80 190,220 190,140 110,0 110,80 190))', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'POLYGON ((330 150,200 110,150 150,280 190,330 150))', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'POLYGON ((290 190,340 150,220 120,170 170,290 190))', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'POLYGON ((220 190,340 190,260 110,140 110,220 190))', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'POLYGON ((140 190,220 190,100 70,20 70,140 190))', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'POLYGON ((140 220,60 140,140 60,220 140,140 220))', 'wkt2':'MULTIPOLYGON (((100 20,180 20,180 100,100 100,100 20)),((20 100,100 100,100 180,20 180,20 100)),((100 180,180 180,180 260,100 260,100 180)),((180 100,260 100,260 180,180 180,180 100)))', result:true},
+{'wkt1':'MULTIPOLYGON (((110 110,70 200,150 200,110 110)),((110 110,150 20,70 20,110 110)))', 'wkt2':'MULTIPOLYGON (((110 110,160 160,210 110,160 60,110 110)),((110 110,60 60,10 110,60 160,110 110)))', result:true},
+{'wkt1':'MULTIPOLYGON (((110 110,70 200,150 200,110 110),(110 110,100 180,120 180,110 110)),((110 110,150 20,70 20,110 110),(110 110,120 40,100 40,110 110)))', 'wkt2':'MULTIPOLYGON (((110 110,160 160,210 110,160 60,110 110),(110 110,160 130,160 90,110 110)),((110 110,60 60,10 110,60 160,110 110),(110 110,60 90,60 130,110 110)))', result:true},
+{'wkt1':'MULTIPOLYGON (((110 110,70 200,200 200,110 110),(110 110,100 180,120 180,110 110)),((110 110,200 20,70 20,110 110),(110 110,120 40,100 40,110 110)))', 'wkt2':'MULTIPOLYGON (((110 110,160 160,210 110,160 60,110 110),(110 110,160 130,160 90,110 110)),((110 110,60 60,10 110,60 160,110 110),(110 110,60 90,60 130,110 110)))', result:true},
+{'wkt1':'MULTIPOLYGON (((110 110,20 200,200 200,110 110),(110 110,100 180,120 180,110 110)),((110 110,200 20,20 20,110 110),(110 110,120 40,100 40,110 110)))', 'wkt2':'MULTIPOLYGON (((110 110,160 160,210 110,160 60,110 110),(110 110,160 130,160 90,110 110)),((110 110,60 60,10 110,60 160,110 110),(110 110,60 90,60 130,110 110)))', result:true},
+{'wkt1':'MULTIPOLYGON (((110 110,70 200,200 200,110 110),(110 110,100 180,120 180,110 110)),((110 110,200 20,70 20,110 110),(110 110,120 40,100 40,110 110)))', 'wkt2':'MULTIPOLYGON (((110 110,160 160,210 110,160 60,110 110),(110 110,160 130,160 90,110 110)),((110 110,60 60,10 110,60 160,110 110),(110 110,60 90,60 130,110 110)))', result:true},
+{'wkt1':'MULTIPOLYGON (((110 110,70 200,200 200,110 110),(110 110,100 180,120 180,110 110)),((110 110,200 20,70 20,110 110),(110 110,120 40,100 40,110 110)))', 'wkt2':'MULTIPOLYGON (((110 110,70 200,210 110,70 20,110 110),(110 110,110 140,150 110,110 80,110 110)),((110 110,60 60,10 110,60 160,110 110),(110 110,60 90,60 130,110 110)))', result:true},
+{'wkt1':'POLYGON ((100 60,140 100,100 140,60 100,100 60))', 'wkt2':'MULTIPOLYGON (((80 40,120 40,120 80,80 80,80 40)),((120 80,160 80,160 120,120 120,120 80)),((80 120,120 120,120 160,80 160,80 120)),((40 80,80 80,80 120,40 120,40 80)))', result:true},
+{'wkt1':'LINESTRING (150 150,40 230)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (40 40,50 130,130 130)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (40 230,150 150)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (210 150,330 150)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (200 150,310 150,360 220)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (180 150,250 150,230 250,370 250,410 150)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (210 210,220 150,320 150,370 210)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (20 60,150 60)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (60 90,310 180)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (90 210,210 90)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (290 10,130 170)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (30 100,100 100,180 100)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (20 100,100 100,360 100,410 100)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (90 210,150 150,210 90)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (180 90,280 120)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (70 70,80 20)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (130 20,150 60)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (70 70,80 20,140 20,150 60)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (170 50,170 20,240 20,260 60)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150))', result:true},
+{'wkt1':'LINESTRING (50 100,140 190,280 190)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:false},
+{'wkt1':'LINESTRING (140 60,180 100,290 100)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:false},
+{'wkt1':'LINESTRING (170 120,210 80,270 80)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'LINESTRING (170 120,260 50)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'LINESTRING (190 90,190 270)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(190 190,280 50,100 50,190 190))', result:true},
+{'wkt1':'LINESTRING (60 160,150 70)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(110 110,250 100,140 30,110 110))', result:true},
+{'wkt1':'LINESTRING (60 160,150 70)', 'wkt2':'POLYGON ((190 190,20 20,360 20,190 190),(250 100,110 110,140 30,250 100))', result:true},
+{'wkt1':'LINESTRING (60 160,150 70)', 'wkt2':'POLYGON ((190 190,20 20,360 20,190 190),(250 100,110 110,140 30,250 100))', result:true},
+{'wkt1':'LINESTRING (190 90,190 190,190 270)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(190 190,280 50,100 50,190 190))', result:true},
+{'wkt1':'LINESTRING (60 160,110 110,150 70)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(110 110,250 100,140 30,110 110))', result:true},
+{'wkt1':'LINESTRING (60 160,110 110,150 70)', 'wkt2':'POLYGON ((190 190,20 20,360 20,190 190),(250 100,110 110,140 30,250 100))', result:true},
+{'wkt1':'LINESTRING (60 160,110 110,150 70)', 'wkt2':'POLYGON ((190 190,110 110,20 20,360 20,190 190),(250 100,110 110,140 30,250 100))', result:true},
+{'wkt1':'LINESTRING (130 110,180 110,190 60)', 'wkt2':'POLYGON ((20 200,240 200,240 20,20 20,20 200),(130 110,60 180,60 40,130 110),(130 110,200 40,200 180,130 110))', result:true},
+{'wkt1':'LINESTRING (80 110,180 110)', 'wkt2':'POLYGON ((20 200,240 200,240 20,20 20,20 200),(130 110,60 180,60 40,130 110),(130 110,200 40,200 180,130 110))', result:true},
+{'wkt1':'LINESTRING (80 110,180 110)', 'wkt2':'POLYGON ((20 200,20 20,240 20,240 200,20 200),(60 180,130 110,60 40,60 180),(130 110,200 40,200 180,130 110))', result:true},
+{'wkt1':'LINESTRING (80 110,170 110)', 'wkt2':'POLYGON ((20 200,20 20,240 20,240 200,20 200),(130 110,60 40,60 180,130 110),(130 180,130 40,200 110,130 180))', result:true},
+{'wkt1':'LINESTRING (80 110,130 110,170 110)', 'wkt2':'POLYGON ((20 200,20 20,240 20,240 200,20 200),(130 110,60 40,60 180,130 110),(130 180,130 40,200 110,130 180))', result:true},
+{'wkt1':'LINESTRING (80 110,130 110,180 110)', 'wkt2':'POLYGON ((20 200,240 200,240 20,20 20,20 200),(130 110,60 180,60 40,130 110),(130 110,200 40,200 180,130 110))', result:true},
+{'wkt1':'LINESTRING (80 110,130 110,180 110)', 'wkt2':'POLYGON ((20 200,20 20,240 20,240 200,20 200),(60 180,130 110,60 40,60 180),(130 110,200 40,200 180,130 110))', result:true},
+{'wkt1':'LINESTRING (80 110,130 110,170 110)', 'wkt2':'POLYGON ((20 200,20 20,240 20,240 200,20 200),(130 110,60 40,60 180,130 110),(130 180,130 40,200 110,130 180))', result:true},
+{'wkt1':'LINESTRING (160 70,320 230)', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'LINESTRING (160 70,200 110,280 190,320 230)', 'wkt2':'MULTIPOLYGON (((140 110,260 110,170 20,50 20,140 110)),((300 270,420 270,340 190,220 190,300 270)))', result:true},
+{'wkt1':'LINESTRING (70 50,70 150)', 'wkt2':'MULTIPOLYGON (((0 0,0 100,140 100,140 0,0 0)),((20 170,70 100,130 170,20 170)))', result:true},
+{'wkt1':'LINESTRING (110 110,20 200,200 200,110 110)', 'wkt2':'POLYGON ((20 20,200 20,110 110,20 20))', result:true},
+{'wkt1':'LINESTRING (150 70,160 110,200 60,150 70)', 'wkt2':'POLYGON ((20 20,200 20,110 110,20 20))', result:true},
+{'wkt1':'LINESTRING (80 60,120 40,120 70,80 60)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,50 30,170 30,110 90))', result:true},
+{'wkt1':'LINESTRING (20 20,200 20,110 110,20 20)', 'wkt2':'POLYGON ((20 20,200 20,110 110,20 20))', result:true},
+{'wkt1':'LINESTRING (110 90,170 30,50 30,110 90)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,50 30,170 30,110 90))', result:true},
+{'wkt1':'LINESTRING (110 110,170 50,170 110,110 110)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,50 30,170 30,110 90))', result:true},
+{'wkt1':'LINESTRING (110 90,70 50,130 50,110 90)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,50 30,170 30,110 90))', result:true},
+{'wkt1':'LINESTRING (110 60,20 150,200 150,110 60)', 'wkt2':'POLYGON ((20 20,200 20,110 110,20 20))', result:true},
+{'wkt1':'LINESTRING (110 130,110 70,200 100,110 130)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,50 30,170 30,110 90))', result:true},
+{'wkt1':'LINESTRING (110 90,160 40,60 40,110 90)', 'wkt2':'POLYGON ((20 20,200 20,110 110,20 20))', result:true},
+{'wkt1':'LINESTRING (110 100,40 30,180 30,110 100)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,60 40,160 40,110 90))', result:true},
+{'wkt1':'LINESTRING (110 110,180 30,40 30,110 110)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,60 40,160 40,110 90))', result:true},
+{'wkt1':'LINESTRING (110 90,180 30,40 30,110 90)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,60 40,160 40,110 90))', result:true},
+{'wkt1':'LINESTRING (110 90,50 30,180 30,110 90)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110),(110 90,60 40,160 40,110 90))', result:true},
+{'wkt1':'LINESTRING (110 110,200 200,200 110,110 200)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 110,200 200,110 110,20 200,20 110,200 110)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 110,20 110,200 110,50 110,110 170)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 110,20 200,110 200,110 110,200 200)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 110,170 50,20 200,20 110,200 110)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 110,180 40,110 40,110 180)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 60,50 30,170 30,90 70)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (110 110,180 40,110 40,110 110,70 40)', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'LINESTRING (230 70,170 120,190 60,140 60,170 120,270 90)', 'wkt2':'POLYGON ((150 150,410 150,280 20,20 20,150 150),(170 120,330 120,260 50,100 50,170 120))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(200 200,110 110,20 210,110 110))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(60 180,60 110,160 110,110 110))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(200 200,110 110,20 200,110 200,110 110))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(110 50,110 170,110 70,110 150,200 150))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(50 110,170 110,110 170,110 50,110 170,110 50))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(110 60,110 160,200 160))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(110 60,110 160,200 160))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((110 100,40 30,180 30),(170 30,110 90,50 30))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((110 110,60 40,70 20,150 20,170 40),(180 30,40 30,110 80))', 'wkt2':'POLYGON ((110 110,200 20,20 20,110 110))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110,200 160),(110 110,200 110,200 70,20 150))', 'wkt2':'MULTIPOLYGON (((110 110,20 20,200 20,110 110)),((110 110,20 200,200 200,110 110)))', result:true},
+{'wkt1':'MULTILINESTRING ((20 160,70 110,150 110,200 160),(110 110,20 110,50 80,70 110,200 110))', 'wkt2':'MULTIPOLYGON (((110 110,20 20,200 20,110 110)),((110 110,20 200,200 200,110 110)))', result:true},
+{'wkt1':'MULTILINESTRING ((20 110,200 110),(110 110,20 170,20 130,200 90))', 'wkt2':'MULTIPOLYGON (((110 110,20 20,200 20,110 110)),((110 110,20 200,200 200,110 110)))', result:true},
+{'wkt1':'LINESTRING (0 0,0 50,50 50,50 0,0 0)', 'wkt2':'MULTILINESTRING ((0 0,0 50),(0 50,50 50),(50 50,50 0),(50 0,0 0))', result:true},
+{'wkt1':'LINESTRING (40 180,140 180)', 'wkt2':'MULTIPOLYGON (((20 320,180 320,180 180,20 180,20 320)),((20 180,20 80,180 80,180 180,20 180)))', result:true},
+{'wkt1':'LINESTRING (40 180,140 180)', 'wkt2':'MULTIPOLYGON (((20 320,180 320,180 180,20 180,20 320)),((60 180,60 80,180 80,180 180,60 180)))', result:true},
+{'wkt1':'LINESTRING (0 0,60 0,60 60,60 0,120 0)', 'wkt2':'MULTILINESTRING ((0 0,60 0),(60 0,120 0),(60 0,60 60))', result:true},
+{'wkt1':'LINESTRING (40 40,120 120)', 'wkt2':'LINESTRING (40 40,60 120)', result:true},
+{'wkt1':'LINESTRING (40 40,120 120)', 'wkt2':'LINESTRING (60 240,40 40)', result:true},
+{'wkt1':'LINESTRING (40 40,180 180)', 'wkt2':'LINESTRING (120 120,20 200)', result:true},
+{'wkt1':'LINESTRING (40 40,120 120)', 'wkt2':'LINESTRING (60 240,120 120)', result:true},
+{'wkt1':'LINESTRING (40 40,180 180)', 'wkt2':'LINESTRING (20 180,140 140)', result:true},
+{'wkt1':'LINESTRING (40 40,120 120)', 'wkt2':'LINESTRING (40 120,120 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100)', 'wkt2':'LINESTRING (40 40,100 100)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100)', 'wkt2':'LINESTRING (100 100,40 40)', result:true},
+{'wkt1':'LINESTRING (40 40,120 120)', 'wkt2':'LINESTRING (40 120,120 160)', result:false},
+{'wkt1':'LINESTRING (20 20,180 180)', 'wkt2':'LINESTRING (20 20,180 180)', result:true},
+{'wkt1':'LINESTRING (20 20,180 180)', 'wkt2':'LINESTRING (20 20,110 110)', result:true},
+{'wkt1':'LINESTRING (20 20,180 180)', 'wkt2':'LINESTRING (50 50,140 140)', result:true},
+{'wkt1':'LINESTRING (180 180,40 40)', 'wkt2':'LINESTRING (120 120,260 260)', result:true},
+{'wkt1':'LINESTRING (40 40,180 180)', 'wkt2':'LINESTRING (260 260,120 120)', result:true},
+{'wkt1':'LINESTRING (40 40,180 180)', 'wkt2':'LINESTRING (120 120,260 260)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (40 40,20 100,40 160,20 200)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (20 200,40 160,20 100,40 40)', result:true},
+{'wkt1':'LINESTRING (80 240,200 120,100 100,40 40)', 'wkt2':'LINESTRING (20 200,40 160,20 100,40 40)', result:true},
+{'wkt1':'LINESTRING (60 60,60 230,140 230,250 160)', 'wkt2':'LINESTRING (20 20,60 60,250 160,310 230)', result:true},
+{'wkt1':'LINESTRING (60 60,60 230,140 230,250 160)', 'wkt2':'LINESTRING (20 20,110 110,200 110,320 230)', result:true},
+{'wkt1':'LINESTRING (60 110,60 250,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (60 110,60 250,360 210)', 'wkt2':'LINESTRING (360 210,310 160,110 160,60 110)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (160 160,240 240)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (240 240,160 160)', result:true},
+{'wkt1':'LINESTRING (60 60,60 230,140 230,250 160)', 'wkt2':'LINESTRING (60 150,110 100,170 100,110 230)', result:true},
+{'wkt1':'LINESTRING (60 60,60 230,140 230,250 160)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (200 120,200 190,150 240,200 240)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (200 240,150 240,200 200,200 120)', result:true},
+{'wkt1':'LINESTRING (60 60,60 230,140 230,250 160)', 'wkt2':'LINESTRING (60 230,80 140,120 140,140 230)', result:true},
+{'wkt1':'LINESTRING (60 110,200 110,250 160,300 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (60 110,200 110,250 160,300 210,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (60 110,220 110,250 160,280 110)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (60 110,150 110,200 160,250 110,360 110,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (130 160,160 110,220 110,250 160,250 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (130 160,160 110,190 110,230 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (130 160,160 110,200 110,230 160,260 210,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (130 160,160 110,200 110,230 160,260 210,360 210,380 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (130 160,160 110,200 110,230 160,260 210,380 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (110 160,160 110,200 110,250 160,250 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (110 160,180 110,250 160,320 110)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (140 160,180 80,220 160,250 80)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,130 190)', 'wkt2':'LINESTRING (20 130,70 130,160 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,130 190)', 'wkt2':'LINESTRING (40 160,40 100,110 40,170 40)', result:true},
+{'wkt1':'LINESTRING (130 110,180 160,230 110,280 160,330 110)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,130 190)', 'wkt2':'LINESTRING (30 140,80 140,100 100,200 30)', result:true},
+{'wkt1':'LINESTRING (110 110,110 160,180 110,250 160,250 110)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (20 20,80 80,160 80,240 80,300 140)', 'wkt2':'LINESTRING (20 60,60 60,60 140,80 80,100 20,140 140,180 20,200 80,220 20,240 80,300 80,270 110,200 110)', result:true},
+{'wkt1':'LINESTRING (20 20,230 20,20 30,170 30,20 40,230 40,20 50,230 60,60 60,230 70,20 70,180 80,60 80,230 90,20 90,230 100,30 100,210 110,20 110,80 120,20 130,170 130,90 120,230 130,170 140,230 140,80 150,160 140,20 140,70 150,20 150,230 160,80 160,230 170,20 160,180 170,20 170,230 180,20 180,40 190,230 190,20 200,230 200)', 'wkt2':'LINESTRING (30 210,30 60,40 210,40 30,50 190,50 20,60 160,60 50,70 220,70 50,80 20,80 210,90 50,90 150,100 30,100 210,110 20,110 190,120 50,120 180,130 210,120 20,140 210,130 50,150 210,130 20,160 210,140 30,170 210,150 20,180 210,160 20,190 210,180 80,170 50,170 20,180 70,180 20,190 190,190 30,200 210,200 30,210 210,210 20,220 150,220 20)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (40 40,100 100,200 120,80 240)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (80 240,200 120,100 100,40 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (80 240,120 200,200 120,100 100,80 80,40 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (260 210,240 130,280 120,260 40)', result:false},
+{'wkt1':'LINESTRING (100 20,20 20,20 160,210 160,210 20,110 20,50 120,120 150,200 150)', 'wkt2':'LINESTRING (140 130,100 110,120 60,170 60)', result:false},
+{'wkt1':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (60 110,110 160,310 160,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', result:true},
+{'wkt1':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', 'wkt2':'LINESTRING (60 110,110 160,250 160)', result:true},
+{'wkt1':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', 'wkt2':'LINESTRING (110 160,310 160,340 190)', result:true},
+{'wkt1':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', 'wkt2':'LINESTRING (140 160,250 160,310 160,340 190)', result:true},
+{'wkt1':'LINESTRING (60 110,110 160,250 160,310 160,360 210)', 'wkt2':'LINESTRING (110 160,250 160,310 160)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (200 120,100 100,40 40,140 80,200 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (280 240,240 140,200 120,100 100,40 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (80 190,140 140,40 40)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (240 200,200 260,80 240,140 180)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (140 180,80 240,200 260,240 200)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (280 240,240 140,200 120,80 240)', result:true},
+{'wkt1':'LINESTRING (20 20,80 80,160 80,240 80,300 140)', 'wkt2':'LINESTRING (20 80,120 80,200 80,260 20)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (100 100,200 120,240 140,280 240)', result:true},
+{'wkt1':'LINESTRING (40 40,100 100,200 120,80 240)', 'wkt2':'LINESTRING (280 240,240 140,200 120,100 100)', result:true},
+{'wkt1':'LINESTRING (20 20,80 80,160 80,240 80,300 140)', 'wkt2':'LINESTRING (80 20,80 80,240 80,300 20)', result:true},
+{'wkt1':'LINESTRING (20 20,80 80,160 80,240 80,300 140)', 'wkt2':'LINESTRING (20 80,80 80,120 80,140 140,160 80,200 80,220 20,240 80,270 110,300 80)', result:true},
+{'wkt1':'LINESTRING (100 100,20 180,180 180)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (20 100,180 100,100 180)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (100 40,100 160,180 160)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (20 100,100 100,180 100,100 180)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (100 100,160 40)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (100 100,180 20)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (60 60,100 100,140 60)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (100 100,190 10,190 100)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (100 100,160 40,160 100)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (60 140,160 40,160 140)', 'wkt2':'LINESTRING (100 100,180 20,20 20,100 100)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (80 80,20 80,140 80,80 20,80 140)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (80 80,20 80,140 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (80 80,140 80,80 20,80 140)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (80 80,20 80,140 80,80 20,80 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (80 80,20 80,140 80,80 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (80 80,20 80,20 140,140 20,80 20,80 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (20 140,140 20,100 20,100 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,20 80,120 80,80 20,80 140)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,20 80,140 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,20 80,80 140,80 20)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,80 80,20 80,50 140,50 60)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,20 80,120 80,80 20,80 80,80 140)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,20 80,80 80,140 80)', result:true},
+{'wkt1':'LINESTRING (20 20,140 140)', 'wkt2':'LINESTRING (140 80,20 80,80 140,80 80,80 20)', result:true},
+{'wkt1':'LINESTRING (130 150,220 150,220 240)', 'wkt2':'LINESTRING (130 240,130 150,220 20,50 20,130 150)', result:true},
+{'wkt1':'LINESTRING (30 150,130 150,250 150)', 'wkt2':'LINESTRING (130 240,130 150,220 20,50 20,130 150)', result:true},
+{'wkt1':'LINESTRING (30 150,250 150)', 'wkt2':'LINESTRING (130 240,130 150,220 20,50 20,130 150)', result:true},
+{'wkt1':'LINESTRING (30 150,130 150,250 150)', 'wkt2':'LINESTRING (130 240,130 20,30 20,130 150)', result:true},
+{'wkt1':'LINESTRING (30 150,250 150)', 'wkt2':'LINESTRING (120 240,120 20,20 20,120 170)', result:true},
+{'wkt1':'LINESTRING (200 200,20 20,200 20,110 110,20 200,110 200,110 110)', 'wkt2':'LINESTRING (110 110,200 110)', result:true},
+{'wkt1':'LINESTRING (110 110,200 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,110 110,20 200,110 200,110 110)', result:true},
+{'wkt1':'LINESTRING (20 110,200 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,110 110,20 200,110 200,110 110)', result:true},
+{'wkt1':'LINESTRING (200 200,20 20,200 20,110 110,20 200,110 200,110 110)', 'wkt2':'LINESTRING (20 110,200 110)', result:true},
+{'wkt1':'LINESTRING (90 200,90 130,110 110,150 200)', 'wkt2':'LINESTRING (200 200,20 20,200 20,20 200,20 130,90 130)', result:true},
+{'wkt1':'LINESTRING (200 110,110 110,90 130,90 200)', 'wkt2':'LINESTRING (200 200,20 20,200 20,20 200,20 130,90 130)', result:true},
+{'wkt1':'LINESTRING (80 80,150 80,210 80)', 'wkt2':'MULTILINESTRING ((20 20,140 140),(20 140,140 20))', result:true},
+{'wkt1':'LINESTRING (40 80,160 200,260 20,40 80)', 'wkt2':'LINESTRING (40 80,160 200,260 20,40 80)', result:true},
+{'wkt1':'LINESTRING (40 80,160 200,260 20,40 80)', 'wkt2':'LINESTRING (40 80,260 20,160 200,40 80)', result:true},
+{'wkt1':'LINESTRING (40 80,160 200,260 20,40 80)', 'wkt2':'LINESTRING (260 20,40 80,160 200,260 20)', result:true},
+{'wkt1':'LINESTRING (40 80,160 200,260 20,40 80)', 'wkt2':'LINESTRING (100 140,160 200,260 20,40 80,100 140)', result:true},
+{'wkt1':'LINESTRING (100 100,180 20,20 20,100 100)', 'wkt2':'LINESTRING (100 100,180 180,20 180,100 100)', result:true},
+{'wkt1':'LINESTRING (40 150,40 40,150 40,150 150,40 150)', 'wkt2':'LINESTRING (40 150,150 40,170 20,170 190,40 150)', result:true},
+{'wkt1':'LINESTRING (100 100,180 20,20 20,100 100)', 'wkt2':'LINESTRING (180 100,20 100,100 180,180 100)', result:true},
+{'wkt1':'LINESTRING (100 100,180 20,20 20,100 100)', 'wkt2':'LINESTRING (180 180,100 100,20 180,180 180)', result:true},
+{'wkt1':'LINESTRING (20 180,100 100,20 20,20 180)', 'wkt2':'LINESTRING (100 20,100 180,180 100,100 20)', result:true},
+{'wkt1':'LINESTRING (40 150,40 40,150 40,150 150,40 150)', 'wkt2':'LINESTRING (170 20,20 170,170 170,170 20)', result:true},
+{'wkt1':'LINESTRING (40 150,40 40,150 40,150 150,40 150)', 'wkt2':'LINESTRING (40 150,150 150,90 210,40 150)', result:true},
+{'wkt1':'LINESTRING (40 150,40 40,150 40,150 150,40 150)', 'wkt2':'LINESTRING (20 150,170 150,90 230,20 150)', result:true},
+{'wkt1':'LINESTRING (40 150,40 40,150 40,150 150,40 150)', 'wkt2':'LINESTRING (40 150,150 150,150 40,20 40,20 150,40 150)', result:true},
+{'wkt1':'LINESTRING (110 110,200 20,20 20,110 110)', 'wkt2':'LINESTRING (110 110,200 200,110 110,20 200,20 110,200 110)', result:true},
+{'wkt1':'LINESTRING (110 110,200 20,20 20,110 110)', 'wkt2':'LINESTRING (110 110,20 110,200 110,50 110,110 170)', result:true},
+{'wkt1':'LINESTRING (110 110,200 20,20 20,110 110)', 'wkt2':'LINESTRING (110 110,20 200,110 200,110 110,200 200)', result:true},
+{'wkt1':'LINESTRING (110 110,200 20,20 20,110 110)', 'wkt2':'LINESTRING (200 20,20 200,200 200,110 110,110 40)', result:true},
+{'wkt1':'LINESTRING (110 110,200 20,20 20,110 110)', 'wkt2':'LINESTRING (200 20,20 200,200 200,20 20)', result:true},
+{'wkt1':'LINESTRING (110 110,20 110,110 20,20 20,110 110)', 'wkt2':'LINESTRING (110 110,200 200,110 200,200 110,110 110)', result:true},
+{'wkt1':'LINESTRING (20 120,120 120,20 20,120 20,20 120)', 'wkt2':'LINESTRING (170 100,70 100,170 170,70 170,170 100)', result:true},
+{'wkt1':'LINESTRING (20 110,110 110,20 20,110 20,20 110)', 'wkt2':'LINESTRING (110 160,70 110,60 160,20 130,110 160)', result:true},
+{'wkt1':'LINESTRING (20 200,200 200,20 20,200 20,20 200)', 'wkt2':'LINESTRING (20 110,200 110,200 160,20 60,20 110)', result:true},
+{'wkt1':'LINESTRING (20 110,110 110,20 20,110 20,20 110)', 'wkt2':'LINESTRING (200 200,110 110,200 110,110 200,200 200)', result:true},
+{'wkt1':'LINESTRING (20 120,120 120,20 20,120 20,20 120)', 'wkt2':'LINESTRING (220 120,120 20,220 20,120 120,220 120)', result:true},
+{'wkt1':'MULTILINESTRING ((70 20,20 90,70 170),(70 170,120 90,70 20))', 'wkt2':'MULTILINESTRING ((70 20,20 90,70 170),(70 170,120 90,70 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 140,90 60,90 20),(170 20,130 20,20 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(170 60,90 20,20 60),(130 100,130 60,90 20,50 90))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(130 140,130 60,90 20,20 90,90 20,130 60,170 60))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(170 60,90 20,20 60))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(170 60,90 20,20 60),(130 100,90 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(170 60,90 20,20 60),(120 100,170 100,90 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(170 60,90 20,20 60),(120 100,170 100,90 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(130 140,130 60,90 20,20 90,90 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 20,170 20),(90 20,90 80,90 140))', 'wkt2':'MULTILINESTRING ((90 20,170 100,170 140),(170 60,90 20,20 60,20 140,90 20))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 90,20 160),(90 160,90 20))', 'wkt2':'MULTILINESTRING ((160 160,90 90,160 20),(160 120,120 120,90 90,160 60))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 90,20 160),(90 160,90 20))', 'wkt2':'MULTILINESTRING ((160 160,90 90,160 20),(160 120,120 120,90 90,120 60,160 60))', result:true},
+{'wkt1':'MULTILINESTRING ((20 20,90 90,20 160),(90 160,90 20))', 'wkt2':'MULTILINESTRING ((160 160,90 90,160 20),(160 120,90 90,160 60))', result:true},
+{'wkt1':'POINT (20 20)', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:false},
+{'wkt1':'POINT (70 170)', 'wkt2':'POLYGON ((110 230,80 160,20 160,20 20,200 20,200 160,140 160,110 230))', result:false},
+{'wkt1':'POINT (110 130)', 'wkt2':'POLYGON ((20 160,80 160,110 100,140 160,200 160,200 20,20 20,20 160))', result:false},
+{'wkt1':'POINT (100 70)', 'wkt2':'POLYGON ((20 150,100 150,40 50,170 50,110 150,190 150,190 20,20 20,20 150))', result:false},
+{'wkt1':'POINT (100 70)', 'wkt2':'POLYGON ((20 150,100 150,40 50,160 50,100 150,180 150,180 20,20 20,20 150))', result:false},
+{'wkt1':'POINT (60 120)', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'POINT (110 120)', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'POINT (160 120)', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'POINT (100 150)', 'wkt2':'POLYGON ((20 150,100 150,40 50,160 50,100 150,180 150,180 20,20 20,20 150))', result:true},
+{'wkt1':'POINT (100 80)', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'POINT (60 160)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:false},
+{'wkt1':'POINT (190 90)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:false},
+{'wkt1':'POINT (190 190)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (360 20)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (130 130)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (280 50)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (150 100)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (100 50)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (140 120)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:true},
+{'wkt1':'POINT (190 50)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(90 50,150 110,190 50,90 50),(190 50,230 110,290 50,190 50))', result:true},
+{'wkt1':'POINT (180 90)', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(180 140,180 40,80 40,180 140),(180 90,210 140,310 40,230 40,180 90))', result:true},
+{'wkt1':'MULTIPOINT ((20 80),(110 160),(20 160))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:false},
+{'wkt1':'MULTIPOINT ((20 80),(60 120),(20 160))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((10 80),(110 170),(110 120))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((10 80),(110 170),(160 120))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((20 120),(60 120),(110 120),(160 120),(200 120))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((60 120),(110 120),(160 120))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((60 120),(160 120),(160 40),(60 40))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((20 150),(60 120),(110 80))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((110 80),(160 120),(200 160))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((110 80),(110 120),(110 160))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((110 170),(110 80))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((60 120),(160 120),(110 80),(110 170))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((90 80),(130 80))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((60 120),(160 120),(110 80))', 'wkt2':'POLYGON ((60 120,60 40,160 40,160 120,60 120))', result:true},
+{'wkt1':'MULTIPOINT ((40 170),(40 90),(130 170))', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:false},
+{'wkt1':'MULTIPOINT ((90 170),(280 170),(190 90))', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:false},
+{'wkt1':'MULTIPOINT ((190 110),(150 70),(230 70))', 'wkt2':'POLYGON ((190 190,360 20,20 20,190 190),(280 50,100 50,190 140,280 50))', result:false},
+{'wkt1':'POINT (100 100)', 'wkt2':'MULTIPOLYGON (((20 100,20 20,100 20,100 100,20 100)),((100 180,100 100,180 100,180 180,100 180)))', result:true},
+{'wkt1':'POINT (20 100)', 'wkt2':'MULTIPOLYGON (((20 100,20 20,100 20,100 100,20 100)),((100 180,100 100,180 100,180 180,100 180)))', result:true},
+{'wkt1':'POINT (60 100)', 'wkt2':'MULTIPOLYGON (((20 100,20 20,100 20,100 100,20 100)),((100 180,100 100,180 100,180 180,100 180)))', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'MULTIPOLYGON (((110 110,20 200,200 200,110 110),(110 110,80 180,140 180,110 110)),((110 110,20 20,200 20,110 110),(110 110,80 40,140 40,110 110)))', result:true},
+{'wkt1':'POINT (110 200)', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:false},
+{'wkt1':'POINT (90 80)', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'POINT (340 240)', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'POINT (230 150)', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'POINT (160 150)', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'POINT (90 150)', 'wkt2':'LINESTRING (150 150,20 20,280 20,150 150)', result:false},
+{'wkt1':'POINT (150 80)', 'wkt2':'LINESTRING (150 150,20 20,280 20,150 150)', result:false},
+{'wkt1':'POINT (150 150)', 'wkt2':'LINESTRING (150 150,20 20,280 20,150 150)', result:true},
+{'wkt1':'POINT (100 20)', 'wkt2':'LINESTRING (150 150,20 20,280 20,150 150)', result:true},
+{'wkt1':'POINT (20 20)', 'wkt2':'LINESTRING (150 150,20 20,280 20,150 150)', result:true},
+{'wkt1':'POINT (220 220)', 'wkt2':'LINESTRING (110 110,220 20,20 20,110 110,220 220)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,220 20,20 20,110 110,220 220)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,220 20,20 20,220 220)', result:true},
+{'wkt1':'POINT (110 20)', 'wkt2':'LINESTRING (110 110,220 20,20 20,220 220)', result:true},
+{'wkt1':'POINT (220 20)', 'wkt2':'LINESTRING (110 110,220 20,20 20,220 220)', result:true},
+{'wkt1':'POINT (110 20)', 'wkt2':'LINESTRING (220 220,20 20,220 20,110 110)', result:true},
+{'wkt1':'POINT (20 20)', 'wkt2':'LINESTRING (220 220,20 20,220 20,110 110)', result:true},
+{'wkt1':'POINT (20 110)', 'wkt2':'LINESTRING (20 200,20 20,110 20,20 110,110 200)', result:true},
+{'wkt1':'POINT (20 200)', 'wkt2':'LINESTRING (20 200,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,200 20,140 20,140 80,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (80 140)', 'wkt2':'LINESTRING (20 200,110 110,200 20,140 20,140 80,110 110,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,110 110,200 20,140 20,140 80,110 110,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,200 20,140 20,140 80,110 110,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,110 110,200 20,20 20,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,200 20,20 20,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,110 110,20 20,200 20,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,110 110,200 20,140 20,140 80,110 110,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,200 20,140 20,140 80,110 110,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,200 20,140 20,140 80,80 140,20 140)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,110 110,200 20,20 20,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,200 20,20 20,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,110 110,20 20,200 20,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,110 200,20 200,200 20,200 110,110 110,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,110 110,20 20,200 20,110 110,20 200,110 200,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,110 110,20 200,110 200,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,20 200,110 200,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,110 110,200 20,20 20,110 110,20 200,110 200,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,20 20,20 110,110 110,20 200,110 200,110 110)', result:true},
+{'wkt1':'POINT (110 160)', 'wkt2':'LINESTRING (110 160,200 250,110 250,110 160,110 110,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 160)', 'wkt2':'LINESTRING (110 160,200 250,110 250,110 110,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 160,200 250,110 250,110 160,110 110,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 160,200 250,110 250,110 160,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 110,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (140 200)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 110,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 200)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 110,110 20,20 20,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 110,110 20,200 20,110 110)', result:true},
+{'wkt1':'POINT (140 200)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 110,110 20,200 20,110 110)', result:true},
+{'wkt1':'POINT (110 200)', 'wkt2':'LINESTRING (110 110,200 200,110 200,110 110,110 20,200 20,110 110)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,20 130,20 200,90 130,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (90 130,20 130,20 200,90 130,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,20 130,20 200,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (90 130,20 130,20 200,200 20,20 20,200 200)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (200 200,20 20,200 20,90 130,20 200,20 130,90 130)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,90 130,20 200,20 130,90 130)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (200 200,20 20,200 20,20 200,20 130,90 130)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,20 200,20 130,90 130)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,20 130,20 200,110 110,200 20,20 20,110 110,200 200,200 130,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,20 130,20 200,200 20,20 20,200 200,200 130,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,80 200,20 200,110 110,200 20,20 20,110 110,200 200,140 200,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 110,80 200,20 200,200 20,20 20,200 200,140 200,110 110)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,20 20,200 20,20 200,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,110 110,20 20,200 20,110 110,20 200,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (200 200,110 110,200 20,20 20,110 110,20 200,200 200)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,20 130,20 200,90 130,110 110,200 20,20 20,110 110,200 200,90 130)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,20 130,20 200,110 110,200 20,20 20,110 110,200 200,90 130)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,90 200,20 200,90 130,110 110,200 20,20 20,110 110,200 200,90 130)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,90 200,20 200,200 20,20 20,200 200,90 130)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,90 200,20 200,110 110,200 20,20 20,110 110,200 200,90 130)', result:true},
+{'wkt1':'POINT (90 130)', 'wkt2':'LINESTRING (90 130,90 200,20 200,200 20,20 20,200 200,90 130)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (90 130,90 200,20 200,200 20,20 20,200 200,90 130)', result:true},
+{'wkt1':'POINT (110 200)', 'wkt2':'LINESTRING (110 200,110 110,20 20,200 20,110 110,110 200,200 200)', result:true},
+{'wkt1':'POINT (110 150)', 'wkt2':'LINESTRING (110 200,110 110,20 20,200 20,110 110,110 200,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 200,110 110,20 20,200 20,110 110,110 200,200 200)', result:true},
+{'wkt1':'POINT (110 200)', 'wkt2':'LINESTRING (110 200,110 110,20 20,200 20,110 110,110 200)', result:true},
+{'wkt1':'POINT (110 150)', 'wkt2':'LINESTRING (110 200,110 110,20 20,200 20,110 110,110 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (110 200,110 110,20 20,200 20,110 110,110 200)', result:true},
+{'wkt1':'POINT (110 150)', 'wkt2':'LINESTRING (20 200,110 200,110 110,20 20,200 20,110 110,110 200,200 200)', result:true},
+{'wkt1':'POINT (110 110)', 'wkt2':'LINESTRING (20 200,110 200,110 110,20 20,200 20,110 110,110 200,200 200)', result:true},
+{'wkt1':'POINT (110 200)', 'wkt2':'LINESTRING (20 200,110 200,110 110,20 20,200 20,110 110,110 200,200 200)', result:true},
+{'wkt1':'MULTIPOINT ((50 250),(90 220),(130 190))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:false},
+{'wkt1':'MULTIPOINT ((180 180),(230 130),(280 80))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:false},
+{'wkt1':'MULTIPOINT ((50 120),(90 80),(130 40))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((300 280),(340 240),(380 200))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((230 150),(260 120),(290 90))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((200 190),(240 150),(270 110))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((160 150),(190 120),(220 90))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((120 190),(160 150),(200 110))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((90 80),(160 150),(340 240))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((90 80),(160 150),(300 150))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((90 80),(160 150),(240 150))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((90 80),(130 120),(210 150))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((130 120),(210 150),(340 200))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((160 150),(240 150),(340 210))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((160 150),(300 150),(340 150))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'MULTIPOINT ((160 150),(240 150),(340 240))', 'wkt2':'LINESTRING (90 80,160 150,300 150,340 150,340 240)', result:true},
+{'wkt1':'POINT (20 20)', 'wkt2':'POINT (20 20)', result:true},
+{'wkt1':'POINT (20 20)', 'wkt2':'POINT (40 60)', result:false},
+{'wkt1':'POINT (40 40)', 'wkt2':'MULTIPOINT ((20 20),(80 80),(20 120))', result:false},
+{'wkt1':'POINT (20 20)', 'wkt2':'MULTIPOINT ((20 20),(80 80),(20 120))', result:true},
+{'wkt1':'MULTIPOINT ((40 40),(80 60),(120 100))', 'wkt2':'MULTIPOINT ((40 40),(80 60),(120 100))', result:true},
+{'wkt1':'MULTIPOINT ((40 40),(80 60),(120 100))', 'wkt2':'MULTIPOINT ((40 40),(120 100),(80 60))', result:true},
+{'wkt1':'MULTIPOINT ((40 40),(60 100),(100 60),(120 120))', 'wkt2':'MULTIPOINT ((20 120),(60 60),(100 100),(140 40))', result:false},
+{'wkt1':'MULTIPOINT ((20 20),(80 70),(140 120),(200 170))', 'wkt2':'MULTIPOINT ((20 20),80 70),(140 120),(200 170))', result:true},
+{'wkt1':'MULTIPOINT ((20 20),(140 120),(80 70),(200 170))', 'wkt2':'MULTIPOINT ((80 70),20 20),(200 170),(140 120))', result:true},
+{'wkt1':'MULTIPOINT ((20 20),(80 70),(140 120),(200 170))', 'wkt2':'MULTIPOINT ((80 70),(140 120))', result:true},
+{'wkt1':'MULTIPOINT ((80 70),(20 20),(200 170),(140 120))', 'wkt2':'MULTIPOINT ((140 120),(80 70))', result:true},
+{'wkt1':'MULTIPOINT ((80 70),(20 20),(200 170),(140 120))', 'wkt2':'MULTIPOINT ((80 170),(140 120),(200 80))', result:true},
+{'wkt1':'MULTIPOINT ((80 70),(20 20),(200 170),(140 120))', 'wkt2':'MULTIPOINT ((80 170),(140 120),(200 80),(80 70))', result:true},
+{'wkt1':'POLYGON((-8239529.462853361 4980952.065110421,-8224242.057199065 4980952.065110421,-8224242.057199064 4988844.188279452,-8239529.462853361 4988844.188279452,-8239529.462853361 4980952.065110421))', 'wkt2':'POINT(-8225445.94039435 4982695.78481786)', result:true},
+{'wkt1':'POLYGON((-8239529.462853361 4980952.065110421,-8224242.057199065 4980952.065110421,-8224242.057199064 4988844.188279452,-8239529.462853361 4988844.188279452,-8239529.462853361 4980952.065110421))', 'wkt2':'POINT(-8224242.0571985 4982695.78481786)', result:false},
+{'wkt1':'POLYGON((-8239529.462853361 4980952.065110421,-8224242.057199065 4980952.065110421,-8224242.057199064 4988844.188279452,-8239529.462853361 4988844.188279452,-8239529.462853361 4980952.065110421))', 'wkt2':'POINT(-8224242.0571995 4982695.78481786)', result:true}
+];
diff --git a/misc/openlayers/tests/data/osm.js b/misc/openlayers/tests/data/osm.js
new file mode 100644
index 0000000..6c94459
--- /dev/null
+++ b/misc/openlayers/tests/data/osm.js
@@ -0,0 +1,14 @@
+var osm_test_data = {
+ 'node': '<?xml version="1.0" encoding="UTF-8"?><osm version="0.5" generator="OpenStreetMap server"> <node id="200545" lat="52.5503033" lon="-1.8166417" user="blackadder" visible="true" timestamp="2006-03-22T16:33:41+00:00"/></osm>',
+ 'node_with_tags': '<?xml version="1.0" encoding="UTF-8"?><osm version="0.5" generator="OpenStreetMap server"> <node id="200545" lat="52.5503033" lon="-1.8166417" user="blackadder" visible="true" timestamp="2006-03-22T16:33:41+00:00"><tag k="a" v="b" /></node></osm>',
+ 'way': '<?xml version="1.0" encoding="UTF-8"?><osm version="0.5" generator="OpenStreetMap server"> <node id="29783468" lat="52.5506446" lon="-1.8141177" user="blackadder" visible="true" timestamp="2007-05-30T14:22:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783470" lat="52.5501275" lon="-1.8151451" user="blackadder" visible="true" timestamp="2007-05-30T14:22:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783471" lat="52.5505521" lon="-1.8157703" user="blackadder" visible="true" timestamp="2007-12-18T15:33:59+00:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783472" lat="52.5501836" lon="-1.8164007" user="blackadder" visible="true" timestamp="2007-12-18T15:33:59+00:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783473" lat="52.5506035" lon="-1.8170311" user="blackadder" visible="true" timestamp="2007-05-30T14:21:32+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783474" lat="52.5509559" lon="-1.8164092" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783476" lat="52.5513103" lon="-1.8169385" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783477" lat="52.5517893" lon="-1.8159626" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783478" lat="52.5518461" lon="-1.8145067" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <node id="29783479" lat="52.5511883" lon="-1.8143197" user="blackadder" visible="true" timestamp="2007-05-30T14:21:33+01:00"> <tag k="created_by" v="JOSM"/> </node> <way id="4685537" visible="true" timestamp="2007-05-30T14:21:35+01:00" user="blackadder"> <nd ref="29783472"/> <nd ref="29783473"/> <nd ref="29783474"/> <nd ref="29783476"/> <nd ref="29783477"/> <nd ref="29783478"/> <nd ref="29783479"/> <nd ref="29783468"/> <nd ref="29783470"/> <nd ref="29783471"/> <nd ref="29783472"/> <tag k="name" v="Maney Hill School"/> <tag k="created_by" v="JOSM"/> <tag k="landuse" v="school"/> <tag k="amenity" v="school"/> </way></osm>',
+ 'node_way': '<?xml version="1.0" encoding="UTF-8"?><osm version="0.5" generator="OpenStreetMap server"> <node id="200565" lat="52.5526654" lon="-1.8146664" user="blackadder" visible="true" timestamp="2006-03-22T16:34:29+00:00"/> <node id="200571" lat="52.5535575" lon="-1.8148566" user="blackadder" visible="true" timestamp="2007-11-15T12:54:40+00:00"/> <node id="200572" lat="52.5522848" lon="-1.8145676" user="blackadder" visible="true" timestamp="2008-01-15T17:36:32+00:00"> <tag k="ref" v="0562901"/> <tag k="both_sides" v="true"/> <tag k="route_ref" v="167|757"/> <tag k="highway" v="bus_stop"/> <tag k="location" v="East View Road, Shooters Hill"/> </node> <node id="200573" lat="52.5520736" lon="-1.8145054" user="blackadder" visible="true" timestamp="2006-03-22T16:34:49+00:00"/> <node id="200751" lat="52.5511951" lon="-1.8142246" user="blackadder" visible="true" timestamp="2006-03-22T16:36:18+00:00"/> <node id="200752" lat="52.5505598" lon="-1.8140051" user="blackadder" visible="true" timestamp="2006-03-22T16:36:20+00:00"/> <node id="200753" lat="52.5496876" lon="-1.8136891" user="blackadder" visible="true" timestamp="2006-03-22T21:55:13+00:00"/> <node id="200754" lat="52.549009" lon="-1.8133906" user="blackadder" visible="true" timestamp="2006-03-22T16:36:24+00:00"/> <node id="200755" lat="52.5478879" lon="-1.8128287" user="blackadder" visible="true" timestamp="2006-03-22T16:36:26+00:00"/> <node id="200759" lat="52.5464722" lon="-1.8119684" user="blackadder" visible="true" timestamp="2006-03-22T16:36:34+00:00"/> <node id="200771" lat="52.5466788" lon="-1.8121387" user="blackadder" visible="true" timestamp="2008-01-15T16:56:49+00:00"/> <node id="645730" lat="52.5491787" lon="-1.8134657" user="blackadder" visible="true" timestamp="2008-01-15T17:36:32+00:00"> <tag k="created_by" v="JOSM"/> <tag k="ref" v="0562201"/> <tag k="both_sides" v="true"/> <tag k="route_ref" v="167|757"/> <tag k="highway" v="bus_stop"/> <tag k="location" v="East View Road, Recretaion Ground"/> <tag k="amenity" v="bus_stop"/> </node> <way id="21329267" visible="true" timestamp="2008-01-15T16:24:55+00:00" user="blackadder"> <nd ref="200571"/> <nd ref="200565"/> <nd ref="200572"/> <nd ref="200573"/> <nd ref="200751"/> <nd ref="200752"/> <nd ref="200753"/> <nd ref="645730"/> <nd ref="200754"/> <nd ref="200755"/> <nd ref="200771"/> <nd ref="200759"/> <tag k="name" v="East View Road"/> <tag k="postal_code" v="B72"/> <tag k="place_name" v="Sutton Coldfield"/> <tag k="created_by" v="JOSM"/> <tag k="highway" v="unclassified"/> </way></osm>'
+};
+
+var osm_serialized_data = {
+ 'node':'<osm version="0.5" generator="OpenLayers '+OpenLayers.VERSION_NUMBER+'"><node id="200545" lon="-1.8166417" lat="52.5503033"/></osm>',
+ 'node_with_tags':'<osm version="0.5" generator="OpenLayers '+OpenLayers.VERSION_NUMBER+'"><node id="200545" lon="-1.8166417" lat="52.5503033"><tag k="a" v="b"/></node></osm>',
+ 'way':'<osm version="0.5" generator="OpenLayers '+OpenLayers.VERSION_NUMBER+'"><node id="29783472" lon="-1.8164007" lat="52.5501836"/><node id="29783473" lon="-1.8170311" lat="52.5506035"/><node id="29783474" lon="-1.8164092" lat="52.5509559"/><node id="29783476" lon="-1.8169385" lat="52.5513103"/><node id="29783477" lon="-1.8159626" lat="52.5517893"/><node id="29783478" lon="-1.8145067" lat="52.5518461"/><node id="29783479" lon="-1.8143197" lat="52.5511883"/><node id="29783468" lon="-1.8141177" lat="52.5506446"/><node id="29783470" lon="-1.8151451" lat="52.5501275"/><node id="29783471" lon="-1.8157703" lat="52.5505521"/><way id="4685537"><nd ref="29783472"/><nd ref="29783473"/><nd ref="29783474"/><nd ref="29783476"/><nd ref="29783477"/><nd ref="29783478"/><nd ref="29783479"/><nd ref="29783468"/><nd ref="29783470"/><nd ref="29783471"/><nd ref="29783472"/><tag k="area" v="yes"/><tag k="name" v="Maney Hill School"/><tag k="created_by" v="JOSM"/><tag k="landuse" v="school"/><tag k="amenity" v="school"/></way></osm>',
+ 'node_way':'<osm version="0.5" generator="OpenLayers '+OpenLayers.VERSION_NUMBER+'"><node id="645730" lon="-1.8134657" lat="52.5491787"><tag k="created_by" v="JOSM"/><tag k="ref" v="0562201"/><tag k="both_sides" v="true"/><tag k="route_ref" v="167|757"/><tag k="highway" v="bus_stop"/><tag k="location" v="East View Road, Recretaion Ground"/><tag k="amenity" v="bus_stop"/></node><node id="200572" lon="-1.8145676" lat="52.5522848"><tag k="ref" v="0562901"/><tag k="both_sides" v="true"/><tag k="route_ref" v="167|757"/><tag k="highway" v="bus_stop"/><tag k="location" v="East View Road, Shooters Hill"/></node><node id="200571" lon="-1.8148566" lat="52.5535575"/><node id="200565" lon="-1.8146664" lat="52.5526654"/><node id="200573" lon="-1.8145054" lat="52.5520736"/><node id="200751" lon="-1.8142246" lat="52.5511951"/><node id="200752" lon="-1.8140051" lat="52.5505598"/><node id="200753" lon="-1.8136891" lat="52.5496876"/><node id="200754" lon="-1.8133906" lat="52.549009"/><node id="200755" lon="-1.8128287" lat="52.5478879"/><node id="200771" lon="-1.8121387" lat="52.5466788"/><node id="200759" lon="-1.8119684" lat="52.5464722"/><way id="21329267"><nd ref="200571"/><nd ref="200565"/><nd ref="200572"/><nd ref="200573"/><nd ref="200751"/><nd ref="200752"/><nd ref="200753"/><nd ref="645730"/><nd ref="200754"/><nd ref="200755"/><nd ref="200771"/><nd ref="200759"/><tag k="name" v="East View Road"/><tag k="postal_code" v="B72"/><tag k="place_name" v="Sutton Coldfield"/><tag k="created_by" v="JOSM"/><tag k="highway" v="unclassified"/></way></osm>'
+};
+
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/0.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/0.json
new file mode 100644
index 0000000..e1f305b
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/0.json
@@ -0,0 +1 @@
+{"keys": ["", "269", "270", "572", "271", "272", "585", "586", "273", "589", "573", "274", "275", "560", "558", "559", "562", "561", "279", "563", "566", "564", "281", "574", "565", "285", "286", "287", "576", "575", "1", "289", "569", "568", "567", "590", "295", "292", "294", "2", "299", "297", "578", "587", "556", "309", "570", "577", "313", "310", "312", "588", "315", "579", "592", "591", "557", "582", "580", "318", "319", "583", "321", "571", "584", "322", "323", "326", "325", "329", "332", "331", "336", "337", "611", "612", "339", "341", "617", "622", "623", "18", "349", "624", "350", "19", "20", "619", "625", "353", "357", "361", "5", "364", "359", "338", "620", "367", "370", "626", "365", "627", "376", "9", "7", "377", "378", "621", "383", "6", "11", "374", "380", "385", "394", "386", "396", "399", "398", "407", "400", "409", "412", "4", "24", "405", "427", "424", "420", "404", "431", "432", "433", "419", "429", "92", "117", "88", "440", "441", "94", "442", "91", "444", "97", "96", "95", "443", "439", "449", "446", "100", "451", "106", "109", "105", "103", "102", "456", "453", "450", "113", "112", "459", "114", "458", "461", "111", "467", "473", "118", "462", "474", "480", "479"], "data": {"623": {"dom_desc": "", "pro_desc": ""}, "622": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SMALL-LEAFED AND CONIFEROUS WOODED STEPPES OF CONTINENTAL CLIMATE"}, "621": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "620": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "627": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "571": {"dom_desc": "", "pro_desc": ""}, "626": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "24": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "1": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "20": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "624": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "289": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "573": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "405": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "404": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "4": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "400": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "281": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "5": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "285": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "349": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "287": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "286": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "453": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "577": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "575": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "420": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "269": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "574": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "378": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "412": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "299": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "370": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "294": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "295": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "292": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "374": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "377": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "376": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "591": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "586": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "319": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "318": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "587": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "313": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "312": {"dom_desc": "", "pro_desc": ""}, "310": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "584": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "315": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "270": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "271": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "117": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "273": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "111": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "275": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "113": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "112": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "279": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "399": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "398": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "118": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "429": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "7": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "367": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "364": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "365": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "424": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "427": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "361": {"dom_desc": "", "pro_desc": ""}, "570": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "309": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "449": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "585": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "582": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "583": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "580": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "443": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "442": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "441": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "440": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "446": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "588": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "444": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "380": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "109": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "385": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "386": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "297": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "102": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "103": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "100": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "589": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "106": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "105": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "419": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "383": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "88": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "439": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "432": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "433": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "431": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT"}, "458": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "459": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "579": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "578": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "339": {"dom_desc": "POLAR DOMAIN", "pro_desc": "MODERATE CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "338": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "625": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "590": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "450": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "451": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "337": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "336": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "331": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "576": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "456": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "332": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY CONTINENTAL AND CONTINENTAL CLIMATE"}, "592": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC MOSS-AND-GRASS TUNDRA"}, "407": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-MEADOW OF EASTERN OCEANIC (MONSOON CLIMATE)"}, "2": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "6": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "341": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "568": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "569": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "556": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "560": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "561": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "467": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "563": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "461": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "565": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "566": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "462": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "91": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "92": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "95": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "94": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "97": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "96": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "11": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "114": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "19": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "18": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "272": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "409": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "274": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "396": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "559": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "558": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "557": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC MOSS-AND-GRASS TUNDRA"}, "394": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "322": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "323": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "321": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "326": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MIXED CONIFEROUS AND SMALL-LEAFED FOREST"}, "325": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "9": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "329": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "562": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "619": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "612": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "564": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "611": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW-TUNDRA"}, "617": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "567": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "480": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "357": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "473": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "353": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "474": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "350": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "479": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "572": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "359": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}}, "grid": [" ", " ", " ", " ", " ! ", " ! !!!! ", " !!!!!!!! ## # ", " !!!!!!!!!! ##### ", " !!$!$$!!!!! ####%%# ", " &&!!$$$$$$$!! ' (%%#%%%%# ", " &&!!$$$$$$$!!)'' (%%%%%%%## * ", " &&!!!!!!!!!! )''(((%%%%%%## ** ", " & !!! !!!!! %%%%%(%%%%%%%* * ", " &&& !!++!,,%%%%%%%%%%%%**** ", " --&!!!+++,,,%%%%%%%%%%%%**** ", " &-&!!+++ ,%%%%%%%%%%%%**** ", " .. &-&!++ ,,%%%%%%%%%%%%%%** ", " / ..00&&!!+++,,,,%%%%%%%%%%%%%%%* ", " / .. 0& !!+++%%%%%%%%%%%%%%%%%%%* ", " 1 2 !!!!+ %%%%%%%%%%%%%%%%%%* ", " 333 1 4 !!!!! %%%%%%%%%%%%%%%%%%* ", " 3333 5 666447!!! %%%%%%%%%%%%%%%%%* ", " 5555 66677788 % %%%%%%%%%%%%%%* ", " 5555 66997888 %%%%%%%%%%%%%%* ", " :::5555 ; < => %%%%%%%%%%%%** ", " ?? @::A BB;;;<<<= %%%%%%%%%%%%* ", " @@@AA AACBB;;<<<<<< %%%%%%%%%%%%D ", " E @@FAAAA BBG <<<<<<<< %%%%%%%%%%DD ", "H EEEEEEI J FFAAA GG <<KKK<<< %%%%%%%%%%%% ", "HH IIEEEIIIIJJJJJJJ FFFAAA GG GK KK<< LL%%%%%%%%% ", "MMH IIIIIIIIIIINNJJJJJJJ JAAOOGG G PK<< LL%%%%%%%% ", "MMM MMIIIIIIQQRRNNNNNSNJJJJJJJGGGGG P KK< TT%%%%%% ", "MMMMMUUUUUUUQQQRRRNNNSSNJJJJJJJJGGG KVKK< TT%%%%% WXX ", "YM M UUUUUUUQQRRRRRRRSNNNJJJJJJJJZ KKK[KK TT%%% WXWX ", " UUU]]]^^RQRRRRRRNNNNJJJJJJJJZZZKKK_KK T%%% WWW ", " QU]]^``^RRRRRRRNNaaNNJJJJJ b c K_K %%%% ", " QQdd^````^^RRRRReeafNNNNJJ cccc %%% ", " Qdd```ggg^^^RRheeeeffNNNN cccc i % ", " dj^ ^^^RhheeeefffNNN cccNii k", " lmm ^hhhheeeeefnnnNJ cccNiii kk", " l ^hhhheeeeennnnNNJ cNNNNii ko", " p^^hhhqqqqqernnnnN NNNNNNNi ssso", " ^hhtqquuuqrnnnnNNNNNNvvvvN wsxo", " ythttuzzzuq{{nnnNNvvvvvv|| oo", " yytttzzzzzu{{}}{nnvvv~\u007f \u0080\u0080 \u0081\u0081", " \u0082\u0083\u0083ttzzzzuu}}}}{{{{{\u007f\u007f\u007f \u0080\u0080 \u0081", " \u0082\u0083\u0084tt\u0084\u0085zzzu\u0086{}}}{{{\u007f\u007f\u007f\u007f ", " ^\u0084\u0084\u0084\u0087\u0084\u0085zzuu\u0088\u0088}\u0086}{\u0089\u008a\u008a \u008b\u008b\u008c", " \u008d\u008e\u0084\u0084\u0087\u0087\u0085\u0085zu\u0088\u0088\u0088\u0088\u0086\u0089\u0089\u008f\u0090\u008a \u0091\u0091\u0092", " \u008d\u0093\u0084\u0084\u0084\u0087\u0085\u0085zuu\u0094\u0088\u0088\u0086\u0089\u0095\u0095 \u0091\u0091\u0092", " \u0096\u008e\u0084\u0084\u0084\u0085z\u0097\u0098\u0099\u0099\u0094\u0095\u0095\u0095\u0095\u0095 \u009a ", " \u0096\u0096\u009b\u009c\u009d\u009d\u0085\u0097\u0098\u0094\u0099\u0095\u0095\u0095\u0095\u0095 \u009e\u009a\u009f", " \u0096\u009b\u00a0\u00a1\u00a2\u00a2\u0097\u0098\u0094\u0095\u0095\u0095\u0095\u0095 \u009a\u00a3\u00a3", " \u00a4\u00a0\u00a1\u00a5\u00a6\u00a6\u0097\u0094\u0095\u0095 \u0095\u0095 \u00a3\u00a3\u00a7\u00a7", " \u00a8\u00a4\u00a0\u00a5\u00a2\u00a6\u0097 \u00a9 \u00aa\u00a7\u00a7\u00a7\u00a7", " \u00a8\u00a4\u00a1\u00a5\u00a5\u00a6 \u00aa\u00a7\u00a7\u00a7\u00a7", " \u00ab\u00a1\u00a5\u00a5 \u00ac\u00ad\u00ae\u00ae\u00ae\u00ae \u00aa\u00a7\u00a7\u00a7\u00a7\u00a7", " \u00af\u00af\u00b0\u00b1\u00ad\u00ad \u00b2 \u00b3\u00b3\u00b4 \u00b5\u00b5\u00b5\u00b6\u00b6\u00b6", " \u00af\u00af\u00b7\u00b8\u00b8\u00b9 \u00b5\u00b5\u00b5\u00b5\u00b5\u00b5", " \u00b7\u00af\u00b8 \u00ba\u00ba\u00ba\u00ba\u00ba\u00ba", " \u00af \u00bb\u00bc\u00bd\u00be\u00bf\u00c0 \u00c1\u00c2\u00c2\u00c2\u00ba", " \u00c3\u00c4\u00c4\u00c5\u00c6\u00c6\u00c7\u00c8\u00c0 \u00c1\u00c1\u00c1\u00c1\u00c1", " \u00c5\u00c5\u00c9\u00c7\u00ca\u00ca\u00cb\u00c8\u00c8 \u00cc\u00cc\u00c1\u00c1", " \u00cd\u00ce\u00ce\u00ce\u00ce\u00ce\u00ce\u00cf\u00c8\u00d0\u00c0 "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/1.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/1.json
new file mode 100644
index 0000000..d3a0f0d
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/1.json
@@ -0,0 +1 @@
+{"keys": ["", "487", "474", "483", "489", "161", "492", "494", "171", "162", "459", "173", "164", "172", "502", "505", "175", "166", "511", "508", "510", "512", "519", "518", "177", "168", "176", "517", "522", "528", "538", "541", "542", "534", "549", "235", "237", "550", "239", "241", "240", "243", "242", "244", "245", "246", "247", "552", "248", "249", "553", "268", "555", "554"], "data": {"459": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "489": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "555": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "554": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "510": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "550": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "553": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "552": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "487": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "239": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "176": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "177": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "235": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "175": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "237": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "173": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "172": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "171": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "483": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "248": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "542": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "541": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES, OPEN WOODLAND, AND SHRUB OF CONTINENTAL CLIMATE"}, "492": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "508": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "505": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "502": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "522": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "245": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "244": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "247": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "246": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "241": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC CONSTANTLY HUMID FOREST-ALPINE MEADOWS"}, "240": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "243": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "242": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "164": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "166": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE"}, "268": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "249": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MEADOW"}, "161": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "162": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "549": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-MEADOW-STEPPE OF CONTINENTAL CLIMATE"}, "494": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "528": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "519": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "518": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "534": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "474": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "511": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT"}, "168": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "512": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "517": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "538": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE AND DESERT-STEPPE-DESERT OF CONTINENTAL CLIMATE"}}, "grid": [" !######$$$$% ", " &!######$$%%'()) ", " *+#####$%''''()), ", " -+###%%''''.(),, ", " -#//((''(.(((, ", " *-++/0(((((((1 ", " 232445/(((((1 ", " 336778((((91 ", " :36778(;;<< ", " :36788==< ", " 3>7788== ", " ??@AABBB ", " ??CAABB ", " D?EFAAB ", " G?@FFF ", " GHI@FF ", " JJIK ", " LMII ", " NOI ", " NJOI ", " JJOI ", " JM P ", " JQ ", " JRJ ", " ", " ", " ", " ", " ", " S ", " SS ", " SS ", " SSS ", " S ", " TSU ", " TUU ", " TTUUU V ", " TTTUUU VV", " TTTUUU V", " UU ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/2.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/0/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/0.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/0.json
new file mode 100644
index 0000000..0c2dede
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/0.json
@@ -0,0 +1 @@
+{"keys": ["", "276", "593", "277", "595", "594", "602", "604", "596", "603", "597", "278", "280", "606", "282", "283", "284", "288", "607", "608", "598", "600", "290", "291", "293", "301", "296", "300", "601", "605", "609", "298", "303", "599", "304", "305", "306", "302", "307", "308", "610", "311", "316", "314", "317", "320", "324", "328", "330", "327", "333", "334", "335", "338", "25", "26", "342", "340", "87", "341", "27", "344", "345", "343", "347", "68", "346", "348", "351", "71", "32", "28", "29", "30", "352", "356", "355", "74", "69", "72", "31", "358", "363", "360", "65", "362", "70", "34", "35", "369", "66", "366", "368", "374", "44", "52", "53", "54", "373", "375", "372", "371", "79", "380", "38", "40", "379", "56", "78", "46", "41", "384", "382", "57", "381", "80", "82", "47", "48", "393", "58", "387", "62", "389", "390", "391", "392", "388", "86", "83", "400", "405", "404", "49", "403", "60", "401", "402", "406", "397", "419", "61", "408", "418", "414", "411", "415", "410", "85", "417", "84", "416", "422", "425", "421", "423", "426", "434", "430", "428", "436", "136", "138", "435", "438", "439", "126", "146", "149", "437", "128", "140", "141", "150", "153", "155", "152", "443", "127", "154", "448", "451", "129", "131", "143", "452", "132", "130", "133", "455", "134", "142", "454", "157", "158", "450", "121", "457", "144", "159", "160", "453", "122", "123", "124", "470", "465", "471", "472", "135", "145", "469", "464", "466", "478", "475", "463"], "data": {"133": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "132": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "131": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "130": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "136": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "135": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "134": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "138": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "25": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "26": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "27": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "28": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "29": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "344": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SMALL-LEAFED AND CONIFEROUS WOODED STEPPES OF CONTINENTAL CLIMATE"}, "345": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "CONTINENTAL STEPPE-FOREST-TUNDRA AND STEPPE-FOREST-MEADOW"}, "346": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "347": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY AND CONTINENTAL CLIMATE"}, "340": {"dom_desc": "POLAR DOMAIN", "pro_desc": "MODERATE CONTINENTAL SMALL-LEAFED FOREST"}, "341": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "342": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "BROADLEAF-WOODED STEPPES AND MEADOW STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "343": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SMALL-LEAFED AND CONIFEROUS WOODED STEPPES OF CONTINENTAL CLIMATE"}, "280": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "283": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "282": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "348": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "284": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "408": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS AND DESERTS"}, "455": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "121": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "122": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "123": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "124": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "126": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT"}, "127": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "128": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "129": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "69": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-CREEPING TREES"}, "58": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "425": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-SHRUB-DESERT"}, "57": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "56": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "53": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "52": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "379": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC CONSTANTLY HUMID FOREST-ALPINE MEADOWS"}, "415": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "416": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "417": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "410": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "411": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-MEADOW OF EASTERN OCEANIC (MONSOON CLIMATE)"}, "298": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC TAYGA"}, "54": {"dom_desc": "", "pro_desc": ""}, "296": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "373": {"dom_desc": "DRY DOMAIN", "pro_desc": "EXTREME CONTINENTAL DESERT-STEPPE"}, "372": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "375": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "293": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL LIGHT DECIDUOUS NEEDLELEAF OPEN FOREST"}, "290": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-CREEPING TREES-TUNDRA"}, "291": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "593": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "443": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "595": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERT"}, "594": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "597": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "596": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "599": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "598": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "311": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "317": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "316": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "314": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL AND EXTREME CONTINENTAL LIGHT DECIDUOUS TAYGA"}, "393": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-MEADOW-STEPPE OF CONTINENTAL CLIMATE"}, "392": {"dom_desc": "DRY DOMAIN", "pro_desc": "FOREST-MEADOW-STEPPE OF CONTINENTAL CLIMATE"}, "391": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE AND DESERT-STEPPE-DESERT OF CONTINENTAL CLIMATE"}, "390": {"dom_desc": "DRY DOMAIN", "pro_desc": "EXTREME CONTINENTAL DESERT"}, "397": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "276": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "277": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "278": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "83": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "80": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "86": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "87": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "84": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "85": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "414": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "428": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "368": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID EASTERN OCEANIC BROADLEAF FORESTS"}, "369": {"dom_desc": "", "pro_desc": ""}, "366": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "423": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "422": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SEMI-DESERTS AND DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "362": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "363": {"dom_desc": "DRY DOMAIN", "pro_desc": "CONTINENTAL OPEN WOODLAND-STEPPE"}, "360": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERTS OF EXTREME CONTINENTAL CLIMATE"}, "426": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "308": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "448": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "300": {"dom_desc": "POLAR DOMAIN", "pro_desc": "TUNDRA-POLAR DESERT"}, "301": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "302": {"dom_desc": "POLAR DOMAIN", "pro_desc": "MODERATE CONTINENTAL DARK EVERGREEN NEEDLELEAF TAYGA"}, "303": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL DARK EVERGREEN NEEDLELEAF OPEN FOREST"}, "304": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL LIGHT DECIDUOUS NEEDLELEAF OPEN FOREST"}, "305": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "306": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL BUSH-AND-SHRUB TUNDRA"}, "307": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "380": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "371": {"dom_desc": "DRY DOMAIN", "pro_desc": "STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "382": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES, OPEN WOODLAND, AND SHRUB OF CONTINENTAL CLIMATE"}, "384": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "406": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "387": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "388": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "389": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "607": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "38": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "381": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "32": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "31": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "BROADLEAF-WOODED STEPPES AND MEADOW STEPPES OF MODERATELY CONTINENTAL CLIMATE"}, "30": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "35": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "34": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "438": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "439": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "436": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE"}, "437": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "434": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "435": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "430": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "338": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "PERMANENTLY HUMID WESTERN OCEANIC BROADLEAF FORESTS"}, "604": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "335": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY AND CONTINENTAL CLIMATE"}, "334": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-CREEPING TREES-TUNDRA OF EXTREME CONTINENTAL CLIMATE"}, "452": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "453": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "454": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "330": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "333": {"dom_desc": "POLAR DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY AND CONTINENTAL CLIMATE"}, "457": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "60": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC CONSTANTLY HUMID FOREST-ALPINE MEADOWS"}, "61": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "62": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-STEPPE AND DESERT-STEPPE-DESERT OF CONTINENTAL CLIMATE"}, "606": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "65": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "CONTINENTAL STEPPE-FOREST-TUNDRA AND STEPPE-FOREST-MEADOW"}, "66": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "CONTINENTAL STEPPE-FOREST-TUNDRA AND STEPPE-FOREST-MEADOW"}, "68": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "601": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL MOSS-AND-LICHEN (TYPICAL) TUNDRA"}, "600": {"dom_desc": "POLAR DOMAIN", "pro_desc": "ARCTIC TUNDRAS"}, "603": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "288": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL BUSH-AND-SHRUB TUNDRA"}, "405": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "404": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "403": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "402": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "469": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "401": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS AND DESERTS"}, "465": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "464": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "400": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SHRUB-FOREST-MEADOW OF MEDITERRANEAN CLIMATE"}, "463": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "160": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "419": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "605": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-TUNDRA"}, "150": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "153": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "152": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "155": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "154": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "157": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "602": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "159": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "158": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "609": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL BUSH-AND-SHRUB TUNDRA"}, "608": {"dom_desc": "POLAR DOMAIN", "pro_desc": "POLAR DESERTS"}, "82": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "EASTERN OCEANIC MIXED MONSOON FOREST"}, "466": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "48": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "49": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "46": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "47": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "44": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "470": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "40": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS OF WESTERN OCEANIC (MEDITERRANEAN) CLIMATE"}, "41": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "418": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "320": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OPEN WOODLAND-CREEPING TREES-TUNDRA"}, "327": {"dom_desc": "POLAR DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "324": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "328": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "374": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "146": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "144": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "145": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "142": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "143": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "140": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "141": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "610": {"dom_desc": "POLAR DOMAIN", "pro_desc": "CONTINENTAL LIGHT DECIDUOUS NEEDLELEAF OPEN FOREST"}, "475": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "450": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "149": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "74": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATELY HUMID BROADLEAF FOREST IN MODERATELY CONTINENTAL CLIMATE"}, "72": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-CREEPING TREES"}, "71": {"dom_desc": "POLAR DOMAIN", "pro_desc": "EASTERN OCEANIC TAYGA"}, "70": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "MODERATE CONTINENTAL MIXED FORESTS"}, "79": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC FOREST-TUNDRA"}, "78": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "EASTERN OCEANIC MIXED MONSOON FOREST"}, "451": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "472": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "356": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF CONTINENTAL CLIMATE"}, "355": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES OF EXTREME CONTINENTAL CLIMATE"}, "471": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "352": {"dom_desc": "DRY DOMAIN", "pro_desc": "SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "351": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-TUNDRA OF MODERATELY CONTINENTAL AND CONTINENTAL CLIMATE"}, "421": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "478": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "358": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERTS OF CONTINENTAL CLIMATE"}}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ! ", " #### $!! ", " %%% && $!''( ", " ))%& ****(( ", " )))+ (( ", " )) + ( ", " )) + ,, ", " - ,,, ", " --- ,,,,,, .../// ", " --- ,,,,,00,, ...// ", " 1- ,,,,,00000 . ", " 11 ,,,0000200 333 ", " 11 ,,,,00222222223333 4 ", " 55 66 ,,,,002222222222733 888 ", " 55 66,,,,222222999999229738888888 ", " :;; < 6======22229999>>9999977?????88888 8 ", " @:::AAB < =======22999999>>>99997777?C?DEEE88888 ", " :FFFFABB G GGGG<=======9HHHHH9>>>9999777II77CDEEEEE777 ", " @:FFFFFFBB GGJJJJG<======9HHHHH99999999777II7CDDCCCCCC777 ", " :FFFFFFFAB GGFJJJJ<======KHHHHH9999999LL7III7CCDCCCCCC777 ", " @:FF FFFF FFFFFFFFK=K=9999KHHHHH999999LLL7II7777CCC7777777 ", " :FFFFFFFFFFFFFFFMMKKKKKKKKKLHHHH9999LLLLLL7777777CC777777 ", " @::FF FFFFFFFFFFFFMMKKKKKKKKKLLLLLLLLLLLLLLLLLL77777777777NNNN", " @::FF FOOFFFFFFFFFMMKKKKKKKKKLLLLLLLLLLLLLLLLLL7777777777NNN ", " @:FFP OQQQFFFFFFFFMMKKKKKKKKKLLLLLLLLLLLLLLLLL77777777 RRN ", " @:PPP QQQQQFFFFFFFMMKKKKKKKKKSSLLLLLLLLLLLLLLTTUU 7 R ", " VWP QQQQQQQQFFFFQMKKKKKKKKKSSLLLLLLTTTTTTTTTUU RR ", " VXW QQQQQQQQQQQYQQZZZZZZKKZZKSLLLLLTTTTTTTTTUU [RR ", "] VVV^QQQQQQQYYYYYYY________``ZabLLLTTTTTTTTTUUcc [RR ", "] VVVV^^QQQQQYYddddddeeeeeee_```bbbaLTTTTTfffUUUccg [R ", "]VVhijik^YYYYdddlllllelleeeee`bmmbbbaTTTnnnffoUUfpqq R ", "VVVhirrrrYYdddddlsssslllleeee`ttubbnnvvnnnnwwfffxpqq ", "VVViyzrrrdddddddsssss{ssllllllttun|nnnnnnnn}}ww~pp q ", "V\u007fVy\u007f\u0080\u0080rddd \u0081 \u0082ds\u0083sss{{sssss\u0084\u0084\u0085\u0086\u0086tnnnnnnnnn\u0087}~~~p \u0088\u0088 ", "\u0089\u007f\u007f\u007f\u007f\u007f\u0080\u008a\u008b\u008b \u008c \u008c\u008d\u0083\u0083sssss\u0084s\u0084\u0084\u0084\u0085\u0085\u0085\u0085\u0085\u0085\u0086\u0086\u0086\u0086nnn\u0087\u0087~~~p \u008e\u0088 ", "\u0089 \u008f \u007f\u007f\u0080\u0090\u0090\u0091\u0092\u0092\u0092\u008c\u008d\u0093\u0093\u0093sssss\u0084\u0084\u0084\u0084\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0085\u0086nn\u0094\u0094~~\u0095~ \u0096 ", "\u007f \u008f \u0097\u007f\u0090\u0098\u0091\u0091\u0092\u0092\u0092\u008d\u008d\u0099\u0099\u0083\u009a\u009b\u009bss\u009c\u009d\u009e\u0085\u0085\u0085\u0085\u009f\u009f\u009f\u00a0\u00a0\u0085\u0085\u0086n\u0094\u00a1\u00a1~~~ \u00a2\u00a3 ", "\u00a4\u00a5\u00a5\u00a6\u00a7\u0097 \u0098 \u0091\u0091\u0091\u00a8\u00a8\u00a8\u0099\u0099\u00a9\u00aa\u00aa\u009bss\u009d\u009f\u009f\u009e\u009f\u009f\u009f\u009e\u009f\u00ab\u00ab\u00a0\u00a0\u0086n\u0094\u0094\u00a1\u00ac\u00ad ~~ \u00a2\u00a2\u00a3 ", "\u00a5\u00a6\u00ae\u00ae \u00af\u00a8\u00a8\u00a8\u00a8\u0099\u00aa\u00aa\u00aa\u009b\u00b0\u009d\u009d\u00b1\u00b2\u009f\u009e\u009e\u009e\u009e\u009f\u00ab\u00ab\u00ab\u00a0\u00b3\u00b3\u00b3\u00b3\u00b4\u00b5 \u00b6 \u00b7\u00b8 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00b9\u00b9\u00b9\u00b9\u00b9\u00b9\u00b9\u00b9\u00a8\u00ba\u00ba\u00ba\u00ba\u00bb\u00bc\u00bc\u00aa\u00b0\u00b0\u00b0\u00bd\u00b1\u00b2\u009f\u009f\u009e\u009e\u009f\u00ab\u00ab\u00ab\u00b3\u00b4\u00b4\u00b3\u00b4\u00b4\u00b4\u00b4 \u00b7 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00bb\u00bb\u00bb\u00bc\u00b0\u00bb\u00be\u00bd\u00bd\u00bd\u00bf\u00c0\u009f\u009f\u009f\u00c1\u00c0\u00ab\u00b3\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ba\u00ba\u00ba\u00ba\u00ba \u00bb\u00c2\u00bb\u00bb\u00bb\u00bd\u00bd\u00bd\u00b1\u00c3\u00bf\u00bf\u00c0\u00bf\u00bf\u00bf\u00c0\u00b3\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4\u00b4 ", "\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae \u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00c2\u00c2 \u00bd\u00bd\u00b1\u00b1\u00c3\u00c4\u00bf\u00bf\u00bf\u00bf\u00c0\u00b3\u00b3\u00b3\u00b4\u00b4\u00b4\u00c5\u00c5\u00c6 ", "\u00ae\u00c7\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00ae\u00c7\u00c8\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba \u00bd\u00b1\u00c3\u00c3\u00c4\u00c4\u00bf\u00bf\u00bf\u00c0\u00c9\u00c9\u00ca\u00c5\u00c5\u00c5\u00c5 \u00cb ", "\u00c7\u00c7\u00c7\u00c7\u00ae\u00ae\u00ae\u00c7\u00c7\u00ae\u00ae\u00c7\u00c7\u00c8\u00cc\u00ba\u00ba\u00ba\u00ba\u00ba\u00ba \u00cd\u00c3\u00c3\u00ce\u00ce \u00bf\u00cf\u00c9\u00d0\u00d1 \u00d2 ", "\u00d3\u00d3\u00c7\u00d3\u00d3\u00c7\u00c7\u00d3\u00d3\u00c7\u00c7\u00c7\u00d3\u00d3\u00d4\u00cc\u00ba\u00ba\u00ba \u00cd\u00c3\u00ce\u00ce \u00bf\u00d5\u00d0\u00d0\u00c5 \u00d6 ", "\u00d7\u00d7\u00d7\u00d7\u00d3\u00d3\u00d7\u00d7\u00d7\u00d3\u00d3\u00d3\u00d3\u00d3\u00d8\u00d9\u00cc\u00cc \u00da\u00da\u00ce \u00d5\u00d5\u00d0\u00d1\u00c5 \u00db\u00d6 ", "\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d7\u00d3\u00d3\u00dc\u00dc\u00dd\u00de\u00df\u00e0 \u00e1\u00da \u00d5 \u00d5\u00d1 \u00e2\u00e3\u00e4 ", "\u00e5\u00e5\u00e5\u00e5\u00e6\u00e7\u00e7\u00e7\u00e7\u00e7\u00e7\u00d7\u00d7\u00dc\u00dc\u00df\u00df\u00e0\u00e0 \u00e1\u00e8\u00e8 \u00d5 \u00d5 \u00e9\u00ea ", "\u00eb\u00ec\u00ed\u00ee\u00ef\u00ef\u00ef\u00ef\u00ef\u00f0\u00f0\u00f1\u00f2\u00d3\u00f3\u00df\u00df\u00df \u00f4 \u00f5 \u00f6 \u00f7\u00f7 ", " \u00ef\u00f8\u00ef\u00f9\u00f9\u00f9\u00f9\u00f9\u00f1\u00f2\u00df\u00df\u00df\u00df \u00f5\u00f5\u00f6 \u00fa\u00fa\u00f7\u00fa "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/1.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/1.json
new file mode 100644
index 0000000..5457be3
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/1.json
@@ -0,0 +1 @@
+{"keys": ["", "486", "478", "475", "471", "493", "491", "484", "455", "468", "469", "463", "466", "481", "490", "201", "207", "208", "209", "488", "495", "179", "497", "496", "185", "202", "498", "499", "500", "234", "181", "186", "203", "501", "215", "214", "213", "504", "188", "218", "217", "216", "183", "195", "189", "219", "220", "221", "506", "507", "196", "194", "509", "228", "513", "199", "514", "520", "515", "516", "182", "521", "198", "523", "525", "524", "530", "527", "537", "539", "531", "545", "544", "543", "536", "548", "546", "547", "255", "250", "254", "262", "258", "257", "259", "263", "261", "265", "264", "266", "267", "554"], "data": {"216": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "217": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "214": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "215": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "213": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "218": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}, "219": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "498": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "499": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "495": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "496": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "497": {"dom_desc": "", "pro_desc": ""}, "490": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID FORESTS WITH SHORT DRY SEASON"}, "491": {"dom_desc": "", "pro_desc": ""}, "493": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "543": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "546": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "547": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "544": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "545": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "548": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES AND SHRUB OF MODERATE CONTINENTAL CLIMATE"}, "263": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "SUBTROPICAL PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "262": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "261": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "267": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC CONIFEROUS AND MIXED FORESTS"}, "266": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "TEMPERATE PRAIRIES (HUMID STEPPES AND WOODED STEPPES) OF EASTERN PARTS OF CONTINENTS"}, "265": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "264": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-ALPINE MEADOWS"}, "537": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "536": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "531": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "530": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "539": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS AND DESERTS OF CONTINENTAL CLIMATE"}, "199": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "198": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "195": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "194": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE AND FOREST-MEADOW OF SEASONALLY HUMID TYPE"}, "196": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "524": {"dom_desc": "DRY DOMAIN", "pro_desc": "OPEN WOODLAND-STEPPE OF CONTINENTAL CLIMATE"}, "525": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "527": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "520": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "521": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW OF CONSTANTLY HUMID EASTERN OCEANIC TYPE"}, "523": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "513": {"dom_desc": "DRY DOMAIN", "pro_desc": "WESTERN OCEANIC SEMI-DESERTS AND DESERTS WITH HIGH RELATIVE HUMIDITY"}, "515": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL DESERTS OF CONTINENTAL CLIMATE"}, "514": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "516": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "455": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "258": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "FOREST-MEADOW OF EASTERN OCEANIC (MONSOON CLIMATE)"}, "259": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OPEN WOODLAND, SAVANNAS, AND SHRUB OF EASTERN PARTS OF CONTINENTS"}, "179": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "250": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "WESTERN OCEANIC MIXED SCLEROPHYLL FORESTS AND SHRUB"}, "257": {"dom_desc": "HUMID TEMPERATE DOMAIN", "pro_desc": "OCEANIC MIXED CONSTANTLY HUMID FORESTS"}, "254": {"dom_desc": "DRY DOMAIN", "pro_desc": "SHRUB AND SEMI-SHRUB SEMI-DESERTS OF CONTINENTAL CLIMATE"}, "255": {"dom_desc": "DRY DOMAIN", "pro_desc": "DRY STEPPES, OPEN WOODLAND, AND SHRUB OF CONTINENTAL CLIMATE"}, "182": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "183": {"dom_desc": "DRY DOMAIN", "pro_desc": "INNER CONTINENTAL SHRUB SEMI-DESERT"}, "181": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "186": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "185": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "506": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID MIXED (DECIDUOUS AND EVERGREEN) FORESTS"}, "507": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SAVANNAS, OPEN WOODLAND AND SHRUB WITH SEASONAL MOISTURE SUPPLY"}, "188": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-MEADOW, SEASONALLY HUMID"}, "189": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "500": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "EASTERN OCEANIC CONSTANTLY HUMID FORESTS"}, "501": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "469": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "468": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "509": {"dom_desc": "DRY DOMAIN", "pro_desc": "DESERT-LIKE SAVANNAS, OPEN WOODLAND, AND SHRUB"}, "463": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "228": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "DRY SAVANNAS AND OPEN WOODLAND"}, "504": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "221": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "220": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "HUMID TALL-GRASS SAVANNAS AND SAVANNA FORESTS"}, "554": {"dom_desc": "POLAR DOMAIN", "pro_desc": ""}, "234": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW OF CONSTANTLY HUMID OCEANIC (AND WINDWARD-SLOPE) TYPE"}, "466": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "201": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "203": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "202": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "207": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "209": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "208": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "488": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "486": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "SEASONALLY HUMID, PREDOMINANTLY DECIDUOUS FORESTS"}, "484": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MODERATELY HUMID GRASSY SAVANNAS"}, "481": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-PARAMO AND FOREST-MEADOW"}, "471": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "FOREST-STEPPE, INNER CONTINENTAL AND LEEWARD SLOPES"}, "475": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "CONSTANTLY HUMID EVERGREEN FORESTS"}, "478": {"dom_desc": "HUMID TROPICAL DOMAIN", "pro_desc": "MIXED FORESTS WITH SHORT DRY SEASON"}}, "grid": [" !##$$$$%&'()) *++ ,-- .. / ", " !!####%&'(( ++0,,,.. 1234444 5 ", " 6!!!!!78889 +: . 4;;;<=> ", " ?!!!!!788@ AA B CC DDE;; ", " ?!!!!FF%88 G HH IJ ", " K!!!!!!!%88 LM NNNON PQ ", " KRRRRRRR%8@ ST UVVUUUUQ ", " WRRRRRRRR SX YZ[[[]]]UUQ ", " W^^^RRRR SX ZZZ[[]]]]]UUQ ", " WK^^RR_R ` ZZZZ[]]][]UUab ", " WKKccd eeZZ[[]][]]Uab ", " fggcdh ieee[[[jjklmno ", " ggdh iieeeepjjklmbb ", " qh ii rpmmmb s ", " matb ss", " uvt sw", " xx yss", " x z{ ", " || ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " }}} ", " }}}}}}}}} ", " } }}}}}}}}}}}} ", " } }}}}}}}} }} ", " }}}}}}}}}}}} }} ", "}}}}}}}}} }} ", "}}}}}} ", " }}} ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/2.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/1/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/0.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/0.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/0.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/1.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/1.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/1.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/2.json b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/bio_utfgrid/1/2/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/demo-1.1.json b/misc/openlayers/tests/data/utfgrid/demo-1.1.json
new file mode 100644
index 0000000..0848e26
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/demo-1.1.json
@@ -0,0 +1 @@
+{"grid":[" !#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~€Â‚ƒ„…†‡ˆ‰Š‹ŒÂŽ‘’“”•–—˜™š›œÂžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃÂÃÄÅÆÇÈÉÊËÌÃÃŽÃÃÑÒÓÔÕÖ×ØÙÚÛÜÃÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿĀÄĂ㥹ĆćĈĉĊċČÄÄŽÄÄđĒēĔĕĖėĘęĚěĜÄĞğĠġ","ĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀÅłŃńŅņŇňʼnŊŋŌÅÅŽÅÅőŒœŔŕŖŗŘřŚśŜÅŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƀÆƂƃƄƅƆƇƈƉƊƋƌÆÆŽÆÆƑƒƓƔƕƖƗƘƙƚƛƜÆƞƟƠơƢƣƤƥƦƧƨƩƪƫƬƭƮƯưƱƲƳƴƵƶƷƸƹƺƻƼƽƾƿǀÇǂǃDŽDždžLJLjljNJNjnjÇÇŽÇÇǑǒǓǔǕǖǗǘǙǚǛǜÇǞǟǠǡǢǣǤǥǦǧǨǩǪǫǬǭǮǯǰDZDzdzǴǵǶǷǸǹǺǻǼǽǾǿȀÈȂȃȄȅȆȇȈȉȊȋȌÈÈŽÈÈȑȒȓȔȕȖȗȘșȚțȜÈȞȟȠȡ","ȢȣȤȥȦȧȨȩȪȫȬȭȮȯȰȱȲȳȴȵȶȷȸȹȺȻȼȽȾȿɀÉɂɃɄɅɆɇɈɉɊɋɌÉÉŽÉÉɑɒɓɔɕɖɗɘəɚɛɜÉɞɟɠɡɢɣɤɥɦɧɨɩɪɫɬɭɮɯɰɱɲɳɴɵɶɷɸɹɺɻɼɽɾɿʀÊʂʃʄʅʆʇʈʉʊʋʌÊÊŽÊÊʑʒʓʔʕʖʗʘʙʚʛʜÊʞʟʠʡʢʣʤʥʦʧʨʩʪʫʬʭʮʯʰʱʲʳʴʵʶʷʸʹʺʻʼʽʾʿˀË˂˃˄˅ˆˇˈˉˊˋˌËËŽËËˑ˒˓˔˕˖˗˘˙˚˛˜Ë˞˟ˠˡˢˣˤ˥˦˧˨˩˪˫ˬ˭ˮ˯˰˱˲˳˴˵˶˷˸˹˺˻˼˽˾˿̀Ì̂̃̄̅̆̇̈̉̊̋̌ÌÌŽÌÌ̛̖̗̘̙̜̑̒̓̔̕̚Ì̡̞̟̠","̴̵̶̷̸̢̧̨̣̤̥̦̩̪̫̬̭̮̯̰̱̲̳̹̺̻̼̽̾̿̀Í͇͈͉͂̓̈́͆͊͋͌ͅÍÍŽÍÍ͓͔͕͖͙͚͑͒͗͛͘͜Íͣͤͥͦͧͨͩͪͫͬͭͮͯ͟͢͞͠͡ͰͱͲͳʹ͵Ͷͷ͸͹ͺͻͼͽ;Ϳ΀Î΂΃΄΅Ά·ΈΉΊ΋ΌÎÎŽÎÎΑΒΓΔΕΖΗΘΙΚΛΜÎΞΟΠΡ΢ΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπÏςστυφχψωϊϋόÏÏŽÏÏϑϒϓϔϕϖϗϘϙϚϛϜÏϞϟϠϡϢϣϤϥϦϧϨϩϪϫϬϭϮϯϰϱϲϳϴϵ϶ϷϸϹϺϻϼϽϾϿЀÐЂЃЄЅІЇЈЉЊЋЌÐÐŽÐÐБВГДЕЖЗИЙКЛМÐОПРС","ТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрÑтуфхцчшщъыьÑÑŽÑÑёђѓєѕіїјљњћќÑўџѠѡѢѣѤѥѦѧѨѩѪѫѬѭѮѯѰѱѲѳѴѵѶѷѸѹѺѻѼѽѾѿҀÒÒ‚ÒƒÒ„Ò…Ò†Ò‡ÒˆÒ‰ÒŠÒ‹ÒŒÒÒŽÒÒÒ‘Ò’Ò“Ò”Ò•Ò–Ò—Ò˜Ò™ÒšÒ›ÒœÒÒžÒŸÒ Ò¡Ò¢Ò£Ò¤Ò¥Ò¦Ò§Ò¨Ò©ÒªÒ«Ò¬Ò­Ò®Ò¯Ò°Ò±Ò²Ò³Ò´ÒµÒ¶Ò·Ò¸Ò¹ÒºÒ»Ò¼Ò½Ò¾Ò¿Ó€ÓÓ‚ÓƒÓ„Ó…Ó†Ó‡ÓˆÓ‰ÓŠÓ‹ÓŒÓÓŽÓÓÓ‘Ó’Ó“Ó”Ó•Ó–Ó—Ó˜Ó™ÓšÓ›ÓœÓÓžÓŸÓ Ó¡Ó¢Ó£Ó¤Ó¥Ó¦Ó§Ó¨Ó©ÓªÓ«Ó¬Ó­Ó®Ó¯Ó°Ó±Ó²Ó³Ó´ÓµÓ¶Ó·Ó¸Ó¹ÓºÓ»Ó¼Ó½Ó¾Ó¿Ô€ÔÔ‚ÔƒÔ„Ô…Ô†Ô‡ÔˆÔ‰ÔŠÔ‹ÔŒÔÔŽÔÔÔ‘Ô’Ô“Ô”Ô•Ô–Ô—Ô˜Ô™ÔšÔ›ÔœÔÔžÔŸÔ Ô¡","Ô¢Ô£Ô¤Ô¥Ô¦Ô§Ô¨Ô©ÔªÔ«Ô¬Ô­Ô®Ô¯Ô°Ô±Ô²Ô³Ô´ÔµÔ¶Ô·Ô¸Ô¹ÔºÔ»Ô¼Ô½Ô¾Ô¿Õ€ÕÕ‚ÕƒÕ„Õ…Õ†Õ‡ÕˆÕ‰ÕŠÕ‹ÕŒÕÕŽÕÕÕ‘Õ’Õ“Õ”Õ•Õ–Õ—Õ˜Õ™ÕšÕ›ÕœÕÕžÕŸÕ Õ¡Õ¢Õ£Õ¤Õ¥Õ¦Õ§Õ¨Õ©ÕªÕ«Õ¬Õ­Õ®Õ¯Õ°Õ±Õ²Õ³Õ´ÕµÕ¶Õ·Õ¸Õ¹ÕºÕ»Õ¼Õ½Õ¾Õ¿Ö€ÖÖ‚ÖƒÖ„Ö…Ö†Ö‡ÖˆÖ‰ÖŠÖ‹ÖŒÖÖŽÖÖÖ‘Ö’Ö“Ö”Ö•Ö–Ö—Ö˜Ö™ÖšÖ›ÖœÖÖžÖŸÖ Ö¡Ö¢Ö£Ö¤Ö¥Ö¦Ö§Ö¨Ö©ÖªÖ«Ö¬Ö­Ö®Ö¯Ö°Ö±Ö²Ö³Ö´ÖµÖ¶Ö·Ö¸Ö¹ÖºÖ»Ö¼Ö½Ö¾Ö¿×€×ׂ׃ׅׄ׆ׇ׈׉׊׋׌×׎××בגדהוזחטיךכל×מןנסעףפץצקרשת׫׬׭׮ׯװױײ׳״׵׶׷׸׹׺׻׼׽׾׿؀Ø؂؃؄؅؆؇؈؉؊؋،ØØŽØØؘؙؚؑؒؓؔؕؖؗ؛؜Ø؞؟ؠء","آأؤإئابةتثجحخدذرزسشصضطظعغػؼؽؾؿـÙقكلمنهوىيًٌÙÙŽÙÙّْٕٖٜٓٔٗ٘ٙٚٛÙٟٞ٠١٢٣٤٥٦٧٨٩٪٫٬٭ٮٯٰٱٲٳٴٵٶٷٸٹٺٻټٽپٿڀÚÚ‚ÚƒÚ„Ú…Ú†Ú‡ÚˆÚ‰ÚŠÚ‹ÚŒÚÚŽÚÚÚ‘Ú’Ú“Ú”Ú•Ú–Ú—Ú˜Ú™ÚšÚ›ÚœÚÚžÚŸÚ Ú¡Ú¢Ú£Ú¤Ú¥Ú¦Ú§Ú¨Ú©ÚªÚ«Ú¬Ú­Ú®Ú¯Ú°Ú±Ú²Ú³Ú´ÚµÚ¶Ú·Ú¸Ú¹ÚºÚ»Ú¼Ú½Ú¾Ú¿Û€ÛÛ‚ÛƒÛ„Û…Û†Û‡ÛˆÛ‰ÛŠÛ‹ÛŒÛÛŽÛÛÛ‘Û’Û“Û”Û•Û–Û—Û˜Û™ÛšÛ›ÛœÛÛžÛŸÛ Û¡Û¢Û£Û¤Û¥Û¦Û§Û¨Û©ÛªÛ«Û¬Û­Û®Û¯Û°Û±Û²Û³Û´ÛµÛ¶Û·Û¸Û¹ÛºÛ»Û¼Û½Û¾Û¿Ü€Ü܂܃܄܅܆܇܈܉܊܋܌ÜÜŽÜÜܑܒܓܔܕܖܗܘܙܚܛܜÜܞܟܠܡ","ܢܣܤܥܦܧܨܩܪܫܬܭܮܯܱܴܷܸܹܻܼܾܰܲܳܵܶܺܽܿ݀Ý݂݄݆݈݃݅݇݉݊݋݌ÝÝŽÝÝݑݒݓݔݕݖݗݘݙݚݛݜÝݞݟݠݡݢݣݤݥݦݧݨݩݪݫݬݭݮݯݰݱݲݳݴݵݶݷݸݹݺݻݼݽݾݿހÞÞ‚ÞƒÞ„Þ…Þ†Þ‡ÞˆÞ‰ÞŠÞ‹ÞŒÞÞŽÞÞÞ‘Þ’Þ“Þ”Þ•Þ–Þ—Þ˜Þ™ÞšÞ›ÞœÞޞޟޠޡޢޣޤޥަާިީުޫެޭޮޯްޱ޲޳޴޵޶޷޸޹޺޻޼޽޾޿߀ß߂߃߄߅߆߇߈߉ߊߋߌßߎßßߑߒߓߔߕߖߗߘߙߚߛߜßߞߟߠߡߢߣߤߥߦߧߨߩߪ߲߫߬߭߮߯߰߱߳ߴߵ߶߷߸߹ߺ߻߼߽߾߿ࠀà à ‚ࠃࠄࠅࠆࠇࠈࠉࠊࠋࠌà à Žà à à ‘ࠒࠓࠔࠕࠖࠗ࠘࠙ࠚࠛࠜà à žà Ÿà  à ¡","ࠢࠣࠤࠥࠦࠧࠨࠩࠪࠫࠬ࠭࠮࠯࠰࠱࠲࠳࠴࠵࠶࠷࠸࠹࠺࠻࠼࠽࠾࠿ࡀà¡à¡‚ࡃࡄࡅࡆࡇࡈࡉࡊࡋࡌà¡à¡Žà¡à¡à¡‘ࡒࡓࡔࡕࡖࡗࡘ࡙࡚࡛࡜à¡à¡žà¡Ÿà¡ à¡¡à¡¢à¡£à¡¤à¡¥à¡¦à¡§à¡¨à¡©à¡ªà¡«à¡¬à¡­à¡®à¡¯à¡°à¡±à¡²à¡³à¡´à¡µà¡¶à¡·à¡¸à¡¹à¡ºà¡»à¡¼à¡½à¡¾à¡¿à¢€à¢à¢‚ࢃࢄࢅࢆࢇ࢈ࢉࢊࢋࢌà¢à¢Žà¢à¢à¢‘࢒࢓࢔࢕࢖࢙࢚࢛ࢗ࢘࢜à¢à¢žà¢Ÿà¢ à¢¡à¢¢à¢£à¢¤à¢¥à¢¦à¢§à¢¨à¢©à¢ªà¢«à¢¬à¢­à¢®à¢¯à¢°à¢±à¢²à¢³à¢´à¢µà¢¶à¢·à¢¸à¢¹à¢ºà¢»à¢¼à¢½à¢¾à¢¿à£€à£à£‚ࣃࣄࣅࣆࣇࣈࣉ࣊࣋࣌à£à£Žà£à£à£‘࣒࣓ࣔࣕࣖࣗࣘࣙࣚࣛࣜà£à£žà£Ÿà£ à£¡à£¢à££à£¤à£¥à£¦à£§à£¨à£©à£ªà£«à£¬à£­à£®à£¯à£°à£±à£²à£³à£´à£µà£¶à£·à£¸à£¹à£ºà£»à£¼à£½à£¾à£¿à¤€à¤à¤‚ःऄअआइईउऊऋऌà¤à¤Žà¤à¤à¤‘ऒओऔकखगघङचछजà¤à¤žà¤Ÿà¤ à¤¡","ढणतथदधनऩपफबभमयरऱलळऴवशषसहऺऻ़ऽािीà¥à¥‚ृॄॅॆेैॉॊोौà¥à¥Žà¥à¥à¥‘॒॓॔ॕॖॗक़ख़ग़ज़ड़à¥à¥žà¥Ÿà¥ à¥¡à¥¢à¥£à¥¤à¥¥à¥¦à¥§à¥¨à¥©à¥ªà¥«à¥¬à¥­à¥®à¥¯à¥°à¥±à¥²à¥³à¥´à¥µà¥¶à¥·à¥¸à¥¹à¥ºà¥»à¥¼à¥½à¥¾à¥¿à¦€à¦à¦‚ঃ঄অআইঈউঊঋঌà¦à¦Žà¦à¦à¦‘঒ওঔকখগঘঙচছজà¦à¦žà¦Ÿà¦ à¦¡à¦¢à¦£à¦¤à¦¥à¦¦à¦§à¦¨à¦©à¦ªà¦«à¦¬à¦­à¦®à¦¯à¦°à¦±à¦²à¦³à¦´à¦µà¦¶à¦·à¦¸à¦¹à¦ºà¦»à¦¼à¦½à¦¾à¦¿à§€à§à§‚ৃৄ৅৆েৈ৉৊োৌà§à§Žà§à§à§‘৒৓৔৕৖ৗ৘৙৚৛ড়à§à§žà§Ÿà§ à§¡à§¢à§£à§¤à§¥à§¦à§§à§¨à§©à§ªà§«à§¬à§­à§®à§¯à§°à§±à§²à§³à§´à§µà§¶à§·à§¸à§¹à§ºà§»à§¼à§½à§¾à§¿à¨€à¨à¨‚ਃ਄ਅਆਇਈਉਊ਋਌à¨à¨Žà¨à¨à¨‘਒ਓਔਕਖਗਘਙਚਛਜà¨à¨žà¨Ÿà¨ à¨¡","ਢਣਤਥਦਧਨ਩ਪਫਬਭਮਯਰ਱ਲਲ਼਴ਵਸ਼਷ਸਹ਺਻਼਽ਾਿੀà©à©‚੃੄੅੆ੇੈ੉੊ੋੌà©à©Žà©à©à©‘੒੓੔੕੖੗੘ਖ਼ਗ਼ਜ਼ੜà©à©žà©Ÿà© à©¡à©¢à©£à©¤à©¥à©¦à©§à©¨à©©à©ªà©«à©¬à©­à©®à©¯à©°à©±à©²à©³à©´à©µà©¶à©·à©¸à©¹à©ºà©»à©¼à©½à©¾à©¿àª€àªàª‚ઃ઄અઆઇઈઉઊઋઌàªàªŽàªàªàª‘઒ઓઔકખગઘઙચછજàªàªžàªŸàª àª¡àª¢àª£àª¤àª¥àª¦àª§àª¨àª©àªªàª«àª¬àª­àª®àª¯àª°àª±àª²àª³àª´àªµàª¶àª·àª¸àª¹àªºàª»àª¼àª½àª¾àª¿à«€à«à«‚ૃૄૅ૆ેૈૉ૊ોૌà«à«Žà«à«à«‘૒૓૔૕૖૗૘૙૚૛૜à«à«žà«Ÿà« à«¡à«¢à«£à«¤à«¥à«¦à«§à«¨à«©à«ªà««à«¬à«­à«®à«¯à«°à«±à«²à«³à«´à«µà«¶à«·à«¸à«¹à«ºà«»à«¼à«½à«¾à«¿à¬€à¬à¬‚ଃ଄ଅଆଇଈଉଊଋଌà¬à¬Žà¬à¬à¬‘଒ଓଔକଖଗଘଙଚଛଜà¬à¬žà¬Ÿà¬ à¬¡","ଢଣତଥଦଧନ଩ପଫବଭମଯର଱ଲଳ଴ଵଶଷସହ଺଻଼ଽାିୀà­à­‚ୃୄ୅୆େୈ୉୊ୋୌà­à­Žà­à­à­‘୒୓୔୕ୖୗ୘୙୚୛ଡ଼à­à­žà­Ÿà­ à­¡à­¢à­£à­¤à­¥à­¦à­§à­¨à­©à­ªà­«à­¬à­­à­®à­¯à­°à­±à­²à­³à­´à­µà­¶à­·à­¸à­¹à­ºà­»à­¼à­½à­¾à­¿à®€à®à®‚ஃ஄அஆஇஈஉஊ஋஌à®à®Žà®à®à®‘ஒஓஔக஖஗஘ஙச஛ஜà®à®žà®Ÿà® à®¡à®¢à®£à®¤à®¥à®¦à®§à®¨à®©à®ªà®«à®¬à®­à®®à®¯à®°à®±à®²à®³à®´à®µà®¶à®·à®¸à®¹à®ºà®»à®¼à®½à®¾à®¿à¯€à¯à¯‚௃௄௅ெேை௉ொோௌà¯à¯Žà¯à¯à¯‘௒௓௔௕௖ௗ௘௙௚௛௜à¯à¯žà¯Ÿà¯ à¯¡à¯¢à¯£à¯¤à¯¥à¯¦à¯§à¯¨à¯©à¯ªà¯«à¯¬à¯­à¯®à¯¯à¯°à¯±à¯²à¯³à¯´à¯µà¯¶à¯·à¯¸à¯¹à¯ºà¯»à¯¼à¯½à¯¾à¯¿à°€à°à°‚ఃఄఅఆఇఈఉఊఋఌà°à°Žà°à°à°‘ఒఓఔకఖగఘఙచఛజà°à°žà°Ÿà° à°¡","ఢణతథదధన఩పఫబభమయరఱలళఴవశషసహ఺఻఼ఽాిీà±à±‚ృౄ౅ెేై౉ొోౌà±à±Žà±à±à±‘౒౓౔ౕౖ౗ౘౙౚ౛౜à±à±žà±Ÿà± à±¡à±¢à±£à±¤à±¥à±¦à±§à±¨à±©à±ªà±«à±¬à±­à±®à±¯à±°à±±à±²à±³à±´à±µà±¶à±·à±¸à±¹à±ºà±»à±¼à±½à±¾à±¿à²€à²à²‚ಃ಄ಅಆಇಈಉಊಋಌà²à²Žà²à²à²‘ಒಓಔಕಖಗಘಙಚಛಜà²à²žà²Ÿà² à²¡à²¢à²£à²¤à²¥à²¦à²§à²¨à²©à²ªà²«à²¬à²­à²®à²¯à²°à²±à²²à²³à²´à²µà²¶à²·à²¸à²¹à²ºà²»à²¼à²½à²¾à²¿à³€à³à³‚ೃೄ೅ೆೇೈ೉ೊೋೌà³à³Žà³à³à³‘೒೓೔ೕೖ೗೘೙೚೛೜à³à³žà³Ÿà³ à³¡à³¢à³£à³¤à³¥à³¦à³§à³¨à³©à³ªà³«à³¬à³­à³®à³¯à³°à³±à³²à³³à³´à³µà³¶à³·à³¸à³¹à³ºà³»à³¼à³½à³¾à³¿à´€à´à´‚ഃഄഅആഇഈഉഊഋഌà´à´Žà´à´à´‘ഒഓഔകഖഗഘങചഛജà´à´žà´Ÿà´ à´¡","ഢണതഥദധനഩപഫബഭമയരറലളഴവശഷസഹഺ഻഼ഽാിീàµàµ‚ൃൄ൅െേൈ൉ൊോൌàµàµŽàµàµàµ‘൒൓ൔൕൖൗ൘൙൚൛൜àµàµžàµŸàµ àµ¡àµ¢àµ£àµ¤àµ¥àµ¦àµ§àµ¨àµ©àµªàµ«àµ¬àµ­àµ®àµ¯àµ°àµ±àµ²àµ³àµ´àµµàµ¶àµ·àµ¸àµ¹àµºàµ»àµ¼àµ½àµ¾àµ¿à¶€à¶à¶‚ඃ඄අආඇඈඉඊඋඌà¶à¶Žà¶à¶à¶‘ඒඓඔඕඖ඗඘඙කඛගà¶à¶žà¶Ÿà¶ à¶¡à¶¢à¶£à¶¤à¶¥à¶¦à¶§à¶¨à¶©à¶ªà¶«à¶¬à¶­à¶®à¶¯à¶°à¶±à¶²à¶³à¶´à¶µà¶¶à¶·à¶¸à¶¹à¶ºà¶»à¶¼à¶½à¶¾à¶¿à·€à·à·‚සහළෆ෇෈෉්෋෌à·à·Žà·à·à·‘ිීු෕ූ෗ෘෙේෛොà·à·žà·Ÿà· à·¡à·¢à·£à·¤à·¥à·¦à·§à·¨à·©à·ªà·«à·¬à·­à·®à·¯à·°à·±à·²à·³à·´à·µà·¶à··à·¸à·¹à·ºà·»à·¼à·½à·¾à·¿à¸€à¸à¸‚ฃคฅฆงจฉชซฌà¸à¸Žà¸à¸à¸‘ฒณดตถทธนบปผà¸à¸žà¸Ÿà¸ à¸¡","ยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู฻฼฽฾฿เà¹à¹‚ใไๅๆ็่้๊๋์à¹à¹Žà¹à¹à¹‘๒๓๔๕๖๗๘๙๚๛๜à¹à¹žà¹Ÿà¹ à¹¡à¹¢à¹£à¹¤à¹¥à¹¦à¹§à¹¨à¹©à¹ªà¹«à¹¬à¹­à¹®à¹¯à¹°à¹±à¹²à¹³à¹´à¹µà¹¶à¹·à¹¸à¹¹à¹ºà¹»à¹¼à¹½à¹¾à¹¿àº€àºàº‚຃ຄ຅ຆງຈຉຊ຋ຌàºàºŽàºàºàº‘ຒຓດຕຖທຘນບປຜàºàºžàºŸàº àº¡àº¢àº£àº¤àº¥àº¦àº§àº¨àº©àºªàº«àº¬àº­àº®àº¯àº°àº±àº²àº³àº´àºµàº¶àº·àº¸àº¹àººàº»àº¼àº½àº¾àº¿à»€à»à»‚ໃໄ໅ໆ໇່້໊໋໌à»à»Žà»à»à»‘໒໓໔໕໖໗໘໙໚໛ໜà»à»žà»Ÿà» à»¡à»¢à»£à»¤à»¥à»¦à»§à»¨à»©à»ªà»«à»¬à»­à»®à»¯à»°à»±à»²à»³à»´à»µà»¶à»·à»¸à»¹à»ºà»»à»¼à»½à»¾à»¿à¼€à¼à¼‚༃༄༅༆༇༈༉༊་༌à¼à¼Žà¼à¼à¼‘༒༓༔༕༖༗༘༙༚༛༜à¼à¼žà¼Ÿà¼ à¼¡","༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿ཀà½à½‚གྷངཅཆཇ཈ཉཊཋཌà½à½Žà½à½à½‘དྷནཔཕབབྷམཙཚཛཛྷà½à½žà½Ÿà½ à½¡à½¢à½£à½¤à½¥à½¦à½§à½¨à½©à½ªà½«à½¬à½­à½®à½¯à½°à½±à½²à½³à½´à½µà½¶à½·à½¸à½¹à½ºà½»à½¼à½½à½¾à½¿à¾€à¾à¾‚྄ྃ྅྆྇ྈྉྊྋྌà¾à¾Žà¾à¾à¾‘ྒྒྷྔྕྖྗ྘ྙྚྛྜà¾à¾žà¾Ÿà¾ à¾¡à¾¢à¾£à¾¤à¾¥à¾¦à¾§à¾¨à¾©à¾ªà¾«à¾¬à¾­à¾®à¾¯à¾°à¾±à¾²à¾³à¾´à¾µà¾¶à¾·à¾¸à¾¹à¾ºà¾»à¾¼à¾½à¾¾à¾¿à¿€à¿à¿‚࿃࿄࿅࿆࿇࿈࿉࿊࿋࿌à¿à¿Žà¿à¿à¿‘࿒࿓࿔࿕࿖࿗࿘࿙࿚࿛࿜à¿à¿žà¿Ÿà¿ à¿¡à¿¢à¿£à¿¤à¿¥à¿¦à¿§à¿¨à¿©à¿ªà¿«à¿¬à¿­à¿®à¿¯à¿°à¿±à¿²à¿³à¿´à¿µà¿¶à¿·à¿¸à¿¹à¿ºà¿»à¿¼à¿½à¿¾à¿¿á€€á€á€‚ဃငစဆဇဈဉညဋဌá€á€Žá€á€á€‘ဒဓနပဖဗဘမယရလá€á€žá€Ÿá€ á€¡","ဢဣဤဥဦဧဨဩဪါာိီုူေဲဳဴဵံ့း္်ျြွှဿá€áá‚áƒá„á…á†á‡áˆá‰áŠá‹áŒááŽááá‘á’á“á”á•á–á—á˜á™ášá›áœáážáŸá á¡á¢á£á¤á¥á¦á§á¨á©áªá«á¬á­á®á¯á°á±á²á³á´áµá¶á·á¸á¹áºá»á¼á½á¾á¿á‚€á‚ႂႃႄႅႆႇႈႉႊႋႌá‚á‚Žá‚á‚႑႒႓႔႕႖႗႘႙ႚႛႜá‚႞႟ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀáƒáƒ‚ჃჄჅ჆Ⴧ჈჉჊჋჌áƒáƒŽáƒáƒáƒ‘გდევზთიკლმნáƒáƒžáƒŸáƒ áƒ¡áƒ¢áƒ£áƒ¤áƒ¥áƒ¦áƒ§áƒ¨áƒ©áƒªáƒ«áƒ¬áƒ­áƒ®áƒ¯áƒ°áƒ±áƒ²áƒ³áƒ´áƒµáƒ¶áƒ·áƒ¸áƒ¹áƒºáƒ»áƒ¼áƒ½áƒ¾áƒ¿á„€á„ᄂᄃᄄᄅᄆᄇᄈᄉᄊᄋᄌá„á„Žá„á„ᄑᄒᄓᄔᄕᄖᄗᄘᄙᄚᄛᄜá„á„žá„Ÿá„ á„¡","ᄢᄣᄤᄥᄦᄧᄨᄩᄪᄫᄬᄭᄮᄯᄰᄱᄲᄳᄴᄵᄶᄷᄸᄹᄺᄻᄼᄽᄾᄿᅀá…ᅂᅃᅄᅅᅆᅇᅈᅉᅊᅋᅌá…á…Žá…á…ᅑᅒᅓᅔᅕᅖᅗᅘᅙᅚᅛᅜá…ᅞᅟᅠᅡᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵᅶᅷᅸᅹᅺᅻᅼᅽᅾᅿᆀá†á†‚ᆃᆄᆅᆆᆇᆈᆉᆊᆋᆌá†á†Žá†á†á†‘ᆒᆓᆔᆕᆖᆗᆘᆙᆚᆛᆜá†á†žá†Ÿá† á†¡á†¢á†£á†¤á†¥á†¦á†§á†¨á†©á†ªá†«á†¬á†­á†®á†¯á†°á†±á†²á†³á†´á†µá†¶á†·á†¸á†¹á†ºá†»á†¼á†½á†¾á†¿á‡€á‡á‡‚ᇃᇄᇅᇆᇇᇈᇉᇊᇋᇌá‡á‡Žá‡á‡á‡‘ᇒᇓᇔᇕᇖᇗᇘᇙᇚᇛᇜá‡á‡žá‡Ÿá‡ á‡¡á‡¢á‡£á‡¤á‡¥á‡¦á‡§á‡¨á‡©á‡ªá‡«á‡¬á‡­á‡®á‡¯á‡°á‡±á‡²á‡³á‡´á‡µá‡¶á‡·á‡¸á‡¹á‡ºá‡»á‡¼á‡½á‡¾á‡¿áˆ€áˆáˆ‚ሃሄህሆሇለሉሊላሌáˆáˆŽáˆáˆáˆ‘ሒሓሔሕሖሗመሙሚማሜáˆáˆžáˆŸáˆ áˆ¡","ሢሣሤሥሦሧረሩሪራሬርሮሯሰሱሲሳሴስሶሷሸሹሺሻሼሽሾሿቀá‰á‰‚ቃቄቅቆቇቈ቉ቊቋቌá‰á‰Žá‰á‰á‰‘ቒቓቔቕቖ቗ቘ቙ቚቛቜá‰á‰žá‰Ÿá‰ á‰¡á‰¢á‰£á‰¤á‰¥á‰¦á‰§á‰¨á‰©á‰ªá‰«á‰¬á‰­á‰®á‰¯á‰°á‰±á‰²á‰³á‰´á‰µá‰¶á‰·á‰¸á‰¹á‰ºá‰»á‰¼á‰½á‰¾á‰¿áŠ€áŠáŠ‚ኃኄኅኆኇኈ኉ኊኋኌáŠáŠŽáŠáŠáŠ‘ኒናኔንኖኗኘኙኚኛኜáŠáŠžáŠŸáŠ áŠ¡áŠ¢áŠ£áŠ¤áŠ¥áŠ¦áŠ§áŠ¨áŠ©áŠªáŠ«áŠ¬áŠ­áŠ®áŠ¯áŠ°áŠ±áŠ²áŠ³áŠ´áŠµáŠ¶áŠ·áŠ¸áŠ¹áŠºáŠ»áŠ¼áŠ½áŠ¾áŠ¿á‹€á‹á‹‚ዃዄዅ዆዇ወዉዊዋዌá‹á‹Žá‹á‹á‹‘ዒዓዔዕዖ዗ዘዙዚዛዜá‹á‹žá‹Ÿá‹ á‹¡á‹¢á‹£á‹¤á‹¥á‹¦á‹§á‹¨á‹©á‹ªá‹«á‹¬á‹­á‹®á‹¯á‹°á‹±á‹²á‹³á‹´á‹µá‹¶á‹·á‹¸á‹¹á‹ºá‹»á‹¼á‹½á‹¾á‹¿áŒ€áŒáŒ‚ጃጄጅጆጇገጉጊጋጌáŒáŒŽáŒáŒáŒ‘ጒጓጔጕ጖጗ጘጙጚጛጜáŒáŒžáŒŸáŒ áŒ¡","ጢጣጤጥጦጧጨጩጪጫጬጭጮጯጰጱጲጳጴጵጶጷጸጹጺጻጼጽጾጿá€áá‚áƒá„á…á†á‡áˆá‰áŠá‹áŒááŽááá‘á’á“á”á•á–á—á˜á™ášá›áœáážáŸá á¡á¢á£á¤á¥á¦á§á¨á©áªá«á¬á­á®á¯á°á±á²á³á´áµá¶á·á¸á¹áºá»á¼á½á¾á¿áŽ€áŽáŽ‚ᎃᎄᎅᎆᎇᎈᎉᎊᎋᎌáŽáŽŽáŽáŽáŽ‘᎒᎓᎔᎕᎖᎗᎘᎙᎚᎛᎜áŽáŽžáŽŸáŽ áŽ¡áŽ¢áŽ£áŽ¤áŽ¥áŽ¦áŽ§áŽ¨áŽ©áŽªáŽ«áŽ¬áŽ­áŽ®áŽ¯áŽ°áŽ±áŽ²áŽ³áŽ´áŽµáŽ¶áŽ·áŽ¸áŽ¹áŽºáŽ»áŽ¼áŽ½áŽ¾áŽ¿á€áá‚áƒá„á…á†á‡áˆá‰áŠá‹áŒááŽááá‘á’á“á”á•á–á—á˜á™ášá›áœáážáŸá á¡á¢á£á¤á¥á¦á§á¨á©áªá«á¬á­á®á¯á°á±á²á³á´áµá¶á·á¸á¹áºá»á¼á½á¾á¿á€áá‚áƒá„á…á†á‡áˆá‰áŠá‹áŒááŽááá‘á’á“á”á•á–á—á˜á™ášá›áœáážáŸá á¡","á¢á£á¤á¥á¦á§á¨á©áªá«á¬á­á®á¯á°á±á²á³á´áµá¶á·á¸á¹áºá»á¼á½á¾á¿á‘€á‘ᑂᑃᑄᑅᑆᑇᑈᑉᑊᑋᑌá‘á‘Žá‘á‘ᑑᑒᑓᑔᑕᑖᑗᑘᑙᑚᑛᑜá‘ᑞᑟᑠᑡᑢᑣᑤᑥᑦᑧᑨᑩᑪᑫᑬᑭᑮᑯᑰᑱᑲᑳᑴᑵᑶᑷᑸᑹᑺᑻᑼᑽᑾᑿᒀá’ᒂᒃᒄᒅᒆᒇᒈᒉᒊᒋᒌá’á’Žá’á’ᒑᒒᒓᒔᒕᒖᒗᒘᒙᒚᒛᒜá’ᒞᒟᒠᒡᒢᒣᒤᒥᒦᒧᒨᒩᒪᒫᒬᒭᒮᒯᒰᒱᒲᒳᒴᒵᒶᒷᒸᒹᒺᒻᒼᒽᒾᒿᓀá“ᓂᓃᓄᓅᓆᓇᓈᓉᓊᓋᓌá“á“Žá“á“ᓑᓒᓓᓔᓕᓖᓗᓘᓙᓚᓛᓜá“ᓞᓟᓠᓡᓢᓣᓤᓥᓦᓧᓨᓩᓪᓫᓬᓭᓮᓯᓰᓱᓲᓳᓴᓵᓶᓷᓸᓹᓺᓻᓼᓽᓾᓿᔀá”ᔂᔃᔄᔅᔆᔇᔈᔉᔊᔋᔌá”ᔎá”á”ᔑᔒᔓᔔᔕᔖᔗᔘᔙᔚᔛᔜá”ᔞᔟᔠᔡ","ᔢᔣᔤᔥᔦᔧᔨᔩᔪᔫᔬᔭᔮᔯᔰᔱᔲᔳᔴᔵᔶᔷᔸᔹᔺᔻᔼᔽᔾᔿᕀá•á•‚ᕃᕄᕅᕆᕇᕈᕉᕊᕋᕌá•á•Žá•á•á•‘ᕒᕓᕔᕕᕖᕗᕘᕙᕚᕛᕜá•á•žá•Ÿá• á•¡á•¢á•£á•¤á•¥á•¦á•§á•¨á•©á•ªá•«á•¬á•­á•®á•¯á•°á•±á•²á•³á•´á•µá•¶á•·á•¸á•¹á•ºá•»á•¼á•½á•¾á•¿á–€á–ᖂᖃᖄᖅᖆᖇᖈᖉᖊᖋᖌá–á–Žá–á–ᖑᖒᖓᖔᖕᖖᖗᖘᖙᖚᖛᖜá–ᖞᖟᖠᖡᖢᖣᖤᖥᖦᖧᖨᖩᖪᖫᖬᖭᖮᖯᖰᖱᖲᖳᖴᖵᖶᖷᖸᖹᖺᖻᖼᖽᖾᖿᗀá—ᗂᗃᗄᗅᗆᗇᗈᗉᗊᗋᗌá—á—Žá—á—ᗑᗒᗓᗔᗕᗖᗗᗘᗙᗚᗛᗜá—ᗞᗟᗠᗡᗢᗣᗤᗥᗦᗧᗨᗩᗪᗫᗬᗭᗮᗯᗰᗱᗲᗳᗴᗵᗶᗷᗸᗹᗺᗻᗼᗽᗾᗿᘀá˜á˜‚ᘃᘄᘅᘆᘇᘈᘉᘊᘋᘌá˜á˜Žá˜á˜á˜‘ᘒᘓᘔᘕᘖᘗᘘᘙᘚᘛᘜá˜á˜žá˜Ÿá˜ á˜¡","ᘢᘣᘤᘥᘦᘧᘨᘩᘪᘫᘬᘭᘮᘯᘰᘱᘲᘳᘴᘵᘶᘷᘸᘹᘺᘻᘼᘽᘾᘿᙀá™á™‚ᙃᙄᙅᙆᙇᙈᙉᙊᙋᙌá™á™Žá™á™á™‘ᙒᙓᙔᙕᙖᙗᙘᙙᙚᙛᙜá™á™žá™Ÿá™ á™¡á™¢á™£á™¤á™¥á™¦á™§á™¨á™©á™ªá™«á™¬á™­á™®á™¯á™°á™±á™²á™³á™´á™µá™¶á™·á™¸á™¹á™ºá™»á™¼á™½á™¾á™¿áš€ášáš‚ᚃᚄᚅᚆᚇᚈᚉᚊᚋᚌášášŽášášáš‘ᚒᚓᚔᚕᚖᚗᚘᚙᚚ᚛᚜ášášžášŸáš áš¡áš¢áš£áš¤áš¥áš¦áš§áš¨áš©ášªáš«áš¬áš­áš®áš¯áš°áš±áš²áš³áš´ášµáš¶áš·áš¸áš¹ášºáš»áš¼áš½áš¾áš¿á›€á›á›‚ᛃᛄᛅᛆᛇᛈᛉᛊᛋᛌá›á›Žá›á›á›‘ᛒᛓᛔᛕᛖᛗᛘᛙᛚᛛᛜá›á›žá›Ÿá› á›¡á›¢á›£á›¤á›¥á›¦á›§á›¨á›©á›ªá›«á›¬á›­á›®á›¯á›°á›±á›²á›³á›´á›µá›¶á›·á›¸á›¹á›ºá›»á›¼á›½á›¾á›¿áœ€áœáœ‚ᜃᜄᜅᜆᜇᜈᜉᜊᜋᜌáœáœŽáœáœáœ‘ᜒᜓ᜔᜕᜖᜗᜘᜙᜚᜛᜜áœáœžáœŸáœ áœ¡","ᜢᜣᜤᜥᜦᜧᜨᜩᜪᜫᜬᜭᜮᜯᜰᜱᜲᜳ᜴᜵᜶᜷᜸᜹᜺᜻᜼᜽᜾᜿á€áá‚áƒá„á…á†á‡áˆá‰áŠá‹áŒááŽááá‘á’á“á”á•á–á—á˜á™ášá›áœáážáŸá á¡á¢á£á¤á¥á¦á§á¨á©áªá«á¬á­á®á¯á°á±á²á³á´áµá¶á·á¸á¹áºá»á¼á½á¾á¿áž€ážáž‚ឃងចឆជឈញដឋឌážážŽážážáž‘ធនបផពភមយរលវážážžážŸáž áž¡áž¢áž£áž¤áž¥áž¦áž§áž¨áž©ážªáž«áž¬áž­áž®áž¯áž°áž±áž²áž³áž´ážµáž¶áž·áž¸áž¹ážºáž»áž¼áž½áž¾áž¿áŸ€áŸáŸ‚ៃោៅំះៈ៉៊់៌áŸáŸŽáŸáŸáŸ‘្៓។៕៖ៗ៘៙៚៛ៜáŸáŸžáŸŸáŸ áŸ¡áŸ¢áŸ£áŸ¤áŸ¥áŸ¦áŸ§áŸ¨áŸ©áŸªáŸ«áŸ¬áŸ­áŸ®áŸ¯áŸ°áŸ±áŸ²áŸ³áŸ´áŸµáŸ¶áŸ·áŸ¸áŸ¹áŸºáŸ»áŸ¼áŸ½áŸ¾áŸ¿á €á á ‚᠃᠄᠅᠆᠇᠈᠉᠊᠋᠌á á Žá á á ‘᠒᠓᠔᠕᠖᠗᠘᠙᠚᠛᠜á á žá Ÿá  á ¡","ᠢᠣᠤᠥᠦᠧᠨᠩᠪᠫᠬᠭᠮᠯᠰᠱᠲᠳᠴᠵᠶᠷᠸᠹᠺᠻᠼᠽᠾᠿᡀá¡á¡‚ᡃᡄᡅᡆᡇᡈᡉᡊᡋᡌá¡á¡Žá¡á¡á¡‘ᡒᡓᡔᡕᡖᡗᡘᡙᡚᡛᡜá¡á¡žá¡Ÿá¡ á¡¡á¡¢á¡£á¡¤á¡¥á¡¦á¡§á¡¨á¡©á¡ªá¡«á¡¬á¡­á¡®á¡¯á¡°á¡±á¡²á¡³á¡´á¡µá¡¶á¡·á¡¸á¡¹á¡ºá¡»á¡¼á¡½á¡¾á¡¿á¢€á¢á¢‚ᢃᢄᢅᢆᢇᢈᢉᢊᢋᢌá¢á¢Žá¢á¢á¢‘ᢒᢓᢔᢕᢖᢗᢘᢙᢚᢛᢜá¢á¢žá¢Ÿá¢ á¢¡á¢¢á¢£á¢¤á¢¥á¢¦á¢§á¢¨á¢©á¢ªá¢«á¢¬á¢­á¢®á¢¯á¢°á¢±á¢²á¢³á¢´á¢µá¢¶á¢·á¢¸á¢¹á¢ºá¢»á¢¼á¢½á¢¾á¢¿á£€á£á£‚ᣃᣄᣅᣆᣇᣈᣉᣊᣋᣌá£á£Žá£á£á£‘ᣒᣓᣔᣕᣖᣗᣘᣙᣚᣛᣜá£á£žá£Ÿá£ á£¡á£¢á££á£¤á£¥á£¦á£§á£¨á£©á£ªá£«á£¬á£­á£®á£¯á£°á£±á£²á£³á£´á£µá£¶á£·á£¸á£¹á£ºá£»á£¼á£½á£¾á£¿á¤€á¤á¤‚ᤃᤄᤅᤆᤇᤈᤉᤊᤋᤌá¤á¤Žá¤á¤á¤‘ᤒᤓᤔᤕᤖᤗᤘᤙᤚᤛᤜá¤á¤žá¤Ÿá¤ á¤¡","ᤢᤣᤤᤥᤦᤧᤨᤩᤪᤫ᤬᤭᤮᤯ᤰᤱᤲᤳᤴᤵᤶᤷᤸ᤻᤹᤺᤼᤽᤾᤿᥀á¥á¥‚᥃᥄᥅᥆᥇᥈᥉᥊᥋᥌á¥á¥Žá¥á¥á¥‘ᥒᥓᥔᥕᥖᥗᥘᥙᥚᥛᥜá¥á¥žá¥Ÿá¥ á¥¡á¥¢á¥£á¥¤á¥¥á¥¦á¥§á¥¨á¥©á¥ªá¥«á¥¬á¥­á¥®á¥¯á¥°á¥±á¥²á¥³á¥´á¥µá¥¶á¥·á¥¸á¥¹á¥ºá¥»á¥¼á¥½á¥¾á¥¿á¦€á¦á¦‚ᦃᦄᦅᦆᦇᦈᦉᦊᦋᦌá¦á¦Žá¦á¦á¦‘ᦒᦓᦔᦕᦖᦗᦘᦙᦚᦛᦜá¦á¦žá¦Ÿá¦ á¦¡á¦¢á¦£á¦¤á¦¥á¦¦á¦§á¦¨á¦©á¦ªá¦«á¦¬á¦­á¦®á¦¯á¦°á¦±á¦²á¦³á¦´á¦µá¦¶á¦·á¦¸á¦¹á¦ºá¦»á¦¼á¦½á¦¾á¦¿á§€á§á§‚ᧃᧄᧅᧆᧇᧈᧉ᧊᧋᧌á§á§Žá§á§á§‘᧒᧓᧔᧕᧖᧗᧘᧙᧚᧛᧜á§á§žá§Ÿá§ á§¡á§¢á§£á§¤á§¥á§¦á§§á§¨á§©á§ªá§«á§¬á§­á§®á§¯á§°á§±á§²á§³á§´á§µá§¶á§·á§¸á§¹á§ºá§»á§¼á§½á§¾á§¿á¨€á¨á¨‚ᨃᨄᨅᨆᨇᨈᨉᨊᨋᨌá¨á¨Žá¨á¨á¨‘ᨒᨓᨔᨕᨖᨘᨗᨙᨚᨛ᨜á¨á¨žá¨Ÿá¨ á¨¡","ᨢᨣᨤᨥᨦᨧᨨᨩᨪᨫᨬᨭᨮᨯᨰᨱᨲᨳᨴᨵᨶᨷᨸᨹᨺᨻᨼᨽᨾᨿᩀá©á©‚ᩃᩄᩅᩆᩇᩈᩉᩊᩋᩌá©á©Žá©á©á©‘ᩒᩓᩔᩕᩖᩗᩘᩙᩚᩛᩜá©á©žá©Ÿá© á©¡á©¢á©£á©¤á©¥á©¦á©§á©¨á©©á©ªá©«á©¬á©­á©®á©¯á©°á©±á©²á©³á©´á©µá©¶á©·á©¸á©¹á©ºá©»á©¼á©½á©¾á©¿áª€áªáª‚᪃᪄᪅᪆᪇᪈᪉᪊᪋᪌áªáªŽáªáªáª‘᪒᪓᪔᪕᪖᪗᪘᪙᪚᪛᪜áªáªžáªŸáª áª¡áª¢áª£áª¤áª¥áª¦áª§áª¨áª©áªªáª«áª¬áª­áª®áª¯áª°áª±áª²áª³áª´áªµáª¶áª·áª¸áª¹áªºáª»áª¼áª½áª¾áª¿á«€á«á«‚᫃᫄᫊᫅᫆᫇᫈᫉᫋ᫌá«á«Žá«á«á«‘᫒᫓᫔᫕᫖᫗᫘᫙᫚᫛᫜á«á«žá«Ÿá« á«¡á«¢á«£á«¤á«¥á«¦á«§á«¨á«©á«ªá««á«¬á«­á«®á«¯á«°á«±á«²á«³á«´á«µá«¶á«·á«¸á«¹á«ºá«»á«¼á«½á«¾á«¿á¬€á¬á¬‚ᬃᬄᬅᬆᬇᬈᬉᬊᬋᬌá¬á¬Žá¬á¬á¬‘ᬒᬓᬔᬕᬖᬗᬘᬙᬚᬛᬜá¬á¬žá¬Ÿá¬ á¬¡","ᬢᬣᬤᬥᬦᬧᬨᬩᬪᬫᬬᬭᬮᬯᬰᬱᬲᬳ᬴ᬵᬶᬷᬸᬹᬺᬻᬼᬽᬾᬿᭀá­á­‚ᭃ᭄ᭅᭆᭇᭈᭉᭊᭋᭌá­á­Žá­á­á­‘᭒᭓᭔᭕᭖᭗᭘᭙᭚᭛᭜á­á­žá­Ÿá­ á­¡á­¢á­£á­¤á­¥á­¦á­§á­¨á­©á­ªá­«á­¬á­­á­®á­¯á­°á­±á­²á­³á­´á­µá­¶á­·á­¸á­¹á­ºá­»á­¼á­½á­¾á­¿á®€á®á®‚ᮃᮄᮅᮆᮇᮈᮉᮊᮋᮌá®á®Žá®á®á®‘ᮒᮓᮔᮕᮖᮗᮘᮙᮚᮛᮜá®á®žá®Ÿá® á®¡á®¢á®£á®¤á®¥á®¦á®§á®¨á®©á®ªá®«á®¬á®­á®®á®¯á®°á®±á®²á®³á®´á®µá®¶á®·á®¸á®¹á®ºá®»á®¼á®½á®¾á®¿á¯€á¯á¯‚ᯃᯄᯅᯆᯇᯈᯉᯊᯋᯌá¯á¯Žá¯á¯á¯‘ᯒᯓᯔᯕᯖᯗᯘᯙᯚᯛᯜá¯á¯žá¯Ÿá¯ á¯¡á¯¢á¯£á¯¤á¯¥á¯¦á¯§á¯¨á¯©á¯ªá¯«á¯¬á¯­á¯®á¯¯á¯°á¯±á¯²á¯³á¯´á¯µá¯¶á¯·á¯¸á¯¹á¯ºá¯»á¯¼á¯½á¯¾á¯¿á°€á°á°‚ᰃᰄᰅᰆᰇᰈᰉᰊᰋᰌá°á°Žá°á°á°‘ᰒᰓᰔᰕᰖᰗᰘᰙᰚᰛᰜá°á°žá°Ÿá° á°¡","ᰢᰣᰤᰥᰦᰧᰨᰩᰪᰫᰬᰭᰮᰯᰰᰱᰲᰳᰴᰵᰶ᰷᰸᰹᰺᰻᰼᰽᰾᰿᱀á±á±‚᱃᱄᱅᱆᱇᱈᱉᱊᱋᱌á±á±Žá±á±á±‘᱒᱓᱔᱕᱖᱗᱘᱙ᱚᱛᱜá±á±žá±Ÿá± á±¡á±¢á±£á±¤á±¥á±¦á±§á±¨á±©á±ªá±«á±¬á±­á±®á±¯á±°á±±á±²á±³á±´á±µá±¶á±·á±¸á±¹á±ºá±»á±¼á±½á±¾á±¿á²€á²á²‚ᲃᲄᲅᲆᲇᲈᲉᲊ᲋᲌á²á²Žá²á²á²‘ᲒᲓᲔᲕᲖᲗᲘᲙᲚᲛᲜá²á²žá²Ÿá² á²¡á²¢á²£á²¤á²¥á²¦á²§á²¨á²©á²ªá²«á²¬á²­á²®á²¯á²°á²±á²²á²³á²´á²µá²¶á²·á²¸á²¹á²ºá²»á²¼á²½á²¾á²¿á³€á³á³‚᳃᳄᳅᳆᳇᳈᳉᳊᳋᳌á³á³Žá³á³á³‘᳒᳓᳔᳕᳖᳗᳘᳙᳜᳚᳛á³á³žá³Ÿá³ á³¡á³¢á³£á³¤á³¥á³¦á³§á³¨á³©á³ªá³«á³¬á³­á³®á³¯á³°á³±á³²á³³á³´á³µá³¶á³·á³¸á³¹á³ºá³»á³¼á³½á³¾á³¿á´€á´á´‚ᴃᴄᴅᴆᴇᴈᴉᴊᴋᴌá´á´Žá´á´á´‘ᴒᴓᴔᴕᴖᴗᴘᴙᴚᴛᴜá´á´žá´Ÿá´ á´¡","ᴢᴣᴤᴥᴦᴧᴨᴩᴪᴫᴬᴭᴮᴯᴰᴱᴲᴳᴴᴵᴶᴷᴸᴹᴺᴻᴼᴽᴾᴿᵀáµáµ‚ᵃᵄᵅᵆᵇᵈᵉᵊᵋᵌáµáµŽáµáµáµ‘ᵒᵓᵔᵕᵖᵗᵘᵙᵚᵛᵜáµáµžáµŸáµ áµ¡áµ¢áµ£áµ¤áµ¥áµ¦áµ§áµ¨áµ©áµªáµ«áµ¬áµ­áµ®áµ¯áµ°áµ±áµ²áµ³áµ´áµµáµ¶áµ·áµ¸áµ¹áµºáµ»áµ¼áµ½áµ¾áµ¿á¶€á¶á¶‚ᶃᶄᶅᶆᶇᶈᶉᶊᶋᶌá¶á¶Žá¶á¶á¶‘ᶒᶓᶔᶕᶖᶗᶘᶙᶚᶛᶜá¶á¶žá¶Ÿá¶ á¶¡á¶¢á¶£á¶¤á¶¥á¶¦á¶§á¶¨á¶©á¶ªá¶«á¶¬á¶­á¶®á¶¯á¶°á¶±á¶²á¶³á¶´á¶µá¶¶á¶·á¶¸á¶¹á¶ºá¶»á¶¼á¶½á¶¾á¶¿á·€á·á·‚᷊᷃᷄᷅᷆᷇᷈᷉᷋᷌á·á·Žá·á·á·‘᷒ᷓᷔᷕᷖᷗᷘᷙᷚᷛᷜá·á·žá·Ÿá· á·¡á·¢á·£á·¤á·¥á·¦á·§á·¨á·©á·ªá·«á·¬á·­á·®á·¯á·°á·±á·²á·³á·´á·µá·¶á··á·¸á·¹á·ºá·»á·¼á·½á·¾á·¿á¸€á¸á¸‚ḃḄḅḆḇḈḉḊḋḌá¸á¸Žá¸á¸á¸‘ḒḓḔḕḖḗḘḙḚḛḜá¸á¸žá¸Ÿá¸ á¸¡","ḢḣḤḥḦḧḨḩḪḫḬḭḮḯḰḱḲḳḴḵḶḷḸḹḺḻḼḽḾḿṀá¹á¹‚ṃṄṅṆṇṈṉṊṋṌá¹á¹Žá¹á¹á¹‘ṒṓṔṕṖṗṘṙṚṛṜá¹á¹žá¹Ÿá¹ á¹¡á¹¢á¹£á¹¤á¹¥á¹¦á¹§á¹¨á¹©á¹ªá¹«á¹¬á¹­á¹®á¹¯á¹°á¹±á¹²á¹³á¹´á¹µá¹¶á¹·á¹¸á¹¹á¹ºá¹»á¹¼á¹½á¹¾á¹¿áº€áºáº‚ẃẄẅẆẇẈẉẊẋẌáºáºŽáºáºáº‘ẒẓẔẕẖẗẘẙẚẛẜáºáºžáºŸáº áº¡áº¢áº£áº¤áº¥áº¦áº§áº¨áº©áºªáº«áº¬áº­áº®áº¯áº°áº±áº²áº³áº´áºµáº¶áº·áº¸áº¹áººáº»áº¼áº½áº¾áº¿á»€á»á»‚ểỄễỆệỈỉỊịỌá»á»Žá»á»á»‘ỒồỔổỖỗỘộỚớỜá»á»žá»Ÿá» á»¡á»¢á»£á»¤á»¥á»¦á»§á»¨á»©á»ªá»«á»¬á»­á»®á»¯á»°á»±á»²á»³á»´á»µá»¶á»·á»¸á»¹á»ºá»»á»¼á»½á»¾á»¿á¼€á¼á¼‚ἃἄἅἆἇἈἉἊἋἌá¼á¼Žá¼á¼á¼‘ἒἓἔἕ἖἗ἘἙἚἛἜá¼á¼žá¼Ÿá¼ á¼¡","ἢἣἤἥἦἧἨἩἪἫἬἭἮἯἰἱἲἳἴἵἶἷἸἹἺἻἼἽἾἿὀá½á½‚ὃὄὅ὆὇ὈὉὊὋὌá½á½Žá½á½á½‘ὒὓὔὕὖὗ὘Ὑ὚Ὓ὜á½á½žá½Ÿá½ á½¡á½¢á½£á½¤á½¥á½¦á½§á½¨á½©á½ªá½«á½¬á½­á½®á½¯á½°á½±á½²á½³á½´á½µá½¶á½·á½¸á½¹á½ºá½»á½¼á½½á½¾á½¿á¾€á¾á¾‚ᾃᾄᾅᾆᾇᾈᾉᾊᾋᾌá¾á¾Žá¾á¾á¾‘ᾒᾓᾔᾕᾖᾗᾘᾙᾚᾛᾜá¾á¾žá¾Ÿá¾ á¾¡á¾¢á¾£á¾¤á¾¥á¾¦á¾§á¾¨á¾©á¾ªá¾«á¾¬á¾­á¾®á¾¯á¾°á¾±á¾²á¾³á¾´á¾µá¾¶á¾·á¾¸á¾¹á¾ºá¾»á¾¼á¾½á¾¾á¾¿á¿€á¿á¿‚ῃῄ῅ῆῇῈΈῊΉῌá¿á¿Žá¿á¿á¿‘ῒΐ῔῕ῖῗῘῙῚΊ῜á¿á¿žá¿Ÿá¿ á¿¡á¿¢á¿£á¿¤á¿¥á¿¦á¿§á¿¨á¿©á¿ªá¿«á¿¬á¿­á¿®á¿¯á¿°á¿±á¿²á¿³á¿´á¿µá¿¶á¿·á¿¸á¿¹á¿ºá¿»á¿¼á¿½á¿¾á¿¿â€€â€â€‚        ​‌â€â€Žâ€â€â€‘‒–—―‖‗‘’‚‛“â€â€žâ€Ÿâ€ â€¡","•‣․‥…‧

‪‫‬‭‮ ‰‱′″‴‵‶‷‸‹›※‼‽‾‿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââžâŸâ â¡â¢â£â¤â¥â¦â§â¨â©âªâ«â¬â­â®â¯â°â±â²â³â´âµâ¶â·â¸â¹âºâ»â¼â½â¾â¿â‚€â‚₂₃₄₅₆₇₈₉₊₋₌â‚â‚Žâ‚â‚ₑₒₓₔₕₖₗₘₙₚₛₜâ‚₞₟₠₡₢₣₤₥₦₧₨₩₪₫€₭₮₯₰₱₲₳₴₵₶₷₸₹₺₻₼₽₾₿⃀âƒâƒ‚⃃⃄⃅⃆⃇⃈⃉⃊⃋⃌âƒâƒŽâƒâƒâƒ‘⃒⃓⃘⃙⃚⃔⃕⃖⃗⃛⃜âƒâƒžâƒŸâƒ âƒ¡âƒ¢âƒ£âƒ¤âƒ¥âƒ¦âƒ§âƒ¨âƒ©âƒªâƒ«âƒ¬âƒ­âƒ®âƒ¯âƒ°âƒ±âƒ²âƒ³âƒ´âƒµâƒ¶âƒ·âƒ¸âƒ¹âƒºâƒ»âƒ¼âƒ½âƒ¾âƒ¿â„€â„ℂ℃℄℅℆ℇ℈℉ℊℋℌâ„â„Žâ„â„ℑℒℓ℔ℕ№℗℘ℙℚℛℜâ„â„žâ„Ÿâ„ â„¡","™℣ℤ℥Ω℧ℨ℩KÅℬℭ℮ℯℰℱℲℳℴℵℶℷℸℹ℺℻ℼℽℾℿ⅀â…⅂⅃⅄ⅅⅆⅇⅈⅉ⅊⅋⅌â…â…Žâ…â…⅑⅒⅓⅔⅕⅖⅗⅘⅙⅚⅛⅜â…⅞⅟ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⅺⅻⅼⅽⅾⅿↀâ†â†‚Ↄↄↅↆↇↈ↉↊↋↌â†â†Žâ†â†â†‘→↓↔↕↖↗↘↙↚↛↜â†â†žâ†Ÿâ† â†¡â†¢â†£â†¤â†¥â†¦â†§â†¨â†©â†ªâ†«â†¬â†­â†®â†¯â†°â†±â†²â†³â†´â†µâ†¶â†·â†¸â†¹â†ºâ†»â†¼â†½â†¾â†¿â‡€â‡â‡‚⇃⇄⇅⇆⇇⇈⇉⇊⇋⇌â‡â‡Žâ‡â‡â‡‘⇒⇓⇔⇕⇖⇗⇘⇙⇚⇛⇜â‡â‡žâ‡Ÿâ‡ â‡¡â‡¢â‡£â‡¤â‡¥â‡¦â‡§â‡¨â‡©â‡ªâ‡«â‡¬â‡­â‡®â‡¯â‡°â‡±â‡²â‡³â‡´â‡µâ‡¶â‡·â‡¸â‡¹â‡ºâ‡»â‡¼â‡½â‡¾â‡¿âˆ€âˆâˆ‚∃∄∅∆∇∈∉∊∋∌âˆâˆŽâˆâˆâˆ‘−∓∔∕∖∗∘∙√∛∜âˆâˆžâˆŸâˆ âˆ¡","∢∣∤∥∦∧∨∩∪∫∬∭∮∯∰∱∲∳∴∵∶∷∸∹∺∻∼∽∾∿≀â‰â‰‚≃≄≅≆≇≈≉≊≋≌â‰â‰Žâ‰â‰â‰‘≒≓≔≕≖≗≘≙≚≛≜â‰â‰žâ‰Ÿâ‰ â‰¡â‰¢â‰£â‰¤â‰¥â‰¦â‰§â‰¨â‰©â‰ªâ‰«â‰¬â‰­â‰®â‰¯â‰°â‰±â‰²â‰³â‰´â‰µâ‰¶â‰·â‰¸â‰¹â‰ºâ‰»â‰¼â‰½â‰¾â‰¿âŠ€âŠâŠ‚⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌âŠâŠŽâŠâŠâŠ‘⊒⊓⊔⊕⊖⊗⊘⊙⊚⊛⊜âŠâŠžâŠŸâŠ âŠ¡âŠ¢âŠ£âŠ¤âŠ¥âŠ¦âŠ§âŠ¨âŠ©âŠªâŠ«âŠ¬âŠ­âŠ®âŠ¯âŠ°âŠ±âŠ²âŠ³âŠ´âŠµâŠ¶âŠ·âŠ¸âŠ¹âŠºâŠ»âŠ¼âŠ½âŠ¾âŠ¿â‹€â‹â‹‚⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌â‹â‹Žâ‹â‹â‹‘⋒⋓⋔⋕⋖⋗⋘⋙⋚⋛⋜â‹â‹žâ‹Ÿâ‹ â‹¡â‹¢â‹£â‹¤â‹¥â‹¦â‹§â‹¨â‹©â‹ªâ‹«â‹¬â‹­â‹®â‹¯â‹°â‹±â‹²â‹³â‹´â‹µâ‹¶â‹·â‹¸â‹¹â‹ºâ‹»â‹¼â‹½â‹¾â‹¿âŒ€âŒâŒ‚⌃⌄⌅⌆⌇⌈⌉⌊⌋⌌âŒâŒŽâŒâŒâŒ‘⌒⌓⌔⌕⌖⌗⌘⌙⌚⌛⌜âŒâŒžâŒŸâŒ âŒ¡","⌢⌣⌤⌥⌦⌧⌨〈〉⌫⌬⌭⌮⌯⌰⌱⌲⌳⌴⌵⌶⌷⌸⌹⌺⌻⌼⌽⌾⌿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââžâŸâ â¡â¢â£â¤â¥â¦â§â¨â©âªâ«â¬â­â®â¯â°â±â²â³â´âµâ¶â·â¸â¹âºâ»â¼â½â¾â¿âŽ€âŽâŽ‚⎃⎄⎅⎆⎇⎈⎉⎊⎋⎌âŽâŽŽâŽâŽâŽ‘⎒⎓⎔⎕⎖⎗⎘⎙⎚⎛⎜âŽâŽžâŽŸâŽ âŽ¡âŽ¢âŽ£âŽ¤âŽ¥âŽ¦âŽ§âŽ¨âŽ©âŽªâŽ«âŽ¬âŽ­âŽ®âŽ¯âŽ°âŽ±âŽ²âŽ³âŽ´âŽµâŽ¶âŽ·âŽ¸âŽ¹âŽºâŽ»âŽ¼âŽ½âŽ¾âŽ¿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââžâŸâ â¡â¢â£â¤â¥â¦â§â¨â©âªâ«â¬â­â®â¯â°â±â²â³â´âµâ¶â·â¸â¹âºâ»â¼â½â¾â¿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââžâŸâ â¡","â¢â£â¤â¥â¦â§â¨â©âªâ«â¬â­â®â¯â°â±â²â³â´âµâ¶â·â¸â¹âºâ»â¼â½â¾â¿â‘€â‘⑂⑃⑄⑅⑆⑇⑈⑉⑊⑋⑌â‘â‘Žâ‘â‘⑑⑒⑓⑔⑕⑖⑗⑘⑙⑚⑛⑜â‘⑞⑟①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀â’⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌â’â’Žâ’â’⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛⒜â’⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀâ“ⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌâ“â“Žâ“â“ⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜâ“ⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾⓿─â”│┃┄┅┆┇┈┉┊┋┌â”┎â”â”┑┒┓└┕┖┗┘┙┚┛├â”┞┟┠┡","┢┣┤┥┦┧┨┩┪┫┬┭┮┯┰┱┲┳┴┵┶┷┸┹┺┻┼┽┾┿╀â•â•‚╃╄╅╆╇╈╉╊╋╌â•â•Žâ•â•â•‘╒╓╔╕╖╗╘╙╚╛╜â•â•žâ•Ÿâ• â•¡â•¢â•£â•¤â•¥â•¦â•§â•¨â•©â•ªâ•«â•¬â•­â•®â•¯â•°â•±â•²â•³â•´â•µâ•¶â•·â•¸â•¹â•ºâ•»â•¼â•½â•¾â•¿â–€â–▂▃▄▅▆▇█▉▊▋▌â–â–Žâ–â–░▒▓▔▕▖▗▘▙▚▛▜â–▞▟■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿◀â—◂◃◄◅◆◇◈◉◊○◌â—â—Žâ—â—◑◒◓◔◕◖◗◘◙◚◛◜â—◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◻◼◽◾◿☀â˜â˜‚☃☄★☆☇☈☉☊☋☌â˜â˜Žâ˜â˜â˜‘☒☓☔☕☖☗☘☙☚☛☜â˜â˜žâ˜Ÿâ˜ â˜¡","☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀â™â™‚♃♄♅♆♇♈♉♊♋♌â™â™Žâ™â™â™‘♒♓♔♕♖♗♘♙♚♛♜â™â™žâ™Ÿâ™ â™¡â™¢â™£â™¤â™¥â™¦â™§â™¨â™©â™ªâ™«â™¬â™­â™®â™¯â™°â™±â™²â™³â™´â™µâ™¶â™·â™¸â™¹â™ºâ™»â™¼â™½â™¾â™¿âš€âšâš‚⚃⚄⚅⚆⚇⚈⚉⚊⚋⚌âšâšŽâšâšâš‘⚒⚓⚔⚕⚖⚗⚘⚙⚚⚛⚜âšâšžâšŸâš âš¡âš¢âš£âš¤âš¥âš¦âš§âš¨âš©âšªâš«âš¬âš­âš®âš¯âš°âš±âš²âš³âš´âšµâš¶âš·âš¸âš¹âšºâš»âš¼âš½âš¾âš¿â›€â›â›‚⛃⛄⛅⛆⛇⛈⛉⛊⛋⛌â›â›Žâ›â›â›‘⛒⛓⛔⛕⛖⛗⛘⛙⛚⛛⛜â›â›žâ›Ÿâ› â›¡â›¢â›£â›¤â›¥â›¦â›§â›¨â›©â›ªâ›«â›¬â›­â›®â›¯â›°â›±â›²â›³â›´â›µâ›¶â›·â›¸â›¹â›ºâ›»â›¼â›½â›¾â›¿âœ€âœâœ‚✃✄✅✆✇✈✉✊✋✌âœâœŽâœâœâœ‘✒✓✔✕✖✗✘✙✚✛✜âœâœžâœŸâœ âœ¡","✢✣✤✥✦✧✨✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽✾✿â€ââ‚âƒâ„â…â†â‡âˆâ‰âŠâ‹âŒââŽâââ‘â’â“â”â•â–â—â˜â™âšâ›âœââžâŸâ â¡â¢â£â¤â¥â¦â§â¨â©âªâ«â¬â­â®â¯â°â±â²â³â´âµâ¶â·â¸â¹âºâ»â¼â½â¾â¿âž€âžâž‚➃➄➅➆➇➈➉➊➋➌âžâžŽâžâžâž‘➒➓➔➕➖➗➘➙➚➛➜âžâžžâžŸâž âž¡âž¢âž£âž¤âž¥âž¦âž§âž¨âž©âžªâž«âž¬âž­âž®âž¯âž°âž±âž²âž³âž´âžµâž¶âž·âž¸âž¹âžºâž»âž¼âž½âž¾âž¿âŸ€âŸâŸ‚⟃⟄⟅⟆⟇⟈⟉⟊⟋⟌âŸâŸŽâŸâŸâŸ‘⟒⟓⟔⟕⟖⟗⟘⟙⟚⟛⟜âŸâŸžâŸŸâŸ âŸ¡âŸ¢âŸ£âŸ¤âŸ¥âŸ¦âŸ§âŸ¨âŸ©âŸªâŸ«âŸ¬âŸ­âŸ®âŸ¯âŸ°âŸ±âŸ²âŸ³âŸ´âŸµâŸ¶âŸ·âŸ¸âŸ¹âŸºâŸ»âŸ¼âŸ½âŸ¾âŸ¿â €â â ‚⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌â â Žâ â â ‘⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜â â žâ Ÿâ  â ¡","⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀â¡â¡‚⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌â¡â¡Žâ¡â¡â¡‘⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜â¡â¡žâ¡Ÿâ¡ â¡¡â¡¢â¡£â¡¤â¡¥â¡¦â¡§â¡¨â¡©â¡ªâ¡«â¡¬â¡­â¡®â¡¯â¡°â¡±â¡²â¡³â¡´â¡µâ¡¶â¡·â¡¸â¡¹â¡ºâ¡»â¡¼â¡½â¡¾â¡¿â¢€â¢â¢‚⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌â¢â¢Žâ¢â¢â¢‘⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜â¢â¢žâ¢Ÿâ¢ â¢¡â¢¢â¢£â¢¤â¢¥â¢¦â¢§â¢¨â¢©â¢ªâ¢«â¢¬â¢­â¢®â¢¯â¢°â¢±â¢²â¢³â¢´â¢µâ¢¶â¢·â¢¸â¢¹â¢ºâ¢»â¢¼â¢½â¢¾â¢¿â£€â£â£‚⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌â£â£Žâ£â£â£‘⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜â£â£žâ£Ÿâ£ â£¡â£¢â££â£¤â£¥â£¦â£§â£¨â£©â£ªâ£«â£¬â£­â£®â£¯â£°â£±â£²â£³â£´â£µâ£¶â£·â£¸â£¹â£ºâ£»â£¼â£½â£¾â£¿â¤€â¤â¤‚⤃⤄⤅⤆⤇⤈⤉⤊⤋⤌â¤â¤Žâ¤â¤â¤‘⤒⤓⤔⤕⤖⤗⤘⤙⤚⤛⤜â¤â¤žâ¤Ÿâ¤ â¤¡","⤢⤣⤤⤥⤦⤧⤨⤩⤪⤫⤬⤭⤮⤯⤰⤱⤲⤳⤴⤵⤶⤷⤸⤹⤺⤻⤼⤽⤾⤿⥀â¥â¥‚⥃⥄⥅⥆⥇⥈⥉⥊⥋⥌â¥â¥Žâ¥â¥â¥‘⥒⥓⥔⥕⥖⥗⥘⥙⥚⥛⥜â¥â¥žâ¥Ÿâ¥ â¥¡â¥¢â¥£â¥¤â¥¥â¥¦â¥§â¥¨â¥©â¥ªâ¥«â¥¬â¥­â¥®â¥¯â¥°â¥±â¥²â¥³â¥´â¥µâ¥¶â¥·â¥¸â¥¹â¥ºâ¥»â¥¼â¥½â¥¾â¥¿â¦€â¦â¦‚⦃⦄⦅⦆⦇⦈⦉⦊⦋⦌â¦â¦Žâ¦â¦â¦‘⦒⦓⦔⦕⦖⦗⦘⦙⦚⦛⦜â¦â¦žâ¦Ÿâ¦ â¦¡â¦¢â¦£â¦¤â¦¥â¦¦â¦§â¦¨â¦©â¦ªâ¦«â¦¬â¦­â¦®â¦¯â¦°â¦±â¦²â¦³â¦´â¦µâ¦¶â¦·â¦¸â¦¹â¦ºâ¦»â¦¼â¦½â¦¾â¦¿â§€â§â§‚⧃⧄⧅⧆⧇⧈⧉⧊⧋⧌â§â§Žâ§â§â§‘⧒⧓⧔⧕⧖⧗⧘⧙⧚⧛⧜â§â§žâ§Ÿâ§ â§¡â§¢â§£â§¤â§¥â§¦â§§â§¨â§©â§ªâ§«â§¬â§­â§®â§¯â§°â§±â§²â§³â§´â§µâ§¶â§·â§¸â§¹â§ºâ§»â§¼â§½â§¾â§¿â¨€â¨â¨‚⨃⨄⨅⨆⨇⨈⨉⨊⨋⨌â¨â¨Žâ¨â¨â¨‘⨒⨓⨔⨕⨖⨗⨘⨙⨚⨛⨜â¨â¨žâ¨Ÿâ¨ â¨¡","⨢⨣⨤⨥⨦⨧⨨⨩⨪⨫⨬⨭⨮⨯⨰⨱⨲⨳⨴⨵⨶⨷⨸⨹⨺⨻⨼⨽⨾⨿⩀â©â©‚⩃⩄⩅⩆⩇⩈⩉⩊⩋⩌â©â©Žâ©â©â©‘⩒⩓⩔⩕⩖⩗⩘⩙⩚⩛⩜â©â©žâ©Ÿâ© â©¡â©¢â©£â©¤â©¥â©¦â©§â©¨â©©â©ªâ©«â©¬â©­â©®â©¯â©°â©±â©²â©³â©´â©µâ©¶â©·â©¸â©¹â©ºâ©»â©¼â©½â©¾â©¿âª€âªâª‚⪃⪄⪅⪆⪇⪈⪉⪊⪋⪌âªâªŽâªâªâª‘⪒⪓⪔⪕⪖⪗⪘⪙⪚⪛⪜âªâªžâªŸâª âª¡âª¢âª£âª¤âª¥âª¦âª§âª¨âª©âªªâª«âª¬âª­âª®âª¯âª°âª±âª²âª³âª´âªµâª¶âª·âª¸âª¹âªºâª»âª¼âª½âª¾âª¿â«€â«â«‚⫃⫄⫅⫆⫇⫈⫉⫊⫋⫌â«â«Žâ«â«â«‘⫒⫓⫔⫕⫖⫗⫘⫙⫚⫛⫝̸â«â«žâ«Ÿâ« â«¡â«¢â«£â«¤â«¥â«¦â«§â«¨â«©â«ªâ««â«¬â«­â«®â«¯â«°â«±â«²â«³â«´â«µâ«¶â«·â«¸â«¹â«ºâ«»â«¼â«½â«¾â«¿â¬€â¬â¬‚⬃⬄⬅⬆⬇⬈⬉⬊⬋⬌â¬â¬Žâ¬â¬â¬‘⬒⬓⬔⬕⬖⬗⬘⬙⬚⬛⬜â¬â¬žâ¬Ÿâ¬ â¬¡","⬢⬣⬤⬥⬦⬧⬨⬩⬪⬫⬬⬭⬮⬯⬰⬱⬲⬳⬴⬵⬶⬷⬸⬹⬺⬻⬼⬽⬾⬿⭀â­â­‚⭃⭄⭅⭆⭇⭈⭉⭊⭋⭌â­â­Žâ­â­â­‘⭒⭓⭔⭕⭖⭗⭘⭙⭚⭛⭜â­â­žâ­Ÿâ­ â­¡â­¢â­£â­¤â­¥â­¦â­§â­¨â­©â­ªâ­«â­¬â­­â­®â­¯â­°â­±â­²â­³â­´â­µâ­¶â­·â­¸â­¹â­ºâ­»â­¼â­½â­¾â­¿â®€â®â®‚⮃⮄⮅⮆⮇⮈⮉⮊⮋⮌â®â®Žâ®â®â®‘⮒⮓⮔⮕⮖⮗⮘⮙⮚⮛⮜â®â®žâ®Ÿâ® â®¡â®¢â®£â®¤â®¥â®¦â®§â®¨â®©â®ªâ®«â®¬â®­â®®â®¯â®°â®±â®²â®³â®´â®µâ®¶â®·â®¸â®¹â®ºâ®»â®¼â®½â®¾â®¿â¯€â¯â¯‚⯃⯄⯅⯆⯇⯈⯉⯊⯋⯌â¯â¯Žâ¯â¯â¯‘⯒⯓⯔⯕⯖⯗⯘⯙⯚⯛⯜â¯â¯žâ¯Ÿâ¯ â¯¡â¯¢â¯£â¯¤â¯¥â¯¦â¯§â¯¨â¯©â¯ªâ¯«â¯¬â¯­â¯®â¯¯â¯°â¯±â¯²â¯³â¯´â¯µâ¯¶â¯·â¯¸â¯¹â¯ºâ¯»â¯¼â¯½â¯¾â¯¿â°€â°â°‚ⰃⰄⰅⰆⰇⰈⰉⰊⰋⰌâ°â°Žâ°â°â°‘ⰒⰓⰔⰕⰖⰗⰘⰙⰚⰛⰜâ°â°žâ°Ÿâ° â°¡","ⰢⰣⰤⰥⰦⰧⰨⰩⰪⰫⰬⰭⰮⰯⰰⰱⰲⰳⰴⰵⰶⰷⰸⰹⰺⰻⰼⰽⰾⰿⱀâ±â±‚ⱃⱄⱅⱆⱇⱈⱉⱊⱋⱌâ±â±Žâ±â±â±‘ⱒⱓⱔⱕⱖⱗⱘⱙⱚⱛⱜâ±â±žâ±Ÿâ± â±¡â±¢â±£â±¤â±¥â±¦â±§â±¨â±©â±ªâ±«â±¬â±­â±®â±¯â±°â±±â±²â±³â±´â±µâ±¶â±·â±¸â±¹â±ºâ±»â±¼â±½â±¾â±¿â²€â²â²‚ⲃⲄⲅⲆⲇⲈⲉⲊⲋⲌâ²â²Žâ²â²â²‘ⲒⲓⲔⲕⲖⲗⲘⲙⲚⲛⲜâ²â²žâ²Ÿâ² â²¡â²¢â²£â²¤â²¥â²¦â²§â²¨â²©â²ªâ²«â²¬â²­â²®â²¯â²°â²±â²²â²³â²´â²µâ²¶â²·â²¸â²¹â²ºâ²»â²¼â²½â²¾â²¿â³€â³â³‚ⳃⳄⳅⳆⳇⳈⳉⳊⳋⳌâ³â³Žâ³â³â³‘ⳒⳓⳔⳕⳖⳗⳘⳙⳚⳛⳜâ³â³žâ³Ÿâ³ â³¡â³¢â³£â³¤â³¥â³¦â³§â³¨â³©â³ªâ³«â³¬â³­â³®â³¯â³°â³±â³²â³³â³´â³µâ³¶â³·â³¸â³¹â³ºâ³»â³¼â³½â³¾â³¿â´€â´â´‚ⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌâ´â´Žâ´â´â´‘ⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜâ´â´žâ´Ÿâ´ â´¡","ⴢⴣⴤⴥ⴦ⴧ⴨⴩⴪⴫⴬ⴭ⴮⴯ⴰⴱⴲⴳⴴⴵⴶⴷⴸⴹⴺⴻⴼⴽⴾⴿⵀâµâµ‚ⵃⵄⵅⵆⵇⵈⵉⵊⵋⵌâµâµŽâµâµâµ‘ⵒⵓⵔⵕⵖⵗⵘⵙⵚⵛⵜâµâµžâµŸâµ âµ¡âµ¢âµ£âµ¤âµ¥âµ¦âµ§âµ¨âµ©âµªâµ«âµ¬âµ­âµ®âµ¯âµ°âµ±âµ²âµ³âµ´âµµâµ¶âµ·âµ¸âµ¹âµºâµ»âµ¼âµ½âµ¾âµ¿â¶€â¶â¶‚ⶃⶄⶅⶆⶇⶈⶉⶊⶋⶌâ¶â¶Žâ¶â¶â¶‘ⶒⶓⶔⶕⶖ⶗⶘⶙⶚⶛⶜â¶â¶žâ¶Ÿâ¶ â¶¡â¶¢â¶£â¶¤â¶¥â¶¦â¶§â¶¨â¶©â¶ªâ¶«â¶¬â¶­â¶®â¶¯â¶°â¶±â¶²â¶³â¶´â¶µâ¶¶â¶·â¶¸â¶¹â¶ºâ¶»â¶¼â¶½â¶¾â¶¿â·€â·â·‚ⷃⷄⷅⷆ⷇ⷈⷉⷊⷋⷌâ·â·Žâ·â·â·‘ⷒⷓⷔⷕⷖ⷗ⷘⷙⷚⷛⷜâ·â·žâ·Ÿâ· â·¡â·¢â·£â·¤â·¥â·¦â·§â·¨â·©â·ªâ·«â·¬â·­â·®â·¯â·°â·±â·²â·³â·´â·µâ·¶â··â·¸â·¹â·ºâ·»â·¼â·½â·¾â·¿â¸€â¸â¸‚⸃⸄⸅⸆⸇⸈⸉⸊⸋⸌â¸â¸Žâ¸â¸â¸‘⸒⸓⸔⸕⸖⸗⸘⸙⸚⸛⸜â¸â¸žâ¸Ÿâ¸ â¸¡","⸢⸣⸤⸥⸦⸧⸨⸩⸪⸫⸬⸭⸮ⸯ⸰⸱⸲⸳⸴⸵⸶⸷⸸⸹⸺⸻⸼⸽⸾⸿⹀â¹â¹‚⹃⹄⹅⹆⹇⹈⹉⹊⹋⹌â¹â¹Žâ¹â¹â¹‘⹒⹓⹔⹕⹖⹗⹘⹙⹚⹛⹜â¹â¹žâ¹Ÿâ¹ â¹¡â¹¢â¹£â¹¤â¹¥â¹¦â¹§â¹¨â¹©â¹ªâ¹«â¹¬â¹­â¹®â¹¯â¹°â¹±â¹²â¹³â¹´â¹µâ¹¶â¹·â¹¸â¹¹â¹ºâ¹»â¹¼â¹½â¹¾â¹¿âº€âºâº‚⺃⺄⺅⺆⺇⺈⺉⺊⺋⺌âºâºŽâºâºâº‘⺒⺓⺔⺕⺖⺗⺘⺙⺚⺛⺜âºâºžâºŸâº âº¡âº¢âº£âº¤âº¥âº¦âº§âº¨âº©âºªâº«âº¬âº­âº®âº¯âº°âº±âº²âº³âº´âºµâº¶âº·âº¸âº¹âººâº»âº¼âº½âº¾âº¿â»€â»â»‚⻃⻄⻅⻆⻇⻈⻉⻊⻋⻌â»â»Žâ»â»â»‘⻒⻓⻔⻕⻖⻗⻘⻙⻚⻛⻜â»â»žâ»Ÿâ» â»¡â»¢â»£â»¤â»¥â»¦â»§â»¨â»©â»ªâ»«â»¬â»­â»®â»¯â»°â»±â»²â»³â»´â»µâ»¶â»·â»¸â»¹â»ºâ»»â»¼â»½â»¾â»¿â¼€â¼â¼‚⼃⼄⼅⼆⼇⼈⼉⼊⼋⼌â¼â¼Žâ¼â¼â¼‘⼒⼓⼔⼕⼖⼗⼘⼙⼚⼛⼜â¼â¼žâ¼Ÿâ¼ â¼¡","⼢⼣⼤⼥⼦⼧⼨⼩⼪⼫⼬⼭⼮⼯⼰⼱⼲⼳⼴⼵⼶⼷⼸⼹⼺⼻⼼⼽⼾⼿⽀â½â½‚⽃⽄⽅⽆⽇⽈⽉⽊⽋⽌â½â½Žâ½â½â½‘⽒⽓⽔⽕⽖⽗⽘⽙⽚⽛⽜â½â½žâ½Ÿâ½ â½¡â½¢â½£â½¤â½¥â½¦â½§â½¨â½©â½ªâ½«â½¬â½­â½®â½¯â½°â½±â½²â½³â½´â½µâ½¶â½·â½¸â½¹â½ºâ½»â½¼â½½â½¾â½¿â¾€â¾â¾‚⾃⾄⾅⾆⾇⾈⾉⾊⾋⾌â¾â¾Žâ¾â¾â¾‘⾒⾓⾔⾕⾖⾗⾘⾙⾚⾛⾜â¾â¾žâ¾Ÿâ¾ â¾¡â¾¢â¾£â¾¤â¾¥â¾¦â¾§â¾¨â¾©â¾ªâ¾«â¾¬â¾­â¾®â¾¯â¾°â¾±â¾²â¾³â¾´â¾µâ¾¶â¾·â¾¸â¾¹â¾ºâ¾»â¾¼â¾½â¾¾â¾¿â¿€â¿â¿‚⿃⿄⿅⿆⿇⿈⿉⿊⿋⿌â¿â¿Žâ¿â¿â¿‘⿒⿓⿔⿕⿖⿗⿘⿙⿚⿛⿜â¿â¿žâ¿Ÿâ¿ â¿¡â¿¢â¿£â¿¤â¿¥â¿¦â¿§â¿¨â¿©â¿ªâ¿«â¿¬â¿­â¿®â¿¯â¿°â¿±â¿²â¿³â¿´â¿µâ¿¶â¿·â¿¸â¿¹â¿ºâ¿»â¿¼â¿½â¿¾â¿¿ã€€ã€ã€‚〃〄々〆〇〈〉《》「ã€ã€Žã€ã€ã€‘〒〓〔〕〖〗〘〙〚〛〜ã€ã€žã€Ÿã€ ã€¡","〢〣〤〥〦〧〨〩〪〭〮〯〫〬〰〱〲〳〴〵〶〷〸〹〺〻〼〽〾〿ã€ãã‚ãƒã„ã…ã†ã‡ãˆã‰ãŠã‹ãŒããŽããã‘ã’ã“ã”ã•ã–ã—ã˜ã™ãšã›ãœããžãŸã ã¡ã¢ã£ã¤ã¥ã¦ã§ã¨ã©ãªã«ã¬ã­ã®ã¯ã°ã±ã²ã³ã´ãµã¶ã·ã¸ã¹ãºã»ã¼ã½ã¾ã¿ã‚€ã‚もゃやゅゆょよらりるれã‚ã‚Žã‚ã‚ゑをんゔゕゖ゗゘゙゚゛゜ã‚ゞゟ゠ァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダãƒãƒ‚ッツヅテデトドナニヌãƒãƒŽãƒãƒãƒ‘ヒビピフブプヘベペホボãƒãƒžãƒŸãƒ ãƒ¡ãƒ¢ãƒ£ãƒ¤ãƒ¥ãƒ¦ãƒ§ãƒ¨ãƒ©ãƒªãƒ«ãƒ¬ãƒ­ãƒ®ãƒ¯ãƒ°ãƒ±ãƒ²ãƒ³ãƒ´ãƒµãƒ¶ãƒ·ãƒ¸ãƒ¹ãƒºãƒ»ãƒ¼ãƒ½ãƒ¾ãƒ¿ã„€ã„㄂㄃㄄ㄅㄆㄇㄈㄉㄊㄋㄌã„ã„Žã„ã„ㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜã„ã„žã„Ÿã„ ã„¡","ㄢㄣㄤㄥㄦㄧㄨㄩㄪㄫㄬㄭㄮㄯ㄰ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿㅀã…ㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌã…ã…Žã…ã…ㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜã…ㅞㅟㅠㅡㅢㅣㅤㅥㅦㅧㅨㅩㅪㅫㅬㅭㅮㅯㅰㅱㅲㅳㅴㅵㅶㅷㅸㅹㅺㅻㅼㅽㅾㅿㆀã†ã†‚ㆃㆄㆅㆆㆇㆈㆉㆊㆋㆌã†ã†Žã†ã†ã†‘㆒㆓㆔㆕㆖㆗㆘㆙㆚㆛㆜ã†ã†žã†Ÿã† ã†¡ã†¢ã†£ã†¤ã†¥ã†¦ã†§ã†¨ã†©ã†ªã†«ã†¬ã†­ã†®ã†¯ã†°ã†±ã†²ã†³ã†´ã†µã†¶ã†·ã†¸ã†¹ã†ºã†»ã†¼ã†½ã†¾ã†¿ã‡€ã‡ã‡‚㇃㇄㇅㇆㇇㇈㇉㇊㇋㇌ã‡ã‡Žã‡ã‡ã‡‘㇒㇓㇔㇕㇖㇗㇘㇙㇚㇛㇜ã‡ã‡žã‡Ÿã‡ ã‡¡ã‡¢ã‡£ã‡¤ã‡¥ã‡¦ã‡§ã‡¨ã‡©ã‡ªã‡«ã‡¬ã‡­ã‡®ã‡¯ã‡°ã‡±ã‡²ã‡³ã‡´ã‡µã‡¶ã‡·ã‡¸ã‡¹ã‡ºã‡»ã‡¼ã‡½ã‡¾ã‡¿ãˆ€ãˆãˆ‚㈃㈄㈅㈆㈇㈈㈉㈊㈋㈌ãˆãˆŽãˆãˆãˆ‘㈒㈓㈔㈕㈖㈗㈘㈙㈚㈛㈜ãˆãˆžãˆŸãˆ ãˆ¡","㈢㈣㈤㈥㈦㈧㈨㈩㈪㈫㈬㈭㈮㈯㈰㈱㈲㈳㈴㈵㈶㈷㈸㈹㈺㈻㈼㈽㈾㈿㉀ã‰ã‰‚㉃㉄㉅㉆㉇㉈㉉㉊㉋㉌ã‰ã‰Žã‰ã‰ã‰‘㉒㉓㉔㉕㉖㉗㉘㉙㉚㉛㉜ã‰ã‰žã‰Ÿã‰ ã‰¡ã‰¢ã‰£ã‰¤ã‰¥ã‰¦ã‰§ã‰¨ã‰©ã‰ªã‰«ã‰¬ã‰­ã‰®ã‰¯ã‰°ã‰±ã‰²ã‰³ã‰´ã‰µã‰¶ã‰·ã‰¸ã‰¹ã‰ºã‰»ã‰¼ã‰½ã‰¾ã‰¿ãŠ€ãŠãŠ‚㊃㊄㊅㊆㊇㊈㊉㊊㊋㊌ãŠãŠŽãŠãŠãŠ‘㊒㊓㊔㊕㊖㊗㊘㊙㊚㊛㊜ãŠãŠžãŠŸãŠ ãŠ¡ãŠ¢ãŠ£ãŠ¤ãŠ¥ãŠ¦ãŠ§ãŠ¨ãŠ©ãŠªãŠ«ãŠ¬ãŠ­ãŠ®ãŠ¯ãŠ°ãŠ±ãŠ²ãŠ³ãŠ´ãŠµãŠ¶ãŠ·ãŠ¸ãŠ¹ãŠºãŠ»ãŠ¼ãŠ½ãŠ¾ãŠ¿ã‹€ã‹ã‹‚㋃㋄㋅㋆㋇㋈㋉㋊㋋㋌ã‹ã‹Žã‹ã‹ã‹‘㋒㋓㋔㋕㋖㋗㋘㋙㋚㋛㋜ã‹ã‹žã‹Ÿã‹ ã‹¡ã‹¢ã‹£ã‹¤ã‹¥ã‹¦ã‹§ã‹¨ã‹©ã‹ªã‹«ã‹¬ã‹­ã‹®ã‹¯ã‹°ã‹±ã‹²ã‹³ã‹´ã‹µã‹¶ã‹·ã‹¸ã‹¹ã‹ºã‹»ã‹¼ã‹½ã‹¾ã‹¿ãŒ€ãŒãŒ‚㌃㌄㌅㌆㌇㌈㌉㌊㌋㌌ãŒãŒŽãŒãŒãŒ‘㌒㌓㌔㌕㌖㌗㌘㌙㌚㌛㌜ãŒãŒžãŒŸãŒ ãŒ¡","㌢㌣㌤㌥㌦㌧㌨㌩㌪㌫㌬㌭㌮㌯㌰㌱㌲㌳㌴㌵㌶㌷㌸㌹㌺㌻㌼㌽㌾㌿ã€ãã‚ãƒã„ã…ã†ã‡ãˆã‰ãŠã‹ãŒããŽããã‘ã’ã“ã”ã•ã–ã—ã˜ã™ãšã›ãœããžãŸã ã¡ã¢ã£ã¤ã¥ã¦ã§ã¨ã©ãªã«ã¬ã­ã®ã¯ã°ã±ã²ã³ã´ãµã¶ã·ã¸ã¹ãºã»ã¼ã½ã¾ã¿ãŽ€ãŽãŽ‚㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌ãŽãŽŽãŽãŽãŽ‘㎒㎓㎔㎕㎖㎗㎘㎙㎚㎛㎜ãŽãŽžãŽŸãŽ ãŽ¡ãŽ¢ãŽ£ãŽ¤ãŽ¥ãŽ¦ãŽ§ãŽ¨ãŽ©ãŽªãŽ«ãŽ¬ãŽ­ãŽ®ãŽ¯ãŽ°ãŽ±ãŽ²ãŽ³ãŽ´ãŽµãŽ¶ãŽ·ãŽ¸ãŽ¹ãŽºãŽ»ãŽ¼ãŽ½ãŽ¾ãŽ¿ã€ãã‚ãƒã„ã…ã†ã‡ãˆã‰ãŠã‹ãŒããŽããã‘ã’ã“ã”ã•ã–ã—ã˜ã™ãšã›ãœããžãŸã ã¡ã¢ã£ã¤ã¥ã¦ã§ã¨ã©ãªã«ã¬ã­ã®ã¯ã°ã±ã²ã³ã´ãµã¶ã·ã¸ã¹ãºã»ã¼ã½ã¾ã¿ã€ãã‚ãƒã„ã…ã†ã‡ãˆã‰ãŠã‹ãŒããŽããã‘ã’ã“ã”ã•ã–ã—ã˜ã™ãšã›ãœããžãŸã ã¡","ã¢ã£ã¤ã¥ã¦ã§ã¨ã©ãªã«ã¬ã­ã®ã¯ã°ã±ã²ã³ã´ãµã¶ã·ã¸ã¹ãºã»ã¼ã½ã¾ã¿ã‘€ã‘㑂㑃㑄㑅㑆㑇㑈㑉㑊㑋㑌ã‘ã‘Žã‘ã‘㑑㑒㑓㑔㑕㑖㑗㑘㑙㑚㑛㑜ã‘㑞㑟㑠㑡㑢㑣㑤㑥㑦㑧㑨㑩㑪㑫㑬㑭㑮㑯㑰㑱㑲㑳㑴㑵㑶㑷㑸㑹㑺㑻㑼㑽㑾㑿㒀ã’㒂㒃㒄㒅㒆㒇㒈㒉㒊㒋㒌ã’ã’Žã’ã’㒑㒒㒓㒔㒕㒖㒗㒘㒙㒚㒛㒜ã’㒞㒟㒠㒡㒢㒣㒤㒥㒦㒧㒨㒩㒪㒫㒬㒭㒮㒯㒰㒱㒲㒳㒴㒵㒶㒷㒸㒹㒺㒻㒼㒽㒾㒿㓀ã“㓂㓃㓄㓅㓆㓇㓈㓉㓊㓋㓌ã“ã“Žã“ã“㓑㓒㓓㓔㓕㓖㓗㓘㓙㓚㓛㓜ã“㓞㓟㓠㓡㓢㓣㓤㓥㓦㓧㓨㓩㓪㓫㓬㓭㓮㓯㓰㓱㓲㓳㓴㓵㓶㓷㓸㓹㓺㓻㓼㓽㓾㓿㔀ã”㔂㔃㔄㔅㔆㔇㔈㔉㔊㔋㔌ã”㔎ã”ã”㔑㔒㔓㔔㔕㔖㔗㔘㔙㔚㔛㔜ã”㔞㔟㔠㔡","㔢㔣㔤㔥㔦㔧㔨㔩㔪㔫㔬㔭㔮㔯㔰㔱㔲㔳㔴㔵㔶㔷㔸㔹㔺㔻㔼㔽㔾㔿㕀ã•ã•‚㕃㕄㕅㕆㕇㕈㕉㕊㕋㕌ã•ã•Žã•ã•ã•‘㕒㕓㕔㕕㕖㕗㕘㕙㕚㕛㕜ã•ã•žã•Ÿã• ã•¡ã•¢ã•£ã•¤ã•¥ã•¦ã•§ã•¨ã•©ã•ªã•«ã•¬ã•­ã•®ã•¯ã•°ã•±ã•²ã•³ã•´ã•µã•¶ã•·ã•¸ã•¹ã•ºã•»ã•¼ã•½ã•¾ã•¿ã–€ã–㖂㖃㖄㖅㖆㖇㖈㖉㖊㖋㖌ã–ã–Žã–ã–㖑㖒㖓㖔㖕㖖㖗㖘㖙㖚㖛㖜ã–㖞㖟㖠㖡㖢㖣㖤㖥㖦㖧㖨㖩㖪㖫㖬㖭㖮㖯㖰㖱㖲㖳㖴㖵㖶㖷㖸㖹㖺㖻㖼㖽㖾㖿㗀ã—㗂㗃㗄㗅㗆㗇㗈㗉㗊㗋㗌ã—ã—Žã—ã—㗑㗒㗓㗔㗕㗖㗗㗘㗙㗚㗛㗜ã—㗞㗟㗠㗡㗢㗣㗤㗥㗦㗧㗨㗩㗪㗫㗬㗭㗮㗯㗰㗱㗲㗳㗴㗵㗶㗷㗸㗹㗺㗻㗼㗽㗾㗿㘀ã˜ã˜‚㘃㘄㘅㘆㘇㘈㘉㘊㘋㘌ã˜ã˜Žã˜ã˜ã˜‘㘒㘓㘔㘕㘖㘗㘘㘙㘚㘛㘜ã˜ã˜žã˜Ÿã˜ ã˜¡","㘢㘣㘤㘥㘦㘧㘨㘩㘪㘫㘬㘭㘮㘯㘰㘱㘲㘳㘴㘵㘶㘷㘸㘹㘺㘻㘼㘽㘾㘿㙀ã™ã™‚㙃㙄㙅㙆㙇㙈㙉㙊㙋㙌ã™ã™Žã™ã™ã™‘㙒㙓㙔㙕㙖㙗㙘㙙㙚㙛㙜ã™ã™žã™Ÿã™ ã™¡ã™¢ã™£ã™¤ã™¥ã™¦ã™§ã™¨ã™©ã™ªã™«ã™¬ã™­ã™®ã™¯ã™°ã™±ã™²ã™³ã™´ã™µã™¶ã™·ã™¸ã™¹ã™ºã™»ã™¼ã™½ã™¾ã™¿ãš€ãšãš‚㚃㚄㚅㚆㚇㚈㚉㚊㚋㚌ãšãšŽãšãšãš‘㚒㚓㚔㚕㚖㚗㚘㚙㚚㚛㚜ãšãšžãšŸãš ãš¡ãš¢ãš£ãš¤ãš¥ãš¦ãš§ãš¨ãš©ãšªãš«ãš¬ãš­ãš®ãš¯ãš°ãš±ãš²ãš³ãš´ãšµãš¶ãš·ãš¸ãš¹ãšºãš»ãš¼ãš½ãš¾ãš¿ã›€ã›ã›‚㛃㛄㛅㛆㛇㛈㛉㛊㛋㛌ã›ã›Žã›ã›ã›‘㛒㛓㛔㛕㛖㛗㛘㛙㛚㛛㛜ã›ã›žã›Ÿã› ã›¡ã›¢ã›£ã›¤ã›¥ã›¦ã›§ã›¨ã›©ã›ªã›«ã›¬ã›­ã›®ã›¯ã›°ã›±ã›²ã›³ã›´ã›µã›¶ã›·ã›¸ã›¹ã›ºã›»ã›¼ã›½ã›¾ã›¿ãœ€ãœãœ‚㜃㜄㜅㜆㜇㜈㜉㜊㜋㜌ãœãœŽãœãœãœ‘㜒㜓㜔㜕㜖㜗㜘㜙㜚㜛㜜ãœãœžãœŸãœ ãœ¡","㜢㜣㜤㜥㜦㜧㜨㜩㜪㜫㜬㜭㜮㜯㜰㜱㜲㜳㜴㜵㜶㜷㜸㜹㜺㜻㜼㜽㜾㜿ã€ãã‚ãƒã„ã…ã†ã‡ãˆã‰ãŠã‹ãŒããŽããã‘ã’ã“ã”ã•ã–ã—ã˜ã™ãšã›ãœããžãŸã ã¡ã¢ã£ã¤ã¥ã¦ã§ã¨ã©ãªã«ã¬ã­ã®ã¯ã°ã±ã²ã³ã´ãµã¶ã·ã¸ã¹ãºã»ã¼ã½ã¾ã¿ãž€ãžãž‚㞃㞄㞅㞆㞇㞈㞉㞊㞋㞌ãžãžŽãžãžãž‘㞒㞓㞔㞕㞖㞗㞘㞙㞚㞛㞜ãžãžžãžŸãž ãž¡ãž¢ãž£ãž¤ãž¥ãž¦ãž§ãž¨ãž©ãžªãž«ãž¬ãž­ãž®ãž¯ãž°ãž±ãž²ãž³ãž´ãžµãž¶ãž·ãž¸ãž¹ãžºãž»ãž¼ãž½ãž¾ãž¿ãŸ€ãŸãŸ‚㟃㟄㟅㟆㟇㟈㟉㟊㟋㟌ãŸãŸŽãŸãŸãŸ‘㟒㟓㟔㟕㟖㟗㟘㟙㟚㟛㟜ãŸãŸžãŸŸãŸ ãŸ¡ãŸ¢ãŸ£ãŸ¤ãŸ¥ãŸ¦ãŸ§ãŸ¨ãŸ©ãŸªãŸ«ãŸ¬ãŸ­ãŸ®ãŸ¯ãŸ°ãŸ±ãŸ²ãŸ³ãŸ´ãŸµãŸ¶ãŸ·ãŸ¸ãŸ¹ãŸºãŸ»ãŸ¼ãŸ½ãŸ¾ãŸ¿ã €ã ã ‚㠃㠄㠅㠆㠇㠈㠉㠊㠋㠌ã ã Žã ã ã ‘㠒㠓㠔㠕㠖㠗㠘㠙㠚㠛㠜ã ã žã Ÿã  ã ¡","㠢㠣㠤㠥㠦㠧㠨㠩㠪㠫㠬㠭㠮㠯㠰㠱㠲㠳㠴㠵㠶㠷㠸㠹㠺㠻㠼㠽㠾㠿㡀ã¡ã¡‚㡃㡄㡅㡆㡇㡈㡉㡊㡋㡌ã¡ã¡Žã¡ã¡ã¡‘㡒㡓㡔㡕㡖㡗㡘㡙㡚㡛㡜ã¡ã¡žã¡Ÿã¡ ã¡¡ã¡¢ã¡£ã¡¤ã¡¥ã¡¦ã¡§ã¡¨ã¡©ã¡ªã¡«ã¡¬ã¡­ã¡®ã¡¯ã¡°ã¡±ã¡²ã¡³ã¡´ã¡µã¡¶ã¡·ã¡¸ã¡¹ã¡ºã¡»ã¡¼ã¡½ã¡¾ã¡¿ã¢€ã¢ã¢‚㢃㢄㢅㢆㢇㢈㢉㢊㢋㢌ã¢ã¢Žã¢ã¢ã¢‘㢒㢓㢔㢕㢖㢗㢘㢙㢚㢛㢜ã¢ã¢žã¢Ÿã¢ ã¢¡ã¢¢ã¢£ã¢¤ã¢¥ã¢¦ã¢§ã¢¨ã¢©ã¢ªã¢«ã¢¬ã¢­ã¢®ã¢¯ã¢°ã¢±ã¢²ã¢³ã¢´ã¢µã¢¶ã¢·ã¢¸ã¢¹ã¢ºã¢»ã¢¼ã¢½ã¢¾ã¢¿ã£€ã£ã£‚㣃㣄㣅㣆㣇㣈㣉㣊㣋㣌ã£ã£Žã£ã£ã£‘㣒㣓㣔㣕㣖㣗㣘㣙㣚㣛㣜ã£ã£žã£Ÿã£ ã£¡ã£¢ã££ã£¤ã£¥ã£¦ã£§ã£¨ã£©ã£ªã£«ã£¬ã£­ã£®ã£¯ã£°ã£±ã£²ã£³ã£´ã£µã£¶ã£·ã£¸ã£¹ã£ºã£»ã£¼ã£½ã£¾ã£¿ã¤€ã¤ã¤‚㤃㤄㤅㤆㤇㤈㤉㤊㤋㤌ã¤ã¤Žã¤ã¤ã¤‘㤒㤓㤔㤕㤖㤗㤘㤙㤚㤛㤜ã¤ã¤žã¤Ÿã¤ ã¤¡","㤢㤣㤤㤥㤦㤧㤨㤩㤪㤫㤬㤭㤮㤯㤰㤱㤲㤳㤴㤵㤶㤷㤸㤹㤺㤻㤼㤽㤾㤿㥀ã¥ã¥‚㥃㥄㥅㥆㥇㥈㥉㥊㥋㥌ã¥ã¥Žã¥ã¥ã¥‘㥒㥓㥔㥕㥖㥗㥘㥙㥚㥛㥜ã¥ã¥žã¥Ÿã¥ ã¥¡ã¥¢ã¥£ã¥¤ã¥¥ã¥¦ã¥§ã¥¨ã¥©ã¥ªã¥«ã¥¬ã¥­ã¥®ã¥¯ã¥°ã¥±ã¥²ã¥³ã¥´ã¥µã¥¶ã¥·ã¥¸ã¥¹ã¥ºã¥»ã¥¼ã¥½ã¥¾ã¥¿ã¦€ã¦ã¦‚㦃㦄㦅㦆㦇㦈㦉㦊㦋㦌ã¦ã¦Žã¦ã¦ã¦‘㦒㦓㦔㦕㦖㦗㦘㦙㦚㦛㦜ã¦ã¦žã¦Ÿã¦ ã¦¡ã¦¢ã¦£ã¦¤ã¦¥ã¦¦ã¦§ã¦¨ã¦©ã¦ªã¦«ã¦¬ã¦­ã¦®ã¦¯ã¦°ã¦±ã¦²ã¦³ã¦´ã¦µã¦¶ã¦·ã¦¸ã¦¹ã¦ºã¦»ã¦¼ã¦½ã¦¾ã¦¿ã§€ã§ã§‚㧃㧄㧅㧆㧇㧈㧉㧊㧋㧌ã§ã§Žã§ã§ã§‘㧒㧓㧔㧕㧖㧗㧘㧙㧚㧛㧜ã§ã§žã§Ÿã§ ã§¡ã§¢ã§£ã§¤ã§¥ã§¦ã§§ã§¨ã§©ã§ªã§«ã§¬ã§­ã§®ã§¯ã§°ã§±ã§²ã§³ã§´ã§µã§¶ã§·ã§¸ã§¹ã§ºã§»ã§¼ã§½ã§¾ã§¿ã¨€ã¨ã¨‚㨃㨄㨅㨆㨇㨈㨉㨊㨋㨌ã¨ã¨Žã¨ã¨ã¨‘㨒㨓㨔㨕㨖㨗㨘㨙㨚㨛㨜ã¨ã¨žã¨Ÿã¨ ã¨¡","㨢㨣㨤㨥㨦㨧㨨㨩㨪㨫㨬㨭㨮㨯㨰㨱㨲㨳㨴㨵㨶㨷㨸㨹㨺㨻㨼㨽㨾㨿㩀ã©ã©‚㩃㩄㩅㩆㩇㩈㩉㩊㩋㩌ã©ã©Žã©ã©ã©‘㩒㩓㩔㩕㩖㩗㩘㩙㩚㩛㩜ã©ã©žã©Ÿã© ã©¡ã©¢ã©£ã©¤ã©¥ã©¦ã©§ã©¨ã©©ã©ªã©«ã©¬ã©­ã©®ã©¯ã©°ã©±ã©²ã©³ã©´ã©µã©¶ã©·ã©¸ã©¹ã©ºã©»ã©¼ã©½ã©¾ã©¿ãª€ãªãª‚㪃㪄㪅㪆㪇㪈㪉㪊㪋㪌ãªãªŽãªãªãª‘㪒㪓㪔㪕㪖㪗㪘㪙㪚㪛㪜ãªãªžãªŸãª ãª¡ãª¢ãª£ãª¤ãª¥ãª¦ãª§ãª¨ãª©ãªªãª«ãª¬ãª­ãª®ãª¯ãª°ãª±ãª²ãª³ãª´ãªµãª¶ãª·ãª¸ãª¹ãªºãª»ãª¼ãª½ãª¾ãª¿ã«€ã«ã«‚㫃㫄㫅㫆㫇㫈㫉㫊㫋㫌ã«ã«Žã«ã«ã«‘㫒㫓㫔㫕㫖㫗㫘㫙㫚㫛㫜ã«ã«žã«Ÿã« ã«¡ã«¢ã«£ã«¤ã«¥ã«¦ã«§ã«¨ã«©ã«ªã««ã«¬ã«­ã«®ã«¯ã«°ã«±ã«²ã«³ã«´ã«µã«¶ã«·ã«¸ã«¹ã«ºã«»ã«¼ã«½ã«¾ã«¿ã¬€ã¬ã¬‚㬃㬄㬅㬆㬇㬈㬉㬊㬋㬌ã¬ã¬Žã¬ã¬ã¬‘㬒㬓㬔㬕㬖㬗㬘㬙㬚㬛㬜ã¬ã¬žã¬Ÿã¬ ã¬¡","㬢㬣㬤㬥㬦㬧㬨㬩㬪㬫㬬㬭㬮㬯㬰㬱㬲㬳㬴㬵㬶㬷㬸㬹㬺㬻㬼㬽㬾㬿㭀ã­ã­‚㭃㭄㭅㭆㭇㭈㭉㭊㭋㭌ã­ã­Žã­ã­ã­‘㭒㭓㭔㭕㭖㭗㭘㭙㭚㭛㭜ã­ã­žã­Ÿã­ ã­¡ã­¢ã­£ã­¤ã­¥ã­¦ã­§ã­¨ã­©ã­ªã­«ã­¬ã­­ã­®ã­¯ã­°ã­±ã­²ã­³ã­´ã­µã­¶ã­·ã­¸ã­¹ã­ºã­»ã­¼ã­½ã­¾ã­¿ã®€ã®ã®‚㮃㮄㮅㮆㮇㮈㮉㮊㮋㮌ã®ã®Žã®ã®ã®‘㮒㮓㮔㮕㮖㮗㮘㮙㮚㮛㮜ã®ã®žã®Ÿã® ã®¡ã®¢ã®£ã®¤ã®¥ã®¦ã®§ã®¨ã®©ã®ªã®«ã®¬ã®­ã®®ã®¯ã®°ã®±ã®²ã®³ã®´ã®µã®¶ã®·ã®¸ã®¹ã®ºã®»ã®¼ã®½ã®¾ã®¿ã¯€ã¯ã¯‚㯃㯄㯅㯆㯇㯈㯉㯊㯋㯌ã¯ã¯Žã¯ã¯ã¯‘㯒㯓㯔㯕㯖㯗㯘㯙㯚㯛㯜ã¯ã¯žã¯Ÿã¯ ã¯¡ã¯¢ã¯£ã¯¤ã¯¥ã¯¦ã¯§ã¯¨ã¯©ã¯ªã¯«ã¯¬ã¯­ã¯®ã¯¯ã¯°ã¯±ã¯²ã¯³ã¯´ã¯µã¯¶ã¯·ã¯¸ã¯¹ã¯ºã¯»ã¯¼ã¯½ã¯¾ã¯¿ã°€ã°ã°‚㰃㰄㰅㰆㰇㰈㰉㰊㰋㰌ã°ã°Žã°ã°ã°‘㰒㰓㰔㰕㰖㰗㰘㰙㰚㰛㰜ã°ã°žã°Ÿã° ã°¡","㰢㰣㰤㰥㰦㰧㰨㰩㰪㰫㰬㰭㰮㰯㰰㰱㰲㰳㰴㰵㰶㰷㰸㰹㰺㰻㰼㰽㰾㰿㱀ã±ã±‚㱃㱄㱅㱆㱇㱈㱉㱊㱋㱌ã±ã±Žã±ã±ã±‘㱒㱓㱔㱕㱖㱗㱘㱙㱚㱛㱜ã±ã±žã±Ÿã± ã±¡ã±¢ã±£ã±¤ã±¥ã±¦ã±§ã±¨ã±©ã±ªã±«ã±¬ã±­ã±®ã±¯ã±°ã±±ã±²ã±³ã±´ã±µã±¶ã±·ã±¸ã±¹ã±ºã±»ã±¼ã±½ã±¾ã±¿ã²€ã²ã²‚㲃㲄㲅㲆㲇㲈㲉㲊㲋㲌ã²ã²Žã²ã²ã²‘㲒㲓㲔㲕㲖㲗㲘㲙㲚㲛㲜ã²ã²žã²Ÿã² ã²¡ã²¢ã²£ã²¤ã²¥ã²¦ã²§ã²¨ã²©ã²ªã²«ã²¬ã²­ã²®ã²¯ã²°ã²±ã²²ã²³ã²´ã²µã²¶ã²·ã²¸ã²¹ã²ºã²»ã²¼ã²½ã²¾ã²¿ã³€ã³ã³‚㳃㳄㳅㳆㳇㳈㳉㳊㳋㳌ã³ã³Žã³ã³ã³‘㳒㳓㳔㳕㳖㳗㳘㳙㳚㳛㳜ã³ã³žã³Ÿã³ ã³¡ã³¢ã³£ã³¤ã³¥ã³¦ã³§ã³¨ã³©ã³ªã³«ã³¬ã³­ã³®ã³¯ã³°ã³±ã³²ã³³ã³´ã³µã³¶ã³·ã³¸ã³¹ã³ºã³»ã³¼ã³½ã³¾ã³¿ã´€ã´ã´‚㴃㴄㴅㴆㴇㴈㴉㴊㴋㴌ã´ã´Žã´ã´ã´‘㴒㴓㴔㴕㴖㴗㴘㴙㴚㴛㴜ã´ã´žã´Ÿã´ ã´¡","㴢㴣㴤㴥㴦㴧㴨㴩㴪㴫㴬㴭㴮㴯㴰㴱㴲㴳㴴㴵㴶㴷㴸㴹㴺㴻㴼㴽㴾㴿㵀ãµãµ‚㵃㵄㵅㵆㵇㵈㵉㵊㵋㵌ãµãµŽãµãµãµ‘㵒㵓㵔㵕㵖㵗㵘㵙㵚㵛㵜ãµãµžãµŸãµ ãµ¡ãµ¢ãµ£ãµ¤ãµ¥ãµ¦ãµ§ãµ¨ãµ©ãµªãµ«ãµ¬ãµ­ãµ®ãµ¯ãµ°ãµ±ãµ²ãµ³ãµ´ãµµãµ¶ãµ·ãµ¸ãµ¹ãµºãµ»ãµ¼ãµ½ãµ¾ãµ¿ã¶€ã¶ã¶‚㶃㶄㶅㶆㶇㶈㶉㶊㶋㶌ã¶ã¶Žã¶ã¶ã¶‘㶒㶓㶔㶕㶖㶗㶘㶙㶚㶛㶜ã¶ã¶žã¶Ÿã¶ ã¶¡ã¶¢ã¶£ã¶¤ã¶¥ã¶¦ã¶§ã¶¨ã¶©ã¶ªã¶«ã¶¬ã¶­ã¶®ã¶¯ã¶°ã¶±ã¶²ã¶³ã¶´ã¶µã¶¶ã¶·ã¶¸ã¶¹ã¶ºã¶»ã¶¼ã¶½ã¶¾ã¶¿ã·€ã·ã·‚㷃㷄㷅㷆㷇㷈㷉㷊㷋㷌ã·ã·Žã·ã·ã·‘㷒㷓㷔㷕㷖㷗㷘㷙㷚㷛㷜ã·ã·žã·Ÿã· ã·¡ã·¢ã·£ã·¤ã·¥ã·¦ã·§ã·¨ã·©ã·ªã·«ã·¬ã·­ã·®ã·¯ã·°ã·±ã·²ã·³ã·´ã·µã·¶ã··ã·¸ã·¹ã·ºã·»ã·¼ã·½ã·¾ã·¿ã¸€ã¸ã¸‚㸃㸄㸅㸆㸇㸈㸉㸊㸋㸌ã¸ã¸Žã¸ã¸ã¸‘㸒㸓㸔㸕㸖㸗㸘㸙㸚㸛㸜ã¸ã¸žã¸Ÿã¸ ã¸¡","㸢㸣㸤㸥㸦㸧㸨㸩㸪㸫㸬㸭㸮㸯㸰㸱㸲㸳㸴㸵㸶㸷㸸㸹㸺㸻㸼㸽㸾㸿㹀ã¹ã¹‚㹃㹄㹅㹆㹇㹈㹉㹊㹋㹌ã¹ã¹Žã¹ã¹ã¹‘㹒㹓㹔㹕㹖㹗㹘㹙㹚㹛㹜ã¹ã¹žã¹Ÿã¹ ã¹¡ã¹¢ã¹£ã¹¤ã¹¥ã¹¦ã¹§ã¹¨ã¹©ã¹ªã¹«ã¹¬ã¹­ã¹®ã¹¯ã¹°ã¹±ã¹²ã¹³ã¹´ã¹µã¹¶ã¹·ã¹¸ã¹¹ã¹ºã¹»ã¹¼ã¹½ã¹¾ã¹¿ãº€ãºãº‚㺃㺄㺅㺆㺇㺈㺉㺊㺋㺌ãºãºŽãºãºãº‘㺒㺓㺔㺕㺖㺗㺘㺙㺚㺛㺜ãºãºžãºŸãº ãº¡ãº¢ãº£ãº¤ãº¥ãº¦ãº§ãº¨ãº©ãºªãº«ãº¬ãº­ãº®ãº¯ãº°ãº±ãº²ãº³ãº´ãºµãº¶ãº·ãº¸ãº¹ãººãº»ãº¼ãº½ãº¾ãº¿ã»€ã»ã»‚㻃㻄㻅㻆㻇㻈㻉㻊㻋㻌ã»ã»Žã»ã»ã»‘㻒㻓㻔㻕㻖㻗㻘㻙㻚㻛㻜ã»ã»žã»Ÿã» ã»¡ã»¢ã»£ã»¤ã»¥ã»¦ã»§ã»¨ã»©ã»ªã»«ã»¬ã»­ã»®ã»¯ã»°ã»±ã»²ã»³ã»´ã»µã»¶ã»·ã»¸ã»¹ã»ºã»»ã»¼ã»½ã»¾ã»¿ã¼€ã¼ã¼‚㼃㼄㼅㼆㼇㼈㼉㼊㼋㼌ã¼ã¼Žã¼ã¼ã¼‘㼒㼓㼔㼕㼖㼗㼘㼙㼚㼛㼜ã¼ã¼žã¼Ÿã¼ ã¼¡","㼢㼣㼤㼥㼦㼧㼨㼩㼪㼫㼬㼭㼮㼯㼰㼱㼲㼳㼴㼵㼶㼷㼸㼹㼺㼻㼼㼽㼾㼿㽀ã½ã½‚㽃㽄㽅㽆㽇㽈㽉㽊㽋㽌ã½ã½Žã½ã½ã½‘㽒㽓㽔㽕㽖㽗㽘㽙㽚㽛㽜ã½ã½žã½Ÿã½ ã½¡ã½¢ã½£ã½¤ã½¥ã½¦ã½§ã½¨ã½©ã½ªã½«ã½¬ã½­ã½®ã½¯ã½°ã½±ã½²ã½³ã½´ã½µã½¶ã½·ã½¸ã½¹ã½ºã½»ã½¼ã½½ã½¾ã½¿ã¾€ã¾ã¾‚㾃㾄㾅㾆㾇㾈㾉㾊㾋㾌ã¾ã¾Žã¾ã¾ã¾‘㾒㾓㾔㾕㾖㾗㾘㾙㾚㾛㾜ã¾ã¾žã¾Ÿã¾ ã¾¡ã¾¢ã¾£ã¾¤ã¾¥ã¾¦ã¾§ã¾¨ã¾©ã¾ªã¾«ã¾¬ã¾­ã¾®ã¾¯ã¾°ã¾±ã¾²ã¾³ã¾´ã¾µã¾¶ã¾·ã¾¸ã¾¹ã¾ºã¾»ã¾¼ã¾½ã¾¾ã¾¿ã¿€ã¿ã¿‚㿃㿄㿅㿆㿇㿈㿉㿊㿋㿌ã¿ã¿Žã¿ã¿ã¿‘㿒㿓㿔㿕㿖㿗㿘㿙㿚㿛㿜ã¿ã¿žã¿Ÿã¿ ã¿¡ã¿¢ã¿£ã¿¤ã¿¥ã¿¦ã¿§ã¿¨ã¿©ã¿ªã¿«ã¿¬ã¿­ã¿®ã¿¯ã¿°ã¿±ã¿²ã¿³ã¿´ã¿µã¿¶ã¿·ã¿¸ã¿¹ã¿ºã¿»ã¿¼ã¿½ã¿¾ã¿¿ä€€ä€ä€‚䀃䀄䀅䀆䀇䀈䀉䀊䀋䀌ä€ä€Žä€ä€ä€‘䀒䀓䀔䀕䀖䀗䀘䀙䀚䀛䀜ä€ä€žä€Ÿä€ ä€¡","䀢䀣䀤䀥䀦䀧䀨䀩䀪䀫䀬䀭䀮䀯䀰䀱䀲䀳䀴䀵䀶䀷䀸䀹䀺䀻䀼䀽䀾䀿ä€ää‚äƒä„ä…ä†ä‡äˆä‰äŠä‹äŒääŽäää‘ä’ä“ä”ä•ä–ä—ä˜ä™äšä›äœääžäŸä ä¡ä¢ä£ä¤ä¥ä¦ä§ä¨ä©äªä«ä¬ä­ä®ä¯ä°ä±ä²ä³ä´äµä¶ä·ä¸ä¹äºä»ä¼ä½ä¾ä¿ä‚€ä‚䂂䂃䂄䂅䂆䂇䂈䂉䂊䂋䂌ä‚ä‚Žä‚ä‚䂑䂒䂓䂔䂕䂖䂗䂘䂙䂚䂛䂜ä‚䂞䂟䂠䂡䂢䂣䂤䂥䂦䂧䂨䂩䂪䂫䂬䂭䂮䂯䂰䂱䂲䂳䂴䂵䂶䂷䂸䂹䂺䂻䂼䂽䂾䂿䃀äƒäƒ‚䃃䃄䃅䃆䃇䃈䃉䃊䃋䃌äƒäƒŽäƒäƒäƒ‘䃒䃓䃔䃕䃖䃗䃘䃙䃚䃛䃜äƒäƒžäƒŸäƒ äƒ¡äƒ¢äƒ£äƒ¤äƒ¥äƒ¦äƒ§äƒ¨äƒ©äƒªäƒ«äƒ¬äƒ­äƒ®äƒ¯äƒ°äƒ±äƒ²äƒ³äƒ´äƒµäƒ¶äƒ·äƒ¸äƒ¹äƒºäƒ»äƒ¼äƒ½äƒ¾äƒ¿ä„€ä„䄂䄃䄄䄅䄆䄇䄈䄉䄊䄋䄌ä„ä„Žä„ä„䄑䄒䄓䄔䄕䄖䄗䄘䄙䄚䄛䄜ä„ä„žä„Ÿä„ ä„¡","䄢䄣䄤䄥䄦䄧䄨䄩䄪䄫䄬䄭䄮䄯䄰䄱䄲䄳䄴䄵䄶䄷䄸䄹䄺䄻䄼䄽䄾䄿䅀ä…䅂䅃䅄䅅䅆䅇䅈䅉䅊䅋䅌ä…ä…Žä…ä…䅑䅒䅓䅔䅕䅖䅗䅘䅙䅚䅛䅜ä…䅞䅟䅠䅡䅢䅣䅤䅥䅦䅧䅨䅩䅪䅫䅬䅭䅮䅯䅰䅱䅲䅳䅴䅵䅶䅷䅸䅹䅺䅻䅼䅽䅾䅿䆀ä†ä†‚䆃䆄䆅䆆䆇䆈䆉䆊䆋䆌ä†ä†Žä†ä†ä†‘䆒䆓䆔䆕䆖䆗䆘䆙䆚䆛䆜ä†ä†žä†Ÿä† ä†¡ä†¢ä†£ä†¤ä†¥ä†¦ä†§ä†¨ä†©ä†ªä†«ä†¬ä†­ä†®ä†¯ä†°ä†±ä†²ä†³ä†´ä†µä†¶ä†·ä†¸ä†¹ä†ºä†»ä†¼ä†½ä†¾ä†¿ä‡€ä‡ä‡‚䇃䇄䇅䇆䇇䇈䇉䇊䇋䇌ä‡ä‡Žä‡ä‡ä‡‘䇒䇓䇔䇕䇖䇗䇘䇙䇚䇛䇜ä‡ä‡žä‡Ÿä‡ ä‡¡ä‡¢ä‡£ä‡¤ä‡¥ä‡¦ä‡§ä‡¨ä‡©ä‡ªä‡«ä‡¬ä‡­ä‡®ä‡¯ä‡°ä‡±ä‡²ä‡³ä‡´ä‡µä‡¶ä‡·ä‡¸ä‡¹ä‡ºä‡»ä‡¼ä‡½ä‡¾ä‡¿äˆ€äˆäˆ‚䈃䈄䈅䈆䈇䈈䈉䈊䈋䈌äˆäˆŽäˆäˆäˆ‘䈒䈓䈔䈕䈖䈗䈘䈙䈚䈛䈜äˆäˆžäˆŸäˆ äˆ¡","䈢䈣䈤䈥䈦䈧䈨䈩䈪䈫䈬䈭䈮䈯䈰䈱䈲䈳䈴䈵䈶䈷䈸䈹䈺䈻䈼䈽䈾䈿䉀ä‰ä‰‚䉃䉄䉅䉆䉇䉈䉉䉊䉋䉌ä‰ä‰Žä‰ä‰ä‰‘䉒䉓䉔䉕䉖䉗䉘䉙䉚䉛䉜ä‰ä‰žä‰Ÿä‰ ä‰¡ä‰¢ä‰£ä‰¤ä‰¥ä‰¦ä‰§ä‰¨ä‰©ä‰ªä‰«ä‰¬ä‰­ä‰®ä‰¯ä‰°ä‰±ä‰²ä‰³ä‰´ä‰µä‰¶ä‰·ä‰¸ä‰¹ä‰ºä‰»ä‰¼ä‰½ä‰¾ä‰¿äŠ€äŠäŠ‚䊃䊄䊅䊆䊇䊈䊉䊊䊋䊌äŠäŠŽäŠäŠäŠ‘䊒䊓䊔䊕䊖䊗䊘䊙䊚䊛䊜äŠäŠžäŠŸäŠ äŠ¡äŠ¢äŠ£äŠ¤äŠ¥äŠ¦äŠ§äŠ¨äŠ©äŠªäŠ«äŠ¬äŠ­äŠ®äŠ¯äŠ°äŠ±äŠ²äŠ³äŠ´äŠµäŠ¶äŠ·äŠ¸äŠ¹äŠºäŠ»äŠ¼äŠ½äŠ¾äŠ¿ä‹€ä‹ä‹‚䋃䋄䋅䋆䋇䋈䋉䋊䋋䋌ä‹ä‹Žä‹ä‹ä‹‘䋒䋓䋔䋕䋖䋗䋘䋙䋚䋛䋜ä‹ä‹žä‹Ÿä‹ ä‹¡ä‹¢ä‹£ä‹¤ä‹¥ä‹¦ä‹§ä‹¨ä‹©ä‹ªä‹«ä‹¬ä‹­ä‹®ä‹¯ä‹°ä‹±ä‹²ä‹³ä‹´ä‹µä‹¶ä‹·ä‹¸ä‹¹ä‹ºä‹»ä‹¼ä‹½ä‹¾ä‹¿äŒ€äŒäŒ‚䌃䌄䌅䌆䌇䌈䌉䌊䌋䌌äŒäŒŽäŒäŒäŒ‘䌒䌓䌔䌕䌖䌗䌘䌙䌚䌛䌜äŒäŒžäŒŸäŒ äŒ¡","䌢䌣䌤䌥䌦䌧䌨䌩䌪䌫䌬䌭䌮䌯䌰䌱䌲䌳䌴䌵䌶䌷䌸䌹䌺䌻䌼䌽䌾䌿ä€ää‚äƒä„ä…ä†ä‡äˆä‰äŠä‹äŒääŽäää‘ä’ä“ä”ä•ä–ä—ä˜ä™äšä›äœääžäŸä ä¡ä¢ä£ä¤ä¥ä¦ä§ä¨ä©äªä«ä¬ä­ä®ä¯ä°ä±ä²ä³ä´äµä¶ä·ä¸ä¹äºä»ä¼ä½ä¾ä¿äŽ€äŽäŽ‚䎃䎄䎅䎆䎇䎈䎉䎊䎋䎌äŽäŽŽäŽäŽäŽ‘䎒䎓䎔䎕䎖䎗䎘䎙䎚䎛䎜äŽäŽžäŽŸäŽ äŽ¡äŽ¢äŽ£äŽ¤äŽ¥äŽ¦äŽ§äŽ¨äŽ©äŽªäŽ«äŽ¬äŽ­äŽ®äŽ¯äŽ°äŽ±äŽ²äŽ³äŽ´äŽµäŽ¶äŽ·äŽ¸äŽ¹äŽºäŽ»äŽ¼äŽ½äŽ¾äŽ¿ä€ää‚äƒä„ä…ä†ä‡äˆä‰äŠä‹äŒääŽäää‘ä’ä“ä”ä•ä–ä—ä˜ä™äšä›äœääžäŸä ä¡ä¢ä£ä¤ä¥ä¦ä§ä¨ä©äªä«ä¬ä­ä®ä¯ä°ä±ä²ä³ä´äµä¶ä·ä¸ä¹äºä»ä¼ä½ä¾ä¿ä€ää‚äƒä„ä…ä†ä‡äˆä‰äŠä‹äŒääŽäää‘ä’ä“ä”ä•ä–ä—ä˜ä™äšä›äœääžäŸä ä¡","ä¢ä£ä¤ä¥ä¦ä§ä¨ä©äªä«ä¬ä­ä®ä¯ä°ä±ä²ä³ä´äµä¶ä·ä¸ä¹äºä»ä¼ä½ä¾ä¿ä‘€ä‘䑂䑃䑄䑅䑆䑇䑈䑉䑊䑋䑌ä‘ä‘Žä‘ä‘䑑䑒䑓䑔䑕䑖䑗䑘䑙䑚䑛䑜ä‘䑞䑟䑠䑡䑢䑣䑤䑥䑦䑧䑨䑩䑪䑫䑬䑭䑮䑯䑰䑱䑲䑳䑴䑵䑶䑷䑸䑹䑺䑻䑼䑽䑾䑿䒀ä’䒂䒃䒄䒅䒆䒇䒈䒉䒊䒋䒌ä’ä’Žä’ä’䒑䒒䒓䒔䒕䒖䒗䒘䒙䒚䒛䒜ä’䒞䒟䒠䒡䒢䒣䒤䒥䒦䒧䒨䒩䒪䒫䒬䒭䒮䒯䒰䒱䒲䒳䒴䒵䒶䒷䒸䒹䒺䒻䒼䒽䒾䒿䓀ä“䓂䓃䓄䓅䓆䓇䓈䓉䓊䓋䓌ä“ä“Žä“ä“䓑䓒䓓䓔䓕䓖䓗䓘䓙䓚䓛䓜ä“䓞䓟䓠䓡䓢䓣䓤䓥䓦䓧䓨䓩䓪䓫䓬䓭䓮䓯䓰䓱䓲䓳䓴䓵䓶䓷䓸䓹䓺䓻䓼䓽䓾䓿䔀ä”䔂䔃䔄䔅䔆䔇䔈䔉䔊䔋䔌ä”䔎ä”ä”䔑䔒䔓䔔䔕䔖䔗䔘䔙䔚䔛䔜ä”䔞䔟䔠䔡","䔢䔣䔤䔥䔦䔧䔨䔩䔪䔫䔬䔭䔮䔯䔰䔱䔲䔳䔴䔵䔶䔷䔸䔹䔺䔻䔼䔽䔾䔿䕀ä•ä•‚䕃䕄䕅䕆䕇䕈䕉䕊䕋䕌ä•ä•Žä•ä•ä•‘䕒䕓䕔䕕䕖䕗䕘䕙䕚䕛䕜ä•ä•žä•Ÿä• ä•¡ä•¢ä•£ä•¤ä•¥ä•¦ä•§ä•¨ä•©ä•ªä•«ä•¬ä•­ä•®ä•¯ä•°ä•±ä•²ä•³ä•´ä•µä•¶ä•·ä•¸ä•¹ä•ºä•»ä•¼ä•½ä•¾ä•¿ä–€ä–䖂䖃䖄䖅䖆䖇䖈䖉䖊䖋䖌ä–ä–Žä–ä–䖑䖒䖓䖔䖕䖖䖗䖘䖙䖚䖛䖜ä–䖞䖟䖠䖡䖢䖣䖤䖥䖦䖧䖨䖩䖪䖫䖬䖭䖮䖯䖰䖱䖲䖳䖴䖵䖶䖷䖸䖹䖺䖻䖼䖽䖾䖿䗀ä—䗂䗃䗄䗅䗆䗇䗈䗉䗊䗋䗌ä—ä—Žä—ä—䗑䗒䗓䗔䗕䗖䗗䗘䗙䗚䗛䗜ä—䗞䗟䗠䗡䗢䗣䗤䗥䗦䗧䗨䗩䗪䗫䗬䗭䗮䗯䗰䗱䗲䗳䗴䗵䗶䗷䗸䗹䗺䗻䗼䗽䗾䗿䘀ä˜ä˜‚䘃䘄䘅䘆䘇䘈䘉䘊䘋䘌ä˜ä˜Žä˜ä˜ä˜‘䘒䘓䘔䘕䘖䘗䘘䘙䘚䘛䘜ä˜ä˜žä˜Ÿä˜ ä˜¡","䘢䘣䘤䘥䘦䘧䘨䘩䘪䘫䘬䘭䘮䘯䘰䘱䘲䘳䘴䘵䘶䘷䘸䘹䘺䘻䘼䘽䘾䘿䙀ä™ä™‚䙃䙄䙅䙆䙇䙈䙉䙊䙋䙌ä™ä™Žä™ä™ä™‘䙒䙓䙔䙕䙖䙗䙘䙙䙚䙛䙜ä™ä™žä™Ÿä™ ä™¡ä™¢ä™£ä™¤ä™¥ä™¦ä™§ä™¨ä™©ä™ªä™«ä™¬ä™­ä™®ä™¯ä™°ä™±ä™²ä™³ä™´ä™µä™¶ä™·ä™¸ä™¹ä™ºä™»ä™¼ä™½ä™¾ä™¿äš€äšäš‚䚃䚄䚅䚆䚇䚈䚉䚊䚋䚌äšäšŽäšäšäš‘䚒䚓䚔䚕䚖䚗䚘䚙䚚䚛䚜äšäšžäšŸäš äš¡äš¢äš£äš¤äš¥äš¦äš§äš¨äš©äšªäš«äš¬äš­äš®äš¯äš°äš±äš²äš³äš´äšµäš¶äš·äš¸äš¹äšºäš»äš¼äš½äš¾äš¿ä›€ä›ä›‚䛃䛄䛅䛆䛇䛈䛉䛊䛋䛌ä›ä›Žä›ä›ä›‘䛒䛓䛔䛕䛖䛗䛘䛙䛚䛛䛜ä›ä›žä›Ÿä› ä›¡ä›¢ä›£ä›¤ä›¥ä›¦ä›§ä›¨ä›©ä›ªä›«ä›¬ä›­ä›®ä›¯ä›°ä›±ä›²ä›³ä›´ä›µä›¶ä›·ä›¸ä›¹ä›ºä›»ä›¼ä›½ä›¾ä›¿äœ€äœäœ‚䜃䜄䜅䜆䜇䜈䜉䜊䜋䜌äœäœŽäœäœäœ‘䜒䜓䜔䜕䜖䜗䜘䜙䜚䜛䜜äœäœžäœŸäœ äœ¡","䜢䜣䜤䜥䜦䜧䜨䜩䜪䜫䜬䜭䜮䜯䜰䜱䜲䜳䜴䜵䜶䜷䜸䜹䜺䜻䜼䜽䜾䜿ä€ää‚äƒä„ä…ä†ä‡äˆä‰äŠä‹äŒääŽäää‘ä’ä“ä”ä•ä–ä—ä˜ä™äšä›äœääžäŸä ä¡ä¢ä£ä¤ä¥ä¦ä§ä¨ä©äªä«ä¬ä­ä®ä¯ä°ä±ä²ä³ä´äµä¶ä·ä¸ä¹äºä»ä¼ä½ä¾ä¿äž€äžäž‚䞃䞄䞅䞆䞇䞈䞉䞊䞋䞌äžäžŽäžäžäž‘䞒䞓䞔䞕䞖䞗䞘䞙䞚䞛䞜äžäžžäžŸäž äž¡äž¢äž£äž¤äž¥äž¦äž§äž¨äž©äžªäž«äž¬äž­äž®äž¯äž°äž±äž²äž³äž´äžµäž¶äž·äž¸äž¹äžºäž»äž¼äž½äž¾äž¿äŸ€äŸäŸ‚䟃䟄䟅䟆䟇䟈䟉䟊䟋䟌äŸäŸŽäŸäŸäŸ‘䟒䟓䟔䟕䟖䟗䟘䟙䟚䟛䟜äŸäŸžäŸŸäŸ äŸ¡äŸ¢äŸ£äŸ¤äŸ¥äŸ¦äŸ§äŸ¨äŸ©äŸªäŸ«äŸ¬äŸ­äŸ®äŸ¯äŸ°äŸ±äŸ²äŸ³äŸ´äŸµäŸ¶äŸ·äŸ¸äŸ¹äŸºäŸ»äŸ¼äŸ½äŸ¾äŸ¿ä €ä ä ‚䠃䠄䠅䠆䠇䠈䠉䠊䠋䠌ä ä Žä ä ä ‘䠒䠓䠔䠕䠖䠗䠘䠙䠚䠛䠜ä ä žä Ÿä  ä ¡","䠢䠣䠤䠥䠦䠧䠨䠩䠪䠫䠬䠭䠮䠯䠰䠱䠲䠳䠴䠵䠶䠷䠸䠹䠺䠻䠼䠽䠾䠿䡀ä¡ä¡‚䡃䡄䡅䡆䡇䡈䡉䡊䡋䡌ä¡ä¡Žä¡ä¡ä¡‘䡒䡓䡔䡕䡖䡗䡘䡙䡚䡛䡜ä¡ä¡žä¡Ÿä¡ ä¡¡ä¡¢ä¡£ä¡¤ä¡¥ä¡¦ä¡§ä¡¨ä¡©ä¡ªä¡«ä¡¬ä¡­ä¡®ä¡¯ä¡°ä¡±ä¡²ä¡³ä¡´ä¡µä¡¶ä¡·ä¡¸ä¡¹ä¡ºä¡»ä¡¼ä¡½ä¡¾ä¡¿ä¢€ä¢ä¢‚䢃䢄䢅䢆䢇䢈䢉䢊䢋䢌ä¢ä¢Žä¢ä¢ä¢‘䢒䢓䢔䢕䢖䢗䢘䢙䢚䢛䢜ä¢ä¢žä¢Ÿä¢ ä¢¡ä¢¢ä¢£ä¢¤ä¢¥ä¢¦ä¢§ä¢¨ä¢©ä¢ªä¢«ä¢¬ä¢­ä¢®ä¢¯ä¢°ä¢±ä¢²ä¢³ä¢´ä¢µä¢¶ä¢·ä¢¸ä¢¹ä¢ºä¢»ä¢¼ä¢½ä¢¾ä¢¿ä£€ä£ä£‚䣃䣄䣅䣆䣇䣈䣉䣊䣋䣌ä£ä£Žä£ä£ä£‘䣒䣓䣔䣕䣖䣗䣘䣙䣚䣛䣜ä£ä£žä£Ÿä£ ä£¡ä£¢ä££ä£¤ä£¥ä£¦ä£§ä£¨ä£©ä£ªä£«ä£¬ä£­ä£®ä£¯ä£°ä£±ä£²ä£³ä£´ä£µä£¶ä£·ä£¸ä£¹ä£ºä£»ä£¼ä£½ä£¾ä£¿ä¤€ä¤ä¤‚䤃䤄䤅䤆䤇䤈䤉䤊䤋䤌ä¤ä¤Žä¤ä¤ä¤‘䤒䤓䤔䤕䤖䤗䤘䤙䤚䤛䤜ä¤ä¤žä¤Ÿä¤ ä¤¡","䤢䤣䤤䤥䤦䤧䤨䤩䤪䤫䤬䤭䤮䤯䤰䤱䤲䤳䤴䤵䤶䤷䤸䤹䤺䤻䤼䤽䤾䤿䥀ä¥ä¥‚䥃䥄䥅䥆䥇䥈䥉䥊䥋䥌ä¥ä¥Žä¥ä¥ä¥‘䥒䥓䥔䥕䥖䥗䥘䥙䥚䥛䥜ä¥ä¥žä¥Ÿä¥ ä¥¡ä¥¢ä¥£ä¥¤ä¥¥ä¥¦ä¥§ä¥¨ä¥©ä¥ªä¥«ä¥¬ä¥­ä¥®ä¥¯ä¥°ä¥±ä¥²ä¥³ä¥´ä¥µä¥¶ä¥·ä¥¸ä¥¹ä¥ºä¥»ä¥¼ä¥½ä¥¾ä¥¿ä¦€ä¦ä¦‚䦃䦄䦅䦆䦇䦈䦉䦊䦋䦌ä¦ä¦Žä¦ä¦ä¦‘䦒䦓䦔䦕䦖䦗䦘䦙䦚䦛䦜ä¦ä¦žä¦Ÿä¦ ä¦¡ä¦¢ä¦£ä¦¤ä¦¥ä¦¦ä¦§ä¦¨ä¦©ä¦ªä¦«ä¦¬ä¦­ä¦®ä¦¯ä¦°ä¦±ä¦²ä¦³ä¦´ä¦µä¦¶ä¦·ä¦¸ä¦¹ä¦ºä¦»ä¦¼ä¦½ä¦¾ä¦¿ä§€ä§ä§‚䧃䧄䧅䧆䧇䧈䧉䧊䧋䧌ä§ä§Žä§ä§ä§‘䧒䧓䧔䧕䧖䧗䧘䧙䧚䧛䧜ä§ä§žä§Ÿä§ ä§¡ä§¢ä§£ä§¤ä§¥ä§¦ä§§ä§¨ä§©ä§ªä§«ä§¬ä§­ä§®ä§¯ä§°ä§±ä§²ä§³ä§´ä§µä§¶ä§·ä§¸ä§¹ä§ºä§»ä§¼ä§½ä§¾ä§¿ä¨€ä¨ä¨‚䨃䨄䨅䨆䨇䨈䨉䨊䨋䨌ä¨ä¨Žä¨ä¨ä¨‘䨒䨓䨔䨕䨖䨗䨘䨙䨚䨛䨜ä¨ä¨žä¨Ÿä¨ ä¨¡","䨢䨣䨤䨥䨦䨧䨨䨩䨪䨫䨬䨭䨮䨯䨰䨱䨲䨳䨴䨵䨶䨷䨸䨹䨺䨻䨼䨽䨾䨿䩀ä©ä©‚䩃䩄䩅䩆䩇䩈䩉䩊䩋䩌ä©ä©Žä©ä©ä©‘䩒䩓䩔䩕䩖䩗䩘䩙䩚䩛䩜ä©ä©žä©Ÿä© ä©¡ä©¢ä©£ä©¤ä©¥ä©¦ä©§ä©¨ä©©ä©ªä©«ä©¬ä©­ä©®ä©¯ä©°ä©±ä©²ä©³ä©´ä©µä©¶ä©·ä©¸ä©¹ä©ºä©»ä©¼ä©½ä©¾ä©¿äª€äªäª‚䪃䪄䪅䪆䪇䪈䪉䪊䪋䪌äªäªŽäªäªäª‘䪒䪓䪔䪕䪖䪗䪘䪙䪚䪛䪜äªäªžäªŸäª äª¡äª¢äª£äª¤äª¥äª¦äª§äª¨äª©äªªäª«äª¬äª­äª®äª¯äª°äª±äª²äª³äª´äªµäª¶äª·äª¸äª¹äªºäª»äª¼äª½äª¾äª¿ä«€ä«ä«‚䫃䫄䫅䫆䫇䫈䫉䫊䫋䫌ä«ä«Žä«ä«ä«‘䫒䫓䫔䫕䫖䫗䫘䫙䫚䫛䫜ä«ä«žä«Ÿä« ä«¡ä«¢ä«£ä«¤ä«¥ä«¦ä«§ä«¨ä«©ä«ªä««ä«¬ä«­ä«®ä«¯ä«°ä«±ä«²ä«³ä«´ä«µä«¶ä«·ä«¸ä«¹ä«ºä«»ä«¼ä«½ä«¾ä«¿ä¬€ä¬ä¬‚䬃䬄䬅䬆䬇䬈䬉䬊䬋䬌ä¬ä¬Žä¬ä¬ä¬‘䬒䬓䬔䬕䬖䬗䬘䬙䬚䬛䬜ä¬ä¬žä¬Ÿä¬ ä¬¡","䬢䬣䬤䬥䬦䬧䬨䬩䬪䬫䬬䬭䬮䬯䬰䬱䬲䬳䬴䬵䬶䬷䬸䬹䬺䬻䬼䬽䬾䬿䭀ä­ä­‚䭃䭄䭅䭆䭇䭈䭉䭊䭋䭌ä­ä­Žä­ä­ä­‘䭒䭓䭔䭕䭖䭗䭘䭙䭚䭛䭜ä­ä­žä­Ÿä­ ä­¡ä­¢ä­£ä­¤ä­¥ä­¦ä­§ä­¨ä­©ä­ªä­«ä­¬ä­­ä­®ä­¯ä­°ä­±ä­²ä­³ä­´ä­µä­¶ä­·ä­¸ä­¹ä­ºä­»ä­¼ä­½ä­¾ä­¿ä®€ä®ä®‚䮃䮄䮅䮆䮇䮈䮉䮊䮋䮌ä®ä®Žä®ä®ä®‘䮒䮓䮔䮕䮖䮗䮘䮙䮚䮛䮜ä®ä®žä®Ÿä® ä®¡ä®¢ä®£ä®¤ä®¥ä®¦ä®§ä®¨ä®©ä®ªä®«ä®¬ä®­ä®®ä®¯ä®°ä®±ä®²ä®³ä®´ä®µä®¶ä®·ä®¸ä®¹ä®ºä®»ä®¼ä®½ä®¾ä®¿ä¯€ä¯ä¯‚䯃䯄䯅䯆䯇䯈䯉䯊䯋䯌ä¯ä¯Žä¯ä¯ä¯‘䯒䯓䯔䯕䯖䯗䯘䯙䯚䯛䯜ä¯ä¯žä¯Ÿä¯ ä¯¡ä¯¢ä¯£ä¯¤ä¯¥ä¯¦ä¯§ä¯¨ä¯©ä¯ªä¯«ä¯¬ä¯­ä¯®ä¯¯ä¯°ä¯±ä¯²ä¯³ä¯´ä¯µä¯¶ä¯·ä¯¸ä¯¹ä¯ºä¯»ä¯¼ä¯½ä¯¾ä¯¿ä°€ä°ä°‚䰃䰄䰅䰆䰇䰈䰉䰊䰋䰌ä°ä°Žä°ä°ä°‘䰒䰓䰔䰕䰖䰗䰘䰙䰚䰛䰜ä°ä°žä°Ÿä° ä°¡","䰢䰣䰤䰥䰦䰧䰨䰩䰪䰫䰬䰭䰮䰯䰰䰱䰲䰳䰴䰵䰶䰷䰸䰹䰺䰻䰼䰽䰾䰿䱀ä±ä±‚䱃䱄䱅䱆䱇䱈䱉䱊䱋䱌ä±ä±Žä±ä±ä±‘䱒䱓䱔䱕䱖䱗䱘䱙䱚䱛䱜ä±ä±žä±Ÿä± ä±¡ä±¢ä±£ä±¤ä±¥ä±¦ä±§ä±¨ä±©ä±ªä±«ä±¬ä±­ä±®ä±¯ä±°ä±±ä±²ä±³ä±´ä±µä±¶ä±·ä±¸ä±¹ä±ºä±»ä±¼ä±½ä±¾ä±¿ä²€ä²ä²‚䲃䲄䲅䲆䲇䲈䲉䲊䲋䲌ä²ä²Žä²ä²ä²‘䲒䲓䲔䲕䲖䲗䲘䲙䲚䲛䲜ä²ä²žä²Ÿä² ä²¡ä²¢ä²£ä²¤ä²¥ä²¦ä²§ä²¨ä²©ä²ªä²«ä²¬ä²­ä²®ä²¯ä²°ä²±ä²²ä²³ä²´ä²µä²¶ä²·ä²¸ä²¹ä²ºä²»ä²¼ä²½ä²¾ä²¿ä³€ä³ä³‚䳃䳄䳅䳆䳇䳈䳉䳊䳋䳌ä³ä³Žä³ä³ä³‘䳒䳓䳔䳕䳖䳗䳘䳙䳚䳛䳜ä³ä³žä³Ÿä³ ä³¡ä³¢ä³£ä³¤ä³¥ä³¦ä³§ä³¨ä³©ä³ªä³«ä³¬ä³­ä³®ä³¯ä³°ä³±ä³²ä³³ä³´ä³µä³¶ä³·ä³¸ä³¹ä³ºä³»ä³¼ä³½ä³¾ä³¿ä´€ä´ä´‚䴃䴄䴅䴆䴇䴈䴉䴊䴋䴌ä´ä´Žä´ä´ä´‘䴒䴓䴔䴕䴖䴗䴘䴙䴚䴛䴜ä´ä´žä´Ÿä´ ä´¡","䴢䴣䴤䴥䴦䴧䴨䴩䴪䴫䴬䴭䴮䴯䴰䴱䴲䴳䴴䴵䴶䴷䴸䴹䴺䴻䴼䴽䴾䴿䵀äµäµ‚䵃䵄䵅䵆䵇䵈䵉䵊䵋䵌äµäµŽäµäµäµ‘䵒䵓䵔䵕䵖䵗䵘䵙䵚䵛䵜äµäµžäµŸäµ äµ¡äµ¢äµ£äµ¤äµ¥äµ¦äµ§äµ¨äµ©äµªäµ«äµ¬äµ­äµ®äµ¯äµ°äµ±äµ²äµ³äµ´äµµäµ¶äµ·äµ¸äµ¹äµºäµ»äµ¼äµ½äµ¾äµ¿ä¶€ä¶ä¶‚䶃䶄䶅䶆䶇䶈䶉䶊䶋䶌ä¶ä¶Žä¶ä¶ä¶‘䶒䶓䶔䶕䶖䶗䶘䶙䶚䶛䶜ä¶ä¶žä¶Ÿä¶ ä¶¡ä¶¢ä¶£ä¶¤ä¶¥ä¶¦ä¶§ä¶¨ä¶©ä¶ªä¶«ä¶¬ä¶­ä¶®ä¶¯ä¶°ä¶±ä¶²ä¶³ä¶´ä¶µä¶¶ä¶·ä¶¸ä¶¹ä¶ºä¶»ä¶¼ä¶½ä¶¾ä¶¿ä·€ä·ä·‚䷃䷄䷅䷆䷇䷈䷉䷊䷋䷌ä·ä·Žä·ä·ä·‘䷒䷓䷔䷕䷖䷗䷘䷙䷚䷛䷜ä·ä·žä·Ÿä· ä·¡ä·¢ä·£ä·¤ä·¥ä·¦ä·§ä·¨ä·©ä·ªä·«ä·¬ä·­ä·®ä·¯ä·°ä·±ä·²ä·³ä·´ä·µä·¶ä··ä·¸ä·¹ä·ºä·»ä·¼ä·½ä·¾ä·¿ä¸€ä¸ä¸‚七丄丅丆万丈三上下丌ä¸ä¸Žä¸ä¸ä¸‘丒专且丕世丗丘丙业丛东ä¸ä¸žä¸Ÿä¸ ä¸¡","丢丣两严並丧丨丩个丫丬中丮丯丰丱串丳临丵丶丷丸丹为主丼丽举丿乀ä¹ä¹‚乃乄久乆乇么义乊之乌ä¹ä¹Žä¹ä¹ä¹‘乒乓乔乕乖乗乘乙乚乛乜ä¹ä¹žä¹Ÿä¹ ä¹¡ä¹¢ä¹£ä¹¤ä¹¥ä¹¦ä¹§ä¹¨ä¹©ä¹ªä¹«ä¹¬ä¹­ä¹®ä¹¯ä¹°ä¹±ä¹²ä¹³ä¹´ä¹µä¹¶ä¹·ä¹¸ä¹¹ä¹ºä¹»ä¹¼ä¹½ä¹¾ä¹¿äº€äºäº‚亃亄亅了亇予争亊事二äºäºŽäºäºäº‘互亓五井亖亗亘亙亚些亜äºäºžäºŸäº äº¡äº¢äº£äº¤äº¥äº¦äº§äº¨äº©äºªäº«äº¬äº­äº®äº¯äº°äº±äº²äº³äº´äºµäº¶äº·äº¸äº¹äººäº»äº¼äº½äº¾äº¿ä»€ä»ä»‚仃仄仅仆仇仈仉今介仌ä»ä»Žä»ä»ä»‘仒仓仔仕他仗付仙仚仛仜ä»ä»žä»Ÿä» ä»¡ä»¢ä»£ä»¤ä»¥ä»¦ä»§ä»¨ä»©ä»ªä»«ä»¬ä»­ä»®ä»¯ä»°ä»±ä»²ä»³ä»´ä»µä»¶ä»·ä»¸ä»¹ä»ºä»»ä»¼ä»½ä»¾ä»¿ä¼€ä¼ä¼‚伃伄伅伆伇伈伉伊伋伌ä¼ä¼Žä¼ä¼ä¼‘伒伓伔伕伖众优伙会伛伜ä¼ä¼žä¼Ÿä¼ ä¼¡","伢伣伤伥伦伧伨伩伪伫伬伭伮伯估伱伲伳伴伵伶伷伸伹伺伻似伽伾伿佀ä½ä½‚佃佄佅但佇佈佉佊佋佌ä½ä½Žä½ä½ä½‘佒体佔何佖佗佘余佚佛作ä½ä½žä½Ÿä½ ä½¡ä½¢ä½£ä½¤ä½¥ä½¦ä½§ä½¨ä½©ä½ªä½«ä½¬ä½­ä½®ä½¯ä½°ä½±ä½²ä½³ä½´ä½µä½¶ä½·ä½¸ä½¹ä½ºä½»ä½¼ä½½ä½¾ä½¿ä¾€ä¾ä¾‚侃侄侅來侇侈侉侊例侌ä¾ä¾Žä¾ä¾ä¾‘侒侓侔侕侖侗侘侙侚供侜ä¾ä¾žä¾Ÿä¾ ä¾¡ä¾¢ä¾£ä¾¤ä¾¥ä¾¦ä¾§ä¾¨ä¾©ä¾ªä¾«ä¾¬ä¾­ä¾®ä¾¯ä¾°ä¾±ä¾²ä¾³ä¾´ä¾µä¾¶ä¾·ä¾¸ä¾¹ä¾ºä¾»ä¾¼ä¾½ä¾¾ä¾¿ä¿€ä¿ä¿‚促俄俅俆俇俈俉俊俋俌ä¿ä¿Žä¿ä¿ä¿‘俒俓俔俕俖俗俘俙俚俛俜ä¿ä¿žä¿Ÿä¿ ä¿¡ä¿¢ä¿£ä¿¤ä¿¥ä¿¦ä¿§ä¿¨ä¿©ä¿ªä¿«ä¿¬ä¿­ä¿®ä¿¯ä¿°ä¿±ä¿²ä¿³ä¿´ä¿µä¿¶ä¿·ä¿¸ä¿¹ä¿ºä¿»ä¿¼ä¿½ä¿¾ä¿¿å€€å€å€‚倃倄倅倆倇倈倉倊個倌å€å€Žå€å€å€‘倒倓倔倕倖倗倘候倚倛倜å€å€žå€Ÿå€ å€¡","倢倣値倥倦倧倨倩倪倫倬倭倮倯倰倱倲倳倴倵倶倷倸倹债倻值倽倾倿å€åå‚åƒå„å…å†å‡åˆå‰åŠå‹åŒååŽååå‘å’å“å”å•å–å—å˜å™åšå›åœååžåŸå å¡å¢å£å¤å¥å¦å§å¨å©åªå«å¬å­å®å¯å°å±å²å³å´åµå¶å·å¸å¹åºå»å¼å½å¾å¿å‚€å‚傂傃傄傅傆傇傈傉傊傋傌å‚å‚Žå‚å‚傑傒傓傔傕傖傗傘備傚傛傜å‚傞傟傠傡傢傣傤傥傦傧储傩傪傫催傭傮傯傰傱傲傳傴債傶傷傸傹傺傻傼傽傾傿僀åƒåƒ‚僃僄僅僆僇僈僉僊僋僌åƒåƒŽåƒåƒåƒ‘僒僓僔僕僖僗僘僙僚僛僜åƒåƒžåƒŸåƒ åƒ¡åƒ¢åƒ£åƒ¤åƒ¥åƒ¦åƒ§åƒ¨åƒ©åƒªåƒ«åƒ¬åƒ­åƒ®åƒ¯åƒ°åƒ±åƒ²åƒ³åƒ´åƒµåƒ¶åƒ·åƒ¸åƒ¹åƒºåƒ»åƒ¼åƒ½åƒ¾åƒ¿å„€å„儂儃億儅儆儇儈儉儊儋儌å„å„Žå„å„儑儒儓儔儕儖儗儘儙儚儛儜å„å„žå„Ÿå„ å„¡","儢儣儤儥儦儧儨儩優儫儬儭儮儯儰儱儲儳儴儵儶儷儸儹儺儻儼儽儾儿兀å…兂元兄充兆兇先光兊克兌å…å…Žå…å…兑兒兓兔兕兖兗兘兙党兛兜å…兞兟兠兡兢兣兤入兦內全兩兪八公六兮兯兰共兲关兴兵其具典兹兺养兼兽兾兿冀å†å†‚冃冄内円冇冈冉冊冋册å†å†Žå†å†å†‘冒冓冔冕冖冗冘写冚军农å†å†žå†Ÿå† å†¡å†¢å†£å†¤å†¥å†¦å†§å†¨å†©å†ªå†«å†¬å†­å†®å†¯å†°å†±å†²å†³å†´å†µå†¶å†·å†¸å†¹å†ºå†»å†¼å†½å†¾å†¿å‡€å‡å‡‚凃凄凅准凇凈凉凊凋凌å‡å‡Žå‡å‡å‡‘凒凓凔凕凖凗凘凙凚凛凜å‡å‡žå‡Ÿå‡ å‡¡å‡¢å‡£å‡¤å‡¥å‡¦å‡§å‡¨å‡©å‡ªå‡«å‡¬å‡­å‡®å‡¯å‡°å‡±å‡²å‡³å‡´å‡µå‡¶å‡·å‡¸å‡¹å‡ºå‡»å‡¼å‡½å‡¾å‡¿åˆ€åˆåˆ‚刃刄刅分切刈刉刊刋刌åˆåˆŽåˆåˆåˆ‘划刓刔刕刖列刘则刚创刜åˆåˆžåˆŸåˆ åˆ¡","刢刣判別刦刧刨利刪别刬刭刮刯到刱刲刳刴刵制刷券刹刺刻刼刽刾刿剀å‰å‰‚剃剄剅剆則剈剉削剋剌å‰å‰Žå‰å‰å‰‘剒剓剔剕剖剗剘剙剚剛剜å‰å‰žå‰Ÿå‰ å‰¡å‰¢å‰£å‰¤å‰¥å‰¦å‰§å‰¨å‰©å‰ªå‰«å‰¬å‰­å‰®å‰¯å‰°å‰±å‰²å‰³å‰´å‰µå‰¶å‰·å‰¸å‰¹å‰ºå‰»å‰¼å‰½å‰¾å‰¿åŠ€åŠåŠ‚劃劄劅劆劇劈劉劊劋劌åŠåŠŽåŠåŠåŠ‘劒劓劔劕劖劗劘劙劚力劜åŠåŠžåŠŸåŠ åŠ¡åŠ¢åŠ£åŠ¤åŠ¥åŠ¦åŠ§åŠ¨åŠ©åŠªåŠ«åŠ¬åŠ­åŠ®åŠ¯åŠ°åŠ±åŠ²åŠ³åŠ´åŠµåŠ¶åŠ·åŠ¸åŠ¹åŠºåŠ»åŠ¼åŠ½åŠ¾åŠ¿å‹€å‹å‹‚勃勄勅勆勇勈勉勊勋勌å‹å‹Žå‹å‹å‹‘勒勓勔動勖勗勘務勚勛勜å‹å‹žå‹Ÿå‹ å‹¡å‹¢å‹£å‹¤å‹¥å‹¦å‹§å‹¨å‹©å‹ªå‹«å‹¬å‹­å‹®å‹¯å‹°å‹±å‹²å‹³å‹´å‹µå‹¶å‹·å‹¸å‹¹å‹ºå‹»å‹¼å‹½å‹¾å‹¿åŒ€åŒåŒ‚匃匄包匆匇匈匉匊匋匌åŒåŒŽåŒåŒåŒ‘匒匓匔匕化北匘匙匚匛匜åŒåŒžåŒŸåŒ åŒ¡","匢匣匤匥匦匧匨匩匪匫匬匭匮匯匰匱匲匳匴匵匶匷匸匹区医匼匽匾匿å€åå‚åƒå„å…å†å‡åˆå‰åŠå‹åŒååŽååå‘å’å“å”å•å–å—å˜å™åšå›åœååžåŸå å¡å¢å£å¤å¥å¦å§å¨å©åªå«å¬å­å®å¯å°å±å²å³å´åµå¶å·å¸å¹åºå»å¼å½å¾å¿åŽ€åŽåŽ‚厃厄厅历厇厈厉厊压厌åŽåŽŽåŽåŽåŽ‘厒厓厔厕厖厗厘厙厚厛厜åŽåŽžåŽŸåŽ åŽ¡åŽ¢åŽ£åŽ¤åŽ¥åŽ¦åŽ§åŽ¨åŽ©åŽªåŽ«åŽ¬åŽ­åŽ®åŽ¯åŽ°åŽ±åŽ²åŽ³åŽ´åŽµåŽ¶åŽ·åŽ¸åŽ¹åŽºåŽ»åŽ¼åŽ½åŽ¾åŽ¿å€åå‚åƒå„å…å†å‡åˆå‰åŠå‹åŒååŽååå‘å’å“å”å•å–å—å˜å™åšå›åœååžåŸå å¡å¢å£å¤å¥å¦å§å¨å©åªå«å¬å­å®å¯å°å±å²å³å´åµå¶å·å¸å¹åºå»å¼å½å¾å¿å€åå‚åƒå„å…å†å‡åˆå‰åŠå‹åŒååŽååå‘å’å“å”å•å–å—å˜å™åšå›åœååžåŸå å¡","å¢å£å¤å¥å¦å§å¨å©åªå«å¬å­å®å¯å°å±å²å³å´åµå¶å·å¸å¹åºå»å¼å½å¾å¿å‘€å‘呂呃呄呅呆呇呈呉告呋呌å‘å‘Žå‘å‘呑呒呓呔呕呖呗员呙呚呛呜å‘呞呟呠呡呢呣呤呥呦呧周呩呪呫呬呭呮呯呰呱呲味呴呵呶呷呸呹呺呻呼命呾呿咀å’咂咃咄咅咆咇咈咉咊咋和å’å’Žå’å’咑咒咓咔咕咖咗咘咙咚咛咜å’咞咟咠咡咢咣咤咥咦咧咨咩咪咫咬咭咮咯咰咱咲咳咴咵咶咷咸咹咺咻咼咽咾咿哀å“哂哃哄哅哆哇哈哉哊哋哌å“å“Žå“å“哑哒哓哔哕哖哗哘哙哚哛哜å“哞哟哠員哢哣哤哥哦哧哨哩哪哫哬哭哮哯哰哱哲哳哴哵哶哷哸哹哺哻哼哽哾哿唀å”唂唃唄唅唆唇唈唉唊唋唌å”唎å”å”唑唒唓唔唕唖唗唘唙唚唛唜å”唞唟唠唡","唢唣唤唥唦唧唨唩唪唫唬唭售唯唰唱唲唳唴唵唶唷唸唹唺唻唼唽唾唿啀å•å•‚啃啄啅商啇啈啉啊啋啌å•å•Žå•å•å•‘啒啓啔啕啖啗啘啙啚啛啜å•å•žå•Ÿå• å•¡å•¢å•£å•¤å•¥å•¦å•§å•¨å•©å•ªå•«å•¬å•­å•®å•¯å•°å•±å•²å•³å•´å•µå•¶å•·å•¸å•¹å•ºå•»å•¼å•½å•¾å•¿å–€å–喂喃善喅喆喇喈喉喊喋喌å–å–Žå–å–喑喒喓喔喕喖喗喘喙喚喛喜å–喞喟喠喡喢喣喤喥喦喧喨喩喪喫喬喭單喯喰喱喲喳喴喵営喷喸喹喺喻喼喽喾喿嗀å—嗂嗃嗄嗅嗆嗇嗈嗉嗊嗋嗌å—å—Žå—å—嗑嗒嗓嗔嗕嗖嗗嗘嗙嗚嗛嗜å—嗞嗟嗠嗡嗢嗣嗤嗥嗦嗧嗨嗩嗪嗫嗬嗭嗮嗯嗰嗱嗲嗳嗴嗵嗶嗷嗸嗹嗺嗻嗼嗽嗾嗿嘀å˜å˜‚嘃嘄嘅嘆嘇嘈嘉嘊嘋嘌å˜å˜Žå˜å˜å˜‘嘒嘓嘔嘕嘖嘗嘘嘙嘚嘛嘜å˜å˜žå˜Ÿå˜ å˜¡","嘢嘣嘤嘥嘦嘧嘨嘩嘪嘫嘬嘭嘮嘯嘰嘱嘲嘳嘴嘵嘶嘷嘸嘹嘺嘻嘼嘽嘾嘿噀å™å™‚噃噄噅噆噇噈噉噊噋噌å™å™Žå™å™å™‘噒噓噔噕噖噗噘噙噚噛噜å™å™žå™Ÿå™ å™¡å™¢å™£å™¤å™¥å™¦å™§å™¨å™©å™ªå™«å™¬å™­å™®å™¯å™°å™±å™²å™³å™´å™µå™¶å™·å™¸å™¹å™ºå™»å™¼å™½å™¾å™¿åš€åšåš‚嚃嚄嚅嚆嚇嚈嚉嚊嚋嚌åšåšŽåšåšåš‘嚒嚓嚔嚕嚖嚗嚘嚙嚚嚛嚜åšåšžåšŸåš åš¡åš¢åš£åš¤åš¥åš¦åš§åš¨åš©åšªåš«åš¬åš­åš®åš¯åš°åš±åš²åš³åš´åšµåš¶åš·åš¸åš¹åšºåš»åš¼åš½åš¾åš¿å›€å›å›‚囃囄囅囆囇囈囉囊囋囌å›å›Žå›å›å›‘囒囓囔囕囖囗囘囙囚四囜å›å›žå›Ÿå› å›¡å›¢å›£å›¤å›¥å›¦å›§å›¨å›©å›ªå›«å›¬å›­å›®å›¯å›°å›±å›²å›³å›´å›µå›¶å›·å›¸å›¹å›ºå›»å›¼å›½å›¾å›¿åœ€åœåœ‚圃圄圅圆圇圈圉圊國圌åœåœŽåœåœåœ‘園圓圔圕圖圗團圙圚圛圜åœåœžåœŸåœ åœ¡","圢圣圤圥圦圧在圩圪圫圬圭圮圯地圱圲圳圴圵圶圷圸圹场圻圼圽圾圿å€åå‚åƒå„å…å†å‡åˆå‰åŠå‹åŒååŽååå‘å’å“å”å•å–å—å˜å™åšå›åœååžåŸå å¡å¢å£å¤å¥å¦å§å¨å©åªå«å¬å­å®å¯å°å±å²å³å´åµå¶å·å¸å¹åºå»å¼å½å¾å¿åž€åžåž‚垃垄垅垆垇垈垉垊型垌åžåžŽåžåžåž‘垒垓垔垕垖垗垘垙垚垛垜åžåžžåžŸåž åž¡åž¢åž£åž¤åž¥åž¦åž§åž¨åž©åžªåž«åž¬åž­åž®åž¯åž°åž±åž²åž³åž´åžµåž¶åž·åž¸åž¹åžºåž»åž¼åž½åž¾åž¿åŸ€åŸåŸ‚埃埄埅埆埇埈埉埊埋埌åŸåŸŽåŸåŸåŸ‘埒埓埔埕埖埗埘埙埚埛埜åŸåŸžåŸŸåŸ åŸ¡åŸ¢åŸ£åŸ¤åŸ¥åŸ¦åŸ§åŸ¨åŸ©åŸªåŸ«åŸ¬åŸ­åŸ®åŸ¯åŸ°åŸ±åŸ²åŸ³åŸ´åŸµåŸ¶åŸ·åŸ¸åŸ¹åŸºåŸ»åŸ¼åŸ½åŸ¾åŸ¿å €å å ‚堃堄堅堆堇堈堉堊堋堌å å Žå å å ‘堒堓堔堕堖堗堘堙堚堛堜å å žå Ÿå  å ¡","堢堣堤堥堦堧堨堩堪堫堬堭堮堯堰報堲堳場堵堶堷堸堹堺堻堼堽堾堿塀å¡å¡‚塃塄塅塆塇塈塉塊塋塌å¡å¡Žå¡å¡å¡‘塒塓塔塕塖塗塘塙塚塛塜å¡å¡žå¡Ÿå¡ å¡¡å¡¢å¡£å¡¤å¡¥å¡¦å¡§å¡¨å¡©å¡ªå¡«å¡¬å¡­å¡®å¡¯å¡°å¡±å¡²å¡³å¡´å¡µå¡¶å¡·å¡¸å¡¹å¡ºå¡»å¡¼å¡½å¡¾å¡¿å¢€å¢å¢‚境墄墅墆墇墈墉墊墋墌å¢å¢Žå¢å¢å¢‘墒墓墔墕墖増墘墙墚墛墜å¢å¢žå¢Ÿå¢ å¢¡å¢¢å¢£å¢¤å¢¥å¢¦å¢§å¢¨å¢©å¢ªå¢«å¢¬å¢­å¢®å¢¯å¢°å¢±å¢²å¢³å¢´å¢µå¢¶å¢·å¢¸å¢¹å¢ºå¢»å¢¼å¢½å¢¾å¢¿å£€å£å£‚壃壄壅壆壇壈壉壊壋壌å£å£Žå£å£å£‘壒壓壔壕壖壗壘壙壚壛壜å£å£žå£Ÿå£ å£¡å£¢å££å£¤å£¥å£¦å£§å£¨å£©å£ªå£«å£¬å£­å£®å£¯å£°å£±å£²å£³å£´å£µå£¶å£·å£¸å£¹å£ºå£»å£¼å£½å£¾å£¿å¤€å¤å¤‚夃处夅夆备夈変夊夋夌å¤å¤Žå¤å¤å¤‘夒夓夔夕外夗夘夙多夛夜å¤å¤žå¤Ÿå¤ å¤¡","夢夣夤夥夦大夨天太夫夬夭央夯夰失夲夳头夵夶夷夸夹夺夻夼夽夾夿奀å¥å¥‚奃奄奅奆奇奈奉奊奋奌å¥å¥Žå¥å¥å¥‘奒奓奔奕奖套奘奙奚奛奜å¥å¥žå¥Ÿå¥ å¥¡å¥¢å¥£å¥¤å¥¥å¥¦å¥§å¥¨å¥©å¥ªå¥«å¥¬å¥­å¥®å¥¯å¥°å¥±å¥²å¥³å¥´å¥µå¥¶å¥·å¥¸å¥¹å¥ºå¥»å¥¼å¥½å¥¾å¥¿å¦€å¦å¦‚妃妄妅妆妇妈妉妊妋妌å¦å¦Žå¦å¦å¦‘妒妓妔妕妖妗妘妙妚妛妜å¦å¦žå¦Ÿå¦ å¦¡å¦¢å¦£å¦¤å¦¥å¦¦å¦§å¦¨å¦©å¦ªå¦«å¦¬å¦­å¦®å¦¯å¦°å¦±å¦²å¦³å¦´å¦µå¦¶å¦·å¦¸å¦¹å¦ºå¦»å¦¼å¦½å¦¾å¦¿å§€å§å§‚姃姄姅姆姇姈姉姊始姌å§å§Žå§å§å§‘姒姓委姕姖姗姘姙姚姛姜å§å§žå§Ÿå§ å§¡å§¢å§£å§¤å§¥å§¦å§§å§¨å§©å§ªå§«å§¬å§­å§®å§¯å§°å§±å§²å§³å§´å§µå§¶å§·å§¸å§¹å§ºå§»å§¼å§½å§¾å§¿å¨€å¨å¨‚娃娄娅娆娇娈娉娊娋娌å¨å¨Žå¨å¨å¨‘娒娓娔娕娖娗娘娙娚娛娜å¨å¨žå¨Ÿå¨ å¨¡","娢娣娤娥娦娧娨娩娪娫娬娭娮娯娰娱娲娳娴娵娶娷娸娹娺娻娼娽娾娿婀å©å©‚婃婄婅婆婇婈婉婊婋婌å©å©Žå©å©å©‘婒婓婔婕婖婗婘婙婚婛婜å©å©žå©Ÿå© å©¡å©¢å©£å©¤å©¥å©¦å©§å©¨å©©å©ªå©«å©¬å©­å©®å©¯å©°å©±å©²å©³å©´å©µå©¶å©·å©¸å©¹å©ºå©»å©¼å©½å©¾å©¿åª€åªåª‚媃媄媅媆媇媈媉媊媋媌åªåªŽåªåªåª‘媒媓媔媕媖媗媘媙媚媛媜åªåªžåªŸåª åª¡åª¢åª£åª¤åª¥åª¦åª§åª¨åª©åªªåª«åª¬åª­åª®åª¯åª°åª±åª²åª³åª´åªµåª¶åª·åª¸åª¹åªºåª»åª¼åª½åª¾åª¿å«€å«å«‚嫃嫄嫅嫆嫇嫈嫉嫊嫋嫌å«å«Žå«å«å«‘嫒嫓嫔嫕嫖嫗嫘嫙嫚嫛嫜å«å«žå«Ÿå« å«¡å«¢å«£å«¤å«¥å«¦å«§å«¨å«©å«ªå««å«¬å«­å«®å«¯å«°å«±å«²å«³å«´å«µå«¶å«·å«¸å«¹å«ºå«»å«¼å«½å«¾å«¿å¬€å¬å¬‚嬃嬄嬅嬆嬇嬈嬉嬊嬋嬌å¬å¬Žå¬å¬å¬‘嬒嬓嬔嬕嬖嬗嬘嬙嬚嬛嬜å¬å¬žå¬Ÿå¬ å¬¡","嬢嬣嬤嬥嬦嬧嬨嬩嬪嬫嬬嬭嬮嬯嬰嬱嬲嬳嬴嬵嬶嬷嬸嬹嬺嬻嬼嬽嬾嬿孀å­å­‚孃孄孅孆孇孈孉孊孋孌å­å­Žå­å­å­‘孒孓孔孕孖字存孙孚孛孜å­å­žå­Ÿå­ å­¡å­¢å­£å­¤å­¥å­¦å­§å­¨å­©å­ªå­«å­¬å­­å­®å­¯å­°å­±å­²å­³å­´å­µå­¶å­·å­¸å­¹å­ºå­»å­¼å­½å­¾å­¿å®€å®å®‚它宄宅宆宇守安宊宋完å®å®Žå®å®å®‘宒宓宔宕宖宗官宙定宛宜å®å®žå®Ÿå® å®¡å®¢å®£å®¤å®¥å®¦å®§å®¨å®©å®ªå®«å®¬å®­å®®å®¯å®°å®±å®²å®³å®´å®µå®¶å®·å®¸å®¹å®ºå®»å®¼å®½å®¾å®¿å¯€å¯å¯‚寃寄寅密寇寈寉寊寋富å¯å¯Žå¯å¯å¯‘寒寓寔寕寖寗寘寙寚寛寜å¯å¯žå¯Ÿå¯ å¯¡å¯¢å¯£å¯¤å¯¥å¯¦å¯§å¯¨å¯©å¯ªå¯«å¯¬å¯­å¯®å¯¯å¯°å¯±å¯²å¯³å¯´å¯µå¯¶å¯·å¯¸å¯¹å¯ºå¯»å¯¼å¯½å¯¾å¯¿å°€å°å°‚尃射尅将將專尉尊尋尌å°å°Žå°å°å°‘尒尓尔尕尖尗尘尙尚尛尜å°å°žå°Ÿå° å°¡","尢尣尤尥尦尧尨尩尪尫尬尭尮尯尰就尲尳尴尵尶尷尸尹尺尻尼尽尾尿局å±å±‚屃屄居屆屇屈屉届屋屌å±å±Žå±å±å±‘屒屓屔展屖屗屘屙屚屛屜å±å±žå±Ÿå± å±¡å±¢å±£å±¤å±¥å±¦å±§å±¨å±©å±ªå±«å±¬å±­å±®å±¯å±°å±±å±²å±³å±´å±µå±¶å±·å±¸å±¹å±ºå±»å±¼å±½å±¾å±¿å²€å²å²‚岃岄岅岆岇岈岉岊岋岌å²å²Žå²å²å²‘岒岓岔岕岖岗岘岙岚岛岜å²å²žå²Ÿå² å²¡å²¢å²£å²¤å²¥å²¦å²§å²¨å²©å²ªå²«å²¬å²­å²®å²¯å²°å²±å²²å²³å²´å²µå²¶å²·å²¸å²¹å²ºå²»å²¼å²½å²¾å²¿å³€å³å³‚峃峄峅峆峇峈峉峊峋峌å³å³Žå³å³å³‘峒峓峔峕峖峗峘峙峚峛峜å³å³žå³Ÿå³ å³¡å³¢å³£å³¤å³¥å³¦å³§å³¨å³©å³ªå³«å³¬å³­å³®å³¯å³°å³±å³²å³³å³´å³µå³¶å³·å³¸å³¹å³ºå³»å³¼å³½å³¾å³¿å´€å´å´‚崃崄崅崆崇崈崉崊崋崌å´å´Žå´å´å´‘崒崓崔崕崖崗崘崙崚崛崜å´å´žå´Ÿå´ å´¡","崢崣崤崥崦崧崨崩崪崫崬崭崮崯崰崱崲崳崴崵崶崷崸崹崺崻崼崽崾崿嵀åµåµ‚嵃嵄嵅嵆嵇嵈嵉嵊嵋嵌åµåµŽåµåµåµ‘嵒嵓嵔嵕嵖嵗嵘嵙嵚嵛嵜åµåµžåµŸåµ åµ¡åµ¢åµ£åµ¤åµ¥åµ¦åµ§åµ¨åµ©åµªåµ«åµ¬åµ­åµ®åµ¯åµ°åµ±åµ²åµ³åµ´åµµåµ¶åµ·åµ¸åµ¹åµºåµ»åµ¼åµ½åµ¾åµ¿å¶€å¶å¶‚嶃嶄嶅嶆嶇嶈嶉嶊嶋嶌å¶å¶Žå¶å¶å¶‘嶒嶓嶔嶕嶖嶗嶘嶙嶚嶛嶜å¶å¶žå¶Ÿå¶ å¶¡å¶¢å¶£å¶¤å¶¥å¶¦å¶§å¶¨å¶©å¶ªå¶«å¶¬å¶­å¶®å¶¯å¶°å¶±å¶²å¶³å¶´å¶µå¶¶å¶·å¶¸å¶¹å¶ºå¶»å¶¼å¶½å¶¾å¶¿å·€å·å·‚巃巄巅巆巇巈巉巊巋巌å·å·Žå·å·å·‘巒巓巔巕巖巗巘巙巚巛巜å·å·žå·Ÿå· å·¡å·¢å·£å·¤å·¥å·¦å·§å·¨å·©å·ªå·«å·¬å·­å·®å·¯å·°å·±å·²å·³å·´å·µå·¶å··å·¸å·¹å·ºå·»å·¼å·½å·¾å·¿å¸€å¸å¸‚布帄帅帆帇师帉帊帋希å¸å¸Žå¸å¸å¸‘帒帓帔帕帖帗帘帙帚帛帜å¸å¸žå¸Ÿå¸ å¸¡","帢帣帤帥带帧帨帩帪師帬席帮帯帰帱帲帳帴帵帶帷常帹帺帻帼帽帾帿幀å¹å¹‚幃幄幅幆幇幈幉幊幋幌å¹å¹Žå¹å¹å¹‘幒幓幔幕幖幗幘幙幚幛幜å¹å¹žå¹Ÿå¹ å¹¡å¹¢å¹£å¹¤å¹¥å¹¦å¹§å¹¨å¹©å¹ªå¹«å¹¬å¹­å¹®å¹¯å¹°å¹±å¹²å¹³å¹´å¹µå¹¶å¹·å¹¸å¹¹å¹ºå¹»å¹¼å¹½å¹¾å¹¿åº€åºåº‚広庄庅庆庇庈庉床庋庌åºåºŽåºåºåº‘庒库应底庖店庘庙庚庛府åºåºžåºŸåº åº¡åº¢åº£åº¤åº¥åº¦åº§åº¨åº©åºªåº«åº¬åº­åº®åº¯åº°åº±åº²åº³åº´åºµåº¶åº·åº¸åº¹åººåº»åº¼åº½åº¾åº¿å»€å»å»‚廃廄廅廆廇廈廉廊廋廌å»å»Žå»å»å»‘廒廓廔廕廖廗廘廙廚廛廜å»å»žå»Ÿå» å»¡å»¢å»£å»¤å»¥å»¦å»§å»¨å»©å»ªå»«å»¬å»­å»®å»¯å»°å»±å»²å»³å»´å»µå»¶å»·å»¸å»¹å»ºå»»å»¼å»½å»¾å»¿å¼€å¼å¼‚弃弄弅弆弇弈弉弊弋弌å¼å¼Žå¼å¼å¼‘弒弓弔引弖弗弘弙弚弛弜å¼å¼žå¼Ÿå¼ å¼¡","弢弣弤弥弦弧弨弩弪弫弬弭弮弯弰弱弲弳弴張弶強弸弹强弻弼弽弾弿彀å½å½‚彃彄彅彆彇彈彉彊彋彌å½å½Žå½å½å½‘归当彔录彖彗彘彙彚彛彜å½å½žå½Ÿå½ å½¡å½¢å½£å½¤å½¥å½¦å½§å½¨å½©å½ªå½«å½¬å½­å½®å½¯å½°å½±å½²å½³å½´å½µå½¶å½·å½¸å½¹å½ºå½»å½¼å½½å½¾å½¿å¾€å¾å¾‚徃径待徆徇很徉徊律後å¾å¾Žå¾å¾å¾‘徒従徔徕徖得徘徙徚徛徜å¾å¾žå¾Ÿå¾ å¾¡å¾¢å¾£å¾¤å¾¥å¾¦å¾§å¾¨å¾©å¾ªå¾«å¾¬å¾­å¾®å¾¯å¾°å¾±å¾²å¾³å¾´å¾µå¾¶å¾·å¾¸å¾¹å¾ºå¾»å¾¼å¾½å¾¾å¾¿å¿€å¿å¿‚心忄必忆忇忈忉忊忋忌å¿å¿Žå¿å¿å¿‘忒忓忔忕忖志忘忙忚忛応å¿å¿žå¿Ÿå¿ å¿¡å¿¢å¿£å¿¤å¿¥å¿¦å¿§å¿¨å¿©å¿ªå¿«å¿¬å¿­å¿®å¿¯å¿°å¿±å¿²å¿³å¿´å¿µå¿¶å¿·å¿¸å¿¹å¿ºå¿»å¿¼å¿½å¿¾å¿¿æ€€æ€æ€‚怃怄怅怆怇怈怉怊怋怌æ€æ€Žæ€æ€æ€‘怒怓怔怕怖怗怘怙怚怛怜æ€æ€žæ€Ÿæ€ æ€¡","怢怣怤急怦性怨怩怪怫怬怭怮怯怰怱怲怳怴怵怶怷怸怹怺总怼怽怾怿æ€ææ‚æƒæ„æ…æ†æ‡æˆæ‰æŠæ‹æŒææŽæææ‘æ’æ“æ”æ•æ–æ—æ˜æ™æšæ›æœææžæŸæ æ¡æ¢æ£æ¤æ¥æ¦æ§æ¨æ©æªæ«æ¬æ­æ®æ¯æ°æ±æ²æ³æ´æµæ¶æ·æ¸æ¹æºæ»æ¼æ½æ¾æ¿æ‚€æ‚悂悃悄悅悆悇悈悉悊悋悌æ‚æ‚Žæ‚æ‚悑悒悓悔悕悖悗悘悙悚悛悜æ‚悞悟悠悡悢患悤悥悦悧您悩悪悫悬悭悮悯悰悱悲悳悴悵悶悷悸悹悺悻悼悽悾悿惀æƒæƒ‚惃惄情惆惇惈惉惊惋惌æƒæƒŽæƒæƒæƒ‘惒惓惔惕惖惗惘惙惚惛惜æƒæƒžæƒŸæƒ æƒ¡æƒ¢æƒ£æƒ¤æƒ¥æƒ¦æƒ§æƒ¨æƒ©æƒªæƒ«æƒ¬æƒ­æƒ®æƒ¯æƒ°æƒ±æƒ²æƒ³æƒ´æƒµæƒ¶æƒ·æƒ¸æƒ¹æƒºæƒ»æƒ¼æƒ½æƒ¾æƒ¿æ„€æ„愂愃愄愅愆愇愈愉愊愋愌æ„æ„Žæ„æ„愑愒愓愔愕愖愗愘愙愚愛愜æ„æ„žæ„Ÿæ„ æ„¡","愢愣愤愥愦愧愨愩愪愫愬愭愮愯愰愱愲愳愴愵愶愷愸愹愺愻愼愽愾愿慀æ…慂慃慄慅慆慇慈慉慊態慌æ…æ…Žæ…æ…慑慒慓慔慕慖慗慘慙慚慛慜æ…慞慟慠慡慢慣慤慥慦慧慨慩慪慫慬慭慮慯慰慱慲慳慴慵慶慷慸慹慺慻慼慽慾慿憀æ†æ†‚憃憄憅憆憇憈憉憊憋憌æ†æ†Žæ†æ†æ†‘憒憓憔憕憖憗憘憙憚憛憜æ†æ†žæ†Ÿæ† æ†¡æ†¢æ†£æ†¤æ†¥æ†¦æ†§æ†¨æ†©æ†ªæ†«æ†¬æ†­æ†®æ†¯æ†°æ†±æ†²æ†³æ†´æ†µæ†¶æ†·æ†¸æ†¹æ†ºæ†»æ†¼æ†½æ†¾æ†¿æ‡€æ‡æ‡‚懃懄懅懆懇懈應懊懋懌æ‡æ‡Žæ‡æ‡æ‡‘懒懓懔懕懖懗懘懙懚懛懜æ‡æ‡žæ‡Ÿæ‡ æ‡¡æ‡¢æ‡£æ‡¤æ‡¥æ‡¦æ‡§æ‡¨æ‡©æ‡ªæ‡«æ‡¬æ‡­æ‡®æ‡¯æ‡°æ‡±æ‡²æ‡³æ‡´æ‡µæ‡¶æ‡·æ‡¸æ‡¹æ‡ºæ‡»æ‡¼æ‡½æ‡¾æ‡¿æˆ€æˆæˆ‚戃戄戅戆戇戈戉戊戋戌æˆæˆŽæˆæˆæˆ‘戒戓戔戕或戗战戙戚戛戜æˆæˆžæˆŸæˆ æˆ¡","戢戣戤戥戦戧戨戩截戫戬戭戮戯戰戱戲戳戴戵戶户戸戹戺戻戼戽戾房所æ‰æ‰‚扃扄扅扆扇扈扉扊手扌æ‰æ‰Žæ‰æ‰æ‰‘扒打扔払扖扗托扙扚扛扜æ‰æ‰žæ‰Ÿæ‰ æ‰¡æ‰¢æ‰£æ‰¤æ‰¥æ‰¦æ‰§æ‰¨æ‰©æ‰ªæ‰«æ‰¬æ‰­æ‰®æ‰¯æ‰°æ‰±æ‰²æ‰³æ‰´æ‰µæ‰¶æ‰·æ‰¸æ‰¹æ‰ºæ‰»æ‰¼æ‰½æ‰¾æ‰¿æŠ€æŠæŠ‚抃抄抅抆抇抈抉把抋抌æŠæŠŽæŠæŠæŠ‘抒抓抔投抖抗折抙抚抛抜æŠæŠžæŠŸæŠ æŠ¡æŠ¢æŠ£æŠ¤æŠ¥æŠ¦æŠ§æŠ¨æŠ©æŠªæŠ«æŠ¬æŠ­æŠ®æŠ¯æŠ°æŠ±æŠ²æŠ³æŠ´æŠµæŠ¶æŠ·æŠ¸æŠ¹æŠºæŠ»æŠ¼æŠ½æŠ¾æŠ¿æ‹€æ‹æ‹‚拃拄担拆拇拈拉拊拋拌æ‹æ‹Žæ‹æ‹æ‹‘拒拓拔拕拖拗拘拙拚招拜æ‹æ‹žæ‹Ÿæ‹ æ‹¡æ‹¢æ‹£æ‹¤æ‹¥æ‹¦æ‹§æ‹¨æ‹©æ‹ªæ‹«æ‹¬æ‹­æ‹®æ‹¯æ‹°æ‹±æ‹²æ‹³æ‹´æ‹µæ‹¶æ‹·æ‹¸æ‹¹æ‹ºæ‹»æ‹¼æ‹½æ‹¾æ‹¿æŒ€æŒæŒ‚挃挄挅挆指挈按挊挋挌æŒæŒŽæŒæŒæŒ‘挒挓挔挕挖挗挘挙挚挛挜æŒæŒžæŒŸæŒ æŒ¡","挢挣挤挥挦挧挨挩挪挫挬挭挮振挰挱挲挳挴挵挶挷挸挹挺挻挼挽挾挿æ€ææ‚æƒæ„æ…æ†æ‡æˆæ‰æŠæ‹æŒææŽæææ‘æ’æ“æ”æ•æ–æ—æ˜æ™æšæ›æœææžæŸæ æ¡æ¢æ£æ¤æ¥æ¦æ§æ¨æ©æªæ«æ¬æ­æ®æ¯æ°æ±æ²æ³æ´æµæ¶æ·æ¸æ¹æºæ»æ¼æ½æ¾æ¿æŽ€æŽæŽ‚掃掄掅掆掇授掉掊掋掌æŽæŽŽæŽæŽæŽ‘排掓掔掕掖掗掘掙掚掛掜æŽæŽžæŽŸæŽ æŽ¡æŽ¢æŽ£æŽ¤æŽ¥æŽ¦æŽ§æŽ¨æŽ©æŽªæŽ«æŽ¬æŽ­æŽ®æŽ¯æŽ°æŽ±æŽ²æŽ³æŽ´æŽµæŽ¶æŽ·æŽ¸æŽ¹æŽºæŽ»æŽ¼æŽ½æŽ¾æŽ¿æ€ææ‚æƒæ„æ…æ†æ‡æˆæ‰æŠæ‹æŒææŽæææ‘æ’æ“æ”æ•æ–æ—æ˜æ™æšæ›æœææžæŸæ æ¡æ¢æ£æ¤æ¥æ¦æ§æ¨æ©æªæ«æ¬æ­æ®æ¯æ°æ±æ²æ³æ´æµæ¶æ·æ¸æ¹æºæ»æ¼æ½æ¾æ¿æ€ææ‚æƒæ„æ…æ†æ‡æˆæ‰æŠæ‹æŒææŽæææ‘æ’æ“æ”æ•æ–æ—æ˜æ™æšæ›æœææžæŸæ æ¡","æ¢æ£æ¤æ¥æ¦æ§æ¨æ©æªæ«æ¬æ­æ®æ¯æ°æ±æ²æ³æ´æµæ¶æ·æ¸æ¹æºæ»æ¼æ½æ¾æ¿æ‘€æ‘摂摃摄摅摆摇摈摉摊摋摌æ‘æ‘Žæ‘æ‘摑摒摓摔摕摖摗摘摙摚摛摜æ‘摞摟摠摡摢摣摤摥摦摧摨摩摪摫摬摭摮摯摰摱摲摳摴摵摶摷摸摹摺摻摼摽摾摿撀æ’撂撃撄撅撆撇撈撉撊撋撌æ’æ’Žæ’æ’撑撒撓撔撕撖撗撘撙撚撛撜æ’撞撟撠撡撢撣撤撥撦撧撨撩撪撫撬播撮撯撰撱撲撳撴撵撶撷撸撹撺撻撼撽撾撿擀æ“擂擃擄擅擆擇擈擉擊擋擌æ“æ“Žæ“æ“擑擒擓擔擕擖擗擘擙據擛擜æ“擞擟擠擡擢擣擤擥擦擧擨擩擪擫擬擭擮擯擰擱擲擳擴擵擶擷擸擹擺擻擼擽擾擿攀æ”攂攃攄攅攆攇攈攉攊攋攌æ”攎æ”æ”攑攒攓攔攕攖攗攘攙攚攛攜æ”攞攟攠攡","攢攣攤攥攦攧攨攩攪攫攬攭攮支攰攱攲攳攴攵收攷攸改攺攻攼攽放政敀æ•æ•‚敃敄故敆敇效敉敊敋敌æ•æ•Žæ•æ•æ•‘敒敓敔敕敖敗敘教敚敛敜æ•æ•žæ•Ÿæ• æ•¡æ•¢æ•£æ•¤æ•¥æ•¦æ•§æ•¨æ•©æ•ªæ•«æ•¬æ•­æ•®æ•¯æ•°æ•±æ•²æ•³æ•´æ•µæ•¶æ•·æ•¸æ•¹æ•ºæ•»æ•¼æ•½æ•¾æ•¿æ–€æ–斂斃斄斅斆文斈斉斊斋斌æ–æ–Žæ–æ–斑斒斓斔斕斖斗斘料斚斛斜æ–斞斟斠斡斢斣斤斥斦斧斨斩斪斫斬断斮斯新斱斲斳斴斵斶斷斸方斺斻於施斾斿旀æ—旂旃旄旅旆旇旈旉旊旋旌æ—æ—Žæ—æ—旑旒旓旔旕旖旗旘旙旚旛旜æ—旞旟无旡既旣旤日旦旧旨早旪旫旬旭旮旯旰旱旲旳旴旵时旷旸旹旺旻旼旽旾旿昀æ˜æ˜‚昃昄昅昆昇昈昉昊昋昌æ˜æ˜Žæ˜æ˜æ˜‘昒易昔昕昖昗昘昙昚昛昜æ˜æ˜žæ˜Ÿæ˜ æ˜¡","昢昣昤春昦昧昨昩昪昫昬昭昮是昰昱昲昳昴昵昶昷昸昹昺昻昼昽显昿晀æ™æ™‚晃晄晅晆晇晈晉晊晋晌æ™æ™Žæ™æ™æ™‘晒晓晔晕晖晗晘晙晚晛晜æ™æ™žæ™Ÿæ™ æ™¡æ™¢æ™£æ™¤æ™¥æ™¦æ™§æ™¨æ™©æ™ªæ™«æ™¬æ™­æ™®æ™¯æ™°æ™±æ™²æ™³æ™´æ™µæ™¶æ™·æ™¸æ™¹æ™ºæ™»æ™¼æ™½æ™¾æ™¿æš€æšæš‚暃暄暅暆暇暈暉暊暋暌æšæšŽæšæšæš‘暒暓暔暕暖暗暘暙暚暛暜æšæšžæšŸæš æš¡æš¢æš£æš¤æš¥æš¦æš§æš¨æš©æšªæš«æš¬æš­æš®æš¯æš°æš±æš²æš³æš´æšµæš¶æš·æš¸æš¹æšºæš»æš¼æš½æš¾æš¿æ›€æ›æ›‚曃曄曅曆曇曈曉曊曋曌æ›æ›Žæ›æ›æ›‘曒曓曔曕曖曗曘曙曚曛曜æ›æ›žæ›Ÿæ› æ›¡æ›¢æ›£æ›¤æ›¥æ›¦æ›§æ›¨æ›©æ›ªæ›«æ›¬æ›­æ›®æ›¯æ›°æ›±æ›²æ›³æ›´æ›µæ›¶æ›·æ›¸æ›¹æ›ºæ›»æ›¼æ›½æ›¾æ›¿æœ€æœæœ‚會朄朅朆朇月有朊朋朌æœæœŽæœæœæœ‘朒朓朔朕朖朗朘朙朚望朜æœæœžæœŸæœ æœ¡","朢朣朤朥朦朧木朩未末本札朮术朰朱朲朳朴朵朶朷朸朹机朻朼朽朾朿æ€ææ‚æƒæ„æ…æ†æ‡æˆæ‰æŠæ‹æŒææŽæææ‘æ’æ“æ”æ•æ–æ—æ˜æ™æšæ›æœææžæŸæ æ¡æ¢æ£æ¤æ¥æ¦æ§æ¨æ©æªæ«æ¬æ­æ®æ¯æ°æ±æ²æ³æ´æµæ¶æ·æ¸æ¹æºæ»æ¼æ½æ¾æ¿æž€æžæž‚枃构枅枆枇枈枉枊枋枌æžæžŽæžæžæž‘枒枓枔枕枖林枘枙枚枛果æžæžžæžŸæž æž¡æž¢æž£æž¤æž¥æž¦æž§æž¨æž©æžªæž«æž¬æž­æž®æž¯æž°æž±æž²æž³æž´æžµæž¶æž·æž¸æž¹æžºæž»æž¼æž½æž¾æž¿æŸ€æŸæŸ‚柃柄柅柆柇柈柉柊柋柌æŸæŸŽæŸæŸæŸ‘柒染柔柕柖柗柘柙柚柛柜æŸæŸžæŸŸæŸ æŸ¡æŸ¢æŸ£æŸ¤æŸ¥æŸ¦æŸ§æŸ¨æŸ©æŸªæŸ«æŸ¬æŸ­æŸ®æŸ¯æŸ°æŸ±æŸ²æŸ³æŸ´æŸµæŸ¶æŸ·æŸ¸æŸ¹æŸºæŸ»æŸ¼æŸ½æŸ¾æŸ¿æ €æ æ ‚栃栄栅栆标栈栉栊栋栌æ æ Žæ æ æ ‘栒栓栔栕栖栗栘栙栚栛栜æ æ žæ Ÿæ  æ ¡","栢栣栤栥栦栧栨栩株栫栬栭栮栯栰栱栲栳栴栵栶样核根栺栻格栽栾栿桀æ¡æ¡‚桃桄桅框桇案桉桊桋桌æ¡æ¡Žæ¡æ¡æ¡‘桒桓桔桕桖桗桘桙桚桛桜æ¡æ¡žæ¡Ÿæ¡ æ¡¡æ¡¢æ¡£æ¡¤æ¡¥æ¡¦æ¡§æ¡¨æ¡©æ¡ªæ¡«æ¡¬æ¡­æ¡®æ¡¯æ¡°æ¡±æ¡²æ¡³æ¡´æ¡µæ¡¶æ¡·æ¡¸æ¡¹æ¡ºæ¡»æ¡¼æ¡½æ¡¾æ¡¿æ¢€æ¢æ¢‚梃梄梅梆梇梈梉梊梋梌æ¢æ¢Žæ¢æ¢æ¢‘梒梓梔梕梖梗梘梙梚梛梜æ¢æ¢žæ¢Ÿæ¢ æ¢¡æ¢¢æ¢£æ¢¤æ¢¥æ¢¦æ¢§æ¢¨æ¢©æ¢ªæ¢«æ¢¬æ¢­æ¢®æ¢¯æ¢°æ¢±æ¢²æ¢³æ¢´æ¢µæ¢¶æ¢·æ¢¸æ¢¹æ¢ºæ¢»æ¢¼æ¢½æ¢¾æ¢¿æ£€æ£æ£‚棃棄棅棆棇棈棉棊棋棌æ£æ£Žæ£æ£æ£‘棒棓棔棕棖棗棘棙棚棛棜æ£æ£žæ£Ÿæ£ æ£¡æ£¢æ££æ£¤æ£¥æ£¦æ£§æ£¨æ£©æ£ªæ£«æ£¬æ£­æ£®æ£¯æ£°æ£±æ£²æ£³æ£´æ£µæ£¶æ£·æ£¸æ£¹æ£ºæ£»æ£¼æ£½æ£¾æ£¿æ¤€æ¤æ¤‚椃椄椅椆椇椈椉椊椋椌æ¤æ¤Žæ¤æ¤æ¤‘椒椓椔椕椖椗椘椙椚椛検æ¤æ¤žæ¤Ÿæ¤ æ¤¡","椢椣椤椥椦椧椨椩椪椫椬椭椮椯椰椱椲椳椴椵椶椷椸椹椺椻椼椽椾椿楀æ¥æ¥‚楃楄楅楆楇楈楉楊楋楌æ¥æ¥Žæ¥æ¥æ¥‘楒楓楔楕楖楗楘楙楚楛楜æ¥æ¥žæ¥Ÿæ¥ æ¥¡æ¥¢æ¥£æ¥¤æ¥¥æ¥¦æ¥§æ¥¨æ¥©æ¥ªæ¥«æ¥¬æ¥­æ¥®æ¥¯æ¥°æ¥±æ¥²æ¥³æ¥´æ¥µæ¥¶æ¥·æ¥¸æ¥¹æ¥ºæ¥»æ¥¼æ¥½æ¥¾æ¥¿æ¦€æ¦æ¦‚榃榄榅榆榇榈榉榊榋榌æ¦æ¦Žæ¦æ¦æ¦‘榒榓榔榕榖榗榘榙榚榛榜æ¦æ¦žæ¦Ÿæ¦ æ¦¡æ¦¢æ¦£æ¦¤æ¦¥æ¦¦æ¦§æ¦¨æ¦©æ¦ªæ¦«æ¦¬æ¦­æ¦®æ¦¯æ¦°æ¦±æ¦²æ¦³æ¦´æ¦µæ¦¶æ¦·æ¦¸æ¦¹æ¦ºæ¦»æ¦¼æ¦½æ¦¾æ¦¿æ§€æ§æ§‚槃槄槅槆槇槈槉槊構槌æ§æ§Žæ§æ§æ§‘槒槓槔槕槖槗様槙槚槛槜æ§æ§žæ§Ÿæ§ æ§¡æ§¢æ§£æ§¤æ§¥æ§¦æ§§æ§¨æ§©æ§ªæ§«æ§¬æ§­æ§®æ§¯æ§°æ§±æ§²æ§³æ§´æ§µæ§¶æ§·æ§¸æ§¹æ§ºæ§»æ§¼æ§½æ§¾æ§¿æ¨€æ¨æ¨‚樃樄樅樆樇樈樉樊樋樌æ¨æ¨Žæ¨æ¨æ¨‘樒樓樔樕樖樗樘標樚樛樜æ¨æ¨žæ¨Ÿæ¨ æ¨¡","樢樣樤樥樦樧樨権横樫樬樭樮樯樰樱樲樳樴樵樶樷樸樹樺樻樼樽樾樿橀æ©æ©‚橃橄橅橆橇橈橉橊橋橌æ©æ©Žæ©æ©æ©‘橒橓橔橕橖橗橘橙橚橛橜æ©æ©žæ©Ÿæ© æ©¡æ©¢æ©£æ©¤æ©¥æ©¦æ©§æ©¨æ©©æ©ªæ©«æ©¬æ©­æ©®æ©¯æ©°æ©±æ©²æ©³æ©´æ©µæ©¶æ©·æ©¸æ©¹æ©ºæ©»æ©¼æ©½æ©¾æ©¿æª€æªæª‚檃檄檅檆檇檈檉檊檋檌æªæªŽæªæªæª‘檒檓檔檕檖檗檘檙檚檛檜æªæªžæªŸæª æª¡æª¢æª£æª¤æª¥æª¦æª§æª¨æª©æªªæª«æª¬æª­æª®æª¯æª°æª±æª²æª³æª´æªµæª¶æª·æª¸æª¹æªºæª»æª¼æª½æª¾æª¿æ«€æ«æ«‚櫃櫄櫅櫆櫇櫈櫉櫊櫋櫌æ«æ«Žæ«æ«æ«‘櫒櫓櫔櫕櫖櫗櫘櫙櫚櫛櫜æ«æ«žæ«Ÿæ« æ«¡æ«¢æ«£æ«¤æ«¥æ«¦æ«§æ«¨æ«©æ«ªæ««æ«¬æ«­æ«®æ«¯æ«°æ«±æ«²æ«³æ«´æ«µæ«¶æ«·æ«¸æ«¹æ«ºæ«»æ«¼æ«½æ«¾æ«¿æ¬€æ¬æ¬‚欃欄欅欆欇欈欉權欋欌æ¬æ¬Žæ¬æ¬æ¬‘欒欓欔欕欖欗欘欙欚欛欜æ¬æ¬žæ¬Ÿæ¬ æ¬¡","欢欣欤欥欦欧欨欩欪欫欬欭欮欯欰欱欲欳欴欵欶欷欸欹欺欻欼欽款欿歀æ­æ­‚歃歄歅歆歇歈歉歊歋歌æ­æ­Žæ­æ­æ­‘歒歓歔歕歖歗歘歙歚歛歜æ­æ­žæ­Ÿæ­ æ­¡æ­¢æ­£æ­¤æ­¥æ­¦æ­§æ­¨æ­©æ­ªæ­«æ­¬æ­­æ­®æ­¯æ­°æ­±æ­²æ­³æ­´æ­µæ­¶æ­·æ­¸æ­¹æ­ºæ­»æ­¼æ­½æ­¾æ­¿æ®€æ®æ®‚殃殄殅殆殇殈殉殊残殌æ®æ®Žæ®æ®æ®‘殒殓殔殕殖殗殘殙殚殛殜æ®æ®žæ®Ÿæ® æ®¡æ®¢æ®£æ®¤æ®¥æ®¦æ®§æ®¨æ®©æ®ªæ®«æ®¬æ®­æ®®æ®¯æ®°æ®±æ®²æ®³æ®´æ®µæ®¶æ®·æ®¸æ®¹æ®ºæ®»æ®¼æ®½æ®¾æ®¿æ¯€æ¯æ¯‚毃毄毅毆毇毈毉毊毋毌æ¯æ¯Žæ¯æ¯æ¯‘毒毓比毕毖毗毘毙毚毛毜æ¯æ¯žæ¯Ÿæ¯ æ¯¡æ¯¢æ¯£æ¯¤æ¯¥æ¯¦æ¯§æ¯¨æ¯©æ¯ªæ¯«æ¯¬æ¯­æ¯®æ¯¯æ¯°æ¯±æ¯²æ¯³æ¯´æ¯µæ¯¶æ¯·æ¯¸æ¯¹æ¯ºæ¯»æ¯¼æ¯½æ¯¾æ¯¿æ°€æ°æ°‚氃氄氅氆氇氈氉氊氋氌æ°æ°Žæ°æ°æ°‘氒氓气氕氖気氘氙氚氛氜æ°æ°žæ°Ÿæ° æ°¡","氢氣氤氥氦氧氨氩氪氫氬氭氮氯氰氱氲氳水氵氶氷永氹氺氻氼氽氾氿汀æ±æ±‚汃汄汅汆汇汈汉汊汋汌æ±æ±Žæ±æ±æ±‘汒汓汔汕汖汗汘汙汚汛汜æ±æ±žæ±Ÿæ± æ±¡æ±¢æ±£æ±¤æ±¥æ±¦æ±§æ±¨æ±©æ±ªæ±«æ±¬æ±­æ±®æ±¯æ±°æ±±æ±²æ±³æ±´æ±µæ±¶æ±·æ±¸æ±¹æ±ºæ±»æ±¼æ±½æ±¾æ±¿æ²€æ²æ²‚沃沄沅沆沇沈沉沊沋沌æ²æ²Žæ²æ²æ²‘沒沓沔沕沖沗沘沙沚沛沜æ²æ²žæ²Ÿæ² æ²¡æ²¢æ²£æ²¤æ²¥æ²¦æ²§æ²¨æ²©æ²ªæ²«æ²¬æ²­æ²®æ²¯æ²°æ²±æ²²æ²³æ²´æ²µæ²¶æ²·æ²¸æ²¹æ²ºæ²»æ²¼æ²½æ²¾æ²¿æ³€æ³æ³‚泃泄泅泆泇泈泉泊泋泌æ³æ³Žæ³æ³æ³‘泒泓泔法泖泗泘泙泚泛泜æ³æ³žæ³Ÿæ³ æ³¡æ³¢æ³£æ³¤æ³¥æ³¦æ³§æ³¨æ³©æ³ªæ³«æ³¬æ³­æ³®æ³¯æ³°æ³±æ³²æ³³æ³´æ³µæ³¶æ³·æ³¸æ³¹æ³ºæ³»æ³¼æ³½æ³¾æ³¿æ´€æ´æ´‚洃洄洅洆洇洈洉洊洋洌æ´æ´Žæ´æ´æ´‘洒洓洔洕洖洗洘洙洚洛洜æ´æ´žæ´Ÿæ´ æ´¡","洢洣洤津洦洧洨洩洪洫洬洭洮洯洰洱洲洳洴洵洶洷洸洹洺活洼洽派洿浀æµæµ‚浃浄浅浆浇浈浉浊测浌æµæµŽæµæµæµ‘浒浓浔浕浖浗浘浙浚浛浜æµæµžæµŸæµ æµ¡æµ¢æµ£æµ¤æµ¥æµ¦æµ§æµ¨æµ©æµªæµ«æµ¬æµ­æµ®æµ¯æµ°æµ±æµ²æµ³æµ´æµµæµ¶æµ·æµ¸æµ¹æµºæµ»æµ¼æµ½æµ¾æµ¿æ¶€æ¶æ¶‚涃涄涅涆涇消涉涊涋涌æ¶æ¶Žæ¶æ¶æ¶‘涒涓涔涕涖涗涘涙涚涛涜æ¶æ¶žæ¶Ÿæ¶ æ¶¡æ¶¢æ¶£æ¶¤æ¶¥æ¶¦æ¶§æ¶¨æ¶©æ¶ªæ¶«æ¶¬æ¶­æ¶®æ¶¯æ¶°æ¶±æ¶²æ¶³æ¶´æ¶µæ¶¶æ¶·æ¶¸æ¶¹æ¶ºæ¶»æ¶¼æ¶½æ¶¾æ¶¿æ·€æ·æ·‚淃淄淅淆淇淈淉淊淋淌æ·æ·Žæ·æ·æ·‘淒淓淔淕淖淗淘淙淚淛淜æ·æ·žæ·Ÿæ· æ·¡æ·¢æ·£æ·¤æ·¥æ·¦æ·§æ·¨æ·©æ·ªæ·«æ·¬æ·­æ·®æ·¯æ·°æ·±æ·²æ·³æ·´æ·µæ·¶æ··æ·¸æ·¹æ·ºæ·»æ·¼æ·½æ·¾æ·¿æ¸€æ¸æ¸‚渃渄清渆渇済渉渊渋渌æ¸æ¸Žæ¸æ¸æ¸‘渒渓渔渕渖渗渘渙渚減渜æ¸æ¸žæ¸Ÿæ¸ æ¸¡","渢渣渤渥渦渧渨温渪渫測渭渮港渰渱渲渳渴渵渶渷游渹渺渻渼渽渾渿湀æ¹æ¹‚湃湄湅湆湇湈湉湊湋湌æ¹æ¹Žæ¹æ¹æ¹‘湒湓湔湕湖湗湘湙湚湛湜æ¹æ¹žæ¹Ÿæ¹ æ¹¡æ¹¢æ¹£æ¹¤æ¹¥æ¹¦æ¹§æ¹¨æ¹©æ¹ªæ¹«æ¹¬æ¹­æ¹®æ¹¯æ¹°æ¹±æ¹²æ¹³æ¹´æ¹µæ¹¶æ¹·æ¹¸æ¹¹æ¹ºæ¹»æ¹¼æ¹½æ¹¾æ¹¿æº€æºæº‚溃溄溅溆溇溈溉溊溋溌æºæºŽæºæºæº‘溒溓溔溕準溗溘溙溚溛溜æºæºžæºŸæº æº¡æº¢æº£æº¤æº¥æº¦æº§æº¨æº©æºªæº«æº¬æº­æº®æº¯æº°æº±æº²æº³æº´æºµæº¶æº·æº¸æº¹æººæº»æº¼æº½æº¾æº¿æ»€æ»æ»‚滃滄滅滆滇滈滉滊滋滌æ»æ»Žæ»æ»æ»‘滒滓滔滕滖滗滘滙滚滛滜æ»æ»žæ»Ÿæ» æ»¡æ»¢æ»£æ»¤æ»¥æ»¦æ»§æ»¨æ»©æ»ªæ»«æ»¬æ»­æ»®æ»¯æ»°æ»±æ»²æ»³æ»´æ»µæ»¶æ»·æ»¸æ»¹æ»ºæ»»æ»¼æ»½æ»¾æ»¿æ¼€æ¼æ¼‚漃漄漅漆漇漈漉漊漋漌æ¼æ¼Žæ¼æ¼æ¼‘漒漓演漕漖漗漘漙漚漛漜æ¼æ¼žæ¼Ÿæ¼ æ¼¡","漢漣漤漥漦漧漨漩漪漫漬漭漮漯漰漱漲漳漴漵漶漷漸漹漺漻漼漽漾漿潀æ½æ½‚潃潄潅潆潇潈潉潊潋潌æ½æ½Žæ½æ½æ½‘潒潓潔潕潖潗潘潙潚潛潜æ½æ½žæ½Ÿæ½ æ½¡æ½¢æ½£æ½¤æ½¥æ½¦æ½§æ½¨æ½©æ½ªæ½«æ½¬æ½­æ½®æ½¯æ½°æ½±æ½²æ½³æ½´æ½µæ½¶æ½·æ½¸æ½¹æ½ºæ½»æ½¼æ½½æ½¾æ½¿æ¾€æ¾æ¾‚澃澄澅澆澇澈澉澊澋澌æ¾æ¾Žæ¾æ¾æ¾‘澒澓澔澕澖澗澘澙澚澛澜æ¾æ¾žæ¾Ÿæ¾ æ¾¡æ¾¢æ¾£æ¾¤æ¾¥æ¾¦æ¾§æ¾¨æ¾©æ¾ªæ¾«æ¾¬æ¾­æ¾®æ¾¯æ¾°æ¾±æ¾²æ¾³æ¾´æ¾µæ¾¶æ¾·æ¾¸æ¾¹æ¾ºæ¾»æ¾¼æ¾½æ¾¾æ¾¿æ¿€æ¿æ¿‚濃濄濅濆濇濈濉濊濋濌æ¿æ¿Žæ¿æ¿æ¿‘濒濓濔濕濖濗濘濙濚濛濜æ¿æ¿žæ¿Ÿæ¿ æ¿¡æ¿¢æ¿£æ¿¤æ¿¥æ¿¦æ¿§æ¿¨æ¿©æ¿ªæ¿«æ¿¬æ¿­æ¿®æ¿¯æ¿°æ¿±æ¿²æ¿³æ¿´æ¿µæ¿¶æ¿·æ¿¸æ¿¹æ¿ºæ¿»æ¿¼æ¿½æ¿¾æ¿¿ç€€ç€ç€‚瀃瀄瀅瀆瀇瀈瀉瀊瀋瀌ç€ç€Žç€ç€ç€‘瀒瀓瀔瀕瀖瀗瀘瀙瀚瀛瀜ç€ç€žç€Ÿç€ ç€¡","瀢瀣瀤瀥瀦瀧瀨瀩瀪瀫瀬瀭瀮瀯瀰瀱瀲瀳瀴瀵瀶瀷瀸瀹瀺瀻瀼瀽瀾瀿ç€çç‚çƒç„ç…ç†ç‡çˆç‰çŠç‹çŒççŽççç‘ç’ç“ç”ç•ç–ç—ç˜ç™çšç›çœççžçŸç ç¡ç¢ç£ç¤ç¥ç¦ç§ç¨ç©çªç«ç¬ç­ç®ç¯ç°ç±ç²ç³ç´çµç¶ç·ç¸ç¹çºç»ç¼ç½ç¾ç¿ç‚€ç‚炂炃炄炅炆炇炈炉炊炋炌ç‚ç‚Žç‚ç‚炑炒炓炔炕炖炗炘炙炚炛炜ç‚炞炟炠炡炢炣炤炥炦炧炨炩炪炫炬炭炮炯炰炱炲炳炴炵炶炷炸点為炻炼炽炾炿烀çƒçƒ‚烃烄烅烆烇烈烉烊烋烌çƒçƒŽçƒçƒçƒ‘烒烓烔烕烖烗烘烙烚烛烜çƒçƒžçƒŸçƒ çƒ¡çƒ¢çƒ£çƒ¤çƒ¥çƒ¦çƒ§çƒ¨çƒ©çƒªçƒ«çƒ¬çƒ­çƒ®çƒ¯çƒ°çƒ±çƒ²çƒ³çƒ´çƒµçƒ¶çƒ·çƒ¸çƒ¹çƒºçƒ»çƒ¼çƒ½çƒ¾çƒ¿ç„€ç„焂焃焄焅焆焇焈焉焊焋焌ç„ç„Žç„ç„焑焒焓焔焕焖焗焘焙焚焛焜ç„ç„žç„Ÿç„ ç„¡","焢焣焤焥焦焧焨焩焪焫焬焭焮焯焰焱焲焳焴焵然焷焸焹焺焻焼焽焾焿煀ç…煂煃煄煅煆煇煈煉煊煋煌ç…ç…Žç…ç…煑煒煓煔煕煖煗煘煙煚煛煜ç…煞煟煠煡煢煣煤煥煦照煨煩煪煫煬煭煮煯煰煱煲煳煴煵煶煷煸煹煺煻煼煽煾煿熀ç†ç†‚熃熄熅熆熇熈熉熊熋熌ç†ç†Žç†ç†ç†‘熒熓熔熕熖熗熘熙熚熛熜ç†ç†žç†Ÿç† ç†¡ç†¢ç†£ç†¤ç†¥ç†¦ç†§ç†¨ç†©ç†ªç†«ç†¬ç†­ç†®ç†¯ç†°ç†±ç†²ç†³ç†´ç†µç†¶ç†·ç†¸ç†¹ç†ºç†»ç†¼ç†½ç†¾ç†¿ç‡€ç‡ç‡‚燃燄燅燆燇燈燉燊燋燌ç‡ç‡Žç‡ç‡ç‡‘燒燓燔燕燖燗燘燙燚燛燜ç‡ç‡žç‡Ÿç‡ ç‡¡ç‡¢ç‡£ç‡¤ç‡¥ç‡¦ç‡§ç‡¨ç‡©ç‡ªç‡«ç‡¬ç‡­ç‡®ç‡¯ç‡°ç‡±ç‡²ç‡³ç‡´ç‡µç‡¶ç‡·ç‡¸ç‡¹ç‡ºç‡»ç‡¼ç‡½ç‡¾ç‡¿çˆ€çˆçˆ‚爃爄爅爆爇爈爉爊爋爌çˆçˆŽçˆçˆçˆ‘爒爓爔爕爖爗爘爙爚爛爜çˆçˆžçˆŸçˆ çˆ¡","爢爣爤爥爦爧爨爩爪爫爬爭爮爯爰爱爲爳爴爵父爷爸爹爺爻爼爽爾爿牀ç‰ç‰‚牃牄牅牆片版牉牊牋牌ç‰ç‰Žç‰ç‰ç‰‘牒牓牔牕牖牗牘牙牚牛牜ç‰ç‰žç‰Ÿç‰ ç‰¡ç‰¢ç‰£ç‰¤ç‰¥ç‰¦ç‰§ç‰¨ç‰©ç‰ªç‰«ç‰¬ç‰­ç‰®ç‰¯ç‰°ç‰±ç‰²ç‰³ç‰´ç‰µç‰¶ç‰·ç‰¸ç‰¹ç‰ºç‰»ç‰¼ç‰½ç‰¾ç‰¿çŠ€çŠçŠ‚犃犄犅犆犇犈犉犊犋犌çŠçŠŽçŠçŠçŠ‘犒犓犔犕犖犗犘犙犚犛犜çŠçŠžçŠŸçŠ çŠ¡çŠ¢çŠ£çŠ¤çŠ¥çŠ¦çŠ§çŠ¨çŠ©çŠªçŠ«çŠ¬çŠ­çŠ®çŠ¯çŠ°çŠ±çŠ²çŠ³çŠ´çŠµçŠ¶çŠ·çŠ¸çŠ¹çŠºçŠ»çŠ¼çŠ½çŠ¾çŠ¿ç‹€ç‹ç‹‚狃狄狅狆狇狈狉狊狋狌ç‹ç‹Žç‹ç‹ç‹‘狒狓狔狕狖狗狘狙狚狛狜ç‹ç‹žç‹Ÿç‹ ç‹¡ç‹¢ç‹£ç‹¤ç‹¥ç‹¦ç‹§ç‹¨ç‹©ç‹ªç‹«ç‹¬ç‹­ç‹®ç‹¯ç‹°ç‹±ç‹²ç‹³ç‹´ç‹µç‹¶ç‹·ç‹¸ç‹¹ç‹ºç‹»ç‹¼ç‹½ç‹¾ç‹¿çŒ€çŒçŒ‚猃猄猅猆猇猈猉猊猋猌çŒçŒŽçŒçŒçŒ‘猒猓猔猕猖猗猘猙猚猛猜çŒçŒžçŒŸçŒ çŒ¡","猢猣猤猥猦猧猨猩猪猫猬猭献猯猰猱猲猳猴猵猶猷猸猹猺猻猼猽猾猿ç€çç‚çƒç„ç…ç†ç‡çˆç‰çŠç‹çŒççŽççç‘ç’ç“ç”ç•ç–ç—ç˜ç™çšç›çœççžçŸç ç¡ç¢ç£ç¤ç¥ç¦ç§ç¨ç©çªç«ç¬ç­ç®ç¯ç°ç±ç²ç³ç´çµç¶ç·ç¸ç¹çºç»ç¼ç½ç¾ç¿çŽ€çŽçŽ‚玃玄玅玆率玈玉玊王玌çŽçŽŽçŽçŽçŽ‘玒玓玔玕玖玗玘玙玚玛玜çŽçŽžçŽŸçŽ çŽ¡çŽ¢çŽ£çŽ¤çŽ¥çŽ¦çŽ§çŽ¨çŽ©çŽªçŽ«çŽ¬çŽ­çŽ®çŽ¯çŽ°çŽ±çŽ²çŽ³çŽ´çŽµçŽ¶çŽ·çŽ¸çŽ¹çŽºçŽ»çŽ¼çŽ½çŽ¾çŽ¿ç€çç‚çƒç„ç…ç†ç‡çˆç‰çŠç‹çŒççŽççç‘ç’ç“ç”ç•ç–ç—ç˜ç™çšç›çœççžçŸç ç¡ç¢ç£ç¤ç¥ç¦ç§ç¨ç©çªç«ç¬ç­ç®ç¯ç°ç±ç²ç³ç´çµç¶ç·ç¸ç¹çºç»ç¼ç½ç¾ç¿ç€çç‚çƒç„ç…ç†ç‡çˆç‰çŠç‹çŒççŽççç‘ç’ç“ç”ç•ç–ç—ç˜ç™çšç›çœççžçŸç ç¡","ç¢ç£ç¤ç¥ç¦ç§ç¨ç©çªç«ç¬ç­ç®ç¯ç°ç±ç²ç³ç´çµç¶ç·ç¸ç¹çºç»ç¼ç½ç¾ç¿ç‘€ç‘瑂瑃瑄瑅瑆瑇瑈瑉瑊瑋瑌ç‘ç‘Žç‘ç‘瑑瑒瑓瑔瑕瑖瑗瑘瑙瑚瑛瑜ç‘瑞瑟瑠瑡瑢瑣瑤瑥瑦瑧瑨瑩瑪瑫瑬瑭瑮瑯瑰瑱瑲瑳瑴瑵瑶瑷瑸瑹瑺瑻瑼瑽瑾瑿璀ç’璂璃璄璅璆璇璈璉璊璋璌ç’ç’Žç’ç’璑璒璓璔璕璖璗璘璙璚璛璜ç’璞璟璠璡璢璣璤璥璦璧璨璩璪璫璬璭璮璯環璱璲璳璴璵璶璷璸璹璺璻璼璽璾璿瓀ç“瓂瓃瓄瓅瓆瓇瓈瓉瓊瓋瓌ç“ç“Žç“ç“瓑瓒瓓瓔瓕瓖瓗瓘瓙瓚瓛瓜ç“瓞瓟瓠瓡瓢瓣瓤瓥瓦瓧瓨瓩瓪瓫瓬瓭瓮瓯瓰瓱瓲瓳瓴瓵瓶瓷瓸瓹瓺瓻瓼瓽瓾瓿甀ç”甂甃甄甅甆甇甈甉甊甋甌ç”甎ç”ç”甑甒甓甔甕甖甗甘甙甚甛甜ç”甞生甠甡","產産甤甥甦甧用甩甪甫甬甭甮甯田由甲申甴电甶男甸甹町画甼甽甾甿畀ç•ç•‚畃畄畅畆畇畈畉畊畋界ç•ç•Žç•ç•ç•‘畒畓畔畕畖畗畘留畚畛畜ç•ç•žç•Ÿç• ç•¡ç•¢ç•£ç•¤ç•¥ç•¦ç•§ç•¨ç•©ç•ªç•«ç•¬ç•­ç•®ç•¯ç•°ç•±ç•²ç•³ç•´ç•µç•¶ç•·ç•¸ç•¹ç•ºç•»ç•¼ç•½ç•¾ç•¿ç–€ç–疂疃疄疅疆疇疈疉疊疋疌ç–ç–Žç–ç–疑疒疓疔疕疖疗疘疙疚疛疜ç–疞疟疠疡疢疣疤疥疦疧疨疩疪疫疬疭疮疯疰疱疲疳疴疵疶疷疸疹疺疻疼疽疾疿痀ç—痂痃痄病痆症痈痉痊痋痌ç—ç—Žç—ç—痑痒痓痔痕痖痗痘痙痚痛痜ç—痞痟痠痡痢痣痤痥痦痧痨痩痪痫痬痭痮痯痰痱痲痳痴痵痶痷痸痹痺痻痼痽痾痿瘀ç˜ç˜‚瘃瘄瘅瘆瘇瘈瘉瘊瘋瘌ç˜ç˜Žç˜ç˜ç˜‘瘒瘓瘔瘕瘖瘗瘘瘙瘚瘛瘜ç˜ç˜žç˜Ÿç˜ ç˜¡","瘢瘣瘤瘥瘦瘧瘨瘩瘪瘫瘬瘭瘮瘯瘰瘱瘲瘳瘴瘵瘶瘷瘸瘹瘺瘻瘼瘽瘾瘿癀ç™ç™‚癃癄癅癆癇癈癉癊癋癌ç™ç™Žç™ç™ç™‘癒癓癔癕癖癗癘癙癚癛癜ç™ç™žç™Ÿç™ ç™¡ç™¢ç™£ç™¤ç™¥ç™¦ç™§ç™¨ç™©ç™ªç™«ç™¬ç™­ç™®ç™¯ç™°ç™±ç™²ç™³ç™´ç™µç™¶ç™·ç™¸ç™¹ç™ºç™»ç™¼ç™½ç™¾ç™¿çš€çšçš‚皃的皅皆皇皈皉皊皋皌çšçšŽçšçšçš‘皒皓皔皕皖皗皘皙皚皛皜çšçšžçšŸçš çš¡çš¢çš£çš¤çš¥çš¦çš§çš¨çš©çšªçš«çš¬çš­çš®çš¯çš°çš±çš²çš³çš´çšµçš¶çš·çš¸çš¹çšºçš»çš¼çš½çš¾çš¿ç›€ç›ç›‚盃盄盅盆盇盈盉益盋盌ç›ç›Žç›ç›ç›‘盒盓盔盕盖盗盘盙盚盛盜ç›ç›žç›Ÿç› ç›¡ç›¢ç›£ç›¤ç›¥ç›¦ç›§ç›¨ç›©ç›ªç›«ç›¬ç›­ç›®ç›¯ç›°ç›±ç›²ç›³ç›´ç›µç›¶ç›·ç›¸ç›¹ç›ºç›»ç›¼ç›½ç›¾ç›¿çœ€çœçœ‚眃眄眅眆眇眈眉眊看県çœçœŽçœçœçœ‘眒眓眔眕眖眗眘眙眚眛眜çœçœžçœŸçœ çœ¡","眢眣眤眥眦眧眨眩眪眫眬眭眮眯眰眱眲眳眴眵眶眷眸眹眺眻眼眽眾眿ç€çç‚çƒç„ç…ç†ç‡çˆç‰çŠç‹çŒççŽççç‘ç’ç“ç”ç•ç–ç—ç˜ç™çšç›çœççžçŸç ç¡ç¢ç£ç¤ç¥ç¦ç§ç¨ç©çªç«ç¬ç­ç®ç¯ç°ç±ç²ç³ç´çµç¶ç·ç¸ç¹çºç»ç¼ç½ç¾ç¿çž€çžçž‚瞃瞄瞅瞆瞇瞈瞉瞊瞋瞌çžçžŽçžçžçž‘瞒瞓瞔瞕瞖瞗瞘瞙瞚瞛瞜çžçžžçžŸçž çž¡çž¢çž£çž¤çž¥çž¦çž§çž¨çž©çžªçž«çž¬çž­çž®çž¯çž°çž±çž²çž³çž´çžµçž¶çž·çž¸çž¹çžºçž»çž¼çž½çž¾çž¿çŸ€çŸçŸ‚矃矄矅矆矇矈矉矊矋矌çŸçŸŽçŸçŸçŸ‘矒矓矔矕矖矗矘矙矚矛矜çŸçŸžçŸŸçŸ çŸ¡çŸ¢çŸ£çŸ¤çŸ¥çŸ¦çŸ§çŸ¨çŸ©çŸªçŸ«çŸ¬çŸ­çŸ®çŸ¯çŸ°çŸ±çŸ²çŸ³çŸ´çŸµçŸ¶çŸ·çŸ¸çŸ¹çŸºçŸ»çŸ¼çŸ½çŸ¾çŸ¿ç €ç ç ‚砃砄砅砆砇砈砉砊砋砌ç ç Žç ç ç ‘砒砓研砕砖砗砘砙砚砛砜ç ç žç Ÿç  ç ¡","砢砣砤砥砦砧砨砩砪砫砬砭砮砯砰砱砲砳破砵砶砷砸砹砺砻砼砽砾砿础ç¡ç¡‚硃硄硅硆硇硈硉硊硋硌ç¡ç¡Žç¡ç¡ç¡‘硒硓硔硕硖硗硘硙硚硛硜ç¡ç¡žç¡Ÿç¡ ç¡¡ç¡¢ç¡£ç¡¤ç¡¥ç¡¦ç¡§ç¡¨ç¡©ç¡ªç¡«ç¡¬ç¡­ç¡®ç¡¯ç¡°ç¡±ç¡²ç¡³ç¡´ç¡µç¡¶ç¡·ç¡¸ç¡¹ç¡ºç¡»ç¡¼ç¡½ç¡¾ç¡¿ç¢€ç¢ç¢‚碃碄碅碆碇碈碉碊碋碌ç¢ç¢Žç¢ç¢ç¢‘碒碓碔碕碖碗碘碙碚碛碜ç¢ç¢žç¢Ÿç¢ ç¢¡ç¢¢ç¢£ç¢¤ç¢¥ç¢¦ç¢§ç¢¨ç¢©ç¢ªç¢«ç¢¬ç¢­ç¢®ç¢¯ç¢°ç¢±ç¢²ç¢³ç¢´ç¢µç¢¶ç¢·ç¢¸ç¢¹ç¢ºç¢»ç¢¼ç¢½ç¢¾ç¢¿ç£€ç£ç£‚磃磄磅磆磇磈磉磊磋磌ç£ç£Žç£ç£ç£‘磒磓磔磕磖磗磘磙磚磛磜ç£ç£žç£Ÿç£ ç£¡ç£¢ç££ç£¤ç£¥ç£¦ç£§ç£¨ç£©ç£ªç£«ç£¬ç£­ç£®ç£¯ç£°ç£±ç£²ç£³ç£´ç£µç£¶ç£·ç£¸ç£¹ç£ºç£»ç£¼ç£½ç£¾ç£¿ç¤€ç¤ç¤‚礃礄礅礆礇礈礉礊礋礌ç¤ç¤Žç¤ç¤ç¤‘礒礓礔礕礖礗礘礙礚礛礜ç¤ç¤žç¤Ÿç¤ ç¤¡","礢礣礤礥礦礧礨礩礪礫礬礭礮礯礰礱礲礳礴礵礶礷礸礹示礻礼礽社礿祀ç¥ç¥‚祃祄祅祆祇祈祉祊祋祌ç¥ç¥Žç¥ç¥ç¥‘祒祓祔祕祖祗祘祙祚祛祜ç¥ç¥žç¥Ÿç¥ ç¥¡ç¥¢ç¥£ç¥¤ç¥¥ç¥¦ç¥§ç¥¨ç¥©ç¥ªç¥«ç¥¬ç¥­ç¥®ç¥¯ç¥°ç¥±ç¥²ç¥³ç¥´ç¥µç¥¶ç¥·ç¥¸ç¥¹ç¥ºç¥»ç¥¼ç¥½ç¥¾ç¥¿ç¦€ç¦ç¦‚禃禄禅禆禇禈禉禊禋禌ç¦ç¦Žç¦ç¦ç¦‘禒禓禔禕禖禗禘禙禚禛禜ç¦ç¦žç¦Ÿç¦ ç¦¡ç¦¢ç¦£ç¦¤ç¦¥ç¦¦ç¦§ç¦¨ç¦©ç¦ªç¦«ç¦¬ç¦­ç¦®ç¦¯ç¦°ç¦±ç¦²ç¦³ç¦´ç¦µç¦¶ç¦·ç¦¸ç¦¹ç¦ºç¦»ç¦¼ç¦½ç¦¾ç¦¿ç§€ç§ç§‚秃秄秅秆秇秈秉秊秋秌ç§ç§Žç§ç§ç§‘秒秓秔秕秖秗秘秙秚秛秜ç§ç§žç§Ÿç§ ç§¡ç§¢ç§£ç§¤ç§¥ç§¦ç§§ç§¨ç§©ç§ªç§«ç§¬ç§­ç§®ç§¯ç§°ç§±ç§²ç§³ç§´ç§µç§¶ç§·ç§¸ç§¹ç§ºç§»ç§¼ç§½ç§¾ç§¿ç¨€ç¨ç¨‚稃稄稅稆稇稈稉稊程稌ç¨ç¨Žç¨ç¨ç¨‘稒稓稔稕稖稗稘稙稚稛稜ç¨ç¨žç¨Ÿç¨ ç¨¡","稢稣稤稥稦稧稨稩稪稫稬稭種稯稰稱稲稳稴稵稶稷稸稹稺稻稼稽稾稿穀ç©ç©‚穃穄穅穆穇穈穉穊穋穌ç©ç©Žç©ç©ç©‘穒穓穔穕穖穗穘穙穚穛穜ç©ç©žç©Ÿç© ç©¡ç©¢ç©£ç©¤ç©¥ç©¦ç©§ç©¨ç©©ç©ªç©«ç©¬ç©­ç©®ç©¯ç©°ç©±ç©²ç©³ç©´ç©µç©¶ç©·ç©¸ç©¹ç©ºç©»ç©¼ç©½ç©¾ç©¿çª€çªçª‚窃窄窅窆窇窈窉窊窋窌çªçªŽçªçªçª‘窒窓窔窕窖窗窘窙窚窛窜çªçªžçªŸçª çª¡çª¢çª£çª¤çª¥çª¦çª§çª¨çª©çªªçª«çª¬çª­çª®çª¯çª°çª±çª²çª³çª´çªµçª¶çª·çª¸çª¹çªºçª»çª¼çª½çª¾çª¿ç«€ç«ç«‚竃竄竅竆竇竈竉竊立竌ç«ç«Žç«ç«ç«‘竒竓竔竕竖竗竘站竚竛竜ç«ç«žç«Ÿç« ç«¡ç«¢ç«£ç«¤ç«¥ç«¦ç«§ç«¨ç«©ç«ªç««ç«¬ç«­ç«®ç«¯ç«°ç«±ç«²ç«³ç«´ç«µç«¶ç«·ç«¸ç«¹ç«ºç«»ç«¼ç«½ç«¾ç«¿ç¬€ç¬ç¬‚笃笄笅笆笇笈笉笊笋笌ç¬ç¬Žç¬ç¬ç¬‘笒笓笔笕笖笗笘笙笚笛笜ç¬ç¬žç¬Ÿç¬ ç¬¡","笢笣笤笥符笧笨笩笪笫第笭笮笯笰笱笲笳笴笵笶笷笸笹笺笻笼笽笾笿筀ç­ç­‚筃筄筅筆筇筈等筊筋筌ç­ç­Žç­ç­ç­‘筒筓答筕策筗筘筙筚筛筜ç­ç­žç­Ÿç­ ç­¡ç­¢ç­£ç­¤ç­¥ç­¦ç­§ç­¨ç­©ç­ªç­«ç­¬ç­­ç­®ç­¯ç­°ç­±ç­²ç­³ç­´ç­µç­¶ç­·ç­¸ç­¹ç­ºç­»ç­¼ç­½ç­¾ç­¿ç®€ç®ç®‚箃箄箅箆箇箈箉箊箋箌ç®ç®Žç®ç®ç®‘箒箓箔箕箖算箘箙箚箛箜ç®ç®žç®Ÿç® ç®¡ç®¢ç®£ç®¤ç®¥ç®¦ç®§ç®¨ç®©ç®ªç®«ç®¬ç®­ç®®ç®¯ç®°ç®±ç®²ç®³ç®´ç®µç®¶ç®·ç®¸ç®¹ç®ºç®»ç®¼ç®½ç®¾ç®¿ç¯€ç¯ç¯‚篃範篅篆篇篈築篊篋篌ç¯ç¯Žç¯ç¯ç¯‘篒篓篔篕篖篗篘篙篚篛篜ç¯ç¯žç¯Ÿç¯ ç¯¡ç¯¢ç¯£ç¯¤ç¯¥ç¯¦ç¯§ç¯¨ç¯©ç¯ªç¯«ç¯¬ç¯­ç¯®ç¯¯ç¯°ç¯±ç¯²ç¯³ç¯´ç¯µç¯¶ç¯·ç¯¸ç¯¹ç¯ºç¯»ç¯¼ç¯½ç¯¾ç¯¿ç°€ç°ç°‚簃簄簅簆簇簈簉簊簋簌ç°ç°Žç°ç°ç°‘簒簓簔簕簖簗簘簙簚簛簜ç°ç°žç°Ÿç° ç°¡","簢簣簤簥簦簧簨簩簪簫簬簭簮簯簰簱簲簳簴簵簶簷簸簹簺簻簼簽簾簿籀ç±ç±‚籃籄籅籆籇籈籉籊籋籌ç±ç±Žç±ç±ç±‘籒籓籔籕籖籗籘籙籚籛籜ç±ç±žç±Ÿç± ç±¡ç±¢ç±£ç±¤ç±¥ç±¦ç±§ç±¨ç±©ç±ªç±«ç±¬ç±­ç±®ç±¯ç±°ç±±ç±²ç±³ç±´ç±µç±¶ç±·ç±¸ç±¹ç±ºç±»ç±¼ç±½ç±¾ç±¿ç²€ç²ç²‚粃粄粅粆粇粈粉粊粋粌ç²ç²Žç²ç²ç²‘粒粓粔粕粖粗粘粙粚粛粜ç²ç²žç²Ÿç² ç²¡ç²¢ç²£ç²¤ç²¥ç²¦ç²§ç²¨ç²©ç²ªç²«ç²¬ç²­ç²®ç²¯ç²°ç²±ç²²ç²³ç²´ç²µç²¶ç²·ç²¸ç²¹ç²ºç²»ç²¼ç²½ç²¾ç²¿ç³€ç³ç³‚糃糄糅糆糇糈糉糊糋糌ç³ç³Žç³ç³ç³‘糒糓糔糕糖糗糘糙糚糛糜ç³ç³žç³Ÿç³ ç³¡ç³¢ç³£ç³¤ç³¥ç³¦ç³§ç³¨ç³©ç³ªç³«ç³¬ç³­ç³®ç³¯ç³°ç³±ç³²ç³³ç³´ç³µç³¶ç³·ç³¸ç³¹ç³ºç³»ç³¼ç³½ç³¾ç³¿ç´€ç´ç´‚紃約紅紆紇紈紉紊紋紌ç´ç´Žç´ç´ç´‘紒紓純紕紖紗紘紙級紛紜ç´ç´žç´Ÿç´ ç´¡","索紣紤紥紦紧紨紩紪紫紬紭紮累細紱紲紳紴紵紶紷紸紹紺紻紼紽紾紿絀çµçµ‚絃組絅絆絇絈絉絊絋経çµçµŽçµçµçµ‘絒絓絔絕絖絗絘絙絚絛絜çµçµžçµŸçµ çµ¡çµ¢çµ£çµ¤çµ¥çµ¦çµ§çµ¨çµ©çµªçµ«çµ¬çµ­çµ®çµ¯çµ°çµ±çµ²çµ³çµ´çµµçµ¶çµ·çµ¸çµ¹çµºçµ»çµ¼çµ½çµ¾çµ¿ç¶€ç¶ç¶‚綃綄綅綆綇綈綉綊綋綌ç¶ç¶Žç¶ç¶ç¶‘綒經綔綕綖綗綘継続綛綜ç¶ç¶žç¶Ÿç¶ ç¶¡ç¶¢ç¶£ç¶¤ç¶¥ç¶¦ç¶§ç¶¨ç¶©ç¶ªç¶«ç¶¬ç¶­ç¶®ç¶¯ç¶°ç¶±ç¶²ç¶³ç¶´ç¶µç¶¶ç¶·ç¶¸ç¶¹ç¶ºç¶»ç¶¼ç¶½ç¶¾ç¶¿ç·€ç·ç·‚緃緄緅緆緇緈緉緊緋緌ç·ç·Žç·ç·ç·‘緒緓緔緕緖緗緘緙線緛緜ç·ç·žç·Ÿç· ç·¡ç·¢ç·£ç·¤ç·¥ç·¦ç·§ç·¨ç·©ç·ªç·«ç·¬ç·­ç·®ç·¯ç·°ç·±ç·²ç·³ç·´ç·µç·¶ç··ç·¸ç·¹ç·ºç·»ç·¼ç·½ç·¾ç·¿ç¸€ç¸ç¸‚縃縄縅縆縇縈縉縊縋縌ç¸ç¸Žç¸ç¸ç¸‘縒縓縔縕縖縗縘縙縚縛縜ç¸ç¸žç¸Ÿç¸ ç¸¡","縢縣縤縥縦縧縨縩縪縫縬縭縮縯縰縱縲縳縴縵縶縷縸縹縺縻縼總績縿繀ç¹ç¹‚繃繄繅繆繇繈繉繊繋繌ç¹ç¹Žç¹ç¹ç¹‘繒繓織繕繖繗繘繙繚繛繜ç¹ç¹žç¹Ÿç¹ ç¹¡ç¹¢ç¹£ç¹¤ç¹¥ç¹¦ç¹§ç¹¨ç¹©ç¹ªç¹«ç¹¬ç¹­ç¹®ç¹¯ç¹°ç¹±ç¹²ç¹³ç¹´ç¹µç¹¶ç¹·ç¹¸ç¹¹ç¹ºç¹»ç¹¼ç¹½ç¹¾ç¹¿çº€çºçº‚纃纄纅纆纇纈纉纊纋續çºçºŽçºçºçº‘纒纓纔纕纖纗纘纙纚纛纜çºçºžçºŸçº çº¡çº¢çº£çº¤çº¥çº¦çº§çº¨çº©çºªçº«çº¬çº­çº®çº¯çº°çº±çº²çº³çº´çºµçº¶çº·çº¸çº¹çººçº»çº¼çº½çº¾çº¿ç»€ç»ç»‚练组绅细织终绉绊绋绌ç»ç»Žç»ç»ç»‘绒结绔绕绖绗绘给绚绛络ç»ç»žç»Ÿç» ç»¡ç»¢ç»£ç»¤ç»¥ç»¦ç»§ç»¨ç»©ç»ªç»«ç»¬ç»­ç»®ç»¯ç»°ç»±ç»²ç»³ç»´ç»µç»¶ç»·ç»¸ç»¹ç»ºç»»ç»¼ç»½ç»¾ç»¿ç¼€ç¼ç¼‚缃缄缅缆缇缈缉缊缋缌ç¼ç¼Žç¼ç¼ç¼‘缒缓缔缕编缗缘缙缚缛缜ç¼ç¼žç¼Ÿç¼ ç¼¡","缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵缶缷缸缹缺缻缼缽缾缿罀ç½ç½‚罃罄罅罆罇罈罉罊罋罌ç½ç½Žç½ç½ç½‘罒罓罔罕罖罗罘罙罚罛罜ç½ç½žç½Ÿç½ ç½¡ç½¢ç½£ç½¤ç½¥ç½¦ç½§ç½¨ç½©ç½ªç½«ç½¬ç½­ç½®ç½¯ç½°ç½±ç½²ç½³ç½´ç½µç½¶ç½·ç½¸ç½¹ç½ºç½»ç½¼ç½½ç½¾ç½¿ç¾€ç¾ç¾‚羃羄羅羆羇羈羉羊羋羌ç¾ç¾Žç¾ç¾ç¾‘羒羓羔羕羖羗羘羙羚羛羜ç¾ç¾žç¾Ÿç¾ ç¾¡ç¾¢ç¾£ç¾¤ç¾¥ç¾¦ç¾§ç¾¨ç¾©ç¾ªç¾«ç¾¬ç¾­ç¾®ç¾¯ç¾°ç¾±ç¾²ç¾³ç¾´ç¾µç¾¶ç¾·ç¾¸ç¾¹ç¾ºç¾»ç¾¼ç¾½ç¾¾ç¾¿ç¿€ç¿ç¿‚翃翄翅翆翇翈翉翊翋翌ç¿ç¿Žç¿ç¿ç¿‘習翓翔翕翖翗翘翙翚翛翜ç¿ç¿žç¿Ÿç¿ ç¿¡ç¿¢ç¿£ç¿¤ç¿¥ç¿¦ç¿§ç¿¨ç¿©ç¿ªç¿«ç¿¬ç¿­ç¿®ç¿¯ç¿°ç¿±ç¿²ç¿³ç¿´ç¿µç¿¶ç¿·ç¿¸ç¿¹ç¿ºç¿»ç¿¼ç¿½ç¿¾ç¿¿è€€è€è€‚考耄者耆耇耈耉耊耋而è€è€Žè€è€è€‘耒耓耔耕耖耗耘耙耚耛耜è€è€žè€Ÿè€ è€¡","耢耣耤耥耦耧耨耩耪耫耬耭耮耯耰耱耲耳耴耵耶耷耸耹耺耻耼耽耾耿è€èè‚èƒè„è…è†è‡èˆè‰èŠè‹èŒèèŽèèè‘è’è“è”è•è–è—è˜è™èšè›èœèèžèŸè è¡è¢è£è¤è¥è¦è§è¨è©èªè«è¬è­è®è¯è°è±è²è³è´èµè¶è·è¸è¹èºè»è¼è½è¾è¿è‚€è‚肂肃肄肅肆肇肈肉肊肋肌è‚è‚Žè‚è‚肑肒肓肔肕肖肗肘肙肚肛肜è‚肞肟肠股肢肣肤肥肦肧肨肩肪肫肬肭肮肯肰肱育肳肴肵肶肷肸肹肺肻肼肽肾肿胀èƒèƒ‚胃胄胅胆胇胈胉胊胋背èƒèƒŽèƒèƒèƒ‘胒胓胔胕胖胗胘胙胚胛胜èƒèƒžèƒŸèƒ èƒ¡èƒ¢èƒ£èƒ¤èƒ¥èƒ¦èƒ§èƒ¨èƒ©èƒªèƒ«èƒ¬èƒ­èƒ®èƒ¯èƒ°èƒ±èƒ²èƒ³èƒ´èƒµèƒ¶èƒ·èƒ¸èƒ¹èƒºèƒ»èƒ¼èƒ½èƒ¾èƒ¿è„€è„脂脃脄脅脆脇脈脉脊脋脌è„è„Žè„è„脑脒脓脔脕脖脗脘脙脚脛脜è„è„žè„Ÿè„ è„¡","脢脣脤脥脦脧脨脩脪脫脬脭脮脯脰脱脲脳脴脵脶脷脸脹脺脻脼脽脾脿腀è…腂腃腄腅腆腇腈腉腊腋腌è…è…Žè…è…腑腒腓腔腕腖腗腘腙腚腛腜è…腞腟腠腡腢腣腤腥腦腧腨腩腪腫腬腭腮腯腰腱腲腳腴腵腶腷腸腹腺腻腼腽腾腿膀è†è†‚膃膄膅膆膇膈膉膊膋膌è†è†Žè†è†è†‘膒膓膔膕膖膗膘膙膚膛膜è†è†žè†Ÿè† è†¡è†¢è†£è†¤è†¥è†¦è†§è†¨è†©è†ªè†«è†¬è†­è†®è†¯è†°è†±è†²è†³è†´è†µè†¶è†·è†¸è†¹è†ºè†»è†¼è†½è†¾è†¿è‡€è‡è‡‚臃臄臅臆臇臈臉臊臋臌è‡è‡Žè‡è‡è‡‘臒臓臔臕臖臗臘臙臚臛臜è‡è‡žè‡Ÿè‡ è‡¡è‡¢è‡£è‡¤è‡¥è‡¦è‡§è‡¨è‡©è‡ªè‡«è‡¬è‡­è‡®è‡¯è‡°è‡±è‡²è‡³è‡´è‡µè‡¶è‡·è‡¸è‡¹è‡ºè‡»è‡¼è‡½è‡¾è‡¿èˆ€èˆèˆ‚舃舄舅舆與興舉舊舋舌èˆèˆŽèˆèˆèˆ‘舒舓舔舕舖舗舘舙舚舛舜èˆèˆžèˆŸèˆ èˆ¡","舢舣舤舥舦舧舨舩航舫般舭舮舯舰舱舲舳舴舵舶舷舸船舺舻舼舽舾舿艀è‰è‰‚艃艄艅艆艇艈艉艊艋艌è‰è‰Žè‰è‰è‰‘艒艓艔艕艖艗艘艙艚艛艜è‰è‰žè‰Ÿè‰ è‰¡è‰¢è‰£è‰¤è‰¥è‰¦è‰§è‰¨è‰©è‰ªè‰«è‰¬è‰­è‰®è‰¯è‰°è‰±è‰²è‰³è‰´è‰µè‰¶è‰·è‰¸è‰¹è‰ºè‰»è‰¼è‰½è‰¾è‰¿èŠ€èŠèŠ‚芃芄芅芆芇芈芉芊芋芌èŠèŠŽèŠèŠèŠ‘芒芓芔芕芖芗芘芙芚芛芜èŠèŠžèŠŸèŠ èŠ¡èŠ¢èŠ£èŠ¤èŠ¥èŠ¦èŠ§èŠ¨èŠ©èŠªèŠ«èŠ¬èŠ­èŠ®èŠ¯èŠ°èŠ±èŠ²èŠ³èŠ´èŠµèŠ¶èŠ·èŠ¸èŠ¹èŠºèŠ»èŠ¼èŠ½èŠ¾èŠ¿è‹€è‹è‹‚苃苄苅苆苇苈苉苊苋苌è‹è‹Žè‹è‹è‹‘苒苓苔苕苖苗苘苙苚苛苜è‹è‹žè‹Ÿè‹ è‹¡è‹¢è‹£è‹¤è‹¥è‹¦è‹§è‹¨è‹©è‹ªè‹«è‹¬è‹­è‹®è‹¯è‹°è‹±è‹²è‹³è‹´è‹µè‹¶è‹·è‹¸è‹¹è‹ºè‹»è‹¼è‹½è‹¾è‹¿èŒ€èŒèŒ‚范茄茅茆茇茈茉茊茋茌èŒèŒŽèŒèŒèŒ‘茒茓茔茕茖茗茘茙茚茛茜èŒèŒžèŒŸèŒ èŒ¡","茢茣茤茥茦茧茨茩茪茫茬茭茮茯茰茱茲茳茴茵茶茷茸茹茺茻茼茽茾茿è€èè‚èƒè„è…è†è‡èˆè‰èŠè‹èŒèèŽèèè‘è’è“è”è•è–è—è˜è™èšè›èœèèžèŸè è¡è¢è£è¤è¥è¦è§è¨è©èªè«è¬è­è®è¯è°è±è²è³è´èµè¶è·è¸è¹èºè»è¼è½è¾è¿èŽ€èŽèŽ‚莃莄莅莆莇莈莉莊莋莌èŽèŽŽèŽèŽèŽ‘莒莓莔莕莖莗莘莙莚莛莜èŽèŽžèŽŸèŽ èŽ¡èŽ¢èŽ£èŽ¤èŽ¥èŽ¦èŽ§èŽ¨èŽ©èŽªèŽ«èŽ¬èŽ­èŽ®èŽ¯èŽ°èŽ±èŽ²èŽ³èŽ´èŽµèŽ¶èŽ·èŽ¸èŽ¹èŽºèŽ»èŽ¼èŽ½èŽ¾èŽ¿è€èè‚èƒè„è…è†è‡èˆè‰èŠè‹èŒèèŽèèè‘è’è“è”è•è–è—è˜è™èšè›èœèèžèŸè è¡è¢è£è¤è¥è¦è§è¨è©èªè«è¬è­è®è¯è°è±è²è³è´èµè¶è·è¸è¹èºè»è¼è½è¾è¿è€èè‚èƒè„è…è†è‡èˆè‰èŠè‹èŒèèŽèèè‘è’è“è”è•è–è—è˜è™èšè›èœèèžèŸè è¡","è¢è£è¤è¥è¦è§è¨è©èªè«è¬è­è®è¯è°è±è²è³è´èµè¶è·è¸è¹èºè»è¼è½è¾è¿è‘€è‘葂葃葄葅葆葇葈葉葊葋葌è‘è‘Žè‘è‘葑葒葓葔葕葖著葘葙葚葛葜è‘葞葟葠葡葢董葤葥葦葧葨葩葪葫葬葭葮葯葰葱葲葳葴葵葶葷葸葹葺葻葼葽葾葿蒀è’蒂蒃蒄蒅蒆蒇蒈蒉蒊蒋蒌è’è’Žè’è’蒑蒒蒓蒔蒕蒖蒗蒘蒙蒚蒛蒜è’蒞蒟蒠蒡蒢蒣蒤蒥蒦蒧蒨蒩蒪蒫蒬蒭蒮蒯蒰蒱蒲蒳蒴蒵蒶蒷蒸蒹蒺蒻蒼蒽蒾蒿蓀è“蓂蓃蓄蓅蓆蓇蓈蓉蓊蓋蓌è“è“Žè“è“蓑蓒蓓蓔蓕蓖蓗蓘蓙蓚蓛蓜è“蓞蓟蓠蓡蓢蓣蓤蓥蓦蓧蓨蓩蓪蓫蓬蓭蓮蓯蓰蓱蓲蓳蓴蓵蓶蓷蓸蓹蓺蓻蓼蓽蓾蓿蔀è”蔂蔃蔄蔅蔆蔇蔈蔉蔊蔋蔌è”蔎è”è”蔑蔒蔓蔔蔕蔖蔗蔘蔙蔚蔛蔜è”蔞蔟蔠蔡","蔢蔣蔤蔥蔦蔧蔨蔩蔪蔫蔬蔭蔮蔯蔰蔱蔲蔳蔴蔵蔶蔷蔸蔹蔺蔻蔼蔽蔾蔿蕀è•è•‚蕃蕄蕅蕆蕇蕈蕉蕊蕋蕌è•è•Žè•è•è•‘蕒蕓蕔蕕蕖蕗蕘蕙蕚蕛蕜è•è•žè•Ÿè• è•¡è•¢è•£è•¤è•¥è•¦è•§è•¨è•©è•ªè•«è•¬è•­è•®è•¯è•°è•±è•²è•³è•´è•µè•¶è•·è•¸è•¹è•ºè•»è•¼è•½è•¾è•¿è–€è–薂薃薄薅薆薇薈薉薊薋薌è–è–Žè–è–薑薒薓薔薕薖薗薘薙薚薛薜è–薞薟薠薡薢薣薤薥薦薧薨薩薪薫薬薭薮薯薰薱薲薳薴薵薶薷薸薹薺薻薼薽薾薿藀è—藂藃藄藅藆藇藈藉藊藋藌è—è—Žè—è—藑藒藓藔藕藖藗藘藙藚藛藜è—藞藟藠藡藢藣藤藥藦藧藨藩藪藫藬藭藮藯藰藱藲藳藴藵藶藷藸藹藺藻藼藽藾藿蘀è˜è˜‚蘃蘄蘅蘆蘇蘈蘉蘊蘋蘌è˜è˜Žè˜è˜è˜‘蘒蘓蘔蘕蘖蘗蘘蘙蘚蘛蘜è˜è˜žè˜Ÿè˜ è˜¡","蘢蘣蘤蘥蘦蘧蘨蘩蘪蘫蘬蘭蘮蘯蘰蘱蘲蘳蘴蘵蘶蘷蘸蘹蘺蘻蘼蘽蘾蘿虀è™è™‚虃虄虅虆虇虈虉虊虋虌è™è™Žè™è™è™‘虒虓虔處虖虗虘虙虚虛虜è™è™žè™Ÿè™ è™¡è™¢è™£è™¤è™¥è™¦è™§è™¨è™©è™ªè™«è™¬è™­è™®è™¯è™°è™±è™²è™³è™´è™µè™¶è™·è™¸è™¹è™ºè™»è™¼è™½è™¾è™¿èš€èšèš‚蚃蚄蚅蚆蚇蚈蚉蚊蚋蚌èšèšŽèšèšèš‘蚒蚓蚔蚕蚖蚗蚘蚙蚚蚛蚜èšèšžèšŸèš èš¡èš¢èš£èš¤èš¥èš¦èš§èš¨èš©èšªèš«èš¬èš­èš®èš¯èš°èš±èš²èš³èš´èšµèš¶èš·èš¸èš¹èšºèš»èš¼èš½èš¾èš¿è›€è›è›‚蛃蛄蛅蛆蛇蛈蛉蛊蛋蛌è›è›Žè›è›è›‘蛒蛓蛔蛕蛖蛗蛘蛙蛚蛛蛜è›è›žè›Ÿè› è›¡è›¢è›£è›¤è›¥è›¦è›§è›¨è›©è›ªè›«è›¬è›­è›®è›¯è›°è›±è›²è›³è›´è›µè›¶è›·è›¸è›¹è›ºè›»è›¼è›½è›¾è›¿èœ€èœèœ‚蜃蜄蜅蜆蜇蜈蜉蜊蜋蜌èœèœŽèœèœèœ‘蜒蜓蜔蜕蜖蜗蜘蜙蜚蜛蜜èœèœžèœŸèœ èœ¡","蜢蜣蜤蜥蜦蜧蜨蜩蜪蜫蜬蜭蜮蜯蜰蜱蜲蜳蜴蜵蜶蜷蜸蜹蜺蜻蜼蜽蜾蜿è€èè‚èƒè„è…è†è‡èˆè‰èŠè‹èŒèèŽèèè‘è’è“è”è•è–è—è˜è™èšè›èœèèžèŸè è¡è¢è£è¤è¥è¦è§è¨è©èªè«è¬è­è®è¯è°è±è²è³è´èµè¶è·è¸è¹èºè»è¼è½è¾è¿èž€èžèž‚螃螄螅螆螇螈螉螊螋螌èžèžŽèžèžèž‘螒螓螔螕螖螗螘螙螚螛螜èžèžžèžŸèž èž¡èž¢èž£èž¤èž¥èž¦èž§èž¨èž©èžªèž«èž¬èž­èž®èž¯èž°èž±èž²èž³èž´èžµèž¶èž·èž¸èž¹èžºèž»èž¼èž½èž¾èž¿èŸ€èŸèŸ‚蟃蟄蟅蟆蟇蟈蟉蟊蟋蟌èŸèŸŽèŸèŸèŸ‘蟒蟓蟔蟕蟖蟗蟘蟙蟚蟛蟜èŸèŸžèŸŸèŸ èŸ¡èŸ¢èŸ£èŸ¤èŸ¥èŸ¦èŸ§èŸ¨èŸ©èŸªèŸ«èŸ¬èŸ­èŸ®èŸ¯èŸ°èŸ±èŸ²èŸ³èŸ´èŸµèŸ¶èŸ·èŸ¸èŸ¹èŸºèŸ»èŸ¼èŸ½èŸ¾èŸ¿è €è è ‚蠃蠄蠅蠆蠇蠈蠉蠊蠋蠌è è Žè è è ‘蠒蠓蠔蠕蠖蠗蠘蠙蠚蠛蠜è è žè Ÿè  è ¡","蠢蠣蠤蠥蠦蠧蠨蠩蠪蠫蠬蠭蠮蠯蠰蠱蠲蠳蠴蠵蠶蠷蠸蠹蠺蠻蠼蠽蠾蠿血è¡è¡‚衃衄衅衆衇衈衉衊衋行è¡è¡Žè¡è¡è¡‘衒術衔衕衖街衘衙衚衛衜è¡è¡žè¡Ÿè¡ è¡¡è¡¢è¡£è¡¤è¡¥è¡¦è¡§è¡¨è¡©è¡ªè¡«è¡¬è¡­è¡®è¡¯è¡°è¡±è¡²è¡³è¡´è¡µè¡¶è¡·è¡¸è¡¹è¡ºè¡»è¡¼è¡½è¡¾è¡¿è¢€è¢è¢‚袃袄袅袆袇袈袉袊袋袌è¢è¢Žè¢è¢è¢‘袒袓袔袕袖袗袘袙袚袛袜è¢è¢žè¢Ÿè¢ è¢¡è¢¢è¢£è¢¤è¢¥è¢¦è¢§è¢¨è¢©è¢ªè¢«è¢¬è¢­è¢®è¢¯è¢°è¢±è¢²è¢³è¢´è¢µè¢¶è¢·è¢¸è¢¹è¢ºè¢»è¢¼è¢½è¢¾è¢¿è£€è£è£‚裃裄装裆裇裈裉裊裋裌è£è£Žè£è£è£‘裒裓裔裕裖裗裘裙裚裛補è£è£žè£Ÿè£ è£¡è£¢è££è£¤è£¥è£¦è£§è£¨è£©è£ªè£«è£¬è£­è£®è£¯è£°è£±è£²è£³è£´è£µè£¶è£·è£¸è£¹è£ºè£»è£¼è£½è£¾è£¿è¤€è¤è¤‚褃褄褅褆複褈褉褊褋褌è¤è¤Žè¤è¤è¤‘褒褓褔褕褖褗褘褙褚褛褜è¤è¤žè¤Ÿè¤ è¤¡","褢褣褤褥褦褧褨褩褪褫褬褭褮褯褰褱褲褳褴褵褶褷褸褹褺褻褼褽褾褿襀è¥è¥‚襃襄襅襆襇襈襉襊襋襌è¥è¥Žè¥è¥è¥‘襒襓襔襕襖襗襘襙襚襛襜è¥è¥žè¥Ÿè¥ è¥¡è¥¢è¥£è¥¤è¥¥è¥¦è¥§è¥¨è¥©è¥ªè¥«è¥¬è¥­è¥®è¥¯è¥°è¥±è¥²è¥³è¥´è¥µè¥¶è¥·è¥¸è¥¹è¥ºè¥»è¥¼è¥½è¥¾è¥¿è¦€è¦è¦‚覃覄覅覆覇覈覉覊見覌è¦è¦Žè¦è¦è¦‘覒覓覔覕視覗覘覙覚覛覜è¦è¦žè¦Ÿè¦ è¦¡è¦¢è¦£è¦¤è¦¥è¦¦è¦§è¦¨è¦©è¦ªè¦«è¦¬è¦­è¦®è¦¯è¦°è¦±è¦²è¦³è¦´è¦µè¦¶è¦·è¦¸è¦¹è¦ºè¦»è¦¼è¦½è¦¾è¦¿è§€è§è§‚觃规觅视觇览觉觊觋觌è§è§Žè§è§è§‘角觓觔觕觖觗觘觙觚觛觜è§è§žè§Ÿè§ è§¡è§¢è§£è§¤è§¥è§¦è§§è§¨è§©è§ªè§«è§¬è§­è§®è§¯è§°è§±è§²è§³è§´è§µè§¶è§·è§¸è§¹è§ºè§»è§¼è§½è§¾è§¿è¨€è¨è¨‚訃訄訅訆訇計訉訊訋訌è¨è¨Žè¨è¨è¨‘訒訓訔訕訖託記訙訚訛訜è¨è¨žè¨Ÿè¨ è¨¡","訢訣訤訥訦訧訨訩訪訫訬設訮訯訰許訲訳訴訵訶訷訸訹診註証訽訾訿詀è©è©‚詃詄詅詆詇詈詉詊詋詌è©è©Žè©è©è©‘詒詓詔評詖詗詘詙詚詛詜è©è©žè©Ÿè© è©¡è©¢è©£è©¤è©¥è©¦è©§è©¨è©©è©ªè©«è©¬è©­è©®è©¯è©°è©±è©²è©³è©´è©µè©¶è©·è©¸è©¹è©ºè©»è©¼è©½è©¾è©¿èª€èªèª‚誃誄誅誆誇誈誉誊誋誌èªèªŽèªèªèª‘誒誓誔誕誖誗誘誙誚誛誜èªèªžèªŸèª èª¡èª¢èª£èª¤èª¥èª¦èª§èª¨èª©èªªèª«èª¬èª­èª®èª¯èª°èª±èª²èª³èª´èªµèª¶èª·èª¸èª¹èªºèª»èª¼èª½èª¾èª¿è«€è«è«‚諃諄諅諆談諈諉諊請諌è«è«Žè«è«è«‘諒諓諔諕論諗諘諙諚諛諜è«è«žè«Ÿè« è«¡è«¢è«£è«¤è«¥è«¦è«§è«¨è«©è«ªè««è«¬è«­è«®è«¯è«°è«±è«²è«³è«´è«µè«¶è«·è«¸è«¹è«ºè«»è«¼è«½è«¾è«¿è¬€è¬è¬‚謃謄謅謆謇謈謉謊謋謌è¬è¬Žè¬è¬è¬‘謒謓謔謕謖謗謘謙謚講謜è¬è¬žè¬Ÿè¬ è¬¡","謢謣謤謥謦謧謨謩謪謫謬謭謮謯謰謱謲謳謴謵謶謷謸謹謺謻謼謽謾謿譀è­è­‚譃譄譅譆譇譈證譊譋譌è­è­Žè­è­è­‘譒譓譔譕譖譗識譙譚譛譜è­è­žè­Ÿè­ è­¡è­¢è­£è­¤è­¥è­¦è­§è­¨è­©è­ªè­«è­¬è­­è­®è­¯è­°è­±è­²è­³è­´è­µè­¶è­·è­¸è­¹è­ºè­»è­¼è­½è­¾è­¿è®€è®è®‚讃讄讅讆讇讈讉變讋讌è®è®Žè®è®è®‘讒讓讔讕讖讗讘讙讚讛讜è®è®žè®Ÿè® è®¡è®¢è®£è®¤è®¥è®¦è®§è®¨è®©è®ªè®«è®¬è®­è®®è®¯è®°è®±è®²è®³è®´è®µè®¶è®·è®¸è®¹è®ºè®»è®¼è®½è®¾è®¿è¯€è¯è¯‚诃评诅识诇诈诉诊诋诌è¯è¯Žè¯è¯è¯‘诒诓诔试诖诗诘诙诚诛诜è¯è¯žè¯Ÿè¯ è¯¡è¯¢è¯£è¯¤è¯¥è¯¦è¯§è¯¨è¯©è¯ªè¯«è¯¬è¯­è¯®è¯¯è¯°è¯±è¯²è¯³è¯´è¯µè¯¶è¯·è¯¸è¯¹è¯ºè¯»è¯¼è¯½è¯¾è¯¿è°€è°è°‚调谄谅谆谇谈谉谊谋谌è°è°Žè°è°è°‘谒谓谔谕谖谗谘谙谚谛谜è°è°žè°Ÿè° è°¡","谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷谸谹谺谻谼谽谾谿豀è±è±‚豃豄豅豆豇豈豉豊豋豌è±è±Žè±è±è±‘豒豓豔豕豖豗豘豙豚豛豜è±è±žè±Ÿè± è±¡è±¢è±£è±¤è±¥è±¦è±§è±¨è±©è±ªè±«è±¬è±­è±®è±¯è±°è±±è±²è±³è±´è±µè±¶è±·è±¸è±¹è±ºè±»è±¼è±½è±¾è±¿è²€è²è²‚貃貄貅貆貇貈貉貊貋貌è²è²Žè²è²è²‘貒貓貔貕貖貗貘貙貚貛貜è²è²žè²Ÿè² è²¡è²¢è²£è²¤è²¥è²¦è²§è²¨è²©è²ªè²«è²¬è²­è²®è²¯è²°è²±è²²è²³è²´è²µè²¶è²·è²¸è²¹è²ºè²»è²¼è²½è²¾è²¿è³€è³è³‚賃賄賅賆資賈賉賊賋賌è³è³Žè³è³è³‘賒賓賔賕賖賗賘賙賚賛賜è³è³žè³Ÿè³ è³¡è³¢è³£è³¤è³¥è³¦è³§è³¨è³©è³ªè³«è³¬è³­è³®è³¯è³°è³±è³²è³³è³´è³µè³¶è³·è³¸è³¹è³ºè³»è³¼è³½è³¾è³¿è´€è´è´‚贃贄贅贆贇贈贉贊贋贌è´è´Žè´è´è´‘贒贓贔贕贖贗贘贙贚贛贜è´è´žè´Ÿè´ è´¡","财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀èµèµ‚赃资赅赆赇赈赉赊赋赌èµèµŽèµèµèµ‘赒赓赔赕赖赗赘赙赚赛赜èµèµžèµŸèµ èµ¡èµ¢èµ£èµ¤èµ¥èµ¦èµ§èµ¨èµ©èµªèµ«èµ¬èµ­èµ®èµ¯èµ°èµ±èµ²èµ³èµ´èµµèµ¶èµ·èµ¸èµ¹èµºèµ»èµ¼èµ½èµ¾èµ¿è¶€è¶è¶‚趃趄超趆趇趈趉越趋趌è¶è¶Žè¶è¶è¶‘趒趓趔趕趖趗趘趙趚趛趜è¶è¶žè¶Ÿè¶ è¶¡è¶¢è¶£è¶¤è¶¥è¶¦è¶§è¶¨è¶©è¶ªè¶«è¶¬è¶­è¶®è¶¯è¶°è¶±è¶²è¶³è¶´è¶µè¶¶è¶·è¶¸è¶¹è¶ºè¶»è¶¼è¶½è¶¾è¶¿è·€è·è·‚跃跄跅跆跇跈跉跊跋跌è·è·Žè·è·è·‘跒跓跔跕跖跗跘跙跚跛跜è·è·žè·Ÿè· è·¡è·¢è·£è·¤è·¥è·¦è·§è·¨è·©è·ªè·«è·¬è·­è·®è·¯è·°è·±è·²è·³è·´è·µè·¶è··è·¸è·¹è·ºè·»è·¼è·½è·¾è·¿è¸€è¸è¸‚踃踄踅踆踇踈踉踊踋踌è¸è¸Žè¸è¸è¸‘踒踓踔踕踖踗踘踙踚踛踜è¸è¸žè¸Ÿè¸ è¸¡","踢踣踤踥踦踧踨踩踪踫踬踭踮踯踰踱踲踳踴踵踶踷踸踹踺踻踼踽踾踿蹀è¹è¹‚蹃蹄蹅蹆蹇蹈蹉蹊蹋蹌è¹è¹Žè¹è¹è¹‘蹒蹓蹔蹕蹖蹗蹘蹙蹚蹛蹜è¹è¹žè¹Ÿè¹ è¹¡è¹¢è¹£è¹¤è¹¥è¹¦è¹§è¹¨è¹©è¹ªè¹«è¹¬è¹­è¹®è¹¯è¹°è¹±è¹²è¹³è¹´è¹µè¹¶è¹·è¹¸è¹¹è¹ºè¹»è¹¼è¹½è¹¾è¹¿èº€èºèº‚躃躄躅躆躇躈躉躊躋躌èºèºŽèºèºèº‘躒躓躔躕躖躗躘躙躚躛躜èºèºžèºŸèº èº¡èº¢èº£èº¤èº¥èº¦èº§èº¨èº©èºªèº«èº¬èº­èº®èº¯èº°èº±èº²èº³èº´èºµèº¶èº·èº¸èº¹èººèº»èº¼èº½èº¾èº¿è»€è»è»‚軃軄軅軆軇軈軉車軋軌è»è»Žè»è»è»‘軒軓軔軕軖軗軘軙軚軛軜è»è»žè»Ÿè» è»¡è»¢è»£è»¤è»¥è»¦è»§è»¨è»©è»ªè»«è»¬è»­è»®è»¯è»°è»±è»²è»³è»´è»µè»¶è»·è»¸è»¹è»ºè»»è»¼è»½è»¾è»¿è¼€è¼è¼‚較輄輅輆輇輈載輊輋輌è¼è¼Žè¼è¼è¼‘輒輓輔輕輖輗輘輙輚輛輜è¼è¼žè¼Ÿè¼ è¼¡","輢輣輤輥輦輧輨輩輪輫輬輭輮輯輰輱輲輳輴輵輶輷輸輹輺輻輼輽輾輿轀è½è½‚轃轄轅轆轇轈轉轊轋轌è½è½Žè½è½è½‘轒轓轔轕轖轗轘轙轚轛轜è½è½žè½Ÿè½ è½¡è½¢è½£è½¤è½¥è½¦è½§è½¨è½©è½ªè½«è½¬è½­è½®è½¯è½°è½±è½²è½³è½´è½µè½¶è½·è½¸è½¹è½ºè½»è½¼è½½è½¾è½¿è¾€è¾è¾‚较辄辅辆辇辈辉辊辋辌è¾è¾Žè¾è¾è¾‘辒输辔辕辖辗辘辙辚辛辜è¾è¾žè¾Ÿè¾ è¾¡è¾¢è¾£è¾¤è¾¥è¾¦è¾§è¾¨è¾©è¾ªè¾«è¾¬è¾­è¾®è¾¯è¾°è¾±è¾²è¾³è¾´è¾µè¾¶è¾·è¾¸è¾¹è¾ºè¾»è¾¼è¾½è¾¾è¾¿è¿€è¿è¿‚迃迄迅迆过迈迉迊迋迌è¿è¿Žè¿è¿è¿‘迒迓返迕迖迗还这迚进远è¿è¿žè¿Ÿè¿ è¿¡è¿¢è¿£è¿¤è¿¥è¿¦è¿§è¿¨è¿©è¿ªè¿«è¿¬è¿­è¿®è¿¯è¿°è¿±è¿²è¿³è¿´è¿µè¿¶è¿·è¿¸è¿¹è¿ºè¿»è¿¼è¿½è¿¾è¿¿é€€é€é€‚逃逄逅逆逇逈选逊逋逌é€é€Žé€é€é€‘递逓途逕逖逗逘這通逛逜é€é€žé€Ÿé€ é€¡","逢連逤逥逦逧逨逩逪逫逬逭逮逯逰週進逳逴逵逶逷逸逹逺逻逼逽逾逿é€éé‚éƒé„é…é†é‡éˆé‰éŠé‹éŒééŽééé‘é’é“é”é•é–é—é˜é™éšé›éœééžéŸé é¡é¢é£é¤é¥é¦é§é¨é©éªé«é¬é­é®é¯é°é±é²é³é´éµé¶é·é¸é¹éºé»é¼é½é¾é¿é‚€é‚邂邃還邅邆邇邈邉邊邋邌é‚é‚Žé‚é‚邑邒邓邔邕邖邗邘邙邚邛邜é‚邞邟邠邡邢那邤邥邦邧邨邩邪邫邬邭邮邯邰邱邲邳邴邵邶邷邸邹邺邻邼邽邾邿郀éƒéƒ‚郃郄郅郆郇郈郉郊郋郌éƒéƒŽéƒéƒéƒ‘郒郓郔郕郖郗郘郙郚郛郜éƒéƒžéƒŸéƒ éƒ¡éƒ¢éƒ£éƒ¤éƒ¥éƒ¦éƒ§éƒ¨éƒ©éƒªéƒ«éƒ¬éƒ­éƒ®éƒ¯éƒ°éƒ±éƒ²éƒ³éƒ´éƒµéƒ¶éƒ·éƒ¸éƒ¹éƒºéƒ»éƒ¼éƒ½éƒ¾éƒ¿é„€é„鄂鄃鄄鄅鄆鄇鄈鄉鄊鄋鄌é„é„Žé„é„鄑鄒鄓鄔鄕鄖鄗鄘鄙鄚鄛鄜é„é„žé„Ÿé„ é„¡","鄢鄣鄤鄥鄦鄧鄨鄩鄪鄫鄬鄭鄮鄯鄰鄱鄲鄳鄴鄵鄶鄷鄸鄹鄺鄻鄼鄽鄾鄿酀é…酂酃酄酅酆酇酈酉酊酋酌é…é…Žé…é…酑酒酓酔酕酖酗酘酙酚酛酜é…酞酟酠酡酢酣酤酥酦酧酨酩酪酫酬酭酮酯酰酱酲酳酴酵酶酷酸酹酺酻酼酽酾酿醀é†é†‚醃醄醅醆醇醈醉醊醋醌é†é†Žé†é†é†‘醒醓醔醕醖醗醘醙醚醛醜é†é†žé†Ÿé† é†¡é†¢é†£é†¤é†¥é†¦é†§é†¨é†©é†ªé†«é†¬é†­é†®é†¯é†°é†±é†²é†³é†´é†µé†¶é†·é†¸é†¹é†ºé†»é†¼é†½é†¾é†¿é‡€é‡é‡‚釃釄釅釆采釈釉释釋里é‡é‡Žé‡é‡é‡‘釒釓釔釕釖釗釘釙釚釛釜é‡é‡žé‡Ÿé‡ é‡¡é‡¢é‡£é‡¤é‡¥é‡¦é‡§é‡¨é‡©é‡ªé‡«é‡¬é‡­é‡®é‡¯é‡°é‡±é‡²é‡³é‡´é‡µé‡¶é‡·é‡¸é‡¹é‡ºé‡»é‡¼é‡½é‡¾é‡¿éˆ€éˆéˆ‚鈃鈄鈅鈆鈇鈈鈉鈊鈋鈌éˆéˆŽéˆéˆéˆ‘鈒鈓鈔鈕鈖鈗鈘鈙鈚鈛鈜éˆéˆžéˆŸéˆ éˆ¡","鈢鈣鈤鈥鈦鈧鈨鈩鈪鈫鈬鈭鈮鈯鈰鈱鈲鈳鈴鈵鈶鈷鈸鈹鈺鈻鈼鈽鈾鈿鉀é‰é‰‚鉃鉄鉅鉆鉇鉈鉉鉊鉋鉌é‰é‰Žé‰é‰é‰‘鉒鉓鉔鉕鉖鉗鉘鉙鉚鉛鉜é‰é‰žé‰Ÿé‰ é‰¡é‰¢é‰£é‰¤é‰¥é‰¦é‰§é‰¨é‰©é‰ªé‰«é‰¬é‰­é‰®é‰¯é‰°é‰±é‰²é‰³é‰´é‰µé‰¶é‰·é‰¸é‰¹é‰ºé‰»é‰¼é‰½é‰¾é‰¿éŠ€éŠéŠ‚銃銄銅銆銇銈銉銊銋銌éŠéŠŽéŠéŠéŠ‘銒銓銔銕銖銗銘銙銚銛銜éŠéŠžéŠŸéŠ éŠ¡éŠ¢éŠ£éŠ¤éŠ¥éŠ¦éŠ§éŠ¨éŠ©éŠªéŠ«éŠ¬éŠ­éŠ®éŠ¯éŠ°éŠ±éŠ²éŠ³éŠ´éŠµéŠ¶éŠ·éŠ¸éŠ¹éŠºéŠ»éŠ¼éŠ½éŠ¾éŠ¿é‹€é‹é‹‚鋃鋄鋅鋆鋇鋈鋉鋊鋋鋌é‹é‹Žé‹é‹é‹‘鋒鋓鋔鋕鋖鋗鋘鋙鋚鋛鋜é‹é‹žé‹Ÿé‹ é‹¡é‹¢é‹£é‹¤é‹¥é‹¦é‹§é‹¨é‹©é‹ªé‹«é‹¬é‹­é‹®é‹¯é‹°é‹±é‹²é‹³é‹´é‹µé‹¶é‹·é‹¸é‹¹é‹ºé‹»é‹¼é‹½é‹¾é‹¿éŒ€éŒéŒ‚錃錄錅錆錇錈錉錊錋錌éŒéŒŽéŒéŒéŒ‘錒錓錔錕錖錗錘錙錚錛錜éŒéŒžéŒŸéŒ éŒ¡","錢錣錤錥錦錧錨錩錪錫錬錭錮錯錰錱録錳錴錵錶錷錸錹錺錻錼錽錾錿é€éé‚éƒé„é…é†é‡éˆé‰éŠé‹éŒééŽééé‘é’é“é”é•é–é—é˜é™éšé›éœééžéŸé é¡é¢é£é¤é¥é¦é§é¨é©éªé«é¬é­é®é¯é°é±é²é³é´éµé¶é·é¸é¹éºé»é¼é½é¾é¿éŽ€éŽéŽ‚鎃鎄鎅鎆鎇鎈鎉鎊鎋鎌éŽéŽŽéŽéŽéŽ‘鎒鎓鎔鎕鎖鎗鎘鎙鎚鎛鎜éŽéŽžéŽŸéŽ éŽ¡éŽ¢éŽ£éŽ¤éŽ¥éŽ¦éŽ§éŽ¨éŽ©éŽªéŽ«éŽ¬éŽ­éŽ®éŽ¯éŽ°éŽ±éŽ²éŽ³éŽ´éŽµéŽ¶éŽ·éŽ¸éŽ¹éŽºéŽ»éŽ¼éŽ½éŽ¾éŽ¿é€éé‚éƒé„é…é†é‡éˆé‰éŠé‹éŒééŽééé‘é’é“é”é•é–é—é˜é™éšé›éœééžéŸé é¡é¢é£é¤é¥é¦é§é¨é©éªé«é¬é­é®é¯é°é±é²é³é´éµé¶é·é¸é¹éºé»é¼é½é¾é¿é€éé‚éƒé„é…é†é‡éˆé‰éŠé‹éŒééŽééé‘é’é“é”é•é–é—é˜é™éšé›éœééžéŸé é¡","é¢é£é¤é¥é¦é§é¨é©éªé«é¬é­é®é¯é°é±é²é³é´éµé¶é·é¸é¹éºé»é¼é½é¾é¿é‘€é‘鑂鑃鑄鑅鑆鑇鑈鑉鑊鑋鑌é‘é‘Žé‘é‘鑑鑒鑓鑔鑕鑖鑗鑘鑙鑚鑛鑜é‘鑞鑟鑠鑡鑢鑣鑤鑥鑦鑧鑨鑩鑪鑫鑬鑭鑮鑯鑰鑱鑲鑳鑴鑵鑶鑷鑸鑹鑺鑻鑼鑽鑾鑿钀é’钂钃钄钅钆钇针钉钊钋钌é’é’Žé’é’钑钒钓钔钕钖钗钘钙钚钛钜é’钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀é“铂铃铄铅铆铇铈铉铊铋铌é“é“Žé“é“铑铒铓铔铕铖铗铘铙铚铛铜é“铞铟铠铡铢铣铤铥铦铧铨铩铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销é”锂锃锄锅锆锇锈锉锊锋锌é”锎é”é”锑锒锓锔锕锖锗锘错锚锛锜é”锞锟锠锡","锢锣锤锥锦锧锨锩锪锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀é•é•‚镃镄镅镆镇镈镉镊镋镌é•é•Žé•é•é•‘镒镓镔镕镖镗镘镙镚镛镜é•é•žé•Ÿé• é•¡é•¢é•£é•¤é•¥é•¦é•§é•¨é•©é•ªé•«é•¬é•­é•®é•¯é•°é•±é•²é•³é•´é•µé•¶é•·é•¸é•¹é•ºé•»é•¼é•½é•¾é•¿é–€é–閂閃閄閅閆閇閈閉閊開閌é–é–Žé–é–閑閒間閔閕閖閗閘閙閚閛閜é–閞閟閠閡関閣閤閥閦閧閨閩閪閫閬閭閮閯閰閱閲閳閴閵閶閷閸閹閺閻閼閽閾閿闀é—闂闃闄闅闆闇闈闉闊闋闌é—é—Žé—é—闑闒闓闔闕闖闗闘闙闚闛關é—闞闟闠闡闢闣闤闥闦闧门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀é˜é˜‚阃阄阅阆阇阈阉阊阋阌é˜é˜Žé˜é˜é˜‘阒阓阔阕阖阗阘阙阚阛阜é˜é˜žé˜Ÿé˜ é˜¡","阢阣阤阥阦阧阨阩阪阫阬阭阮阯阰阱防阳阴阵阶阷阸阹阺阻阼阽阾阿陀é™é™‚陃附际陆陇陈陉陊陋陌é™é™Žé™é™é™‘陒陓陔陕陖陗陘陙陚陛陜é™é™žé™Ÿé™ é™¡é™¢é™£é™¤é™¥é™¦é™§é™¨é™©é™ªé™«é™¬é™­é™®é™¯é™°é™±é™²é™³é™´é™µé™¶é™·é™¸é™¹é™ºé™»é™¼é™½é™¾é™¿éš€éšéš‚隃隄隅隆隇隈隉隊隋隌éšéšŽéšéšéš‘隒隓隔隕隖隗隘隙隚際障éšéšžéšŸéš éš¡éš¢éš£éš¤éš¥éš¦éš§éš¨éš©éšªéš«éš¬éš­éš®éš¯éš°éš±éš²éš³éš´éšµéš¶éš·éš¸éš¹éšºéš»éš¼éš½éš¾éš¿é›€é›é›‚雃雄雅集雇雈雉雊雋雌é›é›Žé›é›é›‘雒雓雔雕雖雗雘雙雚雛雜é›é›žé›Ÿé› é›¡é›¢é›£é›¤é›¥é›¦é›§é›¨é›©é›ªé›«é›¬é›­é›®é›¯é›°é›±é›²é›³é›´é›µé›¶é›·é›¸é›¹é›ºé›»é›¼é›½é›¾é›¿éœ€éœéœ‚霃霄霅霆震霈霉霊霋霌éœéœŽéœéœéœ‘霒霓霔霕霖霗霘霙霚霛霜éœéœžéœŸéœ éœ¡","霢霣霤霥霦霧霨霩霪霫霬霭霮霯霰霱露霳霴霵霶霷霸霹霺霻霼霽霾霿é€éé‚éƒé„é…é†é‡éˆé‰éŠé‹éŒééŽééé‘é’é“é”é•é–é—é˜é™éšé›éœééžéŸé é¡é¢é£é¤é¥é¦é§é¨é©éªé«é¬é­é®é¯é°é±é²é³é´éµé¶é·é¸é¹éºé»é¼é½é¾é¿éž€éžéž‚鞃鞄鞅鞆鞇鞈鞉鞊鞋鞌éžéžŽéžéžéž‘鞒鞓鞔鞕鞖鞗鞘鞙鞚鞛鞜éžéžžéžŸéž éž¡éž¢éž£éž¤éž¥éž¦éž§éž¨éž©éžªéž«éž¬éž­éž®éž¯éž°éž±éž²éž³éž´éžµéž¶éž·éž¸éž¹éžºéž»éž¼éž½éž¾éž¿éŸ€éŸéŸ‚韃韄韅韆韇韈韉韊韋韌éŸéŸŽéŸéŸéŸ‘韒韓韔韕韖韗韘韙韚韛韜éŸéŸžéŸŸéŸ éŸ¡éŸ¢éŸ£éŸ¤éŸ¥éŸ¦éŸ§éŸ¨éŸ©éŸªéŸ«éŸ¬éŸ­éŸ®éŸ¯éŸ°éŸ±éŸ²éŸ³éŸ´éŸµéŸ¶éŸ·éŸ¸éŸ¹éŸºéŸ»éŸ¼éŸ½éŸ¾éŸ¿é €é é ‚頃頄項順頇須頉頊頋頌é é Žé é é ‘頒頓頔頕頖頗領頙頚頛頜é é žé Ÿé  é ¡","頢頣頤頥頦頧頨頩頪頫頬頭頮頯頰頱頲頳頴頵頶頷頸頹頺頻頼頽頾頿顀é¡é¡‚顃顄顅顆顇顈顉顊顋題é¡é¡Žé¡é¡é¡‘顒顓顔顕顖顗願顙顚顛顜é¡é¡žé¡Ÿé¡ é¡¡é¡¢é¡£é¡¤é¡¥é¡¦é¡§é¡¨é¡©é¡ªé¡«é¡¬é¡­é¡®é¡¯é¡°é¡±é¡²é¡³é¡´é¡µé¡¶é¡·é¡¸é¡¹é¡ºé¡»é¡¼é¡½é¡¾é¡¿é¢€é¢é¢‚颃预颅领颇颈颉颊颋颌é¢é¢Žé¢é¢é¢‘颒颓颔颕颖颗题颙颚颛颜é¢é¢žé¢Ÿé¢ é¢¡é¢¢é¢£é¢¤é¢¥é¢¦é¢§é¢¨é¢©é¢ªé¢«é¢¬é¢­é¢®é¢¯é¢°é¢±é¢²é¢³é¢´é¢µé¢¶é¢·é¢¸é¢¹é¢ºé¢»é¢¼é¢½é¢¾é¢¿é£€é£é£‚飃飄飅飆飇飈飉飊飋飌é£é£Žé£é£é£‘飒飓飔飕飖飗飘飙飚飛飜é£é£žé£Ÿé£ é£¡é£¢é££é£¤é£¥é£¦é£§é£¨é£©é£ªé£«é£¬é£­é£®é£¯é£°é£±é£²é£³é£´é£µé£¶é£·é£¸é£¹é£ºé£»é£¼é£½é£¾é£¿é¤€é¤é¤‚餃餄餅餆餇餈餉養餋餌é¤é¤Žé¤é¤é¤‘餒餓餔餕餖餗餘餙餚餛餜é¤é¤žé¤Ÿé¤ é¤¡","餢餣餤餥餦餧館餩餪餫餬餭餮餯餰餱餲餳餴餵餶餷餸餹餺餻餼餽餾餿饀é¥é¥‚饃饄饅饆饇饈饉饊饋饌é¥é¥Žé¥é¥é¥‘饒饓饔饕饖饗饘饙饚饛饜é¥é¥žé¥Ÿé¥ é¥¡é¥¢é¥£é¥¤é¥¥é¥¦é¥§é¥¨é¥©é¥ªé¥«é¥¬é¥­é¥®é¥¯é¥°é¥±é¥²é¥³é¥´é¥µé¥¶é¥·é¥¸é¥¹é¥ºé¥»é¥¼é¥½é¥¾é¥¿é¦€é¦é¦‚馃馄馅馆馇馈馉馊馋馌é¦é¦Žé¦é¦é¦‘馒馓馔馕首馗馘香馚馛馜é¦é¦žé¦Ÿé¦ é¦¡é¦¢é¦£é¦¤é¦¥é¦¦é¦§é¦¨é¦©é¦ªé¦«é¦¬é¦­é¦®é¦¯é¦°é¦±é¦²é¦³é¦´é¦µé¦¶é¦·é¦¸é¦¹é¦ºé¦»é¦¼é¦½é¦¾é¦¿é§€é§é§‚駃駄駅駆駇駈駉駊駋駌é§é§Žé§é§é§‘駒駓駔駕駖駗駘駙駚駛駜é§é§žé§Ÿé§ é§¡é§¢é§£é§¤é§¥é§¦é§§é§¨é§©é§ªé§«é§¬é§­é§®é§¯é§°é§±é§²é§³é§´é§µé§¶é§·é§¸é§¹é§ºé§»é§¼é§½é§¾é§¿é¨€é¨é¨‚騃騄騅騆騇騈騉騊騋騌é¨é¨Žé¨é¨é¨‘騒験騔騕騖騗騘騙騚騛騜é¨é¨žé¨Ÿé¨ é¨¡","騢騣騤騥騦騧騨騩騪騫騬騭騮騯騰騱騲騳騴騵騶騷騸騹騺騻騼騽騾騿驀é©é©‚驃驄驅驆驇驈驉驊驋驌é©é©Žé©é©é©‘驒驓驔驕驖驗驘驙驚驛驜é©é©žé©Ÿé© é©¡é©¢é©£é©¤é©¥é©¦é©§é©¨é©©é©ªé©«é©¬é©­é©®é©¯é©°é©±é©²é©³é©´é©µé©¶é©·é©¸é©¹é©ºé©»é©¼é©½é©¾é©¿éª€éªéª‚骃骄骅骆骇骈骉骊骋验éªéªŽéªéªéª‘骒骓骔骕骖骗骘骙骚骛骜éªéªžéªŸéª éª¡éª¢éª£éª¤éª¥éª¦éª§éª¨éª©éªªéª«éª¬éª­éª®éª¯éª°éª±éª²éª³éª´éªµéª¶éª·éª¸éª¹éªºéª»éª¼éª½éª¾éª¿é«€é«é«‚髃髄髅髆髇髈髉髊髋髌é«é«Žé«é«é«‘髒髓體髕髖髗高髙髚髛髜é«é«žé«Ÿé« é«¡é«¢é«£é«¤é«¥é«¦é«§é«¨é«©é«ªé««é«¬é«­é«®é«¯é«°é«±é«²é«³é«´é«µé«¶é«·é«¸é«¹é«ºé«»é«¼é«½é«¾é«¿é¬€é¬é¬‚鬃鬄鬅鬆鬇鬈鬉鬊鬋鬌é¬é¬Žé¬é¬é¬‘鬒鬓鬔鬕鬖鬗鬘鬙鬚鬛鬜é¬é¬žé¬Ÿé¬ é¬¡","鬢鬣鬤鬥鬦鬧鬨鬩鬪鬫鬬鬭鬮鬯鬰鬱鬲鬳鬴鬵鬶鬷鬸鬹鬺鬻鬼鬽鬾鬿魀é­é­‚魃魄魅魆魇魈魉魊魋魌é­é­Žé­é­é­‘魒魓魔魕魖魗魘魙魚魛魜é­é­žé­Ÿé­ é­¡é­¢é­£é­¤é­¥é­¦é­§é­¨é­©é­ªé­«é­¬é­­é­®é­¯é­°é­±é­²é­³é­´é­µé­¶é­·é­¸é­¹é­ºé­»é­¼é­½é­¾é­¿é®€é®é®‚鮃鮄鮅鮆鮇鮈鮉鮊鮋鮌é®é®Žé®é®é®‘鮒鮓鮔鮕鮖鮗鮘鮙鮚鮛鮜é®é®žé®Ÿé® é®¡é®¢é®£é®¤é®¥é®¦é®§é®¨é®©é®ªé®«é®¬é®­é®®é®¯é®°é®±é®²é®³é®´é®µé®¶é®·é®¸é®¹é®ºé®»é®¼é®½é®¾é®¿é¯€é¯é¯‚鯃鯄鯅鯆鯇鯈鯉鯊鯋鯌é¯é¯Žé¯é¯é¯‘鯒鯓鯔鯕鯖鯗鯘鯙鯚鯛鯜é¯é¯žé¯Ÿé¯ é¯¡é¯¢é¯£é¯¤é¯¥é¯¦é¯§é¯¨é¯©é¯ªé¯«é¯¬é¯­é¯®é¯¯é¯°é¯±é¯²é¯³é¯´é¯µé¯¶é¯·é¯¸é¯¹é¯ºé¯»é¯¼é¯½é¯¾é¯¿é°€é°é°‚鰃鰄鰅鰆鰇鰈鰉鰊鰋鰌é°é°Žé°é°é°‘鰒鰓鰔鰕鰖鰗鰘鰙鰚鰛鰜é°é°žé°Ÿé° é°¡","鰢鰣鰤鰥鰦鰧鰨鰩鰪鰫鰬鰭鰮鰯鰰鰱鰲鰳鰴鰵鰶鰷鰸鰹鰺鰻鰼鰽鰾鰿鱀é±é±‚鱃鱄鱅鱆鱇鱈鱉鱊鱋鱌é±é±Žé±é±é±‘鱒鱓鱔鱕鱖鱗鱘鱙鱚鱛鱜é±é±žé±Ÿé± é±¡é±¢é±£é±¤é±¥é±¦é±§é±¨é±©é±ªé±«é±¬é±­é±®é±¯é±°é±±é±²é±³é±´é±µé±¶é±·é±¸é±¹é±ºé±»é±¼é±½é±¾é±¿é²€é²é²‚鲃鲄鲅鲆鲇鲈鲉鲊鲋鲌é²é²Žé²é²é²‘鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜é²é²žé²Ÿé² é²¡é²¢é²£é²¤é²¥é²¦é²§é²¨é²©é²ªé²«é²¬é²­é²®é²¯é²°é²±é²²é²³é²´é²µé²¶é²·é²¸é²¹é²ºé²»é²¼é²½é²¾é²¿é³€é³é³‚鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌é³é³Žé³é³é³‘鳒鳓鳔鳕鳖鳗鳘鳙鳚鳛鳜é³é³žé³Ÿé³ é³¡é³¢é³£é³¤é³¥é³¦é³§é³¨é³©é³ªé³«é³¬é³­é³®é³¯é³°é³±é³²é³³é³´é³µé³¶é³·é³¸é³¹é³ºé³»é³¼é³½é³¾é³¿é´€é´é´‚鴃鴄鴅鴆鴇鴈鴉鴊鴋鴌é´é´Žé´é´é´‘鴒鴓鴔鴕鴖鴗鴘鴙鴚鴛鴜é´é´žé´Ÿé´ é´¡","鴢鴣鴤鴥鴦鴧鴨鴩鴪鴫鴬鴭鴮鴯鴰鴱鴲鴳鴴鴵鴶鴷鴸鴹鴺鴻鴼鴽鴾鴿鵀éµéµ‚鵃鵄鵅鵆鵇鵈鵉鵊鵋鵌éµéµŽéµéµéµ‘鵒鵓鵔鵕鵖鵗鵘鵙鵚鵛鵜éµéµžéµŸéµ éµ¡éµ¢éµ£éµ¤éµ¥éµ¦éµ§éµ¨éµ©éµªéµ«éµ¬éµ­éµ®éµ¯éµ°éµ±éµ²éµ³éµ´éµµéµ¶éµ·éµ¸éµ¹éµºéµ»éµ¼éµ½éµ¾éµ¿é¶€é¶é¶‚鶃鶄鶅鶆鶇鶈鶉鶊鶋鶌é¶é¶Žé¶é¶é¶‘鶒鶓鶔鶕鶖鶗鶘鶙鶚鶛鶜é¶é¶žé¶Ÿé¶ é¶¡é¶¢é¶£é¶¤é¶¥é¶¦é¶§é¶¨é¶©é¶ªé¶«é¶¬é¶­é¶®é¶¯é¶°é¶±é¶²é¶³é¶´é¶µé¶¶é¶·é¶¸é¶¹é¶ºé¶»é¶¼é¶½é¶¾é¶¿é·€é·é·‚鷃鷄鷅鷆鷇鷈鷉鷊鷋鷌é·é·Žé·é·é·‘鷒鷓鷔鷕鷖鷗鷘鷙鷚鷛鷜é·é·žé·Ÿé· é·¡é·¢é·£é·¤é·¥é·¦é·§é·¨é·©é·ªé·«é·¬é·­é·®é·¯é·°é·±é·²é·³é·´é·µé·¶é··é·¸é·¹é·ºé·»é·¼é·½é·¾é·¿é¸€é¸é¸‚鸃鸄鸅鸆鸇鸈鸉鸊鸋鸌é¸é¸Žé¸é¸é¸‘鸒鸓鸔鸕鸖鸗鸘鸙鸚鸛鸜é¸é¸žé¸Ÿé¸ é¸¡","鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀é¹é¹‚鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌é¹é¹Žé¹é¹é¹‘鹒鹓鹔鹕鹖鹗鹘鹙鹚鹛鹜é¹é¹žé¹Ÿé¹ é¹¡é¹¢é¹£é¹¤é¹¥é¹¦é¹§é¹¨é¹©é¹ªé¹«é¹¬é¹­é¹®é¹¯é¹°é¹±é¹²é¹³é¹´é¹µé¹¶é¹·é¹¸é¹¹é¹ºé¹»é¹¼é¹½é¹¾é¹¿éº€éºéº‚麃麄麅麆麇麈麉麊麋麌éºéºŽéºéºéº‘麒麓麔麕麖麗麘麙麚麛麜éºéºžéºŸéº éº¡éº¢éº£éº¤éº¥éº¦éº§éº¨éº©éºªéº«éº¬éº­éº®éº¯éº°éº±éº²éº³éº´éºµéº¶éº·éº¸éº¹éººéº»éº¼éº½éº¾éº¿é»€é»é»‚黃黄黅黆黇黈黉黊黋黌é»é»Žé»é»é»‘黒黓黔黕黖黗默黙黚黛黜é»é»žé»Ÿé» é»¡é»¢é»£é»¤é»¥é»¦é»§é»¨é»©é»ªé»«é»¬é»­é»®é»¯é»°é»±é»²é»³é»´é»µé»¶é»·é»¸é»¹é»ºé»»é»¼é»½é»¾é»¿é¼€é¼é¼‚鼃鼄鼅鼆鼇鼈鼉鼊鼋鼌é¼é¼Žé¼é¼é¼‘鼒鼓鼔鼕鼖鼗鼘鼙鼚鼛鼜é¼é¼žé¼Ÿé¼ é¼¡","鼢鼣鼤鼥鼦鼧鼨鼩鼪鼫鼬鼭鼮鼯鼰鼱鼲鼳鼴鼵鼶鼷鼸鼹鼺鼻鼼鼽鼾鼿齀é½é½‚齃齄齅齆齇齈齉齊齋齌é½é½Žé½é½é½‘齒齓齔齕齖齗齘齙齚齛齜é½é½žé½Ÿé½ é½¡é½¢é½£é½¤é½¥é½¦é½§é½¨é½©é½ªé½«é½¬é½­é½®é½¯é½°é½±é½²é½³é½´é½µé½¶é½·é½¸é½¹é½ºé½»é½¼é½½é½¾é½¿é¾€é¾é¾‚龃龄龅龆龇龈龉龊龋龌é¾é¾Žé¾é¾é¾‘龒龓龔龕龖龗龘龙龚龛龜é¾é¾žé¾Ÿé¾ é¾¡é¾¢é¾£é¾¤é¾¥é¾¦é¾§é¾¨é¾©é¾ªé¾«é¾¬é¾­é¾®é¾¯é¾°é¾±é¾²é¾³é¾´é¾µé¾¶é¾·é¾¸é¾¹é¾ºé¾»é¾¼é¾½é¾¾é¾¿é¿€é¿é¿‚鿃鿄鿅鿆鿇鿈鿉鿊鿋鿌é¿é¿Žé¿é¿é¿‘鿒鿓鿔鿕鿖鿗鿘鿙鿚鿛鿜é¿é¿žé¿Ÿé¿ é¿¡é¿¢é¿£é¿¤é¿¥é¿¦é¿§é¿¨é¿©é¿ªé¿«é¿¬é¿­é¿®é¿¯é¿°é¿±é¿²é¿³é¿´é¿µé¿¶é¿·é¿¸é¿¹é¿ºé¿»é¿¼é¿½é¿¾é¿¿ê€€ê€ê€‚ꀃꀄꀅꀆꀇꀈꀉꀊꀋꀌê€ê€Žê€ê€ê€‘ꀒꀓꀔꀕꀖꀗꀘꀙꀚꀛꀜê€ê€žê€Ÿê€ ê€¡","ꀢꀣꀤꀥꀦꀧꀨꀩꀪꀫꀬꀭꀮꀯꀰꀱꀲꀳꀴꀵꀶꀷꀸꀹꀺꀻꀼꀽꀾꀿê€êê‚êƒê„ê…ê†ê‡êˆê‰êŠê‹êŒêêŽêêê‘ê’ê“ê”ê•ê–ê—ê˜ê™êšê›êœêêžêŸê ê¡ê¢ê£ê¤ê¥ê¦ê§ê¨ê©êªê«ê¬ê­ê®ê¯ê°ê±ê²ê³ê´êµê¶ê·ê¸ê¹êºê»ê¼ê½ê¾ê¿ê‚€ê‚ꂂꂃꂄꂅꂆꂇꂈꂉꂊꂋꂌê‚ê‚Žê‚ê‚ꂑꂒꂓꂔꂕꂖꂗꂘꂙꂚꂛꂜê‚ꂞꂟꂠꂡꂢꂣꂤꂥꂦꂧꂨꂩꂪꂫꂬꂭꂮꂯꂰꂱꂲꂳꂴꂵꂶꂷꂸꂹꂺꂻꂼꂽꂾꂿꃀêƒêƒ‚ꃃꃄꃅꃆꃇꃈꃉꃊꃋꃌêƒêƒŽêƒêƒêƒ‘ꃒꃓꃔꃕꃖꃗꃘꃙꃚꃛꃜêƒêƒžêƒŸêƒ êƒ¡êƒ¢êƒ£êƒ¤êƒ¥êƒ¦êƒ§êƒ¨êƒ©êƒªêƒ«êƒ¬êƒ­êƒ®êƒ¯êƒ°êƒ±êƒ²êƒ³êƒ´êƒµêƒ¶êƒ·êƒ¸êƒ¹êƒºêƒ»êƒ¼êƒ½êƒ¾êƒ¿ê„€ê„ꄂꄃꄄꄅꄆꄇꄈꄉꄊꄋꄌê„ê„Žê„ê„ꄑꄒꄓꄔꄕꄖꄗꄘꄙꄚꄛꄜê„ê„žê„Ÿê„ ê„¡","ꄢꄣꄤꄥꄦꄧꄨꄩꄪꄫꄬꄭꄮꄯꄰꄱꄲꄳꄴꄵꄶꄷꄸꄹꄺꄻꄼꄽꄾꄿꅀê…ꅂꅃꅄꅅꅆꅇꅈꅉꅊꅋꅌê…ê…Žê…ê…ꅑꅒꅓꅔꅕꅖꅗꅘꅙꅚꅛꅜê…ꅞꅟꅠꅡꅢꅣꅤꅥꅦꅧꅨꅩꅪꅫꅬꅭꅮꅯꅰꅱꅲꅳꅴꅵꅶꅷꅸꅹꅺꅻꅼꅽꅾꅿꆀê†ê†‚ꆃꆄꆅꆆꆇꆈꆉꆊꆋꆌê†ê†Žê†ê†ê†‘ꆒꆓꆔꆕꆖꆗꆘꆙꆚꆛꆜê†ê†žê†Ÿê† ê†¡ê†¢ê†£ê†¤ê†¥ê†¦ê†§ê†¨ê†©ê†ªê†«ê†¬ê†­ê†®ê†¯ê†°ê†±ê†²ê†³ê†´ê†µê†¶ê†·ê†¸ê†¹ê†ºê†»ê†¼ê†½ê†¾ê†¿ê‡€ê‡ê‡‚ꇃꇄꇅꇆꇇꇈꇉꇊꇋꇌê‡ê‡Žê‡ê‡ê‡‘ꇒꇓꇔꇕꇖꇗꇘꇙꇚꇛꇜê‡ê‡žê‡Ÿê‡ ê‡¡ê‡¢ê‡£ê‡¤ê‡¥ê‡¦ê‡§ê‡¨ê‡©ê‡ªê‡«ê‡¬ê‡­ê‡®ê‡¯ê‡°ê‡±ê‡²ê‡³ê‡´ê‡µê‡¶ê‡·ê‡¸ê‡¹ê‡ºê‡»ê‡¼ê‡½ê‡¾ê‡¿êˆ€êˆêˆ‚ꈃꈄꈅꈆꈇꈈꈉꈊꈋꈌêˆêˆŽêˆêˆêˆ‘ꈒꈓꈔꈕꈖꈗꈘꈙꈚꈛꈜêˆêˆžêˆŸêˆ êˆ¡","ꈢꈣꈤꈥꈦꈧꈨꈩꈪꈫꈬꈭꈮꈯꈰꈱꈲꈳꈴꈵꈶꈷꈸꈹꈺꈻꈼꈽꈾꈿꉀê‰ê‰‚ꉃꉄꉅꉆꉇꉈꉉꉊꉋꉌê‰ê‰Žê‰ê‰ê‰‘ꉒꉓꉔꉕꉖꉗꉘꉙꉚꉛꉜê‰ê‰žê‰Ÿê‰ ê‰¡ê‰¢ê‰£ê‰¤ê‰¥ê‰¦ê‰§ê‰¨ê‰©ê‰ªê‰«ê‰¬ê‰­ê‰®ê‰¯ê‰°ê‰±ê‰²ê‰³ê‰´ê‰µê‰¶ê‰·ê‰¸ê‰¹ê‰ºê‰»ê‰¼ê‰½ê‰¾ê‰¿êŠ€êŠêŠ‚ꊃꊄꊅꊆꊇꊈꊉꊊꊋꊌêŠêŠŽêŠêŠêŠ‘ꊒꊓꊔꊕꊖꊗꊘꊙꊚꊛꊜêŠêŠžêŠŸêŠ êŠ¡êŠ¢êŠ£êŠ¤êŠ¥êŠ¦êŠ§êŠ¨êŠ©êŠªêŠ«êŠ¬êŠ­êŠ®êŠ¯êŠ°êŠ±êŠ²êŠ³êŠ´êŠµêŠ¶êŠ·êŠ¸êŠ¹êŠºêŠ»êŠ¼êŠ½êŠ¾êŠ¿ê‹€ê‹ê‹‚ꋃꋄꋅꋆꋇꋈꋉꋊꋋꋌê‹ê‹Žê‹ê‹ê‹‘ꋒꋓꋔꋕꋖꋗꋘꋙꋚꋛꋜê‹ê‹žê‹Ÿê‹ ê‹¡ê‹¢ê‹£ê‹¤ê‹¥ê‹¦ê‹§ê‹¨ê‹©ê‹ªê‹«ê‹¬ê‹­ê‹®ê‹¯ê‹°ê‹±ê‹²ê‹³ê‹´ê‹µê‹¶ê‹·ê‹¸ê‹¹ê‹ºê‹»ê‹¼ê‹½ê‹¾ê‹¿êŒ€êŒêŒ‚ꌃꌄꌅꌆꌇꌈꌉꌊꌋꌌêŒêŒŽêŒêŒêŒ‘ꌒꌓꌔꌕꌖꌗꌘꌙꌚꌛꌜêŒêŒžêŒŸêŒ êŒ¡","ꌢꌣꌤꌥꌦꌧꌨꌩꌪꌫꌬꌭꌮꌯꌰꌱꌲꌳꌴꌵꌶꌷꌸꌹꌺꌻꌼꌽꌾꌿê€êê‚êƒê„ê…ê†ê‡êˆê‰êŠê‹êŒêêŽêêê‘ê’ê“ê”ê•ê–ê—ê˜ê™êšê›êœêêžêŸê ê¡ê¢ê£ê¤ê¥ê¦ê§ê¨ê©êªê«ê¬ê­ê®ê¯ê°ê±ê²ê³ê´êµê¶ê·ê¸ê¹êºê»ê¼ê½ê¾ê¿êŽ€êŽêŽ‚ꎃꎄꎅꎆꎇꎈꎉꎊꎋꎌêŽêŽŽêŽêŽêŽ‘ꎒꎓꎔꎕꎖꎗꎘꎙꎚꎛꎜêŽêŽžêŽŸêŽ êŽ¡êŽ¢êŽ£êŽ¤êŽ¥êŽ¦êŽ§êŽ¨êŽ©êŽªêŽ«êŽ¬êŽ­êŽ®êŽ¯êŽ°êŽ±êŽ²êŽ³êŽ´êŽµêŽ¶êŽ·êŽ¸êŽ¹êŽºêŽ»êŽ¼êŽ½êŽ¾êŽ¿ê€êê‚êƒê„ê…ê†ê‡êˆê‰êŠê‹êŒêêŽêêê‘ê’ê“ê”ê•ê–ê—ê˜ê™êšê›êœêêžêŸê ê¡ê¢ê£ê¤ê¥ê¦ê§ê¨ê©êªê«ê¬ê­ê®ê¯ê°ê±ê²ê³ê´êµê¶ê·ê¸ê¹êºê»ê¼ê½ê¾ê¿ê€êê‚êƒê„ê…ê†ê‡êˆê‰êŠê‹êŒêêŽêêê‘ê’ê“ê”ê•ê–ê—ê˜ê™êšê›êœêêžêŸê ê¡","ê¢ê£ê¤ê¥ê¦ê§ê¨ê©êªê«ê¬ê­ê®ê¯ê°ê±ê²ê³ê´êµê¶ê·ê¸ê¹êºê»ê¼ê½ê¾ê¿ê‘€ê‘ꑂꑃꑄꑅꑆꑇꑈꑉꑊꑋꑌê‘ê‘Žê‘ê‘ꑑꑒꑓꑔꑕꑖꑗꑘꑙꑚꑛꑜê‘ꑞꑟꑠꑡꑢꑣꑤꑥꑦꑧꑨꑩꑪꑫꑬꑭꑮꑯꑰꑱꑲꑳꑴꑵꑶꑷꑸꑹꑺꑻꑼꑽꑾꑿꒀê’ꒂꒃꒄꒅꒆꒇꒈꒉꒊꒋꒌê’ê’Žê’ê’꒑꒒꒓꒔꒕꒖꒗꒘꒙꒚꒛꒜ê’꒞꒟꒠꒡꒢꒣꒤꒥꒦꒧꒨꒩꒪꒫꒬꒭꒮꒯꒰꒱꒲꒳꒴꒵꒶꒷꒸꒹꒺꒻꒼꒽꒾꒿꓀ê“꓂꓃꓄꓅꓆꓇꓈꓉꓊꓋꓌ê“ê“Žê“ê“ꓑꓒꓓꓔꓕꓖꓗꓘꓙꓚꓛꓜê“ꓞꓟꓠꓡꓢꓣꓤꓥꓦꓧꓨꓩꓪꓫꓬꓭꓮꓯꓰꓱꓲꓳꓴꓵꓶꓷꓸꓹꓺꓻꓼꓽ꓾꓿ꔀê”ꔂꔃꔄꔅꔆꔇꔈꔉꔊꔋꔌê”ꔎê”ê”ꔑꔒꔓꔔꔕꔖꔗꔘꔙꔚꔛꔜê”ꔞꔟꔠꔡ","ꔢꔣꔤꔥꔦꔧꔨꔩꔪꔫꔬꔭꔮꔯꔰꔱꔲꔳꔴꔵꔶꔷꔸꔹꔺꔻꔼꔽꔾꔿꕀê•ê•‚ꕃꕄꕅꕆꕇꕈꕉꕊꕋꕌê•ê•Žê•ê•ê•‘ꕒꕓꕔꕕꕖꕗꕘꕙꕚꕛꕜê•ê•žê•Ÿê• ê•¡ê•¢ê•£ê•¤ê•¥ê•¦ê•§ê•¨ê•©ê•ªê•«ê•¬ê•­ê•®ê•¯ê•°ê•±ê•²ê•³ê•´ê•µê•¶ê•·ê•¸ê•¹ê•ºê•»ê•¼ê•½ê•¾ê•¿ê–€ê–ꖂꖃꖄꖅꖆꖇꖈꖉꖊꖋꖌê–ê–Žê–ê–ꖑꖒꖓꖔꖕꖖꖗꖘꖙꖚꖛꖜê–ꖞꖟꖠꖡꖢꖣꖤꖥꖦꖧꖨꖩꖪꖫꖬꖭꖮꖯꖰꖱꖲꖳꖴꖵꖶꖷꖸꖹꖺꖻꖼꖽꖾꖿꗀê—ꗂꗃꗄꗅꗆꗇꗈꗉꗊꗋꗌê—ê—Žê—ê—ꗑꗒꗓꗔꗕꗖꗗꗘꗙꗚꗛꗜê—ꗞꗟꗠꗡꗢꗣꗤꗥꗦꗧꗨꗩꗪꗫꗬꗭꗮꗯꗰꗱꗲꗳꗴꗵꗶꗷꗸꗹꗺꗻꗼꗽꗾꗿꘀê˜ê˜‚ꘃꘄꘅꘆꘇꘈꘉꘊꘋꘌê˜ê˜Žê˜ê˜ê˜‘ꘒꘓꘔꘕꘖꘗꘘꘙꘚꘛꘜê˜ê˜žê˜Ÿê˜ ê˜¡","꘢꘣꘤꘥꘦꘧꘨꘩ꘪꘫ꘬꘭꘮꘯꘰꘱꘲꘳꘴꘵꘶꘷꘸꘹꘺꘻꘼꘽꘾꘿Ꙁê™ê™‚ꙃꙄꙅꙆꙇꙈꙉꙊꙋꙌê™ê™Žê™ê™ê™‘ꙒꙓꙔꙕꙖꙗꙘꙙꙚꙛꙜê™ê™žê™Ÿê™ ê™¡ê™¢ê™£ê™¤ê™¥ê™¦ê™§ê™¨ê™©ê™ªê™«ê™¬ê™­ê™®ê™¯ê™°ê™±ê™²ê™³ê™´ê™µê™¶ê™·ê™¸ê™¹ê™ºê™»ê™¼ê™½ê™¾ê™¿êš€êšêš‚ꚃꚄꚅꚆꚇꚈꚉꚊꚋꚌêšêšŽêšêšêš‘ꚒꚓꚔꚕꚖꚗꚘꚙꚚꚛꚜêšêšžêšŸêš êš¡êš¢êš£êš¤êš¥êš¦êš§êš¨êš©êšªêš«êš¬êš­êš®êš¯êš°êš±êš²êš³êš´êšµêš¶êš·êš¸êš¹êšºêš»êš¼êš½êš¾êš¿ê›€ê›ê›‚ꛃꛄꛅꛆꛇꛈꛉꛊꛋꛌê›ê›Žê›ê›ê›‘ꛒꛓꛔꛕꛖꛗꛘꛙꛚꛛꛜê›ê›žê›Ÿê› ê›¡ê›¢ê›£ê›¤ê›¥ê›¦ê›§ê›¨ê›©ê›ªê›«ê›¬ê›­ê›®ê›¯ê›°ê›±ê›²ê›³ê›´ê›µê›¶ê›·ê›¸ê›¹ê›ºê›»ê›¼ê›½ê›¾ê›¿êœ€êœêœ‚꜃꜄꜅꜆꜇꜈꜉꜊꜋꜌êœêœŽêœêœêœ‘꜒꜓꜔꜕꜖ꜗꜘꜙꜚꜛꜜêœêœžêœŸêœ êœ¡","ꜢꜣꜤꜥꜦꜧꜨꜩꜪꜫꜬꜭꜮꜯꜰꜱꜲꜳꜴꜵꜶꜷꜸꜹꜺꜻꜼꜽꜾꜿê€êê‚êƒê„ê…ê†ê‡êˆê‰êŠê‹êŒêêŽêêê‘ê’ê“ê”ê•ê–ê—ê˜ê™êšê›êœêêžêŸê ê¡ê¢ê£ê¤ê¥ê¦ê§ê¨ê©êªê«ê¬ê­ê®ê¯ê°ê±ê²ê³ê´êµê¶ê·ê¸ê¹êºê»ê¼ê½ê¾ê¿êž€êžêž‚ꞃꞄꞅꞆꞇꞈ꞉꞊ꞋꞌêžêžŽêžêžêž‘ꞒꞓꞔꞕꞖꞗꞘꞙꞚꞛꞜêžêžžêžŸêž êž¡êž¢êž£êž¤êž¥êž¦êž§êž¨êž©êžªêž«êž¬êž­êž®êž¯êž°êž±êž²êž³êž´êžµêž¶êž·êž¸êž¹êžºêž»êž¼êž½êž¾êž¿êŸ€êŸêŸ‚ꟃꟄꟅꟆꟇꟈꟉꟊꟋꟌêŸêŸŽêŸêŸêŸ‘꟒ꟓ꟔ꟕꟖꟗꟘꟙꟚꟛꟜêŸêŸžêŸŸêŸ êŸ¡êŸ¢êŸ£êŸ¤êŸ¥êŸ¦êŸ§êŸ¨êŸ©êŸªêŸ«êŸ¬êŸ­êŸ®êŸ¯êŸ°êŸ±êŸ²êŸ³êŸ´êŸµêŸ¶êŸ·êŸ¸êŸ¹êŸºêŸ»êŸ¼êŸ½êŸ¾êŸ¿ê €ê ê ‚ꠃꠄꠅ꠆ꠇꠈꠉꠊꠋꠌê ê Žê ê ê ‘ꠒꠓꠔꠕꠖꠗꠘꠙꠚꠛꠜê ê žê Ÿê  ê ¡","ꠢꠣꠤꠥꠦꠧ꠨꠩꠪꠫꠬꠭꠮꠯꠰꠱꠲꠳꠴꠵꠶꠷꠸꠹꠺꠻꠼꠽꠾꠿ꡀê¡ê¡‚ꡃꡄꡅꡆꡇꡈꡉꡊꡋꡌê¡ê¡Žê¡ê¡ê¡‘ꡒꡓꡔꡕꡖꡗꡘꡙꡚꡛꡜê¡ê¡žê¡Ÿê¡ ê¡¡ê¡¢ê¡£ê¡¤ê¡¥ê¡¦ê¡§ê¡¨ê¡©ê¡ªê¡«ê¡¬ê¡­ê¡®ê¡¯ê¡°ê¡±ê¡²ê¡³ê¡´ê¡µê¡¶ê¡·ê¡¸ê¡¹ê¡ºê¡»ê¡¼ê¡½ê¡¾ê¡¿ê¢€ê¢ê¢‚ꢃꢄꢅꢆꢇꢈꢉꢊꢋꢌê¢ê¢Žê¢ê¢ê¢‘ꢒꢓꢔꢕꢖꢗꢘꢙꢚꢛꢜê¢ê¢žê¢Ÿê¢ ê¢¡ê¢¢ê¢£ê¢¤ê¢¥ê¢¦ê¢§ê¢¨ê¢©ê¢ªê¢«ê¢¬ê¢­ê¢®ê¢¯ê¢°ê¢±ê¢²ê¢³ê¢´ê¢µê¢¶ê¢·ê¢¸ê¢¹ê¢ºê¢»ê¢¼ê¢½ê¢¾ê¢¿ê£€ê£ê£‚ꣃ꣄ꣅ꣆꣇꣈꣉꣊꣋꣌ê£ê£Žê£ê£ê£‘꣒꣓꣔꣕꣖꣗꣘꣙꣚꣛꣜ê£ê£žê£Ÿê£ ê£¡ê£¢ê££ê£¤ê£¥ê£¦ê£§ê£¨ê£©ê£ªê£«ê£¬ê£­ê£®ê£¯ê£°ê£±ê£²ê£³ê£´ê£µê£¶ê£·ê£¸ê£¹ê£ºê£»ê£¼ê£½ê£¾ê£¿ê¤€ê¤ê¤‚꤃꤄꤅꤆꤇꤈꤉ꤊꤋꤌê¤ê¤Žê¤ê¤ê¤‘ꤒꤓꤔꤕꤖꤗꤘꤙꤚꤛꤜê¤ê¤žê¤Ÿê¤ ê¤¡","ꤢꤣꤤꤥꤦꤧꤨꤩꤪ꤫꤬꤭꤮꤯ꤰꤱꤲꤳꤴꤵꤶꤷꤸꤹꤺꤻꤼꤽꤾꤿꥀê¥ê¥‚ꥃꥄꥅꥆꥇꥈꥉꥊꥋꥌê¥ê¥Žê¥ê¥ê¥‘ꥒ꥓꥔꥕꥖꥗꥘꥙꥚꥛꥜ê¥ê¥žê¥Ÿê¥ ê¥¡ê¥¢ê¥£ê¥¤ê¥¥ê¥¦ê¥§ê¥¨ê¥©ê¥ªê¥«ê¥¬ê¥­ê¥®ê¥¯ê¥°ê¥±ê¥²ê¥³ê¥´ê¥µê¥¶ê¥·ê¥¸ê¥¹ê¥ºê¥»ê¥¼ê¥½ê¥¾ê¥¿ê¦€ê¦ê¦‚ꦃꦄꦅꦆꦇꦈꦉꦊꦋꦌê¦ê¦Žê¦ê¦ê¦‘ꦒꦓꦔꦕꦖꦗꦘꦙꦚꦛꦜê¦ê¦žê¦Ÿê¦ ê¦¡ê¦¢ê¦£ê¦¤ê¦¥ê¦¦ê¦§ê¦¨ê¦©ê¦ªê¦«ê¦¬ê¦­ê¦®ê¦¯ê¦°ê¦±ê¦²ê¦³ê¦´ê¦µê¦¶ê¦·ê¦¸ê¦¹ê¦ºê¦»ê¦¼ê¦½ê¦¾ê¦¿ê§€ê§ê§‚꧃꧄꧅꧆꧇꧈꧉꧊꧋꧌ê§ê§Žê§ê§ê§‘꧒꧓꧔꧕꧖꧗꧘꧙꧚꧛꧜ê§ê§žê§Ÿê§ ê§¡ê§¢ê§£ê§¤ê§¥ê§¦ê§§ê§¨ê§©ê§ªê§«ê§¬ê§­ê§®ê§¯ê§°ê§±ê§²ê§³ê§´ê§µê§¶ê§·ê§¸ê§¹ê§ºê§»ê§¼ê§½ê§¾ê§¿ê¨€ê¨ê¨‚ꨃꨄꨅꨆꨇꨈꨉꨊꨋꨌê¨ê¨Žê¨ê¨ê¨‘ꨒꨓꨔꨕꨖꨗꨘꨙꨚꨛꨜê¨ê¨žê¨Ÿê¨ ê¨¡","ꨢꨣꨤꨥꨦꨧꨨꨩꨪꨫꨬꨭꨮꨯꨰꨱꨲꨳꨴꨵꨶ꨷꨸꨹꨺꨻꨼꨽꨾꨿ꩀê©ê©‚ꩃꩄꩅꩆꩇꩈꩉꩊꩋꩌê©ê©Žê©ê©ê©‘꩒꩓꩔꩕꩖꩗꩘꩙꩚꩛꩜ê©ê©žê©Ÿê© ê©¡ê©¢ê©£ê©¤ê©¥ê©¦ê©§ê©¨ê©©ê©ªê©«ê©¬ê©­ê©®ê©¯ê©°ê©±ê©²ê©³ê©´ê©µê©¶ê©·ê©¸ê©¹ê©ºê©»ê©¼ê©½ê©¾ê©¿êª€êªêª‚ꪃꪄꪅꪆꪇꪈꪉꪊꪋꪌêªêªŽêªêªêª‘ꪒꪓꪔꪕꪖꪗꪘꪙꪚꪛꪜêªêªžêªŸêª êª¡êª¢êª£êª¤êª¥êª¦êª§êª¨êª©êªªêª«êª¬êª­êª®êª¯êª°êª±êª²êª³êª´êªµêª¶êª·êª¸êª¹êªºêª»êª¼êª½êª¾êª¿ê«€ê«ê«‚꫃꫄꫅꫆꫇꫈꫉꫊꫋꫌ê«ê«Žê«ê«ê«‘꫒꫓꫔꫕꫖꫗꫘꫙꫚ꫛꫜê«ê«žê«Ÿê« ê«¡ê«¢ê«£ê«¤ê«¥ê«¦ê«§ê«¨ê«©ê«ªê««ê«¬ê«­ê«®ê«¯ê«°ê«±ê«²ê«³ê«´ê«µê«¶ê«·ê«¸ê«¹ê«ºê«»ê«¼ê«½ê«¾ê«¿ê¬€ê¬ê¬‚ꬃꬄꬅꬆ꬇꬈ꬉꬊꬋꬌê¬ê¬Žê¬ê¬ê¬‘ꬒꬓꬔꬕꬖ꬗꬘꬙꬚꬛꬜ê¬ê¬žê¬Ÿê¬ ê¬¡","ꬢꬣꬤꬥꬦ꬧ꬨꬩꬪꬫꬬꬭꬮ꬯ꬰꬱꬲꬳꬴꬵꬶꬷꬸꬹꬺꬻꬼꬽꬾꬿꭀê­ê­‚ꭃꭄꭅꭆꭇꭈꭉꭊꭋꭌê­ê­Žê­ê­ê­‘ꭒꭓꭔꭕꭖꭗꭘꭙꭚ꭛ꭜê­ê­žê­Ÿê­ ê­¡ê­¢ê­£ê­¤ê­¥ê­¦ê­§ê­¨ê­©ê­ªê­«ê­¬ê­­ê­®ê­¯ê­°ê­±ê­²ê­³ê­´ê­µê­¶ê­·ê­¸ê­¹ê­ºê­»ê­¼ê­½ê­¾ê­¿ê®€ê®ê®‚ꮃꮄꮅꮆꮇꮈꮉꮊꮋꮌê®ê®Žê®ê®ê®‘ꮒꮓꮔꮕꮖꮗꮘꮙꮚꮛꮜê®ê®žê®Ÿê® ê®¡ê®¢ê®£ê®¤ê®¥ê®¦ê®§ê®¨ê®©ê®ªê®«ê®¬ê®­ê®®ê®¯ê®°ê®±ê®²ê®³ê®´ê®µê®¶ê®·ê®¸ê®¹ê®ºê®»ê®¼ê®½ê®¾ê®¿ê¯€ê¯ê¯‚ꯃꯄꯅꯆꯇꯈꯉꯊꯋꯌê¯ê¯Žê¯ê¯ê¯‘ꯒꯓꯔꯕꯖꯗꯘꯙꯚꯛꯜê¯ê¯žê¯Ÿê¯ ê¯¡ê¯¢ê¯£ê¯¤ê¯¥ê¯¦ê¯§ê¯¨ê¯©ê¯ªê¯«ê¯¬ê¯­ê¯®ê¯¯ê¯°ê¯±ê¯²ê¯³ê¯´ê¯µê¯¶ê¯·ê¯¸ê¯¹ê¯ºê¯»ê¯¼ê¯½ê¯¾ê¯¿ê°€ê°ê°‚갃간갅갆갇갈갉갊갋갌ê°ê°Žê°ê°ê°‘값갓갔강갖갗갘같갚갛개ê°ê°žê°Ÿê° ê°¡","갢갣갤갥갦갧갨갩갪갫갬갭갮갯갰갱갲갳갴갵갶갷갸갹갺갻갼갽갾갿걀ê±ê±‚걃걄걅걆걇걈걉걊걋걌ê±ê±Žê±ê±ê±‘걒걓걔걕걖걗걘걙걚걛걜ê±ê±žê±Ÿê± ê±¡ê±¢ê±£ê±¤ê±¥ê±¦ê±§ê±¨ê±©ê±ªê±«ê±¬ê±­ê±®ê±¯ê±°ê±±ê±²ê±³ê±´ê±µê±¶ê±·ê±¸ê±¹ê±ºê±»ê±¼ê±½ê±¾ê±¿ê²€ê²ê²‚것겄겅겆겇겈겉겊겋게ê²ê²Žê²ê²ê²‘겒겓겔겕겖겗겘겙겚겛겜ê²ê²žê²Ÿê² ê²¡ê²¢ê²£ê²¤ê²¥ê²¦ê²§ê²¨ê²©ê²ªê²«ê²¬ê²­ê²®ê²¯ê²°ê²±ê²²ê²³ê²´ê²µê²¶ê²·ê²¸ê²¹ê²ºê²»ê²¼ê²½ê²¾ê²¿ê³€ê³ê³‚곃계곅곆곇곈곉곊곋곌ê³ê³Žê³ê³ê³‘곒곓곔곕곖곗곘곙곚곛곜ê³ê³žê³Ÿê³ ê³¡ê³¢ê³£ê³¤ê³¥ê³¦ê³§ê³¨ê³©ê³ªê³«ê³¬ê³­ê³®ê³¯ê³°ê³±ê³²ê³³ê³´ê³µê³¶ê³·ê³¸ê³¹ê³ºê³»ê³¼ê³½ê³¾ê³¿ê´€ê´ê´‚괃괄괅괆괇괈괉괊괋괌ê´ê´Žê´ê´ê´‘괒괓괔괕괖괗괘괙괚괛괜ê´ê´žê´Ÿê´ ê´¡","괢괣괤괥괦괧괨괩괪괫괬괭괮괯괰괱괲괳괴괵괶괷괸괹괺괻괼괽괾괿굀êµêµ‚굃굄굅굆굇굈굉굊굋굌êµêµŽêµêµêµ‘굒굓굔굕굖굗굘굙굚굛굜êµêµžêµŸêµ êµ¡êµ¢êµ£êµ¤êµ¥êµ¦êµ§êµ¨êµ©êµªêµ«êµ¬êµ­êµ®êµ¯êµ°êµ±êµ²êµ³êµ´êµµêµ¶êµ·êµ¸êµ¹êµºêµ»êµ¼êµ½êµ¾êµ¿ê¶€ê¶ê¶‚궃궄궅궆궇궈궉궊궋권ê¶ê¶Žê¶ê¶ê¶‘궒궓궔궕궖궗궘궙궚궛궜ê¶ê¶žê¶Ÿê¶ ê¶¡ê¶¢ê¶£ê¶¤ê¶¥ê¶¦ê¶§ê¶¨ê¶©ê¶ªê¶«ê¶¬ê¶­ê¶®ê¶¯ê¶°ê¶±ê¶²ê¶³ê¶´ê¶µê¶¶ê¶·ê¶¸ê¶¹ê¶ºê¶»ê¶¼ê¶½ê¶¾ê¶¿ê·€ê·ê·‚귃귄귅귆귇귈귉귊귋귌ê·ê·Žê·ê·ê·‘귒귓귔귕귖귗귘귙귚귛규ê·ê·žê·Ÿê· ê·¡ê·¢ê·£ê·¤ê·¥ê·¦ê·§ê·¨ê·©ê·ªê·«ê·¬ê·­ê·®ê·¯ê·°ê·±ê·²ê·³ê·´ê·µê·¶ê··ê·¸ê·¹ê·ºê·»ê·¼ê·½ê·¾ê·¿ê¸€ê¸ê¸‚긃긄긅긆긇금급긊긋긌ê¸ê¸Žê¸ê¸ê¸‘긒긓긔긕긖긗긘긙긚긛긜ê¸ê¸žê¸Ÿê¸ ê¸¡","긢긣긤긥긦긧긨긩긪긫긬긭긮긯기긱긲긳긴긵긶긷길긹긺긻긼긽긾긿김ê¹ê¹‚깃깄깅깆깇깈깉깊깋까ê¹ê¹Žê¹ê¹ê¹‘깒깓깔깕깖깗깘깙깚깛깜ê¹ê¹žê¹Ÿê¹ ê¹¡ê¹¢ê¹£ê¹¤ê¹¥ê¹¦ê¹§ê¹¨ê¹©ê¹ªê¹«ê¹¬ê¹­ê¹®ê¹¯ê¹°ê¹±ê¹²ê¹³ê¹´ê¹µê¹¶ê¹·ê¹¸ê¹¹ê¹ºê¹»ê¹¼ê¹½ê¹¾ê¹¿êº€êºêº‚꺃꺄꺅꺆꺇꺈꺉꺊꺋꺌êºêºŽêºêºêº‘꺒꺓꺔꺕꺖꺗꺘꺙꺚꺛꺜êºêºžêºŸêº êº¡êº¢êº£êº¤êº¥êº¦êº§êº¨êº©êºªêº«êº¬êº­êº®êº¯êº°êº±êº²êº³êº´êºµêº¶êº·êº¸êº¹êººêº»êº¼êº½êº¾êº¿ê»€ê»ê»‚껃껄껅껆껇껈껉껊껋껌ê»ê»Žê»ê»ê»‘껒껓껔껕껖껗께껙껚껛껜ê»ê»žê»Ÿê» ê»¡ê»¢ê»£ê»¤ê»¥ê»¦ê»§ê»¨ê»©ê»ªê»«ê»¬ê»­ê»®ê»¯ê»°ê»±ê»²ê»³ê»´ê»µê»¶ê»·ê»¸ê»¹ê»ºê»»ê»¼ê»½ê»¾ê»¿ê¼€ê¼ê¼‚꼃꼄꼅꼆꼇꼈꼉꼊꼋꼌ê¼ê¼Žê¼ê¼ê¼‘꼒꼓꼔꼕꼖꼗꼘꼙꼚꼛꼜ê¼ê¼žê¼Ÿê¼ ê¼¡","꼢꼣꼤꼥꼦꼧꼨꼩꼪꼫꼬꼭꼮꼯꼰꼱꼲꼳꼴꼵꼶꼷꼸꼹꼺꼻꼼꼽꼾꼿꽀ê½ê½‚꽃꽄꽅꽆꽇꽈꽉꽊꽋꽌ê½ê½Žê½ê½ê½‘꽒꽓꽔꽕꽖꽗꽘꽙꽚꽛꽜ê½ê½žê½Ÿê½ ê½¡ê½¢ê½£ê½¤ê½¥ê½¦ê½§ê½¨ê½©ê½ªê½«ê½¬ê½­ê½®ê½¯ê½°ê½±ê½²ê½³ê½´ê½µê½¶ê½·ê½¸ê½¹ê½ºê½»ê½¼ê½½ê½¾ê½¿ê¾€ê¾ê¾‚꾃꾄꾅꾆꾇꾈꾉꾊꾋꾌ê¾ê¾Žê¾ê¾ê¾‘꾒꾓꾔꾕꾖꾗꾘꾙꾚꾛꾜ê¾ê¾žê¾Ÿê¾ ê¾¡ê¾¢ê¾£ê¾¤ê¾¥ê¾¦ê¾§ê¾¨ê¾©ê¾ªê¾«ê¾¬ê¾­ê¾®ê¾¯ê¾°ê¾±ê¾²ê¾³ê¾´ê¾µê¾¶ê¾·ê¾¸ê¾¹ê¾ºê¾»ê¾¼ê¾½ê¾¾ê¾¿ê¿€ê¿ê¿‚꿃꿄꿅꿆꿇꿈꿉꿊꿋꿌ê¿ê¿Žê¿ê¿ê¿‘꿒꿓꿔꿕꿖꿗꿘꿙꿚꿛꿜ê¿ê¿žê¿Ÿê¿ ê¿¡ê¿¢ê¿£ê¿¤ê¿¥ê¿¦ê¿§ê¿¨ê¿©ê¿ªê¿«ê¿¬ê¿­ê¿®ê¿¯ê¿°ê¿±ê¿²ê¿³ê¿´ê¿µê¿¶ê¿·ê¿¸ê¿¹ê¿ºê¿»ê¿¼ê¿½ê¿¾ê¿¿ë€€ë€ë€‚뀃뀄뀅뀆뀇뀈뀉뀊뀋뀌ë€ë€Žë€ë€ë€‘뀒뀓뀔뀕뀖뀗뀘뀙뀚뀛뀜ë€ë€žë€Ÿë€ ë€¡","뀢뀣뀤뀥뀦뀧뀨뀩뀪뀫뀬뀭뀮뀯뀰뀱뀲뀳뀴뀵뀶뀷뀸뀹뀺뀻뀼뀽뀾뀿ë€ëë‚ëƒë„ë…ë†ë‡ëˆë‰ëŠë‹ëŒëëŽëëë‘ë’ë“ë”ë•ë–ë—ë˜ë™ëšë›ëœëëžëŸë ë¡ë¢ë£ë¤ë¥ë¦ë§ë¨ë©ëªë«ë¬ë­ë®ë¯ë°ë±ë²ë³ë´ëµë¶ë·ë¸ë¹ëºë»ë¼ë½ë¾ë¿ë‚€ë‚낂낃낄낅낆낇낈낉낊낋낌ë‚ë‚Žë‚ë‚낑낒낓낔낕낖낗나낙낚낛난ë‚낞낟날낡낢낣낤낥낦낧남납낪낫났낭낮낯낰낱낲낳내낵낶낷낸낹낺낻낼낽낾낿냀ëƒëƒ‚냃냄냅냆냇냈냉냊냋냌ëƒëƒŽëƒëƒëƒ‘냒냓냔냕냖냗냘냙냚냛냜ëƒëƒžëƒŸëƒ ëƒ¡ëƒ¢ëƒ£ëƒ¤ëƒ¥ëƒ¦ëƒ§ëƒ¨ëƒ©ëƒªëƒ«ëƒ¬ëƒ­ëƒ®ëƒ¯ëƒ°ëƒ±ëƒ²ëƒ³ëƒ´ëƒµëƒ¶ëƒ·ëƒ¸ëƒ¹ëƒºëƒ»ëƒ¼ëƒ½ëƒ¾ëƒ¿ë„€ë„넂넃넄넅넆넇너넉넊넋넌ë„ë„Žë„ë„넑넒넓넔넕넖넗넘넙넚넛넜ë„ë„žë„Ÿë„ ë„¡","넢넣네넥넦넧넨넩넪넫넬넭넮넯넰넱넲넳넴넵넶넷넸넹넺넻넼넽넾넿녀ë…녂녃년녅녆녇녈녉녊녋녌ë…ë…Žë…ë…녑녒녓녔녕녖녗녘녙녚녛녜ë…녞녟녠녡녢녣녤녥녦녧녨녩녪녫녬녭녮녯녰녱녲녳녴녵녶녷노녹녺녻논녽녾녿놀ë†ë†‚놃놄놅놆놇놈놉놊놋놌ë†ë†Žë†ë†ë†‘높놓놔놕놖놗놘놙놚놛놜ë†ë†žë†Ÿë† ë†¡ë†¢ë†£ë†¤ë†¥ë†¦ë†§ë†¨ë†©ë†ªë†«ë†¬ë†­ë†®ë†¯ë†°ë†±ë†²ë†³ë†´ë†µë†¶ë†·ë†¸ë†¹ë†ºë†»ë†¼ë†½ë†¾ë†¿ë‡€ë‡ë‡‚뇃뇄뇅뇆뇇뇈뇉뇊뇋뇌ë‡ë‡Žë‡ë‡ë‡‘뇒뇓뇔뇕뇖뇗뇘뇙뇚뇛뇜ë‡ë‡žë‡Ÿë‡ ë‡¡ë‡¢ë‡£ë‡¤ë‡¥ë‡¦ë‡§ë‡¨ë‡©ë‡ªë‡«ë‡¬ë‡­ë‡®ë‡¯ë‡°ë‡±ë‡²ë‡³ë‡´ë‡µë‡¶ë‡·ë‡¸ë‡¹ë‡ºë‡»ë‡¼ë‡½ë‡¾ë‡¿ëˆ€ëˆëˆ‚눃누눅눆눇눈눉눊눋눌ëˆëˆŽëˆëˆëˆ‘눒눓눔눕눖눗눘눙눚눛눜ëˆëˆžëˆŸëˆ ëˆ¡","눢눣눤눥눦눧눨눩눪눫눬눭눮눯눰눱눲눳눴눵눶눷눸눹눺눻눼눽눾눿뉀ë‰ë‰‚뉃뉄뉅뉆뉇뉈뉉뉊뉋뉌ë‰ë‰Žë‰ë‰ë‰‘뉒뉓뉔뉕뉖뉗뉘뉙뉚뉛뉜ë‰ë‰žë‰Ÿë‰ ë‰¡ë‰¢ë‰£ë‰¤ë‰¥ë‰¦ë‰§ë‰¨ë‰©ë‰ªë‰«ë‰¬ë‰­ë‰®ë‰¯ë‰°ë‰±ë‰²ë‰³ë‰´ë‰µë‰¶ë‰·ë‰¸ë‰¹ë‰ºë‰»ë‰¼ë‰½ë‰¾ë‰¿ëŠ€ëŠëŠ‚늃늄늅늆늇늈늉늊늋늌ëŠëŠŽëŠëŠëŠ‘늒늓는늕늖늗늘늙늚늛늜ëŠëŠžëŠŸëŠ ëŠ¡ëŠ¢ëŠ£ëŠ¤ëŠ¥ëŠ¦ëŠ§ëŠ¨ëŠ©ëŠªëŠ«ëŠ¬ëŠ­ëŠ®ëŠ¯ëŠ°ëŠ±ëŠ²ëŠ³ëŠ´ëŠµëŠ¶ëŠ·ëŠ¸ëŠ¹ëŠºëŠ»ëŠ¼ëŠ½ëŠ¾ëŠ¿ë‹€ë‹ë‹‚닃닄닅닆닇니닉닊닋닌ë‹ë‹Žë‹ë‹ë‹‘닒닓닔닕닖닗님닙닚닛닜ë‹ë‹žë‹Ÿë‹ ë‹¡ë‹¢ë‹£ë‹¤ë‹¥ë‹¦ë‹§ë‹¨ë‹©ë‹ªë‹«ë‹¬ë‹­ë‹®ë‹¯ë‹°ë‹±ë‹²ë‹³ë‹´ë‹µë‹¶ë‹·ë‹¸ë‹¹ë‹ºë‹»ë‹¼ë‹½ë‹¾ë‹¿ëŒ€ëŒëŒ‚댃댄댅댆댇댈댉댊댋댌ëŒëŒŽëŒëŒëŒ‘댒댓댔댕댖댗댘댙댚댛댜ëŒëŒžëŒŸëŒ ëŒ¡","댢댣댤댥댦댧댨댩댪댫댬댭댮댯댰댱댲댳댴댵댶댷댸댹댺댻댼댽댾댿ë€ëë‚ëƒë„ë…ë†ë‡ëˆë‰ëŠë‹ëŒëëŽëëë‘ë’ë“ë”ë•ë–ë—ë˜ë™ëšë›ëœëëžëŸë ë¡ë¢ë£ë¤ë¥ë¦ë§ë¨ë©ëªë«ë¬ë­ë®ë¯ë°ë±ë²ë³ë´ëµë¶ë·ë¸ë¹ëºë»ë¼ë½ë¾ë¿ëŽ€ëŽëŽ‚뎃뎄뎅뎆뎇뎈뎉뎊뎋뎌ëŽëŽŽëŽëŽëŽ‘뎒뎓뎔뎕뎖뎗뎘뎙뎚뎛뎜ëŽëŽžëŽŸëŽ ëŽ¡ëŽ¢ëŽ£ëŽ¤ëŽ¥ëŽ¦ëŽ§ëŽ¨ëŽ©ëŽªëŽ«ëŽ¬ëŽ­ëŽ®ëŽ¯ëŽ°ëŽ±ëŽ²ëŽ³ëŽ´ëŽµëŽ¶ëŽ·ëŽ¸ëŽ¹ëŽºëŽ»ëŽ¼ëŽ½ëŽ¾ëŽ¿ë€ëë‚ëƒë„ë…ë†ë‡ëˆë‰ëŠë‹ëŒëëŽëëë‘ë’ë“ë”ë•ë–ë—ë˜ë™ëšë›ëœëëžëŸë ë¡ë¢ë£ë¤ë¥ë¦ë§ë¨ë©ëªë«ë¬ë­ë®ë¯ë°ë±ë²ë³ë´ëµë¶ë·ë¸ë¹ëºë»ë¼ë½ë¾ë¿ë€ëë‚ëƒë„ë…ë†ë‡ëˆë‰ëŠë‹ëŒëëŽëëë‘ë’ë“ë”ë•ë–ë—ë˜ë™ëšë›ëœëëžëŸë ë¡","ë¢ë£ë¤ë¥ë¦ë§ë¨ë©ëªë«ë¬ë­ë®ë¯ë°ë±ë²ë³ë´ëµë¶ë·ë¸ë¹ëºë»ë¼ë½ë¾ë¿ë‘€ë‘둂둃둄둅둆둇둈둉둊둋둌ë‘ë‘Žë‘ë‘둑둒둓둔둕둖둗둘둙둚둛둜ë‘둞둟둠둡둢둣둤둥둦둧둨둩둪둫둬둭둮둯둰둱둲둳둴둵둶둷둸둹둺둻둼둽둾둿뒀ë’뒂뒃뒄뒅뒆뒇뒈뒉뒊뒋뒌ë’ë’Žë’ë’뒑뒒뒓뒔뒕뒖뒗뒘뒙뒚뒛뒜ë’뒞뒟뒠뒡뒢뒣뒤뒥뒦뒧뒨뒩뒪뒫뒬뒭뒮뒯뒰뒱뒲뒳뒴뒵뒶뒷뒸뒹뒺뒻뒼뒽뒾뒿듀ë“듂듃듄듅듆듇듈듉듊듋듌ë“ë“Žë“ë“듑듒듓듔듕듖듗듘듙듚듛드ë“듞듟든듡듢듣들듥듦듧듨듩듪듫듬듭듮듯듰등듲듳듴듵듶듷듸듹듺듻듼듽듾듿딀ë”딂딃딄딅딆딇딈딉딊딋딌ë”딎ë”ë”딑딒딓디딕딖딗딘딙딚딛딜ë”딞딟딠딡","딢딣딤딥딦딧딨딩딪딫딬딭딮딯따딱딲딳딴딵딶딷딸딹딺딻딼딽딾딿땀ë•ë•‚땃땄땅땆땇땈땉땊땋때ë•ë•Žë•ë•ë•‘땒땓땔땕땖땗땘땙땚땛땜ë•ë•žë•Ÿë• ë•¡ë•¢ë•£ë•¤ë•¥ë•¦ë•§ë•¨ë•©ë•ªë•«ë•¬ë•­ë•®ë•¯ë•°ë•±ë•²ë•³ë•´ë•µë•¶ë•·ë•¸ë•¹ë•ºë•»ë•¼ë•½ë•¾ë•¿ë–€ë–떂떃떄떅떆떇떈떉떊떋떌ë–ë–Žë–ë–떑떒떓떔떕떖떗떘떙떚떛떜ë–떞떟떠떡떢떣떤떥떦떧떨떩떪떫떬떭떮떯떰떱떲떳떴떵떶떷떸떹떺떻떼떽떾떿뗀ë—뗂뗃뗄뗅뗆뗇뗈뗉뗊뗋뗌ë—ë—Žë—ë—뗑뗒뗓뗔뗕뗖뗗뗘뗙뗚뗛뗜ë—뗞뗟뗠뗡뗢뗣뗤뗥뗦뗧뗨뗩뗪뗫뗬뗭뗮뗯뗰뗱뗲뗳뗴뗵뗶뗷뗸뗹뗺뗻뗼뗽뗾뗿똀ë˜ë˜‚똃똄똅똆똇똈똉똊똋똌ë˜ë˜Žë˜ë˜ë˜‘똒똓똔똕똖똗똘똙똚똛똜ë˜ë˜žë˜Ÿë˜ ë˜¡","똢똣똤똥똦똧똨똩똪똫똬똭똮똯똰똱똲똳똴똵똶똷똸똹똺똻똼똽똾똿뙀ë™ë™‚뙃뙄뙅뙆뙇뙈뙉뙊뙋뙌ë™ë™Žë™ë™ë™‘뙒뙓뙔뙕뙖뙗뙘뙙뙚뙛뙜ë™ë™žë™Ÿë™ ë™¡ë™¢ë™£ë™¤ë™¥ë™¦ë™§ë™¨ë™©ë™ªë™«ë™¬ë™­ë™®ë™¯ë™°ë™±ë™²ë™³ë™´ë™µë™¶ë™·ë™¸ë™¹ë™ºë™»ë™¼ë™½ë™¾ë™¿ëš€ëšëš‚뚃뚄뚅뚆뚇뚈뚉뚊뚋뚌ëšëšŽëšëšëš‘뚒뚓뚔뚕뚖뚗뚘뚙뚚뚛뚜ëšëšžëšŸëš ëš¡ëš¢ëš£ëš¤ëš¥ëš¦ëš§ëš¨ëš©ëšªëš«ëš¬ëš­ëš®ëš¯ëš°ëš±ëš²ëš³ëš´ëšµëš¶ëš·ëš¸ëš¹ëšºëš»ëš¼ëš½ëš¾ëš¿ë›€ë›ë›‚뛃뛄뛅뛆뛇뛈뛉뛊뛋뛌ë›ë›Žë›ë›ë›‘뛒뛓뛔뛕뛖뛗뛘뛙뛚뛛뛜ë›ë›žë›Ÿë› ë›¡ë›¢ë›£ë›¤ë›¥ë›¦ë›§ë›¨ë›©ë›ªë›«ë›¬ë›­ë›®ë›¯ë›°ë›±ë›²ë›³ë›´ë›µë›¶ë›·ë›¸ë›¹ë›ºë›»ë›¼ë›½ë›¾ë›¿ëœ€ëœëœ‚뜃뜄뜅뜆뜇뜈뜉뜊뜋뜌ëœëœŽëœëœëœ‘뜒뜓뜔뜕뜖뜗뜘뜙뜚뜛뜜ëœëœžëœŸëœ ëœ¡","뜢뜣뜤뜥뜦뜧뜨뜩뜪뜫뜬뜭뜮뜯뜰뜱뜲뜳뜴뜵뜶뜷뜸뜹뜺뜻뜼뜽뜾뜿ë€ëë‚ëƒë„ë…ë†ë‡ëˆë‰ëŠë‹ëŒëëŽëëë‘ë’ë“ë”ë•ë–ë—ë˜ë™ëšë›ëœëëžëŸë ë¡ë¢ë£ë¤ë¥ë¦ë§ë¨ë©ëªë«ë¬ë­ë®ë¯ë°ë±ë²ë³ë´ëµë¶ë·ë¸ë¹ëºë»ë¼ë½ë¾ë¿ëž€ëžëž‚랃랄랅랆랇랈랉랊랋람ëžëžŽëžëžëž‘랒랓랔랕랖랗래랙랚랛랜ëžëžžëžŸëž ëž¡ëž¢ëž£ëž¤ëž¥ëž¦ëž§ëž¨ëž©ëžªëž«ëž¬ëž­ëž®ëž¯ëž°ëž±ëž²ëž³ëž´ëžµëž¶ëž·ëž¸ëž¹ëžºëž»ëž¼ëž½ëž¾ëž¿ëŸ€ëŸëŸ‚럃럄럅럆럇럈량럊럋럌ëŸëŸŽëŸëŸëŸ‘럒럓럔럕럖럗럘럙럚럛럜ëŸëŸžëŸŸëŸ ëŸ¡ëŸ¢ëŸ£ëŸ¤ëŸ¥ëŸ¦ëŸ§ëŸ¨ëŸ©ëŸªëŸ«ëŸ¬ëŸ­ëŸ®ëŸ¯ëŸ°ëŸ±ëŸ²ëŸ³ëŸ´ëŸµëŸ¶ëŸ·ëŸ¸ëŸ¹ëŸºëŸ»ëŸ¼ëŸ½ëŸ¾ëŸ¿ë €ë ë ‚렃렄렅렆렇레렉렊렋렌ë ë Žë ë ë ‘렒렓렔렕렖렗렘렙렚렛렜ë ë žë Ÿë  ë ¡","렢렣려력렦렧련렩렪렫렬렭렮렯렰렱렲렳렴렵렶렷렸령렺렻렼렽렾렿례ë¡ë¡‚롃롄롅롆롇롈롉롊롋롌ë¡ë¡Žë¡ë¡ë¡‘롒롓롔롕롖롗롘롙롚롛로ë¡ë¡žë¡Ÿë¡ ë¡¡ë¡¢ë¡£ë¡¤ë¡¥ë¡¦ë¡§ë¡¨ë¡©ë¡ªë¡«ë¡¬ë¡­ë¡®ë¡¯ë¡°ë¡±ë¡²ë¡³ë¡´ë¡µë¡¶ë¡·ë¡¸ë¡¹ë¡ºë¡»ë¡¼ë¡½ë¡¾ë¡¿ë¢€ë¢ë¢‚뢃뢄뢅뢆뢇뢈뢉뢊뢋뢌ë¢ë¢Žë¢ë¢ë¢‘뢒뢓뢔뢕뢖뢗뢘뢙뢚뢛뢜ë¢ë¢žë¢Ÿë¢ ë¢¡ë¢¢ë¢£ë¢¤ë¢¥ë¢¦ë¢§ë¢¨ë¢©ë¢ªë¢«ë¢¬ë¢­ë¢®ë¢¯ë¢°ë¢±ë¢²ë¢³ë¢´ë¢µë¢¶ë¢·ë¢¸ë¢¹ë¢ºë¢»ë¢¼ë¢½ë¢¾ë¢¿ë£€ë£ë£‚룃룄룅룆룇룈룉룊룋료ë£ë£Žë£ë£ë£‘룒룓룔룕룖룗룘룙룚룛룜ë£ë£žë£Ÿë£ ë£¡ë£¢ë££ë£¤ë£¥ë£¦ë£§ë£¨ë£©ë£ªë£«ë£¬ë£­ë£®ë£¯ë£°ë£±ë£²ë£³ë£´ë£µë£¶ë£·ë£¸ë£¹ë£ºë£»ë£¼ë£½ë£¾ë£¿ë¤€ë¤ë¤‚뤃뤄뤅뤆뤇뤈뤉뤊뤋뤌ë¤ë¤Žë¤ë¤ë¤‘뤒뤓뤔뤕뤖뤗뤘뤙뤚뤛뤜ë¤ë¤žë¤Ÿë¤ ë¤¡","뤢뤣뤤뤥뤦뤧뤨뤩뤪뤫뤬뤭뤮뤯뤰뤱뤲뤳뤴뤵뤶뤷뤸뤹뤺뤻뤼뤽뤾뤿륀ë¥ë¥‚륃륄륅륆륇륈륉륊륋륌ë¥ë¥Žë¥ë¥ë¥‘륒륓륔륕륖륗류륙륚륛륜ë¥ë¥žë¥Ÿë¥ ë¥¡ë¥¢ë¥£ë¥¤ë¥¥ë¥¦ë¥§ë¥¨ë¥©ë¥ªë¥«ë¥¬ë¥­ë¥®ë¥¯ë¥°ë¥±ë¥²ë¥³ë¥´ë¥µë¥¶ë¥·ë¥¸ë¥¹ë¥ºë¥»ë¥¼ë¥½ë¥¾ë¥¿ë¦€ë¦ë¦‚릃름릅릆릇릈릉릊릋릌ë¦ë¦Žë¦ë¦ë¦‘릒릓릔릕릖릗릘릙릚릛릜ë¦ë¦žë¦Ÿë¦ ë¦¡ë¦¢ë¦£ë¦¤ë¦¥ë¦¦ë¦§ë¦¨ë¦©ë¦ªë¦«ë¦¬ë¦­ë¦®ë¦¯ë¦°ë¦±ë¦²ë¦³ë¦´ë¦µë¦¶ë¦·ë¦¸ë¦¹ë¦ºë¦»ë¦¼ë¦½ë¦¾ë¦¿ë§€ë§ë§‚맃맄맅맆맇마막맊맋만ë§ë§Žë§ë§ë§‘맒맓맔맕맖맗맘맙맚맛맜ë§ë§žë§Ÿë§ ë§¡ë§¢ë§£ë§¤ë§¥ë§¦ë§§ë§¨ë§©ë§ªë§«ë§¬ë§­ë§®ë§¯ë§°ë§±ë§²ë§³ë§´ë§µë§¶ë§·ë§¸ë§¹ë§ºë§»ë§¼ë§½ë§¾ë§¿ë¨€ë¨ë¨‚먃먄먅먆먇먈먉먊먋먌ë¨ë¨Žë¨ë¨ë¨‘먒먓먔먕먖먗먘먙먚먛먜ë¨ë¨žë¨Ÿë¨ ë¨¡","먢먣먤먥먦먧먨먩먪먫먬먭먮먯먰먱먲먳먴먵먶먷머먹먺먻먼먽먾먿멀ë©ë©‚멃멄멅멆멇멈멉멊멋멌ë©ë©Žë©ë©ë©‘멒멓메멕멖멗멘멙멚멛멜ë©ë©žë©Ÿë© ë©¡ë©¢ë©£ë©¤ë©¥ë©¦ë©§ë©¨ë©©ë©ªë©«ë©¬ë©­ë©®ë©¯ë©°ë©±ë©²ë©³ë©´ë©µë©¶ë©·ë©¸ë©¹ë©ºë©»ë©¼ë©½ë©¾ë©¿ëª€ëªëª‚몃몄명몆몇몈몉몊몋몌ëªëªŽëªëªëª‘몒몓몔몕몖몗몘몙몚몛몜ëªëªžëªŸëª ëª¡ëª¢ëª£ëª¤ëª¥ëª¦ëª§ëª¨ëª©ëªªëª«ëª¬ëª­ëª®ëª¯ëª°ëª±ëª²ëª³ëª´ëªµëª¶ëª·ëª¸ëª¹ëªºëª»ëª¼ëª½ëª¾ëª¿ë«€ë«ë«‚뫃뫄뫅뫆뫇뫈뫉뫊뫋뫌ë«ë«Žë«ë«ë«‘뫒뫓뫔뫕뫖뫗뫘뫙뫚뫛뫜ë«ë«žë«Ÿë« ë«¡ë«¢ë«£ë«¤ë«¥ë«¦ë«§ë«¨ë«©ë«ªë««ë«¬ë«­ë«®ë«¯ë«°ë«±ë«²ë«³ë«´ë«µë«¶ë«·ë«¸ë«¹ë«ºë«»ë«¼ë«½ë«¾ë«¿ë¬€ë¬ë¬‚묃묄묅묆묇묈묉묊묋묌ë¬ë¬Žë¬ë¬ë¬‘묒묓묔묕묖묗묘묙묚묛묜ë¬ë¬žë¬Ÿë¬ ë¬¡","묢묣묤묥묦묧묨묩묪묫묬묭묮묯묰묱묲묳무묵묶묷문묹묺묻물묽묾묿뭀ë­ë­‚뭃뭄뭅뭆뭇뭈뭉뭊뭋뭌ë­ë­Žë­ë­ë­‘뭒뭓뭔뭕뭖뭗뭘뭙뭚뭛뭜ë­ë­žë­Ÿë­ ë­¡ë­¢ë­£ë­¤ë­¥ë­¦ë­§ë­¨ë­©ë­ªë­«ë­¬ë­­ë­®ë­¯ë­°ë­±ë­²ë­³ë­´ë­µë­¶ë­·ë­¸ë­¹ë­ºë­»ë­¼ë­½ë­¾ë­¿ë®€ë®ë®‚뮃뮄뮅뮆뮇뮈뮉뮊뮋뮌ë®ë®Žë®ë®ë®‘뮒뮓뮔뮕뮖뮗뮘뮙뮚뮛뮜ë®ë®žë®Ÿë® ë®¡ë®¢ë®£ë®¤ë®¥ë®¦ë®§ë®¨ë®©ë®ªë®«ë®¬ë®­ë®®ë®¯ë®°ë®±ë®²ë®³ë®´ë®µë®¶ë®·ë®¸ë®¹ë®ºë®»ë®¼ë®½ë®¾ë®¿ë¯€ë¯ë¯‚믃믄믅믆믇믈믉믊믋믌ë¯ë¯Žë¯ë¯ë¯‘믒믓믔믕믖믗믘믙믚믛믜ë¯ë¯žë¯Ÿë¯ ë¯¡ë¯¢ë¯£ë¯¤ë¯¥ë¯¦ë¯§ë¯¨ë¯©ë¯ªë¯«ë¯¬ë¯­ë¯®ë¯¯ë¯°ë¯±ë¯²ë¯³ë¯´ë¯µë¯¶ë¯·ë¯¸ë¯¹ë¯ºë¯»ë¯¼ë¯½ë¯¾ë¯¿ë°€ë°ë°‚밃밄밅밆밇밈밉밊밋밌ë°ë°Žë°ë°ë°‘밒밓바박밖밗반밙밚받발ë°ë°žë°Ÿë° ë°¡","밢밣밤밥밦밧밨방밪밫밬밭밮밯배백밲밳밴밵밶밷밸밹밺밻밼밽밾밿뱀ë±ë±‚뱃뱄뱅뱆뱇뱈뱉뱊뱋뱌ë±ë±Žë±ë±ë±‘뱒뱓뱔뱕뱖뱗뱘뱙뱚뱛뱜ë±ë±žë±Ÿë± ë±¡ë±¢ë±£ë±¤ë±¥ë±¦ë±§ë±¨ë±©ë±ªë±«ë±¬ë±­ë±®ë±¯ë±°ë±±ë±²ë±³ë±´ë±µë±¶ë±·ë±¸ë±¹ë±ºë±»ë±¼ë±½ë±¾ë±¿ë²€ë²ë²‚벃버벅벆벇번벉벊벋벌ë²ë²Žë²ë²ë²‘벒벓범법벖벗벘벙벚벛벜ë²ë²žë²Ÿë² ë²¡ë²¢ë²£ë²¤ë²¥ë²¦ë²§ë²¨ë²©ë²ªë²«ë²¬ë²­ë²®ë²¯ë²°ë²±ë²²ë²³ë²´ë²µë²¶ë²·ë²¸ë²¹ë²ºë²»ë²¼ë²½ë²¾ë²¿ë³€ë³ë³‚볃별볅볆볇볈볉볊볋볌ë³ë³Žë³ë³ë³‘볒볓볔볕볖볗볘볙볚볛볜ë³ë³žë³Ÿë³ ë³¡ë³¢ë³£ë³¤ë³¥ë³¦ë³§ë³¨ë³©ë³ªë³«ë³¬ë³­ë³®ë³¯ë³°ë³±ë³²ë³³ë³´ë³µë³¶ë³·ë³¸ë³¹ë³ºë³»ë³¼ë³½ë³¾ë³¿ë´€ë´ë´‚봃봄봅봆봇봈봉봊봋봌ë´ë´Žë´ë´ë´‘봒봓봔봕봖봗봘봙봚봛봜ë´ë´žë´Ÿë´ ë´¡","봢봣봤봥봦봧봨봩봪봫봬봭봮봯봰봱봲봳봴봵봶봷봸봹봺봻봼봽봾봿뵀ëµëµ‚뵃뵄뵅뵆뵇뵈뵉뵊뵋뵌ëµëµŽëµëµëµ‘뵒뵓뵔뵕뵖뵗뵘뵙뵚뵛뵜ëµëµžëµŸëµ ëµ¡ëµ¢ëµ£ëµ¤ëµ¥ëµ¦ëµ§ëµ¨ëµ©ëµªëµ«ëµ¬ëµ­ëµ®ëµ¯ëµ°ëµ±ëµ²ëµ³ëµ´ëµµëµ¶ëµ·ëµ¸ëµ¹ëµºëµ»ëµ¼ëµ½ëµ¾ëµ¿ë¶€ë¶ë¶‚붃분붅붆붇불붉붊붋붌ë¶ë¶Žë¶ë¶ë¶‘붒붓붔붕붖붗붘붙붚붛붜ë¶ë¶žë¶Ÿë¶ ë¶¡ë¶¢ë¶£ë¶¤ë¶¥ë¶¦ë¶§ë¶¨ë¶©ë¶ªë¶«ë¶¬ë¶­ë¶®ë¶¯ë¶°ë¶±ë¶²ë¶³ë¶´ë¶µë¶¶ë¶·ë¶¸ë¶¹ë¶ºë¶»ë¶¼ë¶½ë¶¾ë¶¿ë·€ë·ë·‚뷃뷄뷅뷆뷇뷈뷉뷊뷋뷌ë·ë·Žë·ë·ë·‘뷒뷓뷔뷕뷖뷗뷘뷙뷚뷛뷜ë·ë·žë·Ÿë· ë·¡ë·¢ë·£ë·¤ë·¥ë·¦ë·§ë·¨ë·©ë·ªë·«ë·¬ë·­ë·®ë·¯ë·°ë·±ë·²ë·³ë·´ë·µë·¶ë··ë·¸ë·¹ë·ºë·»ë·¼ë·½ë·¾ë·¿ë¸€ë¸ë¸‚븃븄븅븆븇븈븉븊븋브ë¸ë¸Žë¸ë¸ë¸‘븒븓블븕븖븗븘븙븚븛븜ë¸ë¸žë¸Ÿë¸ ë¸¡","븢븣븤븥븦븧븨븩븪븫븬븭븮븯븰븱븲븳븴븵븶븷븸븹븺븻븼븽븾븿빀ë¹ë¹‚빃비빅빆빇빈빉빊빋빌ë¹ë¹Žë¹ë¹ë¹‘빒빓빔빕빖빗빘빙빚빛빜ë¹ë¹žë¹Ÿë¹ ë¹¡ë¹¢ë¹£ë¹¤ë¹¥ë¹¦ë¹§ë¹¨ë¹©ë¹ªë¹«ë¹¬ë¹­ë¹®ë¹¯ë¹°ë¹±ë¹²ë¹³ë¹´ë¹µë¹¶ë¹·ë¹¸ë¹¹ë¹ºë¹»ë¹¼ë¹½ë¹¾ë¹¿ëº€ëºëº‚뺃뺄뺅뺆뺇뺈뺉뺊뺋뺌ëºëºŽëºëºëº‘뺒뺓뺔뺕뺖뺗뺘뺙뺚뺛뺜ëºëºžëºŸëº ëº¡ëº¢ëº£ëº¤ëº¥ëº¦ëº§ëº¨ëº©ëºªëº«ëº¬ëº­ëº®ëº¯ëº°ëº±ëº²ëº³ëº´ëºµëº¶ëº·ëº¸ëº¹ëººëº»ëº¼ëº½ëº¾ëº¿ë»€ë»ë»‚뻃뻄뻅뻆뻇뻈뻉뻊뻋뻌ë»ë»Žë»ë»ë»‘뻒뻓뻔뻕뻖뻗뻘뻙뻚뻛뻜ë»ë»žë»Ÿë» ë»¡ë»¢ë»£ë»¤ë»¥ë»¦ë»§ë»¨ë»©ë»ªë»«ë»¬ë»­ë»®ë»¯ë»°ë»±ë»²ë»³ë»´ë»µë»¶ë»·ë»¸ë»¹ë»ºë»»ë»¼ë»½ë»¾ë»¿ë¼€ë¼ë¼‚뼃뼄뼅뼆뼇뼈뼉뼊뼋뼌ë¼ë¼Žë¼ë¼ë¼‘뼒뼓뼔뼕뼖뼗뼘뼙뼚뼛뼜ë¼ë¼žë¼Ÿë¼ ë¼¡","뼢뼣뼤뼥뼦뼧뼨뼩뼪뼫뼬뼭뼮뼯뼰뼱뼲뼳뼴뼵뼶뼷뼸뼹뼺뼻뼼뼽뼾뼿뽀ë½ë½‚뽃뽄뽅뽆뽇뽈뽉뽊뽋뽌ë½ë½Žë½ë½ë½‘뽒뽓뽔뽕뽖뽗뽘뽙뽚뽛뽜ë½ë½žë½Ÿë½ ë½¡ë½¢ë½£ë½¤ë½¥ë½¦ë½§ë½¨ë½©ë½ªë½«ë½¬ë½­ë½®ë½¯ë½°ë½±ë½²ë½³ë½´ë½µë½¶ë½·ë½¸ë½¹ë½ºë½»ë½¼ë½½ë½¾ë½¿ë¾€ë¾ë¾‚뾃뾄뾅뾆뾇뾈뾉뾊뾋뾌ë¾ë¾Žë¾ë¾ë¾‘뾒뾓뾔뾕뾖뾗뾘뾙뾚뾛뾜ë¾ë¾žë¾Ÿë¾ ë¾¡ë¾¢ë¾£ë¾¤ë¾¥ë¾¦ë¾§ë¾¨ë¾©ë¾ªë¾«ë¾¬ë¾­ë¾®ë¾¯ë¾°ë¾±ë¾²ë¾³ë¾´ë¾µë¾¶ë¾·ë¾¸ë¾¹ë¾ºë¾»ë¾¼ë¾½ë¾¾ë¾¿ë¿€ë¿ë¿‚뿃뿄뿅뿆뿇뿈뿉뿊뿋뿌ë¿ë¿Žë¿ë¿ë¿‘뿒뿓뿔뿕뿖뿗뿘뿙뿚뿛뿜ë¿ë¿žë¿Ÿë¿ ë¿¡ë¿¢ë¿£ë¿¤ë¿¥ë¿¦ë¿§ë¿¨ë¿©ë¿ªë¿«ë¿¬ë¿­ë¿®ë¿¯ë¿°ë¿±ë¿²ë¿³ë¿´ë¿µë¿¶ë¿·ë¿¸ë¿¹ë¿ºë¿»ë¿¼ë¿½ë¿¾ë¿¿ì€€ì€ì€‚쀃쀄쀅쀆쀇쀈쀉쀊쀋쀌ì€ì€Žì€ì€ì€‘쀒쀓쀔쀕쀖쀗쀘쀙쀚쀛쀜ì€ì€žì€Ÿì€ ì€¡","쀢쀣쀤쀥쀦쀧쀨쀩쀪쀫쀬쀭쀮쀯쀰쀱쀲쀳쀴쀵쀶쀷쀸쀹쀺쀻쀼쀽쀾쀿ì€ìì‚ìƒì„ì…ì†ì‡ìˆì‰ìŠì‹ìŒììŽììì‘ì’ì“ì”ì•ì–ì—ì˜ì™ìšì›ìœììžìŸì ì¡ì¢ì£ì¤ì¥ì¦ì§ì¨ì©ìªì«ì¬ì­ì®ì¯ì°ì±ì²ì³ì´ìµì¶ì·ì¸ì¹ìºì»ì¼ì½ì¾ì¿ì‚€ì‚삂삃삄삅삆삇삈삉삊삋삌ì‚ì‚Žì‚ì‚삑삒삓삔삕삖삗삘삙삚삛삜ì‚삞삟삠삡삢삣삤삥삦삧삨삩삪삫사삭삮삯산삱삲삳살삵삶삷삸삹삺삻삼삽삾삿샀ìƒìƒ‚샃샄샅샆샇새색샊샋샌ìƒìƒŽìƒìƒìƒ‘샒샓샔샕샖샗샘샙샚샛샜ìƒìƒžìƒŸìƒ ìƒ¡ìƒ¢ìƒ£ìƒ¤ìƒ¥ìƒ¦ìƒ§ìƒ¨ìƒ©ìƒªìƒ«ìƒ¬ìƒ­ìƒ®ìƒ¯ìƒ°ìƒ±ìƒ²ìƒ³ìƒ´ìƒµìƒ¶ìƒ·ìƒ¸ìƒ¹ìƒºìƒ»ìƒ¼ìƒ½ìƒ¾ìƒ¿ì„€ì„섂섃섄섅섆섇섈섉섊섋섌ì„ì„Žì„ì„섑섒섓섔섕섖섗섘섙섚섛서ì„ì„žì„Ÿì„ ì„¡","섢섣설섥섦섧섨섩섪섫섬섭섮섯섰성섲섳섴섵섶섷세섹섺섻센섽섾섿셀ì…셂셃셄셅셆셇셈셉셊셋셌ì…ì…Žì…ì…셑셒셓셔셕셖셗션셙셚셛셜ì…셞셟셠셡셢셣셤셥셦셧셨셩셪셫셬셭셮셯셰셱셲셳셴셵셶셷셸셹셺셻셼셽셾셿솀ì†ì†‚솃솄솅솆솇솈솉솊솋소ì†ì†Žì†ì†ì†‘솒솓솔솕솖솗솘솙솚솛솜ì†ì†žì†Ÿì† ì†¡ì†¢ì†£ì†¤ì†¥ì†¦ì†§ì†¨ì†©ì†ªì†«ì†¬ì†­ì†®ì†¯ì†°ì†±ì†²ì†³ì†´ì†µì†¶ì†·ì†¸ì†¹ì†ºì†»ì†¼ì†½ì†¾ì†¿ì‡€ì‡ì‡‚쇃쇄쇅쇆쇇쇈쇉쇊쇋쇌ì‡ì‡Žì‡ì‡ì‡‘쇒쇓쇔쇕쇖쇗쇘쇙쇚쇛쇜ì‡ì‡žì‡Ÿì‡ ì‡¡ì‡¢ì‡£ì‡¤ì‡¥ì‡¦ì‡§ì‡¨ì‡©ì‡ªì‡«ì‡¬ì‡­ì‡®ì‡¯ì‡°ì‡±ì‡²ì‡³ì‡´ì‡µì‡¶ì‡·ì‡¸ì‡¹ì‡ºì‡»ì‡¼ì‡½ì‡¾ì‡¿ìˆ€ìˆìˆ‚숃숄숅숆숇숈숉숊숋숌ìˆìˆŽìˆìˆìˆ‘숒숓숔숕숖숗수숙숚숛순ìˆìˆžìˆŸìˆ ìˆ¡","숢숣숤숥숦숧숨숩숪숫숬숭숮숯숰숱숲숳숴숵숶숷숸숹숺숻숼숽숾숿쉀ì‰ì‰‚쉃쉄쉅쉆쉇쉈쉉쉊쉋쉌ì‰ì‰Žì‰ì‰ì‰‘쉒쉓쉔쉕쉖쉗쉘쉙쉚쉛쉜ì‰ì‰žì‰Ÿì‰ ì‰¡ì‰¢ì‰£ì‰¤ì‰¥ì‰¦ì‰§ì‰¨ì‰©ì‰ªì‰«ì‰¬ì‰­ì‰®ì‰¯ì‰°ì‰±ì‰²ì‰³ì‰´ì‰µì‰¶ì‰·ì‰¸ì‰¹ì‰ºì‰»ì‰¼ì‰½ì‰¾ì‰¿ìŠ€ìŠìŠ‚슃슄슅슆슇슈슉슊슋슌ìŠìŠŽìŠìŠìŠ‘슒슓슔슕슖슗슘슙슚슛슜ìŠìŠžìŠŸìŠ ìŠ¡ìŠ¢ìŠ£ìŠ¤ìŠ¥ìŠ¦ìŠ§ìŠ¨ìŠ©ìŠªìŠ«ìŠ¬ìŠ­ìŠ®ìŠ¯ìŠ°ìŠ±ìŠ²ìŠ³ìŠ´ìŠµìŠ¶ìŠ·ìŠ¸ìŠ¹ìŠºìŠ»ìŠ¼ìŠ½ìŠ¾ìŠ¿ì‹€ì‹ì‹‚싃싄싅싆싇싈싉싊싋싌ì‹ì‹Žì‹ì‹ì‹‘싒싓싔싕싖싗싘싙싚싛시ì‹ì‹žì‹Ÿì‹ ì‹¡ì‹¢ì‹£ì‹¤ì‹¥ì‹¦ì‹§ì‹¨ì‹©ì‹ªì‹«ì‹¬ì‹­ì‹®ì‹¯ì‹°ì‹±ì‹²ì‹³ì‹´ì‹µì‹¶ì‹·ì‹¸ì‹¹ì‹ºì‹»ì‹¼ì‹½ì‹¾ì‹¿ìŒ€ìŒìŒ‚쌃쌄쌅쌆쌇쌈쌉쌊쌋쌌ìŒìŒŽìŒìŒìŒ‘쌒쌓쌔쌕쌖쌗쌘쌙쌚쌛쌜ìŒìŒžìŒŸìŒ ìŒ¡","쌢쌣쌤쌥쌦쌧쌨쌩쌪쌫쌬쌭쌮쌯쌰쌱쌲쌳쌴쌵쌶쌷쌸쌹쌺쌻쌼쌽쌾쌿ì€ìì‚ìƒì„ì…ì†ì‡ìˆì‰ìŠì‹ìŒììŽììì‘ì’ì“ì”ì•ì–ì—ì˜ì™ìšì›ìœììžìŸì ì¡ì¢ì£ì¤ì¥ì¦ì§ì¨ì©ìªì«ì¬ì­ì®ì¯ì°ì±ì²ì³ì´ìµì¶ì·ì¸ì¹ìºì»ì¼ì½ì¾ì¿ìŽ€ìŽìŽ‚쎃쎄쎅쎆쎇쎈쎉쎊쎋쎌ìŽìŽŽìŽìŽìŽ‘쎒쎓쎔쎕쎖쎗쎘쎙쎚쎛쎜ìŽìŽžìŽŸìŽ ìŽ¡ìŽ¢ìŽ£ìŽ¤ìŽ¥ìŽ¦ìŽ§ìŽ¨ìŽ©ìŽªìŽ«ìŽ¬ìŽ­ìŽ®ìŽ¯ìŽ°ìŽ±ìŽ²ìŽ³ìŽ´ìŽµìŽ¶ìŽ·ìŽ¸ìŽ¹ìŽºìŽ»ìŽ¼ìŽ½ìŽ¾ìŽ¿ì€ìì‚ìƒì„ì…ì†ì‡ìˆì‰ìŠì‹ìŒììŽììì‘ì’ì“ì”ì•ì–ì—ì˜ì™ìšì›ìœììžìŸì ì¡ì¢ì£ì¤ì¥ì¦ì§ì¨ì©ìªì«ì¬ì­ì®ì¯ì°ì±ì²ì³ì´ìµì¶ì·ì¸ì¹ìºì»ì¼ì½ì¾ì¿ì€ìì‚ìƒì„ì…ì†ì‡ìˆì‰ìŠì‹ìŒììŽììì‘ì’ì“ì”ì•ì–ì—ì˜ì™ìšì›ìœììžìŸì ì¡","ì¢ì£ì¤ì¥ì¦ì§ì¨ì©ìªì«ì¬ì­ì®ì¯ì°ì±ì²ì³ì´ìµì¶ì·ì¸ì¹ìºì»ì¼ì½ì¾ì¿ì‘€ì‘쑂쑃쑄쑅쑆쑇쑈쑉쑊쑋쑌ì‘ì‘Žì‘ì‘쑑쑒쑓쑔쑕쑖쑗쑘쑙쑚쑛쑜ì‘쑞쑟쑠쑡쑢쑣쑤쑥쑦쑧쑨쑩쑪쑫쑬쑭쑮쑯쑰쑱쑲쑳쑴쑵쑶쑷쑸쑹쑺쑻쑼쑽쑾쑿쒀ì’쒂쒃쒄쒅쒆쒇쒈쒉쒊쒋쒌ì’ì’Žì’ì’쒑쒒쒓쒔쒕쒖쒗쒘쒙쒚쒛쒜ì’쒞쒟쒠쒡쒢쒣쒤쒥쒦쒧쒨쒩쒪쒫쒬쒭쒮쒯쒰쒱쒲쒳쒴쒵쒶쒷쒸쒹쒺쒻쒼쒽쒾쒿쓀ì“쓂쓃쓄쓅쓆쓇쓈쓉쓊쓋쓌ì“ì“Žì“ì“쓑쓒쓓쓔쓕쓖쓗쓘쓙쓚쓛쓜ì“쓞쓟쓠쓡쓢쓣쓤쓥쓦쓧쓨쓩쓪쓫쓬쓭쓮쓯쓰쓱쓲쓳쓴쓵쓶쓷쓸쓹쓺쓻쓼쓽쓾쓿씀ì”씂씃씄씅씆씇씈씉씊씋씌ì”씎ì”ì”씑씒씓씔씕씖씗씘씙씚씛씜ì”씞씟씠씡","씢씣씤씥씦씧씨씩씪씫씬씭씮씯씰씱씲씳씴씵씶씷씸씹씺씻씼씽씾씿앀ì•ì•‚앃아악앆앇안앉않앋알ì•ì•Žì•ì•ì•‘앒앓암압앖앗았앙앚앛앜ì•ì•žì•Ÿì• ì•¡ì•¢ì•£ì•¤ì•¥ì•¦ì•§ì•¨ì•©ì•ªì•«ì•¬ì•­ì•®ì•¯ì•°ì•±ì•²ì•³ì•´ì•µì•¶ì•·ì•¸ì•¹ì•ºì•»ì•¼ì•½ì•¾ì•¿ì–€ì–얂얃얄얅얆얇얈얉얊얋얌ì–ì–Žì–ì–양얒얓얔얕얖얗얘얙얚얛얜ì–얞얟얠얡얢얣얤얥얦얧얨얩얪얫얬얭얮얯얰얱얲얳어억얶얷언얹얺얻얼얽얾얿엀ì—엂엃엄업없엇었엉엊엋엌ì—ì—Žì—ì—엑엒엓엔엕엖엗엘엙엚엛엜ì—엞엟엠엡엢엣엤엥엦엧엨엩엪엫여역엮엯연엱엲엳열엵엶엷엸엹엺엻염엽엾엿였ì˜ì˜‚옃옄옅옆옇예옉옊옋옌ì˜ì˜Žì˜ì˜ì˜‘옒옓옔옕옖옗옘옙옚옛옜ì˜ì˜žì˜Ÿì˜ ì˜¡","옢옣오옥옦옧온옩옪옫올옭옮옯옰옱옲옳옴옵옶옷옸옹옺옻옼옽옾옿와ì™ì™‚왃완왅왆왇왈왉왊왋왌ì™ì™Žì™ì™ì™‘왒왓왔왕왖왗왘왙왚왛왜ì™ì™žì™Ÿì™ ì™¡ì™¢ì™£ì™¤ì™¥ì™¦ì™§ì™¨ì™©ì™ªì™«ì™¬ì™­ì™®ì™¯ì™°ì™±ì™²ì™³ì™´ì™µì™¶ì™·ì™¸ì™¹ì™ºì™»ì™¼ì™½ì™¾ì™¿ìš€ìšìš‚욃욄욅욆욇욈욉욊욋욌ìšìšŽìšìšìš‘욒욓요욕욖욗욘욙욚욛욜ìšìšžìšŸìš ìš¡ìš¢ìš£ìš¤ìš¥ìš¦ìš§ìš¨ìš©ìšªìš«ìš¬ìš­ìš®ìš¯ìš°ìš±ìš²ìš³ìš´ìšµìš¶ìš·ìš¸ìš¹ìšºìš»ìš¼ìš½ìš¾ìš¿ì›€ì›ì›‚웃웄웅웆웇웈웉웊웋워ì›ì›Žì›ì›ì›‘웒웓월웕웖웗웘웙웚웛웜ì›ì›žì›Ÿì› ì›¡ì›¢ì›£ì›¤ì›¥ì›¦ì›§ì›¨ì›©ì›ªì›«ì›¬ì›­ì›®ì›¯ì›°ì›±ì›²ì›³ì›´ì›µì›¶ì›·ì›¸ì›¹ì›ºì›»ì›¼ì›½ì›¾ì›¿ìœ€ìœìœ‚윃위윅윆윇윈윉윊윋윌ìœìœŽìœìœìœ‘윒윓윔윕윖윗윘윙윚윛윜ìœìœžìœŸìœ ìœ¡","윢윣윤윥윦윧율윩윪윫윬윭윮윯윰윱윲윳윴융윶윷윸윹윺윻으윽윾윿ì€ìì‚ìƒì„ì…ì†ì‡ìˆì‰ìŠì‹ìŒììŽììì‘ì’ì“ì”ì•ì–ì—ì˜ì™ìšì›ìœììžìŸì ì¡ì¢ì£ì¤ì¥ì¦ì§ì¨ì©ìªì«ì¬ì­ì®ì¯ì°ì±ì²ì³ì´ìµì¶ì·ì¸ì¹ìºì»ì¼ì½ì¾ì¿ìž€ìžìž‚잃임입잆잇있잉잊잋잌ìžìžŽìžìžìž‘잒잓잔잕잖잗잘잙잚잛잜ìžìžžìžŸìž ìž¡ìž¢ìž£ìž¤ìž¥ìž¦ìž§ìž¨ìž©ìžªìž«ìž¬ìž­ìž®ìž¯ìž°ìž±ìž²ìž³ìž´ìžµìž¶ìž·ìž¸ìž¹ìžºìž»ìž¼ìž½ìž¾ìž¿ìŸ€ìŸìŸ‚쟃쟄쟅쟆쟇쟈쟉쟊쟋쟌ìŸìŸŽìŸìŸìŸ‘쟒쟓쟔쟕쟖쟗쟘쟙쟚쟛쟜ìŸìŸžìŸŸìŸ ìŸ¡ìŸ¢ìŸ£ìŸ¤ìŸ¥ìŸ¦ìŸ§ìŸ¨ìŸ©ìŸªìŸ«ìŸ¬ìŸ­ìŸ®ìŸ¯ìŸ°ìŸ±ìŸ²ìŸ³ìŸ´ìŸµìŸ¶ìŸ·ìŸ¸ìŸ¹ìŸºìŸ»ìŸ¼ìŸ½ìŸ¾ìŸ¿ì €ì ì ‚젃전젅젆젇절젉젊젋젌ì ì Žì ì ì ‘젒젓젔정젖젗젘젙젚젛제ì ì žì Ÿì  ì ¡","젢젣젤젥젦젧젨젩젪젫젬젭젮젯젰젱젲젳젴젵젶젷져젹젺젻젼젽젾젿졀ì¡ì¡‚졃졄졅졆졇졈졉졊졋졌ì¡ì¡Žì¡ì¡ì¡‘졒졓졔졕졖졗졘졙졚졛졜ì¡ì¡žì¡Ÿì¡ ì¡¡ì¡¢ì¡£ì¡¤ì¡¥ì¡¦ì¡§ì¡¨ì¡©ì¡ªì¡«ì¡¬ì¡­ì¡®ì¡¯ì¡°ì¡±ì¡²ì¡³ì¡´ì¡µì¡¶ì¡·ì¡¸ì¡¹ì¡ºì¡»ì¡¼ì¡½ì¡¾ì¡¿ì¢€ì¢ì¢‚좃좄종좆좇좈좉좊좋좌ì¢ì¢Žì¢ì¢ì¢‘좒좓좔좕좖좗좘좙좚좛좜ì¢ì¢žì¢Ÿì¢ ì¢¡ì¢¢ì¢£ì¢¤ì¢¥ì¢¦ì¢§ì¢¨ì¢©ì¢ªì¢«ì¢¬ì¢­ì¢®ì¢¯ì¢°ì¢±ì¢²ì¢³ì¢´ì¢µì¢¶ì¢·ì¢¸ì¢¹ì¢ºì¢»ì¢¼ì¢½ì¢¾ì¢¿ì£€ì£ì£‚죃죄죅죆죇죈죉죊죋죌ì£ì£Žì£ì£ì£‘죒죓죔죕죖죗죘죙죚죛죜ì£ì£žì£Ÿì£ ì£¡ì£¢ì££ì£¤ì£¥ì£¦ì£§ì£¨ì£©ì£ªì£«ì£¬ì£­ì£®ì£¯ì£°ì£±ì£²ì£³ì£´ì£µì£¶ì£·ì£¸ì£¹ì£ºì£»ì£¼ì£½ì£¾ì£¿ì¤€ì¤ì¤‚줃줄줅줆줇줈줉줊줋줌ì¤ì¤Žì¤ì¤ì¤‘줒줓줔줕줖줗줘줙줚줛줜ì¤ì¤žì¤Ÿì¤ ì¤¡","줢줣줤줥줦줧줨줩줪줫줬줭줮줯줰줱줲줳줴줵줶줷줸줹줺줻줼줽줾줿쥀ì¥ì¥‚쥃쥄쥅쥆쥇쥈쥉쥊쥋쥌ì¥ì¥Žì¥ì¥ì¥‘쥒쥓쥔쥕쥖쥗쥘쥙쥚쥛쥜ì¥ì¥žì¥Ÿì¥ ì¥¡ì¥¢ì¥£ì¥¤ì¥¥ì¥¦ì¥§ì¥¨ì¥©ì¥ªì¥«ì¥¬ì¥­ì¥®ì¥¯ì¥°ì¥±ì¥²ì¥³ì¥´ì¥µì¥¶ì¥·ì¥¸ì¥¹ì¥ºì¥»ì¥¼ì¥½ì¥¾ì¥¿ì¦€ì¦ì¦‚즃즄즅즆즇즈즉즊즋즌ì¦ì¦Žì¦ì¦ì¦‘즒즓즔즕즖즗즘즙즚즛즜ì¦ì¦žì¦Ÿì¦ ì¦¡ì¦¢ì¦£ì¦¤ì¦¥ì¦¦ì¦§ì¦¨ì¦©ì¦ªì¦«ì¦¬ì¦­ì¦®ì¦¯ì¦°ì¦±ì¦²ì¦³ì¦´ì¦µì¦¶ì¦·ì¦¸ì¦¹ì¦ºì¦»ì¦¼ì¦½ì¦¾ì¦¿ì§€ì§ì§‚짃진짅짆짇질짉짊짋짌ì§ì§Žì§ì§ì§‘짒짓짔징짖짗짘짙짚짛짜ì§ì§žì§Ÿì§ ì§¡ì§¢ì§£ì§¤ì§¥ì§¦ì§§ì§¨ì§©ì§ªì§«ì§¬ì§­ì§®ì§¯ì§°ì§±ì§²ì§³ì§´ì§µì§¶ì§·ì§¸ì§¹ì§ºì§»ì§¼ì§½ì§¾ì§¿ì¨€ì¨ì¨‚쨃쨄쨅쨆쨇쨈쨉쨊쨋쨌ì¨ì¨Žì¨ì¨ì¨‘쨒쨓쨔쨕쨖쨗쨘쨙쨚쨛쨜ì¨ì¨žì¨Ÿì¨ ì¨¡","쨢쨣쨤쨥쨦쨧쨨쨩쨪쨫쨬쨭쨮쨯쨰쨱쨲쨳쨴쨵쨶쨷쨸쨹쨺쨻쨼쨽쨾쨿쩀ì©ì©‚쩃쩄쩅쩆쩇쩈쩉쩊쩋쩌ì©ì©Žì©ì©ì©‘쩒쩓쩔쩕쩖쩗쩘쩙쩚쩛쩜ì©ì©žì©Ÿì© ì©¡ì©¢ì©£ì©¤ì©¥ì©¦ì©§ì©¨ì©©ì©ªì©«ì©¬ì©­ì©®ì©¯ì©°ì©±ì©²ì©³ì©´ì©µì©¶ì©·ì©¸ì©¹ì©ºì©»ì©¼ì©½ì©¾ì©¿ìª€ìªìª‚쪃쪄쪅쪆쪇쪈쪉쪊쪋쪌ìªìªŽìªìªìª‘쪒쪓쪔쪕쪖쪗쪘쪙쪚쪛쪜ìªìªžìªŸìª ìª¡ìª¢ìª£ìª¤ìª¥ìª¦ìª§ìª¨ìª©ìªªìª«ìª¬ìª­ìª®ìª¯ìª°ìª±ìª²ìª³ìª´ìªµìª¶ìª·ìª¸ìª¹ìªºìª»ìª¼ìª½ìª¾ìª¿ì«€ì«ì«‚쫃쫄쫅쫆쫇쫈쫉쫊쫋쫌ì«ì«Žì«ì«ì«‘쫒쫓쫔쫕쫖쫗쫘쫙쫚쫛쫜ì«ì«žì«Ÿì« ì«¡ì«¢ì«£ì«¤ì«¥ì«¦ì«§ì«¨ì«©ì«ªì««ì«¬ì«­ì«®ì«¯ì«°ì«±ì«²ì«³ì«´ì«µì«¶ì«·ì«¸ì«¹ì«ºì«»ì«¼ì«½ì«¾ì«¿ì¬€ì¬ì¬‚쬃쬄쬅쬆쬇쬈쬉쬊쬋쬌ì¬ì¬Žì¬ì¬ì¬‘쬒쬓쬔쬕쬖쬗쬘쬙쬚쬛쬜ì¬ì¬žì¬Ÿì¬ ì¬¡","쬢쬣쬤쬥쬦쬧쬨쬩쬪쬫쬬쬭쬮쬯쬰쬱쬲쬳쬴쬵쬶쬷쬸쬹쬺쬻쬼쬽쬾쬿쭀ì­ì­‚쭃쭄쭅쭆쭇쭈쭉쭊쭋쭌ì­ì­Žì­ì­ì­‘쭒쭓쭔쭕쭖쭗쭘쭙쭚쭛쭜ì­ì­žì­Ÿì­ ì­¡ì­¢ì­£ì­¤ì­¥ì­¦ì­§ì­¨ì­©ì­ªì­«ì­¬ì­­ì­®ì­¯ì­°ì­±ì­²ì­³ì­´ì­µì­¶ì­·ì­¸ì­¹ì­ºì­»ì­¼ì­½ì­¾ì­¿ì®€ì®ì®‚쮃쮄쮅쮆쮇쮈쮉쮊쮋쮌ì®ì®Žì®ì®ì®‘쮒쮓쮔쮕쮖쮗쮘쮙쮚쮛쮜ì®ì®žì®Ÿì® ì®¡ì®¢ì®£ì®¤ì®¥ì®¦ì®§ì®¨ì®©ì®ªì®«ì®¬ì®­ì®®ì®¯ì®°ì®±ì®²ì®³ì®´ì®µì®¶ì®·ì®¸ì®¹ì®ºì®»ì®¼ì®½ì®¾ì®¿ì¯€ì¯ì¯‚쯃쯄쯅쯆쯇쯈쯉쯊쯋쯌ì¯ì¯Žì¯ì¯ì¯‘쯒쯓쯔쯕쯖쯗쯘쯙쯚쯛쯜ì¯ì¯žì¯Ÿì¯ ì¯¡ì¯¢ì¯£ì¯¤ì¯¥ì¯¦ì¯§ì¯¨ì¯©ì¯ªì¯«ì¯¬ì¯­ì¯®ì¯¯ì¯°ì¯±ì¯²ì¯³ì¯´ì¯µì¯¶ì¯·ì¯¸ì¯¹ì¯ºì¯»ì¯¼ì¯½ì¯¾ì¯¿ì°€ì°ì°‚찃찄찅찆찇찈찉찊찋찌ì°ì°Žì°ì°ì°‘찒찓찔찕찖찗찘찙찚찛찜ì°ì°žì°Ÿì° ì°¡","찢찣찤찥찦찧차착찪찫찬찭찮찯찰찱찲찳찴찵찶찷참찹찺찻찼창찾찿챀ì±ì±‚챃채책챆챇챈챉챊챋챌ì±ì±Žì±ì±ì±‘챒챓챔챕챖챗챘챙챚챛챜ì±ì±žì±Ÿì± ì±¡ì±¢ì±£ì±¤ì±¥ì±¦ì±§ì±¨ì±©ì±ªì±«ì±¬ì±­ì±®ì±¯ì±°ì±±ì±²ì±³ì±´ì±µì±¶ì±·ì±¸ì±¹ì±ºì±»ì±¼ì±½ì±¾ì±¿ì²€ì²ì²‚첃첄첅첆첇첈첉첊첋첌ì²ì²Žì²ì²ì²‘첒첓첔첕첖첗처척첚첛천ì²ì²žì²Ÿì² ì²¡ì²¢ì²£ì²¤ì²¥ì²¦ì²§ì²¨ì²©ì²ªì²«ì²¬ì²­ì²®ì²¯ì²°ì²±ì²²ì²³ì²´ì²µì²¶ì²·ì²¸ì²¹ì²ºì²»ì²¼ì²½ì²¾ì²¿ì³€ì³ì³‚쳃쳄쳅쳆쳇쳈쳉쳊쳋쳌ì³ì³Žì³ì³ì³‘쳒쳓쳔쳕쳖쳗쳘쳙쳚쳛쳜ì³ì³žì³Ÿì³ ì³¡ì³¢ì³£ì³¤ì³¥ì³¦ì³§ì³¨ì³©ì³ªì³«ì³¬ì³­ì³®ì³¯ì³°ì³±ì³²ì³³ì³´ì³µì³¶ì³·ì³¸ì³¹ì³ºì³»ì³¼ì³½ì³¾ì³¿ì´€ì´ì´‚촃촄촅촆촇초촉촊촋촌ì´ì´Žì´ì´ì´‘촒촓촔촕촖촗촘촙촚촛촜ì´ì´žì´Ÿì´ ì´¡","촢촣촤촥촦촧촨촩촪촫촬촭촮촯촰촱촲촳촴촵촶촷촸촹촺촻촼촽촾촿쵀ìµìµ‚쵃쵄쵅쵆쵇쵈쵉쵊쵋쵌ìµìµŽìµìµìµ‘쵒쵓쵔쵕쵖쵗쵘쵙쵚쵛최ìµìµžìµŸìµ ìµ¡ìµ¢ìµ£ìµ¤ìµ¥ìµ¦ìµ§ìµ¨ìµ©ìµªìµ«ìµ¬ìµ­ìµ®ìµ¯ìµ°ìµ±ìµ²ìµ³ìµ´ìµµìµ¶ìµ·ìµ¸ìµ¹ìµºìµ»ìµ¼ìµ½ìµ¾ìµ¿ì¶€ì¶ì¶‚춃춄춅춆춇춈춉춊춋춌ì¶ì¶Žì¶ì¶ì¶‘춒춓추축춖춗춘춙춚춛출ì¶ì¶žì¶Ÿì¶ ì¶¡ì¶¢ì¶£ì¶¤ì¶¥ì¶¦ì¶§ì¶¨ì¶©ì¶ªì¶«ì¶¬ì¶­ì¶®ì¶¯ì¶°ì¶±ì¶²ì¶³ì¶´ì¶µì¶¶ì¶·ì¶¸ì¶¹ì¶ºì¶»ì¶¼ì¶½ì¶¾ì¶¿ì·€ì·ì·‚췃췄췅췆췇췈췉췊췋췌ì·ì·Žì·ì·ì·‘췒췓췔췕췖췗췘췙췚췛췜ì·ì·žì·Ÿì· ì·¡ì·¢ì·£ì·¤ì·¥ì·¦ì·§ì·¨ì·©ì·ªì·«ì·¬ì·­ì·®ì·¯ì·°ì·±ì·²ì·³ì·´ì·µì·¶ì··ì·¸ì·¹ì·ºì·»ì·¼ì·½ì·¾ì·¿ì¸€ì¸ì¸‚츃츄츅츆츇츈츉츊츋츌ì¸ì¸Žì¸ì¸ì¸‘츒츓츔츕츖츗츘츙츚츛츜ì¸ì¸žì¸Ÿì¸ ì¸¡","츢츣츤츥츦츧츨츩츪츫츬츭츮츯츰츱츲츳츴층츶츷츸츹츺츻츼츽츾츿칀ì¹ì¹‚칃칄칅칆칇칈칉칊칋칌ì¹ì¹Žì¹ì¹ì¹‘칒칓칔칕칖칗치칙칚칛친ì¹ì¹žì¹Ÿì¹ ì¹¡ì¹¢ì¹£ì¹¤ì¹¥ì¹¦ì¹§ì¹¨ì¹©ì¹ªì¹«ì¹¬ì¹­ì¹®ì¹¯ì¹°ì¹±ì¹²ì¹³ì¹´ì¹µì¹¶ì¹·ì¹¸ì¹¹ì¹ºì¹»ì¹¼ì¹½ì¹¾ì¹¿ìº€ìºìº‚캃캄캅캆캇캈캉캊캋캌ìºìºŽìºìºìº‘캒캓캔캕캖캗캘캙캚캛캜ìºìºžìºŸìº ìº¡ìº¢ìº£ìº¤ìº¥ìº¦ìº§ìº¨ìº©ìºªìº«ìº¬ìº­ìº®ìº¯ìº°ìº±ìº²ìº³ìº´ìºµìº¶ìº·ìº¸ìº¹ìººìº»ìº¼ìº½ìº¾ìº¿ì»€ì»ì»‚컃컄컅컆컇컈컉컊컋컌ì»ì»Žì»ì»ì»‘컒컓컔컕컖컗컘컙컚컛컜ì»ì»žì»Ÿì» ì»¡ì»¢ì»£ì»¤ì»¥ì»¦ì»§ì»¨ì»©ì»ªì»«ì»¬ì»­ì»®ì»¯ì»°ì»±ì»²ì»³ì»´ì»µì»¶ì»·ì»¸ì»¹ì»ºì»»ì»¼ì»½ì»¾ì»¿ì¼€ì¼ì¼‚켃켄켅켆켇켈켉켊켋켌ì¼ì¼Žì¼ì¼ì¼‘켒켓켔켕켖켗켘켙켚켛켜ì¼ì¼žì¼Ÿì¼ ì¼¡","켢켣켤켥켦켧켨켩켪켫켬켭켮켯켰켱켲켳켴켵켶켷켸켹켺켻켼켽켾켿콀ì½ì½‚콃콄콅콆콇콈콉콊콋콌ì½ì½Žì½ì½ì½‘콒콓코콕콖콗콘콙콚콛콜ì½ì½žì½Ÿì½ ì½¡ì½¢ì½£ì½¤ì½¥ì½¦ì½§ì½¨ì½©ì½ªì½«ì½¬ì½­ì½®ì½¯ì½°ì½±ì½²ì½³ì½´ì½µì½¶ì½·ì½¸ì½¹ì½ºì½»ì½¼ì½½ì½¾ì½¿ì¾€ì¾ì¾‚쾃쾄쾅쾆쾇쾈쾉쾊쾋쾌ì¾ì¾Žì¾ì¾ì¾‘쾒쾓쾔쾕쾖쾗쾘쾙쾚쾛쾜ì¾ì¾žì¾Ÿì¾ ì¾¡ì¾¢ì¾£ì¾¤ì¾¥ì¾¦ì¾§ì¾¨ì¾©ì¾ªì¾«ì¾¬ì¾­ì¾®ì¾¯ì¾°ì¾±ì¾²ì¾³ì¾´ì¾µì¾¶ì¾·ì¾¸ì¾¹ì¾ºì¾»ì¾¼ì¾½ì¾¾ì¾¿ì¿€ì¿ì¿‚쿃쿄쿅쿆쿇쿈쿉쿊쿋쿌ì¿ì¿Žì¿ì¿ì¿‘쿒쿓쿔쿕쿖쿗쿘쿙쿚쿛쿜ì¿ì¿žì¿Ÿì¿ ì¿¡ì¿¢ì¿£ì¿¤ì¿¥ì¿¦ì¿§ì¿¨ì¿©ì¿ªì¿«ì¿¬ì¿­ì¿®ì¿¯ì¿°ì¿±ì¿²ì¿³ì¿´ì¿µì¿¶ì¿·ì¿¸ì¿¹ì¿ºì¿»ì¿¼ì¿½ì¿¾ì¿¿í€€í€í€‚퀃퀄퀅퀆퀇퀈퀉퀊퀋퀌í€í€Ží€í€í€‘퀒퀓퀔퀕퀖퀗퀘퀙퀚퀛퀜í€í€ží€Ÿí€ í€¡","퀢퀣퀤퀥퀦퀧퀨퀩퀪퀫퀬퀭퀮퀯퀰퀱퀲퀳퀴퀵퀶퀷퀸퀹퀺퀻퀼퀽퀾퀿í€íí‚íƒí„í…í†í‡íˆí‰íŠí‹íŒííŽííí‘í’í“í”í•í–í—í˜í™íší›íœíížíŸí í¡í¢í£í¤í¥í¦í§í¨í©íªí«í¬í­í®í¯í°í±í²í³í´íµí¶í·í¸í¹íºí»í¼í½í¾í¿í‚€í‚킂킃킄킅킆킇킈킉킊킋킌í‚í‚Ží‚í‚킑킒킓킔킕킖킗킘킙킚킛킜í‚킞킟킠킡킢킣키킥킦킧킨킩킪킫킬킭킮킯킰킱킲킳킴킵킶킷킸킹킺킻킼킽킾킿타íƒíƒ‚탃탄탅탆탇탈탉탊탋탌íƒíƒŽíƒíƒíƒ‘탒탓탔탕탖탗탘탙탚탛태íƒíƒžíƒŸíƒ íƒ¡íƒ¢íƒ£íƒ¤íƒ¥íƒ¦íƒ§íƒ¨íƒ©íƒªíƒ«íƒ¬íƒ­íƒ®íƒ¯íƒ°íƒ±íƒ²íƒ³íƒ´íƒµíƒ¶íƒ·íƒ¸íƒ¹íƒºíƒ»íƒ¼íƒ½íƒ¾íƒ¿í„€í„턂턃턄턅턆턇턈턉턊턋턌í„í„Ží„í„턑턒턓턔턕턖턗턘턙턚턛턜í„í„ží„Ÿí„ í„¡","턢턣턤턥턦턧턨턩턪턫턬턭턮턯터턱턲턳턴턵턶턷털턹턺턻턼턽턾턿텀í…텂텃텄텅텆텇텈텉텊텋테í…í…Ží…í…텑텒텓텔텕텖텗텘텙텚텛템í…텞텟텠텡텢텣텤텥텦텧텨텩텪텫텬텭텮텯텰텱텲텳텴텵텶텷텸텹텺텻텼텽텾텿톀í†í†‚톃톄톅톆톇톈톉톊톋톌í†í†Ží†í†í†‘톒톓톔톕톖톗톘톙톚톛톜í†í†ží†Ÿí† í†¡í†¢í†£í†¤í†¥í†¦í†§í†¨í†©í†ªí†«í†¬í†­í†®í†¯í†°í†±í†²í†³í†´í†µí†¶í†·í†¸í†¹í†ºí†»í†¼í†½í†¾í†¿í‡€í‡í‡‚퇃퇄퇅퇆퇇퇈퇉퇊퇋퇌í‡í‡Ží‡í‡í‡‘퇒퇓퇔퇕퇖퇗퇘퇙퇚퇛퇜í‡í‡ží‡Ÿí‡ í‡¡í‡¢í‡£í‡¤í‡¥í‡¦í‡§í‡¨í‡©í‡ªí‡«í‡¬í‡­í‡®í‡¯í‡°í‡±í‡²í‡³í‡´í‡µí‡¶í‡·í‡¸í‡¹í‡ºí‡»í‡¼í‡½í‡¾í‡¿íˆ€íˆíˆ‚툃툄툅툆툇툈툉툊툋툌íˆíˆŽíˆíˆíˆ‘툒툓툔툕툖툗툘툙툚툛툜íˆíˆžíˆŸíˆ íˆ¡","툢툣툤툥툦툧툨툩툪툫투툭툮툯툰툱툲툳툴툵툶툷툸툹툺툻툼툽툾툿퉀í‰í‰‚퉃퉄퉅퉆퉇퉈퉉퉊퉋퉌í‰í‰Ží‰í‰í‰‘퉒퉓퉔퉕퉖퉗퉘퉙퉚퉛퉜í‰í‰ží‰Ÿí‰ í‰¡í‰¢í‰£í‰¤í‰¥í‰¦í‰§í‰¨í‰©í‰ªí‰«í‰¬í‰­í‰®í‰¯í‰°í‰±í‰²í‰³í‰´í‰µí‰¶í‰·í‰¸í‰¹í‰ºí‰»í‰¼í‰½í‰¾í‰¿íŠ€íŠíŠ‚튃튄튅튆튇튈튉튊튋튌íŠíŠŽíŠíŠíŠ‘튒튓튔튕튖튗튘튙튚튛튜íŠíŠžíŠŸíŠ íŠ¡íŠ¢íŠ£íŠ¤íŠ¥íŠ¦íŠ§íŠ¨íŠ©íŠªíŠ«íŠ¬íŠ­íŠ®íŠ¯íŠ°íŠ±íŠ²íŠ³íŠ´íŠµíŠ¶íŠ·íŠ¸íŠ¹íŠºíŠ»íŠ¼íŠ½íŠ¾íŠ¿í‹€í‹í‹‚틃틄틅틆틇틈틉틊틋틌í‹í‹Ží‹í‹í‹‘틒틓틔틕틖틗틘틙틚틛틜í‹í‹ží‹Ÿí‹ í‹¡í‹¢í‹£í‹¤í‹¥í‹¦í‹§í‹¨í‹©í‹ªí‹«í‹¬í‹­í‹®í‹¯í‹°í‹±í‹²í‹³í‹´í‹µí‹¶í‹·í‹¸í‹¹í‹ºí‹»í‹¼í‹½í‹¾í‹¿íŒ€íŒíŒ‚팃팄팅팆팇팈팉팊팋파íŒíŒŽíŒíŒíŒ‘팒팓팔팕팖팗팘팙팚팛팜íŒíŒžíŒŸíŒ íŒ¡","팢팣팤팥팦팧패팩팪팫팬팭팮팯팰팱팲팳팴팵팶팷팸팹팺팻팼팽팾팿í€íí‚íƒí„í…í†í‡íˆí‰íŠí‹íŒííŽííí‘í’í“í”í•í–í—í˜í™íší›íœíížíŸí í¡í¢í£í¤í¥í¦í§í¨í©íªí«í¬í­í®í¯í°í±í²í³í´íµí¶í·í¸í¹íºí»í¼í½í¾í¿íŽ€íŽíŽ‚펃펄펅펆펇펈펉펊펋펌íŽíŽŽíŽíŽíŽ‘펒펓펔펕펖펗페펙펚펛펜íŽíŽžíŽŸíŽ íŽ¡íŽ¢íŽ£íŽ¤íŽ¥íŽ¦íŽ§íŽ¨íŽ©íŽªíŽ«íŽ¬íŽ­íŽ®íŽ¯íŽ°íŽ±íŽ²íŽ³íŽ´íŽµíŽ¶íŽ·íŽ¸íŽ¹íŽºíŽ»íŽ¼íŽ½íŽ¾íŽ¿í€íí‚íƒí„í…í†í‡íˆí‰íŠí‹íŒííŽííí‘í’í“í”í•í–í—í˜í™íší›íœíížíŸí í¡í¢í£í¤í¥í¦í§í¨í©íªí«í¬í­í®í¯í°í±í²í³í´íµí¶í·í¸í¹íºí»í¼í½í¾í¿í€íí‚íƒí„í…í†í‡íˆí‰íŠí‹íŒííŽííí‘í’í“í”í•í–í—í˜í™íší›íœíížíŸí í¡","í¢í£í¤í¥í¦í§í¨í©íªí«í¬í­í®í¯í°í±í²í³í´íµí¶í·í¸í¹íºí»í¼í½í¾í¿í‘€í‘푂푃푄푅푆푇푈푉푊푋푌í‘í‘Ží‘í‘푑푒푓푔푕푖푗푘푙푚푛표í‘푞푟푠푡푢푣푤푥푦푧푨푩푪푫푬푭푮푯푰푱푲푳푴푵푶푷푸푹푺푻푼푽푾푿풀í’풂풃풄풅풆풇품풉풊풋풌í’í’Ží’í’풑풒풓풔풕풖풗풘풙풚풛풜í’풞풟풠풡풢풣풤풥풦풧풨풩풪풫풬풭풮풯풰풱풲풳풴풵풶풷풸풹풺풻풼풽풾풿퓀í“퓂퓃퓄퓅퓆퓇퓈퓉퓊퓋퓌í“í“Ží“í“퓑퓒퓓퓔퓕퓖퓗퓘퓙퓚퓛퓜í“퓞퓟퓠퓡퓢퓣퓤퓥퓦퓧퓨퓩퓪퓫퓬퓭퓮퓯퓰퓱퓲퓳퓴퓵퓶퓷퓸퓹퓺퓻퓼퓽퓾퓿픀í”픂픃프픅픆픇픈픉픊픋플í”픎í”í”픑픒픓픔픕픖픗픘픙픚픛픜í”픞픟픠픡","픢픣픤픥픦픧픨픩픪픫픬픭픮픯픰픱픲픳픴픵픶픷픸픹픺픻피픽픾픿핀í•í•‚핃필핅핆핇핈핉핊핋핌í•í•Ží•í•í•‘핒핓핔핕핖핗하학핚핛한í•í•ží•Ÿí• í•¡í•¢í•£í•¤í•¥í•¦í•§í•¨í•©í•ªí•«í•¬í•­í•®í•¯í•°í•±í•²í•³í•´í•µí•¶í•·í•¸í•¹í•ºí•»í•¼í•½í•¾í•¿í–€í–햂햃햄햅햆햇했행햊햋햌í–í–Ží–í–햑햒햓햔햕햖햗햘햙햚햛햜í–햞햟햠햡햢햣햤향햦햧햨햩햪햫햬햭햮햯햰햱햲햳햴햵햶햷햸햹햺햻햼햽햾햿헀í—헂헃헄헅헆헇허헉헊헋헌í—í—Ží—í—헑헒헓헔헕헖헗험헙헚헛헜í—헞헟헠헡헢헣헤헥헦헧헨헩헪헫헬헭헮헯헰헱헲헳헴헵헶헷헸헹헺헻헼헽헾헿혀í˜í˜‚혃현혅혆혇혈혉혊혋혌í˜í˜Ží˜í˜í˜‘혒혓혔형혖혗혘혙혚혛혜í˜í˜ží˜Ÿí˜ í˜¡","혢혣혤혥혦혧혨혩혪혫혬혭혮혯혰혱혲혳혴혵혶혷호혹혺혻혼혽혾혿홀í™í™‚홃홄홅홆홇홈홉홊홋홌í™í™Ží™í™í™‘홒홓화확홖홗환홙홚홛활í™í™ží™Ÿí™ í™¡í™¢í™£í™¤í™¥í™¦í™§í™¨í™©í™ªí™«í™¬í™­í™®í™¯í™°í™±í™²í™³í™´í™µí™¶í™·í™¸í™¹í™ºí™»í™¼í™½í™¾í™¿íš€íšíš‚횃횄횅횆횇횈횉횊횋회íšíšŽíšíšíš‘횒횓횔횕횖횗횘횙횚횛횜íšíšžíšŸíš íš¡íš¢íš£íš¤íš¥íš¦íš§íš¨íš©íšªíš«íš¬íš­íš®íš¯íš°íš±íš²íš³íš´íšµíš¶íš·íš¸íš¹íšºíš»íš¼íš½íš¾íš¿í›€í›í›‚훃후훅훆훇훈훉훊훋훌í›í›Ží›í›í›‘훒훓훔훕훖훗훘훙훚훛훜í›í›ží›Ÿí› í›¡í›¢í›£í›¤í›¥í›¦í›§í›¨í›©í›ªí›«í›¬í›­í›®í›¯í›°í›±í›²í›³í›´í›µí›¶í›·í›¸í›¹í›ºí›»í›¼í›½í›¾í›¿íœ€íœíœ‚휃휄휅휆휇휈휉휊휋휌íœíœŽíœíœíœ‘휒휓휔휕휖휗휘휙휚휛휜íœíœžíœŸíœ íœ¡","휢휣휤휥휦휧휨휩휪휫휬휭휮휯휰휱휲휳휴휵휶휷휸휹휺휻휼휽휾휿í€íí‚íƒí„í…í†í‡íˆí‰íŠí‹íŒííŽííí‘í’í“í”í•í–í—í˜í™íší›íœíížíŸí í¡í¢í£í¤í¥í¦í§í¨í©íªí«í¬í­í®í¯í°í±í²í³í´íµí¶í·í¸í¹íºí»í¼í½í¾í¿íž€ížíž‚힃힄힅힆힇히힉힊힋힌ížížŽížížíž‘힒힓힔힕힖힗힘힙힚힛힜ížížžížŸíž íž¡íž¢íž£íž¤íž¥íž¦íž§íž¨íž©ížªíž«íž¬íž­íž®íž¯íž°íž±íž²íž³íž´ížµíž¶íž·íž¸íž¹ížºíž»íž¼íž½íž¾íž¿íŸ€íŸíŸ‚ퟃퟄퟅퟆ퟇퟈퟉퟊ퟋퟌíŸíŸŽíŸíŸíŸ‘ퟒퟓퟔퟕퟖퟗퟘퟙퟚퟛퟜíŸíŸžíŸŸíŸ íŸ¡íŸ¢íŸ£íŸ¤íŸ¥íŸ¦íŸ§íŸ¨íŸ©íŸªíŸ«íŸ¬íŸ­íŸ®íŸ¯íŸ°íŸ±íŸ²íŸ³íŸ´íŸµíŸ¶íŸ·íŸ¸íŸ¹íŸºíŸ»íŸ¼íŸ½íŸ¾íŸ¿í €í í ‚í ƒí „í …í †í ‡í ˆí ‰í Ší ‹í Œí í Ží í í ‘í ’í “í ”í •í –í —í ˜í ™í ší ›í œí í ží Ÿí  í ¡","í ¢í £í ¤í ¥í ¦í §í ¨í ©í ªí «í ¬í ­í ®í ¯í °í ±í ²í ³í ´í µí ¶í ·í ¸í ¹í ºí »í ¼í ½í ¾í ¿í¡€í¡í¡‚í¡ƒí¡„í¡…í¡†í¡‡í¡ˆí¡‰í¡Ší¡‹í¡Œí¡í¡Ží¡í¡í¡‘í¡’í¡“í¡”í¡•í¡–í¡—í¡˜í¡™í¡ší¡›í¡œí¡í¡ží¡Ÿí¡ í¡¡í¡¢í¡£í¡¤í¡¥í¡¦í¡§í¡¨í¡©í¡ªí¡«í¡¬í¡­í¡®í¡¯í¡°í¡±í¡²í¡³í¡´í¡µí¡¶í¡·í¡¸í¡¹í¡ºí¡»í¡¼í¡½í¡¾í¡¿í¢€í¢í¢‚í¢ƒí¢„í¢…í¢†í¢‡í¢ˆí¢‰í¢Ší¢‹í¢Œí¢í¢Ží¢í¢í¢‘í¢’í¢“í¢”í¢•í¢–í¢—í¢˜í¢™í¢ší¢›í¢œí¢í¢ží¢Ÿí¢ í¢¡í¢¢í¢£í¢¤í¢¥í¢¦í¢§í¢¨í¢©í¢ªí¢«í¢¬í¢­í¢®í¢¯í¢°í¢±í¢²í¢³í¢´í¢µí¢¶í¢·í¢¸í¢¹í¢ºí¢»í¢¼í¢½í¢¾í¢¿í£€í£í£‚í£ƒí£„í£…í£†í£‡í£ˆí£‰í£Ší£‹í£Œí£í£Ží£í£í£‘í£’í£“í£”í£•í£–í£—í£˜í£™í£ší£›í£œí£í£ží£Ÿí£ í£¡í£¢í££í£¤í£¥í£¦í£§í£¨í£©í£ªí£«í£¬í£­í£®í£¯í£°í£±í£²í£³í£´í£µí£¶í£·í£¸í£¹í£ºí£»í£¼í£½í£¾í£¿í¤€í¤í¤‚í¤ƒí¤„í¤…í¤†í¤‡í¤ˆí¤‰í¤Ší¤‹í¤Œí¤í¤Ží¤í¤í¤‘í¤’í¤“í¤”í¤•í¤–í¤—í¤˜í¤™í¤ší¤›í¤œí¤í¤ží¤Ÿí¤ í¤¡","í¤¢í¤£í¤¤í¤¥í¤¦í¤§í¤¨í¤©í¤ªí¤«í¤¬í¤­í¤®í¤¯í¤°í¤±í¤²í¤³í¤´í¤µí¤¶í¤·í¤¸í¤¹í¤ºí¤»í¤¼í¤½í¤¾í¤¿í¥€í¥í¥‚í¥ƒí¥„í¥…í¥†í¥‡í¥ˆí¥‰í¥Ší¥‹í¥Œí¥í¥Ží¥í¥í¥‘í¥’í¥“í¥”í¥•í¥–í¥—í¥˜í¥™í¥ší¥›í¥œí¥í¥ží¥Ÿí¥ í¥¡í¥¢í¥£í¥¤í¥¥í¥¦í¥§í¥¨í¥©í¥ªí¥«í¥¬í¥­í¥®í¥¯í¥°í¥±í¥²í¥³í¥´í¥µí¥¶í¥·í¥¸í¥¹í¥ºí¥»í¥¼í¥½í¥¾í¥¿í¦€í¦í¦‚í¦ƒí¦„í¦…í¦†í¦‡í¦ˆí¦‰í¦Ší¦‹í¦Œí¦í¦Ží¦í¦í¦‘í¦’í¦“í¦”í¦•í¦–í¦—í¦˜í¦™í¦ší¦›í¦œí¦í¦ží¦Ÿí¦ í¦¡í¦¢í¦£í¦¤í¦¥í¦¦í¦§í¦¨í¦©í¦ªí¦«í¦¬í¦­í¦®í¦¯í¦°í¦±í¦²í¦³í¦´í¦µí¦¶í¦·í¦¸í¦¹í¦ºí¦»í¦¼í¦½í¦¾í¦¿í§€í§í§‚í§ƒí§„í§…í§†í§‡í§ˆí§‰í§Ší§‹í§Œí§í§Ží§í§í§‘í§’í§“í§”í§•í§–í§—í§˜í§™í§ší§›í§œí§í§ží§Ÿí§ í§¡í§¢í§£í§¤í§¥í§¦í§§í§¨í§©í§ªí§«í§¬í§­í§®í§¯í§°í§±í§²í§³í§´í§µí§¶í§·í§¸í§¹í§ºí§»í§¼í§½í§¾í§¿í¨€í¨í¨‚í¨ƒí¨„í¨…í¨†í¨‡í¨ˆí¨‰í¨Ší¨‹í¨Œí¨í¨Ží¨í¨í¨‘í¨’í¨“í¨”í¨•í¨–í¨—í¨˜í¨™í¨ší¨›í¨œí¨í¨ží¨Ÿí¨ í¨¡","í¨¢í¨£í¨¤í¨¥í¨¦í¨§í¨¨í¨©í¨ªí¨«í¨¬í¨­í¨®í¨¯í¨°í¨±í¨²í¨³í¨´í¨µí¨¶í¨·í¨¸í¨¹í¨ºí¨»í¨¼í¨½í¨¾í¨¿í©€í©í©‚í©ƒí©„í©…í©†í©‡í©ˆí©‰í©Ší©‹í©Œí©í©Ží©í©í©‘í©’í©“í©”í©•í©–í©—í©˜í©™í©ší©›í©œí©í©ží©Ÿí© í©¡í©¢í©£í©¤í©¥í©¦í©§í©¨í©©í©ªí©«í©¬í©­í©®í©¯í©°í©±í©²í©³í©´í©µí©¶í©·í©¸í©¹í©ºí©»í©¼í©½í©¾í©¿íª€íªíª‚íªƒíª„íª…íª†íª‡íªˆíª‰íªŠíª‹íªŒíªíªŽíªíªíª‘íª’íª“íª”íª•íª–íª—íª˜íª™íªšíª›íªœíªíªžíªŸíª íª¡íª¢íª£íª¤íª¥íª¦íª§íª¨íª©íªªíª«íª¬íª­íª®íª¯íª°íª±íª²íª³íª´íªµíª¶íª·íª¸íª¹íªºíª»íª¼íª½íª¾íª¿í«€í«í«‚í«ƒí«„í«…í«†í«‡í«ˆí«‰í«Ší«‹í«Œí«í«Ží«í«í«‘í«’í«“í«”í«•í«–í«—í«˜í«™í«ší«›í«œí«í«ží«Ÿí« í«¡í«¢í«£í«¤í«¥í«¦í«§í«¨í«©í«ªí««í«¬í«­í«®í«¯í«°í«±í«²í«³í«´í«µí«¶í«·í«¸í«¹í«ºí«»í«¼í«½í«¾í«¿í¬€í¬í¬‚í¬ƒí¬„í¬…í¬†í¬‡í¬ˆí¬‰í¬Ší¬‹í¬Œí¬í¬Ží¬í¬í¬‘í¬’í¬“í¬”í¬•í¬–í¬—í¬˜í¬™í¬ší¬›í¬œí¬í¬ží¬Ÿí¬ í¬¡","í¬¢í¬£í¬¤í¬¥í¬¦í¬§í¬¨í¬©í¬ªí¬«í¬¬í¬­í¬®í¬¯í¬°í¬±í¬²í¬³í¬´í¬µí¬¶í¬·í¬¸í¬¹í¬ºí¬»í¬¼í¬½í¬¾í¬¿í­€í­í­‚í­ƒí­„í­…í­†í­‡í­ˆí­‰í­Ší­‹í­Œí­í­Ží­í­í­‘í­’í­“í­”í­•í­–í­—í­˜í­™í­ší­›í­œí­í­ží­Ÿí­ í­¡í­¢í­£í­¤í­¥í­¦í­§í­¨í­©í­ªí­«í­¬í­­í­®í­¯í­°í­±í­²í­³í­´í­µí­¶í­·í­¸í­¹í­ºí­»í­¼í­½í­¾í­¿í®€í®í®‚í®ƒí®„í®…í®†í®‡í®ˆí®‰í®Ší®‹í®Œí®í®Ží®í®í®‘í®’í®“í®”í®•í®–í®—í®˜í®™í®ší®›í®œí®í®ží®Ÿí® í®¡í®¢í®£í®¤í®¥í®¦í®§í®¨í®©í®ªí®«í®¬í®­í®®í®¯í®°í®±í®²í®³í®´í®µí®¶í®·í®¸í®¹í®ºí®»í®¼í®½í®¾í®¿í¯€í¯í¯‚í¯ƒí¯„í¯…í¯†í¯‡í¯ˆí¯‰í¯Ší¯‹í¯Œí¯í¯Ží¯í¯í¯‘í¯’í¯“í¯”í¯•í¯–í¯—í¯˜í¯™í¯ší¯›í¯œí¯í¯ží¯Ÿí¯ í¯¡í¯¢í¯£í¯¤í¯¥í¯¦í¯§í¯¨í¯©í¯ªí¯«í¯¬í¯­í¯®í¯¯í¯°í¯±í¯²í¯³í¯´í¯µí¯¶í¯·í¯¸í¯¹í¯ºí¯»í¯¼í¯½í¯¾í¯¿í°€í°í°‚í°ƒí°„í°…í°†í°‡í°ˆí°‰í°Ší°‹í°Œí°í°Ží°í°í°‘í°’í°“í°”í°•í°–í°—í°˜í°™í°ší°›í°œí°í°ží°Ÿí° í°¡","í°¢í°£í°¤í°¥í°¦í°§í°¨í°©í°ªí°«í°¬í°­í°®í°¯í°°í°±í°²í°³í°´í°µí°¶í°·í°¸í°¹í°ºí°»í°¼í°½í°¾í°¿í±€í±í±‚í±ƒí±„í±…í±†í±‡í±ˆí±‰í±Ší±‹í±Œí±í±Ží±í±í±‘í±’í±“í±”í±•í±–í±—í±˜í±™í±ší±›í±œí±í±ží±Ÿí± í±¡í±¢í±£í±¤í±¥í±¦í±§í±¨í±©í±ªí±«í±¬í±­í±®í±¯í±°í±±í±²í±³í±´í±µí±¶í±·í±¸í±¹í±ºí±»í±¼í±½í±¾í±¿í²€í²í²‚í²ƒí²„í²…í²†í²‡í²ˆí²‰í²Ší²‹í²Œí²í²Ží²í²í²‘í²’í²“í²”í²•í²–í²—í²˜í²™í²ší²›í²œí²í²ží²Ÿí² í²¡í²¢í²£í²¤í²¥í²¦í²§í²¨í²©í²ªí²«í²¬í²­í²®í²¯í²°í²±í²²í²³í²´í²µí²¶í²·í²¸í²¹í²ºí²»í²¼í²½í²¾í²¿í³€í³í³‚í³ƒí³„í³…í³†í³‡í³ˆí³‰í³Ší³‹í³Œí³í³Ží³í³í³‘í³’í³“í³”í³•í³–í³—í³˜í³™í³ší³›í³œí³í³ží³Ÿí³ í³¡í³¢í³£í³¤í³¥í³¦í³§í³¨í³©í³ªí³«í³¬í³­í³®í³¯í³°í³±í³²í³³í³´í³µí³¶í³·í³¸í³¹í³ºí³»í³¼í³½í³¾í³¿í´€í´í´‚í´ƒí´„í´…í´†í´‡í´ˆí´‰í´Ší´‹í´Œí´í´Ží´í´í´‘í´’í´“í´”í´•í´–í´—í´˜í´™í´ší´›í´œí´í´ží´Ÿí´ í´¡","í´¢í´£í´¤í´¥í´¦í´§í´¨í´©í´ªí´«í´¬í´­í´®í´¯í´°í´±í´²í´³í´´í´µí´¶í´·í´¸í´¹í´ºí´»í´¼í´½í´¾í´¿íµ€íµíµ‚íµƒíµ„íµ…íµ†íµ‡íµˆíµ‰íµŠíµ‹íµŒíµíµŽíµíµíµ‘íµ’íµ“íµ”íµ•íµ–íµ—íµ˜íµ™íµšíµ›íµœíµíµžíµŸíµ íµ¡íµ¢íµ£íµ¤íµ¥íµ¦íµ§íµ¨íµ©íµªíµ«íµ¬íµ­íµ®íµ¯íµ°íµ±íµ²íµ³íµ´íµµíµ¶íµ·íµ¸íµ¹íµºíµ»íµ¼íµ½íµ¾íµ¿í¶€í¶í¶‚í¶ƒí¶„í¶…í¶†í¶‡í¶ˆí¶‰í¶Ší¶‹í¶Œí¶í¶Ží¶í¶í¶‘í¶’í¶“í¶”í¶•í¶–í¶—í¶˜í¶™í¶ší¶›í¶œí¶í¶ží¶Ÿí¶ í¶¡í¶¢í¶£í¶¤í¶¥í¶¦í¶§í¶¨í¶©í¶ªí¶«í¶¬í¶­í¶®í¶¯í¶°í¶±í¶²í¶³í¶´í¶µí¶¶í¶·í¶¸í¶¹í¶ºí¶»í¶¼í¶½í¶¾í¶¿í·€í·í·‚í·ƒí·„í·…í·†í·‡í·ˆí·‰í·Ší·‹í·Œí·í·Ží·í·í·‘í·’í·“í·”í·•í·–í·—í·˜í·™í·ší·›í·œí·í·ží·Ÿí· í·¡í·¢í·£í·¤í·¥í·¦í·§í·¨í·©í·ªí·«í·¬í·­í·®í·¯í·°í·±í·²í·³í·´í·µí·¶í··í·¸í·¹í·ºí·»í·¼í·½í·¾í·¿í¸€í¸í¸‚í¸ƒí¸„í¸…í¸†í¸‡í¸ˆí¸‰í¸Ší¸‹í¸Œí¸í¸Ží¸í¸í¸‘í¸’í¸“í¸”í¸•í¸–í¸—í¸˜í¸™í¸ší¸›í¸œí¸í¸ží¸Ÿí¸ í¸¡","í¸¢í¸£í¸¤í¸¥í¸¦í¸§í¸¨í¸©í¸ªí¸«í¸¬í¸­í¸®í¸¯í¸°í¸±í¸²í¸³í¸´í¸µí¸¶í¸·í¸¸í¸¹í¸ºí¸»í¸¼í¸½í¸¾í¸¿í¹€í¹í¹‚í¹ƒí¹„í¹…í¹†í¹‡í¹ˆí¹‰í¹Ší¹‹í¹Œí¹í¹Ží¹í¹í¹‘í¹’í¹“í¹”í¹•í¹–í¹—í¹˜í¹™í¹ší¹›í¹œí¹í¹ží¹Ÿí¹ í¹¡í¹¢í¹£í¹¤í¹¥í¹¦í¹§í¹¨í¹©í¹ªí¹«í¹¬í¹­í¹®í¹¯í¹°í¹±í¹²í¹³í¹´í¹µí¹¶í¹·í¹¸í¹¹í¹ºí¹»í¹¼í¹½í¹¾í¹¿íº€íºíº‚íºƒíº„íº…íº†íº‡íºˆíº‰íºŠíº‹íºŒíºíºŽíºíºíº‘íº’íº“íº”íº•íº–íº—íº˜íº™íºšíº›íºœíºíºžíºŸíº íº¡íº¢íº£íº¤íº¥íº¦íº§íº¨íº©íºªíº«íº¬íº­íº®íº¯íº°íº±íº²íº³íº´íºµíº¶íº·íº¸íº¹íººíº»íº¼íº½íº¾íº¿í»€í»í»‚í»ƒí»„í»…í»†í»‡í»ˆí»‰í»Ší»‹í»Œí»í»Ží»í»í»‘í»’í»“í»”í»•í»–í»—í»˜í»™í»ší»›í»œí»í»ží»Ÿí» í»¡í»¢í»£í»¤í»¥í»¦í»§í»¨í»©í»ªí»«í»¬í»­í»®í»¯í»°í»±í»²í»³í»´í»µí»¶í»·í»¸í»¹í»ºí»»í»¼í»½í»¾í»¿í¼€í¼í¼‚í¼ƒí¼„í¼…í¼†í¼‡í¼ˆí¼‰í¼Ší¼‹í¼Œí¼í¼Ží¼í¼í¼‘í¼’í¼“í¼”í¼•í¼–í¼—í¼˜í¼™í¼ší¼›í¼œí¼í¼ží¼Ÿí¼ í¼¡","í¼¢í¼£í¼¤í¼¥í¼¦í¼§í¼¨í¼©í¼ªí¼«í¼¬í¼­í¼®í¼¯í¼°í¼±í¼²í¼³í¼´í¼µí¼¶í¼·í¼¸í¼¹í¼ºí¼»í¼¼í¼½í¼¾í¼¿í½€í½í½‚í½ƒí½„í½…í½†í½‡í½ˆí½‰í½Ší½‹í½Œí½í½Ží½í½í½‘í½’í½“í½”í½•í½–í½—í½˜í½™í½ší½›í½œí½í½ží½Ÿí½ í½¡í½¢í½£í½¤í½¥í½¦í½§í½¨í½©í½ªí½«í½¬í½­í½®í½¯í½°í½±í½²í½³í½´í½µí½¶í½·í½¸í½¹í½ºí½»í½¼í½½í½¾í½¿í¾€í¾í¾‚í¾ƒí¾„í¾…í¾†í¾‡í¾ˆí¾‰í¾Ší¾‹í¾Œí¾í¾Ží¾í¾í¾‘í¾’í¾“í¾”í¾•í¾–í¾—í¾˜í¾™í¾ší¾›í¾œí¾í¾ží¾Ÿí¾ í¾¡í¾¢í¾£í¾¤í¾¥í¾¦í¾§í¾¨í¾©í¾ªí¾«í¾¬í¾­í¾®í¾¯í¾°í¾±í¾²í¾³í¾´í¾µí¾¶í¾·í¾¸í¾¹í¾ºí¾»í¾¼í¾½í¾¾í¾¿í¿€í¿í¿‚í¿ƒí¿„í¿…í¿†í¿‡í¿ˆí¿‰í¿Ší¿‹í¿Œí¿í¿Ží¿í¿í¿‘í¿’í¿“í¿”í¿•í¿–í¿—í¿˜í¿™í¿ší¿›í¿œí¿í¿ží¿Ÿí¿ í¿¡í¿¢í¿£í¿¤í¿¥í¿¦í¿§í¿¨í¿©í¿ªí¿«í¿¬í¿­í¿®í¿¯í¿°í¿±í¿²í¿³í¿´í¿µí¿¶í¿·í¿¸í¿¹í¿ºí¿»í¿¼í¿½í¿¾í¿¿î€€î€î€‚î€î€Žî€î€î€‘î€î€žî€Ÿî€ î€¡","î€îî‚îƒî„î…î†î‡îˆî‰îŠî‹îŒîîŽîîî‘î’î“î”î•î–î—î˜î™îšî›îœîîžîŸî î¡î¢î£î¤î¥î¦î§î¨î©îªî«î¬î­î®î¯î°î±î²î³î´îµî¶î·î¸î¹îºî»î¼î½î¾î¿î‚€î‚î‚î‚Žî‚î‚î‚îƒîƒ‚îƒîƒŽîƒîƒîƒ‘îƒîƒžîƒŸîƒ îƒ¡îƒ¢îƒ£îƒ¤îƒ¥îƒ¦îƒ§îƒ¨îƒ©îƒªîƒ«îƒ¬îƒ­îƒ®îƒ¯îƒ°îƒ±îƒ²îƒ³îƒ´îƒµîƒ¶îƒ·îƒ¸îƒ¹îƒºîƒ»îƒ¼îƒ½îƒ¾îƒ¿î„€î„î„î„Žî„î„î„î„žî„Ÿî„ î„¡","î…î…î…Žî…î…î…î†î†‚î†î†Žî†î†î†‘î†î†žî†Ÿî† î†¡î†¢î†£î†¤î†¥î†¦î†§î†¨î†©î†ªî†«î†¬î†­î†®î†¯î†°î†±î†²î†³î†´î†µî†¶î†·î†¸î†¹î†ºî†»î†¼î†½î†¾î†¿î‡€î‡î‡‚î‡î‡Žî‡î‡î‡‘î‡î‡žî‡Ÿî‡ î‡¡î‡¢î‡£î‡¤î‡¥î‡¦î‡§î‡¨î‡©î‡ªî‡«î‡¬î‡­î‡®î‡¯î‡°î‡±î‡²î‡³î‡´î‡µî‡¶î‡·î‡¸î‡¹î‡ºî‡»î‡¼î‡½î‡¾î‡¿îˆ€îˆîˆ‚îˆîˆŽîˆîˆîˆ‘îˆîˆžîˆŸîˆ îˆ¡","î‰î‰‚î‰î‰Žî‰î‰î‰‘î‰î‰žî‰Ÿî‰ î‰¡î‰¢î‰£î‰¤î‰¥î‰¦î‰§î‰¨î‰©î‰ªî‰«î‰¬î‰­î‰®î‰¯î‰°î‰±î‰²î‰³î‰´î‰µî‰¶î‰·î‰¸î‰¹î‰ºî‰»î‰¼î‰½î‰¾î‰¿îŠ€îŠîŠ‚îŠîŠŽîŠîŠîŠ‘îŠîŠžîŠŸîŠ îŠ¡îŠ¢îŠ£îŠ¤îŠ¥îŠ¦îŠ§îŠ¨îŠ©îŠªîŠ«îŠ¬îŠ­îŠ®îŠ¯îŠ°îŠ±îŠ²îŠ³îŠ´îŠµîŠ¶îŠ·îŠ¸îŠ¹îŠºîŠ»îŠ¼îŠ½îŠ¾îŠ¿î‹€î‹î‹‚î‹î‹Žî‹î‹î‹‘î‹î‹žî‹Ÿî‹ î‹¡î‹¢î‹£î‹¤î‹¥î‹¦î‹§î‹¨î‹©î‹ªî‹«î‹¬î‹­î‹®î‹¯î‹°î‹±î‹²î‹³î‹´î‹µî‹¶î‹·î‹¸î‹¹î‹ºî‹»î‹¼î‹½î‹¾î‹¿îŒ€îŒîŒ‚îŒîŒŽîŒîŒîŒ‘îŒîŒžîŒŸîŒ îŒ¡","î€îî‚îƒî„î…î†î‡îˆî‰îŠî‹îŒîîŽîîî‘î’î“î”î•î–î—î˜î™îšî›îœîîžîŸî î¡î¢î£î¤î¥î¦î§î¨î©îªî«î¬î­î®î¯î°î±î²î³î´îµî¶î·î¸î¹îºî»î¼î½î¾î¿îŽ€îŽîŽ‚îŽîŽŽîŽîŽîŽ‘îŽîŽžîŽŸîŽ îŽ¡îŽ¢îŽ£îŽ¤îŽ¥îŽ¦îŽ§îŽ¨îŽ©îŽªîŽ«îŽ¬îŽ­îŽ®îŽ¯îŽ°îŽ±îŽ²îŽ³îŽ´îŽµîŽ¶îŽ·îŽ¸îŽ¹îŽºîŽ»îŽ¼îŽ½îŽ¾îŽ¿î€îî‚îƒî„î…î†î‡îˆî‰îŠî‹îŒîîŽîîî‘î’î“î”î•î–î—î˜î™îšî›îœîîžîŸî î¡î¢î£î¤î¥î¦î§î¨î©îªî«î¬î­î®î¯î°î±î²î³î´îµî¶î·î¸î¹îºî»î¼î½î¾î¿î€îî‚îƒî„î…î†î‡îˆî‰îŠî‹îŒîîŽîîî‘î’î“î”î•î–î—î˜î™îšî›îœîîžîŸî î¡","î¢î£î¤î¥î¦î§î¨î©îªî«î¬î­î®î¯î°î±î²î³î´îµî¶î·î¸î¹îºî»î¼î½î¾î¿î‘€î‘î‘î‘Žî‘î‘î‘î’î’î’Žî’î’î’î“î“î“Žî“î“î“î”î”î”î”î”","î•î•‚î•î•Žî•î•î•‘î•î•žî•Ÿî• î•¡î•¢î•£î•¤î•¥î•¦î•§î•¨î•©î•ªî•«î•¬î•­î•®î•¯î•°î•±î•²î•³î•´î•µî•¶î•·î•¸î•¹î•ºî•»î•¼î•½î•¾î•¿î–€î–î–î–Žî–î–î–î—î—î—Žî—î—î—î˜î˜‚î˜î˜Žî˜î˜î˜‘î˜î˜žî˜Ÿî˜ î˜¡","î™î™‚î™î™Žî™î™î™‘î™î™žî™Ÿî™ î™¡î™¢î™£î™¤î™¥î™¦î™§î™¨î™©î™ªî™«î™¬î™­î™®î™¯î™°î™±î™²î™³î™´î™µî™¶î™·î™¸î™¹î™ºî™»î™¼î™½î™¾î™¿îš€îšîš‚îšîšŽîšîšîš‘îšîšžîšŸîš îš¡îš¢îš£îš¤îš¥îš¦îš§îš¨îš©îšªîš«îš¬îš­îš®îš¯îš°îš±îš²îš³îš´îšµîš¶îš·îš¸îš¹îšºîš»îš¼îš½îš¾îš¿î›€î›î›‚î›î›Žî›î›î›‘î›î›žî›Ÿî› î›¡î›¢î›£î›¤î›¥î›¦î›§î›¨î›©î›ªî›«î›¬î›­î›®î›¯î›°î›±î›²î›³î›´î›µî›¶î›·î›¸î›¹î›ºî›»î›¼î›½î›¾î›¿îœ€îœîœ‚îœîœŽîœîœîœ‘îœîœžîœŸîœ îœ¡","î€îî‚îƒî„î…î†î‡îˆî‰îŠî‹îŒîîŽîîî‘î’î“î”î•î–î—î˜î™îšî›îœîîžîŸî î¡î¢î£î¤î¥î¦î§î¨î©îªî«î¬î­î®î¯î°î±î²î³î´îµî¶î·î¸î¹îºî»î¼î½î¾î¿îž€îžîž‚îžîžŽîžîžîž‘îžîžžîžŸîž îž¡îž¢îž£îž¤îž¥îž¦îž§îž¨îž©îžªîž«îž¬îž­îž®îž¯îž°îž±îž²îž³îž´îžµîž¶îž·îž¸îž¹îžºîž»îž¼îž½îž¾îž¿îŸ€îŸîŸ‚îŸîŸŽîŸîŸîŸ‘îŸîŸžîŸŸîŸ îŸ¡îŸ¢îŸ£îŸ¤îŸ¥îŸ¦îŸ§îŸ¨îŸ©îŸªîŸ«îŸ¬îŸ­îŸ®îŸ¯îŸ°îŸ±îŸ²îŸ³îŸ´îŸµîŸ¶îŸ·îŸ¸îŸ¹îŸºîŸ»îŸ¼îŸ½îŸ¾îŸ¿î €î î ‚î î Žî î î ‘î î žî Ÿî  î ¡","î¡î¡‚î¡î¡Žî¡î¡î¡‘î¡î¡žî¡Ÿî¡ î¡¡î¡¢î¡£î¡¤î¡¥î¡¦î¡§î¡¨î¡©î¡ªî¡«î¡¬î¡­î¡®î¡¯î¡°î¡±î¡²î¡³î¡´î¡µî¡¶î¡·î¡¸î¡¹î¡ºî¡»î¡¼î¡½î¡¾î¡¿î¢€î¢î¢‚î¢î¢Žî¢î¢î¢‘î¢î¢žî¢Ÿî¢ î¢¡î¢¢î¢£î¢¤î¢¥î¢¦î¢§î¢¨î¢©î¢ªî¢«î¢¬î¢­î¢®î¢¯î¢°î¢±î¢²î¢³î¢´î¢µî¢¶î¢·î¢¸î¢¹î¢ºî¢»î¢¼î¢½î¢¾î¢¿î£€î£î£‚î£î£Žî£î£î£‘î£î£žî£Ÿî£ î£¡î£¢î££î£¤î£¥î£¦î£§î£¨î£©î£ªî£«î£¬î£­î£®î£¯î£°î£±î£²î£³î£´î£µî£¶î£·î£¸î£¹î£ºî£»î£¼î£½î£¾î£¿î¤€î¤î¤‚î¤î¤Žî¤î¤î¤‘î¤î¤žî¤Ÿî¤ î¤¡","î¥î¥‚î¥î¥Žî¥î¥î¥‘î¥î¥žî¥Ÿî¥ î¥¡î¥¢î¥£î¥¤î¥¥î¥¦î¥§î¥¨î¥©î¥ªî¥«î¥¬î¥­î¥®î¥¯î¥°î¥±î¥²î¥³î¥´î¥µî¥¶î¥·î¥¸î¥¹î¥ºî¥»î¥¼î¥½î¥¾î¥¿î¦€î¦î¦‚î¦î¦Žî¦î¦î¦‘î¦î¦žî¦Ÿî¦ î¦¡î¦¢î¦£î¦¤î¦¥î¦¦î¦§î¦¨î¦©î¦ªî¦«î¦¬î¦­î¦®î¦¯î¦°î¦±î¦²î¦³î¦´î¦µî¦¶î¦·î¦¸î¦¹î¦ºî¦»î¦¼î¦½î¦¾î¦¿î§€î§î§‚î§î§Žî§î§î§‘î§î§žî§Ÿî§ î§¡î§¢î§£î§¤î§¥î§¦î§§î§¨î§©î§ªî§«î§¬î§­î§®î§¯î§°î§±î§²î§³î§´î§µî§¶î§·î§¸î§¹î§ºî§»î§¼î§½î§¾î§¿î¨€î¨î¨‚î¨î¨Žî¨î¨î¨‘î¨î¨žî¨Ÿî¨ î¨¡","î©î©‚î©î©Žî©î©î©‘î©î©žî©Ÿî© î©¡î©¢î©£î©¤î©¥î©¦î©§î©¨î©©î©ªî©«î©¬î©­î©®î©¯î©°î©±î©²î©³î©´î©µî©¶î©·î©¸î©¹î©ºî©»î©¼î©½î©¾î©¿îª€îªîª‚îªîªŽîªîªîª‘îªîªžîªŸîª îª¡îª¢îª£îª¤îª¥îª¦îª§îª¨îª©îªªîª«îª¬îª­îª®îª¯îª°îª±îª²îª³îª´îªµîª¶îª·îª¸îª¹îªºîª»îª¼îª½îª¾îª¿î«€î«î«‚î«î«Žî«î«î«‘î«î«žî«Ÿî« î«¡î«¢î«£î«¤î«¥î«¦î«§î«¨î«©î«ªî««î«¬î«­î«®î«¯î«°î«±î«²î«³î«´î«µî«¶î«·î«¸î«¹î«ºî«»î«¼î«½î«¾î«¿î¬€î¬î¬‚î¬î¬Žî¬î¬î¬‘î¬î¬žî¬Ÿî¬ î¬¡","î­î­‚î­î­Žî­î­î­‘î­î­žî­Ÿî­ î­¡î­¢î­£î­¤î­¥î­¦î­§î­¨î­©î­ªî­«î­¬î­­î­®î­¯î­°î­±î­²î­³î­´î­µî­¶î­·î­¸î­¹î­ºî­»î­¼î­½î­¾î­¿î®€î®î®‚î®î®Žî®î®î®‘î®î®žî®Ÿî® î®¡î®¢î®£î®¤î®¥î®¦î®§î®¨î®©î®ªî®«î®¬î®­î®®î®¯î®°î®±î®²î®³î®´î®µî®¶î®·î®¸î®¹î®ºî®»î®¼î®½î®¾î®¿î¯€î¯î¯‚î¯î¯Žî¯î¯î¯‘î¯î¯žî¯Ÿî¯ î¯¡î¯¢î¯£î¯¤î¯¥î¯¦î¯§î¯¨î¯©î¯ªî¯«î¯¬î¯­î¯®î¯¯î¯°î¯±î¯²î¯³î¯´î¯µî¯¶î¯·î¯¸î¯¹î¯ºî¯»î¯¼î¯½î¯¾î¯¿î°€î°î°‚î°î°Žî°î°î°‘î°î°žî°Ÿî° î°¡","î±î±‚î±î±Žî±î±î±‘î±î±žî±Ÿî± î±¡î±¢î±£î±¤î±¥î±¦î±§î±¨î±©î±ªî±«î±¬î±­î±®î±¯î±°î±±î±²î±³î±´î±µî±¶î±·î±¸î±¹î±ºî±»î±¼î±½î±¾î±¿î²€î²î²‚î²î²Žî²î²î²‘î²î²žî²Ÿî² î²¡î²¢î²£î²¤î²¥î²¦î²§î²¨î²©î²ªî²«î²¬î²­î²®î²¯î²°î²±î²²î²³î²´î²µî²¶î²·î²¸î²¹î²ºî²»î²¼î²½î²¾î²¿î³€î³î³‚î³î³Žî³î³î³‘î³î³žî³Ÿî³ î³¡î³¢î³£î³¤î³¥î³¦î³§î³¨î³©î³ªî³«î³¬î³­î³®î³¯î³°î³±î³²î³³î³´î³µî³¶î³·î³¸î³¹î³ºî³»î³¼î³½î³¾î³¿î´€î´î´‚î´î´Žî´î´î´‘î´î´žî´Ÿî´ î´¡","îµîµ‚îµîµŽîµîµîµ‘îµîµžîµŸîµ îµ¡îµ¢îµ£îµ¤îµ¥îµ¦îµ§îµ¨îµ©îµªîµ«îµ¬îµ­îµ®îµ¯îµ°îµ±îµ²îµ³îµ´îµµîµ¶îµ·îµ¸îµ¹îµºîµ»îµ¼îµ½îµ¾îµ¿î¶€î¶î¶‚î¶î¶Žî¶î¶î¶‘î¶î¶žî¶Ÿî¶ î¶¡î¶¢î¶£î¶¤î¶¥î¶¦î¶§î¶¨î¶©î¶ªî¶«î¶¬î¶­î¶®î¶¯î¶°î¶±î¶²î¶³î¶´î¶µî¶¶î¶·î¶¸î¶¹î¶ºî¶»î¶¼î¶½î¶¾î¶¿î·€î·î·‚î·î·Žî·î·î·‘î·î·žî·Ÿî· î·¡î·¢î·£î·¤î·¥î·¦î·§î·¨î·©î·ªî·«î·¬î·­î·®î·¯î·°î·±î·²î·³î·´î·µî·¶î··î·¸î·¹î·ºî·»î·¼î·½î·¾î·¿î¸€î¸î¸‚î¸î¸Žî¸î¸î¸‘î¸î¸žî¸Ÿî¸ î¸¡","î¹î¹‚î¹î¹Žî¹î¹î¹‘î¹î¹žî¹Ÿî¹ î¹¡î¹¢î¹£î¹¤î¹¥î¹¦î¹§î¹¨î¹©î¹ªî¹«î¹¬î¹­î¹®î¹¯î¹°î¹±î¹²î¹³î¹´î¹µî¹¶î¹·î¹¸î¹¹î¹ºî¹»î¹¼î¹½î¹¾î¹¿îº€îºîº‚îºîºŽîºîºîº‘îºîºžîºŸîº îº¡îº¢îº£îº¤îº¥îº¦îº§îº¨îº©îºªîº«îº¬îº­îº®îº¯îº°îº±îº²îº³îº´îºµîº¶îº·îº¸îº¹îººîº»îº¼îº½îº¾îº¿î»€î»î»‚î»î»Žî»î»î»‘î»î»žî»Ÿî» î»¡î»¢î»£î»¤î»¥î»¦î»§î»¨î»©î»ªî»«î»¬î»­î»®î»¯î»°î»±î»²î»³î»´î»µî»¶î»·î»¸î»¹î»ºî»»î»¼î»½î»¾î»¿î¼€î¼î¼‚î¼î¼Žî¼î¼î¼‘î¼î¼žî¼Ÿî¼ î¼¡","î½î½‚î½î½Žî½î½î½‘î½î½žî½Ÿî½ î½¡î½¢î½£î½¤î½¥î½¦î½§î½¨î½©î½ªî½«î½¬î½­î½®î½¯î½°î½±î½²î½³î½´î½µî½¶î½·î½¸î½¹î½ºî½»î½¼î½½î½¾î½¿î¾€î¾î¾‚î¾î¾Žî¾î¾î¾‘î¾î¾žî¾Ÿî¾ î¾¡î¾¢î¾£î¾¤î¾¥î¾¦î¾§î¾¨î¾©î¾ªî¾«î¾¬î¾­î¾®î¾¯î¾°î¾±î¾²î¾³î¾´î¾µî¾¶î¾·î¾¸î¾¹î¾ºî¾»î¾¼î¾½î¾¾î¾¿î¿€î¿î¿‚î¿î¿Žî¿î¿î¿‘î¿î¿žî¿Ÿî¿ î¿¡î¿¢î¿£î¿¤î¿¥î¿¦î¿§î¿¨î¿©î¿ªî¿«î¿¬î¿­î¿®î¿¯î¿°î¿±î¿²î¿³î¿´î¿µî¿¶î¿·î¿¸î¿¹î¿ºî¿»î¿¼î¿½î¿¾î¿¿ï€€ï€ï€‚ï€ï€Žï€ï€ï€‘ï€ï€žï€Ÿï€ ï€¡","ï€ïï‚ïƒï„ï…ï†ï‡ïˆï‰ïŠï‹ïŒïïŽïïï‘ï’ï“ï”ï•ï–ï—ï˜ï™ïšï›ïœïïžïŸï ï¡ï¢ï£ï¤ï¥ï¦ï§ï¨ï©ïªï«ï¬ï­ï®ï¯ï°ï±ï²ï³ï´ïµï¶ï·ï¸ï¹ïºï»ï¼ï½ï¾ï¿ï‚€ï‚ï‚ï‚Žï‚ï‚ï‚ïƒïƒ‚ïƒïƒŽïƒïƒïƒ‘ïƒïƒžïƒŸïƒ ïƒ¡ïƒ¢ïƒ£ïƒ¤ïƒ¥ïƒ¦ïƒ§ïƒ¨ïƒ©ïƒªïƒ«ïƒ¬ïƒ­ïƒ®ïƒ¯ïƒ°ïƒ±ïƒ²ïƒ³ïƒ´ïƒµïƒ¶ïƒ·ïƒ¸ïƒ¹ïƒºïƒ»ïƒ¼ïƒ½ïƒ¾ïƒ¿ï„€ï„ï„ï„Žï„ï„ï„ï„žï„Ÿï„ ï„¡","ï…ï…ï…Žï…ï…ï…ï†ï†‚ï†ï†Žï†ï†ï†‘ï†ï†žï†Ÿï† ï†¡ï†¢ï†£ï†¤ï†¥ï†¦ï†§ï†¨ï†©ï†ªï†«ï†¬ï†­ï†®ï†¯ï†°ï†±ï†²ï†³ï†´ï†µï†¶ï†·ï†¸ï†¹ï†ºï†»ï†¼ï†½ï†¾ï†¿ï‡€ï‡ï‡‚ï‡ï‡Žï‡ï‡ï‡‘ï‡ï‡žï‡Ÿï‡ ï‡¡ï‡¢ï‡£ï‡¤ï‡¥ï‡¦ï‡§ï‡¨ï‡©ï‡ªï‡«ï‡¬ï‡­ï‡®ï‡¯ï‡°ï‡±ï‡²ï‡³ï‡´ï‡µï‡¶ï‡·ï‡¸ï‡¹ï‡ºï‡»ï‡¼ï‡½ï‡¾ï‡¿ïˆ€ïˆïˆ‚ïˆïˆŽïˆïˆïˆ‘ïˆïˆžïˆŸïˆ ïˆ¡","ï‰ï‰‚ï‰ï‰Žï‰ï‰ï‰‘ï‰ï‰žï‰Ÿï‰ ï‰¡ï‰¢ï‰£ï‰¤ï‰¥ï‰¦ï‰§ï‰¨ï‰©ï‰ªï‰«ï‰¬ï‰­ï‰®ï‰¯ï‰°ï‰±ï‰²ï‰³ï‰´ï‰µï‰¶ï‰·ï‰¸ï‰¹ï‰ºï‰»ï‰¼ï‰½ï‰¾ï‰¿ïŠ€ïŠïŠ‚ïŠïŠŽïŠïŠïŠ‘ïŠïŠžïŠŸïŠ ïŠ¡ïŠ¢ïŠ£ïŠ¤ïŠ¥ïŠ¦ïŠ§ïŠ¨ïŠ©ïŠªïŠ«ïŠ¬ïŠ­ïŠ®ïŠ¯ïŠ°ïŠ±ïŠ²ïŠ³ïŠ´ïŠµïŠ¶ïŠ·ïŠ¸ïŠ¹ïŠºïŠ»ïŠ¼ïŠ½ïŠ¾ïŠ¿ï‹€ï‹ï‹‚ï‹ï‹Žï‹ï‹ï‹‘ï‹ï‹žï‹Ÿï‹ ï‹¡ï‹¢ï‹£ï‹¤ï‹¥ï‹¦ï‹§ï‹¨ï‹©ï‹ªï‹«ï‹¬ï‹­ï‹®ï‹¯ï‹°ï‹±ï‹²ï‹³ï‹´ï‹µï‹¶ï‹·ï‹¸ï‹¹ï‹ºï‹»ï‹¼ï‹½ï‹¾ï‹¿ïŒ€ïŒïŒ‚ïŒïŒŽïŒïŒïŒ‘ïŒïŒžïŒŸïŒ ïŒ¡","ï€ïï‚ïƒï„ï…ï†ï‡ïˆï‰ïŠï‹ïŒïïŽïïï‘ï’ï“ï”ï•ï–ï—ï˜ï™ïšï›ïœïïžïŸï ï¡ï¢ï£ï¤ï¥ï¦ï§ï¨ï©ïªï«ï¬ï­ï®ï¯ï°ï±ï²ï³ï´ïµï¶ï·ï¸ï¹ïºï»ï¼ï½ï¾ï¿ïŽ€ïŽïŽ‚ïŽïŽŽïŽïŽïŽ‘ïŽïŽžïŽŸïŽ ïŽ¡ïŽ¢ïŽ£ïŽ¤ïŽ¥ïŽ¦ïŽ§ïŽ¨ïŽ©ïŽªïŽ«ïŽ¬ïŽ­ïŽ®ïŽ¯ïŽ°ïŽ±ïŽ²ïŽ³ïŽ´ïŽµïŽ¶ïŽ·ïŽ¸ïŽ¹ïŽºïŽ»ïŽ¼ïŽ½ïŽ¾ïŽ¿ï€ïï‚ïƒï„ï…ï†ï‡ïˆï‰ïŠï‹ïŒïïŽïïï‘ï’ï“ï”ï•ï–ï—ï˜ï™ïšï›ïœïïžïŸï ï¡ï¢ï£ï¤ï¥ï¦ï§ï¨ï©ïªï«ï¬ï­ï®ï¯ï°ï±ï²ï³ï´ïµï¶ï·ï¸ï¹ïºï»ï¼ï½ï¾ï¿ï€ïï‚ïƒï„ï…ï†ï‡ïˆï‰ïŠï‹ïŒïïŽïïï‘ï’ï“ï”ï•ï–ï—ï˜ï™ïšï›ïœïïžïŸï ï¡","ï¢ï£ï¤ï¥ï¦ï§ï¨ï©ïªï«ï¬ï­ï®ï¯ï°ï±ï²ï³ï´ïµï¶ï·ï¸ï¹ïºï»ï¼ï½ï¾ï¿ï‘€ï‘ï‘ï‘Žï‘ï‘ï‘ï’ï’ï’Žï’ï’ï’ï“ï“ï“Žï“ï“ï“ï”ï”ï”ï”ï”","ï•ï•‚ï•ï•Žï•ï•ï•‘ï•ï•žï•Ÿï• ï•¡ï•¢ï•£ï•¤ï•¥ï•¦ï•§ï•¨ï•©ï•ªï•«ï•¬ï•­ï•®ï•¯ï•°ï•±ï•²ï•³ï•´ï•µï•¶ï•·ï•¸ï•¹ï•ºï•»ï•¼ï•½ï•¾ï•¿ï–€ï–ï–ï–Žï–ï–ï–ï—ï—ï—Žï—ï—ï—ï˜ï˜‚ï˜ï˜Žï˜ï˜ï˜‘ï˜ï˜žï˜Ÿï˜ ï˜¡","ï™ï™‚ï™ï™Žï™ï™ï™‘ï™ï™žï™Ÿï™ ï™¡ï™¢ï™£ï™¤ï™¥ï™¦ï™§ï™¨ï™©ï™ªï™«ï™¬ï™­ï™®ï™¯ï™°ï™±ï™²ï™³ï™´ï™µï™¶ï™·ï™¸ï™¹ï™ºï™»ï™¼ï™½ï™¾ï™¿ïš€ïšïš‚ïšïšŽïšïšïš‘ïšïšžïšŸïš ïš¡ïš¢ïš£ïš¤ïš¥ïš¦ïš§ïš¨ïš©ïšªïš«ïš¬ïš­ïš®ïš¯ïš°ïš±ïš²ïš³ïš´ïšµïš¶ïš·ïš¸ïš¹ïšºïš»ïš¼ïš½ïš¾ïš¿ï›€ï›ï›‚ï›ï›Žï›ï›ï›‘ï›ï›žï›Ÿï› ï›¡ï›¢ï›£ï›¤ï›¥ï›¦ï›§ï›¨ï›©ï›ªï›«ï›¬ï›­ï›®ï›¯ï›°ï›±ï›²ï›³ï›´ï›µï›¶ï›·ï›¸ï›¹ï›ºï›»ï›¼ï›½ï›¾ï›¿ïœ€ïœïœ‚ïœïœŽïœïœïœ‘ïœïœžïœŸïœ ïœ¡","ï€ïï‚ïƒï„ï…ï†ï‡ïˆï‰ïŠï‹ïŒïïŽïïï‘ï’ï“ï”ï•ï–ï—ï˜ï™ïšï›ïœïïžïŸï ï¡ï¢ï£ï¤ï¥ï¦ï§ï¨ï©ïªï«ï¬ï­ï®ï¯ï°ï±ï²ï³ï´ïµï¶ï·ï¸ï¹ïºï»ï¼ï½ï¾ï¿ïž€ïžïž‚ïžïžŽïžïžïž‘ïžïžžïžŸïž ïž¡ïž¢ïž£ïž¤ïž¥ïž¦ïž§ïž¨ïž©ïžªïž«ïž¬ïž­ïž®ïž¯ïž°ïž±ïž²ïž³ïž´ïžµïž¶ïž·ïž¸ïž¹ïžºïž»ïž¼ïž½ïž¾ïž¿ïŸ€ïŸïŸ‚ïŸïŸŽïŸïŸïŸ‘ïŸïŸžïŸŸïŸ ïŸ¡ïŸ¢ïŸ£ïŸ¤ïŸ¥ïŸ¦ïŸ§ïŸ¨ïŸ©ïŸªïŸ«ïŸ¬ïŸ­ïŸ®ïŸ¯ïŸ°ïŸ±ïŸ²ïŸ³ïŸ´ïŸµïŸ¶ïŸ·ïŸ¸ïŸ¹ïŸºïŸ»ïŸ¼ïŸ½ïŸ¾ïŸ¿ï €ï ï ‚ï ï Žï ï ï ‘ï ï žï Ÿï  ï ¡","ï¡ï¡‚ï¡ï¡Žï¡ï¡ï¡‘ï¡ï¡žï¡Ÿï¡ ï¡¡ï¡¢ï¡£ï¡¤ï¡¥ï¡¦ï¡§ï¡¨ï¡©ï¡ªï¡«ï¡¬ï¡­ï¡®ï¡¯ï¡°ï¡±ï¡²ï¡³ï¡´ï¡µï¡¶ï¡·ï¡¸ï¡¹ï¡ºï¡»ï¡¼ï¡½ï¡¾ï¡¿ï¢€ï¢ï¢‚ï¢ï¢Žï¢ï¢ï¢‘ï¢ï¢žï¢Ÿï¢ ï¢¡ï¢¢ï¢£ï¢¤ï¢¥ï¢¦ï¢§ï¢¨ï¢©ï¢ªï¢«ï¢¬ï¢­ï¢®ï¢¯ï¢°ï¢±ï¢²ï¢³ï¢´ï¢µï¢¶ï¢·ï¢¸ï¢¹ï¢ºï¢»ï¢¼ï¢½ï¢¾ï¢¿ï£€ï£ï£‚ï£ï£Žï£ï£ï£‘ï£ï£žï£Ÿï£ ï£¡ï£¢ï££ï£¤ï£¥ï£¦ï£§ï£¨ï£©ï£ªï£«ï£¬ï£­ï£®ï£¯ï£°ï£±ï£²ï£³ï£´ï£µï£¶ï£·ï£¸ï£¹ï£ºï£»ï£¼ï£½ï£¾ï£¿ï¤€ï¤ï¤‚賈滑串句龜龜契金喇奈ï¤ï¤Žï¤ï¤ï¤‘裸邏樂洛烙珞落酪駱亂卵ï¤ï¤žï¤Ÿï¤ ï¤¡","濫藍襤拉臘蠟廊朗浪狼郎來冷勞擄櫓爐盧老蘆虜路露魯鷺碌祿綠菉錄鹿ï¥ï¥‚弄籠聾牢磊賂雷壘屢樓ï¥ï¥Žï¥ï¥ï¥‘勒肋凜凌稜綾菱陵讀拏樂ï¥ï¥žï¥Ÿï¥ ï¥¡ï¥¢ï¥£ï¥¤ï¥¥ï¥¦ï¥§ï¥¨ï¥©ï¥ªï¥«ï¥¬ï¥­ï¥®ï¥¯ï¥°ï¥±ï¥²ï¥³ï¥´ï¥µï¥¶ï¥·ï¥¸ï¥¹ï¥ºï¥»ï¥¼ï¥½ï¥¾ï¥¿ï¦€ï¦ï¦‚旅濾礪閭驪麗黎力曆歷ï¦ï¦Žï¦ï¦ï¦‘漣煉璉秊練聯輦蓮連鍊列ï¦ï¦žï¦Ÿï¦ ï¦¡ï¦¢ï¦£ï¦¤ï¦¥ï¦¦ï¦§ï¦¨ï¦©ï¦ªï¦«ï¦¬ï¦­ï¦®ï¦¯ï¦°ï¦±ï¦²ï¦³ï¦´ï¦µï¦¶ï¦·ï¦¸ï¦¹ï¦ºï¦»ï¦¼ï¦½ï¦¾ï¦¿ï§€ï§ï§‚遼龍暈阮劉杻柳流溜琉ï§ï§Žï§ï§ï§‘戮陸倫崙淪輪律慄栗率隆ï§ï§žï§Ÿï§ ï§¡ï§¢ï§£ï§¤ï§¥ï§¦ï§§ï§¨ï§©ï§ªï§«ï§¬ï§­ï§®ï§¯ï§°ï§±ï§²ï§³ï§´ï§µï§¶ï§·ï§¸ï§¹ï§ºï§»ï§¼ï§½ï§¾ï§¿ï¨€ï¨ï¨‚糖宅洞暴輻行降見廓兀ï¨ï¨Žï¨ï¨ï¨‘晴﨓﨔凞猪益礼神祥福靖ï¨ï¨žï¨Ÿï¨ ï¨¡","諸﨣﨤逸都﨧﨨﨩飯飼館鶴郞隷侮僧免勉勤卑喝嘆器塀墨層屮悔慨憎懲ï©ï©‚暑梅海渚漢煮爫琢碑社ï©ï©Žï©ï©ï©‘禍禎穀突節練縉繁署者臭ï©ï©žï©Ÿï© ï©¡ï©¢ï©£ï©¤ï©¥ï©¦ï©§ï©¨ï©©ï©ªï©«ï©¬ï©­ï©®ï©¯ï©°ï©±ï©²ï©³ï©´ï©µï©¶ï©·ï©¸ï©¹ï©ºï©»ï©¼ï©½ï©¾ï©¿ïª€ïªïª‚廙彩徭惘慎愈憎慠懲戴ïªïªŽïªïªïª‘朗望杖歹殺流滛滋漢瀞煮ïªïªžïªŸïª ïª¡ïª¢ïª£ïª¤ïª¥ïª¦ïª§ïª¨ïª©ïªªïª«ïª¬ïª­ïª®ïª¯ïª°ïª±ïª²ïª³ïª´ïªµïª¶ïª·ïª¸ïª¹ïªºïª»ïª¼ïª½ïª¾ïª¿ï«€ï«ï«‚遲醙鉶陼難靖韛響頋頻ï«ï«Žï«ï«ï«‘㮝䀘䀹𥉉𥳐𧻓齃龎﫚﫛﫜ï«ï«žï«Ÿï« ï«¡ï«¢ï«£ï«¤ï«¥ï«¦ï«§ï«¨ï«©ï«ªï««ï«¬ï«­ï«®ï«¯ï«°ï«±ï«²ï«³ï«´ï«µï«¶ï«·ï«¸ï«¹ï«ºï«»ï«¼ï«½ï«¾ï«¿ï¬€ï¬ï¬‚ffifflſtst﬇﬈﬉﬊﬋﬌ï¬ï¬Žï¬ï¬ï¬‘﬒ﬓﬔﬕﬖﬗ﬘﬙﬚﬛﬜ï¬ï¬žï¬Ÿï¬ ï¬¡","ﬢﬣﬤﬥﬦﬧﬨ﬩שׁשׂשּׁשּׂאַאָאּבּגּדּהּוּזּ﬷טּיּךּכּלּ﬽מּ﬿נּï­ï­‚ףּפּ﭅צּקּרּשּתּוֹבֿï­ï­Žï­ï­ï­‘ﭒﭓﭔﭕﭖﭗﭘﭙﭚﭛﭜï­ï­žï­Ÿï­ ï­¡ï­¢ï­£ï­¤ï­¥ï­¦ï­§ï­¨ï­©ï­ªï­«ï­¬ï­­ï­®ï­¯ï­°ï­±ï­²ï­³ï­´ï­µï­¶ï­·ï­¸ï­¹ï­ºï­»ï­¼ï­½ï­¾ï­¿ï®€ï®ï®‚ﮃﮄﮅﮆﮇﮈﮉﮊﮋﮌï®ï®Žï®ï®ï®‘ﮒﮓﮔﮕﮖﮗﮘﮙﮚﮛﮜï®ï®žï®Ÿï® ï®¡ï®¢ï®£ï®¤ï®¥ï®¦ï®§ï®¨ï®©ï®ªï®«ï®¬ï®­ï®®ï®¯ï®°ï®±ï®²ï®³ï®´ï®µï®¶ï®·ï®¸ï®¹ï®ºï®»ï®¼ï®½ï®¾ï®¿ï¯€ï¯ï¯‚﯃﯄﯅﯆﯇﯈﯉﯊﯋﯌ï¯ï¯Žï¯ï¯ï¯‘﯒ﯓﯔﯕﯖﯗﯘﯙﯚﯛﯜï¯ï¯žï¯Ÿï¯ ï¯¡ï¯¢ï¯£ï¯¤ï¯¥ï¯¦ï¯§ï¯¨ï¯©ï¯ªï¯«ï¯¬ï¯­ï¯®ï¯¯ï¯°ï¯±ï¯²ï¯³ï¯´ï¯µï¯¶ï¯·ï¯¸ï¯¹ï¯ºï¯»ï¯¼ï¯½ï¯¾ï¯¿ï°€ï°ï°‚ﰃﰄﰅﰆﰇﰈﰉﰊﰋﰌï°ï°Žï°ï°ï°‘ﰒﰓﰔﰕﰖﰗﰘﰙﰚﰛﰜï°ï°žï°Ÿï° ï°¡","ﰢﰣﰤﰥﰦﰧﰨﰩﰪﰫﰬﰭﰮﰯﰰﰱﰲﰳﰴﰵﰶﰷﰸﰹﰺﰻﰼﰽﰾﰿﱀï±ï±‚ﱃﱄﱅﱆﱇﱈﱉﱊﱋﱌï±ï±Žï±ï±ï±‘ﱒﱓﱔﱕﱖﱗﱘﱙﱚﱛﱜï±ï±žï±Ÿï± ï±¡ï±¢ï±£ï±¤ï±¥ï±¦ï±§ï±¨ï±©ï±ªï±«ï±¬ï±­ï±®ï±¯ï±°ï±±ï±²ï±³ï±´ï±µï±¶ï±·ï±¸ï±¹ï±ºï±»ï±¼ï±½ï±¾ï±¿ï²€ï²ï²‚ﲃﲄﲅﲆﲇﲈﲉﲊﲋﲌï²ï²Žï²ï²ï²‘ﲒﲓﲔﲕﲖﲗﲘﲙﲚﲛﲜï²ï²žï²Ÿï² ï²¡ï²¢ï²£ï²¤ï²¥ï²¦ï²§ï²¨ï²©ï²ªï²«ï²¬ï²­ï²®ï²¯ï²°ï²±ï²²ï²³ï²´ï²µï²¶ï²·ï²¸ï²¹ï²ºï²»ï²¼ï²½ï²¾ï²¿ï³€ï³ï³‚ﳃﳄﳅﳆﳇﳈﳉﳊﳋﳌï³ï³Žï³ï³ï³‘ﳒﳓﳔﳕﳖﳗﳘﳙﳚﳛﳜï³ï³žï³Ÿï³ ï³¡ï³¢ï³£ï³¤ï³¥ï³¦ï³§ï³¨ï³©ï³ªï³«ï³¬ï³­ï³®ï³¯ï³°ï³±ï³²ï³³ï³´ï³µï³¶ï³·ï³¸ï³¹ï³ºï³»ï³¼ï³½ï³¾ï³¿ï´€ï´ï´‚ﴃﴄﴅﴆﴇﴈﴉﴊﴋﴌï´ï´Žï´ï´ï´‘ﴒﴓﴔﴕﴖﴗﴘﴙﴚﴛﴜï´ï´žï´Ÿï´ ï´¡","ﴢﴣﴤﴥﴦﴧﴨﴩﴪﴫﴬﴭﴮﴯﴰﴱﴲﴳﴴﴵﴶﴷﴸﴹﴺﴻﴼﴽ﴾﴿﵀ïµïµ‚﵃﵄﵅﵆﵇﵈﵉﵊﵋﵌ïµïµŽïµïµïµ‘ﵒﵓﵔﵕﵖﵗﵘﵙﵚﵛﵜïµïµžïµŸïµ ïµ¡ïµ¢ïµ£ïµ¤ïµ¥ïµ¦ïµ§ïµ¨ïµ©ïµªïµ«ïµ¬ïµ­ïµ®ïµ¯ïµ°ïµ±ïµ²ïµ³ïµ´ïµµïµ¶ïµ·ïµ¸ïµ¹ïµºïµ»ïµ¼ïµ½ïµ¾ïµ¿ï¶€ï¶ï¶‚ﶃﶄﶅﶆﶇﶈﶉﶊﶋﶌï¶ï¶Žï¶ï¶ï¶‘ﶒﶓﶔﶕﶖﶗﶘﶙﶚﶛﶜï¶ï¶žï¶Ÿï¶ ï¶¡ï¶¢ï¶£ï¶¤ï¶¥ï¶¦ï¶§ï¶¨ï¶©ï¶ªï¶«ï¶¬ï¶­ï¶®ï¶¯ï¶°ï¶±ï¶²ï¶³ï¶´ï¶µï¶¶ï¶·ï¶¸ï¶¹ï¶ºï¶»ï¶¼ï¶½ï¶¾ï¶¿ï·€ï·ï·‚ﷃﷄﷅﷆﷇ﷈﷉﷊﷋﷌ï·ï·Žï·ï·ï·‘﷒﷓﷔﷕﷖﷗﷘﷙﷚﷛﷜ï·ï·žï·Ÿï· ï·¡ï·¢ï·£ï·¤ï·¥ï·¦ï·§ï·¨ï·©ï·ªï·«ï·¬ï·­ï·®ï·¯ï·°ï·±ï·²ï·³ï·´ï·µï·¶ï··ï·¸ï·¹ï·ºï·»ï·¼ï·½ï·¾ï·¿ï¸€ï¸ï¸‚︃︄︅︆︇︈︉︊︋︌ï¸ï¸Žï¸ï¸ï¸‘︒︓︔︕︖︗︘︙︚︛︜ï¸ï¸žï¸Ÿï¸ ï¸¡","︧︨︩︪︫︬︭︢︣︤︥︦︮︯︰︱︲︳︴︵︶︷︸︹︺︻︼︽︾︿﹀ï¹ï¹‚﹃﹄﹅﹆﹇﹈﹉﹊﹋﹌ï¹ï¹Žï¹ï¹ï¹‘﹒﹓﹔﹕﹖﹗﹘﹙﹚﹛﹜ï¹ï¹žï¹Ÿï¹ ï¹¡ï¹¢ï¹£ï¹¤ï¹¥ï¹¦ï¹§ï¹¨ï¹©ï¹ªï¹«ï¹¬ï¹­ï¹®ï¹¯ï¹°ï¹±ï¹²ï¹³ï¹´ï¹µï¹¶ï¹·ï¹¸ï¹¹ï¹ºï¹»ï¹¼ï¹½ï¹¾ï¹¿ïº€ïºïº‚ﺃﺄﺅﺆﺇﺈﺉﺊﺋﺌïºïºŽïºïºïº‘ﺒﺓﺔﺕﺖﺗﺘﺙﺚﺛﺜïºïºžïºŸïº ïº¡ïº¢ïº£ïº¤ïº¥ïº¦ïº§ïº¨ïº©ïºªïº«ïº¬ïº­ïº®ïº¯ïº°ïº±ïº²ïº³ïº´ïºµïº¶ïº·ïº¸ïº¹ïººïº»ïº¼ïº½ïº¾ïº¿ï»€ï»ï»‚ﻃﻄﻅﻆﻇﻈﻉﻊﻋﻌï»ï»Žï»ï»ï»‘ﻒﻓﻔﻕﻖﻗﻘﻙﻚﻛﻜï»ï»žï»Ÿï» ï»¡ï»¢ï»£ï»¤ï»¥ï»¦ï»§ï»¨ï»©ï»ªï»«ï»¬ï»­ï»®ï»¯ï»°ï»±ï»²ï»³ï»´ï»µï»¶ï»·ï»¸ï»¹ï»ºï»»ï»¼ï»½ï»¾ï»¿ï¼€ï¼ï¼‚#$%&'()*+,ï¼ï¼Žï¼ï¼ï¼‘23456789:;<ï¼ï¼žï¼Ÿï¼ ï¼¡","BCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`ï½ï½‚cdefghijklï½ï½Žï½ï½ï½‘rstuvwxyz{|ï½ï½žï½Ÿï½ ï½¡ï½¢ï½£ï½¤ï½¥ï½¦ï½§ï½¨ï½©ï½ªï½«ï½¬ï½­ï½®ï½¯ï½°ï½±ï½²ï½³ï½´ï½µï½¶ï½·ï½¸ï½¹ï½ºï½»ï½¼ï½½ï½¾ï½¿ï¾€ï¾ï¾‚テトナニヌネノハヒフï¾ï¾Žï¾ï¾ï¾‘メモヤユヨラリルレロワï¾ï¾žï¾Ÿï¾ ï¾¡ï¾¢ï¾£ï¾¤ï¾¥ï¾¦ï¾§ï¾¨ï¾©ï¾ªï¾«ï¾¬ï¾­ï¾®ï¾¯ï¾°ï¾±ï¾²ï¾³ï¾´ï¾µï¾¶ï¾·ï¾¸ï¾¹ï¾ºï¾»ï¾¼ï¾½ï¾¾ï¾¿ï¿€ï¿ï¿‚ᅢᅣᅤᅥᅦ￈￉ᅧᅨᅩï¿ï¿Žï¿ï¿ï¿‘ᅭᅮᅯᅰᅱᅲ￘￙ᅳᅴᅵï¿ï¿žï¿Ÿï¿ ï¿¡ï¿¢ï¿£ï¿¤ï¿¥ï¿¦ï¿§ï¿¨ï¿©ï¿ªï¿«ï¿¬ï¿­ï¿®ï¿¯ï¿°ï¿±ï¿²ï¿³ï¿´ï¿µï¿¶ï¿·ï¿¸ï¿¹ï¿ºï¿»ï¿¼ï¿½ï¿¾ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿ï¿¿"],"keys":["0","1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59","60","61","62","63","64","65","66","67","68","69","70","71","72","73","74","75","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100","101","102","103","104","105","106","107","108","109","110","111","112","113","114","115","116","117","118","119","120","121","122","123","124","125","126","127","128","129","130","131","132","133","134","135","136","137","138","139","140","141","142","143","144","145","146","147","148","149","150","151","152","153","154","155","156","157","158","159","160","161","162","163","164","165","166","167","168","169","170","171","172","173","174","175","176","177","178","179","180","181","182","183","184","185","186","187","188","189","190","191","192","193","194","195","196","197","198","199","200","201","202","203","204","205","206","207","208","209","210","211","212","213","214","215","216","217","218","219","220","221","222","223","224","225","226","227","228","229","230","231","232","233","234","235","236","237","238","239","240","241","242","243","244","245","246","247","248","249","250","251","252","253","254","255","256","257","258","259","260","261","262","263","264","265","266","267","268","269","270","271","272","273","274","275","276","277","278","279","280","281","282","283","284","285","286","287","288","289","290","291","292","293","294","295","296","297","298","299","300","301","302","303","304","305","306","307","308","309","310","311","312","313","314","315","316","317","318","319","320","321","322","323","324","325","326","327","328","329","330","331","332","333","334","335","336","337","338","339","340","341","342","343","344","345","346","347","348","349","350","351","352","353","354","355","356","357","358","359","360","361","362","363","364","365","366","367","368","369","370","371","372","373","374","375","376","377","378","379","380","381","382","383","384","385","386","387","388","389","390","391","392","393","394","395","396","397","398","399","400","401","402","403","404","405","406","407","408","409","410","411","412","413","414","415","416","417","418","419","420","421","422","423","424","425","426","427","428","429","430","431","432","433","434","435","436","437","438","439","440","441","442","443","444","445","446","447","448","449","450","451","452","453","454","455","456","457","458","459","460","461","462","463","464","465","466","467","468","469","470","471","472","473","474","475","476","477","478","479","480","481","482","483","484","485","486","487","488","489","490","491","492","493","494","495","496","497","498","499","500","501","502","503","504","505","506","507","508","509","510","511","512","513","514","515","516","517","518","519","520","521","522","523","524","525","526","527","528","529","530","531","532","533","534","535","536","537","538","539","540","541","542","543","544","545","546","547","548","549","550","551","552","553","554","555","556","557","558","559","560","561","562","563","564","565","566","567","568","569","570","571","572","573","574","575","576","577","578","579","580","581","582","583","584","585","586","587","588","589","590","591","592","593","594","595","596","597","598","599","600","601","602","603","604","605","606","607","608","609","610","611","612","613","614","615","616","617","618","619","620","621","622","623","624","625","626","627","628","629","630","631","632","633","634","635","636","637","638","639","640","641","642","643","644","645","646","647","648","649","650","651","652","653","654","655","656","657","658","659","660","661","662","663","664","665","666","667","668","669","670","671","672","673","674","675","676","677","678","679","680","681","682","683","684","685","686","687","688","689","690","691","692","693","694","695","696","697","698","699","700","701","702","703","704","705","706","707","708","709","710","711","712","713","714","715","716","717","718","719","720","721","722","723","724","725","726","727","728","729","730","731","732","733","734","735","736","737","738","739","740","741","742","743","744","745","746","747","748","749","750","751","752","753","754","755","756","757","758","759","760","761","762","763","764","765","766","767","768","769","770","771","772","773","774","775","776","777","778","779","780","781","782","783","784","785","786","787","788","789","790","791","792","793","794","795","796","797","798","799","800","801","802","803","804","805","806","807","808","809","810","811","812","813","814","815","816","817","818","819","820","821","822","823","824","825","826","827","828","829","830","831","832","833","834","835","836","837","838","839","840","841","842","843","844","845","846","847","848","849","850","851","852","853","854","855","856","857","858","859","860","861","862","863","864","865","866","867","868","869","870","871","872","873","874","875","876","877","878","879","880","881","882","883","884","885","886","887","888","889","890","891","892","893","894","895","896","897","898","899","900","901","902","903","904","905","906","907","908","909","910","911","912","913","914","915","916","917","918","919","920","921","922","923","924","925","926","927","928","929","930","931","932","933","934","935","936","937","938","939","940","941","942","943","944","945","946","947","948","949","950","951","952","953","954","955","956","957","958","959","960","961","962","963","964","965","966","967","968","969","970","971","972","973","974","975","976","977","978","979","980","981","982","983","984","985","986","987","988","989","990","991","992","993","994","995","996","997","998","999","1000","1001","1002","1003","1004","1005","1006","1007","1008","1009","1010","1011","1012","1013","1014","1015","1016","1017","1018","1019","1020","1021","1022","1023","1024","1025","1026","1027","1028","1029","1030","1031","1032","1033","1034","1035","1036","1037","1038","1039","1040","1041","1042","1043","1044","1045","1046","1047","1048","1049","1050","1051","1052","1053","1054","1055","1056","1057","1058","1059","1060","1061","1062","1063","1064","1065","1066","1067","1068","1069","1070","1071","1072","1073","1074","1075","1076","1077","1078","1079","1080","1081","1082","1083","1084","1085","1086","1087","1088","1089","1090","1091","1092","1093","1094","1095","1096","1097","1098","1099","1100","1101","1102","1103","1104","1105","1106","1107","1108","1109","1110","1111","1112","1113","1114","1115","1116","1117","1118","1119","1120","1121","1122","1123","1124","1125","1126","1127","1128","1129","1130","1131","1132","1133","1134","1135","1136","1137","1138","1139","1140","1141","1142","1143","1144","1145","1146","1147","1148","1149","1150","1151","1152","1153","1154","1155","1156","1157","1158","1159","1160","1161","1162","1163","1164","1165","1166","1167","1168","1169","1170","1171","1172","1173","1174","1175","1176","1177","1178","1179","1180","1181","1182","1183","1184","1185","1186","1187","1188","1189","1190","1191","1192","1193","1194","1195","1196","1197","1198","1199","1200","1201","1202","1203","1204","1205","1206","1207","1208","1209","1210","1211","1212","1213","1214","1215","1216","1217","1218","1219","1220","1221","1222","1223","1224","1225","1226","1227","1228","1229","1230","1231","1232","1233","1234","1235","1236","1237","1238","1239","1240","1241","1242","1243","1244","1245","1246","1247","1248","1249","1250","1251","1252","1253","1254","1255","1256","1257","1258","1259","1260","1261","1262","1263","1264","1265","1266","1267","1268","1269","1270","1271","1272","1273","1274","1275","1276","1277","1278","1279","1280","1281","1282","1283","1284","1285","1286","1287","1288","1289","1290","1291","1292","1293","1294","1295","1296","1297","1298","1299","1300","1301","1302","1303","1304","1305","1306","1307","1308","1309","1310","1311","1312","1313","1314","1315","1316","1317","1318","1319","1320","1321","1322","1323","1324","1325","1326","1327","1328","1329","1330","1331","1332","1333","1334","1335","1336","1337","1338","1339","1340","1341","1342","1343","1344","1345","1346","1347","1348","1349","1350","1351","1352","1353","1354","1355","1356","1357","1358","1359","1360","1361","1362","1363","1364","1365","1366","1367","1368","1369","1370","1371","1372","1373","1374","1375","1376","1377","1378","1379","1380","1381","1382","1383","1384","1385","1386","1387","1388","1389","1390","1391","1392","1393","1394","1395","1396","1397","1398","1399","1400","1401","1402","1403","1404","1405","1406","1407","1408","1409","1410","1411","1412","1413","1414","1415","1416","1417","1418","1419","1420","1421","1422","1423","1424","1425","1426","1427","1428","1429","1430","1431","1432","1433","1434","1435","1436","1437","1438","1439","1440","1441","1442","1443","1444","1445","1446","1447","1448","1449","1450","1451","1452","1453","1454","1455","1456","1457","1458","1459","1460","1461","1462","1463","1464","1465","1466","1467","1468","1469","1470","1471","1472","1473","1474","1475","1476","1477","1478","1479","1480","1481","1482","1483","1484","1485","1486","1487","1488","1489","1490","1491","1492","1493","1494","1495","1496","1497","1498","1499","1500","1501","1502","1503","1504","1505","1506","1507","1508","1509","1510","1511","1512","1513","1514","1515","1516","1517","1518","1519","1520","1521","1522","1523","1524","1525","1526","1527","1528","1529","1530","1531","1532","1533","1534","1535","1536","1537","1538","1539","1540","1541","1542","1543","1544","1545","1546","1547","1548","1549","1550","1551","1552","1553","1554","1555","1556","1557","1558","1559","1560","1561","1562","1563","1564","1565","1566","1567","1568","1569","1570","1571","1572","1573","1574","1575","1576","1577","1578","1579","1580","1581","1582","1583","1584","1585","1586","1587","1588","1589","1590","1591","1592","1593","1594","1595","1596","1597","1598","1599","1600","1601","1602","1603","1604","1605","1606","1607","1608","1609","1610","1611","1612","1613","1614","1615","1616","1617","1618","1619","1620","1621","1622","1623","1624","1625","1626","1627","1628","1629","1630","1631","1632","1633","1634","1635","1636","1637","1638","1639","1640","1641","1642","1643","1644","1645","1646","1647","1648","1649","1650","1651","1652","1653","1654","1655","1656","1657","1658","1659","1660","1661","1662","1663","1664","1665","1666","1667","1668","1669","1670","1671","1672","1673","1674","1675","1676","1677","1678","1679","1680","1681","1682","1683","1684","1685","1686","1687","1688","1689","1690","1691","1692","1693","1694","1695","1696","1697","1698","1699","1700","1701","1702","1703","1704","1705","1706","1707","1708","1709","1710","1711","1712","1713","1714","1715","1716","1717","1718","1719","1720","1721","1722","1723","1724","1725","1726","1727","1728","1729","1730","1731","1732","1733","1734","1735","1736","1737","1738","1739","1740","1741","1742","1743","1744","1745","1746","1747","1748","1749","1750","1751","1752","1753","1754","1755","1756","1757","1758","1759","1760","1761","1762","1763","1764","1765","1766","1767","1768","1769","1770","1771","1772","1773","1774","1775","1776","1777","1778","1779","1780","1781","1782","1783","1784","1785","1786","1787","1788","1789","1790","1791","1792","1793","1794","1795","1796","1797","1798","1799","1800","1801","1802","1803","1804","1805","1806","1807","1808","1809","1810","1811","1812","1813","1814","1815","1816","1817","1818","1819","1820","1821","1822","1823","1824","1825","1826","1827","1828","1829","1830","1831","1832","1833","1834","1835","1836","1837","1838","1839","1840","1841","1842","1843","1844","1845","1846","1847","1848","1849","1850","1851","1852","1853","1854","1855","1856","1857","1858","1859","1860","1861","1862","1863","1864","1865","1866","1867","1868","1869","1870","1871","1872","1873","1874","1875","1876","1877","1878","1879","1880","1881","1882","1883","1884","1885","1886","1887","1888","1889","1890","1891","1892","1893","1894","1895","1896","1897","1898","1899","1900","1901","1902","1903","1904","1905","1906","1907","1908","1909","1910","1911","1912","1913","1914","1915","1916","1917","1918","1919","1920","1921","1922","1923","1924","1925","1926","1927","1928","1929","1930","1931","1932","1933","1934","1935","1936","1937","1938","1939","1940","1941","1942","1943","1944","1945","1946","1947","1948","1949","1950","1951","1952","1953","1954","1955","1956","1957","1958","1959","1960","1961","1962","1963","1964","1965","1966","1967","1968","1969","1970","1971","1972","1973","1974","1975","1976","1977","1978","1979","1980","1981","1982","1983","1984","1985","1986","1987","1988","1989","1990","1991","1992","1993","1994","1995","1996","1997","1998","1999","2000","2001","2002","2003","2004","2005","2006","2007","2008","2009","2010","2011","2012","2013","2014","2015","2016","2017","2018","2019","2020","2021","2022","2023","2024","2025","2026","2027","2028","2029","2030","2031","2032","2033","2034","2035","2036","2037","2038","2039","2040","2041","2042","2043","2044","2045","2046","2047","2048","2049","2050","2051","2052","2053","2054","2055","2056","2057","2058","2059","2060","2061","2062","2063","2064","2065","2066","2067","2068","2069","2070","2071","2072","2073","2074","2075","2076","2077","2078","2079","2080","2081","2082","2083","2084","2085","2086","2087","2088","2089","2090","2091","2092","2093","2094","2095","2096","2097","2098","2099","2100","2101","2102","2103","2104","2105","2106","2107","2108","2109","2110","2111","2112","2113","2114","2115","2116","2117","2118","2119","2120","2121","2122","2123","2124","2125","2126","2127","2128","2129","2130","2131","2132","2133","2134","2135","2136","2137","2138","2139","2140","2141","2142","2143","2144","2145","2146","2147","2148","2149","2150","2151","2152","2153","2154","2155","2156","2157","2158","2159","2160","2161","2162","2163","2164","2165","2166","2167","2168","2169","2170","2171","2172","2173","2174","2175","2176","2177","2178","2179","2180","2181","2182","2183","2184","2185","2186","2187","2188","2189","2190","2191","2192","2193","2194","2195","2196","2197","2198","2199","2200","2201","2202","2203","2204","2205","2206","2207","2208","2209","2210","2211","2212","2213","2214","2215","2216","2217","2218","2219","2220","2221","2222","2223","2224","2225","2226","2227","2228","2229","2230","2231","2232","2233","2234","2235","2236","2237","2238","2239","2240","2241","2242","2243","2244","2245","2246","2247","2248","2249","2250","2251","2252","2253","2254","2255","2256","2257","2258","2259","2260","2261","2262","2263","2264","2265","2266","2267","2268","2269","2270","2271","2272","2273","2274","2275","2276","2277","2278","2279","2280","2281","2282","2283","2284","2285","2286","2287","2288","2289","2290","2291","2292","2293","2294","2295","2296","2297","2298","2299","2300","2301","2302","2303","2304","2305","2306","2307","2308","2309","2310","2311","2312","2313","2314","2315","2316","2317","2318","2319","2320","2321","2322","2323","2324","2325","2326","2327","2328","2329","2330","2331","2332","2333","2334","2335","2336","2337","2338","2339","2340","2341","2342","2343","2344","2345","2346","2347","2348","2349","2350","2351","2352","2353","2354","2355","2356","2357","2358","2359","2360","2361","2362","2363","2364","2365","2366","2367","2368","2369","2370","2371","2372","2373","2374","2375","2376","2377","2378","2379","2380","2381","2382","2383","2384","2385","2386","2387","2388","2389","2390","2391","2392","2393","2394","2395","2396","2397","2398","2399","2400","2401","2402","2403","2404","2405","2406","2407","2408","2409","2410","2411","2412","2413","2414","2415","2416","2417","2418","2419","2420","2421","2422","2423","2424","2425","2426","2427","2428","2429","2430","2431","2432","2433","2434","2435","2436","2437","2438","2439","2440","2441","2442","2443","2444","2445","2446","2447","2448","2449","2450","2451","2452","2453","2454","2455","2456","2457","2458","2459","2460","2461","2462","2463","2464","2465","2466","2467","2468","2469","2470","2471","2472","2473","2474","2475","2476","2477","2478","2479","2480","2481","2482","2483","2484","2485","2486","2487","2488","2489","2490","2491","2492","2493","2494","2495","2496","2497","2498","2499","2500","2501","2502","2503","2504","2505","2506","2507","2508","2509","2510","2511","2512","2513","2514","2515","2516","2517","2518","2519","2520","2521","2522","2523","2524","2525","2526","2527","2528","2529","2530","2531","2532","2533","2534","2535","2536","2537","2538","2539","2540","2541","2542","2543","2544","2545","2546","2547","2548","2549","2550","2551","2552","2553","2554","2555","2556","2557","2558","2559","2560","2561","2562","2563","2564","2565","2566","2567","2568","2569","2570","2571","2572","2573","2574","2575","2576","2577","2578","2579","2580","2581","2582","2583","2584","2585","2586","2587","2588","2589","2590","2591","2592","2593","2594","2595","2596","2597","2598","2599","2600","2601","2602","2603","2604","2605","2606","2607","2608","2609","2610","2611","2612","2613","2614","2615","2616","2617","2618","2619","2620","2621","2622","2623","2624","2625","2626","2627","2628","2629","2630","2631","2632","2633","2634","2635","2636","2637","2638","2639","2640","2641","2642","2643","2644","2645","2646","2647","2648","2649","2650","2651","2652","2653","2654","2655","2656","2657","2658","2659","2660","2661","2662","2663","2664","2665","2666","2667","2668","2669","2670","2671","2672","2673","2674","2675","2676","2677","2678","2679","2680","2681","2682","2683","2684","2685","2686","2687","2688","2689","2690","2691","2692","2693","2694","2695","2696","2697","2698","2699","2700","2701","2702","2703","2704","2705","2706","2707","2708","2709","2710","2711","2712","2713","2714","2715","2716","2717","2718","2719","2720","2721","2722","2723","2724","2725","2726","2727","2728","2729","2730","2731","2732","2733","2734","2735","2736","2737","2738","2739","2740","2741","2742","2743","2744","2745","2746","2747","2748","2749","2750","2751","2752","2753","2754","2755","2756","2757","2758","2759","2760","2761","2762","2763","2764","2765","2766","2767","2768","2769","2770","2771","2772","2773","2774","2775","2776","2777","2778","2779","2780","2781","2782","2783","2784","2785","2786","2787","2788","2789","2790","2791","2792","2793","2794","2795","2796","2797","2798","2799","2800","2801","2802","2803","2804","2805","2806","2807","2808","2809","2810","2811","2812","2813","2814","2815","2816","2817","2818","2819","2820","2821","2822","2823","2824","2825","2826","2827","2828","2829","2830","2831","2832","2833","2834","2835","2836","2837","2838","2839","2840","2841","2842","2843","2844","2845","2846","2847","2848","2849","2850","2851","2852","2853","2854","2855","2856","2857","2858","2859","2860","2861","2862","2863","2864","2865","2866","2867","2868","2869","2870","2871","2872","2873","2874","2875","2876","2877","2878","2879","2880","2881","2882","2883","2884","2885","2886","2887","2888","2889","2890","2891","2892","2893","2894","2895","2896","2897","2898","2899","2900","2901","2902","2903","2904","2905","2906","2907","2908","2909","2910","2911","2912","2913","2914","2915","2916","2917","2918","2919","2920","2921","2922","2923","2924","2925","2926","2927","2928","2929","2930","2931","2932","2933","2934","2935","2936","2937","2938","2939","2940","2941","2942","2943","2944","2945","2946","2947","2948","2949","2950","2951","2952","2953","2954","2955","2956","2957","2958","2959","2960","2961","2962","2963","2964","2965","2966","2967","2968","2969","2970","2971","2972","2973","2974","2975","2976","2977","2978","2979","2980","2981","2982","2983","2984","2985","2986","2987","2988","2989","2990","2991","2992","2993","2994","2995","2996","2997","2998","2999","3000","3001","3002","3003","3004","3005","3006","3007","3008","3009","3010","3011","3012","3013","3014","3015","3016","3017","3018","3019","3020","3021","3022","3023","3024","3025","3026","3027","3028","3029","3030","3031","3032","3033","3034","3035","3036","3037","3038","3039","3040","3041","3042","3043","3044","3045","3046","3047","3048","3049","3050","3051","3052","3053","3054","3055","3056","3057","3058","3059","3060","3061","3062","3063","3064","3065","3066","3067","3068","3069","3070","3071","3072","3073","3074","3075","3076","3077","3078","3079","3080","3081","3082","3083","3084","3085","3086","3087","3088","3089","3090","3091","3092","3093","3094","3095","3096","3097","3098","3099","3100","3101","3102","3103","3104","3105","3106","3107","3108","3109","3110","3111","3112","3113","3114","3115","3116","3117","3118","3119","3120","3121","3122","3123","3124","3125","3126","3127","3128","3129","3130","3131","3132","3133","3134","3135","3136","3137","3138","3139","3140","3141","3142","3143","3144","3145","3146","3147","3148","3149","3150","3151","3152","3153","3154","3155","3156","3157","3158","3159","3160","3161","3162","3163","3164","3165","3166","3167","3168","3169","3170","3171","3172","3173","3174","3175","3176","3177","3178","3179","3180","3181","3182","3183","3184","3185","3186","3187","3188","3189","3190","3191","3192","3193","3194","3195","3196","3197","3198","3199","3200","3201","3202","3203","3204","3205","3206","3207","3208","3209","3210","3211","3212","3213","3214","3215","3216","3217","3218","3219","3220","3221","3222","3223","3224","3225","3226","3227","3228","3229","3230","3231","3232","3233","3234","3235","3236","3237","3238","3239","3240","3241","3242","3243","3244","3245","3246","3247","3248","3249","3250","3251","3252","3253","3254","3255","3256","3257","3258","3259","3260","3261","3262","3263","3264","3265","3266","3267","3268","3269","3270","3271","3272","3273","3274","3275","3276","3277","3278","3279","3280","3281","3282","3283","3284","3285","3286","3287","3288","3289","3290","3291","3292","3293","3294","3295","3296","3297","3298","3299","3300","3301","3302","3303","3304","3305","3306","3307","3308","3309","3310","3311","3312","3313","3314","3315","3316","3317","3318","3319","3320","3321","3322","3323","3324","3325","3326","3327","3328","3329","3330","3331","3332","3333","3334","3335","3336","3337","3338","3339","3340","3341","3342","3343","3344","3345","3346","3347","3348","3349","3350","3351","3352","3353","3354","3355","3356","3357","3358","3359","3360","3361","3362","3363","3364","3365","3366","3367","3368","3369","3370","3371","3372","3373","3374","3375","3376","3377","3378","3379","3380","3381","3382","3383","3384","3385","3386","3387","3388","3389","3390","3391","3392","3393","3394","3395","3396","3397","3398","3399","3400","3401","3402","3403","3404","3405","3406","3407","3408","3409","3410","3411","3412","3413","3414","3415","3416","3417","3418","3419","3420","3421","3422","3423","3424","3425","3426","3427","3428","3429","3430","3431","3432","3433","3434","3435","3436","3437","3438","3439","3440","3441","3442","3443","3444","3445","3446","3447","3448","3449","3450","3451","3452","3453","3454","3455","3456","3457","3458","3459","3460","3461","3462","3463","3464","3465","3466","3467","3468","3469","3470","3471","3472","3473","3474","3475","3476","3477","3478","3479","3480","3481","3482","3483","3484","3485","3486","3487","3488","3489","3490","3491","3492","3493","3494","3495","3496","3497","3498","3499","3500","3501","3502","3503","3504","3505","3506","3507","3508","3509","3510","3511","3512","3513","3514","3515","3516","3517","3518","3519","3520","3521","3522","3523","3524","3525","3526","3527","3528","3529","3530","3531","3532","3533","3534","3535","3536","3537","3538","3539","3540","3541","3542","3543","3544","3545","3546","3547","3548","3549","3550","3551","3552","3553","3554","3555","3556","3557","3558","3559","3560","3561","3562","3563","3564","3565","3566","3567","3568","3569","3570","3571","3572","3573","3574","3575","3576","3577","3578","3579","3580","3581","3582","3583","3584","3585","3586","3587","3588","3589","3590","3591","3592","3593","3594","3595","3596","3597","3598","3599","3600","3601","3602","3603","3604","3605","3606","3607","3608","3609","3610","3611","3612","3613","3614","3615","3616","3617","3618","3619","3620","3621","3622","3623","3624","3625","3626","3627","3628","3629","3630","3631","3632","3633","3634","3635","3636","3637","3638","3639","3640","3641","3642","3643","3644","3645","3646","3647","3648","3649","3650","3651","3652","3653","3654","3655","3656","3657","3658","3659","3660","3661","3662","3663","3664","3665","3666","3667","3668","3669","3670","3671","3672","3673","3674","3675","3676","3677","3678","3679","3680","3681","3682","3683","3684","3685","3686","3687","3688","3689","3690","3691","3692","3693","3694","3695","3696","3697","3698","3699","3700","3701","3702","3703","3704","3705","3706","3707","3708","3709","3710","3711","3712","3713","3714","3715","3716","3717","3718","3719","3720","3721","3722","3723","3724","3725","3726","3727","3728","3729","3730","3731","3732","3733","3734","3735","3736","3737","3738","3739","3740","3741","3742","3743","3744","3745","3746","3747","3748","3749","3750","3751","3752","3753","3754","3755","3756","3757","3758","3759","3760","3761","3762","3763","3764","3765","3766","3767","3768","3769","3770","3771","3772","3773","3774","3775","3776","3777","3778","3779","3780","3781","3782","3783","3784","3785","3786","3787","3788","3789","3790","3791","3792","3793","3794","3795","3796","3797","3798","3799","3800","3801","3802","3803","3804","3805","3806","3807","3808","3809","3810","3811","3812","3813","3814","3815","3816","3817","3818","3819","3820","3821","3822","3823","3824","3825","3826","3827","3828","3829","3830","3831","3832","3833","3834","3835","3836","3837","3838","3839","3840","3841","3842","3843","3844","3845","3846","3847","3848","3849","3850","3851","3852","3853","3854","3855","3856","3857","3858","3859","3860","3861","3862","3863","3864","3865","3866","3867","3868","3869","3870","3871","3872","3873","3874","3875","3876","3877","3878","3879","3880","3881","3882","3883","3884","3885","3886","3887","3888","3889","3890","3891","3892","3893","3894","3895","3896","3897","3898","3899","3900","3901","3902","3903","3904","3905","3906","3907","3908","3909","3910","3911","3912","3913","3914","3915","3916","3917","3918","3919","3920","3921","3922","3923","3924","3925","3926","3927","3928","3929","3930","3931","3932","3933","3934","3935","3936","3937","3938","3939","3940","3941","3942","3943","3944","3945","3946","3947","3948","3949","3950","3951","3952","3953","3954","3955","3956","3957","3958","3959","3960","3961","3962","3963","3964","3965","3966","3967","3968","3969","3970","3971","3972","3973","3974","3975","3976","3977","3978","3979","3980","3981","3982","3983","3984","3985","3986","3987","3988","3989","3990","3991","3992","3993","3994","3995","3996","3997","3998","3999","4000","4001","4002","4003","4004","4005","4006","4007","4008","4009","4010","4011","4012","4013","4014","4015","4016","4017","4018","4019","4020","4021","4022","4023","4024","4025","4026","4027","4028","4029","4030","4031","4032","4033","4034","4035","4036","4037","4038","4039","4040","4041","4042","4043","4044","4045","4046","4047","4048","4049","4050","4051","4052","4053","4054","4055","4056","4057","4058","4059","4060","4061","4062","4063","4064","4065","4066","4067","4068","4069","4070","4071","4072","4073","4074","4075","4076","4077","4078","4079","4080","4081","4082","4083","4084","4085","4086","4087","4088","4089","4090","4091","4092","4093","4094","4095","4096","4097","4098","4099","4100","4101","4102","4103","4104","4105","4106","4107","4108","4109","4110","4111","4112","4113","4114","4115","4116","4117","4118","4119","4120","4121","4122","4123","4124","4125","4126","4127","4128","4129","4130","4131","4132","4133","4134","4135","4136","4137","4138","4139","4140","4141","4142","4143","4144","4145","4146","4147","4148","4149","4150","4151","4152","4153","4154","4155","4156","4157","4158","4159","4160","4161","4162","4163","4164","4165","4166","4167","4168","4169","4170","4171","4172","4173","4174","4175","4176","4177","4178","4179","4180","4181","4182","4183","4184","4185","4186","4187","4188","4189","4190","4191","4192","4193","4194","4195","4196","4197","4198","4199","4200","4201","4202","4203","4204","4205","4206","4207","4208","4209","4210","4211","4212","4213","4214","4215","4216","4217","4218","4219","4220","4221","4222","4223","4224","4225","4226","4227","4228","4229","4230","4231","4232","4233","4234","4235","4236","4237","4238","4239","4240","4241","4242","4243","4244","4245","4246","4247","4248","4249","4250","4251","4252","4253","4254","4255","4256","4257","4258","4259","4260","4261","4262","4263","4264","4265","4266","4267","4268","4269","4270","4271","4272","4273","4274","4275","4276","4277","4278","4279","4280","4281","4282","4283","4284","4285","4286","4287","4288","4289","4290","4291","4292","4293","4294","4295","4296","4297","4298","4299","4300","4301","4302","4303","4304","4305","4306","4307","4308","4309","4310","4311","4312","4313","4314","4315","4316","4317","4318","4319","4320","4321","4322","4323","4324","4325","4326","4327","4328","4329","4330","4331","4332","4333","4334","4335","4336","4337","4338","4339","4340","4341","4342","4343","4344","4345","4346","4347","4348","4349","4350","4351","4352","4353","4354","4355","4356","4357","4358","4359","4360","4361","4362","4363","4364","4365","4366","4367","4368","4369","4370","4371","4372","4373","4374","4375","4376","4377","4378","4379","4380","4381","4382","4383","4384","4385","4386","4387","4388","4389","4390","4391","4392","4393","4394","4395","4396","4397","4398","4399","4400","4401","4402","4403","4404","4405","4406","4407","4408","4409","4410","4411","4412","4413","4414","4415","4416","4417","4418","4419","4420","4421","4422","4423","4424","4425","4426","4427","4428","4429","4430","4431","4432","4433","4434","4435","4436","4437","4438","4439","4440","4441","4442","4443","4444","4445","4446","4447","4448","4449","4450","4451","4452","4453","4454","4455","4456","4457","4458","4459","4460","4461","4462","4463","4464","4465","4466","4467","4468","4469","4470","4471","4472","4473","4474","4475","4476","4477","4478","4479","4480","4481","4482","4483","4484","4485","4486","4487","4488","4489","4490","4491","4492","4493","4494","4495","4496","4497","4498","4499","4500","4501","4502","4503","4504","4505","4506","4507","4508","4509","4510","4511","4512","4513","4514","4515","4516","4517","4518","4519","4520","4521","4522","4523","4524","4525","4526","4527","4528","4529","4530","4531","4532","4533","4534","4535","4536","4537","4538","4539","4540","4541","4542","4543","4544","4545","4546","4547","4548","4549","4550","4551","4552","4553","4554","4555","4556","4557","4558","4559","4560","4561","4562","4563","4564","4565","4566","4567","4568","4569","4570","4571","4572","4573","4574","4575","4576","4577","4578","4579","4580","4581","4582","4583","4584","4585","4586","4587","4588","4589","4590","4591","4592","4593","4594","4595","4596","4597","4598","4599","4600","4601","4602","4603","4604","4605","4606","4607","4608","4609","4610","4611","4612","4613","4614","4615","4616","4617","4618","4619","4620","4621","4622","4623","4624","4625","4626","4627","4628","4629","4630","4631","4632","4633","4634","4635","4636","4637","4638","4639","4640","4641","4642","4643","4644","4645","4646","4647","4648","4649","4650","4651","4652","4653","4654","4655","4656","4657","4658","4659","4660","4661","4662","4663","4664","4665","4666","4667","4668","4669","4670","4671","4672","4673","4674","4675","4676","4677","4678","4679","4680","4681","4682","4683","4684","4685","4686","4687","4688","4689","4690","4691","4692","4693","4694","4695","4696","4697","4698","4699","4700","4701","4702","4703","4704","4705","4706","4707","4708","4709","4710","4711","4712","4713","4714","4715","4716","4717","4718","4719","4720","4721","4722","4723","4724","4725","4726","4727","4728","4729","4730","4731","4732","4733","4734","4735","4736","4737","4738","4739","4740","4741","4742","4743","4744","4745","4746","4747","4748","4749","4750","4751","4752","4753","4754","4755","4756","4757","4758","4759","4760","4761","4762","4763","4764","4765","4766","4767","4768","4769","4770","4771","4772","4773","4774","4775","4776","4777","4778","4779","4780","4781","4782","4783","4784","4785","4786","4787","4788","4789","4790","4791","4792","4793","4794","4795","4796","4797","4798","4799","4800","4801","4802","4803","4804","4805","4806","4807","4808","4809","4810","4811","4812","4813","4814","4815","4816","4817","4818","4819","4820","4821","4822","4823","4824","4825","4826","4827","4828","4829","4830","4831","4832","4833","4834","4835","4836","4837","4838","4839","4840","4841","4842","4843","4844","4845","4846","4847","4848","4849","4850","4851","4852","4853","4854","4855","4856","4857","4858","4859","4860","4861","4862","4863","4864","4865","4866","4867","4868","4869","4870","4871","4872","4873","4874","4875","4876","4877","4878","4879","4880","4881","4882","4883","4884","4885","4886","4887","4888","4889","4890","4891","4892","4893","4894","4895","4896","4897","4898","4899","4900","4901","4902","4903","4904","4905","4906","4907","4908","4909","4910","4911","4912","4913","4914","4915","4916","4917","4918","4919","4920","4921","4922","4923","4924","4925","4926","4927","4928","4929","4930","4931","4932","4933","4934","4935","4936","4937","4938","4939","4940","4941","4942","4943","4944","4945","4946","4947","4948","4949","4950","4951","4952","4953","4954","4955","4956","4957","4958","4959","4960","4961","4962","4963","4964","4965","4966","4967","4968","4969","4970","4971","4972","4973","4974","4975","4976","4977","4978","4979","4980","4981","4982","4983","4984","4985","4986","4987","4988","4989","4990","4991","4992","4993","4994","4995","4996","4997","4998","4999","5000","5001","5002","5003","5004","5005","5006","5007","5008","5009","5010","5011","5012","5013","5014","5015","5016","5017","5018","5019","5020","5021","5022","5023","5024","5025","5026","5027","5028","5029","5030","5031","5032","5033","5034","5035","5036","5037","5038","5039","5040","5041","5042","5043","5044","5045","5046","5047","5048","5049","5050","5051","5052","5053","5054","5055","5056","5057","5058","5059","5060","5061","5062","5063","5064","5065","5066","5067","5068","5069","5070","5071","5072","5073","5074","5075","5076","5077","5078","5079","5080","5081","5082","5083","5084","5085","5086","5087","5088","5089","5090","5091","5092","5093","5094","5095","5096","5097","5098","5099","5100","5101","5102","5103","5104","5105","5106","5107","5108","5109","5110","5111","5112","5113","5114","5115","5116","5117","5118","5119","5120","5121","5122","5123","5124","5125","5126","5127","5128","5129","5130","5131","5132","5133","5134","5135","5136","5137","5138","5139","5140","5141","5142","5143","5144","5145","5146","5147","5148","5149","5150","5151","5152","5153","5154","5155","5156","5157","5158","5159","5160","5161","5162","5163","5164","5165","5166","5167","5168","5169","5170","5171","5172","5173","5174","5175","5176","5177","5178","5179","5180","5181","5182","5183","5184","5185","5186","5187","5188","5189","5190","5191","5192","5193","5194","5195","5196","5197","5198","5199","5200","5201","5202","5203","5204","5205","5206","5207","5208","5209","5210","5211","5212","5213","5214","5215","5216","5217","5218","5219","5220","5221","5222","5223","5224","5225","5226","5227","5228","5229","5230","5231","5232","5233","5234","5235","5236","5237","5238","5239","5240","5241","5242","5243","5244","5245","5246","5247","5248","5249","5250","5251","5252","5253","5254","5255","5256","5257","5258","5259","5260","5261","5262","5263","5264","5265","5266","5267","5268","5269","5270","5271","5272","5273","5274","5275","5276","5277","5278","5279","5280","5281","5282","5283","5284","5285","5286","5287","5288","5289","5290","5291","5292","5293","5294","5295","5296","5297","5298","5299","5300","5301","5302","5303","5304","5305","5306","5307","5308","5309","5310","5311","5312","5313","5314","5315","5316","5317","5318","5319","5320","5321","5322","5323","5324","5325","5326","5327","5328","5329","5330","5331","5332","5333","5334","5335","5336","5337","5338","5339","5340","5341","5342","5343","5344","5345","5346","5347","5348","5349","5350","5351","5352","5353","5354","5355","5356","5357","5358","5359","5360","5361","5362","5363","5364","5365","5366","5367","5368","5369","5370","5371","5372","5373","5374","5375","5376","5377","5378","5379","5380","5381","5382","5383","5384","5385","5386","5387","5388","5389","5390","5391","5392","5393","5394","5395","5396","5397","5398","5399","5400","5401","5402","5403","5404","5405","5406","5407","5408","5409","5410","5411","5412","5413","5414","5415","5416","5417","5418","5419","5420","5421","5422","5423","5424","5425","5426","5427","5428","5429","5430","5431","5432","5433","5434","5435","5436","5437","5438","5439","5440","5441","5442","5443","5444","5445","5446","5447","5448","5449","5450","5451","5452","5453","5454","5455","5456","5457","5458","5459","5460","5461","5462","5463","5464","5465","5466","5467","5468","5469","5470","5471","5472","5473","5474","5475","5476","5477","5478","5479","5480","5481","5482","5483","5484","5485","5486","5487","5488","5489","5490","5491","5492","5493","5494","5495","5496","5497","5498","5499","5500","5501","5502","5503","5504","5505","5506","5507","5508","5509","5510","5511","5512","5513","5514","5515","5516","5517","5518","5519","5520","5521","5522","5523","5524","5525","5526","5527","5528","5529","5530","5531","5532","5533","5534","5535","5536","5537","5538","5539","5540","5541","5542","5543","5544","5545","5546","5547","5548","5549","5550","5551","5552","5553","5554","5555","5556","5557","5558","5559","5560","5561","5562","5563","5564","5565","5566","5567","5568","5569","5570","5571","5572","5573","5574","5575","5576","5577","5578","5579","5580","5581","5582","5583","5584","5585","5586","5587","5588","5589","5590","5591","5592","5593","5594","5595","5596","5597","5598","5599","5600","5601","5602","5603","5604","5605","5606","5607","5608","5609","5610","5611","5612","5613","5614","5615","5616","5617","5618","5619","5620","5621","5622","5623","5624","5625","5626","5627","5628","5629","5630","5631","5632","5633","5634","5635","5636","5637","5638","5639","5640","5641","5642","5643","5644","5645","5646","5647","5648","5649","5650","5651","5652","5653","5654","5655","5656","5657","5658","5659","5660","5661","5662","5663","5664","5665","5666","5667","5668","5669","5670","5671","5672","5673","5674","5675","5676","5677","5678","5679","5680","5681","5682","5683","5684","5685","5686","5687","5688","5689","5690","5691","5692","5693","5694","5695","5696","5697","5698","5699","5700","5701","5702","5703","5704","5705","5706","5707","5708","5709","5710","5711","5712","5713","5714","5715","5716","5717","5718","5719","5720","5721","5722","5723","5724","5725","5726","5727","5728","5729","5730","5731","5732","5733","5734","5735","5736","5737","5738","5739","5740","5741","5742","5743","5744","5745","5746","5747","5748","5749","5750","5751","5752","5753","5754","5755","5756","5757","5758","5759","5760","5761","5762","5763","5764","5765","5766","5767","5768","5769","5770","5771","5772","5773","5774","5775","5776","5777","5778","5779","5780","5781","5782","5783","5784","5785","5786","5787","5788","5789","5790","5791","5792","5793","5794","5795","5796","5797","5798","5799","5800","5801","5802","5803","5804","5805","5806","5807","5808","5809","5810","5811","5812","5813","5814","5815","5816","5817","5818","5819","5820","5821","5822","5823","5824","5825","5826","5827","5828","5829","5830","5831","5832","5833","5834","5835","5836","5837","5838","5839","5840","5841","5842","5843","5844","5845","5846","5847","5848","5849","5850","5851","5852","5853","5854","5855","5856","5857","5858","5859","5860","5861","5862","5863","5864","5865","5866","5867","5868","5869","5870","5871","5872","5873","5874","5875","5876","5877","5878","5879","5880","5881","5882","5883","5884","5885","5886","5887","5888","5889","5890","5891","5892","5893","5894","5895","5896","5897","5898","5899","5900","5901","5902","5903","5904","5905","5906","5907","5908","5909","5910","5911","5912","5913","5914","5915","5916","5917","5918","5919","5920","5921","5922","5923","5924","5925","5926","5927","5928","5929","5930","5931","5932","5933","5934","5935","5936","5937","5938","5939","5940","5941","5942","5943","5944","5945","5946","5947","5948","5949","5950","5951","5952","5953","5954","5955","5956","5957","5958","5959","5960","5961","5962","5963","5964","5965","5966","5967","5968","5969","5970","5971","5972","5973","5974","5975","5976","5977","5978","5979","5980","5981","5982","5983","5984","5985","5986","5987","5988","5989","5990","5991","5992","5993","5994","5995","5996","5997","5998","5999","6000","6001","6002","6003","6004","6005","6006","6007","6008","6009","6010","6011","6012","6013","6014","6015","6016","6017","6018","6019","6020","6021","6022","6023","6024","6025","6026","6027","6028","6029","6030","6031","6032","6033","6034","6035","6036","6037","6038","6039","6040","6041","6042","6043","6044","6045","6046","6047","6048","6049","6050","6051","6052","6053","6054","6055","6056","6057","6058","6059","6060","6061","6062","6063","6064","6065","6066","6067","6068","6069","6070","6071","6072","6073","6074","6075","6076","6077","6078","6079","6080","6081","6082","6083","6084","6085","6086","6087","6088","6089","6090","6091","6092","6093","6094","6095","6096","6097","6098","6099","6100","6101","6102","6103","6104","6105","6106","6107","6108","6109","6110","6111","6112","6113","6114","6115","6116","6117","6118","6119","6120","6121","6122","6123","6124","6125","6126","6127","6128","6129","6130","6131","6132","6133","6134","6135","6136","6137","6138","6139","6140","6141","6142","6143","6144","6145","6146","6147","6148","6149","6150","6151","6152","6153","6154","6155","6156","6157","6158","6159","6160","6161","6162","6163","6164","6165","6166","6167","6168","6169","6170","6171","6172","6173","6174","6175","6176","6177","6178","6179","6180","6181","6182","6183","6184","6185","6186","6187","6188","6189","6190","6191","6192","6193","6194","6195","6196","6197","6198","6199","6200","6201","6202","6203","6204","6205","6206","6207","6208","6209","6210","6211","6212","6213","6214","6215","6216","6217","6218","6219","6220","6221","6222","6223","6224","6225","6226","6227","6228","6229","6230","6231","6232","6233","6234","6235","6236","6237","6238","6239","6240","6241","6242","6243","6244","6245","6246","6247","6248","6249","6250","6251","6252","6253","6254","6255","6256","6257","6258","6259","6260","6261","6262","6263","6264","6265","6266","6267","6268","6269","6270","6271","6272","6273","6274","6275","6276","6277","6278","6279","6280","6281","6282","6283","6284","6285","6286","6287","6288","6289","6290","6291","6292","6293","6294","6295","6296","6297","6298","6299","6300","6301","6302","6303","6304","6305","6306","6307","6308","6309","6310","6311","6312","6313","6314","6315","6316","6317","6318","6319","6320","6321","6322","6323","6324","6325","6326","6327","6328","6329","6330","6331","6332","6333","6334","6335","6336","6337","6338","6339","6340","6341","6342","6343","6344","6345","6346","6347","6348","6349","6350","6351","6352","6353","6354","6355","6356","6357","6358","6359","6360","6361","6362","6363","6364","6365","6366","6367","6368","6369","6370","6371","6372","6373","6374","6375","6376","6377","6378","6379","6380","6381","6382","6383","6384","6385","6386","6387","6388","6389","6390","6391","6392","6393","6394","6395","6396","6397","6398","6399","6400","6401","6402","6403","6404","6405","6406","6407","6408","6409","6410","6411","6412","6413","6414","6415","6416","6417","6418","6419","6420","6421","6422","6423","6424","6425","6426","6427","6428","6429","6430","6431","6432","6433","6434","6435","6436","6437","6438","6439","6440","6441","6442","6443","6444","6445","6446","6447","6448","6449","6450","6451","6452","6453","6454","6455","6456","6457","6458","6459","6460","6461","6462","6463","6464","6465","6466","6467","6468","6469","6470","6471","6472","6473","6474","6475","6476","6477","6478","6479","6480","6481","6482","6483","6484","6485","6486","6487","6488","6489","6490","6491","6492","6493","6494","6495","6496","6497","6498","6499","6500","6501","6502","6503","6504","6505","6506","6507","6508","6509","6510","6511","6512","6513","6514","6515","6516","6517","6518","6519","6520","6521","6522","6523","6524","6525","6526","6527","6528","6529","6530","6531","6532","6533","6534","6535","6536","6537","6538","6539","6540","6541","6542","6543","6544","6545","6546","6547","6548","6549","6550","6551","6552","6553","6554","6555","6556","6557","6558","6559","6560","6561","6562","6563","6564","6565","6566","6567","6568","6569","6570","6571","6572","6573","6574","6575","6576","6577","6578","6579","6580","6581","6582","6583","6584","6585","6586","6587","6588","6589","6590","6591","6592","6593","6594","6595","6596","6597","6598","6599","6600","6601","6602","6603","6604","6605","6606","6607","6608","6609","6610","6611","6612","6613","6614","6615","6616","6617","6618","6619","6620","6621","6622","6623","6624","6625","6626","6627","6628","6629","6630","6631","6632","6633","6634","6635","6636","6637","6638","6639","6640","6641","6642","6643","6644","6645","6646","6647","6648","6649","6650","6651","6652","6653","6654","6655","6656","6657","6658","6659","6660","6661","6662","6663","6664","6665","6666","6667","6668","6669","6670","6671","6672","6673","6674","6675","6676","6677","6678","6679","6680","6681","6682","6683","6684","6685","6686","6687","6688","6689","6690","6691","6692","6693","6694","6695","6696","6697","6698","6699","6700","6701","6702","6703","6704","6705","6706","6707","6708","6709","6710","6711","6712","6713","6714","6715","6716","6717","6718","6719","6720","6721","6722","6723","6724","6725","6726","6727","6728","6729","6730","6731","6732","6733","6734","6735","6736","6737","6738","6739","6740","6741","6742","6743","6744","6745","6746","6747","6748","6749","6750","6751","6752","6753","6754","6755","6756","6757","6758","6759","6760","6761","6762","6763","6764","6765","6766","6767","6768","6769","6770","6771","6772","6773","6774","6775","6776","6777","6778","6779","6780","6781","6782","6783","6784","6785","6786","6787","6788","6789","6790","6791","6792","6793","6794","6795","6796","6797","6798","6799","6800","6801","6802","6803","6804","6805","6806","6807","6808","6809","6810","6811","6812","6813","6814","6815","6816","6817","6818","6819","6820","6821","6822","6823","6824","6825","6826","6827","6828","6829","6830","6831","6832","6833","6834","6835","6836","6837","6838","6839","6840","6841","6842","6843","6844","6845","6846","6847","6848","6849","6850","6851","6852","6853","6854","6855","6856","6857","6858","6859","6860","6861","6862","6863","6864","6865","6866","6867","6868","6869","6870","6871","6872","6873","6874","6875","6876","6877","6878","6879","6880","6881","6882","6883","6884","6885","6886","6887","6888","6889","6890","6891","6892","6893","6894","6895","6896","6897","6898","6899","6900","6901","6902","6903","6904","6905","6906","6907","6908","6909","6910","6911","6912","6913","6914","6915","6916","6917","6918","6919","6920","6921","6922","6923","6924","6925","6926","6927","6928","6929","6930","6931","6932","6933","6934","6935","6936","6937","6938","6939","6940","6941","6942","6943","6944","6945","6946","6947","6948","6949","6950","6951","6952","6953","6954","6955","6956","6957","6958","6959","6960","6961","6962","6963","6964","6965","6966","6967","6968","6969","6970","6971","6972","6973","6974","6975","6976","6977","6978","6979","6980","6981","6982","6983","6984","6985","6986","6987","6988","6989","6990","6991","6992","6993","6994","6995","6996","6997","6998","6999","7000","7001","7002","7003","7004","7005","7006","7007","7008","7009","7010","7011","7012","7013","7014","7015","7016","7017","7018","7019","7020","7021","7022","7023","7024","7025","7026","7027","7028","7029","7030","7031","7032","7033","7034","7035","7036","7037","7038","7039","7040","7041","7042","7043","7044","7045","7046","7047","7048","7049","7050","7051","7052","7053","7054","7055","7056","7057","7058","7059","7060","7061","7062","7063","7064","7065","7066","7067","7068","7069","7070","7071","7072","7073","7074","7075","7076","7077","7078","7079","7080","7081","7082","7083","7084","7085","7086","7087","7088","7089","7090","7091","7092","7093","7094","7095","7096","7097","7098","7099","7100","7101","7102","7103","7104","7105","7106","7107","7108","7109","7110","7111","7112","7113","7114","7115","7116","7117","7118","7119","7120","7121","7122","7123","7124","7125","7126","7127","7128","7129","7130","7131","7132","7133","7134","7135","7136","7137","7138","7139","7140","7141","7142","7143","7144","7145","7146","7147","7148","7149","7150","7151","7152","7153","7154","7155","7156","7157","7158","7159","7160","7161","7162","7163","7164","7165","7166","7167","7168","7169","7170","7171","7172","7173","7174","7175","7176","7177","7178","7179","7180","7181","7182","7183","7184","7185","7186","7187","7188","7189","7190","7191","7192","7193","7194","7195","7196","7197","7198","7199","7200","7201","7202","7203","7204","7205","7206","7207","7208","7209","7210","7211","7212","7213","7214","7215","7216","7217","7218","7219","7220","7221","7222","7223","7224","7225","7226","7227","7228","7229","7230","7231","7232","7233","7234","7235","7236","7237","7238","7239","7240","7241","7242","7243","7244","7245","7246","7247","7248","7249","7250","7251","7252","7253","7254","7255","7256","7257","7258","7259","7260","7261","7262","7263","7264","7265","7266","7267","7268","7269","7270","7271","7272","7273","7274","7275","7276","7277","7278","7279","7280","7281","7282","7283","7284","7285","7286","7287","7288","7289","7290","7291","7292","7293","7294","7295","7296","7297","7298","7299","7300","7301","7302","7303","7304","7305","7306","7307","7308","7309","7310","7311","7312","7313","7314","7315","7316","7317","7318","7319","7320","7321","7322","7323","7324","7325","7326","7327","7328","7329","7330","7331","7332","7333","7334","7335","7336","7337","7338","7339","7340","7341","7342","7343","7344","7345","7346","7347","7348","7349","7350","7351","7352","7353","7354","7355","7356","7357","7358","7359","7360","7361","7362","7363","7364","7365","7366","7367","7368","7369","7370","7371","7372","7373","7374","7375","7376","7377","7378","7379","7380","7381","7382","7383","7384","7385","7386","7387","7388","7389","7390","7391","7392","7393","7394","7395","7396","7397","7398","7399","7400","7401","7402","7403","7404","7405","7406","7407","7408","7409","7410","7411","7412","7413","7414","7415","7416","7417","7418","7419","7420","7421","7422","7423","7424","7425","7426","7427","7428","7429","7430","7431","7432","7433","7434","7435","7436","7437","7438","7439","7440","7441","7442","7443","7444","7445","7446","7447","7448","7449","7450","7451","7452","7453","7454","7455","7456","7457","7458","7459","7460","7461","7462","7463","7464","7465","7466","7467","7468","7469","7470","7471","7472","7473","7474","7475","7476","7477","7478","7479","7480","7481","7482","7483","7484","7485","7486","7487","7488","7489","7490","7491","7492","7493","7494","7495","7496","7497","7498","7499","7500","7501","7502","7503","7504","7505","7506","7507","7508","7509","7510","7511","7512","7513","7514","7515","7516","7517","7518","7519","7520","7521","7522","7523","7524","7525","7526","7527","7528","7529","7530","7531","7532","7533","7534","7535","7536","7537","7538","7539","7540","7541","7542","7543","7544","7545","7546","7547","7548","7549","7550","7551","7552","7553","7554","7555","7556","7557","7558","7559","7560","7561","7562","7563","7564","7565","7566","7567","7568","7569","7570","7571","7572","7573","7574","7575","7576","7577","7578","7579","7580","7581","7582","7583","7584","7585","7586","7587","7588","7589","7590","7591","7592","7593","7594","7595","7596","7597","7598","7599","7600","7601","7602","7603","7604","7605","7606","7607","7608","7609","7610","7611","7612","7613","7614","7615","7616","7617","7618","7619","7620","7621","7622","7623","7624","7625","7626","7627","7628","7629","7630","7631","7632","7633","7634","7635","7636","7637","7638","7639","7640","7641","7642","7643","7644","7645","7646","7647","7648","7649","7650","7651","7652","7653","7654","7655","7656","7657","7658","7659","7660","7661","7662","7663","7664","7665","7666","7667","7668","7669","7670","7671","7672","7673","7674","7675","7676","7677","7678","7679","7680","7681","7682","7683","7684","7685","7686","7687","7688","7689","7690","7691","7692","7693","7694","7695","7696","7697","7698","7699","7700","7701","7702","7703","7704","7705","7706","7707","7708","7709","7710","7711","7712","7713","7714","7715","7716","7717","7718","7719","7720","7721","7722","7723","7724","7725","7726","7727","7728","7729","7730","7731","7732","7733","7734","7735","7736","7737","7738","7739","7740","7741","7742","7743","7744","7745","7746","7747","7748","7749","7750","7751","7752","7753","7754","7755","7756","7757","7758","7759","7760","7761","7762","7763","7764","7765","7766","7767","7768","7769","7770","7771","7772","7773","7774","7775","7776","7777","7778","7779","7780","7781","7782","7783","7784","7785","7786","7787","7788","7789","7790","7791","7792","7793","7794","7795","7796","7797","7798","7799","7800","7801","7802","7803","7804","7805","7806","7807","7808","7809","7810","7811","7812","7813","7814","7815","7816","7817","7818","7819","7820","7821","7822","7823","7824","7825","7826","7827","7828","7829","7830","7831","7832","7833","7834","7835","7836","7837","7838","7839","7840","7841","7842","7843","7844","7845","7846","7847","7848","7849","7850","7851","7852","7853","7854","7855","7856","7857","7858","7859","7860","7861","7862","7863","7864","7865","7866","7867","7868","7869","7870","7871","7872","7873","7874","7875","7876","7877","7878","7879","7880","7881","7882","7883","7884","7885","7886","7887","7888","7889","7890","7891","7892","7893","7894","7895","7896","7897","7898","7899","7900","7901","7902","7903","7904","7905","7906","7907","7908","7909","7910","7911","7912","7913","7914","7915","7916","7917","7918","7919","7920","7921","7922","7923","7924","7925","7926","7927","7928","7929","7930","7931","7932","7933","7934","7935","7936","7937","7938","7939","7940","7941","7942","7943","7944","7945","7946","7947","7948","7949","7950","7951","7952","7953","7954","7955","7956","7957","7958","7959","7960","7961","7962","7963","7964","7965","7966","7967","7968","7969","7970","7971","7972","7973","7974","7975","7976","7977","7978","7979","7980","7981","7982","7983","7984","7985","7986","7987","7988","7989","7990","7991","7992","7993","7994","7995","7996","7997","7998","7999","8000","8001","8002","8003","8004","8005","8006","8007","8008","8009","8010","8011","8012","8013","8014","8015","8016","8017","8018","8019","8020","8021","8022","8023","8024","8025","8026","8027","8028","8029","8030","8031","8032","8033","8034","8035","8036","8037","8038","8039","8040","8041","8042","8043","8044","8045","8046","8047","8048","8049","8050","8051","8052","8053","8054","8055","8056","8057","8058","8059","8060","8061","8062","8063","8064","8065","8066","8067","8068","8069","8070","8071","8072","8073","8074","8075","8076","8077","8078","8079","8080","8081","8082","8083","8084","8085","8086","8087","8088","8089","8090","8091","8092","8093","8094","8095","8096","8097","8098","8099","8100","8101","8102","8103","8104","8105","8106","8107","8108","8109","8110","8111","8112","8113","8114","8115","8116","8117","8118","8119","8120","8121","8122","8123","8124","8125","8126","8127","8128","8129","8130","8131","8132","8133","8134","8135","8136","8137","8138","8139","8140","8141","8142","8143","8144","8145","8146","8147","8148","8149","8150","8151","8152","8153","8154","8155","8156","8157","8158","8159","8160","8161","8162","8163","8164","8165","8166","8167","8168","8169","8170","8171","8172","8173","8174","8175","8176","8177","8178","8179","8180","8181","8182","8183","8184","8185","8186","8187","8188","8189","8190","8191","8192","8193","8194","8195","8196","8197","8198","8199","8200","8201","8202","8203","8204","8205","8206","8207","8208","8209","8210","8211","8212","8213","8214","8215","8216","8217","8218","8219","8220","8221","8222","8223","8224","8225","8226","8227","8228","8229","8230","8231","8232","8233","8234","8235","8236","8237","8238","8239","8240","8241","8242","8243","8244","8245","8246","8247","8248","8249","8250","8251","8252","8253","8254","8255","8256","8257","8258","8259","8260","8261","8262","8263","8264","8265","8266","8267","8268","8269","8270","8271","8272","8273","8274","8275","8276","8277","8278","8279","8280","8281","8282","8283","8284","8285","8286","8287","8288","8289","8290","8291","8292","8293","8294","8295","8296","8297","8298","8299","8300","8301","8302","8303","8304","8305","8306","8307","8308","8309","8310","8311","8312","8313","8314","8315","8316","8317","8318","8319","8320","8321","8322","8323","8324","8325","8326","8327","8328","8329","8330","8331","8332","8333","8334","8335","8336","8337","8338","8339","8340","8341","8342","8343","8344","8345","8346","8347","8348","8349","8350","8351","8352","8353","8354","8355","8356","8357","8358","8359","8360","8361","8362","8363","8364","8365","8366","8367","8368","8369","8370","8371","8372","8373","8374","8375","8376","8377","8378","8379","8380","8381","8382","8383","8384","8385","8386","8387","8388","8389","8390","8391","8392","8393","8394","8395","8396","8397","8398","8399","8400","8401","8402","8403","8404","8405","8406","8407","8408","8409","8410","8411","8412","8413","8414","8415","8416","8417","8418","8419","8420","8421","8422","8423","8424","8425","8426","8427","8428","8429","8430","8431","8432","8433","8434","8435","8436","8437","8438","8439","8440","8441","8442","8443","8444","8445","8446","8447","8448","8449","8450","8451","8452","8453","8454","8455","8456","8457","8458","8459","8460","8461","8462","8463","8464","8465","8466","8467","8468","8469","8470","8471","8472","8473","8474","8475","8476","8477","8478","8479","8480","8481","8482","8483","8484","8485","8486","8487","8488","8489","8490","8491","8492","8493","8494","8495","8496","8497","8498","8499","8500","8501","8502","8503","8504","8505","8506","8507","8508","8509","8510","8511","8512","8513","8514","8515","8516","8517","8518","8519","8520","8521","8522","8523","8524","8525","8526","8527","8528","8529","8530","8531","8532","8533","8534","8535","8536","8537","8538","8539","8540","8541","8542","8543","8544","8545","8546","8547","8548","8549","8550","8551","8552","8553","8554","8555","8556","8557","8558","8559","8560","8561","8562","8563","8564","8565","8566","8567","8568","8569","8570","8571","8572","8573","8574","8575","8576","8577","8578","8579","8580","8581","8582","8583","8584","8585","8586","8587","8588","8589","8590","8591","8592","8593","8594","8595","8596","8597","8598","8599","8600","8601","8602","8603","8604","8605","8606","8607","8608","8609","8610","8611","8612","8613","8614","8615","8616","8617","8618","8619","8620","8621","8622","8623","8624","8625","8626","8627","8628","8629","8630","8631","8632","8633","8634","8635","8636","8637","8638","8639","8640","8641","8642","8643","8644","8645","8646","8647","8648","8649","8650","8651","8652","8653","8654","8655","8656","8657","8658","8659","8660","8661","8662","8663","8664","8665","8666","8667","8668","8669","8670","8671","8672","8673","8674","8675","8676","8677","8678","8679","8680","8681","8682","8683","8684","8685","8686","8687","8688","8689","8690","8691","8692","8693","8694","8695","8696","8697","8698","8699","8700","8701","8702","8703","8704","8705","8706","8707","8708","8709","8710","8711","8712","8713","8714","8715","8716","8717","8718","8719","8720","8721","8722","8723","8724","8725","8726","8727","8728","8729","8730","8731","8732","8733","8734","8735","8736","8737","8738","8739","8740","8741","8742","8743","8744","8745","8746","8747","8748","8749","8750","8751","8752","8753","8754","8755","8756","8757","8758","8759","8760","8761","8762","8763","8764","8765","8766","8767","8768","8769","8770","8771","8772","8773","8774","8775","8776","8777","8778","8779","8780","8781","8782","8783","8784","8785","8786","8787","8788","8789","8790","8791","8792","8793","8794","8795","8796","8797","8798","8799","8800","8801","8802","8803","8804","8805","8806","8807","8808","8809","8810","8811","8812","8813","8814","8815","8816","8817","8818","8819","8820","8821","8822","8823","8824","8825","8826","8827","8828","8829","8830","8831","8832","8833","8834","8835","8836","8837","8838","8839","8840","8841","8842","8843","8844","8845","8846","8847","8848","8849","8850","8851","8852","8853","8854","8855","8856","8857","8858","8859","8860","8861","8862","8863","8864","8865","8866","8867","8868","8869","8870","8871","8872","8873","8874","8875","8876","8877","8878","8879","8880","8881","8882","8883","8884","8885","8886","8887","8888","8889","8890","8891","8892","8893","8894","8895","8896","8897","8898","8899","8900","8901","8902","8903","8904","8905","8906","8907","8908","8909","8910","8911","8912","8913","8914","8915","8916","8917","8918","8919","8920","8921","8922","8923","8924","8925","8926","8927","8928","8929","8930","8931","8932","8933","8934","8935","8936","8937","8938","8939","8940","8941","8942","8943","8944","8945","8946","8947","8948","8949","8950","8951","8952","8953","8954","8955","8956","8957","8958","8959","8960","8961","8962","8963","8964","8965","8966","8967","8968","8969","8970","8971","8972","8973","8974","8975","8976","8977","8978","8979","8980","8981","8982","8983","8984","8985","8986","8987","8988","8989","8990","8991","8992","8993","8994","8995","8996","8997","8998","8999","9000","9001","9002","9003","9004","9005","9006","9007","9008","9009","9010","9011","9012","9013","9014","9015","9016","9017","9018","9019","9020","9021","9022","9023","9024","9025","9026","9027","9028","9029","9030","9031","9032","9033","9034","9035","9036","9037","9038","9039","9040","9041","9042","9043","9044","9045","9046","9047","9048","9049","9050","9051","9052","9053","9054","9055","9056","9057","9058","9059","9060","9061","9062","9063","9064","9065","9066","9067","9068","9069","9070","9071","9072","9073","9074","9075","9076","9077","9078","9079","9080","9081","9082","9083","9084","9085","9086","9087","9088","9089","9090","9091","9092","9093","9094","9095","9096","9097","9098","9099","9100","9101","9102","9103","9104","9105","9106","9107","9108","9109","9110","9111","9112","9113","9114","9115","9116","9117","9118","9119","9120","9121","9122","9123","9124","9125","9126","9127","9128","9129","9130","9131","9132","9133","9134","9135","9136","9137","9138","9139","9140","9141","9142","9143","9144","9145","9146","9147","9148","9149","9150","9151","9152","9153","9154","9155","9156","9157","9158","9159","9160","9161","9162","9163","9164","9165","9166","9167","9168","9169","9170","9171","9172","9173","9174","9175","9176","9177","9178","9179","9180","9181","9182","9183","9184","9185","9186","9187","9188","9189","9190","9191","9192","9193","9194","9195","9196","9197","9198","9199","9200","9201","9202","9203","9204","9205","9206","9207","9208","9209","9210","9211","9212","9213","9214","9215","9216","9217","9218","9219","9220","9221","9222","9223","9224","9225","9226","9227","9228","9229","9230","9231","9232","9233","9234","9235","9236","9237","9238","9239","9240","9241","9242","9243","9244","9245","9246","9247","9248","9249","9250","9251","9252","9253","9254","9255","9256","9257","9258","9259","9260","9261","9262","9263","9264","9265","9266","9267","9268","9269","9270","9271","9272","9273","9274","9275","9276","9277","9278","9279","9280","9281","9282","9283","9284","9285","9286","9287","9288","9289","9290","9291","9292","9293","9294","9295","9296","9297","9298","9299","9300","9301","9302","9303","9304","9305","9306","9307","9308","9309","9310","9311","9312","9313","9314","9315","9316","9317","9318","9319","9320","9321","9322","9323","9324","9325","9326","9327","9328","9329","9330","9331","9332","9333","9334","9335","9336","9337","9338","9339","9340","9341","9342","9343","9344","9345","9346","9347","9348","9349","9350","9351","9352","9353","9354","9355","9356","9357","9358","9359","9360","9361","9362","9363","9364","9365","9366","9367","9368","9369","9370","9371","9372","9373","9374","9375","9376","9377","9378","9379","9380","9381","9382","9383","9384","9385","9386","9387","9388","9389","9390","9391","9392","9393","9394","9395","9396","9397","9398","9399","9400","9401","9402","9403","9404","9405","9406","9407","9408","9409","9410","9411","9412","9413","9414","9415","9416","9417","9418","9419","9420","9421","9422","9423","9424","9425","9426","9427","9428","9429","9430","9431","9432","9433","9434","9435","9436","9437","9438","9439","9440","9441","9442","9443","9444","9445","9446","9447","9448","9449","9450","9451","9452","9453","9454","9455","9456","9457","9458","9459","9460","9461","9462","9463","9464","9465","9466","9467","9468","9469","9470","9471","9472","9473","9474","9475","9476","9477","9478","9479","9480","9481","9482","9483","9484","9485","9486","9487","9488","9489","9490","9491","9492","9493","9494","9495","9496","9497","9498","9499","9500","9501","9502","9503","9504","9505","9506","9507","9508","9509","9510","9511","9512","9513","9514","9515","9516","9517","9518","9519","9520","9521","9522","9523","9524","9525","9526","9527","9528","9529","9530","9531","9532","9533","9534","9535","9536","9537","9538","9539","9540","9541","9542","9543","9544","9545","9546","9547","9548","9549","9550","9551","9552","9553","9554","9555","9556","9557","9558","9559","9560","9561","9562","9563","9564","9565","9566","9567","9568","9569","9570","9571","9572","9573","9574","9575","9576","9577","9578","9579","9580","9581","9582","9583","9584","9585","9586","9587","9588","9589","9590","9591","9592","9593","9594","9595","9596","9597","9598","9599","9600","9601","9602","9603","9604","9605","9606","9607","9608","9609","9610","9611","9612","9613","9614","9615","9616","9617","9618","9619","9620","9621","9622","9623","9624","9625","9626","9627","9628","9629","9630","9631","9632","9633","9634","9635","9636","9637","9638","9639","9640","9641","9642","9643","9644","9645","9646","9647","9648","9649","9650","9651","9652","9653","9654","9655","9656","9657","9658","9659","9660","9661","9662","9663","9664","9665","9666","9667","9668","9669","9670","9671","9672","9673","9674","9675","9676","9677","9678","9679","9680","9681","9682","9683","9684","9685","9686","9687","9688","9689","9690","9691","9692","9693","9694","9695","9696","9697","9698","9699","9700","9701","9702","9703","9704","9705","9706","9707","9708","9709","9710","9711","9712","9713","9714","9715","9716","9717","9718","9719","9720","9721","9722","9723","9724","9725","9726","9727","9728","9729","9730","9731","9732","9733","9734","9735","9736","9737","9738","9739","9740","9741","9742","9743","9744","9745","9746","9747","9748","9749","9750","9751","9752","9753","9754","9755","9756","9757","9758","9759","9760","9761","9762","9763","9764","9765","9766","9767","9768","9769","9770","9771","9772","9773","9774","9775","9776","9777","9778","9779","9780","9781","9782","9783","9784","9785","9786","9787","9788","9789","9790","9791","9792","9793","9794","9795","9796","9797","9798","9799","9800","9801","9802","9803","9804","9805","9806","9807","9808","9809","9810","9811","9812","9813","9814","9815","9816","9817","9818","9819","9820","9821","9822","9823","9824","9825","9826","9827","9828","9829","9830","9831","9832","9833","9834","9835","9836","9837","9838","9839","9840","9841","9842","9843","9844","9845","9846","9847","9848","9849","9850","9851","9852","9853","9854","9855","9856","9857","9858","9859","9860","9861","9862","9863","9864","9865","9866","9867","9868","9869","9870","9871","9872","9873","9874","9875","9876","9877","9878","9879","9880","9881","9882","9883","9884","9885","9886","9887","9888","9889","9890","9891","9892","9893","9894","9895","9896","9897","9898","9899","9900","9901","9902","9903","9904","9905","9906","9907","9908","9909","9910","9911","9912","9913","9914","9915","9916","9917","9918","9919","9920","9921","9922","9923","9924","9925","9926","9927","9928","9929","9930","9931","9932","9933","9934","9935","9936","9937","9938","9939","9940","9941","9942","9943","9944","9945","9946","9947","9948","9949","9950","9951","9952","9953","9954","9955","9956","9957","9958","9959","9960","9961","9962","9963","9964","9965","9966","9967","9968","9969","9970","9971","9972","9973","9974","9975","9976","9977","9978","9979","9980","9981","9982","9983","9984","9985","9986","9987","9988","9989","9990","9991","9992","9993","9994","9995","9996","9997","9998","9999","10000","10001","10002","10003","10004","10005","10006","10007","10008","10009","10010","10011","10012","10013","10014","10015","10016","10017","10018","10019","10020","10021","10022","10023","10024","10025","10026","10027","10028","10029","10030","10031","10032","10033","10034","10035","10036","10037","10038","10039","10040","10041","10042","10043","10044","10045","10046","10047","10048","10049","10050","10051","10052","10053","10054","10055","10056","10057","10058","10059","10060","10061","10062","10063","10064","10065","10066","10067","10068","10069","10070","10071","10072","10073","10074","10075","10076","10077","10078","10079","10080","10081","10082","10083","10084","10085","10086","10087","10088","10089","10090","10091","10092","10093","10094","10095","10096","10097","10098","10099","10100","10101","10102","10103","10104","10105","10106","10107","10108","10109","10110","10111","10112","10113","10114","10115","10116","10117","10118","10119","10120","10121","10122","10123","10124","10125","10126","10127","10128","10129","10130","10131","10132","10133","10134","10135","10136","10137","10138","10139","10140","10141","10142","10143","10144","10145","10146","10147","10148","10149","10150","10151","10152","10153","10154","10155","10156","10157","10158","10159","10160","10161","10162","10163","10164","10165","10166","10167","10168","10169","10170","10171","10172","10173","10174","10175","10176","10177","10178","10179","10180","10181","10182","10183","10184","10185","10186","10187","10188","10189","10190","10191","10192","10193","10194","10195","10196","10197","10198","10199","10200","10201","10202","10203","10204","10205","10206","10207","10208","10209","10210","10211","10212","10213","10214","10215","10216","10217","10218","10219","10220","10221","10222","10223","10224","10225","10226","10227","10228","10229","10230","10231","10232","10233","10234","10235","10236","10237","10238","10239","10240","10241","10242","10243","10244","10245","10246","10247","10248","10249","10250","10251","10252","10253","10254","10255","10256","10257","10258","10259","10260","10261","10262","10263","10264","10265","10266","10267","10268","10269","10270","10271","10272","10273","10274","10275","10276","10277","10278","10279","10280","10281","10282","10283","10284","10285","10286","10287","10288","10289","10290","10291","10292","10293","10294","10295","10296","10297","10298","10299","10300","10301","10302","10303","10304","10305","10306","10307","10308","10309","10310","10311","10312","10313","10314","10315","10316","10317","10318","10319","10320","10321","10322","10323","10324","10325","10326","10327","10328","10329","10330","10331","10332","10333","10334","10335","10336","10337","10338","10339","10340","10341","10342","10343","10344","10345","10346","10347","10348","10349","10350","10351","10352","10353","10354","10355","10356","10357","10358","10359","10360","10361","10362","10363","10364","10365","10366","10367","10368","10369","10370","10371","10372","10373","10374","10375","10376","10377","10378","10379","10380","10381","10382","10383","10384","10385","10386","10387","10388","10389","10390","10391","10392","10393","10394","10395","10396","10397","10398","10399","10400","10401","10402","10403","10404","10405","10406","10407","10408","10409","10410","10411","10412","10413","10414","10415","10416","10417","10418","10419","10420","10421","10422","10423","10424","10425","10426","10427","10428","10429","10430","10431","10432","10433","10434","10435","10436","10437","10438","10439","10440","10441","10442","10443","10444","10445","10446","10447","10448","10449","10450","10451","10452","10453","10454","10455","10456","10457","10458","10459","10460","10461","10462","10463","10464","10465","10466","10467","10468","10469","10470","10471","10472","10473","10474","10475","10476","10477","10478","10479","10480","10481","10482","10483","10484","10485","10486","10487","10488","10489","10490","10491","10492","10493","10494","10495","10496","10497","10498","10499","10500","10501","10502","10503","10504","10505","10506","10507","10508","10509","10510","10511","10512","10513","10514","10515","10516","10517","10518","10519","10520","10521","10522","10523","10524","10525","10526","10527","10528","10529","10530","10531","10532","10533","10534","10535","10536","10537","10538","10539","10540","10541","10542","10543","10544","10545","10546","10547","10548","10549","10550","10551","10552","10553","10554","10555","10556","10557","10558","10559","10560","10561","10562","10563","10564","10565","10566","10567","10568","10569","10570","10571","10572","10573","10574","10575","10576","10577","10578","10579","10580","10581","10582","10583","10584","10585","10586","10587","10588","10589","10590","10591","10592","10593","10594","10595","10596","10597","10598","10599","10600","10601","10602","10603","10604","10605","10606","10607","10608","10609","10610","10611","10612","10613","10614","10615","10616","10617","10618","10619","10620","10621","10622","10623","10624","10625","10626","10627","10628","10629","10630","10631","10632","10633","10634","10635","10636","10637","10638","10639","10640","10641","10642","10643","10644","10645","10646","10647","10648","10649","10650","10651","10652","10653","10654","10655","10656","10657","10658","10659","10660","10661","10662","10663","10664","10665","10666","10667","10668","10669","10670","10671","10672","10673","10674","10675","10676","10677","10678","10679","10680","10681","10682","10683","10684","10685","10686","10687","10688","10689","10690","10691","10692","10693","10694","10695","10696","10697","10698","10699","10700","10701","10702","10703","10704","10705","10706","10707","10708","10709","10710","10711","10712","10713","10714","10715","10716","10717","10718","10719","10720","10721","10722","10723","10724","10725","10726","10727","10728","10729","10730","10731","10732","10733","10734","10735","10736","10737","10738","10739","10740","10741","10742","10743","10744","10745","10746","10747","10748","10749","10750","10751","10752","10753","10754","10755","10756","10757","10758","10759","10760","10761","10762","10763","10764","10765","10766","10767","10768","10769","10770","10771","10772","10773","10774","10775","10776","10777","10778","10779","10780","10781","10782","10783","10784","10785","10786","10787","10788","10789","10790","10791","10792","10793","10794","10795","10796","10797","10798","10799","10800","10801","10802","10803","10804","10805","10806","10807","10808","10809","10810","10811","10812","10813","10814","10815","10816","10817","10818","10819","10820","10821","10822","10823","10824","10825","10826","10827","10828","10829","10830","10831","10832","10833","10834","10835","10836","10837","10838","10839","10840","10841","10842","10843","10844","10845","10846","10847","10848","10849","10850","10851","10852","10853","10854","10855","10856","10857","10858","10859","10860","10861","10862","10863","10864","10865","10866","10867","10868","10869","10870","10871","10872","10873","10874","10875","10876","10877","10878","10879","10880","10881","10882","10883","10884","10885","10886","10887","10888","10889","10890","10891","10892","10893","10894","10895","10896","10897","10898","10899","10900","10901","10902","10903","10904","10905","10906","10907","10908","10909","10910","10911","10912","10913","10914","10915","10916","10917","10918","10919","10920","10921","10922","10923","10924","10925","10926","10927","10928","10929","10930","10931","10932","10933","10934","10935","10936","10937","10938","10939","10940","10941","10942","10943","10944","10945","10946","10947","10948","10949","10950","10951","10952","10953","10954","10955","10956","10957","10958","10959","10960","10961","10962","10963","10964","10965","10966","10967","10968","10969","10970","10971","10972","10973","10974","10975","10976","10977","10978","10979","10980","10981","10982","10983","10984","10985","10986","10987","10988","10989","10990","10991","10992","10993","10994","10995","10996","10997","10998","10999","11000","11001","11002","11003","11004","11005","11006","11007","11008","11009","11010","11011","11012","11013","11014","11015","11016","11017","11018","11019","11020","11021","11022","11023","11024","11025","11026","11027","11028","11029","11030","11031","11032","11033","11034","11035","11036","11037","11038","11039","11040","11041","11042","11043","11044","11045","11046","11047","11048","11049","11050","11051","11052","11053","11054","11055","11056","11057","11058","11059","11060","11061","11062","11063","11064","11065","11066","11067","11068","11069","11070","11071","11072","11073","11074","11075","11076","11077","11078","11079","11080","11081","11082","11083","11084","11085","11086","11087","11088","11089","11090","11091","11092","11093","11094","11095","11096","11097","11098","11099","11100","11101","11102","11103","11104","11105","11106","11107","11108","11109","11110","11111","11112","11113","11114","11115","11116","11117","11118","11119","11120","11121","11122","11123","11124","11125","11126","11127","11128","11129","11130","11131","11132","11133","11134","11135","11136","11137","11138","11139","11140","11141","11142","11143","11144","11145","11146","11147","11148","11149","11150","11151","11152","11153","11154","11155","11156","11157","11158","11159","11160","11161","11162","11163","11164","11165","11166","11167","11168","11169","11170","11171","11172","11173","11174","11175","11176","11177","11178","11179","11180","11181","11182","11183","11184","11185","11186","11187","11188","11189","11190","11191","11192","11193","11194","11195","11196","11197","11198","11199","11200","11201","11202","11203","11204","11205","11206","11207","11208","11209","11210","11211","11212","11213","11214","11215","11216","11217","11218","11219","11220","11221","11222","11223","11224","11225","11226","11227","11228","11229","11230","11231","11232","11233","11234","11235","11236","11237","11238","11239","11240","11241","11242","11243","11244","11245","11246","11247","11248","11249","11250","11251","11252","11253","11254","11255","11256","11257","11258","11259","11260","11261","11262","11263","11264","11265","11266","11267","11268","11269","11270","11271","11272","11273","11274","11275","11276","11277","11278","11279","11280","11281","11282","11283","11284","11285","11286","11287","11288","11289","11290","11291","11292","11293","11294","11295","11296","11297","11298","11299","11300","11301","11302","11303","11304","11305","11306","11307","11308","11309","11310","11311","11312","11313","11314","11315","11316","11317","11318","11319","11320","11321","11322","11323","11324","11325","11326","11327","11328","11329","11330","11331","11332","11333","11334","11335","11336","11337","11338","11339","11340","11341","11342","11343","11344","11345","11346","11347","11348","11349","11350","11351","11352","11353","11354","11355","11356","11357","11358","11359","11360","11361","11362","11363","11364","11365","11366","11367","11368","11369","11370","11371","11372","11373","11374","11375","11376","11377","11378","11379","11380","11381","11382","11383","11384","11385","11386","11387","11388","11389","11390","11391","11392","11393","11394","11395","11396","11397","11398","11399","11400","11401","11402","11403","11404","11405","11406","11407","11408","11409","11410","11411","11412","11413","11414","11415","11416","11417","11418","11419","11420","11421","11422","11423","11424","11425","11426","11427","11428","11429","11430","11431","11432","11433","11434","11435","11436","11437","11438","11439","11440","11441","11442","11443","11444","11445","11446","11447","11448","11449","11450","11451","11452","11453","11454","11455","11456","11457","11458","11459","11460","11461","11462","11463","11464","11465","11466","11467","11468","11469","11470","11471","11472","11473","11474","11475","11476","11477","11478","11479","11480","11481","11482","11483","11484","11485","11486","11487","11488","11489","11490","11491","11492","11493","11494","11495","11496","11497","11498","11499","11500","11501","11502","11503","11504","11505","11506","11507","11508","11509","11510","11511","11512","11513","11514","11515","11516","11517","11518","11519","11520","11521","11522","11523","11524","11525","11526","11527","11528","11529","11530","11531","11532","11533","11534","11535","11536","11537","11538","11539","11540","11541","11542","11543","11544","11545","11546","11547","11548","11549","11550","11551","11552","11553","11554","11555","11556","11557","11558","11559","11560","11561","11562","11563","11564","11565","11566","11567","11568","11569","11570","11571","11572","11573","11574","11575","11576","11577","11578","11579","11580","11581","11582","11583","11584","11585","11586","11587","11588","11589","11590","11591","11592","11593","11594","11595","11596","11597","11598","11599","11600","11601","11602","11603","11604","11605","11606","11607","11608","11609","11610","11611","11612","11613","11614","11615","11616","11617","11618","11619","11620","11621","11622","11623","11624","11625","11626","11627","11628","11629","11630","11631","11632","11633","11634","11635","11636","11637","11638","11639","11640","11641","11642","11643","11644","11645","11646","11647","11648","11649","11650","11651","11652","11653","11654","11655","11656","11657","11658","11659","11660","11661","11662","11663","11664","11665","11666","11667","11668","11669","11670","11671","11672","11673","11674","11675","11676","11677","11678","11679","11680","11681","11682","11683","11684","11685","11686","11687","11688","11689","11690","11691","11692","11693","11694","11695","11696","11697","11698","11699","11700","11701","11702","11703","11704","11705","11706","11707","11708","11709","11710","11711","11712","11713","11714","11715","11716","11717","11718","11719","11720","11721","11722","11723","11724","11725","11726","11727","11728","11729","11730","11731","11732","11733","11734","11735","11736","11737","11738","11739","11740","11741","11742","11743","11744","11745","11746","11747","11748","11749","11750","11751","11752","11753","11754","11755","11756","11757","11758","11759","11760","11761","11762","11763","11764","11765","11766","11767","11768","11769","11770","11771","11772","11773","11774","11775","11776","11777","11778","11779","11780","11781","11782","11783","11784","11785","11786","11787","11788","11789","11790","11791","11792","11793","11794","11795","11796","11797","11798","11799","11800","11801","11802","11803","11804","11805","11806","11807","11808","11809","11810","11811","11812","11813","11814","11815","11816","11817","11818","11819","11820","11821","11822","11823","11824","11825","11826","11827","11828","11829","11830","11831","11832","11833","11834","11835","11836","11837","11838","11839","11840","11841","11842","11843","11844","11845","11846","11847","11848","11849","11850","11851","11852","11853","11854","11855","11856","11857","11858","11859","11860","11861","11862","11863","11864","11865","11866","11867","11868","11869","11870","11871","11872","11873","11874","11875","11876","11877","11878","11879","11880","11881","11882","11883","11884","11885","11886","11887","11888","11889","11890","11891","11892","11893","11894","11895","11896","11897","11898","11899","11900","11901","11902","11903","11904","11905","11906","11907","11908","11909","11910","11911","11912","11913","11914","11915","11916","11917","11918","11919","11920","11921","11922","11923","11924","11925","11926","11927","11928","11929","11930","11931","11932","11933","11934","11935","11936","11937","11938","11939","11940","11941","11942","11943","11944","11945","11946","11947","11948","11949","11950","11951","11952","11953","11954","11955","11956","11957","11958","11959","11960","11961","11962","11963","11964","11965","11966","11967","11968","11969","11970","11971","11972","11973","11974","11975","11976","11977","11978","11979","11980","11981","11982","11983","11984","11985","11986","11987","11988","11989","11990","11991","11992","11993","11994","11995","11996","11997","11998","11999","12000","12001","12002","12003","12004","12005","12006","12007","12008","12009","12010","12011","12012","12013","12014","12015","12016","12017","12018","12019","12020","12021","12022","12023","12024","12025","12026","12027","12028","12029","12030","12031","12032","12033","12034","12035","12036","12037","12038","12039","12040","12041","12042","12043","12044","12045","12046","12047","12048","12049","12050","12051","12052","12053","12054","12055","12056","12057","12058","12059","12060","12061","12062","12063","12064","12065","12066","12067","12068","12069","12070","12071","12072","12073","12074","12075","12076","12077","12078","12079","12080","12081","12082","12083","12084","12085","12086","12087","12088","12089","12090","12091","12092","12093","12094","12095","12096","12097","12098","12099","12100","12101","12102","12103","12104","12105","12106","12107","12108","12109","12110","12111","12112","12113","12114","12115","12116","12117","12118","12119","12120","12121","12122","12123","12124","12125","12126","12127","12128","12129","12130","12131","12132","12133","12134","12135","12136","12137","12138","12139","12140","12141","12142","12143","12144","12145","12146","12147","12148","12149","12150","12151","12152","12153","12154","12155","12156","12157","12158","12159","12160","12161","12162","12163","12164","12165","12166","12167","12168","12169","12170","12171","12172","12173","12174","12175","12176","12177","12178","12179","12180","12181","12182","12183","12184","12185","12186","12187","12188","12189","12190","12191","12192","12193","12194","12195","12196","12197","12198","12199","12200","12201","12202","12203","12204","12205","12206","12207","12208","12209","12210","12211","12212","12213","12214","12215","12216","12217","12218","12219","12220","12221","12222","12223","12224","12225","12226","12227","12228","12229","12230","12231","12232","12233","12234","12235","12236","12237","12238","12239","12240","12241","12242","12243","12244","12245","12246","12247","12248","12249","12250","12251","12252","12253","12254","12255","12256","12257","12258","12259","12260","12261","12262","12263","12264","12265","12266","12267","12268","12269","12270","12271","12272","12273","12274","12275","12276","12277","12278","12279","12280","12281","12282","12283","12284","12285","12286","12287","12288","12289","12290","12291","12292","12293","12294","12295","12296","12297","12298","12299","12300","12301","12302","12303","12304","12305","12306","12307","12308","12309","12310","12311","12312","12313","12314","12315","12316","12317","12318","12319","12320","12321","12322","12323","12324","12325","12326","12327","12328","12329","12330","12331","12332","12333","12334","12335","12336","12337","12338","12339","12340","12341","12342","12343","12344","12345","12346","12347","12348","12349","12350","12351","12352","12353","12354","12355","12356","12357","12358","12359","12360","12361","12362","12363","12364","12365","12366","12367","12368","12369","12370","12371","12372","12373","12374","12375","12376","12377","12378","12379","12380","12381","12382","12383","12384","12385","12386","12387","12388","12389","12390","12391","12392","12393","12394","12395","12396","12397","12398","12399","12400","12401","12402","12403","12404","12405","12406","12407","12408","12409","12410","12411","12412","12413","12414","12415","12416","12417","12418","12419","12420","12421","12422","12423","12424","12425","12426","12427","12428","12429","12430","12431","12432","12433","12434","12435","12436","12437","12438","12439","12440","12441","12442","12443","12444","12445","12446","12447","12448","12449","12450","12451","12452","12453","12454","12455","12456","12457","12458","12459","12460","12461","12462","12463","12464","12465","12466","12467","12468","12469","12470","12471","12472","12473","12474","12475","12476","12477","12478","12479","12480","12481","12482","12483","12484","12485","12486","12487","12488","12489","12490","12491","12492","12493","12494","12495","12496","12497","12498","12499","12500","12501","12502","12503","12504","12505","12506","12507","12508","12509","12510","12511","12512","12513","12514","12515","12516","12517","12518","12519","12520","12521","12522","12523","12524","12525","12526","12527","12528","12529","12530","12531","12532","12533","12534","12535","12536","12537","12538","12539","12540","12541","12542","12543","12544","12545","12546","12547","12548","12549","12550","12551","12552","12553","12554","12555","12556","12557","12558","12559","12560","12561","12562","12563","12564","12565","12566","12567","12568","12569","12570","12571","12572","12573","12574","12575","12576","12577","12578","12579","12580","12581","12582","12583","12584","12585","12586","12587","12588","12589","12590","12591","12592","12593","12594","12595","12596","12597","12598","12599","12600","12601","12602","12603","12604","12605","12606","12607","12608","12609","12610","12611","12612","12613","12614","12615","12616","12617","12618","12619","12620","12621","12622","12623","12624","12625","12626","12627","12628","12629","12630","12631","12632","12633","12634","12635","12636","12637","12638","12639","12640","12641","12642","12643","12644","12645","12646","12647","12648","12649","12650","12651","12652","12653","12654","12655","12656","12657","12658","12659","12660","12661","12662","12663","12664","12665","12666","12667","12668","12669","12670","12671","12672","12673","12674","12675","12676","12677","12678","12679","12680","12681","12682","12683","12684","12685","12686","12687","12688","12689","12690","12691","12692","12693","12694","12695","12696","12697","12698","12699","12700","12701","12702","12703","12704","12705","12706","12707","12708","12709","12710","12711","12712","12713","12714","12715","12716","12717","12718","12719","12720","12721","12722","12723","12724","12725","12726","12727","12728","12729","12730","12731","12732","12733","12734","12735","12736","12737","12738","12739","12740","12741","12742","12743","12744","12745","12746","12747","12748","12749","12750","12751","12752","12753","12754","12755","12756","12757","12758","12759","12760","12761","12762","12763","12764","12765","12766","12767","12768","12769","12770","12771","12772","12773","12774","12775","12776","12777","12778","12779","12780","12781","12782","12783","12784","12785","12786","12787","12788","12789","12790","12791","12792","12793","12794","12795","12796","12797","12798","12799","12800","12801","12802","12803","12804","12805","12806","12807","12808","12809","12810","12811","12812","12813","12814","12815","12816","12817","12818","12819","12820","12821","12822","12823","12824","12825","12826","12827","12828","12829","12830","12831","12832","12833","12834","12835","12836","12837","12838","12839","12840","12841","12842","12843","12844","12845","12846","12847","12848","12849","12850","12851","12852","12853","12854","12855","12856","12857","12858","12859","12860","12861","12862","12863","12864","12865","12866","12867","12868","12869","12870","12871","12872","12873","12874","12875","12876","12877","12878","12879","12880","12881","12882","12883","12884","12885","12886","12887","12888","12889","12890","12891","12892","12893","12894","12895","12896","12897","12898","12899","12900","12901","12902","12903","12904","12905","12906","12907","12908","12909","12910","12911","12912","12913","12914","12915","12916","12917","12918","12919","12920","12921","12922","12923","12924","12925","12926","12927","12928","12929","12930","12931","12932","12933","12934","12935","12936","12937","12938","12939","12940","12941","12942","12943","12944","12945","12946","12947","12948","12949","12950","12951","12952","12953","12954","12955","12956","12957","12958","12959","12960","12961","12962","12963","12964","12965","12966","12967","12968","12969","12970","12971","12972","12973","12974","12975","12976","12977","12978","12979","12980","12981","12982","12983","12984","12985","12986","12987","12988","12989","12990","12991","12992","12993","12994","12995","12996","12997","12998","12999","13000","13001","13002","13003","13004","13005","13006","13007","13008","13009","13010","13011","13012","13013","13014","13015","13016","13017","13018","13019","13020","13021","13022","13023","13024","13025","13026","13027","13028","13029","13030","13031","13032","13033","13034","13035","13036","13037","13038","13039","13040","13041","13042","13043","13044","13045","13046","13047","13048","13049","13050","13051","13052","13053","13054","13055","13056","13057","13058","13059","13060","13061","13062","13063","13064","13065","13066","13067","13068","13069","13070","13071","13072","13073","13074","13075","13076","13077","13078","13079","13080","13081","13082","13083","13084","13085","13086","13087","13088","13089","13090","13091","13092","13093","13094","13095","13096","13097","13098","13099","13100","13101","13102","13103","13104","13105","13106","13107","13108","13109","13110","13111","13112","13113","13114","13115","13116","13117","13118","13119","13120","13121","13122","13123","13124","13125","13126","13127","13128","13129","13130","13131","13132","13133","13134","13135","13136","13137","13138","13139","13140","13141","13142","13143","13144","13145","13146","13147","13148","13149","13150","13151","13152","13153","13154","13155","13156","13157","13158","13159","13160","13161","13162","13163","13164","13165","13166","13167","13168","13169","13170","13171","13172","13173","13174","13175","13176","13177","13178","13179","13180","13181","13182","13183","13184","13185","13186","13187","13188","13189","13190","13191","13192","13193","13194","13195","13196","13197","13198","13199","13200","13201","13202","13203","13204","13205","13206","13207","13208","13209","13210","13211","13212","13213","13214","13215","13216","13217","13218","13219","13220","13221","13222","13223","13224","13225","13226","13227","13228","13229","13230","13231","13232","13233","13234","13235","13236","13237","13238","13239","13240","13241","13242","13243","13244","13245","13246","13247","13248","13249","13250","13251","13252","13253","13254","13255","13256","13257","13258","13259","13260","13261","13262","13263","13264","13265","13266","13267","13268","13269","13270","13271","13272","13273","13274","13275","13276","13277","13278","13279","13280","13281","13282","13283","13284","13285","13286","13287","13288","13289","13290","13291","13292","13293","13294","13295","13296","13297","13298","13299","13300","13301","13302","13303","13304","13305","13306","13307","13308","13309","13310","13311","13312","13313","13314","13315","13316","13317","13318","13319","13320","13321","13322","13323","13324","13325","13326","13327","13328","13329","13330","13331","13332","13333","13334","13335","13336","13337","13338","13339","13340","13341","13342","13343","13344","13345","13346","13347","13348","13349","13350","13351","13352","13353","13354","13355","13356","13357","13358","13359","13360","13361","13362","13363","13364","13365","13366","13367","13368","13369","13370","13371","13372","13373","13374","13375","13376","13377","13378","13379","13380","13381","13382","13383","13384","13385","13386","13387","13388","13389","13390","13391","13392","13393","13394","13395","13396","13397","13398","13399","13400","13401","13402","13403","13404","13405","13406","13407","13408","13409","13410","13411","13412","13413","13414","13415","13416","13417","13418","13419","13420","13421","13422","13423","13424","13425","13426","13427","13428","13429","13430","13431","13432","13433","13434","13435","13436","13437","13438","13439","13440","13441","13442","13443","13444","13445","13446","13447","13448","13449","13450","13451","13452","13453","13454","13455","13456","13457","13458","13459","13460","13461","13462","13463","13464","13465","13466","13467","13468","13469","13470","13471","13472","13473","13474","13475","13476","13477","13478","13479","13480","13481","13482","13483","13484","13485","13486","13487","13488","13489","13490","13491","13492","13493","13494","13495","13496","13497","13498","13499","13500","13501","13502","13503","13504","13505","13506","13507","13508","13509","13510","13511","13512","13513","13514","13515","13516","13517","13518","13519","13520","13521","13522","13523","13524","13525","13526","13527","13528","13529","13530","13531","13532","13533","13534","13535","13536","13537","13538","13539","13540","13541","13542","13543","13544","13545","13546","13547","13548","13549","13550","13551","13552","13553","13554","13555","13556","13557","13558","13559","13560","13561","13562","13563","13564","13565","13566","13567","13568","13569","13570","13571","13572","13573","13574","13575","13576","13577","13578","13579","13580","13581","13582","13583","13584","13585","13586","13587","13588","13589","13590","13591","13592","13593","13594","13595","13596","13597","13598","13599","13600","13601","13602","13603","13604","13605","13606","13607","13608","13609","13610","13611","13612","13613","13614","13615","13616","13617","13618","13619","13620","13621","13622","13623","13624","13625","13626","13627","13628","13629","13630","13631","13632","13633","13634","13635","13636","13637","13638","13639","13640","13641","13642","13643","13644","13645","13646","13647","13648","13649","13650","13651","13652","13653","13654","13655","13656","13657","13658","13659","13660","13661","13662","13663","13664","13665","13666","13667","13668","13669","13670","13671","13672","13673","13674","13675","13676","13677","13678","13679","13680","13681","13682","13683","13684","13685","13686","13687","13688","13689","13690","13691","13692","13693","13694","13695","13696","13697","13698","13699","13700","13701","13702","13703","13704","13705","13706","13707","13708","13709","13710","13711","13712","13713","13714","13715","13716","13717","13718","13719","13720","13721","13722","13723","13724","13725","13726","13727","13728","13729","13730","13731","13732","13733","13734","13735","13736","13737","13738","13739","13740","13741","13742","13743","13744","13745","13746","13747","13748","13749","13750","13751","13752","13753","13754","13755","13756","13757","13758","13759","13760","13761","13762","13763","13764","13765","13766","13767","13768","13769","13770","13771","13772","13773","13774","13775","13776","13777","13778","13779","13780","13781","13782","13783","13784","13785","13786","13787","13788","13789","13790","13791","13792","13793","13794","13795","13796","13797","13798","13799","13800","13801","13802","13803","13804","13805","13806","13807","13808","13809","13810","13811","13812","13813","13814","13815","13816","13817","13818","13819","13820","13821","13822","13823","13824","13825","13826","13827","13828","13829","13830","13831","13832","13833","13834","13835","13836","13837","13838","13839","13840","13841","13842","13843","13844","13845","13846","13847","13848","13849","13850","13851","13852","13853","13854","13855","13856","13857","13858","13859","13860","13861","13862","13863","13864","13865","13866","13867","13868","13869","13870","13871","13872","13873","13874","13875","13876","13877","13878","13879","13880","13881","13882","13883","13884","13885","13886","13887","13888","13889","13890","13891","13892","13893","13894","13895","13896","13897","13898","13899","13900","13901","13902","13903","13904","13905","13906","13907","13908","13909","13910","13911","13912","13913","13914","13915","13916","13917","13918","13919","13920","13921","13922","13923","13924","13925","13926","13927","13928","13929","13930","13931","13932","13933","13934","13935","13936","13937","13938","13939","13940","13941","13942","13943","13944","13945","13946","13947","13948","13949","13950","13951","13952","13953","13954","13955","13956","13957","13958","13959","13960","13961","13962","13963","13964","13965","13966","13967","13968","13969","13970","13971","13972","13973","13974","13975","13976","13977","13978","13979","13980","13981","13982","13983","13984","13985","13986","13987","13988","13989","13990","13991","13992","13993","13994","13995","13996","13997","13998","13999","14000","14001","14002","14003","14004","14005","14006","14007","14008","14009","14010","14011","14012","14013","14014","14015","14016","14017","14018","14019","14020","14021","14022","14023","14024","14025","14026","14027","14028","14029","14030","14031","14032","14033","14034","14035","14036","14037","14038","14039","14040","14041","14042","14043","14044","14045","14046","14047","14048","14049","14050","14051","14052","14053","14054","14055","14056","14057","14058","14059","14060","14061","14062","14063","14064","14065","14066","14067","14068","14069","14070","14071","14072","14073","14074","14075","14076","14077","14078","14079","14080","14081","14082","14083","14084","14085","14086","14087","14088","14089","14090","14091","14092","14093","14094","14095","14096","14097","14098","14099","14100","14101","14102","14103","14104","14105","14106","14107","14108","14109","14110","14111","14112","14113","14114","14115","14116","14117","14118","14119","14120","14121","14122","14123","14124","14125","14126","14127","14128","14129","14130","14131","14132","14133","14134","14135","14136","14137","14138","14139","14140","14141","14142","14143","14144","14145","14146","14147","14148","14149","14150","14151","14152","14153","14154","14155","14156","14157","14158","14159","14160","14161","14162","14163","14164","14165","14166","14167","14168","14169","14170","14171","14172","14173","14174","14175","14176","14177","14178","14179","14180","14181","14182","14183","14184","14185","14186","14187","14188","14189","14190","14191","14192","14193","14194","14195","14196","14197","14198","14199","14200","14201","14202","14203","14204","14205","14206","14207","14208","14209","14210","14211","14212","14213","14214","14215","14216","14217","14218","14219","14220","14221","14222","14223","14224","14225","14226","14227","14228","14229","14230","14231","14232","14233","14234","14235","14236","14237","14238","14239","14240","14241","14242","14243","14244","14245","14246","14247","14248","14249","14250","14251","14252","14253","14254","14255","14256","14257","14258","14259","14260","14261","14262","14263","14264","14265","14266","14267","14268","14269","14270","14271","14272","14273","14274","14275","14276","14277","14278","14279","14280","14281","14282","14283","14284","14285","14286","14287","14288","14289","14290","14291","14292","14293","14294","14295","14296","14297","14298","14299","14300","14301","14302","14303","14304","14305","14306","14307","14308","14309","14310","14311","14312","14313","14314","14315","14316","14317","14318","14319","14320","14321","14322","14323","14324","14325","14326","14327","14328","14329","14330","14331","14332","14333","14334","14335","14336","14337","14338","14339","14340","14341","14342","14343","14344","14345","14346","14347","14348","14349","14350","14351","14352","14353","14354","14355","14356","14357","14358","14359","14360","14361","14362","14363","14364","14365","14366","14367","14368","14369","14370","14371","14372","14373","14374","14375","14376","14377","14378","14379","14380","14381","14382","14383","14384","14385","14386","14387","14388","14389","14390","14391","14392","14393","14394","14395","14396","14397","14398","14399","14400","14401","14402","14403","14404","14405","14406","14407","14408","14409","14410","14411","14412","14413","14414","14415","14416","14417","14418","14419","14420","14421","14422","14423","14424","14425","14426","14427","14428","14429","14430","14431","14432","14433","14434","14435","14436","14437","14438","14439","14440","14441","14442","14443","14444","14445","14446","14447","14448","14449","14450","14451","14452","14453","14454","14455","14456","14457","14458","14459","14460","14461","14462","14463","14464","14465","14466","14467","14468","14469","14470","14471","14472","14473","14474","14475","14476","14477","14478","14479","14480","14481","14482","14483","14484","14485","14486","14487","14488","14489","14490","14491","14492","14493","14494","14495","14496","14497","14498","14499","14500","14501","14502","14503","14504","14505","14506","14507","14508","14509","14510","14511","14512","14513","14514","14515","14516","14517","14518","14519","14520","14521","14522","14523","14524","14525","14526","14527","14528","14529","14530","14531","14532","14533","14534","14535","14536","14537","14538","14539","14540","14541","14542","14543","14544","14545","14546","14547","14548","14549","14550","14551","14552","14553","14554","14555","14556","14557","14558","14559","14560","14561","14562","14563","14564","14565","14566","14567","14568","14569","14570","14571","14572","14573","14574","14575","14576","14577","14578","14579","14580","14581","14582","14583","14584","14585","14586","14587","14588","14589","14590","14591","14592","14593","14594","14595","14596","14597","14598","14599","14600","14601","14602","14603","14604","14605","14606","14607","14608","14609","14610","14611","14612","14613","14614","14615","14616","14617","14618","14619","14620","14621","14622","14623","14624","14625","14626","14627","14628","14629","14630","14631","14632","14633","14634","14635","14636","14637","14638","14639","14640","14641","14642","14643","14644","14645","14646","14647","14648","14649","14650","14651","14652","14653","14654","14655","14656","14657","14658","14659","14660","14661","14662","14663","14664","14665","14666","14667","14668","14669","14670","14671","14672","14673","14674","14675","14676","14677","14678","14679","14680","14681","14682","14683","14684","14685","14686","14687","14688","14689","14690","14691","14692","14693","14694","14695","14696","14697","14698","14699","14700","14701","14702","14703","14704","14705","14706","14707","14708","14709","14710","14711","14712","14713","14714","14715","14716","14717","14718","14719","14720","14721","14722","14723","14724","14725","14726","14727","14728","14729","14730","14731","14732","14733","14734","14735","14736","14737","14738","14739","14740","14741","14742","14743","14744","14745","14746","14747","14748","14749","14750","14751","14752","14753","14754","14755","14756","14757","14758","14759","14760","14761","14762","14763","14764","14765","14766","14767","14768","14769","14770","14771","14772","14773","14774","14775","14776","14777","14778","14779","14780","14781","14782","14783","14784","14785","14786","14787","14788","14789","14790","14791","14792","14793","14794","14795","14796","14797","14798","14799","14800","14801","14802","14803","14804","14805","14806","14807","14808","14809","14810","14811","14812","14813","14814","14815","14816","14817","14818","14819","14820","14821","14822","14823","14824","14825","14826","14827","14828","14829","14830","14831","14832","14833","14834","14835","14836","14837","14838","14839","14840","14841","14842","14843","14844","14845","14846","14847","14848","14849","14850","14851","14852","14853","14854","14855","14856","14857","14858","14859","14860","14861","14862","14863","14864","14865","14866","14867","14868","14869","14870","14871","14872","14873","14874","14875","14876","14877","14878","14879","14880","14881","14882","14883","14884","14885","14886","14887","14888","14889","14890","14891","14892","14893","14894","14895","14896","14897","14898","14899","14900","14901","14902","14903","14904","14905","14906","14907","14908","14909","14910","14911","14912","14913","14914","14915","14916","14917","14918","14919","14920","14921","14922","14923","14924","14925","14926","14927","14928","14929","14930","14931","14932","14933","14934","14935","14936","14937","14938","14939","14940","14941","14942","14943","14944","14945","14946","14947","14948","14949","14950","14951","14952","14953","14954","14955","14956","14957","14958","14959","14960","14961","14962","14963","14964","14965","14966","14967","14968","14969","14970","14971","14972","14973","14974","14975","14976","14977","14978","14979","14980","14981","14982","14983","14984","14985","14986","14987","14988","14989","14990","14991","14992","14993","14994","14995","14996","14997","14998","14999","15000","15001","15002","15003","15004","15005","15006","15007","15008","15009","15010","15011","15012","15013","15014","15015","15016","15017","15018","15019","15020","15021","15022","15023","15024","15025","15026","15027","15028","15029","15030","15031","15032","15033","15034","15035","15036","15037","15038","15039","15040","15041","15042","15043","15044","15045","15046","15047","15048","15049","15050","15051","15052","15053","15054","15055","15056","15057","15058","15059","15060","15061","15062","15063","15064","15065","15066","15067","15068","15069","15070","15071","15072","15073","15074","15075","15076","15077","15078","15079","15080","15081","15082","15083","15084","15085","15086","15087","15088","15089","15090","15091","15092","15093","15094","15095","15096","15097","15098","15099","15100","15101","15102","15103","15104","15105","15106","15107","15108","15109","15110","15111","15112","15113","15114","15115","15116","15117","15118","15119","15120","15121","15122","15123","15124","15125","15126","15127","15128","15129","15130","15131","15132","15133","15134","15135","15136","15137","15138","15139","15140","15141","15142","15143","15144","15145","15146","15147","15148","15149","15150","15151","15152","15153","15154","15155","15156","15157","15158","15159","15160","15161","15162","15163","15164","15165","15166","15167","15168","15169","15170","15171","15172","15173","15174","15175","15176","15177","15178","15179","15180","15181","15182","15183","15184","15185","15186","15187","15188","15189","15190","15191","15192","15193","15194","15195","15196","15197","15198","15199","15200","15201","15202","15203","15204","15205","15206","15207","15208","15209","15210","15211","15212","15213","15214","15215","15216","15217","15218","15219","15220","15221","15222","15223","15224","15225","15226","15227","15228","15229","15230","15231","15232","15233","15234","15235","15236","15237","15238","15239","15240","15241","15242","15243","15244","15245","15246","15247","15248","15249","15250","15251","15252","15253","15254","15255","15256","15257","15258","15259","15260","15261","15262","15263","15264","15265","15266","15267","15268","15269","15270","15271","15272","15273","15274","15275","15276","15277","15278","15279","15280","15281","15282","15283","15284","15285","15286","15287","15288","15289","15290","15291","15292","15293","15294","15295","15296","15297","15298","15299","15300","15301","15302","15303","15304","15305","15306","15307","15308","15309","15310","15311","15312","15313","15314","15315","15316","15317","15318","15319","15320","15321","15322","15323","15324","15325","15326","15327","15328","15329","15330","15331","15332","15333","15334","15335","15336","15337","15338","15339","15340","15341","15342","15343","15344","15345","15346","15347","15348","15349","15350","15351","15352","15353","15354","15355","15356","15357","15358","15359","15360","15361","15362","15363","15364","15365","15366","15367","15368","15369","15370","15371","15372","15373","15374","15375","15376","15377","15378","15379","15380","15381","15382","15383","15384","15385","15386","15387","15388","15389","15390","15391","15392","15393","15394","15395","15396","15397","15398","15399","15400","15401","15402","15403","15404","15405","15406","15407","15408","15409","15410","15411","15412","15413","15414","15415","15416","15417","15418","15419","15420","15421","15422","15423","15424","15425","15426","15427","15428","15429","15430","15431","15432","15433","15434","15435","15436","15437","15438","15439","15440","15441","15442","15443","15444","15445","15446","15447","15448","15449","15450","15451","15452","15453","15454","15455","15456","15457","15458","15459","15460","15461","15462","15463","15464","15465","15466","15467","15468","15469","15470","15471","15472","15473","15474","15475","15476","15477","15478","15479","15480","15481","15482","15483","15484","15485","15486","15487","15488","15489","15490","15491","15492","15493","15494","15495","15496","15497","15498","15499","15500","15501","15502","15503","15504","15505","15506","15507","15508","15509","15510","15511","15512","15513","15514","15515","15516","15517","15518","15519","15520","15521","15522","15523","15524","15525","15526","15527","15528","15529","15530","15531","15532","15533","15534","15535","15536","15537","15538","15539","15540","15541","15542","15543","15544","15545","15546","15547","15548","15549","15550","15551","15552","15553","15554","15555","15556","15557","15558","15559","15560","15561","15562","15563","15564","15565","15566","15567","15568","15569","15570","15571","15572","15573","15574","15575","15576","15577","15578","15579","15580","15581","15582","15583","15584","15585","15586","15587","15588","15589","15590","15591","15592","15593","15594","15595","15596","15597","15598","15599","15600","15601","15602","15603","15604","15605","15606","15607","15608","15609","15610","15611","15612","15613","15614","15615","15616","15617","15618","15619","15620","15621","15622","15623","15624","15625","15626","15627","15628","15629","15630","15631","15632","15633","15634","15635","15636","15637","15638","15639","15640","15641","15642","15643","15644","15645","15646","15647","15648","15649","15650","15651","15652","15653","15654","15655","15656","15657","15658","15659","15660","15661","15662","15663","15664","15665","15666","15667","15668","15669","15670","15671","15672","15673","15674","15675","15676","15677","15678","15679","15680","15681","15682","15683","15684","15685","15686","15687","15688","15689","15690","15691","15692","15693","15694","15695","15696","15697","15698","15699","15700","15701","15702","15703","15704","15705","15706","15707","15708","15709","15710","15711","15712","15713","15714","15715","15716","15717","15718","15719","15720","15721","15722","15723","15724","15725","15726","15727","15728","15729","15730","15731","15732","15733","15734","15735","15736","15737","15738","15739","15740","15741","15742","15743","15744","15745","15746","15747","15748","15749","15750","15751","15752","15753","15754","15755","15756","15757","15758","15759","15760","15761","15762","15763","15764","15765","15766","15767","15768","15769","15770","15771","15772","15773","15774","15775","15776","15777","15778","15779","15780","15781","15782","15783","15784","15785","15786","15787","15788","15789","15790","15791","15792","15793","15794","15795","15796","15797","15798","15799","15800","15801","15802","15803","15804","15805","15806","15807","15808","15809","15810","15811","15812","15813","15814","15815","15816","15817","15818","15819","15820","15821","15822","15823","15824","15825","15826","15827","15828","15829","15830","15831","15832","15833","15834","15835","15836","15837","15838","15839","15840","15841","15842","15843","15844","15845","15846","15847","15848","15849","15850","15851","15852","15853","15854","15855","15856","15857","15858","15859","15860","15861","15862","15863","15864","15865","15866","15867","15868","15869","15870","15871","15872","15873","15874","15875","15876","15877","15878","15879","15880","15881","15882","15883","15884","15885","15886","15887","15888","15889","15890","15891","15892","15893","15894","15895","15896","15897","15898","15899","15900","15901","15902","15903","15904","15905","15906","15907","15908","15909","15910","15911","15912","15913","15914","15915","15916","15917","15918","15919","15920","15921","15922","15923","15924","15925","15926","15927","15928","15929","15930","15931","15932","15933","15934","15935","15936","15937","15938","15939","15940","15941","15942","15943","15944","15945","15946","15947","15948","15949","15950","15951","15952","15953","15954","15955","15956","15957","15958","15959","15960","15961","15962","15963","15964","15965","15966","15967","15968","15969","15970","15971","15972","15973","15974","15975","15976","15977","15978","15979","15980","15981","15982","15983","15984","15985","15986","15987","15988","15989","15990","15991","15992","15993","15994","15995","15996","15997","15998","15999","16000","16001","16002","16003","16004","16005","16006","16007","16008","16009","16010","16011","16012","16013","16014","16015","16016","16017","16018","16019","16020","16021","16022","16023","16024","16025","16026","16027","16028","16029","16030","16031","16032","16033","16034","16035","16036","16037","16038","16039","16040","16041","16042","16043","16044","16045","16046","16047","16048","16049","16050","16051","16052","16053","16054","16055","16056","16057","16058","16059","16060","16061","16062","16063","16064","16065","16066","16067","16068","16069","16070","16071","16072","16073","16074","16075","16076","16077","16078","16079","16080","16081","16082","16083","16084","16085","16086","16087","16088","16089","16090","16091","16092","16093","16094","16095","16096","16097","16098","16099","16100","16101","16102","16103","16104","16105","16106","16107","16108","16109","16110","16111","16112","16113","16114","16115","16116","16117","16118","16119","16120","16121","16122","16123","16124","16125","16126","16127","16128","16129","16130","16131","16132","16133","16134","16135","16136","16137","16138","16139","16140","16141","16142","16143","16144","16145","16146","16147","16148","16149","16150","16151","16152","16153","16154","16155","16156","16157","16158","16159","16160","16161","16162","16163","16164","16165","16166","16167","16168","16169","16170","16171","16172","16173","16174","16175","16176","16177","16178","16179","16180","16181","16182","16183","16184","16185","16186","16187","16188","16189","16190","16191","16192","16193","16194","16195","16196","16197","16198","16199","16200","16201","16202","16203","16204","16205","16206","16207","16208","16209","16210","16211","16212","16213","16214","16215","16216","16217","16218","16219","16220","16221","16222","16223","16224","16225","16226","16227","16228","16229","16230","16231","16232","16233","16234","16235","16236","16237","16238","16239","16240","16241","16242","16243","16244","16245","16246","16247","16248","16249","16250","16251","16252","16253","16254","16255","16256","16257","16258","16259","16260","16261","16262","16263","16264","16265","16266","16267","16268","16269","16270","16271","16272","16273","16274","16275","16276","16277","16278","16279","16280","16281","16282","16283","16284","16285","16286","16287","16288","16289","16290","16291","16292","16293","16294","16295","16296","16297","16298","16299","16300","16301","16302","16303","16304","16305","16306","16307","16308","16309","16310","16311","16312","16313","16314","16315","16316","16317","16318","16319","16320","16321","16322","16323","16324","16325","16326","16327","16328","16329","16330","16331","16332","16333","16334","16335","16336","16337","16338","16339","16340","16341","16342","16343","16344","16345","16346","16347","16348","16349","16350","16351","16352","16353","16354","16355","16356","16357","16358","16359","16360","16361","16362","16363","16364","16365","16366","16367","16368","16369","16370","16371","16372","16373","16374","16375","16376","16377","16378","16379","16380","16381","16382","16383","16384","16385","16386","16387","16388","16389","16390","16391","16392","16393","16394","16395","16396","16397","16398","16399","16400","16401","16402","16403","16404","16405","16406","16407","16408","16409","16410","16411","16412","16413","16414","16415","16416","16417","16418","16419","16420","16421","16422","16423","16424","16425","16426","16427","16428","16429","16430","16431","16432","16433","16434","16435","16436","16437","16438","16439","16440","16441","16442","16443","16444","16445","16446","16447","16448","16449","16450","16451","16452","16453","16454","16455","16456","16457","16458","16459","16460","16461","16462","16463","16464","16465","16466","16467","16468","16469","16470","16471","16472","16473","16474","16475","16476","16477","16478","16479","16480","16481","16482","16483","16484","16485","16486","16487","16488","16489","16490","16491","16492","16493","16494","16495","16496","16497","16498","16499","16500","16501","16502","16503","16504","16505","16506","16507","16508","16509","16510","16511","16512","16513","16514","16515","16516","16517","16518","16519","16520","16521","16522","16523","16524","16525","16526","16527","16528","16529","16530","16531","16532","16533","16534","16535","16536","16537","16538","16539","16540","16541","16542","16543","16544","16545","16546","16547","16548","16549","16550","16551","16552","16553","16554","16555","16556","16557","16558","16559","16560","16561","16562","16563","16564","16565","16566","16567","16568","16569","16570","16571","16572","16573","16574","16575","16576","16577","16578","16579","16580","16581","16582","16583","16584","16585","16586","16587","16588","16589","16590","16591","16592","16593","16594","16595","16596","16597","16598","16599","16600","16601","16602","16603","16604","16605","16606","16607","16608","16609","16610","16611","16612","16613","16614","16615","16616","16617","16618","16619","16620","16621","16622","16623","16624","16625","16626","16627","16628","16629","16630","16631","16632","16633","16634","16635","16636","16637","16638","16639","16640","16641","16642","16643","16644","16645","16646","16647","16648","16649","16650","16651","16652","16653","16654","16655","16656","16657","16658","16659","16660","16661","16662","16663","16664","16665","16666","16667","16668","16669","16670","16671","16672","16673","16674","16675","16676","16677","16678","16679","16680","16681","16682","16683","16684","16685","16686","16687","16688","16689","16690","16691","16692","16693","16694","16695","16696","16697","16698","16699","16700","16701","16702","16703","16704","16705","16706","16707","16708","16709","16710","16711","16712","16713","16714","16715","16716","16717","16718","16719","16720","16721","16722","16723","16724","16725","16726","16727","16728","16729","16730","16731","16732","16733","16734","16735","16736","16737","16738","16739","16740","16741","16742","16743","16744","16745","16746","16747","16748","16749","16750","16751","16752","16753","16754","16755","16756","16757","16758","16759","16760","16761","16762","16763","16764","16765","16766","16767","16768","16769","16770","16771","16772","16773","16774","16775","16776","16777","16778","16779","16780","16781","16782","16783","16784","16785","16786","16787","16788","16789","16790","16791","16792","16793","16794","16795","16796","16797","16798","16799","16800","16801","16802","16803","16804","16805","16806","16807","16808","16809","16810","16811","16812","16813","16814","16815","16816","16817","16818","16819","16820","16821","16822","16823","16824","16825","16826","16827","16828","16829","16830","16831","16832","16833","16834","16835","16836","16837","16838","16839","16840","16841","16842","16843","16844","16845","16846","16847","16848","16849","16850","16851","16852","16853","16854","16855","16856","16857","16858","16859","16860","16861","16862","16863","16864","16865","16866","16867","16868","16869","16870","16871","16872","16873","16874","16875","16876","16877","16878","16879","16880","16881","16882","16883","16884","16885","16886","16887","16888","16889","16890","16891","16892","16893","16894","16895","16896","16897","16898","16899","16900","16901","16902","16903","16904","16905","16906","16907","16908","16909","16910","16911","16912","16913","16914","16915","16916","16917","16918","16919","16920","16921","16922","16923","16924","16925","16926","16927","16928","16929","16930","16931","16932","16933","16934","16935","16936","16937","16938","16939","16940","16941","16942","16943","16944","16945","16946","16947","16948","16949","16950","16951","16952","16953","16954","16955","16956","16957","16958","16959","16960","16961","16962","16963","16964","16965","16966","16967","16968","16969","16970","16971","16972","16973","16974","16975","16976","16977","16978","16979","16980","16981","16982","16983","16984","16985","16986","16987","16988","16989","16990","16991","16992","16993","16994","16995","16996","16997","16998","16999","17000","17001","17002","17003","17004","17005","17006","17007","17008","17009","17010","17011","17012","17013","17014","17015","17016","17017","17018","17019","17020","17021","17022","17023","17024","17025","17026","17027","17028","17029","17030","17031","17032","17033","17034","17035","17036","17037","17038","17039","17040","17041","17042","17043","17044","17045","17046","17047","17048","17049","17050","17051","17052","17053","17054","17055","17056","17057","17058","17059","17060","17061","17062","17063","17064","17065","17066","17067","17068","17069","17070","17071","17072","17073","17074","17075","17076","17077","17078","17079","17080","17081","17082","17083","17084","17085","17086","17087","17088","17089","17090","17091","17092","17093","17094","17095","17096","17097","17098","17099","17100","17101","17102","17103","17104","17105","17106","17107","17108","17109","17110","17111","17112","17113","17114","17115","17116","17117","17118","17119","17120","17121","17122","17123","17124","17125","17126","17127","17128","17129","17130","17131","17132","17133","17134","17135","17136","17137","17138","17139","17140","17141","17142","17143","17144","17145","17146","17147","17148","17149","17150","17151","17152","17153","17154","17155","17156","17157","17158","17159","17160","17161","17162","17163","17164","17165","17166","17167","17168","17169","17170","17171","17172","17173","17174","17175","17176","17177","17178","17179","17180","17181","17182","17183","17184","17185","17186","17187","17188","17189","17190","17191","17192","17193","17194","17195","17196","17197","17198","17199","17200","17201","17202","17203","17204","17205","17206","17207","17208","17209","17210","17211","17212","17213","17214","17215","17216","17217","17218","17219","17220","17221","17222","17223","17224","17225","17226","17227","17228","17229","17230","17231","17232","17233","17234","17235","17236","17237","17238","17239","17240","17241","17242","17243","17244","17245","17246","17247","17248","17249","17250","17251","17252","17253","17254","17255","17256","17257","17258","17259","17260","17261","17262","17263","17264","17265","17266","17267","17268","17269","17270","17271","17272","17273","17274","17275","17276","17277","17278","17279","17280","17281","17282","17283","17284","17285","17286","17287","17288","17289","17290","17291","17292","17293","17294","17295","17296","17297","17298","17299","17300","17301","17302","17303","17304","17305","17306","17307","17308","17309","17310","17311","17312","17313","17314","17315","17316","17317","17318","17319","17320","17321","17322","17323","17324","17325","17326","17327","17328","17329","17330","17331","17332","17333","17334","17335","17336","17337","17338","17339","17340","17341","17342","17343","17344","17345","17346","17347","17348","17349","17350","17351","17352","17353","17354","17355","17356","17357","17358","17359","17360","17361","17362","17363","17364","17365","17366","17367","17368","17369","17370","17371","17372","17373","17374","17375","17376","17377","17378","17379","17380","17381","17382","17383","17384","17385","17386","17387","17388","17389","17390","17391","17392","17393","17394","17395","17396","17397","17398","17399","17400","17401","17402","17403","17404","17405","17406","17407","17408","17409","17410","17411","17412","17413","17414","17415","17416","17417","17418","17419","17420","17421","17422","17423","17424","17425","17426","17427","17428","17429","17430","17431","17432","17433","17434","17435","17436","17437","17438","17439","17440","17441","17442","17443","17444","17445","17446","17447","17448","17449","17450","17451","17452","17453","17454","17455","17456","17457","17458","17459","17460","17461","17462","17463","17464","17465","17466","17467","17468","17469","17470","17471","17472","17473","17474","17475","17476","17477","17478","17479","17480","17481","17482","17483","17484","17485","17486","17487","17488","17489","17490","17491","17492","17493","17494","17495","17496","17497","17498","17499","17500","17501","17502","17503","17504","17505","17506","17507","17508","17509","17510","17511","17512","17513","17514","17515","17516","17517","17518","17519","17520","17521","17522","17523","17524","17525","17526","17527","17528","17529","17530","17531","17532","17533","17534","17535","17536","17537","17538","17539","17540","17541","17542","17543","17544","17545","17546","17547","17548","17549","17550","17551","17552","17553","17554","17555","17556","17557","17558","17559","17560","17561","17562","17563","17564","17565","17566","17567","17568","17569","17570","17571","17572","17573","17574","17575","17576","17577","17578","17579","17580","17581","17582","17583","17584","17585","17586","17587","17588","17589","17590","17591","17592","17593","17594","17595","17596","17597","17598","17599","17600","17601","17602","17603","17604","17605","17606","17607","17608","17609","17610","17611","17612","17613","17614","17615","17616","17617","17618","17619","17620","17621","17622","17623","17624","17625","17626","17627","17628","17629","17630","17631","17632","17633","17634","17635","17636","17637","17638","17639","17640","17641","17642","17643","17644","17645","17646","17647","17648","17649","17650","17651","17652","17653","17654","17655","17656","17657","17658","17659","17660","17661","17662","17663","17664","17665","17666","17667","17668","17669","17670","17671","17672","17673","17674","17675","17676","17677","17678","17679","17680","17681","17682","17683","17684","17685","17686","17687","17688","17689","17690","17691","17692","17693","17694","17695","17696","17697","17698","17699","17700","17701","17702","17703","17704","17705","17706","17707","17708","17709","17710","17711","17712","17713","17714","17715","17716","17717","17718","17719","17720","17721","17722","17723","17724","17725","17726","17727","17728","17729","17730","17731","17732","17733","17734","17735","17736","17737","17738","17739","17740","17741","17742","17743","17744","17745","17746","17747","17748","17749","17750","17751","17752","17753","17754","17755","17756","17757","17758","17759","17760","17761","17762","17763","17764","17765","17766","17767","17768","17769","17770","17771","17772","17773","17774","17775","17776","17777","17778","17779","17780","17781","17782","17783","17784","17785","17786","17787","17788","17789","17790","17791","17792","17793","17794","17795","17796","17797","17798","17799","17800","17801","17802","17803","17804","17805","17806","17807","17808","17809","17810","17811","17812","17813","17814","17815","17816","17817","17818","17819","17820","17821","17822","17823","17824","17825","17826","17827","17828","17829","17830","17831","17832","17833","17834","17835","17836","17837","17838","17839","17840","17841","17842","17843","17844","17845","17846","17847","17848","17849","17850","17851","17852","17853","17854","17855","17856","17857","17858","17859","17860","17861","17862","17863","17864","17865","17866","17867","17868","17869","17870","17871","17872","17873","17874","17875","17876","17877","17878","17879","17880","17881","17882","17883","17884","17885","17886","17887","17888","17889","17890","17891","17892","17893","17894","17895","17896","17897","17898","17899","17900","17901","17902","17903","17904","17905","17906","17907","17908","17909","17910","17911","17912","17913","17914","17915","17916","17917","17918","17919","17920","17921","17922","17923","17924","17925","17926","17927","17928","17929","17930","17931","17932","17933","17934","17935","17936","17937","17938","17939","17940","17941","17942","17943","17944","17945","17946","17947","17948","17949","17950","17951","17952","17953","17954","17955","17956","17957","17958","17959","17960","17961","17962","17963","17964","17965","17966","17967","17968","17969","17970","17971","17972","17973","17974","17975","17976","17977","17978","17979","17980","17981","17982","17983","17984","17985","17986","17987","17988","17989","17990","17991","17992","17993","17994","17995","17996","17997","17998","17999","18000","18001","18002","18003","18004","18005","18006","18007","18008","18009","18010","18011","18012","18013","18014","18015","18016","18017","18018","18019","18020","18021","18022","18023","18024","18025","18026","18027","18028","18029","18030","18031","18032","18033","18034","18035","18036","18037","18038","18039","18040","18041","18042","18043","18044","18045","18046","18047","18048","18049","18050","18051","18052","18053","18054","18055","18056","18057","18058","18059","18060","18061","18062","18063","18064","18065","18066","18067","18068","18069","18070","18071","18072","18073","18074","18075","18076","18077","18078","18079","18080","18081","18082","18083","18084","18085","18086","18087","18088","18089","18090","18091","18092","18093","18094","18095","18096","18097","18098","18099","18100","18101","18102","18103","18104","18105","18106","18107","18108","18109","18110","18111","18112","18113","18114","18115","18116","18117","18118","18119","18120","18121","18122","18123","18124","18125","18126","18127","18128","18129","18130","18131","18132","18133","18134","18135","18136","18137","18138","18139","18140","18141","18142","18143","18144","18145","18146","18147","18148","18149","18150","18151","18152","18153","18154","18155","18156","18157","18158","18159","18160","18161","18162","18163","18164","18165","18166","18167","18168","18169","18170","18171","18172","18173","18174","18175","18176","18177","18178","18179","18180","18181","18182","18183","18184","18185","18186","18187","18188","18189","18190","18191","18192","18193","18194","18195","18196","18197","18198","18199","18200","18201","18202","18203","18204","18205","18206","18207","18208","18209","18210","18211","18212","18213","18214","18215","18216","18217","18218","18219","18220","18221","18222","18223","18224","18225","18226","18227","18228","18229","18230","18231","18232","18233","18234","18235","18236","18237","18238","18239","18240","18241","18242","18243","18244","18245","18246","18247","18248","18249","18250","18251","18252","18253","18254","18255","18256","18257","18258","18259","18260","18261","18262","18263","18264","18265","18266","18267","18268","18269","18270","18271","18272","18273","18274","18275","18276","18277","18278","18279","18280","18281","18282","18283","18284","18285","18286","18287","18288","18289","18290","18291","18292","18293","18294","18295","18296","18297","18298","18299","18300","18301","18302","18303","18304","18305","18306","18307","18308","18309","18310","18311","18312","18313","18314","18315","18316","18317","18318","18319","18320","18321","18322","18323","18324","18325","18326","18327","18328","18329","18330","18331","18332","18333","18334","18335","18336","18337","18338","18339","18340","18341","18342","18343","18344","18345","18346","18347","18348","18349","18350","18351","18352","18353","18354","18355","18356","18357","18358","18359","18360","18361","18362","18363","18364","18365","18366","18367","18368","18369","18370","18371","18372","18373","18374","18375","18376","18377","18378","18379","18380","18381","18382","18383","18384","18385","18386","18387","18388","18389","18390","18391","18392","18393","18394","18395","18396","18397","18398","18399","18400","18401","18402","18403","18404","18405","18406","18407","18408","18409","18410","18411","18412","18413","18414","18415","18416","18417","18418","18419","18420","18421","18422","18423","18424","18425","18426","18427","18428","18429","18430","18431","18432","18433","18434","18435","18436","18437","18438","18439","18440","18441","18442","18443","18444","18445","18446","18447","18448","18449","18450","18451","18452","18453","18454","18455","18456","18457","18458","18459","18460","18461","18462","18463","18464","18465","18466","18467","18468","18469","18470","18471","18472","18473","18474","18475","18476","18477","18478","18479","18480","18481","18482","18483","18484","18485","18486","18487","18488","18489","18490","18491","18492","18493","18494","18495","18496","18497","18498","18499","18500","18501","18502","18503","18504","18505","18506","18507","18508","18509","18510","18511","18512","18513","18514","18515","18516","18517","18518","18519","18520","18521","18522","18523","18524","18525","18526","18527","18528","18529","18530","18531","18532","18533","18534","18535","18536","18537","18538","18539","18540","18541","18542","18543","18544","18545","18546","18547","18548","18549","18550","18551","18552","18553","18554","18555","18556","18557","18558","18559","18560","18561","18562","18563","18564","18565","18566","18567","18568","18569","18570","18571","18572","18573","18574","18575","18576","18577","18578","18579","18580","18581","18582","18583","18584","18585","18586","18587","18588","18589","18590","18591","18592","18593","18594","18595","18596","18597","18598","18599","18600","18601","18602","18603","18604","18605","18606","18607","18608","18609","18610","18611","18612","18613","18614","18615","18616","18617","18618","18619","18620","18621","18622","18623","18624","18625","18626","18627","18628","18629","18630","18631","18632","18633","18634","18635","18636","18637","18638","18639","18640","18641","18642","18643","18644","18645","18646","18647","18648","18649","18650","18651","18652","18653","18654","18655","18656","18657","18658","18659","18660","18661","18662","18663","18664","18665","18666","18667","18668","18669","18670","18671","18672","18673","18674","18675","18676","18677","18678","18679","18680","18681","18682","18683","18684","18685","18686","18687","18688","18689","18690","18691","18692","18693","18694","18695","18696","18697","18698","18699","18700","18701","18702","18703","18704","18705","18706","18707","18708","18709","18710","18711","18712","18713","18714","18715","18716","18717","18718","18719","18720","18721","18722","18723","18724","18725","18726","18727","18728","18729","18730","18731","18732","18733","18734","18735","18736","18737","18738","18739","18740","18741","18742","18743","18744","18745","18746","18747","18748","18749","18750","18751","18752","18753","18754","18755","18756","18757","18758","18759","18760","18761","18762","18763","18764","18765","18766","18767","18768","18769","18770","18771","18772","18773","18774","18775","18776","18777","18778","18779","18780","18781","18782","18783","18784","18785","18786","18787","18788","18789","18790","18791","18792","18793","18794","18795","18796","18797","18798","18799","18800","18801","18802","18803","18804","18805","18806","18807","18808","18809","18810","18811","18812","18813","18814","18815","18816","18817","18818","18819","18820","18821","18822","18823","18824","18825","18826","18827","18828","18829","18830","18831","18832","18833","18834","18835","18836","18837","18838","18839","18840","18841","18842","18843","18844","18845","18846","18847","18848","18849","18850","18851","18852","18853","18854","18855","18856","18857","18858","18859","18860","18861","18862","18863","18864","18865","18866","18867","18868","18869","18870","18871","18872","18873","18874","18875","18876","18877","18878","18879","18880","18881","18882","18883","18884","18885","18886","18887","18888","18889","18890","18891","18892","18893","18894","18895","18896","18897","18898","18899","18900","18901","18902","18903","18904","18905","18906","18907","18908","18909","18910","18911","18912","18913","18914","18915","18916","18917","18918","18919","18920","18921","18922","18923","18924","18925","18926","18927","18928","18929","18930","18931","18932","18933","18934","18935","18936","18937","18938","18939","18940","18941","18942","18943","18944","18945","18946","18947","18948","18949","18950","18951","18952","18953","18954","18955","18956","18957","18958","18959","18960","18961","18962","18963","18964","18965","18966","18967","18968","18969","18970","18971","18972","18973","18974","18975","18976","18977","18978","18979","18980","18981","18982","18983","18984","18985","18986","18987","18988","18989","18990","18991","18992","18993","18994","18995","18996","18997","18998","18999","19000","19001","19002","19003","19004","19005","19006","19007","19008","19009","19010","19011","19012","19013","19014","19015","19016","19017","19018","19019","19020","19021","19022","19023","19024","19025","19026","19027","19028","19029","19030","19031","19032","19033","19034","19035","19036","19037","19038","19039","19040","19041","19042","19043","19044","19045","19046","19047","19048","19049","19050","19051","19052","19053","19054","19055","19056","19057","19058","19059","19060","19061","19062","19063","19064","19065","19066","19067","19068","19069","19070","19071","19072","19073","19074","19075","19076","19077","19078","19079","19080","19081","19082","19083","19084","19085","19086","19087","19088","19089","19090","19091","19092","19093","19094","19095","19096","19097","19098","19099","19100","19101","19102","19103","19104","19105","19106","19107","19108","19109","19110","19111","19112","19113","19114","19115","19116","19117","19118","19119","19120","19121","19122","19123","19124","19125","19126","19127","19128","19129","19130","19131","19132","19133","19134","19135","19136","19137","19138","19139","19140","19141","19142","19143","19144","19145","19146","19147","19148","19149","19150","19151","19152","19153","19154","19155","19156","19157","19158","19159","19160","19161","19162","19163","19164","19165","19166","19167","19168","19169","19170","19171","19172","19173","19174","19175","19176","19177","19178","19179","19180","19181","19182","19183","19184","19185","19186","19187","19188","19189","19190","19191","19192","19193","19194","19195","19196","19197","19198","19199","19200","19201","19202","19203","19204","19205","19206","19207","19208","19209","19210","19211","19212","19213","19214","19215","19216","19217","19218","19219","19220","19221","19222","19223","19224","19225","19226","19227","19228","19229","19230","19231","19232","19233","19234","19235","19236","19237","19238","19239","19240","19241","19242","19243","19244","19245","19246","19247","19248","19249","19250","19251","19252","19253","19254","19255","19256","19257","19258","19259","19260","19261","19262","19263","19264","19265","19266","19267","19268","19269","19270","19271","19272","19273","19274","19275","19276","19277","19278","19279","19280","19281","19282","19283","19284","19285","19286","19287","19288","19289","19290","19291","19292","19293","19294","19295","19296","19297","19298","19299","19300","19301","19302","19303","19304","19305","19306","19307","19308","19309","19310","19311","19312","19313","19314","19315","19316","19317","19318","19319","19320","19321","19322","19323","19324","19325","19326","19327","19328","19329","19330","19331","19332","19333","19334","19335","19336","19337","19338","19339","19340","19341","19342","19343","19344","19345","19346","19347","19348","19349","19350","19351","19352","19353","19354","19355","19356","19357","19358","19359","19360","19361","19362","19363","19364","19365","19366","19367","19368","19369","19370","19371","19372","19373","19374","19375","19376","19377","19378","19379","19380","19381","19382","19383","19384","19385","19386","19387","19388","19389","19390","19391","19392","19393","19394","19395","19396","19397","19398","19399","19400","19401","19402","19403","19404","19405","19406","19407","19408","19409","19410","19411","19412","19413","19414","19415","19416","19417","19418","19419","19420","19421","19422","19423","19424","19425","19426","19427","19428","19429","19430","19431","19432","19433","19434","19435","19436","19437","19438","19439","19440","19441","19442","19443","19444","19445","19446","19447","19448","19449","19450","19451","19452","19453","19454","19455","19456","19457","19458","19459","19460","19461","19462","19463","19464","19465","19466","19467","19468","19469","19470","19471","19472","19473","19474","19475","19476","19477","19478","19479","19480","19481","19482","19483","19484","19485","19486","19487","19488","19489","19490","19491","19492","19493","19494","19495","19496","19497","19498","19499","19500","19501","19502","19503","19504","19505","19506","19507","19508","19509","19510","19511","19512","19513","19514","19515","19516","19517","19518","19519","19520","19521","19522","19523","19524","19525","19526","19527","19528","19529","19530","19531","19532","19533","19534","19535","19536","19537","19538","19539","19540","19541","19542","19543","19544","19545","19546","19547","19548","19549","19550","19551","19552","19553","19554","19555","19556","19557","19558","19559","19560","19561","19562","19563","19564","19565","19566","19567","19568","19569","19570","19571","19572","19573","19574","19575","19576","19577","19578","19579","19580","19581","19582","19583","19584","19585","19586","19587","19588","19589","19590","19591","19592","19593","19594","19595","19596","19597","19598","19599","19600","19601","19602","19603","19604","19605","19606","19607","19608","19609","19610","19611","19612","19613","19614","19615","19616","19617","19618","19619","19620","19621","19622","19623","19624","19625","19626","19627","19628","19629","19630","19631","19632","19633","19634","19635","19636","19637","19638","19639","19640","19641","19642","19643","19644","19645","19646","19647","19648","19649","19650","19651","19652","19653","19654","19655","19656","19657","19658","19659","19660","19661","19662","19663","19664","19665","19666","19667","19668","19669","19670","19671","19672","19673","19674","19675","19676","19677","19678","19679","19680","19681","19682","19683","19684","19685","19686","19687","19688","19689","19690","19691","19692","19693","19694","19695","19696","19697","19698","19699","19700","19701","19702","19703","19704","19705","19706","19707","19708","19709","19710","19711","19712","19713","19714","19715","19716","19717","19718","19719","19720","19721","19722","19723","19724","19725","19726","19727","19728","19729","19730","19731","19732","19733","19734","19735","19736","19737","19738","19739","19740","19741","19742","19743","19744","19745","19746","19747","19748","19749","19750","19751","19752","19753","19754","19755","19756","19757","19758","19759","19760","19761","19762","19763","19764","19765","19766","19767","19768","19769","19770","19771","19772","19773","19774","19775","19776","19777","19778","19779","19780","19781","19782","19783","19784","19785","19786","19787","19788","19789","19790","19791","19792","19793","19794","19795","19796","19797","19798","19799","19800","19801","19802","19803","19804","19805","19806","19807","19808","19809","19810","19811","19812","19813","19814","19815","19816","19817","19818","19819","19820","19821","19822","19823","19824","19825","19826","19827","19828","19829","19830","19831","19832","19833","19834","19835","19836","19837","19838","19839","19840","19841","19842","19843","19844","19845","19846","19847","19848","19849","19850","19851","19852","19853","19854","19855","19856","19857","19858","19859","19860","19861","19862","19863","19864","19865","19866","19867","19868","19869","19870","19871","19872","19873","19874","19875","19876","19877","19878","19879","19880","19881","19882","19883","19884","19885","19886","19887","19888","19889","19890","19891","19892","19893","19894","19895","19896","19897","19898","19899","19900","19901","19902","19903","19904","19905","19906","19907","19908","19909","19910","19911","19912","19913","19914","19915","19916","19917","19918","19919","19920","19921","19922","19923","19924","19925","19926","19927","19928","19929","19930","19931","19932","19933","19934","19935","19936","19937","19938","19939","19940","19941","19942","19943","19944","19945","19946","19947","19948","19949","19950","19951","19952","19953","19954","19955","19956","19957","19958","19959","19960","19961","19962","19963","19964","19965","19966","19967","19968","19969","19970","19971","19972","19973","19974","19975","19976","19977","19978","19979","19980","19981","19982","19983","19984","19985","19986","19987","19988","19989","19990","19991","19992","19993","19994","19995","19996","19997","19998","19999","20000","20001","20002","20003","20004","20005","20006","20007","20008","20009","20010","20011","20012","20013","20014","20015","20016","20017","20018","20019","20020","20021","20022","20023","20024","20025","20026","20027","20028","20029","20030","20031","20032","20033","20034","20035","20036","20037","20038","20039","20040","20041","20042","20043","20044","20045","20046","20047","20048","20049","20050","20051","20052","20053","20054","20055","20056","20057","20058","20059","20060","20061","20062","20063","20064","20065","20066","20067","20068","20069","20070","20071","20072","20073","20074","20075","20076","20077","20078","20079","20080","20081","20082","20083","20084","20085","20086","20087","20088","20089","20090","20091","20092","20093","20094","20095","20096","20097","20098","20099","20100","20101","20102","20103","20104","20105","20106","20107","20108","20109","20110","20111","20112","20113","20114","20115","20116","20117","20118","20119","20120","20121","20122","20123","20124","20125","20126","20127","20128","20129","20130","20131","20132","20133","20134","20135","20136","20137","20138","20139","20140","20141","20142","20143","20144","20145","20146","20147","20148","20149","20150","20151","20152","20153","20154","20155","20156","20157","20158","20159","20160","20161","20162","20163","20164","20165","20166","20167","20168","20169","20170","20171","20172","20173","20174","20175","20176","20177","20178","20179","20180","20181","20182","20183","20184","20185","20186","20187","20188","20189","20190","20191","20192","20193","20194","20195","20196","20197","20198","20199","20200","20201","20202","20203","20204","20205","20206","20207","20208","20209","20210","20211","20212","20213","20214","20215","20216","20217","20218","20219","20220","20221","20222","20223","20224","20225","20226","20227","20228","20229","20230","20231","20232","20233","20234","20235","20236","20237","20238","20239","20240","20241","20242","20243","20244","20245","20246","20247","20248","20249","20250","20251","20252","20253","20254","20255","20256","20257","20258","20259","20260","20261","20262","20263","20264","20265","20266","20267","20268","20269","20270","20271","20272","20273","20274","20275","20276","20277","20278","20279","20280","20281","20282","20283","20284","20285","20286","20287","20288","20289","20290","20291","20292","20293","20294","20295","20296","20297","20298","20299","20300","20301","20302","20303","20304","20305","20306","20307","20308","20309","20310","20311","20312","20313","20314","20315","20316","20317","20318","20319","20320","20321","20322","20323","20324","20325","20326","20327","20328","20329","20330","20331","20332","20333","20334","20335","20336","20337","20338","20339","20340","20341","20342","20343","20344","20345","20346","20347","20348","20349","20350","20351","20352","20353","20354","20355","20356","20357","20358","20359","20360","20361","20362","20363","20364","20365","20366","20367","20368","20369","20370","20371","20372","20373","20374","20375","20376","20377","20378","20379","20380","20381","20382","20383","20384","20385","20386","20387","20388","20389","20390","20391","20392","20393","20394","20395","20396","20397","20398","20399","20400","20401","20402","20403","20404","20405","20406","20407","20408","20409","20410","20411","20412","20413","20414","20415","20416","20417","20418","20419","20420","20421","20422","20423","20424","20425","20426","20427","20428","20429","20430","20431","20432","20433","20434","20435","20436","20437","20438","20439","20440","20441","20442","20443","20444","20445","20446","20447","20448","20449","20450","20451","20452","20453","20454","20455","20456","20457","20458","20459","20460","20461","20462","20463","20464","20465","20466","20467","20468","20469","20470","20471","20472","20473","20474","20475","20476","20477","20478","20479","20480","20481","20482","20483","20484","20485","20486","20487","20488","20489","20490","20491","20492","20493","20494","20495","20496","20497","20498","20499","20500","20501","20502","20503","20504","20505","20506","20507","20508","20509","20510","20511","20512","20513","20514","20515","20516","20517","20518","20519","20520","20521","20522","20523","20524","20525","20526","20527","20528","20529","20530","20531","20532","20533","20534","20535","20536","20537","20538","20539","20540","20541","20542","20543","20544","20545","20546","20547","20548","20549","20550","20551","20552","20553","20554","20555","20556","20557","20558","20559","20560","20561","20562","20563","20564","20565","20566","20567","20568","20569","20570","20571","20572","20573","20574","20575","20576","20577","20578","20579","20580","20581","20582","20583","20584","20585","20586","20587","20588","20589","20590","20591","20592","20593","20594","20595","20596","20597","20598","20599","20600","20601","20602","20603","20604","20605","20606","20607","20608","20609","20610","20611","20612","20613","20614","20615","20616","20617","20618","20619","20620","20621","20622","20623","20624","20625","20626","20627","20628","20629","20630","20631","20632","20633","20634","20635","20636","20637","20638","20639","20640","20641","20642","20643","20644","20645","20646","20647","20648","20649","20650","20651","20652","20653","20654","20655","20656","20657","20658","20659","20660","20661","20662","20663","20664","20665","20666","20667","20668","20669","20670","20671","20672","20673","20674","20675","20676","20677","20678","20679","20680","20681","20682","20683","20684","20685","20686","20687","20688","20689","20690","20691","20692","20693","20694","20695","20696","20697","20698","20699","20700","20701","20702","20703","20704","20705","20706","20707","20708","20709","20710","20711","20712","20713","20714","20715","20716","20717","20718","20719","20720","20721","20722","20723","20724","20725","20726","20727","20728","20729","20730","20731","20732","20733","20734","20735","20736","20737","20738","20739","20740","20741","20742","20743","20744","20745","20746","20747","20748","20749","20750","20751","20752","20753","20754","20755","20756","20757","20758","20759","20760","20761","20762","20763","20764","20765","20766","20767","20768","20769","20770","20771","20772","20773","20774","20775","20776","20777","20778","20779","20780","20781","20782","20783","20784","20785","20786","20787","20788","20789","20790","20791","20792","20793","20794","20795","20796","20797","20798","20799","20800","20801","20802","20803","20804","20805","20806","20807","20808","20809","20810","20811","20812","20813","20814","20815","20816","20817","20818","20819","20820","20821","20822","20823","20824","20825","20826","20827","20828","20829","20830","20831","20832","20833","20834","20835","20836","20837","20838","20839","20840","20841","20842","20843","20844","20845","20846","20847","20848","20849","20850","20851","20852","20853","20854","20855","20856","20857","20858","20859","20860","20861","20862","20863","20864","20865","20866","20867","20868","20869","20870","20871","20872","20873","20874","20875","20876","20877","20878","20879","20880","20881","20882","20883","20884","20885","20886","20887","20888","20889","20890","20891","20892","20893","20894","20895","20896","20897","20898","20899","20900","20901","20902","20903","20904","20905","20906","20907","20908","20909","20910","20911","20912","20913","20914","20915","20916","20917","20918","20919","20920","20921","20922","20923","20924","20925","20926","20927","20928","20929","20930","20931","20932","20933","20934","20935","20936","20937","20938","20939","20940","20941","20942","20943","20944","20945","20946","20947","20948","20949","20950","20951","20952","20953","20954","20955","20956","20957","20958","20959","20960","20961","20962","20963","20964","20965","20966","20967","20968","20969","20970","20971","20972","20973","20974","20975","20976","20977","20978","20979","20980","20981","20982","20983","20984","20985","20986","20987","20988","20989","20990","20991","20992","20993","20994","20995","20996","20997","20998","20999","21000","21001","21002","21003","21004","21005","21006","21007","21008","21009","21010","21011","21012","21013","21014","21015","21016","21017","21018","21019","21020","21021","21022","21023","21024","21025","21026","21027","21028","21029","21030","21031","21032","21033","21034","21035","21036","21037","21038","21039","21040","21041","21042","21043","21044","21045","21046","21047","21048","21049","21050","21051","21052","21053","21054","21055","21056","21057","21058","21059","21060","21061","21062","21063","21064","21065","21066","21067","21068","21069","21070","21071","21072","21073","21074","21075","21076","21077","21078","21079","21080","21081","21082","21083","21084","21085","21086","21087","21088","21089","21090","21091","21092","21093","21094","21095","21096","21097","21098","21099","21100","21101","21102","21103","21104","21105","21106","21107","21108","21109","21110","21111","21112","21113","21114","21115","21116","21117","21118","21119","21120","21121","21122","21123","21124","21125","21126","21127","21128","21129","21130","21131","21132","21133","21134","21135","21136","21137","21138","21139","21140","21141","21142","21143","21144","21145","21146","21147","21148","21149","21150","21151","21152","21153","21154","21155","21156","21157","21158","21159","21160","21161","21162","21163","21164","21165","21166","21167","21168","21169","21170","21171","21172","21173","21174","21175","21176","21177","21178","21179","21180","21181","21182","21183","21184","21185","21186","21187","21188","21189","21190","21191","21192","21193","21194","21195","21196","21197","21198","21199","21200","21201","21202","21203","21204","21205","21206","21207","21208","21209","21210","21211","21212","21213","21214","21215","21216","21217","21218","21219","21220","21221","21222","21223","21224","21225","21226","21227","21228","21229","21230","21231","21232","21233","21234","21235","21236","21237","21238","21239","21240","21241","21242","21243","21244","21245","21246","21247","21248","21249","21250","21251","21252","21253","21254","21255","21256","21257","21258","21259","21260","21261","21262","21263","21264","21265","21266","21267","21268","21269","21270","21271","21272","21273","21274","21275","21276","21277","21278","21279","21280","21281","21282","21283","21284","21285","21286","21287","21288","21289","21290","21291","21292","21293","21294","21295","21296","21297","21298","21299","21300","21301","21302","21303","21304","21305","21306","21307","21308","21309","21310","21311","21312","21313","21314","21315","21316","21317","21318","21319","21320","21321","21322","21323","21324","21325","21326","21327","21328","21329","21330","21331","21332","21333","21334","21335","21336","21337","21338","21339","21340","21341","21342","21343","21344","21345","21346","21347","21348","21349","21350","21351","21352","21353","21354","21355","21356","21357","21358","21359","21360","21361","21362","21363","21364","21365","21366","21367","21368","21369","21370","21371","21372","21373","21374","21375","21376","21377","21378","21379","21380","21381","21382","21383","21384","21385","21386","21387","21388","21389","21390","21391","21392","21393","21394","21395","21396","21397","21398","21399","21400","21401","21402","21403","21404","21405","21406","21407","21408","21409","21410","21411","21412","21413","21414","21415","21416","21417","21418","21419","21420","21421","21422","21423","21424","21425","21426","21427","21428","21429","21430","21431","21432","21433","21434","21435","21436","21437","21438","21439","21440","21441","21442","21443","21444","21445","21446","21447","21448","21449","21450","21451","21452","21453","21454","21455","21456","21457","21458","21459","21460","21461","21462","21463","21464","21465","21466","21467","21468","21469","21470","21471","21472","21473","21474","21475","21476","21477","21478","21479","21480","21481","21482","21483","21484","21485","21486","21487","21488","21489","21490","21491","21492","21493","21494","21495","21496","21497","21498","21499","21500","21501","21502","21503","21504","21505","21506","21507","21508","21509","21510","21511","21512","21513","21514","21515","21516","21517","21518","21519","21520","21521","21522","21523","21524","21525","21526","21527","21528","21529","21530","21531","21532","21533","21534","21535","21536","21537","21538","21539","21540","21541","21542","21543","21544","21545","21546","21547","21548","21549","21550","21551","21552","21553","21554","21555","21556","21557","21558","21559","21560","21561","21562","21563","21564","21565","21566","21567","21568","21569","21570","21571","21572","21573","21574","21575","21576","21577","21578","21579","21580","21581","21582","21583","21584","21585","21586","21587","21588","21589","21590","21591","21592","21593","21594","21595","21596","21597","21598","21599","21600","21601","21602","21603","21604","21605","21606","21607","21608","21609","21610","21611","21612","21613","21614","21615","21616","21617","21618","21619","21620","21621","21622","21623","21624","21625","21626","21627","21628","21629","21630","21631","21632","21633","21634","21635","21636","21637","21638","21639","21640","21641","21642","21643","21644","21645","21646","21647","21648","21649","21650","21651","21652","21653","21654","21655","21656","21657","21658","21659","21660","21661","21662","21663","21664","21665","21666","21667","21668","21669","21670","21671","21672","21673","21674","21675","21676","21677","21678","21679","21680","21681","21682","21683","21684","21685","21686","21687","21688","21689","21690","21691","21692","21693","21694","21695","21696","21697","21698","21699","21700","21701","21702","21703","21704","21705","21706","21707","21708","21709","21710","21711","21712","21713","21714","21715","21716","21717","21718","21719","21720","21721","21722","21723","21724","21725","21726","21727","21728","21729","21730","21731","21732","21733","21734","21735","21736","21737","21738","21739","21740","21741","21742","21743","21744","21745","21746","21747","21748","21749","21750","21751","21752","21753","21754","21755","21756","21757","21758","21759","21760","21761","21762","21763","21764","21765","21766","21767","21768","21769","21770","21771","21772","21773","21774","21775","21776","21777","21778","21779","21780","21781","21782","21783","21784","21785","21786","21787","21788","21789","21790","21791","21792","21793","21794","21795","21796","21797","21798","21799","21800","21801","21802","21803","21804","21805","21806","21807","21808","21809","21810","21811","21812","21813","21814","21815","21816","21817","21818","21819","21820","21821","21822","21823","21824","21825","21826","21827","21828","21829","21830","21831","21832","21833","21834","21835","21836","21837","21838","21839","21840","21841","21842","21843","21844","21845","21846","21847","21848","21849","21850","21851","21852","21853","21854","21855","21856","21857","21858","21859","21860","21861","21862","21863","21864","21865","21866","21867","21868","21869","21870","21871","21872","21873","21874","21875","21876","21877","21878","21879","21880","21881","21882","21883","21884","21885","21886","21887","21888","21889","21890","21891","21892","21893","21894","21895","21896","21897","21898","21899","21900","21901","21902","21903","21904","21905","21906","21907","21908","21909","21910","21911","21912","21913","21914","21915","21916","21917","21918","21919","21920","21921","21922","21923","21924","21925","21926","21927","21928","21929","21930","21931","21932","21933","21934","21935","21936","21937","21938","21939","21940","21941","21942","21943","21944","21945","21946","21947","21948","21949","21950","21951","21952","21953","21954","21955","21956","21957","21958","21959","21960","21961","21962","21963","21964","21965","21966","21967","21968","21969","21970","21971","21972","21973","21974","21975","21976","21977","21978","21979","21980","21981","21982","21983","21984","21985","21986","21987","21988","21989","21990","21991","21992","21993","21994","21995","21996","21997","21998","21999","22000","22001","22002","22003","22004","22005","22006","22007","22008","22009","22010","22011","22012","22013","22014","22015","22016","22017","22018","22019","22020","22021","22022","22023","22024","22025","22026","22027","22028","22029","22030","22031","22032","22033","22034","22035","22036","22037","22038","22039","22040","22041","22042","22043","22044","22045","22046","22047","22048","22049","22050","22051","22052","22053","22054","22055","22056","22057","22058","22059","22060","22061","22062","22063","22064","22065","22066","22067","22068","22069","22070","22071","22072","22073","22074","22075","22076","22077","22078","22079","22080","22081","22082","22083","22084","22085","22086","22087","22088","22089","22090","22091","22092","22093","22094","22095","22096","22097","22098","22099","22100","22101","22102","22103","22104","22105","22106","22107","22108","22109","22110","22111","22112","22113","22114","22115","22116","22117","22118","22119","22120","22121","22122","22123","22124","22125","22126","22127","22128","22129","22130","22131","22132","22133","22134","22135","22136","22137","22138","22139","22140","22141","22142","22143","22144","22145","22146","22147","22148","22149","22150","22151","22152","22153","22154","22155","22156","22157","22158","22159","22160","22161","22162","22163","22164","22165","22166","22167","22168","22169","22170","22171","22172","22173","22174","22175","22176","22177","22178","22179","22180","22181","22182","22183","22184","22185","22186","22187","22188","22189","22190","22191","22192","22193","22194","22195","22196","22197","22198","22199","22200","22201","22202","22203","22204","22205","22206","22207","22208","22209","22210","22211","22212","22213","22214","22215","22216","22217","22218","22219","22220","22221","22222","22223","22224","22225","22226","22227","22228","22229","22230","22231","22232","22233","22234","22235","22236","22237","22238","22239","22240","22241","22242","22243","22244","22245","22246","22247","22248","22249","22250","22251","22252","22253","22254","22255","22256","22257","22258","22259","22260","22261","22262","22263","22264","22265","22266","22267","22268","22269","22270","22271","22272","22273","22274","22275","22276","22277","22278","22279","22280","22281","22282","22283","22284","22285","22286","22287","22288","22289","22290","22291","22292","22293","22294","22295","22296","22297","22298","22299","22300","22301","22302","22303","22304","22305","22306","22307","22308","22309","22310","22311","22312","22313","22314","22315","22316","22317","22318","22319","22320","22321","22322","22323","22324","22325","22326","22327","22328","22329","22330","22331","22332","22333","22334","22335","22336","22337","22338","22339","22340","22341","22342","22343","22344","22345","22346","22347","22348","22349","22350","22351","22352","22353","22354","22355","22356","22357","22358","22359","22360","22361","22362","22363","22364","22365","22366","22367","22368","22369","22370","22371","22372","22373","22374","22375","22376","22377","22378","22379","22380","22381","22382","22383","22384","22385","22386","22387","22388","22389","22390","22391","22392","22393","22394","22395","22396","22397","22398","22399","22400","22401","22402","22403","22404","22405","22406","22407","22408","22409","22410","22411","22412","22413","22414","22415","22416","22417","22418","22419","22420","22421","22422","22423","22424","22425","22426","22427","22428","22429","22430","22431","22432","22433","22434","22435","22436","22437","22438","22439","22440","22441","22442","22443","22444","22445","22446","22447","22448","22449","22450","22451","22452","22453","22454","22455","22456","22457","22458","22459","22460","22461","22462","22463","22464","22465","22466","22467","22468","22469","22470","22471","22472","22473","22474","22475","22476","22477","22478","22479","22480","22481","22482","22483","22484","22485","22486","22487","22488","22489","22490","22491","22492","22493","22494","22495","22496","22497","22498","22499","22500","22501","22502","22503","22504","22505","22506","22507","22508","22509","22510","22511","22512","22513","22514","22515","22516","22517","22518","22519","22520","22521","22522","22523","22524","22525","22526","22527","22528","22529","22530","22531","22532","22533","22534","22535","22536","22537","22538","22539","22540","22541","22542","22543","22544","22545","22546","22547","22548","22549","22550","22551","22552","22553","22554","22555","22556","22557","22558","22559","22560","22561","22562","22563","22564","22565","22566","22567","22568","22569","22570","22571","22572","22573","22574","22575","22576","22577","22578","22579","22580","22581","22582","22583","22584","22585","22586","22587","22588","22589","22590","22591","22592","22593","22594","22595","22596","22597","22598","22599","22600","22601","22602","22603","22604","22605","22606","22607","22608","22609","22610","22611","22612","22613","22614","22615","22616","22617","22618","22619","22620","22621","22622","22623","22624","22625","22626","22627","22628","22629","22630","22631","22632","22633","22634","22635","22636","22637","22638","22639","22640","22641","22642","22643","22644","22645","22646","22647","22648","22649","22650","22651","22652","22653","22654","22655","22656","22657","22658","22659","22660","22661","22662","22663","22664","22665","22666","22667","22668","22669","22670","22671","22672","22673","22674","22675","22676","22677","22678","22679","22680","22681","22682","22683","22684","22685","22686","22687","22688","22689","22690","22691","22692","22693","22694","22695","22696","22697","22698","22699","22700","22701","22702","22703","22704","22705","22706","22707","22708","22709","22710","22711","22712","22713","22714","22715","22716","22717","22718","22719","22720","22721","22722","22723","22724","22725","22726","22727","22728","22729","22730","22731","22732","22733","22734","22735","22736","22737","22738","22739","22740","22741","22742","22743","22744","22745","22746","22747","22748","22749","22750","22751","22752","22753","22754","22755","22756","22757","22758","22759","22760","22761","22762","22763","22764","22765","22766","22767","22768","22769","22770","22771","22772","22773","22774","22775","22776","22777","22778","22779","22780","22781","22782","22783","22784","22785","22786","22787","22788","22789","22790","22791","22792","22793","22794","22795","22796","22797","22798","22799","22800","22801","22802","22803","22804","22805","22806","22807","22808","22809","22810","22811","22812","22813","22814","22815","22816","22817","22818","22819","22820","22821","22822","22823","22824","22825","22826","22827","22828","22829","22830","22831","22832","22833","22834","22835","22836","22837","22838","22839","22840","22841","22842","22843","22844","22845","22846","22847","22848","22849","22850","22851","22852","22853","22854","22855","22856","22857","22858","22859","22860","22861","22862","22863","22864","22865","22866","22867","22868","22869","22870","22871","22872","22873","22874","22875","22876","22877","22878","22879","22880","22881","22882","22883","22884","22885","22886","22887","22888","22889","22890","22891","22892","22893","22894","22895","22896","22897","22898","22899","22900","22901","22902","22903","22904","22905","22906","22907","22908","22909","22910","22911","22912","22913","22914","22915","22916","22917","22918","22919","22920","22921","22922","22923","22924","22925","22926","22927","22928","22929","22930","22931","22932","22933","22934","22935","22936","22937","22938","22939","22940","22941","22942","22943","22944","22945","22946","22947","22948","22949","22950","22951","22952","22953","22954","22955","22956","22957","22958","22959","22960","22961","22962","22963","22964","22965","22966","22967","22968","22969","22970","22971","22972","22973","22974","22975","22976","22977","22978","22979","22980","22981","22982","22983","22984","22985","22986","22987","22988","22989","22990","22991","22992","22993","22994","22995","22996","22997","22998","22999","23000","23001","23002","23003","23004","23005","23006","23007","23008","23009","23010","23011","23012","23013","23014","23015","23016","23017","23018","23019","23020","23021","23022","23023","23024","23025","23026","23027","23028","23029","23030","23031","23032","23033","23034","23035","23036","23037","23038","23039","23040","23041","23042","23043","23044","23045","23046","23047","23048","23049","23050","23051","23052","23053","23054","23055","23056","23057","23058","23059","23060","23061","23062","23063","23064","23065","23066","23067","23068","23069","23070","23071","23072","23073","23074","23075","23076","23077","23078","23079","23080","23081","23082","23083","23084","23085","23086","23087","23088","23089","23090","23091","23092","23093","23094","23095","23096","23097","23098","23099","23100","23101","23102","23103","23104","23105","23106","23107","23108","23109","23110","23111","23112","23113","23114","23115","23116","23117","23118","23119","23120","23121","23122","23123","23124","23125","23126","23127","23128","23129","23130","23131","23132","23133","23134","23135","23136","23137","23138","23139","23140","23141","23142","23143","23144","23145","23146","23147","23148","23149","23150","23151","23152","23153","23154","23155","23156","23157","23158","23159","23160","23161","23162","23163","23164","23165","23166","23167","23168","23169","23170","23171","23172","23173","23174","23175","23176","23177","23178","23179","23180","23181","23182","23183","23184","23185","23186","23187","23188","23189","23190","23191","23192","23193","23194","23195","23196","23197","23198","23199","23200","23201","23202","23203","23204","23205","23206","23207","23208","23209","23210","23211","23212","23213","23214","23215","23216","23217","23218","23219","23220","23221","23222","23223","23224","23225","23226","23227","23228","23229","23230","23231","23232","23233","23234","23235","23236","23237","23238","23239","23240","23241","23242","23243","23244","23245","23246","23247","23248","23249","23250","23251","23252","23253","23254","23255","23256","23257","23258","23259","23260","23261","23262","23263","23264","23265","23266","23267","23268","23269","23270","23271","23272","23273","23274","23275","23276","23277","23278","23279","23280","23281","23282","23283","23284","23285","23286","23287","23288","23289","23290","23291","23292","23293","23294","23295","23296","23297","23298","23299","23300","23301","23302","23303","23304","23305","23306","23307","23308","23309","23310","23311","23312","23313","23314","23315","23316","23317","23318","23319","23320","23321","23322","23323","23324","23325","23326","23327","23328","23329","23330","23331","23332","23333","23334","23335","23336","23337","23338","23339","23340","23341","23342","23343","23344","23345","23346","23347","23348","23349","23350","23351","23352","23353","23354","23355","23356","23357","23358","23359","23360","23361","23362","23363","23364","23365","23366","23367","23368","23369","23370","23371","23372","23373","23374","23375","23376","23377","23378","23379","23380","23381","23382","23383","23384","23385","23386","23387","23388","23389","23390","23391","23392","23393","23394","23395","23396","23397","23398","23399","23400","23401","23402","23403","23404","23405","23406","23407","23408","23409","23410","23411","23412","23413","23414","23415","23416","23417","23418","23419","23420","23421","23422","23423","23424","23425","23426","23427","23428","23429","23430","23431","23432","23433","23434","23435","23436","23437","23438","23439","23440","23441","23442","23443","23444","23445","23446","23447","23448","23449","23450","23451","23452","23453","23454","23455","23456","23457","23458","23459","23460","23461","23462","23463","23464","23465","23466","23467","23468","23469","23470","23471","23472","23473","23474","23475","23476","23477","23478","23479","23480","23481","23482","23483","23484","23485","23486","23487","23488","23489","23490","23491","23492","23493","23494","23495","23496","23497","23498","23499","23500","23501","23502","23503","23504","23505","23506","23507","23508","23509","23510","23511","23512","23513","23514","23515","23516","23517","23518","23519","23520","23521","23522","23523","23524","23525","23526","23527","23528","23529","23530","23531","23532","23533","23534","23535","23536","23537","23538","23539","23540","23541","23542","23543","23544","23545","23546","23547","23548","23549","23550","23551","23552","23553","23554","23555","23556","23557","23558","23559","23560","23561","23562","23563","23564","23565","23566","23567","23568","23569","23570","23571","23572","23573","23574","23575","23576","23577","23578","23579","23580","23581","23582","23583","23584","23585","23586","23587","23588","23589","23590","23591","23592","23593","23594","23595","23596","23597","23598","23599","23600","23601","23602","23603","23604","23605","23606","23607","23608","23609","23610","23611","23612","23613","23614","23615","23616","23617","23618","23619","23620","23621","23622","23623","23624","23625","23626","23627","23628","23629","23630","23631","23632","23633","23634","23635","23636","23637","23638","23639","23640","23641","23642","23643","23644","23645","23646","23647","23648","23649","23650","23651","23652","23653","23654","23655","23656","23657","23658","23659","23660","23661","23662","23663","23664","23665","23666","23667","23668","23669","23670","23671","23672","23673","23674","23675","23676","23677","23678","23679","23680","23681","23682","23683","23684","23685","23686","23687","23688","23689","23690","23691","23692","23693","23694","23695","23696","23697","23698","23699","23700","23701","23702","23703","23704","23705","23706","23707","23708","23709","23710","23711","23712","23713","23714","23715","23716","23717","23718","23719","23720","23721","23722","23723","23724","23725","23726","23727","23728","23729","23730","23731","23732","23733","23734","23735","23736","23737","23738","23739","23740","23741","23742","23743","23744","23745","23746","23747","23748","23749","23750","23751","23752","23753","23754","23755","23756","23757","23758","23759","23760","23761","23762","23763","23764","23765","23766","23767","23768","23769","23770","23771","23772","23773","23774","23775","23776","23777","23778","23779","23780","23781","23782","23783","23784","23785","23786","23787","23788","23789","23790","23791","23792","23793","23794","23795","23796","23797","23798","23799","23800","23801","23802","23803","23804","23805","23806","23807","23808","23809","23810","23811","23812","23813","23814","23815","23816","23817","23818","23819","23820","23821","23822","23823","23824","23825","23826","23827","23828","23829","23830","23831","23832","23833","23834","23835","23836","23837","23838","23839","23840","23841","23842","23843","23844","23845","23846","23847","23848","23849","23850","23851","23852","23853","23854","23855","23856","23857","23858","23859","23860","23861","23862","23863","23864","23865","23866","23867","23868","23869","23870","23871","23872","23873","23874","23875","23876","23877","23878","23879","23880","23881","23882","23883","23884","23885","23886","23887","23888","23889","23890","23891","23892","23893","23894","23895","23896","23897","23898","23899","23900","23901","23902","23903","23904","23905","23906","23907","23908","23909","23910","23911","23912","23913","23914","23915","23916","23917","23918","23919","23920","23921","23922","23923","23924","23925","23926","23927","23928","23929","23930","23931","23932","23933","23934","23935","23936","23937","23938","23939","23940","23941","23942","23943","23944","23945","23946","23947","23948","23949","23950","23951","23952","23953","23954","23955","23956","23957","23958","23959","23960","23961","23962","23963","23964","23965","23966","23967","23968","23969","23970","23971","23972","23973","23974","23975","23976","23977","23978","23979","23980","23981","23982","23983","23984","23985","23986","23987","23988","23989","23990","23991","23992","23993","23994","23995","23996","23997","23998","23999","24000","24001","24002","24003","24004","24005","24006","24007","24008","24009","24010","24011","24012","24013","24014","24015","24016","24017","24018","24019","24020","24021","24022","24023","24024","24025","24026","24027","24028","24029","24030","24031","24032","24033","24034","24035","24036","24037","24038","24039","24040","24041","24042","24043","24044","24045","24046","24047","24048","24049","24050","24051","24052","24053","24054","24055","24056","24057","24058","24059","24060","24061","24062","24063","24064","24065","24066","24067","24068","24069","24070","24071","24072","24073","24074","24075","24076","24077","24078","24079","24080","24081","24082","24083","24084","24085","24086","24087","24088","24089","24090","24091","24092","24093","24094","24095","24096","24097","24098","24099","24100","24101","24102","24103","24104","24105","24106","24107","24108","24109","24110","24111","24112","24113","24114","24115","24116","24117","24118","24119","24120","24121","24122","24123","24124","24125","24126","24127","24128","24129","24130","24131","24132","24133","24134","24135","24136","24137","24138","24139","24140","24141","24142","24143","24144","24145","24146","24147","24148","24149","24150","24151","24152","24153","24154","24155","24156","24157","24158","24159","24160","24161","24162","24163","24164","24165","24166","24167","24168","24169","24170","24171","24172","24173","24174","24175","24176","24177","24178","24179","24180","24181","24182","24183","24184","24185","24186","24187","24188","24189","24190","24191","24192","24193","24194","24195","24196","24197","24198","24199","24200","24201","24202","24203","24204","24205","24206","24207","24208","24209","24210","24211","24212","24213","24214","24215","24216","24217","24218","24219","24220","24221","24222","24223","24224","24225","24226","24227","24228","24229","24230","24231","24232","24233","24234","24235","24236","24237","24238","24239","24240","24241","24242","24243","24244","24245","24246","24247","24248","24249","24250","24251","24252","24253","24254","24255","24256","24257","24258","24259","24260","24261","24262","24263","24264","24265","24266","24267","24268","24269","24270","24271","24272","24273","24274","24275","24276","24277","24278","24279","24280","24281","24282","24283","24284","24285","24286","24287","24288","24289","24290","24291","24292","24293","24294","24295","24296","24297","24298","24299","24300","24301","24302","24303","24304","24305","24306","24307","24308","24309","24310","24311","24312","24313","24314","24315","24316","24317","24318","24319","24320","24321","24322","24323","24324","24325","24326","24327","24328","24329","24330","24331","24332","24333","24334","24335","24336","24337","24338","24339","24340","24341","24342","24343","24344","24345","24346","24347","24348","24349","24350","24351","24352","24353","24354","24355","24356","24357","24358","24359","24360","24361","24362","24363","24364","24365","24366","24367","24368","24369","24370","24371","24372","24373","24374","24375","24376","24377","24378","24379","24380","24381","24382","24383","24384","24385","24386","24387","24388","24389","24390","24391","24392","24393","24394","24395","24396","24397","24398","24399","24400","24401","24402","24403","24404","24405","24406","24407","24408","24409","24410","24411","24412","24413","24414","24415","24416","24417","24418","24419","24420","24421","24422","24423","24424","24425","24426","24427","24428","24429","24430","24431","24432","24433","24434","24435","24436","24437","24438","24439","24440","24441","24442","24443","24444","24445","24446","24447","24448","24449","24450","24451","24452","24453","24454","24455","24456","24457","24458","24459","24460","24461","24462","24463","24464","24465","24466","24467","24468","24469","24470","24471","24472","24473","24474","24475","24476","24477","24478","24479","24480","24481","24482","24483","24484","24485","24486","24487","24488","24489","24490","24491","24492","24493","24494","24495","24496","24497","24498","24499","24500","24501","24502","24503","24504","24505","24506","24507","24508","24509","24510","24511","24512","24513","24514","24515","24516","24517","24518","24519","24520","24521","24522","24523","24524","24525","24526","24527","24528","24529","24530","24531","24532","24533","24534","24535","24536","24537","24538","24539","24540","24541","24542","24543","24544","24545","24546","24547","24548","24549","24550","24551","24552","24553","24554","24555","24556","24557","24558","24559","24560","24561","24562","24563","24564","24565","24566","24567","24568","24569","24570","24571","24572","24573","24574","24575","24576","24577","24578","24579","24580","24581","24582","24583","24584","24585","24586","24587","24588","24589","24590","24591","24592","24593","24594","24595","24596","24597","24598","24599","24600","24601","24602","24603","24604","24605","24606","24607","24608","24609","24610","24611","24612","24613","24614","24615","24616","24617","24618","24619","24620","24621","24622","24623","24624","24625","24626","24627","24628","24629","24630","24631","24632","24633","24634","24635","24636","24637","24638","24639","24640","24641","24642","24643","24644","24645","24646","24647","24648","24649","24650","24651","24652","24653","24654","24655","24656","24657","24658","24659","24660","24661","24662","24663","24664","24665","24666","24667","24668","24669","24670","24671","24672","24673","24674","24675","24676","24677","24678","24679","24680","24681","24682","24683","24684","24685","24686","24687","24688","24689","24690","24691","24692","24693","24694","24695","24696","24697","24698","24699","24700","24701","24702","24703","24704","24705","24706","24707","24708","24709","24710","24711","24712","24713","24714","24715","24716","24717","24718","24719","24720","24721","24722","24723","24724","24725","24726","24727","24728","24729","24730","24731","24732","24733","24734","24735","24736","24737","24738","24739","24740","24741","24742","24743","24744","24745","24746","24747","24748","24749","24750","24751","24752","24753","24754","24755","24756","24757","24758","24759","24760","24761","24762","24763","24764","24765","24766","24767","24768","24769","24770","24771","24772","24773","24774","24775","24776","24777","24778","24779","24780","24781","24782","24783","24784","24785","24786","24787","24788","24789","24790","24791","24792","24793","24794","24795","24796","24797","24798","24799","24800","24801","24802","24803","24804","24805","24806","24807","24808","24809","24810","24811","24812","24813","24814","24815","24816","24817","24818","24819","24820","24821","24822","24823","24824","24825","24826","24827","24828","24829","24830","24831","24832","24833","24834","24835","24836","24837","24838","24839","24840","24841","24842","24843","24844","24845","24846","24847","24848","24849","24850","24851","24852","24853","24854","24855","24856","24857","24858","24859","24860","24861","24862","24863","24864","24865","24866","24867","24868","24869","24870","24871","24872","24873","24874","24875","24876","24877","24878","24879","24880","24881","24882","24883","24884","24885","24886","24887","24888","24889","24890","24891","24892","24893","24894","24895","24896","24897","24898","24899","24900","24901","24902","24903","24904","24905","24906","24907","24908","24909","24910","24911","24912","24913","24914","24915","24916","24917","24918","24919","24920","24921","24922","24923","24924","24925","24926","24927","24928","24929","24930","24931","24932","24933","24934","24935","24936","24937","24938","24939","24940","24941","24942","24943","24944","24945","24946","24947","24948","24949","24950","24951","24952","24953","24954","24955","24956","24957","24958","24959","24960","24961","24962","24963","24964","24965","24966","24967","24968","24969","24970","24971","24972","24973","24974","24975","24976","24977","24978","24979","24980","24981","24982","24983","24984","24985","24986","24987","24988","24989","24990","24991","24992","24993","24994","24995","24996","24997","24998","24999","25000","25001","25002","25003","25004","25005","25006","25007","25008","25009","25010","25011","25012","25013","25014","25015","25016","25017","25018","25019","25020","25021","25022","25023","25024","25025","25026","25027","25028","25029","25030","25031","25032","25033","25034","25035","25036","25037","25038","25039","25040","25041","25042","25043","25044","25045","25046","25047","25048","25049","25050","25051","25052","25053","25054","25055","25056","25057","25058","25059","25060","25061","25062","25063","25064","25065","25066","25067","25068","25069","25070","25071","25072","25073","25074","25075","25076","25077","25078","25079","25080","25081","25082","25083","25084","25085","25086","25087","25088","25089","25090","25091","25092","25093","25094","25095","25096","25097","25098","25099","25100","25101","25102","25103","25104","25105","25106","25107","25108","25109","25110","25111","25112","25113","25114","25115","25116","25117","25118","25119","25120","25121","25122","25123","25124","25125","25126","25127","25128","25129","25130","25131","25132","25133","25134","25135","25136","25137","25138","25139","25140","25141","25142","25143","25144","25145","25146","25147","25148","25149","25150","25151","25152","25153","25154","25155","25156","25157","25158","25159","25160","25161","25162","25163","25164","25165","25166","25167","25168","25169","25170","25171","25172","25173","25174","25175","25176","25177","25178","25179","25180","25181","25182","25183","25184","25185","25186","25187","25188","25189","25190","25191","25192","25193","25194","25195","25196","25197","25198","25199","25200","25201","25202","25203","25204","25205","25206","25207","25208","25209","25210","25211","25212","25213","25214","25215","25216","25217","25218","25219","25220","25221","25222","25223","25224","25225","25226","25227","25228","25229","25230","25231","25232","25233","25234","25235","25236","25237","25238","25239","25240","25241","25242","25243","25244","25245","25246","25247","25248","25249","25250","25251","25252","25253","25254","25255","25256","25257","25258","25259","25260","25261","25262","25263","25264","25265","25266","25267","25268","25269","25270","25271","25272","25273","25274","25275","25276","25277","25278","25279","25280","25281","25282","25283","25284","25285","25286","25287","25288","25289","25290","25291","25292","25293","25294","25295","25296","25297","25298","25299","25300","25301","25302","25303","25304","25305","25306","25307","25308","25309","25310","25311","25312","25313","25314","25315","25316","25317","25318","25319","25320","25321","25322","25323","25324","25325","25326","25327","25328","25329","25330","25331","25332","25333","25334","25335","25336","25337","25338","25339","25340","25341","25342","25343","25344","25345","25346","25347","25348","25349","25350","25351","25352","25353","25354","25355","25356","25357","25358","25359","25360","25361","25362","25363","25364","25365","25366","25367","25368","25369","25370","25371","25372","25373","25374","25375","25376","25377","25378","25379","25380","25381","25382","25383","25384","25385","25386","25387","25388","25389","25390","25391","25392","25393","25394","25395","25396","25397","25398","25399","25400","25401","25402","25403","25404","25405","25406","25407","25408","25409","25410","25411","25412","25413","25414","25415","25416","25417","25418","25419","25420","25421","25422","25423","25424","25425","25426","25427","25428","25429","25430","25431","25432","25433","25434","25435","25436","25437","25438","25439","25440","25441","25442","25443","25444","25445","25446","25447","25448","25449","25450","25451","25452","25453","25454","25455","25456","25457","25458","25459","25460","25461","25462","25463","25464","25465","25466","25467","25468","25469","25470","25471","25472","25473","25474","25475","25476","25477","25478","25479","25480","25481","25482","25483","25484","25485","25486","25487","25488","25489","25490","25491","25492","25493","25494","25495","25496","25497","25498","25499","25500","25501","25502","25503","25504","25505","25506","25507","25508","25509","25510","25511","25512","25513","25514","25515","25516","25517","25518","25519","25520","25521","25522","25523","25524","25525","25526","25527","25528","25529","25530","25531","25532","25533","25534","25535","25536","25537","25538","25539","25540","25541","25542","25543","25544","25545","25546","25547","25548","25549","25550","25551","25552","25553","25554","25555","25556","25557","25558","25559","25560","25561","25562","25563","25564","25565","25566","25567","25568","25569","25570","25571","25572","25573","25574","25575","25576","25577","25578","25579","25580","25581","25582","25583","25584","25585","25586","25587","25588","25589","25590","25591","25592","25593","25594","25595","25596","25597","25598","25599","25600","25601","25602","25603","25604","25605","25606","25607","25608","25609","25610","25611","25612","25613","25614","25615","25616","25617","25618","25619","25620","25621","25622","25623","25624","25625","25626","25627","25628","25629","25630","25631","25632","25633","25634","25635","25636","25637","25638","25639","25640","25641","25642","25643","25644","25645","25646","25647","25648","25649","25650","25651","25652","25653","25654","25655","25656","25657","25658","25659","25660","25661","25662","25663","25664","25665","25666","25667","25668","25669","25670","25671","25672","25673","25674","25675","25676","25677","25678","25679","25680","25681","25682","25683","25684","25685","25686","25687","25688","25689","25690","25691","25692","25693","25694","25695","25696","25697","25698","25699","25700","25701","25702","25703","25704","25705","25706","25707","25708","25709","25710","25711","25712","25713","25714","25715","25716","25717","25718","25719","25720","25721","25722","25723","25724","25725","25726","25727","25728","25729","25730","25731","25732","25733","25734","25735","25736","25737","25738","25739","25740","25741","25742","25743","25744","25745","25746","25747","25748","25749","25750","25751","25752","25753","25754","25755","25756","25757","25758","25759","25760","25761","25762","25763","25764","25765","25766","25767","25768","25769","25770","25771","25772","25773","25774","25775","25776","25777","25778","25779","25780","25781","25782","25783","25784","25785","25786","25787","25788","25789","25790","25791","25792","25793","25794","25795","25796","25797","25798","25799","25800","25801","25802","25803","25804","25805","25806","25807","25808","25809","25810","25811","25812","25813","25814","25815","25816","25817","25818","25819","25820","25821","25822","25823","25824","25825","25826","25827","25828","25829","25830","25831","25832","25833","25834","25835","25836","25837","25838","25839","25840","25841","25842","25843","25844","25845","25846","25847","25848","25849","25850","25851","25852","25853","25854","25855","25856","25857","25858","25859","25860","25861","25862","25863","25864","25865","25866","25867","25868","25869","25870","25871","25872","25873","25874","25875","25876","25877","25878","25879","25880","25881","25882","25883","25884","25885","25886","25887","25888","25889","25890","25891","25892","25893","25894","25895","25896","25897","25898","25899","25900","25901","25902","25903","25904","25905","25906","25907","25908","25909","25910","25911","25912","25913","25914","25915","25916","25917","25918","25919","25920","25921","25922","25923","25924","25925","25926","25927","25928","25929","25930","25931","25932","25933","25934","25935","25936","25937","25938","25939","25940","25941","25942","25943","25944","25945","25946","25947","25948","25949","25950","25951","25952","25953","25954","25955","25956","25957","25958","25959","25960","25961","25962","25963","25964","25965","25966","25967","25968","25969","25970","25971","25972","25973","25974","25975","25976","25977","25978","25979","25980","25981","25982","25983","25984","25985","25986","25987","25988","25989","25990","25991","25992","25993","25994","25995","25996","25997","25998","25999","26000","26001","26002","26003","26004","26005","26006","26007","26008","26009","26010","26011","26012","26013","26014","26015","26016","26017","26018","26019","26020","26021","26022","26023","26024","26025","26026","26027","26028","26029","26030","26031","26032","26033","26034","26035","26036","26037","26038","26039","26040","26041","26042","26043","26044","26045","26046","26047","26048","26049","26050","26051","26052","26053","26054","26055","26056","26057","26058","26059","26060","26061","26062","26063","26064","26065","26066","26067","26068","26069","26070","26071","26072","26073","26074","26075","26076","26077","26078","26079","26080","26081","26082","26083","26084","26085","26086","26087","26088","26089","26090","26091","26092","26093","26094","26095","26096","26097","26098","26099","26100","26101","26102","26103","26104","26105","26106","26107","26108","26109","26110","26111","26112","26113","26114","26115","26116","26117","26118","26119","26120","26121","26122","26123","26124","26125","26126","26127","26128","26129","26130","26131","26132","26133","26134","26135","26136","26137","26138","26139","26140","26141","26142","26143","26144","26145","26146","26147","26148","26149","26150","26151","26152","26153","26154","26155","26156","26157","26158","26159","26160","26161","26162","26163","26164","26165","26166","26167","26168","26169","26170","26171","26172","26173","26174","26175","26176","26177","26178","26179","26180","26181","26182","26183","26184","26185","26186","26187","26188","26189","26190","26191","26192","26193","26194","26195","26196","26197","26198","26199","26200","26201","26202","26203","26204","26205","26206","26207","26208","26209","26210","26211","26212","26213","26214","26215","26216","26217","26218","26219","26220","26221","26222","26223","26224","26225","26226","26227","26228","26229","26230","26231","26232","26233","26234","26235","26236","26237","26238","26239","26240","26241","26242","26243","26244","26245","26246","26247","26248","26249","26250","26251","26252","26253","26254","26255","26256","26257","26258","26259","26260","26261","26262","26263","26264","26265","26266","26267","26268","26269","26270","26271","26272","26273","26274","26275","26276","26277","26278","26279","26280","26281","26282","26283","26284","26285","26286","26287","26288","26289","26290","26291","26292","26293","26294","26295","26296","26297","26298","26299","26300","26301","26302","26303","26304","26305","26306","26307","26308","26309","26310","26311","26312","26313","26314","26315","26316","26317","26318","26319","26320","26321","26322","26323","26324","26325","26326","26327","26328","26329","26330","26331","26332","26333","26334","26335","26336","26337","26338","26339","26340","26341","26342","26343","26344","26345","26346","26347","26348","26349","26350","26351","26352","26353","26354","26355","26356","26357","26358","26359","26360","26361","26362","26363","26364","26365","26366","26367","26368","26369","26370","26371","26372","26373","26374","26375","26376","26377","26378","26379","26380","26381","26382","26383","26384","26385","26386","26387","26388","26389","26390","26391","26392","26393","26394","26395","26396","26397","26398","26399","26400","26401","26402","26403","26404","26405","26406","26407","26408","26409","26410","26411","26412","26413","26414","26415","26416","26417","26418","26419","26420","26421","26422","26423","26424","26425","26426","26427","26428","26429","26430","26431","26432","26433","26434","26435","26436","26437","26438","26439","26440","26441","26442","26443","26444","26445","26446","26447","26448","26449","26450","26451","26452","26453","26454","26455","26456","26457","26458","26459","26460","26461","26462","26463","26464","26465","26466","26467","26468","26469","26470","26471","26472","26473","26474","26475","26476","26477","26478","26479","26480","26481","26482","26483","26484","26485","26486","26487","26488","26489","26490","26491","26492","26493","26494","26495","26496","26497","26498","26499","26500","26501","26502","26503","26504","26505","26506","26507","26508","26509","26510","26511","26512","26513","26514","26515","26516","26517","26518","26519","26520","26521","26522","26523","26524","26525","26526","26527","26528","26529","26530","26531","26532","26533","26534","26535","26536","26537","26538","26539","26540","26541","26542","26543","26544","26545","26546","26547","26548","26549","26550","26551","26552","26553","26554","26555","26556","26557","26558","26559","26560","26561","26562","26563","26564","26565","26566","26567","26568","26569","26570","26571","26572","26573","26574","26575","26576","26577","26578","26579","26580","26581","26582","26583","26584","26585","26586","26587","26588","26589","26590","26591","26592","26593","26594","26595","26596","26597","26598","26599","26600","26601","26602","26603","26604","26605","26606","26607","26608","26609","26610","26611","26612","26613","26614","26615","26616","26617","26618","26619","26620","26621","26622","26623","26624","26625","26626","26627","26628","26629","26630","26631","26632","26633","26634","26635","26636","26637","26638","26639","26640","26641","26642","26643","26644","26645","26646","26647","26648","26649","26650","26651","26652","26653","26654","26655","26656","26657","26658","26659","26660","26661","26662","26663","26664","26665","26666","26667","26668","26669","26670","26671","26672","26673","26674","26675","26676","26677","26678","26679","26680","26681","26682","26683","26684","26685","26686","26687","26688","26689","26690","26691","26692","26693","26694","26695","26696","26697","26698","26699","26700","26701","26702","26703","26704","26705","26706","26707","26708","26709","26710","26711","26712","26713","26714","26715","26716","26717","26718","26719","26720","26721","26722","26723","26724","26725","26726","26727","26728","26729","26730","26731","26732","26733","26734","26735","26736","26737","26738","26739","26740","26741","26742","26743","26744","26745","26746","26747","26748","26749","26750","26751","26752","26753","26754","26755","26756","26757","26758","26759","26760","26761","26762","26763","26764","26765","26766","26767","26768","26769","26770","26771","26772","26773","26774","26775","26776","26777","26778","26779","26780","26781","26782","26783","26784","26785","26786","26787","26788","26789","26790","26791","26792","26793","26794","26795","26796","26797","26798","26799","26800","26801","26802","26803","26804","26805","26806","26807","26808","26809","26810","26811","26812","26813","26814","26815","26816","26817","26818","26819","26820","26821","26822","26823","26824","26825","26826","26827","26828","26829","26830","26831","26832","26833","26834","26835","26836","26837","26838","26839","26840","26841","26842","26843","26844","26845","26846","26847","26848","26849","26850","26851","26852","26853","26854","26855","26856","26857","26858","26859","26860","26861","26862","26863","26864","26865","26866","26867","26868","26869","26870","26871","26872","26873","26874","26875","26876","26877","26878","26879","26880","26881","26882","26883","26884","26885","26886","26887","26888","26889","26890","26891","26892","26893","26894","26895","26896","26897","26898","26899","26900","26901","26902","26903","26904","26905","26906","26907","26908","26909","26910","26911","26912","26913","26914","26915","26916","26917","26918","26919","26920","26921","26922","26923","26924","26925","26926","26927","26928","26929","26930","26931","26932","26933","26934","26935","26936","26937","26938","26939","26940","26941","26942","26943","26944","26945","26946","26947","26948","26949","26950","26951","26952","26953","26954","26955","26956","26957","26958","26959","26960","26961","26962","26963","26964","26965","26966","26967","26968","26969","26970","26971","26972","26973","26974","26975","26976","26977","26978","26979","26980","26981","26982","26983","26984","26985","26986","26987","26988","26989","26990","26991","26992","26993","26994","26995","26996","26997","26998","26999","27000","27001","27002","27003","27004","27005","27006","27007","27008","27009","27010","27011","27012","27013","27014","27015","27016","27017","27018","27019","27020","27021","27022","27023","27024","27025","27026","27027","27028","27029","27030","27031","27032","27033","27034","27035","27036","27037","27038","27039","27040","27041","27042","27043","27044","27045","27046","27047","27048","27049","27050","27051","27052","27053","27054","27055","27056","27057","27058","27059","27060","27061","27062","27063","27064","27065","27066","27067","27068","27069","27070","27071","27072","27073","27074","27075","27076","27077","27078","27079","27080","27081","27082","27083","27084","27085","27086","27087","27088","27089","27090","27091","27092","27093","27094","27095","27096","27097","27098","27099","27100","27101","27102","27103","27104","27105","27106","27107","27108","27109","27110","27111","27112","27113","27114","27115","27116","27117","27118","27119","27120","27121","27122","27123","27124","27125","27126","27127","27128","27129","27130","27131","27132","27133","27134","27135","27136","27137","27138","27139","27140","27141","27142","27143","27144","27145","27146","27147","27148","27149","27150","27151","27152","27153","27154","27155","27156","27157","27158","27159","27160","27161","27162","27163","27164","27165","27166","27167","27168","27169","27170","27171","27172","27173","27174","27175","27176","27177","27178","27179","27180","27181","27182","27183","27184","27185","27186","27187","27188","27189","27190","27191","27192","27193","27194","27195","27196","27197","27198","27199","27200","27201","27202","27203","27204","27205","27206","27207","27208","27209","27210","27211","27212","27213","27214","27215","27216","27217","27218","27219","27220","27221","27222","27223","27224","27225","27226","27227","27228","27229","27230","27231","27232","27233","27234","27235","27236","27237","27238","27239","27240","27241","27242","27243","27244","27245","27246","27247","27248","27249","27250","27251","27252","27253","27254","27255","27256","27257","27258","27259","27260","27261","27262","27263","27264","27265","27266","27267","27268","27269","27270","27271","27272","27273","27274","27275","27276","27277","27278","27279","27280","27281","27282","27283","27284","27285","27286","27287","27288","27289","27290","27291","27292","27293","27294","27295","27296","27297","27298","27299","27300","27301","27302","27303","27304","27305","27306","27307","27308","27309","27310","27311","27312","27313","27314","27315","27316","27317","27318","27319","27320","27321","27322","27323","27324","27325","27326","27327","27328","27329","27330","27331","27332","27333","27334","27335","27336","27337","27338","27339","27340","27341","27342","27343","27344","27345","27346","27347","27348","27349","27350","27351","27352","27353","27354","27355","27356","27357","27358","27359","27360","27361","27362","27363","27364","27365","27366","27367","27368","27369","27370","27371","27372","27373","27374","27375","27376","27377","27378","27379","27380","27381","27382","27383","27384","27385","27386","27387","27388","27389","27390","27391","27392","27393","27394","27395","27396","27397","27398","27399","27400","27401","27402","27403","27404","27405","27406","27407","27408","27409","27410","27411","27412","27413","27414","27415","27416","27417","27418","27419","27420","27421","27422","27423","27424","27425","27426","27427","27428","27429","27430","27431","27432","27433","27434","27435","27436","27437","27438","27439","27440","27441","27442","27443","27444","27445","27446","27447","27448","27449","27450","27451","27452","27453","27454","27455","27456","27457","27458","27459","27460","27461","27462","27463","27464","27465","27466","27467","27468","27469","27470","27471","27472","27473","27474","27475","27476","27477","27478","27479","27480","27481","27482","27483","27484","27485","27486","27487","27488","27489","27490","27491","27492","27493","27494","27495","27496","27497","27498","27499","27500","27501","27502","27503","27504","27505","27506","27507","27508","27509","27510","27511","27512","27513","27514","27515","27516","27517","27518","27519","27520","27521","27522","27523","27524","27525","27526","27527","27528","27529","27530","27531","27532","27533","27534","27535","27536","27537","27538","27539","27540","27541","27542","27543","27544","27545","27546","27547","27548","27549","27550","27551","27552","27553","27554","27555","27556","27557","27558","27559","27560","27561","27562","27563","27564","27565","27566","27567","27568","27569","27570","27571","27572","27573","27574","27575","27576","27577","27578","27579","27580","27581","27582","27583","27584","27585","27586","27587","27588","27589","27590","27591","27592","27593","27594","27595","27596","27597","27598","27599","27600","27601","27602","27603","27604","27605","27606","27607","27608","27609","27610","27611","27612","27613","27614","27615","27616","27617","27618","27619","27620","27621","27622","27623","27624","27625","27626","27627","27628","27629","27630","27631","27632","27633","27634","27635","27636","27637","27638","27639","27640","27641","27642","27643","27644","27645","27646","27647","27648","27649","27650","27651","27652","27653","27654","27655","27656","27657","27658","27659","27660","27661","27662","27663","27664","27665","27666","27667","27668","27669","27670","27671","27672","27673","27674","27675","27676","27677","27678","27679","27680","27681","27682","27683","27684","27685","27686","27687","27688","27689","27690","27691","27692","27693","27694","27695","27696","27697","27698","27699","27700","27701","27702","27703","27704","27705","27706","27707","27708","27709","27710","27711","27712","27713","27714","27715","27716","27717","27718","27719","27720","27721","27722","27723","27724","27725","27726","27727","27728","27729","27730","27731","27732","27733","27734","27735","27736","27737","27738","27739","27740","27741","27742","27743","27744","27745","27746","27747","27748","27749","27750","27751","27752","27753","27754","27755","27756","27757","27758","27759","27760","27761","27762","27763","27764","27765","27766","27767","27768","27769","27770","27771","27772","27773","27774","27775","27776","27777","27778","27779","27780","27781","27782","27783","27784","27785","27786","27787","27788","27789","27790","27791","27792","27793","27794","27795","27796","27797","27798","27799","27800","27801","27802","27803","27804","27805","27806","27807","27808","27809","27810","27811","27812","27813","27814","27815","27816","27817","27818","27819","27820","27821","27822","27823","27824","27825","27826","27827","27828","27829","27830","27831","27832","27833","27834","27835","27836","27837","27838","27839","27840","27841","27842","27843","27844","27845","27846","27847","27848","27849","27850","27851","27852","27853","27854","27855","27856","27857","27858","27859","27860","27861","27862","27863","27864","27865","27866","27867","27868","27869","27870","27871","27872","27873","27874","27875","27876","27877","27878","27879","27880","27881","27882","27883","27884","27885","27886","27887","27888","27889","27890","27891","27892","27893","27894","27895","27896","27897","27898","27899","27900","27901","27902","27903","27904","27905","27906","27907","27908","27909","27910","27911","27912","27913","27914","27915","27916","27917","27918","27919","27920","27921","27922","27923","27924","27925","27926","27927","27928","27929","27930","27931","27932","27933","27934","27935","27936","27937","27938","27939","27940","27941","27942","27943","27944","27945","27946","27947","27948","27949","27950","27951","27952","27953","27954","27955","27956","27957","27958","27959","27960","27961","27962","27963","27964","27965","27966","27967","27968","27969","27970","27971","27972","27973","27974","27975","27976","27977","27978","27979","27980","27981","27982","27983","27984","27985","27986","27987","27988","27989","27990","27991","27992","27993","27994","27995","27996","27997","27998","27999","28000","28001","28002","28003","28004","28005","28006","28007","28008","28009","28010","28011","28012","28013","28014","28015","28016","28017","28018","28019","28020","28021","28022","28023","28024","28025","28026","28027","28028","28029","28030","28031","28032","28033","28034","28035","28036","28037","28038","28039","28040","28041","28042","28043","28044","28045","28046","28047","28048","28049","28050","28051","28052","28053","28054","28055","28056","28057","28058","28059","28060","28061","28062","28063","28064","28065","28066","28067","28068","28069","28070","28071","28072","28073","28074","28075","28076","28077","28078","28079","28080","28081","28082","28083","28084","28085","28086","28087","28088","28089","28090","28091","28092","28093","28094","28095","28096","28097","28098","28099","28100","28101","28102","28103","28104","28105","28106","28107","28108","28109","28110","28111","28112","28113","28114","28115","28116","28117","28118","28119","28120","28121","28122","28123","28124","28125","28126","28127","28128","28129","28130","28131","28132","28133","28134","28135","28136","28137","28138","28139","28140","28141","28142","28143","28144","28145","28146","28147","28148","28149","28150","28151","28152","28153","28154","28155","28156","28157","28158","28159","28160","28161","28162","28163","28164","28165","28166","28167","28168","28169","28170","28171","28172","28173","28174","28175","28176","28177","28178","28179","28180","28181","28182","28183","28184","28185","28186","28187","28188","28189","28190","28191","28192","28193","28194","28195","28196","28197","28198","28199","28200","28201","28202","28203","28204","28205","28206","28207","28208","28209","28210","28211","28212","28213","28214","28215","28216","28217","28218","28219","28220","28221","28222","28223","28224","28225","28226","28227","28228","28229","28230","28231","28232","28233","28234","28235","28236","28237","28238","28239","28240","28241","28242","28243","28244","28245","28246","28247","28248","28249","28250","28251","28252","28253","28254","28255","28256","28257","28258","28259","28260","28261","28262","28263","28264","28265","28266","28267","28268","28269","28270","28271","28272","28273","28274","28275","28276","28277","28278","28279","28280","28281","28282","28283","28284","28285","28286","28287","28288","28289","28290","28291","28292","28293","28294","28295","28296","28297","28298","28299","28300","28301","28302","28303","28304","28305","28306","28307","28308","28309","28310","28311","28312","28313","28314","28315","28316","28317","28318","28319","28320","28321","28322","28323","28324","28325","28326","28327","28328","28329","28330","28331","28332","28333","28334","28335","28336","28337","28338","28339","28340","28341","28342","28343","28344","28345","28346","28347","28348","28349","28350","28351","28352","28353","28354","28355","28356","28357","28358","28359","28360","28361","28362","28363","28364","28365","28366","28367","28368","28369","28370","28371","28372","28373","28374","28375","28376","28377","28378","28379","28380","28381","28382","28383","28384","28385","28386","28387","28388","28389","28390","28391","28392","28393","28394","28395","28396","28397","28398","28399","28400","28401","28402","28403","28404","28405","28406","28407","28408","28409","28410","28411","28412","28413","28414","28415","28416","28417","28418","28419","28420","28421","28422","28423","28424","28425","28426","28427","28428","28429","28430","28431","28432","28433","28434","28435","28436","28437","28438","28439","28440","28441","28442","28443","28444","28445","28446","28447","28448","28449","28450","28451","28452","28453","28454","28455","28456","28457","28458","28459","28460","28461","28462","28463","28464","28465","28466","28467","28468","28469","28470","28471","28472","28473","28474","28475","28476","28477","28478","28479","28480","28481","28482","28483","28484","28485","28486","28487","28488","28489","28490","28491","28492","28493","28494","28495","28496","28497","28498","28499","28500","28501","28502","28503","28504","28505","28506","28507","28508","28509","28510","28511","28512","28513","28514","28515","28516","28517","28518","28519","28520","28521","28522","28523","28524","28525","28526","28527","28528","28529","28530","28531","28532","28533","28534","28535","28536","28537","28538","28539","28540","28541","28542","28543","28544","28545","28546","28547","28548","28549","28550","28551","28552","28553","28554","28555","28556","28557","28558","28559","28560","28561","28562","28563","28564","28565","28566","28567","28568","28569","28570","28571","28572","28573","28574","28575","28576","28577","28578","28579","28580","28581","28582","28583","28584","28585","28586","28587","28588","28589","28590","28591","28592","28593","28594","28595","28596","28597","28598","28599","28600","28601","28602","28603","28604","28605","28606","28607","28608","28609","28610","28611","28612","28613","28614","28615","28616","28617","28618","28619","28620","28621","28622","28623","28624","28625","28626","28627","28628","28629","28630","28631","28632","28633","28634","28635","28636","28637","28638","28639","28640","28641","28642","28643","28644","28645","28646","28647","28648","28649","28650","28651","28652","28653","28654","28655","28656","28657","28658","28659","28660","28661","28662","28663","28664","28665","28666","28667","28668","28669","28670","28671","28672","28673","28674","28675","28676","28677","28678","28679","28680","28681","28682","28683","28684","28685","28686","28687","28688","28689","28690","28691","28692","28693","28694","28695","28696","28697","28698","28699","28700","28701","28702","28703","28704","28705","28706","28707","28708","28709","28710","28711","28712","28713","28714","28715","28716","28717","28718","28719","28720","28721","28722","28723","28724","28725","28726","28727","28728","28729","28730","28731","28732","28733","28734","28735","28736","28737","28738","28739","28740","28741","28742","28743","28744","28745","28746","28747","28748","28749","28750","28751","28752","28753","28754","28755","28756","28757","28758","28759","28760","28761","28762","28763","28764","28765","28766","28767","28768","28769","28770","28771","28772","28773","28774","28775","28776","28777","28778","28779","28780","28781","28782","28783","28784","28785","28786","28787","28788","28789","28790","28791","28792","28793","28794","28795","28796","28797","28798","28799","28800","28801","28802","28803","28804","28805","28806","28807","28808","28809","28810","28811","28812","28813","28814","28815","28816","28817","28818","28819","28820","28821","28822","28823","28824","28825","28826","28827","28828","28829","28830","28831","28832","28833","28834","28835","28836","28837","28838","28839","28840","28841","28842","28843","28844","28845","28846","28847","28848","28849","28850","28851","28852","28853","28854","28855","28856","28857","28858","28859","28860","28861","28862","28863","28864","28865","28866","28867","28868","28869","28870","28871","28872","28873","28874","28875","28876","28877","28878","28879","28880","28881","28882","28883","28884","28885","28886","28887","28888","28889","28890","28891","28892","28893","28894","28895","28896","28897","28898","28899","28900","28901","28902","28903","28904","28905","28906","28907","28908","28909","28910","28911","28912","28913","28914","28915","28916","28917","28918","28919","28920","28921","28922","28923","28924","28925","28926","28927","28928","28929","28930","28931","28932","28933","28934","28935","28936","28937","28938","28939","28940","28941","28942","28943","28944","28945","28946","28947","28948","28949","28950","28951","28952","28953","28954","28955","28956","28957","28958","28959","28960","28961","28962","28963","28964","28965","28966","28967","28968","28969","28970","28971","28972","28973","28974","28975","28976","28977","28978","28979","28980","28981","28982","28983","28984","28985","28986","28987","28988","28989","28990","28991","28992","28993","28994","28995","28996","28997","28998","28999","29000","29001","29002","29003","29004","29005","29006","29007","29008","29009","29010","29011","29012","29013","29014","29015","29016","29017","29018","29019","29020","29021","29022","29023","29024","29025","29026","29027","29028","29029","29030","29031","29032","29033","29034","29035","29036","29037","29038","29039","29040","29041","29042","29043","29044","29045","29046","29047","29048","29049","29050","29051","29052","29053","29054","29055","29056","29057","29058","29059","29060","29061","29062","29063","29064","29065","29066","29067","29068","29069","29070","29071","29072","29073","29074","29075","29076","29077","29078","29079","29080","29081","29082","29083","29084","29085","29086","29087","29088","29089","29090","29091","29092","29093","29094","29095","29096","29097","29098","29099","29100","29101","29102","29103","29104","29105","29106","29107","29108","29109","29110","29111","29112","29113","29114","29115","29116","29117","29118","29119","29120","29121","29122","29123","29124","29125","29126","29127","29128","29129","29130","29131","29132","29133","29134","29135","29136","29137","29138","29139","29140","29141","29142","29143","29144","29145","29146","29147","29148","29149","29150","29151","29152","29153","29154","29155","29156","29157","29158","29159","29160","29161","29162","29163","29164","29165","29166","29167","29168","29169","29170","29171","29172","29173","29174","29175","29176","29177","29178","29179","29180","29181","29182","29183","29184","29185","29186","29187","29188","29189","29190","29191","29192","29193","29194","29195","29196","29197","29198","29199","29200","29201","29202","29203","29204","29205","29206","29207","29208","29209","29210","29211","29212","29213","29214","29215","29216","29217","29218","29219","29220","29221","29222","29223","29224","29225","29226","29227","29228","29229","29230","29231","29232","29233","29234","29235","29236","29237","29238","29239","29240","29241","29242","29243","29244","29245","29246","29247","29248","29249","29250","29251","29252","29253","29254","29255","29256","29257","29258","29259","29260","29261","29262","29263","29264","29265","29266","29267","29268","29269","29270","29271","29272","29273","29274","29275","29276","29277","29278","29279","29280","29281","29282","29283","29284","29285","29286","29287","29288","29289","29290","29291","29292","29293","29294","29295","29296","29297","29298","29299","29300","29301","29302","29303","29304","29305","29306","29307","29308","29309","29310","29311","29312","29313","29314","29315","29316","29317","29318","29319","29320","29321","29322","29323","29324","29325","29326","29327","29328","29329","29330","29331","29332","29333","29334","29335","29336","29337","29338","29339","29340","29341","29342","29343","29344","29345","29346","29347","29348","29349","29350","29351","29352","29353","29354","29355","29356","29357","29358","29359","29360","29361","29362","29363","29364","29365","29366","29367","29368","29369","29370","29371","29372","29373","29374","29375","29376","29377","29378","29379","29380","29381","29382","29383","29384","29385","29386","29387","29388","29389","29390","29391","29392","29393","29394","29395","29396","29397","29398","29399","29400","29401","29402","29403","29404","29405","29406","29407","29408","29409","29410","29411","29412","29413","29414","29415","29416","29417","29418","29419","29420","29421","29422","29423","29424","29425","29426","29427","29428","29429","29430","29431","29432","29433","29434","29435","29436","29437","29438","29439","29440","29441","29442","29443","29444","29445","29446","29447","29448","29449","29450","29451","29452","29453","29454","29455","29456","29457","29458","29459","29460","29461","29462","29463","29464","29465","29466","29467","29468","29469","29470","29471","29472","29473","29474","29475","29476","29477","29478","29479","29480","29481","29482","29483","29484","29485","29486","29487","29488","29489","29490","29491","29492","29493","29494","29495","29496","29497","29498","29499","29500","29501","29502","29503","29504","29505","29506","29507","29508","29509","29510","29511","29512","29513","29514","29515","29516","29517","29518","29519","29520","29521","29522","29523","29524","29525","29526","29527","29528","29529","29530","29531","29532","29533","29534","29535","29536","29537","29538","29539","29540","29541","29542","29543","29544","29545","29546","29547","29548","29549","29550","29551","29552","29553","29554","29555","29556","29557","29558","29559","29560","29561","29562","29563","29564","29565","29566","29567","29568","29569","29570","29571","29572","29573","29574","29575","29576","29577","29578","29579","29580","29581","29582","29583","29584","29585","29586","29587","29588","29589","29590","29591","29592","29593","29594","29595","29596","29597","29598","29599","29600","29601","29602","29603","29604","29605","29606","29607","29608","29609","29610","29611","29612","29613","29614","29615","29616","29617","29618","29619","29620","29621","29622","29623","29624","29625","29626","29627","29628","29629","29630","29631","29632","29633","29634","29635","29636","29637","29638","29639","29640","29641","29642","29643","29644","29645","29646","29647","29648","29649","29650","29651","29652","29653","29654","29655","29656","29657","29658","29659","29660","29661","29662","29663","29664","29665","29666","29667","29668","29669","29670","29671","29672","29673","29674","29675","29676","29677","29678","29679","29680","29681","29682","29683","29684","29685","29686","29687","29688","29689","29690","29691","29692","29693","29694","29695","29696","29697","29698","29699","29700","29701","29702","29703","29704","29705","29706","29707","29708","29709","29710","29711","29712","29713","29714","29715","29716","29717","29718","29719","29720","29721","29722","29723","29724","29725","29726","29727","29728","29729","29730","29731","29732","29733","29734","29735","29736","29737","29738","29739","29740","29741","29742","29743","29744","29745","29746","29747","29748","29749","29750","29751","29752","29753","29754","29755","29756","29757","29758","29759","29760","29761","29762","29763","29764","29765","29766","29767","29768","29769","29770","29771","29772","29773","29774","29775","29776","29777","29778","29779","29780","29781","29782","29783","29784","29785","29786","29787","29788","29789","29790","29791","29792","29793","29794","29795","29796","29797","29798","29799","29800","29801","29802","29803","29804","29805","29806","29807","29808","29809","29810","29811","29812","29813","29814","29815","29816","29817","29818","29819","29820","29821","29822","29823","29824","29825","29826","29827","29828","29829","29830","29831","29832","29833","29834","29835","29836","29837","29838","29839","29840","29841","29842","29843","29844","29845","29846","29847","29848","29849","29850","29851","29852","29853","29854","29855","29856","29857","29858","29859","29860","29861","29862","29863","29864","29865","29866","29867","29868","29869","29870","29871","29872","29873","29874","29875","29876","29877","29878","29879","29880","29881","29882","29883","29884","29885","29886","29887","29888","29889","29890","29891","29892","29893","29894","29895","29896","29897","29898","29899","29900","29901","29902","29903","29904","29905","29906","29907","29908","29909","29910","29911","29912","29913","29914","29915","29916","29917","29918","29919","29920","29921","29922","29923","29924","29925","29926","29927","29928","29929","29930","29931","29932","29933","29934","29935","29936","29937","29938","29939","29940","29941","29942","29943","29944","29945","29946","29947","29948","29949","29950","29951","29952","29953","29954","29955","29956","29957","29958","29959","29960","29961","29962","29963","29964","29965","29966","29967","29968","29969","29970","29971","29972","29973","29974","29975","29976","29977","29978","29979","29980","29981","29982","29983","29984","29985","29986","29987","29988","29989","29990","29991","29992","29993","29994","29995","29996","29997","29998","29999","30000","30001","30002","30003","30004","30005","30006","30007","30008","30009","30010","30011","30012","30013","30014","30015","30016","30017","30018","30019","30020","30021","30022","30023","30024","30025","30026","30027","30028","30029","30030","30031","30032","30033","30034","30035","30036","30037","30038","30039","30040","30041","30042","30043","30044","30045","30046","30047","30048","30049","30050","30051","30052","30053","30054","30055","30056","30057","30058","30059","30060","30061","30062","30063","30064","30065","30066","30067","30068","30069","30070","30071","30072","30073","30074","30075","30076","30077","30078","30079","30080","30081","30082","30083","30084","30085","30086","30087","30088","30089","30090","30091","30092","30093","30094","30095","30096","30097","30098","30099","30100","30101","30102","30103","30104","30105","30106","30107","30108","30109","30110","30111","30112","30113","30114","30115","30116","30117","30118","30119","30120","30121","30122","30123","30124","30125","30126","30127","30128","30129","30130","30131","30132","30133","30134","30135","30136","30137","30138","30139","30140","30141","30142","30143","30144","30145","30146","30147","30148","30149","30150","30151","30152","30153","30154","30155","30156","30157","30158","30159","30160","30161","30162","30163","30164","30165","30166","30167","30168","30169","30170","30171","30172","30173","30174","30175","30176","30177","30178","30179","30180","30181","30182","30183","30184","30185","30186","30187","30188","30189","30190","30191","30192","30193","30194","30195","30196","30197","30198","30199","30200","30201","30202","30203","30204","30205","30206","30207","30208","30209","30210","30211","30212","30213","30214","30215","30216","30217","30218","30219","30220","30221","30222","30223","30224","30225","30226","30227","30228","30229","30230","30231","30232","30233","30234","30235","30236","30237","30238","30239","30240","30241","30242","30243","30244","30245","30246","30247","30248","30249","30250","30251","30252","30253","30254","30255","30256","30257","30258","30259","30260","30261","30262","30263","30264","30265","30266","30267","30268","30269","30270","30271","30272","30273","30274","30275","30276","30277","30278","30279","30280","30281","30282","30283","30284","30285","30286","30287","30288","30289","30290","30291","30292","30293","30294","30295","30296","30297","30298","30299","30300","30301","30302","30303","30304","30305","30306","30307","30308","30309","30310","30311","30312","30313","30314","30315","30316","30317","30318","30319","30320","30321","30322","30323","30324","30325","30326","30327","30328","30329","30330","30331","30332","30333","30334","30335","30336","30337","30338","30339","30340","30341","30342","30343","30344","30345","30346","30347","30348","30349","30350","30351","30352","30353","30354","30355","30356","30357","30358","30359","30360","30361","30362","30363","30364","30365","30366","30367","30368","30369","30370","30371","30372","30373","30374","30375","30376","30377","30378","30379","30380","30381","30382","30383","30384","30385","30386","30387","30388","30389","30390","30391","30392","30393","30394","30395","30396","30397","30398","30399","30400","30401","30402","30403","30404","30405","30406","30407","30408","30409","30410","30411","30412","30413","30414","30415","30416","30417","30418","30419","30420","30421","30422","30423","30424","30425","30426","30427","30428","30429","30430","30431","30432","30433","30434","30435","30436","30437","30438","30439","30440","30441","30442","30443","30444","30445","30446","30447","30448","30449","30450","30451","30452","30453","30454","30455","30456","30457","30458","30459","30460","30461","30462","30463","30464","30465","30466","30467","30468","30469","30470","30471","30472","30473","30474","30475","30476","30477","30478","30479","30480","30481","30482","30483","30484","30485","30486","30487","30488","30489","30490","30491","30492","30493","30494","30495","30496","30497","30498","30499","30500","30501","30502","30503","30504","30505","30506","30507","30508","30509","30510","30511","30512","30513","30514","30515","30516","30517","30518","30519","30520","30521","30522","30523","30524","30525","30526","30527","30528","30529","30530","30531","30532","30533","30534","30535","30536","30537","30538","30539","30540","30541","30542","30543","30544","30545","30546","30547","30548","30549","30550","30551","30552","30553","30554","30555","30556","30557","30558","30559","30560","30561","30562","30563","30564","30565","30566","30567","30568","30569","30570","30571","30572","30573","30574","30575","30576","30577","30578","30579","30580","30581","30582","30583","30584","30585","30586","30587","30588","30589","30590","30591","30592","30593","30594","30595","30596","30597","30598","30599","30600","30601","30602","30603","30604","30605","30606","30607","30608","30609","30610","30611","30612","30613","30614","30615","30616","30617","30618","30619","30620","30621","30622","30623","30624","30625","30626","30627","30628","30629","30630","30631","30632","30633","30634","30635","30636","30637","30638","30639","30640","30641","30642","30643","30644","30645","30646","30647","30648","30649","30650","30651","30652","30653","30654","30655","30656","30657","30658","30659","30660","30661","30662","30663","30664","30665","30666","30667","30668","30669","30670","30671","30672","30673","30674","30675","30676","30677","30678","30679","30680","30681","30682","30683","30684","30685","30686","30687","30688","30689","30690","30691","30692","30693","30694","30695","30696","30697","30698","30699","30700","30701","30702","30703","30704","30705","30706","30707","30708","30709","30710","30711","30712","30713","30714","30715","30716","30717","30718","30719","30720","30721","30722","30723","30724","30725","30726","30727","30728","30729","30730","30731","30732","30733","30734","30735","30736","30737","30738","30739","30740","30741","30742","30743","30744","30745","30746","30747","30748","30749","30750","30751","30752","30753","30754","30755","30756","30757","30758","30759","30760","30761","30762","30763","30764","30765","30766","30767","30768","30769","30770","30771","30772","30773","30774","30775","30776","30777","30778","30779","30780","30781","30782","30783","30784","30785","30786","30787","30788","30789","30790","30791","30792","30793","30794","30795","30796","30797","30798","30799","30800","30801","30802","30803","30804","30805","30806","30807","30808","30809","30810","30811","30812","30813","30814","30815","30816","30817","30818","30819","30820","30821","30822","30823","30824","30825","30826","30827","30828","30829","30830","30831","30832","30833","30834","30835","30836","30837","30838","30839","30840","30841","30842","30843","30844","30845","30846","30847","30848","30849","30850","30851","30852","30853","30854","30855","30856","30857","30858","30859","30860","30861","30862","30863","30864","30865","30866","30867","30868","30869","30870","30871","30872","30873","30874","30875","30876","30877","30878","30879","30880","30881","30882","30883","30884","30885","30886","30887","30888","30889","30890","30891","30892","30893","30894","30895","30896","30897","30898","30899","30900","30901","30902","30903","30904","30905","30906","30907","30908","30909","30910","30911","30912","30913","30914","30915","30916","30917","30918","30919","30920","30921","30922","30923","30924","30925","30926","30927","30928","30929","30930","30931","30932","30933","30934","30935","30936","30937","30938","30939","30940","30941","30942","30943","30944","30945","30946","30947","30948","30949","30950","30951","30952","30953","30954","30955","30956","30957","30958","30959","30960","30961","30962","30963","30964","30965","30966","30967","30968","30969","30970","30971","30972","30973","30974","30975","30976","30977","30978","30979","30980","30981","30982","30983","30984","30985","30986","30987","30988","30989","30990","30991","30992","30993","30994","30995","30996","30997","30998","30999","31000","31001","31002","31003","31004","31005","31006","31007","31008","31009","31010","31011","31012","31013","31014","31015","31016","31017","31018","31019","31020","31021","31022","31023","31024","31025","31026","31027","31028","31029","31030","31031","31032","31033","31034","31035","31036","31037","31038","31039","31040","31041","31042","31043","31044","31045","31046","31047","31048","31049","31050","31051","31052","31053","31054","31055","31056","31057","31058","31059","31060","31061","31062","31063","31064","31065","31066","31067","31068","31069","31070","31071","31072","31073","31074","31075","31076","31077","31078","31079","31080","31081","31082","31083","31084","31085","31086","31087","31088","31089","31090","31091","31092","31093","31094","31095","31096","31097","31098","31099","31100","31101","31102","31103","31104","31105","31106","31107","31108","31109","31110","31111","31112","31113","31114","31115","31116","31117","31118","31119","31120","31121","31122","31123","31124","31125","31126","31127","31128","31129","31130","31131","31132","31133","31134","31135","31136","31137","31138","31139","31140","31141","31142","31143","31144","31145","31146","31147","31148","31149","31150","31151","31152","31153","31154","31155","31156","31157","31158","31159","31160","31161","31162","31163","31164","31165","31166","31167","31168","31169","31170","31171","31172","31173","31174","31175","31176","31177","31178","31179","31180","31181","31182","31183","31184","31185","31186","31187","31188","31189","31190","31191","31192","31193","31194","31195","31196","31197","31198","31199","31200","31201","31202","31203","31204","31205","31206","31207","31208","31209","31210","31211","31212","31213","31214","31215","31216","31217","31218","31219","31220","31221","31222","31223","31224","31225","31226","31227","31228","31229","31230","31231","31232","31233","31234","31235","31236","31237","31238","31239","31240","31241","31242","31243","31244","31245","31246","31247","31248","31249","31250","31251","31252","31253","31254","31255","31256","31257","31258","31259","31260","31261","31262","31263","31264","31265","31266","31267","31268","31269","31270","31271","31272","31273","31274","31275","31276","31277","31278","31279","31280","31281","31282","31283","31284","31285","31286","31287","31288","31289","31290","31291","31292","31293","31294","31295","31296","31297","31298","31299","31300","31301","31302","31303","31304","31305","31306","31307","31308","31309","31310","31311","31312","31313","31314","31315","31316","31317","31318","31319","31320","31321","31322","31323","31324","31325","31326","31327","31328","31329","31330","31331","31332","31333","31334","31335","31336","31337","31338","31339","31340","31341","31342","31343","31344","31345","31346","31347","31348","31349","31350","31351","31352","31353","31354","31355","31356","31357","31358","31359","31360","31361","31362","31363","31364","31365","31366","31367","31368","31369","31370","31371","31372","31373","31374","31375","31376","31377","31378","31379","31380","31381","31382","31383","31384","31385","31386","31387","31388","31389","31390","31391","31392","31393","31394","31395","31396","31397","31398","31399","31400","31401","31402","31403","31404","31405","31406","31407","31408","31409","31410","31411","31412","31413","31414","31415","31416","31417","31418","31419","31420","31421","31422","31423","31424","31425","31426","31427","31428","31429","31430","31431","31432","31433","31434","31435","31436","31437","31438","31439","31440","31441","31442","31443","31444","31445","31446","31447","31448","31449","31450","31451","31452","31453","31454","31455","31456","31457","31458","31459","31460","31461","31462","31463","31464","31465","31466","31467","31468","31469","31470","31471","31472","31473","31474","31475","31476","31477","31478","31479","31480","31481","31482","31483","31484","31485","31486","31487","31488","31489","31490","31491","31492","31493","31494","31495","31496","31497","31498","31499","31500","31501","31502","31503","31504","31505","31506","31507","31508","31509","31510","31511","31512","31513","31514","31515","31516","31517","31518","31519","31520","31521","31522","31523","31524","31525","31526","31527","31528","31529","31530","31531","31532","31533","31534","31535","31536","31537","31538","31539","31540","31541","31542","31543","31544","31545","31546","31547","31548","31549","31550","31551","31552","31553","31554","31555","31556","31557","31558","31559","31560","31561","31562","31563","31564","31565","31566","31567","31568","31569","31570","31571","31572","31573","31574","31575","31576","31577","31578","31579","31580","31581","31582","31583","31584","31585","31586","31587","31588","31589","31590","31591","31592","31593","31594","31595","31596","31597","31598","31599","31600","31601","31602","31603","31604","31605","31606","31607","31608","31609","31610","31611","31612","31613","31614","31615","31616","31617","31618","31619","31620","31621","31622","31623","31624","31625","31626","31627","31628","31629","31630","31631","31632","31633","31634","31635","31636","31637","31638","31639","31640","31641","31642","31643","31644","31645","31646","31647","31648","31649","31650","31651","31652","31653","31654","31655","31656","31657","31658","31659","31660","31661","31662","31663","31664","31665","31666","31667","31668","31669","31670","31671","31672","31673","31674","31675","31676","31677","31678","31679","31680","31681","31682","31683","31684","31685","31686","31687","31688","31689","31690","31691","31692","31693","31694","31695","31696","31697","31698","31699","31700","31701","31702","31703","31704","31705","31706","31707","31708","31709","31710","31711","31712","31713","31714","31715","31716","31717","31718","31719","31720","31721","31722","31723","31724","31725","31726","31727","31728","31729","31730","31731","31732","31733","31734","31735","31736","31737","31738","31739","31740","31741","31742","31743","31744","31745","31746","31747","31748","31749","31750","31751","31752","31753","31754","31755","31756","31757","31758","31759","31760","31761","31762","31763","31764","31765","31766","31767","31768","31769","31770","31771","31772","31773","31774","31775","31776","31777","31778","31779","31780","31781","31782","31783","31784","31785","31786","31787","31788","31789","31790","31791","31792","31793","31794","31795","31796","31797","31798","31799","31800","31801","31802","31803","31804","31805","31806","31807","31808","31809","31810","31811","31812","31813","31814","31815","31816","31817","31818","31819","31820","31821","31822","31823","31824","31825","31826","31827","31828","31829","31830","31831","31832","31833","31834","31835","31836","31837","31838","31839","31840","31841","31842","31843","31844","31845","31846","31847","31848","31849","31850","31851","31852","31853","31854","31855","31856","31857","31858","31859","31860","31861","31862","31863","31864","31865","31866","31867","31868","31869","31870","31871","31872","31873","31874","31875","31876","31877","31878","31879","31880","31881","31882","31883","31884","31885","31886","31887","31888","31889","31890","31891","31892","31893","31894","31895","31896","31897","31898","31899","31900","31901","31902","31903","31904","31905","31906","31907","31908","31909","31910","31911","31912","31913","31914","31915","31916","31917","31918","31919","31920","31921","31922","31923","31924","31925","31926","31927","31928","31929","31930","31931","31932","31933","31934","31935","31936","31937","31938","31939","31940","31941","31942","31943","31944","31945","31946","31947","31948","31949","31950","31951","31952","31953","31954","31955","31956","31957","31958","31959","31960","31961","31962","31963","31964","31965","31966","31967","31968","31969","31970","31971","31972","31973","31974","31975","31976","31977","31978","31979","31980","31981","31982","31983","31984","31985","31986","31987","31988","31989","31990","31991","31992","31993","31994","31995","31996","31997","31998","31999","32000","32001","32002","32003","32004","32005","32006","32007","32008","32009","32010","32011","32012","32013","32014","32015","32016","32017","32018","32019","32020","32021","32022","32023","32024","32025","32026","32027","32028","32029","32030","32031","32032","32033","32034","32035","32036","32037","32038","32039","32040","32041","32042","32043","32044","32045","32046","32047","32048","32049","32050","32051","32052","32053","32054","32055","32056","32057","32058","32059","32060","32061","32062","32063","32064","32065","32066","32067","32068","32069","32070","32071","32072","32073","32074","32075","32076","32077","32078","32079","32080","32081","32082","32083","32084","32085","32086","32087","32088","32089","32090","32091","32092","32093","32094","32095","32096","32097","32098","32099","32100","32101","32102","32103","32104","32105","32106","32107","32108","32109","32110","32111","32112","32113","32114","32115","32116","32117","32118","32119","32120","32121","32122","32123","32124","32125","32126","32127","32128","32129","32130","32131","32132","32133","32134","32135","32136","32137","32138","32139","32140","32141","32142","32143","32144","32145","32146","32147","32148","32149","32150","32151","32152","32153","32154","32155","32156","32157","32158","32159","32160","32161","32162","32163","32164","32165","32166","32167","32168","32169","32170","32171","32172","32173","32174","32175","32176","32177","32178","32179","32180","32181","32182","32183","32184","32185","32186","32187","32188","32189","32190","32191","32192","32193","32194","32195","32196","32197","32198","32199","32200","32201","32202","32203","32204","32205","32206","32207","32208","32209","32210","32211","32212","32213","32214","32215","32216","32217","32218","32219","32220","32221","32222","32223","32224","32225","32226","32227","32228","32229","32230","32231","32232","32233","32234","32235","32236","32237","32238","32239","32240","32241","32242","32243","32244","32245","32246","32247","32248","32249","32250","32251","32252","32253","32254","32255","32256","32257","32258","32259","32260","32261","32262","32263","32264","32265","32266","32267","32268","32269","32270","32271","32272","32273","32274","32275","32276","32277","32278","32279","32280","32281","32282","32283","32284","32285","32286","32287","32288","32289","32290","32291","32292","32293","32294","32295","32296","32297","32298","32299","32300","32301","32302","32303","32304","32305","32306","32307","32308","32309","32310","32311","32312","32313","32314","32315","32316","32317","32318","32319","32320","32321","32322","32323","32324","32325","32326","32327","32328","32329","32330","32331","32332","32333","32334","32335","32336","32337","32338","32339","32340","32341","32342","32343","32344","32345","32346","32347","32348","32349","32350","32351","32352","32353","32354","32355","32356","32357","32358","32359","32360","32361","32362","32363","32364","32365","32366","32367","32368","32369","32370","32371","32372","32373","32374","32375","32376","32377","32378","32379","32380","32381","32382","32383","32384","32385","32386","32387","32388","32389","32390","32391","32392","32393","32394","32395","32396","32397","32398","32399","32400","32401","32402","32403","32404","32405","32406","32407","32408","32409","32410","32411","32412","32413","32414","32415","32416","32417","32418","32419","32420","32421","32422","32423","32424","32425","32426","32427","32428","32429","32430","32431","32432","32433","32434","32435","32436","32437","32438","32439","32440","32441","32442","32443","32444","32445","32446","32447","32448","32449","32450","32451","32452","32453","32454","32455","32456","32457","32458","32459","32460","32461","32462","32463","32464","32465","32466","32467","32468","32469","32470","32471","32472","32473","32474","32475","32476","32477","32478","32479","32480","32481","32482","32483","32484","32485","32486","32487","32488","32489","32490","32491","32492","32493","32494","32495","32496","32497","32498","32499","32500","32501","32502","32503","32504","32505","32506","32507","32508","32509","32510","32511","32512","32513","32514","32515","32516","32517","32518","32519","32520","32521","32522","32523","32524","32525","32526","32527","32528","32529","32530","32531","32532","32533","32534","32535","32536","32537","32538","32539","32540","32541","32542","32543","32544","32545","32546","32547","32548","32549","32550","32551","32552","32553","32554","32555","32556","32557","32558","32559","32560","32561","32562","32563","32564","32565","32566","32567","32568","32569","32570","32571","32572","32573","32574","32575","32576","32577","32578","32579","32580","32581","32582","32583","32584","32585","32586","32587","32588","32589","32590","32591","32592","32593","32594","32595","32596","32597","32598","32599","32600","32601","32602","32603","32604","32605","32606","32607","32608","32609","32610","32611","32612","32613","32614","32615","32616","32617","32618","32619","32620","32621","32622","32623","32624","32625","32626","32627","32628","32629","32630","32631","32632","32633","32634","32635","32636","32637","32638","32639","32640","32641","32642","32643","32644","32645","32646","32647","32648","32649","32650","32651","32652","32653","32654","32655","32656","32657","32658","32659","32660","32661","32662","32663","32664","32665","32666","32667","32668","32669","32670","32671","32672","32673","32674","32675","32676","32677","32678","32679","32680","32681","32682","32683","32684","32685","32686","32687","32688","32689","32690","32691","32692","32693","32694","32695","32696","32697","32698","32699","32700","32701","32702","32703","32704","32705","32706","32707","32708","32709","32710","32711","32712","32713","32714","32715","32716","32717","32718","32719","32720","32721","32722","32723","32724","32725","32726","32727","32728","32729","32730","32731","32732","32733","32734","32735","32736","32737","32738","32739","32740","32741","32742","32743","32744","32745","32746","32747","32748","32749","32750","32751","32752","32753","32754","32755","32756","32757","32758","32759","32760","32761","32762","32763","32764","32765","32766","32767","32768","32769","32770","32771","32772","32773","32774","32775","32776","32777","32778","32779","32780","32781","32782","32783","32784","32785","32786","32787","32788","32789","32790","32791","32792","32793","32794","32795","32796","32797","32798","32799","32800","32801","32802","32803","32804","32805","32806","32807","32808","32809","32810","32811","32812","32813","32814","32815","32816","32817","32818","32819","32820","32821","32822","32823","32824","32825","32826","32827","32828","32829","32830","32831","32832","32833","32834","32835","32836","32837","32838","32839","32840","32841","32842","32843","32844","32845","32846","32847","32848","32849","32850","32851","32852","32853","32854","32855","32856","32857","32858","32859","32860","32861","32862","32863","32864","32865","32866","32867","32868","32869","32870","32871","32872","32873","32874","32875","32876","32877","32878","32879","32880","32881","32882","32883","32884","32885","32886","32887","32888","32889","32890","32891","32892","32893","32894","32895","32896","32897","32898","32899","32900","32901","32902","32903","32904","32905","32906","32907","32908","32909","32910","32911","32912","32913","32914","32915","32916","32917","32918","32919","32920","32921","32922","32923","32924","32925","32926","32927","32928","32929","32930","32931","32932","32933","32934","32935","32936","32937","32938","32939","32940","32941","32942","32943","32944","32945","32946","32947","32948","32949","32950","32951","32952","32953","32954","32955","32956","32957","32958","32959","32960","32961","32962","32963","32964","32965","32966","32967","32968","32969","32970","32971","32972","32973","32974","32975","32976","32977","32978","32979","32980","32981","32982","32983","32984","32985","32986","32987","32988","32989","32990","32991","32992","32993","32994","32995","32996","32997","32998","32999","33000","33001","33002","33003","33004","33005","33006","33007","33008","33009","33010","33011","33012","33013","33014","33015","33016","33017","33018","33019","33020","33021","33022","33023","33024","33025","33026","33027","33028","33029","33030","33031","33032","33033","33034","33035","33036","33037","33038","33039","33040","33041","33042","33043","33044","33045","33046","33047","33048","33049","33050","33051","33052","33053","33054","33055","33056","33057","33058","33059","33060","33061","33062","33063","33064","33065","33066","33067","33068","33069","33070","33071","33072","33073","33074","33075","33076","33077","33078","33079","33080","33081","33082","33083","33084","33085","33086","33087","33088","33089","33090","33091","33092","33093","33094","33095","33096","33097","33098","33099","33100","33101","33102","33103","33104","33105","33106","33107","33108","33109","33110","33111","33112","33113","33114","33115","33116","33117","33118","33119","33120","33121","33122","33123","33124","33125","33126","33127","33128","33129","33130","33131","33132","33133","33134","33135","33136","33137","33138","33139","33140","33141","33142","33143","33144","33145","33146","33147","33148","33149","33150","33151","33152","33153","33154","33155","33156","33157","33158","33159","33160","33161","33162","33163","33164","33165","33166","33167","33168","33169","33170","33171","33172","33173","33174","33175","33176","33177","33178","33179","33180","33181","33182","33183","33184","33185","33186","33187","33188","33189","33190","33191","33192","33193","33194","33195","33196","33197","33198","33199","33200","33201","33202","33203","33204","33205","33206","33207","33208","33209","33210","33211","33212","33213","33214","33215","33216","33217","33218","33219","33220","33221","33222","33223","33224","33225","33226","33227","33228","33229","33230","33231","33232","33233","33234","33235","33236","33237","33238","33239","33240","33241","33242","33243","33244","33245","33246","33247","33248","33249","33250","33251","33252","33253","33254","33255","33256","33257","33258","33259","33260","33261","33262","33263","33264","33265","33266","33267","33268","33269","33270","33271","33272","33273","33274","33275","33276","33277","33278","33279","33280","33281","33282","33283","33284","33285","33286","33287","33288","33289","33290","33291","33292","33293","33294","33295","33296","33297","33298","33299","33300","33301","33302","33303","33304","33305","33306","33307","33308","33309","33310","33311","33312","33313","33314","33315","33316","33317","33318","33319","33320","33321","33322","33323","33324","33325","33326","33327","33328","33329","33330","33331","33332","33333","33334","33335","33336","33337","33338","33339","33340","33341","33342","33343","33344","33345","33346","33347","33348","33349","33350","33351","33352","33353","33354","33355","33356","33357","33358","33359","33360","33361","33362","33363","33364","33365","33366","33367","33368","33369","33370","33371","33372","33373","33374","33375","33376","33377","33378","33379","33380","33381","33382","33383","33384","33385","33386","33387","33388","33389","33390","33391","33392","33393","33394","33395","33396","33397","33398","33399","33400","33401","33402","33403","33404","33405","33406","33407","33408","33409","33410","33411","33412","33413","33414","33415","33416","33417","33418","33419","33420","33421","33422","33423","33424","33425","33426","33427","33428","33429","33430","33431","33432","33433","33434","33435","33436","33437","33438","33439","33440","33441","33442","33443","33444","33445","33446","33447","33448","33449","33450","33451","33452","33453","33454","33455","33456","33457","33458","33459","33460","33461","33462","33463","33464","33465","33466","33467","33468","33469","33470","33471","33472","33473","33474","33475","33476","33477","33478","33479","33480","33481","33482","33483","33484","33485","33486","33487","33488","33489","33490","33491","33492","33493","33494","33495","33496","33497","33498","33499","33500","33501","33502","33503","33504","33505","33506","33507","33508","33509","33510","33511","33512","33513","33514","33515","33516","33517","33518","33519","33520","33521","33522","33523","33524","33525","33526","33527","33528","33529","33530","33531","33532","33533","33534","33535","33536","33537","33538","33539","33540","33541","33542","33543","33544","33545","33546","33547","33548","33549","33550","33551","33552","33553","33554","33555","33556","33557","33558","33559","33560","33561","33562","33563","33564","33565","33566","33567","33568","33569","33570","33571","33572","33573","33574","33575","33576","33577","33578","33579","33580","33581","33582","33583","33584","33585","33586","33587","33588","33589","33590","33591","33592","33593","33594","33595","33596","33597","33598","33599","33600","33601","33602","33603","33604","33605","33606","33607","33608","33609","33610","33611","33612","33613","33614","33615","33616","33617","33618","33619","33620","33621","33622","33623","33624","33625","33626","33627","33628","33629","33630","33631","33632","33633","33634","33635","33636","33637","33638","33639","33640","33641","33642","33643","33644","33645","33646","33647","33648","33649","33650","33651","33652","33653","33654","33655","33656","33657","33658","33659","33660","33661","33662","33663","33664","33665","33666","33667","33668","33669","33670","33671","33672","33673","33674","33675","33676","33677","33678","33679","33680","33681","33682","33683","33684","33685","33686","33687","33688","33689","33690","33691","33692","33693","33694","33695","33696","33697","33698","33699","33700","33701","33702","33703","33704","33705","33706","33707","33708","33709","33710","33711","33712","33713","33714","33715","33716","33717","33718","33719","33720","33721","33722","33723","33724","33725","33726","33727","33728","33729","33730","33731","33732","33733","33734","33735","33736","33737","33738","33739","33740","33741","33742","33743","33744","33745","33746","33747","33748","33749","33750","33751","33752","33753","33754","33755","33756","33757","33758","33759","33760","33761","33762","33763","33764","33765","33766","33767","33768","33769","33770","33771","33772","33773","33774","33775","33776","33777","33778","33779","33780","33781","33782","33783","33784","33785","33786","33787","33788","33789","33790","33791","33792","33793","33794","33795","33796","33797","33798","33799","33800","33801","33802","33803","33804","33805","33806","33807","33808","33809","33810","33811","33812","33813","33814","33815","33816","33817","33818","33819","33820","33821","33822","33823","33824","33825","33826","33827","33828","33829","33830","33831","33832","33833","33834","33835","33836","33837","33838","33839","33840","33841","33842","33843","33844","33845","33846","33847","33848","33849","33850","33851","33852","33853","33854","33855","33856","33857","33858","33859","33860","33861","33862","33863","33864","33865","33866","33867","33868","33869","33870","33871","33872","33873","33874","33875","33876","33877","33878","33879","33880","33881","33882","33883","33884","33885","33886","33887","33888","33889","33890","33891","33892","33893","33894","33895","33896","33897","33898","33899","33900","33901","33902","33903","33904","33905","33906","33907","33908","33909","33910","33911","33912","33913","33914","33915","33916","33917","33918","33919","33920","33921","33922","33923","33924","33925","33926","33927","33928","33929","33930","33931","33932","33933","33934","33935","33936","33937","33938","33939","33940","33941","33942","33943","33944","33945","33946","33947","33948","33949","33950","33951","33952","33953","33954","33955","33956","33957","33958","33959","33960","33961","33962","33963","33964","33965","33966","33967","33968","33969","33970","33971","33972","33973","33974","33975","33976","33977","33978","33979","33980","33981","33982","33983","33984","33985","33986","33987","33988","33989","33990","33991","33992","33993","33994","33995","33996","33997","33998","33999","34000","34001","34002","34003","34004","34005","34006","34007","34008","34009","34010","34011","34012","34013","34014","34015","34016","34017","34018","34019","34020","34021","34022","34023","34024","34025","34026","34027","34028","34029","34030","34031","34032","34033","34034","34035","34036","34037","34038","34039","34040","34041","34042","34043","34044","34045","34046","34047","34048","34049","34050","34051","34052","34053","34054","34055","34056","34057","34058","34059","34060","34061","34062","34063","34064","34065","34066","34067","34068","34069","34070","34071","34072","34073","34074","34075","34076","34077","34078","34079","34080","34081","34082","34083","34084","34085","34086","34087","34088","34089","34090","34091","34092","34093","34094","34095","34096","34097","34098","34099","34100","34101","34102","34103","34104","34105","34106","34107","34108","34109","34110","34111","34112","34113","34114","34115","34116","34117","34118","34119","34120","34121","34122","34123","34124","34125","34126","34127","34128","34129","34130","34131","34132","34133","34134","34135","34136","34137","34138","34139","34140","34141","34142","34143","34144","34145","34146","34147","34148","34149","34150","34151","34152","34153","34154","34155","34156","34157","34158","34159","34160","34161","34162","34163","34164","34165","34166","34167","34168","34169","34170","34171","34172","34173","34174","34175","34176","34177","34178","34179","34180","34181","34182","34183","34184","34185","34186","34187","34188","34189","34190","34191","34192","34193","34194","34195","34196","34197","34198","34199","34200","34201","34202","34203","34204","34205","34206","34207","34208","34209","34210","34211","34212","34213","34214","34215","34216","34217","34218","34219","34220","34221","34222","34223","34224","34225","34226","34227","34228","34229","34230","34231","34232","34233","34234","34235","34236","34237","34238","34239","34240","34241","34242","34243","34244","34245","34246","34247","34248","34249","34250","34251","34252","34253","34254","34255","34256","34257","34258","34259","34260","34261","34262","34263","34264","34265","34266","34267","34268","34269","34270","34271","34272","34273","34274","34275","34276","34277","34278","34279","34280","34281","34282","34283","34284","34285","34286","34287","34288","34289","34290","34291","34292","34293","34294","34295","34296","34297","34298","34299","34300","34301","34302","34303","34304","34305","34306","34307","34308","34309","34310","34311","34312","34313","34314","34315","34316","34317","34318","34319","34320","34321","34322","34323","34324","34325","34326","34327","34328","34329","34330","34331","34332","34333","34334","34335","34336","34337","34338","34339","34340","34341","34342","34343","34344","34345","34346","34347","34348","34349","34350","34351","34352","34353","34354","34355","34356","34357","34358","34359","34360","34361","34362","34363","34364","34365","34366","34367","34368","34369","34370","34371","34372","34373","34374","34375","34376","34377","34378","34379","34380","34381","34382","34383","34384","34385","34386","34387","34388","34389","34390","34391","34392","34393","34394","34395","34396","34397","34398","34399","34400","34401","34402","34403","34404","34405","34406","34407","34408","34409","34410","34411","34412","34413","34414","34415","34416","34417","34418","34419","34420","34421","34422","34423","34424","34425","34426","34427","34428","34429","34430","34431","34432","34433","34434","34435","34436","34437","34438","34439","34440","34441","34442","34443","34444","34445","34446","34447","34448","34449","34450","34451","34452","34453","34454","34455","34456","34457","34458","34459","34460","34461","34462","34463","34464","34465","34466","34467","34468","34469","34470","34471","34472","34473","34474","34475","34476","34477","34478","34479","34480","34481","34482","34483","34484","34485","34486","34487","34488","34489","34490","34491","34492","34493","34494","34495","34496","34497","34498","34499","34500","34501","34502","34503","34504","34505","34506","34507","34508","34509","34510","34511","34512","34513","34514","34515","34516","34517","34518","34519","34520","34521","34522","34523","34524","34525","34526","34527","34528","34529","34530","34531","34532","34533","34534","34535","34536","34537","34538","34539","34540","34541","34542","34543","34544","34545","34546","34547","34548","34549","34550","34551","34552","34553","34554","34555","34556","34557","34558","34559","34560","34561","34562","34563","34564","34565","34566","34567","34568","34569","34570","34571","34572","34573","34574","34575","34576","34577","34578","34579","34580","34581","34582","34583","34584","34585","34586","34587","34588","34589","34590","34591","34592","34593","34594","34595","34596","34597","34598","34599","34600","34601","34602","34603","34604","34605","34606","34607","34608","34609","34610","34611","34612","34613","34614","34615","34616","34617","34618","34619","34620","34621","34622","34623","34624","34625","34626","34627","34628","34629","34630","34631","34632","34633","34634","34635","34636","34637","34638","34639","34640","34641","34642","34643","34644","34645","34646","34647","34648","34649","34650","34651","34652","34653","34654","34655","34656","34657","34658","34659","34660","34661","34662","34663","34664","34665","34666","34667","34668","34669","34670","34671","34672","34673","34674","34675","34676","34677","34678","34679","34680","34681","34682","34683","34684","34685","34686","34687","34688","34689","34690","34691","34692","34693","34694","34695","34696","34697","34698","34699","34700","34701","34702","34703","34704","34705","34706","34707","34708","34709","34710","34711","34712","34713","34714","34715","34716","34717","34718","34719","34720","34721","34722","34723","34724","34725","34726","34727","34728","34729","34730","34731","34732","34733","34734","34735","34736","34737","34738","34739","34740","34741","34742","34743","34744","34745","34746","34747","34748","34749","34750","34751","34752","34753","34754","34755","34756","34757","34758","34759","34760","34761","34762","34763","34764","34765","34766","34767","34768","34769","34770","34771","34772","34773","34774","34775","34776","34777","34778","34779","34780","34781","34782","34783","34784","34785","34786","34787","34788","34789","34790","34791","34792","34793","34794","34795","34796","34797","34798","34799","34800","34801","34802","34803","34804","34805","34806","34807","34808","34809","34810","34811","34812","34813","34814","34815","34816","34817","34818","34819","34820","34821","34822","34823","34824","34825","34826","34827","34828","34829","34830","34831","34832","34833","34834","34835","34836","34837","34838","34839","34840","34841","34842","34843","34844","34845","34846","34847","34848","34849","34850","34851","34852","34853","34854","34855","34856","34857","34858","34859","34860","34861","34862","34863","34864","34865","34866","34867","34868","34869","34870","34871","34872","34873","34874","34875","34876","34877","34878","34879","34880","34881","34882","34883","34884","34885","34886","34887","34888","34889","34890","34891","34892","34893","34894","34895","34896","34897","34898","34899","34900","34901","34902","34903","34904","34905","34906","34907","34908","34909","34910","34911","34912","34913","34914","34915","34916","34917","34918","34919","34920","34921","34922","34923","34924","34925","34926","34927","34928","34929","34930","34931","34932","34933","34934","34935","34936","34937","34938","34939","34940","34941","34942","34943","34944","34945","34946","34947","34948","34949","34950","34951","34952","34953","34954","34955","34956","34957","34958","34959","34960","34961","34962","34963","34964","34965","34966","34967","34968","34969","34970","34971","34972","34973","34974","34975","34976","34977","34978","34979","34980","34981","34982","34983","34984","34985","34986","34987","34988","34989","34990","34991","34992","34993","34994","34995","34996","34997","34998","34999","35000","35001","35002","35003","35004","35005","35006","35007","35008","35009","35010","35011","35012","35013","35014","35015","35016","35017","35018","35019","35020","35021","35022","35023","35024","35025","35026","35027","35028","35029","35030","35031","35032","35033","35034","35035","35036","35037","35038","35039","35040","35041","35042","35043","35044","35045","35046","35047","35048","35049","35050","35051","35052","35053","35054","35055","35056","35057","35058","35059","35060","35061","35062","35063","35064","35065","35066","35067","35068","35069","35070","35071","35072","35073","35074","35075","35076","35077","35078","35079","35080","35081","35082","35083","35084","35085","35086","35087","35088","35089","35090","35091","35092","35093","35094","35095","35096","35097","35098","35099","35100","35101","35102","35103","35104","35105","35106","35107","35108","35109","35110","35111","35112","35113","35114","35115","35116","35117","35118","35119","35120","35121","35122","35123","35124","35125","35126","35127","35128","35129","35130","35131","35132","35133","35134","35135","35136","35137","35138","35139","35140","35141","35142","35143","35144","35145","35146","35147","35148","35149","35150","35151","35152","35153","35154","35155","35156","35157","35158","35159","35160","35161","35162","35163","35164","35165","35166","35167","35168","35169","35170","35171","35172","35173","35174","35175","35176","35177","35178","35179","35180","35181","35182","35183","35184","35185","35186","35187","35188","35189","35190","35191","35192","35193","35194","35195","35196","35197","35198","35199","35200","35201","35202","35203","35204","35205","35206","35207","35208","35209","35210","35211","35212","35213","35214","35215","35216","35217","35218","35219","35220","35221","35222","35223","35224","35225","35226","35227","35228","35229","35230","35231","35232","35233","35234","35235","35236","35237","35238","35239","35240","35241","35242","35243","35244","35245","35246","35247","35248","35249","35250","35251","35252","35253","35254","35255","35256","35257","35258","35259","35260","35261","35262","35263","35264","35265","35266","35267","35268","35269","35270","35271","35272","35273","35274","35275","35276","35277","35278","35279","35280","35281","35282","35283","35284","35285","35286","35287","35288","35289","35290","35291","35292","35293","35294","35295","35296","35297","35298","35299","35300","35301","35302","35303","35304","35305","35306","35307","35308","35309","35310","35311","35312","35313","35314","35315","35316","35317","35318","35319","35320","35321","35322","35323","35324","35325","35326","35327","35328","35329","35330","35331","35332","35333","35334","35335","35336","35337","35338","35339","35340","35341","35342","35343","35344","35345","35346","35347","35348","35349","35350","35351","35352","35353","35354","35355","35356","35357","35358","35359","35360","35361","35362","35363","35364","35365","35366","35367","35368","35369","35370","35371","35372","35373","35374","35375","35376","35377","35378","35379","35380","35381","35382","35383","35384","35385","35386","35387","35388","35389","35390","35391","35392","35393","35394","35395","35396","35397","35398","35399","35400","35401","35402","35403","35404","35405","35406","35407","35408","35409","35410","35411","35412","35413","35414","35415","35416","35417","35418","35419","35420","35421","35422","35423","35424","35425","35426","35427","35428","35429","35430","35431","35432","35433","35434","35435","35436","35437","35438","35439","35440","35441","35442","35443","35444","35445","35446","35447","35448","35449","35450","35451","35452","35453","35454","35455","35456","35457","35458","35459","35460","35461","35462","35463","35464","35465","35466","35467","35468","35469","35470","35471","35472","35473","35474","35475","35476","35477","35478","35479","35480","35481","35482","35483","35484","35485","35486","35487","35488","35489","35490","35491","35492","35493","35494","35495","35496","35497","35498","35499","35500","35501","35502","35503","35504","35505","35506","35507","35508","35509","35510","35511","35512","35513","35514","35515","35516","35517","35518","35519","35520","35521","35522","35523","35524","35525","35526","35527","35528","35529","35530","35531","35532","35533","35534","35535","35536","35537","35538","35539","35540","35541","35542","35543","35544","35545","35546","35547","35548","35549","35550","35551","35552","35553","35554","35555","35556","35557","35558","35559","35560","35561","35562","35563","35564","35565","35566","35567","35568","35569","35570","35571","35572","35573","35574","35575","35576","35577","35578","35579","35580","35581","35582","35583","35584","35585","35586","35587","35588","35589","35590","35591","35592","35593","35594","35595","35596","35597","35598","35599","35600","35601","35602","35603","35604","35605","35606","35607","35608","35609","35610","35611","35612","35613","35614","35615","35616","35617","35618","35619","35620","35621","35622","35623","35624","35625","35626","35627","35628","35629","35630","35631","35632","35633","35634","35635","35636","35637","35638","35639","35640","35641","35642","35643","35644","35645","35646","35647","35648","35649","35650","35651","35652","35653","35654","35655","35656","35657","35658","35659","35660","35661","35662","35663","35664","35665","35666","35667","35668","35669","35670","35671","35672","35673","35674","35675","35676","35677","35678","35679","35680","35681","35682","35683","35684","35685","35686","35687","35688","35689","35690","35691","35692","35693","35694","35695","35696","35697","35698","35699","35700","35701","35702","35703","35704","35705","35706","35707","35708","35709","35710","35711","35712","35713","35714","35715","35716","35717","35718","35719","35720","35721","35722","35723","35724","35725","35726","35727","35728","35729","35730","35731","35732","35733","35734","35735","35736","35737","35738","35739","35740","35741","35742","35743","35744","35745","35746","35747","35748","35749","35750","35751","35752","35753","35754","35755","35756","35757","35758","35759","35760","35761","35762","35763","35764","35765","35766","35767","35768","35769","35770","35771","35772","35773","35774","35775","35776","35777","35778","35779","35780","35781","35782","35783","35784","35785","35786","35787","35788","35789","35790","35791","35792","35793","35794","35795","35796","35797","35798","35799","35800","35801","35802","35803","35804","35805","35806","35807","35808","35809","35810","35811","35812","35813","35814","35815","35816","35817","35818","35819","35820","35821","35822","35823","35824","35825","35826","35827","35828","35829","35830","35831","35832","35833","35834","35835","35836","35837","35838","35839","35840","35841","35842","35843","35844","35845","35846","35847","35848","35849","35850","35851","35852","35853","35854","35855","35856","35857","35858","35859","35860","35861","35862","35863","35864","35865","35866","35867","35868","35869","35870","35871","35872","35873","35874","35875","35876","35877","35878","35879","35880","35881","35882","35883","35884","35885","35886","35887","35888","35889","35890","35891","35892","35893","35894","35895","35896","35897","35898","35899","35900","35901","35902","35903","35904","35905","35906","35907","35908","35909","35910","35911","35912","35913","35914","35915","35916","35917","35918","35919","35920","35921","35922","35923","35924","35925","35926","35927","35928","35929","35930","35931","35932","35933","35934","35935","35936","35937","35938","35939","35940","35941","35942","35943","35944","35945","35946","35947","35948","35949","35950","35951","35952","35953","35954","35955","35956","35957","35958","35959","35960","35961","35962","35963","35964","35965","35966","35967","35968","35969","35970","35971","35972","35973","35974","35975","35976","35977","35978","35979","35980","35981","35982","35983","35984","35985","35986","35987","35988","35989","35990","35991","35992","35993","35994","35995","35996","35997","35998","35999","36000","36001","36002","36003","36004","36005","36006","36007","36008","36009","36010","36011","36012","36013","36014","36015","36016","36017","36018","36019","36020","36021","36022","36023","36024","36025","36026","36027","36028","36029","36030","36031","36032","36033","36034","36035","36036","36037","36038","36039","36040","36041","36042","36043","36044","36045","36046","36047","36048","36049","36050","36051","36052","36053","36054","36055","36056","36057","36058","36059","36060","36061","36062","36063","36064","36065","36066","36067","36068","36069","36070","36071","36072","36073","36074","36075","36076","36077","36078","36079","36080","36081","36082","36083","36084","36085","36086","36087","36088","36089","36090","36091","36092","36093","36094","36095","36096","36097","36098","36099","36100","36101","36102","36103","36104","36105","36106","36107","36108","36109","36110","36111","36112","36113","36114","36115","36116","36117","36118","36119","36120","36121","36122","36123","36124","36125","36126","36127","36128","36129","36130","36131","36132","36133","36134","36135","36136","36137","36138","36139","36140","36141","36142","36143","36144","36145","36146","36147","36148","36149","36150","36151","36152","36153","36154","36155","36156","36157","36158","36159","36160","36161","36162","36163","36164","36165","36166","36167","36168","36169","36170","36171","36172","36173","36174","36175","36176","36177","36178","36179","36180","36181","36182","36183","36184","36185","36186","36187","36188","36189","36190","36191","36192","36193","36194","36195","36196","36197","36198","36199","36200","36201","36202","36203","36204","36205","36206","36207","36208","36209","36210","36211","36212","36213","36214","36215","36216","36217","36218","36219","36220","36221","36222","36223","36224","36225","36226","36227","36228","36229","36230","36231","36232","36233","36234","36235","36236","36237","36238","36239","36240","36241","36242","36243","36244","36245","36246","36247","36248","36249","36250","36251","36252","36253","36254","36255","36256","36257","36258","36259","36260","36261","36262","36263","36264","36265","36266","36267","36268","36269","36270","36271","36272","36273","36274","36275","36276","36277","36278","36279","36280","36281","36282","36283","36284","36285","36286","36287","36288","36289","36290","36291","36292","36293","36294","36295","36296","36297","36298","36299","36300","36301","36302","36303","36304","36305","36306","36307","36308","36309","36310","36311","36312","36313","36314","36315","36316","36317","36318","36319","36320","36321","36322","36323","36324","36325","36326","36327","36328","36329","36330","36331","36332","36333","36334","36335","36336","36337","36338","36339","36340","36341","36342","36343","36344","36345","36346","36347","36348","36349","36350","36351","36352","36353","36354","36355","36356","36357","36358","36359","36360","36361","36362","36363","36364","36365","36366","36367","36368","36369","36370","36371","36372","36373","36374","36375","36376","36377","36378","36379","36380","36381","36382","36383","36384","36385","36386","36387","36388","36389","36390","36391","36392","36393","36394","36395","36396","36397","36398","36399","36400","36401","36402","36403","36404","36405","36406","36407","36408","36409","36410","36411","36412","36413","36414","36415","36416","36417","36418","36419","36420","36421","36422","36423","36424","36425","36426","36427","36428","36429","36430","36431","36432","36433","36434","36435","36436","36437","36438","36439","36440","36441","36442","36443","36444","36445","36446","36447","36448","36449","36450","36451","36452","36453","36454","36455","36456","36457","36458","36459","36460","36461","36462","36463","36464","36465","36466","36467","36468","36469","36470","36471","36472","36473","36474","36475","36476","36477","36478","36479","36480","36481","36482","36483","36484","36485","36486","36487","36488","36489","36490","36491","36492","36493","36494","36495","36496","36497","36498","36499","36500","36501","36502","36503","36504","36505","36506","36507","36508","36509","36510","36511","36512","36513","36514","36515","36516","36517","36518","36519","36520","36521","36522","36523","36524","36525","36526","36527","36528","36529","36530","36531","36532","36533","36534","36535","36536","36537","36538","36539","36540","36541","36542","36543","36544","36545","36546","36547","36548","36549","36550","36551","36552","36553","36554","36555","36556","36557","36558","36559","36560","36561","36562","36563","36564","36565","36566","36567","36568","36569","36570","36571","36572","36573","36574","36575","36576","36577","36578","36579","36580","36581","36582","36583","36584","36585","36586","36587","36588","36589","36590","36591","36592","36593","36594","36595","36596","36597","36598","36599","36600","36601","36602","36603","36604","36605","36606","36607","36608","36609","36610","36611","36612","36613","36614","36615","36616","36617","36618","36619","36620","36621","36622","36623","36624","36625","36626","36627","36628","36629","36630","36631","36632","36633","36634","36635","36636","36637","36638","36639","36640","36641","36642","36643","36644","36645","36646","36647","36648","36649","36650","36651","36652","36653","36654","36655","36656","36657","36658","36659","36660","36661","36662","36663","36664","36665","36666","36667","36668","36669","36670","36671","36672","36673","36674","36675","36676","36677","36678","36679","36680","36681","36682","36683","36684","36685","36686","36687","36688","36689","36690","36691","36692","36693","36694","36695","36696","36697","36698","36699","36700","36701","36702","36703","36704","36705","36706","36707","36708","36709","36710","36711","36712","36713","36714","36715","36716","36717","36718","36719","36720","36721","36722","36723","36724","36725","36726","36727","36728","36729","36730","36731","36732","36733","36734","36735","36736","36737","36738","36739","36740","36741","36742","36743","36744","36745","36746","36747","36748","36749","36750","36751","36752","36753","36754","36755","36756","36757","36758","36759","36760","36761","36762","36763","36764","36765","36766","36767","36768","36769","36770","36771","36772","36773","36774","36775","36776","36777","36778","36779","36780","36781","36782","36783","36784","36785","36786","36787","36788","36789","36790","36791","36792","36793","36794","36795","36796","36797","36798","36799","36800","36801","36802","36803","36804","36805","36806","36807","36808","36809","36810","36811","36812","36813","36814","36815","36816","36817","36818","36819","36820","36821","36822","36823","36824","36825","36826","36827","36828","36829","36830","36831","36832","36833","36834","36835","36836","36837","36838","36839","36840","36841","36842","36843","36844","36845","36846","36847","36848","36849","36850","36851","36852","36853","36854","36855","36856","36857","36858","36859","36860","36861","36862","36863","36864","36865","36866","36867","36868","36869","36870","36871","36872","36873","36874","36875","36876","36877","36878","36879","36880","36881","36882","36883","36884","36885","36886","36887","36888","36889","36890","36891","36892","36893","36894","36895","36896","36897","36898","36899","36900","36901","36902","36903","36904","36905","36906","36907","36908","36909","36910","36911","36912","36913","36914","36915","36916","36917","36918","36919","36920","36921","36922","36923","36924","36925","36926","36927","36928","36929","36930","36931","36932","36933","36934","36935","36936","36937","36938","36939","36940","36941","36942","36943","36944","36945","36946","36947","36948","36949","36950","36951","36952","36953","36954","36955","36956","36957","36958","36959","36960","36961","36962","36963","36964","36965","36966","36967","36968","36969","36970","36971","36972","36973","36974","36975","36976","36977","36978","36979","36980","36981","36982","36983","36984","36985","36986","36987","36988","36989","36990","36991","36992","36993","36994","36995","36996","36997","36998","36999","37000","37001","37002","37003","37004","37005","37006","37007","37008","37009","37010","37011","37012","37013","37014","37015","37016","37017","37018","37019","37020","37021","37022","37023","37024","37025","37026","37027","37028","37029","37030","37031","37032","37033","37034","37035","37036","37037","37038","37039","37040","37041","37042","37043","37044","37045","37046","37047","37048","37049","37050","37051","37052","37053","37054","37055","37056","37057","37058","37059","37060","37061","37062","37063","37064","37065","37066","37067","37068","37069","37070","37071","37072","37073","37074","37075","37076","37077","37078","37079","37080","37081","37082","37083","37084","37085","37086","37087","37088","37089","37090","37091","37092","37093","37094","37095","37096","37097","37098","37099","37100","37101","37102","37103","37104","37105","37106","37107","37108","37109","37110","37111","37112","37113","37114","37115","37116","37117","37118","37119","37120","37121","37122","37123","37124","37125","37126","37127","37128","37129","37130","37131","37132","37133","37134","37135","37136","37137","37138","37139","37140","37141","37142","37143","37144","37145","37146","37147","37148","37149","37150","37151","37152","37153","37154","37155","37156","37157","37158","37159","37160","37161","37162","37163","37164","37165","37166","37167","37168","37169","37170","37171","37172","37173","37174","37175","37176","37177","37178","37179","37180","37181","37182","37183","37184","37185","37186","37187","37188","37189","37190","37191","37192","37193","37194","37195","37196","37197","37198","37199","37200","37201","37202","37203","37204","37205","37206","37207","37208","37209","37210","37211","37212","37213","37214","37215","37216","37217","37218","37219","37220","37221","37222","37223","37224","37225","37226","37227","37228","37229","37230","37231","37232","37233","37234","37235","37236","37237","37238","37239","37240","37241","37242","37243","37244","37245","37246","37247","37248","37249","37250","37251","37252","37253","37254","37255","37256","37257","37258","37259","37260","37261","37262","37263","37264","37265","37266","37267","37268","37269","37270","37271","37272","37273","37274","37275","37276","37277","37278","37279","37280","37281","37282","37283","37284","37285","37286","37287","37288","37289","37290","37291","37292","37293","37294","37295","37296","37297","37298","37299","37300","37301","37302","37303","37304","37305","37306","37307","37308","37309","37310","37311","37312","37313","37314","37315","37316","37317","37318","37319","37320","37321","37322","37323","37324","37325","37326","37327","37328","37329","37330","37331","37332","37333","37334","37335","37336","37337","37338","37339","37340","37341","37342","37343","37344","37345","37346","37347","37348","37349","37350","37351","37352","37353","37354","37355","37356","37357","37358","37359","37360","37361","37362","37363","37364","37365","37366","37367","37368","37369","37370","37371","37372","37373","37374","37375","37376","37377","37378","37379","37380","37381","37382","37383","37384","37385","37386","37387","37388","37389","37390","37391","37392","37393","37394","37395","37396","37397","37398","37399","37400","37401","37402","37403","37404","37405","37406","37407","37408","37409","37410","37411","37412","37413","37414","37415","37416","37417","37418","37419","37420","37421","37422","37423","37424","37425","37426","37427","37428","37429","37430","37431","37432","37433","37434","37435","37436","37437","37438","37439","37440","37441","37442","37443","37444","37445","37446","37447","37448","37449","37450","37451","37452","37453","37454","37455","37456","37457","37458","37459","37460","37461","37462","37463","37464","37465","37466","37467","37468","37469","37470","37471","37472","37473","37474","37475","37476","37477","37478","37479","37480","37481","37482","37483","37484","37485","37486","37487","37488","37489","37490","37491","37492","37493","37494","37495","37496","37497","37498","37499","37500","37501","37502","37503","37504","37505","37506","37507","37508","37509","37510","37511","37512","37513","37514","37515","37516","37517","37518","37519","37520","37521","37522","37523","37524","37525","37526","37527","37528","37529","37530","37531","37532","37533","37534","37535","37536","37537","37538","37539","37540","37541","37542","37543","37544","37545","37546","37547","37548","37549","37550","37551","37552","37553","37554","37555","37556","37557","37558","37559","37560","37561","37562","37563","37564","37565","37566","37567","37568","37569","37570","37571","37572","37573","37574","37575","37576","37577","37578","37579","37580","37581","37582","37583","37584","37585","37586","37587","37588","37589","37590","37591","37592","37593","37594","37595","37596","37597","37598","37599","37600","37601","37602","37603","37604","37605","37606","37607","37608","37609","37610","37611","37612","37613","37614","37615","37616","37617","37618","37619","37620","37621","37622","37623","37624","37625","37626","37627","37628","37629","37630","37631","37632","37633","37634","37635","37636","37637","37638","37639","37640","37641","37642","37643","37644","37645","37646","37647","37648","37649","37650","37651","37652","37653","37654","37655","37656","37657","37658","37659","37660","37661","37662","37663","37664","37665","37666","37667","37668","37669","37670","37671","37672","37673","37674","37675","37676","37677","37678","37679","37680","37681","37682","37683","37684","37685","37686","37687","37688","37689","37690","37691","37692","37693","37694","37695","37696","37697","37698","37699","37700","37701","37702","37703","37704","37705","37706","37707","37708","37709","37710","37711","37712","37713","37714","37715","37716","37717","37718","37719","37720","37721","37722","37723","37724","37725","37726","37727","37728","37729","37730","37731","37732","37733","37734","37735","37736","37737","37738","37739","37740","37741","37742","37743","37744","37745","37746","37747","37748","37749","37750","37751","37752","37753","37754","37755","37756","37757","37758","37759","37760","37761","37762","37763","37764","37765","37766","37767","37768","37769","37770","37771","37772","37773","37774","37775","37776","37777","37778","37779","37780","37781","37782","37783","37784","37785","37786","37787","37788","37789","37790","37791","37792","37793","37794","37795","37796","37797","37798","37799","37800","37801","37802","37803","37804","37805","37806","37807","37808","37809","37810","37811","37812","37813","37814","37815","37816","37817","37818","37819","37820","37821","37822","37823","37824","37825","37826","37827","37828","37829","37830","37831","37832","37833","37834","37835","37836","37837","37838","37839","37840","37841","37842","37843","37844","37845","37846","37847","37848","37849","37850","37851","37852","37853","37854","37855","37856","37857","37858","37859","37860","37861","37862","37863","37864","37865","37866","37867","37868","37869","37870","37871","37872","37873","37874","37875","37876","37877","37878","37879","37880","37881","37882","37883","37884","37885","37886","37887","37888","37889","37890","37891","37892","37893","37894","37895","37896","37897","37898","37899","37900","37901","37902","37903","37904","37905","37906","37907","37908","37909","37910","37911","37912","37913","37914","37915","37916","37917","37918","37919","37920","37921","37922","37923","37924","37925","37926","37927","37928","37929","37930","37931","37932","37933","37934","37935","37936","37937","37938","37939","37940","37941","37942","37943","37944","37945","37946","37947","37948","37949","37950","37951","37952","37953","37954","37955","37956","37957","37958","37959","37960","37961","37962","37963","37964","37965","37966","37967","37968","37969","37970","37971","37972","37973","37974","37975","37976","37977","37978","37979","37980","37981","37982","37983","37984","37985","37986","37987","37988","37989","37990","37991","37992","37993","37994","37995","37996","37997","37998","37999","38000","38001","38002","38003","38004","38005","38006","38007","38008","38009","38010","38011","38012","38013","38014","38015","38016","38017","38018","38019","38020","38021","38022","38023","38024","38025","38026","38027","38028","38029","38030","38031","38032","38033","38034","38035","38036","38037","38038","38039","38040","38041","38042","38043","38044","38045","38046","38047","38048","38049","38050","38051","38052","38053","38054","38055","38056","38057","38058","38059","38060","38061","38062","38063","38064","38065","38066","38067","38068","38069","38070","38071","38072","38073","38074","38075","38076","38077","38078","38079","38080","38081","38082","38083","38084","38085","38086","38087","38088","38089","38090","38091","38092","38093","38094","38095","38096","38097","38098","38099","38100","38101","38102","38103","38104","38105","38106","38107","38108","38109","38110","38111","38112","38113","38114","38115","38116","38117","38118","38119","38120","38121","38122","38123","38124","38125","38126","38127","38128","38129","38130","38131","38132","38133","38134","38135","38136","38137","38138","38139","38140","38141","38142","38143","38144","38145","38146","38147","38148","38149","38150","38151","38152","38153","38154","38155","38156","38157","38158","38159","38160","38161","38162","38163","38164","38165","38166","38167","38168","38169","38170","38171","38172","38173","38174","38175","38176","38177","38178","38179","38180","38181","38182","38183","38184","38185","38186","38187","38188","38189","38190","38191","38192","38193","38194","38195","38196","38197","38198","38199","38200","38201","38202","38203","38204","38205","38206","38207","38208","38209","38210","38211","38212","38213","38214","38215","38216","38217","38218","38219","38220","38221","38222","38223","38224","38225","38226","38227","38228","38229","38230","38231","38232","38233","38234","38235","38236","38237","38238","38239","38240","38241","38242","38243","38244","38245","38246","38247","38248","38249","38250","38251","38252","38253","38254","38255","38256","38257","38258","38259","38260","38261","38262","38263","38264","38265","38266","38267","38268","38269","38270","38271","38272","38273","38274","38275","38276","38277","38278","38279","38280","38281","38282","38283","38284","38285","38286","38287","38288","38289","38290","38291","38292","38293","38294","38295","38296","38297","38298","38299","38300","38301","38302","38303","38304","38305","38306","38307","38308","38309","38310","38311","38312","38313","38314","38315","38316","38317","38318","38319","38320","38321","38322","38323","38324","38325","38326","38327","38328","38329","38330","38331","38332","38333","38334","38335","38336","38337","38338","38339","38340","38341","38342","38343","38344","38345","38346","38347","38348","38349","38350","38351","38352","38353","38354","38355","38356","38357","38358","38359","38360","38361","38362","38363","38364","38365","38366","38367","38368","38369","38370","38371","38372","38373","38374","38375","38376","38377","38378","38379","38380","38381","38382","38383","38384","38385","38386","38387","38388","38389","38390","38391","38392","38393","38394","38395","38396","38397","38398","38399","38400","38401","38402","38403","38404","38405","38406","38407","38408","38409","38410","38411","38412","38413","38414","38415","38416","38417","38418","38419","38420","38421","38422","38423","38424","38425","38426","38427","38428","38429","38430","38431","38432","38433","38434","38435","38436","38437","38438","38439","38440","38441","38442","38443","38444","38445","38446","38447","38448","38449","38450","38451","38452","38453","38454","38455","38456","38457","38458","38459","38460","38461","38462","38463","38464","38465","38466","38467","38468","38469","38470","38471","38472","38473","38474","38475","38476","38477","38478","38479","38480","38481","38482","38483","38484","38485","38486","38487","38488","38489","38490","38491","38492","38493","38494","38495","38496","38497","38498","38499","38500","38501","38502","38503","38504","38505","38506","38507","38508","38509","38510","38511","38512","38513","38514","38515","38516","38517","38518","38519","38520","38521","38522","38523","38524","38525","38526","38527","38528","38529","38530","38531","38532","38533","38534","38535","38536","38537","38538","38539","38540","38541","38542","38543","38544","38545","38546","38547","38548","38549","38550","38551","38552","38553","38554","38555","38556","38557","38558","38559","38560","38561","38562","38563","38564","38565","38566","38567","38568","38569","38570","38571","38572","38573","38574","38575","38576","38577","38578","38579","38580","38581","38582","38583","38584","38585","38586","38587","38588","38589","38590","38591","38592","38593","38594","38595","38596","38597","38598","38599","38600","38601","38602","38603","38604","38605","38606","38607","38608","38609","38610","38611","38612","38613","38614","38615","38616","38617","38618","38619","38620","38621","38622","38623","38624","38625","38626","38627","38628","38629","38630","38631","38632","38633","38634","38635","38636","38637","38638","38639","38640","38641","38642","38643","38644","38645","38646","38647","38648","38649","38650","38651","38652","38653","38654","38655","38656","38657","38658","38659","38660","38661","38662","38663","38664","38665","38666","38667","38668","38669","38670","38671","38672","38673","38674","38675","38676","38677","38678","38679","38680","38681","38682","38683","38684","38685","38686","38687","38688","38689","38690","38691","38692","38693","38694","38695","38696","38697","38698","38699","38700","38701","38702","38703","38704","38705","38706","38707","38708","38709","38710","38711","38712","38713","38714","38715","38716","38717","38718","38719","38720","38721","38722","38723","38724","38725","38726","38727","38728","38729","38730","38731","38732","38733","38734","38735","38736","38737","38738","38739","38740","38741","38742","38743","38744","38745","38746","38747","38748","38749","38750","38751","38752","38753","38754","38755","38756","38757","38758","38759","38760","38761","38762","38763","38764","38765","38766","38767","38768","38769","38770","38771","38772","38773","38774","38775","38776","38777","38778","38779","38780","38781","38782","38783","38784","38785","38786","38787","38788","38789","38790","38791","38792","38793","38794","38795","38796","38797","38798","38799","38800","38801","38802","38803","38804","38805","38806","38807","38808","38809","38810","38811","38812","38813","38814","38815","38816","38817","38818","38819","38820","38821","38822","38823","38824","38825","38826","38827","38828","38829","38830","38831","38832","38833","38834","38835","38836","38837","38838","38839","38840","38841","38842","38843","38844","38845","38846","38847","38848","38849","38850","38851","38852","38853","38854","38855","38856","38857","38858","38859","38860","38861","38862","38863","38864","38865","38866","38867","38868","38869","38870","38871","38872","38873","38874","38875","38876","38877","38878","38879","38880","38881","38882","38883","38884","38885","38886","38887","38888","38889","38890","38891","38892","38893","38894","38895","38896","38897","38898","38899","38900","38901","38902","38903","38904","38905","38906","38907","38908","38909","38910","38911","38912","38913","38914","38915","38916","38917","38918","38919","38920","38921","38922","38923","38924","38925","38926","38927","38928","38929","38930","38931","38932","38933","38934","38935","38936","38937","38938","38939","38940","38941","38942","38943","38944","38945","38946","38947","38948","38949","38950","38951","38952","38953","38954","38955","38956","38957","38958","38959","38960","38961","38962","38963","38964","38965","38966","38967","38968","38969","38970","38971","38972","38973","38974","38975","38976","38977","38978","38979","38980","38981","38982","38983","38984","38985","38986","38987","38988","38989","38990","38991","38992","38993","38994","38995","38996","38997","38998","38999","39000","39001","39002","39003","39004","39005","39006","39007","39008","39009","39010","39011","39012","39013","39014","39015","39016","39017","39018","39019","39020","39021","39022","39023","39024","39025","39026","39027","39028","39029","39030","39031","39032","39033","39034","39035","39036","39037","39038","39039","39040","39041","39042","39043","39044","39045","39046","39047","39048","39049","39050","39051","39052","39053","39054","39055","39056","39057","39058","39059","39060","39061","39062","39063","39064","39065","39066","39067","39068","39069","39070","39071","39072","39073","39074","39075","39076","39077","39078","39079","39080","39081","39082","39083","39084","39085","39086","39087","39088","39089","39090","39091","39092","39093","39094","39095","39096","39097","39098","39099","39100","39101","39102","39103","39104","39105","39106","39107","39108","39109","39110","39111","39112","39113","39114","39115","39116","39117","39118","39119","39120","39121","39122","39123","39124","39125","39126","39127","39128","39129","39130","39131","39132","39133","39134","39135","39136","39137","39138","39139","39140","39141","39142","39143","39144","39145","39146","39147","39148","39149","39150","39151","39152","39153","39154","39155","39156","39157","39158","39159","39160","39161","39162","39163","39164","39165","39166","39167","39168","39169","39170","39171","39172","39173","39174","39175","39176","39177","39178","39179","39180","39181","39182","39183","39184","39185","39186","39187","39188","39189","39190","39191","39192","39193","39194","39195","39196","39197","39198","39199","39200","39201","39202","39203","39204","39205","39206","39207","39208","39209","39210","39211","39212","39213","39214","39215","39216","39217","39218","39219","39220","39221","39222","39223","39224","39225","39226","39227","39228","39229","39230","39231","39232","39233","39234","39235","39236","39237","39238","39239","39240","39241","39242","39243","39244","39245","39246","39247","39248","39249","39250","39251","39252","39253","39254","39255","39256","39257","39258","39259","39260","39261","39262","39263","39264","39265","39266","39267","39268","39269","39270","39271","39272","39273","39274","39275","39276","39277","39278","39279","39280","39281","39282","39283","39284","39285","39286","39287","39288","39289","39290","39291","39292","39293","39294","39295","39296","39297","39298","39299","39300","39301","39302","39303","39304","39305","39306","39307","39308","39309","39310","39311","39312","39313","39314","39315","39316","39317","39318","39319","39320","39321","39322","39323","39324","39325","39326","39327","39328","39329","39330","39331","39332","39333","39334","39335","39336","39337","39338","39339","39340","39341","39342","39343","39344","39345","39346","39347","39348","39349","39350","39351","39352","39353","39354","39355","39356","39357","39358","39359","39360","39361","39362","39363","39364","39365","39366","39367","39368","39369","39370","39371","39372","39373","39374","39375","39376","39377","39378","39379","39380","39381","39382","39383","39384","39385","39386","39387","39388","39389","39390","39391","39392","39393","39394","39395","39396","39397","39398","39399","39400","39401","39402","39403","39404","39405","39406","39407","39408","39409","39410","39411","39412","39413","39414","39415","39416","39417","39418","39419","39420","39421","39422","39423","39424","39425","39426","39427","39428","39429","39430","39431","39432","39433","39434","39435","39436","39437","39438","39439","39440","39441","39442","39443","39444","39445","39446","39447","39448","39449","39450","39451","39452","39453","39454","39455","39456","39457","39458","39459","39460","39461","39462","39463","39464","39465","39466","39467","39468","39469","39470","39471","39472","39473","39474","39475","39476","39477","39478","39479","39480","39481","39482","39483","39484","39485","39486","39487","39488","39489","39490","39491","39492","39493","39494","39495","39496","39497","39498","39499","39500","39501","39502","39503","39504","39505","39506","39507","39508","39509","39510","39511","39512","39513","39514","39515","39516","39517","39518","39519","39520","39521","39522","39523","39524","39525","39526","39527","39528","39529","39530","39531","39532","39533","39534","39535","39536","39537","39538","39539","39540","39541","39542","39543","39544","39545","39546","39547","39548","39549","39550","39551","39552","39553","39554","39555","39556","39557","39558","39559","39560","39561","39562","39563","39564","39565","39566","39567","39568","39569","39570","39571","39572","39573","39574","39575","39576","39577","39578","39579","39580","39581","39582","39583","39584","39585","39586","39587","39588","39589","39590","39591","39592","39593","39594","39595","39596","39597","39598","39599","39600","39601","39602","39603","39604","39605","39606","39607","39608","39609","39610","39611","39612","39613","39614","39615","39616","39617","39618","39619","39620","39621","39622","39623","39624","39625","39626","39627","39628","39629","39630","39631","39632","39633","39634","39635","39636","39637","39638","39639","39640","39641","39642","39643","39644","39645","39646","39647","39648","39649","39650","39651","39652","39653","39654","39655","39656","39657","39658","39659","39660","39661","39662","39663","39664","39665","39666","39667","39668","39669","39670","39671","39672","39673","39674","39675","39676","39677","39678","39679","39680","39681","39682","39683","39684","39685","39686","39687","39688","39689","39690","39691","39692","39693","39694","39695","39696","39697","39698","39699","39700","39701","39702","39703","39704","39705","39706","39707","39708","39709","39710","39711","39712","39713","39714","39715","39716","39717","39718","39719","39720","39721","39722","39723","39724","39725","39726","39727","39728","39729","39730","39731","39732","39733","39734","39735","39736","39737","39738","39739","39740","39741","39742","39743","39744","39745","39746","39747","39748","39749","39750","39751","39752","39753","39754","39755","39756","39757","39758","39759","39760","39761","39762","39763","39764","39765","39766","39767","39768","39769","39770","39771","39772","39773","39774","39775","39776","39777","39778","39779","39780","39781","39782","39783","39784","39785","39786","39787","39788","39789","39790","39791","39792","39793","39794","39795","39796","39797","39798","39799","39800","39801","39802","39803","39804","39805","39806","39807","39808","39809","39810","39811","39812","39813","39814","39815","39816","39817","39818","39819","39820","39821","39822","39823","39824","39825","39826","39827","39828","39829","39830","39831","39832","39833","39834","39835","39836","39837","39838","39839","39840","39841","39842","39843","39844","39845","39846","39847","39848","39849","39850","39851","39852","39853","39854","39855","39856","39857","39858","39859","39860","39861","39862","39863","39864","39865","39866","39867","39868","39869","39870","39871","39872","39873","39874","39875","39876","39877","39878","39879","39880","39881","39882","39883","39884","39885","39886","39887","39888","39889","39890","39891","39892","39893","39894","39895","39896","39897","39898","39899","39900","39901","39902","39903","39904","39905","39906","39907","39908","39909","39910","39911","39912","39913","39914","39915","39916","39917","39918","39919","39920","39921","39922","39923","39924","39925","39926","39927","39928","39929","39930","39931","39932","39933","39934","39935","39936","39937","39938","39939","39940","39941","39942","39943","39944","39945","39946","39947","39948","39949","39950","39951","39952","39953","39954","39955","39956","39957","39958","39959","39960","39961","39962","39963","39964","39965","39966","39967","39968","39969","39970","39971","39972","39973","39974","39975","39976","39977","39978","39979","39980","39981","39982","39983","39984","39985","39986","39987","39988","39989","39990","39991","39992","39993","39994","39995","39996","39997","39998","39999","40000","40001","40002","40003","40004","40005","40006","40007","40008","40009","40010","40011","40012","40013","40014","40015","40016","40017","40018","40019","40020","40021","40022","40023","40024","40025","40026","40027","40028","40029","40030","40031","40032","40033","40034","40035","40036","40037","40038","40039","40040","40041","40042","40043","40044","40045","40046","40047","40048","40049","40050","40051","40052","40053","40054","40055","40056","40057","40058","40059","40060","40061","40062","40063","40064","40065","40066","40067","40068","40069","40070","40071","40072","40073","40074","40075","40076","40077","40078","40079","40080","40081","40082","40083","40084","40085","40086","40087","40088","40089","40090","40091","40092","40093","40094","40095","40096","40097","40098","40099","40100","40101","40102","40103","40104","40105","40106","40107","40108","40109","40110","40111","40112","40113","40114","40115","40116","40117","40118","40119","40120","40121","40122","40123","40124","40125","40126","40127","40128","40129","40130","40131","40132","40133","40134","40135","40136","40137","40138","40139","40140","40141","40142","40143","40144","40145","40146","40147","40148","40149","40150","40151","40152","40153","40154","40155","40156","40157","40158","40159","40160","40161","40162","40163","40164","40165","40166","40167","40168","40169","40170","40171","40172","40173","40174","40175","40176","40177","40178","40179","40180","40181","40182","40183","40184","40185","40186","40187","40188","40189","40190","40191","40192","40193","40194","40195","40196","40197","40198","40199","40200","40201","40202","40203","40204","40205","40206","40207","40208","40209","40210","40211","40212","40213","40214","40215","40216","40217","40218","40219","40220","40221","40222","40223","40224","40225","40226","40227","40228","40229","40230","40231","40232","40233","40234","40235","40236","40237","40238","40239","40240","40241","40242","40243","40244","40245","40246","40247","40248","40249","40250","40251","40252","40253","40254","40255","40256","40257","40258","40259","40260","40261","40262","40263","40264","40265","40266","40267","40268","40269","40270","40271","40272","40273","40274","40275","40276","40277","40278","40279","40280","40281","40282","40283","40284","40285","40286","40287","40288","40289","40290","40291","40292","40293","40294","40295","40296","40297","40298","40299","40300","40301","40302","40303","40304","40305","40306","40307","40308","40309","40310","40311","40312","40313","40314","40315","40316","40317","40318","40319","40320","40321","40322","40323","40324","40325","40326","40327","40328","40329","40330","40331","40332","40333","40334","40335","40336","40337","40338","40339","40340","40341","40342","40343","40344","40345","40346","40347","40348","40349","40350","40351","40352","40353","40354","40355","40356","40357","40358","40359","40360","40361","40362","40363","40364","40365","40366","40367","40368","40369","40370","40371","40372","40373","40374","40375","40376","40377","40378","40379","40380","40381","40382","40383","40384","40385","40386","40387","40388","40389","40390","40391","40392","40393","40394","40395","40396","40397","40398","40399","40400","40401","40402","40403","40404","40405","40406","40407","40408","40409","40410","40411","40412","40413","40414","40415","40416","40417","40418","40419","40420","40421","40422","40423","40424","40425","40426","40427","40428","40429","40430","40431","40432","40433","40434","40435","40436","40437","40438","40439","40440","40441","40442","40443","40444","40445","40446","40447","40448","40449","40450","40451","40452","40453","40454","40455","40456","40457","40458","40459","40460","40461","40462","40463","40464","40465","40466","40467","40468","40469","40470","40471","40472","40473","40474","40475","40476","40477","40478","40479","40480","40481","40482","40483","40484","40485","40486","40487","40488","40489","40490","40491","40492","40493","40494","40495","40496","40497","40498","40499","40500","40501","40502","40503","40504","40505","40506","40507","40508","40509","40510","40511","40512","40513","40514","40515","40516","40517","40518","40519","40520","40521","40522","40523","40524","40525","40526","40527","40528","40529","40530","40531","40532","40533","40534","40535","40536","40537","40538","40539","40540","40541","40542","40543","40544","40545","40546","40547","40548","40549","40550","40551","40552","40553","40554","40555","40556","40557","40558","40559","40560","40561","40562","40563","40564","40565","40566","40567","40568","40569","40570","40571","40572","40573","40574","40575","40576","40577","40578","40579","40580","40581","40582","40583","40584","40585","40586","40587","40588","40589","40590","40591","40592","40593","40594","40595","40596","40597","40598","40599","40600","40601","40602","40603","40604","40605","40606","40607","40608","40609","40610","40611","40612","40613","40614","40615","40616","40617","40618","40619","40620","40621","40622","40623","40624","40625","40626","40627","40628","40629","40630","40631","40632","40633","40634","40635","40636","40637","40638","40639","40640","40641","40642","40643","40644","40645","40646","40647","40648","40649","40650","40651","40652","40653","40654","40655","40656","40657","40658","40659","40660","40661","40662","40663","40664","40665","40666","40667","40668","40669","40670","40671","40672","40673","40674","40675","40676","40677","40678","40679","40680","40681","40682","40683","40684","40685","40686","40687","40688","40689","40690","40691","40692","40693","40694","40695","40696","40697","40698","40699","40700","40701","40702","40703","40704","40705","40706","40707","40708","40709","40710","40711","40712","40713","40714","40715","40716","40717","40718","40719","40720","40721","40722","40723","40724","40725","40726","40727","40728","40729","40730","40731","40732","40733","40734","40735","40736","40737","40738","40739","40740","40741","40742","40743","40744","40745","40746","40747","40748","40749","40750","40751","40752","40753","40754","40755","40756","40757","40758","40759","40760","40761","40762","40763","40764","40765","40766","40767","40768","40769","40770","40771","40772","40773","40774","40775","40776","40777","40778","40779","40780","40781","40782","40783","40784","40785","40786","40787","40788","40789","40790","40791","40792","40793","40794","40795","40796","40797","40798","40799","40800","40801","40802","40803","40804","40805","40806","40807","40808","40809","40810","40811","40812","40813","40814","40815","40816","40817","40818","40819","40820","40821","40822","40823","40824","40825","40826","40827","40828","40829","40830","40831","40832","40833","40834","40835","40836","40837","40838","40839","40840","40841","40842","40843","40844","40845","40846","40847","40848","40849","40850","40851","40852","40853","40854","40855","40856","40857","40858","40859","40860","40861","40862","40863","40864","40865","40866","40867","40868","40869","40870","40871","40872","40873","40874","40875","40876","40877","40878","40879","40880","40881","40882","40883","40884","40885","40886","40887","40888","40889","40890","40891","40892","40893","40894","40895","40896","40897","40898","40899","40900","40901","40902","40903","40904","40905","40906","40907","40908","40909","40910","40911","40912","40913","40914","40915","40916","40917","40918","40919","40920","40921","40922","40923","40924","40925","40926","40927","40928","40929","40930","40931","40932","40933","40934","40935","40936","40937","40938","40939","40940","40941","40942","40943","40944","40945","40946","40947","40948","40949","40950","40951","40952","40953","40954","40955","40956","40957","40958","40959","40960","40961","40962","40963","40964","40965","40966","40967","40968","40969","40970","40971","40972","40973","40974","40975","40976","40977","40978","40979","40980","40981","40982","40983","40984","40985","40986","40987","40988","40989","40990","40991","40992","40993","40994","40995","40996","40997","40998","40999","41000","41001","41002","41003","41004","41005","41006","41007","41008","41009","41010","41011","41012","41013","41014","41015","41016","41017","41018","41019","41020","41021","41022","41023","41024","41025","41026","41027","41028","41029","41030","41031","41032","41033","41034","41035","41036","41037","41038","41039","41040","41041","41042","41043","41044","41045","41046","41047","41048","41049","41050","41051","41052","41053","41054","41055","41056","41057","41058","41059","41060","41061","41062","41063","41064","41065","41066","41067","41068","41069","41070","41071","41072","41073","41074","41075","41076","41077","41078","41079","41080","41081","41082","41083","41084","41085","41086","41087","41088","41089","41090","41091","41092","41093","41094","41095","41096","41097","41098","41099","41100","41101","41102","41103","41104","41105","41106","41107","41108","41109","41110","41111","41112","41113","41114","41115","41116","41117","41118","41119","41120","41121","41122","41123","41124","41125","41126","41127","41128","41129","41130","41131","41132","41133","41134","41135","41136","41137","41138","41139","41140","41141","41142","41143","41144","41145","41146","41147","41148","41149","41150","41151","41152","41153","41154","41155","41156","41157","41158","41159","41160","41161","41162","41163","41164","41165","41166","41167","41168","41169","41170","41171","41172","41173","41174","41175","41176","41177","41178","41179","41180","41181","41182","41183","41184","41185","41186","41187","41188","41189","41190","41191","41192","41193","41194","41195","41196","41197","41198","41199","41200","41201","41202","41203","41204","41205","41206","41207","41208","41209","41210","41211","41212","41213","41214","41215","41216","41217","41218","41219","41220","41221","41222","41223","41224","41225","41226","41227","41228","41229","41230","41231","41232","41233","41234","41235","41236","41237","41238","41239","41240","41241","41242","41243","41244","41245","41246","41247","41248","41249","41250","41251","41252","41253","41254","41255","41256","41257","41258","41259","41260","41261","41262","41263","41264","41265","41266","41267","41268","41269","41270","41271","41272","41273","41274","41275","41276","41277","41278","41279","41280","41281","41282","41283","41284","41285","41286","41287","41288","41289","41290","41291","41292","41293","41294","41295","41296","41297","41298","41299","41300","41301","41302","41303","41304","41305","41306","41307","41308","41309","41310","41311","41312","41313","41314","41315","41316","41317","41318","41319","41320","41321","41322","41323","41324","41325","41326","41327","41328","41329","41330","41331","41332","41333","41334","41335","41336","41337","41338","41339","41340","41341","41342","41343","41344","41345","41346","41347","41348","41349","41350","41351","41352","41353","41354","41355","41356","41357","41358","41359","41360","41361","41362","41363","41364","41365","41366","41367","41368","41369","41370","41371","41372","41373","41374","41375","41376","41377","41378","41379","41380","41381","41382","41383","41384","41385","41386","41387","41388","41389","41390","41391","41392","41393","41394","41395","41396","41397","41398","41399","41400","41401","41402","41403","41404","41405","41406","41407","41408","41409","41410","41411","41412","41413","41414","41415","41416","41417","41418","41419","41420","41421","41422","41423","41424","41425","41426","41427","41428","41429","41430","41431","41432","41433","41434","41435","41436","41437","41438","41439","41440","41441","41442","41443","41444","41445","41446","41447","41448","41449","41450","41451","41452","41453","41454","41455","41456","41457","41458","41459","41460","41461","41462","41463","41464","41465","41466","41467","41468","41469","41470","41471","41472","41473","41474","41475","41476","41477","41478","41479","41480","41481","41482","41483","41484","41485","41486","41487","41488","41489","41490","41491","41492","41493","41494","41495","41496","41497","41498","41499","41500","41501","41502","41503","41504","41505","41506","41507","41508","41509","41510","41511","41512","41513","41514","41515","41516","41517","41518","41519","41520","41521","41522","41523","41524","41525","41526","41527","41528","41529","41530","41531","41532","41533","41534","41535","41536","41537","41538","41539","41540","41541","41542","41543","41544","41545","41546","41547","41548","41549","41550","41551","41552","41553","41554","41555","41556","41557","41558","41559","41560","41561","41562","41563","41564","41565","41566","41567","41568","41569","41570","41571","41572","41573","41574","41575","41576","41577","41578","41579","41580","41581","41582","41583","41584","41585","41586","41587","41588","41589","41590","41591","41592","41593","41594","41595","41596","41597","41598","41599","41600","41601","41602","41603","41604","41605","41606","41607","41608","41609","41610","41611","41612","41613","41614","41615","41616","41617","41618","41619","41620","41621","41622","41623","41624","41625","41626","41627","41628","41629","41630","41631","41632","41633","41634","41635","41636","41637","41638","41639","41640","41641","41642","41643","41644","41645","41646","41647","41648","41649","41650","41651","41652","41653","41654","41655","41656","41657","41658","41659","41660","41661","41662","41663","41664","41665","41666","41667","41668","41669","41670","41671","41672","41673","41674","41675","41676","41677","41678","41679","41680","41681","41682","41683","41684","41685","41686","41687","41688","41689","41690","41691","41692","41693","41694","41695","41696","41697","41698","41699","41700","41701","41702","41703","41704","41705","41706","41707","41708","41709","41710","41711","41712","41713","41714","41715","41716","41717","41718","41719","41720","41721","41722","41723","41724","41725","41726","41727","41728","41729","41730","41731","41732","41733","41734","41735","41736","41737","41738","41739","41740","41741","41742","41743","41744","41745","41746","41747","41748","41749","41750","41751","41752","41753","41754","41755","41756","41757","41758","41759","41760","41761","41762","41763","41764","41765","41766","41767","41768","41769","41770","41771","41772","41773","41774","41775","41776","41777","41778","41779","41780","41781","41782","41783","41784","41785","41786","41787","41788","41789","41790","41791","41792","41793","41794","41795","41796","41797","41798","41799","41800","41801","41802","41803","41804","41805","41806","41807","41808","41809","41810","41811","41812","41813","41814","41815","41816","41817","41818","41819","41820","41821","41822","41823","41824","41825","41826","41827","41828","41829","41830","41831","41832","41833","41834","41835","41836","41837","41838","41839","41840","41841","41842","41843","41844","41845","41846","41847","41848","41849","41850","41851","41852","41853","41854","41855","41856","41857","41858","41859","41860","41861","41862","41863","41864","41865","41866","41867","41868","41869","41870","41871","41872","41873","41874","41875","41876","41877","41878","41879","41880","41881","41882","41883","41884","41885","41886","41887","41888","41889","41890","41891","41892","41893","41894","41895","41896","41897","41898","41899","41900","41901","41902","41903","41904","41905","41906","41907","41908","41909","41910","41911","41912","41913","41914","41915","41916","41917","41918","41919","41920","41921","41922","41923","41924","41925","41926","41927","41928","41929","41930","41931","41932","41933","41934","41935","41936","41937","41938","41939","41940","41941","41942","41943","41944","41945","41946","41947","41948","41949","41950","41951","41952","41953","41954","41955","41956","41957","41958","41959","41960","41961","41962","41963","41964","41965","41966","41967","41968","41969","41970","41971","41972","41973","41974","41975","41976","41977","41978","41979","41980","41981","41982","41983","41984","41985","41986","41987","41988","41989","41990","41991","41992","41993","41994","41995","41996","41997","41998","41999","42000","42001","42002","42003","42004","42005","42006","42007","42008","42009","42010","42011","42012","42013","42014","42015","42016","42017","42018","42019","42020","42021","42022","42023","42024","42025","42026","42027","42028","42029","42030","42031","42032","42033","42034","42035","42036","42037","42038","42039","42040","42041","42042","42043","42044","42045","42046","42047","42048","42049","42050","42051","42052","42053","42054","42055","42056","42057","42058","42059","42060","42061","42062","42063","42064","42065","42066","42067","42068","42069","42070","42071","42072","42073","42074","42075","42076","42077","42078","42079","42080","42081","42082","42083","42084","42085","42086","42087","42088","42089","42090","42091","42092","42093","42094","42095","42096","42097","42098","42099","42100","42101","42102","42103","42104","42105","42106","42107","42108","42109","42110","42111","42112","42113","42114","42115","42116","42117","42118","42119","42120","42121","42122","42123","42124","42125","42126","42127","42128","42129","42130","42131","42132","42133","42134","42135","42136","42137","42138","42139","42140","42141","42142","42143","42144","42145","42146","42147","42148","42149","42150","42151","42152","42153","42154","42155","42156","42157","42158","42159","42160","42161","42162","42163","42164","42165","42166","42167","42168","42169","42170","42171","42172","42173","42174","42175","42176","42177","42178","42179","42180","42181","42182","42183","42184","42185","42186","42187","42188","42189","42190","42191","42192","42193","42194","42195","42196","42197","42198","42199","42200","42201","42202","42203","42204","42205","42206","42207","42208","42209","42210","42211","42212","42213","42214","42215","42216","42217","42218","42219","42220","42221","42222","42223","42224","42225","42226","42227","42228","42229","42230","42231","42232","42233","42234","42235","42236","42237","42238","42239","42240","42241","42242","42243","42244","42245","42246","42247","42248","42249","42250","42251","42252","42253","42254","42255","42256","42257","42258","42259","42260","42261","42262","42263","42264","42265","42266","42267","42268","42269","42270","42271","42272","42273","42274","42275","42276","42277","42278","42279","42280","42281","42282","42283","42284","42285","42286","42287","42288","42289","42290","42291","42292","42293","42294","42295","42296","42297","42298","42299","42300","42301","42302","42303","42304","42305","42306","42307","42308","42309","42310","42311","42312","42313","42314","42315","42316","42317","42318","42319","42320","42321","42322","42323","42324","42325","42326","42327","42328","42329","42330","42331","42332","42333","42334","42335","42336","42337","42338","42339","42340","42341","42342","42343","42344","42345","42346","42347","42348","42349","42350","42351","42352","42353","42354","42355","42356","42357","42358","42359","42360","42361","42362","42363","42364","42365","42366","42367","42368","42369","42370","42371","42372","42373","42374","42375","42376","42377","42378","42379","42380","42381","42382","42383","42384","42385","42386","42387","42388","42389","42390","42391","42392","42393","42394","42395","42396","42397","42398","42399","42400","42401","42402","42403","42404","42405","42406","42407","42408","42409","42410","42411","42412","42413","42414","42415","42416","42417","42418","42419","42420","42421","42422","42423","42424","42425","42426","42427","42428","42429","42430","42431","42432","42433","42434","42435","42436","42437","42438","42439","42440","42441","42442","42443","42444","42445","42446","42447","42448","42449","42450","42451","42452","42453","42454","42455","42456","42457","42458","42459","42460","42461","42462","42463","42464","42465","42466","42467","42468","42469","42470","42471","42472","42473","42474","42475","42476","42477","42478","42479","42480","42481","42482","42483","42484","42485","42486","42487","42488","42489","42490","42491","42492","42493","42494","42495","42496","42497","42498","42499","42500","42501","42502","42503","42504","42505","42506","42507","42508","42509","42510","42511","42512","42513","42514","42515","42516","42517","42518","42519","42520","42521","42522","42523","42524","42525","42526","42527","42528","42529","42530","42531","42532","42533","42534","42535","42536","42537","42538","42539","42540","42541","42542","42543","42544","42545","42546","42547","42548","42549","42550","42551","42552","42553","42554","42555","42556","42557","42558","42559","42560","42561","42562","42563","42564","42565","42566","42567","42568","42569","42570","42571","42572","42573","42574","42575","42576","42577","42578","42579","42580","42581","42582","42583","42584","42585","42586","42587","42588","42589","42590","42591","42592","42593","42594","42595","42596","42597","42598","42599","42600","42601","42602","42603","42604","42605","42606","42607","42608","42609","42610","42611","42612","42613","42614","42615","42616","42617","42618","42619","42620","42621","42622","42623","42624","42625","42626","42627","42628","42629","42630","42631","42632","42633","42634","42635","42636","42637","42638","42639","42640","42641","42642","42643","42644","42645","42646","42647","42648","42649","42650","42651","42652","42653","42654","42655","42656","42657","42658","42659","42660","42661","42662","42663","42664","42665","42666","42667","42668","42669","42670","42671","42672","42673","42674","42675","42676","42677","42678","42679","42680","42681","42682","42683","42684","42685","42686","42687","42688","42689","42690","42691","42692","42693","42694","42695","42696","42697","42698","42699","42700","42701","42702","42703","42704","42705","42706","42707","42708","42709","42710","42711","42712","42713","42714","42715","42716","42717","42718","42719","42720","42721","42722","42723","42724","42725","42726","42727","42728","42729","42730","42731","42732","42733","42734","42735","42736","42737","42738","42739","42740","42741","42742","42743","42744","42745","42746","42747","42748","42749","42750","42751","42752","42753","42754","42755","42756","42757","42758","42759","42760","42761","42762","42763","42764","42765","42766","42767","42768","42769","42770","42771","42772","42773","42774","42775","42776","42777","42778","42779","42780","42781","42782","42783","42784","42785","42786","42787","42788","42789","42790","42791","42792","42793","42794","42795","42796","42797","42798","42799","42800","42801","42802","42803","42804","42805","42806","42807","42808","42809","42810","42811","42812","42813","42814","42815","42816","42817","42818","42819","42820","42821","42822","42823","42824","42825","42826","42827","42828","42829","42830","42831","42832","42833","42834","42835","42836","42837","42838","42839","42840","42841","42842","42843","42844","42845","42846","42847","42848","42849","42850","42851","42852","42853","42854","42855","42856","42857","42858","42859","42860","42861","42862","42863","42864","42865","42866","42867","42868","42869","42870","42871","42872","42873","42874","42875","42876","42877","42878","42879","42880","42881","42882","42883","42884","42885","42886","42887","42888","42889","42890","42891","42892","42893","42894","42895","42896","42897","42898","42899","42900","42901","42902","42903","42904","42905","42906","42907","42908","42909","42910","42911","42912","42913","42914","42915","42916","42917","42918","42919","42920","42921","42922","42923","42924","42925","42926","42927","42928","42929","42930","42931","42932","42933","42934","42935","42936","42937","42938","42939","42940","42941","42942","42943","42944","42945","42946","42947","42948","42949","42950","42951","42952","42953","42954","42955","42956","42957","42958","42959","42960","42961","42962","42963","42964","42965","42966","42967","42968","42969","42970","42971","42972","42973","42974","42975","42976","42977","42978","42979","42980","42981","42982","42983","42984","42985","42986","42987","42988","42989","42990","42991","42992","42993","42994","42995","42996","42997","42998","42999","43000","43001","43002","43003","43004","43005","43006","43007","43008","43009","43010","43011","43012","43013","43014","43015","43016","43017","43018","43019","43020","43021","43022","43023","43024","43025","43026","43027","43028","43029","43030","43031","43032","43033","43034","43035","43036","43037","43038","43039","43040","43041","43042","43043","43044","43045","43046","43047","43048","43049","43050","43051","43052","43053","43054","43055","43056","43057","43058","43059","43060","43061","43062","43063","43064","43065","43066","43067","43068","43069","43070","43071","43072","43073","43074","43075","43076","43077","43078","43079","43080","43081","43082","43083","43084","43085","43086","43087","43088","43089","43090","43091","43092","43093","43094","43095","43096","43097","43098","43099","43100","43101","43102","43103","43104","43105","43106","43107","43108","43109","43110","43111","43112","43113","43114","43115","43116","43117","43118","43119","43120","43121","43122","43123","43124","43125","43126","43127","43128","43129","43130","43131","43132","43133","43134","43135","43136","43137","43138","43139","43140","43141","43142","43143","43144","43145","43146","43147","43148","43149","43150","43151","43152","43153","43154","43155","43156","43157","43158","43159","43160","43161","43162","43163","43164","43165","43166","43167","43168","43169","43170","43171","43172","43173","43174","43175","43176","43177","43178","43179","43180","43181","43182","43183","43184","43185","43186","43187","43188","43189","43190","43191","43192","43193","43194","43195","43196","43197","43198","43199","43200","43201","43202","43203","43204","43205","43206","43207","43208","43209","43210","43211","43212","43213","43214","43215","43216","43217","43218","43219","43220","43221","43222","43223","43224","43225","43226","43227","43228","43229","43230","43231","43232","43233","43234","43235","43236","43237","43238","43239","43240","43241","43242","43243","43244","43245","43246","43247","43248","43249","43250","43251","43252","43253","43254","43255","43256","43257","43258","43259","43260","43261","43262","43263","43264","43265","43266","43267","43268","43269","43270","43271","43272","43273","43274","43275","43276","43277","43278","43279","43280","43281","43282","43283","43284","43285","43286","43287","43288","43289","43290","43291","43292","43293","43294","43295","43296","43297","43298","43299","43300","43301","43302","43303","43304","43305","43306","43307","43308","43309","43310","43311","43312","43313","43314","43315","43316","43317","43318","43319","43320","43321","43322","43323","43324","43325","43326","43327","43328","43329","43330","43331","43332","43333","43334","43335","43336","43337","43338","43339","43340","43341","43342","43343","43344","43345","43346","43347","43348","43349","43350","43351","43352","43353","43354","43355","43356","43357","43358","43359","43360","43361","43362","43363","43364","43365","43366","43367","43368","43369","43370","43371","43372","43373","43374","43375","43376","43377","43378","43379","43380","43381","43382","43383","43384","43385","43386","43387","43388","43389","43390","43391","43392","43393","43394","43395","43396","43397","43398","43399","43400","43401","43402","43403","43404","43405","43406","43407","43408","43409","43410","43411","43412","43413","43414","43415","43416","43417","43418","43419","43420","43421","43422","43423","43424","43425","43426","43427","43428","43429","43430","43431","43432","43433","43434","43435","43436","43437","43438","43439","43440","43441","43442","43443","43444","43445","43446","43447","43448","43449","43450","43451","43452","43453","43454","43455","43456","43457","43458","43459","43460","43461","43462","43463","43464","43465","43466","43467","43468","43469","43470","43471","43472","43473","43474","43475","43476","43477","43478","43479","43480","43481","43482","43483","43484","43485","43486","43487","43488","43489","43490","43491","43492","43493","43494","43495","43496","43497","43498","43499","43500","43501","43502","43503","43504","43505","43506","43507","43508","43509","43510","43511","43512","43513","43514","43515","43516","43517","43518","43519","43520","43521","43522","43523","43524","43525","43526","43527","43528","43529","43530","43531","43532","43533","43534","43535","43536","43537","43538","43539","43540","43541","43542","43543","43544","43545","43546","43547","43548","43549","43550","43551","43552","43553","43554","43555","43556","43557","43558","43559","43560","43561","43562","43563","43564","43565","43566","43567","43568","43569","43570","43571","43572","43573","43574","43575","43576","43577","43578","43579","43580","43581","43582","43583","43584","43585","43586","43587","43588","43589","43590","43591","43592","43593","43594","43595","43596","43597","43598","43599","43600","43601","43602","43603","43604","43605","43606","43607","43608","43609","43610","43611","43612","43613","43614","43615","43616","43617","43618","43619","43620","43621","43622","43623","43624","43625","43626","43627","43628","43629","43630","43631","43632","43633","43634","43635","43636","43637","43638","43639","43640","43641","43642","43643","43644","43645","43646","43647","43648","43649","43650","43651","43652","43653","43654","43655","43656","43657","43658","43659","43660","43661","43662","43663","43664","43665","43666","43667","43668","43669","43670","43671","43672","43673","43674","43675","43676","43677","43678","43679","43680","43681","43682","43683","43684","43685","43686","43687","43688","43689","43690","43691","43692","43693","43694","43695","43696","43697","43698","43699","43700","43701","43702","43703","43704","43705","43706","43707","43708","43709","43710","43711","43712","43713","43714","43715","43716","43717","43718","43719","43720","43721","43722","43723","43724","43725","43726","43727","43728","43729","43730","43731","43732","43733","43734","43735","43736","43737","43738","43739","43740","43741","43742","43743","43744","43745","43746","43747","43748","43749","43750","43751","43752","43753","43754","43755","43756","43757","43758","43759","43760","43761","43762","43763","43764","43765","43766","43767","43768","43769","43770","43771","43772","43773","43774","43775","43776","43777","43778","43779","43780","43781","43782","43783","43784","43785","43786","43787","43788","43789","43790","43791","43792","43793","43794","43795","43796","43797","43798","43799","43800","43801","43802","43803","43804","43805","43806","43807","43808","43809","43810","43811","43812","43813","43814","43815","43816","43817","43818","43819","43820","43821","43822","43823","43824","43825","43826","43827","43828","43829","43830","43831","43832","43833","43834","43835","43836","43837","43838","43839","43840","43841","43842","43843","43844","43845","43846","43847","43848","43849","43850","43851","43852","43853","43854","43855","43856","43857","43858","43859","43860","43861","43862","43863","43864","43865","43866","43867","43868","43869","43870","43871","43872","43873","43874","43875","43876","43877","43878","43879","43880","43881","43882","43883","43884","43885","43886","43887","43888","43889","43890","43891","43892","43893","43894","43895","43896","43897","43898","43899","43900","43901","43902","43903","43904","43905","43906","43907","43908","43909","43910","43911","43912","43913","43914","43915","43916","43917","43918","43919","43920","43921","43922","43923","43924","43925","43926","43927","43928","43929","43930","43931","43932","43933","43934","43935","43936","43937","43938","43939","43940","43941","43942","43943","43944","43945","43946","43947","43948","43949","43950","43951","43952","43953","43954","43955","43956","43957","43958","43959","43960","43961","43962","43963","43964","43965","43966","43967","43968","43969","43970","43971","43972","43973","43974","43975","43976","43977","43978","43979","43980","43981","43982","43983","43984","43985","43986","43987","43988","43989","43990","43991","43992","43993","43994","43995","43996","43997","43998","43999","44000","44001","44002","44003","44004","44005","44006","44007","44008","44009","44010","44011","44012","44013","44014","44015","44016","44017","44018","44019","44020","44021","44022","44023","44024","44025","44026","44027","44028","44029","44030","44031","44032","44033","44034","44035","44036","44037","44038","44039","44040","44041","44042","44043","44044","44045","44046","44047","44048","44049","44050","44051","44052","44053","44054","44055","44056","44057","44058","44059","44060","44061","44062","44063","44064","44065","44066","44067","44068","44069","44070","44071","44072","44073","44074","44075","44076","44077","44078","44079","44080","44081","44082","44083","44084","44085","44086","44087","44088","44089","44090","44091","44092","44093","44094","44095","44096","44097","44098","44099","44100","44101","44102","44103","44104","44105","44106","44107","44108","44109","44110","44111","44112","44113","44114","44115","44116","44117","44118","44119","44120","44121","44122","44123","44124","44125","44126","44127","44128","44129","44130","44131","44132","44133","44134","44135","44136","44137","44138","44139","44140","44141","44142","44143","44144","44145","44146","44147","44148","44149","44150","44151","44152","44153","44154","44155","44156","44157","44158","44159","44160","44161","44162","44163","44164","44165","44166","44167","44168","44169","44170","44171","44172","44173","44174","44175","44176","44177","44178","44179","44180","44181","44182","44183","44184","44185","44186","44187","44188","44189","44190","44191","44192","44193","44194","44195","44196","44197","44198","44199","44200","44201","44202","44203","44204","44205","44206","44207","44208","44209","44210","44211","44212","44213","44214","44215","44216","44217","44218","44219","44220","44221","44222","44223","44224","44225","44226","44227","44228","44229","44230","44231","44232","44233","44234","44235","44236","44237","44238","44239","44240","44241","44242","44243","44244","44245","44246","44247","44248","44249","44250","44251","44252","44253","44254","44255","44256","44257","44258","44259","44260","44261","44262","44263","44264","44265","44266","44267","44268","44269","44270","44271","44272","44273","44274","44275","44276","44277","44278","44279","44280","44281","44282","44283","44284","44285","44286","44287","44288","44289","44290","44291","44292","44293","44294","44295","44296","44297","44298","44299","44300","44301","44302","44303","44304","44305","44306","44307","44308","44309","44310","44311","44312","44313","44314","44315","44316","44317","44318","44319","44320","44321","44322","44323","44324","44325","44326","44327","44328","44329","44330","44331","44332","44333","44334","44335","44336","44337","44338","44339","44340","44341","44342","44343","44344","44345","44346","44347","44348","44349","44350","44351","44352","44353","44354","44355","44356","44357","44358","44359","44360","44361","44362","44363","44364","44365","44366","44367","44368","44369","44370","44371","44372","44373","44374","44375","44376","44377","44378","44379","44380","44381","44382","44383","44384","44385","44386","44387","44388","44389","44390","44391","44392","44393","44394","44395","44396","44397","44398","44399","44400","44401","44402","44403","44404","44405","44406","44407","44408","44409","44410","44411","44412","44413","44414","44415","44416","44417","44418","44419","44420","44421","44422","44423","44424","44425","44426","44427","44428","44429","44430","44431","44432","44433","44434","44435","44436","44437","44438","44439","44440","44441","44442","44443","44444","44445","44446","44447","44448","44449","44450","44451","44452","44453","44454","44455","44456","44457","44458","44459","44460","44461","44462","44463","44464","44465","44466","44467","44468","44469","44470","44471","44472","44473","44474","44475","44476","44477","44478","44479","44480","44481","44482","44483","44484","44485","44486","44487","44488","44489","44490","44491","44492","44493","44494","44495","44496","44497","44498","44499","44500","44501","44502","44503","44504","44505","44506","44507","44508","44509","44510","44511","44512","44513","44514","44515","44516","44517","44518","44519","44520","44521","44522","44523","44524","44525","44526","44527","44528","44529","44530","44531","44532","44533","44534","44535","44536","44537","44538","44539","44540","44541","44542","44543","44544","44545","44546","44547","44548","44549","44550","44551","44552","44553","44554","44555","44556","44557","44558","44559","44560","44561","44562","44563","44564","44565","44566","44567","44568","44569","44570","44571","44572","44573","44574","44575","44576","44577","44578","44579","44580","44581","44582","44583","44584","44585","44586","44587","44588","44589","44590","44591","44592","44593","44594","44595","44596","44597","44598","44599","44600","44601","44602","44603","44604","44605","44606","44607","44608","44609","44610","44611","44612","44613","44614","44615","44616","44617","44618","44619","44620","44621","44622","44623","44624","44625","44626","44627","44628","44629","44630","44631","44632","44633","44634","44635","44636","44637","44638","44639","44640","44641","44642","44643","44644","44645","44646","44647","44648","44649","44650","44651","44652","44653","44654","44655","44656","44657","44658","44659","44660","44661","44662","44663","44664","44665","44666","44667","44668","44669","44670","44671","44672","44673","44674","44675","44676","44677","44678","44679","44680","44681","44682","44683","44684","44685","44686","44687","44688","44689","44690","44691","44692","44693","44694","44695","44696","44697","44698","44699","44700","44701","44702","44703","44704","44705","44706","44707","44708","44709","44710","44711","44712","44713","44714","44715","44716","44717","44718","44719","44720","44721","44722","44723","44724","44725","44726","44727","44728","44729","44730","44731","44732","44733","44734","44735","44736","44737","44738","44739","44740","44741","44742","44743","44744","44745","44746","44747","44748","44749","44750","44751","44752","44753","44754","44755","44756","44757","44758","44759","44760","44761","44762","44763","44764","44765","44766","44767","44768","44769","44770","44771","44772","44773","44774","44775","44776","44777","44778","44779","44780","44781","44782","44783","44784","44785","44786","44787","44788","44789","44790","44791","44792","44793","44794","44795","44796","44797","44798","44799","44800","44801","44802","44803","44804","44805","44806","44807","44808","44809","44810","44811","44812","44813","44814","44815","44816","44817","44818","44819","44820","44821","44822","44823","44824","44825","44826","44827","44828","44829","44830","44831","44832","44833","44834","44835","44836","44837","44838","44839","44840","44841","44842","44843","44844","44845","44846","44847","44848","44849","44850","44851","44852","44853","44854","44855","44856","44857","44858","44859","44860","44861","44862","44863","44864","44865","44866","44867","44868","44869","44870","44871","44872","44873","44874","44875","44876","44877","44878","44879","44880","44881","44882","44883","44884","44885","44886","44887","44888","44889","44890","44891","44892","44893","44894","44895","44896","44897","44898","44899","44900","44901","44902","44903","44904","44905","44906","44907","44908","44909","44910","44911","44912","44913","44914","44915","44916","44917","44918","44919","44920","44921","44922","44923","44924","44925","44926","44927","44928","44929","44930","44931","44932","44933","44934","44935","44936","44937","44938","44939","44940","44941","44942","44943","44944","44945","44946","44947","44948","44949","44950","44951","44952","44953","44954","44955","44956","44957","44958","44959","44960","44961","44962","44963","44964","44965","44966","44967","44968","44969","44970","44971","44972","44973","44974","44975","44976","44977","44978","44979","44980","44981","44982","44983","44984","44985","44986","44987","44988","44989","44990","44991","44992","44993","44994","44995","44996","44997","44998","44999","45000","45001","45002","45003","45004","45005","45006","45007","45008","45009","45010","45011","45012","45013","45014","45015","45016","45017","45018","45019","45020","45021","45022","45023","45024","45025","45026","45027","45028","45029","45030","45031","45032","45033","45034","45035","45036","45037","45038","45039","45040","45041","45042","45043","45044","45045","45046","45047","45048","45049","45050","45051","45052","45053","45054","45055","45056","45057","45058","45059","45060","45061","45062","45063","45064","45065","45066","45067","45068","45069","45070","45071","45072","45073","45074","45075","45076","45077","45078","45079","45080","45081","45082","45083","45084","45085","45086","45087","45088","45089","45090","45091","45092","45093","45094","45095","45096","45097","45098","45099","45100","45101","45102","45103","45104","45105","45106","45107","45108","45109","45110","45111","45112","45113","45114","45115","45116","45117","45118","45119","45120","45121","45122","45123","45124","45125","45126","45127","45128","45129","45130","45131","45132","45133","45134","45135","45136","45137","45138","45139","45140","45141","45142","45143","45144","45145","45146","45147","45148","45149","45150","45151","45152","45153","45154","45155","45156","45157","45158","45159","45160","45161","45162","45163","45164","45165","45166","45167","45168","45169","45170","45171","45172","45173","45174","45175","45176","45177","45178","45179","45180","45181","45182","45183","45184","45185","45186","45187","45188","45189","45190","45191","45192","45193","45194","45195","45196","45197","45198","45199","45200","45201","45202","45203","45204","45205","45206","45207","45208","45209","45210","45211","45212","45213","45214","45215","45216","45217","45218","45219","45220","45221","45222","45223","45224","45225","45226","45227","45228","45229","45230","45231","45232","45233","45234","45235","45236","45237","45238","45239","45240","45241","45242","45243","45244","45245","45246","45247","45248","45249","45250","45251","45252","45253","45254","45255","45256","45257","45258","45259","45260","45261","45262","45263","45264","45265","45266","45267","45268","45269","45270","45271","45272","45273","45274","45275","45276","45277","45278","45279","45280","45281","45282","45283","45284","45285","45286","45287","45288","45289","45290","45291","45292","45293","45294","45295","45296","45297","45298","45299","45300","45301","45302","45303","45304","45305","45306","45307","45308","45309","45310","45311","45312","45313","45314","45315","45316","45317","45318","45319","45320","45321","45322","45323","45324","45325","45326","45327","45328","45329","45330","45331","45332","45333","45334","45335","45336","45337","45338","45339","45340","45341","45342","45343","45344","45345","45346","45347","45348","45349","45350","45351","45352","45353","45354","45355","45356","45357","45358","45359","45360","45361","45362","45363","45364","45365","45366","45367","45368","45369","45370","45371","45372","45373","45374","45375","45376","45377","45378","45379","45380","45381","45382","45383","45384","45385","45386","45387","45388","45389","45390","45391","45392","45393","45394","45395","45396","45397","45398","45399","45400","45401","45402","45403","45404","45405","45406","45407","45408","45409","45410","45411","45412","45413","45414","45415","45416","45417","45418","45419","45420","45421","45422","45423","45424","45425","45426","45427","45428","45429","45430","45431","45432","45433","45434","45435","45436","45437","45438","45439","45440","45441","45442","45443","45444","45445","45446","45447","45448","45449","45450","45451","45452","45453","45454","45455","45456","45457","45458","45459","45460","45461","45462","45463","45464","45465","45466","45467","45468","45469","45470","45471","45472","45473","45474","45475","45476","45477","45478","45479","45480","45481","45482","45483","45484","45485","45486","45487","45488","45489","45490","45491","45492","45493","45494","45495","45496","45497","45498","45499","45500","45501","45502","45503","45504","45505","45506","45507","45508","45509","45510","45511","45512","45513","45514","45515","45516","45517","45518","45519","45520","45521","45522","45523","45524","45525","45526","45527","45528","45529","45530","45531","45532","45533","45534","45535","45536","45537","45538","45539","45540","45541","45542","45543","45544","45545","45546","45547","45548","45549","45550","45551","45552","45553","45554","45555","45556","45557","45558","45559","45560","45561","45562","45563","45564","45565","45566","45567","45568","45569","45570","45571","45572","45573","45574","45575","45576","45577","45578","45579","45580","45581","45582","45583","45584","45585","45586","45587","45588","45589","45590","45591","45592","45593","45594","45595","45596","45597","45598","45599","45600","45601","45602","45603","45604","45605","45606","45607","45608","45609","45610","45611","45612","45613","45614","45615","45616","45617","45618","45619","45620","45621","45622","45623","45624","45625","45626","45627","45628","45629","45630","45631","45632","45633","45634","45635","45636","45637","45638","45639","45640","45641","45642","45643","45644","45645","45646","45647","45648","45649","45650","45651","45652","45653","45654","45655","45656","45657","45658","45659","45660","45661","45662","45663","45664","45665","45666","45667","45668","45669","45670","45671","45672","45673","45674","45675","45676","45677","45678","45679","45680","45681","45682","45683","45684","45685","45686","45687","45688","45689","45690","45691","45692","45693","45694","45695","45696","45697","45698","45699","45700","45701","45702","45703","45704","45705","45706","45707","45708","45709","45710","45711","45712","45713","45714","45715","45716","45717","45718","45719","45720","45721","45722","45723","45724","45725","45726","45727","45728","45729","45730","45731","45732","45733","45734","45735","45736","45737","45738","45739","45740","45741","45742","45743","45744","45745","45746","45747","45748","45749","45750","45751","45752","45753","45754","45755","45756","45757","45758","45759","45760","45761","45762","45763","45764","45765","45766","45767","45768","45769","45770","45771","45772","45773","45774","45775","45776","45777","45778","45779","45780","45781","45782","45783","45784","45785","45786","45787","45788","45789","45790","45791","45792","45793","45794","45795","45796","45797","45798","45799","45800","45801","45802","45803","45804","45805","45806","45807","45808","45809","45810","45811","45812","45813","45814","45815","45816","45817","45818","45819","45820","45821","45822","45823","45824","45825","45826","45827","45828","45829","45830","45831","45832","45833","45834","45835","45836","45837","45838","45839","45840","45841","45842","45843","45844","45845","45846","45847","45848","45849","45850","45851","45852","45853","45854","45855","45856","45857","45858","45859","45860","45861","45862","45863","45864","45865","45866","45867","45868","45869","45870","45871","45872","45873","45874","45875","45876","45877","45878","45879","45880","45881","45882","45883","45884","45885","45886","45887","45888","45889","45890","45891","45892","45893","45894","45895","45896","45897","45898","45899","45900","45901","45902","45903","45904","45905","45906","45907","45908","45909","45910","45911","45912","45913","45914","45915","45916","45917","45918","45919","45920","45921","45922","45923","45924","45925","45926","45927","45928","45929","45930","45931","45932","45933","45934","45935","45936","45937","45938","45939","45940","45941","45942","45943","45944","45945","45946","45947","45948","45949","45950","45951","45952","45953","45954","45955","45956","45957","45958","45959","45960","45961","45962","45963","45964","45965","45966","45967","45968","45969","45970","45971","45972","45973","45974","45975","45976","45977","45978","45979","45980","45981","45982","45983","45984","45985","45986","45987","45988","45989","45990","45991","45992","45993","45994","45995","45996","45997","45998","45999","46000","46001","46002","46003","46004","46005","46006","46007","46008","46009","46010","46011","46012","46013","46014","46015","46016","46017","46018","46019","46020","46021","46022","46023","46024","46025","46026","46027","46028","46029","46030","46031","46032","46033","46034","46035","46036","46037","46038","46039","46040","46041","46042","46043","46044","46045","46046","46047","46048","46049","46050","46051","46052","46053","46054","46055","46056","46057","46058","46059","46060","46061","46062","46063","46064","46065","46066","46067","46068","46069","46070","46071","46072","46073","46074","46075","46076","46077","46078","46079","46080","46081","46082","46083","46084","46085","46086","46087","46088","46089","46090","46091","46092","46093","46094","46095","46096","46097","46098","46099","46100","46101","46102","46103","46104","46105","46106","46107","46108","46109","46110","46111","46112","46113","46114","46115","46116","46117","46118","46119","46120","46121","46122","46123","46124","46125","46126","46127","46128","46129","46130","46131","46132","46133","46134","46135","46136","46137","46138","46139","46140","46141","46142","46143","46144","46145","46146","46147","46148","46149","46150","46151","46152","46153","46154","46155","46156","46157","46158","46159","46160","46161","46162","46163","46164","46165","46166","46167","46168","46169","46170","46171","46172","46173","46174","46175","46176","46177","46178","46179","46180","46181","46182","46183","46184","46185","46186","46187","46188","46189","46190","46191","46192","46193","46194","46195","46196","46197","46198","46199","46200","46201","46202","46203","46204","46205","46206","46207","46208","46209","46210","46211","46212","46213","46214","46215","46216","46217","46218","46219","46220","46221","46222","46223","46224","46225","46226","46227","46228","46229","46230","46231","46232","46233","46234","46235","46236","46237","46238","46239","46240","46241","46242","46243","46244","46245","46246","46247","46248","46249","46250","46251","46252","46253","46254","46255","46256","46257","46258","46259","46260","46261","46262","46263","46264","46265","46266","46267","46268","46269","46270","46271","46272","46273","46274","46275","46276","46277","46278","46279","46280","46281","46282","46283","46284","46285","46286","46287","46288","46289","46290","46291","46292","46293","46294","46295","46296","46297","46298","46299","46300","46301","46302","46303","46304","46305","46306","46307","46308","46309","46310","46311","46312","46313","46314","46315","46316","46317","46318","46319","46320","46321","46322","46323","46324","46325","46326","46327","46328","46329","46330","46331","46332","46333","46334","46335","46336","46337","46338","46339","46340","46341","46342","46343","46344","46345","46346","46347","46348","46349","46350","46351","46352","46353","46354","46355","46356","46357","46358","46359","46360","46361","46362","46363","46364","46365","46366","46367","46368","46369","46370","46371","46372","46373","46374","46375","46376","46377","46378","46379","46380","46381","46382","46383","46384","46385","46386","46387","46388","46389","46390","46391","46392","46393","46394","46395","46396","46397","46398","46399","46400","46401","46402","46403","46404","46405","46406","46407","46408","46409","46410","46411","46412","46413","46414","46415","46416","46417","46418","46419","46420","46421","46422","46423","46424","46425","46426","46427","46428","46429","46430","46431","46432","46433","46434","46435","46436","46437","46438","46439","46440","46441","46442","46443","46444","46445","46446","46447","46448","46449","46450","46451","46452","46453","46454","46455","46456","46457","46458","46459","46460","46461","46462","46463","46464","46465","46466","46467","46468","46469","46470","46471","46472","46473","46474","46475","46476","46477","46478","46479","46480","46481","46482","46483","46484","46485","46486","46487","46488","46489","46490","46491","46492","46493","46494","46495","46496","46497","46498","46499","46500","46501","46502","46503","46504","46505","46506","46507","46508","46509","46510","46511","46512","46513","46514","46515","46516","46517","46518","46519","46520","46521","46522","46523","46524","46525","46526","46527","46528","46529","46530","46531","46532","46533","46534","46535","46536","46537","46538","46539","46540","46541","46542","46543","46544","46545","46546","46547","46548","46549","46550","46551","46552","46553","46554","46555","46556","46557","46558","46559","46560","46561","46562","46563","46564","46565","46566","46567","46568","46569","46570","46571","46572","46573","46574","46575","46576","46577","46578","46579","46580","46581","46582","46583","46584","46585","46586","46587","46588","46589","46590","46591","46592","46593","46594","46595","46596","46597","46598","46599","46600","46601","46602","46603","46604","46605","46606","46607","46608","46609","46610","46611","46612","46613","46614","46615","46616","46617","46618","46619","46620","46621","46622","46623","46624","46625","46626","46627","46628","46629","46630","46631","46632","46633","46634","46635","46636","46637","46638","46639","46640","46641","46642","46643","46644","46645","46646","46647","46648","46649","46650","46651","46652","46653","46654","46655","46656","46657","46658","46659","46660","46661","46662","46663","46664","46665","46666","46667","46668","46669","46670","46671","46672","46673","46674","46675","46676","46677","46678","46679","46680","46681","46682","46683","46684","46685","46686","46687","46688","46689","46690","46691","46692","46693","46694","46695","46696","46697","46698","46699","46700","46701","46702","46703","46704","46705","46706","46707","46708","46709","46710","46711","46712","46713","46714","46715","46716","46717","46718","46719","46720","46721","46722","46723","46724","46725","46726","46727","46728","46729","46730","46731","46732","46733","46734","46735","46736","46737","46738","46739","46740","46741","46742","46743","46744","46745","46746","46747","46748","46749","46750","46751","46752","46753","46754","46755","46756","46757","46758","46759","46760","46761","46762","46763","46764","46765","46766","46767","46768","46769","46770","46771","46772","46773","46774","46775","46776","46777","46778","46779","46780","46781","46782","46783","46784","46785","46786","46787","46788","46789","46790","46791","46792","46793","46794","46795","46796","46797","46798","46799","46800","46801","46802","46803","46804","46805","46806","46807","46808","46809","46810","46811","46812","46813","46814","46815","46816","46817","46818","46819","46820","46821","46822","46823","46824","46825","46826","46827","46828","46829","46830","46831","46832","46833","46834","46835","46836","46837","46838","46839","46840","46841","46842","46843","46844","46845","46846","46847","46848","46849","46850","46851","46852","46853","46854","46855","46856","46857","46858","46859","46860","46861","46862","46863","46864","46865","46866","46867","46868","46869","46870","46871","46872","46873","46874","46875","46876","46877","46878","46879","46880","46881","46882","46883","46884","46885","46886","46887","46888","46889","46890","46891","46892","46893","46894","46895","46896","46897","46898","46899","46900","46901","46902","46903","46904","46905","46906","46907","46908","46909","46910","46911","46912","46913","46914","46915","46916","46917","46918","46919","46920","46921","46922","46923","46924","46925","46926","46927","46928","46929","46930","46931","46932","46933","46934","46935","46936","46937","46938","46939","46940","46941","46942","46943","46944","46945","46946","46947","46948","46949","46950","46951","46952","46953","46954","46955","46956","46957","46958","46959","46960","46961","46962","46963","46964","46965","46966","46967","46968","46969","46970","46971","46972","46973","46974","46975","46976","46977","46978","46979","46980","46981","46982","46983","46984","46985","46986","46987","46988","46989","46990","46991","46992","46993","46994","46995","46996","46997","46998","46999","47000","47001","47002","47003","47004","47005","47006","47007","47008","47009","47010","47011","47012","47013","47014","47015","47016","47017","47018","47019","47020","47021","47022","47023","47024","47025","47026","47027","47028","47029","47030","47031","47032","47033","47034","47035","47036","47037","47038","47039","47040","47041","47042","47043","47044","47045","47046","47047","47048","47049","47050","47051","47052","47053","47054","47055","47056","47057","47058","47059","47060","47061","47062","47063","47064","47065","47066","47067","47068","47069","47070","47071","47072","47073","47074","47075","47076","47077","47078","47079","47080","47081","47082","47083","47084","47085","47086","47087","47088","47089","47090","47091","47092","47093","47094","47095","47096","47097","47098","47099","47100","47101","47102","47103","47104","47105","47106","47107","47108","47109","47110","47111","47112","47113","47114","47115","47116","47117","47118","47119","47120","47121","47122","47123","47124","47125","47126","47127","47128","47129","47130","47131","47132","47133","47134","47135","47136","47137","47138","47139","47140","47141","47142","47143","47144","47145","47146","47147","47148","47149","47150","47151","47152","47153","47154","47155","47156","47157","47158","47159","47160","47161","47162","47163","47164","47165","47166","47167","47168","47169","47170","47171","47172","47173","47174","47175","47176","47177","47178","47179","47180","47181","47182","47183","47184","47185","47186","47187","47188","47189","47190","47191","47192","47193","47194","47195","47196","47197","47198","47199","47200","47201","47202","47203","47204","47205","47206","47207","47208","47209","47210","47211","47212","47213","47214","47215","47216","47217","47218","47219","47220","47221","47222","47223","47224","47225","47226","47227","47228","47229","47230","47231","47232","47233","47234","47235","47236","47237","47238","47239","47240","47241","47242","47243","47244","47245","47246","47247","47248","47249","47250","47251","47252","47253","47254","47255","47256","47257","47258","47259","47260","47261","47262","47263","47264","47265","47266","47267","47268","47269","47270","47271","47272","47273","47274","47275","47276","47277","47278","47279","47280","47281","47282","47283","47284","47285","47286","47287","47288","47289","47290","47291","47292","47293","47294","47295","47296","47297","47298","47299","47300","47301","47302","47303","47304","47305","47306","47307","47308","47309","47310","47311","47312","47313","47314","47315","47316","47317","47318","47319","47320","47321","47322","47323","47324","47325","47326","47327","47328","47329","47330","47331","47332","47333","47334","47335","47336","47337","47338","47339","47340","47341","47342","47343","47344","47345","47346","47347","47348","47349","47350","47351","47352","47353","47354","47355","47356","47357","47358","47359","47360","47361","47362","47363","47364","47365","47366","47367","47368","47369","47370","47371","47372","47373","47374","47375","47376","47377","47378","47379","47380","47381","47382","47383","47384","47385","47386","47387","47388","47389","47390","47391","47392","47393","47394","47395","47396","47397","47398","47399","47400","47401","47402","47403","47404","47405","47406","47407","47408","47409","47410","47411","47412","47413","47414","47415","47416","47417","47418","47419","47420","47421","47422","47423","47424","47425","47426","47427","47428","47429","47430","47431","47432","47433","47434","47435","47436","47437","47438","47439","47440","47441","47442","47443","47444","47445","47446","47447","47448","47449","47450","47451","47452","47453","47454","47455","47456","47457","47458","47459","47460","47461","47462","47463","47464","47465","47466","47467","47468","47469","47470","47471","47472","47473","47474","47475","47476","47477","47478","47479","47480","47481","47482","47483","47484","47485","47486","47487","47488","47489","47490","47491","47492","47493","47494","47495","47496","47497","47498","47499","47500","47501","47502","47503","47504","47505","47506","47507","47508","47509","47510","47511","47512","47513","47514","47515","47516","47517","47518","47519","47520","47521","47522","47523","47524","47525","47526","47527","47528","47529","47530","47531","47532","47533","47534","47535","47536","47537","47538","47539","47540","47541","47542","47543","47544","47545","47546","47547","47548","47549","47550","47551","47552","47553","47554","47555","47556","47557","47558","47559","47560","47561","47562","47563","47564","47565","47566","47567","47568","47569","47570","47571","47572","47573","47574","47575","47576","47577","47578","47579","47580","47581","47582","47583","47584","47585","47586","47587","47588","47589","47590","47591","47592","47593","47594","47595","47596","47597","47598","47599","47600","47601","47602","47603","47604","47605","47606","47607","47608","47609","47610","47611","47612","47613","47614","47615","47616","47617","47618","47619","47620","47621","47622","47623","47624","47625","47626","47627","47628","47629","47630","47631","47632","47633","47634","47635","47636","47637","47638","47639","47640","47641","47642","47643","47644","47645","47646","47647","47648","47649","47650","47651","47652","47653","47654","47655","47656","47657","47658","47659","47660","47661","47662","47663","47664","47665","47666","47667","47668","47669","47670","47671","47672","47673","47674","47675","47676","47677","47678","47679","47680","47681","47682","47683","47684","47685","47686","47687","47688","47689","47690","47691","47692","47693","47694","47695","47696","47697","47698","47699","47700","47701","47702","47703","47704","47705","47706","47707","47708","47709","47710","47711","47712","47713","47714","47715","47716","47717","47718","47719","47720","47721","47722","47723","47724","47725","47726","47727","47728","47729","47730","47731","47732","47733","47734","47735","47736","47737","47738","47739","47740","47741","47742","47743","47744","47745","47746","47747","47748","47749","47750","47751","47752","47753","47754","47755","47756","47757","47758","47759","47760","47761","47762","47763","47764","47765","47766","47767","47768","47769","47770","47771","47772","47773","47774","47775","47776","47777","47778","47779","47780","47781","47782","47783","47784","47785","47786","47787","47788","47789","47790","47791","47792","47793","47794","47795","47796","47797","47798","47799","47800","47801","47802","47803","47804","47805","47806","47807","47808","47809","47810","47811","47812","47813","47814","47815","47816","47817","47818","47819","47820","47821","47822","47823","47824","47825","47826","47827","47828","47829","47830","47831","47832","47833","47834","47835","47836","47837","47838","47839","47840","47841","47842","47843","47844","47845","47846","47847","47848","47849","47850","47851","47852","47853","47854","47855","47856","47857","47858","47859","47860","47861","47862","47863","47864","47865","47866","47867","47868","47869","47870","47871","47872","47873","47874","47875","47876","47877","47878","47879","47880","47881","47882","47883","47884","47885","47886","47887","47888","47889","47890","47891","47892","47893","47894","47895","47896","47897","47898","47899","47900","47901","47902","47903","47904","47905","47906","47907","47908","47909","47910","47911","47912","47913","47914","47915","47916","47917","47918","47919","47920","47921","47922","47923","47924","47925","47926","47927","47928","47929","47930","47931","47932","47933","47934","47935","47936","47937","47938","47939","47940","47941","47942","47943","47944","47945","47946","47947","47948","47949","47950","47951","47952","47953","47954","47955","47956","47957","47958","47959","47960","47961","47962","47963","47964","47965","47966","47967","47968","47969","47970","47971","47972","47973","47974","47975","47976","47977","47978","47979","47980","47981","47982","47983","47984","47985","47986","47987","47988","47989","47990","47991","47992","47993","47994","47995","47996","47997","47998","47999","48000","48001","48002","48003","48004","48005","48006","48007","48008","48009","48010","48011","48012","48013","48014","48015","48016","48017","48018","48019","48020","48021","48022","48023","48024","48025","48026","48027","48028","48029","48030","48031","48032","48033","48034","48035","48036","48037","48038","48039","48040","48041","48042","48043","48044","48045","48046","48047","48048","48049","48050","48051","48052","48053","48054","48055","48056","48057","48058","48059","48060","48061","48062","48063","48064","48065","48066","48067","48068","48069","48070","48071","48072","48073","48074","48075","48076","48077","48078","48079","48080","48081","48082","48083","48084","48085","48086","48087","48088","48089","48090","48091","48092","48093","48094","48095","48096","48097","48098","48099","48100","48101","48102","48103","48104","48105","48106","48107","48108","48109","48110","48111","48112","48113","48114","48115","48116","48117","48118","48119","48120","48121","48122","48123","48124","48125","48126","48127","48128","48129","48130","48131","48132","48133","48134","48135","48136","48137","48138","48139","48140","48141","48142","48143","48144","48145","48146","48147","48148","48149","48150","48151","48152","48153","48154","48155","48156","48157","48158","48159","48160","48161","48162","48163","48164","48165","48166","48167","48168","48169","48170","48171","48172","48173","48174","48175","48176","48177","48178","48179","48180","48181","48182","48183","48184","48185","48186","48187","48188","48189","48190","48191","48192","48193","48194","48195","48196","48197","48198","48199","48200","48201","48202","48203","48204","48205","48206","48207","48208","48209","48210","48211","48212","48213","48214","48215","48216","48217","48218","48219","48220","48221","48222","48223","48224","48225","48226","48227","48228","48229","48230","48231","48232","48233","48234","48235","48236","48237","48238","48239","48240","48241","48242","48243","48244","48245","48246","48247","48248","48249","48250","48251","48252","48253","48254","48255","48256","48257","48258","48259","48260","48261","48262","48263","48264","48265","48266","48267","48268","48269","48270","48271","48272","48273","48274","48275","48276","48277","48278","48279","48280","48281","48282","48283","48284","48285","48286","48287","48288","48289","48290","48291","48292","48293","48294","48295","48296","48297","48298","48299","48300","48301","48302","48303","48304","48305","48306","48307","48308","48309","48310","48311","48312","48313","48314","48315","48316","48317","48318","48319","48320","48321","48322","48323","48324","48325","48326","48327","48328","48329","48330","48331","48332","48333","48334","48335","48336","48337","48338","48339","48340","48341","48342","48343","48344","48345","48346","48347","48348","48349","48350","48351","48352","48353","48354","48355","48356","48357","48358","48359","48360","48361","48362","48363","48364","48365","48366","48367","48368","48369","48370","48371","48372","48373","48374","48375","48376","48377","48378","48379","48380","48381","48382","48383","48384","48385","48386","48387","48388","48389","48390","48391","48392","48393","48394","48395","48396","48397","48398","48399","48400","48401","48402","48403","48404","48405","48406","48407","48408","48409","48410","48411","48412","48413","48414","48415","48416","48417","48418","48419","48420","48421","48422","48423","48424","48425","48426","48427","48428","48429","48430","48431","48432","48433","48434","48435","48436","48437","48438","48439","48440","48441","48442","48443","48444","48445","48446","48447","48448","48449","48450","48451","48452","48453","48454","48455","48456","48457","48458","48459","48460","48461","48462","48463","48464","48465","48466","48467","48468","48469","48470","48471","48472","48473","48474","48475","48476","48477","48478","48479","48480","48481","48482","48483","48484","48485","48486","48487","48488","48489","48490","48491","48492","48493","48494","48495","48496","48497","48498","48499","48500","48501","48502","48503","48504","48505","48506","48507","48508","48509","48510","48511","48512","48513","48514","48515","48516","48517","48518","48519","48520","48521","48522","48523","48524","48525","48526","48527","48528","48529","48530","48531","48532","48533","48534","48535","48536","48537","48538","48539","48540","48541","48542","48543","48544","48545","48546","48547","48548","48549","48550","48551","48552","48553","48554","48555","48556","48557","48558","48559","48560","48561","48562","48563","48564","48565","48566","48567","48568","48569","48570","48571","48572","48573","48574","48575","48576","48577","48578","48579","48580","48581","48582","48583","48584","48585","48586","48587","48588","48589","48590","48591","48592","48593","48594","48595","48596","48597","48598","48599","48600","48601","48602","48603","48604","48605","48606","48607","48608","48609","48610","48611","48612","48613","48614","48615","48616","48617","48618","48619","48620","48621","48622","48623","48624","48625","48626","48627","48628","48629","48630","48631","48632","48633","48634","48635","48636","48637","48638","48639","48640","48641","48642","48643","48644","48645","48646","48647","48648","48649","48650","48651","48652","48653","48654","48655","48656","48657","48658","48659","48660","48661","48662","48663","48664","48665","48666","48667","48668","48669","48670","48671","48672","48673","48674","48675","48676","48677","48678","48679","48680","48681","48682","48683","48684","48685","48686","48687","48688","48689","48690","48691","48692","48693","48694","48695","48696","48697","48698","48699","48700","48701","48702","48703","48704","48705","48706","48707","48708","48709","48710","48711","48712","48713","48714","48715","48716","48717","48718","48719","48720","48721","48722","48723","48724","48725","48726","48727","48728","48729","48730","48731","48732","48733","48734","48735","48736","48737","48738","48739","48740","48741","48742","48743","48744","48745","48746","48747","48748","48749","48750","48751","48752","48753","48754","48755","48756","48757","48758","48759","48760","48761","48762","48763","48764","48765","48766","48767","48768","48769","48770","48771","48772","48773","48774","48775","48776","48777","48778","48779","48780","48781","48782","48783","48784","48785","48786","48787","48788","48789","48790","48791","48792","48793","48794","48795","48796","48797","48798","48799","48800","48801","48802","48803","48804","48805","48806","48807","48808","48809","48810","48811","48812","48813","48814","48815","48816","48817","48818","48819","48820","48821","48822","48823","48824","48825","48826","48827","48828","48829","48830","48831","48832","48833","48834","48835","48836","48837","48838","48839","48840","48841","48842","48843","48844","48845","48846","48847","48848","48849","48850","48851","48852","48853","48854","48855","48856","48857","48858","48859","48860","48861","48862","48863","48864","48865","48866","48867","48868","48869","48870","48871","48872","48873","48874","48875","48876","48877","48878","48879","48880","48881","48882","48883","48884","48885","48886","48887","48888","48889","48890","48891","48892","48893","48894","48895","48896","48897","48898","48899","48900","48901","48902","48903","48904","48905","48906","48907","48908","48909","48910","48911","48912","48913","48914","48915","48916","48917","48918","48919","48920","48921","48922","48923","48924","48925","48926","48927","48928","48929","48930","48931","48932","48933","48934","48935","48936","48937","48938","48939","48940","48941","48942","48943","48944","48945","48946","48947","48948","48949","48950","48951","48952","48953","48954","48955","48956","48957","48958","48959","48960","48961","48962","48963","48964","48965","48966","48967","48968","48969","48970","48971","48972","48973","48974","48975","48976","48977","48978","48979","48980","48981","48982","48983","48984","48985","48986","48987","48988","48989","48990","48991","48992","48993","48994","48995","48996","48997","48998","48999","49000","49001","49002","49003","49004","49005","49006","49007","49008","49009","49010","49011","49012","49013","49014","49015","49016","49017","49018","49019","49020","49021","49022","49023","49024","49025","49026","49027","49028","49029","49030","49031","49032","49033","49034","49035","49036","49037","49038","49039","49040","49041","49042","49043","49044","49045","49046","49047","49048","49049","49050","49051","49052","49053","49054","49055","49056","49057","49058","49059","49060","49061","49062","49063","49064","49065","49066","49067","49068","49069","49070","49071","49072","49073","49074","49075","49076","49077","49078","49079","49080","49081","49082","49083","49084","49085","49086","49087","49088","49089","49090","49091","49092","49093","49094","49095","49096","49097","49098","49099","49100","49101","49102","49103","49104","49105","49106","49107","49108","49109","49110","49111","49112","49113","49114","49115","49116","49117","49118","49119","49120","49121","49122","49123","49124","49125","49126","49127","49128","49129","49130","49131","49132","49133","49134","49135","49136","49137","49138","49139","49140","49141","49142","49143","49144","49145","49146","49147","49148","49149","49150","49151","49152","49153","49154","49155","49156","49157","49158","49159","49160","49161","49162","49163","49164","49165","49166","49167","49168","49169","49170","49171","49172","49173","49174","49175","49176","49177","49178","49179","49180","49181","49182","49183","49184","49185","49186","49187","49188","49189","49190","49191","49192","49193","49194","49195","49196","49197","49198","49199","49200","49201","49202","49203","49204","49205","49206","49207","49208","49209","49210","49211","49212","49213","49214","49215","49216","49217","49218","49219","49220","49221","49222","49223","49224","49225","49226","49227","49228","49229","49230","49231","49232","49233","49234","49235","49236","49237","49238","49239","49240","49241","49242","49243","49244","49245","49246","49247","49248","49249","49250","49251","49252","49253","49254","49255","49256","49257","49258","49259","49260","49261","49262","49263","49264","49265","49266","49267","49268","49269","49270","49271","49272","49273","49274","49275","49276","49277","49278","49279","49280","49281","49282","49283","49284","49285","49286","49287","49288","49289","49290","49291","49292","49293","49294","49295","49296","49297","49298","49299","49300","49301","49302","49303","49304","49305","49306","49307","49308","49309","49310","49311","49312","49313","49314","49315","49316","49317","49318","49319","49320","49321","49322","49323","49324","49325","49326","49327","49328","49329","49330","49331","49332","49333","49334","49335","49336","49337","49338","49339","49340","49341","49342","49343","49344","49345","49346","49347","49348","49349","49350","49351","49352","49353","49354","49355","49356","49357","49358","49359","49360","49361","49362","49363","49364","49365","49366","49367","49368","49369","49370","49371","49372","49373","49374","49375","49376","49377","49378","49379","49380","49381","49382","49383","49384","49385","49386","49387","49388","49389","49390","49391","49392","49393","49394","49395","49396","49397","49398","49399","49400","49401","49402","49403","49404","49405","49406","49407","49408","49409","49410","49411","49412","49413","49414","49415","49416","49417","49418","49419","49420","49421","49422","49423","49424","49425","49426","49427","49428","49429","49430","49431","49432","49433","49434","49435","49436","49437","49438","49439","49440","49441","49442","49443","49444","49445","49446","49447","49448","49449","49450","49451","49452","49453","49454","49455","49456","49457","49458","49459","49460","49461","49462","49463","49464","49465","49466","49467","49468","49469","49470","49471","49472","49473","49474","49475","49476","49477","49478","49479","49480","49481","49482","49483","49484","49485","49486","49487","49488","49489","49490","49491","49492","49493","49494","49495","49496","49497","49498","49499","49500","49501","49502","49503","49504","49505","49506","49507","49508","49509","49510","49511","49512","49513","49514","49515","49516","49517","49518","49519","49520","49521","49522","49523","49524","49525","49526","49527","49528","49529","49530","49531","49532","49533","49534","49535","49536","49537","49538","49539","49540","49541","49542","49543","49544","49545","49546","49547","49548","49549","49550","49551","49552","49553","49554","49555","49556","49557","49558","49559","49560","49561","49562","49563","49564","49565","49566","49567","49568","49569","49570","49571","49572","49573","49574","49575","49576","49577","49578","49579","49580","49581","49582","49583","49584","49585","49586","49587","49588","49589","49590","49591","49592","49593","49594","49595","49596","49597","49598","49599","49600","49601","49602","49603","49604","49605","49606","49607","49608","49609","49610","49611","49612","49613","49614","49615","49616","49617","49618","49619","49620","49621","49622","49623","49624","49625","49626","49627","49628","49629","49630","49631","49632","49633","49634","49635","49636","49637","49638","49639","49640","49641","49642","49643","49644","49645","49646","49647","49648","49649","49650","49651","49652","49653","49654","49655","49656","49657","49658","49659","49660","49661","49662","49663","49664","49665","49666","49667","49668","49669","49670","49671","49672","49673","49674","49675","49676","49677","49678","49679","49680","49681","49682","49683","49684","49685","49686","49687","49688","49689","49690","49691","49692","49693","49694","49695","49696","49697","49698","49699","49700","49701","49702","49703","49704","49705","49706","49707","49708","49709","49710","49711","49712","49713","49714","49715","49716","49717","49718","49719","49720","49721","49722","49723","49724","49725","49726","49727","49728","49729","49730","49731","49732","49733","49734","49735","49736","49737","49738","49739","49740","49741","49742","49743","49744","49745","49746","49747","49748","49749","49750","49751","49752","49753","49754","49755","49756","49757","49758","49759","49760","49761","49762","49763","49764","49765","49766","49767","49768","49769","49770","49771","49772","49773","49774","49775","49776","49777","49778","49779","49780","49781","49782","49783","49784","49785","49786","49787","49788","49789","49790","49791","49792","49793","49794","49795","49796","49797","49798","49799","49800","49801","49802","49803","49804","49805","49806","49807","49808","49809","49810","49811","49812","49813","49814","49815","49816","49817","49818","49819","49820","49821","49822","49823","49824","49825","49826","49827","49828","49829","49830","49831","49832","49833","49834","49835","49836","49837","49838","49839","49840","49841","49842","49843","49844","49845","49846","49847","49848","49849","49850","49851","49852","49853","49854","49855","49856","49857","49858","49859","49860","49861","49862","49863","49864","49865","49866","49867","49868","49869","49870","49871","49872","49873","49874","49875","49876","49877","49878","49879","49880","49881","49882","49883","49884","49885","49886","49887","49888","49889","49890","49891","49892","49893","49894","49895","49896","49897","49898","49899","49900","49901","49902","49903","49904","49905","49906","49907","49908","49909","49910","49911","49912","49913","49914","49915","49916","49917","49918","49919","49920","49921","49922","49923","49924","49925","49926","49927","49928","49929","49930","49931","49932","49933","49934","49935","49936","49937","49938","49939","49940","49941","49942","49943","49944","49945","49946","49947","49948","49949","49950","49951","49952","49953","49954","49955","49956","49957","49958","49959","49960","49961","49962","49963","49964","49965","49966","49967","49968","49969","49970","49971","49972","49973","49974","49975","49976","49977","49978","49979","49980","49981","49982","49983","49984","49985","49986","49987","49988","49989","49990","49991","49992","49993","49994","49995","49996","49997","49998","49999","50000","50001","50002","50003","50004","50005","50006","50007","50008","50009","50010","50011","50012","50013","50014","50015","50016","50017","50018","50019","50020","50021","50022","50023","50024","50025","50026","50027","50028","50029","50030","50031","50032","50033","50034","50035","50036","50037","50038","50039","50040","50041","50042","50043","50044","50045","50046","50047","50048","50049","50050","50051","50052","50053","50054","50055","50056","50057","50058","50059","50060","50061","50062","50063","50064","50065","50066","50067","50068","50069","50070","50071","50072","50073","50074","50075","50076","50077","50078","50079","50080","50081","50082","50083","50084","50085","50086","50087","50088","50089","50090","50091","50092","50093","50094","50095","50096","50097","50098","50099","50100","50101","50102","50103","50104","50105","50106","50107","50108","50109","50110","50111","50112","50113","50114","50115","50116","50117","50118","50119","50120","50121","50122","50123","50124","50125","50126","50127","50128","50129","50130","50131","50132","50133","50134","50135","50136","50137","50138","50139","50140","50141","50142","50143","50144","50145","50146","50147","50148","50149","50150","50151","50152","50153","50154","50155","50156","50157","50158","50159","50160","50161","50162","50163","50164","50165","50166","50167","50168","50169","50170","50171","50172","50173","50174","50175","50176","50177","50178","50179","50180","50181","50182","50183","50184","50185","50186","50187","50188","50189","50190","50191","50192","50193","50194","50195","50196","50197","50198","50199","50200","50201","50202","50203","50204","50205","50206","50207","50208","50209","50210","50211","50212","50213","50214","50215","50216","50217","50218","50219","50220","50221","50222","50223","50224","50225","50226","50227","50228","50229","50230","50231","50232","50233","50234","50235","50236","50237","50238","50239","50240","50241","50242","50243","50244","50245","50246","50247","50248","50249","50250","50251","50252","50253","50254","50255","50256","50257","50258","50259","50260","50261","50262","50263","50264","50265","50266","50267","50268","50269","50270","50271","50272","50273","50274","50275","50276","50277","50278","50279","50280","50281","50282","50283","50284","50285","50286","50287","50288","50289","50290","50291","50292","50293","50294","50295","50296","50297","50298","50299","50300","50301","50302","50303","50304","50305","50306","50307","50308","50309","50310","50311","50312","50313","50314","50315","50316","50317","50318","50319","50320","50321","50322","50323","50324","50325","50326","50327","50328","50329","50330","50331","50332","50333","50334","50335","50336","50337","50338","50339","50340","50341","50342","50343","50344","50345","50346","50347","50348","50349","50350","50351","50352","50353","50354","50355","50356","50357","50358","50359","50360","50361","50362","50363","50364","50365","50366","50367","50368","50369","50370","50371","50372","50373","50374","50375","50376","50377","50378","50379","50380","50381","50382","50383","50384","50385","50386","50387","50388","50389","50390","50391","50392","50393","50394","50395","50396","50397","50398","50399","50400","50401","50402","50403","50404","50405","50406","50407","50408","50409","50410","50411","50412","50413","50414","50415","50416","50417","50418","50419","50420","50421","50422","50423","50424","50425","50426","50427","50428","50429","50430","50431","50432","50433","50434","50435","50436","50437","50438","50439","50440","50441","50442","50443","50444","50445","50446","50447","50448","50449","50450","50451","50452","50453","50454","50455","50456","50457","50458","50459","50460","50461","50462","50463","50464","50465","50466","50467","50468","50469","50470","50471","50472","50473","50474","50475","50476","50477","50478","50479","50480","50481","50482","50483","50484","50485","50486","50487","50488","50489","50490","50491","50492","50493","50494","50495","50496","50497","50498","50499","50500","50501","50502","50503","50504","50505","50506","50507","50508","50509","50510","50511","50512","50513","50514","50515","50516","50517","50518","50519","50520","50521","50522","50523","50524","50525","50526","50527","50528","50529","50530","50531","50532","50533","50534","50535","50536","50537","50538","50539","50540","50541","50542","50543","50544","50545","50546","50547","50548","50549","50550","50551","50552","50553","50554","50555","50556","50557","50558","50559","50560","50561","50562","50563","50564","50565","50566","50567","50568","50569","50570","50571","50572","50573","50574","50575","50576","50577","50578","50579","50580","50581","50582","50583","50584","50585","50586","50587","50588","50589","50590","50591","50592","50593","50594","50595","50596","50597","50598","50599","50600","50601","50602","50603","50604","50605","50606","50607","50608","50609","50610","50611","50612","50613","50614","50615","50616","50617","50618","50619","50620","50621","50622","50623","50624","50625","50626","50627","50628","50629","50630","50631","50632","50633","50634","50635","50636","50637","50638","50639","50640","50641","50642","50643","50644","50645","50646","50647","50648","50649","50650","50651","50652","50653","50654","50655","50656","50657","50658","50659","50660","50661","50662","50663","50664","50665","50666","50667","50668","50669","50670","50671","50672","50673","50674","50675","50676","50677","50678","50679","50680","50681","50682","50683","50684","50685","50686","50687","50688","50689","50690","50691","50692","50693","50694","50695","50696","50697","50698","50699","50700","50701","50702","50703","50704","50705","50706","50707","50708","50709","50710","50711","50712","50713","50714","50715","50716","50717","50718","50719","50720","50721","50722","50723","50724","50725","50726","50727","50728","50729","50730","50731","50732","50733","50734","50735","50736","50737","50738","50739","50740","50741","50742","50743","50744","50745","50746","50747","50748","50749","50750","50751","50752","50753","50754","50755","50756","50757","50758","50759","50760","50761","50762","50763","50764","50765","50766","50767","50768","50769","50770","50771","50772","50773","50774","50775","50776","50777","50778","50779","50780","50781","50782","50783","50784","50785","50786","50787","50788","50789","50790","50791","50792","50793","50794","50795","50796","50797","50798","50799","50800","50801","50802","50803","50804","50805","50806","50807","50808","50809","50810","50811","50812","50813","50814","50815","50816","50817","50818","50819","50820","50821","50822","50823","50824","50825","50826","50827","50828","50829","50830","50831","50832","50833","50834","50835","50836","50837","50838","50839","50840","50841","50842","50843","50844","50845","50846","50847","50848","50849","50850","50851","50852","50853","50854","50855","50856","50857","50858","50859","50860","50861","50862","50863","50864","50865","50866","50867","50868","50869","50870","50871","50872","50873","50874","50875","50876","50877","50878","50879","50880","50881","50882","50883","50884","50885","50886","50887","50888","50889","50890","50891","50892","50893","50894","50895","50896","50897","50898","50899","50900","50901","50902","50903","50904","50905","50906","50907","50908","50909","50910","50911","50912","50913","50914","50915","50916","50917","50918","50919","50920","50921","50922","50923","50924","50925","50926","50927","50928","50929","50930","50931","50932","50933","50934","50935","50936","50937","50938","50939","50940","50941","50942","50943","50944","50945","50946","50947","50948","50949","50950","50951","50952","50953","50954","50955","50956","50957","50958","50959","50960","50961","50962","50963","50964","50965","50966","50967","50968","50969","50970","50971","50972","50973","50974","50975","50976","50977","50978","50979","50980","50981","50982","50983","50984","50985","50986","50987","50988","50989","50990","50991","50992","50993","50994","50995","50996","50997","50998","50999","51000","51001","51002","51003","51004","51005","51006","51007","51008","51009","51010","51011","51012","51013","51014","51015","51016","51017","51018","51019","51020","51021","51022","51023","51024","51025","51026","51027","51028","51029","51030","51031","51032","51033","51034","51035","51036","51037","51038","51039","51040","51041","51042","51043","51044","51045","51046","51047","51048","51049","51050","51051","51052","51053","51054","51055","51056","51057","51058","51059","51060","51061","51062","51063","51064","51065","51066","51067","51068","51069","51070","51071","51072","51073","51074","51075","51076","51077","51078","51079","51080","51081","51082","51083","51084","51085","51086","51087","51088","51089","51090","51091","51092","51093","51094","51095","51096","51097","51098","51099","51100","51101","51102","51103","51104","51105","51106","51107","51108","51109","51110","51111","51112","51113","51114","51115","51116","51117","51118","51119","51120","51121","51122","51123","51124","51125","51126","51127","51128","51129","51130","51131","51132","51133","51134","51135","51136","51137","51138","51139","51140","51141","51142","51143","51144","51145","51146","51147","51148","51149","51150","51151","51152","51153","51154","51155","51156","51157","51158","51159","51160","51161","51162","51163","51164","51165","51166","51167","51168","51169","51170","51171","51172","51173","51174","51175","51176","51177","51178","51179","51180","51181","51182","51183","51184","51185","51186","51187","51188","51189","51190","51191","51192","51193","51194","51195","51196","51197","51198","51199","51200","51201","51202","51203","51204","51205","51206","51207","51208","51209","51210","51211","51212","51213","51214","51215","51216","51217","51218","51219","51220","51221","51222","51223","51224","51225","51226","51227","51228","51229","51230","51231","51232","51233","51234","51235","51236","51237","51238","51239","51240","51241","51242","51243","51244","51245","51246","51247","51248","51249","51250","51251","51252","51253","51254","51255","51256","51257","51258","51259","51260","51261","51262","51263","51264","51265","51266","51267","51268","51269","51270","51271","51272","51273","51274","51275","51276","51277","51278","51279","51280","51281","51282","51283","51284","51285","51286","51287","51288","51289","51290","51291","51292","51293","51294","51295","51296","51297","51298","51299","51300","51301","51302","51303","51304","51305","51306","51307","51308","51309","51310","51311","51312","51313","51314","51315","51316","51317","51318","51319","51320","51321","51322","51323","51324","51325","51326","51327","51328","51329","51330","51331","51332","51333","51334","51335","51336","51337","51338","51339","51340","51341","51342","51343","51344","51345","51346","51347","51348","51349","51350","51351","51352","51353","51354","51355","51356","51357","51358","51359","51360","51361","51362","51363","51364","51365","51366","51367","51368","51369","51370","51371","51372","51373","51374","51375","51376","51377","51378","51379","51380","51381","51382","51383","51384","51385","51386","51387","51388","51389","51390","51391","51392","51393","51394","51395","51396","51397","51398","51399","51400","51401","51402","51403","51404","51405","51406","51407","51408","51409","51410","51411","51412","51413","51414","51415","51416","51417","51418","51419","51420","51421","51422","51423","51424","51425","51426","51427","51428","51429","51430","51431","51432","51433","51434","51435","51436","51437","51438","51439","51440","51441","51442","51443","51444","51445","51446","51447","51448","51449","51450","51451","51452","51453","51454","51455","51456","51457","51458","51459","51460","51461","51462","51463","51464","51465","51466","51467","51468","51469","51470","51471","51472","51473","51474","51475","51476","51477","51478","51479","51480","51481","51482","51483","51484","51485","51486","51487","51488","51489","51490","51491","51492","51493","51494","51495","51496","51497","51498","51499","51500","51501","51502","51503","51504","51505","51506","51507","51508","51509","51510","51511","51512","51513","51514","51515","51516","51517","51518","51519","51520","51521","51522","51523","51524","51525","51526","51527","51528","51529","51530","51531","51532","51533","51534","51535","51536","51537","51538","51539","51540","51541","51542","51543","51544","51545","51546","51547","51548","51549","51550","51551","51552","51553","51554","51555","51556","51557","51558","51559","51560","51561","51562","51563","51564","51565","51566","51567","51568","51569","51570","51571","51572","51573","51574","51575","51576","51577","51578","51579","51580","51581","51582","51583","51584","51585","51586","51587","51588","51589","51590","51591","51592","51593","51594","51595","51596","51597","51598","51599","51600","51601","51602","51603","51604","51605","51606","51607","51608","51609","51610","51611","51612","51613","51614","51615","51616","51617","51618","51619","51620","51621","51622","51623","51624","51625","51626","51627","51628","51629","51630","51631","51632","51633","51634","51635","51636","51637","51638","51639","51640","51641","51642","51643","51644","51645","51646","51647","51648","51649","51650","51651","51652","51653","51654","51655","51656","51657","51658","51659","51660","51661","51662","51663","51664","51665","51666","51667","51668","51669","51670","51671","51672","51673","51674","51675","51676","51677","51678","51679","51680","51681","51682","51683","51684","51685","51686","51687","51688","51689","51690","51691","51692","51693","51694","51695","51696","51697","51698","51699","51700","51701","51702","51703","51704","51705","51706","51707","51708","51709","51710","51711","51712","51713","51714","51715","51716","51717","51718","51719","51720","51721","51722","51723","51724","51725","51726","51727","51728","51729","51730","51731","51732","51733","51734","51735","51736","51737","51738","51739","51740","51741","51742","51743","51744","51745","51746","51747","51748","51749","51750","51751","51752","51753","51754","51755","51756","51757","51758","51759","51760","51761","51762","51763","51764","51765","51766","51767","51768","51769","51770","51771","51772","51773","51774","51775","51776","51777","51778","51779","51780","51781","51782","51783","51784","51785","51786","51787","51788","51789","51790","51791","51792","51793","51794","51795","51796","51797","51798","51799","51800","51801","51802","51803","51804","51805","51806","51807","51808","51809","51810","51811","51812","51813","51814","51815","51816","51817","51818","51819","51820","51821","51822","51823","51824","51825","51826","51827","51828","51829","51830","51831","51832","51833","51834","51835","51836","51837","51838","51839","51840","51841","51842","51843","51844","51845","51846","51847","51848","51849","51850","51851","51852","51853","51854","51855","51856","51857","51858","51859","51860","51861","51862","51863","51864","51865","51866","51867","51868","51869","51870","51871","51872","51873","51874","51875","51876","51877","51878","51879","51880","51881","51882","51883","51884","51885","51886","51887","51888","51889","51890","51891","51892","51893","51894","51895","51896","51897","51898","51899","51900","51901","51902","51903","51904","51905","51906","51907","51908","51909","51910","51911","51912","51913","51914","51915","51916","51917","51918","51919","51920","51921","51922","51923","51924","51925","51926","51927","51928","51929","51930","51931","51932","51933","51934","51935","51936","51937","51938","51939","51940","51941","51942","51943","51944","51945","51946","51947","51948","51949","51950","51951","51952","51953","51954","51955","51956","51957","51958","51959","51960","51961","51962","51963","51964","51965","51966","51967","51968","51969","51970","51971","51972","51973","51974","51975","51976","51977","51978","51979","51980","51981","51982","51983","51984","51985","51986","51987","51988","51989","51990","51991","51992","51993","51994","51995","51996","51997","51998","51999","52000","52001","52002","52003","52004","52005","52006","52007","52008","52009","52010","52011","52012","52013","52014","52015","52016","52017","52018","52019","52020","52021","52022","52023","52024","52025","52026","52027","52028","52029","52030","52031","52032","52033","52034","52035","52036","52037","52038","52039","52040","52041","52042","52043","52044","52045","52046","52047","52048","52049","52050","52051","52052","52053","52054","52055","52056","52057","52058","52059","52060","52061","52062","52063","52064","52065","52066","52067","52068","52069","52070","52071","52072","52073","52074","52075","52076","52077","52078","52079","52080","52081","52082","52083","52084","52085","52086","52087","52088","52089","52090","52091","52092","52093","52094","52095","52096","52097","52098","52099","52100","52101","52102","52103","52104","52105","52106","52107","52108","52109","52110","52111","52112","52113","52114","52115","52116","52117","52118","52119","52120","52121","52122","52123","52124","52125","52126","52127","52128","52129","52130","52131","52132","52133","52134","52135","52136","52137","52138","52139","52140","52141","52142","52143","52144","52145","52146","52147","52148","52149","52150","52151","52152","52153","52154","52155","52156","52157","52158","52159","52160","52161","52162","52163","52164","52165","52166","52167","52168","52169","52170","52171","52172","52173","52174","52175","52176","52177","52178","52179","52180","52181","52182","52183","52184","52185","52186","52187","52188","52189","52190","52191","52192","52193","52194","52195","52196","52197","52198","52199","52200","52201","52202","52203","52204","52205","52206","52207","52208","52209","52210","52211","52212","52213","52214","52215","52216","52217","52218","52219","52220","52221","52222","52223","52224","52225","52226","52227","52228","52229","52230","52231","52232","52233","52234","52235","52236","52237","52238","52239","52240","52241","52242","52243","52244","52245","52246","52247","52248","52249","52250","52251","52252","52253","52254","52255","52256","52257","52258","52259","52260","52261","52262","52263","52264","52265","52266","52267","52268","52269","52270","52271","52272","52273","52274","52275","52276","52277","52278","52279","52280","52281","52282","52283","52284","52285","52286","52287","52288","52289","52290","52291","52292","52293","52294","52295","52296","52297","52298","52299","52300","52301","52302","52303","52304","52305","52306","52307","52308","52309","52310","52311","52312","52313","52314","52315","52316","52317","52318","52319","52320","52321","52322","52323","52324","52325","52326","52327","52328","52329","52330","52331","52332","52333","52334","52335","52336","52337","52338","52339","52340","52341","52342","52343","52344","52345","52346","52347","52348","52349","52350","52351","52352","52353","52354","52355","52356","52357","52358","52359","52360","52361","52362","52363","52364","52365","52366","52367","52368","52369","52370","52371","52372","52373","52374","52375","52376","52377","52378","52379","52380","52381","52382","52383","52384","52385","52386","52387","52388","52389","52390","52391","52392","52393","52394","52395","52396","52397","52398","52399","52400","52401","52402","52403","52404","52405","52406","52407","52408","52409","52410","52411","52412","52413","52414","52415","52416","52417","52418","52419","52420","52421","52422","52423","52424","52425","52426","52427","52428","52429","52430","52431","52432","52433","52434","52435","52436","52437","52438","52439","52440","52441","52442","52443","52444","52445","52446","52447","52448","52449","52450","52451","52452","52453","52454","52455","52456","52457","52458","52459","52460","52461","52462","52463","52464","52465","52466","52467","52468","52469","52470","52471","52472","52473","52474","52475","52476","52477","52478","52479","52480","52481","52482","52483","52484","52485","52486","52487","52488","52489","52490","52491","52492","52493","52494","52495","52496","52497","52498","52499","52500","52501","52502","52503","52504","52505","52506","52507","52508","52509","52510","52511","52512","52513","52514","52515","52516","52517","52518","52519","52520","52521","52522","52523","52524","52525","52526","52527","52528","52529","52530","52531","52532","52533","52534","52535","52536","52537","52538","52539","52540","52541","52542","52543","52544","52545","52546","52547","52548","52549","52550","52551","52552","52553","52554","52555","52556","52557","52558","52559","52560","52561","52562","52563","52564","52565","52566","52567","52568","52569","52570","52571","52572","52573","52574","52575","52576","52577","52578","52579","52580","52581","52582","52583","52584","52585","52586","52587","52588","52589","52590","52591","52592","52593","52594","52595","52596","52597","52598","52599","52600","52601","52602","52603","52604","52605","52606","52607","52608","52609","52610","52611","52612","52613","52614","52615","52616","52617","52618","52619","52620","52621","52622","52623","52624","52625","52626","52627","52628","52629","52630","52631","52632","52633","52634","52635","52636","52637","52638","52639","52640","52641","52642","52643","52644","52645","52646","52647","52648","52649","52650","52651","52652","52653","52654","52655","52656","52657","52658","52659","52660","52661","52662","52663","52664","52665","52666","52667","52668","52669","52670","52671","52672","52673","52674","52675","52676","52677","52678","52679","52680","52681","52682","52683","52684","52685","52686","52687","52688","52689","52690","52691","52692","52693","52694","52695","52696","52697","52698","52699","52700","52701","52702","52703","52704","52705","52706","52707","52708","52709","52710","52711","52712","52713","52714","52715","52716","52717","52718","52719","52720","52721","52722","52723","52724","52725","52726","52727","52728","52729","52730","52731","52732","52733","52734","52735","52736","52737","52738","52739","52740","52741","52742","52743","52744","52745","52746","52747","52748","52749","52750","52751","52752","52753","52754","52755","52756","52757","52758","52759","52760","52761","52762","52763","52764","52765","52766","52767","52768","52769","52770","52771","52772","52773","52774","52775","52776","52777","52778","52779","52780","52781","52782","52783","52784","52785","52786","52787","52788","52789","52790","52791","52792","52793","52794","52795","52796","52797","52798","52799","52800","52801","52802","52803","52804","52805","52806","52807","52808","52809","52810","52811","52812","52813","52814","52815","52816","52817","52818","52819","52820","52821","52822","52823","52824","52825","52826","52827","52828","52829","52830","52831","52832","52833","52834","52835","52836","52837","52838","52839","52840","52841","52842","52843","52844","52845","52846","52847","52848","52849","52850","52851","52852","52853","52854","52855","52856","52857","52858","52859","52860","52861","52862","52863","52864","52865","52866","52867","52868","52869","52870","52871","52872","52873","52874","52875","52876","52877","52878","52879","52880","52881","52882","52883","52884","52885","52886","52887","52888","52889","52890","52891","52892","52893","52894","52895","52896","52897","52898","52899","52900","52901","52902","52903","52904","52905","52906","52907","52908","52909","52910","52911","52912","52913","52914","52915","52916","52917","52918","52919","52920","52921","52922","52923","52924","52925","52926","52927","52928","52929","52930","52931","52932","52933","52934","52935","52936","52937","52938","52939","52940","52941","52942","52943","52944","52945","52946","52947","52948","52949","52950","52951","52952","52953","52954","52955","52956","52957","52958","52959","52960","52961","52962","52963","52964","52965","52966","52967","52968","52969","52970","52971","52972","52973","52974","52975","52976","52977","52978","52979","52980","52981","52982","52983","52984","52985","52986","52987","52988","52989","52990","52991","52992","52993","52994","52995","52996","52997","52998","52999","53000","53001","53002","53003","53004","53005","53006","53007","53008","53009","53010","53011","53012","53013","53014","53015","53016","53017","53018","53019","53020","53021","53022","53023","53024","53025","53026","53027","53028","53029","53030","53031","53032","53033","53034","53035","53036","53037","53038","53039","53040","53041","53042","53043","53044","53045","53046","53047","53048","53049","53050","53051","53052","53053","53054","53055","53056","53057","53058","53059","53060","53061","53062","53063","53064","53065","53066","53067","53068","53069","53070","53071","53072","53073","53074","53075","53076","53077","53078","53079","53080","53081","53082","53083","53084","53085","53086","53087","53088","53089","53090","53091","53092","53093","53094","53095","53096","53097","53098","53099","53100","53101","53102","53103","53104","53105","53106","53107","53108","53109","53110","53111","53112","53113","53114","53115","53116","53117","53118","53119","53120","53121","53122","53123","53124","53125","53126","53127","53128","53129","53130","53131","53132","53133","53134","53135","53136","53137","53138","53139","53140","53141","53142","53143","53144","53145","53146","53147","53148","53149","53150","53151","53152","53153","53154","53155","53156","53157","53158","53159","53160","53161","53162","53163","53164","53165","53166","53167","53168","53169","53170","53171","53172","53173","53174","53175","53176","53177","53178","53179","53180","53181","53182","53183","53184","53185","53186","53187","53188","53189","53190","53191","53192","53193","53194","53195","53196","53197","53198","53199","53200","53201","53202","53203","53204","53205","53206","53207","53208","53209","53210","53211","53212","53213","53214","53215","53216","53217","53218","53219","53220","53221","53222","53223","53224","53225","53226","53227","53228","53229","53230","53231","53232","53233","53234","53235","53236","53237","53238","53239","53240","53241","53242","53243","53244","53245","53246","53247","53248","53249","53250","53251","53252","53253","53254","53255","53256","53257","53258","53259","53260","53261","53262","53263","53264","53265","53266","53267","53268","53269","53270","53271","53272","53273","53274","53275","53276","53277","53278","53279","53280","53281","53282","53283","53284","53285","53286","53287","53288","53289","53290","53291","53292","53293","53294","53295","53296","53297","53298","53299","53300","53301","53302","53303","53304","53305","53306","53307","53308","53309","53310","53311","53312","53313","53314","53315","53316","53317","53318","53319","53320","53321","53322","53323","53324","53325","53326","53327","53328","53329","53330","53331","53332","53333","53334","53335","53336","53337","53338","53339","53340","53341","53342","53343","53344","53345","53346","53347","53348","53349","53350","53351","53352","53353","53354","53355","53356","53357","53358","53359","53360","53361","53362","53363","53364","53365","53366","53367","53368","53369","53370","53371","53372","53373","53374","53375","53376","53377","53378","53379","53380","53381","53382","53383","53384","53385","53386","53387","53388","53389","53390","53391","53392","53393","53394","53395","53396","53397","53398","53399","53400","53401","53402","53403","53404","53405","53406","53407","53408","53409","53410","53411","53412","53413","53414","53415","53416","53417","53418","53419","53420","53421","53422","53423","53424","53425","53426","53427","53428","53429","53430","53431","53432","53433","53434","53435","53436","53437","53438","53439","53440","53441","53442","53443","53444","53445","53446","53447","53448","53449","53450","53451","53452","53453","53454","53455","53456","53457","53458","53459","53460","53461","53462","53463","53464","53465","53466","53467","53468","53469","53470","53471","53472","53473","53474","53475","53476","53477","53478","53479","53480","53481","53482","53483","53484","53485","53486","53487","53488","53489","53490","53491","53492","53493","53494","53495","53496","53497","53498","53499","53500","53501","53502","53503","53504","53505","53506","53507","53508","53509","53510","53511","53512","53513","53514","53515","53516","53517","53518","53519","53520","53521","53522","53523","53524","53525","53526","53527","53528","53529","53530","53531","53532","53533","53534","53535","53536","53537","53538","53539","53540","53541","53542","53543","53544","53545","53546","53547","53548","53549","53550","53551","53552","53553","53554","53555","53556","53557","53558","53559","53560","53561","53562","53563","53564","53565","53566","53567","53568","53569","53570","53571","53572","53573","53574","53575","53576","53577","53578","53579","53580","53581","53582","53583","53584","53585","53586","53587","53588","53589","53590","53591","53592","53593","53594","53595","53596","53597","53598","53599","53600","53601","53602","53603","53604","53605","53606","53607","53608","53609","53610","53611","53612","53613","53614","53615","53616","53617","53618","53619","53620","53621","53622","53623","53624","53625","53626","53627","53628","53629","53630","53631","53632","53633","53634","53635","53636","53637","53638","53639","53640","53641","53642","53643","53644","53645","53646","53647","53648","53649","53650","53651","53652","53653","53654","53655","53656","53657","53658","53659","53660","53661","53662","53663","53664","53665","53666","53667","53668","53669","53670","53671","53672","53673","53674","53675","53676","53677","53678","53679","53680","53681","53682","53683","53684","53685","53686","53687","53688","53689","53690","53691","53692","53693","53694","53695","53696","53697","53698","53699","53700","53701","53702","53703","53704","53705","53706","53707","53708","53709","53710","53711","53712","53713","53714","53715","53716","53717","53718","53719","53720","53721","53722","53723","53724","53725","53726","53727","53728","53729","53730","53731","53732","53733","53734","53735","53736","53737","53738","53739","53740","53741","53742","53743","53744","53745","53746","53747","53748","53749","53750","53751","53752","53753","53754","53755","53756","53757","53758","53759","53760","53761","53762","53763","53764","53765","53766","53767","53768","53769","53770","53771","53772","53773","53774","53775","53776","53777","53778","53779","53780","53781","53782","53783","53784","53785","53786","53787","53788","53789","53790","53791","53792","53793","53794","53795","53796","53797","53798","53799","53800","53801","53802","53803","53804","53805","53806","53807","53808","53809","53810","53811","53812","53813","53814","53815","53816","53817","53818","53819","53820","53821","53822","53823","53824","53825","53826","53827","53828","53829","53830","53831","53832","53833","53834","53835","53836","53837","53838","53839","53840","53841","53842","53843","53844","53845","53846","53847","53848","53849","53850","53851","53852","53853","53854","53855","53856","53857","53858","53859","53860","53861","53862","53863","53864","53865","53866","53867","53868","53869","53870","53871","53872","53873","53874","53875","53876","53877","53878","53879","53880","53881","53882","53883","53884","53885","53886","53887","53888","53889","53890","53891","53892","53893","53894","53895","53896","53897","53898","53899","53900","53901","53902","53903","53904","53905","53906","53907","53908","53909","53910","53911","53912","53913","53914","53915","53916","53917","53918","53919","53920","53921","53922","53923","53924","53925","53926","53927","53928","53929","53930","53931","53932","53933","53934","53935","53936","53937","53938","53939","53940","53941","53942","53943","53944","53945","53946","53947","53948","53949","53950","53951","53952","53953","53954","53955","53956","53957","53958","53959","53960","53961","53962","53963","53964","53965","53966","53967","53968","53969","53970","53971","53972","53973","53974","53975","53976","53977","53978","53979","53980","53981","53982","53983","53984","53985","53986","53987","53988","53989","53990","53991","53992","53993","53994","53995","53996","53997","53998","53999","54000","54001","54002","54003","54004","54005","54006","54007","54008","54009","54010","54011","54012","54013","54014","54015","54016","54017","54018","54019","54020","54021","54022","54023","54024","54025","54026","54027","54028","54029","54030","54031","54032","54033","54034","54035","54036","54037","54038","54039","54040","54041","54042","54043","54044","54045","54046","54047","54048","54049","54050","54051","54052","54053","54054","54055","54056","54057","54058","54059","54060","54061","54062","54063","54064","54065","54066","54067","54068","54069","54070","54071","54072","54073","54074","54075","54076","54077","54078","54079","54080","54081","54082","54083","54084","54085","54086","54087","54088","54089","54090","54091","54092","54093","54094","54095","54096","54097","54098","54099","54100","54101","54102","54103","54104","54105","54106","54107","54108","54109","54110","54111","54112","54113","54114","54115","54116","54117","54118","54119","54120","54121","54122","54123","54124","54125","54126","54127","54128","54129","54130","54131","54132","54133","54134","54135","54136","54137","54138","54139","54140","54141","54142","54143","54144","54145","54146","54147","54148","54149","54150","54151","54152","54153","54154","54155","54156","54157","54158","54159","54160","54161","54162","54163","54164","54165","54166","54167","54168","54169","54170","54171","54172","54173","54174","54175","54176","54177","54178","54179","54180","54181","54182","54183","54184","54185","54186","54187","54188","54189","54190","54191","54192","54193","54194","54195","54196","54197","54198","54199","54200","54201","54202","54203","54204","54205","54206","54207","54208","54209","54210","54211","54212","54213","54214","54215","54216","54217","54218","54219","54220","54221","54222","54223","54224","54225","54226","54227","54228","54229","54230","54231","54232","54233","54234","54235","54236","54237","54238","54239","54240","54241","54242","54243","54244","54245","54246","54247","54248","54249","54250","54251","54252","54253","54254","54255","54256","54257","54258","54259","54260","54261","54262","54263","54264","54265","54266","54267","54268","54269","54270","54271","54272","54273","54274","54275","54276","54277","54278","54279","54280","54281","54282","54283","54284","54285","54286","54287","54288","54289","54290","54291","54292","54293","54294","54295","54296","54297","54298","54299","54300","54301","54302","54303","54304","54305","54306","54307","54308","54309","54310","54311","54312","54313","54314","54315","54316","54317","54318","54319","54320","54321","54322","54323","54324","54325","54326","54327","54328","54329","54330","54331","54332","54333","54334","54335","54336","54337","54338","54339","54340","54341","54342","54343","54344","54345","54346","54347","54348","54349","54350","54351","54352","54353","54354","54355","54356","54357","54358","54359","54360","54361","54362","54363","54364","54365","54366","54367","54368","54369","54370","54371","54372","54373","54374","54375","54376","54377","54378","54379","54380","54381","54382","54383","54384","54385","54386","54387","54388","54389","54390","54391","54392","54393","54394","54395","54396","54397","54398","54399","54400","54401","54402","54403","54404","54405","54406","54407","54408","54409","54410","54411","54412","54413","54414","54415","54416","54417","54418","54419","54420","54421","54422","54423","54424","54425","54426","54427","54428","54429","54430","54431","54432","54433","54434","54435","54436","54437","54438","54439","54440","54441","54442","54443","54444","54445","54446","54447","54448","54449","54450","54451","54452","54453","54454","54455","54456","54457","54458","54459","54460","54461","54462","54463","54464","54465","54466","54467","54468","54469","54470","54471","54472","54473","54474","54475","54476","54477","54478","54479","54480","54481","54482","54483","54484","54485","54486","54487","54488","54489","54490","54491","54492","54493","54494","54495","54496","54497","54498","54499","54500","54501","54502","54503","54504","54505","54506","54507","54508","54509","54510","54511","54512","54513","54514","54515","54516","54517","54518","54519","54520","54521","54522","54523","54524","54525","54526","54527","54528","54529","54530","54531","54532","54533","54534","54535","54536","54537","54538","54539","54540","54541","54542","54543","54544","54545","54546","54547","54548","54549","54550","54551","54552","54553","54554","54555","54556","54557","54558","54559","54560","54561","54562","54563","54564","54565","54566","54567","54568","54569","54570","54571","54572","54573","54574","54575","54576","54577","54578","54579","54580","54581","54582","54583","54584","54585","54586","54587","54588","54589","54590","54591","54592","54593","54594","54595","54596","54597","54598","54599","54600","54601","54602","54603","54604","54605","54606","54607","54608","54609","54610","54611","54612","54613","54614","54615","54616","54617","54618","54619","54620","54621","54622","54623","54624","54625","54626","54627","54628","54629","54630","54631","54632","54633","54634","54635","54636","54637","54638","54639","54640","54641","54642","54643","54644","54645","54646","54647","54648","54649","54650","54651","54652","54653","54654","54655","54656","54657","54658","54659","54660","54661","54662","54663","54664","54665","54666","54667","54668","54669","54670","54671","54672","54673","54674","54675","54676","54677","54678","54679","54680","54681","54682","54683","54684","54685","54686","54687","54688","54689","54690","54691","54692","54693","54694","54695","54696","54697","54698","54699","54700","54701","54702","54703","54704","54705","54706","54707","54708","54709","54710","54711","54712","54713","54714","54715","54716","54717","54718","54719","54720","54721","54722","54723","54724","54725","54726","54727","54728","54729","54730","54731","54732","54733","54734","54735","54736","54737","54738","54739","54740","54741","54742","54743","54744","54745","54746","54747","54748","54749","54750","54751","54752","54753","54754","54755","54756","54757","54758","54759","54760","54761","54762","54763","54764","54765","54766","54767","54768","54769","54770","54771","54772","54773","54774","54775","54776","54777","54778","54779","54780","54781","54782","54783","54784","54785","54786","54787","54788","54789","54790","54791","54792","54793","54794","54795","54796","54797","54798","54799","54800","54801","54802","54803","54804","54805","54806","54807","54808","54809","54810","54811","54812","54813","54814","54815","54816","54817","54818","54819","54820","54821","54822","54823","54824","54825","54826","54827","54828","54829","54830","54831","54832","54833","54834","54835","54836","54837","54838","54839","54840","54841","54842","54843","54844","54845","54846","54847","54848","54849","54850","54851","54852","54853","54854","54855","54856","54857","54858","54859","54860","54861","54862","54863","54864","54865","54866","54867","54868","54869","54870","54871","54872","54873","54874","54875","54876","54877","54878","54879","54880","54881","54882","54883","54884","54885","54886","54887","54888","54889","54890","54891","54892","54893","54894","54895","54896","54897","54898","54899","54900","54901","54902","54903","54904","54905","54906","54907","54908","54909","54910","54911","54912","54913","54914","54915","54916","54917","54918","54919","54920","54921","54922","54923","54924","54925","54926","54927","54928","54929","54930","54931","54932","54933","54934","54935","54936","54937","54938","54939","54940","54941","54942","54943","54944","54945","54946","54947","54948","54949","54950","54951","54952","54953","54954","54955","54956","54957","54958","54959","54960","54961","54962","54963","54964","54965","54966","54967","54968","54969","54970","54971","54972","54973","54974","54975","54976","54977","54978","54979","54980","54981","54982","54983","54984","54985","54986","54987","54988","54989","54990","54991","54992","54993","54994","54995","54996","54997","54998","54999","55000","55001","55002","55003","55004","55005","55006","55007","55008","55009","55010","55011","55012","55013","55014","55015","55016","55017","55018","55019","55020","55021","55022","55023","55024","55025","55026","55027","55028","55029","55030","55031","55032","55033","55034","55035","55036","55037","55038","55039","55040","55041","55042","55043","55044","55045","55046","55047","55048","55049","55050","55051","55052","55053","55054","55055","55056","55057","55058","55059","55060","55061","55062","55063","55064","55065","55066","55067","55068","55069","55070","55071","55072","55073","55074","55075","55076","55077","55078","55079","55080","55081","55082","55083","55084","55085","55086","55087","55088","55089","55090","55091","55092","55093","55094","55095","55096","55097","55098","55099","55100","55101","55102","55103","55104","55105","55106","55107","55108","55109","55110","55111","55112","55113","55114","55115","55116","55117","55118","55119","55120","55121","55122","55123","55124","55125","55126","55127","55128","55129","55130","55131","55132","55133","55134","55135","55136","55137","55138","55139","55140","55141","55142","55143","55144","55145","55146","55147","55148","55149","55150","55151","55152","55153","55154","55155","55156","55157","55158","55159","55160","55161","55162","55163","55164","55165","55166","55167","55168","55169","55170","55171","55172","55173","55174","55175","55176","55177","55178","55179","55180","55181","55182","55183","55184","55185","55186","55187","55188","55189","55190","55191","55192","55193","55194","55195","55196","55197","55198","55199","55200","55201","55202","55203","55204","55205","55206","55207","55208","55209","55210","55211","55212","55213","55214","55215","55216","55217","55218","55219","55220","55221","55222","55223","55224","55225","55226","55227","55228","55229","55230","55231","55232","55233","55234","55235","55236","55237","55238","55239","55240","55241","55242","55243","55244","55245","55246","55247","55248","55249","55250","55251","55252","55253","55254","55255","55256","55257","55258","55259","55260","55261","55262","55263","55264","55265","55266","55267","55268","55269","55270","55271","55272","55273","55274","55275","55276","55277","55278","55279","55280","55281","55282","55283","55284","55285","55286","55287","55288","55289","55290","55291","55292","55293","55294","55295","55296","55297","55298","55299","55300","55301","55302","55303","55304","55305","55306","55307","55308","55309","55310","55311","55312","55313","55314","55315","55316","55317","55318","55319","55320","55321","55322","55323","55324","55325","55326","55327","55328","55329","55330","55331","55332","55333","55334","55335","55336","55337","55338","55339","55340","55341","55342","55343","55344","55345","55346","55347","55348","55349","55350","55351","55352","55353","55354","55355","55356","55357","55358","55359","55360","55361","55362","55363","55364","55365","55366","55367","55368","55369","55370","55371","55372","55373","55374","55375","55376","55377","55378","55379","55380","55381","55382","55383","55384","55385","55386","55387","55388","55389","55390","55391","55392","55393","55394","55395","55396","55397","55398","55399","55400","55401","55402","55403","55404","55405","55406","55407","55408","55409","55410","55411","55412","55413","55414","55415","55416","55417","55418","55419","55420","55421","55422","55423","55424","55425","55426","55427","55428","55429","55430","55431","55432","55433","55434","55435","55436","55437","55438","55439","55440","55441","55442","55443","55444","55445","55446","55447","55448","55449","55450","55451","55452","55453","55454","55455","55456","55457","55458","55459","55460","55461","55462","55463","55464","55465","55466","55467","55468","55469","55470","55471","55472","55473","55474","55475","55476","55477","55478","55479","55480","55481","55482","55483","55484","55485","55486","55487","55488","55489","55490","55491","55492","55493","55494","55495","55496","55497","55498","55499","55500","55501","55502","55503","55504","55505","55506","55507","55508","55509","55510","55511","55512","55513","55514","55515","55516","55517","55518","55519","55520","55521","55522","55523","55524","55525","55526","55527","55528","55529","55530","55531","55532","55533","55534","55535","55536","55537","55538","55539","55540","55541","55542","55543","55544","55545","55546","55547","55548","55549","55550","55551","55552","55553","55554","55555","55556","55557","55558","55559","55560","55561","55562","55563","55564","55565","55566","55567","55568","55569","55570","55571","55572","55573","55574","55575","55576","55577","55578","55579","55580","55581","55582","55583","55584","55585","55586","55587","55588","55589","55590","55591","55592","55593","55594","55595","55596","55597","55598","55599","55600","55601","55602","55603","55604","55605","55606","55607","55608","55609","55610","55611","55612","55613","55614","55615","55616","55617","55618","55619","55620","55621","55622","55623","55624","55625","55626","55627","55628","55629","55630","55631","55632","55633","55634","55635","55636","55637","55638","55639","55640","55641","55642","55643","55644","55645","55646","55647","55648","55649","55650","55651","55652","55653","55654","55655","55656","55657","55658","55659","55660","55661","55662","55663","55664","55665","55666","55667","55668","55669","55670","55671","55672","55673","55674","55675","55676","55677","55678","55679","55680","55681","55682","55683","55684","55685","55686","55687","55688","55689","55690","55691","55692","55693","55694","55695","55696","55697","55698","55699","55700","55701","55702","55703","55704","55705","55706","55707","55708","55709","55710","55711","55712","55713","55714","55715","55716","55717","55718","55719","55720","55721","55722","55723","55724","55725","55726","55727","55728","55729","55730","55731","55732","55733","55734","55735","55736","55737","55738","55739","55740","55741","55742","55743","55744","55745","55746","55747","55748","55749","55750","55751","55752","55753","55754","55755","55756","55757","55758","55759","55760","55761","55762","55763","55764","55765","55766","55767","55768","55769","55770","55771","55772","55773","55774","55775","55776","55777","55778","55779","55780","55781","55782","55783","55784","55785","55786","55787","55788","55789","55790","55791","55792","55793","55794","55795","55796","55797","55798","55799","55800","55801","55802","55803","55804","55805","55806","55807","55808","55809","55810","55811","55812","55813","55814","55815","55816","55817","55818","55819","55820","55821","55822","55823","55824","55825","55826","55827","55828","55829","55830","55831","55832","55833","55834","55835","55836","55837","55838","55839","55840","55841","55842","55843","55844","55845","55846","55847","55848","55849","55850","55851","55852","55853","55854","55855","55856","55857","55858","55859","55860","55861","55862","55863","55864","55865","55866","55867","55868","55869","55870","55871","55872","55873","55874","55875","55876","55877","55878","55879","55880","55881","55882","55883","55884","55885","55886","55887","55888","55889","55890","55891","55892","55893","55894","55895","55896","55897","55898","55899","55900","55901","55902","55903","55904","55905","55906","55907","55908","55909","55910","55911","55912","55913","55914","55915","55916","55917","55918","55919","55920","55921","55922","55923","55924","55925","55926","55927","55928","55929","55930","55931","55932","55933","55934","55935","55936","55937","55938","55939","55940","55941","55942","55943","55944","55945","55946","55947","55948","55949","55950","55951","55952","55953","55954","55955","55956","55957","55958","55959","55960","55961","55962","55963","55964","55965","55966","55967","55968","55969","55970","55971","55972","55973","55974","55975","55976","55977","55978","55979","55980","55981","55982","55983","55984","55985","55986","55987","55988","55989","55990","55991","55992","55993","55994","55995","55996","55997","55998","55999","56000","56001","56002","56003","56004","56005","56006","56007","56008","56009","56010","56011","56012","56013","56014","56015","56016","56017","56018","56019","56020","56021","56022","56023","56024","56025","56026","56027","56028","56029","56030","56031","56032","56033","56034","56035","56036","56037","56038","56039","56040","56041","56042","56043","56044","56045","56046","56047","56048","56049","56050","56051","56052","56053","56054","56055","56056","56057","56058","56059","56060","56061","56062","56063","56064","56065","56066","56067","56068","56069","56070","56071","56072","56073","56074","56075","56076","56077","56078","56079","56080","56081","56082","56083","56084","56085","56086","56087","56088","56089","56090","56091","56092","56093","56094","56095","56096","56097","56098","56099","56100","56101","56102","56103","56104","56105","56106","56107","56108","56109","56110","56111","56112","56113","56114","56115","56116","56117","56118","56119","56120","56121","56122","56123","56124","56125","56126","56127","56128","56129","56130","56131","56132","56133","56134","56135","56136","56137","56138","56139","56140","56141","56142","56143","56144","56145","56146","56147","56148","56149","56150","56151","56152","56153","56154","56155","56156","56157","56158","56159","56160","56161","56162","56163","56164","56165","56166","56167","56168","56169","56170","56171","56172","56173","56174","56175","56176","56177","56178","56179","56180","56181","56182","56183","56184","56185","56186","56187","56188","56189","56190","56191","56192","56193","56194","56195","56196","56197","56198","56199","56200","56201","56202","56203","56204","56205","56206","56207","56208","56209","56210","56211","56212","56213","56214","56215","56216","56217","56218","56219","56220","56221","56222","56223","56224","56225","56226","56227","56228","56229","56230","56231","56232","56233","56234","56235","56236","56237","56238","56239","56240","56241","56242","56243","56244","56245","56246","56247","56248","56249","56250","56251","56252","56253","56254","56255","56256","56257","56258","56259","56260","56261","56262","56263","56264","56265","56266","56267","56268","56269","56270","56271","56272","56273","56274","56275","56276","56277","56278","56279","56280","56281","56282","56283","56284","56285","56286","56287","56288","56289","56290","56291","56292","56293","56294","56295","56296","56297","56298","56299","56300","56301","56302","56303","56304","56305","56306","56307","56308","56309","56310","56311","56312","56313","56314","56315","56316","56317","56318","56319","56320","56321","56322","56323","56324","56325","56326","56327","56328","56329","56330","56331","56332","56333","56334","56335","56336","56337","56338","56339","56340","56341","56342","56343","56344","56345","56346","56347","56348","56349","56350","56351","56352","56353","56354","56355","56356","56357","56358","56359","56360","56361","56362","56363","56364","56365","56366","56367","56368","56369","56370","56371","56372","56373","56374","56375","56376","56377","56378","56379","56380","56381","56382","56383","56384","56385","56386","56387","56388","56389","56390","56391","56392","56393","56394","56395","56396","56397","56398","56399","56400","56401","56402","56403","56404","56405","56406","56407","56408","56409","56410","56411","56412","56413","56414","56415","56416","56417","56418","56419","56420","56421","56422","56423","56424","56425","56426","56427","56428","56429","56430","56431","56432","56433","56434","56435","56436","56437","56438","56439","56440","56441","56442","56443","56444","56445","56446","56447","56448","56449","56450","56451","56452","56453","56454","56455","56456","56457","56458","56459","56460","56461","56462","56463","56464","56465","56466","56467","56468","56469","56470","56471","56472","56473","56474","56475","56476","56477","56478","56479","56480","56481","56482","56483","56484","56485","56486","56487","56488","56489","56490","56491","56492","56493","56494","56495","56496","56497","56498","56499","56500","56501","56502","56503","56504","56505","56506","56507","56508","56509","56510","56511","56512","56513","56514","56515","56516","56517","56518","56519","56520","56521","56522","56523","56524","56525","56526","56527","56528","56529","56530","56531","56532","56533","56534","56535","56536","56537","56538","56539","56540","56541","56542","56543","56544","56545","56546","56547","56548","56549","56550","56551","56552","56553","56554","56555","56556","56557","56558","56559","56560","56561","56562","56563","56564","56565","56566","56567","56568","56569","56570","56571","56572","56573","56574","56575","56576","56577","56578","56579","56580","56581","56582","56583","56584","56585","56586","56587","56588","56589","56590","56591","56592","56593","56594","56595","56596","56597","56598","56599","56600","56601","56602","56603","56604","56605","56606","56607","56608","56609","56610","56611","56612","56613","56614","56615","56616","56617","56618","56619","56620","56621","56622","56623","56624","56625","56626","56627","56628","56629","56630","56631","56632","56633","56634","56635","56636","56637","56638","56639","56640","56641","56642","56643","56644","56645","56646","56647","56648","56649","56650","56651","56652","56653","56654","56655","56656","56657","56658","56659","56660","56661","56662","56663","56664","56665","56666","56667","56668","56669","56670","56671","56672","56673","56674","56675","56676","56677","56678","56679","56680","56681","56682","56683","56684","56685","56686","56687","56688","56689","56690","56691","56692","56693","56694","56695","56696","56697","56698","56699","56700","56701","56702","56703","56704","56705","56706","56707","56708","56709","56710","56711","56712","56713","56714","56715","56716","56717","56718","56719","56720","56721","56722","56723","56724","56725","56726","56727","56728","56729","56730","56731","56732","56733","56734","56735","56736","56737","56738","56739","56740","56741","56742","56743","56744","56745","56746","56747","56748","56749","56750","56751","56752","56753","56754","56755","56756","56757","56758","56759","56760","56761","56762","56763","56764","56765","56766","56767","56768","56769","56770","56771","56772","56773","56774","56775","56776","56777","56778","56779","56780","56781","56782","56783","56784","56785","56786","56787","56788","56789","56790","56791","56792","56793","56794","56795","56796","56797","56798","56799","56800","56801","56802","56803","56804","56805","56806","56807","56808","56809","56810","56811","56812","56813","56814","56815","56816","56817","56818","56819","56820","56821","56822","56823","56824","56825","56826","56827","56828","56829","56830","56831","56832","56833","56834","56835","56836","56837","56838","56839","56840","56841","56842","56843","56844","56845","56846","56847","56848","56849","56850","56851","56852","56853","56854","56855","56856","56857","56858","56859","56860","56861","56862","56863","56864","56865","56866","56867","56868","56869","56870","56871","56872","56873","56874","56875","56876","56877","56878","56879","56880","56881","56882","56883","56884","56885","56886","56887","56888","56889","56890","56891","56892","56893","56894","56895","56896","56897","56898","56899","56900","56901","56902","56903","56904","56905","56906","56907","56908","56909","56910","56911","56912","56913","56914","56915","56916","56917","56918","56919","56920","56921","56922","56923","56924","56925","56926","56927","56928","56929","56930","56931","56932","56933","56934","56935","56936","56937","56938","56939","56940","56941","56942","56943","56944","56945","56946","56947","56948","56949","56950","56951","56952","56953","56954","56955","56956","56957","56958","56959","56960","56961","56962","56963","56964","56965","56966","56967","56968","56969","56970","56971","56972","56973","56974","56975","56976","56977","56978","56979","56980","56981","56982","56983","56984","56985","56986","56987","56988","56989","56990","56991","56992","56993","56994","56995","56996","56997","56998","56999","57000","57001","57002","57003","57004","57005","57006","57007","57008","57009","57010","57011","57012","57013","57014","57015","57016","57017","57018","57019","57020","57021","57022","57023","57024","57025","57026","57027","57028","57029","57030","57031","57032","57033","57034","57035","57036","57037","57038","57039","57040","57041","57042","57043","57044","57045","57046","57047","57048","57049","57050","57051","57052","57053","57054","57055","57056","57057","57058","57059","57060","57061","57062","57063","57064","57065","57066","57067","57068","57069","57070","57071","57072","57073","57074","57075","57076","57077","57078","57079","57080","57081","57082","57083","57084","57085","57086","57087","57088","57089","57090","57091","57092","57093","57094","57095","57096","57097","57098","57099","57100","57101","57102","57103","57104","57105","57106","57107","57108","57109","57110","57111","57112","57113","57114","57115","57116","57117","57118","57119","57120","57121","57122","57123","57124","57125","57126","57127","57128","57129","57130","57131","57132","57133","57134","57135","57136","57137","57138","57139","57140","57141","57142","57143","57144","57145","57146","57147","57148","57149","57150","57151","57152","57153","57154","57155","57156","57157","57158","57159","57160","57161","57162","57163","57164","57165","57166","57167","57168","57169","57170","57171","57172","57173","57174","57175","57176","57177","57178","57179","57180","57181","57182","57183","57184","57185","57186","57187","57188","57189","57190","57191","57192","57193","57194","57195","57196","57197","57198","57199","57200","57201","57202","57203","57204","57205","57206","57207","57208","57209","57210","57211","57212","57213","57214","57215","57216","57217","57218","57219","57220","57221","57222","57223","57224","57225","57226","57227","57228","57229","57230","57231","57232","57233","57234","57235","57236","57237","57238","57239","57240","57241","57242","57243","57244","57245","57246","57247","57248","57249","57250","57251","57252","57253","57254","57255","57256","57257","57258","57259","57260","57261","57262","57263","57264","57265","57266","57267","57268","57269","57270","57271","57272","57273","57274","57275","57276","57277","57278","57279","57280","57281","57282","57283","57284","57285","57286","57287","57288","57289","57290","57291","57292","57293","57294","57295","57296","57297","57298","57299","57300","57301","57302","57303","57304","57305","57306","57307","57308","57309","57310","57311","57312","57313","57314","57315","57316","57317","57318","57319","57320","57321","57322","57323","57324","57325","57326","57327","57328","57329","57330","57331","57332","57333","57334","57335","57336","57337","57338","57339","57340","57341","57342","57343","57344","57345","57346","57347","57348","57349","57350","57351","57352","57353","57354","57355","57356","57357","57358","57359","57360","57361","57362","57363","57364","57365","57366","57367","57368","57369","57370","57371","57372","57373","57374","57375","57376","57377","57378","57379","57380","57381","57382","57383","57384","57385","57386","57387","57388","57389","57390","57391","57392","57393","57394","57395","57396","57397","57398","57399","57400","57401","57402","57403","57404","57405","57406","57407","57408","57409","57410","57411","57412","57413","57414","57415","57416","57417","57418","57419","57420","57421","57422","57423","57424","57425","57426","57427","57428","57429","57430","57431","57432","57433","57434","57435","57436","57437","57438","57439","57440","57441","57442","57443","57444","57445","57446","57447","57448","57449","57450","57451","57452","57453","57454","57455","57456","57457","57458","57459","57460","57461","57462","57463","57464","57465","57466","57467","57468","57469","57470","57471","57472","57473","57474","57475","57476","57477","57478","57479","57480","57481","57482","57483","57484","57485","57486","57487","57488","57489","57490","57491","57492","57493","57494","57495","57496","57497","57498","57499","57500","57501","57502","57503","57504","57505","57506","57507","57508","57509","57510","57511","57512","57513","57514","57515","57516","57517","57518","57519","57520","57521","57522","57523","57524","57525","57526","57527","57528","57529","57530","57531","57532","57533","57534","57535","57536","57537","57538","57539","57540","57541","57542","57543","57544","57545","57546","57547","57548","57549","57550","57551","57552","57553","57554","57555","57556","57557","57558","57559","57560","57561","57562","57563","57564","57565","57566","57567","57568","57569","57570","57571","57572","57573","57574","57575","57576","57577","57578","57579","57580","57581","57582","57583","57584","57585","57586","57587","57588","57589","57590","57591","57592","57593","57594","57595","57596","57597","57598","57599","57600","57601","57602","57603","57604","57605","57606","57607","57608","57609","57610","57611","57612","57613","57614","57615","57616","57617","57618","57619","57620","57621","57622","57623","57624","57625","57626","57627","57628","57629","57630","57631","57632","57633","57634","57635","57636","57637","57638","57639","57640","57641","57642","57643","57644","57645","57646","57647","57648","57649","57650","57651","57652","57653","57654","57655","57656","57657","57658","57659","57660","57661","57662","57663","57664","57665","57666","57667","57668","57669","57670","57671","57672","57673","57674","57675","57676","57677","57678","57679","57680","57681","57682","57683","57684","57685","57686","57687","57688","57689","57690","57691","57692","57693","57694","57695","57696","57697","57698","57699","57700","57701","57702","57703","57704","57705","57706","57707","57708","57709","57710","57711","57712","57713","57714","57715","57716","57717","57718","57719","57720","57721","57722","57723","57724","57725","57726","57727","57728","57729","57730","57731","57732","57733","57734","57735","57736","57737","57738","57739","57740","57741","57742","57743","57744","57745","57746","57747","57748","57749","57750","57751","57752","57753","57754","57755","57756","57757","57758","57759","57760","57761","57762","57763","57764","57765","57766","57767","57768","57769","57770","57771","57772","57773","57774","57775","57776","57777","57778","57779","57780","57781","57782","57783","57784","57785","57786","57787","57788","57789","57790","57791","57792","57793","57794","57795","57796","57797","57798","57799","57800","57801","57802","57803","57804","57805","57806","57807","57808","57809","57810","57811","57812","57813","57814","57815","57816","57817","57818","57819","57820","57821","57822","57823","57824","57825","57826","57827","57828","57829","57830","57831","57832","57833","57834","57835","57836","57837","57838","57839","57840","57841","57842","57843","57844","57845","57846","57847","57848","57849","57850","57851","57852","57853","57854","57855","57856","57857","57858","57859","57860","57861","57862","57863","57864","57865","57866","57867","57868","57869","57870","57871","57872","57873","57874","57875","57876","57877","57878","57879","57880","57881","57882","57883","57884","57885","57886","57887","57888","57889","57890","57891","57892","57893","57894","57895","57896","57897","57898","57899","57900","57901","57902","57903","57904","57905","57906","57907","57908","57909","57910","57911","57912","57913","57914","57915","57916","57917","57918","57919","57920","57921","57922","57923","57924","57925","57926","57927","57928","57929","57930","57931","57932","57933","57934","57935","57936","57937","57938","57939","57940","57941","57942","57943","57944","57945","57946","57947","57948","57949","57950","57951","57952","57953","57954","57955","57956","57957","57958","57959","57960","57961","57962","57963","57964","57965","57966","57967","57968","57969","57970","57971","57972","57973","57974","57975","57976","57977","57978","57979","57980","57981","57982","57983","57984","57985","57986","57987","57988","57989","57990","57991","57992","57993","57994","57995","57996","57997","57998","57999","58000","58001","58002","58003","58004","58005","58006","58007","58008","58009","58010","58011","58012","58013","58014","58015","58016","58017","58018","58019","58020","58021","58022","58023","58024","58025","58026","58027","58028","58029","58030","58031","58032","58033","58034","58035","58036","58037","58038","58039","58040","58041","58042","58043","58044","58045","58046","58047","58048","58049","58050","58051","58052","58053","58054","58055","58056","58057","58058","58059","58060","58061","58062","58063","58064","58065","58066","58067","58068","58069","58070","58071","58072","58073","58074","58075","58076","58077","58078","58079","58080","58081","58082","58083","58084","58085","58086","58087","58088","58089","58090","58091","58092","58093","58094","58095","58096","58097","58098","58099","58100","58101","58102","58103","58104","58105","58106","58107","58108","58109","58110","58111","58112","58113","58114","58115","58116","58117","58118","58119","58120","58121","58122","58123","58124","58125","58126","58127","58128","58129","58130","58131","58132","58133","58134","58135","58136","58137","58138","58139","58140","58141","58142","58143","58144","58145","58146","58147","58148","58149","58150","58151","58152","58153","58154","58155","58156","58157","58158","58159","58160","58161","58162","58163","58164","58165","58166","58167","58168","58169","58170","58171","58172","58173","58174","58175","58176","58177","58178","58179","58180","58181","58182","58183","58184","58185","58186","58187","58188","58189","58190","58191","58192","58193","58194","58195","58196","58197","58198","58199","58200","58201","58202","58203","58204","58205","58206","58207","58208","58209","58210","58211","58212","58213","58214","58215","58216","58217","58218","58219","58220","58221","58222","58223","58224","58225","58226","58227","58228","58229","58230","58231","58232","58233","58234","58235","58236","58237","58238","58239","58240","58241","58242","58243","58244","58245","58246","58247","58248","58249","58250","58251","58252","58253","58254","58255","58256","58257","58258","58259","58260","58261","58262","58263","58264","58265","58266","58267","58268","58269","58270","58271","58272","58273","58274","58275","58276","58277","58278","58279","58280","58281","58282","58283","58284","58285","58286","58287","58288","58289","58290","58291","58292","58293","58294","58295","58296","58297","58298","58299","58300","58301","58302","58303","58304","58305","58306","58307","58308","58309","58310","58311","58312","58313","58314","58315","58316","58317","58318","58319","58320","58321","58322","58323","58324","58325","58326","58327","58328","58329","58330","58331","58332","58333","58334","58335","58336","58337","58338","58339","58340","58341","58342","58343","58344","58345","58346","58347","58348","58349","58350","58351","58352","58353","58354","58355","58356","58357","58358","58359","58360","58361","58362","58363","58364","58365","58366","58367","58368","58369","58370","58371","58372","58373","58374","58375","58376","58377","58378","58379","58380","58381","58382","58383","58384","58385","58386","58387","58388","58389","58390","58391","58392","58393","58394","58395","58396","58397","58398","58399","58400","58401","58402","58403","58404","58405","58406","58407","58408","58409","58410","58411","58412","58413","58414","58415","58416","58417","58418","58419","58420","58421","58422","58423","58424","58425","58426","58427","58428","58429","58430","58431","58432","58433","58434","58435","58436","58437","58438","58439","58440","58441","58442","58443","58444","58445","58446","58447","58448","58449","58450","58451","58452","58453","58454","58455","58456","58457","58458","58459","58460","58461","58462","58463","58464","58465","58466","58467","58468","58469","58470","58471","58472","58473","58474","58475","58476","58477","58478","58479","58480","58481","58482","58483","58484","58485","58486","58487","58488","58489","58490","58491","58492","58493","58494","58495","58496","58497","58498","58499","58500","58501","58502","58503","58504","58505","58506","58507","58508","58509","58510","58511","58512","58513","58514","58515","58516","58517","58518","58519","58520","58521","58522","58523","58524","58525","58526","58527","58528","58529","58530","58531","58532","58533","58534","58535","58536","58537","58538","58539","58540","58541","58542","58543","58544","58545","58546","58547","58548","58549","58550","58551","58552","58553","58554","58555","58556","58557","58558","58559","58560","58561","58562","58563","58564","58565","58566","58567","58568","58569","58570","58571","58572","58573","58574","58575","58576","58577","58578","58579","58580","58581","58582","58583","58584","58585","58586","58587","58588","58589","58590","58591","58592","58593","58594","58595","58596","58597","58598","58599","58600","58601","58602","58603","58604","58605","58606","58607","58608","58609","58610","58611","58612","58613","58614","58615","58616","58617","58618","58619","58620","58621","58622","58623","58624","58625","58626","58627","58628","58629","58630","58631","58632","58633","58634","58635","58636","58637","58638","58639","58640","58641","58642","58643","58644","58645","58646","58647","58648","58649","58650","58651","58652","58653","58654","58655","58656","58657","58658","58659","58660","58661","58662","58663","58664","58665","58666","58667","58668","58669","58670","58671","58672","58673","58674","58675","58676","58677","58678","58679","58680","58681","58682","58683","58684","58685","58686","58687","58688","58689","58690","58691","58692","58693","58694","58695","58696","58697","58698","58699","58700","58701","58702","58703","58704","58705","58706","58707","58708","58709","58710","58711","58712","58713","58714","58715","58716","58717","58718","58719","58720","58721","58722","58723","58724","58725","58726","58727","58728","58729","58730","58731","58732","58733","58734","58735","58736","58737","58738","58739","58740","58741","58742","58743","58744","58745","58746","58747","58748","58749","58750","58751","58752","58753","58754","58755","58756","58757","58758","58759","58760","58761","58762","58763","58764","58765","58766","58767","58768","58769","58770","58771","58772","58773","58774","58775","58776","58777","58778","58779","58780","58781","58782","58783","58784","58785","58786","58787","58788","58789","58790","58791","58792","58793","58794","58795","58796","58797","58798","58799","58800","58801","58802","58803","58804","58805","58806","58807","58808","58809","58810","58811","58812","58813","58814","58815","58816","58817","58818","58819","58820","58821","58822","58823","58824","58825","58826","58827","58828","58829","58830","58831","58832","58833","58834","58835","58836","58837","58838","58839","58840","58841","58842","58843","58844","58845","58846","58847","58848","58849","58850","58851","58852","58853","58854","58855","58856","58857","58858","58859","58860","58861","58862","58863","58864","58865","58866","58867","58868","58869","58870","58871","58872","58873","58874","58875","58876","58877","58878","58879","58880","58881","58882","58883","58884","58885","58886","58887","58888","58889","58890","58891","58892","58893","58894","58895","58896","58897","58898","58899","58900","58901","58902","58903","58904","58905","58906","58907","58908","58909","58910","58911","58912","58913","58914","58915","58916","58917","58918","58919","58920","58921","58922","58923","58924","58925","58926","58927","58928","58929","58930","58931","58932","58933","58934","58935","58936","58937","58938","58939","58940","58941","58942","58943","58944","58945","58946","58947","58948","58949","58950","58951","58952","58953","58954","58955","58956","58957","58958","58959","58960","58961","58962","58963","58964","58965","58966","58967","58968","58969","58970","58971","58972","58973","58974","58975","58976","58977","58978","58979","58980","58981","58982","58983","58984","58985","58986","58987","58988","58989","58990","58991","58992","58993","58994","58995","58996","58997","58998","58999","59000","59001","59002","59003","59004","59005","59006","59007","59008","59009","59010","59011","59012","59013","59014","59015","59016","59017","59018","59019","59020","59021","59022","59023","59024","59025","59026","59027","59028","59029","59030","59031","59032","59033","59034","59035","59036","59037","59038","59039","59040","59041","59042","59043","59044","59045","59046","59047","59048","59049","59050","59051","59052","59053","59054","59055","59056","59057","59058","59059","59060","59061","59062","59063","59064","59065","59066","59067","59068","59069","59070","59071","59072","59073","59074","59075","59076","59077","59078","59079","59080","59081","59082","59083","59084","59085","59086","59087","59088","59089","59090","59091","59092","59093","59094","59095","59096","59097","59098","59099","59100","59101","59102","59103","59104","59105","59106","59107","59108","59109","59110","59111","59112","59113","59114","59115","59116","59117","59118","59119","59120","59121","59122","59123","59124","59125","59126","59127","59128","59129","59130","59131","59132","59133","59134","59135","59136","59137","59138","59139","59140","59141","59142","59143","59144","59145","59146","59147","59148","59149","59150","59151","59152","59153","59154","59155","59156","59157","59158","59159","59160","59161","59162","59163","59164","59165","59166","59167","59168","59169","59170","59171","59172","59173","59174","59175","59176","59177","59178","59179","59180","59181","59182","59183","59184","59185","59186","59187","59188","59189","59190","59191","59192","59193","59194","59195","59196","59197","59198","59199","59200","59201","59202","59203","59204","59205","59206","59207","59208","59209","59210","59211","59212","59213","59214","59215","59216","59217","59218","59219","59220","59221","59222","59223","59224","59225","59226","59227","59228","59229","59230","59231","59232","59233","59234","59235","59236","59237","59238","59239","59240","59241","59242","59243","59244","59245","59246","59247","59248","59249","59250","59251","59252","59253","59254","59255","59256","59257","59258","59259","59260","59261","59262","59263","59264","59265","59266","59267","59268","59269","59270","59271","59272","59273","59274","59275","59276","59277","59278","59279","59280","59281","59282","59283","59284","59285","59286","59287","59288","59289","59290","59291","59292","59293","59294","59295","59296","59297","59298","59299","59300","59301","59302","59303","59304","59305","59306","59307","59308","59309","59310","59311","59312","59313","59314","59315","59316","59317","59318","59319","59320","59321","59322","59323","59324","59325","59326","59327","59328","59329","59330","59331","59332","59333","59334","59335","59336","59337","59338","59339","59340","59341","59342","59343","59344","59345","59346","59347","59348","59349","59350","59351","59352","59353","59354","59355","59356","59357","59358","59359","59360","59361","59362","59363","59364","59365","59366","59367","59368","59369","59370","59371","59372","59373","59374","59375","59376","59377","59378","59379","59380","59381","59382","59383","59384","59385","59386","59387","59388","59389","59390","59391","59392","59393","59394","59395","59396","59397","59398","59399","59400","59401","59402","59403","59404","59405","59406","59407","59408","59409","59410","59411","59412","59413","59414","59415","59416","59417","59418","59419","59420","59421","59422","59423","59424","59425","59426","59427","59428","59429","59430","59431","59432","59433","59434","59435","59436","59437","59438","59439","59440","59441","59442","59443","59444","59445","59446","59447","59448","59449","59450","59451","59452","59453","59454","59455","59456","59457","59458","59459","59460","59461","59462","59463","59464","59465","59466","59467","59468","59469","59470","59471","59472","59473","59474","59475","59476","59477","59478","59479","59480","59481","59482","59483","59484","59485","59486","59487","59488","59489","59490","59491","59492","59493","59494","59495","59496","59497","59498","59499","59500","59501","59502","59503","59504","59505","59506","59507","59508","59509","59510","59511","59512","59513","59514","59515","59516","59517","59518","59519","59520","59521","59522","59523","59524","59525","59526","59527","59528","59529","59530","59531","59532","59533","59534","59535","59536","59537","59538","59539","59540","59541","59542","59543","59544","59545","59546","59547","59548","59549","59550","59551","59552","59553","59554","59555","59556","59557","59558","59559","59560","59561","59562","59563","59564","59565","59566","59567","59568","59569","59570","59571","59572","59573","59574","59575","59576","59577","59578","59579","59580","59581","59582","59583","59584","59585","59586","59587","59588","59589","59590","59591","59592","59593","59594","59595","59596","59597","59598","59599","59600","59601","59602","59603","59604","59605","59606","59607","59608","59609","59610","59611","59612","59613","59614","59615","59616","59617","59618","59619","59620","59621","59622","59623","59624","59625","59626","59627","59628","59629","59630","59631","59632","59633","59634","59635","59636","59637","59638","59639","59640","59641","59642","59643","59644","59645","59646","59647","59648","59649","59650","59651","59652","59653","59654","59655","59656","59657","59658","59659","59660","59661","59662","59663","59664","59665","59666","59667","59668","59669","59670","59671","59672","59673","59674","59675","59676","59677","59678","59679","59680","59681","59682","59683","59684","59685","59686","59687","59688","59689","59690","59691","59692","59693","59694","59695","59696","59697","59698","59699","59700","59701","59702","59703","59704","59705","59706","59707","59708","59709","59710","59711","59712","59713","59714","59715","59716","59717","59718","59719","59720","59721","59722","59723","59724","59725","59726","59727","59728","59729","59730","59731","59732","59733","59734","59735","59736","59737","59738","59739","59740","59741","59742","59743","59744","59745","59746","59747","59748","59749","59750","59751","59752","59753","59754","59755","59756","59757","59758","59759","59760","59761","59762","59763","59764","59765","59766","59767","59768","59769","59770","59771","59772","59773","59774","59775","59776","59777","59778","59779","59780","59781","59782","59783","59784","59785","59786","59787","59788","59789","59790","59791","59792","59793","59794","59795","59796","59797","59798","59799","59800","59801","59802","59803","59804","59805","59806","59807","59808","59809","59810","59811","59812","59813","59814","59815","59816","59817","59818","59819","59820","59821","59822","59823","59824","59825","59826","59827","59828","59829","59830","59831","59832","59833","59834","59835","59836","59837","59838","59839","59840","59841","59842","59843","59844","59845","59846","59847","59848","59849","59850","59851","59852","59853","59854","59855","59856","59857","59858","59859","59860","59861","59862","59863","59864","59865","59866","59867","59868","59869","59870","59871","59872","59873","59874","59875","59876","59877","59878","59879","59880","59881","59882","59883","59884","59885","59886","59887","59888","59889","59890","59891","59892","59893","59894","59895","59896","59897","59898","59899","59900","59901","59902","59903","59904","59905","59906","59907","59908","59909","59910","59911","59912","59913","59914","59915","59916","59917","59918","59919","59920","59921","59922","59923","59924","59925","59926","59927","59928","59929","59930","59931","59932","59933","59934","59935","59936","59937","59938","59939","59940","59941","59942","59943","59944","59945","59946","59947","59948","59949","59950","59951","59952","59953","59954","59955","59956","59957","59958","59959","59960","59961","59962","59963","59964","59965","59966","59967","59968","59969","59970","59971","59972","59973","59974","59975","59976","59977","59978","59979","59980","59981","59982","59983","59984","59985","59986","59987","59988","59989","59990","59991","59992","59993","59994","59995","59996","59997","59998","59999","60000","60001","60002","60003","60004","60005","60006","60007","60008","60009","60010","60011","60012","60013","60014","60015","60016","60017","60018","60019","60020","60021","60022","60023","60024","60025","60026","60027","60028","60029","60030","60031","60032","60033","60034","60035","60036","60037","60038","60039","60040","60041","60042","60043","60044","60045","60046","60047","60048","60049","60050","60051","60052","60053","60054","60055","60056","60057","60058","60059","60060","60061","60062","60063","60064","60065","60066","60067","60068","60069","60070","60071","60072","60073","60074","60075","60076","60077","60078","60079","60080","60081","60082","60083","60084","60085","60086","60087","60088","60089","60090","60091","60092","60093","60094","60095","60096","60097","60098","60099","60100","60101","60102","60103","60104","60105","60106","60107","60108","60109","60110","60111","60112","60113","60114","60115","60116","60117","60118","60119","60120","60121","60122","60123","60124","60125","60126","60127","60128","60129","60130","60131","60132","60133","60134","60135","60136","60137","60138","60139","60140","60141","60142","60143","60144","60145","60146","60147","60148","60149","60150","60151","60152","60153","60154","60155","60156","60157","60158","60159","60160","60161","60162","60163","60164","60165","60166","60167","60168","60169","60170","60171","60172","60173","60174","60175","60176","60177","60178","60179","60180","60181","60182","60183","60184","60185","60186","60187","60188","60189","60190","60191","60192","60193","60194","60195","60196","60197","60198","60199","60200","60201","60202","60203","60204","60205","60206","60207","60208","60209","60210","60211","60212","60213","60214","60215","60216","60217","60218","60219","60220","60221","60222","60223","60224","60225","60226","60227","60228","60229","60230","60231","60232","60233","60234","60235","60236","60237","60238","60239","60240","60241","60242","60243","60244","60245","60246","60247","60248","60249","60250","60251","60252","60253","60254","60255","60256","60257","60258","60259","60260","60261","60262","60263","60264","60265","60266","60267","60268","60269","60270","60271","60272","60273","60274","60275","60276","60277","60278","60279","60280","60281","60282","60283","60284","60285","60286","60287","60288","60289","60290","60291","60292","60293","60294","60295","60296","60297","60298","60299","60300","60301","60302","60303","60304","60305","60306","60307","60308","60309","60310","60311","60312","60313","60314","60315","60316","60317","60318","60319","60320","60321","60322","60323","60324","60325","60326","60327","60328","60329","60330","60331","60332","60333","60334","60335","60336","60337","60338","60339","60340","60341","60342","60343","60344","60345","60346","60347","60348","60349","60350","60351","60352","60353","60354","60355","60356","60357","60358","60359","60360","60361","60362","60363","60364","60365","60366","60367","60368","60369","60370","60371","60372","60373","60374","60375","60376","60377","60378","60379","60380","60381","60382","60383","60384","60385","60386","60387","60388","60389","60390","60391","60392","60393","60394","60395","60396","60397","60398","60399","60400","60401","60402","60403","60404","60405","60406","60407","60408","60409","60410","60411","60412","60413","60414","60415","60416","60417","60418","60419","60420","60421","60422","60423","60424","60425","60426","60427","60428","60429","60430","60431","60432","60433","60434","60435","60436","60437","60438","60439","60440","60441","60442","60443","60444","60445","60446","60447","60448","60449","60450","60451","60452","60453","60454","60455","60456","60457","60458","60459","60460","60461","60462","60463","60464","60465","60466","60467","60468","60469","60470","60471","60472","60473","60474","60475","60476","60477","60478","60479","60480","60481","60482","60483","60484","60485","60486","60487","60488","60489","60490","60491","60492","60493","60494","60495","60496","60497","60498","60499","60500","60501","60502","60503","60504","60505","60506","60507","60508","60509","60510","60511","60512","60513","60514","60515","60516","60517","60518","60519","60520","60521","60522","60523","60524","60525","60526","60527","60528","60529","60530","60531","60532","60533","60534","60535","60536","60537","60538","60539","60540","60541","60542","60543","60544","60545","60546","60547","60548","60549","60550","60551","60552","60553","60554","60555","60556","60557","60558","60559","60560","60561","60562","60563","60564","60565","60566","60567","60568","60569","60570","60571","60572","60573","60574","60575","60576","60577","60578","60579","60580","60581","60582","60583","60584","60585","60586","60587","60588","60589","60590","60591","60592","60593","60594","60595","60596","60597","60598","60599","60600","60601","60602","60603","60604","60605","60606","60607","60608","60609","60610","60611","60612","60613","60614","60615","60616","60617","60618","60619","60620","60621","60622","60623","60624","60625","60626","60627","60628","60629","60630","60631","60632","60633","60634","60635","60636","60637","60638","60639","60640","60641","60642","60643","60644","60645","60646","60647","60648","60649","60650","60651","60652","60653","60654","60655","60656","60657","60658","60659","60660","60661","60662","60663","60664","60665","60666","60667","60668","60669","60670","60671","60672","60673","60674","60675","60676","60677","60678","60679","60680","60681","60682","60683","60684","60685","60686","60687","60688","60689","60690","60691","60692","60693","60694","60695","60696","60697","60698","60699","60700","60701","60702","60703","60704","60705","60706","60707","60708","60709","60710","60711","60712","60713","60714","60715","60716","60717","60718","60719","60720","60721","60722","60723","60724","60725","60726","60727","60728","60729","60730","60731","60732","60733","60734","60735","60736","60737","60738","60739","60740","60741","60742","60743","60744","60745","60746","60747","60748","60749","60750","60751","60752","60753","60754","60755","60756","60757","60758","60759","60760","60761","60762","60763","60764","60765","60766","60767","60768","60769","60770","60771","60772","60773","60774","60775","60776","60777","60778","60779","60780","60781","60782","60783","60784","60785","60786","60787","60788","60789","60790","60791","60792","60793","60794","60795","60796","60797","60798","60799","60800","60801","60802","60803","60804","60805","60806","60807","60808","60809","60810","60811","60812","60813","60814","60815","60816","60817","60818","60819","60820","60821","60822","60823","60824","60825","60826","60827","60828","60829","60830","60831","60832","60833","60834","60835","60836","60837","60838","60839","60840","60841","60842","60843","60844","60845","60846","60847","60848","60849","60850","60851","60852","60853","60854","60855","60856","60857","60858","60859","60860","60861","60862","60863","60864","60865","60866","60867","60868","60869","60870","60871","60872","60873","60874","60875","60876","60877","60878","60879","60880","60881","60882","60883","60884","60885","60886","60887","60888","60889","60890","60891","60892","60893","60894","60895","60896","60897","60898","60899","60900","60901","60902","60903","60904","60905","60906","60907","60908","60909","60910","60911","60912","60913","60914","60915","60916","60917","60918","60919","60920","60921","60922","60923","60924","60925","60926","60927","60928","60929","60930","60931","60932","60933","60934","60935","60936","60937","60938","60939","60940","60941","60942","60943","60944","60945","60946","60947","60948","60949","60950","60951","60952","60953","60954","60955","60956","60957","60958","60959","60960","60961","60962","60963","60964","60965","60966","60967","60968","60969","60970","60971","60972","60973","60974","60975","60976","60977","60978","60979","60980","60981","60982","60983","60984","60985","60986","60987","60988","60989","60990","60991","60992","60993","60994","60995","60996","60997","60998","60999","61000","61001","61002","61003","61004","61005","61006","61007","61008","61009","61010","61011","61012","61013","61014","61015","61016","61017","61018","61019","61020","61021","61022","61023","61024","61025","61026","61027","61028","61029","61030","61031","61032","61033","61034","61035","61036","61037","61038","61039","61040","61041","61042","61043","61044","61045","61046","61047","61048","61049","61050","61051","61052","61053","61054","61055","61056","61057","61058","61059","61060","61061","61062","61063","61064","61065","61066","61067","61068","61069","61070","61071","61072","61073","61074","61075","61076","61077","61078","61079","61080","61081","61082","61083","61084","61085","61086","61087","61088","61089","61090","61091","61092","61093","61094","61095","61096","61097","61098","61099","61100","61101","61102","61103","61104","61105","61106","61107","61108","61109","61110","61111","61112","61113","61114","61115","61116","61117","61118","61119","61120","61121","61122","61123","61124","61125","61126","61127","61128","61129","61130","61131","61132","61133","61134","61135","61136","61137","61138","61139","61140","61141","61142","61143","61144","61145","61146","61147","61148","61149","61150","61151","61152","61153","61154","61155","61156","61157","61158","61159","61160","61161","61162","61163","61164","61165","61166","61167","61168","61169","61170","61171","61172","61173","61174","61175","61176","61177","61178","61179","61180","61181","61182","61183","61184","61185","61186","61187","61188","61189","61190","61191","61192","61193","61194","61195","61196","61197","61198","61199","61200","61201","61202","61203","61204","61205","61206","61207","61208","61209","61210","61211","61212","61213","61214","61215","61216","61217","61218","61219","61220","61221","61222","61223","61224","61225","61226","61227","61228","61229","61230","61231","61232","61233","61234","61235","61236","61237","61238","61239","61240","61241","61242","61243","61244","61245","61246","61247","61248","61249","61250","61251","61252","61253","61254","61255","61256","61257","61258","61259","61260","61261","61262","61263","61264","61265","61266","61267","61268","61269","61270","61271","61272","61273","61274","61275","61276","61277","61278","61279","61280","61281","61282","61283","61284","61285","61286","61287","61288","61289","61290","61291","61292","61293","61294","61295","61296","61297","61298","61299","61300","61301","61302","61303","61304","61305","61306","61307","61308","61309","61310","61311","61312","61313","61314","61315","61316","61317","61318","61319","61320","61321","61322","61323","61324","61325","61326","61327","61328","61329","61330","61331","61332","61333","61334","61335","61336","61337","61338","61339","61340","61341","61342","61343","61344","61345","61346","61347","61348","61349","61350","61351","61352","61353","61354","61355","61356","61357","61358","61359","61360","61361","61362","61363","61364","61365","61366","61367","61368","61369","61370","61371","61372","61373","61374","61375","61376","61377","61378","61379","61380","61381","61382","61383","61384","61385","61386","61387","61388","61389","61390","61391","61392","61393","61394","61395","61396","61397","61398","61399","61400","61401","61402","61403","61404","61405","61406","61407","61408","61409","61410","61411","61412","61413","61414","61415","61416","61417","61418","61419","61420","61421","61422","61423","61424","61425","61426","61427","61428","61429","61430","61431","61432","61433","61434","61435","61436","61437","61438","61439","61440","61441","61442","61443","61444","61445","61446","61447","61448","61449","61450","61451","61452","61453","61454","61455","61456","61457","61458","61459","61460","61461","61462","61463","61464","61465","61466","61467","61468","61469","61470","61471","61472","61473","61474","61475","61476","61477","61478","61479","61480","61481","61482","61483","61484","61485","61486","61487","61488","61489","61490","61491","61492","61493","61494","61495","61496","61497","61498","61499","61500","61501","61502","61503","61504","61505","61506","61507","61508","61509","61510","61511","61512","61513","61514","61515","61516","61517","61518","61519","61520","61521","61522","61523","61524","61525","61526","61527","61528","61529","61530","61531","61532","61533","61534","61535","61536","61537","61538","61539","61540","61541","61542","61543","61544","61545","61546","61547","61548","61549","61550","61551","61552","61553","61554","61555","61556","61557","61558","61559","61560","61561","61562","61563","61564","61565","61566","61567","61568","61569","61570","61571","61572","61573","61574","61575","61576","61577","61578","61579","61580","61581","61582","61583","61584","61585","61586","61587","61588","61589","61590","61591","61592","61593","61594","61595","61596","61597","61598","61599","61600","61601","61602","61603","61604","61605","61606","61607","61608","61609","61610","61611","61612","61613","61614","61615","61616","61617","61618","61619","61620","61621","61622","61623","61624","61625","61626","61627","61628","61629","61630","61631","61632","61633","61634","61635","61636","61637","61638","61639","61640","61641","61642","61643","61644","61645","61646","61647","61648","61649","61650","61651","61652","61653","61654","61655","61656","61657","61658","61659","61660","61661","61662","61663","61664","61665","61666","61667","61668","61669","61670","61671","61672","61673","61674","61675","61676","61677","61678","61679","61680","61681","61682","61683","61684","61685","61686","61687","61688","61689","61690","61691","61692","61693","61694","61695","61696","61697","61698","61699","61700","61701","61702","61703","61704","61705","61706","61707","61708","61709","61710","61711","61712","61713","61714","61715","61716","61717","61718","61719","61720","61721","61722","61723","61724","61725","61726","61727","61728","61729","61730","61731","61732","61733","61734","61735","61736","61737","61738","61739","61740","61741","61742","61743","61744","61745","61746","61747","61748","61749","61750","61751","61752","61753","61754","61755","61756","61757","61758","61759","61760","61761","61762","61763","61764","61765","61766","61767","61768","61769","61770","61771","61772","61773","61774","61775","61776","61777","61778","61779","61780","61781","61782","61783","61784","61785","61786","61787","61788","61789","61790","61791","61792","61793","61794","61795","61796","61797","61798","61799","61800","61801","61802","61803","61804","61805","61806","61807","61808","61809","61810","61811","61812","61813","61814","61815","61816","61817","61818","61819","61820","61821","61822","61823","61824","61825","61826","61827","61828","61829","61830","61831","61832","61833","61834","61835","61836","61837","61838","61839","61840","61841","61842","61843","61844","61845","61846","61847","61848","61849","61850","61851","61852","61853","61854","61855","61856","61857","61858","61859","61860","61861","61862","61863","61864","61865","61866","61867","61868","61869","61870","61871","61872","61873","61874","61875","61876","61877","61878","61879","61880","61881","61882","61883","61884","61885","61886","61887","61888","61889","61890","61891","61892","61893","61894","61895","61896","61897","61898","61899","61900","61901","61902","61903","61904","61905","61906","61907","61908","61909","61910","61911","61912","61913","61914","61915","61916","61917","61918","61919","61920","61921","61922","61923","61924","61925","61926","61927","61928","61929","61930","61931","61932","61933","61934","61935","61936","61937","61938","61939","61940","61941","61942","61943","61944","61945","61946","61947","61948","61949","61950","61951","61952","61953","61954","61955","61956","61957","61958","61959","61960","61961","61962","61963","61964","61965","61966","61967","61968","61969","61970","61971","61972","61973","61974","61975","61976","61977","61978","61979","61980","61981","61982","61983","61984","61985","61986","61987","61988","61989","61990","61991","61992","61993","61994","61995","61996","61997","61998","61999","62000","62001","62002","62003","62004","62005","62006","62007","62008","62009","62010","62011","62012","62013","62014","62015","62016","62017","62018","62019","62020","62021","62022","62023","62024","62025","62026","62027","62028","62029","62030","62031","62032","62033","62034","62035","62036","62037","62038","62039","62040","62041","62042","62043","62044","62045","62046","62047","62048","62049","62050","62051","62052","62053","62054","62055","62056","62057","62058","62059","62060","62061","62062","62063","62064","62065","62066","62067","62068","62069","62070","62071","62072","62073","62074","62075","62076","62077","62078","62079","62080","62081","62082","62083","62084","62085","62086","62087","62088","62089","62090","62091","62092","62093","62094","62095","62096","62097","62098","62099","62100","62101","62102","62103","62104","62105","62106","62107","62108","62109","62110","62111","62112","62113","62114","62115","62116","62117","62118","62119","62120","62121","62122","62123","62124","62125","62126","62127","62128","62129","62130","62131","62132","62133","62134","62135","62136","62137","62138","62139","62140","62141","62142","62143","62144","62145","62146","62147","62148","62149","62150","62151","62152","62153","62154","62155","62156","62157","62158","62159","62160","62161","62162","62163","62164","62165","62166","62167","62168","62169","62170","62171","62172","62173","62174","62175","62176","62177","62178","62179","62180","62181","62182","62183","62184","62185","62186","62187","62188","62189","62190","62191","62192","62193","62194","62195","62196","62197","62198","62199","62200","62201","62202","62203","62204","62205","62206","62207","62208","62209","62210","62211","62212","62213","62214","62215","62216","62217","62218","62219","62220","62221","62222","62223","62224","62225","62226","62227","62228","62229","62230","62231","62232","62233","62234","62235","62236","62237","62238","62239","62240","62241","62242","62243","62244","62245","62246","62247","62248","62249","62250","62251","62252","62253","62254","62255","62256","62257","62258","62259","62260","62261","62262","62263","62264","62265","62266","62267","62268","62269","62270","62271","62272","62273","62274","62275","62276","62277","62278","62279","62280","62281","62282","62283","62284","62285","62286","62287","62288","62289","62290","62291","62292","62293","62294","62295","62296","62297","62298","62299","62300","62301","62302","62303","62304","62305","62306","62307","62308","62309","62310","62311","62312","62313","62314","62315","62316","62317","62318","62319","62320","62321","62322","62323","62324","62325","62326","62327","62328","62329","62330","62331","62332","62333","62334","62335","62336","62337","62338","62339","62340","62341","62342","62343","62344","62345","62346","62347","62348","62349","62350","62351","62352","62353","62354","62355","62356","62357","62358","62359","62360","62361","62362","62363","62364","62365","62366","62367","62368","62369","62370","62371","62372","62373","62374","62375","62376","62377","62378","62379","62380","62381","62382","62383","62384","62385","62386","62387","62388","62389","62390","62391","62392","62393","62394","62395","62396","62397","62398","62399","62400","62401","62402","62403","62404","62405","62406","62407","62408","62409","62410","62411","62412","62413","62414","62415","62416","62417","62418","62419","62420","62421","62422","62423","62424","62425","62426","62427","62428","62429","62430","62431","62432","62433","62434","62435","62436","62437","62438","62439","62440","62441","62442","62443","62444","62445","62446","62447","62448","62449","62450","62451","62452","62453","62454","62455","62456","62457","62458","62459","62460","62461","62462","62463","62464","62465","62466","62467","62468","62469","62470","62471","62472","62473","62474","62475","62476","62477","62478","62479","62480","62481","62482","62483","62484","62485","62486","62487","62488","62489","62490","62491","62492","62493","62494","62495","62496","62497","62498","62499","62500","62501","62502","62503","62504","62505","62506","62507","62508","62509","62510","62511","62512","62513","62514","62515","62516","62517","62518","62519","62520","62521","62522","62523","62524","62525","62526","62527","62528","62529","62530","62531","62532","62533","62534","62535","62536","62537","62538","62539","62540","62541","62542","62543","62544","62545","62546","62547","62548","62549","62550","62551","62552","62553","62554","62555","62556","62557","62558","62559","62560","62561","62562","62563","62564","62565","62566","62567","62568","62569","62570","62571","62572","62573","62574","62575","62576","62577","62578","62579","62580","62581","62582","62583","62584","62585","62586","62587","62588","62589","62590","62591","62592","62593","62594","62595","62596","62597","62598","62599","62600","62601","62602","62603","62604","62605","62606","62607","62608","62609","62610","62611","62612","62613","62614","62615","62616","62617","62618","62619","62620","62621","62622","62623","62624","62625","62626","62627","62628","62629","62630","62631","62632","62633","62634","62635","62636","62637","62638","62639","62640","62641","62642","62643","62644","62645","62646","62647","62648","62649","62650","62651","62652","62653","62654","62655","62656","62657","62658","62659","62660","62661","62662","62663","62664","62665","62666","62667","62668","62669","62670","62671","62672","62673","62674","62675","62676","62677","62678","62679","62680","62681","62682","62683","62684","62685","62686","62687","62688","62689","62690","62691","62692","62693","62694","62695","62696","62697","62698","62699","62700","62701","62702","62703","62704","62705","62706","62707","62708","62709","62710","62711","62712","62713","62714","62715","62716","62717","62718","62719","62720","62721","62722","62723","62724","62725","62726","62727","62728","62729","62730","62731","62732","62733","62734","62735","62736","62737","62738","62739","62740","62741","62742","62743","62744","62745","62746","62747","62748","62749","62750","62751","62752","62753","62754","62755","62756","62757","62758","62759","62760","62761","62762","62763","62764","62765","62766","62767","62768","62769","62770","62771","62772","62773","62774","62775","62776","62777","62778","62779","62780","62781","62782","62783","62784","62785","62786","62787","62788","62789","62790","62791","62792","62793","62794","62795","62796","62797","62798","62799","62800","62801","62802","62803","62804","62805","62806","62807","62808","62809","62810","62811","62812","62813","62814","62815","62816","62817","62818","62819","62820","62821","62822","62823","62824","62825","62826","62827","62828","62829","62830","62831","62832","62833","62834","62835","62836","62837","62838","62839","62840","62841","62842","62843","62844","62845","62846","62847","62848","62849","62850","62851","62852","62853","62854","62855","62856","62857","62858","62859","62860","62861","62862","62863","62864","62865","62866","62867","62868","62869","62870","62871","62872","62873","62874","62875","62876","62877","62878","62879","62880","62881","62882","62883","62884","62885","62886","62887","62888","62889","62890","62891","62892","62893","62894","62895","62896","62897","62898","62899","62900","62901","62902","62903","62904","62905","62906","62907","62908","62909","62910","62911","62912","62913","62914","62915","62916","62917","62918","62919","62920","62921","62922","62923","62924","62925","62926","62927","62928","62929","62930","62931","62932","62933","62934","62935","62936","62937","62938","62939","62940","62941","62942","62943","62944","62945","62946","62947","62948","62949","62950","62951","62952","62953","62954","62955","62956","62957","62958","62959","62960","62961","62962","62963","62964","62965","62966","62967","62968","62969","62970","62971","62972","62973","62974","62975","62976","62977","62978","62979","62980","62981","62982","62983","62984","62985","62986","62987","62988","62989","62990","62991","62992","62993","62994","62995","62996","62997","62998","62999","63000","63001","63002","63003","63004","63005","63006","63007","63008","63009","63010","63011","63012","63013","63014","63015","63016","63017","63018","63019","63020","63021","63022","63023","63024","63025","63026","63027","63028","63029","63030","63031","63032","63033","63034","63035","63036","63037","63038","63039","63040","63041","63042","63043","63044","63045","63046","63047","63048","63049","63050","63051","63052","63053","63054","63055","63056","63057","63058","63059","63060","63061","63062","63063","63064","63065","63066","63067","63068","63069","63070","63071","63072","63073","63074","63075","63076","63077","63078","63079","63080","63081","63082","63083","63084","63085","63086","63087","63088","63089","63090","63091","63092","63093","63094","63095","63096","63097","63098","63099","63100","63101","63102","63103","63104","63105","63106","63107","63108","63109","63110","63111","63112","63113","63114","63115","63116","63117","63118","63119","63120","63121","63122","63123","63124","63125","63126","63127","63128","63129","63130","63131","63132","63133","63134","63135","63136","63137","63138","63139","63140","63141","63142","63143","63144","63145","63146","63147","63148","63149","63150","63151","63152","63153","63154","63155","63156","63157","63158","63159","63160","63161","63162","63163","63164","63165","63166","63167","63168","63169","63170","63171","63172","63173","63174","63175","63176","63177","63178","63179","63180","63181","63182","63183","63184","63185","63186","63187","63188","63189","63190","63191","63192","63193","63194","63195","63196","63197","63198","63199","63200","63201","63202","63203","63204","63205","63206","63207","63208","63209","63210","63211","63212","63213","63214","63215","63216","63217","63218","63219","63220","63221","63222","63223","63224","63225","63226","63227","63228","63229","63230","63231","63232","63233","63234","63235","63236","63237","63238","63239","63240","63241","63242","63243","63244","63245","63246","63247","63248","63249","63250","63251","63252","63253","63254","63255","63256","63257","63258","63259","63260","63261","63262","63263","63264","63265","63266","63267","63268","63269","63270","63271","63272","63273","63274","63275","63276","63277","63278","63279","63280","63281","63282","63283","63284","63285","63286","63287","63288","63289","63290","63291","63292","63293","63294","63295","63296","63297","63298","63299","63300","63301","63302","63303","63304","63305","63306","63307","63308","63309","63310","63311","63312","63313","63314","63315","63316","63317","63318","63319","63320","63321","63322","63323","63324","63325","63326","63327","63328","63329","63330","63331","63332","63333","63334","63335","63336","63337","63338","63339","63340","63341","63342","63343","63344","63345","63346","63347","63348","63349","63350","63351","63352","63353","63354","63355","63356","63357","63358","63359","63360","63361","63362","63363","63364","63365","63366","63367","63368","63369","63370","63371","63372","63373","63374","63375","63376","63377","63378","63379","63380","63381","63382","63383","63384","63385","63386","63387","63388","63389","63390","63391","63392","63393","63394","63395","63396","63397","63398","63399","63400","63401","63402","63403","63404","63405","63406","63407","63408","63409","63410","63411","63412","63413","63414","63415","63416","63417","63418","63419","63420","63421","63422","63423","63424","63425","63426","63427","63428","63429","63430","63431","63432","63433","63434","63435","63436","63437","63438","63439","63440","63441","63442","63443","63444","63445","63446","63447","63448","63449","63450","63451","63452","63453","63454","63455","63456","63457","63458","63459","63460","63461","63462","63463","63464","63465","63466","63467","63468","63469","63470","63471","63472","63473","63474","63475","63476","63477","63478","63479","63480","63481","63482","63483","63484","63485","63486","63487","63488","63489","63490","63491","63492","63493","63494","63495","63496","63497","63498","63499","63500","63501","63502","63503","63504","63505","63506","63507","63508","63509","63510","63511","63512","63513","63514","63515","63516","63517","63518","63519","63520","63521","63522","63523","63524","63525","63526","63527","63528","63529","63530","63531","63532","63533","63534","63535","63536","63537","63538","63539","63540","63541","63542","63543","63544","63545","63546","63547","63548","63549","63550","63551","63552","63553","63554","63555","63556","63557","63558","63559","63560","63561","63562","63563","63564","63565","63566","63567","63568","63569","63570","63571","63572","63573","63574","63575","63576","63577","63578","63579","63580","63581","63582","63583","63584","63585","63586","63587","63588","63589","63590","63591","63592","63593","63594","63595","63596","63597","63598","63599","63600","63601","63602","63603","63604","63605","63606","63607","63608","63609","63610","63611","63612","63613","63614","63615","63616","63617","63618","63619","63620","63621","63622","63623","63624","63625","63626","63627","63628","63629","63630","63631","63632","63633","63634","63635","63636","63637","63638","63639","63640","63641","63642","63643","63644","63645","63646","63647","63648","63649","63650","63651","63652","63653","63654","63655","63656","63657","63658","63659","63660","63661","63662","63663","63664","63665","63666","63667","63668","63669","63670","63671","63672","63673","63674","63675","63676","63677","63678","63679","63680","63681","63682","63683","63684","63685","63686","63687","63688","63689","63690","63691","63692","63693","63694","63695","63696","63697","63698","63699","63700","63701","63702","63703","63704","63705","63706","63707","63708","63709","63710","63711","63712","63713","63714","63715","63716","63717","63718","63719","63720","63721","63722","63723","63724","63725","63726","63727","63728","63729","63730","63731","63732","63733","63734","63735","63736","63737","63738","63739","63740","63741","63742","63743","63744","63745","63746","63747","63748","63749","63750","63751","63752","63753","63754","63755","63756","63757","63758","63759","63760","63761","63762","63763","63764","63765","63766","63767","63768","63769","63770","63771","63772","63773","63774","63775","63776","63777","63778","63779","63780","63781","63782","63783","63784","63785","63786","63787","63788","63789","63790","63791","63792","63793","63794","63795","63796","63797","63798","63799","63800","63801","63802","63803","63804","63805","63806","63807","63808","63809","63810","63811","63812","63813","63814","63815","63816","63817","63818","63819","63820","63821","63822","63823","63824","63825","63826","63827","63828","63829","63830","63831","63832","63833","63834","63835","63836","63837","63838","63839","63840","63841","63842","63843","63844","63845","63846","63847","63848","63849","63850","63851","63852","63853","63854","63855","63856","63857","63858","63859","63860","63861","63862","63863","63864","63865","63866","63867","63868","63869","63870","63871","63872","63873","63874","63875","63876","63877","63878","63879","63880","63881","63882","63883","63884","63885","63886","63887","63888","63889","63890","63891","63892","63893","63894","63895","63896","63897","63898","63899","63900","63901","63902","63903","63904","63905","63906","63907","63908","63909","63910","63911","63912","63913","63914","63915","63916","63917","63918","63919","63920","63921","63922","63923","63924","63925","63926","63927","63928","63929","63930","63931","63932","63933","63934","63935","63936","63937","63938","63939","63940","63941","63942","63943","63944","63945","63946","63947","63948","63949","63950","63951","63952","63953","63954","63955","63956","63957","63958","63959","63960","63961","63962","63963","63964","63965","63966","63967","63968","63969","63970","63971","63972","63973","63974","63975","63976","63977","63978","63979","63980","63981","63982","63983","63984","63985","63986","63987","63988","63989","63990","63991","63992","63993","63994","63995","63996","63997","63998","63999","64000","64001","64002","64003","64004","64005","64006","64007","64008","64009","64010","64011","64012","64013","64014","64015","64016","64017","64018","64019","64020","64021","64022","64023","64024","64025","64026","64027","64028","64029","64030","64031","64032","64033","64034","64035","64036","64037","64038","64039","64040","64041","64042","64043","64044","64045","64046","64047","64048","64049","64050","64051","64052","64053","64054","64055","64056","64057","64058","64059","64060","64061","64062","64063","64064","64065","64066","64067","64068","64069","64070","64071","64072","64073","64074","64075","64076","64077","64078","64079","64080","64081","64082","64083","64084","64085","64086","64087","64088","64089","64090","64091","64092","64093","64094","64095","64096","64097","64098","64099","64100","64101","64102","64103","64104","64105","64106","64107","64108","64109","64110","64111","64112","64113","64114","64115","64116","64117","64118","64119","64120","64121","64122","64123","64124","64125","64126","64127","64128","64129","64130","64131","64132","64133","64134","64135","64136","64137","64138","64139","64140","64141","64142","64143","64144","64145","64146","64147","64148","64149","64150","64151","64152","64153","64154","64155","64156","64157","64158","64159","64160","64161","64162","64163","64164","64165","64166","64167","64168","64169","64170","64171","64172","64173","64174","64175","64176","64177","64178","64179","64180","64181","64182","64183","64184","64185","64186","64187","64188","64189","64190","64191","64192","64193","64194","64195","64196","64197","64198","64199","64200","64201","64202","64203","64204","64205","64206","64207","64208","64209","64210","64211","64212","64213","64214","64215","64216","64217","64218","64219","64220","64221","64222","64223","64224","64225","64226","64227","64228","64229","64230","64231","64232","64233","64234","64235","64236","64237","64238","64239","64240","64241","64242","64243","64244","64245","64246","64247","64248","64249","64250","64251","64252","64253","64254","64255","64256","64257","64258","64259","64260","64261","64262","64263","64264","64265","64266","64267","64268","64269","64270","64271","64272","64273","64274","64275","64276","64277","64278","64279","64280","64281","64282","64283","64284","64285","64286","64287","64288","64289","64290","64291","64292","64293","64294","64295","64296","64297","64298","64299","64300","64301","64302","64303","64304","64305","64306","64307","64308","64309","64310","64311","64312","64313","64314","64315","64316","64317","64318","64319","64320","64321","64322","64323","64324","64325","64326","64327","64328","64329","64330","64331","64332","64333","64334","64335","64336","64337","64338","64339","64340","64341","64342","64343","64344","64345","64346","64347","64348","64349","64350","64351","64352","64353","64354","64355","64356","64357","64358","64359","64360","64361","64362","64363","64364","64365","64366","64367","64368","64369","64370","64371","64372","64373","64374","64375","64376","64377","64378","64379","64380","64381","64382","64383","64384","64385","64386","64387","64388","64389","64390","64391","64392","64393","64394","64395","64396","64397","64398","64399","64400","64401","64402","64403","64404","64405","64406","64407","64408","64409","64410","64411","64412","64413","64414","64415","64416","64417","64418","64419","64420","64421","64422","64423","64424","64425","64426","64427","64428","64429","64430","64431","64432","64433","64434","64435","64436","64437","64438","64439","64440","64441","64442","64443","64444","64445","64446","64447","64448","64449","64450","64451","64452","64453","64454","64455","64456","64457","64458","64459","64460","64461","64462","64463","64464","64465","64466","64467","64468","64469","64470","64471","64472","64473","64474","64475","64476","64477","64478","64479","64480","64481","64482","64483","64484","64485","64486","64487","64488","64489","64490","64491","64492","64493","64494","64495","64496","64497","64498","64499","64500","64501","64502","64503","64504","64505","64506","64507","64508","64509","64510","64511","64512","64513","64514","64515","64516","64517","64518","64519","64520","64521","64522","64523","64524","64525","64526","64527","64528","64529","64530","64531","64532","64533","64534","64535","64536","64537","64538","64539","64540","64541","64542","64543","64544","64545","64546","64547","64548","64549","64550","64551","64552","64553","64554","64555","64556","64557","64558","64559","64560","64561","64562","64563","64564","64565","64566","64567","64568","64569","64570","64571","64572","64573","64574","64575","64576","64577","64578","64579","64580","64581","64582","64583","64584","64585","64586","64587","64588","64589","64590","64591","64592","64593","64594","64595","64596","64597","64598","64599","64600","64601","64602","64603","64604","64605","64606","64607","64608","64609","64610","64611","64612","64613","64614","64615","64616","64617","64618","64619","64620","64621","64622","64623","64624","64625","64626","64627","64628","64629","64630","64631","64632","64633","64634","64635","64636","64637","64638","64639","64640","64641","64642","64643","64644","64645","64646","64647","64648","64649","64650","64651","64652","64653","64654","64655","64656","64657","64658","64659","64660","64661","64662","64663","64664","64665","64666","64667","64668","64669","64670","64671","64672","64673","64674","64675","64676","64677","64678","64679","64680","64681","64682","64683","64684","64685","64686","64687","64688","64689","64690","64691","64692","64693","64694","64695","64696","64697","64698","64699","64700","64701","64702","64703","64704","64705","64706","64707","64708","64709","64710","64711","64712","64713","64714","64715","64716","64717","64718","64719","64720","64721","64722","64723","64724","64725","64726","64727","64728","64729","64730","64731","64732","64733","64734","64735","64736","64737","64738","64739","64740","64741","64742","64743","64744","64745","64746","64747","64748","64749","64750","64751","64752","64753","64754","64755","64756","64757","64758","64759","64760","64761","64762","64763","64764","64765","64766","64767","64768","64769","64770","64771","64772","64773","64774","64775","64776","64777","64778","64779","64780","64781","64782","64783","64784","64785","64786","64787","64788","64789","64790","64791","64792","64793","64794","64795","64796","64797","64798","64799","64800","64801","64802","64803","64804","64805","64806","64807","64808","64809","64810","64811","64812","64813","64814","64815","64816","64817","64818","64819","64820","64821","64822","64823","64824","64825","64826","64827","64828","64829","64830","64831","64832","64833","64834","64835","64836","64837","64838","64839","64840","64841","64842","64843","64844","64845","64846","64847","64848","64849","64850","64851","64852","64853","64854","64855","64856","64857","64858","64859","64860","64861","64862","64863","64864","64865","64866","64867","64868","64869","64870","64871","64872","64873","64874","64875","64876","64877","64878","64879","64880","64881","64882","64883","64884","64885","64886","64887","64888","64889","64890","64891","64892","64893","64894","64895","64896","64897","64898","64899","64900","64901","64902","64903","64904","64905","64906","64907","64908","64909","64910","64911","64912","64913","64914","64915","64916","64917","64918","64919","64920","64921","64922","64923","64924","64925","64926","64927","64928","64929","64930","64931","64932","64933","64934","64935","64936","64937","64938","64939","64940","64941","64942","64943","64944","64945","64946","64947","64948","64949","64950","64951","64952","64953","64954","64955","64956","64957","64958","64959","64960","64961","64962","64963","64964","64965","64966","64967","64968","64969","64970","64971","64972","64973","64974","64975","64976","64977","64978","64979","64980","64981","64982","64983","64984","64985","64986","64987","64988","64989","64990","64991","64992","64993","64994","64995","64996","64997","64998","64999","65000","65001","65002","65003","65004","65005","65006","65007","65008","65009","65010","65011","65012","65013","65014","65015","65016","65017","65018","65019","65020","65021","65022","65023","65024","65025","65026","65027","65028","65029","65030","65031","65032","65033","65034","65035","65036","65037","65038","65039","65040","65041","65042","65043","65044","65045","65046","65047","65048","65049","65050","65051","65052","65053","65054","65055","65056","65057","65058","65059","65060","65061","65062","65063","65064","65065","65066","65067","65068","65069","65070","65071","65072","65073","65074","65075","65076","65077","65078","65079","65080","65081","65082","65083","65084","65085","65086","65087","65088","65089","65090","65091","65092","65093","65094","65095","65096","65097","65098","65099","65100","65101","65102","65103","65104","65105","65106","65107","65108","65109","65110","65111","65112","65113","65114","65115","65116","65117","65118","65119","65120","65121","65122","65123","65124","65125","65126","65127","65128","65129","65130","65131","65132","65133","65134","65135","65136","65137","65138","65139","65140","65141","65142","65143","65144","65145","65146","65147","65148","65149","65150","65151","65152","65153","65154","65155","65156","65157","65158","65159","65160","65161","65162","65163","65164","65165","65166","65167","65168","65169","65170","65171","65172","65173","65174","65175","65176","65177","65178","65179","65180","65181","65182","65183","65184","65185","65186","65187","65188","65189","65190","65191","65192","65193","65194","65195","65196","65197","65198","65199","65200","65201","65202","65203","65204","65205","65206","65207","65208","65209","65210","65211","65212","65213","65214","65215","65216","65217","65218","65219","65220","65221","65222","65223","65224","65225","65226","65227","65228","65229","65230","65231","65232","65233","65234","65235","65236","65237","65238","65239","65240","65241","65242","65243","65244","65245","65246","65247","65248","65249","65250","65251","65252","65253","65254","65255","65256","65257","65258","65259","65260","65261","65262","65263","65264","65265","65266","65267","65268","65269","65270","65271","65272","65273","65274","65275","65276","65277","65278","65279","65280","65281","65282","65283","65284","65285","65286","65287","65288","65289","65290","65291","65292","65293","65294","65295","65296","65297","65298","65299","65300","65301","65302","65303","65304","65305","65306","65307","65308","65309","65310","65311","65312","65313","65314","65315","65316","65317","65318","65319","65320","65321","65322","65323","65324","65325","65326","65327","65328","65329","65330","65331","65332","65333","65334","65335","65336","65337","65338","65339","65340","65341","65342","65343","65344","65345","65346","65347","65348","65349","65350","65351","65352","65353","65354","65355","65356","65357","65358","65359","65360","65361","65362","65363","65364","65365","65366","65367","65368","65369","65370","65371","65372","65373","65374","65375","65376","65377","65378","65379","65380","65381","65382","65383","65384","65385","65386","65387","65388","65389","65390","65391","65392","65393","65394","65395","65396","65397","65398","65399","65400","65401","65402","65403","65404","65405","65406","65407","65408","65409","65410","65411","65412","65413","65414","65415","65416","65417","65418","65419","65420","65421","65422","65423","65424","65425","65426","65427","65428","65429","65430","65431","65432","65433","65434","65435","65436","65437","65438","65439","65440","65441","65442","65443","65444","65445","65446","65447","65448","65449","65450","65451","65452","65453","65454","65455","65456","65457","65458","65459","65460","65461","65462","65463","65464","65465","65466","65467","65468","65469","65470","65471","65472","65473","65474","65475","65476","65477","65478","65479","65480","65481","65482","65483","65484","65485","65486","65487","65488","65489","65490","65491","65492","65493","65494","65495","65496","65497","65498","65499","65500","65501"]}
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/0.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/0.json
new file mode 100644
index 0000000..7ad337b
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/0.json
@@ -0,0 +1 @@
+{"keys": ["", "71", "24", "245", "207", "238", "82", "132", "205", "51", "65", "242", "231", "186", "165", "114", "120", "2", "13", "235", "116", "113", "41", "234", "34", "90", "78", "48", "173", "215", "150", "75", "79", "224", "42", "181", "158", "38", "225", "211", "11", "208", "212", "167", "76", "39", "164", "77", "183", "104", "87", "95", "157", "59", "49", "21"], "data": {"150": {"NAME": "United States Minor Outlying Islands", "POP2005": 0}, "215": {"NAME": "United States Virgin Islands", "POP2005": 111408}, "212": {"NAME": "Venezuela", "POP2005": 26725573}, "157": {"NAME": "Suriname", "POP2005": 452468}, "211": {"NAME": "Saint Vincent and the Grenadines", "POP2005": 119137}, "158": {"NAME": "Nicaragua", "POP2005": 5462539}, "132": {"NAME": "Faroe Islands", "POP2005": 48205}, "116": {"NAME": "Mauritania", "POP2005": 2963105}, "238": {"NAME": "Svalbard", "POP2005": 0}, "65": {"NAME": "France", "POP2005": 60990544}, "113": {"NAME": "Mali", "POP2005": 1161109}, "90": {"NAME": "Jamaica", "POP2005": 2682469}, "234": {"NAME": "Turks and Caicos Islands", "POP2005": 24459}, "235": {"NAME": "Western Sahara", "POP2005": 440428}, "173": {"NAME": "Puerto Rico", "POP2005": 3946779}, "231": {"NAME": "Saint Pierre and Miquelon", "POP2005": 6346}, "24": {"NAME": "Canada", "POP2005": 32270507}, "224": {"NAME": "Guadeloupe", "POP2005": 438403}, "21": {"NAME": "Brazil", "POP2005": 186830759}, "48": {"NAME": "Dominican Republic", "POP2005": 9469601}, "49": {"NAME": "Ecuador", "POP2005": 13060993}, "82": {"NAME": "Iceland", "POP2005": 295732}, "42": {"NAME": "Cape Verde", "POP2005": 506807}, "41": {"NAME": "Cuba", "POP2005": 11259905}, "183": {"NAME": "Sierra Leone", "POP2005": 5586403}, "181": {"NAME": "Senegal", "POP2005": 1177034}, "186": {"NAME": "Spain", "POP2005": 43397491}, "79": {"NAME": "Honduras", "POP2005": 683411}, "87": {"NAME": "Cote d'Ivoire", "POP2005": 18584701}, "205": {"NAME": "United Kingdom", "POP2005": 60244834}, "207": {"NAME": "United States", "POP2005": 299846449}, "208": {"NAME": "Burkina Faso", "POP2005": 13933363}, "39": {"NAME": "Costa Rica", "POP2005": 4327228}, "120": {"NAME": "Mexico", "POP2005": 104266392}, "76": {"NAME": "Guinea", "POP2005": 9002656}, "2": {"NAME": "Algeria", "POP2005": 32854159}, "71": {"NAME": "Greenland", "POP2005": 57475}, "242": {"NAME": "Jersey", "POP2005": 0}, "164": {"NAME": "Panama", "POP2005": 3231502}, "165": {"NAME": "Portugal", "POP2005": 10528226}, "225": {"NAME": "Netherlands Antilles", "POP2005": 186392}, "167": {"NAME": "Guinea-Bissau", "POP2005": 1596929}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "104": {"NAME": "Liberia", "POP2005": 3441796}, "78": {"NAME": "Haiti", "POP2005": 9296291}, "11": {"NAME": "Barbados", "POP2005": 291933}, "245": {"NAME": "Russia", "POP2005": 143953092}, "13": {"NAME": "Bahamas", "POP2005": 323295}, "38": {"NAME": "Colombia", "POP2005": 4494579}, "59": {"NAME": "French Guiana", "POP2005": 192099}, "114": {"NAME": "Morocco", "POP2005": 30494991}, "51": {"NAME": "Ireland", "POP2005": 4143294}, "75": {"NAME": "Guatemala", "POP2005": 12709564}, "34": {"NAME": "Cayman Islands", "POP2005": 45591}, "77": {"NAME": "Guyana", "POP2005": 739472}}, "grid": [" ", " ", " ", " ", " ", " ", " !!!!!!! ", " ###### !!!!!!!!! ", " ####### !!!!!!!!!! ", " ########## !!!!!!!!!!!! ! ", " ##########!!!!!!!!!!!!!!!!! ", " # ##########!!!!!!!!!!!!!!!!!! ", " ##########!!!!!!!!!!!!!!!!!!! ", " ##########!!!!!!!!!!!!!!!!!!! ", " # ##########!!!!!!!!!!!!!!!!!!! ", " ########## !!!!!!!!!!!!!!!!! ", " ### ####### !!!!!!!!!!!!!!!!!! ", " ## ###########!!!!!!!!!!!!!!!!!!!! ", " #### ### #####!!!!!!!!!!!!!!!!!!!!! ", " ### # ##### !!!!!!!!!!!!!!!!!!!! ", " ### # ######### !!!!!!!!!!!!!!!!!!! ", " ############# ## !!!!!!!!!!!!!!!!!! ", " ############## !!!!!!!!!!!!!!! ", " ## ######## !!!!!!!!!!!!!!! ", " #### ##### # !!!!!!!!!!!!!! ", " ####### ########## !!!!!!!!!!!!! ", " ######## ### ###### !!!!!!!!!!!!! ", " $ % ######## ########## !!!!!!!!!!!!! & ", " %%%%% # ####### ## ######## !!!!!!!!!!!! ", " %%%%%%%%######## ########## ####### !!!!!!!!!!!! ", " %%%%%%%%%########################### !!!!!!!!!! ", " %%%%%%%%###################### ###### !!!!!!!! ", " %%%%%%%%%###################### ##### !!!!!!! ''''' ", " %%%%%%%%%##################### ####### !!!!!! ''''' ", " % %%%%%%%%############################ !!!!! ''' ", " %%%%%%%%%################## # ###### !!!! ( ", " %%%%%%%%%################# #### # !!!! ", " %%%%%%%%%%%%################ #### # !!! )", " %%%%% %%############### ####### ))", " % %%% %%############### ######## )))", " %% %%%######################### *))", " % %################# ######### *)))", "%%%% ############################ **))", " ########################### ))", " #%%%%%%%%%%%%############## +,", " %%%%%%%%%%%%%%%####%%## -# +", " %%%%%%%%%%%%%%%##%%%%## ", " %%%%%%%%%%%%%%%#%%%% ...", " %%%%%%%%%%%%%%%%%%%% /..", " %%%%%%%%%%%%%%%%%% / ...", " %%%%%%%%%%%%%%%%% ..", " %%%%%%%%%%%%%%%% 000", " 1%%%%%%%%%%%% 000", " 11111%%%%%%%%% .0022", " % 111111% %3 44422", " 111111 %33 445566", " %% 1111 1177778 445566", " % 1 1111111 9:%;<=> 555556", " ? 111@AA B CC DD5556", " 1@EEF G HI DDD66J", " E FKKKKK LMM6JJ", " NOOFKKKKKP QQRSS", " T FFFKKKPUV RSS", " T WFFFKKXPUVX "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/1.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/1.json
new file mode 100644
index 0000000..549b5e6
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/1.json
@@ -0,0 +1 @@
+{"keys": ["", "150", "49", "161", "38", "21", "95", "195", "64", "43", "17", "218", "61", "196", "33", "160", "8", "209", "159", "62", "243"], "data": {"150": {"NAME": "United States Minor Outlying Islands", "POP2005": 0}, "38": {"NAME": "Colombia", "POP2005": 4494579}, "21": {"NAME": "Brazil", "POP2005": 186830759}, "17": {"NAME": "Bolivia", "POP2005": 9182015}, "49": {"NAME": "Ecuador", "POP2005": 13060993}, "159": {"NAME": "New Zealand", "POP2005": 4097112}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "196": {"NAME": "Tonga", "POP2005": 99361}, "61": {"NAME": "Fiji", "POP2005": 828046}, "43": {"NAME": "Cook Islands", "POP2005": 13984}, "218": {"NAME": "Samoa", "POP2005": 183845}, "195": {"NAME": "Tokelau", "POP2005": 1401}, "62": {"NAME": "Falkland Islands (Malvinas)", "POP2005": 2975}, "209": {"NAME": "Uruguay", "POP2005": 3325727}, "243": {"NAME": "South Georgia South Sandwich Islands", "POP2005": 0}, "8": {"NAME": "Argentina", "POP2005": 38747148}, "64": {"NAME": "French Polynesia", "POP2005": 255632}, "160": {"NAME": "Paraguay", "POP2005": 5904342}, "161": {"NAME": "Peru", "POP2005": 27274266}, "33": {"NAME": "Chile", "POP2005": 16295102}}, "grid": [" ! ! ## ##$%%&&&&&&&&& ", " ' $$$$$&&&&&&&&&&& ", " ' $$$$&&&&&&&&&&&&& ", " ( ) $$$$&&&&&&&&&&&& ", " * ' $$$$+&&&&&&&&&& ", " , $$$+++&&&&&&&& ", "- * ) $$+++&&&&&&&& ", "- . * /++00&&&&&& & ", " ) ) //+000&&&&& ", " /11100&&& ", " ) / /11100&& ", " /1111&&& ", " /111222 ", " //111222 ", " /11111 ", " /11111 ", " /111 ", " 3 //111 ", " 3 //11 ", " //11 ", " //11 ", " //1 44 ", " //// 4 ", " //11 55 ", " / 5 ", " ", " 5 ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/2.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/0/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/0.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/0.json
new file mode 100644
index 0000000..1be321e
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/0.json
@@ -0,0 +1 @@
+{"keys": ["", "245", "238", "154", "189", "60", "142", "53", "45", "101", "103", "102", "205", "153", "72", "163", "98", "206", "30", "207", "129", "65", "105", "110", "191", "86", "182", "81", "170", "80", "236", "171", "210", "89", "186", "22", "202", "112", "200", "5", "3", "93", "94", "74", "84", "194", "96", "2", "199", "117", "88", "162", "190", "31", "83", "107", "50", "139", "175", "155", "168", "226", "244", "14", "18", "113", "126", "36", "188", "118", "99", "214", "220", "193", "172", "150", "54", "131", "208", "152", "56", "25", "185", "230", "197", "35", "40", "119", "26", "229", "63", "69", "223", "121", "67", "27", "28", "204", "92", "95"], "data": {"214": {"NAME": "Viet Nam", "POP2005": 85028643}, "210": {"NAME": "Uzbekistan", "POP2005": 26593123}, "131": {"NAME": "Northern Mariana Islands", "POP2005": 80258}, "139": {"NAME": "Palestine", "POP2005": 3762005}, "25": {"NAME": "Cambodia", "POP2005": 13955507}, "26": {"NAME": "Sri Lanka", "POP2005": 19120763}, "27": {"NAME": "Congo", "POP2005": 3609851}, "22": {"NAME": "Bulgaria", "POP2005": 7744591}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "28": {"NAME": "Democratic Republic of the Congo", "POP2005": 58740547}, "220": {"NAME": "Yemen", "POP2005": 21095679}, "121": {"NAME": "Malaysia", "POP2005": 25652985}, "126": {"NAME": "Niger", "POP2005": 1326419}, "129": {"NAME": "Belgium", "POP2005": 10398049}, "54": {"NAME": "Eritrea", "POP2005": 4526722}, "56": {"NAME": "Ethiopia", "POP2005": 78985857}, "50": {"NAME": "Egypt", "POP2005": 72849793}, "53": {"NAME": "Estonia", "POP2005": 1344312}, "199": {"NAME": "Tunisia", "POP2005": 10104685}, "194": {"NAME": "Tajikistan", "POP2005": 6550213}, "197": {"NAME": "Togo", "POP2005": 6238572}, "191": {"NAME": "Switzerland", "POP2005": 7424389}, "190": {"NAME": "Syrian Arab Republic", "POP2005": 18893881}, "193": {"NAME": "Thailand", "POP2005": 63002911}, "117": {"NAME": "Malta", "POP2005": 402617}, "89": {"NAME": "Japan", "POP2005": 127896740}, "110": {"NAME": "Mongolia", "POP2005": 2580704}, "113": {"NAME": "Mali", "POP2005": 1161109}, "112": {"NAME": "The former Yugoslav Republic of Macedonia", "POP2005": 2033655}, "205": {"NAME": "United Kingdom", "POP2005": 60244834}, "80": {"NAME": "Croatia", "POP2005": 455149}, "81": {"NAME": "Hungary", "POP2005": 10086387}, "119": {"NAME": "Maldives", "POP2005": 295297}, "118": {"NAME": "Oman", "POP2005": 2507042}, "84": {"NAME": "Iran (Islamic Republic of)", "POP2005": 69420607}, "3": {"NAME": "Azerbaijan", "POP2005": 8352021}, "245": {"NAME": "Russia", "POP2005": 143953092}, "244": {"NAME": "Taiwan", "POP2005": 0}, "102": {"NAME": "Belarus", "POP2005": 9795287}, "103": {"NAME": "Lithuania", "POP2005": 3425077}, "101": {"NAME": "Latvia", "POP2005": 2301793}, "107": {"NAME": "Libyan Arab Jamahiriya", "POP2005": 5918217}, "105": {"NAME": "Slovakia", "POP2005": 5386995}, "31": {"NAME": "Afghanistan", "POP2005": 25067407}, "30": {"NAME": "China", "POP2005": 1312978855}, "36": {"NAME": "Chad", "POP2005": 10145609}, "35": {"NAME": "Cameroon", "POP2005": 17795149}, "60": {"NAME": "Finland", "POP2005": 5246004}, "63": {"NAME": "Micronesia, Federated States of", "POP2005": 110058}, "65": {"NAME": "France", "POP2005": 60990544}, "67": {"NAME": "Gabon", "POP2005": 1290693}, "69": {"NAME": "Ghana", "POP2005": 2253501}, "175": {"NAME": "Saudi Arabia", "POP2005": 2361236}, "172": {"NAME": "Philippines", "POP2005": 84566163}, "171": {"NAME": "Republic of Moldova", "POP2005": 3876661}, "170": {"NAME": "Romania", "POP2005": 21627557}, "182": {"NAME": "Slovenia", "POP2005": 1999425}, "96": {"NAME": "Korea, Republic of", "POP2005": 47869837}, "2": {"NAME": "Algeria", "POP2005": 32854159}, "186": {"NAME": "Spain", "POP2005": 43397491}, "185": {"NAME": "Somalia", "POP2005": 8196395}, "188": {"NAME": "Sudan", "POP2005": 36899747}, "189": {"NAME": "Sweden", "POP2005": 9038049}, "99": {"NAME": "Lao People's Democratic Republic", "POP2005": 566391}, "98": {"NAME": "Kazakhstan", "POP2005": 15210609}, "168": {"NAME": "Qatar", "POP2005": 796186}, "229": {"NAME": "Palau", "POP2005": 20127}, "226": {"NAME": "United Arab Emirates", "POP2005": 4104291}, "93": {"NAME": "Kyrgyzstan", "POP2005": 5203547}, "92": {"NAME": "Kenya", "POP2005": 35598952}, "223": {"NAME": "Indonesia", "POP2005": 226063044}, "94": {"NAME": "Korea, Democratic People's Republic of", "POP2005": 23615611}, "162": {"NAME": "Pakistan", "POP2005": 158080591}, "163": {"NAME": "Poland", "POP2005": 38195558}, "14": {"NAME": "Bangladesh", "POP2005": 15328112}, "18": {"NAME": "Burma", "POP2005": 47967266}, "88": {"NAME": "Iraq", "POP2005": 27995984}, "150": {"NAME": "United States Minor Outlying Islands", "POP2005": 0}, "153": {"NAME": "Netherlands", "POP2005": 1632769}, "152": {"NAME": "Nigeria", "POP2005": 141356083}, "155": {"NAME": "Nepal", "POP2005": 27093656}, "154": {"NAME": "Norway", "POP2005": 4638836}, "238": {"NAME": "Svalbard", "POP2005": 0}, "83": {"NAME": "India", "POP2005": 1134403141}, "236": {"NAME": "Serbia", "POP2005": 9863026}, "230": {"NAME": "Marshall Islands", "POP2005": 5672}, "86": {"NAME": "Italy", "POP2005": 5864636}, "45": {"NAME": "Denmark", "POP2005": 5416945}, "40": {"NAME": "Central African Republic", "POP2005": 4191429}, "5": {"NAME": "Armenia", "POP2005": 3017661}, "200": {"NAME": "Turkey", "POP2005": 72969723}, "202": {"NAME": "Turkmenistan", "POP2005": 4833266}, "142": {"NAME": "\ufffdland Islands", "POP2005": 0}, "204": {"NAME": "Uganda", "POP2005": 28947181}, "207": {"NAME": "United States", "POP2005": 299846449}, "206": {"NAME": "Ukraine", "POP2005": 46917544}, "208": {"NAME": "Burkina Faso", "POP2005": 13933363}, "74": {"NAME": "Greece", "POP2005": 11099737}, "72": {"NAME": "Germany", "POP2005": 82652369}}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ! ", " !! ! ", " !!!!!! !!! ", " ## !!!!!!! !! ", " ######## !!!!! !!!! ", " ###### ! !!!! ", " ##### # !!!!! ", " #### # !! ", " ## # ! ! ", " #### ! !!! ", " # # ! ! !!!! ! ", " !!! ! !!!!!!!! !!! ! ", " !!! ! !!!!!!!!!! !!!!!! ", " !! ! !!!!!!!!!!! ! ! ", " !! !!!!!!!!!!!! ! ! ! ", " !! !!!!!!!!!!!!!!!!!!!!!! !! ", " ! !!!!!!!!!!!!!!!!!!!!!!! !!!!! ", " !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ", " $$$$$ !!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ", " $$$$$$!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%$!!!! ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $%%%&&!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " %%%%&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $%%%%&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%%&&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%% &&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", " $$%%% &&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ", " $$%%%'&&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!!! ", " $$%%%%((!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! !! ", " )%%% **!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!! ", " ))% ++,!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !!! ! ", "- .//000!+,!!!!!!!!!!!!!1!!!!!!!!!!!!!!!!!!!!!!!!!! !! ! ", "- .//00002,!!!!!!!!!!!111111!!!!!!!!!!!!!!!!3!!!!!!! ! 444", "-5.//00002222!!!!!11!!1111111!!!!!!!!!!!!!!33!!!!!!! ! ", "666///70222222!!!11111111111111!88888888883333!!!! ! ! ", "6699:;;<==222!!!!!111111111111138888888888833333!! ! ! ", "66:::>>??=@ 2!!!!!11AA1111111133888888888333333!! BB! ", "CC6:::>??DD !!!!1EAAAA1111133333888888333333!! BB ", "C : ::FFGGGGGGGH!IEEEAAAAAJ3333333333333333KKK B ", "C : ::LLGGGGGGGGM EEEEAANN33333333333333333KO BB ", "PPPQ:R LGGGGGGSSMMMMMEENTTT3333333333333333 OOBBBB ", "PPPQQ UUSSMMMMMVVVTTWW33333333333333 OBBBB ", "PPPQQXXXXXYYZ[[[SSMMMMMVTTTWW333333333333333 B ", "PPPPXXXXXXYYY[[[[[MMMMTTTTTWW]]33WW333333333 B ", "PPPPXXXXXXYYY[[[[[^M_MTTTTWWWW]]WWW33333333` B B ", "PPPPXXXXXXYYY[[[[[___ TTWWWWWWaWb3333333``BB ", "cPPdddeXXfffff[[[[[[gg WWWWWWWaWb3hii333 ` ", "ccddddeeefffff [[[j[g WWWWW bbkki 3 l m ", "cdddddeeefffffnjjjjj WWWW bkkki l o ", "pdqqqqeeffffffrjjj WW W kksii lll o ", "pqqqqqeefffffrrtttt WWW W k iii lll uu ", "vqqqqwxxfffffrrrttt yWzz W k i ll { |||| uu ", "} qqwxxxxxfffrrrtt y ~ k \u007flll~ { | ", " \u0080w\u0081\u0082\u0082\u0082\u0082\u0083\u0083\u0084\u0084tt y ~~~\u007f ~\u007f~~ ~ \u0085 "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/1.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/1.json
new file mode 100644
index 0000000..66a2faf
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/1.json
@@ -0,0 +1 @@
+{"keys": ["", "52", "67", "28", "204", "92", "185", "119", "223", "156", "95", "174", "203", "166", "177", "148", "201", "6", "221", "227", "20", "123", "37", "9", "122", "108", "151", "216", "222", "146", "61", "180", "115", "124", "178", "219", "179", "159", "147", "145"], "data": {"151": {"NAME": "Vanuatu", "POP2005": 215366}, "201": {"NAME": "Tuvalu", "POP2005": 10441}, "156": {"NAME": "Nauru", "POP2005": 10111}, "159": {"NAME": "New Zealand", "POP2005": 4097112}, "67": {"NAME": "Gabon", "POP2005": 1290693}, "219": {"NAME": "Swaziland", "POP2005": 1124529}, "115": {"NAME": "Mauritius", "POP2005": 1241173}, "61": {"NAME": "Fiji", "POP2005": 828046}, "179": {"NAME": "Lesotho", "POP2005": 1980831}, "178": {"NAME": "South Africa", "POP2005": 47938663}, "177": {"NAME": "Seychelles", "POP2005": 85532}, "174": {"NAME": "Rwanda", "POP2005": 9233793}, "119": {"NAME": "Maldives", "POP2005": 295297}, "20": {"NAME": "Solomon Islands", "POP2005": 472419}, "95": {"NAME": "Kiribati", "POP2005": 92003}, "28": {"NAME": "Democratic Republic of the Congo", "POP2005": 58740547}, "180": {"NAME": "Botswana", "POP2005": 1835938}, "185": {"NAME": "Somalia", "POP2005": 8196395}, "9": {"NAME": "Australia", "POP2005": 20310208}, "146": {"NAME": "French Southern and Antarctic Lands", "POP2005": 0}, "147": {"NAME": "Heard Island and McDonald Islands", "POP2005": 0}, "203": {"NAME": "United Republic of Tanzania", "POP2005": 38477873}, "145": {"NAME": "Bouvet Island", "POP2005": 0}, "204": {"NAME": "Uganda", "POP2005": 28947181}, "6": {"NAME": "Angola", "POP2005": 16095214}, "148": {"NAME": "British Indian Ocean Territory", "POP2005": 0}, "122": {"NAME": "Mozambique", "POP2005": 20532675}, "123": {"NAME": "Malawi", "POP2005": 13226091}, "124": {"NAME": "New Caledonia", "POP2005": 234185}, "227": {"NAME": "Timor-Leste", "POP2005": 1067285}, "166": {"NAME": "Papua New Guinea", "POP2005": 6069715}, "92": {"NAME": "Kenya", "POP2005": 35598952}, "223": {"NAME": "Indonesia", "POP2005": 226063044}, "222": {"NAME": "Zimbabwe", "POP2005": 13119679}, "221": {"NAME": "Zambia", "POP2005": 11478317}, "37": {"NAME": "Comoros", "POP2005": 797902}, "108": {"NAME": "Madagascar", "POP2005": 18642586}, "52": {"NAME": "Equatorial Guinea", "POP2005": 484098}, "216": {"NAME": "Namibia", "POP2005": 2019677}}, "grid": [" !###$$$$$%%&&' ( ))) ))) )))))) * + ", " ##$$$$,---& )))))))) ))))))..... ", " $$$$$$$---- / 0 ))) ) ) ))))..... 11", " 2$2$$33--- / ))))44 )).... 555 ", " 222$3336--7 888 . 555 ", " 22223333399 : 8888888 ;; ", " <<<233==699>:: 888888888 ; ?", " <<<@===9 :: A 8888888888888 B ; ?", " <<<@@==9 ::: 8888888888888 BB ", " <<<@@CC9 : 888888888888888 ", " <<CCCD 88888888888888 ", " CCCCEC 88888888888888 ", " CCCC 88888888888888 ", " CCC 88 888888 FF ", " 8888 F ", " 88 FF", " 8 FF ", " 8 FF ", " FFF ", " C ", " >> ", " F ", " G F ", " H ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/2.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/1/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/0.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/0.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/0.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/1.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/1.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/1.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/2.json b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/2.json
new file mode 100644
index 0000000..a0e62f4
--- /dev/null
+++ b/misc/openlayers/tests/data/utfgrid/world_utfgrid/1/2/2.json
@@ -0,0 +1 @@
+{"keys": [""], "data": {}, "grid": [" ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " ", " "]} \ No newline at end of file
diff --git a/misc/openlayers/tests/data_Layer_Text_textfile.txt b/misc/openlayers/tests/data_Layer_Text_textfile.txt
new file mode 100644
index 0000000..8250988
--- /dev/null
+++ b/misc/openlayers/tests/data_Layer_Text_textfile.txt
@@ -0,0 +1,3 @@
+point image
+10,20 http://boston.openguides.org/markers/ORANGE.png
+15,25 http://boston.openguides.org/markers/ORANGE.png
diff --git a/misc/openlayers/tests/data_Layer_Text_textfile_2.txt b/misc/openlayers/tests/data_Layer_Text_textfile_2.txt
new file mode 100644
index 0000000..91a8093
--- /dev/null
+++ b/misc/openlayers/tests/data_Layer_Text_textfile_2.txt
@@ -0,0 +1,3 @@
+point title description image
+10,20 a b http://boston.openguides.org/markers/ORANGE.png
+15,25 c d http://boston.openguides.org/markers/ORANGE.png
diff --git a/misc/openlayers/tests/data_Layer_Text_textfile_overflow.txt b/misc/openlayers/tests/data_Layer_Text_textfile_overflow.txt
new file mode 100644
index 0000000..bb4768e
--- /dev/null
+++ b/misc/openlayers/tests/data_Layer_Text_textfile_overflow.txt
@@ -0,0 +1,3 @@
+overflow point title description image
+auto 10,20 a b http://boston.openguides.org/markers/ORANGE.png
+hidden 15,25 c d http://boston.openguides.org/markers/ORANGE.png
diff --git a/misc/openlayers/tests/deprecated/Ajax.html b/misc/openlayers/tests/deprecated/Ajax.html
new file mode 100644
index 0000000..e73e80c
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Ajax.html
@@ -0,0 +1,28 @@
+<html>
+<head>
+ <script src="../OLLoader.js"></script>
+ <script src="../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ function test_Ajax_loadUrl(t) {
+ t.plan(5);
+ var _get = OpenLayers.Request.GET;
+ var caller = {};
+ var onComplete = function() {};
+ var onFailure = function() {};
+ var params = {};
+ OpenLayers.Request.GET = function(config) {
+ t.eq(config.url, "http://example.com/?format=image+kml", "correct url")
+ t.eq(config.params, params, "correct params");
+ t.eq(config.scope, caller, "correct scope");
+ t.ok(config.success === onComplete, "correct success callback");
+ t.ok(config.failure === onFailure, "correct failure callback");
+ };
+ OpenLayers.loadURL("http://example.com/?format=image+kml", params, caller, onComplete, onFailure);
+ OpenLayers.Request.GET = _get;
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/BaseTypes/Class.html b/misc/openlayers/tests/deprecated/BaseTypes/Class.html
new file mode 100644
index 0000000..572765d
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/BaseTypes/Class.html
@@ -0,0 +1,142 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+ // remove this next line at 3.0
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+
+ // Remove this at 3.0
+ function test_Class_backwards(t) {
+ t.plan(4);
+ // test that a new style class supports old style inheritance
+ var NewClass = OpenLayers.Class({
+ newProp: "new",
+ initialize: function() {
+ t.ok(false, "the base class is never instantiated");
+ },
+ toString: function() {
+ return "new style";
+ }
+ });
+
+ var OldClass = OpenLayers.Class.create();
+ OldClass.prototype = OpenLayers.Class.inherit(NewClass, {
+ oldProp: "old",
+ initialize: function() {
+ t.ok(true, "only the child class constructor is called");
+ }
+ });
+
+ var oldObj = new OldClass();
+ t.eq(oldObj.oldProp, "old",
+ "old style classes can still be instantiated");
+ t.eq(oldObj.newProp, "new",
+ "old style inheritance of properties works with new style base");
+ t.eq(oldObj.toString(), "new style",
+ "toString inheritance works with backwards style");
+
+ }
+
+ // Remove this at 3.0
+ function test_Class_create (t) {
+ t.plan( 3 );
+ var cls = OpenLayers.Class.create();
+ cls.prototype = {
+ initialize: function () {
+ if (isMozilla)
+ t.ok(this instanceof cls,
+ "initialize is called on the right class");
+ else
+ t.ok(true, "initialize is called");
+ }
+ };
+ var obj = new cls();
+ t.eq(typeof obj, "object", "obj is an object");
+ if (isMozilla)
+ t.ok(obj instanceof cls,
+ "object is of the right class");
+ else
+ t.ok(true, "this test doesn't work in IE");
+ }
+
+ // Remove this at 3.0
+ function test_Class_inherit (t) {
+ t.plan( 20 );
+ var A = OpenLayers.Class.create();
+ var initA = 0;
+ A.prototype = {
+ count: 0,
+ mixed: false,
+ initialize: function () {
+ initA++;
+ this.count++;
+ }
+ };
+
+ var B = OpenLayers.Class.create();
+ var initB = 0;
+ B.prototype = OpenLayers.Class.inherit( A, {
+ initialize: function () {
+ A.prototype.initialize.apply(this, arguments);
+ initB++;
+ this.count++;
+ }
+ });
+
+ var mixin = OpenLayers.Class.create()
+ mixin.prototype = {
+ mixed: true
+ };
+
+ t.eq( initA, 0, "class A not inited" );
+ t.eq( initB, 0, "class B not inited" );
+
+ var objA = new A();
+ t.eq( objA.count, 1, "object A init" );
+ t.eq( initA, 1, "class A init" );
+ if (isMozilla)
+ t.ok( objA instanceof A, "obj A isa A" );
+ else
+ t.ok( true, "IE sucks" );
+
+ var objB = new B();
+ t.eq( initA, 2, "class A init" );
+ t.eq( initB, 1, "class B init" );
+ t.eq( objB.count, 2, "object B init twice" );
+ if (isMozilla) {
+ t.ok( objB instanceof A, "obj B isa A" );
+ t.ok( objB instanceof B, "obj B isa B" );
+ } else {
+ t.ok( true, "IE sucks" );
+ t.ok( true, "IE sucks" );
+ }
+
+ var C = OpenLayers.Class.create();
+ C.prototype = OpenLayers.Class.inherit( B, mixin, {count: 0} );
+ t.eq( initA, 2, "class A init unchanged" );
+ t.eq( initB, 1, "class B init unchanged" );
+
+ var objC = new C();
+ t.eq( initA, 3, "class A init changed" );
+ t.eq( initB, 2, "class B init changed" );
+ t.eq( objC.count, 2, "object C init changed" );
+ if (isMozilla) {
+ t.ok( objC instanceof A, "obj C isa A" );
+ t.ok( objC instanceof B, "obj C isa B" );
+ t.ok( objC instanceof C, "obj C isa C" );
+ t.ok( !(objC instanceof mixin), "obj C isn'ta mixin" );
+ } else {
+ t.ok( true, "IE sucks" );
+ t.ok( true, "IE sucks" );
+ t.ok( true, "IE sucks" );
+ t.ok( true, "IE sucks" );
+ }
+ t.eq( objC.mixed, true, "class C has mixin properties" );
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/BaseTypes/Element.html b/misc/openlayers/tests/deprecated/BaseTypes/Element.html
new file mode 100644
index 0000000..bd7e074
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/BaseTypes/Element.html
@@ -0,0 +1,56 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+
+ <script type="text/javascript">
+
+ function test_Element_hide(t) {
+ t.plan(2);
+
+ var elem1 = {
+ style: {
+ 'display': "none"
+ }
+ };
+
+ var elem2 = {
+ style: {
+ 'display': ""
+ }
+ };
+
+ OpenLayers.Element.hide(elem1, elem2, "do-not-exists");
+
+ t.eq(elem1.style.display, "none", "hidden element stays hidden");
+ t.eq(elem2.style.display, "none", "shown element hidden");
+ }
+
+ function test_Element_show(t) {
+ t.plan(2);
+
+ var elem1 = {
+ style: {
+ 'display': "none"
+ }
+ };
+
+ var elem2 = {
+ style: {
+ 'display': ""
+ }
+ };
+
+ OpenLayers.Element.show(elem1, "do-not-exists", elem2);
+
+ t.eq(elem1.style.display, "", "hidden element shown");
+ t.eq(elem2.style.display, "", "shown element stays shown");
+ }
+
+ </script>
+</head>
+<body>
+ <div id="elemID" style="width:50px; height:100px; background-color:red">test</div>
+</body>
+</html>
+ \ No newline at end of file
diff --git a/misc/openlayers/tests/deprecated/Control/MouseToolbar.html b/misc/openlayers/tests/deprecated/Control/MouseToolbar.html
new file mode 100644
index 0000000..f66a18b
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Control/MouseToolbar.html
@@ -0,0 +1,60 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+ var map;
+ function test_Control_MouseToolbar_constructor (t) {
+ t.plan( 1 );
+
+ control = new OpenLayers.Control.MouseToolbar();
+ t.ok( control instanceof OpenLayers.Control.MouseToolbar, "new OpenLayers.Control.MouseToolbar returns object" );
+ }
+ function test_Control_MouseToolbar_addControl (t) {
+ t.plan( 8 );
+ map = new OpenLayers.Map('map');
+ control = new OpenLayers.Control.MouseToolbar();
+ t.ok( control instanceof OpenLayers.Control.MouseToolbar, "new OpenLayers.Control.MouseToolbar returns object" );
+ t.ok( map instanceof OpenLayers.Map, "new OpenLayers.Map creates map" );
+ map.addControl(control);
+ t.ok( control.map === map, "Control.map is set to the map object" );
+ t.ok( map.controls[4] === control, "map.controls contains control" );
+ t.eq( parseInt(control.div.style.zIndex), map.Z_INDEX_BASE['Control'] + 5, "Control div zIndexed properly" );
+ t.eq( parseInt(map.viewPortDiv.lastChild.style.zIndex), map.Z_INDEX_BASE['Control'] + 5, "Viewport div contains control div" );
+ t.eq( control.div.style.left, "6px", "Control div left located correctly by default");
+ t.eq( control.div.style.top, "300px", "Control div top located correctly by default");
+
+ }
+ function test_Control_MouseToolbar_control_events (t) {
+ t.plan( 1 );
+ if ((navigator.userAgent.indexOf("compatible") == -1)) {
+ var evt = {which: 1}; // control expects left-click
+ map = new OpenLayers.Map('map');
+ var layer = new OpenLayers.Layer.WMS("Test Layer",
+ "http://octo.metacarta.com/cgi-bin/mapserv?",
+ {map: "/mapdata/vmap_wms.map", layers: "basic"});
+ map.addLayer(layer);
+
+ control = new OpenLayers.Control.MouseToolbar();
+ map.addControl(control);
+
+ var centerLL = new OpenLayers.LonLat(0,0);
+ map.setCenter(centerLL, 5);
+
+ evt.shiftKey = true;
+ evt.xy = new OpenLayers.Size(5,5);
+ control.defaultMouseDown(evt);
+ evt.xy = new OpenLayers.Size(15,15);
+ control.defaultMouseUp(evt);
+ t.eq(map.getZoom(), 6, "Map zoom set correctly after zoombox");
+ } else {
+ t.ok(true, "IE does not run this test.")
+ }
+ }
+
+ </script>
+</head>
+<body>
+ <div id="map" style="width: 1024px; height: 512px;"/>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Geometry/Rectangle.html b/misc/openlayers/tests/deprecated/Geometry/Rectangle.html
new file mode 100644
index 0000000..75778e8
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Geometry/Rectangle.html
@@ -0,0 +1,77 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ function test_Rectangle_constructor (t) {
+ t.plan( 8 );
+
+ //empty
+ var rect = new OpenLayers.Geometry.Rectangle();
+ t.ok( rect instanceof OpenLayers.Geometry.Rectangle, "new OpenLayers.Geometry.Rectangle returns Rectangle object" );
+ t.eq( rect.CLASS_NAME, "OpenLayers.Geometry.Rectangle", "Rectangle.CLASS_NAME is set correctly");
+ t.ok( rect.id != null, "rect.id is set");
+ t.ok( ! (rect.x || rect.y || rect.width || rect.height), "empty construct leaves properties empty");
+
+ //good
+ var x = {};
+ var y = {};
+ var w = {};
+ var h = {};
+ var rect = new OpenLayers.Geometry.Rectangle(x, y, w, h);
+ t.eq( rect.x, x, "good init correctly sets x property");
+ t.eq( rect.y, y, "good init correctly sets y property");
+ t.eq( rect.width, w, "good init correctly sets width property");
+ t.eq( rect.height, h, "good init correctly sets height property");
+ }
+
+ function test_Rectangle_calculateBounds(t) {
+ t.plan(1);
+
+ var x = 1;
+ var y = 2;
+ var w = 10;
+ var h = 20;
+ var rect = new OpenLayers.Geometry.Rectangle(x, y, w, h);
+ rect.calculateBounds();
+
+ var testBounds = new OpenLayers.Bounds(x, y, x + w, y + h)
+
+ t.ok( rect.bounds.equals(testBounds), "calculateBounds works correctly");
+ }
+
+ function test_Rectangle_getLength(t) {
+ t.plan(1);
+
+ var x = 1;
+ var y = 2;
+ var w = 10;
+ var h = 20;
+ var rect = new OpenLayers.Geometry.Rectangle(x, y, w, h);
+
+ var testLength = (2 * w) + (2 * h);
+
+ t.eq(rect.getLength(), testLength, "getLength() works");
+ }
+
+ function test_Rectangle_getArea(t) {
+ t.plan(1);
+
+ var x = 1;
+ var y = 2;
+ var w = 10;
+ var h = 20;
+ var rect = new OpenLayers.Geometry.Rectangle(x, y, w, h);
+
+ var testArea = w * h;
+ t.eq(rect.getArea(), testArea, "testArea() works");
+ }
+
+
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/GML.html b/misc/openlayers/tests/deprecated/Layer/GML.html
new file mode 100644
index 0000000..daf5917
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/GML.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ var name = "GML Layer";
+
+ var gml = "./owls.xml";
+ var gml2 = "./mice.xml";
+
+ // if this test is running online, different rules apply
+ var isMSIE = (navigator.userAgent.indexOf("MSIE") > -1);
+ if (isMSIE) {
+ gml = "." + gml;
+ gml2 = "." + gml2;
+ }
+
+ function test_Layer_GML_constructor(t) {
+ t.plan(3);
+
+ var layer = new OpenLayers.Layer.GML(name);
+ t.ok(layer instanceof OpenLayers.Layer.GML, "new OpenLayers.Layer.GML returns correct object" );
+ t.eq(layer.name, name, "layer name is correctly set");
+ t.ok(layer.renderer.CLASS_NAME, "layer has a renderer");
+
+ }
+ function test_Layer_GML_events(t) {
+ t.plan(3);
+
+ var layer = new OpenLayers.Layer.GML(name, gml, {isBaseLayer: true});
+ layer.events.register("loadstart", layer, function() {
+ t.ok(true, "loadstart called.")
+ });
+ layer.events.register("loadend", layer, function() {
+ t.ok(true, "loadend called.")
+ });
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ t.delay_call(3, function() {
+ t.ok(true, "waited for 3s");
+ });
+
+ }
+ function test_GML_setUrl(t) {
+ t.plan(2);
+ var layer = new OpenLayers.Layer.GML(name, gml);
+ var map = new OpenLayers.Map("map");
+ map.addLayer(layer);
+ t.eq(layer.url, gml, "layer has correct original url");
+ layer.setUrl(gml2);
+ t.eq(layer.url, gml2, "layer has correctly changed url");
+ }
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/deprecated/Layer/MapServer.html b/misc/openlayers/tests/deprecated/Layer/MapServer.html
new file mode 100644
index 0000000..d65fef6
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/MapServer.html
@@ -0,0 +1,59 @@
+<html>
+<head>
+<script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+
+<script src="../../OLLoader.js"></script>
+<script src="../../../lib/deprecated.js"></script>
+<script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic'};
+
+ function test_Layer_MapServer_Reproject (t) {
+ var validkey = (window.location.protocol == "file:") ||
+ (window.location.host == "localhost") ||
+ (window.location.host == "openlayers.org");
+
+ if (OpenLayers.BROWSER_NAME == "opera" || OpenLayers.BROWSER_NAME == "safari") {
+ t.plan(1);
+ t.debug_print("Can't test google reprojection in Opera or Safari.");
+ } else if(validkey) {
+ t.plan(5);
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.Google("Google");
+ map.addLayer(layer);
+ layer = new OpenLayers.Layer.MapServer(name, url, params, {reproject: true, isBaseLayer: false, buffer: 2});
+ layer.isBaseLayer=false;
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ t.eq( tile.bounds.left, -22.5, "left side matches" );
+ t.eq( tile.bounds.right, -11.25, "right side matches" );
+ t.eq( tile.bounds.bottom.toFixed(6), '11.781325', "bottom side matches" );
+ t.eq( tile.bounds.top.toFixed(6), '22.512557', "top side matches" );
+ map.destroy();
+ } else {
+ t.plan(1);
+ t.debug_print("can't test google layer from " +
+ window.location.host);
+ }
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ layer = new OpenLayers.Layer.MapServer(name, url, params, {buffer:2});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ t.ok( tile.bounds.equals(new OpenLayers.Bounds(-33.75, 33.75, -22.5, 45)), "okay");
+ map.destroy();
+ }
+
+</script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/MapServer/Untiled.html b/misc/openlayers/tests/deprecated/Layer/MapServer/Untiled.html
new file mode 100644
index 0000000..f235492
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/MapServer/Untiled.html
@@ -0,0 +1,158 @@
+<html>
+<head>
+
+ <script src="../../../OLLoader.js"></script>
+ <script src="../../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic'};
+
+ function test_Layer_MapServer_Untiled_constructor (t) {
+ t.plan( 4 );
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, params);
+ t.ok( layer instanceof OpenLayers.Layer.MapServer.Untiled, "new OpenLayers.Layer.MapServer returns object" );
+ t.eq( layer.url, "http://labs.metacarta.com/cgi-bin/mapserv", "layer.url is correct (HTTPRequest inited)" );
+
+ t.eq( layer.params.mode, "map", "default mode param correctly copied");
+ t.eq( layer.params.map_imagetype, "png", "default imagetype correctly copied");
+
+
+ }
+
+ function test_Layer_MapServer_Untiled_clone (t) {
+ t.plan(3);
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ var map = new OpenLayers.Map('map', {});
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, params);
+ map.addLayer(layer);
+
+ var clone = layer.clone();
+ layer.tile = [[1,2],[3,4]];
+
+ t.ok( clone.tile != layer.tile, "clone does not copy tile");
+
+ layer.ratio += 1;
+
+ t.eq( clone.ratio, 1.5, "changing layer.ratio does not change clone.ratio -- a fresh copy was made, not just copied reference");
+
+ t.eq( clone.alpha, layer.alpha, "alpha copied correctly");
+
+ layer.tile = null;
+ map.destroy();
+ }
+
+ function test_Layer_MapServer_Untiled_isBaseLayer(t) {
+ t.plan(3);
+
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, params);
+ t.ok( layer.isBaseLayer, "baselayer is true by default");
+
+ var newParams = OpenLayers.Util.extend({}, params);
+ newParams.transparent = "true";
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, newParams);
+ t.ok( !layer.isBaseLayer, "baselayer is false when transparent is set to true");
+
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, params, {isBaseLayer: false});
+ t.ok( !layer.isBaseLayer, "baselayer is false when option is set to false" );
+ }
+
+ function test_Layer_MapServer_Untiled_mergeNewParams (t) {
+ t.plan( 5 );
+
+ var map = new OpenLayers.Map("map", {tileManager: null});
+ var url = "http://labs.metacarta.com/cgi-bin/mapserv";
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, params);
+
+ var newParams = { layers: 'sooper',
+ chickpeas: 'image/png'};
+
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ t.ok( !layer.grid[0][0].url.match("chickpeas"), "chickpeas is not in URL of first tile in grid" );
+
+ layer.mergeNewParams(newParams);
+
+ t.eq( layer.params.layers, "sooper", "mergeNewParams() overwrites well");
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() adds well");
+ t.ok( layer.grid[0][0].url.match("chickpeas"), "chickpeas is in URL of first tile in grid" );
+
+ newParams.chickpeas = 151;
+
+ t.eq( layer.params.chickpeas, "image/png", "mergeNewParams() makes clean copy of hashtable");
+ map.destroy();
+ }
+
+ function test_Layer_MapServer_Untiled_getFullRequestString (t) {
+
+
+ t.plan( 1 );
+ var map = new OpenLayers.Map('map');
+ tUrl = "http://labs.metacarta.com/cgi-bin/mapserv";
+ tParams = { layers: 'basic',
+ format: 'png'};
+ var tLayer = new OpenLayers.Layer.MapServer.Untiled(name, tUrl, tParams);
+ map.addLayer(tLayer);
+ str = tLayer.getFullRequestString();
+ var tParams = {
+ layers: 'basic',
+ format: 'png',
+ mode: 'map',
+ map_imagetype: 'png'
+ };
+
+ var sStr = tUrl + "?" + OpenLayers.Util.getParameterString(tParams);
+ sStr = sStr.replace(/,/g, "+");
+
+ t.eq(str, sStr , "getFullRequestString() works");
+ map.destroy();
+
+ }
+
+ // DEPRECATED -- REMOVE IN 3.0
+ function test_Layer_Untiled_MapServer(t) {
+ t.plan(1);
+
+ var layer = new OpenLayers.Layer.MapServer.Untiled();
+
+ var clone = layer.clone();
+
+ t.ok(clone.singleTile, "regression test: clone works. this is for #1013");
+ }
+
+ function test_Layer_MapServer_Untiled_destroy (t) {
+
+ t.plan( 1 );
+
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.MapServer.Untiled(name, url, params);
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+
+ //grab a reference to one of the tiles
+ var tile = layer.tile;
+
+ layer.destroy();
+
+ // checks to make sure superclass (grid) destroy() was called
+
+ t.ok( layer.tile == null, "tile set to null");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/WFS.html b/misc/openlayers/tests/deprecated/Layer/WFS.html
new file mode 100644
index 0000000..09b6a54
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/WFS.html
@@ -0,0 +1,178 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ var name = "Vector Layer";
+
+ function test_Layer_WFS_constructor(t) {
+ t.plan(3);
+
+ var layer = new OpenLayers.Layer.WFS(name, "url", {});
+ t.ok(layer instanceof OpenLayers.Layer.WFS, "new OpenLayers.Layer.Vector returns correct object" );
+ t.eq(layer.name, name, "layer name is correctly set");
+ t.ok(layer.renderer.CLASS_NAME, "layer has a renderer");
+
+ }
+
+ function test_Layer_WFS_getDataExtent(t) {
+ t.plan(1);
+
+ var layer = new OpenLayers.Layer.WFS(name, "url", {});
+ layer.addFeatures(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 0)));
+ layer.addFeatures(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(0, 1)));
+ t.eq(layer.getDataExtent().toBBOX(), "0,0,0,1", "bbox is correctly pulled from vectors.");
+
+ }
+
+ function test_Layer_WFS_setOpacity(t) {
+ t.plan(3);
+
+ var layer = new OpenLayers.Layer.WFS(name, "url", {});
+ layer.setOpacity(.5);
+ t.eq(layer.opacity, 0.5, "vector setOpacity didn't fail");
+ var layer = new OpenLayers.Layer.WFS(name, "url", {}, {'featureClass': OpenLayers.Feature.WFS});
+ var marker = new OpenLayers.Marker(new OpenLayers.LonLat(0,0));
+ marker.setOpacity = function() {
+ t.ok(true, "Marker setOpacity was called");
+ }
+ layer.addMarker(marker);
+ layer.setOpacity(.6);
+ t.eq(layer.opacity, 0.6, "setOpacity didn't fail on markers");
+ }
+
+ function test_Layer_WFS_destroy(t) {
+ t.plan(13);
+
+ var tVectorDestroy = OpenLayers.Layer.Vector.prototype.destroy;
+ OpenLayers.Layer.Vector.prototype.destroy = function() {
+ g_VectorDestroyed = true;
+ }
+
+ var tMarkersDestroy = OpenLayers.Layer.Markers.prototype.destroy;
+ OpenLayers.Layer.Markers.prototype.destroy = function() {
+ g_MarkersDestroyed = true;
+ }
+
+ var layer = {
+ 'vectorMode': true,
+ 'tile': {
+ 'destroy': function() {
+ t.ok(true, "wfs layer's tile is destroyed");
+ }
+ },
+ 'ratio': {},
+ 'featureClass': {},
+ 'format': {},
+ 'formatObject': {
+ 'destroy': function() {
+ t.ok(true, "wfs layer's format object is destroyed");
+ }
+ },
+ 'formatOptions': {},
+ 'encodeBBOX': {},
+ 'extractAttributes': {}
+ };
+
+ //this call should set off two tests (destroys for tile and format object)
+ g_VectorDestroyed = null;
+ g_MarkersDestroyed = null;
+ OpenLayers.Layer.WFS.prototype.destroy.apply(layer, []);
+
+ t.ok(g_VectorDestroyed && !g_MarkersDestroyed, "when vector mode is set to true, the default vector layer's destroy() method is called");
+ t.eq(layer.vectorMode, null, "'vectorMode' property nullified");
+ t.eq(layer.tile, null, "'tile' property nullified");
+ t.eq(layer.ratio, null, "'ratio' property nullified");
+ t.eq(layer.featureClass, null, "'featureClass' property nullified");
+ t.eq(layer.format, null, "'format' property nullified");
+ t.eq(layer.formatObject, null, "'formatObject' property nullified");
+ t.eq(layer.formatOptions, null, "'formatOptions' property nullified");
+ t.eq(layer.encodeBBOX, null, "'encodeBBOX' property nullified");
+ t.eq(layer.extractAttributes, null, "'extractAttributes' property nullified");
+
+ layer.vectorMode = false;
+
+ //this call will *not* set off two tests (tile and format object are null)
+ g_VectorDestroyed = null;
+ g_MarkersDestroyed = null;
+ OpenLayers.Layer.WFS.prototype.destroy.apply(layer, []);
+ t.ok(!g_VectorDestroyed && g_MarkersDestroyed, "when vector mode is set to false, the default markers layer's destroy() method is called");
+
+ OpenLayers.Layer.Vector.prototype.destroy = tVectorDestroy;
+ OpenLayers.Layer.Markers.prototype.destroy = tMarkersDestroy;
+ }
+
+ function test_Layer_WFS_mapresizevector(t) {
+ t.plan(2);
+
+ var map = new OpenLayers.Map("map");
+ map.addLayer(new OpenLayers.Layer.WMS("WMS", "url", {}));
+ var layer = new OpenLayers.Layer.WFS(name, "url", {});
+ t.ok(layer.renderer.CLASS_NAME, "layer has a renderer");
+ map.addLayer(layer);
+ setSize = false;
+ layer.renderer.setSize = function() { setSize = true; }
+ layer.onMapResize();
+ t.eq(setSize, true, "Renderer resize called on map size change.");
+ map.destroy();
+
+ }
+ function test_Layer_WFS_drawmap(t) {
+ t.plan(2);
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+
+ layer = new OpenLayers.Layer.WFS( "Owl Survey",
+ "http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?",
+ {typename: "OWLS", maxfeatures: 10},
+ { featureClass: OpenLayers.Feature.WFS});
+ map.addLayer(layer);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ try {
+ map.setCenter(new OpenLayers.LonLat(-100, 60), 3);
+ } catch (Exception) {
+ }
+ t.eq(layer.tile.url, "http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?TYPENAME=OWLS&MAXFEATURES=10&SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&SRS=EPSG%3A4326&BBOX=-187.890625,-36.6796875,-12.109375,156.6796875", "Tile URL is set correctly when not encoded");
+ map.destroy();
+ var map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'}
+ );
+ map.addLayer(layer);
+
+ layer = new OpenLayers.Layer.WFS( "Owl Survey",
+ "http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?",
+ {typename: "OWLS", maxfeatures: 10},
+ { featureClass: OpenLayers.Feature.WFS, 'encodeBBOX': true});
+ map.addLayer(layer);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ try {
+ map.setCenter(new OpenLayers.LonLat(-100, 60), 3);
+ } catch (Exception) {
+ }
+ t.eq(layer.tile.url, "http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?TYPENAME=OWLS&MAXFEATURES=10&SERVICE=WFS&VERSION=1.0.0&REQUEST=GetFeature&SRS=EPSG%3A4326&BBOX=-187.890625%2C-36.679687%2C-12.109375%2C156.679688", "Tile URL is set correctly when not encoded");
+ map.destroy();
+ }
+ function test_projection_srs(t) {
+ t.plan(1);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer("",{isBaseLayer: true} ));
+ // we use an empty moveTo function because we don't want to request tiles
+ var layer = new OpenLayers.Layer.WFS("","/wfs",{},{'projection': new OpenLayers.Projection("EPSG:900913"),
+ moveTo: function() {}});
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ var params = OpenLayers.Util.getParameters(layer.getFullRequestString());
+ t.eq(params.SRS, "EPSG:900913", "SRS represents projection of WFS layer, instead of map (#1537)");
+ }
+
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/WMS.html b/misc/openlayers/tests/deprecated/Layer/WMS.html
new file mode 100644
index 0000000..43977c8
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/WMS.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <!-- this gmaps key generated for http://openlayers.org/dev/ -->
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAA9XNhd8q0UdwNC7YSO4YZghSPUCi5aRYVveCcVYxzezM4iaj_gxQ9t-UajFL70jfcpquH5l1IJ-Zyyw'></script>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ var name = 'Test Layer';
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/jpeg'};
+
+ function test_Layer_WMS_Reproject (t) {
+ var validkey = (window.location.protocol == "file:") ||
+ (window.location.host == "localhost") ||
+ (window.location.host == "openlayers.org");
+ if (OpenLayers.BROWSER_NAME == "opera" || OpenLayers.BROWSER_NAME == "safari") {
+ t.plan(1);
+ t.debug_print("Can't test google reprojection in Opera or Safari.");
+ } else if(validkey) {
+ t.plan(5);
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ var layer = new OpenLayers.Layer.Google("Google");
+ map.addLayer(layer);
+ var wmslayer = new OpenLayers.Layer.WMS(name, url, params,
+ {isBaseLayer: false, reproject:true, buffer: 2});
+ wmslayer.isBaseLayer=false;
+ map.addLayer(wmslayer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = wmslayer.grid[0][0];
+ t.eq( tile.bounds.left, -22.5, "left side matches" );
+ t.eq( tile.bounds.right, -11.25, "right side matches" );
+ t.eq( tile.bounds.bottom.toFixed(6), '11.781325', "bottom side matches" );
+ t.eq( tile.bounds.top.toFixed(6), '22.512557', "top side matches" );
+ map.destroy();
+ } else {
+ t.plan(1);
+ t.debug_print("can't test google layer from " +
+ window.location.host);
+ }
+
+ var map = new OpenLayers.Map('map', {tileManager: null});
+ layer = new OpenLayers.Layer.WMS(name, url, params, {buffer: 2});
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ var tile = layer.grid[0][0];
+ t.ok( tile.bounds.equals(new OpenLayers.Bounds(-33.75, 33.75, -22.5, 45)), "okay");
+
+ map.destroy();
+ }
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/WMS/Post.html b/misc/openlayers/tests/deprecated/Layer/WMS/Post.html
new file mode 100644
index 0000000..d79aec5
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/WMS/Post.html
@@ -0,0 +1,89 @@
+<html>
+<head>
+ <script src="../../../OLLoader.js"></script>
+ <script src="../../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+ var isMozilla = (navigator.userAgent.indexOf("compatible") == -1);
+ var isOpera = (navigator.userAgent.indexOf("Opera") != -1);
+ var layer;
+
+ var name = 'Test Layer';
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var params = { map: '/mapdata/vmap_wms.map',
+ layers: 'basic',
+ format: 'image/jpeg'};
+
+ function test_Layer_WMS_Post_constructor (t) {
+ t.plan( 2 );
+
+ var url = "http://octo.metacarta.com/cgi-bin/mapserv";
+ var options = {unsupportedBrowsers: []};
+ layer = new OpenLayers.Layer.WMS.Post(name, url, params, options);
+
+ t.eq(
+ layer.usePost, true,
+ "Supported browsers use IFrame tiles.");
+
+ layer.destroy();
+
+ var options = { unsupportedBrowsers: [OpenLayers.BROWSER_NAME]};
+ layer = new OpenLayers.Layer.WMS.Post(name, url, params, options);
+ t.eq(
+ layer.usePost, false,
+ "unsupported browsers use Image tiles.");
+ layer.destroy();
+ }
+
+ function test_Layer_WMS_Post_addtile (t) {
+ t.plan( 3 );
+
+ layer = new OpenLayers.Layer.WMS.Post(name, url, params);
+ var map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var pixel = new OpenLayers.Pixel(5,6);
+ var tile = layer.addTile(bounds, pixel);
+
+ if(isMozilla || isOpera) {
+ t.ok(
+ tile instanceof OpenLayers.Tile.Image,
+ "tile is an instance of OpenLayers.Tile.Image");
+ }
+ else {
+ t.ok(
+ tile.useIFrame !== undefined,
+ "tile is created with the OpenLayers.Tile.Image.IFrame mixin");
+ }
+ map.destroy();
+
+ // test the unsupported browser
+ layer = new OpenLayers.Layer.WMS.Post(name, url, params, {
+ unsupportedBrowsers: [OpenLayers.BROWSER_NAME]
+ });
+ map = new OpenLayers.Map('map');
+ map.addLayer(layer);
+ tile = layer.addTile(bounds, pixel);
+ t.ok(
+ tile instanceof OpenLayers.Tile.Image,
+ "unsupported browser: tile is an instance of Tile.Image");
+ layer.destroy();
+
+ // test a supported browser
+ layer = new OpenLayers.Layer.WMS.Post(name, url, params, {
+ unsupportedBrowsers: []
+ });
+ map.addLayer(layer);
+ var tile2 = layer.addTile(bounds, pixel);
+ tile2.draw();
+ t.eq(
+ tile2.useIFrame, true,
+ "supported browser: tile is created with the Tile.Image.IFrame mixin");
+ map.destroy();
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/Yahoo.html b/misc/openlayers/tests/deprecated/Layer/Yahoo.html
new file mode 100755
index 0000000..f7c67c0
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/Yahoo.html
@@ -0,0 +1,121 @@
+<html>
+<head>
+ <script src="http://api.maps.yahoo.com/ajaxymap?v=3.0&appid=euzuro-openlayers"></script>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+ var layer;
+
+ function test_Layer_Yahoo_constructor (t) {
+ t.plan( 4 );
+
+ var tempEventPane = OpenLayers.Layer.EventPane.prototype.initialize;
+ OpenLayers.Layer.EventPane.prototype.initialize = function(name, options) {
+ t.ok(name == g_Name, "EventPane initialize() called with correct name");
+ t.ok(options == g_Options, "EventPane initialize() called with correct options");
+ }
+
+ var tempFixedZoomLevels = OpenLayers.Layer.FixedZoomLevels.prototype.initialize;
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize = function(name, options) {
+ t.ok(name == g_Name, "FixedZoomLevels initialize() called with correct name");
+ t.ok(options == g_Options, "FixedZoomLevels initialize() called with correct options");
+ }
+
+
+ g_Name = {};
+ g_Options = {};
+ var l = new OpenLayers.Layer.Yahoo(g_Name, g_Options);
+
+ OpenLayers.Layer.EventPane.prototype.initialize = tempEventPane;
+ OpenLayers.Layer.FixedZoomLevels.prototype.initialize = tempFixedZoomLevels;
+ }
+
+ function test_Layer_Yahoo_loadMapObject(t) {
+ t.plan(5);
+
+ var temp = YMap;
+ YMap = OpenLayers.Class({
+ initialize: function(div, type, size) {
+ t.ok(div == g_Div, "correct div passed to YMap constructor");
+ t.ok(type == g_Type, "correct type passed to YMap constructor");
+ t.ok(size == g_YMapSize, "correct size passed to YMap constructor");
+ },
+ disableKeyControls: function() {
+ t.ok(true, "disableKeyControls called on map object");
+ }
+ });
+
+ g_Div = {};
+ g_Type = {};
+ g_MapSize = {};
+ g_YMapSize = {};
+
+ var l = new OpenLayers.Layer.Yahoo();
+ l.div = g_Div;
+ l.type = g_Type;
+ l.map = {
+ 'getSize': function() {
+ return g_MapSize;
+ }
+ };
+ l.getMapObjectSizeFromOLSize = function(mapSize) {
+ t.ok(mapSize == g_MapSize, "correctly translating map size from ol to YSize");
+ return g_YMapSize;
+ };
+
+ l.loadMapObject();
+
+ YMap = temp;
+ }
+
+ function test_Layer_Yahoo_onMapResize(t) {
+ t.plan(2);
+
+ g_MapSize = {};
+ g_YMapSize = {};
+
+ var l = new OpenLayers.Layer.Yahoo();
+ l.mapObject = {
+ 'resizeTo': function(size) {
+ t.ok(size == g_YMapSize, "correct YSize passed to reiszeTo on map object");
+ }
+ }
+ l.map = {
+ 'getSize': function() {
+ return g_MapSize;
+ }
+ };
+ l.getMapObjectSizeFromOLSize = function(mapSize) {
+ t.ok(mapSize == g_MapSize, "correctly translating map size from ol to YSize");
+ return g_YMapSize;
+ };
+
+ l.onMapResize();
+ }
+
+ function test_Layer_Yahoo_getMapObjectSizeFromOLSize(t) {
+ t.plan(2);
+
+ var temp = YSize;
+ YSize = function(w, h) {
+ t.ok(w == g_Size.w, "correct width passed to YSize constructor");
+ t.ok(h == g_Size.h, "correct height passed to YSize constructor");
+ }
+
+ g_Size = {
+ 'w': {},
+ 'h': {}
+ };
+
+ OpenLayers.Layer.Yahoo.prototype.getMapObjectSizeFromOLSize(g_Size);
+
+ YSize = temp;
+ }
+
+
+ </script>
+</head>
+<body>
+ <div id="map"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Layer/mice.xml b/misc/openlayers/tests/deprecated/Layer/mice.xml
new file mode 100644
index 0000000..4a001ec
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/mice.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:bsc="http://www.bsc-eoc.org/bsc"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd
+ http://www.bsc-eoc.org/bsc http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=OWLS&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-89.817223,45.005555 -74.755001,51.701388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember><bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110 -79.771668,45.891110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277 -83.755834,46.365277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:owlname>owl</bsc:owlname>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277 -83.808612,46.175277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166 -84.111112,46.309166</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110 -83.678612,46.821110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888 -83.664445,46.518888</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277 -80.613334,46.730277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054 -79.676946,45.428054</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944 -83.853056,46.236944</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388 -82.289167,45.896388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+
diff --git a/misc/openlayers/tests/deprecated/Layer/owls.xml b/misc/openlayers/tests/deprecated/Layer/owls.xml
new file mode 100644
index 0000000..4a001ec
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Layer/owls.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:bsc="http://www.bsc-eoc.org/bsc"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd
+ http://www.bsc-eoc.org/bsc http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=OWLS&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-89.817223,45.005555 -74.755001,51.701388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember><bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110 -79.771668,45.891110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277 -83.755834,46.365277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:owlname>owl</bsc:owlname>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277 -83.808612,46.175277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166 -84.111112,46.309166</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110 -83.678612,46.821110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888 -83.664445,46.518888</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277 -80.613334,46.730277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054 -79.676946,45.428054</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944 -83.853056,46.236944</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388 -82.289167,45.896388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+
diff --git a/misc/openlayers/tests/deprecated/Popup/AnchoredBubble.html b/misc/openlayers/tests/deprecated/Popup/AnchoredBubble.html
new file mode 100644
index 0000000..ffad069
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Popup/AnchoredBubble.html
@@ -0,0 +1,61 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/Rico/Corner.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ function test_Popup_Anchored_setOpacity(t) {
+ t.plan(5);
+ var opacity = 0.5;
+ var id = "chicken";
+ var w = 500;
+ var h = 400;
+ var sz = new OpenLayers.Size(w,h);
+ var lon = 5;
+ var lat = 40;
+ var ll = new OpenLayers.LonLat(lon, lat);
+ var content = "foo";
+ var x = 50;
+ var y = 100;
+
+ var map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer('name', {'isBaseLayer':true}));
+ map.zoomToMaxExtent();
+
+ var popup = new OpenLayers.Popup.AnchoredBubble(id,
+ ll,
+ sz,
+ content,
+ null,
+ false);
+ map.addPopup(popup);
+
+ popup.setOpacity(opacity);
+ popup.draw(new OpenLayers.Pixel(x, y));
+
+ if (navigator.appName.indexOf("Microsoft") == -1 || new RegExp(/msie 10/).test(navigator.userAgent.toLowerCase())) {
+ t.eq(parseFloat(popup.div.style.opacity), opacity, "good default popup.opacity");
+ } else {
+ t.eq(popup.div.style.filter, "alpha(opacity=" + opacity*100 + ")", "good default popup.opacity");
+ }
+
+ t.ok(popup.groupDiv!=null, "popup.groupDiv exists");
+ t.ok(popup.groupDiv.parentNode!=null, "popup.groupDiv.parentNode exists");
+ t.ok(popup.groupDiv.parentNode.getElementsByTagName("span").length > 0, "popup.groupDiv.parentNode has SPAN children");
+
+ var ricoCornerDiv = popup.groupDiv.parentNode.getElementsByTagName("span")[0];
+ if (navigator.appName.indexOf("Microsoft") == -1 || new RegExp(/msie 10/).test(navigator.userAgent.toLowerCase())) {
+ t.eq(parseFloat(ricoCornerDiv.style.opacity), opacity, "good default ricoCornerDiv.opacity");
+ } else {
+ t.eq(ricoCornerDiv.style.filter, "alpha(opacity=" + opacity*100 + ")", "good default ricoCornerDiv.opacity");
+ }
+
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:512px; height:256px"> </div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Protocol/SQL.html b/misc/openlayers/tests/deprecated/Protocol/SQL.html
new file mode 100644
index 0000000..f45203d
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Protocol/SQL.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ t.plan(3);
+ var options = {tableName: 'my_features',
+ databaseName: 'my_database_name'}
+ var protocol = new OpenLayers.Protocol.SQL(options);
+
+ t.ok(protocol instanceof OpenLayers.Protocol.SQL,
+ "new OpenLayers.Protocol.SQL returns object");
+
+ t.eq(protocol.tableName, options.tableName, "tableName property is set");
+ t.eq(protocol.databaseName, options.databaseName, "databaseName property is set");
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Protocol/SQL/Gears.html b/misc/openlayers/tests/deprecated/Protocol/SQL/Gears.html
new file mode 100644
index 0000000..0909fb4
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Protocol/SQL/Gears.html
@@ -0,0 +1,474 @@
+<html>
+<head>
+ <script src="../../../OLLoader.js"></script>
+ <script src="../../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ function test_initialize(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ t.eq(protocol.CLASS_NAME, "OpenLayers.Protocol.SQL.Gears",
+ "ctor returns correct value");
+
+ t.eq(protocol.jsonParser.CLASS_NAME,
+ "OpenLayers.Format.JSON",
+ "ctor creates a JSON parser");
+
+ t.eq(protocol.wktParser.CLASS_NAME,
+ "OpenLayers.Format.WKT",
+ "ctor creates a WKT parser");
+
+ var str = protocol.FID_PREFIX + "foo_bar";
+ t.ok(str.match(protocol.fidRegExp),
+ "ctor creates correct regexp");
+
+ t.ok(typeof protocol.db == "object",
+ "ctor creates a db object");
+
+ protocol.clear();
+ protocol.destroy();
+ }
+
+ function test_destroy(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(3);
+
+ protocol.destroy();
+
+ t.eq(protocol.db, null,
+ "destroy nullifies db");
+ t.eq(protocol.jsonParser, null,
+ "destroy nullifies jsonParser");
+ t.eq(protocol.wktParser, null,
+ "destroy nullifies wktParser");
+ }
+
+ function test_read(t) {
+ var protocolCallback, readCallback;
+ var protocolOptions = {callback: protocolCallback};
+ var readOptions = {callback: readCallback};
+
+ var protocol = new OpenLayers.Protocol.SQL.Gears(protocolOptions);
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ function okCallback(resp) {
+ t.eq(resp.CLASS_NAME, "OpenLayers.Protocol.Response",
+ "read calls correct callback with a response object");
+ }
+
+ function failCallback(resp) {
+ t.fail("read calls incorrect callback");
+ }
+
+ t.plan(4);
+
+ var resp;
+
+ // 2 tests
+ protocolOptions.callback = okCallback;
+ readOptions.callback = failCallback;
+ resp = protocol.read();
+ t.eq(resp.CLASS_NAME, "OpenLayers.Protocol.Response",
+ "read returns a response object");
+
+ // 2 test
+ protocolOptions.callback = failCallback;
+ readOptions.callback = okCallback;
+ resp = protocol.read(readOptions);
+ t.eq(resp.CLASS_NAME, "OpenLayers.Protocol.Response",
+ "read returns a response object");
+
+ protocol.clear();
+ protocol.destroy();
+ }
+
+ function test_unfreezeFeature(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(10);
+
+ var feature;
+ var wkt, json, fid, state;
+
+ json = "{\"fake\":\"properties\"}";
+ fid = "1000";
+ state = OpenLayers.State.INSERT;
+
+ var row = {
+ fieldByName: function(str) {
+ if (str == "geometry") {
+ return wkt;
+ }
+ if (str == "properties") {
+ return json;
+ }
+ if (str == "fid") {
+ return fid;
+ }
+ if (str == "state") {
+ return state;
+ }
+ }
+ };
+
+ // 5 tests
+ wkt = "POINT(1 2)";
+ feature = protocol.unfreezeFeature(row);
+ t.eq(feature.CLASS_NAME, "OpenLayers.Feature.Vector",
+ "unfreezeFeature returns an OpenLayers.Feature.Vector");
+ t.ok(feature.geometry.x == 1 && feature.geometry.y == 2,
+ "unfreezeFeature returns a feature with correct geometry");
+ t.eq(feature.attributes.fake, "properties",
+ "unfreezeFeature returns a feature with correct attributes");
+ t.eq(feature.fid, fid,
+ "unfreezeFeature returns a feature with fid");
+ t.eq(feature.state, state,
+ "unfreezeFeature returns a feature with state");
+
+ // 5 tests
+ wkt = protocol.NULL_GEOMETRY;
+ state = protocol.NULL_FEATURE_STATE;
+ feature = protocol.unfreezeFeature(row);
+ t.eq(feature.CLASS_NAME, "OpenLayers.Feature.Vector",
+ "unfreezeFeature returns an OpenLayers.Feature.Vector");
+ t.eq(feature.geometry, null,
+ "unfreezeFeature returns a feature with correct geometry");
+ t.eq(feature.attributes.fake, "properties",
+ "unfreezeFeature returns a feature with correct attributes");
+ t.eq(feature.fid, fid,
+ "unfreezeFeature returns a feature with fid");
+ t.eq(feature.state, null,
+ "unfreezeFeature returns a feature with state");
+
+ protocol.clear();
+ protocol.destroy();
+ }
+
+ function test_extractFidFromField(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var field, fid;
+
+ // fid is a string, field is not prefixed with FID_PREFIX
+ // 1 test
+ field = "10";
+ res = protocol.extractFidFromField(field);
+ t.eq(res, "10",
+ "extractFidFromField returns expected string");
+
+ // fid is a string, field is prefixed with FID_PREFIX
+ // 1 test
+ field = protocol.FIX_PREFIX + "10";
+ res = protocol.extractFidFromField(field);
+ t.eq(res, protocol.FIX_PREFIX + "10",
+ "extractFidFromField returns expected prefixed string");
+
+ // fid is a number, field is not prefixed with FIX_PREFIX
+ // 1 test
+ protocol.typeOfFid = "number";
+ field = "10";
+ res = protocol.extractFidFromField(field);
+ t.eq(res, 10,
+ "extractFidFromField returns expected number");
+
+ // fid is a number, field is prefixed with FIX_PREFIX
+ // 1 test
+ protocol.typeOfFid = "number";
+ field = protocol.FID_PREFIX + "10";
+ res = protocol.extractFidFromField(field);
+ t.eq(res, protocol.FID_PREFIX + "10",
+ "extractFidFromField returns expected prefixed string");
+ }
+
+ function test_freezeFeature(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(8);
+
+ var feature, res;
+
+ // 4 tests
+ feature = new OpenLayers.Feature.Vector();
+ feature.geometry = new OpenLayers.Geometry.Point(1, 2);
+ feature.attributes.fake = "properties";
+ feature.fid = "1000";
+ feature.state = OpenLayers.State.INSERT;
+ res = protocol.freezeFeature(feature);
+ t.eq(res[0], feature.fid,
+ "freezeFeature returns correct fid");
+ t.eq(res[1], "POINT(1 2)",
+ "freezeFeature returns correct WKT");
+ t.eq(res[2], "{\"fake\":\"properties\"}",
+ "freezeFeature returns correct JSON");
+ t.eq(res[3], feature.state,
+ "freezeFeature returns correct feature state");
+
+ // 4 tests
+ protocol.saveFeatureState = false;
+ feature = new OpenLayers.Feature.Vector();
+ feature.attributes.fake = "properties";
+ feature.fid = "1000";
+ feature.state = OpenLayers.State.INSERT;
+ res = protocol.freezeFeature(feature);
+ t.eq(res[0], feature.fid,
+ "freezeFeature returns correct fid");
+ t.eq(res[1], protocol.NULL_GEOMETRY,
+ "freezeFeature returns expected null geom string");
+ t.eq(res[2], "{\"fake\":\"properties\"}",
+ "freezeFeature returns correct JSON");
+ t.eq(res[3], protocol.NULL_FEATURE_STATE,
+ "freezeFeature returns expected null feature state string");
+
+ protocol.clear();
+ protocol.destroy();
+ }
+
+ function test_create(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(8);
+
+ var resp;
+ var scope = {"fake": "scope"};
+
+ var options = {
+ callback: function(resp) {
+ t.eq(resp.CLASS_NAME, "OpenLayers.Protocol.Response",
+ "user callback is passed a response");
+ t.eq(resp.requestType, "create",
+ "user callback is passed correct request type in resp");
+ t.ok(this == scope,
+ "user callback called with correct scope");
+ },
+ scope: scope
+ };
+
+ // 4 tests
+ var feature = new OpenLayers.Feature.Vector();
+ feature.fid = "1000";
+ feature.attributes.fake = "properties";
+ feature.state = OpenLayers.State.INSERT;
+ resp = protocol.create([feature], options);
+ t.eq(resp.CLASS_NAME, "OpenLayers.Protocol.Response",
+ "create returns a response");
+
+ // check what we have in the DB
+ // 4 tests
+ resp = protocol.read({"noFeatureStateReset": true});
+ t.eq(resp.features.length, 1,
+ "create inserts feature in the DB");
+ t.eq(resp.features[0].fid, feature.fid,
+ "create inserts feature with correct fid");
+ t.eq(resp.features[0].attributes.fake, feature.attributes.fake,
+ "create inserts feature with correct attributes");
+ t.eq(resp.features[0].state, feature.state,
+ "create inserts feature with correct state");
+
+ protocol.clear();
+ protocol.destroy();
+ }
+
+ function test_createOrUpdate(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ // 1 test
+ var feature = new OpenLayers.Feature.Vector();
+ feature.fid = "1000";
+ feature.attributes.fake = "properties";
+ feature.state = OpenLayers.State.INSERT;
+ resp = protocol.createOrUpdate([feature]);
+ t.eq(resp.CLASS_NAME, "OpenLayers.Protocol.Response",
+ "createOrUpdate returns a response");
+
+ // check what we have in the DB
+ // 4 tests
+ resp = protocol.read({"noFeatureStateReset": true});
+ t.eq(resp.features.length, 1,
+ "createOrUpdate inserts feature in the DB");
+ t.eq(resp.features[0].fid, feature.fid,
+ "createOrUpdate inserts feature with correct fid");
+ t.eq(resp.features[0].attributes.fake, feature.attributes.fake,
+ "createOrUpdate inserts feature with correct attributes");
+ t.eq(resp.features[0].state, feature.state,
+ "createOrUpdate inserts feature with correct state");
+
+ protocol.clear();
+ protocol.destroy();
+ }
+
+ function test_delete(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ function createOneAndDeleteOne(fid, deleteOptions) {
+ var feature = new OpenLayers.Feature.Vector();
+ feature.fid = fid;
+ feature.attributes.fake = "properties";
+ feature.state = OpenLayers.State.INSERT;
+ var r = protocol.create([feature]);
+ protocol["delete"](r.reqFeatures, deleteOptions);
+ }
+
+ var resp, fid;
+
+ // 1 test
+ fid = 1000;
+ protocol.saveFeatureState = false;
+ createOneAndDeleteOne(fid)
+ resp = protocol.read();
+ t.eq(resp.features.length, 0,
+ "delete deletes feature if saveFeatureState is false");
+ protocol.clear();
+
+ // 1 test
+ fid = 1000;
+ protocol.saveFeatureState = true;
+ createOneAndDeleteOne(fid);
+ resp = protocol.read();
+ t.eq(resp.features.length, 1,
+ "delete does not delete feature if saveFeatureState is true");
+ protocol.clear();
+
+ // 1 test
+ fid = "1000";
+ protocol.saveFeatureState = true;
+ createOneAndDeleteOne(fid);
+ resp = protocol.read();
+ t.eq(resp.features.length, 1,
+ "delete does not delete feature if saveFeatureState is true");
+ protocol.clear();
+
+ // 1 test
+ fid = protocol.FID_PREFIX + "1000";
+ protocol.saveFeatureState = true;
+ createOneAndDeleteOne(fid, {dontDelete: true});
+ resp = protocol.read();
+ t.eq(resp.features.length, 0,
+ "delete deletes feature if saveFeatureState is true and fid is prefixed");
+ protocol.clear();
+
+ protocol.destroy();
+ }
+
+ function test_callUserCallback(t) {
+ var protocol = new OpenLayers.Protocol.SQL.Gears();
+ if (!protocol.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(6);
+
+ var options, resp;
+ var scope = {'fake': 'scope'};
+
+ // test commit callback
+ // 1 tests
+ options = {
+ 'callback': function() {
+ t.ok(this == scope, 'callback called with correct scope');
+ },
+ 'scope': scope
+ };
+ resp = {'requestType': 'create', 'last': true};
+ protocol.callUserCallback(options, resp);
+ // 0 test
+ resp = {'requestType': 'create', 'last': false};
+ protocol.callUserCallback(options, resp);
+
+ // test create callback
+ // 2 tests
+ options = {
+ 'create': {
+ 'callback': function(r) {
+ t.ok(this == scope, 'callback called with correct scope');
+ t.ok(r == resp, 'callback called with correct response');
+ },
+ 'scope': scope
+ }
+ };
+ resp = {'requestType': 'create'};
+ protocol.callUserCallback(options, resp);
+
+ // test with both callbacks set
+ // 3 tests
+ options = {
+ 'create': {
+ 'callback': function(r) {
+ t.ok(this == scope, 'callback called with correct scope');
+ t.ok(r == resp, 'callback called with correct response');
+ },
+ 'scope': scope
+ },
+ 'callback': function() {
+ t.ok(this == scope, 'callback called with correct scope');
+ },
+ 'scope': scope
+ };
+ resp = {'requestType': 'create', 'last': true};
+ protocol.callUserCallback(options, resp);
+
+ // no callback set
+ // 0 test
+ options = {
+ 'delete': {
+ 'callback': function(resp) {
+ t.fail('callback should not get called');
+ }
+ }
+ };
+ resp = {'requestType': 'create'};
+ protocol.callUserCallback(options, resp);
+
+ // cleanup
+ protocol.destroy();
+ }
+
+ </script>
+</head>
+<body>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Renderer/SVG2.html b/misc/openlayers/tests/deprecated/Renderer/SVG2.html
new file mode 100644
index 0000000..c23b95c
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Renderer/SVG2.html
@@ -0,0 +1,399 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+
+ var geometry = null, node = null;
+
+ function test_SVG_constructor(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+ t.ok(r instanceof OpenLayers.Renderer.SVG2, "new OpenLayers.Renderer.SVG2 returns SVG object" );
+ }
+
+ function test_SVG_destroy(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var g_Destroy = false;
+
+ OpenLayers.Renderer.Elements.prototype._destroy =
+ OpenLayers.Renderer.Elements.prototype.destroy;
+
+ OpenLayers.Renderer.prototype.destroy = function() {
+ g_Destroy = true;
+ }
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+ r.destroy();
+
+ t.eq(g_Destroy, true, "OpenLayers.Renderer.Elements.destroy() called");
+
+ OpenLayers.Renderer.prototype.destroy =
+ OpenLayers.Renderer.prototype._destroy;
+ }
+
+ function test_SVG_updateDimensions(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(7);
+
+ OpenLayers.Renderer.SVG2.prototype._setExtent =
+ OpenLayers.Renderer.SVG2.prototype.setExtent;
+
+ var g_SetExtent = false;
+ OpenLayers.Renderer.SVG2.prototype.setExtent = function() {
+ g_SetExtent = true;
+ OpenLayers.Renderer.SVG2.prototype._setExtent.apply(this, arguments);
+ }
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+ var extent = new OpenLayers.Bounds(1,2,3,4);
+ r.map = {
+ getResolution: function() {
+ return 0.5;
+ },
+ getExtent: function() {
+ return extent;
+ },
+ getMaxExtent: function() {
+ return extent;
+ }
+ }
+ r.updateDimensions();
+
+ t.eq(g_SetExtent, true, "Elements.setExtent() called");
+
+ t.eq(r.extent.toString(), extent.scale(3).toString(), "renderer's extent is correct");
+ t.eq(r.rendererRoot.getAttributeNS(null, "width"), "12", "width is correct");
+ t.eq(r.rendererRoot.getAttributeNS(null, "height"), "12", "height is correct");
+ t.eq(r.rendererRoot.getAttributeNS(null, "viewBox"), "-1 -6 6 6", "rendererRoot viewBox is correct");
+
+ // test extent changes
+ extent = new OpenLayers.Bounds(2,3,5,6);
+ r.updateDimensions();
+ t.eq(r.extent.toString(), extent.scale(3).toString(), "renderer's extent changed after updateDimensions");
+ t.eq(r.rendererRoot.getAttributeNS(null, "viewBox"), "-1 -9 9 9", "rendererRoot viewBox is correct after a new setExtent");
+
+ OpenLayers.Renderer.SVG2.prototype.setExtent =
+ OpenLayers.Renderer.SVG2.prototype._setExtent;
+ }
+
+ function test_SVG_drawpoint(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ var properDraw = false;
+ var g_Radius = null;
+ r.drawCircle = function(n, g, r) {
+ properDraw = true;
+ g_Radius = 1;
+ }
+ r.drawPoint();
+
+ t.ok(properDraw && g_Radius == 1, "drawPoint called drawCircle with radius set to 1");
+ }
+
+ function test_SVG_drawcircle(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+ r.resolution = 0.5;
+ r.left = 0;
+ r.top = 0;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ x: 1,
+ y: 2
+ }
+
+ r.drawCircle(node, geometry, 3);
+
+ t.eq(node.getAttributeNS(null, 'cx'), '1', "cx is correct");
+ t.eq(node.getAttributeNS(null, 'cy'), '-2', "cy is correct");
+ t.eq(node._radius, 3, "radius preset is correct");
+
+ // #1274: out of bound node fails when first added
+ var geometry = {
+ x: 10000000,
+ y: 200000000,
+ CLASS_NAME: "OpenLayers.Geometry.Point",
+ id: "foo",
+ getBounds: function() {return {bottom: 0}}
+ }
+ node.id = geometry.id;
+ r.root.appendChild(node);
+
+ var drawCircleCalled = false;
+ r.drawCircle = function() {
+ drawCircleCalled = true;
+ return OpenLayers.Renderer.SVG2.prototype.drawCircle.apply(r, arguments);
+ }
+
+ r.drawGeometry(geometry, {pointRadius: 3}, "blah_4000");
+ t.eq(drawCircleCalled, true, "drawCircle called on drawGeometry for a point geometry.")
+ t.ok(node.parentNode != r.root, "circle will not be drawn when coordinates are outside the valid range");
+ }
+
+ function test_SVG_drawlinestring(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ components: "foo"
+ }
+ g_GetString = false;
+ g_Components = null;
+ r.getComponentsString = function(c) {
+ g_GetString = true;
+ g_Components = c;
+ return "bar";
+ }
+
+ r.drawLineString(node, geometry);
+
+ t.ok(g_GetString && g_Components == "foo", "getComponentString is called with valid arguments");
+ t.eq(node.getAttributeNS(null, "points"), "bar", "points attribute is correct");
+ }
+
+ function test_SVG_drawlinearring(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ components: "foo"
+ }
+ g_GetString = false;
+ g_Components = null;
+ r.getComponentsString = function(c) {
+ g_GetString = true;
+ g_Components = c;
+ return "bar";
+ }
+
+ r.drawLinearRing(node, geometry);
+
+ t.ok(g_GetString, "getComponentString is called with valid arguments");
+ t.eq(node.getAttributeNS(null, "points"), "bar", "points attribute is correct");
+ }
+
+ function test_SVG_drawpolygon(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(3);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ var node = document.createElement('div');
+
+ var linearRings = [{
+ components: ["foo"]
+ },{
+ components: ["bar"]
+ }]
+
+ var geometry = {
+ components: linearRings
+ }
+ g_GetString = false;
+ r.getShortString = function(c) {
+ g_GetString = true;
+ return c;
+ }
+
+ r.drawPolygon(node, geometry);
+
+ t.ok(g_GetString, "getShortString is called");
+ t.eq(node.getAttributeNS(null, "d"), "M foo M bar z", "d attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "fill-rule"), "evenodd", "fill-rule attribute is correctly set");
+ }
+
+ function test_SVG_drawrectangle(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(4);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+ r.resolution = 0.5;
+ r.left = 0;
+ r.top = 0;
+
+ var node = document.createElement('div');
+
+ var geometry = {
+ x: 1,
+ y: 2,
+ width: 3,
+ height: 4
+ }
+
+ r.drawRectangle(node, geometry);
+
+ t.eq(node.getAttributeNS(null, "x"), "1", "x attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "y"), "-2", "y attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "width"), "3", "width attribute is correctly set");
+ t.eq(node.getAttributeNS(null, "height"), "4", "height attribute is correctly set");
+ }
+
+ function test_SVG_getcomponentsstring(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var components = ['foo', 'bar'];
+
+ OpenLayers.Renderer.SVG2.prototype._getShortString =
+ OpenLayers.Renderer.SVG2.prototype.getShortString;
+
+ OpenLayers.Renderer.SVG2.prototype.getShortString = function(p) {
+ return p;
+ };
+
+ var string = OpenLayers.Renderer.SVG2.prototype.getComponentsString(components);
+ t.eq(string, "foo,bar", "returned string is correct");
+
+ OpenLayers.Renderer.SVG2.prototype.getShortString =
+ OpenLayers.Renderer.SVG2.prototype._getShortString;
+ }
+
+
+
+ function test_SVG_getshortstring(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+ r.resolution = 0.5;
+ r.left = 0;
+ r.top = 0;
+
+ var point = {
+ x: 1,
+ y: 2
+ };
+
+ var string = r.getShortString(point);
+ t.eq(string, "1,-2", "returned string is correct");
+ }
+
+ function test_svg_getnodetype(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(1);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ var g = {CLASS_NAME: "OpenLayers.Geometry.Point"}
+ var s = {graphicName: "square"};
+
+ t.eq(r.getNodeType(g, s), "svg", "Correct node type for well known symbols");
+ }
+
+ function test_svg_importsymbol(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(2);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ r.importSymbol("square");
+
+ var polygon = document.getElementById(r.container.id + "_defs").firstChild.firstChild;
+
+ var pass = false;
+ for (var i = 0; i < polygon.points.numberOfItems; i++) {
+ var p = polygon.points.getItem(i);
+ pass = p.x === OpenLayers.Renderer.symbol.square[2*i] &&
+ p.y === OpenLayers.Renderer.symbol.square[2*i+1];
+ if (!pass) {
+ break;
+ }
+ }
+ t.ok(pass, "Square symbol rendered correctly");
+ t.ok(r.symbolMetrics["-square"], "Symbol metrics cached correctly.");
+ }
+
+ function test_svg_dashstyle(t) {
+ if (!OpenLayers.Renderer.SVG2.prototype.supported()) {
+ t.plan(0);
+ return;
+ }
+
+ t.plan(5);
+
+ var r = new OpenLayers.Renderer.SVG2(document.body);
+
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "dot"}, 1), "1,4", "dot dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "dash"}, 1), "4,4", "dash dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "longdash"}, 1), "8,4", "longdash dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "dashdot"}, 1), "4,4,1,4", "dashdot dasharray created correctly");
+ t.eq(r.dashStyle({strokeWidth: 1, strokeDashstyle: "longdashdot"}, 1), "8,4,1,4", "dashdot dasharray created correctly");
+ }
+
+ </script>
+</head>
+<body>
+<div id="map" style="width:500px;height:550px"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/deprecated/Tile/WFS.html b/misc/openlayers/tests/deprecated/Tile/WFS.html
new file mode 100644
index 0000000..3dee1c7
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Tile/WFS.html
@@ -0,0 +1,215 @@
+<html>
+<head>
+ <script src="../../OLLoader.js"></script>
+ <script src="../../../lib/deprecated.js"></script>
+ <script type="text/javascript">
+ var tile;
+
+ var map, layer;
+ function setUp() {
+ map = new OpenLayers.Map("map");
+ layer = new OpenLayers.Layer(null, {
+ isBaseLayer: true
+ });
+ map.addLayer(layer)
+ map.setCenter(new OpenLayers.LonLat(0, 0));
+ }
+
+ function tearDown() {
+ map.destroy();
+ map = null;
+ layer = null;
+ }
+
+ function test_Tile_WFS_constructor (t) {
+ t.plan( 8 );
+ setUp();
+
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ tile = new OpenLayers.Tile.WFS(layer, position, bounds, url, size);
+
+ t.ok( tile instanceof OpenLayers.Tile.WFS, "new OpenLayers.Tile.WFS returns Tile.WFS object" );
+ t.ok( tile.layer === layer, "tile.layer set correctly");
+ t.ok( tile.position.equals(position), "tile.position set correctly");
+ t.ok( tile.bounds.equals(bounds), "tile.bounds set correctly");
+ t.eq( tile.url, url, "tile.url set correctly");
+ t.ok( tile.size.equals(size), "tile.size is set correctly" );
+
+ t.ok( tile.id != null, "tile is given an id");
+ t.ok( tile.events != null, "tile's events intitialized");
+
+ tearDown();
+ }
+
+ function test_Tile_WFS_requestSuccess(t) {
+ t.plan(2);
+ setUp();
+
+ var tile = {
+ 'request': {}
+ };
+
+ OpenLayers.Tile.WFS.prototype.requestSuccess.apply(tile, []);
+
+ t.ok(tile.request == null, "request property on tile set to null");
+
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ tile = new OpenLayers.Tile.WFS(layer, position, bounds, url, size);
+ tile.destroy();
+ tile.requestSuccess({'requestText': '<xml><foo /></xml>'});
+ t.ok(true, "Didn't fail after calling requestSuccess on destroyed tile.");
+
+ tearDown();
+ }
+
+ function test_Tile_WFS_loadFeaturesForRegion(t) {
+ t.plan(9);
+
+ var tile = {
+ 'url': {}
+ };
+
+ var g_Success = {};
+
+ var _get = OpenLayers.Request.GET;
+ OpenLayers.Request.GET = function(config) {
+ t.ok(config.url == tile.url, "tile's url correctly passed");
+ t.ok(config.params == null, "null params");
+ t.ok(config.scope == tile, "tile passed as scope");
+ t.ok(config.success == g_Success, "success passed");
+ };
+
+ //no running request -- 4 tests
+ OpenLayers.Tile.WFS.prototype.loadFeaturesForRegion.apply(tile, [g_Success]);
+
+ //running request (cancelled) -- 4 tests + 1 test (for request abort)
+ tile.request = {
+ 'abort': function() {
+ t.ok(true, "request aborted");
+ }
+ };
+ OpenLayers.Tile.WFS.prototype.loadFeaturesForRegion.apply(tile, [g_Success]);
+
+ OpenLayers.Request.GET = _get;
+ }
+
+ function test_Tile_WFS_destroy(t) {
+ t.plan(9);
+ setUp();
+
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ tile = new OpenLayers.Tile.WFS(layer, position, bounds, url, size);
+ tile.events.destroy = function() {
+ t.ok(true, "tile events destroy() called");
+ };
+
+
+ var _gAbort = false;
+ tile.request = {
+ abort: function() {
+ _gAbort = true;
+ }
+ }
+
+
+ tile.destroy();
+
+ t.ok(tile.layer == null, "tile.layer set to null");
+ t.ok(tile.bounds == null, "tile.bounds set to null");
+ t.ok(tile.size == null, "tile.size set to null");
+ t.ok(tile.position == null, "tile.position set to null");
+ t.ok(_gAbort, "request transport is aborted");
+ t.ok(tile.request == null, "tile.request set to null");
+
+ t.ok(tile.events == null, "tile.events set to null");
+
+ tile.requestSuccess({'requestText': '<xml><foo /></xml>'});
+ t.ok(true, "Didn't fail after calling requestSuccess on destroyed tile.");
+
+ tearDown();
+ }
+ function test_nonxml_format(t) {
+ t.plan(2);
+
+ setUp();
+
+ var data = '{"type":"Feature", "id":"OpenLayers.Feature.Vector_135", "properties":{}, "geometry":{"type":"Point", "coordinates":[118.125, -18.6328125]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}'
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ var log = [];
+
+ var l = new OpenLayers.Layer(null, {
+ vectorMode: true,
+ formatObject: new OpenLayers.Format.GeoJSON(),
+ addFeatures: function(features) {
+ log.push(features);
+ }
+ })
+ map.addLayer(l);
+
+ var tile = new OpenLayers.Tile.WFS(l, position, bounds, url, size);
+
+ tile.requestSuccess({responseText: data});
+
+ t.eq(log.length, 1, "one call logged")
+ t.eq(log[0] && log[0].length, 1, "GeoJSON format returned a single feature which was added.");
+
+ tearDown();
+ }
+
+ function test_xml_string_and_dom(t) {
+ t.plan(4);
+ setUp();
+
+ var data = '<?xml version="1.0" encoding="ISO-8859-1" ?><wfs:FeatureCollection xmlns:bsc="http://www.bsc-eoc.org/bsc" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd http://www.bsc-eoc.org/bsc http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=OWLS&amp;OUTPUTFORMAT=XMLSCHEMA"> <gml:boundedBy> <gml:Box srsName="EPSG:4326"> <gml:coordinates>-94.989723,43.285833 -74.755001,51.709520</gml:coordinates> </gml:Box> </gml:boundedBy> <gml:featureMember> <bsc:OWLS> <gml:boundedBy> <gml:Box srsName="EPSG:4326"> <gml:coordinates>-94.142500,50.992777 -94.142500,50.992777</gml:coordinates> </gml:Box> </gml:boundedBy> <bsc:msGeometry> <gml:Point srsName="EPSG:4326"> <gml:coordinates>-94.142500,50.992777</gml:coordinates> </gml:Point> </bsc:msGeometry> <bsc:ROUTEID>ON_2</bsc:ROUTEID> <bsc:ROUTE_NAME>Suffel Road</bsc:ROUTE_NAME> <bsc:LATITUDE>50.9927770</bsc:LATITUDE> <bsc:LONGITUDE>-94.1425000</bsc:LONGITUDE> </bsc:OWLS> </gml:featureMember></wfs:FeatureCollection>';
+ var position = new OpenLayers.Pixel(10,20);
+ var bounds = new OpenLayers.Bounds(1,2,3,4);
+ var url = "bobob";
+ var size = new OpenLayers.Size(5,6);
+
+ var l = new OpenLayers.Layer();
+ map.addLayer(l);
+
+ var tile = new OpenLayers.Tile.WFS(l, position, bounds, url, size);
+
+ var log = [];
+ tile.addResults = function(results) {
+ log.push(results);
+ }
+ tile.requestSuccess({responseText: data});
+
+ t.eq(log.length, 1, "first call logged");
+ t.eq(log[0] && log[0].length, 1, "results count is correct when passing in XML as a string into non-vectormode");
+
+ log.length = 0;
+ tile.addResults = function(results) {
+ log.push(results);
+ }
+ tile.requestSuccess({responseXML: OpenLayers.Format.XML.prototype.read(data)});
+
+ t.eq(log.length, 1, "second call logged");
+ t.eq(log[0] && log[0].length, 1, "results count is correct when passing in XML as DOM into non-vectormode");
+
+ tearDown();
+ }
+ </script>
+</head>
+<body>
+</body>
+</html>
+
diff --git a/misc/openlayers/tests/deprecated/Util.html b/misc/openlayers/tests/deprecated/Util.html
new file mode 100644
index 0000000..e65340b
--- /dev/null
+++ b/misc/openlayers/tests/deprecated/Util.html
@@ -0,0 +1,20 @@
+<html>
+ <head>
+ <script>
+var custom$ = function() {};
+window.$ = custom$;
+ </script>
+ <script src="../OLLoader.js"></script>
+ <script src="../../lib/deprecated.js"></script>
+ <script>
+
+function test_$(t) {
+ t.plan(1);
+ t.ok($ === custom$, "OpenLayers doesn't clobber existing definition of $.");
+}
+
+ </script>
+ </head>
+ <body>
+ </body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/georss.txt b/misc/openlayers/tests/georss.txt
new file mode 100644
index 0000000..053749b
--- /dev/null
+++ b/misc/openlayers/tests/georss.txt
@@ -0,0 +1,378 @@
+<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet type="text/css" href="/css/rss.css" ?>
+
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://purl.org/rss/1.0/"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:georss="http://www.georss.org/georss">
+<docs>This is an RSS file. Copy the URL into your aggregator of choice. If you don't know what this means and want to learn more, please see: <span>http://platial.typepad.com/news/2006/04/really_simple_t.html</span> for more info.</docs><channel rdf:about="http://platial.com">
+<link>http://platial.com</link>
+<title>Crschmidt's Places At Platial</title>
+<description></description>
+<items>
+<rdf:Seq>
+<rdf:li resource="http://platial.com/place/90306"/>
+<rdf:li resource="http://platial.com/place/67230"/>
+<rdf:li resource="http://platial.com/place/65645"/>
+<rdf:li resource="http://platial.com/place/62200"/>
+<rdf:li resource="http://platial.com/place/28232"/>
+<rdf:li resource="http://platial.com/place/43666"/>
+<rdf:li resource="http://platial.com/place/28394"/>
+<rdf:li resource="http://platial.com/place/28251"/>
+<rdf:li resource="http://platial.com/place/28392"/>
+<rdf:li resource="http://platial.com/place/28391"/>
+<rdf:li resource="http://platial.com/place/28231"/>
+<rdf:li resource="http://platial.com/place/28393"/>
+<rdf:li resource="http://platial.com/place/31685"/>
+<rdf:li resource="http://platial.com/place/28596"/>
+<rdf:li resource="http://platial.com/place/28595"/>
+<rdf:li resource="http://platial.com/place/28594"/>
+<rdf:li resource="http://platial.com/place/28593"/>
+<rdf:li resource="http://platial.com/place/28592"/>
+<rdf:li resource="http://platial.com/place/28591"/>
+<rdf:li resource="http://platial.com/place/28590"/>
+<rdf:li resource="http://platial.com/place/28589"/>
+<rdf:li resource="http://platial.com/place/28588"/>
+<rdf:li resource="http://platial.com/place/28587"/>
+<rdf:li resource="http://platial.com/place/28586"/>
+<rdf:li resource="http://platial.com/place/28585"/>
+<rdf:li resource="http://platial.com/place/28584"/>
+<rdf:li resource="http://platial.com/place/28583"/>
+<rdf:li resource="http://platial.com/place/28582"/>
+<rdf:li resource="http://platial.com/place/28581"/>
+<rdf:li resource="http://platial.com/place/28580"/>
+<rdf:li resource="http://platial.com/place/28579"/>
+<rdf:li resource="http://platial.com/place/28578"/>
+<rdf:li resource="http://platial.com/place/28577"/>
+<rdf:li resource="http://platial.com/place/28576"/>
+<rdf:li resource="http://platial.com/place/28575"/>
+<rdf:li resource="http://platial.com/place/28574"/>
+<rdf:li resource="http://platial.com/place/28573"/>
+<rdf:li resource="http://platial.com/place/28572"/>
+<rdf:li resource="http://platial.com/place/28571"/>
+<rdf:li resource="http://platial.com/place/28570"/>
+</rdf:Seq>
+</items>
+</channel>
+<item rdf:about="http://platial.com/place/90306">
+<link>http://platial.com/place/90306</link>
+<title>Knitting Room</title>
+<description><![CDATA[This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/90306">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/90306">Grab this on Platial</a> ]]></description>
+<georss:point>42.405696 -71.142197</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-06-08T17:35:01.942452+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/67230">
+<link>http://platial.com/place/67230</link>
+<title>Knitting Room</title>
+<description><![CDATA[This little shop is jammed full. Yarn, yarn everywhere. They make the most of every possible nook and cranny. I like this place also because they have a lot of different kinds of knitting needles in all different sizes. Also, the people who work here are younger and hipper than in the other stores I go to. I reccomend buying supplies here and then knitting your way through a good documentary at the Capitol Theater across the street.<br/>Address: 2 lake St, Arlington, MA <br/>Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats<br /><br /><a href="http://platial.com/place/67230">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/67230">Grab this on Platial</a> ]]></description>
+<georss:point>42.405524 -71.142273</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-24T11:35:26.733857+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/65645">
+<link>http://platial.com/place/65645</link>
+<title>†¢¢™£ˆøœ</title>
+<description><![CDATA[ijeª£∆µˆ˚î<br/>Address: 151 Erie St., Cambridge, MA<br/>Tags: platial graffiti<br /><br /><a href="http://platial.com/place/65645">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/65645">Grab this on Platial</a> ]]></description>
+<georss:point>42.352455 -71.110210</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-20T08:56:12.696224+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/62200">
+<link>http://platial.com/place/62200</link>
+<title>Allen Hall</title>
+<description><![CDATA[My dorm at UIUC.<br/>Address: 1301 W Gregory Dr, Urbana, IL<br/>Tags: dorm, uiuc, college<br/><a href="http://platial.com/place/62200"><img src="http://platial.comhttp://static.flickr.com/4/8576450_0d59cc2531_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/62200">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/62200">Grab this on Platial</a> ]]></description>
+<georss:point>40.104172 -88.220623</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-04-14T08:01:01.872873+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28232">
+<link>http://platial.com/place/28232</link>
+<title>Bagby Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C. However, the area around the springs are not exactly well looked upon by people who know the place.
+
+<br/>Tags: 20s, rosalie, romance, childhood, hike, camping, soak, relax, beautiful, hot springs, bathhouse, favorite, popular, crowded, organized, honeymoon tub, plumbing made from hollowed out trees, hot springs, mt hood, notorious car break in spot, rash, bacteria<br /><br /><a href="http://platial.com/place/28232">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28232">Grab this on Platial</a> ]]></description>
+<georss:point>44.936000 -122.173000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:10:18.553063+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/43666">
+<link>http://platial.com/place/43666</link>
+<title>Shooting Location for "The Field of Dreams" Film</title>
+<description><![CDATA[1989's Field of Dreams was a Best Picture Academy Award nominee, and the baseball field in the cornfield still stands today, and has become quite a tourist destination. Games are occasionally played at the field, re-enacting professional baseball at the turn of the 20th Century.<br/>Address: Dyersville, Iowa<br/>Tags: iowa, baseball, movie locations, field of dreams, kevin costner, costner, dyersville, kinsella, james earl jones, chicago black sox, shoeless joe, joe jackson, famous farms, film, movie, cinema, shooting location<br /><br /><a href="http://platial.com/place/43666">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/43666">Grab this on Platial</a> ]]></description>
+<georss:point>42.481213 -91.111679</georss:point>
+<dc:creator>echinodermata</dc:creator>
+<dc:date>2006-03-23T11:40:17.654061+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28394">
+<link>http://platial.com/place/28394</link>
+<title>Moffetts (Bonneville) Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 97 degress F, 36 degress C<br/>Tags: soak, hot springs, relax, nature<br /><br /><a href="http://platial.com/place/28394">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28394">Grab this on Platial</a> ]]></description>
+<georss:point>45.658000 -121.962000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:27.329816+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28251">
+<link>http://platial.com/place/28251</link>
+<title>Austin Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 186 degress F, 86 degress C<br/>Tags: soak, hot springs, relax, nature, popular, crowded<br /><br /><a href="http://platial.com/place/28251">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28251">Grab this on Platial</a> ]]></description>
+<georss:point>45.021000 -122.009000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:11:04.489886+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28392">
+<link>http://platial.com/place/28392</link>
+<title>Rock Creek Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br/>Tags: soak, hot springs, relax, nature<br /><br /><a href="http://platial.com/place/28392">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28392">Grab this on Platial</a> ]]></description>
+<georss:point>45.723000 -121.927000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:22.636855+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28391">
+<link>http://platial.com/place/28391</link>
+<title>St. Martins (Wind River) Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 120 degress F, 49 degress C<br/>Tags: hot springs, soak, relax, nature, wonderful<br /><br /><a href="http://platial.com/place/28391">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28391">Grab this on Platial</a> ]]></description>
+<georss:point>45.728000 -121.800000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:20.383244+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28231">
+<link>http://platial.com/place/28231</link>
+<title>Breitenbush Hot Springs, OR</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br/>Tags: hot springs, resort, relax, nature, beautiful, http:www.breitenbush.com, soaking<br /><br /><a href="http://platial.com/place/28231">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28231">Grab this on Platial</a> ]]></description>
+<georss:point>44.782000 -121.975000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:10:16.529195+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28393">
+<link>http://platial.com/place/28393</link>
+<title>Collins Hot Springs, WA</title>
+<description><![CDATA[Hot spring, temperature: 122 degress F, 50 degress C<br/>Tags: portland, nice, hot springs, soak<br /><br /><a href="http://platial.com/place/28393">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28393">Grab this on Platial</a> ]]></description>
+<georss:point>45.701000 -121.728000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:16:24.648745+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/31685">
+<link>http://platial.com/place/31685</link>
+<title>Darwin's Ltd.</title>
+<description><![CDATA[Nice little coffee shop/cafe, free Wifi, close enough to walk from Harvard Square.<br/>Address: 148 Mount Auburn St, Cambridge, MA<br/>Tags: coffee, beer, sandwiches, freewifi<br/><a href="http://platial.com/place/31685"><img src="http://platial.comhttp://static.flickr.com/38/84885937_74fd3d1025_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/31685">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/31685">Grab this on Platial</a> ]]></description>
+<georss:point>42.373974 -71.125053</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-10T09:24:08.152985+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28596">
+<link>http://platial.com/place/28596</link>
+<title>Huckleberry Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, Boiling degress C<br /><br /><a href="http://platial.com/place/28596">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28596">Grab this on Platial</a> ]]></description>
+<georss:point>44.115000 -110.684000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:32.283094+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28595">
+<link>http://platial.com/place/28595</link>
+<title>South Entrance Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 156 degress F, 69 degress C<br/><a href="http://platial.com/place/28595"><img src="http://platial.comhttp://static.flickr.com/52/130989872_f1457f68b5_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/28595">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28595">Grab this on Platial</a> ]]></description>
+<georss:point>44.142000 -110.656000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:30.279497+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28594">
+<link>http://platial.com/place/28594</link>
+<title>Crawfish Creek Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C<br/><a href="http://platial.com/place/28594"><img src="http://platial.comhttp://static.flickr.com/52/128312256_d6a879924c_s.jpg"/></a><br/><br /><br /><a href="http://platial.com/place/28594">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28594">Grab this on Platial</a> ]]></description>
+<georss:point>44.157000 -110.699000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:28.280271+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28593">
+<link>http://platial.com/place/28593</link>
+<title>Crawfish Creek Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 138 degress F, 59 degress C<br /><br /><a href="http://platial.com/place/28593">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28593">Grab this on Platial</a> ]]></description>
+<georss:point>44.165000 -110.723000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:20.364077+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28592">
+<link>http://platial.com/place/28592</link>
+<title>Snake Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 136 degress F, 58 degress C<br /><br /><a href="http://platial.com/place/28592">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28592">Grab this on Platial</a> ]]></description>
+<georss:point>44.169000 -110.583000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:12.234974+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28591">
+<link>http://platial.com/place/28591</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 142 degress F, 61 degress C<br /><br /><a href="http://platial.com/place/28591">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28591">Grab this on Platial</a> ]]></description>
+<georss:point>44.187000 -110.726000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:10.027857+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28590">
+<link>http://platial.com/place/28590</link>
+<title>Hot Springs on Upper Snake River, WY</title>
+<description><![CDATA[Hot spring, temperature: 167 degress F, 75 degress C<br /><br /><a href="http://platial.com/place/28590">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28590">Grab this on Platial</a> ]]></description>
+<georss:point>44.204000 -110.486000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:07.79658+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28589">
+<link>http://platial.com/place/28589</link>
+<title>Hot Springs on lewis Lake, WY</title>
+<description><![CDATA[Hot spring, temperature: 154 degress F, 68 degress C<br /><br /><a href="http://platial.com/place/28589">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28589">Grab this on Platial</a> ]]></description>
+<georss:point>44.276000 -110.636000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:05.683418+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28588">
+<link>http://platial.com/place/28588</link>
+<title>Rustic Geyser, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28588">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28588">Grab this on Platial</a> ]]></description>
+<georss:point>44.282000 -110.506000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:03.66329+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28587">
+<link>http://platial.com/place/28587</link>
+<title>Bechler River Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 194 degress F, 90 degress C<br /><br /><a href="http://platial.com/place/28587">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28587">Grab this on Platial</a> ]]></description>
+<georss:point>44.285000 -110.900000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:24:01.611442+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28586">
+<link>http://platial.com/place/28586</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, 201 degress C<br /><br /><a href="http://platial.com/place/28586">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28586">Grab this on Platial</a> ]]></description>
+<georss:point>44.290000 -110.504000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:59.658699+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28585">
+<link>http://platial.com/place/28585</link>
+<title>Heart Lake Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: Middle Group degress F, 174 degress C<br /><br /><a href="http://platial.com/place/28585">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28585">Grab this on Platial</a> ]]></description>
+<georss:point>44.299000 -110.517000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:57.181801+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28584">
+<link>http://platial.com/place/28584</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Boiling degress F, 201 degress C<br /><br /><a href="http://platial.com/place/28584">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28584">Grab this on Platial</a> ]]></description>
+<georss:point>44.307000 -110.526000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:55.240485+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28583">
+<link>http://platial.com/place/28583</link>
+<title>Hot Springs on lewis Lake, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28583">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28583">Grab this on Platial</a> ]]></description>
+<georss:point>44.309000 -110.654000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:53.22295+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28582">
+<link>http://platial.com/place/28582</link>
+<title>Shoshone Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28582">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28582">Grab this on Platial</a> ]]></description>
+<georss:point>44.354000 -110.800000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:51.179049+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28581">
+<link>http://platial.com/place/28581</link>
+<title>Hot Springs on Continental Divide, WY</title>
+<description><![CDATA[Hot spring, temperature: 189 degress F, 87 degress C<br /><br /><a href="http://platial.com/place/28581">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28581">Grab this on Platial</a> ]]></description>
+<georss:point>44.401000 -110.936000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:49.077176+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28580">
+<link>http://platial.com/place/28580</link>
+<title>Hot Springs on Upper Firehole River, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28580">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28580">Grab this on Platial</a> ]]></description>
+<georss:point>44.404000 -110.824000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:47.054664+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28579">
+<link>http://platial.com/place/28579</link>
+<title>Summit Lake Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 162 degress F, 72 degress C<br /><br /><a href="http://platial.com/place/28579">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28579">Grab this on Platial</a> ]]></description>
+<georss:point>44.410000 -110.953000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:45.039394+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28578">
+<link>http://platial.com/place/28578</link>
+<title>Lone Star Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: Footbridge degress F, 183 degress C<br /><br /><a href="http://platial.com/place/28578">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28578">Grab this on Platial</a> ]]></description>
+<georss:point>44.414000 -110.817000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:42.938808+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28577">
+<link>http://platial.com/place/28577</link>
+<title>West. Thumb Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28577">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28577">Grab this on Platial</a> ]]></description>
+<georss:point>44.417000 -110.570000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:40.90238+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28576">
+<link>http://platial.com/place/28576</link>
+<title>Lone Star Geyser, WY</title>
+<description><![CDATA[Hot spring, temperature: 199 degress F, 93 degress C<br /><br /><a href="http://platial.com/place/28576">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28576">Grab this on Platial</a> ]]></description>
+<georss:point>44.418000 -110.805000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:38.844625+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28575">
+<link>http://platial.com/place/28575</link>
+<title>Smoke Jumper Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br /><br /><a href="http://platial.com/place/28575">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28575">Grab this on Platial</a> ]]></description>
+<georss:point>44.421000 -110.952000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:36.818513+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28574">
+<link>http://platial.com/place/28574</link>
+<title>West. Thumb Geyser Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 196 degress F, 91 degress C<br /><br /><a href="http://platial.com/place/28574">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28574">Grab this on Platial</a> ]]></description>
+<georss:point>44.422000 -110.574000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:34.767729+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28573">
+<link>http://platial.com/place/28573</link>
+<title>Potts Hot Spring Basin, WY</title>
+<description><![CDATA[Hot spring, temperature: 203 degress F, 95 degress C<br /><br /><a href="http://platial.com/place/28573">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28573">Grab this on Platial</a> ]]></description>
+<georss:point>44.433000 -110.581000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:32.749915+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28572">
+<link>http://platial.com/place/28572</link>
+<title>Hot Springs, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28572">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28572">Grab this on Platial</a> ]]></description>
+<georss:point>44.433000 -110.813000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:30.829745+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28571">
+<link>http://platial.com/place/28571</link>
+<title>Hot Springs on Continental Divide, WY</title>
+<description><![CDATA[Hot spring, temperature: Hot degress F, Hot degress C<br /><br /><a href="http://platial.com/place/28571">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28571">Grab this on Platial</a> ]]></description>
+<georss:point>44.438000 -110.977000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:28.730401+00:00</dc:date>
+</item>
+<item rdf:about="http://platial.com/place/28570">
+<link>http://platial.com/place/28570</link>
+<title>SouthEastern Group, WY</title>
+<description><![CDATA[Hot spring, temperature: 198 degress F, 92 degress C<br /><br /><a href="http://platial.com/place/28570">Map this on Platial</a><br /> <a href="http://platial.com/place_grab/28570">Grab this on Platial</a> ]]></description>
+<georss:point>44.459000 -110.817000</georss:point>
+<dc:creator>crschmidt</dc:creator>
+<dc:date>2006-01-03T23:23:26.706763+00:00</dc:date>
+</item>
+</rdf:RDF> \ No newline at end of file
diff --git a/misc/openlayers/tests/grid_inittiles.html b/misc/openlayers/tests/grid_inittiles.html
new file mode 100644
index 0000000..40035a2
--- /dev/null
+++ b/misc/openlayers/tests/grid_inittiles.html
@@ -0,0 +1,30 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <style type="text/css">
+ #map {
+ width: 800px;
+ height: 475px;
+ border: 1px solid black;
+ }
+ </style>
+ <script src="../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ function init(){
+ var map = new OpenLayers.Map('map', {'maxResolution': 1.40625/2, tileSize: new OpenLayers.Size(256,256)});
+ ww = new OpenLayers.Layer.WMS( "Basic",
+ "http://labs.metacarta.com/wms-c/Basic.py?",
+ {layers:"basic"});
+ map.addLayers([ww]);
+ map.zoomToMaxExtent();
+ map.zoomIn();
+ map.zoomOut();
+ map.zoomOut();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1>Grid Test</h1>
+ <p>Map should display with two centered tiles. If there appear to be a combination of two zoom levels, then this test is failed, and something is broken in OpenLayers.</p>
+ <div id="map"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/index.html b/misc/openlayers/tests/index.html
new file mode 100644
index 0000000..86ec617
--- /dev/null
+++ b/misc/openlayers/tests/index.html
@@ -0,0 +1,6 @@
+<html>
+ <head>
+ <meta http-equiv="refresh" content="0;url=run-tests.html">
+ </head>
+ <body></body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/list-tests.html b/misc/openlayers/tests/list-tests.html
new file mode 100644
index 0000000..f59c477
--- /dev/null
+++ b/misc/openlayers/tests/list-tests.html
@@ -0,0 +1,260 @@
+<ul id="testlist">
+ <li>Animation.html</li>
+ <li>BaseTypes.html</li>
+ <li>BaseTypes/Bounds.html</li>
+ <li>BaseTypes/Class.html</li>
+ <li>BaseTypes/Date.html</li>
+ <li>BaseTypes/Element.html</li>
+ <li>BaseTypes/LonLat.html</li>
+ <li>BaseTypes/Pixel.html</li>
+ <li>BaseTypes/Size.html</li>
+ <li>Console.html</li>
+ <li>Control.html</li>
+ <li>Control/Attribution.html</li>
+ <li>Control/ArgParser.html</li>
+ <li>Control/Button.html</li>
+ <li>Control/CacheRead.html</li>
+ <li>Control/CacheWrite.html</li>
+ <li>Control/DragFeature.html</li>
+ <li>Control/DragPan.html</li>
+ <li>Control/DrawFeature.html</li>
+ <li>Control/EditingToolbar.html</li>
+ <li>Control/Geolocate.html</li>
+ <li>Control/GetFeature.html</li>
+ <li>Control/Graticule.html</li>
+ <li>Control/KeyboardDefaults.html</li>
+ <li>Control/LayerSwitcher.html</li>
+ <li>Control/Measure.html</li>
+ <li>Control/ModifyFeature.html</li>
+ <li>Control/MousePosition.html</li>
+ <li>Control/Navigation.html</li>
+ <li>Control/NavigationHistory.html</li>
+ <li>Control/NavToolbar.html</li>
+ <li>Control/OverviewMap.html</li>
+ <li>Control/Panel.html</li>
+ <li>Control/PanZoom.html</li>
+ <li>Control/PanZoomBar.html</li>
+ <li>Control/Permalink.html</li>
+ <li>Control/PinchZoom.html</li>
+ <li>Control/Scale.html</li>
+ <li>Control/ScaleLine.html</li>
+ <li>Control/SelectFeature.html</li>
+ <li>Control/Snapping.html</li>
+ <li>Control/Split.html</li>
+ <li>Control/TouchNavigation.html</li>
+ <li>Control/TransformFeature.html</li>
+ <li>Control/UTFGrid.html</li>
+ <li>Control/WMSGetFeatureInfo.html</li>
+ <li>Control/WMTSGetFeatureInfo.html</li>
+ <li>Control/PanPanel.html</li>
+ <li>Control/SLDSelect.html</li>
+ <li>Control/Zoom.html</li>
+ <li>Control/ZoomBox.html</li>
+ <li>Events.html</li>
+ <li>Events/buttonclick.html</li>
+ <li>Events/featureclick.html?visible</li>
+ <li>Extras.html</li>
+ <li>Feature.html</li>
+ <li>Feature/Vector.html</li>
+ <li>Filter.html</li>
+ <li>Filter/Comparison.html</li>
+ <li>Filter/FeatureId.html</li>
+ <li>Filter/Logical.html</li>
+ <li>Filter/Spatial.html</li>
+ <li>Format.html</li>
+ <li>Format/Atom.html</li>
+ <li>Format/ArcXML.html</li>
+ <li>Format/XML/VersionedOGC.html</li>
+ <li>Format/ArcXML/Features.html</li>
+ <li>Format/CQL.html</li>
+ <li>Format/EncodedPolyline.html</li>
+ <li>Format/GeoJSON.html</li>
+ <li>Format/GeoRSS.html</li>
+ <li>Format/GML.html</li>
+ <li>Format/GML/v2.html</li>
+ <li>Format/GML/v3.html</li>
+ <li>Format/GPX.html</li>
+ <li>Format/JSON.html</li>
+ <li>Format/KML.html</li>
+ <li>Format/OSM.html</li>
+ <li>Format/Text.html</li>
+ <li>Format/SLD.html</li>
+ <li>Format/SLD/v1_0_0.html</li>
+ <li>Format/SLD/v1_0_0_GeoServer.html</li>
+ <li>Format/Filter.html</li>
+ <li>Format/Filter/v1.html</li>
+ <li>Format/Filter/v1_0_0.html</li>
+ <li>Format/Filter/v1_1_0.html</li>
+ <li>Format/QueryStringFilter.html</li>
+ <li>Format/WCSCapabilities.html</li>
+ <li>Format/WCSCapabilities/v1.html</li>
+ <li>Format/WFS.html</li>
+ <li>Format/WFSCapabilities.html</li>
+ <li>Format/WFSCapabilities/v1.html</li>
+ <li>Format/WFSDescribeFeatureType.html</li>
+ <li>Format/WFST.html</li>
+ <li>Format/WFST/v1.html</li>
+ <li>Format/WFST/v1_0_0.html</li>
+ <li>Format/WFST/v1_1_0.html</li>
+ <li>Format/WKT.html</li>
+ <li>Format/WMC.html</li>
+ <li>Format/WMC/v1_1_0.html</li>
+ <li>Format/WMC/v1.html</li>
+ <li>Format/WMSCapabilities.html</li>
+ <li>Format/WMSCapabilities/v1_1_1.html</li>
+ <li>Format/WMSCapabilities/v1_1_1_WMSC.html</li>
+ <li>Format/WMSCapabilities/v1_3_0.html</li>
+ <li>Format/WMSDescribeLayer.html</li>
+ <li>Format/WMSGetFeatureInfo.html</li>
+ <li>Format/WMTSCapabilities.html</li>
+ <li>Format/WMTSCapabilities/v1_0_0.html</li>
+ <li>Format/WPSCapabilities/v1_0_0.html</li>
+ <li>Format/WPSDescribeProcess.html</li>
+ <li>Format/WPSExecute.html</li>
+ <li>Format/CSWGetDomain.html</li>
+ <li>Format/CSWGetDomain/v2_0_2.html</li>
+ <li>Format/CSWGetRecords.html</li>
+ <li>Format/CSWGetRecords/v2_0_2.html</li>
+ <li>Format/SOSCapabilities/v1_0_0.html</li>
+ <li>Format/SOSGetObservation.html</li>
+ <li>Format/SOSGetFeatureOfInterest.html</li>
+ <li>Format/OWSContext/v0_3_1.html</li>
+ <li>Format/OWSCommon/v1_0_0.html</li>
+ <li>Format/OWSCommon/v1_1_0.html</li>
+ <li>Format/OGCExceptionReport.html</li>
+ <li>Format/XLS/v1_1_0.html</li>
+ <li>Format/WCSGetCoverage.html</li>
+ <li>Format/XML.html</li>
+ <li>Geometry.html</li>
+ <li>Geometry/Collection.html</li>
+ <li>Geometry/Curve.html</li>
+ <li>Geometry/LinearRing.html</li>
+ <li>Geometry/LineString.html</li>
+ <li>Geometry/MultiLineString.html</li>
+ <li>Geometry/MultiPoint.html</li>
+ <li>Geometry/MultiPolygon.html</li>
+ <li>Geometry/Point.html</li>
+ <li>Geometry/Polygon.html</li>
+ <li>Handler.html</li>
+ <li>Handler/Box.html</li>
+ <li>Handler/Click.html</li>
+ <li>Handler/Drag.html</li>
+ <li>Handler/Pinch.html</li>
+ <li>Handler/Feature.html</li>
+ <li>Handler/Hover.html</li>
+ <li>Handler/Keyboard.html</li>
+ <li>Handler/MouseWheel.html</li>
+ <li>Handler/Path.html</li>
+ <li>Handler/Point.html</li>
+ <li>Handler/Polygon.html</li>
+ <li>Handler/RegularPolygon.html</li>
+ <li>Icon.html</li>
+ <li>Lang.html</li>
+ <li>Layer.html</li>
+ <li>Layer/ArcIMS.html</li>
+ <li>Layer/ArcGIS93Rest.html</li>
+ <li>Layer/ArcGISCache.html</li>
+ <li>Layer/Bing.html</li>
+ <li>Layer/EventPane.html</li>
+ <li>Layer/FixedZoomLevels.html</li>
+ <li>Layer/GeoRSS.html</li>
+ <li>Layer/Google.html</li>
+ <li>Layer/Google/v3.html</li>
+ <li>Layer/Grid.html</li>
+ <li>Layer/HTTPRequest.html</li>
+ <li>Layer/Image.html</li>
+ <li>Layer/KaMap.html</li>
+ <li>Layer/MapGuide.html</li>
+ <li>Layer/MapServer.html</li>
+ <li>Layer/Markers.html</li>
+ <li>Layer/PointGrid.html</li>
+ <li>Layer/PointTrack.html</li>
+ <li>Layer/SphericalMercator.html</li>
+ <li>Layer/Text.html</li>
+ <li>Layer/TileCache.html</li>
+ <li>Layer/TMS.html</li>
+ <li>Layer/UTFGrid.html</li>
+ <li>Layer/Vector.html</li>
+ <li>Layer/Vector/RootContainer.html</li>
+ <li>Layer/WMS.html</li>
+ <li>Layer/WMTS.html</li>
+ <li>Layer/WrapDateLine.html</li>
+ <li>Layer/XYZ.html</li>
+ <li>Layer/OSM.html</li>
+ <li>Map.html</li>
+ <li>Marker.html</li>
+ <li>Marker/Box.html</li>
+ <li>OpenLayers1.html</li>
+ <li>OpenLayers2.html</li>
+ <li>OpenLayers3.html</li>
+ <li>OpenLayers4.html</li>
+ <li>OpenLayersJsFiles.html</li>
+ <li>SingleFile1.html</html>
+ <li>SingleFile2.html</html>
+ <li>SingleFile3.html</html>
+ <li>Popup.html?visible</li>
+ <li>Popup/Anchored.html</li>
+ <li>Popup/FramedCloud.html</li>
+ <li>Projection.html</li>
+ <li>Protocol.html</li>
+ <li>Protocol/HTTP.html</li>
+ <li>Protocol/Script.html</li>
+ <li>Protocol/WFS.html</li>
+ <li>Protocol/CSW.html</li>
+ <li>Protocol/SOS.html</li>
+ <li>Renderer.html</li>
+ <li>Renderer/Canvas.html</li>
+ <li>Renderer/Elements.html</li>
+ <li>Renderer/SVG.html</li>
+ <li>Renderer/VML.html</li>
+ <li>Request.html</li>
+ <li>Request/XMLHttpRequest.html</li>
+ <li>Rule.html</li>
+ <li>Strategy.html</li>
+ <li>Strategy/BBOX.html</li>
+ <li>Strategy/Cluster.html</li>
+ <li>Strategy/Filter.html</li>
+ <li>Strategy/Fixed.html</li>
+ <li>Strategy/Paging.html</li>
+ <li>Strategy/Save.html</li>
+ <li>Strategy/Refresh.html</li>
+ <li>Style.html</li>
+ <li>Style2.html</li>
+ <li>StyleMap.html</li>
+ <li>Symbolizer.html</li>
+ <li>Symbolizer/Line.html</li>
+ <li>Symbolizer/Point.html</li>
+ <li>Symbolizer/Polygon.html</li>
+ <li>Symbolizer/Raster.html</li>
+ <li>Symbolizer/Text.html</li>
+ <li>Tile.html</li>
+ <li>Tile/Image.html</li>
+ <li>Tile/Image/IFrame.html</li>
+ <li>Tile/UTFGrid.html</li>
+ <li>TileManager.html</li>
+ <li>Tween.html</li>
+ <li>Kinetic.html</li>
+ <li>Util.html?visible</li>
+ <li>Util_w3c.html?visible</li>
+ <li>Util/vendorPrefix.html</li>
+ <li>WPSClient.html</li>
+ <li>WPSProcess.html</li>
+ <li>deprecated/Ajax.html</li>
+ <li>deprecated/Util.html</li>
+ <li>deprecated/BaseTypes/Class.html</li>
+ <li>deprecated/BaseTypes/Element.html</li>
+ <li>deprecated/Control/MouseToolbar.html</li>
+ <li>deprecated/Geometry/Rectangle.html</li>
+ <li>deprecated/Layer/GML.html</li>
+ <li>deprecated/Layer/MapServer.html</li>
+ <li>deprecated/Layer/MapServer/Untiled.html</li>
+ <li>deprecated/Layer/WFS.html</li>
+ <li>deprecated/Layer/WMS.html</li>
+ <li>deprecated/Layer/WMS/Post.html</li>
+ <li>deprecated/Popup/AnchoredBubble.html</li>
+ <li>deprecated/Protocol/SQL.html</li>
+ <li>deprecated/Protocol/SQL/Gears.html</li>
+ <li>deprecated/Renderer/SVG2.html</li>
+ <li>deprecated/Layer/Yahoo.html</li>
+ <li>deprecated/Tile/WFS.html</li>
+</ul>
diff --git a/misc/openlayers/tests/manual/ajax.html b/misc/openlayers/tests/manual/ajax.html
new file mode 100644
index 0000000..be038ad
--- /dev/null
+++ b/misc/openlayers/tests/manual/ajax.html
@@ -0,0 +1,49 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>XHR Acceptance Test</title>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var url = "ajax.txt";
+ function sendSynchronous(){
+ var request = OpenLayers.Request.GET({
+ url: url,
+ async: false,
+ callback: function() {
+ document.getElementById('send_sync').value += 'request completed\n';
+ }
+ });
+ document.getElementById('send_sync').value += 'other processing\n';
+ }
+ function sendAsynchronous(){
+ var request = OpenLayers.Request.GET({
+ url: url,
+ callback: function() {
+ document.getElementById('send_sync').value += 'request completed\n';
+ }
+ });
+ document.getElementById('send_sync').value += 'other processing\n';
+ }
+ function sendAndAbort(){
+ var request = OpenLayers.Request.GET({
+ url: url,
+ callback: function() {
+ document.getElementById('send_sync').value += 'never called\n';
+ }
+ });
+ request.abort();
+ document.getElementById('send_sync').value += 'other processing\n';
+ }
+
+ </script>
+ </head>
+ <body >
+ <button onclick="sendSynchronous()">synchronous</button>
+ expected output: "request completed" then "other processing"<br />
+ <button onclick="sendAsynchronous()">asynchronous</button>
+ expected output: "other processing" then "request completed"<br />
+ <button onclick="sendAndAbort()">send and abort</button>
+ expected output: "other processing" (and not "never called")<br />
+ <textarea id="send_sync" rows="6"></textarea><br />
+ <button onclick="document.getElementById('send_sync').value = ''">Clear</button>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/ajax.txt b/misc/openlayers/tests/manual/ajax.txt
new file mode 100644
index 0000000..b10a427
--- /dev/null
+++ b/misc/openlayers/tests/manual/ajax.txt
@@ -0,0 +1 @@
+one fake text file \ No newline at end of file
diff --git a/misc/openlayers/tests/manual/alloverlays-mixed.html b/misc/openlayers/tests/manual/alloverlays-mixed.html
new file mode 100644
index 0000000..2f8f959
--- /dev/null
+++ b/misc/openlayers/tests/manual/alloverlays-mixed.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Mixed allOverlays Test</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../../theme/default/google.css" type="text/css">
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <script src="http://maps.google.com/maps/api/js?sensor=false&amp;v=3.6"></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ map = new OpenLayers.Map('map', {allOverlays: true});
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ var osm = new OpenLayers.Layer.OSM("OSM", null, {
+ visibility: false,
+ maxResolution: 78271.516953125,
+ serverResolutions: [156543.03390625, 78271.516953125, 39135.7584765625, 19567.87923828125, 9783.939619140625, 4891.9698095703125, 2445.9849047851562, 1222.9924523925781, 611.4962261962891, 305.74811309814453, 152.87405654907226, 76.43702827453613, 38.218514137268066, 19.109257068634033, 9.554628534317017, 4.777314267158508, 2.388657133579254, 1.194328566789627, 0.5971642833948135]
+ });
+ var google = new OpenLayers.Layer.Google("Google");
+ var wms = new OpenLayers.Layer.WMS("WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'}, {
+ opacity: .5,
+ maxExtent: new OpenLayers.Bounds(
+ -20037508.34, -20037508.34, 20037508.34, 20037508.34
+ ),
+ wrapDateLine: true
+ }
+ );
+
+ map.addLayers([osm, google, wms]);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Mixed allOverlays Test</h1>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ The map image aboved should show a Google layer and an opaque WMS
+ layer. They both should align (look at the border of West Africa)
+ </p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/arcims-2117.html b/misc/openlayers/tests/manual/arcims-2117.html
new file mode 100644
index 0000000..08dc4aa
--- /dev/null
+++ b/misc/openlayers/tests/manual/arcims-2117.html
@@ -0,0 +1,103 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>ArcIMS Test Ticket #2117</title>
+ <link rel="stylesheet" href="../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 0;
+ var lat = 0;
+ var zoom = 1;
+ var map, layer;
+
+ function init(){
+ OpenLayers.ProxyHost = "../../examples/proxy.cgi?url=";
+
+ map = new OpenLayers.Map( 'map' );
+
+ var options = {
+ serviceName: "OpenLayers_Sample",
+ async: true,
+ layers: [{
+ id:1,
+ visible:'true',
+ /*query:{
+ where: '1=1',
+ spatialfilter: true
+ },*/
+ renderer:{
+ type: 'valuemaplabel',
+ lookupfield: 'FIPS_ID',
+ labelfield: 'FIPS_CNTRY',
+ exacts:[{
+ value: '227',
+ symbol: {
+ type: 'text',
+ antialiasing: 'true',
+ interval: 6,
+ blockout: '255,255,255',
+ font: 'Arial',
+ fontcolor: '0,0,0',
+ fontsize: 11,
+ transparency: 0.7
+ }
+ },{
+ value: '150',
+ symbol: {
+ type: 'text',
+ antialiasing: 'true',
+ interval: 6,
+ blockout: '255,255,255',
+ font: 'Arial',
+ fontcolor: '0,0,0',
+ fontsize: 11,
+ transparency: 0.7
+ }
+ },{
+ value: '75',
+ symbol: {
+ type: 'text',
+ antialiasing: 'true',
+ interval: 6,
+ blockout: '255,255,255',
+ font: 'Arial',
+ fontcolor: '0,0,0',
+ fontsize: 11,
+ transparency: 0.7
+ }
+ }]
+ }
+ }]
+ };
+
+ layer = new OpenLayers.Layer.ArcIMS( "Global Sample Map",
+ "http://sample.avencia.com/servlet/com.esri.esrimap.Esrimap", options );
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ map.addControl( new OpenLayers.Control.LayerSwitcher() );
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">ArcIMS Test Ticket #2117</h1>
+
+ <div id="tags">
+ </div>
+ <p id="shortdesc">
+ <a href="http://trac.openlayers.org/ticket/2117">Testing ticket #2117</a>
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ This is an example of a bug in the ArcXML format writer.
+ If you don't see a map, it's broken.
+ </div>
+
+ </body>
+</html>
+
+
+
+
diff --git a/misc/openlayers/tests/manual/arkansas.rss b/misc/openlayers/tests/manual/arkansas.rss
new file mode 100644
index 0000000..926d357
--- /dev/null
+++ b/misc/openlayers/tests/manual/arkansas.rss
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss" version="2.0"><channel><title>topp:states</title><description>Feed auto-generated by GeoServer</description><link><![CDATA[http://localhost:8080/geoserver/wms?format_options=ENCODING:simple;&width=512&featureid=states.21&transparent=false&version=1.1.1&service=wms&srs=EPSG:4326&styles=population&height=216&bbox=-124.731422,24.955967,-66.969849,49.371735&request=GetMap&layers=topp:states&format=application/rss+xml]]></link><atom:link href="http://localhost:8080/geoserver/wms?format_options=ENCODING:simple;&amp;width=512&amp;featureid=states.21&amp;transparent=false&amp;version=1.1.1&amp;service=wms&amp;srs=EPSG:4326&amp;styles=population&amp;height=216&amp;bbox=-124.731422,24.955967,-66.969849,49.371735&amp;request=GetMap&amp;layers=topp:states&amp;format=application/rss+xml" rel="self"/><item><title>Arkansas
+</title><link><![CDATA[http://localhost:8080/geoserver/wfs?request=getfeature&service=wfs&version=1.0.0&featureid=states.21]]></link><guid><![CDATA[http://localhost:8080/geoserver/wfs?request=getfeature&service=wfs&version=1.0.0&featureid=states.21]]></guid><description><![CDATA[This is the state of Arkansas.
+2350725.0 people live in an area of 134875.075 square
+kilometers, and only 5096.0 take public transportation.
+<br>
+<br>
+
+Map by:<br> <a href="http://topp.openplans.org/geoserver"><img alt="TOPP" src="http://topp.openplans.org/images/logo.jpg"></a>
+]]></description><georss:polygon>34.19665500000001 -94.461479 34.508326999999994 -94.452408 34.735504000000006 -94.44574 34.929050000000004 -94.439102 35.400454999999994 -94.428337 35.641003 -94.468269 35.760227 -94.485718 36.106753999999995 -94.542198 36.164444 -94.552895 36.478714 -94.607231 36.489338000000004 -94.617035 36.49095199999999 -94.080849 36.489716 -93.857323 36.489891 -93.59626 36.490196 -93.328163 36.490616 -93.297142 36.489819 -92.852104 36.489918 -92.777466 36.490855999999994 -92.522888 36.491596 -92.146164 36.491371 -92.127487 36.490955 -91.688416 36.490376 -91.45285 36.491039 -91.411659 36.487953000000005 -91.133827 36.489204 -90.804314 36.490962999999994 -90.581619 36.492751999999996 -90.224373 36.491814000000005 -90.150162 36.45741700000001 -90.137276 36.453896 -90.117226 36.422565000000006 -90.123833 36.404915 -90.116829 36.39738800000001 -90.080177 36.382553 -90.052063 36.362606 -90.050201 36.325333 -90.067635 36.300472 -90.049751 36.272273999999996 -90.066093 36.257996000000006 -90.109917 36.21207 -90.131218 36.196940999999995 -90.161308 36.172565000000006 -90.219223 36.161148 -90.232224 36.137089 -90.23484 36.118763 -90.263702 36.115905999999995 -90.284752 36.091656 -90.315239 35.989586 -90.37896 35.991158 -90.283455 35.996838 -89.963203 35.999877999999995 -89.721756 35.966324 -89.713135 35.93782 -89.664192 35.913799 -89.645401 35.894287000000006 -89.649338 35.885647000000006 -89.66465 35.911427 -89.714684 35.915012000000004 -89.737976 35.896812 -89.762909 35.884102 -89.766273 35.871418000000006 -89.757713 35.842037000000005 -89.701439 35.827515000000005 -89.700829 35.807036999999994 -89.735939 35.817420999999996 -89.759796 35.805553 -89.790382 35.774223000000006 -89.799904 35.758269999999996 -89.827042 35.748192 -89.859871 35.754836999999995 -89.909782 35.734268 -89.951035 35.712486 -89.952034 35.676266 -89.929741 35.655972000000006 -89.893402 35.673306 -89.865181 35.671062000000006 -89.857246 35.645222000000004 -89.849197 35.629745 -89.863838 35.633335 -89.877441 35.603104 -89.957047 35.578593999999995 -89.958031 35.546059 -89.921661 35.52923199999999 -89.931175 35.526900999999995 -89.947548 35.532291 -89.962273 35.561676000000006 -89.989586 35.552414 -90.033051 35.542846999999995 -90.040901 35.51244 -90.041817 35.445454 -89.999565 35.417103 -90.046783 35.41341 -90.060295 35.426506 -90.073936 35.472342999999995 -90.074844 35.478207 -90.08223 35.473568 -90.101959 35.442524000000006 -90.137276 35.423716999999996 -90.172676 35.384254 -90.167816 35.383044999999996 -90.140167 35.4076 -90.132469 35.41768999999999 -90.112244 35.418282000000005 -90.085159 35.406527999999994 -90.075478 35.381508 -90.087135 35.365982 -90.105621 35.345591999999996 -90.098701 35.314685999999995 -90.106346 35.30624400000001 -90.15699 35.282566 -90.169746 35.264056999999994 -90.152122 35.263847 -90.105942 35.254397999999995 -90.090103 35.212738 -90.068962 35.191833 -90.073303 35.166916 -90.062431 35.147385 -90.064537 35.12505 -90.082924 35.13653600000001 -90.14373 35.129611999999995 -90.164474 35.10864599999999 -90.178345 35.077827 -90.169083 35.040897 -90.195709 35.048458 -90.291809 35.000693999999996 -90.305351 34.978481 -90.299507 34.94976 -90.248169 34.938903999999994 -90.241898 34.920731 -90.242844 34.896511000000004 -90.266708 34.88269 -90.296272 34.864959999999996 -90.299446 34.851776 -90.301552 34.850266000000005 -90.322823 34.860577000000006 -90.341423 34.841038 -90.403931 34.832268 -90.42231 34.835353999999995 -90.433548 34.872643 -90.427841 34.88618099999999 -90.438087 34.88092399999999 -90.470528 34.857727 -90.474716 34.82521800000001 -90.451904 34.79966400000001 -90.466705 34.76075 -90.448868 34.741198999999995 -90.451431 34.726833 -90.485924 34.729855 -90.504417 34.748371000000006 -90.516968 34.765784999999994 -90.498734 34.789833 -90.501282 34.805603000000005 -90.516045 34.807323 -90.52726 34.790336999999994 -90.547745 34.713252999999995 -90.533279 34.702068 -90.513565 34.704254000000006 -90.469978 34.672039 -90.466225 34.638065 -90.508812 34.636894 -90.538963 34.65180599999999 -90.547546 34.685947 -90.539062 34.700287 -90.561058 34.645611 -90.5793 34.627815 -90.58799 34.604744 -90.577614 34.555649 -90.530617 34.543327000000005 -90.537148 34.532509000000005 -90.565681 34.520222000000004 -90.580345 34.496506 -90.59005 34.453945000000004 -90.574402 34.432998999999995 -90.579124 34.40459799999999 -90.60379 34.36591 -90.657242 34.330006 -90.657814 34.31797400000001 -90.679337 34.320145 -90.689377 34.3634 -90.681137 34.377871999999996 -90.687485 34.37216600000001 -90.75531 34.363913999999994 -90.761856 34.317719 -90.747757 34.278976 -90.758255 34.299957000000006 -90.792526 34.299347 -90.806419 34.277339999999995 -90.823837 34.229534 -90.83136 34.219162 -90.863411 34.250195000000005 -90.928436 34.23467599999999 -90.933708 34.20483 -90.921486 34.190544 -90.822922 34.166172 -90.80751 34.148658999999995 -90.828865 34.147544999999994 -90.846611 34.185649999999995 -90.928917 34.155804 -90.953346 34.125941999999995 -90.942245 34.102749 -90.906311 34.10095200000001 -90.866333 34.040710000000004 -90.886345 34.031288 -90.95047 34.010998 -90.973366 33.994606000000005 -90.975273 33.978874000000005 -90.961128 33.967461 -90.96479 33.960815 -90.986816 33.968372 -91.000259 33.990528 -91.009544 33.985699 -91.031059 34.006096 -91.069695 33.994468999999995 -91.088852 33.974644 -91.075706 33.936306 -91.018463 33.867081 -91.061371 33.843525 -91.054817 33.816586 -91.028854 33.798897 -90.990372 33.78533899999999 -90.984039 33.77154899999999 -90.995377 33.76397299999999 -91.018433 33.769554 -91.043404 33.786525999999995 -91.066391 33.776439999999994 -91.105415 33.780086999999995 -91.136986 33.771820000000005 -91.142891 33.723225 -91.138 33.71244 -91.128944 33.70823300000001 -91.104568 33.719357 -91.056213 33.70549 -91.038826 33.683216 -91.037834 33.662586000000005 -91.083771 33.677527999999995 -91.121048 33.718315000000004 -91.1633 33.708965000000006 -91.211678 33.690723000000006 -91.215141 33.669945 -91.205307 33.637032000000005 -91.154404 33.616161000000005 -91.15065 33.57724399999999 -91.168022 33.57468 -91.187805 33.590481 -91.22673 33.55635100000001 -91.227631 33.539276 -91.213486 33.538506 -91.204102 33.52334999999999 -91.182846 33.512012 -91.180405 33.473395999999994 -91.207642 33.459453999999994 -91.22715 33.443443 -91.232849 33.447388000000004 -91.18177 33.466644 -91.17173 33.504368 -91.174179 33.51178 -91.164978 33.493190999999996 -91.128799 33.469673 -91.118958 33.452831 -91.119713 33.443123 -91.130516 33.422112 -91.19883 33.414299 -91.204163 33.39183 -91.184982 33.388878000000005 -91.137634 33.414897999999994 -91.099129 33.462856 -91.0858 33.466206 -91.073761 33.460010999999994 -91.060982 33.431797 -91.061516 33.410179 -91.07869 33.393406 -91.106941 33.359402 -91.130424 33.322384 -91.141747 33.268505000000005 -91.122505 33.249210000000005 -91.102905 33.29235799999999 -91.076324 33.293575000000004 -91.053833 33.281921 -91.040428 33.245780999999994 -91.054543 33.225697 -91.092003 33.161602 -91.086319 33.145084 -91.095856 33.131069 -91.12133 33.150288 -91.177628 33.140465000000006 -91.195503 33.113224 -91.190536 33.090652000000006 -91.14679 33.065571000000006 -91.11779 33.04727200000001 -91.123985 33.040431999999996 -91.156685 33.021709 -91.160675 33.013039000000006 -91.162132 33.013476999999995 -91.254616 33.013419999999996 -91.427528 33.013874 -91.454353 33.010025 -92.063309 33.016701 -92.717079 33.018135 -92.978828 33.019238 -93.232376 33.021393 -93.478897 33.021152 -93.511742 33.022594 -93.809753 33.023289000000005 -94.03875 33.270325 -94.036507 33.555912000000006 -94.035927 33.577213 -94.061432 33.583954000000006 -94.086655 33.572998 -94.098701 33.567085000000006 -94.155167 33.593773 -94.159515 33.58507899999999 -94.205345 33.557987 -94.210884 33.561535000000006 -94.235367 33.585719999999995 -94.223038 33.592422 -94.237236 33.561736999999994 -94.274544 33.584605999999994 -94.272079 33.589332999999996 -94.278984 33.579853 -94.29882 33.556934 -94.302383 33.57313499999999 -94.328751 33.547684000000004 -94.370758 33.560303000000005 -94.395264 33.572661999999994 -94.372307 33.590042 -94.370628 33.593327 -94.379112 33.57495900000001 -94.393417 33.573486 -94.40657 33.59714099999999 -94.428467 33.596503999999996 -94.443329 33.604347000000004 -94.451553 33.616844 -94.436333 33.636444 -94.435913 33.631966000000006 -94.476486 33.939198000000005 -94.468376 34.19665500000001 -94.461479</georss:polygon></item></channel></rss> \ No newline at end of file
diff --git a/misc/openlayers/tests/manual/big-georss.html b/misc/openlayers/tests/manual/big-georss.html
new file mode 100644
index 0000000..7e2f2b5
--- /dev/null
+++ b/misc/openlayers/tests/manual/big-georss.html
@@ -0,0 +1,33 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>OpenLayers GML Layer Example</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToExtent(new OpenLayers.Bounds(-94.617035,33.010025,-89.645401,36.492752));
+ map.addLayer(new OpenLayers.Layer.Vector("arkansas", {
+ protocol: new OpenLayers.Protocol.HTTP({
+ url: "arkansas.rss",
+ format: new OpenLayers.Format.GeoRSS()
+ }),
+ strategies: [new OpenLayers.Strategy.Fixed()]
+ }));
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <p>Does this map look like arkansas?</p>
+ <div id="map" class="smallmap"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/box-quirks.html b/misc/openlayers/tests/manual/box-quirks.html
new file mode 100644
index 0000000..eb74bed
--- /dev/null
+++ b/misc/openlayers/tests/manual/box-quirks.html
@@ -0,0 +1,52 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Box Handler Quirks Mode Test</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <style type="text/css">
+ /* simulate quirks mode (traditional box model) in browsers other than IE */
+ div {
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ }
+
+ .olHandlerBoxZoomBox {
+ border: 20px solid red;
+ border-left-width: 10px;
+ border-bottom-width: 30px;
+ }
+ </style>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Box handler Quirks Mode Test</h1>
+
+ <div id="shortdesc">Test the correct appearance of the ZoomBox in quirks mode</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>For the box to be positioned correctly, we need to know the
+ width of the borders.</p>
+ <p>Shift-click on the map. A red box should be visible around the mouse
+ cursor position, with 20 pixels to the top and right, 10 pixels to
+ the left and 30 pixels to the bottom edge of the box.</p>
+ <p>Drag the box both to the top-left and the bottom-right. The cursor
+ should always be at the top-left or bottom-right inner corner of
+ the box.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/box-strict.html b/misc/openlayers/tests/manual/box-strict.html
new file mode 100644
index 0000000..5b38ea5
--- /dev/null
+++ b/misc/openlayers/tests/manual/box-strict.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+ <title>Box Handler Strict Mode Test</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <style type="text/css">
+ .olHandlerBoxZoomBox {
+ border: 20px solid red;
+ border-left-width: 10px;
+ border-bottom-width: 30px;
+ }
+ </style>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0",
+ {layers: 'basic'} );
+ map.addLayer(layer);
+ map.zoomToMaxExtent();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Box Handler Strict Mode Test</h1>
+
+ <div id="shortdesc">Test the correct appearance of the ZoomBox in strict mode</div>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>For the box to be positioned correctly, we need to know the
+ width of the borders.</p>
+ <p>Shift-click on the map. A red box should be visible around the mouse
+ cursor position, with 20 pixels to the top and right, 10 pixels to
+ the left and 30 pixels to the bottom edge of the box.</p>
+ <p>Drag the box both to the top-left and the bottom-right. The cursor
+ should always be at the top-left or bottom-right inner corner of
+ the box.</p>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/clip-features-svg.html b/misc/openlayers/tests/manual/clip-features-svg.html
new file mode 100644
index 0000000..f4137ea
--- /dev/null
+++ b/misc/openlayers/tests/manual/clip-features-svg.html
@@ -0,0 +1,128 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>SVG inValidRange Test Case</title><link
+href="../../theme/default/style.css"
+rel="stylesheet" type="text/css">
+<style>
+ #map {
+ width: 512px;
+ height: 512px;
+ border: 1px solid #4B3624;
+ background: White;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+
+ .olControlAttribution { bottom: 0px!important }
+</style>
+<script src="../../lib/OpenLayers.js"
+type="text/javascript"></script>
+<script type="text/javascript">var map;
+
+ // increase reload attempts
+ OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
+
+ var vectorLayer;
+ var markerLayer, boxes, newPoint;
+
+ function init(){
+ var options = {
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ units: "m",
+ numZoomLevels: 19,
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+ 20037508, 20037508.34)
+ };
+ map = new OpenLayers.Map('map', options);
+
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ vectorLayer = new OpenLayers.Layer.Vector("Trails", {isBaseLayer: true});
+ markerLayer = new OpenLayers.Layer.Markers("WayPoints");
+
+ map.addLayers([vectorLayer,markerLayer]);
+
+ var style_trail = OpenLayers.Util.extend({},
+ OpenLayers.Feature.Vector.style['default']);
+ style_trail.strokeColor = "green";
+ style_trail.strokeWidth = 5;
+
+ var pointList = [];
+
+ newPoint = new OpenLayers.Geometry.Point(-13653735.8487833,5726045.3578081);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653731.3960036,5726056.5070679);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653730.8394062,5726044.7207079);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653743.1958697,5726043.9243328);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653754.1051798,5726046.9505586);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653760.4503907,5726056.5070679);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653767.4635187,5726065.5857612);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653830.136392,5726052.2066375);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-13653846.5003571,5726042.3315828);
+ pointList.push(newPoint);
+
+ var lineFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(pointList));
+ lineFeature.fid = 52730;
+ vectorLayer.addFeatures(lineFeature);
+
+ pointList = [];
+
+ newPoint = new OpenLayers.Geometry.Point(-12250153.3626406,4852001.6114048);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-12194315.5060664,4800503.5113048);
+ pointList.push(newPoint);
+ newPoint = new OpenLayers.Geometry.Point(-12180445.0975155,4873109.008858);
+ pointList.push(newPoint);
+
+ lineFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LineString(pointList),null,style_trail);
+ lineFeature.fid = 52751;
+ vectorLayer.addFeatures([lineFeature]);
+
+ var size = new OpenLayers.Size(15, 15);
+ var offset = new OpenLayers.Pixel(-(size.w/2), -size.h);
+ var icon = new OpenLayers.Icon('../../img/marker.png', size, offset);
+ markerLayer.addMarker(new OpenLayers.Marker(
+ new OpenLayers.LonLat((newPoint.x + 400), (newPoint.y - 400)), icon));
+
+ map.setCenter(new OpenLayers.LonLat(newPoint.x, newPoint.y), 13)
+ }
+
+ function zoomToScale(zoom) {
+ if (zoom == 8) map.zoomToScale(3385.5001275);
+ else if(zoom == 7) map.zoomToScale(6771.000255);
+ else if (zoom == 6) map.zoomToScale(13542);
+ else if (zoom == 5) map.zoomToScale(27084.001020);
+ else if (zoom == 4) map.zoomToScale(54168.001020);
+ else if (zoom == 3) map.zoomToScale(108337);
+ else if (zoom == 2) map.zoomToScale(3466752.1306573446);
+ else if (zoom == 1) map.zoomToScale(13867008.522629378);
+ else if (zoom == 0) map.zoomToScale(55468034.09051751);
+ }
+
+</script>
+</head>
+<body onLoad="init()">
+<h1 id="title">SVG inValidRange Clipping Test Case</h1>
+<p>Behavior before fixing #1631: Push Zoom 5. You see lines. Push
+Zoom 6. No lines.</p>
+ <div id="map">
+ </div>
+ <button onClick="zoomToScale(5);">Zoom 5</button>
+ <button onClick="zoomToScale(6);">Zoom 6</button>
+ <button onClick="zoomToScale(7);">Zoom 7</button>
+ <button onClick="zoomToScale(8);">Zoom 8</button>
+</body>
+</html>
diff --git a/misc/openlayers/tests/manual/dateline-sketch.html b/misc/openlayers/tests/manual/dateline-sketch.html
new file mode 100644
index 0000000..1be1f36
--- /dev/null
+++ b/misc/openlayers/tests/manual/dateline-sketch.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Sketch handlers crossing the dateline</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ height: 512px;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <h1 id="title">OpenLayers sketch handlers crossing the dateline example</h1>
+
+ <div id="tags">
+ international date line, dateline, sketch
+ </div>
+ <p id="shortdesc">
+ Start digitizing a polygon or line
+ on one side of the international dateline, and then cross the dateline
+ whilst digitizing. The feature should behave like digitizing on any
+ other location.
+ </p>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ </div>
+ <script type="text/javascript">
+
+ var map = new OpenLayers.Map('map');
+
+ var base = new OpenLayers.Layer.WMS("marble",
+ "http://demo.opengeo.org/geoserver/wms",
+ {layers: "topp:naturalearth"},
+ {wrapDateLine: true}
+ );
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var vector = new OpenLayers.Layer.Vector("Editable Vectors", {renderers: renderer});
+
+ map.addLayers([base, vector]);
+
+ var wkt = new OpenLayers.Format.WKT();
+ var f = wkt.read("POLYGON((210.8828125 39.7265625,151.8203125 36.2109375,152.171875 -9.4921875,219.3203125 -10.546875,210.8828125 39.7265625))");
+
+ var f2 = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(-190, 0));
+
+ vector.addFeatures([f, f2]);
+
+ map.addControl(new OpenLayers.Control.EditingToolbar(vector));
+
+ map.setCenter(new OpenLayers.LonLat(-179, 0), 2);
+
+ </script>
+
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/dateline-smallextent.html b/misc/openlayers/tests/manual/dateline-smallextent.html
new file mode 100644
index 0000000..1d05e84
--- /dev/null
+++ b/misc/openlayers/tests/manual/dateline-smallextent.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers: Overlay layer extents crossing the dateline</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <style type="text/css">
+ #map {
+ height: 512px;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+// make map available for easy debugging
+var map;
+
+function init(){
+ map = new OpenLayers.Map('map');
+
+ var base = new OpenLayers.Layer.WMS("marble",
+ "http://demo.opengeo.org/geoserver/wms",
+ {layers: "topp:naturalearth"},
+ {wrapDateLine: true}
+ );
+ var extent = new OpenLayers.Bounds(142.3828125,-70.902270266175,233.6171875,-12.039326531729);
+ var wms = new OpenLayers.Layer.WMS( "world",
+ "http://demo.opengeo.org/geoserver/wms",
+ {layers: 'world', transparent: true},
+ {maxExtent: extent}
+ );
+
+ var vector = new OpenLayers.Layer.Vector();
+ vector.addFeatures([
+ new OpenLayers.Feature.Vector(extent.toGeometry())
+ ]);
+
+ map.addLayers([base, wms, vector]);
+
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.zoomToExtent(extent);
+}
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers overlays crossing the dateline test</h1>
+
+ <p id="shortdesc">
+ The overlay has an extent smaller than the world extent, but exceeds
+ the world extent. The base layer is configured with wrapDateLine set to
+ true. The area inside the orange rectangle should always contain tiles
+ from the world layer, regardless of the zoom level.
+ </p>
+ <div id="map" class="smallmap"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/draw-feature.html b/misc/openlayers/tests/manual/draw-feature.html
new file mode 100644
index 0000000..8872b63
--- /dev/null
+++ b/misc/openlayers/tests/manual/draw-feature.html
@@ -0,0 +1,73 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Draw Feature Acceptance Test</title>
+ <style type="text/css">
+
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+
+ .map {
+ margin: 1em;
+ float: left;
+ width: 256px;
+ height: 256px;
+ }
+
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var map1, map2;
+
+ function init(){
+ var wms, vector, ctrl;
+ var goodMaxRes = OpenLayers.Map.prototype.maxResolution;
+ var badMaxRes = 0.00000001;
+
+ map1 = new OpenLayers.Map('map1');
+ wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0?", {layers: 'basic'});
+ vector = new OpenLayers.Layer.Vector("vector1");
+ map1.addLayers([wms, vector]);
+ ctrl = new OpenLayers.Control.DrawFeature(vector,
+ OpenLayers.Handler.Path);
+ map1.addControl(ctrl);
+ ctrl.activate();
+
+ map2 = new OpenLayers.Map('map2',
+ {maxResolution: badMaxRes});
+ wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0?", {layers: 'basic'},
+ {maxResolution: goodMaxRes});
+ vector = new OpenLayers.Layer.Vector("vector2",
+ {maxResolution: goodMaxRes});
+ map2.addLayers([wms, vector]);
+ ctrl = new OpenLayers.Control.DrawFeature(vector,
+ OpenLayers.Handler.Path);
+ map2.addControl(ctrl);
+ ctrl.activate();
+
+ map1.setCenter(new OpenLayers.LonLat(0, 0), 3);
+ map2.setCenter(new OpenLayers.LonLat(0, 0), 3);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="map1" class="map"></div>
+ <p><b>Resolution properties set at the map level.</b></p>
+ <p>Points should draw as you draw lines. Click to start
+ drawing and double-click to draw the last point.</p>
+ <br style="clear: both;" />
+
+ <div id="map2" class="map"></div>
+ <p><b>Resolution properties set at the layer level.</b></p>
+ <p>Points should draw as you draw lines. Click to start
+ drawing and double-click to draw the last point.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/feature-handler.html b/misc/openlayers/tests/manual/feature-handler.html
new file mode 100644
index 0000000..f9b8892
--- /dev/null
+++ b/misc/openlayers/tests/manual/feature-handler.html
@@ -0,0 +1,126 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Feature Handler Acceptance Test</title>
+ <style type="text/css">
+
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ li {
+ list-style: none;
+ }
+ #output {
+ width: 300px;
+ height: 300px;
+ }
+ #west {
+ width: 425px;
+ }
+
+ #east {
+ position: absolute;
+ left: 450px;
+ top: 5px;
+ }
+ #map {
+ width: 400px;
+ height: 400px;
+ border: 1px solid gray;
+ }
+
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var map, draw, handler, controls;
+ OpenLayers.Feature.Vector.style['default']['strokeWidth'] = '2';
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var vectors = new OpenLayers.Layer.Vector(
+ "Vector Layer",
+ {isBaseLayer: true}
+ );
+ map.addLayer(vectors);
+
+
+ draw = new OpenLayers.Control.DrawFeature(
+ vectors, OpenLayers.Handler.Polygon
+ );
+ map.addControl(draw);
+
+ var callbacks = {
+ "over": function(feature) {
+ log("over " + feature.id);
+ },
+ "out": function(feature) {
+ log("out " + feature.id);
+ },
+ "click": function(feature) {
+ log("click " + feature.id);
+ },
+ "dblclick": function(feature) {
+ log("dblclick " + feature.id);
+ },
+ "clickout": function(feature) {
+ log("clickout " + feature.id);
+ }
+ };
+
+ handler = new OpenLayers.Handler.Feature(
+ {map: map}, vectors, callbacks
+ );
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ }
+
+ function log(msg) {
+ document.getElementById('output').value += msg + "\n";
+ }
+
+ function clearLog() {
+ document.getElementById('output').value = "";
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="west">
+ <div id="map"></div>
+ <p>
+ Draw a few polygons on the map. Some overlapping. Activate the
+ feature handler and ensure that "over" and "out" are called only
+ when mousing over/out of a feature for the first time. The
+ "click" callback should be called for every click on a feature.
+ The "clickout" callback should be called when?
+ </p>
+ </div>
+ <div id="east">
+ <ul>
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="draw.deactivate();handler.deactivate();" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle"
+ onclick="draw.activate();handler.deactivate();" />
+ <label for="polygonToggle">draw polygon</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="feature" id="featureToggle"
+ onclick="draw.deactivate();handler.activate();" />
+ <label for="featureToggle">activate feature handler</label>
+ </li>
+ </ul>
+ <button onclick="clearLog();">clear log</button><br />
+ <textarea id="output"></textarea>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/geodesic.html b/misc/openlayers/tests/manual/geodesic.html
new file mode 100644
index 0000000..e642558
--- /dev/null
+++ b/misc/openlayers/tests/manual/geodesic.html
@@ -0,0 +1,160 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <style type="text/css">
+ #controlToggle li {
+ list-style: none;
+ }
+ #options {
+ position: relative;
+ width: 512px;
+ }
+ #output {
+ float: right;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+ </style>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, measureControls;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var wmsLayer = new OpenLayers.Layer.OSM();
+
+ map.addLayers([wmsLayer]);
+ map.setCenter(new OpenLayers.LonLat(0,0), 5);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.addControl(new OpenLayers.Control.ScaleLine({geodesic: true}))
+
+ // style the sketch fancy
+ var sketchSymbolizers = {
+ "Point": {
+ pointRadius: 4,
+ graphicName: "square",
+ fillColor: "white",
+ fillOpacity: 1,
+ strokeWidth: 1,
+ strokeOpacity: 1,
+ strokeColor: "#333333"
+ },
+ "Line": {
+ strokeWidth: 3,
+ strokeOpacity: 1,
+ strokeColor: "#666666",
+ strokeDashstyle: "dash"
+ },
+ "Polygon": {
+ strokeWidth: 2,
+ strokeOpacity: 1,
+ strokeColor: "#666666",
+ fillColor: "white",
+ fillOpacity: 0.3
+ }
+ };
+ var style = new OpenLayers.Style();
+ style.addRules([
+ new OpenLayers.Rule({symbolizer: sketchSymbolizers})
+ ]);
+ var styleMap = new OpenLayers.StyleMap({"default": style});
+
+ measureControls = {
+ line: new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Path, {
+ geodesic: true,
+ persist: true,
+ handlerOptions: {
+ layerOptions: {styleMap: styleMap}
+ }
+ }
+ ),
+ polygon: new OpenLayers.Control.Measure(
+ OpenLayers.Handler.Polygon, {
+ geodesic: true,
+ persist: true,
+ handlerOptions: {
+ layerOptions: {styleMap: styleMap}
+ }
+ }
+ )
+ };
+
+ var control;
+ for(var key in measureControls) {
+ control = measureControls[key];
+ control.events.on({
+ "measure": handleMeasurements,
+ "measurepartial": handleMeasurements
+ });
+ map.addControl(control);
+ }
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ document.getElementById('noneToggle').checked = true;
+ }
+
+ function handleMeasurements(event) {
+ var geometry = event.geometry;
+ var units = event.units;
+ var order = event.order;
+ var measure = event.measure;
+ var element = document.getElementById('output');
+ var out = "";
+ if(order == 1) {
+ out += "measure: " + measure.toFixed(3) + " " + units;
+ } else {
+ out += "measure: " + measure.toFixed(3) + " " + units + "<sup>2</" + "sup>";
+ }
+ element.innerHTML = out;
+ }
+
+ function toggleControl(element) {
+ for(key in measureControls) {
+ var control = measureControls[key];
+ if(element.value == key && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Geodesic Measurement & ScaleLine</h1>
+ <p id="shortdesc">
+ Tests geodesic measurement of distances and areas against a geodesic ScaleLine.
+ </p>
+ <div id="map" style="width: 512px; height: 300px;"></div>
+ <div id="options">
+ <div id="output">
+ </div>
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="line" id="lineToggle" onclick="toggleControl(this);" />
+ <label for="lineToggle">measure distance</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="polygon" id="polygonToggle" onclick="toggleControl(this);" />
+ <label for="polygonToggle">measure area</label>
+ </li>
+ </ul>
+ </div>
+ <p>Zoom in so the ScaleLine shows units in the range of 10-100 km. Measure
+ the length of the ScaleLine. The result should be approximately the same
+ as the distance printed on the ScaleLine.</p>
+ <p>Zoom out so the ScaleLine shows units in the range of 100-500 km. Drag
+ the map to the South or North and see how the ScaleLine length changes.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/geojson-geomcoll-reprojection.html b/misc/openlayers/tests/manual/geojson-geomcoll-reprojection.html
new file mode 100644
index 0000000..e82e08a
--- /dev/null
+++ b/misc/openlayers/tests/manual/geojson-geomcoll-reprojection.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <style type="text/css" media="screen">
+ #map { height: 500px; }
+ </style>
+ <script src="../../lib/OpenLayers.js" type="text/javascript" charset="utf-8"></script>
+ <script src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js" type="text/javascript" charset="utf-8"></script>
+ <script type="text/javascript" charset="utf-8">
+ function init(){
+ var map = new OpenLayers.Map ("map", {
+ controls: [
+ new OpenLayers.Control.Navigation(),
+ new OpenLayers.Control.Attribution()
+ ],
+ maxExtent: new OpenLayers.Bounds(-20037508.34,-20037508.34,20037508.34,20037508.34),
+ maxResolution: 156543.0399,
+ numZoomLevels: 19,
+ units: 'm',
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326")
+ });
+
+ var osm = new OpenLayers.Layer.OSM.Mapnik('OSM');
+ map.addLayer(osm);
+ var lonLat = new OpenLayers.LonLat(5, 40).transform(new OpenLayers.Projection("EPSG:4326"), map.getProjectionObject());
+ map.setCenter (lonLat, 5);
+
+ var featurecollection = {
+ "type": "FeatureCollection",
+ "features": [{
+ "geometry": {
+ "type": "GeometryCollection",
+ "geometries": [
+ {
+ "type": "LineString",
+ "coordinates":
+ [[11.0878902207, 45.1602390564],
+ [15.01953125, 48.1298828125]]
+ },
+ {
+ "type": "Polygon",
+ "coordinates":
+ [[[11.0878902207, 45.1602390564],
+ [14.931640625, 40.9228515625],
+ [0.8251953125, 41.0986328125],
+ [7.63671875, 48.96484375],
+ [11.0878902207, 45.1602390564]]]
+ },
+ {
+ "type":"Point",
+ "coordinates":[15.87646484375, 44.1748046875]
+ }
+ ]
+ },
+ "type": "Feature",
+ "properties": {}
+ }]
+ };
+ var geojson_format = new OpenLayers.Format.GeoJSON({
+ 'internalProjection': new OpenLayers.Projection("EPSG:900913"),
+ 'externalProjection': new OpenLayers.Projection("EPSG:4326")
+ });
+ var vector_layer = new OpenLayers.Layer.Vector();
+ map.addLayer(vector_layer);
+ vector_layer.addFeatures(geojson_format.read(featurecollection));
+ };
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="map"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/google-fullscreen-overlay.html b/misc/openlayers/tests/manual/google-fullscreen-overlay.html
new file mode 100644
index 0000000..80a8fd4
--- /dev/null
+++ b/misc/openlayers/tests/manual/google-fullscreen-overlay.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Google v3 with Overlay Test</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <style type="text/css">
+ html, body, #map {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ }
+ #text {
+ position: absolute;
+ top: 1em;
+ right: 1em;
+ width: 512px;
+ z-index: 20000;
+ background-color: white;
+ padding: 0 0.5em 0.5em 0.5em;
+ }
+ </style>
+ <script src="http://maps.google.com/maps/api/js?v=3.6&amp;sensor=false"></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <div id="map"></div>
+ <div id="text">
+ <h1 id="title">Google v3 with Overlay Test</h1>
+
+ <div id="docs">
+ <p>This test shows that the Google layer and overlays are not in sync while dragging or zooming.</p>
+ </div>
+ </div>
+ <script type="text/javascript">
+ var options = {
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ units: "m",
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508)
+ };
+ var map = new OpenLayers.Map('map', options);
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", {sphericalMercator: true}
+ );
+ var states = new OpenLayers.Layer.WMS(
+ "USA States", "http://demo.opengeo.org/geoserver/wms",
+ {layers: "topp:states", transparent: true}
+ );
+ map.addLayers([gmap, states]);
+ map.setCenter(new OpenLayers.LonLat(-10028537.429619, 4598451.0222853), 5);
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/google-panning.html b/misc/openlayers/tests/manual/google-panning.html
new file mode 100644
index 0000000..0ccdaf2
--- /dev/null
+++ b/misc/openlayers/tests/manual/google-panning.html
@@ -0,0 +1,122 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Google Panning Acceptance Test</title>
+ <style type="text/css">
+
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+
+ #evenmap {
+ margin: 1em;
+ float: left;
+ width: 256px;
+ height: 256px;
+ }
+
+ #oddmap {
+ margin: 1em;
+ float: left;
+ width: 255px;
+ height: 255px;
+ }
+
+ /* avoid pink tiles */
+ .olImageLoadError {
+ background-color: transparent !important;
+ }
+
+ </style>
+
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ var evenmap, oddmap;
+
+ // increase reload attempts
+ OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
+
+ function init(){
+ evenmap = new OpenLayers.Map('evenmap');
+ var evenlayer = new OpenLayers.Layer.Google(
+ "Imagery",
+ {type: G_SATELLITE_MAP}
+ );
+ evenmap.addLayer(evenlayer);
+ var epc = document.getElementById("epc");
+ var emc = document.getElementById("emc");
+ var ee = document.getElementById("ee");
+ evenmap.events.register("moveend", null, function() {
+ var px = new OpenLayers.Pixel(evenmap.size.w / 2,
+ evenmap.size.h / 2);
+ var pc = evenmap.getLonLatFromViewPortPx(px);
+ pc.lon = parseFloat(pc.lon.toPrecision(6));
+ pc.lat = parseFloat(pc.lat.toPrecision(6));
+ var mc = evenmap.baseLayer.getOLLonLatFromMapObjectLonLat(
+ evenmap.baseLayer.mapObject.getCenter()
+ );
+ mc.lon = parseFloat(mc.lon.toPrecision(6));
+ mc.lat = parseFloat(mc.lat.toPrecision(6));
+ epc.innerHTML = "(" + pc.lon + ", " + pc.lat + ")";
+ emc.innerHTML = "(" + mc.lon + ", " + mc.lat + ")";
+ ee.innerHTML = pc.equals(mc);
+ });
+ evenmap.zoomToMaxExtent();
+
+ oddmap = new OpenLayers.Map('oddmap');
+ var oddlayer = new OpenLayers.Layer.Google(
+ "Imagery",
+ {type: G_SATELLITE_MAP}
+ );
+ oddmap.addLayer(oddlayer);
+ var opc = document.getElementById("opc");
+ var omc = document.getElementById("omc");
+ var oe = document.getElementById("oe");
+ oddmap.events.register("moveend", null, function() {
+ var px = new OpenLayers.Pixel(oddmap.size.w / 2,
+ oddmap.size.h / 2);
+ var pc = oddmap.getLonLatFromViewPortPx(px);
+ pc.lon = parseFloat(pc.lon.toPrecision(6));
+ pc.lat = parseFloat(pc.lat.toPrecision(6));
+ var mc = oddmap.baseLayer.getOLLonLatFromMapObjectLonLat(
+ oddmap.baseLayer.mapObject.getCenter()
+ );
+ mc.lon = parseFloat(mc.lon.toPrecision(6));
+ mc.lat = parseFloat(mc.lat.toPrecision(6));
+ opc.innerHTML = "(" + pc.lon + ", " + pc.lat + ")";
+ omc.innerHTML = "(" + mc.lon + ", " + mc.lat + ")";
+ oe.innerHTML = pc.equals(mc);
+ });
+ oddmap.zoomToMaxExtent();
+
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="evenmap"></div>
+ <p><b>Even sized map.</b> The map on the left should pan regularly, and the
+ two centers below should be equivalent. Both dragging and panning with
+ buttons should maintain the same center.</p>
+ <p><b>pixel center:</b> <span id="epc"></span>
+ <br /><b>map center:</b> <span id="emc"></span>
+ <br /><b>equvalent:</b> <span id="ee"></span>
+ </p>
+ <br style="clear: both;" />
+
+ <div id="oddmap"></div>
+ <p><b>Odd sized map.</b> The map on the left should pan regularly, and the
+ two centers below should be equivalent. Both dragging and panning with
+ buttons should maintain the same center.</p>
+ <p><b>pixel center:</b> <span id="opc"></span>
+ <br /><b>map center:</b> <span id="omc"></span>
+ <br /><b>equvalent:</b> <span id="oe"></span>
+ </p>
+ </ul>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/google-resize.html b/misc/openlayers/tests/manual/google-resize.html
new file mode 100644
index 0000000..c1384df
--- /dev/null
+++ b/misc/openlayers/tests/manual/google-resize.html
@@ -0,0 +1,55 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>OpenLayers Google Layer Example</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <!-- this gmaps key generated for http://openlayers.org/dev/ -->
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ var mapOptions = {
+ projection: "EPSG:900913",
+ displayProjection: new OpenLayers.Projection("EPSG:4326"), //Pour afficher les coord lat long
+ units: "m",
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
+ controls: [new OpenLayers.Control.LayerSwitcher()],
+ numZoomLevels: 20
+ };
+ map = new OpenLayers.Map('map', mapOptions);
+
+ var dummy = new OpenLayers.Layer(
+ "Dummy",
+ {isBaseLayer: true}
+ );
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", {sphericalMercator: true}
+ );
+
+ map.addLayers([dummy, gmap]);
+
+ map.setCenter(new OpenLayers.LonLat(-7712190.388467473, 6567469.498697457), 6);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Google Layer Resize Issue</h1>
+
+ <div id="tags"></div>
+
+ <p id="shortdesc">
+ <ol>
+ <li>Click
+ <button onclick="var m = document.getElementById('map').style; m.height = '400px'; m.width = '800px'; map.updateSize(); return false;">Resize</button></li>
+ <li>Open the LayerSwitcher and switch to Google Streets</li>
+ <li>Confirm that the whole map area is populated with tiles</li>
+ </ol>
+ </p>
+
+ <div id="map" style="width: 200px; height: 200px"></div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/google-tilt.html b/misc/openlayers/tests/manual/google-tilt.html
new file mode 100644
index 0000000..d0b2ed6
--- /dev/null
+++ b/misc/openlayers/tests/manual/google-tilt.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>OpenLayers Google (v3) Layer Example</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <script src="http://maps.google.com/maps/api/js?v=3.6&amp;sensor=false"></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Google (v3) Unexpected Tilt Test</h1>
+ <div id="map" class="smallmap"></div>
+ <div id="docs">
+ <p>
+ OpenLayers uses the disableDefaultUI option of the GMaps API.
+ Despite that, the tilt feature is active. To see it, zoom in
+ once and see the buildings from a 45° angle instead of from the
+ top as you would expect from aerial imagery.
+ </p>
+ </div>
+ <script>
+ var map = new OpenLayers.Map('map');
+
+ var ghyb = new OpenLayers.Layer.Google(
+ "Google Hybrid",
+ {type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20}
+ );
+
+ map.addLayers([ghyb]);
+
+ map.setCenter(new OpenLayers.LonLat(-13635213, 4544641), 17);
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/google-v3-resize.html b/misc/openlayers/tests/manual/google-v3-resize.html
new file mode 100644
index 0000000..6949ddc
--- /dev/null
+++ b/misc/openlayers/tests/manual/google-v3-resize.html
@@ -0,0 +1,54 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Google v3 Resize Test</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <script src="http://maps.google.com/maps/api/js?sensor=false&amp;v=3.6"></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+
+ function init() {
+ var mapOptions = {
+ projection: "EPSG:900913",
+ displayProjection: new OpenLayers.Projection("EPSG:4326"), //Pour afficher les coord lat long
+ units: "m",
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
+ controls: [new OpenLayers.Control.Navigation(), new OpenLayers.Control.LayerSwitcher()],
+ numZoomLevels: 20
+ };
+ map = new OpenLayers.Map('map', mapOptions);
+
+ var dummy = new OpenLayers.Layer(
+ "Dummy",
+ {isBaseLayer: true}
+ );
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets", {sphericalMercator: true}
+ );
+
+ map.addLayers([dummy, gmap]);
+
+ map.setCenter(new OpenLayers.LonLat(-7712190.388467473, 6567469.498697457), 6);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Google Layer Resize Issue</h1>
+
+ <div id="tags"></div>
+
+ <p id="shortdesc">
+ <ol>
+ <li>Click
+ <button onclick="var m = document.getElementById('map').style; m.height = '400px'; m.width = '800px';map.updateSize(); return false;">Resize</button></li>
+ <li>Open the LayerSwitcher and switch to Google Streets</li>
+ <li>Confirm that the whole map area is populated with tiles</li>
+ </ol>
+ </p>
+
+ <div id="map" style="width: 350px; height: 200px"></div>
+
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/loadend.html b/misc/openlayers/tests/manual/loadend.html
new file mode 100644
index 0000000..0536b75
--- /dev/null
+++ b/misc/openlayers/tests/manual/loadend.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var lon = 5;
+ var lat = 40;
+ var zoom = 5;
+ var map, layer;
+
+ var numLoadingLayers = 0;
+
+ function init(){
+ map = new OpenLayers.Map( 'map' );
+ layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'});
+
+ layer.events.register('loadstart', this, onloadstart);
+ layer.events.register('loadend', this, onloadend);
+
+ map.addLayer(layer);
+
+ map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
+ }
+
+ function log(msg) {
+ document.getElementById("output").innerHTML += msg + "<br />";
+ }
+
+ function onloadstart(evt) {
+ numLoadingLayers++;
+ var msg = ['loadstart', '# layers loading:', numLoadingLayers].join(' ');
+ log (msg);
+ };
+
+ function onloadend(evt) {
+ numLoadingLayers--;
+ var msg = ['loadend ', '# layers loading:', numLoadingLayers].join(' ');
+ log (msg);
+ };
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">WMS loadstart/loadend events</h1>
+
+ <div id="tags">
+ wms, layer, singletile
+ </div>
+ <p id="shortdesc">
+ Shows the loadstart and loadend events of a WMS layer
+ </p>
+
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ <p>
+ This example is helpful in testing whether all loadstart events are followed
+ by a loadend event.
+ Test by using scroll-wheel up and down.
+ </p>
+ </div>
+
+ <h1>loadstart and loadend events</h1>
+ <pre id="output"></pre>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/manual/map-events.html b/misc/openlayers/tests/manual/map-events.html
new file mode 100644
index 0000000..3695e82
--- /dev/null
+++ b/misc/openlayers/tests/manual/map-events.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <title>map.div Events Acceptance Test</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css">
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css">
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+ function init() {
+ map = new OpenLayers.Map('map');
+ layer = new OpenLayers.Layer.OSM( "Simple OSM Map");
+ map.addLayer(layer);
+ map.setCenter(
+ new OpenLayers.LonLat(-71.147, 42.472).transform(
+ new OpenLayers.Projection("EPSG:4326"),
+ map.getProjectionObject()
+ ), 12
+ );
+
+ var element = document.getElementById('map');
+ element.addEventListener('mousedown', function(evt) {
+ alert('mousedown on map div');
+ });
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">map.div Events Acceptance Test</h1>
+
+ <div id="map" class="smallmap"></div>
+
+ <p><b>Test 1</b> : mousedown the map; an alert must be displayed.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/memory/Marker-2258.html b/misc/openlayers/tests/manual/memory/Marker-2258.html
new file mode 100644
index 0000000..b2d8a37
--- /dev/null
+++ b/misc/openlayers/tests/manual/memory/Marker-2258.html
@@ -0,0 +1,60 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Memory Test - Layer.Markers / Marker</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ width: 256px;
+ height: 256px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../../lib/Firebug/firebug.js"></script>
+ <script src="../../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer, marker;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ map.addLayer(new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} ));
+ map.setCenter(new OpenLayers.LonLat(0, 0), 0);
+
+ layer = new OpenLayers.Layer.Markers( "Markers" );
+ map.addLayer(layer);
+
+ marker = new OpenLayers.Marker(new OpenLayers.LonLat(0,0));
+ layer.addMarker(marker);
+
+ window.setTimeout(function() {
+ layer.removeMarker(marker);
+ layer.addMarker(marker);
+
+ // people SHOULD call marker.destroy(). But if they don't
+ // we leak memory.
+ //marker.destroy();
+
+ window.alert("Setup - hit STOP in the leak detector now");
+ }, 100);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Memory Test - Layer.Markers / Marker</h1>
+ <pre id="status"></pre>
+ <div id="map"></div>
+ <p>
+ This test is a memory leak test for usage of Layer.Markers / Marker.
+ </p>
+ <p>
+ Run this test in IE6/7 with <a href="http://blogs.msdn.com/gpde/pages/javascript-memory-leak-detector-v2.aspx">JavaScript Memory Leak Detector v2</a>
+ and watch it identify a leak unless this is fixed.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/memory/PanZoom-2323.html b/misc/openlayers/tests/manual/memory/PanZoom-2323.html
new file mode 100644
index 0000000..de629a6
--- /dev/null
+++ b/misc/openlayers/tests/manual/memory/PanZoom-2323.html
@@ -0,0 +1,41 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Memory Test - PanZoom.getSlideFactor</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ width: 256px;
+ height: 256px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../../lib/Firebug/firebug.js"></script>
+ <script src="../../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ var layer;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Memory Test - PanZoom.getSlideFactor</h1>
+ <pre id="status"></pre>
+ <div id="map"></div>
+ <p>
+ This test is a memory leak test for: PanZoom.getSlideFactor.
+ </p>
+ <p>
+ Run this test in IE6/7 with <a href="http://blogs.msdn.com/gpde/pages/javascript-memory-leak-detector-v2.aspx">JavaScript Memory Leak Detector v2</a>
+ and watch it identify a leak unless this is fixed.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/memory/RemoveChild-2170.html b/misc/openlayers/tests/manual/memory/RemoveChild-2170.html
new file mode 100644
index 0000000..abe9249
--- /dev/null
+++ b/misc/openlayers/tests/manual/memory/RemoveChild-2170.html
@@ -0,0 +1,56 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Memory Test - DOMNode.removeChild</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ width: 512px;
+ height: 512px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../../lib/Firebug/firebug.js"></script>
+ <script src="../../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layer;
+
+ function tearDown() {
+ layer.events.unregister("loadend", layer, tearDown);
+ window.setTimeout(function() {
+ map.removeLayer(layer);
+ //map.addLayer(layer);
+ layer.destroy();
+ window.alert("Setup - hit STOP in the leak detector now");
+ }, 100);
+ }
+
+ function init(){
+ map = new OpenLayers.Map( 'map', {maxResolution:1.40625/2} );
+ layer = new OpenLayers.Layer.TMS( "TMS",
+ "http://labs.metacarta.com/wms-c/Basic.py/", {layername: 'basic', type:'png'} );
+ map.addLayer(layer);
+ map.setCenter(new OpenLayers.LonLat(5, 40), 5);
+
+ layer.events.register("loadend", layer, tearDown);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Memory Test - DOMNode.removeChild</h1>
+ <pre id="status"></pre>
+ <div id="map"></div>
+ <p>
+ This test is a memory leak test for usage of DOMNode.removeChild
+ </p>
+ <p>
+ Run this test in IE6/7 with <a href="http://blogs.msdn.com/gpde/pages/javascript-memory-leak-detector-v2.aspx">JavaScript Memory Leak Detector v2</a>
+ and watch it identify a leak unless this is fixed.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/memory/VML-2170.html b/misc/openlayers/tests/manual/memory/VML-2170.html
new file mode 100644
index 0000000..2f72300
--- /dev/null
+++ b/misc/openlayers/tests/manual/memory/VML-2170.html
@@ -0,0 +1,49 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Memory Test - Renderer.VML - onselectstart</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ width: 256px;
+ height: 256px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../../lib/Firebug/firebug.js"></script>
+ <script src="../../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ var layer;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer.Vector("Test-VML", {renderers:['VML']});
+ map.addLayers([layer]);
+
+ window.setTimeout(function() {
+ layer.redraw();
+ window.alert("Setup - hit STOP in the leak detector now");
+ }, 100);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Memory Test - Renderer.VML - onselectstart</h1>
+ <pre id="status"></pre>
+ <div id="map"></div>
+ <p>
+ This test is a memory leak test for usage of "onselectstart" event handler in Renderer.VML
+ </p>
+ <p>
+ Run this test in IE6/7 with <a href="http://blogs.msdn.com/gpde/pages/javascript-memory-leak-detector-v2.aspx">JavaScript Memory Leak Detector v2</a>
+ and watch it identify a leak unless this is fixed.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/multiple-google-layers.html b/misc/openlayers/tests/manual/multiple-google-layers.html
new file mode 100644
index 0000000..df5c4f0
--- /dev/null
+++ b/misc/openlayers/tests/manual/multiple-google-layers.html
@@ -0,0 +1,135 @@
+<html>
+ <head>
+ <title>Multiple Google Layers Acceptance Test</title>
+ <script src="http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ"></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../theme/default/google.css" type="text/css" />
+ <style>
+ .col {
+ position: relative;
+ width: 50%;
+ }
+ #col1 {
+ float: left;
+ }
+ #col2 {
+ float: right;
+ }
+ .map {
+ position: relative;
+ height: 300px;
+ }
+ .wrap {
+ position: relative;
+ padding: 10px;
+ }
+ ul {
+ padding: 0;
+ }
+ ul li {
+ list-style: none;
+ }
+ p.clear {
+ clear: both;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="col1" class="col">
+ <div class="wrap">
+ <div id="map1" class="map"></div>
+ layers for map1
+ <ul>
+ <li><input type="checkbox" checked="checked" name="streets1" id="streets1"><label for="streets1">streets</label></li>
+ <li><input type="checkbox" checked="checked" name="sat1" id="sat1"><label for="sat1">imagery</label></li>
+ <li><input type="checkbox" checked="checked" name="topo1" id="topo1"><label for="topo1">topography</label></li>
+ </ul>
+ </div>
+ </div>
+ <div id="col2" class="col">
+ <div class="wrap">
+ <div id="map2" class="map"></div>
+ layers for map2
+ <ul>
+ <li><input type="checkbox" name="streets2" id="streets2"><label for="streets2">streets</label></li>
+ <li><input type="checkbox" name="sat2" id="sat2"><label for="sat2">imagery</label></li>
+ <li><input type="checkbox" name="topo2" id="topo2"><label for="topo2">topography</label></li>
+ </ul>
+ </div>
+ </div>
+ <p class="clear">
+ This example is used to confirm that resizable maps with multiple
+ Google layers work properly. Click the checkboxes to add/remove
+ layers from the maps. Use the layer switcher to change the map's
+ base layer. You should be able to confirm the following:
+ <ol>
+ <li>Adding and removing layers doesn't raise any errors
+ (regardless of how many times the same layer is added/removed).</li>
+ <li>The Google "Powered By" link is always visible and clickable
+ when a Google layer is displayed.</li>
+ <li>The Google "Terms of Use" link is always visible and clickable
+ when a Google layer is displayed.</li>
+ <li>Resizing a map (by resizing the browser window) and then
+ changing base layer works well. That is, the center & scale are
+ preserved and all tiles are well aligned.</li>
+ <li>Setting the base layer to the "Dummy Layer" hides all other
+ Google base layers, "Powered By" link, and "Terms of Use" link.</li>
+ </ol>
+ </p>
+ <script>
+
+ var map1 = new OpenLayers.Map("map1");
+ var streets1 = new OpenLayers.Layer.Google("Streets", {
+ type: G_NORMAL_MAP
+ });
+ var sat1 = new OpenLayers.Layer.Google("Imagery", {
+ type: G_SATELLITE_MAP
+ });
+ var topo1 = new OpenLayers.Layer.Google("Topography", {
+ type: G_PHYSICAL_MAP
+ });
+ var dummy1 = new OpenLayers.Layer("Dummy Layer", {
+ isBaseLayer: true
+ });
+ map1.addLayers([streets1, sat1, topo1, dummy1]);
+ map1.addControl(new OpenLayers.Control.LayerSwitcher);
+ map1.zoomToMaxExtent();
+
+ var map2 = new OpenLayers.Map("map2");
+ var streets2 = new OpenLayers.Layer.Google("Streets", {
+ type: G_NORMAL_MAP
+ });
+ var sat2 = new OpenLayers.Layer.Google("Imagery", {
+ type: G_SATELLITE_MAP
+ });
+ var topo2 = new OpenLayers.Layer.Google("Topography", {
+ type: G_PHYSICAL_MAP
+ });
+ var dummy2 = new OpenLayers.Layer("Dummy Layer", {
+ isBaseLayer: true
+ });
+ map2.addLayer(dummy2);
+ map2.addControl(new OpenLayers.Control.LayerSwitcher);
+ map2.zoomToMaxExtent();
+
+ // add behavior to checkboxes
+ var check, inputs = document.getElementsByTagName("input");
+ for (var i=0, len=inputs.length; i<len; ++i) {
+ check = inputs[i];
+ check.onclick = function() {
+ var name = this.name;
+ var num = name.match(/\d$/)[0];
+ var layer = window[name];
+ var map = window["map" + num];
+ if (this.checked) {
+ map.addLayer(layer);
+ } else {
+ map.removeLayer(layer);
+ }
+ }
+ }
+
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/manual/overviewmap-projection.html b/misc/openlayers/tests/manual/overviewmap-projection.html
new file mode 100644
index 0000000..bb15c9f
--- /dev/null
+++ b/misc/openlayers/tests/manual/overviewmap-projection.html
@@ -0,0 +1,70 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <style type="text/css">
+ .olControlAttribution { bottom: 0px!important }
+ #map {
+ height: 512px;
+ }
+ </style>
+
+ <script src='http://maps.google.com/maps?file=api&amp;v=2&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+
+ // make map available for easy debugging
+ var map;
+
+ function init(){
+ var options = {
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326"),
+ units: "m",
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+ 20037508, 20037508.34)
+ };
+ map = new OpenLayers.Map('map', options);
+
+ // create Google Mercator layers
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets",
+ {'sphericalMercator': true}
+ );
+ map.addLayers([gmap]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.Permalink());
+ map.addControl(new OpenLayers.Control.MousePosition());
+ var ovmap = new OpenLayers.Control.OverviewMap({
+ maxRatio: 16,
+ layers: [new OpenLayers.Layer.WMS("OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'})]
+ });
+ map.addControl(ovmap);
+ ovmap.maximizeControl();
+ if (!map.getCenter()) {map.zoomToMaxExtent()};
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Overview Map Projection Test</h1>
+
+ <div id="tags">
+ </div>
+ <p id="shortdesc">
+ Acceptance test for different projections in map and overview map.
+ The map uses EPSG:900913, the overview map EPSG:4326. Zoom the map and
+ drag both the map and the overview map to see it in action.
+ </p>
+ <div id="map" class="smallmap"></div>
+
+ <div id="docs">
+ </div>
+ </body>
+</html>
+
+
+
diff --git a/misc/openlayers/tests/manual/page-position.html b/misc/openlayers/tests/manual/page-position.html
new file mode 100644
index 0000000..a59dfde
--- /dev/null
+++ b/misc/openlayers/tests/manual/page-position.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Page Position Test</title>
+
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ <style type="text/css">
+ #mapwrap {
+ border: 10px solid red;
+ width: 532px;
+ height: 276px;
+ }
+ #map {
+ position: absolute;
+ border: 10px solid #ccc;
+ width: 512px;
+ height: 256px;
+ }
+ #controlToggle li {
+ list-style: none;
+ }
+ p {
+ width: 512px;
+ }
+ #scrollspace {
+ height: 500px;
+ }
+ </style>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, drawControls;
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
+
+ var lineLayer = new OpenLayers.Layer.Vector("Line Layer");
+
+ map.addLayers([wmsLayer, lineLayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.MousePosition());
+
+ drawControl = new OpenLayers.Control.DrawFeature(lineLayer,
+ OpenLayers.Handler.Path);
+
+ map.addControl(drawControl);
+
+ map.setCenter(new OpenLayers.LonLat(0, 0), 3);
+
+ document.getElementById('noneToggle').checked = true;
+ }
+
+ function toggleControl(element) {
+ var control = drawControl;
+ if(element.value == "draw" && element.checked) {
+ control.activate();
+ } else {
+ control.deactivate();
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">OpenLayers Page Position Test</h1>
+
+ <p id="shortdesc">
+ Test if borders and scroll position cause unwanted offsets on the
+ mouse positions reported by map events.
+ </p>
+ <div id="mapwrap">
+ <div id="map"></div>
+ </div>
+
+ <ul id="controlToggle">
+ <li>
+ <input type="radio" name="type" value="none" id="noneToggle"
+ onclick="toggleControl(this);" checked="checked" />
+ <label for="noneToggle">navigate</label>
+ </li>
+ <li>
+ <input type="radio" name="type" value="draw" id="lineToggle" onclick="toggleControl(this);" />
+ <label for="lineToggle">draw line</label>
+ </li>
+ </ul>
+
+ <div id="docs">
+ <p>This map's div has a border and absolute positioning, wrapped
+ by a container which also has a border. The page is also
+ scrollable. Neither the borders nor scrolling the page should
+ result in unwanted offsets on pixel positions reported by map
+ events.</p>
+ <p>With the line drawing control active, click on the map to add a
+ point. The point should be drawn at the exact mouse location.</p>
+ <p>With the navigation control active, shift-drag a zoom rectangle.
+ The rectangle's corner should align exactly with the mouse
+ cursor.</p>
+ <p>Scroll the page and repeat the above tests.</p>
+ <div id="scrollspace"><div>
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/pan-redraw-svg.html b/misc/openlayers/tests/manual/pan-redraw-svg.html
new file mode 100644
index 0000000..ec1126b
--- /dev/null
+++ b/misc/openlayers/tests/manual/pan-redraw-svg.html
@@ -0,0 +1,58 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <style type="text/css">
+ #map {
+ width: 512px;
+ height: 512px;
+ border: 1px solid gray;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, point;
+
+ function init(){
+ var options = {
+ projection: new OpenLayers.Projection("EPSG:900913"),
+ displayProjection: new OpenLayers.Projection("EPSG:4326"),
+ units: "m",
+ maxResolution: 20, //0.07464553542137146,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+ 20037508, 20037508.34)
+ };
+ map = new OpenLayers.Map('map', options);
+ var vector = new OpenLayers.Layer.Vector(
+ "Vectors",
+ {isBaseLayer: true}
+ );
+ map.addLayer(vector);
+
+ var x = -20000;//4.33791754;
+ var y = 20000;
+ point = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y)
+ );
+
+ map.addLayer(vector);
+ vector.addFeatures([point]);
+ map.setCenter(new OpenLayers.LonLat(0, 0), 5);
+ }
+
+ function pan(){
+ map.panTo(point.geometry.getBounds().getCenterLonLat());
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h3 id="title">SVG inValidRange Redraw Test Case</h3>
+ <p>Before fixing #1631, after klicking Go! no point would have appeared. The Go! button
+ pans the map over a long distance. Before dragging, the point would have been
+ outside the valid range, and the pan operation would not have triggered the SVG
+ coordinate system to be recreated. The new vector rendering takes care of all this. </p>
+ <div id="map"></div>
+ <input type="button" value="Go!" onclick="pan();"></input>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/popup-keepInMap.html b/misc/openlayers/tests/manual/popup-keepInMap.html
new file mode 100644
index 0000000..4ba1c18
--- /dev/null
+++ b/misc/openlayers/tests/manual/popup-keepInMap.html
@@ -0,0 +1,100 @@
+<html xmlns="http://www.w3.org/1999/xhtml" debug="true">
+ <head>
+ <title>OpenLayers: Popup Mayhem</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <style type="text/css">
+ #map {
+ width: 900px;
+ height: 500px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ var layer, markers;
+
+ var currentPopup;
+
+
+ AutoSizeFramedCloud = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
+ 'autoSize': true,
+ 'panMapIfOutOfView': false
+ });
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ markers = new OpenLayers.Layer.Markers("zibo", {isBaseLayer: true});
+ map.addLayer(markers);
+
+ addMarkers();
+ map.zoomToMaxExtent();
+ }
+
+ function addMarkers() {
+
+ var ll, popupClass, popupContentHTML;
+
+ //anchored bubble popup small contents autosize closebox
+ ll = new OpenLayers.LonLat(-35,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = "<div>This popup can't be panned to fit in view, so its popup text should fit inside the map. If it doens't, instead of expaning outside, it will simply make the content scroll. Scroll scroll scroll your boat, gently down the stream! Chicken chicken says the popup text is really boring to write. Did you ever see a popup a popup a popup did you ever see a popup a popup right now. With this way and that way and this way and that way did you ever see a popup a popup right now. I wonder if this is long enough. it might be, but maybe i should throw in some other content. <ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>(get your booty on the floor) </div>";
+ addMarker(ll, popupClass, popupContentHTML, true, true);
+
+
+ }
+
+ /**
+ * Function: addMarker
+ * Add a new marker to the markers layer given the following lonlat,
+ * popupClass, and popup contents HTML. Also allow specifying
+ * whether or not to give the popup a close box.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>} Where to place the marker
+ * popupClass - {<OpenLayers.Class>} Which class of popup to bring up
+ * when the marker is clicked.
+ * popupContentHTML - {String} What to put in the popup
+ * closeBox - {Boolean} Should popup have a close box?
+ * overflow - {Boolean} Let the popup overflow scrollbars?
+ */
+ function addMarker(ll, popupClass, popupContentHTML, closeBox, overflow) {
+
+ var feature = new OpenLayers.Feature(markers, ll);
+ feature.closeBox = closeBox;
+ feature.popupClass = popupClass;
+ feature.data.popupContentHTML = popupContentHTML;
+ feature.data.overflow = (overflow) ? "auto" : "hidden";
+
+ var marker = feature.createMarker();
+
+ var markerClick = function (evt) {
+ if (this.popup == null) {
+ this.popup = this.createPopup(this.closeBox);
+ map.addPopup(this.popup);
+ this.popup.show();
+ } else {
+ this.popup.toggle();
+ this.popup.updateSize();
+ }
+ currentPopup = this.popup;
+ OpenLayers.Event.stop(evt);
+ };
+ marker.events.register("mousedown", feature, markerClick);
+
+ markers.addMarker(marker);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Popup KeepInMap</h1>
+
+ <div id="map" class="smallmap"></div>
+ </div>
+ Click on popup, and the content should scroll rather than expanding outside the map.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/reflow.html b/misc/openlayers/tests/manual/reflow.html
new file mode 100644
index 0000000..bb9585e
--- /dev/null
+++ b/misc/openlayers/tests/manual/reflow.html
@@ -0,0 +1,59 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <style type="text/css">
+ #map {
+ width: 800px;
+ height: 475px;
+ border: 1px solid black;
+ }
+ </style>
+ <script src="../../lib/OpenLayers.js" type="text/javascript"></script>
+ <script type="text/javascript">
+
+ var map;
+ var vectors;
+
+ function init(){
+ map = new OpenLayers.Map('map');
+ var wms = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ vectors = new OpenLayers.Layer.Vector(
+ "Simple Geometry",
+ {
+ styleMap: new OpenLayers.StyleMap({
+ externalGraphic: "../../img/marker-gold.png",
+ pointRadius: 10
+ })
+ }
+ );
+
+ map.addLayers([wms, vectors]);
+
+ var features = [];
+ var x = -111.04;
+ var y = 45.68;
+ for(var i = 0; i < 10; i++){
+ x += i * .5;
+ y += i * .1;
+ features.push(
+ new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(x, y)
+ )
+ );
+ }
+
+ map.setCenter(new OpenLayers.LonLat(x, y), 5);
+ vectors.addFeatures(features);
+ };
+
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="map"></div>
+ <p>Use the pan buttons. See flicker at end of animated pan.</p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/renderedDimensions.html b/misc/openlayers/tests/manual/renderedDimensions.html
new file mode 100644
index 0000000..b01624b
--- /dev/null
+++ b/misc/openlayers/tests/manual/renderedDimensions.html
@@ -0,0 +1,113 @@
+<html xmlns="http://www.w3.org/1999/xhtml" debug="true">
+ <head>
+ <title>OpenLayers: Popup Mayhem</title>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <style type="text/css">
+ #map {
+ width: 900px;
+ height: 500px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map;
+ var layer, markers;
+
+ var currentPopup;
+
+
+// different popup types
+
+
+ //disable the autosize for the purpose of our matrix
+ OpenLayers.Popup.FramedCloud.prototype.autoSize = false;
+
+ AutoSizeFramedCloud = OpenLayers.Class(OpenLayers.Popup.FramedCloud, {
+ 'autoSize': true
+ });
+
+ function init(){
+ map = new OpenLayers.Map('map');
+
+ layer = new OpenLayers.Layer(
+ "popupMatrix",
+ {isBaseLayer: true}
+ );
+ map.addLayer(layer);
+
+ markers = new OpenLayers.Layer.Markers("zibo");
+ map.addLayer(markers);
+
+ addMarkers();
+ map.zoomToMaxExtent();
+ }
+
+ function addMarkers() {
+
+ var ll, popupClass, popupContentHTML;
+
+ //anchored bubble popup small contents autosize closebox
+ ll = new OpenLayers.LonLat(-35,-15);
+ popupClass = AutoSizeFramedCloud;
+ popupContentHTML = "<div>This text's line-height is affected<br/>by it's parents. Thus we have to<br/>place the content inside<br/>the correct container to get<br/>the rendered size.</div>";
+ addMarker(ll, popupClass, popupContentHTML, true);
+
+
+ }
+
+ /**
+ * Function: addMarker
+ * Add a new marker to the markers layer given the following lonlat,
+ * popupClass, and popup contents HTML. Also allow specifying
+ * whether or not to give the popup a close box.
+ *
+ * Parameters:
+ * ll - {<OpenLayers.LonLat>} Where to place the marker
+ * popupClass - {<OpenLayers.Class>} Which class of popup to bring up
+ * when the marker is clicked.
+ * popupContentHTML - {String} What to put in the popup
+ * closeBox - {Boolean} Should popup have a close box?
+ * overflow - {Boolean} Let the popup overflow scrollbars?
+ */
+ function addMarker(ll, popupClass, popupContentHTML, closeBox, overflow) {
+
+ var feature = new OpenLayers.Feature(markers, ll);
+ feature.closeBox = closeBox;
+ feature.popupClass = popupClass;
+ feature.data.popupContentHTML = popupContentHTML;
+ feature.data.overflow = (overflow) ? "auto" : "hidden";
+
+ var marker = feature.createMarker();
+
+ var markerClick = function (evt) {
+ if (this.popup == null) {
+ this.popup = this.createPopup(this.closeBox);
+ map.addPopup(this.popup);
+ this.popup.show();
+ } else {
+ this.popup.toggle();
+ }
+ currentPopup = this.popup;
+ OpenLayers.Event.stop(evt);
+ };
+ marker.events.register("mousedown", feature, markerClick);
+
+ markers.addMarker(marker);
+ }
+
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Popup Matrix</h1>
+
+ <div id="tags">
+ </div>
+ <div style="line-height: 40px;">
+ <div id="map" class="smallmap"></div>
+ </div>
+ Click on popup, should be able to read a full sentence, not just two lines.
+ </div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/select-feature-right-click.html b/misc/openlayers/tests/manual/select-feature-right-click.html
new file mode 100644
index 0000000..edd79d6
--- /dev/null
+++ b/misc/openlayers/tests/manual/select-feature-right-click.html
@@ -0,0 +1,86 @@
+<html>
+ <head>
+ <title>OpenLayers Ticket 3404</title>
+ <script src="../../lib/OpenLayers.js"></script>
+ </head>
+ <body>
+ <table cellpadding="10px">
+ <tr>
+ <td width="600">
+ <p><a href="http://trac.osgeo.org/openlayers/ticket/3404">Ticket 3404</a> Test Page</p>
+ <p>This bug is only triggered by physical right mouse clicks so it is not possible to write
+ an automated js unit test</p>
+ <p>When a SelectFeature control and a Navigation control are added to a map the left-click
+ mousedown events are stopped by a Drag handler before reaching the Feature handler. However,
+ right-click mousedown events so pass through and reach the Feature handler.</p>
+ <p>The Feature handler records the xy of
+ each mousedown and mouseup events so they can be compared in the click event. Because only right-click
+ mousedown event are received the location of future left-click mouseup events are compared
+ to the location of the 'stale' right-click mousedown event resulting in the feature not being selected.</p>
+ <p>Steps to recreate the bug:
+ <ol>
+ <li>Left-click a point to select it.</li>
+ <li>Left-click the map to deselect the point.</li>
+ <li>Left-click a different point to select it.</li>
+ <li>Left-click the map to deselect the second point.</li>
+ <li>Right-click the map then left-click to close the browser context menu.</li>
+ <li>Left-click a point.</li>
+ </ol>
+ </p>
+ <p>Expected: The point is selected.</p>
+ </td>
+ <td>
+ <div style="width:300; height:400" id="map"></div>
+ </td>
+ </tr>
+ </table>
+
+ <script defer="defer" type="text/javascript">
+ var map = new OpenLayers.Map('map');
+
+ var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+ "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} );
+
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ var vectorLayer = new OpenLayers.Layer.Vector("Vector Layer", {
+ renderers: renderer
+ });
+
+ map.addLayers([wmsLayer, vectorLayer]);
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+
+ var selectControl = new OpenLayers.Control.SelectFeature(
+ vectorLayer,
+ {
+ clickout: true, toggle: false,
+ multiple: false, hover: false,
+ toggleKey: "ctrlKey", // ctrl key removes from selection
+ multipleKey: "shiftKey", // shift key adds to selection
+ }
+ );
+
+ map.addControl(selectControl);
+ selectControl.activate();
+
+ map.addControl(new OpenLayers.Control.Navigation());
+ map.setCenter(new OpenLayers.LonLat(-75.1641667, 39.9522222), 10);
+
+ var createRandomFeatures = function() {
+ var extent = map.getExtent();
+ var features = [];
+ for(var i=0; i<10; ++i) {
+ features.push(new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(extent.left + (extent.right - extent.left) * Math.random(),
+ extent.bottom + (extent.top - extent.bottom) * Math.random()
+ )));
+ }
+ return features;
+ }
+
+ vectorLayer.addFeatures(createRandomFeatures());
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/select-feature.html b/misc/openlayers/tests/manual/select-feature.html
new file mode 100644
index 0000000..6e1fba0
--- /dev/null
+++ b/misc/openlayers/tests/manual/select-feature.html
@@ -0,0 +1,75 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Select Feature Test</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ margin: 1em;
+ width: 512px;
+ height: 512px;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, selectControl1, selectControl2;
+
+ function init() {
+ map = new OpenLayers.Map('map');
+ var wmsLayer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}
+ );
+ var vectorLayer = new OpenLayers.Layer.Vector("Vector Layer");
+ var pointFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Point(-50, -45)
+ );
+ var polygonFeature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-50,-50),
+ new OpenLayers.Geometry.Point(-40,-50),
+ new OpenLayers.Geometry.Point(-40,-40),
+ new OpenLayers.Geometry.Point(-50,-50)
+ ])
+ ])
+ );
+ vectorLayer.addFeatures([pointFeature, polygonFeature]);
+ map.addLayers([wmsLayer, vectorLayer]);
+ selectControl1 = new OpenLayers.Control.SelectFeature(
+ vectorLayer, {geometryTypes: ['OpenLayers.Geometry.Point']}
+ );
+ selectControl2 = new OpenLayers.Control.SelectFeature(
+ vectorLayer, {
+ geometryTypes: ['OpenLayers.Geometry.Polygon'],
+ hover: true
+ });
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.addControl(selectControl1);
+ map.addControl(selectControl2);
+ selectControl1.activate();
+ selectControl2.activate();
+ map.setCenter(new OpenLayers.LonLat(-45, -45), 4);
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Select Feature Test</h1>
+ <div id="map"></div>
+ <p>
+
+ The map includes two select feature controls. The first one operates on
+ geometries of type OpenLayers.Geometry.Point only and works on clicks. The
+ second one operates on geometries of type OpenLayers.Geometry.Polygon and
+ works on mouseover's. If you select the point geometry by clicking on it,
+ it shouldn't be unselected when the mouse moves out if it.
+
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/tiles-loading.html b/misc/openlayers/tests/manual/tiles-loading.html
new file mode 100644
index 0000000..cbd0e10
--- /dev/null
+++ b/misc/openlayers/tests/manual/tiles-loading.html
@@ -0,0 +1,122 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Tiles Loading Acceptance Test</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ margin: 1em;
+ float: left;
+ width: 512px;
+ height: 512px;
+ }
+
+ </style>
+
+ <script src='http://maps.google.com/maps?file=api&amp;v=2.82&amp;key=ABQIAAAAjpkAC9ePGem0lIq5XcMiuhR_wWLPFku8Ix9i2SXYRVK3e45q1BQUd_beF8dtzKET_EteAjPdGDwqpQ'></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ // make map available for easy debugging
+ var map;
+
+ // increase reload attempts
+ OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
+
+ function init(){
+ var options = {
+ controls: [],
+ projection: "EPSG:900913",
+ units: "m",
+ maxResolution: 156543.0339,
+ maxExtent: new OpenLayers.Bounds(-20037508, -20037508,
+ 20037508, 20037508.34)
+ };
+ map = new OpenLayers.Map('map', options);
+ // create Google Mercator layers
+ var gmap = new OpenLayers.Layer.Google(
+ "Google Streets",
+ {'sphericalMercator': true}
+ );
+ // create WMS layer
+ var wmsMaxResolution = 78271.51695;
+ var wms = new OpenLayers.Layer.WMS(
+ "World Map",
+ "http://world.freemap.in/tiles/",
+ {'layers': 'factbook-overlay', 'format':'png'},
+ {
+ 'opacity': 0.4,
+ 'isBaseLayer': false,
+ 'wrapDateLine': true,
+ 'buffer': 0,
+ 'maxResolution' : wmsMaxResolution
+ }
+ );
+ map.addLayers([gmap, wms]);
+ map.addControl(new OpenLayers.Control.Navigation());
+ map.addControl(new OpenLayers.Control.LayerSwitcher());
+ map.addControl(new OpenLayers.Control.PanZoomBar());
+
+ function onLayerChanged() {
+ var html = '<p>WMS Layer state - in range: '
+ + this.inRange
+ + ', visibility: '
+ + this.visibility;
+ + '</p>';
+ document.getElementById('layerstate').innerHTML = html;
+ }
+ map.events.register('changelayer', wms, onLayerChanged);
+
+ function onTileLoaded() {
+ var html = '<p>Message: ';
+ if (this.numLoadingTiles > 0) {
+ html += 'Loading tiles...';
+ } else {
+ html += 'Done loading tiles';
+ }
+ html += '</p>';
+ document.getElementById('tilesloading').innerHTML = html;
+ }
+ wms.events.register('tileloaded', wms, onTileLoaded);
+
+ map.zoomToMaxExtent()
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="map"></div>
+ <p>
+
+ <b>Test 0</b> : at the initial zoom the WMS layer is in range, you should
+ therefore see the 'Loading tiles...' message when loading the page for
+ the first time.
+
+ </p>
+ <p>
+
+ <b>Test 1</b> : If you zoom out by one level (using the zoombar), the WMS
+ layer becomes out of range. No tile should be loaded so you shouldn't see
+ the 'Loading tiles...' message.
+
+ </p>
+ <p>
+
+ <b>Test 2</b> : Zoom in by one level to go back to initial state (the WMS
+ is back). Open the layer switcher and turn off the WMS layer. No tile
+ should be loaded so you shouldn't see the 'Loading tiles...' message.
+
+ </p>
+ <p>
+
+ <b>Test 3</b> : Keep the WMS layer turned off in the layer switcher. Zoom
+ out by one level again. The layer is both invisible and out of range, so
+ you shouldn't see the 'Loading tiles...' message.
+
+ </p>
+ <div id="layerstate"><p>WMS Layer state - in range: true, visibility: true</p></div>
+ <div id="tilesloading"><p>Message:</p></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/tween.html b/misc/openlayers/tests/manual/tween.html
new file mode 100644
index 0000000..88606cb
--- /dev/null
+++ b/misc/openlayers/tests/manual/tween.html
@@ -0,0 +1,82 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Tween Example</title>
+ <style type="text/css">
+ #viewport {
+ width: 500px;
+ height: 300px;
+ border: 1px solid black;
+ }
+ #block {
+ width: 10px;
+ height: 10px;
+ background-color: red;
+ position: absolute;
+ }
+ </style>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var tween, events;
+
+ function init(){
+ tween = new OpenLayers.Tween(OpenLayers.Easing.Linear.easeIn);
+
+ events = new OpenLayers.Events(null, OpenLayers.Util.getElement('viewport'), null, true);
+ events.register("mousedown", null, moveBlock);
+
+ changeTween();
+ }
+ function moveBlock(e) {
+ var block = OpenLayers.Util.getElement('block');
+ var viewport = OpenLayers.Util.getElement('viewport');
+ var blockPosition = OpenLayers.Util.pagePosition(block);
+ var viewportPosition = OpenLayers.Util.pagePosition(viewport);
+ e.xy = events.getMousePosition(e);
+ var from = {
+ x: blockPosition[0] - viewportPosition[0],
+ y: blockPosition[1] - viewportPosition[1]
+ };
+ var to = {
+ x: e.xy.x,
+ y: e.xy.y
+ }
+ var duration = OpenLayers.Util.getElement('duration').value;
+ var callbacks = {
+ eachStep: function(value) {
+ block.style.left = (value.x + viewportPosition[0]) + 'px';
+ block.style.top = (value.y + viewportPosition[1]) + 'px';
+ }
+ }
+ tween.start(from, to, duration, {callbacks: callbacks});
+
+ }
+ function changeTween() {
+ var transition = OpenLayers.Util.getElement('transition').value;
+ var easing = OpenLayers.Util.getElement('easing').value;
+ tween.stop();
+ tween.easing = OpenLayers.Easing[transition][easing];
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <div id="title">Tween Example</div>
+ <div id="tags"></div>
+ <div id="shortdesc">Show transition effects</div>
+ <select name="transition" id="transition" onchange="changeTween()">
+ <option value="Linear">Linear</option>
+ <option value="Expo">Expo</option>
+ <option value="Quad">Quad</option>
+ </select>
+ <select name="easing" id="easing" onchange="changeTween()">
+ <option value="easeIn">EaseIn</option>
+ <option value="easeOut">EaseOut</option>
+ </select>
+ <input type="text" name="duration" id="duration" value="100"></input>
+ <div id="viewport">
+ <div id="block"></div>
+ </div>
+ <div id="docs">
+ This is an example of transition effects.
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/manual/vector-features-performance.html b/misc/openlayers/tests/manual/vector-features-performance.html
new file mode 100644
index 0000000..7990379
--- /dev/null
+++ b/misc/openlayers/tests/manual/vector-features-performance.html
@@ -0,0 +1,149 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Vector Features Performance Test</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ width: 512px;
+ height: 512px;
+ border: 1px solid black;
+ }
+ </style>
+
+ <script src="../../lib/Firebug/firebug.js"></script>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, vectorLayer, drawFeature, features
+
+ var run = 0;
+
+ function nextRun() {
+ window.setTimeout(function() {
+ if (run < 5) {
+ vectorLayer.removeFeatures(features);
+ }
+ }, 900);
+
+ window.setTimeout(function(){
+ run++;
+
+ switch(run) {
+ case 1:
+ console.log("First run - feature bboxes will be cached");
+ map.setCenter(new OpenLayers.LonLat(-22.5, -22.5), 3);
+ vectorTestNew()
+ break;
+ case 2:
+ console.log("Test with all features inside extent");
+ vectorTestOld();
+ break;
+ case 3:
+ vectorTestNew();
+ break;
+ case 4:
+ console.log("Test with some features outside extent");
+ map.setCenter(new OpenLayers.LonLat(-22.5, -22.5), 5);
+ vectorTestOld();
+ break;
+ case 5:
+ vectorTestNew();
+ break;
+ }
+ }, 1000);
+ }
+
+ function vectorTestOld(){
+ vectorLayer.renderer.drawFeature = function(feature, style) {
+ if(style == null) {
+ style = feature.style;
+ }
+ if (feature.geometry) {
+ this.drawGeometry(feature.geometry, style, feature.id);
+ }
+ };
+
+ console.time("addFeaturesOld");
+ vectorLayer.addFeatures(features);
+ console.timeEnd("addFeaturesOld");
+ }
+
+ function vectorTestNew() {
+ vectorLayer.renderer.drawFeature = OpenLayers.Renderer[vectorLayer.renderer.CLASS_NAME.split(".")[2]].prototype.drawFeature;
+
+ console.time("addFeatures");
+ vectorLayer.addFeatures(features);
+ console.timeEnd("addFeatures");
+ }
+
+ function init(){
+ // allow testing of specific renderers via "?renderer=Canvas", etc
+ var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+ renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+ map = new OpenLayers.Map('map');
+
+ vectorLayer = new OpenLayers.Layer.Vector("Vector Layer", {
+ isBaseLayer: true,
+ renderers: renderer
+ });
+
+ map.addLayers([vectorLayer]);
+ map.addControl(new OpenLayers.Control.MousePosition());
+ map.setCenter(new OpenLayers.LonLat(-22.5, -22.5), 3);
+
+ vectorLayer.events.register("featuresadded", this, nextRun);
+
+ features = new Array(200);
+ var x, y
+ for (var i = 0; i < 200; i++) {
+ x = -Math.random()*45;
+ y = -Math.random()*45;
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y),
+ new OpenLayers.Geometry.Point(
+ -Math.random()*5+x, -Math.random()*5+y)
+ ]));
+
+ }
+
+ nextRun();
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">New Rendering - Vector Features Performance Test</h1>
+ <div id="map"></div>
+ <p>
+ This test examines if checking for a feature being inside the visible
+ extent before rendering it has an impact on performance. Open the Firebug
+ console after running this test (hit F12) to see the results.
+ <br/>
+ After the performance test, you can drag around the map to see how the new
+ vector rendering feels where features get rendered only when they are visible
+ inside the map extent.
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/manual/vector-layer-zindex.html b/misc/openlayers/tests/manual/vector-layer-zindex.html
new file mode 100644
index 0000000..d33c853
--- /dev/null
+++ b/misc/openlayers/tests/manual/vector-layer-zindex.html
@@ -0,0 +1,143 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Vector Layer ZIndex Test</title>
+ <style type="text/css">
+ body {
+ font-size: 0.8em;
+ }
+ p {
+ padding-top: 1em;
+ }
+ #map {
+ margin: 1em;
+ width: 512px;
+ height: 512px;
+ }
+ </style>
+
+ <script src="../../lib/OpenLayers.js"></script>
+ <script type="text/javascript">
+ var map, layerA, layerB, layerV, selectControl1, selectControl2;
+
+ function init() {
+ map = new OpenLayers.Map('map');
+ var wmsLayer = new OpenLayers.Layer.WMS(
+ "OpenLayers WMS",
+ "http://labs.metacarta.com/wms/vmap0",
+ {layers: 'basic'}
+ );
+
+ layerV = new OpenLayers.Layer.Vector('v');
+ var feature = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.Polygon([
+ new OpenLayers.Geometry.LinearRing([
+ new OpenLayers.Geometry.Point(-110, 60),
+ new OpenLayers.Geometry.Point(-110, 30),
+ new OpenLayers.Geometry.Point(-80, 30),
+ new OpenLayers.Geometry.Point(-110, 60)
+ ])
+ ])
+ );
+ layerV.addFeatures([feature]);
+ selectControl1 = new OpenLayers.Control.SelectFeature(layerV);
+ selectControl2 = new OpenLayers.Control.SelectFeature(layerV, {
+ hover: true,
+ selectStyle: OpenLayers.Util.applyDefaults({fillColor: "red"}, OpenLayers.Feature.Vector.style["select"]),
+ onSelect: function(feature) {
+ selectControl2.unselect(feature);
+ layerV.drawFeature(feature, selectControl2.selectStyle);
+ }
+ });
+ selectControl2.events.register("beforefeatureselected", null, function(feature) {
+ layerV.drawFeature(feature, selectControl2.selectStyle);
+ return false;
+ })
+
+ layerA = new OpenLayers.Layer('a', {'isBaseLayer': false});
+ layerB = new OpenLayers.Layer.WMS(
+ 'b', 'http://www2.dmsolutions.ca/cgi-bin/mswms_gmap', {
+ 'layers': [
+ 'bathymetry', 'land_fn', 'park', 'drain_fn', 'drainage',
+ 'prov_bound', 'fedlimit', 'rail', 'road', 'popplace'
+ ],
+ 'transparent': 'true',
+ 'format': 'image/png'
+ }, {
+ 'reproject': false
+ });
+
+ map.addLayers([wmsLayer, layerV, layerA, layerB]);
+ map.addControl(selectControl2);
+ map.addControl(selectControl1);
+ map.addControl(new OpenLayers.Control.MousePosition());
+ selectControl2.activate();
+ selectControl1.activate();
+
+ map.setCenter(new OpenLayers.LonLat(-90, 20), 2);
+ }
+
+ function removeLayerA() {
+ if (OpenLayers.Util.indexOf(map.layers, layerA) > -1) {
+ map.removeLayer(layerA);
+ }
+ }
+
+ function toggleSelectControl1() {
+ if (selectControl1.active) {
+ selectControl1.deactivate();
+ alert("SelectFeature control for clicks deactivated.");
+ } else {
+ selectControl1.activate();
+ alert("SelectFeature control for clicks activated.");
+ }
+ }
+
+ function toggleSelectControl2() {
+ if (selectControl2.active) {
+ selectControl2.deactivate();
+ alert("SelectFeature control for hover deactivated.");
+ } else {
+ selectControl2.activate();
+ alert("SelectFeature control for hover activated.");
+ }
+ }
+ </script>
+ </head>
+ <body onload="init()">
+ <h1 id="title">Vector Layer ZIndex Test</h1>
+ <div id="map"></div>
+ <p>
+
+ The map includes one base layer (vmap0) and three overlays, namely a vector
+ layer, a fake layer with no images, and a dmsolutions layer. The overlays are
+ added to the map in this order: the vector layer, the fake layer, and the
+ dmsolutions layer. The map also includes a select feature control, which
+ when activated bumped the vector layer z-index to some high value. This
+ makes feature selection work, even though other overlays were added after
+ the vector layer.
+
+ </p>
+ <p>
+
+ If the fake layer is removed from the map (first link below), the vector layer's
+ z-index must not be reset, so the vector layer must not go under the
+ dmsolutions layer and feature selection must continue to function as
+ expected.
+
+ </p>
+ <p>
+
+ If one of the SelectFeature controls is deactivated or activated (second
+ and third link below), the vector layer should change it's position in the
+ layer stack: on top if at least one of the controls is activated, covered
+ by other layers if both are deactivated.
+
+ </p>
+
+ <p>
+ <a href="javascript:removeLayerA()">Remove the fake layer</a>
+ <br/><a href="javascript:toggleSelectControl1()">Toggle the click SelectFeature control's active status</a>
+ <br/><a href="javascript:toggleSelectControl2()">Toggle the hover SelectFeature control's active status</a>
+ </p>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/mice.xml b/misc/openlayers/tests/mice.xml
new file mode 100644
index 0000000..4a001ec
--- /dev/null
+++ b/misc/openlayers/tests/mice.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:bsc="http://www.bsc-eoc.org/bsc"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd
+ http://www.bsc-eoc.org/bsc http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=OWLS&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-89.817223,45.005555 -74.755001,51.701388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember><bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110 -79.771668,45.891110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277 -83.755834,46.365277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:owlname>owl</bsc:owlname>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277 -83.808612,46.175277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166 -84.111112,46.309166</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110 -83.678612,46.821110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888 -83.664445,46.518888</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277 -80.613334,46.730277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054 -79.676946,45.428054</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944 -83.853056,46.236944</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388 -82.289167,45.896388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+
diff --git a/misc/openlayers/tests/node.js/mockdom.js b/misc/openlayers/tests/node.js/mockdom.js
new file mode 100644
index 0000000..68c088a
--- /dev/null
+++ b/misc/openlayers/tests/node.js/mockdom.js
@@ -0,0 +1,104 @@
+XMLHttpRequest = function() {
+ return {
+ 'open': function() { },
+ 'send': function() { }
+ }
+};
+
+navigator = {
+ 'appName': 'mockdom',
+ 'userAgent': 'mockdom',
+ 'appVersion': '0.1',
+ 'language': 'en',
+ 'userLanguage': 'en'
+}
+
+element = function(type) {
+ type = type || "";
+
+ return {
+ 'childNodes': [],
+ 'className': '',
+ 'tagName': type.toUpperCase(),
+ 'style': {},
+ 'setAttribute': function(attr, value) {
+ this[attr] = value;
+ },
+ 'appendChild': function(element) {
+ if (this.childNodes.length) {
+ this.childNodes[this.childNodes.length - 1].nextSibling = element;
+ } else {
+ this.firstChild = element;
+ }
+ element.parentNode = this;
+ this.childNodes.push(element);
+
+ },
+ 'removeChild': function(element) {
+ var i = this.childNodes.indexOf(element);
+ this.childNodes.splice(i, 1);
+ },
+ 'addEventListener': function() {
+ },
+ 'removeEventListener': function() {
+ },
+ 'getElementsByTagName': function(name, externalList) {
+ var uc = name.toUpperCase();
+ var list = externalList || [];
+ for(var i = 0; i < this.childNodes.length; i++) {
+ if (this.childNodes[i].tagName == uc) {
+ list.push(this.childNodes[i]);
+ }
+ this.childNodes[i].getElementsByTagName(name, list);
+ }
+ return list;
+ },
+ 'getElementById': function(id) {
+ for(var i = 0; i < this.childNodes.length; i++) {
+ if (this.childNodes[i].id == id) {
+ return this.childNodes[i];
+ } else {
+ var elem = this.childNodes[i].getElementById(id);
+ if (elem) {
+ return elem
+ }
+ }
+ }
+ }
+ }
+};
+
+document = element();
+document.createElement = function(type) {
+ return element(type);
+};
+document.createTextNode = function(text) {
+ var e = element("Text");
+ e.innerHTML = text;
+}
+
+document.appendChild(element("head"));
+document.body = element("body");
+document.appendChild(document.body);
+
+window = {
+ 'addEventListener': function() {
+ },
+ 'getSelection': function() {
+ return {
+ collapseToStart: function() {}
+ }
+ },
+ document: document,
+ navigator: navigator,
+ location: {
+ href: '#',
+ port: '',
+ hostname: 'openlayers.org',
+ host: 'openlayers.org',
+ proto: 'https'
+ }
+};
+document.location = window.location;
+
+window.Function = Function;
diff --git a/misc/openlayers/tests/node.js/node-tests.cfg b/misc/openlayers/tests/node.js/node-tests.cfg
new file mode 100644
index 0000000..bc79baa
--- /dev/null
+++ b/misc/openlayers/tests/node.js/node-tests.cfg
@@ -0,0 +1,12 @@
+# This build config is supposed to be used for the units tests with "mode=build"
+
+[first]
+mockdom.js
+[last]
+node.js
+
+[include]
+
+[exclude]
+OpenLayers.js
+Firebug/firebug.js
diff --git a/misc/openlayers/tests/node.js/node.js b/misc/openlayers/tests/node.js/node.js
new file mode 100644
index 0000000..32249d1
--- /dev/null
+++ b/misc/openlayers/tests/node.js/node.js
@@ -0,0 +1 @@
+exports.OpenLayers = OpenLayers;
diff --git a/misc/openlayers/tests/node.js/run-test.js b/misc/openlayers/tests/node.js/run-test.js
new file mode 100644
index 0000000..7b0dd8b
--- /dev/null
+++ b/misc/openlayers/tests/node.js/run-test.js
@@ -0,0 +1,26 @@
+// Requires:
+/// 0. nodejs
+// 1. jsdom installed (npm install jsdom)
+// 2. A build profile with mockdom.js included in [first], and node.js
+// inclded in [last], at ../../build/OpenLayers.js , like node-tests.js.
+// 3. Run with node run-tests.js
+//
+// Missing: integration with a solid node.js testrunner.
+var jsdom = require('jsdom');
+jsdom.env('<html><body></body></html>', function(errors, window) {
+ for (var i in window) {
+ if (i == "console") {
+ continue;
+ }
+ eval(i+"=window['"+i+"'];");
+ }
+ OpenLayers = require("../../build/OpenLayers.js")['OpenLayers'];
+ var map = new OpenLayers.Map(document.createElement("map"));
+ map.addLayer(new OpenLayers.Layer("", {isBaseLayer:true}));
+ map.setCenter(new OpenLayers.LonLat(-71,42), 10);
+ var px = map.getPixelFromLonLat(map.getLonLatFromPixel(new OpenLayers.Pixel(100,100)));
+ console.log(px);
+ var px = map.getLonLatFromPixel(map.getPixelFromLonLat(new OpenLayers.LonLat(10,10)));
+ console.log(px);
+
+});
diff --git a/misc/openlayers/tests/node.js/run.sh b/misc/openlayers/tests/node.js/run.sh
new file mode 100755
index 0000000..1434dd4
--- /dev/null
+++ b/misc/openlayers/tests/node.js/run.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+cp mockdom.js node.js ../../lib
+cp node-tests.cfg ../../build
+cd ../../build
+python build.py -c none node-tests
+cd ../tests/node.js/
+
+node run-test.js
+rm ../../lib/mockdom.js
+rm ../../lib/node.js
diff --git a/misc/openlayers/tests/owls.xml b/misc/openlayers/tests/owls.xml
new file mode 100644
index 0000000..4a001ec
--- /dev/null
+++ b/misc/openlayers/tests/owls.xml
@@ -0,0 +1,156 @@
+<?xml version='1.0' encoding="ISO-8859-1" ?>
+<wfs:FeatureCollection
+ xmlns:bsc="http://www.bsc-eoc.org/bsc"
+ xmlns:wfs="http://www.opengis.net/wfs"
+ xmlns:gml="http://www.opengis.net/gml"
+ xmlns:ogc="http://www.opengis.net/ogc"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengeospatial.net//wfs/1.0.0/WFS-basic.xsd
+ http://www.bsc-eoc.org/bsc http://www.bsc-eoc.org/cgi-bin/bsc_ows.asp?SERVICE=WFS&amp;VERSION=1.0.0&amp;REQUEST=DescribeFeatureType&amp;TYPENAME=OWLS&amp;OUTPUTFORMAT=XMLSCHEMA">
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-89.817223,45.005555 -74.755001,51.701388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <gml:featureMember><bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110 -79.771668,45.891110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.771668,45.891110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277 -83.755834,46.365277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:owlname>owl</bsc:owlname>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.755834,46.365277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277 -83.808612,46.175277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.808612,46.175277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166 -84.111112,46.309166</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-84.111112,46.309166</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110 -83.678612,46.821110</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.678612,46.821110</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888 -83.664445,46.518888</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.664445,46.518888</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277 -80.613334,46.730277</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-80.613334,46.730277</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054 -79.676946,45.428054</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-79.676946,45.428054</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944 -83.853056,46.236944</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-83.853056,46.236944</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+ <gml:featureMember>
+ <bsc:OWLS>
+ <gml:boundedBy>
+ <gml:Box srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388 -82.289167,45.896388</gml:coordinates>
+ </gml:Box>
+ </gml:boundedBy>
+ <bsc:msGeometry>
+ <gml:Point srsName="EPSG:4326">
+ <gml:coordinates>-82.289167,45.896388</gml:coordinates>
+ </gml:Point>
+ </bsc:msGeometry>
+ </bsc:OWLS>
+ </gml:featureMember>
+</wfs:FeatureCollection>
+
diff --git a/misc/openlayers/tests/run-tests.html b/misc/openlayers/tests/run-tests.html
new file mode 100644
index 0000000..d1517e4
--- /dev/null
+++ b/misc/openlayers/tests/run-tests.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Run the testsuite</title>
+ <noscript>
+ Javascript is disabled in your browser. This page cannot be
+ displayed correctly without Javascript. Sorry.
+ <br/>
+ If you want to view this page, please change your browser settings
+ so that Javascript is enabled.
+ </noscript>
+ <!--
+
+ Test.AnotherWay version 0.5
+
+ Copyright (c) 2005 Artem Khodush, http://straytree.org
+
+ Permission is hereby granted, free of charge, to any person obtaining
+ a copy of this software and associated documentation files (the
+ "Software"), to deal in the Software without restriction, including
+ without limitation the rights to use, copy, modify, merge, publish,
+ distribute, sublicense, and/or sell copies of the Software, and to
+ permit persons to whom the Software is furnished to do so, subject to
+ the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ -->
+ <link rel="stylesheet" href="Test.AnotherWay.css" />
+ <script type="text/javascript" src="Test.AnotherWay.js"></script>
+ <script type="text/javascript" src="Test.AnotherWay.baseadditions.js"></script>
+ <script type="text/javascript" src="Test.AnotherWay.xml_eq.js"></script>
+ <script type="text/javascript" src="Test.AnotherWay.geom_eq.js"></script>
+ </head>
+ <body>
+ <div id="col1">
+ <div id="col1_header">
+ Test pages:
+ <input id="quickfilter" placeholder="quick filter">
+ </div>
+ <div id="scroller">
+ <table id="testtable">
+ </table>
+ </div>
+ <div id="run_buttons">
+ <input type="button" value=" clear " id="clear_btn" /><input type="button" value=" run all " id="run_all" /><input type="button" value=" run selected " id="run_selected" /><input type="button" value=" unselect all " id="unselect_all" />
+ </div>
+ <div id="running-time">
+ </div>
+ <input type="checkbox" id="dont_close_test_windows" /> do not close windows opened by tests
+ <div id="error">
+ </div>
+ <div id="record_div">
+ <p id="record_not_supported" style="display:none">
+ </p>
+ <p>
+ Record mouse input for the page:
+ </p>
+ <p>
+ <input type="radio" name="record_choose" value="select" checked="checked" />
+ <select id="record_select">
+ <option selected="selected">-- select a page: --</option>
+ </select>
+ </p>
+ <p>
+ <input type="radio" name="record_choose" value="input" /> or enter page url: <input type="text" id="record_input" />
+ </p>
+ <p>
+ <input type="button" value=" record " id="record_start" />
+ </p>
+ </div>
+ </div>
+ <div id="col2">
+ <div id="right_header">
+ <span id="results_count">Results: <span id="total"></span></span>
+ <span id="results_tab" class="active_tab" style="visibility:hidden">Results</span>
+ <span id="debug_tab" class="inactive_tab" style="visibility:hidden">Debug</span>
+ </div>
+ <div id="right_frame">
+ <div id="results">
+ </div>
+ <div id="debug">
+ </div>
+ </div>
+ </div>
+ <span>
+ <iframe id="test_iframe_el" style="display:none" name="test_iframe" onload="Test.AnotherWay._test_page_onload();">
+ </iframe>
+ </span>
+ <span style="display:none">
+ <iframe name="list_iframe" onload="Test.AnotherWay._list_iframe_onload();">
+ </iframe>
+ <!-- record_control div is to be imported into other documents, so all its styles are inline -->-
+ <div id="record_control" style="position:absolute;bottom:0;left:0;margin:0;padding:0.5em;width:22em;height:22em;border:1px solid;background:#ffd;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+ <p style="margin:0 0 0 0; padding:0">
+ &nbsp;<span style="display:none;font-weight:bold;color:#408" id="record_indicator">recording. <span style="font-weight:normal">time: <span id="record_time"></span></span><span id="record_pause_indicator">paused</span></span>
+ </p>
+ <div id="record_cursor_over" style="margin:0;padding:2px;width:14em;height:1.1em;overflow:hidden;float:right;border:1px solid #777;background:#fff;font: normal normal 8pt sans-serif;position:relative;top:3px;color:#000;text-align:left;">
+ &nbsp;
+ </div>
+ <p style="margin:2px 0 0 0; padding:0">
+ cursor is over
+ </p>
+ <p style="margin:8px 0 0 0; padding:0;">
+ keyboard control: press<span id="record_ctrl_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">ctrl</span>
+ -<span id="record_shift_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">shift</span>
+ -
+ </p>
+ <p style="margin:4px 0 0 0; padding:0">
+ <span id="record_s" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">s</span>
+ <span id="record_on">to <b>start</b> recording</span>
+ <span id="record_off" style="display:none">to <b>stop</b> recording</span>
+ </p>
+ <p style="margin:4px 0 0 0; padding:0">
+ <span id="record_h" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">h</span>
+ <span>to <b>hide/show</b> this window</span>
+ </p>
+ <p style="margin:4px 0 0 0; padding:0">
+ <span id="record_m" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">m</span>
+ <span id="record_include_mousemove">to <b>record</b> mousemove</span>
+ <span id="record_omit_mousemove" style="display:none">to <b>omit</b> mousemove</span>
+ </p>
+ <p style="margin:4px 0 0 0; padding:0">
+ <span id="record_p" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">p</span>
+ <span id="record_pause_on">to <b>pause</b> recording</span>
+ <span id="record_pause_off" style="display:none">to <b>continue</b> recording</span>
+ </p>
+ <p style="margin:4px 0 0 0; padding:0">
+ <span id="record_c" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">c</span>
+ <span>to add checkpoint</span>
+ </p>
+ <p style="margin:6px 0 0 0; padding:0">
+ checkpoints:
+ </p>
+ <div id="record_checkpoints" style="position:relative;width:100%;height:6em;overflow:auto;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+ </div>
+ </div>
+ </span>
+ <script>
+ if (/noscroll/.test(location.href)) {
+ document.getElementById('scroller').style.height = 'auto';
+ document.getElementById('right_frame').style.height = 'auto';
+ }
+ </script>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/selenium/remotecontrol/config.cfg b/misc/openlayers/tests/selenium/remotecontrol/config.cfg
new file mode 100644
index 0000000..764bd3d
--- /dev/null
+++ b/misc/openlayers/tests/selenium/remotecontrol/config.cfg
@@ -0,0 +1,48 @@
+[config]
+server=http://openlayers.org/
+url=/dev/tests/run-tests.html?run=all&windows=none
+
+[local_ff]
+host=localhost
+browserCmd=firefox
+comment=Firefox on localhost
+
+[local_safari]
+host=localhost
+browserCmd=safari
+comment=Safari on localhost
+
+#[ie6-winxp]
+#host=208.80.142.184
+#browserCmd=iexploreproxy C:\Program Files\MultipleIEs\IE6\iexplore.exe
+#comment=IE6 on WinXP
+
+[ie7-winxp]
+host=208.80.142.184
+browserCmd=iexploreproxy
+comment=IE7 on WinXP
+
+# Running on alta: debian etch
+[opera-winxp]
+host=208.80.142.184
+browserCmd=opera C:\Program Files\Opera 9\Opera.exe
+comment=Opera on WinXP
+
+# Running on alta: debian etch
+[opera]
+host=208.80.142.140
+browserCmd=opera
+comment=Opera on Debian Etch
+
+# Running on alta: Debian Etch
+[firefox2]
+host=208.80.142.140
+browserCmd=firefox /usr/lib/iceweasel/firefox-bin
+comment=Iceweasel 2 on Debian Etch
+
+# Running on alta: Debian Etch
+[firefox3]
+host=208.80.142.105
+browserCmd=firefox /usr/lib/firefox-3.0.1/firefox-bin
+comment=FF3 on Ubuntu
+
diff --git a/misc/openlayers/tests/selenium/remotecontrol/selenium.py b/misc/openlayers/tests/selenium/remotecontrol/selenium.py
new file mode 100644
index 0000000..e5505ed
--- /dev/null
+++ b/misc/openlayers/tests/selenium/remotecontrol/selenium.py
@@ -0,0 +1,1846 @@
+
+"""
+Copyright 2006 ThoughtWorks, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+"""
+__docformat__ = "restructuredtext en"
+
+# This file has been automatically generated via XSL
+
+import httplib
+import urllib
+import re
+
+class selenium:
+ """
+ Defines an object that runs Selenium commands.
+
+ Element Locators
+ ~~~~~~~~~~~~~~~~
+
+ Element Locators tell Selenium which HTML element a command refers to.
+ The format of a locator is:
+
+ \ *locatorType*\ **=**\ \ *argument*
+
+
+ We support the following strategies for locating elements:
+
+
+ * \ **identifier**\ =\ *id*:
+ Select the element with the specified @id attribute. If no match is
+ found, select the first element whose @name attribute is \ *id*.
+ (This is normally the default; see below.)
+ * \ **id**\ =\ *id*:
+ Select the element with the specified @id attribute.
+ * \ **name**\ =\ *name*:
+ Select the first element with the specified @name attribute.
+
+ * username
+ * name=username
+
+
+ The name may optionally be followed by one or more \ *element-filters*, separated from the name by whitespace. If the \ *filterType* is not specified, \ **value**\ is assumed.
+
+ * name=flavour value=chocolate
+
+
+ * \ **dom**\ =\ *javascriptExpression*:
+
+ Find an element by evaluating the specified string. This allows you to traverse the HTML Document Object
+ Model using JavaScript. Note that you must not return a value in this string; simply make it the last expression in the block.
+
+ * dom=document.forms['myForm'].myDropdown
+ * dom=document.images[56]
+ * dom=function foo() { return document.links[1]; }; foo();
+
+
+ * \ **xpath**\ =\ *xpathExpression*:
+ Locate an element using an XPath expression.
+
+ * xpath=//img[@alt='The image alt text']
+ * xpath=//table[@id='table1']//tr[4]/td[2]
+ * xpath=//a[contains(@href,'#id1')]
+ * xpath=//a[contains(@href,'#id1')]/@class
+ * xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td
+ * xpath=//input[@name='name2' and @value='yes']
+ * xpath=//\*[text()="right"]
+
+
+ * \ **link**\ =\ *textPattern*:
+ Select the link (anchor) element which contains text matching the
+ specified \ *pattern*.
+
+ * link=The link text
+
+
+ * \ **css**\ =\ *cssSelectorSyntax*:
+ Select the element using css selectors. Please refer to CSS2 selectors, CSS3 selectors for more information. You can also check the TestCssLocators test in the selenium test suite for an example of usage, which is included in the downloaded selenium core package.
+
+ * css=a[href="#id3"]
+ * css=span#firstChild + span
+
+
+ Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after).
+
+
+
+
+ Without an explicit locator prefix, Selenium uses the following default
+ strategies:
+
+
+ * \ **dom**\ , for locators starting with "document."
+ * \ **xpath**\ , for locators starting with "//"
+ * \ **identifier**\ , otherwise
+
+ Element Filters
+ ~~~~~~~~~~~~~~~
+
+ Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator.
+
+ Filters look much like locators, ie.
+
+ \ *filterType*\ **=**\ \ *argument*
+
+ Supported element-filters are:
+
+ \ **value=**\ \ *valuePattern*
+
+
+ Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons.
+
+ \ **index=**\ \ *index*
+
+
+ Selects a single element based on its position in the list (offset from zero).
+
+ String-match Patterns
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ Various Pattern syntaxes are available for matching string values:
+
+
+ * \ **glob:**\ \ *pattern*:
+ Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a
+ kind of limited regular-expression syntax typically used in command-line
+ shells. In a glob pattern, "\*" represents any sequence of characters, and "?"
+ represents any single character. Glob patterns match against the entire
+ string.
+ * \ **regexp:**\ \ *regexp*:
+ Match a string using a regular-expression. The full power of JavaScript
+ regular-expressions is available.
+ * \ **regexpi:**\ \ *regexpi*:
+ Match a string using a case-insensitive regular-expression.
+ * \ **exact:**\ \ *string*:
+
+ Match a string exactly, verbatim, without any of that fancy wildcard
+ stuff.
+
+
+
+ If no pattern prefix is specified, Selenium assumes that it's a "glob"
+ pattern.
+
+
+
+ For commands that return multiple values (such as verifySelectOptions),
+ the string being matched is a comma-separated list of the return values,
+ where both commas and backslashes in the values are backslash-escaped.
+ When providing a pattern, the optional matching syntax (i.e. glob,
+ regexp, etc.) is specified once, as usual, at the beginning of the
+ pattern.
+
+
+ """
+
+### This part is hard-coded in the XSL
+ def __init__(self, host, port, browserStartCommand, browserURL):
+ self.host = host
+ self.port = port
+ self.browserStartCommand = browserStartCommand
+ self.browserURL = browserURL
+ self.sessionId = None
+
+ def start(self):
+ result = self.get_string("getNewBrowserSession", [self.browserStartCommand, self.browserURL])
+ try:
+ self.sessionId = result
+ except ValueError:
+ raise Exception, result
+
+ def stop(self):
+ self.do_command("testComplete", [])
+ self.sessionId = None
+
+ def do_command(self, verb, args):
+ conn = httplib.HTTPConnection(self.host, self.port)
+ commandString = u'/selenium-server/driver/?cmd=' + urllib.quote_plus(unicode(verb).encode('utf-8'))
+ for i in range(len(args)):
+ commandString = commandString + '&' + unicode(i+1) + '=' + urllib.quote_plus(unicode(args[i]).encode('utf-8'))
+ if (None != self.sessionId):
+ commandString = commandString + "&sessionId=" + unicode(self.sessionId)
+ conn.request("GET", commandString)
+
+ response = conn.getresponse()
+ #print response.status, response.reason
+ data = unicode(response.read(), "UTF-8")
+ result = response.reason
+ #print "Selenium Result: " + repr(data) + "\n\n"
+ if (not data.startswith('OK')):
+ raise Exception, data
+ return data
+
+ def get_string(self, verb, args):
+ result = self.do_command(verb, args)
+ return result[3:]
+
+ def get_string_array(self, verb, args):
+ csv = self.get_string(verb, args)
+ token = ""
+ tokens = []
+ escape = False
+ for i in range(len(csv)):
+ letter = csv[i]
+ if (escape):
+ token = token + letter
+ escape = False
+ continue
+ if (letter == '\\'):
+ escape = True
+ elif (letter == ','):
+ tokens.append(token)
+ token = ""
+ else:
+ token = token + letter
+ tokens.append(token)
+ return tokens
+
+ def get_number(self, verb, args):
+ # Is there something I need to do here?
+ return self.get_string(verb, args)
+
+ def get_number_array(self, verb, args):
+ # Is there something I need to do here?
+ return self.get_string_array(verb, args)
+
+ def get_boolean(self, verb, args):
+ boolstr = self.get_string(verb, args)
+ if ("true" == boolstr):
+ return True
+ if ("false" == boolstr):
+ return False
+ raise ValueError, "result is neither 'true' nor 'false': " + boolstr
+
+ def get_boolean_array(self, verb, args):
+ boolarr = self.get_string_array(verb, args)
+ for i in range(len(boolarr)):
+ if ("true" == boolstr):
+ boolarr[i] = True
+ continue
+ if ("false" == boolstr):
+ boolarr[i] = False
+ continue
+ raise ValueError, "result is neither 'true' nor 'false': " + boolarr[i]
+ return boolarr
+
+
+
+### From here on, everything's auto-generated from XML
+
+
+ def click(self,locator):
+ """
+ Clicks on a link, button, checkbox or radio button. If the click action
+ causes a new page to load (like a link usually does), call
+ waitForPageToLoad.
+
+ 'locator' is an element locator
+ """
+ self.do_command("click", [locator,])
+
+
+ def double_click(self,locator):
+ """
+ Double clicks on a link, button, checkbox or radio button. If the double click action
+ causes a new page to load (like a link usually does), call
+ waitForPageToLoad.
+
+ 'locator' is an element locator
+ """
+ self.do_command("doubleClick", [locator,])
+
+
+ def context_menu(self,locator):
+ """
+ Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element).
+
+ 'locator' is an element locator
+ """
+ self.do_command("contextMenu", [locator,])
+
+
+ def click_at(self,locator,coordString):
+ """
+ Clicks on a link, button, checkbox or radio button. If the click action
+ causes a new page to load (like a link usually does), call
+ waitForPageToLoad.
+
+ 'locator' is an element locator
+ 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.
+ """
+ self.do_command("clickAt", [locator,coordString,])
+
+
+ def double_click_at(self,locator,coordString):
+ """
+ Doubleclicks on a link, button, checkbox or radio button. If the action
+ causes a new page to load (like a link usually does), call
+ waitForPageToLoad.
+
+ 'locator' is an element locator
+ 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.
+ """
+ self.do_command("doubleClickAt", [locator,coordString,])
+
+
+ def context_menu_at(self,locator,coordString):
+ """
+ Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element).
+
+ 'locator' is an element locator
+ 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.
+ """
+ self.do_command("contextMenuAt", [locator,coordString,])
+
+
+ def fire_event(self,locator,eventName):
+ """
+ Explicitly simulate an event, to trigger the corresponding "on\ *event*"
+ handler.
+
+ 'locator' is an element locator
+ 'eventName' is the event name, e.g. "focus" or "blur"
+ """
+ self.do_command("fireEvent", [locator,eventName,])
+
+
+ def focus(self,locator):
+ """
+ Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field.
+
+ 'locator' is an element locator
+ """
+ self.do_command("focus", [locator,])
+
+
+ def key_press(self,locator,keySequence):
+ """
+ Simulates a user pressing and releasing a key.
+
+ 'locator' is an element locator
+ 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119".
+ """
+ self.do_command("keyPress", [locator,keySequence,])
+
+
+ def shift_key_down(self):
+ """
+ Press the shift key and hold it down until doShiftUp() is called or a new page is loaded.
+
+ """
+ self.do_command("shiftKeyDown", [])
+
+
+ def shift_key_up(self):
+ """
+ Release the shift key.
+
+ """
+ self.do_command("shiftKeyUp", [])
+
+
+ def meta_key_down(self):
+ """
+ Press the meta key and hold it down until doMetaUp() is called or a new page is loaded.
+
+ """
+ self.do_command("metaKeyDown", [])
+
+
+ def meta_key_up(self):
+ """
+ Release the meta key.
+
+ """
+ self.do_command("metaKeyUp", [])
+
+
+ def alt_key_down(self):
+ """
+ Press the alt key and hold it down until doAltUp() is called or a new page is loaded.
+
+ """
+ self.do_command("altKeyDown", [])
+
+
+ def alt_key_up(self):
+ """
+ Release the alt key.
+
+ """
+ self.do_command("altKeyUp", [])
+
+
+ def control_key_down(self):
+ """
+ Press the control key and hold it down until doControlUp() is called or a new page is loaded.
+
+ """
+ self.do_command("controlKeyDown", [])
+
+
+ def control_key_up(self):
+ """
+ Release the control key.
+
+ """
+ self.do_command("controlKeyUp", [])
+
+
+ def key_down(self,locator,keySequence):
+ """
+ Simulates a user pressing a key (without releasing it yet).
+
+ 'locator' is an element locator
+ 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119".
+ """
+ self.do_command("keyDown", [locator,keySequence,])
+
+
+ def key_up(self,locator,keySequence):
+ """
+ Simulates a user releasing a key.
+
+ 'locator' is an element locator
+ 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119".
+ """
+ self.do_command("keyUp", [locator,keySequence,])
+
+
+ def mouse_over(self,locator):
+ """
+ Simulates a user hovering a mouse over the specified element.
+
+ 'locator' is an element locator
+ """
+ self.do_command("mouseOver", [locator,])
+
+
+ def mouse_out(self,locator):
+ """
+ Simulates a user moving the mouse pointer away from the specified element.
+
+ 'locator' is an element locator
+ """
+ self.do_command("mouseOut", [locator,])
+
+
+ def mouse_down(self,locator):
+ """
+ Simulates a user pressing the mouse button (without releasing it yet) on
+ the specified element.
+
+ 'locator' is an element locator
+ """
+ self.do_command("mouseDown", [locator,])
+
+
+ def mouse_down_at(self,locator,coordString):
+ """
+ Simulates a user pressing the mouse button (without releasing it yet) at
+ the specified location.
+
+ 'locator' is an element locator
+ 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.
+ """
+ self.do_command("mouseDownAt", [locator,coordString,])
+
+
+ def mouse_up(self,locator):
+ """
+ Simulates the event that occurs when the user releases the mouse button (i.e., stops
+ holding the button down) on the specified element.
+
+ 'locator' is an element locator
+ """
+ self.do_command("mouseUp", [locator,])
+
+
+ def mouse_up_at(self,locator,coordString):
+ """
+ Simulates the event that occurs when the user releases the mouse button (i.e., stops
+ holding the button down) at the specified location.
+
+ 'locator' is an element locator
+ 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.
+ """
+ self.do_command("mouseUpAt", [locator,coordString,])
+
+
+ def mouse_move(self,locator):
+ """
+ Simulates a user pressing the mouse button (without releasing it yet) on
+ the specified element.
+
+ 'locator' is an element locator
+ """
+ self.do_command("mouseMove", [locator,])
+
+
+ def mouse_move_at(self,locator,coordString):
+ """
+ Simulates a user pressing the mouse button (without releasing it yet) on
+ the specified element.
+
+ 'locator' is an element locator
+ 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator.
+ """
+ self.do_command("mouseMoveAt", [locator,coordString,])
+
+
+ def type(self,locator,value):
+ """
+ Sets the value of an input field, as though you typed it in.
+
+
+ Can also be used to set the value of combo boxes, check boxes, etc. In these cases,
+ value should be the value of the option selected, not the visible text.
+
+
+ 'locator' is an element locator
+ 'value' is the value to type
+ """
+ self.do_command("type", [locator,value,])
+
+
+ def type_keys(self,locator,value):
+ """
+ Simulates keystroke events on the specified element, as though you typed the value key-by-key.
+
+
+ This is a convenience method for calling keyDown, keyUp, keyPress for every character in the specified string;
+ this is useful for dynamic UI widgets (like auto-completing combo boxes) that require explicit key events.
+
+ Unlike the simple "type" command, which forces the specified value into the page directly, this command
+ may or may not have any visible effect, even in cases where typing keys would normally have a visible effect.
+ For example, if you use "typeKeys" on a form element, you may or may not see the results of what you typed in
+ the field.
+
+ In some cases, you may need to use the simple "type" command to set the value of the field and then the "typeKeys" command to
+ send the keystroke events corresponding to what you just typed.
+
+
+ 'locator' is an element locator
+ 'value' is the value to type
+ """
+ self.do_command("typeKeys", [locator,value,])
+
+
+ def set_speed(self,value):
+ """
+ Set execution speed (i.e., set the millisecond length of a delay which will follow each selenium operation). By default, there is no such delay, i.e.,
+ the delay is 0 milliseconds.
+
+ 'value' is the number of milliseconds to pause after operation
+ """
+ self.do_command("setSpeed", [value,])
+
+
+ def get_speed(self):
+ """
+ Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e.,
+ the delay is 0 milliseconds.
+
+ See also setSpeed.
+
+ """
+ return self.get_string("getSpeed", [])
+
+
+ def check(self,locator):
+ """
+ Check a toggle-button (checkbox/radio)
+
+ 'locator' is an element locator
+ """
+ self.do_command("check", [locator,])
+
+
+ def uncheck(self,locator):
+ """
+ Uncheck a toggle-button (checkbox/radio)
+
+ 'locator' is an element locator
+ """
+ self.do_command("uncheck", [locator,])
+
+
+ def select(self,selectLocator,optionLocator):
+ """
+ Select an option from a drop-down using an option locator.
+
+
+
+ Option locators provide different ways of specifying options of an HTML
+ Select element (e.g. for selecting a specific option, or for asserting
+ that the selected option satisfies a specification). There are several
+ forms of Select Option Locator.
+
+
+ * \ **label**\ =\ *labelPattern*:
+ matches options based on their labels, i.e. the visible text. (This
+ is the default.)
+
+ * label=regexp:^[Oo]ther
+
+
+ * \ **value**\ =\ *valuePattern*:
+ matches options based on their values.
+
+ * value=other
+
+
+ * \ **id**\ =\ *id*:
+
+ matches options based on their ids.
+
+ * id=option1
+
+
+ * \ **index**\ =\ *index*:
+ matches an option based on its index (offset from zero).
+
+ * index=2
+
+
+
+
+
+ If no option locator prefix is provided, the default behaviour is to match on \ **label**\ .
+
+
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ 'optionLocator' is an option locator (a label by default)
+ """
+ self.do_command("select", [selectLocator,optionLocator,])
+
+
+ def add_selection(self,locator,optionLocator):
+ """
+ Add a selection to the set of selected options in a multi-select element using an option locator.
+
+ @see #doSelect for details of option locators
+
+ 'locator' is an element locator identifying a multi-select box
+ 'optionLocator' is an option locator (a label by default)
+ """
+ self.do_command("addSelection", [locator,optionLocator,])
+
+
+ def remove_selection(self,locator,optionLocator):
+ """
+ Remove a selection from the set of selected options in a multi-select element using an option locator.
+
+ @see #doSelect for details of option locators
+
+ 'locator' is an element locator identifying a multi-select box
+ 'optionLocator' is an option locator (a label by default)
+ """
+ self.do_command("removeSelection", [locator,optionLocator,])
+
+
+ def remove_all_selections(self,locator):
+ """
+ Unselects all of the selected options in a multi-select element.
+
+ 'locator' is an element locator identifying a multi-select box
+ """
+ self.do_command("removeAllSelections", [locator,])
+
+
+ def submit(self,formLocator):
+ """
+ Submit the specified form. This is particularly useful for forms without
+ submit buttons, e.g. single-input "Search" forms.
+
+ 'formLocator' is an element locator for the form you want to submit
+ """
+ self.do_command("submit", [formLocator,])
+
+
+ def open(self,url):
+ """
+ Opens an URL in the test frame. This accepts both relative and absolute
+ URLs.
+
+ The "open" command waits for the page to load before proceeding,
+ ie. the "AndWait" suffix is implicit.
+
+ \ *Note*: The URL must be on the same domain as the runner HTML
+ due to security restrictions in the browser (Same Origin Policy). If you
+ need to open an URL on another domain, use the Selenium Server to start a
+ new browser session on that domain.
+
+ 'url' is the URL to open; may be relative or absolute
+ """
+ self.do_command("open", [url,])
+
+
+ def open_window(self,url,windowID):
+ """
+ Opens a popup window (if a window with that ID isn't already open).
+ After opening the window, you'll need to select it using the selectWindow
+ command.
+
+
+ This command can also be a useful workaround for bug SEL-339. In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example).
+ In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using
+ an empty (blank) url, like this: openWindow("", "myFunnyWindow").
+
+
+ 'url' is the URL to open, which can be blank
+ 'windowID' is the JavaScript window ID of the window to select
+ """
+ self.do_command("openWindow", [url,windowID,])
+
+
+ def select_window(self,windowID):
+ """
+ Selects a popup window using a window locator; once a popup window has been selected, all
+ commands go to that window. To select the main window again, use null
+ as the target.
+
+
+
+
+ Window locators provide different ways of specifying the window object:
+ by title, by internal JavaScript "name," or by JavaScript variable.
+
+
+ * \ **title**\ =\ *My Special Window*:
+ Finds the window using the text that appears in the title bar. Be careful;
+ two windows can share the same title. If that happens, this locator will
+ just pick one.
+
+ * \ **name**\ =\ *myWindow*:
+ Finds the window using its internal JavaScript "name" property. This is the second
+ parameter "windowName" passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
+ (which Selenium intercepts).
+
+ * \ **var**\ =\ *variableName*:
+ Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current
+ application window, e.g. "window.foo = window.open(url);". In those cases, you can open the window using
+ "var=foo".
+
+
+
+
+ If no window locator prefix is provided, we'll try to guess what you mean like this:
+
+ 1.) if windowID is null, (or the string "null") then it is assumed the user is referring to the original window instantiated by the browser).
+
+ 2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed
+ that this variable contains the return value from a call to the JavaScript window.open() method.
+
+ 3.) Otherwise, selenium looks in a hash it maintains that maps string names to window "names".
+
+ 4.) If \ *that* fails, we'll try looping over all of the known windows to try to find the appropriate "title".
+ Since "title" is not necessarily unique, this may have unexpected behavior.
+
+ If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages
+ which identify the names of windows created via window.open (and therefore intercepted by Selenium). You will see messages
+ like the following for each window as it is opened:
+
+ ``debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"``
+
+ In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example).
+ (This is bug SEL-339.) In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using
+ an empty (blank) url, like this: openWindow("", "myFunnyWindow").
+
+
+ 'windowID' is the JavaScript window ID of the window to select
+ """
+ self.do_command("selectWindow", [windowID,])
+
+
+ def select_frame(self,locator):
+ """
+ Selects a frame within the current window. (You may invoke this command
+ multiple times to select nested frames.) To select the parent frame, use
+ "relative=parent" as a locator; to select the top frame, use "relative=top".
+ You can also select a frame by its 0-based index number; select the first frame with
+ "index=0", or the third frame with "index=2".
+
+
+ You may also use a DOM expression to identify the frame you want directly,
+ like this: ``dom=frames["main"].frames["subframe"]``
+
+
+ 'locator' is an element locator identifying a frame or iframe
+ """
+ self.do_command("selectFrame", [locator,])
+
+
+ def get_whether_this_frame_match_frame_expression(self,currentFrameString,target):
+ """
+ Determine whether current/locator identify the frame containing this running code.
+
+
+ This is useful in proxy injection mode, where this code runs in every
+ browser frame and window, and sometimes the selenium server needs to identify
+ the "current" frame. In this case, when the test calls selectFrame, this
+ routine is called for each frame to figure out which one has been selected.
+ The selected frame will return true, while all others will return false.
+
+
+ 'currentFrameString' is starting frame
+ 'target' is new frame (which might be relative to the current one)
+ """
+ return self.get_boolean("getWhetherThisFrameMatchFrameExpression", [currentFrameString,target,])
+
+
+ def get_whether_this_window_match_window_expression(self,currentWindowString,target):
+ """
+ Determine whether currentWindowString plus target identify the window containing this running code.
+
+
+ This is useful in proxy injection mode, where this code runs in every
+ browser frame and window, and sometimes the selenium server needs to identify
+ the "current" window. In this case, when the test calls selectWindow, this
+ routine is called for each window to figure out which one has been selected.
+ The selected window will return true, while all others will return false.
+
+
+ 'currentWindowString' is starting window
+ 'target' is new window (which might be relative to the current one, e.g., "_parent")
+ """
+ return self.get_boolean("getWhetherThisWindowMatchWindowExpression", [currentWindowString,target,])
+
+
+ def wait_for_pop_up(self,windowID,timeout):
+ """
+ Waits for a popup window to appear and load up.
+
+ 'windowID' is the JavaScript window "name" of the window that will appear (not the text of the title bar)
+ 'timeout' is a timeout in milliseconds, after which the action will return with an error
+ """
+ self.do_command("waitForPopUp", [windowID,timeout,])
+
+
+ def choose_cancel_on_next_confirmation(self):
+ """
+ By default, Selenium's overridden window.confirm() function will
+ return true, as if the user had manually clicked OK; after running
+ this command, the next call to confirm() will return false, as if
+ the user had clicked Cancel. Selenium will then resume using the
+ default behavior for future confirmations, automatically returning
+ true (OK) unless/until you explicitly call this command for each
+ confirmation.
+
+ """
+ self.do_command("chooseCancelOnNextConfirmation", [])
+
+
+ def choose_ok_on_next_confirmation(self):
+ """
+ Undo the effect of calling chooseCancelOnNextConfirmation. Note
+ that Selenium's overridden window.confirm() function will normally automatically
+ return true, as if the user had manually clicked OK, so you shouldn't
+ need to use this command unless for some reason you need to change
+ your mind prior to the next confirmation. After any confirmation, Selenium will resume using the
+ default behavior for future confirmations, automatically returning
+ true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each
+ confirmation.
+
+ """
+ self.do_command("chooseOkOnNextConfirmation", [])
+
+
+ def answer_on_next_prompt(self,answer):
+ """
+ Instructs Selenium to return the specified answer string in response to
+ the next JavaScript prompt [window.prompt()].
+
+ 'answer' is the answer to give in response to the prompt pop-up
+ """
+ self.do_command("answerOnNextPrompt", [answer,])
+
+
+ def go_back(self):
+ """
+ Simulates the user clicking the "back" button on their browser.
+
+ """
+ self.do_command("goBack", [])
+
+
+ def refresh(self):
+ """
+ Simulates the user clicking the "Refresh" button on their browser.
+
+ """
+ self.do_command("refresh", [])
+
+
+ def close(self):
+ """
+ Simulates the user clicking the "close" button in the titlebar of a popup
+ window or tab.
+
+ """
+ self.do_command("close", [])
+
+
+ def is_alert_present(self):
+ """
+ Has an alert occurred?
+
+
+
+ This function never throws an exception
+
+
+
+ """
+ return self.get_boolean("isAlertPresent", [])
+
+
+ def is_prompt_present(self):
+ """
+ Has a prompt occurred?
+
+
+
+ This function never throws an exception
+
+
+
+ """
+ return self.get_boolean("isPromptPresent", [])
+
+
+ def is_confirmation_present(self):
+ """
+ Has confirm() been called?
+
+
+
+ This function never throws an exception
+
+
+
+ """
+ return self.get_boolean("isConfirmationPresent", [])
+
+
+ def get_alert(self):
+ """
+ Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
+
+
+ Getting an alert has the same effect as manually clicking OK. If an
+ alert is generated but you do not get/verify it, the next Selenium action
+ will fail.
+
+ NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
+ dialog.
+
+ NOTE: Selenium does NOT support JavaScript alerts that are generated in a
+ page's onload() event handler. In this case a visible dialog WILL be
+ generated and Selenium will hang until someone manually clicks OK.
+
+
+ """
+ return self.get_string("getAlert", [])
+
+
+ def get_confirmation(self):
+ """
+ Retrieves the message of a JavaScript confirmation dialog generated during
+ the previous action.
+
+
+
+ By default, the confirm function will return true, having the same effect
+ as manually clicking OK. This can be changed by prior execution of the
+ chooseCancelOnNextConfirmation command. If an confirmation is generated
+ but you do not get/verify it, the next Selenium action will fail.
+
+
+
+ NOTE: under Selenium, JavaScript confirmations will NOT pop up a visible
+ dialog.
+
+
+
+ NOTE: Selenium does NOT support JavaScript confirmations that are
+ generated in a page's onload() event handler. In this case a visible
+ dialog WILL be generated and Selenium will hang until you manually click
+ OK.
+
+
+
+ """
+ return self.get_string("getConfirmation", [])
+
+
+ def get_prompt(self):
+ """
+ Retrieves the message of a JavaScript question prompt dialog generated during
+ the previous action.
+
+
+ Successful handling of the prompt requires prior execution of the
+ answerOnNextPrompt command. If a prompt is generated but you
+ do not get/verify it, the next Selenium action will fail.
+
+ NOTE: under Selenium, JavaScript prompts will NOT pop up a visible
+ dialog.
+
+ NOTE: Selenium does NOT support JavaScript prompts that are generated in a
+ page's onload() event handler. In this case a visible dialog WILL be
+ generated and Selenium will hang until someone manually clicks OK.
+
+
+ """
+ return self.get_string("getPrompt", [])
+
+
+ def get_location(self):
+ """
+ Gets the absolute URL of the current page.
+
+ """
+ return self.get_string("getLocation", [])
+
+
+ def get_title(self):
+ """
+ Gets the title of the current page.
+
+ """
+ return self.get_string("getTitle", [])
+
+
+ def get_body_text(self):
+ """
+ Gets the entire text of the page.
+
+ """
+ return self.get_string("getBodyText", [])
+
+
+ def get_value(self,locator):
+ """
+ Gets the (whitespace-trimmed) value of an input field (or anything else with a value parameter).
+ For checkbox/radio elements, the value will be "on" or "off" depending on
+ whether the element is checked or not.
+
+ 'locator' is an element locator
+ """
+ return self.get_string("getValue", [locator,])
+
+
+ def get_text(self,locator):
+ """
+ Gets the text of an element. This works for any element that contains
+ text. This command uses either the textContent (Mozilla-like browsers) or
+ the innerText (IE-like browsers) of the element, which is the rendered
+ text shown to the user.
+
+ 'locator' is an element locator
+ """
+ return self.get_string("getText", [locator,])
+
+
+ def highlight(self,locator):
+ """
+ Briefly changes the backgroundColor of the specified element yellow. Useful for debugging.
+
+ 'locator' is an element locator
+ """
+ self.do_command("highlight", [locator,])
+
+
+ def get_eval(self,script):
+ """
+ Gets the result of evaluating the specified JavaScript snippet. The snippet may
+ have multiple lines, but only the result of the last line will be returned.
+
+
+ Note that, by default, the snippet will run in the context of the "selenium"
+ object itself, so ``this`` will refer to the Selenium object. Use ``window`` to
+ refer to the window of your application, e.g. ``window.document.getElementById('foo')``
+
+ If you need to use
+ a locator to refer to a single element in your application page, you can
+ use ``this.browserbot.findElement("id=foo")`` where "id=foo" is your locator.
+
+
+ 'script' is the JavaScript snippet to run
+ """
+ return self.get_string("getEval", [script,])
+
+
+ def is_checked(self,locator):
+ """
+ Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button.
+
+ 'locator' is an element locator pointing to a checkbox or radio button
+ """
+ return self.get_boolean("isChecked", [locator,])
+
+
+ def get_table(self,tableCellAddress):
+ """
+ Gets the text from a cell of a table. The cellAddress syntax
+ tableLocator.row.column, where row and column start at 0.
+
+ 'tableCellAddress' is a cell address, e.g. "foo.1.4"
+ """
+ return self.get_string("getTable", [tableCellAddress,])
+
+
+ def get_selected_labels(self,selectLocator):
+ """
+ Gets all option labels (visible text) for selected options in the specified select or multi-select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string_array("getSelectedLabels", [selectLocator,])
+
+
+ def get_selected_label(self,selectLocator):
+ """
+ Gets option label (visible text) for selected option in the specified select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string("getSelectedLabel", [selectLocator,])
+
+
+ def get_selected_values(self,selectLocator):
+ """
+ Gets all option values (value attributes) for selected options in the specified select or multi-select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string_array("getSelectedValues", [selectLocator,])
+
+
+ def get_selected_value(self,selectLocator):
+ """
+ Gets option value (value attribute) for selected option in the specified select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string("getSelectedValue", [selectLocator,])
+
+
+ def get_selected_indexes(self,selectLocator):
+ """
+ Gets all option indexes (option number, starting at 0) for selected options in the specified select or multi-select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string_array("getSelectedIndexes", [selectLocator,])
+
+
+ def get_selected_index(self,selectLocator):
+ """
+ Gets option index (option number, starting at 0) for selected option in the specified select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string("getSelectedIndex", [selectLocator,])
+
+
+ def get_selected_ids(self,selectLocator):
+ """
+ Gets all option element IDs for selected options in the specified select or multi-select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string_array("getSelectedIds", [selectLocator,])
+
+
+ def get_selected_id(self,selectLocator):
+ """
+ Gets option element ID for selected option in the specified select element.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string("getSelectedId", [selectLocator,])
+
+
+ def is_something_selected(self,selectLocator):
+ """
+ Determines whether some option in a drop-down menu is selected.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_boolean("isSomethingSelected", [selectLocator,])
+
+
+ def get_select_options(self,selectLocator):
+ """
+ Gets all option labels in the specified select drop-down.
+
+ 'selectLocator' is an element locator identifying a drop-down menu
+ """
+ return self.get_string_array("getSelectOptions", [selectLocator,])
+
+
+ def get_attribute(self,attributeLocator):
+ """
+ Gets the value of an element attribute. The value of the attribute may
+ differ across browsers (this is the case for the "style" attribute, for
+ example).
+
+ 'attributeLocator' is an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar"
+ """
+ return self.get_string("getAttribute", [attributeLocator,])
+
+
+ def is_text_present(self,pattern):
+ """
+ Verifies that the specified text pattern appears somewhere on the rendered page shown to the user.
+
+ 'pattern' is a pattern to match with the text of the page
+ """
+ return self.get_boolean("isTextPresent", [pattern,])
+
+
+ def is_element_present(self,locator):
+ """
+ Verifies that the specified element is somewhere on the page.
+
+ 'locator' is an element locator
+ """
+ return self.get_boolean("isElementPresent", [locator,])
+
+
+ def is_visible(self,locator):
+ """
+ Determines if the specified element is visible. An
+ element can be rendered invisible by setting the CSS "visibility"
+ property to "hidden", or the "display" property to "none", either for the
+ element itself or one if its ancestors. This method will fail if
+ the element is not present.
+
+ 'locator' is an element locator
+ """
+ return self.get_boolean("isVisible", [locator,])
+
+
+ def is_editable(self,locator):
+ """
+ Determines whether the specified input element is editable, ie hasn't been disabled.
+ This method will fail if the specified element isn't an input element.
+
+ 'locator' is an element locator
+ """
+ return self.get_boolean("isEditable", [locator,])
+
+
+ def get_all_buttons(self):
+ """
+ Returns the IDs of all buttons on the page.
+
+
+ If a given button has no ID, it will appear as "" in this array.
+
+
+ """
+ return self.get_string_array("getAllButtons", [])
+
+
+ def get_all_links(self):
+ """
+ Returns the IDs of all links on the page.
+
+
+ If a given link has no ID, it will appear as "" in this array.
+
+
+ """
+ return self.get_string_array("getAllLinks", [])
+
+
+ def get_all_fields(self):
+ """
+ Returns the IDs of all input fields on the page.
+
+
+ If a given field has no ID, it will appear as "" in this array.
+
+
+ """
+ return self.get_string_array("getAllFields", [])
+
+
+ def get_attribute_from_all_windows(self,attributeName):
+ """
+ Returns every instance of some attribute from all known windows.
+
+ 'attributeName' is name of an attribute on the windows
+ """
+ return self.get_string_array("getAttributeFromAllWindows", [attributeName,])
+
+
+ def dragdrop(self,locator,movementsString):
+ """
+ deprecated - use dragAndDrop instead
+
+ 'locator' is an element locator
+ 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300"
+ """
+ self.do_command("dragdrop", [locator,movementsString,])
+
+
+ def set_mouse_speed(self,pixels):
+ """
+ Configure the number of pixels between "mousemove" events during dragAndDrop commands (default=10).
+
+ Setting this value to 0 means that we'll send a "mousemove" event to every single pixel
+ in between the start location and the end location; that can be very slow, and may
+ cause some browsers to force the JavaScript to timeout.
+
+ If the mouse speed is greater than the distance between the two dragged objects, we'll
+ just send one "mousemove" at the start location and then one final one at the end location.
+
+
+ 'pixels' is the number of pixels between "mousemove" events
+ """
+ self.do_command("setMouseSpeed", [pixels,])
+
+
+ def get_mouse_speed(self):
+ """
+ Returns the number of pixels between "mousemove" events during dragAndDrop commands (default=10).
+
+ """
+ return self.get_number("getMouseSpeed", [])
+
+
+ def drag_and_drop(self,locator,movementsString):
+ """
+ Drags an element a certain distance and then drops it
+
+ 'locator' is an element locator
+ 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300"
+ """
+ self.do_command("dragAndDrop", [locator,movementsString,])
+
+
+ def drag_and_drop_to_object(self,locatorOfObjectToBeDragged,locatorOfDragDestinationObject):
+ """
+ Drags an element and drops it on another element
+
+ 'locatorOfObjectToBeDragged' is an element to be dragged
+ 'locatorOfDragDestinationObject' is an element whose location (i.e., whose center-most pixel) will be the point where locatorOfObjectToBeDragged is dropped
+ """
+ self.do_command("dragAndDropToObject", [locatorOfObjectToBeDragged,locatorOfDragDestinationObject,])
+
+
+ def window_focus(self):
+ """
+ Gives focus to the currently selected window
+
+ """
+ self.do_command("windowFocus", [])
+
+
+ def window_maximize(self):
+ """
+ Resize currently selected window to take up the entire screen
+
+ """
+ self.do_command("windowMaximize", [])
+
+
+ def get_all_window_ids(self):
+ """
+ Returns the IDs of all windows that the browser knows about.
+
+ """
+ return self.get_string_array("getAllWindowIds", [])
+
+
+ def get_all_window_names(self):
+ """
+ Returns the names of all windows that the browser knows about.
+
+ """
+ return self.get_string_array("getAllWindowNames", [])
+
+
+ def get_all_window_titles(self):
+ """
+ Returns the titles of all windows that the browser knows about.
+
+ """
+ return self.get_string_array("getAllWindowTitles", [])
+
+
+ def get_html_source(self):
+ """
+ Returns the entire HTML source between the opening and
+ closing "html" tags.
+
+ """
+ return self.get_string("getHtmlSource", [])
+
+
+ def set_cursor_position(self,locator,position):
+ """
+ Moves the text cursor to the specified position in the given input element or textarea.
+ This method will fail if the specified element isn't an input element or textarea.
+
+ 'locator' is an element locator pointing to an input element or textarea
+ 'position' is the numerical position of the cursor in the field; position should be 0 to move the position to the beginning of the field. You can also set the cursor to -1 to move it to the end of the field.
+ """
+ self.do_command("setCursorPosition", [locator,position,])
+
+
+ def get_element_index(self,locator):
+ """
+ Get the relative index of an element to its parent (starting from 0). The comment node and empty text node
+ will be ignored.
+
+ 'locator' is an element locator pointing to an element
+ """
+ return self.get_number("getElementIndex", [locator,])
+
+
+ def is_ordered(self,locator1,locator2):
+ """
+ Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will
+ not be considered ordered.
+
+ 'locator1' is an element locator pointing to the first element
+ 'locator2' is an element locator pointing to the second element
+ """
+ return self.get_boolean("isOrdered", [locator1,locator2,])
+
+
+ def get_element_position_left(self,locator):
+ """
+ Retrieves the horizontal position of an element
+
+ 'locator' is an element locator pointing to an element OR an element itself
+ """
+ return self.get_number("getElementPositionLeft", [locator,])
+
+
+ def get_element_position_top(self,locator):
+ """
+ Retrieves the vertical position of an element
+
+ 'locator' is an element locator pointing to an element OR an element itself
+ """
+ return self.get_number("getElementPositionTop", [locator,])
+
+
+ def get_element_width(self,locator):
+ """
+ Retrieves the width of an element
+
+ 'locator' is an element locator pointing to an element
+ """
+ return self.get_number("getElementWidth", [locator,])
+
+
+ def get_element_height(self,locator):
+ """
+ Retrieves the height of an element
+
+ 'locator' is an element locator pointing to an element
+ """
+ return self.get_number("getElementHeight", [locator,])
+
+
+ def get_cursor_position(self,locator):
+ """
+ Retrieves the text cursor position in the given input element or textarea; beware, this may not work perfectly on all browsers.
+
+
+ Specifically, if the cursor/selection has been cleared by JavaScript, this command will tend to
+ return the position of the last location of the cursor, even though the cursor is now gone from the page. This is filed as SEL-243.
+
+ This method will fail if the specified element isn't an input element or textarea, or there is no cursor in the element.
+
+ 'locator' is an element locator pointing to an input element or textarea
+ """
+ return self.get_number("getCursorPosition", [locator,])
+
+
+ def get_expression(self,expression):
+ """
+ Returns the specified expression.
+
+
+ This is useful because of JavaScript preprocessing.
+ It is used to generate commands like assertExpression and waitForExpression.
+
+
+ 'expression' is the value to return
+ """
+ return self.get_string("getExpression", [expression,])
+
+
+ def get_xpath_count(self,xpath):
+ """
+ Returns the number of nodes that match the specified xpath, eg. "//table" would give
+ the number of tables.
+
+ 'xpath' is the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you.
+ """
+ return self.get_number("getXpathCount", [xpath,])
+
+
+ def assign_id(self,locator,identifier):
+ """
+ Temporarily sets the "id" attribute of the specified element, so you can locate it in the future
+ using its ID rather than a slow/complicated XPath. This ID will disappear once the page is
+ reloaded.
+
+ 'locator' is an element locator pointing to an element
+ 'identifier' is a string to be used as the ID of the specified element
+ """
+ self.do_command("assignId", [locator,identifier,])
+
+
+ def allow_native_xpath(self,allow):
+ """
+ Specifies whether Selenium should use the native in-browser implementation
+ of XPath (if any native version is available); if you pass "false" to
+ this function, we will always use our pure-JavaScript xpath library.
+ Using the pure-JS xpath library can improve the consistency of xpath
+ element locators between different browser vendors, but the pure-JS
+ version is much slower than the native implementations.
+
+ 'allow' is boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath
+ """
+ self.do_command("allowNativeXpath", [allow,])
+
+
+ def ignore_attributes_without_value(self,ignore):
+ """
+ Specifies whether Selenium will ignore xpath attributes that have no
+ value, i.e. are the empty string, when using the non-native xpath
+ evaluation engine. You'd want to do this for performance reasons in IE.
+ However, this could break certain xpaths, for example an xpath that looks
+ for an attribute whose value is NOT the empty string.
+
+ The hope is that such xpaths are relatively rare, but the user should
+ have the option of using them. Note that this only influences xpath
+ evaluation when using the ajaxslt engine (i.e. not "javascript-xpath").
+
+ 'ignore' is boolean, true means we'll ignore attributes without value at the expense of xpath "correctness"; false means we'll sacrifice speed for correctness.
+ """
+ self.do_command("ignoreAttributesWithoutValue", [ignore,])
+
+
+ def wait_for_condition(self,script,timeout):
+ """
+ Runs the specified JavaScript snippet repeatedly until it evaluates to "true".
+ The snippet may have multiple lines, but only the result of the last line
+ will be considered.
+
+
+ Note that, by default, the snippet will be run in the runner's test window, not in the window
+ of your application. To get the window of your application, you can use
+ the JavaScript snippet ``selenium.browserbot.getCurrentWindow()``, and then
+ run your JavaScript in there
+
+
+ 'script' is the JavaScript snippet to run
+ 'timeout' is a timeout in milliseconds, after which this command will return with an error
+ """
+ self.do_command("waitForCondition", [script,timeout,])
+
+
+ def set_timeout(self,timeout):
+ """
+ Specifies the amount of time that Selenium will wait for actions to complete.
+
+
+ Actions that require waiting include "open" and the "waitFor\*" actions.
+
+ The default timeout is 30 seconds.
+
+ 'timeout' is a timeout in milliseconds, after which the action will return with an error
+ """
+ self.do_command("setTimeout", [timeout,])
+
+
+ def wait_for_page_to_load(self,timeout):
+ """
+ Waits for a new page to load.
+
+
+ You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc.
+ (which are only available in the JS API).
+
+ Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded"
+ flag when it first notices a page load. Running any other Selenium command after
+ turns the flag to false. Hence, if you want to wait for a page to load, you must
+ wait immediately after a Selenium command that caused a page-load.
+
+
+ 'timeout' is a timeout in milliseconds, after which this command will return with an error
+ """
+ self.do_command("waitForPageToLoad", [timeout,])
+
+
+ def wait_for_frame_to_load(self,frameAddress,timeout):
+ """
+ Waits for a new frame to load.
+
+
+ Selenium constantly keeps track of new pages and frames loading,
+ and sets a "newPageLoaded" flag when it first notices a page load.
+
+
+ See waitForPageToLoad for more information.
+
+ 'frameAddress' is FrameAddress from the server side
+ 'timeout' is a timeout in milliseconds, after which this command will return with an error
+ """
+ self.do_command("waitForFrameToLoad", [frameAddress,timeout,])
+
+
+ def get_cookie(self):
+ """
+ Return all cookies of the current page under test.
+
+ """
+ return self.get_string("getCookie", [])
+
+
+ def get_cookie_by_name(self,name):
+ """
+ Returns the value of the cookie with the specified name, or throws an error if the cookie is not present.
+
+ 'name' is the name of the cookie
+ """
+ return self.get_string("getCookieByName", [name,])
+
+
+ def is_cookie_present(self,name):
+ """
+ Returns true if a cookie with the specified name is present, or false otherwise.
+
+ 'name' is the name of the cookie
+ """
+ return self.get_boolean("isCookiePresent", [name,])
+
+
+ def create_cookie(self,nameValuePair,optionsString):
+ """
+ Create a new cookie whose path and domain are same with those of current page
+ under test, unless you specified a path for this cookie explicitly.
+
+ 'nameValuePair' is name and value of the cookie in a format "name=value"
+ 'optionsString' is options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'. the optionsString's format is "path=/path/, max_age=60, domain=.foo.com". The order of options are irrelevant, the unit of the value of 'max_age' is second. Note that specifying a domain that isn't a subset of the current domain will usually fail.
+ """
+ self.do_command("createCookie", [nameValuePair,optionsString,])
+
+
+ def delete_cookie(self,name,optionsString):
+ """
+ Delete a named cookie with specified path and domain. Be careful; to delete a cookie, you
+ need to delete it using the exact same path and domain that were used to create the cookie.
+ If the path is wrong, or the domain is wrong, the cookie simply won't be deleted. Also
+ note that specifying a domain that isn't a subset of the current domain will usually fail.
+
+ Since there's no way to discover at runtime the original path and domain of a given cookie,
+ we've added an option called 'recurse' to try all sub-domains of the current domain with
+ all paths that are a subset of the current path. Beware; this option can be slow. In
+ big-O notation, it operates in O(n\*m) time, where n is the number of dots in the domain
+ name and m is the number of slashes in the path.
+
+ 'name' is the name of the cookie to be deleted
+ 'optionsString' is options for the cookie. Currently supported options include 'path', 'domain' and 'recurse.' The optionsString's format is "path=/path/, domain=.foo.com, recurse=true". The order of options are irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail.
+ """
+ self.do_command("deleteCookie", [name,optionsString,])
+
+
+ def delete_all_visible_cookies(self):
+ """
+ Calls deleteCookie with recurse=true on all cookies visible to the current page.
+ As noted on the documentation for deleteCookie, recurse=true can be much slower
+ than simply deleting the cookies using a known domain/path.
+
+ """
+ self.do_command("deleteAllVisibleCookies", [])
+
+
+ def set_browser_log_level(self,logLevel):
+ """
+ Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded.
+ Valid logLevel strings are: "debug", "info", "warn", "error" or "off".
+ To see the browser logs, you need to
+ either show the log window in GUI mode, or enable browser-side logging in Selenium RC.
+
+ 'logLevel' is one of the following: "debug", "info", "warn", "error" or "off"
+ """
+ self.do_command("setBrowserLogLevel", [logLevel,])
+
+
+ def run_script(self,script):
+ """
+ Creates a new "script" tag in the body of the current test window, and
+ adds the specified text into the body of the command. Scripts run in
+ this way can often be debugged more easily than scripts executed using
+ Selenium's "getEval" command. Beware that JS exceptions thrown in these script
+ tags aren't managed by Selenium, so you should probably wrap your script
+ in try/catch blocks if there is any chance that the script will throw
+ an exception.
+
+ 'script' is the JavaScript snippet to run
+ """
+ self.do_command("runScript", [script,])
+
+
+ def add_location_strategy(self,strategyName,functionDefinition):
+ """
+ Defines a new function for Selenium to locate elements on the page.
+ For example,
+ if you define the strategy "foo", and someone runs click("foo=blah"), we'll
+ run your function, passing you the string "blah", and click on the element
+ that your function
+ returns, or throw an "Element not found" error if your function returns null.
+
+ We'll pass three arguments to your function:
+
+ * locator: the string the user passed in
+ * inWindow: the currently selected window
+ * inDocument: the currently selected document
+
+
+ The function must return null if the element can't be found.
+
+ 'strategyName' is the name of the strategy to define; this should use only letters [a-zA-Z] with no spaces or other punctuation.
+ 'functionDefinition' is a string defining the body of a function in JavaScript. For example: ``return inDocument.getElementById(locator);``
+ """
+ self.do_command("addLocationStrategy", [strategyName,functionDefinition,])
+
+
+ def capture_entire_page_screenshot(self,filename):
+ """
+ Saves the entire contents of the current window canvas to a PNG file.
+ Currently this only works in Mozilla and when running in chrome mode.
+ Contrast this with the captureScreenshot command, which captures the
+ contents of the OS viewport (i.e. whatever is currently being displayed
+ on the monitor), and is implemented in the RC only. Implementation
+ mostly borrowed from the Screengrab! Firefox extension. Please see
+ http://www.screengrab.org for details.
+
+ 'filename' is the path to the file to persist the screenshot as. No filename extension will be appended by default. Directories will not be created if they do not exist, and an exception will be thrown, possibly by native code.
+ """
+ self.do_command("captureEntirePageScreenshot", [filename,])
+
+
+ def set_context(self,context):
+ """
+ Writes a message to the status bar and adds a note to the browser-side
+ log.
+
+ 'context' is the message to be sent to the browser
+ """
+ self.do_command("setContext", [context,])
+
+
+ def attach_file(self,fieldLocator,fileLocator):
+ """
+ Sets a file input (upload) field to the file listed in fileLocator
+
+ 'fieldLocator' is an element locator
+ 'fileLocator' is a URL pointing to the specified file. Before the file can be set in the input field (fieldLocator), Selenium RC may need to transfer the file to the local machine before attaching the file in a web page form. This is common in selenium grid configurations where the RC server driving the browser is not the same machine that started the test. Supported Browsers: Firefox ("\*chrome") only.
+ """
+ self.do_command("attachFile", [fieldLocator,fileLocator,])
+
+
+ def capture_screenshot(self,filename):
+ """
+ Captures a PNG screenshot to the specified file.
+
+ 'filename' is the absolute path to the file to be written, e.g. "c:\blah\screenshot.png"
+ """
+ self.do_command("captureScreenshot", [filename,])
+
+
+ def shut_down_selenium_server(self):
+ """
+ Kills the running Selenium Server and all browser sessions. After you run this command, you will no longer be able to send
+ commands to the server; you can't remotely start the server once it has been stopped. Normally
+ you should prefer to run the "stop" command, which terminates the current browser session, rather than
+ shutting down the entire server.
+
+ """
+ self.do_command("shutDownSeleniumServer", [])
+
+
+ def key_down_native(self,keycode):
+ """
+ Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke.
+ This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+ a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+ metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
+ element, focus on the element first before running this command.
+
+ 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
+ """
+ self.do_command("keyDownNative", [keycode,])
+
+
+ def key_up_native(self,keycode):
+ """
+ Simulates a user releasing a key by sending a native operating system keystroke.
+ This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+ a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+ metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
+ element, focus on the element first before running this command.
+
+ 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
+ """
+ self.do_command("keyUpNative", [keycode,])
+
+
+ def key_press_native(self,keycode):
+ """
+ Simulates a user pressing and releasing a key by sending a native operating system keystroke.
+ This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+ a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+ metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular
+ element, focus on the element first before running this command.
+
+ 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
+ """
+ self.do_command("keyPressNative", [keycode,])
+
diff --git a/misc/openlayers/tests/selenium/remotecontrol/setup.txt b/misc/openlayers/tests/selenium/remotecontrol/setup.txt
new file mode 100644
index 0000000..d349491
--- /dev/null
+++ b/misc/openlayers/tests/selenium/remotecontrol/setup.txt
@@ -0,0 +1,8 @@
+ * Install selenium remote control from the latest snapshot:
+
+ http://nexus.openqa.org/content/repositories/snapshots/org/seleniumhq/selenium/selenium-remote-control/1.0-SNAPSHOT/
+
+ * Run the server with java -jar selenium-server.jar
+ * Run the python script
+
+
diff --git a/misc/openlayers/tests/selenium/remotecontrol/test_ol.py b/misc/openlayers/tests/selenium/remotecontrol/test_ol.py
new file mode 100644
index 0000000..cb8ad98
--- /dev/null
+++ b/misc/openlayers/tests/selenium/remotecontrol/test_ol.py
@@ -0,0 +1,95 @@
+from selenium import selenium
+import time
+import sys
+from ConfigParser import ConfigParser
+
+MAX_TEST_LENGTH = 300
+if len(sys.argv) > 2:
+ filename = sys.argv[2]
+else:
+ filename = "config.cfg"
+
+c = ConfigParser()
+c.read(filename)
+
+targets = {}
+
+server = c.get('config', 'server')
+url= c.get('config', 'url')
+if c.has_option('config', 'timeout'):
+ MAX_TEST_LENGTH = int(c.get('config', 'timeout'))
+
+
+sections = c.sections()
+for s in sections:
+ if s == 'config':
+ continue
+ targets[s] = dict(c.items(s))
+ targets[s]['name'] = s
+
+if sys.argv[1] == "all":
+ browsers = list(targets.values())
+elif sys.argv[1] not in targets:
+ print "Invalid target"
+ sys.exit()
+else:
+ browsers = [targets[sys.argv[1]]]
+
+keep_going = True
+
+if 1:
+ for b in browsers:
+ if not keep_going:
+ continue
+
+ print "Running %s on %s" % (b['name'], b['host'])
+ s = selenium(b['host'], 4444, "*%s" % b['browsercmd'], server)
+ s.start()
+ try:
+ s.open_window(url, "test_running")
+ time.sleep(2)
+ s.select_window("test_running")
+ time.sleep(2)
+ s.refresh()
+
+ count = 0
+ while count == 0:
+ count = int(s.get_eval("window.document.getElementById('testtable').getElementsByTagName('tr').length"))
+ time.sleep(5)
+
+ ok = 0
+ fail = 0
+ last_change = time.time()
+ while True:
+ new_ok = int(s.get_eval('window.Test.AnotherWay._g_ok_pages'))
+ new_fail = int(s.get_eval('window.Test.AnotherWay._g_fail_pages'))
+ if new_ok != ok or new_fail != fail:
+ ok = new_ok
+ fail = new_fail
+ last_change = time.time()
+
+ if (ok + fail) >= count:
+ break
+ if time.time() - last_change > MAX_TEST_LENGTH:
+ raise Exception("Failed: with %s okay and %s failed, ran out of time: %s is more than %s" % (ok, fail, (time.time() - last_change), MAX_TEST_LENGTH))
+ time.sleep(10)
+
+ if fail:
+ print "Failed: %s" % fail
+ html = s.get_eval("window.document.getElementById('results').innerHTML").encode("utf-8")
+ all_html = """<html>
+ <head>
+ <meta content="text/html; charset=utf-8" http-equiv="content-type" />
+ </head>
+ <body>%s</body></html>""" % html
+
+ f = open("fail.%s.%s.html" % (time.time(), b['name']), "w")
+ f.write(all_html)
+ f.close()
+ except KeyboardInterrupt, E:
+ keep_going = False
+ print "Stopped by keyboard interrupt"
+ except Exception, E:
+ print "Error: ", E
+ s.stop()
+
diff --git a/misc/openlayers/tests/speed/geometry.html b/misc/openlayers/tests/speed/geometry.html
new file mode 100644
index 0000000..bc2b488
--- /dev/null
+++ b/misc/openlayers/tests/speed/geometry.html
@@ -0,0 +1,43 @@
+<html>
+<script src="../../lib/OpenLayers.js"></script>
+<script>
+
+var test_data = {};
+
+function setup_test() {
+ if (test_data['polygon']) { return; }
+ var f = new OpenLayers.Format.WKT();
+ var list = f.read("POLYGON((-78.046875 5.9765625, -78.75 5.9765625, -79.453125 5.2734375, -79.453125 4.5703125, -80.15625 3.8671875, -80.15625 2.4609375, -80.15625 1.0546875, -80.15625 -1.0546875, -80.15625 -2.4609375, -80.15625 -3.8671875, -80.15625 -4.5703125, -80.15625 -5.2734375, -80.15625 -5.9765625, -80.15625 -6.6796875, -80.15625 -8.0859375, -80.15625 -8.7890625, -80.15625 -9.4921875, -80.15625 -10.8984375, -79.453125 -13.0078125, -78.046875 -13.7109375, -78.046875 -15.1171875, -76.640625 -15.1171875, -76.640625 -16.5234375, -75.9375 -16.5234375, -75.9375 -17.2265625, -75.234375 -17.9296875, -75.234375 -18.6328125, -74.53125 -19.3359375, -74.53125 -20.0390625, -74.53125 -20.7421875, -73.828125 -20.7421875, -73.828125 -22.8515625, -73.828125 -24.2578125, -72.421875 -25.6640625, -71.015625 -25.6640625, -71.015625 -26.3671875, -69.609375 -27.7734375, -69.609375 -28.4765625, -68.90625 -29.1796875, -68.203125 -29.8828125, -67.5 -30.5859375, -67.5 -31.9921875, -67.5 -32.6953125, -67.5 -33.3984375, -67.5 -34.8046875, -67.5 -36.9140625, -68.203125 -39.0234375, -68.203125 -39.7265625, -70.3125 -41.1328125, -71.015625 -41.8359375, -71.015625 -43.2421875, -71.015625 -43.9453125, -71.015625 -44.6484375, -71.71875 -48.8671875, -71.71875 -50.2734375, -71.71875 -50.9765625, -72.421875 -52.3828125, -72.421875 -53.0859375, -73.828125 -53.7890625, -73.828125 -55.1953125, -73.828125 -55.8984375, -73.828125 -56.6015625, -73.828125 -57.3046875, -73.828125 -58.0078125, -72.421875 -59.4140625, -71.71875 -60.1171875, -71.015625 -60.1171875, -70.3125 -60.1171875, -68.90625 -60.8203125, -68.203125 -60.8203125, -67.5 -60.8203125, -66.796875 -60.8203125, -66.796875 -59.4140625, -65.390625 -58.0078125, -65.390625 -57.3046875, -65.390625 -55.8984375, -65.390625 -55.1953125, -65.390625 -54.4921875, -65.390625 -53.7890625, -65.390625 -53.0859375, -64.6875 -52.3828125, -60.46875 -50.2734375, -57.65625 -48.8671875, -55.546875 -47.4609375, -54.84375 -47.4609375, -54.84375 -46.7578125, -54.140625 -46.7578125, -54.140625 -46.0546875, -54.140625 -45.3515625, -54.140625 -43.2421875, -54.140625 -41.1328125, -54.140625 -39.0234375, -53.4375 -37.6171875, -52.734375 -36.2109375, -51.328125 -36.2109375, -49.921875 -35.5078125, -48.515625 -34.8046875, -45 -33.3984375, -41.484375 -31.9921875, -35.15625 -28.4765625, -33.046875 -27.0703125, -33.046875 -24.9609375, -33.046875 -23.5546875, -33.046875 -22.1484375, -33.046875 -19.3359375, -32.34375 -13.0078125, -31.640625 -11.6015625, -31.640625 -10.8984375, -31.640625 -10.1953125, -31.640625 -8.0859375, -31.640625 -7.3828125, -31.640625 -5.9765625, -31.640625 -4.5703125, -31.640625 -2.4609375, -32.34375 -2.4609375, -34.453125 0.3515625, -37.265625 3.1640625, -41.484375 6.6796875, -45.703125 8.7890625, -53.4375 12.3046875, -55.546875 14.4140625, -56.953125 14.4140625, -59.0625 15.1171875, -61.875 13.7109375, -65.390625 13.0078125, -71.015625 12.3046875, -75.234375 11.6015625, -78.75 11.6015625, -80.859375 11.6015625, -81.5625 11.6015625, -81.5625 10.8984375, -81.5625 10.1953125, -81.5625 9.4921875, -81.5625 8.7890625, -80.15625 8.7890625, -80.15625 6.6796875, -80.15625 5.2734375, -80.15625 4.5703125, -80.15625 3.8671875, -79.453125 3.8671875, -78.046875 5.9765625))");
+ test_data['polygon'] = list.geometry;
+ var list = f.read("POLYGON((-125.15625 48.1640625, -125.859375 48.1640625, -125.859375 46.7578125, -125.15625 46.7578125, -124.453125 46.0546875, -123.75 46.0546875, -123.046875 46.0546875, -122.34375 46.0546875, -120.9375 46.0546875, -116.71875 46.7578125, -113.90625 46.7578125, -113.203125 46.7578125, -112.5 46.7578125, -111.796875 46.7578125, -111.09375 46.7578125, -110.390625 46.7578125, -109.6875 46.7578125, -106.875 46.7578125, -102.65625 46.7578125, -95.625 47.4609375, -91.40625 48.1640625, -87.890625 48.1640625, -87.1875 48.1640625, -86.484375 48.1640625, -85.78125 48.1640625, -84.375 48.1640625, -82.96875 48.1640625, -81.5625 48.1640625, -80.15625 48.1640625, -79.453125 47.4609375, -79.453125 46.7578125, -78.75 46.7578125, -78.75 45.3515625, -78.046875 45.3515625, -77.34375 45.3515625, -76.640625 45.3515625, -75.234375 45.3515625, -71.015625 46.7578125, -69.609375 47.4609375, -68.90625 47.4609375, -68.203125 48.1640625, -67.5 48.1640625, -66.09375 48.1640625, -65.390625 49.5703125, -63.984375 49.5703125, -63.28125 49.5703125, -62.578125 49.5703125, -61.875 48.8671875, -61.875 48.1640625, -61.875 46.7578125, -61.171875 46.0546875, -61.171875 43.2421875, -61.171875 41.8359375, -61.171875 40.4296875, -61.171875 39.7265625, -61.875 39.7265625, -62.578125 39.7265625, -63.28125 39.7265625, -63.984375 39.7265625, -66.796875 39.7265625, -67.5 39.7265625, -68.203125 39.7265625, -68.90625 39.7265625, -68.90625 39.0234375, -69.609375 38.3203125, -69.609375 36.9140625, -71.015625 34.8046875, -71.015625 32.6953125, -72.421875 31.2890625, -73.125 30.5859375, -73.828125 29.8828125, -74.53125 29.1796875, -75.234375 28.4765625, -76.640625 26.3671875, -76.640625 25.6640625, -76.640625 24.9609375, -77.34375 24.2578125, -77.34375 23.5546875, -78.046875 23.5546875, -79.453125 23.5546875, -80.15625 23.5546875, -80.859375 23.5546875, -81.5625 23.5546875, -82.265625 23.5546875, -82.96875 23.5546875, -83.671875 23.5546875, -83.671875 24.2578125, -85.078125 24.2578125, -85.78125 24.9609375, -85.78125 25.6640625, -86.484375 25.6640625, -87.1875 26.3671875, -87.1875 27.7734375, -87.1875 28.4765625, -87.890625 28.4765625, -88.59375 28.4765625, -89.296875 28.4765625, -90 28.4765625, -90.703125 28.4765625, -91.40625 28.4765625, -91.40625 27.7734375, -92.8125 27.7734375, -92.8125 27.0703125, -92.8125 26.3671875, -92.8125 25.6640625, -92.8125 24.9609375, -92.8125 24.2578125, -93.515625 24.2578125, -94.921875 23.5546875, -95.625 23.5546875, -97.03125 23.5546875, -97.734375 23.5546875, -98.4375 23.5546875, -99.140625 23.5546875, -99.84375 24.2578125, -101.25 24.2578125, -104.0625 24.9609375, -106.171875 25.6640625, -106.875 26.3671875, -107.578125 26.3671875, -108.28125 26.3671875, -108.984375 26.3671875, -110.390625 26.3671875, -113.90625 26.3671875, -116.015625 26.3671875, -116.71875 27.0703125, -118.125 27.0703125, -118.828125 27.7734375, -120.234375 27.7734375, -120.234375 28.4765625, -120.9375 28.4765625, -120.9375 29.1796875, -121.640625 29.8828125, -121.640625 30.5859375, -121.640625 31.2890625, -123.046875 31.2890625, -123.75 32.6953125, -124.453125 33.3984375, -125.15625 34.1015625, -126.5625 34.8046875, -127.265625 34.8046875, -127.96875 35.5078125, -125.15625 48.1640625))");
+ test_data['miss_polygon'] = list.geometry;
+ var list = f.read("POLYGON((-32.34375 13.0078125, -33.046875 13.0078125, -33.75 13.0078125, -34.453125 13.0078125, -35.15625 13.0078125, -35.859375 13.0078125, -36.5625 13.0078125, -37.265625 13.0078125, -37.96875 13.0078125, -37.96875 12.3046875, -38.671875 12.3046875, -39.375 12.3046875, -40.078125 12.3046875, -40.078125 11.6015625, -41.484375 11.6015625, -42.890625 10.8984375, -43.59375 10.8984375, -44.296875 10.1953125, -44.296875 9.4921875, -44.296875 8.7890625, -44.296875 8.0859375, -44.296875 6.6796875, -44.296875 5.9765625, -44.296875 5.2734375, -43.59375 5.2734375, -43.59375 4.5703125, -43.59375 3.8671875, -42.890625 3.1640625, -42.890625 2.4609375, -42.890625 1.7578125, -42.890625 1.0546875, -42.1875 -0.3515625, -41.484375 -1.0546875, -41.484375 -2.4609375, -41.484375 -3.1640625, -42.890625 -3.1640625, -44.296875 -3.8671875, -44.296875 -4.5703125, -45.703125 -5.2734375, -46.40625 -5.2734375, -47.109375 -5.2734375, -47.109375 -5.9765625, -47.109375 -6.6796875, -47.109375 -7.3828125, -47.109375 -8.0859375, -47.109375 -9.4921875, -47.109375 -10.1953125, -47.109375 -10.8984375, -47.109375 -11.6015625, -47.109375 -12.3046875, -47.109375 -13.7109375, -47.109375 -15.1171875, -47.109375 -15.8203125, -47.8125 -15.8203125, -47.8125 -16.5234375, -46.40625 -16.5234375, -45.703125 -16.5234375, -44.296875 -17.2265625, -43.59375 -17.9296875, -42.890625 -18.6328125, -42.1875 -18.6328125, -41.484375 -18.6328125, -40.78125 -18.6328125, -39.375 -18.6328125, -37.265625 -18.6328125, -35.859375 -18.6328125, -34.453125 -17.9296875, -33.75 -17.9296875, -33.046875 -17.9296875, -32.34375 -17.9296875, -32.34375 -18.6328125, -32.34375 -19.3359375, -32.34375 -20.0390625, -32.34375 -21.4453125, -31.640625 -22.1484375, -30.9375 -22.8515625, -28.828125 -22.8515625, -26.71875 -22.8515625, -23.90625 -20.0390625, -23.203125 -19.3359375, -22.5 -19.3359375, -21.796875 -17.2265625, -21.09375 -16.5234375, -21.09375 -15.8203125, -21.09375 -15.1171875, -21.09375 -14.4140625, -19.6875 -10.8984375, -19.6875 -8.0859375, -19.6875 -7.3828125, -19.6875 -5.9765625, -19.6875 -5.2734375, -19.6875 -3.1640625, -19.6875 -1.7578125, -19.6875 -0.3515625, -19.6875 0.3515625, -19.6875 1.0546875, -19.6875 1.7578125, -19.6875 2.4609375, -19.6875 3.1640625, -20.390625 4.5703125, -20.390625 5.9765625, -20.390625 6.6796875, -21.09375 6.6796875, -22.5 8.0859375, -23.90625 8.7890625, -25.3125 8.7890625, -26.015625 9.4921875, -26.71875 9.4921875, -27.421875 9.4921875, -28.125 9.4921875, -32.34375 13.0078125))");
+ test_data['hit_polygon'] = list.geometry;
+
+}
+
+function run_test() {
+ test_data['polygon'].intersects(test_data['hit_polygon']);
+ test_data['polygon'].intersects(test_data['miss_polygon']);
+}
+
+function run_many(x) {
+ var elapsed = 0;
+ for (var i = 0; i < x; i++) {
+ var time = new Date();
+ run_test();
+ elapsed += (new Date() - time);
+ }
+ return elapsed;
+}
+
+function print_results(x) {
+ var t = run_many(x);
+ document.getElementById("out").innerHTML += (t +"ms to run " + x + " times<br />");
+}
+ setup_test()
+</script>
+<body>
+<input type="submit" value="Run" onclick="print_results(5)" />
+<div id="out"></div>
+</body>
diff --git a/misc/openlayers/tests/speed/string_format.html b/misc/openlayers/tests/speed/string_format.html
new file mode 100644
index 0000000..39146b7
--- /dev/null
+++ b/misc/openlayers/tests/speed/string_format.html
@@ -0,0 +1,29 @@
+<html>
+<head>
+<script src="../../lib/OpenLayers.js"></script>
+<script>
+
+function stringformat() {
+ var string = OpenLayers.String.format("${abc} 123 ${def}", {'abc': 456, 'def': 789})
+}
+function run(x) {
+ var date = new Date();
+ for (var i = 0; i < x; i++) {
+ stringformat();
+ }
+ var elapsed = (new Date() - date);
+ return elapsed;
+}
+
+function show_time(x)
+{
+ var t = run(x);
+ document.getElementById("out").innerHTML = t + "ms for " + x + " runs";
+}
+</script>
+</head>
+<body>
+<a onclick="javascript:show_time(100000); return false" href="#">Run</a>
+<div id="out"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/speed/vector-renderers.html b/misc/openlayers/tests/speed/vector-renderers.html
new file mode 100644
index 0000000..4d88dfc
--- /dev/null
+++ b/misc/openlayers/tests/speed/vector-renderers.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Vector Features Performance Test</title>
+ <script type="text/javascript" src="https://getfirebug.com/firebug-lite.js#startOpened=true"></script>
+ <link rel="stylesheet" href="../../theme/default/style.css" type="text/css" />
+ <link rel="stylesheet" href="../../examples/style.css" type="text/css" />
+ </head>
+ <body>
+ <h1 id="title">Vector Rendering Performance</h1>
+ <div id="map" class="smallmap"></div>
+ <p>
+ This is a benchmark for vector rendering performance. Test results are
+ written to the debug console.
+ Select a renderer here:
+ <br/>
+ <select id="renderers"></select>
+ </p><p>
+ The benchmark shows the time needed to render the features, and how long a
+ move (drag or zoom) takes. Drag and zoom around to produce move results.
+ </p>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script src="vector-renderers.js"></script>
+ </body>
+</html> \ No newline at end of file
diff --git a/misc/openlayers/tests/speed/vector-renderers.js b/misc/openlayers/tests/speed/vector-renderers.js
new file mode 100644
index 0000000..a11b361
--- /dev/null
+++ b/misc/openlayers/tests/speed/vector-renderers.js
@@ -0,0 +1,70 @@
+var map, vectorLayer, drawFeature, features
+
+map = new OpenLayers.Map('map', {
+ eventListeners: {
+ movestart: function() {
+ console.time("move");
+ },
+ moveend: function() {
+ console.timeEnd("move");
+ }
+ }
+});
+
+// allow testing of specific renderers via "?renderer=Canvas", etc
+var renderer = OpenLayers.Util.getParameters(window.location.href).renderer;
+renderer = (renderer) ? [renderer] : OpenLayers.Layer.Vector.prototype.renderers;
+
+vectorLayer = new OpenLayers.Layer.Vector("Vector Layer", {
+ isBaseLayer: true,
+ renderers: renderer,
+ eventListeners: {
+ beforefeaturesadded: function() {
+ console.time("addFeatures");
+ },
+ featuresadded: function() {
+ console.timeEnd("addFeatures");
+ }
+ }
+});
+
+map.addLayers([vectorLayer]);
+map.addControl(new OpenLayers.Control.MousePosition());
+map.setCenter(new OpenLayers.LonLat(0, 0), 2);
+
+features = new Array(500);
+var x, y, points
+for (var i = 0; i < 500; i++) {
+ x = 90-Math.random()*180;
+ y = 45-Math.random()*90;
+ var pointList = [];
+ for(var p=0; p<19; ++p) {
+ var a = p * (2 * Math.PI) / 20;
+ var r = Math.random() * 3 + 1;
+ var newPoint = new OpenLayers.Geometry.Point(x + (r * Math.cos(a)),
+ y + (r * Math.sin(a)));
+ pointList.push(newPoint);
+ }
+ pointList.push(pointList[0]);
+ features[i] = new OpenLayers.Feature.Vector(
+ new OpenLayers.Geometry.LinearRing(pointList));
+
+}
+vectorLayer.addFeatures(features);
+
+var select = document.getElementById("renderers");
+var renderers = OpenLayers.Layer.Vector.prototype.renderers;
+var option;
+for (var i=0, len=renderers.length; i<len; i++) {
+ if (OpenLayers.Renderer[renderers[i]].prototype.supported()) {
+ option = document.createElement("option");
+ option.textContent = renderers[i];
+ option.value = renderers[i];
+ option.selected = renderers[i] == vectorLayer.renderer.CLASS_NAME.split(".").pop();
+ select.appendChild(option);
+ }
+}
+select.onchange = function() {
+ window.location.href = window.location.href.split("?")[0] +
+ "?renderer=" + select.options[select.selectedIndex].value;
+}
diff --git a/misc/openlayers/tests/speed/wmc_speed.html b/misc/openlayers/tests/speed/wmc_speed.html
new file mode 100644
index 0000000..0dd0ce8
--- /dev/null
+++ b/misc/openlayers/tests/speed/wmc_speed.html
@@ -0,0 +1,30 @@
+<html>
+<head>
+<script src="../../lib/OpenLayers.js"></script>
+<script>
+var context = '<ViewContext xmlns="http://www.opengis.net/context" version="1.1.0" id="OpenLayers_Context_232" xsi:schemaLocation="http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><General><Window width="512" height="256"/><BoundingBox minx="-109.9709708" miny="27.01451459" maxx="-80.02902918" maxy="41.98548541" SRS="EPSG:4326"/><Title/><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/></Extension></General><LayerList><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://t1.hypercube.telascience.org/cgi-bin/landsat7"/></Server><Name>landsat7</Name><Title>NASA Global Mosaic</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6299645.760</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">31498228.80</sld:MaxScaleDenominator><FormatList><Format current="1">image/jpeg</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">true</ol:isBaseLayer><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="1"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://labs.metacarta.com/wms/vmap0"/></Server><Name>basic</Name><Title>OpenLayers WMS</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6299645.760</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">31498228.80</sld:MaxScaleDenominator><FormatList><Format current="1">image/jpeg</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-130.0000000" miny="14.00000000" maxx="-60.00000000" maxy="55.00000000"/><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">true</ol:isBaseLayer><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://lioapp.lrc.gov.on.ca/cubeserv/cubeserv.pl"/></Server><Name>na_road:CCRS</Name><Title>Transportation Network</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6200000.000</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">32000000.00</sld:MaxScaleDenominator><FormatList><Format current="1">image/png</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-166.5320000" miny="4.050460000" maxx="-0.2068180000" maxy="70.28700000"/><ol:transparent xmlns:ol="http://openlayers.org/context">TRUE</ol:transparent><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer><ol:opacity xmlns:ol="http://openlayers.org/context">0.6</ol:opacity><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">false</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">false</ol:singleTile></Extension></Layer><Layer queryable="1" hidden="0"><Server service="OGC:WMS" version="1.1.1"><OnlineResource xlink:type="simple" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="http://columbo.nrlssc.navy.mil/ogcwms/servlet/WMSServlet/AccuWeather_Maps.wms"/></Server><Name>3:1</Name><Title>Radar 3:1</Title><sld:MinScaleDenominator xmlns:sld="http://www.opengis.net/sld">6299645.760</sld:MinScaleDenominator><sld:MaxScaleDenominator xmlns:sld="http://www.opengis.net/sld">31498228.80</sld:MaxScaleDenominator><FormatList><Format current="1">image/png</Format></FormatList><StyleList><Style current="1"><Name/><Title>Default</Title></Style></StyleList><Extension><ol:maxExtent xmlns:ol="http://openlayers.org/context" minx="-131.0294952" miny="14.56289673" maxx="-61.02950287" maxy="54.56289673"/><ol:transparent xmlns:ol="http://openlayers.org/context">TRUE</ol:transparent><ol:numZoomLevels xmlns:ol="http://openlayers.org/context">4</ol:numZoomLevels><ol:units xmlns:ol="http://openlayers.org/context">degrees</ol:units><ol:isBaseLayer xmlns:ol="http://openlayers.org/context">false</ol:isBaseLayer><ol:opacity xmlns:ol="http://openlayers.org/context">0.8</ol:opacity><ol:displayInLayerSwitcher xmlns:ol="http://openlayers.org/context">true</ol:displayInLayerSwitcher><ol:singleTile xmlns:ol="http://openlayers.org/context">true</ol:singleTile></Extension></Layer></LayerList></ViewContext>';
+function parse_wmc() {
+ var format = new OpenLayers.Format.WMC.v1()
+ var data = format.read(context);
+}
+function run(x) {
+ var date = new Date();
+ for (var i = 0; i < x; i++) {
+ parse_wmc();
+ }
+ var elapsed = (new Date() - date);
+ return elapsed;
+}
+
+function show_time(x)
+{
+ var t = run(x);
+ document.getElementById("out").innerHTML = t + "ms for " + x + " runs";
+}
+</script>
+</head>
+<body>
+<a onclick="javascript:show_time(100); return false" href="#">Run</a>
+<div id="out"></div>
+</body>
+</html>
diff --git a/misc/openlayers/tests/speed/wmscaps.html b/misc/openlayers/tests/speed/wmscaps.html
new file mode 100644
index 0000000..3cab542
--- /dev/null
+++ b/misc/openlayers/tests/speed/wmscaps.html
@@ -0,0 +1,52 @@
+<html>
+ <head>
+ <title>WMS Capabilities Speed Test</title>
+ <script src="../../lib/OpenLayers.js"></script>
+ <script src="wmscaps.js"></script>
+ <script>
+ var data;
+ var stats = [];
+
+ function parseCaps(id, done) {
+ var format = new OpenLayers.Format.WMSCapabilities();
+ data = format.read(caps);
+ done(id);
+ }
+
+ function run(func, x) {
+ document.getElementById("out").innerHTML = "running ...";
+ var starts = {};
+ var elapsed = 0;
+ completed = 0;
+ function callback(id) {
+ elapsed += new Date() - starts[id];
+ ++completed;
+ if (completed === x) {
+ report(x, elapsed);
+ }
+ }
+ var runner;
+ for (var i=0; i<x; i++) {
+ runner = createRunner(i, starts, func, callback);
+ window.setTimeout(runner, 0);
+ }
+ }
+
+ function createRunner(id, starts, func, done) {
+ return function() {
+ starts[id] = new Date();
+ func(id, done);
+ }
+ }
+
+ function report(x, elapsed) {
+ document.getElementById("out").innerHTML = elapsed + " ms for " + x + " runs (" + elapsed/x + " ms average)";
+ }
+
+ </script>
+ </head>
+ <body>
+ <a onclick="javascript:run(parseCaps, 5); return false" href="#">Run</a>
+ <div id="out"></div>
+ </body>
+</html>
diff --git a/misc/openlayers/tests/speed/wmscaps.js b/misc/openlayers/tests/speed/wmscaps.js
new file mode 100644
index 0000000..a186779
--- /dev/null
+++ b/misc/openlayers/tests/speed/wmscaps.js
@@ -0,0 +1,4956 @@
+var caps =
+'<?xml version="1.0" encoding="UTF-8"?>' +
+'<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://demo.opengeo.org/geoserver/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd">' +
+'<WMT_MS_Capabilities version="1.1.1" updateSequence="145">' +
+' <Service>' +
+' <Name>OGC:WMS</Name>' +
+' <Title>GeoServer Web Map Service</Title>' +
+' <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>' +
+' <KeywordList>' +
+' <Keyword>WFS</Keyword>' +
+' <Keyword>WMS</Keyword>' +
+' <Keyword>GEOSERVER</Keyword>' +
+' </KeywordList>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms"/>' +
+' <ContactInformation>' +
+' <ContactPersonPrimary>' +
+' <ContactPerson>Claudius Ptolomaeus</ContactPerson>' +
+' <ContactOrganization>The ancient geographes INC</ContactOrganization>' +
+' </ContactPersonPrimary>' +
+' <ContactPosition>Chief geographer</ContactPosition>' +
+' <ContactAddress>' +
+' <AddressType>Work</AddressType>' +
+' <Address/>' +
+' <City>Alexandria</City>' +
+' <StateOrProvince/>' +
+' <PostCode/>' +
+' <Country>Egypt</Country>' +
+' </ContactAddress>' +
+' <ContactVoiceTelephone/>' +
+' <ContactFacsimileTelephone/>' +
+' <ContactElectronicMailAddress>claudius.ptolomaeus@gmail.com</ContactElectronicMailAddress>' +
+' </ContactInformation>' +
+' <Fees>NONE</Fees>' +
+' <AccessConstraints>NONE</AccessConstraints>' +
+' </Service>' +
+' <Capability>' +
+' <Request>' +
+' <GetCapabilities>' +
+' <Format>application/vnd.ogc.wms_xml</Format>' +
+' <DCPType>' +
+' <HTTP>' +
+' <Get>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Get>' +
+' <Post>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Post>' +
+' </HTTP>' +
+' </DCPType>' +
+' </GetCapabilities>' +
+' <GetMap>' +
+' <Format>image/png</Format>' +
+' <Format>application/atom xml</Format>' +
+' <Format>application/atom+xml</Format>' +
+' <Format>application/openlayers</Format>' +
+' <Format>application/pdf</Format>' +
+' <Format>application/rss xml</Format>' +
+' <Format>application/rss+xml</Format>' +
+' <Format>application/vnd.google-earth.kml</Format>' +
+' <Format>application/vnd.google-earth.kml xml</Format>' +
+' <Format>application/vnd.google-earth.kml+xml</Format>' +
+' <Format>application/vnd.google-earth.kmz</Format>' +
+' <Format>application/vnd.google-earth.kmz xml</Format>' +
+' <Format>application/vnd.google-earth.kmz+xml</Format>' +
+' <Format>atom</Format>' +
+' <Format>image/geotiff</Format>' +
+' <Format>image/geotiff8</Format>' +
+' <Format>image/gif</Format>' +
+' <Format>image/jpeg</Format>' +
+' <Format>image/png8</Format>' +
+' <Format>image/svg</Format>' +
+' <Format>image/svg xml</Format>' +
+' <Format>image/svg+xml</Format>' +
+' <Format>image/tiff</Format>' +
+' <Format>image/tiff8</Format>' +
+' <Format>kml</Format>' +
+' <Format>kmz</Format>' +
+' <Format>openlayers</Format>' +
+' <Format>rss</Format>' +
+' <DCPType>' +
+' <HTTP>' +
+' <Get>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Get>' +
+' </HTTP>' +
+' </DCPType>' +
+' </GetMap>' +
+' <GetFeatureInfo>' +
+' <Format>text/plain</Format>' +
+' <Format>text/html</Format>' +
+' <Format>application/vnd.ogc.gml</Format>' +
+' <DCPType>' +
+' <HTTP>' +
+' <Get>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Get>' +
+' <Post>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Post>' +
+' </HTTP>' +
+' </DCPType>' +
+' </GetFeatureInfo>' +
+' <DescribeLayer>' +
+' <Format>application/vnd.ogc.wms_xml</Format>' +
+' <DCPType>' +
+' <HTTP>' +
+' <Get>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Get>' +
+' </HTTP>' +
+' </DCPType>' +
+' </DescribeLayer>' +
+' <GetLegendGraphic>' +
+' <Format>image/png</Format>' +
+' <Format>image/jpeg</Format>' +
+' <Format>image/gif</Format>' +
+' <DCPType>' +
+' <HTTP>' +
+' <Get>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>' +
+' </Get>' +
+' </HTTP>' +
+' </DCPType>' +
+' </GetLegendGraphic>' +
+' </Request>' +
+' <Exception>' +
+' <Format>application/vnd.ogc.se_xml</Format>' +
+' </Exception>' +
+' <UserDefinedSymbolization SupportSLD="1" UserLayer="1" UserStyle="1" RemoteWFS="1"/>' +
+' <Layer>' +
+' <Title>GeoServer Web Map Service</Title>' +
+' <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>' +
+' <!--All supported EPSG projections:-->' +
+' <SRS>EPSG:WGS84(DD)</SRS>' +
+' <SRS>EPSG:2000</SRS>' +
+' <SRS>EPSG:2001</SRS>' +
+' <SRS>EPSG:2002</SRS>' +
+' <SRS>EPSG:2003</SRS>' +
+' <SRS>EPSG:2004</SRS>' +
+' <SRS>EPSG:2005</SRS>' +
+' <SRS>EPSG:2006</SRS>' +
+' <SRS>EPSG:2007</SRS>' +
+' <SRS>EPSG:2008</SRS>' +
+' <SRS>EPSG:2009</SRS>' +
+' <SRS>EPSG:2010</SRS>' +
+' <SRS>EPSG:2011</SRS>' +
+' <SRS>EPSG:2012</SRS>' +
+' <SRS>EPSG:2013</SRS>' +
+' <SRS>EPSG:2014</SRS>' +
+' <SRS>EPSG:2015</SRS>' +
+' <SRS>EPSG:2016</SRS>' +
+' <SRS>EPSG:2017</SRS>' +
+' <SRS>EPSG:2018</SRS>' +
+' <SRS>EPSG:2019</SRS>' +
+' <SRS>EPSG:2020</SRS>' +
+' <SRS>EPSG:2021</SRS>' +
+' <SRS>EPSG:2022</SRS>' +
+' <SRS>EPSG:2023</SRS>' +
+' <SRS>EPSG:2024</SRS>' +
+' <SRS>EPSG:2025</SRS>' +
+' <SRS>EPSG:2026</SRS>' +
+' <SRS>EPSG:2027</SRS>' +
+' <SRS>EPSG:2028</SRS>' +
+' <SRS>EPSG:2029</SRS>' +
+' <SRS>EPSG:2030</SRS>' +
+' <SRS>EPSG:2031</SRS>' +
+' <SRS>EPSG:2032</SRS>' +
+' <SRS>EPSG:2033</SRS>' +
+' <SRS>EPSG:2034</SRS>' +
+' <SRS>EPSG:2035</SRS>' +
+' <SRS>EPSG:2036</SRS>' +
+' <SRS>EPSG:2037</SRS>' +
+' <SRS>EPSG:2038</SRS>' +
+' <SRS>EPSG:2039</SRS>' +
+' <SRS>EPSG:2040</SRS>' +
+' <SRS>EPSG:2041</SRS>' +
+' <SRS>EPSG:2042</SRS>' +
+' <SRS>EPSG:2043</SRS>' +
+' <SRS>EPSG:2044</SRS>' +
+' <SRS>EPSG:2045</SRS>' +
+' <SRS>EPSG:2046</SRS>' +
+' <SRS>EPSG:2047</SRS>' +
+' <SRS>EPSG:2048</SRS>' +
+' <SRS>EPSG:2049</SRS>' +
+' <SRS>EPSG:2050</SRS>' +
+' <SRS>EPSG:2051</SRS>' +
+' <SRS>EPSG:2052</SRS>' +
+' <SRS>EPSG:2053</SRS>' +
+' <SRS>EPSG:2054</SRS>' +
+' <SRS>EPSG:2055</SRS>' +
+' <SRS>EPSG:2056</SRS>' +
+' <SRS>EPSG:2057</SRS>' +
+' <SRS>EPSG:2058</SRS>' +
+' <SRS>EPSG:2059</SRS>' +
+' <SRS>EPSG:2060</SRS>' +
+' <SRS>EPSG:2061</SRS>' +
+' <SRS>EPSG:2062</SRS>' +
+' <SRS>EPSG:2063</SRS>' +
+' <SRS>EPSG:2064</SRS>' +
+' <SRS>EPSG:2065</SRS>' +
+' <SRS>EPSG:2066</SRS>' +
+' <SRS>EPSG:2067</SRS>' +
+' <SRS>EPSG:2068</SRS>' +
+' <SRS>EPSG:2069</SRS>' +
+' <SRS>EPSG:2070</SRS>' +
+' <SRS>EPSG:2071</SRS>' +
+' <SRS>EPSG:2072</SRS>' +
+' <SRS>EPSG:2073</SRS>' +
+' <SRS>EPSG:2074</SRS>' +
+' <SRS>EPSG:2075</SRS>' +
+' <SRS>EPSG:2076</SRS>' +
+' <SRS>EPSG:2077</SRS>' +
+' <SRS>EPSG:2078</SRS>' +
+' <SRS>EPSG:2079</SRS>' +
+' <SRS>EPSG:2080</SRS>' +
+' <SRS>EPSG:2081</SRS>' +
+' <SRS>EPSG:2082</SRS>' +
+' <SRS>EPSG:2083</SRS>' +
+' <SRS>EPSG:2084</SRS>' +
+' <SRS>EPSG:2085</SRS>' +
+' <SRS>EPSG:2086</SRS>' +
+' <SRS>EPSG:2087</SRS>' +
+' <SRS>EPSG:2088</SRS>' +
+' <SRS>EPSG:2089</SRS>' +
+' <SRS>EPSG:2090</SRS>' +
+' <SRS>EPSG:2091</SRS>' +
+' <SRS>EPSG:2092</SRS>' +
+' <SRS>EPSG:2093</SRS>' +
+' <SRS>EPSG:2094</SRS>' +
+' <SRS>EPSG:2095</SRS>' +
+' <SRS>EPSG:2096</SRS>' +
+' <SRS>EPSG:2097</SRS>' +
+' <SRS>EPSG:2098</SRS>' +
+' <SRS>EPSG:2099</SRS>' +
+' <SRS>EPSG:2100</SRS>' +
+' <SRS>EPSG:2101</SRS>' +
+' <SRS>EPSG:2102</SRS>' +
+' <SRS>EPSG:2103</SRS>' +
+' <SRS>EPSG:2104</SRS>' +
+' <SRS>EPSG:2105</SRS>' +
+' <SRS>EPSG:2106</SRS>' +
+' <SRS>EPSG:2107</SRS>' +
+' <SRS>EPSG:2108</SRS>' +
+' <SRS>EPSG:2109</SRS>' +
+' <SRS>EPSG:2110</SRS>' +
+' <SRS>EPSG:2111</SRS>' +
+' <SRS>EPSG:2112</SRS>' +
+' <SRS>EPSG:2113</SRS>' +
+' <SRS>EPSG:2114</SRS>' +
+' <SRS>EPSG:2115</SRS>' +
+' <SRS>EPSG:2116</SRS>' +
+' <SRS>EPSG:2117</SRS>' +
+' <SRS>EPSG:2118</SRS>' +
+' <SRS>EPSG:2119</SRS>' +
+' <SRS>EPSG:2120</SRS>' +
+' <SRS>EPSG:2121</SRS>' +
+' <SRS>EPSG:2122</SRS>' +
+' <SRS>EPSG:2123</SRS>' +
+' <SRS>EPSG:2124</SRS>' +
+' <SRS>EPSG:2125</SRS>' +
+' <SRS>EPSG:2126</SRS>' +
+' <SRS>EPSG:2127</SRS>' +
+' <SRS>EPSG:2128</SRS>' +
+' <SRS>EPSG:2129</SRS>' +
+' <SRS>EPSG:2130</SRS>' +
+' <SRS>EPSG:2131</SRS>' +
+' <SRS>EPSG:2132</SRS>' +
+' <SRS>EPSG:2133</SRS>' +
+' <SRS>EPSG:2134</SRS>' +
+' <SRS>EPSG:2135</SRS>' +
+' <SRS>EPSG:2136</SRS>' +
+' <SRS>EPSG:2137</SRS>' +
+' <SRS>EPSG:2138</SRS>' +
+' <SRS>EPSG:2139</SRS>' +
+' <SRS>EPSG:2140</SRS>' +
+' <SRS>EPSG:2141</SRS>' +
+' <SRS>EPSG:2142</SRS>' +
+' <SRS>EPSG:2143</SRS>' +
+' <SRS>EPSG:2144</SRS>' +
+' <SRS>EPSG:2145</SRS>' +
+' <SRS>EPSG:2146</SRS>' +
+' <SRS>EPSG:2147</SRS>' +
+' <SRS>EPSG:2148</SRS>' +
+' <SRS>EPSG:2149</SRS>' +
+' <SRS>EPSG:2150</SRS>' +
+' <SRS>EPSG:2151</SRS>' +
+' <SRS>EPSG:2152</SRS>' +
+' <SRS>EPSG:2153</SRS>' +
+' <SRS>EPSG:2154</SRS>' +
+' <SRS>EPSG:2155</SRS>' +
+' <SRS>EPSG:2156</SRS>' +
+' <SRS>EPSG:2157</SRS>' +
+' <SRS>EPSG:2158</SRS>' +
+' <SRS>EPSG:2159</SRS>' +
+' <SRS>EPSG:2160</SRS>' +
+' <SRS>EPSG:2161</SRS>' +
+' <SRS>EPSG:2162</SRS>' +
+' <SRS>EPSG:2163</SRS>' +
+' <SRS>EPSG:2164</SRS>' +
+' <SRS>EPSG:2165</SRS>' +
+' <SRS>EPSG:2166</SRS>' +
+' <SRS>EPSG:2167</SRS>' +
+' <SRS>EPSG:2168</SRS>' +
+' <SRS>EPSG:2169</SRS>' +
+' <SRS>EPSG:2170</SRS>' +
+' <SRS>EPSG:2171</SRS>' +
+' <SRS>EPSG:2172</SRS>' +
+' <SRS>EPSG:2173</SRS>' +
+' <SRS>EPSG:2174</SRS>' +
+' <SRS>EPSG:2175</SRS>' +
+' <SRS>EPSG:2176</SRS>' +
+' <SRS>EPSG:2177</SRS>' +
+' <SRS>EPSG:2178</SRS>' +
+' <SRS>EPSG:2179</SRS>' +
+' <SRS>EPSG:2180</SRS>' +
+' <SRS>EPSG:2188</SRS>' +
+' <SRS>EPSG:2189</SRS>' +
+' <SRS>EPSG:2190</SRS>' +
+' <SRS>EPSG:2191</SRS>' +
+' <SRS>EPSG:2192</SRS>' +
+' <SRS>EPSG:2193</SRS>' +
+' <SRS>EPSG:2194</SRS>' +
+' <SRS>EPSG:2195</SRS>' +
+' <SRS>EPSG:2196</SRS>' +
+' <SRS>EPSG:2197</SRS>' +
+' <SRS>EPSG:2198</SRS>' +
+' <SRS>EPSG:2199</SRS>' +
+' <SRS>EPSG:2200</SRS>' +
+' <SRS>EPSG:2201</SRS>' +
+' <SRS>EPSG:2202</SRS>' +
+' <SRS>EPSG:2203</SRS>' +
+' <SRS>EPSG:2204</SRS>' +
+' <SRS>EPSG:2205</SRS>' +
+' <SRS>EPSG:2206</SRS>' +
+' <SRS>EPSG:2207</SRS>' +
+' <SRS>EPSG:2208</SRS>' +
+' <SRS>EPSG:2209</SRS>' +
+' <SRS>EPSG:2210</SRS>' +
+' <SRS>EPSG:2211</SRS>' +
+' <SRS>EPSG:2212</SRS>' +
+' <SRS>EPSG:2213</SRS>' +
+' <SRS>EPSG:2214</SRS>' +
+' <SRS>EPSG:2215</SRS>' +
+' <SRS>EPSG:2216</SRS>' +
+' <SRS>EPSG:2217</SRS>' +
+' <SRS>EPSG:2218</SRS>' +
+' <SRS>EPSG:2219</SRS>' +
+' <SRS>EPSG:2220</SRS>' +
+' <SRS>EPSG:2221</SRS>' +
+' <SRS>EPSG:2222</SRS>' +
+' <SRS>EPSG:2223</SRS>' +
+' <SRS>EPSG:2224</SRS>' +
+' <SRS>EPSG:2225</SRS>' +
+' <SRS>EPSG:2226</SRS>' +
+' <SRS>EPSG:2227</SRS>' +
+' <SRS>EPSG:2228</SRS>' +
+' <SRS>EPSG:2229</SRS>' +
+' <SRS>EPSG:2230</SRS>' +
+' <SRS>EPSG:2231</SRS>' +
+' <SRS>EPSG:2232</SRS>' +
+' <SRS>EPSG:2233</SRS>' +
+' <SRS>EPSG:2234</SRS>' +
+' <SRS>EPSG:2235</SRS>' +
+' <SRS>EPSG:2236</SRS>' +
+' <SRS>EPSG:2237</SRS>' +
+' <SRS>EPSG:2238</SRS>' +
+' <SRS>EPSG:2239</SRS>' +
+' <SRS>EPSG:2240</SRS>' +
+' <SRS>EPSG:2241</SRS>' +
+' <SRS>EPSG:2242</SRS>' +
+' <SRS>EPSG:2243</SRS>' +
+' <SRS>EPSG:2244</SRS>' +
+' <SRS>EPSG:2245</SRS>' +
+' <SRS>EPSG:2246</SRS>' +
+' <SRS>EPSG:2247</SRS>' +
+' <SRS>EPSG:2248</SRS>' +
+' <SRS>EPSG:2249</SRS>' +
+' <SRS>EPSG:2250</SRS>' +
+' <SRS>EPSG:2251</SRS>' +
+' <SRS>EPSG:2252</SRS>' +
+' <SRS>EPSG:2253</SRS>' +
+' <SRS>EPSG:2254</SRS>' +
+' <SRS>EPSG:2255</SRS>' +
+' <SRS>EPSG:2256</SRS>' +
+' <SRS>EPSG:2257</SRS>' +
+' <SRS>EPSG:2258</SRS>' +
+' <SRS>EPSG:2259</SRS>' +
+' <SRS>EPSG:2260</SRS>' +
+' <SRS>EPSG:2261</SRS>' +
+' <SRS>EPSG:2262</SRS>' +
+' <SRS>EPSG:2263</SRS>' +
+' <SRS>EPSG:2264</SRS>' +
+' <SRS>EPSG:2265</SRS>' +
+' <SRS>EPSG:2266</SRS>' +
+' <SRS>EPSG:2267</SRS>' +
+' <SRS>EPSG:2268</SRS>' +
+' <SRS>EPSG:2269</SRS>' +
+' <SRS>EPSG:2270</SRS>' +
+' <SRS>EPSG:2271</SRS>' +
+' <SRS>EPSG:2272</SRS>' +
+' <SRS>EPSG:2273</SRS>' +
+' <SRS>EPSG:2274</SRS>' +
+' <SRS>EPSG:2275</SRS>' +
+' <SRS>EPSG:2276</SRS>' +
+' <SRS>EPSG:2277</SRS>' +
+' <SRS>EPSG:2278</SRS>' +
+' <SRS>EPSG:2279</SRS>' +
+' <SRS>EPSG:2280</SRS>' +
+' <SRS>EPSG:2281</SRS>' +
+' <SRS>EPSG:2282</SRS>' +
+' <SRS>EPSG:2283</SRS>' +
+' <SRS>EPSG:2284</SRS>' +
+' <SRS>EPSG:2285</SRS>' +
+' <SRS>EPSG:2286</SRS>' +
+' <SRS>EPSG:2287</SRS>' +
+' <SRS>EPSG:2288</SRS>' +
+' <SRS>EPSG:2289</SRS>' +
+' <SRS>EPSG:2290</SRS>' +
+' <SRS>EPSG:2291</SRS>' +
+' <SRS>EPSG:2292</SRS>' +
+' <SRS>EPSG:2294</SRS>' +
+' <SRS>EPSG:2295</SRS>' +
+' <SRS>EPSG:2296</SRS>' +
+' <SRS>EPSG:2297</SRS>' +
+' <SRS>EPSG:2298</SRS>' +
+' <SRS>EPSG:2299</SRS>' +
+' <SRS>EPSG:2300</SRS>' +
+' <SRS>EPSG:2301</SRS>' +
+' <SRS>EPSG:2302</SRS>' +
+' <SRS>EPSG:2303</SRS>' +
+' <SRS>EPSG:2304</SRS>' +
+' <SRS>EPSG:2305</SRS>' +
+' <SRS>EPSG:2306</SRS>' +
+' <SRS>EPSG:2307</SRS>' +
+' <SRS>EPSG:2308</SRS>' +
+' <SRS>EPSG:2309</SRS>' +
+' <SRS>EPSG:2310</SRS>' +
+' <SRS>EPSG:2311</SRS>' +
+' <SRS>EPSG:2312</SRS>' +
+' <SRS>EPSG:2313</SRS>' +
+' <SRS>EPSG:2314</SRS>' +
+' <SRS>EPSG:2315</SRS>' +
+' <SRS>EPSG:2316</SRS>' +
+' <SRS>EPSG:2317</SRS>' +
+' <SRS>EPSG:2318</SRS>' +
+' <SRS>EPSG:2319</SRS>' +
+' <SRS>EPSG:2320</SRS>' +
+' <SRS>EPSG:2321</SRS>' +
+' <SRS>EPSG:2322</SRS>' +
+' <SRS>EPSG:2323</SRS>' +
+' <SRS>EPSG:2324</SRS>' +
+' <SRS>EPSG:2325</SRS>' +
+' <SRS>EPSG:2326</SRS>' +
+' <SRS>EPSG:2327</SRS>' +
+' <SRS>EPSG:2328</SRS>' +
+' <SRS>EPSG:2329</SRS>' +
+' <SRS>EPSG:2330</SRS>' +
+' <SRS>EPSG:2331</SRS>' +
+' <SRS>EPSG:2332</SRS>' +
+' <SRS>EPSG:2333</SRS>' +
+' <SRS>EPSG:2334</SRS>' +
+' <SRS>EPSG:2335</SRS>' +
+' <SRS>EPSG:2336</SRS>' +
+' <SRS>EPSG:2337</SRS>' +
+' <SRS>EPSG:2338</SRS>' +
+' <SRS>EPSG:2339</SRS>' +
+' <SRS>EPSG:2340</SRS>' +
+' <SRS>EPSG:2341</SRS>' +
+' <SRS>EPSG:2342</SRS>' +
+' <SRS>EPSG:2343</SRS>' +
+' <SRS>EPSG:2344</SRS>' +
+' <SRS>EPSG:2345</SRS>' +
+' <SRS>EPSG:2346</SRS>' +
+' <SRS>EPSG:2347</SRS>' +
+' <SRS>EPSG:2348</SRS>' +
+' <SRS>EPSG:2349</SRS>' +
+' <SRS>EPSG:2350</SRS>' +
+' <SRS>EPSG:2351</SRS>' +
+' <SRS>EPSG:2352</SRS>' +
+' <SRS>EPSG:2353</SRS>' +
+' <SRS>EPSG:2354</SRS>' +
+' <SRS>EPSG:2355</SRS>' +
+' <SRS>EPSG:2356</SRS>' +
+' <SRS>EPSG:2357</SRS>' +
+' <SRS>EPSG:2358</SRS>' +
+' <SRS>EPSG:2359</SRS>' +
+' <SRS>EPSG:2360</SRS>' +
+' <SRS>EPSG:2361</SRS>' +
+' <SRS>EPSG:2362</SRS>' +
+' <SRS>EPSG:2363</SRS>' +
+' <SRS>EPSG:2364</SRS>' +
+' <SRS>EPSG:2365</SRS>' +
+' <SRS>EPSG:2366</SRS>' +
+' <SRS>EPSG:2367</SRS>' +
+' <SRS>EPSG:2368</SRS>' +
+' <SRS>EPSG:2369</SRS>' +
+' <SRS>EPSG:2370</SRS>' +
+' <SRS>EPSG:2371</SRS>' +
+' <SRS>EPSG:2372</SRS>' +
+' <SRS>EPSG:2373</SRS>' +
+' <SRS>EPSG:2374</SRS>' +
+' <SRS>EPSG:2375</SRS>' +
+' <SRS>EPSG:2376</SRS>' +
+' <SRS>EPSG:2377</SRS>' +
+' <SRS>EPSG:2378</SRS>' +
+' <SRS>EPSG:2379</SRS>' +
+' <SRS>EPSG:2380</SRS>' +
+' <SRS>EPSG:2381</SRS>' +
+' <SRS>EPSG:2382</SRS>' +
+' <SRS>EPSG:2383</SRS>' +
+' <SRS>EPSG:2384</SRS>' +
+' <SRS>EPSG:2385</SRS>' +
+' <SRS>EPSG:2386</SRS>' +
+' <SRS>EPSG:2387</SRS>' +
+' <SRS>EPSG:2388</SRS>' +
+' <SRS>EPSG:2389</SRS>' +
+' <SRS>EPSG:2390</SRS>' +
+' <SRS>EPSG:2391</SRS>' +
+' <SRS>EPSG:2392</SRS>' +
+' <SRS>EPSG:2393</SRS>' +
+' <SRS>EPSG:2394</SRS>' +
+' <SRS>EPSG:2395</SRS>' +
+' <SRS>EPSG:2396</SRS>' +
+' <SRS>EPSG:2397</SRS>' +
+' <SRS>EPSG:2398</SRS>' +
+' <SRS>EPSG:2399</SRS>' +
+' <SRS>EPSG:2400</SRS>' +
+' <SRS>EPSG:2401</SRS>' +
+' <SRS>EPSG:2402</SRS>' +
+' <SRS>EPSG:2403</SRS>' +
+' <SRS>EPSG:2404</SRS>' +
+' <SRS>EPSG:2405</SRS>' +
+' <SRS>EPSG:2406</SRS>' +
+' <SRS>EPSG:2407</SRS>' +
+' <SRS>EPSG:2408</SRS>' +
+' <SRS>EPSG:2409</SRS>' +
+' <SRS>EPSG:2410</SRS>' +
+' <SRS>EPSG:2411</SRS>' +
+' <SRS>EPSG:2412</SRS>' +
+' <SRS>EPSG:2413</SRS>' +
+' <SRS>EPSG:2414</SRS>' +
+' <SRS>EPSG:2415</SRS>' +
+' <SRS>EPSG:2416</SRS>' +
+' <SRS>EPSG:2417</SRS>' +
+' <SRS>EPSG:2418</SRS>' +
+' <SRS>EPSG:2419</SRS>' +
+' <SRS>EPSG:2420</SRS>' +
+' <SRS>EPSG:2421</SRS>' +
+' <SRS>EPSG:2422</SRS>' +
+' <SRS>EPSG:2423</SRS>' +
+' <SRS>EPSG:2424</SRS>' +
+' <SRS>EPSG:2425</SRS>' +
+' <SRS>EPSG:2426</SRS>' +
+' <SRS>EPSG:2427</SRS>' +
+' <SRS>EPSG:2428</SRS>' +
+' <SRS>EPSG:2429</SRS>' +
+' <SRS>EPSG:2430</SRS>' +
+' <SRS>EPSG:2431</SRS>' +
+' <SRS>EPSG:2432</SRS>' +
+' <SRS>EPSG:2433</SRS>' +
+' <SRS>EPSG:2434</SRS>' +
+' <SRS>EPSG:2435</SRS>' +
+' <SRS>EPSG:2436</SRS>' +
+' <SRS>EPSG:2437</SRS>' +
+' <SRS>EPSG:2438</SRS>' +
+' <SRS>EPSG:2439</SRS>' +
+' <SRS>EPSG:2440</SRS>' +
+' <SRS>EPSG:2441</SRS>' +
+' <SRS>EPSG:2442</SRS>' +
+' <SRS>EPSG:2443</SRS>' +
+' <SRS>EPSG:2444</SRS>' +
+' <SRS>EPSG:2445</SRS>' +
+' <SRS>EPSG:2446</SRS>' +
+' <SRS>EPSG:2447</SRS>' +
+' <SRS>EPSG:2448</SRS>' +
+' <SRS>EPSG:2449</SRS>' +
+' <SRS>EPSG:2450</SRS>' +
+' <SRS>EPSG:2451</SRS>' +
+' <SRS>EPSG:2452</SRS>' +
+' <SRS>EPSG:2453</SRS>' +
+' <SRS>EPSG:2454</SRS>' +
+' <SRS>EPSG:2455</SRS>' +
+' <SRS>EPSG:2456</SRS>' +
+' <SRS>EPSG:2457</SRS>' +
+' <SRS>EPSG:2458</SRS>' +
+' <SRS>EPSG:2459</SRS>' +
+' <SRS>EPSG:2460</SRS>' +
+' <SRS>EPSG:2461</SRS>' +
+' <SRS>EPSG:2462</SRS>' +
+' <SRS>EPSG:2463</SRS>' +
+' <SRS>EPSG:2464</SRS>' +
+' <SRS>EPSG:2465</SRS>' +
+' <SRS>EPSG:2466</SRS>' +
+' <SRS>EPSG:2467</SRS>' +
+' <SRS>EPSG:2468</SRS>' +
+' <SRS>EPSG:2469</SRS>' +
+' <SRS>EPSG:2470</SRS>' +
+' <SRS>EPSG:2471</SRS>' +
+' <SRS>EPSG:2472</SRS>' +
+' <SRS>EPSG:2473</SRS>' +
+' <SRS>EPSG:2474</SRS>' +
+' <SRS>EPSG:2475</SRS>' +
+' <SRS>EPSG:2476</SRS>' +
+' <SRS>EPSG:2477</SRS>' +
+' <SRS>EPSG:2478</SRS>' +
+' <SRS>EPSG:2479</SRS>' +
+' <SRS>EPSG:2480</SRS>' +
+' <SRS>EPSG:2481</SRS>' +
+' <SRS>EPSG:2482</SRS>' +
+' <SRS>EPSG:2483</SRS>' +
+' <SRS>EPSG:2484</SRS>' +
+' <SRS>EPSG:2485</SRS>' +
+' <SRS>EPSG:2486</SRS>' +
+' <SRS>EPSG:2487</SRS>' +
+' <SRS>EPSG:2488</SRS>' +
+' <SRS>EPSG:2489</SRS>' +
+' <SRS>EPSG:2490</SRS>' +
+' <SRS>EPSG:2491</SRS>' +
+' <SRS>EPSG:2492</SRS>' +
+' <SRS>EPSG:2493</SRS>' +
+' <SRS>EPSG:2494</SRS>' +
+' <SRS>EPSG:2495</SRS>' +
+' <SRS>EPSG:2496</SRS>' +
+' <SRS>EPSG:2497</SRS>' +
+' <SRS>EPSG:2498</SRS>' +
+' <SRS>EPSG:2499</SRS>' +
+' <SRS>EPSG:2500</SRS>' +
+' <SRS>EPSG:2501</SRS>' +
+' <SRS>EPSG:2502</SRS>' +
+' <SRS>EPSG:2503</SRS>' +
+' <SRS>EPSG:2504</SRS>' +
+' <SRS>EPSG:2505</SRS>' +
+' <SRS>EPSG:2506</SRS>' +
+' <SRS>EPSG:2507</SRS>' +
+' <SRS>EPSG:2508</SRS>' +
+' <SRS>EPSG:2509</SRS>' +
+' <SRS>EPSG:2510</SRS>' +
+' <SRS>EPSG:2511</SRS>' +
+' <SRS>EPSG:2512</SRS>' +
+' <SRS>EPSG:2513</SRS>' +
+' <SRS>EPSG:2514</SRS>' +
+' <SRS>EPSG:2515</SRS>' +
+' <SRS>EPSG:2516</SRS>' +
+' <SRS>EPSG:2517</SRS>' +
+' <SRS>EPSG:2518</SRS>' +
+' <SRS>EPSG:2519</SRS>' +
+' <SRS>EPSG:2520</SRS>' +
+' <SRS>EPSG:2521</SRS>' +
+' <SRS>EPSG:2522</SRS>' +
+' <SRS>EPSG:2523</SRS>' +
+' <SRS>EPSG:2524</SRS>' +
+' <SRS>EPSG:2525</SRS>' +
+' <SRS>EPSG:2526</SRS>' +
+' <SRS>EPSG:2527</SRS>' +
+' <SRS>EPSG:2528</SRS>' +
+' <SRS>EPSG:2529</SRS>' +
+' <SRS>EPSG:2530</SRS>' +
+' <SRS>EPSG:2531</SRS>' +
+' <SRS>EPSG:2532</SRS>' +
+' <SRS>EPSG:2533</SRS>' +
+' <SRS>EPSG:2534</SRS>' +
+' <SRS>EPSG:2535</SRS>' +
+' <SRS>EPSG:2536</SRS>' +
+' <SRS>EPSG:2537</SRS>' +
+' <SRS>EPSG:2538</SRS>' +
+' <SRS>EPSG:2539</SRS>' +
+' <SRS>EPSG:2540</SRS>' +
+' <SRS>EPSG:2541</SRS>' +
+' <SRS>EPSG:2542</SRS>' +
+' <SRS>EPSG:2543</SRS>' +
+' <SRS>EPSG:2544</SRS>' +
+' <SRS>EPSG:2545</SRS>' +
+' <SRS>EPSG:2546</SRS>' +
+' <SRS>EPSG:2547</SRS>' +
+' <SRS>EPSG:2548</SRS>' +
+' <SRS>EPSG:2549</SRS>' +
+' <SRS>EPSG:2550</SRS>' +
+' <SRS>EPSG:2551</SRS>' +
+' <SRS>EPSG:2552</SRS>' +
+' <SRS>EPSG:2553</SRS>' +
+' <SRS>EPSG:2554</SRS>' +
+' <SRS>EPSG:2555</SRS>' +
+' <SRS>EPSG:2556</SRS>' +
+' <SRS>EPSG:2557</SRS>' +
+' <SRS>EPSG:2558</SRS>' +
+' <SRS>EPSG:2559</SRS>' +
+' <SRS>EPSG:2560</SRS>' +
+' <SRS>EPSG:2561</SRS>' +
+' <SRS>EPSG:2562</SRS>' +
+' <SRS>EPSG:2563</SRS>' +
+' <SRS>EPSG:2564</SRS>' +
+' <SRS>EPSG:2565</SRS>' +
+' <SRS>EPSG:2566</SRS>' +
+' <SRS>EPSG:2567</SRS>' +
+' <SRS>EPSG:2568</SRS>' +
+' <SRS>EPSG:2569</SRS>' +
+' <SRS>EPSG:2570</SRS>' +
+' <SRS>EPSG:2571</SRS>' +
+' <SRS>EPSG:2572</SRS>' +
+' <SRS>EPSG:2573</SRS>' +
+' <SRS>EPSG:2574</SRS>' +
+' <SRS>EPSG:2575</SRS>' +
+' <SRS>EPSG:2576</SRS>' +
+' <SRS>EPSG:2577</SRS>' +
+' <SRS>EPSG:2578</SRS>' +
+' <SRS>EPSG:2579</SRS>' +
+' <SRS>EPSG:2580</SRS>' +
+' <SRS>EPSG:2581</SRS>' +
+' <SRS>EPSG:2582</SRS>' +
+' <SRS>EPSG:2583</SRS>' +
+' <SRS>EPSG:2584</SRS>' +
+' <SRS>EPSG:2585</SRS>' +
+' <SRS>EPSG:2586</SRS>' +
+' <SRS>EPSG:2587</SRS>' +
+' <SRS>EPSG:2588</SRS>' +
+' <SRS>EPSG:2589</SRS>' +
+' <SRS>EPSG:2590</SRS>' +
+' <SRS>EPSG:2591</SRS>' +
+' <SRS>EPSG:2592</SRS>' +
+' <SRS>EPSG:2593</SRS>' +
+' <SRS>EPSG:2594</SRS>' +
+' <SRS>EPSG:2595</SRS>' +
+' <SRS>EPSG:2596</SRS>' +
+' <SRS>EPSG:2597</SRS>' +
+' <SRS>EPSG:2598</SRS>' +
+' <SRS>EPSG:2599</SRS>' +
+' <SRS>EPSG:2600</SRS>' +
+' <SRS>EPSG:2601</SRS>' +
+' <SRS>EPSG:2602</SRS>' +
+' <SRS>EPSG:2603</SRS>' +
+' <SRS>EPSG:2604</SRS>' +
+' <SRS>EPSG:2605</SRS>' +
+' <SRS>EPSG:2606</SRS>' +
+' <SRS>EPSG:2607</SRS>' +
+' <SRS>EPSG:2608</SRS>' +
+' <SRS>EPSG:2609</SRS>' +
+' <SRS>EPSG:2610</SRS>' +
+' <SRS>EPSG:2611</SRS>' +
+' <SRS>EPSG:2612</SRS>' +
+' <SRS>EPSG:2613</SRS>' +
+' <SRS>EPSG:2614</SRS>' +
+' <SRS>EPSG:2615</SRS>' +
+' <SRS>EPSG:2616</SRS>' +
+' <SRS>EPSG:2617</SRS>' +
+' <SRS>EPSG:2618</SRS>' +
+' <SRS>EPSG:2619</SRS>' +
+' <SRS>EPSG:2620</SRS>' +
+' <SRS>EPSG:2621</SRS>' +
+' <SRS>EPSG:2622</SRS>' +
+' <SRS>EPSG:2623</SRS>' +
+' <SRS>EPSG:2624</SRS>' +
+' <SRS>EPSG:2625</SRS>' +
+' <SRS>EPSG:2626</SRS>' +
+' <SRS>EPSG:2627</SRS>' +
+' <SRS>EPSG:2628</SRS>' +
+' <SRS>EPSG:2629</SRS>' +
+' <SRS>EPSG:2630</SRS>' +
+' <SRS>EPSG:2631</SRS>' +
+' <SRS>EPSG:2632</SRS>' +
+' <SRS>EPSG:2633</SRS>' +
+' <SRS>EPSG:2634</SRS>' +
+' <SRS>EPSG:2635</SRS>' +
+' <SRS>EPSG:2636</SRS>' +
+' <SRS>EPSG:2637</SRS>' +
+' <SRS>EPSG:2638</SRS>' +
+' <SRS>EPSG:2639</SRS>' +
+' <SRS>EPSG:2640</SRS>' +
+' <SRS>EPSG:2641</SRS>' +
+' <SRS>EPSG:2642</SRS>' +
+' <SRS>EPSG:2643</SRS>' +
+' <SRS>EPSG:2644</SRS>' +
+' <SRS>EPSG:2645</SRS>' +
+' <SRS>EPSG:2646</SRS>' +
+' <SRS>EPSG:2647</SRS>' +
+' <SRS>EPSG:2648</SRS>' +
+' <SRS>EPSG:2649</SRS>' +
+' <SRS>EPSG:2650</SRS>' +
+' <SRS>EPSG:2651</SRS>' +
+' <SRS>EPSG:2652</SRS>' +
+' <SRS>EPSG:2653</SRS>' +
+' <SRS>EPSG:2654</SRS>' +
+' <SRS>EPSG:2655</SRS>' +
+' <SRS>EPSG:2656</SRS>' +
+' <SRS>EPSG:2657</SRS>' +
+' <SRS>EPSG:2658</SRS>' +
+' <SRS>EPSG:2659</SRS>' +
+' <SRS>EPSG:2660</SRS>' +
+' <SRS>EPSG:2661</SRS>' +
+' <SRS>EPSG:2662</SRS>' +
+' <SRS>EPSG:2663</SRS>' +
+' <SRS>EPSG:2664</SRS>' +
+' <SRS>EPSG:2665</SRS>' +
+' <SRS>EPSG:2666</SRS>' +
+' <SRS>EPSG:2667</SRS>' +
+' <SRS>EPSG:2668</SRS>' +
+' <SRS>EPSG:2669</SRS>' +
+' <SRS>EPSG:2670</SRS>' +
+' <SRS>EPSG:2671</SRS>' +
+' <SRS>EPSG:2672</SRS>' +
+' <SRS>EPSG:2673</SRS>' +
+' <SRS>EPSG:2674</SRS>' +
+' <SRS>EPSG:2675</SRS>' +
+' <SRS>EPSG:2676</SRS>' +
+' <SRS>EPSG:2677</SRS>' +
+' <SRS>EPSG:2678</SRS>' +
+' <SRS>EPSG:2679</SRS>' +
+' <SRS>EPSG:2680</SRS>' +
+' <SRS>EPSG:2681</SRS>' +
+' <SRS>EPSG:2682</SRS>' +
+' <SRS>EPSG:2683</SRS>' +
+' <SRS>EPSG:2684</SRS>' +
+' <SRS>EPSG:2685</SRS>' +
+' <SRS>EPSG:2686</SRS>' +
+' <SRS>EPSG:2687</SRS>' +
+' <SRS>EPSG:2688</SRS>' +
+' <SRS>EPSG:2689</SRS>' +
+' <SRS>EPSG:2690</SRS>' +
+' <SRS>EPSG:2691</SRS>' +
+' <SRS>EPSG:2692</SRS>' +
+' <SRS>EPSG:2693</SRS>' +
+' <SRS>EPSG:2694</SRS>' +
+' <SRS>EPSG:2695</SRS>' +
+' <SRS>EPSG:2696</SRS>' +
+' <SRS>EPSG:2697</SRS>' +
+' <SRS>EPSG:2698</SRS>' +
+' <SRS>EPSG:2699</SRS>' +
+' <SRS>EPSG:2700</SRS>' +
+' <SRS>EPSG:2701</SRS>' +
+' <SRS>EPSG:2702</SRS>' +
+' <SRS>EPSG:2703</SRS>' +
+' <SRS>EPSG:2704</SRS>' +
+' <SRS>EPSG:2705</SRS>' +
+' <SRS>EPSG:2706</SRS>' +
+' <SRS>EPSG:2707</SRS>' +
+' <SRS>EPSG:2708</SRS>' +
+' <SRS>EPSG:2709</SRS>' +
+' <SRS>EPSG:2710</SRS>' +
+' <SRS>EPSG:2711</SRS>' +
+' <SRS>EPSG:2712</SRS>' +
+' <SRS>EPSG:2713</SRS>' +
+' <SRS>EPSG:2714</SRS>' +
+' <SRS>EPSG:2715</SRS>' +
+' <SRS>EPSG:2716</SRS>' +
+' <SRS>EPSG:2717</SRS>' +
+' <SRS>EPSG:2718</SRS>' +
+' <SRS>EPSG:2719</SRS>' +
+' <SRS>EPSG:2720</SRS>' +
+' <SRS>EPSG:2721</SRS>' +
+' <SRS>EPSG:2722</SRS>' +
+' <SRS>EPSG:2723</SRS>' +
+' <SRS>EPSG:2724</SRS>' +
+' <SRS>EPSG:2725</SRS>' +
+' <SRS>EPSG:2726</SRS>' +
+' <SRS>EPSG:2727</SRS>' +
+' <SRS>EPSG:2728</SRS>' +
+' <SRS>EPSG:2729</SRS>' +
+' <SRS>EPSG:2730</SRS>' +
+' <SRS>EPSG:2731</SRS>' +
+' <SRS>EPSG:2732</SRS>' +
+' <SRS>EPSG:2733</SRS>' +
+' <SRS>EPSG:2734</SRS>' +
+' <SRS>EPSG:2735</SRS>' +
+' <SRS>EPSG:2736</SRS>' +
+' <SRS>EPSG:2737</SRS>' +
+' <SRS>EPSG:2738</SRS>' +
+' <SRS>EPSG:2739</SRS>' +
+' <SRS>EPSG:2740</SRS>' +
+' <SRS>EPSG:2741</SRS>' +
+' <SRS>EPSG:2742</SRS>' +
+' <SRS>EPSG:2743</SRS>' +
+' <SRS>EPSG:2744</SRS>' +
+' <SRS>EPSG:2745</SRS>' +
+' <SRS>EPSG:2746</SRS>' +
+' <SRS>EPSG:2747</SRS>' +
+' <SRS>EPSG:2748</SRS>' +
+' <SRS>EPSG:2749</SRS>' +
+' <SRS>EPSG:2750</SRS>' +
+' <SRS>EPSG:2751</SRS>' +
+' <SRS>EPSG:2752</SRS>' +
+' <SRS>EPSG:2753</SRS>' +
+' <SRS>EPSG:2754</SRS>' +
+' <SRS>EPSG:2755</SRS>' +
+' <SRS>EPSG:2756</SRS>' +
+' <SRS>EPSG:2757</SRS>' +
+' <SRS>EPSG:2758</SRS>' +
+' <SRS>EPSG:2759</SRS>' +
+' <SRS>EPSG:2760</SRS>' +
+' <SRS>EPSG:2761</SRS>' +
+' <SRS>EPSG:2762</SRS>' +
+' <SRS>EPSG:2763</SRS>' +
+' <SRS>EPSG:2764</SRS>' +
+' <SRS>EPSG:2765</SRS>' +
+' <SRS>EPSG:2766</SRS>' +
+' <SRS>EPSG:2767</SRS>' +
+' <SRS>EPSG:2768</SRS>' +
+' <SRS>EPSG:2769</SRS>' +
+' <SRS>EPSG:2770</SRS>' +
+' <SRS>EPSG:2771</SRS>' +
+' <SRS>EPSG:2772</SRS>' +
+' <SRS>EPSG:2773</SRS>' +
+' <SRS>EPSG:2774</SRS>' +
+' <SRS>EPSG:2775</SRS>' +
+' <SRS>EPSG:2776</SRS>' +
+' <SRS>EPSG:2777</SRS>' +
+' <SRS>EPSG:2778</SRS>' +
+' <SRS>EPSG:2779</SRS>' +
+' <SRS>EPSG:2780</SRS>' +
+' <SRS>EPSG:2781</SRS>' +
+' <SRS>EPSG:2782</SRS>' +
+' <SRS>EPSG:2783</SRS>' +
+' <SRS>EPSG:2784</SRS>' +
+' <SRS>EPSG:2785</SRS>' +
+' <SRS>EPSG:2786</SRS>' +
+' <SRS>EPSG:2787</SRS>' +
+' <SRS>EPSG:2788</SRS>' +
+' <SRS>EPSG:2789</SRS>' +
+' <SRS>EPSG:2790</SRS>' +
+' <SRS>EPSG:2791</SRS>' +
+' <SRS>EPSG:2792</SRS>' +
+' <SRS>EPSG:2793</SRS>' +
+' <SRS>EPSG:2794</SRS>' +
+' <SRS>EPSG:2795</SRS>' +
+' <SRS>EPSG:2796</SRS>' +
+' <SRS>EPSG:2797</SRS>' +
+' <SRS>EPSG:2798</SRS>' +
+' <SRS>EPSG:2799</SRS>' +
+' <SRS>EPSG:2800</SRS>' +
+' <SRS>EPSG:2801</SRS>' +
+' <SRS>EPSG:2802</SRS>' +
+' <SRS>EPSG:2803</SRS>' +
+' <SRS>EPSG:2804</SRS>' +
+' <SRS>EPSG:2805</SRS>' +
+' <SRS>EPSG:2806</SRS>' +
+' <SRS>EPSG:2807</SRS>' +
+' <SRS>EPSG:2808</SRS>' +
+' <SRS>EPSG:2809</SRS>' +
+' <SRS>EPSG:2810</SRS>' +
+' <SRS>EPSG:2811</SRS>' +
+' <SRS>EPSG:2812</SRS>' +
+' <SRS>EPSG:2813</SRS>' +
+' <SRS>EPSG:2814</SRS>' +
+' <SRS>EPSG:2815</SRS>' +
+' <SRS>EPSG:2816</SRS>' +
+' <SRS>EPSG:2817</SRS>' +
+' <SRS>EPSG:2818</SRS>' +
+' <SRS>EPSG:2819</SRS>' +
+' <SRS>EPSG:2820</SRS>' +
+' <SRS>EPSG:2821</SRS>' +
+' <SRS>EPSG:2822</SRS>' +
+' <SRS>EPSG:2823</SRS>' +
+' <SRS>EPSG:2824</SRS>' +
+' <SRS>EPSG:2825</SRS>' +
+' <SRS>EPSG:2826</SRS>' +
+' <SRS>EPSG:2827</SRS>' +
+' <SRS>EPSG:2828</SRS>' +
+' <SRS>EPSG:2829</SRS>' +
+' <SRS>EPSG:2830</SRS>' +
+' <SRS>EPSG:2831</SRS>' +
+' <SRS>EPSG:2832</SRS>' +
+' <SRS>EPSG:2833</SRS>' +
+' <SRS>EPSG:2834</SRS>' +
+' <SRS>EPSG:2835</SRS>' +
+' <SRS>EPSG:2836</SRS>' +
+' <SRS>EPSG:2837</SRS>' +
+' <SRS>EPSG:2838</SRS>' +
+' <SRS>EPSG:2839</SRS>' +
+' <SRS>EPSG:2840</SRS>' +
+' <SRS>EPSG:2841</SRS>' +
+' <SRS>EPSG:2842</SRS>' +
+' <SRS>EPSG:2843</SRS>' +
+' <SRS>EPSG:2844</SRS>' +
+' <SRS>EPSG:2845</SRS>' +
+' <SRS>EPSG:2846</SRS>' +
+' <SRS>EPSG:2847</SRS>' +
+' <SRS>EPSG:2848</SRS>' +
+' <SRS>EPSG:2849</SRS>' +
+' <SRS>EPSG:2850</SRS>' +
+' <SRS>EPSG:2851</SRS>' +
+' <SRS>EPSG:2852</SRS>' +
+' <SRS>EPSG:2853</SRS>' +
+' <SRS>EPSG:2854</SRS>' +
+' <SRS>EPSG:2855</SRS>' +
+' <SRS>EPSG:2856</SRS>' +
+' <SRS>EPSG:2857</SRS>' +
+' <SRS>EPSG:2858</SRS>' +
+' <SRS>EPSG:2859</SRS>' +
+' <SRS>EPSG:2860</SRS>' +
+' <SRS>EPSG:2861</SRS>' +
+' <SRS>EPSG:2862</SRS>' +
+' <SRS>EPSG:2863</SRS>' +
+' <SRS>EPSG:2864</SRS>' +
+' <SRS>EPSG:2865</SRS>' +
+' <SRS>EPSG:2866</SRS>' +
+' <SRS>EPSG:2867</SRS>' +
+' <SRS>EPSG:2868</SRS>' +
+' <SRS>EPSG:2869</SRS>' +
+' <SRS>EPSG:2870</SRS>' +
+' <SRS>EPSG:2871</SRS>' +
+' <SRS>EPSG:2872</SRS>' +
+' <SRS>EPSG:2873</SRS>' +
+' <SRS>EPSG:2874</SRS>' +
+' <SRS>EPSG:2875</SRS>' +
+' <SRS>EPSG:2876</SRS>' +
+' <SRS>EPSG:2877</SRS>' +
+' <SRS>EPSG:2878</SRS>' +
+' <SRS>EPSG:2879</SRS>' +
+' <SRS>EPSG:2880</SRS>' +
+' <SRS>EPSG:2881</SRS>' +
+' <SRS>EPSG:2882</SRS>' +
+' <SRS>EPSG:2883</SRS>' +
+' <SRS>EPSG:2884</SRS>' +
+' <SRS>EPSG:2885</SRS>' +
+' <SRS>EPSG:2886</SRS>' +
+' <SRS>EPSG:2887</SRS>' +
+' <SRS>EPSG:2888</SRS>' +
+' <SRS>EPSG:2889</SRS>' +
+' <SRS>EPSG:2890</SRS>' +
+' <SRS>EPSG:2891</SRS>' +
+' <SRS>EPSG:2892</SRS>' +
+' <SRS>EPSG:2893</SRS>' +
+' <SRS>EPSG:2894</SRS>' +
+' <SRS>EPSG:2895</SRS>' +
+' <SRS>EPSG:2896</SRS>' +
+' <SRS>EPSG:2897</SRS>' +
+' <SRS>EPSG:2898</SRS>' +
+' <SRS>EPSG:2899</SRS>' +
+' <SRS>EPSG:2900</SRS>' +
+' <SRS>EPSG:2901</SRS>' +
+' <SRS>EPSG:2902</SRS>' +
+' <SRS>EPSG:2903</SRS>' +
+' <SRS>EPSG:2904</SRS>' +
+' <SRS>EPSG:2905</SRS>' +
+' <SRS>EPSG:2906</SRS>' +
+' <SRS>EPSG:2907</SRS>' +
+' <SRS>EPSG:2908</SRS>' +
+' <SRS>EPSG:2909</SRS>' +
+' <SRS>EPSG:2910</SRS>' +
+' <SRS>EPSG:2911</SRS>' +
+' <SRS>EPSG:2912</SRS>' +
+' <SRS>EPSG:2913</SRS>' +
+' <SRS>EPSG:2914</SRS>' +
+' <SRS>EPSG:2915</SRS>' +
+' <SRS>EPSG:2916</SRS>' +
+' <SRS>EPSG:2917</SRS>' +
+' <SRS>EPSG:2918</SRS>' +
+' <SRS>EPSG:2919</SRS>' +
+' <SRS>EPSG:2920</SRS>' +
+' <SRS>EPSG:2921</SRS>' +
+' <SRS>EPSG:2922</SRS>' +
+' <SRS>EPSG:2923</SRS>' +
+' <SRS>EPSG:2924</SRS>' +
+' <SRS>EPSG:2925</SRS>' +
+' <SRS>EPSG:2926</SRS>' +
+' <SRS>EPSG:2927</SRS>' +
+' <SRS>EPSG:2928</SRS>' +
+' <SRS>EPSG:2929</SRS>' +
+' <SRS>EPSG:2930</SRS>' +
+' <SRS>EPSG:2931</SRS>' +
+' <SRS>EPSG:2932</SRS>' +
+' <SRS>EPSG:2933</SRS>' +
+' <SRS>EPSG:2934</SRS>' +
+' <SRS>EPSG:2935</SRS>' +
+' <SRS>EPSG:2936</SRS>' +
+' <SRS>EPSG:2937</SRS>' +
+' <SRS>EPSG:2938</SRS>' +
+' <SRS>EPSG:2939</SRS>' +
+' <SRS>EPSG:2940</SRS>' +
+' <SRS>EPSG:2941</SRS>' +
+' <SRS>EPSG:2942</SRS>' +
+' <SRS>EPSG:2943</SRS>' +
+' <SRS>EPSG:2944</SRS>' +
+' <SRS>EPSG:2945</SRS>' +
+' <SRS>EPSG:2946</SRS>' +
+' <SRS>EPSG:2947</SRS>' +
+' <SRS>EPSG:2948</SRS>' +
+' <SRS>EPSG:2949</SRS>' +
+' <SRS>EPSG:2950</SRS>' +
+' <SRS>EPSG:2951</SRS>' +
+' <SRS>EPSG:2952</SRS>' +
+' <SRS>EPSG:2953</SRS>' +
+' <SRS>EPSG:2954</SRS>' +
+' <SRS>EPSG:2955</SRS>' +
+' <SRS>EPSG:2956</SRS>' +
+' <SRS>EPSG:2957</SRS>' +
+' <SRS>EPSG:2958</SRS>' +
+' <SRS>EPSG:2959</SRS>' +
+' <SRS>EPSG:2960</SRS>' +
+' <SRS>EPSG:2961</SRS>' +
+' <SRS>EPSG:2962</SRS>' +
+' <SRS>EPSG:2963</SRS>' +
+' <SRS>EPSG:2964</SRS>' +
+' <SRS>EPSG:2965</SRS>' +
+' <SRS>EPSG:2966</SRS>' +
+' <SRS>EPSG:2967</SRS>' +
+' <SRS>EPSG:2968</SRS>' +
+' <SRS>EPSG:2969</SRS>' +
+' <SRS>EPSG:2970</SRS>' +
+' <SRS>EPSG:2971</SRS>' +
+' <SRS>EPSG:2972</SRS>' +
+' <SRS>EPSG:2973</SRS>' +
+' <SRS>EPSG:2975</SRS>' +
+' <SRS>EPSG:2976</SRS>' +
+' <SRS>EPSG:2977</SRS>' +
+' <SRS>EPSG:2978</SRS>' +
+' <SRS>EPSG:2979</SRS>' +
+' <SRS>EPSG:2980</SRS>' +
+' <SRS>EPSG:2981</SRS>' +
+' <SRS>EPSG:2982</SRS>' +
+' <SRS>EPSG:2983</SRS>' +
+' <SRS>EPSG:2984</SRS>' +
+' <SRS>EPSG:2985</SRS>' +
+' <SRS>EPSG:2986</SRS>' +
+' <SRS>EPSG:2987</SRS>' +
+' <SRS>EPSG:2988</SRS>' +
+' <SRS>EPSG:2989</SRS>' +
+' <SRS>EPSG:2990</SRS>' +
+' <SRS>EPSG:2991</SRS>' +
+' <SRS>EPSG:2992</SRS>' +
+' <SRS>EPSG:2993</SRS>' +
+' <SRS>EPSG:2994</SRS>' +
+' <SRS>EPSG:2995</SRS>' +
+' <SRS>EPSG:2996</SRS>' +
+' <SRS>EPSG:2997</SRS>' +
+' <SRS>EPSG:2998</SRS>' +
+' <SRS>EPSG:2999</SRS>' +
+' <SRS>EPSG:3000</SRS>' +
+' <SRS>EPSG:3001</SRS>' +
+' <SRS>EPSG:3002</SRS>' +
+' <SRS>EPSG:3003</SRS>' +
+' <SRS>EPSG:3004</SRS>' +
+' <SRS>EPSG:3005</SRS>' +
+' <SRS>EPSG:3006</SRS>' +
+' <SRS>EPSG:3007</SRS>' +
+' <SRS>EPSG:3008</SRS>' +
+' <SRS>EPSG:3009</SRS>' +
+' <SRS>EPSG:3010</SRS>' +
+' <SRS>EPSG:3011</SRS>' +
+' <SRS>EPSG:3012</SRS>' +
+' <SRS>EPSG:3013</SRS>' +
+' <SRS>EPSG:3014</SRS>' +
+' <SRS>EPSG:3015</SRS>' +
+' <SRS>EPSG:3016</SRS>' +
+' <SRS>EPSG:3017</SRS>' +
+' <SRS>EPSG:3018</SRS>' +
+' <SRS>EPSG:3019</SRS>' +
+' <SRS>EPSG:3020</SRS>' +
+' <SRS>EPSG:3021</SRS>' +
+' <SRS>EPSG:3022</SRS>' +
+' <SRS>EPSG:3023</SRS>' +
+' <SRS>EPSG:3024</SRS>' +
+' <SRS>EPSG:3025</SRS>' +
+' <SRS>EPSG:3026</SRS>' +
+' <SRS>EPSG:3027</SRS>' +
+' <SRS>EPSG:3028</SRS>' +
+' <SRS>EPSG:3029</SRS>' +
+' <SRS>EPSG:3030</SRS>' +
+' <SRS>EPSG:3031</SRS>' +
+' <SRS>EPSG:3032</SRS>' +
+' <SRS>EPSG:3033</SRS>' +
+' <SRS>EPSG:3034</SRS>' +
+' <SRS>EPSG:3035</SRS>' +
+' <SRS>EPSG:3036</SRS>' +
+' <SRS>EPSG:3037</SRS>' +
+' <SRS>EPSG:3038</SRS>' +
+' <SRS>EPSG:3039</SRS>' +
+' <SRS>EPSG:3040</SRS>' +
+' <SRS>EPSG:3041</SRS>' +
+' <SRS>EPSG:3042</SRS>' +
+' <SRS>EPSG:3043</SRS>' +
+' <SRS>EPSG:3044</SRS>' +
+' <SRS>EPSG:3045</SRS>' +
+' <SRS>EPSG:3046</SRS>' +
+' <SRS>EPSG:3047</SRS>' +
+' <SRS>EPSG:3048</SRS>' +
+' <SRS>EPSG:3049</SRS>' +
+' <SRS>EPSG:3050</SRS>' +
+' <SRS>EPSG:3051</SRS>' +
+' <SRS>EPSG:3052</SRS>' +
+' <SRS>EPSG:3053</SRS>' +
+' <SRS>EPSG:3054</SRS>' +
+' <SRS>EPSG:3055</SRS>' +
+' <SRS>EPSG:3056</SRS>' +
+' <SRS>EPSG:3057</SRS>' +
+' <SRS>EPSG:3058</SRS>' +
+' <SRS>EPSG:3059</SRS>' +
+' <SRS>EPSG:3060</SRS>' +
+' <SRS>EPSG:3061</SRS>' +
+' <SRS>EPSG:3062</SRS>' +
+' <SRS>EPSG:3063</SRS>' +
+' <SRS>EPSG:3064</SRS>' +
+' <SRS>EPSG:3065</SRS>' +
+' <SRS>EPSG:3066</SRS>' +
+' <SRS>EPSG:3067</SRS>' +
+' <SRS>EPSG:3068</SRS>' +
+' <SRS>EPSG:3069</SRS>' +
+' <SRS>EPSG:3070</SRS>' +
+' <SRS>EPSG:3071</SRS>' +
+' <SRS>EPSG:3072</SRS>' +
+' <SRS>EPSG:3073</SRS>' +
+' <SRS>EPSG:3074</SRS>' +
+' <SRS>EPSG:3075</SRS>' +
+' <SRS>EPSG:3076</SRS>' +
+' <SRS>EPSG:3077</SRS>' +
+' <SRS>EPSG:3078</SRS>' +
+' <SRS>EPSG:3079</SRS>' +
+' <SRS>EPSG:3080</SRS>' +
+' <SRS>EPSG:3081</SRS>' +
+' <SRS>EPSG:3082</SRS>' +
+' <SRS>EPSG:3083</SRS>' +
+' <SRS>EPSG:3084</SRS>' +
+' <SRS>EPSG:3085</SRS>' +
+' <SRS>EPSG:3086</SRS>' +
+' <SRS>EPSG:3087</SRS>' +
+' <SRS>EPSG:3088</SRS>' +
+' <SRS>EPSG:3089</SRS>' +
+' <SRS>EPSG:3090</SRS>' +
+' <SRS>EPSG:3091</SRS>' +
+' <SRS>EPSG:3092</SRS>' +
+' <SRS>EPSG:3093</SRS>' +
+' <SRS>EPSG:3094</SRS>' +
+' <SRS>EPSG:3095</SRS>' +
+' <SRS>EPSG:3096</SRS>' +
+' <SRS>EPSG:3097</SRS>' +
+' <SRS>EPSG:3098</SRS>' +
+' <SRS>EPSG:3099</SRS>' +
+' <SRS>EPSG:3100</SRS>' +
+' <SRS>EPSG:3101</SRS>' +
+' <SRS>EPSG:3102</SRS>' +
+' <SRS>EPSG:3103</SRS>' +
+' <SRS>EPSG:3104</SRS>' +
+' <SRS>EPSG:3105</SRS>' +
+' <SRS>EPSG:3106</SRS>' +
+' <SRS>EPSG:3107</SRS>' +
+' <SRS>EPSG:3108</SRS>' +
+' <SRS>EPSG:3109</SRS>' +
+' <SRS>EPSG:3110</SRS>' +
+' <SRS>EPSG:3111</SRS>' +
+' <SRS>EPSG:3112</SRS>' +
+' <SRS>EPSG:3113</SRS>' +
+' <SRS>EPSG:3114</SRS>' +
+' <SRS>EPSG:3115</SRS>' +
+' <SRS>EPSG:3116</SRS>' +
+' <SRS>EPSG:3117</SRS>' +
+' <SRS>EPSG:3118</SRS>' +
+' <SRS>EPSG:3119</SRS>' +
+' <SRS>EPSG:3120</SRS>' +
+' <SRS>EPSG:3121</SRS>' +
+' <SRS>EPSG:3122</SRS>' +
+' <SRS>EPSG:3123</SRS>' +
+' <SRS>EPSG:3124</SRS>' +
+' <SRS>EPSG:3125</SRS>' +
+' <SRS>EPSG:3126</SRS>' +
+' <SRS>EPSG:3127</SRS>' +
+' <SRS>EPSG:3128</SRS>' +
+' <SRS>EPSG:3129</SRS>' +
+' <SRS>EPSG:3130</SRS>' +
+' <SRS>EPSG:3131</SRS>' +
+' <SRS>EPSG:3132</SRS>' +
+' <SRS>EPSG:3133</SRS>' +
+' <SRS>EPSG:3134</SRS>' +
+' <SRS>EPSG:3135</SRS>' +
+' <SRS>EPSG:3136</SRS>' +
+' <SRS>EPSG:3137</SRS>' +
+' <SRS>EPSG:3138</SRS>' +
+' <SRS>EPSG:3139</SRS>' +
+' <SRS>EPSG:3140</SRS>' +
+' <SRS>EPSG:3141</SRS>' +
+' <SRS>EPSG:3142</SRS>' +
+' <SRS>EPSG:3143</SRS>' +
+' <SRS>EPSG:3144</SRS>' +
+' <SRS>EPSG:3145</SRS>' +
+' <SRS>EPSG:3146</SRS>' +
+' <SRS>EPSG:3147</SRS>' +
+' <SRS>EPSG:3148</SRS>' +
+' <SRS>EPSG:3149</SRS>' +
+' <SRS>EPSG:3150</SRS>' +
+' <SRS>EPSG:3151</SRS>' +
+' <SRS>EPSG:3152</SRS>' +
+' <SRS>EPSG:3153</SRS>' +
+' <SRS>EPSG:3154</SRS>' +
+' <SRS>EPSG:3155</SRS>' +
+' <SRS>EPSG:3156</SRS>' +
+' <SRS>EPSG:3157</SRS>' +
+' <SRS>EPSG:3158</SRS>' +
+' <SRS>EPSG:3159</SRS>' +
+' <SRS>EPSG:3160</SRS>' +
+' <SRS>EPSG:3161</SRS>' +
+' <SRS>EPSG:3162</SRS>' +
+' <SRS>EPSG:3163</SRS>' +
+' <SRS>EPSG:3164</SRS>' +
+' <SRS>EPSG:3165</SRS>' +
+' <SRS>EPSG:3166</SRS>' +
+' <SRS>EPSG:3167</SRS>' +
+' <SRS>EPSG:3168</SRS>' +
+' <SRS>EPSG:3169</SRS>' +
+' <SRS>EPSG:3170</SRS>' +
+' <SRS>EPSG:3171</SRS>' +
+' <SRS>EPSG:3172</SRS>' +
+' <SRS>EPSG:3173</SRS>' +
+' <SRS>EPSG:3174</SRS>' +
+' <SRS>EPSG:3175</SRS>' +
+' <SRS>EPSG:3176</SRS>' +
+' <SRS>EPSG:3177</SRS>' +
+' <SRS>EPSG:3178</SRS>' +
+' <SRS>EPSG:3179</SRS>' +
+' <SRS>EPSG:3180</SRS>' +
+' <SRS>EPSG:3181</SRS>' +
+' <SRS>EPSG:3182</SRS>' +
+' <SRS>EPSG:3183</SRS>' +
+' <SRS>EPSG:3184</SRS>' +
+' <SRS>EPSG:3185</SRS>' +
+' <SRS>EPSG:3186</SRS>' +
+' <SRS>EPSG:3187</SRS>' +
+' <SRS>EPSG:3188</SRS>' +
+' <SRS>EPSG:3189</SRS>' +
+' <SRS>EPSG:3190</SRS>' +
+' <SRS>EPSG:3191</SRS>' +
+' <SRS>EPSG:3192</SRS>' +
+' <SRS>EPSG:3193</SRS>' +
+' <SRS>EPSG:3194</SRS>' +
+' <SRS>EPSG:3195</SRS>' +
+' <SRS>EPSG:3196</SRS>' +
+' <SRS>EPSG:3197</SRS>' +
+' <SRS>EPSG:3198</SRS>' +
+' <SRS>EPSG:3199</SRS>' +
+' <SRS>EPSG:3200</SRS>' +
+' <SRS>EPSG:3201</SRS>' +
+' <SRS>EPSG:3202</SRS>' +
+' <SRS>EPSG:3203</SRS>' +
+' <SRS>EPSG:3204</SRS>' +
+' <SRS>EPSG:3205</SRS>' +
+' <SRS>EPSG:3206</SRS>' +
+' <SRS>EPSG:3207</SRS>' +
+' <SRS>EPSG:3208</SRS>' +
+' <SRS>EPSG:3209</SRS>' +
+' <SRS>EPSG:3210</SRS>' +
+' <SRS>EPSG:3211</SRS>' +
+' <SRS>EPSG:3212</SRS>' +
+' <SRS>EPSG:3213</SRS>' +
+' <SRS>EPSG:3214</SRS>' +
+' <SRS>EPSG:3215</SRS>' +
+' <SRS>EPSG:3216</SRS>' +
+' <SRS>EPSG:3217</SRS>' +
+' <SRS>EPSG:3218</SRS>' +
+' <SRS>EPSG:3219</SRS>' +
+' <SRS>EPSG:3220</SRS>' +
+' <SRS>EPSG:3221</SRS>' +
+' <SRS>EPSG:3222</SRS>' +
+' <SRS>EPSG:3223</SRS>' +
+' <SRS>EPSG:3224</SRS>' +
+' <SRS>EPSG:3225</SRS>' +
+' <SRS>EPSG:3226</SRS>' +
+' <SRS>EPSG:3227</SRS>' +
+' <SRS>EPSG:3228</SRS>' +
+' <SRS>EPSG:3229</SRS>' +
+' <SRS>EPSG:3230</SRS>' +
+' <SRS>EPSG:3231</SRS>' +
+' <SRS>EPSG:3232</SRS>' +
+' <SRS>EPSG:3233</SRS>' +
+' <SRS>EPSG:3234</SRS>' +
+' <SRS>EPSG:3235</SRS>' +
+' <SRS>EPSG:3236</SRS>' +
+' <SRS>EPSG:3237</SRS>' +
+' <SRS>EPSG:3238</SRS>' +
+' <SRS>EPSG:3239</SRS>' +
+' <SRS>EPSG:3240</SRS>' +
+' <SRS>EPSG:3241</SRS>' +
+' <SRS>EPSG:3242</SRS>' +
+' <SRS>EPSG:3243</SRS>' +
+' <SRS>EPSG:3244</SRS>' +
+' <SRS>EPSG:3245</SRS>' +
+' <SRS>EPSG:3246</SRS>' +
+' <SRS>EPSG:3247</SRS>' +
+' <SRS>EPSG:3248</SRS>' +
+' <SRS>EPSG:3249</SRS>' +
+' <SRS>EPSG:3250</SRS>' +
+' <SRS>EPSG:3251</SRS>' +
+' <SRS>EPSG:3252</SRS>' +
+' <SRS>EPSG:3253</SRS>' +
+' <SRS>EPSG:3254</SRS>' +
+' <SRS>EPSG:3255</SRS>' +
+' <SRS>EPSG:3256</SRS>' +
+' <SRS>EPSG:3257</SRS>' +
+' <SRS>EPSG:3258</SRS>' +
+' <SRS>EPSG:3259</SRS>' +
+' <SRS>EPSG:3260</SRS>' +
+' <SRS>EPSG:3261</SRS>' +
+' <SRS>EPSG:3262</SRS>' +
+' <SRS>EPSG:3263</SRS>' +
+' <SRS>EPSG:3264</SRS>' +
+' <SRS>EPSG:3265</SRS>' +
+' <SRS>EPSG:3266</SRS>' +
+' <SRS>EPSG:3267</SRS>' +
+' <SRS>EPSG:3268</SRS>' +
+' <SRS>EPSG:3269</SRS>' +
+' <SRS>EPSG:3270</SRS>' +
+' <SRS>EPSG:3271</SRS>' +
+' <SRS>EPSG:3272</SRS>' +
+' <SRS>EPSG:3273</SRS>' +
+' <SRS>EPSG:3274</SRS>' +
+' <SRS>EPSG:3275</SRS>' +
+' <SRS>EPSG:3276</SRS>' +
+' <SRS>EPSG:3277</SRS>' +
+' <SRS>EPSG:3278</SRS>' +
+' <SRS>EPSG:3279</SRS>' +
+' <SRS>EPSG:3280</SRS>' +
+' <SRS>EPSG:3281</SRS>' +
+' <SRS>EPSG:3282</SRS>' +
+' <SRS>EPSG:3283</SRS>' +
+' <SRS>EPSG:3284</SRS>' +
+' <SRS>EPSG:3285</SRS>' +
+' <SRS>EPSG:3286</SRS>' +
+' <SRS>EPSG:3287</SRS>' +
+' <SRS>EPSG:3288</SRS>' +
+' <SRS>EPSG:3289</SRS>' +
+' <SRS>EPSG:3290</SRS>' +
+' <SRS>EPSG:3291</SRS>' +
+' <SRS>EPSG:3292</SRS>' +
+' <SRS>EPSG:3293</SRS>' +
+' <SRS>EPSG:3294</SRS>' +
+' <SRS>EPSG:3295</SRS>' +
+' <SRS>EPSG:3296</SRS>' +
+' <SRS>EPSG:3297</SRS>' +
+' <SRS>EPSG:3298</SRS>' +
+' <SRS>EPSG:3299</SRS>' +
+' <SRS>EPSG:3300</SRS>' +
+' <SRS>EPSG:3301</SRS>' +
+' <SRS>EPSG:3302</SRS>' +
+' <SRS>EPSG:3303</SRS>' +
+' <SRS>EPSG:3304</SRS>' +
+' <SRS>EPSG:3305</SRS>' +
+' <SRS>EPSG:3306</SRS>' +
+' <SRS>EPSG:3307</SRS>' +
+' <SRS>EPSG:3308</SRS>' +
+' <SRS>EPSG:3309</SRS>' +
+' <SRS>EPSG:3310</SRS>' +
+' <SRS>EPSG:3311</SRS>' +
+' <SRS>EPSG:3312</SRS>' +
+' <SRS>EPSG:3313</SRS>' +
+' <SRS>EPSG:3314</SRS>' +
+' <SRS>EPSG:3315</SRS>' +
+' <SRS>EPSG:3316</SRS>' +
+' <SRS>EPSG:3317</SRS>' +
+' <SRS>EPSG:3318</SRS>' +
+' <SRS>EPSG:3319</SRS>' +
+' <SRS>EPSG:3320</SRS>' +
+' <SRS>EPSG:3321</SRS>' +
+' <SRS>EPSG:3322</SRS>' +
+' <SRS>EPSG:3323</SRS>' +
+' <SRS>EPSG:3324</SRS>' +
+' <SRS>EPSG:3325</SRS>' +
+' <SRS>EPSG:3326</SRS>' +
+' <SRS>EPSG:3327</SRS>' +
+' <SRS>EPSG:3328</SRS>' +
+' <SRS>EPSG:3329</SRS>' +
+' <SRS>EPSG:3330</SRS>' +
+' <SRS>EPSG:3331</SRS>' +
+' <SRS>EPSG:3332</SRS>' +
+' <SRS>EPSG:3333</SRS>' +
+' <SRS>EPSG:3334</SRS>' +
+' <SRS>EPSG:3335</SRS>' +
+' <SRS>EPSG:3336</SRS>' +
+' <SRS>EPSG:3337</SRS>' +
+' <SRS>EPSG:3338</SRS>' +
+' <SRS>EPSG:3339</SRS>' +
+' <SRS>EPSG:3340</SRS>' +
+' <SRS>EPSG:3341</SRS>' +
+' <SRS>EPSG:3342</SRS>' +
+' <SRS>EPSG:3343</SRS>' +
+' <SRS>EPSG:3344</SRS>' +
+' <SRS>EPSG:3345</SRS>' +
+' <SRS>EPSG:3346</SRS>' +
+' <SRS>EPSG:3347</SRS>' +
+' <SRS>EPSG:3348</SRS>' +
+' <SRS>EPSG:3349</SRS>' +
+' <SRS>EPSG:3350</SRS>' +
+' <SRS>EPSG:3351</SRS>' +
+' <SRS>EPSG:3352</SRS>' +
+' <SRS>EPSG:3353</SRS>' +
+' <SRS>EPSG:3354</SRS>' +
+' <SRS>EPSG:3355</SRS>' +
+' <SRS>EPSG:3356</SRS>' +
+' <SRS>EPSG:3357</SRS>' +
+' <SRS>EPSG:3358</SRS>' +
+' <SRS>EPSG:3359</SRS>' +
+' <SRS>EPSG:3360</SRS>' +
+' <SRS>EPSG:3361</SRS>' +
+' <SRS>EPSG:3362</SRS>' +
+' <SRS>EPSG:3363</SRS>' +
+' <SRS>EPSG:3364</SRS>' +
+' <SRS>EPSG:3365</SRS>' +
+' <SRS>EPSG:3366</SRS>' +
+' <SRS>EPSG:3367</SRS>' +
+' <SRS>EPSG:3368</SRS>' +
+' <SRS>EPSG:3369</SRS>' +
+' <SRS>EPSG:3370</SRS>' +
+' <SRS>EPSG:3371</SRS>' +
+' <SRS>EPSG:3372</SRS>' +
+' <SRS>EPSG:3373</SRS>' +
+' <SRS>EPSG:3374</SRS>' +
+' <SRS>EPSG:3375</SRS>' +
+' <SRS>EPSG:3376</SRS>' +
+' <SRS>EPSG:3377</SRS>' +
+' <SRS>EPSG:3378</SRS>' +
+' <SRS>EPSG:3379</SRS>' +
+' <SRS>EPSG:3380</SRS>' +
+' <SRS>EPSG:3381</SRS>' +
+' <SRS>EPSG:3382</SRS>' +
+' <SRS>EPSG:3383</SRS>' +
+' <SRS>EPSG:3384</SRS>' +
+' <SRS>EPSG:3385</SRS>' +
+' <SRS>EPSG:3386</SRS>' +
+' <SRS>EPSG:3387</SRS>' +
+' <SRS>EPSG:3388</SRS>' +
+' <SRS>EPSG:3389</SRS>' +
+' <SRS>EPSG:3390</SRS>' +
+' <SRS>EPSG:3391</SRS>' +
+' <SRS>EPSG:3392</SRS>' +
+' <SRS>EPSG:3393</SRS>' +
+' <SRS>EPSG:3394</SRS>' +
+' <SRS>EPSG:3395</SRS>' +
+' <SRS>EPSG:3396</SRS>' +
+' <SRS>EPSG:3397</SRS>' +
+' <SRS>EPSG:3398</SRS>' +
+' <SRS>EPSG:3399</SRS>' +
+' <SRS>EPSG:3400</SRS>' +
+' <SRS>EPSG:3401</SRS>' +
+' <SRS>EPSG:3402</SRS>' +
+' <SRS>EPSG:3403</SRS>' +
+' <SRS>EPSG:3404</SRS>' +
+' <SRS>EPSG:3405</SRS>' +
+' <SRS>EPSG:3406</SRS>' +
+' <SRS>EPSG:3407</SRS>' +
+' <SRS>EPSG:3408</SRS>' +
+' <SRS>EPSG:3409</SRS>' +
+' <SRS>EPSG:3410</SRS>' +
+' <SRS>EPSG:3411</SRS>' +
+' <SRS>EPSG:3412</SRS>' +
+' <SRS>EPSG:3413</SRS>' +
+' <SRS>EPSG:3414</SRS>' +
+' <SRS>EPSG:3415</SRS>' +
+' <SRS>EPSG:3416</SRS>' +
+' <SRS>EPSG:3417</SRS>' +
+' <SRS>EPSG:3418</SRS>' +
+' <SRS>EPSG:3419</SRS>' +
+' <SRS>EPSG:3420</SRS>' +
+' <SRS>EPSG:3421</SRS>' +
+' <SRS>EPSG:3422</SRS>' +
+' <SRS>EPSG:3423</SRS>' +
+' <SRS>EPSG:3424</SRS>' +
+' <SRS>EPSG:3425</SRS>' +
+' <SRS>EPSG:3426</SRS>' +
+' <SRS>EPSG:3427</SRS>' +
+' <SRS>EPSG:3428</SRS>' +
+' <SRS>EPSG:3429</SRS>' +
+' <SRS>EPSG:3430</SRS>' +
+' <SRS>EPSG:3431</SRS>' +
+' <SRS>EPSG:3432</SRS>' +
+' <SRS>EPSG:3433</SRS>' +
+' <SRS>EPSG:3434</SRS>' +
+' <SRS>EPSG:3435</SRS>' +
+' <SRS>EPSG:3436</SRS>' +
+' <SRS>EPSG:3437</SRS>' +
+' <SRS>EPSG:3438</SRS>' +
+' <SRS>EPSG:3439</SRS>' +
+' <SRS>EPSG:3440</SRS>' +
+' <SRS>EPSG:3441</SRS>' +
+' <SRS>EPSG:3442</SRS>' +
+' <SRS>EPSG:3443</SRS>' +
+' <SRS>EPSG:3444</SRS>' +
+' <SRS>EPSG:3445</SRS>' +
+' <SRS>EPSG:3446</SRS>' +
+' <SRS>EPSG:3447</SRS>' +
+' <SRS>EPSG:3448</SRS>' +
+' <SRS>EPSG:3449</SRS>' +
+' <SRS>EPSG:3450</SRS>' +
+' <SRS>EPSG:3451</SRS>' +
+' <SRS>EPSG:3452</SRS>' +
+' <SRS>EPSG:3453</SRS>' +
+' <SRS>EPSG:3454</SRS>' +
+' <SRS>EPSG:3455</SRS>' +
+' <SRS>EPSG:3456</SRS>' +
+' <SRS>EPSG:3457</SRS>' +
+' <SRS>EPSG:3458</SRS>' +
+' <SRS>EPSG:3459</SRS>' +
+' <SRS>EPSG:3460</SRS>' +
+' <SRS>EPSG:3461</SRS>' +
+' <SRS>EPSG:3462</SRS>' +
+' <SRS>EPSG:3463</SRS>' +
+' <SRS>EPSG:3464</SRS>' +
+' <SRS>EPSG:3560</SRS>' +
+' <SRS>EPSG:3561</SRS>' +
+' <SRS>EPSG:3562</SRS>' +
+' <SRS>EPSG:3563</SRS>' +
+' <SRS>EPSG:3564</SRS>' +
+' <SRS>EPSG:3565</SRS>' +
+' <SRS>EPSG:3566</SRS>' +
+' <SRS>EPSG:3567</SRS>' +
+' <SRS>EPSG:3568</SRS>' +
+' <SRS>EPSG:3569</SRS>' +
+' <SRS>EPSG:3570</SRS>' +
+' <SRS>EPSG:3571</SRS>' +
+' <SRS>EPSG:3572</SRS>' +
+' <SRS>EPSG:3573</SRS>' +
+' <SRS>EPSG:3574</SRS>' +
+' <SRS>EPSG:3575</SRS>' +
+' <SRS>EPSG:3576</SRS>' +
+' <SRS>EPSG:3577</SRS>' +
+' <SRS>EPSG:3920</SRS>' +
+' <SRS>EPSG:3991</SRS>' +
+' <SRS>EPSG:3992</SRS>' +
+' <SRS>EPSG:3993</SRS>' +
+' <SRS>EPSG:4001</SRS>' +
+' <SRS>EPSG:4002</SRS>' +
+' <SRS>EPSG:4003</SRS>' +
+' <SRS>EPSG:4004</SRS>' +
+' <SRS>EPSG:4005</SRS>' +
+' <SRS>EPSG:4006</SRS>' +
+' <SRS>EPSG:4007</SRS>' +
+' <SRS>EPSG:4008</SRS>' +
+' <SRS>EPSG:4009</SRS>' +
+' <SRS>EPSG:4010</SRS>' +
+' <SRS>EPSG:4011</SRS>' +
+' <SRS>EPSG:4012</SRS>' +
+' <SRS>EPSG:4013</SRS>' +
+' <SRS>EPSG:4014</SRS>' +
+' <SRS>EPSG:4015</SRS>' +
+' <SRS>EPSG:4016</SRS>' +
+' <SRS>EPSG:4018</SRS>' +
+' <SRS>EPSG:4019</SRS>' +
+' <SRS>EPSG:4020</SRS>' +
+' <SRS>EPSG:4021</SRS>' +
+' <SRS>EPSG:4022</SRS>' +
+' <SRS>EPSG:4024</SRS>' +
+' <SRS>EPSG:4025</SRS>' +
+' <SRS>EPSG:4027</SRS>' +
+' <SRS>EPSG:4028</SRS>' +
+' <SRS>EPSG:4029</SRS>' +
+' <SRS>EPSG:4030</SRS>' +
+' <SRS>EPSG:4031</SRS>' +
+' <SRS>EPSG:4032</SRS>' +
+' <SRS>EPSG:4033</SRS>' +
+' <SRS>EPSG:4034</SRS>' +
+' <SRS>EPSG:4035</SRS>' +
+' <SRS>EPSG:4036</SRS>' +
+' <SRS>EPSG:4041</SRS>' +
+' <SRS>EPSG:4042</SRS>' +
+' <SRS>EPSG:4043</SRS>' +
+' <SRS>EPSG:4044</SRS>' +
+' <SRS>EPSG:4045</SRS>' +
+' <SRS>EPSG:4047</SRS>' +
+' <SRS>EPSG:4052</SRS>' +
+' <SRS>EPSG:4053</SRS>' +
+' <SRS>EPSG:4054</SRS>' +
+' <SRS>EPSG:4120</SRS>' +
+' <SRS>EPSG:4121</SRS>' +
+' <SRS>EPSG:4122</SRS>' +
+' <SRS>EPSG:4123</SRS>' +
+' <SRS>EPSG:4124</SRS>' +
+' <SRS>EPSG:4125</SRS>' +
+' <SRS>EPSG:4126</SRS>' +
+' <SRS>EPSG:4127</SRS>' +
+' <SRS>EPSG:4128</SRS>' +
+' <SRS>EPSG:4129</SRS>' +
+' <SRS>EPSG:4130</SRS>' +
+' <SRS>EPSG:4131</SRS>' +
+' <SRS>EPSG:4132</SRS>' +
+' <SRS>EPSG:4133</SRS>' +
+' <SRS>EPSG:4134</SRS>' +
+' <SRS>EPSG:4135</SRS>' +
+' <SRS>EPSG:4136</SRS>' +
+' <SRS>EPSG:4137</SRS>' +
+' <SRS>EPSG:4138</SRS>' +
+' <SRS>EPSG:4139</SRS>' +
+' <SRS>EPSG:4140</SRS>' +
+' <SRS>EPSG:4141</SRS>' +
+' <SRS>EPSG:4142</SRS>' +
+' <SRS>EPSG:4143</SRS>' +
+' <SRS>EPSG:4144</SRS>' +
+' <SRS>EPSG:4145</SRS>' +
+' <SRS>EPSG:4146</SRS>' +
+' <SRS>EPSG:4147</SRS>' +
+' <SRS>EPSG:4148</SRS>' +
+' <SRS>EPSG:4149</SRS>' +
+' <SRS>EPSG:4150</SRS>' +
+' <SRS>EPSG:4151</SRS>' +
+' <SRS>EPSG:4152</SRS>' +
+' <SRS>EPSG:4153</SRS>' +
+' <SRS>EPSG:4154</SRS>' +
+' <SRS>EPSG:4155</SRS>' +
+' <SRS>EPSG:4156</SRS>' +
+' <SRS>EPSG:4157</SRS>' +
+' <SRS>EPSG:4158</SRS>' +
+' <SRS>EPSG:4159</SRS>' +
+' <SRS>EPSG:4160</SRS>' +
+' <SRS>EPSG:4161</SRS>' +
+' <SRS>EPSG:4162</SRS>' +
+' <SRS>EPSG:4163</SRS>' +
+' <SRS>EPSG:4164</SRS>' +
+' <SRS>EPSG:4165</SRS>' +
+' <SRS>EPSG:4166</SRS>' +
+' <SRS>EPSG:4167</SRS>' +
+' <SRS>EPSG:4168</SRS>' +
+' <SRS>EPSG:4169</SRS>' +
+' <SRS>EPSG:4170</SRS>' +
+' <SRS>EPSG:4171</SRS>' +
+' <SRS>EPSG:4172</SRS>' +
+' <SRS>EPSG:4173</SRS>' +
+' <SRS>EPSG:4174</SRS>' +
+' <SRS>EPSG:4175</SRS>' +
+' <SRS>EPSG:4176</SRS>' +
+' <SRS>EPSG:4178</SRS>' +
+' <SRS>EPSG:4179</SRS>' +
+' <SRS>EPSG:4180</SRS>' +
+' <SRS>EPSG:4181</SRS>' +
+' <SRS>EPSG:4182</SRS>' +
+' <SRS>EPSG:4183</SRS>' +
+' <SRS>EPSG:4184</SRS>' +
+' <SRS>EPSG:4185</SRS>' +
+' <SRS>EPSG:4188</SRS>' +
+' <SRS>EPSG:4189</SRS>' +
+' <SRS>EPSG:4190</SRS>' +
+' <SRS>EPSG:4191</SRS>' +
+' <SRS>EPSG:4192</SRS>' +
+' <SRS>EPSG:4193</SRS>' +
+' <SRS>EPSG:4194</SRS>' +
+' <SRS>EPSG:4195</SRS>' +
+' <SRS>EPSG:4196</SRS>' +
+' <SRS>EPSG:4197</SRS>' +
+' <SRS>EPSG:4198</SRS>' +
+' <SRS>EPSG:4199</SRS>' +
+' <SRS>EPSG:4200</SRS>' +
+' <SRS>EPSG:4201</SRS>' +
+' <SRS>EPSG:4202</SRS>' +
+' <SRS>EPSG:4203</SRS>' +
+' <SRS>EPSG:4204</SRS>' +
+' <SRS>EPSG:4205</SRS>' +
+' <SRS>EPSG:4206</SRS>' +
+' <SRS>EPSG:4207</SRS>' +
+' <SRS>EPSG:4208</SRS>' +
+' <SRS>EPSG:4209</SRS>' +
+' <SRS>EPSG:4210</SRS>' +
+' <SRS>EPSG:4211</SRS>' +
+' <SRS>EPSG:4212</SRS>' +
+' <SRS>EPSG:4213</SRS>' +
+' <SRS>EPSG:4214</SRS>' +
+' <SRS>EPSG:4215</SRS>' +
+' <SRS>EPSG:4216</SRS>' +
+' <SRS>EPSG:4218</SRS>' +
+' <SRS>EPSG:4219</SRS>' +
+' <SRS>EPSG:4220</SRS>' +
+' <SRS>EPSG:4221</SRS>' +
+' <SRS>EPSG:4222</SRS>' +
+' <SRS>EPSG:4223</SRS>' +
+' <SRS>EPSG:4224</SRS>' +
+' <SRS>EPSG:4225</SRS>' +
+' <SRS>EPSG:4226</SRS>' +
+' <SRS>EPSG:4227</SRS>' +
+' <SRS>EPSG:4228</SRS>' +
+' <SRS>EPSG:4229</SRS>' +
+' <SRS>EPSG:4230</SRS>' +
+' <SRS>EPSG:4231</SRS>' +
+' <SRS>EPSG:4232</SRS>' +
+' <SRS>EPSG:4233</SRS>' +
+' <SRS>EPSG:4234</SRS>' +
+' <SRS>EPSG:4235</SRS>' +
+' <SRS>EPSG:4236</SRS>' +
+' <SRS>EPSG:4237</SRS>' +
+' <SRS>EPSG:4238</SRS>' +
+' <SRS>EPSG:4239</SRS>' +
+' <SRS>EPSG:4240</SRS>' +
+' <SRS>EPSG:4241</SRS>' +
+' <SRS>EPSG:4242</SRS>' +
+' <SRS>EPSG:4243</SRS>' +
+' <SRS>EPSG:4244</SRS>' +
+' <SRS>EPSG:4245</SRS>' +
+' <SRS>EPSG:4246</SRS>' +
+' <SRS>EPSG:4247</SRS>' +
+' <SRS>EPSG:4248</SRS>' +
+' <SRS>EPSG:4249</SRS>' +
+' <SRS>EPSG:4250</SRS>' +
+' <SRS>EPSG:4251</SRS>' +
+' <SRS>EPSG:4252</SRS>' +
+' <SRS>EPSG:4253</SRS>' +
+' <SRS>EPSG:4254</SRS>' +
+' <SRS>EPSG:4255</SRS>' +
+' <SRS>EPSG:4256</SRS>' +
+' <SRS>EPSG:4257</SRS>' +
+' <SRS>EPSG:4258</SRS>' +
+' <SRS>EPSG:4259</SRS>' +
+' <SRS>EPSG:4260</SRS>' +
+' <SRS>EPSG:4261</SRS>' +
+' <SRS>EPSG:4262</SRS>' +
+' <SRS>EPSG:4263</SRS>' +
+' <SRS>EPSG:4264</SRS>' +
+' <SRS>EPSG:4265</SRS>' +
+' <SRS>EPSG:4266</SRS>' +
+' <SRS>EPSG:4267</SRS>' +
+' <SRS>EPSG:4268</SRS>' +
+' <SRS>EPSG:4269</SRS>' +
+' <SRS>EPSG:4270</SRS>' +
+' <SRS>EPSG:4271</SRS>' +
+' <SRS>EPSG:4272</SRS>' +
+' <SRS>EPSG:4273</SRS>' +
+' <SRS>EPSG:4274</SRS>' +
+' <SRS>EPSG:4275</SRS>' +
+' <SRS>EPSG:4276</SRS>' +
+' <SRS>EPSG:4277</SRS>' +
+' <SRS>EPSG:4278</SRS>' +
+' <SRS>EPSG:4279</SRS>' +
+' <SRS>EPSG:4280</SRS>' +
+' <SRS>EPSG:4281</SRS>' +
+' <SRS>EPSG:4282</SRS>' +
+' <SRS>EPSG:4283</SRS>' +
+' <SRS>EPSG:4284</SRS>' +
+' <SRS>EPSG:4285</SRS>' +
+' <SRS>EPSG:4286</SRS>' +
+' <SRS>EPSG:4287</SRS>' +
+' <SRS>EPSG:4288</SRS>' +
+' <SRS>EPSG:4289</SRS>' +
+' <SRS>EPSG:4291</SRS>' +
+' <SRS>EPSG:4292</SRS>' +
+' <SRS>EPSG:4293</SRS>' +
+' <SRS>EPSG:4294</SRS>' +
+' <SRS>EPSG:4295</SRS>' +
+' <SRS>EPSG:4296</SRS>' +
+' <SRS>EPSG:4297</SRS>' +
+' <SRS>EPSG:4298</SRS>' +
+' <SRS>EPSG:4299</SRS>' +
+' <SRS>EPSG:4300</SRS>' +
+' <SRS>EPSG:4301</SRS>' +
+' <SRS>EPSG:4302</SRS>' +
+' <SRS>EPSG:4303</SRS>' +
+' <SRS>EPSG:4304</SRS>' +
+' <SRS>EPSG:4306</SRS>' +
+' <SRS>EPSG:4307</SRS>' +
+' <SRS>EPSG:4308</SRS>' +
+' <SRS>EPSG:4309</SRS>' +
+' <SRS>EPSG:4310</SRS>' +
+' <SRS>EPSG:4311</SRS>' +
+' <SRS>EPSG:4312</SRS>' +
+' <SRS>EPSG:4313</SRS>' +
+' <SRS>EPSG:4314</SRS>' +
+' <SRS>EPSG:4315</SRS>' +
+' <SRS>EPSG:4316</SRS>' +
+' <SRS>EPSG:4317</SRS>' +
+' <SRS>EPSG:4318</SRS>' +
+' <SRS>EPSG:4319</SRS>' +
+' <SRS>EPSG:4322</SRS>' +
+' <SRS>EPSG:4324</SRS>' +
+' <SRS>EPSG:4326</SRS>' +
+' <SRS>EPSG:4327</SRS>' +
+' <SRS>EPSG:4328</SRS>' +
+' <SRS>EPSG:4329</SRS>' +
+' <SRS>EPSG:4330</SRS>' +
+' <SRS>EPSG:4331</SRS>' +
+' <SRS>EPSG:4332</SRS>' +
+' <SRS>EPSG:4333</SRS>' +
+' <SRS>EPSG:4334</SRS>' +
+' <SRS>EPSG:4335</SRS>' +
+' <SRS>EPSG:4336</SRS>' +
+' <SRS>EPSG:4337</SRS>' +
+' <SRS>EPSG:4338</SRS>' +
+' <SRS>EPSG:4339</SRS>' +
+' <SRS>EPSG:4340</SRS>' +
+' <SRS>EPSG:4341</SRS>' +
+' <SRS>EPSG:4342</SRS>' +
+' <SRS>EPSG:4343</SRS>' +
+' <SRS>EPSG:4344</SRS>' +
+' <SRS>EPSG:4345</SRS>' +
+' <SRS>EPSG:4346</SRS>' +
+' <SRS>EPSG:4347</SRS>' +
+' <SRS>EPSG:4348</SRS>' +
+' <SRS>EPSG:4349</SRS>' +
+' <SRS>EPSG:4350</SRS>' +
+' <SRS>EPSG:4351</SRS>' +
+' <SRS>EPSG:4352</SRS>' +
+' <SRS>EPSG:4353</SRS>' +
+' <SRS>EPSG:4354</SRS>' +
+' <SRS>EPSG:4355</SRS>' +
+' <SRS>EPSG:4356</SRS>' +
+' <SRS>EPSG:4357</SRS>' +
+' <SRS>EPSG:4358</SRS>' +
+' <SRS>EPSG:4359</SRS>' +
+' <SRS>EPSG:4360</SRS>' +
+' <SRS>EPSG:4361</SRS>' +
+' <SRS>EPSG:4362</SRS>' +
+' <SRS>EPSG:4363</SRS>' +
+' <SRS>EPSG:4364</SRS>' +
+' <SRS>EPSG:4365</SRS>' +
+' <SRS>EPSG:4366</SRS>' +
+' <SRS>EPSG:4367</SRS>' +
+' <SRS>EPSG:4368</SRS>' +
+' <SRS>EPSG:4369</SRS>' +
+' <SRS>EPSG:4370</SRS>' +
+' <SRS>EPSG:4371</SRS>' +
+' <SRS>EPSG:4372</SRS>' +
+' <SRS>EPSG:4373</SRS>' +
+' <SRS>EPSG:4374</SRS>' +
+' <SRS>EPSG:4375</SRS>' +
+' <SRS>EPSG:4376</SRS>' +
+' <SRS>EPSG:4377</SRS>' +
+' <SRS>EPSG:4378</SRS>' +
+' <SRS>EPSG:4379</SRS>' +
+' <SRS>EPSG:4380</SRS>' +
+' <SRS>EPSG:4381</SRS>' +
+' <SRS>EPSG:4382</SRS>' +
+' <SRS>EPSG:4383</SRS>' +
+' <SRS>EPSG:4384</SRS>' +
+' <SRS>EPSG:4385</SRS>' +
+' <SRS>EPSG:4386</SRS>' +
+' <SRS>EPSG:4387</SRS>' +
+' <SRS>EPSG:4388</SRS>' +
+' <SRS>EPSG:4389</SRS>' +
+' <SRS>EPSG:4600</SRS>' +
+' <SRS>EPSG:4601</SRS>' +
+' <SRS>EPSG:4602</SRS>' +
+' <SRS>EPSG:4603</SRS>' +
+' <SRS>EPSG:4604</SRS>' +
+' <SRS>EPSG:4605</SRS>' +
+' <SRS>EPSG:4606</SRS>' +
+' <SRS>EPSG:4607</SRS>' +
+' <SRS>EPSG:4608</SRS>' +
+' <SRS>EPSG:4609</SRS>' +
+' <SRS>EPSG:4610</SRS>' +
+' <SRS>EPSG:4611</SRS>' +
+' <SRS>EPSG:4612</SRS>' +
+' <SRS>EPSG:4613</SRS>' +
+' <SRS>EPSG:4614</SRS>' +
+' <SRS>EPSG:4615</SRS>' +
+' <SRS>EPSG:4616</SRS>' +
+' <SRS>EPSG:4617</SRS>' +
+' <SRS>EPSG:4618</SRS>' +
+' <SRS>EPSG:4619</SRS>' +
+' <SRS>EPSG:4620</SRS>' +
+' <SRS>EPSG:4621</SRS>' +
+' <SRS>EPSG:4622</SRS>' +
+' <SRS>EPSG:4623</SRS>' +
+' <SRS>EPSG:4624</SRS>' +
+' <SRS>EPSG:4625</SRS>' +
+' <SRS>EPSG:4626</SRS>' +
+' <SRS>EPSG:4627</SRS>' +
+' <SRS>EPSG:4628</SRS>' +
+' <SRS>EPSG:4629</SRS>' +
+' <SRS>EPSG:4630</SRS>' +
+' <SRS>EPSG:4631</SRS>' +
+' <SRS>EPSG:4632</SRS>' +
+' <SRS>EPSG:4633</SRS>' +
+' <SRS>EPSG:4634</SRS>' +
+' <SRS>EPSG:4635</SRS>' +
+' <SRS>EPSG:4636</SRS>' +
+' <SRS>EPSG:4637</SRS>' +
+' <SRS>EPSG:4638</SRS>' +
+' <SRS>EPSG:4639</SRS>' +
+' <SRS>EPSG:4640</SRS>' +
+' <SRS>EPSG:4641</SRS>' +
+' <SRS>EPSG:4642</SRS>' +
+' <SRS>EPSG:4643</SRS>' +
+' <SRS>EPSG:4644</SRS>' +
+' <SRS>EPSG:4645</SRS>' +
+' <SRS>EPSG:4646</SRS>' +
+' <SRS>EPSG:4657</SRS>' +
+' <SRS>EPSG:4658</SRS>' +
+' <SRS>EPSG:4659</SRS>' +
+' <SRS>EPSG:4660</SRS>' +
+' <SRS>EPSG:4661</SRS>' +
+' <SRS>EPSG:4662</SRS>' +
+' <SRS>EPSG:4663</SRS>' +
+' <SRS>EPSG:4664</SRS>' +
+' <SRS>EPSG:4665</SRS>' +
+' <SRS>EPSG:4666</SRS>' +
+' <SRS>EPSG:4667</SRS>' +
+' <SRS>EPSG:4668</SRS>' +
+' <SRS>EPSG:4669</SRS>' +
+' <SRS>EPSG:4670</SRS>' +
+' <SRS>EPSG:4671</SRS>' +
+' <SRS>EPSG:4672</SRS>' +
+' <SRS>EPSG:4673</SRS>' +
+' <SRS>EPSG:4674</SRS>' +
+' <SRS>EPSG:4675</SRS>' +
+' <SRS>EPSG:4676</SRS>' +
+' <SRS>EPSG:4677</SRS>' +
+' <SRS>EPSG:4678</SRS>' +
+' <SRS>EPSG:4679</SRS>' +
+' <SRS>EPSG:4680</SRS>' +
+' <SRS>EPSG:4681</SRS>' +
+' <SRS>EPSG:4682</SRS>' +
+' <SRS>EPSG:4683</SRS>' +
+' <SRS>EPSG:4684</SRS>' +
+' <SRS>EPSG:4685</SRS>' +
+' <SRS>EPSG:4686</SRS>' +
+' <SRS>EPSG:4687</SRS>' +
+' <SRS>EPSG:4688</SRS>' +
+' <SRS>EPSG:4689</SRS>' +
+' <SRS>EPSG:4690</SRS>' +
+' <SRS>EPSG:4691</SRS>' +
+' <SRS>EPSG:4692</SRS>' +
+' <SRS>EPSG:4693</SRS>' +
+' <SRS>EPSG:4694</SRS>' +
+' <SRS>EPSG:4695</SRS>' +
+' <SRS>EPSG:4696</SRS>' +
+' <SRS>EPSG:4697</SRS>' +
+' <SRS>EPSG:4698</SRS>' +
+' <SRS>EPSG:4699</SRS>' +
+' <SRS>EPSG:4700</SRS>' +
+' <SRS>EPSG:4701</SRS>' +
+' <SRS>EPSG:4702</SRS>' +
+' <SRS>EPSG:4703</SRS>' +
+' <SRS>EPSG:4704</SRS>' +
+' <SRS>EPSG:4705</SRS>' +
+' <SRS>EPSG:4706</SRS>' +
+' <SRS>EPSG:4707</SRS>' +
+' <SRS>EPSG:4708</SRS>' +
+' <SRS>EPSG:4709</SRS>' +
+' <SRS>EPSG:4710</SRS>' +
+' <SRS>EPSG:4711</SRS>' +
+' <SRS>EPSG:4712</SRS>' +
+' <SRS>EPSG:4713</SRS>' +
+' <SRS>EPSG:4714</SRS>' +
+' <SRS>EPSG:4715</SRS>' +
+' <SRS>EPSG:4716</SRS>' +
+' <SRS>EPSG:4717</SRS>' +
+' <SRS>EPSG:4718</SRS>' +
+' <SRS>EPSG:4719</SRS>' +
+' <SRS>EPSG:4720</SRS>' +
+' <SRS>EPSG:4721</SRS>' +
+' <SRS>EPSG:4722</SRS>' +
+' <SRS>EPSG:4723</SRS>' +
+' <SRS>EPSG:4724</SRS>' +
+' <SRS>EPSG:4725</SRS>' +
+' <SRS>EPSG:4726</SRS>' +
+' <SRS>EPSG:4727</SRS>' +
+' <SRS>EPSG:4728</SRS>' +
+' <SRS>EPSG:4729</SRS>' +
+' <SRS>EPSG:4730</SRS>' +
+' <SRS>EPSG:4731</SRS>' +
+' <SRS>EPSG:4732</SRS>' +
+' <SRS>EPSG:4733</SRS>' +
+' <SRS>EPSG:4734</SRS>' +
+' <SRS>EPSG:4735</SRS>' +
+' <SRS>EPSG:4736</SRS>' +
+' <SRS>EPSG:4737</SRS>' +
+' <SRS>EPSG:4738</SRS>' +
+' <SRS>EPSG:4739</SRS>' +
+' <SRS>EPSG:4740</SRS>' +
+' <SRS>EPSG:4741</SRS>' +
+' <SRS>EPSG:4742</SRS>' +
+' <SRS>EPSG:4743</SRS>' +
+' <SRS>EPSG:4744</SRS>' +
+' <SRS>EPSG:4745</SRS>' +
+' <SRS>EPSG:4746</SRS>' +
+' <SRS>EPSG:4747</SRS>' +
+' <SRS>EPSG:4748</SRS>' +
+' <SRS>EPSG:4749</SRS>' +
+' <SRS>EPSG:4750</SRS>' +
+' <SRS>EPSG:4751</SRS>' +
+' <SRS>EPSG:4752</SRS>' +
+' <SRS>EPSG:4753</SRS>' +
+' <SRS>EPSG:4754</SRS>' +
+' <SRS>EPSG:4755</SRS>' +
+' <SRS>EPSG:4756</SRS>' +
+' <SRS>EPSG:4757</SRS>' +
+' <SRS>EPSG:4758</SRS>' +
+' <SRS>EPSG:4801</SRS>' +
+' <SRS>EPSG:4802</SRS>' +
+' <SRS>EPSG:4803</SRS>' +
+' <SRS>EPSG:4804</SRS>' +
+' <SRS>EPSG:4805</SRS>' +
+' <SRS>EPSG:4806</SRS>' +
+' <SRS>EPSG:4807</SRS>' +
+' <SRS>EPSG:4808</SRS>' +
+' <SRS>EPSG:4809</SRS>' +
+' <SRS>EPSG:4810</SRS>' +
+' <SRS>EPSG:4811</SRS>' +
+' <SRS>EPSG:4813</SRS>' +
+' <SRS>EPSG:4814</SRS>' +
+' <SRS>EPSG:4815</SRS>' +
+' <SRS>EPSG:4816</SRS>' +
+' <SRS>EPSG:4817</SRS>' +
+' <SRS>EPSG:4818</SRS>' +
+' <SRS>EPSG:4819</SRS>' +
+' <SRS>EPSG:4820</SRS>' +
+' <SRS>EPSG:4821</SRS>' +
+' <SRS>EPSG:4894</SRS>' +
+' <SRS>EPSG:4895</SRS>' +
+' <SRS>EPSG:4896</SRS>' +
+' <SRS>EPSG:4897</SRS>' +
+' <SRS>EPSG:4898</SRS>' +
+' <SRS>EPSG:4899</SRS>' +
+' <SRS>EPSG:4900</SRS>' +
+' <SRS>EPSG:4901</SRS>' +
+' <SRS>EPSG:4902</SRS>' +
+' <SRS>EPSG:4903</SRS>' +
+' <SRS>EPSG:4904</SRS>' +
+' <SRS>EPSG:4906</SRS>' +
+' <SRS>EPSG:4907</SRS>' +
+' <SRS>EPSG:4908</SRS>' +
+' <SRS>EPSG:4909</SRS>' +
+' <SRS>EPSG:4910</SRS>' +
+' <SRS>EPSG:4911</SRS>' +
+' <SRS>EPSG:4912</SRS>' +
+' <SRS>EPSG:4913</SRS>' +
+' <SRS>EPSG:4914</SRS>' +
+' <SRS>EPSG:4915</SRS>' +
+' <SRS>EPSG:4916</SRS>' +
+' <SRS>EPSG:4917</SRS>' +
+' <SRS>EPSG:4918</SRS>' +
+' <SRS>EPSG:4919</SRS>' +
+' <SRS>EPSG:4920</SRS>' +
+' <SRS>EPSG:4921</SRS>' +
+' <SRS>EPSG:4922</SRS>' +
+' <SRS>EPSG:4923</SRS>' +
+' <SRS>EPSG:4924</SRS>' +
+' <SRS>EPSG:4925</SRS>' +
+' <SRS>EPSG:4926</SRS>' +
+' <SRS>EPSG:4927</SRS>' +
+' <SRS>EPSG:4928</SRS>' +
+' <SRS>EPSG:4929</SRS>' +
+' <SRS>EPSG:4930</SRS>' +
+' <SRS>EPSG:4931</SRS>' +
+' <SRS>EPSG:4932</SRS>' +
+' <SRS>EPSG:4933</SRS>' +
+' <SRS>EPSG:4934</SRS>' +
+' <SRS>EPSG:4935</SRS>' +
+' <SRS>EPSG:4936</SRS>' +
+' <SRS>EPSG:4937</SRS>' +
+' <SRS>EPSG:4938</SRS>' +
+' <SRS>EPSG:4939</SRS>' +
+' <SRS>EPSG:4940</SRS>' +
+' <SRS>EPSG:4941</SRS>' +
+' <SRS>EPSG:4942</SRS>' +
+' <SRS>EPSG:4943</SRS>' +
+' <SRS>EPSG:4944</SRS>' +
+' <SRS>EPSG:4945</SRS>' +
+' <SRS>EPSG:4946</SRS>' +
+' <SRS>EPSG:4947</SRS>' +
+' <SRS>EPSG:4948</SRS>' +
+' <SRS>EPSG:4949</SRS>' +
+' <SRS>EPSG:4950</SRS>' +
+' <SRS>EPSG:4951</SRS>' +
+' <SRS>EPSG:4952</SRS>' +
+' <SRS>EPSG:4953</SRS>' +
+' <SRS>EPSG:4954</SRS>' +
+' <SRS>EPSG:4955</SRS>' +
+' <SRS>EPSG:4956</SRS>' +
+' <SRS>EPSG:4957</SRS>' +
+' <SRS>EPSG:4958</SRS>' +
+' <SRS>EPSG:4959</SRS>' +
+' <SRS>EPSG:4960</SRS>' +
+' <SRS>EPSG:4961</SRS>' +
+' <SRS>EPSG:4962</SRS>' +
+' <SRS>EPSG:4963</SRS>' +
+' <SRS>EPSG:4964</SRS>' +
+' <SRS>EPSG:4965</SRS>' +
+' <SRS>EPSG:4966</SRS>' +
+' <SRS>EPSG:4967</SRS>' +
+' <SRS>EPSG:4968</SRS>' +
+' <SRS>EPSG:4969</SRS>' +
+' <SRS>EPSG:4970</SRS>' +
+' <SRS>EPSG:4971</SRS>' +
+' <SRS>EPSG:4972</SRS>' +
+' <SRS>EPSG:4973</SRS>' +
+' <SRS>EPSG:4974</SRS>' +
+' <SRS>EPSG:4975</SRS>' +
+' <SRS>EPSG:4976</SRS>' +
+' <SRS>EPSG:4977</SRS>' +
+' <SRS>EPSG:4978</SRS>' +
+' <SRS>EPSG:4979</SRS>' +
+' <SRS>EPSG:4980</SRS>' +
+' <SRS>EPSG:4981</SRS>' +
+' <SRS>EPSG:4982</SRS>' +
+' <SRS>EPSG:4983</SRS>' +
+' <SRS>EPSG:4984</SRS>' +
+' <SRS>EPSG:4985</SRS>' +
+' <SRS>EPSG:4986</SRS>' +
+' <SRS>EPSG:4987</SRS>' +
+' <SRS>EPSG:4988</SRS>' +
+' <SRS>EPSG:4989</SRS>' +
+' <SRS>EPSG:4990</SRS>' +
+' <SRS>EPSG:4991</SRS>' +
+' <SRS>EPSG:4992</SRS>' +
+' <SRS>EPSG:4993</SRS>' +
+' <SRS>EPSG:4994</SRS>' +
+' <SRS>EPSG:4995</SRS>' +
+' <SRS>EPSG:4996</SRS>' +
+' <SRS>EPSG:4997</SRS>' +
+' <SRS>EPSG:4998</SRS>' +
+' <SRS>EPSG:4999</SRS>' +
+' <SRS>EPSG:5600</SRS>' +
+' <SRS>EPSG:5601</SRS>' +
+' <SRS>EPSG:5602</SRS>' +
+' <SRS>EPSG:5603</SRS>' +
+' <SRS>EPSG:5604</SRS>' +
+' <SRS>EPSG:5605</SRS>' +
+' <SRS>EPSG:5606</SRS>' +
+' <SRS>EPSG:5607</SRS>' +
+' <SRS>EPSG:5608</SRS>' +
+' <SRS>EPSG:5609</SRS>' +
+' <SRS>EPSG:5701</SRS>' +
+' <SRS>EPSG:5702</SRS>' +
+' <SRS>EPSG:5703</SRS>' +
+' <SRS>EPSG:5704</SRS>' +
+' <SRS>EPSG:5705</SRS>' +
+' <SRS>EPSG:5706</SRS>' +
+' <SRS>EPSG:5709</SRS>' +
+' <SRS>EPSG:5710</SRS>' +
+' <SRS>EPSG:5711</SRS>' +
+' <SRS>EPSG:5712</SRS>' +
+' <SRS>EPSG:5713</SRS>' +
+' <SRS>EPSG:5714</SRS>' +
+' <SRS>EPSG:5715</SRS>' +
+' <SRS>EPSG:5716</SRS>' +
+' <SRS>EPSG:5717</SRS>' +
+' <SRS>EPSG:5718</SRS>' +
+' <SRS>EPSG:5719</SRS>' +
+' <SRS>EPSG:5720</SRS>' +
+' <SRS>EPSG:5721</SRS>' +
+' <SRS>EPSG:5722</SRS>' +
+' <SRS>EPSG:5723</SRS>' +
+' <SRS>EPSG:5724</SRS>' +
+' <SRS>EPSG:5725</SRS>' +
+' <SRS>EPSG:5726</SRS>' +
+' <SRS>EPSG:5727</SRS>' +
+' <SRS>EPSG:5728</SRS>' +
+' <SRS>EPSG:5729</SRS>' +
+' <SRS>EPSG:5730</SRS>' +
+' <SRS>EPSG:5731</SRS>' +
+' <SRS>EPSG:5732</SRS>' +
+' <SRS>EPSG:5733</SRS>' +
+' <SRS>EPSG:5734</SRS>' +
+' <SRS>EPSG:5735</SRS>' +
+' <SRS>EPSG:5736</SRS>' +
+' <SRS>EPSG:5737</SRS>' +
+' <SRS>EPSG:5738</SRS>' +
+' <SRS>EPSG:5739</SRS>' +
+' <SRS>EPSG:5740</SRS>' +
+' <SRS>EPSG:5741</SRS>' +
+' <SRS>EPSG:5742</SRS>' +
+' <SRS>EPSG:5743</SRS>' +
+' <SRS>EPSG:5744</SRS>' +
+' <SRS>EPSG:5745</SRS>' +
+' <SRS>EPSG:5746</SRS>' +
+' <SRS>EPSG:5747</SRS>' +
+' <SRS>EPSG:5748</SRS>' +
+' <SRS>EPSG:5749</SRS>' +
+' <SRS>EPSG:5750</SRS>' +
+' <SRS>EPSG:5751</SRS>' +
+' <SRS>EPSG:5752</SRS>' +
+' <SRS>EPSG:5753</SRS>' +
+' <SRS>EPSG:5754</SRS>' +
+' <SRS>EPSG:5755</SRS>' +
+' <SRS>EPSG:5756</SRS>' +
+' <SRS>EPSG:5757</SRS>' +
+' <SRS>EPSG:5758</SRS>' +
+' <SRS>EPSG:5759</SRS>' +
+' <SRS>EPSG:5760</SRS>' +
+' <SRS>EPSG:5761</SRS>' +
+' <SRS>EPSG:5762</SRS>' +
+' <SRS>EPSG:5763</SRS>' +
+' <SRS>EPSG:5764</SRS>' +
+' <SRS>EPSG:5765</SRS>' +
+' <SRS>EPSG:5766</SRS>' +
+' <SRS>EPSG:5767</SRS>' +
+' <SRS>EPSG:5768</SRS>' +
+' <SRS>EPSG:5769</SRS>' +
+' <SRS>EPSG:5770</SRS>' +
+' <SRS>EPSG:5771</SRS>' +
+' <SRS>EPSG:5772</SRS>' +
+' <SRS>EPSG:5773</SRS>' +
+' <SRS>EPSG:5774</SRS>' +
+' <SRS>EPSG:5775</SRS>' +
+' <SRS>EPSG:5776</SRS>' +
+' <SRS>EPSG:5777</SRS>' +
+' <SRS>EPSG:5778</SRS>' +
+' <SRS>EPSG:5779</SRS>' +
+' <SRS>EPSG:5780</SRS>' +
+' <SRS>EPSG:5781</SRS>' +
+' <SRS>EPSG:5782</SRS>' +
+' <SRS>EPSG:5783</SRS>' +
+' <SRS>EPSG:5784</SRS>' +
+' <SRS>EPSG:5785</SRS>' +
+' <SRS>EPSG:5786</SRS>' +
+' <SRS>EPSG:5787</SRS>' +
+' <SRS>EPSG:5788</SRS>' +
+' <SRS>EPSG:5789</SRS>' +
+' <SRS>EPSG:5790</SRS>' +
+' <SRS>EPSG:5791</SRS>' +
+' <SRS>EPSG:5792</SRS>' +
+' <SRS>EPSG:5793</SRS>' +
+' <SRS>EPSG:5794</SRS>' +
+' <SRS>EPSG:5795</SRS>' +
+' <SRS>EPSG:5796</SRS>' +
+' <SRS>EPSG:5797</SRS>' +
+' <SRS>EPSG:5798</SRS>' +
+' <SRS>EPSG:5799</SRS>' +
+' <SRS>EPSG:5800</SRS>' +
+' <SRS>EPSG:5801</SRS>' +
+' <SRS>EPSG:5802</SRS>' +
+' <SRS>EPSG:5803</SRS>' +
+' <SRS>EPSG:5804</SRS>' +
+' <SRS>EPSG:5805</SRS>' +
+' <SRS>EPSG:5806</SRS>' +
+' <SRS>EPSG:5807</SRS>' +
+' <SRS>EPSG:5808</SRS>' +
+' <SRS>EPSG:5809</SRS>' +
+' <SRS>EPSG:5810</SRS>' +
+' <SRS>EPSG:5811</SRS>' +
+' <SRS>EPSG:5812</SRS>' +
+' <SRS>EPSG:5813</SRS>' +
+' <SRS>EPSG:5814</SRS>' +
+' <SRS>EPSG:5815</SRS>' +
+' <SRS>EPSG:5816</SRS>' +
+' <SRS>EPSG:5817</SRS>' +
+' <SRS>EPSG:5818</SRS>' +
+' <SRS>EPSG:7400</SRS>' +
+' <SRS>EPSG:7401</SRS>' +
+' <SRS>EPSG:7402</SRS>' +
+' <SRS>EPSG:7403</SRS>' +
+' <SRS>EPSG:7404</SRS>' +
+' <SRS>EPSG:7405</SRS>' +
+' <SRS>EPSG:7406</SRS>' +
+' <SRS>EPSG:7407</SRS>' +
+' <SRS>EPSG:7408</SRS>' +
+' <SRS>EPSG:7409</SRS>' +
+' <SRS>EPSG:7410</SRS>' +
+' <SRS>EPSG:7411</SRS>' +
+' <SRS>EPSG:7412</SRS>' +
+' <SRS>EPSG:7413</SRS>' +
+' <SRS>EPSG:7414</SRS>' +
+' <SRS>EPSG:7415</SRS>' +
+' <SRS>EPSG:7416</SRS>' +
+' <SRS>EPSG:7417</SRS>' +
+' <SRS>EPSG:7418</SRS>' +
+' <SRS>EPSG:7419</SRS>' +
+' <SRS>EPSG:7420</SRS>' +
+' <SRS>EPSG:20004</SRS>' +
+' <SRS>EPSG:20005</SRS>' +
+' <SRS>EPSG:20006</SRS>' +
+' <SRS>EPSG:20007</SRS>' +
+' <SRS>EPSG:20008</SRS>' +
+' <SRS>EPSG:20009</SRS>' +
+' <SRS>EPSG:20010</SRS>' +
+' <SRS>EPSG:20011</SRS>' +
+' <SRS>EPSG:20012</SRS>' +
+' <SRS>EPSG:20013</SRS>' +
+' <SRS>EPSG:20014</SRS>' +
+' <SRS>EPSG:20015</SRS>' +
+' <SRS>EPSG:20016</SRS>' +
+' <SRS>EPSG:20017</SRS>' +
+' <SRS>EPSG:20018</SRS>' +
+' <SRS>EPSG:20019</SRS>' +
+' <SRS>EPSG:20020</SRS>' +
+' <SRS>EPSG:20021</SRS>' +
+' <SRS>EPSG:20022</SRS>' +
+' <SRS>EPSG:20023</SRS>' +
+' <SRS>EPSG:20024</SRS>' +
+' <SRS>EPSG:20025</SRS>' +
+' <SRS>EPSG:20026</SRS>' +
+' <SRS>EPSG:20027</SRS>' +
+' <SRS>EPSG:20028</SRS>' +
+' <SRS>EPSG:20029</SRS>' +
+' <SRS>EPSG:20030</SRS>' +
+' <SRS>EPSG:20031</SRS>' +
+' <SRS>EPSG:20032</SRS>' +
+' <SRS>EPSG:20064</SRS>' +
+' <SRS>EPSG:20065</SRS>' +
+' <SRS>EPSG:20066</SRS>' +
+' <SRS>EPSG:20067</SRS>' +
+' <SRS>EPSG:20068</SRS>' +
+' <SRS>EPSG:20069</SRS>' +
+' <SRS>EPSG:20070</SRS>' +
+' <SRS>EPSG:20071</SRS>' +
+' <SRS>EPSG:20072</SRS>' +
+' <SRS>EPSG:20073</SRS>' +
+' <SRS>EPSG:20074</SRS>' +
+' <SRS>EPSG:20075</SRS>' +
+' <SRS>EPSG:20076</SRS>' +
+' <SRS>EPSG:20077</SRS>' +
+' <SRS>EPSG:20078</SRS>' +
+' <SRS>EPSG:20079</SRS>' +
+' <SRS>EPSG:20080</SRS>' +
+' <SRS>EPSG:20081</SRS>' +
+' <SRS>EPSG:20082</SRS>' +
+' <SRS>EPSG:20083</SRS>' +
+' <SRS>EPSG:20084</SRS>' +
+' <SRS>EPSG:20085</SRS>' +
+' <SRS>EPSG:20086</SRS>' +
+' <SRS>EPSG:20087</SRS>' +
+' <SRS>EPSG:20088</SRS>' +
+' <SRS>EPSG:20089</SRS>' +
+' <SRS>EPSG:20090</SRS>' +
+' <SRS>EPSG:20091</SRS>' +
+' <SRS>EPSG:20092</SRS>' +
+' <SRS>EPSG:20135</SRS>' +
+' <SRS>EPSG:20136</SRS>' +
+' <SRS>EPSG:20137</SRS>' +
+' <SRS>EPSG:20138</SRS>' +
+' <SRS>EPSG:20248</SRS>' +
+' <SRS>EPSG:20249</SRS>' +
+' <SRS>EPSG:20250</SRS>' +
+' <SRS>EPSG:20251</SRS>' +
+' <SRS>EPSG:20252</SRS>' +
+' <SRS>EPSG:20253</SRS>' +
+' <SRS>EPSG:20254</SRS>' +
+' <SRS>EPSG:20255</SRS>' +
+' <SRS>EPSG:20256</SRS>' +
+' <SRS>EPSG:20257</SRS>' +
+' <SRS>EPSG:20258</SRS>' +
+' <SRS>EPSG:20348</SRS>' +
+' <SRS>EPSG:20349</SRS>' +
+' <SRS>EPSG:20350</SRS>' +
+' <SRS>EPSG:20351</SRS>' +
+' <SRS>EPSG:20352</SRS>' +
+' <SRS>EPSG:20353</SRS>' +
+' <SRS>EPSG:20354</SRS>' +
+' <SRS>EPSG:20355</SRS>' +
+' <SRS>EPSG:20356</SRS>' +
+' <SRS>EPSG:20357</SRS>' +
+' <SRS>EPSG:20358</SRS>' +
+' <SRS>EPSG:20436</SRS>' +
+' <SRS>EPSG:20437</SRS>' +
+' <SRS>EPSG:20438</SRS>' +
+' <SRS>EPSG:20439</SRS>' +
+' <SRS>EPSG:20440</SRS>' +
+' <SRS>EPSG:20499</SRS>' +
+' <SRS>EPSG:20538</SRS>' +
+' <SRS>EPSG:20539</SRS>' +
+' <SRS>EPSG:20790</SRS>' +
+' <SRS>EPSG:20791</SRS>' +
+' <SRS>EPSG:20822</SRS>' +
+' <SRS>EPSG:20823</SRS>' +
+' <SRS>EPSG:20824</SRS>' +
+' <SRS>EPSG:20934</SRS>' +
+' <SRS>EPSG:20935</SRS>' +
+' <SRS>EPSG:20936</SRS>' +
+' <SRS>EPSG:21035</SRS>' +
+' <SRS>EPSG:21036</SRS>' +
+' <SRS>EPSG:21037</SRS>' +
+' <SRS>EPSG:21095</SRS>' +
+' <SRS>EPSG:21096</SRS>' +
+' <SRS>EPSG:21097</SRS>' +
+' <SRS>EPSG:21100</SRS>' +
+' <SRS>EPSG:21148</SRS>' +
+' <SRS>EPSG:21149</SRS>' +
+' <SRS>EPSG:21150</SRS>' +
+' <SRS>EPSG:21291</SRS>' +
+' <SRS>EPSG:21292</SRS>' +
+' <SRS>EPSG:21413</SRS>' +
+' <SRS>EPSG:21414</SRS>' +
+' <SRS>EPSG:21415</SRS>' +
+' <SRS>EPSG:21416</SRS>' +
+' <SRS>EPSG:21417</SRS>' +
+' <SRS>EPSG:21418</SRS>' +
+' <SRS>EPSG:21419</SRS>' +
+' <SRS>EPSG:21420</SRS>' +
+' <SRS>EPSG:21421</SRS>' +
+' <SRS>EPSG:21422</SRS>' +
+' <SRS>EPSG:21423</SRS>' +
+' <SRS>EPSG:21453</SRS>' +
+' <SRS>EPSG:21454</SRS>' +
+' <SRS>EPSG:21455</SRS>' +
+' <SRS>EPSG:21456</SRS>' +
+' <SRS>EPSG:21457</SRS>' +
+' <SRS>EPSG:21458</SRS>' +
+' <SRS>EPSG:21459</SRS>' +
+' <SRS>EPSG:21460</SRS>' +
+' <SRS>EPSG:21461</SRS>' +
+' <SRS>EPSG:21462</SRS>' +
+' <SRS>EPSG:21463</SRS>' +
+' <SRS>EPSG:21473</SRS>' +
+' <SRS>EPSG:21474</SRS>' +
+' <SRS>EPSG:21475</SRS>' +
+' <SRS>EPSG:21476</SRS>' +
+' <SRS>EPSG:21477</SRS>' +
+' <SRS>EPSG:21478</SRS>' +
+' <SRS>EPSG:21479</SRS>' +
+' <SRS>EPSG:21480</SRS>' +
+' <SRS>EPSG:21481</SRS>' +
+' <SRS>EPSG:21482</SRS>' +
+' <SRS>EPSG:21483</SRS>' +
+' <SRS>EPSG:21500</SRS>' +
+' <SRS>EPSG:21780</SRS>' +
+' <SRS>EPSG:21781</SRS>' +
+' <SRS>EPSG:21817</SRS>' +
+' <SRS>EPSG:21818</SRS>' +
+' <SRS>EPSG:21891</SRS>' +
+' <SRS>EPSG:21892</SRS>' +
+' <SRS>EPSG:21893</SRS>' +
+' <SRS>EPSG:21894</SRS>' +
+' <SRS>EPSG:21896</SRS>' +
+' <SRS>EPSG:21897</SRS>' +
+' <SRS>EPSG:21898</SRS>' +
+' <SRS>EPSG:21899</SRS>' +
+' <SRS>EPSG:22032</SRS>' +
+' <SRS>EPSG:22033</SRS>' +
+' <SRS>EPSG:22091</SRS>' +
+' <SRS>EPSG:22092</SRS>' +
+' <SRS>EPSG:22171</SRS>' +
+' <SRS>EPSG:22172</SRS>' +
+' <SRS>EPSG:22173</SRS>' +
+' <SRS>EPSG:22174</SRS>' +
+' <SRS>EPSG:22175</SRS>' +
+' <SRS>EPSG:22176</SRS>' +
+' <SRS>EPSG:22177</SRS>' +
+' <SRS>EPSG:22181</SRS>' +
+' <SRS>EPSG:22182</SRS>' +
+' <SRS>EPSG:22183</SRS>' +
+' <SRS>EPSG:22184</SRS>' +
+' <SRS>EPSG:22185</SRS>' +
+' <SRS>EPSG:22186</SRS>' +
+' <SRS>EPSG:22187</SRS>' +
+' <SRS>EPSG:22191</SRS>' +
+' <SRS>EPSG:22192</SRS>' +
+' <SRS>EPSG:22193</SRS>' +
+' <SRS>EPSG:22194</SRS>' +
+' <SRS>EPSG:22195</SRS>' +
+' <SRS>EPSG:22196</SRS>' +
+' <SRS>EPSG:22197</SRS>' +
+' <SRS>EPSG:22234</SRS>' +
+' <SRS>EPSG:22235</SRS>' +
+' <SRS>EPSG:22236</SRS>' +
+' <SRS>EPSG:22275</SRS>' +
+' <SRS>EPSG:22277</SRS>' +
+' <SRS>EPSG:22279</SRS>' +
+' <SRS>EPSG:22281</SRS>' +
+' <SRS>EPSG:22283</SRS>' +
+' <SRS>EPSG:22285</SRS>' +
+' <SRS>EPSG:22287</SRS>' +
+' <SRS>EPSG:22289</SRS>' +
+' <SRS>EPSG:22291</SRS>' +
+' <SRS>EPSG:22293</SRS>' +
+' <SRS>EPSG:22300</SRS>' +
+' <SRS>EPSG:22332</SRS>' +
+' <SRS>EPSG:22391</SRS>' +
+' <SRS>EPSG:22392</SRS>' +
+' <SRS>EPSG:22521</SRS>' +
+' <SRS>EPSG:22522</SRS>' +
+' <SRS>EPSG:22523</SRS>' +
+' <SRS>EPSG:22524</SRS>' +
+' <SRS>EPSG:22525</SRS>' +
+' <SRS>EPSG:22700</SRS>' +
+' <SRS>EPSG:22770</SRS>' +
+' <SRS>EPSG:22780</SRS>' +
+' <SRS>EPSG:22832</SRS>' +
+' <SRS>EPSG:22991</SRS>' +
+' <SRS>EPSG:22992</SRS>' +
+' <SRS>EPSG:22993</SRS>' +
+' <SRS>EPSG:22994</SRS>' +
+' <SRS>EPSG:23028</SRS>' +
+' <SRS>EPSG:23029</SRS>' +
+' <SRS>EPSG:23030</SRS>' +
+' <SRS>EPSG:23031</SRS>' +
+' <SRS>EPSG:23032</SRS>' +
+' <SRS>EPSG:23033</SRS>' +
+' <SRS>EPSG:23034</SRS>' +
+' <SRS>EPSG:23035</SRS>' +
+' <SRS>EPSG:23036</SRS>' +
+' <SRS>EPSG:23037</SRS>' +
+' <SRS>EPSG:23038</SRS>' +
+' <SRS>EPSG:23090</SRS>' +
+' <SRS>EPSG:23095</SRS>' +
+' <SRS>EPSG:23239</SRS>' +
+' <SRS>EPSG:23240</SRS>' +
+' <SRS>EPSG:23433</SRS>' +
+' <SRS>EPSG:23700</SRS>' +
+' <SRS>EPSG:23846</SRS>' +
+' <SRS>EPSG:23847</SRS>' +
+' <SRS>EPSG:23848</SRS>' +
+' <SRS>EPSG:23849</SRS>' +
+' <SRS>EPSG:23850</SRS>' +
+' <SRS>EPSG:23851</SRS>' +
+' <SRS>EPSG:23852</SRS>' +
+' <SRS>EPSG:23853</SRS>' +
+' <SRS>EPSG:23866</SRS>' +
+' <SRS>EPSG:23867</SRS>' +
+' <SRS>EPSG:23868</SRS>' +
+' <SRS>EPSG:23869</SRS>' +
+' <SRS>EPSG:23870</SRS>' +
+' <SRS>EPSG:23871</SRS>' +
+' <SRS>EPSG:23872</SRS>' +
+' <SRS>EPSG:23877</SRS>' +
+' <SRS>EPSG:23878</SRS>' +
+' <SRS>EPSG:23879</SRS>' +
+' <SRS>EPSG:23880</SRS>' +
+' <SRS>EPSG:23881</SRS>' +
+' <SRS>EPSG:23882</SRS>' +
+' <SRS>EPSG:23883</SRS>' +
+' <SRS>EPSG:23884</SRS>' +
+' <SRS>EPSG:23886</SRS>' +
+' <SRS>EPSG:23887</SRS>' +
+' <SRS>EPSG:23888</SRS>' +
+' <SRS>EPSG:23889</SRS>' +
+' <SRS>EPSG:23890</SRS>' +
+' <SRS>EPSG:23891</SRS>' +
+' <SRS>EPSG:23892</SRS>' +
+' <SRS>EPSG:23893</SRS>' +
+' <SRS>EPSG:23894</SRS>' +
+' <SRS>EPSG:23946</SRS>' +
+' <SRS>EPSG:23947</SRS>' +
+' <SRS>EPSG:23948</SRS>' +
+' <SRS>EPSG:24047</SRS>' +
+' <SRS>EPSG:24048</SRS>' +
+' <SRS>EPSG:24100</SRS>' +
+' <SRS>EPSG:24200</SRS>' +
+' <SRS>EPSG:24305</SRS>' +
+' <SRS>EPSG:24306</SRS>' +
+' <SRS>EPSG:24311</SRS>' +
+' <SRS>EPSG:24312</SRS>' +
+' <SRS>EPSG:24313</SRS>' +
+' <SRS>EPSG:24342</SRS>' +
+' <SRS>EPSG:24343</SRS>' +
+' <SRS>EPSG:24344</SRS>' +
+' <SRS>EPSG:24345</SRS>' +
+' <SRS>EPSG:24346</SRS>' +
+' <SRS>EPSG:24347</SRS>' +
+' <SRS>EPSG:24370</SRS>' +
+' <SRS>EPSG:24371</SRS>' +
+' <SRS>EPSG:24372</SRS>' +
+' <SRS>EPSG:24373</SRS>' +
+' <SRS>EPSG:24374</SRS>' +
+' <SRS>EPSG:24375</SRS>' +
+' <SRS>EPSG:24376</SRS>' +
+' <SRS>EPSG:24377</SRS>' +
+' <SRS>EPSG:24378</SRS>' +
+' <SRS>EPSG:24379</SRS>' +
+' <SRS>EPSG:24380</SRS>' +
+' <SRS>EPSG:24381</SRS>' +
+' <SRS>EPSG:24382</SRS>' +
+' <SRS>EPSG:24383</SRS>' +
+' <SRS>EPSG:24500</SRS>' +
+' <SRS>EPSG:24547</SRS>' +
+' <SRS>EPSG:24548</SRS>' +
+' <SRS>EPSG:24571</SRS>' +
+' <SRS>EPSG:24600</SRS>' +
+' <SRS>EPSG:24718</SRS>' +
+' <SRS>EPSG:24719</SRS>' +
+' <SRS>EPSG:24720</SRS>' +
+' <SRS>EPSG:24817</SRS>' +
+' <SRS>EPSG:24818</SRS>' +
+' <SRS>EPSG:24819</SRS>' +
+' <SRS>EPSG:24820</SRS>' +
+' <SRS>EPSG:24821</SRS>' +
+' <SRS>EPSG:24877</SRS>' +
+' <SRS>EPSG:24878</SRS>' +
+' <SRS>EPSG:24879</SRS>' +
+' <SRS>EPSG:24880</SRS>' +
+' <SRS>EPSG:24881</SRS>' +
+' <SRS>EPSG:24882</SRS>' +
+' <SRS>EPSG:24891</SRS>' +
+' <SRS>EPSG:24892</SRS>' +
+' <SRS>EPSG:24893</SRS>' +
+' <SRS>EPSG:25000</SRS>' +
+' <SRS>EPSG:25231</SRS>' +
+' <SRS>EPSG:25391</SRS>' +
+' <SRS>EPSG:25392</SRS>' +
+' <SRS>EPSG:25393</SRS>' +
+' <SRS>EPSG:25394</SRS>' +
+' <SRS>EPSG:25395</SRS>' +
+' <SRS>EPSG:25700</SRS>' +
+' <SRS>EPSG:25828</SRS>' +
+' <SRS>EPSG:25829</SRS>' +
+' <SRS>EPSG:25830</SRS>' +
+' <SRS>EPSG:25831</SRS>' +
+' <SRS>EPSG:25832</SRS>' +
+' <SRS>EPSG:25833</SRS>' +
+' <SRS>EPSG:25834</SRS>' +
+' <SRS>EPSG:25835</SRS>' +
+' <SRS>EPSG:25836</SRS>' +
+' <SRS>EPSG:25837</SRS>' +
+' <SRS>EPSG:25838</SRS>' +
+' <SRS>EPSG:25884</SRS>' +
+' <SRS>EPSG:25932</SRS>' +
+' <SRS>EPSG:26191</SRS>' +
+' <SRS>EPSG:26192</SRS>' +
+' <SRS>EPSG:26193</SRS>' +
+' <SRS>EPSG:26194</SRS>' +
+' <SRS>EPSG:26195</SRS>' +
+' <SRS>EPSG:26237</SRS>' +
+' <SRS>EPSG:26331</SRS>' +
+' <SRS>EPSG:26332</SRS>' +
+' <SRS>EPSG:26391</SRS>' +
+' <SRS>EPSG:26392</SRS>' +
+' <SRS>EPSG:26393</SRS>' +
+' <SRS>EPSG:26432</SRS>' +
+' <SRS>EPSG:26591</SRS>' +
+' <SRS>EPSG:26592</SRS>' +
+' <SRS>EPSG:26632</SRS>' +
+' <SRS>EPSG:26692</SRS>' +
+' <SRS>EPSG:26701</SRS>' +
+' <SRS>EPSG:26702</SRS>' +
+' <SRS>EPSG:26703</SRS>' +
+' <SRS>EPSG:26704</SRS>' +
+' <SRS>EPSG:26705</SRS>' +
+' <SRS>EPSG:26706</SRS>' +
+' <SRS>EPSG:26707</SRS>' +
+' <SRS>EPSG:26708</SRS>' +
+' <SRS>EPSG:26709</SRS>' +
+' <SRS>EPSG:26710</SRS>' +
+' <SRS>EPSG:26711</SRS>' +
+' <SRS>EPSG:26712</SRS>' +
+' <SRS>EPSG:26713</SRS>' +
+' <SRS>EPSG:26714</SRS>' +
+' <SRS>EPSG:26715</SRS>' +
+' <SRS>EPSG:26716</SRS>' +
+' <SRS>EPSG:26717</SRS>' +
+' <SRS>EPSG:26718</SRS>' +
+' <SRS>EPSG:26719</SRS>' +
+' <SRS>EPSG:26720</SRS>' +
+' <SRS>EPSG:26721</SRS>' +
+' <SRS>EPSG:26722</SRS>' +
+' <SRS>EPSG:26729</SRS>' +
+' <SRS>EPSG:26730</SRS>' +
+' <SRS>EPSG:26731</SRS>' +
+' <SRS>EPSG:26732</SRS>' +
+' <SRS>EPSG:26733</SRS>' +
+' <SRS>EPSG:26734</SRS>' +
+' <SRS>EPSG:26735</SRS>' +
+' <SRS>EPSG:26736</SRS>' +
+' <SRS>EPSG:26737</SRS>' +
+' <SRS>EPSG:26738</SRS>' +
+' <SRS>EPSG:26739</SRS>' +
+' <SRS>EPSG:26740</SRS>' +
+' <SRS>EPSG:26741</SRS>' +
+' <SRS>EPSG:26742</SRS>' +
+' <SRS>EPSG:26743</SRS>' +
+' <SRS>EPSG:26744</SRS>' +
+' <SRS>EPSG:26745</SRS>' +
+' <SRS>EPSG:26746</SRS>' +
+' <SRS>EPSG:26747</SRS>' +
+' <SRS>EPSG:26748</SRS>' +
+' <SRS>EPSG:26749</SRS>' +
+' <SRS>EPSG:26750</SRS>' +
+' <SRS>EPSG:26751</SRS>' +
+' <SRS>EPSG:26752</SRS>' +
+' <SRS>EPSG:26753</SRS>' +
+' <SRS>EPSG:26754</SRS>' +
+' <SRS>EPSG:26755</SRS>' +
+' <SRS>EPSG:26756</SRS>' +
+' <SRS>EPSG:26757</SRS>' +
+' <SRS>EPSG:26758</SRS>' +
+' <SRS>EPSG:26759</SRS>' +
+' <SRS>EPSG:26760</SRS>' +
+' <SRS>EPSG:26766</SRS>' +
+' <SRS>EPSG:26767</SRS>' +
+' <SRS>EPSG:26768</SRS>' +
+' <SRS>EPSG:26769</SRS>' +
+' <SRS>EPSG:26770</SRS>' +
+' <SRS>EPSG:26771</SRS>' +
+' <SRS>EPSG:26772</SRS>' +
+' <SRS>EPSG:26773</SRS>' +
+' <SRS>EPSG:26774</SRS>' +
+' <SRS>EPSG:26775</SRS>' +
+' <SRS>EPSG:26776</SRS>' +
+' <SRS>EPSG:26777</SRS>' +
+' <SRS>EPSG:26778</SRS>' +
+' <SRS>EPSG:26779</SRS>' +
+' <SRS>EPSG:26780</SRS>' +
+' <SRS>EPSG:26781</SRS>' +
+' <SRS>EPSG:26782</SRS>' +
+' <SRS>EPSG:26783</SRS>' +
+' <SRS>EPSG:26784</SRS>' +
+' <SRS>EPSG:26785</SRS>' +
+' <SRS>EPSG:26786</SRS>' +
+' <SRS>EPSG:26787</SRS>' +
+' <SRS>EPSG:26791</SRS>' +
+' <SRS>EPSG:26792</SRS>' +
+' <SRS>EPSG:26793</SRS>' +
+' <SRS>EPSG:26794</SRS>' +
+' <SRS>EPSG:26795</SRS>' +
+' <SRS>EPSG:26796</SRS>' +
+' <SRS>EPSG:26797</SRS>' +
+' <SRS>EPSG:26798</SRS>' +
+' <SRS>EPSG:26799</SRS>' +
+' <SRS>EPSG:26801</SRS>' +
+' <SRS>EPSG:26802</SRS>' +
+' <SRS>EPSG:26803</SRS>' +
+' <SRS>EPSG:26811</SRS>' +
+' <SRS>EPSG:26812</SRS>' +
+' <SRS>EPSG:26813</SRS>' +
+' <SRS>EPSG:26901</SRS>' +
+' <SRS>EPSG:26902</SRS>' +
+' <SRS>EPSG:26903</SRS>' +
+' <SRS>EPSG:26904</SRS>' +
+' <SRS>EPSG:26905</SRS>' +
+' <SRS>EPSG:26906</SRS>' +
+' <SRS>EPSG:26907</SRS>' +
+' <SRS>EPSG:26908</SRS>' +
+' <SRS>EPSG:26909</SRS>' +
+' <SRS>EPSG:26910</SRS>' +
+' <SRS>EPSG:26911</SRS>' +
+' <SRS>EPSG:26912</SRS>' +
+' <SRS>EPSG:26913</SRS>' +
+' <SRS>EPSG:26914</SRS>' +
+' <SRS>EPSG:26915</SRS>' +
+' <SRS>EPSG:26916</SRS>' +
+' <SRS>EPSG:26917</SRS>' +
+' <SRS>EPSG:26918</SRS>' +
+' <SRS>EPSG:26919</SRS>' +
+' <SRS>EPSG:26920</SRS>' +
+' <SRS>EPSG:26921</SRS>' +
+' <SRS>EPSG:26922</SRS>' +
+' <SRS>EPSG:26923</SRS>' +
+' <SRS>EPSG:26929</SRS>' +
+' <SRS>EPSG:26930</SRS>' +
+' <SRS>EPSG:26931</SRS>' +
+' <SRS>EPSG:26932</SRS>' +
+' <SRS>EPSG:26933</SRS>' +
+' <SRS>EPSG:26934</SRS>' +
+' <SRS>EPSG:26935</SRS>' +
+' <SRS>EPSG:26936</SRS>' +
+' <SRS>EPSG:26937</SRS>' +
+' <SRS>EPSG:26938</SRS>' +
+' <SRS>EPSG:26939</SRS>' +
+' <SRS>EPSG:26940</SRS>' +
+' <SRS>EPSG:26941</SRS>' +
+' <SRS>EPSG:26942</SRS>' +
+' <SRS>EPSG:26943</SRS>' +
+' <SRS>EPSG:26944</SRS>' +
+' <SRS>EPSG:26945</SRS>' +
+' <SRS>EPSG:26946</SRS>' +
+' <SRS>EPSG:26948</SRS>' +
+' <SRS>EPSG:26949</SRS>' +
+' <SRS>EPSG:26950</SRS>' +
+' <SRS>EPSG:26951</SRS>' +
+' <SRS>EPSG:26952</SRS>' +
+' <SRS>EPSG:26953</SRS>' +
+' <SRS>EPSG:26954</SRS>' +
+' <SRS>EPSG:26955</SRS>' +
+' <SRS>EPSG:26956</SRS>' +
+' <SRS>EPSG:26957</SRS>' +
+' <SRS>EPSG:26958</SRS>' +
+' <SRS>EPSG:26959</SRS>' +
+' <SRS>EPSG:26960</SRS>' +
+' <SRS>EPSG:26961</SRS>' +
+' <SRS>EPSG:26962</SRS>' +
+' <SRS>EPSG:26963</SRS>' +
+' <SRS>EPSG:26964</SRS>' +
+' <SRS>EPSG:26965</SRS>' +
+' <SRS>EPSG:26966</SRS>' +
+' <SRS>EPSG:26967</SRS>' +
+' <SRS>EPSG:26968</SRS>' +
+' <SRS>EPSG:26969</SRS>' +
+' <SRS>EPSG:26970</SRS>' +
+' <SRS>EPSG:26971</SRS>' +
+' <SRS>EPSG:26972</SRS>' +
+' <SRS>EPSG:26973</SRS>' +
+' <SRS>EPSG:26974</SRS>' +
+' <SRS>EPSG:26975</SRS>' +
+' <SRS>EPSG:26976</SRS>' +
+' <SRS>EPSG:26977</SRS>' +
+' <SRS>EPSG:26978</SRS>' +
+' <SRS>EPSG:26979</SRS>' +
+' <SRS>EPSG:26980</SRS>' +
+' <SRS>EPSG:26981</SRS>' +
+' <SRS>EPSG:26982</SRS>' +
+' <SRS>EPSG:26983</SRS>' +
+' <SRS>EPSG:26984</SRS>' +
+' <SRS>EPSG:26985</SRS>' +
+' <SRS>EPSG:26986</SRS>' +
+' <SRS>EPSG:26987</SRS>' +
+' <SRS>EPSG:26988</SRS>' +
+' <SRS>EPSG:26989</SRS>' +
+' <SRS>EPSG:26990</SRS>' +
+' <SRS>EPSG:26991</SRS>' +
+' <SRS>EPSG:26992</SRS>' +
+' <SRS>EPSG:26993</SRS>' +
+' <SRS>EPSG:26994</SRS>' +
+' <SRS>EPSG:26995</SRS>' +
+' <SRS>EPSG:26996</SRS>' +
+' <SRS>EPSG:26997</SRS>' +
+' <SRS>EPSG:26998</SRS>' +
+' <SRS>EPSG:27037</SRS>' +
+' <SRS>EPSG:27038</SRS>' +
+' <SRS>EPSG:27039</SRS>' +
+' <SRS>EPSG:27040</SRS>' +
+' <SRS>EPSG:27120</SRS>' +
+' <SRS>EPSG:27200</SRS>' +
+' <SRS>EPSG:27205</SRS>' +
+' <SRS>EPSG:27206</SRS>' +
+' <SRS>EPSG:27207</SRS>' +
+' <SRS>EPSG:27208</SRS>' +
+' <SRS>EPSG:27209</SRS>' +
+' <SRS>EPSG:27210</SRS>' +
+' <SRS>EPSG:27211</SRS>' +
+' <SRS>EPSG:27212</SRS>' +
+' <SRS>EPSG:27213</SRS>' +
+' <SRS>EPSG:27214</SRS>' +
+' <SRS>EPSG:27215</SRS>' +
+' <SRS>EPSG:27216</SRS>' +
+' <SRS>EPSG:27217</SRS>' +
+' <SRS>EPSG:27218</SRS>' +
+' <SRS>EPSG:27219</SRS>' +
+' <SRS>EPSG:27220</SRS>' +
+' <SRS>EPSG:27221</SRS>' +
+' <SRS>EPSG:27222</SRS>' +
+' <SRS>EPSG:27223</SRS>' +
+' <SRS>EPSG:27224</SRS>' +
+' <SRS>EPSG:27225</SRS>' +
+' <SRS>EPSG:27226</SRS>' +
+' <SRS>EPSG:27227</SRS>' +
+' <SRS>EPSG:27228</SRS>' +
+' <SRS>EPSG:27229</SRS>' +
+' <SRS>EPSG:27230</SRS>' +
+' <SRS>EPSG:27231</SRS>' +
+' <SRS>EPSG:27232</SRS>' +
+' <SRS>EPSG:27258</SRS>' +
+' <SRS>EPSG:27259</SRS>' +
+' <SRS>EPSG:27260</SRS>' +
+' <SRS>EPSG:27291</SRS>' +
+' <SRS>EPSG:27292</SRS>' +
+' <SRS>EPSG:27391</SRS>' +
+' <SRS>EPSG:27392</SRS>' +
+' <SRS>EPSG:27393</SRS>' +
+' <SRS>EPSG:27394</SRS>' +
+' <SRS>EPSG:27395</SRS>' +
+' <SRS>EPSG:27396</SRS>' +
+' <SRS>EPSG:27397</SRS>' +
+' <SRS>EPSG:27398</SRS>' +
+' <SRS>EPSG:27429</SRS>' +
+' <SRS>EPSG:27492</SRS>' +
+' <SRS>EPSG:27500</SRS>' +
+' <SRS>EPSG:27561</SRS>' +
+' <SRS>EPSG:27562</SRS>' +
+' <SRS>EPSG:27563</SRS>' +
+' <SRS>EPSG:27564</SRS>' +
+' <SRS>EPSG:27571</SRS>' +
+' <SRS>EPSG:27572</SRS>' +
+' <SRS>EPSG:27573</SRS>' +
+' <SRS>EPSG:27574</SRS>' +
+' <SRS>EPSG:27581</SRS>' +
+' <SRS>EPSG:27582</SRS>' +
+' <SRS>EPSG:27583</SRS>' +
+' <SRS>EPSG:27584</SRS>' +
+' <SRS>EPSG:27591</SRS>' +
+' <SRS>EPSG:27592</SRS>' +
+' <SRS>EPSG:27593</SRS>' +
+' <SRS>EPSG:27594</SRS>' +
+' <SRS>EPSG:27700</SRS>' +
+' <SRS>EPSG:28191</SRS>' +
+' <SRS>EPSG:28192</SRS>' +
+' <SRS>EPSG:28193</SRS>' +
+' <SRS>EPSG:28232</SRS>' +
+' <SRS>EPSG:28348</SRS>' +
+' <SRS>EPSG:28349</SRS>' +
+' <SRS>EPSG:28350</SRS>' +
+' <SRS>EPSG:28351</SRS>' +
+' <SRS>EPSG:28352</SRS>' +
+' <SRS>EPSG:28353</SRS>' +
+' <SRS>EPSG:28354</SRS>' +
+' <SRS>EPSG:28355</SRS>' +
+' <SRS>EPSG:28356</SRS>' +
+' <SRS>EPSG:28357</SRS>' +
+' <SRS>EPSG:28358</SRS>' +
+' <SRS>EPSG:28402</SRS>' +
+' <SRS>EPSG:28403</SRS>' +
+' <SRS>EPSG:28404</SRS>' +
+' <SRS>EPSG:28405</SRS>' +
+' <SRS>EPSG:28406</SRS>' +
+' <SRS>EPSG:28407</SRS>' +
+' <SRS>EPSG:28408</SRS>' +
+' <SRS>EPSG:28409</SRS>' +
+' <SRS>EPSG:28410</SRS>' +
+' <SRS>EPSG:28411</SRS>' +
+' <SRS>EPSG:28412</SRS>' +
+' <SRS>EPSG:28413</SRS>' +
+' <SRS>EPSG:28414</SRS>' +
+' <SRS>EPSG:28415</SRS>' +
+' <SRS>EPSG:28416</SRS>' +
+' <SRS>EPSG:28417</SRS>' +
+' <SRS>EPSG:28418</SRS>' +
+' <SRS>EPSG:28419</SRS>' +
+' <SRS>EPSG:28420</SRS>' +
+' <SRS>EPSG:28421</SRS>' +
+' <SRS>EPSG:28422</SRS>' +
+' <SRS>EPSG:28423</SRS>' +
+' <SRS>EPSG:28424</SRS>' +
+' <SRS>EPSG:28425</SRS>' +
+' <SRS>EPSG:28426</SRS>' +
+' <SRS>EPSG:28427</SRS>' +
+' <SRS>EPSG:28428</SRS>' +
+' <SRS>EPSG:28429</SRS>' +
+' <SRS>EPSG:28430</SRS>' +
+' <SRS>EPSG:28431</SRS>' +
+' <SRS>EPSG:28432</SRS>' +
+' <SRS>EPSG:28462</SRS>' +
+' <SRS>EPSG:28463</SRS>' +
+' <SRS>EPSG:28464</SRS>' +
+' <SRS>EPSG:28465</SRS>' +
+' <SRS>EPSG:28466</SRS>' +
+' <SRS>EPSG:28467</SRS>' +
+' <SRS>EPSG:28468</SRS>' +
+' <SRS>EPSG:28469</SRS>' +
+' <SRS>EPSG:28470</SRS>' +
+' <SRS>EPSG:28471</SRS>' +
+' <SRS>EPSG:28472</SRS>' +
+' <SRS>EPSG:28473</SRS>' +
+' <SRS>EPSG:28474</SRS>' +
+' <SRS>EPSG:28475</SRS>' +
+' <SRS>EPSG:28476</SRS>' +
+' <SRS>EPSG:28477</SRS>' +
+' <SRS>EPSG:28478</SRS>' +
+' <SRS>EPSG:28479</SRS>' +
+' <SRS>EPSG:28480</SRS>' +
+' <SRS>EPSG:28481</SRS>' +
+' <SRS>EPSG:28482</SRS>' +
+' <SRS>EPSG:28483</SRS>' +
+' <SRS>EPSG:28484</SRS>' +
+' <SRS>EPSG:28485</SRS>' +
+' <SRS>EPSG:28486</SRS>' +
+' <SRS>EPSG:28487</SRS>' +
+' <SRS>EPSG:28488</SRS>' +
+' <SRS>EPSG:28489</SRS>' +
+' <SRS>EPSG:28490</SRS>' +
+' <SRS>EPSG:28491</SRS>' +
+' <SRS>EPSG:28492</SRS>' +
+' <SRS>EPSG:28600</SRS>' +
+' <SRS>EPSG:28991</SRS>' +
+' <SRS>EPSG:28992</SRS>' +
+' <SRS>EPSG:29100</SRS>' +
+' <SRS>EPSG:29101</SRS>' +
+' <SRS>EPSG:29118</SRS>' +
+' <SRS>EPSG:29119</SRS>' +
+' <SRS>EPSG:29120</SRS>' +
+' <SRS>EPSG:29121</SRS>' +
+' <SRS>EPSG:29122</SRS>' +
+' <SRS>EPSG:29168</SRS>' +
+' <SRS>EPSG:29169</SRS>' +
+' <SRS>EPSG:29170</SRS>' +
+' <SRS>EPSG:29171</SRS>' +
+' <SRS>EPSG:29172</SRS>' +
+' <SRS>EPSG:29177</SRS>' +
+' <SRS>EPSG:29178</SRS>' +
+' <SRS>EPSG:29179</SRS>' +
+' <SRS>EPSG:29180</SRS>' +
+' <SRS>EPSG:29181</SRS>' +
+' <SRS>EPSG:29182</SRS>' +
+' <SRS>EPSG:29183</SRS>' +
+' <SRS>EPSG:29184</SRS>' +
+' <SRS>EPSG:29185</SRS>' +
+' <SRS>EPSG:29187</SRS>' +
+' <SRS>EPSG:29188</SRS>' +
+' <SRS>EPSG:29189</SRS>' +
+' <SRS>EPSG:29190</SRS>' +
+' <SRS>EPSG:29191</SRS>' +
+' <SRS>EPSG:29192</SRS>' +
+' <SRS>EPSG:29193</SRS>' +
+' <SRS>EPSG:29194</SRS>' +
+' <SRS>EPSG:29195</SRS>' +
+' <SRS>EPSG:29220</SRS>' +
+' <SRS>EPSG:29221</SRS>' +
+' <SRS>EPSG:29333</SRS>' +
+' <SRS>EPSG:29371</SRS>' +
+' <SRS>EPSG:29373</SRS>' +
+' <SRS>EPSG:29375</SRS>' +
+' <SRS>EPSG:29377</SRS>' +
+' <SRS>EPSG:29379</SRS>' +
+' <SRS>EPSG:29381</SRS>' +
+' <SRS>EPSG:29383</SRS>' +
+' <SRS>EPSG:29385</SRS>' +
+' <SRS>EPSG:29635</SRS>' +
+' <SRS>EPSG:29636</SRS>' +
+' <SRS>EPSG:29700</SRS>' +
+' <SRS>EPSG:29701</SRS>' +
+' <SRS>EPSG:29702</SRS>' +
+' <SRS>EPSG:29738</SRS>' +
+' <SRS>EPSG:29739</SRS>' +
+' <SRS>EPSG:29849</SRS>' +
+' <SRS>EPSG:29850</SRS>' +
+' <SRS>EPSG:29871</SRS>' +
+' <SRS>EPSG:29872</SRS>' +
+' <SRS>EPSG:29873</SRS>' +
+' <SRS>EPSG:29900</SRS>' +
+' <SRS>EPSG:29901</SRS>' +
+' <SRS>EPSG:29902</SRS>' +
+' <SRS>EPSG:29903</SRS>' +
+' <SRS>EPSG:30161</SRS>' +
+' <SRS>EPSG:30162</SRS>' +
+' <SRS>EPSG:30163</SRS>' +
+' <SRS>EPSG:30164</SRS>' +
+' <SRS>EPSG:30165</SRS>' +
+' <SRS>EPSG:30166</SRS>' +
+' <SRS>EPSG:30167</SRS>' +
+' <SRS>EPSG:30168</SRS>' +
+' <SRS>EPSG:30169</SRS>' +
+' <SRS>EPSG:30170</SRS>' +
+' <SRS>EPSG:30171</SRS>' +
+' <SRS>EPSG:30172</SRS>' +
+' <SRS>EPSG:30173</SRS>' +
+' <SRS>EPSG:30174</SRS>' +
+' <SRS>EPSG:30175</SRS>' +
+' <SRS>EPSG:30176</SRS>' +
+' <SRS>EPSG:30177</SRS>' +
+' <SRS>EPSG:30178</SRS>' +
+' <SRS>EPSG:30179</SRS>' +
+' <SRS>EPSG:30200</SRS>' +
+' <SRS>EPSG:30339</SRS>' +
+' <SRS>EPSG:30340</SRS>' +
+' <SRS>EPSG:30491</SRS>' +
+' <SRS>EPSG:30492</SRS>' +
+' <SRS>EPSG:30493</SRS>' +
+' <SRS>EPSG:30494</SRS>' +
+' <SRS>EPSG:30729</SRS>' +
+' <SRS>EPSG:30730</SRS>' +
+' <SRS>EPSG:30731</SRS>' +
+' <SRS>EPSG:30732</SRS>' +
+' <SRS>EPSG:30791</SRS>' +
+' <SRS>EPSG:30792</SRS>' +
+' <SRS>EPSG:30800</SRS>' +
+' <SRS>EPSG:31028</SRS>' +
+' <SRS>EPSG:31121</SRS>' +
+' <SRS>EPSG:31154</SRS>' +
+' <SRS>EPSG:31170</SRS>' +
+' <SRS>EPSG:31171</SRS>' +
+' <SRS>EPSG:31251</SRS>' +
+' <SRS>EPSG:31252</SRS>' +
+' <SRS>EPSG:31253</SRS>' +
+' <SRS>EPSG:31254</SRS>' +
+' <SRS>EPSG:31255</SRS>' +
+' <SRS>EPSG:31256</SRS>' +
+' <SRS>EPSG:31257</SRS>' +
+' <SRS>EPSG:31258</SRS>' +
+' <SRS>EPSG:31259</SRS>' +
+' <SRS>EPSG:31265</SRS>' +
+' <SRS>EPSG:31266</SRS>' +
+' <SRS>EPSG:31267</SRS>' +
+' <SRS>EPSG:31268</SRS>' +
+' <SRS>EPSG:31275</SRS>' +
+' <SRS>EPSG:31276</SRS>' +
+' <SRS>EPSG:31277</SRS>' +
+' <SRS>EPSG:31278</SRS>' +
+' <SRS>EPSG:31279</SRS>' +
+' <SRS>EPSG:31281</SRS>' +
+' <SRS>EPSG:31282</SRS>' +
+' <SRS>EPSG:31283</SRS>' +
+' <SRS>EPSG:31284</SRS>' +
+' <SRS>EPSG:31285</SRS>' +
+' <SRS>EPSG:31286</SRS>' +
+' <SRS>EPSG:31287</SRS>' +
+' <SRS>EPSG:31288</SRS>' +
+' <SRS>EPSG:31289</SRS>' +
+' <SRS>EPSG:31290</SRS>' +
+' <SRS>EPSG:31291</SRS>' +
+' <SRS>EPSG:31292</SRS>' +
+' <SRS>EPSG:31293</SRS>' +
+' <SRS>EPSG:31294</SRS>' +
+' <SRS>EPSG:31295</SRS>' +
+' <SRS>EPSG:31296</SRS>' +
+' <SRS>EPSG:31297</SRS>' +
+' <SRS>EPSG:31300</SRS>' +
+' <SRS>EPSG:31370</SRS>' +
+' <SRS>EPSG:31461</SRS>' +
+' <SRS>EPSG:31462</SRS>' +
+' <SRS>EPSG:31463</SRS>' +
+' <SRS>EPSG:31464</SRS>' +
+' <SRS>EPSG:31465</SRS>' +
+' <SRS>EPSG:31466</SRS>' +
+' <SRS>EPSG:31467</SRS>' +
+' <SRS>EPSG:31468</SRS>' +
+' <SRS>EPSG:31469</SRS>' +
+' <SRS>EPSG:31528</SRS>' +
+' <SRS>EPSG:31529</SRS>' +
+' <SRS>EPSG:31600</SRS>' +
+' <SRS>EPSG:31700</SRS>' +
+' <SRS>EPSG:31838</SRS>' +
+' <SRS>EPSG:31839</SRS>' +
+' <SRS>EPSG:31900</SRS>' +
+' <SRS>EPSG:31901</SRS>' +
+' <SRS>EPSG:31965</SRS>' +
+' <SRS>EPSG:31966</SRS>' +
+' <SRS>EPSG:31967</SRS>' +
+' <SRS>EPSG:31968</SRS>' +
+' <SRS>EPSG:31969</SRS>' +
+' <SRS>EPSG:31970</SRS>' +
+' <SRS>EPSG:31971</SRS>' +
+' <SRS>EPSG:31972</SRS>' +
+' <SRS>EPSG:31973</SRS>' +
+' <SRS>EPSG:31974</SRS>' +
+' <SRS>EPSG:31975</SRS>' +
+' <SRS>EPSG:31976</SRS>' +
+' <SRS>EPSG:31977</SRS>' +
+' <SRS>EPSG:31978</SRS>' +
+' <SRS>EPSG:31979</SRS>' +
+' <SRS>EPSG:31980</SRS>' +
+' <SRS>EPSG:31981</SRS>' +
+' <SRS>EPSG:31982</SRS>' +
+' <SRS>EPSG:31983</SRS>' +
+' <SRS>EPSG:31984</SRS>' +
+' <SRS>EPSG:31985</SRS>' +
+' <SRS>EPSG:31986</SRS>' +
+' <SRS>EPSG:31987</SRS>' +
+' <SRS>EPSG:31988</SRS>' +
+' <SRS>EPSG:31989</SRS>' +
+' <SRS>EPSG:31990</SRS>' +
+' <SRS>EPSG:31991</SRS>' +
+' <SRS>EPSG:31992</SRS>' +
+' <SRS>EPSG:31993</SRS>' +
+' <SRS>EPSG:31994</SRS>' +
+' <SRS>EPSG:31995</SRS>' +
+' <SRS>EPSG:31996</SRS>' +
+' <SRS>EPSG:31997</SRS>' +
+' <SRS>EPSG:31998</SRS>' +
+' <SRS>EPSG:31999</SRS>' +
+' <SRS>EPSG:32000</SRS>' +
+' <SRS>EPSG:32001</SRS>' +
+' <SRS>EPSG:32002</SRS>' +
+' <SRS>EPSG:32003</SRS>' +
+' <SRS>EPSG:32005</SRS>' +
+' <SRS>EPSG:32006</SRS>' +
+' <SRS>EPSG:32007</SRS>' +
+' <SRS>EPSG:32008</SRS>' +
+' <SRS>EPSG:32009</SRS>' +
+' <SRS>EPSG:32010</SRS>' +
+' <SRS>EPSG:32011</SRS>' +
+' <SRS>EPSG:32012</SRS>' +
+' <SRS>EPSG:32013</SRS>' +
+' <SRS>EPSG:32014</SRS>' +
+' <SRS>EPSG:32015</SRS>' +
+' <SRS>EPSG:32016</SRS>' +
+' <SRS>EPSG:32017</SRS>' +
+' <SRS>EPSG:32018</SRS>' +
+' <SRS>EPSG:32019</SRS>' +
+' <SRS>EPSG:32020</SRS>' +
+' <SRS>EPSG:32021</SRS>' +
+' <SRS>EPSG:32022</SRS>' +
+' <SRS>EPSG:32023</SRS>' +
+' <SRS>EPSG:32024</SRS>' +
+' <SRS>EPSG:32025</SRS>' +
+' <SRS>EPSG:32026</SRS>' +
+' <SRS>EPSG:32027</SRS>' +
+' <SRS>EPSG:32028</SRS>' +
+' <SRS>EPSG:32029</SRS>' +
+' <SRS>EPSG:32030</SRS>' +
+' <SRS>EPSG:32031</SRS>' +
+' <SRS>EPSG:32033</SRS>' +
+' <SRS>EPSG:32034</SRS>' +
+' <SRS>EPSG:32035</SRS>' +
+' <SRS>EPSG:32036</SRS>' +
+' <SRS>EPSG:32037</SRS>' +
+' <SRS>EPSG:32038</SRS>' +
+' <SRS>EPSG:32039</SRS>' +
+' <SRS>EPSG:32040</SRS>' +
+' <SRS>EPSG:32041</SRS>' +
+' <SRS>EPSG:32042</SRS>' +
+' <SRS>EPSG:32043</SRS>' +
+' <SRS>EPSG:32044</SRS>' +
+' <SRS>EPSG:32045</SRS>' +
+' <SRS>EPSG:32046</SRS>' +
+' <SRS>EPSG:32047</SRS>' +
+' <SRS>EPSG:32048</SRS>' +
+' <SRS>EPSG:32049</SRS>' +
+' <SRS>EPSG:32050</SRS>' +
+' <SRS>EPSG:32051</SRS>' +
+' <SRS>EPSG:32052</SRS>' +
+' <SRS>EPSG:32053</SRS>' +
+' <SRS>EPSG:32054</SRS>' +
+' <SRS>EPSG:32055</SRS>' +
+' <SRS>EPSG:32056</SRS>' +
+' <SRS>EPSG:32057</SRS>' +
+' <SRS>EPSG:32058</SRS>' +
+' <SRS>EPSG:32061</SRS>' +
+' <SRS>EPSG:32062</SRS>' +
+' <SRS>EPSG:32064</SRS>' +
+' <SRS>EPSG:32065</SRS>' +
+' <SRS>EPSG:32066</SRS>' +
+' <SRS>EPSG:32067</SRS>' +
+' <SRS>EPSG:32074</SRS>' +
+' <SRS>EPSG:32075</SRS>' +
+' <SRS>EPSG:32076</SRS>' +
+' <SRS>EPSG:32077</SRS>' +
+' <SRS>EPSG:32081</SRS>' +
+' <SRS>EPSG:32082</SRS>' +
+' <SRS>EPSG:32083</SRS>' +
+' <SRS>EPSG:32084</SRS>' +
+' <SRS>EPSG:32085</SRS>' +
+' <SRS>EPSG:32086</SRS>' +
+' <SRS>EPSG:32098</SRS>' +
+' <SRS>EPSG:32099</SRS>' +
+' <SRS>EPSG:32100</SRS>' +
+' <SRS>EPSG:32104</SRS>' +
+' <SRS>EPSG:32107</SRS>' +
+' <SRS>EPSG:32108</SRS>' +
+' <SRS>EPSG:32109</SRS>' +
+' <SRS>EPSG:32110</SRS>' +
+' <SRS>EPSG:32111</SRS>' +
+' <SRS>EPSG:32112</SRS>' +
+' <SRS>EPSG:32113</SRS>' +
+' <SRS>EPSG:32114</SRS>' +
+' <SRS>EPSG:32115</SRS>' +
+' <SRS>EPSG:32116</SRS>' +
+' <SRS>EPSG:32117</SRS>' +
+' <SRS>EPSG:32118</SRS>' +
+' <SRS>EPSG:32119</SRS>' +
+' <SRS>EPSG:32120</SRS>' +
+' <SRS>EPSG:32121</SRS>' +
+' <SRS>EPSG:32122</SRS>' +
+' <SRS>EPSG:32123</SRS>' +
+' <SRS>EPSG:32124</SRS>' +
+' <SRS>EPSG:32125</SRS>' +
+' <SRS>EPSG:32126</SRS>' +
+' <SRS>EPSG:32127</SRS>' +
+' <SRS>EPSG:32128</SRS>' +
+' <SRS>EPSG:32129</SRS>' +
+' <SRS>EPSG:32130</SRS>' +
+' <SRS>EPSG:32133</SRS>' +
+' <SRS>EPSG:32134</SRS>' +
+' <SRS>EPSG:32135</SRS>' +
+' <SRS>EPSG:32136</SRS>' +
+' <SRS>EPSG:32137</SRS>' +
+' <SRS>EPSG:32138</SRS>' +
+' <SRS>EPSG:32139</SRS>' +
+' <SRS>EPSG:32140</SRS>' +
+' <SRS>EPSG:32141</SRS>' +
+' <SRS>EPSG:32142</SRS>' +
+' <SRS>EPSG:32143</SRS>' +
+' <SRS>EPSG:32144</SRS>' +
+' <SRS>EPSG:32145</SRS>' +
+' <SRS>EPSG:32146</SRS>' +
+' <SRS>EPSG:32147</SRS>' +
+' <SRS>EPSG:32148</SRS>' +
+' <SRS>EPSG:32149</SRS>' +
+' <SRS>EPSG:32150</SRS>' +
+' <SRS>EPSG:32151</SRS>' +
+' <SRS>EPSG:32152</SRS>' +
+' <SRS>EPSG:32153</SRS>' +
+' <SRS>EPSG:32154</SRS>' +
+' <SRS>EPSG:32155</SRS>' +
+' <SRS>EPSG:32156</SRS>' +
+' <SRS>EPSG:32157</SRS>' +
+' <SRS>EPSG:32158</SRS>' +
+' <SRS>EPSG:32161</SRS>' +
+' <SRS>EPSG:32164</SRS>' +
+' <SRS>EPSG:32165</SRS>' +
+' <SRS>EPSG:32166</SRS>' +
+' <SRS>EPSG:32167</SRS>' +
+' <SRS>EPSG:32180</SRS>' +
+' <SRS>EPSG:32181</SRS>' +
+' <SRS>EPSG:32182</SRS>' +
+' <SRS>EPSG:32183</SRS>' +
+' <SRS>EPSG:32184</SRS>' +
+' <SRS>EPSG:32185</SRS>' +
+' <SRS>EPSG:32186</SRS>' +
+' <SRS>EPSG:32187</SRS>' +
+' <SRS>EPSG:32188</SRS>' +
+' <SRS>EPSG:32189</SRS>' +
+' <SRS>EPSG:32190</SRS>' +
+' <SRS>EPSG:32191</SRS>' +
+' <SRS>EPSG:32192</SRS>' +
+' <SRS>EPSG:32193</SRS>' +
+' <SRS>EPSG:32194</SRS>' +
+' <SRS>EPSG:32195</SRS>' +
+' <SRS>EPSG:32196</SRS>' +
+' <SRS>EPSG:32197</SRS>' +
+' <SRS>EPSG:32198</SRS>' +
+' <SRS>EPSG:32199</SRS>' +
+' <SRS>EPSG:32201</SRS>' +
+' <SRS>EPSG:32202</SRS>' +
+' <SRS>EPSG:32203</SRS>' +
+' <SRS>EPSG:32204</SRS>' +
+' <SRS>EPSG:32205</SRS>' +
+' <SRS>EPSG:32206</SRS>' +
+' <SRS>EPSG:32207</SRS>' +
+' <SRS>EPSG:32208</SRS>' +
+' <SRS>EPSG:32209</SRS>' +
+' <SRS>EPSG:32210</SRS>' +
+' <SRS>EPSG:32211</SRS>' +
+' <SRS>EPSG:32212</SRS>' +
+' <SRS>EPSG:32213</SRS>' +
+' <SRS>EPSG:32214</SRS>' +
+' <SRS>EPSG:32215</SRS>' +
+' <SRS>EPSG:32216</SRS>' +
+' <SRS>EPSG:32217</SRS>' +
+' <SRS>EPSG:32218</SRS>' +
+' <SRS>EPSG:32219</SRS>' +
+' <SRS>EPSG:32220</SRS>' +
+' <SRS>EPSG:32221</SRS>' +
+' <SRS>EPSG:32222</SRS>' +
+' <SRS>EPSG:32223</SRS>' +
+' <SRS>EPSG:32224</SRS>' +
+' <SRS>EPSG:32225</SRS>' +
+' <SRS>EPSG:32226</SRS>' +
+' <SRS>EPSG:32227</SRS>' +
+' <SRS>EPSG:32228</SRS>' +
+' <SRS>EPSG:32229</SRS>' +
+' <SRS>EPSG:32230</SRS>' +
+' <SRS>EPSG:32231</SRS>' +
+' <SRS>EPSG:32232</SRS>' +
+' <SRS>EPSG:32233</SRS>' +
+' <SRS>EPSG:32234</SRS>' +
+' <SRS>EPSG:32235</SRS>' +
+' <SRS>EPSG:32236</SRS>' +
+' <SRS>EPSG:32237</SRS>' +
+' <SRS>EPSG:32238</SRS>' +
+' <SRS>EPSG:32239</SRS>' +
+' <SRS>EPSG:32240</SRS>' +
+' <SRS>EPSG:32241</SRS>' +
+' <SRS>EPSG:32242</SRS>' +
+' <SRS>EPSG:32243</SRS>' +
+' <SRS>EPSG:32244</SRS>' +
+' <SRS>EPSG:32245</SRS>' +
+' <SRS>EPSG:32246</SRS>' +
+' <SRS>EPSG:32247</SRS>' +
+' <SRS>EPSG:32248</SRS>' +
+' <SRS>EPSG:32249</SRS>' +
+' <SRS>EPSG:32250</SRS>' +
+' <SRS>EPSG:32251</SRS>' +
+' <SRS>EPSG:32252</SRS>' +
+' <SRS>EPSG:32253</SRS>' +
+' <SRS>EPSG:32254</SRS>' +
+' <SRS>EPSG:32255</SRS>' +
+' <SRS>EPSG:32256</SRS>' +
+' <SRS>EPSG:32257</SRS>' +
+' <SRS>EPSG:32258</SRS>' +
+' <SRS>EPSG:32259</SRS>' +
+' <SRS>EPSG:32260</SRS>' +
+' <SRS>EPSG:32301</SRS>' +
+' <SRS>EPSG:32302</SRS>' +
+' <SRS>EPSG:32303</SRS>' +
+' <SRS>EPSG:32304</SRS>' +
+' <SRS>EPSG:32305</SRS>' +
+' <SRS>EPSG:32306</SRS>' +
+' <SRS>EPSG:32307</SRS>' +
+' <SRS>EPSG:32308</SRS>' +
+' <SRS>EPSG:32309</SRS>' +
+' <SRS>EPSG:32310</SRS>' +
+' <SRS>EPSG:32311</SRS>' +
+' <SRS>EPSG:32312</SRS>' +
+' <SRS>EPSG:32313</SRS>' +
+' <SRS>EPSG:32314</SRS>' +
+' <SRS>EPSG:32315</SRS>' +
+' <SRS>EPSG:32316</SRS>' +
+' <SRS>EPSG:32317</SRS>' +
+' <SRS>EPSG:32318</SRS>' +
+' <SRS>EPSG:32319</SRS>' +
+' <SRS>EPSG:32320</SRS>' +
+' <SRS>EPSG:32321</SRS>' +
+' <SRS>EPSG:32322</SRS>' +
+' <SRS>EPSG:32323</SRS>' +
+' <SRS>EPSG:32324</SRS>' +
+' <SRS>EPSG:32325</SRS>' +
+' <SRS>EPSG:32326</SRS>' +
+' <SRS>EPSG:32327</SRS>' +
+' <SRS>EPSG:32328</SRS>' +
+' <SRS>EPSG:32329</SRS>' +
+' <SRS>EPSG:32330</SRS>' +
+' <SRS>EPSG:32331</SRS>' +
+' <SRS>EPSG:32332</SRS>' +
+' <SRS>EPSG:32333</SRS>' +
+' <SRS>EPSG:32334</SRS>' +
+' <SRS>EPSG:32335</SRS>' +
+' <SRS>EPSG:32336</SRS>' +
+' <SRS>EPSG:32337</SRS>' +
+' <SRS>EPSG:32338</SRS>' +
+' <SRS>EPSG:32339</SRS>' +
+' <SRS>EPSG:32340</SRS>' +
+' <SRS>EPSG:32341</SRS>' +
+' <SRS>EPSG:32342</SRS>' +
+' <SRS>EPSG:32343</SRS>' +
+' <SRS>EPSG:32344</SRS>' +
+' <SRS>EPSG:32345</SRS>' +
+' <SRS>EPSG:32346</SRS>' +
+' <SRS>EPSG:32347</SRS>' +
+' <SRS>EPSG:32348</SRS>' +
+' <SRS>EPSG:32349</SRS>' +
+' <SRS>EPSG:32350</SRS>' +
+' <SRS>EPSG:32351</SRS>' +
+' <SRS>EPSG:32352</SRS>' +
+' <SRS>EPSG:32353</SRS>' +
+' <SRS>EPSG:32354</SRS>' +
+' <SRS>EPSG:32355</SRS>' +
+' <SRS>EPSG:32356</SRS>' +
+' <SRS>EPSG:32357</SRS>' +
+' <SRS>EPSG:32358</SRS>' +
+' <SRS>EPSG:32359</SRS>' +
+' <SRS>EPSG:32360</SRS>' +
+' <SRS>EPSG:32401</SRS>' +
+' <SRS>EPSG:32402</SRS>' +
+' <SRS>EPSG:32403</SRS>' +
+' <SRS>EPSG:32404</SRS>' +
+' <SRS>EPSG:32405</SRS>' +
+' <SRS>EPSG:32406</SRS>' +
+' <SRS>EPSG:32407</SRS>' +
+' <SRS>EPSG:32408</SRS>' +
+' <SRS>EPSG:32409</SRS>' +
+' <SRS>EPSG:32410</SRS>' +
+' <SRS>EPSG:32411</SRS>' +
+' <SRS>EPSG:32412</SRS>' +
+' <SRS>EPSG:32413</SRS>' +
+' <SRS>EPSG:32414</SRS>' +
+' <SRS>EPSG:32415</SRS>' +
+' <SRS>EPSG:32416</SRS>' +
+' <SRS>EPSG:32417</SRS>' +
+' <SRS>EPSG:32418</SRS>' +
+' <SRS>EPSG:32419</SRS>' +
+' <SRS>EPSG:32420</SRS>' +
+' <SRS>EPSG:32421</SRS>' +
+' <SRS>EPSG:32422</SRS>' +
+' <SRS>EPSG:32423</SRS>' +
+' <SRS>EPSG:32424</SRS>' +
+' <SRS>EPSG:32425</SRS>' +
+' <SRS>EPSG:32426</SRS>' +
+' <SRS>EPSG:32427</SRS>' +
+' <SRS>EPSG:32428</SRS>' +
+' <SRS>EPSG:32429</SRS>' +
+' <SRS>EPSG:32430</SRS>' +
+' <SRS>EPSG:32431</SRS>' +
+' <SRS>EPSG:32432</SRS>' +
+' <SRS>EPSG:32433</SRS>' +
+' <SRS>EPSG:32434</SRS>' +
+' <SRS>EPSG:32435</SRS>' +
+' <SRS>EPSG:32436</SRS>' +
+' <SRS>EPSG:32437</SRS>' +
+' <SRS>EPSG:32438</SRS>' +
+' <SRS>EPSG:32439</SRS>' +
+' <SRS>EPSG:32440</SRS>' +
+' <SRS>EPSG:32441</SRS>' +
+' <SRS>EPSG:32442</SRS>' +
+' <SRS>EPSG:32443</SRS>' +
+' <SRS>EPSG:32444</SRS>' +
+' <SRS>EPSG:32445</SRS>' +
+' <SRS>EPSG:32446</SRS>' +
+' <SRS>EPSG:32447</SRS>' +
+' <SRS>EPSG:32448</SRS>' +
+' <SRS>EPSG:32449</SRS>' +
+' <SRS>EPSG:32450</SRS>' +
+' <SRS>EPSG:32451</SRS>' +
+' <SRS>EPSG:32452</SRS>' +
+' <SRS>EPSG:32453</SRS>' +
+' <SRS>EPSG:32454</SRS>' +
+' <SRS>EPSG:32455</SRS>' +
+' <SRS>EPSG:32456</SRS>' +
+' <SRS>EPSG:32457</SRS>' +
+' <SRS>EPSG:32458</SRS>' +
+' <SRS>EPSG:32459</SRS>' +
+' <SRS>EPSG:32460</SRS>' +
+' <SRS>EPSG:32501</SRS>' +
+' <SRS>EPSG:32502</SRS>' +
+' <SRS>EPSG:32503</SRS>' +
+' <SRS>EPSG:32504</SRS>' +
+' <SRS>EPSG:32505</SRS>' +
+' <SRS>EPSG:32506</SRS>' +
+' <SRS>EPSG:32507</SRS>' +
+' <SRS>EPSG:32508</SRS>' +
+' <SRS>EPSG:32509</SRS>' +
+' <SRS>EPSG:32510</SRS>' +
+' <SRS>EPSG:32511</SRS>' +
+' <SRS>EPSG:32512</SRS>' +
+' <SRS>EPSG:32513</SRS>' +
+' <SRS>EPSG:32514</SRS>' +
+' <SRS>EPSG:32515</SRS>' +
+' <SRS>EPSG:32516</SRS>' +
+' <SRS>EPSG:32517</SRS>' +
+' <SRS>EPSG:32518</SRS>' +
+' <SRS>EPSG:32519</SRS>' +
+' <SRS>EPSG:32520</SRS>' +
+' <SRS>EPSG:32521</SRS>' +
+' <SRS>EPSG:32522</SRS>' +
+' <SRS>EPSG:32523</SRS>' +
+' <SRS>EPSG:32524</SRS>' +
+' <SRS>EPSG:32525</SRS>' +
+' <SRS>EPSG:32526</SRS>' +
+' <SRS>EPSG:32527</SRS>' +
+' <SRS>EPSG:32528</SRS>' +
+' <SRS>EPSG:32529</SRS>' +
+' <SRS>EPSG:32530</SRS>' +
+' <SRS>EPSG:32531</SRS>' +
+' <SRS>EPSG:32532</SRS>' +
+' <SRS>EPSG:32533</SRS>' +
+' <SRS>EPSG:32534</SRS>' +
+' <SRS>EPSG:32535</SRS>' +
+' <SRS>EPSG:32536</SRS>' +
+' <SRS>EPSG:32537</SRS>' +
+' <SRS>EPSG:32538</SRS>' +
+' <SRS>EPSG:32539</SRS>' +
+' <SRS>EPSG:32540</SRS>' +
+' <SRS>EPSG:32541</SRS>' +
+' <SRS>EPSG:32542</SRS>' +
+' <SRS>EPSG:32543</SRS>' +
+' <SRS>EPSG:32544</SRS>' +
+' <SRS>EPSG:32545</SRS>' +
+' <SRS>EPSG:32546</SRS>' +
+' <SRS>EPSG:32547</SRS>' +
+' <SRS>EPSG:32548</SRS>' +
+' <SRS>EPSG:32549</SRS>' +
+' <SRS>EPSG:32550</SRS>' +
+' <SRS>EPSG:32551</SRS>' +
+' <SRS>EPSG:32552</SRS>' +
+' <SRS>EPSG:32553</SRS>' +
+' <SRS>EPSG:32554</SRS>' +
+' <SRS>EPSG:32555</SRS>' +
+' <SRS>EPSG:32556</SRS>' +
+' <SRS>EPSG:32557</SRS>' +
+' <SRS>EPSG:32558</SRS>' +
+' <SRS>EPSG:32559</SRS>' +
+' <SRS>EPSG:32560</SRS>' +
+' <SRS>EPSG:32600</SRS>' +
+' <SRS>EPSG:32601</SRS>' +
+' <SRS>EPSG:32602</SRS>' +
+' <SRS>EPSG:32603</SRS>' +
+' <SRS>EPSG:32604</SRS>' +
+' <SRS>EPSG:32605</SRS>' +
+' <SRS>EPSG:32606</SRS>' +
+' <SRS>EPSG:32607</SRS>' +
+' <SRS>EPSG:32608</SRS>' +
+' <SRS>EPSG:32609</SRS>' +
+' <SRS>EPSG:32610</SRS>' +
+' <SRS>EPSG:32611</SRS>' +
+' <SRS>EPSG:32612</SRS>' +
+' <SRS>EPSG:32613</SRS>' +
+' <SRS>EPSG:32614</SRS>' +
+' <SRS>EPSG:32615</SRS>' +
+' <SRS>EPSG:32616</SRS>' +
+' <SRS>EPSG:32617</SRS>' +
+' <SRS>EPSG:32618</SRS>' +
+' <SRS>EPSG:32619</SRS>' +
+' <SRS>EPSG:32620</SRS>' +
+' <SRS>EPSG:32621</SRS>' +
+' <SRS>EPSG:32622</SRS>' +
+' <SRS>EPSG:32623</SRS>' +
+' <SRS>EPSG:32624</SRS>' +
+' <SRS>EPSG:32625</SRS>' +
+' <SRS>EPSG:32626</SRS>' +
+' <SRS>EPSG:32627</SRS>' +
+' <SRS>EPSG:32628</SRS>' +
+' <SRS>EPSG:32629</SRS>' +
+' <SRS>EPSG:32630</SRS>' +
+' <SRS>EPSG:32631</SRS>' +
+' <SRS>EPSG:32632</SRS>' +
+' <SRS>EPSG:32633</SRS>' +
+' <SRS>EPSG:32634</SRS>' +
+' <SRS>EPSG:32635</SRS>' +
+' <SRS>EPSG:32636</SRS>' +
+' <SRS>EPSG:32637</SRS>' +
+' <SRS>EPSG:32638</SRS>' +
+' <SRS>EPSG:32639</SRS>' +
+' <SRS>EPSG:32640</SRS>' +
+' <SRS>EPSG:32641</SRS>' +
+' <SRS>EPSG:32642</SRS>' +
+' <SRS>EPSG:32643</SRS>' +
+' <SRS>EPSG:32644</SRS>' +
+' <SRS>EPSG:32645</SRS>' +
+' <SRS>EPSG:32646</SRS>' +
+' <SRS>EPSG:32647</SRS>' +
+' <SRS>EPSG:32648</SRS>' +
+' <SRS>EPSG:32649</SRS>' +
+' <SRS>EPSG:32650</SRS>' +
+' <SRS>EPSG:32651</SRS>' +
+' <SRS>EPSG:32652</SRS>' +
+' <SRS>EPSG:32653</SRS>' +
+' <SRS>EPSG:32654</SRS>' +
+' <SRS>EPSG:32655</SRS>' +
+' <SRS>EPSG:32656</SRS>' +
+' <SRS>EPSG:32657</SRS>' +
+' <SRS>EPSG:32658</SRS>' +
+' <SRS>EPSG:32659</SRS>' +
+' <SRS>EPSG:32660</SRS>' +
+' <SRS>EPSG:32661</SRS>' +
+' <SRS>EPSG:32662</SRS>' +
+' <SRS>EPSG:32664</SRS>' +
+' <SRS>EPSG:32665</SRS>' +
+' <SRS>EPSG:32666</SRS>' +
+' <SRS>EPSG:32667</SRS>' +
+' <SRS>EPSG:32700</SRS>' +
+' <SRS>EPSG:32701</SRS>' +
+' <SRS>EPSG:32702</SRS>' +
+' <SRS>EPSG:32703</SRS>' +
+' <SRS>EPSG:32704</SRS>' +
+' <SRS>EPSG:32705</SRS>' +
+' <SRS>EPSG:32706</SRS>' +
+' <SRS>EPSG:32707</SRS>' +
+' <SRS>EPSG:32708</SRS>' +
+' <SRS>EPSG:32709</SRS>' +
+' <SRS>EPSG:32710</SRS>' +
+' <SRS>EPSG:32711</SRS>' +
+' <SRS>EPSG:32712</SRS>' +
+' <SRS>EPSG:32713</SRS>' +
+' <SRS>EPSG:32714</SRS>' +
+' <SRS>EPSG:32715</SRS>' +
+' <SRS>EPSG:32716</SRS>' +
+' <SRS>EPSG:32717</SRS>' +
+' <SRS>EPSG:32718</SRS>' +
+' <SRS>EPSG:32719</SRS>' +
+' <SRS>EPSG:32720</SRS>' +
+' <SRS>EPSG:32721</SRS>' +
+' <SRS>EPSG:32722</SRS>' +
+' <SRS>EPSG:32723</SRS>' +
+' <SRS>EPSG:32724</SRS>' +
+' <SRS>EPSG:32725</SRS>' +
+' <SRS>EPSG:32726</SRS>' +
+' <SRS>EPSG:32727</SRS>' +
+' <SRS>EPSG:32728</SRS>' +
+' <SRS>EPSG:32729</SRS>' +
+' <SRS>EPSG:32730</SRS>' +
+' <SRS>EPSG:32731</SRS>' +
+' <SRS>EPSG:32732</SRS>' +
+' <SRS>EPSG:32733</SRS>' +
+' <SRS>EPSG:32734</SRS>' +
+' <SRS>EPSG:32735</SRS>' +
+' <SRS>EPSG:32736</SRS>' +
+' <SRS>EPSG:32737</SRS>' +
+' <SRS>EPSG:32738</SRS>' +
+' <SRS>EPSG:32739</SRS>' +
+' <SRS>EPSG:32740</SRS>' +
+' <SRS>EPSG:32741</SRS>' +
+' <SRS>EPSG:32742</SRS>' +
+' <SRS>EPSG:32743</SRS>' +
+' <SRS>EPSG:32744</SRS>' +
+' <SRS>EPSG:32745</SRS>' +
+' <SRS>EPSG:32746</SRS>' +
+' <SRS>EPSG:32747</SRS>' +
+' <SRS>EPSG:32748</SRS>' +
+' <SRS>EPSG:32749</SRS>' +
+' <SRS>EPSG:32750</SRS>' +
+' <SRS>EPSG:32751</SRS>' +
+' <SRS>EPSG:32752</SRS>' +
+' <SRS>EPSG:32753</SRS>' +
+' <SRS>EPSG:32754</SRS>' +
+' <SRS>EPSG:32755</SRS>' +
+' <SRS>EPSG:32756</SRS>' +
+' <SRS>EPSG:32757</SRS>' +
+' <SRS>EPSG:32758</SRS>' +
+' <SRS>EPSG:32759</SRS>' +
+' <SRS>EPSG:32760</SRS>' +
+' <SRS>EPSG:32761</SRS>' +
+' <SRS>EPSG:32766</SRS>' +
+' <SRS>EPSG:61206405</SRS>' +
+' <SRS>EPSG:61216405</SRS>' +
+' <SRS>EPSG:61226405</SRS>' +
+' <SRS>EPSG:61236405</SRS>' +
+' <SRS>EPSG:61246405</SRS>' +
+' <SRS>EPSG:61266405</SRS>' +
+' <SRS>EPSG:61266413</SRS>' +
+' <SRS>EPSG:61276405</SRS>' +
+' <SRS>EPSG:61286405</SRS>' +
+' <SRS>EPSG:61296405</SRS>' +
+' <SRS>EPSG:61306405</SRS>' +
+' <SRS>EPSG:61306413</SRS>' +
+' <SRS>EPSG:61316405</SRS>' +
+' <SRS>EPSG:61326405</SRS>' +
+' <SRS>EPSG:61336405</SRS>' +
+' <SRS>EPSG:61346405</SRS>' +
+' <SRS>EPSG:61356405</SRS>' +
+' <SRS>EPSG:61366405</SRS>' +
+' <SRS>EPSG:61376405</SRS>' +
+' <SRS>EPSG:61386405</SRS>' +
+' <SRS>EPSG:61396405</SRS>' +
+' <SRS>EPSG:61406405</SRS>' +
+' <SRS>EPSG:61406413</SRS>' +
+' <SRS>EPSG:61416405</SRS>' +
+' <SRS>EPSG:61426405</SRS>' +
+' <SRS>EPSG:61436405</SRS>' +
+' <SRS>EPSG:61446405</SRS>' +
+' <SRS>EPSG:61456405</SRS>' +
+' <SRS>EPSG:61466405</SRS>' +
+' <SRS>EPSG:61476405</SRS>' +
+' <SRS>EPSG:61486405</SRS>' +
+' <SRS>EPSG:61486413</SRS>' +
+' <SRS>EPSG:61496405</SRS>' +
+' <SRS>EPSG:61506405</SRS>' +
+' <SRS>EPSG:61516405</SRS>' +
+' <SRS>EPSG:61516413</SRS>' +
+' <SRS>EPSG:61526405</SRS>' +
+' <SRS>EPSG:61526413</SRS>' +
+' <SRS>EPSG:61536405</SRS>' +
+' <SRS>EPSG:61546405</SRS>' +
+' <SRS>EPSG:61556405</SRS>' +
+' <SRS>EPSG:61566405</SRS>' +
+' <SRS>EPSG:61576405</SRS>' +
+' <SRS>EPSG:61586405</SRS>' +
+' <SRS>EPSG:61596405</SRS>' +
+' <SRS>EPSG:61606405</SRS>' +
+' <SRS>EPSG:61616405</SRS>' +
+' <SRS>EPSG:61626405</SRS>' +
+' <SRS>EPSG:61636405</SRS>' +
+' <SRS>EPSG:61636413</SRS>' +
+' <SRS>EPSG:61646405</SRS>' +
+' <SRS>EPSG:61656405</SRS>' +
+' <SRS>EPSG:61666405</SRS>' +
+' <SRS>EPSG:61676405</SRS>' +
+' <SRS>EPSG:61676413</SRS>' +
+' <SRS>EPSG:61686405</SRS>' +
+' <SRS>EPSG:61696405</SRS>' +
+' <SRS>EPSG:61706405</SRS>' +
+' <SRS>EPSG:61706413</SRS>' +
+' <SRS>EPSG:61716405</SRS>' +
+' <SRS>EPSG:61716413</SRS>' +
+' <SRS>EPSG:61736405</SRS>' +
+' <SRS>EPSG:61736413</SRS>' +
+' <SRS>EPSG:61746405</SRS>' +
+' <SRS>EPSG:61756405</SRS>' +
+' <SRS>EPSG:61766405</SRS>' +
+' <SRS>EPSG:61766413</SRS>' +
+' <SRS>EPSG:61786405</SRS>' +
+' <SRS>EPSG:61796405</SRS>' +
+' <SRS>EPSG:61806405</SRS>' +
+' <SRS>EPSG:61806413</SRS>' +
+' <SRS>EPSG:61816405</SRS>' +
+' <SRS>EPSG:61826405</SRS>' +
+' <SRS>EPSG:61836405</SRS>' +
+' <SRS>EPSG:61846405</SRS>' +
+' <SRS>EPSG:61886405</SRS>' +
+' <SRS>EPSG:61896405</SRS>' +
+' <SRS>EPSG:61896413</SRS>' +
+' <SRS>EPSG:61906405</SRS>' +
+' <SRS>EPSG:61906413</SRS>' +
+' <SRS>EPSG:61916405</SRS>' +
+' <SRS>EPSG:61926405</SRS>' +
+' <SRS>EPSG:61936405</SRS>' +
+' <SRS>EPSG:61946405</SRS>' +
+' <SRS>EPSG:61956405</SRS>' +
+' <SRS>EPSG:61966405</SRS>' +
+' <SRS>EPSG:61976405</SRS>' +
+' <SRS>EPSG:61986405</SRS>' +
+' <SRS>EPSG:61996405</SRS>' +
+' <SRS>EPSG:62006405</SRS>' +
+' <SRS>EPSG:62016405</SRS>' +
+' <SRS>EPSG:62026405</SRS>' +
+' <SRS>EPSG:62036405</SRS>' +
+' <SRS>EPSG:62046405</SRS>' +
+' <SRS>EPSG:62056405</SRS>' +
+' <SRS>EPSG:62066405</SRS>' +
+' <SRS>EPSG:62076405</SRS>' +
+' <SRS>EPSG:62086405</SRS>' +
+' <SRS>EPSG:62096405</SRS>' +
+' <SRS>EPSG:62106405</SRS>' +
+' <SRS>EPSG:62116405</SRS>' +
+' <SRS>EPSG:62126405</SRS>' +
+' <SRS>EPSG:62136405</SRS>' +
+' <SRS>EPSG:62146405</SRS>' +
+' <SRS>EPSG:62156405</SRS>' +
+' <SRS>EPSG:62166405</SRS>' +
+' <SRS>EPSG:62186405</SRS>' +
+' <SRS>EPSG:62196405</SRS>' +
+' <SRS>EPSG:62206405</SRS>' +
+' <SRS>EPSG:62216405</SRS>' +
+' <SRS>EPSG:62226405</SRS>' +
+' <SRS>EPSG:62236405</SRS>' +
+' <SRS>EPSG:62246405</SRS>' +
+' <SRS>EPSG:62256405</SRS>' +
+' <SRS>EPSG:62276405</SRS>' +
+' <SRS>EPSG:62296405</SRS>' +
+' <SRS>EPSG:62306405</SRS>' +
+' <SRS>EPSG:62316405</SRS>' +
+' <SRS>EPSG:62326405</SRS>' +
+' <SRS>EPSG:62336405</SRS>' +
+' <SRS>EPSG:62366405</SRS>' +
+' <SRS>EPSG:62376405</SRS>' +
+' <SRS>EPSG:62386405</SRS>' +
+' <SRS>EPSG:62396405</SRS>' +
+' <SRS>EPSG:62406405</SRS>' +
+' <SRS>EPSG:62416405</SRS>' +
+' <SRS>EPSG:62426405</SRS>' +
+' <SRS>EPSG:62436405</SRS>' +
+' <SRS>EPSG:62446405</SRS>' +
+' <SRS>EPSG:62456405</SRS>' +
+' <SRS>EPSG:62466405</SRS>' +
+' <SRS>EPSG:62476405</SRS>' +
+' <SRS>EPSG:62486405</SRS>' +
+' <SRS>EPSG:62496405</SRS>' +
+' <SRS>EPSG:62506405</SRS>' +
+' <SRS>EPSG:62516405</SRS>' +
+' <SRS>EPSG:62526405</SRS>' +
+' <SRS>EPSG:62536405</SRS>' +
+' <SRS>EPSG:62546405</SRS>' +
+' <SRS>EPSG:62556405</SRS>' +
+' <SRS>EPSG:62566405</SRS>' +
+' <SRS>EPSG:62576405</SRS>' +
+' <SRS>EPSG:62586405</SRS>' +
+' <SRS>EPSG:62586413</SRS>' +
+' <SRS>EPSG:62596405</SRS>' +
+' <SRS>EPSG:62616405</SRS>' +
+' <SRS>EPSG:62626405</SRS>' +
+' <SRS>EPSG:62636405</SRS>' +
+' <SRS>EPSG:62646405</SRS>' +
+' <SRS>EPSG:62656405</SRS>' +
+' <SRS>EPSG:62666405</SRS>' +
+' <SRS>EPSG:62676405</SRS>' +
+' <SRS>EPSG:62686405</SRS>' +
+' <SRS>EPSG:62696405</SRS>' +
+' <SRS>EPSG:62706405</SRS>' +
+' <SRS>EPSG:62716405</SRS>' +
+' <SRS>EPSG:62726405</SRS>' +
+' <SRS>EPSG:62736405</SRS>' +
+' <SRS>EPSG:62746405</SRS>' +
+' <SRS>EPSG:62756405</SRS>' +
+' <SRS>EPSG:62766405</SRS>' +
+' <SRS>EPSG:62776405</SRS>' +
+' <SRS>EPSG:62786405</SRS>' +
+' <SRS>EPSG:62796405</SRS>' +
+' <SRS>EPSG:62806405</SRS>' +
+' <SRS>EPSG:62816405</SRS>' +
+' <SRS>EPSG:62826405</SRS>' +
+' <SRS>EPSG:62836405</SRS>' +
+' <SRS>EPSG:62836413</SRS>' +
+' <SRS>EPSG:62846405</SRS>' +
+' <SRS>EPSG:62856405</SRS>' +
+' <SRS>EPSG:62866405</SRS>' +
+' <SRS>EPSG:62886405</SRS>' +
+' <SRS>EPSG:62896405</SRS>' +
+' <SRS>EPSG:62926405</SRS>' +
+' <SRS>EPSG:62936405</SRS>' +
+' <SRS>EPSG:62956405</SRS>' +
+' <SRS>EPSG:62976405</SRS>' +
+' <SRS>EPSG:62986405</SRS>' +
+' <SRS>EPSG:62996405</SRS>' +
+' <SRS>EPSG:63006405</SRS>' +
+' <SRS>EPSG:63016405</SRS>' +
+' <SRS>EPSG:63026405</SRS>' +
+' <SRS>EPSG:63036405</SRS>' +
+' <SRS>EPSG:63046405</SRS>' +
+' <SRS>EPSG:63066405</SRS>' +
+' <SRS>EPSG:63076405</SRS>' +
+' <SRS>EPSG:63086405</SRS>' +
+' <SRS>EPSG:63096405</SRS>' +
+' <SRS>EPSG:63106405</SRS>' +
+' <SRS>EPSG:63116405</SRS>' +
+' <SRS>EPSG:63126405</SRS>' +
+' <SRS>EPSG:63136405</SRS>' +
+' <SRS>EPSG:63146405</SRS>' +
+' <SRS>EPSG:63156405</SRS>' +
+' <SRS>EPSG:63166405</SRS>' +
+' <SRS>EPSG:63176405</SRS>' +
+' <SRS>EPSG:63186405</SRS>' +
+' <SRS>EPSG:63196405</SRS>' +
+' <SRS>EPSG:63226405</SRS>' +
+' <SRS>EPSG:63246405</SRS>' +
+' <SRS>EPSG:63266405</SRS>' +
+' <SRS>EPSG:63266406</SRS>' +
+' <SRS>EPSG:63266407</SRS>' +
+' <SRS>EPSG:63266408</SRS>' +
+' <SRS>EPSG:63266409</SRS>' +
+' <SRS>EPSG:63266410</SRS>' +
+' <SRS>EPSG:63266411</SRS>' +
+' <SRS>EPSG:63266412</SRS>' +
+' <SRS>EPSG:63266413</SRS>' +
+' <SRS>EPSG:63266414</SRS>' +
+' <SRS>EPSG:63266415</SRS>' +
+' <SRS>EPSG:63266416</SRS>' +
+' <SRS>EPSG:63266417</SRS>' +
+' <SRS>EPSG:63266418</SRS>' +
+' <SRS>EPSG:63266419</SRS>' +
+' <SRS>EPSG:63266420</SRS>' +
+' <SRS>EPSG:66006405</SRS>' +
+' <SRS>EPSG:66016405</SRS>' +
+' <SRS>EPSG:66026405</SRS>' +
+' <SRS>EPSG:66036405</SRS>' +
+' <SRS>EPSG:66046405</SRS>' +
+' <SRS>EPSG:66056405</SRS>' +
+' <SRS>EPSG:66066405</SRS>' +
+' <SRS>EPSG:66076405</SRS>' +
+' <SRS>EPSG:66086405</SRS>' +
+' <SRS>EPSG:66096405</SRS>' +
+' <SRS>EPSG:66106405</SRS>' +
+' <SRS>EPSG:66116405</SRS>' +
+' <SRS>EPSG:66126405</SRS>' +
+' <SRS>EPSG:66126413</SRS>' +
+' <SRS>EPSG:66136405</SRS>' +
+' <SRS>EPSG:66146405</SRS>' +
+' <SRS>EPSG:66156405</SRS>' +
+' <SRS>EPSG:66166405</SRS>' +
+' <SRS>EPSG:66186405</SRS>' +
+' <SRS>EPSG:66196405</SRS>' +
+' <SRS>EPSG:66196413</SRS>' +
+' <SRS>EPSG:66206405</SRS>' +
+' <SRS>EPSG:66216405</SRS>' +
+' <SRS>EPSG:66226405</SRS>' +
+' <SRS>EPSG:66236405</SRS>' +
+' <SRS>EPSG:66246405</SRS>' +
+' <SRS>EPSG:66246413</SRS>' +
+' <SRS>EPSG:66256405</SRS>' +
+' <SRS>EPSG:66266405</SRS>' +
+' <SRS>EPSG:66276405</SRS>' +
+' <SRS>EPSG:66276413</SRS>' +
+' <SRS>EPSG:66286405</SRS>' +
+' <SRS>EPSG:66296405</SRS>' +
+' <SRS>EPSG:66306405</SRS>' +
+' <SRS>EPSG:66316405</SRS>' +
+' <SRS>EPSG:66326405</SRS>' +
+' <SRS>EPSG:66336405</SRS>' +
+' <SRS>EPSG:66346405</SRS>' +
+' <SRS>EPSG:66356405</SRS>' +
+' <SRS>EPSG:66366405</SRS>' +
+' <SRS>EPSG:66376405</SRS>' +
+' <SRS>EPSG:66386405</SRS>' +
+' <SRS>EPSG:66396405</SRS>' +
+' <SRS>EPSG:66406405</SRS>' +
+' <SRS>EPSG:66406413</SRS>' +
+' <SRS>EPSG:66416405</SRS>' +
+' <SRS>EPSG:66426405</SRS>' +
+' <SRS>EPSG:66436405</SRS>' +
+' <SRS>EPSG:66446405</SRS>' +
+' <SRS>EPSG:66456405</SRS>' +
+' <SRS>EPSG:66456413</SRS>' +
+' <SRS>EPSG:66466405</SRS>' +
+' <SRS>EPSG:66576405</SRS>' +
+' <SRS>EPSG:66586405</SRS>' +
+' <SRS>EPSG:66596405</SRS>' +
+' <SRS>EPSG:66596413</SRS>' +
+' <SRS>EPSG:66606405</SRS>' +
+' <SRS>EPSG:66616405</SRS>' +
+' <SRS>EPSG:66616413</SRS>' +
+' <SRS>EPSG:66636405</SRS>' +
+' <SRS>EPSG:66646405</SRS>' +
+' <SRS>EPSG:66656405</SRS>' +
+' <SRS>EPSG:66666405</SRS>' +
+' <SRS>EPSG:66676405</SRS>' +
+' <SRS>EPSG:68016405</SRS>' +
+' <SRS>EPSG:68026405</SRS>' +
+' <SRS>EPSG:68036405</SRS>' +
+' <SRS>EPSG:68046405</SRS>' +
+' <SRS>EPSG:68056405</SRS>' +
+' <SRS>EPSG:68066405</SRS>' +
+' <SRS>EPSG:68086405</SRS>' +
+' <SRS>EPSG:68096405</SRS>' +
+' <SRS>EPSG:68136405</SRS>' +
+' <SRS>EPSG:68146405</SRS>' +
+' <SRS>EPSG:68156405</SRS>' +
+' <SRS>EPSG:68186405</SRS>' +
+' <SRS>EPSG:68206405</SRS>' +
+' <SRS>EPSG:69036405</SRS>' +
+' <SRS>EPSG:42302</SRS>' +
+' <SRS>EPSG:42301</SRS>' +
+' <SRS>EPSG:900913</SRS>' +
+' <SRS>EPSG:45556</SRS>' +
+' <SRS>EPSG:45555</SRS>' +
+' <SRS>EPSG:54004</SRS>' +
+' <SRS>EPSG:41001</SRS>' +
+' <SRS>EPSG:42311</SRS>' +
+' <SRS>EPSG:42310</SRS>' +
+' <SRS>EPSG:18001</SRS>' +
+' <SRS>EPSG:100003</SRS>' +
+' <SRS>EPSG:42106</SRS>' +
+' <SRS>EPSG:100002</SRS>' +
+' <SRS>EPSG:42105</SRS>' +
+' <SRS>EPSG:100001</SRS>' +
+' <SRS>EPSG:42309</SRS>' +
+' <SRS>EPSG:42104</SRS>' +
+' <SRS>EPSG:42308</SRS>' +
+' <SRS>EPSG:42103</SRS>' +
+' <SRS>EPSG:42307</SRS>' +
+' <SRS>EPSG:42102</SRS>' +
+' <SRS>EPSG:42306</SRS>' +
+' <SRS>EPSG:42101</SRS>' +
+' <SRS>EPSG:42305</SRS>' +
+' <SRS>EPSG:42304</SRS>' +
+' <SRS>EPSG:42303</SRS>' +
+' <LatLonBoundingBox minx="-297176.16529836657" miny="-1.2694600326676274E7" maxx="3.0016785704606913E7" maxy="1.7619361543229006E7"/>' +
+' <Layer queryable="1">' +
+' <Name>og:bugsites</Name>' +
+' <Title/>' +
+' <Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract>' +
+' <KeywordList>' +
+' <Keyword>spearfish</Keyword>' +
+' <Keyword>sfBugsites</Keyword>' +
+' <Keyword>insects</Keyword>' +
+' <Keyword>bugsites</Keyword>' +
+' <Keyword>tiger_beetles</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:26713</SRS>' +
+' <!--WKT definition of this CRS:' +
+'PROJCS["NAD27 / UTM zone 13N", ' +
+' GEOGCS["NAD27", ' +
+' DATUM["North American Datum 1927", ' +
+' SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], ' +
+' TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], ' +
+' AUTHORITY["EPSG","6267"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4267"]], ' +
+' PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], ' +
+' PARAMETER["central_meridian", -105.0], ' +
+' PARAMETER["latitude_of_origin", 0.0], ' +
+' PARAMETER["scale_factor", 0.9996], ' +
+' PARAMETER["false_easting", 500000.0], ' +
+' PARAMETER["false_northing", 0.0], ' +
+' UNIT["m", 1.0], ' +
+' AXIS["Easting", EAST], ' +
+' AXIS["Northing", NORTH], ' +
+' AUTHORITY["EPSG","26713"]]-->' +
+' <LatLonBoundingBox minx="-103.8701581843142" miny="44.286540361238224" maxx="-103.63532819794625" maxy="44.52137034760618"/>' +
+' <BoundingBox SRS="EPSG:26713" minx="590232.0" miny="4914096.0" maxx="608471.0" maxy="4920512.0"/>' +
+' <Style>' +
+' <Name>capitals</Name>' +
+' <Title>Capital cities</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:bugsites"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>og:restricted</Name>' +
+' <Title/>' +
+' <Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract>' +
+' <KeywordList>' +
+' <Keyword>spearfish</Keyword>' +
+' <Keyword>restricted</Keyword>' +
+' <Keyword>sfRestricted</Keyword>' +
+' <Keyword>areas</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:26713</SRS>' +
+' <!--WKT definition of this CRS:' +
+'PROJCS["NAD27 / UTM zone 13N", ' +
+' GEOGCS["NAD27", ' +
+' DATUM["North American Datum 1927", ' +
+' SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], ' +
+' TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], ' +
+' AUTHORITY["EPSG","6267"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4267"]], ' +
+' PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], ' +
+' PARAMETER["central_meridian", -105.0], ' +
+' PARAMETER["latitude_of_origin", 0.0], ' +
+' PARAMETER["scale_factor", 0.9996], ' +
+' PARAMETER["false_easting", 500000.0], ' +
+' PARAMETER["false_northing", 0.0], ' +
+' UNIT["m", 1.0], ' +
+' AXIS["Easting", EAST], ' +
+' AXIS["Northing", NORTH], ' +
+' AUTHORITY["EPSG","26713"]]-->' +
+' <LatLonBoundingBox minx="-104.36424600670885" miny="43.78798270975212" maxx="-103.06226503558304" maxy="45.089963680877936"/>' +
+' <BoundingBox SRS="EPSG:26713" minx="551796.8125" miny="4901896.0" maxx="652788.5625" maxy="4940954.0"/>' +
+' <Style>' +
+' <Name>restricted</Name>' +
+' <Title>Red, translucent style</Title>' +
+' <Abstract>A sample style that just prints out a transparent red interior with a red outline</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:restricted"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>og:archsites</Name>' +
+' <Title/>' +
+' <Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract>' +
+' <KeywordList>' +
+' <Keyword>archsites</Keyword>' +
+' <Keyword>spearfish</Keyword>' +
+' <Keyword>sfArchsites</Keyword>' +
+' <Keyword>archeology</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:26713</SRS>' +
+' <!--WKT definition of this CRS:' +
+'PROJCS["NAD27 / UTM zone 13N", ' +
+' GEOGCS["NAD27", ' +
+' DATUM["North American Datum 1927", ' +
+' SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], ' +
+' TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], ' +
+' AUTHORITY["EPSG","6267"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4267"]], ' +
+' PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], ' +
+' PARAMETER["central_meridian", -105.0], ' +
+' PARAMETER["latitude_of_origin", 0.0], ' +
+' PARAMETER["scale_factor", 0.9996], ' +
+' PARAMETER["false_easting", 500000.0], ' +
+' PARAMETER["false_northing", 0.0], ' +
+' UNIT["m", 1.0], ' +
+' AXIS["Easting", EAST], ' +
+' AXIS["Northing", NORTH], ' +
+' AUTHORITY["EPSG","26713"]]-->' +
+' <LatLonBoundingBox minx="-103.87480459767542" miny="44.31295793136913" maxx="-103.63549073047534" maxy="44.55227179856921"/>' +
+' <BoundingBox SRS="EPSG:26713" minx="589860.0" miny="4914479.0" maxx="608355.0" maxy="4926490.0"/>' +
+' <Style>' +
+' <Name>point</Name>' +
+' <Title>Default point</Title>' +
+' <Abstract>A sample style that just prints out a 6px wide red square</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:archsites"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>og:streams</Name>' +
+' <Title/>' +
+' <Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract>' +
+' <KeywordList>' +
+' <Keyword>spearfish</Keyword>' +
+' <Keyword>sfStreams</Keyword>' +
+' <Keyword>streams</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:26713</SRS>' +
+' <!--WKT definition of this CRS:' +
+'PROJCS["NAD27 / UTM zone 13N", ' +
+' GEOGCS["NAD27", ' +
+' DATUM["North American Datum 1927", ' +
+' SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], ' +
+' TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], ' +
+' AUTHORITY["EPSG","6267"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4267"]], ' +
+' PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], ' +
+' PARAMETER["central_meridian", -105.0], ' +
+' PARAMETER["latitude_of_origin", 0.0], ' +
+' PARAMETER["scale_factor", 0.9996], ' +
+' PARAMETER["false_easting", 500000.0], ' +
+' PARAMETER["false_northing", 0.0], ' +
+' UNIT["m", 1.0], ' +
+' AXIS["Easting", EAST], ' +
+' AXIS["Northing", NORTH], ' +
+' AUTHORITY["EPSG","26713"]]-->' +
+' <LatLonBoundingBox minx="-103.88033574142051" miny="44.30711172484593" maxx="-103.62022283326024" maxy="44.5672246330062"/>' +
+' <BoundingBox SRS="EPSG:26713" minx="589443.0" miny="4913935.0" maxx="609526.75" maxy="4928059.5"/>' +
+' <Style>' +
+' <Name>simple_streams</Name>' +
+' <Title>Default Styler for streams segments</Title>' +
+' <Abstract>Blue lines, 2px wide</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:streams"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tiger:poly_landmarks</Name>' +
+' <Title>Manhattan (NY) landmarks</Title>' +
+' <Abstract>Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs</Abstract>' +
+' <KeywordList>' +
+' <Keyword>DS_poly_landmarks</Keyword>' +
+' <Keyword>landmarks</Keyword>' +
+' <Keyword>manhattan</Keyword>' +
+' <Keyword>poly_landmarks</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="-74.0828672737" miny="40.67246384130001" maxx="-73.8660689563" maxy="40.8892621587"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-74.047185" miny="40.679648" maxx="-73.90782" maxy="40.882078"/>' +
+' <Style>' +
+' <Name>poly_landmarks</Name>' +
+' <Title>Default Styler</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poly_landmarks"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tiger:poi</Name>' +
+' <Title>Manhattan (NY) points of interest</Title>' +
+' <Abstract>Points of interest in New York, New York (on Manhattan). One of the attributes contains the name of a file with a picture of the point of interest.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>poi</Keyword>' +
+' <Keyword>Manhattan</Keyword>' +
+' <Keyword>DS_poi</Keyword>' +
+' <Keyword>points_of_interest</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="-74.01244590356289" miny="40.70750285086222" maxx="-74.00795911725866" maxy="40.711989637166425"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-74.0118315772888" miny="40.70754683896324" maxx="-74.00153046439813" maxy="40.719885123828675"/>' +
+' <Style>' +
+' <Name>poi</Name>' +
+' <Title>Points of interest</Title>' +
+' <Abstract>Manhattan points of interest</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poi"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tiger:tiger_roads</Name>' +
+' <Title>Manhattan (NY) roads</Title>' +
+' <Abstract>Highly simplified road layout of Manhattan in New York..</Abstract>' +
+' <KeywordList>' +
+' <Keyword>DS_tiger_roads</Keyword>' +
+' <Keyword>tiger_roads</Keyword>' +
+' <Keyword>roads</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="-74.06603057" miny="40.68228143" maxx="-73.86819443" maxy="40.880117569999996"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-74.02722" miny="40.684221" maxx="-73.907005" maxy="40.878178"/>' +
+' <Style>' +
+' <Name>tiger_roads</Name>' +
+' <Title>Default Styler</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:tiger_roads"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>za:za_natural</Name>' +
+' <Title>Natural Landmarks in South Africa</Title>' +
+' <Abstract>This layer describes natural features of South Africa such as forests and lakes.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>water</Keyword>' +
+' <Keyword>forests</Keyword>' +
+' <Keyword>landmarks</Keyword>' +
+' <Keyword>Africa</Keyword>' +
+' <Keyword>South</Keyword>' +
+' <Keyword>natural</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="16.779241142272962" miny="-36.53577846527099" maxx="32.70336002349853" maxy="-20.611659584045416"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="16.935359954834" miny="-34.3737831115723" maxx="32.5472412109375" maxy="-22.7736549377441"/>' +
+' <Style>' +
+' <Name>za_natural</Name>' +
+' <Title>Default Styler</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_natural"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>za:za_points</Name>' +
+' <Title>Points of Interest in South Africa</Title>' +
+' <Abstract>Noteworthy locations such as hotels and tourist attractions in South Africa.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>of</Keyword>' +
+' <Keyword>tourist</Keyword>' +
+' <Keyword>landmarks</Keyword>' +
+' <Keyword>zoo</Keyword>' +
+' <Keyword>cities</Keyword>' +
+' <Keyword>interest</Keyword>' +
+' <Keyword>attractions</Keyword>' +
+' <Keyword>points</Keyword>' +
+' <Keyword>hotel</Keyword>' +
+' <Keyword>museum</Keyword>' +
+' <Keyword>picnic</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="14.629095230102537" miny="-47.151258316040014" maxx="39.792314376831065" maxy="-21.988039169311488"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="16.4827766418457" miny="-46.9045600891113" maxx="37.9386329650879" maxy="-22.2347373962402"/>' +
+' <Style>' +
+' <Name>za_points</Name>' +
+' <Title>Default Styler</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_points"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>za:za_roads</Name>' +
+' <Title>South African Roads</Title>' +
+' <Abstract>This layer describes roads in South Africa.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>south</Keyword>' +
+' <Keyword>africa</Keyword>' +
+' <Keyword>roads</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="16.29388177871706" miny="-36.85438787460323" maxx="33.04232465744013" maxy="-20.10594499588016"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="16.4580821990967" miny="-34.8331336975098" maxx="32.8781242370605" maxy="-22.1271991729736"/>' +
+' <Style>' +
+' <Name>za_roads</Name>' +
+' <Title>Default Styler</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_roads"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>za:za_vegetation</Name>' +
+' <Title>South African Vegetation</Title>' +
+' <Abstract>This layer describes vegetated areas in South Africa, categorized by biome.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>south</Keyword>' +
+' <Keyword>vegetation</Keyword>' +
+' <Keyword>africa</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="16.30492322921758" miny="-36.855452365875216" maxx="33.05824930191042" maxy="-20.102126293182376"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="16.4691715240479" miny="-34.8336486816406" maxx="32.8940010070801" maxy="-22.123929977417"/>' +
+' <Style>' +
+' <Name>za_vegetation</Name>' +
+' <Title>Default Styler</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_vegetation"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>topp:tasmania_cities</Name>' +
+' <Title>Tasmania cities</Title>' +
+' <Abstract>Cities in Tasmania (actually, just the capital)</Abstract>' +
+' <KeywordList>' +
+' <Keyword>cities</Keyword>' +
+' <Keyword>Tasmania</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="145.1667856" miny="-43.706631400000006" maxx="148.30373440000002" maxy="-40.56968259999999"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="147.2910004483" miny="-42.851001816890005" maxx="147.2910004483" maxy="-42.851001816890005"/>' +
+' <Style>' +
+' <Name>capitals</Name>' +
+' <Title>Capital cities</Title>' +
+' <Abstract/>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_cities"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>topp:tasmania_roads</Name>' +
+' <Title>Tasmania roads</Title>' +
+' <Abstract>Main Tasmania roads</Abstract>' +
+' <KeywordList>' +
+' <Keyword>Roads</Keyword>' +
+' <Keyword>Tasmania</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="145.1667856" miny="-43.706631400000006" maxx="148.30373440000002" maxy="-40.56968259999999"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="145.19754" miny="-43.423512" maxx="148.27298000000002" maxy="-40.852802"/>' +
+' <Style>' +
+' <Name>simple_roads</Name>' +
+' <Title>Default Styler for simple road segments</Title>' +
+' <Abstract>Light red line, 2px wide</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_roads"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>topp:tasmania_state_boundaries</Name>' +
+' <Title>Tasmania state boundaries</Title>' +
+' <Abstract>Tasmania state boundaries</Abstract>' +
+' <KeywordList>' +
+' <Keyword>boundaries</Keyword>' +
+' <Keyword>tasmania_state_boundaries</Keyword>' +
+' <Keyword>Tasmania</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="143.74100879660003" miny="-44.026947203400006" maxx="148.57295620340003" maxy="-39.194999796599994"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>' +
+' <Style>' +
+' <Name>green</Name>' +
+' <Title>Green polygon</Title>' +
+' <Abstract>Green fill with black outline</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_state_boundaries"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>topp:tasmania_water_bodies</Name>' +
+' <Title>Tasmania water bodies</Title>' +
+' <Abstract>Tasmania water bodies</Abstract>' +
+' <KeywordList>' +
+' <Keyword>Lakes</Keyword>' +
+' <Keyword>Bodies</Keyword>' +
+' <Keyword>Australia</Keyword>' +
+' <Keyword>Water</Keyword>' +
+' <Keyword>Tasmania</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="145.95490063999998" miny="-43.04450786" maxx="147.23641436" maxy="-41.762994139999996"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="145.97161899999998" miny="-43.031944" maxx="147.219696" maxy="-41.775558"/>' +
+' <Style>' +
+' <Name>cite_lakes</Name>' +
+' <Title>Blue lake</Title>' +
+' <Abstract>A blue fill, solid black outline style</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_water_bodies"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>topp:states</Name>' +
+' <Title>USA Population</Title>' +
+' <Abstract>This is some census data on the states.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>census</Keyword>' +
+' <Keyword>united</Keyword>' +
+' <Keyword>boundaries</Keyword>' +
+' <Keyword>state</Keyword>' +
+' <Keyword>states</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="-125.30903773" miny="7.705448770000002" maxx="-66.39223326999999" maxy="66.62225323"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-124.73142200000001" miny="24.955967" maxx="-66.969849" maxy="49.371735"/>' +
+' <Style>' +
+' <Name>population</Name>' +
+' <Title>Population in the United States</Title>' +
+' <Abstract>A sample filter that filters the United States into three' +
+' categories of population, drawn in different colors</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:states"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tike:waterways</Name>' +
+' <Title>Waterways</Title>' +
+' <Abstract>Waterways in Finland.</Abstract>' +
+' <KeywordList>' +
+' <Keyword>Finland</Keyword>' +
+' <Keyword>waterways</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="19.530168895721403" miny="58.860618000030506" maxx="31.6566005897522" maxy="70.9870496940613"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="19.649055480957" miny="59.9357719421387" maxx="31.5377140045166" maxy="69.9118957519531"/>' +
+' <Style>' +
+' <Name>line</Name>' +
+' <Title>1 px blue line</Title>' +
+' <Abstract>Default line style, 1 pixel wide blue</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:waterways"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tike:railways</Name>' +
+' <Title>roads_Type</Title>' +
+' <Abstract>Generated from tike</Abstract>' +
+' <KeywordList>' +
+' <Keyword>tike</Keyword>' +
+' <Keyword>roads</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="-297176.16529836657" miny="-1.2694600326676274E7" maxx="3.0016785704606913E7" maxy="1.7619361543229006E7"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>' +
+' <Style>' +
+' <Name>line</Name>' +
+' <Title>1 px blue line</Title>' +
+' <Abstract>Default line style, 1 pixel wide blue</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:railways"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tike:roads</Name>' +
+' <Title>roads_Type</Title>' +
+' <Abstract>Generated from tike</Abstract>' +
+' <KeywordList>' +
+' <Keyword>tike</Keyword>' +
+' <Keyword>roads</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="-297176.16529836657" miny="-1.2694600326676274E7" maxx="3.0016785704606913E7" maxy="1.7619361543229006E7"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>' +
+' <Style>' +
+' <Name>line</Name>' +
+' <Title>1 px blue line</Title>' +
+' <Abstract>Default line style, 1 pixel wide blue</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:roads"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>og:roads</Name>' +
+' <Title>roads_Type</Title>' +
+' <Abstract>Generated from sf_reset</Abstract>' +
+' <KeywordList>' +
+' <Keyword>roads</Keyword>' +
+' <Keyword>sf_reset</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:26713</SRS>' +
+' <!--WKT definition of this CRS:' +
+'PROJCS["NAD27 / UTM zone 13N", ' +
+' GEOGCS["NAD27", ' +
+' DATUM["North American Datum 1927", ' +
+' SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], ' +
+' TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], ' +
+' AUTHORITY["EPSG","6267"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4267"]], ' +
+' PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], ' +
+' PARAMETER["central_meridian", -105.0], ' +
+' PARAMETER["latitude_of_origin", 0.0], ' +
+' PARAMETER["scale_factor", 0.9996], ' +
+' PARAMETER["false_easting", 500000.0], ' +
+' PARAMETER["false_northing", 0.0], ' +
+' UNIT["m", 1.0], ' +
+' AXIS["Easting", EAST], ' +
+' AXIS["Northing", NORTH], ' +
+' AUTHORITY["EPSG","26713"]]-->' +
+' <LatLonBoundingBox minx="-103.88042792817339" miny="44.308776913708805" maxx="-103.62014761945467" maxy="44.56905722242751"/>' +
+' <BoundingBox SRS="EPSG:26713" minx="589434.8125" miny="4914006.0" maxx="609527.25" maxy="4928377.0"/>' +
+' <Style>' +
+' <Name>simple_roads</Name>' +
+' <Title>Default Styler for simple road segments</Title>' +
+' <Abstract>Light red line, 2px wide</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:roads"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>tike:points</Name>' +
+' <Title>roads_Type</Title>' +
+' <Abstract>Generated from tike</Abstract>' +
+' <KeywordList>' +
+' <Keyword>tike</Keyword>' +
+' <Keyword>roads</Keyword>' +
+' </KeywordList>' +
+' <SRS>EPSG:4326</SRS>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <LatLonBoundingBox minx="19.73377216339108" miny="59.107116584777835" maxx="31.40053188323972" maxy="70.77387630462647"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="19.8481521606445" miny="59.8213005065918" maxx="31.2861518859863" maxy="70.0596923828125"/>' +
+' <Style>' +
+' <Name>line</Name>' +
+' <Title>1 px blue line</Title>' +
+' <Abstract>Default line style, 1 pixel wide blue</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:points"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>topp:bluemarble</Name>' +
+' <Title>Blue Marble Imagery</Title>' +
+' <Abstract>Blue Marble NG global bathymetry and topography data from NASA. More information about the Blue Marble NG project is available from http://earthobservatory.nasa.gov/Features/BlueMarble .</Abstract>' +
+' <KeywordList>' +
+' <Keyword>WCS</Keyword>' +
+' <Keyword>bluemarble</Keyword>' +
+' <Keyword>bluemarble</Keyword>' +
+' </KeywordList>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <SRS>EPSG:4326</SRS>' +
+' <LatLonBoundingBox minx="-180.00000003333" miny="-89.99999996486703" maxx="179.99999993067" maxy="90.000000033333"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-180.00000003333" miny="-89.99999996486703" maxx="179.99999993067" maxy="90.000000033333"/>' +
+' <Style>' +
+' <Name>raster</Name>' +
+' <Title>Raster</Title>' +
+' <Abstract>A sample style for rasters, good for displaying imagery</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:bluemarble"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>nurc:Arc_Sample</Name>' +
+' <Title>Global annual rainfall</Title>' +
+' <Abstract>Global annual rainfall in ArcGrid format</Abstract>' +
+' <KeywordList>' +
+' <Keyword>WCS</Keyword>' +
+' <Keyword>arcGridSample</Keyword>' +
+' <Keyword>arcGridSample_Coverage</Keyword>' +
+' </KeywordList>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <SRS>EPSG:4326</SRS>' +
+' <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>' +
+' <Style>' +
+' <Name>raster</Name>' +
+' <Title>Raster</Title>' +
+' <Abstract>A sample style for rasters, good for displaying imagery</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Arc_Sample"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>nurc:Img_Sample</Name>' +
+' <Title>North America sample imagery</Title>' +
+' <Abstract>A very rough imagery of North America</Abstract>' +
+' <KeywordList>' +
+' <Keyword>WCS</Keyword>' +
+' <Keyword>worldImageSample</Keyword>' +
+' <Keyword>worldImageSample_Coverage</Keyword>' +
+' </KeywordList>' +
+' <!--WKT definition of this CRS:' +
+'GEOGCS["WGS 84", ' +
+' DATUM["World Geodetic System 1984", ' +
+' SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]], ' +
+' AUTHORITY["EPSG","6326"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4326"]]-->' +
+' <SRS>EPSG:4326</SRS>' +
+' <LatLonBoundingBox minx="-130.85168" miny="20.7052" maxx="-62.0054" maxy="54.1141"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-130.85168" miny="20.7052" maxx="-62.0054" maxy="54.1141"/>' +
+' <Style>' +
+' <Name>raster</Name>' +
+' <Title>Raster</Title>' +
+' <Abstract>A sample style for rasters, good for displaying imagery</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Img_Sample"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="1">' +
+' <Name>sf:sfdem</Name>' +
+' <Title>Spearfish DEM</Title>' +
+' <Abstract>Digital Elevation Model data for Spearfish, South Dakota</Abstract>' +
+' <KeywordList>' +
+' <Keyword>WCS</Keyword>' +
+' <Keyword>sf</Keyword>' +
+' <Keyword>dem</Keyword>' +
+' <Keyword>digital</Keyword>' +
+' <Keyword>elevation</Keyword>' +
+' <Keyword>model</Keyword>' +
+' </KeywordList>' +
+' <!--WKT definition of this CRS:' +
+'PROJCS["NAD27 / UTM zone 13N", ' +
+' GEOGCS["NAD27", ' +
+' DATUM["North American Datum 1927", ' +
+' SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]], ' +
+' TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0], ' +
+' AUTHORITY["EPSG","6267"]], ' +
+' PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]], ' +
+' UNIT["degree", 0.017453292519943295], ' +
+' AXIS["Geodetic longitude", EAST], ' +
+' AXIS["Geodetic latitude", NORTH], ' +
+' AUTHORITY["EPSG","4267"]], ' +
+' PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]], ' +
+' PARAMETER["central_meridian", -105.0], ' +
+' PARAMETER["latitude_of_origin", 0.0], ' +
+' PARAMETER["scale_factor", 0.9996], ' +
+' PARAMETER["false_easting", 500000.0], ' +
+' PARAMETER["false_northing", 0.0], ' +
+' UNIT["m", 1.0], ' +
+' AXIS["Easting", EAST], ' +
+' AXIS["Northing", NORTH], ' +
+' AUTHORITY["EPSG","26713"]]-->' +
+' <SRS>EPSG:26713</SRS>' +
+' <LatLonBoundingBox minx="-103.87108701853181" miny="44.370187074132616" maxx="-103.62940739432703" maxy="44.5016011535299"/>' +
+' <BoundingBox SRS="EPSG:26713" minx="589980.0" miny="4913700.0" maxx="609000.0" maxy="4928010.0"/>' +
+' <Style>' +
+' <Name>dem</Name>' +
+' <Title>Simple DEM style</Title>' +
+' <Abstract>Classic elevation color progression</Abstract>' +
+' <LegendURL width="20" height="20">' +
+' <Format>image/png</Format>' +
+' <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:sfdem"/>' +
+' </LegendURL>' +
+' </Style>' +
+' </Layer>' +
+' <Layer queryable="0">' +
+' <Name>tasmania</Name>' +
+' <Title>tasmania</Title>' +
+' <Abstract>Layer-Group type layer: tasmania</Abstract>' +
+' <SRS>EPSG:4326</SRS>' +
+' <LatLonBoundingBox minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>' +
+' </Layer>' +
+' <Layer queryable="0">' +
+' <Name>tiger-ny</Name>' +
+' <Title>tiger-ny</Title>' +
+' <Abstract>Layer-Group type layer: tiger-ny</Abstract>' +
+' <SRS>EPSG:4326</SRS>' +
+' <LatLonBoundingBox minx="-74.047185" miny="40.679648" maxx="-73.907005" maxy="40.882078"/>' +
+' <BoundingBox SRS="EPSG:4326" minx="-74.047185" miny="40.679648" maxx="-73.907005" maxy="40.882078"/>' +
+' </Layer>' +
+' </Layer>' +
+' </Capability>' +
+'</WMT_MS_Capabilities>';
+
diff --git a/misc/openlayers/tests/speed/wmscaps.xml b/misc/openlayers/tests/speed/wmscaps.xml
new file mode 100644
index 0000000..885f712
--- /dev/null
+++ b/misc/openlayers/tests/speed/wmscaps.xml
@@ -0,0 +1,4954 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE WMT_MS_Capabilities SYSTEM "http://demo.opengeo.org/geoserver/schemas/wms/1.1.1/WMS_MS_Capabilities.dtd">
+<WMT_MS_Capabilities version="1.1.1" updateSequence="145">
+ <Service>
+ <Name>OGC:WMS</Name>
+ <Title>GeoServer Web Map Service</Title>
+ <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+ <KeywordList>
+ <Keyword>WFS</Keyword>
+ <Keyword>WMS</Keyword>
+ <Keyword>GEOSERVER</Keyword>
+ </KeywordList>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms"/>
+ <ContactInformation>
+ <ContactPersonPrimary>
+ <ContactPerson>Claudius Ptolomaeus</ContactPerson>
+ <ContactOrganization>The ancient geographes INC</ContactOrganization>
+ </ContactPersonPrimary>
+ <ContactPosition>Chief geographer</ContactPosition>
+ <ContactAddress>
+ <AddressType>Work</AddressType>
+ <Address/>
+ <City>Alexandria</City>
+ <StateOrProvince/>
+ <PostCode/>
+ <Country>Egypt</Country>
+ </ContactAddress>
+ <ContactVoiceTelephone/>
+ <ContactFacsimileTelephone/>
+ <ContactElectronicMailAddress>claudius.ptolomaeus@gmail.com</ContactElectronicMailAddress>
+ </ContactInformation>
+ <Fees>NONE</Fees>
+ <AccessConstraints>NONE</AccessConstraints>
+ </Service>
+ <Capability>
+ <Request>
+ <GetCapabilities>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetCapabilities>
+ <GetMap>
+ <Format>image/png</Format>
+ <Format>application/atom xml</Format>
+ <Format>application/atom+xml</Format>
+ <Format>application/openlayers</Format>
+ <Format>application/pdf</Format>
+ <Format>application/rss xml</Format>
+ <Format>application/rss+xml</Format>
+ <Format>application/vnd.google-earth.kml</Format>
+ <Format>application/vnd.google-earth.kml xml</Format>
+ <Format>application/vnd.google-earth.kml+xml</Format>
+ <Format>application/vnd.google-earth.kmz</Format>
+ <Format>application/vnd.google-earth.kmz xml</Format>
+ <Format>application/vnd.google-earth.kmz+xml</Format>
+ <Format>atom</Format>
+ <Format>image/geotiff</Format>
+ <Format>image/geotiff8</Format>
+ <Format>image/gif</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/png8</Format>
+ <Format>image/svg</Format>
+ <Format>image/svg xml</Format>
+ <Format>image/svg+xml</Format>
+ <Format>image/tiff</Format>
+ <Format>image/tiff8</Format>
+ <Format>kml</Format>
+ <Format>kmz</Format>
+ <Format>openlayers</Format>
+ <Format>rss</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetMap>
+ <GetFeatureInfo>
+ <Format>text/plain</Format>
+ <Format>text/html</Format>
+ <Format>application/vnd.ogc.gml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ <Post>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Post>
+ </HTTP>
+ </DCPType>
+ </GetFeatureInfo>
+ <DescribeLayer>
+ <Format>application/vnd.ogc.wms_xml</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </DescribeLayer>
+ <GetLegendGraphic>
+ <Format>image/png</Format>
+ <Format>image/jpeg</Format>
+ <Format>image/gif</Format>
+ <DCPType>
+ <HTTP>
+ <Get>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms?SERVICE=WMS&amp;"/>
+ </Get>
+ </HTTP>
+ </DCPType>
+ </GetLegendGraphic>
+ </Request>
+ <Exception>
+ <Format>application/vnd.ogc.se_xml</Format>
+ </Exception>
+ <UserDefinedSymbolization SupportSLD="1" UserLayer="1" UserStyle="1" RemoteWFS="1"/>
+ <Layer>
+ <Title>GeoServer Web Map Service</Title>
+ <Abstract>A compliant implementation of WMS 1.1.1 plus most of the SLD 1.0 extension (dynamic styling). Can also generate PDF, SVG, KML, GeoRSS</Abstract>
+ <!--All supported EPSG projections:-->
+ <SRS>EPSG:WGS84(DD)</SRS>
+ <SRS>EPSG:2000</SRS>
+ <SRS>EPSG:2001</SRS>
+ <SRS>EPSG:2002</SRS>
+ <SRS>EPSG:2003</SRS>
+ <SRS>EPSG:2004</SRS>
+ <SRS>EPSG:2005</SRS>
+ <SRS>EPSG:2006</SRS>
+ <SRS>EPSG:2007</SRS>
+ <SRS>EPSG:2008</SRS>
+ <SRS>EPSG:2009</SRS>
+ <SRS>EPSG:2010</SRS>
+ <SRS>EPSG:2011</SRS>
+ <SRS>EPSG:2012</SRS>
+ <SRS>EPSG:2013</SRS>
+ <SRS>EPSG:2014</SRS>
+ <SRS>EPSG:2015</SRS>
+ <SRS>EPSG:2016</SRS>
+ <SRS>EPSG:2017</SRS>
+ <SRS>EPSG:2018</SRS>
+ <SRS>EPSG:2019</SRS>
+ <SRS>EPSG:2020</SRS>
+ <SRS>EPSG:2021</SRS>
+ <SRS>EPSG:2022</SRS>
+ <SRS>EPSG:2023</SRS>
+ <SRS>EPSG:2024</SRS>
+ <SRS>EPSG:2025</SRS>
+ <SRS>EPSG:2026</SRS>
+ <SRS>EPSG:2027</SRS>
+ <SRS>EPSG:2028</SRS>
+ <SRS>EPSG:2029</SRS>
+ <SRS>EPSG:2030</SRS>
+ <SRS>EPSG:2031</SRS>
+ <SRS>EPSG:2032</SRS>
+ <SRS>EPSG:2033</SRS>
+ <SRS>EPSG:2034</SRS>
+ <SRS>EPSG:2035</SRS>
+ <SRS>EPSG:2036</SRS>
+ <SRS>EPSG:2037</SRS>
+ <SRS>EPSG:2038</SRS>
+ <SRS>EPSG:2039</SRS>
+ <SRS>EPSG:2040</SRS>
+ <SRS>EPSG:2041</SRS>
+ <SRS>EPSG:2042</SRS>
+ <SRS>EPSG:2043</SRS>
+ <SRS>EPSG:2044</SRS>
+ <SRS>EPSG:2045</SRS>
+ <SRS>EPSG:2046</SRS>
+ <SRS>EPSG:2047</SRS>
+ <SRS>EPSG:2048</SRS>
+ <SRS>EPSG:2049</SRS>
+ <SRS>EPSG:2050</SRS>
+ <SRS>EPSG:2051</SRS>
+ <SRS>EPSG:2052</SRS>
+ <SRS>EPSG:2053</SRS>
+ <SRS>EPSG:2054</SRS>
+ <SRS>EPSG:2055</SRS>
+ <SRS>EPSG:2056</SRS>
+ <SRS>EPSG:2057</SRS>
+ <SRS>EPSG:2058</SRS>
+ <SRS>EPSG:2059</SRS>
+ <SRS>EPSG:2060</SRS>
+ <SRS>EPSG:2061</SRS>
+ <SRS>EPSG:2062</SRS>
+ <SRS>EPSG:2063</SRS>
+ <SRS>EPSG:2064</SRS>
+ <SRS>EPSG:2065</SRS>
+ <SRS>EPSG:2066</SRS>
+ <SRS>EPSG:2067</SRS>
+ <SRS>EPSG:2068</SRS>
+ <SRS>EPSG:2069</SRS>
+ <SRS>EPSG:2070</SRS>
+ <SRS>EPSG:2071</SRS>
+ <SRS>EPSG:2072</SRS>
+ <SRS>EPSG:2073</SRS>
+ <SRS>EPSG:2074</SRS>
+ <SRS>EPSG:2075</SRS>
+ <SRS>EPSG:2076</SRS>
+ <SRS>EPSG:2077</SRS>
+ <SRS>EPSG:2078</SRS>
+ <SRS>EPSG:2079</SRS>
+ <SRS>EPSG:2080</SRS>
+ <SRS>EPSG:2081</SRS>
+ <SRS>EPSG:2082</SRS>
+ <SRS>EPSG:2083</SRS>
+ <SRS>EPSG:2084</SRS>
+ <SRS>EPSG:2085</SRS>
+ <SRS>EPSG:2086</SRS>
+ <SRS>EPSG:2087</SRS>
+ <SRS>EPSG:2088</SRS>
+ <SRS>EPSG:2089</SRS>
+ <SRS>EPSG:2090</SRS>
+ <SRS>EPSG:2091</SRS>
+ <SRS>EPSG:2092</SRS>
+ <SRS>EPSG:2093</SRS>
+ <SRS>EPSG:2094</SRS>
+ <SRS>EPSG:2095</SRS>
+ <SRS>EPSG:2096</SRS>
+ <SRS>EPSG:2097</SRS>
+ <SRS>EPSG:2098</SRS>
+ <SRS>EPSG:2099</SRS>
+ <SRS>EPSG:2100</SRS>
+ <SRS>EPSG:2101</SRS>
+ <SRS>EPSG:2102</SRS>
+ <SRS>EPSG:2103</SRS>
+ <SRS>EPSG:2104</SRS>
+ <SRS>EPSG:2105</SRS>
+ <SRS>EPSG:2106</SRS>
+ <SRS>EPSG:2107</SRS>
+ <SRS>EPSG:2108</SRS>
+ <SRS>EPSG:2109</SRS>
+ <SRS>EPSG:2110</SRS>
+ <SRS>EPSG:2111</SRS>
+ <SRS>EPSG:2112</SRS>
+ <SRS>EPSG:2113</SRS>
+ <SRS>EPSG:2114</SRS>
+ <SRS>EPSG:2115</SRS>
+ <SRS>EPSG:2116</SRS>
+ <SRS>EPSG:2117</SRS>
+ <SRS>EPSG:2118</SRS>
+ <SRS>EPSG:2119</SRS>
+ <SRS>EPSG:2120</SRS>
+ <SRS>EPSG:2121</SRS>
+ <SRS>EPSG:2122</SRS>
+ <SRS>EPSG:2123</SRS>
+ <SRS>EPSG:2124</SRS>
+ <SRS>EPSG:2125</SRS>
+ <SRS>EPSG:2126</SRS>
+ <SRS>EPSG:2127</SRS>
+ <SRS>EPSG:2128</SRS>
+ <SRS>EPSG:2129</SRS>
+ <SRS>EPSG:2130</SRS>
+ <SRS>EPSG:2131</SRS>
+ <SRS>EPSG:2132</SRS>
+ <SRS>EPSG:2133</SRS>
+ <SRS>EPSG:2134</SRS>
+ <SRS>EPSG:2135</SRS>
+ <SRS>EPSG:2136</SRS>
+ <SRS>EPSG:2137</SRS>
+ <SRS>EPSG:2138</SRS>
+ <SRS>EPSG:2139</SRS>
+ <SRS>EPSG:2140</SRS>
+ <SRS>EPSG:2141</SRS>
+ <SRS>EPSG:2142</SRS>
+ <SRS>EPSG:2143</SRS>
+ <SRS>EPSG:2144</SRS>
+ <SRS>EPSG:2145</SRS>
+ <SRS>EPSG:2146</SRS>
+ <SRS>EPSG:2147</SRS>
+ <SRS>EPSG:2148</SRS>
+ <SRS>EPSG:2149</SRS>
+ <SRS>EPSG:2150</SRS>
+ <SRS>EPSG:2151</SRS>
+ <SRS>EPSG:2152</SRS>
+ <SRS>EPSG:2153</SRS>
+ <SRS>EPSG:2154</SRS>
+ <SRS>EPSG:2155</SRS>
+ <SRS>EPSG:2156</SRS>
+ <SRS>EPSG:2157</SRS>
+ <SRS>EPSG:2158</SRS>
+ <SRS>EPSG:2159</SRS>
+ <SRS>EPSG:2160</SRS>
+ <SRS>EPSG:2161</SRS>
+ <SRS>EPSG:2162</SRS>
+ <SRS>EPSG:2163</SRS>
+ <SRS>EPSG:2164</SRS>
+ <SRS>EPSG:2165</SRS>
+ <SRS>EPSG:2166</SRS>
+ <SRS>EPSG:2167</SRS>
+ <SRS>EPSG:2168</SRS>
+ <SRS>EPSG:2169</SRS>
+ <SRS>EPSG:2170</SRS>
+ <SRS>EPSG:2171</SRS>
+ <SRS>EPSG:2172</SRS>
+ <SRS>EPSG:2173</SRS>
+ <SRS>EPSG:2174</SRS>
+ <SRS>EPSG:2175</SRS>
+ <SRS>EPSG:2176</SRS>
+ <SRS>EPSG:2177</SRS>
+ <SRS>EPSG:2178</SRS>
+ <SRS>EPSG:2179</SRS>
+ <SRS>EPSG:2180</SRS>
+ <SRS>EPSG:2188</SRS>
+ <SRS>EPSG:2189</SRS>
+ <SRS>EPSG:2190</SRS>
+ <SRS>EPSG:2191</SRS>
+ <SRS>EPSG:2192</SRS>
+ <SRS>EPSG:2193</SRS>
+ <SRS>EPSG:2194</SRS>
+ <SRS>EPSG:2195</SRS>
+ <SRS>EPSG:2196</SRS>
+ <SRS>EPSG:2197</SRS>
+ <SRS>EPSG:2198</SRS>
+ <SRS>EPSG:2199</SRS>
+ <SRS>EPSG:2200</SRS>
+ <SRS>EPSG:2201</SRS>
+ <SRS>EPSG:2202</SRS>
+ <SRS>EPSG:2203</SRS>
+ <SRS>EPSG:2204</SRS>
+ <SRS>EPSG:2205</SRS>
+ <SRS>EPSG:2206</SRS>
+ <SRS>EPSG:2207</SRS>
+ <SRS>EPSG:2208</SRS>
+ <SRS>EPSG:2209</SRS>
+ <SRS>EPSG:2210</SRS>
+ <SRS>EPSG:2211</SRS>
+ <SRS>EPSG:2212</SRS>
+ <SRS>EPSG:2213</SRS>
+ <SRS>EPSG:2214</SRS>
+ <SRS>EPSG:2215</SRS>
+ <SRS>EPSG:2216</SRS>
+ <SRS>EPSG:2217</SRS>
+ <SRS>EPSG:2218</SRS>
+ <SRS>EPSG:2219</SRS>
+ <SRS>EPSG:2220</SRS>
+ <SRS>EPSG:2221</SRS>
+ <SRS>EPSG:2222</SRS>
+ <SRS>EPSG:2223</SRS>
+ <SRS>EPSG:2224</SRS>
+ <SRS>EPSG:2225</SRS>
+ <SRS>EPSG:2226</SRS>
+ <SRS>EPSG:2227</SRS>
+ <SRS>EPSG:2228</SRS>
+ <SRS>EPSG:2229</SRS>
+ <SRS>EPSG:2230</SRS>
+ <SRS>EPSG:2231</SRS>
+ <SRS>EPSG:2232</SRS>
+ <SRS>EPSG:2233</SRS>
+ <SRS>EPSG:2234</SRS>
+ <SRS>EPSG:2235</SRS>
+ <SRS>EPSG:2236</SRS>
+ <SRS>EPSG:2237</SRS>
+ <SRS>EPSG:2238</SRS>
+ <SRS>EPSG:2239</SRS>
+ <SRS>EPSG:2240</SRS>
+ <SRS>EPSG:2241</SRS>
+ <SRS>EPSG:2242</SRS>
+ <SRS>EPSG:2243</SRS>
+ <SRS>EPSG:2244</SRS>
+ <SRS>EPSG:2245</SRS>
+ <SRS>EPSG:2246</SRS>
+ <SRS>EPSG:2247</SRS>
+ <SRS>EPSG:2248</SRS>
+ <SRS>EPSG:2249</SRS>
+ <SRS>EPSG:2250</SRS>
+ <SRS>EPSG:2251</SRS>
+ <SRS>EPSG:2252</SRS>
+ <SRS>EPSG:2253</SRS>
+ <SRS>EPSG:2254</SRS>
+ <SRS>EPSG:2255</SRS>
+ <SRS>EPSG:2256</SRS>
+ <SRS>EPSG:2257</SRS>
+ <SRS>EPSG:2258</SRS>
+ <SRS>EPSG:2259</SRS>
+ <SRS>EPSG:2260</SRS>
+ <SRS>EPSG:2261</SRS>
+ <SRS>EPSG:2262</SRS>
+ <SRS>EPSG:2263</SRS>
+ <SRS>EPSG:2264</SRS>
+ <SRS>EPSG:2265</SRS>
+ <SRS>EPSG:2266</SRS>
+ <SRS>EPSG:2267</SRS>
+ <SRS>EPSG:2268</SRS>
+ <SRS>EPSG:2269</SRS>
+ <SRS>EPSG:2270</SRS>
+ <SRS>EPSG:2271</SRS>
+ <SRS>EPSG:2272</SRS>
+ <SRS>EPSG:2273</SRS>
+ <SRS>EPSG:2274</SRS>
+ <SRS>EPSG:2275</SRS>
+ <SRS>EPSG:2276</SRS>
+ <SRS>EPSG:2277</SRS>
+ <SRS>EPSG:2278</SRS>
+ <SRS>EPSG:2279</SRS>
+ <SRS>EPSG:2280</SRS>
+ <SRS>EPSG:2281</SRS>
+ <SRS>EPSG:2282</SRS>
+ <SRS>EPSG:2283</SRS>
+ <SRS>EPSG:2284</SRS>
+ <SRS>EPSG:2285</SRS>
+ <SRS>EPSG:2286</SRS>
+ <SRS>EPSG:2287</SRS>
+ <SRS>EPSG:2288</SRS>
+ <SRS>EPSG:2289</SRS>
+ <SRS>EPSG:2290</SRS>
+ <SRS>EPSG:2291</SRS>
+ <SRS>EPSG:2292</SRS>
+ <SRS>EPSG:2294</SRS>
+ <SRS>EPSG:2295</SRS>
+ <SRS>EPSG:2296</SRS>
+ <SRS>EPSG:2297</SRS>
+ <SRS>EPSG:2298</SRS>
+ <SRS>EPSG:2299</SRS>
+ <SRS>EPSG:2300</SRS>
+ <SRS>EPSG:2301</SRS>
+ <SRS>EPSG:2302</SRS>
+ <SRS>EPSG:2303</SRS>
+ <SRS>EPSG:2304</SRS>
+ <SRS>EPSG:2305</SRS>
+ <SRS>EPSG:2306</SRS>
+ <SRS>EPSG:2307</SRS>
+ <SRS>EPSG:2308</SRS>
+ <SRS>EPSG:2309</SRS>
+ <SRS>EPSG:2310</SRS>
+ <SRS>EPSG:2311</SRS>
+ <SRS>EPSG:2312</SRS>
+ <SRS>EPSG:2313</SRS>
+ <SRS>EPSG:2314</SRS>
+ <SRS>EPSG:2315</SRS>
+ <SRS>EPSG:2316</SRS>
+ <SRS>EPSG:2317</SRS>
+ <SRS>EPSG:2318</SRS>
+ <SRS>EPSG:2319</SRS>
+ <SRS>EPSG:2320</SRS>
+ <SRS>EPSG:2321</SRS>
+ <SRS>EPSG:2322</SRS>
+ <SRS>EPSG:2323</SRS>
+ <SRS>EPSG:2324</SRS>
+ <SRS>EPSG:2325</SRS>
+ <SRS>EPSG:2326</SRS>
+ <SRS>EPSG:2327</SRS>
+ <SRS>EPSG:2328</SRS>
+ <SRS>EPSG:2329</SRS>
+ <SRS>EPSG:2330</SRS>
+ <SRS>EPSG:2331</SRS>
+ <SRS>EPSG:2332</SRS>
+ <SRS>EPSG:2333</SRS>
+ <SRS>EPSG:2334</SRS>
+ <SRS>EPSG:2335</SRS>
+ <SRS>EPSG:2336</SRS>
+ <SRS>EPSG:2337</SRS>
+ <SRS>EPSG:2338</SRS>
+ <SRS>EPSG:2339</SRS>
+ <SRS>EPSG:2340</SRS>
+ <SRS>EPSG:2341</SRS>
+ <SRS>EPSG:2342</SRS>
+ <SRS>EPSG:2343</SRS>
+ <SRS>EPSG:2344</SRS>
+ <SRS>EPSG:2345</SRS>
+ <SRS>EPSG:2346</SRS>
+ <SRS>EPSG:2347</SRS>
+ <SRS>EPSG:2348</SRS>
+ <SRS>EPSG:2349</SRS>
+ <SRS>EPSG:2350</SRS>
+ <SRS>EPSG:2351</SRS>
+ <SRS>EPSG:2352</SRS>
+ <SRS>EPSG:2353</SRS>
+ <SRS>EPSG:2354</SRS>
+ <SRS>EPSG:2355</SRS>
+ <SRS>EPSG:2356</SRS>
+ <SRS>EPSG:2357</SRS>
+ <SRS>EPSG:2358</SRS>
+ <SRS>EPSG:2359</SRS>
+ <SRS>EPSG:2360</SRS>
+ <SRS>EPSG:2361</SRS>
+ <SRS>EPSG:2362</SRS>
+ <SRS>EPSG:2363</SRS>
+ <SRS>EPSG:2364</SRS>
+ <SRS>EPSG:2365</SRS>
+ <SRS>EPSG:2366</SRS>
+ <SRS>EPSG:2367</SRS>
+ <SRS>EPSG:2368</SRS>
+ <SRS>EPSG:2369</SRS>
+ <SRS>EPSG:2370</SRS>
+ <SRS>EPSG:2371</SRS>
+ <SRS>EPSG:2372</SRS>
+ <SRS>EPSG:2373</SRS>
+ <SRS>EPSG:2374</SRS>
+ <SRS>EPSG:2375</SRS>
+ <SRS>EPSG:2376</SRS>
+ <SRS>EPSG:2377</SRS>
+ <SRS>EPSG:2378</SRS>
+ <SRS>EPSG:2379</SRS>
+ <SRS>EPSG:2380</SRS>
+ <SRS>EPSG:2381</SRS>
+ <SRS>EPSG:2382</SRS>
+ <SRS>EPSG:2383</SRS>
+ <SRS>EPSG:2384</SRS>
+ <SRS>EPSG:2385</SRS>
+ <SRS>EPSG:2386</SRS>
+ <SRS>EPSG:2387</SRS>
+ <SRS>EPSG:2388</SRS>
+ <SRS>EPSG:2389</SRS>
+ <SRS>EPSG:2390</SRS>
+ <SRS>EPSG:2391</SRS>
+ <SRS>EPSG:2392</SRS>
+ <SRS>EPSG:2393</SRS>
+ <SRS>EPSG:2394</SRS>
+ <SRS>EPSG:2395</SRS>
+ <SRS>EPSG:2396</SRS>
+ <SRS>EPSG:2397</SRS>
+ <SRS>EPSG:2398</SRS>
+ <SRS>EPSG:2399</SRS>
+ <SRS>EPSG:2400</SRS>
+ <SRS>EPSG:2401</SRS>
+ <SRS>EPSG:2402</SRS>
+ <SRS>EPSG:2403</SRS>
+ <SRS>EPSG:2404</SRS>
+ <SRS>EPSG:2405</SRS>
+ <SRS>EPSG:2406</SRS>
+ <SRS>EPSG:2407</SRS>
+ <SRS>EPSG:2408</SRS>
+ <SRS>EPSG:2409</SRS>
+ <SRS>EPSG:2410</SRS>
+ <SRS>EPSG:2411</SRS>
+ <SRS>EPSG:2412</SRS>
+ <SRS>EPSG:2413</SRS>
+ <SRS>EPSG:2414</SRS>
+ <SRS>EPSG:2415</SRS>
+ <SRS>EPSG:2416</SRS>
+ <SRS>EPSG:2417</SRS>
+ <SRS>EPSG:2418</SRS>
+ <SRS>EPSG:2419</SRS>
+ <SRS>EPSG:2420</SRS>
+ <SRS>EPSG:2421</SRS>
+ <SRS>EPSG:2422</SRS>
+ <SRS>EPSG:2423</SRS>
+ <SRS>EPSG:2424</SRS>
+ <SRS>EPSG:2425</SRS>
+ <SRS>EPSG:2426</SRS>
+ <SRS>EPSG:2427</SRS>
+ <SRS>EPSG:2428</SRS>
+ <SRS>EPSG:2429</SRS>
+ <SRS>EPSG:2430</SRS>
+ <SRS>EPSG:2431</SRS>
+ <SRS>EPSG:2432</SRS>
+ <SRS>EPSG:2433</SRS>
+ <SRS>EPSG:2434</SRS>
+ <SRS>EPSG:2435</SRS>
+ <SRS>EPSG:2436</SRS>
+ <SRS>EPSG:2437</SRS>
+ <SRS>EPSG:2438</SRS>
+ <SRS>EPSG:2439</SRS>
+ <SRS>EPSG:2440</SRS>
+ <SRS>EPSG:2441</SRS>
+ <SRS>EPSG:2442</SRS>
+ <SRS>EPSG:2443</SRS>
+ <SRS>EPSG:2444</SRS>
+ <SRS>EPSG:2445</SRS>
+ <SRS>EPSG:2446</SRS>
+ <SRS>EPSG:2447</SRS>
+ <SRS>EPSG:2448</SRS>
+ <SRS>EPSG:2449</SRS>
+ <SRS>EPSG:2450</SRS>
+ <SRS>EPSG:2451</SRS>
+ <SRS>EPSG:2452</SRS>
+ <SRS>EPSG:2453</SRS>
+ <SRS>EPSG:2454</SRS>
+ <SRS>EPSG:2455</SRS>
+ <SRS>EPSG:2456</SRS>
+ <SRS>EPSG:2457</SRS>
+ <SRS>EPSG:2458</SRS>
+ <SRS>EPSG:2459</SRS>
+ <SRS>EPSG:2460</SRS>
+ <SRS>EPSG:2461</SRS>
+ <SRS>EPSG:2462</SRS>
+ <SRS>EPSG:2463</SRS>
+ <SRS>EPSG:2464</SRS>
+ <SRS>EPSG:2465</SRS>
+ <SRS>EPSG:2466</SRS>
+ <SRS>EPSG:2467</SRS>
+ <SRS>EPSG:2468</SRS>
+ <SRS>EPSG:2469</SRS>
+ <SRS>EPSG:2470</SRS>
+ <SRS>EPSG:2471</SRS>
+ <SRS>EPSG:2472</SRS>
+ <SRS>EPSG:2473</SRS>
+ <SRS>EPSG:2474</SRS>
+ <SRS>EPSG:2475</SRS>
+ <SRS>EPSG:2476</SRS>
+ <SRS>EPSG:2477</SRS>
+ <SRS>EPSG:2478</SRS>
+ <SRS>EPSG:2479</SRS>
+ <SRS>EPSG:2480</SRS>
+ <SRS>EPSG:2481</SRS>
+ <SRS>EPSG:2482</SRS>
+ <SRS>EPSG:2483</SRS>
+ <SRS>EPSG:2484</SRS>
+ <SRS>EPSG:2485</SRS>
+ <SRS>EPSG:2486</SRS>
+ <SRS>EPSG:2487</SRS>
+ <SRS>EPSG:2488</SRS>
+ <SRS>EPSG:2489</SRS>
+ <SRS>EPSG:2490</SRS>
+ <SRS>EPSG:2491</SRS>
+ <SRS>EPSG:2492</SRS>
+ <SRS>EPSG:2493</SRS>
+ <SRS>EPSG:2494</SRS>
+ <SRS>EPSG:2495</SRS>
+ <SRS>EPSG:2496</SRS>
+ <SRS>EPSG:2497</SRS>
+ <SRS>EPSG:2498</SRS>
+ <SRS>EPSG:2499</SRS>
+ <SRS>EPSG:2500</SRS>
+ <SRS>EPSG:2501</SRS>
+ <SRS>EPSG:2502</SRS>
+ <SRS>EPSG:2503</SRS>
+ <SRS>EPSG:2504</SRS>
+ <SRS>EPSG:2505</SRS>
+ <SRS>EPSG:2506</SRS>
+ <SRS>EPSG:2507</SRS>
+ <SRS>EPSG:2508</SRS>
+ <SRS>EPSG:2509</SRS>
+ <SRS>EPSG:2510</SRS>
+ <SRS>EPSG:2511</SRS>
+ <SRS>EPSG:2512</SRS>
+ <SRS>EPSG:2513</SRS>
+ <SRS>EPSG:2514</SRS>
+ <SRS>EPSG:2515</SRS>
+ <SRS>EPSG:2516</SRS>
+ <SRS>EPSG:2517</SRS>
+ <SRS>EPSG:2518</SRS>
+ <SRS>EPSG:2519</SRS>
+ <SRS>EPSG:2520</SRS>
+ <SRS>EPSG:2521</SRS>
+ <SRS>EPSG:2522</SRS>
+ <SRS>EPSG:2523</SRS>
+ <SRS>EPSG:2524</SRS>
+ <SRS>EPSG:2525</SRS>
+ <SRS>EPSG:2526</SRS>
+ <SRS>EPSG:2527</SRS>
+ <SRS>EPSG:2528</SRS>
+ <SRS>EPSG:2529</SRS>
+ <SRS>EPSG:2530</SRS>
+ <SRS>EPSG:2531</SRS>
+ <SRS>EPSG:2532</SRS>
+ <SRS>EPSG:2533</SRS>
+ <SRS>EPSG:2534</SRS>
+ <SRS>EPSG:2535</SRS>
+ <SRS>EPSG:2536</SRS>
+ <SRS>EPSG:2537</SRS>
+ <SRS>EPSG:2538</SRS>
+ <SRS>EPSG:2539</SRS>
+ <SRS>EPSG:2540</SRS>
+ <SRS>EPSG:2541</SRS>
+ <SRS>EPSG:2542</SRS>
+ <SRS>EPSG:2543</SRS>
+ <SRS>EPSG:2544</SRS>
+ <SRS>EPSG:2545</SRS>
+ <SRS>EPSG:2546</SRS>
+ <SRS>EPSG:2547</SRS>
+ <SRS>EPSG:2548</SRS>
+ <SRS>EPSG:2549</SRS>
+ <SRS>EPSG:2550</SRS>
+ <SRS>EPSG:2551</SRS>
+ <SRS>EPSG:2552</SRS>
+ <SRS>EPSG:2553</SRS>
+ <SRS>EPSG:2554</SRS>
+ <SRS>EPSG:2555</SRS>
+ <SRS>EPSG:2556</SRS>
+ <SRS>EPSG:2557</SRS>
+ <SRS>EPSG:2558</SRS>
+ <SRS>EPSG:2559</SRS>
+ <SRS>EPSG:2560</SRS>
+ <SRS>EPSG:2561</SRS>
+ <SRS>EPSG:2562</SRS>
+ <SRS>EPSG:2563</SRS>
+ <SRS>EPSG:2564</SRS>
+ <SRS>EPSG:2565</SRS>
+ <SRS>EPSG:2566</SRS>
+ <SRS>EPSG:2567</SRS>
+ <SRS>EPSG:2568</SRS>
+ <SRS>EPSG:2569</SRS>
+ <SRS>EPSG:2570</SRS>
+ <SRS>EPSG:2571</SRS>
+ <SRS>EPSG:2572</SRS>
+ <SRS>EPSG:2573</SRS>
+ <SRS>EPSG:2574</SRS>
+ <SRS>EPSG:2575</SRS>
+ <SRS>EPSG:2576</SRS>
+ <SRS>EPSG:2577</SRS>
+ <SRS>EPSG:2578</SRS>
+ <SRS>EPSG:2579</SRS>
+ <SRS>EPSG:2580</SRS>
+ <SRS>EPSG:2581</SRS>
+ <SRS>EPSG:2582</SRS>
+ <SRS>EPSG:2583</SRS>
+ <SRS>EPSG:2584</SRS>
+ <SRS>EPSG:2585</SRS>
+ <SRS>EPSG:2586</SRS>
+ <SRS>EPSG:2587</SRS>
+ <SRS>EPSG:2588</SRS>
+ <SRS>EPSG:2589</SRS>
+ <SRS>EPSG:2590</SRS>
+ <SRS>EPSG:2591</SRS>
+ <SRS>EPSG:2592</SRS>
+ <SRS>EPSG:2593</SRS>
+ <SRS>EPSG:2594</SRS>
+ <SRS>EPSG:2595</SRS>
+ <SRS>EPSG:2596</SRS>
+ <SRS>EPSG:2597</SRS>
+ <SRS>EPSG:2598</SRS>
+ <SRS>EPSG:2599</SRS>
+ <SRS>EPSG:2600</SRS>
+ <SRS>EPSG:2601</SRS>
+ <SRS>EPSG:2602</SRS>
+ <SRS>EPSG:2603</SRS>
+ <SRS>EPSG:2604</SRS>
+ <SRS>EPSG:2605</SRS>
+ <SRS>EPSG:2606</SRS>
+ <SRS>EPSG:2607</SRS>
+ <SRS>EPSG:2608</SRS>
+ <SRS>EPSG:2609</SRS>
+ <SRS>EPSG:2610</SRS>
+ <SRS>EPSG:2611</SRS>
+ <SRS>EPSG:2612</SRS>
+ <SRS>EPSG:2613</SRS>
+ <SRS>EPSG:2614</SRS>
+ <SRS>EPSG:2615</SRS>
+ <SRS>EPSG:2616</SRS>
+ <SRS>EPSG:2617</SRS>
+ <SRS>EPSG:2618</SRS>
+ <SRS>EPSG:2619</SRS>
+ <SRS>EPSG:2620</SRS>
+ <SRS>EPSG:2621</SRS>
+ <SRS>EPSG:2622</SRS>
+ <SRS>EPSG:2623</SRS>
+ <SRS>EPSG:2624</SRS>
+ <SRS>EPSG:2625</SRS>
+ <SRS>EPSG:2626</SRS>
+ <SRS>EPSG:2627</SRS>
+ <SRS>EPSG:2628</SRS>
+ <SRS>EPSG:2629</SRS>
+ <SRS>EPSG:2630</SRS>
+ <SRS>EPSG:2631</SRS>
+ <SRS>EPSG:2632</SRS>
+ <SRS>EPSG:2633</SRS>
+ <SRS>EPSG:2634</SRS>
+ <SRS>EPSG:2635</SRS>
+ <SRS>EPSG:2636</SRS>
+ <SRS>EPSG:2637</SRS>
+ <SRS>EPSG:2638</SRS>
+ <SRS>EPSG:2639</SRS>
+ <SRS>EPSG:2640</SRS>
+ <SRS>EPSG:2641</SRS>
+ <SRS>EPSG:2642</SRS>
+ <SRS>EPSG:2643</SRS>
+ <SRS>EPSG:2644</SRS>
+ <SRS>EPSG:2645</SRS>
+ <SRS>EPSG:2646</SRS>
+ <SRS>EPSG:2647</SRS>
+ <SRS>EPSG:2648</SRS>
+ <SRS>EPSG:2649</SRS>
+ <SRS>EPSG:2650</SRS>
+ <SRS>EPSG:2651</SRS>
+ <SRS>EPSG:2652</SRS>
+ <SRS>EPSG:2653</SRS>
+ <SRS>EPSG:2654</SRS>
+ <SRS>EPSG:2655</SRS>
+ <SRS>EPSG:2656</SRS>
+ <SRS>EPSG:2657</SRS>
+ <SRS>EPSG:2658</SRS>
+ <SRS>EPSG:2659</SRS>
+ <SRS>EPSG:2660</SRS>
+ <SRS>EPSG:2661</SRS>
+ <SRS>EPSG:2662</SRS>
+ <SRS>EPSG:2663</SRS>
+ <SRS>EPSG:2664</SRS>
+ <SRS>EPSG:2665</SRS>
+ <SRS>EPSG:2666</SRS>
+ <SRS>EPSG:2667</SRS>
+ <SRS>EPSG:2668</SRS>
+ <SRS>EPSG:2669</SRS>
+ <SRS>EPSG:2670</SRS>
+ <SRS>EPSG:2671</SRS>
+ <SRS>EPSG:2672</SRS>
+ <SRS>EPSG:2673</SRS>
+ <SRS>EPSG:2674</SRS>
+ <SRS>EPSG:2675</SRS>
+ <SRS>EPSG:2676</SRS>
+ <SRS>EPSG:2677</SRS>
+ <SRS>EPSG:2678</SRS>
+ <SRS>EPSG:2679</SRS>
+ <SRS>EPSG:2680</SRS>
+ <SRS>EPSG:2681</SRS>
+ <SRS>EPSG:2682</SRS>
+ <SRS>EPSG:2683</SRS>
+ <SRS>EPSG:2684</SRS>
+ <SRS>EPSG:2685</SRS>
+ <SRS>EPSG:2686</SRS>
+ <SRS>EPSG:2687</SRS>
+ <SRS>EPSG:2688</SRS>
+ <SRS>EPSG:2689</SRS>
+ <SRS>EPSG:2690</SRS>
+ <SRS>EPSG:2691</SRS>
+ <SRS>EPSG:2692</SRS>
+ <SRS>EPSG:2693</SRS>
+ <SRS>EPSG:2694</SRS>
+ <SRS>EPSG:2695</SRS>
+ <SRS>EPSG:2696</SRS>
+ <SRS>EPSG:2697</SRS>
+ <SRS>EPSG:2698</SRS>
+ <SRS>EPSG:2699</SRS>
+ <SRS>EPSG:2700</SRS>
+ <SRS>EPSG:2701</SRS>
+ <SRS>EPSG:2702</SRS>
+ <SRS>EPSG:2703</SRS>
+ <SRS>EPSG:2704</SRS>
+ <SRS>EPSG:2705</SRS>
+ <SRS>EPSG:2706</SRS>
+ <SRS>EPSG:2707</SRS>
+ <SRS>EPSG:2708</SRS>
+ <SRS>EPSG:2709</SRS>
+ <SRS>EPSG:2710</SRS>
+ <SRS>EPSG:2711</SRS>
+ <SRS>EPSG:2712</SRS>
+ <SRS>EPSG:2713</SRS>
+ <SRS>EPSG:2714</SRS>
+ <SRS>EPSG:2715</SRS>
+ <SRS>EPSG:2716</SRS>
+ <SRS>EPSG:2717</SRS>
+ <SRS>EPSG:2718</SRS>
+ <SRS>EPSG:2719</SRS>
+ <SRS>EPSG:2720</SRS>
+ <SRS>EPSG:2721</SRS>
+ <SRS>EPSG:2722</SRS>
+ <SRS>EPSG:2723</SRS>
+ <SRS>EPSG:2724</SRS>
+ <SRS>EPSG:2725</SRS>
+ <SRS>EPSG:2726</SRS>
+ <SRS>EPSG:2727</SRS>
+ <SRS>EPSG:2728</SRS>
+ <SRS>EPSG:2729</SRS>
+ <SRS>EPSG:2730</SRS>
+ <SRS>EPSG:2731</SRS>
+ <SRS>EPSG:2732</SRS>
+ <SRS>EPSG:2733</SRS>
+ <SRS>EPSG:2734</SRS>
+ <SRS>EPSG:2735</SRS>
+ <SRS>EPSG:2736</SRS>
+ <SRS>EPSG:2737</SRS>
+ <SRS>EPSG:2738</SRS>
+ <SRS>EPSG:2739</SRS>
+ <SRS>EPSG:2740</SRS>
+ <SRS>EPSG:2741</SRS>
+ <SRS>EPSG:2742</SRS>
+ <SRS>EPSG:2743</SRS>
+ <SRS>EPSG:2744</SRS>
+ <SRS>EPSG:2745</SRS>
+ <SRS>EPSG:2746</SRS>
+ <SRS>EPSG:2747</SRS>
+ <SRS>EPSG:2748</SRS>
+ <SRS>EPSG:2749</SRS>
+ <SRS>EPSG:2750</SRS>
+ <SRS>EPSG:2751</SRS>
+ <SRS>EPSG:2752</SRS>
+ <SRS>EPSG:2753</SRS>
+ <SRS>EPSG:2754</SRS>
+ <SRS>EPSG:2755</SRS>
+ <SRS>EPSG:2756</SRS>
+ <SRS>EPSG:2757</SRS>
+ <SRS>EPSG:2758</SRS>
+ <SRS>EPSG:2759</SRS>
+ <SRS>EPSG:2760</SRS>
+ <SRS>EPSG:2761</SRS>
+ <SRS>EPSG:2762</SRS>
+ <SRS>EPSG:2763</SRS>
+ <SRS>EPSG:2764</SRS>
+ <SRS>EPSG:2765</SRS>
+ <SRS>EPSG:2766</SRS>
+ <SRS>EPSG:2767</SRS>
+ <SRS>EPSG:2768</SRS>
+ <SRS>EPSG:2769</SRS>
+ <SRS>EPSG:2770</SRS>
+ <SRS>EPSG:2771</SRS>
+ <SRS>EPSG:2772</SRS>
+ <SRS>EPSG:2773</SRS>
+ <SRS>EPSG:2774</SRS>
+ <SRS>EPSG:2775</SRS>
+ <SRS>EPSG:2776</SRS>
+ <SRS>EPSG:2777</SRS>
+ <SRS>EPSG:2778</SRS>
+ <SRS>EPSG:2779</SRS>
+ <SRS>EPSG:2780</SRS>
+ <SRS>EPSG:2781</SRS>
+ <SRS>EPSG:2782</SRS>
+ <SRS>EPSG:2783</SRS>
+ <SRS>EPSG:2784</SRS>
+ <SRS>EPSG:2785</SRS>
+ <SRS>EPSG:2786</SRS>
+ <SRS>EPSG:2787</SRS>
+ <SRS>EPSG:2788</SRS>
+ <SRS>EPSG:2789</SRS>
+ <SRS>EPSG:2790</SRS>
+ <SRS>EPSG:2791</SRS>
+ <SRS>EPSG:2792</SRS>
+ <SRS>EPSG:2793</SRS>
+ <SRS>EPSG:2794</SRS>
+ <SRS>EPSG:2795</SRS>
+ <SRS>EPSG:2796</SRS>
+ <SRS>EPSG:2797</SRS>
+ <SRS>EPSG:2798</SRS>
+ <SRS>EPSG:2799</SRS>
+ <SRS>EPSG:2800</SRS>
+ <SRS>EPSG:2801</SRS>
+ <SRS>EPSG:2802</SRS>
+ <SRS>EPSG:2803</SRS>
+ <SRS>EPSG:2804</SRS>
+ <SRS>EPSG:2805</SRS>
+ <SRS>EPSG:2806</SRS>
+ <SRS>EPSG:2807</SRS>
+ <SRS>EPSG:2808</SRS>
+ <SRS>EPSG:2809</SRS>
+ <SRS>EPSG:2810</SRS>
+ <SRS>EPSG:2811</SRS>
+ <SRS>EPSG:2812</SRS>
+ <SRS>EPSG:2813</SRS>
+ <SRS>EPSG:2814</SRS>
+ <SRS>EPSG:2815</SRS>
+ <SRS>EPSG:2816</SRS>
+ <SRS>EPSG:2817</SRS>
+ <SRS>EPSG:2818</SRS>
+ <SRS>EPSG:2819</SRS>
+ <SRS>EPSG:2820</SRS>
+ <SRS>EPSG:2821</SRS>
+ <SRS>EPSG:2822</SRS>
+ <SRS>EPSG:2823</SRS>
+ <SRS>EPSG:2824</SRS>
+ <SRS>EPSG:2825</SRS>
+ <SRS>EPSG:2826</SRS>
+ <SRS>EPSG:2827</SRS>
+ <SRS>EPSG:2828</SRS>
+ <SRS>EPSG:2829</SRS>
+ <SRS>EPSG:2830</SRS>
+ <SRS>EPSG:2831</SRS>
+ <SRS>EPSG:2832</SRS>
+ <SRS>EPSG:2833</SRS>
+ <SRS>EPSG:2834</SRS>
+ <SRS>EPSG:2835</SRS>
+ <SRS>EPSG:2836</SRS>
+ <SRS>EPSG:2837</SRS>
+ <SRS>EPSG:2838</SRS>
+ <SRS>EPSG:2839</SRS>
+ <SRS>EPSG:2840</SRS>
+ <SRS>EPSG:2841</SRS>
+ <SRS>EPSG:2842</SRS>
+ <SRS>EPSG:2843</SRS>
+ <SRS>EPSG:2844</SRS>
+ <SRS>EPSG:2845</SRS>
+ <SRS>EPSG:2846</SRS>
+ <SRS>EPSG:2847</SRS>
+ <SRS>EPSG:2848</SRS>
+ <SRS>EPSG:2849</SRS>
+ <SRS>EPSG:2850</SRS>
+ <SRS>EPSG:2851</SRS>
+ <SRS>EPSG:2852</SRS>
+ <SRS>EPSG:2853</SRS>
+ <SRS>EPSG:2854</SRS>
+ <SRS>EPSG:2855</SRS>
+ <SRS>EPSG:2856</SRS>
+ <SRS>EPSG:2857</SRS>
+ <SRS>EPSG:2858</SRS>
+ <SRS>EPSG:2859</SRS>
+ <SRS>EPSG:2860</SRS>
+ <SRS>EPSG:2861</SRS>
+ <SRS>EPSG:2862</SRS>
+ <SRS>EPSG:2863</SRS>
+ <SRS>EPSG:2864</SRS>
+ <SRS>EPSG:2865</SRS>
+ <SRS>EPSG:2866</SRS>
+ <SRS>EPSG:2867</SRS>
+ <SRS>EPSG:2868</SRS>
+ <SRS>EPSG:2869</SRS>
+ <SRS>EPSG:2870</SRS>
+ <SRS>EPSG:2871</SRS>
+ <SRS>EPSG:2872</SRS>
+ <SRS>EPSG:2873</SRS>
+ <SRS>EPSG:2874</SRS>
+ <SRS>EPSG:2875</SRS>
+ <SRS>EPSG:2876</SRS>
+ <SRS>EPSG:2877</SRS>
+ <SRS>EPSG:2878</SRS>
+ <SRS>EPSG:2879</SRS>
+ <SRS>EPSG:2880</SRS>
+ <SRS>EPSG:2881</SRS>
+ <SRS>EPSG:2882</SRS>
+ <SRS>EPSG:2883</SRS>
+ <SRS>EPSG:2884</SRS>
+ <SRS>EPSG:2885</SRS>
+ <SRS>EPSG:2886</SRS>
+ <SRS>EPSG:2887</SRS>
+ <SRS>EPSG:2888</SRS>
+ <SRS>EPSG:2889</SRS>
+ <SRS>EPSG:2890</SRS>
+ <SRS>EPSG:2891</SRS>
+ <SRS>EPSG:2892</SRS>
+ <SRS>EPSG:2893</SRS>
+ <SRS>EPSG:2894</SRS>
+ <SRS>EPSG:2895</SRS>
+ <SRS>EPSG:2896</SRS>
+ <SRS>EPSG:2897</SRS>
+ <SRS>EPSG:2898</SRS>
+ <SRS>EPSG:2899</SRS>
+ <SRS>EPSG:2900</SRS>
+ <SRS>EPSG:2901</SRS>
+ <SRS>EPSG:2902</SRS>
+ <SRS>EPSG:2903</SRS>
+ <SRS>EPSG:2904</SRS>
+ <SRS>EPSG:2905</SRS>
+ <SRS>EPSG:2906</SRS>
+ <SRS>EPSG:2907</SRS>
+ <SRS>EPSG:2908</SRS>
+ <SRS>EPSG:2909</SRS>
+ <SRS>EPSG:2910</SRS>
+ <SRS>EPSG:2911</SRS>
+ <SRS>EPSG:2912</SRS>
+ <SRS>EPSG:2913</SRS>
+ <SRS>EPSG:2914</SRS>
+ <SRS>EPSG:2915</SRS>
+ <SRS>EPSG:2916</SRS>
+ <SRS>EPSG:2917</SRS>
+ <SRS>EPSG:2918</SRS>
+ <SRS>EPSG:2919</SRS>
+ <SRS>EPSG:2920</SRS>
+ <SRS>EPSG:2921</SRS>
+ <SRS>EPSG:2922</SRS>
+ <SRS>EPSG:2923</SRS>
+ <SRS>EPSG:2924</SRS>
+ <SRS>EPSG:2925</SRS>
+ <SRS>EPSG:2926</SRS>
+ <SRS>EPSG:2927</SRS>
+ <SRS>EPSG:2928</SRS>
+ <SRS>EPSG:2929</SRS>
+ <SRS>EPSG:2930</SRS>
+ <SRS>EPSG:2931</SRS>
+ <SRS>EPSG:2932</SRS>
+ <SRS>EPSG:2933</SRS>
+ <SRS>EPSG:2934</SRS>
+ <SRS>EPSG:2935</SRS>
+ <SRS>EPSG:2936</SRS>
+ <SRS>EPSG:2937</SRS>
+ <SRS>EPSG:2938</SRS>
+ <SRS>EPSG:2939</SRS>
+ <SRS>EPSG:2940</SRS>
+ <SRS>EPSG:2941</SRS>
+ <SRS>EPSG:2942</SRS>
+ <SRS>EPSG:2943</SRS>
+ <SRS>EPSG:2944</SRS>
+ <SRS>EPSG:2945</SRS>
+ <SRS>EPSG:2946</SRS>
+ <SRS>EPSG:2947</SRS>
+ <SRS>EPSG:2948</SRS>
+ <SRS>EPSG:2949</SRS>
+ <SRS>EPSG:2950</SRS>
+ <SRS>EPSG:2951</SRS>
+ <SRS>EPSG:2952</SRS>
+ <SRS>EPSG:2953</SRS>
+ <SRS>EPSG:2954</SRS>
+ <SRS>EPSG:2955</SRS>
+ <SRS>EPSG:2956</SRS>
+ <SRS>EPSG:2957</SRS>
+ <SRS>EPSG:2958</SRS>
+ <SRS>EPSG:2959</SRS>
+ <SRS>EPSG:2960</SRS>
+ <SRS>EPSG:2961</SRS>
+ <SRS>EPSG:2962</SRS>
+ <SRS>EPSG:2963</SRS>
+ <SRS>EPSG:2964</SRS>
+ <SRS>EPSG:2965</SRS>
+ <SRS>EPSG:2966</SRS>
+ <SRS>EPSG:2967</SRS>
+ <SRS>EPSG:2968</SRS>
+ <SRS>EPSG:2969</SRS>
+ <SRS>EPSG:2970</SRS>
+ <SRS>EPSG:2971</SRS>
+ <SRS>EPSG:2972</SRS>
+ <SRS>EPSG:2973</SRS>
+ <SRS>EPSG:2975</SRS>
+ <SRS>EPSG:2976</SRS>
+ <SRS>EPSG:2977</SRS>
+ <SRS>EPSG:2978</SRS>
+ <SRS>EPSG:2979</SRS>
+ <SRS>EPSG:2980</SRS>
+ <SRS>EPSG:2981</SRS>
+ <SRS>EPSG:2982</SRS>
+ <SRS>EPSG:2983</SRS>
+ <SRS>EPSG:2984</SRS>
+ <SRS>EPSG:2985</SRS>
+ <SRS>EPSG:2986</SRS>
+ <SRS>EPSG:2987</SRS>
+ <SRS>EPSG:2988</SRS>
+ <SRS>EPSG:2989</SRS>
+ <SRS>EPSG:2990</SRS>
+ <SRS>EPSG:2991</SRS>
+ <SRS>EPSG:2992</SRS>
+ <SRS>EPSG:2993</SRS>
+ <SRS>EPSG:2994</SRS>
+ <SRS>EPSG:2995</SRS>
+ <SRS>EPSG:2996</SRS>
+ <SRS>EPSG:2997</SRS>
+ <SRS>EPSG:2998</SRS>
+ <SRS>EPSG:2999</SRS>
+ <SRS>EPSG:3000</SRS>
+ <SRS>EPSG:3001</SRS>
+ <SRS>EPSG:3002</SRS>
+ <SRS>EPSG:3003</SRS>
+ <SRS>EPSG:3004</SRS>
+ <SRS>EPSG:3005</SRS>
+ <SRS>EPSG:3006</SRS>
+ <SRS>EPSG:3007</SRS>
+ <SRS>EPSG:3008</SRS>
+ <SRS>EPSG:3009</SRS>
+ <SRS>EPSG:3010</SRS>
+ <SRS>EPSG:3011</SRS>
+ <SRS>EPSG:3012</SRS>
+ <SRS>EPSG:3013</SRS>
+ <SRS>EPSG:3014</SRS>
+ <SRS>EPSG:3015</SRS>
+ <SRS>EPSG:3016</SRS>
+ <SRS>EPSG:3017</SRS>
+ <SRS>EPSG:3018</SRS>
+ <SRS>EPSG:3019</SRS>
+ <SRS>EPSG:3020</SRS>
+ <SRS>EPSG:3021</SRS>
+ <SRS>EPSG:3022</SRS>
+ <SRS>EPSG:3023</SRS>
+ <SRS>EPSG:3024</SRS>
+ <SRS>EPSG:3025</SRS>
+ <SRS>EPSG:3026</SRS>
+ <SRS>EPSG:3027</SRS>
+ <SRS>EPSG:3028</SRS>
+ <SRS>EPSG:3029</SRS>
+ <SRS>EPSG:3030</SRS>
+ <SRS>EPSG:3031</SRS>
+ <SRS>EPSG:3032</SRS>
+ <SRS>EPSG:3033</SRS>
+ <SRS>EPSG:3034</SRS>
+ <SRS>EPSG:3035</SRS>
+ <SRS>EPSG:3036</SRS>
+ <SRS>EPSG:3037</SRS>
+ <SRS>EPSG:3038</SRS>
+ <SRS>EPSG:3039</SRS>
+ <SRS>EPSG:3040</SRS>
+ <SRS>EPSG:3041</SRS>
+ <SRS>EPSG:3042</SRS>
+ <SRS>EPSG:3043</SRS>
+ <SRS>EPSG:3044</SRS>
+ <SRS>EPSG:3045</SRS>
+ <SRS>EPSG:3046</SRS>
+ <SRS>EPSG:3047</SRS>
+ <SRS>EPSG:3048</SRS>
+ <SRS>EPSG:3049</SRS>
+ <SRS>EPSG:3050</SRS>
+ <SRS>EPSG:3051</SRS>
+ <SRS>EPSG:3052</SRS>
+ <SRS>EPSG:3053</SRS>
+ <SRS>EPSG:3054</SRS>
+ <SRS>EPSG:3055</SRS>
+ <SRS>EPSG:3056</SRS>
+ <SRS>EPSG:3057</SRS>
+ <SRS>EPSG:3058</SRS>
+ <SRS>EPSG:3059</SRS>
+ <SRS>EPSG:3060</SRS>
+ <SRS>EPSG:3061</SRS>
+ <SRS>EPSG:3062</SRS>
+ <SRS>EPSG:3063</SRS>
+ <SRS>EPSG:3064</SRS>
+ <SRS>EPSG:3065</SRS>
+ <SRS>EPSG:3066</SRS>
+ <SRS>EPSG:3067</SRS>
+ <SRS>EPSG:3068</SRS>
+ <SRS>EPSG:3069</SRS>
+ <SRS>EPSG:3070</SRS>
+ <SRS>EPSG:3071</SRS>
+ <SRS>EPSG:3072</SRS>
+ <SRS>EPSG:3073</SRS>
+ <SRS>EPSG:3074</SRS>
+ <SRS>EPSG:3075</SRS>
+ <SRS>EPSG:3076</SRS>
+ <SRS>EPSG:3077</SRS>
+ <SRS>EPSG:3078</SRS>
+ <SRS>EPSG:3079</SRS>
+ <SRS>EPSG:3080</SRS>
+ <SRS>EPSG:3081</SRS>
+ <SRS>EPSG:3082</SRS>
+ <SRS>EPSG:3083</SRS>
+ <SRS>EPSG:3084</SRS>
+ <SRS>EPSG:3085</SRS>
+ <SRS>EPSG:3086</SRS>
+ <SRS>EPSG:3087</SRS>
+ <SRS>EPSG:3088</SRS>
+ <SRS>EPSG:3089</SRS>
+ <SRS>EPSG:3090</SRS>
+ <SRS>EPSG:3091</SRS>
+ <SRS>EPSG:3092</SRS>
+ <SRS>EPSG:3093</SRS>
+ <SRS>EPSG:3094</SRS>
+ <SRS>EPSG:3095</SRS>
+ <SRS>EPSG:3096</SRS>
+ <SRS>EPSG:3097</SRS>
+ <SRS>EPSG:3098</SRS>
+ <SRS>EPSG:3099</SRS>
+ <SRS>EPSG:3100</SRS>
+ <SRS>EPSG:3101</SRS>
+ <SRS>EPSG:3102</SRS>
+ <SRS>EPSG:3103</SRS>
+ <SRS>EPSG:3104</SRS>
+ <SRS>EPSG:3105</SRS>
+ <SRS>EPSG:3106</SRS>
+ <SRS>EPSG:3107</SRS>
+ <SRS>EPSG:3108</SRS>
+ <SRS>EPSG:3109</SRS>
+ <SRS>EPSG:3110</SRS>
+ <SRS>EPSG:3111</SRS>
+ <SRS>EPSG:3112</SRS>
+ <SRS>EPSG:3113</SRS>
+ <SRS>EPSG:3114</SRS>
+ <SRS>EPSG:3115</SRS>
+ <SRS>EPSG:3116</SRS>
+ <SRS>EPSG:3117</SRS>
+ <SRS>EPSG:3118</SRS>
+ <SRS>EPSG:3119</SRS>
+ <SRS>EPSG:3120</SRS>
+ <SRS>EPSG:3121</SRS>
+ <SRS>EPSG:3122</SRS>
+ <SRS>EPSG:3123</SRS>
+ <SRS>EPSG:3124</SRS>
+ <SRS>EPSG:3125</SRS>
+ <SRS>EPSG:3126</SRS>
+ <SRS>EPSG:3127</SRS>
+ <SRS>EPSG:3128</SRS>
+ <SRS>EPSG:3129</SRS>
+ <SRS>EPSG:3130</SRS>
+ <SRS>EPSG:3131</SRS>
+ <SRS>EPSG:3132</SRS>
+ <SRS>EPSG:3133</SRS>
+ <SRS>EPSG:3134</SRS>
+ <SRS>EPSG:3135</SRS>
+ <SRS>EPSG:3136</SRS>
+ <SRS>EPSG:3137</SRS>
+ <SRS>EPSG:3138</SRS>
+ <SRS>EPSG:3139</SRS>
+ <SRS>EPSG:3140</SRS>
+ <SRS>EPSG:3141</SRS>
+ <SRS>EPSG:3142</SRS>
+ <SRS>EPSG:3143</SRS>
+ <SRS>EPSG:3144</SRS>
+ <SRS>EPSG:3145</SRS>
+ <SRS>EPSG:3146</SRS>
+ <SRS>EPSG:3147</SRS>
+ <SRS>EPSG:3148</SRS>
+ <SRS>EPSG:3149</SRS>
+ <SRS>EPSG:3150</SRS>
+ <SRS>EPSG:3151</SRS>
+ <SRS>EPSG:3152</SRS>
+ <SRS>EPSG:3153</SRS>
+ <SRS>EPSG:3154</SRS>
+ <SRS>EPSG:3155</SRS>
+ <SRS>EPSG:3156</SRS>
+ <SRS>EPSG:3157</SRS>
+ <SRS>EPSG:3158</SRS>
+ <SRS>EPSG:3159</SRS>
+ <SRS>EPSG:3160</SRS>
+ <SRS>EPSG:3161</SRS>
+ <SRS>EPSG:3162</SRS>
+ <SRS>EPSG:3163</SRS>
+ <SRS>EPSG:3164</SRS>
+ <SRS>EPSG:3165</SRS>
+ <SRS>EPSG:3166</SRS>
+ <SRS>EPSG:3167</SRS>
+ <SRS>EPSG:3168</SRS>
+ <SRS>EPSG:3169</SRS>
+ <SRS>EPSG:3170</SRS>
+ <SRS>EPSG:3171</SRS>
+ <SRS>EPSG:3172</SRS>
+ <SRS>EPSG:3173</SRS>
+ <SRS>EPSG:3174</SRS>
+ <SRS>EPSG:3175</SRS>
+ <SRS>EPSG:3176</SRS>
+ <SRS>EPSG:3177</SRS>
+ <SRS>EPSG:3178</SRS>
+ <SRS>EPSG:3179</SRS>
+ <SRS>EPSG:3180</SRS>
+ <SRS>EPSG:3181</SRS>
+ <SRS>EPSG:3182</SRS>
+ <SRS>EPSG:3183</SRS>
+ <SRS>EPSG:3184</SRS>
+ <SRS>EPSG:3185</SRS>
+ <SRS>EPSG:3186</SRS>
+ <SRS>EPSG:3187</SRS>
+ <SRS>EPSG:3188</SRS>
+ <SRS>EPSG:3189</SRS>
+ <SRS>EPSG:3190</SRS>
+ <SRS>EPSG:3191</SRS>
+ <SRS>EPSG:3192</SRS>
+ <SRS>EPSG:3193</SRS>
+ <SRS>EPSG:3194</SRS>
+ <SRS>EPSG:3195</SRS>
+ <SRS>EPSG:3196</SRS>
+ <SRS>EPSG:3197</SRS>
+ <SRS>EPSG:3198</SRS>
+ <SRS>EPSG:3199</SRS>
+ <SRS>EPSG:3200</SRS>
+ <SRS>EPSG:3201</SRS>
+ <SRS>EPSG:3202</SRS>
+ <SRS>EPSG:3203</SRS>
+ <SRS>EPSG:3204</SRS>
+ <SRS>EPSG:3205</SRS>
+ <SRS>EPSG:3206</SRS>
+ <SRS>EPSG:3207</SRS>
+ <SRS>EPSG:3208</SRS>
+ <SRS>EPSG:3209</SRS>
+ <SRS>EPSG:3210</SRS>
+ <SRS>EPSG:3211</SRS>
+ <SRS>EPSG:3212</SRS>
+ <SRS>EPSG:3213</SRS>
+ <SRS>EPSG:3214</SRS>
+ <SRS>EPSG:3215</SRS>
+ <SRS>EPSG:3216</SRS>
+ <SRS>EPSG:3217</SRS>
+ <SRS>EPSG:3218</SRS>
+ <SRS>EPSG:3219</SRS>
+ <SRS>EPSG:3220</SRS>
+ <SRS>EPSG:3221</SRS>
+ <SRS>EPSG:3222</SRS>
+ <SRS>EPSG:3223</SRS>
+ <SRS>EPSG:3224</SRS>
+ <SRS>EPSG:3225</SRS>
+ <SRS>EPSG:3226</SRS>
+ <SRS>EPSG:3227</SRS>
+ <SRS>EPSG:3228</SRS>
+ <SRS>EPSG:3229</SRS>
+ <SRS>EPSG:3230</SRS>
+ <SRS>EPSG:3231</SRS>
+ <SRS>EPSG:3232</SRS>
+ <SRS>EPSG:3233</SRS>
+ <SRS>EPSG:3234</SRS>
+ <SRS>EPSG:3235</SRS>
+ <SRS>EPSG:3236</SRS>
+ <SRS>EPSG:3237</SRS>
+ <SRS>EPSG:3238</SRS>
+ <SRS>EPSG:3239</SRS>
+ <SRS>EPSG:3240</SRS>
+ <SRS>EPSG:3241</SRS>
+ <SRS>EPSG:3242</SRS>
+ <SRS>EPSG:3243</SRS>
+ <SRS>EPSG:3244</SRS>
+ <SRS>EPSG:3245</SRS>
+ <SRS>EPSG:3246</SRS>
+ <SRS>EPSG:3247</SRS>
+ <SRS>EPSG:3248</SRS>
+ <SRS>EPSG:3249</SRS>
+ <SRS>EPSG:3250</SRS>
+ <SRS>EPSG:3251</SRS>
+ <SRS>EPSG:3252</SRS>
+ <SRS>EPSG:3253</SRS>
+ <SRS>EPSG:3254</SRS>
+ <SRS>EPSG:3255</SRS>
+ <SRS>EPSG:3256</SRS>
+ <SRS>EPSG:3257</SRS>
+ <SRS>EPSG:3258</SRS>
+ <SRS>EPSG:3259</SRS>
+ <SRS>EPSG:3260</SRS>
+ <SRS>EPSG:3261</SRS>
+ <SRS>EPSG:3262</SRS>
+ <SRS>EPSG:3263</SRS>
+ <SRS>EPSG:3264</SRS>
+ <SRS>EPSG:3265</SRS>
+ <SRS>EPSG:3266</SRS>
+ <SRS>EPSG:3267</SRS>
+ <SRS>EPSG:3268</SRS>
+ <SRS>EPSG:3269</SRS>
+ <SRS>EPSG:3270</SRS>
+ <SRS>EPSG:3271</SRS>
+ <SRS>EPSG:3272</SRS>
+ <SRS>EPSG:3273</SRS>
+ <SRS>EPSG:3274</SRS>
+ <SRS>EPSG:3275</SRS>
+ <SRS>EPSG:3276</SRS>
+ <SRS>EPSG:3277</SRS>
+ <SRS>EPSG:3278</SRS>
+ <SRS>EPSG:3279</SRS>
+ <SRS>EPSG:3280</SRS>
+ <SRS>EPSG:3281</SRS>
+ <SRS>EPSG:3282</SRS>
+ <SRS>EPSG:3283</SRS>
+ <SRS>EPSG:3284</SRS>
+ <SRS>EPSG:3285</SRS>
+ <SRS>EPSG:3286</SRS>
+ <SRS>EPSG:3287</SRS>
+ <SRS>EPSG:3288</SRS>
+ <SRS>EPSG:3289</SRS>
+ <SRS>EPSG:3290</SRS>
+ <SRS>EPSG:3291</SRS>
+ <SRS>EPSG:3292</SRS>
+ <SRS>EPSG:3293</SRS>
+ <SRS>EPSG:3294</SRS>
+ <SRS>EPSG:3295</SRS>
+ <SRS>EPSG:3296</SRS>
+ <SRS>EPSG:3297</SRS>
+ <SRS>EPSG:3298</SRS>
+ <SRS>EPSG:3299</SRS>
+ <SRS>EPSG:3300</SRS>
+ <SRS>EPSG:3301</SRS>
+ <SRS>EPSG:3302</SRS>
+ <SRS>EPSG:3303</SRS>
+ <SRS>EPSG:3304</SRS>
+ <SRS>EPSG:3305</SRS>
+ <SRS>EPSG:3306</SRS>
+ <SRS>EPSG:3307</SRS>
+ <SRS>EPSG:3308</SRS>
+ <SRS>EPSG:3309</SRS>
+ <SRS>EPSG:3310</SRS>
+ <SRS>EPSG:3311</SRS>
+ <SRS>EPSG:3312</SRS>
+ <SRS>EPSG:3313</SRS>
+ <SRS>EPSG:3314</SRS>
+ <SRS>EPSG:3315</SRS>
+ <SRS>EPSG:3316</SRS>
+ <SRS>EPSG:3317</SRS>
+ <SRS>EPSG:3318</SRS>
+ <SRS>EPSG:3319</SRS>
+ <SRS>EPSG:3320</SRS>
+ <SRS>EPSG:3321</SRS>
+ <SRS>EPSG:3322</SRS>
+ <SRS>EPSG:3323</SRS>
+ <SRS>EPSG:3324</SRS>
+ <SRS>EPSG:3325</SRS>
+ <SRS>EPSG:3326</SRS>
+ <SRS>EPSG:3327</SRS>
+ <SRS>EPSG:3328</SRS>
+ <SRS>EPSG:3329</SRS>
+ <SRS>EPSG:3330</SRS>
+ <SRS>EPSG:3331</SRS>
+ <SRS>EPSG:3332</SRS>
+ <SRS>EPSG:3333</SRS>
+ <SRS>EPSG:3334</SRS>
+ <SRS>EPSG:3335</SRS>
+ <SRS>EPSG:3336</SRS>
+ <SRS>EPSG:3337</SRS>
+ <SRS>EPSG:3338</SRS>
+ <SRS>EPSG:3339</SRS>
+ <SRS>EPSG:3340</SRS>
+ <SRS>EPSG:3341</SRS>
+ <SRS>EPSG:3342</SRS>
+ <SRS>EPSG:3343</SRS>
+ <SRS>EPSG:3344</SRS>
+ <SRS>EPSG:3345</SRS>
+ <SRS>EPSG:3346</SRS>
+ <SRS>EPSG:3347</SRS>
+ <SRS>EPSG:3348</SRS>
+ <SRS>EPSG:3349</SRS>
+ <SRS>EPSG:3350</SRS>
+ <SRS>EPSG:3351</SRS>
+ <SRS>EPSG:3352</SRS>
+ <SRS>EPSG:3353</SRS>
+ <SRS>EPSG:3354</SRS>
+ <SRS>EPSG:3355</SRS>
+ <SRS>EPSG:3356</SRS>
+ <SRS>EPSG:3357</SRS>
+ <SRS>EPSG:3358</SRS>
+ <SRS>EPSG:3359</SRS>
+ <SRS>EPSG:3360</SRS>
+ <SRS>EPSG:3361</SRS>
+ <SRS>EPSG:3362</SRS>
+ <SRS>EPSG:3363</SRS>
+ <SRS>EPSG:3364</SRS>
+ <SRS>EPSG:3365</SRS>
+ <SRS>EPSG:3366</SRS>
+ <SRS>EPSG:3367</SRS>
+ <SRS>EPSG:3368</SRS>
+ <SRS>EPSG:3369</SRS>
+ <SRS>EPSG:3370</SRS>
+ <SRS>EPSG:3371</SRS>
+ <SRS>EPSG:3372</SRS>
+ <SRS>EPSG:3373</SRS>
+ <SRS>EPSG:3374</SRS>
+ <SRS>EPSG:3375</SRS>
+ <SRS>EPSG:3376</SRS>
+ <SRS>EPSG:3377</SRS>
+ <SRS>EPSG:3378</SRS>
+ <SRS>EPSG:3379</SRS>
+ <SRS>EPSG:3380</SRS>
+ <SRS>EPSG:3381</SRS>
+ <SRS>EPSG:3382</SRS>
+ <SRS>EPSG:3383</SRS>
+ <SRS>EPSG:3384</SRS>
+ <SRS>EPSG:3385</SRS>
+ <SRS>EPSG:3386</SRS>
+ <SRS>EPSG:3387</SRS>
+ <SRS>EPSG:3388</SRS>
+ <SRS>EPSG:3389</SRS>
+ <SRS>EPSG:3390</SRS>
+ <SRS>EPSG:3391</SRS>
+ <SRS>EPSG:3392</SRS>
+ <SRS>EPSG:3393</SRS>
+ <SRS>EPSG:3394</SRS>
+ <SRS>EPSG:3395</SRS>
+ <SRS>EPSG:3396</SRS>
+ <SRS>EPSG:3397</SRS>
+ <SRS>EPSG:3398</SRS>
+ <SRS>EPSG:3399</SRS>
+ <SRS>EPSG:3400</SRS>
+ <SRS>EPSG:3401</SRS>
+ <SRS>EPSG:3402</SRS>
+ <SRS>EPSG:3403</SRS>
+ <SRS>EPSG:3404</SRS>
+ <SRS>EPSG:3405</SRS>
+ <SRS>EPSG:3406</SRS>
+ <SRS>EPSG:3407</SRS>
+ <SRS>EPSG:3408</SRS>
+ <SRS>EPSG:3409</SRS>
+ <SRS>EPSG:3410</SRS>
+ <SRS>EPSG:3411</SRS>
+ <SRS>EPSG:3412</SRS>
+ <SRS>EPSG:3413</SRS>
+ <SRS>EPSG:3414</SRS>
+ <SRS>EPSG:3415</SRS>
+ <SRS>EPSG:3416</SRS>
+ <SRS>EPSG:3417</SRS>
+ <SRS>EPSG:3418</SRS>
+ <SRS>EPSG:3419</SRS>
+ <SRS>EPSG:3420</SRS>
+ <SRS>EPSG:3421</SRS>
+ <SRS>EPSG:3422</SRS>
+ <SRS>EPSG:3423</SRS>
+ <SRS>EPSG:3424</SRS>
+ <SRS>EPSG:3425</SRS>
+ <SRS>EPSG:3426</SRS>
+ <SRS>EPSG:3427</SRS>
+ <SRS>EPSG:3428</SRS>
+ <SRS>EPSG:3429</SRS>
+ <SRS>EPSG:3430</SRS>
+ <SRS>EPSG:3431</SRS>
+ <SRS>EPSG:3432</SRS>
+ <SRS>EPSG:3433</SRS>
+ <SRS>EPSG:3434</SRS>
+ <SRS>EPSG:3435</SRS>
+ <SRS>EPSG:3436</SRS>
+ <SRS>EPSG:3437</SRS>
+ <SRS>EPSG:3438</SRS>
+ <SRS>EPSG:3439</SRS>
+ <SRS>EPSG:3440</SRS>
+ <SRS>EPSG:3441</SRS>
+ <SRS>EPSG:3442</SRS>
+ <SRS>EPSG:3443</SRS>
+ <SRS>EPSG:3444</SRS>
+ <SRS>EPSG:3445</SRS>
+ <SRS>EPSG:3446</SRS>
+ <SRS>EPSG:3447</SRS>
+ <SRS>EPSG:3448</SRS>
+ <SRS>EPSG:3449</SRS>
+ <SRS>EPSG:3450</SRS>
+ <SRS>EPSG:3451</SRS>
+ <SRS>EPSG:3452</SRS>
+ <SRS>EPSG:3453</SRS>
+ <SRS>EPSG:3454</SRS>
+ <SRS>EPSG:3455</SRS>
+ <SRS>EPSG:3456</SRS>
+ <SRS>EPSG:3457</SRS>
+ <SRS>EPSG:3458</SRS>
+ <SRS>EPSG:3459</SRS>
+ <SRS>EPSG:3460</SRS>
+ <SRS>EPSG:3461</SRS>
+ <SRS>EPSG:3462</SRS>
+ <SRS>EPSG:3463</SRS>
+ <SRS>EPSG:3464</SRS>
+ <SRS>EPSG:3560</SRS>
+ <SRS>EPSG:3561</SRS>
+ <SRS>EPSG:3562</SRS>
+ <SRS>EPSG:3563</SRS>
+ <SRS>EPSG:3564</SRS>
+ <SRS>EPSG:3565</SRS>
+ <SRS>EPSG:3566</SRS>
+ <SRS>EPSG:3567</SRS>
+ <SRS>EPSG:3568</SRS>
+ <SRS>EPSG:3569</SRS>
+ <SRS>EPSG:3570</SRS>
+ <SRS>EPSG:3571</SRS>
+ <SRS>EPSG:3572</SRS>
+ <SRS>EPSG:3573</SRS>
+ <SRS>EPSG:3574</SRS>
+ <SRS>EPSG:3575</SRS>
+ <SRS>EPSG:3576</SRS>
+ <SRS>EPSG:3577</SRS>
+ <SRS>EPSG:3920</SRS>
+ <SRS>EPSG:3991</SRS>
+ <SRS>EPSG:3992</SRS>
+ <SRS>EPSG:3993</SRS>
+ <SRS>EPSG:4001</SRS>
+ <SRS>EPSG:4002</SRS>
+ <SRS>EPSG:4003</SRS>
+ <SRS>EPSG:4004</SRS>
+ <SRS>EPSG:4005</SRS>
+ <SRS>EPSG:4006</SRS>
+ <SRS>EPSG:4007</SRS>
+ <SRS>EPSG:4008</SRS>
+ <SRS>EPSG:4009</SRS>
+ <SRS>EPSG:4010</SRS>
+ <SRS>EPSG:4011</SRS>
+ <SRS>EPSG:4012</SRS>
+ <SRS>EPSG:4013</SRS>
+ <SRS>EPSG:4014</SRS>
+ <SRS>EPSG:4015</SRS>
+ <SRS>EPSG:4016</SRS>
+ <SRS>EPSG:4018</SRS>
+ <SRS>EPSG:4019</SRS>
+ <SRS>EPSG:4020</SRS>
+ <SRS>EPSG:4021</SRS>
+ <SRS>EPSG:4022</SRS>
+ <SRS>EPSG:4024</SRS>
+ <SRS>EPSG:4025</SRS>
+ <SRS>EPSG:4027</SRS>
+ <SRS>EPSG:4028</SRS>
+ <SRS>EPSG:4029</SRS>
+ <SRS>EPSG:4030</SRS>
+ <SRS>EPSG:4031</SRS>
+ <SRS>EPSG:4032</SRS>
+ <SRS>EPSG:4033</SRS>
+ <SRS>EPSG:4034</SRS>
+ <SRS>EPSG:4035</SRS>
+ <SRS>EPSG:4036</SRS>
+ <SRS>EPSG:4041</SRS>
+ <SRS>EPSG:4042</SRS>
+ <SRS>EPSG:4043</SRS>
+ <SRS>EPSG:4044</SRS>
+ <SRS>EPSG:4045</SRS>
+ <SRS>EPSG:4047</SRS>
+ <SRS>EPSG:4052</SRS>
+ <SRS>EPSG:4053</SRS>
+ <SRS>EPSG:4054</SRS>
+ <SRS>EPSG:4120</SRS>
+ <SRS>EPSG:4121</SRS>
+ <SRS>EPSG:4122</SRS>
+ <SRS>EPSG:4123</SRS>
+ <SRS>EPSG:4124</SRS>
+ <SRS>EPSG:4125</SRS>
+ <SRS>EPSG:4126</SRS>
+ <SRS>EPSG:4127</SRS>
+ <SRS>EPSG:4128</SRS>
+ <SRS>EPSG:4129</SRS>
+ <SRS>EPSG:4130</SRS>
+ <SRS>EPSG:4131</SRS>
+ <SRS>EPSG:4132</SRS>
+ <SRS>EPSG:4133</SRS>
+ <SRS>EPSG:4134</SRS>
+ <SRS>EPSG:4135</SRS>
+ <SRS>EPSG:4136</SRS>
+ <SRS>EPSG:4137</SRS>
+ <SRS>EPSG:4138</SRS>
+ <SRS>EPSG:4139</SRS>
+ <SRS>EPSG:4140</SRS>
+ <SRS>EPSG:4141</SRS>
+ <SRS>EPSG:4142</SRS>
+ <SRS>EPSG:4143</SRS>
+ <SRS>EPSG:4144</SRS>
+ <SRS>EPSG:4145</SRS>
+ <SRS>EPSG:4146</SRS>
+ <SRS>EPSG:4147</SRS>
+ <SRS>EPSG:4148</SRS>
+ <SRS>EPSG:4149</SRS>
+ <SRS>EPSG:4150</SRS>
+ <SRS>EPSG:4151</SRS>
+ <SRS>EPSG:4152</SRS>
+ <SRS>EPSG:4153</SRS>
+ <SRS>EPSG:4154</SRS>
+ <SRS>EPSG:4155</SRS>
+ <SRS>EPSG:4156</SRS>
+ <SRS>EPSG:4157</SRS>
+ <SRS>EPSG:4158</SRS>
+ <SRS>EPSG:4159</SRS>
+ <SRS>EPSG:4160</SRS>
+ <SRS>EPSG:4161</SRS>
+ <SRS>EPSG:4162</SRS>
+ <SRS>EPSG:4163</SRS>
+ <SRS>EPSG:4164</SRS>
+ <SRS>EPSG:4165</SRS>
+ <SRS>EPSG:4166</SRS>
+ <SRS>EPSG:4167</SRS>
+ <SRS>EPSG:4168</SRS>
+ <SRS>EPSG:4169</SRS>
+ <SRS>EPSG:4170</SRS>
+ <SRS>EPSG:4171</SRS>
+ <SRS>EPSG:4172</SRS>
+ <SRS>EPSG:4173</SRS>
+ <SRS>EPSG:4174</SRS>
+ <SRS>EPSG:4175</SRS>
+ <SRS>EPSG:4176</SRS>
+ <SRS>EPSG:4178</SRS>
+ <SRS>EPSG:4179</SRS>
+ <SRS>EPSG:4180</SRS>
+ <SRS>EPSG:4181</SRS>
+ <SRS>EPSG:4182</SRS>
+ <SRS>EPSG:4183</SRS>
+ <SRS>EPSG:4184</SRS>
+ <SRS>EPSG:4185</SRS>
+ <SRS>EPSG:4188</SRS>
+ <SRS>EPSG:4189</SRS>
+ <SRS>EPSG:4190</SRS>
+ <SRS>EPSG:4191</SRS>
+ <SRS>EPSG:4192</SRS>
+ <SRS>EPSG:4193</SRS>
+ <SRS>EPSG:4194</SRS>
+ <SRS>EPSG:4195</SRS>
+ <SRS>EPSG:4196</SRS>
+ <SRS>EPSG:4197</SRS>
+ <SRS>EPSG:4198</SRS>
+ <SRS>EPSG:4199</SRS>
+ <SRS>EPSG:4200</SRS>
+ <SRS>EPSG:4201</SRS>
+ <SRS>EPSG:4202</SRS>
+ <SRS>EPSG:4203</SRS>
+ <SRS>EPSG:4204</SRS>
+ <SRS>EPSG:4205</SRS>
+ <SRS>EPSG:4206</SRS>
+ <SRS>EPSG:4207</SRS>
+ <SRS>EPSG:4208</SRS>
+ <SRS>EPSG:4209</SRS>
+ <SRS>EPSG:4210</SRS>
+ <SRS>EPSG:4211</SRS>
+ <SRS>EPSG:4212</SRS>
+ <SRS>EPSG:4213</SRS>
+ <SRS>EPSG:4214</SRS>
+ <SRS>EPSG:4215</SRS>
+ <SRS>EPSG:4216</SRS>
+ <SRS>EPSG:4218</SRS>
+ <SRS>EPSG:4219</SRS>
+ <SRS>EPSG:4220</SRS>
+ <SRS>EPSG:4221</SRS>
+ <SRS>EPSG:4222</SRS>
+ <SRS>EPSG:4223</SRS>
+ <SRS>EPSG:4224</SRS>
+ <SRS>EPSG:4225</SRS>
+ <SRS>EPSG:4226</SRS>
+ <SRS>EPSG:4227</SRS>
+ <SRS>EPSG:4228</SRS>
+ <SRS>EPSG:4229</SRS>
+ <SRS>EPSG:4230</SRS>
+ <SRS>EPSG:4231</SRS>
+ <SRS>EPSG:4232</SRS>
+ <SRS>EPSG:4233</SRS>
+ <SRS>EPSG:4234</SRS>
+ <SRS>EPSG:4235</SRS>
+ <SRS>EPSG:4236</SRS>
+ <SRS>EPSG:4237</SRS>
+ <SRS>EPSG:4238</SRS>
+ <SRS>EPSG:4239</SRS>
+ <SRS>EPSG:4240</SRS>
+ <SRS>EPSG:4241</SRS>
+ <SRS>EPSG:4242</SRS>
+ <SRS>EPSG:4243</SRS>
+ <SRS>EPSG:4244</SRS>
+ <SRS>EPSG:4245</SRS>
+ <SRS>EPSG:4246</SRS>
+ <SRS>EPSG:4247</SRS>
+ <SRS>EPSG:4248</SRS>
+ <SRS>EPSG:4249</SRS>
+ <SRS>EPSG:4250</SRS>
+ <SRS>EPSG:4251</SRS>
+ <SRS>EPSG:4252</SRS>
+ <SRS>EPSG:4253</SRS>
+ <SRS>EPSG:4254</SRS>
+ <SRS>EPSG:4255</SRS>
+ <SRS>EPSG:4256</SRS>
+ <SRS>EPSG:4257</SRS>
+ <SRS>EPSG:4258</SRS>
+ <SRS>EPSG:4259</SRS>
+ <SRS>EPSG:4260</SRS>
+ <SRS>EPSG:4261</SRS>
+ <SRS>EPSG:4262</SRS>
+ <SRS>EPSG:4263</SRS>
+ <SRS>EPSG:4264</SRS>
+ <SRS>EPSG:4265</SRS>
+ <SRS>EPSG:4266</SRS>
+ <SRS>EPSG:4267</SRS>
+ <SRS>EPSG:4268</SRS>
+ <SRS>EPSG:4269</SRS>
+ <SRS>EPSG:4270</SRS>
+ <SRS>EPSG:4271</SRS>
+ <SRS>EPSG:4272</SRS>
+ <SRS>EPSG:4273</SRS>
+ <SRS>EPSG:4274</SRS>
+ <SRS>EPSG:4275</SRS>
+ <SRS>EPSG:4276</SRS>
+ <SRS>EPSG:4277</SRS>
+ <SRS>EPSG:4278</SRS>
+ <SRS>EPSG:4279</SRS>
+ <SRS>EPSG:4280</SRS>
+ <SRS>EPSG:4281</SRS>
+ <SRS>EPSG:4282</SRS>
+ <SRS>EPSG:4283</SRS>
+ <SRS>EPSG:4284</SRS>
+ <SRS>EPSG:4285</SRS>
+ <SRS>EPSG:4286</SRS>
+ <SRS>EPSG:4287</SRS>
+ <SRS>EPSG:4288</SRS>
+ <SRS>EPSG:4289</SRS>
+ <SRS>EPSG:4291</SRS>
+ <SRS>EPSG:4292</SRS>
+ <SRS>EPSG:4293</SRS>
+ <SRS>EPSG:4294</SRS>
+ <SRS>EPSG:4295</SRS>
+ <SRS>EPSG:4296</SRS>
+ <SRS>EPSG:4297</SRS>
+ <SRS>EPSG:4298</SRS>
+ <SRS>EPSG:4299</SRS>
+ <SRS>EPSG:4300</SRS>
+ <SRS>EPSG:4301</SRS>
+ <SRS>EPSG:4302</SRS>
+ <SRS>EPSG:4303</SRS>
+ <SRS>EPSG:4304</SRS>
+ <SRS>EPSG:4306</SRS>
+ <SRS>EPSG:4307</SRS>
+ <SRS>EPSG:4308</SRS>
+ <SRS>EPSG:4309</SRS>
+ <SRS>EPSG:4310</SRS>
+ <SRS>EPSG:4311</SRS>
+ <SRS>EPSG:4312</SRS>
+ <SRS>EPSG:4313</SRS>
+ <SRS>EPSG:4314</SRS>
+ <SRS>EPSG:4315</SRS>
+ <SRS>EPSG:4316</SRS>
+ <SRS>EPSG:4317</SRS>
+ <SRS>EPSG:4318</SRS>
+ <SRS>EPSG:4319</SRS>
+ <SRS>EPSG:4322</SRS>
+ <SRS>EPSG:4324</SRS>
+ <SRS>EPSG:4326</SRS>
+ <SRS>EPSG:4327</SRS>
+ <SRS>EPSG:4328</SRS>
+ <SRS>EPSG:4329</SRS>
+ <SRS>EPSG:4330</SRS>
+ <SRS>EPSG:4331</SRS>
+ <SRS>EPSG:4332</SRS>
+ <SRS>EPSG:4333</SRS>
+ <SRS>EPSG:4334</SRS>
+ <SRS>EPSG:4335</SRS>
+ <SRS>EPSG:4336</SRS>
+ <SRS>EPSG:4337</SRS>
+ <SRS>EPSG:4338</SRS>
+ <SRS>EPSG:4339</SRS>
+ <SRS>EPSG:4340</SRS>
+ <SRS>EPSG:4341</SRS>
+ <SRS>EPSG:4342</SRS>
+ <SRS>EPSG:4343</SRS>
+ <SRS>EPSG:4344</SRS>
+ <SRS>EPSG:4345</SRS>
+ <SRS>EPSG:4346</SRS>
+ <SRS>EPSG:4347</SRS>
+ <SRS>EPSG:4348</SRS>
+ <SRS>EPSG:4349</SRS>
+ <SRS>EPSG:4350</SRS>
+ <SRS>EPSG:4351</SRS>
+ <SRS>EPSG:4352</SRS>
+ <SRS>EPSG:4353</SRS>
+ <SRS>EPSG:4354</SRS>
+ <SRS>EPSG:4355</SRS>
+ <SRS>EPSG:4356</SRS>
+ <SRS>EPSG:4357</SRS>
+ <SRS>EPSG:4358</SRS>
+ <SRS>EPSG:4359</SRS>
+ <SRS>EPSG:4360</SRS>
+ <SRS>EPSG:4361</SRS>
+ <SRS>EPSG:4362</SRS>
+ <SRS>EPSG:4363</SRS>
+ <SRS>EPSG:4364</SRS>
+ <SRS>EPSG:4365</SRS>
+ <SRS>EPSG:4366</SRS>
+ <SRS>EPSG:4367</SRS>
+ <SRS>EPSG:4368</SRS>
+ <SRS>EPSG:4369</SRS>
+ <SRS>EPSG:4370</SRS>
+ <SRS>EPSG:4371</SRS>
+ <SRS>EPSG:4372</SRS>
+ <SRS>EPSG:4373</SRS>
+ <SRS>EPSG:4374</SRS>
+ <SRS>EPSG:4375</SRS>
+ <SRS>EPSG:4376</SRS>
+ <SRS>EPSG:4377</SRS>
+ <SRS>EPSG:4378</SRS>
+ <SRS>EPSG:4379</SRS>
+ <SRS>EPSG:4380</SRS>
+ <SRS>EPSG:4381</SRS>
+ <SRS>EPSG:4382</SRS>
+ <SRS>EPSG:4383</SRS>
+ <SRS>EPSG:4384</SRS>
+ <SRS>EPSG:4385</SRS>
+ <SRS>EPSG:4386</SRS>
+ <SRS>EPSG:4387</SRS>
+ <SRS>EPSG:4388</SRS>
+ <SRS>EPSG:4389</SRS>
+ <SRS>EPSG:4600</SRS>
+ <SRS>EPSG:4601</SRS>
+ <SRS>EPSG:4602</SRS>
+ <SRS>EPSG:4603</SRS>
+ <SRS>EPSG:4604</SRS>
+ <SRS>EPSG:4605</SRS>
+ <SRS>EPSG:4606</SRS>
+ <SRS>EPSG:4607</SRS>
+ <SRS>EPSG:4608</SRS>
+ <SRS>EPSG:4609</SRS>
+ <SRS>EPSG:4610</SRS>
+ <SRS>EPSG:4611</SRS>
+ <SRS>EPSG:4612</SRS>
+ <SRS>EPSG:4613</SRS>
+ <SRS>EPSG:4614</SRS>
+ <SRS>EPSG:4615</SRS>
+ <SRS>EPSG:4616</SRS>
+ <SRS>EPSG:4617</SRS>
+ <SRS>EPSG:4618</SRS>
+ <SRS>EPSG:4619</SRS>
+ <SRS>EPSG:4620</SRS>
+ <SRS>EPSG:4621</SRS>
+ <SRS>EPSG:4622</SRS>
+ <SRS>EPSG:4623</SRS>
+ <SRS>EPSG:4624</SRS>
+ <SRS>EPSG:4625</SRS>
+ <SRS>EPSG:4626</SRS>
+ <SRS>EPSG:4627</SRS>
+ <SRS>EPSG:4628</SRS>
+ <SRS>EPSG:4629</SRS>
+ <SRS>EPSG:4630</SRS>
+ <SRS>EPSG:4631</SRS>
+ <SRS>EPSG:4632</SRS>
+ <SRS>EPSG:4633</SRS>
+ <SRS>EPSG:4634</SRS>
+ <SRS>EPSG:4635</SRS>
+ <SRS>EPSG:4636</SRS>
+ <SRS>EPSG:4637</SRS>
+ <SRS>EPSG:4638</SRS>
+ <SRS>EPSG:4639</SRS>
+ <SRS>EPSG:4640</SRS>
+ <SRS>EPSG:4641</SRS>
+ <SRS>EPSG:4642</SRS>
+ <SRS>EPSG:4643</SRS>
+ <SRS>EPSG:4644</SRS>
+ <SRS>EPSG:4645</SRS>
+ <SRS>EPSG:4646</SRS>
+ <SRS>EPSG:4657</SRS>
+ <SRS>EPSG:4658</SRS>
+ <SRS>EPSG:4659</SRS>
+ <SRS>EPSG:4660</SRS>
+ <SRS>EPSG:4661</SRS>
+ <SRS>EPSG:4662</SRS>
+ <SRS>EPSG:4663</SRS>
+ <SRS>EPSG:4664</SRS>
+ <SRS>EPSG:4665</SRS>
+ <SRS>EPSG:4666</SRS>
+ <SRS>EPSG:4667</SRS>
+ <SRS>EPSG:4668</SRS>
+ <SRS>EPSG:4669</SRS>
+ <SRS>EPSG:4670</SRS>
+ <SRS>EPSG:4671</SRS>
+ <SRS>EPSG:4672</SRS>
+ <SRS>EPSG:4673</SRS>
+ <SRS>EPSG:4674</SRS>
+ <SRS>EPSG:4675</SRS>
+ <SRS>EPSG:4676</SRS>
+ <SRS>EPSG:4677</SRS>
+ <SRS>EPSG:4678</SRS>
+ <SRS>EPSG:4679</SRS>
+ <SRS>EPSG:4680</SRS>
+ <SRS>EPSG:4681</SRS>
+ <SRS>EPSG:4682</SRS>
+ <SRS>EPSG:4683</SRS>
+ <SRS>EPSG:4684</SRS>
+ <SRS>EPSG:4685</SRS>
+ <SRS>EPSG:4686</SRS>
+ <SRS>EPSG:4687</SRS>
+ <SRS>EPSG:4688</SRS>
+ <SRS>EPSG:4689</SRS>
+ <SRS>EPSG:4690</SRS>
+ <SRS>EPSG:4691</SRS>
+ <SRS>EPSG:4692</SRS>
+ <SRS>EPSG:4693</SRS>
+ <SRS>EPSG:4694</SRS>
+ <SRS>EPSG:4695</SRS>
+ <SRS>EPSG:4696</SRS>
+ <SRS>EPSG:4697</SRS>
+ <SRS>EPSG:4698</SRS>
+ <SRS>EPSG:4699</SRS>
+ <SRS>EPSG:4700</SRS>
+ <SRS>EPSG:4701</SRS>
+ <SRS>EPSG:4702</SRS>
+ <SRS>EPSG:4703</SRS>
+ <SRS>EPSG:4704</SRS>
+ <SRS>EPSG:4705</SRS>
+ <SRS>EPSG:4706</SRS>
+ <SRS>EPSG:4707</SRS>
+ <SRS>EPSG:4708</SRS>
+ <SRS>EPSG:4709</SRS>
+ <SRS>EPSG:4710</SRS>
+ <SRS>EPSG:4711</SRS>
+ <SRS>EPSG:4712</SRS>
+ <SRS>EPSG:4713</SRS>
+ <SRS>EPSG:4714</SRS>
+ <SRS>EPSG:4715</SRS>
+ <SRS>EPSG:4716</SRS>
+ <SRS>EPSG:4717</SRS>
+ <SRS>EPSG:4718</SRS>
+ <SRS>EPSG:4719</SRS>
+ <SRS>EPSG:4720</SRS>
+ <SRS>EPSG:4721</SRS>
+ <SRS>EPSG:4722</SRS>
+ <SRS>EPSG:4723</SRS>
+ <SRS>EPSG:4724</SRS>
+ <SRS>EPSG:4725</SRS>
+ <SRS>EPSG:4726</SRS>
+ <SRS>EPSG:4727</SRS>
+ <SRS>EPSG:4728</SRS>
+ <SRS>EPSG:4729</SRS>
+ <SRS>EPSG:4730</SRS>
+ <SRS>EPSG:4731</SRS>
+ <SRS>EPSG:4732</SRS>
+ <SRS>EPSG:4733</SRS>
+ <SRS>EPSG:4734</SRS>
+ <SRS>EPSG:4735</SRS>
+ <SRS>EPSG:4736</SRS>
+ <SRS>EPSG:4737</SRS>
+ <SRS>EPSG:4738</SRS>
+ <SRS>EPSG:4739</SRS>
+ <SRS>EPSG:4740</SRS>
+ <SRS>EPSG:4741</SRS>
+ <SRS>EPSG:4742</SRS>
+ <SRS>EPSG:4743</SRS>
+ <SRS>EPSG:4744</SRS>
+ <SRS>EPSG:4745</SRS>
+ <SRS>EPSG:4746</SRS>
+ <SRS>EPSG:4747</SRS>
+ <SRS>EPSG:4748</SRS>
+ <SRS>EPSG:4749</SRS>
+ <SRS>EPSG:4750</SRS>
+ <SRS>EPSG:4751</SRS>
+ <SRS>EPSG:4752</SRS>
+ <SRS>EPSG:4753</SRS>
+ <SRS>EPSG:4754</SRS>
+ <SRS>EPSG:4755</SRS>
+ <SRS>EPSG:4756</SRS>
+ <SRS>EPSG:4757</SRS>
+ <SRS>EPSG:4758</SRS>
+ <SRS>EPSG:4801</SRS>
+ <SRS>EPSG:4802</SRS>
+ <SRS>EPSG:4803</SRS>
+ <SRS>EPSG:4804</SRS>
+ <SRS>EPSG:4805</SRS>
+ <SRS>EPSG:4806</SRS>
+ <SRS>EPSG:4807</SRS>
+ <SRS>EPSG:4808</SRS>
+ <SRS>EPSG:4809</SRS>
+ <SRS>EPSG:4810</SRS>
+ <SRS>EPSG:4811</SRS>
+ <SRS>EPSG:4813</SRS>
+ <SRS>EPSG:4814</SRS>
+ <SRS>EPSG:4815</SRS>
+ <SRS>EPSG:4816</SRS>
+ <SRS>EPSG:4817</SRS>
+ <SRS>EPSG:4818</SRS>
+ <SRS>EPSG:4819</SRS>
+ <SRS>EPSG:4820</SRS>
+ <SRS>EPSG:4821</SRS>
+ <SRS>EPSG:4894</SRS>
+ <SRS>EPSG:4895</SRS>
+ <SRS>EPSG:4896</SRS>
+ <SRS>EPSG:4897</SRS>
+ <SRS>EPSG:4898</SRS>
+ <SRS>EPSG:4899</SRS>
+ <SRS>EPSG:4900</SRS>
+ <SRS>EPSG:4901</SRS>
+ <SRS>EPSG:4902</SRS>
+ <SRS>EPSG:4903</SRS>
+ <SRS>EPSG:4904</SRS>
+ <SRS>EPSG:4906</SRS>
+ <SRS>EPSG:4907</SRS>
+ <SRS>EPSG:4908</SRS>
+ <SRS>EPSG:4909</SRS>
+ <SRS>EPSG:4910</SRS>
+ <SRS>EPSG:4911</SRS>
+ <SRS>EPSG:4912</SRS>
+ <SRS>EPSG:4913</SRS>
+ <SRS>EPSG:4914</SRS>
+ <SRS>EPSG:4915</SRS>
+ <SRS>EPSG:4916</SRS>
+ <SRS>EPSG:4917</SRS>
+ <SRS>EPSG:4918</SRS>
+ <SRS>EPSG:4919</SRS>
+ <SRS>EPSG:4920</SRS>
+ <SRS>EPSG:4921</SRS>
+ <SRS>EPSG:4922</SRS>
+ <SRS>EPSG:4923</SRS>
+ <SRS>EPSG:4924</SRS>
+ <SRS>EPSG:4925</SRS>
+ <SRS>EPSG:4926</SRS>
+ <SRS>EPSG:4927</SRS>
+ <SRS>EPSG:4928</SRS>
+ <SRS>EPSG:4929</SRS>
+ <SRS>EPSG:4930</SRS>
+ <SRS>EPSG:4931</SRS>
+ <SRS>EPSG:4932</SRS>
+ <SRS>EPSG:4933</SRS>
+ <SRS>EPSG:4934</SRS>
+ <SRS>EPSG:4935</SRS>
+ <SRS>EPSG:4936</SRS>
+ <SRS>EPSG:4937</SRS>
+ <SRS>EPSG:4938</SRS>
+ <SRS>EPSG:4939</SRS>
+ <SRS>EPSG:4940</SRS>
+ <SRS>EPSG:4941</SRS>
+ <SRS>EPSG:4942</SRS>
+ <SRS>EPSG:4943</SRS>
+ <SRS>EPSG:4944</SRS>
+ <SRS>EPSG:4945</SRS>
+ <SRS>EPSG:4946</SRS>
+ <SRS>EPSG:4947</SRS>
+ <SRS>EPSG:4948</SRS>
+ <SRS>EPSG:4949</SRS>
+ <SRS>EPSG:4950</SRS>
+ <SRS>EPSG:4951</SRS>
+ <SRS>EPSG:4952</SRS>
+ <SRS>EPSG:4953</SRS>
+ <SRS>EPSG:4954</SRS>
+ <SRS>EPSG:4955</SRS>
+ <SRS>EPSG:4956</SRS>
+ <SRS>EPSG:4957</SRS>
+ <SRS>EPSG:4958</SRS>
+ <SRS>EPSG:4959</SRS>
+ <SRS>EPSG:4960</SRS>
+ <SRS>EPSG:4961</SRS>
+ <SRS>EPSG:4962</SRS>
+ <SRS>EPSG:4963</SRS>
+ <SRS>EPSG:4964</SRS>
+ <SRS>EPSG:4965</SRS>
+ <SRS>EPSG:4966</SRS>
+ <SRS>EPSG:4967</SRS>
+ <SRS>EPSG:4968</SRS>
+ <SRS>EPSG:4969</SRS>
+ <SRS>EPSG:4970</SRS>
+ <SRS>EPSG:4971</SRS>
+ <SRS>EPSG:4972</SRS>
+ <SRS>EPSG:4973</SRS>
+ <SRS>EPSG:4974</SRS>
+ <SRS>EPSG:4975</SRS>
+ <SRS>EPSG:4976</SRS>
+ <SRS>EPSG:4977</SRS>
+ <SRS>EPSG:4978</SRS>
+ <SRS>EPSG:4979</SRS>
+ <SRS>EPSG:4980</SRS>
+ <SRS>EPSG:4981</SRS>
+ <SRS>EPSG:4982</SRS>
+ <SRS>EPSG:4983</SRS>
+ <SRS>EPSG:4984</SRS>
+ <SRS>EPSG:4985</SRS>
+ <SRS>EPSG:4986</SRS>
+ <SRS>EPSG:4987</SRS>
+ <SRS>EPSG:4988</SRS>
+ <SRS>EPSG:4989</SRS>
+ <SRS>EPSG:4990</SRS>
+ <SRS>EPSG:4991</SRS>
+ <SRS>EPSG:4992</SRS>
+ <SRS>EPSG:4993</SRS>
+ <SRS>EPSG:4994</SRS>
+ <SRS>EPSG:4995</SRS>
+ <SRS>EPSG:4996</SRS>
+ <SRS>EPSG:4997</SRS>
+ <SRS>EPSG:4998</SRS>
+ <SRS>EPSG:4999</SRS>
+ <SRS>EPSG:5600</SRS>
+ <SRS>EPSG:5601</SRS>
+ <SRS>EPSG:5602</SRS>
+ <SRS>EPSG:5603</SRS>
+ <SRS>EPSG:5604</SRS>
+ <SRS>EPSG:5605</SRS>
+ <SRS>EPSG:5606</SRS>
+ <SRS>EPSG:5607</SRS>
+ <SRS>EPSG:5608</SRS>
+ <SRS>EPSG:5609</SRS>
+ <SRS>EPSG:5701</SRS>
+ <SRS>EPSG:5702</SRS>
+ <SRS>EPSG:5703</SRS>
+ <SRS>EPSG:5704</SRS>
+ <SRS>EPSG:5705</SRS>
+ <SRS>EPSG:5706</SRS>
+ <SRS>EPSG:5709</SRS>
+ <SRS>EPSG:5710</SRS>
+ <SRS>EPSG:5711</SRS>
+ <SRS>EPSG:5712</SRS>
+ <SRS>EPSG:5713</SRS>
+ <SRS>EPSG:5714</SRS>
+ <SRS>EPSG:5715</SRS>
+ <SRS>EPSG:5716</SRS>
+ <SRS>EPSG:5717</SRS>
+ <SRS>EPSG:5718</SRS>
+ <SRS>EPSG:5719</SRS>
+ <SRS>EPSG:5720</SRS>
+ <SRS>EPSG:5721</SRS>
+ <SRS>EPSG:5722</SRS>
+ <SRS>EPSG:5723</SRS>
+ <SRS>EPSG:5724</SRS>
+ <SRS>EPSG:5725</SRS>
+ <SRS>EPSG:5726</SRS>
+ <SRS>EPSG:5727</SRS>
+ <SRS>EPSG:5728</SRS>
+ <SRS>EPSG:5729</SRS>
+ <SRS>EPSG:5730</SRS>
+ <SRS>EPSG:5731</SRS>
+ <SRS>EPSG:5732</SRS>
+ <SRS>EPSG:5733</SRS>
+ <SRS>EPSG:5734</SRS>
+ <SRS>EPSG:5735</SRS>
+ <SRS>EPSG:5736</SRS>
+ <SRS>EPSG:5737</SRS>
+ <SRS>EPSG:5738</SRS>
+ <SRS>EPSG:5739</SRS>
+ <SRS>EPSG:5740</SRS>
+ <SRS>EPSG:5741</SRS>
+ <SRS>EPSG:5742</SRS>
+ <SRS>EPSG:5743</SRS>
+ <SRS>EPSG:5744</SRS>
+ <SRS>EPSG:5745</SRS>
+ <SRS>EPSG:5746</SRS>
+ <SRS>EPSG:5747</SRS>
+ <SRS>EPSG:5748</SRS>
+ <SRS>EPSG:5749</SRS>
+ <SRS>EPSG:5750</SRS>
+ <SRS>EPSG:5751</SRS>
+ <SRS>EPSG:5752</SRS>
+ <SRS>EPSG:5753</SRS>
+ <SRS>EPSG:5754</SRS>
+ <SRS>EPSG:5755</SRS>
+ <SRS>EPSG:5756</SRS>
+ <SRS>EPSG:5757</SRS>
+ <SRS>EPSG:5758</SRS>
+ <SRS>EPSG:5759</SRS>
+ <SRS>EPSG:5760</SRS>
+ <SRS>EPSG:5761</SRS>
+ <SRS>EPSG:5762</SRS>
+ <SRS>EPSG:5763</SRS>
+ <SRS>EPSG:5764</SRS>
+ <SRS>EPSG:5765</SRS>
+ <SRS>EPSG:5766</SRS>
+ <SRS>EPSG:5767</SRS>
+ <SRS>EPSG:5768</SRS>
+ <SRS>EPSG:5769</SRS>
+ <SRS>EPSG:5770</SRS>
+ <SRS>EPSG:5771</SRS>
+ <SRS>EPSG:5772</SRS>
+ <SRS>EPSG:5773</SRS>
+ <SRS>EPSG:5774</SRS>
+ <SRS>EPSG:5775</SRS>
+ <SRS>EPSG:5776</SRS>
+ <SRS>EPSG:5777</SRS>
+ <SRS>EPSG:5778</SRS>
+ <SRS>EPSG:5779</SRS>
+ <SRS>EPSG:5780</SRS>
+ <SRS>EPSG:5781</SRS>
+ <SRS>EPSG:5782</SRS>
+ <SRS>EPSG:5783</SRS>
+ <SRS>EPSG:5784</SRS>
+ <SRS>EPSG:5785</SRS>
+ <SRS>EPSG:5786</SRS>
+ <SRS>EPSG:5787</SRS>
+ <SRS>EPSG:5788</SRS>
+ <SRS>EPSG:5789</SRS>
+ <SRS>EPSG:5790</SRS>
+ <SRS>EPSG:5791</SRS>
+ <SRS>EPSG:5792</SRS>
+ <SRS>EPSG:5793</SRS>
+ <SRS>EPSG:5794</SRS>
+ <SRS>EPSG:5795</SRS>
+ <SRS>EPSG:5796</SRS>
+ <SRS>EPSG:5797</SRS>
+ <SRS>EPSG:5798</SRS>
+ <SRS>EPSG:5799</SRS>
+ <SRS>EPSG:5800</SRS>
+ <SRS>EPSG:5801</SRS>
+ <SRS>EPSG:5802</SRS>
+ <SRS>EPSG:5803</SRS>
+ <SRS>EPSG:5804</SRS>
+ <SRS>EPSG:5805</SRS>
+ <SRS>EPSG:5806</SRS>
+ <SRS>EPSG:5807</SRS>
+ <SRS>EPSG:5808</SRS>
+ <SRS>EPSG:5809</SRS>
+ <SRS>EPSG:5810</SRS>
+ <SRS>EPSG:5811</SRS>
+ <SRS>EPSG:5812</SRS>
+ <SRS>EPSG:5813</SRS>
+ <SRS>EPSG:5814</SRS>
+ <SRS>EPSG:5815</SRS>
+ <SRS>EPSG:5816</SRS>
+ <SRS>EPSG:5817</SRS>
+ <SRS>EPSG:5818</SRS>
+ <SRS>EPSG:7400</SRS>
+ <SRS>EPSG:7401</SRS>
+ <SRS>EPSG:7402</SRS>
+ <SRS>EPSG:7403</SRS>
+ <SRS>EPSG:7404</SRS>
+ <SRS>EPSG:7405</SRS>
+ <SRS>EPSG:7406</SRS>
+ <SRS>EPSG:7407</SRS>
+ <SRS>EPSG:7408</SRS>
+ <SRS>EPSG:7409</SRS>
+ <SRS>EPSG:7410</SRS>
+ <SRS>EPSG:7411</SRS>
+ <SRS>EPSG:7412</SRS>
+ <SRS>EPSG:7413</SRS>
+ <SRS>EPSG:7414</SRS>
+ <SRS>EPSG:7415</SRS>
+ <SRS>EPSG:7416</SRS>
+ <SRS>EPSG:7417</SRS>
+ <SRS>EPSG:7418</SRS>
+ <SRS>EPSG:7419</SRS>
+ <SRS>EPSG:7420</SRS>
+ <SRS>EPSG:20004</SRS>
+ <SRS>EPSG:20005</SRS>
+ <SRS>EPSG:20006</SRS>
+ <SRS>EPSG:20007</SRS>
+ <SRS>EPSG:20008</SRS>
+ <SRS>EPSG:20009</SRS>
+ <SRS>EPSG:20010</SRS>
+ <SRS>EPSG:20011</SRS>
+ <SRS>EPSG:20012</SRS>
+ <SRS>EPSG:20013</SRS>
+ <SRS>EPSG:20014</SRS>
+ <SRS>EPSG:20015</SRS>
+ <SRS>EPSG:20016</SRS>
+ <SRS>EPSG:20017</SRS>
+ <SRS>EPSG:20018</SRS>
+ <SRS>EPSG:20019</SRS>
+ <SRS>EPSG:20020</SRS>
+ <SRS>EPSG:20021</SRS>
+ <SRS>EPSG:20022</SRS>
+ <SRS>EPSG:20023</SRS>
+ <SRS>EPSG:20024</SRS>
+ <SRS>EPSG:20025</SRS>
+ <SRS>EPSG:20026</SRS>
+ <SRS>EPSG:20027</SRS>
+ <SRS>EPSG:20028</SRS>
+ <SRS>EPSG:20029</SRS>
+ <SRS>EPSG:20030</SRS>
+ <SRS>EPSG:20031</SRS>
+ <SRS>EPSG:20032</SRS>
+ <SRS>EPSG:20064</SRS>
+ <SRS>EPSG:20065</SRS>
+ <SRS>EPSG:20066</SRS>
+ <SRS>EPSG:20067</SRS>
+ <SRS>EPSG:20068</SRS>
+ <SRS>EPSG:20069</SRS>
+ <SRS>EPSG:20070</SRS>
+ <SRS>EPSG:20071</SRS>
+ <SRS>EPSG:20072</SRS>
+ <SRS>EPSG:20073</SRS>
+ <SRS>EPSG:20074</SRS>
+ <SRS>EPSG:20075</SRS>
+ <SRS>EPSG:20076</SRS>
+ <SRS>EPSG:20077</SRS>
+ <SRS>EPSG:20078</SRS>
+ <SRS>EPSG:20079</SRS>
+ <SRS>EPSG:20080</SRS>
+ <SRS>EPSG:20081</SRS>
+ <SRS>EPSG:20082</SRS>
+ <SRS>EPSG:20083</SRS>
+ <SRS>EPSG:20084</SRS>
+ <SRS>EPSG:20085</SRS>
+ <SRS>EPSG:20086</SRS>
+ <SRS>EPSG:20087</SRS>
+ <SRS>EPSG:20088</SRS>
+ <SRS>EPSG:20089</SRS>
+ <SRS>EPSG:20090</SRS>
+ <SRS>EPSG:20091</SRS>
+ <SRS>EPSG:20092</SRS>
+ <SRS>EPSG:20135</SRS>
+ <SRS>EPSG:20136</SRS>
+ <SRS>EPSG:20137</SRS>
+ <SRS>EPSG:20138</SRS>
+ <SRS>EPSG:20248</SRS>
+ <SRS>EPSG:20249</SRS>
+ <SRS>EPSG:20250</SRS>
+ <SRS>EPSG:20251</SRS>
+ <SRS>EPSG:20252</SRS>
+ <SRS>EPSG:20253</SRS>
+ <SRS>EPSG:20254</SRS>
+ <SRS>EPSG:20255</SRS>
+ <SRS>EPSG:20256</SRS>
+ <SRS>EPSG:20257</SRS>
+ <SRS>EPSG:20258</SRS>
+ <SRS>EPSG:20348</SRS>
+ <SRS>EPSG:20349</SRS>
+ <SRS>EPSG:20350</SRS>
+ <SRS>EPSG:20351</SRS>
+ <SRS>EPSG:20352</SRS>
+ <SRS>EPSG:20353</SRS>
+ <SRS>EPSG:20354</SRS>
+ <SRS>EPSG:20355</SRS>
+ <SRS>EPSG:20356</SRS>
+ <SRS>EPSG:20357</SRS>
+ <SRS>EPSG:20358</SRS>
+ <SRS>EPSG:20436</SRS>
+ <SRS>EPSG:20437</SRS>
+ <SRS>EPSG:20438</SRS>
+ <SRS>EPSG:20439</SRS>
+ <SRS>EPSG:20440</SRS>
+ <SRS>EPSG:20499</SRS>
+ <SRS>EPSG:20538</SRS>
+ <SRS>EPSG:20539</SRS>
+ <SRS>EPSG:20790</SRS>
+ <SRS>EPSG:20791</SRS>
+ <SRS>EPSG:20822</SRS>
+ <SRS>EPSG:20823</SRS>
+ <SRS>EPSG:20824</SRS>
+ <SRS>EPSG:20934</SRS>
+ <SRS>EPSG:20935</SRS>
+ <SRS>EPSG:20936</SRS>
+ <SRS>EPSG:21035</SRS>
+ <SRS>EPSG:21036</SRS>
+ <SRS>EPSG:21037</SRS>
+ <SRS>EPSG:21095</SRS>
+ <SRS>EPSG:21096</SRS>
+ <SRS>EPSG:21097</SRS>
+ <SRS>EPSG:21100</SRS>
+ <SRS>EPSG:21148</SRS>
+ <SRS>EPSG:21149</SRS>
+ <SRS>EPSG:21150</SRS>
+ <SRS>EPSG:21291</SRS>
+ <SRS>EPSG:21292</SRS>
+ <SRS>EPSG:21413</SRS>
+ <SRS>EPSG:21414</SRS>
+ <SRS>EPSG:21415</SRS>
+ <SRS>EPSG:21416</SRS>
+ <SRS>EPSG:21417</SRS>
+ <SRS>EPSG:21418</SRS>
+ <SRS>EPSG:21419</SRS>
+ <SRS>EPSG:21420</SRS>
+ <SRS>EPSG:21421</SRS>
+ <SRS>EPSG:21422</SRS>
+ <SRS>EPSG:21423</SRS>
+ <SRS>EPSG:21453</SRS>
+ <SRS>EPSG:21454</SRS>
+ <SRS>EPSG:21455</SRS>
+ <SRS>EPSG:21456</SRS>
+ <SRS>EPSG:21457</SRS>
+ <SRS>EPSG:21458</SRS>
+ <SRS>EPSG:21459</SRS>
+ <SRS>EPSG:21460</SRS>
+ <SRS>EPSG:21461</SRS>
+ <SRS>EPSG:21462</SRS>
+ <SRS>EPSG:21463</SRS>
+ <SRS>EPSG:21473</SRS>
+ <SRS>EPSG:21474</SRS>
+ <SRS>EPSG:21475</SRS>
+ <SRS>EPSG:21476</SRS>
+ <SRS>EPSG:21477</SRS>
+ <SRS>EPSG:21478</SRS>
+ <SRS>EPSG:21479</SRS>
+ <SRS>EPSG:21480</SRS>
+ <SRS>EPSG:21481</SRS>
+ <SRS>EPSG:21482</SRS>
+ <SRS>EPSG:21483</SRS>
+ <SRS>EPSG:21500</SRS>
+ <SRS>EPSG:21780</SRS>
+ <SRS>EPSG:21781</SRS>
+ <SRS>EPSG:21817</SRS>
+ <SRS>EPSG:21818</SRS>
+ <SRS>EPSG:21891</SRS>
+ <SRS>EPSG:21892</SRS>
+ <SRS>EPSG:21893</SRS>
+ <SRS>EPSG:21894</SRS>
+ <SRS>EPSG:21896</SRS>
+ <SRS>EPSG:21897</SRS>
+ <SRS>EPSG:21898</SRS>
+ <SRS>EPSG:21899</SRS>
+ <SRS>EPSG:22032</SRS>
+ <SRS>EPSG:22033</SRS>
+ <SRS>EPSG:22091</SRS>
+ <SRS>EPSG:22092</SRS>
+ <SRS>EPSG:22171</SRS>
+ <SRS>EPSG:22172</SRS>
+ <SRS>EPSG:22173</SRS>
+ <SRS>EPSG:22174</SRS>
+ <SRS>EPSG:22175</SRS>
+ <SRS>EPSG:22176</SRS>
+ <SRS>EPSG:22177</SRS>
+ <SRS>EPSG:22181</SRS>
+ <SRS>EPSG:22182</SRS>
+ <SRS>EPSG:22183</SRS>
+ <SRS>EPSG:22184</SRS>
+ <SRS>EPSG:22185</SRS>
+ <SRS>EPSG:22186</SRS>
+ <SRS>EPSG:22187</SRS>
+ <SRS>EPSG:22191</SRS>
+ <SRS>EPSG:22192</SRS>
+ <SRS>EPSG:22193</SRS>
+ <SRS>EPSG:22194</SRS>
+ <SRS>EPSG:22195</SRS>
+ <SRS>EPSG:22196</SRS>
+ <SRS>EPSG:22197</SRS>
+ <SRS>EPSG:22234</SRS>
+ <SRS>EPSG:22235</SRS>
+ <SRS>EPSG:22236</SRS>
+ <SRS>EPSG:22275</SRS>
+ <SRS>EPSG:22277</SRS>
+ <SRS>EPSG:22279</SRS>
+ <SRS>EPSG:22281</SRS>
+ <SRS>EPSG:22283</SRS>
+ <SRS>EPSG:22285</SRS>
+ <SRS>EPSG:22287</SRS>
+ <SRS>EPSG:22289</SRS>
+ <SRS>EPSG:22291</SRS>
+ <SRS>EPSG:22293</SRS>
+ <SRS>EPSG:22300</SRS>
+ <SRS>EPSG:22332</SRS>
+ <SRS>EPSG:22391</SRS>
+ <SRS>EPSG:22392</SRS>
+ <SRS>EPSG:22521</SRS>
+ <SRS>EPSG:22522</SRS>
+ <SRS>EPSG:22523</SRS>
+ <SRS>EPSG:22524</SRS>
+ <SRS>EPSG:22525</SRS>
+ <SRS>EPSG:22700</SRS>
+ <SRS>EPSG:22770</SRS>
+ <SRS>EPSG:22780</SRS>
+ <SRS>EPSG:22832</SRS>
+ <SRS>EPSG:22991</SRS>
+ <SRS>EPSG:22992</SRS>
+ <SRS>EPSG:22993</SRS>
+ <SRS>EPSG:22994</SRS>
+ <SRS>EPSG:23028</SRS>
+ <SRS>EPSG:23029</SRS>
+ <SRS>EPSG:23030</SRS>
+ <SRS>EPSG:23031</SRS>
+ <SRS>EPSG:23032</SRS>
+ <SRS>EPSG:23033</SRS>
+ <SRS>EPSG:23034</SRS>
+ <SRS>EPSG:23035</SRS>
+ <SRS>EPSG:23036</SRS>
+ <SRS>EPSG:23037</SRS>
+ <SRS>EPSG:23038</SRS>
+ <SRS>EPSG:23090</SRS>
+ <SRS>EPSG:23095</SRS>
+ <SRS>EPSG:23239</SRS>
+ <SRS>EPSG:23240</SRS>
+ <SRS>EPSG:23433</SRS>
+ <SRS>EPSG:23700</SRS>
+ <SRS>EPSG:23846</SRS>
+ <SRS>EPSG:23847</SRS>
+ <SRS>EPSG:23848</SRS>
+ <SRS>EPSG:23849</SRS>
+ <SRS>EPSG:23850</SRS>
+ <SRS>EPSG:23851</SRS>
+ <SRS>EPSG:23852</SRS>
+ <SRS>EPSG:23853</SRS>
+ <SRS>EPSG:23866</SRS>
+ <SRS>EPSG:23867</SRS>
+ <SRS>EPSG:23868</SRS>
+ <SRS>EPSG:23869</SRS>
+ <SRS>EPSG:23870</SRS>
+ <SRS>EPSG:23871</SRS>
+ <SRS>EPSG:23872</SRS>
+ <SRS>EPSG:23877</SRS>
+ <SRS>EPSG:23878</SRS>
+ <SRS>EPSG:23879</SRS>
+ <SRS>EPSG:23880</SRS>
+ <SRS>EPSG:23881</SRS>
+ <SRS>EPSG:23882</SRS>
+ <SRS>EPSG:23883</SRS>
+ <SRS>EPSG:23884</SRS>
+ <SRS>EPSG:23886</SRS>
+ <SRS>EPSG:23887</SRS>
+ <SRS>EPSG:23888</SRS>
+ <SRS>EPSG:23889</SRS>
+ <SRS>EPSG:23890</SRS>
+ <SRS>EPSG:23891</SRS>
+ <SRS>EPSG:23892</SRS>
+ <SRS>EPSG:23893</SRS>
+ <SRS>EPSG:23894</SRS>
+ <SRS>EPSG:23946</SRS>
+ <SRS>EPSG:23947</SRS>
+ <SRS>EPSG:23948</SRS>
+ <SRS>EPSG:24047</SRS>
+ <SRS>EPSG:24048</SRS>
+ <SRS>EPSG:24100</SRS>
+ <SRS>EPSG:24200</SRS>
+ <SRS>EPSG:24305</SRS>
+ <SRS>EPSG:24306</SRS>
+ <SRS>EPSG:24311</SRS>
+ <SRS>EPSG:24312</SRS>
+ <SRS>EPSG:24313</SRS>
+ <SRS>EPSG:24342</SRS>
+ <SRS>EPSG:24343</SRS>
+ <SRS>EPSG:24344</SRS>
+ <SRS>EPSG:24345</SRS>
+ <SRS>EPSG:24346</SRS>
+ <SRS>EPSG:24347</SRS>
+ <SRS>EPSG:24370</SRS>
+ <SRS>EPSG:24371</SRS>
+ <SRS>EPSG:24372</SRS>
+ <SRS>EPSG:24373</SRS>
+ <SRS>EPSG:24374</SRS>
+ <SRS>EPSG:24375</SRS>
+ <SRS>EPSG:24376</SRS>
+ <SRS>EPSG:24377</SRS>
+ <SRS>EPSG:24378</SRS>
+ <SRS>EPSG:24379</SRS>
+ <SRS>EPSG:24380</SRS>
+ <SRS>EPSG:24381</SRS>
+ <SRS>EPSG:24382</SRS>
+ <SRS>EPSG:24383</SRS>
+ <SRS>EPSG:24500</SRS>
+ <SRS>EPSG:24547</SRS>
+ <SRS>EPSG:24548</SRS>
+ <SRS>EPSG:24571</SRS>
+ <SRS>EPSG:24600</SRS>
+ <SRS>EPSG:24718</SRS>
+ <SRS>EPSG:24719</SRS>
+ <SRS>EPSG:24720</SRS>
+ <SRS>EPSG:24817</SRS>
+ <SRS>EPSG:24818</SRS>
+ <SRS>EPSG:24819</SRS>
+ <SRS>EPSG:24820</SRS>
+ <SRS>EPSG:24821</SRS>
+ <SRS>EPSG:24877</SRS>
+ <SRS>EPSG:24878</SRS>
+ <SRS>EPSG:24879</SRS>
+ <SRS>EPSG:24880</SRS>
+ <SRS>EPSG:24881</SRS>
+ <SRS>EPSG:24882</SRS>
+ <SRS>EPSG:24891</SRS>
+ <SRS>EPSG:24892</SRS>
+ <SRS>EPSG:24893</SRS>
+ <SRS>EPSG:25000</SRS>
+ <SRS>EPSG:25231</SRS>
+ <SRS>EPSG:25391</SRS>
+ <SRS>EPSG:25392</SRS>
+ <SRS>EPSG:25393</SRS>
+ <SRS>EPSG:25394</SRS>
+ <SRS>EPSG:25395</SRS>
+ <SRS>EPSG:25700</SRS>
+ <SRS>EPSG:25828</SRS>
+ <SRS>EPSG:25829</SRS>
+ <SRS>EPSG:25830</SRS>
+ <SRS>EPSG:25831</SRS>
+ <SRS>EPSG:25832</SRS>
+ <SRS>EPSG:25833</SRS>
+ <SRS>EPSG:25834</SRS>
+ <SRS>EPSG:25835</SRS>
+ <SRS>EPSG:25836</SRS>
+ <SRS>EPSG:25837</SRS>
+ <SRS>EPSG:25838</SRS>
+ <SRS>EPSG:25884</SRS>
+ <SRS>EPSG:25932</SRS>
+ <SRS>EPSG:26191</SRS>
+ <SRS>EPSG:26192</SRS>
+ <SRS>EPSG:26193</SRS>
+ <SRS>EPSG:26194</SRS>
+ <SRS>EPSG:26195</SRS>
+ <SRS>EPSG:26237</SRS>
+ <SRS>EPSG:26331</SRS>
+ <SRS>EPSG:26332</SRS>
+ <SRS>EPSG:26391</SRS>
+ <SRS>EPSG:26392</SRS>
+ <SRS>EPSG:26393</SRS>
+ <SRS>EPSG:26432</SRS>
+ <SRS>EPSG:26591</SRS>
+ <SRS>EPSG:26592</SRS>
+ <SRS>EPSG:26632</SRS>
+ <SRS>EPSG:26692</SRS>
+ <SRS>EPSG:26701</SRS>
+ <SRS>EPSG:26702</SRS>
+ <SRS>EPSG:26703</SRS>
+ <SRS>EPSG:26704</SRS>
+ <SRS>EPSG:26705</SRS>
+ <SRS>EPSG:26706</SRS>
+ <SRS>EPSG:26707</SRS>
+ <SRS>EPSG:26708</SRS>
+ <SRS>EPSG:26709</SRS>
+ <SRS>EPSG:26710</SRS>
+ <SRS>EPSG:26711</SRS>
+ <SRS>EPSG:26712</SRS>
+ <SRS>EPSG:26713</SRS>
+ <SRS>EPSG:26714</SRS>
+ <SRS>EPSG:26715</SRS>
+ <SRS>EPSG:26716</SRS>
+ <SRS>EPSG:26717</SRS>
+ <SRS>EPSG:26718</SRS>
+ <SRS>EPSG:26719</SRS>
+ <SRS>EPSG:26720</SRS>
+ <SRS>EPSG:26721</SRS>
+ <SRS>EPSG:26722</SRS>
+ <SRS>EPSG:26729</SRS>
+ <SRS>EPSG:26730</SRS>
+ <SRS>EPSG:26731</SRS>
+ <SRS>EPSG:26732</SRS>
+ <SRS>EPSG:26733</SRS>
+ <SRS>EPSG:26734</SRS>
+ <SRS>EPSG:26735</SRS>
+ <SRS>EPSG:26736</SRS>
+ <SRS>EPSG:26737</SRS>
+ <SRS>EPSG:26738</SRS>
+ <SRS>EPSG:26739</SRS>
+ <SRS>EPSG:26740</SRS>
+ <SRS>EPSG:26741</SRS>
+ <SRS>EPSG:26742</SRS>
+ <SRS>EPSG:26743</SRS>
+ <SRS>EPSG:26744</SRS>
+ <SRS>EPSG:26745</SRS>
+ <SRS>EPSG:26746</SRS>
+ <SRS>EPSG:26747</SRS>
+ <SRS>EPSG:26748</SRS>
+ <SRS>EPSG:26749</SRS>
+ <SRS>EPSG:26750</SRS>
+ <SRS>EPSG:26751</SRS>
+ <SRS>EPSG:26752</SRS>
+ <SRS>EPSG:26753</SRS>
+ <SRS>EPSG:26754</SRS>
+ <SRS>EPSG:26755</SRS>
+ <SRS>EPSG:26756</SRS>
+ <SRS>EPSG:26757</SRS>
+ <SRS>EPSG:26758</SRS>
+ <SRS>EPSG:26759</SRS>
+ <SRS>EPSG:26760</SRS>
+ <SRS>EPSG:26766</SRS>
+ <SRS>EPSG:26767</SRS>
+ <SRS>EPSG:26768</SRS>
+ <SRS>EPSG:26769</SRS>
+ <SRS>EPSG:26770</SRS>
+ <SRS>EPSG:26771</SRS>
+ <SRS>EPSG:26772</SRS>
+ <SRS>EPSG:26773</SRS>
+ <SRS>EPSG:26774</SRS>
+ <SRS>EPSG:26775</SRS>
+ <SRS>EPSG:26776</SRS>
+ <SRS>EPSG:26777</SRS>
+ <SRS>EPSG:26778</SRS>
+ <SRS>EPSG:26779</SRS>
+ <SRS>EPSG:26780</SRS>
+ <SRS>EPSG:26781</SRS>
+ <SRS>EPSG:26782</SRS>
+ <SRS>EPSG:26783</SRS>
+ <SRS>EPSG:26784</SRS>
+ <SRS>EPSG:26785</SRS>
+ <SRS>EPSG:26786</SRS>
+ <SRS>EPSG:26787</SRS>
+ <SRS>EPSG:26791</SRS>
+ <SRS>EPSG:26792</SRS>
+ <SRS>EPSG:26793</SRS>
+ <SRS>EPSG:26794</SRS>
+ <SRS>EPSG:26795</SRS>
+ <SRS>EPSG:26796</SRS>
+ <SRS>EPSG:26797</SRS>
+ <SRS>EPSG:26798</SRS>
+ <SRS>EPSG:26799</SRS>
+ <SRS>EPSG:26801</SRS>
+ <SRS>EPSG:26802</SRS>
+ <SRS>EPSG:26803</SRS>
+ <SRS>EPSG:26811</SRS>
+ <SRS>EPSG:26812</SRS>
+ <SRS>EPSG:26813</SRS>
+ <SRS>EPSG:26901</SRS>
+ <SRS>EPSG:26902</SRS>
+ <SRS>EPSG:26903</SRS>
+ <SRS>EPSG:26904</SRS>
+ <SRS>EPSG:26905</SRS>
+ <SRS>EPSG:26906</SRS>
+ <SRS>EPSG:26907</SRS>
+ <SRS>EPSG:26908</SRS>
+ <SRS>EPSG:26909</SRS>
+ <SRS>EPSG:26910</SRS>
+ <SRS>EPSG:26911</SRS>
+ <SRS>EPSG:26912</SRS>
+ <SRS>EPSG:26913</SRS>
+ <SRS>EPSG:26914</SRS>
+ <SRS>EPSG:26915</SRS>
+ <SRS>EPSG:26916</SRS>
+ <SRS>EPSG:26917</SRS>
+ <SRS>EPSG:26918</SRS>
+ <SRS>EPSG:26919</SRS>
+ <SRS>EPSG:26920</SRS>
+ <SRS>EPSG:26921</SRS>
+ <SRS>EPSG:26922</SRS>
+ <SRS>EPSG:26923</SRS>
+ <SRS>EPSG:26929</SRS>
+ <SRS>EPSG:26930</SRS>
+ <SRS>EPSG:26931</SRS>
+ <SRS>EPSG:26932</SRS>
+ <SRS>EPSG:26933</SRS>
+ <SRS>EPSG:26934</SRS>
+ <SRS>EPSG:26935</SRS>
+ <SRS>EPSG:26936</SRS>
+ <SRS>EPSG:26937</SRS>
+ <SRS>EPSG:26938</SRS>
+ <SRS>EPSG:26939</SRS>
+ <SRS>EPSG:26940</SRS>
+ <SRS>EPSG:26941</SRS>
+ <SRS>EPSG:26942</SRS>
+ <SRS>EPSG:26943</SRS>
+ <SRS>EPSG:26944</SRS>
+ <SRS>EPSG:26945</SRS>
+ <SRS>EPSG:26946</SRS>
+ <SRS>EPSG:26948</SRS>
+ <SRS>EPSG:26949</SRS>
+ <SRS>EPSG:26950</SRS>
+ <SRS>EPSG:26951</SRS>
+ <SRS>EPSG:26952</SRS>
+ <SRS>EPSG:26953</SRS>
+ <SRS>EPSG:26954</SRS>
+ <SRS>EPSG:26955</SRS>
+ <SRS>EPSG:26956</SRS>
+ <SRS>EPSG:26957</SRS>
+ <SRS>EPSG:26958</SRS>
+ <SRS>EPSG:26959</SRS>
+ <SRS>EPSG:26960</SRS>
+ <SRS>EPSG:26961</SRS>
+ <SRS>EPSG:26962</SRS>
+ <SRS>EPSG:26963</SRS>
+ <SRS>EPSG:26964</SRS>
+ <SRS>EPSG:26965</SRS>
+ <SRS>EPSG:26966</SRS>
+ <SRS>EPSG:26967</SRS>
+ <SRS>EPSG:26968</SRS>
+ <SRS>EPSG:26969</SRS>
+ <SRS>EPSG:26970</SRS>
+ <SRS>EPSG:26971</SRS>
+ <SRS>EPSG:26972</SRS>
+ <SRS>EPSG:26973</SRS>
+ <SRS>EPSG:26974</SRS>
+ <SRS>EPSG:26975</SRS>
+ <SRS>EPSG:26976</SRS>
+ <SRS>EPSG:26977</SRS>
+ <SRS>EPSG:26978</SRS>
+ <SRS>EPSG:26979</SRS>
+ <SRS>EPSG:26980</SRS>
+ <SRS>EPSG:26981</SRS>
+ <SRS>EPSG:26982</SRS>
+ <SRS>EPSG:26983</SRS>
+ <SRS>EPSG:26984</SRS>
+ <SRS>EPSG:26985</SRS>
+ <SRS>EPSG:26986</SRS>
+ <SRS>EPSG:26987</SRS>
+ <SRS>EPSG:26988</SRS>
+ <SRS>EPSG:26989</SRS>
+ <SRS>EPSG:26990</SRS>
+ <SRS>EPSG:26991</SRS>
+ <SRS>EPSG:26992</SRS>
+ <SRS>EPSG:26993</SRS>
+ <SRS>EPSG:26994</SRS>
+ <SRS>EPSG:26995</SRS>
+ <SRS>EPSG:26996</SRS>
+ <SRS>EPSG:26997</SRS>
+ <SRS>EPSG:26998</SRS>
+ <SRS>EPSG:27037</SRS>
+ <SRS>EPSG:27038</SRS>
+ <SRS>EPSG:27039</SRS>
+ <SRS>EPSG:27040</SRS>
+ <SRS>EPSG:27120</SRS>
+ <SRS>EPSG:27200</SRS>
+ <SRS>EPSG:27205</SRS>
+ <SRS>EPSG:27206</SRS>
+ <SRS>EPSG:27207</SRS>
+ <SRS>EPSG:27208</SRS>
+ <SRS>EPSG:27209</SRS>
+ <SRS>EPSG:27210</SRS>
+ <SRS>EPSG:27211</SRS>
+ <SRS>EPSG:27212</SRS>
+ <SRS>EPSG:27213</SRS>
+ <SRS>EPSG:27214</SRS>
+ <SRS>EPSG:27215</SRS>
+ <SRS>EPSG:27216</SRS>
+ <SRS>EPSG:27217</SRS>
+ <SRS>EPSG:27218</SRS>
+ <SRS>EPSG:27219</SRS>
+ <SRS>EPSG:27220</SRS>
+ <SRS>EPSG:27221</SRS>
+ <SRS>EPSG:27222</SRS>
+ <SRS>EPSG:27223</SRS>
+ <SRS>EPSG:27224</SRS>
+ <SRS>EPSG:27225</SRS>
+ <SRS>EPSG:27226</SRS>
+ <SRS>EPSG:27227</SRS>
+ <SRS>EPSG:27228</SRS>
+ <SRS>EPSG:27229</SRS>
+ <SRS>EPSG:27230</SRS>
+ <SRS>EPSG:27231</SRS>
+ <SRS>EPSG:27232</SRS>
+ <SRS>EPSG:27258</SRS>
+ <SRS>EPSG:27259</SRS>
+ <SRS>EPSG:27260</SRS>
+ <SRS>EPSG:27291</SRS>
+ <SRS>EPSG:27292</SRS>
+ <SRS>EPSG:27391</SRS>
+ <SRS>EPSG:27392</SRS>
+ <SRS>EPSG:27393</SRS>
+ <SRS>EPSG:27394</SRS>
+ <SRS>EPSG:27395</SRS>
+ <SRS>EPSG:27396</SRS>
+ <SRS>EPSG:27397</SRS>
+ <SRS>EPSG:27398</SRS>
+ <SRS>EPSG:27429</SRS>
+ <SRS>EPSG:27492</SRS>
+ <SRS>EPSG:27500</SRS>
+ <SRS>EPSG:27561</SRS>
+ <SRS>EPSG:27562</SRS>
+ <SRS>EPSG:27563</SRS>
+ <SRS>EPSG:27564</SRS>
+ <SRS>EPSG:27571</SRS>
+ <SRS>EPSG:27572</SRS>
+ <SRS>EPSG:27573</SRS>
+ <SRS>EPSG:27574</SRS>
+ <SRS>EPSG:27581</SRS>
+ <SRS>EPSG:27582</SRS>
+ <SRS>EPSG:27583</SRS>
+ <SRS>EPSG:27584</SRS>
+ <SRS>EPSG:27591</SRS>
+ <SRS>EPSG:27592</SRS>
+ <SRS>EPSG:27593</SRS>
+ <SRS>EPSG:27594</SRS>
+ <SRS>EPSG:27700</SRS>
+ <SRS>EPSG:28191</SRS>
+ <SRS>EPSG:28192</SRS>
+ <SRS>EPSG:28193</SRS>
+ <SRS>EPSG:28232</SRS>
+ <SRS>EPSG:28348</SRS>
+ <SRS>EPSG:28349</SRS>
+ <SRS>EPSG:28350</SRS>
+ <SRS>EPSG:28351</SRS>
+ <SRS>EPSG:28352</SRS>
+ <SRS>EPSG:28353</SRS>
+ <SRS>EPSG:28354</SRS>
+ <SRS>EPSG:28355</SRS>
+ <SRS>EPSG:28356</SRS>
+ <SRS>EPSG:28357</SRS>
+ <SRS>EPSG:28358</SRS>
+ <SRS>EPSG:28402</SRS>
+ <SRS>EPSG:28403</SRS>
+ <SRS>EPSG:28404</SRS>
+ <SRS>EPSG:28405</SRS>
+ <SRS>EPSG:28406</SRS>
+ <SRS>EPSG:28407</SRS>
+ <SRS>EPSG:28408</SRS>
+ <SRS>EPSG:28409</SRS>
+ <SRS>EPSG:28410</SRS>
+ <SRS>EPSG:28411</SRS>
+ <SRS>EPSG:28412</SRS>
+ <SRS>EPSG:28413</SRS>
+ <SRS>EPSG:28414</SRS>
+ <SRS>EPSG:28415</SRS>
+ <SRS>EPSG:28416</SRS>
+ <SRS>EPSG:28417</SRS>
+ <SRS>EPSG:28418</SRS>
+ <SRS>EPSG:28419</SRS>
+ <SRS>EPSG:28420</SRS>
+ <SRS>EPSG:28421</SRS>
+ <SRS>EPSG:28422</SRS>
+ <SRS>EPSG:28423</SRS>
+ <SRS>EPSG:28424</SRS>
+ <SRS>EPSG:28425</SRS>
+ <SRS>EPSG:28426</SRS>
+ <SRS>EPSG:28427</SRS>
+ <SRS>EPSG:28428</SRS>
+ <SRS>EPSG:28429</SRS>
+ <SRS>EPSG:28430</SRS>
+ <SRS>EPSG:28431</SRS>
+ <SRS>EPSG:28432</SRS>
+ <SRS>EPSG:28462</SRS>
+ <SRS>EPSG:28463</SRS>
+ <SRS>EPSG:28464</SRS>
+ <SRS>EPSG:28465</SRS>
+ <SRS>EPSG:28466</SRS>
+ <SRS>EPSG:28467</SRS>
+ <SRS>EPSG:28468</SRS>
+ <SRS>EPSG:28469</SRS>
+ <SRS>EPSG:28470</SRS>
+ <SRS>EPSG:28471</SRS>
+ <SRS>EPSG:28472</SRS>
+ <SRS>EPSG:28473</SRS>
+ <SRS>EPSG:28474</SRS>
+ <SRS>EPSG:28475</SRS>
+ <SRS>EPSG:28476</SRS>
+ <SRS>EPSG:28477</SRS>
+ <SRS>EPSG:28478</SRS>
+ <SRS>EPSG:28479</SRS>
+ <SRS>EPSG:28480</SRS>
+ <SRS>EPSG:28481</SRS>
+ <SRS>EPSG:28482</SRS>
+ <SRS>EPSG:28483</SRS>
+ <SRS>EPSG:28484</SRS>
+ <SRS>EPSG:28485</SRS>
+ <SRS>EPSG:28486</SRS>
+ <SRS>EPSG:28487</SRS>
+ <SRS>EPSG:28488</SRS>
+ <SRS>EPSG:28489</SRS>
+ <SRS>EPSG:28490</SRS>
+ <SRS>EPSG:28491</SRS>
+ <SRS>EPSG:28492</SRS>
+ <SRS>EPSG:28600</SRS>
+ <SRS>EPSG:28991</SRS>
+ <SRS>EPSG:28992</SRS>
+ <SRS>EPSG:29100</SRS>
+ <SRS>EPSG:29101</SRS>
+ <SRS>EPSG:29118</SRS>
+ <SRS>EPSG:29119</SRS>
+ <SRS>EPSG:29120</SRS>
+ <SRS>EPSG:29121</SRS>
+ <SRS>EPSG:29122</SRS>
+ <SRS>EPSG:29168</SRS>
+ <SRS>EPSG:29169</SRS>
+ <SRS>EPSG:29170</SRS>
+ <SRS>EPSG:29171</SRS>
+ <SRS>EPSG:29172</SRS>
+ <SRS>EPSG:29177</SRS>
+ <SRS>EPSG:29178</SRS>
+ <SRS>EPSG:29179</SRS>
+ <SRS>EPSG:29180</SRS>
+ <SRS>EPSG:29181</SRS>
+ <SRS>EPSG:29182</SRS>
+ <SRS>EPSG:29183</SRS>
+ <SRS>EPSG:29184</SRS>
+ <SRS>EPSG:29185</SRS>
+ <SRS>EPSG:29187</SRS>
+ <SRS>EPSG:29188</SRS>
+ <SRS>EPSG:29189</SRS>
+ <SRS>EPSG:29190</SRS>
+ <SRS>EPSG:29191</SRS>
+ <SRS>EPSG:29192</SRS>
+ <SRS>EPSG:29193</SRS>
+ <SRS>EPSG:29194</SRS>
+ <SRS>EPSG:29195</SRS>
+ <SRS>EPSG:29220</SRS>
+ <SRS>EPSG:29221</SRS>
+ <SRS>EPSG:29333</SRS>
+ <SRS>EPSG:29371</SRS>
+ <SRS>EPSG:29373</SRS>
+ <SRS>EPSG:29375</SRS>
+ <SRS>EPSG:29377</SRS>
+ <SRS>EPSG:29379</SRS>
+ <SRS>EPSG:29381</SRS>
+ <SRS>EPSG:29383</SRS>
+ <SRS>EPSG:29385</SRS>
+ <SRS>EPSG:29635</SRS>
+ <SRS>EPSG:29636</SRS>
+ <SRS>EPSG:29700</SRS>
+ <SRS>EPSG:29701</SRS>
+ <SRS>EPSG:29702</SRS>
+ <SRS>EPSG:29738</SRS>
+ <SRS>EPSG:29739</SRS>
+ <SRS>EPSG:29849</SRS>
+ <SRS>EPSG:29850</SRS>
+ <SRS>EPSG:29871</SRS>
+ <SRS>EPSG:29872</SRS>
+ <SRS>EPSG:29873</SRS>
+ <SRS>EPSG:29900</SRS>
+ <SRS>EPSG:29901</SRS>
+ <SRS>EPSG:29902</SRS>
+ <SRS>EPSG:29903</SRS>
+ <SRS>EPSG:30161</SRS>
+ <SRS>EPSG:30162</SRS>
+ <SRS>EPSG:30163</SRS>
+ <SRS>EPSG:30164</SRS>
+ <SRS>EPSG:30165</SRS>
+ <SRS>EPSG:30166</SRS>
+ <SRS>EPSG:30167</SRS>
+ <SRS>EPSG:30168</SRS>
+ <SRS>EPSG:30169</SRS>
+ <SRS>EPSG:30170</SRS>
+ <SRS>EPSG:30171</SRS>
+ <SRS>EPSG:30172</SRS>
+ <SRS>EPSG:30173</SRS>
+ <SRS>EPSG:30174</SRS>
+ <SRS>EPSG:30175</SRS>
+ <SRS>EPSG:30176</SRS>
+ <SRS>EPSG:30177</SRS>
+ <SRS>EPSG:30178</SRS>
+ <SRS>EPSG:30179</SRS>
+ <SRS>EPSG:30200</SRS>
+ <SRS>EPSG:30339</SRS>
+ <SRS>EPSG:30340</SRS>
+ <SRS>EPSG:30491</SRS>
+ <SRS>EPSG:30492</SRS>
+ <SRS>EPSG:30493</SRS>
+ <SRS>EPSG:30494</SRS>
+ <SRS>EPSG:30729</SRS>
+ <SRS>EPSG:30730</SRS>
+ <SRS>EPSG:30731</SRS>
+ <SRS>EPSG:30732</SRS>
+ <SRS>EPSG:30791</SRS>
+ <SRS>EPSG:30792</SRS>
+ <SRS>EPSG:30800</SRS>
+ <SRS>EPSG:31028</SRS>
+ <SRS>EPSG:31121</SRS>
+ <SRS>EPSG:31154</SRS>
+ <SRS>EPSG:31170</SRS>
+ <SRS>EPSG:31171</SRS>
+ <SRS>EPSG:31251</SRS>
+ <SRS>EPSG:31252</SRS>
+ <SRS>EPSG:31253</SRS>
+ <SRS>EPSG:31254</SRS>
+ <SRS>EPSG:31255</SRS>
+ <SRS>EPSG:31256</SRS>
+ <SRS>EPSG:31257</SRS>
+ <SRS>EPSG:31258</SRS>
+ <SRS>EPSG:31259</SRS>
+ <SRS>EPSG:31265</SRS>
+ <SRS>EPSG:31266</SRS>
+ <SRS>EPSG:31267</SRS>
+ <SRS>EPSG:31268</SRS>
+ <SRS>EPSG:31275</SRS>
+ <SRS>EPSG:31276</SRS>
+ <SRS>EPSG:31277</SRS>
+ <SRS>EPSG:31278</SRS>
+ <SRS>EPSG:31279</SRS>
+ <SRS>EPSG:31281</SRS>
+ <SRS>EPSG:31282</SRS>
+ <SRS>EPSG:31283</SRS>
+ <SRS>EPSG:31284</SRS>
+ <SRS>EPSG:31285</SRS>
+ <SRS>EPSG:31286</SRS>
+ <SRS>EPSG:31287</SRS>
+ <SRS>EPSG:31288</SRS>
+ <SRS>EPSG:31289</SRS>
+ <SRS>EPSG:31290</SRS>
+ <SRS>EPSG:31291</SRS>
+ <SRS>EPSG:31292</SRS>
+ <SRS>EPSG:31293</SRS>
+ <SRS>EPSG:31294</SRS>
+ <SRS>EPSG:31295</SRS>
+ <SRS>EPSG:31296</SRS>
+ <SRS>EPSG:31297</SRS>
+ <SRS>EPSG:31300</SRS>
+ <SRS>EPSG:31370</SRS>
+ <SRS>EPSG:31461</SRS>
+ <SRS>EPSG:31462</SRS>
+ <SRS>EPSG:31463</SRS>
+ <SRS>EPSG:31464</SRS>
+ <SRS>EPSG:31465</SRS>
+ <SRS>EPSG:31466</SRS>
+ <SRS>EPSG:31467</SRS>
+ <SRS>EPSG:31468</SRS>
+ <SRS>EPSG:31469</SRS>
+ <SRS>EPSG:31528</SRS>
+ <SRS>EPSG:31529</SRS>
+ <SRS>EPSG:31600</SRS>
+ <SRS>EPSG:31700</SRS>
+ <SRS>EPSG:31838</SRS>
+ <SRS>EPSG:31839</SRS>
+ <SRS>EPSG:31900</SRS>
+ <SRS>EPSG:31901</SRS>
+ <SRS>EPSG:31965</SRS>
+ <SRS>EPSG:31966</SRS>
+ <SRS>EPSG:31967</SRS>
+ <SRS>EPSG:31968</SRS>
+ <SRS>EPSG:31969</SRS>
+ <SRS>EPSG:31970</SRS>
+ <SRS>EPSG:31971</SRS>
+ <SRS>EPSG:31972</SRS>
+ <SRS>EPSG:31973</SRS>
+ <SRS>EPSG:31974</SRS>
+ <SRS>EPSG:31975</SRS>
+ <SRS>EPSG:31976</SRS>
+ <SRS>EPSG:31977</SRS>
+ <SRS>EPSG:31978</SRS>
+ <SRS>EPSG:31979</SRS>
+ <SRS>EPSG:31980</SRS>
+ <SRS>EPSG:31981</SRS>
+ <SRS>EPSG:31982</SRS>
+ <SRS>EPSG:31983</SRS>
+ <SRS>EPSG:31984</SRS>
+ <SRS>EPSG:31985</SRS>
+ <SRS>EPSG:31986</SRS>
+ <SRS>EPSG:31987</SRS>
+ <SRS>EPSG:31988</SRS>
+ <SRS>EPSG:31989</SRS>
+ <SRS>EPSG:31990</SRS>
+ <SRS>EPSG:31991</SRS>
+ <SRS>EPSG:31992</SRS>
+ <SRS>EPSG:31993</SRS>
+ <SRS>EPSG:31994</SRS>
+ <SRS>EPSG:31995</SRS>
+ <SRS>EPSG:31996</SRS>
+ <SRS>EPSG:31997</SRS>
+ <SRS>EPSG:31998</SRS>
+ <SRS>EPSG:31999</SRS>
+ <SRS>EPSG:32000</SRS>
+ <SRS>EPSG:32001</SRS>
+ <SRS>EPSG:32002</SRS>
+ <SRS>EPSG:32003</SRS>
+ <SRS>EPSG:32005</SRS>
+ <SRS>EPSG:32006</SRS>
+ <SRS>EPSG:32007</SRS>
+ <SRS>EPSG:32008</SRS>
+ <SRS>EPSG:32009</SRS>
+ <SRS>EPSG:32010</SRS>
+ <SRS>EPSG:32011</SRS>
+ <SRS>EPSG:32012</SRS>
+ <SRS>EPSG:32013</SRS>
+ <SRS>EPSG:32014</SRS>
+ <SRS>EPSG:32015</SRS>
+ <SRS>EPSG:32016</SRS>
+ <SRS>EPSG:32017</SRS>
+ <SRS>EPSG:32018</SRS>
+ <SRS>EPSG:32019</SRS>
+ <SRS>EPSG:32020</SRS>
+ <SRS>EPSG:32021</SRS>
+ <SRS>EPSG:32022</SRS>
+ <SRS>EPSG:32023</SRS>
+ <SRS>EPSG:32024</SRS>
+ <SRS>EPSG:32025</SRS>
+ <SRS>EPSG:32026</SRS>
+ <SRS>EPSG:32027</SRS>
+ <SRS>EPSG:32028</SRS>
+ <SRS>EPSG:32029</SRS>
+ <SRS>EPSG:32030</SRS>
+ <SRS>EPSG:32031</SRS>
+ <SRS>EPSG:32033</SRS>
+ <SRS>EPSG:32034</SRS>
+ <SRS>EPSG:32035</SRS>
+ <SRS>EPSG:32036</SRS>
+ <SRS>EPSG:32037</SRS>
+ <SRS>EPSG:32038</SRS>
+ <SRS>EPSG:32039</SRS>
+ <SRS>EPSG:32040</SRS>
+ <SRS>EPSG:32041</SRS>
+ <SRS>EPSG:32042</SRS>
+ <SRS>EPSG:32043</SRS>
+ <SRS>EPSG:32044</SRS>
+ <SRS>EPSG:32045</SRS>
+ <SRS>EPSG:32046</SRS>
+ <SRS>EPSG:32047</SRS>
+ <SRS>EPSG:32048</SRS>
+ <SRS>EPSG:32049</SRS>
+ <SRS>EPSG:32050</SRS>
+ <SRS>EPSG:32051</SRS>
+ <SRS>EPSG:32052</SRS>
+ <SRS>EPSG:32053</SRS>
+ <SRS>EPSG:32054</SRS>
+ <SRS>EPSG:32055</SRS>
+ <SRS>EPSG:32056</SRS>
+ <SRS>EPSG:32057</SRS>
+ <SRS>EPSG:32058</SRS>
+ <SRS>EPSG:32061</SRS>
+ <SRS>EPSG:32062</SRS>
+ <SRS>EPSG:32064</SRS>
+ <SRS>EPSG:32065</SRS>
+ <SRS>EPSG:32066</SRS>
+ <SRS>EPSG:32067</SRS>
+ <SRS>EPSG:32074</SRS>
+ <SRS>EPSG:32075</SRS>
+ <SRS>EPSG:32076</SRS>
+ <SRS>EPSG:32077</SRS>
+ <SRS>EPSG:32081</SRS>
+ <SRS>EPSG:32082</SRS>
+ <SRS>EPSG:32083</SRS>
+ <SRS>EPSG:32084</SRS>
+ <SRS>EPSG:32085</SRS>
+ <SRS>EPSG:32086</SRS>
+ <SRS>EPSG:32098</SRS>
+ <SRS>EPSG:32099</SRS>
+ <SRS>EPSG:32100</SRS>
+ <SRS>EPSG:32104</SRS>
+ <SRS>EPSG:32107</SRS>
+ <SRS>EPSG:32108</SRS>
+ <SRS>EPSG:32109</SRS>
+ <SRS>EPSG:32110</SRS>
+ <SRS>EPSG:32111</SRS>
+ <SRS>EPSG:32112</SRS>
+ <SRS>EPSG:32113</SRS>
+ <SRS>EPSG:32114</SRS>
+ <SRS>EPSG:32115</SRS>
+ <SRS>EPSG:32116</SRS>
+ <SRS>EPSG:32117</SRS>
+ <SRS>EPSG:32118</SRS>
+ <SRS>EPSG:32119</SRS>
+ <SRS>EPSG:32120</SRS>
+ <SRS>EPSG:32121</SRS>
+ <SRS>EPSG:32122</SRS>
+ <SRS>EPSG:32123</SRS>
+ <SRS>EPSG:32124</SRS>
+ <SRS>EPSG:32125</SRS>
+ <SRS>EPSG:32126</SRS>
+ <SRS>EPSG:32127</SRS>
+ <SRS>EPSG:32128</SRS>
+ <SRS>EPSG:32129</SRS>
+ <SRS>EPSG:32130</SRS>
+ <SRS>EPSG:32133</SRS>
+ <SRS>EPSG:32134</SRS>
+ <SRS>EPSG:32135</SRS>
+ <SRS>EPSG:32136</SRS>
+ <SRS>EPSG:32137</SRS>
+ <SRS>EPSG:32138</SRS>
+ <SRS>EPSG:32139</SRS>
+ <SRS>EPSG:32140</SRS>
+ <SRS>EPSG:32141</SRS>
+ <SRS>EPSG:32142</SRS>
+ <SRS>EPSG:32143</SRS>
+ <SRS>EPSG:32144</SRS>
+ <SRS>EPSG:32145</SRS>
+ <SRS>EPSG:32146</SRS>
+ <SRS>EPSG:32147</SRS>
+ <SRS>EPSG:32148</SRS>
+ <SRS>EPSG:32149</SRS>
+ <SRS>EPSG:32150</SRS>
+ <SRS>EPSG:32151</SRS>
+ <SRS>EPSG:32152</SRS>
+ <SRS>EPSG:32153</SRS>
+ <SRS>EPSG:32154</SRS>
+ <SRS>EPSG:32155</SRS>
+ <SRS>EPSG:32156</SRS>
+ <SRS>EPSG:32157</SRS>
+ <SRS>EPSG:32158</SRS>
+ <SRS>EPSG:32161</SRS>
+ <SRS>EPSG:32164</SRS>
+ <SRS>EPSG:32165</SRS>
+ <SRS>EPSG:32166</SRS>
+ <SRS>EPSG:32167</SRS>
+ <SRS>EPSG:32180</SRS>
+ <SRS>EPSG:32181</SRS>
+ <SRS>EPSG:32182</SRS>
+ <SRS>EPSG:32183</SRS>
+ <SRS>EPSG:32184</SRS>
+ <SRS>EPSG:32185</SRS>
+ <SRS>EPSG:32186</SRS>
+ <SRS>EPSG:32187</SRS>
+ <SRS>EPSG:32188</SRS>
+ <SRS>EPSG:32189</SRS>
+ <SRS>EPSG:32190</SRS>
+ <SRS>EPSG:32191</SRS>
+ <SRS>EPSG:32192</SRS>
+ <SRS>EPSG:32193</SRS>
+ <SRS>EPSG:32194</SRS>
+ <SRS>EPSG:32195</SRS>
+ <SRS>EPSG:32196</SRS>
+ <SRS>EPSG:32197</SRS>
+ <SRS>EPSG:32198</SRS>
+ <SRS>EPSG:32199</SRS>
+ <SRS>EPSG:32201</SRS>
+ <SRS>EPSG:32202</SRS>
+ <SRS>EPSG:32203</SRS>
+ <SRS>EPSG:32204</SRS>
+ <SRS>EPSG:32205</SRS>
+ <SRS>EPSG:32206</SRS>
+ <SRS>EPSG:32207</SRS>
+ <SRS>EPSG:32208</SRS>
+ <SRS>EPSG:32209</SRS>
+ <SRS>EPSG:32210</SRS>
+ <SRS>EPSG:32211</SRS>
+ <SRS>EPSG:32212</SRS>
+ <SRS>EPSG:32213</SRS>
+ <SRS>EPSG:32214</SRS>
+ <SRS>EPSG:32215</SRS>
+ <SRS>EPSG:32216</SRS>
+ <SRS>EPSG:32217</SRS>
+ <SRS>EPSG:32218</SRS>
+ <SRS>EPSG:32219</SRS>
+ <SRS>EPSG:32220</SRS>
+ <SRS>EPSG:32221</SRS>
+ <SRS>EPSG:32222</SRS>
+ <SRS>EPSG:32223</SRS>
+ <SRS>EPSG:32224</SRS>
+ <SRS>EPSG:32225</SRS>
+ <SRS>EPSG:32226</SRS>
+ <SRS>EPSG:32227</SRS>
+ <SRS>EPSG:32228</SRS>
+ <SRS>EPSG:32229</SRS>
+ <SRS>EPSG:32230</SRS>
+ <SRS>EPSG:32231</SRS>
+ <SRS>EPSG:32232</SRS>
+ <SRS>EPSG:32233</SRS>
+ <SRS>EPSG:32234</SRS>
+ <SRS>EPSG:32235</SRS>
+ <SRS>EPSG:32236</SRS>
+ <SRS>EPSG:32237</SRS>
+ <SRS>EPSG:32238</SRS>
+ <SRS>EPSG:32239</SRS>
+ <SRS>EPSG:32240</SRS>
+ <SRS>EPSG:32241</SRS>
+ <SRS>EPSG:32242</SRS>
+ <SRS>EPSG:32243</SRS>
+ <SRS>EPSG:32244</SRS>
+ <SRS>EPSG:32245</SRS>
+ <SRS>EPSG:32246</SRS>
+ <SRS>EPSG:32247</SRS>
+ <SRS>EPSG:32248</SRS>
+ <SRS>EPSG:32249</SRS>
+ <SRS>EPSG:32250</SRS>
+ <SRS>EPSG:32251</SRS>
+ <SRS>EPSG:32252</SRS>
+ <SRS>EPSG:32253</SRS>
+ <SRS>EPSG:32254</SRS>
+ <SRS>EPSG:32255</SRS>
+ <SRS>EPSG:32256</SRS>
+ <SRS>EPSG:32257</SRS>
+ <SRS>EPSG:32258</SRS>
+ <SRS>EPSG:32259</SRS>
+ <SRS>EPSG:32260</SRS>
+ <SRS>EPSG:32301</SRS>
+ <SRS>EPSG:32302</SRS>
+ <SRS>EPSG:32303</SRS>
+ <SRS>EPSG:32304</SRS>
+ <SRS>EPSG:32305</SRS>
+ <SRS>EPSG:32306</SRS>
+ <SRS>EPSG:32307</SRS>
+ <SRS>EPSG:32308</SRS>
+ <SRS>EPSG:32309</SRS>
+ <SRS>EPSG:32310</SRS>
+ <SRS>EPSG:32311</SRS>
+ <SRS>EPSG:32312</SRS>
+ <SRS>EPSG:32313</SRS>
+ <SRS>EPSG:32314</SRS>
+ <SRS>EPSG:32315</SRS>
+ <SRS>EPSG:32316</SRS>
+ <SRS>EPSG:32317</SRS>
+ <SRS>EPSG:32318</SRS>
+ <SRS>EPSG:32319</SRS>
+ <SRS>EPSG:32320</SRS>
+ <SRS>EPSG:32321</SRS>
+ <SRS>EPSG:32322</SRS>
+ <SRS>EPSG:32323</SRS>
+ <SRS>EPSG:32324</SRS>
+ <SRS>EPSG:32325</SRS>
+ <SRS>EPSG:32326</SRS>
+ <SRS>EPSG:32327</SRS>
+ <SRS>EPSG:32328</SRS>
+ <SRS>EPSG:32329</SRS>
+ <SRS>EPSG:32330</SRS>
+ <SRS>EPSG:32331</SRS>
+ <SRS>EPSG:32332</SRS>
+ <SRS>EPSG:32333</SRS>
+ <SRS>EPSG:32334</SRS>
+ <SRS>EPSG:32335</SRS>
+ <SRS>EPSG:32336</SRS>
+ <SRS>EPSG:32337</SRS>
+ <SRS>EPSG:32338</SRS>
+ <SRS>EPSG:32339</SRS>
+ <SRS>EPSG:32340</SRS>
+ <SRS>EPSG:32341</SRS>
+ <SRS>EPSG:32342</SRS>
+ <SRS>EPSG:32343</SRS>
+ <SRS>EPSG:32344</SRS>
+ <SRS>EPSG:32345</SRS>
+ <SRS>EPSG:32346</SRS>
+ <SRS>EPSG:32347</SRS>
+ <SRS>EPSG:32348</SRS>
+ <SRS>EPSG:32349</SRS>
+ <SRS>EPSG:32350</SRS>
+ <SRS>EPSG:32351</SRS>
+ <SRS>EPSG:32352</SRS>
+ <SRS>EPSG:32353</SRS>
+ <SRS>EPSG:32354</SRS>
+ <SRS>EPSG:32355</SRS>
+ <SRS>EPSG:32356</SRS>
+ <SRS>EPSG:32357</SRS>
+ <SRS>EPSG:32358</SRS>
+ <SRS>EPSG:32359</SRS>
+ <SRS>EPSG:32360</SRS>
+ <SRS>EPSG:32401</SRS>
+ <SRS>EPSG:32402</SRS>
+ <SRS>EPSG:32403</SRS>
+ <SRS>EPSG:32404</SRS>
+ <SRS>EPSG:32405</SRS>
+ <SRS>EPSG:32406</SRS>
+ <SRS>EPSG:32407</SRS>
+ <SRS>EPSG:32408</SRS>
+ <SRS>EPSG:32409</SRS>
+ <SRS>EPSG:32410</SRS>
+ <SRS>EPSG:32411</SRS>
+ <SRS>EPSG:32412</SRS>
+ <SRS>EPSG:32413</SRS>
+ <SRS>EPSG:32414</SRS>
+ <SRS>EPSG:32415</SRS>
+ <SRS>EPSG:32416</SRS>
+ <SRS>EPSG:32417</SRS>
+ <SRS>EPSG:32418</SRS>
+ <SRS>EPSG:32419</SRS>
+ <SRS>EPSG:32420</SRS>
+ <SRS>EPSG:32421</SRS>
+ <SRS>EPSG:32422</SRS>
+ <SRS>EPSG:32423</SRS>
+ <SRS>EPSG:32424</SRS>
+ <SRS>EPSG:32425</SRS>
+ <SRS>EPSG:32426</SRS>
+ <SRS>EPSG:32427</SRS>
+ <SRS>EPSG:32428</SRS>
+ <SRS>EPSG:32429</SRS>
+ <SRS>EPSG:32430</SRS>
+ <SRS>EPSG:32431</SRS>
+ <SRS>EPSG:32432</SRS>
+ <SRS>EPSG:32433</SRS>
+ <SRS>EPSG:32434</SRS>
+ <SRS>EPSG:32435</SRS>
+ <SRS>EPSG:32436</SRS>
+ <SRS>EPSG:32437</SRS>
+ <SRS>EPSG:32438</SRS>
+ <SRS>EPSG:32439</SRS>
+ <SRS>EPSG:32440</SRS>
+ <SRS>EPSG:32441</SRS>
+ <SRS>EPSG:32442</SRS>
+ <SRS>EPSG:32443</SRS>
+ <SRS>EPSG:32444</SRS>
+ <SRS>EPSG:32445</SRS>
+ <SRS>EPSG:32446</SRS>
+ <SRS>EPSG:32447</SRS>
+ <SRS>EPSG:32448</SRS>
+ <SRS>EPSG:32449</SRS>
+ <SRS>EPSG:32450</SRS>
+ <SRS>EPSG:32451</SRS>
+ <SRS>EPSG:32452</SRS>
+ <SRS>EPSG:32453</SRS>
+ <SRS>EPSG:32454</SRS>
+ <SRS>EPSG:32455</SRS>
+ <SRS>EPSG:32456</SRS>
+ <SRS>EPSG:32457</SRS>
+ <SRS>EPSG:32458</SRS>
+ <SRS>EPSG:32459</SRS>
+ <SRS>EPSG:32460</SRS>
+ <SRS>EPSG:32501</SRS>
+ <SRS>EPSG:32502</SRS>
+ <SRS>EPSG:32503</SRS>
+ <SRS>EPSG:32504</SRS>
+ <SRS>EPSG:32505</SRS>
+ <SRS>EPSG:32506</SRS>
+ <SRS>EPSG:32507</SRS>
+ <SRS>EPSG:32508</SRS>
+ <SRS>EPSG:32509</SRS>
+ <SRS>EPSG:32510</SRS>
+ <SRS>EPSG:32511</SRS>
+ <SRS>EPSG:32512</SRS>
+ <SRS>EPSG:32513</SRS>
+ <SRS>EPSG:32514</SRS>
+ <SRS>EPSG:32515</SRS>
+ <SRS>EPSG:32516</SRS>
+ <SRS>EPSG:32517</SRS>
+ <SRS>EPSG:32518</SRS>
+ <SRS>EPSG:32519</SRS>
+ <SRS>EPSG:32520</SRS>
+ <SRS>EPSG:32521</SRS>
+ <SRS>EPSG:32522</SRS>
+ <SRS>EPSG:32523</SRS>
+ <SRS>EPSG:32524</SRS>
+ <SRS>EPSG:32525</SRS>
+ <SRS>EPSG:32526</SRS>
+ <SRS>EPSG:32527</SRS>
+ <SRS>EPSG:32528</SRS>
+ <SRS>EPSG:32529</SRS>
+ <SRS>EPSG:32530</SRS>
+ <SRS>EPSG:32531</SRS>
+ <SRS>EPSG:32532</SRS>
+ <SRS>EPSG:32533</SRS>
+ <SRS>EPSG:32534</SRS>
+ <SRS>EPSG:32535</SRS>
+ <SRS>EPSG:32536</SRS>
+ <SRS>EPSG:32537</SRS>
+ <SRS>EPSG:32538</SRS>
+ <SRS>EPSG:32539</SRS>
+ <SRS>EPSG:32540</SRS>
+ <SRS>EPSG:32541</SRS>
+ <SRS>EPSG:32542</SRS>
+ <SRS>EPSG:32543</SRS>
+ <SRS>EPSG:32544</SRS>
+ <SRS>EPSG:32545</SRS>
+ <SRS>EPSG:32546</SRS>
+ <SRS>EPSG:32547</SRS>
+ <SRS>EPSG:32548</SRS>
+ <SRS>EPSG:32549</SRS>
+ <SRS>EPSG:32550</SRS>
+ <SRS>EPSG:32551</SRS>
+ <SRS>EPSG:32552</SRS>
+ <SRS>EPSG:32553</SRS>
+ <SRS>EPSG:32554</SRS>
+ <SRS>EPSG:32555</SRS>
+ <SRS>EPSG:32556</SRS>
+ <SRS>EPSG:32557</SRS>
+ <SRS>EPSG:32558</SRS>
+ <SRS>EPSG:32559</SRS>
+ <SRS>EPSG:32560</SRS>
+ <SRS>EPSG:32600</SRS>
+ <SRS>EPSG:32601</SRS>
+ <SRS>EPSG:32602</SRS>
+ <SRS>EPSG:32603</SRS>
+ <SRS>EPSG:32604</SRS>
+ <SRS>EPSG:32605</SRS>
+ <SRS>EPSG:32606</SRS>
+ <SRS>EPSG:32607</SRS>
+ <SRS>EPSG:32608</SRS>
+ <SRS>EPSG:32609</SRS>
+ <SRS>EPSG:32610</SRS>
+ <SRS>EPSG:32611</SRS>
+ <SRS>EPSG:32612</SRS>
+ <SRS>EPSG:32613</SRS>
+ <SRS>EPSG:32614</SRS>
+ <SRS>EPSG:32615</SRS>
+ <SRS>EPSG:32616</SRS>
+ <SRS>EPSG:32617</SRS>
+ <SRS>EPSG:32618</SRS>
+ <SRS>EPSG:32619</SRS>
+ <SRS>EPSG:32620</SRS>
+ <SRS>EPSG:32621</SRS>
+ <SRS>EPSG:32622</SRS>
+ <SRS>EPSG:32623</SRS>
+ <SRS>EPSG:32624</SRS>
+ <SRS>EPSG:32625</SRS>
+ <SRS>EPSG:32626</SRS>
+ <SRS>EPSG:32627</SRS>
+ <SRS>EPSG:32628</SRS>
+ <SRS>EPSG:32629</SRS>
+ <SRS>EPSG:32630</SRS>
+ <SRS>EPSG:32631</SRS>
+ <SRS>EPSG:32632</SRS>
+ <SRS>EPSG:32633</SRS>
+ <SRS>EPSG:32634</SRS>
+ <SRS>EPSG:32635</SRS>
+ <SRS>EPSG:32636</SRS>
+ <SRS>EPSG:32637</SRS>
+ <SRS>EPSG:32638</SRS>
+ <SRS>EPSG:32639</SRS>
+ <SRS>EPSG:32640</SRS>
+ <SRS>EPSG:32641</SRS>
+ <SRS>EPSG:32642</SRS>
+ <SRS>EPSG:32643</SRS>
+ <SRS>EPSG:32644</SRS>
+ <SRS>EPSG:32645</SRS>
+ <SRS>EPSG:32646</SRS>
+ <SRS>EPSG:32647</SRS>
+ <SRS>EPSG:32648</SRS>
+ <SRS>EPSG:32649</SRS>
+ <SRS>EPSG:32650</SRS>
+ <SRS>EPSG:32651</SRS>
+ <SRS>EPSG:32652</SRS>
+ <SRS>EPSG:32653</SRS>
+ <SRS>EPSG:32654</SRS>
+ <SRS>EPSG:32655</SRS>
+ <SRS>EPSG:32656</SRS>
+ <SRS>EPSG:32657</SRS>
+ <SRS>EPSG:32658</SRS>
+ <SRS>EPSG:32659</SRS>
+ <SRS>EPSG:32660</SRS>
+ <SRS>EPSG:32661</SRS>
+ <SRS>EPSG:32662</SRS>
+ <SRS>EPSG:32664</SRS>
+ <SRS>EPSG:32665</SRS>
+ <SRS>EPSG:32666</SRS>
+ <SRS>EPSG:32667</SRS>
+ <SRS>EPSG:32700</SRS>
+ <SRS>EPSG:32701</SRS>
+ <SRS>EPSG:32702</SRS>
+ <SRS>EPSG:32703</SRS>
+ <SRS>EPSG:32704</SRS>
+ <SRS>EPSG:32705</SRS>
+ <SRS>EPSG:32706</SRS>
+ <SRS>EPSG:32707</SRS>
+ <SRS>EPSG:32708</SRS>
+ <SRS>EPSG:32709</SRS>
+ <SRS>EPSG:32710</SRS>
+ <SRS>EPSG:32711</SRS>
+ <SRS>EPSG:32712</SRS>
+ <SRS>EPSG:32713</SRS>
+ <SRS>EPSG:32714</SRS>
+ <SRS>EPSG:32715</SRS>
+ <SRS>EPSG:32716</SRS>
+ <SRS>EPSG:32717</SRS>
+ <SRS>EPSG:32718</SRS>
+ <SRS>EPSG:32719</SRS>
+ <SRS>EPSG:32720</SRS>
+ <SRS>EPSG:32721</SRS>
+ <SRS>EPSG:32722</SRS>
+ <SRS>EPSG:32723</SRS>
+ <SRS>EPSG:32724</SRS>
+ <SRS>EPSG:32725</SRS>
+ <SRS>EPSG:32726</SRS>
+ <SRS>EPSG:32727</SRS>
+ <SRS>EPSG:32728</SRS>
+ <SRS>EPSG:32729</SRS>
+ <SRS>EPSG:32730</SRS>
+ <SRS>EPSG:32731</SRS>
+ <SRS>EPSG:32732</SRS>
+ <SRS>EPSG:32733</SRS>
+ <SRS>EPSG:32734</SRS>
+ <SRS>EPSG:32735</SRS>
+ <SRS>EPSG:32736</SRS>
+ <SRS>EPSG:32737</SRS>
+ <SRS>EPSG:32738</SRS>
+ <SRS>EPSG:32739</SRS>
+ <SRS>EPSG:32740</SRS>
+ <SRS>EPSG:32741</SRS>
+ <SRS>EPSG:32742</SRS>
+ <SRS>EPSG:32743</SRS>
+ <SRS>EPSG:32744</SRS>
+ <SRS>EPSG:32745</SRS>
+ <SRS>EPSG:32746</SRS>
+ <SRS>EPSG:32747</SRS>
+ <SRS>EPSG:32748</SRS>
+ <SRS>EPSG:32749</SRS>
+ <SRS>EPSG:32750</SRS>
+ <SRS>EPSG:32751</SRS>
+ <SRS>EPSG:32752</SRS>
+ <SRS>EPSG:32753</SRS>
+ <SRS>EPSG:32754</SRS>
+ <SRS>EPSG:32755</SRS>
+ <SRS>EPSG:32756</SRS>
+ <SRS>EPSG:32757</SRS>
+ <SRS>EPSG:32758</SRS>
+ <SRS>EPSG:32759</SRS>
+ <SRS>EPSG:32760</SRS>
+ <SRS>EPSG:32761</SRS>
+ <SRS>EPSG:32766</SRS>
+ <SRS>EPSG:61206405</SRS>
+ <SRS>EPSG:61216405</SRS>
+ <SRS>EPSG:61226405</SRS>
+ <SRS>EPSG:61236405</SRS>
+ <SRS>EPSG:61246405</SRS>
+ <SRS>EPSG:61266405</SRS>
+ <SRS>EPSG:61266413</SRS>
+ <SRS>EPSG:61276405</SRS>
+ <SRS>EPSG:61286405</SRS>
+ <SRS>EPSG:61296405</SRS>
+ <SRS>EPSG:61306405</SRS>
+ <SRS>EPSG:61306413</SRS>
+ <SRS>EPSG:61316405</SRS>
+ <SRS>EPSG:61326405</SRS>
+ <SRS>EPSG:61336405</SRS>
+ <SRS>EPSG:61346405</SRS>
+ <SRS>EPSG:61356405</SRS>
+ <SRS>EPSG:61366405</SRS>
+ <SRS>EPSG:61376405</SRS>
+ <SRS>EPSG:61386405</SRS>
+ <SRS>EPSG:61396405</SRS>
+ <SRS>EPSG:61406405</SRS>
+ <SRS>EPSG:61406413</SRS>
+ <SRS>EPSG:61416405</SRS>
+ <SRS>EPSG:61426405</SRS>
+ <SRS>EPSG:61436405</SRS>
+ <SRS>EPSG:61446405</SRS>
+ <SRS>EPSG:61456405</SRS>
+ <SRS>EPSG:61466405</SRS>
+ <SRS>EPSG:61476405</SRS>
+ <SRS>EPSG:61486405</SRS>
+ <SRS>EPSG:61486413</SRS>
+ <SRS>EPSG:61496405</SRS>
+ <SRS>EPSG:61506405</SRS>
+ <SRS>EPSG:61516405</SRS>
+ <SRS>EPSG:61516413</SRS>
+ <SRS>EPSG:61526405</SRS>
+ <SRS>EPSG:61526413</SRS>
+ <SRS>EPSG:61536405</SRS>
+ <SRS>EPSG:61546405</SRS>
+ <SRS>EPSG:61556405</SRS>
+ <SRS>EPSG:61566405</SRS>
+ <SRS>EPSG:61576405</SRS>
+ <SRS>EPSG:61586405</SRS>
+ <SRS>EPSG:61596405</SRS>
+ <SRS>EPSG:61606405</SRS>
+ <SRS>EPSG:61616405</SRS>
+ <SRS>EPSG:61626405</SRS>
+ <SRS>EPSG:61636405</SRS>
+ <SRS>EPSG:61636413</SRS>
+ <SRS>EPSG:61646405</SRS>
+ <SRS>EPSG:61656405</SRS>
+ <SRS>EPSG:61666405</SRS>
+ <SRS>EPSG:61676405</SRS>
+ <SRS>EPSG:61676413</SRS>
+ <SRS>EPSG:61686405</SRS>
+ <SRS>EPSG:61696405</SRS>
+ <SRS>EPSG:61706405</SRS>
+ <SRS>EPSG:61706413</SRS>
+ <SRS>EPSG:61716405</SRS>
+ <SRS>EPSG:61716413</SRS>
+ <SRS>EPSG:61736405</SRS>
+ <SRS>EPSG:61736413</SRS>
+ <SRS>EPSG:61746405</SRS>
+ <SRS>EPSG:61756405</SRS>
+ <SRS>EPSG:61766405</SRS>
+ <SRS>EPSG:61766413</SRS>
+ <SRS>EPSG:61786405</SRS>
+ <SRS>EPSG:61796405</SRS>
+ <SRS>EPSG:61806405</SRS>
+ <SRS>EPSG:61806413</SRS>
+ <SRS>EPSG:61816405</SRS>
+ <SRS>EPSG:61826405</SRS>
+ <SRS>EPSG:61836405</SRS>
+ <SRS>EPSG:61846405</SRS>
+ <SRS>EPSG:61886405</SRS>
+ <SRS>EPSG:61896405</SRS>
+ <SRS>EPSG:61896413</SRS>
+ <SRS>EPSG:61906405</SRS>
+ <SRS>EPSG:61906413</SRS>
+ <SRS>EPSG:61916405</SRS>
+ <SRS>EPSG:61926405</SRS>
+ <SRS>EPSG:61936405</SRS>
+ <SRS>EPSG:61946405</SRS>
+ <SRS>EPSG:61956405</SRS>
+ <SRS>EPSG:61966405</SRS>
+ <SRS>EPSG:61976405</SRS>
+ <SRS>EPSG:61986405</SRS>
+ <SRS>EPSG:61996405</SRS>
+ <SRS>EPSG:62006405</SRS>
+ <SRS>EPSG:62016405</SRS>
+ <SRS>EPSG:62026405</SRS>
+ <SRS>EPSG:62036405</SRS>
+ <SRS>EPSG:62046405</SRS>
+ <SRS>EPSG:62056405</SRS>
+ <SRS>EPSG:62066405</SRS>
+ <SRS>EPSG:62076405</SRS>
+ <SRS>EPSG:62086405</SRS>
+ <SRS>EPSG:62096405</SRS>
+ <SRS>EPSG:62106405</SRS>
+ <SRS>EPSG:62116405</SRS>
+ <SRS>EPSG:62126405</SRS>
+ <SRS>EPSG:62136405</SRS>
+ <SRS>EPSG:62146405</SRS>
+ <SRS>EPSG:62156405</SRS>
+ <SRS>EPSG:62166405</SRS>
+ <SRS>EPSG:62186405</SRS>
+ <SRS>EPSG:62196405</SRS>
+ <SRS>EPSG:62206405</SRS>
+ <SRS>EPSG:62216405</SRS>
+ <SRS>EPSG:62226405</SRS>
+ <SRS>EPSG:62236405</SRS>
+ <SRS>EPSG:62246405</SRS>
+ <SRS>EPSG:62256405</SRS>
+ <SRS>EPSG:62276405</SRS>
+ <SRS>EPSG:62296405</SRS>
+ <SRS>EPSG:62306405</SRS>
+ <SRS>EPSG:62316405</SRS>
+ <SRS>EPSG:62326405</SRS>
+ <SRS>EPSG:62336405</SRS>
+ <SRS>EPSG:62366405</SRS>
+ <SRS>EPSG:62376405</SRS>
+ <SRS>EPSG:62386405</SRS>
+ <SRS>EPSG:62396405</SRS>
+ <SRS>EPSG:62406405</SRS>
+ <SRS>EPSG:62416405</SRS>
+ <SRS>EPSG:62426405</SRS>
+ <SRS>EPSG:62436405</SRS>
+ <SRS>EPSG:62446405</SRS>
+ <SRS>EPSG:62456405</SRS>
+ <SRS>EPSG:62466405</SRS>
+ <SRS>EPSG:62476405</SRS>
+ <SRS>EPSG:62486405</SRS>
+ <SRS>EPSG:62496405</SRS>
+ <SRS>EPSG:62506405</SRS>
+ <SRS>EPSG:62516405</SRS>
+ <SRS>EPSG:62526405</SRS>
+ <SRS>EPSG:62536405</SRS>
+ <SRS>EPSG:62546405</SRS>
+ <SRS>EPSG:62556405</SRS>
+ <SRS>EPSG:62566405</SRS>
+ <SRS>EPSG:62576405</SRS>
+ <SRS>EPSG:62586405</SRS>
+ <SRS>EPSG:62586413</SRS>
+ <SRS>EPSG:62596405</SRS>
+ <SRS>EPSG:62616405</SRS>
+ <SRS>EPSG:62626405</SRS>
+ <SRS>EPSG:62636405</SRS>
+ <SRS>EPSG:62646405</SRS>
+ <SRS>EPSG:62656405</SRS>
+ <SRS>EPSG:62666405</SRS>
+ <SRS>EPSG:62676405</SRS>
+ <SRS>EPSG:62686405</SRS>
+ <SRS>EPSG:62696405</SRS>
+ <SRS>EPSG:62706405</SRS>
+ <SRS>EPSG:62716405</SRS>
+ <SRS>EPSG:62726405</SRS>
+ <SRS>EPSG:62736405</SRS>
+ <SRS>EPSG:62746405</SRS>
+ <SRS>EPSG:62756405</SRS>
+ <SRS>EPSG:62766405</SRS>
+ <SRS>EPSG:62776405</SRS>
+ <SRS>EPSG:62786405</SRS>
+ <SRS>EPSG:62796405</SRS>
+ <SRS>EPSG:62806405</SRS>
+ <SRS>EPSG:62816405</SRS>
+ <SRS>EPSG:62826405</SRS>
+ <SRS>EPSG:62836405</SRS>
+ <SRS>EPSG:62836413</SRS>
+ <SRS>EPSG:62846405</SRS>
+ <SRS>EPSG:62856405</SRS>
+ <SRS>EPSG:62866405</SRS>
+ <SRS>EPSG:62886405</SRS>
+ <SRS>EPSG:62896405</SRS>
+ <SRS>EPSG:62926405</SRS>
+ <SRS>EPSG:62936405</SRS>
+ <SRS>EPSG:62956405</SRS>
+ <SRS>EPSG:62976405</SRS>
+ <SRS>EPSG:62986405</SRS>
+ <SRS>EPSG:62996405</SRS>
+ <SRS>EPSG:63006405</SRS>
+ <SRS>EPSG:63016405</SRS>
+ <SRS>EPSG:63026405</SRS>
+ <SRS>EPSG:63036405</SRS>
+ <SRS>EPSG:63046405</SRS>
+ <SRS>EPSG:63066405</SRS>
+ <SRS>EPSG:63076405</SRS>
+ <SRS>EPSG:63086405</SRS>
+ <SRS>EPSG:63096405</SRS>
+ <SRS>EPSG:63106405</SRS>
+ <SRS>EPSG:63116405</SRS>
+ <SRS>EPSG:63126405</SRS>
+ <SRS>EPSG:63136405</SRS>
+ <SRS>EPSG:63146405</SRS>
+ <SRS>EPSG:63156405</SRS>
+ <SRS>EPSG:63166405</SRS>
+ <SRS>EPSG:63176405</SRS>
+ <SRS>EPSG:63186405</SRS>
+ <SRS>EPSG:63196405</SRS>
+ <SRS>EPSG:63226405</SRS>
+ <SRS>EPSG:63246405</SRS>
+ <SRS>EPSG:63266405</SRS>
+ <SRS>EPSG:63266406</SRS>
+ <SRS>EPSG:63266407</SRS>
+ <SRS>EPSG:63266408</SRS>
+ <SRS>EPSG:63266409</SRS>
+ <SRS>EPSG:63266410</SRS>
+ <SRS>EPSG:63266411</SRS>
+ <SRS>EPSG:63266412</SRS>
+ <SRS>EPSG:63266413</SRS>
+ <SRS>EPSG:63266414</SRS>
+ <SRS>EPSG:63266415</SRS>
+ <SRS>EPSG:63266416</SRS>
+ <SRS>EPSG:63266417</SRS>
+ <SRS>EPSG:63266418</SRS>
+ <SRS>EPSG:63266419</SRS>
+ <SRS>EPSG:63266420</SRS>
+ <SRS>EPSG:66006405</SRS>
+ <SRS>EPSG:66016405</SRS>
+ <SRS>EPSG:66026405</SRS>
+ <SRS>EPSG:66036405</SRS>
+ <SRS>EPSG:66046405</SRS>
+ <SRS>EPSG:66056405</SRS>
+ <SRS>EPSG:66066405</SRS>
+ <SRS>EPSG:66076405</SRS>
+ <SRS>EPSG:66086405</SRS>
+ <SRS>EPSG:66096405</SRS>
+ <SRS>EPSG:66106405</SRS>
+ <SRS>EPSG:66116405</SRS>
+ <SRS>EPSG:66126405</SRS>
+ <SRS>EPSG:66126413</SRS>
+ <SRS>EPSG:66136405</SRS>
+ <SRS>EPSG:66146405</SRS>
+ <SRS>EPSG:66156405</SRS>
+ <SRS>EPSG:66166405</SRS>
+ <SRS>EPSG:66186405</SRS>
+ <SRS>EPSG:66196405</SRS>
+ <SRS>EPSG:66196413</SRS>
+ <SRS>EPSG:66206405</SRS>
+ <SRS>EPSG:66216405</SRS>
+ <SRS>EPSG:66226405</SRS>
+ <SRS>EPSG:66236405</SRS>
+ <SRS>EPSG:66246405</SRS>
+ <SRS>EPSG:66246413</SRS>
+ <SRS>EPSG:66256405</SRS>
+ <SRS>EPSG:66266405</SRS>
+ <SRS>EPSG:66276405</SRS>
+ <SRS>EPSG:66276413</SRS>
+ <SRS>EPSG:66286405</SRS>
+ <SRS>EPSG:66296405</SRS>
+ <SRS>EPSG:66306405</SRS>
+ <SRS>EPSG:66316405</SRS>
+ <SRS>EPSG:66326405</SRS>
+ <SRS>EPSG:66336405</SRS>
+ <SRS>EPSG:66346405</SRS>
+ <SRS>EPSG:66356405</SRS>
+ <SRS>EPSG:66366405</SRS>
+ <SRS>EPSG:66376405</SRS>
+ <SRS>EPSG:66386405</SRS>
+ <SRS>EPSG:66396405</SRS>
+ <SRS>EPSG:66406405</SRS>
+ <SRS>EPSG:66406413</SRS>
+ <SRS>EPSG:66416405</SRS>
+ <SRS>EPSG:66426405</SRS>
+ <SRS>EPSG:66436405</SRS>
+ <SRS>EPSG:66446405</SRS>
+ <SRS>EPSG:66456405</SRS>
+ <SRS>EPSG:66456413</SRS>
+ <SRS>EPSG:66466405</SRS>
+ <SRS>EPSG:66576405</SRS>
+ <SRS>EPSG:66586405</SRS>
+ <SRS>EPSG:66596405</SRS>
+ <SRS>EPSG:66596413</SRS>
+ <SRS>EPSG:66606405</SRS>
+ <SRS>EPSG:66616405</SRS>
+ <SRS>EPSG:66616413</SRS>
+ <SRS>EPSG:66636405</SRS>
+ <SRS>EPSG:66646405</SRS>
+ <SRS>EPSG:66656405</SRS>
+ <SRS>EPSG:66666405</SRS>
+ <SRS>EPSG:66676405</SRS>
+ <SRS>EPSG:68016405</SRS>
+ <SRS>EPSG:68026405</SRS>
+ <SRS>EPSG:68036405</SRS>
+ <SRS>EPSG:68046405</SRS>
+ <SRS>EPSG:68056405</SRS>
+ <SRS>EPSG:68066405</SRS>
+ <SRS>EPSG:68086405</SRS>
+ <SRS>EPSG:68096405</SRS>
+ <SRS>EPSG:68136405</SRS>
+ <SRS>EPSG:68146405</SRS>
+ <SRS>EPSG:68156405</SRS>
+ <SRS>EPSG:68186405</SRS>
+ <SRS>EPSG:68206405</SRS>
+ <SRS>EPSG:69036405</SRS>
+ <SRS>EPSG:42302</SRS>
+ <SRS>EPSG:42301</SRS>
+ <SRS>EPSG:900913</SRS>
+ <SRS>EPSG:45556</SRS>
+ <SRS>EPSG:45555</SRS>
+ <SRS>EPSG:54004</SRS>
+ <SRS>EPSG:41001</SRS>
+ <SRS>EPSG:42311</SRS>
+ <SRS>EPSG:42310</SRS>
+ <SRS>EPSG:18001</SRS>
+ <SRS>EPSG:100003</SRS>
+ <SRS>EPSG:42106</SRS>
+ <SRS>EPSG:100002</SRS>
+ <SRS>EPSG:42105</SRS>
+ <SRS>EPSG:100001</SRS>
+ <SRS>EPSG:42309</SRS>
+ <SRS>EPSG:42104</SRS>
+ <SRS>EPSG:42308</SRS>
+ <SRS>EPSG:42103</SRS>
+ <SRS>EPSG:42307</SRS>
+ <SRS>EPSG:42102</SRS>
+ <SRS>EPSG:42306</SRS>
+ <SRS>EPSG:42101</SRS>
+ <SRS>EPSG:42305</SRS>
+ <SRS>EPSG:42304</SRS>
+ <SRS>EPSG:42303</SRS>
+ <LatLonBoundingBox minx="-297176.16529836657" miny="-1.2694600326676274E7" maxx="3.0016785704606913E7" maxy="1.7619361543229006E7"/>
+ <Layer queryable="1">
+ <Name>og:bugsites</Name>
+ <Title/>
+ <Abstract>Sample data from GRASS, bug sites location, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>spearfish</Keyword>
+ <Keyword>sfBugsites</Keyword>
+ <Keyword>insects</Keyword>
+ <Keyword>bugsites</Keyword>
+ <Keyword>tiger_beetles</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N",
+ GEOGCS["NAD27",
+ DATUM["North American Datum 1927",
+ SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]],
+ TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0],
+ AUTHORITY["EPSG","6267"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4267"]],
+ PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]],
+ PARAMETER["central_meridian", -105.0],
+ PARAMETER["latitude_of_origin", 0.0],
+ PARAMETER["scale_factor", 0.9996],
+ PARAMETER["false_easting", 500000.0],
+ PARAMETER["false_northing", 0.0],
+ UNIT["m", 1.0],
+ AXIS["Easting", EAST],
+ AXIS["Northing", NORTH],
+ AUTHORITY["EPSG","26713"]]-->
+ <LatLonBoundingBox minx="-103.8701581843142" miny="44.286540361238224" maxx="-103.63532819794625" maxy="44.52137034760618"/>
+ <BoundingBox SRS="EPSG:26713" minx="590232.0" miny="4914096.0" maxx="608471.0" maxy="4920512.0"/>
+ <Style>
+ <Name>capitals</Name>
+ <Title>Capital cities</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:bugsites"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>og:restricted</Name>
+ <Title/>
+ <Abstract>Sample data from GRASS, restricted areas, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>spearfish</Keyword>
+ <Keyword>restricted</Keyword>
+ <Keyword>sfRestricted</Keyword>
+ <Keyword>areas</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N",
+ GEOGCS["NAD27",
+ DATUM["North American Datum 1927",
+ SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]],
+ TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0],
+ AUTHORITY["EPSG","6267"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4267"]],
+ PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]],
+ PARAMETER["central_meridian", -105.0],
+ PARAMETER["latitude_of_origin", 0.0],
+ PARAMETER["scale_factor", 0.9996],
+ PARAMETER["false_easting", 500000.0],
+ PARAMETER["false_northing", 0.0],
+ UNIT["m", 1.0],
+ AXIS["Easting", EAST],
+ AXIS["Northing", NORTH],
+ AUTHORITY["EPSG","26713"]]-->
+ <LatLonBoundingBox minx="-104.36424600670885" miny="43.78798270975212" maxx="-103.06226503558304" maxy="45.089963680877936"/>
+ <BoundingBox SRS="EPSG:26713" minx="551796.8125" miny="4901896.0" maxx="652788.5625" maxy="4940954.0"/>
+ <Style>
+ <Name>restricted</Name>
+ <Title>Red, translucent style</Title>
+ <Abstract>A sample style that just prints out a transparent red interior with a red outline</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:restricted"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>og:archsites</Name>
+ <Title/>
+ <Abstract>Sample data from GRASS, archeological sites location, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>archsites</Keyword>
+ <Keyword>spearfish</Keyword>
+ <Keyword>sfArchsites</Keyword>
+ <Keyword>archeology</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N",
+ GEOGCS["NAD27",
+ DATUM["North American Datum 1927",
+ SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]],
+ TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0],
+ AUTHORITY["EPSG","6267"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4267"]],
+ PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]],
+ PARAMETER["central_meridian", -105.0],
+ PARAMETER["latitude_of_origin", 0.0],
+ PARAMETER["scale_factor", 0.9996],
+ PARAMETER["false_easting", 500000.0],
+ PARAMETER["false_northing", 0.0],
+ UNIT["m", 1.0],
+ AXIS["Easting", EAST],
+ AXIS["Northing", NORTH],
+ AUTHORITY["EPSG","26713"]]-->
+ <LatLonBoundingBox minx="-103.87480459767542" miny="44.31295793136913" maxx="-103.63549073047534" maxy="44.55227179856921"/>
+ <BoundingBox SRS="EPSG:26713" minx="589860.0" miny="4914479.0" maxx="608355.0" maxy="4926490.0"/>
+ <Style>
+ <Name>point</Name>
+ <Title>Default point</Title>
+ <Abstract>A sample style that just prints out a 6px wide red square</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:archsites"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>og:streams</Name>
+ <Title/>
+ <Abstract>Sample data from GRASS, streams, Spearfish, South Dakota, USA</Abstract>
+ <KeywordList>
+ <Keyword>spearfish</Keyword>
+ <Keyword>sfStreams</Keyword>
+ <Keyword>streams</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N",
+ GEOGCS["NAD27",
+ DATUM["North American Datum 1927",
+ SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]],
+ TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0],
+ AUTHORITY["EPSG","6267"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4267"]],
+ PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]],
+ PARAMETER["central_meridian", -105.0],
+ PARAMETER["latitude_of_origin", 0.0],
+ PARAMETER["scale_factor", 0.9996],
+ PARAMETER["false_easting", 500000.0],
+ PARAMETER["false_northing", 0.0],
+ UNIT["m", 1.0],
+ AXIS["Easting", EAST],
+ AXIS["Northing", NORTH],
+ AUTHORITY["EPSG","26713"]]-->
+ <LatLonBoundingBox minx="-103.88033574142051" miny="44.30711172484593" maxx="-103.62022283326024" maxy="44.5672246330062"/>
+ <BoundingBox SRS="EPSG:26713" minx="589443.0" miny="4913935.0" maxx="609526.75" maxy="4928059.5"/>
+ <Style>
+ <Name>simple_streams</Name>
+ <Title>Default Styler for streams segments</Title>
+ <Abstract>Blue lines, 2px wide</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:streams"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tiger:poly_landmarks</Name>
+ <Title>Manhattan (NY) landmarks</Title>
+ <Abstract>Manhattan landmarks, identifies water, lakes, parks, interesting buildilngs</Abstract>
+ <KeywordList>
+ <Keyword>DS_poly_landmarks</Keyword>
+ <Keyword>landmarks</Keyword>
+ <Keyword>manhattan</Keyword>
+ <Keyword>poly_landmarks</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="-74.0828672737" miny="40.67246384130001" maxx="-73.8660689563" maxy="40.8892621587"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.047185" miny="40.679648" maxx="-73.90782" maxy="40.882078"/>
+ <Style>
+ <Name>poly_landmarks</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poly_landmarks"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tiger:poi</Name>
+ <Title>Manhattan (NY) points of interest</Title>
+ <Abstract>Points of interest in New York, New York (on Manhattan). One of the attributes contains the name of a file with a picture of the point of interest.</Abstract>
+ <KeywordList>
+ <Keyword>poi</Keyword>
+ <Keyword>Manhattan</Keyword>
+ <Keyword>DS_poi</Keyword>
+ <Keyword>points_of_interest</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="-74.01244590356289" miny="40.70750285086222" maxx="-74.00795911725866" maxy="40.711989637166425"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.0118315772888" miny="40.70754683896324" maxx="-74.00153046439813" maxy="40.719885123828675"/>
+ <Style>
+ <Name>poi</Name>
+ <Title>Points of interest</Title>
+ <Abstract>Manhattan points of interest</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:poi"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tiger:tiger_roads</Name>
+ <Title>Manhattan (NY) roads</Title>
+ <Abstract>Highly simplified road layout of Manhattan in New York..</Abstract>
+ <KeywordList>
+ <Keyword>DS_tiger_roads</Keyword>
+ <Keyword>tiger_roads</Keyword>
+ <Keyword>roads</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="-74.06603057" miny="40.68228143" maxx="-73.86819443" maxy="40.880117569999996"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.02722" miny="40.684221" maxx="-73.907005" maxy="40.878178"/>
+ <Style>
+ <Name>tiger_roads</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tiger:tiger_roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>za:za_natural</Name>
+ <Title>Natural Landmarks in South Africa</Title>
+ <Abstract>This layer describes natural features of South Africa such as forests and lakes.</Abstract>
+ <KeywordList>
+ <Keyword>water</Keyword>
+ <Keyword>forests</Keyword>
+ <Keyword>landmarks</Keyword>
+ <Keyword>Africa</Keyword>
+ <Keyword>South</Keyword>
+ <Keyword>natural</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="16.779241142272962" miny="-36.53577846527099" maxx="32.70336002349853" maxy="-20.611659584045416"/>
+ <BoundingBox SRS="EPSG:4326" minx="16.935359954834" miny="-34.3737831115723" maxx="32.5472412109375" maxy="-22.7736549377441"/>
+ <Style>
+ <Name>za_natural</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_natural"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>za:za_points</Name>
+ <Title>Points of Interest in South Africa</Title>
+ <Abstract>Noteworthy locations such as hotels and tourist attractions in South Africa.</Abstract>
+ <KeywordList>
+ <Keyword>of</Keyword>
+ <Keyword>tourist</Keyword>
+ <Keyword>landmarks</Keyword>
+ <Keyword>zoo</Keyword>
+ <Keyword>cities</Keyword>
+ <Keyword>interest</Keyword>
+ <Keyword>attractions</Keyword>
+ <Keyword>points</Keyword>
+ <Keyword>hotel</Keyword>
+ <Keyword>museum</Keyword>
+ <Keyword>picnic</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="14.629095230102537" miny="-47.151258316040014" maxx="39.792314376831065" maxy="-21.988039169311488"/>
+ <BoundingBox SRS="EPSG:4326" minx="16.4827766418457" miny="-46.9045600891113" maxx="37.9386329650879" maxy="-22.2347373962402"/>
+ <Style>
+ <Name>za_points</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_points"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>za:za_roads</Name>
+ <Title>South African Roads</Title>
+ <Abstract>This layer describes roads in South Africa.</Abstract>
+ <KeywordList>
+ <Keyword>south</Keyword>
+ <Keyword>africa</Keyword>
+ <Keyword>roads</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="16.29388177871706" miny="-36.85438787460323" maxx="33.04232465744013" maxy="-20.10594499588016"/>
+ <BoundingBox SRS="EPSG:4326" minx="16.4580821990967" miny="-34.8331336975098" maxx="32.8781242370605" maxy="-22.1271991729736"/>
+ <Style>
+ <Name>za_roads</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>za:za_vegetation</Name>
+ <Title>South African Vegetation</Title>
+ <Abstract>This layer describes vegetated areas in South Africa, categorized by biome.</Abstract>
+ <KeywordList>
+ <Keyword>south</Keyword>
+ <Keyword>vegetation</Keyword>
+ <Keyword>africa</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="16.30492322921758" miny="-36.855452365875216" maxx="33.05824930191042" maxy="-20.102126293182376"/>
+ <BoundingBox SRS="EPSG:4326" minx="16.4691715240479" miny="-34.8336486816406" maxx="32.8940010070801" maxy="-22.123929977417"/>
+ <Style>
+ <Name>za_vegetation</Name>
+ <Title>Default Styler</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=za:za_vegetation"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_cities</Name>
+ <Title>Tasmania cities</Title>
+ <Abstract>Cities in Tasmania (actually, just the capital)</Abstract>
+ <KeywordList>
+ <Keyword>cities</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="145.1667856" miny="-43.706631400000006" maxx="148.30373440000002" maxy="-40.56968259999999"/>
+ <BoundingBox SRS="EPSG:4326" minx="147.2910004483" miny="-42.851001816890005" maxx="147.2910004483" maxy="-42.851001816890005"/>
+ <Style>
+ <Name>capitals</Name>
+ <Title>Capital cities</Title>
+ <Abstract/>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_cities"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_roads</Name>
+ <Title>Tasmania roads</Title>
+ <Abstract>Main Tasmania roads</Abstract>
+ <KeywordList>
+ <Keyword>Roads</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="145.1667856" miny="-43.706631400000006" maxx="148.30373440000002" maxy="-40.56968259999999"/>
+ <BoundingBox SRS="EPSG:4326" minx="145.19754" miny="-43.423512" maxx="148.27298000000002" maxy="-40.852802"/>
+ <Style>
+ <Name>simple_roads</Name>
+ <Title>Default Styler for simple road segments</Title>
+ <Abstract>Light red line, 2px wide</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_state_boundaries</Name>
+ <Title>Tasmania state boundaries</Title>
+ <Abstract>Tasmania state boundaries</Abstract>
+ <KeywordList>
+ <Keyword>boundaries</Keyword>
+ <Keyword>tasmania_state_boundaries</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="143.74100879660003" miny="-44.026947203400006" maxx="148.57295620340003" maxy="-39.194999796599994"/>
+ <BoundingBox SRS="EPSG:4326" minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>
+ <Style>
+ <Name>green</Name>
+ <Title>Green polygon</Title>
+ <Abstract>Green fill with black outline</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_state_boundaries"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:tasmania_water_bodies</Name>
+ <Title>Tasmania water bodies</Title>
+ <Abstract>Tasmania water bodies</Abstract>
+ <KeywordList>
+ <Keyword>Lakes</Keyword>
+ <Keyword>Bodies</Keyword>
+ <Keyword>Australia</Keyword>
+ <Keyword>Water</Keyword>
+ <Keyword>Tasmania</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="145.95490063999998" miny="-43.04450786" maxx="147.23641436" maxy="-41.762994139999996"/>
+ <BoundingBox SRS="EPSG:4326" minx="145.97161899999998" miny="-43.031944" maxx="147.219696" maxy="-41.775558"/>
+ <Style>
+ <Name>cite_lakes</Name>
+ <Title>Blue lake</Title>
+ <Abstract>A blue fill, solid black outline style</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:tasmania_water_bodies"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:states</Name>
+ <Title>USA Population</Title>
+ <Abstract>This is some census data on the states.</Abstract>
+ <KeywordList>
+ <Keyword>census</Keyword>
+ <Keyword>united</Keyword>
+ <Keyword>boundaries</Keyword>
+ <Keyword>state</Keyword>
+ <Keyword>states</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="-125.30903773" miny="7.705448770000002" maxx="-66.39223326999999" maxy="66.62225323"/>
+ <BoundingBox SRS="EPSG:4326" minx="-124.73142200000001" miny="24.955967" maxx="-66.969849" maxy="49.371735"/>
+ <Style>
+ <Name>population</Name>
+ <Title>Population in the United States</Title>
+ <Abstract>A sample filter that filters the United States into three
+ categories of population, drawn in different colors</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:states"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tike:waterways</Name>
+ <Title>Waterways</Title>
+ <Abstract>Waterways in Finland.</Abstract>
+ <KeywordList>
+ <Keyword>Finland</Keyword>
+ <Keyword>waterways</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="19.530168895721403" miny="58.860618000030506" maxx="31.6566005897522" maxy="70.9870496940613"/>
+ <BoundingBox SRS="EPSG:4326" minx="19.649055480957" miny="59.9357719421387" maxx="31.5377140045166" maxy="69.9118957519531"/>
+ <Style>
+ <Name>line</Name>
+ <Title>1 px blue line</Title>
+ <Abstract>Default line style, 1 pixel wide blue</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:waterways"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tike:railways</Name>
+ <Title>roads_Type</Title>
+ <Abstract>Generated from tike</Abstract>
+ <KeywordList>
+ <Keyword>tike</Keyword>
+ <Keyword>roads</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="-297176.16529836657" miny="-1.2694600326676274E7" maxx="3.0016785704606913E7" maxy="1.7619361543229006E7"/>
+ <BoundingBox SRS="EPSG:4326" minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
+ <Style>
+ <Name>line</Name>
+ <Title>1 px blue line</Title>
+ <Abstract>Default line style, 1 pixel wide blue</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:railways"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tike:roads</Name>
+ <Title>roads_Type</Title>
+ <Abstract>Generated from tike</Abstract>
+ <KeywordList>
+ <Keyword>tike</Keyword>
+ <Keyword>roads</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="-297176.16529836657" miny="-1.2694600326676274E7" maxx="3.0016785704606913E7" maxy="1.7619361543229006E7"/>
+ <BoundingBox SRS="EPSG:4326" minx="19.5393085479736" miny="-2277.78344726562" maxx="2.971959E7" maxy="4927039.0"/>
+ <Style>
+ <Name>line</Name>
+ <Title>1 px blue line</Title>
+ <Abstract>Default line style, 1 pixel wide blue</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>og:roads</Name>
+ <Title>roads_Type</Title>
+ <Abstract>Generated from sf_reset</Abstract>
+ <KeywordList>
+ <Keyword>roads</Keyword>
+ <Keyword>sf_reset</Keyword>
+ </KeywordList>
+ <SRS>EPSG:26713</SRS>
+ <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N",
+ GEOGCS["NAD27",
+ DATUM["North American Datum 1927",
+ SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]],
+ TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0],
+ AUTHORITY["EPSG","6267"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4267"]],
+ PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]],
+ PARAMETER["central_meridian", -105.0],
+ PARAMETER["latitude_of_origin", 0.0],
+ PARAMETER["scale_factor", 0.9996],
+ PARAMETER["false_easting", 500000.0],
+ PARAMETER["false_northing", 0.0],
+ UNIT["m", 1.0],
+ AXIS["Easting", EAST],
+ AXIS["Northing", NORTH],
+ AUTHORITY["EPSG","26713"]]-->
+ <LatLonBoundingBox minx="-103.88042792817339" miny="44.308776913708805" maxx="-103.62014761945467" maxy="44.56905722242751"/>
+ <BoundingBox SRS="EPSG:26713" minx="589434.8125" miny="4914006.0" maxx="609527.25" maxy="4928377.0"/>
+ <Style>
+ <Name>simple_roads</Name>
+ <Title>Default Styler for simple road segments</Title>
+ <Abstract>Light red line, 2px wide</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=og:roads"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>tike:points</Name>
+ <Title>roads_Type</Title>
+ <Abstract>Generated from tike</Abstract>
+ <KeywordList>
+ <Keyword>tike</Keyword>
+ <Keyword>roads</Keyword>
+ </KeywordList>
+ <SRS>EPSG:4326</SRS>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <LatLonBoundingBox minx="19.73377216339108" miny="59.107116584777835" maxx="31.40053188323972" maxy="70.77387630462647"/>
+ <BoundingBox SRS="EPSG:4326" minx="19.8481521606445" miny="59.8213005065918" maxx="31.2861518859863" maxy="70.0596923828125"/>
+ <Style>
+ <Name>line</Name>
+ <Title>1 px blue line</Title>
+ <Abstract>Default line style, 1 pixel wide blue</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=tike:points"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>topp:bluemarble</Name>
+ <Title>Blue Marble Imagery</Title>
+ <Abstract>Blue Marble NG global bathymetry and topography data from NASA. More information about the Blue Marble NG project is available from http://earthobservatory.nasa.gov/Features/BlueMarble .</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>bluemarble</Keyword>
+ <Keyword>bluemarble</Keyword>
+ </KeywordList>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-180.00000003333" miny="-89.99999996486703" maxx="179.99999993067" maxy="90.000000033333"/>
+ <BoundingBox SRS="EPSG:4326" minx="-180.00000003333" miny="-89.99999996486703" maxx="179.99999993067" maxy="90.000000033333"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=topp:bluemarble"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>nurc:Arc_Sample</Name>
+ <Title>Global annual rainfall</Title>
+ <Abstract>Global annual rainfall in ArcGrid format</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>arcGridSample</Keyword>
+ <Keyword>arcGridSample_Coverage</Keyword>
+ </KeywordList>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+ <BoundingBox SRS="EPSG:4326" minx="-180.0" miny="-90.0" maxx="180.0" maxy="90.0"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Arc_Sample"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>nurc:Img_Sample</Name>
+ <Title>North America sample imagery</Title>
+ <Abstract>A very rough imagery of North America</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>worldImageSample</Keyword>
+ <Keyword>worldImageSample_Coverage</Keyword>
+ </KeywordList>
+ <!--WKT definition of this CRS:
+GEOGCS["WGS 84",
+ DATUM["World Geodetic System 1984",
+ SPHEROID["WGS 84", 6378137.0, 298.257223563, AUTHORITY["EPSG","7030"]],
+ AUTHORITY["EPSG","6326"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4326"]]-->
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-130.85168" miny="20.7052" maxx="-62.0054" maxy="54.1141"/>
+ <BoundingBox SRS="EPSG:4326" minx="-130.85168" miny="20.7052" maxx="-62.0054" maxy="54.1141"/>
+ <Style>
+ <Name>raster</Name>
+ <Title>Raster</Title>
+ <Abstract>A sample style for rasters, good for displaying imagery</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=nurc:Img_Sample"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="1">
+ <Name>sf:sfdem</Name>
+ <Title>Spearfish DEM</Title>
+ <Abstract>Digital Elevation Model data for Spearfish, South Dakota</Abstract>
+ <KeywordList>
+ <Keyword>WCS</Keyword>
+ <Keyword>sf</Keyword>
+ <Keyword>dem</Keyword>
+ <Keyword>digital</Keyword>
+ <Keyword>elevation</Keyword>
+ <Keyword>model</Keyword>
+ </KeywordList>
+ <!--WKT definition of this CRS:
+PROJCS["NAD27 / UTM zone 13N",
+ GEOGCS["NAD27",
+ DATUM["North American Datum 1927",
+ SPHEROID["Clarke 1866", 6378206.4, 294.9786982138982, AUTHORITY["EPSG","7008"]],
+ TOWGS84[-4.2, 135.4, 181.9, 0.0, 0.0, 0.0, 0.0],
+ AUTHORITY["EPSG","6267"]],
+ PRIMEM["Greenwich", 0.0, AUTHORITY["EPSG","8901"]],
+ UNIT["degree", 0.017453292519943295],
+ AXIS["Geodetic longitude", EAST],
+ AXIS["Geodetic latitude", NORTH],
+ AUTHORITY["EPSG","4267"]],
+ PROJECTION["Transverse Mercator", AUTHORITY["EPSG","9807"]],
+ PARAMETER["central_meridian", -105.0],
+ PARAMETER["latitude_of_origin", 0.0],
+ PARAMETER["scale_factor", 0.9996],
+ PARAMETER["false_easting", 500000.0],
+ PARAMETER["false_northing", 0.0],
+ UNIT["m", 1.0],
+ AXIS["Easting", EAST],
+ AXIS["Northing", NORTH],
+ AUTHORITY["EPSG","26713"]]-->
+ <SRS>EPSG:26713</SRS>
+ <LatLonBoundingBox minx="-103.87108701853181" miny="44.370187074132616" maxx="-103.62940739432703" maxy="44.5016011535299"/>
+ <BoundingBox SRS="EPSG:26713" minx="589980.0" miny="4913700.0" maxx="609000.0" maxy="4928010.0"/>
+ <Style>
+ <Name>dem</Name>
+ <Title>Simple DEM style</Title>
+ <Abstract>Classic elevation color progression</Abstract>
+ <LegendURL width="20" height="20">
+ <Format>image/png</Format>
+ <OnlineResource xmlns:xlink="http://www.w3.org/1999/xlink" xlink:type="simple" xlink:href="http://demo.opengeo.org/geoserver/wms/GetLegendGraphic?VERSION=1.0.0&amp;FORMAT=image/png&amp;WIDTH=20&amp;HEIGHT=20&amp;LAYER=sf:sfdem"/>
+ </LegendURL>
+ </Style>
+ </Layer>
+ <Layer queryable="0">
+ <Name>tasmania</Name>
+ <Title>tasmania</Title>
+ <Abstract>Layer-Group type layer: tasmania</Abstract>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>
+ <BoundingBox SRS="EPSG:4326" minx="143.83482400000003" miny="-43.648056" maxx="148.47914100000003" maxy="-39.573891"/>
+ </Layer>
+ <Layer queryable="0">
+ <Name>tiger-ny</Name>
+ <Title>tiger-ny</Title>
+ <Abstract>Layer-Group type layer: tiger-ny</Abstract>
+ <SRS>EPSG:4326</SRS>
+ <LatLonBoundingBox minx="-74.047185" miny="40.679648" maxx="-73.907005" maxy="40.882078"/>
+ <BoundingBox SRS="EPSG:4326" minx="-74.047185" miny="40.679648" maxx="-73.907005" maxy="40.882078"/>
+ </Layer>
+ </Layer>
+ </Capability>
+</WMT_MS_Capabilities>
diff --git a/misc/openlayers/tests/throws.js b/misc/openlayers/tests/throws.js
new file mode 100644
index 0000000..0e2ac9a
--- /dev/null
+++ b/misc/openlayers/tests/throws.js
@@ -0,0 +1,82 @@
+/*
+
+ throws.js -- Adds a `throws_` method to AnotherWay test objects.
+
+ Copyright 2005 OpenLayers Contributors. released under the BSD License.
+
+
+ A reference to this file needs to be added to `run-tests.html` in the
+ head element after the AnotherWay classes are created:
+
+ <script type="text/javascript" src="throws.js"></script>
+
+ Then, it can be used just like the `ok`, `fail` and other such methods
+ in your unit tests.
+
+ e.g.
+
+ t.throws_(function () {new OpenLayers.View.Map.Dynamic();},
+ ReferenceError("No container supplied."),
+ "OpenLayers.View.Map.Dynamic instantiation with no container "
+ + "must throw.");
+
+ This was inspired by the `assertRaises` method of Python's unittest
+ library.
+
+ Possible future enhancements:
+
+ * Contribute to official AnotherWay distribution.
+ * Use `apply` rather than require a inner function (or as an option).
+ * Preserve the stack fields.
+
+ */
+
+Test.AnotherWay._test_object_t.prototype.throws_ =
+function (fn, expectedException, doc) {
+ /*
+
+ Executes the supplied function object catching any exception(s)
+ thrown, then verifies the supplied expected exception occurred.
+
+ If no exception is thrown the test fails.
+
+ If an exception is thrown and it does not match the supplied
+ expected exception the test fails.
+
+ If the exception thrown matches the supplied expected exception
+ the test passes.
+
+ Two exceptions "match" if Test.AnotherWay's `eq` method considers
+ the two equal when their respective stacks are ignored.
+
+ fn - The function object to be executed
+ expectedException - The exception object expected to result
+ doc - Description of the test
+
+ Note: The name of this method is `throws_` (with a trailing
+ underscore) as `throws` is a reserved identifier and can
+ not be used as a method name.
+
+ Note: This function does not preserve the stack field associated
+ with either exception.
+
+ */
+ var theCaughtException = null;
+
+ try {
+ fn();
+ } catch (innerCaughtException) {
+ // As `innerCaughtException` is not visible outside the scope
+ // of this `catch` block we need to make it visible explicitly.
+ theCaughtException = innerCaughtException;
+ }
+
+ if (theCaughtException) {
+ // We delete the stacks before comparison as they will never match.
+ delete theCaughtException.stack;
+ delete expectedException.stack;
+ this.eq(theCaughtException, expectedException, doc);
+ } else {
+ this.fail(doc);
+ }
+};
diff --git a/misc/openlayers/theme/default/google.css b/misc/openlayers/theme/default/google.css
new file mode 100644
index 0000000..1b748ef
--- /dev/null
+++ b/misc/openlayers/theme/default/google.css
@@ -0,0 +1,9 @@
+.olLayerGoogleCopyright {
+ right: 3px;
+ bottom: 2px;
+ left: auto;
+}
+.olLayerGooglePoweredBy {
+ left: 2px;
+ bottom: 2px;
+}
diff --git a/misc/openlayers/theme/default/google.tidy.css b/misc/openlayers/theme/default/google.tidy.css
new file mode 100644
index 0000000..c0e07ac
--- /dev/null
+++ b/misc/openlayers/theme/default/google.tidy.css
@@ -0,0 +1 @@
+.olLayerGoogleCopyright{right:3px;bottom:2px;left:auto;}.olLayerGooglePoweredBy{left:2px;bottom:2px;} \ No newline at end of file
diff --git a/misc/openlayers/theme/default/ie6-style.css b/misc/openlayers/theme/default/ie6-style.css
new file mode 100644
index 0000000..a0fd7c6
--- /dev/null
+++ b/misc/openlayers/theme/default/ie6-style.css
@@ -0,0 +1,10 @@
+.olControlZoomPanel div {
+ background-image: url(img/zoom-panel-NOALPHA.png);
+}
+.olControlPanPanel div {
+ background-image: url(img/pan-panel-NOALPHA.png);
+}
+.olControlEditingToolbar {
+ width: 200px;
+}
+
diff --git a/misc/openlayers/theme/default/ie6-style.tidy.css b/misc/openlayers/theme/default/ie6-style.tidy.css
new file mode 100644
index 0000000..7a23bbc
--- /dev/null
+++ b/misc/openlayers/theme/default/ie6-style.tidy.css
@@ -0,0 +1 @@
+.olControlZoomPanel div{background-image:url(img/zoom-panel-NOALPHA.png);}.olControlPanPanel div{background-image:url(img/pan-panel-NOALPHA.png);}.olControlEditingToolbar{width:200px;} \ No newline at end of file
diff --git a/misc/openlayers/theme/default/img/add_point_off.png b/misc/openlayers/theme/default/img/add_point_off.png
new file mode 100644
index 0000000..26c0233
--- /dev/null
+++ b/misc/openlayers/theme/default/img/add_point_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/add_point_on.png b/misc/openlayers/theme/default/img/add_point_on.png
new file mode 100644
index 0000000..1294a2c
--- /dev/null
+++ b/misc/openlayers/theme/default/img/add_point_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/blank.gif b/misc/openlayers/theme/default/img/blank.gif
new file mode 100644
index 0000000..4bcc753
--- /dev/null
+++ b/misc/openlayers/theme/default/img/blank.gif
Binary files differ
diff --git a/misc/openlayers/theme/default/img/close.gif b/misc/openlayers/theme/default/img/close.gif
new file mode 100644
index 0000000..a8958de
--- /dev/null
+++ b/misc/openlayers/theme/default/img/close.gif
Binary files differ
diff --git a/misc/openlayers/theme/default/img/drag-rectangle-off.png b/misc/openlayers/theme/default/img/drag-rectangle-off.png
new file mode 100644
index 0000000..382a81d
--- /dev/null
+++ b/misc/openlayers/theme/default/img/drag-rectangle-off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/drag-rectangle-on.png b/misc/openlayers/theme/default/img/drag-rectangle-on.png
new file mode 100644
index 0000000..2ed2d5b
--- /dev/null
+++ b/misc/openlayers/theme/default/img/drag-rectangle-on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/draw_line_off.png b/misc/openlayers/theme/default/img/draw_line_off.png
new file mode 100644
index 0000000..a4d67b3
--- /dev/null
+++ b/misc/openlayers/theme/default/img/draw_line_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/draw_line_on.png b/misc/openlayers/theme/default/img/draw_line_on.png
new file mode 100644
index 0000000..90dcf3e
--- /dev/null
+++ b/misc/openlayers/theme/default/img/draw_line_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/draw_point_off.png b/misc/openlayers/theme/default/img/draw_point_off.png
new file mode 100644
index 0000000..5633407
--- /dev/null
+++ b/misc/openlayers/theme/default/img/draw_point_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/draw_point_on.png b/misc/openlayers/theme/default/img/draw_point_on.png
new file mode 100644
index 0000000..fff50b7
--- /dev/null
+++ b/misc/openlayers/theme/default/img/draw_point_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/draw_polygon_off.png b/misc/openlayers/theme/default/img/draw_polygon_off.png
new file mode 100644
index 0000000..917af35
--- /dev/null
+++ b/misc/openlayers/theme/default/img/draw_polygon_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/draw_polygon_on.png b/misc/openlayers/theme/default/img/draw_polygon_on.png
new file mode 100644
index 0000000..05a2cc5
--- /dev/null
+++ b/misc/openlayers/theme/default/img/draw_polygon_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/editing_tool_bar.png b/misc/openlayers/theme/default/img/editing_tool_bar.png
new file mode 100644
index 0000000..5977856
--- /dev/null
+++ b/misc/openlayers/theme/default/img/editing_tool_bar.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/move_feature_off.png b/misc/openlayers/theme/default/img/move_feature_off.png
new file mode 100644
index 0000000..ed4472d
--- /dev/null
+++ b/misc/openlayers/theme/default/img/move_feature_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/move_feature_on.png b/misc/openlayers/theme/default/img/move_feature_on.png
new file mode 100644
index 0000000..62226a2
--- /dev/null
+++ b/misc/openlayers/theme/default/img/move_feature_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/navigation_history.png b/misc/openlayers/theme/default/img/navigation_history.png
new file mode 100644
index 0000000..84e3489
--- /dev/null
+++ b/misc/openlayers/theme/default/img/navigation_history.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/overview_replacement.gif b/misc/openlayers/theme/default/img/overview_replacement.gif
new file mode 100644
index 0000000..a82cf5f
--- /dev/null
+++ b/misc/openlayers/theme/default/img/overview_replacement.gif
Binary files differ
diff --git a/misc/openlayers/theme/default/img/pan-panel-NOALPHA.png b/misc/openlayers/theme/default/img/pan-panel-NOALPHA.png
new file mode 100644
index 0000000..6987268
--- /dev/null
+++ b/misc/openlayers/theme/default/img/pan-panel-NOALPHA.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/pan-panel.png b/misc/openlayers/theme/default/img/pan-panel.png
new file mode 100644
index 0000000..dfe6748
--- /dev/null
+++ b/misc/openlayers/theme/default/img/pan-panel.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/pan_off.png b/misc/openlayers/theme/default/img/pan_off.png
new file mode 100644
index 0000000..30b2aed
--- /dev/null
+++ b/misc/openlayers/theme/default/img/pan_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/pan_on.png b/misc/openlayers/theme/default/img/pan_on.png
new file mode 100644
index 0000000..e3953a8
--- /dev/null
+++ b/misc/openlayers/theme/default/img/pan_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/panning-hand-off.png b/misc/openlayers/theme/default/img/panning-hand-off.png
new file mode 100644
index 0000000..d1c593e
--- /dev/null
+++ b/misc/openlayers/theme/default/img/panning-hand-off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/panning-hand-on.png b/misc/openlayers/theme/default/img/panning-hand-on.png
new file mode 100644
index 0000000..9b7e064
--- /dev/null
+++ b/misc/openlayers/theme/default/img/panning-hand-on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/remove_point_off.png b/misc/openlayers/theme/default/img/remove_point_off.png
new file mode 100644
index 0000000..76c8606
--- /dev/null
+++ b/misc/openlayers/theme/default/img/remove_point_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/remove_point_on.png b/misc/openlayers/theme/default/img/remove_point_on.png
new file mode 100644
index 0000000..cc8d7b2
--- /dev/null
+++ b/misc/openlayers/theme/default/img/remove_point_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/ruler.png b/misc/openlayers/theme/default/img/ruler.png
new file mode 100644
index 0000000..aa4883b
--- /dev/null
+++ b/misc/openlayers/theme/default/img/ruler.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/save_features_off.png b/misc/openlayers/theme/default/img/save_features_off.png
new file mode 100644
index 0000000..3d305b6
--- /dev/null
+++ b/misc/openlayers/theme/default/img/save_features_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/save_features_on.png b/misc/openlayers/theme/default/img/save_features_on.png
new file mode 100644
index 0000000..5640ae8
--- /dev/null
+++ b/misc/openlayers/theme/default/img/save_features_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/view_next_off.png b/misc/openlayers/theme/default/img/view_next_off.png
new file mode 100644
index 0000000..9149a24
--- /dev/null
+++ b/misc/openlayers/theme/default/img/view_next_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/view_next_on.png b/misc/openlayers/theme/default/img/view_next_on.png
new file mode 100644
index 0000000..e41fb7b
--- /dev/null
+++ b/misc/openlayers/theme/default/img/view_next_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/view_previous_off.png b/misc/openlayers/theme/default/img/view_previous_off.png
new file mode 100644
index 0000000..8a9ef21
--- /dev/null
+++ b/misc/openlayers/theme/default/img/view_previous_off.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/view_previous_on.png b/misc/openlayers/theme/default/img/view_previous_on.png
new file mode 100644
index 0000000..c009c25
--- /dev/null
+++ b/misc/openlayers/theme/default/img/view_previous_on.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/zoom-panel-NOALPHA.png b/misc/openlayers/theme/default/img/zoom-panel-NOALPHA.png
new file mode 100644
index 0000000..cdde6fc
--- /dev/null
+++ b/misc/openlayers/theme/default/img/zoom-panel-NOALPHA.png
Binary files differ
diff --git a/misc/openlayers/theme/default/img/zoom-panel.png b/misc/openlayers/theme/default/img/zoom-panel.png
new file mode 100644
index 0000000..c91a4ef
--- /dev/null
+++ b/misc/openlayers/theme/default/img/zoom-panel.png
Binary files differ
diff --git a/misc/openlayers/theme/default/style.css b/misc/openlayers/theme/default/style.css
new file mode 100644
index 0000000..cbed84e
--- /dev/null
+++ b/misc/openlayers/theme/default/style.css
@@ -0,0 +1,516 @@
+div.olMap {
+ z-index: 0;
+ padding: 0 !important;
+ margin: 0 !important;
+ cursor: default;
+}
+
+div.olMapViewport {
+ text-align: left;
+ -ms-touch-action: none;
+}
+
+div.olLayerDiv {
+ -moz-user-select: none;
+ -khtml-user-select: none;
+}
+
+.olLayerGoogleCopyright {
+ left: 2px;
+ bottom: 2px;
+}
+.olLayerGoogleV3.olLayerGoogleCopyright {
+ right: auto !important;
+}
+.olLayerGooglePoweredBy {
+ left: 2px;
+ bottom: 15px;
+}
+.olLayerGoogleV3.olLayerGooglePoweredBy {
+ bottom: 15px !important;
+}
+/* GMaps should not set styles on its container */
+.olForeignContainer {
+ opacity: 1 !important;
+}
+.olControlAttribution {
+ font-size: smaller;
+ right: 3px;
+ bottom: 4.5em;
+ position: absolute;
+ display: block;
+}
+.olControlScale {
+ right: 3px;
+ bottom: 3em;
+ display: block;
+ position: absolute;
+ font-size: smaller;
+}
+.olControlScaleLine {
+ display: block;
+ position: absolute;
+ left: 10px;
+ bottom: 15px;
+ font-size: xx-small;
+}
+.olControlScaleLineBottom {
+ border: solid 2px black;
+ border-bottom: none;
+ margin-top:-2px;
+ text-align: center;
+}
+.olControlScaleLineTop {
+ border: solid 2px black;
+ border-top: none;
+ text-align: center;
+}
+
+.olControlPermalink {
+ right: 3px;
+ bottom: 1.5em;
+ display: block;
+ position: absolute;
+ font-size: smaller;
+}
+
+div.olControlMousePosition {
+ bottom: 0;
+ right: 3px;
+ display: block;
+ position: absolute;
+ font-family: Arial;
+ font-size: smaller;
+}
+
+.olControlOverviewMapContainer {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+}
+
+.olControlOverviewMapElement {
+ padding: 10px 18px 10px 10px;
+ background-color: #00008B;
+ -moz-border-radius: 1em 0 0 0;
+}
+
+.olControlOverviewMapMinimizeButton,
+.olControlOverviewMapMaximizeButton {
+ height: 18px;
+ width: 18px;
+ right: 0;
+ bottom: 80px;
+ cursor: pointer;
+}
+
+.olControlOverviewMapExtentRectangle {
+ overflow: hidden;
+ background-image: url("img/blank.gif");
+ cursor: move;
+ border: 2px dotted red;
+}
+.olControlOverviewMapRectReplacement {
+ overflow: hidden;
+ cursor: move;
+ background-image: url("img/overview_replacement.gif");
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+.olLayerGeoRSSDescription {
+ float:left;
+ width:100%;
+ overflow:auto;
+ font-size:1.0em;
+}
+.olLayerGeoRSSClose {
+ float:right;
+ color:gray;
+ font-size:1.2em;
+ margin-right:6px;
+ font-family:sans-serif;
+}
+.olLayerGeoRSSTitle {
+ float:left;font-size:1.2em;
+}
+
+.olPopupContent {
+ padding:5px;
+ overflow: auto;
+}
+
+.olControlNavigationHistory {
+ background-image: url("img/navigation_history.png");
+ background-repeat: no-repeat;
+ width: 24px;
+ height: 24px;
+
+}
+.olControlNavigationHistoryPreviousItemActive {
+ background-position: 0 0;
+}
+.olControlNavigationHistoryPreviousItemInactive {
+ background-position: 0 -24px;
+}
+.olControlNavigationHistoryNextItemActive {
+ background-position: -24px 0;
+}
+.olControlNavigationHistoryNextItemInactive {
+ background-position: -24px -24px;
+}
+
+div.olControlSaveFeaturesItemActive {
+ background-image: url(img/save_features_on.png);
+ background-repeat: no-repeat;
+ background-position: 0 1px;
+}
+div.olControlSaveFeaturesItemInactive {
+ background-image: url(img/save_features_off.png);
+ background-repeat: no-repeat;
+ background-position: 0 1px;
+}
+
+.olHandlerBoxZoomBox {
+ border: 2px solid red;
+ position: absolute;
+ background-color: white;
+ opacity: 0.50;
+ font-size: 1px;
+ filter: alpha(opacity=50);
+}
+.olHandlerBoxSelectFeature {
+ border: 2px solid blue;
+ position: absolute;
+ background-color: white;
+ opacity: 0.50;
+ font-size: 1px;
+ filter: alpha(opacity=50);
+}
+
+.olControlPanPanel {
+ top: 10px;
+ left: 5px;
+}
+
+.olControlPanPanel div {
+ background-image: url(img/pan-panel.png);
+ height: 18px;
+ width: 18px;
+ cursor: pointer;
+ position: absolute;
+}
+
+.olControlPanPanel .olControlPanNorthItemInactive {
+ top: 0;
+ left: 9px;
+ background-position: 0 0;
+}
+.olControlPanPanel .olControlPanSouthItemInactive {
+ top: 36px;
+ left: 9px;
+ background-position: 18px 0;
+}
+.olControlPanPanel .olControlPanWestItemInactive {
+ position: absolute;
+ top: 18px;
+ left: 0;
+ background-position: 0 18px;
+}
+.olControlPanPanel .olControlPanEastItemInactive {
+ top: 18px;
+ left: 18px;
+ background-position: 18px 18px;
+}
+
+.olControlZoomPanel {
+ top: 71px;
+ left: 14px;
+}
+
+.olControlZoomPanel div {
+ background-image: url(img/zoom-panel.png);
+ position: absolute;
+ height: 18px;
+ width: 18px;
+ cursor: pointer;
+}
+
+.olControlZoomPanel .olControlZoomInItemInactive {
+ top: 0;
+ left: 0;
+ background-position: 0 0;
+}
+
+.olControlZoomPanel .olControlZoomToMaxExtentItemInactive {
+ top: 18px;
+ left: 0;
+ background-position: 0 -18px;
+}
+
+.olControlZoomPanel .olControlZoomOutItemInactive {
+ top: 36px;
+ left: 0;
+ background-position: 0 18px;
+}
+
+/*
+ * When a potential text is bigger than the image it move the image
+ * with some headers (closes #3154)
+ */
+.olControlPanZoomBar div {
+ font-size: 1px;
+}
+
+.olPopupCloseBox {
+ background: url("img/close.gif") no-repeat;
+ cursor: pointer;
+}
+
+.olFramedCloudPopupContent {
+ padding: 5px;
+ overflow: auto;
+}
+
+.olControlNoSelect {
+ -moz-user-select: none;
+ -khtml-user-select: none;
+}
+
+.olImageLoadError {
+ background-color: pink;
+ opacity: 0.5;
+ filter: alpha(opacity=50); /* IE */
+}
+
+/**
+ * Cursor styles
+ */
+
+.olCursorWait {
+ cursor: wait;
+}
+.olDragDown {
+ cursor: move;
+}
+.olDrawBox {
+ cursor: crosshair;
+}
+.olControlDragFeatureOver {
+ cursor: move;
+}
+.olControlDragFeatureActive.olControlDragFeatureOver.olDragDown {
+ cursor: -moz-grabbing;
+}
+
+/**
+ * Layer switcher
+ */
+.olControlLayerSwitcher {
+ position: absolute;
+ top: 25px;
+ right: 0;
+ width: 20em;
+ font-family: sans-serif;
+ font-weight: bold;
+ margin-top: 3px;
+ margin-left: 3px;
+ margin-bottom: 3px;
+ font-size: smaller;
+ color: white;
+ background-color: transparent;
+}
+
+.olControlLayerSwitcher .layersDiv {
+ padding-top: 5px;
+ padding-left: 10px;
+ padding-bottom: 5px;
+ padding-right: 10px;
+ background-color: darkblue;
+}
+
+.olControlLayerSwitcher .layersDiv .baseLbl,
+.olControlLayerSwitcher .layersDiv .dataLbl {
+ margin-top: 3px;
+ margin-left: 3px;
+ margin-bottom: 3px;
+}
+
+.olControlLayerSwitcher .layersDiv .baseLayersDiv,
+.olControlLayerSwitcher .layersDiv .dataLayersDiv {
+ padding-left: 10px;
+}
+
+.olControlLayerSwitcher .maximizeDiv,
+.olControlLayerSwitcher .minimizeDiv {
+ width: 18px;
+ height: 18px;
+ top: 5px;
+ right: 0;
+ cursor: pointer;
+}
+
+.olBingAttribution {
+ color: #DDD;
+}
+.olBingAttribution.road {
+ color: #333;
+}
+
+.olGoogleAttribution.hybrid, .olGoogleAttribution.satellite {
+ color: #EEE;
+}
+.olGoogleAttribution {
+ color: #333;
+}
+span.olGoogleAttribution a {
+ color: #77C;
+}
+span.olGoogleAttribution.hybrid a, span.olGoogleAttribution.satellite a {
+ color: #EEE;
+}
+
+/**
+ * Editing and navigation icons.
+ * (using the editing_tool_bar.png sprint image)
+ */
+.olControlNavToolbar ,
+.olControlEditingToolbar {
+ margin: 5px 5px 0 0;
+}
+.olControlNavToolbar div,
+.olControlEditingToolbar div {
+ background-image: url("img/editing_tool_bar.png");
+ background-repeat: no-repeat;
+ margin: 0 0 5px 5px;
+ width: 24px;
+ height: 22px;
+ cursor: pointer
+}
+/* positions */
+.olControlEditingToolbar {
+ right: 0;
+ top: 0;
+}
+.olControlNavToolbar {
+ top: 295px;
+ left: 9px;
+}
+/* layouts */
+.olControlEditingToolbar div {
+ float: right;
+}
+/* individual controls */
+.olControlNavToolbar .olControlNavigationItemInactive,
+.olControlEditingToolbar .olControlNavigationItemInactive {
+ background-position: -103px -1px;
+}
+.olControlNavToolbar .olControlNavigationItemActive ,
+.olControlEditingToolbar .olControlNavigationItemActive {
+ background-position: -103px -24px;
+}
+.olControlNavToolbar .olControlZoomBoxItemInactive {
+ background-position: -128px -1px;
+}
+.olControlNavToolbar .olControlZoomBoxItemActive {
+ background-position: -128px -24px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePointItemInactive {
+ background-position: -77px -1px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePointItemActive {
+ background-position: -77px -24px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePathItemInactive {
+ background-position: -51px -1px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePathItemActive {
+ background-position: -51px -24px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive{
+ background-position: -26px -1px;
+}
+.olControlEditingToolbar .olControlDrawFeaturePolygonItemActive {
+ background-position: -26px -24px;
+}
+
+div.olControlZoom {
+ position: absolute;
+ top: 8px;
+ left: 8px;
+ background: rgba(255,255,255,0.4);
+ border-radius: 4px;
+ padding: 2px;
+}
+div.olControlZoom a {
+ display: block;
+ margin: 1px;
+ padding: 0;
+ color: white;
+ font-size: 18px;
+ font-family: 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ text-decoration: none;
+ text-align: center;
+ height: 22px;
+ width:22px;
+ line-height: 19px;
+ background: #130085; /* fallback for IE - IE6 requires background shorthand*/
+ background: rgba(0, 60, 136, 0.5);
+ filter: alpha(opacity=80);
+}
+div.olControlZoom a:hover {
+ background: #130085; /* fallback for IE */
+ background: rgba(0, 60, 136, 0.7);
+ filter: alpha(opacity=100);
+}
+@media only screen and (max-width: 600px) {
+ div.olControlZoom a:hover {
+ background: rgba(0, 60, 136, 0.5);
+ }
+}
+a.olControlZoomIn {
+ border-radius: 4px 4px 0 0;
+}
+a.olControlZoomOut {
+ border-radius: 0 0 4px 4px;
+}
+
+
+/**
+ * Animations
+ */
+
+.olLayerGrid .olTileImage {
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ -o-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+}
+
+/* Turn on GPU support where available */
+.olTileImage {
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -ms-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-perspective: 1000;
+ -moz-perspective: 1000;
+ -ms-perspective: 1000;
+ perspective: 1000;
+}
+
+/* when replacing tiles, do not show tile and backbuffer at the same time */
+.olTileReplacing {
+ display: none;
+}
+
+/* override any max-width image settings (e.g. bootstrap.css) */
+img.olTileImage {
+ max-width: none;
+}
diff --git a/misc/openlayers/theme/default/style.mobile.css b/misc/openlayers/theme/default/style.mobile.css
new file mode 100644
index 0000000..92e7d00
--- /dev/null
+++ b/misc/openlayers/theme/default/style.mobile.css
@@ -0,0 +1,70 @@
+div.olControlZoom {
+ position: absolute;
+ top: 8px;
+ left: 8px;
+ background: rgba(255,255,255,0.4);
+ border-radius: 4px;
+ padding: 2px;
+}
+* {
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+div.olControlZoom a {
+ display: block;
+ margin: 1px;
+ padding: 0;
+ color: white;
+ font-size: 28px;
+ font-family: sans-serif;
+ font-weight: bold;
+ text-decoration: none;
+ text-align: center;
+ height: 32px;
+ width: 32px;
+ line-height: 28px;
+ text-shadow: 0 0 3px rgba(0,0,0,0.8);
+ background: #130085; /* fallback for IE - IE6 requires background shorthand*/
+ background: rgba(0, 60, 136, 0.5);
+ filter: alpha(opacity=80);
+}
+a.olControlZoomIn {
+ border-radius: 4px 4px 0 0;
+}
+a.olControlZoomOut {
+ border-radius: 0 0 4px 4px;
+}
+div.olControlZoom a:hover {
+ background: #130085; /* fallback for IE */
+ background: rgba(0, 60, 136, 0.7);
+ filter: alpha(opacity=100);
+}
+@media only screen and (max-width: 600px) {
+ div.olControlZoom a:hover {
+ background: rgba(0, 60, 136, 0.5);
+ }
+}
+div.olMapViewport {
+ -ms-touch-action: none;
+}
+.olLayerGrid .olTileImage {
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ -o-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+}
+/* Turn on GPU support where available */
+.olTileImage {
+ -webkit-transform: translateZ(0);
+ -moz-transform: translateZ(0);
+ -o-transform: translateZ(0);
+ -ms-transform: translateZ(0);
+ transform: translateZ(0);
+ -webkit-backface-visibility: hidden;
+ -moz-backface-visibility: hidden;
+ -ms-backface-visibility: hidden;
+ backface-visibility: hidden;
+ -webkit-perspective: 1000;
+ -moz-perspective: 1000;
+ -ms-perspective: 1000;
+ perspective: 1000;
+}
diff --git a/misc/openlayers/theme/default/style.mobile.tidy.css b/misc/openlayers/theme/default/style.mobile.tidy.css
new file mode 100644
index 0000000..bf7eeaf
--- /dev/null
+++ b/misc/openlayers/theme/default/style.mobile.tidy.css
@@ -0,0 +1 @@
+div.olControlZoom{position:absolute;top:8px;left:8px;background:rgba(255,255,255,0.4);border-radius:4px;padding:2px;}*{-webkit-tap-highlight-color:rgba(0,0,0,0);}div.olControlZoom a{display:block;color:#FFF;font-size:28px;font-family:sans-serif;font-weight:700;text-decoration:none;text-align:center;height:32px;width:32px;line-height:28px;text-shadow:0 0 3px rgba(0,0,0,0.8);background:rgba(0,60,136,0.5);filter:alpha(opacity=80);margin:1px;padding:0;}a.olControlZoomIn{border-radius:4px 4px 0 0;}a.olControlZoomOut{border-radius:0 0 4px 4px;}div.olControlZoom a:hover{background:rgba(0,60,136,0.7);filter:alpha(opacity=100);}div.olMapViewport{-ms-touch-action:none;}.olLayerGrid .olTileImage{-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;-o-transition:opacity .2s linear;transition:opacity .2s linear;}.olTileImage{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-o-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;-moz-perspective:1000;-ms-perspective:1000;perspective:1000;}@media only screen and max-width 600px{div.olControlZoom a:hover{background:rgba(0,60,136,0.5);}} \ No newline at end of file
diff --git a/misc/openlayers/theme/default/style.tidy.css b/misc/openlayers/theme/default/style.tidy.css
new file mode 100644
index 0000000..f973d45
--- /dev/null
+++ b/misc/openlayers/theme/default/style.tidy.css
@@ -0,0 +1 @@
+div.olMap{z-index:0;cursor:default;margin:0!important;padding:0!important;}div.olMapViewport{text-align:left;-ms-touch-action:none;}.olLayerGoogleCopyright{left:2px;bottom:2px;}.olLayerGoogleV3.olLayerGoogleCopyright{right:auto!important;}.olLayerGooglePoweredBy{left:2px;bottom:15px;}.olLayerGoogleV3.olLayerGooglePoweredBy{bottom:15px!important;}.olForeignContainer{opacity:1!important;}.olControlAttribution{font-size:smaller;right:3px;bottom:4.5em;position:absolute;display:block;}.olControlScale{right:3px;bottom:3em;display:block;position:absolute;font-size:smaller;}.olControlScaleLine{display:block;position:absolute;left:10px;bottom:15px;font-size:xx-small;}.olControlScaleLineBottom{border:solid 2px #000;border-bottom:none;margin-top:-2px;text-align:center;}.olControlScaleLineTop{border:solid 2px #000;border-top:none;text-align:center;}.olControlPermalink{right:3px;bottom:1.5em;display:block;position:absolute;font-size:smaller;}div.olControlMousePosition{bottom:0;right:3px;display:block;position:absolute;font-family:Arial;font-size:smaller;}.olControlOverviewMapContainer{position:absolute;bottom:0;right:0;}.olControlOverviewMapElement{background-color:#00008B;-moz-border-radius:1em 0 0;padding:10px 18px 10px 10px;}.olControlOverviewMapMinimizeButton,.olControlOverviewMapMaximizeButton{height:18px;width:18px;right:0;bottom:80px;cursor:pointer;}.olControlOverviewMapExtentRectangle{overflow:hidden;background-image:url(img/blank.gif);cursor:move;border:2px dotted red;}.olControlOverviewMapRectReplacement{overflow:hidden;cursor:move;background-image:url(img/overview_replacement.gif);background-repeat:no-repeat;background-position:center;}.olLayerGeoRSSDescription{float:left;width:100%;overflow:auto;font-size:1em;}.olLayerGeoRSSClose{float:right;color:gray;font-size:1.2em;margin-right:6px;font-family:sans-serif;}.olLayerGeoRSSTitle{float:left;font-size:1.2em;}.olControlNavigationHistory{background-image:url(img/navigation_history.png);background-repeat:no-repeat;width:24px;height:24px;}.olControlNavigationHistoryPreviousItemActive{background-position:0 0;}.olControlNavigationHistoryPreviousItemInactive{background-position:0 -24px;}.olControlNavigationHistoryNextItemActive{background-position:-24px 0;}.olControlNavigationHistoryNextItemInactive{background-position:-24px -24px;}div.olControlSaveFeaturesItemActive{background-image:url(img/save_features_on.png);background-repeat:no-repeat;background-position:0 1px;}div.olControlSaveFeaturesItemInactive{background-image:url(img/save_features_off.png);background-repeat:no-repeat;background-position:0 1px;}.olHandlerBoxZoomBox{border:2px solid red;position:absolute;background-color:#FFF;opacity:.5;font-size:1px;filter:alpha(opacity=50);}.olHandlerBoxSelectFeature{border:2px solid blue;position:absolute;background-color:#FFF;opacity:.5;font-size:1px;filter:alpha(opacity=50);}.olControlPanPanel{top:10px;left:5px;}.olControlPanPanel div{background-image:url(img/pan-panel.png);height:18px;width:18px;cursor:pointer;position:absolute;}.olControlPanPanel .olControlPanNorthItemInactive{top:0;left:9px;background-position:0 0;}.olControlPanPanel .olControlPanSouthItemInactive{top:36px;left:9px;background-position:18px 0;}.olControlPanPanel .olControlPanWestItemInactive{position:absolute;top:18px;left:0;background-position:0 18px;}.olControlPanPanel .olControlPanEastItemInactive{top:18px;left:18px;background-position:18px 18px;}.olControlZoomPanel{top:71px;left:14px;}.olControlZoomPanel div{background-image:url(img/zoom-panel.png);position:absolute;height:18px;width:18px;cursor:pointer;}.olControlZoomPanel .olControlZoomInItemInactive{top:0;left:0;background-position:0 0;}.olControlZoomPanel .olControlZoomToMaxExtentItemInactive{top:18px;left:0;background-position:0 -18px;}.olControlZoomPanel .olControlZoomOutItemInactive{top:36px;left:0;background-position:0 18px;}.olControlPanZoomBar div{font-size:1px;}.olPopupCloseBox{background:url(img/close.gif) no-repeat;cursor:pointer;}.olImageLoadError{background-color:#FFC0CB;opacity:.5;filter:alpha(opacity=50);}.olCursorWait{cursor:wait;}.olDrawBox{cursor:crosshair;}.olControlDragFeatureActive.olControlDragFeatureOver.olDragDown{cursor:0;}.olControlLayerSwitcher{position:absolute;top:25px;right:0;width:20em;font-family:sans-serif;font-weight:700;margin-top:3px;margin-left:3px;margin-bottom:3px;font-size:smaller;color:#FFF;background-color:transparent;}.olControlLayerSwitcher .layersDiv{background-color:#00008B;padding:5px 10px;}.olControlLayerSwitcher .layersDiv .baseLbl,.olControlLayerSwitcher .layersDiv .dataLbl{margin-top:3px;margin-left:3px;margin-bottom:3px;}.olControlLayerSwitcher .layersDiv .baseLayersDiv,.olControlLayerSwitcher .layersDiv .dataLayersDiv{padding-left:10px;}.olControlLayerSwitcher .maximizeDiv,.olControlLayerSwitcher .minimizeDiv{width:18px;height:18px;top:5px;right:0;cursor:pointer;}.olBingAttribution{color:#DDD;}span.olGoogleAttribution a{color:#77C;}.olControlNavToolbar,.olControlEditingToolbar{margin:5px 5px 0 0;}.olControlNavToolbar div,.olControlEditingToolbar div{background-image:url(img/editing_tool_bar.png);background-repeat:no-repeat;width:24px;height:22px;cursor:pointer;margin:0 0 5px 5px;}.olControlEditingToolbar{right:0;top:0;}.olControlNavToolbar{top:295px;left:9px;}.olControlEditingToolbar div{float:right;}.olControlNavToolbar .olControlNavigationItemInactive,.olControlEditingToolbar .olControlNavigationItemInactive{background-position:-103px -1px;}.olControlNavToolbar .olControlNavigationItemActive,.olControlEditingToolbar .olControlNavigationItemActive{background-position:-103px -24px;}.olControlNavToolbar .olControlZoomBoxItemInactive{background-position:-128px -1px;}.olControlNavToolbar .olControlZoomBoxItemActive{background-position:-128px -24px;}.olControlEditingToolbar .olControlDrawFeaturePointItemInactive{background-position:-77px -1px;}.olControlEditingToolbar .olControlDrawFeaturePointItemActive{background-position:-77px -24px;}.olControlEditingToolbar .olControlDrawFeaturePathItemInactive{background-position:-51px -1px;}.olControlEditingToolbar .olControlDrawFeaturePathItemActive{background-position:-51px -24px;}.olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive{background-position:-26px -1px;}.olControlEditingToolbar .olControlDrawFeaturePolygonItemActive{background-position:-26px -24px;}div.olControlZoom{position:absolute;top:8px;left:8px;background:rgba(255,255,255,0.4);border-radius:4px;padding:2px;}div.olControlZoom a{display:block;color:#FFF;font-size:18px;font-family:'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif;font-weight:700;text-decoration:none;text-align:center;height:22px;width:22px;line-height:19px;background:rgba(0,60,136,0.5);filter:alpha(opacity=80);margin:1px;padding:0;}div.olControlZoom a:hover{background:rgba(0,60,136,0.7);filter:alpha(opacity=100);}a.olControlZoomIn{border-radius:4px 4px 0 0;}a.olControlZoomOut{border-radius:0 0 4px 4px;}.olLayerGrid .olTileImage{-webkit-transition:opacity .2s linear;-moz-transition:opacity .2s linear;-o-transition:opacity .2s linear;transition:opacity .2s linear;}.olTileImage{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-o-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;-moz-perspective:1000;-ms-perspective:1000;perspective:1000;}.olTileReplacing{display:none;}img.olTileImage{max-width:none;}div.olLayerDiv,.olControlNoSelect{-khtml-user-select:none;-moz-user-select:none;}.olPopupContent,.olFramedCloudPopupContent{overflow:auto;padding:5px;}.olDragDown,.olControlDragFeatureOver{cursor:move;}.olBingAttribution.road,.olGoogleAttribution{color:#333;}.olGoogleAttribution.hybrid,.olGoogleAttribution.satellite,span.olGoogleAttribution.hybrid a,span.olGoogleAttribution.satellite a{color:#EEE;}@media only screen and max-width 600px{div.olControlZoom a:hover{background:rgba(0,60,136,0.5);}} \ No newline at end of file
diff --git a/misc/openlayers/tools/BeautifulSoup.py b/misc/openlayers/tools/BeautifulSoup.py
new file mode 100644
index 0000000..6ef8ac0
--- /dev/null
+++ b/misc/openlayers/tools/BeautifulSoup.py
@@ -0,0 +1,1767 @@
+"""Beautiful Soup
+Elixir and Tonic
+"The Screen-Scraper's Friend"
+http://www.crummy.com/software/BeautifulSoup/
+
+Beautiful Soup parses a (possibly invalid) XML or HTML document into a
+tree representation. It provides methods and Pythonic idioms that make
+it easy to navigate, search, and modify the tree.
+
+A well-formed XML/HTML document yields a well-formed data
+structure. An ill-formed XML/HTML document yields a correspondingly
+ill-formed data structure. If your document is only locally
+well-formed, you can use this library to find and process the
+well-formed part of it. The BeautifulSoup class
+
+Beautiful Soup works with Python 2.2 and up. It has no external
+dependencies, but you'll have more success at converting data to UTF-8
+if you also install these three packages:
+
+* chardet, for auto-detecting character encodings
+ http://chardet.feedparser.org/
+* cjkcodecs and iconv_codec, which add more encodings to the ones supported
+ by stock Python.
+ http://cjkpython.i18n.org/
+
+Beautiful Soup defines classes for two main parsing strategies:
+
+ * BeautifulStoneSoup, for parsing XML, SGML, or your domain-specific
+ language that kind of looks like XML.
+
+ * BeautifulSoup, for parsing run-of-the-mill HTML code, be it valid
+ or invalid. This class has web browser-like heuristics for
+ obtaining a sensible parse tree in the face of common HTML errors.
+
+Beautiful Soup also defines a class (UnicodeDammit) for autodetecting
+the encoding of an HTML or XML document, and converting it to
+Unicode. Much of this code is taken from Mark Pilgrim's Universal Feed Parser.
+
+For more than you ever wanted to know about Beautiful Soup, see the
+documentation:
+http://www.crummy.com/software/BeautifulSoup/documentation.html
+
+"""
+from __future__ import generators
+
+__author__ = "Leonard Richardson (leonardr@segfault.org)"
+__version__ = "3.0.4"
+__copyright__ = "Copyright (c) 2004-2007 Leonard Richardson"
+__license__ = "PSF"
+
+from sgmllib import SGMLParser, SGMLParseError
+import codecs
+import types
+import re
+import sgmllib
+try:
+ from htmlentitydefs import name2codepoint
+except ImportError:
+ name2codepoint = {}
+
+#This hack makes Beautiful Soup able to parse XML with namespaces
+sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
+
+DEFAULT_OUTPUT_ENCODING = "utf-8"
+
+# First, the classes that represent markup elements.
+
+class PageElement:
+ """Contains the navigational information for some part of the page
+ (either a tag or a piece of text)"""
+
+ def setup(self, parent=None, previous=None):
+ """Sets up the initial relations between this element and
+ other elements."""
+ self.parent = parent
+ self.previous = previous
+ self.next = None
+ self.previousSibling = None
+ self.nextSibling = None
+ if self.parent and self.parent.contents:
+ self.previousSibling = self.parent.contents[-1]
+ self.previousSibling.nextSibling = self
+
+ def replaceWith(self, replaceWith):
+ oldParent = self.parent
+ myIndex = self.parent.contents.index(self)
+ if hasattr(replaceWith, 'parent') and replaceWith.parent == self.parent:
+ # We're replacing this element with one of its siblings.
+ index = self.parent.contents.index(replaceWith)
+ if index and index < myIndex:
+ # Furthermore, it comes before this element. That
+ # means that when we extract it, the index of this
+ # element will change.
+ myIndex = myIndex - 1
+ self.extract()
+ oldParent.insert(myIndex, replaceWith)
+
+ def extract(self):
+ """Destructively rips this element out of the tree."""
+ if self.parent:
+ try:
+ self.parent.contents.remove(self)
+ except ValueError:
+ pass
+
+ #Find the two elements that would be next to each other if
+ #this element (and any children) hadn't been parsed. Connect
+ #the two.
+ lastChild = self._lastRecursiveChild()
+ nextElement = lastChild.next
+
+ if self.previous:
+ self.previous.next = nextElement
+ if nextElement:
+ nextElement.previous = self.previous
+ self.previous = None
+ lastChild.next = None
+
+ self.parent = None
+ if self.previousSibling:
+ self.previousSibling.nextSibling = self.nextSibling
+ if self.nextSibling:
+ self.nextSibling.previousSibling = self.previousSibling
+ self.previousSibling = self.nextSibling = None
+
+ def _lastRecursiveChild(self):
+ "Finds the last element beneath this object to be parsed."
+ lastChild = self
+ while hasattr(lastChild, 'contents') and lastChild.contents:
+ lastChild = lastChild.contents[-1]
+ return lastChild
+
+ def insert(self, position, newChild):
+ if (isinstance(newChild, basestring)
+ or isinstance(newChild, unicode)) \
+ and not isinstance(newChild, NavigableString):
+ newChild = NavigableString(newChild)
+
+ position = min(position, len(self.contents))
+ if hasattr(newChild, 'parent') and newChild.parent != None:
+ # We're 'inserting' an element that's already one
+ # of this object's children.
+ if newChild.parent == self:
+ index = self.find(newChild)
+ if index and index < position:
+ # Furthermore we're moving it further down the
+ # list of this object's children. That means that
+ # when we extract this element, our target index
+ # will jump down one.
+ position = position - 1
+ newChild.extract()
+
+ newChild.parent = self
+ previousChild = None
+ if position == 0:
+ newChild.previousSibling = None
+ newChild.previous = self
+ else:
+ previousChild = self.contents[position-1]
+ newChild.previousSibling = previousChild
+ newChild.previousSibling.nextSibling = newChild
+ newChild.previous = previousChild._lastRecursiveChild()
+ if newChild.previous:
+ newChild.previous.next = newChild
+
+ newChildsLastElement = newChild._lastRecursiveChild()
+
+ if position >= len(self.contents):
+ newChild.nextSibling = None
+
+ parent = self
+ parentsNextSibling = None
+ while not parentsNextSibling:
+ parentsNextSibling = parent.nextSibling
+ parent = parent.parent
+ if not parent: # This is the last element in the document.
+ break
+ if parentsNextSibling:
+ newChildsLastElement.next = parentsNextSibling
+ else:
+ newChildsLastElement.next = None
+ else:
+ nextChild = self.contents[position]
+ newChild.nextSibling = nextChild
+ if newChild.nextSibling:
+ newChild.nextSibling.previousSibling = newChild
+ newChildsLastElement.next = nextChild
+
+ if newChildsLastElement.next:
+ newChildsLastElement.next.previous = newChildsLastElement
+ self.contents.insert(position, newChild)
+
+ def findNext(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the first item that matches the given criteria and
+ appears after this Tag in the document."""
+ return self._findOne(self.findAllNext, name, attrs, text, **kwargs)
+
+ def findAllNext(self, name=None, attrs={}, text=None, limit=None,
+ **kwargs):
+ """Returns all items that match the given criteria and appear
+ before after Tag in the document."""
+ return self._findAll(name, attrs, text, limit, self.nextGenerator)
+
+ def findNextSibling(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the closest sibling to this Tag that matches the
+ given criteria and appears after this Tag in the document."""
+ return self._findOne(self.findNextSiblings, name, attrs, text,
+ **kwargs)
+
+ def findNextSiblings(self, name=None, attrs={}, text=None, limit=None,
+ **kwargs):
+ """Returns the siblings of this Tag that match the given
+ criteria and appear after this Tag in the document."""
+ return self._findAll(name, attrs, text, limit,
+ self.nextSiblingGenerator, **kwargs)
+ fetchNextSiblings = findNextSiblings # Compatibility with pre-3.x
+
+ def findPrevious(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the first item that matches the given criteria and
+ appears before this Tag in the document."""
+ return self._findOne(self.findAllPrevious, name, attrs, text, **kwargs)
+
+ def findAllPrevious(self, name=None, attrs={}, text=None, limit=None,
+ **kwargs):
+ """Returns all items that match the given criteria and appear
+ before this Tag in the document."""
+ return self._findAll(name, attrs, text, limit, self.previousGenerator,
+ **kwargs)
+ fetchPrevious = findAllPrevious # Compatibility with pre-3.x
+
+ def findPreviousSibling(self, name=None, attrs={}, text=None, **kwargs):
+ """Returns the closest sibling to this Tag that matches the
+ given criteria and appears before this Tag in the document."""
+ return self._findOne(self.findPreviousSiblings, name, attrs, text,
+ **kwargs)
+
+ def findPreviousSiblings(self, name=None, attrs={}, text=None,
+ limit=None, **kwargs):
+ """Returns the siblings of this Tag that match the given
+ criteria and appear before this Tag in the document."""
+ return self._findAll(name, attrs, text, limit,
+ self.previousSiblingGenerator, **kwargs)
+ fetchPreviousSiblings = findPreviousSiblings # Compatibility with pre-3.x
+
+ def findParent(self, name=None, attrs={}, **kwargs):
+ """Returns the closest parent of this Tag that matches the given
+ criteria."""
+ # NOTE: We can't use _findOne because findParents takes a different
+ # set of arguments.
+ r = None
+ l = self.findParents(name, attrs, 1)
+ if l:
+ r = l[0]
+ return r
+
+ def findParents(self, name=None, attrs={}, limit=None, **kwargs):
+ """Returns the parents of this Tag that match the given
+ criteria."""
+
+ return self._findAll(name, attrs, None, limit, self.parentGenerator,
+ **kwargs)
+ fetchParents = findParents # Compatibility with pre-3.x
+
+ #These methods do the real heavy lifting.
+
+ def _findOne(self, method, name, attrs, text, **kwargs):
+ r = None
+ l = method(name, attrs, text, 1, **kwargs)
+ if l:
+ r = l[0]
+ return r
+
+ def _findAll(self, name, attrs, text, limit, generator, **kwargs):
+ "Iterates over a generator looking for things that match."
+
+ if isinstance(name, SoupStrainer):
+ strainer = name
+ else:
+ # Build a SoupStrainer
+ strainer = SoupStrainer(name, attrs, text, **kwargs)
+ results = ResultSet(strainer)
+ g = generator()
+ while True:
+ try:
+ i = g.next()
+ except StopIteration:
+ break
+ if i:
+ found = strainer.search(i)
+ if found:
+ results.append(found)
+ if limit and len(results) >= limit:
+ break
+ return results
+
+ #These Generators can be used to navigate starting from both
+ #NavigableStrings and Tags.
+ def nextGenerator(self):
+ i = self
+ while i:
+ i = i.next
+ yield i
+
+ def nextSiblingGenerator(self):
+ i = self
+ while i:
+ i = i.nextSibling
+ yield i
+
+ def previousGenerator(self):
+ i = self
+ while i:
+ i = i.previous
+ yield i
+
+ def previousSiblingGenerator(self):
+ i = self
+ while i:
+ i = i.previousSibling
+ yield i
+
+ def parentGenerator(self):
+ i = self
+ while i:
+ i = i.parent
+ yield i
+
+ # Utility methods
+ def substituteEncoding(self, str, encoding=None):
+ encoding = encoding or "utf-8"
+ return str.replace("%SOUP-ENCODING%", encoding)
+
+ def toEncoding(self, s, encoding=None):
+ """Encodes an object to a string in some encoding, or to Unicode.
+ ."""
+ if isinstance(s, unicode):
+ if encoding:
+ s = s.encode(encoding)
+ elif isinstance(s, str):
+ if encoding:
+ s = s.encode(encoding)
+ else:
+ s = unicode(s)
+ else:
+ if encoding:
+ s = self.toEncoding(str(s), encoding)
+ else:
+ s = unicode(s)
+ return s
+
+class NavigableString(unicode, PageElement):
+
+ def __getattr__(self, attr):
+ """text.string gives you text. This is for backwards
+ compatibility for Navigable*String, but for CData* it lets you
+ get the string without the CData wrapper."""
+ if attr == 'string':
+ return self
+ else:
+ raise AttributeError, "'%s' object has no attribute '%s'" % (self.__class__.__name__, attr)
+
+ def __unicode__(self):
+ return self.__str__(None)
+
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ if encoding:
+ return self.encode(encoding)
+ else:
+ return self
+
+class CData(NavigableString):
+
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return "<![CDATA[%s]]>" % NavigableString.__str__(self, encoding)
+
+class ProcessingInstruction(NavigableString):
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ output = self
+ if "%SOUP-ENCODING%" in output:
+ output = self.substituteEncoding(output, encoding)
+ return "<?%s?>" % self.toEncoding(output, encoding)
+
+class Comment(NavigableString):
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return "<!--%s-->" % NavigableString.__str__(self, encoding)
+
+class Declaration(NavigableString):
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return "<!%s>" % NavigableString.__str__(self, encoding)
+
+class Tag(PageElement):
+
+ """Represents a found HTML tag with its attributes and contents."""
+
+ XML_SPECIAL_CHARS_TO_ENTITIES = { "'" : "squot",
+ '"' : "quote",
+ "&" : "amp",
+ "<" : "lt",
+ ">" : "gt" }
+
+ def __init__(self, parser, name, attrs=None, parent=None,
+ previous=None):
+ "Basic constructor."
+
+ # We don't actually store the parser object: that lets extracted
+ # chunks be garbage-collected
+ self.parserClass = parser.__class__
+ self.isSelfClosing = parser.isSelfClosingTag(name)
+ self.name = name
+ if attrs == None:
+ attrs = []
+ self.attrs = attrs
+ self.contents = []
+ self.setup(parent, previous)
+ self.hidden = False
+ self.containsSubstitutions = False
+
+ def get(self, key, default=None):
+ """Returns the value of the 'key' attribute for the tag, or
+ the value given for 'default' if it doesn't have that
+ attribute."""
+ return self._getAttrMap().get(key, default)
+
+ def has_key(self, key):
+ return self._getAttrMap().has_key(key)
+
+ def __getitem__(self, key):
+ """tag[key] returns the value of the 'key' attribute for the tag,
+ and throws an exception if it's not there."""
+ return self._getAttrMap()[key]
+
+ def __iter__(self):
+ "Iterating over a tag iterates over its contents."
+ return iter(self.contents)
+
+ def __len__(self):
+ "The length of a tag is the length of its list of contents."
+ return len(self.contents)
+
+ def __contains__(self, x):
+ return x in self.contents
+
+ def __nonzero__(self):
+ "A tag is non-None even if it has no contents."
+ return True
+
+ def __setitem__(self, key, value):
+ """Setting tag[key] sets the value of the 'key' attribute for the
+ tag."""
+ self._getAttrMap()
+ self.attrMap[key] = value
+ found = False
+ for i in range(0, len(self.attrs)):
+ if self.attrs[i][0] == key:
+ self.attrs[i] = (key, value)
+ found = True
+ if not found:
+ self.attrs.append((key, value))
+ self._getAttrMap()[key] = value
+
+ def __delitem__(self, key):
+ "Deleting tag[key] deletes all 'key' attributes for the tag."
+ for item in self.attrs:
+ if item[0] == key:
+ self.attrs.remove(item)
+ #We don't break because bad HTML can define the same
+ #attribute multiple times.
+ self._getAttrMap()
+ if self.attrMap.has_key(key):
+ del self.attrMap[key]
+
+ def __call__(self, *args, **kwargs):
+ """Calling a tag like a function is the same as calling its
+ findAll() method. Eg. tag('a') returns a list of all the A tags
+ found within this tag."""
+ return apply(self.findAll, args, kwargs)
+
+ def __getattr__(self, tag):
+ #print "Getattr %s.%s" % (self.__class__, tag)
+ if len(tag) > 3 and tag.rfind('Tag') == len(tag)-3:
+ return self.find(tag[:-3])
+ elif tag.find('__') != 0:
+ return self.find(tag)
+
+ def __eq__(self, other):
+ """Returns true iff this tag has the same name, the same attributes,
+ and the same contents (recursively) as the given tag.
+
+ NOTE: right now this will return false if two tags have the
+ same attributes in a different order. Should this be fixed?"""
+ if not hasattr(other, 'name') or not hasattr(other, 'attrs') or not hasattr(other, 'contents') or self.name != other.name or self.attrs != other.attrs or len(self) != len(other):
+ return False
+ for i in range(0, len(self.contents)):
+ if self.contents[i] != other.contents[i]:
+ return False
+ return True
+
+ def __ne__(self, other):
+ """Returns true iff this tag is not identical to the other tag,
+ as defined in __eq__."""
+ return not self == other
+
+ def __repr__(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ """Renders this tag as a string."""
+ return self.__str__(encoding)
+
+ def __unicode__(self):
+ return self.__str__(None)
+
+ def __str__(self, encoding=DEFAULT_OUTPUT_ENCODING,
+ prettyPrint=False, indentLevel=0):
+ """Returns a string or Unicode representation of this tag and
+ its contents. To get Unicode, pass None for encoding.
+
+ NOTE: since Python's HTML parser consumes whitespace, this
+ method is not certain to reproduce the whitespace present in
+ the original string."""
+
+ encodedName = self.toEncoding(self.name, encoding)
+
+ attrs = []
+ if self.attrs:
+ for key, val in self.attrs:
+ fmt = '%s="%s"'
+ if isString(val):
+ if self.containsSubstitutions and '%SOUP-ENCODING%' in val:
+ val = self.substituteEncoding(val, encoding)
+
+ # The attribute value either:
+ #
+ # * Contains no embedded double quotes or single quotes.
+ # No problem: we enclose it in double quotes.
+ # * Contains embedded single quotes. No problem:
+ # double quotes work here too.
+ # * Contains embedded double quotes. No problem:
+ # we enclose it in single quotes.
+ # * Embeds both single _and_ double quotes. This
+ # can't happen naturally, but it can happen if
+ # you modify an attribute value after parsing
+ # the document. Now we have a bit of a
+ # problem. We solve it by enclosing the
+ # attribute in single quotes, and escaping any
+ # embedded single quotes to XML entities.
+ if '"' in val:
+ fmt = "%s='%s'"
+ # This can't happen naturally, but it can happen
+ # if you modify an attribute value after parsing.
+ if "'" in val:
+ val = val.replace("'", "&squot;")
+
+ # Now we're okay w/r/t quotes. But the attribute
+ # value might also contain angle brackets, or
+ # ampersands that aren't part of entities. We need
+ # to escape those to XML entities too.
+ val = re.sub("([<>]|&(?![^\s]+;))",
+ lambda x: "&" + self.XML_SPECIAL_CHARS_TO_ENTITIES[x.group(0)[0]] + ";",
+ val)
+
+ attrs.append(fmt % (self.toEncoding(key, encoding),
+ self.toEncoding(val, encoding)))
+ close = ''
+ closeTag = ''
+ if self.isSelfClosing:
+ close = ' /'
+ else:
+ closeTag = '</%s>' % encodedName
+
+ indentTag, indentContents = 0, 0
+ if prettyPrint:
+ indentTag = indentLevel
+ space = (' ' * (indentTag-1))
+ indentContents = indentTag + 1
+ contents = self.renderContents(encoding, prettyPrint, indentContents)
+ if self.hidden:
+ s = contents
+ else:
+ s = []
+ attributeString = ''
+ if attrs:
+ attributeString = ' ' + ' '.join(attrs)
+ if prettyPrint:
+ s.append(space)
+ s.append('<%s%s%s>' % (encodedName, attributeString, close))
+ if prettyPrint:
+ s.append("\n")
+ s.append(contents)
+ if prettyPrint and contents and contents[-1] != "\n":
+ s.append("\n")
+ if prettyPrint and closeTag:
+ s.append(space)
+ s.append(closeTag)
+ if prettyPrint and closeTag and self.nextSibling:
+ s.append("\n")
+ s = ''.join(s)
+ return s
+
+ def prettify(self, encoding=DEFAULT_OUTPUT_ENCODING):
+ return self.__str__(encoding, True)
+
+ def renderContents(self, encoding=DEFAULT_OUTPUT_ENCODING,
+ prettyPrint=False, indentLevel=0):
+ """Renders the contents of this tag as a string in the given
+ encoding. If encoding is None, returns a Unicode string.."""
+ s=[]
+ for c in self:
+ text = None
+ if isinstance(c, NavigableString):
+ text = c.__str__(encoding)
+ elif isinstance(c, Tag):
+ s.append(c.__str__(encoding, prettyPrint, indentLevel))
+ if text and prettyPrint:
+ text = text.strip()
+ if text:
+ if prettyPrint:
+ s.append(" " * (indentLevel-1))
+ s.append(text)
+ if prettyPrint:
+ s.append("\n")
+ return ''.join(s)
+
+ #Soup methods
+
+ def find(self, name=None, attrs={}, recursive=True, text=None,
+ **kwargs):
+ """Return only the first child of this Tag matching the given
+ criteria."""
+ r = None
+ l = self.findAll(name, attrs, recursive, text, 1, **kwargs)
+ if l:
+ r = l[0]
+ return r
+ findChild = find
+
+ def findAll(self, name=None, attrs={}, recursive=True, text=None,
+ limit=None, **kwargs):
+ """Extracts a list of Tag objects that match the given
+ criteria. You can specify the name of the Tag and any
+ attributes you want the Tag to have.
+
+ The value of a key-value pair in the 'attrs' map can be a
+ string, a list of strings, a regular expression object, or a
+ callable that takes a string and returns whether or not the
+ string matches for some custom definition of 'matches'. The
+ same is true of the tag name."""
+ generator = self.recursiveChildGenerator
+ if not recursive:
+ generator = self.childGenerator
+ return self._findAll(name, attrs, text, limit, generator, **kwargs)
+ findChildren = findAll
+
+ # Pre-3.x compatibility methods
+ first = find
+ fetch = findAll
+
+ def fetchText(self, text=None, recursive=True, limit=None):
+ return self.findAll(text=text, recursive=recursive, limit=limit)
+
+ def firstText(self, text=None, recursive=True):
+ return self.find(text=text, recursive=recursive)
+
+ #Utility methods
+
+ def append(self, tag):
+ """Appends the given tag to the contents of this tag."""
+ self.contents.append(tag)
+
+ #Private methods
+
+ def _getAttrMap(self):
+ """Initializes a map representation of this tag's attributes,
+ if not already initialized."""
+ if not getattr(self, 'attrMap'):
+ self.attrMap = {}
+ for (key, value) in self.attrs:
+ self.attrMap[key] = value
+ return self.attrMap
+
+ #Generator methods
+ def childGenerator(self):
+ for i in range(0, len(self.contents)):
+ yield self.contents[i]
+ raise StopIteration
+
+ def recursiveChildGenerator(self):
+ stack = [(self, 0)]
+ while stack:
+ tag, start = stack.pop()
+ if isinstance(tag, Tag):
+ for i in range(start, len(tag.contents)):
+ a = tag.contents[i]
+ yield a
+ if isinstance(a, Tag) and tag.contents:
+ if i < len(tag.contents) - 1:
+ stack.append((tag, i+1))
+ stack.append((a, 0))
+ break
+ raise StopIteration
+
+# Next, a couple classes to represent queries and their results.
+class SoupStrainer:
+ """Encapsulates a number of ways of matching a markup element (tag or
+ text)."""
+
+ def __init__(self, name=None, attrs={}, text=None, **kwargs):
+ self.name = name
+ if isString(attrs):
+ kwargs['class'] = attrs
+ attrs = None
+ if kwargs:
+ if attrs:
+ attrs = attrs.copy()
+ attrs.update(kwargs)
+ else:
+ attrs = kwargs
+ self.attrs = attrs
+ self.text = text
+
+ def __str__(self):
+ if self.text:
+ return self.text
+ else:
+ return "%s|%s" % (self.name, self.attrs)
+
+ def searchTag(self, markupName=None, markupAttrs={}):
+ found = None
+ markup = None
+ if isinstance(markupName, Tag):
+ markup = markupName
+ markupAttrs = markup
+ callFunctionWithTagData = callable(self.name) \
+ and not isinstance(markupName, Tag)
+
+ if (not self.name) \
+ or callFunctionWithTagData \
+ or (markup and self._matches(markup, self.name)) \
+ or (not markup and self._matches(markupName, self.name)):
+ if callFunctionWithTagData:
+ match = self.name(markupName, markupAttrs)
+ else:
+ match = True
+ markupAttrMap = None
+ for attr, matchAgainst in self.attrs.items():
+ if not markupAttrMap:
+ if hasattr(markupAttrs, 'get'):
+ markupAttrMap = markupAttrs
+ else:
+ markupAttrMap = {}
+ for k,v in markupAttrs:
+ markupAttrMap[k] = v
+ attrValue = markupAttrMap.get(attr)
+ if not self._matches(attrValue, matchAgainst):
+ match = False
+ break
+ if match:
+ if markup:
+ found = markup
+ else:
+ found = markupName
+ return found
+
+ def search(self, markup):
+ #print 'looking for %s in %s' % (self, markup)
+ found = None
+ # If given a list of items, scan it for a text element that
+ # matches.
+ if isList(markup) and not isinstance(markup, Tag):
+ for element in markup:
+ if isinstance(element, NavigableString) \
+ and self.search(element):
+ found = element
+ break
+ # If it's a Tag, make sure its name or attributes match.
+ # Don't bother with Tags if we're searching for text.
+ elif isinstance(markup, Tag):
+ if not self.text:
+ found = self.searchTag(markup)
+ # If it's text, make sure the text matches.
+ elif isinstance(markup, NavigableString) or \
+ isString(markup):
+ if self._matches(markup, self.text):
+ found = markup
+ else:
+ raise Exception, "I don't know how to match against a %s" \
+ % markup.__class__
+ return found
+
+ def _matches(self, markup, matchAgainst):
+ #print "Matching %s against %s" % (markup, matchAgainst)
+ result = False
+ if matchAgainst == True and type(matchAgainst) == types.BooleanType:
+ result = markup != None
+ elif callable(matchAgainst):
+ result = matchAgainst(markup)
+ else:
+ #Custom match methods take the tag as an argument, but all
+ #other ways of matching match the tag name as a string.
+ if isinstance(markup, Tag):
+ markup = markup.name
+ if markup and not isString(markup):
+ markup = unicode(markup)
+ #Now we know that chunk is either a string, or None.
+ if hasattr(matchAgainst, 'match'):
+ # It's a regexp object.
+ result = markup and matchAgainst.search(markup)
+ elif isList(matchAgainst):
+ result = markup in matchAgainst
+ elif hasattr(matchAgainst, 'items'):
+ result = markup.has_key(matchAgainst)
+ elif matchAgainst and isString(markup):
+ if isinstance(markup, unicode):
+ matchAgainst = unicode(matchAgainst)
+ else:
+ matchAgainst = str(matchAgainst)
+
+ if not result:
+ result = matchAgainst == markup
+ return result
+
+class ResultSet(list):
+ """A ResultSet is just a list that keeps track of the SoupStrainer
+ that created it."""
+ def __init__(self, source):
+ list.__init__([])
+ self.source = source
+
+# Now, some helper functions.
+
+def isList(l):
+ """Convenience method that works with all 2.x versions of Python
+ to determine whether or not something is listlike."""
+ return hasattr(l, '__iter__') \
+ or (type(l) in (types.ListType, types.TupleType))
+
+def isString(s):
+ """Convenience method that works with all 2.x versions of Python
+ to determine whether or not something is stringlike."""
+ try:
+ return isinstance(s, unicode) or isintance(s, basestring)
+ except NameError:
+ return isinstance(s, str)
+
+def buildTagMap(default, *args):
+ """Turns a list of maps, lists, or scalars into a single map.
+ Used to build the SELF_CLOSING_TAGS, NESTABLE_TAGS, and
+ NESTING_RESET_TAGS maps out of lists and partial maps."""
+ built = {}
+ for portion in args:
+ if hasattr(portion, 'items'):
+ #It's a map. Merge it.
+ for k,v in portion.items():
+ built[k] = v
+ elif isList(portion):
+ #It's a list. Map each item to the default.
+ for k in portion:
+ built[k] = default
+ else:
+ #It's a scalar. Map it to the default.
+ built[portion] = default
+ return built
+
+# Now, the parser classes.
+
+class BeautifulStoneSoup(Tag, SGMLParser):
+
+ """This class contains the basic parser and search code. It defines
+ a parser that knows nothing about tag behavior except for the
+ following:
+
+ You can't close a tag without closing all the tags it encloses.
+ That is, "<foo><bar></foo>" actually means
+ "<foo><bar></bar></foo>".
+
+ [Another possible explanation is "<foo><bar /></foo>", but since
+ this class defines no SELF_CLOSING_TAGS, it will never use that
+ explanation.]
+
+ This class is useful for parsing XML or made-up markup languages,
+ or when BeautifulSoup makes an assumption counter to what you were
+ expecting."""
+
+ XML_ENTITY_LIST = {}
+ for i in Tag.XML_SPECIAL_CHARS_TO_ENTITIES.values():
+ XML_ENTITY_LIST[i] = True
+
+ SELF_CLOSING_TAGS = {}
+ NESTABLE_TAGS = {}
+ RESET_NESTING_TAGS = {}
+ QUOTE_TAGS = {}
+
+ MARKUP_MASSAGE = [(re.compile('(<[^<>]*)/>'),
+ lambda x: x.group(1) + ' />'),
+ (re.compile('<!\s+([^<>]*)>'),
+ lambda x: '<!' + x.group(1) + '>')
+ ]
+
+ ROOT_TAG_NAME = u'[document]'
+
+ HTML_ENTITIES = "html"
+ XML_ENTITIES = "xml"
+
+ def __init__(self, markup="", parseOnlyThese=None, fromEncoding=None,
+ markupMassage=True, smartQuotesTo=XML_ENTITIES,
+ convertEntities=None, selfClosingTags=None):
+ """The Soup object is initialized as the 'root tag', and the
+ provided markup (which can be a string or a file-like object)
+ is fed into the underlying parser.
+
+ sgmllib will process most bad HTML, and the BeautifulSoup
+ class has some tricks for dealing with some HTML that kills
+ sgmllib, but Beautiful Soup can nonetheless choke or lose data
+ if your data uses self-closing tags or declarations
+ incorrectly.
+
+ By default, Beautiful Soup uses regexes to sanitize input,
+ avoiding the vast majority of these problems. If the problems
+ don't apply to you, pass in False for markupMassage, and
+ you'll get better performance.
+
+ The default parser massage techniques fix the two most common
+ instances of invalid HTML that choke sgmllib:
+
+ <br/> (No space between name of closing tag and tag close)
+ <! --Comment--> (Extraneous whitespace in declaration)
+
+ You can pass in a custom list of (RE object, replace method)
+ tuples to get Beautiful Soup to scrub your input the way you
+ want."""
+
+ self.parseOnlyThese = parseOnlyThese
+ self.fromEncoding = fromEncoding
+ self.smartQuotesTo = smartQuotesTo
+ self.convertEntities = convertEntities
+ if self.convertEntities:
+ # It doesn't make sense to convert encoded characters to
+ # entities even while you're converting entities to Unicode.
+ # Just convert it all to Unicode.
+ self.smartQuotesTo = None
+ self.instanceSelfClosingTags = buildTagMap(None, selfClosingTags)
+ SGMLParser.__init__(self)
+
+ if hasattr(markup, 'read'): # It's a file-type object.
+ markup = markup.read()
+ self.markup = markup
+ self.markupMassage = markupMassage
+ try:
+ self._feed()
+ except StopParsing:
+ pass
+ self.markup = None # The markup can now be GCed
+
+ def _feed(self, inDocumentEncoding=None):
+ # Convert the document to Unicode.
+ markup = self.markup
+ if isinstance(markup, unicode):
+ if not hasattr(self, 'originalEncoding'):
+ self.originalEncoding = None
+ else:
+ dammit = UnicodeDammit\
+ (markup, [self.fromEncoding, inDocumentEncoding],
+ smartQuotesTo=self.smartQuotesTo)
+ markup = dammit.unicode
+ self.originalEncoding = dammit.originalEncoding
+ if markup:
+ if self.markupMassage:
+ if not isList(self.markupMassage):
+ self.markupMassage = self.MARKUP_MASSAGE
+ for fix, m in self.markupMassage:
+ markup = fix.sub(m, markup)
+ self.reset()
+
+ SGMLParser.feed(self, markup)
+ # Close out any unfinished strings and close all the open tags.
+ self.endData()
+ while self.currentTag.name != self.ROOT_TAG_NAME:
+ self.popTag()
+
+ def __getattr__(self, methodName):
+ """This method routes method call requests to either the SGMLParser
+ superclass or the Tag superclass, depending on the method name."""
+ #print "__getattr__ called on %s.%s" % (self.__class__, methodName)
+
+ if methodName.find('start_') == 0 or methodName.find('end_') == 0 \
+ or methodName.find('do_') == 0:
+ return SGMLParser.__getattr__(self, methodName)
+ elif methodName.find('__') != 0:
+ return Tag.__getattr__(self, methodName)
+ else:
+ raise AttributeError
+
+ def isSelfClosingTag(self, name):
+ """Returns true iff the given string is the name of a
+ self-closing tag according to this parser."""
+ return self.SELF_CLOSING_TAGS.has_key(name) \
+ or self.instanceSelfClosingTags.has_key(name)
+
+ def reset(self):
+ Tag.__init__(self, self, self.ROOT_TAG_NAME)
+ self.hidden = 1
+ SGMLParser.reset(self)
+ self.currentData = []
+ self.currentTag = None
+ self.tagStack = []
+ self.quoteStack = []
+ self.pushTag(self)
+
+ def popTag(self):
+ tag = self.tagStack.pop()
+ # Tags with just one string-owning child get the child as a
+ # 'string' property, so that soup.tag.string is shorthand for
+ # soup.tag.contents[0]
+ if len(self.currentTag.contents) == 1 and \
+ isinstance(self.currentTag.contents[0], NavigableString):
+ self.currentTag.string = self.currentTag.contents[0]
+
+ #print "Pop", tag.name
+ if self.tagStack:
+ self.currentTag = self.tagStack[-1]
+ return self.currentTag
+
+ def pushTag(self, tag):
+ #print "Push", tag.name
+ if self.currentTag:
+ self.currentTag.append(tag)
+ self.tagStack.append(tag)
+ self.currentTag = self.tagStack[-1]
+
+ def endData(self, containerClass=NavigableString):
+ if self.currentData:
+ currentData = ''.join(self.currentData)
+ if not currentData.strip():
+ if '\n' in currentData:
+ currentData = '\n'
+ else:
+ currentData = ' '
+ self.currentData = []
+ if self.parseOnlyThese and len(self.tagStack) <= 1 and \
+ (not self.parseOnlyThese.text or \
+ not self.parseOnlyThese.search(currentData)):
+ return
+ o = containerClass(currentData)
+ o.setup(self.currentTag, self.previous)
+ if self.previous:
+ self.previous.next = o
+ self.previous = o
+ self.currentTag.contents.append(o)
+
+
+ def _popToTag(self, name, inclusivePop=True):
+ """Pops the tag stack up to and including the most recent
+ instance of the given tag. If inclusivePop is false, pops the tag
+ stack up to but *not* including the most recent instqance of
+ the given tag."""
+ #print "Popping to %s" % name
+ if name == self.ROOT_TAG_NAME:
+ return
+
+ numPops = 0
+ mostRecentTag = None
+ for i in range(len(self.tagStack)-1, 0, -1):
+ if name == self.tagStack[i].name:
+ numPops = len(self.tagStack)-i
+ break
+ if not inclusivePop:
+ numPops = numPops - 1
+
+ for i in range(0, numPops):
+ mostRecentTag = self.popTag()
+ return mostRecentTag
+
+ def _smartPop(self, name):
+
+ """We need to pop up to the previous tag of this type, unless
+ one of this tag's nesting reset triggers comes between this
+ tag and the previous tag of this type, OR unless this tag is a
+ generic nesting trigger and another generic nesting trigger
+ comes between this tag and the previous tag of this type.
+
+ Examples:
+ <p>Foo<b>Bar<p> should pop to 'p', not 'b'.
+ <p>Foo<table>Bar<p> should pop to 'table', not 'p'.
+ <p>Foo<table><tr>Bar<p> should pop to 'tr', not 'p'.
+ <p>Foo<b>Bar<p> should pop to 'p', not 'b'.
+
+ <li><ul><li> *<li>* should pop to 'ul', not the first 'li'.
+ <tr><table><tr> *<tr>* should pop to 'table', not the first 'tr'
+ <td><tr><td> *<td>* should pop to 'tr', not the first 'td'
+ """
+
+ nestingResetTriggers = self.NESTABLE_TAGS.get(name)
+ isNestable = nestingResetTriggers != None
+ isResetNesting = self.RESET_NESTING_TAGS.has_key(name)
+ popTo = None
+ inclusive = True
+ for i in range(len(self.tagStack)-1, 0, -1):
+ p = self.tagStack[i]
+ if (not p or p.name == name) and not isNestable:
+ #Non-nestable tags get popped to the top or to their
+ #last occurance.
+ popTo = name
+ break
+ if (nestingResetTriggers != None
+ and p.name in nestingResetTriggers) \
+ or (nestingResetTriggers == None and isResetNesting
+ and self.RESET_NESTING_TAGS.has_key(p.name)):
+
+ #If we encounter one of the nesting reset triggers
+ #peculiar to this tag, or we encounter another tag
+ #that causes nesting to reset, pop up to but not
+ #including that tag.
+ popTo = p.name
+ inclusive = False
+ break
+ p = p.parent
+ if popTo:
+ self._popToTag(popTo, inclusive)
+
+ def unknown_starttag(self, name, attrs, selfClosing=0):
+ #print "Start tag %s: %s" % (name, attrs)
+ if self.quoteStack:
+ #This is not a real tag.
+ #print "<%s> is not real!" % name
+ attrs = ''.join(map(lambda(x, y): ' %s="%s"' % (x, y), attrs))
+ self.handle_data('<%s%s>' % (name, attrs))
+ return
+ self.endData()
+
+ if not self.isSelfClosingTag(name) and not selfClosing:
+ self._smartPop(name)
+
+ if self.parseOnlyThese and len(self.tagStack) <= 1 \
+ and (self.parseOnlyThese.text or not self.parseOnlyThese.searchTag(name, attrs)):
+ return
+
+ tag = Tag(self, name, attrs, self.currentTag, self.previous)
+ if self.previous:
+ self.previous.next = tag
+ self.previous = tag
+ self.pushTag(tag)
+ if selfClosing or self.isSelfClosingTag(name):
+ self.popTag()
+ if name in self.QUOTE_TAGS:
+ #print "Beginning quote (%s)" % name
+ self.quoteStack.append(name)
+ self.literal = 1
+ return tag
+
+ def unknown_endtag(self, name):
+ #print "End tag %s" % name
+ if self.quoteStack and self.quoteStack[-1] != name:
+ #This is not a real end tag.
+ #print "</%s> is not real!" % name
+ self.handle_data('</%s>' % name)
+ return
+ self.endData()
+ self._popToTag(name)
+ if self.quoteStack and self.quoteStack[-1] == name:
+ self.quoteStack.pop()
+ self.literal = (len(self.quoteStack) > 0)
+
+ def handle_data(self, data):
+ self.currentData.append(data)
+
+ def _toStringSubclass(self, text, subclass):
+ """Adds a certain piece of text to the tree as a NavigableString
+ subclass."""
+ self.endData()
+ self.handle_data(text)
+ self.endData(subclass)
+
+ def handle_pi(self, text):
+ """Handle a processing instruction as a ProcessingInstruction
+ object, possibly one with a %SOUP-ENCODING% slot into which an
+ encoding will be plugged later."""
+ if text[:3] == "xml":
+ text = "xml version='1.0' encoding='%SOUP-ENCODING%'"
+ self._toStringSubclass(text, ProcessingInstruction)
+
+ def handle_comment(self, text):
+ "Handle comments as Comment objects."
+ self._toStringSubclass(text, Comment)
+
+ def handle_charref(self, ref):
+ "Handle character references as data."
+ if self.convertEntities in [self.HTML_ENTITIES,
+ self.XML_ENTITIES]:
+ data = unichr(int(ref))
+ else:
+ data = '&#%s;' % ref
+ self.handle_data(data)
+
+ def handle_entityref(self, ref):
+ """Handle entity references as data, possibly converting known
+ HTML entity references to the corresponding Unicode
+ characters."""
+ data = None
+ if self.convertEntities == self.HTML_ENTITIES or \
+ (self.convertEntities == self.XML_ENTITIES and \
+ self.XML_ENTITY_LIST.get(ref)):
+ try:
+ data = unichr(name2codepoint[ref])
+ except KeyError:
+ pass
+ if not data:
+ data = '&%s;' % ref
+ self.handle_data(data)
+
+ def handle_decl(self, data):
+ "Handle DOCTYPEs and the like as Declaration objects."
+ self._toStringSubclass(data, Declaration)
+
+ def parse_declaration(self, i):
+ """Treat a bogus SGML declaration as raw data. Treat a CDATA
+ declaration as a CData object."""
+ j = None
+ if self.rawdata[i:i+9] == '<![CDATA[':
+ k = self.rawdata.find(']]>', i)
+ if k == -1:
+ k = len(self.rawdata)
+ data = self.rawdata[i+9:k]
+ j = k+3
+ self._toStringSubclass(data, CData)
+ else:
+ try:
+ j = SGMLParser.parse_declaration(self, i)
+ except SGMLParseError:
+ toHandle = self.rawdata[i:]
+ self.handle_data(toHandle)
+ j = i + len(toHandle)
+ return j
+
+class BeautifulSoup(BeautifulStoneSoup):
+
+ """This parser knows the following facts about HTML:
+
+ * Some tags have no closing tag and should be interpreted as being
+ closed as soon as they are encountered.
+
+ * The text inside some tags (ie. 'script') may contain tags which
+ are not really part of the document and which should be parsed
+ as text, not tags. If you want to parse the text as tags, you can
+ always fetch it and parse it explicitly.
+
+ * Tag nesting rules:
+
+ Most tags can't be nested at all. For instance, the occurance of
+ a <p> tag should implicitly close the previous <p> tag.
+
+ <p>Para1<p>Para2
+ should be transformed into:
+ <p>Para1</p><p>Para2
+
+ Some tags can be nested arbitrarily. For instance, the occurance
+ of a <blockquote> tag should _not_ implicitly close the previous
+ <blockquote> tag.
+
+ Alice said: <blockquote>Bob said: <blockquote>Blah
+ should NOT be transformed into:
+ Alice said: <blockquote>Bob said: </blockquote><blockquote>Blah
+
+ Some tags can be nested, but the nesting is reset by the
+ interposition of other tags. For instance, a <tr> tag should
+ implicitly close the previous <tr> tag within the same <table>,
+ but not close a <tr> tag in another table.
+
+ <table><tr>Blah<tr>Blah
+ should be transformed into:
+ <table><tr>Blah</tr><tr>Blah
+ but,
+ <tr>Blah<table><tr>Blah
+ should NOT be transformed into
+ <tr>Blah<table></tr><tr>Blah
+
+ Differing assumptions about tag nesting rules are a major source
+ of problems with the BeautifulSoup class. If BeautifulSoup is not
+ treating as nestable a tag your page author treats as nestable,
+ try ICantBelieveItsBeautifulSoup, MinimalSoup, or
+ BeautifulStoneSoup before writing your own subclass."""
+
+ def __init__(self, *args, **kwargs):
+ if not kwargs.has_key('smartQuotesTo'):
+ kwargs['smartQuotesTo'] = self.HTML_ENTITIES
+ BeautifulStoneSoup.__init__(self, *args, **kwargs)
+
+ SELF_CLOSING_TAGS = buildTagMap(None,
+ ['br' , 'hr', 'input', 'img', 'meta',
+ 'spacer', 'link', 'frame', 'base'])
+
+ QUOTE_TAGS = {'script': None}
+
+ #According to the HTML standard, each of these inline tags can
+ #contain another tag of the same type. Furthermore, it's common
+ #to actually use these tags this way.
+ NESTABLE_INLINE_TAGS = ['span', 'font', 'q', 'object', 'bdo', 'sub', 'sup',
+ 'center']
+
+ #According to the HTML standard, these block tags can contain
+ #another tag of the same type. Furthermore, it's common
+ #to actually use these tags this way.
+ NESTABLE_BLOCK_TAGS = ['blockquote', 'div', 'fieldset', 'ins', 'del']
+
+ #Lists can contain other lists, but there are restrictions.
+ NESTABLE_LIST_TAGS = { 'ol' : [],
+ 'ul' : [],
+ 'li' : ['ul', 'ol'],
+ 'dl' : [],
+ 'dd' : ['dl'],
+ 'dt' : ['dl'] }
+
+ #Tables can contain other tables, but there are restrictions.
+ NESTABLE_TABLE_TAGS = {'table' : [],
+ 'tr' : ['table', 'tbody', 'tfoot', 'thead'],
+ 'td' : ['tr'],
+ 'th' : ['tr'],
+ 'thead' : ['table'],
+ 'tbody' : ['table'],
+ 'tfoot' : ['table'],
+ }
+
+ NON_NESTABLE_BLOCK_TAGS = ['address', 'form', 'p', 'pre']
+
+ #If one of these tags is encountered, all tags up to the next tag of
+ #this type are popped.
+ RESET_NESTING_TAGS = buildTagMap(None, NESTABLE_BLOCK_TAGS, 'noscript',
+ NON_NESTABLE_BLOCK_TAGS,
+ NESTABLE_LIST_TAGS,
+ NESTABLE_TABLE_TAGS)
+
+ NESTABLE_TAGS = buildTagMap([], NESTABLE_INLINE_TAGS, NESTABLE_BLOCK_TAGS,
+ NESTABLE_LIST_TAGS, NESTABLE_TABLE_TAGS)
+
+ # Used to detect the charset in a META tag; see start_meta
+ CHARSET_RE = re.compile("((^|;)\s*charset=)([^;]*)")
+
+ def start_meta(self, attrs):
+ """Beautiful Soup can detect a charset included in a META tag,
+ try to convert the document to that charset, and re-parse the
+ document from the beginning."""
+ httpEquiv = None
+ contentType = None
+ contentTypeIndex = None
+ tagNeedsEncodingSubstitution = False
+
+ for i in range(0, len(attrs)):
+ key, value = attrs[i]
+ key = key.lower()
+ if key == 'http-equiv':
+ httpEquiv = value
+ elif key == 'content':
+ contentType = value
+ contentTypeIndex = i
+
+ if httpEquiv and contentType: # It's an interesting meta tag.
+ match = self.CHARSET_RE.search(contentType)
+ if match:
+ if getattr(self, 'declaredHTMLEncoding') or \
+ (self.originalEncoding == self.fromEncoding):
+ # This is our second pass through the document, or
+ # else an encoding was specified explicitly and it
+ # worked. Rewrite the meta tag.
+ newAttr = self.CHARSET_RE.sub\
+ (lambda(match):match.group(1) +
+ "%SOUP-ENCODING%", value)
+ attrs[contentTypeIndex] = (attrs[contentTypeIndex][0],
+ newAttr)
+ tagNeedsEncodingSubstitution = True
+ else:
+ # This is our first pass through the document.
+ # Go through it again with the new information.
+ newCharset = match.group(3)
+ if newCharset and newCharset != self.originalEncoding:
+ self.declaredHTMLEncoding = newCharset
+ self._feed(self.declaredHTMLEncoding)
+ raise StopParsing
+ tag = self.unknown_starttag("meta", attrs)
+ if tag and tagNeedsEncodingSubstitution:
+ tag.containsSubstitutions = True
+
+class StopParsing(Exception):
+ pass
+
+class ICantBelieveItsBeautifulSoup(BeautifulSoup):
+
+ """The BeautifulSoup class is oriented towards skipping over
+ common HTML errors like unclosed tags. However, sometimes it makes
+ errors of its own. For instance, consider this fragment:
+
+ <b>Foo<b>Bar</b></b>
+
+ This is perfectly valid (if bizarre) HTML. However, the
+ BeautifulSoup class will implicitly close the first b tag when it
+ encounters the second 'b'. It will think the author wrote
+ "<b>Foo<b>Bar", and didn't close the first 'b' tag, because
+ there's no real-world reason to bold something that's already
+ bold. When it encounters '</b></b>' it will close two more 'b'
+ tags, for a grand total of three tags closed instead of two. This
+ can throw off the rest of your document structure. The same is
+ true of a number of other tags, listed below.
+
+ It's much more common for someone to forget to close a 'b' tag
+ than to actually use nested 'b' tags, and the BeautifulSoup class
+ handles the common case. This class handles the not-co-common
+ case: where you can't believe someone wrote what they did, but
+ it's valid HTML and BeautifulSoup screwed up by assuming it
+ wouldn't be."""
+
+ I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS = \
+ ['em', 'big', 'i', 'small', 'tt', 'abbr', 'acronym', 'strong',
+ 'cite', 'code', 'dfn', 'kbd', 'samp', 'strong', 'var', 'b',
+ 'big']
+
+ I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS = ['noscript']
+
+ NESTABLE_TAGS = buildTagMap([], BeautifulSoup.NESTABLE_TAGS,
+ I_CANT_BELIEVE_THEYRE_NESTABLE_BLOCK_TAGS,
+ I_CANT_BELIEVE_THEYRE_NESTABLE_INLINE_TAGS)
+
+class MinimalSoup(BeautifulSoup):
+ """The MinimalSoup class is for parsing HTML that contains
+ pathologically bad markup. It makes no assumptions about tag
+ nesting, but it does know which tags are self-closing, that
+ <script> tags contain Javascript and should not be parsed, that
+ META tags may contain encoding information, and so on.
+
+ This also makes it better for subclassing than BeautifulStoneSoup
+ or BeautifulSoup."""
+
+ RESET_NESTING_TAGS = buildTagMap('noscript')
+ NESTABLE_TAGS = {}
+
+class BeautifulSOAP(BeautifulStoneSoup):
+ """This class will push a tag with only a single string child into
+ the tag's parent as an attribute. The attribute's name is the tag
+ name, and the value is the string child. An example should give
+ the flavor of the change:
+
+ <foo><bar>baz</bar></foo>
+ =>
+ <foo bar="baz"><bar>baz</bar></foo>
+
+ You can then access fooTag['bar'] instead of fooTag.barTag.string.
+
+ This is, of course, useful for scraping structures that tend to
+ use subelements instead of attributes, such as SOAP messages. Note
+ that it modifies its input, so don't print the modified version
+ out.
+
+ I'm not sure how many people really want to use this class; let me
+ know if you do. Mainly I like the name."""
+
+ def popTag(self):
+ if len(self.tagStack) > 1:
+ tag = self.tagStack[-1]
+ parent = self.tagStack[-2]
+ parent._getAttrMap()
+ if (isinstance(tag, Tag) and len(tag.contents) == 1 and
+ isinstance(tag.contents[0], NavigableString) and
+ not parent.attrMap.has_key(tag.name)):
+ parent[tag.name] = tag.contents[0]
+ BeautifulStoneSoup.popTag(self)
+
+#Enterprise class names! It has come to our attention that some people
+#think the names of the Beautiful Soup parser classes are too silly
+#and "unprofessional" for use in enterprise screen-scraping. We feel
+#your pain! For such-minded folk, the Beautiful Soup Consortium And
+#All-Night Kosher Bakery recommends renaming this file to
+#"RobustParser.py" (or, in cases of extreme enterprisness,
+#"RobustParserBeanInterface.class") and using the following
+#enterprise-friendly class aliases:
+class RobustXMLParser(BeautifulStoneSoup):
+ pass
+class RobustHTMLParser(BeautifulSoup):
+ pass
+class RobustWackAssHTMLParser(ICantBelieveItsBeautifulSoup):
+ pass
+class RobustInsanelyWackAssHTMLParser(MinimalSoup):
+ pass
+class SimplifyingSOAPParser(BeautifulSOAP):
+ pass
+
+######################################################
+#
+# Bonus library: Unicode, Dammit
+#
+# This class forces XML data into a standard format (usually to UTF-8
+# or Unicode). It is heavily based on code from Mark Pilgrim's
+# Universal Feed Parser. It does not rewrite the XML or HTML to
+# reflect a new encoding: that happens in BeautifulStoneSoup.handle_pi
+# (XML) and BeautifulSoup.start_meta (HTML).
+
+# Autodetects character encodings.
+# Download from http://chardet.feedparser.org/
+try:
+ import chardet
+# import chardet.constants
+# chardet.constants._debug = 1
+except:
+ chardet = None
+chardet = None
+
+# cjkcodecs and iconv_codec make Python know about more character encodings.
+# Both are available from http://cjkpython.i18n.org/
+# They're built in if you use Python 2.4.
+try:
+ import cjkcodecs.aliases
+except:
+ pass
+try:
+ import iconv_codec
+except:
+ pass
+
+class UnicodeDammit:
+ """A class for detecting the encoding of a *ML document and
+ converting it to a Unicode string. If the source encoding is
+ windows-1252, can replace MS smart quotes with their HTML or XML
+ equivalents."""
+
+ # This dictionary maps commonly seen values for "charset" in HTML
+ # meta tags to the corresponding Python codec names. It only covers
+ # values that aren't in Python's aliases and can't be determined
+ # by the heuristics in find_codec.
+ CHARSET_ALIASES = { "macintosh" : "mac-roman",
+ "x-sjis" : "shift-jis" }
+
+ def __init__(self, markup, overrideEncodings=[],
+ smartQuotesTo='xml'):
+ self.markup, documentEncoding, sniffedEncoding = \
+ self._detectEncoding(markup)
+ self.smartQuotesTo = smartQuotesTo
+ self.triedEncodings = []
+ if markup == '' or isinstance(markup, unicode):
+ self.originalEncoding = None
+ self.unicode = unicode(markup)
+ return
+
+ u = None
+ for proposedEncoding in overrideEncodings:
+ u = self._convertFrom(proposedEncoding)
+ if u: break
+ if not u:
+ for proposedEncoding in (documentEncoding, sniffedEncoding):
+ u = self._convertFrom(proposedEncoding)
+ if u: break
+
+ # If no luck and we have auto-detection library, try that:
+ if not u and chardet and not isinstance(self.markup, unicode):
+ u = self._convertFrom(chardet.detect(self.markup)['encoding'])
+
+ # As a last resort, try utf-8 and windows-1252:
+ if not u:
+ for proposed_encoding in ("utf-8", "windows-1252"):
+ u = self._convertFrom(proposed_encoding)
+ if u: break
+ self.unicode = u
+ if not u: self.originalEncoding = None
+
+ def _subMSChar(self, orig):
+ """Changes a MS smart quote character to an XML or HTML
+ entity."""
+ sub = self.MS_CHARS.get(orig)
+ if type(sub) == types.TupleType:
+ if self.smartQuotesTo == 'xml':
+ sub = '&#x%s;' % sub[1]
+ else:
+ sub = '&%s;' % sub[0]
+ return sub
+
+ def _convertFrom(self, proposed):
+ proposed = self.find_codec(proposed)
+ if not proposed or proposed in self.triedEncodings:
+ return None
+ self.triedEncodings.append(proposed)
+ markup = self.markup
+
+ # Convert smart quotes to HTML if coming from an encoding
+ # that might have them.
+ if self.smartQuotesTo and proposed.lower() in("windows-1252",
+ "iso-8859-1",
+ "iso-8859-2"):
+ markup = re.compile("([\x80-\x9f])").sub \
+ (lambda(x): self._subMSChar(x.group(1)),
+ markup)
+
+ try:
+ # print "Trying to convert document to %s" % proposed
+ u = self._toUnicode(markup, proposed)
+ self.markup = u
+ self.originalEncoding = proposed
+ except Exception, e:
+ # print "That didn't work!"
+ # print e
+ return None
+ #print "Correct encoding: %s" % proposed
+ return self.markup
+
+ def _toUnicode(self, data, encoding):
+ '''Given a string and its encoding, decodes the string into Unicode.
+ %encoding is a string recognized by encodings.aliases'''
+
+ # strip Byte Order Mark (if present)
+ if (len(data) >= 4) and (data[:2] == '\xfe\xff') \
+ and (data[2:4] != '\x00\x00'):
+ encoding = 'utf-16be'
+ data = data[2:]
+ elif (len(data) >= 4) and (data[:2] == '\xff\xfe') \
+ and (data[2:4] != '\x00\x00'):
+ encoding = 'utf-16le'
+ data = data[2:]
+ elif data[:3] == '\xef\xbb\xbf':
+ encoding = 'utf-8'
+ data = data[3:]
+ elif data[:4] == '\x00\x00\xfe\xff':
+ encoding = 'utf-32be'
+ data = data[4:]
+ elif data[:4] == '\xff\xfe\x00\x00':
+ encoding = 'utf-32le'
+ data = data[4:]
+ newdata = unicode(data, encoding)
+ return newdata
+
+ def _detectEncoding(self, xml_data):
+ """Given a document, tries to detect its XML encoding."""
+ xml_encoding = sniffed_xml_encoding = None
+ try:
+ if xml_data[:4] == '\x4c\x6f\xa7\x94':
+ # EBCDIC
+ xml_data = self._ebcdic_to_ascii(xml_data)
+ elif xml_data[:4] == '\x00\x3c\x00\x3f':
+ # UTF-16BE
+ sniffed_xml_encoding = 'utf-16be'
+ xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
+ elif (len(xml_data) >= 4) and (xml_data[:2] == '\xfe\xff') \
+ and (xml_data[2:4] != '\x00\x00'):
+ # UTF-16BE with BOM
+ sniffed_xml_encoding = 'utf-16be'
+ xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
+ elif xml_data[:4] == '\x3c\x00\x3f\x00':
+ # UTF-16LE
+ sniffed_xml_encoding = 'utf-16le'
+ xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
+ elif (len(xml_data) >= 4) and (xml_data[:2] == '\xff\xfe') and \
+ (xml_data[2:4] != '\x00\x00'):
+ # UTF-16LE with BOM
+ sniffed_xml_encoding = 'utf-16le'
+ xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
+ elif xml_data[:4] == '\x00\x00\x00\x3c':
+ # UTF-32BE
+ sniffed_xml_encoding = 'utf-32be'
+ xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
+ elif xml_data[:4] == '\x3c\x00\x00\x00':
+ # UTF-32LE
+ sniffed_xml_encoding = 'utf-32le'
+ xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
+ elif xml_data[:4] == '\x00\x00\xfe\xff':
+ # UTF-32BE with BOM
+ sniffed_xml_encoding = 'utf-32be'
+ xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
+ elif xml_data[:4] == '\xff\xfe\x00\x00':
+ # UTF-32LE with BOM
+ sniffed_xml_encoding = 'utf-32le'
+ xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
+ elif xml_data[:3] == '\xef\xbb\xbf':
+ # UTF-8 with BOM
+ sniffed_xml_encoding = 'utf-8'
+ xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
+ else:
+ sniffed_xml_encoding = 'ascii'
+ pass
+ xml_encoding_match = re.compile \
+ ('^<\?.*encoding=[\'"](.*?)[\'"].*\?>')\
+ .match(xml_data)
+ except:
+ xml_encoding_match = None
+ if xml_encoding_match:
+ xml_encoding = xml_encoding_match.groups()[0].lower()
+ if sniffed_xml_encoding and \
+ (xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode',
+ 'iso-10646-ucs-4', 'ucs-4', 'csucs4',
+ 'utf-16', 'utf-32', 'utf_16', 'utf_32',
+ 'utf16', 'u16')):
+ xml_encoding = sniffed_xml_encoding
+ return xml_data, xml_encoding, sniffed_xml_encoding
+
+
+ def find_codec(self, charset):
+ return self._codec(self.CHARSET_ALIASES.get(charset, charset)) \
+ or (charset and self._codec(charset.replace("-", ""))) \
+ or (charset and self._codec(charset.replace("-", "_"))) \
+ or charset
+
+ def _codec(self, charset):
+ if not charset: return charset
+ codec = None
+ try:
+ codecs.lookup(charset)
+ codec = charset
+ except LookupError:
+ pass
+ return codec
+
+ EBCDIC_TO_ASCII_MAP = None
+ def _ebcdic_to_ascii(self, s):
+ c = self.__class__
+ if not c.EBCDIC_TO_ASCII_MAP:
+ emap = (0,1,2,3,156,9,134,127,151,141,142,11,12,13,14,15,
+ 16,17,18,19,157,133,8,135,24,25,146,143,28,29,30,31,
+ 128,129,130,131,132,10,23,27,136,137,138,139,140,5,6,7,
+ 144,145,22,147,148,149,150,4,152,153,154,155,20,21,158,26,
+ 32,160,161,162,163,164,165,166,167,168,91,46,60,40,43,33,
+ 38,169,170,171,172,173,174,175,176,177,93,36,42,41,59,94,
+ 45,47,178,179,180,181,182,183,184,185,124,44,37,95,62,63,
+ 186,187,188,189,190,191,192,193,194,96,58,35,64,39,61,34,
+ 195,97,98,99,100,101,102,103,104,105,196,197,198,199,200,
+ 201,202,106,107,108,109,110,111,112,113,114,203,204,205,
+ 206,207,208,209,126,115,116,117,118,119,120,121,122,210,
+ 211,212,213,214,215,216,217,218,219,220,221,222,223,224,
+ 225,226,227,228,229,230,231,123,65,66,67,68,69,70,71,72,
+ 73,232,233,234,235,236,237,125,74,75,76,77,78,79,80,81,
+ 82,238,239,240,241,242,243,92,159,83,84,85,86,87,88,89,
+ 90,244,245,246,247,248,249,48,49,50,51,52,53,54,55,56,57,
+ 250,251,252,253,254,255)
+ import string
+ c.EBCDIC_TO_ASCII_MAP = string.maketrans( \
+ ''.join(map(chr, range(256))), ''.join(map(chr, emap)))
+ return s.translate(c.EBCDIC_TO_ASCII_MAP)
+
+ MS_CHARS = { '\x80' : ('euro', '20AC'),
+ '\x81' : ' ',
+ '\x82' : ('sbquo', '201A'),
+ '\x83' : ('fnof', '192'),
+ '\x84' : ('bdquo', '201E'),
+ '\x85' : ('hellip', '2026'),
+ '\x86' : ('dagger', '2020'),
+ '\x87' : ('Dagger', '2021'),
+ '\x88' : ('circ', '2C6'),
+ '\x89' : ('permil', '2030'),
+ '\x8A' : ('Scaron', '160'),
+ '\x8B' : ('lsaquo', '2039'),
+ '\x8C' : ('OElig', '152'),
+ '\x8D' : '?',
+ '\x8E' : ('#x17D', '17D'),
+ '\x8F' : '?',
+ '\x90' : '?',
+ '\x91' : ('lsquo', '2018'),
+ '\x92' : ('rsquo', '2019'),
+ '\x93' : ('ldquo', '201C'),
+ '\x94' : ('rdquo', '201D'),
+ '\x95' : ('bull', '2022'),
+ '\x96' : ('ndash', '2013'),
+ '\x97' : ('mdash', '2014'),
+ '\x98' : ('tilde', '2DC'),
+ '\x99' : ('trade', '2122'),
+ '\x9a' : ('scaron', '161'),
+ '\x9b' : ('rsaquo', '203A'),
+ '\x9c' : ('oelig', '153'),
+ '\x9d' : '?',
+ '\x9e' : ('#x17E', '17E'),
+ '\x9f' : ('Yuml', ''),}
+
+#######################################################################
+
+
+#By default, act as an HTML pretty-printer.
+if __name__ == '__main__':
+ import sys
+ soup = BeautifulSoup(sys.stdin.read())
+ print soup.prettify()
diff --git a/misc/openlayers/tools/README.txt b/misc/openlayers/tools/README.txt
new file mode 100644
index 0000000..cee5121
--- /dev/null
+++ b/misc/openlayers/tools/README.txt
@@ -0,0 +1,14 @@
+This directory contains tools used in the packaging or deployment of OpenLayers.
+
+Javascript minimizing tools:
+
+ * jsmin.c, jsmin.py:
+ jsmin.py is a direct translation of the jsmin.c code into Python. jsmin.py
+ will therefore run anyplace Python runs... but at significantly slower speed.
+
+ * shrinksafe.py
+ shrinksafe.py calls out to a third party javascript shrinking service. This
+ creates file sizes about 4% smaller (as of commit 501) of the OpenLayers
+ code. However, this also has the side effect of making you dependant on the
+ web service -- and since that service sometimes goes dead, it's risky to
+ depend on it.
diff --git a/misc/openlayers/tools/closure_library_jscompiler.py b/misc/openlayers/tools/closure_library_jscompiler.py
new file mode 100644
index 0000000..fd1882f
--- /dev/null
+++ b/misc/openlayers/tools/closure_library_jscompiler.py
@@ -0,0 +1,71 @@
+# Copyright 2010 The Closure Library Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS-IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility to use the Closure Compiler CLI from Python."""
+
+import distutils.version
+import logging
+import re
+import subprocess
+
+
+# Pulls a version number from the first line of 'java -version'
+# See http://java.sun.com/j2se/versioning_naming.html to learn more about the
+# command's output format.
+_VERSION_REGEX = re.compile('"([0-9][.0-9]*)')
+
+
+def _GetJavaVersion():
+ """Returns the string for the current version of Java installed."""
+ proc = subprocess.Popen(['java', '-version'], stderr=subprocess.PIPE)
+ unused_stdoutdata, stderrdata = proc.communicate()
+ version_line = stderrdata.splitlines()[0]
+ return _VERSION_REGEX.search(version_line).group(1)
+
+
+def Compile(compiler_jar_path, source_paths, flags=None):
+ """Prepares command-line call to Closure Compiler.
+
+ Args:
+ compiler_jar_path: Path to the Closure compiler .jar file.
+ source_paths: Source paths to build, in order.
+ flags: A list of additional flags to pass on to Closure Compiler.
+
+ Returns:
+ The compiled source, as a string, or None if compilation failed.
+ """
+
+ # User friendly version check.
+ if not (distutils.version.LooseVersion(_GetJavaVersion()) >=
+ distutils.version.LooseVersion('1.6')):
+ logging.error('Closure Compiler requires Java 1.6 or higher. '
+ 'Please visit http://www.java.com/getjava')
+ return
+
+ args = ['java', '-jar', compiler_jar_path]
+ for path in source_paths:
+ args += ['--js', path]
+
+ if flags:
+ args += flags
+
+ logging.info('Compiling with the following command: %s', ' '.join(args))
+
+ proc = subprocess.Popen(args, stdout=subprocess.PIPE)
+ stdoutdata, unused_stderrdata = proc.communicate()
+
+ if proc.returncode != 0:
+ return
+
+ return stdoutdata
diff --git a/misc/openlayers/tools/closure_ws.py b/misc/openlayers/tools/closure_ws.py
new file mode 100644
index 0000000..3bf925a
--- /dev/null
+++ b/misc/openlayers/tools/closure_ws.py
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+
+import httplib, urllib, sys
+import time
+# Define the parameters for the POST request and encode them in
+# a URL-safe format.
+
+def minimize(code):
+
+ params = urllib.urlencode([
+ ('js_code', code),
+ ('compilation_level', 'SIMPLE_OPTIMIZATIONS'),
+ ('output_format', 'text'),
+ ('output_info', 'compiled_code'),
+ ])
+
+ t = time.time()
+ # Always use the following value for the Content-type header.
+ headers = { "Content-type": "application/x-www-form-urlencoded" }
+ conn = httplib.HTTPConnection('closure-compiler.appspot.com')
+ conn.request('POST', '/compile', params, headers)
+ response = conn.getresponse()
+ data = response.read()
+ conn.close()
+ if data.startswith("Error"):
+ raise Exception(data)
+ print "%.3f seconds to compile" % (time.time() - t)
+ return data
diff --git a/misc/openlayers/tools/exampleparser.py b/misc/openlayers/tools/exampleparser.py
new file mode 100755
index 0000000..6ef123a
--- /dev/null
+++ b/misc/openlayers/tools/exampleparser.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import re
+import time
+from xml.dom.minidom import Document
+
+try:
+ import xml.etree.ElementTree as ElementTree
+except ImportError:
+ try:
+ import cElementTree as ElementTree
+ except ImportError:
+ try:
+ import elementtree.ElementTree as ElementTree
+ except ImportError:
+ import lxml.etree as ElementTree
+
+missing_deps = False
+try:
+ import json
+except ImportError:
+ try:
+ import simplejson as json
+ except ImportError, E:
+ missing_deps = E
+
+try:
+ from BeautifulSoup import BeautifulSoup
+except ImportError, E:
+ missing_deps = E
+
+feedName = "example-list.xml"
+feedPath = "http://openlayers.org/dev/examples/"
+
+def getListOfExamples(relPath):
+ """
+ returns list of .html filenames within a given path - excludes example-list.html
+ """
+ examples = os.listdir(relPath)
+ examples = [example for example in examples if example.endswith('.html') and example != "example-list.html"]
+ return examples
+
+
+def getExampleHtml(path):
+ """
+ returns html of a specific example
+ """
+ print '.',
+ f = open(path)
+ html = f.read()
+ f.close()
+ return html
+
+
+def extractById(soup, tagId, value=None):
+ """
+ returns full contents of a particular tag id
+ """
+ beautifulTag = soup.find(id=tagId)
+ if beautifulTag:
+ if beautifulTag.contents:
+ value = str(beautifulTag.renderContents()).strip()
+ value = value.replace('\t','')
+ value = value.replace('\n','')
+ return value
+
+def getRelatedClasses(html):
+ """
+ parses the html, and returns a list of all OpenLayers Classes
+ used within (ie what parts of OL the javascript uses).
+ """
+ rawstr = r'''(?P<class>OpenLayers\..*?)\('''
+ return re.findall(rawstr, html)
+
+def parseHtml(html,ids):
+ """
+ returns dictionary of items of interest
+ """
+ soup = BeautifulSoup(html)
+ d = {}
+ for tagId in ids:
+ d[tagId] = extractById(soup,tagId)
+ #classes should eventually be parsed from docs - not automatically created.
+ classes = getRelatedClasses(html)
+ d['classes'] = classes
+ return d
+
+def getGitInfo(exampleDir, exampleName):
+ orig = os.getcwd()
+ os.chdir(exampleDir)
+ h = os.popen("git log -n 1 --pretty=format:'%an|%ai' " + exampleName)
+ os.chdir(orig)
+ log = h.read()
+ h.close()
+ d = {}
+ parts = log.split("|")
+ d["author"] = parts[0]
+ # compensate for spaces in git log time
+ td = parts[1].split(" ")
+ td.insert(1, "T")
+ d["date"] = "".join(td)
+ return d
+
+def createFeed(examples):
+ doc = Document()
+ atomuri = "http://www.w3.org/2005/Atom"
+ feed = doc.createElementNS(atomuri, "feed")
+ feed.setAttribute("xmlns", atomuri)
+ title = doc.createElementNS(atomuri, "title")
+ title.appendChild(doc.createTextNode("OpenLayers Examples"))
+ feed.appendChild(title)
+ link = doc.createElementNS(atomuri, "link")
+ link.setAttribute("rel", "self")
+ link.setAttribute("href", feedPath + feedName)
+
+ modtime = time.strftime("%Y-%m-%dT%I:%M:%SZ", time.gmtime())
+ id = doc.createElementNS(atomuri, "id")
+ id.appendChild(doc.createTextNode("%s%s#%s" % (feedPath, feedName, modtime)))
+ feed.appendChild(id)
+
+ updated = doc.createElementNS(atomuri, "updated")
+ updated.appendChild(doc.createTextNode(modtime))
+ feed.appendChild(updated)
+
+ examples.sort(key=lambda x:x["modified"])
+ for example in sorted(examples, key=lambda x:x["modified"], reverse=True):
+ entry = doc.createElementNS(atomuri, "entry")
+
+ title = doc.createElementNS(atomuri, "title")
+ title.appendChild(doc.createTextNode(example["title"] or example["example"]))
+ entry.appendChild(title)
+
+ tags = doc.createElementNS(atomuri, "tags")
+ tags.appendChild(doc.createTextNode(example["tags"] or example["example"]))
+ entry.appendChild(tags)
+
+ link = doc.createElementNS(atomuri, "link")
+ link.setAttribute("href", "%s%s" % (feedPath, example["example"]))
+ entry.appendChild(link)
+
+ summary = doc.createElementNS(atomuri, "summary")
+ summary.appendChild(doc.createTextNode(example["shortdesc"] or example["example"]))
+ entry.appendChild(summary)
+
+ updated = doc.createElementNS(atomuri, "updated")
+ updated.appendChild(doc.createTextNode(example["modified"]))
+ entry.appendChild(updated)
+
+ author = doc.createElementNS(atomuri, "author")
+ name = doc.createElementNS(atomuri, "name")
+ name.appendChild(doc.createTextNode(example["author"]))
+ author.appendChild(name)
+ entry.appendChild(author)
+
+ id = doc.createElementNS(atomuri, "id")
+ id.appendChild(doc.createTextNode("%s%s#%s" % (feedPath, example["example"], example["modified"])))
+ entry.appendChild(id)
+
+ feed.appendChild(entry)
+
+ doc.appendChild(feed)
+ return doc
+
+def wordIndex(examples):
+ """
+ Create an inverted index based on words in title and shortdesc. Keys are
+ lower cased words. Values are dictionaries with example index keys and
+ count values.
+ """
+ index = {}
+ unword = re.compile("\\W+")
+ keys = ["shortdesc", "title", "tags"]
+ for i in range(len(examples)):
+ for key in keys:
+ text = examples[i][key]
+ if text:
+ words = unword.split(text)
+ for word in words:
+ if word:
+ word = word.lower()
+ if index.has_key(word):
+ if index[word].has_key(i):
+ index[word][i] += 1
+ else:
+ index[word][i] = 1
+ else:
+ index[word] = {i: 1}
+ return index
+
+if __name__ == "__main__":
+
+ if missing_deps:
+ print "This script requires json or simplejson and BeautifulSoup. You don't have them. \n(%s)" % E
+ sys.exit()
+
+ if len(sys.argv) == 3:
+ inExampleDir = sys.argv[1]
+ outExampleDir = sys.argv[2]
+ else:
+ inExampleDir = "../examples"
+ outExampleDir = "../examples"
+
+ outFile = open(os.path.join(outExampleDir, "example-list.js"), "w")
+
+ print 'Reading examples from %s and writing out to %s' % (inExampleDir, outFile.name)
+
+ exampleList = []
+ docIds = ['title','shortdesc','tags']
+
+ examples = getListOfExamples(inExampleDir)
+
+ modtime = time.strftime("%Y-%m-%dT%I:%M:%SZ", time.gmtime())
+
+ for example in examples:
+ path = os.path.join(inExampleDir, example)
+ html = getExampleHtml(path)
+ tagvalues = parseHtml(html,docIds)
+ tagvalues['example'] = example
+ # add in author/date info
+ d = getGitInfo(inExampleDir, example)
+ tagvalues["author"] = d["author"] or "anonymous"
+ tagvalues["modified"] = d["date"] or modtime
+ tagvalues['link'] = example
+
+ exampleList.append(tagvalues)
+
+ print
+
+ exampleList.sort(key=lambda x:x['example'].lower())
+
+ index = wordIndex(exampleList)
+
+ json = json.dumps({"examples": exampleList, "index": index})
+ #give the json a global variable we can use in our js. This should be replaced or made optional.
+ json = 'var info=' + json
+ outFile.write(json)
+ outFile.close()
+
+ outFeedPath = os.path.join(outExampleDir, feedName);
+ print "writing feed to %s " % outFeedPath
+ atom = open(outFeedPath, 'w')
+ doc = createFeed(exampleList)
+ atom.write(doc.toxml())
+ atom.close()
+
+
+ print 'complete'
+
+
diff --git a/misc/openlayers/tools/jsmin.c b/misc/openlayers/tools/jsmin.c
new file mode 100644
index 0000000..86d53da
--- /dev/null
+++ b/misc/openlayers/tools/jsmin.c
@@ -0,0 +1,272 @@
+/* jsmin.c
+ 2006-05-04
+
+Copyright (c) 2002 Douglas Crockford (www.crockford.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+The Software shall be used for Good, not Evil.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+
+static int theA;
+static int theB;
+static int theLookahead = EOF;
+
+
+/* isAlphanum -- return true if the character is a letter, digit, underscore,
+ dollar sign, or non-ASCII character.
+*/
+
+static int
+isAlphanum(int c)
+{
+ return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
+ c > 126);
+}
+
+
+/* get -- return the next character from stdin. Watch out for lookahead. If
+ the character is a control character, translate it to a space or
+ linefeed.
+*/
+
+static int
+get()
+{
+ int c = theLookahead;
+ theLookahead = EOF;
+ if (c == EOF) {
+ c = getc(stdin);
+ }
+ if (c >= ' ' || c == '\n' || c == EOF) {
+ return c;
+ }
+ if (c == '\r') {
+ return '\n';
+ }
+ return ' ';
+}
+
+
+/* peek -- get the next character without getting it.
+*/
+
+static int
+peek()
+{
+ theLookahead = get();
+ return theLookahead;
+}
+
+
+/* next -- get the next character, excluding comments. peek() is used to see
+ if a '/' is followed by a '/' or '*'.
+*/
+
+static int
+next()
+{
+ int c = get();
+ if (c == '/') {
+ switch (peek()) {
+ case '/':
+ for (;;) {
+ c = get();
+ if (c <= '\n') {
+ return c;
+ }
+ }
+ case '*':
+ get();
+ for (;;) {
+ switch (get()) {
+ case '*':
+ if (peek() == '/') {
+ get();
+ return ' ';
+ }
+ break;
+ case EOF:
+ fprintf(stderr, "Error: JSMIN Unterminated comment.\n");
+ exit(1);
+ }
+ }
+ default:
+ return c;
+ }
+ }
+ return c;
+}
+
+
+/* action -- do something! What you do is determined by the argument:
+ 1 Output A. Copy B to A. Get the next B.
+ 2 Copy B to A. Get the next B. (Delete A).
+ 3 Get the next B. (Delete B).
+ action treats a string as a single character. Wow!
+ action recognizes a regular expression if it is preceded by ( or , or =.
+*/
+
+static void
+action(int d)
+{
+ switch (d) {
+ case 1:
+ putc(theA, stdout);
+ case 2:
+ theA = theB;
+ if (theA == '\'' || theA == '"') {
+ for (;;) {
+ putc(theA, stdout);
+ theA = get();
+ if (theA == theB) {
+ break;
+ }
+ if (theA <= '\n') {
+ fprintf(stderr,
+"Error: JSMIN unterminated string literal: %c\n", theA);
+ exit(1);
+ }
+ if (theA == '\\') {
+ putc(theA, stdout);
+ theA = get();
+ }
+ }
+ }
+ case 3:
+ theB = next();
+ if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
+ theA == ':' || theA == '[' || theA == '!' || theA == '&' ||
+ theA == '|')) {
+ putc(theA, stdout);
+ putc(theB, stdout);
+ for (;;) {
+ theA = get();
+ if (theA == '/') {
+ break;
+ } else if (theA =='\\') {
+ putc(theA, stdout);
+ theA = get();
+ } else if (theA <= '\n') {
+ fprintf(stderr,
+"Error: JSMIN unterminated Regular Expression literal.\n", theA);
+ exit(1);
+ }
+ putc(theA, stdout);
+ }
+ theB = next();
+ }
+ }
+}
+
+
+/* jsmin -- Copy the input to the output, deleting the characters which are
+ insignificant to JavaScript. Comments will be removed. Tabs will be
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
+ Most spaces and linefeeds will be removed.
+*/
+
+static void
+jsmin()
+{
+ theA = '\n';
+ action(3);
+ while (theA != EOF) {
+ switch (theA) {
+ case ' ':
+ if (isAlphanum(theB)) {
+ action(1);
+ } else {
+ action(2);
+ }
+ break;
+ case '\n':
+ switch (theB) {
+ case '{':
+ case '[':
+ case '(':
+ case '+':
+ case '-':
+ action(1);
+ break;
+ case ' ':
+ action(3);
+ break;
+ default:
+ if (isAlphanum(theB)) {
+ action(1);
+ } else {
+ action(2);
+ }
+ }
+ break;
+ default:
+ switch (theB) {
+ case ' ':
+ if (isAlphanum(theA)) {
+ action(1);
+ break;
+ }
+ action(3);
+ break;
+ case '\n':
+ switch (theA) {
+ case '}':
+ case ']':
+ case ')':
+ case '+':
+ case '-':
+ case '"':
+ case '\'':
+ action(1);
+ break;
+ default:
+ if (isAlphanum(theA)) {
+ action(1);
+ } else {
+ action(3);
+ }
+ }
+ break;
+ default:
+ action(1);
+ break;
+ }
+ }
+ }
+}
+
+
+/* main -- Output any command line arguments as comments
+ and then minify the input.
+*/
+extern int
+main(int argc, char* argv[])
+{
+ int i;
+ for (i = 1; i < argc; i += 1) {
+ fprintf(stdout, "// %s\n", argv[i]);
+ }
+ jsmin();
+ return 0;
+}
diff --git a/misc/openlayers/tools/jsmin.py b/misc/openlayers/tools/jsmin.py
new file mode 100755
index 0000000..d188790
--- /dev/null
+++ b/misc/openlayers/tools/jsmin.py
@@ -0,0 +1,216 @@
+#!/usr/bin/python
+
+# This code is original from jsmin by Douglas Crockford, it was translated to
+# Python by Baruch Even. The original code had the following copyright and
+# license.
+#
+# /* jsmin.c
+# 2007-01-08
+#
+# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy of
+# this software and associated documentation files (the "Software"), to deal in
+# the Software without restriction, including without limitation the rights to
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+# of the Software, and to permit persons to whom the Software is furnished to do
+# so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# The Software shall be used for Good, not Evil.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+# */
+
+from StringIO import StringIO
+
+def jsmin(js):
+ ins = StringIO(js)
+ outs = StringIO()
+ JavascriptMinify().minify(ins, outs)
+ str = outs.getvalue()
+ if len(str) > 0 and str[0] == '\n':
+ str = str[1:]
+ return str
+
+def isAlphanum(c):
+ """return true if the character is a letter, digit, underscore,
+ dollar sign, or non-ASCII character.
+ """
+ return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or
+ (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));
+
+class UnterminatedComment(Exception):
+ pass
+
+class UnterminatedStringLiteral(Exception):
+ pass
+
+class UnterminatedRegularExpression(Exception):
+ pass
+
+class JavascriptMinify(object):
+
+ def _outA(self):
+ self.outstream.write(self.theA)
+ def _outB(self):
+ self.outstream.write(self.theB)
+
+ def _get(self):
+ """return the next character from stdin. Watch out for lookahead. If
+ the character is a control character, translate it to a space or
+ linefeed.
+ """
+ c = self.theLookahead
+ self.theLookahead = None
+ if c == None:
+ c = self.instream.read(1)
+ if c >= ' ' or c == '\n':
+ return c
+ if c == '': # EOF
+ return '\000'
+ if c == '\r':
+ return '\n'
+ return ' '
+
+ def _peek(self):
+ self.theLookahead = self._get()
+ return self.theLookahead
+
+ def _next(self):
+ """get the next character, excluding comments. peek() is used to see
+ if a '/' is followed by a '/' or '*'.
+ """
+ c = self._get()
+ if c == '/':
+ p = self._peek()
+ if p == '/':
+ c = self._get()
+ while c > '\n':
+ c = self._get()
+ return c
+ if p == '*':
+ c = self._get()
+ while 1:
+ c = self._get()
+ if c == '*':
+ if self._peek() == '/':
+ self._get()
+ return ' '
+ if c == '\000':
+ raise UnterminatedComment()
+
+ return c
+
+ def _action(self, action):
+ """do something! What you do is determined by the argument:
+ 1 Output A. Copy B to A. Get the next B.
+ 2 Copy B to A. Get the next B. (Delete A).
+ 3 Get the next B. (Delete B).
+ action treats a string as a single character. Wow!
+ action recognizes a regular expression if it is preceded by ( or , or =.
+ """
+ if action <= 1:
+ self._outA()
+
+ if action <= 2:
+ self.theA = self.theB
+ if self.theA == "'" or self.theA == '"':
+ while 1:
+ self._outA()
+ self.theA = self._get()
+ if self.theA == self.theB:
+ break
+ if self.theA <= '\n':
+ raise UnterminatedStringLiteral()
+ if self.theA == '\\':
+ self._outA()
+ self.theA = self._get()
+
+
+ if action <= 3:
+ self.theB = self._next()
+ if self.theB == '/' and (self.theA == '(' or self.theA == ',' or
+ self.theA == '=' or self.theA == ':' or
+ self.theA == '[' or self.theA == '?' or
+ self.theA == '!' or self.theA == '&' or
+ self.theA == '|'):
+ self._outA()
+ self._outB()
+ while 1:
+ self.theA = self._get()
+ if self.theA == '/':
+ break
+ elif self.theA == '\\':
+ self._outA()
+ self.theA = self._get()
+ elif self.theA <= '\n':
+ raise UnterminatedRegularExpression()
+ self._outA()
+ self.theB = self._next()
+
+
+ def _jsmin(self):
+ """Copy the input to the output, deleting the characters which are
+ insignificant to JavaScript. Comments will be removed. Tabs will be
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
+ Most spaces and linefeeds will be removed.
+ """
+ self.theA = '\n'
+ self._action(3)
+
+ while self.theA != '\000':
+ if self.theA == ' ':
+ if isAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ elif self.theA == '\n':
+ if self.theB in ['{', '[', '(', '+', '-']:
+ self._action(1)
+ elif self.theB == ' ':
+ self._action(3)
+ else:
+ if isAlphanum(self.theB):
+ self._action(1)
+ else:
+ self._action(2)
+ else:
+ if self.theB == ' ':
+ if isAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ elif self.theB == '\n':
+ if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:
+ self._action(1)
+ else:
+ if isAlphanum(self.theA):
+ self._action(1)
+ else:
+ self._action(3)
+ else:
+ self._action(1)
+
+ def minify(self, instream, outstream):
+ self.instream = instream
+ self.outstream = outstream
+ self.theA = None
+ self.thaB = None
+ self.theLookahead = None
+
+ self._jsmin()
+ self.instream.close()
+
+if __name__ == '__main__':
+ import sys
+ jsm = JavascriptMinify()
+ jsm.minify(sys.stdin, sys.stdout)
diff --git a/misc/openlayers/tools/mergejs.py b/misc/openlayers/tools/mergejs.py
new file mode 100755
index 0000000..1b26f2e
--- /dev/null
+++ b/misc/openlayers/tools/mergejs.py
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+#
+# Merge multiple JavaScript source code files into one.
+#
+# Usage:
+# This script requires source files to have dependencies specified in them.
+#
+# Dependencies are specified with a comment of the form:
+#
+# // @requires <file path>
+#
+# e.g.
+#
+# // @requires Geo/DataSource.js
+#
+# This script should be executed like so:
+#
+# mergejs.py <output.js> <directory> [...]
+#
+# e.g.
+#
+# mergejs.py openlayers.js Geo/ CrossBrowser/
+#
+# This example will cause the script to walk the `Geo` and
+# `CrossBrowser` directories--and subdirectories thereof--and import
+# all `*.js` files encountered. The dependency declarations will be extracted
+# and then the source code from imported files will be output to
+# a file named `openlayers.js` in an order which fulfils the dependencies
+# specified.
+#
+#
+# Note: This is a very rough initial version of this code.
+#
+# -- Copyright 2005-2013 OpenLayers contributors / OpenLayers project --
+#
+
+# TODO: Allow files to be excluded. e.g. `Crossbrowser/DebugMode.js`?
+# TODO: Report error when dependency can not be found rather than KeyError.
+
+import re
+import os
+import sys
+
+SUFFIX_JAVASCRIPT = ".js"
+
+RE_REQUIRE = "@requires?:?\s+(\S*)\s*\n" # TODO: Ensure in comment?
+
+class MissingImport(Exception):
+ """Exception raised when a listed import is not found in the lib."""
+
+class SourceFile:
+ """
+ Represents a Javascript source code file.
+ """
+
+ def __init__(self, filepath, source, cfgExclude):
+ """
+ """
+ self.filepath = filepath
+ self.source = source
+
+ self.excludedFiles = []
+ self.requiredFiles = []
+ auxReq = re.findall(RE_REQUIRE, self.source)
+ for filename in auxReq:
+ if undesired(filename, cfgExclude):
+ self.excludedFiles.append(filename)
+ else:
+ self.requiredFiles.append(filename)
+
+ self.requiredBy = []
+
+
+ def _getRequirements(self):
+ """
+ Extracts the dependencies specified in the source code and returns
+ a list of them.
+ """
+ return self.requiredFiles
+
+ requires = property(fget=_getRequirements, doc="")
+
+
+
+def usage(filename):
+ """
+ Displays a usage message.
+ """
+ print "%s [-c <config file>] <output.js> <directory> [...]" % filename
+
+
+class Config:
+ """
+ Represents a parsed configuration file.
+
+ A configuration file should be of the following form:
+
+ [first]
+ 3rd/prototype.js
+ core/application.js
+ core/params.js
+ # A comment
+
+ [last]
+ core/api.js # Another comment
+
+ [exclude]
+ 3rd/logger.js
+ exclude/this/dir
+
+ All headings are required.
+
+ The files listed in the `first` section will be forced to load
+ *before* all other files (in the order listed). The files in `last`
+ section will be forced to load *after* all the other files (in the
+ order listed).
+
+ The files list in the `exclude` section will not be imported.
+
+ Any text appearing after a # symbol indicates a comment.
+
+ """
+
+ def __init__(self, filename):
+ """
+ Parses the content of the named file and stores the values.
+ """
+ lines = [re.sub("#.*?$", "", line).strip() # Assumes end-of-line character is present
+ for line in open(filename)
+ if line.strip() and not line.strip().startswith("#")] # Skip blank lines and comments
+
+ self.forceFirst = lines[lines.index("[first]") + 1:lines.index("[last]")]
+
+ self.forceLast = lines[lines.index("[last]") + 1:lines.index("[include]")]
+ self.include = lines[lines.index("[include]") + 1:lines.index("[exclude]")]
+ self.exclude = lines[lines.index("[exclude]") + 1:]
+
+def undesired(filepath, excludes):
+ # exclude file if listed
+ exclude = filepath in excludes
+ if not exclude:
+ # check if directory is listed
+ for excludepath in excludes:
+ if not excludepath.endswith("/"):
+ excludepath += "/"
+ if filepath.startswith(excludepath):
+ exclude = True
+ break
+ return exclude
+
+
+def getNames (sourceDirectory, configFile = None):
+ return run(sourceDirectory, None, configFile, True)
+
+
+def run (sourceDirectory, outputFilename = None, configFile = None,
+ returnAsListOfNames = False):
+ cfg = None
+ if configFile:
+ cfg = Config(configFile)
+
+ allFiles = []
+
+ ## Find all the Javascript source files
+ for root, dirs, files in os.walk(sourceDirectory):
+ for filename in files:
+ if filename.endswith(SUFFIX_JAVASCRIPT) and not filename.startswith("."):
+ filepath = os.path.join(root, filename)[len(sourceDirectory)+1:]
+ filepath = filepath.replace("\\", "/")
+ if cfg and cfg.include:
+ if filepath in cfg.include or filepath in cfg.forceFirst:
+ allFiles.append(filepath)
+ elif (not cfg) or (not undesired(filepath, cfg.exclude)):
+ allFiles.append(filepath)
+
+ ## Header inserted at the start of each file in the output
+ HEADER = "/* " + "=" * 70 + "\n %s\n" + " " + "=" * 70 + " */\n\n"
+
+ files = {}
+
+ ## Import file source code
+ ## TODO: Do import when we walk the directories above?
+ for filepath in allFiles:
+ print "Importing: %s" % filepath
+ fullpath = os.path.join(sourceDirectory, filepath).strip()
+ content = open(fullpath, "U").read() # TODO: Ensure end of line @ EOF?
+ files[filepath] = SourceFile(filepath, content, cfg.exclude) # TODO: Chop path?
+
+ print
+
+ from toposort import toposort
+
+ complete = False
+ resolution_pass = 1
+
+ while not complete:
+ complete = True
+
+ ## Resolve the dependencies
+ print "Resolution pass %s... " % resolution_pass
+ resolution_pass += 1
+
+ for filepath, info in files.items():
+ for path in info.requires:
+ if not files.has_key(path):
+ complete = False
+ fullpath = os.path.join(sourceDirectory, path).strip()
+ if os.path.exists(fullpath):
+ print "Importing: %s" % path
+ content = open(fullpath, "U").read() # TODO: Ensure end of line @ EOF?
+ files[path] = SourceFile(path, content, cfg.exclude) # TODO: Chop path?
+ else:
+ raise MissingImport("File '%s' not found (required by '%s')." % (path, filepath))
+
+ # create dictionary of dependencies
+ dependencies = {}
+ for filepath, info in files.items():
+ dependencies[filepath] = info.requires
+
+ print "Sorting..."
+ order = toposort(dependencies) #[x for x in toposort(dependencies)]
+
+ ## Move forced first and last files to the required position
+ if cfg:
+ print "Re-ordering files..."
+ order = cfg.forceFirst + [item
+ for item in order
+ if ((item not in cfg.forceFirst) and
+ (item not in cfg.forceLast))] + cfg.forceLast
+
+ print
+ ## Output the files in the determined order
+ result = []
+
+ # Return as a list of filenames
+ if returnAsListOfNames:
+ for fp in order:
+ fName = os.path.normpath(os.path.join(sourceDirectory, fp)).replace("\\","/")
+ print "Append: ", fName
+ f = files[fp]
+ for fExclude in f.excludedFiles:
+ print " Required file \"%s\" is excluded." % fExclude
+ result.append(fName)
+ print "\nTotal files: %d " % len(result)
+ return result
+
+ # Return as merged source code
+ for fp in order:
+ f = files[fp]
+ print "Exporting: ", f.filepath
+ for fExclude in f.excludedFiles:
+ print " Required file \"%s\" is excluded." % fExclude
+ result.append(HEADER % f.filepath)
+ source = f.source
+ result.append(source)
+ if not source.endswith("\n"):
+ result.append("\n")
+
+ print "\nTotal files merged: %d " % len(files)
+
+ if outputFilename:
+ print "\nGenerating: %s" % (outputFilename)
+ open(outputFilename, "w").write("".join(result))
+ return "".join(result)
+
+if __name__ == "__main__":
+ import getopt
+
+ options, args = getopt.getopt(sys.argv[1:], "-c:")
+
+ try:
+ outputFilename = args[0]
+ except IndexError:
+ usage(sys.argv[0])
+ raise SystemExit
+ else:
+ sourceDirectory = args[1]
+ if not sourceDirectory:
+ usage(sys.argv[0])
+ raise SystemExit
+
+ configFile = None
+ if options and options[0][0] == "-c":
+ configFile = options[0][1]
+ print "Parsing configuration file: %s" % filename
+
+ run( sourceDirectory, outputFilename, configFile )
diff --git a/misc/openlayers/tools/minimize.py b/misc/openlayers/tools/minimize.py
new file mode 100644
index 0000000..5358bd5
--- /dev/null
+++ b/misc/openlayers/tools/minimize.py
@@ -0,0 +1,47 @@
+# Minimal Python Minimizer
+# Copyright 2008, Christopher Schmidt
+# Released under the MIT License
+#
+# Taken from: http://svn.crschmidt.net/personal/python/minimize.py
+# $Id: minimize.py 6 2008-01-03 06:33:35Z crschmidt $
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import re
+
+def strip_comments_helper(data):
+ """remove all /* */ format comments and surrounding whitespace."""
+ p = re.compile(r'[\s]*/\*.*?\*/[\s]*', re.DOTALL)
+ return p.sub('',data)
+
+def minimize(data, exclude=None):
+ """Central function call. This will call all other compression
+ functions. To add further compression algorithms, simply add
+ functions whose names end in _helper which take a string as input
+ and return a more compressed string as output."""
+ for key, item in globals().iteritems():
+ if key.endswith("_helper"):
+ func_key = key[:-7]
+ if not exclude or not func_key in exclude:
+ data = item(data)
+ return data
+
+if __name__ == "__main__":
+ import sys
+ print minimize(open(sys.argv[1]).read())
diff --git a/misc/openlayers/tools/oldot.py b/misc/openlayers/tools/oldot.py
new file mode 100644
index 0000000..396fb17
--- /dev/null
+++ b/misc/openlayers/tools/oldot.py
@@ -0,0 +1,43 @@
+import re
+import os
+def run():
+ sourceDirectory = "../lib/OpenLayers"
+ allFiles = []
+ SUFFIX_JAVASCRIPT = ".js"
+ ## Find all the Javascript source files
+ for root, dirs, files in os.walk(sourceDirectory):
+ for filename in files:
+ if filename.endswith(SUFFIX_JAVASCRIPT) and not filename.startswith("."):
+ filepath = os.path.join(root, filename)[len(sourceDirectory)+1:]
+ filepath = filepath.replace("\\", "/")
+ data = open(os.path.join(sourceDirectory, filepath)).read()
+ parents = re.search("OpenLayers.Class\((.*?){", data,
+ re.DOTALL)
+ if parents:
+ parents = [x.strip() for x in parents.group(1).strip().strip(",").split(",")]
+ else:
+ parents = []
+ cls = "OpenLayers.%s" % filepath.strip(".js").replace("/", ".")
+ allFiles.append([cls, parents])
+ return allFiles
+print """
+digraph name {
+ fontname = "Helvetica"
+ fontsize = 8
+ K = 0.6
+
+ node [
+ fontname = "Helvetica"
+ fontsize = 8
+ shape = "plaintext"
+ ]
+"""
+
+for i in run():
+ print i[0].replace(".", "_")
+ for item in i[1]:
+ if not item: continue
+ print "%s -> %s" % (i[0].replace(".","_"), item.replace(".", "_"))
+ print "; "
+
+print """}"""
diff --git a/misc/openlayers/tools/release.sh b/misc/openlayers/tools/release.sh
new file mode 100755
index 0000000..125065b
--- /dev/null
+++ b/misc/openlayers/tools/release.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+#
+#
+# Usage:
+# $ ./release.sh <release_number>
+#
+# Example:
+# $ ./release.sh 2.12-rc7
+#
+# This script should be run on the www.openlayers.org server.
+#
+# What the script does:
+#
+# 1. Download release tarball from from GitHub.
+# 2. Create builds using the Closure Compiler.
+# 3. Run the exampleparser.py script to create the examples index.
+# 4. Run csstidy for each CSS file in theme/default.
+# 5. Publish builds and resources on api.openlayers.org.
+# 6. Build the API docs.
+# 7. Create release archives
+# 8. Make the release archives available on openlayers.org/downloads.
+#
+#
+
+VERSION=$1
+
+wget -c http://closure-compiler.googlecode.com/files/compiler-latest.zip
+unzip compiler-latest.zip
+
+wget -O release-${VERSION}.tar.gz https://github.com/openlayers/openlayers/tarball/release-${VERSION}
+tar xvzf release-${VERSION}.tar.gz
+mv openlayers-openlayers-* OpenLayers-${VERSION}
+cd OpenLayers-${VERSION}/build
+
+mv ../../compiler.jar ../tools/closure-compiler.jar
+./build.py -c closure full
+./build.py -c closure mobile OpenLayers.mobile.js
+./build.py -c closure light OpenLayers.light.js
+./build.py -c none full OpenLayers.debug.js
+./build.py -c none mobile OpenLayers.mobile.debug.js
+./build.py -c none light OpenLayers.light.debug.js
+mv OpenLayers*.js ../
+rm ../tools/closure-compiler.jar
+
+cd ..
+cd tools
+python exampleparser.py
+cd ..
+for i in google ie6-style style style.mobile; do
+ csstidy theme/default/$i.css --template=highest theme/default/$i.tidy.css
+done
+
+mkdir -p doc/devdocs
+mkdir -p doc/apidocs
+rm tools/*.pyc
+
+mkdir -p /osgeo/openlayers/sites/openlayers.org/api/$VERSION
+cp OpenLayers*.js /osgeo/openlayers/sites/openlayers.org/api/$VERSION
+cp -a img/ /osgeo/openlayers/sites/openlayers.org/api/$VERSION
+cp -a theme/ /osgeo/openlayers/sites/openlayers.org/api/$VERSION
+
+cd ..
+
+naturaldocs -i OpenLayers-$VERSION/lib -o HTML OpenLayers-$VERSION/doc/devdocs -p OpenLayers-$VERSION/doc_config -s Small OL
+naturaldocs -i OpenLayers-$VERSION/lib -o HTML OpenLayers-$VERSION/doc/apidocs -p OpenLayers-$VERSION/apidoc_config -s Small OL
+
+tar cvfz OpenLayers-$VERSION.tar.gz OpenLayers-$VERSION/
+zip -9r OpenLayers-$VERSION.zip OpenLayers-$VERSION/
+
+cp OpenLayers-$VERSION.* /osgeo/openlayers/sites/openlayers.org/download
diff --git a/misc/openlayers/tools/shrinksafe.py b/misc/openlayers/tools/shrinksafe.py
new file mode 100755
index 0000000..a42e941
--- /dev/null
+++ b/misc/openlayers/tools/shrinksafe.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+#
+# Script to provide a wrapper around the ShrinkSafe "web service"
+# <http://shrinksafe.dojotoolkit.org/>
+#
+
+#
+# We use this script for two reasons:
+#
+# * This avoids having to install and configure Java and the standalone
+# ShrinkSafe utility.
+#
+# * The current ShrinkSafe standalone utility was broken when we last
+# used it.
+#
+
+import sys
+
+import urllib
+import urllib2
+
+URL_SHRINK_SAFE = "http://shrinksafe.dojotoolkit.org/shrinksafe.php"
+
+# This would normally be dynamically generated:
+BOUNDARY_MARKER = "---------------------------72288400411964641492083565382"
+
+if __name__ == "__main__":
+ ## Grab the source code
+ try:
+ sourceFilename = sys.argv[1]
+ except:
+ print "Usage: %s (<source filename>|-)" % sys.argv[0]
+ raise SystemExit
+
+ if sourceFilename == "-":
+ sourceCode = sys.stdin.read()
+ sourceFilename = "stdin.js"
+ else:
+ sourceCode = open(sourceFilename).read()
+
+ ## Create the request replicating posting of the form from the web page
+ request = urllib2.Request(url=URL_SHRINK_SAFE)
+ request.add_header("Content-Type",
+ "multipart/form-data; boundary=%s" % BOUNDARY_MARKER)
+ request.add_data("""
+--%s
+Content-Disposition: form-data; name="shrinkfile[]"; filename="%s"
+Content-Type: application/x-javascript
+
+%s
+""" % (BOUNDARY_MARKER, sourceFilename, sourceCode))
+
+ ## Deliver the result
+ print urllib2.urlopen(request).read(),
diff --git a/misc/openlayers/tools/toposort.py b/misc/openlayers/tools/toposort.py
new file mode 100644
index 0000000..ba586ef
--- /dev/null
+++ b/misc/openlayers/tools/toposort.py
@@ -0,0 +1,35 @@
+"""
+toposort.py
+Sorts dictionary keys based on lists of dependencies.
+"""
+
+class MissingDependency(Exception):
+ """Exception raised when a listed dependency is not in the dictionary."""
+
+class Sorter(object):
+ def __init__(self, dependencies):
+ self.dependencies = dependencies
+ self.visited = set()
+ self.sorted = ()
+
+ def sort(self):
+ for key in self.dependencies:
+ self._visit(key)
+ return self.sorted
+
+ def _visit(self, key):
+ if key not in self.visited:
+ self.visited.add(key)
+ if not self.dependencies.has_key(key):
+ raise MissingDependency(key)
+ for depends in self.dependencies[key]:
+ self._visit(depends)
+ self.sorted += (key,)
+
+def toposort(dependencies):
+ """Returns a tuple of the dependencies dictionary keys sorted by entries
+ in the dependency lists. Given circular dependencies, sort will impose
+ an order. Raises MissingDependency if a key is not found.
+ """
+ s = Sorter(dependencies)
+ return s.sort()
diff --git a/misc/openlayers/tools/uglify_js.py b/misc/openlayers/tools/uglify_js.py
new file mode 100644
index 0000000..50ef098
--- /dev/null
+++ b/misc/openlayers/tools/uglify_js.py
@@ -0,0 +1,35 @@
+"""Utility to use the Uglify JS Compiler CLI from Python."""
+
+import logging
+import subprocess
+
+
+def check_available():
+ """ Returns whether the uglify-js tool is available. """
+ subprocess.check_output(['which', 'uglifyjs'])
+
+
+def compile(source_paths, flags=None):
+ """
+ Prepares command-line call to uglify-js compiler.
+
+ Args:
+ source_paths: Source paths to build, in order.
+ flags: A list of additional flags to pass on to uglify-js.
+
+ Returns:
+ The compiled source, as a string, or None if compilation failed.
+ """
+
+ args = ['uglifyjs']
+ args.extend(source_paths)
+ args.extend(['-c', '-m'])
+ if flags:
+ args += flags
+
+ logging.info('Compiling with the following command: %s', ' '.join(args))
+
+ try:
+ return subprocess.check_output(args)
+ except subprocess.CalledProcessError:
+ return
diff --git a/misc/openlayers/tools/update_dev_dir.sh b/misc/openlayers/tools/update_dev_dir.sh
new file mode 100755
index 0000000..5bca2e6
--- /dev/null
+++ b/misc/openlayers/tools/update_dev_dir.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+# check to see if the hosted examples or API docs need an update
+cd /osgeo/openlayers/repos/openlayers
+REMOTE_HEAD=`git ls-remote https://github.com/openlayers/openlayers/ | grep HEAD | awk '{print $1}'`
+LOCAL_HEAD=`git rev-parse HEAD`
+
+# if there's something different in the remote, update and build
+if [ ! o$REMOTE_HEAD = o$LOCAL_HEAD ]; then
+
+ git checkout master
+ git clean -f
+ git pull origin master
+
+ # copy everything over to the dev dir within the website (keep the clone clean)
+ rsync -r --exclude=.git . /osgeo/openlayers/sites/openlayers.org/dev
+
+ # make examples use built lib
+ cd /osgeo/openlayers/sites/openlayers.org/dev/tools
+
+ python exampleparser.py /osgeo/openlayers/repos/openlayers/examples /osgeo/openlayers/sites/openlayers.org/dev/examples
+
+ if [ ! -f closure-compiler.jar ]; then
+ wget -c http://closure-compiler.googlecode.com/files/compiler-latest.zip
+ unzip compiler-latest.zip
+ mv compiler.jar closure-compiler.jar
+ fi
+
+ cd /osgeo/openlayers/sites/openlayers.org/dev/build
+ ./build.py -c closure tests.cfg
+ ./build.py -c closure mobile.cfg OpenLayers.mobile.js
+ ./build.py -c closure light.cfg OpenLayers.light.js
+ ./build.py -c none tests.cfg OpenLayers.debug.js
+ ./build.py -c none mobile.cfg OpenLayers.mobile.debug.js
+ ./build.py -c none light.cfg OpenLayers.light.debug.js
+ cp OpenLayers*.js ..
+
+ cd /osgeo/openlayers/sites/openlayers.org/dev
+ sed -i -e 's!../lib/OpenLayers.js?mobile!../OpenLayers.mobile.js!' examples/*.html
+ sed -i -e 's!../lib/OpenLayers.js!../OpenLayers.js!' examples/*.html
+
+ # update the API docs
+ if [ ! -d /osgeo/openlayers/sites/dev.openlayers.org/apidocs ]; then
+ mkdir -p /osgeo/openlayers/sites/dev.openlayers.org/apidocs
+ fi
+ if [ ! -d /osgeo/openlayers/sites/dev.openlayers.org/docs ]; then
+ mkdir -p /osgeo/openlayers/sites/dev.openlayers.org/docs
+ fi
+ naturaldocs --input lib --output HTML /osgeo/openlayers/sites/dev.openlayers.org/apidocs -p apidoc_config -s Default OL
+ naturaldocs --input lib --output HTML /osgeo/openlayers/sites/dev.openlayers.org/docs -p doc_config -s Default OL
+
+fi
+
+# check to see if the website needs an update
+cd /osgeo/openlayers/repos/website
+REMOTE_HEAD=`git ls-remote https://github.com/openlayers/website/ | grep HEAD | awk '{print $1}'`
+LOCAL_HEAD=`git rev-parse HEAD`
+
+# if there's something different in the remote, update the clone
+if [ ! o$REMOTE_HEAD = o$LOCAL_HEAD ]; then
+
+ git checkout master
+ git clean -f
+ git pull origin master
+
+ # copy everything over to the website dir (keep the clone clean)
+ # can't use --delete here because of nested dev dir from above
+ rsync -r --exclude=.git . /osgeo/openlayers/sites/openlayers.org
+
+fi
+
+# check to see if prose docs need an update
+cd /osgeo/openlayers/repos/docs
+REMOTE_HEAD=`git ls-remote https://github.com/openlayers/docs/ | grep HEAD | awk '{print $1}'`
+LOCAL_HEAD=`git rev-parse HEAD`
+
+# if there's something different in the remote, update the clone
+if [ ! o$REMOTE_HEAD = o$LOCAL_HEAD ]; then
+
+ git checkout master
+ git clean -f
+ git pull origin master
+
+ mkdir -p /osgeo/openlayers/sites/docs.openlayers.org /tmp/ol/docs/build/doctrees
+ sphinx-build -b html -d /tmp/ol/docs/build/doctrees . /osgeo/openlayers/sites/docs.openlayers.org
+
+fi
+
+## UPDATES FROM THE OLD SVN REPO
+
+# Get current 'Last Changed Rev'
+SVNREV=`svn info http://svn.openlayers.org/ | grep 'Revision' | awk '{print $2}'`
+
+# Get the last svn rev
+touch /tmp/ol_svn_rev
+OLD_SVNREV="o`cat /tmp/ol_svn_rev`"
+
+# If they're not equal, do some work.
+if [ ! o$SVNREV = $OLD_SVNREV ]; then
+ svn up /osgeo/openlayers/repos/old_svn_repo/
+ # Record the revision
+ echo -n $SVNREV > /tmp/ol_svn_rev
+fi